[android-platform-tools-base] 04/30: Imported Upstream version 2.0.0
Kai-Chung Yan
seamlik-guest at moszumanska.debian.org
Fri Jul 22 10:33:43 UTC 2016
This is an automated email from the git hooks/post-receive script.
seamlik-guest pushed a commit to branch master
in repository android-platform-tools-base.
commit 26a254ab8976e84abc83b4c28c18a58779716919
Author: Kai-Chung Yan <seamlikok at gmail.com>
Date: Tue Jul 19 20:04:31 2016 +0800
Imported Upstream version 2.0.0
---
adt.iml | 31 +
{base/annotations => annotations}/.classpath | 0
{base/annotations => annotations}/.gitignore | 0
{base/annotations => annotations}/.project | 0
{base/annotations => annotations}/NOTICE | 0
.../android-annotations.iml | 0
annotations/build.gradle | 22 +
.../main/java/com/android/annotations/NonNull.java | 0
.../com/android/annotations/NonNullByDefault.java | 0
.../java/com/android/annotations/Nullable.java | 0
.../com/android/annotations/VisibleForTesting.java | 0
.../android/annotations/concurrency/GuardedBy.java | 0
.../android/annotations/concurrency/Immutable.java | 0
{base/apps => apps}/.gitignore | 0
apps/DeviceConfig/.gitignore | 6 +
.../DeviceConfig/app/.gitignore | 0
apps/DeviceConfig/app/build.gradle | 12 +
apps/DeviceConfig/app/src/main/AndroidManifest.xml | 30 +
.../android/deviceconfig/ConfigGenerator.java | 893 +++++
.../example/android/deviceconfig/MyActivity.java | 191 ++
.../app/src/main}/res/drawable-hdpi/icon.png | Bin
.../app/src/main}/res/drawable-ldpi/icon.png | Bin
.../app/src/main}/res/drawable-mdpi/icon.png | Bin
.../app/src/main}/res/drawable-nodpi/icon.png | Bin
.../app/src/main}/res/drawable/icon.png | Bin
.../DeviceConfig/app/src/main}/res/layout/main.xml | 0
.../app/src/main}/res/values-12key/strings.xml | 0
.../app/src/main}/res/values-car/strings.xml | 0
.../app/src/main}/res/values-desk/strings.xml | 0
.../app/src/main}/res/values-dpad/strings.xml | 0
.../app/src/main}/res/values-finger/strings.xml | 0
.../app/src/main}/res/values-hdpi/strings.xml | 0
.../src/main}/res/values-keysexposed/strings.xml | 0
.../src/main}/res/values-keyshidden/strings.xml | 0
.../app/src/main}/res/values-keyssoft/strings.xml | 0
.../app/src/main}/res/values-land/strings.xml | 0
.../app/src/main}/res/values-large/strings.xml | 0
.../app/src/main}/res/values-ldpi/strings.xml | 0
.../app/src/main}/res/values-long/strings.xml | 0
.../app/src/main}/res/values-mdpi/strings.xml | 0
.../src/main}/res/values-navexposed/strings.xml | 0
.../app/src/main}/res/values-navhidden/strings.xml | 0
.../app/src/main}/res/values-night/strings.xml | 0
.../app/src/main}/res/values-nodpi/strings.xml | 0
.../app/src/main}/res/values-nokeys/strings.xml | 0
.../app/src/main}/res/values-nonav/strings.xml | 0
.../app/src/main}/res/values-normal/strings.xml | 0
.../app/src/main}/res/values-notlong/strings.xml | 0
.../app/src/main}/res/values-notnight/strings.xml | 0
.../app/src/main}/res/values-notouch/strings.xml | 0
.../app/src/main}/res/values-port/strings.xml | 0
.../app/src/main}/res/values-qwerty/strings.xml | 0
.../app/src/main}/res/values-small/strings.xml | 0
.../app/src/main}/res/values-stylus/strings.xml | 0
.../app/src/main}/res/values-trackball/strings.xml | 0
.../app/src/main}/res/values-tvdpi/strings.xml | 0
.../app/src/main}/res/values-v1/strings.xml | 0
.../app/src/main}/res/values-v14/strings.xml | 0
.../app/src/main}/res/values-v2/strings.xml | 0
.../app/src/main}/res/values-v3/strings.xml | 0
.../app/src/main}/res/values-v4/strings.xml | 0
.../app/src/main}/res/values-v5/strings.xml | 0
.../app/src/main}/res/values-v6/strings.xml | 0
.../app/src/main}/res/values-v7/strings.xml | 0
.../app/src/main}/res/values-v8/strings.xml | 0
.../app/src/main}/res/values-v9/strings.xml | 0
.../app/src/main}/res/values-wheel/strings.xml | 0
.../app/src/main}/res/values-xhdpi/strings.xml | 0
.../app/src/main}/res/values-xlarge/strings.xml | 0
.../app/src/main}/res/values/strings.xml | 0
apps/DeviceConfig/build.gradle | 15 +
apps/DeviceConfig/settings.gradle | 1 +
{base/apps => apps}/NotificationStudio/.classpath | 0
{base/apps => apps}/NotificationStudio/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../NotificationStudio/AndroidManifest.xml | 0
.../NotificationStudio/project.properties | 0
.../res/drawable-hdpi/android_logo.gif | Bin
.../ic_notification_multiple_mail_holo_dark.png | Bin
.../res/drawable-hdpi/romain.jpg | Bin
.../res/drawable-nodpi/icon_bg.xml | 0
.../res/drawable-nodpi/romainguy_rockaway.jpg | Bin
.../res/layout-v14/boolean_editor.xml | 0
.../NotificationStudio/res/layout-v16/studio.xml | 0
.../res/layout-w801dp/studio.xml | 0
.../res/layout/boolean_editor.xml | 0
.../NotificationStudio/res/layout/divider.xml | 0
.../res/layout/editable_item.xml | 0
.../NotificationStudio/res/layout/editors.xml | 0
.../NotificationStudio/res/layout/preview.xml | 0
.../NotificationStudio/res/layout/studio.xml | 0
.../NotificationStudio/res/menu/action_bar.xml | 0
.../res/values-sw600dp/dimens.xml | 0
.../res/values-sw720dp/dimens.xml | 0
.../NotificationStudio/res/values-v11/colors.xml | 0
.../NotificationStudio/res/values-v11/dimens.xml | 0
.../NotificationStudio/res/values/colors.xml | 0
.../NotificationStudio/res/values/dimens.xml | 0
.../NotificationStudio/res/values/strings.xml | 0
.../notificationstudio/MaxHeightScrollView.java | 0
.../NotificationStudioActivity.java | 0
.../notificationstudio/action/ShareCodeAction.java | 0
.../action/ShareMockupAction.java | 0
.../notificationstudio/editor/BitmapEditor.java | 0
.../notificationstudio/editor/BooleanEditor.java | 0
.../notificationstudio/editor/DateTimeEditor.java | 0
.../notificationstudio/editor/DropDownEditor.java | 0
.../android/notificationstudio/editor/Editors.java | 0
.../notificationstudio/editor/IconEditor.java | 0
.../notificationstudio/editor/IntEditor.java | 0
.../notificationstudio/editor/LinesEditor.java | 0
.../notificationstudio/editor/TextEditor.java | 0
.../generator/CodeGenerator.java | 0
.../generator/NotificationGenerator.java | 0
.../notificationstudio/model/EditableItem.java | 0
.../model/EditableItemConstants.java | 0
{base/apps => apps}/SdkController/.classpath | 0
{base/apps => apps}/SdkController/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../SdkController/AndroidManifest.xml | 0
.../apps => apps}/SdkController/Implementation.txt | 0
.../SdkController/assets/intro_help.html | 0
.../SdkController}/proguard-project.txt | 0
.../apps => apps}/SdkController/project.properties | 0
.../res/drawable-hdpi/ic_launcher.png | Bin
.../res/drawable-ldpi/ic_launcher.png | Bin
.../res/drawable-mdpi/ic_launcher.png | Bin
.../res/drawable-xhdpi/ic_launcher.png | Bin
.../SdkController/res/layout-land/sensors.xml | 0
.../SdkController/res/layout/main.xml | 0
.../SdkController/res/layout/multitouch.xml | 0
.../SdkController/res/layout/sensor_row.xml | 0
.../SdkController/res/layout/sensors.xml | 0
.../SdkController/res/values-v11/styles_v11.xml | 0
.../SdkController/res/values/strings.xml | 0
.../SdkController/res/values/styles.xml | 0
.../activities/BaseBindingActivity.java | 0
.../sdkcontroller/activities/MainActivity.java | 0
.../activities/MultiTouchActivity.java | 0
.../sdkcontroller/activities/SensorActivity.java | 0
.../sdkcontroller/handlers/MultiTouchChannel.java | 0
.../sdkcontroller/handlers/SensorChannel.java | 0
.../android/tools/sdkcontroller/lib/Channel.java | 0
.../tools/sdkcontroller/lib/Connection.java | 0
.../tools/sdkcontroller/lib/ProtocolConstants.java | 0
.../android/tools/sdkcontroller/lib/Socket.java | 0
.../sdkcontroller/service/ControllerService.java | 0
.../tools/sdkcontroller/utils/ApiHelper.java | 0
.../tools/sdkcontroller/utils/ApiHelper_11.java | 0
.../tools/sdkcontroller/views/MultiTouchView.java | 0
{base/asset-studio => asset-studio}/.classpath | 0
{base/asset-studio => asset-studio}/.gitignore | 0
{base/asset-studio => asset-studio}/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
{base/asset-studio => asset-studio}/NOTICE | 0
.../assetstudio-base.iml | 0
.../asset-studio => asset-studio}/assetstudio.iml | 0
{base/asset-studio => asset-studio}/build.gradle | 0
.../assetstudiolib/ActionBarIconGenerator.java | 0
.../android/assetstudiolib/GraphicGenerator.java | 0
.../assetstudiolib/GraphicGeneratorContext.java | 0
.../assetstudiolib/LauncherIconGenerator.java | 0
.../android/assetstudiolib/MenuIconGenerator.java | 0
.../assetstudiolib/NotificationIconGenerator.java | 0
.../android/assetstudiolib/TabIconGenerator.java | 0
.../com/android/assetstudiolib/TextRenderUtil.java | 0
.../assetstudiolib/VectorIconGenerator.java | 0
.../images/clipart/big/1-navigation-accept.png | Bin
.../java/images/clipart/big/1-navigation-back.png | Bin
.../images/clipart/big/1-navigation-cancel.png | Bin
.../images/clipart/big/1-navigation-collapse.png | Bin
.../images/clipart/big/1-navigation-drawer.png | Bin
.../images/clipart/big/1-navigation-expand.png | Bin
.../images/clipart/big/1-navigation-forward.png | Bin
.../images/clipart/big/1-navigation-next-item.png | Bin
.../clipart/big/1-navigation-previous-item.png | Bin
.../images/clipart/big/1-navigation-refresh.png | Bin
.../clipart/big/10-device-access-accounts.png | Bin
.../clipart/big/10-device-access-add-alarm.png | Bin
.../images/clipart/big/10-device-access-alarms.png | Bin
.../clipart/big/10-device-access-battery.png | Bin
.../clipart/big/10-device-access-bightness-low.png | Bin
.../big/10-device-access-bluetooth-connected.png | Bin
.../big/10-device-access-bluetooth-searching.png | Bin
.../clipart/big/10-device-access-bluetooth.png | Bin
.../big/10-device-access-brightness-auto.png | Bin
.../big/10-device-access-brightness-high.png | Bin
.../big/10-device-access-brightness-medium.png | Bin
.../images/clipart/big/10-device-access-call.png | Bin
.../images/clipart/big/10-device-access-camera.png | Bin
.../clipart/big/10-device-access-data-usage.png | Bin
.../clipart/big/10-device-access-dial-pad.png | Bin
.../clipart/big/10-device-access-end-call.png | Bin
.../big/10-device-access-flash-automatic.png | Bin
.../clipart/big/10-device-access-flash-off.png | Bin
.../clipart/big/10-device-access-flash-on.png | Bin
.../big/10-device-access-location-found.png | Bin
.../clipart/big/10-device-access-location-off.png | Bin
.../big/10-device-access-location-searching.png | Bin
.../clipart/big/10-device-access-mic-muted.png | Bin
.../images/clipart/big/10-device-access-mic.png | Bin
.../clipart/big/10-device-access-network-cell.png | Bin
.../clipart/big/10-device-access-network-wifi.png | Bin
.../clipart/big/10-device-access-new-account.png | Bin
.../clipart/big/10-device-access-not-secure.png | Bin
.../clipart/big/10-device-access-ring-volume.png | Bin
...10-device-access-screen-locked-to-landscape.png | Bin
.../10-device-access-screen-locked-to-portrait.png | Bin
.../big/10-device-access-screen-rotation.png | Bin
.../clipart/big/10-device-access-sd-storage.png | Bin
.../images/clipart/big/10-device-access-secure.png | Bin
.../clipart/big/10-device-access-storage.png | Bin
.../clipart/big/10-device-access-switch-camera.png | Bin
.../clipart/big/10-device-access-switch-video.png | Bin
.../images/clipart/big/10-device-access-time.png | Bin
.../images/clipart/big/10-device-access-usb.png | Bin
.../images/clipart/big/10-device-access-video.png | Bin
.../clipart/big/10-device-access-volume-muted.png | Bin
.../clipart/big/10-device-access-volume-on.png | Bin
.../big/11-alerts-and-states-airplane-mode-off.png | Bin
.../big/11-alerts-and-states-airplane-mode-on.png | Bin
.../clipart/big/11-alerts-and-states-error.png | Bin
.../clipart/big/11-alerts-and-states-warning.png | Bin
.../images/clipart/big/12-hardware-computer.png | Bin
.../java/images/clipart/big/12-hardware-dock.png | Bin
.../images/clipart/big/12-hardware-gamepad.png | Bin
.../images/clipart/big/12-hardware-headphones.png | Bin
.../images/clipart/big/12-hardware-headset.png | Bin
.../images/clipart/big/12-hardware-keyboard.png | Bin
.../java/images/clipart/big/12-hardware-mouse.png | Bin
.../java/images/clipart/big/12-hardware-phone.png | Bin
.../java/images/clipart/big/2-action-about.png | Bin
.../main/java/images/clipart/big/2-action-help.png | Bin
.../java/images/clipart/big/2-action-overflow.png | Bin
.../java/images/clipart/big/2-action-search.png | Bin
.../java/images/clipart/big/2-action-settings.png | Bin
.../main/java/images/clipart/big/3-rating-bad.png | Bin
.../java/images/clipart/big/3-rating-favorite.png | Bin
.../main/java/images/clipart/big/3-rating-good.png | Bin
.../images/clipart/big/3-rating-half-important.png | Bin
.../java/images/clipart/big/3-rating-important.png | Bin
.../images/clipart/big/3-rating-not-important.png | Bin
.../images/clipart/big/4-collections-cloud.png | Bin
.../clipart/big/4-collections-collection.png | Bin
.../clipart/big/4-collections-go-to-today.png | Bin
.../images/clipart/big/4-collections-labels.png | Bin
.../images/clipart/big/4-collections-new-label.png | Bin
.../clipart/big/4-collections-sort-by-size.png | Bin
.../clipart/big/4-collections-view-as-grid.png | Bin
.../clipart/big/4-collections-view-as-list.png | Bin
.../images/clipart/big/5-content-attachment.png | Bin
.../images/clipart/big/5-content-backspace.png | Bin
.../java/images/clipart/big/5-content-copy.png | Bin
.../main/java/images/clipart/big/5-content-cut.png | Bin
.../java/images/clipart/big/5-content-discard.png | Bin
.../java/images/clipart/big/5-content-edit.png | Bin
.../java/images/clipart/big/5-content-email.png | Bin
.../java/images/clipart/big/5-content-event.png | Bin
.../images/clipart/big/5-content-import-export.png | Bin
.../java/images/clipart/big/5-content-merge.png | Bin
.../clipart/big/5-content-new-attachment.png | Bin
.../images/clipart/big/5-content-new-email.png | Bin
.../images/clipart/big/5-content-new-event.png | Bin
.../images/clipart/big/5-content-new-picture.png | Bin
.../main/java/images/clipart/big/5-content-new.png | Bin
.../java/images/clipart/big/5-content-paste.png | Bin
.../java/images/clipart/big/5-content-picture.png | Bin
.../java/images/clipart/big/5-content-read.png | Bin
.../java/images/clipart/big/5-content-remove.png | Bin
.../java/images/clipart/big/5-content-save.png | Bin
.../images/clipart/big/5-content-select-all.png | Bin
.../java/images/clipart/big/5-content-split.png | Bin
.../java/images/clipart/big/5-content-undo.png | Bin
.../java/images/clipart/big/5-content-unread.png | Bin
.../java/images/clipart/big/6-social-add-group.png | Bin
.../images/clipart/big/6-social-add-person.png | Bin
.../java/images/clipart/big/6-social-cc-bcc.png | Bin
.../main/java/images/clipart/big/6-social-chat.png | Bin
.../java/images/clipart/big/6-social-forward.png | Bin
.../java/images/clipart/big/6-social-group.png | Bin
.../java/images/clipart/big/6-social-person.png | Bin
.../java/images/clipart/big/6-social-reply-all.png | Bin
.../java/images/clipart/big/6-social-reply.png | Bin
.../java/images/clipart/big/6-social-send-now.png | Bin
.../java/images/clipart/big/6-social-share.png | Bin
.../images/clipart/big/7-location-directions.png | Bin
.../java/images/clipart/big/7-location-map.png | Bin
.../java/images/clipart/big/7-location-place.png | Bin
.../images/clipart/big/7-location-web-site.png | Bin
.../main/java/images/clipart/big/8-images-crop.png | Bin
.../images/clipart/big/8-images-rotate-left.png | Bin
.../images/clipart/big/8-images-rotate-right.png | Bin
.../java/images/clipart/big/8-images-slideshow.png | Bin
.../java/images/clipart/big/9-av-add-to-queue.png | Bin
.../main/java/images/clipart/big/9-av-download.png | Bin
.../java/images/clipart/big/9-av-fast-forward.png | Bin
.../java/images/clipart/big/9-av-full-screen.png | Bin
.../clipart/big/9-av-make-available-offline.png | Bin
.../src/main/java/images/clipart/big/9-av-next.png | Bin
.../images/clipart/big/9-av-pause-over-video.png | Bin
.../main/java/images/clipart/big/9-av-pause.png | Bin
.../images/clipart/big/9-av-play-over-video.png | Bin
.../src/main/java/images/clipart/big/9-av-play.png | Bin
.../main/java/images/clipart/big/9-av-previous.png | Bin
.../main/java/images/clipart/big/9-av-repeat.png | Bin
.../main/java/images/clipart/big/9-av-replay.png | Bin
.../clipart/big/9-av-return-from-full-screen.png | Bin
.../main/java/images/clipart/big/9-av-rewind.png | Bin
.../main/java/images/clipart/big/9-av-shuffle.png | Bin
.../src/main/java/images/clipart/big/9-av-stop.png | Bin
.../main/java/images/clipart/big/9-av-upload.png | Bin
.../src/main/java/images/clipart/big/android.png | Bin
.../images/clipart/small/1-navigation-accept.png | Bin
.../images/clipart/small/1-navigation-back.png | Bin
.../images/clipart/small/1-navigation-cancel.png | Bin
.../images/clipart/small/1-navigation-collapse.png | Bin
.../images/clipart/small/1-navigation-drawer.png | Bin
.../images/clipart/small/1-navigation-expand.png | Bin
.../images/clipart/small/1-navigation-forward.png | Bin
.../clipart/small/1-navigation-next-item.png | Bin
.../clipart/small/1-navigation-previous-item.png | Bin
.../images/clipart/small/1-navigation-refresh.png | Bin
.../clipart/small/10-device-access-accounts.png | Bin
.../clipart/small/10-device-access-add-alarm.png | Bin
.../clipart/small/10-device-access-alarms.png | Bin
.../clipart/small/10-device-access-battery.png | Bin
.../small/10-device-access-bightness-low.png | Bin
.../small/10-device-access-bluetooth-connected.png | Bin
.../small/10-device-access-bluetooth-searching.png | Bin
.../clipart/small/10-device-access-bluetooth.png | Bin
.../small/10-device-access-brightness-auto.png | Bin
.../small/10-device-access-brightness-high.png | Bin
.../small/10-device-access-brightness-medium.png | Bin
.../images/clipart/small/10-device-access-call.png | Bin
.../clipart/small/10-device-access-camera.png | Bin
.../clipart/small/10-device-access-data-usage.png | Bin
.../clipart/small/10-device-access-dial-pad.png | Bin
.../clipart/small/10-device-access-end-call.png | Bin
.../small/10-device-access-flash-automatic.png | Bin
.../clipart/small/10-device-access-flash-off.png | Bin
.../clipart/small/10-device-access-flash-on.png | Bin
.../small/10-device-access-location-found.png | Bin
.../small/10-device-access-location-off.png | Bin
.../small/10-device-access-location-searching.png | Bin
.../clipart/small/10-device-access-mic-muted.png | Bin
.../images/clipart/small/10-device-access-mic.png | Bin
.../small/10-device-access-network-cell.png | Bin
.../small/10-device-access-network-wifi.png | Bin
.../clipart/small/10-device-access-new-account.png | Bin
.../clipart/small/10-device-access-not-secure.png | Bin
.../clipart/small/10-device-access-ring-volume.png | Bin
...10-device-access-screen-locked-to-landscape.png | Bin
.../10-device-access-screen-locked-to-portrait.png | Bin
.../small/10-device-access-screen-rotation.png | Bin
.../clipart/small/10-device-access-sd-storage.png | Bin
.../clipart/small/10-device-access-secure.png | Bin
.../clipart/small/10-device-access-storage.png | Bin
.../small/10-device-access-switch-camera.png | Bin
.../small/10-device-access-switch-video.png | Bin
.../images/clipart/small/10-device-access-time.png | Bin
.../images/clipart/small/10-device-access-usb.png | Bin
.../clipart/small/10-device-access-video.png | Bin
.../small/10-device-access-volume-muted.png | Bin
.../clipart/small/10-device-access-volume-on.png | Bin
.../11-alerts-and-states-airplane-mode-off.png | Bin
.../11-alerts-and-states-airplane-mode-on.png | Bin
.../clipart/small/11-alerts-and-states-error.png | Bin
.../clipart/small/11-alerts-and-states-warning.png | Bin
.../images/clipart/small/12-hardware-computer.png | Bin
.../java/images/clipart/small/12-hardware-dock.png | Bin
.../images/clipart/small/12-hardware-gamepad.png | Bin
.../clipart/small/12-hardware-headphones.png | Bin
.../images/clipart/small/12-hardware-headset.png | Bin
.../images/clipart/small/12-hardware-keyboard.png | Bin
.../images/clipart/small/12-hardware-mouse.png | Bin
.../images/clipart/small/12-hardware-phone.png | Bin
.../java/images/clipart/small/2-action-about.png | Bin
.../java/images/clipart/small/2-action-help.png | Bin
.../images/clipart/small/2-action-overflow.png | Bin
.../java/images/clipart/small/2-action-search.png | Bin
.../images/clipart/small/2-action-settings.png | Bin
.../java/images/clipart/small/3-rating-bad.png | Bin
.../images/clipart/small/3-rating-favorite.png | Bin
.../java/images/clipart/small/3-rating-good.png | Bin
.../clipart/small/3-rating-half-important.png | Bin
.../images/clipart/small/3-rating-important.png | Bin
.../clipart/small/3-rating-not-important.png | Bin
.../images/clipart/small/4-collections-cloud.png | Bin
.../clipart/small/4-collections-collection.png | Bin
.../clipart/small/4-collections-go-to-today.png | Bin
.../images/clipart/small/4-collections-labels.png | Bin
.../clipart/small/4-collections-new-label.png | Bin
.../clipart/small/4-collections-sort-by-size.png | Bin
.../clipart/small/4-collections-view-as-grid.png | Bin
.../clipart/small/4-collections-view-as-list.png | Bin
.../images/clipart/small/5-content-attachment.png | Bin
.../images/clipart/small/5-content-backspace.png | Bin
.../java/images/clipart/small/5-content-copy.png | Bin
.../java/images/clipart/small/5-content-cut.png | Bin
.../images/clipart/small/5-content-discard.png | Bin
.../java/images/clipart/small/5-content-edit.png | Bin
.../java/images/clipart/small/5-content-email.png | Bin
.../java/images/clipart/small/5-content-event.png | Bin
.../clipart/small/5-content-import-export.png | Bin
.../java/images/clipart/small/5-content-merge.png | Bin
.../clipart/small/5-content-new-attachment.png | Bin
.../images/clipart/small/5-content-new-email.png | Bin
.../images/clipart/small/5-content-new-event.png | Bin
.../images/clipart/small/5-content-new-picture.png | Bin
.../java/images/clipart/small/5-content-new.png | Bin
.../java/images/clipart/small/5-content-paste.png | Bin
.../images/clipart/small/5-content-picture.png | Bin
.../java/images/clipart/small/5-content-read.png | Bin
.../java/images/clipart/small/5-content-remove.png | Bin
.../java/images/clipart/small/5-content-save.png | Bin
.../images/clipart/small/5-content-select-all.png | Bin
.../java/images/clipart/small/5-content-split.png | Bin
.../java/images/clipart/small/5-content-undo.png | Bin
.../java/images/clipart/small/5-content-unread.png | Bin
.../images/clipart/small/6-social-add-group.png | Bin
.../images/clipart/small/6-social-add-person.png | Bin
.../java/images/clipart/small/6-social-cc-bcc.png | Bin
.../java/images/clipart/small/6-social-chat.png | Bin
.../java/images/clipart/small/6-social-forward.png | Bin
.../java/images/clipart/small/6-social-group.png | Bin
.../java/images/clipart/small/6-social-person.png | Bin
.../images/clipart/small/6-social-reply-all.png | Bin
.../java/images/clipart/small/6-social-reply.png | Bin
.../images/clipart/small/6-social-send-now.png | Bin
.../java/images/clipart/small/6-social-share.png | Bin
.../images/clipart/small/7-location-directions.png | Bin
.../java/images/clipart/small/7-location-map.png | Bin
.../java/images/clipart/small/7-location-place.png | Bin
.../images/clipart/small/7-location-web-site.png | Bin
.../java/images/clipart/small/8-images-crop.png | Bin
.../images/clipart/small/8-images-rotate-left.png | Bin
.../images/clipart/small/8-images-rotate-right.png | Bin
.../images/clipart/small/8-images-slideshow.png | Bin
.../images/clipart/small/9-av-add-to-queue.png | Bin
.../java/images/clipart/small/9-av-download.png | Bin
.../images/clipart/small/9-av-fast-forward.png | Bin
.../java/images/clipart/small/9-av-full-screen.png | Bin
.../clipart/small/9-av-make-available-offline.png | Bin
.../main/java/images/clipart/small/9-av-next.png | Bin
.../images/clipart/small/9-av-pause-over-video.png | Bin
.../main/java/images/clipart/small/9-av-pause.png | Bin
.../images/clipart/small/9-av-play-over-video.png | Bin
.../main/java/images/clipart/small/9-av-play.png | Bin
.../java/images/clipart/small/9-av-previous.png | Bin
.../main/java/images/clipart/small/9-av-repeat.png | Bin
.../main/java/images/clipart/small/9-av-replay.png | Bin
.../clipart/small/9-av-return-from-full-screen.png | Bin
.../main/java/images/clipart/small/9-av-rewind.png | Bin
.../java/images/clipart/small/9-av-shuffle.png | Bin
.../main/java/images/clipart/small/9-av-stop.png | Bin
.../main/java/images/clipart/small/9-av-upload.png | Bin
.../src/main/java/images/clipart/small/android.png | Bin
.../images/launcher_stencil/circle/hdpi/back.png | Bin
.../images/launcher_stencil/circle/hdpi/fore1.png | Bin
.../images/launcher_stencil/circle/hdpi/mask.png | Bin
.../images/launcher_stencil/circle/mdpi/back.png | Bin
.../images/launcher_stencil/circle/mdpi/fore1.png | Bin
.../images/launcher_stencil/circle/mdpi/mask.png | Bin
.../images/launcher_stencil/circle/web/back.png | Bin
.../images/launcher_stencil/circle/web/fore1.png | Bin
.../images/launcher_stencil/circle/web/mask.png | Bin
.../images/launcher_stencil/circle/xhdpi/back.png | Bin
.../images/launcher_stencil/circle/xhdpi/fore1.png | Bin
.../images/launcher_stencil/circle/xhdpi/mask.png | Bin
.../images/launcher_stencil/circle/xxhdpi/back.png | Bin
.../launcher_stencil/circle/xxhdpi/fore1.png | Bin
.../images/launcher_stencil/circle/xxhdpi/mask.png | Bin
.../launcher_stencil/circle/xxxhdpi/back.png | Bin
.../launcher_stencil/circle/xxxhdpi/fore1.png | Bin
.../launcher_stencil/circle/xxxhdpi/mask.png | Bin
.../launcher_stencil/circle/xxxhdpi/mask_inner.png | Bin
.../images/launcher_stencil/hrect/hdpi/back.png | Bin
.../images/launcher_stencil/hrect/hdpi/fore1.png | Bin
.../images/launcher_stencil/hrect/hdpi/mask.png | Bin
.../images/launcher_stencil/hrect/mdpi/back.png | Bin
.../images/launcher_stencil/hrect/mdpi/fore1.png | Bin
.../images/launcher_stencil/hrect/mdpi/mask.png | Bin
.../images/launcher_stencil/hrect/web/back.png | Bin
.../images/launcher_stencil/hrect/web/fore1.png | Bin
.../images/launcher_stencil/hrect/web/mask.png | Bin
.../images/launcher_stencil/hrect/xhdpi/back.png | Bin
.../images/launcher_stencil/hrect/xhdpi/fore1.png | Bin
.../images/launcher_stencil/hrect/xhdpi/mask.png | Bin
.../images/launcher_stencil/hrect/xxhdpi/back.png | Bin
.../images/launcher_stencil/hrect/xxhdpi/fore1.png | Bin
.../images/launcher_stencil/hrect/xxhdpi/mask.png | Bin
.../images/launcher_stencil/hrect/xxxhdpi/back.png | Bin
.../launcher_stencil/hrect/xxxhdpi/fore1.png | Bin
.../images/launcher_stencil/hrect/xxxhdpi/mask.png | Bin
.../launcher_stencil/hrect_dogear/hdpi/back.png | Bin
.../launcher_stencil/hrect_dogear/hdpi/fore1.png | Bin
.../launcher_stencil/hrect_dogear/hdpi/mask.png | Bin
.../launcher_stencil/hrect_dogear/mdpi/back.png | Bin
.../launcher_stencil/hrect_dogear/mdpi/fore1.png | Bin
.../launcher_stencil/hrect_dogear/mdpi/mask.png | Bin
.../launcher_stencil/hrect_dogear/web/back.png | Bin
.../launcher_stencil/hrect_dogear/web/fore1.png | Bin
.../launcher_stencil/hrect_dogear/web/mask.png | Bin
.../launcher_stencil/hrect_dogear/xhdpi/back.png | Bin
.../launcher_stencil/hrect_dogear/xhdpi/fore1.png | Bin
.../launcher_stencil/hrect_dogear/xhdpi/mask.png | Bin
.../launcher_stencil/hrect_dogear/xxhdpi/back.png | Bin
.../launcher_stencil/hrect_dogear/xxhdpi/fore1.png | Bin
.../launcher_stencil/hrect_dogear/xxhdpi/mask.png | Bin
.../launcher_stencil/hrect_dogear/xxxhdpi/back.png | Bin
.../hrect_dogear/xxxhdpi/fore1.png | Bin
.../launcher_stencil/hrect_dogear/xxxhdpi/mask.png | Bin
.../images/launcher_stencil/square/hdpi/back.png | Bin
.../images/launcher_stencil/square/hdpi/fore1.png | Bin
.../images/launcher_stencil/square/hdpi/mask.png | Bin
.../images/launcher_stencil/square/mdpi/back.png | Bin
.../images/launcher_stencil/square/mdpi/fore1.png | Bin
.../images/launcher_stencil/square/mdpi/mask.png | Bin
.../images/launcher_stencil/square/web/back.png | Bin
.../images/launcher_stencil/square/web/fore1.png | Bin
.../images/launcher_stencil/square/web/mask.png | Bin
.../images/launcher_stencil/square/xhdpi/back.png | Bin
.../images/launcher_stencil/square/xhdpi/fore1.png | Bin
.../images/launcher_stencil/square/xhdpi/mask.png | Bin
.../images/launcher_stencil/square/xxhdpi/back.png | Bin
.../launcher_stencil/square/xxhdpi/fore1.png | Bin
.../images/launcher_stencil/square/xxhdpi/mask.png | Bin
.../launcher_stencil/square/xxxhdpi/back.png | Bin
.../launcher_stencil/square/xxxhdpi/fore1.png | Bin
.../launcher_stencil/square/xxxhdpi/mask.png | Bin
.../launcher_stencil/square/xxxhdpi/mask_inner.png | Bin
.../launcher_stencil/square_dogear/hdpi/back.png | Bin
.../launcher_stencil/square_dogear/hdpi/fore1.png | Bin
.../launcher_stencil/square_dogear/hdpi/mask.png | Bin
.../launcher_stencil/square_dogear/mdpi/back.png | Bin
.../launcher_stencil/square_dogear/mdpi/fore1.png | Bin
.../launcher_stencil/square_dogear/mdpi/mask.png | Bin
.../launcher_stencil/square_dogear/web/back.png | Bin
.../launcher_stencil/square_dogear/web/fore1.png | Bin
.../launcher_stencil/square_dogear/web/mask.png | Bin
.../launcher_stencil/square_dogear/xhdpi/back.png | Bin
.../launcher_stencil/square_dogear/xhdpi/fore1.png | Bin
.../launcher_stencil/square_dogear/xhdpi/mask.png | Bin
.../launcher_stencil/square_dogear/xxhdpi/back.png | Bin
.../square_dogear/xxhdpi/fore1.png | Bin
.../launcher_stencil/square_dogear/xxhdpi/mask.png | Bin
.../square_dogear/xxxhdpi/back.png | Bin
.../square_dogear/xxxhdpi/fore1.png | Bin
.../square_dogear/xxxhdpi/mask.png | Bin
.../images/launcher_stencil/vrect/hdpi/back.png | Bin
.../images/launcher_stencil/vrect/hdpi/fore1.png | Bin
.../images/launcher_stencil/vrect/hdpi/mask.png | Bin
.../images/launcher_stencil/vrect/mdpi/back.png | Bin
.../images/launcher_stencil/vrect/mdpi/fore1.png | Bin
.../images/launcher_stencil/vrect/mdpi/mask.png | Bin
.../images/launcher_stencil/vrect/web/back.png | Bin
.../images/launcher_stencil/vrect/web/fore1.png | Bin
.../images/launcher_stencil/vrect/web/mask.png | Bin
.../images/launcher_stencil/vrect/xhdpi/back.png | Bin
.../images/launcher_stencil/vrect/xhdpi/fore1.png | Bin
.../images/launcher_stencil/vrect/xhdpi/mask.png | Bin
.../images/launcher_stencil/vrect/xxhdpi/back.png | Bin
.../images/launcher_stencil/vrect/xxhdpi/fore1.png | Bin
.../images/launcher_stencil/vrect/xxhdpi/mask.png | Bin
.../images/launcher_stencil/vrect/xxxhdpi/back.png | Bin
.../launcher_stencil/vrect/xxxhdpi/fore1.png | Bin
.../images/launcher_stencil/vrect/xxxhdpi/mask.png | Bin
.../launcher_stencil/vrect_dogear/hdpi/back.png | Bin
.../launcher_stencil/vrect_dogear/hdpi/fore1.png | Bin
.../launcher_stencil/vrect_dogear/hdpi/mask.png | Bin
.../launcher_stencil/vrect_dogear/mdpi/back.png | Bin
.../launcher_stencil/vrect_dogear/mdpi/fore1.png | Bin
.../launcher_stencil/vrect_dogear/mdpi/mask.png | Bin
.../launcher_stencil/vrect_dogear/web/back.png | Bin
.../launcher_stencil/vrect_dogear/web/fore1.png | Bin
.../launcher_stencil/vrect_dogear/web/mask.png | Bin
.../launcher_stencil/vrect_dogear/xhdpi/back.png | Bin
.../launcher_stencil/vrect_dogear/xhdpi/fore1.png | Bin
.../launcher_stencil/vrect_dogear/xhdpi/mask.png | Bin
.../launcher_stencil/vrect_dogear/xxhdpi/back.png | Bin
.../launcher_stencil/vrect_dogear/xxhdpi/fore1.png | Bin
.../launcher_stencil/vrect_dogear/xxhdpi/mask.png | Bin
.../launcher_stencil/vrect_dogear/xxxhdpi/back.png | Bin
.../vrect_dogear/xxxhdpi/fore1.png | Bin
.../launcher_stencil/vrect_dogear/xxxhdpi/mask.png | Bin
.../action/ic_3d_rotation_black_24dp.xml | 9 +
.../action/ic_accessibility_black_24dp.xml | 9 +
.../action/ic_accessible_black_24dp.xml | 12 +
.../action/ic_account_balance_black_24dp.xml | 9 +
.../ic_account_balance_wallet_black_24dp.xml | 9 +
.../action/ic_account_box_black_24dp.xml | 9 +
.../action/ic_account_circle_black_24dp.xml | 9 +
.../action/ic_add_shopping_cart_black_24dp.xml | 9 +
.../action/ic_alarm_add_black_24dp.xml | 9 +
.../action/ic_alarm_black_24dp.xml | 9 +
.../action/ic_alarm_off_black_24dp.xml | 9 +
.../action/ic_alarm_on_black_24dp.xml | 9 +
.../action/ic_all_out_black_24dp.xml | 9 +
.../action/ic_android_black_24dp.xml | 9 +
.../action/ic_announcement_black_24dp.xml | 9 +
.../action/ic_aspect_ratio_black_24dp.xml | 9 +
.../action/ic_assessment_black_24dp.xml | 9 +
.../action/ic_assignment_black_24dp.xml | 9 +
.../action/ic_assignment_ind_black_24dp.xml | 9 +
.../action/ic_assignment_late_black_24dp.xml | 9 +
.../action/ic_assignment_return_black_24dp.xml | 9 +
.../action/ic_assignment_returned_black_24dp.xml | 9 +
.../action/ic_assignment_turned_in_black_24dp.xml | 9 +
.../action/ic_autorenew_black_24dp.xml | 9 +
.../action/ic_backup_black_24dp.xml | 9 +
.../action/ic_book_black_24dp.xml | 9 +
.../action/ic_bookmark_black_24dp.xml | 9 +
.../action/ic_bookmark_border_black_24dp.xml | 9 +
.../action/ic_bug_report_black_24dp.xml | 9 +
.../action/ic_build_black_24dp.xml | 9 +
.../action/ic_cached_black_24dp.xml | 9 +
.../action/ic_camera_enhance_black_24dp.xml | 9 +
.../action/ic_card_giftcard_black_24dp.xml | 9 +
.../action/ic_card_membership_black_24dp.xml | 9 +
.../action/ic_card_travel_black_24dp.xml | 9 +
.../action/ic_change_history_black_24dp.xml | 9 +
.../action/ic_check_circle_black_24dp.xml | 9 +
.../action/ic_chrome_reader_mode_black_24dp.xml | 9 +
.../action/ic_class_black_24dp.xml | 9 +
.../action/ic_code_black_24dp.xml | 9 +
.../action/ic_compare_arrows_black_24dp.xml | 9 +
.../action/ic_copyright_black_24dp.xml | 9 +
.../action/ic_credit_card_black_24dp.xml | 9 +
.../action/ic_dashboard_black_24dp.xml | 9 +
.../action/ic_date_range_black_24dp.xml | 9 +
.../action/ic_delete_black_24dp.xml | 9 +
.../action/ic_description_black_24dp.xml | 9 +
.../action/ic_dns_black_24dp.xml | 9 +
.../action/ic_done_all_black_24dp.xml | 9 +
.../action/ic_done_black_24dp.xml | 9 +
.../action/ic_donut_large_black_24dp.xml | 9 +
.../action/ic_donut_small_black_24dp.xml | 9 +
.../action/ic_eject_black_24dp.xml | 9 +
.../action/ic_event_black_24dp.xml | 9 +
.../action/ic_event_seat_black_24dp.xml | 9 +
.../action/ic_exit_to_app_black_24dp.xml | 9 +
.../action/ic_explore_black_24dp.xml | 9 +
.../action/ic_extension_black_24dp.xml | 9 +
.../action/ic_face_black_24dp.xml | 9 +
.../action/ic_favorite_black_24dp.xml | 9 +
.../action/ic_favorite_border_black_24dp.xml | 9 +
.../action/ic_feedback_black_24dp.xml | 9 +
.../action/ic_find_in_page_black_24dp.xml | 9 +
.../action/ic_find_replace_black_24dp.xml | 9 +
.../action/ic_fingerprint_black_24dp.xml | 9 +
.../action/ic_flight_land_black_24dp.xml | 9 +
.../action/ic_flight_takeoff_black_24dp.xml | 9 +
.../action/ic_flip_to_back_black_24dp.xml | 9 +
.../action/ic_flip_to_front_black_24dp.xml | 9 +
.../action/ic_gavel_black_24dp.xml | 9 +
.../action/ic_get_app_black_24dp.xml | 9 +
.../action/ic_gif_black_24dp.xml | 9 +
.../action/ic_grade_black_24dp.xml | 9 +
.../action/ic_group_work_black_24dp.xml | 9 +
.../action/ic_help_black_24dp.xml | 9 +
.../action/ic_help_outline_black_24dp.xml | 9 +
.../action/ic_highlight_off_black_24dp.xml | 9 +
.../action/ic_history_black_24dp.xml | 9 +
.../action/ic_home_black_24dp.xml | 9 +
.../action/ic_hourglass_empty_black_24dp.xml | 9 +
.../action/ic_hourglass_full_black_24dp.xml | 9 +
.../action/ic_http_black_24dp.xml | 9 +
.../action/ic_https_black_24dp.xml | 9 +
.../action/ic_important_devices_black_24dp.xml | 9 +
.../action/ic_info_black_24dp.xml | 9 +
.../action/ic_info_outline_black_24dp.xml | 9 +
.../action/ic_input_black_24dp.xml | 9 +
.../action/ic_invert_colors_black_24dp.xml | 9 +
.../action/ic_label_black_24dp.xml | 9 +
.../action/ic_label_outline_black_24dp.xml | 9 +
.../action/ic_language_black_24dp.xml | 9 +
.../action/ic_launch_black_24dp.xml | 9 +
.../action/ic_lightbulb_outline_black_24dp.xml | 9 +
.../action/ic_line_style_black_24dp.xml | 9 +
.../action/ic_line_weight_black_24dp.xml | 9 +
.../action/ic_list_black_24dp.xml | 9 +
.../action/ic_lock_black_24dp.xml | 9 +
.../action/ic_lock_open_black_24dp.xml | 9 +
.../action/ic_lock_outline_black_24dp.xml | 9 +
.../action/ic_loyalty_black_24dp.xml | 9 +
.../action/ic_markunread_mailbox_black_24dp.xml | 9 +
.../action/ic_motorcycle_black_24dp.xml | 9 +
.../action/ic_note_add_black_24dp.xml | 9 +
.../action/ic_offline_pin_black_24dp.xml | 9 +
.../action/ic_opacity_black_24dp.xml | 9 +
.../action/ic_open_in_browser_black_24dp.xml | 9 +
.../action/ic_open_in_new_black_24dp.xml | 9 +
.../action/ic_open_with_black_24dp.xml | 9 +
.../action/ic_pageview_black_24dp.xml | 9 +
.../action/ic_pan_tool_black_24dp.xml | 9 +
.../action/ic_payment_black_24dp.xml | 9 +
.../action/ic_perm_camera_mic_black_24dp.xml | 9 +
.../action/ic_perm_contact_calendar_black_24dp.xml | 9 +
.../action/ic_perm_data_setting_black_24dp.xml | 9 +
.../ic_perm_device_information_black_24dp.xml | 9 +
.../action/ic_perm_identity_black_24dp.xml | 9 +
.../action/ic_perm_media_black_24dp.xml | 9 +
.../action/ic_perm_phone_msg_black_24dp.xml | 9 +
.../action/ic_perm_scan_wifi_black_24dp.xml | 9 +
.../action/ic_pets_black_24dp.xml | 21 +
.../ic_picture_in_picture_alt_black_24dp.xml | 9 +
.../action/ic_picture_in_picture_black_24dp.xml | 9 +
.../action/ic_play_for_work_black_24dp.xml | 9 +
.../action/ic_polymer_black_24dp.xml | 9 +
.../action/ic_power_settings_new_black_24dp.xml | 9 +
.../action/ic_pregnant_woman_black_24dp.xml | 9 +
.../action/ic_print_black_24dp.xml | 9 +
.../action/ic_query_builder_black_24dp.xml | 9 +
.../action/ic_question_answer_black_24dp.xml | 9 +
.../action/ic_receipt_black_24dp.xml | 9 +
.../action/ic_record_voice_over_black_24dp.xml | 12 +
.../action/ic_redeem_black_24dp.xml | 9 +
.../action/ic_reorder_black_24dp.xml | 9 +
.../action/ic_report_problem_black_24dp.xml | 9 +
.../action/ic_restore_black_24dp.xml | 9 +
.../action/ic_room_black_24dp.xml | 9 +
.../action/ic_rounded_corner_black_24dp.xml | 9 +
.../action/ic_rowing_black_24dp.xml | 9 +
.../action/ic_schedule_black_24dp.xml | 9 +
.../action/ic_search_black_24dp.xml | 9 +
.../action/ic_settings_applications_black_24dp.xml | 9 +
.../ic_settings_backup_restore_black_24dp.xml | 9 +
.../action/ic_settings_black_24dp.xml | 9 +
.../action/ic_settings_bluetooth_black_24dp.xml | 9 +
.../action/ic_settings_brightness_black_24dp.xml | 9 +
.../action/ic_settings_cell_black_24dp.xml | 9 +
.../action/ic_settings_ethernet_black_24dp.xml | 9 +
.../ic_settings_input_antenna_black_24dp.xml | 9 +
.../ic_settings_input_component_black_24dp.xml | 9 +
.../ic_settings_input_composite_black_24dp.xml | 9 +
.../action/ic_settings_input_hdmi_black_24dp.xml | 9 +
.../action/ic_settings_input_svideo_black_24dp.xml | 9 +
.../action/ic_settings_overscan_black_24dp.xml | 9 +
.../action/ic_settings_phone_black_24dp.xml | 9 +
.../action/ic_settings_power_black_24dp.xml | 9 +
.../action/ic_settings_remote_black_24dp.xml | 9 +
.../action/ic_settings_voice_black_24dp.xml | 9 +
.../action/ic_shop_black_24dp.xml | 9 +
.../action/ic_shop_two_black_24dp.xml | 9 +
.../action/ic_shopping_basket_black_24dp.xml | 9 +
.../action/ic_shopping_cart_black_24dp.xml | 9 +
.../action/ic_speaker_notes_black_24dp.xml | 9 +
.../action/ic_spellcheck_black_24dp.xml | 9 +
.../action/ic_stars_black_24dp.xml | 9 +
.../action/ic_store_black_24dp.xml | 9 +
.../action/ic_subject_black_24dp.xml | 9 +
.../action/ic_supervisor_account_black_24dp.xml | 9 +
.../action/ic_swap_horiz_black_24dp.xml | 9 +
.../action/ic_swap_vert_black_24dp.xml | 9 +
.../action/ic_swap_vertical_circle_black_24dp.xml | 9 +
.../action/ic_system_update_alt_black_24dp.xml | 9 +
.../action/ic_tab_black_24dp.xml | 9 +
.../action/ic_tab_unselected_black_24dp.xml | 9 +
.../action/ic_theaters_black_24dp.xml | 9 +
.../action/ic_thumb_down_black_24dp.xml | 9 +
.../action/ic_thumb_up_black_24dp.xml | 9 +
.../action/ic_thumbs_up_down_black_24dp.xml | 9 +
.../action/ic_timeline_black_24dp.xml | 9 +
.../action/ic_toc_black_24dp.xml | 9 +
.../action/ic_today_black_24dp.xml | 9 +
.../action/ic_toll_black_24dp.xml | 9 +
.../action/ic_touch_app_black_24dp.xml | 9 +
.../action/ic_track_changes_black_24dp.xml | 9 +
.../action/ic_translate_black_24dp.xml | 9 +
.../action/ic_trending_down_black_24dp.xml | 9 +
.../action/ic_trending_flat_black_24dp.xml | 9 +
.../action/ic_trending_up_black_24dp.xml | 9 +
.../action/ic_turned_in_black_24dp.xml | 9 +
.../action/ic_turned_in_not_black_24dp.xml | 9 +
.../action/ic_update_black_24dp.xml | 9 +
.../action/ic_verified_user_black_24dp.xml | 9 +
.../action/ic_view_agenda_black_24dp.xml | 9 +
.../action/ic_view_array_black_24dp.xml | 9 +
.../action/ic_view_carousel_black_24dp.xml | 9 +
.../action/ic_view_column_black_24dp.xml | 9 +
.../action/ic_view_day_black_24dp.xml | 9 +
.../action/ic_view_headline_black_24dp.xml | 9 +
.../action/ic_view_list_black_24dp.xml | 9 +
.../action/ic_view_module_black_24dp.xml | 9 +
.../action/ic_view_quilt_black_24dp.xml | 9 +
.../action/ic_view_stream_black_24dp.xml | 9 +
.../action/ic_view_week_black_24dp.xml | 9 +
.../action/ic_visibility_black_24dp.xml | 9 +
.../action/ic_visibility_off_black_24dp.xml | 9 +
.../action/ic_watch_later_black_24dp.xml | 9 +
.../action/ic_work_black_24dp.xml | 9 +
.../action/ic_youtube_searched_for_black_24dp.xml | 9 +
.../action/ic_zoom_in_black_24dp.xml | 9 +
.../action/ic_zoom_out_black_24dp.xml | 9 +
.../alert/ic_add_alert_black_24dp.xml | 9 +
.../alert/ic_error_black_24dp.xml | 9 +
.../alert/ic_error_outline_black_24dp.xml | 9 +
.../alert/ic_warning_black_24dp.xml | 9 +
.../av/ic_add_to_queue_black_24dp.xml | 9 +
.../av/ic_airplay_black_24dp.xml | 9 +
.../av/ic_album_black_24dp.xml | 9 +
.../av/ic_art_track_black_24dp.xml | 9 +
.../av/ic_av_timer_black_24dp.xml | 9 +
.../av/ic_closed_caption_black_24dp.xml | 9 +
.../av/ic_equalizer_black_24dp.xml | 9 +
.../av/ic_explicit_black_24dp.xml | 9 +
.../av/ic_fast_forward_black_24dp.xml | 9 +
.../av/ic_fast_rewind_black_24dp.xml | 9 +
.../av/ic_fiber_dvr_black_24dp.xml | 9 +
.../av/ic_fiber_manual_record_black_24dp.xml | 9 +
.../av/ic_fiber_new_black_24dp.xml | 9 +
.../av/ic_fiber_pin_black_24dp.xml | 9 +
.../av/ic_fiber_smart_record_black_24dp.xml | 12 +
.../av/ic_forward_10_black_24dp.xml | 9 +
.../av/ic_forward_30_black_24dp.xml | 9 +
.../av/ic_forward_5_black_24dp.xml | 9 +
.../av/ic_games_black_24dp.xml | 9 +
.../material_design_icons/av/ic_hd_black_24dp.xml | 9 +
.../av/ic_hearing_black_24dp.xml | 9 +
.../av/ic_high_quality_black_24dp.xml | 9 +
.../av/ic_library_add_black_24dp.xml | 9 +
.../av/ic_library_books_black_24dp.xml | 9 +
.../av/ic_library_music_black_24dp.xml | 9 +
.../av/ic_loop_black_24dp.xml | 9 +
.../material_design_icons/av/ic_mic_black_24dp.xml | 9 +
.../av/ic_mic_none_black_24dp.xml | 9 +
.../av/ic_mic_off_black_24dp.xml | 9 +
.../av/ic_movie_black_24dp.xml | 9 +
.../av/ic_music_video_black_24dp.xml | 9 +
.../av/ic_new_releases_black_24dp.xml | 9 +
.../av/ic_not_interested_black_24dp.xml | 9 +
.../av/ic_pause_black_24dp.xml | 9 +
.../av/ic_pause_circle_filled_black_24dp.xml | 9 +
.../av/ic_pause_circle_outline_black_24dp.xml | 9 +
.../av/ic_play_arrow_black_24dp.xml | 9 +
.../av/ic_play_circle_filled_black_24dp.xml | 9 +
.../av/ic_play_circle_outline_black_24dp.xml | 9 +
.../av/ic_playlist_add_black_24dp.xml | 9 +
.../av/ic_playlist_add_check_black_24dp.xml | 9 +
.../av/ic_playlist_play_black_24dp.xml | 9 +
.../av/ic_queue_black_24dp.xml | 9 +
.../av/ic_queue_music_black_24dp.xml | 9 +
.../av/ic_queue_play_next_black_24dp.xml | 9 +
.../av/ic_radio_black_24dp.xml | 9 +
.../av/ic_recent_actors_black_24dp.xml | 9 +
.../av/ic_remove_from_queue_black_24dp.xml | 9 +
.../av/ic_repeat_black_24dp.xml | 9 +
.../av/ic_repeat_one_black_24dp.xml | 9 +
.../av/ic_replay_10_black_24dp.xml | 9 +
.../av/ic_replay_30_black_24dp.xml | 9 +
.../av/ic_replay_5_black_24dp.xml | 9 +
.../av/ic_replay_black_24dp.xml | 9 +
.../av/ic_shuffle_black_24dp.xml | 9 +
.../av/ic_skip_next_black_24dp.xml | 9 +
.../av/ic_skip_previous_black_24dp.xml | 9 +
.../av/ic_slow_motion_video_black_24dp.xml | 9 +
.../av/ic_snooze_black_24dp.xml | 9 +
.../av/ic_sort_by_alpha_black_24dp.xml | 9 +
.../av/ic_stop_black_24dp.xml | 9 +
.../av/ic_subscriptions_black_24dp.xml | 9 +
.../av/ic_subtitles_black_24dp.xml | 9 +
.../av/ic_surround_sound_black_24dp.xml | 9 +
.../av/ic_video_library_black_24dp.xml | 9 +
.../av/ic_videocam_black_24dp.xml | 9 +
.../av/ic_videocam_off_black_24dp.xml | 9 +
.../av/ic_volume_down_black_24dp.xml | 9 +
.../av/ic_volume_mute_black_24dp.xml | 9 +
.../av/ic_volume_off_black_24dp.xml | 9 +
.../av/ic_volume_up_black_24dp.xml | 9 +
.../av/ic_web_asset_black_24dp.xml | 9 +
.../material_design_icons/av/ic_web_black_24dp.xml | 9 +
.../communication/ic_business_black_24dp.xml | 9 +
.../communication/ic_call_black_24dp.xml | 9 +
.../communication/ic_call_end_black_24dp.xml | 9 +
.../communication/ic_call_made_black_24dp.xml | 9 +
.../communication/ic_call_merge_black_24dp.xml | 9 +
.../communication/ic_call_missed_black_24dp.xml | 9 +
.../ic_call_missed_outgoing_black_24dp.xml | 9 +
.../communication/ic_call_received_black_24dp.xml | 9 +
.../communication/ic_call_split_black_24dp.xml | 9 +
.../communication/ic_chat_black_24dp.xml | 9 +
.../communication/ic_chat_bubble_black_24dp.xml | 9 +
.../ic_chat_bubble_outline_black_24dp.xml | 9 +
.../communication/ic_clear_all_black_24dp.xml | 9 +
.../communication/ic_comment_black_24dp.xml | 9 +
.../communication/ic_contact_mail_black_24dp.xml | 9 +
.../communication/ic_contact_phone_black_24dp.xml | 9 +
.../communication/ic_contacts_black_24dp.xml | 9 +
.../communication/ic_dialer_sip_black_24dp.xml | 9 +
.../communication/ic_dialpad_black_24dp.xml | 9 +
.../communication/ic_email_black_24dp.xml | 9 +
.../communication/ic_forum_black_24dp.xml | 9 +
.../ic_import_contacts_black_24dp.xml | 9 +
.../communication/ic_import_export_black_24dp.xml | 9 +
.../ic_invert_colors_off_black_24dp.xml | 9 +
.../communication/ic_live_help_black_24dp.xml | 9 +
.../communication/ic_location_off_black_24dp.xml | 9 +
.../communication/ic_location_on_black_24dp.xml | 9 +
.../communication/ic_mail_outline_black_24dp.xml | 9 +
.../communication/ic_message_black_24dp.xml | 9 +
.../communication/ic_no_sim_black_24dp.xml | 9 +
.../communication/ic_phone_black_24dp.xml | 9 +
.../ic_phonelink_erase_black_24dp.xml | 9 +
.../communication/ic_phonelink_lock_black_24dp.xml | 9 +
.../communication/ic_phonelink_ring_black_24dp.xml | 9 +
.../ic_phonelink_setup_black_24dp.xml | 9 +
.../ic_portable_wifi_off_black_24dp.xml | 9 +
.../communication/ic_present_to_all_black_24dp.xml | 9 +
.../communication/ic_ring_volume_black_24dp.xml | 9 +
.../communication/ic_screen_share_black_24dp.xml | 9 +
.../communication/ic_speaker_phone_black_24dp.xml | 9 +
.../ic_stay_current_landscape_black_24dp.xml | 9 +
.../ic_stay_current_portrait_black_24dp.xml | 9 +
.../ic_stay_primary_landscape_black_24dp.xml | 9 +
.../ic_stay_primary_portrait_black_24dp.xml | 9 +
.../ic_stop_screen_share_black_24dp.xml | 9 +
.../communication/ic_swap_calls_black_24dp.xml | 9 +
.../communication/ic_textsms_black_24dp.xml | 9 +
.../communication/ic_voicemail_black_24dp.xml | 9 +
.../communication/ic_vpn_key_black_24dp.xml | 9 +
.../content/ic_add_black_24dp.xml | 9 +
.../content/ic_add_box_black_24dp.xml | 9 +
.../content/ic_add_circle_black_24dp.xml | 9 +
.../content/ic_add_circle_outline_black_24dp.xml | 9 +
.../content/ic_archive_black_24dp.xml | 9 +
.../content/ic_backspace_black_24dp.xml | 9 +
.../content/ic_block_black_24dp.xml | 9 +
.../content/ic_clear_black_24dp.xml | 9 +
.../content/ic_content_copy_black_24dp.xml | 9 +
.../content/ic_content_cut_black_24dp.xml | 9 +
.../content/ic_content_paste_black_24dp.xml | 9 +
.../content/ic_create_black_24dp.xml | 9 +
.../content/ic_drafts_black_24dp.xml | 9 +
.../content/ic_filter_list_black_24dp.xml | 9 +
.../content/ic_flag_black_24dp.xml | 9 +
.../content/ic_font_download_black_24dp.xml | 9 +
.../content/ic_forward_black_24dp.xml | 9 +
.../content/ic_gesture_black_24dp.xml | 9 +
.../content/ic_inbox_black_24dp.xml | 9 +
.../content/ic_link_black_24dp.xml | 9 +
.../content/ic_mail_black_24dp.xml | 9 +
.../content/ic_markunread_black_24dp.xml | 9 +
.../content/ic_move_to_inbox_black_24dp.xml | 9 +
.../content/ic_next_week_black_24dp.xml | 9 +
.../content/ic_redo_black_24dp.xml | 9 +
.../content/ic_remove_black_24dp.xml | 9 +
.../content/ic_remove_circle_black_24dp.xml | 9 +
.../ic_remove_circle_outline_black_24dp.xml | 9 +
.../content/ic_reply_all_black_24dp.xml | 9 +
.../content/ic_reply_black_24dp.xml | 9 +
.../content/ic_report_black_24dp.xml | 9 +
.../content/ic_save_black_24dp.xml | 9 +
.../content/ic_select_all_black_24dp.xml | 9 +
.../content/ic_send_black_24dp.xml | 9 +
.../content/ic_sort_black_24dp.xml | 9 +
.../content/ic_text_format_black_24dp.xml | 9 +
.../content/ic_unarchive_black_24dp.xml | 9 +
.../content/ic_undo_black_24dp.xml | 9 +
.../content/ic_weekend_black_24dp.xml | 9 +
.../device/ic_access_alarm_black_24dp.xml | 9 +
.../device/ic_access_alarms_black_24dp.xml | 9 +
.../device/ic_access_time_black_24dp.xml | 9 +
.../device/ic_add_alarm_black_24dp.xml | 9 +
.../device/ic_airplanemode_active_black_24dp.xml | 12 +
.../device/ic_airplanemode_inactive_black_24dp.xml | 9 +
.../device/ic_battery_20_black_24dp.xml | 13 +
.../device/ic_battery_30_black_24dp.xml | 13 +
.../device/ic_battery_50_black_24dp.xml | 13 +
.../device/ic_battery_60_black_24dp.xml | 13 +
.../device/ic_battery_80_black_24dp.xml | 13 +
.../device/ic_battery_90_black_24dp.xml | 13 +
.../device/ic_battery_alert_black_24dp.xml | 9 +
.../device/ic_battery_charging_20_black_24dp.xml | 13 +
.../device/ic_battery_charging_30_black_24dp.xml | 13 +
.../device/ic_battery_charging_50_black_24dp.xml | 13 +
.../device/ic_battery_charging_60_black_24dp.xml | 13 +
.../device/ic_battery_charging_80_black_24dp.xml | 13 +
.../device/ic_battery_charging_90_black_24dp.xml | 13 +
.../device/ic_battery_charging_full_black_24dp.xml | 9 +
.../device/ic_battery_full_black_24dp.xml | 9 +
.../device/ic_battery_std_black_24dp.xml | 9 +
.../device/ic_battery_unknown_black_24dp.xml | 9 +
.../device/ic_bluetooth_black_24dp.xml | 9 +
.../device/ic_bluetooth_connected_black_24dp.xml | 9 +
.../device/ic_bluetooth_disabled_black_24dp.xml | 9 +
.../device/ic_bluetooth_searching_black_24dp.xml | 9 +
.../device/ic_brightness_auto_black_24dp.xml | 9 +
.../device/ic_brightness_high_black_24dp.xml | 9 +
.../device/ic_brightness_low_black_24dp.xml | 9 +
.../device/ic_brightness_medium_black_24dp.xml | 9 +
.../device/ic_data_usage_black_24dp.xml | 9 +
.../device/ic_developer_mode_black_24dp.xml | 9 +
.../device/ic_devices_black_24dp.xml | 9 +
.../device/ic_dvr_black_24dp.xml | 9 +
.../device/ic_gps_fixed_black_24dp.xml | 9 +
.../device/ic_gps_not_fixed_black_24dp.xml | 9 +
.../device/ic_gps_off_black_24dp.xml | 9 +
.../device/ic_graphic_eq_black_24dp.xml | 9 +
.../device/ic_location_disabled_black_24dp.xml | 9 +
.../device/ic_location_searching_black_24dp.xml | 9 +
.../device/ic_network_cell_black_24dp.xml | 13 +
.../device/ic_network_wifi_black_24dp.xml | 13 +
.../device/ic_nfc_black_24dp.xml | 9 +
.../device/ic_screen_lock_landscape_black_24dp.xml | 9 +
.../device/ic_screen_lock_portrait_black_24dp.xml | 9 +
.../device/ic_screen_lock_rotation_black_24dp.xml | 9 +
.../device/ic_screen_rotation_black_24dp.xml | 9 +
.../device/ic_sd_storage_black_24dp.xml | 9 +
.../ic_settings_system_daydream_black_24dp.xml | 9 +
.../device/ic_signal_cellular_0_bar_black_24dp.xml | 10 +
.../device/ic_signal_cellular_1_bar_black_24dp.xml | 13 +
.../device/ic_signal_cellular_2_bar_black_24dp.xml | 13 +
.../device/ic_signal_cellular_3_bar_black_24dp.xml | 13 +
.../device/ic_signal_cellular_4_bar_black_24dp.xml | 9 +
...ular_connected_no_internet_0_bar_black_24dp.xml | 13 +
...ular_connected_no_internet_1_bar_black_24dp.xml | 13 +
...ular_connected_no_internet_2_bar_black_24dp.xml | 13 +
...ular_connected_no_internet_3_bar_black_24dp.xml | 13 +
...ular_connected_no_internet_4_bar_black_24dp.xml | 9 +
.../ic_signal_cellular_no_sim_black_24dp.xml | 9 +
.../device/ic_signal_cellular_null_black_24dp.xml | 9 +
.../device/ic_signal_cellular_off_black_24dp.xml | 9 +
.../device/ic_signal_wifi_0_bar_black_24dp.xml | 10 +
.../device/ic_signal_wifi_1_bar_black_24dp.xml | 13 +
.../ic_signal_wifi_1_bar_lock_black_24dp.xml | 16 +
.../device/ic_signal_wifi_2_bar_black_24dp.xml | 13 +
.../ic_signal_wifi_2_bar_lock_black_24dp.xml | 16 +
.../device/ic_signal_wifi_3_bar_black_24dp.xml | 13 +
.../ic_signal_wifi_3_bar_lock_black_24dp.xml | 13 +
.../device/ic_signal_wifi_4_bar_black_24dp.xml | 9 +
.../ic_signal_wifi_4_bar_lock_black_24dp.xml | 9 +
.../device/ic_signal_wifi_off_black_24dp.xml | 9 +
.../device/ic_storage_black_24dp.xml | 9 +
.../device/ic_usb_black_24dp.xml | 9 +
.../device/ic_wallpaper_black_24dp.xml | 9 +
.../device/ic_widgets_black_24dp.xml | 9 +
.../device/ic_wifi_lock_black_24dp.xml | 9 +
.../device/ic_wifi_tethering_black_24dp.xml | 9 +
.../editor/ic_attach_file_black_24dp.xml | 9 +
.../editor/ic_attach_money_black_24dp.xml | 9 +
.../editor/ic_border_all_black_24dp.xml | 9 +
.../editor/ic_border_bottom_black_24dp.xml | 9 +
.../editor/ic_border_clear_black_24dp.xml | 9 +
.../editor/ic_border_color_black_24dp.xml | 13 +
.../editor/ic_border_horizontal_black_24dp.xml | 9 +
.../editor/ic_border_inner_black_24dp.xml | 9 +
.../editor/ic_border_left_black_24dp.xml | 9 +
.../editor/ic_border_outer_black_24dp.xml | 9 +
.../editor/ic_border_right_black_24dp.xml | 9 +
.../editor/ic_border_style_black_24dp.xml | 9 +
.../editor/ic_border_top_black_24dp.xml | 9 +
.../editor/ic_border_vertical_black_24dp.xml | 9 +
.../editor/ic_drag_handle_black_24dp.xml | 9 +
.../editor/ic_format_align_center_black_24dp.xml | 9 +
.../editor/ic_format_align_justify_black_24dp.xml | 9 +
.../editor/ic_format_align_left_black_24dp.xml | 9 +
.../editor/ic_format_align_right_black_24dp.xml | 9 +
.../editor/ic_format_bold_black_24dp.xml | 9 +
.../editor/ic_format_clear_black_24dp.xml | 9 +
.../editor/ic_format_color_fill_black_24dp.xml | 13 +
.../editor/ic_format_color_reset_black_24dp.xml | 9 +
.../editor/ic_format_color_text_black_24dp.xml | 13 +
.../ic_format_indent_decrease_black_24dp.xml | 9 +
.../ic_format_indent_increase_black_24dp.xml | 9 +
.../editor/ic_format_italic_black_24dp.xml | 9 +
.../editor/ic_format_line_spacing_black_24dp.xml | 9 +
.../editor/ic_format_list_bulleted_black_24dp.xml | 9 +
.../editor/ic_format_list_numbered_black_24dp.xml | 9 +
.../editor/ic_format_paint_black_24dp.xml | 9 +
.../editor/ic_format_quote_black_24dp.xml | 9 +
.../editor/ic_format_shapes_black_24dp.xml | 9 +
.../editor/ic_format_size_black_24dp.xml | 9 +
.../editor/ic_format_strikethrough_black_24dp.xml | 9 +
.../ic_format_textdirection_l_to_r_black_24dp.xml | 9 +
.../ic_format_textdirection_r_to_l_black_24dp.xml | 9 +
.../editor/ic_format_underlined_black_24dp.xml | 9 +
.../editor/ic_functions_black_24dp.xml | 9 +
.../editor/ic_highlight_black_24dp.xml | 9 +
.../editor/ic_insert_chart_black_24dp.xml | 9 +
.../editor/ic_insert_comment_black_24dp.xml | 9 +
.../editor/ic_insert_drive_file_black_24dp.xml | 9 +
.../editor/ic_insert_emoticon_black_24dp.xml | 9 +
.../editor/ic_insert_invitation_black_24dp.xml | 9 +
.../editor/ic_insert_link_black_24dp.xml | 9 +
.../editor/ic_insert_photo_black_24dp.xml | 9 +
.../editor/ic_linear_scale_black_24dp.xml | 9 +
.../editor/ic_merge_type_black_24dp.xml | 9 +
.../editor/ic_mode_comment_black_24dp.xml | 9 +
.../editor/ic_mode_edit_black_24dp.xml | 9 +
.../editor/ic_money_off_black_24dp.xml | 9 +
.../editor/ic_publish_black_24dp.xml | 9 +
.../editor/ic_short_text_black_24dp.xml | 9 +
.../editor/ic_space_bar_black_24dp.xml | 9 +
.../editor/ic_strikethrough_s_black_24dp.xml | 9 +
.../editor/ic_text_fields_black_24dp.xml | 9 +
.../editor/ic_vertical_align_bottom_black_24dp.xml | 9 +
.../editor/ic_vertical_align_center_black_24dp.xml | 9 +
.../editor/ic_vertical_align_top_black_24dp.xml | 9 +
.../editor/ic_wrap_text_black_24dp.xml | 9 +
.../file/ic_attachment_black_24dp.xml | 9 +
.../file/ic_cloud_black_24dp.xml | 9 +
.../file/ic_cloud_circle_black_24dp.xml | 9 +
.../file/ic_cloud_done_black_24dp.xml | 9 +
.../file/ic_cloud_download_black_24dp.xml | 9 +
.../file/ic_cloud_off_black_24dp.xml | 9 +
.../file/ic_cloud_queue_black_24dp.xml | 9 +
.../file/ic_cloud_upload_black_24dp.xml | 9 +
.../file/ic_create_new_folder_black_24dp.xml | 9 +
.../file/ic_file_download_black_24dp.xml | 9 +
.../file/ic_file_upload_black_24dp.xml | 9 +
.../file/ic_folder_black_24dp.xml | 9 +
.../file/ic_folder_open_black_24dp.xml | 9 +
.../file/ic_folder_shared_black_24dp.xml | 9 +
.../hardware/ic_cast_black_24dp.xml | 9 +
.../hardware/ic_cast_connected_black_24dp.xml | 9 +
.../hardware/ic_computer_black_24dp.xml | 9 +
.../hardware/ic_desktop_mac_black_24dp.xml | 9 +
.../hardware/ic_desktop_windows_black_24dp.xml | 9 +
.../hardware/ic_developer_board_black_24dp.xml | 9 +
.../hardware/ic_device_hub_black_24dp.xml | 9 +
.../hardware/ic_devices_other_black_24dp.xml | 9 +
.../hardware/ic_dock_black_24dp.xml | 9 +
.../hardware/ic_gamepad_black_24dp.xml | 9 +
.../hardware/ic_headset_black_24dp.xml | 9 +
.../hardware/ic_headset_mic_black_24dp.xml | 9 +
.../hardware/ic_keyboard_arrow_down_black_24dp.xml | 9 +
.../hardware/ic_keyboard_arrow_left_black_24dp.xml | 9 +
.../ic_keyboard_arrow_right_black_24dp.xml | 9 +
.../hardware/ic_keyboard_arrow_up_black_24dp.xml | 9 +
.../hardware/ic_keyboard_backspace_black_24dp.xml | 9 +
.../hardware/ic_keyboard_black_24dp.xml | 9 +
.../hardware/ic_keyboard_capslock_black_24dp.xml | 9 +
.../hardware/ic_keyboard_hide_black_24dp.xml | 9 +
.../hardware/ic_keyboard_return_black_24dp.xml | 9 +
.../hardware/ic_keyboard_tab_black_24dp.xml | 9 +
.../hardware/ic_keyboard_voice_black_24dp.xml | 9 +
.../hardware/ic_laptop_black_24dp.xml | 9 +
.../hardware/ic_laptop_chromebook_black_24dp.xml | 9 +
.../hardware/ic_laptop_mac_black_24dp.xml | 9 +
.../hardware/ic_laptop_windows_black_24dp.xml | 9 +
.../hardware/ic_memory_black_24dp.xml | 9 +
.../hardware/ic_mouse_black_24dp.xml | 9 +
.../hardware/ic_phone_android_black_24dp.xml | 9 +
.../hardware/ic_phone_iphone_black_24dp.xml | 9 +
.../hardware/ic_phonelink_black_24dp.xml | 9 +
.../hardware/ic_phonelink_off_black_24dp.xml | 9 +
.../hardware/ic_power_input_black_24dp.xml | 9 +
.../hardware/ic_router_black_24dp.xml | 9 +
.../hardware/ic_scanner_black_24dp.xml | 9 +
.../hardware/ic_security_black_24dp.xml | 9 +
.../hardware/ic_sim_card_black_24dp.xml | 9 +
.../hardware/ic_smartphone_black_24dp.xml | 9 +
.../hardware/ic_speaker_black_24dp.xml | 9 +
.../hardware/ic_speaker_group_black_24dp.xml | 15 +
.../hardware/ic_tablet_android_black_24dp.xml | 9 +
.../hardware/ic_tablet_black_24dp.xml | 9 +
.../hardware/ic_tablet_mac_black_24dp.xml | 9 +
.../hardware/ic_toys_black_24dp.xml | 9 +
.../hardware/ic_tv_black_24dp.xml | 9 +
.../hardware/ic_videogame_asset_black_24dp.xml | 12 +
.../hardware/ic_watch_black_24dp.xml | 9 +
.../image/ic_add_a_photo_black_24dp.xml | 9 +
.../image/ic_add_to_photos_black_24dp.xml | 9 +
.../image/ic_adjust_black_24dp.xml | 9 +
.../image/ic_assistant_black_24dp.xml | 9 +
.../image/ic_assistant_photo_black_24dp.xml | 9 +
.../image/ic_audiotrack_black_24dp.xml | 9 +
.../image/ic_blur_circular_black_24dp.xml | 9 +
.../image/ic_blur_linear_black_24dp.xml | 9 +
.../image/ic_blur_off_black_24dp.xml | 9 +
.../image/ic_blur_on_black_24dp.xml | 9 +
.../image/ic_brightness_1_black_24dp.xml | 9 +
.../image/ic_brightness_2_black_24dp.xml | 9 +
.../image/ic_brightness_3_black_24dp.xml | 9 +
.../image/ic_brightness_4_black_24dp.xml | 9 +
.../image/ic_brightness_5_black_24dp.xml | 9 +
.../image/ic_brightness_6_black_24dp.xml | 9 +
.../image/ic_brightness_7_black_24dp.xml | 9 +
.../image/ic_broken_image_black_24dp.xml | 9 +
.../image/ic_brush_black_24dp.xml | 9 +
.../image/ic_camera_alt_black_24dp.xml | 12 +
.../image/ic_camera_black_24dp.xml | 9 +
.../image/ic_camera_front_black_24dp.xml | 9 +
.../image/ic_camera_rear_black_24dp.xml | 9 +
.../image/ic_camera_roll_black_24dp.xml | 9 +
.../image/ic_center_focus_strong_black_24dp.xml | 9 +
.../image/ic_center_focus_weak_black_24dp.xml | 9 +
.../image/ic_collections_black_24dp.xml | 9 +
.../image/ic_collections_bookmark_black_24dp.xml | 9 +
.../image/ic_color_lens_black_24dp.xml | 9 +
.../image/ic_colorize_black_24dp.xml | 9 +
.../image/ic_compare_black_24dp.xml | 9 +
.../image/ic_control_point_black_24dp.xml | 9 +
.../ic_control_point_duplicate_black_24dp.xml | 9 +
.../image/ic_crop_16_9_black_24dp.xml | 9 +
.../image/ic_crop_3_2_black_24dp.xml | 9 +
.../image/ic_crop_5_4_black_24dp.xml | 9 +
.../image/ic_crop_7_5_black_24dp.xml | 9 +
.../image/ic_crop_black_24dp.xml | 9 +
.../image/ic_crop_din_black_24dp.xml | 9 +
.../image/ic_crop_free_black_24dp.xml | 9 +
.../image/ic_crop_landscape_black_24dp.xml | 9 +
.../image/ic_crop_original_black_24dp.xml | 9 +
.../image/ic_crop_portrait_black_24dp.xml | 9 +
.../image/ic_crop_rotate_black_24dp.xml | 9 +
.../image/ic_crop_square_black_24dp.xml | 9 +
.../image/ic_dehaze_black_24dp.xml | 9 +
.../image/ic_details_black_24dp.xml | 9 +
.../image/ic_edit_black_24dp.xml | 9 +
.../image/ic_exposure_black_24dp.xml | 9 +
.../image/ic_exposure_neg_1_black_24dp.xml | 9 +
.../image/ic_exposure_neg_2_black_24dp.xml | 9 +
.../image/ic_exposure_plus_1_black_24dp.xml | 9 +
.../image/ic_exposure_plus_2_black_24dp.xml | 9 +
.../image/ic_exposure_zero_black_24dp.xml | 9 +
.../image/ic_filter_1_black_24dp.xml | 9 +
.../image/ic_filter_2_black_24dp.xml | 9 +
.../image/ic_filter_3_black_24dp.xml | 9 +
.../image/ic_filter_4_black_24dp.xml | 9 +
.../image/ic_filter_5_black_24dp.xml | 9 +
.../image/ic_filter_6_black_24dp.xml | 9 +
.../image/ic_filter_7_black_24dp.xml | 9 +
.../image/ic_filter_8_black_24dp.xml | 9 +
.../image/ic_filter_9_black_24dp.xml | 9 +
.../image/ic_filter_9_plus_black_24dp.xml | 9 +
.../image/ic_filter_b_and_w_black_24dp.xml | 9 +
.../image/ic_filter_black_24dp.xml | 9 +
.../image/ic_filter_center_focus_black_24dp.xml | 9 +
.../image/ic_filter_drama_black_24dp.xml | 9 +
.../image/ic_filter_frames_black_24dp.xml | 9 +
.../image/ic_filter_hdr_black_24dp.xml | 9 +
.../image/ic_filter_none_black_24dp.xml | 9 +
.../image/ic_filter_tilt_shift_black_24dp.xml | 9 +
.../image/ic_filter_vintage_black_24dp.xml | 9 +
.../image/ic_flare_black_24dp.xml | 9 +
.../image/ic_flash_auto_black_24dp.xml | 9 +
.../image/ic_flash_off_black_24dp.xml | 9 +
.../image/ic_flash_on_black_24dp.xml | 9 +
.../image/ic_flip_black_24dp.xml | 9 +
.../image/ic_gradient_black_24dp.xml | 9 +
.../image/ic_grain_black_24dp.xml | 9 +
.../image/ic_grid_off_black_24dp.xml | 9 +
.../image/ic_grid_on_black_24dp.xml | 9 +
.../image/ic_hdr_off_black_24dp.xml | 9 +
.../image/ic_hdr_on_black_24dp.xml | 9 +
.../image/ic_hdr_strong_black_24dp.xml | 9 +
.../image/ic_hdr_weak_black_24dp.xml | 9 +
.../image/ic_healing_black_24dp.xml | 9 +
.../image/ic_image_aspect_ratio_black_24dp.xml | 9 +
.../image/ic_image_black_24dp.xml | 9 +
.../image/ic_iso_black_24dp.xml | 9 +
.../image/ic_landscape_black_24dp.xml | 9 +
.../image/ic_leak_add_black_24dp.xml | 9 +
.../image/ic_leak_remove_black_24dp.xml | 9 +
.../image/ic_lens_black_24dp.xml | 9 +
.../image/ic_linked_camera_black_24dp.xml | 15 +
.../image/ic_looks_3_black_24dp.xml | 9 +
.../image/ic_looks_4_black_24dp.xml | 9 +
.../image/ic_looks_5_black_24dp.xml | 9 +
.../image/ic_looks_6_black_24dp.xml | 9 +
.../image/ic_looks_black_24dp.xml | 9 +
.../image/ic_looks_one_black_24dp.xml | 9 +
.../image/ic_looks_two_black_24dp.xml | 9 +
.../image/ic_loupe_black_24dp.xml | 9 +
.../image/ic_monochrome_photos_black_24dp.xml | 9 +
.../image/ic_movie_creation_black_24dp.xml | 9 +
.../image/ic_movie_filter_black_24dp.xml | 9 +
.../image/ic_music_note_black_24dp.xml | 9 +
.../image/ic_nature_black_24dp.xml | 9 +
.../image/ic_nature_people_black_24dp.xml | 9 +
.../image/ic_navigate_before_black_24dp.xml | 9 +
.../image/ic_navigate_next_black_24dp.xml | 9 +
.../image/ic_palette_black_24dp.xml | 9 +
.../image/ic_panorama_black_24dp.xml | 9 +
.../image/ic_panorama_fish_eye_black_24dp.xml | 9 +
.../image/ic_panorama_horizontal_black_24dp.xml | 9 +
.../image/ic_panorama_vertical_black_24dp.xml | 9 +
.../image/ic_panorama_wide_angle_black_24dp.xml | 9 +
.../image/ic_photo_album_black_24dp.xml | 9 +
.../image/ic_photo_black_24dp.xml | 9 +
.../image/ic_photo_camera_black_24dp.xml | 12 +
.../image/ic_photo_filter_black_24dp.xml | 9 +
.../image/ic_photo_library_black_24dp.xml | 9 +
.../ic_photo_size_select_actual_black_24dp.xml | 9 +
.../ic_photo_size_select_large_black_24dp.xml | 9 +
.../ic_photo_size_select_small_black_24dp.xml | 9 +
.../image/ic_picture_as_pdf_black_24dp.xml | 9 +
.../image/ic_portrait_black_24dp.xml | 9 +
.../image/ic_remove_red_eye_black_24dp.xml | 9 +
.../image/ic_rotate_90_degrees_ccw_black_24dp.xml | 9 +
.../image/ic_rotate_left_black_24dp.xml | 9 +
.../image/ic_rotate_right_black_24dp.xml | 9 +
.../image/ic_slideshow_black_24dp.xml | 9 +
.../image/ic_straighten_black_24dp.xml | 9 +
.../image/ic_style_black_24dp.xml | 9 +
.../image/ic_switch_camera_black_24dp.xml | 9 +
.../image/ic_switch_video_black_24dp.xml | 9 +
.../image/ic_tag_faces_black_24dp.xml | 9 +
.../image/ic_texture_black_24dp.xml | 9 +
.../image/ic_timelapse_black_24dp.xml | 9 +
.../image/ic_timer_10_black_24dp.xml | 9 +
.../image/ic_timer_3_black_24dp.xml | 9 +
.../image/ic_timer_black_24dp.xml | 9 +
.../image/ic_timer_off_black_24dp.xml | 9 +
.../image/ic_tonality_black_24dp.xml | 9 +
.../image/ic_transform_black_24dp.xml | 9 +
.../image/ic_tune_black_24dp.xml | 9 +
.../image/ic_view_comfy_black_24dp.xml | 9 +
.../image/ic_view_compact_black_24dp.xml | 9 +
.../image/ic_vignette_black_24dp.xml | 9 +
.../image/ic_wb_auto_black_24dp.xml | 9 +
.../image/ic_wb_cloudy_black_24dp.xml | 9 +
.../image/ic_wb_incandescent_black_24dp.xml | 9 +
.../image/ic_wb_iridescent_black_24dp.xml | 9 +
.../image/ic_wb_sunny_black_24dp.xml | 9 +
.../maps/ic_add_location_black_24dp.xml | 9 +
.../maps/ic_beenhere_black_24dp.xml | 9 +
.../maps/ic_directions_bike_black_24dp.xml | 9 +
.../maps/ic_directions_black_24dp.xml | 9 +
.../maps/ic_directions_boat_black_24dp.xml | 9 +
.../maps/ic_directions_bus_black_24dp.xml | 9 +
.../maps/ic_directions_car_black_24dp.xml | 9 +
.../maps/ic_directions_railway_black_24dp.xml | 9 +
.../maps/ic_directions_run_black_24dp.xml | 9 +
.../maps/ic_directions_subway_black_24dp.xml | 9 +
.../maps/ic_directions_transit_black_24dp.xml | 9 +
.../maps/ic_directions_walk_black_24dp.xml | 9 +
.../maps/ic_edit_location_black_24dp.xml | 9 +
.../maps/ic_flight_black_24dp.xml | 12 +
.../maps/ic_hotel_black_24dp.xml | 9 +
.../maps/ic_layers_black_24dp.xml | 9 +
.../maps/ic_layers_clear_black_24dp.xml | 9 +
.../maps/ic_local_activity_black_24dp.xml | 9 +
.../maps/ic_local_airport_black_24dp.xml | 9 +
.../maps/ic_local_atm_black_24dp.xml | 9 +
.../maps/ic_local_bar_black_24dp.xml | 9 +
.../maps/ic_local_cafe_black_24dp.xml | 9 +
.../maps/ic_local_car_wash_black_24dp.xml | 9 +
.../maps/ic_local_convenience_store_black_24dp.xml | 9 +
.../maps/ic_local_dining_black_24dp.xml | 9 +
.../maps/ic_local_drink_black_24dp.xml | 9 +
.../maps/ic_local_florist_black_24dp.xml | 9 +
.../maps/ic_local_gas_station_black_24dp.xml | 9 +
.../maps/ic_local_grocery_store_black_24dp.xml | 9 +
.../maps/ic_local_hospital_black_24dp.xml | 9 +
.../maps/ic_local_hotel_black_24dp.xml | 9 +
.../maps/ic_local_laundry_service_black_24dp.xml | 9 +
.../maps/ic_local_library_black_24dp.xml | 9 +
.../maps/ic_local_mall_black_24dp.xml | 9 +
.../maps/ic_local_movies_black_24dp.xml | 9 +
.../maps/ic_local_offer_black_24dp.xml | 9 +
.../maps/ic_local_parking_black_24dp.xml | 9 +
.../maps/ic_local_pharmacy_black_24dp.xml | 9 +
.../maps/ic_local_phone_black_24dp.xml | 9 +
.../maps/ic_local_pizza_black_24dp.xml | 9 +
.../maps/ic_local_play_black_24dp.xml | 9 +
.../maps/ic_local_post_office_black_24dp.xml | 9 +
.../maps/ic_local_printshop_black_24dp.xml | 9 +
.../maps/ic_local_see_black_24dp.xml | 12 +
.../maps/ic_local_shipping_black_24dp.xml | 9 +
.../maps/ic_local_taxi_black_24dp.xml | 9 +
.../maps/ic_map_black_24dp.xml | 9 +
.../maps/ic_my_location_black_24dp.xml | 9 +
.../maps/ic_navigation_black_24dp.xml | 9 +
.../maps/ic_near_me_black_24dp.xml | 9 +
.../maps/ic_person_pin_black_24dp.xml | 9 +
.../maps/ic_person_pin_circle_black_24dp.xml | 9 +
.../maps/ic_pin_drop_black_24dp.xml | 9 +
.../maps/ic_place_black_24dp.xml | 9 +
.../maps/ic_rate_review_black_24dp.xml | 9 +
.../maps/ic_restaurant_menu_black_24dp.xml | 9 +
.../maps/ic_satellite_black_24dp.xml | 9 +
.../maps/ic_store_mall_directory_black_24dp.xml | 9 +
.../maps/ic_terrain_black_24dp.xml | 9 +
.../maps/ic_traffic_black_24dp.xml | 9 +
.../maps/ic_zoom_out_map_black_24dp.xml | 9 +
.../navigation/ic_apps_black_24dp.xml | 9 +
.../navigation/ic_arrow_back_black_24dp.xml | 9 +
.../navigation/ic_arrow_downward_black_24dp.xml | 9 +
.../navigation/ic_arrow_drop_down_black_24dp.xml | 9 +
.../ic_arrow_drop_down_circle_black_24dp.xml | 9 +
.../navigation/ic_arrow_drop_up_black_24dp.xml | 9 +
.../navigation/ic_arrow_forward_black_24dp.xml | 9 +
.../navigation/ic_arrow_upward_black_24dp.xml | 9 +
.../navigation/ic_cancel_black_24dp.xml | 9 +
.../navigation/ic_check_black_24dp.xml | 9 +
.../navigation/ic_chevron_left_black_24dp.xml | 9 +
.../navigation/ic_chevron_right_black_24dp.xml | 9 +
.../navigation/ic_close_black_24dp.xml | 9 +
.../navigation/ic_expand_less_black_24dp.xml | 9 +
.../navigation/ic_expand_more_black_24dp.xml | 9 +
.../navigation/ic_fullscreen_black_24dp.xml | 9 +
.../navigation/ic_fullscreen_exit_black_24dp.xml | 9 +
.../navigation/ic_menu_black_24dp.xml | 9 +
.../navigation/ic_more_horiz_black_24dp.xml | 9 +
.../navigation/ic_more_vert_black_24dp.xml | 9 +
.../navigation/ic_refresh_black_24dp.xml | 9 +
.../ic_subdirectory_arrow_left_black_24dp.xml | 9 +
.../ic_subdirectory_arrow_right_black_24dp.xml | 9 +
.../navigation/ic_unfold_less_black_24dp.xml | 9 +
.../navigation/ic_unfold_more_black_24dp.xml | 9 +
.../notification/ic_adb_black_24dp.xml | 9 +
.../ic_airline_seat_flat_angled_black_24dp.xml | 9 +
.../ic_airline_seat_flat_black_24dp.xml | 9 +
...ic_airline_seat_individual_suite_black_24dp.xml | 9 +
.../ic_airline_seat_legroom_extra_black_24dp.xml | 9 +
.../ic_airline_seat_legroom_normal_black_24dp.xml | 9 +
.../ic_airline_seat_legroom_reduced_black_24dp.xml | 9 +
.../ic_airline_seat_recline_extra_black_24dp.xml | 9 +
.../ic_airline_seat_recline_normal_black_24dp.xml | 9 +
.../notification/ic_bluetooth_audio_black_24dp.xml | 9 +
.../ic_confirmation_number_black_24dp.xml | 9 +
.../notification/ic_disc_full_black_24dp.xml | 9 +
.../ic_do_not_disturb_alt_black_24dp.xml | 9 +
.../notification/ic_do_not_disturb_black_24dp.xml | 9 +
.../notification/ic_drive_eta_black_24dp.xml | 9 +
.../ic_enhanced_encryption_black_24dp.xml | 9 +
.../notification/ic_event_available_black_24dp.xml | 9 +
.../notification/ic_event_busy_black_24dp.xml | 9 +
.../notification/ic_event_note_black_24dp.xml | 9 +
.../notification/ic_folder_special_black_24dp.xml | 9 +
.../notification/ic_live_tv_black_24dp.xml | 9 +
.../notification/ic_mms_black_24dp.xml | 9 +
.../notification/ic_more_black_24dp.xml | 9 +
.../notification/ic_network_check_black_24dp.xml | 9 +
.../notification/ic_network_locked_black_24dp.xml | 9 +
.../notification/ic_no_encryption_black_24dp.xml | 9 +
.../notification/ic_ondemand_video_black_24dp.xml | 9 +
.../notification/ic_personal_video_black_24dp.xml | 9 +
.../ic_phone_bluetooth_speaker_black_24dp.xml | 9 +
.../notification/ic_phone_forwarded_black_24dp.xml | 9 +
.../notification/ic_phone_in_talk_black_24dp.xml | 9 +
.../notification/ic_phone_locked_black_24dp.xml | 9 +
.../notification/ic_phone_missed_black_24dp.xml | 9 +
.../notification/ic_phone_paused_black_24dp.xml | 9 +
.../notification/ic_power_black_24dp.xml | 9 +
.../notification/ic_rv_hookup_black_24dp.xml | 9 +
.../notification/ic_sd_card_black_24dp.xml | 9 +
.../notification/ic_sim_card_alert_black_24dp.xml | 9 +
.../notification/ic_sms_black_24dp.xml | 9 +
.../notification/ic_sms_failed_black_24dp.xml | 9 +
.../notification/ic_sync_black_24dp.xml | 9 +
.../notification/ic_sync_disabled_black_24dp.xml | 9 +
.../notification/ic_sync_problem_black_24dp.xml | 9 +
.../notification/ic_system_update_black_24dp.xml | 9 +
.../notification/ic_tap_and_play_black_24dp.xml | 9 +
.../notification/ic_time_to_leave_black_24dp.xml | 9 +
.../notification/ic_vibration_black_24dp.xml | 9 +
.../notification/ic_voice_chat_black_24dp.xml | 9 +
.../notification/ic_vpn_lock_black_24dp.xml | 9 +
.../notification/ic_wc_black_24dp.xml | 9 +
.../notification/ic_wifi_black_24dp.xml | 9 +
.../places/ic_ac_unit_black_24dp.xml | 9 +
.../places/ic_airport_shuttle_black_24dp.xml | 9 +
.../places/ic_all_inclusive_black_24dp.xml | 9 +
.../places/ic_beach_access_black_24dp.xml | 9 +
.../places/ic_business_center_black_24dp.xml | 9 +
.../places/ic_casino_black_24dp.xml | 9 +
.../places/ic_child_care_black_24dp.xml | 15 +
.../places/ic_child_friendly_black_24dp.xml | 9 +
.../places/ic_fitness_center_black_24dp.xml | 9 +
.../places/ic_free_breakfast_black_24dp.xml | 9 +
.../places/ic_golf_course_black_24dp.xml | 12 +
.../places/ic_hot_tub_black_24dp.xml | 12 +
.../places/ic_kitchen_black_24dp.xml | 9 +
.../places/ic_pool_black_24dp.xml | 12 +
.../places/ic_room_service_black_24dp.xml | 9 +
.../places/ic_smoke_free_black_24dp.xml | 9 +
.../places/ic_smoking_rooms_black_24dp.xml | 9 +
.../places/ic_spa_black_24dp.xml | 12 +
.../social/ic_cake_black_24dp.xml | 9 +
.../social/ic_domain_black_24dp.xml | 9 +
.../social/ic_group_add_black_24dp.xml | 9 +
.../social/ic_group_black_24dp.xml | 9 +
.../social/ic_location_city_black_24dp.xml | 9 +
.../social/ic_mood_bad_black_24dp.xml | 9 +
.../social/ic_mood_black_24dp.xml | 9 +
.../social/ic_notifications_active_black_24dp.xml | 9 +
.../social/ic_notifications_black_24dp.xml | 9 +
.../social/ic_notifications_none_black_24dp.xml | 9 +
.../social/ic_notifications_off_black_24dp.xml | 9 +
.../social/ic_notifications_paused_black_24dp.xml | 9 +
.../social/ic_pages_black_24dp.xml | 9 +
.../social/ic_party_mode_black_24dp.xml | 9 +
.../social/ic_people_black_24dp.xml | 9 +
.../social/ic_people_outline_black_24dp.xml | 9 +
.../social/ic_person_add_black_24dp.xml | 9 +
.../social/ic_person_black_24dp.xml | 9 +
.../social/ic_person_outline_black_24dp.xml | 9 +
.../social/ic_plus_one_black_24dp.xml | 9 +
.../social/ic_poll_black_24dp.xml | 9 +
.../social/ic_public_black_24dp.xml | 9 +
.../social/ic_school_black_24dp.xml | 9 +
.../social/ic_share_black_24dp.xml | 9 +
.../social/ic_whatshot_black_24dp.xml | 9 +
.../toggle/ic_check_box_black_24dp.xml | 9 +
.../ic_check_box_outline_blank_black_24dp.xml | 9 +
.../ic_indeterminate_check_box_black_24dp.xml | 9 +
.../toggle/ic_radio_button_checked_black_24dp.xml | 9 +
.../ic_radio_button_unchecked_black_24dp.xml | 9 +
.../toggle/ic_star_black_24dp.xml | 9 +
.../toggle/ic_star_border_black_24dp.xml | 9 +
.../toggle/ic_star_half_black_24dp.xml | 9 +
.../main/java/images/notification_stencil/hdpi.png | Bin
.../main/java/images/notification_stencil/mdpi.png | Bin
.../java/images/notification_stencil/xhdpi.png | Bin
.../assetstudiolib/ActionBarIconGeneratorTest.java | 0
.../assetstudiolib/BitmapGeneratorTest.java | 0
.../com/android/assetstudiolib/GeneratorTest.java | 0
.../assetstudiolib/LauncherIconGeneratorTest.java | 0
.../assetstudiolib/MenuIconGeneratorTest.java | 0
.../NotificationIconGeneratorTest.java | 0
.../assetstudiolib/TabIconGeneratorTest.java | 0
.../actions/res/drawable-hdpi/ic_action_dark.png | Bin
.../actions/res/drawable-hdpi/ic_action_light.png | Bin
.../actions/res/drawable-mdpi/ic_action_dark.png | Bin
.../actions/res/drawable-mdpi/ic_action_light.png | Bin
.../actions/res/drawable-xhdpi/ic_action_dark.png | Bin
.../actions/res/drawable-xhdpi/ic_action_light.png | Bin
.../actions/res/drawable-xxhdpi/ic_action_dark.png | Bin
.../res/drawable-xxhdpi/ic_action_light.png | Bin
.../testdata/launcher/red_simple_circle-web.png | Bin
.../launcher/res/mipmap-hdpi/red_simple_circle.png | Bin
.../launcher/res/mipmap-mdpi/red_simple_circle.png | Bin
.../res/mipmap-xhdpi/red_simple_circle.png | Bin
.../res/mipmap-xxhdpi/red_simple_circle.png | Bin
.../res/mipmap-xxxhdpi/red_simple_circle.png | Bin
.../testdata/menus/res/drawable-hdpi/ic_menu_1.png | Bin
.../testdata/menus/res/drawable-mdpi/ic_menu_1.png | Bin
.../menus/res/drawable-xhdpi/ic_menu_1.png | Bin
.../menus/res/drawable-xxhdpi/ic_menu_1.png | Bin
.../res/drawable-hdpi/ic_stat_1.png | Bin
.../res/drawable-mdpi/ic_stat_1.png | Bin
.../res/drawable-xhdpi/ic_stat_1.png | Bin
.../res/drawable-xxhdpi/ic_stat_1.png | Bin
.../res/drawable-hdpi-v11/ic_stat_1.png | Bin
.../res/drawable-hdpi/ic_stat_1.png | Bin
.../res/drawable-mdpi-v11/ic_stat_1.png | Bin
.../res/drawable-mdpi/ic_stat_1.png | Bin
.../res/drawable-xhdpi-v11/ic_stat_1.png | Bin
.../res/drawable-xhdpi/ic_stat_1.png | Bin
.../res/drawable-xxhdpi-v11/ic_stat_1.png | Bin
.../res/drawable-xxhdpi/ic_stat_1.png | Bin
.../res/drawable-hdpi-v11/ic_stat_1.png | Bin
.../res/drawable-hdpi-v9/ic_stat_1.png | Bin
.../notification/res/drawable-hdpi/ic_stat_1.png | Bin
.../res/drawable-mdpi-v11/ic_stat_1.png | Bin
.../res/drawable-mdpi-v9/ic_stat_1.png | Bin
.../notification/res/drawable-mdpi/ic_stat_1.png | Bin
.../res/drawable-xhdpi-v11/ic_stat_1.png | Bin
.../res/drawable-xhdpi-v9/ic_stat_1.png | Bin
.../notification/res/drawable-xhdpi/ic_stat_1.png | Bin
.../res/drawable-xxhdpi-v11/ic_stat_1.png | Bin
.../res/drawable-xxhdpi-v9/ic_stat_1.png | Bin
.../notification/res/drawable-xxhdpi/ic_stat_1.png | Bin
.../res/drawable-hdpi/ic_tab_1_selected.png | Bin
.../res/drawable-hdpi/ic_tab_1_unselected.png | Bin
.../res/drawable-ldpi/ic_tab_1_selected.png | Bin
.../res/drawable-ldpi/ic_tab_1_unselected.png | Bin
.../res/drawable-mdpi/ic_tab_1_selected.png | Bin
.../res/drawable-mdpi/ic_tab_1_unselected.png | Bin
.../res/drawable-xhdpi/ic_tab_1_selected.png | Bin
.../res/drawable-xhdpi/ic_tab_1_unselected.png | Bin
.../res/drawable-xxhdpi/ic_action_dark.png | Bin
.../res/drawable-xxhdpi/ic_action_light.png | Bin
.../res/drawable-xxhdpi/ic_tab_1_selected.png | Bin
.../res/drawable-xxhdpi/ic_tab_1_unselected.png | Bin
.../res/drawable-hdpi-v5/ic_tab_1_selected.png | Bin
.../res/drawable-hdpi-v5/ic_tab_1_unselected.png | Bin
.../tabs/res/drawable-hdpi/ic_tab_1_selected.png | Bin
.../tabs/res/drawable-hdpi/ic_tab_1_unselected.png | Bin
.../res/drawable-mdpi-v5/ic_tab_1_selected.png | Bin
.../res/drawable-mdpi-v5/ic_tab_1_unselected.png | Bin
.../tabs/res/drawable-mdpi/ic_tab_1_selected.png | Bin
.../tabs/res/drawable-mdpi/ic_tab_1_unselected.png | Bin
.../res/drawable-xhdpi-v5/ic_tab_1_selected.png | Bin
.../res/drawable-xhdpi-v5/ic_tab_1_unselected.png | Bin
.../tabs/res/drawable-xhdpi/ic_tab_1_selected.png | Bin
.../res/drawable-xhdpi/ic_tab_1_unselected.png | Bin
.../res/drawable-xxhdpi-v5/ic_tab_1_selected.png | Bin
.../res/drawable-xxhdpi-v5/ic_tab_1_unselected.png | Bin
.../tabs/res/drawable-xxhdpi/ic_tab_1_selected.png | Bin
.../res/drawable-xxhdpi/ic_tab_1_unselected.png | Bin
base/.eclipse/dictionary.txt | 407 ---
base/.gitignore | 17 -
base/.gradle/2.9/taskArtifacts/cache.properties | 1 -
.../2.9/taskArtifacts/cache.properties.lock | Bin 17 -> 0 bytes
base/.gradle/2.9/taskArtifacts/fileHashes.bin | Bin 18733 -> 0 bytes
base/.gradle/2.9/taskArtifacts/fileSnapshots.bin | Bin 19362 -> 0 bytes
.../.gradle/2.9/taskArtifacts/outputFileStates.bin | Bin 18632 -> 0 bytes
base/.gradle/2.9/taskArtifacts/taskArtifacts.bin | Bin 19764 -> 0 bytes
base/.idea/.name | 1 -
base/.idea/ant.xml | 3 -
base/.idea/codeStyleSettings.xml | 151 -
base/.idea/compiler.xml | 28 -
base/.idea/copyright/aosp.xml | 9 -
base/.idea/copyright/profiles_settings.xml | 7 -
base/.idea/dictionaries/adt.xml | 319 --
base/.idea/encodings.xml | 5 -
base/.idea/gradle.xml | 3 -
base/.idea/inspectionProfiles/Project_Default.xml | 213 --
.../.idea/inspectionProfiles/profiles_settings.xml | 7 -
base/.idea/libraries/JUnit3.xml | 9 -
base/.idea/libraries/JUnit4.xml | 13 -
base/.idea/libraries/Trove4j.xml | 12 -
base/.idea/libraries/ant.xml | 9 -
base/.idea/libraries/asm_tools.xml | 15 -
base/.idea/libraries/bouncy_castle.xml | 13 -
base/.idea/libraries/builder_model.xml | 12 -
base/.idea/libraries/commons_compress.xml | 11 -
base/.idea/libraries/easymock_tools.xml | 16 -
base/.idea/libraries/ecj.xml | 11 -
base/.idea/libraries/gradle_tooling_api_1_9.xml | 11 -
base/.idea/libraries/groovy.xml | 9 -
base/.idea/libraries/gson.xml | 11 -
base/.idea/libraries/guava_tools.xml | 14 -
base/.idea/libraries/http_client.xml | 19 -
base/.idea/libraries/icu4j.xml | 11 -
base/.idea/libraries/intellij_annotations.xml | 11 -
base/.idea/libraries/jacoco.xml | 11 -
base/.idea/libraries/javawriter.xml | 11 -
base/.idea/libraries/jsr_305.xml | 11 -
base/.idea/libraries/kxml2.xml | 11 -
base/.idea/libraries/lombok_ast.xml | 14 -
base/.idea/libraries/mockito.xml | 12 -
base/.idea/libraries/proguard_gradle.xml | 13 -
base/.idea/libraries/slf4j_api.xml | 11 -
base/.idea/libraries/slf4j_simple.xml | 11 -
base/.idea/libraries/truth.xml | 11 -
base/.idea/misc.xml | 54 -
base/.idea/modules.xml | 42 -
base/.idea/projectCodeStyle.xml | 135 -
base/.idea/scopes/custom_scope.xml | 3 -
base/.idea/scopes/scope_settings.xml | 5 -
base/.idea/uiDesigner.xml | 125 -
base/.idea/vcs.xml | 7 -
base/adt.iml | 31 -
base/annotations/build.gradle | 13 -
base/apps/DeviceConfig/.classpath | 9 -
base/apps/DeviceConfig/.gitignore | 2 -
base/apps/DeviceConfig/.project | 33 -
.../.settings/org.eclipse.jdt.core.prefs | 5 -
base/apps/DeviceConfig/AndroidManifest.xml | 29 -
base/apps/DeviceConfig/build.xml | 90 -
base/apps/DeviceConfig/project.properties | 11 -
.../android/deviceconfig/ConfigGenerator.java | 673 ----
.../example/android/deviceconfig/MyActivity.java | 186 --
.../action/ic_3d_rotation_24dp.xml | 24 -
.../action/ic_accessibility_24dp.xml | 24 -
.../action/ic_account_balance_24dp.xml | 24 -
.../action/ic_account_balance_wallet_24dp.xml | 24 -
.../action/ic_account_box_24dp.xml | 24 -
.../action/ic_account_child_24dp.xml | 27 -
.../action/ic_account_circle_24dp.xml | 24 -
.../action/ic_add_shopping_cart_24dp.xml | 24 -
.../material_design_icons/action/ic_alarm_24dp.xml | 24 -
.../action/ic_alarm_add_24dp.xml | 24 -
.../action/ic_alarm_off_24dp.xml | 24 -
.../action/ic_alarm_on_24dp.xml | 24 -
.../action/ic_android_24dp.xml | 24 -
.../action/ic_announcement_24dp.xml | 24 -
.../action/ic_aspect_ratio_24dp.xml | 24 -
.../action/ic_assessment_24dp.xml | 24 -
.../action/ic_assignment_24dp.xml | 24 -
.../action/ic_assignment_ind_24dp.xml | 24 -
.../action/ic_assignment_late_24dp.xml | 24 -
.../action/ic_assignment_return_24dp.xml | 24 -
.../action/ic_assignment_returned_24dp.xml | 24 -
.../action/ic_assignment_turned_in_24dp.xml | 24 -
.../action/ic_autorenew_24dp.xml | 24 -
.../action/ic_backup_24dp.xml | 24 -
.../material_design_icons/action/ic_book_24dp.xml | 24 -
.../action/ic_bookmark_24dp.xml | 24 -
.../action/ic_bookmark_outline_24dp.xml | 24 -
.../action/ic_bug_report_24dp.xml | 24 -
.../action/ic_cached_24dp.xml | 24 -
.../action/ic_check_circle_24dp.xml | 24 -
.../material_design_icons/action/ic_class_24dp.xml | 24 -
.../action/ic_credit_card_24dp.xml | 24 -
.../action/ic_dashboard_24dp.xml | 24 -
.../action/ic_delete_24dp.xml | 24 -
.../action/ic_description_24dp.xml | 24 -
.../material_design_icons/action/ic_dns_24dp.xml | 24 -
.../material_design_icons/action/ic_done_24dp.xml | 24 -
.../action/ic_done_all_24dp.xml | 24 -
.../material_design_icons/action/ic_event_24dp.xml | 24 -
.../action/ic_exit_to_app_24dp.xml | 24 -
.../action/ic_explore_24dp.xml | 24 -
.../action/ic_extension_24dp.xml | 24 -
.../material_design_icons/action/ic_face_24dp.xml | 33 -
.../action/ic_favorite_24dp.xml | 24 -
.../action/ic_favorite_outline_24dp.xml | 24 -
.../action/ic_find_in_page_24dp.xml | 24 -
.../action/ic_find_replace_24dp.xml | 24 -
.../action/ic_flip_to_back_24dp.xml | 24 -
.../action/ic_flip_to_front_24dp.xml | 24 -
.../action/ic_get_app_24dp.xml | 24 -
.../material_design_icons/action/ic_grade_24dp.xml | 24 -
.../action/ic_group_work_24dp.xml | 24 -
.../material_design_icons/action/ic_help_24dp.xml | 24 -
.../action/ic_highlight_remove_24dp.xml | 24 -
.../action/ic_history_24dp.xml | 25 -
.../material_design_icons/action/ic_home_24dp.xml | 24 -
.../material_design_icons/action/ic_https_24dp.xml | 24 -
.../material_design_icons/action/ic_info_24dp.xml | 24 -
.../action/ic_info_outline_24dp.xml | 24 -
.../material_design_icons/action/ic_input_24dp.xml | 24 -
.../action/ic_invert_colors_24dp.xml | 24 -
.../material_design_icons/action/ic_label_24dp.xml | 24 -
.../action/ic_label_outline_24dp.xml | 24 -
.../action/ic_language_24dp.xml | 24 -
.../action/ic_launch_24dp.xml | 24 -
.../material_design_icons/action/ic_list_24dp.xml | 24 -
.../material_design_icons/action/ic_lock_24dp.xml | 24 -
.../action/ic_lock_open_24dp.xml | 24 -
.../action/ic_lock_outline_24dp.xml | 24 -
.../action/ic_loyalty_24dp.xml | 24 -
.../action/ic_markunread_mailbox_24dp.xml | 24 -
.../action/ic_note_add_24dp.xml | 24 -
.../action/ic_open_in_browser_24dp.xml | 24 -
.../action/ic_open_in_new_24dp.xml | 24 -
.../action/ic_open_with_24dp.xml | 24 -
.../action/ic_pageview_24dp.xml | 24 -
.../action/ic_payment_24dp.xml | 24 -
.../action/ic_perm_camera_mic_24dp.xml | 24 -
.../action/ic_perm_contact_cal_24dp.xml | 24 -
.../action/ic_perm_data_setting_24dp.xml | 24 -
.../action/ic_perm_device_info_24dp.xml | 24 -
.../action/ic_perm_identity_24dp.xml | 24 -
.../action/ic_perm_media_24dp.xml | 24 -
.../action/ic_perm_phone_msg_24dp.xml | 24 -
.../action/ic_perm_scan_wifi_24dp.xml | 24 -
.../action/ic_picture_in_picture_24dp.xml | 24 -
.../action/ic_polymer_24dp.xml | 24 -
.../material_design_icons/action/ic_print_24dp.xml | 24 -
.../action/ic_query_builder_24dp.xml | 24 -
.../action/ic_question_answer_24dp.xml | 24 -
.../action/ic_receipt_24dp.xml | 24 -
.../action/ic_redeem_24dp.xml | 24 -
.../action/ic_reorder_24dp.xml | 24 -
.../action/ic_report_problem_24dp.xml | 24 -
.../action/ic_restore_24dp.xml | 24 -
.../material_design_icons/action/ic_room_24dp.xml | 24 -
.../action/ic_schedule_24dp.xml | 25 -
.../action/ic_search_24dp.xml | 24 -
.../action/ic_settings_24dp.xml | 24 -
.../action/ic_settings_applications_24dp.xml | 24 -
.../action/ic_settings_backup_restore_24dp.xml | 24 -
.../action/ic_settings_bluetooth_24dp.xml | 24 -
.../action/ic_settings_cell_24dp.xml | 24 -
.../action/ic_settings_display_24dp.xml | 24 -
.../action/ic_settings_ethernet_24dp.xml | 24 -
.../action/ic_settings_input_antenna_24dp.xml | 24 -
.../action/ic_settings_input_component_24dp.xml | 24 -
.../action/ic_settings_input_composite_24dp.xml | 24 -
.../action/ic_settings_input_hdmi_24dp.xml | 24 -
.../action/ic_settings_input_svideo_24dp.xml | 24 -
.../action/ic_settings_overscan_24dp.xml | 24 -
.../action/ic_settings_phone_24dp.xml | 24 -
.../action/ic_settings_power_24dp.xml | 24 -
.../action/ic_settings_remote_24dp.xml | 24 -
.../action/ic_settings_voice_24dp.xml | 24 -
.../material_design_icons/action/ic_shop_24dp.xml | 24 -
.../action/ic_shop_two_24dp.xml | 24 -
.../action/ic_shopping_basket_24dp.xml | 24 -
.../action/ic_shopping_cart_24dp.xml | 24 -
.../action/ic_speaker_notes_24dp.xml | 24 -
.../action/ic_spellcheck_24dp.xml | 24 -
.../action/ic_star_rate_24dp.xml | 24 -
.../material_design_icons/action/ic_stars_24dp.xml | 24 -
.../material_design_icons/action/ic_store_24dp.xml | 24 -
.../action/ic_subject_24dp.xml | 24 -
.../action/ic_supervisor_account_24dp.xml | 24 -
.../action/ic_swap_horiz_24dp.xml | 24 -
.../action/ic_swap_vert_24dp.xml | 24 -
.../action/ic_swap_vert_circle_24dp.xml | 24 -
.../action/ic_system_update_tv_24dp.xml | 24 -
.../material_design_icons/action/ic_tab_24dp.xml | 24 -
.../action/ic_tab_unselected_24dp.xml | 24 -
.../action/ic_theaters_24dp.xml | 24 -
.../action/ic_thumb_down_24dp.xml | 24 -
.../action/ic_thumb_up_24dp.xml | 24 -
.../action/ic_thumbs_up_down_24dp.xml | 24 -
.../material_design_icons/action/ic_toc_24dp.xml | 24 -
.../material_design_icons/action/ic_today_24dp.xml | 24 -
.../action/ic_track_changes_24dp.xml | 24 -
.../action/ic_translate_24dp.xml | 24 -
.../action/ic_trending_down_24dp.xml | 24 -
.../action/ic_trending_neutral_24dp.xml | 24 -
.../action/ic_trending_up_24dp.xml | 24 -
.../action/ic_turned_in_24dp.xml | 24 -
.../action/ic_turned_in_not_24dp.xml | 24 -
.../action/ic_verified_user_24dp.xml | 24 -
.../action/ic_view_agenda_24dp.xml | 24 -
.../action/ic_view_array_24dp.xml | 24 -
.../action/ic_view_carousel_24dp.xml | 24 -
.../action/ic_view_column_24dp.xml | 24 -
.../action/ic_view_day_24dp.xml | 24 -
.../action/ic_view_headline_24dp.xml | 24 -
.../action/ic_view_list_24dp.xml | 24 -
.../action/ic_view_module_24dp.xml | 24 -
.../action/ic_view_quilt_24dp.xml | 24 -
.../action/ic_view_stream_24dp.xml | 24 -
.../action/ic_view_week_24dp.xml | 24 -
.../action/ic_visibility_24dp.xml | 24 -
.../action/ic_visibility_off_24dp.xml | 24 -
.../action/ic_wallet_giftcard_24dp.xml | 24 -
.../action/ic_wallet_membership_24dp.xml | 24 -
.../action/ic_wallet_travel_24dp.xml | 24 -
.../material_design_icons/action/ic_work_24dp.xml | 24 -
.../material_design_icons/alert/ic_error_24dp.xml | 24 -
.../alert/ic_warning_24dp.xml | 24 -
.../material_design_icons/av/ic_album_24dp.xml | 24 -
.../material_design_icons/av/ic_av_timer_24dp.xml | 24 -
.../av/ic_closed_caption_24dp.xml | 24 -
.../material_design_icons/av/ic_equalizer_24dp.xml | 24 -
.../material_design_icons/av/ic_explicit_24dp.xml | 24 -
.../av/ic_fast_forward_24dp.xml | 24 -
.../av/ic_fast_rewind_24dp.xml | 24 -
.../material_design_icons/av/ic_games_24dp.xml | 24 -
.../material_design_icons/av/ic_hearing_24dp.xml | 24 -
.../av/ic_high_quality_24dp.xml | 24 -
.../material_design_icons/av/ic_loop_24dp.xml | 24 -
.../material_design_icons/av/ic_mic_24dp.xml | 24 -
.../material_design_icons/av/ic_mic_none_24dp.xml | 24 -
.../material_design_icons/av/ic_mic_off_24dp.xml | 24 -
.../material_design_icons/av/ic_movie_24dp.xml | 24 -
.../av/ic_my_library_add_24dp.xml | 24 -
.../av/ic_my_library_books_24dp.xml | 24 -
.../av/ic_my_library_music_24dp.xml | 24 -
.../av/ic_new_releases_24dp.xml | 24 -
.../av/ic_not_interested_24dp.xml | 24 -
.../material_design_icons/av/ic_pause_24dp.xml | 24 -
.../av/ic_pause_circle_fill_24dp.xml | 24 -
.../av/ic_pause_circle_outline_24dp.xml | 24 -
.../av/ic_play_arrow_24dp.xml | 24 -
.../av/ic_play_circle_fill_24dp.xml | 24 -
.../av/ic_play_circle_outline_24dp.xml | 24 -
.../av/ic_play_shopping_bag_24dp.xml | 24 -
.../av/ic_playlist_add_24dp.xml | 24 -
.../material_design_icons/av/ic_queue_24dp.xml | 24 -
.../av/ic_queue_music_24dp.xml | 24 -
.../material_design_icons/av/ic_radio_24dp.xml | 24 -
.../av/ic_recent_actors_24dp.xml | 24 -
.../material_design_icons/av/ic_repeat_24dp.xml | 24 -
.../av/ic_repeat_one_24dp.xml | 24 -
.../material_design_icons/av/ic_replay_24dp.xml | 24 -
.../material_design_icons/av/ic_shuffle_24dp.xml | 24 -
.../material_design_icons/av/ic_skip_next_24dp.xml | 24 -
.../av/ic_skip_previous_24dp.xml | 24 -
.../material_design_icons/av/ic_snooze_24dp.xml | 24 -
.../material_design_icons/av/ic_stop_24dp.xml | 24 -
.../material_design_icons/av/ic_subtitles_24dp.xml | 24 -
.../av/ic_surround_sound_24dp.xml | 24 -
.../av/ic_video_collection_24dp.xml | 24 -
.../material_design_icons/av/ic_videocam_24dp.xml | 24 -
.../av/ic_videocam_off_24dp.xml | 24 -
.../av/ic_volume_down_24dp.xml | 24 -
.../av/ic_volume_mute_24dp.xml | 24 -
.../av/ic_volume_off_24dp.xml | 24 -
.../material_design_icons/av/ic_volume_up_24dp.xml | 24 -
.../material_design_icons/av/ic_web_24dp.xml | 24 -
.../communication/ic_business_24dp.xml | 24 -
.../communication/ic_call_24dp.xml | 24 -
.../communication/ic_call_end_24dp.xml | 24 -
.../communication/ic_call_made_24dp.xml | 24 -
.../communication/ic_call_merge_24dp.xml | 24 -
.../communication/ic_call_missed_24dp.xml | 24 -
.../communication/ic_call_received_24dp.xml | 24 -
.../communication/ic_call_split_24dp.xml | 24 -
.../communication/ic_chat_24dp.xml | 24 -
.../communication/ic_clear_all_24dp.xml | 24 -
.../communication/ic_comment_24dp.xml | 24 -
.../communication/ic_contacts_24dp.xml | 24 -
.../communication/ic_dialer_sip_24dp.xml | 24 -
.../communication/ic_dialpad_24dp.xml | 24 -
.../communication/ic_dnd_on_24dp.xml | 24 -
.../communication/ic_email_24dp.xml | 24 -
.../communication/ic_forum_24dp.xml | 24 -
.../communication/ic_import_export_24dp.xml | 24 -
.../communication/ic_invert_colors_off_24dp.xml | 24 -
.../communication/ic_invert_colors_on_24dp.xml | 24 -
.../communication/ic_live_help_24dp.xml | 24 -
.../communication/ic_location_off_24dp.xml | 24 -
.../communication/ic_location_on_24dp.xml | 24 -
.../communication/ic_message_24dp.xml | 24 -
.../communication/ic_messenger_24dp.xml | 24 -
.../communication/ic_no_sim_24dp.xml | 24 -
.../communication/ic_phone_24dp.xml | 24 -
.../communication/ic_portable_wifi_off_24dp.xml | 24 -
.../ic_quick_contacts_dialer_24dp.xml | 24 -
.../communication/ic_quick_contacts_mail_24dp.xml | 24 -
.../communication/ic_ring_volume_24dp.xml | 24 -
.../ic_stay_current_landscape_24dp.xml | 24 -
.../ic_stay_current_portrait_24dp.xml | 24 -
.../ic_stay_primary_landscape_24dp.xml | 24 -
.../ic_stay_primary_portrait_24dp.xml | 24 -
.../communication/ic_swap_calls_24dp.xml | 24 -
.../communication/ic_textsms_24dp.xml | 24 -
.../communication/ic_voicemail_24dp.xml | 24 -
.../communication/ic_vpn_key_24dp.xml | 24 -
.../material_design_icons/content/ic_add_24dp.xml | 24 -
.../content/ic_add_box_24dp.xml | 24 -
.../content/ic_add_circle_24dp.xml | 24 -
.../content/ic_add_circle_outline_24dp.xml | 24 -
.../content/ic_archive_24dp.xml | 24 -
.../content/ic_backspace_24dp.xml | 24 -
.../content/ic_block_24dp.xml | 24 -
.../content/ic_clear_24dp.xml | 24 -
.../content/ic_content_copy_24dp.xml | 24 -
.../content/ic_content_cut_24dp.xml | 24 -
.../content/ic_content_paste_24dp.xml | 24 -
.../content/ic_create_24dp.xml | 24 -
.../content/ic_drafts_24dp.xml | 24 -
.../content/ic_filter_list_24dp.xml | 24 -
.../material_design_icons/content/ic_flag_24dp.xml | 24 -
.../content/ic_forward_24dp.xml | 24 -
.../content/ic_gesture_24dp.xml | 24 -
.../content/ic_inbox_24dp.xml | 24 -
.../material_design_icons/content/ic_link_24dp.xml | 24 -
.../material_design_icons/content/ic_mail_24dp.xml | 24 -
.../content/ic_markunread_24dp.xml | 24 -
.../material_design_icons/content/ic_redo_24dp.xml | 24 -
.../content/ic_remove_24dp.xml | 24 -
.../content/ic_remove_circle_24dp.xml | 24 -
.../content/ic_remove_circle_outline_24dp.xml | 24 -
.../content/ic_reply_24dp.xml | 24 -
.../content/ic_reply_all_24dp.xml | 24 -
.../content/ic_report_24dp.xml | 24 -
.../material_design_icons/content/ic_save_24dp.xml | 24 -
.../content/ic_select_all_24dp.xml | 24 -
.../material_design_icons/content/ic_send_24dp.xml | 24 -
.../material_design_icons/content/ic_sort_24dp.xml | 24 -
.../content/ic_text_format_24dp.xml | 24 -
.../material_design_icons/content/ic_undo_24dp.xml | 24 -
.../device/ic_access_alarm_24dp.xml | 24 -
.../device/ic_access_alarms_24dp.xml | 24 -
.../device/ic_access_time_24dp.xml | 25 -
.../device/ic_add_alarm_24dp.xml | 24 -
.../device/ic_airplanemode_off_24dp.xml | 24 -
.../device/ic_airplanemode_on_24dp.xml | 27 -
.../device/ic_battery_20_24dp.xml | 28 -
.../device/ic_battery_30_24dp.xml | 28 -
.../device/ic_battery_50_24dp.xml | 28 -
.../device/ic_battery_60_24dp.xml | 28 -
.../device/ic_battery_80_24dp.xml | 28 -
.../device/ic_battery_90_24dp.xml | 28 -
.../device/ic_battery_alert_24dp.xml | 24 -
.../device/ic_battery_charging_20_24dp.xml | 28 -
.../device/ic_battery_charging_30_24dp.xml | 28 -
.../device/ic_battery_charging_50_24dp.xml | 28 -
.../device/ic_battery_charging_60_24dp.xml | 28 -
.../device/ic_battery_charging_80_24dp.xml | 28 -
.../device/ic_battery_charging_90_24dp.xml | 28 -
.../device/ic_battery_charging_full_24dp.xml | 24 -
.../device/ic_battery_full_24dp.xml | 24 -
.../device/ic_battery_std_24dp.xml | 24 -
.../device/ic_battery_unknown_24dp.xml | 24 -
.../device/ic_bluetooth_24dp.xml | 24 -
.../device/ic_bluetooth_connected_24dp.xml | 24 -
.../device/ic_bluetooth_disabled_24dp.xml | 24 -
.../device/ic_bluetooth_searching_24dp.xml | 24 -
.../device/ic_brightness_auto_24dp.xml | 24 -
.../device/ic_brightness_high_24dp.xml | 24 -
.../device/ic_brightness_low_24dp.xml | 24 -
.../device/ic_brightness_medium_24dp.xml | 24 -
.../device/ic_data_usage_24dp.xml | 24 -
.../device/ic_developer_mode_24dp.xml | 24 -
.../device/ic_devices_24dp.xml | 24 -
.../material_design_icons/device/ic_dvr_24dp.xml | 24 -
.../device/ic_gps_fixed_24dp.xml | 24 -
.../device/ic_gps_not_fixed_24dp.xml | 24 -
.../device/ic_gps_off_24dp.xml | 24 -
.../device/ic_location_disabled_24dp.xml | 24 -
.../device/ic_location_searching_24dp.xml | 24 -
.../device/ic_multitrack_audio_24dp.xml | 24 -
.../device/ic_network_cell_24dp.xml | 28 -
.../device/ic_network_wifi_24dp.xml | 28 -
.../material_design_icons/device/ic_nfc_24dp.xml | 24 -
.../device/ic_now_wallpaper_24dp.xml | 24 -
.../device/ic_now_widgets_24dp.xml | 24 -
.../device/ic_screen_lock_landscape_24dp.xml | 24 -
.../device/ic_screen_lock_portrait_24dp.xml | 24 -
.../device/ic_screen_lock_rotation_24dp.xml | 24 -
.../device/ic_screen_rotation_24dp.xml | 24 -
.../device/ic_sd_storage_24dp.xml | 24 -
.../device/ic_settings_system_daydream_24dp.xml | 24 -
.../device/ic_signal_cellular_0_bar_24dp.xml | 25 -
.../device/ic_signal_cellular_1_bar_24dp.xml | 28 -
.../device/ic_signal_cellular_2_bar_24dp.xml | 28 -
.../device/ic_signal_cellular_3_bar_24dp.xml | 28 -
.../device/ic_signal_cellular_4_bar_24dp.xml | 24 -
...l_cellular_connected_no_internet_0_bar_24dp.xml | 28 -
...l_cellular_connected_no_internet_1_bar_24dp.xml | 28 -
...l_cellular_connected_no_internet_2_bar_24dp.xml | 28 -
...l_cellular_connected_no_internet_3_bar_24dp.xml | 28 -
...l_cellular_connected_no_internet_4_bar_24dp.xml | 24 -
.../device/ic_signal_cellular_no_sim_24dp.xml | 24 -
.../device/ic_signal_cellular_null_24dp.xml | 24 -
.../device/ic_signal_cellular_off_24dp.xml | 24 -
.../device/ic_signal_wifi_0_bar_24dp.xml | 25 -
.../device/ic_signal_wifi_1_bar_24dp.xml | 28 -
.../device/ic_signal_wifi_2_bar_24dp.xml | 28 -
.../device/ic_signal_wifi_3_bar_24dp.xml | 28 -
.../device/ic_signal_wifi_4_bar_24dp.xml | 24 -
.../device/ic_signal_wifi_off_24dp.xml | 24 -
.../ic_signal_wifi_statusbar_1_bar_26x24px.xml | 28 -
.../ic_signal_wifi_statusbar_2_bar_26x24px.xml | 28 -
.../ic_signal_wifi_statusbar_3_bar_26x24px.xml | 28 -
.../ic_signal_wifi_statusbar_4_bar_26x24px.xml | 24 -
...i_statusbar_connected_no_internet_1_26x24px.xml | 28 -
...ifi_statusbar_connected_no_internet_26x24px.xml | 28 -
...i_statusbar_connected_no_internet_2_26x24px.xml | 28 -
...i_statusbar_connected_no_internet_3_26x24px.xml | 28 -
...i_statusbar_connected_no_internet_4_26x24px.xml | 24 -
...signal_wifi_statusbar_not_connected_26x24px.xml | 28 -
.../ic_signal_wifi_statusbar_null_26x24px.xml | 24 -
.../device/ic_storage_24dp.xml | 24 -
.../material_design_icons/device/ic_usb_24dp.xml | 24 -
.../device/ic_wifi_lock_24dp.xml | 24 -
.../device/ic_wifi_tethering_24dp.xml | 24 -
.../editor/ic_attach_file_24dp.xml | 24 -
.../editor/ic_attach_money_24dp.xml | 24 -
.../editor/ic_border_all_24dp.xml | 24 -
.../editor/ic_border_bottom_24dp.xml | 24 -
.../editor/ic_border_clear_24dp.xml | 24 -
.../editor/ic_border_color_24dp.xml | 28 -
.../editor/ic_border_horizontal_24dp.xml | 24 -
.../editor/ic_border_inner_24dp.xml | 24 -
.../editor/ic_border_left_24dp.xml | 24 -
.../editor/ic_border_outer_24dp.xml | 24 -
.../editor/ic_border_right_24dp.xml | 24 -
.../editor/ic_border_style_24dp.xml | 24 -
.../editor/ic_border_top_24dp.xml | 24 -
.../editor/ic_border_vertical_24dp.xml | 24 -
.../editor/ic_format_align_center_24dp.xml | 24 -
.../editor/ic_format_align_justify_24dp.xml | 24 -
.../editor/ic_format_align_left_24dp.xml | 24 -
.../editor/ic_format_align_right_24dp.xml | 24 -
.../editor/ic_format_bold_24dp.xml | 24 -
.../editor/ic_format_clear_24dp.xml | 24 -
.../editor/ic_format_color_fill_24dp.xml | 28 -
.../editor/ic_format_color_reset_24dp.xml | 24 -
.../editor/ic_format_color_text_24dp.xml | 28 -
.../editor/ic_format_indent_decrease_24dp.xml | 24 -
.../editor/ic_format_indent_increase_24dp.xml | 24 -
.../editor/ic_format_italic_24dp.xml | 24 -
.../editor/ic_format_line_spacing_24dp.xml | 24 -
.../editor/ic_format_list_bulleted_24dp.xml | 24 -
.../editor/ic_format_list_numbered_24dp.xml | 24 -
.../editor/ic_format_paint_24dp.xml | 24 -
.../editor/ic_format_quote_24dp.xml | 24 -
.../editor/ic_format_size_24dp.xml | 24 -
.../editor/ic_format_strikethrough_24dp.xml | 24 -
.../editor/ic_format_textdirection_l_to_r_24dp.xml | 24 -
.../editor/ic_format_textdirection_r_to_l_24dp.xml | 24 -
.../editor/ic_format_underline_24dp.xml | 24 -
.../editor/ic_functions_24dp.xml | 24 -
.../editor/ic_insert_chart_24dp.xml | 24 -
.../editor/ic_insert_comment_24dp.xml | 24 -
.../editor/ic_insert_drive_file_24dp.xml | 24 -
.../editor/ic_insert_emoticon_24dp.xml | 24 -
.../editor/ic_insert_invitation_24dp.xml | 24 -
.../editor/ic_insert_link_24dp.xml | 24 -
.../editor/ic_insert_photo_24dp.xml | 24 -
.../editor/ic_merge_type_24dp.xml | 24 -
.../editor/ic_mode_comment_24dp.xml | 24 -
.../editor/ic_mode_edit_24dp.xml | 24 -
.../editor/ic_publish_24dp.xml | 24 -
.../editor/ic_vertical_align_bottom_24dp.xml | 24 -
.../editor/ic_vertical_align_center_24dp.xml | 24 -
.../editor/ic_vertical_align_top_24dp.xml | 24 -
.../editor/ic_wrap_text_24dp.xml | 24 -
.../file/ic_attachment_24dp.xml | 24 -
.../material_design_icons/file/ic_cloud_24dp.xml | 24 -
.../file/ic_cloud_circle_24dp.xml | 24 -
.../file/ic_cloud_done_24dp.xml | 24 -
.../file/ic_cloud_download_24dp.xml | 24 -
.../file/ic_cloud_off_24dp.xml | 24 -
.../file/ic_cloud_queue_24dp.xml | 24 -
.../file/ic_cloud_upload_24dp.xml | 24 -
.../file/ic_file_download_24dp.xml | 24 -
.../file/ic_file_upload_24dp.xml | 24 -
.../material_design_icons/file/ic_folder_24dp.xml | 24 -
.../file/ic_folder_open_24dp.xml | 24 -
.../file/ic_folder_shared_24dp.xml | 24 -
.../hardware/ic_cast_24dp.xml | 24 -
.../hardware/ic_cast_connected_24dp.xml | 24 -
.../hardware/ic_computer_24dp.xml | 24 -
.../hardware/ic_desktop_mac_24dp.xml | 24 -
.../hardware/ic_desktop_windows_24dp.xml | 24 -
.../hardware/ic_dock_24dp.xml | 24 -
.../hardware/ic_gamepad_24dp.xml | 24 -
.../hardware/ic_headset_24dp.xml | 24 -
.../hardware/ic_headset_mic_24dp.xml | 24 -
.../hardware/ic_keyboard_24dp.xml | 24 -
.../hardware/ic_keyboard_alt_24dp.xml | 24 -
.../hardware/ic_keyboard_arrow_down_24dp.xml | 24 -
.../hardware/ic_keyboard_arrow_left_24dp.xml | 24 -
.../hardware/ic_keyboard_arrow_right_24dp.xml | 24 -
.../hardware/ic_keyboard_arrow_up_24dp.xml | 24 -
.../hardware/ic_keyboard_backspace_24dp.xml | 24 -
.../hardware/ic_keyboard_capslock_24dp.xml | 24 -
.../hardware/ic_keyboard_control_24dp.xml | 24 -
.../hardware/ic_keyboard_hide_24dp.xml | 24 -
.../hardware/ic_keyboard_return_24dp.xml | 24 -
.../hardware/ic_keyboard_tab_24dp.xml | 24 -
.../hardware/ic_keyboard_voice_24dp.xml | 24 -
.../hardware/ic_laptop_24dp.xml | 24 -
.../hardware/ic_laptop_chromebook_24dp.xml | 24 -
.../hardware/ic_laptop_mac_24dp.xml | 24 -
.../hardware/ic_laptop_windows_24dp.xml | 24 -
.../hardware/ic_memory_24dp.xml | 24 -
.../hardware/ic_mouse_24dp.xml | 24 -
.../hardware/ic_phone_android_24dp.xml | 24 -
.../hardware/ic_phone_iphone_24dp.xml | 24 -
.../hardware/ic_phonelink_24dp.xml | 24 -
.../hardware/ic_phonelink_off_24dp.xml | 24 -
.../hardware/ic_security_24dp.xml | 24 -
.../hardware/ic_sim_card_24dp.xml | 24 -
.../hardware/ic_smartphone_24dp.xml | 24 -
.../hardware/ic_speaker_24dp.xml | 24 -
.../hardware/ic_tablet_24dp.xml | 24 -
.../hardware/ic_tablet_android_24dp.xml | 24 -
.../hardware/ic_tablet_mac_24dp.xml | 24 -
.../material_design_icons/hardware/ic_tv_24dp.xml | 24 -
.../hardware/ic_watch_24dp.xml | 24 -
.../material_design_icons/ic_act_delivery_36px.xml | 24 -
.../material_design_icons/ic_act_flight_36px.xml | 24 -
.../material_design_icons/ic_act_hangout_36px.xml | 24 -
.../material_design_icons/ic_act_location_36px.xml | 24 -
.../material_design_icons/ic_act_purchase_36px.xml | 24 -
.../ic_act_restaurant_36px.xml | 28 -
.../material_design_icons/ic_act_ticket_36px.xml | 24 -
.../material_design_icons/ic_act_wallet_36px.xml | 35 -
.../material_design_icons/ic_act_weather_36px.xml | 27 -
.../material_design_icons/ic_act_youtube_36px.xml | 24 -
.../material_design_icons/ic_add_cluster_36px.xml | 27 -
.../images/material_design_icons/ic_check_24dp.xml | 24 -
.../material_design_icons/ic_chevron_down_36px.xml | 24 -
.../ic_compose_popout_36px.xml | 31 -
.../ic_custom_cluster_36px_clr.xml | 29 -
.../material_design_icons/ic_finance_36px_clr.xml | 40 -
.../material_design_icons/ic_forums_36px_clr.xml | 24 -
.../material_design_icons/ic_history_24dp.xml | 25 -
.../material_design_icons/ic_inbox_36px_clr.xml | 24 -
.../material_design_icons/ic_low_priority_36px.xml | 35 -
.../ic_multiselect_check_36px.xml | 24 -
.../material_design_icons/ic_offers_36px_clr.xml | 24 -
.../images/material_design_icons/ic_pin_36px.xml | 24 -
.../material_design_icons/ic_purchase_36px_clr.xml | 30 -
.../material_design_icons/ic_reminder_36px_clr.xml | 27 -
.../material_design_icons/ic_reply_all_36px.xml | 24 -
.../material_design_icons/ic_rotate_24_01.xml | 30 -
.../material_design_icons/ic_search_24dp.xml | 24 -
.../material_design_icons/ic_social_36px_clr.xml | 33 -
.../ic_speed_dial_48px_clr.xml | 28 -
.../material_design_icons/ic_sys_tty_24px.xml | 24 -
.../material_design_icons/ic_travel_36px_clr.xml | 24 -
.../material_design_icons/ic_upcoming_36px_clr.xml | 27 -
.../material_design_icons/ic_updates_36px_clr.xml | 27 -
.../material_design_icons/ic_vasquette_36px.xml | 24 -
.../images/material_design_icons/ic_volume_off.xml | 24 -
.../image/ic_add_to_photos_24dp.xml | 24 -
.../material_design_icons/image/ic_adjust_24dp.xml | 24 -
.../image/ic_assistant_photo_24dp.xml | 24 -
.../image/ic_audiotrack_24dp.xml | 24 -
.../image/ic_blur_circular_24dp.xml | 24 -
.../image/ic_blur_linear_24dp.xml | 24 -
.../image/ic_blur_off_24dp.xml | 24 -
.../image/ic_blur_on_24dp.xml | 24 -
.../image/ic_brightness_1_24dp.xml | 24 -
.../image/ic_brightness_2_24dp.xml | 24 -
.../image/ic_brightness_3_24dp.xml | 24 -
.../image/ic_brightness_4_24dp.xml | 24 -
.../image/ic_brightness_5_24dp.xml | 24 -
.../image/ic_brightness_6_24dp.xml | 24 -
.../image/ic_brightness_7_24dp.xml | 24 -
.../material_design_icons/image/ic_brush_24dp.xml | 24 -
.../material_design_icons/image/ic_camera_24dp.xml | 24 -
.../image/ic_camera_alt_24dp.xml | 27 -
.../image/ic_camera_front_24dp.xml | 24 -
.../image/ic_camera_rear_24dp.xml | 24 -
.../image/ic_camera_roll_24dp.xml | 24 -
.../image/ic_center_focus_strong_24dp.xml | 24 -
.../image/ic_center_focus_weak_24dp.xml | 24 -
.../image/ic_collections_24dp.xml | 24 -
.../image/ic_color_lens_24dp.xml | 24 -
.../image/ic_colorize_24dp.xml | 24 -
.../image/ic_compare_24dp.xml | 24 -
.../image/ic_control_point_24dp.xml | 24 -
.../image/ic_control_point_duplicate_24dp.xml | 24 -
.../image/ic_crop_16_9_24dp.xml | 24 -
.../material_design_icons/image/ic_crop_24dp.xml | 24 -
.../image/ic_crop_3_2_24dp.xml | 24 -
.../image/ic_crop_5_4_24dp.xml | 24 -
.../image/ic_crop_7_5_24dp.xml | 24 -
.../image/ic_crop_din_24dp.xml | 24 -
.../image/ic_crop_free_24dp.xml | 24 -
.../image/ic_crop_landscape_24dp.xml | 24 -
.../image/ic_crop_original_24dp.xml | 24 -
.../image/ic_crop_portrait_24dp.xml | 24 -
.../image/ic_crop_square_24dp.xml | 24 -
.../material_design_icons/image/ic_dehaze_24dp.xml | 24 -
.../image/ic_details_24dp.xml | 24 -
.../material_design_icons/image/ic_edit_24dp.xml | 24 -
.../image/ic_exposure_24dp.xml | 24 -
.../image/ic_exposure_minus_1_24dp.xml | 24 -
.../image/ic_exposure_minus_2_24dp.xml | 24 -
.../image/ic_exposure_plus_1_24dp.xml | 24 -
.../image/ic_exposure_plus_2_24dp.xml | 24 -
.../image/ic_exposure_zero_24dp.xml | 24 -
.../image/ic_filter_1_24dp.xml | 24 -
.../material_design_icons/image/ic_filter_24dp.xml | 24 -
.../image/ic_filter_2_24dp.xml | 24 -
.../image/ic_filter_3_24dp.xml | 24 -
.../image/ic_filter_4_24dp.xml | 24 -
.../image/ic_filter_5_24dp.xml | 24 -
.../image/ic_filter_6_24dp.xml | 24 -
.../image/ic_filter_7_24dp.xml | 24 -
.../image/ic_filter_8_24dp.xml | 24 -
.../image/ic_filter_9_24dp.xml | 24 -
.../image/ic_filter_9_plus_24dp.xml | 24 -
.../image/ic_filter_b_and_w_24dp.xml | 24 -
.../image/ic_filter_center_focus_24dp.xml | 24 -
.../image/ic_filter_drama_24dp.xml | 24 -
.../image/ic_filter_frames_24dp.xml | 24 -
.../image/ic_filter_hdr_24dp.xml | 24 -
.../image/ic_filter_none_24dp.xml | 24 -
.../image/ic_filter_tilt_shift_24dp.xml | 24 -
.../image/ic_filter_vintage_24dp.xml | 24 -
.../material_design_icons/image/ic_flare_24dp.xml | 24 -
.../image/ic_flash_auto_24dp.xml | 24 -
.../image/ic_flash_off_24dp.xml | 24 -
.../image/ic_flash_on_24dp.xml | 24 -
.../material_design_icons/image/ic_flip_24dp.xml | 24 -
.../image/ic_gradient_24dp.xml | 24 -
.../material_design_icons/image/ic_grain_24dp.xml | 24 -
.../image/ic_grid_off_24dp.xml | 24 -
.../image/ic_grid_on_24dp.xml | 24 -
.../image/ic_hdr_off_24dp.xml | 24 -
.../material_design_icons/image/ic_hdr_on_24dp.xml | 24 -
.../image/ic_hdr_strong_24dp.xml | 24 -
.../image/ic_hdr_weak_24dp.xml | 24 -
.../image/ic_healing_24dp.xml | 24 -
.../material_design_icons/image/ic_image_24dp.xml | 24 -
.../image/ic_image_aspect_ratio_24dp.xml | 24 -
.../material_design_icons/image/ic_iso_24dp.xml | 24 -
.../image/ic_landscape_24dp.xml | 24 -
.../image/ic_leak_add_24dp.xml | 24 -
.../image/ic_leak_remove_24dp.xml | 24 -
.../material_design_icons/image/ic_lens_24dp.xml | 24 -
.../material_design_icons/image/ic_looks_24dp.xml | 24 -
.../image/ic_looks_3_24dp.xml | 24 -
.../image/ic_looks_4_24dp.xml | 24 -
.../image/ic_looks_5_24dp.xml | 24 -
.../image/ic_looks_6_24dp.xml | 24 -
.../image/ic_looks_one_24dp.xml | 24 -
.../image/ic_looks_two_24dp.xml | 24 -
.../material_design_icons/image/ic_loupe_24dp.xml | 24 -
.../image/ic_movie_creation_24dp.xml | 24 -
.../material_design_icons/image/ic_nature_24dp.xml | 24 -
.../image/ic_nature_people_24dp.xml | 24 -
.../image/ic_navigate_before_24dp.xml | 24 -
.../image/ic_navigate_next_24dp.xml | 24 -
.../image/ic_palette_24dp.xml | 24 -
.../image/ic_panorama_24dp.xml | 24 -
.../image/ic_panorama_fisheye_24dp.xml | 24 -
.../image/ic_panorama_horizontal_24dp.xml | 24 -
.../image/ic_panorama_vertical_24dp.xml | 24 -
.../image/ic_panorama_wide_angle_24dp.xml | 24 -
.../material_design_icons/image/ic_photo_24dp.xml | 24 -
.../image/ic_photo_album_24dp.xml | 24 -
.../image/ic_photo_camera_24dp.xml | 27 -
.../image/ic_photo_library_24dp.xml | 24 -
.../image/ic_portrait_24dp.xml | 24 -
.../image/ic_remove_red_eye_24dp.xml | 24 -
.../image/ic_rotate_left_24dp.xml | 24 -
.../image/ic_rotate_right_24dp.xml | 24 -
.../image/ic_slideshow_24dp.xml | 24 -
.../image/ic_straighten_24dp.xml | 24 -
.../material_design_icons/image/ic_style_24dp.xml | 24 -
.../image/ic_switch_camera_24dp.xml | 24 -
.../image/ic_switch_video_24dp.xml | 24 -
.../image/ic_tag_faces_24dp.xml | 24 -
.../image/ic_texture_24dp.xml | 24 -
.../image/ic_timelapse_24dp.xml | 24 -
.../image/ic_timer_10_24dp.xml | 24 -
.../material_design_icons/image/ic_timer_24dp.xml | 24 -
.../image/ic_timer_3_24dp.xml | 24 -
.../image/ic_timer_auto_24dp.xml | 24 -
.../image/ic_timer_off_24dp.xml | 24 -
.../image/ic_tonality_24dp.xml | 24 -
.../image/ic_transform_24dp.xml | 24 -
.../material_design_icons/image/ic_tune_24dp.xml | 24 -
.../image/ic_wb_auto_24dp.xml | 24 -
.../image/ic_wb_cloudy_24dp.xml | 24 -
.../image/ic_wb_incandescent_24dp.xml | 24 -
.../image/ic_wb_irradescent_24dp.xml | 24 -
.../image/ic_wb_sunny_24dp.xml | 24 -
.../maps/ic_beenhere_24dp.xml | 24 -
.../maps/ic_directions_24dp.xml | 24 -
.../maps/ic_directions_bike_24dp.xml | 24 -
.../maps/ic_directions_bus_24dp.xml | 24 -
.../maps/ic_directions_car_24dp.xml | 24 -
.../maps/ic_directions_ferry_24dp.xml | 24 -
.../maps/ic_directions_subway_24dp.xml | 24 -
.../maps/ic_directions_train_24dp.xml | 24 -
.../maps/ic_directions_transit_24dp.xml | 24 -
.../maps/ic_directions_walk_24dp.xml | 24 -
.../material_design_icons/maps/ic_flight_24dp.xml | 27 -
.../material_design_icons/maps/ic_hotel_24dp.xml | 24 -
.../material_design_icons/maps/ic_layers_24dp.xml | 24 -
.../maps/ic_layers_clear_24dp.xml | 24 -
.../maps/ic_local_airport_24dp.xml | 24 -
.../maps/ic_local_atm_24dp.xml | 24 -
.../maps/ic_local_attraction_24dp.xml | 24 -
.../maps/ic_local_bar_24dp.xml | 24 -
.../maps/ic_local_cafe_24dp.xml | 24 -
.../maps/ic_local_car_wash_24dp.xml | 24 -
.../maps/ic_local_convenience_store_24dp.xml | 24 -
.../maps/ic_local_drink_24dp.xml | 24 -
.../maps/ic_local_florist_24dp.xml | 24 -
.../maps/ic_local_gas_station_24dp.xml | 24 -
.../maps/ic_local_grocery_store_24dp.xml | 24 -
.../maps/ic_local_hospital_24dp.xml | 24 -
.../maps/ic_local_hotel_24dp.xml | 24 -
.../maps/ic_local_laundry_service_24dp.xml | 24 -
.../maps/ic_local_library_24dp.xml | 24 -
.../maps/ic_local_mall_24dp.xml | 24 -
.../maps/ic_local_movies_24dp.xml | 24 -
.../maps/ic_local_offer_24dp.xml | 24 -
.../maps/ic_local_parking_24dp.xml | 24 -
.../maps/ic_local_pharmacy_24dp.xml | 24 -
.../maps/ic_local_phone_24dp.xml | 24 -
.../maps/ic_local_pizza_24dp.xml | 24 -
.../maps/ic_local_play_24dp.xml | 24 -
.../maps/ic_local_post_office_24dp.xml | 24 -
.../maps/ic_local_print_shop_24dp.xml | 24 -
.../maps/ic_local_restaurant_24dp.xml | 24 -
.../maps/ic_local_see_24dp.xml | 27 -
.../maps/ic_local_shipping_24dp.xml | 24 -
.../maps/ic_local_taxi_24dp.xml | 24 -
.../maps/ic_location_history_24dp.xml | 24 -
.../material_design_icons/maps/ic_map_24dp.xml | 24 -
.../maps/ic_my_location_24dp.xml | 24 -
.../maps/ic_navigation_24dp.xml | 24 -
.../maps/ic_pin_drop_24dp.xml | 24 -
.../material_design_icons/maps/ic_place_24dp.xml | 24 -
.../maps/ic_rate_review_24dp.xml | 24 -
.../maps/ic_restaurant_menu_24dp.xml | 24 -
.../maps/ic_satellite_24dp.xml | 24 -
.../maps/ic_store_mall_directory_24dp.xml | 24 -
.../material_design_icons/maps/ic_terrain_24dp.xml | 24 -
.../material_design_icons/maps/ic_traffic_24dp.xml | 24 -
.../images/material_design_icons/microphone.xml | 24 -
.../navigation/ic_apps_24dp.xml | 24 -
.../navigation/ic_apps_36px.xml | 24 -
.../navigation/ic_arrow_back_24dp.xml | 24 -
.../navigation/ic_arrow_back_36px.xml | 24 -
.../navigation/ic_arrow_drop_down_24dp.xml | 24 -
.../navigation/ic_arrow_drop_down_36px.xml | 24 -
.../navigation/ic_arrow_drop_down_circle_24dp.xml | 24 -
.../navigation/ic_arrow_drop_up_24dp.xml | 24 -
.../navigation/ic_arrow_drop_up_36px.xml | 24 -
.../navigation/ic_arrow_forward_24dp.xml | 24 -
.../navigation/ic_arrow_forward_36px.xml | 24 -
.../navigation/ic_cancel_24dp.xml | 24 -
.../navigation/ic_cancel_36px.xml | 24 -
.../navigation/ic_check_24dp.xml | 24 -
.../navigation/ic_check_36px.xml | 24 -
.../navigation/ic_chevron_left_24dp.xml | 24 -
.../navigation/ic_chevron_left_36px.xml | 24 -
.../navigation/ic_chevron_right_24dp.xml | 24 -
.../navigation/ic_chevron_right_36px.xml | 24 -
.../navigation/ic_close_24dp.xml | 24 -
.../navigation/ic_close_36px.xml | 24 -
.../navigation/ic_expand_less_24dp.xml | 24 -
.../navigation/ic_expand_less_36px.xml | 24 -
.../navigation/ic_expand_more_24dp.xml | 24 -
.../navigation/ic_expand_more_36px.xml | 24 -
.../navigation/ic_fullscreen_24dp.xml | 24 -
.../navigation/ic_fullscreen_36px.xml | 24 -
.../navigation/ic_fullscreen_exit_24dp.xml | 24 -
.../navigation/ic_fullscreen_exit_36px.xml | 24 -
.../navigation/ic_menu_24dp.xml | 24 -
.../navigation/ic_menu_36px.xml | 24 -
.../navigation/ic_more_horiz_24dp.xml | 24 -
.../navigation/ic_more_horiz_36px.xml | 24 -
.../navigation/ic_more_vert_24dp.xml | 24 -
.../navigation/ic_more_vert_36px.xml | 24 -
.../navigation/ic_refresh_24dp.xml | 24 -
.../navigation/ic_refresh_36px.xml | 24 -
.../navigation/ic_unfold_less_24dp.xml | 24 -
.../navigation/ic_unfold_less_36px.xml | 24 -
.../navigation/ic_unfold_more_24dp.xml | 24 -
.../navigation/ic_unfold_more_36px.xml | 24 -
.../notification/ic_adb_24dp.xml | 24 -
.../notification/ic_bluetooth_audio_24dp.xml | 24 -
.../notification/ic_disc_full_24dp.xml | 24 -
.../notification/ic_dnd_forwardslash_24dp.xml | 24 -
.../notification/ic_do_not_disturb_24dp.xml | 24 -
.../notification/ic_drive_eta_24dp.xml | 24 -
.../notification/ic_event_available_24dp.xml | 24 -
.../notification/ic_event_busy_24dp.xml | 24 -
.../notification/ic_event_note_24dp.xml | 24 -
.../notification/ic_folder_special_24dp.xml | 24 -
.../notification/ic_mms_24dp.xml | 24 -
.../notification/ic_more_24dp.xml | 24 -
.../notification/ic_network_locked_24dp.xml | 24 -
.../ic_phone_bluetooth_speaker_24dp.xml | 24 -
.../notification/ic_phone_forwarded_24dp.xml | 24 -
.../notification/ic_phone_in_talk_24dp.xml | 24 -
.../notification/ic_phone_locked_24dp.xml | 24 -
.../notification/ic_phone_missed_24dp.xml | 24 -
.../notification/ic_phone_paused_24dp.xml | 24 -
.../notification/ic_play_download_24dp.xml | 24 -
.../notification/ic_play_install_24dp.xml | 24 -
.../notification/ic_sd_card_24dp.xml | 24 -
.../notification/ic_sim_card_alert_24dp.xml | 24 -
.../notification/ic_sms_24dp.xml | 24 -
.../notification/ic_sms_failed_24dp.xml | 24 -
.../notification/ic_sync_24dp.xml | 24 -
.../notification/ic_sync_disabled_24dp.xml | 24 -
.../notification/ic_sync_problem_24dp.xml | 24 -
.../notification/ic_system_update_24dp.xml | 24 -
.../notification/ic_tap_and_play_24dp.xml | 24 -
.../notification/ic_time_to_leave_24dp.xml | 24 -
.../notification/ic_vibration_24dp.xml | 24 -
.../notification/ic_voice_chat_24dp.xml | 24 -
.../notification/ic_vpn_lock_24dp.xml | 24 -
.../material_design_icons/social/ic_cake_24dp.xml | 24 -
.../social/ic_domain_24dp.xml | 24 -
.../material_design_icons/social/ic_group_24dp.xml | 24 -
.../social/ic_group_add_24dp.xml | 24 -
.../social/ic_location_city_24dp.xml | 24 -
.../material_design_icons/social/ic_mood_24dp.xml | 24 -
.../social/ic_notifications_24dp.xml | 24 -
.../social/ic_notifications_none_24dp.xml | 24 -
.../social/ic_notifications_off_24dp.xml | 24 -
.../social/ic_notifications_on_24dp.xml | 24 -
.../social/ic_notifications_paused_24dp.xml | 24 -
.../material_design_icons/social/ic_pages_24dp.xml | 24 -
.../social/ic_party_mode_24dp.xml | 24 -
.../social/ic_people_24dp.xml | 24 -
.../social/ic_people_outline_24dp.xml | 24 -
.../social/ic_person_24dp.xml | 24 -
.../social/ic_person_add_24dp.xml | 24 -
.../social/ic_person_outline_24dp.xml | 24 -
.../social/ic_plus_one_24dp.xml | 24 -
.../material_design_icons/social/ic_poll_24dp.xml | 24 -
.../social/ic_public_24dp.xml | 24 -
.../social/ic_school_24dp.xml | 24 -
.../material_design_icons/social/ic_share_24dp.xml | 24 -
.../social/ic_whatshot_24dp.xml | 24 -
.../images/material_design_icons/test_path_01.xml | 25 -
.../images/material_design_icons/test_path_02.xml | 25 -
.../images/material_design_icons/test_qt_01.xml | 25 -
.../toggle/ic_check_box_24dp.xml | 24 -
.../toggle/ic_check_box_outline_blank_24dp.xml | 24 -
.../toggle/ic_radio_button_off_24dp.xml | 24 -
.../toggle/ic_radio_button_on_24dp.xml | 24 -
.../material_design_icons/toggle/ic_star_24dp.xml | 24 -
.../toggle/ic_star_half_24dp.xml | 24 -
.../toggle/ic_star_outline_24dp.xml | 24 -
.../com/android/builder/model/AaptOptions.java | 46 -
.../com/android/builder/model/AndroidArtifact.java | 125 -
.../com/android/builder/model/AndroidProject.java | 239 --
.../java/com/android/builder/model/BaseConfig.java | 110 -
.../java/com/android/builder/model/BuildType.java | 126 -
.../com/android/builder/model/JavaLibrary.java | 39 -
.../com/android/builder/model/LintOptions.java | 212 --
.../com/android/builder/model/NativeToolchain.java | 53 -
.../com/android/builder/model/ProductFlavor.java | 180 -
.../java/com/android/builder/model/SyncIssue.java | 93 -
.../java/com/android/builder/model/Variant.java | 102 -
base/build-system/builder/build.gradle | 105 -
base/build-system/builder/builder.iml | 32 -
.../builder/core/AaptPackageProcessBuilder.java | 498 ---
.../com/android/builder/core/AndroidBuilder.java | 1844 -----------
.../builder/core/BuildToolsServiceLoader.java | 254 --
.../android/builder/core/DefaultApiVersion.java | 109 -
.../com/android/builder/core/DefaultBuildType.java | 337 --
.../builder/core/DefaultManifestParser.java | 128 -
.../android/builder/core/DefaultProductFlavor.java | 659 ----
.../java/com/android/builder/core/DexOptions.java | 30 -
.../android/builder/core/DexProcessBuilder.java | 259 --
.../com/android/builder/core/ErrorReporter.java | 57 -
.../android/builder/core/JackProcessBuilder.java | 222 --
.../android/builder/core/VariantConfiguration.java | 1855 -----------
.../java/com/android/builder/core/VariantType.java | 134 -
.../android/builder/internal/BaseConfigImpl.java | 303 --
.../builder/internal/FakeAndroidTarget.java | 252 --
.../builder/internal/compiler/AidlProcessor.java | 117 -
.../android/builder/internal/compiler/DexKey.java | 78 -
.../internal/compiler/JackConversionCache.java | 145 -
.../builder/internal/compiler/PreDexCache.java | 197 --
.../builder/internal/compiler/PreProcessCache.java | 581 ----
.../builder/internal/packaging/Packager.java | 617 ----
.../internal/testing/SimpleTestCallable.java | 266 --
.../builder/packaging/DuplicateFileException.java | 63 -
.../java/com/android/builder/png/AaptProcess.java | 274 --
.../com/android/builder/png/QueuedCruncher.java | 244 --
.../builder/png/VectorDrawableRenderer.java | 191 --
.../com/android/builder/sdk/DefaultSdkLoader.java | 189 --
.../com/android/builder/sdk/PlatformLoader.java | 155 -
.../main/java/com/android/builder/sdk/SdkInfo.java | 64 -
.../java/com/android/builder/sdk/SdkLoader.java | 67 -
.../android/builder/signing/SignedJarBuilder.java | 474 ---
.../android/builder/testing/ConnectedDevice.java | 262 --
.../builder/testing/ConnectedDeviceProvider.java | 158 -
.../builder/testing/MockableJarGenerator.java | 253 --
.../core/AaptPackageProcessBuilderTest.java | 695 ----
.../builder/internal/compiler/PreDexCacheTest.java | 479 ---
.../builder/png/NinePatchAaptProcessorTest.java | 97 -
.../png/NinePatchAaptProcessorTestUtils.java | 299 --
.../builder/png/NinePatchAsyncAaptProcessTest.java | 95 -
base/build-system/changelog.txt | 649 ----
base/build-system/docs/build.gradle | 328 --
.../dsl/com.android.build.gradle.BaseExtension.xml | 53 -
...m.android.build.gradle.api.AndroidSourceSet.xml | 43 -
...ndroid.build.gradle.internal.CompileOptions.xml | 20 -
...oid.build.gradle.internal.api.VariantFilter.xml | 22 -
...droid.build.gradle.internal.dsl.AaptOptions.xml | 22 -
...android.build.gradle.internal.dsl.BuildType.xml | 42 -
...ild.gradle.internal.dsl.DensitySplitOptions.xml | 19 -
...ndroid.build.gradle.internal.dsl.DexOptions.xml | 22 -
...droid.build.gradle.internal.dsl.LintOptions.xml | 46 -
....build.gradle.internal.dsl.PackagingOptions.xml | 22 -
...oid.build.gradle.internal.dsl.ProductFlavor.xml | 45 -
...om.android.build.gradle.internal.dsl.Splits.xml | 24 -
.../docs/src/fromGradle/docs/dsl/dsl.xml | 69 -
.../gms/googleservices/GoogleServicesPlugin.groovy | 77 -
.../gms/googleservices/GoogleServicesTask.java | 287 --
base/build-system/gradle-core/build.gradle | 79 -
base/build-system/gradle-core/gradle-core.iml | 1213 -------
.../com/android/build/gradle/AndroidConfig.java | 138 -
.../android/build/gradle/AndroidGradleOptions.java | 149 -
.../android/build/gradle/ReportingPlugin.groovy | 96 -
.../gradle/api/AndroidSourceDirectorySet.java | 95 -
.../com/android/build/gradle/api/ApkVariant.java | 93 -
.../com/android/build/gradle/api/BaseVariant.java | 316 --
.../gradle/internal/ApplicationTaskManager.java | 259 --
.../build/gradle/internal/CompileOptions.java | 129 -
.../gradle/internal/ConfigurationDependencies.java | 72 -
.../build/gradle/internal/DependencyManager.java | 1096 ------
.../internal/ExecutionConfigurationUtil.java | 40 -
.../build/gradle/internal/ExtraModelInfo.java | 333 --
.../build/gradle/internal/LibraryCache.groovy | 103 -
.../build/gradle/internal/LibraryTaskManager.java | 613 ----
.../build/gradle/internal/LintGradleClient.java | 198 --
.../build/gradle/internal/LintGradleProject.java | 658 ----
.../build/gradle/internal/LintGradleRequest.java | 69 -
.../build/gradle/internal/LoggerWrapper.java | 115 -
.../android/build/gradle/internal/NdkHandler.java | 470 ---
.../build/gradle/internal/PostCompilationData.java | 122 -
.../build/gradle/internal/ProductFlavorCombo.java | 167 -
.../android/build/gradle/internal/SdkHandler.java | 227 --
.../android/build/gradle/internal/TaskFactory.java | 66 -
.../android/build/gradle/internal/TaskManager.java | 2459 --------------
.../internal/TestApplicationTaskManager.java | 264 --
.../build/gradle/internal/VariantManager.java | 828 -----
.../build/gradle/internal/api/ApkVariantImpl.java | 97 -
.../build/gradle/internal/api/BaseVariantImpl.java | 275 --
.../api/DefaultAndroidSourceDirectorySet.java | 183 -
.../gradle/internal/api/ReadOnlyBaseConfig.groovy | 142 -
.../gradle/internal/api/ReadOnlyBuildType.java | 124 -
.../gradle/internal/api/ReadOnlyProductFlavor.java | 172 -
.../internal/core/GradleVariantConfiguration.java | 157 -
.../internal/coverage/JacocoExtension.groovy | 27 -
.../internal/coverage/JacocoInstrumentTask.groovy | 98 -
.../gradle/internal/coverage/JacocoPlugin.groovy | 85 -
.../internal/dependency/VariantDependencies.groovy | 251 --
.../build/gradle/internal/dsl/AaptOptions.java | 175 -
.../build/gradle/internal/dsl/AdbOptions.java | 66 -
.../build/gradle/internal/dsl/BuildType.java | 326 --
.../build/gradle/internal/dsl/CoreBuildType.java | 34 -
.../gradle/internal/dsl/DensitySplitOptions.java | 127 -
.../build/gradle/internal/dsl/DexOptions.java | 110 -
.../build/gradle/internal/dsl/LintOptions.java | 798 -----
.../gradle/internal/dsl/PackagingOptions.java | 104 -
.../gradle/internal/dsl/PreprocessingOptions.java | 92 -
.../build/gradle/internal/dsl/ProductFlavor.java | 466 ---
.../android/build/gradle/internal/dsl/Splits.java | 120 -
.../gradle/internal/model/AndroidArtifactImpl.java | 163 -
.../gradle/internal/model/BaseConfigImpl.java | 134 -
.../build/gradle/internal/model/BuildTypeImpl.java | 155 -
.../internal/model/DefaultAndroidProject.java | 280 --
.../gradle/internal/model/DependenciesImpl.java | 258 --
.../gradle/internal/model/JavaArtifactImpl.java | 89 -
.../gradle/internal/model/JavaLibraryImpl.java | 59 -
.../build/gradle/internal/model/ModelBuilder.java | 594 ----
.../gradle/internal/model/NativeToolchainImpl.java | 72 -
.../gradle/internal/model/ProductFlavorImpl.java | 229 --
.../build/gradle/internal/model/SyncIssueImpl.java | 74 -
.../build/gradle/internal/model/SyncIssueKey.java | 60 -
.../process/GradleJavaProcessExecutor.java | 93 -
.../internal/process/GradleProcessResult.java | 62 -
.../OutputHandlerFailedGradleProcessResult.java | 45 -
.../internal/profile/RecordingBuildListener.java | 108 -
.../internal/publishing/FilterDataPersistence.java | 73 -
.../build/gradle/internal/scope/AndroidTask.java | 173 -
.../gradle/internal/scope/AndroidTaskRegistry.java | 81 -
.../internal/scope/ConventionMappingHelper.groovy | 34 -
.../build/gradle/internal/scope/GlobalScope.java | 147 -
.../gradle/internal/scope/TaskConfigAction.java | 35 -
.../gradle/internal/scope/VariantOutputScope.java | 169 -
.../build/gradle/internal/scope/VariantScope.java | 723 ----
.../gradle/internal/tasks/AndroidReportTask.java | 173 -
.../build/gradle/internal/tasks/CheckManifest.java | 62 -
.../tasks/DeviceProviderInstrumentTestTask.java | 376 ---
.../internal/tasks/ExtractJavaResourcesTask.java | 261 --
.../gradle/internal/tasks/GenerateApkDataTask.java | 226 --
.../gradle/internal/tasks/IncrementalTask.java | 118 -
.../gradle/internal/tasks/InstallVariantTask.java | 270 --
.../build/gradle/internal/tasks/MergeFileTask.java | 83 -
.../internal/tasks/MergeJavaResourcesTask.java | 549 ---
.../internal/tasks/MockableAndroidJarTask.java | 79 -
.../internal/tasks/PrepareDependenciesTask.java | 95 -
.../gradle/internal/tasks/PrepareLibraryTask.java | 54 -
.../gradle/internal/tasks/TestServerTask.java | 79 -
.../build/gradle/internal/tasks/UninstallTask.java | 159 -
.../gradle/internal/tasks/ValidateSigningTask.java | 100 -
.../tasks/multidex/CreateMainDexList.groovy | 139 -
.../tasks/multidex/CreateManifestKeepList.groovy | 159 -
.../internal/tasks/multidex/JarMergingTask.groovy | 192 --
.../tasks/multidex/RetraceMainDexList.groovy | 161 -
.../gradle/internal/variant/ApkVariantData.java | 75 -
.../internal/variant/ApkVariantOutputData.java | 189 --
.../internal/variant/ApplicationVariantData.java | 71 -
.../variant/ApplicationVariantFactory.java | 210 --
.../gradle/internal/variant/BaseVariantData.java | 654 ----
.../internal/variant/LibraryVariantData.java | 114 -
.../internal/variant/LibraryVariantFactory.java | 162 -
.../gradle/internal/variant/TestVariantData.java | 85 -
.../internal/variant/TestVariantFactory.java | 86 -
.../android/build/gradle/tasks/AidlCompile.java | 404 ---
.../android/build/gradle/tasks/AndroidJarTask.java | 33 -
.../build/gradle/tasks/AndroidProGuardTask.java | 411 ---
.../gradle/tasks/CompatibleScreensManifest.groovy | 120 -
.../com/android/build/gradle/tasks/Dex.groovy | 240 --
.../build/gradle/tasks/ExtractAnnotations.groovy | 265 --
.../build/gradle/tasks/GenerateBuildConfig.groovy | 208 --
.../build/gradle/tasks/GenerateResValues.groovy | 116 -
.../build/gradle/tasks/GenerateSplitAbiRes.java | 190 --
.../build/gradle/tasks/InvokeManifestMerger.groovy | 75 -
.../com/android/build/gradle/tasks/JackTask.java | 503 ---
.../build/gradle/tasks/JavaResourcesProvider.java | 71 -
.../com/android/build/gradle/tasks/JillTask.java | 311 --
.../com/android/build/gradle/tasks/Lint.groovy | 295 --
.../gradle/tasks/ManifestProcessorTask.groovy | 74 -
.../android/build/gradle/tasks/MergeAssets.groovy | 202 --
.../build/gradle/tasks/MergeManifests.groovy | 259 --
.../android/build/gradle/tasks/MergeResources.java | 445 ---
.../android/build/gradle/tasks/NdkCompile.groovy | 302 --
.../build/gradle/tasks/PackageApplication.java | 479 ---
.../build/gradle/tasks/PackageSplitAbi.java | 269 --
.../build/gradle/tasks/PackageSplitRes.java | 306 --
.../tasks/PreCompilationVerificationTask.groovy | 45 -
.../com/android/build/gradle/tasks/PreDex.java | 300 --
.../gradle/tasks/ProcessAndroidResources.java | 503 ---
.../build/gradle/tasks/ProcessManifest.groovy | 175 -
.../build/gradle/tasks/ProcessTestManifest.groovy | 186 --
.../build/gradle/tasks/RenderscriptCompile.groovy | 195 --
.../build/gradle/tasks/ResourceUsageAnalyzer.java | 2568 --------------
.../build/gradle/tasks/ShrinkResources.groovy | 220 --
.../build/gradle/tasks/SimpleWorkQueue.java | 58 -
.../build/gradle/tasks/SplitRelatedTask.groovy | 129 -
.../build/gradle/tasks/SplitZipAlign.groovy | 197 --
.../build/gradle/tasks/TestModuleProGuardTask.java | 85 -
.../com/android/build/gradle/tasks/ZipAlign.java | 143 -
.../annotations/ExtractAnnotationsDriver.java | 400 ---
.../gradle/tasks/annotations/TypedefCollector.java | 154 -
.../tasks/factory/JavaCompileConfigAction.java | 103 -
.../tasks/factory/ProGuardTaskConfigAction.java | 96 -
.../factory/ProcessJavaResConfigAction.groovy | 74 -
.../build/gradle/internal/CompileOptionsTest.java | 72 -
.../internal/tasks/MergeJavaResourcesTaskTest.java | 422 ---
.../tasks/multidex/RetraceMainDexListTest.java | 55 -
.../profiling/RecordingBuildListenerTest.groovy | 284 --
.../gradle/tasks/ResourceUsageAnalyzerTest.java | 1022 ------
base/build-system/gradle-experimental/build.gradle | 75 -
.../gradle-experimental/gradle-experimental.iml | 1256 -------
.../build/gradle/internal/AndroidConfigHelper.java | 134 -
.../build/gradle/internal/NdkOptionsHelper.java | 77 -
.../build/gradle/managed/AndroidConfig.java | 167 -
.../android/build/gradle/managed/BuildType.java | 212 --
.../android/build/gradle/managed/ClassField.java | 45 -
.../android/build/gradle/managed/FilePattern.java | 31 -
.../build/gradle/managed/ManagedString.java | 31 -
.../android/build/gradle/managed/NdkConfig.java | 30 -
.../android/build/gradle/managed/NdkOptions.java | 92 -
.../build/gradle/managed/ProductFlavor.java | 285 --
.../build/gradle/managed/SigningConfig.java | 42 -
.../managed/adaptor/AndroidConfigAdaptor.java | 347 --
.../gradle/managed/adaptor/BuildTypeAdaptor.java | 214 --
.../gradle/managed/adaptor/NdkOptionsAdaptor.java | 74 -
.../managed/adaptor/ProductFlavorAdaptor.java | 258 --
.../managed/adaptor/SigningConfigAdaptor.java | 89 -
.../gradle/model/AndroidComponentModelPlugin.java | 277 --
.../model/AndroidComponentModelSourceSet.java | 105 -
.../model/AndroidComponentModelTestPlugin.java | 76 -
.../gradle/model/AndroidLanguageSourceSet.java | 26 -
.../android/build/gradle/model/AndroidObject.java | 25 -
.../gradle/model/AppComponentModelPlugin.java | 86 -
.../model/ApplicationComponentTaskManager.java | 60 -
.../gradle/model/BaseComponentModelPlugin.java | 576 ----
.../build/gradle/model/ComponentModelBuilder.java | 110 -
.../model/ComponentNativeLibraryFactory.java | 106 -
.../build/gradle/model/DefaultAndroidBinary.java | 108 -
.../gradle/model/DefaultAndroidComponentSpec.java | 71 -
.../gradle/model/LibraryComponentModelPlugin.java | 87 -
.../gradle/model/LibraryComponentTaskManager.java | 60 -
.../android/build/gradle/model/ModelConstants.java | 39 -
.../gradle/model/NdkComponentModelPlugin.java | 463 ---
.../android/build/gradle/model/NdkConfigImpl.java | 164 -
.../internal/AbstractNativeToolSpecification.java | 48 -
.../gradle/ndk/internal/BinaryToolHelper.java | 39 -
.../ndk/internal/ClangNativeToolSpecification.java | 224 --
.../gradle/ndk/internal/NdkConfiguration.java | 308 --
.../ndk/internal/NdkExtensionConvention.java | 36 -
.../build/gradle/ndk/internal/NdkNamingScheme.java | 121 -
.../gradle/ndk/internal/StlConfiguration.java | 127 -
.../ndk/internal/StlNativeToolSpecification.java | 99 -
.../ndk/internal/ToolchainConfiguration.java | 103 -
.../android/build/gradle/tasks/GdbSetupTask.java | 136 -
.../build/gradle/tasks/StripDebugSymbolTask.java | 137 -
.../gradle-plugins/com.android.native.properties | 1 -
.../com/android/build/gradle/AppExtension.java | 48 -
.../com/android/build/gradle/AppPlugin.groovy | 73 -
.../com/android/build/gradle/BaseExtension.java | 799 -----
.../com/android/build/gradle/BasePlugin.java | 714 ----
.../com/android/build/gradle/LibraryExtension.java | 73 -
.../com/android/build/gradle/LibraryPlugin.groovy | 90 -
.../com/android/build/gradle/TestExtension.java | 88 -
.../com/android/build/gradle/TestPlugin.groovy | 73 -
.../gradle/internal/NativeLibraryFactoryImpl.java | 82 -
.../gradle/internal/dsl/ProductFlavorFactory.java | 52 -
.../android/build/gradle/AppPluginDslTest.groovy | 687 ----
.../build/gradle/AppPluginInternalTest.groovy | 437 ---
base/build-system/integration-test/build.gradle | 141 -
.../integration-test/integration-test.iml | 18 -
.../integration/application/AaptOptionsTest.groovy | 72 -
.../integration/application/AbiPureSplits.groovy | 113 -
.../application/AndroidManifestInTestTest.groovy | 83 -
.../application/AndroidTestResourcesTest.groovy | 162 -
.../application/ApplicationIdInLibsTest.groovy | 94 -
.../application/ArchivesBaseNameTest.groovy | 69 -
.../integration/application/ArtifactApiTest.groovy | 188 --
.../application/AutoDensitySplitTest.groovy | 107 -
.../integration/application/BasicTest.groovy | 169 -
.../integration/application/BasicTest2.groovy | 296 --
.../application/BootClasspathTest.groovy | 56 -
.../integration/application/BuildConfigTest.groovy | 298 --
.../integration/application/BuildToolsTest.groovy | 122 -
.../application/DensitySplitInLTest.groovy | 101 -
.../application/DensitySplitTest.groovy | 157 -
.../DensitySplitWithPublishNonDefaultTest.groovy | 60 -
.../application/DependenciesTest.groovy | 58 -
.../DependenciesWithVariantsTest.groovy | 50 -
.../application/DependencyCheckerTest.groovy | 82 -
.../integration/application/EmptySplitTest.groovy | 50 -
.../application/ExternalTestProjectTest.groovy | 165 -
.../application/ExtractAnnotationTest.groovy | 131 -
.../application/FilteredOutBuildTypeTest.groovy | 64 -
.../application/FilteredOutVariantsTest.groovy | 74 -
.../integration/application/FlavoredTest.groovy | 58 -
.../integration/application/FlavorsTest.groovy | 131 -
.../application/GenFolderApi2Test.groovy | 96 -
.../application/GenFolderApiTest.groovy | 136 -
.../InvalidResourceDirectoryTest.groovy | 81 -
.../gradle/integration/application/JackTest.groovy | 105 -
.../integration/application/JarJarTest.groovy | 85 -
.../application/MessageRewriteTest.groovy | 66 -
.../integration/application/MigratedTest.groovy | 107 -
.../MinifyLibAndAppWithJavaResTest.groovy | 90 -
.../integration/application/MinifyTest.groovy | 130 -
.../integration/application/ModelTest.groovy | 64 -
.../integration/application/MultiDexTest.groovy | 79 -
.../integration/application/MultiresTest.groovy | 58 -
.../application/NdkJniPureSplitLibTest.groovy | 64 -
.../integration/application/NoPreDexTest.groovy | 50 -
.../application/OptionalLibraryTest.groovy | 133 -
.../integration/application/Overlay1Test.groovy | 71 -
.../integration/application/Overlay2Test.groovy | 74 -
.../integration/application/Overlay3Test.groovy | 100 -
.../application/PackagingOptionsTest.groovy | 212 --
.../integration/application/PkgOverrideTest.groovy | 58 -
.../application/PrivateResourceTest.groovy | 63 -
.../application/PseudoLocalizationTest.groovy | 56 -
.../integration/application/RenamedApkTest.groovy | 89 -
.../application/RenderscriptMultiSrcTest.groovy | 50 -
.../application/RenderscriptTest.groovy | 50 -
.../integration/application/ResValueTest.groovy | 268 --
.../application/ResValueTypeTest.groovy | 141 -
.../application/RsSupportModeTest.groovy | 86 -
.../integration/application/ShrinkTest.groovy | 453 ---
.../application/SigningConfigTest.groovy | 63 -
.../integration/application/TictactoeTest.groovy | 90 -
.../application/UnitTestingModelTest.groovy | 105 -
.../application/VectorDrawableTest.groovy | 316 --
.../integration/application/WearVariantTest.groovy | 93 -
.../WearWithCustomApplicationIdTest.groovy | 66 -
.../common/fixture/GetAndroidModelAction.java | 120 -
.../common/fixture/GradleTestProject.java | 1206 -------
.../fixture/TemporaryProjectModification.java | 141 -
.../common/fixture/app/AbstractAndroidTestApp.java | 107 -
.../common/fixture/app/HelloWorldApp.groovy | 126 -
.../common/fixture/app/HelloWorldJniApp.groovy | 170 -
.../common/fixture/app/HelloWorldLibraryApp.groovy | 56 -
.../common/fixture/app/MultiModuleTestProject.java | 88 -
.../gradle/integration/common/runner/AllTests.java | 105 -
.../integration/common/truth/AarSubject.java | 93 -
.../common/truth/AbstractAndroidSubject.java | 87 -
.../common/truth/AbstractZipSubject.java | 171 -
.../integration/common/truth/ApkSubject.java | 198 --
.../common/truth/DependenciesSubject.java | 69 -
.../integration/common/truth/FileSubject.java | 64 -
.../integration/common/truth/ModelSubject.java | 178 -
.../integration/common/truth/TruthHelper.java | 242 --
.../gradle/integration/common/utils/ApkHelper.java | 119 -
.../integration/common/utils/FileHelper.java | 76 -
.../gradle/integration/common/utils/SdkHelper.java | 83 -
.../component/AndroidComponentPluginTest.groovy | 64 -
.../component/AppComponentPluginTest.groovy | 115 -
.../component/BasicNdkComponentTest.groovy | 110 -
.../integration/component/ComponentDslTest.groovy | 97 -
.../component/ComponentSourceSetTest.groovy | 142 -
.../component/LibraryComponentPluginTest.groovy | 104 -
.../component/NdkComponentModelTest.groovy | 244 --
.../component/NdkComponentPluginTest.groovy | 74 -
.../component/NdkComponentSplitTest.groovy | 117 -
.../component/NdkComponentVariantTest.groovy | 168 -
.../integration/component/NdkFlagsTest.groovy | 204 --
.../integration/component/NdkJniLib2Test.groovy | 61 -
.../component/NdkSanAngeles2Test.groovy | 98 -
.../component/NdkStandaloneSoTest.groovy | 61 -
.../gradle/integration/component/NdkStlTest.groovy | 115 -
.../integration/component/NdkVariantsTest.groovy | 60 -
.../dependencies/AppWithClassifierDepTest.groovy | 95 -
.../AppWithCompileDirectJarTest.groovy | 85 -
.../AppWithCompileIndirectJarTest.groovy | 95 -
.../dependencies/AppWithCompileLibTest.groovy | 87 -
.../AppWithCompileLocalAarFromOlderIdeTest.groovy | 64 -
.../dependencies/AppWithCompileLocalAarTest.groovy | 71 -
.../dependencies/AppWithIvyDependencyTest.groovy | 51 -
.../dependencies/AppWithJarDependOnLibTest.groovy | 73 -
...hNonExistentResolutionStrategyForAarTest.groovy | 93 -
.../AppWithPackageDirectJarTest.groovy | 85 -
.../dependencies/AppWithPackageLibTest.groovy | 64 -
.../dependencies/AppWithPackageLocalAarTest.groovy | 71 -
.../dependencies/AppWithPackageLocalJarTest.groovy | 93 -
.../AppWithProvidedAarAsJarTest.groovy | 103 -
.../AppWithProvidedDirectJarTest.groovy | 86 -
.../dependencies/AppWithProvidedLibTest.groovy | 64 -
.../AppWithProvidedLocalAarTest.groovy | 71 -
.../AppWithProvidedLocalJarTest.groovy | 93 -
.../AppWithResolutionStrategyForAarTest.groovy | 118 -
.../AppWithResolutionStrategyForJarTest.groovy | 116 -
.../DepOnLocalJarThroughAModuleTest.groovy | 81 -
.../dependencies/LibWithPackageLocalJarTest.groovy | 95 -
.../LibWithProvidedAarAsJarTest.groovy | 103 -
.../LibWithProvidedDirectJarTest.groovy | 98 -
.../LibWithProvidedLocalJarTest.groovy | 95 -
.../dependencies/LocalJarInAarInModelTest.groovy | 111 -
.../dependencies/OptionalAarTest.groovy | 125 -
.../dependencies/TestLibraryWithDep.groovy | 49 -
.../TestWithCompileDirectJarTest.groovy | 83 -
.../dependencies/TestWithCompileLibTest.groovy | 84 -
.../TestWithFlavorsWithCompileDirectJarTest.groovy | 92 -
.../dependencies/TestWithMismatchDep.groovy | 115 -
.../dependencies/TestWithSameDepAsApp.groovy | 66 -
.../TestWithSameDepAsAppWithProguard.groovy | 78 -
.../dependencies/VariantDependencyTest.groovy | 240 --
.../build/gradle/integration/dsl/DslTest.groovy | 143 -
.../integration/dsl/TestedVariantTest.groovy | 58 -
.../integration/googleservices/BasicTest.groovy | 114 -
.../googleservices/DisabledServiceTest.groovy | 101 -
.../integration/googleservices/FlavorTest.groovy | 141 -
.../integration/googleservices/NoClientTest.groovy | 80 -
.../googleservices/NoServiceTest.groovy | 103 -
.../gradle/integration/library/AidlTest.groovy | 50 -
.../gradle/integration/library/ApiTest.groovy | 71 -
.../gradle/integration/library/AssetsTest.groovy | 58 -
.../gradle/integration/library/DslTest.groovy | 121 -
.../integration/library/FlavoredlibTest.groovy | 118 -
.../integration/library/FlavorlibTest.groovy | 125 -
.../integration/library/LibMinifyLibDepTest.groovy | 58 -
.../integration/library/LibMinifyTest.groovy | 54 -
.../library/LibProguardConsumerFilesTest.groovy | 58 -
.../integration/library/MinifyLibTest.groovy | 58 -
.../integration/library/MultiDexWithLibTest.groovy | 59 -
.../library/ProguardAarPackagingTest.groovy | 158 -
.../gradle/integration/ndk/NdkModelTest.groovy | 203 --
.../integration/ndk/NoSplitNdkVariantsTest.groovy | 137 -
.../performance/IOScheduleCodeChangeTest.groovy | 81 -
.../performance/IOScheduleResChangeTest.groovy | 69 -
.../LargeVariantAndroidComponentTest.groovy | 80 -
.../performance/LargeVariantAndroidTest.groovy | 73 -
.../MediumAndroidComponentEvaluationTest.groovy | 56 -
.../MediumAndroidComponentModelTest.groovy | 59 -
.../MultiProjectsAndroidComponentTest.groovy | 96 -
.../performance/MultiProjectsAndroidTest.groovy | 92 -
.../SmallAndroidComponentEvaluationTest.groovy | 55 -
.../SmallAndroidComponentModelTest.groovy | 56 -
.../integration/test/SeparateTestModuleTest.groovy | 52 -
.../test/SeparateTestWithAarDependencyTest.groovy | 128 -
.../test/SeparateTestWithDependenciesTest.groovy | 65 -
...tWithoutMinificationWithDependenciesTest.groovy | 67 -
.../testing/TestingSupportLibraryTest.groovy | 134 -
.../testing/UnitTestingComplexProjectTest.groovy | 40 -
.../testing/UnitTestingDefaultValuesTest.groovy | 44 -
.../testing/UnitTestingFlavorsSupportTest.groovy | 70 -
.../src/test/resources/basic/example.json | 90 -
.../src/test/resources/basic/global_tracker.xml | 4 -
.../src/test/resources/basic/values.xml | 7 -
.../test/resources/disabledservice/example.json | 90 -
.../resources/disabledservice/global_tracker.xml | 4 -
.../src/test/resources/disabledservice/values.xml | 5 -
.../src/test/resources/flavor/example.json | 85 -
.../test/resources/flavor/free.global_tracker.xml | 4 -
.../src/test/resources/flavor/free.values.xml | 5 -
.../test/resources/flavor/paid.global_tracker.xml | 4 -
.../src/test/resources/flavor/paid.values.xml | 5 -
.../test/resources/noservice/global_tracker.xml | 4 -
.../src/test/resources/noservice/no_services.json | 11 -
.../src/test/resources/noservice/values.xml | 4 -
.../test-projects/3rdPartyTests/app/build.gradle | 44 -
.../test-projects/3rdPartyTests/lib/build.gradle | 40 -
.../3rdPartyTests/lib/src/main/AndroidManifest.xml | 30 -
.../test-projects/abiPureSplits/build.gradle | 29 -
.../test-projects/aidl/build.gradle | 9 -
.../aidl/src/main/AndroidManifest.xml | 14 -
.../android/tests/basicprojectwithaidl/Main.java | 15 -
.../android/tests/basicprojectwithaidl/Rect.java | 52 -
.../aidl/src/main/res/layout/main.xml | 13 -
.../aidl/src/main/res/values/strings.xml | 4 -
.../androidManifestInTest/build.gradle | 27 -
.../test-projects/androidTestLibDep/build.gradle | 16 -
.../test-projects/api/app/build.gradle | 39 -
.../test-projects/api/lib/build.gradle | 29 -
.../test-projects/applibtest/app/build.gradle | 13 -
.../test-projects/applibtest/lib/build.gradle | 18 -
.../android/tests/basicprojectwithaidl/ITest.aidl | 7 -
.../android/tests/basicprojectwithaidl/Rect.aidl | 5 -
.../applicationIdInLibsTest/app/build.gradle | 33 -
.../examplelibrary/build.gradle | 18 -
.../test-projects/artifactApi/build.gradle | 142 -
.../test-projects/assets/app/build.gradle | 10 -
.../test-projects/assets/lib/build.gradle | 6 -
.../test-projects/attrOrder/app/build.gradle | 11 -
.../test-projects/attrOrder/lib/build.gradle | 7 -
.../test-projects/basic/build.gradle | 129 -
.../test-projects/basicMultiFlavors/build.gradle | 30 -
.../combinedAbiDensityPureSplits/build.gradle | 33 -
.../build.gradle | 29 -
.../test-projects/commonGradlePluginVersion.gradle | 40 -
.../test-projects/commonHeader.gradle | 55 -
.../test-projects/commonLocalRepo.gradle | 28 -
.../test-projects/componentModel/build.gradle | 47 -
.../customArtifactDep/app/build.gradle | 10 -
.../test-projects/customSigning/build.gradle | 25 -
.../test-projects/densitySplit/build.gradle | 45 -
.../test-projects/densitySplitInL/build.gradle | 25 -
.../test-projects/dependencies/build.gradle | 18 -
.../dependenciesWithVariants/build.gradle | 17 -
.../test-projects/dependencyChecker/build.gradle | 15 -
.../build.gradle | 21 -
.../src/main/AndroidManifest.xml | 20 -
.../main/java/com/android/tests/MainActivity.java | 21 -
.../test-projects/duplicateNameImport/build.gradle | 26 -
.../test-projects/embedded/main/build.gradle | 29 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../embedded/micro-apps/custom/build.gradle | 15 -
.../com/android/tests/basic/custom/MainTest.java | 43 -
.../micro-apps/custom/src/main/AndroidManifest.xml | 13 -
.../embedded/micro-apps/default/build.gradle | 15 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../embedded/micro-apps/flavor1/build.gradle | 15 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../test-projects/emptySplit/build.gradle | 21 -
.../test-projects/extractAnnotations/build.gradle | 19 -
.../extractRsEnabledAnnotations/build.gradle | 22 -
.../filteredOutBuildType/build.gradle | 16 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../test-projects/filteredOutVariants/build.gradle | 37 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../test-projects/flavored/build.gradle | 50 -
.../test-projects/flavoredlib/app/build.gradle | 25 -
.../test-projects/flavoredlib/lib/build.gradle | 18 -
.../lib/src/flavor1/AndroidManifest.xml | 15 -
.../lib/src/flavor2/AndroidManifest.xml | 15 -
.../test-projects/flavorlib/app/build.gradle | 25 -
.../test-projects/flavorlib/lib1/build.gradle | 6 -
.../test-projects/flavorlib/lib2/build.gradle | 6 -
.../flavorlibWithFailedTests/app/build.gradle | 20 -
.../flavorlibWithFailedTests/lib1/build.gradle | 6 -
.../lib1/src/main/AndroidManifest.xml | 21 -
.../flavorlibWithFailedTests/lib2/build.gradle | 6 -
.../lib2/src/main/AndroidManifest.xml | 21 -
.../test-projects/flavors/build.gradle | 27 -
.../test-projects/genFolderApi/build.gradle | 64 -
.../test-projects/genFolderApi2/build.gradle | 27 -
.../main/java/com/android/tests/basic/Main.java | 19 -
.../invalidDependencyOnAppProject/app/build.gradle | 27 -
.../invalidDependencyOnAppProject/build.gradle | 19 -
.../dependency-app/build.gradle | 22 -
.../invalidDependencyOnAppProject/settings.gradle | 18 -
.../test-projects/jarjarIntegration/build.gradle | 89 -
.../test-projects/jarjarWithJack/build.gradle | 37 -
.../test-projects/libMinify/build.gradle | 23 -
.../test-projects/libMinifyJarDep/app/build.gradle | 33 -
.../test-projects/libMinifyJarDep/lib/build.gradle | 30 -
.../test-projects/libMinifyLibDep/app/build.gradle | 31 -
.../java/com/android/tests/basic/MainTest.java | 101 -
.../test-projects/libMinifyLibDep/lib/build.gradle | 28 -
.../java/com/android/tests/basic/StringGetter.java | 25 -
.../libMinifyLibDep/lib2/build.gradle | 23 -
.../libProguardConsumerFiles/build.gradle | 29 -
.../test-projects/libTestDep/build.gradle | 17 -
.../test-projects/libsTest/app/build.gradle | 15 -
.../test-projects/libsTest/lib1/build.gradle | 16 -
.../test-projects/libsTest/lib2/build.gradle | 15 -
.../test-projects/libsTest/lib2b/build.gradle | 7 -
.../test-projects/libsTest/libapp/build.gradle | 7 -
.../test-projects/localAarTest/app/build.gradle | 13 -
.../test-projects/localAarTest/lib1/build.gradle | 17 -
.../test-projects/localJars/app/build.gradle | 15 -
.../localJars/baseLibrary/build.gradle | 15 -
.../test-projects/localJars/library/build.gradle | 10 -
.../test-projects/mavenLocal/app/build.gradle | 18 -
.../mavenLocal/baseLibrary/build.gradle | 29 -
.../test-projects/mavenLocal/library/build.gradle | 28 -
.../test-projects/maxSdkVersion/build.gradle | 52 -
.../test-projects/migrated/build.gradle | 53 -
.../test-projects/minify/build.gradle | 50 -
.../test-projects/minifyLib/app/build.gradle | 32 -
.../test-projects/minifyLib/lib/build.gradle | 14 -
.../minifyLibWithJavaRes/app/build.gradle | 39 -
.../minifyLibWithJavaRes/app/debug.keystore | Bin 1269 -> 0 bytes
.../minifyLibWithJavaRes/lib/build.gradle | 14 -
.../test-projects/multiDex/build.gradle | 39 -
.../test-projects/multiDexWithLib/app/build.gradle | 33 -
.../java/com/android/tests/basic/MainTest.java | 44 -
.../test-projects/multiDexWithLib/lib/build.gradle | 22 -
.../test-projects/multiproject/app/build.gradle | 14 -
.../multiproject/baseLibrary/build.gradle | 14 -
.../multiproject/library/build.gradle | 15 -
.../test-projects/multires/build.gradle | 17 -
.../test-projects/ndkJniLib/app/build.gradle | 55 -
.../test-projects/ndkJniLib/lib/build.gradle | 12 -
.../test-projects/ndkJniLib2/app/build.gradle | 55 -
.../ndkJniLib2/app/src/main/AndroidManifest.xml | 11 -
.../test-projects/ndkJniLib2/lib/build.gradle | 12 -
.../ndkJniLib2/lib/src/main/AndroidManifest.xml | 15 -
.../ndkJniPureSplitLib/app/build.gradle | 30 -
.../ndkJniPureSplitLib/lib/build.gradle | 12 -
.../lib/src/main/jni/hello-jni.c | 72 -
.../ndkJniPureSplitLib/settings.gradle | 1 -
.../test-projects/ndkLibPrebuilts/build.gradle | 10 -
.../com/example/hellojni/lib/HelloJniTest.java | 29 -
.../src/main/res/values/strings.xml | 4 -
.../test-projects/ndkPrebuilts/build.gradle | 29 -
.../com/example/hellojni/lib/HelloJniTest.java | 29 -
.../java/com/example/hellojni/lib/HelloJni.java | 54 -
.../ndkPrebuilts/src/main/res/values/strings.xml | 4 -
.../test-projects/ndkRsHelloCompute/build.gradle | 39 -
.../test-projects/ndkSanAngeles/build.gradle | 45 -
.../test-projects/ndkSanAngeles2/build.gradle | 51 -
.../test-projects/ndkStandaloneSo/app/build.gradle | 7 -
.../com/example/hellojni/lib/HelloJniTest.java | 29 -
.../app/src/main/AndroidManifest.xml | 15 -
.../java/com/example/hellojni/lib/HelloJni.java | 54 -
.../app/src/main/res/values/strings.xml | 4 -
.../test-projects/ndkStandaloneSo/build.gradle | 4 -
.../test-projects/ndkStandaloneSo/lib/build.gradle | 8 -
.../ndkStandaloneSo/lib/src/main/c/hello-jni.c | 72 -
.../test-projects/ndkStandaloneSo/settings.gradle | 1 -
.../test-projects/ndkVariants/build.gradle | 34 -
.../test-projects/noPngCrunch/build.gradle | 22 -
.../test-projects/noPreDex/build.gradle | 15 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../test-projects/overlay1/build.gradle | 10 -
.../test-projects/overlay2/build.gradle | 14 -
.../test-projects/overlay3/build.gradle | 40 -
.../test-projects/packagingOptions/build.gradle | 22 -
.../parentLibsTest/app/application/build.gradle | 13 -
.../test-projects/pkgOverride/build.gradle | 13 -
.../placeholderInLibsTest/app/build.gradle | 35 -
.../examplelibrary/build.gradle | 18 -
.../privateResources/app/build.gradle | 19 -
.../privateResources/mylibrary/build.gradle | 14 -
.../mylibrary/src/main/res/layout/main_layout.xml | 5 -
.../mylibrary/src/main/res/values/public.xml | 6 -
.../mylibrary/src/main/res/values/strings.xml | 6 -
.../privateResources/mylibrary2/build.gradle | 14 -
.../projectWithClassifierDep/build.gradle | 18 -
.../projectWithIvyDependency/build.gradle | 24 -
.../projectWithModules/app/build.gradle | 10 -
.../app/src/main/res/layout/main.xml | 13 -
.../projectWithModules/library/build.gradle | 6 -
.../projectWithModules/library2/build.gradle | 6 -
.../test-projects/pseudolocalized/build.gradle | 24 -
.../test-projects/renamedApk/build.gradle | 19 -
.../test-projects/renderscript/build.gradle | 14 -
.../renderscriptInLib/app/build.gradle | 14 -
.../renderscriptInLib/lib/build.gradle | 6 -
.../renderscriptMultiSrc/build.gradle | 13 -
.../test-projects/repo/app/build.gradle | 20 -
.../test-projects/repo/baseLibrary/build.gradle | 32 -
.../repo/baseLibrary/src/main/AndroidManifest.xml | 4 -
.../test-projects/repo/library/build.gradle | 32 -
.../test-projects/rsSupportMode/build.gradle | 34 -
.../test-projects/sameNamedLibs/app/build.gradle | 15 -
.../tests/libstest/app/MainActivityTest.java | 116 -
.../sameNamedLibs/lib1/libs/build.gradle | 15 -
.../sameNamedLibs/lib2/libs/build.gradle | 6 -
.../sameNamedLibs/lib2b/libs/build.gradle | 6 -
.../sameNamedLibs/libapp/libs/build.gradle | 6 -
.../separateTestModule/app/build.gradle | 12 -
.../separateTestModule/test/build.gradle | 9 -
.../app/build.gradle | 25 -
.../lib/build.gradle | 17 -
.../test/build.gradle | 9 -
.../app/build.gradle | 43 -
.../test/build.gradle | 27 -
.../app/build.gradle | 43 -
.../app/proguard.txt | 2 -
.../test/build.gradle | 27 -
.../test-projects/shrink/abisplits/build.gradle | 25 -
.../test-projects/shrink/build.gradle | 43 -
.../test-projects/shrink/keep/build.gradle | 19 -
.../test-projects/shrink/lib/build.gradle | 12 -
.../com/android/tests/shrink/RootActivity.java | 80 -
.../test-projects/shrink/src/main/res/raw/keep.xml | 3 -
.../test-projects/shrink/webview/build.gradle | 19 -
.../tests/shrink/webview/WebViewActivity.java | 27 -
.../test-projects/simpleMicroApp/main/build.gradle | 12 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../main/src/main/AndroidManifest.xml | 13 -
.../test-projects/simpleMicroApp/wear/build.gradle | 15 -
.../java/com/android/tests/basic/MainTest.java | 43 -
.../wear/src/main/AndroidManifest.xml | 13 -
.../splitAwareSeparateTestModule/app/build.gradle | 23 -
.../splitAwareSeparateTestModule/test/build.gradle | 9 -
.../test-projects/testDependency/build.gradle | 25 -
.../testDependency/src/main/AndroidManifest.xml | 13 -
.../test-projects/testWithDep/build.gradle | 15 -
.../java/com/android/tests/basic/MainTest.java | 47 -
.../testWithDep/src/main/AndroidManifest.xml | 30 -
.../testWithDep/src/main/assets/notice.txt | 190 --
.../main/java/com/android/tests/basic/Main.java | 15 -
.../testWithDep/src/main/res/raw/notice.txt | 190 --
.../testWithDep/src/main/res/values/strings.xml | 4 -
.../testWithDep/src/release/res/values/strings.xml | 4 -
.../test-projects/tictactoe/app/build.gradle | 10 -
.../test-projects/tictactoe/lib/build.gradle | 10 -
.../test-projects/unitTesting/build.gradle | 25 -
.../unitTestingBuildTypes/build.gradle | 24 -
.../unitTestingComplexProject/app/build.gradle | 40 -
.../app/src/main/java/com/android/tests/Foo.java | 30 -
.../unitTestingComplexProject/lib/build.gradle | 39 -
.../main/java/com/android/tests/lib/LibFoo.java | 23 -
.../unitTestingDefaultValues/build.gradle | 38 -
.../test-projects/unitTestingFlavors/build.gradle | 53 -
.../doesntBuild/java/com/android/tests/Broken.java | 21 -
.../unitTestingLibraryModules/build.gradle | 42 -
.../test-projects/vectorDrawables/build.gradle | 23 -
.../vectorDrawables/gradle.properties | 1 -
.../manifest-merger/manifest-merger-base.iml | 19 -
.../com/android/manifmerger/ActionRecorder.java | 307 --
.../main/java/com/android/manifmerger/Actions.java | 459 ---
.../com/android/manifmerger/AttributeModel.java | 466 ---
.../com/android/manifmerger/ElementsTrimmer.java | 153 -
.../com/android/manifmerger/ManifestMerger2.java | 1122 -------
.../com/android/manifmerger/ManifestModel.java | 681 ----
.../main/java/com/android/manifmerger/Merger.java | 212 --
.../com/android/manifmerger/MergingReport.java | 296 --
.../com/android/manifmerger/OrphanXmlElement.java | 121 -
.../android/manifmerger/PlaceholderEncoder.java | 54 -
.../android/manifmerger/PlaceholderHandler.java | 142 -
.../com/android/manifmerger/PostValidator.java | 315 --
.../java/com/android/manifmerger/PreValidator.java | 285 --
.../java/com/android/manifmerger/Selector.java | 61 -
.../manifmerger/ToolsInstructionsCleaner.java | 139 -
.../java/com/android/manifmerger/XmlAttribute.java | 398 ---
.../java/com/android/manifmerger/XmlDocument.java | 576 ----
.../java/com/android/manifmerger/XmlElement.java | 880 -----
.../java/com/android/manifmerger/XmlLoader.java | 102 -
.../main/java/com/android/manifmerger/XmlNode.java | 287 --
.../manifmerger/ManifestMerger2SmallTest.java | 520 ---
.../android/manifmerger/ManifestMerger2Test.java | 303 --
.../manifmerger/ManifestMergerSourceLinkTest.java | 257 --
.../android/manifmerger/ManifestMergerTest.java | 661 ----
.../java/com/android/manifmerger/MergerTest.java | 292 --
.../com/android/manifmerger/MergingReportTest.java | 161 -
.../manifmerger/PlaceholderHandlerTest.java | 231 --
.../com/android/builder/profile/ExecutionType.java | 120 -
.../android/builder/profile/ThreadRecorder.java | 177 -
.../main/java/com/android/builder/tasks/Job.java | 75 -
.../java/com/android/builder/tasks/WorkQueue.java | 260 --
.../build/tests/AndroidProjectConnector.java | 121 -
base/build.gradle | 7 -
.../android/tools/chartlib/TimelineComponent.java | 693 ----
.../com/android/tools/chartlib/TimelineData.java | 112 -
.../test/java/AnimatedComponentVisualTests.java | 518 ---
.../android/tools/chartlib/TimelineDataTest.java | 83 -
base/common/build.gradle | 21 -
base/common/common.iml | 17 -
.../src/main/java/com/android/SdkConstants.java | 1446 --------
.../java/com/android/ide/common/blame/Message.java | 193 --
.../com/android/ide/common/blame/SourceFile.java | 121 -
.../ide/common/blame/SourceFilePosition.java | 88 -
.../android/ide/common/blame/SourcePosition.java | 156 -
.../java/com/android/prefs/AndroidLocation.java | 166 -
.../src/main/java/com/android/utils/FileUtils.java | 99 -
.../main/java/com/android/utils/HtmlBuilder.java | 338 --
.../java/com/android/utils/PositionXmlParser.java | 791 -----
.../src/main/java/com/android/utils/SdkUtils.java | 511 ---
.../main/java/com/android/utils/StringHelper.java | 34 -
.../src/main/java/com/android/utils/XmlUtils.java | 586 ----
.../java/com/android/utils/HtmlBuilderTest.java | 139 -
.../test/java/com/android/utils/SdkUtilsTest.java | 391 ---
.../test/java/com/android/utils/XmlUtilsTest.java | 466 ---
.../main/java/com/android/ddmlib/AdbHelper.java | 894 -----
.../com/android/ddmlib/AndroidDebugBridge.java | 1183 -------
.../main/java/com/android/ddmlib/ChunkHandler.java | 206 --
.../src/main/java/com/android/ddmlib/Client.java | 948 ------
.../main/java/com/android/ddmlib/ClientData.java | 843 -----
.../src/main/java/com/android/ddmlib/Debugger.java | 353 --
.../src/main/java/com/android/ddmlib/Device.java | 1295 --------
.../java/com/android/ddmlib/DeviceMonitor.java | 884 -----
.../java/com/android/ddmlib/EmulatorConsole.java | 783 -----
.../com/android/ddmlib/FileListingService.java | 852 -----
.../main/java/com/android/ddmlib/HandleExit.java | 76 -
.../main/java/com/android/ddmlib/HandleHeap.java | 401 ---
.../main/java/com/android/ddmlib/HandleHello.java | 230 --
.../java/com/android/ddmlib/HandleNativeHeap.java | 351 --
.../java/com/android/ddmlib/HandleProfiling.java | 354 --
.../main/java/com/android/ddmlib/HandleThread.java | 379 ---
.../java/com/android/ddmlib/HandleViewDebug.java | 363 --
.../src/main/java/com/android/ddmlib/IDevice.java | 639 ----
.../main/java/com/android/ddmlib/JdwpPacket.java | 371 ---
.../src/main/java/com/android/ddmlib/Log.java | 359 --
.../java/com/android/ddmlib/MonitorThread.java | 790 -----
.../com/android/ddmlib/NativeStackCallInfo.java | 117 -
.../java/com/android/ddmlib/PropertyFetcher.java | 192 --
.../com/android/ddmlib/logcat/LogCatFilter.java | 231 --
.../com/android/ddmlib/logcat/LogCatMessage.java | 105 -
.../android/ddmlib/logcat/LogCatMessageParser.java | 101 -
.../android/ddmlib/logcat/LogCatReceiverTask.java | 136 -
.../ddmlib/testrunner/RemoteAndroidTestRunner.java | 328 --
.../android/ddmlib/logcat/LogCatFilterTest.java | 165 -
.../ddmlib/logcat/LogCatMessageParserTest.java | 101 -
.../main/resources/com/android/dvlib/devices-2.xsd | 954 ------
base/gradle/wrapper/gradle-wrapper.properties | 6 -
.../src/main/java/Twofish/Twofish_Algorithm.java | 835 -----
.../android/ide/common/rendering/api/Bridge.java | 184 --
.../android/ide/common/rendering/api/Features.java | 106 -
.../common/rendering/api/LayoutlibCallback.java | 124 -
.../ide/common/rendering/api/RenderSession.java | 309 --
.../ide/common/rendering/api/ResourceValue.java | 129 -
.../main/java/com/android/resources/Density.java | 162 -
base/legacy/ant-tasks/.classpath | 10 -
base/legacy/ant-tasks/.gitignore | 2 -
base/legacy/ant-tasks/.project | 17 -
base/legacy/ant-tasks/.settings/README.txt | 2 -
base/legacy/ant-tasks/ant-tasks.iml | 14 -
base/legacy/ant-tasks/build.gradle | 27 -
base/legacy/ant-tasks/src/main/ant-tasks.iml | 14 -
.../main/java/com/android/ant/AaptExecTask.java | 797 -----
.../main/java/com/android/ant/AidlExecTask.java | 205 --
.../main/java/com/android/ant/ApkBuilderTask.java | 393 ---
.../main/java/com/android/ant/BuildConfigTask.java | 76 -
.../main/java/com/android/ant/BuildTypedTask.java | 63 -
.../main/java/com/android/ant/CheckEnvTask.java | 105 -
.../com/android/ant/ComputeDependencyTask.java | 321 --
.../android/ant/ComputeProjectClasspathTask.java | 94 -
.../main/java/com/android/ant/DependencyGraph.java | 441 ---
.../java/com/android/ant/DependencyHelper.java | 310 --
.../src/main/java/com/android/ant/DexExecTask.java | 279 --
.../java/com/android/ant/GetBuildToolsTask.java | 91 -
.../java/com/android/ant/GetEmmaFilterTask.java | 95 -
.../java/com/android/ant/GetLibraryPathTask.java | 208 --
.../java/com/android/ant/GetProjectPathsTask.java | 70 -
.../main/java/com/android/ant/GetTargetTask.java | 301 --
.../src/main/java/com/android/ant/GetTypeTask.java | 109 -
.../main/java/com/android/ant/GetUiTargetTask.java | 160 -
.../src/main/java/com/android/ant/IfElseTask.java | 128 -
.../src/main/java/com/android/ant/InputPath.java | 107 -
.../main/java/com/android/ant/LintExecTask.java | 108 -
.../java/com/android/ant/ManifestMergerTask.java | 179 -
.../main/java/com/android/ant/MultiFilesTask.java | 201 --
.../com/android/ant/PropertyByReplaceTask.java | 48 -
.../java/com/android/ant/RenderScriptTask.java | 247 --
.../src/main/java/com/android/ant/SignApkTask.java | 147 -
.../java/com/android/ant/SingleDependencyTask.java | 168 -
.../com/android/ant/SingleInputOutputTask.java | 78 -
.../src/main/java/com/android/ant/TaskHelper.java | 161 -
.../src/main/java/com/android/ant/XPathTask.java | 90 -
.../main/java/com/android/ant/ZipAlignTask.java | 84 -
.../src/main/resources/anttasks.properties | 23 -
base/lint/cli/lint-cli.iml | 26 -
.../java/com/android/tools/lint/EcjParser.java | 2124 ------------
.../tools/lint/ExternalAnnotationRepository.java | 1170 -------
.../java/com/android/tools/lint/HtmlReporter.java | 827 -----
.../java/com/android/tools/lint/LintCliClient.java | 761 -----
.../src/main/java/com/android/tools/lint/Main.java | 1067 ------
.../main/java/com/android/tools/lint/Reporter.java | 516 ---
.../main/java/com/android/tools/lint/Warning.java | 263 --
.../java/com/android/tools/lint/XmlReporter.java | 209 --
base/lint/libs/lint-api/lint-api-base.iml | 22 -
.../tools/lint/client/api/IssueRegistry.java | 322 --
.../lint/client/api/JarFileIssueRegistry.java | 113 -
.../android/tools/lint/client/api/JavaParser.java | 601 ----
.../android/tools/lint/client/api/JavaVisitor.java | 1392 --------
.../android/tools/lint/client/api/LintClient.java | 1088 ------
.../android/tools/lint/client/api/LintDriver.java | 2557 --------------
.../android/tools/lint/detector/api/Category.java | 155 -
.../tools/lint/detector/api/ClassContext.java | 720 ----
.../tools/lint/detector/api/ConstantEvaluator.java | 463 ---
.../android/tools/lint/detector/api/Detector.java | 778 -----
.../tools/lint/detector/api/JavaContext.java | 436 ---
.../android/tools/lint/detector/api/LintUtils.java | 1265 -------
.../android/tools/lint/detector/api/Location.java | 777 -----
.../android/tools/lint/detector/api/Project.java | 1378 --------
.../com/android/tools/lint/detector/api/Scope.java | 264 --
.../tools/lint/detector/api/TextFormat.java | 300 --
.../tools/lint/checks/AnnotationDetector.java | 431 ---
.../java/com/android/tools/lint/checks/Api.java | 87 -
.../com/android/tools/lint/checks/ApiClass.java | 424 ---
.../com/android/tools/lint/checks/ApiDetector.java | 2181 ------------
.../com/android/tools/lint/checks/ApiLookup.java | 996 ------
.../com/android/tools/lint/checks/ApiParser.java | 122 -
.../lint/checks/AppCompatResourceDetector.java | 109 -
.../tools/lint/checks/AppIndexingApiDetector.java | 287 --
.../tools/lint/checks/BuiltinIssueRegistry.java | 310 --
.../android/tools/lint/checks/ButtonDetector.java | 784 -----
.../tools/lint/checks/CallSuperDetector.java | 222 --
.../lint/checks/CipherGetInstanceDetector.java | 127 -
.../android/tools/lint/checks/CleanupDetector.java | 601 ----
.../tools/lint/checks/ControlFlowGraph.java | 533 ---
.../tools/lint/checks/DeprecationDetector.java | 177 -
.../tools/lint/checks/DosLineEndingDetector.java | 117 -
.../lint/checks/DuplicateResourceDetector.java | 295 --
.../tools/lint/checks/FragmentDetector.java | 165 -
.../lint/checks/FullBackupContentDetector.java | 234 --
.../android/tools/lint/checks/GradleDetector.java | 1175 -------
.../android/tools/lint/checks/HandlerDetector.java | 139 -
.../tools/lint/checks/HardcodedValuesDetector.java | 117 -
.../android/tools/lint/checks/LocaleDetector.java | 184 --
.../com/android/tools/lint/checks/LogDetector.java | 327 --
.../tools/lint/checks/ManifestDetector.java | 1041 ------
.../android/tools/lint/checks/MathDetector.java | 100 -
.../tools/lint/checks/MissingClassDetector.java | 527 ---
.../android/tools/lint/checks/ParcelDetector.java | 138 -
.../tools/lint/checks/PermissionFinder.java | 249 --
.../tools/lint/checks/PermissionRequirement.java | 763 -----
.../android/tools/lint/checks/PluralsDatabase.java | 329 --
.../tools/lint/checks/PrivateResourceDetector.java | 323 --
.../tools/lint/checks/PropertyFileDetector.java | 193 --
.../tools/lint/checks/RegistrationDetector.java | 263 --
.../tools/lint/checks/RelativeOverlapDetector.java | 412 ---
.../lint/checks/RequiredAttributeDetector.java | 622 ----
.../tools/lint/checks/ResourcePrefixDetector.java | 197 --
.../com/android/tools/lint/checks/RtlDetector.java | 645 ----
.../tools/lint/checks/SecureRandomDetector.java | 161 -
.../lint/checks/SecureRandomGeneratorDetector.java | 240 --
.../tools/lint/checks/SecurityDetector.java | 411 ---
.../tools/lint/checks/ServiceCastDetector.java | 192 --
.../tools/lint/checks/SharedPrefsDetector.java | 325 --
.../tools/lint/checks/StringFormatDetector.java | 1682 ----------
.../lint/checks/SupportAnnotationDetector.java | 1605 ---------
.../tools/lint/checks/TranslationDetector.java | 722 ----
.../android/tools/lint/checks/TypoDetector.java | 496 ---
.../com/android/tools/lint/checks/TypoLookup.java | 778 -----
.../tools/lint/checks/UnusedResourceDetector.java | 602 ----
.../android/tools/lint/checks/ViewTagDetector.java | 176 -
.../tools/lint/checks/ViewTypeDetector.java | 334 --
.../tools/lint/checks/WakelockDetector.java | 431 ---
.../tools/lint/checks/WrongCallDetector.java | 161 -
.../android/tools/lint/checks/WrongIdDetector.java | 507 ---
base/lint/libs/lint-tests/lint-tests.iml | 28 -
.../checks/infrastructure/LintDetectorTest.java | 886 -----
.../java/com/android/tools/lint/EcjParserTest.java | 916 -----
.../lint/ExternalAnnotationRepositoryTest.java | 614 ----
.../tools/lint/MultiProjectHtmlReporterTest.java | 150 -
.../java/com/android/tools/lint/WarningTest.java | 94 -
.../tools/lint/checks/AnnotationDetectorTest.java | 183 -
.../android/tools/lint/checks/ApiDetectorTest.java | 1774 ----------
.../android/tools/lint/checks/ApiLookupTest.java | 221 --
.../lint/checks/AppIndexingApiDetectorTest.java | 452 ---
.../lint/checks/BuiltinIssueRegistryTest.java | 85 -
.../tools/lint/checks/CallSuperDetectorTest.java | 126 -
.../tools/lint/checks/CleanupDetectorTest.java | 248 --
.../tools/lint/checks/CutPasteDetectorTest.java | 55 -
.../tools/lint/checks/DeprecationDetectorTest.java | 87 -
.../lint/checks/DuplicateResourceDetectorTest.java | 153 -
.../tools/lint/checks/FragmentDetectorTest.java | 60 -
.../lint/checks/GetSignaturesDetectorTest.java | 114 -
.../tools/lint/checks/GradleDetectorTest.java | 882 -----
.../tools/lint/checks/HandlerDetectorTest.java | 72 -
.../lint/checks/HardcodedValuesDetectorTest.java | 82 -
.../tools/lint/checks/IconDetectorTest.java | 812 -----
.../lint/checks/InvalidPackageDetectorTest.java | 97 -
.../lint/checks/JavaPerformanceDetectorTest.java | 159 -
.../lint/checks/LayoutConsistencyDetectorTest.java | 111 -
.../lint/checks/LayoutInflationDetectorTest.java | 96 -
.../tools/lint/checks/LocaleDetectorTest.java | 67 -
.../android/tools/lint/checks/LogDetectorTest.java | 82 -
.../tools/lint/checks/ManifestDetectorTest.java | 945 ------
.../tools/lint/checks/MathDetectorTest.java | 67 -
.../checks/MergeRootFrameLayoutDetectorTest.java | 69 -
.../lint/checks/MissingClassDetectorTest.java | 529 ---
.../tools/lint/checks/ParcelDetectorTest.java | 50 -
.../lint/checks/PermissionRequirementTest.java | 282 --
.../tools/lint/checks/PluralsDatabaseTest.java | 716 ----
.../lint/checks/PrivateResourceDetectorTest.java | 215 --
.../lint/checks/RegistrationDetectorTest.java | 131 -
.../lint/checks/RelativeOverlapDetectorTest.java | 36 -
.../lint/checks/RequiredAttributeDetectorTest.java | 140 -
.../lint/checks/ResourcePrefixDetectorTest.java | 138 -
.../android/tools/lint/checks/RtlDetectorTest.java | 469 ---
.../tools/lint/checks/SQLiteDetectorTest.java | 44 -
.../tools/lint/checks/SdCardDetectorTest.java | 98 -
.../lint/checks/SecureRandomDetectorTest.java | 64 -
.../tools/lint/checks/SecurityDetectorTest.java | 230 --
.../tools/lint/checks/SharedPrefsDetectorTest.java | 142 -
.../lint/checks/StringFormatDetectorTest.java | 420 ---
.../lint/checks/SupportAnnotationDetectorTest.java | 1263 -------
.../tools/lint/checks/ToastDetectorTest.java | 47 -
.../tools/lint/checks/TranslationDetectorTest.java | 523 ---
.../tools/lint/checks/TypoDetectorTest.java | 212 --
.../lint/checks/UnusedResourceDetectorTest.java | 328 --
.../tools/lint/checks/ViewTagDetectorTest.java | 65 -
.../tools/lint/checks/WakelockDetectorTest.java | 188 --
.../tools/lint/checks/WrongIdDetectorTest.java | 187 --
.../checks/data/apicheck/ApiSourceCheck.java.txt | 123 -
.../checks/data/apicheck/SuppressTest1.java.txt | 137 -
.../checks/data/apicheck/SuppressTest2.java.txt | 25 -
.../checks/data/apicheck/SuppressTest3.java.txt | 25 -
.../checks/data/apicheck/SuppressTest4.java.txt | 21 -
.../data/apicheck/android-support-v4.jar.data | Bin 385685 -> 0 bytes
.../checks/data/apicheck/fragment_support.jar.data | Bin 1658 -> 0 bytes
.../lint/checks/data/apicheck/unsupported.jar.data | Bin 1196 -> 0 bytes
.../data/appcompat/AppCompatPrefTest.java.txt | 15 -
.../checks/data/appcompat/DialogFragment.java.txt | 9 -
.../data/appcompat/FragmentTransaction.java.txt | 17 -
.../lint/checks/data/bytecode/CommitTest.java.txt | 119 -
.../lint/checks/data/bytecode/GetterTest.jar.data | Bin 1298 -> 0 bytes
.../checks/data/bytecode/HandlerTest$1.class.data | Bin 629 -> 0 bytes
.../checks/data/bytecode/HandlerTest$2.class.data | Bin 720 -> 0 bytes
.../data/bytecode/HandlerTest$Inner.class.data | Bin 599 -> 0 bytes
.../bytecode/HandlerTest$StaticInner.class.data | Bin 532 -> 0 bytes
.../HandlerTest$WithArbitraryLooper.class.data | Bin 869 -> 0 bytes
.../checks/data/bytecode/HandlerTest.class.data | Bin 860 -> 0 bytes
.../lint/checks/data/bytecode/HandlerTest.java.txt | 41 -
.../data/bytecode/SecureRandomTest.class.data | Bin 1379 -> 0 bytes
.../checks/data/bytecode/SecureRandomTest.java.txt | 42 -
.../checks/data/bytecode/ViewTagTest.class.data | Bin 1645 -> 0 bytes
.../lint/checks/data/bytecode/ViewTagTest.java.txt | 37 -
.../data/bytecode/butterknife-2.0.1.jar.data | Bin 18971 -> 0 bytes
.../bytecode/dagger-compiler-1.2.1-subset.jar.data | Bin 3572 -> 0 bytes
.../tools/lint/checks/data/bytecode/javax.jar.data | Bin 1649 -> 0 bytes
.../lint/checks/data/gradle/Compatibility.gradle | 24 -
.../tools/lint/checks/data/gradle/Plus.gradle | 11 -
.../lint/checks/data/rules/appcompat.jar.data | Bin 3675 -> 0 bytes
.../src/android/support/annotation/AnyRes.java.txt | 13 -
.../support/annotation/DrawableRes.java.txt | 16 -
.../data/src/test/pkg/ActionTest1_ignore.java.txt | 10 -
.../checks/data/src/test/pkg/AlarmTest.java.txt | 22 -
.../lint/checks/data/src/test/pkg/Assert.java.txt | 27 -
.../data/src/test/pkg/DetachedFromWindow.java.txt | 40 -
.../data/src/test/pkg/LongSparseArray.java.txt | 12 -
...tivitySubclassOverridesIsValidFragment.java.txt | 10 -
.../checks/data/src/test/pkg/SQLiteTest.java.txt | 41 -
.../src/test/pkg/SetJavaScriptEnabled.java.txt | 28 -
.../data/src/test/pkg/SparseLongArray.java.txt | 12 -
.../data/src/test/pkg/StringFormat2.java.txt | 14 -
.../data/src/test/pkg/StringFormat3.java.txt | 28 -
.../data/src/test/pkg/StringFormat4.java.txt | 14 -
.../data/src/test/pkg/StringFormat5.java.txt | 18 -
.../data/src/test/pkg/StringFormat8.java.txt | 16 -
.../data/src/test/pkg/StringFormat9.java.txt | 9 -
.../src/test/pkg/StringFormatActivity.java.txt | 35 -
.../src/test/pkg/StringFormatActivity2.java.txt | 20 -
.../src/test/pkg/StringFormatActivity3.java.txt | 15 -
.../test/pkg/StringFormatActivity_ignore.java.txt | 27 -
.../data/src/test/pkg/SuppressTest5.java.txt | 63 -
.../data/src/test/pkg/SystemServiceTest.java.txt | 30 -
.../checks/data/src/test/pkg/ToastTest.java.txt | 51 -
.../data/src/test/pkg/ViewHolderTest.java.txt | 155 -
.../data/src/test/pkg/WorldWriteableFile.java.txt | 38 -
.../data/src/test/pkg/WrongAnnotation.java.txt | 36 -
.../data/src/test/pkg/WrongCastActivity.java.txt | 16 -
.../data/src/test/pkg/WrongCastActivity2.java.txt | 15 -
.../data/src/test/pkg/WrongCastActivity3.java.txt | 11 -
.../lint/client/api/JarFileIssueRegistryTest.java | 76 -
.../lint/detector/api/ConstantEvaluatorTest.java | 193 --
.../tools/lint/detector/api/LintUtilsTest.java | 523 ---
.../tools/lint/detector/api/TextFormatTest.java | 286 --
base/misc/api-generator/build.gradle | 19 -
.../com/android/apigenerator/AndroidJarReader.java | 271 --
.../main/java/com/android/apigenerator/Main.java | 142 -
.../android/tools/perflib/heap/ArrayInstance.java | 115 -
.../android/tools/perflib/heap/ClassInstance.java | 102 -
.../com/android/tools/perflib/heap/ClassObj.java | 306 --
.../java/com/android/tools/perflib/heap/Heap.java | 208 --
.../android/tools/perflib/heap/HprofParser.java | 625 ----
.../com/android/tools/perflib/heap/Instance.java | 287 --
.../java/com/android/tools/perflib/heap/Main.java | 94 -
.../com/android/tools/perflib/heap/Queries.java | 259 --
.../com/android/tools/perflib/heap/RootObj.java | 86 -
.../com/android/tools/perflib/heap/Snapshot.java | 358 --
.../com/android/tools/perflib/heap/StackFrame.java | 78 -
.../com/android/tools/perflib/heap/StackTrace.java | 73 -
.../java/com/android/tools/perflib/heap/Value.java | 43 -
.../tools/perflib/heap/analysis/Dominators.java | 126 -
.../heap/analysis/ShortestDistanceVisitor.java | 65 -
.../perflib/heap/analysis/TopologicalSort.java | 102 -
.../android/tools/perflib/heap/io/HprofBuffer.java | 48 -
.../perflib/heap/io/MemoryMappedFileBuffer.java | 220 --
.../android/tools/perflib/heap/ClassObjTest.java | 70 -
.../tools/perflib/heap/HprofParserTest.java | 180 -
.../android/tools/perflib/heap/QueriesTest.java | 52 -
.../tools/perflib/heap/SnapshotBuilder.java | 190 --
.../perflib/heap/analysis/DominatorsTest.java | 294 --
.../tools/perflib/heap/analysis/VisitorsTest.java | 162 -
.../tools/perflib/heap/io/HprofBufferTest.java | 132 -
.../tools/perflib/heap/io/InMemoryBuffer.java | 97 -
base/rpclib/rpclib.iml | 16 -
.../android/tools/rpclib/binary/BinaryObject.java | 29 -
.../tools/rpclib/binary/BinaryObjectCreator.java | 25 -
.../com/android/tools/rpclib/binary/Decodable.java | 27 -
.../com/android/tools/rpclib/binary/Decoder.java | 180 -
.../com/android/tools/rpclib/binary/Encodable.java | 27 -
.../com/android/tools/rpclib/binary/Encoder.java | 165 -
.../com/android/tools/rpclib/binary/Handle.java | 70 -
.../android/tools/rpclib/binary/ObjectTypeID.java | 45 -
.../android/tools/rpclib/multiplex/Channel.java | 96 -
.../tools/rpclib/multiplex/Multiplexer.java | 188 --
.../tools/rpclib/multiplex/PipeInputStream.java | 141 -
.../com/android/tools/rpclib/multiplex/Sender.java | 291 --
.../android/tools/rpclib/rpccore/Broadcaster.java | 84 -
.../com/android/tools/rpclib/rpccore/Call.java | 24 -
.../tools/rpclib/rpccore/ObjectFactory.java | 51 -
.../com/android/tools/rpclib/rpccore/Result.java | 21 -
.../com/android/tools/rpclib/rpccore/RpcError.java | 46 -
.../android/tools/rpclib/rpccore/RpcException.java | 29 -
.../android/tools/rpclib/binary/DecoderTest.java | 249 --
.../android/tools/rpclib/binary/EncoderTest.java | 274 --
.../android/tools/rpclib/binary/HandleTest.java | 99 -
.../tools/rpclib/binary/ObjectTypeIDTest.java | 45 -
base/sdk-common/build.gradle | 29 -
base/sdk-common/sdk-common-base.iml | 25 -
base/sdk-common/sdk-common.iml | 26 -
.../ide/common/blame/MessageJsonSerializer.java | 218 --
.../android/ide/common/blame/MessageReceiver.java | 23 -
.../common/blame/parser/LegacyNdkOutputParser.java | 220 --
.../ide/common/blame/parser/ToolOutputParser.java | 98 -
.../parser/aapt/AbstractAaptOutputParser.java | 570 ----
.../common/blame/parser/util/OutputLineReader.java | 88 -
.../ide/common/internal/WaitableExecutor.java | 195 --
.../ide/common/packaging/PackagingUtils.java | 115 -
.../common/process/BaseProcessOutputHandler.java | 79 -
.../android/ide/common/process/ProcessResult.java | 42 -
.../ide/common/process/ProcessResultImpl.java | 65 -
.../ide/common/rendering/HardwareConfigHelper.java | 335 --
.../ide/common/repository/GradleCoordinate.java | 671 ----
.../repository/ResourceVisibilityLookup.java | 467 ---
.../ide/common/repository/SdkMavenRepository.java | 218 --
.../common/res2/AbstractResourceRepository.java | 548 ---
.../java/com/android/ide/common/res2/DataItem.java | 234 --
.../com/android/ide/common/res2/DataMerger.java | 694 ----
.../ide/common/res2/DuplicateDataException.java | 72 -
.../ide/common/res2/GeneratedResourceSet.java | 69 -
.../com/android/ide/common/res2/MergeConsumer.java | 92 -
.../ide/common/res2/MergedResourceWriter.java | 428 ---
.../android/ide/common/res2/MergingException.java | 221 --
.../com/android/ide/common/res2/NodeUtils.java | 357 --
.../com/android/ide/common/res2/ResourceFile.java | 121 -
.../android/ide/common/res2/ResourceMerger.java | 366 --
.../com/android/ide/common/res2/ResourceSet.java | 541 ---
.../common/res2/ValueResourceNameValidator.java | 91 -
.../android/ide/common/res2/ValueXmlHelper.java | 410 ---
.../ide/common/resources/ResourceItemResolver.java | 396 ---
.../ide/common/resources/ResourceResolver.java | 904 -----
.../configuration/FolderConfiguration.java | 1197 -------
.../resources/configuration/LocaleQualifier.java | 566 ----
.../configuration/ScreenSizeQualifier.java | 102 -
.../android/ide/common/signing/KeystoreHelper.java | 237 --
.../ide/common/vectordrawable/Svg2Vector.java | 658 ----
.../ide/common/vectordrawable/SvgGroupNode.java | 72 -
.../ide/common/vectordrawable/SvgLeafNode.java | 177 -
.../android/ide/common/vectordrawable/SvgNode.java | 72 -
.../android/ide/common/vectordrawable/SvgTree.java | 129 -
.../ide/common/vectordrawable/VdElement.java | 28 -
.../android/ide/common/vectordrawable/VdGroup.java | 44 -
.../android/ide/common/vectordrawable/VdIcon.java | 85 -
.../ide/common/vectordrawable/VdNodeRender.java | 389 ---
.../ide/common/vectordrawable/VdParser.java | 543 ---
.../android/ide/common/vectordrawable/VdPath.java | 266 --
.../ide/common/vectordrawable/VdPreview.java | 291 --
.../android/ide/common/vectordrawable/VdTree.java | 154 -
.../common/blame/MessageJsonSerializerTest.java | 178 -
.../blame/ParsingProcessOutputHandlerTest.java | 159 -
.../common/repository/GradleCoordinateTest.java | 328 --
.../repository/ResourceVisibilityLookupTest.java | 306 --
.../common/repository/SdkMavenRepositoryTest.java | 151 -
.../android/ide/common/res2/AssetMergerTest.java | 415 ---
.../ide/common/res2/MergingExceptionTest.java | 138 -
.../com/android/ide/common/res2/NodeUtilsTest.java | 195 --
.../ide/common/res2/ResourceMergerTest.java | 1704 ----------
.../ide/common/res2/ResourceRepositoryTest.java | 756 -----
.../ide/common/res2/ResourceRepositoryTest2.java | 408 ---
.../android/ide/common/res2/ResourceSetTest.java | 231 --
.../res2/ValueResourceNameValidatorTest.java | 82 -
.../ide/common/res2/ValueXmlHelperTest.java | 185 --
.../common/resources/ResourceItemResolverTest.java | 293 --
.../ide/common/resources/ResourceResolverTest.java | 588 ----
.../common/resources/TestResourceRepository.java | 151 -
.../configuration/FolderConfigurationTest.java | 498 ---
.../configuration/LocaleQualifierTest.java | 341 --
.../configuration/ScreenSizeQualifierTest.java | 134 -
.../vectordrawable/VectorDrawbleGeneratorTest.java | 116 -
.../vectordrawable/ic_content_cut_24px.png | Bin 271 -> 0 bytes
.../testData/vectordrawable/ic_input_24px.png | Bin 148 -> 0 bytes
.../testData/vectordrawable/ic_live_help_24px.png | Bin 234 -> 0 bytes
.../vectordrawable/ic_local_library_24px.png | Bin 238 -> 0 bytes
.../vectordrawable/ic_local_phone_24px.png | Bin 253 -> 0 bytes
.../testData/vectordrawable/ic_mic_off_24px.png | Bin 300 -> 0 bytes
.../testData/vectordrawable/ic_plus_sign.png | Bin 148 -> 0 bytes
.../testData/vectordrawable/ic_shapes.png | Bin 447 -> 0 bytes
.../testData/vectordrawable/ic_temp_high.png | Bin 253 -> 0 bytes
.../vectordrawable/test_control_points_01.png | Bin 160 -> 0 bytes
.../vectordrawable/test_control_points_02.png | Bin 141 -> 0 bytes
.../vectordrawable/test_control_points_03.png | Bin 222 -> 0 bytes
base/sdklib/NOTICE | 190 --
base/sdklib/build.gradle | 68 -
base/sdklib/sdklib-base.iml | 21 -
base/sdklib/sdklib.iml | 21 -
.../java/com/android/sdklib/AndroidTargetHash.java | 182 -
.../java/com/android/sdklib/AndroidVersion.java | 392 ---
.../java/com/android/sdklib/BuildToolInfo.java | 391 ---
.../java/com/android/sdklib/IAndroidTarget.java | 334 --
.../main/java/com/android/sdklib/ISystemImage.java | 95 -
.../main/java/com/android/sdklib/SdkManager.java | 410 ---
.../java/com/android/sdklib/SdkVersionInfo.java | 351 --
.../main/java/com/android/sdklib/SystemImage.java | 325 --
.../main/java/com/android/sdklib/devices/Abi.java | 128 -
.../com/android/sdklib/devices/DeviceManager.java | 719 ----
.../java/com/android/sdklib/devices/Storage.java | 147 -
.../main/java/com/android/sdklib/devices/nexus.xml | 1073 ------
.../sdklib/internal/androidTarget/AddOnTarget.java | 463 ---
.../internal/androidTarget/MissingTarget.java | 244 --
.../androidTarget/OptionalLibraryImpl.java | 70 -
.../internal/androidTarget/PlatformTarget.java | 456 ---
.../com/android/sdklib/internal/avd/AvdInfo.java | 410 ---
.../android/sdklib/internal/avd/AvdManager.java | 2196 ------------
.../sdklib/internal/project/ProjectCreator.java | 1571 ---------
.../sdklib/internal/project/ProjectProperties.java | 594 ----
.../project/ProjectPropertiesWorkingCopy.java | 280 --
.../sdklib/internal/repository/AdbWrapper.java | 157 -
.../internal/repository/AddonsListFetcher.java | 614 ----
.../sdklib/internal/repository/DownloadCache.java | 848 -----
.../android/sdklib/internal/repository/ITask.java | 31 -
.../sdklib/internal/repository/ITaskFactory.java | 64 -
.../sdklib/internal/repository/LocalSdkParser.java | 804 -----
.../internal/repository/NullTaskMonitor.java | 140 -
.../sdklib/internal/repository/SdkStats.java | 627 ----
.../internal/repository/archives/ArchFilter.java | 324 --
.../internal/repository/archives/Archive.java | 375 ---
.../repository/archives/ArchiveInstaller.java | 1220 -------
.../repository/archives/ArchiveReplacement.java | 117 -
.../internal/repository/archives/BitSize.java | 66 -
.../internal/repository/archives/ChecksumType.java | 54 -
.../internal/repository/archives/HostOs.java | 83 -
.../internal/repository/archives/LegacyArch.java | 52 -
.../internal/repository/archives/LegacyOs.java | 52 -
.../internal/repository/packages/AddonPackage.java | 765 -----
.../repository/packages/BrokenPackage.java | 241 --
.../repository/packages/BuildToolPackage.java | 367 --
.../internal/repository/packages/DocPackage.java | 333 --
.../internal/repository/packages/ExtraPackage.java | 692 ----
.../repository/packages/FullRevisionPackage.java | 164 -
.../packages/IAndroidVersionProvider.java | 43 -
.../packages/IExactApiLevelDependency.java | 54 -
.../repository/packages/IFullRevisionProvider.java | 51 -
.../repository/packages/ILayoutlibVersion.java | 47 -
.../packages/IMinApiLevelDependency.java | 46 -
.../packages/IMinPlatformToolsDependency.java | 55 -
.../repository/packages/IMinToolsDependency.java | 48 -
.../repository/packages/IPlatformDependency.java | 42 -
.../repository/packages/LayoutlibVersionMixin.java | 136 -
.../repository/packages/MajorRevisionPackage.java | 164 -
.../repository/packages/MinToolsMixin.java | 134 -
.../repository/packages/MinToolsPackage.java | 117 -
.../packages/NoPreviewRevisionPackage.java | 163 -
.../internal/repository/packages/Package.java | 902 -----
.../repository/packages/PackageParserUtils.java | 427 ---
.../repository/packages/PlatformPackage.java | 388 ---
.../repository/packages/PlatformToolPackage.java | 313 --
.../repository/packages/SamplePackage.java | 571 ----
.../repository/packages/SourcePackage.java | 377 ---
.../repository/packages/SystemImagePackage.java | 621 ----
.../internal/repository/packages/ToolPackage.java | 384 ---
.../repository/sources/SdkAddonSource.java | 113 -
.../internal/repository/sources/SdkRepoSource.java | 528 ---
.../internal/repository/sources/SdkSource.java | 999 ------
.../repository/sources/SdkSourceCategory.java | 94 -
.../repository/sources/SdkSourceProperties.java | 254 --
.../internal/repository/sources/SdkSources.java | 444 ---
.../repository/sources/SdkSysImgSource.java | 114 -
.../internal/repository/updater/ArchiveInfo.java | 173 -
.../internal/repository/updater/ISettingsPage.java | 113 -
.../internal/repository/updater/IUpdaterData.java | 49 -
.../internal/repository/updater/PackageLoader.java | 502 ---
.../internal/repository/updater/PkgItem.java | 290 --
.../repository/updater/SdkUpdaterLogic.java | 1568 ---------
.../repository/updater/SdkUpdaterNoWindow.java | 715 ----
.../repository/updater/SettingsController.java | 392 ---
.../internal/repository/updater/UpdaterData.java | 1336 --------
.../main/java/com/android/sdklib/io/FileOp.java | 488 ---
.../main/java/com/android/sdklib/io/IFileOp.java | 161 -
.../android/sdklib/repository/FullRevision.java | 426 ---
.../android/sdklib/repository/IDescription.java | 40 -
.../sdklib/repository/IListDescription.java | 34 -
.../sdklib/repository/ISdkChangeListener.java | 54 -
.../com/android/sdklib/repository/License.java | 176 -
.../android/sdklib/repository/MajorRevision.java | 59 -
.../sdklib/repository/NoPreviewRevision.java | 61 -
.../com/android/sdklib/repository/PkgProps.java | 104 -
.../android/sdklib/repository/PreciseRevision.java | 153 -
.../android/sdklib/repository/RepoConstants.java | 213 --
.../sdklib/repository/SdkAddonConstants.java | 89 -
.../sdklib/repository/SdkAddonsListConstants.java | 109 -
.../sdklib/repository/SdkRepoConstants.java | 171 -
.../sdklib/repository/SdkStatsConstants.java | 92 -
.../sdklib/repository/SdkSysImgConstants.java | 90 -
.../repository/descriptors/IPkgCapabilities.java | 82 -
.../sdklib/repository/descriptors/IPkgDesc.java | 189 --
.../repository/descriptors/IPkgDescAddon.java | 40 -
.../repository/descriptors/IPkgDescExtra.java | 51 -
.../sdklib/repository/descriptors/IdDisplay.java | 84 -
.../sdklib/repository/descriptors/PkgDesc.java | 1206 -------
.../repository/descriptors/PkgDescAddon.java | 76 -
.../repository/descriptors/PkgDescExtra.java | 176 -
.../sdklib/repository/descriptors/PkgType.java | 225 --
.../sdklib/repository/local/LocalAddonPkgInfo.java | 503 ---
.../repository/local/LocalAddonSysImgPkgInfo.java | 65 -
.../repository/local/LocalBuildToolPkgInfo.java | 57 -
.../sdklib/repository/local/LocalDirInfo.java | 275 --
.../sdklib/repository/local/LocalDocPkgInfo.java | 47 -
.../sdklib/repository/local/LocalExtraPkgInfo.java | 121 -
.../sdklib/repository/local/LocalNdkPkgInfo.java | 47 -
.../sdklib/repository/local/LocalPkgInfo.java | 240 --
.../repository/local/LocalPlatformPkgInfo.java | 453 ---
.../repository/local/LocalPlatformToolPkgInfo.java | 45 -
.../repository/local/LocalSamplePkgInfo.java | 54 -
.../android/sdklib/repository/local/LocalSdk.java | 1239 -------
.../repository/local/LocalSourcePkgInfo.java | 52 -
.../repository/local/LocalSysImgPkgInfo.java | 135 -
.../sdklib/repository/local/LocalToolPkgInfo.java | 46 -
.../repository/local/PackageParserUtils.java | 166 -
.../com/android/sdklib/util/CommandLineParser.java | 976 ------
.../com/android/sdklib/AndroidTargetHashTest.java | 79 -
.../java/com/android/sdklib/BuildToolInfoTest.java | 90 -
.../com/android/sdklib/LayoutlibVersionTest.java | 51 -
.../java/com/android/sdklib/SdkManagerTest.java | 300 --
.../java/com/android/sdklib/SdkManagerTest7.java | 52 -
.../com/android/sdklib/SdkManagerTestCase.java | 588 ----
.../android/sdklib/devices/DeviceManagerTest.java | 365 --
.../internal/androidTarget/MockAddonTarget.java | 235 --
.../internal/androidTarget/MockPlatformTarget.java | 236 --
.../internal/repository/AddonsListFetcherTest.java | 226 --
.../internal/repository/DownloadCacheTest.java | 351 --
.../internal/repository/LocalSdkParserTest.java | 328 --
.../internal/repository/MockDownloadCache.java | 233 --
.../internal/repository/MockEmptySdkManager.java | 28 -
.../sdklib/internal/repository/SdkStatsTest.java | 172 -
.../repository/archives/ArchFilterTest.java | 102 -
.../repository/archives/ArchiveInstallerTest.java | 395 ---
.../internal/repository/archives/ArchiveTest.java | 110 -
.../internal/repository/archives/BitSizeTest.java | 33 -
.../internal/repository/archives/HostOsTest.java | 34 -
.../packages/AddonSystemImagePackageTest.java | 145 -
.../repository/packages/BrokenPackageTest.java | 68 -
.../repository/packages/BuildToolPackageTest.java | 69 -
.../repository/packages/ExtraPackageTest_Base.java | 90 -
.../repository/packages/ExtraPackageTest_v3.java | 176 -
.../repository/packages/ExtraPackageTest_v4.java | 157 -
.../packages/FullRevisionPackageTest.java | 76 -
.../repository/packages/FullRevisionTest.java | 202 --
.../repository/packages/MajorRevisionTest.java | 78 -
.../repository/packages/MinToolsPackageTest.java | 136 -
.../repository/packages/MockAddonPackage.java | 76 -
.../repository/packages/MockBrokenPackage.java | 61 -
.../repository/packages/MockBuildToolPackage.java | 75 -
.../repository/packages/MockEmptyPackage.java | 198 --
.../repository/packages/MockExtraPackage.java | 97 -
.../repository/packages/MockPlatformPackage.java | 99 -
.../packages/MockPlatformToolPackage.java | 75 -
.../repository/packages/MockSourcePackage.java | 58 -
.../packages/MockSystemImagePackage.java | 62 -
.../repository/packages/MockToolPackage.java | 115 -
.../repository/packages/NoPreviewRevisionTest.java | 163 -
.../internal/repository/packages/PackageTest.java | 223 --
.../repository/packages/PlatformPackageTest.java | 96 -
.../packages/PlatformSystemImagePackageTest.java | 134 -
.../packages/PlatformToolPackageTest.java | 69 -
.../repository/packages/PreciseRevisionTest.java | 192 --
.../repository/packages/SourcePackageTest.java | 112 -
.../repository/packages/ToolPackageTest.java | 69 -
.../repository/sources/SdkAddonSourceTest.java | 1156 -------
.../repository/sources/SdkRepoSourceTest.java | 1717 ----------
.../sources/SdkSourcePropertiesTest.java | 138 -
.../repository/sources/SdkSysImgSourceTest.java | 526 ---
.../repository/updater/SettingsControllerTest.java | 174 -
.../repository/updater/UpdaterDataTest.java | 186 --
.../java/com/android/sdklib/io/MockFileOp.java | 634 ----
.../java/com/android/sdklib/io/MockFileOpTest.java | 249 --
.../sdklib/repository/CaptureErrorHandler.java | 81 -
.../sdklib/repository/ValidateAddonXmlTest.java | 279 --
.../repository/ValidateAddonsListXmlTest.java | 123 -
.../repository/ValidateRepositoryXmlTest.java | 429 ---
.../sdklib/repository/ValidateSysImgXmlTest.java | 145 -
.../sdklib/repository/ValidateTestCase.java | 155 -
.../sdklib/repository/descriptors/PkgDescTest.java | 936 ------
.../sdklib/repository/descriptors/PkgTypeTest.java | 178 -
.../sdklib/repository/local/LocalDirInfoTest.java | 164 -
.../repository/local/LocalPlatformPkgInfoTest.java | 66 -
.../sdklib/repository/local/LocalSdkTest.java | 1134 -------
.../activities/AlwaysOnWearActivity/recipe.xml.ftl | 21 -
.../root/AndroidManifest.xml.ftl | 24 -
.../activities/AlwaysOnWearActivity/template.xml | 54 -
.../AlwaysOnWearActivity/template_thumb.png | Bin 63572 -> 0 bytes
.../activities/AndroidTVActivity/recipe.xml.ftl | 83 -
.../AndroidTVActivity/root/AndroidManifest.xml.ftl | 41 -
.../root/res/layout/activity_details.xml.ftl | 9 -
.../root/res/layout/activity_main.xml.ftl | 11 -
.../root/res/layout/playback_controls.xml.ftl | 39 -
.../root/res/values/colors.xml.ftl | 17 -
.../root/res/values/strings.xml.ftl | 40 -
.../root/src/app_package/CardPresenter.java.ftl | 137 -
.../root/src/app_package/ErrorFragment.java.ftl | 48 -
.../root/src/app_package/MainFragment.java.ftl | 286 --
.../app_package/PlaybackOverlayActivity.java.ftl | 145 -
.../app_package/PlaybackOverlayFragment.java.ftl | 430 ---
.../root/src/app_package/Utils.java.ftl | 93 -
.../src/app_package/VideoDetailsFragment.java.ftl | 200 --
.../AndroidTVActivity/template-leanback-TV.png | Bin 37407 -> 0 bytes
.../activities/AndroidTVActivity/template.xml | 107 -
.../activities/BlankActivity/recipe.xml.ftl | 26 -
.../root/src/app_package/SimpleActivity.java.ftl | 40 -
.../activities/BlankActivity/template.xml | 80 -
.../BlankActivity/template_blank_activity.png | Bin 3401 -> 0 bytes
.../BlankActivityWithFragment/globals.xml.ftl | 17 -
.../BlankActivityWithFragment/recipe.xml.ftl | 32 -
.../res/layout/activity_fragment_container.xml.ftl | 7 -
.../root/res/layout/fragment_simple.xml.ftl | 16 -
.../root/src/app_package/SimpleActivity.java.ftl | 20 -
.../BlankActivityWithFragment/template.xml | 89 -
.../activities/BlankWearActivity/recipe.xml.ftl | 23 -
.../activities/BlankWearActivity/template.xml | 74 -
.../templates-WatchViewStub-Wear.png | Bin 60510 -> 0 bytes
.../activities/EmptyActivity/recipe.xml.ftl | 17 -
.../activities/FullscreenActivity/globals.xml.ftl | 8 -
.../activities/FullscreenActivity/recipe.xml.ftl | 32 -
.../src/app_package/FullscreenActivity.java.ftl | 200 --
.../src/app_package/util/SystemUiHider.java.ftl | 172 -
.../GoogleAdMobAdsActivity/globals.xml.ftl | 16 -
.../GoogleAdMobAdsActivity/recipe.xml.ftl | 27 -
.../root/AndroidManifest.xml.ftl | 36 -
.../activities/GoogleAdMobAdsActivity/template.xml | 93 -
.../template_admob_activity.png | Bin 13800 -> 0 bytes
.../template_admob_activity_banner.png | Bin 12183 -> 0 bytes
.../template_admob_activity_interstitial.png | Bin 12917 -> 0 bytes
.../template_blank_activity.png | Bin 3401 -> 0 bytes
.../activities/GoogleMapsActivity/globals.xml.ftl | 11 -
.../activities/GoogleMapsActivity/recipe.xml.ftl | 28 -
.../root/AndroidManifest.xml.ftl | 37 -
.../activities/GoogleMapsActivity/template.xml | 68 -
.../GoogleMapsActivity/template_map_activity.png | Bin 77975 -> 0 bytes
.../GoogleMapsWearActivity/globals.xml.ftl | 10 -
.../GoogleMapsWearActivity/recipe.xml.ftl | 34 -
.../root/AndroidManifest.xml.ftl | 38 -
.../activities/GoogleMapsWearActivity/template.xml | 53 -
.../GoogleMapsWearActivity/template_thumb.png | Bin 34658 -> 0 bytes
.../GooglePlayServicesActivity/globals.xml.ftl | 7 -
.../GooglePlayServicesActivity/recipe.xml.ftl | 15 -
.../root/AndroidManifest.xml.ftl | 24 -
.../root/res/values/strings.xml.ftl | 5 -
.../root/src/app_package/activity.java.ftl | 202 --
.../GooglePlayServicesActivity/template.xml | 89 -
.../template_play_services_activity.png | Bin 42004 -> 0 bytes
.../activities/LoginActivity/globals.xml.ftl | 17 -
.../activities/LoginActivity/recipe.xml.ftl | 28 -
.../root/src/app_package/LoginActivity.java.ftl | 451 ---
.../root/src/app_package/PlusBaseActivity.java.ftl | 283 --
.../activities/MasterDetailFlow/globals.xml.ftl | 21 -
.../activities/MasterDetailFlow/recipe.xml.ftl | 44 -
.../res/layout/activity_content_twopane.xml.ftl | 39 -
.../src/app_package/ContentDetailActivity.java.ftl | 80 -
.../src/app_package/ContentDetailFragment.java.ftl | 63 -
.../src/app_package/ContentListActivity.java.ftl | 108 -
.../src/app_package/ContentListFragment.java.ftl | 153 -
.../NavigationDrawerActivity/globals.xml.ftl | 19 -
.../NavigationDrawerActivity/recipe.xml.ftl | 48 -
.../root/src/app_package/DrawerActivity.java.ftl | 86 -
.../activities/SettingsActivity/globals.xml.ftl | 8 -
.../activities/SettingsActivity/recipe.xml.ftl | 24 -
.../root/src/app_package/SettingsActivity.java.ftl | 310 --
.../activities/TabbedActivity/globals.xml.ftl | 19 -
.../activities/TabbedActivity/recipe.xml.ftl | 46 -
.../root/src/app_package/DropdownActivity.java.ftl | 88 -
.../src/app_package/TabsAndPagerActivity.java.ftl | 141 -
.../src/app_package/include_options_menu.java.ftl | 22 -
.../activities/TabbedActivity/template.xml | 104 -
.../activities/BlankActivity/recipe.xml.ftl | 28 -
.../BlankActivity/root/AndroidManifest.xml.ftl | 24 -
.../root/res/layout/activity_simple.xml.ftl | 16 -
.../BlankActivity/root/res/values/strings.xml.ftl | 9 -
.../BlankActivityWithFragment/recipe.xml.ftl | 31 -
.../root/AndroidManifest.xml.ftl | 24 -
.../root/res/values/dimens.xml.ftl | 5 -
.../root/res/values/strings.xml.ftl | 7 -
.../template_blank_activity_fragment.png | Bin 4983 -> 0 bytes
.../activities/EmptyActivity/globals.xml.ftl | 7 -
.../activities/EmptyActivity/recipe.xml.ftl | 17 -
.../EmptyActivity/root/AndroidManifest.xml.ftl | 20 -
.../root/res/layout/activity_simple.xml | 12 -
.../EmptyActivity/root/res/values/strings.xml.ftl | 6 -
.../root/src/app_package/SimpleActivity.java.ftl | 15 -
.../eclipse/activities/EmptyActivity/template.xml | 63 -
.../EmptyActivity/template_blank_activity.png | Bin 3264 -> 0 bytes
.../activities/FullscreenActivity/recipe.xml.ftl | 32 -
.../root/AndroidManifest.xml.ftl | 26 -
.../root/res/values-v11/styles.xml | 15 -
.../FullscreenActivity/root/res/values/styles.xml | 22 -
.../app_package/util/SystemUiHiderBase.java.ftl | 63 -
.../util/SystemUiHiderHoneycomb.java.ftl | 133 -
.../activities/FullscreenActivity/template.xml | 68 -
.../template_fullscreen_activity.png | Bin 16885 -> 0 bytes
.../activities/LoginActivity/recipe.xml.ftl | 28 -
.../LoginActivity/root/AndroidManifest.xml.ftl | 37 -
.../root/res/layout/activity_login.xml.ftl | 108 -
.../LoginActivity/root/res/values/strings.xml.ftl | 17 -
.../eclipse/activities/LoginActivity/template.xml | 68 -
.../LoginActivity/template_login_activity.png | Bin 5939 -> 0 bytes
.../activities/MasterDetailFlow/recipe.xml.ftl | 37 -
.../MasterDetailFlow/root/AndroidManifest.xml.ftl | 31 -
.../res/layout/activity_content_detail.xml.ftl | 7 -
.../root/res/layout/activity_content_list.xml.ftl | 10 -
.../root/res/values-large/refs.xml.ftl | 10 -
.../root/res/values-sw600dp/refs.xml.ftl | 11 -
.../activities/MasterDetailFlow/template.xml | 68 -
.../MasterDetailFlow/template_master_detail.png | Bin 7173 -> 0 bytes
.../NavigationDrawerActivity/recipe.xml.ftl | 49 -
.../root/AndroidManifest.xml.ftl | 24 -
.../root/res/layout/activity_drawer.xml.ftl | 31 -
.../NavigationDrawerActivity/template.xml | 87 -
.../template_blank_activity_drawer.png | Bin 2631 -> 0 bytes
.../activities/SettingsActivity/recipe.xml.ftl | 24 -
.../SettingsActivity/root/AndroidManifest.xml.ftl | 18 -
.../SettingsActivity/root/res/xml/pref_general.xml | 33 -
.../root/res/xml/pref_headers.xml.ftl | 17 -
.../root/res/xml/pref_notification.xml | 27 -
.../activities/SettingsActivity/template.xml | 56 -
.../template_settings_activity.png | Bin 6260 -> 0 bytes
.../activities/TabbedActivity/recipe.xml.ftl | 47 -
.../TabbedActivity/root/AndroidManifest.xml.ftl | 24 -
.../root/res/layout/activity_pager.xml.ftl | 6 -
.../root/res/layout/fragment_simple.xml.ftl | 16 -
.../TabbedActivity/root/res/menu/main.xml.ftl | 9 -
.../root/res/values-w820dp/dimens.xml | 6 -
.../TabbedActivity/root/res/values/dimens.xml.ftl | 5 -
.../TabbedActivity/root/res/values/strings.xml.ftl | 14 -
.../root/src/app_package/include_fragment.java.ftl | 36 -
.../template_blank_activity_dropdown.png | Bin 3824 -> 0 bytes
.../template_blank_activity_pager.png | Bin 2600 -> 0 bytes
.../template_blank_activity_tabs.png | Bin 3732 -> 0 bytes
.../eclipse/other/AidlFile/recipe.xml.ftl | 7 -
.../eclipse/other/AidlFolder/recipe.xml.ftl | 12 -
.../eclipse/other/AndroidManifest/recipe.xml.ftl | 14 -
.../eclipse/other/AppWidget/recipe.xml.ftl | 35 -
.../eclipse/other/AssetsFolder/recipe.xml.ftl | 12 -
.../eclipse/other/BlankFragment/recipe.xml.ftl | 19 -
.../eclipse/other/BroadcastReceiver/recipe.xml.ftl | 8 -
.../eclipse/other/ContentProvider/recipe.xml.ftl | 8 -
.../eclipse/other/CustomView/recipe.xml.ftl | 13 -
.../eclipse/other/Daydream/recipe.xml.ftl | 28 -
.../eclipse/other/IntentService/recipe.xml.ftl | 8 -
.../eclipse/other/JavaFolder/recipe.xml.ftl | 12 -
.../eclipse/other/JniFolder/recipe.xml.ftl | 12 -
.../other/LayoutResourceFile/recipe.xml.ftl | 7 -
.../eclipse/other/ListFragment/recipe.xml.ftl | 28 -
.../src/app_package/dummy/DummyContent.java.ftl | 55 -
.../eclipse/other/Notification/recipe.xml.ftl | 31 -
.../eclipse/other/PlusOneFragment/recipe.xml.ftl | 20 -
.../eclipse/other/ResFolder/recipe.xml.ftl | 12 -
.../eclipse/other/ResourcesFolder/recipe.xml.ftl | 12 -
.../eclipse/other/RsFolder/recipe.xml.ftl | 12 -
.../templates/eclipse/other/Service/recipe.xml.ftl | 8 -
.../eclipse/other/ValueResourceFile/recipe.xml.ftl | 7 -
.../projects/NewAndroidApplication/recipe.xml.ftl | 27 -
.../projects/NewAndroidApplication/template.xml | 82 -
.../projects/NewAndroidLibrary/recipe.xml.ftl | 27 -
.../projects/NewAndroidLibrary/template.xml | 82 -
.../eclipse/projects/NewJavaLibrary/recipe.xml.ftl | 5 -
.../AndroidWearModule/recipe.xml.ftl | 39 -
.../AndroidWearModule/root/AndroidManifest.xml.ftl | 11 -
.../gradle-projects/AndroidWearModule/template.xml | 99 -
.../AndroidWearModule/template_new_project.png | Bin 12408 -> 0 bytes
.../ImportExistingProject/template.xml | 13 -
.../ImportExistingProject/template_new_project.png | Bin 12408 -> 0 bytes
.../NewAndroidAutoProject/recipe.xml.ftl | 24 -
.../NewAndroidAutoProject/root/build.gradle.ftl | 29 -
.../NewAndroidAutoProject/template.xml | 24 -
.../NewAndroidAutoProject/template_new_project.png | Bin 12408 -> 0 bytes
.../NewAndroidModule/globals.xml.ftl | 17 -
.../NewAndroidModule/recipe.xml.ftl | 58 -
.../NewAndroidModule/root/AndroidManifest.xml.ftl | 13 -
.../root/res/values-v21/styles.xml | 5 -
.../root/res/values/styles.xml.ftl | 12 -
.../gradle-projects/NewAndroidModule/template.xml | 115 -
.../NewAndroidModule/template_new_project.png | Bin 12408 -> 0 bytes
.../NewAndroidProject/globals.xml.ftl | 5 -
.../NewAndroidProject/recipe.xml.ftl | 24 -
.../NewAndroidProject/root/build.gradle.ftl | 29 -
.../NewAndroidProject/root/gradle.properties.ftl | 18 -
.../NewAndroidProject/root/local.properties.ftl | 10 -
.../NewAndroidProject/root/project_ignore | 7 -
.../gradle-projects/NewAndroidProject/template.xml | 23 -
.../NewAndroidProject/template_new_project.png | Bin 12408 -> 0 bytes
.../NewAndroidTVModule/recipe.xml.ftl | 44 -
.../root/AndroidManifest.xml.ftl | 12 -
.../NewAndroidTVModule/template.xml | 100 -
.../NewAndroidTVModule/template_new_project.png | Bin 12408 -> 0 bytes
.../gradle-projects/NewGlassModule/recipe.xml.ftl | 42 -
.../NewGlassModule/root/AndroidManifest.xml.ftl | 12 -
.../gradle-projects/NewGlassModule/template.xml | 100 -
.../NewGlassModule/template_new_project.png | Bin 12408 -> 0 bytes
.../gradle-projects/NewJavaLibrary/recipe.xml.ftl | 15 -
.../NewJavaLibrary/root/settings.gradle.ftl | 1 -
.../gradle-projects/NewJavaLibrary/template.xml | 44 -
.../NewJavaLibrary/template_new_project.png | Bin 12408 -> 0 bytes
.../gradle/wrapper/gradle-wrapper.properties | 6 -
base/templates/gradle/wrapper/gradlew | 164 -
base/templates/other/AidlFile/recipe.xml.ftl | 7 -
base/templates/other/AidlFile/template.xml | 21 -
base/templates/other/AidlFolder/recipe.xml.ftl | 12 -
base/templates/other/AidlFolder/template.xml | 30 -
.../other/AndroidAutoMediaService/globals.xml.ftl | 7 -
.../other/AndroidAutoMediaService/recipe.xml.ftl | 22 -
.../other/AndroidAutoMediaService/template.xml | 49 -
.../templates-mediaService-Auto.png | Bin 9287 -> 0 bytes
.../AndroidAutoMessagingService/globals.xml.ftl | 7 -
.../AndroidAutoMessagingService/recipe.xml.ftl | 23 -
.../other/AndroidAutoMessagingService/template.xml | 50 -
.../templates-messagingService-Auto.png | Bin 8027 -> 0 bytes
.../templates/other/AndroidManifest/recipe.xml.ftl | 14 -
base/templates/other/AndroidManifest/template.xml | 30 -
base/templates/other/AppWidget/recipe.xml.ftl | 35 -
.../root/src/app_package/AppWidget.java.ftl | 65 -
.../AppWidgetConfigureActivity.java.ftl | 103 -
base/templates/other/AppWidget/template.xml | 148 -
.../other/AppWidget/thumbs/template_widget_1x1.png | Bin 2960 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x1_h.png | Bin 3750 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x1_v.png | Bin 3787 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x1_vh.png | Bin 4004 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_1x2.png | Bin 3010 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x2_h.png | Bin 3771 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x2_v.png | Bin 3835 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x2_vh.png | Bin 4065 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_1x3.png | Bin 3058 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x3_h.png | Bin 3830 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x3_v.png | Bin 3880 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x3_vh.png | Bin 4120 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_1x4.png | Bin 3114 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x4_h.png | Bin 3880 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x4_v.png | Bin 3944 -> 0 bytes
.../AppWidget/thumbs/template_widget_1x4_vh.png | Bin 4178 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_2x1.png | Bin 2973 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x1_h.png | Bin 3765 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x1_v.png | Bin 3794 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x1_vh.png | Bin 4020 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_2x2.png | Bin 3023 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x2_h.png | Bin 3784 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x2_v.png | Bin 3842 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x2_vh.png | Bin 4062 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_2x3.png | Bin 3047 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x3_h.png | Bin 3849 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x3_v.png | Bin 3890 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x3_vh.png | Bin 4133 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_2x4.png | Bin 3071 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x4_h.png | Bin 3888 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x4_v.png | Bin 3946 -> 0 bytes
.../AppWidget/thumbs/template_widget_2x4_vh.png | Bin 4168 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_3x1.png | Bin 2950 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x1_h.png | Bin 3728 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x1_v.png | Bin 3777 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x1_vh.png | Bin 3987 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_3x2.png | Bin 2995 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x2_h.png | Bin 3747 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x2_v.png | Bin 3811 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x2_vh.png | Bin 4045 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_3x3.png | Bin 3035 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x3_h.png | Bin 3790 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x3_v.png | Bin 3844 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x3_vh.png | Bin 4080 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_3x4.png | Bin 3051 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x4_h.png | Bin 3819 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x4_v.png | Bin 3887 -> 0 bytes
.../AppWidget/thumbs/template_widget_3x4_vh.png | Bin 4120 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_4x1.png | Bin 2925 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x1_h.png | Bin 3710 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x1_v.png | Bin 3747 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x1_vh.png | Bin 3973 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_4x2.png | Bin 2931 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x2_h.png | Bin 3667 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x2_v.png | Bin 3730 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x2_vh.png | Bin 3960 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_4x3.png | Bin 2934 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x3_h.png | Bin 3696 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x3_v.png | Bin 3723 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x3_vh.png | Bin 3994 -> 0 bytes
.../other/AppWidget/thumbs/template_widget_4x4.png | Bin 2938 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x4_h.png | Bin 3639 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x4_v.png | Bin 3717 -> 0 bytes
.../AppWidget/thumbs/template_widget_4x4_vh.png | Bin 3932 -> 0 bytes
base/templates/other/AssetsFolder/recipe.xml.ftl | 12 -
base/templates/other/AssetsFolder/template.xml | 30 -
base/templates/other/BlankFragment/globals.xml.ftl | 7 -
base/templates/other/BlankFragment/recipe.xml.ftl | 19 -
.../root/src/app_package/BlankFragment.java.ftl | 132 -
base/templates/other/BlankFragment/template.xml | 60 -
.../BlankFragment/template_blank_fragment.png | Bin 5266 -> 0 bytes
.../other/BroadcastReceiver/recipe.xml.ftl | 8 -
.../templates/other/BroadcastReceiver/template.xml | 32 -
.../templates/other/ContentProvider/recipe.xml.ftl | 8 -
base/templates/other/ContentProvider/template.xml | 40 -
base/templates/other/CustomView/recipe.xml.ftl | 13 -
base/templates/other/CustomView/template.xml | 28 -
base/templates/other/Daydream/recipe.xml.ftl | 28 -
.../root/src/app_package/DreamService.java.ftl | 143 -
base/templates/other/Daydream/template.xml | 47 -
.../other/DisplayNotification/globals.xml.ftl | 7 -
.../other/DisplayNotification/recipe.xml.ftl | 22 -
.../other/DisplayNotification/template.xml | 64 -
.../templates-activityView-Wear.png | Bin 47386 -> 0 bytes
base/templates/other/IntentService/recipe.xml.ftl | 8 -
base/templates/other/IntentService/template.xml | 29 -
base/templates/other/JavaFolder/recipe.xml.ftl | 12 -
base/templates/other/JavaFolder/template.xml | 30 -
base/templates/other/JniFolder/recipe.xml.ftl | 12 -
base/templates/other/JniFolder/template.xml | 30 -
.../other/LayoutResourceFile/recipe.xml.ftl | 7 -
.../other/LayoutResourceFile/template.xml | 29 -
base/templates/other/ListFragment/globals.xml.ftl | 9 -
base/templates/other/ListFragment/recipe.xml.ftl | 28 -
.../ListFragment/root/res/layout/fragment_grid.xml | 20 -
.../ListFragment/root/res/layout/fragment_list.xml | 19 -
.../root/res/values-large/refs_lrg.xml.ftl | 12 -
.../root/res/values-sw600dp/refs_lrg.xml.ftl | 12 -
.../ListFragment/root/res/values/refs.xml.ftl | 12 -
.../ListFragment/root/res/values/refs_lrg.xml.ftl | 12 -
.../root/src/app_package/ListFragment.java.ftl | 196 --
.../src/app_package/dummy/DummyContent.java.ftl | 55 -
base/templates/other/ListFragment/template.xml | 84 -
.../other/ListFragment/templates_list_fragment.png | Bin 7293 -> 0 bytes
base/templates/other/Notification/recipe.xml.ftl | 31 -
base/templates/other/Notification/template.xml | 58 -
.../Notification/template_notification_list.png | Bin 7306 -> 0 bytes
.../template_notification_list_actions.png | Bin 7850 -> 0 bytes
.../Notification/template_notification_none.png | Bin 4146 -> 0 bytes
.../template_notification_none_actions.png | Bin 5904 -> 0 bytes
.../Notification/template_notification_picture.png | Bin 6308 -> 0 bytes
.../template_notification_picture_actions.png | Bin 6749 -> 0 bytes
.../Notification/template_notification_text.png | Bin 6695 -> 0 bytes
.../template_notification_text_actions.png | Bin 7073 -> 0 bytes
.../templates/other/PlusOneFragment/recipe.xml.ftl | 20 -
.../root/src/app_package/PlusOneFragment.java.ftl | 147 -
base/templates/other/PlusOneFragment/template.xml | 45 -
.../PlusOneFragment/templates_plusone_fragment.png | Bin 6178 -> 0 bytes
base/templates/other/ResFolder/recipe.xml.ftl | 12 -
base/templates/other/ResFolder/template.xml | 30 -
.../templates/other/ResourcesFolder/recipe.xml.ftl | 12 -
base/templates/other/ResourcesFolder/template.xml | 30 -
base/templates/other/RsFolder/recipe.xml.ftl | 12 -
base/templates/other/RsFolder/template.xml | 30 -
base/templates/other/Service/recipe.xml.ftl | 8 -
base/templates/other/Service/template.xml | 34 -
.../other/ValueResourceFile/recipe.xml.ftl | 7 -
.../templates/other/ValueResourceFile/template.xml | 21 -
.../other/WatchFaceService/analog_round.png | Bin 33808 -> 0 bytes
.../other/WatchFaceService/analog_square.png | Bin 25233 -> 0 bytes
.../other/WatchFaceService/digital_round.png | Bin 33796 -> 0 bytes
.../other/WatchFaceService/digital_square.png | Bin 25112 -> 0 bytes
.../other/WatchFaceService/globals.xml.ftl | 7 -
.../other/WatchFaceService/recipe.xml.ftl | 40 -
.../WatchFaceService/root/AndroidManifest.xml.ftl | 46 -
.../root/res/values/colors.xml.ftl | 10 -
.../app_package/MyAnalogWatchFaceService.java.ftl | 269 --
.../app_package/MyDigitalWatchFaceService.java.ftl | 273 --
base/templates/other/WatchFaceService/template.xml | 49 -
.../testutils/.settings/org.eclipse.jdt.core.prefs | 98 -
.../java/com/android/testutils/SdkTestCase.java | 535 ---
{base/build-system => build-system}/.gitignore | 0
.../builder-model/NOTICE | 0
.../builder-model/build.gradle | 0
.../builder-model/builder-model.iml | 0
.../main/java/com/android/build/FilterData.java | 0
.../main/java/com/android/build/OutputFile.java | 0
.../main/java/com/android/build/VariantOutput.java | 0
.../com/android/builder/model/AaptOptions.java | 45 +
.../java/com/android/builder/model/AdbOptions.java | 0
.../com/android/builder/model/AndroidArtifact.java | 132 +
.../builder/model/AndroidArtifactOutput.java | 0
.../com/android/builder/model/AndroidLibrary.java | 0
.../com/android/builder/model/AndroidProject.java | 267 ++
.../java/com/android/builder/model/ApiVersion.java | 0
.../android/builder/model/ArtifactMetaData.java | 0
.../com/android/builder/model/BaseArtifact.java | 0
.../java/com/android/builder/model/BaseConfig.java | 119 +
.../java/com/android/builder/model/BuildType.java | 117 +
.../android/builder/model/BuildTypeContainer.java | 0
.../java/com/android/builder/model/ClassField.java | 0
.../android/builder/model/DataBindingOptions.java | 38 +
.../com/android/builder/model/Dependencies.java | 0
.../com/android/builder/model/DimensionAware.java | 0
.../java/com/android/builder/model/InstantRun.java | 47 +
.../com/android/builder/model/JavaArtifact.java | 0
.../android/builder/model/JavaCompileOptions.java | 0
.../com/android/builder/model/JavaLibrary.java | 44 +
.../java/com/android/builder/model/Library.java | 0
.../com/android/builder/model/LintOptions.java | 214 ++
.../android/builder/model/MavenCoordinates.java | 0
.../builder/model/NativeAndroidProject.java | 86 +
.../com/android/builder/model/NativeArtifact.java | 74 +
.../java/com/android/builder/model/NativeFile.java | 40 +
.../com/android/builder/model/NativeFolder.java | 44 +
.../com/android/builder/model/NativeLibrary.java | 0
.../com/android/builder/model/NativeSettings.java | 29 +
.../com/android/builder/model/NativeToolchain.java | 56 +
.../android/builder/model/PackagingOptions.java | 0
.../com/android/builder/model/ProductFlavor.java | 183 +
.../builder/model/ProductFlavorContainer.java | 0
.../com/android/builder/model/SigningConfig.java | 0
.../com/android/builder/model/SourceProvider.java | 0
.../builder/model/SourceProviderContainer.java | 0
.../java/com/android/builder/model/SyncIssue.java | 101 +
.../java/com/android/builder/model/Variant.java | 103 +
.../builder/model/VectorDrawablesOptions.java | 33 +
.../builder-test-api/build.gradle | 0
.../builder-test-api/builder-test-api.iml | 0
.../android/builder/testing/api/DeviceConfig.java | 0
.../builder/testing/api/DeviceConfigProvider.java | 0
.../testing/api/DeviceConfigProviderImpl.java | 0
.../builder/testing/api/DeviceConnector.java | 0
.../builder/testing/api/DeviceException.java | 0
.../builder/testing/api/DeviceProvider.java | 0
.../android/builder/testing/api/TestException.java | 0
.../android/builder/testing/api/TestServer.java | 0
.../builder/testing/api/DeviceConfigTest.java | 0
.../builder/.settings/org.eclipse.jdt.core.prefs | 0
.../builder/.settings/org.moreunit.prefs | 0
.../builder/MODULE_LICENSE_APACHE2 | 0
{base/build-system => build-system}/builder/NOTICE | 0
build-system/builder/build.gradle | 108 +
build-system/builder/builder.iml | 34 +
.../builder/compiling/BuildConfigGenerator.java | 0
.../builder/compiling/DependencyFileProcessor.java | 0
.../builder/compiling/ResValueGenerator.java | 0
.../builder/core/AaptPackageProcessBuilder.java | 510 +++
.../com/android/builder/core/AndroidBuilder.java | 2147 ++++++++++++
.../com/android/builder/core/ApkInfoParser.java | 0
.../builder/core/BuildToolsServiceLoader.java | 254 ++
.../com/android/builder/core/BuilderConstants.java | 0
.../android/builder/core/DefaultApiVersion.java | 109 +
.../com/android/builder/core/DefaultBuildType.java | 314 ++
.../builder/core/DefaultManifestParser.java | 127 +
.../android/builder/core/DefaultProductFlavor.java | 690 ++++
.../core/DefaultVectorDrawablesOptions.java | 94 +
.../java/com/android/builder/core/DexOptions.java | 33 +
.../android/builder/core/DexProcessBuilder.java | 273 ++
.../com/android/builder/core/ErrorReporter.java | 98 +
.../android/builder/core/JackProcessBuilder.java | 222 ++
.../com/android/builder/core/LibraryRequest.java | 0
.../com/android/builder/core/ManifestParser.java | 0
.../android/builder/core/VariantConfiguration.java | 2204 +++++++++++++
.../java/com/android/builder/core/VariantType.java | 144 +
.../builder/dependency/DependencyContainer.java | 0
.../android/builder/dependency/JarDependency.java | 0
.../android/builder/dependency/LibraryBundle.java | 0
.../builder/dependency/LibraryDependency.java | 0
.../builder/dependency/ManifestDependency.java | 0
.../builder/dependency/ManifestProvider.java | 0
.../builder/dependency/SymbolFileProvider.java | 0
.../android/builder/internal/BaseConfigImpl.java | 327 ++
.../android/builder/internal/ClassFieldImpl.java | 0
.../builder/internal/FakeAndroidTarget.java | 225 ++
.../com/android/builder/internal/InstallUtils.java | 0
.../com/android/builder/internal/SymbolLoader.java | 0
.../com/android/builder/internal/SymbolWriter.java | 0
.../builder/internal/TemplateProcessor.java | 0
.../builder/internal/TestManifestGenerator.java | 0
.../builder/internal/compiler/AidlProcessor.java | 133 +
.../android/builder/internal/compiler/DexKey.java | 78 +
.../builder/internal/compiler/DexWrapper.java | 406 +++
.../builder/internal/compiler/FileGatherer.java | 0
.../internal/compiler/JackConversionCache.java | 145 +
.../internal/compiler/LeafFolderGatherer.java | 0
.../builder/internal/compiler/PreDexCache.java | 191 ++
.../builder/internal/compiler/PreProcessCache.java | 581 ++++
.../internal/compiler/RenderScriptProcessor.java | 0
.../builder/internal/compiler/SourceSearcher.java | 0
.../internal/incremental/DependencyData.java | 0
.../internal/incremental/DependencyDataStore.java | 0
.../internal/packaging/JavaResourceProcessor.java | 0
.../builder/internal/packaging/Packager.java | 635 ++++
.../internal/packaging/zip/AlignmentRule.java | 79 +
.../internal/packaging/zip/AlignmentRules.java | 99 +
.../packaging/zip/ByteArrayEntrySource.java | 60 +
.../internal/packaging/zip/CentralDirectory.java | 407 +++
.../packaging/zip/CentralDirectoryHeader.java | 438 +++
.../internal/packaging/zip/CompressionMethod.java | 65 +
.../internal/packaging/zip/DataDescriptorType.java | 58 +
.../internal/packaging/zip/EntrySource.java | 58 +
.../builder/internal/packaging/zip/Eocd.java | 268 ++
.../internal/packaging/zip/FileEntrySource.java | 61 +
.../builder/internal/packaging/zip/FileUseMap.java | 382 +++
.../internal/packaging/zip/FileUseMapEntry.java | 162 +
.../builder/internal/packaging/zip/GPFlags.java | 156 +
.../packaging/zip/InflaterEntrySource.java | 82 +
.../internal/packaging/zip/StoredEntry.java | 544 +++
.../internal/packaging/zip/StoredEntryType.java | 32 +
.../builder/internal/packaging/zip/ZFile.java | 1693 ++++++++++
.../internal/packaging/zip/ZFileExtension.java | 148 +
.../builder/internal/packaging/zip/ZipField.java | 296 ++
.../internal/packaging/zip/ZipFieldInvariant.java | 39 +
.../packaging/zip/ZipFieldInvariantMaxValue.java | 47 +
.../zip/ZipFieldInvariantNonNegative.java | 33 +
.../internal/packaging/zip/ZipFileState.java | 37 +
.../packaging/zip/utils/CachedFileContents.java | 114 +
.../packaging/zip/utils/CachedSupplier.java | 110 +
.../packaging/zip/utils/LittleEndianUtils.java | 126 +
.../packaging/zip/utils/MsDosDateTimeUtils.java | 107 +
.../packaging/zip/utils/RandomAccessFileUtils.java | 57 +
.../internal/testing/CustomTestRunListener.java | 0
.../internal/testing/SimpleTestCallable.java | 266 ++
.../internal/utils/IOExceptionFunction.java | 34 +
.../internal/utils/IOExceptionRunnable.java | 31 +
.../builder/internal/utils/IOExceptionWrapper.java | 43 +
.../builder/packaging/DuplicateFileException.java | 71 +
.../builder/packaging/PackagerException.java | 0
.../builder/packaging/SealedPackageException.java | 0
.../builder/packaging/SigningException.java | 0
.../java/com/android/builder/png/AaptProcess.java | 299 ++
.../com/android/builder/png/QueuedCruncher.java | 292 ++
.../builder/png/VectorDrawableRenderer.java | 213 ++
.../com/android/builder/sdk/DefaultSdkLoader.java | 145 +
.../com/android/builder/sdk/PlatformLoader.java | 155 +
.../main/java/com/android/builder/sdk/SdkInfo.java | 54 +
.../java/com/android/builder/sdk/SdkLoader.java | 67 +
.../java/com/android/builder/sdk/TargetInfo.java | 0
.../builder/signing/DefaultSigningConfig.java | 0
.../android/builder/signing/SignedJarBuilder.java | 520 +++
.../android/builder/signing/SigningException.java | 0
.../android/builder/testing/ConnectedDevice.java | 323 ++
.../builder/testing/ConnectedDeviceProvider.java | 181 +
.../builder/testing/MockableJarGenerator.java | 257 ++
.../android/builder/testing/SimpleTestRunner.java | 0
.../java/com/android/builder/testing/TestData.java | 0
.../com/android/builder/testing/TestRunner.java | 0
.../builder/internal/AndroidManifest.template | 0
.../compiling/BuildConfigGeneratorTest.java | 0
.../core/AaptPackageProcessBuilderTest.java | 689 ++++
.../android/builder/core/AndroidBuilderTest.java | 168 +
.../android/builder/core/ApkInfoParserTest.java | 0
.../builder/core/DefaultProductFlavorTest.java | 0
.../android/builder/core/MockSourceProvider.java | 0
.../builder/core/VariantConfigurationTest.java | 0
.../android/builder/internal/SymbolLoaderTest.java | 0
.../android/builder/internal/SymbolWriterTest.java | 0
.../builder/internal/compiler/PreDexCacheTest.java | 532 +++
.../incremental/DependencyDataStoreTest.java | 0
.../internal/incremental/DependencyDataTest.java | 0
.../internal/packaging/zip/AlignmentTest.java | 190 ++
.../internal/packaging/zip/LinuxUnzipTest.java | 27 +
.../internal/packaging/zip/Windows7ZipTest.java | 27 +
.../zip/WindowsCompressedFoldersTest.java | 27 +
.../packaging/zip/ZFileNotificationTest.java | 340 ++
.../builder/internal/packaging/zip/ZFileTest.java | 916 +++++
.../internal/packaging/zip/ZipToolsTestCase.java | 183 +
.../zip/utils/CachedFileContentsTest.java | 101 +
.../packaging/zip/utils/LittleEndianUtilsTest.java | 112 +
.../java/com/android/builder/png/ByteUtils.java | 0
.../test/java/com/android/builder/png/Chunk.java | 0
.../builder/png/NinePatchAaptProcessorTest.java | 97 +
.../png/NinePatchAaptProcessorTestUtils.java | 297 ++
.../builder/png/NinePatchAsyncAaptProcessTest.java | 95 +
.../builder/png/VectorDrawableRendererTest.java | 302 ++
.../builder/testing/ConnectedDeviceTest.java | 172 +
.../builder/testing/MockableJarGeneratorTest.java | 0
.../src/test/resources/testData/core/aapt20.txt | 0
.../src/test/resources/testData/core/aapt21.txt | 0
.../resources/testData/dependencyData/no_output.d | 0
.../testData/dependencyData/secondary_files.d | 0
.../testData/dependencyData/windows_mode1.d | 0
.../testData/dependencyData/windows_mode2.d | 0
.../resources/testData/packaging/images/lena.png | Bin 0 -> 473831 bytes
.../src/test/resources/testData/packaging/root | 1 +
.../testData/packaging/text-files/rfc2460.txt | 2187 ++++++++++++
.../testData/packaging/text-files/wikipedia.html | 2578 +++++++++++++++
.../test/resources/testData/png/blue_patch.9.png | Bin
.../src/test/resources/testData/png/crunched.png | Bin
.../src/test/resources/testData/png/grayscale.png | Bin
.../resources/testData/png/grayscale.png.crunched | Bin
.../src/test/resources/testData/png/icon.png | Bin
.../resources/testData/png/missing_patch.9.png | Bin
.../png/ninepatch/ab_bottom_solid_dark_holo.9.png | Bin
.../ab_bottom_solid_dark_holo.9.png.crunched | Bin
.../ab_bottom_solid_dark_holo.9.png.crunched.aapt | Bin
.../ninepatch/ab_bottom_solid_inverse_holo.9.png | Bin
.../ab_bottom_solid_inverse_holo.9.png.crunched | Bin
...b_bottom_solid_inverse_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_bottom_solid_light_holo.9.png | Bin
.../ab_bottom_solid_light_holo.9.png.crunched | Bin
.../ab_bottom_solid_light_holo.9.png.crunched.aapt | Bin
.../ab_bottom_transparent_dark_holo.9.png | Bin
.../ab_bottom_transparent_dark_holo.9.png.crunched | Bin
...ottom_transparent_dark_holo.9.png.crunched.aapt | Bin
.../ab_bottom_transparent_light_holo.9.png | Bin
...ab_bottom_transparent_light_holo.9.png.crunched | Bin
...ttom_transparent_light_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_share_pack_holo_dark.9.png | Bin
.../ab_share_pack_holo_dark.9.png.crunched | Bin
.../ab_share_pack_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_share_pack_holo_light.9.png | Bin
.../ab_share_pack_holo_light.9.png.crunched | Bin
.../ab_share_pack_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_solid_dark_holo.9.png | Bin
.../ninepatch/ab_solid_dark_holo.9.png.crunched | Bin
.../ab_solid_dark_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_solid_light_holo.9.png | Bin
.../ninepatch/ab_solid_light_holo.9.png.crunched | Bin
.../ab_solid_light_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_solid_shadow_holo.9.png | Bin
.../ninepatch/ab_solid_shadow_holo.9.png.crunched | Bin
.../ab_solid_shadow_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_stacked_solid_dark_holo.9.png | Bin
.../ab_stacked_solid_dark_holo.9.png.crunched | Bin
.../ab_stacked_solid_dark_holo.9.png.crunched.aapt | Bin
.../ninepatch/ab_stacked_solid_inverse_holo.9.png | Bin
.../ab_stacked_solid_inverse_holo.9.png.crunched | Bin
..._stacked_solid_inverse_holo.9.png.crunched.aapt | Bin
.../ninepatch/ab_stacked_solid_light_holo.9.png | Bin
.../ab_stacked_solid_light_holo.9.png.crunched | Bin
...ab_stacked_solid_light_holo.9.png.crunched.aapt | Bin
.../ab_stacked_transparent_dark_holo.9.png | Bin
...ab_stacked_transparent_dark_holo.9.png.crunched | Bin
...acked_transparent_dark_holo.9.png.crunched.aapt | Bin
.../ab_stacked_transparent_light_holo.9.png | Bin
...b_stacked_transparent_light_holo.9.png.crunched | Bin
...cked_transparent_light_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_transparent_dark_holo.9.png | Bin
.../ab_transparent_dark_holo.9.png.crunched | Bin
.../ab_transparent_dark_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/ab_transparent_light_holo.9.png | Bin
.../ab_transparent_light_holo.9.png.crunched | Bin
.../ab_transparent_light_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/activity_title_bar.9.png | Bin
.../ninepatch/activity_title_bar.9.png.crunched | Bin
.../activity_title_bar.9.png.crunched.aapt | Bin
.../ninepatch/btn_cab_done_default_holo_dark.9.png | Bin
.../btn_cab_done_default_holo_dark.9.png.crunched | Bin
..._cab_done_default_holo_dark.9.png.crunched.aapt | Bin
.../btn_cab_done_default_holo_light.9.png | Bin
.../btn_cab_done_default_holo_light.9.png.crunched | Bin
...cab_done_default_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/btn_cab_done_focused_holo_dark.9.png | Bin
.../btn_cab_done_focused_holo_dark.9.png.crunched | Bin
..._cab_done_focused_holo_dark.9.png.crunched.aapt | Bin
.../btn_cab_done_focused_holo_light.9.png | Bin
.../btn_cab_done_focused_holo_light.9.png.crunched | Bin
...cab_done_focused_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/btn_cab_done_pressed_holo_dark.9.png | Bin
.../btn_cab_done_pressed_holo_dark.9.png.crunched | Bin
..._cab_done_pressed_holo_dark.9.png.crunched.aapt | Bin
.../btn_cab_done_pressed_holo_light.9.png | Bin
.../btn_cab_done_pressed_holo_light.9.png.crunched | Bin
...cab_done_pressed_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_check_label_background.9.png | Bin
.../btn_check_label_background.9.png.crunched | Bin
.../btn_check_label_background.9.png.crunched.aapt | Bin
.../btn_default_disabled_focused_holo_dark.9.png | Bin
...fault_disabled_focused_holo_dark.9.png.crunched | Bin
..._disabled_focused_holo_dark.9.png.crunched.aapt | Bin
.../btn_default_disabled_focused_holo_light.9.png | Bin
...ault_disabled_focused_holo_light.9.png.crunched | Bin
...disabled_focused_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_disabled_holo.9.png | Bin
.../btn_default_disabled_holo.9.png.crunched | Bin
.../btn_default_disabled_holo.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_disabled_holo_dark.9.png | Bin
.../btn_default_disabled_holo_dark.9.png.crunched | Bin
..._default_disabled_holo_dark.9.png.crunched.aapt | Bin
.../btn_default_disabled_holo_light.9.png | Bin
.../btn_default_disabled_holo_light.9.png.crunched | Bin
...default_disabled_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_focused_holo.9.png | Bin
.../btn_default_focused_holo.9.png.crunched | Bin
.../btn_default_focused_holo.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_focused_holo_dark.9.png | Bin
.../btn_default_focused_holo_dark.9.png.crunched | Bin
...n_default_focused_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_focused_holo_light.9.png | Bin
.../btn_default_focused_holo_light.9.png.crunched | Bin
..._default_focused_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_normal.9.png | Bin
.../ninepatch/btn_default_normal.9.png.crunched | Bin
.../btn_default_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_normal_disable.9.png | Bin
.../btn_default_normal_disable.9.png.crunched | Bin
.../btn_default_normal_disable.9.png.crunched.aapt | Bin
.../btn_default_normal_disable_focused.9.png | Bin
...n_default_normal_disable_focused.9.png.crunched | Bin
...ault_normal_disable_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_normal_holo.9.png | Bin
.../btn_default_normal_holo.9.png.crunched | Bin
.../btn_default_normal_holo.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_normal_holo_dark.9.png | Bin
.../btn_default_normal_holo_dark.9.png.crunched | Bin
...tn_default_normal_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_normal_holo_light.9.png | Bin
.../btn_default_normal_holo_light.9.png.crunched | Bin
...n_default_normal_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_pressed.9.png | Bin
.../ninepatch/btn_default_pressed.9.png.crunched | Bin
.../btn_default_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_pressed_holo.9.png | Bin
.../btn_default_pressed_holo.9.png.crunched | Bin
.../btn_default_pressed_holo.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_pressed_holo_dark.9.png | Bin
.../btn_default_pressed_holo_dark.9.png.crunched | Bin
...n_default_pressed_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_pressed_holo_light.9.png | Bin
.../btn_default_pressed_holo_light.9.png.crunched | Bin
..._default_pressed_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_selected.9.png | Bin
.../ninepatch/btn_default_selected.9.png.crunched | Bin
.../btn_default_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_small_normal.9.png | Bin
.../btn_default_small_normal.9.png.crunched | Bin
.../btn_default_small_normal.9.png.crunched.aapt | Bin
.../btn_default_small_normal_disable.9.png | Bin
...btn_default_small_normal_disable.9.png.crunched | Bin
...efault_small_normal_disable.9.png.crunched.aapt | Bin
.../btn_default_small_normal_disable_focused.9.png | Bin
...ult_small_normal_disable_focused.9.png.crunched | Bin
...mall_normal_disable_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_small_pressed.9.png | Bin
.../btn_default_small_pressed.9.png.crunched | Bin
.../btn_default_small_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_default_small_selected.9.png | Bin
.../btn_default_small_selected.9.png.crunched | Bin
.../btn_default_small_selected.9.png.crunched.aapt | Bin
.../ninepatch/btn_default_transparent_normal.9.png | Bin
.../btn_default_transparent_normal.9.png.crunched | Bin
..._default_transparent_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_dropdown_disabled.9.png | Bin
.../ninepatch/btn_dropdown_disabled.9.png.crunched | Bin
.../btn_dropdown_disabled.9.png.crunched.aapt | Bin
.../ninepatch/btn_dropdown_disabled_focused.9.png | Bin
.../btn_dropdown_disabled_focused.9.png.crunched | Bin
...n_dropdown_disabled_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_dropdown_normal.9.png | Bin
.../ninepatch/btn_dropdown_normal.9.png.crunched | Bin
.../btn_dropdown_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_dropdown_pressed.9.png | Bin
.../ninepatch/btn_dropdown_pressed.9.png.crunched | Bin
.../btn_dropdown_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_dropdown_selected.9.png | Bin
.../ninepatch/btn_dropdown_selected.9.png.crunched | Bin
.../btn_dropdown_selected.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/btn_erase_default.9.png | Bin
.../png/ninepatch/btn_erase_default.9.png.crunched | Bin
.../btn_erase_default.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/btn_erase_pressed.9.png | Bin
.../png/ninepatch/btn_erase_pressed.9.png.crunched | Bin
.../btn_erase_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_erase_selected.9.png | Bin
.../ninepatch/btn_erase_selected.9.png.crunched | Bin
.../btn_erase_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_global_search_normal.9.png | Bin
.../btn_global_search_normal.9.png.crunched | Bin
.../btn_global_search_normal.9.png.crunched.aapt | Bin
.../ninepatch/btn_group_disabled_holo_dark.9.png | Bin
.../btn_group_disabled_holo_dark.9.png.crunched | Bin
...tn_group_disabled_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/btn_group_disabled_holo_light.9.png | Bin
.../btn_group_disabled_holo_light.9.png.crunched | Bin
...n_group_disabled_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/btn_group_focused_holo_dark.9.png | Bin
.../btn_group_focused_holo_dark.9.png.crunched | Bin
...btn_group_focused_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/btn_group_focused_holo_light.9.png | Bin
.../btn_group_focused_holo_light.9.png.crunched | Bin
...tn_group_focused_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_group_normal_holo_dark.9.png | Bin
.../btn_group_normal_holo_dark.9.png.crunched | Bin
.../btn_group_normal_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/btn_group_normal_holo_light.9.png | Bin
.../btn_group_normal_holo_light.9.png.crunched | Bin
...btn_group_normal_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/btn_group_pressed_holo_dark.9.png | Bin
.../btn_group_pressed_holo_dark.9.png.crunched | Bin
...btn_group_pressed_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/btn_group_pressed_holo_light.9.png | Bin
.../btn_group_pressed_holo_light.9.png.crunched | Bin
...tn_group_pressed_holo_light.9.png.crunched.aapt | Bin
.../btn_keyboard_key_dark_normal_holo.9.png | Bin
...tn_keyboard_key_dark_normal_holo.9.png.crunched | Bin
...yboard_key_dark_normal_holo.9.png.crunched.aapt | Bin
.../btn_keyboard_key_dark_normal_off_holo.9.png | Bin
...eyboard_key_dark_normal_off_holo.9.png.crunched | Bin
...rd_key_dark_normal_off_holo.9.png.crunched.aapt | Bin
.../btn_keyboard_key_dark_normal_on_holo.9.png | Bin
...keyboard_key_dark_normal_on_holo.9.png.crunched | Bin
...ard_key_dark_normal_on_holo.9.png.crunched.aapt | Bin
.../btn_keyboard_key_dark_pressed_holo.9.png | Bin
...n_keyboard_key_dark_pressed_holo.9.png.crunched | Bin
...board_key_dark_pressed_holo.9.png.crunched.aapt | Bin
.../btn_keyboard_key_dark_pressed_off_holo.9.png | Bin
...yboard_key_dark_pressed_off_holo.9.png.crunched | Bin
...d_key_dark_pressed_off_holo.9.png.crunched.aapt | Bin
.../btn_keyboard_key_dark_pressed_on_holo.9.png | Bin
...eyboard_key_dark_pressed_on_holo.9.png.crunched | Bin
...rd_key_dark_pressed_on_holo.9.png.crunched.aapt | Bin
.../btn_keyboard_key_fulltrans_normal.9.png | Bin
...tn_keyboard_key_fulltrans_normal.9.png.crunched | Bin
...yboard_key_fulltrans_normal.9.png.crunched.aapt | Bin
.../btn_keyboard_key_fulltrans_normal_off.9.png | Bin
...eyboard_key_fulltrans_normal_off.9.png.crunched | Bin
...rd_key_fulltrans_normal_off.9.png.crunched.aapt | Bin
.../btn_keyboard_key_fulltrans_normal_on.9.png | Bin
...keyboard_key_fulltrans_normal_on.9.png.crunched | Bin
...ard_key_fulltrans_normal_on.9.png.crunched.aapt | Bin
.../btn_keyboard_key_fulltrans_pressed.9.png | Bin
...n_keyboard_key_fulltrans_pressed.9.png.crunched | Bin
...board_key_fulltrans_pressed.9.png.crunched.aapt | Bin
.../btn_keyboard_key_fulltrans_pressed_off.9.png | Bin
...yboard_key_fulltrans_pressed_off.9.png.crunched | Bin
...d_key_fulltrans_pressed_off.9.png.crunched.aapt | Bin
.../btn_keyboard_key_fulltrans_pressed_on.9.png | Bin
...eyboard_key_fulltrans_pressed_on.9.png.crunched | Bin
...rd_key_fulltrans_pressed_on.9.png.crunched.aapt | Bin
.../btn_keyboard_key_light_normal_holo.9.png | Bin
...n_keyboard_key_light_normal_holo.9.png.crunched | Bin
...board_key_light_normal_holo.9.png.crunched.aapt | Bin
.../btn_keyboard_key_light_pressed_holo.9.png | Bin
..._keyboard_key_light_pressed_holo.9.png.crunched | Bin
...oard_key_light_pressed_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_keyboard_key_normal.9.png | Bin
.../btn_keyboard_key_normal.9.png.crunched | Bin
.../btn_keyboard_key_normal.9.png.crunched.aapt | Bin
.../ninepatch/btn_keyboard_key_normal_off.9.png | Bin
.../btn_keyboard_key_normal_off.9.png.crunched | Bin
...btn_keyboard_key_normal_off.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_keyboard_key_normal_on.9.png | Bin
.../btn_keyboard_key_normal_on.9.png.crunched | Bin
.../btn_keyboard_key_normal_on.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_keyboard_key_pressed.9.png | Bin
.../btn_keyboard_key_pressed.9.png.crunched | Bin
.../btn_keyboard_key_pressed.9.png.crunched.aapt | Bin
.../ninepatch/btn_keyboard_key_pressed_off.9.png | Bin
.../btn_keyboard_key_pressed_off.9.png.crunched | Bin
...tn_keyboard_key_pressed_off.9.png.crunched.aapt | Bin
.../ninepatch/btn_keyboard_key_pressed_on.9.png | Bin
.../btn_keyboard_key_pressed_on.9.png.crunched | Bin
...btn_keyboard_key_pressed_on.9.png.crunched.aapt | Bin
.../ninepatch/btn_keyboard_key_trans_normal.9.png | Bin
.../btn_keyboard_key_trans_normal.9.png.crunched | Bin
...n_keyboard_key_trans_normal.9.png.crunched.aapt | Bin
.../btn_keyboard_key_trans_normal_off.9.png | Bin
...tn_keyboard_key_trans_normal_off.9.png.crunched | Bin
...yboard_key_trans_normal_off.9.png.crunched.aapt | Bin
.../btn_keyboard_key_trans_normal_on.9.png | Bin
...btn_keyboard_key_trans_normal_on.9.png.crunched | Bin
...eyboard_key_trans_normal_on.9.png.crunched.aapt | Bin
.../ninepatch/btn_keyboard_key_trans_pressed.9.png | Bin
.../btn_keyboard_key_trans_pressed.9.png.crunched | Bin
..._keyboard_key_trans_pressed.9.png.crunched.aapt | Bin
.../btn_keyboard_key_trans_pressed_off.9.png | Bin
...n_keyboard_key_trans_pressed_off.9.png.crunched | Bin
...board_key_trans_pressed_off.9.png.crunched.aapt | Bin
.../btn_keyboard_key_trans_pressed_on.9.png | Bin
...tn_keyboard_key_trans_pressed_on.9.png.crunched | Bin
...yboard_key_trans_pressed_on.9.png.crunched.aapt | Bin
.../btn_keyboard_key_trans_selected.9.png | Bin
.../btn_keyboard_key_trans_selected.9.png.crunched | Bin
...keyboard_key_trans_selected.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/btn_media_player.9.png | Bin
.../png/ninepatch/btn_media_player.9.png.crunched | Bin
.../ninepatch/btn_media_player.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_media_player_disabled.9.png | Bin
.../btn_media_player_disabled.9.png.crunched | Bin
.../btn_media_player_disabled.9.png.crunched.aapt | Bin
.../btn_media_player_disabled_selected.9.png | Bin
...n_media_player_disabled_selected.9.png.crunched | Bin
...ia_player_disabled_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_media_player_pressed.9.png | Bin
.../btn_media_player_pressed.9.png.crunched | Bin
.../btn_media_player_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_media_player_selected.9.png | Bin
.../btn_media_player_selected.9.png.crunched | Bin
.../btn_media_player_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_radio_label_background.9.png | Bin
.../btn_radio_label_background.9.png.crunched | Bin
.../btn_radio_label_background.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_search_dialog_default.9.png | Bin
.../btn_search_dialog_default.9.png.crunched | Bin
.../btn_search_dialog_default.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_search_dialog_pressed.9.png | Bin
.../btn_search_dialog_pressed.9.png.crunched | Bin
.../btn_search_dialog_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_search_dialog_selected.9.png | Bin
.../btn_search_dialog_selected.9.png.crunched | Bin
.../btn_search_dialog_selected.9.png.crunched.aapt | Bin
.../btn_search_dialog_voice_default.9.png | Bin
.../btn_search_dialog_voice_default.9.png.crunched | Bin
...search_dialog_voice_default.9.png.crunched.aapt | Bin
.../btn_search_dialog_voice_pressed.9.png | Bin
.../btn_search_dialog_voice_pressed.9.png.crunched | Bin
...search_dialog_voice_pressed.9.png.crunched.aapt | Bin
.../btn_search_dialog_voice_selected.9.png | Bin
...btn_search_dialog_voice_selected.9.png.crunched | Bin
...earch_dialog_voice_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_star_label_background.9.png | Bin
.../btn_star_label_background.9.png.crunched | Bin
.../btn_star_label_background.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/btn_toggle_off.9.png | Bin
.../png/ninepatch/btn_toggle_off.9.png.crunched | Bin
.../ninepatch/btn_toggle_off.9.png.crunched.aapt | Bin
...btn_toggle_off_disabled_focused_holo_dark.9.png | Bin
...e_off_disabled_focused_holo_dark.9.png.crunched | Bin
..._disabled_focused_holo_dark.9.png.crunched.aapt | Bin
...tn_toggle_off_disabled_focused_holo_light.9.png | Bin
..._off_disabled_focused_holo_light.9.png.crunched | Bin
...disabled_focused_holo_light.9.png.crunched.aapt | Bin
.../btn_toggle_off_disabled_holo_dark.9.png | Bin
...tn_toggle_off_disabled_holo_dark.9.png.crunched | Bin
...ggle_off_disabled_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_off_disabled_holo_light.9.png | Bin
...n_toggle_off_disabled_holo_light.9.png.crunched | Bin
...gle_off_disabled_holo_light.9.png.crunched.aapt | Bin
.../btn_toggle_off_focused_holo_dark.9.png | Bin
...btn_toggle_off_focused_holo_dark.9.png.crunched | Bin
...oggle_off_focused_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_off_focused_holo_light.9.png | Bin
...tn_toggle_off_focused_holo_light.9.png.crunched | Bin
...ggle_off_focused_holo_light.9.png.crunched.aapt | Bin
.../btn_toggle_off_normal_holo_dark.9.png | Bin
.../btn_toggle_off_normal_holo_dark.9.png.crunched | Bin
...toggle_off_normal_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_off_normal_holo_light.9.png | Bin
...btn_toggle_off_normal_holo_light.9.png.crunched | Bin
...oggle_off_normal_holo_light.9.png.crunched.aapt | Bin
.../btn_toggle_off_pressed_holo_dark.9.png | Bin
...btn_toggle_off_pressed_holo_dark.9.png.crunched | Bin
...oggle_off_pressed_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_off_pressed_holo_light.9.png | Bin
...tn_toggle_off_pressed_holo_light.9.png.crunched | Bin
...ggle_off_pressed_holo_light.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/btn_toggle_on.9.png | Bin
.../png/ninepatch/btn_toggle_on.9.png.crunched | Bin
.../ninepatch/btn_toggle_on.9.png.crunched.aapt | Bin
.../btn_toggle_on_disabled_focused_holo_dark.9.png | Bin
...le_on_disabled_focused_holo_dark.9.png.crunched | Bin
..._disabled_focused_holo_dark.9.png.crunched.aapt | Bin
...btn_toggle_on_disabled_focused_holo_light.9.png | Bin
...e_on_disabled_focused_holo_light.9.png.crunched | Bin
...disabled_focused_holo_light.9.png.crunched.aapt | Bin
.../btn_toggle_on_disabled_holo_dark.9.png | Bin
...btn_toggle_on_disabled_holo_dark.9.png.crunched | Bin
...oggle_on_disabled_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_on_disabled_holo_light.9.png | Bin
...tn_toggle_on_disabled_holo_light.9.png.crunched | Bin
...ggle_on_disabled_holo_light.9.png.crunched.aapt | Bin
.../btn_toggle_on_focused_holo_dark.9.png | Bin
.../btn_toggle_on_focused_holo_dark.9.png.crunched | Bin
...toggle_on_focused_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_on_focused_holo_light.9.png | Bin
...btn_toggle_on_focused_holo_light.9.png.crunched | Bin
...oggle_on_focused_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/btn_toggle_on_normal_holo_dark.9.png | Bin
.../btn_toggle_on_normal_holo_dark.9.png.crunched | Bin
..._toggle_on_normal_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_on_normal_holo_light.9.png | Bin
.../btn_toggle_on_normal_holo_light.9.png.crunched | Bin
...toggle_on_normal_holo_light.9.png.crunched.aapt | Bin
.../btn_toggle_on_pressed_holo_dark.9.png | Bin
.../btn_toggle_on_pressed_holo_dark.9.png.crunched | Bin
...toggle_on_pressed_holo_dark.9.png.crunched.aapt | Bin
.../btn_toggle_on_pressed_holo_light.9.png | Bin
...btn_toggle_on_pressed_holo_light.9.png.crunched | Bin
...oggle_on_pressed_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_down_disabled.9.png | Bin
.../btn_zoom_down_disabled.9.png.crunched | Bin
.../btn_zoom_down_disabled.9.png.crunched.aapt | Bin
.../ninepatch/btn_zoom_down_disabled_focused.9.png | Bin
.../btn_zoom_down_disabled_focused.9.png.crunched | Bin
..._zoom_down_disabled_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_down_normal.9.png | Bin
.../ninepatch/btn_zoom_down_normal.9.png.crunched | Bin
.../btn_zoom_down_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_down_pressed.9.png | Bin
.../ninepatch/btn_zoom_down_pressed.9.png.crunched | Bin
.../btn_zoom_down_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_down_selected.9.png | Bin
.../btn_zoom_down_selected.9.png.crunched | Bin
.../btn_zoom_down_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_up_disabled.9.png | Bin
.../ninepatch/btn_zoom_up_disabled.9.png.crunched | Bin
.../btn_zoom_up_disabled.9.png.crunched.aapt | Bin
.../ninepatch/btn_zoom_up_disabled_focused.9.png | Bin
.../btn_zoom_up_disabled_focused.9.png.crunched | Bin
...tn_zoom_up_disabled_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_up_normal.9.png | Bin
.../ninepatch/btn_zoom_up_normal.9.png.crunched | Bin
.../btn_zoom_up_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_up_pressed.9.png | Bin
.../ninepatch/btn_zoom_up_pressed.9.png.crunched | Bin
.../btn_zoom_up_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/btn_zoom_up_selected.9.png | Bin
.../ninepatch/btn_zoom_up_selected.9.png.crunched | Bin
.../btn_zoom_up_selected.9.png.crunched.aapt | Bin
.../cab_background_bottom_holo_dark.9.png | Bin
.../cab_background_bottom_holo_dark.9.png.crunched | Bin
...background_bottom_holo_dark.9.png.crunched.aapt | Bin
.../cab_background_bottom_holo_light.9.png | Bin
...cab_background_bottom_holo_light.9.png.crunched | Bin
...ackground_bottom_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/cab_background_top_holo_dark.9.png | Bin
.../cab_background_top_holo_dark.9.png.crunched | Bin
...ab_background_top_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/cab_background_top_holo_light.9.png | Bin
.../cab_background_top_holo_light.9.png.crunched | Bin
...b_background_top_holo_light.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/cling_bg.9.png | Bin
.../testData/png/ninepatch/cling_bg.9.png.crunched | Bin
.../png/ninepatch/cling_bg.9.png.crunched.aapt | Bin
.../png/ninepatch/cling_button_normal.9.png | Bin
.../ninepatch/cling_button_normal.9.png.crunched | Bin
.../cling_button_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/cling_button_pressed.9.png | Bin
.../ninepatch/cling_button_pressed.9.png.crunched | Bin
.../cling_button_pressed.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/code_lock_bottom.9.png | Bin
.../png/ninepatch/code_lock_bottom.9.png.crunched | Bin
.../ninepatch/code_lock_bottom.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/code_lock_left.9.png | Bin
.../png/ninepatch/code_lock_left.9.png.crunched | Bin
.../ninepatch/code_lock_left.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/code_lock_top.9.png | Bin
.../png/ninepatch/code_lock_top.9.png.crunched | Bin
.../ninepatch/code_lock_top.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/contact_header_bg.9.png | Bin
.../png/ninepatch/contact_header_bg.9.png.crunched | Bin
.../contact_header_bg.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/dark_header.9.png | Bin
.../png/ninepatch/dark_header.9.png.crunched | Bin
.../png/ninepatch/dark_header.9.png.crunched.aapt | Bin
.../day_picker_week_view_dayline_holo.9.png | Bin
...ay_picker_week_view_dayline_holo.9.png.crunched | Bin
...cker_week_view_dayline_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_bottom_holo_dark.9.png | Bin
.../dialog_bottom_holo_dark.9.png.crunched | Bin
.../dialog_bottom_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_bottom_holo_light.9.png | Bin
.../dialog_bottom_holo_light.9.png.crunched | Bin
.../dialog_bottom_holo_light.9.png.crunched.aapt | Bin
.../dialog_divider_horizontal_holo_dark.9.png | Bin
...log_divider_horizontal_holo_dark.9.png.crunched | Bin
...ivider_horizontal_holo_dark.9.png.crunched.aapt | Bin
.../dialog_divider_horizontal_holo_light.9.png | Bin
...og_divider_horizontal_holo_light.9.png.crunched | Bin
...vider_horizontal_holo_light.9.png.crunched.aapt | Bin
.../dialog_divider_horizontal_light.9.png | Bin
.../dialog_divider_horizontal_light.9.png.crunched | Bin
...og_divider_horizontal_light.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_full_holo_dark.9.png | Bin
.../ninepatch/dialog_full_holo_dark.9.png.crunched | Bin
.../dialog_full_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_full_holo_light.9.png | Bin
.../dialog_full_holo_light.9.png.crunched | Bin
.../dialog_full_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_middle_holo.9.png | Bin
.../ninepatch/dialog_middle_holo.9.png.crunched | Bin
.../dialog_middle_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_middle_holo_dark.9.png | Bin
.../dialog_middle_holo_dark.9.png.crunched | Bin
.../dialog_middle_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_middle_holo_light.9.png | Bin
.../dialog_middle_holo_light.9.png.crunched | Bin
.../dialog_middle_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_top_holo_dark.9.png | Bin
.../ninepatch/dialog_top_holo_dark.9.png.crunched | Bin
.../dialog_top_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/dialog_top_holo_light.9.png | Bin
.../ninepatch/dialog_top_holo_light.9.png.crunched | Bin
.../dialog_top_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/divider_horizontal_bright.9.png | Bin
.../divider_horizontal_bright.9.png.crunched | Bin
.../divider_horizontal_bright.9.png.crunched.aapt | Bin
.../divider_horizontal_bright_opaque.9.png | Bin
...divider_horizontal_bright_opaque.9.png.crunched | Bin
...er_horizontal_bright_opaque.9.png.crunched.aapt | Bin
.../png/ninepatch/divider_horizontal_dark.9.png | Bin
.../divider_horizontal_dark.9.png.crunched | Bin
.../divider_horizontal_dark.9.png.crunched.aapt | Bin
.../ninepatch/divider_horizontal_dark_opaque.9.png | Bin
.../divider_horizontal_dark_opaque.9.png.crunched | Bin
...ider_horizontal_dark_opaque.9.png.crunched.aapt | Bin
.../ninepatch/divider_horizontal_dim_dark.9.png | Bin
.../divider_horizontal_dim_dark.9.png.crunched | Bin
...divider_horizontal_dim_dark.9.png.crunched.aapt | Bin
.../ninepatch/divider_horizontal_holo_dark.9.png | Bin
.../divider_horizontal_holo_dark.9.png.crunched | Bin
...ivider_horizontal_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/divider_horizontal_holo_light.9.png | Bin
.../divider_horizontal_holo_light.9.png.crunched | Bin
...vider_horizontal_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/divider_horizontal_textfield.9.png | Bin
.../divider_horizontal_textfield.9.png.crunched | Bin
...ivider_horizontal_textfield.9.png.crunched.aapt | Bin
.../png/ninepatch/divider_strong_holo.9.png | Bin
.../ninepatch/divider_strong_holo.9.png.crunched | Bin
.../divider_strong_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/divider_vertical_bright.9.png | Bin
.../divider_vertical_bright.9.png.crunched | Bin
.../divider_vertical_bright.9.png.crunched.aapt | Bin
.../ninepatch/divider_vertical_bright_opaque.9.png | Bin
.../divider_vertical_bright_opaque.9.png.crunched | Bin
...ider_vertical_bright_opaque.9.png.crunched.aapt | Bin
.../png/ninepatch/divider_vertical_dark.9.png | Bin
.../ninepatch/divider_vertical_dark.9.png.crunched | Bin
.../divider_vertical_dark.9.png.crunched.aapt | Bin
.../ninepatch/divider_vertical_dark_opaque.9.png | Bin
.../divider_vertical_dark_opaque.9.png.crunched | Bin
...ivider_vertical_dark_opaque.9.png.crunched.aapt | Bin
.../png/ninepatch/divider_vertical_holo_dark.9.png | Bin
.../divider_vertical_holo_dark.9.png.crunched | Bin
.../divider_vertical_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/divider_vertical_holo_light.9.png | Bin
.../divider_vertical_holo_light.9.png.crunched | Bin
...divider_vertical_holo_light.9.png.crunched.aapt | Bin
.../dropdown_disabled_focused_holo_dark.9.png | Bin
...pdown_disabled_focused_holo_dark.9.png.crunched | Bin
..._disabled_focused_holo_dark.9.png.crunched.aapt | Bin
.../dropdown_disabled_focused_holo_light.9.png | Bin
...down_disabled_focused_holo_light.9.png.crunched | Bin
...disabled_focused_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/dropdown_disabled_holo_dark.9.png | Bin
.../dropdown_disabled_holo_dark.9.png.crunched | Bin
...dropdown_disabled_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/dropdown_disabled_holo_light.9.png | Bin
.../dropdown_disabled_holo_light.9.png.crunched | Bin
...ropdown_disabled_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/dropdown_focused_holo_dark.9.png | Bin
.../dropdown_focused_holo_dark.9.png.crunched | Bin
.../dropdown_focused_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/dropdown_focused_holo_light.9.png | Bin
.../dropdown_focused_holo_light.9.png.crunched | Bin
...dropdown_focused_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/dropdown_normal_holo_dark.9.png | Bin
.../dropdown_normal_holo_dark.9.png.crunched | Bin
.../dropdown_normal_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/dropdown_normal_holo_light.9.png | Bin
.../dropdown_normal_holo_light.9.png.crunched | Bin
.../dropdown_normal_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/dropdown_pressed_holo_dark.9.png | Bin
.../dropdown_pressed_holo_dark.9.png.crunched | Bin
.../dropdown_pressed_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/dropdown_pressed_holo_light.9.png | Bin
.../dropdown_pressed_holo_light.9.png.crunched | Bin
...dropdown_pressed_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/edit_query_background_normal.9.png | Bin
.../edit_query_background_normal.9.png.crunched | Bin
...dit_query_background_normal.9.png.crunched.aapt | Bin
.../ninepatch/edit_query_background_pressed.9.png | Bin
.../edit_query_background_pressed.9.png.crunched | Bin
...it_query_background_pressed.9.png.crunched.aapt | Bin
.../ninepatch/edit_query_background_selected.9.png | Bin
.../edit_query_background_selected.9.png.crunched | Bin
...t_query_background_selected.9.png.crunched.aapt | Bin
.../editbox_background_focus_yellow.9.png | Bin
.../editbox_background_focus_yellow.9.png.crunched | Bin
...box_background_focus_yellow.9.png.crunched.aapt | Bin
.../png/ninepatch/editbox_background_normal.9.png | Bin
.../editbox_background_normal.9.png.crunched | Bin
.../editbox_background_normal.9.png.crunched.aapt | Bin
.../ninepatch/editbox_dropdown_background.9.png | Bin
.../editbox_dropdown_background.9.png.crunched | Bin
...editbox_dropdown_background.9.png.crunched.aapt | Bin
.../editbox_dropdown_background_dark.9.png | Bin
...editbox_dropdown_background_dark.9.png.crunched | Bin
...ox_dropdown_background_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/expander_close_holo_dark.9.png | Bin
.../expander_close_holo_dark.9.png.crunched | Bin
.../expander_close_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/expander_close_holo_light.9.png | Bin
.../expander_close_holo_light.9.png.crunched | Bin
.../expander_close_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/expander_ic_maximized.9.png | Bin
.../ninepatch/expander_ic_maximized.9.png.crunched | Bin
.../expander_ic_maximized.9.png.crunched.aapt | Bin
.../png/ninepatch/expander_ic_minimized.9.png | Bin
.../ninepatch/expander_ic_minimized.9.png.crunched | Bin
.../expander_ic_minimized.9.png.crunched.aapt | Bin
.../png/ninepatch/expander_open_holo_dark.9.png | Bin
.../expander_open_holo_dark.9.png.crunched | Bin
.../expander_open_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/expander_open_holo_light.9.png | Bin
.../expander_open_holo_light.9.png.crunched | Bin
.../expander_open_holo_light.9.png.crunched.aapt | Bin
.../fastscroll_label_left_holo_dark.9.png | Bin
.../fastscroll_label_left_holo_dark.9.png.crunched | Bin
...scroll_label_left_holo_dark.9.png.crunched.aapt | Bin
.../fastscroll_label_left_holo_light.9.png | Bin
...fastscroll_label_left_holo_light.9.png.crunched | Bin
...croll_label_left_holo_light.9.png.crunched.aapt | Bin
.../fastscroll_label_right_holo_dark.9.png | Bin
...fastscroll_label_right_holo_dark.9.png.crunched | Bin
...croll_label_right_holo_dark.9.png.crunched.aapt | Bin
.../fastscroll_label_right_holo_light.9.png | Bin
...astscroll_label_right_holo_light.9.png.crunched | Bin
...roll_label_right_holo_light.9.png.crunched.aapt | Bin
.../fastscroll_track_default_holo_dark.9.png | Bin
...stscroll_track_default_holo_dark.9.png.crunched | Bin
...oll_track_default_holo_dark.9.png.crunched.aapt | Bin
.../fastscroll_track_default_holo_light.9.png | Bin
...tscroll_track_default_holo_light.9.png.crunched | Bin
...ll_track_default_holo_light.9.png.crunched.aapt | Bin
.../fastscroll_track_pressed_holo_dark.9.png | Bin
...stscroll_track_pressed_holo_dark.9.png.crunched | Bin
...oll_track_pressed_holo_dark.9.png.crunched.aapt | Bin
.../fastscroll_track_pressed_holo_light.9.png | Bin
...tscroll_track_pressed_holo_light.9.png.crunched | Bin
...ll_track_pressed_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/frame_gallery_thumb.9.png | Bin
.../ninepatch/frame_gallery_thumb.9.png.crunched | Bin
.../frame_gallery_thumb.9.png.crunched.aapt | Bin
.../ninepatch/frame_gallery_thumb_pressed.9.png | Bin
.../frame_gallery_thumb_pressed.9.png.crunched | Bin
...frame_gallery_thumb_pressed.9.png.crunched.aapt | Bin
.../ninepatch/frame_gallery_thumb_selected.9.png | Bin
.../frame_gallery_thumb_selected.9.png.crunched | Bin
...rame_gallery_thumb_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/gallery_selected_default.9.png | Bin
.../gallery_selected_default.9.png.crunched | Bin
.../gallery_selected_default.9.png.crunched.aapt | Bin
.../png/ninepatch/gallery_selected_focused.9.png | Bin
.../gallery_selected_focused.9.png.crunched | Bin
.../gallery_selected_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/gallery_selected_pressed.9.png | Bin
.../gallery_selected_pressed.9.png.crunched | Bin
.../gallery_selected_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/gallery_unselected_default.9.png | Bin
.../gallery_unselected_default.9.png.crunched | Bin
.../gallery_unselected_default.9.png.crunched.aapt | Bin
.../png/ninepatch/gallery_unselected_pressed.9.png | Bin
.../gallery_unselected_pressed.9.png.crunched | Bin
.../gallery_unselected_pressed.9.png.crunched.aapt | Bin
.../ninepatch/grid_selector_background_focus.9.png | Bin
.../grid_selector_background_focus.9.png.crunched | Bin
...d_selector_background_focus.9.png.crunched.aapt | Bin
.../grid_selector_background_pressed.9.png | Bin
...grid_selector_background_pressed.9.png.crunched | Bin
...selector_background_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/highlight_disabled.9.png | Bin
.../ninepatch/highlight_disabled.9.png.crunched | Bin
.../highlight_disabled.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/highlight_pressed.9.png | Bin
.../png/ninepatch/highlight_pressed.9.png.crunched | Bin
.../highlight_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/highlight_selected.9.png | Bin
.../ninepatch/highlight_selected.9.png.crunched | Bin
.../highlight_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/ic_notification_overlay.9.png | Bin
.../ic_notification_overlay.9.png.crunched | Bin
.../ic_notification_overlay.9.png.crunched.aapt | Bin
.../png/ninepatch/icon_highlight_rectangle.9.png | Bin
.../icon_highlight_rectangle.9.png.crunched | Bin
.../icon_highlight_rectangle.9.png.crunched.aapt | Bin
.../png/ninepatch/icon_highlight_square.9.png | Bin
.../ninepatch/icon_highlight_square.9.png.crunched | Bin
.../icon_highlight_square.9.png.crunched.aapt | Bin
.../jog_tab_bar_left_end_confirm_gray.9.png | Bin
...og_tab_bar_left_end_confirm_gray.9.png.crunched | Bin
...b_bar_left_end_confirm_gray.9.png.crunched.aapt | Bin
.../jog_tab_bar_left_end_confirm_green.9.png | Bin
...g_tab_bar_left_end_confirm_green.9.png.crunched | Bin
..._bar_left_end_confirm_green.9.png.crunched.aapt | Bin
.../jog_tab_bar_left_end_confirm_red.9.png | Bin
...jog_tab_bar_left_end_confirm_red.9.png.crunched | Bin
...ab_bar_left_end_confirm_red.9.png.crunched.aapt | Bin
.../jog_tab_bar_left_end_confirm_yellow.9.png | Bin
..._tab_bar_left_end_confirm_yellow.9.png.crunched | Bin
...bar_left_end_confirm_yellow.9.png.crunched.aapt | Bin
.../ninepatch/jog_tab_bar_left_end_normal.9.png | Bin
.../jog_tab_bar_left_end_normal.9.png.crunched | Bin
...jog_tab_bar_left_end_normal.9.png.crunched.aapt | Bin
.../ninepatch/jog_tab_bar_left_end_pressed.9.png | Bin
.../jog_tab_bar_left_end_pressed.9.png.crunched | Bin
...og_tab_bar_left_end_pressed.9.png.crunched.aapt | Bin
.../jog_tab_bar_right_end_confirm_gray.9.png | Bin
...g_tab_bar_right_end_confirm_gray.9.png.crunched | Bin
..._bar_right_end_confirm_gray.9.png.crunched.aapt | Bin
.../jog_tab_bar_right_end_confirm_green.9.png | Bin
..._tab_bar_right_end_confirm_green.9.png.crunched | Bin
...bar_right_end_confirm_green.9.png.crunched.aapt | Bin
.../jog_tab_bar_right_end_confirm_red.9.png | Bin
...og_tab_bar_right_end_confirm_red.9.png.crunched | Bin
...b_bar_right_end_confirm_red.9.png.crunched.aapt | Bin
.../jog_tab_bar_right_end_confirm_yellow.9.png | Bin
...tab_bar_right_end_confirm_yellow.9.png.crunched | Bin
...ar_right_end_confirm_yellow.9.png.crunched.aapt | Bin
.../ninepatch/jog_tab_bar_right_end_normal.9.png | Bin
.../jog_tab_bar_right_end_normal.9.png.crunched | Bin
...og_tab_bar_right_end_normal.9.png.crunched.aapt | Bin
.../ninepatch/jog_tab_bar_right_end_pressed.9.png | Bin
.../jog_tab_bar_right_end_pressed.9.png.crunched | Bin
...g_tab_bar_right_end_pressed.9.png.crunched.aapt | Bin
.../keyboard_accessory_bg_landscape.9.png | Bin
.../keyboard_accessory_bg_landscape.9.png.crunched | Bin
...oard_accessory_bg_landscape.9.png.crunched.aapt | Bin
.../png/ninepatch/keyboard_background.9.png | Bin
.../ninepatch/keyboard_background.9.png.crunched | Bin
.../keyboard_background.9.png.crunched.aapt | Bin
.../keyboard_key_feedback_background.9.png | Bin
...keyboard_key_feedback_background.9.png.crunched | Bin
...ard_key_feedback_background.9.png.crunched.aapt | Bin
.../keyboard_key_feedback_more_background.9.png | Bin
...ard_key_feedback_more_background.9.png.crunched | Bin
...ey_feedback_more_background.9.png.crunched.aapt | Bin
.../keyboard_popup_panel_background.9.png | Bin
.../keyboard_popup_panel_background.9.png.crunched | Bin
...oard_popup_panel_background.9.png.crunched.aapt | Bin
.../keyboard_popup_panel_trans_background.9.png | Bin
...ard_popup_panel_trans_background.9.png.crunched | Bin
...opup_panel_trans_background.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/light_header.9.png | Bin
.../png/ninepatch/light_header.9.png.crunched | Bin
.../png/ninepatch/light_header.9.png.crunched.aapt | Bin
.../png/ninepatch/list_activated_holo.9.png | Bin
.../ninepatch/list_activated_holo.9.png.crunched | Bin
.../list_activated_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/list_divider_holo_dark.9.png | Bin
.../list_divider_holo_dark.9.png.crunched | Bin
.../list_divider_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/list_divider_holo_light.9.png | Bin
.../list_divider_holo_light.9.png.crunched | Bin
.../list_divider_holo_light.9.png.crunched.aapt | Bin
.../list_divider_horizontal_holo_dark.9.png | Bin
...ist_divider_horizontal_holo_dark.9.png.crunched | Bin
...ivider_horizontal_holo_dark.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/list_focused_holo.9.png | Bin
.../png/ninepatch/list_focused_holo.9.png.crunched | Bin
.../list_focused_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/list_longpressed_holo.9.png | Bin
.../ninepatch/list_longpressed_holo.9.png.crunched | Bin
.../list_longpressed_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/list_longpressed_holo_dark.9.png | Bin
.../list_longpressed_holo_dark.9.png.crunched | Bin
.../list_longpressed_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/list_longpressed_holo_light.9.png | Bin
.../list_longpressed_holo_light.9.png.crunched | Bin
...list_longpressed_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/list_pressed_holo_dark.9.png | Bin
.../list_pressed_holo_dark.9.png.crunched | Bin
.../list_pressed_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/list_pressed_holo_light.9.png | Bin
.../list_pressed_holo_light.9.png.crunched | Bin
.../list_pressed_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/list_section_divider_holo_dark.9.png | Bin
.../list_section_divider_holo_dark.9.png.crunched | Bin
...t_section_divider_holo_dark.9.png.crunched.aapt | Bin
.../list_section_divider_holo_light.9.png | Bin
.../list_section_divider_holo_light.9.png.crunched | Bin
..._section_divider_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/list_section_header_holo_dark.9.png | Bin
.../list_section_header_holo_dark.9.png.crunched | Bin
...st_section_header_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/list_section_header_holo_light.9.png | Bin
.../list_section_header_holo_light.9.png.crunched | Bin
...t_section_header_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/list_selected_holo_dark.9.png | Bin
.../list_selected_holo_dark.9.png.crunched | Bin
.../list_selected_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/list_selected_holo_light.9.png | Bin
.../list_selected_holo_light.9.png.crunched | Bin
.../list_selected_holo_light.9.png.crunched.aapt | Bin
.../list_selector_activated_holo_dark.9.png | Bin
...ist_selector_activated_holo_dark.9.png.crunched | Bin
...elector_activated_holo_dark.9.png.crunched.aapt | Bin
.../list_selector_activated_holo_light.9.png | Bin
...st_selector_activated_holo_light.9.png.crunched | Bin
...lector_activated_holo_light.9.png.crunched.aapt | Bin
.../list_selector_background_default.9.png | Bin
...list_selector_background_default.9.png.crunched | Bin
...selector_background_default.9.png.crunched.aapt | Bin
.../list_selector_background_default_light.9.png | Bin
...elector_background_default_light.9.png.crunched | Bin
...or_background_default_light.9.png.crunched.aapt | Bin
.../list_selector_background_disabled.9.png | Bin
...ist_selector_background_disabled.9.png.crunched | Bin
...elector_background_disabled.9.png.crunched.aapt | Bin
.../list_selector_background_disabled_light.9.png | Bin
...lector_background_disabled_light.9.png.crunched | Bin
...r_background_disabled_light.9.png.crunched.aapt | Bin
.../ninepatch/list_selector_background_focus.9.png | Bin
.../list_selector_background_focus.9.png.crunched | Bin
...t_selector_background_focus.9.png.crunched.aapt | Bin
.../list_selector_background_focused.9.png | Bin
...list_selector_background_focused.9.png.crunched | Bin
...selector_background_focused.9.png.crunched.aapt | Bin
.../list_selector_background_focused_light.9.png | Bin
...elector_background_focused_light.9.png.crunched | Bin
...or_background_focused_light.9.png.crunched.aapt | Bin
...list_selector_background_focused_selected.9.png | Bin
...ctor_background_focused_selected.9.png.crunched | Bin
...background_focused_selected.9.png.crunched.aapt | Bin
.../list_selector_background_longpress.9.png | Bin
...st_selector_background_longpress.9.png.crunched | Bin
...lector_background_longpress.9.png.crunched.aapt | Bin
.../list_selector_background_longpress_light.9.png | Bin
...ector_background_longpress_light.9.png.crunched | Bin
..._background_longpress_light.9.png.crunched.aapt | Bin
.../list_selector_background_pressed.9.png | Bin
...list_selector_background_pressed.9.png.crunched | Bin
...selector_background_pressed.9.png.crunched.aapt | Bin
.../list_selector_background_pressed_light.9.png | Bin
...elector_background_pressed_light.9.png.crunched | Bin
...or_background_pressed_light.9.png.crunched.aapt | Bin
.../list_selector_background_selected.9.png | Bin
...ist_selector_background_selected.9.png.crunched | Bin
...elector_background_selected.9.png.crunched.aapt | Bin
.../list_selector_background_selected_light.9.png | Bin
...lector_background_selected_light.9.png.crunched | Bin
...r_background_selected_light.9.png.crunched.aapt | Bin
.../list_selector_disabled_holo_dark.9.png | Bin
...list_selector_disabled_holo_dark.9.png.crunched | Bin
...selector_disabled_holo_dark.9.png.crunched.aapt | Bin
.../list_selector_disabled_holo_light.9.png | Bin
...ist_selector_disabled_holo_light.9.png.crunched | Bin
...elector_disabled_holo_light.9.png.crunched.aapt | Bin
.../list_selector_focused_holo_dark.9.png | Bin
.../list_selector_focused_holo_dark.9.png.crunched | Bin
..._selector_focused_holo_dark.9.png.crunched.aapt | Bin
.../list_selector_focused_holo_light.9.png | Bin
...list_selector_focused_holo_light.9.png.crunched | Bin
...selector_focused_holo_light.9.png.crunched.aapt | Bin
.../list_selector_multiselect_holo_dark.9.png | Bin
...t_selector_multiselect_holo_dark.9.png.crunched | Bin
...ector_multiselect_holo_dark.9.png.crunched.aapt | Bin
.../list_selector_multiselect_holo_light.9.png | Bin
..._selector_multiselect_holo_light.9.png.crunched | Bin
...ctor_multiselect_holo_light.9.png.crunched.aapt | Bin
.../list_selector_pressed_holo_dark.9.png | Bin
.../list_selector_pressed_holo_dark.9.png.crunched | Bin
..._selector_pressed_holo_dark.9.png.crunched.aapt | Bin
.../list_selector_pressed_holo_light.9.png | Bin
...list_selector_pressed_holo_light.9.png.crunched | Bin
...selector_pressed_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/magnified_region_frame.9.png | Bin
.../magnified_region_frame.9.png.crunched | Bin
.../magnified_region_frame.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/menu_background.9.png | Bin
.../png/ninepatch/menu_background.9.png.crunched | Bin
.../ninepatch/menu_background.9.png.crunched.aapt | Bin
.../menu_background_fill_parent_width.9.png | Bin
...enu_background_fill_parent_width.9.png.crunched | Bin
...ackground_fill_parent_width.9.png.crunched.aapt | Bin
.../ninepatch/menu_dropdown_panel_holo_dark.9.png | Bin
.../menu_dropdown_panel_holo_dark.9.png.crunched | Bin
...nu_dropdown_panel_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/menu_dropdown_panel_holo_light.9.png | Bin
.../menu_dropdown_panel_holo_light.9.png.crunched | Bin
...u_dropdown_panel_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/menu_hardkey_panel_holo_dark.9.png | Bin
.../menu_hardkey_panel_holo_dark.9.png.crunched | Bin
...enu_hardkey_panel_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/menu_hardkey_panel_holo_light.9.png | Bin
.../menu_hardkey_panel_holo_light.9.png.crunched | Bin
...nu_hardkey_panel_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/menu_popup_panel_holo_dark.9.png | Bin
.../menu_popup_panel_holo_dark.9.png.crunched | Bin
.../menu_popup_panel_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/menu_popup_panel_holo_light.9.png | Bin
.../menu_popup_panel_holo_light.9.png.crunched | Bin
...menu_popup_panel_holo_light.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/menu_separator.9.png | Bin
.../png/ninepatch/menu_separator.9.png.crunched | Bin
.../ninepatch/menu_separator.9.png.crunched.aapt | Bin
.../png/ninepatch/menu_submenu_background.9.png | Bin
.../menu_submenu_background.9.png.crunched | Bin
.../menu_submenu_background.9.png.crunched.aapt | Bin
.../png/ninepatch/menuitem_background_focus.9.png | Bin
.../menuitem_background_focus.9.png.crunched | Bin
.../menuitem_background_focus.9.png.crunched.aapt | Bin
.../ninepatch/menuitem_background_pressed.9.png | Bin
.../menuitem_background_pressed.9.png.crunched | Bin
...menuitem_background_pressed.9.png.crunched.aapt | Bin
.../menuitem_background_solid_focused.9.png | Bin
...enuitem_background_solid_focused.9.png.crunched | Bin
...em_background_solid_focused.9.png.crunched.aapt | Bin
.../menuitem_background_solid_pressed.9.png | Bin
...enuitem_background_solid_pressed.9.png.crunched | Bin
...em_background_solid_pressed.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/minitab_lt_focus.9.png | Bin
.../png/ninepatch/minitab_lt_focus.9.png.crunched | Bin
.../ninepatch/minitab_lt_focus.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/minitab_lt_press.9.png | Bin
.../png/ninepatch/minitab_lt_press.9.png.crunched | Bin
.../ninepatch/minitab_lt_press.9.png.crunched.aapt | Bin
.../png/ninepatch/minitab_lt_selected.9.png | Bin
.../ninepatch/minitab_lt_selected.9.png.crunched | Bin
.../minitab_lt_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/minitab_lt_unselected.9.png | Bin
.../ninepatch/minitab_lt_unselected.9.png.crunched | Bin
.../minitab_lt_unselected.9.png.crunched.aapt | Bin
.../ninepatch/minitab_lt_unselected_press.9.png | Bin
.../minitab_lt_unselected_press.9.png.crunched | Bin
...minitab_lt_unselected_press.9.png.crunched.aapt | Bin
.../png/ninepatch/notification_bg_low_normal.9.png | Bin
.../notification_bg_low_normal.9.png.crunched | Bin
.../notification_bg_low_normal.9.png.crunched.aapt | Bin
.../ninepatch/notification_bg_low_pressed.9.png | Bin
.../notification_bg_low_pressed.9.png.crunched | Bin
...notification_bg_low_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/notification_bg_normal.9.png | Bin
.../notification_bg_normal.9.png.crunched | Bin
.../notification_bg_normal.9.png.crunched.aapt | Bin
.../ninepatch/notification_bg_normal_pressed.9.png | Bin
.../notification_bg_normal_pressed.9.png.crunched | Bin
...ification_bg_normal_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_down_disabled.9.png | Bin
.../numberpicker_down_disabled.9.png.crunched | Bin
.../numberpicker_down_disabled.9.png.crunched.aapt | Bin
.../numberpicker_down_disabled_focused.9.png | Bin
...mberpicker_down_disabled_focused.9.png.crunched | Bin
...icker_down_disabled_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_down_normal.9.png | Bin
.../numberpicker_down_normal.9.png.crunched | Bin
.../numberpicker_down_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_down_pressed.9.png | Bin
.../numberpicker_down_pressed.9.png.crunched | Bin
.../numberpicker_down_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_down_selected.9.png | Bin
.../numberpicker_down_selected.9.png.crunched | Bin
.../numberpicker_down_selected.9.png.crunched.aapt | Bin
.../ninepatch/numberpicker_input_disabled.9.png | Bin
.../numberpicker_input_disabled.9.png.crunched | Bin
...numberpicker_input_disabled.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_input_normal.9.png | Bin
.../numberpicker_input_normal.9.png.crunched | Bin
.../numberpicker_input_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_input_pressed.9.png | Bin
.../numberpicker_input_pressed.9.png.crunched | Bin
.../numberpicker_input_pressed.9.png.crunched.aapt | Bin
.../ninepatch/numberpicker_input_selected.9.png | Bin
.../numberpicker_input_selected.9.png.crunched | Bin
...numberpicker_input_selected.9.png.crunched.aapt | Bin
.../ninepatch/numberpicker_selection_divider.9.png | Bin
.../numberpicker_selection_divider.9.png.crunched | Bin
...berpicker_selection_divider.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_up_disabled.9.png | Bin
.../numberpicker_up_disabled.9.png.crunched | Bin
.../numberpicker_up_disabled.9.png.crunched.aapt | Bin
.../numberpicker_up_disabled_focused.9.png | Bin
...numberpicker_up_disabled_focused.9.png.crunched | Bin
...rpicker_up_disabled_focused.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_up_normal.9.png | Bin
.../numberpicker_up_normal.9.png.crunched | Bin
.../numberpicker_up_normal.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_up_pressed.9.png | Bin
.../numberpicker_up_pressed.9.png.crunched | Bin
.../numberpicker_up_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/numberpicker_up_selected.9.png | Bin
.../numberpicker_up_selected.9.png.crunched | Bin
.../numberpicker_up_selected.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/panel_background.9.png | Bin
.../png/ninepatch/panel_background.9.png.crunched | Bin
.../ninepatch/panel_background.9.png.crunched.aapt | Bin
.../png/ninepatch/panel_bg_holo_dark.9.png | Bin
.../ninepatch/panel_bg_holo_dark.9.png.crunched | Bin
.../panel_bg_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/panel_bg_holo_light.9.png | Bin
.../ninepatch/panel_bg_holo_light.9.png.crunched | Bin
.../panel_bg_holo_light.9.png.crunched.aapt | Bin
.../panel_picture_frame_bg_focus_blue.9.png | Bin
...anel_picture_frame_bg_focus_blue.9.png.crunched | Bin
...picture_frame_bg_focus_blue.9.png.crunched.aapt | Bin
.../ninepatch/panel_picture_frame_bg_normal.9.png | Bin
.../panel_picture_frame_bg_normal.9.png.crunched | Bin
...nel_picture_frame_bg_normal.9.png.crunched.aapt | Bin
.../panel_picture_frame_bg_pressed_blue.9.png | Bin
...el_picture_frame_bg_pressed_blue.9.png.crunched | Bin
...cture_frame_bg_pressed_blue.9.png.crunched.aapt | Bin
.../png/ninepatch/password_field_default.9.png | Bin
.../password_field_default.9.png.crunched | Bin
.../password_field_default.9.png.crunched.aapt | Bin
.../password_keyboard_background_holo.9.png | Bin
...assword_keyboard_background_holo.9.png.crunched | Bin
...rd_keyboard_background_holo.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/picture_frame.9.png | Bin
.../png/ninepatch/picture_frame.9.png.crunched | Bin
.../ninepatch/picture_frame.9.png.crunched.aapt | Bin
.../png/ninepatch/popup_bottom_bright.9.png | Bin
.../ninepatch/popup_bottom_bright.9.png.crunched | Bin
.../popup_bottom_bright.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/popup_bottom_dark.9.png | Bin
.../png/ninepatch/popup_bottom_dark.9.png.crunched | Bin
.../popup_bottom_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/popup_bottom_medium.9.png | Bin
.../ninepatch/popup_bottom_medium.9.png.crunched | Bin
.../popup_bottom_medium.9.png.crunched.aapt | Bin
.../png/ninepatch/popup_center_bright.9.png | Bin
.../ninepatch/popup_center_bright.9.png.crunched | Bin
.../popup_center_bright.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/popup_center_dark.9.png | Bin
.../png/ninepatch/popup_center_dark.9.png.crunched | Bin
.../popup_center_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/popup_center_medium.9.png | Bin
.../ninepatch/popup_center_medium.9.png.crunched | Bin
.../popup_center_medium.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/popup_full_bright.9.png | Bin
.../png/ninepatch/popup_full_bright.9.png.crunched | Bin
.../popup_full_bright.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/popup_full_dark.9.png | Bin
.../png/ninepatch/popup_full_dark.9.png.crunched | Bin
.../ninepatch/popup_full_dark.9.png.crunched.aapt | Bin
.../ninepatch/popup_inline_error_above_am.9.png | Bin
.../popup_inline_error_above_am.9.png.crunched | Bin
...popup_inline_error_above_am.9.png.crunched.aapt | Bin
.../popup_inline_error_above_holo_dark_am.9.png | Bin
..._inline_error_above_holo_dark_am.9.png.crunched | Bin
...ne_error_above_holo_dark_am.9.png.crunched.aapt | Bin
.../popup_inline_error_above_holo_light_am.9.png | Bin
...inline_error_above_holo_light_am.9.png.crunched | Bin
...e_error_above_holo_light_am.9.png.crunched.aapt | Bin
.../png/ninepatch/popup_inline_error_am.9.png | Bin
.../ninepatch/popup_inline_error_am.9.png.crunched | Bin
.../popup_inline_error_am.9.png.crunched.aapt | Bin
.../popup_inline_error_holo_dark_am.9.png | Bin
.../popup_inline_error_holo_dark_am.9.png.crunched | Bin
...p_inline_error_holo_dark_am.9.png.crunched.aapt | Bin
.../popup_inline_error_holo_light_am.9.png | Bin
...popup_inline_error_holo_light_am.9.png.crunched | Bin
..._inline_error_holo_light_am.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/popup_top_bright.9.png | Bin
.../png/ninepatch/popup_top_bright.9.png.crunched | Bin
.../ninepatch/popup_top_bright.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/popup_top_dark.9.png | Bin
.../png/ninepatch/popup_top_dark.9.png.crunched | Bin
.../ninepatch/popup_top_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/progress_bg_holo_dark.9.png | Bin
.../ninepatch/progress_bg_holo_dark.9.png.crunched | Bin
.../progress_bg_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/progress_bg_holo_light.9.png | Bin
.../progress_bg_holo_light.9.png.crunched | Bin
.../progress_bg_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/progress_primary_holo_dark.9.png | Bin
.../progress_primary_holo_dark.9.png.crunched | Bin
.../progress_primary_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/progress_primary_holo_light.9.png | Bin
.../progress_primary_holo_light.9.png.crunched | Bin
...progress_primary_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/progress_secondary_holo_dark.9.png | Bin
.../progress_secondary_holo_dark.9.png.crunched | Bin
...rogress_secondary_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/progress_secondary_holo_light.9.png | Bin
.../progress_secondary_holo_light.9.png.crunched | Bin
...ogress_secondary_holo_light.9.png.crunched.aapt | Bin
.../quickactions_arrowdown_left_holo_dark.9.png | Bin
...actions_arrowdown_left_holo_dark.9.png.crunched | Bin
...ns_arrowdown_left_holo_dark.9.png.crunched.aapt | Bin
.../quickactions_arrowdown_left_holo_light.9.png | Bin
...ctions_arrowdown_left_holo_light.9.png.crunched | Bin
...s_arrowdown_left_holo_light.9.png.crunched.aapt | Bin
.../quickactions_arrowdown_right_holo_dark.9.png | Bin
...ctions_arrowdown_right_holo_dark.9.png.crunched | Bin
...s_arrowdown_right_holo_dark.9.png.crunched.aapt | Bin
.../quickactions_arrowdown_right_holo_light.9.png | Bin
...tions_arrowdown_right_holo_light.9.png.crunched | Bin
..._arrowdown_right_holo_light.9.png.crunched.aapt | Bin
.../quickactions_arrowup_left_holo_dark.9.png | Bin
...ckactions_arrowup_left_holo_dark.9.png.crunched | Bin
...ions_arrowup_left_holo_dark.9.png.crunched.aapt | Bin
.../quickactions_arrowup_left_holo_light.9.png | Bin
...kactions_arrowup_left_holo_light.9.png.crunched | Bin
...ons_arrowup_left_holo_light.9.png.crunched.aapt | Bin
...quickactions_arrowup_left_right_holo_dark.9.png | Bin
...ons_arrowup_left_right_holo_dark.9.png.crunched | Bin
...rrowup_left_right_holo_dark.9.png.crunched.aapt | Bin
.../quickactions_arrowup_right_holo_light.9.png | Bin
...actions_arrowup_right_holo_light.9.png.crunched | Bin
...ns_arrowup_right_holo_light.9.png.crunched.aapt | Bin
...uickcontact_badge_overlay_focused_dark_am.9.png | Bin
...ct_badge_overlay_focused_dark_am.9.png.crunched | Bin
...dge_overlay_focused_dark_am.9.png.crunched.aapt | Bin
...ickcontact_badge_overlay_focused_light_am.9.png | Bin
...t_badge_overlay_focused_light_am.9.png.crunched | Bin
...ge_overlay_focused_light_am.9.png.crunched.aapt | Bin
...quickcontact_badge_overlay_normal_dark_am.9.png | Bin
...act_badge_overlay_normal_dark_am.9.png.crunched | Bin
...adge_overlay_normal_dark_am.9.png.crunched.aapt | Bin
...uickcontact_badge_overlay_normal_light_am.9.png | Bin
...ct_badge_overlay_normal_light_am.9.png.crunched | Bin
...dge_overlay_normal_light_am.9.png.crunched.aapt | Bin
...uickcontact_badge_overlay_pressed_dark_am.9.png | Bin
...ct_badge_overlay_pressed_dark_am.9.png.crunched | Bin
...dge_overlay_pressed_dark_am.9.png.crunched.aapt | Bin
...ickcontact_badge_overlay_pressed_light_am.9.png | Bin
...t_badge_overlay_pressed_light_am.9.png.crunched | Bin
...ge_overlay_pressed_light_am.9.png.crunched.aapt | Bin
.../png/ninepatch/recent_dialog_background.9.png | Bin
.../recent_dialog_background.9.png.crunched | Bin
.../recent_dialog_background.9.png.crunched.aapt | Bin
.../scrollbar_handle_accelerated_anim2.9.png | Bin
...rollbar_handle_accelerated_anim2.9.png.crunched | Bin
...ar_handle_accelerated_anim2.9.png.crunched.aapt | Bin
.../png/ninepatch/scrollbar_handle_holo_dark.9.png | Bin
.../scrollbar_handle_holo_dark.9.png.crunched | Bin
.../scrollbar_handle_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/scrollbar_handle_holo_light.9.png | Bin
.../scrollbar_handle_holo_light.9.png.crunched | Bin
...scrollbar_handle_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/scrollbar_handle_horizontal.9.png | Bin
.../scrollbar_handle_horizontal.9.png.crunched | Bin
...scrollbar_handle_horizontal.9.png.crunched.aapt | Bin
.../png/ninepatch/scrollbar_handle_vertical.9.png | Bin
.../scrollbar_handle_vertical.9.png.crunched | Bin
.../scrollbar_handle_vertical.9.png.crunched.aapt | Bin
.../png/ninepatch/scrubber_primary_holo.9.png | Bin
.../ninepatch/scrubber_primary_holo.9.png.crunched | Bin
.../scrubber_primary_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/scrubber_secondary_holo.9.png | Bin
.../scrubber_secondary_holo.9.png.crunched | Bin
.../scrubber_secondary_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/scrubber_track_holo_dark.9.png | Bin
.../scrubber_track_holo_dark.9.png.crunched | Bin
.../scrubber_track_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/scrubber_track_holo_light.9.png | Bin
.../scrubber_track_holo_light.9.png.crunched | Bin
.../scrubber_track_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/search_dropdown_background.9.png | Bin
.../search_dropdown_background.9.png.crunched | Bin
.../search_dropdown_background.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/search_plate.9.png | Bin
.../png/ninepatch/search_plate.9.png.crunched | Bin
.../png/ninepatch/search_plate.9.png.crunched.aapt | Bin
.../png/ninepatch/search_plate_global.9.png | Bin
.../ninepatch/search_plate_global.9.png.crunched | Bin
.../search_plate_global.9.png.crunched.aapt | Bin
.../png/ninepatch/settings_header_raw.9.png | Bin
.../ninepatch/settings_header_raw.9.png.crunched | Bin
.../settings_header_raw.9.png.crunched.aapt | Bin
.../spinner_ab_default_holo_dark_am.9.png | Bin
.../spinner_ab_default_holo_dark_am.9.png.crunched | Bin
...ner_ab_default_holo_dark_am.9.png.crunched.aapt | Bin
.../spinner_ab_default_holo_light_am.9.png | Bin
...spinner_ab_default_holo_light_am.9.png.crunched | Bin
...er_ab_default_holo_light_am.9.png.crunched.aapt | Bin
.../spinner_ab_disabled_holo_dark_am.9.png | Bin
...spinner_ab_disabled_holo_dark_am.9.png.crunched | Bin
...er_ab_disabled_holo_dark_am.9.png.crunched.aapt | Bin
.../spinner_ab_disabled_holo_light_am.9.png | Bin
...pinner_ab_disabled_holo_light_am.9.png.crunched | Bin
...r_ab_disabled_holo_light_am.9.png.crunched.aapt | Bin
.../spinner_ab_focused_holo_dark_am.9.png | Bin
.../spinner_ab_focused_holo_dark_am.9.png.crunched | Bin
...ner_ab_focused_holo_dark_am.9.png.crunched.aapt | Bin
.../spinner_ab_focused_holo_light_am.9.png | Bin
...spinner_ab_focused_holo_light_am.9.png.crunched | Bin
...er_ab_focused_holo_light_am.9.png.crunched.aapt | Bin
.../spinner_ab_pressed_holo_dark_am.9.png | Bin
.../spinner_ab_pressed_holo_dark_am.9.png.crunched | Bin
...ner_ab_pressed_holo_dark_am.9.png.crunched.aapt | Bin
.../spinner_ab_pressed_holo_light_am.9.png | Bin
...spinner_ab_pressed_holo_light_am.9.png.crunched | Bin
...er_ab_pressed_holo_light_am.9.png.crunched.aapt | Bin
.../ninepatch/spinner_default_holo_dark_am.9.png | Bin
.../spinner_default_holo_dark_am.9.png.crunched | Bin
...pinner_default_holo_dark_am.9.png.crunched.aapt | Bin
.../ninepatch/spinner_default_holo_light_am.9.png | Bin
.../spinner_default_holo_light_am.9.png.crunched | Bin
...inner_default_holo_light_am.9.png.crunched.aapt | Bin
.../ninepatch/spinner_disabled_holo_dark_am.9.png | Bin
.../spinner_disabled_holo_dark_am.9.png.crunched | Bin
...inner_disabled_holo_dark_am.9.png.crunched.aapt | Bin
.../ninepatch/spinner_disabled_holo_light_am.9.png | Bin
.../spinner_disabled_holo_light_am.9.png.crunched | Bin
...nner_disabled_holo_light_am.9.png.crunched.aapt | Bin
.../spinner_dropdown_background_down.9.png | Bin
...spinner_dropdown_background_down.9.png.crunched | Bin
...er_dropdown_background_down.9.png.crunched.aapt | Bin
.../ninepatch/spinner_dropdown_background_up.9.png | Bin
.../spinner_dropdown_background_up.9.png.crunched | Bin
...nner_dropdown_background_up.9.png.crunched.aapt | Bin
.../ninepatch/spinner_focused_holo_dark_am.9.png | Bin
.../spinner_focused_holo_dark_am.9.png.crunched | Bin
...pinner_focused_holo_dark_am.9.png.crunched.aapt | Bin
.../ninepatch/spinner_focused_holo_light_am.9.png | Bin
.../spinner_focused_holo_light_am.9.png.crunched | Bin
...inner_focused_holo_light_am.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/spinner_normal.9.png | Bin
.../png/ninepatch/spinner_normal.9.png.crunched | Bin
.../ninepatch/spinner_normal.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/spinner_press.9.png | Bin
.../png/ninepatch/spinner_press.9.png.crunched | Bin
.../ninepatch/spinner_press.9.png.crunched.aapt | Bin
.../ninepatch/spinner_pressed_holo_dark_am.9.png | Bin
.../spinner_pressed_holo_dark_am.9.png.crunched | Bin
...pinner_pressed_holo_dark_am.9.png.crunched.aapt | Bin
.../ninepatch/spinner_pressed_holo_light_am.9.png | Bin
.../spinner_pressed_holo_light_am.9.png.crunched | Bin
...inner_pressed_holo_light_am.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/spinner_select.9.png | Bin
.../png/ninepatch/spinner_select.9.png.crunched | Bin
.../ninepatch/spinner_select.9.png.crunched.aapt | Bin
.../ninepatch/status_bar_header_background.9.png | Bin
.../status_bar_header_background.9.png.crunched | Bin
...tatus_bar_header_background.9.png.crunched.aapt | Bin
.../status_bar_item_app_background_normal.9.png | Bin
...s_bar_item_app_background_normal.9.png.crunched | Bin
..._item_app_background_normal.9.png.crunched.aapt | Bin
.../status_bar_item_background_focus.9.png | Bin
...status_bar_item_background_focus.9.png.crunched | Bin
...s_bar_item_background_focus.9.png.crunched.aapt | Bin
.../status_bar_item_background_normal.9.png | Bin
...tatus_bar_item_background_normal.9.png.crunched | Bin
..._bar_item_background_normal.9.png.crunched.aapt | Bin
.../status_bar_item_background_pressed.9.png | Bin
...atus_bar_item_background_pressed.9.png.crunched | Bin
...bar_item_background_pressed.9.png.crunched.aapt | Bin
.../png/ninepatch/statusbar_background.9.png | Bin
.../ninepatch/statusbar_background.9.png.crunched | Bin
.../statusbar_background.9.png.crunched.aapt | Bin
.../ninepatch/switch_bg_disabled_holo_dark.9.png | Bin
.../switch_bg_disabled_holo_dark.9.png.crunched | Bin
...witch_bg_disabled_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/switch_bg_disabled_holo_light.9.png | Bin
.../switch_bg_disabled_holo_light.9.png.crunched | Bin
...itch_bg_disabled_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/switch_bg_focused_holo_dark.9.png | Bin
.../switch_bg_focused_holo_dark.9.png.crunched | Bin
...switch_bg_focused_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/switch_bg_focused_holo_light.9.png | Bin
.../switch_bg_focused_holo_light.9.png.crunched | Bin
...witch_bg_focused_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/switch_bg_holo_dark.9.png | Bin
.../ninepatch/switch_bg_holo_dark.9.png.crunched | Bin
.../switch_bg_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/switch_bg_holo_light.9.png | Bin
.../ninepatch/switch_bg_holo_light.9.png.crunched | Bin
.../switch_bg_holo_light.9.png.crunched.aapt | Bin
.../switch_thumb_activated_holo_dark.9.png | Bin
...switch_thumb_activated_holo_dark.9.png.crunched | Bin
...h_thumb_activated_holo_dark.9.png.crunched.aapt | Bin
.../switch_thumb_activated_holo_light.9.png | Bin
...witch_thumb_activated_holo_light.9.png.crunched | Bin
..._thumb_activated_holo_light.9.png.crunched.aapt | Bin
.../switch_thumb_disabled_holo_dark.9.png | Bin
.../switch_thumb_disabled_holo_dark.9.png.crunched | Bin
...ch_thumb_disabled_holo_dark.9.png.crunched.aapt | Bin
.../switch_thumb_disabled_holo_light.9.png | Bin
...switch_thumb_disabled_holo_light.9.png.crunched | Bin
...h_thumb_disabled_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/switch_thumb_holo_dark.9.png | Bin
.../switch_thumb_holo_dark.9.png.crunched | Bin
.../switch_thumb_holo_dark.9.png.crunched.aapt | Bin
.../png/ninepatch/switch_thumb_holo_light.9.png | Bin
.../switch_thumb_holo_light.9.png.crunched | Bin
.../switch_thumb_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/switch_thumb_pressed_holo_dark.9.png | Bin
.../switch_thumb_pressed_holo_dark.9.png.crunched | Bin
...tch_thumb_pressed_holo_dark.9.png.crunched.aapt | Bin
.../switch_thumb_pressed_holo_light.9.png | Bin
.../switch_thumb_pressed_holo_light.9.png.crunched | Bin
...ch_thumb_pressed_holo_light.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_bottom_holo.9.png | Bin
.../png/ninepatch/tab_bottom_holo.9.png.crunched | Bin
.../ninepatch/tab_bottom_holo.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_focus.9.png | Bin
.../png/ninepatch/tab_focus.9.png.crunched | Bin
.../png/ninepatch/tab_focus.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_focus_bar_left.9.png | Bin
.../ninepatch/tab_focus_bar_left.9.png.crunched | Bin
.../tab_focus_bar_left.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_focus_bar_right.9.png | Bin
.../ninepatch/tab_focus_bar_right.9.png.crunched | Bin
.../tab_focus_bar_right.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_press.9.png | Bin
.../png/ninepatch/tab_press.9.png.crunched | Bin
.../png/ninepatch/tab_press.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_press_bar_left.9.png | Bin
.../ninepatch/tab_press_bar_left.9.png.crunched | Bin
.../tab_press_bar_left.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_press_bar_right.9.png | Bin
.../ninepatch/tab_press_bar_right.9.png.crunched | Bin
.../tab_press_bar_right.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_pressed_holo.9.png | Bin
.../png/ninepatch/tab_pressed_holo.9.png.crunched | Bin
.../ninepatch/tab_pressed_holo.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_selected.9.png | Bin
.../png/ninepatch/tab_selected.9.png.crunched | Bin
.../png/ninepatch/tab_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_selected_bar_left.9.png | Bin
.../ninepatch/tab_selected_bar_left.9.png.crunched | Bin
.../tab_selected_bar_left.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_selected_bar_left_v4.9.png | Bin
.../tab_selected_bar_left_v4.9.png.crunched | Bin
.../tab_selected_bar_left_v4.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_selected_bar_right.9.png | Bin
.../tab_selected_bar_right.9.png.crunched | Bin
.../tab_selected_bar_right.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_selected_bar_right_v4.9.png | Bin
.../tab_selected_bar_right_v4.9.png.crunched | Bin
.../tab_selected_bar_right_v4.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_selected_focused_holo.9.png | Bin
.../tab_selected_focused_holo.9.png.crunched | Bin
.../tab_selected_focused_holo.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_selected_holo.9.png | Bin
.../png/ninepatch/tab_selected_holo.9.png.crunched | Bin
.../tab_selected_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_selected_pressed_holo.9.png | Bin
.../tab_selected_pressed_holo.9.png.crunched | Bin
.../tab_selected_pressed_holo.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_selected_v4.9.png | Bin
.../png/ninepatch/tab_selected_v4.9.png.crunched | Bin
.../ninepatch/tab_selected_v4.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_unselected.9.png | Bin
.../png/ninepatch/tab_unselected.9.png.crunched | Bin
.../ninepatch/tab_unselected.9.png.crunched.aapt | Bin
.../ninepatch/tab_unselected_focused_holo.9.png | Bin
.../tab_unselected_focused_holo.9.png.crunched | Bin
...tab_unselected_focused_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/tab_unselected_holo.9.png | Bin
.../ninepatch/tab_unselected_holo.9.png.crunched | Bin
.../tab_unselected_holo.9.png.crunched.aapt | Bin
.../ninepatch/tab_unselected_pressed_holo.9.png | Bin
.../tab_unselected_pressed_holo.9.png.crunched | Bin
...tab_unselected_pressed_holo.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/tab_unselected_v4.9.png | Bin
.../png/ninepatch/tab_unselected_v4.9.png.crunched | Bin
.../tab_unselected_v4.9.png.crunched.aapt | Bin
.../png/ninepatch/text_edit_paste_window.9.png | Bin
.../text_edit_paste_window.9.png.crunched | Bin
.../text_edit_paste_window.9.png.crunched.aapt | Bin
.../ninepatch/text_edit_side_paste_window.9.png | Bin
.../text_edit_side_paste_window.9.png.crunched | Bin
...text_edit_side_paste_window.9.png.crunched.aapt | Bin
.../ninepatch/text_edit_suggestions_window.9.png | Bin
.../text_edit_suggestions_window.9.png.crunched | Bin
...ext_edit_suggestions_window.9.png.crunched.aapt | Bin
.../ninepatch/textfield_activated_holo_dark.9.png | Bin
.../textfield_activated_holo_dark.9.png.crunched | Bin
...xtfield_activated_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/textfield_activated_holo_light.9.png | Bin
.../textfield_activated_holo_light.9.png.crunched | Bin
...tfield_activated_holo_light.9.png.crunched.aapt | Bin
.../textfield_bg_activated_holo_dark.9.png | Bin
...textfield_bg_activated_holo_dark.9.png.crunched | Bin
...ield_bg_activated_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/textfield_bg_default_holo_dark.9.png | Bin
.../textfield_bg_default_holo_dark.9.png.crunched | Bin
...tfield_bg_default_holo_dark.9.png.crunched.aapt | Bin
.../textfield_bg_disabled_focused_holo_dark.9.png | Bin
...ld_bg_disabled_focused_holo_dark.9.png.crunched | Bin
..._disabled_focused_holo_dark.9.png.crunched.aapt | Bin
.../textfield_bg_disabled_holo_dark.9.png | Bin
.../textfield_bg_disabled_holo_dark.9.png.crunched | Bin
...field_bg_disabled_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/textfield_bg_focused_holo_dark.9.png | Bin
.../textfield_bg_focused_holo_dark.9.png.crunched | Bin
...tfield_bg_focused_holo_dark.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/textfield_default.9.png | Bin
.../png/ninepatch/textfield_default.9.png.crunched | Bin
.../textfield_default.9.png.crunched.aapt | Bin
.../ninepatch/textfield_default_holo_dark.9.png | Bin
.../textfield_default_holo_dark.9.png.crunched | Bin
...textfield_default_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/textfield_default_holo_light.9.png | Bin
.../textfield_default_holo_light.9.png.crunched | Bin
...extfield_default_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/textfield_disabled.9.png | Bin
.../ninepatch/textfield_disabled.9.png.crunched | Bin
.../textfield_disabled.9.png.crunched.aapt | Bin
.../textfield_disabled_focused_holo_dark.9.png | Bin
...field_disabled_focused_holo_dark.9.png.crunched | Bin
..._disabled_focused_holo_dark.9.png.crunched.aapt | Bin
.../textfield_disabled_focused_holo_light.9.png | Bin
...ield_disabled_focused_holo_light.9.png.crunched | Bin
...disabled_focused_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/textfield_disabled_holo_dark.9.png | Bin
.../textfield_disabled_holo_dark.9.png.crunched | Bin
...extfield_disabled_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/textfield_disabled_holo_light.9.png | Bin
.../textfield_disabled_holo_light.9.png.crunched | Bin
...xtfield_disabled_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/textfield_disabled_selected.9.png | Bin
.../textfield_disabled_selected.9.png.crunched | Bin
...textfield_disabled_selected.9.png.crunched.aapt | Bin
.../ninepatch/textfield_focused_holo_dark.9.png | Bin
.../textfield_focused_holo_dark.9.png.crunched | Bin
...textfield_focused_holo_dark.9.png.crunched.aapt | Bin
.../ninepatch/textfield_focused_holo_light.9.png | Bin
.../textfield_focused_holo_light.9.png.crunched | Bin
...extfield_focused_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/textfield_longpress_holo.9.png | Bin
.../textfield_longpress_holo.9.png.crunched | Bin
.../textfield_longpress_holo.9.png.crunched.aapt | Bin
.../textfield_multiline_activated_holo_dark.9.png | Bin
...ld_multiline_activated_holo_dark.9.png.crunched | Bin
...ltiline_activated_holo_dark.9.png.crunched.aapt | Bin
.../textfield_multiline_activated_holo_light.9.png | Bin
...d_multiline_activated_holo_light.9.png.crunched | Bin
...tiline_activated_holo_light.9.png.crunched.aapt | Bin
.../textfield_multiline_default_holo_dark.9.png | Bin
...ield_multiline_default_holo_dark.9.png.crunched | Bin
...multiline_default_holo_dark.9.png.crunched.aapt | Bin
.../textfield_multiline_default_holo_light.9.png | Bin
...eld_multiline_default_holo_light.9.png.crunched | Bin
...ultiline_default_holo_light.9.png.crunched.aapt | Bin
...ield_multiline_disabled_focused_holo_dark.9.png | Bin
...iline_disabled_focused_holo_dark.9.png.crunched | Bin
..._disabled_focused_holo_dark.9.png.crunched.aapt | Bin
...eld_multiline_disabled_focused_holo_light.9.png | Bin
...line_disabled_focused_holo_light.9.png.crunched | Bin
...disabled_focused_holo_light.9.png.crunched.aapt | Bin
.../textfield_multiline_disabled_holo_dark.9.png | Bin
...eld_multiline_disabled_holo_dark.9.png.crunched | Bin
...ultiline_disabled_holo_dark.9.png.crunched.aapt | Bin
.../textfield_multiline_disabled_holo_light.9.png | Bin
...ld_multiline_disabled_holo_light.9.png.crunched | Bin
...ltiline_disabled_holo_light.9.png.crunched.aapt | Bin
.../textfield_multiline_focused_holo_dark.9.png | Bin
...ield_multiline_focused_holo_dark.9.png.crunched | Bin
...multiline_focused_holo_dark.9.png.crunched.aapt | Bin
.../textfield_multiline_focused_holo_light.9.png | Bin
...eld_multiline_focused_holo_light.9.png.crunched | Bin
...ultiline_focused_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/textfield_pressed_holo.9.png | Bin
.../textfield_pressed_holo.9.png.crunched | Bin
.../textfield_pressed_holo.9.png.crunched.aapt | Bin
.../png/ninepatch/textfield_search_default.9.png | Bin
.../textfield_search_default.9.png.crunched | Bin
.../textfield_search_default.9.png.crunched.aapt | Bin
.../textfield_search_default_holo_dark.9.png | Bin
...xtfield_search_default_holo_dark.9.png.crunched | Bin
...ld_search_default_holo_dark.9.png.crunched.aapt | Bin
.../textfield_search_default_holo_light.9.png | Bin
...tfield_search_default_holo_light.9.png.crunched | Bin
...d_search_default_holo_light.9.png.crunched.aapt | Bin
.../ninepatch/textfield_search_empty_default.9.png | Bin
.../textfield_search_empty_default.9.png.crunched | Bin
...tfield_search_empty_default.9.png.crunched.aapt | Bin
.../ninepatch/textfield_search_empty_pressed.9.png | Bin
.../textfield_search_empty_pressed.9.png.crunched | Bin
...tfield_search_empty_pressed.9.png.crunched.aapt | Bin
.../textfield_search_empty_selected.9.png | Bin
.../textfield_search_empty_selected.9.png.crunched | Bin
...field_search_empty_selected.9.png.crunched.aapt | Bin
.../png/ninepatch/textfield_search_pressed.9.png | Bin
.../textfield_search_pressed.9.png.crunched | Bin
.../textfield_search_pressed.9.png.crunched.aapt | Bin
.../textfield_search_right_default_holo_dark.9.png | Bin
...d_search_right_default_holo_dark.9.png.crunched | Bin
...rch_right_default_holo_dark.9.png.crunched.aapt | Bin
...textfield_search_right_default_holo_light.9.png | Bin
..._search_right_default_holo_light.9.png.crunched | Bin
...ch_right_default_holo_light.9.png.crunched.aapt | Bin
...textfield_search_right_selected_holo_dark.9.png | Bin
..._search_right_selected_holo_dark.9.png.crunched | Bin
...ch_right_selected_holo_dark.9.png.crunched.aapt | Bin
...extfield_search_right_selected_holo_light.9.png | Bin
...search_right_selected_holo_light.9.png.crunched | Bin
...h_right_selected_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/textfield_search_selected.9.png | Bin
.../textfield_search_selected.9.png.crunched | Bin
.../textfield_search_selected.9.png.crunched.aapt | Bin
.../textfield_search_selected_holo_dark.9.png | Bin
...tfield_search_selected_holo_dark.9.png.crunched | Bin
...d_search_selected_holo_dark.9.png.crunched.aapt | Bin
.../textfield_search_selected_holo_light.9.png | Bin
...field_search_selected_holo_light.9.png.crunched | Bin
..._search_selected_holo_light.9.png.crunched.aapt | Bin
.../png/ninepatch/textfield_selected.9.png | Bin
.../ninepatch/textfield_selected.9.png.crunched | Bin
.../textfield_selected.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/title_bar_medium.9.png | Bin
.../png/ninepatch/title_bar_medium.9.png.crunched | Bin
.../ninepatch/title_bar_medium.9.png.crunched.aapt | Bin
.../png/ninepatch/title_bar_portrait.9.png | Bin
.../ninepatch/title_bar_portrait.9.png.crunched | Bin
.../title_bar_portrait.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/title_bar_shadow.9.png | Bin
.../png/ninepatch/title_bar_shadow.9.png.crunched | Bin
.../ninepatch/title_bar_shadow.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/title_bar_tall.9.png | Bin
.../png/ninepatch/title_bar_tall.9.png.crunched | Bin
.../ninepatch/title_bar_tall.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/toast_frame.9.png | Bin
.../png/ninepatch/toast_frame.9.png.crunched | Bin
.../png/ninepatch/toast_frame.9.png.crunched.aapt | Bin
.../png/ninepatch/transportcontrol_bg.9.png | Bin
.../ninepatch/transportcontrol_bg.9.png.crunched | Bin
.../transportcontrol_bg.9.png.crunched.aapt | Bin
.../testData/png/ninepatch/zoom_plate.9.png | Bin
.../png/ninepatch/zoom_plate.9.png.crunched | Bin
.../png/ninepatch/zoom_plate.9.png.crunched.aapt | Bin
build-system/changelog.txt | 681 ++++
{base/build-system => build-system}/docs/README | 0
build-system/docs/build.gradle | 330 ++
.../docs/src/fromGradle/docs/css/base.css | 0
.../docs/src/fromGradle/docs/css/docs.css | 0
.../docs/src/fromGradle/docs/css/dsl.css | 0
.../fromGradle/docs/css/images/gradle-logo_25o.gif | Bin
.../docs/css/images/studio_logo_background.png | Bin
.../docs/src/fromGradle/docs/css/javadoc.css | 0
.../docs/src/fromGradle/docs/css/print.css | 0
.../docs/src/fromGradle/docs/css/release-notes.css | 0
.../docs/src/fromGradle/docs/css/userguide.css | 0
.../dsl/com.android.build.gradle.AppExtension.xml | 0
.../dsl/com.android.build.gradle.BaseExtension.xml | 61 +
.../com.android.build.gradle.LibraryExtension.xml | 0
.../dsl/com.android.build.gradle.TestExtension.xml | 0
.../com.android.build.gradle.TestedExtension.xml | 0
....build.gradle.api.AndroidSourceDirectorySet.xml | 0
....android.build.gradle.api.AndroidSourceFile.xml | 0
...m.android.build.gradle.api.AndroidSourceSet.xml | 33 +
.../com.android.build.gradle.api.VariantFilter.xml | 22 +
...ndroid.build.gradle.internal.CompileOptions.xml | 21 +
...uild.gradle.internal.coverage.JacocoOptions.xml | 0
...droid.build.gradle.internal.dsl.AaptOptions.xml | 26 +
...d.build.gradle.internal.dsl.AbiSplitOptions.xml | 0
...ndroid.build.gradle.internal.dsl.AdbOptions.xml | 0
...android.build.gradle.internal.dsl.BuildType.xml | 44 +
...uild.gradle.internal.dsl.DataBindingOptions.xml | 37 +
...ild.gradle.internal.dsl.DensitySplitOptions.xml | 20 +
...ndroid.build.gradle.internal.dsl.DexOptions.xml | 23 +
...ld.gradle.internal.dsl.LanguageSplitOptions.xml | 0
...droid.build.gradle.internal.dsl.LintOptions.xml | 46 +
....build.gradle.internal.dsl.PackagingOptions.xml | 24 +
...oid.build.gradle.internal.dsl.ProductFlavor.xml | 52 +
...oid.build.gradle.internal.dsl.SigningConfig.xml | 0
...roid.build.gradle.internal.dsl.SplitOptions.xml | 0
...om.android.build.gradle.internal.dsl.Splits.xml | 27 +
...le.internal.dsl.TestOptions.UnitTestOptions.xml | 0
...droid.build.gradle.internal.dsl.TestOptions.xml | 0
.../com.android.builder.core.DefaultBuildType.xml | 0
...m.android.builder.core.DefaultProductFlavor.xml | 0
...com.android.builder.internal.BaseConfigImpl.xml | 0
...ndroid.builder.signing.DefaultSigningConfig.xml | 0
build-system/docs/src/fromGradle/docs/dsl/dsl.xml | 57 +
.../docs/src/fromGradle/docs/dsl/plugins.xml | 0
.../src/fromGradle/docs/stylesheets/dslHtml.xsl | 0
.../fromGradle/docs/stylesheets/standaloneHtml.xsl | 0
.../fromGradle/docs/stylesheets/userGuideHtml.xsl | 0
.../docs/stylesheets/userGuideHtmlCommon.xsl | 0
.../fromGradle/docs/stylesheets/userGuidePdf.xsl | 0
.../docs/stylesheets/userGuideSingleHtml.xsl | 0
.../google-services/build.gradle | 0
.../google-services/google-services.iml | 0
.../gms/googleservices/GoogleServicesPlugin.groovy | 176 +
.../gms/googleservices/GoogleServicesTask.java | 397 +++
.../com.google.gms.google-services.properties | 0
build-system/gradle-api/build.gradle | 49 +
build-system/gradle-api/gradle-api.iml | 33 +
.../com/android/build/api/transform/Context.java | 67 +
.../build/api/transform/DirectoryInput.java | 64 +
.../com/android/build/api/transform/Format.java | 41 +
.../com/android/build/api/transform/JarInput.java | 51 +
.../build/api/transform/QualifiedContent.java | 155 +
.../android/build/api/transform/SecondaryFile.java | 57 +
.../build/api/transform/SecondaryInput.java | 35 +
.../com/android/build/api/transform/Status.java | 45 +
.../com/android/build/api/transform/Transform.java | 313 ++
.../build/api/transform/TransformException.java | 37 +
.../build/api/transform/TransformInput.java | 46 +
.../build/api/transform/TransformInvocation.java | 69 +
.../api/transform/TransformOutputProvider.java | 69 +
.../gradle-core/MODULE_LICENSE_APACHE2 | 0
.../gradle-core/NOTICE | 0
.../gradle-core/README | 0
build-system/gradle-core/build.gradle | 202 ++
build-system/gradle-core/gradle-core.iml | 1009 ++++++
.../internal/test/report/AllTestResults.java | 0
.../internal/test/report/ClassPageRenderer.java | 0
.../internal/test/report/ClassTestResults.java | 0
.../internal/test/report/CodePanelRenderer.java | 0
.../internal/test/report/CompositeTestResults.java | 0
.../internal/test/report/DeviceTestResults.java | 0
.../internal/test/report/ErroringAction.java | 0
.../internal/test/report/HtmlReportRenderer.java | 0
.../internal/test/report/OverviewPageRenderer.java | 0
.../internal/test/report/PackagePageRenderer.java | 0
.../internal/test/report/PackageTestResults.java | 0
.../gradle/internal/test/report/PageRenderer.java | 0
.../internal/test/report/SimpleHtmlWriter.java | 0
.../internal/test/report/SimpleMarkupWriter.java | 0
.../internal/test/report/TabbedPageRenderer.java | 0
.../gradle/internal/test/report/TabsRenderer.java | 0
.../gradle/internal/test/report/TestReport.java | 0
.../gradle/internal/test/report/TestResult.java | 0
.../internal/test/report/TestResultModel.java | 0
.../internal/test/report/TextReportRenderer.java | 0
.../internal/test/report/VariantTestResults.java | 0
.../org/gradle/api/tasks/ParallelizableTask.java | 0
.../gradle/internal/test/report/base-style.css | 0
.../build/gradle/internal/test/report/report.js | 0
.../build/gradle/internal/test/report/style.css | 0
build-system/gradle-core/src/main/antlr/Proguard.g | 285 ++
.../com/android/build/gradle/AndroidConfig.java | 147 +
.../android/build/gradle/AndroidGradleOptions.java | 269 ++
.../build/gradle/OptionalCompilationStep.java | 42 +
.../com/android/build/gradle/ReportingPlugin.java | 126 +
.../android/build/gradle/TestAndroidConfig.java | 0
.../android/build/gradle/TestedAndroidConfig.java | 0
.../gradle/api/AndroidSourceDirectorySet.java | 106 +
.../build/gradle/api/AndroidSourceFile.java | 0
.../android/build/gradle/api/AndroidSourceSet.java | 0
.../android/build/gradle/api/ApkOutputFile.java | 0
.../com/android/build/gradle/api/ApkVariant.java | 97 +
.../android/build/gradle/api/ApkVariantOutput.java | 0
.../build/gradle/api/ApplicationVariant.java | 0
.../com/android/build/gradle/api/BaseVariant.java | 316 ++
.../build/gradle/api/BaseVariantOutput.java | 0
.../android/build/gradle/api/LibraryVariant.java | 0
.../build/gradle/api/LibraryVariantOutput.java | 0
.../com/android/build/gradle/api/TestVariant.java | 0
.../android/build/gradle/api/UnitTestVariant.java | 0
.../android/build/gradle/api/VariantFilter.java | 0
.../internal/AndroidAsciiReportRenderer.java | 0
.../gradle/internal/ApplicationTaskManager.java | 345 ++
.../build/gradle/internal/BadPluginException.java | 0
.../build/gradle/internal/BuildTypeData.java | 0
.../build/gradle/internal/CompileOptions.java | 148 +
.../gradle/internal/ConfigurationDependencies.java | 72 +
.../gradle/internal/ConfigurationProvider.java | 0
.../gradle/internal/ConfigurationProviderImpl.java | 0
.../build/gradle/internal/DependencyManager.java | 1152 +++++++
.../internal/ExecutionConfigurationUtil.java | 35 +
.../build/gradle/internal/ExtraModelInfo.java | 366 ++
.../build/gradle/internal/LibraryCache.java | 146 +
.../build/gradle/internal/LibraryTaskManager.java | 608 ++++
.../build/gradle/internal/LintGradleClient.java | 225 ++
.../build/gradle/internal/LintGradleProject.java | 726 ++++
.../build/gradle/internal/LoggerWrapper.java | 120 +
.../android/build/gradle/internal/LoggingUtil.java | 0
.../android/build/gradle/internal/NdkHandler.java | 468 +++
.../build/gradle/internal/ProductFlavorCombo.java | 184 ++
.../build/gradle/internal/ProductFlavorData.java | 0
.../android/build/gradle/internal/SdkHandler.java | 265 ++
.../internal/SourceSetSourceProviderWrapper.java | 0
.../gradle/internal/TaskContainerAdaptor.java | 0
.../android/build/gradle/internal/TaskFactory.java | 65 +
.../android/build/gradle/internal/TaskManager.java | 3128 ++++++++++++++++++
.../internal/TestApplicationTaskManager.java | 144 +
.../gradle/internal/VariantDimensionData.java | 0
.../build/gradle/internal/VariantManager.java | 846 +++++
.../build/gradle/internal/VariantModel.java | 0
.../build/gradle/internal/annotations/ApkFile.java | 0
.../build/gradle/internal/api/ApkVariantImpl.java | 99 +
.../gradle/internal/api/ApkVariantOutputImpl.java | 0
.../internal/api/ApplicationVariantImpl.java | 0
.../build/gradle/internal/api/BaseVariantImpl.java | 275 ++
.../gradle/internal/api/BaseVariantOutputImpl.java | 0
.../api/DefaultAndroidSourceDirectorySet.java | 198 ++
.../internal/api/DefaultAndroidSourceFile.java | 0
.../internal/api/DefaultAndroidSourceSet.java | 0
.../gradle/internal/api/ImmutableFlavorList.java | 0
.../gradle/internal/api/LibraryVariantImpl.java | 0
.../internal/api/LibraryVariantOutputImpl.java | 0
.../gradle/internal/api/ReadOnlyBaseConfig.java | 167 +
.../gradle/internal/api/ReadOnlyBuildType.java | 118 +
.../internal/api/ReadOnlyObjectProvider.java | 0
.../gradle/internal/api/ReadOnlyProductFlavor.java | 181 +
.../gradle/internal/api/ReadOnlySigningConfig.java | 0
.../api/ReadOnlyVectorDrawablesOptions.java | 51 +
.../build/gradle/internal/api/TestVariantImpl.java | 0
.../build/gradle/internal/api/TestedVariant.java | 0
.../gradle/internal/api/UnitTestVariantImpl.java | 0
.../build/gradle/internal/api/VariantFilter.java | 0
.../android/build/gradle/internal/core/Abi.java | 0
.../internal/core/GradleVariantConfiguration.java | 172 +
.../gradle/internal/core/MergedNdkConfig.java | 0
.../build/gradle/internal/core/Toolchain.java | 0
.../gradle/internal/coverage/JacocoOptions.java | 52 +
.../gradle/internal/coverage/JacocoPlugin.java | 147 +
.../internal/coverage/JacocoReportTask.groovy | 0
.../internal/dependency/DependencyChecker.java | 0
.../build/gradle/internal/dependency/JarInfo.java | 0
.../build/gradle/internal/dependency/LibInfo.java | 0
.../internal/dependency/LibraryDependencyImpl.java | 0
.../dependency/ManifestDependencyImpl.java | 0
.../dependency/SymbolFileProviderImpl.java | 0
.../internal/dependency/VariantDependencies.java | 271 ++
.../build/gradle/internal/dsl/AaptOptions.java | 197 ++
.../build/gradle/internal/dsl/AbiSplitOptions.java | 0
.../build/gradle/internal/dsl/AdbOptions.java | 72 +
.../internal/dsl/AndroidSourceSetFactory.java | 0
.../build/gradle/internal/dsl/BuildType.java | 396 +++
.../gradle/internal/dsl/BuildTypeFactory.java | 0
.../build/gradle/internal/dsl/CoreBuildType.java | 36 +
.../build/gradle/internal/dsl/CoreNdkOptions.java | 0
.../gradle/internal/dsl/CoreProductFlavor.java | 0
.../gradle/internal/dsl/CoreSigningConfig.java | 0
.../gradle/internal/dsl/DataBindingOptions.java | 62 +
.../gradle/internal/dsl/DensitySplitOptions.java | 129 +
.../build/gradle/internal/dsl/DexOptions.java | 138 +
.../gradle/internal/dsl/LanguageSplitOptions.java | 0
.../build/gradle/internal/dsl/LintOptions.java | 815 +++++
.../build/gradle/internal/dsl/NdkOptions.java | 0
.../gradle/internal/dsl/PackagingOptions.java | 162 +
.../build/gradle/internal/dsl/ProductFlavor.java | 530 +++
.../build/gradle/internal/dsl/SigningConfig.java | 0
.../gradle/internal/dsl/SigningConfigFactory.java | 0
.../build/gradle/internal/dsl/SplitOptions.java | 0
.../android/build/gradle/internal/dsl/Splits.java | 120 +
.../build/gradle/internal/dsl/TestOptions.java | 0
.../internal/incremental/BuildInfoLoaderTask.java | 155 +
.../gradle/internal/incremental/ByteCodeUtils.java | 54 +
.../gradle/internal/incremental/ColdswapMode.java | 40 +
.../incremental/ConstructorArgsRedirection.java | 174 +
.../incremental/ConstructorDelegationDetector.java | 343 ++
.../incremental/IncrementalChangeVisitor.java | 1073 ++++++
.../incremental/IncrementalSupportVisitor.java | 614 ++++
.../internal/incremental/IncrementalVisitor.java | 474 +++
.../internal/incremental/InstantRunAnchorTask.java | 79 +
.../incremental/InstantRunBuildContext.java | 729 ++++
.../incremental/InstantRunMethodVerifier.java | 112 +
.../incremental/InstantRunPatchingPolicy.java | 101 +
.../internal/incremental/InstantRunVerifier.java | 484 +++
.../incremental/InstantRunVerifierStatus.java | 74 +
.../incremental/InstantRunWrapperTask.java | 162 +
.../internal/incremental/MethodRedirection.java | 66 +
.../gradle/internal/incremental/Redirection.java | 132 +
.../gradle/internal/incremental/StringSwitch.java | 241 ++
.../gradle/internal/model/AaptOptionsImpl.java | 0
.../gradle/internal/model/AndroidArtifactImpl.java | 175 +
.../internal/model/AndroidArtifactOutputImpl.java | 0
.../gradle/internal/model/AndroidLibraryImpl.java | 0
.../gradle/internal/model/ApiVersionImpl.java | 0
.../internal/model/ArtifactMetaDataImpl.java | 0
.../gradle/internal/model/BaseArtifactImpl.java | 0
.../gradle/internal/model/BaseConfigImpl.java | 145 +
.../internal/model/BuildTypeContainerImpl.java | 0
.../build/gradle/internal/model/BuildTypeImpl.java | 146 +
.../internal/model/DefaultAndroidProject.java | 300 ++
.../internal/model/DefaultJavaCompileOptions.java | 0
.../gradle/internal/model/DependenciesImpl.java | 263 ++
.../gradle/internal/model/FilterDataImpl.java | 0
.../gradle/internal/model/InstantRunImpl.java | 58 +
.../gradle/internal/model/JavaArtifactImpl.java | 89 +
.../gradle/internal/model/JavaLibraryImpl.java | 67 +
.../build/gradle/internal/model/LibraryImpl.java | 0
.../internal/model/MavenCoordinatesImpl.java | 0
.../build/gradle/internal/model/ModelBuilder.java | 610 ++++
.../internal/model/NativeAndroidProjectImpl.java | 118 +
.../gradle/internal/model/NativeArtifactImpl.java | 107 +
.../gradle/internal/model/NativeFileImpl.java | 65 +
.../gradle/internal/model/NativeFolderImpl.java | 67 +
.../internal/model/NativeLibraryFactory.java | 0
.../gradle/internal/model/NativeLibraryImpl.java | 0
.../gradle/internal/model/NativeSettingsImpl.java | 52 +
.../gradle/internal/model/NativeToolchainImpl.java | 73 +
.../gradle/internal/model/OutputFileImpl.java | 0
.../internal/model/ProductFlavorContainerImpl.java | 0
.../gradle/internal/model/ProductFlavorImpl.java | 241 ++
.../gradle/internal/model/SigningConfigImpl.java | 0
.../model/SourceProviderContainerImpl.java | 0
.../gradle/internal/model/SourceProviderImpl.java | 0
.../build/gradle/internal/model/SyncIssueImpl.java | 76 +
.../build/gradle/internal/model/SyncIssueKey.java | 61 +
.../build/gradle/internal/model/VariantImpl.java | 0
.../internal/pipeline/ExtendedContentType.java | 78 +
.../pipeline/FilterableStreamCollection.java | 82 +
.../internal/pipeline/ImmutableDirectoryInput.java | 75 +
.../internal/pipeline/ImmutableJarInput.java | 72 +
.../internal/pipeline/ImmutableTransformInput.java | 70 +
.../pipeline/IncrementalTransformInput.java | 171 +
.../internal/pipeline/IntermediateFolderUtils.java | 518 +++
.../internal/pipeline/IntermediateStream.java | 167 +
.../internal/pipeline/MutableDirectoryInput.java | 98 +
.../gradle/internal/pipeline/OriginalStream.java | 277 ++
.../internal/pipeline/QualifiedContentImpl.java | 94 +
.../gradle/internal/pipeline/StreamBasedTask.java | 67 +
.../pipeline/TransformInvocationBuilder.java | 112 +
.../gradle/internal/pipeline/TransformManager.java | 426 +++
.../pipeline/TransformOutputProviderImpl.java | 55 +
.../gradle/internal/pipeline/TransformStream.java | 118 +
.../gradle/internal/pipeline/TransformTask.java | 477 +++
.../process/GradleJavaProcessExecutor.java | 96 +
.../internal/process/GradleProcessExecutor.java | 0
.../internal/process/GradleProcessResult.java | 64 +
.../OutputHandlerFailedGradleProcessResult.java | 47 +
.../internal/profile/RecordingBuildListener.java | 107 +
.../gradle/internal/profile/SpanRecorders.java | 0
.../internal/publishing/ApkPublishArtifact.java | 0
.../internal/publishing/BasePublishArtifact.java | 0
.../internal/publishing/FilterDataPersistence.java | 73 +
.../publishing/MappingPublishArtifact.java | 0
.../publishing/MetadataPublishArtifact.java | 0
.../build/gradle/internal/scope/AndroidTask.java | 172 +
.../gradle/internal/scope/AndroidTaskRegistry.java | 83 +
.../build/gradle/internal/scope/BaseScope.java | 60 +
.../internal/scope/ConventionMappingHelper.java | 47 +
.../build/gradle/internal/scope/GlobalScope.java | 216 ++
.../gradle/internal/scope/TaskConfigAction.java | 45 +
.../gradle/internal/scope/VariantOutputScope.java | 220 ++
.../build/gradle/internal/scope/VariantScope.java | 382 +++
.../gradle/internal/scope/VariantScopeImpl.java | 1040 ++++++
.../internal/tasks/AbstractAndroidCompile.java | 0
.../gradle/internal/tasks/AndroidReportTask.java | 175 +
.../gradle/internal/tasks/AndroidTestTask.java | 0
.../build/gradle/internal/tasks/BaseTask.java | 0
.../build/gradle/internal/tasks/CheckManifest.java | 64 +
.../gradle/internal/tasks/DefaultAndroidTask.java | 0
.../internal/tasks/DependencyReportTask.java | 0
.../tasks/DeviceProviderInstrumentTestTask.java | 385 +++
.../internal/tasks/ExtractJavaResourcesTask.java | 263 ++
.../build/gradle/internal/tasks/FileSupplier.java | 0
.../gradle/internal/tasks/FilteredJarCopyTask.java | 134 +
.../gradle/internal/tasks/GenerateApkDataTask.java | 228 ++
.../gradle/internal/tasks/IncrementalTask.java | 120 +
.../gradle/internal/tasks/InstallVariantTask.java | 277 ++
.../gradle/internal/tasks/LibraryJarTransform.java | 370 +++
.../internal/tasks/LibraryJniLibsTransform.java | 195 ++
.../build/gradle/internal/tasks/MergeFileTask.java | 85 +
.../internal/tasks/MockableAndroidJarTask.java | 140 +
.../build/gradle/internal/tasks/NdkTask.groovy | 0
.../internal/tasks/PrepareDependenciesTask.java | 97 +
.../gradle/internal/tasks/PrepareLibraryTask.java | 77 +
.../gradle/internal/tasks/SigningReportTask.java | 0
.../gradle/internal/tasks/SingleFileCopyTask.java | 72 +
.../gradle/internal/tasks/SourceSetsTask.java | 0
.../gradle/internal/tasks/SplitFileSupplier.java | 0
.../gradle/internal/tasks/TestServerTask.java | 81 +
.../build/gradle/internal/tasks/UninstallTask.java | 165 +
.../gradle/internal/tasks/ValidateSigningTask.java | 111 +
.../DataBindingExportBuildInfoTask.java | 195 ++
.../databinding/DataBindingProcessLayoutsTask.java | 166 +
.../tasks/multidex/CreateManifestKeepList.java | 207 ++
.../gradle/internal/test/AbstractTestDataImpl.java | 0
.../internal/test/TestApplicationTestData.java | 0
.../build/gradle/internal/test/TestDataImpl.java | 0
.../gradle/internal/test/report/ReportType.java | 0
.../internal/transforms/BaseProguardAction.java | 170 +
.../gradle/internal/transforms/ChangeRecords.java | 156 +
.../gradle/internal/transforms/DexTransform.java | 557 ++++
.../internal/transforms/ExtractJarsTransform.java | 295 ++
.../gradle/internal/transforms/FileFilter.java | 338 ++
.../internal/transforms/InstantRunBuildType.java | 58 +
.../gradle/internal/transforms/InstantRunDex.java | 312 ++
.../internal/transforms/InstantRunSlicer.java | 560 ++++
.../transforms/InstantRunSplitApkBuilder.java | 335 ++
.../internal/transforms/InstantRunTransform.java | 507 +++
.../transforms/InstantRunVerifierTransform.java | 396 +++
.../internal/transforms/JacocoTransform.java | 185 ++
.../gradle/internal/transforms/JarMerger.java | 193 ++
.../internal/transforms/JarMergingTransform.java | 122 +
.../transforms/MergeJavaResourcesTransform.java | 555 ++++
.../internal/transforms/MultiDexTransform.java | 247 ++
.../internal/transforms/NewShrinkerTransform.java | 292 ++
.../transforms/NoChangesVerifierTransform.java | 123 +
.../internal/transforms/ProGuardTransform.java | 365 ++
.../internal/transforms/ProguardConfigurable.java | 50 +
.../transforms/ShrinkResourcesTransform.java | 296 ++
.../gradle/internal/variant/ApkVariantData.java | 73 +
.../internal/variant/ApkVariantOutputData.java | 193 ++
.../internal/variant/ApplicationVariantData.java | 73 +
.../variant/ApplicationVariantFactory.java | 211 ++
.../gradle/internal/variant/BaseVariantData.java | 728 ++++
.../internal/variant/BaseVariantOutputData.java | 0
.../variant/DefaultSourceProviderContainer.java | 0
.../internal/variant/LibVariantOutputData.java | 0
.../internal/variant/LibraryVariantData.java | 116 +
.../internal/variant/LibraryVariantFactory.java | 178 +
.../gradle/internal/variant/TestVariantData.java | 87 +
.../internal/variant/TestVariantFactory.java | 87 +
.../gradle/internal/variant/TestedVariantData.java | 0
.../gradle/internal/variant/VariantFactory.java | 0
.../gradle/internal/variant/VariantHelper.java | 0
.../android/build/gradle/tasks/AidlCompile.java | 419 +++
.../android/build/gradle/tasks/AndroidJarTask.java | 35 +
.../build/gradle/tasks/BinaryFileProviderTask.java | 0
.../gradle/tasks/ColdswapArtifactsKickerTask.java | 71 +
.../gradle/tasks/CompatibleScreensManifest.groovy | 123 +
.../build/gradle/tasks/ExtractAnnotations.groovy | 256 ++
.../build/gradle/tasks/GenerateBuildConfig.groovy | 207 ++
.../build/gradle/tasks/GenerateResValues.groovy | 118 +
.../build/gradle/tasks/GenerateSplitAbiRes.java | 254 ++
.../build/gradle/tasks/GroovyGradleDetector.java | 0
.../build/gradle/tasks/InvokeManifestMerger.groovy | 75 +
.../com/android/build/gradle/tasks/JackTask.java | 496 +++
.../com/android/build/gradle/tasks/JillTask.java | 311 ++
.../com/android/build/gradle/tasks/KickerTask.java | 86 +
.../com/android/build/gradle/tasks/Lint.groovy | 324 ++
.../build/gradle/tasks/ManifestProcessorTask.java | 108 +
.../com/android/build/gradle/tasks/MarkerFile.java | 51 +
.../android/build/gradle/tasks/MergeManifests.java | 364 ++
.../android/build/gradle/tasks/MergeResources.java | 570 ++++
.../gradle/tasks/MergeSourceSetFolders.groovy | 246 ++
.../android/build/gradle/tasks/NdkCompile.groovy | 302 ++
.../build/gradle/tasks/PackageApplication.java | 547 +++
.../build/gradle/tasks/PackageSplitAbi.java | 313 ++
.../build/gradle/tasks/PackageSplitRes.java | 359 ++
.../build/gradle/tasks/PrePackageApplication.java | 87 +
.../gradle/tasks/ProcessAndroidResources.java | 615 ++++
.../build/gradle/tasks/ProcessManifest.java | 259 ++
.../build/gradle/tasks/ProcessTestManifest.groovy | 0
.../build/gradle/tasks/ProcessTestManifest.java | 316 ++
.../build/gradle/tasks/RenderscriptCompile.groovy | 180 +
.../build/gradle/tasks/ResourceException.java | 0
.../build/gradle/tasks/ResourceUsageAnalyzer.java | 1648 +++++++++
.../build/gradle/tasks/SimpleWorkQueue.java | 58 +
.../build/gradle/tasks/SplitRelatedTask.java | 144 +
.../android/build/gradle/tasks/SplitZipAlign.java | 391 +++
.../build/gradle/tasks/TestModuleProGuardTask.java | 87 +
.../com/android/build/gradle/tasks/ZipAlign.java | 168 +
.../gradle/tasks/annotations/ApiDatabase.java | 0
.../annotations/ExtractAnnotationsDriver.java | 391 +++
.../build/gradle/tasks/annotations/Extractor.java | 0
.../gradle/tasks/annotations/TypedefCollector.java | 154 +
.../gradle/tasks/annotations/TypedefRemover.java | 0
.../gradle/tasks/factory/AbstractCompilesUtil.java | 0
.../gradle/tasks/factory/AndroidJavaCompile.java | 42 +
.../tasks/factory/JavaCompileConfigAction.java | 157 +
.../tasks/factory/ProcessJavaResConfigAction.java | 94 +
.../gradle/tasks/factory/UnitTestConfigAction.java | 163 +
.../tasks/fd/FastDeployRuntimeExtractorTask.java | 140 +
.../tasks/fd/GenerateInstantRunAppInfoTask.java | 274 ++
.../build/gradle/shrinker/AbstractShrinker.java | 382 +++
.../gradle/shrinker/ClassLookupException.java | 39 +
.../gradle/shrinker/ClassStructureVisitor.java | 93 +
.../android/build/gradle/shrinker/Dependency.java | 62 +
.../gradle/shrinker/DependencyFinderVisitor.java | 678 ++++
.../gradle/shrinker/DependencyRemoverVisitor.java | 51 +
.../build/gradle/shrinker/DependencyType.java | 76 +
.../gradle/shrinker/FilterMembersVisitor.java | 85 +
.../build/gradle/shrinker/FullRunShrinker.java | 508 +++
.../gradle/shrinker/IncrementalRunVisitor.java | 281 ++
.../build/gradle/shrinker/IncrementalShrinker.java | 285 ++
.../shrinker/JavaSerializationShrinkerGraph.java | 624 ++++
.../android/build/gradle/shrinker/KeepRules.java | 32 +
.../build/gradle/shrinker/PostProcessingData.java | 94 +
.../build/gradle/shrinker/ProguardConfig.java | 55 +
.../gradle/shrinker/ProguardFlagsKeepRules.java | 222 ++
.../build/gradle/shrinker/ShrinkerGraph.java | 143 +
.../build/gradle/shrinker/ShrinkerLogger.java | 87 +
.../gradle/shrinker/TypeHierarchyTraverser.java | 102 +
.../shrinker/parser/AnnotationSpecification.java | 33 +
.../gradle/shrinker/parser/ClassSpecification.java | 106 +
.../shrinker/parser/ClassTypeSpecification.java | 55 +
.../gradle/shrinker/parser/FieldSpecification.java | 62 +
.../shrinker/parser/FilterSpecification.java | 60 +
.../build/gradle/shrinker/parser/Flags.java | 90 +
.../gradle/shrinker/parser/GrammarActions.java | 351 ++
.../shrinker/parser/InheritanceSpecification.java | 41 +
.../build/gradle/shrinker/parser/KeepModifier.java | 25 +
.../build/gradle/shrinker/parser/Matcher.java | 24 +
.../gradle/shrinker/parser/MatcherWithNegator.java | 40 +
.../shrinker/parser/MethodSpecification.java | 54 +
.../shrinker/parser/ModifierSpecification.java | 82 +
.../gradle/shrinker/parser/NameSpecification.java | 35 +
.../incremental-test-classes/base/NoPackage.java | 33 +
.../base/com/example/basic/AllAccessFields.java | 39 +
.../com/example/basic/AllAccessFieldsSubclass.java | 24 +
.../base/com/example/basic/AllAccessMethods.java | 163 +
.../com/example/basic/AllAccessStaticFields.java | 84 +
.../com/example/basic/AllAccessStaticMethods.java | 71 +
.../base/com/example/basic/AllTypesFields.java | 293 ++
.../base/com/example/basic/AnonymousClasses.java | 40 +
.../example/basic/AnotherPackagePrivateClass.java | 51 +
.../base/com/example/basic/ClashStaticMethod.java | 55 +
.../base/com/example/basic/ClassWithFields.java | 29 +
.../base/com/example/basic/Constructors.java | 224 ++
.../base/com/example/basic/ControlClass.java | 27 +
.../base/com/example/basic/CovariantChild.java | 29 +
.../base/com/example/basic/CovariantParent.java | 27 +
.../base/com/example/basic/Enums.java | 42 +
.../base/com/example/basic/Exceptions.java | 95 +
.../com/example/basic/FieldOverridingChild.java | 58 +
.../example/basic/FieldOverridingGrandChild.java | 29 +
.../com/example/basic/FieldOverridingParent.java | 56 +
.../base/com/example/basic/FinalFieldsInCtor.java | 52 +
.../base/com/example/basic/GrandChild.java | 42 +
.../base/com/example/basic/InnerClass.java | 35 +
.../base/com/example/basic/InnerClassInvoker.java | 27 +
.../base/com/example/basic/InnerOuterInvoker.java | 31 +
.../com/example/basic/InnerSubclassingOuter.java | 45 +
.../example/basic/MultipleMethodInvocations.java | 36 +
.../base/com/example/basic/NoPackageAccess.java | 28 +
.../com/example/basic/PackagePrivateClass.java | 48 +
.../example/basic/PackagePrivateFieldAccess.java | 69 +
.../com/example/basic/PackagePrivateInterface.java | 25 +
.../com/example/basic/PackagePrivateInvoker.java | 43 +
.../base/com/example/basic/ParentInvocation.java | 76 +
.../base/com/example/basic/Provider.java | 25 +
.../com/example/basic/PublicMethodInvoker.java | 87 +
.../base/com/example/basic/ReflectiveUser.java | 55 +
.../com/example/basic/StaticMethodsInvoker.java | 61 +
.../base/com/example/basic/SuperCall.java | 35 +
.../com/ir/disable/InstantRunDisabledClass.java | 40 +
.../com/ir/disable/InstantRunDisabledMethod.java | 68 +
.../base/com/ir/disable/all/DisabledClassOne.java | 27 +
.../base/com/ir/disable/all/DisabledClassTwo.java | 27 +
.../base/com/ir/disable/all/package-info.java | 23 +
.../com/verifier/tests/AddClassAnnotation.java | 25 +
.../base/com/verifier/tests/AddInstanceField.java | 25 +
.../verifier/tests/AddInterfaceImplementation.java | 26 +
.../com/verifier/tests/AddMethodAnnotation.java | 25 +
.../tests/AddNotRuntimeClassAnnotation.java | 25 +
.../base/com/verifier/tests/ChangeFieldType.java | 25 +
.../tests/ChangeInstanceFieldToStatic.java | 26 +
.../tests/ChangeInstanceFieldVisibility.java | 25 +
.../tests/ChangeStaticFieldToInstance.java | 26 +
.../tests/ChangeStaticFieldVisibility.java | 25 +
.../base/com/verifier/tests/ChangeSuperClass.java | 28 +
.../verifier/tests/ChangedClassInitializer1.java | 29 +
.../verifier/tests/ChangedClassInitializer2.java | 25 +
.../verifier/tests/ChangedClassInitializer3.java | 33 +
.../com/verifier/tests/DisabledClassChanging.java | 35 +
.../verifier/tests/DisabledClassNotChanging.java | 35 +
.../com/verifier/tests/DisabledMethodChanging.java | 34 +
.../base/com/verifier/tests/MethodAddedClass.java | 27 +
.../com/verifier/tests/MethodCollisionClass.java | 31 +
.../verifier/tests/NewInstanceReflectionUser.java | 33 +
.../base/com/verifier/tests/R.java | 23 +
.../verifier/tests/ReflectiveUserNotChanging.java | 36 +
.../com/verifier/tests/RemoveClassAnnotation.java | 26 +
.../tests/RemoveInterfaceImplementation.java | 27 +
.../com/verifier/tests/RemoveMethodAnnotation.java | 26 +
.../tests/RemoveNotRuntimeClassAnnotation.java | 27 +
.../base/com/verifier/tests/UnchangedClass.java | 29 +
.../verifier/tests/UnchangedClassInitializer1.java | 33 +
.../patches/changeBaseClass/NoPackage.java | 33 +
.../com/example/basic/AllAccessMethods.java | 117 +
.../com/example/basic/AnonymousClasses.java | 40 +
.../com/example/basic/ClashStaticMethod.java | 44 +
.../com/example/basic/ClassWithFields.java | 29 +
.../com/example/basic/Constructors.java | 226 ++
.../com/example/basic/ControlClass.java | 27 +
.../changeBaseClass/com/example/basic/Enums.java | 42 +
.../com/example/basic/Exceptions.java | 96 +
.../com/example/basic/FinalFieldsInCtor.java | 52 +
.../com/example/basic/InnerSubclassingOuter.java | 45 +
.../example/basic/MultipleMethodInvocations.java | 37 +
.../com/example/basic/NoPackageAccess.java | 28 +
.../com/example/basic/SuperCall.java | 35 +
.../com/ir/disable/InstantRunDisabledClass.java | 40 +
.../com/ir/disable/InstantRunDisabledMethod.java | 67 +
.../com/ir/disable/all/DisabledClassOne.java | 27 +
.../com/ir/disable/all/DisabledClassTwo.java | 27 +
.../com/ir/disable/all/package-info.java | 23 +
.../com/example/basic/AllAccessFieldsSubclass.java | 24 +
.../com/example/basic/AllAccessStaticFields.java | 87 +
.../com/example/basic/CovariantChild.java | 29 +
.../com/example/basic/CovariantParent.java | 27 +
.../com/example/basic/FieldOverridingChild.java | 59 +
.../example/basic/FieldOverridingGrandChild.java | 29 +
.../com/example/basic/FieldOverridingParent.java | 56 +
.../com/example/basic/GrandChild.java | 42 +
.../com/example/basic/InnerClassInvoker.java | 27 +
.../example/basic/PackagePrivateFieldAccess.java | 73 +
.../com/example/basic/PackagePrivateInvoker.java | 44 +
.../com/example/basic/ParentInvocation.java | 77 +
.../com/example/basic/PublicMethodInvoker.java | 88 +
.../com/example/basic/ReflectiveUser.java | 56 +
.../com/example/basic/StaticMethodsInvoker.java | 61 +
.../verifier/tests/UnchangedClassInitializer1.java | 37 +
.../verifier/ChangeInstanceFieldToStatic.java | 26 +
.../com/verifier/tests/AddClassAnnotation.java | 28 +
.../com/verifier/tests/AddInstanceField.java | 27 +
.../verifier/tests/AddInterfaceImplementation.java | 28 +
.../com/verifier/tests/AddMethodAnnotation.java | 28 +
.../tests/AddNotRuntimeClassAnnotation.java | 26 +
.../com/verifier/tests/ChangeFieldType.java | 25 +
.../tests/ChangeInstanceFieldVisibility.java | 25 +
.../tests/ChangeStaticFieldToInstance.java | 26 +
.../tests/ChangeStaticFieldVisibility.java | 25 +
.../com/verifier/tests/ChangeSuperClass.java | 29 +
.../verifier/tests/ChangedClassInitializer1.java | 29 +
.../verifier/tests/ChangedClassInitializer2.java | 25 +
.../verifier/tests/ChangedClassInitializer3.java | 35 +
.../com/verifier/tests/DisabledClassChanging.java | 35 +
.../com/verifier/tests/DisabledMethodChanging.java | 34 +
.../com/verifier/tests/MethodAddedClass.java | 33 +
.../com/verifier/tests/MethodCollisionClass.java | 29 +
.../verifier/tests/NewInstanceReflectionUser.java | 33 +
.../patches/verifier/com/verifier/tests/R.java | 24 +
.../verifier/tests/ReflectiveUserNotChanging.java | 36 +
.../com/verifier/tests/RemoveClassAnnotation.java | 25 +
.../tests/RemoveInterfaceImplementation.java | 25 +
.../com/verifier/tests/RemoveMethodAnnotation.java | 25 +
.../tests/RemoveNotRuntimeClassAnnotation.java | 27 +
.../com/verifier/tests/UnchangedClass.java | 29 +
.../verifier/tests/UnchangedClassInitializer1.java | 33 +
.../build/gradle/internal/CompileOptionsTest.java | 72 +
.../gradle/internal/DependencyManagerTest.java | 0
.../gradle/internal/ProductFlavorComboTest.java | 0
.../gradle/internal/dsl/AbiSplitOptionsTest.java | 0
.../internal/dsl/DensitySplitOptionsTest.java | 0
.../gradle/internal/dsl/SigningConfigTest.java | 0
.../internal/incremental/ByteCodeUtilsTest.java | 68 +
.../incremental/InstantRunBuildContextTest.java | 546 +++
.../InstantRunMethodVerifierTarget.java | 156 +
.../incremental/InstantRunMethodVerifierTest.java | 58 +
.../incremental/InstantRunVerifierTest.java | 290 ++
.../incremental/OverrideClassFieldRemovalTest.java | 56 +
.../internal/incremental/StringSwitchTest.java | 233 ++
.../incremental/fixture/ClassEnhancement.java | 293 ++
.../incremental/fixture/VerifierHarness.java | 55 +
.../incremental/hotswap/AllAccessFieldsTest.java | 46 +
.../hotswap/AllAccessStaticFieldsTest.java | 104 +
.../hotswap/AllAccessStaticMethodsTest.java | 80 +
.../incremental/hotswap/AllTypesFieldsTest.java | 57 +
.../incremental/hotswap/AnonymousClassesTest.java | 52 +
.../hotswap/ClashingMethodNamesTest.java | 59 +
.../incremental/hotswap/ConstructorTest.java | 246 ++
.../incremental/hotswap/CovariantTest.java | 50 +
.../internal/incremental/hotswap/EnumTests.java | 44 +
.../incremental/hotswap/ExceptionsTest.java | 75 +
.../incremental/hotswap/FieldOverridingTest.java | 96 +
.../incremental/hotswap/FinalFieldsInCtorTest.java | 58 +
.../incremental/hotswap/GrandChildTest.java | 72 +
.../incremental/hotswap/InnerClassInvokerTest.java | 52 +
.../hotswap/InnerSubclassingOuterTest.java | 57 +
.../hotswap/InstantRunDisabledTest.java | 144 +
.../hotswap/MultipleMethodInvocationsTest.java | 46 +
.../incremental/hotswap/NoPackageTest.java | 46 +
.../hotswap/PackageAllTypesFieldsTest.java | 81 +
.../hotswap/PackagePrivateClassTest.java | 117 +
.../incremental/hotswap/ParentInvocationTest.java | 122 +
.../incremental/hotswap/PublicMethodTest.java | 72 +
.../incremental/hotswap/ReflectiveUserTest.java | 67 +
.../incremental/hotswap/SuperCallTest.java | 42 +
.../internal/pipeline/ExtendedContentTypeTest.java | 47 +
.../pipeline/IntermediateFolderUtilsTest.java | 40 +
.../gradle/internal/pipeline/TaskTestUtils.java | 303 ++
.../gradle/internal/pipeline/TestTransform.java | 246 ++
.../internal/pipeline/TransformManagerTest.java | 711 ++++
.../internal/pipeline/TransformTaskTest.java | 1990 +++++++++++
.../publishing/FilterDataPersistenceTest.java | 0
.../internal/transforms/ChangeRecordsTest.java | 120 +
.../gradle/internal/transforms/FileFilterTest.java | 434 +++
.../internal/transforms/InstantRunDexTest.java | 386 +++
.../internal/transforms/InstantRunSlicerTest.java | 431 +++
.../transforms/InstantRunTransformTest.java | 372 +++
.../InstantRunVerifierTransformTest.java | 451 +++
.../profiling/RecordingBuildListenerTest.java | 346 ++
.../gradle/shrinker/AbstractShrinkerTest.java | 238 ++
.../build/gradle/shrinker/FullRunShrinkerTest.java | 1433 ++++++++
.../gradle/shrinker/IncrementalShrinkerTest.java | 480 +++
.../android/build/gradle/shrinker/TestClasses.java | 3487 ++++++++++++++++++++
.../gradle/shrinker/TestClassesForIncremental.java | 552 ++++
.../build/gradle/shrinker/TestKeepRules.java | 55 +
.../gradle/tasks/ResourceUsageAnalyzerTest.java | 1108 +++++++
.../annotations/ExtractAnnotationsDriverTest.java | 0
.../fd/InjectBootstrapApplicationTaskTest.java | 62 +
.../gradle/internal/tasks/multidex/mapping.txt | 0
.../gradle-experimental}/MODULE_LICENSE_APACHE2 | 0
.../gradle-experimental/NOTICE | 0
build-system/gradle-experimental/build.gradle | 73 +
.../gradle-experimental/gradle-experimental.iml | 1249 +++++++
.../internal/AbstractNativeDependentSourceSet.java | 43 +
.../build/gradle/internal/AndroidConfigHelper.java | 132 +
...efaultAndroidNativeDependencySpecContainer.java | 107 +
.../gradle/internal/JniLibsLanguageTransform.java | 177 +
.../gradle/internal/LanguageRegistryUtils.java | 38 +
.../gradle/internal/NativeDependencyLinkage.java | 39 +
.../build/gradle/internal/NdkOptionsHelper.java | 67 +
.../build/gradle/internal/SharedObjectFile.java | 25 +
.../dependency/AndroidNativeDependencySpec.java | 157 +
.../AndroidNativeDependencySpecContainer.java | 44 +
.../internal/dependency/ArtifactContainer.java | 28 +
.../dependency/NativeDependencyResolveResult.java | 48 +
.../dependency/NativeDependencyResolver.java | 257 ++
.../internal/dependency/NativeLibraryArtifact.java | 87 +
.../dependency/NativeLibraryArtifactAdaptor.java | 80 +
.../gradle/internal/gson/FileGsonTypeAdaptor.java | 52 +
.../internal/gson/NativeBuildConfigValue.java | 83 +
.../gradle/internal/gson/NativeLibraryValue.java | 87 +
.../internal/gson/NativeSourceFileValue.java | 45 +
.../internal/gson/NativeSourceFolderValue.java | 51 +
.../gradle/internal/gson/NativeToolchainValue.java | 41 +
.../build/gradle/managed/AndroidConfig.java | 165 +
.../android/build/gradle/managed/ApiVersion.java | 0
.../android/build/gradle/managed/BaseConfig.java | 126 +
.../android/build/gradle/managed/BuildType.java | 114 +
.../android/build/gradle/managed/ClassField.java | 43 +
.../build/gradle/managed/DataBindingOptions.java | 41 +
.../build/gradle/managed/JsonConfigFile.java | 52 +
.../build/gradle/managed/NativeBuildConfig.java | 61 +
.../build/gradle/managed/NativeLibrary.java | 87 +
.../build/gradle/managed/NativeSourceFile.java | 52 +
.../build/gradle/managed/NativeSourceFolder.java | 59 +
.../build/gradle/managed/NativeToolchain.java | 51 +
.../build/gradle/managed/NdkAbiOptions.java | 28 +
.../android/build/gradle/managed/NdkBuildType.java | 0
.../android/build/gradle/managed/NdkConfig.java | 35 +
.../android/build/gradle/managed/NdkOptions.java | 85 +
.../build/gradle/managed/ProductFlavor.java | 194 ++
.../build/gradle/managed/SigningConfig.java | 44 +
.../gradle/managed/VectorDrawablesOptions.java | 36 +
.../managed/adaptor/AndroidConfigAdaptor.java | 332 ++
.../gradle/managed/adaptor/ApiVersionAdaptor.java | 0
.../gradle/managed/adaptor/BaseConfigAdaptor.java | 141 +
.../gradle/managed/adaptor/BuildTypeAdaptor.java | 115 +
.../managed/adaptor/DataBindingOptionsAdapter.java | 43 +
.../gradle/managed/adaptor/NdkOptionsAdaptor.java | 76 +
.../managed/adaptor/ProductFlavorAdaptor.java | 177 +
.../managed/adaptor/SigningConfigAdaptor.java | 89 +
.../adaptor/VectorDrawablesOptionsAdaptor.java | 49 +
.../android/build/gradle/model/AndroidBinary.java | 0
.../gradle/model/AndroidComponentModelPlugin.java | 318 ++
.../model/AndroidComponentModelTestPlugin.java | 80 +
.../build/gradle/model/AndroidComponentSpec.java | 0
.../gradle/model/AndroidLanguageSourceSet.java | 25 +
.../gradle/model/AppComponentModelPlugin.java | 90 +
.../model/ApplicationComponentTaskManager.java | 64 +
.../gradle/model/BaseComponentModelPlugin.java | 660 ++++
.../build/gradle/model/ComponentModelBuilder.java | 142 +
.../model/ComponentNativeLibraryFactory.java | 239 ++
.../gradle/model/ExternalNativeBinarySpec.java | 29 +
.../gradle/model/ExternalNativeComponentSpec.java | 30 +
.../build/gradle/model/JniLibsSourceSet.java | 31 +
.../gradle/model/LibraryComponentModelPlugin.java | 90 +
.../gradle/model/LibraryComponentTaskManager.java | 64 +
.../android/build/gradle/model/ModelConstants.java | 51 +
.../gradle/model/NativeComponentModelBuilder.java | 173 +
.../gradle/model/NativeDependentSourceSet.java | 30 +
.../build/gradle/model/NativeSourceSet.java | 27 +
.../gradle/model/NdkComponentModelPlugin.java | 791 +++++
.../android/build/gradle/model/NdkConfigImpl.java | 163 +
.../model/StandaloneNdkComponentModelPlugin.java | 209 ++
.../build/gradle/model/TaskModelMapAdaptor.java | 0
.../model/internal/AndroidBinaryInternal.java | 53 +
.../internal/AndroidComponentSpecInternal.java | 44 +
.../model/internal/DefaultAndroidBinary.java | 117 +
.../internal/DefaultAndroidComponentSpec.java | 79 +
.../internal/DefaultAndroidLanguageSourceSet.java | 28 +
.../internal/DefaultExternalNativeBinarySpec.java | 40 +
.../DefaultExternalNativeComponentSpec.java | 40 +
.../model/internal/DefaultJniLibsSourceSet.java | 27 +
.../model/internal/DefaultNativeSourceSet.java | 48 +
.../internal/AbstractNativeToolSpecification.java | 45 +
.../ndk/internal/ClangNativeToolSpecification.java | 229 ++
.../internal/DefaultNativeToolSpecification.java | 0
.../ndk/internal/GccNativeToolSpecification.java | 0
.../ndk/internal/NativeToolSpecification.java | 0
.../internal/NativeToolSpecificationFactory.java | 0
.../gradle/ndk/internal/NdkConfiguration.java | 433 +++
.../ndk/internal/NdkExtensionConvention.java | 43 +
.../build/gradle/ndk/internal/NdkNamingScheme.java | 145 +
.../gradle/ndk/internal/StlConfiguration.java | 93 +
.../ndk/internal/StlNativeToolSpecification.java | 116 +
.../ndk/internal/ToolchainConfiguration.java | 141 +
.../build/gradle/tasks/StripDebugSymbolTask.java | 186 ++
.../build/gradle/tasks/StripDependenciesTask.java | 224 ++
.../com.android.model.application.properties | 0
.../com.android.model.library.properties | 0
.../com.android.model.native.properties | 1 +
.../gradle}/MODULE_LICENSE_APACHE2 | 0
{base/build-system => build-system}/gradle/NOTICE | 0
.../gradle/build.gradle | 0
.../gradle/gradle.iml | 0
.../com/android/build/gradle/AppExtension.java | 49 +
.../com/android/build/gradle/AppPlugin.groovy | 76 +
.../com/android/build/gradle/BaseExtension.java | 866 +++++
.../com/android/build/gradle/BasePlugin.java | 741 +++++
.../com/android/build/gradle/LibraryExtension.java | 94 +
.../com/android/build/gradle/LibraryPlugin.groovy | 93 +
.../com/android/build/gradle/TestExtension.java | 88 +
.../com/android/build/gradle/TestPlugin.groovy | 76 +
.../com/android/build/gradle/TestedExtension.java | 0
.../build/gradle/internal/ApiObjectFactory.java | 0
.../gradle/internal/NativeLibraryFactoryImpl.java | 82 +
.../gradle/internal/dsl/ProductFlavorFactory.java | 58 +
.../gradle-plugins/android-library.properties | 0
.../gradle-plugins/android-reporting.properties | 0
.../META-INF/gradle-plugins/android.properties | 0
.../com.android.application.properties | 0
.../gradle-plugins/com.android.library.properties | 0
.../gradle-plugins/com.android.test.properties | 0
.../android/build/gradle/AppPluginDslTest.groovy | 798 +++++
.../build/gradle/AppPluginInternalTest.groovy | 431 +++
.../com/android/build/gradle/DslTestUtil.groovy | 0
.../build/gradle/LibraryPluginDslTest.groovy | 0
.../build/gradle/internal/dsl/BuildTypeTest.groovy | 0
.../build/gradle/internal/test/BaseTest.groovy | 0
build-system/integration-test/.gitignore | 1 +
build-system/integration-test/build.gradle | 176 +
build-system/integration-test/integration-test.iml | 20 +
.../integration/application/AaptOptionsTest.groovy | 77 +
.../integration/application/AbiPureSplits.groovy | 206 ++
.../application/AndroidManifestInTestTest.groovy | 78 +
.../application/AndroidTestResourcesTest.groovy | 197 ++
.../application/ApplicationIdInLibsTest.groovy | 103 +
.../application/ApplicationIdTest.groovy | 86 +
.../application/ArchivesBaseNameTest.groovy | 86 +
.../integration/application/ArtifactApiTest.groovy | 188 ++
.../application/AutoDensitySplitTest.groovy | 99 +
.../integration/application/AutoPureSplits.groovy | 0
.../integration/application/AutoResConfig.groovy | 0
.../application/BasicMultiFlavorTest.groovy | 0
.../integration/application/BasicTest.groovy | 138 +
.../integration/application/BasicTest2.groovy | 293 ++
.../application/BootClasspathTest.groovy | 56 +
.../integration/application/BuildConfigTest.groovy | 298 ++
.../integration/application/BuildToolsTest.groovy | 133 +
.../CombinedAbiDensityPureSplits.groovy | 0
.../CombinedDensityAndLanguageTest.groovy | 0
.../CombinedDensityWithDisabledLanguageTest.groovy | 0
.../CombinedLanguageWithDisabledDensityTest.groovy | 0
.../application/CustomArtifactDepTest.groovy | 0
.../application/DensitySplitInLTest.groovy | 212 ++
.../application/DensitySplitTest.groovy | 152 +
.../DensitySplitWithPublishNonDefaultTest.groovy | 60 +
.../application/DependenciesTest.groovy | 53 +
.../application/DependencyCheckerTest.groovy | 100 +
.../application/DependencyCyclesTest.groovy | 0
.../integration/application/DexLimitTest.java | 81 +
.../application/ExternalTestProjectTest.groovy | 165 +
.../application/ExtractAnnotationTest.groovy | 128 +
.../application/FilteredOutBuildTypeTest.groovy | 59 +
.../application/FilteredOutVariantsTest.groovy | 69 +
.../integration/application/FlavoredTest.groovy | 53 +
.../integration/application/FlavorsTest.groovy | 126 +
.../application/GenFolderApi2Test.groovy | 96 +
.../application/GenFolderApiTest.groovy | 131 +
.../application/InjectedDensityTest.java | 105 +
.../integration/application/InstantUnitTest.java | 45 +
.../InvalidResourceDirectoryTest.groovy | 81 +
.../gradle/integration/application/JackTest.groovy | 111 +
.../JacocoOnlySubprojectBuildScriptDependency.java | 39 +
.../application/JacocoTransformTest.java | 77 +
.../application/JacocoWithButterKnifeTest.groovy | 61 +
.../integration/application/JarJarTest.groovy | 84 +
.../integration/application/LintVitalTest.groovy | 70 +
.../application/ManifestMergingTest.groovy | 0
.../application/MaxSdkVersionTest.groovy | 0
.../application/MessageRewriteTest.groovy | 67 +
.../integration/application/MigratedTest.groovy | 102 +
.../MinifyLibAndAppWithJavaResTest.groovy | 96 +
.../integration/application/MinifyTest.groovy | 160 +
.../integration/application/ModelTest.groovy | 64 +
.../integration/application/MultiDexTest.groovy | 80 +
.../application/MultiProjectTest.groovy | 0
.../integration/application/MultiresTest.groovy | 53 +
.../application/NdkJniPureSplitLibTest.groovy | 59 +
.../integration/application/NoCruncherTest.groovy | 0
.../application/OptionalLibraryTest.groovy | 133 +
.../OptionalLibraryWithProguardTest.groovy | 60 +
.../application/OutputRenamingTest.groovy | 105 +
.../integration/application/Overlay1Test.groovy | 66 +
.../integration/application/Overlay2Test.groovy | 69 +
.../integration/application/Overlay3Test.groovy | 95 +
.../application/PackagingOptionsTest.groovy | 204 ++
.../integration/application/ParentLibsTest.groovy | 0
.../integration/application/PkgOverrideTest.groovy | 53 +
.../application/PlaceholderInLibsTest.groovy | 0
.../application/PrivateResourceTest.groovy | 63 +
.../application/PseudoLocalizationTest.groovy | 51 +
.../integration/application/RenamedApkTest.groovy | 84 +
.../application/RenderscriptNdkTest.groovy | 55 +
.../gradle/integration/application/RepoTest.groovy | 0
.../integration/application/ResValueTest.groovy | 263 ++
.../application/ResValueTypeTest.groovy | 140 +
.../application/ResourceValidationTest.java | 71 +
.../application/RsEnabledAnnotationTest.groovy | 0
.../application/RsSupportModeTest.groovy | 81 +
.../application/ShrinkResourcesTest.groovy | 542 +++
.../integration/application/SigningTest.groovy | 258 ++
.../integration/application/TestWithDepTest.groovy | 0
.../integration/application/ThirdPartyTest.groovy | 0
.../integration/application/TictactoeTest.groovy | 87 +
.../application/UnitTestingModelTest.groovy | 116 +
.../application/VectorDrawableTest.java | 498 +++
.../application/VectorDrawableTest_Library.groovy | 246 ++
.../application/WearSimpleDebugOnlyTest.groovy | 0
.../integration/application/WearSimpleTest.groovy | 0
.../integration/application/WearVariantTest.groovy | 88 +
.../WearWithCustomApplicationIdTest.groovy | 58 +
.../gradle/integration/automatic/CheckAll.java | 115 +
.../integration/common/category/DeviceTests.java | 0
.../integration/common/category/SmokeTests.java | 0
.../gradle/integration/common/fixture/Adb.java | 118 +
.../common/fixture/GetAndroidModelAction.java | 141 +
.../common/fixture/GetEmptyModelAction.java | 0
.../common/fixture/GradleTestProject.java | 1319 ++++++++
.../gradle/integration/common/fixture/Logcat.java | 225 ++
.../fixture/TemporaryProjectModification.java | 227 ++
.../integration/common/fixture/TestProject.java | 0
.../common/fixture/app/AbstractAndroidTestApp.java | 114 +
.../fixture/app/AndroidComponentGradleModule.java | 0
.../common/fixture/app/AndroidGradleModule.java | 0
.../common/fixture/app/AndroidTestApp.java | 0
.../common/fixture/app/EmptyAndroidTestApp.groovy | 0
.../common/fixture/app/GradleModule.java | 0
.../common/fixture/app/GradleModuleFactory.java | 0
.../common/fixture/app/HelloWorldApp.groovy | 154 +
.../common/fixture/app/HelloWorldJniApp.groovy | 196 ++
.../common/fixture/app/HelloWorldLibraryApp.groovy | 56 +
.../common/fixture/app/JavaGradleModule.java | 0
.../common/fixture/app/LargeTestProject.java | 0
.../common/fixture/app/MultiModuleTestProject.java | 91 +
.../common/fixture/app/TestSourceFile.java | 0
.../fixture/app/VariantBuildScriptGenerator.java | 0
.../gradle/integration/common/runner/AllTests.java | 105 +
.../common/runner/FilterableParameterized.java | 65 +
.../common/runner/ParallelParameterized.java | 64 +
.../integration/common/truth/AarSubject.java | 227 ++
.../common/truth/AbstractAndroidSubject.java | 190 ++
.../common/truth/AbstractZipSubject.java | 290 ++
.../integration/common/truth/ApkSubject.java | 471 +++
.../integration/common/truth/ApkSubjectTest.java | 0
.../integration/common/truth/ArtifactSubject.java | 0
.../integration/common/truth/CustomTestVerb.java | 39 +
.../common/truth/DependenciesSubject.java | 67 +
.../integration/common/truth/DexClassSubject.java | 113 +
.../integration/common/truth/DexFileSubject.java | 154 +
.../common/truth/FakeFailureStrategy.java | 0
.../integration/common/truth/FileSubject.java | 121 +
.../common/truth/FileSubjectFactory.java | 0
.../common/truth/IncrementalFileSubject.java | 83 +
.../truth/IncrementalFileSubjectFactory.java | 42 +
.../integration/common/truth/IndirectSubject.java | 30 +
.../integration/common/truth/IssueSubject.java | 0
.../integration/common/truth/IssueSubjectTest.java | 0
.../common/truth/LogCatMessagesSubject.java | 88 +
.../integration/common/truth/ModelSubject.java | 181 +
.../integration/common/truth/TruthHelper.java | 254 ++
.../integration/common/truth/VariantSubject.java | 0
.../integration/common/truth/ZipFileSubject.java | 0
.../common/utils/AndroidVersionMatcher.java | 66 +
.../gradle/integration/common/utils/ApkHelper.java | 139 +
.../integration/common/utils/DeviceHelper.java | 98 +
.../integration/common/utils/FakeApiVersion.java | 0
.../common/utils/GradleExceptionsHelper.java | 50 +
.../integration/common/utils/ImageHelper.java | 0
.../utils/IncrementalTaskOutputVerifier.java | 46 +
.../integration/common/utils/JacocoAgent.java | 0
.../integration/common/utils/ModelHelper.java | 0
.../common/utils/ProductFlavorHelper.java | 0
.../gradle/integration/common/utils/SdkHelper.java | 98 +
.../common/utils/SigningConfigHelper.java | 0
.../common/utils/SourceProviderHelper.java | 0
.../integration/common/utils/TestFileUtils.java | 121 +
.../integration/common/utils/VariantHelper.java | 0
.../gradle/integration/common/utils/XmlHelper.java | 59 +
.../gradle/integration/common/utils/ZipHelper.java | 0
.../component/AndroidComponentPluginTest.groovy | 64 +
.../component/AppComponentPluginTest.groovy | 125 +
.../component/BasicNdkComponentTest.groovy | 118 +
.../integration/component/ComponentDslTest.groovy | 95 +
.../component/ComponentInstantRunTest.java | 92 +
.../component/ComponentSigningConfigTest.groovy | 107 +
.../component/ComponentSourceSetTest.groovy | 142 +
.../component/ExternalBuildDependencyTest.groovy | 147 +
.../ExternalNativeComponentPluginTest.groovy | 269 ++
.../component/InvalidDependencyOnAppTest.java | 57 +
.../component/LibraryComponentPluginTest.groovy | 121 +
.../component/NdkComponentModelTest.groovy | 285 ++
.../component/NdkComponentPluginTest.groovy | 74 +
.../component/NdkComponentSplitTest.groovy | 117 +
.../component/NdkComponentVariantTest.groovy | 160 +
.../integration/component/NdkDependencyTest.groovy | 310 ++
.../integration/component/NdkFlagsTest.groovy | 204 ++
.../component/NdkIncrementalTest.groovy | 147 +
.../integration/component/NdkJniLib2Test.groovy | 121 +
.../component/NdkSanAngeles2Test.groovy | 98 +
.../component/NdkStandaloneSoTest.groovy | 187 ++
.../gradle/integration/component/NdkStlTest.groovy | 153 +
.../integration/component/NdkStlVersionTest.java | 70 +
.../integration/component/NdkVariantsTest.groovy | 60 +
.../component/StandaloneNdkModelTest.groovy | 228 ++
.../component/UnitTestingComponentTest.groovy | 104 +
.../databinding/DataBindingIncrementalTest.java | 253 ++
.../integration/databinding/DataBindingTest.java | 110 +
.../dependencies/AppWithClassifierDepTest.java | 98 +
.../dependencies/AppWithCompileDirectJarTest.java | 93 +
.../AppWithCompileIndirectJarTest.java | 103 +
.../dependencies/AppWithCompileLibTest.java | 97 +
.../AppWithCompileLocalAarFromOlderIdeTest.java | 67 +
.../dependencies/AppWithCompileLocalAarTest.java | 76 +
.../dependencies/AppWithIvyDependencyTest.java | 46 +
.../dependencies/AppWithJarDependOnLibTest.java | 74 +
...ithNonExistentResolutionStrategyForAarTest.java | 96 +
.../dependencies/AppWithPackageDirectJarTest.java | 90 +
.../dependencies/AppWithPackageLibTest.java | 68 +
.../dependencies/AppWithPackageLocalAarTest.java | 76 +
.../dependencies/AppWithPackageLocalJarTest.java | 100 +
.../dependencies/AppWithProvidedAarAsJarTest.java | 106 +
.../dependencies/AppWithProvidedLibTest.java | 69 +
.../dependencies/AppWithProvidedLocalAarTest.java | 76 +
.../dependencies/AppWithProvidedLocalJarTest.java | 106 +
.../AppWithProvidedProjectJarTest.java | 91 +
.../dependencies/AppWithProvidedRemoteJarTest.java | 111 +
.../AppWithResolutionStrategyForAarTest.java | 136 +
.../AppWithResolutionStrategyForJarTest.java | 126 +
.../DepOnLocalJarThroughAModuleTest.java | 81 +
.../dependencies/LibWithCompileLocalJarTest.java | 86 +
.../dependencies/LibWithPackageLocalJarTest.java | 99 +
.../dependencies/LibWithProvidedAarAsJarTest.java | 107 +
.../dependencies/LibWithProvidedDirectJarTest.java | 115 +
.../dependencies/LibWithProvidedLocalJarTest.java | 94 +
.../dependencies/LocalJarInAarInModelTest.java | 119 +
.../integration/dependencies/OptionalAarTest.java | 139 +
.../dependencies/TestLibraryWithDep.java | 48 +
.../dependencies/TestWithCompileDirectJarTest.java | 90 +
.../dependencies/TestWithCompileLibTest.java | 89 +
.../TestWithFlavorsWithCompileDirectJarTest.java | 96 +
.../dependencies/TestWithMismatchDep.java | 110 +
.../dependencies/TestWithSameDepAsApp.java | 174 +
.../TestWithSameDepAsAppWithProguard.java | 81 +
.../dependencies/VariantDependencyTest.java | 240 ++
.../build/gradle/integration/dsl/DslTest.groovy | 143 +
.../integration/dsl/TestedVariantTest.groovy | 58 +
.../googleservices/DisabledServiceTest.groovy | 105 +
.../integration/googleservices/FlavorTest.groovy | 149 +
.../googleservices/GoogleServicesTest.groovy | 118 +
.../integration/googleservices/NoClientTest.groovy | 89 +
.../googleservices/NoServiceTest.groovy | 107 +
.../integration/googleservices/TestHelper.java | 0
.../gradle/integration/instant/ColdSwapTest.java | 216 ++
.../integration/instant/ConnectedColdSwapTest.java | 218 ++
.../gradle/integration/instant/DaggerTest.java | 196 ++
.../gradle/integration/instant/HotSwapTest.java | 195 ++
.../gradle/integration/instant/HotSwapTester.java | 153 +
.../instant/InstantRunAddLibraryTest.java | 125 +
.../instant/InstantRunShrinkerTest.java | 110 +
.../integration/instant/InstantRunTestUtils.java | 224 ++
.../integration/instant/JavaResourcesTest.java | 103 +
.../integration/instant/LibDependencyTest.java | 205 ++
.../instant/NativeLibraryInstantRunTest.java | 119 +
.../gradle/integration/library/AidlTest.groovy | 205 ++
.../gradle/integration/library/ApiTest.groovy | 71 +
.../integration/library/ApplibtestTest.groovy | 0
.../gradle/integration/library/AssetsTest.groovy | 127 +
.../integration/library/AttrOrderTest.groovy | 0
.../gradle/integration/library/DslTest.groovy | 121 +
.../integration/library/FlavoredlibTest.groovy | 121 +
.../integration/library/FlavorlibTest.groovy | 128 +
.../GenerateAnnotationsClassPathTest.groovy | 0
.../integration/library/JarJarLibTest.groovy | 167 +
.../integration/library/LibMinifyJarDepTest.groovy | 0
.../integration/library/LibMinifyLibDepTest.groovy | 69 +
.../integration/library/LibMinifyTest.groovy | 65 +
.../library/LibProguardConsumerFilesTest.groovy | 58 +
.../integration/library/LibTestDepTest.groovy | 0
.../gradle/integration/library/LibsTestTest.groovy | 0
.../integration/library/LocalAarTestTest.groovy | 0
.../integration/library/LocalJarsTest.groovy | 0
.../integration/library/MinifyLibTest.groovy | 61 +
.../integration/library/MultiDexWithLibTest.groovy | 60 +
.../integration/library/MultiprojectTest.groovy | 0
.../library/ProguardAarPackagingTest.groovy | 150 +
.../library/RenderscriptInLibTest.groovy | 0
.../integration/library/SameNamedLibsTest.groovy | 0
.../integration/ndk/NdkConnectedCheckTest.groovy | 0
.../gradle/integration/ndk/NdkJniLibTest.groovy | 0
.../integration/ndk/NdkLibPrebuiltsTest.groovy | 0
.../gradle/integration/ndk/NdkModelTest.groovy | 203 ++
.../gradle/integration/ndk/NdkPrebuiltsTest.groovy | 0
.../integration/ndk/NdkSanAngelesTest.groovy | 0
.../integration/ndk/NoSplitNdkVariantsTest.groovy | 143 +
.../gradle/integration/ndk/Pre21SplitTest.groovy | 0
.../packaging/AssetPackagingTest.groovy | 542 +++
.../packaging/IncrementalCodeChangeTest.groovy | 155 +
.../packaging/JavaResPackagingTest.groovy | 554 ++++
.../NativeSoPackagingFromRemoteAarTest.groovy | 217 ++
.../packaging/NativeSoPackagingTest.groovy | 533 +++
.../integration/packaging/ResPackagingTest.groovy | 770 +++++
.../performance/IOScheduleCodeChangeTest.groovy | 82 +
.../performance/IOScheduleResChangeTest.groovy | 70 +
.../LargeVariantAndroidComponentTest.groovy | 80 +
.../performance/LargeVariantAndroidTest.groovy | 73 +
.../MediumAndroidComponentEvaluationTest.groovy | 56 +
.../MediumAndroidComponentModelTest.groovy | 59 +
.../performance/MediumAndroidEvaluationTest.groovy | 0
.../performance/MediumAndroidModelTest.groovy | 0
.../performance/MediumJavaEvaluationTest.groovy | 0
.../MultiProjectsAndroidComponentTest.groovy | 96 +
.../performance/MultiProjectsAndroidTest.groovy | 92 +
.../SmallAndroidComponentEvaluationTest.groovy | 55 +
.../SmallAndroidComponentModelTest.groovy | 56 +
.../performance/SmallAndroidEvaluationTest.groovy | 0
.../performance/SmallAndroidModelTest.groovy | 0
.../performance/SmallJavaEvaluationTest.groovy | 0
.../shrinker/HelloWorldShrinkerTest.groovy | 180 +
.../shrinker/MinifyProjectShrinkerTest.groovy | 60 +
.../integration/shrinker/ShrinkerTestUtils.java | 31 +
.../shrinker/WarningsShrinkerTest.groovy | 208 ++
.../test/DuplicateModuleNameImportTest.groovy | 0
.../integration/test/SeparateTestModuleTest.groovy | 58 +
...eparateTestModuleWithAppDependenciesTest.groovy | 70 +
.../test/SeparateTestWithAarDependencyTest.groovy | 128 +
.../test/SeparateTestWithDependenciesTest.groovy | 56 +
...TestWithMinificationButNoObfuscationTest.groovy | 0
...tWithoutMinificationWithDependenciesTest.groovy | 62 +
.../gradle/integration/testing/JUnitResults.groovy | 0
.../testing/TestingSupportLibraryTest.groovy | 134 +
.../UnitTestingBuildTypesSupportTest.groovy | 0
.../testing/UnitTestingComplexProjectTest.groovy | 48 +
.../testing/UnitTestingDefaultValuesTest.groovy | 48 +
.../testing/UnitTestingFlavorsSupportTest.groovy | 72 +
.../testing/UnitTestingSupportTest.groovy | 0
.../src/test/resources/basic/example.json | 107 +
.../src/test/resources/basic/global_tracker.xml | 4 +
.../src/test/resources/basic/values.xml | 9 +
.../application/SigningTest/dsa_keystore.jks | Bin 0 -> 1300 bytes
.../application/SigningTest/ec_keystore.jks | Bin 0 -> 700 bytes
.../application/SigningTest/rsa_keystore.jks | Bin 0 -> 1264 bytes
.../test/resources/disabledservice/example.json | 107 +
.../resources/disabledservice/global_tracker.xml | 4 +
.../src/test/resources/disabledservice/values.xml | 7 +
.../src/test/resources/flavor/example.json | 102 +
.../test/resources/flavor/free.global_tracker.xml | 4 +
.../src/test/resources/flavor/free.values.xml | 7 +
.../test/resources/flavor/paid.global_tracker.xml | 4 +
.../src/test/resources/flavor/paid.values.xml | 7 +
.../test/resources/noservice/global_tracker.xml | 4 +
.../src/test/resources/noservice/no_services.json | 40 +
.../src/test/resources/noservice/values.xml | 5 +
.../integration-test/test-projects/.gitignore | 0
.../test-projects/3rdPartyTests/app/build.gradle | 44 +
.../app/src/androidTest/AndroidManifest.xml | 0
.../tests/testprojecttest/lib/LibActivityTest.java | 0
.../tests/testprojecttest/test/AllTests.java | 0
.../3rdPartyTests/app/src/main/AndroidManifest.xml | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../app/src/main/res/values/strings.xml | 0
.../test-projects/3rdPartyTests/build.gradle | 0
.../3rdPartyTests/buildSrc/build.gradle | 0
.../tests/basic/buildscript/FakeDevice.java | 0
.../tests/basic/buildscript/FakeProvider.java | 0
.../tests/basic/buildscript/FakeServer.java | 0
.../test-projects/3rdPartyTests/lib/build.gradle | 40 +
.../tests/testprojecttest/lib/LibActivityTest.java | 0
.../tests/testprojecttest/test/AllTests.java | 0
.../lib/src/androidTest/res/values/strings.xml | 0
.../3rdPartyTests/lib/src/main/AndroidManifest.xml | 14 +
.../android/tests/basicprojectwithaidl/ITest.aidl | 0
.../android/tests/basicprojectwithaidl/Rect.aidl | 0
.../tests/testprojecttest/lib/LibActivity.java | 0
.../3rdPartyTests/lib/src/main/res/layout/main.xml | 0
.../lib/src/main/res/values/strings.xml | 0
.../test-projects/3rdPartyTests/settings.gradle | 0
.../test-projects/abiPureSplits/build.gradle | 31 +
.../example/combinedSplits/DemoActivityTest.java | 0
.../abiPureSplits/src/main/AndroidManifest.xml | 0
.../com/example/combinedSplits/DemoActivity.java | 0
.../abiPureSplits/src/main/jni/hello-jni.c | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../abiPureSplits/src/main/res/layout/main.xml | 0
.../abiPureSplits/src/main/res/values/strings.xml | 0
.../androidManifestInTest/build.gradle | 27 +
.../src/androidTest/AndroidManifest.xml | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../src/androidTest/res/values/strings.xml | 0
.../src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable/icon.png | Bin
.../src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/androidTestLibDep/build.gradle | 16 +
.../android/tests/libdeps/MainActivityTest.java | 0
.../androidTestLibDep/src/main/AndroidManifest.xml | 0
.../com/android/tests/libdeps/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/layout/lib1_main.xml | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/api/app/build.gradle | 20 +
.../java/com/android/tests/basic/MainTest.java | 0
.../api/app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../api/app}/src/main/res/drawable/icon.png | Bin
.../api/app/src/main/res/layout/main.xml | 0
.../api/app/src/main/res/values/strings.xml | 0
.../api/app/src/release/res/values/strings.xml | 0
.../test-projects/api/build.gradle | 0
.../test-projects/api/lib/blah/foo.txt | 0
.../test-projects/api/lib/build.gradle | 29 +
.../tests/libstest/lib2/MainActivityTest.java | 0
.../api/lib/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib2/Lib2.java | 0
.../android/tests/libstest/lib2/MainActivity.java | 0
.../lib/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../lib/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../lib/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../api/lib/src/main/res/layout/lib2_main.xml | 0
.../api/lib/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib2/Lib2.txt | 0
.../test-projects/api/settings.gradle | 0
.../test-projects/applibtest/app/build.gradle | 13 +
.../applibtest/app}/proguard-project.txt | 0
.../app/src/androidTest/AndroidManifest.xml | 0
.../tests/testprojecttest/lib/LibActivityTest.java | 0
.../tests/testprojecttest/test/AllTests.java | 0
.../applibtest/app/src/main/AndroidManifest.xml | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../applibtest/app/src/main/res/values/strings.xml | 0
.../test-projects/applibtest/build.gradle | 0
.../test-projects/applibtest/lib/build.gradle | 18 +
.../applibtest/lib}/proguard-project.txt | 0
.../tests/testprojecttest/lib/LibActivityTest.java | 0
.../tests/testprojecttest/test/AllTests.java | 0
.../lib/src/androidTest/res/values/strings.xml | 0
.../applibtest/lib/src/main/AndroidManifest.xml | 0
.../android/tests/basicprojectwithaidl/ITest.aidl | 0
.../android/tests/basicprojectwithaidl/Rect.aidl | 0
.../tests/testprojecttest/lib/LibActivity.java | 0
.../applibtest/lib/src/main/res/layout/main.xml | 0
.../applibtest/lib/src/main/res/values/strings.xml | 0
.../test-projects/applibtest/settings.gradle | 0
.../applicationIdInLibsTest/app/build.gradle | 33 +
.../app/src/flavor/AndroidManifest.xml | 0
.../app/src/main/AndroidManifest.xml | 0
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../app/src/main/res/layout/activity_main.xml | 0
.../app/src/main/res/menu/menu_main.xml | 0
.../app/src/main/res/values/dimens.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/main/res/values/styles.xml | 0
.../applicationIdInLibsTest/build.gradle | 0
.../examplelibrary/build.gradle | 18 +
.../example/examplelibrary/ApplicationTest.java | 0
.../examplelibrary/src/main/AndroidManifest.xml | 0
.../com/example/examplelibrary/LoginActivity.java | 0
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../examplelibrary/src/main/res/values/strings.xml | 0
.../applicationIdInLibsTest/settings.gradle | 0
.../test-projects/artifactApi/build.gradle | 142 +
.../artifactApi/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/overlay2/Main.java | 0
.../artifactApi}/src/main/res/drawable/icon.png | Bin
.../src/main/res/drawable/no_overlay.png | Bin
.../artifactApi/src/main/res/layout/main.xml | 0
.../artifactApi/src/main/res/values/strings.xml | 0
.../test-projects/assets/app/build.gradle | 10 +
.../test-projects/assets/app/proguard-project.txt | 0
.../android/tests/assets/app/MainActivityTest.java | 0
.../assets/app/src/main/AndroidManifest.xml | 0
.../assets/app/src/main/assets/App.txt | 0
.../java/com/android/tests/assets/app/App.java | 0
.../com/android/tests/assets/app/MainActivity.java | 0
.../assets/app/src/main/res/drawable-hdpi/icon.png | Bin
.../assets/app/src/main/res/drawable-ldpi/icon.png | Bin
.../app/src/main/res/drawable-mdpi}/icon.png | Bin
.../assets/app/src/main/res/layout/main.xml | 0
.../assets/app/src/main/res/values/strings.xml | 0
.../test-projects/assets/build.gradle | 0
.../test-projects/assets/lib/build.gradle | 6 +
.../test-projects/assets}/lib/proguard-project.txt | 0
.../android/tests/assets/lib/MainActivityTest.java | 0
.../assets/lib/src/main/AndroidManifest.xml | 0
.../assets/lib/src/main/assets/Lib.txt | 0
.../java/com/android/tests/assets/lib/Lib.java | 0
.../com/android/tests/assets/lib/MainActivity.java | 0
.../lib/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../lib/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../lib/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../assets/lib/src/main/res/layout/lib_main.xml | 0
.../assets/lib/src/main/res/values/strings.xml | 0
.../test-projects/assets/settings.gradle | 0
.../test-projects/attrOrder/app/build.gradle | 11 +
.../java/com/android/tests/basic/MainTest.java | 0
.../attrOrder/app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../attrOrder/app/src/main/res/drawable}/icon.png | Bin
.../attrOrder/app/src/main/res/layout/main.xml | 0
.../attrOrder/app/src/main/res/values/strings.xml | 0
.../app/src/main/res/values/strings_additional.xml | 0
.../test-projects/attrOrder/build.gradle | 0
.../test-projects/attrOrder/lib/build.gradle | 7 +
.../lib/src/androidTest/AndroidManifest.xml | 0
.../tests/libstest/lib/test/TestActivity.java | 0
.../tests/libstest/lib/test/TestActivityTest.java | 0
.../lib/src/androidTest}/res/drawable/icon.png | Bin
.../lib/src/androidTest/res/layout/main.xml | 0
.../lib/src/androidTest/res/values/strings.xml | 0
.../androidTest/res/values/strings_additional.xml | 0
.../attrOrder/lib/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib/Lib.java | 0
.../lib/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../lib/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../lib/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib/src/main/res/layout/lib2_main.xml | 0
.../attrOrder/lib/src/main/res/values/strings.xml | 0
.../lib/src/main/res/values/styleable.xml | 0
.../test-projects/attrOrder/settings.gradle | 0
.../test-projects/basic/build.gradle | 121 +
.../java/com/android/tests/basic/MainTest.java | 0
.../basic/src/main/AndroidManifest.xml | 0
.../test-projects/basic/src/main/assets/notice.txt | 0
.../main/java/com/android/tests/basic/Base.java | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../java/com/android/tests/basic/SomeClass.java | 0
.../test-projects/basic/src/main/res/.vcs/INDEX | 0
.../basic/src/main}/res/drawable/icon.png | Bin
.../basic/src/main/res/layout/main.xml | 0
.../basic/src/main/res/raw/notice.txt | 0
.../basic/src/main/res/values/strings.xml | 0
.../basic/src/release/res/values/strings.xml | 0
.../test-projects/basicMultiFlavors/build.gradle | 30 +
.../src/f1/res/values/strings.xml | 0
.../src/f1Staging/res/values/strings.xml | 0
.../basicMultiFlavors/src/f2/AndroidManifest.xml | 0
.../com/android/tests/flavored/OtherActivity.java | 0
.../basicMultiFlavors/src/f2/res/layout/main2.xml | 0
.../src/f2/res/values/strings.xml | 0
.../basicMultiFlavors/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/flavored/Main.java | 0
.../src/main/res/drawable/icon.png | Bin
.../basicMultiFlavors/src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../integration-test/test-projects/build.gradle | 0
.../combinedAbiDensityPureSplits/build.gradle | 35 +
.../example/combinedSplits/DemoActivityTest.java | 0
.../src/main/AndroidManifest.xml | 0
.../com/example/combinedSplits/DemoActivity.java | 0
.../src/main/jni/hello-jni.c | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../build.gradle | 29 +
.../java/com/android/tests/basic/MainTest.java | 0
.../src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../src/main/res/drawable/ic_launcher.png | Bin
.../src/main/res/layout/main.xml | 0
.../src/main/res/values-en/strings.xml | 0
.../src/main/res/values-fr-rBE/strings.xml | 0
.../src/main/res/values-fr-rCA/strings.xml | 0
.../src/main/res/values-fr/strings.xml | 0
.../src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../test-projects/commonBuildScript.gradle | 0
.../commonBuildScriptExperimental.gradle | 0
.../test-projects/commonGradlePluginVersion.gradle | 43 +
.../test-projects/commonHeader.gradle | 60 +
.../test-projects/commonLocalRepo.gradle | 34 +
.../test-projects/componentModel/build.gradle | 48 +
.../java/com/android/tests/basic/MainTest.java | 0
.../componentModel/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../componentModel}/src/main/res/drawable/icon.png | Bin
.../componentModel/src/main/res/layout/main.xml | 0
.../componentModel/src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../customArtifactDep/app/build.gradle | 10 +
.../app/src/main/AndroidManifest.xml | 0
.../example/android/multiproject/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../test-projects/customArtifactDep/build.gradle | 0
.../customArtifactDep/settings.gradle | 0
.../customArtifactDep/util/build.gradle | 0
.../java/com/example/android/custom/Foo.java | 0
.../test-projects/daggerOne/build.gradle | 37 +
.../daggerOne/src/main/AndroidManifest.xml | 28 +
.../src/main/java/com/android/tests/AppModule.java | 26 +
.../main/java/com/android/tests/MainActivity.java | 33 +
.../main/java/com/android/tests/SomeService.java | 10 +
.../test-projects/daggerTwo/build.gradle | 50 +
.../daggerTwo/src/main/AndroidManifest.xml | 28 +
.../main/java/com/android/tests/AppComponent.java | 22 +
.../src/main/java/com/android/tests/AppModule.java | 26 +
.../main/java/com/android/tests/MainActivity.java | 35 +
.../main/java/com/android/tests/SomeService.java | 10 +
.../databinding/build.forexperimental.gradle | 33 +
.../test-projects/databinding/build.gradle | 29 +
.../build.library-forexperimental.gradle | 33 +
....library-withoutadapters-forexperimental.gradle | 33 +
.../build.library-withoutadapters.gradle | 29 +
.../test-projects/databinding/build.library.gradle | 29 +
.../build.withoutadapters-forexperimental.gradle | 33 +
.../databinding/build.withoutadapters.gradle | 29 +
.../databinding/src/main/AndroidManifest.xml | 37 +
.../android/databinding/testapp/MainActivity.java | 61 +
.../src/main/res/drawable/ic_launcher.xml | 18 +
.../src/main/res/layout/activity_main.xml | 33 +
.../databinding/src/main/res/menu/menu_main.xml | 21 +
.../databinding/src/main/res/values-v21/styles.xml | 21 +
.../src/main/res/values-w820dp/dimens.xml | 22 +
.../databinding/src/main/res/values/dimens.xml | 21 +
.../databinding/src/main/res/values/strings.xml | 24 +
.../databinding/src/main/res/values/styles.xml | 24 +
.../build.forexperimental.gradle | 33 +
.../databindingIncremental/build.gradle | 29 +
.../src/main/AndroidManifest.xml | 36 +
.../android/databinding/testapp/MainActivity.java | 61 +
.../src/main/res/drawable/ic_launcher.xml | 18 +
.../src/main/res/layout/activity_main.xml | 35 +
.../src/main/res/menu/menu_main.xml | 21 +
.../src/main/res/values-v21/styles.xml | 21 +
.../src/main/res/values-w820dp/dimens.xml | 22 +
.../src/main/res/values/dimens.xml | 21 +
.../src/main/res/values/strings.xml | 24 +
.../src/main/res/values/styles.xml | 24 +
.../test-projects/densitySplit/build.gradle | 45 +
.../java/com/android/tests/basic/MainTest.java | 0
.../densitySplit/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/other.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../densitySplit/src/main/res/layout/main.xml | 0
.../densitySplit/src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../test-projects/densitySplitInL/build.gradle | 25 +
.../java/com/android/tests/basic/MainTest.java | 0
.../densitySplitInL/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../src/main/res/drawable/ic_launcher.png | Bin
.../densitySplitInL/src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../test-projects/dependencies/build.gradle | 18 +
.../dependencies/jarProject/build.gradle | 0
.../tests/dependencies/jar/StringHelper.java | 0
.../dependencies/jarProject2/build.gradle | 0
.../tests/dependencies/jar/StringHelper2.java | 0
.../test-projects/dependencies/settings.gradle | 0
.../tests/dependencies/MainActivityTest.java | 0
.../dependencies/src/main/AndroidManifest.xml | 0
.../android/tests/dependencies/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../dependencies/src/main/res/layout/main.xml | 0
.../dependencies/src/main/res/values/strings.xml | 0
.../dependenciesWithVariants/build.gradle | 17 +
.../jarProject/build.gradle | 0
.../tests/dependencies/jar/StringHelper.java | 0
.../jarProject2/build.gradle | 0
.../tests/dependencies/jar/StringHelper2.java | 0
.../dependenciesWithVariants/settings.gradle | 0
.../tests/dependencies/MainActivityTest.java | 0
.../src/main/AndroidManifest.xml | 0
.../android/tests/dependencies/MainActivity.java | 0
.../src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../duplicateNameImport/A/Project/build.gradle | 0
.../A/Project/src/main/AndroidManifest.xml | 0
.../duplicateNameImport/B/Project/build.gradle | 0
.../B/Project/src/main/AndroidManifest.xml | 0
.../test-projects/duplicateNameImport/build.gradle | 26 +
.../duplicateNameImport/settings.gradle | 0
.../test-projects/embedded/build.gradle | 0
.../test-projects/embedded/main/build.gradle | 29 +
.../java/com/android/tests/basic/MainTest.java | 0
.../embedded/main}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../embedded/main}/src/main/res/drawable/icon.png | Bin
.../embedded/main}/src/main/res/layout/main.xml | 0
.../embedded/main}/src/main/res/values/strings.xml | 0
.../embedded/micro-apps/custom/build.gradle | 15 +
.../com/android/tests/basic/custom/MainTest.java | 38 +
.../micro-apps/custom/src/main/AndroidManifest.xml | 14 +
.../java/com/android/tests/basic/custom/Main.java | 0
.../custom}/src/main/res/drawable/icon.png | Bin
.../custom}/src/main/res/layout/main.xml | 0
.../custom}/src/main/res/values/strings.xml | 0
.../embedded/micro-apps/default/build.gradle | 15 +
.../java/com/android/tests/basic/MainTest.java | 0
.../default/src/main/AndroidManifest.xml | 14 +
.../main/java/com/android/tests/basic/Main.java | 0
.../default}/src/main/res/drawable/icon.png | Bin
.../default}/src/main/res/layout/main.xml | 0
.../default}/src/main/res/values/strings.xml | 0
.../embedded/micro-apps/flavor1/build.gradle | 15 +
.../java/com/android/tests/basic/MainTest.java | 0
.../flavor1/src/main/AndroidManifest.xml | 14 +
.../main/java/com/android/tests/basic/Main.java | 0
.../flavor1}/src/main/res/drawable/icon.png | Bin
.../flavor1}/src/main/res/layout/main.xml | 0
.../flavor1}/src/main/res/values/strings.xml | 0
.../test-projects/embedded/settings.gradle | 0
.../test-projects/emptySplit/build.gradle | 21 +
.../emptySplit/src/main/AndroidManifest.xml | 0
.../test-projects/extractAnnotations/build.gradle | 23 +
.../src/main/AndroidManifest.xml | 0
.../tests/extractannotations/Constants.java | 0
.../tests/extractannotations/ExtractTest.java | 0
.../tests/extractannotations/TopLevelTypeDef.java | 0
.../extractRsEnabledAnnotations/build.gradle | 22 +
.../src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib1/Lib1.java | 0
.../android/tests/libstest/lib1/MainActivity.java | 0
.../src/main/res/layout/lib1_main.xml | 0
.../src/main/rs/blend.rs | 0
.../extractRsEnabledAnnotations/src/main/rs/ip.rsh | 0
.../filteredOutBuildType/build.gradle | 16 +
.../java/com/android/tests/basic/MainTest.java | 0
.../src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable/icon.png | Bin
.../src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/filteredOutVariants/build.gradle | 37 +
.../java/com/android/tests/basic/MainTest.java | 0
.../src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable/icon.png | Bin
.../src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/flavored/build.gradle | 50 +
.../test-projects/flavored}/debug.keystore | Bin
.../java/com/android/tests/flavored/MainTest.java | 0
.../android/tests/flavored/OtherActivityTest.java | 0
.../flavored/src/f1/res/values/strings.xml | 0
.../flavored/src/f1Staging/res/values/strings.xml | 0
.../flavored/src/f2/AndroidManifest.xml | 0
.../com/android/tests/flavored/OtherActivity.java | 0
.../flavored/src/f2/res/layout/main2.xml | 0
.../flavored/src/f2/res/values/strings.xml | 0
.../flavored/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/flavored/Main.java | 0
.../flavored}/src/main/res/drawable/icon.png | Bin
.../flavored/src/main/res/layout/main.xml | 0
.../flavored/src/main/res/values/strings.xml | 0
.../test-projects/flavoredlib/app/build.gradle | 25 +
.../flavoredlib/app/proguard-project.txt | 0
.../tests/flavorlib/app/MainActivityTest.java | 0
.../flavorlib/app/MainActivityFlavorTest.java | 0
.../flavorlib/app/MainActivityFlavorTest.java | 0
.../android/tests/flavorlib/app/LibWrapper.java | 0
.../app/src/flavor1/res/values/strings.xml | 0
.../android/tests/flavorlib/app/LibWrapper.java | 0
.../app/src/flavor2/res/values/strings.xml | 0
.../flavoredlib/app/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/flavorlib/app/App.java | 0
.../android/tests/flavorlib/app/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/icon.png | Bin
.../app/src/main/res/drawable-ldpi/icon.png | Bin
.../app/src/main/res/drawable-mdpi}/icon.png | Bin
.../flavoredlib/app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../com/android/tests/flavorlib/app/App.txt | 0
.../test-projects/flavoredlib/build.gradle | 0
.../test-projects/flavoredlib/lib/build.gradle | 18 +
.../flavoredlib}/lib/proguard-project.txt | 0
.../flavorlib/lib/flavor1/MainActivityTest.java | 0
.../flavorlib/lib/flavor2/MainActivityTest.java | 0
.../lib/src/flavor1/AndroidManifest.xml | 16 +
.../android/tests/flavorlib/lib/flavor1/Lib.java | 0
.../tests/flavorlib/lib/flavor1/MainActivity.java | 0
.../lib/src/flavor1/res/layout/lib_main.xml | 0
.../lib/src/flavor1/res/values/strings.xml | 0
.../android/tests/flavorlib/lib/flavor1/Lib.txt | 0
.../lib/src/flavor2/AndroidManifest.xml | 16 +
.../android/tests/flavorlib/lib/flavor2/Lib.java | 0
.../tests/flavorlib/lib/flavor2/MainActivity.java | 0
.../lib/src/flavor2/res/layout/lib_main.xml | 0
.../lib/src/flavor2/res/values/strings.xml | 0
.../android/tests/flavorlib/lib/flavor2/Lib.txt | 0
.../flavoredlib/lib/src/main/AndroidManifest.xml | 0
.../test-projects/flavoredlib/settings.gradle | 0
.../test-projects/flavorlib/app/build.gradle | 25 +
.../flavorlib/app/proguard-project.txt | 0
.../tests/flavorlib/app/MainActivityTest.java | 0
.../flavorlib/app/MainActivityFlavorTest.java | 0
.../flavorlib/app/MainActivityFlavorTest.java | 0
.../app/src/flavor1/res/values/strings.xml | 0
.../app/src/flavor2/res/values/strings.xml | 0
.../flavorlib/app/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/flavorlib/app/App.java | 0
.../android/tests/flavorlib/app/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/icon.png | Bin
.../app/src/main/res/drawable-ldpi/icon.png | Bin
.../app/src/main/res/drawable-mdpi}/icon.png | Bin
.../flavorlib/app/src/main/res/layout/main.xml | 0
.../flavorlib/app/src/main/res/values/strings.xml | 0
.../com/android/tests/flavorlib/app/App.txt | 0
.../test-projects/flavorlib/build.gradle | 0
.../test-projects/flavorlib/lib1/build.gradle | 6 +
.../flavorlib/lib1}/proguard-project.txt | 0
.../tests/flavorlib/lib/MainActivityTest.java | 0
.../flavorlib/lib1/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/flavorlib/lib/Lib.java | 0
.../android/tests/flavorlib/lib/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib1/src/main/res/layout/lib_main.xml | 0
.../flavorlib/lib1/src/main/res/values/strings.xml | 0
.../com/android/tests/flavorlib/lib/Lib.txt | 0
.../test-projects/flavorlib/lib2/build.gradle | 6 +
.../flavorlib/lib2}/proguard-project.txt | 0
.../tests/flavorlib/lib/MainActivityTest.java | 0
.../flavorlib/lib2/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/flavorlib/lib/Lib.java | 0
.../android/tests/flavorlib/lib/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib2/src/main/res/layout/lib_main.xml | 0
.../flavorlib/lib2/src/main/res/values/strings.xml | 0
.../com/android/tests/flavorlib/lib/Lib.txt | 0
.../test-projects/flavorlib/settings.gradle | 0
.../flavorlibWithFailedTests/app/build.gradle | 20 +
.../app/proguard-project.txt | 0
.../tests/flavorlib/app/MainActivityTest.java | 0
.../flavorlib/app/MainActivityFlavorTest.java | 0
.../flavorlib/app/MainActivityFlavorTest.java | 0
.../app/src/flavor1/res/values/strings.xml | 0
.../app/src/flavor2/res/values/strings.xml | 0
.../app/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/flavorlib/app/App.java | 0
.../android/tests/flavorlib/app/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/icon.png | Bin
.../app/src/main/res/drawable-ldpi/icon.png | Bin
.../app/src/main/res/drawable-mdpi/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../com/android/tests/flavorlib/app/App.txt | 0
.../flavorlibWithFailedTests/build.gradle | 0
.../flavorlibWithFailedTests/lib1/build.gradle | 6 +
.../lib1}/proguard-project.txt | 0
.../tests/flavorlib/lib/MainActivityTest.java | 0
.../lib1/src/main/AndroidManifest.xml | 19 +
.../java/com/android/tests/flavorlib/lib/Lib.java | 0
.../android/tests/flavorlib/lib/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib1/src/main/res/layout/lib_main.xml | 0
.../lib1/src/main/res/values/strings.xml | 0
.../com/android/tests/flavorlib/lib/Lib.txt | 0
.../flavorlibWithFailedTests/lib2/build.gradle | 6 +
.../lib2}/proguard-project.txt | 0
.../tests/flavorlib/lib/MainActivityTest.java | 0
.../lib2/src/main/AndroidManifest.xml | 19 +
.../java/com/android/tests/flavorlib/lib/Lib.java | 0
.../android/tests/flavorlib/lib/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib2/src/main/res/layout/lib_main.xml | 0
.../lib2/src/main/res/values/strings.xml | 0
.../com/android/tests/flavorlib/lib/Lib.txt | 0
.../flavorlibWithFailedTests/settings.gradle | 0
.../test-projects/flavors/build.gradle | 27 +
.../test-projects/flavors/proguard-project.txt | 0
.../tests/flavors/MainActivityCustomizedTest.java | 0
.../tests/flavors/MainActivityGroup1Test.java | 0
.../tests/flavors/MainActivityGroup1Test.java | 0
.../tests/flavors/MainActivityGroup2Test.java | 0
.../tests/flavors/MainActivityGroup2Test.java | 0
.../android/tests/flavors/group1/SomeClass.java | 0
.../flavors/src/f1/res/values/strings.xml | 0
.../com/android/tests/flavors/CustomizedClass.java | 0
.../com/android/tests/flavors/CustomizedClass.java | 0
.../android/tests/flavors/group1/SomeClass.java | 0
.../flavors/src/f2/res/values/strings.xml | 0
.../com/android/tests/flavors/CustomizedClass.java | 0
.../com/android/tests/flavors/CustomizedClass.java | 0
.../com/android/tests/flavors/CustomizedClass.java | 0
.../android/tests/flavors/group2/SomeClass.java | 0
.../flavors/src/fa/res/values/strings.xml | 0
.../android/tests/flavors/group2/SomeClass.java | 0
.../flavors/src/fb/res/values/strings.xml | 0
.../flavors/src/main/AndroidManifest.xml | 0
.../com/android/tests/flavors/MainActivity.java | 0
.../flavors/src/main/res/drawable-hdpi/icon.png | Bin
.../flavors/src/main/res/drawable-ldpi/icon.png | Bin
.../flavors}/src/main/res/drawable-mdpi/icon.png | Bin
.../flavors/src/main/res/layout/main.xml | 0
.../flavors/src/main/res/values/strings.xml | 0
.../test-projects/genFolderApi/build.gradle | 64 +
.../genFolderApi}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../genFolderApi/src/main/res/drawable}/icon.png | Bin
.../genFolderApi/src/main/res/layout/main.xml | 0
.../genFolderApi/src/main/res/values/strings.xml | 0
.../test-projects/genFolderApi2/build.gradle | 27 +
.../genFolderApi2}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 18 +
.../genFolderApi2/src/main/res/drawable}/icon.png | Bin
.../genFolderApi2/src/main/res/layout/main.xml | 0
.../genFolderApi2/src/main/res/values/strings.xml | 0
.../test-projects/instant-unit-tests/build.gradle | 154 +
.../incremental/fixture/ClassEnhancement.java | 253 ++
.../src/main/AndroidManifest.xml | 24 +
.../src/main/resources/dummyfile.txt | 0
.../app/build.gradle | 32 +
.../app/src/main/AndroidManifest.xml | 34 +
.../com/test/jacoco/annotation/MainActivity.java | 16 +
.../app/src/main/res/layout/main_layout.xml | 11 +
.../app/src/main}/res/mipmap-mdpi/ic_launcher.png | Bin
.../app/src/main/res/values/strings.xml | 19 +
.../build.gradle | 20 +
.../settings.gradle | 17 +
.../jacocoWithButterKnife/build.gradle | 24 +
.../src/main/AndroidManifest.xml | 18 +
.../com/test/jacoco/annotation/BindActivity.java | 20 +
.../src/main/res/layout/main_layout.xml | 11 +
.../src/main}/res/mipmap-mdpi/ic_launcher.png | Bin
.../src/main/res/values/strings.xml | 3 +
.../test-projects/jarjarIntegration/build.gradle | 30 +
.../jarjarIntegration/buildSrc/build.gradle | 15 +
.../com/android/test/jarjar/JarJarTransform.java | 222 ++
.../jarjarIntegration/proguard-rules.pro | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../jarjarIntegration/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/other.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../jarjarIntegrationLib/build.gradle | 28 +
.../jarjarIntegrationLib/buildSrc/build.gradle | 15 +
.../com/android/test/jarjar/JarJarTransform.java | 234 ++
.../jarjarIntegrationLib}/proguard-rules.pro | 0
.../src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 26 +
.../src/main/res/drawable}/ic_launcher.png | Bin
.../src/main/res/drawable}/other.png | Bin
.../src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/jarjarWithJack/build.gradle | 37 +
.../test-projects/jarjarWithJack/jarjar.rules | 0
.../jarjarWithJack}/proguard-rules.pro | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../jarjarWithJack}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/other.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../jarjarWithJack/src/main/res/layout/main.xml | 0
.../jarjarWithJack/src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../test-projects/libDependency/app/build.gradle | 13 +
.../libDependency}/app/proguard-project.txt | 0
.../tests/libstest/app/MainActivityTest.java | 90 +
.../libDependency/app/src/main/AndroidManifest.xml | 26 +
.../java/com/android/tests/libstest/app/App.java | 41 +
.../android/tests/libstest/app/MainActivity.java | 19 +
.../app/src/main/res/drawable-hdpi/icon.png | Bin
.../app/src/main/res/drawable-ldpi/icon.png | Bin
.../app/src/main/res/drawable-mdpi}/icon.png | Bin
.../libDependency/app/src/main/res/layout/main.xml | 21 +
.../app/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/app/App.txt | 0
.../test-projects/libDependency}/build.gradle | 0
.../test-projects/libDependency/lib/build.gradle | 15 +
.../libDependency/lib}/proguard-project.txt | 0
.../tests/libstest/lib/MainActivityTest.java | 81 +
.../libDependency/lib/src/main/AndroidManifest.xml | 19 +
.../java/com/android/tests/libstest/lib/Lib.java | 7 +
.../android/tests/libstest/lib/MainActivity.java | 17 +
.../src/main/res/drawable-hdpi/lib_ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/lib_ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/lib_ic_launcher.png | Bin
.../lib/src/main/res/layout/lib_main.xml | 12 +
.../lib/src/main/res/values/strings.xml | 7 +
.../com/android/tests/libstest/lib/Lib.txt | 1 +
.../test-projects/libDependency}/settings.gradle | 0
.../test-projects/libMinify/build.gradle | 23 +
.../test-projects/libMinify/config.pro | 0
.../libMinify/src/main/AndroidManifest.xml | 0
.../com/android/tests/basic/StringProvider.java | 0
.../test-projects/libMinifyJarDep/app/build.gradle | 33 +
.../test-projects/libMinifyJarDep/app/config.pro | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../app/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app}/src/main/res/values/strings.xml | 0
.../test-projects/libMinifyJarDep/build.gradle | 0
.../test-projects/libMinifyJarDep/lib/build.gradle | 30 +
.../test-projects/libMinifyJarDep/lib/config.pro | 0
.../com/andriod/tests/basic/StringGetterTest.java | 0
.../lib/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/StringGetter.java | 0
.../test-projects/libMinifyJarDep}/settings.gradle | 0
.../libMinifyJarDep/util/build.gradle | 0
.../android/multiproject/person/People.java | 0
.../android/multiproject/person/Person.java | 0
.../test-projects/libMinifyLibDep/app/build.gradle | 31 +
.../test-projects/libMinifyLibDep/app/config.pro | 0
.../java/com/android/tests/basic/MainTest.java | 115 +
.../app}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../app/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../test-projects/libMinifyLibDep}/build.gradle | 0
.../test-projects/libMinifyLibDep/lib/build.gradle | 28 +
.../test-projects/libMinifyLibDep/lib/config.pro | 0
.../libMinifyLibDep/lib/consumerRules.pro | 0
.../com/android/tests/basic/StringGetterTest.java | 0
.../lib/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/StringGetter.java | 27 +
.../tests/internal/StringGetterInternal.java | 31 +
.../libMinifyLibDep/lib2/build.gradle | 23 +
.../test-projects/libMinifyLibDep/lib2/config.pro | 0
.../android/tests/basic/StringProviderTest.java | 0
.../lib2/src/main/AndroidManifest.xml | 0
.../com/android/tests/basic/StringProvider.java | 0
.../test-projects/libMinifyLibDep/settings.gradle | 0
.../test-projects/libProguardConsumerFiles/A.txt | 0
.../test-projects/libProguardConsumerFiles/B.txt | 0
.../test-projects/libProguardConsumerFiles/C.txt | 0
.../libProguardConsumerFiles/build.gradle | 29 +
.../libProguardConsumerFiles/config.pro | 0
.../src/main/AndroidManifest.xml | 0
.../com/android/tests/basic/StringProvider.java | 0
.../test-projects/libTestDep/build.gradle | 17 +
.../android/tests/libdeps/MainActivityTest.java | 0
.../libTestDep/src/main/AndroidManifest.xml | 0
.../com/android/tests/libdeps/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../libTestDep/src/main/res/layout/lib1_main.xml | 0
.../libTestDep/src/main/res/values/strings.xml | 0
.../test-projects/libsTest/app/build.gradle | 15 +
.../libsTest}/app/proguard-project.txt | 0
.../tests/libstest/app/MainActivityTest.java | 0
.../libsTest/app/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/app/App.java | 0
.../android/tests/libstest/app/MainActivity.java | 0
.../app}/src/main/res/drawable-hdpi/icon.png | Bin
.../app}/src/main/res/drawable-ldpi/icon.png | Bin
.../app/src/main/res/drawable-mdpi/icon.png | Bin
.../libsTest/app/src/main/res/layout/main.xml | 0
.../libsTest}/app/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/app/App.txt | 0
.../test-projects/libsTest}/build.gradle | 0
.../test-projects/libsTest/lib1/build.gradle | 16 +
.../libsTest/lib1/proguard-project.txt | 0
.../tests/libstest/lib1/MainActivityTest.java | 0
.../libsTest/lib1/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib1/Lib1.java | 0
.../android/tests/libstest/lib1/MainActivity.java | 0
.../main/res/drawable-hdpi/lib1_ic_launcher.png | Bin
.../main/res/drawable-ldpi/lib1_ic_launcher.png | Bin
.../main/res/drawable-mdpi/lib1_ic_launcher.png | Bin
.../lib1/src/main/res/layout/lib1_main.xml | 0
.../libsTest/lib1/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib1/Lib1.txt | 0
.../test-projects/libsTest/lib2/build.gradle | 15 +
.../libsTest/lib2/proguard-project.txt | 0
.../tests/libstest/lib2/MainActivityTest.java | 0
.../libsTest/lib2/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib2/Lib2.java | 0
.../android/tests/libstest/lib2/MainActivity.java | 0
.../main/res/drawable-hdpi/lib2_ic_launcher.png | Bin
.../main/res/drawable-ldpi/lib2_ic_launcher.png | Bin
.../main/res/drawable-mdpi/lib2_ic_launcher.png | Bin
.../lib2/src/main/res/layout/lib2_main.xml | 0
.../libsTest/lib2/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib2/Lib2.txt | 0
.../test-projects/libsTest/lib2b/build.gradle | 7 +
.../libsTest/lib2b/proguard-project.txt | 0
.../tests/libstest/lib2/MainActivity2bTest.java | 0
.../libsTest/lib2b/src/main/AndroidManifest.xml | 0
.../com/android/tests/libstest/lib2/Lib2b.java | 0
.../tests/libstest/lib2/MainActivity2b.java | 0
.../main/res/drawable-hdpi/lib2b_ic_launcher.png | Bin
.../main/res/drawable-ldpi/lib2b_ic_launcher.png | Bin
.../main/res/drawable-mdpi/lib2b_ic_launcher.png | Bin
.../lib2b/src/main/res/layout/lib2b_main.xml | 0
.../libsTest/lib2b/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib2/Lib2b.txt | 0
.../test-projects/libsTest/libapp/build.gradle | 7 +
.../libsTest/libapp/proguard-project.txt | 0
.../libstest/libapp/MainActivityLibAppTest.java | 0
.../libsTest/libapp/src/main/AndroidManifest.xml | 0
.../com/android/tests/libstest/app/LibApp.java | 0
.../tests/libstest/app/MainActivityLibApp.java | 0
.../main/res/drawable-hdpi/libapp_ic_launcher.png | Bin
.../main/res/drawable-ldpi/libapp_ic_launcher.png | Bin
.../main/res/drawable-mdpi/libapp_ic_launcher.png | Bin
.../libapp/src/main/res/layout/libapp_main.xml | 0
.../libapp/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/app/Libapp.txt | 0
.../test-projects/libsTest/settings.gradle | 0
.../test-projects/localAarTest/app/build.gradle | 13 +
.../localAarTest/app}/proguard-project.txt | 0
.../localAarTest/app/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/app/App.java | 0
.../android/tests/libstest/app/MainActivity.java | 0
.../localAarTest/app/src/main/res/layout/main.xml | 0
.../app}/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/app/App.txt | 0
.../test-projects/localAarTest}/build.gradle | 0
.../test-projects/localAarTest/lib1/build.gradle | 17 +
.../test-projects/localAarTest/lib1/lib1.aar | Bin
.../localAarTest/lib1/src/main/AndroidManifest.xml | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../test-projects/localAarTest/settings.gradle | 0
.../test-projects/localJars/app/build.gradle | 15 +
.../localJars/app/src/main/AndroidManifest.xml | 0
.../example/android/multiproject/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../localJars/app/src/main/res/layout/main.xml | 0
.../localJars/app/src/main/res/values/strings.xml | 0
.../localJars/baseLibrary/build.gradle | 15 +
.../baseLibrary/src/main/AndroidManifest.xml | 0
.../android/multiproject/library/PersonView.java | 0
.../test-projects/localJars/build.gradle | 0
.../test-projects/localJars/library/build.gradle | 10 +
.../localJars/library/src/main/AndroidManifest.xml | 0
.../multiproject/library/ShowPeopleActivity.java | 0
.../library/src/main/res/values/strings.xml | 0
.../test-projects/localJars/settings.gradle | 0
.../test-projects/localJars/util/build.gradle | 0
.../android/multiproject/person/People.java | 0
.../android/multiproject/person/Person.java | 0
.../util/src/main/resources/META-INF/exclude.txt | 0
.../util/src/main/resources/META-INF/foo.txt | 0
.../resources/META-INF/services/com.foo.MyService | 0
.../test-projects/mavenLocal/.gitignore | 0
.../test-projects/mavenLocal/app/build.gradle | 18 +
.../mavenLocal/app/src/main/AndroidManifest.xml | 0
.../example/android/multiproject/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../mavenLocal/app/src/main/res/layout/main.xml | 0
.../mavenLocal/app/src/main/res/values/strings.xml | 0
.../mavenLocal/baseLibrary/build.gradle | 29 +
.../baseLibrary/src/main/AndroidManifest.xml | 0
.../android/multiproject/library/PersonView.java | 0
.../test-projects/mavenLocal/library/build.gradle | 28 +
.../library/src/main/AndroidManifest.xml | 0
.../multiproject/library/ShowPeopleActivity.java | 0
.../library/src/main/res/values/strings.xml | 0
.../test-projects/mavenLocal/util/build.gradle | 0
.../android/multiproject/person/People.java | 0
.../android/multiproject/person/Person.java | 0
.../test-projects/maxSdkVersion/build.gradle | 52 +
.../test-projects/maxSdkVersion}/debug.keystore | Bin
.../maxSdkVersion/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../maxSdkVersion/src/main/res/drawable/icon.png | Bin
.../maxSdkVersion/src/main/res/layout/main.xml | 0
.../maxSdkVersion}/src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../test-projects/migrated}/AndroidManifest.xml | 0
.../test-projects/migrated}/assets/notice.txt | 0
.../test-projects/migrated/build.gradle | 53 +
.../test-projects/migrated/res/drawable/icon.png | Bin
.../test-projects/migrated/res/layout/main.xml | 0
.../test-projects/migrated}/res/raw/notice.txt | 0
.../test-projects/migrated}/res/values/strings.xml | 0
.../migrated/src/com/android/tests/basic/Foo.aidl | 0
.../migrated/src/com/android/tests/basic/Foo.java | 0
.../src/com/android/tests/basic/IService.aidl | 0
.../src}/com/android/tests/basic/Main.java | 0
.../src/some/unwanted/packageName/Useless.java | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../test-projects/minify/build.gradle | 50 +
.../test-projects/minify}/proguard-rules.pro | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../com/android/tests/basic/UnusedTestClass.java | 0
.../com/android/tests/basic/UsedTestClass.java | 0
.../minify}/src/main/AndroidManifest.xml | 0
.../tests/basic/IndirectlyReferencedClass.java | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../com/android/tests/basic/StringProvider.java | 0
.../java/com/android/tests/basic/UnusedClass.java | 0
.../minify/src/main/res/drawable/icon.png | Bin
.../minify/src/main/res/layout/main.xml | 0
.../minify/src/main}/res/values/strings.xml | 0
.../minify/src/test/java/android/UnitTest.java | 0
.../test-projects/minify/test-proguard-rules.pro | 0
.../test-projects/minifyLib/app/build.gradle | 32 +
.../java/com/android/tests/basic/MainTest.java | 0
.../minifyLib}/app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../minifyLib/app/src/main/res/drawable/icon.png | Bin
.../minifyLib/app/src/main/res/layout/main.xml | 0
.../minifyLib/app}/src/main/res/values/strings.xml | 0
.../test-projects/minifyLib/build.gradle | 0
.../test-projects/minifyLib/lib/build.gradle | 14 +
.../test-projects/minifyLib/lib/config.pro | 0
.../android/tests/basic/StringProviderTest.java | 0
.../minifyLib/lib/src/main/AndroidManifest.xml | 0
.../com/android/tests/basic/StringProvider.java | 0
.../test-projects/minifyLib}/settings.gradle | 0
.../minifyLibWithJavaRes/app/build.gradle | 39 +
.../minifyLibWithJavaRes/app}/debug.keystore | Bin
.../java/com/android/tests/basic/MainTest.java | 0
.../app/src/main}/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../com/android/tests/util/AppStringProvider.java | 0
.../app/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../com/android/tests/util/another.properties | 0
.../com/android/tests/util/resources.properties | 0
.../minifyLibWithJavaRes/build.gradle | 0
.../minifyLibWithJavaRes/lib/build.gradle | 14 +
.../minifyLibWithJavaRes/lib/config.pro | 0
.../android/tests/basic/StringProviderTest.java | 0
.../lib/src/main/AndroidManifest.xml | 0
.../com/android/tests/basic/StringProvider.java | 0
.../android/tests/other/PropertiesProvider.java | 0
.../com/android/tests/other/another.properties | 0
.../com/android/tests/other/resources.properties | 0
.../resources/com/android/tests/other/some.xml | 0
.../minifyLibWithJavaRes}/settings.gradle | 0
.../test-projects/multiDex/build.gradle | 45 +
.../test-projects/multiDex/proguard-android.txt | 0
.../java/com/android/tests/basic/MainTest.java | 49 +
.../multiDex/src/ics/AndroidManifest.xml | 0
.../multiDex/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../android/tests/basic/manymethods/Big001.java | 0
.../android/tests/basic/manymethods/Big002.java | 0
.../android/tests/basic/manymethods/Big003.java | 0
.../android/tests/basic/manymethods/Big004.java | 0
.../android/tests/basic/manymethods/Big005.java | 0
.../android/tests/basic/manymethods/Big006.java | 0
.../android/tests/basic/manymethods/Big007.java | 0
.../android/tests/basic/manymethods/Big008.java | 0
.../android/tests/basic/manymethods/Big009.java | 0
.../android/tests/basic/manymethods/Big010.java | 0
.../android/tests/basic/manymethods/Big011.java | 0
.../android/tests/basic/manymethods/Big012.java | 0
.../android/tests/basic/manymethods/Big013.java | 0
.../android/tests/basic/manymethods/Big014.java | 0
.../android/tests/basic/manymethods/Big015.java | 0
.../android/tests/basic/manymethods/Big016.java | 0
.../android/tests/basic/manymethods/Big017.java | 0
.../android/tests/basic/manymethods/Big018.java | 0
.../android/tests/basic/manymethods/Big019.java | 0
.../android/tests/basic/manymethods/Big020.java | 0
.../android/tests/basic/manymethods/Big021.java | 0
.../android/tests/basic/manymethods/Big022.java | 0
.../android/tests/basic/manymethods/Big023.java | 0
.../android/tests/basic/manymethods/Big024.java | 0
.../android/tests/basic/manymethods/Big025.java | 0
.../android/tests/basic/manymethods/Big026.java | 0
.../android/tests/basic/manymethods/Big027.java | 0
.../android/tests/basic/manymethods/Big028.java | 0
.../android/tests/basic/manymethods/Big029.java | 0
.../android/tests/basic/manymethods/Big030.java | 0
.../android/tests/basic/manymethods/Big031.java | 0
.../android/tests/basic/manymethods/Big032.java | 0
.../android/tests/basic/manymethods/Big033.java | 0
.../android/tests/basic/manymethods/Big034.java | 0
.../android/tests/basic/manymethods/Big035.java | 0
.../android/tests/basic/manymethods/Big036.java | 0
.../android/tests/basic/manymethods/Big037.java | 0
.../android/tests/basic/manymethods/Big038.java | 0
.../android/tests/basic/manymethods/Big039.java | 0
.../android/tests/basic/manymethods/Big040.java | 0
.../android/tests/basic/manymethods/Big041.java | 0
.../android/tests/basic/manymethods/Big042.java | 0
.../android/tests/basic/manymethods/Big043.java | 0
.../android/tests/basic/manymethods/Big044.java | 0
.../android/tests/basic/manymethods/Big045.java | 0
.../android/tests/basic/manymethods/Big046.java | 0
.../android/tests/basic/manymethods/Big047.java | 0
.../android/tests/basic/manymethods/Big048.java | 0
.../android/tests/basic/manymethods/Big049.java | 0
.../android/tests/basic/manymethods/Big050.java | 0
.../android/tests/basic/manymethods/Big051.java | 0
.../android/tests/basic/manymethods/Big052.java | 0
.../android/tests/basic/manymethods/Big053.java | 0
.../android/tests/basic/manymethods/Big054.java | 0
.../android/tests/basic/manymethods/Big055.java | 0
.../android/tests/basic/manymethods/Big056.java | 0
.../android/tests/basic/manymethods/Big057.java | 0
.../android/tests/basic/manymethods/Big058.java | 0
.../android/tests/basic/manymethods/Big059.java | 0
.../android/tests/basic/manymethods/Big060.java | 0
.../android/tests/basic/manymethods/Big061.java | 0
.../android/tests/basic/manymethods/Big062.java | 0
.../android/tests/basic/manymethods/Big063.java | 0
.../android/tests/basic/manymethods/Big064.java | 0
.../android/tests/basic/manymethods/Big065.java | 0
.../android/tests/basic/manymethods/Big066.java | 0
.../android/tests/basic/manymethods/Big067.java | 0
.../android/tests/basic/manymethods/Big068.java | 0
.../android/tests/basic/manymethods/Big069.java | 0
.../android/tests/basic/manymethods/Big070.java | 0
.../multiDex/src/main/res/drawable/icon.png | Bin
.../multiDex/src/main/res/layout/main.xml | 0
.../multiDex}/src/main/res/values/strings.xml | 0
.../test-projects/multiDexWithLib/app/build.gradle | 33 +
.../java/com/android/tests/basic/MainTest.java | 0
.../app/src/ics/AndroidManifest.xml | 0
.../app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../app/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app}/src/main/res/values/strings.xml | 0
.../test-projects/multiDexWithLib/build.gradle | 0
.../test-projects/multiDexWithLib/lib/build.gradle | 24 +
.../lib/src/androidTest/AndroidManifest.xml | 0
.../lib/src/main/AndroidManifest.xml | 0
.../android/tests/basic/manymethods/Big001.java | 0
.../android/tests/basic/manymethods/Big002.java | 0
.../android/tests/basic/manymethods/Big003.java | 0
.../android/tests/basic/manymethods/Big004.java | 0
.../android/tests/basic/manymethods/Big005.java | 0
.../android/tests/basic/manymethods/Big006.java | 0
.../android/tests/basic/manymethods/Big007.java | 0
.../android/tests/basic/manymethods/Big008.java | 0
.../android/tests/basic/manymethods/Big009.java | 0
.../android/tests/basic/manymethods/Big010.java | 0
.../android/tests/basic/manymethods/Big011.java | 0
.../android/tests/basic/manymethods/Big012.java | 0
.../android/tests/basic/manymethods/Big013.java | 0
.../android/tests/basic/manymethods/Big014.java | 0
.../android/tests/basic/manymethods/Big015.java | 0
.../android/tests/basic/manymethods/Big016.java | 0
.../android/tests/basic/manymethods/Big017.java | 0
.../android/tests/basic/manymethods/Big018.java | 0
.../android/tests/basic/manymethods/Big019.java | 0
.../android/tests/basic/manymethods/Big020.java | 0
.../android/tests/basic/manymethods/Big021.java | 0
.../android/tests/basic/manymethods/Big022.java | 0
.../android/tests/basic/manymethods/Big023.java | 0
.../android/tests/basic/manymethods/Big024.java | 0
.../android/tests/basic/manymethods/Big025.java | 0
.../android/tests/basic/manymethods/Big026.java | 0
.../android/tests/basic/manymethods/Big027.java | 0
.../android/tests/basic/manymethods/Big028.java | 0
.../android/tests/basic/manymethods/Big029.java | 0
.../android/tests/basic/manymethods/Big030.java | 0
.../android/tests/basic/manymethods/Big031.java | 0
.../android/tests/basic/manymethods/Big032.java | 0
.../android/tests/basic/manymethods/Big033.java | 0
.../android/tests/basic/manymethods/Big034.java | 0
.../android/tests/basic/manymethods/Big035.java | 0
.../android/tests/basic/manymethods/Big036.java | 0
.../android/tests/basic/manymethods/Big037.java | 0
.../android/tests/basic/manymethods/Big038.java | 0
.../android/tests/basic/manymethods/Big039.java | 0
.../android/tests/basic/manymethods/Big040.java | 0
.../android/tests/basic/manymethods/Big041.java | 0
.../android/tests/basic/manymethods/Big042.java | 0
.../android/tests/basic/manymethods/Big043.java | 0
.../android/tests/basic/manymethods/Big044.java | 0
.../android/tests/basic/manymethods/Big045.java | 0
.../android/tests/basic/manymethods/Big046.java | 0
.../android/tests/basic/manymethods/Big047.java | 0
.../android/tests/basic/manymethods/Big048.java | 0
.../android/tests/basic/manymethods/Big049.java | 0
.../android/tests/basic/manymethods/Big050.java | 0
.../android/tests/basic/manymethods/Big051.java | 0
.../android/tests/basic/manymethods/Big052.java | 0
.../android/tests/basic/manymethods/Big053.java | 0
.../android/tests/basic/manymethods/Big054.java | 0
.../android/tests/basic/manymethods/Big055.java | 0
.../android/tests/basic/manymethods/Big056.java | 0
.../android/tests/basic/manymethods/Big057.java | 0
.../android/tests/basic/manymethods/Big058.java | 0
.../android/tests/basic/manymethods/Big059.java | 0
.../android/tests/basic/manymethods/Big060.java | 0
.../android/tests/basic/manymethods/Big061.java | 0
.../android/tests/basic/manymethods/Big062.java | 0
.../android/tests/basic/manymethods/Big063.java | 0
.../android/tests/basic/manymethods/Big064.java | 0
.../android/tests/basic/manymethods/Big065.java | 0
.../android/tests/basic/manymethods/Big066.java | 0
.../android/tests/basic/manymethods/Big067.java | 0
.../android/tests/basic/manymethods/Big068.java | 0
.../android/tests/basic/manymethods/Big069.java | 0
.../android/tests/basic/manymethods/Big070.java | 0
.../test-projects/multiDexWithLib/settings.gradle | 0
.../test-projects/multiproject/app/build.gradle | 14 +
.../android/multiproject/MainActivityTest.java | 0
.../multiproject/app/src/main/AndroidManifest.xml | 0
.../example/android/multiproject/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../multiproject/app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../multiproject/baseLibrary/build.gradle | 14 +
.../src/androidTest/AndroidManifest.xml | 0
.../library/base/test/TestActivity.java | 0
.../library/base/test/TestActivityTest.java | 0
.../src/androidTest/res/layout/testlayout.xml | 0
.../baseLibrary/src/main/AndroidManifest.xml | 0
.../android/multiproject/library/PersonView.java | 0
.../test-projects/multiproject/build.gradle | 0
.../multiproject/library/build.gradle | 15 +
.../library/ShowPeopleActivityTest.java | 0
.../library/src/main/AndroidManifest.xml | 0
.../multiproject/library/ShowPeopleActivity.java | 0
.../library/src/main/res/values/strings.xml | 0
.../test-projects/multiproject/settings.gradle | 0
.../test-projects/multiproject/util/build.gradle | 0
.../android/multiproject/person/People.java | 0
.../android/multiproject/person/Person.java | 0
.../test-projects/multires/build.gradle | 17 +
.../java/com/android/tests/basic/MainTest.java | 0
.../multires}/src/main/AndroidManifest.xml | 0
.../multires/src/main/assets/notice.txt | 0
.../main/java}/com/android/tests/basic/Main.java | 0
.../multires/src/main/res1/raw/notice.txt | 0
.../multires/src/main/res1/values/strings.xml | 0
.../multires/src/main/res2/drawable/icon.png | Bin
.../multires/src/main/res2/layout/main.xml | 0
.../test-projects/ndkJniLib/app/build.gradle | 55 +
.../com/example/hellojni/lib/HelloJniTest.java | 0
.../ndkJniLib/app/src/main/AndroidManifest.xml | 0
.../ndkJniLib/app/src/main/res/values/strings.xml | 0
.../test-projects/ndkJniLib/build.gradle | 0
.../test-projects/ndkJniLib/lib/build.gradle | 14 +
.../com/example/hellojni/lib/HelloJniTest.java | 0
.../ndkJniLib/lib/src/main/AndroidManifest.xml | 0
.../java/com/example/hellojni/lib/HelloJni.java | 0
.../ndkJniLib/lib/src/main/jni/hello-jni.c | 0
.../ndkJniLib/lib/src/main/res/values/strings.xml | 0
.../test-projects/ndkJniLib/settings.gradle | 0
.../ndkJniPureSplitLib/app/build.gradle | 30 +
.../com/example/hellojni/lib/HelloJniTest.java | 0
.../app/src/main/AndroidManifest.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../test-projects/ndkJniPureSplitLib}/build.gradle | 0
.../ndkJniPureSplitLib/lib/build.gradle | 14 +
.../com/example/hellojni/lib/HelloJniTest.java | 0
.../lib/src/main/AndroidManifest.xml | 0
.../java/com/example/hellojni/lib/HelloJni.java | 0
.../lib/src/main/jni/hello-jni.c | 0
.../lib/src/main/res/values/strings.xml | 0
.../ndkJniPureSplitLib}/settings.gradle | 0
.../test-projects/ndkLibPrebuilts/build.gradle | 10 +
.../com/example/hellojni/lib/HelloJniTest.java | 0
.../ndkLibPrebuilts/src/main/AndroidManifest.xml | 0
.../java/com/example/hellojni/lib/HelloJni.java | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/ndkPrebuilts/build.gradle | 30 +
.../com/example/hellojni/lib/HelloJniTest.java | 0
.../ndkPrebuilts/src/main/AndroidManifest.xml | 0
.../java/com/example/hellojni/lib/HelloJni.java | 0
.../ndkPrebuilts}/src/main/res/values/strings.xml | 0
.../test-projects/ndkRsHelloCompute/Android.mk.old | 0
.../test-projects/ndkRsHelloCompute/build.gradle | 40 +
.../libhellocomputendk/Android.mk.old | 0
.../ndkRsHelloCompute/src/main/AndroidManifest.xml | 0
.../rs/hellocomputendk/HelloComputeNDK.java | 0
.../src/main/jni/helloComputeNDK.cpp | 0
.../src/main/res/drawable/data.jpg | Bin
.../ndkRsHelloCompute/src/main/res/layout/main.xml | 0
.../ndkRsHelloCompute/src/main/rs/mono.rs | 0
.../test-projects/ndkSanAngeles/README.txt | 0
.../test-projects/ndkSanAngeles/build.gradle | 47 +
.../test-projects/ndkSanAngeles/license-BSD.txt | 0
.../test-projects/ndkSanAngeles/license-LGPL.txt | 0
.../test-projects/ndkSanAngeles/license.txt | 0
.../test-projects/ndkSanAngeles/misc/app-linux.c | 0
.../test-projects/ndkSanAngeles/misc/app-win32.c | 0
.../com/example/SanAngeles/DemoActivityTest.java | 0
.../ndkSanAngeles/src/main/AndroidManifest.xml | 0
.../java/com/example/SanAngeles/DemoActivity.java | 0
.../ndkSanAngeles/src/main/jni/app-android.c | 0
.../test-projects/ndkSanAngeles/src/main/jni/app.h | 0
.../ndkSanAngeles/src/main/jni/cams.h | 0
.../ndkSanAngeles/src/main/jni/demo.c | 0
.../ndkSanAngeles/src/main/jni/importgl.c | 0
.../ndkSanAngeles/src/main/jni/importgl.h | 0
.../ndkSanAngeles/src/main/jni/shapes.h | 0
.../ndkSanAngeles/src/main/res/layout/main.xml | 0
.../ndkSanAngeles/src/main/res/values/strings.xml | 0
.../test-projects/ndkSanAngeles2/README.txt | 0
.../test-projects/ndkSanAngeles2/build.gradle | 50 +
.../test-projects/ndkSanAngeles2/license-BSD.txt | 0
.../test-projects/ndkSanAngeles2/license-LGPL.txt | 0
.../test-projects/ndkSanAngeles2/license.txt | 0
.../test-projects/ndkSanAngeles2/misc/app-linux.c | 0
.../test-projects/ndkSanAngeles2/misc/app-win32.c | 0
.../ndkSanAngeles2/src/main/AndroidManifest.xml | 0
.../java/com/example/SanAngeles/DemoActivity.java | 0
.../ndkSanAngeles2/src/main/jni/app-android.c | 0
.../ndkSanAngeles2/src/main/jni/app.h | 0
.../ndkSanAngeles2/src/main/jni/cams.h | 0
.../ndkSanAngeles2/src/main/jni/demo.c | 0
.../ndkSanAngeles2/src/main/jni/importgl.c | 0
.../ndkSanAngeles2/src/main/jni/importgl.h | 0
.../ndkSanAngeles2/src/main/jni/shapes.h | 0
.../ndkSanAngeles2/src/main/res/layout/main.xml | 0
.../ndkSanAngeles2/src/main/res/values/strings.xml | 0
.../test-projects/ndkVariants/build.gradle | 34 +
.../java/com/example/simplejni/SimpleJniTest.java | 0
.../ndkVariants/src/debug/jni/buildType.cpp | 0
.../java/com/example/simplejni/TestConstants.java | 0
.../ndkVariants/src/free/jni/productFlavor.cpp | 0
.../ndkVariants/src/include/buildType.h | 0
.../ndkVariants/src/include/productFlavor.h | 0
.../ndkVariants/src/main/AndroidManifest.xml | 0
.../main/java/com/example/simplejni/SimpleJni.java | 0
.../ndkVariants/src/main/jni/simple-jni.cpp | 0
.../ndkVariants/src/main/res/values/strings.xml | 0
.../java/com/example/simplejni/TestConstants.java | 0
.../ndkVariants/src/premium/jni/productFlavor.cpp | 0
.../ndkVariants/src/release/jni/buildType.cpp | 0
.../test-projects/noPngCrunch/build.gradle | 22 +
.../noPngCrunch/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../noPngCrunch/src/main/res/drawable/icon.png | Bin
.../noPngCrunch/src/main/res/drawable/lib_bg.9.png | Bin
.../noPngCrunch/src/main/res/layout/main.xml | 0
.../noPngCrunch}/src/main/res/values/strings.xml | 0
.../test-projects/noPreDex/build.gradle | 15 +
.../java/com/android/tests/basic/MainTest.java | 0
.../noPreDex}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../noPreDex/src/main/res/drawable/icon.png | Bin
.../noPreDex/src/main/res/layout/main.xml | 0
.../noPreDex}/src/main/res/values/strings.xml | 0
.../noPreDex}/src/release/res/values/strings.xml | 0
.../optionalLibInLibWithProguard/.gitignore | 0
.../optionalLibInLibWithProguard/app/.gitignore | 0
.../optionalLibInLibWithProguard/app/build.gradle | 23 +
.../my/myapplication/ApplicationTest.java | 13 +
.../app/src/main/AndroidManifest.xml | 20 +
.../android/optionallib/app/MainActivity.java | 20 +
.../app/src/main/res/layout/activity_main.xml | 5 +
.../app/src/main}/res/mipmap-xhdpi/ic_launcher.png | Bin
.../app/src/main/res/values/strings.xml | 3 +
.../optionalLibInLibWithProguard}/build.gradle | 0
.../mylibrary/.gitignore | 0
.../mylibrary/build.gradle | 13 +
.../mylibrary/src/main/AndroidManifest.xml | 7 +
.../android/optionallib/library/HttpUser.java | 36 +
.../optionalLibInLibWithProguard/settings.gradle | 1 +
.../test-projects/overlay1/build.gradle | 10 +
.../java/com/android/tests/overlay1/MainTest.java | 0
.../src/debug/res/drawable/type_overlay.png | Bin
.../overlay1/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/overlay1/Main.java | 0
.../overlay1/src/main/res/drawable/icon.png | Bin
.../overlay1/src/main/res/drawable/no_overlay.png | Bin
.../src/main/res/drawable/type_overlay.png | Bin
.../overlay1/src/main/res/layout/main.xml | 0
.../overlay1/src/main/res/values/strings.xml | 0
.../test-projects/overlay2/build.gradle | 14 +
.../java/com/android/tests/overlay2/MainTest.java | 0
.../src/debug/res/drawable-hdpi-v4}/icon.png | Bin
.../src/debug/res/drawable/type_flavor_overlay.png | Bin
.../src/debug/res/drawable/type_overlay.png | Bin
.../res/drawable/variant_type_flavor_overlay.png | Bin
.../overlay2/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/overlay2/Main.java | 0
.../overlay2/src/main/res/drawable-hdpi}/icon.png | Bin
.../src/main/res/drawable/flavor_overlay.png | Bin
.../overlay2}/src/main/res/drawable/icon.png | Bin
.../overlay2/src/main/res/drawable/no_overlay.png | Bin
.../src/main/res/drawable/type_flavor_overlay.png | Bin
.../src/main/res/drawable/type_overlay.png | Bin
.../res/drawable/variant_type_flavor_overlay.png | Bin
.../overlay2/src/main/res/layout/main.xml | 0
.../overlay2/src/main/res/values/strings.xml | 0
.../src/one/res/drawable/flavor_overlay.png | Bin
.../src/one/res/drawable/type_flavor_overlay.png | Bin
.../res/drawable/variant_type_flavor_overlay.png | Bin
.../res/drawable/variant_type_flavor_overlay.png | Bin
.../test-projects/overlay3/build.gradle | 40 +
.../movedSrc/beta/res/drawable/beta_overlay.png | Bin
.../movedSrc/beta/res/drawable/debug_overlay.png | Bin
.../beta/res/drawable/free_beta_debug_overlay.png | Bin
.../beta/res/drawable/free_beta_overlay.png | Bin
.../movedSrc/beta/res/drawable/free_overlay.png | Bin
.../movedSrc/debug/res/drawable/debug_overlay.png | Bin
.../debug/res/drawable/free_beta_debug_overlay.png | Bin
.../movedSrc/free/res/drawable/debug_overlay.png | Bin
.../free/res/drawable/free_beta_debug_overlay.png | Bin
.../free/res/drawable/free_beta_overlay.png | Bin
.../movedSrc/free/res/drawable/free_overlay.png | Bin
.../freeBeta/res/drawable/debug_overlay.png | Bin
.../res/drawable/free_beta_debug_overlay.png | Bin
.../freeBeta/res/drawable/free_beta_overlay.png | Bin
.../res/drawable/free_beta_debug_overlay.png | Bin
.../freeNormal/res/drawable/debug_overlay.png | Bin
.../res/drawable/free_normal_overlay.png | Bin
.../java/com/android/tests/overlay2/MainTest.java | 0
.../overlay3/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/overlay2/Main.java | 0
.../src/main/res/drawable/beta_overlay.png | Bin
.../src/main/res/drawable/debug_overlay.png | Bin
.../main/res/drawable/free_beta_debug_overlay.png | Bin
.../src/main/res/drawable/free_beta_overlay.png | Bin
.../src/main/res/drawable/free_normal_overlay.png | Bin
.../src/main/res/drawable/free_overlay.png | Bin
.../overlay3/src/main/res/drawable}/icon.png | Bin
.../overlay3/src/main/res/drawable/no_overlay.png | Bin
.../overlay3/src/main/res/layout/main.xml | 0
.../overlay3/src/main/res/values/strings.xml | 0
.../test-projects/packagingOptions/build.gradle | 22 +
.../packagingOptions/jars/jar1/build.gradle | 0
.../jars/jar1/src/main/resources/excluded.txt | 0
.../jars/jar1/src/main/resources/first_pick.txt | 0
.../jars/jar1/src/main/resources/merge.txt | 0
.../packagingOptions/jars/jar2/build.gradle | 0
.../jars/jar2/src/main/resources/excluded.txt | 0
.../jars/jar2/src/main/resources/first_pick.txt | 0
.../jars/jar2/src/main/resources/merge.txt | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../packagingOptions}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable/icon.png | Bin
.../packagingOptions/src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../parentLibsTest/app/application/build.gradle | 13 +
.../app/application}/proguard-project.txt | 0
.../tests/libstest/app/MainActivityTest.java | 0
.../app/application/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/app/App.java | 0
.../android/tests/libstest/app/MainActivity.java | 0
.../src/main/res/drawable-hdpi/icon.png | Bin
.../src/main/res/drawable-ldpi/icon.png | Bin
.../src/main/res/drawable-mdpi}/icon.png | Bin
.../app/application/src/main/res/layout/main.xml | 0
.../application}/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/app/App.txt | 0
.../test-projects/parentLibsTest/app/build.gradle | 0
.../parentLibsTest/app/settings.gradle | 0
.../test-projects/parentLibsTest/lib1/build.gradle | 0
.../parentLibsTest/lib1/proguard-project.txt | 0
.../tests/libstest/lib1/MainActivityTest.java | 0
.../lib1/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib1/Lib1.java | 0
.../android/tests/libstest/lib1/MainActivity.java | 0
.../main/res/drawable-hdpi/lib1_ic_launcher.png | Bin
.../main/res/drawable-ldpi/lib1_ic_launcher.png | Bin
.../main/res/drawable-mdpi/lib1_ic_launcher.png | Bin
.../lib1/src/main/res/layout/lib1_main.xml | 0
.../lib1/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib1/Lib1.txt | 0
.../test-projects/pkgOverride/build.gradle | 13 +
.../java/com/android/tests/basic/MainTest.java | 0
.../pkgOverride}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../pkgOverride}/src/main/res/drawable/icon.png | Bin
.../pkgOverride/src/main/res/layout/main.xml | 0
.../pkgOverride}/src/main/res/values/strings.xml | 0
.../placeholderInLibsTest/app/build.gradle | 35 +
.../app/src/flavor/AndroidManifest.xml | 0
.../app/src/main/AndroidManifest.xml | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../app/src/main/res/layout/activity_main.xml | 0
.../app/src/main/res/menu/menu_main.xml | 0
.../app/src/main/res/values-v21/styles.xml | 0
.../app/src/main/res/values-w820dp/dimens.xml | 0
.../app/src/main/res/values/dimens.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/main/res/values/styles.xml | 0
.../placeholderInLibsTest}/build.gradle | 0
.../examplelibrary/build.gradle | 18 +
.../example/examplelibrary/ApplicationTest.java | 0
.../examplelibrary/src/main/AndroidManifest.xml | 0
.../com/example/examplelibrary/LoginActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../examplelibrary/src/main/res/values/strings.xml | 0
.../placeholderInLibsTest/settings.gradle | 0
.../privateResources/app/build.gradle | 19 +
.../app/src/main/AndroidManifest.xml | 0
.../tools/test/publicsymbols/MainActivity.java | 0
.../app/src/main/res/layout/activity_main.xml | 0
.../app/src/main/res/menu/menu_main.xml | 0
.../app/src/main/res/values/dimens.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/main/res/values/styles.xml | 0
.../test-projects/privateResources/build.gradle | 0
.../privateResources/mylibrary/build.gradle | 14 +
.../mylibrary/src/main/AndroidManifest.xml | 0
.../src/main/res/layout/mylib_shared_name.xml | 5 +
.../mylibrary/src/main/res/values/public.xml | 6 +
.../mylibrary/src/main/res/values/strings.xml | 6 +
.../privateResources/mylibrary2/build.gradle | 14 +
.../mylibrary2/src/main/AndroidManifest.xml | 0
.../mylibrary2/src/main/res/values/public.xml | 0
.../mylibrary2/src/main/res/values/strings.xml | 0
.../test-projects/privateResources/settings.gradle | 0
.../projectWithClassifierDep/build.gradle | 18 +
.../repo/com/foo/sample/1.0/sample-1.0.pom | 0
.../repo/com/foo/sample/maven-metadata.xml | 0
.../src/main/AndroidManifest.xml | 0
.../java/com/android/test/ivyapp/MainActivity.java | 0
.../src/main/res/layout/activity_main.xml | 0
.../src/main/res/values/strings.xml | 0
.../projectWithIvyDependency/build.gradle | 24 +
.../ivy-repo/com/foo/sample/1.0/sample-1.0.ivy | 0
.../src/main/AndroidManifest.xml | 0
.../java/com/android/test/ivyapp/MainActivity.java | 0
.../src/main/res/layout/activity_main.xml | 0
.../src/main/res/values/strings.xml | 0
.../projectWithLocalDeps/build.gradle | 0
.../projectWithLocalDeps/libs/baseLib-1.0.aar | Bin
.../src/main/AndroidManifest.xml | 0
.../src/main/res/drawable/icon.png | Bin
.../src/main/res/values/strings.xml | 0
.../projectWithModules/app/build.gradle | 10 +
.../app/src/main/AndroidManifest.xml | 0
.../example/android/multiproject/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../app/src/main/res/layout/main.xml | 12 +
.../app/src/main/res/values/strings.xml | 0
.../test-projects/projectWithModules/build.gradle | 0
.../projectWithModules/jar/build.gradle | 0
.../android/multiproject/person/People.java | 0
.../android/multiproject/person/Person.java | 0
.../projectWithModules/library/build.gradle | 6 +
.../library/src/main/AndroidManifest.xml | 0
.../android/multiproject/library/PersonView.java | 0
.../library/src/main/res/layout/liblayout.xml | 0
.../projectWithModules/library2/build.gradle | 6 +
.../library2/src/main/AndroidManifest.xml | 4 +
.../android/multiproject/library2/PersonView2.java | 0
.../library2/src/main/res/layout/lib2layout.xml | 0
.../localJarAsModule/build.gradle | 0
.../projectWithModules/settings.gradle | 0
.../projectWithModules/test/build.gradle | 6 +
.../test/src/main/AndroidManifest.xml | 11 +
.../test-projects/pseudolocalized/build.gradle | 24 +
.../pseudolocalized}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../src/main/res/drawable}/icon.png | Bin
.../pseudolocalized/src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/renamedApk/build.gradle | 19 +
.../java/com/android/tests/basic/MainTest.java | 0
.../renamedApk}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../renamedApk}/src/main/res/drawable/icon.png | Bin
.../renamedApk/src/main/res/layout/main.xml | 0
.../renamedApk}/src/main/res/values/strings.xml | 0
.../renamedApk}/src/release/res/values/strings.xml | 0
.../test-projects/renderscript/build.gradle | 14 +
.../renderscript/src/main/AndroidManifest.xml | 0
.../android/rs/hellocompute/HelloCompute.java | 0
.../renderscript/src/main/res/drawable/data.jpg | Bin
.../renderscript/src/main/res/layout/main.xml | 0
.../rs/com/example/android/rs/hellocompute/mono.rs | 0
.../renderscriptInLib/app/build.gradle | 14 +
.../app/src/main/AndroidManifest.xml | 0
.../java/com/example/android/rs/balls/Balls.java | 0
.../java/com/example/android/rs/balls/BallsRS.java | 0
.../com/example/android/rs/balls/BallsView.java | 0
.../app/src/main/res/drawable/flares.png | Bin
.../app/src/main/res/drawable/test_pattern.png | Bin
.../com/example/android/rs/balls/ball_physics.rs | 0
.../main/rs/com/example/android/rs/balls/balls.rs | 0
.../test-projects/renderscriptInLib/build.gradle | 0
.../renderscriptInLib/lib/build.gradle | 6 +
.../lib/src/main/AndroidManifest.xml | 0
.../main/rs/com/example/android/rs/balls/balls.rsh | 0
.../renderscriptInLib/settings.gradle | 0
.../renderscriptMultiSrc/build.gradle | 13 +
.../rs/com/example/android/rs/balls/balls.rsh | 0
.../src/main/AndroidManifest.xml | 0
.../java/com/example/android/rs/balls/Balls.java | 0
.../java/com/example/android/rs/balls/BallsRS.java | 0
.../com/example/android/rs/balls/BallsView.java | 0
.../src/main/res/drawable/flares.png | Bin
.../src/main/res/drawable/test_pattern.png | Bin
.../com/example/android/rs/balls/ball_physics.rs | 0
.../main/rs/com/example/android/rs/balls/balls.rs | 0
.../rs/com/example/android/rs/balls/balls.rsh | 0
.../test-projects/renderscriptNdk/build.gradle | 23 +
.../renderscriptNdk}/src/main/AndroidManifest.xml | 0
.../android/rs/hellocompute/HelloCompute.java | 63 +
.../renderscriptNdk/src/main/jni/renderscript.cpp | 15 +
.../src/main/res/drawable/data.jpg | Bin
.../renderscriptNdk}/src/main/res/layout/main.xml | 0
.../rs/com/example/android/rs/hellocompute/mono.rs | 0
.../integration-test/test-projects/repo/.gitignore | 0
.../test-projects/repo/app/build.gradle | 20 +
.../repo/app/src/main/AndroidManifest.xml | 0
.../example/android/multiproject/MainActivity.java | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../repo/app/src/main/res/layout/main.xml | 0
.../repo/app/src/main/res/values/strings.xml | 0
.../test-projects/repo/baseLibrary/build.gradle | 32 +
.../repo/baseLibrary}/src/main/AndroidManifest.xml | 0
.../android/multiproject/library/PersonView.java | 0
.../test-projects/repo/library/build.gradle | 32 +
.../repo/library/src/main/AndroidManifest.xml | 0
.../multiproject/library/ShowPeopleActivity.java | 0
.../repo/library/src/main/res/values/strings.xml | 0
.../test-projects/repo/util/build.gradle | 0
.../android/multiproject/person/People.java | 0
.../android/multiproject/person/Person.java | 0
.../test-projects/rsSupportMode/build.gradle | 36 +
.../rsSupportMode/proguard-project.txt | 0
.../test-projects/rsSupportMode/project.properties | 0
.../rsSupportMode/src/main/AndroidManifest.xml | 0
.../main/java/com/android/rs/image/BWFilter.java | 0
.../src/main/java/com/android/rs/image/Blend.java | 0
.../src/main/java/com/android/rs/image/Blur25.java | 0
.../main/java/com/android/rs/image/Blur25G.java | 0
.../main/java/com/android/rs/image/ColorCube.java | 0
.../java/com/android/rs/image/ColorMatrix.java | 0
.../main/java/com/android/rs/image/Contrast.java | 0
.../java/com/android/rs/image/Convolve3x3.java | 0
.../java/com/android/rs/image/Convolve5x5.java | 0
.../src/main/java/com/android/rs/image/Copy.java | 0
.../java/com/android/rs/image/CrossProcess.java | 0
.../main/java/com/android/rs/image/Exposure.java | 0
.../main/java/com/android/rs/image/Fisheye.java | 0
.../src/main/java/com/android/rs/image/Grain.java | 0
.../main/java/com/android/rs/image/Greyscale.java | 0
.../main/java/com/android/rs/image/GroupTest.java | 0
.../android/rs/image/ImageProcessingActivity2.java | 0
.../main/java/com/android/rs/image/LevelsV4.java | 0
.../main/java/com/android/rs/image/Mandelbrot.java | 0
.../main/java/com/android/rs/image/Shadows.java | 0
.../main/java/com/android/rs/image/TestBase.java | 0
.../main/java/com/android/rs/image/Vibrance.java | 0
.../main/java/com/android/rs/image/Vignette.java | 0
.../java/com/android/rs/image/WhiteBalance.java | 0
.../src/main/res/drawable-nodpi/city.png | Bin
.../src/main/res/drawable-nodpi/img1600x1067.jpg | Bin
.../src/main/res/drawable-nodpi/img1600x1067b.jpg | Bin
.../rsSupportMode/src/main/res/layout/main.xml | 0
.../src/main/res/layout/spinner_layout.xml | 0
.../rsSupportMode/src/main/res/values/strings.xml | 0
.../rsSupportMode/src/main/rs/blend.rs | 0
.../rsSupportMode/src/main/rs/bwfilter.rs | 0
.../rsSupportMode/src/main/rs/colorcube.rs | 0
.../rsSupportMode/src/main/rs/colormatrix.fs | 0
.../rsSupportMode/src/main/rs/contrast.rs | 0
.../rsSupportMode/src/main/rs/convolve5x5.fs | 0
.../rsSupportMode/src/main/rs/copy.fs | 0
.../rsSupportMode/src/main/rs/exposure.rs | 0
.../rsSupportMode/src/main/rs/fisheye.rsh | 0
.../rsSupportMode/src/main/rs/fisheye_approx.rsh | 0
.../src/main/rs/fisheye_approx_full.rs | 0
.../src/main/rs/fisheye_approx_relaxed.fs | 0
.../rsSupportMode/src/main/rs/fisheye_full.rs | 0
.../rsSupportMode/src/main/rs/fisheye_relaxed.fs | 0
.../rsSupportMode/src/main/rs/grain.fs | 0
.../rsSupportMode/src/main/rs/greyscale.fs | 0
.../test-projects/rsSupportMode/src/main/rs/ip.rsh | 0
.../rsSupportMode/src/main/rs/ip2_convolve3x3.rs | 0
.../rsSupportMode/src/main/rs/levels.rsh | 0
.../rsSupportMode/src/main/rs/levels_full.rs | 0
.../rsSupportMode/src/main/rs/levels_relaxed.fs | 0
.../rsSupportMode/src/main/rs/mandelbrot.rs | 0
.../rsSupportMode/src/main/rs/shadows.rs | 0
.../rsSupportMode/src/main/rs/threshold.fs | 0
.../rsSupportMode/src/main/rs/vibrance.rs | 0
.../rsSupportMode/src/main/rs/vignette.rsh | 0
.../rsSupportMode/src/main/rs/vignette_approx.rsh | 0
.../src/main/rs/vignette_approx_full.rs | 0
.../src/main/rs/vignette_approx_relaxed.fs | 0
.../rsSupportMode/src/main/rs/vignette_full.rs | 0
.../rsSupportMode/src/main/rs/vignette_relaxed.fs | 0
.../rsSupportMode/src/main/rs/wbalance.rs | 0
.../test-projects/sameNamedLibs/app/build.gradle | 15 +
.../sameNamedLibs}/app/proguard-project.txt | 0
.../tests/libstest/app/MainActivityTest.java | 0
.../sameNamedLibs/app/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/app/App.java | 0
.../android/tests/libstest/app/MainActivity.java | 0
.../app/src/main}/res/drawable-hdpi/icon.png | Bin
.../app/src/main}/res/drawable-ldpi/icon.png | Bin
.../app/src/main/res/drawable-mdpi}/icon.png | Bin
.../sameNamedLibs/app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/app}/App.txt | 0
.../test-projects/sameNamedLibs}/build.gradle | 0
.../sameNamedLibs/lib1/libs/build.gradle | 15 +
.../sameNamedLibs/lib1/libs/proguard-project.txt | 0
.../tests/libstest/lib1/MainActivityTest.java | 0
.../lib1/libs/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib1/Lib1.java | 0
.../android/tests/libstest/lib1/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib1/libs/src/main/res/layout/lib1_main.xml | 0
.../lib1/libs/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib1/Lib1.txt | 0
.../sameNamedLibs/lib2/libs/build.gradle | 6 +
.../sameNamedLibs/lib2/libs/proguard-project.txt | 0
.../tests/libstest/lib2/MainActivityTest.java | 0
.../lib2/libs/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/libstest/lib2/Lib2.java | 0
.../android/tests/libstest/lib2/MainActivity.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib2/libs/src/main/res/layout/lib2_main.xml | 0
.../lib2/libs/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib2/Lib2.txt | 0
.../sameNamedLibs/lib2b/libs/build.gradle | 6 +
.../sameNamedLibs/lib2b/libs/proguard-project.txt | 0
.../tests/libstest/lib2/MainActivity2bTest.java | 0
.../lib2b/libs/src/main/AndroidManifest.xml | 0
.../com/android/tests/libstest/lib2/Lib2b.java | 0
.../tests/libstest/lib2/MainActivity2b.java | 0
.../src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../lib2b/libs/src/main/res/layout/lib2b_main.xml | 0
.../lib2b/libs/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/lib2/Lib2b.txt | 0
.../sameNamedLibs/libapp/libs/build.gradle | 6 +
.../sameNamedLibs/libapp/libs/proguard-project.txt | 0
.../libstest/libapp/MainActivityLibAppTest.java | 0
.../libapp/libs/src/main/AndroidManifest.xml | 0
.../com/android/tests/libstest/app/LibApp.java | 0
.../tests/libstest/app/MainActivityLibApp.java | 0
.../src/main}/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main}/res/drawable-ldpi/ic_launcher.png | Bin
.../src/main/res/drawable-mdpi/ic_launcher.png | Bin
.../libs/src/main/res/layout/libapp_main.xml | 0
.../libapp/libs/src/main/res/values/strings.xml | 0
.../com/android/tests/libstest/app/Libapp.txt | 0
.../test-projects/sameNamedLibs/settings.gradle | 0
.../separateTestModule/app/build.gradle | 12 +
.../java/com/android/tests/basic/MainTest.java | 0
.../app}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../app/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app}/src/main/res/values/strings.xml | 0
.../app}/src/release/res/values/strings.xml | 0
.../test-projects/separateTestModule/build.gradle | 0
.../separateTestModule/settings.gradle | 0
.../separateTestModule/test/build.gradle | 29 +
.../test/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../app/build.gradle | 25 +
.../java/com/android/tests/basic/MainTest.java | 0
.../app}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../app/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/release/res/values/strings.xml | 0
.../build.gradle | 0
.../jar/build.gradle | 0
.../android/tests/jarDep/JarDependencyUtil.java | 0
.../lib/build.gradle | 17 +
.../com/android/tests/basic/StringGetterTest.java | 0
.../lib/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/StringGetter.java | 0
.../settings.gradle | 0
.../test/build.gradle | 9 +
.../test/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../app/build.gradle | 43 +
.../java/com/android/tests/basic/MainTest.java | 0
.../app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../main/java/com/android/tests/utils/Utility.java | 0
.../app}/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/release/res/values/strings.xml | 0
.../separateTestModuleWithMinifiedApp/build.gradle | 0
.../settings.gradle | 0
.../test/build.gradle | 27 +
.../test/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../app/build.gradle | 43 +
.../app/proguard.txt | 2 +
.../java/com/android/tests/basic/MainTest.java | 0
.../app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../main/java/com/android/tests/utils/Utility.java | 0
.../app}/src/main/res/drawable/icon.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/release/res/values/strings.xml | 0
.../build.gradle | 0
.../settings.gradle | 0
.../test/build.gradle | 27 +
.../test/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../test-projects/shrink/README.txt | 0
.../test-projects/shrink/abisplits/build.gradle | 25 +
.../shrink/abisplits/src/main/AndroidManifest.xml | 0
.../com/android/tests/shrink/UsedActivity.java | 0
.../abisplits/src/main/res/layout/unused.xml | 0
.../shrink/abisplits/src/main/res/layout/used.xml | 0
.../test-projects/shrink/build.gradle | 43 +
.../test-projects/shrink/keep/build.gradle | 19 +
.../shrink/keep/src/main/AndroidManifest.xml | 0
.../android/tests/shrink/keep/KeepActivity.java | 0
.../shrink/keep/src/main/res/layout/unused1.xml | 0
.../shrink/keep/src/main/res/layout/unused2.xml | 0
.../shrink/keep/src/main/res/layout/used1.xml | 0
.../shrink/keep/src/main/res/raw/keep.xml | 0
.../shrink/keep/src/main/res/values/strings.xml | 0
.../test-projects/shrink/lib/build.gradle | 12 +
.../shrink/lib/src/main/AndroidManifest.xml | 0
.../shrink/lib/src/main/res/layout/lib_unused.xml | 0
.../test-projects/shrink/proguard-rules.pro | 0
.../test-projects/shrink/settings.gradle | 0
.../shrink/src/main/AndroidManifest.xml | 0
.../android/tests/shrink/AnnotationInflation.java | 0
.../main/java/com/android/tests/shrink/Layout.java | 0
.../java/com/android/tests/shrink/Layouts.java | 0
.../android/tests/shrink/ResourceReferences.java | 0
.../com/android/tests/shrink/RootActivity.java | 81 +
.../com/android/tests/shrink/UnusedActivity.java | 0
.../shrink/src/main/res/drawable/force_remove.xml | 2 +
.../shrink/src/main/res/drawable/unused10.xml | 0
.../shrink/src/main/res/drawable/unused11.xml | 0
.../shrink/src/main/res/drawable/unused9.xml | 0
.../shrink/src/main/res/drawable/used10.xml | 0
.../shrink/src/main/res/drawable/used11.xml | 0
.../shrink/src/main/res/drawable/used12.xml | 0
.../shrink/src/main/res/drawable/used15.xml | 0
.../shrink/src/main/res/drawable/used9.xml | 0
.../shrink/src/main/res/layout/l_used_a.xml | 0
.../shrink/src/main/res/layout/l_used_b2.xml | 0
.../shrink/src/main/res/layout/l_used_c.xml | 0
.../shrink/src/main/res/layout/prefix_3_suffix.xml | 0
.../shrink/src/main/res/layout/prefix_used_1.xml | 0
.../shrink/src/main/res/layout/prefix_used_2.xml | 0
.../shrink/src/main/res/layout/unused1.xml | 0
.../shrink/src/main/res/layout/unused13.xml | 0
.../shrink/src/main/res/layout/unused14.xml | 0
.../shrink/src/main/res/layout/unused2.xml | 0
.../shrink/src/main/res/layout/used1.xml | 0
.../shrink/src/main/res/layout/used14.xml | 0
.../shrink/src/main/res/layout/used16.xml | 0
.../shrink/src/main/res/layout/used17.xml | 0
.../shrink/src/main/res/layout/used18.xml | 0
.../shrink/src/main/res/layout/used19.xml | 0
.../shrink/src/main/res/layout/used2.xml | 0
.../shrink/src/main/res/layout/used20.xml | 0
.../shrink/src/main/res/layout/used21.xml | 0
.../shrink/src/main/res/layout/used3.xml | 0
.../shrink/src/main/res/layout/used4.xml | 0
.../shrink/src/main/res/layout/used5.xml | 0
.../shrink/src/main/res/layout/used6.xml | 0
.../shrink/src/main/res/layout/used7.xml | 0
.../shrink/src/main/res/layout/used8.xml | 0
.../shrink/src/main/res/menu/unused12.xml | 0
.../shrink/src/main/res/menu/used13.xml | 0
.../test-projects/shrink/src/main/res/raw/keep.xml | 4 +
.../shrink/src/main/res/values-w820dp/aliases.xml | 0
.../shrink/src/main/res/values/aliases.xml | 0
.../shrink/src/main/res/values/strings.xml | 0
.../shrink/src/main/res/values/styles.xml | 0
.../test-projects/shrink/webview/build.gradle | 19 +
.../shrink/webview/src/main/AndroidManifest.xml | 0
.../tests/shrink/webview/WebViewActivity.java | 29 +
.../shrink/webview/src/main/res/drawable/used1.xml | 0
.../webview/src/main/res/layout/used_layout1.xml | 0
.../webview/src/main/res/layout/used_layout2.xml | 0
.../webview/src/main/res/layout/used_layout3.xml | 0
.../shrink/webview/src/main/res/layout/webview.xml | 0
.../shrink/webview/src/main/res/raw/unknown | 0
.../webview/src/main/res/raw/unused_icon.png | Bin
.../webview/src/main/res/raw/unused_index.html | 0
.../shrink/webview/src/main/res/raw/used_icon.png | Bin
.../shrink/webview/src/main/res/raw/used_icon2.png | Bin
.../webview/src/main/res/raw/used_index.html | 0
.../webview/src/main/res/raw/used_index2.html | 0
.../webview/src/main/res/raw/used_index3.html | 0
.../shrink/webview/src/main/res/raw/used_script.js | 0
.../webview/src/main/res/raw/used_styles.css | 0
.../shrink/webview/src/main/res/values/strings.xml | 0
.../shrink/webview/src/main/res/xml/my_xml.xml | 0
.../simpleManifestMergingTask/build.gradle | 0
.../src/main/AndroidManifest.xml | 0
.../src/main/Lib1_AndroidManifest.xml | 0
.../src/main/Lib2_AndroidManifest.xml | 0
.../test-projects/simpleMicroApp/build.gradle | 0
.../test-projects/simpleMicroApp/main/build.gradle | 12 +
.../java/com/android/tests/basic/MainTest.java | 0
.../main}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../main}/src/main/res/drawable/icon.png | Bin
.../main/src/main/res/layout/main.xml | 0
.../main}/src/main/res/values/strings.xml | 0
.../test-projects/simpleMicroApp/settings.gradle | 0
.../test-projects/simpleMicroApp/wear/build.gradle | 15 +
.../java/com/android/tests/basic/MainTest.java | 0
.../wear}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../wear}/src/main/res/drawable/icon.png | Bin
.../wear/src/main/res/layout/main.xml | 0
.../wear}/src/main/res/values/strings.xml | 0
.../splitAwareSeparateTestModule/app/build.gradle | 23 +
.../java/com/android/tests/basic/MainTest.java | 0
.../app/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin
.../src/main}/res/drawable-mdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xhdpi/ic_launcher.png | Bin
.../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin
.../app/src/main/res/drawable/ic_launcher.png | Bin
.../app/src/main/res/layout/main.xml | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/release/res/values/strings.xml | 0
.../splitAwareSeparateTestModule/build.gradle | 0
.../splitAwareSeparateTestModule/settings.gradle | 0
.../splitAwareSeparateTestModule/test/build.gradle | 9 +
.../test/src/main/AndroidManifest.xml | 0
.../java/com/android/tests/basic/MainTest.java | 0
.../test-projects/testDependency/build.gradle | 25 +
.../java/com/android/tests/basic/MainTest.java | 0
.../testDependency}/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../testDependency}/src/main/res/drawable/icon.png | Bin
.../testDependency/src/main/res/layout/main.xml | 0
.../src/main/res/values/strings.xml | 0
.../test-projects/testWithDep/build.gradle | 15 +
.../java/com/android/tests/basic/MainTest.java | 47 +
.../testWithDep}/src/main/AndroidManifest.xml | 0
.../testWithDep/src/main}/assets/notice.txt | 0
.../main/java/com/android/tests/basic/Main.java | 0
.../testWithDep/src/main/res/drawable}/icon.png | Bin
.../testWithDep/src/main/res/layout/main.xml | 0
.../testWithDep/src/main}/res/raw/notice.txt | 0
.../testWithDep}/src/main/res/values/strings.xml | 0
.../src/release/res/values/strings.xml | 0
.../test-projects/tictactoe/README.txt | 0
.../test-projects/tictactoe/app/build.gradle | 10 +
.../tictactoe/app/src/main/AndroidManifest.xml | 0
.../example/android/tictactoe/MainActivity.java | 0
.../tictactoe/app/src/main/res/drawable/icon.png | Bin
.../tictactoe/app/src/main/res/layout/main.xml | 0
.../tictactoe/app/src/main/res/values/strings.xml | 0
.../test-projects/tictactoe/build.gradle | 0
.../test-projects/tictactoe/lib/build.gradle | 10 +
.../tictactoe/lib/src/main/AndroidManifest.xml | 0
.../android/tictactoe/library/GameActivity.java | 0
.../android/tictactoe/library/GameView.java | 0
.../lib/src/main/res/drawable/lib_bg.9.png | Bin
.../lib/src/main/res/drawable/lib_circle.png | Bin
.../lib/src/main/res/drawable/lib_cross.png | Bin
.../lib/src/main/res/layout-land/lib_game.xml | 0
.../tictactoe/lib/src/main/res/layout/lib_game.xml | 0
.../tictactoe/lib/src/main/res/values/strings.xml | 0
.../test-projects/tictactoe/settings.gradle | 0
.../test-projects/unitTesting/build.gradle | 25 +
.../unitTesting/src/main/AndroidManifest.xml | 0
.../src/main/java/com/android/tests/Foo.java | 0
.../main/java/com/android/tests/MainActivity.java | 0
.../unitTesting/src/main/res/values/strings.xml | 0
.../src/main/resources/prod_resource_file.txt | 0
.../java/com/android/tests/NonStandardName.java | 0
.../src/test/java/com/android/tests/UnitTest.java | 0
.../src/test/resources/resource_file.txt | 0
.../unitTestingBuildTypes/build.gradle | 24 +
.../src/debug/AndroidManifest.xml | 0
.../java/com/android/tests/DebugOnlyClass.java | 0
.../src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/MainActivity.java | 0
.../src/main/resources/prod_resource_file.txt | 0
.../java/com/android/tests/UnitTest.java | 0
.../resources/resource_file.txt | 0
.../testDebug/java/com/android/tests/UnitTest.java | 0
.../java/com/android/tests/UnitTest.java | 0
.../unitTestingComplexProject/app/build.gradle | 40 +
.../app/src/main/AndroidManifest.xml | 0
.../app/src/main/java/com/android/tests/Foo.java | 35 +
.../main/java/com/android/tests/MainActivity.java | 0
.../app/src/main/res/values/strings.xml | 0
.../app/src/main/resources/prod_resource_file.txt | 0
.../src/test/java/com/android/tests/UnitTest.java | 0
.../app/src/test/resources/resource_file.txt | 0
.../unitTestingComplexProject/build.gradle | 0
.../unitTestingComplexProject/lib/build.gradle | 39 +
.../lib/src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/lib/LibFoo.java | 29 +
.../lib/src/main/res/values/strings.xml | 0
.../src/main/resources/lib_prod_resource_file.txt | 0
.../test/java/com/android/tests/lib/UnitTest.java | 0
.../lib/src/test/resources/lib_resource_file.txt | 0
.../unitTestingComplexProject/settings.gradle | 0
.../unitTestingDefaultValues/build.gradle | 38 +
.../src/main/AndroidManifest.xml | 0
.../main/java/com/android/tests/MainActivity.java | 0
.../src/test/java/com/android/tests/UnitTest.java | 0
.../test-projects/unitTestingFlavors/build.gradle | 53 +
.../doesntBuild/java/com/android/tests/Broken.java | 20 +
.../src/main/AndroidManifest.xml | 0
.../src/main/java/com/android/tests/Foo.java | 0
.../main/java/com/android/tests/MainActivity.java | 0
.../java/com/android/tests/ClassThatBuilds.java | 0
.../src/test/java/com/android/tests/UnitTest.java | 0
.../java/com/android/tests/FailingTest.java | 0
.../java/com/android/tests/PassingTest.java | 0
.../unitTestingLibraryModules/build.gradle | 42 +
.../src/main/AndroidManifest.xml | 0
.../src/main/java/com/android/tests/Foo.java | 0
.../main/java/com/android/tests/MainActivity.java | 0
.../src/main/res/values/strings.xml | 0
.../src/main/resources/prod_resource_file.txt | 0
.../java/com/android/tests/NonStandardName.java | 0
.../src/test/java/com/android/tests/UnitTest.java | 0
.../src/test/resources/resource_file.txt | 0
.../test-projects/vectorDrawables/build.gradle | 22 +
.../vectorDrawables/src/main/AndroidManifest.xml | 0
.../example/bendowski/vectors/MainActivity.java | 0
.../src/main/res/drawable-fr/french_heart.xml | 0
.../src/main/res/drawable-hdpi/special_heart.png | Bin
.../src/main/res/drawable-v16/modern_heart.xml | 0
.../src/main/res/drawable-v22/no_need.xml | 0
.../src/main/res/drawable/heart.xml | 0
.../src/main/res/drawable}/icon.png | Bin
.../src/main/res/drawable/special_heart.xml | 0
.../src/main/res/layout/activity_main.xml | 0
.../src/main/res/menu/menu_main.xml | 0
.../vectorDrawables/src/main/res/values/dimens.xml | 0
.../src/main/res/values/strings.xml | 0
.../vectorDrawables/src/main/res/values/styles.xml | 0
.../manifest-merger/.classpath | 0
.../manifest-merger/.gitignore | 0
.../manifest-merger/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../manifest-merger/NOTICE | 0
.../manifest-merger/build.gradle | 0
.../manifest-merger/etc/manifmerger | 0
.../manifest-merger/manifest-merger-base.iml | 18 +
.../manifest-merger/manifest-merger.iml | 0
.../com/android/manifmerger/ActionRecorder.java | 315 ++
.../main/java/com/android/manifmerger/Actions.java | 475 +++
.../java/com/android/manifmerger/ArgvParser.java | 0
.../com/android/manifmerger/AttributeModel.java | 478 +++
.../manifmerger/AttributeOperationType.java | 0
.../com/android/manifmerger/ConvertibleName.java | 0
.../com/android/manifmerger/ElementsTrimmer.java | 153 +
.../java/com/android/manifmerger/ICallback.java | 0
.../java/com/android/manifmerger/IMergerLog.java | 0
.../java/com/android/manifmerger/KeyResolver.java | 0
.../main/java/com/android/manifmerger/Main.java | 0
.../com/android/manifmerger/ManifestMerger.java | 0
.../com/android/manifmerger/ManifestMerger2.java | 1282 +++++++
.../com/android/manifmerger/ManifestModel.java | 683 ++++
.../java/com/android/manifmerger/MergeType.java | 0
.../main/java/com/android/manifmerger/Merger.java | 215 ++
.../java/com/android/manifmerger/MergerLog.java | 0
.../com/android/manifmerger/MergerXmlUtils.java | 0
.../com/android/manifmerger/MergingReport.java | 356 ++
.../com/android/manifmerger/NodeOperationType.java | 0
.../com/android/manifmerger/OrphanXmlElement.java | 123 +
.../android/manifmerger/OtherOperationType.java | 0
.../android/manifmerger/PlaceholderEncoder.java | 54 +
.../android/manifmerger/PlaceholderHandler.java | 142 +
.../com/android/manifmerger/PostValidator.java | 315 ++
.../java/com/android/manifmerger/PreValidator.java | 287 ++
.../java/com/android/manifmerger/Selector.java | 62 +
.../manifmerger/ToolsInstructionsCleaner.java | 140 +
.../java/com/android/manifmerger/XmlAttribute.java | 405 +++
.../java/com/android/manifmerger/XmlDocument.java | 603 ++++
.../java/com/android/manifmerger/XmlElement.java | 904 +++++
.../java/com/android/manifmerger/XmlLoader.java | 101 +
.../main/java/com/android/manifmerger/XmlNode.java | 304 ++
.../android/manifmerger/ActionRecorderTest.java | 0
.../java/com/android/manifmerger/ActionsTest.java | 0
.../android/manifmerger/AttributeModelTest.java | 0
.../android/manifmerger/ElementsTrimmerTest.java | 0
.../manifmerger/ManifestMerger2SmallTest.java | 631 ++++
.../android/manifmerger/ManifestMerger2Test.java | 301 ++
.../manifmerger/ManifestMergerSourceLinkTest.java | 257 ++
.../android/manifmerger/ManifestMergerTest.java | 195 ++
.../manifmerger/ManifestMergerTestUtil.java | 433 +++
.../com/android/manifmerger/ManifestModelTest.java | 0
.../java/com/android/manifmerger/MergerTest.java | 292 ++
.../com/android/manifmerger/MergingReportTest.java | 147 +
.../manifmerger/PlaceholderHandlerTest.java | 229 ++
.../com/android/manifmerger/PostValidatorTest.java | 0
.../com/android/manifmerger/PreValidatorTest.java | 0
.../java/com/android/manifmerger/TestUtils.java | 0
.../manifmerger/ToolsInstructionsCleanerTest.java | 0
.../com/android/manifmerger/XmlAttributeTest.java | 0
.../com/android/manifmerger/XmlDocumentTest.java | 0
.../com/android/manifmerger/XmlElementTest.java | 0
.../com/android/manifmerger/XmlLoaderTest.java | 0
.../java/com/android/manifmerger/XmlNodeTest.java | 0
.../java/com/android/manifmerger/data/00_noop.xml | 0
.../manifmerger/data/01_ignore_app_attr.xml | 0
.../manifmerger/data/02_ignore_instrumentation.xml | 0
.../manifmerger/data/03_inject_attributes.xml | 0
.../manifmerger/data/04_inject_attributes.xml | 0
.../android/manifmerger/data/05_inject_package.xml | 0
.../android/manifmerger/data/10_activity_merge.xml | 0
.../android/manifmerger/data/11_activity_dup.xml | 0
.../com/android/manifmerger/data/12_alias_dup.xml | 0
.../android/manifmerger/data/13_service_dup.xml | 0
.../android/manifmerger/data/14_receiver_dup.xml | 0
.../android/manifmerger/data/15_provider_dup.xml | 0
.../com/android/manifmerger/data/16_fqcn_merge.xml | 0
.../android/manifmerger/data/17_fqcn_conflict.xml | 0
.../android/manifmerger/data/20_uses_lib_merge.xml | 0
.../manifmerger/data/21_uses_lib_errors.xml | 0
.../manifmerger/data/25_permission_merge.xml | 0
.../android/manifmerger/data/26_permission_dup.xml | 0
.../manifmerger/data/28_uses_perm_merge.xml | 0
.../android/manifmerger/data/30_uses_sdk_ok.xml | 0
.../manifmerger/data/32_uses_sdk_minsdk_ok.xml | 0
.../data/33_uses_sdk_minsdk_conflict.xml | 0
.../data/36_uses_sdk_targetsdk_warning.xml | 0
.../manifmerger/data/40_uses_feat_merge.xml | 0
.../manifmerger/data/41_uses_feat_errors.xml | 0
.../manifmerger/data/45_uses_feat_gles_once.xml | 0
.../data/47_uses_feat_gles_conflict.xml | 0
.../manifmerger/data/50_uses_conf_warning.xml | 0
.../data/52_support_screens_warning.xml | 0
.../manifmerger/data/54_compat_screens_warning.xml | 0
.../manifmerger/data/56_support_gltext_warning.xml | 0
.../android/manifmerger/data/60_merge_order.xml | 0
.../android/manifmerger/data/65_override_app.xml | 0
.../com/android/manifmerger/data/66_remove_app.xml | 0
.../manifmerger/data/67_override_activities.xml | 0
.../android/manifmerger/data/68_override_uses.xml | 0
.../android/manifmerger/data/69_remove_uses.xml | 0
.../android/manifmerger/data/70_expand_fqcns.xml | 0
.../manifmerger/data/71_extract_package_prefix.xml | 0
.../manifmerger/data/75_app_metadata_merge.xml | 0
.../manifmerger/data/76_app_metadata_ignore.xml | 0
.../manifmerger/data/77_app_metadata_conflict.xml | 0
.../java/com/android/manifmerger/data2/00_noop.xml | 0
.../manifmerger/data2/01_ignore_app_attr.xml | 0
.../data2/02_ignore_instrumentation.xml | 0
.../manifmerger/data2/03_inject_attributes.xml | 0
.../manifmerger/data2/04_inject_attributes.xml | 0
.../manifmerger/data2/05_inject_package.xml | 0
.../data2/05_inject_package_placeholder.xml | 0
.../data2/05_inject_package_with_overlays.xml | 0
.../06_inject_attributes_with_specific_prefix.xml | 0
.../manifmerger/data2/07_no_package_provided.xml | 0
.../data2/08_no_library_package_provided.xml | 0
.../manifmerger/data2/08b_library_injection.xml | 0
.../data2/09_overlay_package_provided.xml | 0
.../data2/09b_overlay_package_different.xml | 0
.../data2/09c_overlay_package_not_provided.xml | 0
.../manifmerger/data2/10_activity_merge.xml | 0
.../android/manifmerger/data2/11_activity_dup.xml | 0
.../android/manifmerger/data2/11b_activity_dup.xml | 0
.../com/android/manifmerger/data2/12_alias_dup.xml | 0
.../android/manifmerger/data2/13_service_dup.xml | 0
.../android/manifmerger/data2/14_receiver_dup.xml | 0
.../android/manifmerger/data2/15_provider_dup.xml | 0
.../android/manifmerger/data2/16_fqcn_merge.xml | 0
.../android/manifmerger/data2/17_fqcn_conflict.xml | 0
.../android/manifmerger/data2/18_fqcn_success.xml | 0
.../manifmerger/data2/20_uses_lib_merge.xml | 0
.../manifmerger/data2/21_uses_main_errors.xml | 0
.../manifmerger/data2/22_uses_lib_errors.xml | 0
.../manifmerger/data2/25_permission_merge.xml | 0
.../manifmerger/data2/26_permission_dup.xml | 0
.../manifmerger/data2/28_uses_perm_merge.xml | 0
.../manifmerger/data2/29_uses_perm_selector.xml | 0
.../data2/29b_uses_perm_invalidSelector.xml | 0
.../android/manifmerger/data2/30_uses_sdk_ok.xml | 0
.../manifmerger/data2/32_uses_sdk_minsdk_ok.xml | 0
.../data2/33_uses_sdk_minsdk_conflict.xml | 0
.../data2/33b_uses_sdk_minsdk_override.xml | 0
.../33c_uses_sdk_minsdk_override_and_conflict.xml | 0
.../data2/33d_uses_sdk_minsdk_codename.xml | 0
.../data2/34_inject_uses_sdk_no_dup.xml | 0
.../data2/36_uses_sdk_targetsdk_warning.xml | 0
.../manifmerger/data2/40_uses_feat_merge.xml | 0
.../manifmerger/data2/41_uses_feat_errors.xml | 0
.../manifmerger/data2/45_uses_feat_gles_once.xml | 0
.../data2/47_uses_feat_gles_conflict.xml | 0
.../manifmerger/data2/50_uses_conf_warning.xml | 0
.../data2/52_support_screens_warning.xml | 0
.../data2/54_compat_screens_warning.xml | 0
.../data2/56_support_gltext_warning.xml | 0
.../android/manifmerger/data2/60_merge_order.xml | 0
.../android/manifmerger/data2/65_override_app.xml | 0
.../android/manifmerger/data2/66_remove_app.xml | 0
.../manifmerger/data2/67_override_activities.xml | 0
.../android/manifmerger/data2/68_override_uses.xml | 0
.../android/manifmerger/data2/69_remove_uses.xml | 0
.../android/manifmerger/data2/70_expand_fqcns.xml | 0
.../data2/71_extract_package_prefix.xml | 0
.../manifmerger/data2/75_app_metadata_merge.xml | 0
.../manifmerger/data2/76_app_metadata_ignore.xml | 0
.../manifmerger/data2/77_app_metadata_conflict.xml | 0
.../com/android/manifmerger/data2/78_removeAll.xml | 0
.../android/manifmerger/data2/79_custom_node.xml | 0
.../profile/build.gradle | 0
.../profile/profile.iml | 0
.../com/android/builder/profile/AsyncRecorder.java | 47 +
.../android/builder/profile/ExecutionRecord.java | 0
.../com/android/builder/profile/ExecutionType.java | 124 +
.../android/builder/profile/ProcessRecorder.java | 0
.../builder/profile/ProcessRecorderFactory.java | 0
.../java/com/android/builder/profile/Recorder.java | 0
.../android/builder/profile/ThreadRecorder.java | 177 +
.../com/android/builder/tasks/BooleanLatch.java | 0
.../main/java/com/android/builder/tasks/Job.java | 97 +
.../java/com/android/builder/tasks/JobContext.java | 0
.../android/builder/tasks/QueueThreadContext.java | 0
.../builder/tasks/QueueThreadContextAdapter.java | 0
.../main/java/com/android/builder/tasks/Task.java | 0
.../java/com/android/builder/tasks/WorkQueue.java | 259 ++
.../builder/profile/ProcessRecorderTest.java | 0
.../builder/profile/ThreadRecorderTest.java | 0
.../project-test-lib/build.gradle | 0
.../project-test-lib/project-test-lib.iml | 0
.../build/tests/AndroidProjectConnector.java | 122 +
.../project-test/build.gradle | 0
.../project-test/build.xml | 0
.../project-test/project-test.iml | 0
.../java/com/android/build/tests/ProjectTest.java | 0
build-system/transform-api/build.gradle | 29 +
build.gradle | 153 +-
base/changes.txt => changes.txt | 0
{base/chartlib => chartlib}/build.gradle | 0
{base/chartlib => chartlib}/chartlib.iml | 0
.../android/tools/chartlib/AnimatedComponent.java | 0
.../android/tools/chartlib/CircularArrayList.java | 0
.../java/com/android/tools/chartlib/EventData.java | 0
.../android/tools/chartlib/SunburstComponent.java | 0
.../android/tools/chartlib/TimelineComponent.java | 810 +++++
.../com/android/tools/chartlib/TimelineData.java | 228 ++
.../com/android/tools/chartlib/ValuedTreeNode.java | 0
.../test/java/AnimatedComponentVisualTests.java | 572 ++++
.../tools/chartlib/CircularArrayListTest.java | 0
.../tools/chartlib/TimelineComponentTest.java | 0
.../android/tools/chartlib/TimelineDataTest.java | 162 +
{base/common => common}/.classpath | 0
{base/common => common}/.gitignore | 0
{base/common => common}/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../common => common}/.settings/org.moreunit.prefs | 0
{base/common => common}/NOTICE | 0
{base/common => common}/README.txt | 0
common/build.gradle | 22 +
common/common.iml | 18 +
common/src/main/java/com/android/SdkConstants.java | 1484 +++++++++
.../java/com/android/ide/common/blame/Message.java | 218 ++
.../com/android/ide/common/blame/SourceFile.java | 125 +
.../ide/common/blame/SourceFilePosition.java | 90 +
.../android/ide/common/blame/SourcePosition.java | 184 ++
.../src/main/java/com/android/io/FileWrapper.java | 0
.../main/java/com/android/io/FolderWrapper.java | 0
.../main/java/com/android/io/IAbstractFile.java | 0
.../main/java/com/android/io/IAbstractFolder.java | 0
.../java/com/android/io/IAbstractResource.java | 0
.../java/com/android/io/NonClosingInputStream.java | 0
.../main/java/com/android/io/StreamException.java | 0
.../java/com/android/prefs/AndroidLocation.java | 254 ++
.../java/com/android/sdklib/AndroidVersion.java | 338 ++
.../main/java/com/android/utils/ArrayUtils.java | 0
.../src/main/java/com/android/utils/AsmUtils.java | 48 +
.../src/main/java/com/android/utils/FileUtils.java | 373 +++
.../java/com/android/utils/GrabProcessOutput.java | 0
.../main/java/com/android/utils/HtmlBuilder.java | 360 ++
.../src/main/java/com/android/utils/ILogger.java | 0
.../main/java/com/android/utils/IReaderLogger.java | 0
.../main/java/com/android/utils/NullLogger.java | 0
.../src/main/java/com/android/utils/Pair.java | 0
.../java/com/android/utils/PositionXmlParser.java | 792 +++++
.../src/main/java/com/android/utils/SdkUtils.java | 514 +++
.../main/java/com/android/utils/SparseArray.java | 0
.../java/com/android/utils/SparseIntArray.java | 0
.../src/main/java/com/android/utils/StdLogger.java | 0
.../main/java/com/android/utils/StringHelper.java | 158 +
.../src/main/java/com/android/utils/XmlUtils.java | 679 ++++
.../main/java/com/android/xml/AndroidManifest.java | 0
.../java/com/android/xml/AndroidXPathFactory.java | 0
{base/common => common}/src/test/.classpath | 0
{base/common => common}/src/test/.project | 0
.../src/test/.settings/org.moreunit.prefs | 0
.../ide/common/blame/SourcePositionTest.java | 82 +
.../com/android/io/NonClosingInputStreamTest.java | 0
.../test/java/com/android/utils/FileUtilsTest.java | 53 +
.../java/com/android/utils/HtmlBuilderTest.java | 160 +
.../com/android/utils/PositionXmlParserTest.java | 0
.../test/java/com/android/utils/SdkUtilsTest.java | 361 ++
.../java/com/android/utils/StringHelperTest.java | 52 +
.../test/java/com/android/utils/XmlUtilsTest.java | 518 +++
{base/ddmlib => ddmlib}/.classpath | 0
{base/ddmlib => ddmlib}/.gitignore | 0
{base/ddmlib => ddmlib}/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
{base/ddmlib => ddmlib}/NOTICE | 0
{base/ddmlib => ddmlib}/build.gradle | 0
{base/ddmlib => ddmlib}/ddmlib.iml | 0
.../ddmlib/AdbCommandRejectedException.java | 0
.../main/java/com/android/ddmlib/AdbHelper.java | 940 ++++++
.../main/java/com/android/ddmlib/AdbVersion.java | 0
.../java/com/android/ddmlib/AllocationInfo.java | 0
.../java/com/android/ddmlib/AllocationsParser.java | 0
.../com/android/ddmlib/AndroidDebugBridge.java | 1126 +++++++
.../com/android/ddmlib/BadPacketException.java | 0
.../java/com/android/ddmlib/BatteryFetcher.java | 0
.../java/com/android/ddmlib/BitmapDecoder.java | 168 +
.../java/com/android/ddmlib/ByteBufferUtil.java | 0
.../java/com/android/ddmlib/CanceledException.java | 0
.../main/java/com/android/ddmlib/ChunkHandler.java | 249 ++
.../src/main/java/com/android/ddmlib/Client.java | 855 +++++
.../main/java/com/android/ddmlib/ClientData.java | 866 +++++
.../android/ddmlib/CollectingOutputReceiver.java | 0
.../main/java/com/android/ddmlib/DdmConstants.java | 0
.../java/com/android/ddmlib/DdmJdwpExtension.java | 134 +
.../java/com/android/ddmlib/DdmPreferences.java | 0
.../java/com/android/ddmlib/DebugPortManager.java | 0
.../src/main/java/com/android/ddmlib/Debugger.java | 344 ++
.../src/main/java/com/android/ddmlib/Device.java | 1134 +++++++
.../java/com/android/ddmlib/DeviceMonitor.java | 893 +++++
.../java/com/android/ddmlib/EmulatorConsole.java | 795 +++++
.../com/android/ddmlib/FileListingService.java | 860 +++++
.../java/com/android/ddmlib/HandleAppName.java | 0
.../main/java/com/android/ddmlib/HandleExit.java | 76 +
.../main/java/com/android/ddmlib/HandleHeap.java | 405 +++
.../main/java/com/android/ddmlib/HandleHello.java | 230 ++
.../java/com/android/ddmlib/HandleNativeHeap.java | 351 ++
.../java/com/android/ddmlib/HandleProfiling.java | 354 ++
.../main/java/com/android/ddmlib/HandleTest.java | 0
.../main/java/com/android/ddmlib/HandleThread.java | 379 +++
.../java/com/android/ddmlib/HandleViewDebug.java | 359 ++
.../main/java/com/android/ddmlib/HandleWait.java | 0
.../main/java/com/android/ddmlib/HeapSegment.java | 0
.../src/main/java/com/android/ddmlib/IDevice.java | 681 ++++
.../com/android/ddmlib/IShellEnabledDevice.java | 0
.../com/android/ddmlib/IShellOutputReceiver.java | 0
.../java/com/android/ddmlib/IStackTraceInfo.java | 0
.../java/com/android/ddmlib/InstallException.java | 0
.../java/com/android/ddmlib/JdwpHandshake.java | 74 +
.../main/java/com/android/ddmlib/JdwpPacket.java | 292 ++
ddmlib/src/main/java/com/android/ddmlib/Log.java | 379 +++
.../java/com/android/ddmlib/MonitorThread.java | 675 ++++
.../java/com/android/ddmlib/MultiLineReceiver.java | 0
.../com/android/ddmlib/NativeAllocationInfo.java | 0
.../com/android/ddmlib/NativeLibraryMapInfo.java | 0
.../com/android/ddmlib/NativeStackCallInfo.java | 117 +
.../com/android/ddmlib/NullOutputReceiver.java | 0
.../java/com/android/ddmlib/PropertyFetcher.java | 192 ++
.../src/main/java/com/android/ddmlib/RawImage.java | 0
.../com/android/ddmlib/ScreenRecorderOptions.java | 0
.../ddmlib/ShellCommandUnresponsiveException.java | 0
.../java/com/android/ddmlib/SplitApkInstaller.java | 269 ++
.../java/com/android/ddmlib/SyncException.java | 0
.../main/java/com/android/ddmlib/SyncService.java | 0
.../main/java/com/android/ddmlib/ThreadInfo.java | 0
.../java/com/android/ddmlib/TimeoutException.java | 0
.../java/com/android/ddmlib/jdwp/JdwpAgent.java | 101 +
.../java/com/android/ddmlib/jdwp/JdwpCommands.java | 138 +
.../com/android/ddmlib/jdwp/JdwpExtension.java | 27 +
.../com/android/ddmlib/jdwp/JdwpInterceptor.java | 26 +
.../java/com/android/ddmlib/jdwp/JdwpPayload.java | 25 +
.../java/com/android/ddmlib/jdwp/JdwpProtocol.java | 80 +
.../android/ddmlib/jdwp/packets/IdSizesReply.java | 41 +
.../com/android/ddmlib/log/EventContainer.java | 0
.../com/android/ddmlib/log/EventLogParser.java | 0
.../android/ddmlib/log/EventValueDescription.java | 0
.../com/android/ddmlib/log/GcEventContainer.java | 0
.../android/ddmlib/log/InvalidTypeException.java | 0
.../ddmlib/log/InvalidValueTypeException.java | 0
.../java/com/android/ddmlib/log/LogReceiver.java | 0
.../com/android/ddmlib/logcat/LogCatFilter.java | 231 ++
.../com/android/ddmlib/logcat/LogCatHeader.java | 89 +
.../com/android/ddmlib/logcat/LogCatListener.java | 0
.../com/android/ddmlib/logcat/LogCatMessage.java | 107 +
.../android/ddmlib/logcat/LogCatMessageParser.java | 137 +
.../android/ddmlib/logcat/LogCatReceiverTask.java | 132 +
.../com/android/ddmlib/logcat/LogCatTimestamp.java | 188 ++
.../testrunner/IRemoteAndroidTestRunner.java | 0
.../ddmlib/testrunner/ITestRunListener.java | 0
.../testrunner/InstrumentationResultParser.java | 0
.../ddmlib/testrunner/RemoteAndroidTestRunner.java | 333 ++
.../android/ddmlib/testrunner/TestIdentifier.java | 0
.../com/android/ddmlib/testrunner/TestResult.java | 0
.../android/ddmlib/testrunner/TestRunResult.java | 0
.../ddmlib/testrunner/XmlTestRunListener.java | 0
.../java/com/android/ddmlib/utils/ArrayHelper.java | 0
.../com/android/ddmlib/utils/DebuggerPorts.java | 0
{base/ddmlib => ddmlib}/src/test/.classpath | 0
{base/ddmlib => ddmlib}/src/test/.project | 0
.../java/com/android/ddmlib/AdbVersionTest.java | 0
.../com/android/ddmlib/AndroidDebugBridgeTest.java | 0
.../com/android/ddmlib/BatteryFetcherTest.java | 0
.../java/com/android/ddmlib/DeviceMonitorTest.java | 0
.../test/java/com/android/ddmlib/DeviceTest.java | 0
.../com/android/ddmlib/EmulatorConsoleTest.java | 0
.../com/android/ddmlib/FileListingServiceTest.java | 81 +
.../com/android/ddmlib/PropertyFetcherTest.java | 0
.../ddmlib/SysFsBatteryLevelReceiverTest.java | 0
.../ddmlib/allocations/AllocationsParserTest.java | 0
.../android/ddmlib/logcat/LogCatFilterTest.java | 189 ++
.../ddmlib/logcat/LogCatMessageParserTest.java | 136 +
.../android/ddmlib/logcat/LogCatTimestampTest.java | 58 +
.../InstrumentationResultParserTest.java | 0
.../testrunner/RemoteAndroidTestRunnerTest.java | 0
.../ddmlib/testrunner/TestRunResultTest.java | 0
.../ddmlib/testrunner/XmlTestRunListenerTest.java | 0
.../android/ddmlib/utils/DebuggerPortsTest.java | 0
.../.gitignore | 0
.../MODULE_LICENSE_APACHE2 | 0
.../app/.classpath | 0
.../app/.gitignore | 0
.../app/.project | 0
.../app/.settings/org.eclipse.jdt.ui.prefs | 0
.../app/etc/README | 0
.../app/etc/device_validator | 0
.../src/com/android/validator/DeviceValidator.java | 0
.../dvlib/.classpath | 0
.../dvlib/.gitignore | 0
.../dvlib/.project | 0
.../dvlib/.settings/org.eclipse.jdt.core.prefs | 0
.../dvlib/NOTICE | 0
.../dvlib/build.gradle | 0
.../dvlib/dvlib.iml | 0
.../main/java/com/android/dvlib/DeviceSchema.java | 0
.../main/resources/com/android/dvlib/devices-1.xsd | 0
.../main/resources/com/android/dvlib/devices-2.xsd | 955 ++++++
.../java/com/android/dvlib/DeviceSchemaTest.java | 0
.../test/resources/com/android/dvlib/devices.xml | 0
.../com/android/dvlib/devices_minimal.xml | 0
.../com/android/dvlib/devices_no_default.xml | 0
.../com/android/dvlib/devices_no_hardware.xml | 0
.../com/android/dvlib/devices_no_software.xml | 0
.../com/android/dvlib/devices_no_states.xml | 0
.../android/dvlib/devices_too_many_defaults.xml | 0
.../resources/com/android/dvlib/devices_v2.xml | 0
.../resources/com/android/dvlib/extras/frame.jpeg | 0
.../resources/com/android/dvlib/extras/frame.png | 0
.../com/android/dvlib/extras/sixteen.jpeg | 0
.../resources/com/android/dvlib/extras/sixteen.png | 0
.../com/android/dvlib/extras/sixtyfour.jpeg | 0
.../com/android/dvlib/extras/sixtyfour.png | 0
{base/lint => draw9patch}/MODULE_LICENSE_APACHE2 | 0
{base/draw9patch => draw9patch}/NOTICE | 0
{base/draw9patch => draw9patch}/build.gradle | 0
{base/draw9patch => draw9patch}/draw9patch.iml | 0
{base/draw9patch => draw9patch}/etc/draw9patch | 0
{base/draw9patch => draw9patch}/etc/draw9patch.bat | 0
.../java/com/android/draw9patch/Application.java | 0
.../draw9patch/graphics/GraphicsUtilities.java | 0
.../com/android/draw9patch/ui/CorruptPatch.java | 0
.../com/android/draw9patch/ui/GradientPanel.java | 0
.../android/draw9patch/ui/ImageEditorPanel.java | 0
.../draw9patch/ui/ImageTransferHandler.java | 0
.../com/android/draw9patch/ui/ImageViewer.java | 0
.../java/com/android/draw9patch/ui/MainFrame.java | 0
.../com/android/draw9patch/ui/OpenFilePanel.java | 0
.../main/java/com/android/draw9patch/ui/Pair.java | 0
.../java/com/android/draw9patch/ui/PatchInfo.java | 0
.../com/android/draw9patch/ui/PngFileFilter.java | 0
.../com/android/draw9patch/ui/StretchesViewer.java | 0
.../draw9patch/ui/action/BackgroundAction.java | 0
.../android/draw9patch/ui/action/ExitAction.java | 0
.../android/draw9patch/ui/action/OpenAction.java | 0
.../android/draw9patch/ui/action/SaveAction.java | 0
.../src/main/resources/images/checker.png | Bin
.../src/main/resources/images/drop.png | Bin
.../com/android/draw9patch/ui/PatchInfoTest.java | 0
{base/files => files}/UPDATING_DEVICES.txt | 0
{base/files => files}/android.el | 0
.../files => files}/proguard-android-optimize.txt | 0
{base/files => files}/proguard-android.txt | 0
{base/files => files}/proguard-project.txt | 0
{base/files => files}/typos/typos-de.txt | 0
{base/files => files}/typos/typos-en.txt | 0
{base/files => files}/typos/typos-es.txt | 0
{base/files => files}/typos/typos-hu.txt | 0
{base/files => files}/typos/typos-it.txt | 0
{base/files => files}/typos/typos-nb.txt | 0
{base/files => files}/typos/typos-pt.txt | 0
{base/files => files}/typos/typos-tr.txt | 0
gradle.properties | 2 -
instant-run/instant-run-annotations/build.gradle | 25 +
.../instant-run-annotations.iml | 12 +
.../android/tools/ir/api/DisableInstantRun.java | 37 +
instant-run/instant-run-client/build.gradle | 24 +
.../instant-run-client/instant-run-client.iml | 17 +
.../java/com/android/tools/fd/client/AppState.java | 32 +
.../tools/fd/client/ApplicationPatchUtil.java | 55 +
.../com/android/tools/fd/client/Communicator.java | 33 +
.../tools/fd/client/InstantRunArtifact.java | 39 +
.../tools/fd/client/InstantRunArtifactType.java | 60 +
.../tools/fd/client/InstantRunBuildInfo.java | 272 ++
.../android/tools/fd/client/InstantRunClient.java | 809 +++++
.../fd/client/InstantRunPushFailedException.java | 24 +
.../com/android/tools/fd/client/UpdateMode.java | 65 +
.../com/android/tools/fd/client/UserFeedback.java | 33 +
.../tools/fd/client/InstantRunBuildInfoTest.java | 93 +
.../instantrun/build-info-no-artifacts.xml | 36 +
.../test/resources/instantrun/build-info-res.xml | 41 +
.../src/test/resources/instantrun/build-info1.xml | 90 +
.../src/test/resources/instantrun/build-info2.xml | 68 +
.../src/test/resources/instantrun/no-changes.xml | 36 +
instant-run/instant-run-common/build.gradle | 25 +
.../instant-run-common/instant-run-common.iml | 12 +
.../main/java/com/android/tools/fd/common/Log.java | 37 +
.../android/tools/fd/common/ProtocolConstants.java | 94 +
.../android/tools/fd/runtime/ApplicationPatch.java | 80 +
.../java/com/android/tools/fd/runtime/Paths.java | 68 +
instant-run/instant-run-runtime/build.gradle | 30 +
.../instant-run-runtime/instant-run-runtime.iml | 19 +
.../fd/runtime/AbstractPatchesLoaderImpl.java | 64 +
.../tools/fd/runtime/AndroidInstantRuntime.java | 268 ++
.../com/android/tools/fd/runtime/BasicType.java | 57 +
.../tools/fd/runtime/IncrementalChange.java | 21 +
.../tools/fd/runtime/InstantReloadException.java | 32 +
.../android/tools/fd/runtime/PatchesLoader.java | 22 +
.../tools/fd/runtime/PatchesLoaderDumper.java | 34 +
.../tools/fd/runtime/ApplicationPatchTest.java | 89 +
instant-run/instant-run-server/.gitignore | 3 +
instant-run/instant-run-server/AndroidManifest.xml | 17 +
instant-run/instant-run-server/build.gradle | 49 +
.../instant-run-server/instant-run-server.iml | 24 +
instant-run/instant-run-server/project.properties | 15 +
.../java/com/android/tools/fd/runtime/AppInfo.java | 56 +
.../tools/fd/runtime/BootstrapApplication.java | 329 ++
.../com/android/tools/fd/runtime/FileManager.java | 859 +++++
.../tools/fd/runtime/IncrementalClassLoader.java | 176 +
.../android/tools/fd/runtime/MonkeyPatcher.java | 585 ++++
.../com/android/tools/fd/runtime/Restarter.java | 308 ++
.../java/com/android/tools/fd/runtime/Server.java | 591 ++++
instant-run/instant-run.iml | 6 +
{base/jack => jack}/jack-api/README.txt | 0
{base/jack => jack}/jack-api/build.gradle | 0
{base/jack => jack}/jack-api/jack-api.iml | 0
.../jack/api/ConfigNotSupportedException.java | 0
.../main/java/com/android/jack/api/JackConfig.java | 0
.../java/com/android/jack/api/JackProvider.java | 0
.../android/jack/api/v01/Api01CompilationTask.java | 0
.../java/com/android/jack/api/v01/Api01Config.java | 0
.../com/android/jack/api/v01/ChainedException.java | 0
.../android/jack/api/v01/CompilationException.java | 0
.../jack/api/v01/ConfigurationException.java | 0
.../com/android/jack/api/v01/DebugInfoLevel.java | 0
.../android/jack/api/v01/JavaSourceVersion.java | 0
.../com/android/jack/api/v01/MultiDexKind.java | 0
.../com/android/jack/api/v01/ReporterKind.java | 0
.../jack/api/v01/ResourceCollisionPolicy.java | 0
.../android/jack/api/v01/TypeCollisionPolicy.java | 0
.../jack/api/v01/UnrecoverableException.java | 0
.../com/android/jack/api/v01/VerbosityLevel.java | 0
{base/jack => jack}/jill-api/README.txt | 0
{base/jack => jack}/jill-api/build.gradle | 0
{base/jack => jack}/jill-api/jill-api.iml | 0
.../jill/api/ConfigNotSupportedException.java | 0
.../main/java/com/android/jill/api/JillConfig.java | 0
.../java/com/android/jill/api/JillProvider.java | 0
.../java/com/android/jill/api/v01/Api01Config.java | 0
.../android/jill/api/v01/Api01TranslationTask.java | 0
.../jill/api/v01/ConfigurationException.java | 0
.../android/jill/api/v01/TranslationException.java | 0
jaxb-inheritance-plugin/build.gradle | 11 +
.../jaxb-inheritance-plugin.iml | 17 +
.../jaxb/inheritance/InheritancePlugin.java | 557 ++++
.../META-INF/services/com.sun.tools.xjc.Plugin | 1 +
{base/jobb => jobb}/.gitignore | 0
{base/jobb => jobb}/NOTICE | 0
{base/jobb => jobb}/build.gradle | 0
{base/jobb => jobb}/etc/jobb | 0
{base/jobb => jobb}/etc/jobb.bat | 0
{base/jobb => jobb}/jobb.iml | 0
jobb/src/main/java/Twofish/Twofish_Algorithm.java | 836 +++++
.../src/main/java/Twofish/Twofish_Properties.java | 0
.../src/main/java/com/android/jobb/Base64.java | 0
.../src/main/java/com/android/jobb/Encoder.java | 0
.../java/com/android/jobb/EncryptedBlockFile.java | 0
.../src/main/java/com/android/jobb/Main.java | 0
.../src/main/java/com/android/jobb/ObbFile.java | 0
.../src/main/java/com/android/jobb/PBKDF.java | 0
{base/layoutlib-api => layoutlib-api}/.classpath | 0
{base/layoutlib-api => layoutlib-api}/.gitignore | 0
{base/layoutlib-api => layoutlib-api}/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
{base/layoutlib-api => layoutlib-api}/NOTICE | 0
{base/layoutlib-api => layoutlib-api}/README.txt | 0
{base/layoutlib-api => layoutlib-api}/build.gradle | 0
.../layoutlib-api-base.iml | 0
.../layoutlib-api.iml | 0
.../sample/.classpath | 0
.../sample/.project | 0
.../sample/README.txt | 0
.../src/com/example/android/render/Main.java | 0
.../example/android/render/ProjectCallback.java | 0
.../com/example/android/render/RenderService.java | 0
.../android/render/RenderServiceFactory.java | 0
.../com/example/android/render/StdOutLogger.java | 0
.../src/com/example/android/render/XmlParser.java | 0
.../sample/testproject/AndroidManifest.xml | 0
.../sample/testproject/build.properties | 0
.../sample/testproject/build.xml | 0
.../sample/testproject/default.properties | 0
.../sample/testproject/proguard.cfg | 0
.../sample/testproject}/res/drawable-hdpi/icon.png | Bin
.../sample/testproject}/res/drawable-ldpi/icon.png | Bin
.../sample/testproject/res/drawable-mdpi}/icon.png | Bin
.../sample/testproject/res/layout/main.xml | 0
.../sample/testproject/res/values/strings.xml | 0
.../com/example/layoutlib/testproject/Main.java | 0
.../common/rendering/api/ActionBarCallback.java | 0
.../ide/common/rendering/api/AdapterBinding.java | 0
.../common/rendering/api/ArrayResourceValue.java | 0
.../ide/common/rendering/api/AssetRepository.java | 0
.../common/rendering/api/AttrResourceValue.java | 0
.../android/ide/common/rendering/api/Bridge.java | 184 ++
.../ide/common/rendering/api/Capability.java | 0
.../ide/common/rendering/api/DataBindingItem.java | 0
.../api/DeclareStyleableResourceValue.java | 0
.../rendering/api/DensityBasedResourceValue.java | 0
.../ide/common/rendering/api/DrawableParams.java | 0
.../android/ide/common/rendering/api/Features.java | 121 +
.../ide/common/rendering/api/HardwareConfig.java | 0
.../common/rendering/api/IAnimationListener.java | 0
.../ide/common/rendering/api/IImageFactory.java | 0
.../common/rendering/api/ILayoutPullParser.java | 0
.../ide/common/rendering/api/IProjectCallback.java | 0
.../common/rendering/api/ItemResourceValue.java | 0
.../ide/common/rendering/api/LayoutLog.java | 0
.../common/rendering/api/LayoutlibCallback.java | 134 +
.../ide/common/rendering/api/MergeCookie.java | 0
.../ide/common/rendering/api/ParserFactory.java | 0
.../common/rendering/api/PluralsResourceValue.java | 0
.../ide/common/rendering/api/RenderParams.java | 0
.../ide/common/rendering/api/RenderResources.java | 0
.../ide/common/rendering/api/RenderSession.java | 337 ++
.../common/rendering/api/ResourceReference.java | 0
.../ide/common/rendering/api/ResourceValue.java | 132 +
.../android/ide/common/rendering/api/Result.java | 0
.../ide/common/rendering/api/SessionParams.java | 0
.../common/rendering/api/StyleResourceValue.java | 0
.../common/rendering/api/TextResourceValue.java | 0
.../android/ide/common/rendering/api/ViewInfo.java | 0
.../android/ide/common/rendering/api/ViewType.java | 0
.../layoutlib/api/IDensityBasedResourceValue.java | 0
.../com/android/layoutlib/api/ILayoutBridge.java | 0
.../java/com/android/layoutlib/api/ILayoutLog.java | 0
.../com/android/layoutlib/api/ILayoutResult.java | 0
.../android/layoutlib/api/IProjectCallback.java | 0
.../com/android/layoutlib/api/IResourceValue.java | 0
.../android/layoutlib/api/IStyleResourceValue.java | 0
.../com/android/layoutlib/api/IXmlPullParser.java | 0
.../main/java/com/android/resources/Density.java | 184 ++
.../android/resources/FolderTypeRelationship.java | 0
.../main/java/com/android/resources/Keyboard.java | 0
.../java/com/android/resources/KeyboardState.java | 0
.../com/android/resources/LayoutDirection.java | 0
.../java/com/android/resources/Navigation.java | 0
.../com/android/resources/NavigationState.java | 0
.../main/java/com/android/resources/NightMode.java | 0
.../com/android/resources/ResourceConstants.java | 0
.../java/com/android/resources/ResourceEnum.java | 0
.../com/android/resources/ResourceFolderType.java | 0
.../java/com/android/resources/ResourceType.java | 0
.../com/android/resources/ScreenOrientation.java | 0
.../java/com/android/resources/ScreenRatio.java | 0
.../java/com/android/resources/ScreenRound.java | 0
.../java/com/android/resources/ScreenSize.java | 0
.../java/com/android/resources/TouchScreen.java | 0
.../main/java/com/android/resources/UiMode.java | 0
.../src/main/java/com/android/util/Pair.java | 0
.../src/test/.classpath | 0
.../src/test/.project | 0
.../resources/FolderTypeRelationShipTest.java | 0
.../android/resources/ResourceFolderTypeTest.java | 0
{base/legacy => legacy}/archquery/.classpath | 0
{base/legacy => legacy}/archquery/.gitignore | 0
{base/legacy => legacy}/archquery/.project | 0
{base/legacy/ant-tasks => legacy/archquery}/NOTICE | 0
{base/legacy => legacy}/archquery/build.gradle | 0
.../src/main/java/com/android/archquery/Main.java | 0
{base/lint => lint}/.gitignore | 0
{base/sdklib => lint}/MODULE_LICENSE_APACHE2 | 0
{base/lint => lint}/cli/.classpath | 0
{base/lint => lint}/cli/.project | 0
.../cli}/.settings/org.eclipse.jdt.core.prefs | 0
{base/lint => lint}/cli/NOTICE | 0
{base/lint => lint}/cli/build.gradle | 0
{base/lint => lint}/cli/etc/lint | 0
{base/lint => lint}/cli/etc/lint.bat | 0
lint/cli/lint-cli.iml | 27 +
.../java/com/android/tools/lint/EcjParser.java | 2669 +++++++++++++++
.../tools/lint/ExternalAnnotationRepository.java | 1178 +++++++
.../java/com/android/tools/lint/HtmlReporter.java | 827 +++++
.../java/com/android/tools/lint/LintCliClient.java | 764 +++++
.../java/com/android/tools/lint/LintCliFlags.java | 0
.../com/android/tools/lint/LintCliXmlParser.java | 0
.../src/main/java/com/android/tools/lint/Main.java | 1069 ++++++
.../tools/lint/MultiProjectHtmlReporter.java | 0
.../main/java/com/android/tools/lint/Reporter.java | 539 +++
.../java/com/android/tools/lint/TextReporter.java | 0
.../main/java/com/android/tools/lint/Warning.java | 263 ++
.../java/com/android/tools/lint/XmlReporter.java | 210 ++
.../main/java/com/android/tools/lint/default.css | 0
.../main/java/com/android/tools/lint/hololike.css | 0
.../java/com/android/tools/lint/lint-error.png | Bin
.../main/java/com/android/tools/lint/lint-run.png | Bin
.../java/com/android/tools/lint/lint-warning.png | Bin
{base/lint => lint}/cli/src/test/.classpath | 0
{base/lint => lint}/cli/src/test/.project | 0
.../.settings/org.eclipse.core.resources.prefs | 0
.../cli/src/test/.settings/org.moreunit.prefs | 0
{base/lint => lint}/libs/lint-api/.classpath | 0
{base/lint => lint}/libs/lint-api/.project | 0
.../lint-api}/.settings/org.eclipse.jdt.core.prefs | 0
.../libs/lint-api/.settings/org.moreunit.prefs | 0
{base/lint => lint}/libs/lint-api/NOTICE | 0
{base/lint => lint}/libs/lint-api/build.gradle | 0
lint/libs/lint-api/lint-api-base.iml | 21 +
{base/lint => lint}/libs/lint-api/lint-api.iml | 0
.../android/tools/lint/client/api/AsmVisitor.java | 0
.../client/api/CircularDependencyException.java | 0
.../android/tools/lint/client/api/ClassEntry.java | 0
.../lint/client/api/CompositeIssueRegistry.java | 0
.../tools/lint/client/api/Configuration.java | 0
.../lint/client/api/DefaultConfiguration.java | 0
.../tools/lint/client/api/DefaultSdkInfo.java | 0
.../tools/lint/client/api/IssueRegistry.java | 347 ++
.../lint/client/api/JarFileIssueRegistry.java | 201 ++
.../android/tools/lint/client/api/JavaParser.java | 790 +++++
.../android/tools/lint/client/api/JavaVisitor.java | 1478 +++++++++
.../android/tools/lint/client/api/LintClient.java | 1243 +++++++
.../android/tools/lint/client/api/LintDriver.java | 2609 +++++++++++++++
.../tools/lint/client/api/LintListener.java | 0
.../android/tools/lint/client/api/LintRequest.java | 0
.../tools/lint/client/api/OtherFileVisitor.java | 0
.../tools/lint/client/api/ResourceVisitor.java | 0
.../com/android/tools/lint/client/api/SdkInfo.java | 0
.../android/tools/lint/client/api/XmlParser.java | 0
.../android/tools/lint/detector/api/Category.java | 190 ++
.../tools/lint/detector/api/ClassContext.java | 721 ++++
.../tools/lint/detector/api/ConstantEvaluator.java | 575 ++++
.../android/tools/lint/detector/api/Context.java | 0
.../tools/lint/detector/api/DefaultPosition.java | 0
.../android/tools/lint/detector/api/Detector.java | 782 +++++
.../tools/lint/detector/api/Implementation.java | 0
.../com/android/tools/lint/detector/api/Issue.java | 0
.../tools/lint/detector/api/JavaContext.java | 507 +++
.../tools/lint/detector/api/LayoutDetector.java | 0
.../android/tools/lint/detector/api/LintUtils.java | 1266 +++++++
.../android/tools/lint/detector/api/Location.java | 805 +++++
.../android/tools/lint/detector/api/Position.java | 0
.../android/tools/lint/detector/api/Project.java | 1411 ++++++++
.../tools/lint/detector/api/ResourceContext.java | 0
.../lint/detector/api/ResourceXmlDetector.java | 0
.../com/android/tools/lint/detector/api/Scope.java | 265 ++
.../android/tools/lint/detector/api/Severity.java | 0
.../com/android/tools/lint/detector/api/Speed.java | 0
.../tools/lint/detector/api/TextFormat.java | 354 ++
.../tools/lint/detector/api/TypeEvaluator.java | 263 ++
.../tools/lint/detector/api/XmlContext.java | 0
{base/lint => lint}/libs/lint-checks/.classpath | 0
{base/lint => lint}/libs/lint-checks/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../libs/lint-checks/.settings/org.moreunit.prefs | 0
{base/lint => lint}/libs/lint-checks/NOTICE | 0
{base/lint => lint}/libs/lint-checks/build.gradle | 0
.../libs/lint-checks/lint-checks-base.iml | 0
.../lint => lint}/libs/lint-checks/lint-checks.iml | 0
.../tools/lint/checks/AccessibilityDetector.java | 0
.../checks/AddJavascriptInterfaceDetector.java | 0
.../android/tools/lint/checks/AlarmDetector.java | 0
.../checks/AllowAllHostnameVerifierDetector.java | 117 +
.../lint/checks/AlwaysShowActionDetector.java | 0
.../tools/lint/checks/AndroidAutoDetector.java | 426 +++
.../tools/lint/checks/AndroidTvDetector.java | 553 ++++
.../tools/lint/checks/AnnotationDetector.java | 704 ++++
.../java/com/android/tools/lint/checks/Api.java | 109 +
.../com/android/tools/lint/checks/ApiClass.java | 506 +++
.../com/android/tools/lint/checks/ApiDetector.java | 2442 ++++++++++++++
.../com/android/tools/lint/checks/ApiLookup.java | 1351 ++++++++
.../com/android/tools/lint/checks/ApiPackage.java | 69 +
.../com/android/tools/lint/checks/ApiParser.java | 154 +
.../tools/lint/checks/AppCompatCallDetector.java | 0
.../lint/checks/AppCompatResourceDetector.java | 109 +
.../tools/lint/checks/AppIndexingApiDetector.java | 715 ++++
.../lint/checks/AppLinksAutoVerifyDetector.java | 421 +++
.../tools/lint/checks/ArraySizeDetector.java | 0
.../android/tools/lint/checks/AssertDetector.java | 0
.../lint/checks/BadHostnameVerifierDetector.java | 166 +
.../tools/lint/checks/BuiltinIssueRegistry.java | 342 ++
.../android/tools/lint/checks/ButtonDetector.java | 785 +++++
.../tools/lint/checks/ByteOrderMarkDetector.java | 0
.../tools/lint/checks/CallSuperDetector.java | 222 ++
.../tools/lint/checks/ChildCountDetector.java | 0
.../lint/checks/CipherGetInstanceDetector.java | 129 +
.../android/tools/lint/checks/CleanupDetector.java | 637 ++++
.../checks/ClickableViewAccessibilityDetector.java | 0
.../android/tools/lint/checks/CommentDetector.java | 0
.../tools/lint/checks/ControlFlowGraph.java | 536 +++
.../tools/lint/checks/CustomViewDetector.java | 0
.../tools/lint/checks/CutPasteDetector.java | 0
.../tools/lint/checks/DateFormatDetector.java | 0
.../tools/lint/checks/DeprecationDetector.java | 188 ++
.../tools/lint/checks/DetectMissingPrefix.java | 0
.../tools/lint/checks/DosLineEndingDetector.java | 118 +
.../tools/lint/checks/DuplicateIdDetector.java | 0
.../lint/checks/DuplicateResourceDetector.java | 306 ++
.../tools/lint/checks/ExtraTextDetector.java | 0
.../tools/lint/checks/FieldGetterDetector.java | 0
.../tools/lint/checks/FragmentDetector.java | 176 +
.../lint/checks/FullBackupContentDetector.java | 235 ++
.../tools/lint/checks/GetSignaturesDetector.java | 0
.../android/tools/lint/checks/GradleDetector.java | 1305 ++++++++
.../tools/lint/checks/GridLayoutDetector.java | 0
.../android/tools/lint/checks/HandlerDetector.java | 178 +
.../lint/checks/HardcodedDebugModeDetector.java | 0
.../tools/lint/checks/HardcodedValuesDetector.java | 152 +
.../android/tools/lint/checks/IconDetector.java | 0
.../android/tools/lint/checks/IncludeDetector.java | 0
.../lint/checks/InefficientWeightDetector.java | 0
.../tools/lint/checks/InvalidPackageDetector.java | 0
.../tools/lint/checks/JavaPerformanceDetector.java | 0
.../lint/checks/JavaScriptInterfaceDetector.java | 0
.../tools/lint/checks/LabelForDetector.java | 0
.../lint/checks/LayoutConsistencyDetector.java | 0
.../tools/lint/checks/LayoutInflationDetector.java | 0
.../android/tools/lint/checks/LocaleDetector.java | 175 +
.../tools/lint/checks/LocaleFolderDetector.java | 0
.../com/android/tools/lint/checks/LogDetector.java | 327 ++
.../tools/lint/checks/ManifestDetector.java | 1071 ++++++
.../lint/checks/ManifestResourceDetector.java | 321 ++
.../tools/lint/checks/ManifestTypoDetector.java | 0
.../android/tools/lint/checks/MathDetector.java | 102 +
.../lint/checks/MergeRootFrameLayoutDetector.java | 0
.../tools/lint/checks/MissingClassDetector.java | 523 +++
.../tools/lint/checks/MissingIdDetector.java | 0
.../tools/lint/checks/NamespaceDetector.java | 0
.../tools/lint/checks/NegativeMarginDetector.java | 0
.../lint/checks/NestedScrollingWidgetDetector.java | 0
.../tools/lint/checks/NfcTechListDetector.java | 0
.../checks/NonInternationalizedSmsDetector.java | 0
.../lint/checks/ObsoleteLayoutParamsDetector.java | 0
.../android/tools/lint/checks/OnClickDetector.java | 0
.../tools/lint/checks/OverdrawDetector.java | 0
.../lint/checks/OverrideConcreteDetector.java | 0
.../tools/lint/checks/OverrideDetector.java | 0
.../android/tools/lint/checks/ParcelDetector.java | 99 +
.../tools/lint/checks/PermissionFinder.java | 254 ++
.../tools/lint/checks/PermissionHolder.java | 0
.../tools/lint/checks/PermissionRequirement.java | 789 +++++
.../android/tools/lint/checks/PluralsDatabase.java | 333 ++
.../android/tools/lint/checks/PluralsDetector.java | 0
.../lint/checks/PreferenceActivityDetector.java | 0
.../tools/lint/checks/PrivateKeyDetector.java | 0
.../tools/lint/checks/PrivateResourceDetector.java | 332 ++
.../tools/lint/checks/ProguardDetector.java | 0
.../tools/lint/checks/PropertyFileDetector.java | 194 ++
.../android/tools/lint/checks/PxUsageDetector.java | 0
.../tools/lint/checks/ReadParcelableDetector.java | 120 +
.../tools/lint/checks/RecyclerViewDetector.java | 279 ++
.../tools/lint/checks/RegistrationDetector.java | 329 ++
.../tools/lint/checks/RelativeOverlapDetector.java | 413 +++
.../lint/checks/RequiredAttributeDetector.java | 647 ++++
.../tools/lint/checks/ResourceCycleDetector.java | 0
.../tools/lint/checks/ResourcePrefixDetector.java | 205 ++
.../tools/lint/checks/ResourceUsageModel.java | 1655 ++++++++++
.../tools/lint/checks/RestrictionsDetector.java | 289 ++
.../com/android/tools/lint/checks/RtlDetector.java | 648 ++++
.../android/tools/lint/checks/SQLiteDetector.java | 0
.../tools/lint/checks/ScrollViewChildDetector.java | 0
.../android/tools/lint/checks/SdCardDetector.java | 0
.../tools/lint/checks/SecureRandomDetector.java | 138 +
.../lint/checks/SecureRandomGeneratorDetector.java | 240 ++
.../tools/lint/checks/SecurityDetector.java | 479 +++
.../tools/lint/checks/ServiceCastDetector.java | 198 ++
.../lint/checks/SetJavaScriptEnabledDetector.java | 0
.../android/tools/lint/checks/SetTextDetector.java | 135 +
.../tools/lint/checks/SharedPrefsDetector.java | 349 ++
.../lint/checks/SignatureOrSystemDetector.java | 0
.../SslCertificateSocketFactoryDetector.java | 134 +
.../tools/lint/checks/StateListDetector.java | 0
.../tools/lint/checks/StringFormatDetector.java | 1774 ++++++++++
.../lint/checks/SupportAnnotationDetector.java | 1719 ++++++++++
.../lint/checks/SystemPermissionsDetector.java | 0
.../tools/lint/checks/TextFieldDetector.java | 0
.../tools/lint/checks/TextViewDetector.java | 0
.../android/tools/lint/checks/TitleDetector.java | 0
.../android/tools/lint/checks/ToastDetector.java | 0
.../tools/lint/checks/TooManyViewsDetector.java | 0
.../tools/lint/checks/TranslationDetector.java | 736 +++++
.../checks/TrustAllX509TrustManagerDetector.java | 181 +
.../android/tools/lint/checks/TypoDetector.java | 523 +++
.../com/android/tools/lint/checks/TypoLookup.java | 777 +++++
.../tools/lint/checks/TypographyDetector.java | 0
.../checks/UnsafeBroadcastReceiverDetector.java | 583 ++++
.../lint/checks/UnsafeNativeCodeDetector.java | 220 ++
.../tools/lint/checks/UnusedResourceDetector.java | 711 ++++
.../lint/checks/UseCompoundDrawableDetector.java | 0
.../tools/lint/checks/UselessViewDetector.java | 0
.../android/tools/lint/checks/Utf8Detector.java | 0
.../android/tools/lint/checks/VectorDetector.java | 189 ++
.../tools/lint/checks/ViewConstructorDetector.java | 0
.../tools/lint/checks/ViewHolderDetector.java | 0
.../android/tools/lint/checks/ViewTagDetector.java | 131 +
.../tools/lint/checks/ViewTypeDetector.java | 334 ++
.../tools/lint/checks/WakelockDetector.java | 428 +++
.../android/tools/lint/checks/WebViewDetector.java | 0
.../tools/lint/checks/WrongCallDetector.java | 163 +
.../tools/lint/checks/WrongCaseDetector.java | 0
.../android/tools/lint/checks/WrongIdDetector.java | 510 +++
.../tools/lint/checks/WrongImportDetector.java | 0
.../tools/lint/checks/WrongLocationDetector.java | 0
.../lint/checks/api-versions-support-library.xml | 275 ++
{base/lint => lint}/libs/lint-tests/NOTICE | 0
{base/lint => lint}/libs/lint-tests/build.gradle | 0
lint/libs/lint-tests/lint-tests.iml | 28 +
.../checks/infrastructure/LintDetectorTest.java | 1363 ++++++++
.../.settings/org.eclipse.core.resources.prefs | 0
.../src/test/.settings/org.moreunit.prefs | 0
.../java/com/android/tools/lint/EcjParserTest.java | 1167 +++++++
.../lint/ExternalAnnotationRepositoryTest.java | 614 ++++
.../com/android/tools/lint/HtmlReporterTest.java | 0
.../android/tools/lint/LintCliXmlParserTest.java | 0
.../test/java/com/android/tools/lint/MainTest.java | 0
.../tools/lint/MultiProjectHtmlReporterTest.java | 150 +
.../java/com/android/tools/lint/ReporterTest.java | 0
.../com/android/tools/lint/TextReporterTest.java | 0
.../java/com/android/tools/lint/WarningTest.java | 101 +
.../com/android/tools/lint/XmlReporterTest.java | 0
.../tools/lint/checks/AbstractCheckTest.java | 0
.../lint/checks/AccessibilityDetectorTest.java | 0
.../checks/AddJavascriptInterfaceDetectorTest.java | 0
.../tools/lint/checks/AlarmDetectorTest.java | 0
.../AllowAllHostnameVerifierDetectorTest.java | 145 +
.../lint/checks/AlwaysShowActionDetectorTest.java | 0
.../tools/lint/checks/AndroidAutoDetectorTest.java | 236 ++
.../tools/lint/checks/AndroidTvDetectorTest.java | 346 ++
.../tools/lint/checks/AnnotationDetectorTest.java | 435 +++
.../android/tools/lint/checks/ApiDetectorTest.java | 2237 +++++++++++++
.../android/tools/lint/checks/ApiLookupTest.java | 813 +++++
.../lint/checks/AppCompatCallDetectorTest.java | 0
.../lint/checks/AppCompatResourceDetectorTest.java | 0
.../lint/checks/AppIndexingApiDetectorTest.java | 729 ++++
.../checks/AppLinksAutoVerifyDetectorTest.java | 607 ++++
.../tools/lint/checks/ArraySizeDetectorTest.java | 0
.../tools/lint/checks/AssertDetectorTest.java | 0
.../checks/BadHostnameVerifierDetectorTest.java | 164 +
.../lint/checks/BuiltinIssueRegistryTest.java | 186 ++
.../tools/lint/checks/ButtonDetectorTest.java | 0
.../lint/checks/ByteOrderMarkDetectorTest.java | 0
.../tools/lint/checks/CallSuperDetectorTest.java | 126 +
.../tools/lint/checks/ChildCountDetectorTest.java | 0
.../lint/checks/CipherGetInstanceDetectorTest.java | 0
.../tools/lint/checks/CleanupDetectorTest.java | 276 ++
.../ClickableViewAccessibilityDetectorTest.java | 0
.../tools/lint/checks/CommentDetectorTest.java | 0
.../tools/lint/checks/CustomViewDetectorTest.java | 0
.../tools/lint/checks/CutPasteDetectorTest.java | 62 +
.../tools/lint/checks/DateFormatDetectorTest.java | 0
.../tools/lint/checks/DeprecationDetectorTest.java | 123 +
.../tools/lint/checks/DetectMissingPrefixTest.java | 0
.../lint/checks/DosLineEndingDetectorTest.java | 0
.../tools/lint/checks/DuplicateIdDetectorTest.java | 0
.../lint/checks/DuplicateResourceDetectorTest.java | 170 +
.../tools/lint/checks/ExtraTextDetectorTest.java | 0
.../tools/lint/checks/FieldGetterDetectorTest.java | 0
.../tools/lint/checks/FragmentDetectorTest.java | 80 +
.../lint/checks/FullBackupContentDetectorTest.java | 0
.../lint/checks/GetSignaturesDetectorTest.java | 121 +
.../tools/lint/checks/GradleDetectorTest.java | 991 ++++++
.../tools/lint/checks/GridLayoutDetectorTest.java | 0
.../tools/lint/checks/HandlerDetectorTest.java | 108 +
.../checks/HardcodedDebugModeDetectorTest.java | 0
.../lint/checks/HardcodedValuesDetectorTest.java | 173 +
.../tools/lint/checks/IconDetectorTest.java | 819 +++++
.../tools/lint/checks/IncludeDetectorTest.java | 0
.../lint/checks/InefficientWeightDetectorTest.java | 0
.../lint/checks/InvalidPackageDetectorTest.java | 142 +
.../lint/checks/JavaPerformanceDetectorTest.java | 166 +
.../checks/JavaScriptInterfaceDetectorTest.java | 0
.../tools/lint/checks/LabelForDetectorTest.java | 0
.../lint/checks/LayoutConsistencyDetectorTest.java | 118 +
.../lint/checks/LayoutInflationDetectorTest.java | 103 +
.../tools/lint/checks/LocaleDetectorTest.java | 123 +
.../lint/checks/LocaleFolderDetectorTest.java | 0
.../android/tools/lint/checks/LogDetectorTest.java | 82 +
.../tools/lint/checks/ManifestDetectorTest.java | 1097 ++++++
.../lint/checks/ManifestResourceDetectorTest.java | 257 ++
.../lint/checks/ManifestTypoDetectorTest.java | 0
.../tools/lint/checks/MathDetectorTest.java | 99 +
.../checks/MergeRootFrameLayoutDetectorTest.java | 75 +
.../lint/checks/MissingClassDetectorTest.java | 566 ++++
.../tools/lint/checks/MissingIdDetectorTest.java | 0
.../tools/lint/checks/NamespaceDetectorTest.java | 0
.../lint/checks/NegativeMarginDetectorTest.java | 0
.../checks/NestedScrollingWidgetDetectorTest.java | 0
.../tools/lint/checks/NfcTechListDetectorTest.java | 0
.../NonInternationalizedSmsDetectorTest.java | 0
.../checks/ObsoleteLayoutParamsDetectorTest.java | 0
.../tools/lint/checks/OnClickDetectorTest.java | 0
.../tools/lint/checks/OverdrawDetectorTest.java | 0
.../lint/checks/OverrideConcreteDetectorTest.java | 0
.../tools/lint/checks/OverrideDetectorTest.java | 0
.../tools/lint/checks/ParcelDetectorTest.java | 125 +
.../tools/lint/checks/PermissionHolderTest.java | 0
.../lint/checks/PermissionRequirementTest.java | 290 ++
.../tools/lint/checks/PluralsDatabaseTest.java | 740 +++++
.../tools/lint/checks/PluralsDetectorTest.java | 0
.../checks/PreferenceActivityDetectorTest.java | 0
.../tools/lint/checks/PrivateKeyDetectorTest.java | 0
.../lint/checks/PrivateResourceDetectorTest.java | 256 ++
.../tools/lint/checks/ProguardDetectorTest.java | 0
.../lint/checks/PropertyFileDetectorTest.java | 0
.../tools/lint/checks/PxUsageDetectorTest.java | 0
.../lint/checks/ReadParcelableDetectorTest.java | 86 +
.../lint/checks/RecyclerViewDetectorTest.java | 191 ++
.../lint/checks/RegistrationDetectorTest.java | 280 ++
.../lint/checks/RelativeOverlapDetectorTest.java | 94 +
.../lint/checks/RequiredAttributeDetectorTest.java | 206 ++
.../lint/checks/ResourceCycleDetectorTest.java | 0
.../lint/checks/ResourcePrefixDetectorTest.java | 146 +
.../lint/checks/RestrictionsDetectorTest.java | 478 +++
.../android/tools/lint/checks/RtlDetectorTest.java | 476 +++
.../tools/lint/checks/SQLiteDetectorTest.java | 44 +
.../lint/checks/ScrollViewChildDetectorTest.java | 0
.../tools/lint/checks/SdCardDetectorTest.java | 109 +
.../lint/checks/SecureRandomDetectorTest.java | 123 +
.../checks/SecureRandomGeneratorDetectorTest.java | 0
.../tools/lint/checks/SecurityDetectorTest.java | 249 ++
.../tools/lint/checks/ServiceCastDetectorTest.java | 0
.../checks/SetJavaScriptEnabledDetectorTest.java | 0
.../tools/lint/checks/SetTextDetectorTest.java | 88 +
.../tools/lint/checks/SharedPrefsDetectorTest.java | 187 ++
.../lint/checks/SignatureOrSystemDetectorTest.java | 0
.../SslCertificateSocketFactoryDetectorTest.java | 92 +
.../tools/lint/checks/StateListDetectorTest.java | 0
.../lint/checks/StringFormatDetectorTest.java | 716 ++++
.../lint/checks/SupportAnnotationDetectorTest.java | 1701 ++++++++++
.../lint/checks/SystemPermissionsDetectorTest.java | 0
.../tools/lint/checks/TextFieldDetectorTest.java | 0
.../tools/lint/checks/TextViewDetectorTest.java | 0
.../tools/lint/checks/TitleDetectorTest.java | 0
.../tools/lint/checks/ToastDetectorTest.java | 48 +
.../lint/checks/TooManyViewsDetectorTest.java | 0
.../tools/lint/checks/TranslationDetectorTest.java | 547 +++
.../TrustAllX509TrustManagerDetectorTest.java | 197 ++
.../tools/lint/checks/TypoDetectorTest.java | 212 ++
.../android/tools/lint/checks/TypoLookupTest.java | 0
.../tools/lint/checks/TypographyDetectorTest.java | 0
.../UnsafeBroadcastReceiverDetectorTest.java | 343 ++
.../lint/checks/UnsafeNativeCodeDetectorTest.java | 94 +
.../lint/checks/UnusedResourceDetectorTest.java | 641 ++++
.../checks/UseCompoundDrawableDetectorTest.java | 0
.../tools/lint/checks/UselessViewDetectorTest.java | 0
.../tools/lint/checks/Utf8DetectorTest.java | 0
.../tools/lint/checks/VectorDetectorTest.java | 159 +
.../lint/checks/ViewConstructorDetectorTest.java | 0
.../tools/lint/checks/ViewHolderDetectorTest.java | 0
.../tools/lint/checks/ViewTagDetectorTest.java | 97 +
.../tools/lint/checks/ViewTypeDetectorTest.java | 0
.../tools/lint/checks/WakelockDetectorTest.java | 201 ++
.../tools/lint/checks/WebViewDetectorTest.java | 0
.../tools/lint/checks/WrongCallDetectorTest.java | 0
.../tools/lint/checks/WrongCaseDetectorTest.java | 0
.../tools/lint/checks/WrongIdDetectorTest.java | 220 ++
.../tools/lint/checks/WrongImportDetectorTest.java | 0
.../lint/checks/WrongLocationDetectorTest.java | 0
.../lint/checks/data/AbstractActivity.java.txt | 0
.../tools/lint/checks/data/AndroidManifest.xml | 0
.../tools/lint/checks/data/Dependencies.gradle | 0
.../android/tools/lint/checks/data/allowbackup.xml | 0
.../tools/lint/checks/data/allowbackup_ignore.xml | 0
.../data/android/PreferenceActivity.java.txt | 0
.../checks/data/apicheck/ApiCallTest.class.data | Bin
.../lint/checks/data/apicheck/ApiCallTest.java.txt | 0
.../checks/data/apicheck/ApiCallTest10.class.data | Bin
.../checks/data/apicheck/ApiCallTest10.java.txt | 0
.../apicheck/ApiCallTest11$MyActivity.class.data | Bin
.../apicheck/ApiCallTest11$MyLinear.class.data | Bin
.../checks/data/apicheck/ApiCallTest11.class.data | Bin
.../checks/data/apicheck/ApiCallTest11.java.txt | 0
.../checks/data/apicheck/ApiCallTest12.class.data | Bin
.../checks/data/apicheck/ApiCallTest12.java.txt | 0
.../checks/data/apicheck/ApiCallTest13.class.data | Bin
.../checks/data/apicheck/ApiCallTest13.java.txt | 0
.../data/apicheck/ApiCallTest14$1.class.data | Bin
.../data/apicheck/ApiCallTest14$2.class.data | Bin
.../data/apicheck/ApiCallTest14$3.class.data | Bin
.../checks/data/apicheck/ApiCallTest14.class.data | Bin
.../checks/data/apicheck/ApiCallTest14.java.txt | 0
.../checks/data/apicheck/ApiCallTest2.class.data | Bin
.../checks/data/apicheck/ApiCallTest2.java.txt | 0
.../checks/data/apicheck/ApiCallTest3.class.data | Bin
.../checks/data/apicheck/ApiCallTest3.java.txt | 0
.../checks/data/apicheck/ApiCallTest4$1.class.data | Bin
...llTest4$InnerClass1$InnerInnerClass1.class.data | Bin
.../apicheck/ApiCallTest4$InnerClass1.class.data | Bin
.../apicheck/ApiCallTest4$InnerClass2.class.data | Bin
.../checks/data/apicheck/ApiCallTest4.class.data | Bin
.../checks/data/apicheck/ApiCallTest4.java.txt | 0
.../checks/data/apicheck/ApiCallTest5.class.data | Bin
.../checks/data/apicheck/ApiCallTest5.java.txt | 0
.../checks/data/apicheck/ApiCallTest6.class.data | Bin
.../checks/data/apicheck/ApiCallTest6.java.txt | 0
.../checks/data/apicheck/ApiCallTest7.class.data | Bin
.../checks/data/apicheck/ApiCallTest7.java.txt | 0
.../checks/data/apicheck/ApiCallTest8.class.data | Bin
.../checks/data/apicheck/ApiCallTest8.java.txt | 0
.../checks/data/apicheck/ApiCallTest9.class.data | Bin
.../checks/data/apicheck/ApiCallTest9.java.txt | 0
.../checks/data/apicheck/ApiSourceCheck.class.data | Bin
.../checks/data/apicheck/ApiSourceCheck.java.txt | 123 +
.../data/apicheck/ApiSourceCheck2.class.data | Bin
.../checks/data/apicheck/ApiSourceCheck2.java.txt | 0
.../apicheck/ApiTargetTest$LocalClass.class.data | Bin
.../checks/data/apicheck/ApiTargetTest.class.data | Bin
.../checks/data/apicheck/ApiTargetTest.java.txt | 0
.../data/apicheck/ApiTargetTest2$1$1.class.data | Bin
.../data/apicheck/ApiTargetTest2$1$2.class.data | Bin
.../data/apicheck/ApiTargetTest2$1.class.data | Bin
.../checks/data/apicheck/ApiTargetTest2.class.data | Bin
.../checks/data/apicheck/ApiTargetTest2.java.txt | 0
.../lint/checks/data/apicheck/CloseTest.class.data | Bin
.../lint/checks/data/apicheck/CloseTest.java.txt | 0
.../data/apicheck/ConditionalApiTest.class.data | Bin
.../data/apicheck/ConditionalApiTest.java.txt | 0
.../data/apicheck/ConditionalApiTest2.class.data | Bin
.../data/apicheck/ConditionalApiTest2.java.txt | 0
.../lint/checks/data/apicheck/Fragment.class.data | Bin
.../lint/checks/data/apicheck/Fragment.java.txt | 0
.../data/apicheck/FragmentActivity.class.data | Bin
.../checks/data/apicheck/FragmentActivity.java.txt | 0
.../lint/checks/data/apicheck/GravityTest.java.txt | 0
.../Intermediate$IntermediateCustomV.class.data | Bin
.../checks/data/apicheck/Intermediate.class.data | Bin
.../checks/data/apicheck/Intermediate.java.txt | 0
.../checks/data/apicheck/MyActivityImpl.class.data | Bin
.../checks/data/apicheck/MyActivityImpl.java.txt | 0
.../data/apicheck/MyFragment$R$color.class.data | Bin
.../checks/data/apicheck/MyFragment$R.class.data | Bin
.../checks/data/apicheck/MyFragment.class.data | Bin
.../lint/checks/data/apicheck/MyFragment.java.txt | 0
.../apicheck/SuperCallTest$MyEngine1.class.data | Bin
.../apicheck/SuperCallTest$MyEngine2.class.data | Bin
.../checks/data/apicheck/SuperCallTest.class.data | Bin
.../checks/data/apicheck/SuperCallTest.java.txt | 0
.../checks/data/apicheck/SuppressTest1.class.data | Bin
.../checks/data/apicheck/SuppressTest1.java.txt | 137 +
.../checks/data/apicheck/SuppressTest2.class.data | Bin
.../checks/data/apicheck/SuppressTest2.java.txt | 25 +
.../checks/data/apicheck/SuppressTest3.class.data | Bin
.../checks/data/apicheck/SuppressTest3.java.txt | 25 +
.../checks/data/apicheck/SuppressTest4.class.data | Bin
.../checks/data/apicheck/SuppressTest4.java.txt | 21 +
.../data/apicheck/TargetApiTest$1.class.data | Bin
.../checks/data/apicheck/TargetApiTest.class.data | Bin
.../checks/data/apicheck/TargetApiTest.java.txt | 0
.../lint/checks/data/apicheck/TestEnum.class.data | Bin
.../lint/checks/data/apicheck/TestEnum.java.txt | 0
.../lint/checks/data/apicheck/TestLint.class.data | Bin
.../data/apicheck/VersionConditional1.class.data | Bin
.../data/apicheck/VersionConditional1.java.txt | 0
.../data/apicheck/VersionConditional1b.class.data | Bin
.../data/apicheck/VersionConditional1b.java.txt | 0
.../data/apicheck/VersionConditional2.class.data | Bin
.../data/apicheck/VersionConditional2.java.txt | 0
.../data/apicheck/VersionConditional2b.class.data | Bin
.../data/apicheck/VersionConditional2b.java.txt | 0
.../data/apicheck/VersionConditional3.class.data | Bin
.../data/apicheck/VersionConditional3.java.txt | 0
.../data/apicheck/VersionConditional3b.class.data | Bin
.../data/apicheck/VersionConditional3b.java.txt | 0
.../checks/data/apicheck/animated_selector.xml | 0
.../lint/checks/data/apicheck/animated_vector.xml | 0
.../tools/lint/checks/data/apicheck/attribute.xml | 0
.../tools/lint/checks/data/apicheck/attribute2.xml | 0
.../tools/lint/checks/data/apicheck/attribute3.xml | 0
.../tools/lint/checks/data/apicheck/classpath | 0
.../tools/lint/checks/data/apicheck/colors.xml | 0
.../tools/lint/checks/data/apicheck/divider.xml | 0
.../lint/checks/data/apicheck/holomanifest.xml | 0
.../tools/lint/checks/data/apicheck/layout.xml | 0
.../lint/checks/data/apicheck/layout_targetapi.xml | 0
.../tools/lint/checks/data/apicheck/layoutattr.xml | 0
.../tools/lint/checks/data/apicheck/minics.xml | 0
.../tools/lint/checks/data/apicheck/minsdk1.xml | 0
.../tools/lint/checks/data/apicheck/minsdk10.xml | 0
.../tools/lint/checks/data/apicheck/minsdk11.xml | 0
.../tools/lint/checks/data/apicheck/minsdk14.xml | 0
.../tools/lint/checks/data/apicheck/minsdk17.xml | 0
.../tools/lint/checks/data/apicheck/minsdk19.xml | 0
.../tools/lint/checks/data/apicheck/minsdk2.xml | 0
.../tools/lint/checks/data/apicheck/minsdk21.xml | 0
.../tools/lint/checks/data/apicheck/minsdk4.xml | 0
.../lint/checks/data/apicheck/padding_start.xml | 0
.../tools/lint/checks/data/apicheck/ripple.xml | 0
.../tools/lint/checks/data/apicheck/themes.xml | 0
.../tools/lint/checks/data/apicheck/vector.xml | 0
.../tools/lint/checks/data/apicheck/view.xml | 0
.../lint/checks/data/app_indexing_api_test.xml | 28 +
.../data/app_indexing_api_test_no_manifest.xml | 25 +
.../data/appcompat/ActionBarActivity.java.txt | 0
.../lint/checks/data/appcompat/ActionMode.java.txt | 0
.../data/appcompat/AppCompatPrefTest.java.txt | 15 +
.../checks/data/appcompat/AppCompatTest.java.txt | 0
.../checks/data/appcompat/DialogFragment.java.txt | 9 +
.../lint/checks/data/appcompat/Fragment.java.txt | 0
.../checks/data/appcompat/FragmentManager.java.txt | 0
.../data/appcompat/FragmentTransaction.java.txt | 18 +
.../data/appcompat/IntermediateActivity.java.txt | 0
.../lint/checks/data/appindexing_manifest.xml | 0
.../tools/lint/checks/data/broken-manifest.xml | 0
.../tools/lint/checks/data/broken-manifest2.xml | 0
.../tools/lint/checks/data/bytecode/.classpath | 0
.../data/bytecode/AbstractActivity.class.data | Bin
.../checks/data/bytecode/AbstractActivity.java.txt | 0
.../data/bytecode/AbstractCustomView.class.data | Bin
.../data/bytecode/AbstractCustomView.java.txt | 0
...llAddJavascriptInterfaceOnNonWebView.class.data | Bin
...$CallAddJavascriptInterfaceOnWebView.class.data | Bin
...AddJavascriptInterfaceOnWebViewChild.class.data | Bin
...ddJavascriptInterfaceTest$NonWebView.class.data | Bin
...JavascriptInterfaceTest$WebViewChild.class.data | Bin
.../bytecode/AddJavascriptInterfaceTest.class.data | Bin
.../bytecode/AddJavascriptInterfaceTest.java.txt | 0
.../lint/checks/data/bytecode/AndroidManifest.xml | 0
.../data/bytecode/AndroidManifestMinSdk17.xml | 0
.../checks/data/bytecode/AndroidManifestReg.xml | 0
.../checks/data/bytecode/AndroidManifestRegs.xml | 0
.../data/bytecode/AndroidManifestTarget17.xml | 0
.../data/bytecode/AndroidManifestWrongRegs.xml | 0
.../data/bytecode/AnnotatedObject.class.data | Bin
.../checks/data/bytecode/AnnotatedObject.java.txt | 0
.../checks/data/bytecode/CipherTest1.class.data | Bin
.../lint/checks/data/bytecode/CipherTest1.java.txt | 0
.../checks/data/bytecode/Class1$Class4.class.data | Bin
.../lint/checks/data/bytecode/Class1.class.data | Bin
.../checks/data/bytecode/Class2$Class3.class.data | Bin
.../lint/checks/data/bytecode/Class2.class.data | Bin
.../ClickableViewAccessibilityTest$1.class.data | Bin
...st$AnonymousInvalidOnTouchListener$1.class.data | Bin
...Test$AnonymousInvalidOnTouchListener.class.data | Bin
...Test$AnonymousValidOnTouchListener$1.class.data | Bin
...tyTest$AnonymousValidOnTouchListener.class.data | Bin
...iewAccessibilityTest$HasPerformClick.class.data | Bin
...HasPerformClickOnTouchListenerSetter.class.data | Bin
...ssibilityTest$InvalidOnTouchListener.class.data | Bin
...ViewAccessibilityTest$NoPerformClick.class.data | Bin
...$NoPerformClickOnTouchListenerSetter.class.data | Bin
...ckableViewAccessibilityTest$NotAView.class.data | Bin
...tyTest$NotAViewOnTouchListenerSetter.class.data | Bin
...ityTest$PerformClickDoesNotCallSuper.class.data | Bin
...cessibilityTest$ValidOnTouchListener.class.data | Bin
...kableViewAccessibilityTest$ValidView.class.data | Bin
...lityTest$ViewDoesNotCallPerformClick.class.data | Bin
...rridesOnTouchEventButNotPerformClick.class.data | Bin
...leViewAccessibilityTest$ViewSubclass.class.data | Bin
...tyTest$ViewWithDifferentOnTouchEvent.class.data | Bin
...tyTest$ViewWithDifferentPerformClick.class.data | Bin
.../ClickableViewAccessibilityTest.class.data | Bin
.../ClickableViewAccessibilityTest.java.txt | 0
.../data/bytecode/CommentsActivity.class.data | Bin
.../checks/data/bytecode/CommentsActivity.java.txt | 0
.../checks/data/bytecode/CommitTest.class.data | Bin
.../lint/checks/data/bytecode/CommitTest.java.txt | 137 +
.../CommitTest2$MyDialogFragment.class.data | Bin
.../checks/data/bytecode/CommitTest2.class.data | Bin
.../lint/checks/data/bytecode/CommitTest2.java.txt | 0
.../CommitTest3$MyCompatDialogFragment.class.data | Bin
.../CommitTest3$MyDialogFragment.class.data | Bin
.../checks/data/bytecode/CommitTest3.class.data | Bin
.../lint/checks/data/bytecode/CommitTest3.java.txt | 0
.../checks/data/bytecode/CustomView1.class.data | Bin
.../lint/checks/data/bytecode/CustomView1.java.txt | 0
.../checks/data/bytecode/CustomView2.class.data | Bin
.../lint/checks/data/bytecode/CustomView2.java.txt | 0
.../checks/data/bytecode/CustomView3.class.data | Bin
.../lint/checks/data/bytecode/CustomView3.java.txt | 0
.../checks/data/bytecode/CustomViewTest.class.data | Bin
.../checks/data/bytecode/DialogFragment.class.data | Bin
.../bytecode/ExampleHostnameVerifier$1.class.data | Bin 0 -> 574 bytes
.../bytecode/ExampleTLSIntentService$1.class.data | Bin 0 -> 2014 bytes
.../bytecode/ExampleTLSIntentService.class.data | Bin 0 -> 1668 bytes
.../data/bytecode/ExampleTLSIntentService.java.txt | 62 +
.../data/bytecode/FloatingActionButton.class.data | Bin 0 -> 868 bytes
.../data/bytecode/FloatingActionButton.java.txt | 20 +
.../bytecode/FragmentTest$Fragment1.class.data | Bin
.../bytecode/FragmentTest$Fragment2.class.data | Bin
.../bytecode/FragmentTest$Fragment3.class.data | Bin
.../bytecode/FragmentTest$Fragment4.class.data | Bin
.../bytecode/FragmentTest$Fragment5.class.data | Bin
.../bytecode/FragmentTest$Fragment6.class.data | Bin
.../bytecode/FragmentTest$NotAFragment.class.data | Bin
.../FragmentTest$ValidFragment1.class.data | Bin
.../checks/data/bytecode/FragmentTest.class.data | Bin
.../checks/data/bytecode/FragmentTest.java.txt | 0
.../checks/data/bytecode/GetterTest.class.data | Bin
.../lint/checks/data/bytecode/GetterTest.java.txt | 0
.../data/bytecode/InheritsFromAnnotated.class.data | Bin
.../data/bytecode/InheritsFromAnnotated.java.txt | 0
.../bytecode/InsecureHostnameVerifier$1.class.data | Bin 0 -> 579 bytes
.../bytecode/InsecureTLSIntentService$1.class.data | Bin 0 -> 1102 bytes
.../bytecode/InsecureTLSIntentService.class.data | Bin 0 -> 1668 bytes
.../bytecode/InsecureTLSIntentService.java.txt | 44 +
.../checks/data/bytecode/JavaScriptTest.class.data | Bin
.../checks/data/bytecode/JavaScriptTest.java.txt | 0
.../checks/data/bytecode/LayoutTest.class.data | Bin
.../lint/checks/data/bytecode/LayoutTest.java.txt | 0
.../checks/data/bytecode/LocaleTest.class.data | Bin
.../lint/checks/data/bytecode/LocaleTest.java.txt | 0
.../lint/checks/data/bytecode/META-INF/MANIFEST.MF | 0
.../lint/checks/data/bytecode/MathTest.class.data | Bin
.../lint/checks/data/bytecode/MathTest.java.txt | 0
.../checks/data/bytecode/MyParcelable1.class.data | Bin
.../checks/data/bytecode/MyParcelable1.java.txt | 0
.../data/bytecode/MyParcelable2$1.class.data | Bin
.../checks/data/bytecode/MyParcelable2.class.data | Bin
.../checks/data/bytecode/MyParcelable2.java.txt | 0
.../checks/data/bytecode/MyParcelable3.class.data | Bin
.../checks/data/bytecode/MyParcelable3.java.txt | 0
.../checks/data/bytecode/MyParcelable4.class.data | Bin
.../checks/data/bytecode/MyParcelable4.java.txt | 0
.../checks/data/bytecode/MyParcelable5.class.data | Bin
.../checks/data/bytecode/MyParcelable5.java.txt | 0
.../data/bytecode/NonAnnotatedObject.class.data | Bin
.../data/bytecode/NonAnnotatedObject.java.txt | 0
.../data/bytecode/OnClickActivity.class.data | Bin
.../checks/data/bytecode/OnClickActivity.java.txt | 0
.../data/bytecode/PowerManagerFlagTest.class.data | Bin
.../data/bytecode/PowerManagerFlagTest.java.txt | 0
.../lint/checks/data/bytecode/PrngCalls.class.data | Bin
.../lint/checks/data/bytecode/PrngCalls.java.txt | 0
...PrngWorkaround$LinuxPRNGSecureRandom.class.data | Bin
...around$LinuxPRNGSecureRandomProvider.class.data | Bin
.../checks/data/bytecode/PrngWorkaround.class.data | Bin
.../checks/data/bytecode/PrngWorkaround.java.txt | 0
.../checks/data/bytecode/RecycleTest.class.data | Bin
.../lint/checks/data/bytecode/RecycleTest.java.txt | 0
.../data/bytecode/SupportLibraryApiTest.class.data | Bin 0 -> 1147 bytes
.../data/bytecode/SupportLibraryApiTest.java.txt | 24 +
.../data/bytecode/TestFieldGetter.class.data | Bin
.../checks/data/bytecode/TestFieldGetter.java.txt | 0
.../checks/data/bytecode/TestProvider.class.data | Bin
.../checks/data/bytecode/TestProvider.java.txt | 0
.../checks/data/bytecode/TestProvider2.class.data | Bin
.../checks/data/bytecode/TestProvider2.java.txt | 0
.../checks/data/bytecode/TestReceiver$1.class.data | Bin
.../checks/data/bytecode/TestReceiver.class.data | Bin
.../checks/data/bytecode/TestReceiver.java.txt | 0
.../checks/data/bytecode/TestService.class.data | Bin
.../lint/checks/data/bytecode/TestService.java.txt | 0
...encesActivity$UserPreferenceFragment.class.data | Bin
.../data/bytecode/WakelockActivity1.class.data | Bin
.../data/bytecode/WakelockActivity1.java.txt | 0
.../data/bytecode/WakelockActivity10.class.data | Bin 0 -> 884 bytes
.../data/bytecode/WakelockActivity10.java.txt | 23 +
.../data/bytecode/WakelockActivity2.class.data | Bin
.../data/bytecode/WakelockActivity2.java.txt | 0
.../data/bytecode/WakelockActivity3.class.data | Bin
.../data/bytecode/WakelockActivity3.java.txt | 0
.../data/bytecode/WakelockActivity4.class.data | Bin
.../data/bytecode/WakelockActivity4.java.txt | 0
.../data/bytecode/WakelockActivity5.class.data | Bin
.../data/bytecode/WakelockActivity5.java.txt | 0
.../data/bytecode/WakelockActivity6.class.data | Bin
.../data/bytecode/WakelockActivity6.java.txt | 0
.../data/bytecode/WakelockActivity7.class.data | Bin
.../data/bytecode/WakelockActivity7.java.txt | 0
.../data/bytecode/WakelockActivity8.class.data | Bin
.../data/bytecode/WakelockActivity8.java.txt | 0
.../data/bytecode/WakelockActivity9.class.data | Bin
.../data/bytecode/WakelockActivity9.java.txt | 0
.../tools/lint/checks/data/bytecode/classpath-jar | 0
.../tools/lint/checks/data/bytecode/classpath-lib | 0
.../checks/data/bytecode/user_prefs_fragment.xml | 0
.../android/tools/lint/checks/data/debuggable.xml | 0
.../android/tools/lint/checks/data/deviceadmin.xml | 0
.../lint/checks/data/duplicate-manifest-ignore.xml | 0
.../tools/lint/checks/data/duplicate-manifest.xml | 0
.../lint/checks/data/duplicate_permissions1.xml | 0
.../lint/checks/data/duplicate_permissions2.xml | 0
.../lint/checks/data/duplicate_permissions3.xml | 0
.../lint/checks/data/duplicate_uses_feature.xml | 0
.../lint/checks/data/duplicate_uses_feature_ok.xml | 0
.../tools/lint/checks/data/encoding/MacRoman.xml | 0
.../checks/data/encoding/UTF-16-bom-implicit.xml | Bin
.../tools/lint/checks/data/encoding/UTF-16-bom.xml | Bin
.../lint/checks/data/encoding/UTF-16-nobom.xml | Bin
.../tools/lint/checks/data/encoding/UTF_32-bom.xml | Bin
.../lint/checks/data/encoding/UTF_32-nobom.xml | Bin
.../lint/checks/data/encoding/UTF_32LE-nobom.xml | Bin
.../lint/checks/data/encoding/Windows-1252.xml | 0
.../data/export_preference_activity_explicit.xml | 0
.../data/export_preference_activity_implicit.xml | 0
.../data/export_preference_activity_no_export.xml | 0
...xport_preference_activity_subclass_explicit.xml | 0
...xport_preference_activity_subclass_implicit.xml | 0
..._preference_activity_subclass_target_sdk_19.xml | 0
.../data/export_preference_activity_suppressed.xml | 0
.../tools/lint/checks/data/exportactivity0.xml | 0
.../tools/lint/checks/data/exportactivity1.xml | 0
.../tools/lint/checks/data/exportactivity2.xml | 0
.../tools/lint/checks/data/exportactivity3.xml | 0
.../tools/lint/checks/data/exportactivity4.xml | 0
.../tools/lint/checks/data/exportprovider1.xml | 0
.../tools/lint/checks/data/exportprovider2.xml | 0
.../tools/lint/checks/data/exportreceiver0.xml | 0
.../tools/lint/checks/data/exportreceiver1.xml | 0
.../tools/lint/checks/data/exportreceiver2.xml | 0
.../tools/lint/checks/data/exportreceiver3.xml | 0
.../tools/lint/checks/data/exportreceiver4.xml | 0
.../tools/lint/checks/data/exportreceiver5.xml | 0
.../tools/lint/checks/data/exportreceiver6.xml | 0
.../tools/lint/checks/data/exportreceiver7.xml | 0
.../tools/lint/checks/data/exportreceiver8.xml | 0
.../tools/lint/checks/data/exportservice1.xml | 0
.../tools/lint/checks/data/exportservice2.xml | 0
.../tools/lint/checks/data/exportservice3.xml | 0
.../tools/lint/checks/data/exportservice4.xml | 0
.../tools/lint/checks/data/exportservice5.xml | 0
.../tools/lint/checks/data/gen/my/pkg/R.java.txt | 0
.../tools/lint/checks/data/gen/my/pkg/R2.java.txt | 0
.../lint/checks/data/gradle/AccidentalOctal.gradle | 0
.../BadDependenciesInBuildscriptBlock.gradle | 0
.../lint/checks/data/gradle/Compatibility.gradle | 24 +
.../lint/checks/data/gradle/Dependencies.gradle | 0
.../lint/checks/data/gradle/Dependencies14.gradle | 0
.../checks/data/gradle/Dependencies14_21.gradle | 0
.../checks/data/gradle/DependenciesGson.gradle | 0
.../data/gradle/DependenciesInAndroidBlock.gradle | 0
.../checks/data/gradle/DependenciesProps.gradle | 0
.../checks/data/gradle/DependenciesVariable.gradle | 0
.../checks/data/gradle/DeprecatedPluginId.gradle | 0
.../checks/data/gradle/DynamicResources.gradle | 23 +
.../tools/lint/checks/data/gradle/IdSuffix.gradle | 0
.../gradle/IgnoresGStringsInDependencies.gradle | 0
.../checks/data/gradle/IncompatiblePlugin.gradle | 0
.../checks/data/gradle/MinSdkAssignment.gradle | 0
.../checks/data/gradle/NoAndroidStatement.gradle | 0
.../lint/checks/data/gradle/NoApplyPlugin.gradle | 0
.../tools/lint/checks/data/gradle/Package.gradle | 0
.../tools/lint/checks/data/gradle/Paths.gradle | 0
.../lint/checks/data/gradle/PlayServices.gradle | 0
.../lint/checks/data/gradle/PlayServices2.gradle | 6 +
.../tools/lint/checks/data/gradle/Plus.gradle | 12 +
.../checks/data/gradle/PreviewDependencies.gradle | 0
.../lint/checks/data/gradle/RemoteVersions.gradle | 0
.../lint/checks/data/gradle/RemoteVersions2.gradle | 0
.../gradle/RepositoriesInDependenciesBlock.gradle | 0
.../tools/lint/checks/data/gradle/Setter.gradle | 0
.../tools/lint/checks/data/gradle/StringInt.gradle | 0
.../lint/checks/data/gradle/SuppressLine2.gradle | 0
.../data/gradle/TopLevelDependenciesBlock.gradle | 0
.../data/gradle/TopLevelRepositoriesBlock.gradle | 0
.../tools/lint/checks/data/gradle_http.properties | 0
.../tools/lint/checks/data/gradle_override.xml | 0
.../checks/data/gradle_override_placeholder.xml | 0
.../tools/lint/checks/data/grantpermission.xml | 0
.../tools/lint/checks/data/hostnameverifier.xml | 18 +
.../tools/lint/checks/data/hostnameverifier1.xml | 18 +
.../tools/lint/checks/data/https_namespace.xml | 0
.../tools/lint/checks/data/ignoremissing.xml | 0
.../tools/lint/checks/data/illegal_version.xml | 0
.../tools/lint/checks/data/lib/armeabi/hello | Bin 0 -> 5244 bytes
.../tools/lint/checks/data/local.properties | 0
.../tools/lint/checks/data/local2.properties | 0
.../tools/lint/checks/data/locale33845/.classpath | 0
.../checks/data/locale33845/AndroidManifest.xml | 0
.../checks/data/locale33845/project.properties | 0
.../data/locale33845/res/values-de/strings.xml | 0
.../data/locale33845/res/values-en-rGB/strings.xml | 0
.../locale33845/res/values-en-rGB/strings2.xml | 0
.../locale33845/res/values-en-rGB/strings3.xml | 0
.../checks/data/locale33845/res/values/strings.xml | 0
.../data/locale33845/res/values/strings2.xml | 0
.../data/locale33845/res/values/strings3.xml | 0
.../data/locale33845/res/values/strings4.xml | 0
.../data/locale33845/res/values/strings5.xml | 0
.../checks/data/locale33845/res/values/styles.xml | 0
.../tools/lint/checks/data/minsdk5targetsdk14.xml | 0
.../tools/lint/checks/data/minsdk5targetsdk9.xml | 0
.../com/android/tools/lint/checks/data/mipmap.xml | 0
.../lint/checks/data/missing_application_icon.xml | 0
.../android/tools/lint/checks/data/missingmin.xml | 0
.../tools/lint/checks/data/missingprefix.xml | 0
.../tools/lint/checks/data/missingtarget.xml | 0
.../tools/lint/checks/data/missingusessdk.xml | 0
.../tools/lint/checks/data/mock_location.xml | 0
.../android/tools/lint/checks/data/multiplesdk.xml | 0
.../checks/data/multiproject/LibraryCode.java.txt | 0
.../checks/data/multiproject/MainCode.java.txt | 0
.../checks/data/multiproject/library-manifest.xml | 0
.../checks/data/multiproject/library-manifest2.xml | 0
.../checks/data/multiproject/library.properties | 0
.../checks/data/multiproject/library2.properties | 0
.../checks/data/multiproject/main-manifest.xml | 0
.../checks/data/multiproject/main-merge.properties | 0
.../lint/checks/data/multiproject/main.properties | 0
.../lint/checks/data/multiproject/strings.xml | 0
.../android/tools/lint/checks/data/no_version.xml | 0
.../android/tools/lint/checks/data/oldtarget.xml | 0
.../tools/lint/checks/data/overdraw/.classpath | 0
.../tools/lint/checks/data/overdraw/.project | 0
.../lint/checks/data/overdraw/AndroidManifest.xml | 0
.../overdraw/gen/test/pkg/BuildConfig.java.txt | 0
.../checks/data/overdraw/gen/test/pkg/R.java.txt | 0
.../lint/checks/data/overdraw/project.properties | 0
.../overdraw}/res/drawable-hdpi/ic_launcher.png | Bin
.../overdraw}/res/drawable-ldpi/ic_launcher.png | Bin
.../overdraw/res/drawable-mdpi/ic_launcher.png | Bin
.../checks/data/overdraw/res/drawable/custombg.xml | 0
.../data/overdraw/res/drawable/custombg2.xml | 0
.../lint/checks/data/overdraw/res/layout/fifth.xml | 0
.../checks/data/overdraw/res/layout/fourth.xml | 0
.../data/overdraw/res/layout/fourth_context.xml | 0
.../data/overdraw/res/layout/fourth_context2.xml | 0
.../lint/checks/data/overdraw/res/layout/main.xml | 0
.../lint/checks/data/overdraw/res/layout/main2.xml | 0
.../lint/checks/data/overdraw/res/layout/main3.xml | 0
.../data/overdraw/res/layout/main_ignore.xml | 0
.../data/overdraw/res/layout/nullbackground.xml | 0
.../checks/data/overdraw/res/layout/second.xml | 0
.../lint/checks/data/overdraw/res/layout/sixth.xml | 0
.../lint/checks/data/overdraw/res/layout/third.xml | 0
.../data/overdraw/res/layout/tools_background.xml | 0
.../checks/data/overdraw/res/values/strings.xml | 0
.../checks/data/overdraw/res/values/styles.xml | 0
.../overdraw/src/test/pkg/FourthActivity.java.txt | 0
.../src/test/pkg/OverdrawActivity.java.txt | 0
.../overdraw/src/test/pkg/SecondActivity.java.txt | 0
.../overdraw/src/test/pkg/ThirdActivity.java.txt | 0
.../android/tools/lint/checks/data/proguard.cfg | 0
.../android/tools/lint/checks/data/proguard.pro | 0
.../tools/lint/checks/data/proguard.properties | 0
.../tools/lint/checks/data/project.properties1 | 0
.../tools/lint/checks/data/project.properties19 | 0
.../tools/lint/checks/data/project.properties2 | 0
.../tools/lint/checks/data/project.properties3 | 0
.../tools/lint/checks/data/project.properties4 | 0
.../lint/checks/data/protectedpermissions.xml | 0
.../lint/checks/data/protectedpermissions2.xml | 0
.../lint/checks/data/protection_level_fail.xml | 0
.../tools/lint/checks/data/protection_level_ok.xml | 0
.../checks/data/registration/AndroidManifest.xml | 0
.../data/registration/AndroidManifestInner.xml | 0
.../data/registration/AndroidManifestWrong.xml | 0
.../data/registration/AndroidManifestWrong2.xml | 0
.../lint/checks/data/registration/Bar.class.data | Bin
.../lint/checks/data/registration/Bar.java.txt | 0
.../checks/data/registration/Foo$Bar.class.data | Bin
.../checks/data/registration/Foo$Baz.class.data | Bin
.../lint/checks/data/registration/Foo.class.data | Bin
.../lint/checks/data/registration/Foo.java.txt | 0
.../lint/checks/data/res/anim/slide_in_out.xml | 0
.../tools/lint/checks/data/res/color/color1.xml | 0
.../data/res/drawable-hdpi/appwidget_bg.9.png | Bin
.../res/drawable-hdpi/appwidget_bg_focus.9.png | Bin
.../lint/checks/data/res/drawable-hdpi/filled.png | Bin
.../checks/data}/res/drawable-hdpi/ic_launcher.png | Bin
.../lint/checks/data/res/drawable-hdpi/other.9.png | Bin
.../checks/data/res/drawable-hdpi/unrelated.png | Bin
.../lint/checks/data/res/drawable-mdpi/frame.png | Bin
.../res/drawable-mdpi/ic_menu_add_clip_normal.png | Bin
.../checks/data/res/drawable-mdpi/sample_icon.gif | Bin
.../checks/data/res/drawable-mdpi/sample_icon.jpg | Bin
.../data/res/drawable-mdpi/stat_notify_alarm.png | Bin
.../lint/checks/data/res/drawable-nodpi/frame.png | Bin
.../data/res/drawable-xhdpi/ic_stat_notify.png | Bin
.../data/res/drawable-xlarge-nodpi-v11/frame.png | Bin
.../lint/checks/data/res/drawable/drawable1.xml | 0
.../lint/checks/data/res/drawable/ic_launcher.png | Bin
.../lint/checks/data/res/drawable/ic_menu_help.xml | 0
.../tools/lint/checks/data/res/drawable/states.xml | 0
.../lint/checks/data/res/drawable/states2.xml | 0
.../lint/checks/data/res/drawable/states3.xml | 0
.../lint/checks/data/res/layout/accessibility.xml | 0
.../lint/checks/data/res/layout/accessibility2.xml | 0
.../data/res/layout/activity_item_two_pane.xml | 0
.../checks/data/res/layout/baseline_weights.xml | 0
.../checks/data/res/layout/baseline_weights2.xml | 0
.../checks/data/res/layout/baseline_weights3.xml | 0
.../data/res/layout/breadcrumbs_in_fragment.xml | 0
.../tools/lint/checks/data/res/layout/broken.xml | 0
.../lint/checks/data/res/layout/buttonbar.xml | 0
.../lint/checks/data/res/layout/buttonbar2.xml | 0
.../lint/checks/data/res/layout/buttonbar3.xml | 0
.../lint/checks/data/res/layout/buttonbar4.xml | 0
.../lint/checks/data/res/layout/buttonbar5.xml | 0
.../data/res/layout/buttonbar_suppressed.xml | 0
.../tools/lint/checks/data/res/layout/case.xml | 0
.../tools/lint/checks/data/res/layout/casts.xml | 0
.../tools/lint/checks/data/res/layout/casts2.xml | 0
.../tools/lint/checks/data/res/layout/casts3.xml | 0
.../tools/lint/checks/data/res/layout/casts4.xml | 0
.../tools/lint/checks/data/res/layout/compound.xml | 0
.../lint/checks/data/res/layout/compound2.xml | 0
.../lint/checks/data/res/layout/compound3.xml | 0
.../tools/lint/checks/data/res/layout/crcrlf.xml | 0
.../lint/checks/data/res/layout/crcrlf_ignore.xml | 0
.../checks/data/res/layout/customattrlayout.xml | 0
.../lint/checks/data/res/layout/customview.xml | 0
.../lint/checks/data/res/layout/customview2.xml | 0
.../lint/checks/data/res/layout/customview3.xml | 0
.../checks/data/res/layout/default_item_badges.xml | 0
.../lint/checks/data/res/layout/deprecation.xml | 0
.../lint/checks/data/res/layout/detailed_item.xml | 0
.../lint/checks/data/res/layout/duplicate.xml | 0
.../lint/checks/data/res/layout/edit_textview.xml | 0
.../lint/checks/data/res/layout/edit_type.xml | 0
.../tools/lint/checks/data/res/layout/encoding.xml | 0
.../lint/checks/data/res/layout/encoding2.xml | 0
.../tools/lint/checks/data/res/layout/fragment.xml | 0
.../lint/checks/data/res/layout/fragment2.xml | 0
.../lint/checks/data/res/layout/gridlayout.xml | 0
.../lint/checks/data/res/layout/gridlayout2.xml | 0
.../lint/checks/data/res/layout/gridlayout3.xml | 0
.../lint/checks/data/res/layout/gridlayout4.xml | 0
.../lint/checks/data/res/layout/has_children.xml | 0
.../lint/checks/data/res/layout/has_children2.xml | 0
.../tools/lint/checks/data/res/layout/ignores.xml | 0
.../tools/lint/checks/data/res/layout/ignores2.xml | 0
.../lint/checks/data/res/layout/include_params.xml | 0
.../checks/data/res/layout/inefficient_weight.xml | 0
.../checks/data/res/layout/inefficient_weight2.xml | 0
.../checks/data/res/layout/inefficient_weight3.xml | 0
.../lint/checks/data/res/layout/invalid_ids.xml | 0
.../lint/checks/data/res/layout/invalid_ids2.xml | 0
.../tools/lint/checks/data/res/layout/labelfor.xml | 0
.../checks/data/res/layout/labelfor_ignore.xml | 0
.../tools/lint/checks/data/res/layout/layout1.xml | 0
.../lint/checks/data/res/layout/layout1_ignore.xml | 0
.../tools/lint/checks/data/res/layout/layout2.xml | 0
.../tools/lint/checks/data/res/layout/layout3.xml | 0
.../tools/lint/checks/data/res/layout/layout4.xml | 0
.../lint/checks/data/res/layout/layout4cycle.xml | 0
.../lint/checks/data/res/layout/layoutcycle1.xml | 0
.../lint/checks/data/res/layout/listseparator.xml | 0
.../checks/data/res/layout/message_edit_detail.xml | 0
.../lint/checks/data/res/layout/namespace.xml | 0
.../lint/checks/data/res/layout/namespace2.xml | 0
.../lint/checks/data/res/layout/namespace3.xml | 0
.../lint/checks/data/res/layout/namespace4.xml | 0
.../lint/checks/data/res/layout/namespace5.xml | 0
.../checks/data/res/layout/negative_margins.xml | 0
.../lint/checks/data/res/layout/nested_weights.xml | 0
.../checks/data/res/layout/nested_weights2.xml | 0
.../lint/checks/data/res/layout/note_edit.xml | 0
.../lint/checks/data/res/layout/note_edit2.xml | 0
.../checks/data/res/layout/now_playing_after.xml | 0
.../tools/lint/checks/data/res/layout/onclick.xml | 0
.../lint/checks/data/res/layout/orientation.xml | 0
.../lint/checks/data/res/layout/orientation2.xml | 0
.../checks/data/res/layout/relative_overlap.xml | 0
.../lint/checks/data/res/layout/scrolling.xml | 0
.../tools/lint/checks/data/res/layout/siblings.xml | 0
.../tools/lint/checks/data/res/layout/simple.xml | 0
.../lint/checks/data/res/layout/simple_ignore.xml | 0
.../lint/checks/data/res/layout/simpleinclude.xml | 0
.../tools/lint/checks/data/res/layout/size.xml | 0
.../tools/lint/checks/data/res/layout/size2.xml | 0
.../lint/checks/data/res/layout/sizeincluded.xml | 0
.../tools/lint/checks/data/res/layout/tag.xml | 0
.../tools/lint/checks/data/res/layout/textsize.xml | 0
.../lint/checks/data/res/layout/textsize2.xml | 0
.../lint/checks/data/res/layout/textureview.xml | 0
.../tools/lint/checks/data/res/layout/too_deep.xml | 0
.../tools/lint/checks/data/res/layout/too_many.xml | 0
.../checks/data/res/layout/unused_namespace.xml | 0
.../tools/lint/checks/data/res/layout/useless.xml | 0
.../tools/lint/checks/data/res/layout/useless2.xml | 0
.../tools/lint/checks/data/res/layout/useless3.xml | 0
.../tools/lint/checks/data/res/layout/useless4.xml | 0
.../tools/lint/checks/data/res/layout/webview.xml | 0
.../tools/lint/checks/data/res/layout/webview2.xml | 0
.../tools/lint/checks/data/res/layout/webview3.xml | 0
.../tools/lint/checks/data/res/layout/wrong0dp.xml | 0
.../checks/data/res/layout/wrong_dimension.xml | 0
.../checks/data/res/layout/wrong_namespace.xml | 0
.../checks/data/res/layout/wrong_namespace2.xml | 0
.../checks/data/res/layout/wrong_namespace3.xml | 0
.../checks/data/res/layout/wrong_namespace4.xml | 0
.../checks/data/res/layout/wrong_namespace5.xml | 0
.../lint/checks/data/res/layout/wrongparams.xml | 0
.../lint/checks/data/res/layout/wrongparams2.xml | 0
.../lint/checks/data/res/layout/wrongparams3.xml | 0
.../lint/checks/data/res/layout/wrongparams4.xml | 0
.../lint/checks/data/res/layout/wrongparams5.xml | 0
.../lint/checks/data/res/layout/wrongparams6.xml | 0
.../checks/data/res/layout/wrongparams_ignore.xml | 0
.../tools/lint/checks/data/res/layout/yesno.xml | 0
.../lint/checks/data/res/menu-land/actions.xml | 0
.../lint/checks/data/res/menu-land/actions2.xml | 0
.../checks/data/res/menu-land/actions2_ignore.xml | 0
.../tools/lint/checks/data/res/menu/menu.xml | 0
.../lint/checks/data/res/menu/showAction1.xml | 0
.../lint/checks/data/res/menu/showAction2.xml | 0
.../tools/lint/checks/data/res/menu/titles.xml | 0
.../tools/lint/checks/data/res/private_key.pem | 0
.../android/tools/lint/checks/data/res/raw/hello | Bin 0 -> 5244 bytes
.../lint/checks/data/res/values-cs/arrays.xml | 0
.../lint/checks/data/res/values-cs/plurals3.xml | 0
.../lint/checks/data/res/values-cs/strings.xml | 0
.../checks/data/res/values-cs/translatedarrays.xml | 0
.../lint/checks/data/res/values-de-rDE/strings.xml | 0
.../lint/checks/data/res/values-de/strings.xml | 0
.../tools/lint/checks/data/res/values-de/typos.xml | 0
.../lint/checks/data/res/values-es-rUS/strings.xml | 0
.../checks/data/res/values-es/donottranslate.xml | 0
.../checks/data/res/values-es/formatstrings.xml | 0
.../data/res/values-es/formatstrings_ignore.xml | 0
.../lint/checks/data/res/values-es/strings.xml | 0
.../checks/data/res/values-es/strings_ignore.xml | 0
.../checks/data/res/values-es/strings_locale.xml | 0
.../lint/checks/data/res/values-fr/strings.xml | 0
.../checks/data/res/values-it/stringarrays.xml | 0
.../lint/checks/data/res/values-land/arrays.xml | 0
.../checks/data/res/values-land/arrays_ignore.xml | 0
.../lint/checks/data/res/values-land/strings.xml | 0
.../tools/lint/checks/data/res/values-nb/typos.xml | 0
.../checks/data/res/values-nb/typos_locale.xml | 0
.../lint/checks/data/res/values-nl-rNL/arrays.xml | 0
.../lint/checks/data/res/values-nl-rNL/strings.xml | 0
.../lint/checks/data/res/values-pl/plurals2.xml | 0
.../lint/checks/data/res/values-ru/plurals6.xml | 0
.../lint/checks/data/res/values-zh-rCN/bom.xml | 0
.../checks/data/res/values-zh-rCN/plurals3.xml | 0
.../lint/checks/data/res/values/aaptcrash.xml | 0
.../tools/lint/checks/data/res/values/aliases.xml | 0
.../tools/lint/checks/data/res/values/aliases2.xml | 0
.../lint/checks/data/res/values/analytics.xml | 0
.../checks/data/res/values/appindexing_strings.xml | 0
.../data/res/values/appindexing_wrong_strings.xml | 0
.../tools/lint/checks/data/res/values/arrays.xml | 0
.../lint/checks/data/res/values/arrayusage.xml | 0
.../checks/data/res/values/buttonbar-values.xml | 0
.../lint/checks/data/res/values/colorcycle1.xml | 0
.../lint/checks/data/res/values/colorcycle2.xml | 0
.../lint/checks/data/res/values/colorcycle3.xml | 0
.../lint/checks/data/res/values/colorcycle4.xml | 0
.../lint/checks/data/res/values/colorcycle5.xml | 0
.../lint/checks/data/res/values/customattr.xml | 0
.../tools/lint/checks/data/res/values/dimens.xml | 0
.../checks/data/res/values/duplicate-items.xml | 0
.../checks/data/res/values/duplicate-strings.xml | 0
.../checks/data/res/values/duplicate-strings2.xml | 0
.../data/res/values/formatstrings-version1.xml | 0
.../data/res/values/formatstrings-version2.xml | 0
.../lint/checks/data/res/values/formatstrings.xml | 0
.../checks/data/res/values/formatstrings10.xml | 0
.../checks/data/res/values/formatstrings11.xml | 0
.../lint/checks/data/res/values/formatstrings2.xml | 0
.../lint/checks/data/res/values/formatstrings3.xml | 0
.../lint/checks/data/res/values/formatstrings4.xml | 0
.../lint/checks/data/res/values/formatstrings5.xml | 0
.../lint/checks/data/res/values/formatstrings6.xml | 0
.../lint/checks/data/res/values/formatstrings7.xml | 0
.../lint/checks/data/res/values/formatstrings8.xml | 0
.../lint/checks/data/res/values/formatstrings9.xml | 0
.../data/res/values/formatstrings_ignore.xml | 0
.../checks/data/res/values/google_maps_api.xml | 0
.../lint/checks/data/res/values/integer_arrays.xml | 0
.../tools/lint/checks/data/res/values/integers.xml | 0
.../checks/data/res/values/negative_margins.xml | 0
.../checks/data/res/values/nontranslatable.xml | 0
.../checks/data/res/values/nontranslatable2.xml | 0
.../tools/lint/checks/data/res/values/plurals.xml | 0
.../tools/lint/checks/data/res/values/plurals2.xml | 0
.../tools/lint/checks/data/res/values/plurals4.xml | 0
.../tools/lint/checks/data/res/values/plurals5.xml | 0
.../checks/data/res/values/plurals_candidates.xml | 0
.../checks/data/res/values/plurals_typography.xml | 0
.../tools/lint/checks/data/res/values/pxsp.xml | 0
.../tools/lint/checks/data/res/values/refs.xml | 0
.../tools/lint/checks/data/res/values/refs2.xml | 0
.../lint/checks/data/res/values/repeated_words.xml | 0
.../checks/data/res/values/shared_prefs_keys.xml | 0
.../lint/checks/data/res/values/sizestyles.xml | 0
.../lint/checks/data/res/values/stringarrays.xml | 0
.../tools/lint/checks/data/res/values/strings.xml | 0
.../tools/lint/checks/data/res/values/strings2.xml | 0
.../tools/lint/checks/data/res/values/strings3.xml | 0
.../tools/lint/checks/data/res/values/strings4.xml | 0
.../lint/checks/data/res/values/strings_ignore.xml | 0
.../lint/checks/data/res/values/stylecycle.xml | 0
.../lint/checks/data/res/values/stylecycle1.xml | 0
.../lint/checks/data/res/values/stylecycle2.xml | 0
.../res/values/styles-inherited-orientation.xml | 0
.../checks/data/res/values/styles-orientation.xml | 0
.../tools/lint/checks/data/res/values/styles.xml | 0
.../tools/lint/checks/data/res/values/styles2.xml | 0
.../tools/lint/checks/data/res/values/themes.xml | 0
.../tools/lint/checks/data/res/values/themes2.xml | 0
.../tools/lint/checks/data/res/values/themes3.xml | 0
.../checks/data/res/values/translatedarrays.xml | 0
.../lint/checks/data/res/values/typography.xml | 0
.../tools/lint/checks/data/res/values/typos.xml | 0
.../lint/checks/data/res/xml/nfc_tech_list.xml | 0
.../data/res/xml/nfc_tech_list_formatted.xml | 0
.../lint/checks/data/res/xml/prefs_headers.xml | 0
.../lint/checks/data/rtl/GravityTest.java.txt | 0
.../lint/checks/data/rtl/GravityTest2.java.txt | 0
.../tools/lint/checks/data/rtl/min17nortl.xml | 0
.../tools/lint/checks/data/rtl/min17rtl.xml | 0
.../lint/checks/data/rtl/minsdk5targetsdk17.xml | 0
.../lint/checks/data/rtl/project-api14.properties | 0
.../lint/checks/data/rtl/project-api17.properties | 0
.../tools/lint/checks/data/rtl/relative.xml | 0
.../tools/lint/checks/data/rtl/relativeCompat.xml | 0
.../tools/lint/checks/data/rtl/relativeOk.xml | 0
.../com/android/tools/lint/checks/data/rtl/rtl.xml | 0
.../tools/lint/checks/data/rtl/rtl_noprefix.xml | 0
.../android/tools/lint/checks/data/rtl/spinner.xml | 0
.../tools/lint/checks/data/rtl/symmetry.xml | 0
.../checks/data/rules/AppCompatTest.class.data | Bin
.../lint/checks/data/rules/AppCompatTest.java.txt | 0
.../tools/lint/checks/data/safereceiver1.xml | 23 +
.../src/android/support/annotation/AnyRes.java.txt | 15 +
.../android/support/annotation/CallSuper.java.txt | 0
.../support/annotation/CheckResult.java.txt | 0
.../android/support/annotation/ColorInt.java.txt | 0
.../support/annotation/DrawableRes.java.txt | 17 +
.../android/support/annotation/FloatRange.java.txt | 0
.../src/android/support/annotation/IntDef.java.txt | 0
.../android/support/annotation/IntRange.java.txt | 0
.../src/android/support/annotation/Size.java.txt | 0
.../android/support/annotation/StringDef.java.txt | 0
.../data/src/com/appindexing/Activity.java.txt | 6 +
.../checks/data/src/com/appindexing/Api.java.txt | 8 +
.../data/src/com/appindexing/AppIndex.java.txt | 10 +
.../data/src/com/appindexing/AppIndexApi.java.txt | 27 +
.../src/com/appindexing/GoogleApiClient.java.txt | 15 +
.../helloworld/AppIndexingApiTestEndMatch.java.txt | 35 +
...ppIndexingApiTestGoogleApiClientAddApi.java.txt | 40 +
.../AppIndexingApiTestNoStartEnd.java.txt | 34 +
.../helloworld/AppIndexingApiTestOk.java.txt | 40 +
.../AppIndexingApiTestStartMatch.java.txt | 36 +
.../AppIndexingApiTestViewEndMatch.java.txt | 31 +
.../AppIndexingApiTestViewMatch.java.txt | 33 +
.../AppIndexingApiTestWrongOrder.java.txt | 40 +
.../lint/checks/data/src/my/pkg/Test.java.txt | 0
.../checks/data/src/p1/p2/ColorAsDrawable.java.txt | 0
.../tools/lint/checks/data/src/p1/p2/Flow.java.txt | 0
.../lint/checks/data/src/pkg1/Class1.java.txt | 0
.../lint/checks/data/src/pkg2/Class2.java.txt | 0
.../data/src/test/pkg/ActionBarTest.java.txt | 0
.../checks/data/src/test/pkg/ActionTest1.java.txt | 0
.../data/src/test/pkg/ActionTest1_ignore.java.txt | 10 +
.../checks/data/src/test/pkg/ActionTest2.java.txt | 0
.../checks/data/src/test/pkg/AlarmTest.java.txt | 22 +
.../lint/checks/data/src/test/pkg/Assert.java.txt | 27 +
.../checks/data/src/test/pkg/BadImport.java.txt | 0
.../data/src/test/pkg/CallSuperTest.java.txt | 0
.../data/src/test/pkg/CheckPermissions.java.txt | 0
.../checks/data/src/test/pkg/CheckResult.java.txt | 0
.../src/test/pkg/CipherGetInstanceAES.java.txt | 0
.../src/test/pkg/CipherGetInstanceAESCBC.java.txt | 0
.../src/test/pkg/CipherGetInstanceAESECB.java.txt | 0
.../src/test/pkg/CipherGetInstanceDES.java.txt | 0
.../src/test/pkg/CipherGetInstanceTest.java.txt | 0
.../checks/data/src/test/pkg/ColorUsage.java.txt | 0
.../test/pkg/ContentProviderClientTest.java.txt | 0
.../checks/data/src/test/pkg/CursorTest.java.txt | 0
.../checks/data/src/test/pkg/CustomView1.java.txt | 0
.../data/src/test/pkg/CustomViewTest.java.txt | 0
.../data/src/test/pkg/DetachedFromWindow.java.txt | 46 +
.../lint/checks/data/src/test/pkg/Foo.java.txt | 0
.../test/pkg/GetSignaturesBitwiseAndTest.java.txt | 0
.../test/pkg/GetSignaturesBitwiseOrTest.java.txt | 0
.../test/pkg/GetSignaturesBitwiseXorTest.java.txt | 0
.../pkg/GetSignaturesLocalVariableTest.java.txt | 0
.../src/test/pkg/GetSignaturesNoFlagTest.java.txt | 0
.../GetSignaturesNotPackageManagerTest.java.txt | 0
.../test/pkg/GetSignaturesSingleFlagTest.java.txt | 0
.../test/pkg/GetSignaturesStaticFieldTest.java.txt | 0
.../lint/checks/data/src/test/pkg/Hidden.java.txt | 0
.../data/src/test/pkg/ImportFrameActivity.java.txt | 0
.../checks/data/src/test/pkg/InflaterTest.java.txt | 0
.../checks/data/src/test/pkg/IntDefTest.java.txt | 0
.../checks/data/src/test/pkg/Java7API.class.data | Bin
.../checks/data/src/test/pkg/Java7API.java.txt | 0
.../data/src/test/pkg/JavaPerformanceTest.java.txt | 0
.../data/src/test/pkg/LayoutInflationTest.java.txt | 0
.../test/pkg/LayoutInflationTest_ignored.java.txt | 0
.../lint/checks/data/src/test/pkg/Log.java.txt | 0
.../lint/checks/data/src/test/pkg/LogTest.java.txt | 0
.../data/src/test/pkg/LongSparseArray.java.txt | 12 +
.../data/src/test/pkg/MyTracksProvider.java.txt | 0
.../NonInternationalizedSmsDetectorTest.java.txt | 0
.../data/src/test/pkg/NotificationTest.java.txt | 0
.../src/test/pkg/OverrideConcreteTest.java.txt | 0
.../checks/data/src/test/pkg/PasteError.java.txt | 0
.../test/pkg/PreferenceActivitySubclass.java.txt | 0
...tivitySubclassOverridesIsValidFragment.java.txt | 10 +
.../checks/data/src/test/pkg/RangeTest.java.txt | 0
.../data/src/test/pkg/SQLiteDatabase.java.txt | 0
.../checks/data/src/test/pkg/SQLiteTest.java.txt | 41 +
.../checks/data/src/test/pkg/SdCardTest.java.txt | 0
.../src/test/pkg/SetJavaScriptEnabled.java.txt | 37 +
.../data/src/test/pkg/SharedPrefsTest.java.txt | 0
.../data/src/test/pkg/SharedPrefsTest2.java.txt | 0
.../data/src/test/pkg/SharedPrefsTest3.java.txt | 0
.../data/src/test/pkg/SharedPrefsTest4.java.txt | 0
.../data/src/test/pkg/SharedPrefsTest5.java.txt | 0
.../data/src/test/pkg/SharedPrefsTest6.java.txt | 0
.../data/src/test/pkg/SharedPrefsTest7.java.txt | 0
.../data/src/test/pkg/SharedPrefsTest8.java.txt | 0
.../data/src/test/pkg/SparseLongArray.java.txt | 12 +
.../data/src/test/pkg/StringFormat10.java.txt | 0
.../data/src/test/pkg/StringFormat11.java.txt | 0
.../data/src/test/pkg/StringFormat2.java.txt | 20 +
.../data/src/test/pkg/StringFormat3.java.txt | 34 +
.../data/src/test/pkg/StringFormat4.java.txt | 26 +
.../data/src/test/pkg/StringFormat5.java.txt | 25 +
.../data/src/test/pkg/StringFormat8.java.txt | 23 +
.../data/src/test/pkg/StringFormat9.java.txt | 15 +
.../src/test/pkg/StringFormatActivity.java.txt | 46 +
.../src/test/pkg/StringFormatActivity2.java.txt | 32 +
.../src/test/pkg/StringFormatActivity3.java.txt | 22 +
.../test/pkg/StringFormatActivity_ignore.java.txt | 27 +
.../data/src/test/pkg/SuppressTest5.java.txt | 63 +
.../data/src/test/pkg/SurfaceTextureTest.java.txt | 0
.../data/src/test/pkg/SystemServiceTest.java.txt | 31 +
.../checks/data/src/test/pkg/ToastTest.java.txt | 57 +
.../data/src/test/pkg/TransactionTest.java.txt | 0
.../checks/data/src/test/pkg/TryCatchHang.java.txt | 0
.../data/src/test/pkg/TryWithResources.java.txt | 0
.../data/src/test/pkg/UnusedReference.java.txt | 0
.../src/test/pkg/UnusedReferenceDynamic.java.txt | 14 +
.../checks/data/src/test/pkg/Utf8BomTest.java.data | 0
.../data/src/test/pkg/ViewHolderTest.java.txt | 168 +
.../data/src/test/pkg/WatchFaceTest.java.txt | 0
.../data/src/test/pkg/WorldWriteableFile.java.txt | 58 +
.../data/src/test/pkg/WrongAnnotation.java.txt | 36 +
.../data/src/test/pkg/WrongCastActivity.java.txt | 26 +
.../data/src/test/pkg/WrongCastActivity2.java.txt | 22 +
.../data/src/test/pkg/WrongCastActivity3.java.txt | 17 +
.../checks/data/src/test/pkg/WrongColor.java.txt | 0
.../data/stubs/CanvasWatchFaceService.java.txt | 0
.../checks/data/stubs/WatchFaceService.java.txt | 0
.../com/android/tools/lint/checks/data/tls.xml | 18 +
.../com/android/tools/lint/checks/data/tls1.xml | 18 +
.../tools/lint/checks/data/typo_manifest.xml | 0
.../tools/lint/checks/data/typo_not_found.xml | 0
.../tools/lint/checks/data/typo_uses_feature.xml | 0
.../tools/lint/checks/data/typo_uses_feature2.xml | 0
.../tools/lint/checks/data/typo_uses_library.xml | 0
.../tools/lint/checks/data/typo_uses_library2.xml | 0
.../lint/checks/data/typo_uses_permission.xml | 0
.../lint/checks/data/typo_uses_permission2.xml | 0
.../tools/lint/checks/data/typo_uses_sdk.xml | 0
.../tools/lint/checks/data/typo_uses_sdk2.xml | 0
.../tools/lint/checks/data/unsafereceiver1.xml | 22 +
.../tools/lint/checks/data/unusedR.java.txt | 0
.../tools/lint/checks/data/wrongid/Foo.java.txt | 0
.../android/tools/lint/checks/data/wrongid/ids.xml | 0
.../lint/checks/data/wrongid/ignorelayout1.xml | 0
.../tools/lint/checks/data/wrongid/layout1.xml | 0
.../lint/checks/data/wrongid/layout1_ignore.xml | 0
.../tools/lint/checks/data/wrongid/layout2.xml | 0
.../tools/lint/checks/data/wrongid/layout3.xml | 0
.../client/api/CompositeIssueRegistryTest.java | 0
.../tools/lint/client/api/CustomRuleTest.java | 0
.../lint/client/api/DefaultConfigurationTest.java | 0
.../tools/lint/client/api/DefaultSdkInfoTest.java | 0
.../lint/client/api/JarFileIssueRegistryTest.java | 88 +
.../tools/lint/client/api/LintClientTest.java | 0
.../tools/lint/client/api/LintDriverTest.java | 0
.../android/tools/lint/client/api/ProjectTest.java | 0
.../tools/lint/detector/api/CategoryTest.java | 74 +
.../tools/lint/detector/api/ClassContextTest.java | 0
.../lint/detector/api/ConstantEvaluatorTest.java | 204 ++
.../lint/detector/api/ImplementationTest.java | 0
.../android/tools/lint/detector/api/IssueTest.java | 0
.../tools/lint/detector/api/LintUtilsTest.java | 522 +++
.../tools/lint/detector/api/LocationTest.java | 0
.../android/tools/lint/detector/api/ScopeTest.java | 0
.../tools/lint/detector/api/SeverityTest.java | 0
.../tools/lint/detector/api/TextFormatTest.java | 295 ++
.../tools/lint/detector/api/TypeEvaluatorTest.java | 218 ++
.../src/android/annotation/SuppressLint.java | 0
.../src/android/annotation/TargetApi.java | 0
{base/misc => misc}/api-generator/.gitignore | 0
misc/api-generator/build.gradle | 15 +
.../com/android/apigenerator/AndroidJarReader.java | 272 ++
.../java/com/android/apigenerator/ApiClass.java | 0
.../main/java/com/android/apigenerator/Main.java | 198 ++
{base/misc => misc}/attribute_stats/.classpath | 0
{base/misc => misc}/attribute_stats/.gitignore | 0
{base/misc => misc}/attribute_stats/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
{base/misc => misc}/attribute_stats/README.txt | 0
.../attribute_stats/src/Analyzer.java | 0
{base/misc => misc}/screenshot2/.classpath | 0
{base/misc => misc}/screenshot2/.gitignore | 0
{base/misc => misc}/screenshot2/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
{base/legacy/archquery => misc/screenshot2}/NOTICE | 0
{base/misc => misc}/screenshot2/build.gradle | 0
{base/misc => misc}/screenshot2/etc/screenshot2 | 0
.../java/com/android/screenshot/Screenshot.java | 0
{base/ninepatch => ninepatch}/.classpath | 0
{base/ninepatch => ninepatch}/.gitignore | 0
{base/ninepatch => ninepatch}/.project | 0
{base/misc/screenshot2 => ninepatch}/NOTICE | 0
{base/ninepatch => ninepatch}/build.gradle | 0
{base/ninepatch => ninepatch}/ninepatch.iml | 0
.../com/android/ninepatch/GraphicsUtilities.java | 0
.../main/java/com/android/ninepatch/NinePatch.java | 0
.../java/com/android/ninepatch/NinePatchChunk.java | 0
{base/ninepatch => ninepatch}/src/test/.classpath | 0
{base/ninepatch => ninepatch}/src/test/.gitignore | 0
{base/ninepatch => ninepatch}/src/test/.project | 0
.../java/com/android/ninepatch/NinePatchTest.java | 0
.../resources/com/android/ninepatch/button.9.png | Bin
{base/perflib => perflib}/build.gradle | 0
{base/perflib => perflib}/perflib.iml | 0
.../tools/perflib/analyzer/AnalysisReport.java | 99 +
.../perflib/analyzer/AnalysisResultEntry.java | 30 +
.../android/tools/perflib/analyzer/Analyzer.java | 36 +
.../tools/perflib/analyzer/AnalyzerTask.java | 27 +
.../android/tools/perflib/analyzer/Capture.java | 28 +
.../tools/perflib/analyzer/CaptureAnalyzer.java | 26 +
.../tools/perflib/analyzer/CaptureGroup.java | 34 +
.../android/tools/perflib/analyzer/Offender.java | 45 +
.../android/tools/perflib/captures/DataBuffer.java | 55 +
.../perflib/captures/MemoryMappedFileBuffer.java | 228 ++
.../android/tools/perflib/heap/ArrayInstance.java | 127 +
.../android/tools/perflib/heap/ClassInstance.java | 151 +
.../com/android/tools/perflib/heap/ClassObj.java | 309 ++
.../java/com/android/tools/perflib/heap/Field.java | 0
.../java/com/android/tools/perflib/heap/Heap.java | 164 +
.../android/tools/perflib/heap/HprofParser.java | 625 ++++
.../com/android/tools/perflib/heap/Instance.java | 349 ++
.../java/com/android/tools/perflib/heap/Main.java | 94 +
.../tools/perflib/heap/NonRecursiveVisitor.java | 0
.../com/android/tools/perflib/heap/Queries.java | 264 ++
.../com/android/tools/perflib/heap/RootObj.java | 91 +
.../com/android/tools/perflib/heap/RootType.java | 0
.../com/android/tools/perflib/heap/Snapshot.java | 530 +++
.../com/android/tools/perflib/heap/StackFrame.java | 94 +
.../com/android/tools/perflib/heap/StackTrace.java | 81 +
.../com/android/tools/perflib/heap/ThreadObj.java | 0
.../java/com/android/tools/perflib/heap/Type.java | 0
.../java/com/android/tools/perflib/heap/Value.java | 43 +
.../com/android/tools/perflib/heap/Visitor.java | 0
.../perflib/heap/analysis/ComputationProgress.java | 48 +
.../perflib/heap/analysis/DominatorsBase.java | 85 +
.../perflib/heap/analysis/LinkEvalDominators.java | 440 +++
.../heap/analysis/ShortestDistanceVisitor.java | 68 +
.../perflib/heap/analysis/TopologicalSort.java | 100 +
.../android/tools/perflib/heap/hprof/Hprof.java | 70 +
.../tools/perflib/heap/hprof/HprofAllocSite.java | 53 +
.../tools/perflib/heap/hprof/HprofAllocSites.java | 65 +
.../tools/perflib/heap/hprof/HprofClassDump.java | 95 +
.../tools/perflib/heap/hprof/HprofConstant.java | 42 +
.../perflib/heap/hprof/HprofControlSettings.java | 43 +
.../tools/perflib/heap/hprof/HprofCpuSample.java | 35 +
.../tools/perflib/heap/hprof/HprofCpuSamples.java | 43 +
.../tools/perflib/heap/hprof/HprofDumpRecord.java | 32 +
.../tools/perflib/heap/hprof/HprofEndThread.java | 36 +
.../tools/perflib/heap/hprof/HprofHeapDump.java | 44 +
.../tools/perflib/heap/hprof/HprofHeapDumpEnd.java | 33 +
.../perflib/heap/hprof/HprofHeapDumpInfo.java | 50 +
.../perflib/heap/hprof/HprofHeapDumpSegment.java | 44 +
.../tools/perflib/heap/hprof/HprofHeapSummary.java | 47 +
.../perflib/heap/hprof/HprofInstanceDump.java | 49 +
.../perflib/heap/hprof/HprofInstanceField.java | 39 +
.../tools/perflib/heap/hprof/HprofLoadClass.java | 46 +
.../perflib/heap/hprof/HprofObjectArrayDump.java | 51 +
.../perflib/heap/hprof/HprofOutputStream.java | 139 +
.../heap/hprof/HprofPrimitiveArrayDump.java | 51 +
.../tools/perflib/heap/hprof/HprofRecord.java | 27 +
.../perflib/heap/hprof/HprofRootDebugger.java | 41 +
.../heap/hprof/HprofRootInternedString.java | 41 +
.../perflib/heap/hprof/HprofRootJavaFrame.java | 45 +
.../perflib/heap/hprof/HprofRootJniGlobal.java | 41 +
.../perflib/heap/hprof/HprofRootJniLocal.java | 45 +
.../perflib/heap/hprof/HprofRootJniMonitor.java | 48 +
.../perflib/heap/hprof/HprofRootMonitorUsed.java | 38 +
.../perflib/heap/hprof/HprofRootNativeStack.java | 41 +
.../perflib/heap/hprof/HprofRootStickyClass.java | 38 +
.../perflib/heap/hprof/HprofRootThreadBlock.java | 41 +
.../perflib/heap/hprof/HprofRootThreadObject.java | 45 +
.../tools/perflib/heap/hprof/HprofRootUnknown.java | 38 +
.../perflib/heap/hprof/HprofRootVmInternal.java | 41 +
.../tools/perflib/heap/hprof/HprofStackFrame.java | 61 +
.../tools/perflib/heap/hprof/HprofStackTrace.java | 48 +
.../tools/perflib/heap/hprof/HprofStartThread.java | 54 +
.../tools/perflib/heap/hprof/HprofStaticField.java | 43 +
.../tools/perflib/heap/hprof/HprofString.java | 40 +
.../perflib/heap/hprof/HprofStringBuilder.java | 55 +
.../tools/perflib/heap/hprof/HprofType.java | 44 +
.../tools/perflib/heap/hprof/HprofUnloadClass.java | 35 +
.../tools/perflib/heap/io/InMemoryBuffer.java | 115 +
.../DuplicatedStringsAnalyzerTask.java | 104 +
.../heap/memoryanalyzer/HprofBitmapProvider.java | 179 +
.../memoryanalyzer/LeakedActivityAnalyzerTask.java | 110 +
.../memoryanalyzer/MemoryAnalysisResultEntry.java | 40 +
.../heap/memoryanalyzer/MemoryAnalyzer.java | 183 +
.../heap/memoryanalyzer/MemoryAnalyzerTask.java | 40 +
.../com/android/tools/perflib/vmtrace/Call.java | 0
.../perflib/vmtrace/CallStackReconstructor.java | 0
.../android/tools/perflib/vmtrace/ClockType.java | 0
.../android/tools/perflib/vmtrace/MethodInfo.java | 0
.../tools/perflib/vmtrace/MethodProfileData.java | 0
.../tools/perflib/vmtrace/SearchResult.java | 0
.../android/tools/perflib/vmtrace/ThreadInfo.java | 0
.../tools/perflib/vmtrace/TimeSelector.java | 0
.../android/tools/perflib/vmtrace/TraceAction.java | 0
.../android/tools/perflib/vmtrace/VmTraceData.java | 0
.../tools/perflib/vmtrace/VmTraceParser.java | 0
.../perflib/vmtrace/viz/CallHierarchyRenderer.java | 0
.../tools/perflib/vmtrace/viz/RenderContext.java | 0
.../perflib/vmtrace/viz/TimeScaleRenderer.java | 0
.../tools/perflib/vmtrace/viz/TimeUtils.java | 0
.../tools/perflib/vmtrace/viz/TraceViewCanvas.java | 0
.../perflib/vmtrace/viz/ZoomPanInteractor.java | 0
.../src/test/TraceViewTestApp/.gitignore | 0
.../src/test/TraceViewTestApp/build.gradle | 0
.../TraceViewTestApp/src/main/AndroidManifest.xml | 0
.../java/com/test/android/traceview/Basic.java | 0
.../com/test/android/traceview/Exceptions.java | 0
.../com/test/android/traceview/MainActivity.java | 0
.../com/test/android/traceview/MisMatched.java | 0
.../java/com/test/android/traceview/Tests.java | 0
.../src/main/res/drawable/ic_launcher.png | Bin
.../src/main/res/layout/activity_main.xml | 0
.../tools/perflib/heap/ArrayInstanceTest.java | 73 +
.../android/tools/perflib/heap/ClassObjTest.java | 78 +
.../tools/perflib/heap/HprofParserTest.java | 189 ++
.../android/tools/perflib/heap/QueriesTest.java | 55 +
.../tools/perflib/heap/SnapshotBuilder.java | 217 ++
.../com/android/tools/perflib/heap/StacksTest.java | 105 +
.../heap/analysis/ConvergingDominators.java | 111 +
.../heap/analysis/DominatorOptimizationTest.java | 77 +
.../perflib/heap/analysis/DominatorsTest.java | 297 ++
.../tools/perflib/heap/analysis/VisitorsTest.java | 168 +
.../heap/io/MemoryMappedFileBufferTest.java | 140 +
.../memoryanalyzer/HprofBitmapProviderTest.java | 106 +
.../vmtrace/CallStackReconstructorTest.java | 0
.../tools/perflib/vmtrace/VmTraceParserTest.java | 0
.../tools/perflib/vmtrace/viz/TraceView.java | 0
.../perflib/vmtrace/viz/ZoomPanInteractorTest.java | 0
.../src/test/resources/.gitignore | 0
.../src/test/resources/README.txt | 0
.../src/test/resources/basic-api10.trace | Bin
.../src/test/resources/basic.android-hprof | Bin
.../src/test/resources/basic.trace | Bin
.../src/test/resources/bitmap_test.android-hprof | Bin 0 -> 19349509 bytes
.../src/test/resources/dialer.android-hprof | Bin
.../src/test/resources/exception.trace | Bin
.../src/test/resources/header.trace | 0
.../src/test/resources/mismatched.trace | Bin
.../test/resources/native_allocation.android-hprof | Bin 0 -> 26580886 bytes
{base/rule-api => repository}/NOTICE | 0
repository/build.gradle | 35 +
repository/repository.iml | 16 +
.../main/java/com/android/repository/Revision.java | 455 +++
.../java/com/android/repository/api/Channel.java | 93 +
.../repository/api/ConsoleProgressIndicator.java | 48 +
.../repository/api/ConstantSourceProvider.java | 68 +
.../com/android/repository/api/Dependency.java | 64 +
.../com/android/repository/api/Downloader.java | 57 +
.../repository/api/FallbackLocalRepoLoader.java | 47 +
.../repository/api/FallbackRemoteRepoLoader.java | 41 +
.../java/com/android/repository/api/License.java | 183 +
.../com/android/repository/api/LocalPackage.java | 40 +
.../android/repository/api/ProgressIndicator.java | 101 +
.../repository/api/ProgressIndicatorAdapter.java | 83 +
.../com/android/repository/api/ProgressRunner.java | 48 +
.../repository/api/RemoteListSourceProvider.java | 55 +
.../com/android/repository/api/RemotePackage.java | 54 +
.../com/android/repository/api/RemoteSource.java | 127 +
.../com/android/repository/api/RepoManager.java | 362 ++
.../com/android/repository/api/RepoPackage.java | 85 +
.../com/android/repository/api/Repository.java | 78 +
.../android/repository/api/RepositorySource.java | 75 +
.../repository/api/RepositorySourceProvider.java | 71 +
.../com/android/repository/api/SchemaModule.java | 215 ++
.../android/repository/api/SettingsController.java | 43 +
.../repository/api/SimpleRepositorySource.java | 134 +
.../android/repository/api/UpdatablePackage.java | 104 +
.../java/com/android/repository/api/catalog.xml | 34 +
.../java/com/android/repository/api/common.xjb | 65 +
.../java/com/android/repository/api/generic-01.xsd | 76 +
.../java/com/android/repository/api/generic.xjb | 20 +
.../java/com/android/repository/api/global.xjb | 23 +
.../com/android/repository/api/list-common.xjb | 26 +
.../com/android/repository/api/repo-common-01.xsd | 378 +++
.../android/repository/api/repo-sites-common-1.xsd | 94 +
.../generated/generic/v1/GenericDetailsType.java | 53 +
.../impl/generated/generic/v1/ObjectFactory.java | 67 +
.../impl/generated/generic/v1/package-info.java | 8 +
.../repository/impl/generated/v1/ArchiveType.java | 246 ++
.../repository/impl/generated/v1/ArchivesType.java | 92 +
.../impl/generated/v1/ChannelRefType.java | 81 +
.../repository/impl/generated/v1/ChannelType.java | 122 +
.../repository/impl/generated/v1/CompleteType.java | 134 +
.../impl/generated/v1/DependenciesType.java | 92 +
.../impl/generated/v1/DependencyType.java | 117 +
.../impl/generated/v1/LicenseRefType.java | 87 +
.../repository/impl/generated/v1/LicenseType.java | 148 +
.../repository/impl/generated/v1/LocalPackage.java | 261 ++
.../impl/generated/v1/ObjectFactory.java | 178 +
.../repository/impl/generated/v1/PatchType.java | 169 +
.../repository/impl/generated/v1/PatchesType.java | 91 +
.../impl/generated/v1/RemotePackage.java | 326 ++
.../impl/generated/v1/RepositoryType.java | 199 ++
.../repository/impl/generated/v1/RevisionType.java | 149 +
.../repository/impl/generated/v1/TypeDetails.java | 47 +
.../repository/impl/generated/v1/package-info.java | 2 +
.../repository/impl/installer/BasicInstaller.java | 127 +
.../impl/installer/PackageInstaller.java | 64 +
.../repository/impl/manager/LocalRepoLoader.java | 259 ++
.../repository/impl/manager/RemoteRepoLoader.java | 201 ++
.../repository/impl/manager/RepoManagerImpl.java | 564 ++++
.../com/android/repository/impl/meta/Archive.java | 327 ++
.../repository/impl/meta/CommonFactory.java | 137 +
.../repository/impl/meta/GenericFactory.java | 28 +
.../repository/impl/meta/LocalPackageImpl.java | 70 +
.../repository/impl/meta/RemotePackageImpl.java | 91 +
.../repository/impl/meta/RepoPackageImpl.java | 237 ++
.../repository/impl/meta/RepositoryPackages.java | 269 ++
.../android/repository/impl/meta/RevisionType.java | 91 +
.../repository/impl/meta/SchemaModuleUtil.java | 383 +++
.../repository/impl/meta/TrimStringAdapter.java | 45 +
.../android/repository/impl/meta/TypeDetails.java | 40 +
.../android/repository/impl/meta/common-custom.xjb | 92 +
.../repository/impl/meta/generic-custom.xjb | 32 +
.../android/repository/impl/meta/package-info.java | 7 +
.../impl/sources/LocalSourceProvider.java | 268 ++
.../impl/sources/RemoteListSourceProviderImpl.java | 269 ++
.../impl/sources/generated/v1/GenericSiteType.java | 49 +
.../impl/sources/generated/v1/ObjectFactory.java | 70 +
.../impl/sources/generated/v1/SiteListType.java | 90 +
.../impl/sources/generated/v1/SiteType.java | 115 +
.../impl/sources/generated/v1/package-info.java | 2 +
.../impl/sources/repo-sites-common-custom.xjb | 39 +
.../java/com/android/repository/io/FileOp.java | 212 ++
.../com/android/repository/io/FileOpUtils.java | 282 ++
.../com/android/repository/io/impl/FileOpImpl.java | 390 +++
.../repository/testframework/FakeDependency.java | 70 +
.../repository/testframework/FakeDownloader.java | 114 +
.../repository/testframework/FakePackage.java | 212 ++
.../testframework/FakeProgressIndicator.java | 155 +
.../testframework/FakeProgressRunner.java | 52 +
.../testframework/FakeSettingsController.java | 52 +
.../repository/testframework/MockFileOp.java | 733 ++++
.../com/android/repository/util/InstallerUtil.java | 365 ++
.../java/com/android/repository/RevisionTest.java | 191 ++
.../impl/installer/BasicInstallerTest.java | 309 ++
.../repository/impl/local/LocalRepoTest.java | 255 ++
.../impl/meta/RepositoryPackagesTest.java | 142 +
.../impl/meta/TrimStringAdapterTest.java | 53 +
.../repository/impl/remote/RemoteRepoTest.java | 276 ++
.../impl/sources/RemoteListSourceProviderTest.java | 160 +
.../repository/impl/testData/testPackage.xml | 33 +
.../impl/testData/testPackage2-lowerVersion.xml | 32 +
.../repository/impl/testData/testPackage2.xml | 34 +
.../android/repository/impl/testData/testRepo.xml | 101 +
.../impl/testData/testRepoWithChannels.xml | 123 +
.../repository/impl/testData/testSourceList-1.xml | 34 +
.../repository/impl/testData/testSourceList2-1.xml | 34 +
.../com/android/repository/io/FileOpUtilsTest.java | 87 +
.../android/repository/io/impl/MockFileOpTest.java | 241 ++
.../android/repository/util/InstallerUtilTest.java | 483 +++
{base/rpclib => rpclib}/build.gradle | 0
rpclib/rpclib.iml | 18 +
.../java/com/android/tools/rpclib/any/Bool.java | 90 +
.../com/android/tools/rpclib/any/BoolSlice.java | 96 +
.../java/com/android/tools/rpclib/any/Box.java | 55 +
.../java/com/android/tools/rpclib/any/Factory.java | 53 +
.../java/com/android/tools/rpclib/any/Float32.java | 90 +
.../com/android/tools/rpclib/any/Float32Slice.java | 96 +
.../java/com/android/tools/rpclib/any/Float64.java | 90 +
.../com/android/tools/rpclib/any/Float64Slice.java | 96 +
.../main/java/com/android/tools/rpclib/any/Id.java | 88 +
.../java/com/android/tools/rpclib/any/IdSlice.java | 94 +
.../java/com/android/tools/rpclib/any/Int16.java | 90 +
.../com/android/tools/rpclib/any/Int16Slice.java | 96 +
.../java/com/android/tools/rpclib/any/Int32.java | 90 +
.../com/android/tools/rpclib/any/Int32Slice.java | 96 +
.../java/com/android/tools/rpclib/any/Int64.java | 90 +
.../com/android/tools/rpclib/any/Int64Slice.java | 96 +
.../java/com/android/tools/rpclib/any/Int8.java | 90 +
.../com/android/tools/rpclib/any/Int8Slice.java | 96 +
.../com/android/tools/rpclib/any/ObjectBox.java | 90 +
.../com/android/tools/rpclib/any/ObjectSlice.java | 96 +
.../com/android/tools/rpclib/any/StringBox.java | 90 +
.../com/android/tools/rpclib/any/StringSlice.java | 96 +
.../java/com/android/tools/rpclib/any/Uint16.java | 90 +
.../com/android/tools/rpclib/any/Uint16Slice.java | 96 +
.../java/com/android/tools/rpclib/any/Uint32.java | 90 +
.../com/android/tools/rpclib/any/Uint32Slice.java | 96 +
.../java/com/android/tools/rpclib/any/Uint64.java | 90 +
.../com/android/tools/rpclib/any/Uint64Slice.java | 96 +
.../java/com/android/tools/rpclib/any/Uint8.java | 90 +
.../com/android/tools/rpclib/any/Uint8Slice.java | 94 +
.../android/tools/rpclib/binary/BinaryClass.java | 31 +
.../com/android/tools/rpclib/binary/BinaryID.java | 79 +
.../android/tools/rpclib/binary/BinaryObject.java | 29 +
.../com/android/tools/rpclib/binary/BitField.java | 67 +
.../com/android/tools/rpclib/binary/Decoder.java | 230 ++
.../com/android/tools/rpclib/binary/Encoder.java | 221 ++
.../tools/rpclib/binary/EncodingControl.java | 42 +
.../com/android/tools/rpclib/binary/Namespace.java | 40 +
.../tools/rpclib/futures/FutureController.java | 49 +
.../tools/rpclib/futures/SingleInFlight.java | 81 +
.../android/tools/rpclib/multiplex/Channel.java | 96 +
.../android/tools/rpclib/multiplex/Message.java | 0
.../tools/rpclib/multiplex/Multiplexer.java | 189 ++
.../tools/rpclib/multiplex/NewChannelListener.java | 0
.../tools/rpclib/multiplex/PipeInputStream.java | 141 +
.../com/android/tools/rpclib/multiplex/Sender.java | 291 ++
.../android/tools/rpclib/rpccore/Broadcaster.java | 85 +
.../tools/rpclib/rpccore/ErrDecodingCall.java | 87 +
.../tools/rpclib/rpccore/ErrInvalidHeader.java | 89 +
.../com/android/tools/rpclib/rpccore/ErrPanic.java | 87 +
.../tools/rpclib/rpccore/ErrUnknownFunction.java | 87 +
.../com/android/tools/rpclib/rpccore/Factory.java | 32 +
.../java/com/android/tools/rpclib/rpccore/Rpc.java | 223 ++
.../com/android/tools/rpclib/rpccore/RpcError.java | 91 +
.../android/tools/rpclib/rpccore/RpcException.java | 22 +
.../com/android/tools/rpclib/schema/AnyType.java | 62 +
.../com/android/tools/rpclib/schema/Array.java | 94 +
.../com/android/tools/rpclib/schema/Constant.java | 30 +
.../android/tools/rpclib/schema/ConstantSet.java | 117 +
.../com/android/tools/rpclib/schema/Dynamic.java | 150 +
.../com/android/tools/rpclib/schema/Entity.java | 140 +
.../com/android/tools/rpclib/schema/Factory.java | 23 +
.../com/android/tools/rpclib/schema/Field.java | 68 +
.../com/android/tools/rpclib/schema/Interface.java | 63 +
.../java/com/android/tools/rpclib/schema/Map.java | 101 +
.../com/android/tools/rpclib/schema/Message.java | 93 +
.../com/android/tools/rpclib/schema/Method.java | 117 +
.../com/android/tools/rpclib/schema/Pointer.java | 70 +
.../com/android/tools/rpclib/schema/Primitive.java | 152 +
.../com/android/tools/rpclib/schema/Slice.java | 99 +
.../com/android/tools/rpclib/schema/Struct.java | 78 +
.../java/com/android/tools/rpclib/schema/Type.java | 87 +
.../com/android/tools/rpclib/schema/TypeTag.java | 108 +
.../com/android/tools/rpclib/schema/Variant.java | 63 +
.../android/tools/rpclib/binary/DecoderTest.java | 232 ++
.../android/tools/rpclib/binary/EncoderTest.java | 259 ++
.../com/android/tools/rpclib/binary/Factory.java | 28 +
.../com/android/tools/rpclib/binary/Simple.java | 41 +
.../com/android/tools/rpclib/binary/TypeA.java | 86 +
.../com/android/tools/rpclib/binary/TypeB.java | 86 +
.../com/android/tools/rpclib/binary/TypeC.java | 89 +
{base/rule-api => rule-api}/.classpath | 0
{base/rule-api => rule-api}/.gitignore | 0
{base/rule-api => rule-api}/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../manifest-merger => rule-api}/NOTICE | 0
{base/rule-api => rule-api}/README.txt | 0
{base/rule-api => rule-api}/build.gradle | 0
{base/rule-api => rule-api}/rule-api.iml | 0
.../android/ide/common/api/AbstractViewRule.java | 0
.../com/android/ide/common/api/DrawingStyle.java | 0
.../com/android/ide/common/api/DropFeedback.java | 0
.../com/android/ide/common/api/IAttributeInfo.java | 0
.../android/ide/common/api/IClientRulesEngine.java | 0
.../java/com/android/ide/common/api/IColor.java | 0
.../com/android/ide/common/api/IDragElement.java | 0
.../android/ide/common/api/IFeedbackPainter.java | 0
.../java/com/android/ide/common/api/IGraphics.java | 0
.../com/android/ide/common/api/IMenuCallback.java | 0
.../java/com/android/ide/common/api/INode.java | 0
.../com/android/ide/common/api/INodeHandler.java | 0
.../com/android/ide/common/api/IValidator.java | 0
.../com/android/ide/common/api/IViewMetadata.java | 0
.../java/com/android/ide/common/api/IViewRule.java | 0
.../com/android/ide/common/api/InsertType.java | 0
.../com/android/ide/common/api/MarginType.java | 0
.../java/com/android/ide/common/api/Margins.java | 0
.../java/com/android/ide/common/api/Point.java | 0
.../main/java/com/android/ide/common/api/Rect.java | 0
.../com/android/ide/common/api/ResizePolicy.java | 0
.../com/android/ide/common/api/RuleAction.java | 0
.../java/com/android/ide/common/api/Segment.java | 0
.../com/android/ide/common/api/SegmentType.java | 0
{base/sdk-common => sdk-common}/.classpath | 0
{base/sdk-common => sdk-common}/.gitignore | 0
{base/sdk-common => sdk-common}/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../.settings/org.moreunit.prefs | 0
{base/ninepatch => sdk-common}/NOTICE | 0
sdk-common/build.gradle | 29 +
.../generate-locale-data/build.gradle | 0
.../generate-locale-data/generate-locale-data.iml | 0
.../android/ide/common/generate/locale/.gitignore | 0
.../generate/locale/LocaleTableGenerator.java | 0
sdk-common/sdk-common-base.iml | 26 +
sdk-common/sdk-common.iml | 27 +
.../com/android/ide/common/blame/MergingLog.java | 245 ++
.../ide/common/blame/MergingLogPersistUtil.java | 301 ++
.../ide/common/blame/MergingLogRewriter.java | 71 +
.../ide/common/blame/MessageJsonSerializer.java | 227 ++
.../android/ide/common/blame/MessageReceiver.java | 35 +
.../common/blame/ParsingProcessOutputHandler.java | 0
.../common/blame/SourceFileJsonTypeAdapter.java | 0
.../blame/SourceFilePositionJsonSerializer.java | 0
.../blame/SourcePositionJsonTypeAdapter.java | 0
.../android/ide/common/blame/parser/DexParser.java | 125 +
.../parser/JsonEncodedGradleMessageParser.java | 0
.../common/blame/parser/LegacyNdkOutputParser.java | 220 ++
.../blame/parser/ParsingFailedException.java | 0
.../blame/parser/PatternAwareOutputParser.java | 0
.../ide/common/blame/parser/ToolOutputParser.java | 111 +
.../common/blame/parser/aapt/AaptOutputParser.java | 0
.../parser/aapt/AbstractAaptOutputParser.java | 573 ++++
.../blame/parser/aapt/BadXmlBlockParser.java | 0
.../ide/common/blame/parser/aapt/Error1Parser.java | 0
.../ide/common/blame/parser/aapt/Error2Parser.java | 0
.../ide/common/blame/parser/aapt/Error3Parser.java | 0
.../ide/common/blame/parser/aapt/Error4Parser.java | 0
.../ide/common/blame/parser/aapt/Error5Parser.java | 0
.../ide/common/blame/parser/aapt/Error6Parser.java | 0
.../ide/common/blame/parser/aapt/Error7Parser.java | 0
.../ide/common/blame/parser/aapt/Error8Parser.java | 0
.../common/blame/parser/aapt/ReadOnlyDocument.java | 0
.../parser/aapt/SkippingHiddenFileParser.java | 0
.../blame/parser/aapt/SkippingWarning1Parser.java | 0
.../blame/parser/aapt/SkippingWarning2Parser.java | 0
.../common/blame/parser/aapt/Warning1Parser.java | 0
.../common/blame/parser/util/OutputLineReader.java | 88 +
.../ide/common/blame/parser/util/ParserUtil.java | 0
.../ide/common/build/SplitOutputMatcher.java | 0
.../android/ide/common/build/SplitSelectTool.java | 0
.../android/ide/common/caching/CreatingCache.java | 0
.../android/ide/common/internal/AaptCruncher.java | 0
.../ide/common/internal/CommandLineRunner.java | 0
.../ide/common/internal/ExecutorSingleton.java | 0
.../ide/common/internal/LoggedErrorException.java | 0
.../android/ide/common/internal/PngCruncher.java | 0
.../android/ide/common/internal/PngException.java | 0
.../ide/common/internal/WaitableExecutor.java | 198 ++
.../ide/common/packaging/PackagingUtils.java | 198 ++
.../common/process/BaseProcessOutputHandler.java | 78 +
.../common/process/CachedProcessOutputHandler.java | 0
.../ide/common/process/DefaultProcessExecutor.java | 0
.../ide/common/process/JavaProcessExecutor.java | 0
.../ide/common/process/JavaProcessInfo.java | 0
.../common/process/LoggedProcessOutputHandler.java | 0
.../ide/common/process/ProcessEnvBuilder.java | 0
.../ide/common/process/ProcessException.java | 0
.../ide/common/process/ProcessExecutor.java | 0
.../android/ide/common/process/ProcessInfo.java | 0
.../ide/common/process/ProcessInfoBuilder.java | 0
.../android/ide/common/process/ProcessOutput.java | 0
.../ide/common/process/ProcessOutputHandler.java | 0
.../android/ide/common/process/ProcessResult.java | 46 +
.../ide/common/process/ProcessResultImpl.java | 69 +
.../ide/common/rendering/HardwareConfigHelper.java | 341 ++
.../ide/common/rendering/LayoutLibrary.java | 0
.../ide/common/rendering/RenderParamsFlags.java | 0
.../common/rendering/RenderSecurityException.java | 0
.../common/rendering/RenderSecurityManager.java | 0
.../ide/common/rendering/StaticRenderSession.java | 0
.../common/rendering/legacy/ILegacyPullParser.java | 0
.../ide/common/repository/GradleCoordinate.java | 685 ++++
.../ide/common/repository/GradleVersion.java | 410 +++
.../repository/ResourceVisibilityLookup.java | 593 ++++
.../ide/common/repository/SdkMavenRepository.java | 247 ++
.../common/res2/AbstractResourceRepository.java | 547 +++
.../com/android/ide/common/res2/AssetFile.java | 0
.../com/android/ide/common/res2/AssetItem.java | 0
.../com/android/ide/common/res2/AssetMerger.java | 0
.../java/com/android/ide/common/res2/AssetSet.java | 0
.../ide/common/res2/DataBindingResourceItem.java | 0
.../ide/common/res2/DataBindingResourceType.java | 0
.../java/com/android/ide/common/res2/DataFile.java | 0
.../java/com/android/ide/common/res2/DataItem.java | 261 ++
.../java/com/android/ide/common/res2/DataMap.java | 0
.../com/android/ide/common/res2/DataMerger.java | 708 ++++
.../java/com/android/ide/common/res2/DataSet.java | 0
.../ide/common/res2/DuplicateDataException.java | 74 +
.../ide/common/res2/FileResourceNameValidator.java | 0
.../com/android/ide/common/res2/FileStatus.java | 0
.../com/android/ide/common/res2/FileValidity.java | 0
.../ide/common/res2/GeneratedResourceItem.java | 0
.../ide/common/res2/GeneratedResourceSet.java | 71 +
.../com/android/ide/common/res2/MergeConsumer.java | 93 +
.../com/android/ide/common/res2/MergeWriter.java | 0
.../android/ide/common/res2/MergedAssetWriter.java | 0
.../ide/common/res2/MergedResourceWriter.java | 434 +++
.../android/ide/common/res2/MergingException.java | 224 ++
.../ide/common/res2/NoOpResourcePreprocessor.java | 42 +
.../com/android/ide/common/res2/NodeUtils.java | 382 +++
.../com/android/ide/common/res2/ResourceFile.java | 122 +
.../com/android/ide/common/res2/ResourceItem.java | 0
.../android/ide/common/res2/ResourceMerger.java | 556 ++++
.../ide/common/res2/ResourcePreprocessor.java | 0
.../ide/common/res2/ResourceRepository.java | 0
.../com/android/ide/common/res2/ResourceSet.java | 568 ++++
.../com/android/ide/common/res2/SourceSet.java | 0
.../common/res2/ValueResourceNameValidator.java | 98 +
.../ide/common/res2/ValueResourceParser2.java | 0
.../android/ide/common/res2/ValueXmlHelper.java | 365 ++
.../common/resources/FrameworkResourceItem.java | 0
.../ide/common/resources/FrameworkResources.java | 0
.../common/resources/IdGeneratingResourceFile.java | 0
.../ide/common/resources/IdResourceParser.java | 0
.../ide/common/resources/InlineResourceItem.java | 0
.../ide/common/resources/IntArrayWrapper.java | 0
.../ide/common/resources/LocaleManager.java | 0
.../ide/common/resources/MultiResourceFile.java | 0
.../ide/common/resources/ResourceDeltaKind.java | 0
.../android/ide/common/resources/ResourceFile.java | 0
.../ide/common/resources/ResourceFolder.java | 0
.../android/ide/common/resources/ResourceItem.java | 0
.../ide/common/resources/ResourceItemResolver.java | 397 +++
.../ide/common/resources/ResourceRepository.java | 0
.../ide/common/resources/ResourceResolver.java | 946 ++++++
.../android/ide/common/resources/ResourceUrl.java | 0
.../ide/common/resources/ScanningContext.java | 0
.../ide/common/resources/SingleResourceFile.java | 0
.../common/resources/ValidatingResourceParser.java | 0
.../ide/common/resources/ValueResourceParser.java | 0
.../resources/configuration/Configurable.java | 0
.../configuration/CountryCodeQualifier.java | 0
.../resources/configuration/DensityQualifier.java | 0
.../configuration/DeviceConfigHelper.java | 0
.../configuration/EnumBasedResourceQualifier.java | 0
.../configuration/FolderConfiguration.java | 1211 +++++++
.../configuration/KeyboardStateQualifier.java | 0
.../configuration/LayoutDirectionQualifier.java | 0
.../resources/configuration/LocaleQualifier.java | 608 ++++
.../configuration/NavigationMethodQualifier.java | 0
.../configuration/NavigationStateQualifier.java | 0
.../configuration/NetworkCodeQualifier.java | 0
.../configuration/NightModeQualifier.java | 0
.../resources/configuration/ResourceQualifier.java | 0
.../configuration/ScreenDimensionQualifier.java | 0
.../configuration/ScreenHeightQualifier.java | 0
.../configuration/ScreenOrientationQualifier.java | 0
.../configuration/ScreenRatioQualifier.java | 0
.../configuration/ScreenRoundQualifier.java | 0
.../configuration/ScreenSizeQualifier.java | 104 +
.../configuration/ScreenWidthQualifier.java | 0
.../SmallestScreenWidthQualifier.java | 0
.../configuration/TextInputMethodQualifier.java | 0
.../configuration/TouchScreenQualifier.java | 0
.../resources/configuration/UiModeQualifier.java | 0
.../resources/configuration/VersionQualifier.java | 0
.../com/android/ide/common/sdk/LoadStatus.java | 0
.../ide/common/signing/CertificateInfo.java | 0
.../android/ide/common/signing/KeystoreHelper.java | 262 ++
.../ide/common/signing/KeytoolException.java | 0
.../com/android/ide/common/util/AssetUtil.java | 0
.../android/ide/common/util/ReferenceHolder.java | 49 +
.../ide/common/util/UrlClassLoaderUtil.java | 38 +
.../ide/common/vectordrawable/EllipseSolver.java | 287 ++
.../ide/common/vectordrawable/PathBuilder.java | 0
.../ide/common/vectordrawable/PathParser.java | 212 ++
.../ide/common/vectordrawable/Svg2Vector.java | 689 ++++
.../ide/common/vectordrawable/SvgGroupNode.java | 87 +
.../ide/common/vectordrawable/SvgLeafNode.java | 211 ++
.../android/ide/common/vectordrawable/SvgNode.java | 216 ++
.../android/ide/common/vectordrawable/SvgTree.java | 214 ++
.../ide/common/vectordrawable/VdElement.java | 39 +
.../android/ide/common/vectordrawable/VdGroup.java | 166 +
.../android/ide/common/vectordrawable/VdIcon.java | 132 +
.../ide/common/vectordrawable/VdNodeRender.java | 403 +++
.../ide/common/vectordrawable/VdOverrideInfo.java | 0
.../ide/common/vectordrawable/VdParser.java | 64 +
.../android/ide/common/vectordrawable/VdPath.java | 588 ++++
.../ide/common/vectordrawable/VdPreview.java | 318 ++
.../android/ide/common/vectordrawable/VdTree.java | 206 ++
.../ide/common/xml/AndroidManifestParser.java | 0
.../com/android/ide/common/xml/ManifestData.java | 0
.../ide/common/xml/XmlAttributeSortOrder.java | 0
.../ide/common/xml/XmlFormatPreferences.java | 0
.../com/android/ide/common/xml/XmlFormatStyle.java | 0
.../android/ide/common/xml/XmlPrettyPrinter.java | 0
.../sdk-common => sdk-common}/src/test/.classpath | 0
{base/sdk-common => sdk-common}/src/test/.project | 0
.../android/ide/common/blame/MergingLogTest.java | 116 +
.../common/blame/MessageJsonSerializerTest.java | 182 +
.../blame/ParsingProcessOutputHandlerTest.java | 162 +
.../blame/SourceFileJsonTypeAdapterTest.java | 0
.../SourceFilePositionJsonSerializerTest.java | 0
.../blame/SourcePositionJsonTypeAdapterTest.java | 0
.../common/blame/parser/AaptOutputParserTest.java | 0
.../ide/common/blame/parser/DexParserTest.java | 92 +
.../blame/parser/LegacyNdkOutputParserTest.java | 0
.../ide/common/build/SplitOutputMatcherTest.java | 0
.../ide/common/caching/CreatingCacheTest.java | 0
.../common/rendering/HardwareConfigHelperTest.java | 0
.../rendering/RenderSecurityManagerTest.java | 0
.../common/repository/GradleCoordinateTest.java | 346 ++
.../ide/common/repository/GradleVersionTest.java | 419 +++
.../repository/ResourceVisibilityLookupTest.java | 425 +++
.../common/repository/SdkMavenRepositoryTest.java | 167 +
.../android/ide/common/res2/AssetMergerTest.java | 415 +++
.../com/android/ide/common/res2/AssetSetTest.java | 0
.../com/android/ide/common/res2/BaseTestCase.java | 0
.../com/android/ide/common/res2/DataSetTest.java | 0
.../common/res2/FileResourceNameValidatorTest.java | 0
.../ide/common/res2/MergingExceptionTest.java | 152 +
.../com/android/ide/common/res2/NodeUtilsTest.java | 248 ++
.../res2/PartialFileResourceNameValidatorTest.java | 0
.../android/ide/common/res2/RecordingLogger.java | 0
.../android/ide/common/res2/ResourceFileTest.java | 0
.../ide/common/res2/ResourceMergerTest.java | 1692 ++++++++++
.../ide/common/res2/ResourceRepositoryTest.java | 755 +++++
.../ide/common/res2/ResourceRepositoryTest2.java | 408 +++
.../android/ide/common/res2/ResourceSetTest.java | 195 ++
.../res2/ValueResourceNameValidatorTest.java | 83 +
.../ide/common/res2/ValueResourceParser2Test.java | 0
.../ide/common/res2/ValueXmlHelperTest.java | 187 ++
.../ide/common/resources/LocaleManagerTest.java | 0
.../common/resources/ResourceItemResolverTest.java | 294 ++
.../common/resources/ResourceRepositoryTest.java | 0
.../ide/common/resources/ResourceResolverTest.java | 643 ++++
.../ide/common/resources/ResourceUrlTest.java | 0
.../common/resources/TestResourceRepository.java | 151 +
.../configuration/CountryCodeQualifierTest.java | 0
.../configuration/DensityQualifierTest.java | 0
.../configuration/DockModeQualifierTest.java | 0
.../configuration/FolderConfigurationTest.java | 508 +++
.../configuration/KeyboardStateQualifierTest.java | 0
.../configuration/LocaleQualifierTest.java | 343 ++
.../NavigationMethodQualifierTest.java | 0
.../configuration/NetworkCodeQualifierTest.java | 0
.../ScreenDimensionQualifierTest.java | 0
.../ScreenOrientationQualifierTest.java | 0
.../configuration/ScreenRoundQualifierTest.java | 0
.../configuration/ScreenSizeQualifierTest.java | 143 +
.../TextInputMethodQualifierTest.java | 0
.../configuration/TouchScreenQualifierTest.java | 0
.../ide/common/signing/KeyStoreHelperTest.java | 0
.../com/android/ide/common/util/GeneratorTest.java | 0
.../VectorDrawableGeneratorTest.java | 494 +++
.../ide/common/xml/AndroidManifestParserTest.java | 0
.../ide/common/xml/SupportsScreensTest.java | 0
.../ide/common/xml/XmlPrettyPrinterTest.java | 0
.../resources/testData/assets/baseMerge/merger.xml | 0
.../testData/assets/baseMerge/overlay/foo/icon.png | Bin
.../testData/assets/baseMerge/overlay/icon2.png | Bin
.../resources/testData/assets/baseSet/CVS/foo.txt | 0
.../resources/testData/assets/baseSet/foo/foo.dat | 0
.../resources/testData/assets/baseSet/foo/icon.png | Bin
.../resources/testData/assets/baseSet/main.xml | 0
.../resources/testData/assets/baseSet/values.xml | 0
.../testData/assets/dupSet/assets1}/icon.png | Bin
.../testData/assets/dupSet/assets2}/icon.png | Bin
.../basicFiles/assetOut/foo/overlay_added.png | Bin
.../basicFiles/assetOut/overlay_removed.png | Bin
.../incMergeData/basicFiles/assetOut/removed.png | Bin
.../incMergeData/basicFiles/assetOut/touched.png | Bin
.../incMergeData/basicFiles/assetOut/untouched.png | Bin
.../assets/incMergeData/basicFiles/main/added.png | Bin
.../basicFiles/main/foo/overlay_added.png | Bin
.../basicFiles/main/overlay_removed.png | Bin
.../incMergeData/basicFiles/main/touched.png | Bin
.../incMergeData/basicFiles/main/untouched.png | Bin
.../assets/incMergeData/basicFiles/merger.xml | 0
.../basicFiles/overlay/foo/overlay_added.png | Bin
.../testData/resources/baseMerge/merger.xml | 0
.../baseMerge/overlay/drawable-ldpi}/icon.png | Bin
.../resources/baseMerge/overlay/drawable/icon2.png | Bin
.../overlay/layout/alias_replaced_by_file.xml | 0
.../resources/baseMerge/overlay/layout/main.xml | 0
.../overlay/values-sw600dp-v13/values.xml | 0
.../resources/baseMerge/overlay/values/values.xml | 0
.../testData/resources/baseSet/CVS/foo.txt | 0
.../testData/resources/baseSet}/drawable/icon.png | Bin
.../resources/baseSet/drawable/patch.9.png | Bin
.../baseSet/layout/file_replaced_by_alias.xml | 0
.../testData/resources/baseSet/layout/main.xml | 0
.../testData/resources/baseSet/raw/foo.dat | 0
.../resources/baseSet/values-sw600dp/values.xml | 0
.../testData/resources/baseSet/values/empty.xml | 0
.../testData/resources/baseSet/values/values.xml | 0
.../resources/brokenSet/layout/activity_main.xml | 0
.../testData/resources/brokenSet/values/dimens.xml | Bin
.../resources/brokenSet2/values/values.xml | 0
.../resources/brokenSet3/values/values.xml | 0
.../resources/brokenSet4/values/values.xml | 0
.../resources/brokenSet5/layout/ActivityMain.xml | 0
.../resources/brokenSet6/values/dimens.xml | 0
.../resources/brokenSet7/values/dimens.xml | 0
.../brokenSetDrawableFileName/drawable/1icon.png | Bin
.../declareStyleable/base/values/values.xml | 0
.../declareStyleable/missing_mergeditem_merger.xml | 0
.../declareStyleable/overlay/values/values.xml | 0
.../resources/declareStyleable/removed_merger.xml | 0
.../declareStyleable/removed_other_merger.xml | 0
.../resources/declareStyleable/touched_merger.xml | 0
.../declareStyleable/touched_nodiff_merger.xml | 0
.../declareStyleable/unchanged_merger.xml | 0
.../resources/dupSet/res1}/drawable/icon.png | Bin
.../resources/dupSet/res2}/drawable/icon.png | Bin
.../resources/filterableSet/raw-v16/foo.txt | 1 +
.../resources/filterableSet/raw-v21/foo.txt | 1 +
.../testData/resources/filterableSet/raw/foo.txt | 1 +
.../basicFiles/main/drawable/new_overlay.png | Bin
.../basicFiles/main/drawable/removed_overlay.png | Bin
.../basicFiles/main/drawable/touched.png | Bin
.../basicFiles/main/drawable/untouched.png | Bin
.../resources/incMergeData/basicFiles/merger.xml | 0
.../incMergeData/basicFiles/new_overlay.png | Bin
.../overlay/drawable-hdpi/new_alternate.png | Bin
.../basicFiles/overlay/drawable/new_overlay.png | Bin
.../basicFiles/resOut/drawable-ldpi/removed.png | Bin
.../basicFiles/resOut/drawable/new_overlay.png | Bin
.../basicFiles/resOut/drawable/removed_overlay.png | Bin
.../basicFiles/resOut/drawable/touched.png | Bin
.../basicFiles/resOut/drawable/untouched.png | Bin
.../basicValues/main/values/values.xml | 0
.../resources/incMergeData/basicValues/merger.xml | 0
.../basicValues/overlay/values-fr/values.xml | 0
.../basicValues/overlay/values/values.xml | 0
.../basicValues/resOut/values-en/values.xml | 0
.../basicValues/resOut/values/values.xml | 0
.../basicValues2/main/values/values.xml | 0
.../resources/incMergeData/basicValues2/merger.xml | 0
.../basicValues2/overlay/keepfolder.txt | 0
.../basicValues2/resOut/values/values.xml | 0
.../main/layout/alias_replaced_by_file.xml | 0
.../filesVsValues/main/layout/main.xml | 0
.../filesVsValues/main/values/values.xml | 0
.../incMergeData/filesVsValues/merger.xml | 0
.../resOut/layout/file_replaced_by_alias.xml | 0
.../filesVsValues/resOut/layout/main.xml | 0
.../filesVsValues/resOut/values/values.xml | 0
.../resources/incMergeData/oldMerge/merger.xml | 0
.../testData/resources/removedFile/merger.xml | 0
.../resources/removedFile/out/drawable/icon.png | 0
.../resources/removedFile/out/drawable/removed.png | 0
.../resources/removedFile/res/drawable/icon.png | 0
.../resources/stringWhiteSpaces/values/strings.xml | 0
.../vectordrawable/ic_contains_ignorable_error.png | Bin 0 -> 1067 bytes
.../vectordrawable/ic_contains_ignorable_error.svg | 34 +
.../vectordrawable/ic_content_cut_24px.png | Bin 0 -> 684 bytes
.../vectordrawable/ic_content_cut_24px.svg | 0
.../vectordrawable/ic_empty_attributes.png | Bin 0 -> 415 bytes
.../vectordrawable/ic_empty_attributes.svg | 88 +
.../testData/vectordrawable/ic_input_24px.png | Bin 0 -> 349 bytes
.../testData/vectordrawable/ic_input_24px.svg | 0
.../testData/vectordrawable/ic_live_help_24px.png | Bin 0 -> 595 bytes
.../testData/vectordrawable/ic_live_help_24px.svg | 0
.../vectordrawable/ic_local_library_24px.png | Bin 0 -> 606 bytes
.../vectordrawable/ic_local_library_24px.svg | 0
.../vectordrawable/ic_local_phone_24px.png | Bin 0 -> 594 bytes
.../vectordrawable/ic_local_phone_24px.svg | 0
.../testData/vectordrawable/ic_mic_off_24px.png | Bin 0 -> 693 bytes
.../testData/vectordrawable/ic_mic_off_24px.svg | 0
.../testData/vectordrawable/ic_plus_sign.png | Bin 0 -> 257 bytes
.../testData/vectordrawable/ic_plus_sign.svg | 0
.../vectordrawable/ic_polyline_strokewidth.png | Bin 0 -> 368 bytes
.../vectordrawable/ic_polyline_strokewidth.svg | 9 +
.../testData/vectordrawable/ic_shapes.png | Bin 0 -> 1104 bytes
.../testData/vectordrawable/ic_shapes.svg | 0
.../vectordrawable/ic_simple_group_info.png | Bin 0 -> 415 bytes
.../vectordrawable/ic_simple_group_info.svg | 12 +
.../testData/vectordrawable/ic_size_opacity.png | Bin 0 -> 774 bytes
.../testData/vectordrawable/ic_size_opacity.xml | 23 +
.../vectordrawable/ic_strokewidth_transform.png | Bin 0 -> 245 bytes
.../vectordrawable/ic_strokewidth_transform.svg | 9 +
.../testData/vectordrawable/ic_temp_high.png | Bin 0 -> 564 bytes
.../testData/vectordrawable/ic_temp_high.svg | 0
.../testData/vectordrawable/test_arcto_1.png | Bin 0 -> 464 bytes
.../testData/vectordrawable/test_arcto_1.svg | 9 +
.../testData/vectordrawable/test_color_formats.png | Bin 0 -> 159 bytes
.../testData/vectordrawable/test_color_formats.svg | 12 +
.../vectordrawable/test_control_points_01.png | Bin 0 -> 320 bytes
.../vectordrawable/test_control_points_01.svg | 0
.../vectordrawable/test_control_points_02.png | Bin 0 -> 284 bytes
.../vectordrawable/test_control_points_02.svg | 0
.../vectordrawable/test_control_points_03.png | Bin 0 -> 509 bytes
.../vectordrawable/test_control_points_03.svg | 0
.../testData/vectordrawable/test_ellipse.png | Bin 0 -> 680 bytes
.../testData/vectordrawable/test_ellipse.svg | 5 +
.../vectordrawable/test_fill_stroke_alpha.png | Bin 0 -> 1436 bytes
.../vectordrawable/test_fill_stroke_alpha.xml | 26 +
.../testData/vectordrawable/test_lineto_moveto.png | Bin 0 -> 135 bytes
.../testData/vectordrawable/test_lineto_moveto.svg | 10 +
.../vectordrawable/test_lineto_moveto2.png | Bin 0 -> 135 bytes
.../vectordrawable/test_lineto_moveto2.svg | 10 +
.../vectordrawable/test_lineto_moveto_viewbox1.png | Bin 0 -> 171 bytes
.../vectordrawable/test_lineto_moveto_viewbox1.svg | 13 +
.../vectordrawable/test_lineto_moveto_viewbox2.png | Bin 0 -> 195 bytes
.../vectordrawable/test_lineto_moveto_viewbox2.svg | 13 +
.../vectordrawable/test_lineto_moveto_viewbox3.png | Bin 0 -> 170 bytes
.../vectordrawable/test_lineto_moveto_viewbox3.svg | 13 +
.../vectordrawable/test_lineto_moveto_viewbox4.png | Bin 0 -> 148 bytes
.../vectordrawable/test_lineto_moveto_viewbox4.svg | 13 +
.../vectordrawable/test_lineto_moveto_viewbox5.png | Bin 0 -> 133 bytes
.../vectordrawable/test_lineto_moveto_viewbox5.svg | 13 +
.../vectordrawable/test_move_after_close.png | Bin 0 -> 296 bytes
.../vectordrawable/test_move_after_close.svg | 5 +
.../test_move_after_close_transform.png | Bin 0 -> 560 bytes
.../test_move_after_close_transform.svg | 4 +
.../vectordrawable/test_transform_arc_complex1.png | Bin 0 -> 839 bytes
.../vectordrawable/test_transform_arc_complex1.svg | 13 +
.../vectordrawable/test_transform_arc_complex2.png | Bin 0 -> 922 bytes
.../vectordrawable/test_transform_arc_complex2.svg | 13 +
.../test_transform_arc_rotate_scale_translate.png | Bin 0 -> 532 bytes
.../test_transform_arc_rotate_scale_translate.svg | 13 +
.../vectordrawable/test_transform_arc_scale.png | Bin 0 -> 487 bytes
.../vectordrawable/test_transform_arc_scale.svg | 13 +
.../test_transform_arc_scale_rotate.png | Bin 0 -> 507 bytes
.../test_transform_arc_scale_rotate.svg | 13 +
.../vectordrawable/test_transform_arc_skewx.png | Bin 0 -> 782 bytes
.../vectordrawable/test_transform_arc_skewx.svg | 13 +
.../vectordrawable/test_transform_arc_skewy.png | Bin 0 -> 747 bytes
.../vectordrawable/test_transform_arc_skewy.svg | 13 +
.../test_transform_big_arc_complex.png | Bin 0 -> 816 bytes
.../test_transform_big_arc_complex.svg | 13 +
.../test_transform_big_arc_complex_viewbox.png | Bin 0 -> 737 bytes
.../test_transform_big_arc_complex_viewbox.svg | 13 +
.../test_transform_big_arc_translate_scale.png | Bin 0 -> 518 bytes
.../test_transform_big_arc_translate_scale.svg | 13 +
.../vectordrawable/test_transform_c_q_no_move.png | Bin 0 -> 565 bytes
.../vectordrawable/test_transform_c_q_no_move.svg | 13 +
.../test_transform_circle_rotate.png | Bin 0 -> 685 bytes
.../test_transform_circle_rotate.svg | 13 +
.../vectordrawable/test_transform_circle_scale.png | Bin 0 -> 575 bytes
.../vectordrawable/test_transform_circle_scale.svg | 13 +
.../test_transform_ellipse_complex.png | Bin 0 -> 592 bytes
.../test_transform_ellipse_complex.svg | 7 +
...st_transform_ellipse_rotate_scale_translate.png | Bin 0 -> 746 bytes
...st_transform_ellipse_rotate_scale_translate.svg | 7 +
.../vectordrawable/test_transform_group_1.png | Bin 0 -> 561 bytes
.../vectordrawable/test_transform_group_1.svg | 17 +
.../vectordrawable/test_transform_group_2.png | Bin 0 -> 796 bytes
.../vectordrawable/test_transform_group_2.svg | 17 +
.../vectordrawable/test_transform_group_3.png | Bin 0 -> 633 bytes
.../vectordrawable/test_transform_group_3.svg | 16 +
.../vectordrawable/test_transform_group_4.png | Bin 0 -> 558 bytes
.../vectordrawable/test_transform_group_4.svg | 18 +
.../test_transform_h_v_a_c_complex.png | Bin 0 -> 1068 bytes
.../test_transform_h_v_a_c_complex.svg | 15 +
.../test_transform_h_v_a_complex.png | Bin 0 -> 682 bytes
.../test_transform_h_v_a_complex.svg | 13 +
.../vectordrawable/test_transform_h_v_c_q.png | Bin 0 -> 664 bytes
.../vectordrawable/test_transform_h_v_c_q.svg | 13 +
.../test_transform_h_v_c_q_complex.png | Bin 0 -> 491 bytes
.../test_transform_h_v_c_q_complex.svg | 13 +
.../test_transform_h_v_loop_basic.png | Bin 0 -> 227 bytes
.../test_transform_h_v_loop_basic.svg | 13 +
.../test_transform_h_v_loop_complex.png | Bin 0 -> 421 bytes
.../test_transform_h_v_loop_complex.svg | 13 +
.../test_transform_h_v_loop_matrix.png | Bin 0 -> 465 bytes
.../test_transform_h_v_loop_matrix.svg | 13 +
.../test_transform_h_v_loop_translate.png | Bin 0 -> 215 bytes
.../test_transform_h_v_loop_translate.svg | 13 +
.../test_transform_h_v_s_t_complex.png | Bin 0 -> 692 bytes
.../test_transform_h_v_s_t_complex.svg | 13 +
.../test_transform_h_v_s_t_complex2.png | Bin 0 -> 911 bytes
.../test_transform_h_v_s_t_complex2.svg | 14 +
.../vectordrawable/test_transform_multiple_1.png | Bin 0 -> 561 bytes
.../vectordrawable/test_transform_multiple_1.svg | 13 +
.../vectordrawable/test_transform_multiple_2.png | Bin 0 -> 796 bytes
.../vectordrawable/test_transform_multiple_2.svg | 13 +
.../vectordrawable/test_transform_multiple_3.png | Bin 0 -> 633 bytes
.../vectordrawable/test_transform_multiple_3.svg | 13 +
.../vectordrawable/test_transform_multiple_4.png | Bin 0 -> 558 bytes
.../vectordrawable/test_transform_multiple_4.svg | 13 +
.../vectordrawable/test_transform_rect_matrix.png | Bin 0 -> 621 bytes
.../vectordrawable/test_transform_rect_matrix.svg | 13 +
.../vectordrawable/test_transform_rect_rotate.png | Bin 0 -> 561 bytes
.../vectordrawable/test_transform_rect_rotate.svg | 13 +
.../vectordrawable/test_transform_rect_scale.png | Bin 0 -> 237 bytes
.../vectordrawable/test_transform_rect_scale.svg | 13 +
.../vectordrawable/test_transform_rect_skewx.png | Bin 0 -> 523 bytes
.../vectordrawable/test_transform_rect_skewx.svg | 13 +
.../vectordrawable/test_transform_rect_skewy.png | Bin 0 -> 522 bytes
.../vectordrawable/test_transform_rect_skewy.svg | 13 +
.../test_transform_rect_translate.png | Bin 0 -> 241 bytes
.../test_transform_rect_translate.svg | 13 +
.../test_transform_round_rect_matrix.png | Bin 0 -> 749 bytes
.../test_transform_round_rect_matrix.svg | 13 +
.../vectordrawable/test_xml_color_formats.png | Bin 0 -> 162 bytes
.../vectordrawable/test_xml_color_formats.xml | 24 +
.../vectordrawable/test_xml_render_order_1.png | Bin 0 -> 230 bytes
.../vectordrawable/test_xml_render_order_1.xml | 89 +
.../vectordrawable/test_xml_render_order_2.png | Bin 0 -> 232 bytes
.../vectordrawable/test_xml_render_order_2.xml | 89 +
.../vectordrawable/test_xml_repeated_a_1.png | Bin 0 -> 2888 bytes
.../vectordrawable/test_xml_repeated_a_1.xml | 43 +
.../vectordrawable/test_xml_repeated_a_2.png | Bin 0 -> 3223 bytes
.../vectordrawable/test_xml_repeated_a_2.xml | 45 +
.../vectordrawable/test_xml_repeated_cq.png | Bin 0 -> 1956 bytes
.../vectordrawable/test_xml_repeated_cq.xml | 42 +
.../vectordrawable/test_xml_repeated_st.png | Bin 0 -> 2590 bytes
.../vectordrawable/test_xml_repeated_st.xml | 42 +
.../vectordrawable/test_xml_scale_stroke_1.png | Bin 0 -> 556 bytes
.../vectordrawable/test_xml_scale_stroke_1.xml | 52 +
.../vectordrawable/test_xml_scale_stroke_2.png | Bin 0 -> 503 bytes
.../vectordrawable/test_xml_scale_stroke_2.xml | 48 +
.../testData/vectordrawable/test_xml_stroke_1.png | Bin 0 -> 432 bytes
.../testData/vectordrawable/test_xml_stroke_1.xml | 46 +
.../testData/vectordrawable/test_xml_stroke_2.png | Bin 0 -> 444 bytes
.../testData/vectordrawable/test_xml_stroke_2.xml | 46 +
.../testData/vectordrawable/test_xml_stroke_3.png | Bin 0 -> 425 bytes
.../testData/vectordrawable/test_xml_stroke_3.xml | 46 +
.../vectordrawable/test_xml_transformation_1.png | Bin 0 -> 612 bytes
.../vectordrawable/test_xml_transformation_1.xml | 39 +
.../vectordrawable/test_xml_transformation_2.png | Bin 0 -> 273 bytes
.../vectordrawable/test_xml_transformation_2.xml | 48 +
.../vectordrawable/test_xml_transformation_3.png | Bin 0 -> 203 bytes
.../vectordrawable/test_xml_transformation_3.xml | 48 +
.../vectordrawable/test_xml_transformation_4.png | Bin 0 -> 734 bytes
.../vectordrawable/test_xml_transformation_4.xml | 68 +
.../vectordrawable/test_xml_transformation_5.png | Bin 0 -> 960 bytes
.../vectordrawable/test_xml_transformation_5.xml | 81 +
.../vectordrawable/test_xml_transformation_6.png | Bin 0 -> 844 bytes
.../vectordrawable/test_xml_transformation_6.xml | 85 +
{base/sdklib => sdklib}/.classpath | 0
{base/sdklib => sdklib}/.gitignore | 0
{base/sdklib => sdklib}/.project | 0
.../.settings/org.eclipse.core.resources.prefs | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
.../.settings/org.eclipse.jdt.ui.prefs | 0
.../builder => sdklib}/MODULE_LICENSE_APACHE2 | 0
{base/sdk-common => sdklib}/NOTICE | 0
sdklib/build.gradle | 70 +
sdklib/sdklib-base.iml | 22 +
sdklib/sdklib.iml | 22 +
.../java/com/android/sdklib/AndroidTargetHash.java | 188 ++
.../com/android/sdklib/AndroidVersionHelper.java | 82 +
.../java/com/android/sdklib/BuildToolInfo.java | 391 +++
.../java/com/android/sdklib/FileOpFileWrapper.java | 161 +
.../java/com/android/sdklib/IAndroidTarget.java | 295 ++
.../main/java/com/android/sdklib/ISystemImage.java | 104 +
.../java/com/android/sdklib/LayoutlibVersion.java | 53 +
.../java/com/android/sdklib/SdkVersionInfo.java | 359 ++
.../java/com/android/sdklib/build/ApkBuilder.java | 0
.../com/android/sdklib/build/ApkBuilderMain.java | 0
.../android/sdklib/build/ApkCreationException.java | 0
.../com/android/sdklib/build/DependencyFile.java | 0
.../sdklib/build/DuplicateFileException.java | 0
.../com/android/sdklib/build/FileGatherer.java | 0
.../com/android/sdklib/build/IArchiveBuilder.java | 0
.../com/android/sdklib/build/JarListSanitizer.java | 0
.../sdklib/build/ManualRenderScriptChecker.java | 0
.../android/sdklib/build/RenderScriptChecker.java | 0
.../sdklib/build/RenderScriptProcessor.java | 0
.../android/sdklib/build/SealedApkException.java | 0
.../com/android/sdklib/build/SourceSearcher.java | 0
.../main/java/com/android/sdklib/devices/Abi.java | 146 +
.../android/sdklib/devices/BluetoothProfile.java | 0
.../com/android/sdklib/devices/ButtonType.java | 0
.../java/com/android/sdklib/devices/Camera.java | 0
.../com/android/sdklib/devices/CameraLocation.java | 0
.../java/com/android/sdklib/devices/Device.java | 0
.../com/android/sdklib/devices/DeviceManager.java | 718 ++++
.../com/android/sdklib/devices/DeviceParser.java | 0
.../com/android/sdklib/devices/DeviceWriter.java | 0
.../java/com/android/sdklib/devices/Hardware.java | 0
.../main/java/com/android/sdklib/devices/Meta.java | 0
.../com/android/sdklib/devices/Multitouch.java | 0
.../java/com/android/sdklib/devices/Network.java | 0
.../java/com/android/sdklib/devices/PowerType.java | 0
.../java/com/android/sdklib/devices/Screen.java | 0
.../com/android/sdklib/devices/ScreenType.java | 0
.../java/com/android/sdklib/devices/Sensor.java | 0
.../java/com/android/sdklib/devices/Software.java | 0
.../java/com/android/sdklib/devices/State.java | 0
.../java/com/android/sdklib/devices/Storage.java | 157 +
.../java/com/android/sdklib/devices/devices.xml | 0
.../main/java/com/android/sdklib/devices/nexus.xml | 1296 ++++++++
.../main/java/com/android/sdklib/devices/tv.xml | 0
.../main/java/com/android/sdklib/devices/wear.xml | 0
.../com/android/sdklib/internal/avd/AvdInfo.java | 403 +++
.../android/sdklib/internal/avd/AvdManager.java | 1990 +++++++++++
.../com/android/sdklib/internal/avd/GpuMode.java | 65 +
.../sdklib/internal/avd/HardwareProperties.java | 0
.../sdklib/internal/build/BuildConfig.template | 0
.../internal/build/BuildConfigGenerator.java | 0
.../sdklib/internal/build/DebugKeyProvider.java | 0
.../sdklib/internal/build/KeystoreHelper.java | 0
.../sdklib/internal/build/SignedJarBuilder.java | 0
.../sdklib/internal/build/SymbolLoader.java | 0
.../sdklib/internal/build/SymbolWriter.java | 0
.../sdklib/internal/project/IPropertySource.java | 0
.../sdklib/internal/project/ProjectProperties.java | 593 ++++
.../project/ProjectPropertiesWorkingCopy.java | 281 ++
.../repository/CanceledByUserException.java | 0
.../sdklib/internal/repository/DownloadCache.java | 848 +++++
.../sdklib/internal/repository/ITaskMonitor.java | 0
.../sdklib/internal/repository/UrlOpener.java | 0
.../internal/repository/UserCredentials.java | 0
.../sdklib/repository/AddonManifestIniProps.java | 0
.../com/android/sdklib/repository/PkgProps.java | 97 +
.../java/com/android/sdklib/repository/README.txt | 0
.../com/android/sdklib/repository/RepoXsdUtil.java | 0
.../sdklib/repository/descriptors/IPkgDesc.java | 187 ++
.../repository/descriptors/IPkgDescExtra.java | 52 +
.../sdklib/repository/descriptors/PkgDesc.java | 1168 +++++++
.../repository/descriptors/PkgDescExtra.java | 175 +
.../sdklib/repository/descriptors/PkgType.java | 209 ++
.../sdklib/repository/local/LocalAddonPkgInfo.java | 86 +
.../repository/local/LocalAddonSysImgPkgInfo.java | 83 +
.../repository/local/LocalBuildToolPkgInfo.java | 57 +
.../sdklib/repository/local/LocalDirInfo.java | 274 ++
.../sdklib/repository/local/LocalDocPkgInfo.java | 47 +
.../sdklib/repository/local/LocalExtraPkgInfo.java | 121 +
.../sdklib/repository/local/LocalLLDBPkgInfo.java | 59 +
.../sdklib/repository/local/LocalNdkPkgInfo.java | 49 +
.../sdklib/repository/local/LocalPkgInfo.java | 190 ++
.../repository/local/LocalPlatformPkgInfo.java | 162 +
.../repository/local/LocalPlatformToolPkgInfo.java | 45 +
.../repository/local/LocalSamplePkgInfo.java | 53 +
.../android/sdklib/repository/local/LocalSdk.java | 1186 +++++++
.../repository/local/LocalSourcePkgInfo.java | 52 +
.../repository/local/LocalSysImgPkgInfo.java | 155 +
.../sdklib/repository/local/LocalToolPkgInfo.java | 46 +
.../repository/local/PackageParserUtils.java | 117 +
.../com/android/sdklib/repository/sdk-addon-01.xsd | 0
.../com/android/sdklib/repository/sdk-addon-02.xsd | 0
.../com/android/sdklib/repository/sdk-addon-03.xsd | 0
.../com/android/sdklib/repository/sdk-addon-04.xsd | 0
.../com/android/sdklib/repository/sdk-addon-05.xsd | 0
.../com/android/sdklib/repository/sdk-addon-06.xsd | 0
.../com/android/sdklib/repository/sdk-addon-07.xsd | 0
.../sdklib/repository/sdk-addons-list-1.xsd | 0
.../sdklib/repository/sdk-addons-list-2.xsd | 0
.../sdklib/repository/sdk-repository-01.xsd | 0
.../sdklib/repository/sdk-repository-02.xsd | 0
.../sdklib/repository/sdk-repository-03.xsd | 0
.../sdklib/repository/sdk-repository-04.xsd | 0
.../sdklib/repository/sdk-repository-05.xsd | 0
.../sdklib/repository/sdk-repository-06.xsd | 0
.../sdklib/repository/sdk-repository-07.xsd | 0
.../sdklib/repository/sdk-repository-08.xsd | 0
.../sdklib/repository/sdk-repository-09.xsd | 0
.../sdklib/repository/sdk-repository-10.xsd | 0
.../sdklib/repository/sdk-repository-11.xsd | 0
.../sdklib/repository/sdk-repository-12.xsd | 709 ++++
.../com/android/sdklib/repository/sdk-stats-1.xsd | 0
.../android/sdklib/repository/sdk-sys-img-01.xsd | 0
.../android/sdklib/repository/sdk-sys-img-02.xsd | 0
.../android/sdklib/repository/sdk-sys-img-03.xsd | 0
.../sdklib/repositoryv2/AndroidSdkHandler.java | 598 ++++
.../com/android/sdklib/repositoryv2/IdDisplay.java | 116 +
.../sdklib/repositoryv2/LegacyDownloader.java | 86 +
.../sdklib/repositoryv2/LegacyLocalRepoLoader.java | 253 ++
.../sdklib/repositoryv2/LegacyRepoUtils.java | 354 ++
.../sdklib/repositoryv2/LegacyTaskMonitor.java | 121 +
.../LoggerProgressIndicatorWrapper.java | 73 +
.../sdklib/repositoryv2/MavenInstaller.java | 404 +++
.../generated/addon/v1/AddonDetailsType.java | 179 +
.../generated/addon/v1/ExtraDetailsType.java | 88 +
.../generated/addon/v1/LibrariesType.java | 93 +
.../repositoryv2/generated/addon/v1/MavenType.java | 84 +
.../generated/addon/v1/ObjectFactory.java | 91 +
.../generated/addon/v1/package-info.java | 8 +
.../generated/common/v1/ApiDetailsType.java | 98 +
.../generated/common/v1/IdDisplayType.java | 117 +
.../generated/common/v1/LibraryType.java | 167 +
.../generated/common/v1/ObjectFactory.java | 56 +
.../generated/common/v1/package-info.java | 8 +
.../generated/repository/v1/LayoutlibType.java | 68 +
.../generated/repository/v1/ObjectFactory.java | 83 +
.../repository/v1/PlatformDetailsType.java | 87 +
.../generated/repository/v1/SourceDetailsType.java | 50 +
.../generated/repository/v1/package-info.java | 8 +
.../generated/sysimg/v1/ObjectFactory.java | 67 +
.../generated/sysimg/v1/SysImgDetailsType.java | 154 +
.../generated/sysimg/v1/package-info.java | 8 +
.../sdklib/repositoryv2/meta/AddonFactory.java | 35 +
.../sdklib/repositoryv2/meta/DetailsTypes.java | 319 ++
.../android/sdklib/repositoryv2/meta/Library.java | 110 +
.../sdklib/repositoryv2/meta/RepoFactory.java | 46 +
.../sdklib/repositoryv2/meta/SdkCommonFactory.java | 61 +
.../sdklib/repositoryv2/meta/SysImgFactory.java | 30 +
.../android/sdklib/repositoryv2/sdk-addon-01.xsd | 132 +
.../android/sdklib/repositoryv2/sdk-common-01.xsd | 123 +
.../sdklib/repositoryv2/sdk-common-custom.xjb | 34 +
.../com/android/sdklib/repositoryv2/sdk-common.xjb | 26 +
.../sdklib/repositoryv2/sdk-repository-01.xsd | 113 +
.../android/sdklib/repositoryv2/sdk-sys-img-01.xsd | 98 +
.../repositoryv2/sources/RemoteSiteType.java | 34 +
.../sources/generated/v1/AddonSiteType.java | 112 +
.../sources/generated/v1/AddonsListType.java | 92 +
.../sources/generated/v1/ObjectFactory.java | 71 +
.../sources/generated/v1/package-info.java | 8 +
.../sources/generated/v2/AddonSiteType.java | 112 +
.../sources/generated/v2/AddonsListType.java | 93 +
.../sources/generated/v2/ObjectFactory.java | 79 +
.../sources/generated/v2/SysImgSiteType.java | 112 +
.../sources/generated/v2/package-info.java | 8 +
.../sources/generated/v3/AddonSiteType.java | 50 +
.../sources/generated/v3/ObjectFactory.java | 71 +
.../sources/generated/v3/SysImgSiteType.java | 50 +
.../sources/generated/v3/package-info.java | 8 +
.../repositoryv2/sources/sdk-sites-list-1.xsd | 93 +
.../repositoryv2/sources/sdk-sites-list-2.xsd | 127 +
.../repositoryv2/sources/sdk-sites-list-3.xsd | 81 +
.../sdklib/repositoryv2/targets/AddonTarget.java | 344 ++
.../repositoryv2/targets/AndroidTargetManager.java | 160 +
.../repositoryv2/targets/OptionalLibraryImpl.java | 101 +
.../repositoryv2/targets/PlatformTarget.java | 434 +++
.../sdklib/repositoryv2/targets/SystemImage.java | 187 ++
.../repositoryv2/targets/SystemImageManager.java | 235 ++
.../com/android/sdklib/util/CommandLineParser.java | 991 ++++++
.../java/com/android/sdklib/util/FormatUtils.java | 0
.../java/com/android/sdklib/util/LineUtil.java | 0
.../android/sdklib/AndroidLocationTestCase.java | 0
.../com/android/sdklib/AndroidTargetHashTest.java | 89 +
.../com/android/sdklib/AndroidVersionTest.java | 0
.../java/com/android/sdklib/BuildToolInfoTest.java | 65 +
.../com/android/sdklib/SdkManagerTestCase.java | 536 +++
.../com/android/sdklib/SdkVersionInfoTest.java | 0
.../android/sdklib/devices/DeviceManagerTest.java | 382 +++
.../android/sdklib/devices/DeviceParserTest.java | 0
.../android/sdklib/devices/DeviceWriterTest.java | 0
.../internal/androidTarget/MockAddonTarget.java | 192 ++
.../internal/androidTarget/MockPlatformTarget.java | 193 ++
.../internal/build/DebugKeyProviderTest.java | 0
.../sdklib/internal/build/SymbolLoaderTest.java | 0
.../sdklib/internal/build/SymbolWriterTest.java | 0
.../internal/repository/DownloadCacheTest.java | 352 ++
.../sdklib/internal/repository/MockMonitor.java | 0
.../test/java/com/android/sdklib/mock/MockLog.java | 0
.../sdklib/repository/descriptors/PkgDescTest.java | 882 +++++
.../sdklib/repository/descriptors/PkgTypeTest.java | 157 +
.../sdklib/repository/local/LocalDirInfoTest.java | 165 +
.../sdklib/repository/local/LocalSdkTest.java | 1128 +++++++
.../repositoryv2/AddonListSourceProviderTest.java | 107 +
.../sdklib/repositoryv2/LegacyLocalRepoTest.java | 207 ++
.../sdklib/repositoryv2/MavenInstallerTest.java | 315 ++
.../android/sdklib/repositoryv2/UnmarshalTest.java | 189 ++
.../targets/AndroidTargetManagerTest.java | 586 ++++
.../targets/SystemImageManagerTest.java | 254 ++
.../testdata/addons_list_sample_1.xml | 0
.../testdata/addons_list_sample_2.xml | 0
.../repositoryv2/testdata/addons_list_sample_3.xml | 54 +
.../repositoryv2/testdata/remote_maven_repo.xml | 44 +
.../sdklib/repositoryv2/testdata/repositories.xml | 9 +
.../repositoryv2/testdata/repository2_sample_1.xml | 121 +
.../testdata/AndroidManifest-activityalias.xml | 0
.../testdata/AndroidManifest-instrumentation.xml | 0
.../sdklib/testdata/AndroidManifest-testapp.xml | 0
.../sdklib/testdata/AndroidManifest-testapp2.xml | 0
.../android/sdklib/testdata/addon2_sample_1.xml | 171 +
.../com/android/sdklib/testdata/addon_sample_1.xml | 0
.../com/android/sdklib/testdata/addon_sample_2.xml | 0
.../com/android/sdklib/testdata/addon_sample_3.xml | 0
.../com/android/sdklib/testdata/addon_sample_4.xml | 0
.../com/android/sdklib/testdata/addon_sample_5.xml | 0
.../com/android/sdklib/testdata/addon_sample_6.xml | 0
.../com/android/sdklib/testdata/addon_sample_7.xml | 0
.../sdklib/testdata/addons_list_sample_1.xml | 0
.../sdklib/testdata/addons_list_sample_2.xml | 0
.../com/android/sdklib/testdata/repositories.xml | 9 +
.../sdklib/testdata/repository_sample_01.xml | 0
.../sdklib/testdata/repository_sample_02.xml | 0
.../sdklib/testdata/repository_sample_03.xml | 0
.../sdklib/testdata/repository_sample_04.xml | 0
.../sdklib/testdata/repository_sample_05.xml | 0
.../sdklib/testdata/repository_sample_06.xml | 0
.../sdklib/testdata/repository_sample_07.xml | 0
.../sdklib/testdata/repository_sample_08.xml | 0
.../sdklib/testdata/repository_sample_09.xml | 0
.../sdklib/testdata/repository_sample_10.xml | 0
.../sdklib/testdata/repository_sample_12-test.xml | 33 +
.../com/android/sdklib/testdata/stats_sample_1.xml | 0
.../android/sdklib/testdata/sys_img_sample_1.xml | 0
.../android/sdklib/testdata/sys_img_sample_2.xml | 0
.../android/sdklib/testdata/sys_img_sample_3.xml | 0
.../sdklib/testdata/www.w3.org/2001/XMLSchema.dtd | 0
.../sdklib/testdata/www.w3.org/2001/XMLSchema.xsd | 0
.../sdklib/testdata/www.w3.org/2001/datatypes.dtd | 0
.../sdklib/testdata/www.w3.org/2001/xml.xsd | 0
.../java/com/android/sdklib/util/BSPatchTest.java | 0
.../android/sdklib/util/CommandLineParserTest.java | 0
.../java/com/android/sdklib/util/LineUtilTest.java | 0
{base/sdklib => sdklib}/test.gradle | 0
settings.gradle | 133 -
{base/templates => templates}/NOTICE | 0
.../AlwaysOnWearActivity/globals.xml.ftl | 4 +
.../activities/AlwaysOnWearActivity/recipe.xml.ftl | 27 +
.../root/AndroidManifest.xml.ftl | 22 +
.../root/AndroidManifestPermissions.xml | 5 +
.../AlwaysOnWearActivity/root/build.gradle.ftl | 0
.../root/res/layout/blank_activity.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/src/app_package/BlankActivity.java.ftl | 0
.../activities/AlwaysOnWearActivity/template.xml | 54 +
.../AlwaysOnWearActivity/template_thumb.png | Bin 0 -> 13173 bytes
.../activities/AndroidTVActivity/globals.xml.ftl | 0
.../activities/AndroidTVActivity/recipe.xml.ftl | 80 +
.../AndroidTVActivity/root/AndroidManifest.xml.ftl | 44 +
.../AndroidTVActivity/root/build.gradle.ftl | 0
.../root/res/drawable-hdpi/app_icon_quantum.png | Bin
.../res/drawable-hdpi/app_icon_quantum_card.png | Bin
.../drawable-hdpi/card_background_default.9.png | Bin
.../root/res/drawable-hdpi/default_background.xml | 0
.../root/res/drawable-hdpi/grid_bg.png | Bin
.../root/res/drawable-hdpi/movie.png | Bin
.../root/res/drawable-hdpi/scrubber_disabled.png | Bin
.../root/res/drawable-hdpi/scrubber_focussed.png | Bin
.../root/res/drawable-hdpi/scrubber_normal.png | Bin
.../root/res/drawable-hdpi/scrubber_pressed.png | Bin
.../root/res/drawable-hdpi/shadow7.9.png | Bin
.../root/res/drawable-hdpi/star_icon.png | Bin
.../res/drawable-hdpi/videos_by_google_banner.png | Bin
.../res/drawable-hdpi/videos_by_google_icon.png | Bin
.../root/res/drawable-mdpi/app_icon_quantum.png | Bin
.../res/drawable-mdpi/app_icon_quantum_card.png | Bin
.../root/res/drawable-mdpi/ic_launcher.png | Bin
.../res/drawable-mdpi/videos_by_google_banner.png | Bin
.../res/drawable-mdpi/videos_by_google_icon.png | Bin
.../root/res/drawable-xhdpi/app_icon_quantum.png | Bin
.../res/drawable-xhdpi/app_icon_quantum_card.png | Bin
.../root/res/drawable-xhdpi/default_background.xml | 0
.../root/res/drawable-xhdpi/grid_bg.png | Bin
.../root/res/drawable-xhdpi/ic_launcher.png | Bin
.../ic_pause_playcontrol_focussed.png | Bin
.../drawable-xhdpi/ic_pause_playcontrol_normal.png | Bin
.../ic_pause_playcontrol_pressed.png | Bin
.../res/drawable-xhdpi/ic_play_action_focussed.png | Bin
.../res/drawable-xhdpi/ic_play_action_normal.png | Bin
.../res/drawable-xhdpi/ic_play_action_pressed.png | Bin
.../ic_play_playcontrol_focussed.png | Bin
.../drawable-xhdpi/ic_play_playcontrol_normal.png | Bin
.../drawable-xhdpi/ic_play_playcontrol_pressed.png | Bin
.../root/res/drawable-xhdpi/movie.png | Bin
.../root/res/drawable-xhdpi/star_icon.png | Bin
.../res/drawable-xhdpi/videos_by_google_banner.png | Bin
.../res/drawable-xhdpi/videos_by_google_icon.png | Bin
.../root/res/drawable-xxhdpi/app_icon_quantum.png | Bin
.../res/drawable-xxhdpi/app_icon_quantum_card.png | Bin
.../drawable-xxhdpi/videos_by_google_banner.png | Bin
.../res/drawable-xxhdpi/videos_by_google_icon.png | Bin
.../root/res/drawable/app_icon_quantum.png | Bin
.../root/res/drawable/app_icon_quantum_card.png | Bin
.../root/res/drawable/app_icon_your_company.png | Bin
.../root/res/drawable/details_img.png | Bin
.../root/res/drawable/ic_action_a.png | Bin
.../root/res/drawable/ic_title.png | Bin
.../AndroidTVActivity/root/res/drawable/movie.png | Bin
.../root/res/drawable/player_bg_gradient_dark.xml | 0
.../root/res/drawable/shadow7.9.png | Bin
.../root/res/drawable/videos_by_google_banner.png | Bin
.../root/res/drawable/videos_by_google_icon.png | Bin
.../root/res/layout/activity_details.xml.ftl | 9 +
.../root/res/layout/activity_main.xml.ftl | 10 +
.../root/res/layout/playback_controls.xml.ftl | 34 +
.../AndroidTVActivity/root/res/menu/global.xml.ftl | 0
.../AndroidTVActivity/root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../root/res/values/colors.xml.ftl | 18 +
.../root/res/values/strings.xml.ftl | 44 +
.../root/res/values/themes.xml.ftl | 0
.../src/app_package/BrowseErrorActivity.java.ftl | 0
.../root/src/app_package/CardPresenter.java.ftl | 94 +
.../root/src/app_package/DetailsActivity.java.ftl | 0
.../DetailsDescriptionPresenter.java.ftl | 0
.../root/src/app_package/ErrorFragment.java.ftl | 47 +
.../root/src/app_package/MainActivity.java.ftl | 0
.../root/src/app_package/MainFragment.java.ftl | 276 ++
.../root/src/app_package/Movie.java.ftl | 0
.../root/src/app_package/MovieList.java.ftl | 0
.../PicassoBackgroundManagerTarget.java.ftl | 0
.../app_package/PlaybackOverlayActivity.java.ftl | 253 ++
.../app_package/PlaybackOverlayFragment.java.ftl | 404 +++
.../root/src/app_package/Utils.java.ftl | 91 +
.../src/app_package/VideoDetailsFragment.java.ftl | 233 ++
.../AndroidTVActivity/template-leanback-TV.png | Bin 0 -> 4519 bytes
.../activities/AndroidTVActivity/template.xml | 107 +
templates/activities/BasicActivity/globals.xml.ftl | 7 +
templates/activities/BasicActivity/recipe.xml.ftl | 20 +
.../BasicActivity/recipe_fragment.xml.ftl | 13 +
.../res/layout/activity_fragment_container.xml.ftl | 13 +
.../root/res/layout/fragment_simple.xml.ftl | 21 +
.../root/src/app_package/SimpleActivity.java.ftl | 68 +
.../src/app_package/SimpleActivityFragment.ftl | 0
templates/activities/BasicActivity/template.xml | 111 +
.../BasicActivity/template_basic_activity.png | Bin 0 -> 4996 bytes
.../template_basic_activity_fragment.png | Bin 0 -> 5168 bytes
.../activities/BlankWearActivity/globals.xml.ftl | 4 +
.../activities/BlankWearActivity/recipe.xml.ftl | 23 +
.../BlankWearActivity/root/AndroidManifest.xml.ftl | 0
.../root/res/layout/blank_activity.xml.ftl | 0
.../BlankWearActivity/root/res/layout/rect.xml.ftl | 0
.../root/res/layout/round.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/src/app_package/BlankActivity.java.ftl | 0
.../activities/BlankWearActivity/template.xml | 74 +
.../templates-WatchViewStub-Wear.png | Bin 0 -> 12170 bytes
templates/activities/EmptyActivity/globals.xml.ftl | 9 +
templates/activities/EmptyActivity/recipe.xml.ftl | 14 +
.../root/src/app_package/SimpleActivity.java.ftl | 15 +
templates/activities/EmptyActivity/template.xml | 62 +
.../EmptyActivity/template_blank_activity.png | Bin 0 -> 4594 bytes
.../activities/FullscreenActivity/globals.xml.ftl | 4 +
.../activities/FullscreenActivity/recipe.xml.ftl | 24 +
.../root/AndroidManifest.xml.ftl | 26 +
.../root/res/layout/activity_fullscreen.xml.ftl | 0
.../FullscreenActivity/root/res/values/attrs.xml | 0
.../FullscreenActivity/root/res/values/colors.xml | 0
.../root/res/values/strings.xml.ftl | 0
.../root/res/values/styles.xml.ftl | 15 +
.../src/app_package/FullscreenActivity.java.ftl | 194 ++
.../activities/FullscreenActivity/template.xml | 69 +
.../template_fullscreen_activity.png | Bin 0 -> 7759 bytes
.../GoogleAdMobAdsActivity}/globals.xml.ftl | 0
.../GoogleAdMobAdsActivity/recipe.xml.ftl | 27 +
.../root/AndroidManifest.xml.ftl | 36 +
.../root/res/layout/activity_simple.xml.ftl | 0
.../root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../root/res/values/dimens.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/src/app_package/SimpleActivity.java.ftl | 0
.../activities/GoogleAdMobAdsActivity/template.xml | 93 +
.../template_admob_activity.png | Bin 0 -> 5321 bytes
.../template_admob_activity_banner.png | Bin 0 -> 5802 bytes
.../template_admob_activity_interstitial.png | Bin 0 -> 6602 bytes
.../template_blank_activity.png | Bin 0 -> 3674 bytes
.../activities/GoogleMapsActivity/globals.xml.ftl | 11 +
.../activities/GoogleMapsActivity/recipe.xml.ftl | 28 +
.../root/AndroidManifest.xml.ftl | 37 +
.../root/debugRes/values/google_maps_api.xml.ftl | 0
.../root/releaseRes/values/google_maps_api.xml.ftl | 0
.../root/res/layout/activity_map.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/src/app_package/MapActivity.java.ftl | 0
.../activities/GoogleMapsActivity/template.xml | 69 +
.../GoogleMapsActivity/template_map_activity.png | Bin 0 -> 14511 bytes
.../GoogleMapsWearActivity/globals.xml.ftl | 4 +
.../GoogleMapsWearActivity/recipe.xml.ftl | 40 +
.../root/AndroidManifest.xml.ftl | 32 +
.../root/AndroidManifestPermissions.xml | 11 +
.../root/debugRes/values/google_maps_api.xml.ftl | 0
.../root/releaseRes/values/google_maps_api.xml.ftl | 0
.../root/res/layout/activity_map.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/src/app_package/MapActivity.java.ftl | 0
.../activities/GoogleMapsWearActivity/template.xml | 53 +
.../GoogleMapsWearActivity/template_thumb.png | Bin 0 -> 21746 bytes
templates/activities/LoginActivity/globals.xml.ftl | 8 +
templates/activities/LoginActivity/recipe.xml.ftl | 28 +
.../LoginActivity/root/AndroidManifest.xml.ftl | 31 +
.../root/res/layout/activity_login.xml.ftl | 83 +
.../LoginActivity/root/res/values/dimens.xml | 0
.../LoginActivity/root/res/values/strings.xml.ftl | 16 +
.../root/src/app_package/LoginActivity.java.ftl | 417 +++
templates/activities/LoginActivity/template.xml | 63 +
.../LoginActivity/template_login_activity.png | Bin 0 -> 3989 bytes
.../activities/MasterDetailFlow/globals.xml.ftl | 12 +
.../activities/MasterDetailFlow/recipe.xml.ftl | 55 +
.../MasterDetailFlow/root/AndroidManifest.xml.ftl | 38 +
.../root/res/layout/activity_item_detail.xml.ftl | 63 +
.../res/layout/activity_item_list_app_bar.xml.ftl | 42 +
.../root/res/layout/fragment_item_detail.xml.ftl | 0
.../root/res/layout/fragment_item_list.xml.ftl | 13 +
.../res/layout/fragment_item_list_twopane.xml.ftl | 44 +
.../root/res/layout/item_list_content.xml.ftl | 20 +
.../root/res/values-w900dp/refs.xml.ftl | 10 +
.../root/res/values/dimens.xml.ftl | 9 +
.../root/res/values/strings.xml.ftl | 0
.../src/app_package/ContentDetailActivity.java.ftl | 102 +
.../src/app_package/ContentDetailFragment.java.ftl | 75 +
.../src/app_package/ContentListActivity.java.ftl | 181 +
templates/activities/MasterDetailFlow/template.xml | 69 +
.../MasterDetailFlow/template_master_detail.png | Bin 0 -> 3885 bytes
.../NavigationDrawerActivity/globals.xml.ftl | 12 +
.../NavigationDrawerActivity/recipe.xml.ftl | 92 +
.../NavigationDrawerActivity/root/build.gradle.ftl | 0
.../res-buildApi22/drawable-v21/ic_menu_camera.xml | 12 +
.../drawable-v21/ic_menu_gallery.xml | 9 +
.../res-buildApi22/drawable-v21/ic_menu_manage.xml | 9 +
.../res-buildApi22/drawable-v21/ic_menu_send.xml | 9 +
.../res-buildApi22/drawable-v21/ic_menu_share.xml | 9 +
.../drawable-v21/ic_menu_slideshow.xml | 9 +
.../root/res-buildApi22/drawable/side_nav_bar.xml | 9 +
.../layout/navigation_header.xml.ftl | 34 +
.../res-buildApi22/layout/navigation_view.xml.ftl | 48 +
.../root/res-buildApi22/menu/drawer.xml.ftl | 36 +
.../root/res-buildApi22/menu/main.xml.ftl | 8 +
.../root/res-buildApi22/values/dimens.xml.ftl | 5 +
.../root/res-buildApi22/values/drawables.xml | 8 +
.../root/res-buildApi22/values/strings.xml.ftl | 10 +
.../root/res/drawable-hdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-hdpi/ic_drawer.png | Bin
.../root/res/drawable-mdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-mdpi/ic_drawer.png | Bin
.../root/res/drawable-xhdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-xhdpi/ic_drawer.png | Bin
.../root/res/drawable-xxhdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-xxhdpi/ic_drawer.png | Bin
.../root/res/layout/activity_drawer.xml.ftl | 26 +
.../res/layout/fragment_navigation_drawer.xml.ftl | 0
.../root/res/layout/fragment_simple.xml.ftl | 0
.../root/res/menu/global.xml.ftl | 0
.../root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../root/res/values/dimens.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../app_package/DrawerActivity.java.ftl | 105 +
.../root/src/app_package/DrawerActivity.java.ftl | 87 +
.../app_package/NavigationDrawerFragment.java.ftl | 0
.../root/src/app_package/include_fragment.java.ftl | 0
.../src/app_package/include_options_menu.java.ftl | 0
.../NavigationDrawerActivity/template.xml | 132 +
.../template_blank_activity_drawer.png | Bin 0 -> 6662 bytes
.../activities/ScrollActivity/globals.xml.ftl | 4 +
templates/activities/ScrollActivity/recipe.xml.ftl | 40 +
.../ScrollActivity/root/res/layout/app_bar.xml.ftl | 47 +
.../ScrollActivity/root/res/layout/simple.xml.ftl | 20 +
.../ScrollActivity/root/res/values/dimens.xml.ftl | 7 +
.../ScrollActivity/root/res/values/strings.xml.ftl | 94 +
.../root/src/app_package/ScrollActivity.java.ftl | 74 +
templates/activities/ScrollActivity/template.xml | 91 +
.../ScrollActivity/template_scroll_activity.png | Bin 0 -> 4782 bytes
.../activities/SettingsActivity/globals.xml.ftl | 16 +
.../activities/SettingsActivity/recipe.xml.ftl | 43 +
.../root/res/drawable-hdpi/ic_info_black_24dp.png | Bin 0 -> 321 bytes
.../drawable-hdpi/ic_notifications_black_24dp.png | Bin 0 -> 233 bytes
.../root/res/drawable-hdpi/ic_sync_black_24dp.png | Bin 0 -> 368 bytes
.../root/res/drawable-mdpi/ic_info_black_24dp.png | Bin 0 -> 222 bytes
.../drawable-mdpi/ic_notifications_black_24dp.png | Bin 0 -> 182 bytes
.../root/res/drawable-mdpi/ic_sync_black_24dp.png | Bin 0 -> 250 bytes
.../root/res/drawable-v21/ic_info_black_24dp.xml | 9 +
.../drawable-v21/ic_notifications_black_24dp.xml | 9 +
.../root/res/drawable-v21/ic_sync_black_24dp.xml | 9 +
.../root/res/drawable-xhdpi/ic_info_black_24dp.png | Bin 0 -> 412 bytes
.../drawable-xhdpi/ic_notifications_black_24dp.png | Bin 0 -> 278 bytes
.../root/res/drawable-xhdpi/ic_sync_black_24dp.png | Bin 0 -> 467 bytes
.../res/drawable-xxhdpi/ic_info_black_24dp.png | Bin 0 -> 579 bytes
.../ic_notifications_black_24dp.png | Bin 0 -> 383 bytes
.../res/drawable-xxhdpi/ic_sync_black_24dp.png | Bin 0 -> 669 bytes
.../res/drawable-xxxhdpi/ic_info_black_24dp.png | Bin 0 -> 766 bytes
.../ic_notifications_black_24dp.png | Bin 0 -> 497 bytes
.../res/drawable-xxxhdpi/ic_sync_black_24dp.png | Bin 0 -> 875 bytes
.../root/res/values/pref_strings.xml.ftl | 0
.../root/res/xml/pref_data_sync.xml | 0
.../SettingsActivity/root/res/xml/pref_general.xml | 33 +
.../root/res/xml/pref_headers.xml.ftl | 20 +
.../root/res/xml/pref_notification.xml | 27 +
.../AppCompatPreferenceActivity.java.ftl | 109 +
.../root/src/app_package/SettingsActivity.java.ftl | 350 ++
templates/activities/SettingsActivity/template.xml | 57 +
.../template_settings_activity.png | Bin 0 -> 4689 bytes
.../activities/TabbedActivity/globals.xml.ftl | 6 +
templates/activities/TabbedActivity/recipe.xml.ftl | 63 +
.../res/layout/activity_fragment_container.xml.ftl | 0
.../root/res/layout/activity_pager.xml.ftl | 6 +
.../root/res/layout/app_bar_activity.xml.ftl | 58 +
.../root/res/layout/fragment_simple.xml.ftl | 0
.../TabbedActivity}/root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../TabbedActivity/root/res/values/dimens.xml.ftl | 9 +
.../TabbedActivity/root/res/values/strings.xml.ftl | 7 +
.../root/src/app_package/DropdownActivity.java.ftl | 88 +
.../src/app_package/TabsAndPagerActivity.java.ftl | 276 ++
.../root/src/app_package/include_fragment.java.ftl | 34 +
.../src/app_package/include_options_menu.java.ftl | 0
templates/activities/TabbedActivity/template.xml | 105 +
.../template_blank_activity_dropdown.png | Bin 0 -> 5174 bytes
.../template_blank_activity_pager.png | Bin 0 -> 3896 bytes
.../template_blank_activity_tabs.png | Bin 0 -> 3775 bytes
templates/activities/common/common_globals.xml.ftl | 46 +
templates/activities/common/recipe_app_bar.xml.ftl | 18 +
.../activities/common/recipe_dummy_content.xml.ftl | 4 +
.../activities/common/recipe_manifest.xml.ftl | 8 +
.../activities/common/recipe_no_actionbar.xml.ftl | 9 +
templates/activities/common/recipe_simple.xml.ftl | 15 +
.../activities/common/recipe_simple_dimens.xml | 6 +
.../activities/common/recipe_simple_menu.xml.ftl | 8 +
.../activities/common/root/AndroidManifest.xml.ftl | 30 +
.../common/root/res/layout/app_bar.xml.ftl | 35 +
.../common/root/res/layout/simple.xml.ftl | 26 +
.../common/root/res/menu/simple_menu.xml.ftl | 0
.../res/values-v21/no_actionbar_styles_v21.xml.ftl | 10 +
.../root/res/values-w820dp/simple_dimens.xml | 0
.../common/root/res/values/app_bar_dimens.xml | 3 +
.../root/res/values/manifest_strings.xml.ftl | 5 +
.../root/res/values/no_actionbar_styles.xml.ftl | 14 +
.../common/root/res/values/simple_dimens.xml | 0
.../root/res/values/simple_menu_strings.xml.ftl | 3 +
.../src/app_package/dummy/DummyContent.java.ftl | 72 +
.../activities/common/wear_common_globals.xml.ftl | 18 +
{base/templates => templates}/build.gradle | 0
.../activities/BlankActivity/globals.xml.ftl | 0
.../activities/BlankActivity/recipe.xml.ftl | 28 +
.../BlankActivity/root/AndroidManifest.xml.ftl | 0
.../root/res/layout/activity_simple.xml.ftl | 0
.../BlankActivity}/root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../BlankActivity}/root/res/values/dimens.xml.ftl | 0
.../BlankActivity/root/res/values/strings.xml.ftl | 0
.../root/src/app_package/SimpleActivity.java.ftl | 0
.../eclipse/activities/BlankActivity/template.xml | 0
.../BlankActivity}/template_blank_activity.png | Bin
.../BlankActivityWithFragment/globals.xml.ftl | 0
.../BlankActivityWithFragment/recipe.xml.ftl | 31 +
.../root/AndroidManifest.xml.ftl | 0
.../res/layout/activity_fragment_container.xml.ftl | 0
.../root/res/layout/fragment_simple.xml.ftl | 0
.../root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../root/res/values/dimens.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/src/app_package/SimpleActivity.java.ftl | 0
.../root/src/app_package/include_fragment.java.ftl | 0
.../src/app_package/include_options_menu.java.ftl | 0
.../BlankActivityWithFragment/template.xml | 0
.../template_blank_activity_fragment.png | Bin
.../activities/EmptyActivity}/globals.xml.ftl | 0
.../activities/EmptyActivity/recipe.xml.ftl | 17 +
.../EmptyActivity/root/AndroidManifest.xml.ftl | 0
.../root/res/layout/activity_simple.xml | 0
.../EmptyActivity/root/res/values/strings.xml.ftl | 0
.../root/src/app_package/SimpleActivity.java.ftl | 0
.../eclipse}/activities/EmptyActivity/template.xml | 0
.../EmptyActivity}/template_blank_activity.png | Bin
.../activities/FullscreenActivity/globals.xml.ftl | 0
.../activities/FullscreenActivity/recipe.xml.ftl | 32 +
.../root/AndroidManifest.xml.ftl | 0
.../root/res/layout/activity_fullscreen.xml.ftl | 0
.../root/res/values-v11/styles.xml | 0
.../FullscreenActivity/root/res/values/attrs.xml | 0
.../FullscreenActivity/root/res/values/colors.xml | 0
.../root/res/values/strings.xml.ftl | 0
.../FullscreenActivity/root/res/values/styles.xml | 0
.../src/app_package/FullscreenActivity.java.ftl | 0
.../src/app_package/util/SystemUiHider.java.ftl | 0
.../app_package/util/SystemUiHiderBase.java.ftl | 0
.../util/SystemUiHiderHoneycomb.java.ftl | 0
.../activities/FullscreenActivity/template.xml | 0
.../template_fullscreen_activity.png | Bin
.../activities/LoginActivity/globals.xml.ftl | 0
.../activities/LoginActivity/recipe.xml.ftl | 28 +
.../LoginActivity/root/AndroidManifest.xml.ftl | 0
.../root/res/layout/activity_login.xml.ftl | 0
.../LoginActivity/root/res/values/dimens.xml | 0
.../LoginActivity/root/res/values/strings.xml.ftl | 0
.../root/src/app_package/LoginActivity.java.ftl | 0
.../root/src/app_package/PlusBaseActivity.java.ftl | 0
.../eclipse}/activities/LoginActivity/template.xml | 0
.../LoginActivity/template_login_activity.png | Bin
.../activities/MasterDetailFlow/globals.xml.ftl | 0
.../activities/MasterDetailFlow/recipe.xml.ftl | 37 +
.../MasterDetailFlow/root/AndroidManifest.xml.ftl | 0
.../res/layout/activity_content_detail.xml.ftl | 0
.../root/res/layout/activity_content_list.xml.ftl | 0
.../res/layout/activity_content_twopane.xml.ftl | 0
.../res/layout/fragment_content_detail.xml.ftl | 0
.../root/res/values-large/refs.xml.ftl | 0
.../root/res/values-sw600dp/refs.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../src/app_package/ContentDetailActivity.java.ftl | 0
.../src/app_package/ContentDetailFragment.java.ftl | 0
.../src/app_package/ContentListActivity.java.ftl | 0
.../src/app_package/ContentListFragment.java.ftl | 0
.../src/app_package/dummy/DummyContent.java.ftl | 0
.../activities/MasterDetailFlow/template.xml | 0
.../MasterDetailFlow/template_master_detail.png | Bin
.../NavigationDrawerActivity/globals.xml.ftl | 0
.../NavigationDrawerActivity/recipe.xml.ftl | 49 +
.../root/AndroidManifest.xml.ftl | 0
.../NavigationDrawerActivity/root/build.gradle.ftl | 0
.../root/res/drawable-hdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-hdpi/ic_drawer.png | Bin
.../root/res/drawable-mdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-mdpi/ic_drawer.png | Bin
.../root/res/drawable-xhdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-xhdpi/ic_drawer.png | Bin
.../root/res/drawable-xxhdpi/drawer_shadow.9.png | Bin
.../root/res/drawable-xxhdpi/ic_drawer.png | Bin
.../root/res/layout/activity_drawer.xml.ftl | 0
.../res/layout/fragment_navigation_drawer.xml.ftl | 0
.../root/res/layout/fragment_simple.xml.ftl | 0
.../root/res/menu/global.xml.ftl | 0
.../root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../root/res/values/dimens.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/src/app_package/DrawerActivity.java.ftl | 0
.../app_package/NavigationDrawerFragment.java.ftl | 0
.../root/src/app_package/include_fragment.java.ftl | 0
.../src/app_package/include_options_menu.java.ftl | 0
.../NavigationDrawerActivity/template.xml | 0
.../template_blank_activity_drawer.png | Bin
.../activities/SettingsActivity/globals.xml.ftl | 0
.../activities/SettingsActivity/recipe.xml.ftl | 24 +
.../SettingsActivity/root/AndroidManifest.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/res/xml/pref_data_sync.xml | 0
.../SettingsActivity/root/res/xml/pref_general.xml | 0
.../root/res/xml/pref_headers.xml.ftl | 0
.../root/res/xml/pref_notification.xml | 0
.../root/src/app_package/SettingsActivity.java.ftl | 0
.../activities/SettingsActivity/template.xml | 0
.../template_settings_activity.png | Bin
.../activities/TabbedActivity/globals.xml.ftl | 0
.../activities/TabbedActivity/recipe.xml.ftl | 47 +
.../TabbedActivity/root/AndroidManifest.xml.ftl | 0
.../res/layout/activity_fragment_container.xml.ftl | 0
.../root/res/layout/activity_pager.xml.ftl | 0
.../root/res/layout/fragment_simple.xml.ftl | 0
.../TabbedActivity}/root/res/menu/main.xml.ftl | 0
.../root/res/values-w820dp/dimens.xml | 0
.../TabbedActivity}/root/res/values/dimens.xml.ftl | 0
.../TabbedActivity/root/res/values/strings.xml.ftl | 0
.../root/src/app_package/DropdownActivity.java.ftl | 0
.../src/app_package/TabsAndPagerActivity.java.ftl | 0
.../root/src/app_package/include_fragment.java.ftl | 0
.../src/app_package/include_options_menu.java.ftl | 0
.../eclipse/activities/TabbedActivity/template.xml | 0
.../template_blank_activity_dropdown.png | Bin
.../template_blank_activity_pager.png | Bin
.../template_blank_activity_tabs.png | Bin
.../eclipse/gradle/utils/dependencies.gradle.ftl | 0
.../gradle/wrapper/gradle-wrapper.properties | 0
.../eclipse/gradle/wrapper/gradlew | 0
.../eclipse/gradle/wrapper/gradlew.bat | 0
templates/eclipse/other/AidlFile/recipe.xml.ftl | 7 +
.../root/src/app_package/interface.aidl.ftl | 0
.../eclipse/other/AidlFile/template.xml | 0
templates/eclipse/other/AidlFolder/recipe.xml.ftl | 12 +
.../eclipse/other/AidlFolder/root/build.gradle.ftl | 0
.../eclipse/other/AidlFolder/template.xml | 0
.../eclipse/other/AndroidManifest/recipe.xml.ftl | 14 +
.../AndroidManifest/root/AndroidManifest.xml.ftl | 0
.../other/AndroidManifest/root/build.gradle.ftl | 0
.../eclipse/other/AndroidManifest/template.xml | 0
.../eclipse/other/AppWidget/globals.xml.ftl | 0
templates/eclipse/other/AppWidget/recipe.xml.ftl | 35 +
.../other/AppWidget/root/AndroidManifest.xml.ftl | 0
.../drawable-nodpi/example_appwidget_preview.png | Bin
.../other/AppWidget/root/res/layout/appwidget.xml | 0
.../root/res/layout/appwidget_configure.xml | 0
.../other/AppWidget/root/res/values-v14/dimens.xml | 0
.../other/AppWidget/root/res/values/dimens.xml | 0
.../AppWidget/root/res/values/strings.xml.ftl | 0
.../AppWidget/root/res/xml/appwidget_info.xml.ftl | 0
.../root/src/app_package/AppWidget.java.ftl | 0
.../AppWidgetConfigureActivity.java.ftl | 0
.../eclipse/other/AppWidget/template.xml | 0
.../other/AppWidget/thumbs/template_widget_1x1.png | Bin
.../AppWidget/thumbs/template_widget_1x1_h.png | Bin
.../AppWidget/thumbs/template_widget_1x1_v.png | Bin
.../AppWidget/thumbs/template_widget_1x1_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_1x2.png | Bin
.../AppWidget/thumbs/template_widget_1x2_h.png | Bin
.../AppWidget/thumbs/template_widget_1x2_v.png | Bin
.../AppWidget/thumbs/template_widget_1x2_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_1x3.png | Bin
.../AppWidget/thumbs/template_widget_1x3_h.png | Bin
.../AppWidget/thumbs/template_widget_1x3_v.png | Bin
.../AppWidget/thumbs/template_widget_1x3_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_1x4.png | Bin
.../AppWidget/thumbs/template_widget_1x4_h.png | Bin
.../AppWidget/thumbs/template_widget_1x4_v.png | Bin
.../AppWidget/thumbs/template_widget_1x4_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_2x1.png | Bin
.../AppWidget/thumbs/template_widget_2x1_h.png | Bin
.../AppWidget/thumbs/template_widget_2x1_v.png | Bin
.../AppWidget/thumbs/template_widget_2x1_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_2x2.png | Bin
.../AppWidget/thumbs/template_widget_2x2_h.png | Bin
.../AppWidget/thumbs/template_widget_2x2_v.png | Bin
.../AppWidget/thumbs/template_widget_2x2_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_2x3.png | Bin
.../AppWidget/thumbs/template_widget_2x3_h.png | Bin
.../AppWidget/thumbs/template_widget_2x3_v.png | Bin
.../AppWidget/thumbs/template_widget_2x3_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_2x4.png | Bin
.../AppWidget/thumbs/template_widget_2x4_h.png | Bin
.../AppWidget/thumbs/template_widget_2x4_v.png | Bin
.../AppWidget/thumbs/template_widget_2x4_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_3x1.png | Bin
.../AppWidget/thumbs/template_widget_3x1_h.png | Bin
.../AppWidget/thumbs/template_widget_3x1_v.png | Bin
.../AppWidget/thumbs/template_widget_3x1_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_3x2.png | Bin
.../AppWidget/thumbs/template_widget_3x2_h.png | Bin
.../AppWidget/thumbs/template_widget_3x2_v.png | Bin
.../AppWidget/thumbs/template_widget_3x2_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_3x3.png | Bin
.../AppWidget/thumbs/template_widget_3x3_h.png | Bin
.../AppWidget/thumbs/template_widget_3x3_v.png | Bin
.../AppWidget/thumbs/template_widget_3x3_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_3x4.png | Bin
.../AppWidget/thumbs/template_widget_3x4_h.png | Bin
.../AppWidget/thumbs/template_widget_3x4_v.png | Bin
.../AppWidget/thumbs/template_widget_3x4_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_4x1.png | Bin
.../AppWidget/thumbs/template_widget_4x1_h.png | Bin
.../AppWidget/thumbs/template_widget_4x1_v.png | Bin
.../AppWidget/thumbs/template_widget_4x1_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_4x2.png | Bin
.../AppWidget/thumbs/template_widget_4x2_h.png | Bin
.../AppWidget/thumbs/template_widget_4x2_v.png | Bin
.../AppWidget/thumbs/template_widget_4x2_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_4x3.png | Bin
.../AppWidget/thumbs/template_widget_4x3_h.png | Bin
.../AppWidget/thumbs/template_widget_4x3_v.png | Bin
.../AppWidget/thumbs/template_widget_4x3_vh.png | Bin
.../other/AppWidget/thumbs/template_widget_4x4.png | Bin
.../AppWidget/thumbs/template_widget_4x4_h.png | Bin
.../AppWidget/thumbs/template_widget_4x4_v.png | Bin
.../AppWidget/thumbs/template_widget_4x4_vh.png | Bin
.../eclipse/other/AssetsFolder/recipe.xml.ftl | 12 +
.../other/AssetsFolder/root/build.gradle.ftl | 0
.../eclipse/other/AssetsFolder/template.xml | 0
.../eclipse/other/BlankFragment/globals.xml.ftl | 0
.../eclipse/other/BlankFragment/recipe.xml.ftl | 19 +
.../root/res/layout/fragment_blank.xml.ftl | 0
.../BlankFragment/root/res/values/strings.xml | 0
.../root/src/app_package/BlankFragment.java.ftl | 0
.../eclipse/other/BlankFragment/template.xml | 0
.../BlankFragment/template_blank_fragment.png | Bin
.../other/BroadcastReceiver/globals.xml.ftl | 0
.../eclipse/other/BroadcastReceiver/recipe.xml.ftl | 8 +
.../BroadcastReceiver/root/AndroidManifest.xml.ftl | 0
.../src/app_package/BroadcastReceiver.java.ftl | 0
.../eclipse/other/BroadcastReceiver/template.xml | 0
.../eclipse/other/ContentProvider/globals.xml.ftl | 0
.../eclipse/other/ContentProvider/recipe.xml.ftl | 8 +
.../ContentProvider/root/AndroidManifest.xml.ftl | 0
.../root/src/app_package/ContentProvider.java.ftl | 0
.../eclipse/other/ContentProvider/template.xml | 0
.../eclipse/other/CustomView/globals.xml.ftl | 0
templates/eclipse/other/CustomView/recipe.xml.ftl | 13 +
.../CustomView/root/res/layout/sample.xml.ftl | 0
.../other/CustomView/root/res/values/attrs.xml.ftl | 0
.../root/src/app_package/CustomView.java.ftl | 0
.../eclipse/other/CustomView/template.xml | 0
.../eclipse/other/Daydream/globals.xml.ftl | 0
templates/eclipse/other/Daydream/recipe.xml.ftl | 28 +
.../other/Daydream/root/AndroidManifest.xml.ftl | 0
.../other/Daydream/root/res/layout-v17/dream.xml | 0
.../other/Daydream/root/res/values/strings.xml.ftl | 0
.../other/Daydream/root/res/xml/dream_prefs.xml | 0
.../other/Daydream/root/res/xml/xml_dream.xml.ftl | 0
.../root/src/app_package/DreamService.java.ftl | 0
.../root/src/app_package/SettingsActivity.java.ftl | 0
.../eclipse/other/Daydream/template.xml | 0
.../eclipse/other/IntentService/globals.xml.ftl | 0
.../eclipse/other/IntentService/recipe.xml.ftl | 8 +
.../IntentService/root/AndroidManifest.xml.ftl | 0
.../root/src/app_package/IntentService.java.ftl | 0
.../eclipse/other/IntentService/template.xml | 0
templates/eclipse/other/JavaFolder/recipe.xml.ftl | 12 +
.../eclipse/other/JavaFolder/root/build.gradle.ftl | 0
.../eclipse/other/JavaFolder/template.xml | 0
templates/eclipse/other/JniFolder/recipe.xml.ftl | 12 +
.../eclipse/other/JniFolder/root/build.gradle.ftl | 0
.../eclipse/other/JniFolder/template.xml | 0
.../other/LayoutResourceFile/recipe.xml.ftl | 7 +
.../LayoutResourceFile/root/res/layout.xml.ftl | 0
.../eclipse/other/LayoutResourceFile/template.xml | 0
.../eclipse/other/ListFragment/globals.xml.ftl | 0
.../eclipse/other/ListFragment/recipe.xml.ftl | 28 +
.../ListFragment/root/res/layout/fragment_grid.xml | 0
.../ListFragment/root/res/layout/fragment_list.xml | 0
.../root/res/values-large/refs_lrg.xml.ftl | 0
.../root/res/values-sw600dp/refs_lrg.xml.ftl | 0
.../ListFragment/root/res/values/refs.xml.ftl | 0
.../ListFragment/root/res/values/refs_lrg.xml.ftl | 0
.../root/src/app_package/ListFragment.java.ftl | 0
.../src/app_package/dummy/DummyContent.java.ftl | 0
.../eclipse/other/ListFragment/template.xml | 0
.../other/ListFragment/templates_list_fragment.png | Bin
.../eclipse/other/Notification/globals.xml.ftl | 0
.../eclipse/other/Notification/recipe.xml.ftl | 31 +
.../Notification/root/AndroidManifest.xml.ftl | 0
.../res/drawable-hdpi/ic_action_stat_reply.png | Bin
.../res/drawable-hdpi/ic_action_stat_share.png | Bin
.../res/drawable-mdpi/ic_action_stat_reply.png | Bin
.../res/drawable-mdpi/ic_action_stat_share.png | Bin
.../res/drawable-nodpi/example_picture_large.png | Bin
.../res/drawable-nodpi/example_picture_small.png | Bin
.../res/drawable-xhdpi/ic_action_stat_reply.png | Bin
.../res/drawable-xhdpi/ic_action_stat_share.png | Bin
.../Notification/root/res/values/strings.xml.ftl | 0
.../src/app_package/NotificationHelper.java.ftl | 0
.../eclipse/other/Notification/template.xml | 0
.../Notification/template_notification_list.png | Bin
.../template_notification_list_actions.png | Bin
.../Notification/template_notification_none.png | Bin
.../template_notification_none_actions.png | Bin
.../Notification/template_notification_picture.png | Bin
.../template_notification_picture_actions.png | Bin
.../Notification/template_notification_text.png | Bin
.../template_notification_text_actions.png | Bin
.../eclipse/other/PlusOneFragment/globals.xml.ftl | 0
.../eclipse/other/PlusOneFragment/recipe.xml.ftl | 20 +
.../PlusOneFragment/root/AndroidManifest.xml.ftl | 0
.../root/res/layout/fragment_plus_one.xml.ftl | 0
.../root/src/app_package/PlusOneFragment.java.ftl | 0
.../eclipse/other/PlusOneFragment/template.xml | 0
.../PlusOneFragment/templates_plusone_fragment.png | Bin
templates/eclipse/other/ResFolder/recipe.xml.ftl | 12 +
.../eclipse/other/ResFolder/root/build.gradle.ftl | 0
.../eclipse/other/ResFolder/template.xml | 0
.../eclipse/other/ResourcesFolder/recipe.xml.ftl | 12 +
.../other/ResourcesFolder/root/build.gradle.ftl | 0
.../eclipse/other/ResourcesFolder/template.xml | 0
templates/eclipse/other/RsFolder/recipe.xml.ftl | 12 +
.../eclipse/other/RsFolder/root/build.gradle.ftl | 0
.../eclipse/other/RsFolder/template.xml | 0
.../eclipse/other/Service/globals.xml.ftl | 0
templates/eclipse/other/Service/recipe.xml.ftl | 8 +
.../other/Service/root/AndroidManifest.xml.ftl | 0
.../Service/root/src/app_package/Service.java.ftl | 0
.../eclipse/other/Service/template.xml | 0
.../eclipse/other/ValueResourceFile/recipe.xml.ftl | 7 +
.../ValueResourceFile/root/res/values.xml.ftl | 0
.../eclipse/other/ValueResourceFile/template.xml | 0
.../projects/NewAndroidApplication/globals.xml.ftl | 0
.../projects/NewAndroidApplication/recipe.xml.ftl | 27 +
.../root/AndroidManifest.xml.ftl | 0
.../NewAndroidApplication/root/build.gradle.ftl | 0
.../root/res/drawable-hdpi/ic_launcher.png | Bin
.../root/res/drawable-mdpi/ic_launcher.png | Bin
.../root/res/drawable-xhdpi/ic_launcher.png | Bin
.../root/res/values-v11/styles_hc.xml.ftl | 0
.../root/res/values-v14/styles_ics.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/res/values/styles.xml.ftl | 0
.../NewAndroidApplication/root/settings.gradle.ftl | 0
.../projects/NewAndroidApplication/template.xml | 82 +
.../NewAndroidApplication/template_new_project.png | Bin
.../projects/NewAndroidLibrary/globals.xml.ftl | 0
.../projects/NewAndroidLibrary/recipe.xml.ftl | 27 +
.../NewAndroidLibrary/root/AndroidManifest.xml.ftl | 0
.../NewAndroidLibrary/root/build.gradle.ftl | 0
.../root/res/drawable-hdpi/ic_launcher.png | Bin
.../root}/res/drawable-mdpi/ic_launcher.png | Bin
.../root/res/drawable-xhdpi/ic_launcher.png | Bin
.../root/res/values-v11/styles_hc.xml.ftl | 0
.../root/res/values-v14/styles_ics.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../root/res/values/styles.xml.ftl | 0
.../NewAndroidLibrary/root/settings.gradle.ftl | 0
.../projects/NewAndroidLibrary/template.xml | 82 +
.../NewAndroidLibrary/template_new_project.png | Bin
.../projects/NewJavaLibrary/globals.xml.ftl | 0
.../eclipse/projects/NewJavaLibrary/recipe.xml.ftl | 5 +
.../projects/NewJavaLibrary/root/build.gradle.ftl | 0
.../NewJavaLibrary/root/settings.gradle.ftl | 0
.../root/src/library_package/Placeholder.java.ftl | 0
.../eclipse/projects/NewJavaLibrary/template.xml | 0
.../NewJavaLibrary/template_new_project.png | Bin
.../AndroidWearModule/globals.xml.ftl | 0
.../AndroidWearModule/recipe.xml.ftl | 39 +
.../AndroidWearModule/root/AndroidManifest.xml.ftl | 12 +
.../AndroidWearModule/root/build.gradle.ftl | 0
.../AndroidWearModule}/root/module_ignore | 0
.../AndroidWearModule/root/proguard-rules.txt.ftl | 0
.../root/res/mipmap-hdpi/ic_launcher.png | Bin
.../root/res/mipmap-mdpi/ic_launcher.png | Bin
.../root/res/mipmap-xhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxxhdpi/ic_launcher.png | Bin
.../root/res/values/strings.xml.ftl | 0
.../AndroidWearModule/root/settings.gradle.ftl | 0
.../gradle-projects/AndroidWearModule/template.xml | 99 +
.../AndroidWearModule/template_new_project.png | Bin 0 -> 4914 bytes
.../ImportExistingProject/template.xml | 13 +
.../ImportExistingProject/template_new_project.png | Bin 0 -> 11710 bytes
.../NewAndroidAutoProject/template.xml | 12 +
.../NewAndroidModule/globals.xml.ftl | 15 +
.../NewAndroidModule/recipe.xml.ftl | 60 +
.../NewAndroidModule/root/AndroidManifest.xml.ftl | 14 +
.../NewAndroidModule/root/build.gradle.ftl | 0
.../NewAndroidModule}/root/module_ignore | 0
.../NewAndroidModule/root/proguard-rules.txt.ftl | 0
.../root/res/mipmap-anydpi/test.svg | 0
.../root/res/mipmap-hdpi/ic_launcher.png | Bin
.../root/res/mipmap-mdpi/ic_launcher.png | Bin
.../root/res/mipmap-xhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxxhdpi/ic_launcher.png | Bin
.../NewAndroidModule/root/res/values/colors.xml | 6 +
.../root/res/values/strings.xml.ftl | 0
.../root/res/values/styles.xml.ftl | 15 +
.../NewAndroidModule}/root/settings.gradle.ftl | 0
.../root/test/app_package/ApplicationTest.java.ftl | 0
.../root/test/app_package/ExampleUnitTest.java.ftl | 0
.../gradle-projects/NewAndroidModule/template.xml | 115 +
.../NewAndroidModule/template_new_project.png | Bin 0 -> 14551 bytes
.../NewAndroidProject}/globals.xml.ftl | 0
.../NewAndroidProject/recipe.xml.ftl | 24 +
.../NewAndroidProject/root/build.gradle.ftl | 33 +
.../NewAndroidProject}/root/gradle.properties.ftl | 0
.../NewAndroidProject}/root/local.properties.ftl | 0
.../NewAndroidProject/root/project_ignore | 8 +
.../NewAndroidProject/root/settings.gradle.ftl | 0
.../gradle-projects/NewAndroidProject/template.xml | 23 +
.../NewAndroidProject/template_new_project.png | Bin 0 -> 4297 bytes
.../NewAndroidTVModule/globals.xml.ftl | 0
.../NewAndroidTVModule/recipe.xml.ftl | 44 +
.../root/AndroidManifest.xml.ftl | 13 +
.../NewAndroidTVModule/root/build.gradle.ftl | 0
.../NewAndroidTVModule}/root/module_ignore | 0
.../NewAndroidTVModule/root/proguard-rules.txt.ftl | 0
.../root/res/mipmap-hdpi/ic_launcher.png | Bin
.../root/res/mipmap-mdpi/ic_launcher.png | Bin
.../root/res/mipmap-xhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxxhdpi/ic_launcher.png | Bin
.../root/res/values/strings.xml.ftl | 0
.../NewAndroidTVModule/root/res/values/styles.xml | 0
.../NewAndroidTVModule}/root/settings.gradle.ftl | 0
.../root/test/app_package/ApplicationTest.java.ftl | 0
.../NewAndroidTVModule/template.xml | 100 +
.../NewAndroidTVModule/template_new_project.png | Bin 0 -> 3708 bytes
.../gradle-projects/NewGlassModule/globals.xml.ftl | 0
.../gradle-projects/NewGlassModule/recipe.xml.ftl | 42 +
.../NewGlassModule/root/AndroidManifest.xml.ftl | 13 +
.../NewGlassModule/root/build.gradle.ftl | 0
.../NewGlassModule}/root/module_ignore | 0
.../NewGlassModule/root/proguard-rules.txt.ftl | 0
.../root/res/mipmap-hdpi/ic_launcher.png | Bin
.../root/res/mipmap-mdpi/ic_launcher.png | Bin
.../root/res/mipmap-xhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxhdpi/ic_launcher.png | Bin
.../root/res/mipmap-xxxhdpi/ic_launcher.png | Bin
.../NewGlassModule/root/res/values/strings.xml.ftl | 0
.../NewGlassModule/root/res/values/styles.xml | 0
.../NewGlassModule}/root/settings.gradle.ftl | 0
.../root/test/app_package/ApplicationTest.java.ftl | 0
.../gradle-projects/NewGlassModule/template.xml | 100 +
.../NewGlassModule/template_new_project.png | Bin 0 -> 5988 bytes
.../gradle-projects/NewJavaLibrary/globals.xml.ftl | 0
.../gradle-projects/NewJavaLibrary/recipe.xml.ftl | 15 +
.../NewJavaLibrary/root/build.gradle.ftl | 0
.../gradle-projects/NewJavaLibrary/root/gitignore | 0
.../NewJavaLibrary}/root/settings.gradle.ftl | 0
.../root/src/library_package/Placeholder.java.ftl | 0
.../gradle-projects/NewJavaLibrary/template.xml | 44 +
.../NewJavaLibrary/template_new_project.png | Bin 0 -> 14551 bytes
.../gradle/utils/dependencies.gradle.ftl | 0
.../gradle/wrapper/gradle-wrapper.properties | 6 +
templates/gradle/wrapper/gradlew | 160 +
.../gradle/wrapper/gradlew.bat | 0
templates/other/AidlFile/recipe.xml.ftl | 7 +
.../root/src/app_package/interface.aidl.ftl | 0
templates/other/AidlFile/template.xml | 21 +
templates/other/AidlFolder/recipe.xml.ftl | 12 +
.../other/AidlFolder/root/build.gradle.ftl | 0
templates/other/AidlFolder/template.xml | 30 +
.../other/AndroidAutoMediaService}/globals.xml.ftl | 0
.../other/AndroidAutoMediaService/recipe.xml.ftl | 22 +
.../root/AndroidManifest.xml.ftl | 0
.../root/res/values-v21/styles.xml.ftl | 0
.../root/res/xml/automotive_app_desc.xml | 0
.../root/src/app_package/MusicService.java.ftl | 0
.../other/AndroidAutoMediaService/template.xml | 49 +
.../templates-mediaService-Auto.png | Bin 0 -> 4002 bytes
.../AndroidAutoMessagingService}/globals.xml.ftl | 0
.../AndroidAutoMessagingService/recipe.xml.ftl | 23 +
.../root/AndroidManifest.xml.ftl | 0
.../root/res/xml/automotive_app_desc.xml | 0
.../src/app_package/MessageReadReceiver.java.ftl | 0
.../src/app_package/MessageReplyReceiver.java.ftl | 0
.../root/src/app_package/MessagingService.java.ftl | 0
.../other/AndroidAutoMessagingService/template.xml | 50 +
.../templates-messagingService-Auto.png | Bin 0 -> 4010 bytes
templates/other/AndroidManifest/recipe.xml.ftl | 14 +
.../AndroidManifest/root/AndroidManifest.xml.ftl | 0
.../other/AndroidManifest/root/build.gradle.ftl | 0
templates/other/AndroidManifest/template.xml | 30 +
.../other/AppWidget/globals.xml.ftl | 0
templates/other/AppWidget/recipe.xml.ftl | 35 +
.../other/AppWidget/root/AndroidManifest.xml.ftl | 0
.../drawable-nodpi/example_appwidget_preview.png | Bin
.../other/AppWidget/root/res/layout/appwidget.xml | 0
.../root/res/layout/appwidget_configure.xml | 0
.../other/AppWidget/root/res/values-v14/dimens.xml | 0
.../other/AppWidget/root/res/values/dimens.xml | 0
.../AppWidget/root/res/values/strings.xml.ftl | 0
.../AppWidget/root/res/xml/appwidget_info.xml.ftl | 0
.../root/src/app_package/AppWidget.java.ftl | 63 +
.../AppWidgetConfigureActivity.java.ftl | 103 +
templates/other/AppWidget/template.xml | 148 +
.../other/AppWidget/thumbs/template_widget_1x1.png | Bin 0 -> 4212 bytes
.../AppWidget/thumbs/template_widget_1x1_h.png | Bin 0 -> 4448 bytes
.../AppWidget/thumbs/template_widget_1x1_v.png | Bin 0 -> 4595 bytes
.../AppWidget/thumbs/template_widget_1x1_vh.png | Bin 0 -> 4777 bytes
.../other/AppWidget/thumbs/template_widget_1x2.png | Bin 0 -> 4319 bytes
.../AppWidget/thumbs/template_widget_1x2_h.png | Bin 0 -> 4525 bytes
.../AppWidget/thumbs/template_widget_1x2_v.png | Bin 0 -> 4696 bytes
.../AppWidget/thumbs/template_widget_1x2_vh.png | Bin 0 -> 4862 bytes
.../other/AppWidget/thumbs/template_widget_1x3.png | Bin 0 -> 4419 bytes
.../AppWidget/thumbs/template_widget_1x3_h.png | Bin 0 -> 4650 bytes
.../AppWidget/thumbs/template_widget_1x3_v.png | Bin 0 -> 4790 bytes
.../AppWidget/thumbs/template_widget_1x3_vh.png | Bin 0 -> 4982 bytes
.../other/AppWidget/thumbs/template_widget_1x4.png | Bin 0 -> 4505 bytes
.../AppWidget/thumbs/template_widget_1x4_h.png | Bin 0 -> 4715 bytes
.../AppWidget/thumbs/template_widget_1x4_v.png | Bin 0 -> 4883 bytes
.../AppWidget/thumbs/template_widget_1x4_vh.png | Bin 0 -> 5051 bytes
.../other/AppWidget/thumbs/template_widget_2x1.png | Bin 0 -> 4214 bytes
.../AppWidget/thumbs/template_widget_2x1_h.png | Bin 0 -> 4469 bytes
.../AppWidget/thumbs/template_widget_2x1_v.png | Bin 0 -> 4582 bytes
.../AppWidget/thumbs/template_widget_2x1_vh.png | Bin 0 -> 4768 bytes
.../other/AppWidget/thumbs/template_widget_2x2.png | Bin 0 -> 4312 bytes
.../AppWidget/thumbs/template_widget_2x2_h.png | Bin 0 -> 4548 bytes
.../AppWidget/thumbs/template_widget_2x2_v.png | Bin 0 -> 4687 bytes
.../AppWidget/thumbs/template_widget_2x2_vh.png | Bin 0 -> 4864 bytes
.../other/AppWidget/thumbs/template_widget_2x3.png | Bin 0 -> 4416 bytes
.../AppWidget/thumbs/template_widget_2x3_h.png | Bin 0 -> 4693 bytes
.../AppWidget/thumbs/template_widget_2x3_v.png | Bin 0 -> 4800 bytes
.../AppWidget/thumbs/template_widget_2x3_vh.png | Bin 0 -> 5005 bytes
.../other/AppWidget/thumbs/template_widget_2x4.png | Bin 0 -> 4499 bytes
.../AppWidget/thumbs/template_widget_2x4_h.png | Bin 0 -> 4755 bytes
.../AppWidget/thumbs/template_widget_2x4_v.png | Bin 0 -> 4891 bytes
.../AppWidget/thumbs/template_widget_2x4_vh.png | Bin 0 -> 5066 bytes
.../other/AppWidget/thumbs/template_widget_3x1.png | Bin 0 -> 4219 bytes
.../AppWidget/thumbs/template_widget_3x1_h.png | Bin 0 -> 4467 bytes
.../AppWidget/thumbs/template_widget_3x1_v.png | Bin 0 -> 4602 bytes
.../AppWidget/thumbs/template_widget_3x1_vh.png | Bin 0 -> 4784 bytes
.../other/AppWidget/thumbs/template_widget_3x2.png | Bin 0 -> 4303 bytes
.../AppWidget/thumbs/template_widget_3x2_h.png | Bin 0 -> 4535 bytes
.../AppWidget/thumbs/template_widget_3x2_v.png | Bin 0 -> 4693 bytes
.../AppWidget/thumbs/template_widget_3x2_vh.png | Bin 0 -> 4872 bytes
.../other/AppWidget/thumbs/template_widget_3x3.png | Bin 0 -> 4387 bytes
.../AppWidget/thumbs/template_widget_3x3_h.png | Bin 0 -> 4653 bytes
.../AppWidget/thumbs/template_widget_3x3_v.png | Bin 0 -> 4783 bytes
.../AppWidget/thumbs/template_widget_3x3_vh.png | Bin 0 -> 4984 bytes
.../other/AppWidget/thumbs/template_widget_3x4.png | Bin 0 -> 4464 bytes
.../AppWidget/thumbs/template_widget_3x4_h.png | Bin 0 -> 4705 bytes
.../AppWidget/thumbs/template_widget_3x4_v.png | Bin 0 -> 4868 bytes
.../AppWidget/thumbs/template_widget_3x4_vh.png | Bin 0 -> 5039 bytes
.../other/AppWidget/thumbs/template_widget_4x1.png | Bin 0 -> 4151 bytes
.../AppWidget/thumbs/template_widget_4x1_h.png | Bin 0 -> 4399 bytes
.../AppWidget/thumbs/template_widget_4x1_v.png | Bin 0 -> 4511 bytes
.../AppWidget/thumbs/template_widget_4x1_vh.png | Bin 0 -> 4689 bytes
.../other/AppWidget/thumbs/template_widget_4x2.png | Bin 0 -> 4160 bytes
.../AppWidget/thumbs/template_widget_4x2_h.png | Bin 0 -> 4381 bytes
.../AppWidget/thumbs/template_widget_4x2_v.png | Bin 0 -> 4532 bytes
.../AppWidget/thumbs/template_widget_4x2_vh.png | Bin 0 -> 4692 bytes
.../other/AppWidget/thumbs/template_widget_4x3.png | Bin 0 -> 4175 bytes
.../AppWidget/thumbs/template_widget_4x3_h.png | Bin 0 -> 4453 bytes
.../AppWidget/thumbs/template_widget_4x3_v.png | Bin 0 -> 4552 bytes
.../AppWidget/thumbs/template_widget_4x3_vh.png | Bin 0 -> 4762 bytes
.../other/AppWidget/thumbs/template_widget_4x4.png | Bin 0 -> 4181 bytes
.../AppWidget/thumbs/template_widget_4x4_h.png | Bin 0 -> 4421 bytes
.../AppWidget/thumbs/template_widget_4x4_v.png | Bin 0 -> 4572 bytes
.../AppWidget/thumbs/template_widget_4x4_vh.png | Bin 0 -> 4727 bytes
templates/other/AssetsFolder/recipe.xml.ftl | 12 +
.../other/AssetsFolder/root/build.gradle.ftl | 0
templates/other/AssetsFolder/template.xml | 30 +
templates/other/BlankFragment/globals.xml.ftl | 9 +
templates/other/BlankFragment/recipe.xml.ftl | 19 +
.../root/res/layout/fragment_blank.xml.ftl | 0
.../BlankFragment/root/res/values/strings.xml | 0
.../root/src/app_package/BlankFragment.java.ftl | 131 +
templates/other/BlankFragment/template.xml | 60 +
.../BlankFragment/template_blank_fragment.png | Bin 0 -> 5168 bytes
.../other/BroadcastReceiver/globals.xml.ftl | 0
templates/other/BroadcastReceiver/recipe.xml.ftl | 8 +
.../BroadcastReceiver/root/AndroidManifest.xml.ftl | 0
.../src/app_package/BroadcastReceiver.java.ftl | 0
templates/other/BroadcastReceiver/template.xml | 32 +
.../other/ContentProvider/globals.xml.ftl | 0
templates/other/ContentProvider/recipe.xml.ftl | 8 +
.../ContentProvider/root/AndroidManifest.xml.ftl | 0
.../root/src/app_package/ContentProvider.java.ftl | 0
templates/other/ContentProvider/template.xml | 40 +
.../other/CustomView/globals.xml.ftl | 0
templates/other/CustomView/recipe.xml.ftl | 13 +
.../CustomView/root/res/layout/sample.xml.ftl | 0
.../other/CustomView/root/res/values/attrs.xml.ftl | 0
.../root/src/app_package/CustomView.java.ftl | 0
templates/other/CustomView/template.xml | 28 +
.../other/Daydream/globals.xml.ftl | 0
templates/other/Daydream/recipe.xml.ftl | 28 +
.../other/Daydream/root/AndroidManifest.xml.ftl | 0
.../other/Daydream/root/res/layout-v17/dream.xml | 0
.../other/Daydream/root/res/values/strings.xml.ftl | 0
.../other/Daydream/root/res/xml/dream_prefs.xml | 0
.../other/Daydream/root/res/xml/xml_dream.xml.ftl | 0
.../root/src/app_package/DreamService.java.ftl | 143 +
.../root/src/app_package/SettingsActivity.java.ftl | 0
templates/other/Daydream/template.xml | 47 +
.../other/DisplayNotification/globals.xml.ftl | 4 +
templates/other/DisplayNotification/recipe.xml.ftl | 22 +
.../root/AndroidManifest.xml.ftl | 0
.../root/res/layout/activity_display.xml.ftl | 0
.../root/res/values/strings.xml | 0
.../src/app_package/BroadcastReceiver.java.ftl | 0
.../root/src/app_package/DisplayActivity.java.ftl | 0
.../root/src/app_package/StubActivity.java.ftl | 0
templates/other/DisplayNotification/template.xml | 64 +
.../templates-activityView-Wear.png | Bin 0 -> 12464 bytes
.../other/IntentService/globals.xml.ftl | 0
templates/other/IntentService/recipe.xml.ftl | 8 +
.../IntentService/root/AndroidManifest.xml.ftl | 0
.../root/src/app_package/IntentService.java.ftl | 0
templates/other/IntentService/template.xml | 29 +
templates/other/JavaFolder/recipe.xml.ftl | 12 +
.../other/JavaFolder/root/build.gradle.ftl | 0
templates/other/JavaFolder/template.xml | 30 +
templates/other/JniFolder/recipe.xml.ftl | 12 +
.../other/JniFolder/root/build.gradle.ftl | 0
templates/other/JniFolder/template.xml | 30 +
templates/other/LayoutResourceFile/recipe.xml.ftl | 7 +
.../LayoutResourceFile/root/res/layout.xml.ftl | 0
templates/other/LayoutResourceFile/template.xml | 29 +
templates/other/ListFragment/globals.xml.ftl | 10 +
templates/other/ListFragment/recipe.xml.ftl | 22 +
.../ListFragment/root/res/layout/fragment_list.xml | 14 +
.../root/res/layout/item_list_content.xml | 20 +
.../other/ListFragment/root/res/values/dimens.xml | 4 +
.../root/src/app_package/ListFragment.java.ftl | 119 +
.../src/app_package/RecyclerViewAdapter.java.ftl | 77 +
templates/other/ListFragment/template.xml | 87 +
.../other/ListFragment/templates_list_fragment.png | Bin 0 -> 5295 bytes
.../other/Notification/globals.xml.ftl | 0
templates/other/Notification/recipe.xml.ftl | 31 +
.../Notification/root/AndroidManifest.xml.ftl | 0
.../res/drawable-hdpi/ic_action_stat_reply.png | Bin
.../res/drawable-hdpi/ic_action_stat_share.png | Bin
.../res/drawable-mdpi/ic_action_stat_reply.png | Bin
.../res/drawable-mdpi/ic_action_stat_share.png | Bin
.../res/drawable-nodpi/example_picture_large.png | Bin
.../res/drawable-nodpi/example_picture_small.png | Bin
.../res/drawable-xhdpi/ic_action_stat_reply.png | Bin
.../res/drawable-xhdpi/ic_action_stat_share.png | Bin
.../Notification/root/res/values/strings.xml.ftl | 0
.../src/app_package/NotificationHelper.java.ftl | 0
templates/other/Notification/template.xml | 58 +
.../Notification/template_notification_list.png | Bin 0 -> 7499 bytes
.../template_notification_list_actions.png | Bin 0 -> 7630 bytes
.../Notification/template_notification_none.png | Bin 0 -> 5291 bytes
.../template_notification_none_actions.png | Bin 0 -> 5437 bytes
.../Notification/template_notification_picture.png | Bin 0 -> 7337 bytes
.../template_notification_picture_actions.png | Bin 0 -> 7490 bytes
.../Notification/template_notification_text.png | Bin 0 -> 7410 bytes
.../template_notification_text_actions.png | Bin 0 -> 7684 bytes
.../other/PlusOneFragment/globals.xml.ftl | 0
templates/other/PlusOneFragment/recipe.xml.ftl | 20 +
.../PlusOneFragment/root/AndroidManifest.xml.ftl | 0
.../root/res/layout/fragment_plus_one.xml.ftl | 0
.../root/src/app_package/PlusOneFragment.java.ftl | 147 +
templates/other/PlusOneFragment/template.xml | 45 +
.../PlusOneFragment/templates_plusone_fragment.png | Bin 0 -> 5623 bytes
templates/other/ResFolder/recipe.xml.ftl | 12 +
.../other/ResFolder/root/build.gradle.ftl | 0
templates/other/ResFolder/template.xml | 30 +
templates/other/ResourcesFolder/recipe.xml.ftl | 12 +
.../other/ResourcesFolder/root/build.gradle.ftl | 0
templates/other/ResourcesFolder/template.xml | 30 +
templates/other/RsFolder/recipe.xml.ftl | 12 +
.../other/RsFolder/root/build.gradle.ftl | 0
templates/other/RsFolder/template.xml | 30 +
.../other/Service/globals.xml.ftl | 0
templates/other/Service/recipe.xml.ftl | 8 +
.../other/Service/root/AndroidManifest.xml.ftl | 0
.../Service/root/src/app_package/Service.java.ftl | 0
templates/other/Service/template.xml | 34 +
templates/other/ValueResourceFile/recipe.xml.ftl | 7 +
.../ValueResourceFile/root/res/values.xml.ftl | 0
templates/other/ValueResourceFile/template.xml | 21 +
templates/other/WatchFaceService/analog_round.png | Bin 0 -> 13214 bytes
templates/other/WatchFaceService/analog_square.png | Bin 0 -> 5478 bytes
templates/other/WatchFaceService/digital_round.png | Bin 0 -> 12923 bytes
.../other/WatchFaceService/digital_square.png | Bin 0 -> 5286 bytes
templates/other/WatchFaceService/globals.xml.ftl | 4 +
templates/other/WatchFaceService/recipe.xml.ftl | 46 +
.../WatchFaceService/root/AndroidManifest.xml.ftl | 42 +
.../root/AndroidManifestPermissions.xml | 7 +
.../other/WatchFaceService/root/build.gradle.ftl | 0
.../root/res/drawable-nodpi/preview_analog.png | Bin
.../root/res/drawable-nodpi/preview_digital.png | Bin
.../drawable-nodpi/preview_digital_circular.png | Bin
.../root/res/values/colors.xml.ftl | 12 +
.../root/res/values/dimens.xml.ftl | 0
.../root/res/values/strings.xml.ftl | 0
.../WatchFaceService/root/res/xml/watch_face.xml | 0
.../app_package/MyAnalogWatchFaceService.java.ftl | 304 ++
.../app_package/MyDigitalWatchFaceService.java.ftl | 308 ++
templates/other/WatchFaceService/template.xml | 56 +
.../other/WatchFaceService/template_thumbnail.png | Bin
{base/testutils => testutils}/.classpath | 0
{base/testutils => testutils}/.gitignore | 0
{base/testutils => testutils}/.project | 0
.../.settings/org.eclipse.jdt.core.prefs | 0
{base/testutils => testutils}/NOTICE | 0
{base/testutils => testutils}/build.gradle | 0
.../java/com/android/testutils/SdkTestCase.java | 553 ++++
.../main/java/com/android/testutils/TestUtils.java | 0
{base/testutils => testutils}/src/test/.classpath | 0
{base/testutils => testutils}/src/test/.project | 0
.../com/android/testutils/SdkTestCaseTest.java | 0
{base/testutils => testutils}/testutils.iml | 0
vector-drawable-tool/build.gradle | 14 +
.../vectordrawable/VdCommandLineOptions.java | 215 ++
.../common/vectordrawable/VdCommandLineTool.java | 263 ++
.../vectordrawable/VdCommandLineOptionsTest.java | 139 +
.../testData/vectordrawable/ic_shapes.svg | 0
vector-drawable-tool/vector-drawable-tool.iml | 15 +
13139 files changed, 403895 insertions(+), 313288 deletions(-)
diff --git a/adt.iml b/adt.iml
new file mode 100644
index 0000000..0ff3f24
--- /dev/null
+++ b/adt.iml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/.eclipse" />
+ <excludeFolder url="file://$MODULE_DIR$/.idea" />
+ <excludeFolder url="file://$MODULE_DIR$/apps" />
+ <excludeFolder url="file://$MODULE_DIR$/asset-studio/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ <excludeFolder url="file://$MODULE_DIR$/common/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/ddmlib/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/files" />
+ <excludeFolder url="file://$MODULE_DIR$/jobb" />
+ <excludeFolder url="file://$MODULE_DIR$/layoutlib-api/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/manifest-merger/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/misc" />
+ <excludeFolder url="file://$MODULE_DIR$/rule-api/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/rule-api/bin" />
+ <excludeFolder url="file://$MODULE_DIR$/sdk-common/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/sdklib/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/sdkstats/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/templates" />
+ <excludeFolder url="file://$MODULE_DIR$/testutils/.settings" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="android" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/base/annotations/.classpath b/annotations/.classpath
similarity index 100%
rename from base/annotations/.classpath
rename to annotations/.classpath
diff --git a/base/annotations/.gitignore b/annotations/.gitignore
similarity index 100%
rename from base/annotations/.gitignore
rename to annotations/.gitignore
diff --git a/base/annotations/.project b/annotations/.project
similarity index 100%
rename from base/annotations/.project
rename to annotations/.project
diff --git a/base/annotations/NOTICE b/annotations/NOTICE
similarity index 100%
rename from base/annotations/NOTICE
rename to annotations/NOTICE
diff --git a/base/annotations/android-annotations.iml b/annotations/android-annotations.iml
similarity index 100%
rename from base/annotations/android-annotations.iml
rename to annotations/android-annotations.iml
diff --git a/annotations/build.gradle b/annotations/build.gradle
new file mode 100644
index 0000000..72ffb9f
--- /dev/null
+++ b/annotations/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'java'
+apply plugin: 'sdk-java-lib'
+
+sourceCompatibility = JavaVersion.VERSION_1_6
+targetCompatibility = JavaVersion.VERSION_1_6
+
+group = 'com.android.tools'
+archivesBaseName = 'annotations'
+version = rootProject.ext.baseVersion
+
+project.ext.pomName = 'Android Tools Annotations library'
+project.ext.pomDesc = 'annotations used throughout the Android tools libraries.'
+
+javadoc {
+ if (JavaVersion.current().isJava8Compatible()) {
+ options.addStringOption 'Xdoclint:none', '-quiet'
+ }
+}
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/base/annotations/src/main/java/com/android/annotations/NonNull.java b/annotations/src/main/java/com/android/annotations/NonNull.java
similarity index 100%
rename from base/annotations/src/main/java/com/android/annotations/NonNull.java
rename to annotations/src/main/java/com/android/annotations/NonNull.java
diff --git a/base/annotations/src/main/java/com/android/annotations/NonNullByDefault.java b/annotations/src/main/java/com/android/annotations/NonNullByDefault.java
similarity index 100%
rename from base/annotations/src/main/java/com/android/annotations/NonNullByDefault.java
rename to annotations/src/main/java/com/android/annotations/NonNullByDefault.java
diff --git a/base/annotations/src/main/java/com/android/annotations/Nullable.java b/annotations/src/main/java/com/android/annotations/Nullable.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/annotations/src/main/java/com/android/annotations/Nullable.java
rename to annotations/src/main/java/com/android/annotations/Nullable.java
diff --git a/base/annotations/src/main/java/com/android/annotations/VisibleForTesting.java b/annotations/src/main/java/com/android/annotations/VisibleForTesting.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/annotations/src/main/java/com/android/annotations/VisibleForTesting.java
rename to annotations/src/main/java/com/android/annotations/VisibleForTesting.java
diff --git a/base/annotations/src/main/java/com/android/annotations/concurrency/GuardedBy.java b/annotations/src/main/java/com/android/annotations/concurrency/GuardedBy.java
similarity index 100%
rename from base/annotations/src/main/java/com/android/annotations/concurrency/GuardedBy.java
rename to annotations/src/main/java/com/android/annotations/concurrency/GuardedBy.java
diff --git a/base/annotations/src/main/java/com/android/annotations/concurrency/Immutable.java b/annotations/src/main/java/com/android/annotations/concurrency/Immutable.java
similarity index 100%
rename from base/annotations/src/main/java/com/android/annotations/concurrency/Immutable.java
rename to annotations/src/main/java/com/android/annotations/concurrency/Immutable.java
diff --git a/base/apps/.gitignore b/apps/.gitignore
similarity index 100%
rename from base/apps/.gitignore
rename to apps/.gitignore
diff --git a/apps/DeviceConfig/.gitignore b/apps/DeviceConfig/.gitignore
new file mode 100644
index 0000000..2816b58
--- /dev/null
+++ b/apps/DeviceConfig/.gitignore
@@ -0,0 +1,6 @@
+.gradle
+/local.properties
+/.idea
+*.iml
+.DS_Store
+/build
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/module_ignore b/apps/DeviceConfig/app/.gitignore
similarity index 100%
copy from base/templates/gradle-projects/AndroidWearModule/root/module_ignore
copy to apps/DeviceConfig/app/.gitignore
diff --git a/apps/DeviceConfig/app/build.gradle b/apps/DeviceConfig/app/build.gradle
new file mode 100644
index 0000000..d934752
--- /dev/null
+++ b/apps/DeviceConfig/app/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
+
+ defaultConfig {
+ applicationId "com.example.android.deviceconfig"
+ minSdkVersion 8
+ targetSdkVersion 23
+ }
+}
diff --git a/apps/DeviceConfig/app/src/main/AndroidManifest.xml b/apps/DeviceConfig/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c6eedaf
--- /dev/null
+++ b/apps/DeviceConfig/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.deviceconfig"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-feature android:name="android.hardware.camera" />
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="15" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MyActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/apps/DeviceConfig/app/src/main/java/com/example/android/deviceconfig/ConfigGenerator.java b/apps/DeviceConfig/app/src/main/java/com/example/android/deviceconfig/ConfigGenerator.java
new file mode 100644
index 0000000..b07f96e
--- /dev/null
+++ b/apps/DeviceConfig/app/src/main/java/com/example/android/deviceconfig/ConfigGenerator.java
@@ -0,0 +1,893 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.deviceconfig;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.os.Build;
+import android.os.Environment;
+import android.os.StatFs;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class ConfigGenerator {
+ private Context mCtx;
+ private String mExtensions;
+
+ public static final String NS_DEVICES_XSD = "http://schemas.android.com/sdk/devices/1";
+
+ /**
+ * The "devices" element is the root element of this schema.
+ *
+ * It must contain one or more "device" elements that each define the
+ * hardware, software, and states for a given device.
+ */
+ public static final String NODE_DEVICES = "devices";
+
+ /**
+ * A "device" element contains a "hardware" element, a "software" element
+ * for each API version it supports, and a "state" element for each possible
+ * state the device could be in.
+ */
+ public static final String NODE_DEVICE = "device";
+
+ /**
+ * The "hardware" element contains all of the hardware information for a
+ * given device.
+ */
+ public static final String NODE_HARDWARE = "hardware";
+
+ /**
+ * The "software" element contains all of the software information for an
+ * API version of the given device.
+ */
+ public static final String NODE_SOFTWARE = "software";
+
+ /**
+ * The "state" element contains all of the parameters for a given state of
+ * the device. It's also capable of redefining hardware configurations if
+ * they change based on state.
+ */
+
+ public static final String NODE_STATE = "state";
+
+ public static final String NODE_KEYBOARD = "keyboard";
+ public static final String NODE_TOUCH = "touch";
+ public static final String NODE_GL_EXTENSIONS = "gl-extensions";
+ public static final String NODE_GL_VERSION = "gl-version";
+ public static final String NODE_NETWORKING = "networking";
+ public static final String NODE_REMOVABLE_STORAGE = "removable-storage";
+ public static final String NODE_FLASH = "flash";
+ public static final String NODE_LIVE_WALLPAPER_SUPPORT = "live-wallpaper-support";
+ public static final String NODE_BUTTONS = "buttons";
+ public static final String NODE_CAMERA = "camera";
+ public static final String NODE_LOCATION = "location";
+ public static final String NODE_GPU = "gpu";
+ public static final String NODE_DOCK = "dock";
+ public static final String NODE_YDPI = "ydpi";
+ public static final String NODE_POWER_TYPE = "power-type";
+ public static final String NODE_Y_DIMENSION = "y-dimension";
+ public static final String NODE_SCREEN_RATIO = "screen-ratio";
+ public static final String NODE_NAV_STATE = "nav-state";
+ public static final String NODE_MIC = "mic";
+ public static final String NODE_RAM = "ram";
+ public static final String NODE_XDPI = "xdpi";
+ public static final String NODE_DIMENSIONS = "dimensions";
+ public static final String NODE_ABI = "abi";
+ public static final String NODE_MECHANISM = "mechanism";
+ public static final String NODE_MULTITOUCH = "multitouch";
+ public static final String NODE_NAV = "nav";
+ public static final String NODE_PIXEL_DENSITY = "pixel-density";
+ public static final String NODE_SCREEN_ORIENTATION = "screen-orientation";
+ public static final String NODE_AUTOFOCUS = "autofocus";
+ public static final String NODE_SCREEN_SIZE = "screen-size";
+ public static final String NODE_DESCRIPTION = "description";
+ public static final String NODE_BLUETOOTH_PROFILES = "bluetooth-profiles";
+ public static final String NODE_SCREEN = "screen";
+ public static final String NODE_SENSORS = "sensors";
+ public static final String NODE_STATUS_BAR = "status-bar";
+ public static final String NODE_DIAGONAL_LENGTH = "diagonal-length";
+ public static final String NODE_SCREEN_TYPE = "screen-type";
+ public static final String NODE_KEYBOARD_STATE = "keyboard-state";
+ public static final String NODE_X_DIMENSION = "x-dimension";
+ public static final String NODE_CPU = "cpu";
+ public static final String NODE_INTERNAL_STORAGE = "internal-storage";
+ public static final String NODE_NAME = "name";
+ public static final String NODE_ID = "id";
+ public static final String NODE_SKIN = "skin";
+ public static final String NODE_MANUFACTURER = "manufacturer";
+ public static final String NODE_API_LEVEL = "api-level";
+ public static final String ATTR_DEFAULT = "default";
+ public static final String ATTR_UNIT = "unit";
+ public static final String UNIT_BYTES = "B";
+ public static final String UNIT_KIBIBYTES = "KiB";
+ public static final String UNIT_MEBIBYTES = "MiB";
+ public static final String UNIT_GIBIBYTES = "GiB";
+ public static final String UNIT_TEBIBYTES = "TiB";
+ public static final String LOCAL_NS = "d";
+ public static final String PREFIX = LOCAL_NS + ":";
+
+ private static final String TAG = "ConfigGenerator";
+
+ public ConfigGenerator(Context context, String extensions) {
+ mCtx = context;
+ mExtensions = extensions;
+ }
+
+ public static int getScreenWidth(WindowManager windowManager, DisplayMetrics metrics) {
+ Display display = windowManager.getDefaultDisplay();
+ if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17) {
+ try {
+ return (Integer) Display.class.getMethod("getRawWidth").invoke(display);
+ } catch (Exception ignored) {
+ }
+ } else if (Build.VERSION.SDK_INT >= 17) {
+ try {
+ Point realSize = new Point();
+ Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
+ return realSize.x;
+ } catch (Exception ignored) {
+ }
+ }
+
+ display.getMetrics(metrics);
+ return metrics.widthPixels;
+ }
+
+
+ public static int getScreenHeight(WindowManager windowManager, DisplayMetrics metrics) {
+ Display display = windowManager.getDefaultDisplay();
+ if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17) {
+ try {
+ return (Integer) Display.class.getMethod("getRawHeight").invoke(display);
+ } catch (Exception ignored) {
+ }
+ } else if (Build.VERSION.SDK_INT >= 17) {
+ try {
+ Point realSize = new Point();
+ Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
+ return realSize.y;
+ } catch (Exception ignored) {
+ }
+ }
+
+ display.getMetrics(metrics);
+ return metrics.heightPixels;
+ }
+
+ @SuppressLint("WorldReadableFiles")
+ public String generateConfig() {
+ Resources resources = mCtx.getResources();
+ PackageManager packageMgr = mCtx.getPackageManager();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+ Configuration config = resources.getConfiguration();
+
+ WindowManager wm = (WindowManager) mCtx.getSystemService(Context.WINDOW_SERVICE);
+ int screenWidth = getScreenWidth(wm, metrics);
+ int screenHeight = getScreenHeight(wm, metrics);
+
+ try {
+ Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+
+ Element devices = doc.createElement(PREFIX + NODE_DEVICES);
+ devices.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":xsi",
+ XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
+ devices.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + LOCAL_NS, NS_DEVICES_XSD);
+ doc.appendChild(devices);
+
+ Element device = doc.createElement(PREFIX + NODE_DEVICE);
+ devices.appendChild(device);
+
+ Element name = doc.createElement(PREFIX + NODE_NAME);
+ device.appendChild(name);
+ name.appendChild(doc.createTextNode(android.os.Build.MODEL));
+
+ Element id = doc.createElement(PREFIX + NODE_ID);
+ device.appendChild(id);
+ id.appendChild(doc.createTextNode(android.os.Build.MODEL));
+
+ Element manufacturer = doc.createElement(PREFIX + NODE_MANUFACTURER);
+ device.appendChild(manufacturer);
+ manufacturer.appendChild(doc.createTextNode(android.os.Build.MANUFACTURER));
+
+ Element hardware = doc.createElement(PREFIX + NODE_HARDWARE);
+ device.appendChild(hardware);
+
+ Element screen = doc.createElement(PREFIX + NODE_SCREEN);
+ hardware.appendChild(screen);
+
+ Element screenSize = doc.createElement(PREFIX + NODE_SCREEN_SIZE);
+ screen.appendChild(screenSize);
+ Text screenSizeText;
+ switch (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) {
+ case Configuration.SCREENLAYOUT_SIZE_SMALL:
+ screenSizeText = doc.createTextNode("small");
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_NORMAL:
+ screenSizeText = doc.createTextNode("normal");
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_LARGE:
+ screenSizeText = doc.createTextNode("large");
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_XLARGE:
+ screenSizeText = doc.createTextNode("xlarge");
+ break;
+ default:
+ screenSizeText = doc.createTextNode(" ");
+ break;
+ }
+ screenSize.appendChild(screenSizeText);
+
+ Element diagonalLength = doc.createElement(PREFIX + NODE_DIAGONAL_LENGTH);
+ screen.appendChild(diagonalLength);
+ double xin = screenWidth / metrics.xdpi;
+ double yin = screenHeight / metrics.ydpi;
+
+ double diag = Math.sqrt(Math.pow(xin, 2) + Math.pow(yin, 2));
+ diagonalLength.appendChild(doc.createTextNode(
+ String.format(Locale.US, "%1$.2f", diag)));
+
+ Element pixelDensity = doc.createElement(PREFIX + NODE_PIXEL_DENSITY);
+ screen.appendChild(pixelDensity);
+ Text pixelDensityText;
+ switch (metrics.densityDpi) {
+ case DisplayMetrics.DENSITY_LOW:
+ pixelDensityText = doc.createTextNode("ldpi");
+ break;
+ case DisplayMetrics.DENSITY_MEDIUM:
+ pixelDensityText = doc.createTextNode("mdpi");
+ break;
+ case DisplayMetrics.DENSITY_TV:
+ pixelDensityText = doc.createTextNode("tvdpi");
+ break;
+ case DisplayMetrics.DENSITY_HIGH:
+ pixelDensityText = doc.createTextNode("hdpi");
+ break;
+ case DisplayMetrics.DENSITY_XHIGH:
+ pixelDensityText = doc.createTextNode("xhdpi");
+ break;
+ case DisplayMetrics.DENSITY_XXHIGH:
+ pixelDensityText = doc.createTextNode("xxhdpi");
+ break;
+ case DisplayMetrics.DENSITY_XXXHIGH:
+ pixelDensityText = doc.createTextNode("xxxhdpi");
+ break;
+ case DisplayMetrics.DENSITY_280:
+ pixelDensityText = doc.createTextNode("280dpi");
+ break;
+ case DisplayMetrics.DENSITY_360:
+ pixelDensityText = doc.createTextNode("360dpi");
+ break;
+ case DisplayMetrics.DENSITY_400:
+ pixelDensityText = doc.createTextNode("400dpi");
+ break;
+ case DisplayMetrics.DENSITY_420:
+ pixelDensityText = doc.createTextNode("420dpi");
+ break;
+ case DisplayMetrics.DENSITY_560:
+ pixelDensityText = doc.createTextNode("560dpi");
+ break;
+ default:
+ pixelDensityText = doc.createTextNode(" ");
+ }
+ pixelDensity.appendChild(pixelDensityText);
+
+ Element screenRatio = doc.createElement(PREFIX + NODE_SCREEN_RATIO);
+ screen.appendChild(screenRatio);
+ Text screenRatioText;
+ switch (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) {
+ case Configuration.SCREENLAYOUT_LONG_YES:
+ screenRatioText = doc.createTextNode("long");
+ break;
+ case Configuration.SCREENLAYOUT_LONG_NO:
+ screenRatioText = doc.createTextNode("notlong");
+ break;
+ default:
+ screenRatioText = doc.createTextNode(" ");
+ break;
+ }
+ screenRatio.appendChild(screenRatioText);
+
+ Element dimensions = doc.createElement(PREFIX + NODE_DIMENSIONS);
+ screen.appendChild(dimensions);
+
+ Element xDimension = doc.createElement(PREFIX + NODE_X_DIMENSION);
+ dimensions.appendChild(xDimension);
+ xDimension.appendChild(doc.createTextNode(Integer.toString(screenWidth)));
+
+ Element yDimension = doc.createElement(PREFIX + NODE_Y_DIMENSION);
+ dimensions.appendChild(yDimension);
+ yDimension.appendChild(doc.createTextNode(Integer.toString(screenHeight)));
+
+ Element xdpi = doc.createElement(PREFIX + NODE_XDPI);
+ screen.appendChild(xdpi);
+ xdpi.appendChild(doc.createTextNode(Double.toString(metrics.xdpi)));
+
+ Element ydpi = doc.createElement(PREFIX + NODE_YDPI);
+ screen.appendChild(ydpi);
+ ydpi.appendChild(doc.createTextNode(Double.toString(metrics.ydpi)));
+
+ Element touch = doc.createElement(PREFIX + NODE_TOUCH);
+ screen.appendChild(touch);
+
+ Element multitouch = doc.createElement(PREFIX + NODE_MULTITOUCH);
+ touch.appendChild(multitouch);
+ Text multitouchText;
+ if (packageMgr
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND)) {
+ multitouchText = doc.createTextNode("jazz-hands");
+ } else if (packageMgr
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
+ multitouchText = doc.createTextNode("distinct");
+ } else if (packageMgr.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) {
+ multitouchText = doc.createTextNode("basic");
+ } else {
+ multitouchText = doc.createTextNode("none");
+ }
+ multitouch.appendChild(multitouchText);
+
+ Element mechanism = doc.createElement(PREFIX + NODE_MECHANISM);
+ touch.appendChild(mechanism);
+ Text mechanismText;
+ switch (config.touchscreen) {
+ case Configuration.TOUCHSCREEN_STYLUS:
+ mechanismText = doc.createTextNode("stylus");
+ case Configuration.TOUCHSCREEN_FINGER:
+ mechanismText = doc.createTextNode("finger");
+ case Configuration.TOUCHSCREEN_NOTOUCH:
+ mechanismText = doc.createTextNode("notouch");
+ default:
+ mechanismText = doc.createTextNode("TODO:typically \"finger\"");
+ }
+ mechanism.appendChild(mechanismText);
+
+ // Create an empty place holder node for screen-type since we can't
+ // actually determine it
+
+ Element screenType = doc.createElement(PREFIX + NODE_SCREEN_TYPE);
+ touch.appendChild(screenType);
+ screenType.appendChild(doc.createTextNode("TODO:typically \"capacitive\""));
+
+ Element networking = doc.createElement(PREFIX + NODE_NETWORKING);
+ hardware.appendChild(networking);
+ Text networkingText = doc.createTextNode("");
+ networking.appendChild(networkingText);
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ networkingText.appendData("\nWifi");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
+ networkingText.appendData("\nBluetooth");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_NFC)) {
+ networkingText.appendData("\nNFC");
+ }
+ networkingText.appendData("\n");
+
+ Element sensors = doc.createElement(PREFIX + NODE_SENSORS);
+ hardware.appendChild(sensors);
+ Text sensorsText = doc.createTextNode("");
+ sensors.appendChild(sensorsText);
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)) {
+ sensorsText.appendData("\nAccelerometer");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_BAROMETER)) {
+ sensorsText.appendData("\nBarometer");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_COMPASS)) {
+ sensorsText.appendData("\nCompass");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) {
+ sensorsText.appendData("\nGPS");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_GYROSCOPE)) {
+ sensorsText.appendData("\nGyroscope");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT)) {
+ sensorsText.appendData("\nLightSensor");
+ }
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_PROXIMITY)) {
+ sensorsText.appendData("\nProximitySensor");
+ }
+ sensorsText.appendData("\n");
+
+ Element mic = doc.createElement(PREFIX + NODE_MIC);
+ hardware.appendChild(mic);
+ Text micText;
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
+ micText = doc.createTextNode("true");
+ } else {
+ micText = doc.createTextNode("false");
+ }
+ mic.appendChild(micText);
+
+ if (android.os.Build.VERSION.SDK_INT >= 9){
+ List<Element> cameras = getCameraElements(doc);
+ for (Element cam : cameras){
+ hardware.appendChild(cam);
+ }
+ } else {
+ Camera c = Camera.open();
+ Element camera = doc.createElement(PREFIX + NODE_CAMERA);
+ hardware.appendChild(camera);
+ Element location = doc.createElement(PREFIX + NODE_LOCATION);
+ camera.appendChild(location);
+ // All camera's before API 9 were on the back
+ location.appendChild(doc.createTextNode("back"));
+ Camera.Parameters cParams = c.getParameters();
+ Element autofocus = doc.createElement(PREFIX + NODE_AUTOFOCUS);
+ camera.appendChild(autofocus);
+ List<String> foci = cParams.getSupportedFocusModes();
+ if (foci == null) {
+ autofocus.appendChild(doc.createTextNode(" "));
+ } else if (foci.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
+ autofocus.appendChild(doc.createTextNode("true"));
+ } else {
+ autofocus.appendChild(doc.createTextNode("false"));
+ }
+
+ Element flash = doc.createElement(PREFIX + NODE_FLASH);
+ camera.appendChild(flash);
+ List<String> flashes = cParams.getSupportedFlashModes();
+ if (flashes == null || !flashes.contains(Camera.Parameters.FLASH_MODE_ON)) {
+ flash.appendChild(doc.createTextNode("false"));
+ } else {
+ flash.appendChild(doc.createTextNode("true"));
+ }
+ c.release();
+ }
+
+
+ Element keyboard = doc.createElement(PREFIX + NODE_KEYBOARD);
+ hardware.appendChild(keyboard);
+ Text keyboardText;
+ switch (config.keyboard) {
+ case Configuration.KEYBOARD_NOKEYS:
+ keyboardText = doc.createTextNode("nokeys");
+ break;
+ case Configuration.KEYBOARD_12KEY:
+ keyboardText = doc.createTextNode("12key");
+ break;
+ case Configuration.KEYBOARD_QWERTY:
+ keyboardText = doc.createTextNode("qwerty");
+ break;
+ default:
+ keyboardText = doc.createTextNode(" ");
+ }
+ keyboard.appendChild(keyboardText);
+
+ Element nav = doc.createElement(PREFIX + NODE_NAV);
+ hardware.appendChild(nav);
+ Text navText;
+ switch (config.navigation) {
+ case Configuration.NAVIGATION_DPAD:
+ navText = doc.createTextNode("dpad");
+ case Configuration.NAVIGATION_TRACKBALL:
+ navText = doc.createTextNode("trackball");
+ case Configuration.NAVIGATION_WHEEL:
+ navText = doc.createTextNode("wheel");
+ case Configuration.NAVIGATION_NONAV:
+ navText = doc.createTextNode("nonav");
+ default:
+ navText = doc.createTextNode("TODO:typically \"nonav\"");
+ }
+ nav.appendChild(navText);
+
+ Element ram = doc.createElement(PREFIX + NODE_RAM);
+ hardware.appendChild(ram);
+
+ ActivityManager actManager = (ActivityManager) mCtx.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
+ actManager.getMemoryInfo(memInfo);
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ long totalMemory = memInfo.totalMem;
+ long ramAmount = totalMemory / (1024 * 1024);
+ ram.setAttribute(ATTR_UNIT, UNIT_MEBIBYTES);
+ ram.appendChild(doc.createTextNode(Long.toString(ramAmount)));
+ } else {
+ // totalMemory given in bytes, divide by 1048576 to get RAM in MiB
+ String line;
+ long ramAmount = 0;
+ String unit = UNIT_BYTES;
+ try {
+ BufferedReader meminfo = new BufferedReader(new FileReader("/proc/meminfo"));
+ while ((line = meminfo.readLine()) != null) {
+ String[] vals = line.split("[\\s]+");
+ if (vals[0].equals("MemTotal:")) {
+ try {
+ /*
+ * We're going to want it as a string eventually,
+ * but parsing it lets us validate it's actually a
+ * number and something strange isn't going on
+ */
+ ramAmount = Long.parseLong(vals[1]);
+ unit = vals[2];
+ break;
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ }
+ meminfo.close();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ }
+ if (ramAmount > 0) {
+ if (unit.equals("B")) {
+ unit = UNIT_BYTES;
+ } else if (unit.equals("kB")) {
+ unit = UNIT_KIBIBYTES;
+ } else if (unit.equals("MB")) {
+ unit = UNIT_MEBIBYTES;
+ } else if (unit.equals("GB")) {
+ unit = UNIT_GIBIBYTES;
+ } else {
+ unit = " ";
+ }
+ }
+ ram.setAttribute(ATTR_UNIT, unit);
+ ram.appendChild(doc.createTextNode(Long.toString(ramAmount)));
+ }
+
+ Element buttons = doc.createElement(PREFIX + NODE_BUTTONS);
+ hardware.appendChild(buttons);
+ Text buttonsText;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ buttonsText = doc.createTextNode(getButtonsType());
+ } else {
+ buttonsText = doc.createTextNode("hard");
+ }
+ buttons.appendChild(buttonsText);
+
+
+
+
+
+ long externalTotal;
+ long internalTotal;
+ StatFs internalStatFs = new StatFs( Environment.getRootDirectory().getAbsolutePath() );
+ StatFs externalStatFs = new StatFs( Environment.getExternalStorageDirectory().getAbsolutePath() );
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ internalTotal = (internalStatFs.getBlockCountLong() * internalStatFs.getBlockSizeLong()) / (1024 * 1024);
+ externalTotal = (externalStatFs.getBlockCountLong() * externalStatFs.getBlockSizeLong()) / (1024 * 1024);
+ } else {
+ internalTotal = ((long) internalStatFs.getBlockCount() * (long) internalStatFs.getBlockSize()) / (1024 * 1024);
+ externalTotal = ((long) externalStatFs.getBlockCount() * (long) externalStatFs.getBlockSize()) / (1024 * 1024);
+ }
+
+ Element internalStorage = doc.createElement(PREFIX + NODE_INTERNAL_STORAGE);
+ hardware.appendChild(internalStorage);
+ internalStorage.appendChild(doc.createTextNode(Long.toString(internalTotal)));
+ internalStorage.setAttribute(ATTR_UNIT, UNIT_MEBIBYTES);
+
+ Element externalStorage = doc.createElement(PREFIX + NODE_REMOVABLE_STORAGE);
+ hardware.appendChild(externalStorage);
+ externalStorage.appendChild(doc.createTextNode(Long.toString(externalTotal)));
+ externalStorage.setAttribute(ATTR_UNIT, UNIT_MEBIBYTES);
+
+ // Don't know CPU, GPU types
+ Element cpu = doc.createElement(PREFIX + NODE_CPU);
+ hardware.appendChild(cpu);
+
+ /* CPU
+ adb shell cat /proc/cpuinfo | grep "model name"
+ */
+ String cpuName = null;
+ try {
+ String line;
+ BufferedReader cpuinfo = new BufferedReader(new FileReader("/proc/cpuinfo"));
+ while ((line = cpuinfo.readLine()) != null) {
+ if (line.startsWith("model name") || line.startsWith("Processor")) {
+ int index = line.indexOf(':');
+ if (index != -1) {
+ cpuName = line.substring(index + 1).trim();
+ break;
+ }
+ }
+ }
+ cpuinfo.close();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ }
+ if (cpuName != null) {
+ cpu.appendChild(doc.createTextNode(cpuName));
+ } else {
+ cpu.appendChild(doc.createTextNode("TODO"));
+ }
+
+ Element gpu = doc.createElement(PREFIX + NODE_GPU);
+ hardware.appendChild(gpu);
+
+ String gpuName = null;
+ try {
+ String line;
+ // This doesn't work; presumably because apps can't hold the dump
+ // permission.
+ ProcessBuilder processBuilder = new ProcessBuilder("/system/bin/dumpsys");
+ Process process = processBuilder.start();
+ process.waitFor();
+ InputStream inputStream = process.getInputStream();
+ BufferedReader gpuInfo = new BufferedReader(new InputStreamReader(inputStream,
+ Charset.forName("UTF-8")));
+ while ((line = gpuInfo.readLine()) != null) {
+ if (line.startsWith("GLES:")) {
+ int index = line.indexOf(':');
+ if (index != -1) {
+ gpuName = line.substring(index + 1).trim();
+ break;
+ }
+ }
+ }
+ gpuInfo.close();
+ } catch (FileNotFoundException | InterruptedException ignore) {
+ // Ignore
+ }
+ if (gpuName != null) {
+ gpu.appendChild(doc.createTextNode(gpuName));
+ } else {
+ gpu.appendChild(doc.createTextNode("TODO"));
+ }
+
+ Element abi = doc.createElement(PREFIX + NODE_ABI);
+ hardware.appendChild(abi);
+ Text abiText = doc.createTextNode("");
+ abi.appendChild(abiText);
+ if (Build.VERSION.SDK_INT >= 21) {
+ for (String abiName : Build.SUPPORTED_ABIS) {
+ abiText.appendData("\n" + abiName);
+ }
+ abiText.appendData("\n");
+ } else {
+ abiText.appendData("\n" + android.os.Build.CPU_ABI);
+ abiText.appendData("\n" + android.os.Build.CPU_ABI2);
+ abiText.appendData("\n");
+ }
+
+ // Don't know about either the dock or plugged-in element
+ Element dock = doc.createElement(PREFIX + NODE_DOCK);
+ hardware.appendChild(dock);
+ dock.appendChild(doc.createTextNode(" "));
+
+ Element pluggedIn = doc.createElement(PREFIX + NODE_POWER_TYPE);
+ hardware.appendChild(pluggedIn);
+ pluggedIn.appendChild(doc.createTextNode("battery"));
+
+ Element skin = doc.createElement(PREFIX + NODE_SKIN);
+ hardware.appendChild(skin);
+ skin.appendChild(doc.createTextNode(android.os.Build.MODEL.toLowerCase().replace(' ', '_'));
+
+ Element software = doc.createElement(PREFIX + NODE_SOFTWARE);
+ device.appendChild(software);
+
+ Element apiLevel = doc.createElement(PREFIX + NODE_API_LEVEL);
+ software.appendChild(apiLevel);
+ apiLevel.appendChild(doc.createTextNode(Integer
+ .toString(android.os.Build.VERSION.SDK_INT)));
+
+ Element liveWallpaperSupport = doc.createElement(PREFIX + NODE_LIVE_WALLPAPER_SUPPORT);
+ software.appendChild(liveWallpaperSupport);
+ if (packageMgr.hasSystemFeature(PackageManager.FEATURE_LIVE_WALLPAPER)) {
+ liveWallpaperSupport.appendChild(doc.createTextNode("true"));
+ } else {
+ liveWallpaperSupport.appendChild(doc.createTextNode("false"));
+ }
+
+ Element bluetoothProfiles = doc.createElement(PREFIX + NODE_BLUETOOTH_PROFILES);
+ software.appendChild(bluetoothProfiles);
+ bluetoothProfiles.appendChild(doc.createTextNode(" "));
+
+ Element glVersion = doc.createElement(PREFIX + NODE_GL_VERSION);
+ software.appendChild(glVersion);
+ String glVersionString = " ";
+
+ FeatureInfo[] features = packageMgr.getSystemAvailableFeatures();
+ for (FeatureInfo feature : features) {
+ if (feature.reqGlEsVersion > 0) {
+ glVersionString = feature.getGlEsVersion();
+ break;
+ }
+ }
+
+ glVersion.appendChild(doc.createTextNode(glVersionString));
+
+ Element glExtensions = doc.createElement(PREFIX + NODE_GL_EXTENSIONS);
+ software.appendChild(glExtensions);
+ if (mExtensions != null && !mExtensions.trim().equals("")) {
+ glExtensions.appendChild(doc.createTextNode(mExtensions));
+ } else {
+ glExtensions.appendChild(doc.createTextNode(" "));
+ }
+
+ Element statusBar = doc.createElement(PREFIX + NODE_STATUS_BAR);
+ software.appendChild(statusBar);
+ statusBar.appendChild(doc.createTextNode("true"));
+
+ // States
+ Element state = doc.createElement(PREFIX + NODE_STATE);
+ device.appendChild(state);
+ boolean isPhone = screenHeight > screenWidth;
+ state.setAttribute("name", "Portrait");
+ if (isPhone) {
+ state.setAttribute(ATTR_DEFAULT, "true");
+ }
+ Element description = doc.createElement(PREFIX + NODE_DESCRIPTION);
+ state.appendChild(description);
+ description.appendChild(doc.createTextNode("The device in portrait view"));
+ Element orientation = doc.createElement(PREFIX + NODE_SCREEN_ORIENTATION);
+ state.appendChild(orientation);
+ orientation.appendChild(doc.createTextNode("port"));
+ Element keyboardState = doc.createElement(PREFIX + NODE_KEYBOARD_STATE);
+ state.appendChild(keyboardState);
+ keyboardState.appendChild(doc.createTextNode("keyssoft"));
+ Element navState = doc.createElement(PREFIX + NODE_NAV_STATE);
+ state.appendChild(navState);
+ navState.appendChild(doc.createTextNode("nonav"));
+
+ state = doc.createElement(PREFIX + NODE_STATE);
+ device.appendChild(state);
+ state.setAttribute("name", "Landscape");
+ if (!isPhone) {
+ state.setAttribute(ATTR_DEFAULT, "true");
+ }
+ description = doc.createElement(PREFIX + NODE_DESCRIPTION);
+ state.appendChild(description);
+ description.appendChild(doc.createTextNode("The device in landscape view"));
+ orientation = doc.createElement(PREFIX + NODE_SCREEN_ORIENTATION);
+ state.appendChild(orientation);
+ orientation.appendChild(doc.createTextNode("land"));
+ keyboardState = doc.createElement(PREFIX + NODE_KEYBOARD_STATE);
+ state.appendChild(keyboardState);
+ keyboardState.appendChild(doc.createTextNode("keyssoft"));
+ navState = doc.createElement(PREFIX + NODE_NAV_STATE);
+ state.appendChild(navState);
+ navState.appendChild(doc.createTextNode("nonav"));
+
+ Transformer tf = TransformerFactory.newInstance().newTransformer();
+ tf.setOutputProperty(OutputKeys.INDENT, "yes");
+ tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+ DOMSource source = new DOMSource(doc);
+ String filename = String.format("devices_%1$tm_%1$td_%1$ty.xml", Calendar.getInstance()
+ .getTime());
+ //File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ File dir = mCtx.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
+ File outFile = new File(dir, filename);
+ FileOutputStream out = new FileOutputStream(new File(dir, filename));
+ StreamResult result = new StreamResult(out);
+ tf.transform(source, result);
+ out.flush();
+ out.close();
+ return outFile.getAbsolutePath();
+ } catch (ParserConfigurationException e) {
+ error("Parser config exception", e);
+ } catch (TransformerConfigurationException e) {
+ error("Transformer config exception", e);
+ } catch (TransformerFactoryConfigurationError e) {
+ error("TransformerFactory config exception", e);
+ } catch (TransformerException e) {
+ error("Error transforming", e);
+ } catch (IOException e) {
+ error("I/O Error", e);
+ }
+ return null;
+ }
+
+ @TargetApi(9)
+ private List<Element> getCameraElements(Document doc) {
+ List<Element> cList = new ArrayList<Element>();
+ for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
+ Element camera = doc.createElement(PREFIX + NODE_CAMERA);
+ cList.add(camera);
+ Element location = doc.createElement(PREFIX + NODE_LOCATION);
+ camera.appendChild(location);
+ Text locationText;
+ Camera.CameraInfo cInfo = new Camera.CameraInfo();
+ Camera.getCameraInfo(i, cInfo);
+ if (cInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ locationText = doc.createTextNode("front");
+ } else if (cInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
+ locationText = doc.createTextNode("back");
+ } else {
+ locationText = doc.createTextNode(" ");
+ }
+ location.appendChild(locationText);
+
+ Camera c = Camera.open(i);
+ Camera.Parameters cParams = c.getParameters();
+
+ Element autofocus = doc.createElement(PREFIX + NODE_AUTOFOCUS);
+ camera.appendChild(autofocus);
+ List<String> foci = cParams.getSupportedFocusModes();
+ if (foci == null) {
+ autofocus.appendChild(doc.createTextNode(" "));
+ } else if (foci.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
+ autofocus.appendChild(doc.createTextNode("true"));
+ } else {
+ autofocus.appendChild(doc.createTextNode("false"));
+ }
+
+ Element flash = doc.createElement(PREFIX + NODE_FLASH);
+ camera.appendChild(flash);
+ List<String> flashes = cParams.getSupportedFlashModes();
+ if (flashes == null || !flashes.contains(Camera.Parameters.FLASH_MODE_ON)) {
+ flash.appendChild(doc.createTextNode("false"));
+ } else {
+ flash.appendChild(doc.createTextNode("true"));
+ }
+ c.release();
+ }
+ return cList;
+ }
+
+ @TargetApi(14)
+ private String getButtonsType() {
+ ViewConfiguration vConfig = ViewConfiguration.get(mCtx);
+
+ if (vConfig.hasPermanentMenuKey()) {
+ return "hard";
+ } else {
+ return "soft";
+ }
+ }
+
+ private void error(String err, Throwable e) {
+ Toast.makeText(mCtx, "Error Generating Configuration", Toast.LENGTH_SHORT).show();
+ Log.e(TAG, err);
+ Log.e(TAG, e.getLocalizedMessage());
+ }
+}
diff --git a/apps/DeviceConfig/app/src/main/java/com/example/android/deviceconfig/MyActivity.java b/apps/DeviceConfig/app/src/main/java/com/example/android/deviceconfig/MyActivity.java
new file mode 100644
index 0000000..324bc53
--- /dev/null
+++ b/apps/DeviceConfig/app/src/main/java/com/example/android/deviceconfig/MyActivity.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.deviceconfig;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.io.File;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+public class MyActivity extends Activity implements OnClickListener {
+
+ private static GLView mGl;
+
+ /** Called when the activity is first created. */
+ @SuppressLint("SetTextI18n")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+ LinearLayout vg = (LinearLayout) findViewById(R.id.buttonHolder);
+ if (vg == null) {
+ return;
+ }
+
+ // Instantiate a GL surface view so we can get extensions information
+ mGl = new GLView(this);
+ // If we set the layout to be 0, it just won't render
+ ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(1, 1);
+ mGl.setLayoutParams(params);
+ vg.addView(mGl);
+
+ Button btn = (Button) findViewById(R.id.generateConfigButton);
+ btn.setOnClickListener(this);
+ Configuration config = getResources().getConfiguration();
+
+ TextView tv = (TextView) findViewById(R.id.keyboard_state_api);
+ if (tv != null) {
+ String separator = config.orientation == Configuration.ORIENTATION_PORTRAIT ? "\n" : "";
+ String foo = "keyboardHidden=" + separator;
+ if (config.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) {
+ foo += "EXPOSED";
+ } else if (config.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
+ foo += "HIDDEN";
+ } else if (config.keyboardHidden == Configuration.KEYBOARDHIDDEN_UNDEFINED) {
+ foo += "UNDEFINED";
+ } else {
+ foo += "?";
+ }
+ foo += "\nhardKeyboardHidden=" + separator;
+ if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
+ foo = foo + "EXPOSED";
+ } else if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ foo = foo + "HIDDEN";
+ } else if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_UNDEFINED) {
+ foo = foo + "UNDEFINED";
+ } else {
+ foo = "?";
+ }
+
+ tv.setText(foo);
+ }
+
+ tv = (TextView) findViewById(R.id.nav_state_api);
+ if (tv != null) {
+ if (config.navigationHidden == Configuration.NAVIGATIONHIDDEN_NO) {
+ tv.setText("EXPOSED");
+ } else if (config.navigationHidden == Configuration.NAVIGATIONHIDDEN_YES) {
+ tv.setText("HIDDEN");
+ } else if (config.navigationHidden == Configuration.NAVIGATIONHIDDEN_UNDEFINED) {
+ tv.setText("UNDEFINED");
+ } else {
+ tv.setText("??");
+ }
+ }
+
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+
+ tv = (TextView) findViewById(R.id.size_api);
+ if (tv != null) {
+ WindowManager windowManager = getWindowManager();
+ int widthPixels = ConfigGenerator.getScreenWidth(windowManager, metrics);
+ int heightPixels = ConfigGenerator.getScreenHeight(windowManager, metrics);
+ tv.setText(widthPixels + "x" + heightPixels);
+ }
+
+ tv = (TextView) findViewById(R.id.xdpi);
+ if (tv != null) {
+ tv.setText(String.format("%f", metrics.xdpi));
+ }
+ tv = (TextView) findViewById(R.id.ydpi);
+ if (tv != null) {
+ tv.setText(String.format("%f", metrics.ydpi));
+ }
+
+ tv = (TextView) findViewById(R.id.scaled_density);
+ if (tv != null) {
+ tv.setText(String.format("%f", metrics.scaledDensity));
+ }
+
+ tv = (TextView) findViewById(R.id.font_scale);
+ if (tv != null) {
+ tv.setText(String.format("%f", config.fontScale));
+ }
+ }
+
+ public void onClick(View v) {
+ ConfigGenerator configGen = new ConfigGenerator(this, mGl.getExtensions());
+ final String filename = configGen.generateConfig();
+ if (filename != null) {
+ Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
+ emailIntent.setType("text/xml");
+ File devicesXml = new File(filename);
+ emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Device XML: " + devicesXml.getName());
+ emailIntent.putExtra(Intent.EXTRA_TEXT, "Note: This is intended to generate a base "
+ + "XML description. After running this, you should double check the generated "
+ + "information and add all of the missing fields.");
+ emailIntent.putExtra(Intent.EXTRA_STREAM,
+ Uri.parse("file://" + devicesXml.getAbsolutePath()));
+ startActivity(emailIntent);
+ }
+ }
+
+ private static class GLView extends GLSurfaceView {
+ private GlRenderer mRenderer;
+
+ public GLView(Context context) {
+ super(context);
+ setEGLContextClientVersion(2);
+ mRenderer = new GlRenderer();
+ setRenderer(mRenderer);
+ requestRender();
+ }
+
+ public String getExtensions() {
+ return mRenderer.extensions;
+ }
+
+ }
+
+ private static class GlRenderer implements GLSurfaceView.Renderer {
+ public String extensions = "";
+
+ public void onDrawFrame(GL10 gl) {
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ gl.glViewport(0, 0, 0, 0);
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ if (extensions.equals("")) {
+ String extensions10 = gl.glGetString(GL10.GL_EXTENSIONS);
+ if(extensions10 != null) {
+ extensions += extensions10;
+ }
+ }
+ }
+ }
+}
diff --git a/base/apps/DeviceConfig/res/drawable-hdpi/icon.png b/apps/DeviceConfig/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/apps/DeviceConfig/res/drawable-hdpi/icon.png
rename to apps/DeviceConfig/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/apps/DeviceConfig/res/drawable-ldpi/icon.png b/apps/DeviceConfig/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/apps/DeviceConfig/res/drawable-ldpi/icon.png
rename to apps/DeviceConfig/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/apps/DeviceConfig/res/drawable-mdpi/icon.png b/apps/DeviceConfig/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/apps/DeviceConfig/res/drawable-mdpi/icon.png
rename to apps/DeviceConfig/app/src/main/res/drawable-mdpi/icon.png
diff --git a/base/apps/DeviceConfig/res/drawable-nodpi/icon.png b/apps/DeviceConfig/app/src/main/res/drawable-nodpi/icon.png
similarity index 100%
rename from base/apps/DeviceConfig/res/drawable-nodpi/icon.png
rename to apps/DeviceConfig/app/src/main/res/drawable-nodpi/icon.png
diff --git a/base/apps/DeviceConfig/res/drawable/icon.png b/apps/DeviceConfig/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/apps/DeviceConfig/res/drawable/icon.png
rename to apps/DeviceConfig/app/src/main/res/drawable/icon.png
diff --git a/base/apps/DeviceConfig/res/layout/main.xml b/apps/DeviceConfig/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/layout/main.xml
rename to apps/DeviceConfig/app/src/main/res/layout/main.xml
diff --git a/base/apps/DeviceConfig/res/values-12key/strings.xml b/apps/DeviceConfig/app/src/main/res/values-12key/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-12key/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-12key/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-car/strings.xml b/apps/DeviceConfig/app/src/main/res/values-car/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-car/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-car/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-desk/strings.xml b/apps/DeviceConfig/app/src/main/res/values-desk/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-desk/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-desk/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-dpad/strings.xml b/apps/DeviceConfig/app/src/main/res/values-dpad/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-dpad/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-dpad/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-finger/strings.xml b/apps/DeviceConfig/app/src/main/res/values-finger/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-finger/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-finger/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-hdpi/strings.xml b/apps/DeviceConfig/app/src/main/res/values-hdpi/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-hdpi/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-hdpi/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-keysexposed/strings.xml b/apps/DeviceConfig/app/src/main/res/values-keysexposed/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-keysexposed/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-keysexposed/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-keyshidden/strings.xml b/apps/DeviceConfig/app/src/main/res/values-keyshidden/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-keyshidden/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-keyshidden/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-keyssoft/strings.xml b/apps/DeviceConfig/app/src/main/res/values-keyssoft/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-keyssoft/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-keyssoft/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-land/strings.xml b/apps/DeviceConfig/app/src/main/res/values-land/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-land/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-land/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-large/strings.xml b/apps/DeviceConfig/app/src/main/res/values-large/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-large/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-large/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-ldpi/strings.xml b/apps/DeviceConfig/app/src/main/res/values-ldpi/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-ldpi/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-ldpi/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-long/strings.xml b/apps/DeviceConfig/app/src/main/res/values-long/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-long/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-long/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-mdpi/strings.xml b/apps/DeviceConfig/app/src/main/res/values-mdpi/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-mdpi/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-mdpi/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-navexposed/strings.xml b/apps/DeviceConfig/app/src/main/res/values-navexposed/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-navexposed/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-navexposed/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-navhidden/strings.xml b/apps/DeviceConfig/app/src/main/res/values-navhidden/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-navhidden/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-navhidden/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-night/strings.xml b/apps/DeviceConfig/app/src/main/res/values-night/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-night/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-night/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-nodpi/strings.xml b/apps/DeviceConfig/app/src/main/res/values-nodpi/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-nodpi/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-nodpi/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-nokeys/strings.xml b/apps/DeviceConfig/app/src/main/res/values-nokeys/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-nokeys/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-nokeys/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-nonav/strings.xml b/apps/DeviceConfig/app/src/main/res/values-nonav/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-nonav/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-nonav/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-normal/strings.xml b/apps/DeviceConfig/app/src/main/res/values-normal/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-normal/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-normal/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-notlong/strings.xml b/apps/DeviceConfig/app/src/main/res/values-notlong/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-notlong/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-notlong/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-notnight/strings.xml b/apps/DeviceConfig/app/src/main/res/values-notnight/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-notnight/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-notnight/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-notouch/strings.xml b/apps/DeviceConfig/app/src/main/res/values-notouch/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-notouch/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-notouch/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-port/strings.xml b/apps/DeviceConfig/app/src/main/res/values-port/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-port/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-port/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-qwerty/strings.xml b/apps/DeviceConfig/app/src/main/res/values-qwerty/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-qwerty/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-qwerty/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-small/strings.xml b/apps/DeviceConfig/app/src/main/res/values-small/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-small/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-small/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-stylus/strings.xml b/apps/DeviceConfig/app/src/main/res/values-stylus/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-stylus/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-stylus/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-trackball/strings.xml b/apps/DeviceConfig/app/src/main/res/values-trackball/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-trackball/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-trackball/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-tvdpi/strings.xml b/apps/DeviceConfig/app/src/main/res/values-tvdpi/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-tvdpi/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-tvdpi/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v1/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v1/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v1/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v1/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v14/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v14/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v14/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v14/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v2/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v2/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v2/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v2/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v3/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v3/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v3/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v3/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v4/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v4/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v4/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v4/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v5/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v5/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v5/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v5/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v6/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v6/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v6/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v6/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v7/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v7/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v7/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v7/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v8/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v8/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v8/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v8/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-v9/strings.xml b/apps/DeviceConfig/app/src/main/res/values-v9/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-v9/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-v9/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-wheel/strings.xml b/apps/DeviceConfig/app/src/main/res/values-wheel/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-wheel/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-wheel/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-xhdpi/strings.xml b/apps/DeviceConfig/app/src/main/res/values-xhdpi/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-xhdpi/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-xhdpi/strings.xml
diff --git a/base/apps/DeviceConfig/res/values-xlarge/strings.xml b/apps/DeviceConfig/app/src/main/res/values-xlarge/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values-xlarge/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values-xlarge/strings.xml
diff --git a/base/apps/DeviceConfig/res/values/strings.xml b/apps/DeviceConfig/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/apps/DeviceConfig/res/values/strings.xml
rename to apps/DeviceConfig/app/src/main/res/values/strings.xml
diff --git a/apps/DeviceConfig/build.gradle b/apps/DeviceConfig/build.gradle
new file mode 100644
index 0000000..4d229a3
--- /dev/null
+++ b/apps/DeviceConfig/build.gradle
@@ -0,0 +1,15 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter();
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.4.0-beta2'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter();
+ }
+}
diff --git a/apps/DeviceConfig/settings.gradle b/apps/DeviceConfig/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/apps/DeviceConfig/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/base/apps/NotificationStudio/.classpath b/apps/NotificationStudio/.classpath
similarity index 100%
rename from base/apps/NotificationStudio/.classpath
rename to apps/NotificationStudio/.classpath
diff --git a/base/apps/NotificationStudio/.project b/apps/NotificationStudio/.project
similarity index 100%
rename from base/apps/NotificationStudio/.project
rename to apps/NotificationStudio/.project
diff --git a/base/apps/NotificationStudio/.settings/org.eclipse.jdt.core.prefs b/apps/NotificationStudio/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/apps/NotificationStudio/.settings/org.eclipse.jdt.core.prefs
rename to apps/NotificationStudio/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/apps/NotificationStudio/AndroidManifest.xml b/apps/NotificationStudio/AndroidManifest.xml
similarity index 100%
rename from base/apps/NotificationStudio/AndroidManifest.xml
rename to apps/NotificationStudio/AndroidManifest.xml
diff --git a/base/apps/NotificationStudio/project.properties b/apps/NotificationStudio/project.properties
similarity index 100%
rename from base/apps/NotificationStudio/project.properties
rename to apps/NotificationStudio/project.properties
diff --git a/base/apps/NotificationStudio/res/drawable-hdpi/android_logo.gif b/apps/NotificationStudio/res/drawable-hdpi/android_logo.gif
similarity index 100%
rename from base/apps/NotificationStudio/res/drawable-hdpi/android_logo.gif
rename to apps/NotificationStudio/res/drawable-hdpi/android_logo.gif
diff --git a/base/apps/NotificationStudio/res/drawable-hdpi/ic_notification_multiple_mail_holo_dark.png b/apps/NotificationStudio/res/drawable-hdpi/ic_notification_multiple_mail_holo_dark.png
similarity index 100%
rename from base/apps/NotificationStudio/res/drawable-hdpi/ic_notification_multiple_mail_holo_dark.png
rename to apps/NotificationStudio/res/drawable-hdpi/ic_notification_multiple_mail_holo_dark.png
diff --git a/base/apps/NotificationStudio/res/drawable-hdpi/romain.jpg b/apps/NotificationStudio/res/drawable-hdpi/romain.jpg
similarity index 100%
rename from base/apps/NotificationStudio/res/drawable-hdpi/romain.jpg
rename to apps/NotificationStudio/res/drawable-hdpi/romain.jpg
diff --git a/base/apps/NotificationStudio/res/drawable-nodpi/icon_bg.xml b/apps/NotificationStudio/res/drawable-nodpi/icon_bg.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/drawable-nodpi/icon_bg.xml
rename to apps/NotificationStudio/res/drawable-nodpi/icon_bg.xml
diff --git a/base/apps/NotificationStudio/res/drawable-nodpi/romainguy_rockaway.jpg b/apps/NotificationStudio/res/drawable-nodpi/romainguy_rockaway.jpg
similarity index 100%
rename from base/apps/NotificationStudio/res/drawable-nodpi/romainguy_rockaway.jpg
rename to apps/NotificationStudio/res/drawable-nodpi/romainguy_rockaway.jpg
diff --git a/base/apps/NotificationStudio/res/layout-v14/boolean_editor.xml b/apps/NotificationStudio/res/layout-v14/boolean_editor.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout-v14/boolean_editor.xml
rename to apps/NotificationStudio/res/layout-v14/boolean_editor.xml
diff --git a/base/apps/NotificationStudio/res/layout-v16/studio.xml b/apps/NotificationStudio/res/layout-v16/studio.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout-v16/studio.xml
rename to apps/NotificationStudio/res/layout-v16/studio.xml
diff --git a/base/apps/NotificationStudio/res/layout-w801dp/studio.xml b/apps/NotificationStudio/res/layout-w801dp/studio.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout-w801dp/studio.xml
rename to apps/NotificationStudio/res/layout-w801dp/studio.xml
diff --git a/base/apps/NotificationStudio/res/layout/boolean_editor.xml b/apps/NotificationStudio/res/layout/boolean_editor.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout/boolean_editor.xml
rename to apps/NotificationStudio/res/layout/boolean_editor.xml
diff --git a/base/apps/NotificationStudio/res/layout/divider.xml b/apps/NotificationStudio/res/layout/divider.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout/divider.xml
rename to apps/NotificationStudio/res/layout/divider.xml
diff --git a/base/apps/NotificationStudio/res/layout/editable_item.xml b/apps/NotificationStudio/res/layout/editable_item.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout/editable_item.xml
rename to apps/NotificationStudio/res/layout/editable_item.xml
diff --git a/base/apps/NotificationStudio/res/layout/editors.xml b/apps/NotificationStudio/res/layout/editors.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout/editors.xml
rename to apps/NotificationStudio/res/layout/editors.xml
diff --git a/base/apps/NotificationStudio/res/layout/preview.xml b/apps/NotificationStudio/res/layout/preview.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout/preview.xml
rename to apps/NotificationStudio/res/layout/preview.xml
diff --git a/base/apps/NotificationStudio/res/layout/studio.xml b/apps/NotificationStudio/res/layout/studio.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/layout/studio.xml
rename to apps/NotificationStudio/res/layout/studio.xml
diff --git a/base/apps/NotificationStudio/res/menu/action_bar.xml b/apps/NotificationStudio/res/menu/action_bar.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/menu/action_bar.xml
rename to apps/NotificationStudio/res/menu/action_bar.xml
diff --git a/base/apps/NotificationStudio/res/values-sw600dp/dimens.xml b/apps/NotificationStudio/res/values-sw600dp/dimens.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/values-sw600dp/dimens.xml
rename to apps/NotificationStudio/res/values-sw600dp/dimens.xml
diff --git a/base/apps/NotificationStudio/res/values-sw720dp/dimens.xml b/apps/NotificationStudio/res/values-sw720dp/dimens.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/values-sw720dp/dimens.xml
rename to apps/NotificationStudio/res/values-sw720dp/dimens.xml
diff --git a/base/apps/NotificationStudio/res/values-v11/colors.xml b/apps/NotificationStudio/res/values-v11/colors.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/values-v11/colors.xml
rename to apps/NotificationStudio/res/values-v11/colors.xml
diff --git a/base/apps/NotificationStudio/res/values-v11/dimens.xml b/apps/NotificationStudio/res/values-v11/dimens.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/values-v11/dimens.xml
rename to apps/NotificationStudio/res/values-v11/dimens.xml
diff --git a/base/apps/NotificationStudio/res/values/colors.xml b/apps/NotificationStudio/res/values/colors.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/values/colors.xml
rename to apps/NotificationStudio/res/values/colors.xml
diff --git a/base/apps/NotificationStudio/res/values/dimens.xml b/apps/NotificationStudio/res/values/dimens.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/values/dimens.xml
rename to apps/NotificationStudio/res/values/dimens.xml
diff --git a/base/apps/NotificationStudio/res/values/strings.xml b/apps/NotificationStudio/res/values/strings.xml
similarity index 100%
rename from base/apps/NotificationStudio/res/values/strings.xml
rename to apps/NotificationStudio/res/values/strings.xml
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/MaxHeightScrollView.java b/apps/NotificationStudio/src/com/android/notificationstudio/MaxHeightScrollView.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/MaxHeightScrollView.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/MaxHeightScrollView.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/NotificationStudioActivity.java b/apps/NotificationStudio/src/com/android/notificationstudio/NotificationStudioActivity.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/NotificationStudioActivity.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/NotificationStudioActivity.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/action/ShareCodeAction.java b/apps/NotificationStudio/src/com/android/notificationstudio/action/ShareCodeAction.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/action/ShareCodeAction.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/action/ShareCodeAction.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/action/ShareMockupAction.java b/apps/NotificationStudio/src/com/android/notificationstudio/action/ShareMockupAction.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/action/ShareMockupAction.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/action/ShareMockupAction.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/BitmapEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/BitmapEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/BitmapEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/BitmapEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/BooleanEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/BooleanEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/BooleanEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/BooleanEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/DateTimeEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/DateTimeEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/DateTimeEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/DateTimeEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/DropDownEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/DropDownEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/DropDownEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/DropDownEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/Editors.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/Editors.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/Editors.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/Editors.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/IconEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/IconEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/IconEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/IconEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/IntEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/IntEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/IntEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/IntEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/LinesEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/LinesEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/LinesEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/LinesEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/editor/TextEditor.java b/apps/NotificationStudio/src/com/android/notificationstudio/editor/TextEditor.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/editor/TextEditor.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/editor/TextEditor.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/generator/CodeGenerator.java b/apps/NotificationStudio/src/com/android/notificationstudio/generator/CodeGenerator.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/generator/CodeGenerator.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/generator/CodeGenerator.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/generator/NotificationGenerator.java b/apps/NotificationStudio/src/com/android/notificationstudio/generator/NotificationGenerator.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/generator/NotificationGenerator.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/generator/NotificationGenerator.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItem.java b/apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItem.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItem.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItem.java
diff --git a/base/apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItemConstants.java b/apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItemConstants.java
similarity index 100%
rename from base/apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItemConstants.java
rename to apps/NotificationStudio/src/com/android/notificationstudio/model/EditableItemConstants.java
diff --git a/base/apps/SdkController/.classpath b/apps/SdkController/.classpath
similarity index 100%
rename from base/apps/SdkController/.classpath
rename to apps/SdkController/.classpath
diff --git a/base/apps/SdkController/.project b/apps/SdkController/.project
similarity index 100%
rename from base/apps/SdkController/.project
rename to apps/SdkController/.project
diff --git a/base/apps/SdkController/.settings/org.eclipse.jdt.core.prefs b/apps/SdkController/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/apps/SdkController/.settings/org.eclipse.jdt.core.prefs
rename to apps/SdkController/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/apps/SdkController/AndroidManifest.xml b/apps/SdkController/AndroidManifest.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/AndroidManifest.xml
rename to apps/SdkController/AndroidManifest.xml
diff --git a/base/apps/SdkController/Implementation.txt b/apps/SdkController/Implementation.txt
similarity index 100%
rename from base/apps/SdkController/Implementation.txt
rename to apps/SdkController/Implementation.txt
diff --git a/base/apps/SdkController/assets/intro_help.html b/apps/SdkController/assets/intro_help.html
similarity index 100%
rename from base/apps/SdkController/assets/intro_help.html
rename to apps/SdkController/assets/intro_help.html
diff --git a/base/apps/DeviceConfig/proguard-project.txt b/apps/SdkController/proguard-project.txt
old mode 100644
new mode 100755
similarity index 100%
rename from base/apps/DeviceConfig/proguard-project.txt
rename to apps/SdkController/proguard-project.txt
diff --git a/base/apps/SdkController/project.properties b/apps/SdkController/project.properties
similarity index 100%
rename from base/apps/SdkController/project.properties
rename to apps/SdkController/project.properties
diff --git a/base/apps/SdkController/res/drawable-hdpi/ic_launcher.png b/apps/SdkController/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/drawable-hdpi/ic_launcher.png
rename to apps/SdkController/res/drawable-hdpi/ic_launcher.png
diff --git a/base/apps/SdkController/res/drawable-ldpi/ic_launcher.png b/apps/SdkController/res/drawable-ldpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/drawable-ldpi/ic_launcher.png
rename to apps/SdkController/res/drawable-ldpi/ic_launcher.png
diff --git a/base/apps/SdkController/res/drawable-mdpi/ic_launcher.png b/apps/SdkController/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from base/apps/SdkController/res/drawable-mdpi/ic_launcher.png
copy to apps/SdkController/res/drawable-mdpi/ic_launcher.png
diff --git a/base/apps/SdkController/res/drawable-xhdpi/ic_launcher.png b/apps/SdkController/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/drawable-xhdpi/ic_launcher.png
rename to apps/SdkController/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/apps/SdkController/res/layout-land/sensors.xml b/apps/SdkController/res/layout-land/sensors.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/layout-land/sensors.xml
rename to apps/SdkController/res/layout-land/sensors.xml
diff --git a/base/apps/SdkController/res/layout/main.xml b/apps/SdkController/res/layout/main.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/layout/main.xml
rename to apps/SdkController/res/layout/main.xml
diff --git a/base/apps/SdkController/res/layout/multitouch.xml b/apps/SdkController/res/layout/multitouch.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/layout/multitouch.xml
rename to apps/SdkController/res/layout/multitouch.xml
diff --git a/base/apps/SdkController/res/layout/sensor_row.xml b/apps/SdkController/res/layout/sensor_row.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/layout/sensor_row.xml
rename to apps/SdkController/res/layout/sensor_row.xml
diff --git a/base/apps/SdkController/res/layout/sensors.xml b/apps/SdkController/res/layout/sensors.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/layout/sensors.xml
rename to apps/SdkController/res/layout/sensors.xml
diff --git a/base/apps/SdkController/res/values-v11/styles_v11.xml b/apps/SdkController/res/values-v11/styles_v11.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/values-v11/styles_v11.xml
rename to apps/SdkController/res/values-v11/styles_v11.xml
diff --git a/base/apps/SdkController/res/values/strings.xml b/apps/SdkController/res/values/strings.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/values/strings.xml
rename to apps/SdkController/res/values/strings.xml
diff --git a/base/apps/SdkController/res/values/styles.xml b/apps/SdkController/res/values/styles.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/values/styles.xml
rename to apps/SdkController/res/values/styles.xml
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MainActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MainActivity.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MainActivity.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/activities/MainActivity.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper.java b/apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper_11.java b/apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper_11.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper_11.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/utils/ApiHelper_11.java
diff --git a/base/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java b/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
diff --git a/base/asset-studio/.classpath b/asset-studio/.classpath
similarity index 100%
rename from base/asset-studio/.classpath
rename to asset-studio/.classpath
diff --git a/base/asset-studio/.gitignore b/asset-studio/.gitignore
similarity index 100%
rename from base/asset-studio/.gitignore
rename to asset-studio/.gitignore
diff --git a/base/asset-studio/.project b/asset-studio/.project
similarity index 100%
rename from base/asset-studio/.project
rename to asset-studio/.project
diff --git a/base/asset-studio/.settings/org.eclipse.jdt.core.prefs b/asset-studio/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/asset-studio/.settings/org.eclipse.jdt.core.prefs
rename to asset-studio/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/asset-studio/NOTICE b/asset-studio/NOTICE
similarity index 100%
rename from base/asset-studio/NOTICE
rename to asset-studio/NOTICE
diff --git a/base/asset-studio/assetstudio-base.iml b/asset-studio/assetstudio-base.iml
similarity index 100%
rename from base/asset-studio/assetstudio-base.iml
rename to asset-studio/assetstudio-base.iml
diff --git a/base/asset-studio/assetstudio.iml b/asset-studio/assetstudio.iml
similarity index 100%
rename from base/asset-studio/assetstudio.iml
rename to asset-studio/assetstudio.iml
diff --git a/base/asset-studio/build.gradle b/asset-studio/build.gradle
similarity index 100%
rename from base/asset-studio/build.gradle
rename to asset-studio/build.gradle
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGeneratorContext.java b/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGeneratorContext.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGeneratorContext.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/GraphicGeneratorContext.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/LauncherIconGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/LauncherIconGenerator.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/LauncherIconGenerator.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/LauncherIconGenerator.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/MenuIconGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/MenuIconGenerator.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/MenuIconGenerator.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/MenuIconGenerator.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/NotificationIconGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/NotificationIconGenerator.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/NotificationIconGenerator.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/NotificationIconGenerator.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/TabIconGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/TabIconGenerator.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/TabIconGenerator.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/TabIconGenerator.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/TextRenderUtil.java b/asset-studio/src/main/java/com/android/assetstudiolib/TextRenderUtil.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/TextRenderUtil.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/TextRenderUtil.java
diff --git a/base/asset-studio/src/main/java/com/android/assetstudiolib/VectorIconGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/VectorIconGenerator.java
similarity index 100%
rename from base/asset-studio/src/main/java/com/android/assetstudiolib/VectorIconGenerator.java
rename to asset-studio/src/main/java/com/android/assetstudiolib/VectorIconGenerator.java
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-accept.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-accept.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-accept.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-accept.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-back.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-back.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-back.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-cancel.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-cancel.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-cancel.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-cancel.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-collapse.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-collapse.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-collapse.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-collapse.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-drawer.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-drawer.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-drawer.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-drawer.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-expand.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-expand.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-expand.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-expand.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-forward.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-forward.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-forward.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-forward.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-next-item.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-next-item.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-next-item.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-next-item.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-previous-item.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-previous-item.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-previous-item.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-previous-item.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/1-navigation-refresh.png b/asset-studio/src/main/java/images/clipart/big/1-navigation-refresh.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/1-navigation-refresh.png
rename to asset-studio/src/main/java/images/clipart/big/1-navigation-refresh.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-accounts.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-accounts.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-accounts.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-accounts.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-add-alarm.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-add-alarm.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-add-alarm.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-add-alarm.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-alarms.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-alarms.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-alarms.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-alarms.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-battery.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-battery.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-battery.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-battery.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-bightness-low.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-bightness-low.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-bightness-low.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-bightness-low.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-connected.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-connected.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-connected.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-connected.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-searching.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-searching.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-searching.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth-searching.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-bluetooth.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-auto.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-auto.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-auto.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-auto.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-high.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-high.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-high.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-high.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-medium.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-medium.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-medium.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-brightness-medium.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-call.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-call.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-call.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-call.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-camera.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-camera.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-camera.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-camera.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-data-usage.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-data-usage.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-data-usage.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-data-usage.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-dial-pad.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-dial-pad.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-dial-pad.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-dial-pad.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-end-call.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-end-call.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-end-call.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-end-call.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-automatic.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-automatic.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-automatic.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-flash-automatic.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-off.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-off.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-off.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-flash-off.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-on.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-on.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-flash-on.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-flash-on.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-location-found.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-location-found.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-location-found.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-location-found.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-location-off.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-location-off.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-location-off.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-location-off.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-location-searching.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-location-searching.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-location-searching.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-location-searching.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-mic-muted.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-mic-muted.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-mic-muted.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-mic-muted.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-mic.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-mic.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-mic.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-mic.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-network-cell.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-network-cell.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-network-cell.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-network-cell.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-network-wifi.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-network-wifi.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-network-wifi.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-network-wifi.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-new-account.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-new-account.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-new-account.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-new-account.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-not-secure.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-not-secure.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-not-secure.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-not-secure.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-ring-volume.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-ring-volume.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-ring-volume.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-ring-volume.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-landscape.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-landscape.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-landscape.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-landscape.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-portrait.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-portrait.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-portrait.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-screen-locked-to-portrait.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-rotation.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-rotation.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-screen-rotation.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-screen-rotation.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-sd-storage.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-sd-storage.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-sd-storage.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-sd-storage.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-secure.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-secure.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-secure.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-secure.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-storage.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-storage.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-storage.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-storage.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-switch-camera.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-switch-camera.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-switch-camera.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-switch-camera.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-switch-video.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-switch-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-switch-video.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-switch-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-time.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-time.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-time.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-time.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-usb.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-usb.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-usb.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-usb.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-video.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-video.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-volume-muted.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-volume-muted.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-volume-muted.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-volume-muted.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/10-device-access-volume-on.png b/asset-studio/src/main/java/images/clipart/big/10-device-access-volume-on.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/10-device-access-volume-on.png
rename to asset-studio/src/main/java/images/clipart/big/10-device-access-volume-on.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-off.png b/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-off.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-off.png
rename to asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-off.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-on.png b/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-on.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-on.png
rename to asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-airplane-mode-on.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-error.png b/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-error.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-error.png
rename to asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-error.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-warning.png b/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-warning.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-warning.png
rename to asset-studio/src/main/java/images/clipart/big/11-alerts-and-states-warning.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-computer.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-computer.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-computer.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-computer.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-dock.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-dock.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-dock.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-dock.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-gamepad.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-gamepad.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-gamepad.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-gamepad.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-headphones.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-headphones.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-headphones.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-headphones.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-headset.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-headset.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-headset.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-headset.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-keyboard.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-keyboard.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-keyboard.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-keyboard.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-mouse.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-mouse.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-mouse.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-mouse.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/12-hardware-phone.png b/asset-studio/src/main/java/images/clipart/big/12-hardware-phone.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/12-hardware-phone.png
rename to asset-studio/src/main/java/images/clipart/big/12-hardware-phone.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/2-action-about.png b/asset-studio/src/main/java/images/clipart/big/2-action-about.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/2-action-about.png
rename to asset-studio/src/main/java/images/clipart/big/2-action-about.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/2-action-help.png b/asset-studio/src/main/java/images/clipart/big/2-action-help.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/2-action-help.png
rename to asset-studio/src/main/java/images/clipart/big/2-action-help.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/2-action-overflow.png b/asset-studio/src/main/java/images/clipart/big/2-action-overflow.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/2-action-overflow.png
rename to asset-studio/src/main/java/images/clipart/big/2-action-overflow.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/2-action-search.png b/asset-studio/src/main/java/images/clipart/big/2-action-search.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/2-action-search.png
rename to asset-studio/src/main/java/images/clipart/big/2-action-search.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/2-action-settings.png b/asset-studio/src/main/java/images/clipart/big/2-action-settings.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/2-action-settings.png
rename to asset-studio/src/main/java/images/clipart/big/2-action-settings.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/3-rating-bad.png b/asset-studio/src/main/java/images/clipart/big/3-rating-bad.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/3-rating-bad.png
rename to asset-studio/src/main/java/images/clipart/big/3-rating-bad.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/3-rating-favorite.png b/asset-studio/src/main/java/images/clipart/big/3-rating-favorite.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/3-rating-favorite.png
rename to asset-studio/src/main/java/images/clipart/big/3-rating-favorite.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/3-rating-good.png b/asset-studio/src/main/java/images/clipart/big/3-rating-good.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/3-rating-good.png
rename to asset-studio/src/main/java/images/clipart/big/3-rating-good.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/3-rating-half-important.png b/asset-studio/src/main/java/images/clipart/big/3-rating-half-important.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/3-rating-half-important.png
rename to asset-studio/src/main/java/images/clipart/big/3-rating-half-important.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/3-rating-important.png b/asset-studio/src/main/java/images/clipart/big/3-rating-important.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/3-rating-important.png
rename to asset-studio/src/main/java/images/clipart/big/3-rating-important.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/3-rating-not-important.png b/asset-studio/src/main/java/images/clipart/big/3-rating-not-important.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/3-rating-not-important.png
rename to asset-studio/src/main/java/images/clipart/big/3-rating-not-important.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-cloud.png b/asset-studio/src/main/java/images/clipart/big/4-collections-cloud.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-cloud.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-cloud.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-collection.png b/asset-studio/src/main/java/images/clipart/big/4-collections-collection.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-collection.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-collection.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-go-to-today.png b/asset-studio/src/main/java/images/clipart/big/4-collections-go-to-today.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-go-to-today.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-go-to-today.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-labels.png b/asset-studio/src/main/java/images/clipart/big/4-collections-labels.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-labels.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-labels.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-new-label.png b/asset-studio/src/main/java/images/clipart/big/4-collections-new-label.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-new-label.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-new-label.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-sort-by-size.png b/asset-studio/src/main/java/images/clipart/big/4-collections-sort-by-size.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-sort-by-size.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-sort-by-size.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-view-as-grid.png b/asset-studio/src/main/java/images/clipart/big/4-collections-view-as-grid.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-view-as-grid.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-view-as-grid.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/4-collections-view-as-list.png b/asset-studio/src/main/java/images/clipart/big/4-collections-view-as-list.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/4-collections-view-as-list.png
rename to asset-studio/src/main/java/images/clipart/big/4-collections-view-as-list.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-attachment.png b/asset-studio/src/main/java/images/clipart/big/5-content-attachment.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-attachment.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-attachment.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-backspace.png b/asset-studio/src/main/java/images/clipart/big/5-content-backspace.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-backspace.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-backspace.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-copy.png b/asset-studio/src/main/java/images/clipart/big/5-content-copy.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-copy.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-copy.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-cut.png b/asset-studio/src/main/java/images/clipart/big/5-content-cut.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-cut.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-cut.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-discard.png b/asset-studio/src/main/java/images/clipart/big/5-content-discard.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-discard.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-discard.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-edit.png b/asset-studio/src/main/java/images/clipart/big/5-content-edit.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-edit.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-edit.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-email.png b/asset-studio/src/main/java/images/clipart/big/5-content-email.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-email.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-email.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-event.png b/asset-studio/src/main/java/images/clipart/big/5-content-event.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-event.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-event.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-import-export.png b/asset-studio/src/main/java/images/clipart/big/5-content-import-export.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-import-export.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-import-export.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-merge.png b/asset-studio/src/main/java/images/clipart/big/5-content-merge.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-merge.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-merge.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-new-attachment.png b/asset-studio/src/main/java/images/clipart/big/5-content-new-attachment.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-new-attachment.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-new-attachment.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-new-email.png b/asset-studio/src/main/java/images/clipart/big/5-content-new-email.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-new-email.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-new-email.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-new-event.png b/asset-studio/src/main/java/images/clipart/big/5-content-new-event.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-new-event.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-new-event.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-new-picture.png b/asset-studio/src/main/java/images/clipart/big/5-content-new-picture.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-new-picture.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-new-picture.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-new.png b/asset-studio/src/main/java/images/clipart/big/5-content-new.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-new.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-new.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-paste.png b/asset-studio/src/main/java/images/clipart/big/5-content-paste.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-paste.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-paste.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-picture.png b/asset-studio/src/main/java/images/clipart/big/5-content-picture.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-picture.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-picture.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-read.png b/asset-studio/src/main/java/images/clipart/big/5-content-read.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-read.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-read.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-remove.png b/asset-studio/src/main/java/images/clipart/big/5-content-remove.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-remove.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-remove.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-save.png b/asset-studio/src/main/java/images/clipart/big/5-content-save.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-save.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-save.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-select-all.png b/asset-studio/src/main/java/images/clipart/big/5-content-select-all.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-select-all.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-select-all.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-split.png b/asset-studio/src/main/java/images/clipart/big/5-content-split.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-split.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-split.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-undo.png b/asset-studio/src/main/java/images/clipart/big/5-content-undo.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-undo.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-undo.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/5-content-unread.png b/asset-studio/src/main/java/images/clipart/big/5-content-unread.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/5-content-unread.png
rename to asset-studio/src/main/java/images/clipart/big/5-content-unread.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-add-group.png b/asset-studio/src/main/java/images/clipart/big/6-social-add-group.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-add-group.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-add-group.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-add-person.png b/asset-studio/src/main/java/images/clipart/big/6-social-add-person.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-add-person.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-add-person.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-cc-bcc.png b/asset-studio/src/main/java/images/clipart/big/6-social-cc-bcc.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-cc-bcc.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-cc-bcc.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-chat.png b/asset-studio/src/main/java/images/clipart/big/6-social-chat.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-chat.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-chat.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-forward.png b/asset-studio/src/main/java/images/clipart/big/6-social-forward.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-forward.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-forward.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-group.png b/asset-studio/src/main/java/images/clipart/big/6-social-group.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-group.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-group.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-person.png b/asset-studio/src/main/java/images/clipart/big/6-social-person.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-person.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-person.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-reply-all.png b/asset-studio/src/main/java/images/clipart/big/6-social-reply-all.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-reply-all.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-reply-all.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-reply.png b/asset-studio/src/main/java/images/clipart/big/6-social-reply.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-reply.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-reply.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-send-now.png b/asset-studio/src/main/java/images/clipart/big/6-social-send-now.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-send-now.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-send-now.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/6-social-share.png b/asset-studio/src/main/java/images/clipart/big/6-social-share.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/6-social-share.png
rename to asset-studio/src/main/java/images/clipart/big/6-social-share.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/7-location-directions.png b/asset-studio/src/main/java/images/clipart/big/7-location-directions.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/7-location-directions.png
rename to asset-studio/src/main/java/images/clipart/big/7-location-directions.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/7-location-map.png b/asset-studio/src/main/java/images/clipart/big/7-location-map.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/7-location-map.png
rename to asset-studio/src/main/java/images/clipart/big/7-location-map.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/7-location-place.png b/asset-studio/src/main/java/images/clipart/big/7-location-place.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/7-location-place.png
rename to asset-studio/src/main/java/images/clipart/big/7-location-place.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/7-location-web-site.png b/asset-studio/src/main/java/images/clipart/big/7-location-web-site.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/7-location-web-site.png
rename to asset-studio/src/main/java/images/clipart/big/7-location-web-site.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/8-images-crop.png b/asset-studio/src/main/java/images/clipart/big/8-images-crop.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/8-images-crop.png
rename to asset-studio/src/main/java/images/clipart/big/8-images-crop.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/8-images-rotate-left.png b/asset-studio/src/main/java/images/clipart/big/8-images-rotate-left.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/8-images-rotate-left.png
rename to asset-studio/src/main/java/images/clipart/big/8-images-rotate-left.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/8-images-rotate-right.png b/asset-studio/src/main/java/images/clipart/big/8-images-rotate-right.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/8-images-rotate-right.png
rename to asset-studio/src/main/java/images/clipart/big/8-images-rotate-right.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/8-images-slideshow.png b/asset-studio/src/main/java/images/clipart/big/8-images-slideshow.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/8-images-slideshow.png
rename to asset-studio/src/main/java/images/clipart/big/8-images-slideshow.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-add-to-queue.png b/asset-studio/src/main/java/images/clipart/big/9-av-add-to-queue.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-add-to-queue.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-add-to-queue.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-download.png b/asset-studio/src/main/java/images/clipart/big/9-av-download.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-download.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-download.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-fast-forward.png b/asset-studio/src/main/java/images/clipart/big/9-av-fast-forward.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-fast-forward.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-fast-forward.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-full-screen.png b/asset-studio/src/main/java/images/clipart/big/9-av-full-screen.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-full-screen.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-full-screen.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-make-available-offline.png b/asset-studio/src/main/java/images/clipart/big/9-av-make-available-offline.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-make-available-offline.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-make-available-offline.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-next.png b/asset-studio/src/main/java/images/clipart/big/9-av-next.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-next.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-next.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-pause-over-video.png b/asset-studio/src/main/java/images/clipart/big/9-av-pause-over-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-pause-over-video.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-pause-over-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-pause.png b/asset-studio/src/main/java/images/clipart/big/9-av-pause.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-pause.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-pause.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-play-over-video.png b/asset-studio/src/main/java/images/clipart/big/9-av-play-over-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-play-over-video.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-play-over-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-play.png b/asset-studio/src/main/java/images/clipart/big/9-av-play.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-play.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-play.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-previous.png b/asset-studio/src/main/java/images/clipart/big/9-av-previous.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-previous.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-previous.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-repeat.png b/asset-studio/src/main/java/images/clipart/big/9-av-repeat.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-repeat.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-repeat.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-replay.png b/asset-studio/src/main/java/images/clipart/big/9-av-replay.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-replay.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-replay.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-return-from-full-screen.png b/asset-studio/src/main/java/images/clipart/big/9-av-return-from-full-screen.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-return-from-full-screen.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-return-from-full-screen.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-rewind.png b/asset-studio/src/main/java/images/clipart/big/9-av-rewind.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-rewind.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-rewind.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-shuffle.png b/asset-studio/src/main/java/images/clipart/big/9-av-shuffle.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-shuffle.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-shuffle.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-stop.png b/asset-studio/src/main/java/images/clipart/big/9-av-stop.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-stop.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-stop.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/9-av-upload.png b/asset-studio/src/main/java/images/clipart/big/9-av-upload.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/9-av-upload.png
rename to asset-studio/src/main/java/images/clipart/big/9-av-upload.png
diff --git a/base/asset-studio/src/main/java/images/clipart/big/android.png b/asset-studio/src/main/java/images/clipart/big/android.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/big/android.png
rename to asset-studio/src/main/java/images/clipart/big/android.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-accept.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-accept.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-accept.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-accept.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-back.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-back.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-back.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-cancel.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-cancel.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-cancel.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-cancel.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-collapse.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-collapse.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-collapse.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-collapse.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-drawer.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-drawer.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-drawer.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-drawer.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-expand.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-expand.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-expand.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-expand.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-forward.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-forward.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-forward.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-forward.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-next-item.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-next-item.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-next-item.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-next-item.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-previous-item.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-previous-item.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-previous-item.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-previous-item.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/1-navigation-refresh.png b/asset-studio/src/main/java/images/clipart/small/1-navigation-refresh.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/1-navigation-refresh.png
rename to asset-studio/src/main/java/images/clipart/small/1-navigation-refresh.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-accounts.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-accounts.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-accounts.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-accounts.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-add-alarm.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-add-alarm.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-add-alarm.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-add-alarm.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-alarms.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-alarms.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-alarms.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-alarms.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-battery.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-battery.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-battery.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-battery.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-bightness-low.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-bightness-low.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-bightness-low.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-bightness-low.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-connected.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-connected.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-connected.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-connected.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-searching.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-searching.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-searching.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth-searching.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-bluetooth.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-auto.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-auto.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-auto.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-auto.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-high.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-high.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-high.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-high.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-medium.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-medium.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-medium.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-brightness-medium.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-call.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-call.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-call.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-call.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-camera.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-camera.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-camera.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-camera.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-data-usage.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-data-usage.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-data-usage.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-data-usage.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-dial-pad.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-dial-pad.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-dial-pad.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-dial-pad.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-end-call.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-end-call.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-end-call.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-end-call.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-automatic.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-automatic.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-automatic.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-flash-automatic.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-off.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-off.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-off.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-flash-off.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-on.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-on.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-flash-on.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-flash-on.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-location-found.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-location-found.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-location-found.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-location-found.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-location-off.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-location-off.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-location-off.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-location-off.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-location-searching.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-location-searching.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-location-searching.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-location-searching.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-mic-muted.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-mic-muted.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-mic-muted.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-mic-muted.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-mic.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-mic.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-mic.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-mic.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-network-cell.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-network-cell.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-network-cell.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-network-cell.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-network-wifi.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-network-wifi.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-network-wifi.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-network-wifi.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-new-account.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-new-account.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-new-account.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-new-account.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-not-secure.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-not-secure.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-not-secure.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-not-secure.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-ring-volume.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-ring-volume.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-ring-volume.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-ring-volume.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-landscape.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-landscape.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-landscape.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-landscape.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-portrait.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-portrait.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-portrait.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-screen-locked-to-portrait.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-rotation.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-rotation.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-screen-rotation.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-screen-rotation.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-sd-storage.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-sd-storage.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-sd-storage.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-sd-storage.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-secure.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-secure.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-secure.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-secure.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-storage.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-storage.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-storage.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-storage.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-switch-camera.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-switch-camera.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-switch-camera.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-switch-camera.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-switch-video.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-switch-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-switch-video.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-switch-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-time.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-time.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-time.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-time.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-usb.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-usb.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-usb.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-usb.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-video.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-video.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-volume-muted.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-volume-muted.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-volume-muted.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-volume-muted.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/10-device-access-volume-on.png b/asset-studio/src/main/java/images/clipart/small/10-device-access-volume-on.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/10-device-access-volume-on.png
rename to asset-studio/src/main/java/images/clipart/small/10-device-access-volume-on.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-off.png b/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-off.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-off.png
rename to asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-off.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-on.png b/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-on.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-on.png
rename to asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-airplane-mode-on.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-error.png b/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-error.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-error.png
rename to asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-error.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-warning.png b/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-warning.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-warning.png
rename to asset-studio/src/main/java/images/clipart/small/11-alerts-and-states-warning.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-computer.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-computer.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-computer.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-computer.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-dock.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-dock.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-dock.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-dock.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-gamepad.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-gamepad.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-gamepad.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-gamepad.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-headphones.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-headphones.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-headphones.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-headphones.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-headset.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-headset.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-headset.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-headset.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-keyboard.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-keyboard.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-keyboard.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-keyboard.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-mouse.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-mouse.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-mouse.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-mouse.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/12-hardware-phone.png b/asset-studio/src/main/java/images/clipart/small/12-hardware-phone.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/12-hardware-phone.png
rename to asset-studio/src/main/java/images/clipart/small/12-hardware-phone.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/2-action-about.png b/asset-studio/src/main/java/images/clipart/small/2-action-about.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/2-action-about.png
rename to asset-studio/src/main/java/images/clipart/small/2-action-about.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/2-action-help.png b/asset-studio/src/main/java/images/clipart/small/2-action-help.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/2-action-help.png
rename to asset-studio/src/main/java/images/clipart/small/2-action-help.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/2-action-overflow.png b/asset-studio/src/main/java/images/clipart/small/2-action-overflow.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/2-action-overflow.png
rename to asset-studio/src/main/java/images/clipart/small/2-action-overflow.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/2-action-search.png b/asset-studio/src/main/java/images/clipart/small/2-action-search.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/2-action-search.png
rename to asset-studio/src/main/java/images/clipart/small/2-action-search.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/2-action-settings.png b/asset-studio/src/main/java/images/clipart/small/2-action-settings.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/2-action-settings.png
rename to asset-studio/src/main/java/images/clipart/small/2-action-settings.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/3-rating-bad.png b/asset-studio/src/main/java/images/clipart/small/3-rating-bad.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/3-rating-bad.png
rename to asset-studio/src/main/java/images/clipart/small/3-rating-bad.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/3-rating-favorite.png b/asset-studio/src/main/java/images/clipart/small/3-rating-favorite.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/3-rating-favorite.png
rename to asset-studio/src/main/java/images/clipart/small/3-rating-favorite.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/3-rating-good.png b/asset-studio/src/main/java/images/clipart/small/3-rating-good.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/3-rating-good.png
rename to asset-studio/src/main/java/images/clipart/small/3-rating-good.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/3-rating-half-important.png b/asset-studio/src/main/java/images/clipart/small/3-rating-half-important.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/3-rating-half-important.png
rename to asset-studio/src/main/java/images/clipart/small/3-rating-half-important.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/3-rating-important.png b/asset-studio/src/main/java/images/clipart/small/3-rating-important.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/3-rating-important.png
rename to asset-studio/src/main/java/images/clipart/small/3-rating-important.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/3-rating-not-important.png b/asset-studio/src/main/java/images/clipart/small/3-rating-not-important.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/3-rating-not-important.png
rename to asset-studio/src/main/java/images/clipart/small/3-rating-not-important.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-cloud.png b/asset-studio/src/main/java/images/clipart/small/4-collections-cloud.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-cloud.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-cloud.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-collection.png b/asset-studio/src/main/java/images/clipart/small/4-collections-collection.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-collection.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-collection.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-go-to-today.png b/asset-studio/src/main/java/images/clipart/small/4-collections-go-to-today.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-go-to-today.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-go-to-today.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-labels.png b/asset-studio/src/main/java/images/clipart/small/4-collections-labels.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-labels.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-labels.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-new-label.png b/asset-studio/src/main/java/images/clipart/small/4-collections-new-label.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-new-label.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-new-label.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-sort-by-size.png b/asset-studio/src/main/java/images/clipart/small/4-collections-sort-by-size.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-sort-by-size.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-sort-by-size.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-view-as-grid.png b/asset-studio/src/main/java/images/clipart/small/4-collections-view-as-grid.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-view-as-grid.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-view-as-grid.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/4-collections-view-as-list.png b/asset-studio/src/main/java/images/clipart/small/4-collections-view-as-list.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/4-collections-view-as-list.png
rename to asset-studio/src/main/java/images/clipart/small/4-collections-view-as-list.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-attachment.png b/asset-studio/src/main/java/images/clipart/small/5-content-attachment.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-attachment.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-attachment.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-backspace.png b/asset-studio/src/main/java/images/clipart/small/5-content-backspace.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-backspace.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-backspace.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-copy.png b/asset-studio/src/main/java/images/clipart/small/5-content-copy.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-copy.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-copy.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-cut.png b/asset-studio/src/main/java/images/clipart/small/5-content-cut.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-cut.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-cut.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-discard.png b/asset-studio/src/main/java/images/clipart/small/5-content-discard.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-discard.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-discard.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-edit.png b/asset-studio/src/main/java/images/clipart/small/5-content-edit.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-edit.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-edit.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-email.png b/asset-studio/src/main/java/images/clipart/small/5-content-email.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-email.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-email.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-event.png b/asset-studio/src/main/java/images/clipart/small/5-content-event.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-event.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-event.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-import-export.png b/asset-studio/src/main/java/images/clipart/small/5-content-import-export.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-import-export.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-import-export.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-merge.png b/asset-studio/src/main/java/images/clipart/small/5-content-merge.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-merge.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-merge.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-new-attachment.png b/asset-studio/src/main/java/images/clipart/small/5-content-new-attachment.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-new-attachment.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-new-attachment.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-new-email.png b/asset-studio/src/main/java/images/clipart/small/5-content-new-email.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-new-email.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-new-email.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-new-event.png b/asset-studio/src/main/java/images/clipart/small/5-content-new-event.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-new-event.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-new-event.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-new-picture.png b/asset-studio/src/main/java/images/clipart/small/5-content-new-picture.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-new-picture.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-new-picture.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-new.png b/asset-studio/src/main/java/images/clipart/small/5-content-new.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-new.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-new.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-paste.png b/asset-studio/src/main/java/images/clipart/small/5-content-paste.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-paste.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-paste.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-picture.png b/asset-studio/src/main/java/images/clipart/small/5-content-picture.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-picture.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-picture.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-read.png b/asset-studio/src/main/java/images/clipart/small/5-content-read.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-read.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-read.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-remove.png b/asset-studio/src/main/java/images/clipart/small/5-content-remove.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-remove.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-remove.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-save.png b/asset-studio/src/main/java/images/clipart/small/5-content-save.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-save.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-save.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-select-all.png b/asset-studio/src/main/java/images/clipart/small/5-content-select-all.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-select-all.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-select-all.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-split.png b/asset-studio/src/main/java/images/clipart/small/5-content-split.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-split.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-split.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-undo.png b/asset-studio/src/main/java/images/clipart/small/5-content-undo.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-undo.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-undo.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/5-content-unread.png b/asset-studio/src/main/java/images/clipart/small/5-content-unread.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/5-content-unread.png
rename to asset-studio/src/main/java/images/clipart/small/5-content-unread.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-add-group.png b/asset-studio/src/main/java/images/clipart/small/6-social-add-group.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-add-group.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-add-group.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-add-person.png b/asset-studio/src/main/java/images/clipart/small/6-social-add-person.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-add-person.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-add-person.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-cc-bcc.png b/asset-studio/src/main/java/images/clipart/small/6-social-cc-bcc.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-cc-bcc.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-cc-bcc.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-chat.png b/asset-studio/src/main/java/images/clipart/small/6-social-chat.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-chat.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-chat.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-forward.png b/asset-studio/src/main/java/images/clipart/small/6-social-forward.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-forward.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-forward.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-group.png b/asset-studio/src/main/java/images/clipart/small/6-social-group.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-group.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-group.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-person.png b/asset-studio/src/main/java/images/clipart/small/6-social-person.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-person.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-person.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-reply-all.png b/asset-studio/src/main/java/images/clipart/small/6-social-reply-all.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-reply-all.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-reply-all.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-reply.png b/asset-studio/src/main/java/images/clipart/small/6-social-reply.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-reply.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-reply.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-send-now.png b/asset-studio/src/main/java/images/clipart/small/6-social-send-now.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-send-now.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-send-now.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/6-social-share.png b/asset-studio/src/main/java/images/clipart/small/6-social-share.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/6-social-share.png
rename to asset-studio/src/main/java/images/clipart/small/6-social-share.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/7-location-directions.png b/asset-studio/src/main/java/images/clipart/small/7-location-directions.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/7-location-directions.png
rename to asset-studio/src/main/java/images/clipart/small/7-location-directions.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/7-location-map.png b/asset-studio/src/main/java/images/clipart/small/7-location-map.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/7-location-map.png
rename to asset-studio/src/main/java/images/clipart/small/7-location-map.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/7-location-place.png b/asset-studio/src/main/java/images/clipart/small/7-location-place.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/7-location-place.png
rename to asset-studio/src/main/java/images/clipart/small/7-location-place.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/7-location-web-site.png b/asset-studio/src/main/java/images/clipart/small/7-location-web-site.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/7-location-web-site.png
rename to asset-studio/src/main/java/images/clipart/small/7-location-web-site.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/8-images-crop.png b/asset-studio/src/main/java/images/clipart/small/8-images-crop.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/8-images-crop.png
rename to asset-studio/src/main/java/images/clipart/small/8-images-crop.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/8-images-rotate-left.png b/asset-studio/src/main/java/images/clipart/small/8-images-rotate-left.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/8-images-rotate-left.png
rename to asset-studio/src/main/java/images/clipart/small/8-images-rotate-left.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/8-images-rotate-right.png b/asset-studio/src/main/java/images/clipart/small/8-images-rotate-right.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/8-images-rotate-right.png
rename to asset-studio/src/main/java/images/clipart/small/8-images-rotate-right.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/8-images-slideshow.png b/asset-studio/src/main/java/images/clipart/small/8-images-slideshow.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/8-images-slideshow.png
rename to asset-studio/src/main/java/images/clipart/small/8-images-slideshow.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-add-to-queue.png b/asset-studio/src/main/java/images/clipart/small/9-av-add-to-queue.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-add-to-queue.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-add-to-queue.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-download.png b/asset-studio/src/main/java/images/clipart/small/9-av-download.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-download.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-download.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-fast-forward.png b/asset-studio/src/main/java/images/clipart/small/9-av-fast-forward.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-fast-forward.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-fast-forward.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-full-screen.png b/asset-studio/src/main/java/images/clipart/small/9-av-full-screen.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-full-screen.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-full-screen.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-make-available-offline.png b/asset-studio/src/main/java/images/clipart/small/9-av-make-available-offline.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-make-available-offline.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-make-available-offline.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-next.png b/asset-studio/src/main/java/images/clipart/small/9-av-next.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-next.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-next.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-pause-over-video.png b/asset-studio/src/main/java/images/clipart/small/9-av-pause-over-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-pause-over-video.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-pause-over-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-pause.png b/asset-studio/src/main/java/images/clipart/small/9-av-pause.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-pause.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-pause.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-play-over-video.png b/asset-studio/src/main/java/images/clipart/small/9-av-play-over-video.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-play-over-video.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-play-over-video.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-play.png b/asset-studio/src/main/java/images/clipart/small/9-av-play.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-play.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-play.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-previous.png b/asset-studio/src/main/java/images/clipart/small/9-av-previous.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-previous.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-previous.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-repeat.png b/asset-studio/src/main/java/images/clipart/small/9-av-repeat.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-repeat.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-repeat.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-replay.png b/asset-studio/src/main/java/images/clipart/small/9-av-replay.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-replay.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-replay.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-return-from-full-screen.png b/asset-studio/src/main/java/images/clipart/small/9-av-return-from-full-screen.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-return-from-full-screen.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-return-from-full-screen.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-rewind.png b/asset-studio/src/main/java/images/clipart/small/9-av-rewind.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-rewind.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-rewind.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-shuffle.png b/asset-studio/src/main/java/images/clipart/small/9-av-shuffle.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-shuffle.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-shuffle.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-stop.png b/asset-studio/src/main/java/images/clipart/small/9-av-stop.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-stop.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-stop.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/9-av-upload.png b/asset-studio/src/main/java/images/clipart/small/9-av-upload.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/9-av-upload.png
rename to asset-studio/src/main/java/images/clipart/small/9-av-upload.png
diff --git a/base/asset-studio/src/main/java/images/clipart/small/android.png b/asset-studio/src/main/java/images/clipart/small/android.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/clipart/small/android.png
rename to asset-studio/src/main/java/images/clipart/small/android.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/hdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/mdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/web/back.png b/asset-studio/src/main/java/images/launcher_stencil/circle/web/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/web/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/web/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/web/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/circle/web/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/web/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/web/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/web/mask.png b/asset-studio/src/main/java/images/launcher_stencil/circle/web/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/web/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/web/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask_inner.png b/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask_inner.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask_inner.png
rename to asset-studio/src/main/java/images/launcher_stencil/circle/xxxhdpi/mask_inner.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/hdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/mdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/web/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/web/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/web/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/web/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/web/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/web/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/web/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/web/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/web/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/web/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/web/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/web/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect/xxxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/hdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/mdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/web/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/hrect_dogear/xxxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/hdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/hdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/hdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/hdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/mdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/mdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/mdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/mdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/web/back.png b/asset-studio/src/main/java/images/launcher_stencil/square/web/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/web/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/web/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/web/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square/web/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/web/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/web/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/web/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square/web/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/web/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/web/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask_inner.png b/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask_inner.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask_inner.png
rename to asset-studio/src/main/java/images/launcher_stencil/square/xxxhdpi/mask_inner.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/hdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/mdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/back.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/web/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/square_dogear/xxxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/hdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/mdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/web/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/web/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/web/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/web/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/web/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/web/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/web/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/web/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/web/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/web/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/web/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/web/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect/xxxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/hdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/mdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/web/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxhdpi/mask.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/back.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/back.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/back.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/back.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/fore1.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/fore1.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/fore1.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/fore1.png
diff --git a/base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/mask.png b/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/mask.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/mask.png
rename to asset-studio/src/main/java/images/launcher_stencil/vrect_dogear/xxxhdpi/mask.png
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_3d_rotation_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_3d_rotation_black_24dp.xml
new file mode 100644
index 0000000..8e0a806
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_3d_rotation_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32zM8.41,14.96c-0.19,0 -0.37,-0.03 -0.52,-0.08 -0.16,-0.06 -0.29,-0.13 -0.4,-0.24 -0.11,-0.1 -0.2,-0.22 -0.26,-0.37 -0.06,-0.14 -0.09,-0.3 -0.09,-0.47h-1.3c0,0.36 0.07,0.68 0.21,0.95 0.14,0.27 0.33,0.5 0.56,0.69 0.24,0.18 0.51,0.32 0.82,0.41 0.3,0.1 0.62,0.15 0.96,0.15 0.37,0 0.72,-0.05 1.03,-0.15 0.32,-0.1 0.6,-0.25 0.83,-0.44s0.42,-0.43 0.55,-0.72c0.13,-0. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_accessibility_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_accessibility_black_24dp.xml
new file mode 100644
index 0000000..e7e53f0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_accessibility_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM21,9h-6v13h-2v-6h-2v6L9,22L9,9L3,9L3,7h18v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_accessible_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_accessible_black_24dp.xml
new file mode 100644
index 0000000..0acbacf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_accessible_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,4m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,13v-2c-1.54,0.02 -3.09,-0.75 -4.07,-1.83l-1.29,-1.43c-0.17,-0.19 -0.38,-0.34 -0.61,-0.45 -0.01,0 -0.01,-0.01 -0.02,-0.01L13,7.28c-0.35,-0.2 -0.75,-0.3 -1.19,-0.26C10.76,7.11 10,8.04 10,9.09L10,15c0,1.1 0.9,2 2,2h5v5h2v-5.5c0,-1.1 -0.9,-2 -2,-2h-3v-3.45c1.29,1.07 3.25,1.94 5,1.95zM12.83,18c-0.41,1.16 -1.52,2 -2.83,2 -1.66,0 -3,-1.34 -3,-3 0,-1.31 0.84,-2.41 2,-2.83L9,12.1c-2.28,0.46 -4,2.48 -4,4.9 0,2.76 2.24,5 5,5 2.42,0 4.44,-1.72 4.9,-4h-2.07z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_black_24dp.xml
new file mode 100644
index 0000000..b9d5db6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,10v7h3v-7L4,10zM10,10v7h3v-7h-3zM2,22h19v-3L2,19v3zM16,10v7h3v-7h-3zM11.5,1L2,6v2h19L21,6l-9.5,-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_wallet_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_wallet_black_24dp.xml
new file mode 100644
index 0000000..326c11a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_wallet_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 -2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_account_box_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_box_black_24dp.xml
new file mode 100644
index 0000000..27ee291
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_box_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2L5,3c-1.11,0 -2,0.9 -2,2zM15,9c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3zM6,17c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1L6,18v-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_account_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_circle_black_24dp.xml
new file mode 100644
index 0000000..7678580
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_account_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_add_shopping_cart_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_add_shopping_cart_black_24dp.xml
new file mode 100644
index 0000000..67d7a63
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_add_shopping_cart_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,9h2L13,6h3L16,4h-3L13,1h-2v3L8,4v2h3v3zM7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM7.17,14.75l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.86,-7.01L19.42,4h-0.01l-1.1,2 -2.76,5L8.53,11l-0.13,-0.27L6.16,6l-0.95,-2 -0.94,-2L1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2L7.42,15c-0.13,0 -0.25,-0.11 -0.25,-0.25z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_add_black_24dp.xml
new file mode 100644
index 0000000..4c5023a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zM13,9h-2v3L8,12v2h3v3h2v-3h3v-2h-3L13,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_black_24dp.xml
new file mode 100644
index 0000000..934b067
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12.5,8L11,8v6l4.75,2.85 0.75,-1.23 -4,-2.37L12.5,8zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_off_black_24dp.xml
new file mode 100644
index 0000000..785e67b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,6c3.87,0 7,3.13 7,7 0,0.84 -0.16,1.65 -0.43,2.4l1.52,1.52c0.58,-1.19 0.91,-2.51 0.91,-3.92 0,-4.97 -4.03,-9 -9,-9 -1.41,0 -2.73,0.33 -3.92,0.91L9.6,6.43C10.35,6.16 11.16,6 12,6zM22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM2.92,2.29L1.65,3.57 2.98,4.9l-1.11,0.93 1.42,1.42 1.11,-0.94 0.8,0.8C3.83,8.69 3,10.75 3,13c0,4.97 4.02,9 9,9 2.25,0 4.31,-0.83 5.89,-2.2l2.2,2.2 1.27,-1.27L3.89,3.27l-0.97,-0.98zM16.47,18.39C15.26,19.39 13.7,20 12,20c-3.87,0 -7,-3.13 - [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_on_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_on_black_24dp.xml
new file mode 100644
index 0000000..73748ef
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_on_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zM10.54,14.53L8.41,12.4l-1.06,1.06 3.18,3.18 6,-6 -1.06,-1.06 -4.93,4.95z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_all_out_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_all_out_black_24dp.xml
new file mode 100644
index 0000000..11e5a11
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_all_out_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M16.21,4.16l4,4v-4zM20.21,16.16l-4,4h4zM8.21,20.16l-4,-4v4zM4.21,8.16l4,-4h-4zM17.16,7.21c-2.73,-2.73 -7.17,-2.73 -9.9,0s-2.73,7.17 0,9.9 7.17,2.73 9.9,0 2.73,-7.16 0,-9.9zM16.06,16.01c-2.13,2.13 -5.57,2.13 -7.7,0s-2.13,-5.57 0,-7.7 5.57,-2.13 7.7,0 2.13,5.57 0,7.7z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_android_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_android_black_24dp.xml
new file mode 100644
index 0000000..401cbf6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_android_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L11,19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L16,19h1c0.55,0 1,-0.45 1,-1L18,8L6,8v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zM20.5,8c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zM15.53,2.16l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_announcement_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_announcement_black_24dp.xml
new file mode 100644
index 0000000..3d8eee2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_announcement_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,11h-2L11,5h2v6zM13,15h-2v-2h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_aspect_ratio_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_aspect_ratio_black_24dp.xml
new file mode 100644
index 0000000..2f3bc91
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_aspect_ratio_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_assessment_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_assessment_black_24dp.xml
new file mode 100644
index 0000000..75d0dde
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_assessment_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_black_24dp.xml
new file mode 100644
index 0000000..4a3eb4f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_ind_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_ind_black_24dp.xml
new file mode 100644
index 0000000..672f228
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_ind_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,7c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,19L6,19v-1.4c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1L18,19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_late_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_late_black_24dp.xml
new file mode 100644
index 0000000..9871806
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_late_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM13,18h-2v-2h2v2zM13,14h-2L11,8h2v6zM12,5c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_return_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_return_black_24dp.xml
new file mode 100644
index 0000000..2c1052d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_return_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM16,15h-4v3l-5,-5 5,-5v3h4v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_returned_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_returned_black_24dp.xml
new file mode 100644
index 0000000..f0e9114
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_returned_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,18l-5,-5h3L10,9h4v4h3l-5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_turned_in_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_turned_in_black_24dp.xml
new file mode 100644
index 0000000..1a2ce74
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_turned_in_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM10,17l-4,-4 1.41,-1.41L10,14.17l6.59,-6.59L18,9l-8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_autorenew_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_autorenew_black_24dp.xml
new file mode 100644
index 0000000..794ca6a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_autorenew_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_backup_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_backup_black_24dp.xml
new file mode 100644
index 0000000..0862816
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_backup_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_book_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_book_black_24dp.xml
new file mode 100644
index 0000000..811d5ac
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_book_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,2H6c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_black_24dp.xml
new file mode 100644
index 0000000..6a6a1b3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_border_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_border_black_24dp.xml
new file mode 100644
index 0000000..6ab9a0c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_border_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_bug_report_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_bug_report_black_24dp.xml
new file mode 100644
index 0000000..4d83902
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_bug_report_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_build_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_build_black_24dp.xml
new file mode 100644
index 0000000..18f5676
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_build_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_cached_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_cached_black_24dp.xml
new file mode 100644
index 0000000..5e776be
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_cached_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,8l-4,4h3c0,3.31 -2.69,6 -6,6 -1.01,0 -1.97,-0.25 -2.8,-0.7l-1.46,1.46C8.97,19.54 10.43,20 12,20c4.42,0 8,-3.58 8,-8h3l-4,-4zM6,12c0,-3.31 2.69,-6 6,-6 1.01,0 1.97,0.25 2.8,0.7l1.46,-1.46C15.03,4.46 13.57,4 12,4c-4.42,0 -8,3.58 -8,8H1l4,4 4,-4H6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_camera_enhance_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_camera_enhance_black_24dp.xml
new file mode 100644
index 0000000..84a604d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_camera_enhance_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,3L7.17,5L4,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2h-3.17L15,3L9,3zM12,18c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,17l1.25,-2.75L16,13l-2.75,-1.25L12,9l-1.25,2.75L8,13l2.75,1.25z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_card_giftcard_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_card_giftcard_black_24dp.xml
new file mode 100644
index 0000000..cbb6995
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_card_giftcard_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-2.18c0.11,-0.31 0.18,-0.65 0.18,-1 0,-1.66 -1.34,-3 -3,-3 -1.05,0 -1.96,0.54 -2.5,1.35l-0.5,0.67 -0.5,-0.68C10.96,2.54 10.05,2 9,2 7.34,2 6,3.34 6,5c0,0.35 0.07,0.69 0.18,1L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM15,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM9,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM20,19L4,19v-2h16v2zM20,14L4,14L4,8h5.08L7,10.83 8.6 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_card_membership_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_card_membership_black_24dp.xml
new file mode 100644
index 0000000..53689e2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_card_membership_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.11,0 -2,0.89 -2,2v11c0,1.11 0.89,2 2,2h4v5l4,-2 4,2v-5h4c1.11,0 2,-0.89 2,-2L22,4c0,-1.11 -0.89,-2 -2,-2zM20,15L4,15v-2h16v2zM20,10L4,10L4,4h16v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_card_travel_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_card_travel_black_24dp.xml
new file mode 100644
index 0000000..29cd27e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_card_travel_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-3L17,4c0,-1.11 -0.89,-2 -2,-2L9,2c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -2,0.89 -2,2v11c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM9,4h6v2L9,6L9,4zM20,19L4,19v-2h16v2zM20,14L4,14L4,8h3v2h2L9,8h6v2h2L17,8h3v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_change_history_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_change_history_black_24dp.xml
new file mode 100644
index 0000000..b88bcb3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_change_history_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,7.77L18.39,18H5.61L12,7.77M12,4L2,20h20L12,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_check_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_check_circle_black_24dp.xml
new file mode 100644
index 0000000..1241eda
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_check_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_chrome_reader_mode_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_chrome_reader_mode_black_24dp.xml
new file mode 100644
index 0000000..99b5867
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_chrome_reader_mode_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,12h7v1.5h-7zM13,9.5h7L20,11h-7zM13,14.5h7L20,16h-7zM21,4L3,4c-1.1,0 -2,0.9 -2,2v13c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,6c0,-1.1 -0.9,-2 -2,-2zM21,19h-9L12,6h9v13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_class_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_class_black_24dp.xml
new file mode 100644
index 0000000..811d5ac
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_class_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,2H6c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_code_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_code_black_24dp.xml
new file mode 100644
index 0000000..6f1ccb6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_code_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_compare_arrows_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_compare_arrows_black_24dp.xml
new file mode 100644
index 0000000..2341703
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_compare_arrows_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_copyright_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_copyright_black_24dp.xml
new file mode 100644
index 0000000..fad6892
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_copyright_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10.08,10.86c0.05,-0.33 0.16,-0.62 0.3,-0.87s0.34,-0.46 0.59,-0.62c0.24,-0.15 0.54,-0.22 0.91,-0.23 0.23,0.01 0.44,0.05 0.63,0.13 0.2,0.09 0.38,0.21 0.52,0.36s0.25,0.33 0.34,0.53 0.13,0.42 0.14,0.64h1.79c-0.02,-0.47 -0.11,-0.9 -0.28,-1.29s-0.4,-0.73 -0.7,-1.01 -0.66,-0.5 -1.08,-0.66 -0.88,-0.23 -1.39,-0.23c-0.65,0 -1.22,0.11 -1.7,0.34s-0.88,0.53 -1.2,0.92 -0.56,0.84 -0.71,1.36S8,11.29 8,11.87v0.27c0,0.58 0.08,1.12 0.23,1.64s0.39,0.97 0.71,1.35 0.72,0.69 1.2,0.91 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_credit_card_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_credit_card_black_24dp.xml
new file mode 100644
index 0000000..62a08a8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_credit_card_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_dashboard_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_dashboard_black_24dp.xml
new file mode 100644
index 0000000..663d572
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_dashboard_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_date_range_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_date_range_black_24dp.xml
new file mode 100644
index 0000000..5ac94e0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_date_range_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,11L7,11v2h2v-2zM13,11h-2v2h2v-2zM17,11h-2v2h2v-2zM19,4h-1L18,2h-2v2L8,4L8,2L6,2v2L5,4c-1.11,0 -1.99,0.9 -1.99,2L3,20c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM19,20L5,20L5,9h14v11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_delete_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_delete_black_24dp.xml
new file mode 100644
index 0000000..39e64d6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_delete_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_description_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_description_black_24dp.xml
new file mode 100644
index 0000000..38c3335
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_description_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_dns_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_dns_black_24dp.xml
new file mode 100644
index 0000000..1a8528c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_dns_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,13H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zM7,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM20,3H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1V4c0,-0.55 -0.45,-1 -1,-1zM7,9c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_done_all_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_done_all_black_24dp.xml
new file mode 100644
index 0000000..3148c51
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_done_all_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_done_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_done_black_24dp.xml
new file mode 100644
index 0000000..7affe9b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_done_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_donut_large_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_donut_large_black_24dp.xml
new file mode 100644
index 0000000..462681d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_donut_large_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,5.08V2c-5,0.5 -9,4.81 -9,10s4,9.5 9,10v-3.08c-3,-0.48 -6,-3.4 -6,-6.92s3,-6.44 6,-6.92zM18.97,11H22c-0.47,-5 -4,-8.53 -9,-9v3.08C16,5.51 18.54,8 18.97,11zM13,18.92V22c5,-0.47 8.53,-4 9,-9h-3.03c-0.43,3 -2.97,5.49 -5.97,5.92z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_donut_small_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_donut_small_black_24dp.xml
new file mode 100644
index 0000000..fd50bd0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_donut_small_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,9.16V2c-5,0.5 -9,4.79 -9,10s4,9.5 9,10v-7.16c-1,-0.41 -2,-1.52 -2,-2.84s1,-2.43 2,-2.84zM14.86,11H22c-0.48,-4.75 -4,-8.53 -9,-9v7.16c1,0.3 1.52,0.98 1.86,1.84zM13,14.84V22c5,-0.47 8.52,-4.25 9,-9h-7.14c-0.34,0.86 -0.86,1.54 -1.86,1.84z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_eject_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_eject_black_24dp.xml
new file mode 100644
index 0000000..d7b9bc9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_eject_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,17h14v2L5,19zM12,5L5.33,15h13.34z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_event_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_event_black_24dp.xml
new file mode 100644
index 0000000..22f1bb6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_event_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,12h-5v5h5v-5zM16,1v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2h-1L18,1h-2zM19,19L5,19L5,8h14v11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_event_seat_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_event_seat_black_24dp.xml
new file mode 100644
index 0000000..69de68c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_event_seat_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,18v3h3v-3h10v3h3v-6L4,15zM19,10h3v3h-3zM2,10h3v3L2,13zM17,13L7,13L7,5c0,-1.1 0.9,-2 2,-2h6c1.1,0 2,0.9 2,2v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_exit_to_app_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_exit_to_app_black_24dp.xml
new file mode 100644
index 0000000..6f40d77
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_exit_to_app_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_explore_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_explore_black_24dp.xml
new file mode 100644
index 0000000..68aa814
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_explore_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM14.19,14.19L6,18l3.81,-8.19L18,6l-3.81,8.19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_extension_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_extension_black_24dp.xml
new file mode 100644
index 0000000..d3dd094
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_extension_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_face_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_face_black_24dp.xml
new file mode 100644
index 0000000..739bbfb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_face_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM15,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8 0,-0.29 0.02,-0.58 0.05,-0.86 2.36,-1.05 4.23,-2.98 5.21,-5.37C11.07,8.33 14.05,10 17.42,10c0.78,0 1.53,-0.09 2.25,-0.26 0.21,0.71 0.33,1.47 0.33,2.26 0,4.41 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_black_24dp.xml
new file mode 100644
index 0000000..cfba5d8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_border_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_border_black_24dp.xml
new file mode 100644
index 0000000..0cfbad6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_border_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_feedback_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_feedback_black_24dp.xml
new file mode 100644
index 0000000..29f9baa
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_feedback_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_find_in_page_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_find_in_page_black_24dp.xml
new file mode 100644
index 0000000..0e70bac
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_find_in_page_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,19.59V8l-6,-6H6c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2H18c0.45,0 0.85,-0.15 1.19,-0.4l-4.43,-4.43c-0.8,0.52 -1.74,0.83 -2.76,0.83 -2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5c0,1.02 -0.31,1.96 -0.83,2.75L20,19.59zM9,13c0,1.66 1.34,3 3,3s3,-1.34 3,-3 -1.34,-3 -3,-3 -3,1.34 -3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_find_replace_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_find_replace_black_24dp.xml
new file mode 100644
index 0000000..2d2e1f3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_find_replace_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,6c1.38,0 2.63,0.56 3.54,1.46L12,10h6L18,4l-2.05,2.05C14.68,4.78 12.93,4 11,4c-3.53,0 -6.43,2.61 -6.92,6L6.1,10c0.46,-2.28 2.48,-4 4.9,-4zM16.64,15.14c0.66,-0.9 1.12,-1.97 1.28,-3.14L15.9,12c-0.46,2.28 -2.48,4 -4.9,4 -1.38,0 -2.63,-0.56 -3.54,-1.46L10,12L4,12v6l2.05,-2.05C7.32,17.22 9.07,18 11,18c1.55,0 2.98,-0.51 4.14,-1.36L20,21.49 21.49,20l-4.85,-4.86z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_fingerprint_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_fingerprint_black_24dp.xml
new file mode 100644
index 0000000..f650f74
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_fingerprint_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_flight_land_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_flight_land_black_24dp.xml
new file mode 100644
index 0000000..94afdf1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_flight_land_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2.5,19h19v2h-19zM9.68,13.27l4.35,1.16 5.31,1.42c0.8,0.21 1.62,-0.26 1.84,-1.06 0.21,-0.8 -0.26,-1.62 -1.06,-1.84l-5.31,-1.42 -2.76,-9.02L10.12,2v8.28L5.15,8.95l-0.93,-2.32 -1.45,-0.39v5.17l1.6,0.43 5.31,1.43z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_flight_takeoff_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_flight_takeoff_black_24dp.xml
new file mode 100644
index 0000000..bbc4064
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_flight_takeoff_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2.5,19h19v2h-19zM22.07,9.64c-0.21,-0.8 -1.04,-1.28 -1.84,-1.06L14.92,10l-6.9,-6.43 -1.93,0.51 4.14,7.17 -4.97,1.33 -1.97,-1.54 -1.45,0.39 1.82,3.16 0.77,1.33 1.6,-0.43 5.31,-1.42 4.35,-1.16L21,11.49c0.81,-0.23 1.28,-1.05 1.07,-1.85z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_back_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_back_black_24dp.xml
new file mode 100644
index 0000000..895b4f1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_back_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,7L7,7v2h2L9,7zM9,11L7,11v2h2v-2zM9,3c-1.11,0 -2,0.9 -2,2h2L9,3zM13,15h-2v2h2v-2zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM13,3h-2v2h2L13,3zM9,17v-2L7,15c0,1.1 0.89,2 2,2zM19,13h2v-2h-2v2zM19,9h2L21,7h-2v2zM19,17c1.1,0 2,-0.9 2,-2h-2v2zM5,7L3,7v12c0,1.1 0.89,2 2,2h12v-2L5,19L5,7zM15,5h2L17,3h-2v2zM15,17h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_front_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_front_black_24dp.xml
new file mode 100644
index 0000000..efcf92f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_front_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM5,21v-2L3,19c0,1.1 0.89,2 2,2zM3,9h2L5,7L3,7v2zM15,21h2v-2h-2v2zM19,3L9,3c-1.11,0 -2,0.9 -2,2v10c0,1.1 0.89,2 2,2h10c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,15L9,15L9,5h10v10zM11,21h2v-2h-2v2zM7,21h2v-2L7,19v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_gavel_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_gavel_black_24dp.xml
new file mode 100644
index 0000000..73a5be6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_gavel_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1,21h12v2H1zM5.245,8.07l2.83,-2.827 14.14,14.142 -2.828,2.828zM12.317,1l5.657,5.656 -2.83,2.83 -5.654,-5.66zM3.825,9.485l5.657,5.657 -2.828,2.828 -5.657,-5.657z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_get_app_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_get_app_black_24dp.xml
new file mode 100644
index 0000000..492b41d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_get_app_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_gif_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_gif_black_24dp.xml
new file mode 100644
index 0000000..9e8e7de
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_gif_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.5,9L13,9v6h-1.5zM9,9L6,9c-0.6,0 -1,0.5 -1,1v4c0,0.5 0.4,1 1,1h3c0.6,0 1,-0.5 1,-1v-2L8.5,12v1.5h-2v-3L10,10.5L10,10c0,-0.5 -0.4,-1 -1,-1zM19,10.5L19,9h-4.5v6L16,15v-2h2v-1.5h-2v-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_grade_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_grade_black_24dp.xml
new file mode 100644
index 0000000..a87ca09
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_grade_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_group_work_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_group_work_black_24dp.xml
new file mode 100644
index 0000000..34490ed
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_group_work_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM8,17.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM9.5,8c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5S9.5,9.38 9.5,8zM16,17.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_help_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_help_black_24dp.xml
new file mode 100644
index 0000000..1517747
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_help_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_help_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_help_outline_black_24dp.xml
new file mode 100644
index 0000000..e7cf8ea
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_help_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_highlight_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_highlight_off_black_24dp.xml
new file mode 100644
index 0000000..e368a10
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_highlight_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_history_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_history_black_24dp.xml
new file mode 100644
index 0000000..a61de1b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_history_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_home_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_home_black_24dp.xml
new file mode 100644
index 0000000..70fb291
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_home_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_hourglass_empty_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_hourglass_empty_black_24dp.xml
new file mode 100644
index 0000000..d55d5c8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_hourglass_empty_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,2v6h0.01L6,8.01 10,12l-4,4 0.01,0.01L6,16.01L6,22h12v-5.99h-0.01L18,16l-4,-4 4,-3.99 -0.01,-0.01L18,8L18,2L6,2zM16,16.5L16,20L8,20v-3.5l4,-4 4,4zM12,11.5l-4,-4L8,4h8v3.5l-4,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_hourglass_full_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_hourglass_full_black_24dp.xml
new file mode 100644
index 0000000..e56ebd5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_hourglass_full_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,2v6h0.01L6,8.01 10,12l-4,4 0.01,0.01H6V22h12v-5.99h-0.01L18,16l-4,-4 4,-3.99 -0.01,-0.01H18V2H6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_http_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_http_black_24dp.xml
new file mode 100644
index 0000000..bd4e915
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_http_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4.5,11h-2L2.5,9L1,9v6h1.5v-2.5h2L4.5,15L6,15L6,9L4.5,9v2zM7,10.5h1.5L8.5,15L10,15v-4.5h1.5L11.5,9L7,9v1.5zM12.5,10.5L14,10.5L14,15h1.5v-4.5L17,10.5L17,9h-4.5v1.5zM21.5,9L18,9v6h1.5v-2h2c0.8,0 1.5,-0.7 1.5,-1.5v-1c0,-0.8 -0.7,-1.5 -1.5,-1.5zM21.5,11.5h-2v-1h2v1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_https_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_https_black_24dp.xml
new file mode 100644
index 0000000..67a7c73
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_https_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_important_devices_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_important_devices_black_24dp.xml
new file mode 100644
index 0000000..18e3154
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_important_devices_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,11.01L18,11c-0.55,0 -1,0.45 -1,1v9c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-9c0,-0.55 -0.45,-0.99 -1,-0.99zM23,20h-5v-7h5v7zM20,2L2,2C0.89,2 0,2.89 0,4v12c0,1.1 0.89,2 2,2h7v2L7,20v2h8v-2h-2v-2h2v-2L2,16L2,4h18v5h2L22,4c0,-1.11 -0.9,-2 -2,-2zM11.97,9L11,6l-0.97,3L7,9l2.47,1.76 -0.94,2.91 2.47,-1.8 2.47,1.8 -0.94,-2.91L15,9h-3.03z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_info_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_info_black_24dp.xml
new file mode 100644
index 0000000..cc94088
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_info_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ 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"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_info_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_info_outline_black_24dp.xml
new file mode 100644
index 0000000..cf53e14
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_info_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_input_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_input_black_24dp.xml
new file mode 100644
index 0000000..fea69df
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_input_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3.01H3c-1.1,0 -2,0.9 -2,2V9h2V4.99h18v14.03H3V15H1v4.01c0,1.1 0.9,1.98 2,1.98h18c1.1,0 2,-0.88 2,-1.98v-14c0,-1.11 -0.9,-2 -2,-2zM11,16l4,-4 -4,-4v3H1v2h10v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_invert_colors_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_invert_colors_black_24dp.xml
new file mode 100644
index 0000000..e3f120f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_invert_colors_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.66,7.93L12,2.27 6.34,7.93c-3.12,3.12 -3.12,8.19 0,11.31C7.9,20.8 9.95,21.58 12,21.58c2.05,0 4.1,-0.78 5.66,-2.34 3.12,-3.12 3.12,-8.19 0,-11.31zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59s0.62,-3.11 1.76,-4.24L12,5.1v14.49z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_label_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_label_black_24dp.xml
new file mode 100644
index 0000000..1aa8d10
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_label_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_label_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_label_outline_black_24dp.xml
new file mode 100644
index 0000000..1580766
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_label_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16zM16,17H5V7h11l3.55,5L16,17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_language_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_language_black_24dp.xml
new file mode 100644
index 0000000..d07324c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_language_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_launch_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_launch_black_24dp.xml
new file mode 100644
index 0000000..60b75a5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_launch_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_lightbulb_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_lightbulb_outline_black_24dp.xml
new file mode 100644
index 0000000..2a8e9d7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_lightbulb_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,21c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-1L9,20v1zM12,2C8.14,2 5,5.14 5,9c0,2.38 1.19,4.47 3,5.74L8,17c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1v-2.26c1.81,-1.27 3,-3.36 3,-5.74 0,-3.86 -3.14,-7 -7,-7zM14.85,13.1l-0.85,0.6L14,16h-4v-2.3l-0.85,-0.6C7.8,12.16 7,10.63 7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.63 -0.8,3.16 -2.15,4.1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_line_style_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_line_style_black_24dp.xml
new file mode 100644
index 0000000..b76acf4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_line_style_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,16h5v-2L3,14v2zM9.5,16h5v-2h-5v2zM16,16h5v-2h-5v2zM3,20h2v-2L3,18v2zM7,20h2v-2L7,18v2zM11,20h2v-2h-2v2zM15,20h2v-2h-2v2zM19,20h2v-2h-2v2zM3,12h8v-2L3,10v2zM13,12h8v-2h-8v2zM3,4v4h18L21,4L3,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_line_weight_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_line_weight_black_24dp.xml
new file mode 100644
index 0000000..9b7c374
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_line_weight_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,17h18v-2L3,15v2zM3,20h18v-1L3,19v1zM3,13h18v-3L3,10v3zM3,4v4h18L21,4L3,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_list_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_list_black_24dp.xml
new file mode 100644
index 0000000..4c2fb88
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_list_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM7,13h14v-2L7,11v2zM7,17h14v-2L7,15v2zM7,7v2h14L21,7L7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_black_24dp.xml
new file mode 100644
index 0000000..67a7c73
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_open_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_open_black_24dp.xml
new file mode 100644
index 0000000..c812598
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_open_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_outline_black_24dp.xml
new file mode 100644
index 0000000..9a14b68
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2L8.9,8L8.9,6zM18,20L6,20L6,10h12v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_loyalty_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_loyalty_black_24dp.xml
new file mode 100644
index 0000000..af3ab2d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_loyalty_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21.41,11.58l-9,-9C12.05,2.22 11.55,2 11,2L4,2c-1.1,0 -2,0.9 -2,2v7c0,0.55 0.22,1.05 0.59,1.42l9,9c0.36,0.36 0.86,0.58 1.41,0.58 0.55,0 1.05,-0.22 1.41,-0.59l7,-7c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-0.55 -0.23,-1.06 -0.59,-1.42zM5.5,7C4.67,7 4,6.33 4,5.5S4.67,4 5.5,4 7,4.67 7,5.5 6.33,7 5.5,7zM17.27,15.27L13,19.54l-4.27,-4.27C8.28,14.81 8,14.19 8,13.5c0,-1.38 1.12,-2.5 2.5,-2.5 0.69,0 1.32,0.28 1.77,0.74l0.73,0.72 0.73,-0.73c0.45,-0.45 1.08,-0.73 1.77,-0.73 1.38 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_markunread_mailbox_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_markunread_mailbox_black_24dp.xml
new file mode 100644
index 0000000..34c9a3a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_markunread_mailbox_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6H10v6H8V4h6V0H6v6H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_motorcycle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_motorcycle_black_24dp.xml
new file mode 100644
index 0000000..539182f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_motorcycle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.44,9.03L15.41,5H11v2h3.59l2,2H5c-2.8,0 -5,2.2 -5,5s2.2,5 5,5c2.46,0 4.45,-1.69 4.9,-4h1.65l2.77,-2.77c-0.21,0.54 -0.32,1.14 -0.32,1.77 0,2.8 2.2,5 5,5s5,-2.2 5,-5c0,-2.65 -1.97,-4.77 -4.56,-4.97zM7.82,15C7.4,16.15 6.28,17 5,17c-1.63,0 -3,-1.37 -3,-3s1.37,-3 3,-3c1.28,0 2.4,0.85 2.82,2H5v2h2.82zM19,17c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_note_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_note_add_black_24dp.xml
new file mode 100644
index 0000000..ba7153f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_note_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,16h-3v3h-2v-3L8,16v-2h3v-3h2v3h3v2zM13,9L13,3.5L18.5,9L13,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_offline_pin_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_offline_pin_black_24dp.xml
new file mode 100644
index 0000000..d04f270
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_offline_pin_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM17,18L7,18v-2h10v2zM10.3,14L7,10.7l1.4,-1.4 1.9,1.9 5.3,-5.3L17,7.3 10.3,14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_opacity_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_opacity_black_24dp.xml
new file mode 100644
index 0000000..c9f47f8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_opacity_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.66,8L12,2.35 6.34,8C4.78,9.56 4,11.64 4,13.64s0.78,4.11 2.34,5.67 3.61,2.35 5.66,2.35 4.1,-0.79 5.66,-2.35S20,15.64 20,13.64 19.22,9.56 17.66,8zM6,14c0.01,-2 0.62,-3.27 1.76,-4.4L12,5.27l4.24,4.38C17.38,10.77 17.99,12 18,14H6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_browser_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_browser_black_24dp.xml
new file mode 100644
index 0000000..d597c37
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_browser_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_new_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_new_black_24dp.xml
new file mode 100644
index 0000000..60b75a5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_new_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_open_with_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_open_with_black_24dp.xml
new file mode 100644
index 0000000..c22fbd0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_open_with_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,9h4L14,6h3l-5,-5 -5,5h3v3zM9,10L6,10L6,7l-5,5 5,5v-3h3v-4zM23,12l-5,-5v3h-3v4h3v3l5,-5zM14,15h-4v3L7,18l5,5 5,-5h-3v-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_pageview_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_pageview_black_24dp.xml
new file mode 100644
index 0000000..6a990af
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_pageview_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.5,9C10.12,9 9,10.12 9,11.5s1.12,2.5 2.5,2.5 2.5,-1.12 2.5,-2.5S12.88,9 11.5,9zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM16.79,18.21l-2.91,-2.91c-0.69,0.44 -1.51,0.7 -2.39,0.7C9.01,16 7,13.99 7,11.5S9.01,7 11.5,7 16,9.01 16,11.5c0,0.88 -0.26,1.69 -0.7,2.39l2.91,2.9 -1.42,1.42z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_pan_tool_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_pan_tool_black_24dp.xml
new file mode 100644
index 0000000..6c09d03
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_pan_tool_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,5.5V20c0,2.2 -1.8,4 -4,4h-7.3c-1.08,0 -2.1,-0.43 -2.85,-1.19L1,14.83s1.26,-1.23 1.3,-1.25c0.22,-0.19 0.49,-0.29 0.79,-0.29 0.22,0 0.42,0.06 0.6,0.16 0.04,0.01 4.31,2.46 4.31,2.46V4c0,-0.83 0.67,-1.5 1.5,-1.5S11,3.17 11,4v7h1V1.5c0,-0.83 0.67,-1.5 1.5,-1.5S15,0.67 15,1.5V11h1V2.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5V11h1V5.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_payment_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_payment_black_24dp.xml
new file mode 100644
index 0000000..62a08a8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_payment_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_camera_mic_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_camera_mic_black_24dp.xml
new file mode 100644
index 0000000..f16141d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_camera_mic_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,5h-3.17L15,3L9,3L7.17,5L4,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v-2.09c-2.83,-0.48 -5,-2.94 -5,-5.91h2c0,2.21 1.79,4 4,4s4,-1.79 4,-4h2c0,2.97 -2.17,5.43 -5,5.91L13,21h7c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM14,13c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2L10,9c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_contact_calendar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_contact_calendar_black_24dp.xml
new file mode 100644
index 0000000..0d6b3bb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_contact_calendar_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,18L6,18v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_data_setting_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_data_setting_black_24dp.xml
new file mode 100644
index 0000000..2e22b3d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_data_setting_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.99,11.5c0.34,0 0.67,0.03 1,0.07L20,0 0,20h11.56c-0.04,-0.33 -0.07,-0.66 -0.07,-1 0,-4.14 3.36,-7.5 7.5,-7.5zM22.7,19.49c0.02,-0.16 0.04,-0.32 0.04,-0.49 0,-0.17 -0.01,-0.33 -0.04,-0.49l1.06,-0.83c0.09,-0.08 0.12,-0.21 0.06,-0.32l-1,-1.73c-0.06,-0.11 -0.19,-0.15 -0.31,-0.11l-1.24,0.5c-0.26,-0.2 -0.54,-0.37 -0.85,-0.49l-0.19,-1.32c-0.01,-0.12 -0.12,-0.21 -0.24,-0.21h-2c-0.12,0 -0.23,0.09 -0.25,0.21l-0.19,1.32c-0.3,0.13 -0.59,0.29 -0.85,0.49l-1.24,-0.5c-0.11,-0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_device_information_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_device_information_black_24dp.xml
new file mode 100644
index 0000000..2c4e5c7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_device_information_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,7h-2v2h2L13,7zM13,11h-2v6h2v-6zM17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_identity_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_identity_black_24dp.xml
new file mode 100644
index 0000000..f182b8d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_identity_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_media_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_media_black_24dp.xml
new file mode 100644
index 0000000..d2bf512
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_media_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,6L0,6v5h0.01L0,20c0,1.1 0.9,2 2,2h18v-2L2,20L2,6zM22,4h-8l-2,-2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L24,6c0,-1.1 -0.9,-2 -2,-2zM7,15l4.5,-6 3.5,4.51 2.5,-3.01L21,15L7,15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_phone_msg_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_phone_msg_black_24dp.xml
new file mode 100644
index 0000000..60f54b1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_phone_msg_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.58l2.2,-2.21c0.28,-0.27 0.36,-0.66 0.25,-1.01C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM12,3v10l3,-3h6V3h-9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_scan_wifi_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_scan_wifi_black_24dp.xml
new file mode 100644
index 0000000..a71c749
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_scan_wifi_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,3C6.95,3 3.15,4.85 0,7.23L12,22 24,7.25C20.85,4.87 17.05,3 12,3zM13,16h-2v-6h2v6zM11,8L11,6h2v2h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_pets_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_pets_black_24dp.xml
new file mode 100644
index 0000000..75874d9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_pets_black_24dp.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4.5,9.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,5.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,5.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.5,9.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.34,14.86c-0.87,-1.02 -1.6,-1.89 -2.48,-2.91 -0.46,-0.54 -1.05,-1.08 -1.75,-1.32 -0.11,-0.04 -0.22,-0.07 -0.33,-0.09 -0.25,-0.04 -0.52,-0.04 -0.78,-0.04s-0.53,0 -0.79,0.05c-0.11,0.02 -0.22,0.05 -0.33,0.09 -0.7,0.24 -1.28,0.78 -1.75,1.32 -0.87,1.02 -1.6,1.89 -2.48,2.91 -1.31,1.31 -2.92,2.76 -2.62,4.79 0.29,1.02 1.02,2.03 2.33,2.32 0.73,0.15 3.06,-0.44 5.54,-0.44h0.18c2.48,0 4.81,0.58 5.54,0.44 1.31,-0.29 2.04,-1.31 2.33,-2.32 0.31,-2.04 -1.3,-3.49 -2.61,-4.8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_alt_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_alt_black_24dp.xml
new file mode 100644
index 0000000..e7d980a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_alt_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,11h-8v6h8v-6zM23,19L23,4.98C23,3.88 22.1,3 21,3L3,3c-1.1,0 -2,0.88 -2,1.98L1,19c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2zM21,19.02L3,19.02L3,4.97h18v14.05z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_black_24dp.xml
new file mode 100644
index 0000000..b61c521
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,7h-8v6h8L19,7zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,1.98 2,1.98h18c1.1,0 2,-0.88 2,-1.98L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.98h18v14.03z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_play_for_work_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_play_for_work_black_24dp.xml
new file mode 100644
index 0000000..5ac5c85
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_play_for_work_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M11,5v5.59L7.5,10.59l4.5,4.5 4.5,-4.5L13,10.59L13,5h-2zM6,14c0,3.31 2.69,6 6,6s6,-2.69 6,-6h-2c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4L6,14z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_polymer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_polymer_black_24dp.xml
new file mode 100644
index 0000000..c2c9b5d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_polymer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,4h-4L7.11,16.63 4.5,12 9,4H5L0.5,12 5,20h4l7.89,-12.63L19.5,12 15,20h4l4.5,-8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_power_settings_new_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_power_settings_new_black_24dp.xml
new file mode 100644
index 0000000..26272ab
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_power_settings_new_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,3h-2v10h2L13,3zM17.83,5.17l-1.42,1.42C17.99,7.86 19,9.81 19,12c0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-2.19 1.01,-4.14 2.58,-5.42L6.17,5.17C4.23,6.82 3,9.26 3,12c0,4.97 4.03,9 9,9s9,-4.03 9,-9c0,-2.74 -1.23,-5.18 -3.17,-6.83z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_pregnant_woman_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_pregnant_woman_black_24dp.xml
new file mode 100644
index 0000000..f6e0151
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_pregnant_woman_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,4c0,-1.11 0.89,-2 2,-2s2,0.89 2,2 -0.89,2 -2,2 -2,-0.89 -2,-2zM16,13c-0.01,-1.34 -0.83,-2.51 -2,-3 0,-1.66 -1.34,-3 -3,-3s-3,1.34 -3,3v7h2v5h3v-5h3v-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_print_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_print_black_24dp.xml
new file mode 100644
index 0000000..7acdb18
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_print_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,8L5,8c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3 -3,-3zM16,19L8,19v-5h8v5zM19,12c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,3L6,3v4h12L18,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_query_builder_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_query_builder_black_24dp.xml
new file mode 100644
index 0000000..fdcce49
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_query_builder_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7L11,7v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_question_answer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_question_answer_black_24dp.xml
new file mode 100644
index 0000000..26eda09
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_question_answer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_receipt_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_receipt_black_24dp.xml
new file mode 100644
index 0000000..4714dde
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_receipt_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,17L6,17v-2h12v2zM18,13L6,13v-2h12v2zM18,9L6,9L6,7h12v2zM3,22l1.5,-1.5L6,22l1.5,-1.5L9,22l1.5,-1.5L12,22l1.5,-1.5L15,22l1.5,-1.5L18,22l1.5,-1.5L21,22L21,2l-1.5,1.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2 7.5,3.5 6,2 4.5,3.5 3,2v20z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_record_voice_over_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_record_voice_over_black_24dp.xml
new file mode 100644
index 0000000..3337021
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_record_voice_over_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,9m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,15c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4zM16.76,5.36l-1.68,1.69c0.84,1.18 0.84,2.71 0,3.89l1.68,1.69c2.02,-2.02 2.02,-5.07 0,-7.27zM20.07,2l-1.63,1.63c2.77,3.02 2.77,7.56 0,10.74L20.07,16c3.9,-3.89 3.91,-9.95 0,-14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_redeem_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_redeem_black_24dp.xml
new file mode 100644
index 0000000..cbb6995
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_redeem_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-2.18c0.11,-0.31 0.18,-0.65 0.18,-1 0,-1.66 -1.34,-3 -3,-3 -1.05,0 -1.96,0.54 -2.5,1.35l-0.5,0.67 -0.5,-0.68C10.96,2.54 10.05,2 9,2 7.34,2 6,3.34 6,5c0,0.35 0.07,0.69 0.18,1L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM15,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM9,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM20,19L4,19v-2h16v2zM20,14L4,14L4,8h5.08L7,10.83 8.6 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_reorder_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_reorder_black_24dp.xml
new file mode 100644
index 0000000..2b87cd8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_reorder_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,15h18v-2L3,13v2zM3,19h18v-2L3,17v2zM3,11h18L21,9L3,9v2zM3,5v2h18L21,5L3,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_report_problem_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_report_problem_black_24dp.xml
new file mode 100644
index 0000000..b3a9e03
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_report_problem_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_restore_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_restore_black_24dp.xml
new file mode 100644
index 0000000..a61de1b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_restore_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_room_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_room_black_24dp.xml
new file mode 100644
index 0000000..e3291a9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_room_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_rounded_corner_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_rounded_corner_black_24dp.xml
new file mode 100644
index 0000000..929f7cf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_rounded_corner_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,19h2v2h-2v-2zM19,17h2v-2h-2v2zM3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM3,5h2L5,3L3,3v2zM7,5h2L9,3L7,3v2zM15,21h2v-2h-2v2zM11,21h2v-2h-2v2zM15,21h2v-2h-2v2zM7,21h2v-2L7,19v2zM3,21h2v-2L3,19v2zM21,8c0,-2.76 -2.24,-5 -5,-5h-5v2h5c1.65,0 3,1.35 3,3v5h2L21,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_rowing_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_rowing_black_24dp.xml
new file mode 100644
index 0000000..a81e4b1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_rowing_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8.5,14.5L4,19l1.5,1.5L9,17h2l-2.5,-2.5zM15,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM21,21.01L18,24l-2.99,-3.01L15.01,19.5l-7.1,-7.09c-0.31,0.05 -0.61,0.07 -0.91,0.07v-2.16c1.66,0.03 3.61,-0.87 4.67,-2.04l1.4,-1.55c0.19,-0.21 0.43,-0.38 0.69,-0.5 0.29,-0.14 0.62,-0.23 0.96,-0.23h0.03C15.99,6.01 17,7.02 17,8.26v5.75c0,0.84 -0.35,1.61 -0.92,2.16l-3.58,-3.58v-2.27c-0.63,0.52 -1.43,1.02 -2.29,1.39L16.5,18L18,18l3,3.01z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_schedule_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_schedule_black_24dp.xml
new file mode 100644
index 0000000..fdcce49
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_schedule_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7L11,7v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_search_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_search_black_24dp.xml
new file mode 100644
index 0000000..affc7ba
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_search_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_applications_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_applications_black_24dp.xml
new file mode 100644
index 0000000..968166e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_applications_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM17.25,12c0,0.23 -0.02,0.46 -0.05,0.68l1.48,1.16c0.13,0.11 0.17,0.3 0.08,0.45l-1.4,2.42c-0.09,0.15 -0.27,0.21 -0.43,0.15l-1.74,-0.7c-0.36,0.28 -0.76,0.51 -1.18,0.69l-0.26,1.85c-0.03,0.17 -0.18,0.3 -0.35,0.3h-2.8c-0.17,0 -0.32,-0.13 -0.35,-0.29l-0.26,-1.85c-0.43,-0.18 -0.82,-0.41 -1.18,-0.69l-1.74,0.7c-0. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_backup_restore_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_backup_restore_black_24dp.xml
new file mode 100644
index 0000000..aa424c0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_backup_restore_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_black_24dp.xml
new file mode 100644
index 0000000..ace746c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.6 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_bluetooth_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_bluetooth_black_24dp.xml
new file mode 100644
index 0000000..da624c4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_bluetooth_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,24h2v-2h-2v2zM7,24h2v-2L7,22v2zM15,24h2v-2h-2v2zM17.71,5.71L12,0h-1v7.59L6.41,3 5,4.41 10.59,10 5,15.59 6.41,17 11,12.41L11,20h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,3.83l1.88,1.88L13,7.59L13,3.83zM14.88,14.29L13,16.17v-3.76l1.88,1.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_brightness_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_brightness_black_24dp.xml
new file mode 100644
index 0000000..26f971e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_brightness_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02zM8,16h2.5l1.5,1.5 1.5,-1.5L16,16v-2.5l1.5,-1.5 -1.5,-1.5L16,8h-2.5L12,6.5 10.5,8L8,8v2.5L6.5,12 8,13.5L8,16zM12,9c1.66,0 3,1.34 3,3s-1.34,3 -3,3L12,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_cell_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_cell_black_24dp.xml
new file mode 100644
index 0000000..76c94be
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_cell_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,24h2v-2L7,22v2zM11,24h2v-2h-2v2zM15,24h2v-2h-2v2zM16,0.01L8,0C6.9,0 6,0.9 6,2v16c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,2c0,-1.1 -0.9,-1.99 -2,-1.99zM16,16L8,16L8,4h8v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_ethernet_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_ethernet_black_24dp.xml
new file mode 100644
index 0000000..d60cda4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_ethernet_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.77,6.76L6.23,5.48 0.82,12l5.41,6.52 1.54,-1.28L3.42,12l4.35,-5.24zM7,13h2v-2L7,11v2zM17,11h-2v2h2v-2zM11,13h2v-2h-2v2zM17.77,5.48l-1.54,1.28L20.58,12l-4.35,5.24 1.54,1.28L23.18,12l-5.41,-6.52z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_antenna_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_antenna_black_24dp.xml
new file mode 100644
index 0000000..0da55e2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_antenna_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5c-3.87,0 -7,3.13 -7,7h2c0,-2.76 2.24,-5 5,-5s5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM13,14.29c0.88,-0.39 1.5,-1.26 1.5,-2.29 0,-1.38 -1.12,-2.5 -2.5,-2.5S9.5,10.62 9.5,12c0,1.02 0.62,1.9 1.5,2.29v3.3L7.59,21 9,22.41l3,-3 3,3L16.41,21 13,17.59v-3.3zM12,1C5.93,1 1,5.93 1,12h2c0,-4.97 4.03,-9 9,-9s9,4.03 9,9h2c0,-6.07 -4.93,-11 -11,-11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_component_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_component_black_24dp.xml
new file mode 100644
index 0000000..389fe7e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_component_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4L1,6v6h6L7,6L5,6L5,2zM9,16c0,1.3 0.84,2.4 2,2.82L11,23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2L9,14v2zM1,16c0,1.3 0.84,2.4 2,2.82L3,23h2v-4.18C6.16,18.4 7,17.3 7,16v-2L1,14v2zM21,6L21,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4h-2v6h6L23,6h-2zM13,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4L9,6v6h6L15,6h-2L13,2zM17,16c0,1.3 0.84,2.4 2,2.82L19,23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2h-6v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_composite_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_composite_black_24dp.xml
new file mode 100644
index 0000000..389fe7e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_composite_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4L1,6v6h6L7,6L5,6L5,2zM9,16c0,1.3 0.84,2.4 2,2.82L11,23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2L9,14v2zM1,16c0,1.3 0.84,2.4 2,2.82L3,23h2v-4.18C6.16,18.4 7,17.3 7,16v-2L1,14v2zM21,6L21,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4h-2v6h6L23,6h-2zM13,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4L9,6v6h6L15,6h-2L13,2zM17,16c0,1.3 0.84,2.4 2,2.82L19,23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2h-6v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_hdmi_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_hdmi_black_24dp.xml
new file mode 100644
index 0000000..c565522
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_hdmi_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,7V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v3H5v6l3,6v3h8v-3l3,-6V7h-1zM8,4h8v3h-2V5h-1v2h-2V5h-1v2H8V4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_svideo_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_svideo_black_24dp.xml
new file mode 100644
index 0000000..ca18729
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_svideo_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,11.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S5,10.67 5,11.5 5.67,13 6.5,13 8,12.33 8,11.5zM15,6.5c0,-0.83 -0.67,-1.5 -1.5,-1.5h-3C9.67,5 9,5.67 9,6.5S9.67,8 10.5,8h3c0.83,0 1.5,-0.67 1.5,-1.5zM8.5,15c-0.83,0 -1.5,0.67 -1.5,1.5S7.67,18 8.5,18s1.5,-0.67 1.5,-1.5S9.33,15 8.5,15zM12,1C5.93,1 1,5.93 1,12s4.93,11 11,11 11,-4.93 11,-11S18.07,1 12,1zM12,21c-4.96,0 -9,-4.04 -9,-9s4.04,-9 9,-9 9,4.04 9,9 -4.04,9 -9,9zM17.5,10c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_overscan_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_overscan_black_24dp.xml
new file mode 100644
index 0000000..b37284a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_overscan_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,5.5L10,8h4l-1.99,-2.5zM18,10v4l2.5,-1.99L18,10zM6,10l-2.5,2.01L6,14v-4zM14,16h-4l2.01,2.5L14,16zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_phone_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_phone_black_24dp.xml
new file mode 100644
index 0000000..e15d7ff
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_phone_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,9h-2v2h2L13,9zM17,9h-2v2h2L17,9zM20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.58l2.2,-2.21c0.28,-0.27 0.36,-0.66 0.25,-1.01C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,9v2h2L21,9h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_power_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_power_black_24dp.xml
new file mode 100644
index 0000000..931de86
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_power_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,24h2v-2L7,22v2zM11,24h2v-2h-2v2zM13,2h-2v10h2L13,2zM16.56,4.44l-1.45,1.45C16.84,6.94 18,8.83 18,11c0,3.31 -2.69,6 -6,6s-6,-2.69 -6,-6c0,-2.17 1.16,-4.06 2.88,-5.12L7.44,4.44C5.36,5.88 4,8.28 4,11c0,4.42 3.58,8 8,8s8,-3.58 8,-8c0,-2.72 -1.36,-5.12 -3.44,-6.56zM15,24h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_remote_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_remote_black_24dp.xml
new file mode 100644
index 0000000..73dd42c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_remote_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,9L9,9c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L16,10c0,-0.55 -0.45,-1 -1,-1zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM7.05,6.05l1.41,1.41C9.37,6.56 10.62,6 12,6s2.63,0.56 3.54,1.46l1.41,-1.41C15.68,4.78 13.93,4 12,4s-3.68,0.78 -4.95,2.05zM12,0C8.96,0 6.21,1.23 4.22,3.22l1.41,1.41C7.26,3.01 9.51,2 12,2s4.74,1.01 6.36,2.64l1.41,-1.41C17.79,1.23 15.04,0 12,0z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_voice_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_voice_black_24dp.xml
new file mode 100644
index 0000000..08c1a40
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_voice_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,24h2v-2L7,22v2zM12,13c1.66,0 2.99,-1.34 2.99,-3L15,4c0,-1.66 -1.34,-3 -3,-3S9,2.34 9,4v6c0,1.66 1.34,3 3,3zM11,24h2v-2h-2v2zM15,24h2v-2h-2v2zM19,10h-1.7c0,3 -2.54,5.1 -5.3,5.1S6.7,13 6.7,10L5,10c0,3.41 2.72,6.23 6,6.72L11,20h2v-3.28c3.28,-0.49 6,-3.31 6,-6.72z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_black_24dp.xml
new file mode 100644
index 0000000..50e6430
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,6L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L2,6v13c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6h-6zM10,4h4v2h-4L10,4zM9,18L9,9l7.5,4L9,18z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_two_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_two_black_24dp.xml
new file mode 100644
index 0000000..4f27a6d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_two_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,9L1,9v11c0,1.11 0.89,2 2,2h14c1.11,0 2,-0.89 2,-2L3,20L3,9zM18,5L18,3c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L5,5v11c0,1.11 0.89,2 2,2h14c1.11,0 2,-0.89 2,-2L23,5h-5zM12,3h4v2h-4L12,3zM12,15L12,8l5.5,3 -5.5,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_basket_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_basket_black_24dp.xml
new file mode 100644
index 0000000..0f128b2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_basket_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.21,9l-4.38,-6.56c-0.19,-0.28 -0.51,-0.42 -0.83,-0.42 -0.32,0 -0.64,0.14 -0.83,0.43L6.79,9L2,9c-0.55,0 -1,0.45 -1,1 0,0.09 0.01,0.18 0.04,0.27l2.54,9.27c0.23,0.84 1,1.46 1.92,1.46h13c0.92,0 1.69,-0.62 1.93,-1.46l2.54,-9.27L23,10c0,-0.55 -0.45,-1 -1,-1h-4.79zM9,9l3,-4.4L15,9L9,9zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_cart_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_cart_black_24dp.xml
new file mode 100644
index 0000000..11887e3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_cart_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2L7.42,15c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1L5.21,4l-0.94,-2L1,2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_speaker_notes_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_speaker_notes_black_24dp.xml
new file mode 100644
index 0000000..c95ec5a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_speaker_notes_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM8,14L6,14v-2h2v2zM8,11L6,11L6,9h2v2zM8,8L6,8L6,6h2v2zM15,14h-5v-2h5v2zM18,11h-8L10,9h8v2zM18,8h-8L10,6h8v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_spellcheck_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_spellcheck_black_24dp.xml
new file mode 100644
index 0000000..f4a1b50
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_spellcheck_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.45,16h2.09L9.43,3L7.57,3L2.46,16h2.09l1.12,-3h5.64l1.14,3zM6.43,11L8.5,5.48 10.57,11L6.43,11zM21.59,11.59l-8.09,8.09L9.83,16l-1.41,1.41 5.09,5.09L23,13l-1.41,-1.41z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_stars_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_stars_black_24dp.xml
new file mode 100644
index 0000000..61c5d7a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_stars_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM16.23,18L12,15.45 7.77,18l1.12,-4.81 -3.73,-3.23 4.92,-0.42L12,5l1.92,4.53 4.92,0.42 -3.73,3.23L16.23,18z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_store_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_store_black_24dp.xml
new file mode 100644
index 0000000..806e675
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_store_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4v2h16L20,4zM21,14v-2l-1,-5L4,7l-1,5v2h1v6h10v-6h4v6h2v-6h1zM12,18L6,18v-4h6v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_subject_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_subject_black_24dp.xml
new file mode 100644
index 0000000..0fcd6f4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_subject_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,17L4,17v2h10v-2zM20,9L4,9v2h16L20,9zM4,15h16v-2L4,13v2zM4,5v2h16L20,5L4,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_supervisor_account_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_supervisor_account_black_24dp.xml
new file mode 100644
index 0000000..8aa9c18
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_supervisor_account_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,12c1.38,0 2.49,-1.12 2.49,-2.5S17.88,7 16.5,7C15.12,7 14,8.12 14,9.5s1.12,2.5 2.5,2.5zM9,11c1.66,0 2.99,-1.34 2.99,-3S10.66,5 9,5C7.34,5 6,6.34 6,8s1.34,3 3,3zM16.5,14c-1.83,0 -5.5,0.92 -5.5,2.75L11,19h11v-2.25c0,-1.83 -3.67,-2.75 -5.5,-2.75zM9,13c-2.33,0 -7,1.17 -7,3.5L2,19h7v-2.25c0,-0.85 0.33,-2.34 2.37,-3.47C10.5,13.1 9.66,13 9,13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_horiz_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_horiz_black_24dp.xml
new file mode 100644
index 0000000..d571dc3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_horiz_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_black_24dp.xml
new file mode 100644
index 0000000..1c9a30b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,17.01V10h-2v7.01h-3L15,21l4,-3.99h-3zM9,3L5,6.99h3V14h2V6.99h3L9,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vertical_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vertical_circle_black_24dp.xml
new file mode 100644
index 0000000..81a9bb7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vertical_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM6.5,9L10,5.5 13.5,9L11,9v4L9,13L9,9L6.5,9zM17.5,15L14,18.5 10.5,15L13,15v-4h2v4h2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_system_update_alt_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_system_update_alt_black_24dp.xml
new file mode 100644
index 0000000..25b07b5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_system_update_alt_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,16.5l4,-4h-3v-9h-2v9L8,12.5l4,4zM21,3.5h-6v1.99h6v14.03L3,19.52L3,5.49h6L9,3.5L3,3.5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2v-14c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_black_24dp.xml
new file mode 100644
index 0000000..2b7d5e9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19L3,19L3,5h10v4h8v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_unselected_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_unselected_black_24dp.xml
new file mode 100644
index 0000000..4d4ad63
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_unselected_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1,9h2L3,7L1,7v2zM1,13h2v-2L1,11v2zM1,5h2L3,3c-1.1,0 -2,0.9 -2,2zM9,21h2v-2L9,19v2zM1,17h2v-2L1,15v2zM3,21v-2L1,19c0,1.1 0.9,2 2,2zM21,3h-8v6h10L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17h2v-2h-2v2zM9,5h2L11,3L9,3v2zM5,21h2v-2L5,19v2zM5,5h2L7,3L5,3v2zM21,21c1.1,0 2,-0.9 2,-2h-2v2zM21,13h2v-2h-2v2zM13,21h2v-2h-2v2zM17,21h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_theaters_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_theaters_black_24dp.xml
new file mode 100644
index 0000000..56665e6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_theaters_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,3v2h-2L16,3L8,3v2L6,5L6,3L4,3v18h2v-2h2v2h8v-2h2v2h2L20,3h-2zM8,17L6,17v-2h2v2zM8,13L6,13v-2h2v2zM8,9L6,9L6,7h2v2zM18,17h-2v-2h2v2zM18,13h-2v-2h2v2zM18,9h-2L16,7h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_down_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_down_black_24dp.xml
new file mode 100644
index 0000000..26ba95c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_down_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,3L6,3c-0.83,0 -1.54,0.5 -1.84,1.22l-3.02,7.05c-0.09,0.23 -0.14,0.47 -0.14,0.73v1.91l0.01,0.01L1,14c0,1.1 0.9,2 2,2h6.31l-0.95,4.57 -0.03,0.32c0,0.41 0.17,0.79 0.44,1.06L9.83,23l6.59,-6.59c0.36,-0.36 0.58,-0.86 0.58,-1.41L17,5c0,-1.1 -0.9,-2 -2,-2zM19,3v12h4L23,3h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_up_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_up_black_24dp.xml
new file mode 100644
index 0000000..34fb51a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_up_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-1.91l-0.01,-0.01L23,10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_thumbs_up_down_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_thumbs_up_down_black_24dp.xml
new file mode 100644
index 0000000..4679d8b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_thumbs_up_down_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,6c0,-0.55 -0.45,-1 -1,-1L5.82,5l0.66,-3.18 0.02,-0.23c0,-0.31 -0.13,-0.59 -0.33,-0.8L5.38,0 0.44,4.94C0.17,5.21 0,5.59 0,6v6.5c0,0.83 0.67,1.5 1.5,1.5h6.75c0.62,0 1.15,-0.38 1.38,-0.91l2.26,-5.29c0.07,-0.17 0.11,-0.36 0.11,-0.55L12,6zM22.5,10h-6.75c-0.62,0 -1.15,0.38 -1.38,0.91l-2.26,5.29c-0.07,0.17 -0.11,0.36 -0.11,0.55L12,18c0,0.55 0.45,1 1,1h5.18l-0.66,3.18 -0.02,0.24c0,0.31 0.13,0.59 0.33,0.8l0.79,0.78 4.94,-4.94c0.27,-0.27 0.44,-0.65 0.44,-1.06v-6.5c0,- [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_timeline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_timeline_black_24dp.xml
new file mode 100644
index 0000000..29ea1c5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_timeline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,8c0,1.1 -0.9,2 -2,2 -0.18,0 -0.35,-0.02 -0.51,-0.07l-3.56,3.55c0.05,0.16 0.07,0.34 0.07,0.52 0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-0.18 0.02,-0.36 0.07,-0.52l-2.55,-2.55c-0.16,0.05 -0.34,0.07 -0.52,0.07s-0.36,-0.02 -0.52,-0.07l-4.55,4.56c0.05,0.16 0.07,0.33 0.07,0.51 0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2 0.9,-2 2,-2c0.18,0 0.35,0.02 0.51,0.07l4.56,-4.55C8.02,9.36 8,9.18 8,9c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,0.18 -0.02,0.36 -0.07,0.52l2.55,2.55c0.16,-0.05 0.34,-0.07 0.52 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_toc_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_toc_black_24dp.xml
new file mode 100644
index 0000000..f319497
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_toc_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,9h14L17,7L3,7v2zM3,13h14v-2L3,11v2zM3,17h14v-2L3,15v2zM19,17h2v-2h-2v2zM19,7v2h2L21,7h-2zM19,13h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_today_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_today_black_24dp.xml
new file mode 100644
index 0000000..77fd98c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_today_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM7,10h5v5L7,15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_toll_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_toll_black_24dp.xml
new file mode 100644
index 0000000..d406ad1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_toll_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,4c-4.42,0 -8,3.58 -8,8s3.58,8 8,8 8,-3.58 8,-8 -3.58,-8 -8,-8zM15,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zM3,12c0,-2.61 1.67,-4.83 4,-5.65L7,4.26C3.55,5.15 1,8.27 1,12s2.55,6.85 6,7.74v-2.09c-2.33,-0.82 -4,-3.04 -4,-5.65z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_touch_app_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_touch_app_black_24dp.xml
new file mode 100644
index 0000000..dda5c85
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_touch_app_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,11.24L9,7.5C9,6.12 10.12,5 11.5,5S14,6.12 14,7.5v3.74c1.21,-0.81 2,-2.18 2,-3.74C16,5.01 13.99,3 11.5,3S7,5.01 7,7.5c0,1.56 0.79,2.93 2,3.74zM18.84,15.87l-4.54,-2.26c-0.17,-0.07 -0.35,-0.11 -0.54,-0.11L13,13.5v-6c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,6.67 10,7.5v10.74l-3.43,-0.72c-0.08,-0.01 -0.15,-0.03 -0.24,-0.03 -0.31,0 -0.59,0.13 -0.79,0.33l-0.79,0.8 4.94,4.94c0.27,0.27 0.65,0.44 1.06,0.44h6.79c0.75,0 1.33,-0.55 1.44,-1.28l0.75,-5.27c0.01,-0.07 0.02,-0.14 0.02, [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_track_changes_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_track_changes_black_24dp.xml
new file mode 100644
index 0000000..358d061
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_track_changes_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M19.07,4.93l-1.41,1.41C19.1,7.79 20,9.79 20,12c0,4.42 -3.58,8 -8,8s-8,-3.58 -8,-8c0,-4.08 3.05,-7.44 7,-7.93v2.02C8.16,6.57 6,9.03 6,12c0,3.31 2.69,6 6,6s6,-2.69 6,-6c0,-1.66 -0.67,-3.16 -1.76,-4.24l-1.41,1.41C15.55,9.9 16,10.9 16,12c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4c0,-1.86 1.28,-3.41 3,-3.86v2.14c-0.6,0.35 -1,0.98 -1,1.72 0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-0.74 -0.4,-1.38 -1,-1.72V2h-1C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10c0,-2.76 -1.12,-5.26 -2.93,-7.07z"
+ android:fillColor="#231F20"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_translate_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_translate_black_24dp.xml
new file mode 100644
index 0000000..1084151
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_translate_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_down_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_down_black_24dp.xml
new file mode 100644
index 0000000..121978a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_down_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,18l2.29,-2.29 -4.88,-4.88 -4,4L2,7.41 3.41,6l6,6 4,-4 6.3,6.29L22,12v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_flat_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_flat_black_24dp.xml
new file mode 100644
index 0000000..0871996
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_flat_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,12l-4,-4v3H3v2h15v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_up_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_up_black_24dp.xml
new file mode 100644
index 0000000..4c9da94
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_up_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,6l2.29,2.29 -4.88,4.88 -4,-4L2,16.59 3.41,18l6,-6 4,4 6.3,-6.29L22,12V6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_black_24dp.xml
new file mode 100644
index 0000000..6a6a1b3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_not_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_not_black_24dp.xml
new file mode 100644
index 0000000..6ab9a0c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_not_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_update_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_update_black_24dp.xml
new file mode 100644
index 0000000..a1a7cbd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_update_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1 -2.73,2.71 -2.73,7.08 0,9.79 2.73,2.71 7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29 -3.51,3.48 -9.21,3.48 -12.72,0 -3.5,-3.47 -3.53,-9.11 -0.02,-12.58 3.51,-3.47 9.14,-3.47 12.65,0L21,3v7.12zM12.5,8v4.25l3.5,2.08 -0.72,1.21L11,13V8h1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_verified_user_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_verified_user_black_24dp.xml
new file mode 100644
index 0000000..e0615e9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_verified_user_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM10,17l-4,-4 1.41,-1.41L10,14.17l6.59,-6.59L18,9l-8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_agenda_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_agenda_black_24dp.xml
new file mode 100644
index 0000000..3528f04
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_agenda_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,13L3,13c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zM20,3L3,3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1L21,4c0,-0.55 -0.45,-1 -1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_array_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_array_black_24dp.xml
new file mode 100644
index 0000000..65f37a4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_array_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,18h3V5H4v13zM18,5v13h3V5h-3zM8,18h9V5H8v13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_carousel_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_carousel_black_24dp.xml
new file mode 100644
index 0000000..d706781
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_carousel_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,19h10L17,4L7,4v15zM2,17h4L6,6L2,6v11zM18,6v11h4L22,6h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_column_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_column_black_24dp.xml
new file mode 100644
index 0000000..06694b8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_column_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,18h5L15,5h-5v13zM4,18h5L9,5L4,5v13zM16,5v13h5L21,5h-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_day_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_day_black_24dp.xml
new file mode 100644
index 0000000..ee43f68
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_day_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,21h19v-3H2v3zM20,8H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zM2,3v3h19V3H2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_headline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_headline_black_24dp.xml
new file mode 100644
index 0000000..7bee91b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_headline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,15h16v-2L4,13v2zM4,19h16v-2L4,17v2zM4,11h16L20,9L4,9v2zM4,5v2h16L20,5L4,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_list_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_list_black_24dp.xml
new file mode 100644
index 0000000..78118c1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_list_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,14h4v-4L4,10v4zM4,19h4v-4L4,15v4zM4,9h4L8,5L4,5v4zM9,14h12v-4L9,10v4zM9,19h12v-4L9,15v4zM9,5v4h12L21,5L9,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_module_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_module_black_24dp.xml
new file mode 100644
index 0000000..ab36b07
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_module_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,11h5L9,5L4,5v6zM4,18h5v-6L4,12v6zM10,18h5v-6h-5v6zM16,18h5v-6h-5v6zM10,11h5L15,5h-5v6zM16,5v6h5L21,5h-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_quilt_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_quilt_black_24dp.xml
new file mode 100644
index 0000000..a2e78f7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_quilt_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,18h5v-6h-5v6zM4,18h5L9,5L4,5v13zM16,18h5v-6h-5v6zM10,5v6h11L21,5L10,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_stream_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_stream_black_24dp.xml
new file mode 100644
index 0000000..fea8313
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_stream_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,18h17v-6H4v6zM4,5v6h17V5H4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_view_week_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_week_black_24dp.xml
new file mode 100644
index 0000000..a92c91c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_view_week_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,5L3,5c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1L7,6c0,-0.55 -0.45,-1 -1,-1zM20,5h-3c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1L21,6c0,-0.55 -0.45,-1 -1,-1zM13,5h-3c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1L14,6c0,-0.55 -0.45,-1 -1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_black_24dp.xml
new file mode 100644
index 0000000..e02f1d1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_off_black_24dp.xml
new file mode 100644
index 0000000..689f3f4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_watch_later_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_watch_later_black_24dp.xml
new file mode 100644
index 0000000..e9f85c8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_watch_later_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM16.2,16.2L11,13L11,7h1.5v5.2l4.5,2.7 -0.8,1.3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_work_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_work_black_24dp.xml
new file mode 100644
index 0000000..4a0748d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_work_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_youtube_searched_for_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_youtube_searched_for_black_24dp.xml
new file mode 100644
index 0000000..ff5b0b0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_youtube_searched_for_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.01,14h-0.8l-0.27,-0.27c0.98,-1.14 1.57,-2.61 1.57,-4.23 0,-3.59 -2.91,-6.5 -6.5,-6.5s-6.5,3 -6.5,6.5H2l3.84,4 4.16,-4H6.51C6.51,7 8.53,5 11.01,5s4.5,2.01 4.5,4.5c0,2.48 -2.02,4.5 -4.5,4.5 -0.65,0 -1.26,-0.14 -1.82,-0.38L7.71,15.1c0.97,0.57 2.09,0.9 3.3,0.9 1.61,0 3.08,-0.59 4.22,-1.57l0.27,0.27v0.79l5.01,4.99L22,19l-4.99,-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_zoom_in_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_zoom_in_black_24dp.xml
new file mode 100644
index 0000000..4ab8465
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_zoom_in_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14zM12,10h-2v2L9,12v-2L7,10L7,9h2L9,7h1v2h2v1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/action/ic_zoom_out_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/action/ic_zoom_out_black_24dp.xml
new file mode 100644
index 0000000..baaef6a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/action/ic_zoom_out_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14zM7,9h5v1L7,10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/alert/ic_add_alert_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/alert/ic_add_alert_black_24dp.xml
new file mode 100644
index 0000000..3e8e1c0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/alert/ic_add_alert_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10.01,21.01c0,1.1 0.89,1.99 1.99,1.99s1.99,-0.89 1.99,-1.99h-3.98zM18.88,16.82L18.88,11c0,-3.25 -2.25,-5.97 -5.29,-6.69v-0.72C13.59,2.71 12.88,2 12,2s-1.59,0.71 -1.59,1.59v0.72C7.37,5.03 5.12,7.75 5.12,11v5.82L3,18.94L3,20h18v-1.06l-2.12,-2.12zM16,13.01h-3v3h-2v-3L8,13.01L8,11h3L11,8h2v3h3v2.01z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_black_24dp.xml
new file mode 100644
index 0000000..3d98979
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ 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-2h2v2zM13,13h-2L11,7h2v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_outline_black_24dp.xml
new file mode 100644
index 0000000..a07a0f9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/alert/ic_warning_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/alert/ic_warning_black_24dp.xml
new file mode 100644
index 0000000..b3a9e03
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/alert/ic_warning_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_add_to_queue_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_add_to_queue_black_24dp.xml
new file mode 100644
index 0000000..c19e9aa
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_add_to_queue_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v12c0,1.1 0.89,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.11 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12zM16,10v2h-3v3h-2v-3L8,12v-2h3L11,7h2v3h3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_airplay_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_airplay_black_24dp.xml
new file mode 100644
index 0000000..18321a9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_airplay_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,22h12l-6,-6zM21,3H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h4v-2H3V5h18v12h-4v2h4c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_album_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_album_black_24dp.xml
new file mode 100644
index 0000000..9cc85ec
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_album_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,16.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zM12,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_art_track_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_art_track_black_24dp.xml
new file mode 100644
index 0000000..ecd3b05
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_art_track_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M22,13h-8v-2h8v2zM22,7h-8v2h8L22,7zM14,17h8v-2h-8v2zM12,9v6c0,1.1 -0.9,2 -2,2L4,17c-1.1,0 -2,-0.9 -2,-2L2,9c0,-1.1 0.9,-2 2,-2h6c1.1,0 2,0.9 2,2zM10.5,15l-2.25,-3 -1.75,2.26 -1.25,-1.51L3.5,15h7z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_av_timer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_av_timer_black_24dp.xml
new file mode 100644
index 0000000..dabe562
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_av_timer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,17c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1zM11,3v4h2L13,5.08c3.39,0.49 6,3.39 6,6.92 0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-1.68 0.59,-3.22 1.58,-4.42L12,13l1.41,-1.41 -6.8,-6.8v0.02C4.42,6.45 3,9.05 3,12c0,4.97 4.02,9 9,9 4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9h-1zM18,12c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1 0.45,1 1,1 1,-0.45 1,-1zM6,12c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_closed_caption_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_closed_caption_black_24dp.xml
new file mode 100644
index 0000000..107771d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_closed_caption_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,11L9.5,11v-0.5h-2v3h2L9.5,13L11,13v1c0,0.55 -0.45,1 -1,1L7,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1zM18,11h-1.5v-0.5h-2v3h2L16.5,13L18,13v1c0,0.55 -0.45,1 -1,1h-3c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_equalizer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_equalizer_black_24dp.xml
new file mode 100644
index 0000000..5048a58
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_equalizer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,20h4L14,4h-4v16zM4,20h4v-8L4,12v8zM16,9v11h4L20,9h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_explicit_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_explicit_black_24dp.xml
new file mode 100644
index 0000000..003730b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_explicit_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM15,9h-4v2h4v2h-4v2h4v2L9,17L9,7h6v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_forward_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_forward_black_24dp.xml
new file mode 100644
index 0000000..8ef7360
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_forward_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_rewind_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_rewind_black_24dp.xml
new file mode 100644
index 0000000..7e942d9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_rewind_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_dvr_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_dvr_black_24dp.xml
new file mode 100644
index 0000000..2b1fc82
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_dvr_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.5,10.5h2v1h-2zM4.5,10.5h2v3h-2zM21,3L3,3c-1.11,0 -2,0.89 -2,2v14c0,1.1 0.89,2 2,2h18c1.11,0 2,-0.9 2,-2L23,5c0,-1.11 -0.89,-2 -2,-2zM8,13.5c0,0.85 -0.65,1.5 -1.5,1.5L3,15L3,9h3.5c0.85,0 1.5,0.65 1.5,1.5v3zM12.62,15h-1.5L9.37,9h1.5l1,3.43 1,-3.43h1.5l-1.75,6zM21,11.5c0,0.6 -0.4,1.15 -0.9,1.4L21,15h-1.5l-0.85,-2L17.5,13v2L16,15L16,9h3.5c0.85,0 1.5,0.65 1.5,1.5v1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_manual_record_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_manual_record_black_24dp.xml
new file mode 100644
index 0000000..d0a1e76
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_manual_record_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_new_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_new_black_24dp.xml
new file mode 100644
index 0000000..6097dff
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_new_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM8.5,15L7.3,15l-2.55,-3.5L4.75,15L3.5,15L3.5,9h1.25l2.5,3.5L7.25,9L8.5,9v6zM13.5,10.26L11,10.26v1.12h2.5v1.26L11,12.64v1.11h2.5L13.5,15h-4L9.5,9h4v1.26zM20.5,14c0,0.55 -0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1L14.5,9h1.25v4.51h1.13L16.88,9.99h1.25v3.51h1.12L19.25,9h1.25v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_pin_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_pin_black_24dp.xml
new file mode 100644
index 0000000..a77ef95
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_pin_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.5,10.5h2v1h-2zM20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM9,11.5c0,0.85 -0.65,1.5 -1.5,1.5h-2v2L4,15L4,9h3.5c0.85,0 1.5,0.65 1.5,1.5v1zM12.5,15L11,15L11,9h1.5v6zM20,15h-1.2l-2.55,-3.5L16.25,15L15,15L15,9h1.25l2.5,3.5L18.75,9L20,9v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_smart_record_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_smart_record_black_24dp.xml
new file mode 100644
index 0000000..aacb3cf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_fiber_smart_record_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M9,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
+ android:fillColor="#010101"/>
+ <path
+ android:pathData="M17,4.26v2.09c2.33,0.82 4,3.04 4,5.65s-1.67,4.83 -4,5.65v2.09c3.45,-0.89 6,-4.01 6,-7.74s-2.55,-6.85 -6,-7.74z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_10_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_10_black_24dp.xml
new file mode 100644
index 0000000..7b3291a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_10_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,13c0,4.4 3.6,8 8,8s8,-3.6 8,-8h-2c0,3.3 -2.7,6 -6,6s-6,-2.7 -6,-6 2.7,-6 6,-6v4l5,-5 -5,-5v4c-4.4,0 -8,3.6 -8,8zM10.8,16L10,16v-3.3L9,13v-0.7l1.8,-0.6h0.1L10.9,16zM15.1,14.2c0,0.3 0,0.6 -0.1,0.8l-0.3,0.6s-0.3,0.3 -0.5,0.3 -0.4,0.1 -0.6,0.1 -0.4,0 -0.6,-0.1 -0.3,-0.2 -0.5,-0.3 -0.2,-0.3 -0.3,-0.6 -0.1,-0.5 -0.1,-0.8v-0.7c0,-0.3 0,-0.6 0.1,-0.8l0.3,-0.6s0.3,-0.3 0.5,-0.3 0.4,-0.1 0.6,-0.1 0.4,0 0.6,0.1 0.3,0.2 0.5,0.3 0.2,0.3 0.3,0.6 0.1,0.5 0.1,0.8v0.7zM14.3,1 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_30_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_30_black_24dp.xml
new file mode 100644
index 0000000..31e37a6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_30_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.6,13.5h0.4c0.2,0 0.4,-0.1 0.5,-0.2s0.2,-0.2 0.2,-0.4v-0.2s-0.1,-0.1 -0.1,-0.2 -0.1,-0.1 -0.2,-0.1h-0.5s-0.1,0.1 -0.2,0.1 -0.1,0.1 -0.1,0.2v0.2h-1c0,-0.2 0,-0.3 0.1,-0.5s0.2,-0.3 0.3,-0.4 0.3,-0.2 0.4,-0.2 0.4,-0.1 0.5,-0.1c0.2,0 0.4,0 0.6,0.1s0.3,0.1 0.5,0.2 0.2,0.2 0.3,0.4 0.1,0.3 0.1,0.5v0.3s-0.1,0.2 -0.1,0.3 -0.1,0.2 -0.2,0.2 -0.2,0.1 -0.3,0.2c0.2,0.1 0.4,0.2 0.5,0.4s0.2,0.4 0.2,0.6c0,0.2 0,0.4 -0.1,0.5s-0.2,0.3 -0.3,0.4 -0.3,0.2 -0.5,0.2 -0.4,0.1 -0.6,0.1 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_5_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_5_black_24dp.xml
new file mode 100644
index 0000000..5259b7b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_forward_5_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,13c0,4.4 3.6,8 8,8s8,-3.6 8,-8h-2c0,3.3 -2.7,6 -6,6s-6,-2.7 -6,-6 2.7,-6 6,-6v4l5,-5 -5,-5v4c-4.4,0 -8,3.6 -8,8zM10.7,13.9l0.2,-2.2h2.4v0.7h-1.7l-0.1,0.9s0.1,0 0.1,-0.1 0.1,0 0.1,-0.1 0.1,0 0.2,0h0.2c0.2,0 0.4,0 0.5,0.1s0.3,0.2 0.4,0.3 0.2,0.3 0.3,0.5 0.1,0.4 0.1,0.6c0,0.2 0,0.4 -0.1,0.5s-0.1,0.3 -0.3,0.5 -0.3,0.2 -0.5,0.3 -0.4,0.1 -0.6,0.1c-0.2,0 -0.4,0 -0.5,-0.1s-0.3,-0.1 -0.5,-0.2 -0.2,-0.2 -0.3,-0.4 -0.1,-0.3 -0.1,-0.5h0.8c0,0.2 0.1,0.3 0.2,0.4s0.2,0.1 0. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_games_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_games_black_24dp.xml
new file mode 100644
index 0000000..d1f300e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_games_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,7.5V2H9v5.5l3,3 3,-3zM7.5,9H2v6h5.5l3,-3 -3,-3zM9,16.5V22h6v-5.5l-3,-3 -3,3zM16.5,9l-3,3 3,3H22V9h-5.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_hd_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_hd_black_24dp.xml
new file mode 100644
index 0000000..5f8f568
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_hd_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM13,9h4c0.55,0 1,0.45 1,1v4c0,0.55 -0.45,1 -1,1h-4L13,9zM14.5,13.5h2v-3h-2v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_hearing_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_hearing_black_24dp.xml
new file mode 100644
index 0000000..f456a82
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_hearing_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_high_quality_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_high_quality_black_24dp.xml
new file mode 100644
index 0000000..1d683ba
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_high_quality_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_library_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_library_add_black_24dp.xml
new file mode 100644
index 0000000..f704ffe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_library_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11h-4v4h-2v-4L9,11L9,9h4L13,5h2v4h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_library_books_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_library_books_black_24dp.xml
new file mode 100644
index 0000000..a51097e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_library_books_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11L9,11L9,9h10v2zM15,15L9,15v-2h6v2zM19,7L9,7L9,5h10v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_library_music_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_library_music_black_24dp.xml
new file mode 100644
index 0000000..3de22fe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_library_music_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,7h-3v5.5c0,1.38 -1.12,2.5 -2.5,2.5S10,13.88 10,12.5s1.12,-2.5 2.5,-2.5c0.57,0 1.08,0.19 1.5,0.51L14,5h4v2zM4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_loop_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_loop_black_24dp.xml
new file mode 100644
index 0000000..ce8796c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_loop_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_black_24dp.xml
new file mode 100644
index 0000000..4f0dc04
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_none_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_none_black_24dp.xml
new file mode 100644
index 0000000..f008a4a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_none_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM10.8,4.9c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2l-0.01,6.2c0,0.66 -0.53,1.2 -1.19,1.2 -0.66,0 -1.2,-0.54 -1.2,-1.2L10.8,4.9zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_off_black_24dp.xml
new file mode 100644
index 0000000..7b1759b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,11h-1.7c0,0.74 -0.16,1.43 -0.43,2.05l1.23,1.23c0.56,-0.98 0.9,-2.09 0.9,-3.28zM14.98,11.17c0,-0.06 0.02,-0.11 0.02,-0.17L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v0.18l5.98,5.99zM4.27,3L3,4.27l6.01,6.01L9.01,11c0,1.66 1.33,3 2.99,3 0.22,0 0.44,-0.03 0.65,-0.08l1.66,1.66c-0.71,0.33 -1.5,0.52 -2.31,0.52 -2.76,0 -5.3,-2.1 -5.3,-5.1L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c0.91,-0.13 1.77,-0.45 2.54,-0.9L19.73,21 21,19.73 4.27,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_movie_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_movie_black_24dp.xml
new file mode 100644
index 0000000..a7f7b65
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_movie_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,4l2,4h-3l-2,-4h-2l2,4h-3l-2,-4H8l2,4H7L5,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_music_video_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_music_video_black_24dp.xml
new file mode 100644
index 0000000..d012978
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_music_video_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19L3,19L3,5h18v14zM8,15c0,-1.66 1.34,-3 3,-3 0.35,0 0.69,0.07 1,0.18L12,6h5v2h-3v7.03c-0.02,1.64 -1.35,2.97 -3,2.97 -1.66,0 -3,-1.34 -3,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_new_releases_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_new_releases_black_24dp.xml
new file mode 100644
index 0000000..ce9baf4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_new_releases_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,12l-2.44,-2.78 0.34,-3.68 -3.61,-0.82 -1.89,-3.18L12,3 8.6,1.54 6.71,4.72l-3.61,0.81 0.34,3.68L1,12l2.44,2.78 -0.34,3.69 3.61,0.82 1.89,3.18L12,21l3.4,1.46 1.89,-3.18 3.61,-0.82 -0.34,-3.68L23,12zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_not_interested_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_not_interested_black_24dp.xml
new file mode 100644
index 0000000..6944a90
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_not_interested_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8 0,-1.85 0.63,-3.55 1.69,-4.9L16.9,18.31C15.55,19.37 13.85,20 12,20zM18.31,16.9L7.1,5.69C8.45,4.63 10.15,4 12,4c4.42,0 8,3.58 8,8 0,1.85 -0.63,3.55 -1.69,4.9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_black_24dp.xml
new file mode 100644
index 0000000..bb28a6c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_filled_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_filled_black_24dp.xml
new file mode 100644
index 0000000..d7c2212
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_filled_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,16L9,16L9,8h2v8zM15,16h-2L13,8h2v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_outline_black_24dp.xml
new file mode 100644
index 0000000..169c05d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_play_arrow_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_play_arrow_black_24dp.xml
new file mode 100644
index 0000000..bf9b895
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_play_arrow_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,5v14l11,-7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_filled_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_filled_black_24dp.xml
new file mode 100644
index 0000000..85ee3ba
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_filled_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_outline_black_24dp.xml
new file mode 100644
index 0000000..f7a497a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_black_24dp.xml
new file mode 100644
index 0000000..905d86e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_check_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_check_black_24dp.xml
new file mode 100644
index 0000000..4f7a1c1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_check_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM2,16h8v-2L2,14v2zM21.5,11.5L23,13l-6.99,7 -4.51,-4.5L13,14l3.01,3 5.49,-5.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_play_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_play_black_24dp.xml
new file mode 100644
index 0000000..f20557c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_play_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,9L2,9v2h17L19,9zM19,5L2,5v2h17L19,5zM2,15h13v-2L2,13v2zM17,13v6l5,-3 -5,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_black_24dp.xml
new file mode 100644
index 0000000..f704ffe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11h-4v4h-2v-4L9,11L9,9h4L13,5h2v4h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_music_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_music_black_24dp.xml
new file mode 100644
index 0000000..848a860
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_music_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,6L3,6v2h12L15,6zM15,10L3,10v2h12v-2zM3,16h8v-2L3,14v2zM17,6v8.18c-0.31,-0.11 -0.65,-0.18 -1,-0.18 -1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3L19,8h3L22,6h-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_play_next_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_play_next_black_24dp.xml
new file mode 100644
index 0000000..dab1589
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_play_next_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v12c0,1.1 0.89,2 2,2h5v2h8v-2h2v-2L3,17L3,5h18v8h2L23,5c0,-1.11 -0.9,-2 -2,-2zM13,10L13,7h-2v3L8,10v2h3v3h2v-3h3v-2h-3zM24,18l-4.5,4.5L18,21l3,-3 -3,-3 1.5,-1.5L24,18z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_radio_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_radio_black_24dp.xml
new file mode 100644
index 0000000..42bc4f3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_radio_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.24,6.15C2.51,6.43 2,7.17 2,8v12c0,1.1 0.89,2 2,2h16c1.11,0 2,-0.9 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2L8.3,6l8.26,-3.34L15.88,1 3.24,6.15zM7,20c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM20,12h-2v-2h-2v2L4,12L4,8h16v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_recent_actors_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_recent_actors_black_24dp.xml
new file mode 100644
index 0000000..31589a1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_recent_actors_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,5v14h2L23,5h-2zM17,19h2L19,5h-2v14zM14,5L2,5c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1L15,6c0,-0.55 -0.45,-1 -1,-1zM8,7.75c1.24,0 2.25,1.01 2.25,2.25S9.24,12.25 8,12.25 5.75,11.24 5.75,10 6.76,7.75 8,7.75zM12.5,17h-9v-0.75c0,-1.5 3,-2.25 4.5,-2.25s4.5,0.75 4.5,2.25L12.5,17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_remove_from_queue_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_remove_from_queue_black_24dp.xml
new file mode 100644
index 0000000..4ccb896
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_remove_from_queue_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v12c0,1.1 0.89,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.11 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12zM16,10v2L8,12v-2h8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_black_24dp.xml
new file mode 100644
index 0000000..e7c67d7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_one_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_one_black_24dp.xml
new file mode 100644
index 0000000..fc8c83a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_one_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_10_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_10_black_24dp.xml
new file mode 100644
index 0000000..1470264
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_10_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5L12,1L7,6l5,5L12,7c3.3,0 6,2.7 6,6s-2.7,6 -6,6 -6,-2.7 -6,-6L4,13c0,4.4 3.6,8 8,8s8,-3.6 8,-8 -3.6,-8 -8,-8zM10.9,16L10,16v-3.3L9,13v-0.7l1.8,-0.6h0.1L10.9,16zM15.2,14.2c0,0.3 0,0.6 -0.1,0.8l-0.3,0.6s-0.3,0.3 -0.5,0.3 -0.4,0.1 -0.6,0.1 -0.4,0 -0.6,-0.1 -0.3,-0.2 -0.5,-0.3 -0.2,-0.3 -0.3,-0.6 -0.1,-0.5 -0.1,-0.8v-0.7c0,-0.3 0,-0.6 0.1,-0.8l0.3,-0.6s0.3,-0.3 0.5,-0.3 0.4,-0.1 0.6,-0.1 0.4,0 0.6,0.1c0.2,0.1 0.3,0.2 0.5,0.3s0.2,0.3 0.3,0.6 0.1,0.5 0.1,0.8v0.7zM [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_30_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_30_black_24dp.xml
new file mode 100644
index 0000000..cccac86
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_30_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5L12,1L7,6l5,5L12,7c3.3,0 6,2.7 6,6s-2.7,6 -6,6 -6,-2.7 -6,-6L4,13c0,4.4 3.6,8 8,8s8,-3.6 8,-8 -3.6,-8 -8,-8zM9.6,13.5h0.4c0.2,0 0.4,-0.1 0.5,-0.2s0.2,-0.2 0.2,-0.4v-0.2s-0.1,-0.1 -0.1,-0.2 -0.1,-0.1 -0.2,-0.1h-0.5s-0.1,0.1 -0.2,0.1 -0.1,0.1 -0.1,0.2v0.2h-1c0,-0.2 0,-0.3 0.1,-0.5s0.2,-0.3 0.3,-0.4 0.3,-0.2 0.4,-0.2 0.4,-0.1 0.5,-0.1c0.2,0 0.4,0 0.6,0.1s0.3,0.1 0.5,0.2 0.2,0.2 0.3,0.4 0.1,0.3 0.1,0.5v0.3s-0.1,0.2 -0.1,0.3 -0.1,0.2 -0.2,0.2 -0.2,0.1 -0.3,0.2c0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_5_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_5_black_24dp.xml
new file mode 100644
index 0000000..4cb5bf0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_5_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5L12,1L7,6l5,5L12,7c3.3,0 6,2.7 6,6s-2.7,6 -6,6 -6,-2.7 -6,-6L4,13c0,4.4 3.6,8 8,8s8,-3.6 8,-8 -3.6,-8 -8,-8zM10.7,13.9l0.2,-2.2h2.4v0.7h-1.7l-0.1,0.9s0.1,0 0.1,-0.1 0.1,0 0.1,-0.1 0.1,0 0.2,0h0.2c0.2,0 0.4,0 0.5,0.1s0.3,0.2 0.4,0.3 0.2,0.3 0.3,0.5 0.1,0.4 0.1,0.6c0,0.2 0,0.4 -0.1,0.5s-0.1,0.3 -0.3,0.5 -0.3,0.2 -0.4,0.3 -0.4,0.1 -0.6,0.1c-0.2,0 -0.4,0 -0.5,-0.1s-0.3,-0.1 -0.5,-0.2 -0.2,-0.2 -0.3,-0.4 -0.1,-0.3 -0.1,-0.5h0.8c0,0.2 0.1,0.3 0.2,0.4s0.2,0.1 0.4, [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_black_24dp.xml
new file mode 100644
index 0000000..0ff0a64
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6 -6,-2.69 -6,-6H4c0,4.42 3.58,8 8,8s8,-3.58 8,-8 -3.58,-8 -8,-8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_shuffle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_shuffle_black_24dp.xml
new file mode 100644
index 0000000..ec80046
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_shuffle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10.59,9.17L5.41,4 4,5.41l5.17,5.17 1.42,-1.41zM14.5,4l2.04,2.04L4,18.59 5.41,20 17.96,7.46 20,9.5L20,4h-5.5zM14.83,13.41l-1.41,1.41 3.13,3.13L14.5,20L20,20v-5.5l-2.04,2.04 -3.13,-3.13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_next_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_next_black_24dp.xml
new file mode 100644
index 0000000..1253ff0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_next_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_previous_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_previous_black_24dp.xml
new file mode 100644
index 0000000..cd05efe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_previous_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_slow_motion_video_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_slow_motion_video_black_24dp.xml
new file mode 100644
index 0000000..fe1d2d9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_slow_motion_video_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM11,4.07L11,2.05c-2.01,0.2 -3.84,1 -5.32,2.21L7.1,5.69c1.11,-0.86 2.44,-1.44 3.9,-1.62zM5.69,7.1L4.26,5.68C3.05,7.16 2.25,8.99 2.05,11h2.02c0.18,-1.46 0.76,-2.79 1.62,-3.9zM4.07,13L2.05,13c0.2,2.01 1,3.84 2.21,5.32l1.43,-1.43c-0.86,-1.1 -1.44,-2.43 -1.62,-3.89zM5.68,19.74C7.16,20.95 9,21.75 11,21.95v-2.02c-1.46,-0.18 -2.79,-0.76 -3.9,-1.62l-1.42,1.43 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_snooze_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_snooze_black_24dp.xml
new file mode 100644
index 0000000..6d88f7d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_snooze_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zM9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_sort_by_alpha_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_sort_by_alpha_black_24dp.xml
new file mode 100644
index 0000000..97bf945
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_sort_by_alpha_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.94,4.66h-4.72l2.36,-2.36zM10.25,19.37h4.66l-2.33,2.33zM6.1,6.27L1.6,17.73h1.84l0.92,-2.45h5.11l0.92,2.45h1.84L7.74,6.27L6.1,6.27zM4.97,13.64l1.94,-5.18 1.94,5.18L4.97,13.64zM15.73,16.14h6.12v1.59h-8.53v-1.29l5.92,-8.56h-5.88v-1.6h8.3v1.26l-5.93,8.6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_stop_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_stop_black_24dp.xml
new file mode 100644
index 0000000..c428d72
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_stop_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,6h12v12H6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_subscriptions_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_subscriptions_black_24dp.xml
new file mode 100644
index 0000000..6f0ed45
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_subscriptions_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,8L4,8L4,6h16v2zM18,2L6,2v2h12L18,2zM22,12v8c0,1.1 -0.9,2 -2,2L4,22c-1.1,0 -2,-0.9 -2,-2v-8c0,-1.1 0.9,-2 2,-2h16c1.1,0 2,0.9 2,2zM16,16l-6,-3.27v6.53L16,16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_subtitles_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_subtitles_black_24dp.xml
new file mode 100644
index 0000000..4a6c028
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_subtitles_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM4,12h4v2L4,14v-2zM14,18L4,18v-2h10v2zM20,18h-4v-2h4v2zM20,14L10,14v-2h10v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_surround_sound_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_surround_sound_black_24dp.xml
new file mode 100644
index 0000000..6c155d3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_surround_sound_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM7.76,16.24l-1.41,1.41C4.78,16.1 4,14.05 4,12c0,-2.05 0.78,-4.1 2.34,-5.66l1.41,1.41C6.59,8.93 6,10.46 6,12s0.59,3.07 1.76,4.24zM12,16c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zM17.66,17.66l-1.41,-1.41C17.41,15.07 18,13.54 18,12s-0.59,-3.07 -1.76,-4.24l1.41,-1.41C19.22,7.9 20,9.95 20,12c0,2.05 -0.78,4.1 -2.34,5.66zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_video_library_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_video_library_black_24dp.xml
new file mode 100644
index 0000000..f808aee
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_video_library_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12,14.5v-9l6,4.5 -6,4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_black_24dp.xml
new file mode 100644
index 0000000..e23eac8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,10.5V7c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_off_black_24dp.xml
new file mode 100644
index 0000000..b7d0b1b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,6.5l-4,4V7c0,-0.55 -0.45,-1 -1,-1H9.82L21,17.18V6.5zM3.27,2L2,3.27 4.73,6H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.21,0 0.39,-0.08 0.54,-0.18L19.73,21 21,19.73 3.27,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_down_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_down_black_24dp.xml
new file mode 100644
index 0000000..06d03cc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_down_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM5,9v6h4l5,5V4L9,9H5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_mute_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_mute_black_24dp.xml
new file mode 100644
index 0000000..b6c24e3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_mute_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,9v6h4l5,5V4l-5,5H7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_off_black_24dp.xml
new file mode 100644
index 0000000..3aed66d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_up_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_up_black_24dp.xml
new file mode 100644
index 0000000..bb0c74b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_up_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_web_asset_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_web_asset_black_24dp.xml
new file mode 100644
index 0000000..e38da99
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_web_asset_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM19,18L5,18L5,8h14v10z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/av/ic_web_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/av/ic_web_black_24dp.xml
new file mode 100644
index 0000000..107f010
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/av/ic_web_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM15,18L4,18v-4h11v4zM15,13L4,13L4,9h11v4zM20,18h-4L16,9h4v9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_business_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_business_black_24dp.xml
new file mode 100644
index 0000000..8924cc8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_business_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,7L12,3L2,3v18h20L22,7L12,7zM6,19L4,19v-2h2v2zM6,15L4,15v-2h2v2zM6,11L4,11L4,9h2v2zM6,7L4,7L4,5h2v2zM10,19L8,19v-2h2v2zM10,15L8,15v-2h2v2zM10,11L8,11L8,9h2v2zM10,7L8,7L8,5h2v2zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2L12,9h8v10zM18,11h-2v2h2v-2zM18,15h-2v2h2v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_black_24dp.xml
new file mode 100644
index 0000000..ebf9de6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_end_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_end_black_24dp.xml
new file mode 100644
index 0000000..f83a534
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_end_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_made_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_made_black_24dp.xml
new file mode 100644
index 0000000..c5b108d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_made_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,5v2h6.59L4,18.59 5.41,20 17,8.41V15h2V5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_merge_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_merge_black_24dp.xml
new file mode 100644
index 0000000..40b6b38
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_merge_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,20.41L18.41,19 15,15.59 13.59,17 17,20.41zM7.5,8H11v5.59L5.59,19 7,20.41l6,-6V8h3.5L12,3.5 7.5,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_black_24dp.xml
new file mode 100644
index 0000000..ed8ff39
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.59,7L12,14.59 6.41,9H11V7H3v8h2v-4.59l7,7 9,-9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_outgoing_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_outgoing_black_24dp.xml
new file mode 100644
index 0000000..ac0dd75
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_outgoing_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,8.41l9,9 7,-7V15h2V7h-8v2h4.59L12,14.59 4.41,7 3,8.41z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_received_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_received_black_24dp.xml
new file mode 100644
index 0000000..aae066e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_received_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,5.41L18.59,4 7,15.59V9H5v10h10v-2H8.41z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_split_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_split_black_24dp.xml
new file mode 100644
index 0000000..327e1d2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_split_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,4l2.29,2.29 -2.88,2.88 1.42,1.42 2.88,-2.88L20,10L20,4zM10,4L4,4v6l2.29,-2.29 4.71,4.7L11,20h2v-8.41l-5.29,-5.3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_black_24dp.xml
new file mode 100644
index 0000000..e3489bd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_bubble_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_bubble_black_24dp.xml
new file mode 100644
index 0000000..3eeab82
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_bubble_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_bubble_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_bubble_outline_black_24dp.xml
new file mode 100644
index 0000000..880a1b1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_bubble_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L6,16l-2,2L4,4h16v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_clear_all_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_clear_all_black_24dp.xml
new file mode 100644
index 0000000..1b0e565
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_clear_all_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_comment_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_comment_black_24dp.xml
new file mode 100644
index 0000000..13ed321
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_comment_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_contact_mail_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_contact_mail_black_24dp.xml
new file mode 100644
index 0000000..e45f371
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_contact_mail_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,8V7l-3,2 -3,-2v1l3,2 3,-2zm1,-5H2C0.9,3 0,3.9 0,5v14c0,1.1 0.9,2 2,2h20c1.1,0 1.99,-0.9 1.99,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM8,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H2v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1zm8,-6h-8V6h8v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_contact_phone_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_contact_phone_black_24dp.xml
new file mode 100644
index 0000000..f1124cf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_contact_phone_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,3L2,3C0.9,3 0,3.9 0,5v14c0,1.1 0.9,2 2,2h20c1.1,0 1.99,-0.9 1.99,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM8,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM14,18L2,18v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1zM17.85,14h1.64L21,16l-1.99,1.99c-1.31,-0.98 -2.28,-2.38 -2.73,-3.99 -0.18,-0.64 -0.28,-1.31 -0.28,-2s0.1,-1.36 0.28,-2c0.45,-1.62 1.42,-3.01 2.73,-3.99L21,8l-1.51,2h-1.64c-0.22,0.63 -0.35,1.3 -0.35,2s0.13,1.37 0.35,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_contacts_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_contacts_black_24dp.xml
new file mode 100644
index 0000000..674b66b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_contacts_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,0L4,0v2h16L20,0zM4,24h16v-2L4,22v2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,6.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17L7,17v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5L17,17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialer_sip_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialer_sip_black_24dp.xml
new file mode 100644
index 0000000..244afde
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialer_sip_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3h-1v5h1L17,3zM15,5h-2L13,4h2L15,3h-3v3h2v1h-2v1h3L15,5zM18,3v5h1L19,6h2L21,3h-3zM20,5h-1L19,4h1v1zM20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.01,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.27,-0.26 0.35,-0.65 0.24,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialpad_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialpad_black_24dp.xml
new file mode 100644
index 0000000..ab1e516
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialpad_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,19c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_email_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_email_black_24dp.xml
new file mode 100644
index 0000000..ce97ab8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_email_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_forum_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_forum_black_24dp.xml
new file mode 100644
index 0000000..26eda09
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_forum_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_contacts_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_contacts_black_24dp.xml
new file mode 100644
index 0000000..9c084ab
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_contacts_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,5c-1.11,-0.35 -2.33,-0.5 -3.5,-0.5 -1.95,0 -4.05,0.4 -5.5,1.5 -1.45,-1.1 -3.55,-1.5 -5.5,-1.5S2.45,4.9 1,6v14.65c0,0.25 0.25,0.5 0.5,0.5 0.1,0 0.15,-0.05 0.25,-0.05C3.1,20.45 5.05,20 6.5,20c1.95,0 4.05,0.4 5.5,1.5 1.35,-0.85 3.8,-1.5 5.5,-1.5 1.65,0 3.35,0.3 4.75,1.05 0.1,0.05 0.15,0.05 0.25,0.05 0.25,0 0.5,-0.25 0.5,-0.5L23,6c-0.6,-0.45 -1.25,-0.75 -2,-1zM21,18.5c-1.1,-0.35 -2.3,-0.5 -3.5,-0.5 -1.7,0 -4.15,0.65 -5.5,1.5L12,8c1.35,-0.85 3.8,-1.5 5.5,-1.5 1.2 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_export_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_export_black_24dp.xml
new file mode 100644
index 0000000..a2d1fa9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_export_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_off_black_24dp.xml
new file mode 100644
index 0000000..29ac30c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.65,20.87l-2.35,-2.35 -6.3,-6.29 -3.56,-3.57 -1.42,-1.41L4.27,4.5 3,5.77l2.78,2.78c-2.55,3.14 -2.36,7.76 0.56,10.69C7.9,20.8 9.95,21.58 12,21.58c1.79,0 3.57,-0.59 5.03,-1.78l2.7,2.7L21,21.23l-0.35,-0.36zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59c0,-1.32 0.43,-2.57 1.21,-3.6L12,14.77v4.82zM12,5.1v4.58l7.25,7.26c1.37,-2.96 0.84,-6.57 -1.6,-9.01L12,2.27l-3.7,3.7 1.41,1.41L12,5.1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_live_help_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_live_help_black_24dp.xml
new file mode 100644
index 0000000..74f5494
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_live_help_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,2L5,2c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM13,18h-2v-2h2v2zM15.07,10.25l-0.9,0.92C13.45,11.9 13,12.5 13,14h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,8c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_off_black_24dp.xml
new file mode 100644
index 0000000..56565e0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,6.5c1.38,0 2.5,1.12 2.5,2.5 0,0.74 -0.33,1.39 -0.83,1.85l3.63,3.63c0.98,-1.86 1.7,-3.8 1.7,-5.48 0,-3.87 -3.13,-7 -7,-7 -1.98,0 -3.76,0.83 -5.04,2.15l3.19,3.19c0.46,-0.52 1.11,-0.84 1.85,-0.84zM16.37,16.1l-4.63,-4.63 -0.11,-0.11L3.27,3 2,4.27l3.18,3.18C5.07,7.95 5,8.47 5,9c0,5.25 7,13 7,13s1.67,-1.85 3.38,-4.35L18.73,21 20,19.73l-3.63,-3.63z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_on_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_on_black_24dp.xml
new file mode 100644
index 0000000..e3291a9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_on_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_mail_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_mail_outline_black_24dp.xml
new file mode 100644
index 0000000..8ea2622
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_mail_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8l8,5 8,-5v10zM12,11L4,6h16l-8,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_message_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_message_black_24dp.xml
new file mode 100644
index 0000000..d2876bf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_message_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_no_sim_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_no_sim_black_24dp.xml
new file mode 100644
index 0000000..1f82b54
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_no_sim_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.99,5c0,-1.1 -0.89,-2 -1.99,-2h-7L7.66,5.34 19,16.68 18.99,5zM3.65,3.88L2.38,5.15 5,7.77V19c0,1.1 0.9,2 2,2h10.01c0.35,0 0.67,-0.1 0.96,-0.26l1.88,1.88 1.27,-1.27L3.65,3.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_phone_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phone_black_24dp.xml
new file mode 100644
index 0000000..ebf9de6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phone_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_erase_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_erase_black_24dp.xml
new file mode 100644
index 0000000..2af6c96
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_erase_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,8.2l-1,-1 -4,4 -4,-4 -1,1 4,4 -4,4 1,1 4,-4 4,4 1,-1 -4,-4 4,-4zM19,1H9c-1.1,0 -2,0.9 -2,2v3h2V4h10v16H9v-2H7v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_lock_black_24dp.xml
new file mode 100644
index 0000000..bbb774a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_lock_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,1L9,1c-1.1,0 -2,0.9 -2,2v3h2L9,4h10v16L9,20v-2L7,18v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L21,3c0,-1.1 -0.9,-2 -2,-2zM10.8,11L10.8,9.5C10.8,8.1 9.4,7 8,7S5.2,8.1 5.2,9.5L5.2,11c-0.6,0 -1.2,0.6 -1.2,1.2v3.5c0,0.7 0.6,1.3 1.2,1.3h5.5c0.7,0 1.3,-0.6 1.3,-1.2v-3.5c0,-0.7 -0.6,-1.3 -1.2,-1.3zM9.5,11h-3L6.5,9.5c0,-0.8 0.7,-1.3 1.5,-1.3s1.5,0.5 1.5,1.3L9.5,11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_ring_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_ring_black_24dp.xml
new file mode 100644
index 0000000..9c2bdec
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_ring_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.1,7.7l-1,1c1.8,1.8 1.8,4.6 0,6.5l1,1c2.5,-2.3 2.5,-6.1 0,-8.5zM18,9.8l-1,1c0.5,0.7 0.5,1.6 0,2.3l1,1c1.2,-1.2 1.2,-3 0,-4.3zM14,1L4,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L16,3c0,-1.1 -0.9,-2 -2,-2zM14,20L4,20L4,4h10v16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_setup_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_setup_black_24dp.xml
new file mode 100644
index 0000000..3a96511
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_phonelink_setup_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.8,12.5v-1l1.1,-0.8c0.1,-0.1 0.1,-0.2 0.1,-0.3l-1,-1.7c-0.1,-0.1 -0.2,-0.2 -0.3,-0.1l-1.3,0.4c-0.3,-0.2 -0.6,-0.4 -0.9,-0.5l-0.2,-1.3c0,-0.1 -0.1,-0.2 -0.3,-0.2H7c-0.1,0 -0.2,0.1 -0.3,0.2l-0.2,1.3c-0.3,0.1 -0.6,0.3 -0.9,0.5l-1.3,-0.5c-0.1,0 -0.2,0 -0.3,0.1l-1,1.7c-0.1,0.1 0,0.2 0.1,0.3l1.1,0.8v1l-1.1,0.8c-0.1,0.2 -0.1,0.3 -0.1,0.4l1,1.7c0.1,0.1 0.2,0.2 0.3,0.1l1.4,-0.4c0.3,0.2 0.6,0.4 0.9,0.5l0.2,1.3c-0.1,0.1 0.1,0.2 0.2,0.2h2c0.1,0 0.2,-0.1 0.3,-0.2l0.2,-1.3 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_portable_wifi_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_portable_wifi_off_black_24dp.xml
new file mode 100644
index 0000000..6a3e06f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_portable_wifi_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.56,14.24c0.28,-0.69 0.44,-1.45 0.44,-2.24 0,-3.31 -2.69,-6 -6,-6 -0.79,0 -1.55,0.16 -2.24,0.44l1.62,1.62c0.2,-0.03 0.41,-0.06 0.62,-0.06 2.21,0 4,1.79 4,4 0,0.21 -0.02,0.42 -0.05,0.63l1.61,1.61zM12,4c4.42,0 8,3.58 8,8 0,1.35 -0.35,2.62 -0.95,3.74l1.47,1.47C21.46,15.69 22,13.91 22,12c0,-5.52 -4.48,-10 -10,-10 -1.91,0 -3.69,0.55 -5.21,1.47l1.46,1.46C9.37,4.34 10.65,4 12,4zM3.27,2.5L2,3.77l2.1,2.1C2.79,7.57 2,9.69 2,12c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,17. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_present_to_all_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_present_to_all_black_24dp.xml
new file mode 100644
index 0000000..56c63ce
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_present_to_all_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v14c0,1.11 0.89,2 2,2h18c1.11,0 2,-0.89 2,-2L23,5c0,-1.11 -0.89,-2 -2,-2zM21,19.02L3,19.02L3,4.98h18v14.04zM10,12L8,12l4,-4 4,4h-2v4h-4v-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_ring_volume_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_ring_volume_black_24dp.xml
new file mode 100644
index 0000000..18d9921
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_ring_volume_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23.71,16.67C20.66,13.78 16.54,12 12,12 7.46,12 3.34,13.78 0.29,16.67c-0.18,0.18 -0.29,0.43 -0.29,0.71 0,0.28 0.11,0.53 0.29,0.71l2.48,2.48c0.18,0.18 0.43,0.29 0.71,0.29 0.27,0 0.52,-0.11 0.7,-0.28 0.79,-0.74 1.69,-1.36 2.66,-1.85 0.33,-0.16 0.56,-0.5 0.56,-0.9v-3.1c1.45,-0.48 3,-0.73 4.6,-0.73s3.15,0.25 4.6,0.72v3.1c0,0.39 0.23,0.74 0.56,0.9 0.98,0.49 1.87,1.12 2.66,1.85 0.18,0.18 0.43,0.28 0.7,0.28 0.28,0 0.53,-0.11 0.71,-0.29l2.48,-2.48c0.18,-0.18 0.29,-0.43 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_screen_share_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_screen_share_black_24dp.xml
new file mode 100644
index 0000000..612f185
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_screen_share_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18c1.1,0 1.99,-0.9 1.99,-2L22,6c0,-1.11 -0.9,-2 -2,-2L4,4c-1.11,0 -2,0.89 -2,2v10c0,1.1 0.89,2 2,2L0,18v2h24v-2h-4zM13,14.47v-2.19c-2.78,0 -4.61,0.85 -6,2.72 0.56,-2.67 2.11,-5.33 6,-5.87L13,7l4,3.73 -4,3.74z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_speaker_phone_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_speaker_phone_black_24dp.xml
new file mode 100644
index 0000000..5e9c580
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_speaker_phone_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,7.07L8.43,8.5c0.91,-0.91 2.18,-1.48 3.57,-1.48s2.66,0.57 3.57,1.48L17,7.07C15.72,5.79 13.95,5 12,5s-3.72,0.79 -5,2.07zM12,1C8.98,1 6.24,2.23 4.25,4.21l1.41,1.41C7.28,4 9.53,3 12,3s4.72,1 6.34,2.62l1.41,-1.41C17.76,2.23 15.02,1 12,1zM14.86,10.01L9.14,10C8.51,10 8,10.51 8,11.14v9.71c0,0.63 0.51,1.14 1.14,1.14h5.71c0.63,0 1.14,-0.51 1.14,-1.14v-9.71c0.01,-0.63 -0.5,-1.13 -1.13,-1.13zM15,20L9,20v-8h6v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_landscape_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_landscape_black_24dp.xml
new file mode 100644
index 0000000..9314079
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_landscape_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1.01,7L1,17c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -1.99,0.9 -1.99,2zM19,7v10H5V7h14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_portrait_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_portrait_black_24dp.xml
new file mode 100644
index 0000000..50c70b3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_portrait_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_landscape_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_landscape_black_24dp.xml
new file mode 100644
index 0000000..9314079
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_landscape_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1.01,7L1,17c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -1.99,0.9 -1.99,2zM19,7v10H5V7h14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_portrait_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_portrait_black_24dp.xml
new file mode 100644
index 0000000..50c70b3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_portrait_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_stop_screen_share_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stop_screen_share_black_24dp.xml
new file mode 100644
index 0000000..3531640
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_stop_screen_share_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21.22,18.02l2,2L24,20.02v-2h-2.78zM21.99,16.02l0.01,-10c0,-1.11 -0.9,-2 -2,-2L7.22,4.02l5.23,5.23c0.18,-0.04 0.36,-0.07 0.55,-0.1L13,7.02l4,3.73 -1.58,1.47 5.54,5.54c0.61,-0.33 1.03,-0.99 1.03,-1.74zM2.39,1.73L1.11,3l1.54,1.54c-0.4,0.36 -0.65,0.89 -0.65,1.48v10c0,1.1 0.89,2 2,2L0,18.02v2h18.13l2.71,2.71 1.27,-1.27L2.39,1.73zM7,15.02c0.31,-1.48 0.92,-2.95 2.07,-4.06l1.59,1.59c-1.54,0.38 -2.7,1.18 -3.66,2.47z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_swap_calls_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_swap_calls_black_24dp.xml
new file mode 100644
index 0000000..1ae514a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_swap_calls_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,4l-4,4h3v7c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2V8c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8v7H2l4,4 4,-4H7V8c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v7c0,2.21 1.79,4 4,4s4,-1.79 4,-4V8h3l-4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_textsms_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_textsms_black_24dp.xml
new file mode 100644
index 0000000..8b8ead2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_textsms_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_voicemail_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_voicemail_black_24dp.xml
new file mode 100644
index 0000000..c311998
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_voicemail_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.5,6C15.46,6 13,8.46 13,11.5c0,1.33 0.47,2.55 1.26,3.5L9.74,15c0.79,-0.95 1.26,-2.17 1.26,-3.5C11,8.46 8.54,6 5.5,6S0,8.46 0,11.5 2.46,17 5.5,17h13c3.04,0 5.5,-2.46 5.5,-5.5S21.54,6 18.5,6zM5.5,15C3.57,15 2,13.43 2,11.5S3.57,8 5.5,8 9,9.57 9,11.5 7.43,15 5.5,15zM18.5,15c-1.93,0 -3.5,-1.57 -3.5,-3.5S16.57,8 18.5,8 22,9.57 22,11.5 20.43,15 18.5,15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/communication/ic_vpn_key_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/communication/ic_vpn_key_black_24dp.xml
new file mode 100644
index 0000000..2eddd16
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/communication/ic_vpn_key_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_black_24dp.xml
new file mode 100644
index 0000000..0258249
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_add_box_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_box_black_24dp.xml
new file mode 100644
index 0000000..b7e59bc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_box_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_black_24dp.xml
new file mode 100644
index 0000000..db4e035
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_outline_black_24dp.xml
new file mode 100644
index 0000000..900f227
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,7h-2v4L7,11v2h4v4h2v-4h4v-2h-4L13,7zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_archive_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_archive_black_24dp.xml
new file mode 100644
index 0000000..8b18a9d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_archive_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_backspace_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_backspace_black_24dp.xml
new file mode 100644
index 0000000..35b3af3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_backspace_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM19,15.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_block_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_block_black_24dp.xml
new file mode 100644
index 0000000..4510bf5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_block_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_clear_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_clear_black_24dp.xml
new file mode 100644
index 0000000..ede4b71
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_clear_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_content_copy_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_content_copy_black_24dp.xml
new file mode 100644
index 0000000..8a894a3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_content_copy_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_content_cut_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_content_cut_black_24dp.xml
new file mode 100644
index 0000000..1c0f96a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_content_cut_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.64,7.64c0.23,-0.5 0.36,-1.05 0.36,-1.64 0,-2.21 -1.79,-4 -4,-4S2,3.79 2,6s1.79,4 4,4c0.59,0 1.14,-0.13 1.64,-0.36L10,12l-2.36,2.36C7.14,14.13 6.59,14 6,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4c0,-0.59 -0.13,-1.14 -0.36,-1.64L12,14l7,7h3v-1L9.64,7.64zM6,8c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM6,20c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM12,12.5c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5 0.5,0.22 0.5,0.5 -0.22,0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_content_paste_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_content_paste_black_24dp.xml
new file mode 100644
index 0000000..a902d9a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_content_paste_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,2h-4.18C14.4,0.84 13.3,0 12,0c-1.3,0 -2.4,0.84 -2.82,2L5,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM12,2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM19,20L5,20L5,4h2v3h10L17,4h2v16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_create_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_create_black_24dp.xml
new file mode 100644
index 0000000..2ab2fb7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_create_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_drafts_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_drafts_black_24dp.xml
new file mode 100644
index 0000000..159567e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_drafts_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21.99,8c0,-0.72 -0.37,-1.35 -0.94,-1.7L12,1 2.95,6.3C2.38,6.65 2,7.28 2,8v10c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2l-0.01,-10zM12,13L3.74,7.84 12,3l8.26,4.84L12,13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_filter_list_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_filter_list_black_24dp.xml
new file mode 100644
index 0000000..b99b672
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_filter_list_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_flag_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_flag_black_24dp.xml
new file mode 100644
index 0000000..82ef104
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_flag_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.4,6L14,4H5v17h2v-7h5.6l0.4,2h7V6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_font_download_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_font_download_black_24dp.xml
new file mode 100644
index 0000000..a00893d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_font_download_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.93,13.5h4.14L12,7.98zM20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM15.95,18.5l-1.14,-3L9.17,15.5l-1.12,3L5.96,18.5l5.11,-13h1.86l5.11,13h-2.09z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_forward_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_forward_black_24dp.xml
new file mode 100644
index 0000000..607178a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_forward_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,8V4l8,8 -8,8v-4H4V8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_gesture_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_gesture_black_24dp.xml
new file mode 100644
index 0000000..33e2536
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_gesture_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4.59,6.89c0.7,-0.71 1.4,-1.35 1.71,-1.22 0.5,0.2 0,1.03 -0.3,1.52 -0.25,0.42 -2.86,3.89 -2.86,6.31 0,1.28 0.48,2.34 1.34,2.98 0.75,0.56 1.74,0.73 2.64,0.46 1.07,-0.31 1.95,-1.4 3.06,-2.77 1.21,-1.49 2.83,-3.44 4.08,-3.44 1.63,0 1.65,1.01 1.76,1.79 -3.78,0.64 -5.38,3.67 -5.38,5.37 0,1.7 1.44,3.09 3.21,3.09 1.63,0 4.29,-1.33 4.69,-6.1L21,14.88v-2.5h-2.47c-0.15,-1.65 -1.09,-4.2 -4.03,-4.2 -2.25,0 -4.18,1.91 -4.94,2.84 -0.58,0.73 -2.06,2.48 -2.29,2.72 -0.25,0.3 -0. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_inbox_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_inbox_black_24dp.xml
new file mode 100644
index 0000000..de5c746
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_inbox_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L4.99,3c-1.11,0 -1.98,0.89 -1.98,2L3,19c0,1.1 0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.11 -0.9,-2 -2,-2zM19,15h-4c0,1.66 -1.35,3 -3,3s-3,-1.34 -3,-3L4.99,15L4.99,5L19,5v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_link_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_link_black_24dp.xml
new file mode 100644
index 0000000..538c5bd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_link_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_mail_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_mail_black_24dp.xml
new file mode 100644
index 0000000..ce97ab8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_mail_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_markunread_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_markunread_black_24dp.xml
new file mode 100644
index 0000000..ce97ab8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_markunread_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_move_to_inbox_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_move_to_inbox_black_24dp.xml
new file mode 100644
index 0000000..349f48f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_move_to_inbox_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L4.99,3c-1.11,0 -1.98,0.9 -1.98,2L3,19c0,1.1 0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,15h-4c0,1.66 -1.35,3 -3,3s-3,-1.34 -3,-3L4.99,15L4.99,5L19,5v10zM16,10h-2L14,7h-4v3L8,10l4,4 4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_next_week_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_next_week_black_24dp.xml
new file mode 100644
index 0000000..9be44e3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_next_week_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M20,7h-4L16,5c0,-0.55 -0.22,-1.05 -0.59,-1.41C15.05,3.22 14.55,3 14,3h-4c-1.1,0 -2,0.9 -2,2v2L4,7c-1.1,0 -2,0.9 -2,2v11c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,9c0,-1.1 -0.9,-2 -2,-2zM10,5h4v2h-4L10,5zM11,18.5l-1,-1 3,-3 -3,-3 1,-1 4,4 -4,4z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_redo_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_redo_black_24dp.xml
new file mode 100644
index 0000000..424e788
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_redo_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_black_24dp.xml
new file mode 100644
index 0000000..a541177
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,13H5v-2h14v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_black_24dp.xml
new file mode 100644
index 0000000..099e650
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_outline_black_24dp.xml
new file mode 100644
index 0000000..9af9456
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,11v2h10v-2L7,11zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_all_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_all_black_24dp.xml
new file mode 100644
index 0000000..d43e0b2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_all_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,8L7,5l-7,7 7,7v-3l-4,-4 4,-4zM13,9L13,5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_black_24dp.xml
new file mode 100644
index 0000000..46b19a0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_report_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_report_black_24dp.xml
new file mode 100644
index 0000000..e20d889
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_report_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_save_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_save_black_24dp.xml
new file mode 100644
index 0000000..a561d63
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_save_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_select_all_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_select_all_black_24dp.xml
new file mode 100644
index 0000000..68702c1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_select_all_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_send_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_send_black_24dp.xml
new file mode 100644
index 0000000..e145ca8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_send_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_sort_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_sort_black_24dp.xml
new file mode 100644
index 0000000..fd4c56f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_sort_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_text_format_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_text_format_black_24dp.xml
new file mode 100644
index 0000000..147d71c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_text_format_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,17v2h14v-2L5,17zM9.5,12.8h5l0.9,2.2h2.1L12.75,4h-1.5L6.5,15h2.1l0.9,-2.2zM12,5.98L13.87,11h-3.74L12,5.98z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_unarchive_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_unarchive_black_24dp.xml
new file mode 100644
index 0000000..9dd0116
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_unarchive_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.55,5.22l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.15,0.55L3.46,5.22C3.17,5.57 3,6.01 3,6.5V19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.49 -0.17,-0.93 -0.45,-1.28zM12,9.5l5.5,5.5H14v2h-4v-2H6.5L12,9.5zM5.12,5l0.82,-1h12l0.93,1H5.12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_undo_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_undo_black_24dp.xml
new file mode 100644
index 0000000..5558e37
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_undo_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/content/ic_weekend_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/content/ic_weekend_black_24dp.xml
new file mode 100644
index 0000000..20befd9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/content/ic_weekend_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M21,10c-1.1,0 -2,0.9 -2,2v3L5,15v-3c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2v5c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2v-5c0,-1.1 -0.9,-2 -2,-2zM18,5L6,5c-1.1,0 -2,0.9 -2,2v2.15c1.16,0.41 2,1.51 2,2.82L6,14h12v-2.03c0,-1.3 0.84,-2.4 2,-2.82L20,7c0,-1.1 -0.9,-2 -2,-2z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarm_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarm_black_24dp.xml
new file mode 100644
index 0000000..934b067
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarm_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12.5,8L11,8v6l4.75,2.85 0.75,-1.23 -4,-2.37L12.5,8zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarms_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarms_black_24dp.xml
new file mode 100644
index 0000000..5f742d3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarms_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,5.7l-4.6,-3.9 -1.3,1.5 4.6,3.9L22,5.7zM7.9,3.4L6.6,1.9 2,5.7l1.3,1.5 4.6,-3.8zM12.5,8L11,8v6l4.7,2.9 0.8,-1.2 -4,-2.4L12.5,8zM12,4c-5,0 -9,4 -9,9s4,9 9,9 9,-4 9,-9 -4,-9 -9,-9zM12,20c-3.9,0 -7,-3.1 -7,-7s3.1,-7 7,-7 7,3.1 7,7 -3.1,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_access_time_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_access_time_black_24dp.xml
new file mode 100644
index 0000000..2239a4f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_access_time_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_add_alarm_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_add_alarm_black_24dp.xml
new file mode 100644
index 0000000..4c5023a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_add_alarm_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zM13,9h-2v3L8,12v2h3v3h2v-3h3v-2h-3L13,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_active_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_active_black_24dp.xml
new file mode 100644
index 0000000..55a8d22
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_active_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10.18,9"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,16v-2l-8,-5V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5V9l-8,5v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-5.5l8,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_inactive_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_inactive_black_24dp.xml
new file mode 100644
index 0000000..6031306
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_inactive_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,9V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v3.68l7.83,7.83L21,16v-2l-8,-5zM3,5.27l4.99,4.99L2,14v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-3.73L18.73,21 20,19.73 4.27,4 3,5.27z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_20_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_20_black_24dp.xml
new file mode 100644
index 0000000..d9a1e64
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_20_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,17v3.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V17H7z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V17h10V5.33z"
+ android:fillAlpha=".3"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_30_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_30_black_24dp.xml
new file mode 100644
index 0000000..3b872ea
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_30_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V15h10V5.33z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,15v5.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V15H7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_50_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_50_black_24dp.xml
new file mode 100644
index 0000000..8236b23
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_50_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V13h10V5.33z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,13v7.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V13H7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_60_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_60_black_24dp.xml
new file mode 100644
index 0000000..22c91cd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_60_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V11h10V5.33z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,11v9.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V11H7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_80_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_80_black_24dp.xml
new file mode 100644
index 0000000..600ad77
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_80_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V9h10V5.33z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,9v11.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V9H7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_90_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_90_black_24dp.xml
new file mode 100644
index 0000000..fe8b8c8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_90_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V8h10V5.33z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,8v12.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V8H7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_alert_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_alert_black_24dp.xml
new file mode 100644
index 0000000..4966395
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_alert_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4L14,4L14,2h-4v2L8.33,4C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33L17,5.33C17,4.6 16.4,4 15.67,4zM13,18h-2v-2h2v2zM13,14h-2L11,9h2v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_20_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_20_black_24dp.xml
new file mode 100644
index 0000000..c3bf3b7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_20_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,20v-3H7v3.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V17h-4.4L11,20z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V17h4v-2.5H9L13,7v5.5h2L12.6,17H17V5.33C17,4.6 16.4,4 15.67,4z"
+ android:fillAlpha=".3"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_30_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_30_black_24dp.xml
new file mode 100644
index 0000000..de56162
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_30_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v9.17h2L13,7v5.5h2l-1.07,2H17V5.33C17,4.6 16.4,4 15.67,4z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,20v-5.5H7v6.17C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V14.5h-3.07L11,20z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_50_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_50_black_24dp.xml
new file mode 100644
index 0000000..d794783
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_50_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.47,13.5L11,20v-5.5H9l0.53,-1H7v7.17C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V13.5h-2.53z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v8.17h2.53L13,7v5.5h2l-0.53,1H17V5.33C17,4.6 16.4,4 15.67,4z"
+ android:fillAlpha=".3"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_60_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_60_black_24dp.xml
new file mode 100644
index 0000000..4673e6a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_60_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V11h3.87L13,7v4h4V5.33C17,4.6 16.4,4 15.67,4z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,12.5h2L11,20v-5.5H9l1.87,-3.5H7v9.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V11h-4v1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_80_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_80_black_24dp.xml
new file mode 100644
index 0000000..e004a0a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_80_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V9h4.93L13,7v2h4V5.33C17,4.6 16.4,4 15.67,4z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,12.5h2L11,20v-5.5H9L11.93,9H7v11.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V9h-4v3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_90_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_90_black_24dp.xml
new file mode 100644
index 0000000..1b5b34d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_90_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V8h5.47L13,7v1h4V5.33C17,4.6 16.4,4 15.67,4z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,12.5h2L11,20v-5.5H9L12.47,8H7v12.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V8h-4v4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_full_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_full_black_24dp.xml
new file mode 100644
index 0000000..9389e7c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_full_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM11,20v-5.5H9L13,7v5.5h2L11,20z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_full_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_full_black_24dp.xml
new file mode 100644
index 0000000..b0e57fe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_full_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_std_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_std_black_24dp.xml
new file mode 100644
index 0000000..b0e57fe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_std_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_unknown_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_unknown_black_24dp.xml
new file mode 100644
index 0000000..dc6df58
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_unknown_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4L14,4L14,2h-4v2L8.33,4C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33L17,5.33C17,4.6 16.4,4 15.67,4zM12.95,17.95h-1.9v-1.9h1.9v1.9zM14.3,12.69s-0.38,0.42 -0.67,0.71c-0.48,0.48 -0.83,1.15 -0.83,1.6h-1.6c0,-0.83 0.46,-1.52 0.93,-2l0.93,-0.94c0.27,-0.27 0.44,-0.65 0.44,-1.06 0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5L9,11c0,-1.66 1.34,-3 3,-3s3,1.34 3,3c0,0.66 -0.27,1.26 -0.7,1.69z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_black_24dp.xml
new file mode 100644
index 0000000..1094756
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_connected_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_connected_black_24dp.xml
new file mode 100644
index 0000000..c81c3a3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_connected_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,12l-2,-2 -2,2 2,2 2,-2zM17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88zM19,10l-2,2 2,2 2,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_disabled_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_disabled_black_24dp.xml
new file mode 100644
index 0000000..1f753a2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_disabled_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,5.83l1.88,1.88 -1.6,1.6 1.41,1.41 3.02,-3.02L12,2h-1v5.03l2,2v-3.2zM5.41,4L4,5.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l4.29,-4.29 2.3,2.29L20,18.59 5.41,4zM13,18.17v-3.76l1.88,1.88L13,18.17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_searching_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_searching_black_24dp.xml
new file mode 100644
index 0000000..ece1684
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_searching_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_auto_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_auto_black_24dp.xml
new file mode 100644
index 0000000..8c55881
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_auto_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10.85,12.65h2.3L12,9l-1.15,3.65zM20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM14.3,16l-0.7,-2h-3.2l-0.7,2H7.8L11,7h2l3.2,9h-1.9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_high_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_high_black_24dp.xml
new file mode 100644
index 0000000..920f615
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_high_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_low_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_low_black_24dp.xml
new file mode 100644
index 0000000..54301c0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_low_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_medium_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_medium_black_24dp.xml
new file mode 100644
index 0000000..2f9cd1e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_medium_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18V6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_data_usage_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_data_usage_black_24dp.xml
new file mode 100644
index 0000000..16b9d74
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_data_usage_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_developer_mode_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_developer_mode_black_24dp.xml
new file mode 100644
index 0000000..538b905
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_developer_mode_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_devices_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_devices_black_24dp.xml
new file mode 100644
index 0000000..150ced4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_devices_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6h18L22,4L4,4c-1.1,0 -2,0.9 -2,2v11L0,17v3h14v-3L4,17L4,6zM23,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L24,9c0,-0.55 -0.45,-1 -1,-1zM22,17h-4v-7h4v7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_dvr_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_dvr_black_24dp.xml
new file mode 100644
index 0000000..5b73a4e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_dvr_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12zM19,8L8,8v2h11L19,8zM19,12L8,12v2h11v-2zM7,8L5,8v2h2L7,8zM7,12L5,12v2h2v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_fixed_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_fixed_black_24dp.xml
new file mode 100644
index 0000000..07d6e46
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_fixed_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_not_fixed_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_not_fixed_black_24dp.xml
new file mode 100644
index 0000000..a1e7c4a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_not_fixed_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_off_black_24dp.xml
new file mode 100644
index 0000000..dad99eb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15L23,13v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zM16.27,17.54C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.5 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_graphic_eq_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_graphic_eq_black_24dp.xml
new file mode 100644
index 0000000..a8eb92f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_graphic_eq_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,18h2L9,6L7,6v12zM11,22h2L13,2h-2v20zM3,14h2v-4L3,10v4zM15,18h2L17,6h-2v12zM19,10v4h2v-4h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_location_disabled_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_location_disabled_black_24dp.xml
new file mode 100644
index 0000000..dad99eb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_location_disabled_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15L23,13v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zM16.27,17.54C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.5 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_location_searching_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_location_searching_black_24dp.xml
new file mode 100644
index 0000000..a1e7c4a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_location_searching_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_network_cell_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_network_cell_black_24dp.xml
new file mode 100644
index 0000000..de5804b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_network_cell_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22h20V2z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,7L2,22h15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_network_wifi_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_network_wifi_black_24dp.xml
new file mode 100644
index 0000000..caac288
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_network_wifi_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_nfc_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_nfc_black_24dp.xml
new file mode 100644
index 0000000..81f4560
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_nfc_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,20L4,20L4,4h16v16zM18,6h-5c-1.1,0 -2,0.9 -2,2v2.28c-0.6,0.35 -1,0.98 -1,1.72 0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-0.74 -0.4,-1.38 -1,-1.72L13,8h3v8L8,16L8,8h2L10,6L6,6v12h12L18,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_landscape_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_landscape_black_24dp.xml
new file mode 100644
index 0000000..329c92a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_landscape_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,5L3,5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,7c0,-1.1 -0.9,-2 -2,-2zM19,17L5,17L5,7h14v10zM10,16h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zM10.8,10c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_portrait_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_portrait_black_24dp.xml
new file mode 100644
index 0000000..0769a31
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_portrait_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,16h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zM10.8,10c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1zM17,1L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_rotation_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_rotation_black_24dp.xml
new file mode 100644
index 0000000..f427d32
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_rotation_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23.25,12.77l-2.57,-2.57 -1.41,1.41 2.22,2.22 -5.66,5.66L4.51,8.17l5.66,-5.66 2.1,2.1 1.41,-1.41L11.23,0.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L2.75,7.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12zM8.47,20.48C5.2,18.94 2.86,15.76 2.5,12L1,12c0.51,6.16 5.66,11 11.95,11l0.66,-0.03 -3.81,-3.82 -1.33,1.33zM16,9h5c0.55,0 1,-0.45 1,-1L22,4c0,-0.55 -0.45,-1 -1,-1v-0.5C21,1.12 19.88,0 18.5,0S16,1.12 16,2.5L16 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_rotation_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_rotation_black_24dp.xml
new file mode 100644
index 0000000..230dc68
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_rotation_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zM10.23,1.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zM14.83,21.19L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zM7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_sd_storage_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_sd_storage_black_24dp.xml
new file mode 100644
index 0000000..f9ad72d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_sd_storage_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM12,8h-2L10,4h2v4zM15,8h-2L13,4h2v4zM18,8h-2L16,4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_settings_system_daydream_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_settings_system_daydream_black_24dp.xml
new file mode 100644
index 0000000..c698276
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_settings_system_daydream_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,16h6.5c1.38,0 2.5,-1.12 2.5,-2.5S16.88,11 15.5,11h-0.05c-0.24,-1.69 -1.69,-3 -3.45,-3 -1.4,0 -2.6,0.83 -3.16,2.02h-0.16C7.17,10.18 6,11.45 6,13c0,1.66 1.34,3 3,3zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_0_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_0_bar_black_24dp.xml
new file mode 100644
index 0000000..61f01c4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_0_bar_black_24dp.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22h20V2z"
+ android:fillAlpha=".3"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_1_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_1_bar_black_24dp.xml
new file mode 100644
index 0000000..b54b810
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_1_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22h20V2z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12L2,22h10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_2_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_2_bar_black_24dp.xml
new file mode 100644
index 0000000..dc50970
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_2_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22h20V2z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,10L2,22h12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_3_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_3_bar_black_24dp.xml
new file mode 100644
index 0000000..de5804b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_3_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22h20V2z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,7L2,22h15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_4_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_4_bar_black_24dp.xml
new file mode 100644
index 0000000..fa5880f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_4_bar_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22h20V2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_0_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_0_bar_black_24dp.xml
new file mode 100644
index 0000000..aaf772e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_0_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,8V2L2,22h16V8z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,22h2v-2h-2v2zM20,10v8h2v-8h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_1_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_1_bar_black_24dp.xml
new file mode 100644
index 0000000..ef569e3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_1_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,8V2L2,22h16V8z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10v8h2v-8h-2zM12,22L12,12L2,22h10zM20,22h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_2_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_2_bar_black_24dp.xml
new file mode 100644
index 0000000..43d0b01
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_2_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,8V2L2,22h16V8z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,22L14,10L2,22h12zM20,10v8h2v-8h-2zM20,22h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_3_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_3_bar_black_24dp.xml
new file mode 100644
index 0000000..33e31e5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_3_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,8V2L2,22h16V8z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,22L17,7L2,22h15zM20,10v8h2v-8h-2zM20,22h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_4_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_4_bar_black_24dp.xml
new file mode 100644
index 0000000..3966b97
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_4_bar_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18h2v-8h-2v8zM20,22h2v-2h-2v2zM2,22h16L18,8h4L22,2L2,22z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_no_sim_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_no_sim_black_24dp.xml
new file mode 100644
index 0000000..1f82b54
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_no_sim_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.99,5c0,-1.1 -0.89,-2 -1.99,-2h-7L7.66,5.34 19,16.68 18.99,5zM3.65,3.88L2.38,5.15 5,7.77V19c0,1.1 0.9,2 2,2h10.01c0.35,0 0.67,-0.1 0.96,-0.26l1.88,1.88 1.27,-1.27L3.65,3.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_null_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_null_black_24dp.xml
new file mode 100644
index 0000000..047dec4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_null_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6.83V20H6.83L20,6.83M22,2L2,22h20V2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_off_black_24dp.xml
new file mode 100644
index 0000000..57d9df1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,1l-8.59,8.59L21,18.18V1zM4.77,4.5L3.5,5.77l6.36,6.36L1,21h17.73l2,2L22,21.73 4.77,4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_0_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_0_bar_black_24dp.xml
new file mode 100644
index 0000000..5807bc6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_0_bar_black_24dp.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+ android:fillAlpha=".3"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_black_24dp.xml
new file mode 100644
index 0000000..f3a728c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.67,14.86L12,21.49v0.01l0.01,-0.01 5.33,-6.63C17.06,14.65 15.03,13 12,13s-5.06,1.65 -5.33,1.86z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..22ebc37
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_lock_black_24dp.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,16v-1.5c0,-1.4 -1.1,-2.5 -2.5,-2.5S18,13.1 18,14.5L18,16c-0.5,0 -1,0.5 -1,1v4c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-4c0,-0.5 -0.5,-1 -1,-1zM22,16h-3v-1.5c0,-0.8 0.7,-1.5 1.5,-1.5s1.5,0.7 1.5,1.5L22,16z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,14.5c0,-2.8 2.2,-5 5,-5 0.4,0 0.7,0 1,0.1L23.6,7c-0.4,-0.3 -4.9,-4 -11.6,-4C5.3,3 0.8,6.7 0.4,7L12,21.5l3.5,-4.3v-2.7z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.7,14.9l5.3,6.6 3.5,-4.3v-2.6c0,-0.2 0,-0.5 0.1,-0.7 -0.9,-0.5 -2.2,-0.9 -3.6,-0.9 -3,0 -5.1,1.7 -5.3,1.9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_black_24dp.xml
new file mode 100644
index 0000000..33d8e47
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4.79,12.52l7.2,8.98H12l0.01,-0.01 7.2,-8.98C18.85,12.24 16.1,10 12,10s-6.85,2.24 -7.21,2.52z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..460a290
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_lock_black_24dp.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,16v-1.5c0,-1.4 -1.1,-2.5 -2.5,-2.5S18,13.1 18,14.5L18,16c-0.5,0 -1,0.5 -1,1v4c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-4c0,-0.5 -0.5,-1 -1,-1zM22,16h-3v-1.5c0,-0.8 0.7,-1.5 1.5,-1.5s1.5,0.7 1.5,1.5L22,16z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,14.5c0,-2.8 2.2,-5 5,-5 0.4,0 0.7,0 1,0.1L23.6,7c-0.4,-0.3 -4.9,-4 -11.6,-4C5.3,3 0.8,6.7 0.4,7L12,21.5l3.5,-4.3v-2.7z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4.8,12.5l7.2,9 3.5,-4.4v-2.6c0,-1.3 0.5,-2.5 1.4,-3.4C15.6,10.5 14,10 12,10c-4.1,0 -6.8,2.2 -7.2,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_black_24dp.xml
new file mode 100644
index 0000000..caac288
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..fa4cf9c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_lock_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,3C5.3,3 0.8,6.7 0.4,7l3.2,3.9L12,21.5l3.5,-4.3v-2.6c0,-2.2 1.4,-4 3.3,-4.7 0.3,-0.1 0.5,-0.2 0.8,-0.2 0.3,-0.1 0.6,-0.1 0.9,-0.1 0.4,0 0.7,0 1,0.1L23.6,7c-0.4,-0.3 -4.9,-4 -11.6,-4z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,16v-1.5c0,-1.4 -1.1,-2.5 -2.5,-2.5S18,13.1 18,14.5L18,16c-0.5,0 -1,0.5 -1,1v4c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-4c0,-0.5 -0.5,-1 -1,-1zM22,16h-3v-1.5c0,-0.8 0.7,-1.5 1.5,-1.5s1.5,0.7 1.5,1.5L22,16zM12,21.5l3.5,-4.3v-2.6c0,-2.2 1.4,-4 3.3,-4.7C17.3,9 14.9,8 12,8c-4.8,0 -8,2.6 -8.5,2.9"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_black_24dp.xml
new file mode 100644
index 0000000..d0ccd71
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..320914d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_lock_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,16v-1.5c0,-1.4 -1.1,-2.5 -2.5,-2.5S18,13.1 18,14.5L18,16c-0.5,0 -1,0.5 -1,1v4c0,0.5 0.5,1 1,1h5c0.5,0 1,-0.5 1,-1v-4c0,-0.5 -0.5,-1 -1,-1zM22,16h-3v-1.5c0,-0.8 0.7,-1.5 1.5,-1.5s1.5,0.7 1.5,1.5L22,16zM15.5,14.5c0,-2.8 2.2,-5 5,-5 0.4,0 0.7,0 1,0.1L23.6,7c-0.4,-0.3 -4.9,-4 -11.6,-4C5.3,3 0.8,6.7 0.4,7L12,21.5l3.5,-4.4v-2.6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_off_black_24dp.xml
new file mode 100644
index 0000000..8339d79
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_storage_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_storage_black_24dp.xml
new file mode 100644
index 0000000..53c595c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_storage_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_usb_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_usb_black_24dp.xml
new file mode 100644
index 0000000..2e7e0dc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_usb_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,7v4h1v2h-3V5h2l-3,-4 -3,4h2v8H8v-2.07c0.7,-0.37 1.2,-1.08 1.2,-1.93 0,-1.21 -0.99,-2.2 -2.2,-2.2 -1.21,0 -2.2,0.99 -2.2,2.2 0,0.85 0.5,1.56 1.2,1.93V13c0,1.11 0.89,2 2,2h3v3.05c-0.71,0.37 -1.2,1.1 -1.2,1.95 0,1.22 0.99,2.2 2.2,2.2 1.21,0 2.2,-0.98 2.2,-2.2 0,-0.85 -0.49,-1.58 -1.2,-1.95V15h3c1.11,0 2,-0.89 2,-2v-2h1V7h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_wallpaper_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_wallpaper_black_24dp.xml
new file mode 100644
index 0000000..9e2340b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_wallpaper_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,4h7L11,2L4,2c-1.1,0 -2,0.9 -2,2v7h2L4,4zM10,13l-4,5h12l-3,-4 -2.03,2.71L10,13zM17,8.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S14,7.67 14,8.5s0.67,1.5 1.5,1.5S17,9.33 17,8.5zM20,2h-7v2h7v7h2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,20h-7v2h7c1.1,0 2,-0.9 2,-2v-7h-2v7zM4,13L2,13v7c0,1.1 0.9,2 2,2h7v-2L4,20v-7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_widgets_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_widgets_black_24dp.xml
new file mode 100644
index 0000000..4abb823
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_widgets_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,13v8h8v-8h-8zM3,21h8v-8L3,13v8zM3,3v8h8L11,3L3,3zM16.66,1.69L11,7.34 16.66,13l5.66,-5.66 -5.66,-5.65z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_lock_black_24dp.xml
new file mode 100644
index 0000000..8eba49d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_lock_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.5,9.5c0.28,0 0.55,0.04 0.81,0.08L24,6c-3.34,-2.51 -7.5,-4 -12,-4S3.34,3.49 0,6l12,16 3.5,-4.67L15.5,14.5c0,-2.76 2.24,-5 5,-5zM23,16v-1.5c0,-1.38 -1.12,-2.5 -2.5,-2.5S18,13.12 18,14.5L18,16c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zM22,16h-3v-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5L22,16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_tethering_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_tethering_black_24dp.xml
new file mode 100644
index 0000000..4e021ad
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_tethering_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.48 -0.81,2.75 -2,3.45l1,1.74c1.79,-1.04 3,-2.97 3,-5.19zM12,3C6.48,3 2,7.48 2,13c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,18.53 4,15.96 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.96 -1.61,5.53 -4,6.92l1,1.73c2.99,-1.73 5,-4.95 5,-8.65 0,-5.52 -4.48,-10 -10,-10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_file_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_file_black_24dp.xml
new file mode 100644
index 0000000..b3d7c35
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_file_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,6v11.5c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4V5c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5v10.5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V6H10v9.5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5V5c0,-2.21 -1.79,-4 -4,-4S7,2.79 7,5v12.5c0,3.04 2.46,5.5 5.5,5.5s5.5,-2.46 5.5,-5.5V6h-1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_money_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_money_black_24dp.xml
new file mode 100644
index 0000000..b520fc9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_money_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_all_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_all_black_24dp.xml
new file mode 100644
index 0000000..27cb924
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_all_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,3v18h18L21,3L3,3zM11,19L5,19v-6h6v6zM11,11L5,11L5,5h6v6zM19,19h-6v-6h6v6zM19,11h-6L13,5h6v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_bottom_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_bottom_black_24dp.xml
new file mode 100644
index 0000000..926b6f7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_bottom_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,11L7,11v2h2v-2zM13,15h-2v2h2v-2zM9,3L7,3v2h2L9,3zM13,11h-2v2h2v-2zM5,3L3,3v2h2L5,3zM13,7h-2v2h2L13,7zM17,11h-2v2h2v-2zM13,3h-2v2h2L13,3zM17,3h-2v2h2L17,3zM19,13h2v-2h-2v2zM19,17h2v-2h-2v2zM5,7L3,7v2h2L5,7zM19,3v2h2L21,3h-2zM19,9h2L21,7h-2v2zM5,11L3,11v2h2v-2zM3,21h18v-2L3,19v2zM5,15L3,15v2h2v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_clear_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_clear_black_24dp.xml
new file mode 100644
index 0000000..5d76947
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_clear_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,5h2L9,3L7,3v2zM7,13h2v-2L7,11v2zM7,21h2v-2L7,19v2zM11,17h2v-2h-2v2zM11,21h2v-2h-2v2zM3,21h2v-2L3,19v2zM3,17h2v-2L3,15v2zM3,13h2v-2L3,11v2zM3,9h2L5,7L3,7v2zM3,5h2L5,3L3,3v2zM11,13h2v-2h-2v2zM19,17h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21h2v-2h-2v2zM19,9h2L21,7h-2v2zM11,9h2L13,7h-2v2zM19,3v2h2L21,3h-2zM11,5h2L13,3h-2v2zM15,21h2v-2h-2v2zM15,13h2v-2h-2v2zM15,5h2L17,3h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_color_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_color_black_24dp.xml
new file mode 100644
index 0000000..1889bdf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_color_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.75,7L14,3.25l-10,10L4,17h3.75l10,-10zM20.71,4.04c0.39,-0.39 0.39,-1.02 0,-1.41L18.37,0.29c-0.39,-0.39 -1.02,-0.39 -1.41,0L15,2.25 18.75,6l1.96,-1.96z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,20h24v4H0z"
+ android:fillAlpha=".36"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_horizontal_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_horizontal_black_24dp.xml
new file mode 100644
index 0000000..987488f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_horizontal_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,21h2v-2L3,19v2zM5,7L3,7v2h2L5,7zM3,17h2v-2L3,15v2zM7,21h2v-2L7,19v2zM5,3L3,3v2h2L5,3zM9,3L7,3v2h2L9,3zM17,3h-2v2h2L17,3zM13,7h-2v2h2L13,7zM13,3h-2v2h2L13,3zM19,17h2v-2h-2v2zM11,21h2v-2h-2v2zM3,13h18v-2L3,11v2zM19,3v2h2L21,3h-2zM19,9h2L21,7h-2v2zM11,17h2v-2h-2v2zM15,21h2v-2h-2v2zM19,21h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_inner_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_inner_black_24dp.xml
new file mode 100644
index 0000000..477844d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_inner_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,21h2v-2L3,19v2zM7,21h2v-2L7,19v2zM5,7L3,7v2h2L5,7zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM5,3L3,3v2h2L5,3zM17,3h-2v2h2L17,3zM19,9h2L21,7h-2v2zM19,3v2h2L21,3h-2zM15,21h2v-2h-2v2zM13,3h-2v8L3,11v2h8v8h2v-8h8v-2h-8L13,3zM19,21h2v-2h-2v2zM19,17h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_left_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_left_black_24dp.xml
new file mode 100644
index 0000000..5b37864
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_left_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,21h2v-2h-2v2zM11,17h2v-2h-2v2zM11,5h2L13,3h-2v2zM11,9h2L13,7h-2v2zM11,13h2v-2h-2v2zM7,21h2v-2L7,19v2zM7,5h2L9,3L7,3v2zM7,13h2v-2L7,11v2zM3,21h2L5,3L3,3v18zM19,9h2L21,7h-2v2zM15,21h2v-2h-2v2zM19,17h2v-2h-2v2zM19,3v2h2L21,3h-2zM19,13h2v-2h-2v2zM19,21h2v-2h-2v2zM15,13h2v-2h-2v2zM15,5h2L17,3h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_outer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_outer_black_24dp.xml
new file mode 100644
index 0000000..e353f68
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_outer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,7h-2v2h2L13,7zM13,11h-2v2h2v-2zM17,11h-2v2h2v-2zM3,3v18h18L21,3L3,3zM19,19L5,19L5,5h14v14zM13,15h-2v2h2v-2zM9,11L7,11v2h2v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_right_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_right_black_24dp.xml
new file mode 100644
index 0000000..d9f5b6b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_right_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,21h2v-2L7,19v2zM3,5h2L5,3L3,3v2zM7,5h2L9,3L7,3v2zM7,13h2v-2L7,11v2zM3,21h2v-2L3,19v2zM11,21h2v-2h-2v2zM3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM11,17h2v-2h-2v2zM15,13h2v-2h-2v2zM19,3v18h2L21,3h-2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM11,13h2v-2h-2v2zM11,5h2L13,3h-2v2zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_style_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_style_black_24dp.xml
new file mode 100644
index 0000000..5d69a9e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_style_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,21h2v-2h-2v2zM19,21h2v-2h-2v2zM7,21h2v-2L7,19v2zM11,21h2v-2h-2v2zM19,17h2v-2h-2v2zM19,13h2v-2h-2v2zM3,3v18h2L5,5h16L21,3L3,3zM19,9h2L21,7h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_top_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_top_black_24dp.xml
new file mode 100644
index 0000000..4de691c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_top_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,21h2v-2L7,19v2zM7,13h2v-2L7,11v2zM11,13h2v-2h-2v2zM11,21h2v-2h-2v2zM3,17h2v-2L3,15v2zM3,21h2v-2L3,19v2zM3,13h2v-2L3,11v2zM3,9h2L5,7L3,7v2zM11,17h2v-2h-2v2zM19,9h2L21,7h-2v2zM19,13h2v-2h-2v2zM3,3v2h18L21,3L3,3zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM11,9h2L13,7h-2v2zM19,21h2v-2h-2v2zM15,13h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_vertical_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_vertical_black_24dp.xml
new file mode 100644
index 0000000..3b3655b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_vertical_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,9h2L5,7L3,7v2zM3,5h2L5,3L3,3v2zM7,21h2v-2L7,19v2zM7,13h2v-2L7,11v2zM3,13h2v-2L3,11v2zM3,21h2v-2L3,19v2zM3,17h2v-2L3,15v2zM7,5h2L9,3L7,3v2zM19,17h2v-2h-2v2zM11,21h2L13,3h-2v18zM19,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,3v2h2L21,3h-2zM19,9h2L21,7h-2v2zM15,5h2L17,3h-2v2zM15,21h2v-2h-2v2zM15,13h2v-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_drag_handle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_drag_handle_black_24dp.xml
new file mode 100644
index 0000000..68a7190
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_drag_handle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_center_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_center_black_24dp.xml
new file mode 100644
index 0000000..6ae7996
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_center_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,15v2h10v-2L7,15zM3,21h18v-2L3,19v2zM3,13h18v-2L3,11v2zM7,7v2h10L17,7L7,7zM3,3v2h18L21,3L3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_justify_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_justify_black_24dp.xml
new file mode 100644
index 0000000..7f4e2fe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_justify_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,21h18v-2L3,19v2zM3,17h18v-2L3,15v2zM3,13h18v-2L3,11v2zM3,9h18L21,7L3,7v2zM3,3v2h18L21,3L3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_left_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_left_black_24dp.xml
new file mode 100644
index 0000000..2896663
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_left_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,15L3,15v2h12v-2zM15,7L3,7v2h12L15,7zM3,13h18v-2L3,11v2zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_right_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_right_black_24dp.xml
new file mode 100644
index 0000000..c5af12b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_right_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,21h18v-2L3,19v2zM9,17h12v-2L9,15v2zM3,13h18v-2L3,11v2zM9,9h12L21,7L9,7v2zM3,3v2h18L21,3L3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_bold_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_bold_black_24dp.xml
new file mode 100644
index 0000000..8879506
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_bold_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.6,10.79c0.97,-0.67 1.65,-1.77 1.65,-2.79 0,-2.26 -1.75,-4 -4,-4L7,4v14h7.04c2.09,0 3.71,-1.7 3.71,-3.79 0,-1.52 -0.86,-2.82 -2.15,-3.42zM10,6.5h3c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5h-3v-3zM13.5,15.5L10,15.5v-3h3.5c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_clear_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_clear_black_24dp.xml
new file mode 100644
index 0000000..4c903c7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_clear_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.27,5L2,6.27l6.97,6.97L6.5,19h3l1.57,-3.66L16.73,21 18,19.73 3.55,5.27 3.27,5zM6,5v0.18L8.82,8h2.4l-0.72,1.68 2.1,2.1L14.21,8H20V5H6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_fill_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_fill_black_24dp.xml
new file mode 100644
index 0000000..24ddb27
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_fill_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.56,8.94L7.62,0 6.21,1.41l2.38,2.38 -5.15,5.15c-0.59,0.59 -0.59,1.54 0,2.12l5.5,5.5c0.29,0.29 0.68,0.44 1.06,0.44s0.77,-0.15 1.06,-0.44l5.5,-5.5c0.59,-0.58 0.59,-1.53 0,-2.12zM5.21,10L10,5.21 14.79,10H5.21zM19,11.5s-2,2.17 -2,3.5c0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-1.33 -2,-3.5 -2,-3.5z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,20h24v4H0z"
+ android:fillAlpha=".36"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_reset_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_reset_black_24dp.xml
new file mode 100644
index 0000000..0a0ecf9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_reset_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,14c0,-4 -6,-10.8 -6,-10.8s-1.33,1.51 -2.73,3.52l8.59,8.59c0.09,-0.42 0.14,-0.86 0.14,-1.31zM17.12,17.12L12.5,12.5 5.27,5.27 4,6.55l3.32,3.32C6.55,11.32 6,12.79 6,14c0,3.31 2.69,6 6,6 1.52,0 2.9,-0.57 3.96,-1.5l2.63,2.63 1.27,-1.27 -2.74,-2.74z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_text_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_text_black_24dp.xml
new file mode 100644
index 0000000..5361c5f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_text_black_24dp.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,20h24v4H0z"
+ android:fillAlpha=".36"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,3L5.5,17h2.25l1.12,-3h6.25l1.12,3h2.25L13,3h-2zM9.62,12L12,5.67 14.38,12L9.62,12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_decrease_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_decrease_black_24dp.xml
new file mode 100644
index 0000000..e51124d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_decrease_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,17h10v-2L11,15v2zM3,12l4,4L7,8l-4,4zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3zM11,9h10L21,7L11,7v2zM11,13h10v-2L11,11v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_increase_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_increase_black_24dp.xml
new file mode 100644
index 0000000..d8a6b90
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_increase_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,21h18v-2L3,19v2zM3,8v8l4,-4 -4,-4zM11,17h10v-2L11,15v2zM3,3v2h18L21,3L3,3zM11,9h10L21,7L11,7v2zM11,13h10v-2L11,11v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_italic_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_italic_black_24dp.xml
new file mode 100644
index 0000000..707d410
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_italic_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_line_spacing_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_line_spacing_black_24dp.xml
new file mode 100644
index 0000000..d529661
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_line_spacing_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,7h2.5L5,3.5 1.5,7L4,7v10L1.5,17L5,20.5 8.5,17L6,17L6,7zM10,5v2h12L22,5L10,5zM10,19h12v-2L10,17v2zM10,13h12v-2L10,11v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_bulleted_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_bulleted_black_24dp.xml
new file mode 100644
index 0000000..5937a4e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_bulleted_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5S3.17,7.5 4,7.5 5.5,6.83 5.5,6 4.83,4.5 4,4.5zM4,16.67c-0.74,0 -1.33,0.6 -1.33,1.33s0.6,1.33 1.33,1.33 1.33,-0.6 1.33,-1.33 -0.59,-1.33 -1.33,-1.33zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2zM7,5v2h14L21,5L7,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_numbered_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_numbered_black_24dp.xml
new file mode 100644
index 0000000..0235afc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_numbered_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,17h2v0.5L3,17.5v1h1v0.5L2,19v1h3v-4L2,16v1zM3,8h1L4,4L2,4v1h1v3zM2,11h1.8L2,13.1v0.9h3v-1L3.2,13L5,10.9L5,10L2,10v1zM7,5v2h14L21,5L7,5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_paint_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_paint_black_24dp.xml
new file mode 100644
index 0000000..ce8fa63
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_paint_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,4V3c0,-0.55 -0.45,-1 -1,-1H5c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1V6h1v4H9v11c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-9h8V4h-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_quote_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_quote_black_24dp.xml
new file mode 100644
index 0000000..68cda7d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_quote_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,17h3l2,-4L11,7L5,7v6h3zM14,17h3l2,-4L19,7h-6v6h3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_shapes_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_shapes_black_24dp.xml
new file mode 100644
index 0000000..4313118
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_shapes_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,7L23,1h-6v2L7,3L7,1L1,1v6h2v10L1,17v6h6v-2h10v2h6v-6h-2L21,7h2zM3,3h2v2L3,5L3,3zM5,21L3,21v-2h2v2zM17,19L7,19v-2L5,17L5,7h2L7,5h10v2h2v10h-2v2zM21,21h-2v-2h2v2zM19,5L19,3h2v2h-2zM13.73,14h-3.49l-0.73,2L7.89,16l3.4,-9h1.4l3.41,9h-1.63l-0.74,-2zM10.69,12.74h2.61L12,8.91l-1.31,3.83z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_size_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_size_black_24dp.xml
new file mode 100644
index 0000000..d41bb05
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_size_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,4v3h5v12h3L17,7h5L22,4L9,4zM3,12h3v7h3v-7h3L12,9L3,9v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_strikethrough_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_strikethrough_black_24dp.xml
new file mode 100644
index 0000000..386eb3e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_strikethrough_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,19h4v-3h-4v3zM5,4v3h5v3h4V7h5V4H5zM3,14h18v-2H3v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_l_to_r_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_l_to_r_black_24dp.xml
new file mode 100644
index 0000000..ddf3abf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_l_to_r_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,10v5h2L11,4h2v11h2L15,4h2L17,2L9,2C6.79,2 5,3.79 5,6s1.79,4 4,4zM21,18l-4,-4v3L5,17v2h12v3l4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_r_to_l_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_r_to_l_black_24dp.xml
new file mode 100644
index 0000000..509cdaf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_r_to_l_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,10v5h2L12,4h2v11h2L16,4h2L18,2h-8C7.79,2 6,3.79 6,6s1.79,4 4,4zM8,17v-3l-4,4 4,4v-3h12v-2L8,17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_underlined_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_underlined_black_24dp.xml
new file mode 100644
index 0000000..948a96c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_underlined_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,17c3.31,0 6,-2.69 6,-6L18,3h-2.5v8c0,1.93 -1.57,3.5 -3.5,3.5S8.5,12.93 8.5,11L8.5,3L6,3v8c0,3.31 2.69,6 6,6zM5,19v2h14v-2L5,19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_functions_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_functions_black_24dp.xml
new file mode 100644
index 0000000..9191074
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_functions_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,4H6v2l6.5,6L6,18v2h12v-3h-7l5,-5 -5,-5h7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_highlight_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_highlight_black_24dp.xml
new file mode 100644
index 0000000..2646f57
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_highlight_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,14l3,3v5h6v-5l3,-3L18,9L6,9zM11,2h2v3h-2zM3.5,5.875L4.914,4.46l2.12,2.122L5.62,7.997zM16.96,6.585l2.123,-2.12 1.414,1.414L18.375,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_chart_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_chart_black_24dp.xml
new file mode 100644
index 0000000..75d0dde
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_chart_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_comment_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_comment_black_24dp.xml
new file mode 100644
index 0000000..dee21b5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_comment_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_drive_file_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_drive_file_black_24dp.xml
new file mode 100644
index 0000000..7a6d094
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_drive_file_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_emoticon_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_emoticon_black_24dp.xml
new file mode 100644
index 0000000..43d5552
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_emoticon_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_invitation_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_invitation_black_24dp.xml
new file mode 100644
index 0000000..22f1bb6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_invitation_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,12h-5v5h5v-5zM16,1v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2h-1L18,1h-2zM19,19L5,19L5,8h14v11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_link_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_link_black_24dp.xml
new file mode 100644
index 0000000..538c5bd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_link_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_photo_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_photo_black_24dp.xml
new file mode 100644
index 0000000..b201859
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_photo_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_linear_scale_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_linear_scale_black_24dp.xml
new file mode 100644
index 0000000..2c012d3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_linear_scale_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.5,9.5c-1.03,0 -1.9,0.62 -2.29,1.5h-2.92c-0.39,-0.88 -1.26,-1.5 -2.29,-1.5s-1.9,0.62 -2.29,1.5H6.79c-0.39,-0.88 -1.26,-1.5 -2.29,-1.5C3.12,9.5 2,10.62 2,12s1.12,2.5 2.5,2.5c1.03,0 1.9,-0.62 2.29,-1.5h2.92c0.39,0.88 1.26,1.5 2.29,1.5s1.9,-0.62 2.29,-1.5h2.92c0.39,0.88 1.26,1.5 2.29,1.5 1.38,0 2.5,-1.12 2.5,-2.5s-1.12,-2.5 -2.5,-2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_merge_type_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_merge_type_black_24dp.xml
new file mode 100644
index 0000000..40b6b38
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_merge_type_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,20.41L18.41,19 15,15.59 13.59,17 17,20.41zM7.5,8H11v5.59L5.59,19 7,20.41l6,-6V8h3.5L12,3.5 7.5,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_comment_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_comment_black_24dp.xml
new file mode 100644
index 0000000..301e46f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_comment_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_edit_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_edit_black_24dp.xml
new file mode 100644
index 0000000..2ab2fb7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_edit_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_money_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_money_off_black_24dp.xml
new file mode 100644
index 0000000..32bd881
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_money_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.5,6.9c1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-0.53,0.12 -1.03,0.3 -1.48,0.54l1.47,1.47c0.41,-0.17 0.91,-0.27 1.51,-0.27zM5.33,4.06L4.06,5.33 7.5,8.77c0,2.08 1.56,3.21 3.91,3.91l3.51,3.51c-0.34,0.48 -1.05,0.91 -2.42,0.91 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c0.96,-0.18 1.82,-0.55 2.45,-1.12l2.22,2.22 1.27,-1.27L5.33,4.06z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_publish_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_publish_black_24dp.xml
new file mode 100644
index 0000000..5121340
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_publish_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,4v2h14L19,4L5,4zM5,14h4v6h6v-6h4l-7,-7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_short_text_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_short_text_black_24dp.xml
new file mode 100644
index 0000000..11c24c5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_short_text_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,9h16v2L4,11zM4,13h10v2L4,15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_space_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_space_bar_black_24dp.xml
new file mode 100644
index 0000000..2ca50db
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_space_bar_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,9v4H6V9H4v6h16V9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_strikethrough_s_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_strikethrough_s_black_24dp.xml
new file mode 100644
index 0000000..c3dd1d9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_strikethrough_s_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M7.24,8.75c-0.26,-0.48 -0.39,-1.03 -0.39,-1.67 0,-0.61 0.13,-1.16 0.4,-1.67 0.26,-0.5 0.63,-0.93 1.11,-1.29 0.48,-0.35 1.05,-0.63 1.7,-0.83 0.66,-0.19 1.39,-0.29 2.18,-0.29 0.81,0 1.54,0.11 2.21,0.34 0.66,0.22 1.23,0.54 1.69,0.94 0.47,0.4 0.83,0.88 1.08,1.43 0.25,0.55 0.38,1.15 0.38,1.81h-3.01c0,-0.31 -0.05,-0.59 -0.15,-0.85 -0.09,-0.27 -0.24,-0.49 -0.44,-0.68 -0.2,-0.19 -0.45,-0.33 -0.75,-0.44 -0.3,-0.1 -0.66,-0.16 -1.06,-0.16 -0.39,0 -0.74,0.04 -1.03,0.13 -0.2 [...]
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_text_fields_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_text_fields_black_24dp.xml
new file mode 100644
index 0000000..dd81ddf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_text_fields_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2.5,4v3h5v12h3L10.5,7h5L15.5,4h-13zM21.5,9h-9v3h3v7h3v-7h3L21.5,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_bottom_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_bottom_black_24dp.xml
new file mode 100644
index 0000000..78c3919
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_bottom_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,13h-3V3h-2v10H8l4,4 4,-4zM4,19v2h16v-2H4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_center_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_center_black_24dp.xml
new file mode 100644
index 0000000..f8d0ee3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_center_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,19h3v4h2v-4h3l-4,-4 -4,4zM16,5h-3L13,1h-2v4L8,5l4,4 4,-4zM4,11v2h16v-2L4,11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_top_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_top_black_24dp.xml
new file mode 100644
index 0000000..1312b6c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_top_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,11h3v10h2V11h3l-4,-4 -4,4zM4,3v2h16V3H4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/editor/ic_wrap_text_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/editor/ic_wrap_text_black_24dp.xml
new file mode 100644
index 0000000..8b2b24a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/editor/ic_wrap_text_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,19h6v-2L4,17v2zM20,5L4,5v2h16L20,5zM17,11L4,11v2h13.25c1.1,0 2,0.9 2,2s-0.9,2 -2,2L15,17v-2l-3,3 3,3v-2h2c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_attachment_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_attachment_black_24dp.xml
new file mode 100644
index 0000000..c18714f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_attachment_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,12.5C2,9.46 4.46,7 7.5,7H18c2.21,0 4,1.79 4,4s-1.79,4 -4,4H9.5C8.12,15 7,13.88 7,12.5S8.12,10 9.5,10H17v2H9.41c-0.55,0 -0.55,1 0,1H18c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2H7.5C5.57,9 4,10.57 4,12.5S5.57,16 7.5,16H17v2H7.5C4.46,18 2,15.54 2,12.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_black_24dp.xml
new file mode 100644
index 0000000..e0940ca
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_circle_black_24dp.xml
new file mode 100644
index 0000000..c0c7001
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM16.5,16L8,16c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3l0.14,0.01C8.58,8.28 10.13,7 12,7c2.21,0 4,1.79 4,4h0.5c1.38,0 2.5,1.12 2.5,2.5S17.88,16 16.5,16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_done_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_done_black_24dp.xml
new file mode 100644
index 0000000..cf7e2cc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_done_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM10,17l-3.5,-3.5 1.41,-1.41L10,14.17 15.18,9l1.41,1.41L10,17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_download_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_download_black_24dp.xml
new file mode 100644
index 0000000..261c312
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_download_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_off_black_24dp.xml
new file mode 100644
index 0000000..1e753cf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4c-1.48,0 -2.85,0.43 -4.01,1.17l1.46,1.46C10.21,6.23 11.08,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3 0,1.13 -0.64,2.11 -1.56,2.62l1.45,1.45C23.16,18.16 24,16.68 24,15c0,-2.64 -2.05,-4.78 -4.65,-4.96zM3,5.27l2.75,2.74C2.56,8.15 0,10.77 0,14c0,3.31 2.69,6 6,6h11.73l2,2L21,20.73 4.27,4 3,5.27zM7.73,10l8,8H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h1.73z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_queue_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_queue_black_24dp.xml
new file mode 100644
index 0000000..0ca5119
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_queue_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h0.71C7.37,7.69 9.48,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3s-1.34,3 -3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_upload_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_upload_black_24dp.xml
new file mode 100644
index 0000000..0862816
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_upload_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_create_new_folder_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_create_new_folder_black_24dp.xml
new file mode 100644
index 0000000..a4dd6d2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_create_new_folder_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM19,14h-3v3h-2v-3h-3v-2h3L14,9h2v3h3v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_file_download_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_file_download_black_24dp.xml
new file mode 100644
index 0000000..492b41d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_file_download_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_file_upload_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_file_upload_black_24dp.xml
new file mode 100644
index 0000000..d633972
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_file_upload_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_black_24dp.xml
new file mode 100644
index 0000000..d7c6145
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_open_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_open_black_24dp.xml
new file mode 100644
index 0000000..b71523a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_open_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_shared_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_shared_black_24dp.xml
new file mode 100644
index 0000000..9ae49b6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_shared_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM15,9c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM19,17h-8v-1c0,-1.33 2.67,-2 4,-2s4,0.67 4,2v1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_black_24dp.xml
new file mode 100644
index 0000000..7b143de
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_connected_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_connected_black_24dp.xml
new file mode 100644
index 0000000..7e49cf2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_connected_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM19,7L5,7v1.63c3.96,1.28 7.09,4.41 8.37,8.37L19,17L19,7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_computer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_computer_black_24dp.xml
new file mode 100644
index 0000000..4599f98
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_computer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18c1.1,0 1.99,-0.9 1.99,-2L22,6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_mac_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_mac_black_24dp.xml
new file mode 100644
index 0000000..2c87323
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_mac_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7l-2,3v1h8v-1l-2,-3h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,14L3,14L3,4h18v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_windows_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_windows_black_24dp.xml
new file mode 100644
index 0000000..bec86e7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_windows_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_developer_board_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_developer_board_black_24dp.xml
new file mode 100644
index 0000000..27d3805
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_developer_board_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,9L22,7h-2L20,5c0,-1.1 -0.9,-2 -2,-2L4,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2v-2h-2L20,9h2zM18,19L4,19L4,5h14v14zM6,13h5v4L6,17zM12,7h4v3h-4zM6,7h5v5L6,12zM12,11h4v6h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_device_hub_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_device_hub_black_24dp.xml
new file mode 100644
index 0000000..d923eb0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_device_hub_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,16l-4,-4V8.82C14.16,8.4 15,7.3 15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6c0,1.3 0.84,2.4 2,2.82V12l-4,4H3v5h5v-3.05l4,-4.2 4,4.2V21h5v-5h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_devices_other_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_devices_other_black_24dp.xml
new file mode 100644
index 0000000..c98c256
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_devices_other_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,6h18L21,4L3,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h4v-2L3,18L3,6zM13,12L9,12v1.78c-0.61,0.55 -1,1.33 -1,2.22s0.39,1.67 1,2.22L9,20h4v-1.78c0.61,-0.55 1,-1.34 1,-2.22s-0.39,-1.67 -1,-2.22L13,12zM11,17.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM22,8h-6c-0.5,0 -1,0.5 -1,1v10c0,0.5 0.5,1 1,1h6c0.5,0 1,-0.5 1,-1L23,9c0,-0.5 -0.5,-1 -1,-1zM21,18h-4v-8h4v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_dock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_dock_black_24dp.xml
new file mode 100644
index 0000000..a88dd39
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_dock_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,23h8v-2L8,21v2zM16,1.01L8,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,3c0,-1.1 -0.9,-1.99 -2,-1.99zM16,15L8,15L8,5h8v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_gamepad_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_gamepad_black_24dp.xml
new file mode 100644
index 0000000..d1f300e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_gamepad_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,7.5V2H9v5.5l3,3 3,-3zM7.5,9H2v6h5.5l3,-3 -3,-3zM9,16.5V22h6v-5.5l-3,-3 -3,3zM16.5,9l-3,3 3,3H22V9h-5.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_black_24dp.xml
new file mode 100644
index 0000000..d4503ce
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7c0,-4.97 -4.03,-9 -9,-9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_mic_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_mic_black_24dp.xml
new file mode 100644
index 0000000..55da400
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_mic_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 -9,-9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_down_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_down_black_24dp.xml
new file mode 100644
index 0000000..ad33063
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_down_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_left_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_left_black_24dp.xml
new file mode 100644
index 0000000..c9f7747
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_left_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14,5.5l-6,6 6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_right_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_right_black_24dp.xml
new file mode 100644
index 0000000..a3d1622
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_right_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_up_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_up_black_24dp.xml
new file mode 100644
index 0000000..57387ee
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_up_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_backspace_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_backspace_black_24dp.xml
new file mode 100644
index 0000000..827cde0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_backspace_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,11H6.83l3.58,-3.59L9,6l-6,6 6,6 1.41,-1.41L6.83,13H21z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_black_24dp.xml
new file mode 100644
index 0000000..f0b93eb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,5L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM11,8h2v2h-2L11,8zM11,11h2v2h-2v-2zM8,8h2v2L8,10L8,8zM8,11h2v2L8,13v-2zM7,13L5,13v-2h2v2zM7,10L5,10L5,8h2v2zM16,17L8,17v-2h8v2zM16,13h-2v-2h2v2zM16,10h-2L14,8h2v2zM19,13h-2v-2h2v2zM19,10h-2L17,8h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_capslock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_capslock_black_24dp.xml
new file mode 100644
index 0000000..cc4f038
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_capslock_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,8.41L16.59,13 18,11.59l-6,-6 -6,6L7.41,13 12,8.41zM6,18h12v-2H6v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_hide_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_hide_black_24dp.xml
new file mode 100644
index 0000000..e26c127
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_hide_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3L4,3c-1.1,0 -1.99,0.9 -1.99,2L2,15c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM11,6h2v2h-2L11,6zM11,9h2v2h-2L11,9zM8,6h2v2L8,8L8,6zM8,9h2v2L8,11L8,9zM7,11L5,11L5,9h2v2zM7,8L5,8L5,6h2v2zM16,15L8,15v-2h8v2zM16,11h-2L14,9h2v2zM16,8h-2L14,6h2v2zM19,11h-2L17,9h2v2zM19,8h-2L17,6h2v2zM12,23l4,-4L8,19l4,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_return_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_return_black_24dp.xml
new file mode 100644
index 0000000..83ccf26
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_return_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,7v4H5.83l3.58,-3.59L8,6l-6,6 6,6 1.41,-1.41L5.83,13H21V7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_tab_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_tab_black_24dp.xml
new file mode 100644
index 0000000..2f54940
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_tab_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.59,7.41L15.17,11H1v2h14.17l-3.59,3.59L13,18l6,-6 -6,-6 -1.41,1.41zM20,6v12h2V6h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_voice_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_voice_black_24dp.xml
new file mode 100644
index 0000000..831bb5f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_voice_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,15c1.66,0 2.99,-1.34 2.99,-3L15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6v6c0,1.66 1.34,3 3,3zM17.3,12c0,3 -2.54,5.1 -5.3,5.1S6.7,15 6.7,12L5,12c0,3.42 2.72,6.23 6,6.72L11,22h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_black_24dp.xml
new file mode 100644
index 0000000..1cba86f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_chromebook_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_chromebook_black_24dp.xml
new file mode 100644
index 0000000..9cba495
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_chromebook_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,18L22,3L2,3v15L0,18v2h24v-2h-2zM14,18h-4v-1h4v1zM20,15L4,15L4,5h16v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_mac_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_mac_black_24dp.xml
new file mode 100644
index 0000000..7a852ec
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_mac_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18c1.1,0 1.99,-0.9 1.99,-2L22,5c0,-1.1 -0.9,-2 -2,-2L4,3c-1.1,0 -2,0.9 -2,2v11c0,1.1 0.9,2 2,2L0,18c0,1.1 0.9,2 2,2h20c1.1,0 2,-0.9 2,-2h-4zM4,5h16v11L4,16L4,5zM12,19c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_windows_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_windows_black_24dp.xml
new file mode 100644
index 0000000..408c4bd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_windows_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18v-1c1.1,0 1.99,-0.9 1.99,-2L22,5c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2v1H0v2h24v-2h-4zM4,5h16v10H4V5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_memory_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_memory_black_24dp.xml
new file mode 100644
index 0000000..76e03b6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_memory_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,9L9,9v6h6L15,9zM13,13h-2v-2h2v2zM21,11L21,9h-2L19,7c0,-1.1 -0.9,-2 -2,-2h-2L15,3h-2v2h-2L11,3L9,3v2L7,5c-1.1,0 -2,0.9 -2,2v2L3,9v2h2v2L3,13v2h2v2c0,1.1 0.9,2 2,2h2v2h2v-2h2v2h2v-2h2c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2zM17,17L7,17L7,7h10v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_mouse_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_mouse_black_24dp.xml
new file mode 100644
index 0000000..bec6a44
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_mouse_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,1.07L13,9h7c0,-4.08 -3.05,-7.44 -7,-7.93zM4,15c0,4.42 3.58,8 8,8s8,-3.58 8,-8v-4L4,11v4zM11,1.07C7.05,1.56 4,4.92 4,9h7L11,1.07z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_android_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_android_black_24dp.xml
new file mode 100644
index 0000000..ab9b9a0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_android_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,1L8,1C6.34,1 5,2.34 5,4v16c0,1.66 1.34,3 3,3h8c1.66,0 3,-1.34 3,-3L19,4c0,-1.66 -1.34,-3 -3,-3zM14,21h-4v-1h4v1zM17.25,18L6.75,18L6.75,4h10.5v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_iphone_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_iphone_black_24dp.xml
new file mode 100644
index 0000000..2e30473
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_iphone_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,1h-8C6.12,1 5,2.12 5,3.5v17C5,21.88 6.12,23 7.5,23h8c1.38,0 2.5,-1.12 2.5,-2.5v-17C18,2.12 16.88,1 15.5,1zM11.5,22c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM16,18L7,18L7,4h9v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_black_24dp.xml
new file mode 100644
index 0000000..150ced4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6h18L22,4L4,4c-1.1,0 -2,0.9 -2,2v11L0,17v3h14v-3L4,17L4,6zM23,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L24,9c0,-0.55 -0.45,-1 -1,-1zM22,17h-4v-7h4v7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_off_black_24dp.xml
new file mode 100644
index 0000000..8f63765
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,6V4H6.82l2,2H22zM1.92,1.65L0.65,2.92l1.82,1.82C2.18,5.08 2,5.52 2,6v11H0v3h17.73l2.35,2.35 1.27,-1.27L3.89,3.62 1.92,1.65zM4,6.27L14.73,17H4V6.27zM23,8h-6c-0.55,0 -1,0.45 -1,1v4.18l2,2V10h4v7h-2.18l3,3H23c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_power_input_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_power_input_black_24dp.xml
new file mode 100644
index 0000000..cfa62a2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_power_input_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,9v2h19L21,9L2,9zM2,15h5v-2L2,13v2zM9,15h5v-2L9,13v2zM16,15h5v-2h-5v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_router_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_router_black_24dp.xml
new file mode 100644
index 0000000..fa79931
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_router_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.2,5.9l0.8,-0.8C19.6,3.7 17.8,3 16,3s-3.6,0.7 -5,2.1l0.8,0.8C13,4.8 14.5,4.2 16,4.2s3,0.6 4.2,1.7zM19.3,6.7c-0.9,-0.9 -2.1,-1.4 -3.3,-1.4s-2.4,0.5 -3.3,1.4l0.8,0.8c0.7,-0.7 1.6,-1 2.5,-1 0.9,0 1.8,0.3 2.5,1l0.8,-0.8zM19,13h-2L17,9h-2v4L5,13c-1.1,0 -2,0.9 -2,2v4c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-4c0,-1.1 -0.9,-2 -2,-2zM8,18L6,18v-2h2v2zM11.5,18h-2v-2h2v2zM15,18h-2v-2h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_scanner_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_scanner_black_24dp.xml
new file mode 100644
index 0000000..1c54776
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_scanner_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.8,10.7L4.2,5l-0.7,1.9L17.6,12L5,12c-1.1,0 -2,0.9 -2,2v4c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-5.5c0,-0.8 -0.5,-1.6 -1.2,-1.8zM7,17L5,17v-2h2v2zM19,17L9,17v-2h10v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_security_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_security_black_24dp.xml
new file mode 100644
index 0000000..9186423
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_security_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_sim_card_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_sim_card_black_24dp.xml
new file mode 100644
index 0000000..6e659a8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_sim_card_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.99,4c0,-1.1 -0.89,-2 -1.99,-2h-8L4,8v12c0,1.1 0.9,2 2,2h12.01c1.1,0 1.99,-0.9 1.99,-2l-0.01,-16zM9,19L7,19v-2h2v2zM17,19h-2v-2h2v2zM9,15L7,15v-4h2v4zM13,19h-2v-4h2v4zM13,13h-2v-2h2v2zM17,15h-2v-4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_smartphone_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_smartphone_black_24dp.xml
new file mode 100644
index 0000000..0f291f1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_smartphone_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_black_24dp.xml
new file mode 100644
index 0000000..cc84a59
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,2L7,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2L19,4c0,-1.1 -0.9,-2 -2,-2zM12,4c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zM12,20c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_group_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_group_black_24dp.xml
new file mode 100644
index 0000000..85236e7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_group_black_24dp.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_android_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_android_black_24dp.xml
new file mode 100644
index 0000000..d81be40
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_android_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,0L6,0C4.34,0 3,1.34 3,3v18c0,1.66 1.34,3 3,3h12c1.66,0 3,-1.34 3,-3L21,3c0,-1.66 -1.34,-3 -3,-3zM14,22h-4v-1h4v1zM19.25,19L4.75,19L4.75,3h14.5v16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_black_24dp.xml
new file mode 100644
index 0000000..901ead2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,4L3,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 1.99,-0.9 1.99,-2L23,6c0,-1.1 -0.9,-2 -2,-2zM19,18L5,18L5,6h14v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_mac_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_mac_black_24dp.xml
new file mode 100644
index 0000000..394d05f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_mac_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.5,0h-14C3.12,0 2,1.12 2,2.5v19C2,22.88 3.12,24 4.5,24h14c1.38,0 2.5,-1.12 2.5,-2.5v-19C21,1.12 19.88,0 18.5,0zM11.5,23c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19,19L4,19L4,3h15v16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_toys_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_toys_black_24dp.xml
new file mode 100644
index 0000000..d56ddfc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_toys_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12c0,-3 2.5,-5.5 5.5,-5.5S23,9 23,12L12,12zM12,12c0,3 -2.5,5.5 -5.5,5.5S1,15 1,12h11zM12,12c-3,0 -5.5,-2.5 -5.5,-5.5S9,1 12,1v11zM12,12c3,0 5.5,2.5 5.5,5.5S15,23 12,23L12,12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tv_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tv_black_24dp.xml
new file mode 100644
index 0000000..36269ad
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tv_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_videogame_asset_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_videogame_asset_black_24dp.xml
new file mode 100644
index 0000000..c61083f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_videogame_asset_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,0v24h24L24,0L0,0zM23,16c0,1.1 -0.9,2 -2,2L3,18c-1.1,0 -2,-0.9 -2,-2L1,8c0,-1.1 0.9,-2 2,-2h18c1.1,0 2,0.9 2,2v8z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM11,13L8,13v3L6,16v-3L3,13v-2h3L6,8h2v3h3v2zM15.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S18.67,9 19.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/hardware/ic_watch_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_watch_black_24dp.xml
new file mode 100644
index 0000000..b658387
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/hardware/ic_watch_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,12c0,-2.54 -1.19,-4.81 -3.04,-6.27L16,0H8l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12zM6,12c0,-3.31 2.69,-6 6,-6s6,2.69 6,6 -2.69,6 -6,6 -6,-2.69 -6,-6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_add_a_photo_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_add_a_photo_black_24dp.xml
new file mode 100644
index 0000000..3d2ba42
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_add_a_photo_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,4L3,1h2v3h3v2L5,6v3L3,9L3,6L0,6L0,4h3zM6,10L6,7h3L9,4h7l1.83,2L21,6c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2L5,22c-1.1,0 -2,-0.9 -2,-2L3,10h3zM13,19c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5 -5,2.24 -5,5 2.24,5 5,5zM9.8,14c0,1.77 1.43,3.2 3.2,3.2s3.2,-1.43 3.2,-3.2 -1.43,-3.2 -3.2,-3.2 -3.2,1.43 -3.2,3.2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_add_to_photos_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_add_to_photos_black_24dp.xml
new file mode 100644
index 0000000..f704ffe
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_add_to_photos_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11h-4v4h-2v-4L9,11L9,9h4L13,5h2v4h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_adjust_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_adjust_black_24dp.xml
new file mode 100644
index 0000000..eef90f7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_adjust_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15,12c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_black_24dp.xml
new file mode 100644
index 0000000..2430c47
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,2L5,2c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM13.88,12.88L12,17l-1.88,-4.12L6,11l4.12,-1.88L12,5l1.88,4.12L18,11l-4.12,1.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_photo_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_photo_black_24dp.xml
new file mode 100644
index 0000000..82ef104
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_photo_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.4,6L14,4H5v17h2v-7h5.6l0.4,2h7V6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_audiotrack_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_audiotrack_black_24dp.xml
new file mode 100644
index 0000000..039afc5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_audiotrack_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_circular_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_circular_black_24dp.xml
new file mode 100644
index 0000000..99db60f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_circular_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM10,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM7,9.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,16.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM7,13.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM10,7.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_linear_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_linear_black_24dp.xml
new file mode 100644
index 0000000..aa6ebaf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_linear_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,17.5c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5 -1.5,0.67 -1.5,1.5 0.67,1.5 1.5,1.5zM9,13c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM9,9c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM3,21h18v-2L3,19v2zM5,9.5c0.83,0 1.5,-0.67 1.5,-1.5S5.83,6.5 5,6.5 3.5,7.17 3.5,8 4.17,9.5 5,9.5zM5,13.5c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5 -1.5,0.67 -1.5,1.5 0.67,1.5 1.5,1.5zM9,17c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_off_black_24dp.xml
new file mode 100644
index 0000000..d342c3e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM13.8,11.48l0.2,0.02c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5 -1.5,0.67 -1.5,1.5l0.02,0.2c0.09,0.67 0.61,1.19 1.28,1.28zM14,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM10,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM21,10.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_on_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_on_black_24dp.xml
new file mode 100644
index 0000000..6bb612b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_on_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM6,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM3,9.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM6,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM21,10.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM14,7c0.55,0 1,-0.45 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_1_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_1_black_24dp.xml
new file mode 100644
index 0000000..d92150a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_1_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_2_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_2_black_24dp.xml
new file mode 100644
index 0000000..b5406ba
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_2_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,2c-1.82,0 -3.53,0.5 -5,1.35C7.99,5.08 10,8.3 10,12s-2.01,6.92 -5,8.65C6.47,21.5 8.18,22 10,22c5.52,0 10,-4.48 10,-10S15.52,2 10,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_3_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_3_black_24dp.xml
new file mode 100644
index 0000000..8247d4a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_3_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,2c-1.05,0 -2.05,0.16 -3,0.46 4.06,1.27 7,5.06 7,9.54 0,4.48 -2.94,8.27 -7,9.54 0.95,0.3 1.95,0.46 3,0.46 5.52,0 10,-4.48 10,-10S14.52,2 9,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_4_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_4_black_24dp.xml
new file mode 100644
index 0000000..bc3cdec
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_4_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-0.89,0 -1.74,-0.2 -2.5,-0.55C11.56,16.5 13,14.42 13,12s-1.44,-4.5 -3.5,-5.45C10.26,6.2 11.11,6 12,6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_5_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_5_black_24dp.xml
new file mode 100644
index 0000000..54301c0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_5_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_6_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_6_black_24dp.xml
new file mode 100644
index 0000000..2f9cd1e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_6_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18V6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_7_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_7_black_24dp.xml
new file mode 100644
index 0000000..920f615
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_7_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_broken_image_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_broken_image_black_24dp.xml
new file mode 100644
index 0000000..8d563a9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_broken_image_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_brush_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_brush_black_24dp.xml
new file mode 100644
index 0000000..54730db
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_brush_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,14c-1.66,0 -3,1.34 -3,3 0,1.31 -1.16,2 -2,2 0.92,1.22 2.49,2 4,2 2.21,0 4,-1.79 4,-4 0,-1.66 -1.34,-3 -3,-3zM20.71,4.63l-1.34,-1.34c-0.39,-0.39 -1.02,-0.39 -1.41,0L9,12.25 11.75,15l8.96,-8.96c0.39,-0.39 0.39,-1.02 0,-1.41z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_alt_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_alt_black_24dp.xml
new file mode 100644
index 0000000..c872f16
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_alt_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_black_24dp.xml
new file mode 100644
index 0000000..9c477f8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_front_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_front_black_24dp.xml
new file mode 100644
index 0000000..7355441
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_front_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,20L5,20v2h5v2l3,-3 -3,-3v2zM14,20v2h5v-2h-5zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -1.99,0.9 -1.99,2S10.9,8 12,8zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM7,2h10v10.5c0,-1.67 -3.33,-2.5 -5,-2.5s-5,0.83 -5,2.5L7,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_rear_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_rear_black_24dp.xml
new file mode 100644
index 0000000..157d4b1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_rear_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,20L5,20v2h5v2l3,-3 -3,-3v2zM14,20v2h5v-2h-5zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM12,6c-1.11,0 -2,-0.9 -2,-2s0.89,-2 1.99,-2 2,0.9 2,2C14,5.1 13.1,6 12,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_roll_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_roll_black_24dp.xml
new file mode 100644
index 0000000..c3f3e95
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_roll_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,5c0,-1.1 -0.9,-2 -2,-2h-1L11,2c0,-0.55 -0.45,-1 -1,-1L6,1c-0.55,0 -1,0.45 -1,1v1L4,3c-1.1,0 -2,0.9 -2,2v15c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2h8L22,5h-8zM12,18h-2v-2h2v2zM12,9h-2L10,7h2v2zM16,18h-2v-2h2v2zM16,9h-2L14,7h2v2zM20,18h-2v-2h2v2zM20,9h-2L18,7h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_strong_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_strong_black_24dp.xml
new file mode 100644
index 0000000..ab723e8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_strong_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_weak_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_weak_black_24dp.xml
new file mode 100644
index 0000000..3fcca95
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_weak_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_black_24dp.xml
new file mode 100644
index 0000000..68d5d0e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,16L22,4c0,-1.1 -0.9,-2 -2,-2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zM11,12l2.03,2.71L16,11l4,5L8,16l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6L2,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_bookmark_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_bookmark_black_24dp.xml
new file mode 100644
index 0000000..05f2dd4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_bookmark_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,12l-2.5,-1.5L15,12L15,4h5v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_color_lens_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_color_lens_black_24dp.xml
new file mode 100644
index 0000000..f75e2fb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_color_lens_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_colorize_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_colorize_black_24dp.xml
new file mode 100644
index 0000000..deaae49
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_colorize_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.71,5.63l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-3.12,3.12 -1.93,-1.91 -1.41,1.41 1.42,1.42L3,16.25V21h4.75l8.92,-8.92 1.42,1.42 1.41,-1.41 -1.92,-1.92 3.12,-3.12c0.4,-0.4 0.4,-1.03 0.01,-1.42zM6.92,19L5,17.08l8.06,-8.06 1.92,1.92L6.92,19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_compare_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_compare_black_24dp.xml
new file mode 100644
index 0000000..4824f3f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_compare_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h5v2h2L12,1h-2v2zM10,18L5,18l5,-6v6zM19,3h-5v2h5v13l-5,-6v9h5c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_black_24dp.xml
new file mode 100644
index 0000000..7953088
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,7h-2v4L7,11v2h4v4h2v-4h4v-2h-4L13,7zM12,2C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_duplicate_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_duplicate_black_24dp.xml
new file mode 100644
index 0000000..472fa9c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_duplicate_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,8h-2v3h-3v2h3v3h2v-3h3v-2h-3zM2,12c0,-2.79 1.64,-5.2 4.01,-6.32L6.01,3.52C2.52,4.76 0,8.09 0,12s2.52,7.24 6.01,8.48v-2.16C3.64,17.2 2,14.79 2,12zM15,3c-4.96,0 -9,4.04 -9,9s4.04,9 9,9 9,-4.04 9,-9 -4.04,-9 -9,-9zM15,19c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_16_9_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_16_9_black_24dp.xml
new file mode 100644
index 0000000..259aaef
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_16_9_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,6L5,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,8c0,-1.1 -0.9,-2 -2,-2zM19,16L5,16L5,8h14v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_3_2_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_3_2_black_24dp.xml
new file mode 100644
index 0000000..85777ed
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_3_2_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,4L5,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM19,18L5,18L5,6h14v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_5_4_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_5_4_black_24dp.xml
new file mode 100644
index 0000000..625e651
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_5_4_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,5L5,5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,17L5,17L5,7h14v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_7_5_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_7_5_black_24dp.xml
new file mode 100644
index 0000000..9e936c7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_7_5_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,7L5,7c-1.1,0 -2,0.9 -2,2v6c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,9c0,-1.1 -0.9,-2 -2,-2zM19,15L5,15L5,9h14v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_black_24dp.xml
new file mode 100644
index 0000000..5a4749c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,15h2V7c0,-1.1 -0.9,-2 -2,-2H9v2h8v8zM7,17V1H5v4H1v2h4v10c0,1.1 0.9,2 2,2h10v4h2v-4h4v-2H7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_din_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_din_black_24dp.xml
new file mode 100644
index 0000000..d6fd5ce
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_din_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_free_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_free_black_24dp.xml
new file mode 100644
index 0000000..9b37938
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_free_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5v4h2L5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2zM5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_landscape_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_landscape_black_24dp.xml
new file mode 100644
index 0000000..625e651
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_landscape_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,5L5,5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,17L5,17L5,7h14v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_original_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_original_black_24dp.xml
new file mode 100644
index 0000000..cf5d94b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_original_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14zM13.96,12.29l-2.75,3.54 -1.96,-2.36L6.5,17h11l-3.54,-4.71z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_portrait_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_portrait_black_24dp.xml
new file mode 100644
index 0000000..e8c60a1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_portrait_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,5c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_rotate_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_rotate_black_24dp.xml
new file mode 100644
index 0000000..d3b3e9f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_rotate_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.47,21.49C4.2,19.93 1.86,16.76 1.5,13L0,13c0.51,6.16 5.66,11 11.95,11 0.23,0 0.44,-0.02 0.66,-0.03L8.8,20.15l-1.33,1.34zM12.05,0c-0.23,0 -0.44,0.02 -0.66,0.04l3.81,3.81 1.33,-1.33C19.8,4.07 22.14,7.24 22.5,11L24,11c-0.51,-6.16 -5.66,-11 -11.95,-11zM16,14h2L18,8c0,-1.11 -0.9,-2 -2,-2h-6v2h6v6zM8,16L8,4L6,4v2L4,6v2h2v8c0,1.1 0.89,2 2,2h8v2h2v-2h2v-2L8,16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_square_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_square_black_24dp.xml
new file mode 100644
index 0000000..f235771
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_square_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,4L6,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,6c0,-1.1 -0.9,-2 -2,-2zM18,18L6,18L6,6h12v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_dehaze_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_dehaze_black_24dp.xml
new file mode 100644
index 0000000..567d841
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_dehaze_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,15.5v2h20v-2L2,15.5zM2,10.5v2h20v-2L2,10.5zM2,5.5v2h20v-2L2,5.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_details_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_details_black_24dp.xml
new file mode 100644
index 0000000..a30104a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_details_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,4l9,16 9,-16L3,4zM6.38,6h11.25L12,16 6.38,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_edit_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_edit_black_24dp.xml
new file mode 100644
index 0000000..2ab2fb7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_edit_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_black_24dp.xml
new file mode 100644
index 0000000..23eabc3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,17v2h2v-2h2v-2h-2v-2h-2v2h-2v2h2zM20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM5,5h6v2L5,7L5,5zM20,20L4,20L20,4v16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_neg_1_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_neg_1_black_24dp.xml
new file mode 100644
index 0000000..94858a0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_neg_1_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,11v2h8v-2L4,11zM19,18h-2L17,7.38L14,8.4L14,6.7L18.7,5h0.3v13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_neg_2_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_neg_2_black_24dp.xml
new file mode 100644
index 0000000..c5c97ca
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_neg_2_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.05,16.29l2.86,-3.07c0.38,-0.39 0.72,-0.79 1.04,-1.18 0.32,-0.39 0.59,-0.78 0.82,-1.17 0.23,-0.39 0.41,-0.78 0.54,-1.17s0.19,-0.79 0.19,-1.18c0,-0.53 -0.09,-1.02 -0.27,-1.46 -0.18,-0.44 -0.44,-0.81 -0.78,-1.11 -0.34,-0.31 -0.77,-0.54 -1.26,-0.71 -0.51,-0.16 -1.08,-0.24 -1.72,-0.24 -0.69,0 -1.31,0.11 -1.85,0.32 -0.54,0.21 -1,0.51 -1.36,0.88 -0.37,0.37 -0.65,0.8 -0.84,1.3 -0.18,0.47 -0.27,0.97 -0.28,1.5h2.14c0.01,-0.31 0.05,-0.6 0.13,-0.87 0.09,-0.29 0.23,-0.54 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_1_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_1_black_24dp.xml
new file mode 100644
index 0000000..9914313
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_1_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,7L8,7v4L4,11v2h4v4h2v-4h4v-2h-4L10,7zM20,18h-2L18,7.38L15,8.4L15,6.7L19.7,5h0.3v13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_2_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_2_black_24dp.xml
new file mode 100644
index 0000000..ad0d66f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_2_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.05,16.29l2.86,-3.07c0.38,-0.39 0.72,-0.79 1.04,-1.18 0.32,-0.39 0.59,-0.78 0.82,-1.17 0.23,-0.39 0.41,-0.78 0.54,-1.17 0.13,-0.39 0.19,-0.79 0.19,-1.18 0,-0.53 -0.09,-1.02 -0.27,-1.46 -0.18,-0.44 -0.44,-0.81 -0.78,-1.11 -0.34,-0.31 -0.77,-0.54 -1.26,-0.71 -0.51,-0.16 -1.08,-0.24 -1.72,-0.24 -0.69,0 -1.31,0.11 -1.85,0.32 -0.54,0.21 -1,0.51 -1.36,0.88 -0.37,0.37 -0.65,0.8 -0.84,1.3 -0.18,0.47 -0.27,0.97 -0.28,1.5h2.14c0.01,-0.31 0.05,-0.6 0.13,-0.87 0.09,-0.29 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_zero_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_zero_black_24dp.xml
new file mode 100644
index 0000000..eddfc71
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_zero_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.14,12.5c0,1 -0.1,1.85 -0.3,2.55 -0.2,0.7 -0.48,1.27 -0.83,1.7 -0.36,0.44 -0.79,0.75 -1.3,0.95 -0.51,0.2 -1.07,0.3 -1.7,0.3 -0.62,0 -1.18,-0.1 -1.69,-0.3 -0.51,-0.2 -0.95,-0.51 -1.31,-0.95 -0.36,-0.44 -0.65,-1.01 -0.85,-1.7 -0.2,-0.7 -0.3,-1.55 -0.3,-2.55v-2.04c0,-1 0.1,-1.85 0.3,-2.55 0.2,-0.7 0.48,-1.26 0.84,-1.69 0.36,-0.43 0.8,-0.74 1.31,-0.93C10.81,5.1 11.38,5 12,5c0.63,0 1.19,0.1 1.7,0.29 0.51,0.19 0.95,0.5 1.31,0.93 0.36,0.43 0.64,0.99 0.84,1.69 0.2,0. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_1_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_1_black_24dp.xml
new file mode 100644
index 0000000..98e9921
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_1_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM14,15h2L16,5h-4v2h2v8zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_2_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_2_black_24dp.xml
new file mode 100644
index 0000000..159b34c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_2_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM17,13h-4v-2h2c1.1,0 2,-0.89 2,-2L17,7c0,-1.11 -0.9,-2 -2,-2h-4v2h4v2h-2c-1.1,0 -2,0.89 -2,2v4h6v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_3_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_3_black_24dp.xml
new file mode 100644
index 0000000..f2ec440
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_3_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM17,13v-1.5c0,-0.83 -0.67,-1.5 -1.5,-1.5 0.83,0 1.5,-0.67 1.5,-1.5L17,7c0,-1.11 -0.9,-2 -2,-2h-4v2h4v2h-2v2h2v2h-4v2h4c1.1,0 2,-0.89 2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_4_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_4_black_24dp.xml
new file mode 100644
index 0000000..c35573c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_4_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM15,15h2L17,5h-2v4h-2L13,5h-2v6h4v4zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_5_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_5_black_24dp.xml
new file mode 100644
index 0000000..7b2d1de
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_5_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM17,13v-2c0,-1.11 -0.9,-2 -2,-2h-2L13,7h4L17,5h-6v6h4v2h-4v2h4c1.1,0 2,-0.89 2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_6_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_6_black_24dp.xml
new file mode 100644
index 0000000..421c5a9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_6_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM13,15h2c1.1,0 2,-0.89 2,-2v-2c0,-1.11 -0.9,-2 -2,-2h-2L13,7h4L17,5h-4c-1.1,0 -2,0.89 -2,2v6c0,1.11 0.9,2 2,2zM13,11h2v2h-2v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_7_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_7_black_24dp.xml
new file mode 100644
index 0000000..d797d29
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_7_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM13,15l4,-8L17,5h-6v2h4l-4,8h2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_8_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_8_black_24dp.xml
new file mode 100644
index 0000000..cab998c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_8_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM13,15h2c1.1,0 2,-0.89 2,-2v-1.5c0,-0.83 -0.67,-1.5 -1.5,-1.5 0.83,0 1.5,-0.67 1.5,-1.5L17,7c0,-1.11 -0.9,-2 -2,-2h-2c-1.1,0 -2,0.89 -2,2v1.5c0,0.83 0.67,1.5 1.5,1.5 -0.83,0 -1.5,0.67 -1.5,1.5L11,13c0,1.11 0.9,2 2,2zM13,7h2v2h-2L13,7zM13,11h2v2h-2v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_black_24dp.xml
new file mode 100644
index 0000000..ad53296
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM15,5h-2c-1.1,0 -2,0.89 -2,2v2c0,1.11 0.9,2 2,2h2v2h-4v2h4c1.1,0 2,-0.89 2,-2L17,7c0,-1.11 -0.9,-2 -2,-2zM15,9h-2L13,7h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_plus_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_plus_black_24dp.xml
new file mode 100644
index 0000000..31308db
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_plus_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM14,12L14,8c0,-1.11 -0.9,-2 -2,-2h-1c-1.1,0 -2,0.89 -2,2v1c0,1.11 0.9,2 2,2h1v1L9,12v2h3c1.1,0 2,-0.89 2,-2zM11,9L11,8h1v1h-1zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,9h-2L19,7h-2v2h-2v2h2v2h2v-2h2v6L7,17L7,3h14v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_b_and_w_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_b_and_w_black_24dp.xml
new file mode 100644
index 0000000..718ce0a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_b_and_w_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19l-7,-8v8L5,19l7,-8L12,5h7v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_black_24dp.xml
new file mode 100644
index 0000000..e21abb3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.96,10.29l-2.75,3.54 -1.96,-2.36L8.5,15h11l-3.54,-4.71zM3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_center_focus_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_center_focus_black_24dp.xml
new file mode 100644
index 0000000..d037f81
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_center_focus_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_drama_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_drama_black_24dp.xml
new file mode 100644
index 0000000..6185cf3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_drama_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.61,5.64 5.36,8.04 2.35,8.36 0,10.9 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4h2c0,-2.76 -1.86,-5.08 -4.4,-5.78C8.61,6.88 10.2,6 12,6c3.03,0 5.5,2.47 5.5,5.5v0.5H19c1.65,0 3,1.35 3,3s-1.35,3 -3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_frames_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_frames_black_24dp.xml
new file mode 100644
index 0000000..9c6a504
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_frames_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4h-4l-4,-4 -4,4L4,4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,20L4,20L4,6h4.52l3.52,-3.5L15.52,6L20,6v14zM18,8L6,8v10h12"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_hdr_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_hdr_black_24dp.xml
new file mode 100644
index 0000000..0350ef5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_hdr_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_none_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_none_black_24dp.xml
new file mode 100644
index 0000000..80e501d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_none_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_tilt_shift_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_tilt_shift_black_24dp.xml
new file mode 100644
index 0000000..598e887
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_tilt_shift_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,4.07L11,2.05c-2.01,0.2 -3.84,1 -5.32,2.21L7.1,5.69c1.11,-0.86 2.44,-1.44 3.9,-1.62zM18.32,4.26C16.84,3.05 15.01,2.25 13,2.05v2.02c1.46,0.18 2.79,0.76 3.9,1.62l1.42,-1.43zM19.93,11h2.02c-0.2,-2.01 -1,-3.84 -2.21,-5.32L18.31,7.1c0.86,1.11 1.44,2.44 1.62,3.9zM5.69,7.1L4.26,5.68C3.05,7.16 2.25,8.99 2.05,11h2.02c0.18,-1.46 0.76,-2.79 1.62,-3.9zM4.07,13L2.05,13c0.2,2.01 1,3.84 2.21,5.32l1.43,-1.43c-0.86,-1.1 -1.44,-2.43 -1.62,-3.89zM15,12c0,-1.66 -1.34,-3 -3,-3s-3 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_vintage_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_vintage_black_24dp.xml
new file mode 100644
index 0000000..98c733f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_vintage_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.7,12.4c-0.28,-0.16 -0.57,-0.29 -0.86,-0.4 0.29,-0.11 0.58,-0.24 0.86,-0.4 1.92,-1.11 2.99,-3.12 3,-5.19 -1.79,-1.03 -4.07,-1.11 -6,0 -0.28,0.16 -0.54,0.35 -0.78,0.54 0.05,-0.31 0.08,-0.63 0.08,-0.95 0,-2.22 -1.21,-4.15 -3,-5.19C10.21,1.85 9,3.78 9,6c0,0.32 0.03,0.64 0.08,0.95 -0.24,-0.2 -0.5,-0.39 -0.78,-0.55 -1.92,-1.11 -4.2,-1.03 -6,0 0,2.07 1.07,4.08 3,5.19 0.28,0.16 0.57,0.29 0.86,0.4 -0.29,0.11 -0.58,0.24 -0.86,0.4 -1.92,1.11 -2.99,3.12 -3,5.19 1.79,1.0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_flare_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_flare_black_24dp.xml
new file mode 100644
index 0000000..73565a5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_flare_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,11L1,11v2h6v-2zM9.17,7.76L7.05,5.64 5.64,7.05l2.12,2.12 1.41,-1.41zM13,1h-2v6h2L13,1zM18.36,7.05l-1.41,-1.41 -2.12,2.12 1.41,1.41 2.12,-2.12zM17,11v2h6v-2h-6zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zM14.83,16.24l2.12,2.12 1.41,-1.41 -2.12,-2.12 -1.41,1.41zM5.64,16.95l1.41,1.41 2.12,-2.12 -1.41,-1.41 -2.12,2.12zM11,23h2v-6h-2v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_auto_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_auto_black_24dp.xml
new file mode 100644
index 0000000..f0423d5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_auto_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,2v12h3v9l7,-12L9,11l4,-9L3,2zM19,2h-2l-3.2,9h1.9l0.7,-2h3.2l0.7,2h1.9L19,2zM16.85,7.65L18,4l1.15,3.65h-2.3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_off_black_24dp.xml
new file mode 100644
index 0000000..51b1769
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.27,3L2,4.27l5,5V13h3v9l3.58,-6.14L17.73,20 19,18.73 3.27,3zM17,10h-4l4,-8H7v2.18l8.46,8.46L17,10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_on_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_on_black_24dp.xml
new file mode 100644
index 0000000..a3c81cc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_on_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_flip_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_flip_black_24dp.xml
new file mode 100644
index 0000000..2bc2762
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_flip_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,21h2v-2h-2v2zM19,9h2L21,7h-2v2zM3,5v14c0,1.1 0.9,2 2,2h4v-2L5,19L5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM11,23h2L13,1h-2v22zM19,17h2v-2h-2v2zM15,5h2L17,3h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_gradient_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_gradient_black_24dp.xml
new file mode 100644
index 0000000..4761d5e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_gradient_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,9h2v2h-2zM9,11h2v2L9,13zM13,11h2v2h-2zM15,9h2v2h-2zM7,9h2v2L7,11zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,18L7,18v-2h2v2zM13,18h-2v-2h2v2zM17,18h-2v-2h2v2zM19,11h-2v2h2v2h-2v-2h-2v2h-2v-2h-2v2L9,15v-2L7,13v2L5,15v-2h2v-2L5,11L5,5h14v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_grain_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_grain_black_24dp.xml
new file mode 100644
index 0000000..847ad6c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_grain_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,12c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,8c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM14,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,12c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM14,8c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM10,4c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,- [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_off_black_24dp.xml
new file mode 100644
index 0000000..7cf3c95
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,4v1.45l2,2L10,4h4v4h-3.45l2,2L14,10v1.45l2,2L16,10h4v4h-3.45l2,2L20,16v1.45l2,2L22,4c0,-1.1 -0.9,-2 -2,-2L4.55,2l2,2L8,4zM16,4h4v4h-4L16,4zM1.27,1.27L0,2.55l2,2L2,20c0,1.1 0.9,2 2,2h15.46l2,2 1.27,-1.27L1.27,1.27zM10,12.55L11.45,14L10,14v-1.45zM4,6.55L5.45,8L4,8L4,6.55zM8,20L4,20v-4h4v4zM8,14L4,14v-4h3.45l0.55,0.55L8,14zM14,20h-4v-4h3.45l0.55,0.54L14,20zM16,20v-1.46L17.46,20L16,20z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_on_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_on_black_24dp.xml
new file mode 100644
index 0000000..b2ff9e5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_on_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM8,20L4,20v-4h4v4zM8,14L4,14v-4h4v4zM8,8L4,8L4,4h4v4zM14,20h-4v-4h4v4zM14,14h-4v-4h4v4zM14,8h-4L10,4h4v4zM20,20h-4v-4h4v4zM20,14h-4v-4h4v4zM20,8h-4L16,4h4v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_off_black_24dp.xml
new file mode 100644
index 0000000..61f68fa
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.5,15v-2h1.1l0.9,2L21,15l-0.9,-2.1c0.5,-0.2 0.9,-0.8 0.9,-1.4v-1c0,-0.8 -0.7,-1.5 -1.5,-1.5L16,9v4.9l1.1,1.1h0.4zM17.5,10.5h2v1h-2v-1zM13,10.5v0.4l1.5,1.5v-1.9c0,-0.8 -0.7,-1.5 -1.5,-1.5h-1.9l1.5,1.5h0.4zM9.5,9.5l-7,-7 -1.1,1L6.9,9h-0.4v2h-2L4.5,9L3,9v6h1.5v-2.5h2L6.5,15L8,15v-4.9l1.5,1.5L9.5,15h3.4l7.6,7.6 1.1,-1.1 -12.1,-12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_on_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_on_black_24dp.xml
new file mode 100644
index 0000000..fadcbd1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_on_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,11.5v-1c0,-0.8 -0.7,-1.5 -1.5,-1.5L16,9v6h1.5v-2h1.1l0.9,2L21,15l-0.9,-2.1c0.5,-0.3 0.9,-0.8 0.9,-1.4zM19.5,11.5h-2v-1h2v1zM6.5,11h-2L4.5,9L3,9v6h1.5v-2.5h2L6.5,15L8,15L8,9L6.5,9v2zM13,9L9.5,9v6L13,15c0.8,0 1.5,-0.7 1.5,-1.5v-3c0,-0.8 -0.7,-1.5 -1.5,-1.5zM13,13.5h-2v-3h2v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_strong_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_strong_black_24dp.xml
new file mode 100644
index 0000000..95dad2b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_strong_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zM5,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM5,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_weak_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_weak_black_24dp.xml
new file mode 100644
index 0000000..f4e0300
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_weak_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM17,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zM17,16c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_healing_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_healing_black_24dp.xml
new file mode 100644
index 0000000..9a63768
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_healing_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.73,12.02l3.98,-3.98c0.39,-0.39 0.39,-1.02 0,-1.41l-4.34,-4.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-3.98,3.98L8,2.29C7.8,2.1 7.55,2 7.29,2c-0.25,0 -0.51,0.1 -0.7,0.29L2.25,6.63c-0.39,0.39 -0.39,1.02 0,1.41l3.98,3.98L2.25,16c-0.39,0.39 -0.39,1.02 0,1.41l4.34,4.34c0.39,0.39 1.02,0.39 1.41,0l3.98,-3.98 3.98,3.98c0.2,0.2 0.45,0.29 0.71,0.29 0.26,0 0.51,-0.1 0.71,-0.29l4.34,-4.34c0.39,-0.39 0.39,-1.02 0,-1.41l-3.99,-3.98zM12,9c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 - [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_image_aspect_ratio_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_image_aspect_ratio_black_24dp.xml
new file mode 100644
index 0000000..1b5f835
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_image_aspect_ratio_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,10h-2v2h2v-2zM16,14h-2v2h2v-2zM8,10L6,10v2h2v-2zM12,10h-2v2h2v-2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,6h16v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_image_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_image_black_24dp.xml
new file mode 100644
index 0000000..b201859
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_image_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_iso_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_iso_black_24dp.xml
new file mode 100644
index 0000000..1c4ef5f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_iso_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM5.5,7.5h2v-2L9,5.5v2h2L11,9L9,9v2L7.5,11L7.5,9h-2L5.5,7.5zM19,19L5,19L19,5v14zM17,17v-1.5h-5L12,17h5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_landscape_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_landscape_black_24dp.xml
new file mode 100644
index 0000000..0350ef5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_landscape_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_add_black_24dp.xml
new file mode 100644
index 0000000..41e4d0c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,3L3,3v3c1.66,0 3,-1.34 3,-3zM14,3h-2c0,4.97 -4.03,9 -9,9v2c6.08,0 11,-4.93 11,-11zM10,3L8,3c0,2.76 -2.24,5 -5,5v2c3.87,0 7,-3.13 7,-7zM10,21h2c0,-4.97 4.03,-9 9,-9v-2c-6.07,0 -11,4.93 -11,11zM18,21h3v-3c-1.66,0 -3,1.34 -3,3zM14,21h2c0,-2.76 2.24,-5 5,-5v-2c-3.87,0 -7,3.13 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_remove_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_remove_black_24dp.xml
new file mode 100644
index 0000000..141dc45
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_remove_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,3L8,3c0,0.37 -0.04,0.72 -0.12,1.06l1.59,1.59C9.81,4.84 10,3.94 10,3zM3,4.27l2.84,2.84C5.03,7.67 4.06,8 3,8v2c1.61,0 3.09,-0.55 4.27,-1.46L8.7,9.97C7.14,11.24 5.16,12 3,12v2c2.71,0 5.19,-0.99 7.11,-2.62l2.5,2.5C10.99,15.81 10,18.29 10,21h2c0,-2.16 0.76,-4.14 2.03,-5.69l1.43,1.43C14.55,17.91 14,19.39 14,21h2c0,-1.06 0.33,-2.03 0.89,-2.84L19.73,21 21,19.73 4.27,3 3,4.27zM14,3h-2c0,1.5 -0.37,2.91 -1.02,4.16l1.46,1.46C13.42,6.98 14,5.06 14,3zM19.94,16.12c0.34,-0. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_lens_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_lens_black_24dp.xml
new file mode 100644
index 0000000..847b0a4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_lens_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_linked_camera_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_linked_camera_black_24dp.xml
new file mode 100644
index 0000000..201c572
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_linked_camera_black_24dp.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,14m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,3.33c2.58,0 4.67,2.09 4.67,4.67H22c0,-3.31 -2.69,-6 -6,-6v1.33M16,6c1.11,0 2,0.89 2,2h1.33c0,-1.84 -1.49,-3.33 -3.33,-3.33V6"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,9c0,-1.11 -0.89,-2 -2,-2L15,4L9,4L7.17,6L4,6c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,9h-5zM12,19c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_3_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_3_black_24dp.xml
new file mode 100644
index 0000000..418e012
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_3_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.01,3h-14c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21.01,5c0,-1.1 -0.9,-2 -2,-2zM15.01,10.5c0,0.83 -0.67,1.5 -1.5,1.5 0.83,0 1.5,0.67 1.5,1.5L15.01,15c0,1.11 -0.9,2 -2,2h-4v-2h4v-2h-2v-2h2L13.01,9h-4L9.01,7h4c1.1,0 2,0.89 2,2v1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_4_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_4_black_24dp.xml
new file mode 100644
index 0000000..4855e98
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_4_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM15,17h-2v-4L9,13L9,7h2v4h2L13,7h2v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_5_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_5_black_24dp.xml
new file mode 100644
index 0000000..45fbfab
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_5_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM15,9h-4v2h2c1.1,0 2,0.89 2,2v2c0,1.11 -0.9,2 -2,2L9,17v-2h4v-2L9,13L9,7h6v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_6_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_6_black_24dp.xml
new file mode 100644
index 0000000..6219b6d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_6_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,15h2v-2h-2v2zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM15,9h-4v2h2c1.1,0 2,0.89 2,2v2c0,1.11 -0.9,2 -2,2h-2c-1.1,0 -2,-0.89 -2,-2L9,9c0,-1.11 0.9,-2 2,-2h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_black_24dp.xml
new file mode 100644
index 0000000..dc2a0a3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,10c-3.86,0 -7,3.14 -7,7h2c0,-2.76 2.24,-5 5,-5s5,2.24 5,5h2c0,-3.86 -3.14,-7 -7,-7zM12,6C5.93,6 1,10.93 1,17h2c0,-4.96 4.04,-9 9,-9s9,4.04 9,9h2c0,-6.07 -4.93,-11 -11,-11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_one_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_one_black_24dp.xml
new file mode 100644
index 0000000..711e012
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_one_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14,17h-2L12,9h-2L10,7h4v10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_two_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_two_black_24dp.xml
new file mode 100644
index 0000000..f15dd65
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_two_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM15,11c0,1.11 -0.9,2 -2,2h-2v2h4v2L9,17v-4c0,-1.11 0.9,-2 2,-2h2L13,9L9,9L9,7h4c1.1,0 2,0.89 2,2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_loupe_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_loupe_black_24dp.xml
new file mode 100644
index 0000000..f55ffc6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_loupe_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,7h-2v4L7,11v2h4v4h2v-4h4v-2h-4L13,7zM12,2C6.49,2 2,6.49 2,12s4.49,10 10,10h8c1.1,0 2,-0.9 2,-2v-8c0,-5.51 -4.49,-10 -10,-10zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_monochrome_photos_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_monochrome_photos_black_24dp.xml
new file mode 100644
index 0000000..5cc7022
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_monochrome_photos_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,5h-3.2L15,3L9,3L7.2,5L4,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM20,19h-8v-1c-2.8,0 -5,-2.2 -5,-5s2.2,-5 5,-5L12,7h8v12zM17,13c0,-2.8 -2.2,-5 -5,-5v1.8c1.8,0 3.2,1.4 3.2,3.2s-1.4,3.2 -3.2,3.2L12,18c2.8,0 5,-2.2 5,-5zM8.8,13c0,1.8 1.4,3.2 3.2,3.2L12,9.8c-1.8,0 -3.2,1.4 -3.2,3.2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_creation_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_creation_black_24dp.xml
new file mode 100644
index 0000000..a7f7b65
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_creation_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,4l2,4h-3l-2,-4h-2l2,4h-3l-2,-4H8l2,4H7L5,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_filter_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_filter_black_24dp.xml
new file mode 100644
index 0000000..4f6f159
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_filter_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,4l2,4h-3l-2,-4h-2l2,4h-3l-2,-4L8,4l2,4L7,8L5,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4h-4zM11.42,15.68L10.37,18l-1.05,-2.32L7,14.63l2.32,-1.05 1.05,-2.32 1.05,2.32 2.32,1.05 -2.32,1.05zM15.11,12.21l-0.53,1.16 -0.53,-1.16 -1.16,-0.53 1.16,-0.53 0.53,-1.15 0.53,1.16 1.16,0.53 -1.16,0.52z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_music_note_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_music_note_black_24dp.xml
new file mode 100644
index 0000000..736c004
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_music_note_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_black_24dp.xml
new file mode 100644
index 0000000..ecd6f69
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,16.12c3.47,-0.41 6.17,-3.36 6.17,-6.95 0,-3.87 -3.13,-7 -7,-7s-7,3.13 -7,7c0,3.47 2.52,6.34 5.83,6.89V20H5v2h14v-2h-6v-3.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_people_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_people_black_24dp.xml
new file mode 100644
index 0000000..592a6ec
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_people_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22.17,9.17c0,-3.87 -3.13,-7 -7,-7s-7,3.13 -7,7c0,3.47 2.52,6.34 5.83,6.89V20H6v-3h1v-4c0,-0.55 -0.45,-1 -1,-1H3c-0.55,0 -1,0.45 -1,1v4h1v5h16v-2h-3v-3.88c3.47,-0.41 6.17,-3.36 6.17,-6.95zM4.5,11c0.83,0 1.5,-0.67 1.5,-1.5S5.33,8 4.5,8 3,8.67 3,9.5 3.67,11 4.5,11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_before_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_before_black_24dp.xml
new file mode 100644
index 0000000..e6bb3ca
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_before_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_next_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_next_black_24dp.xml
new file mode 100644
index 0000000..2483512
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_next_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_palette_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_palette_black_24dp.xml
new file mode 100644
index 0000000..f75e2fb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_palette_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_black_24dp.xml
new file mode 100644
index 0000000..cffa729
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,18V6c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2zM8.5,12.5l2.5,3.01L14.5,11l4.5,6H5l3.5,-4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_fish_eye_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_fish_eye_black_24dp.xml
new file mode 100644
index 0000000..ef6742b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_fish_eye_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_horizontal_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_horizontal_black_24dp.xml
new file mode 100644
index 0000000..f0aec1e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_horizontal_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6.54v10.91c-2.6,-0.77 -5.28,-1.16 -8,-1.16 -2.72,0 -5.4,0.39 -8,1.16V6.54c2.6,0.77 5.28,1.16 8,1.16 2.72,0.01 5.4,-0.38 8,-1.16M21.43,4c-0.1,0 -0.2,0.02 -0.31,0.06C18.18,5.16 15.09,5.7 12,5.7c-3.09,0 -6.18,-0.55 -9.12,-1.64 -0.11,-0.04 -0.22,-0.06 -0.31,-0.06 -0.34,0 -0.57,0.23 -0.57,0.63v14.75c0,0.39 0.23,0.62 0.57,0.62 0.1,0 0.2,-0.02 0.31,-0.06 2.94,-1.1 6.03,-1.64 9.12,-1.64 3.09,0 6.18,0.55 9.12,1.64 0.11,0.04 0.21,0.06 0.31,0.06 0.33,0 0.57,-0.23 0.57, [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_vertical_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_vertical_black_24dp.xml
new file mode 100644
index 0000000..2268133
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_vertical_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.94,21.12c-1.1,-2.94 -1.64,-6.03 -1.64,-9.12 0,-3.09 0.55,-6.18 1.64,-9.12 0.04,-0.11 0.06,-0.22 0.06,-0.31 0,-0.34 -0.23,-0.57 -0.63,-0.57H4.63c-0.4,0 -0.63,0.23 -0.63,0.57 0,0.1 0.02,0.2 0.06,0.31C5.16,5.82 5.71,8.91 5.71,12c0,3.09 -0.55,6.18 -1.64,9.12 -0.05,0.11 -0.07,0.22 -0.07,0.31 0,0.33 0.23,0.57 0.63,0.57h14.75c0.39,0 0.63,-0.24 0.63,-0.57 -0.01,-0.1 -0.03,-0.2 -0.07,-0.31zM6.54,20c0.77,-2.6 1.16,-5.28 1.16,-8 0,-2.72 -0.39,-5.4 -1.16,-8h10.91c-0.77, [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_wide_angle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_wide_angle_black_24dp.xml
new file mode 100644
index 0000000..900b34e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_wide_angle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,6c2.45,0 4.71,0.2 7.29,0.64 0.47,1.78 0.71,3.58 0.71,5.36 0,1.78 -0.24,3.58 -0.71,5.36 -2.58,0.44 -4.84,0.64 -7.29,0.64s-4.71,-0.2 -7.29,-0.64C4.24,15.58 4,13.78 4,12c0,-1.78 0.24,-3.58 0.71,-5.36C7.29,6.2 9.55,6 12,6m0,-2c-2.73,0 -5.22,0.24 -7.95,0.72l-0.93,0.16 -0.25,0.9C2.29,7.85 2,9.93 2,12s0.29,4.15 0.87,6.22l0.25,0.89 0.93,0.16c2.73,0.49 5.22,0.73 7.95,0.73s5.22,-0.24 7.95,-0.72l0.93,-0.16 0.25,-0.89c0.58,-2.08 0.87,-4.16 0.87,-6.23s-0.29,-4.15 -0.87,- [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_album_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_album_black_24dp.xml
new file mode 100644
index 0000000..c39882c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_album_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,2L6,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12L6,4zM6,19l3,-3.86 2.14,2.58 3,-3.86L18,19L6,19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_black_24dp.xml
new file mode 100644
index 0000000..b201859
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_camera_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_camera_black_24dp.xml
new file mode 100644
index 0000000..c872f16
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_camera_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_filter_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_filter_black_24dp.xml
new file mode 100644
index 0000000..2e23f56
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_filter_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.13,8.9l0.59,-1.3 1.3,-0.6 -1.3,-0.59 -0.59,-1.3 -0.59,1.3 -1.31,0.59 1.31,0.6zM12.39,6.53l-1.18,2.61 -2.61,1.18 2.61,1.18 1.18,2.61 1.19,-2.61 2.6,-1.18 -2.6,-1.18zM19.02,10v9L5,19L5,5h9L14,3L5.02,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-9h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_library_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_library_black_24dp.xml
new file mode 100644
index 0000000..68d5d0e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_library_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,16L22,4c0,-1.1 -0.9,-2 -2,-2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zM11,12l2.03,2.71L16,11l4,5L8,16l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6L2,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_actual_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_actual_black_24dp.xml
new file mode 100644
index 0000000..d1c3104
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_actual_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3H3C2,3 1,4 1,5v14c0,1.1 0.9,2 2,2h18c1,0 2,-1 2,-2V5c0,-1 -1,-2 -2,-2zM5,17l3.5,-4.5 2.5,3.01L14.5,11l4.5,6H5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_large_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_large_black_24dp.xml
new file mode 100644
index 0000000..9095333
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_large_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,15h2v2h-2v-2zM21,11h2v2h-2v-2zM23,19h-2v2c1,0 2,-1 2,-2zM13,3h2v2h-2L13,3zM21,7h2v2h-2L21,7zM21,3v2h2c0,-1 -1,-2 -2,-2zM1,7h2v2L1,9L1,7zM17,3h2v2h-2L17,3zM17,19h2v2h-2v-2zM3,3C2,3 1,4 1,5h2L3,3zM9,3h2v2L9,5L9,3zM5,3h2v2L5,5L5,3zM1,11v8c0,1.1 0.9,2 2,2h12L15,11L1,11zM3,19l2.5,-3.21 1.79,2.15 2.5,-3.22L13,19L3,19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_small_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_small_black_24dp.xml
new file mode 100644
index 0000000..380e4b3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_size_select_small_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M23,15h-2v2h2v-2zM23,11h-2v2h2v-2zM23,19h-2v2c1,0 2,-1 2,-2zM15,3h-2v2h2L15,3zM23,7h-2v2h2L23,7zM21,3v2h2c0,-1 -1,-2 -2,-2zM3,21h8v-6L1,15v4c0,1.1 0.9,2 2,2zM3,7L1,7v2h2L3,7zM15,19h-2v2h2v-2zM19,3h-2v2h2L19,3zM19,19h-2v2h2v-2zM3,3C2,3 1,4 1,5h2L3,3zM3,11L1,11v2h2v-2zM11,3L9,3v2h2L11,3zM7,3L5,3v2h2L7,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_picture_as_pdf_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_picture_as_pdf_black_24dp.xml
new file mode 100644
index 0000000..1308ca3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_picture_as_pdf_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM11.5,9.5c0,0.83 -0.67,1.5 -1.5,1.5L9,11v2L7.5,13L7.5,7L10,7c0.83,0 1.5,0.67 1.5,1.5v1zM16.5,11.5c0,0.83 -0.67,1.5 -1.5,1.5h-2.5L12.5,7L15,7c0.83,0 1.5,0.67 1.5,1.5v3zM20.5,8.5L19,8.5v1h1.5L20.5,11L19,11v2h-1.5L17.5,7h3v1.5zM9,9.5h1v-1L9,8.5v1zM4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM14,11.5h1v-3h-1v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_portrait_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_portrait_black_24dp.xml
new file mode 100644
index 0000000..ae8878b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_portrait_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12.25c1.24,0 2.25,-1.01 2.25,-2.25S13.24,7.75 12,7.75 9.75,8.76 9.75,10s1.01,2.25 2.25,2.25zM16.5,16.25c0,-1.5 -3,-2.25 -4.5,-2.25s-4.5,0.75 -4.5,2.25L7.5,17h9v-0.75zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_remove_red_eye_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_remove_red_eye_black_24dp.xml
new file mode 100644
index 0000000..e02f1d1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_remove_red_eye_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_90_degrees_ccw_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_90_degrees_ccw_black_24dp.xml
new file mode 100644
index 0000000..9a3f094
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_90_degrees_ccw_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.34,6.41L0.86,12.9l6.49,6.48 6.49,-6.48 -6.5,-6.49zM3.69,12.9l3.66,-3.66L11,12.9l-3.66,3.66 -3.65,-3.66zM19.36,6.64C17.61,4.88 15.3,4 13,4L13,0.76L8.76,5 13,9.24L13,6c1.79,0 3.58,0.68 4.95,2.05 2.73,2.73 2.73,7.17 0,9.9C16.58,19.32 14.79,20 13,20c-0.97,0 -1.94,-0.21 -2.84,-0.61l-1.49,1.49C10.02,21.62 11.51,22 13,22c2.3,0 4.61,-0.88 6.36,-2.64 3.52,-3.51 3.52,-9.21 0,-12.72z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_left_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_left_black_24dp.xml
new file mode 100644
index 0000000..2fd476d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_left_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.11,8.53L5.7,7.11C4.8,8.27 4.24,9.61 4.07,11h2.02c0.14,-0.87 0.49,-1.72 1.02,-2.47zM6.09,13L4.07,13c0.17,1.39 0.72,2.73 1.62,3.89l1.41,-1.42c-0.52,-0.75 -0.87,-1.59 -1.01,-2.47zM7.1,18.32c1.16,0.9 2.51,1.44 3.9,1.61L11,17.9c-0.87,-0.15 -1.71,-0.49 -2.46,-1.03L7.1,18.32zM13,4.07L13,1L8.45,5.55 13,10L13,6.09c2.84,0.48 5,2.94 5,5.91s-2.16,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93s-3.05,-7.44 -7,-7.93z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_right_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_right_black_24dp.xml
new file mode 100644
index 0000000..a986574
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_right_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.55,5.55L11,1v3.07C7.06,4.56 4,7.92 4,12s3.05,7.44 7,7.93v-2.02c-2.84,-0.48 -5,-2.94 -5,-5.91s2.16,-5.43 5,-5.91L11,10l4.55,-4.45zM19.93,11c-0.17,-1.39 -0.72,-2.73 -1.62,-3.89l-1.42,1.42c0.54,0.75 0.88,1.6 1.02,2.47h2.02zM13,17.9v2.02c1.39,-0.17 2.74,-0.71 3.9,-1.61l-1.44,-1.44c-0.75,0.54 -1.59,0.89 -2.46,1.03zM16.89,15.48l1.42,1.41c0.9,-1.16 1.45,-2.5 1.62,-3.89h-2.02c-0.14,0.87 -0.48,1.72 -1.02,2.48z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_slideshow_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_slideshow_black_24dp.xml
new file mode 100644
index 0000000..de38db8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_slideshow_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,8v8l5,-4 -5,-4zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_straighten_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_straighten_black_24dp.xml
new file mode 100644
index 0000000..dfcddfb
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_straighten_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,8h2v4h2L7,8h2v4h2L11,8h2v4h2L15,8h2v4h2L19,8h2v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_style_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_style_black_24dp.xml
new file mode 100644
index 0000000..ab2c20a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_style_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2.53,19.65l1.34,0.56v-9.03l-2.43,5.86c-0.41,1.02 0.08,2.19 1.09,2.61zM22.03,15.95L17.07,3.98c-0.31,-0.75 -1.04,-1.21 -1.81,-1.23 -0.26,0 -0.53,0.04 -0.79,0.15L7.1,5.95c-0.75,0.31 -1.21,1.03 -1.23,1.8 -0.01,0.27 0.04,0.54 0.15,0.8l4.96,11.97c0.31,0.76 1.05,1.22 1.83,1.23 0.26,0 0.52,-0.05 0.77,-0.15l7.36,-3.05c1.02,-0.42 1.51,-1.59 1.09,-2.6zM7.88,8.75c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM5.88,19.75c0,1.1 0.9,2 2,2h1.45l-3.45,-8.34v6.34z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_camera_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_camera_black_24dp.xml
new file mode 100644
index 0000000..d49ce20
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_camera_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4h-3.17L15,2L9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM15,15.5L15,13L9,13v2.5L5.5,12 9,8.5L9,11h6L15,8.5l3.5,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_video_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_video_black_24dp.xml
new file mode 100644
index 0000000..e0d3730
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_video_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,9.5L18,6c0,-0.55 -0.45,-1 -1,-1L3,5c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h14c0.55,0 1,-0.45 1,-1v-3.5l4,4v-13l-4,4zM13,15.5L13,13L7,13v2.5L3.5,12 7,8.5L7,11h6L13,8.5l3.5,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_tag_faces_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_tag_faces_black_24dp.xml
new file mode 100644
index 0000000..43d5552
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_tag_faces_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_texture_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_texture_black_24dp.xml
new file mode 100644
index 0000000..4adbbba
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_texture_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.51,3.08L3.08,19.51c0.09,0.34 0.27,0.65 0.51,0.9 0.25,0.24 0.56,0.42 0.9,0.51L20.93,4.49c-0.19,-0.69 -0.73,-1.23 -1.42,-1.41zM11.88,3L3,11.88v2.83L14.71,3h-2.83zM5,3c-1.1,0 -2,0.9 -2,2v2l4,-4L5,3zM19,21c0.55,0 1.05,-0.22 1.41,-0.59 0.37,-0.36 0.59,-0.86 0.59,-1.41v-2l-4,4h2zM9.29,21h2.83L21,12.12L21,9.29L9.29,21z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_timelapse_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_timelapse_black_24dp.xml
new file mode 100644
index 0000000..6e1ad69
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_timelapse_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.24,7.76C15.07,6.59 13.54,6 12,6v6l-4.24,4.24c2.34,2.34 6.14,2.34 8.49,0 2.34,-2.34 2.34,-6.14 -0.01,-8.48zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_10_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_10_black_24dp.xml
new file mode 100644
index 0000000..1e1384d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_10_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,7.72L0,9.4l3,-1L3,18h2L5,6h-0.25L0,7.72zM23.78,14.37c-0.14,-0.28 -0.35,-0.53 -0.63,-0.74 -0.28,-0.21 -0.61,-0.39 -1.01,-0.53s-0.85,-0.27 -1.35,-0.38c-0.35,-0.07 -0.64,-0.15 -0.87,-0.23 -0.23,-0.08 -0.41,-0.16 -0.55,-0.25 -0.14,-0.09 -0.23,-0.19 -0.28,-0.3 -0.05,-0.11 -0.08,-0.24 -0.08,-0.39 0,-0.14 0.03,-0.28 0.09,-0.41 0.06,-0.13 0.15,-0.25 0.27,-0.34 0.12,-0.1 0.27,-0.18 0.45,-0.24s0.4,-0.09 0.64,-0.09c0.25,0 0.47,0.04 0.66,0.11 0.19,0.07 0.35,0.17 0.48,0.2 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_3_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_3_black_24dp.xml
new file mode 100644
index 0000000..d72bd6c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_3_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.61,12.97c-0.16,-0.24 -0.36,-0.46 -0.62,-0.65 -0.25,-0.19 -0.56,-0.35 -0.93,-0.48 0.3,-0.14 0.57,-0.3 0.8,-0.5 0.23,-0.2 0.42,-0.41 0.57,-0.64 0.15,-0.23 0.27,-0.46 0.34,-0.71 0.08,-0.24 0.11,-0.49 0.11,-0.73 0,-0.55 -0.09,-1.04 -0.28,-1.46 -0.18,-0.42 -0.44,-0.77 -0.78,-1.06 -0.33,-0.28 -0.73,-0.5 -1.2,-0.64 -0.45,-0.13 -0.97,-0.2 -1.53,-0.2 -0.55,0 -1.06,0.08 -1.52,0.24 -0.47,0.17 -0.87,0.4 -1.2,0.69 -0.33,0.29 -0.6,0.63 -0.78,1.03 -0.2,0.39 -0.29,0.83 -0.2 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_black_24dp.xml
new file mode 100644
index 0000000..f41be80
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,1L9,1v2h6L15,1zM11,14h2L13,8h-2v6zM19.03,7.39l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_off_black_24dp.xml
new file mode 100644
index 0000000..d9cfc94
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.04,4.55l-1.42,1.42C16.07,4.74 14.12,4 12,4c-1.83,0 -3.53,0.55 -4.95,1.48l1.46,1.46C9.53,6.35 10.73,6 12,6c3.87,0 7,3.13 7,7 0,1.27 -0.35,2.47 -0.94,3.49l1.45,1.45C20.45,16.53 21,14.83 21,13c0,-2.12 -0.74,-4.07 -1.97,-5.61l1.42,-1.42 -1.41,-1.42zM15,1L9,1v2h6L15,1zM11,9.44l2,2L13,8h-2v1.44zM3.02,4L1.75,5.27 4.5,8.03C3.55,9.45 3,11.16 3,13c0,4.97 4.02,9 9,9 1.84,0 3.55,-0.55 4.98,-1.5l2.5,2.5 1.27,-1.27 -7.71,-7.71L3.02,4zM12,20c-3.87,0 -7,-3.13 -7,-7 0,-1.28 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_tonality_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_tonality_black_24dp.xml
new file mode 100644
index 0000000..870da47
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_tonality_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.94,-0.49 -7,-3.85 -7,-7.93s3.05,-7.44 7,-7.93v15.86zM13,4.07c1.03,0.13 2,0.45 2.87,0.93L13,5v-0.93zM13,7h5.24c0.25,0.31 0.48,0.65 0.68,1L13,8L13,7zM13,10h6.74c0.08,0.33 0.15,0.66 0.19,1L13,11v-1zM13,19.93L13,19h2.87c-0.87,0.48 -1.84,0.8 -2.87,0.93zM18.24,17L13,17v-1h5.92c-0.2,0.35 -0.43,0.69 -0.68,1zM19.74,14L13,14v-1h6.93c-0.04,0.34 -0.11,0.67 -0.19,1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_transform_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_transform_black_24dp.xml
new file mode 100644
index 0000000..782817a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_transform_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,18v-2H8V4h2L7,1 4,4h2v2H2v2h4v8c0,1.1 0.9,2 2,2h8v2h-2l3,3 3,-3h-2v-2h4zM10,8h6v6h2V8c0,-1.1 -0.9,-2 -2,-2h-6v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_tune_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_tune_black_24dp.xml
new file mode 100644
index 0000000..a15149d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_tune_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,17v2h6v-2L3,17zM3,5v2h10L13,5L3,5zM13,21v-2h8v-2h-8v-2h-2v6h2zM7,9v2L3,11v2h4v2h2L9,9L7,9zM21,13v-2L11,11v2h10zM15,9h2L17,7h4L21,5h-4L17,3h-2v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_view_comfy_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_view_comfy_black_24dp.xml
new file mode 100644
index 0000000..b9d4c74
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_view_comfy_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,9h4L7,5L3,5v4zM3,14h4v-4L3,10v4zM8,14h4v-4L8,10v4zM13,14h4v-4h-4v4zM8,9h4L12,5L8,5v4zM13,5v4h4L17,5h-4zM18,14h4v-4h-4v4zM3,19h4v-4L3,15v4zM8,19h4v-4L8,15v4zM13,19h4v-4h-4v4zM18,19h4v-4h-4v4zM18,5v4h4L22,5h-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_view_compact_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_view_compact_black_24dp.xml
new file mode 100644
index 0000000..f41bd67
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_view_compact_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,19h6v-7L3,12v7zM10,19h12v-7L10,12v7zM3,5v6h19L22,5L3,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_vignette_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_vignette_black_24dp.xml
new file mode 100644
index 0000000..0dec207
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_vignette_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM12,18c-4.42,0 -8,-2.69 -8,-6s3.58,-6 8,-6 8,2.69 8,6 -3.58,6 -8,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_auto_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_auto_black_24dp.xml
new file mode 100644
index 0000000..ba2d39b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_auto_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.85,12.65h2.3L8,9l-1.15,3.65zM22,7l-1.2,6.29L19.3,7h-1.6l-1.49,6.29L15,7h-0.76C12.77,5.17 10.53,4 8,4c-4.42,0 -8,3.58 -8,8s3.58,8 8,8c3.13,0 5.84,-1.81 7.15,-4.43l0.1,0.43L17,16l1.5,-6.1L20,16h1.75l2.05,-9L22,7zM10.3,16l-0.7,-2L6.4,14l-0.7,2L3.8,16L7,7h2l3.2,9h-1.9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_cloudy_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_cloudy_black_24dp.xml
new file mode 100644
index 0000000..b1c638a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_cloudy_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.36,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.64,-4.96z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_incandescent_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_incandescent_black_24dp.xml
new file mode 100644
index 0000000..f15dac4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_incandescent_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.55,18.54l1.41,1.41 1.79,-1.8 -1.41,-1.41 -1.79,1.8zM11,22.45h2L13,19.5h-2v2.95zM4,10.5L1,10.5v2h3v-2zM15,6.31L15,1.5L9,1.5v4.81C7.21,7.35 6,9.28 6,11.5c0,3.31 2.69,6 6,6s6,-2.69 6,-6c0,-2.22 -1.21,-4.15 -3,-5.19zM20,10.5v2h3v-2h-3zM17.24,18.16l1.79,1.8 1.41,-1.41 -1.8,-1.79 -1.4,1.4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_iridescent_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_iridescent_black_24dp.xml
new file mode 100644
index 0000000..7a218b3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_iridescent_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,14.5h14v-6L5,8.5v6zM11,0.55L11,3.5h2L13,0.55h-2zM19.04,3.05l-1.79,1.79 1.41,1.41 1.8,-1.79 -1.42,-1.41zM13,22.45L13,19.5h-2v2.95h2zM20.45,18.54l-1.8,-1.79 -1.41,1.41 1.79,1.8 1.42,-1.42zM3.55,4.46l1.79,1.79 1.41,-1.41 -1.79,-1.79 -1.41,1.41zM4.96,19.95l1.79,-1.8 -1.41,-1.41 -1.79,1.79 1.41,1.42z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_sunny_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_sunny_black_24dp.xml
new file mode 100644
index 0000000..a56fb50
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_sunny_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.76,4.84l-1.8,-1.79 -1.41,1.41 1.79,1.79 1.42,-1.41zM4,10.5L1,10.5v2h3v-2zM13,0.55h-2L11,3.5h2L13,0.55zM20.45,4.46l-1.41,-1.41 -1.79,1.79 1.41,1.41 1.79,-1.79zM17.24,18.16l1.79,1.8 1.41,-1.41 -1.8,-1.79 -1.4,1.4zM20,10.5v2h3v-2h-3zM12,5.5c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zM11,22.45h2L13,19.5h-2v2.95zM3.55,18.54l1.41,1.41 1.79,-1.8 -1.41,-1.41 -1.79,1.8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_add_location_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_add_location_black_24dp.xml
new file mode 100644
index 0000000..c046460
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_add_location_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C8.14,2 5,5.14 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.86 -3.14,-7 -7,-7zM16,10h-3v3h-2v-3L8,10L8,8h3L11,5h2v3h3v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_beenhere_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_beenhere_black_24dp.xml
new file mode 100644
index 0000000..5f1c051
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_beenhere_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,1L5,1c-1.1,0 -1.99,0.9 -1.99,2L3,15.93c0,0.69 0.35,1.3 0.88,1.66L12,23l8.11,-5.41c0.53,-0.36 0.88,-0.97 0.88,-1.66L21,3c0,-1.1 -0.9,-2 -2,-2zM10,16l-5,-5 1.41,-1.41L10,13.17l7.59,-7.59L19,7l-9,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bike_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bike_black_24dp.xml
new file mode 100644
index 0000000..ded5e33
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bike_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM5,12c-2.8,0 -5,2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,-5 -5,-5zM5,20.5c-1.9,0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,1.6 3.5,3.5 -1.6,3.5 -3.5,3.5zM10.8,10.5l2.4,-2.4 0.8,0.8c1.3,1.3 3,2.1 5.1,2.1L19.1,9c-1.5,0 -2.7,-0.6 -3.6,-1.5l-1.9,-1.9c-0.5,-0.4 -1,-0.6 -1.6,-0.6s-1.1,0.2 -1.4,0.6L7.8,8.4c-0.4,0.4 -0.6,0.9 -0.6,1.4 0,0.6 0.2,1.1 0.6,1.4L11,14v5h2v-6.2l-2.2,-2.3zM19,12c-2.8,0 -5,2.2 -5,5s2.2,5 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_black_24dp.xml
new file mode 100644
index 0000000..739dd20
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_boat_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_boat_black_24dp.xml
new file mode 100644
index 0000000..f726d87
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_boat_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,21c-1.39,0 -2.78,-0.47 -4,-1.32 -2.44,1.71 -5.56,1.71 -8,0C6.78,20.53 5.39,21 4,21H2v2h2c1.38,0 2.74,-0.35 4,-0.99 2.52,1.29 5.48,1.29 8,0 1.26,0.65 2.62,0.99 4,0.99h2v-2h-2zM3.95,19H4c1.6,0 3.02,-0.88 4,-2 0.98,1.12 2.4,2 4,2s3.02,-0.88 4,-2c0.98,1.12 2.4,2 4,2h0.05l1.89,-6.68c0.08,-0.26 0.06,-0.54 -0.06,-0.78s-0.34,-0.42 -0.6,-0.5L20,10.62V6c0,-1.1 -0.9,-2 -2,-2h-3V1H9v3H6c-1.1,0 -2,0.9 -2,2v4.62l-1.29,0.42c-0.26,0.08 -0.48,0.26 -0.6,0.5s-0.15,0.52 -0.06,0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bus_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bus_black_24dp.xml
new file mode 100644
index 0000000..e16e259
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bus_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,16c0,0.88 0.39,1.67 1,2.22L5,20c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h8v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1.78c0.61,-0.55 1,-1.34 1,-2.22L20,6c0,-3.5 -3.58,-4 -8,-4s-8,0.5 -8,4v10zM7.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,14 7.5,14s1.5,0.67 1.5,1.5S8.33,17 7.5,17zM16.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM18,11L6,11L6,6h12v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_car_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_car_black_24dp.xml
new file mode 100644
index 0000000..6d6337c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_car_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zM17.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11L5,11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_railway_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_railway_black_24dp.xml
new file mode 100644
index 0000000..ae6f37b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_railway_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,15.5C4,17.43 5.57,19 7.5,19L6,20.5v0.5h12v-0.5L16.5,19c1.93,0 3.5,-1.57 3.5,-3.5L20,5c0,-3.5 -3.58,-4 -8,-4s-8,0.5 -8,4v10.5zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM18,10L6,10L6,5h12v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_run_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_run_black_24dp.xml
new file mode 100644
index 0000000..d18f6a1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_run_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13.49,5.48c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM9.89,19.38l1,-4.4 2.1,2v6h2v-7.5l-2.1,-2 0.6,-3c1.3,1.5 3.3,2.5 5.5,2.5v-2c-1.9,0 -3.5,-1 -4.3,-2.4l-1,-1.6c-0.4,-0.6 -1,-1 -1.7,-1 -0.3,0 -0.5,0.1 -0.8,0.1l-5.2,2.2v4.7h2v-3.4l1.8,-0.7 -1.6,8.1 -4.9,-1 -0.4,2 7,1.4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_subway_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_subway_black_24dp.xml
new file mode 100644
index 0000000..5a82196
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_subway_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2c-4.42,0 -8,0.5 -8,4v9.5C4,17.43 5.57,19 7.5,19L6,20.5v0.5h12v-0.5L16.5,19c1.93,0 3.5,-1.57 3.5,-3.5L20,6c0,-3.5 -3.58,-4 -8,-4zM7.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,14 7.5,14s1.5,0.67 1.5,1.5S8.33,17 7.5,17zM11,11L6,11L6,6h5v5zM16.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM18,11h-5L13,6h5v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_transit_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_transit_black_24dp.xml
new file mode 100644
index 0000000..5a82196
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_transit_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2c-4.42,0 -8,0.5 -8,4v9.5C4,17.43 5.57,19 7.5,19L6,20.5v0.5h12v-0.5L16.5,19c1.93,0 3.5,-1.57 3.5,-3.5L20,6c0,-3.5 -3.58,-4 -8,-4zM7.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,14 7.5,14s1.5,0.67 1.5,1.5S8.33,17 7.5,17zM11,11L6,11L6,6h5v5zM16.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM18,11h-5L13,6h5v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_walk_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_walk_black_24dp.xml
new file mode 100644
index 0000000..407d67b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_walk_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM9.8,8.9L7,23h2.1l1.8,-8 2.1,2v6h2v-7.5l-2.1,-2 0.6,-3C14.8,12 16.8,13 19,13v-2c-1.9,0 -3.5,-1 -4.3,-2.4l-1,-1.6c-0.4,-0.6 -1,-1 -1.7,-1 -0.3,0 -0.5,0.1 -0.8,0.1L6,8.3V13h2V9.6l1.8,-0.7"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_edit_location_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_edit_location_black_24dp.xml
new file mode 100644
index 0000000..f6c9d9b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_edit_location_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C8.14,2 5,5.14 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.86 -3.14,-7 -7,-7zM10.44,12L9,12v-1.44l3.35,-3.34 1.43,1.43L10.44,12zM14.89,7.55l-0.7,0.7 -1.44,-1.44 0.7,-0.7c0.15,-0.15 0.39,-0.15 0.54,0l0.9,0.9c0.15,0.15 0.15,0.39 0,0.54z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_flight_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_flight_black_24dp.xml
new file mode 100644
index 0000000..55a8d22
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_flight_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10.18,9"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,16v-2l-8,-5V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5V9l-8,5v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-5.5l8,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_hotel_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_hotel_black_24dp.xml
new file mode 100644
index 0000000..e89ead0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_hotel_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,13c1.66,0 3,-1.34 3,-3S8.66,7 7,7s-3,1.34 -3,3 1.34,3 3,3zM19,7h-8v7L3,14L3,5L1,5v15h2v-3h18v3h2v-9c0,-2.21 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_black_24dp.xml
new file mode 100644
index 0000000..84f5bb5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_clear_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_clear_black_24dp.xml
new file mode 100644
index 0000000..ef27782
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_clear_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.81,14.99l1.19,-0.92 -1.43,-1.43 -1.19,0.92 1.43,1.43zM19.36,10.27L21,9l-9,-7 -2.91,2.27 7.87,7.88 2.4,-1.88zM3.27,1L2,2.27l4.22,4.22L3,9l1.63,1.27L12,16l2.1,-1.63 1.43,1.43L12,18.54l-7.37,-5.73L3,14.07l9,7 4.95,-3.85L20.73,21 22,19.73 3.27,1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_activity_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_activity_black_24dp.xml
new file mode 100644
index 0000000..49d69e0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_activity_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,12c0,-1.1 0.9,-2 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2v4c1.1,0 1.99,0.9 1.99,2s-0.89,2 -2,2v4c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-4c-1.1,0 -2,-0.9 -2,-2zM15.58,16.8L12,14.5l-3.58,2.3 1.08,-4.12 -3.29,-2.69 4.24,-0.25L12,5.8l1.54,3.95 4.24,0.25 -3.29,2.69 1.09,4.11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_airport_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_airport_black_24dp.xml
new file mode 100644
index 0000000..eee57b4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_airport_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,16v-2l-8,-5V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5V9l-8,5v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-5.5l8,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_atm_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_atm_black_24dp.xml
new file mode 100644
index 0000000..1b86138
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_atm_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,17h2v-1h1c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-1h4L15,8h-2L13,7h-2v1h-1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1h3v1L9,14v2h2v1zM20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18L4,6h16v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_bar_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_bar_black_24dp.xml
new file mode 100644
index 0000000..c1192b4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_bar_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,5V3H3v2l8,9v5H6v2h12v-2h-5v-5l8,-9zM7.43,7L5.66,5h12.69l-1.78,2H7.43z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_cafe_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_cafe_black_24dp.xml
new file mode 100644
index 0000000..29da472
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_cafe_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3L4,3v10c0,2.21 1.79,4 4,4h6c2.21,0 4,-1.79 4,-4v-3h2c1.11,0 2,-0.89 2,-2L22,5c0,-1.11 -0.89,-2 -2,-2zM20,8h-2L18,5h2v3zM2,21h18v-2L2,19v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_car_wash_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_car_wash_black_24dp.xml
new file mode 100644
index 0000000..717505f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_car_wash_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5c0.83,0 1.5,-0.67 1.5,-1.5 0,-1 -1.5,-2.7 -1.5,-2.7s-1.5,1.7 -1.5,2.7c0,0.83 0.67,1.5 1.5,1.5zM12,5c0.83,0 1.5,-0.67 1.5,-1.5 0,-1 -1.5,-2.7 -1.5,-2.7s-1.5,1.7 -1.5,2.7c0,0.83 0.67,1.5 1.5,1.5zM7,5c0.83,0 1.5,-0.67 1.5,-1.5C8.5,2.5 7,0.8 7,0.8S5.5,2.5 5.5,3.5C5.5,4.33 6.17,5 7,5zM18.92,8.01C18.72,7.42 18.16,7 17.5,7h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,14v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_convenience_store_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_convenience_store_black_24dp.xml
new file mode 100644
index 0000000..7d6b977
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_convenience_store_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,7L19,4L5,4v3L2,7v13h8v-4h4v4h8L22,7h-3zM11,10L9,10v1h2v1L8,12L8,9h2L10,8L8,8L8,7h3v3zM16,12h-1v-2h-2L13,7h1v2h1L15,7h1v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_dining_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_dining_black_24dp.xml
new file mode 100644
index 0000000..7375ec3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_dining_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8.1,13.34l2.83,-2.83L3.91,3.5c-1.56,1.56 -1.56,4.09 0,5.66l4.19,4.18zM14.88,11.53c1.53,0.71 3.68,0.21 5.27,-1.38 1.91,-1.91 2.28,-4.65 0.81,-6.12 -1.46,-1.46 -4.2,-1.1 -6.12,0.81 -1.59,1.59 -2.09,3.74 -1.38,5.27L3.7,19.87l1.41,1.41L12,14.41l6.88,6.88 1.41,-1.41L13.41,13l1.47,-1.47z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_drink_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_drink_black_24dp.xml
new file mode 100644
index 0000000..d7dab58
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_drink_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,2l2.01,18.23C5.13,21.23 5.97,22 7,22h10c1.03,0 1.87,-0.77 1.99,-1.77L21,2L3,2zM12,19c-1.66,0 -3,-1.34 -3,-3 0,-2 3,-5.4 3,-5.4s3,3.4 3,5.4c0,1.66 -1.34,3 -3,3zM18.33,8L5.67,8l-0.44,-4h13.53l-0.43,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_florist_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_florist_black_24dp.xml
new file mode 100644
index 0000000..a668098
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_florist_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,22c4.97,0 9,-4.03 9,-9 -4.97,0 -9,4.03 -9,9zM5.6,10.25c0,1.38 1.12,2.5 2.5,2.5 0.53,0 1.01,-0.16 1.42,-0.44l-0.02,0.19c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5l-0.02,-0.19c0.4,0.28 0.89,0.44 1.42,0.44 1.38,0 2.5,-1.12 2.5,-2.5 0,-1 -0.59,-1.85 -1.43,-2.25 0.84,-0.4 1.43,-1.25 1.43,-2.25 0,-1.38 -1.12,-2.5 -2.5,-2.5 -0.53,0 -1.01,0.16 -1.42,0.44l0.02,-0.19C14.5,2.12 13.38,1 12,1S9.5,2.12 9.5,3.5l0.02,0.19c-0.4,-0.28 -0.89,-0.44 -1.42,-0.44 -1.38,0 -2.5,1.12 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_gas_station_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_gas_station_black_24dp.xml
new file mode 100644
index 0000000..8a152dc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_gas_station_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.77,7.23l0.01,-0.01 -3.72,-3.72L15,4.56l2.11,2.11c-0.94,0.36 -1.61,1.26 -1.61,2.33 0,1.38 1.12,2.5 2.5,2.5 0.36,0 0.69,-0.08 1,-0.21v7.21c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L17,14c0,-1.1 -0.9,-2 -2,-2h-1L14,5c0,-1.1 -0.9,-2 -2,-2L6,3c-1.1,0 -2,0.9 -2,2v16h10v-7.5h1.5v5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5L20.5,9c0,-0.69 -0.28,-1.32 -0.73,-1.77zM12,10L6,10L6,5h6v5zM18,10c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_grocery_store_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_grocery_store_black_24dp.xml
new file mode 100644
index 0000000..11887e3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_grocery_store_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2L7.42,15c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1L5.21,4l-0.94,-2L1,2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hospital_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hospital_black_24dp.xml
new file mode 100644
index 0000000..e0f6e14
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hospital_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM18,14h-4v4h-4v-4L6,14v-4h4L10,6h4v4h4v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hotel_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hotel_black_24dp.xml
new file mode 100644
index 0000000..e89ead0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hotel_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,13c1.66,0 3,-1.34 3,-3S8.66,7 7,7s-3,1.34 -3,3 1.34,3 3,3zM19,7h-8v7L3,14L3,5L1,5v15h2v-3h18v3h2v-9c0,-2.21 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_laundry_service_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_laundry_service_black_24dp.xml
new file mode 100644
index 0000000..242d1fa
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_laundry_service_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.17,16.83c1.56,1.56 4.1,1.56 5.66,0 1.56,-1.56 1.56,-4.1 0,-5.66l-5.66,5.66zM18,2.01L6,2c-1.11,0 -2,0.89 -2,2v16c0,1.11 0.89,2 2,2h12c1.11,0 2,-0.89 2,-2L20,4c0,-1.11 -0.89,-1.99 -2,-1.99zM10,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM7,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,20c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_library_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_library_black_24dp.xml
new file mode 100644
index 0000000..3fd5a4f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_library_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,11.55C9.64,9.35 6.48,8 3,8v11c3.48,0 6.64,1.35 9,3.55 2.36,-2.19 5.52,-3.55 9,-3.55V8c-3.48,0 -6.64,1.35 -9,3.55zM12,8c1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3 1.34,3 3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_mall_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_mall_black_24dp.xml
new file mode 100644
index 0000000..206e1a0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_mall_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,6h-2c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6L5,6c-1.1,0 -1.99,0.9 -1.99,2L3,20c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,8c0,-1.1 -0.9,-2 -2,-2zM12,3c1.66,0 3,1.34 3,3L9,6c0,-1.66 1.34,-3 3,-3zM12,13c-2.76,0 -5,-2.24 -5,-5h2c0,1.66 1.34,3 3,3s3,-1.34 3,-3h2c0,2.76 -2.24,5 -5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_movies_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_movies_black_24dp.xml
new file mode 100644
index 0000000..56665e6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_movies_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,3v2h-2L16,3L8,3v2L6,5L6,3L4,3v18h2v-2h2v2h8v-2h2v2h2L20,3h-2zM8,17L6,17v-2h2v2zM8,13L6,13v-2h2v2zM8,9L6,9L6,7h2v2zM18,17h-2v-2h2v2zM18,13h-2v-2h2v2zM18,9h-2L16,7h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_offer_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_offer_black_24dp.xml
new file mode 100644
index 0000000..8b19fe4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_offer_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21.41,11.58l-9,-9C12.05,2.22 11.55,2 11,2H4c-1.1,0 -2,0.9 -2,2v7c0,0.55 0.22,1.05 0.59,1.42l9,9c0.36,0.36 0.86,0.58 1.41,0.58 0.55,0 1.05,-0.22 1.41,-0.59l7,-7c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-0.55 -0.23,-1.06 -0.59,-1.42zM5.5,7C4.67,7 4,6.33 4,5.5S4.67,4 5.5,4 7,4.67 7,5.5 6.33,7 5.5,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_parking_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_parking_black_24dp.xml
new file mode 100644
index 0000000..a5808ce
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_parking_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,3L6,3v18h4v-6h3c3.31,0 6,-2.69 6,-6s-2.69,-6 -6,-6zM13.2,11L10,11L10,7h3.2c1.1,0 2,0.9 2,2s-0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pharmacy_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pharmacy_black_24dp.xml
new file mode 100644
index 0000000..c313f47
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pharmacy_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,5h-2.64l1.14,-3.14L17.15,1l-1.46,4L3,5v2l2,6 -2,6v2h18v-2l-2,-6 2,-6L21,5zM16,14h-3v3h-2v-3L8,14v-2h3L11,9h2v3h3v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_phone_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_phone_black_24dp.xml
new file mode 100644
index 0000000..ebf9de6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_phone_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pizza_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pizza_black_24dp.xml
new file mode 100644
index 0000000..7a5b03b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pizza_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C8.43,2 5.23,3.54 3.01,6L12,22l8.99,-16C18.78,3.55 15.57,2 12,2zM7,7c0,-1.1 0.9,-2 2,-2s2,0.9 2,2 -0.9,2 -2,2 -2,-0.9 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_play_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_play_black_24dp.xml
new file mode 100644
index 0000000..49d69e0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_play_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,12c0,-1.1 0.9,-2 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2v4c1.1,0 1.99,0.9 1.99,2s-0.89,2 -2,2v4c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-4c-1.1,0 -2,-0.9 -2,-2zM15.58,16.8L12,14.5l-3.58,2.3 1.08,-4.12 -3.29,-2.69 4.24,-0.25L12,5.8l1.54,3.95 4.24,0.25 -3.29,2.69 1.09,4.11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_post_office_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_post_office_black_24dp.xml
new file mode 100644
index 0000000..ce97ab8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_post_office_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_printshop_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_printshop_black_24dp.xml
new file mode 100644
index 0000000..7acdb18
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_printshop_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,8L5,8c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3 -3,-3zM16,19L8,19v-5h8v5zM19,12c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,3L6,3v4h12L18,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_see_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_see_black_24dp.xml
new file mode 100644
index 0000000..c872f16
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_see_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_shipping_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_shipping_black_24dp.xml
new file mode 100644
index 0000000..011c1e2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_shipping_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,8h-3L17,4L3,4c-1.1,0 -2,0.9 -2,2v11h2c0,1.66 1.34,3 3,3s3,-1.34 3,-3h6c0,1.66 1.34,3 3,3s3,-1.34 3,-3h2v-5l-3,-4zM6,18.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19.5,9.5l1.96,2.5L17,12L17,9.5h2.5zM18,18.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_taxi_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_taxi_black_24dp.xml
new file mode 100644
index 0000000..21b763e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_taxi_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5L15,5L15,3L9,3v2L6.5,5c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zM17.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11L5,11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_map_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_map_black_24dp.xml
new file mode 100644
index 0000000..b9bacc8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_map_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_my_location_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_my_location_black_24dp.xml
new file mode 100644
index 0000000..07d6e46
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_my_location_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_navigation_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_navigation_black_24dp.xml
new file mode 100644
index 0000000..6df5ae6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_navigation_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2L4.5,20.29l0.71,0.71L12,18l6.79,3 0.71,-0.71z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_near_me_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_near_me_black_24dp.xml
new file mode 100644
index 0000000..fbd8e47
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_near_me_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,10.53v0.98l6.84,2.65L12.48,21h0.98L21,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_person_pin_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_person_pin_black_24dp.xml
new file mode 100644
index 0000000..90ed54d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_person_pin_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,2L5,2c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM12,5.3c1.49,0 2.7,1.21 2.7,2.7 0,1.49 -1.21,2.7 -2.7,2.7 -1.49,0 -2.7,-1.21 -2.7,-2.7 0,-1.49 1.21,-2.7 2.7,-2.7zM18,16L6,16v-0.9c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v0.9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_person_pin_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_person_pin_circle_black_24dp.xml
new file mode 100644
index 0000000..6fdfe70
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_person_pin_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C8.14,2 5,5.14 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.86 -3.14,-7 -7,-7zM12,4c1.1,0 2,0.9 2,2 0,1.11 -0.9,2 -2,2s-2,-0.89 -2,-2c0,-1.1 0.9,-2 2,-2zM12,14c-1.67,0 -3.14,-0.85 -4,-2.15 0.02,-1.32 2.67,-2.05 4,-2.05s3.98,0.73 4,2.05c-0.86,1.3 -2.33,2.15 -4,2.15z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_pin_drop_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_pin_drop_black_24dp.xml
new file mode 100644
index 0000000..49a3317
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_pin_drop_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,8c0,-3.31 -2.69,-6 -6,-6S6,4.69 6,8c0,4.5 6,11 6,11s6,-6.5 6,-11zM10,8c0,-1.1 0.9,-2 2,-2s2,0.9 2,2 -0.89,2 -2,2c-1.1,0 -2,-0.9 -2,-2zM5,20v2h14v-2L5,20z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_place_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_place_black_24dp.xml
new file mode 100644
index 0000000..e3291a9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_place_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_rate_review_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_rate_review_black_24dp.xml
new file mode 100644
index 0000000..df82c52
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_rate_review_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,14v-2.47l6.88,-6.88c0.2,-0.2 0.51,-0.2 0.71,0l1.77,1.77c0.2,0.2 0.2,0.51 0,0.71L8.47,14L6,14zM18,14h-7.5l2,-2L18,12v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_restaurant_menu_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_restaurant_menu_black_24dp.xml
new file mode 100644
index 0000000..7375ec3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_restaurant_menu_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8.1,13.34l2.83,-2.83L3.91,3.5c-1.56,1.56 -1.56,4.09 0,5.66l4.19,4.18zM14.88,11.53c1.53,0.71 3.68,0.21 5.27,-1.38 1.91,-1.91 2.28,-4.65 0.81,-6.12 -1.46,-1.46 -4.2,-1.1 -6.12,0.81 -1.59,1.59 -2.09,3.74 -1.38,5.27L3.7,19.87l1.41,1.41L12,14.41l6.88,6.88 1.41,-1.41L13.41,13l1.47,-1.47z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_satellite_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_satellite_black_24dp.xml
new file mode 100644
index 0000000..82862f0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_satellite_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM5,4.99h3C8,6.65 6.66,8 5,8L5,4.99zM5,12v-2c2.76,0 5,-2.25 5,-5.01h2C12,8.86 8.87,12 5,12zM5,18l3.5,-4.5 2.5,3.01L14.5,12l4.5,6L5,18z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_store_mall_directory_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_store_mall_directory_black_24dp.xml
new file mode 100644
index 0000000..806e675
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_store_mall_directory_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4L4,4v2h16L20,4zM21,14v-2l-1,-5L4,7l-1,5v2h1v6h10v-6h4v6h2v-6h1zM12,18L6,18v-4h6v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_terrain_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_terrain_black_24dp.xml
new file mode 100644
index 0000000..0350ef5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_terrain_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_traffic_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_traffic_black_24dp.xml
new file mode 100644
index 0000000..db5f068
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_traffic_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10h-3L17,8.86c1.72,-0.45 3,-2 3,-3.86h-3L17,4c0,-0.55 -0.45,-1 -1,-1L8,3c-0.55,0 -1,0.45 -1,1v1L4,5c0,1.86 1.28,3.41 3,3.86L7,10L4,10c0,1.86 1.28,3.41 3,3.86L7,15L4,15c0,1.86 1.28,3.41 3,3.86L7,20c0,0.55 0.45,1 1,1h8c0.55,0 1,-0.45 1,-1v-1.14c1.72,-0.45 3,-2 3,-3.86h-3v-1.14c1.72,-0.45 3,-2 3,-3.86zM12,19c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2c1.1,0 2,0.9 2,2s-0.89,2 -2,2zM12,14c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2c1.1,0 2,0.9 2,2s-0.89,2 -2,2zM12,9c-1.11,0 -2,-0. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/maps/ic_zoom_out_map_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/maps/ic_zoom_out_map_black_24dp.xml
new file mode 100644
index 0000000..ff394c0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/maps/ic_zoom_out_map_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,3l2.3,2.3 -2.89,2.87 1.42,1.42L18.7,6.7 21,9L21,3zM3,9l2.3,-2.3 2.87,2.89 1.42,-1.42L6.7,5.3 9,3L3,3zM9,21l-2.3,-2.3 2.89,-2.87 -1.42,-1.42L5.3,17.3 3,15v6zM21,15l-2.3,2.3 -2.87,-2.89 -1.42,1.42 2.89,2.87L15,21h6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_black_24dp.xml
new file mode 100644
index 0000000..ff485cf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_black_24dp.xml
new file mode 100644
index 0000000..beafea3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_downward_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_downward_black_24dp.xml
new file mode 100644
index 0000000..23277e2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_downward_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"
+ android:fillColor="#010101"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_black_24dp.xml
new file mode 100644
index 0000000..62b27ef
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,10l5,5 5,-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_circle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_circle_black_24dp.xml
new file mode 100644
index 0000000..81a3139
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_circle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,14l-4,-4h8l-4,4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_black_24dp.xml
new file mode 100644
index 0000000..b1442ce
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,14l5,-5 5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_black_24dp.xml
new file mode 100644
index 0000000..cf9e208
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_upward_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_upward_black_24dp.xml
new file mode 100644
index 0000000..c64ae35
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_upward_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_black_24dp.xml
new file mode 100644
index 0000000..7d2b57e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_black_24dp.xml
new file mode 100644
index 0000000..3c728c5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_black_24dp.xml
new file mode 100644
index 0000000..e6bb3ca
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_black_24dp.xml
new file mode 100644
index 0000000..2483512
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_black_24dp.xml
new file mode 100644
index 0000000..ede4b71
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_black_24dp.xml
new file mode 100644
index 0000000..3afdf96
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_black_24dp.xml
new file mode 100644
index 0000000..8d57dbc
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_black_24dp.xml
new file mode 100644
index 0000000..8bb67b6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_black_24dp.xml
new file mode 100644
index 0000000..3113ce0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_black_24dp.xml
new file mode 100644
index 0000000..6d9343b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_black_24dp.xml
new file mode 100644
index 0000000..da83afd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_black_24dp.xml
new file mode 100644
index 0000000..5176d8a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_black_24dp.xml
new file mode 100644
index 0000000..8229a9a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_subdirectory_arrow_left_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_subdirectory_arrow_left_black_24dp.xml
new file mode 100644
index 0000000..c3b6c6d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_subdirectory_arrow_left_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,9l1.42,1.42L8.83,14H18V4h2v12H8.83l3.59,3.58L11,21l-6,-6 6,-6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_subdirectory_arrow_right_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_subdirectory_arrow_right_black_24dp.xml
new file mode 100644
index 0000000..65acc69
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_subdirectory_arrow_right_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,15l-6,6 -1.42,-1.42L15.17,16H4V4h2v10h9.17l-3.59,-3.58L13,9l6,6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_black_24dp.xml
new file mode 100644
index 0000000..7282a97
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_black_24dp.xml
new file mode 100644
index 0000000..e9ba754
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zM12,18.17L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_adb_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_adb_black_24dp.xml
new file mode 100644
index 0000000..79ba023
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_adb_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,16c0,3.87 3.13,7 7,7s7,-3.13 7,-7v-4L5,12v4zM16.12,4.37l2.1,-2.1 -0.82,-0.83 -2.3,2.31C14.16,3.28 13.12,3 12,3s-2.16,0.28 -3.09,0.75L6.6,1.44l-0.82,0.83 2.1,2.1C6.14,5.64 5,7.68 5,10v1h14v-1c0,-2.32 -1.14,-4.36 -2.88,-5.63zM9,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM15,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_flat_angled_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_flat_angled_black_24dp.xml
new file mode 100644
index 0000000..7a3d29d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_flat_angled_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22.25,14.29l-0.69,1.89L9.2,11.71l2.08,-5.66 8.56,3.09c2.1,0.76 3.18,3.06 2.41,5.15zM1.5,12.14L8,14.48L8,19h8v-1.63L20.52,19l0.69,-1.89 -19.02,-6.86 -0.69,1.89zM7.3,10.2c1.49,-0.72 2.12,-2.51 1.41,-4C7.99,4.71 6.2,4.08 4.7,4.8c-1.49,0.71 -2.12,2.5 -1.4,4 0.71,1.49 2.5,2.12 4,1.4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_flat_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_flat_black_24dp.xml
new file mode 100644
index 0000000..85c0098
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_flat_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,11v2L9,13L9,7h9c2.21,0 4,1.79 4,4zM2,14v2h6v2h8v-2h6v-2L2,14zM7.14,12.1c1.16,-1.19 1.14,-3.08 -0.04,-4.24 -1.19,-1.16 -3.08,-1.14 -4.24,0.04 -1.16,1.19 -1.14,3.08 0.04,4.24 1.19,1.16 3.08,1.14 4.24,-0.04z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_individual_suite_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_individual_suite_black_24dp.xml
new file mode 100644
index 0000000..38a4ad5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_individual_suite_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,13c1.65,0 3,-1.35 3,-3S8.65,7 7,7s-3,1.35 -3,3 1.35,3 3,3zM19,7h-8v7L3,14L3,7L1,7v10h22v-6c0,-2.21 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_extra_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_extra_black_24dp.xml
new file mode 100644
index 0000000..44b2ed2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_extra_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,12L4,3L2,3v9c0,2.76 2.24,5 5,5h6v-2L7,15c-1.66,0 -3,-1.34 -3,-3zM22.83,17.24c-0.38,-0.72 -1.29,-0.97 -2.03,-0.63l-1.09,0.5 -3.41,-6.98c-0.34,-0.68 -1.03,-1.12 -1.79,-1.12L11,9L11,3L5,3v8c0,1.66 1.34,3 3,3h7l3.41,7 3.72,-1.7c0.77,-0.36 1.1,-1.3 0.7,-2.06z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_normal_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_normal_black_24dp.xml
new file mode 100644
index 0000000..61c753a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_normal_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,12L5,3L3,3v9c0,2.76 2.24,5 5,5h6v-2L8,15c-1.66,0 -3,-1.34 -3,-3zM20.5,18L19,18v-7c0,-1.1 -0.9,-2 -2,-2h-5L12,3L6,3v8c0,1.65 1.35,3 3,3h7v7h4.5c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_reduced_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_reduced_black_24dp.xml
new file mode 100644
index 0000000..3eeb041
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_legroom_reduced_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.97,19.2c0.18,0.96 -0.55,1.8 -1.47,1.8H14v-3l1,-4H9c-1.65,0 -3,-1.35 -3,-3V3h6v6h5c1.1,0 2,0.9 2,2l-2,7h1.44c0.73,0 1.39,0.49 1.53,1.2zM5,12V3H3v9c0,2.76 2.24,5 5,5h4v-2H8c-1.66,0 -3,-1.34 -3,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_recline_extra_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_recline_extra_black_24dp.xml
new file mode 100644
index 0000000..215d2db
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_recline_extra_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.35,5.64c-0.9,-0.64 -1.12,-1.88 -0.49,-2.79 0.63,-0.9 1.88,-1.12 2.79,-0.49 0.9,0.64 1.12,1.88 0.49,2.79 -0.64,0.9 -1.88,1.12 -2.79,0.49zM16,19L8.93,19c-1.48,0 -2.74,-1.08 -2.96,-2.54L4,7L2,7l1.99,9.76C4.37,19.2 6.47,21 8.94,21L16,21v-2zM16.23,15h-4.88l-1.03,-4.1c1.58,0.89 3.28,1.54 5.15,1.22L15.47,9.99c-1.63,0.31 -3.44,-0.27 -4.69,-1.25L9.14,7.47c-0.23,-0.18 -0.49,-0.3 -0.76,-0.38 -0.32,-0.09 -0.66,-0.12 -0.99,-0.06h-0.02c-1.23,0.22 -2.05,1.39 -1.84,2.61l1.35 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_recline_normal_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_recline_normal_black_24dp.xml
new file mode 100644
index 0000000..e67a969
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_airline_seat_recline_normal_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.59,5.41c-0.78,-0.78 -0.78,-2.05 0,-2.83 0.78,-0.78 2.05,-0.78 2.83,0 0.78,0.78 0.78,2.05 0,2.83 -0.79,0.79 -2.05,0.79 -2.83,0zM6,16L6,7L4,7v9c0,2.76 2.24,5 5,5h6v-2L9,19c-1.66,0 -3,-1.34 -3,-3zM20,20.07L14.93,15L11.5,15v-3.68c1.4,1.15 3.6,2.16 5.5,2.16v-2.16c-1.66,0.02 -3.61,-0.87 -4.67,-2.04l-1.4,-1.55c-0.19,-0.21 -0.43,-0.38 -0.69,-0.5 -0.29,-0.14 -0.62,-0.23 -0.96,-0.23h-0.03C8.01,7 7,8.01 7,9.25L7,15c0,1.66 1.34,3 3,3h5.07l3.5,3.5L20,20.07z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_bluetooth_audio_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_bluetooth_audio_black_24dp.xml
new file mode 100644
index 0000000..ece1684
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_bluetooth_audio_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_confirmation_number_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_confirmation_number_black_24dp.xml
new file mode 100644
index 0000000..d8b22a3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_confirmation_number_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,10L22,6c0,-1.11 -0.9,-2 -2,-2L4,4c-1.1,0 -1.99,0.89 -1.99,2v4c1.1,0 1.99,0.9 1.99,2s-0.89,2 -2,2v4c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-4c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2zM13,17.5h-2v-2h2v2zM13,13h-2v-2h2v2zM13,8.5h-2v-2h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_disc_full_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_disc_full_black_24dp.xml
new file mode 100644
index 0000000..95b6004
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_disc_full_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,16h2v-2h-2v2zM20,7v5h2L22,7h-2zM10,4c-4.42,0 -8,3.58 -8,8s3.58,8 8,8 8,-3.58 8,-8 -3.58,-8 -8,-8zM10,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_alt_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_alt_black_24dp.xml
new file mode 100644
index 0000000..5974dd4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_alt_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM4,12c0,-4.4 3.6,-8 8,-8 1.8,0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4,13.8 4,12zM12,20c-1.8,0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20,10.2 20,12c0,4.4 -3.6,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_black_24dp.xml
new file mode 100644
index 0000000..6944a90
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8 0,-1.85 0.63,-3.55 1.69,-4.9L16.9,18.31C15.55,19.37 13.85,20 12,20zM18.31,16.9L7.1,5.69C8.45,4.63 10.15,4 12,4c4.42,0 8,3.58 8,8 0,1.85 -0.63,3.55 -1.69,4.9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_drive_eta_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_drive_eta_black_24dp.xml
new file mode 100644
index 0000000..7df55f0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_drive_eta_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.92,5.01C18.72,4.42 18.16,4 17.5,4h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,11v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,12 6.5,12s1.5,0.67 1.5,1.5S7.33,15 6.5,15zM17.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,10l1.5,-4.5h11L19,10L5,10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_enhanced_encryption_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_enhanced_encryption_black_24dp.xml
new file mode 100644
index 0000000..edba31b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_enhanced_encryption_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2H8.9V6zM16,16h-3v3h-2v-3H8v-2h3v-3h2v3h3v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_available_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_available_black_24dp.xml
new file mode 100644
index 0000000..a0be0d0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_available_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.53,11.06L15.47,10l-4.88,4.88 -2.12,-2.12 -1.06,1.06L10.59,17l5.94,-5.94zM19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_busy_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_busy_black_24dp.xml
new file mode 100644
index 0000000..cbd8810
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_busy_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.31,17l2.44,-2.44L14.19,17l1.06,-1.06 -2.44,-2.44 2.44,-2.44L14.19,10l-2.44,2.44L9.31,10l-1.06,1.06 2.44,2.44 -2.44,2.44L9.31,17zM19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_note_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_note_black_24dp.xml
new file mode 100644
index 0000000..7322c92
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_note_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,10L7,10v2h10v-2zM19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM14,14L7,14v2h7v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_folder_special_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_folder_special_black_24dp.xml
new file mode 100644
index 0000000..fc94033
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_folder_special_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM17.94,17L15,15.28 12.06,17l0.78,-3.33 -2.59,-2.24 3.41,-0.29L15,8l1.34,3.14 3.41,0.29 -2.59,2.24 0.78,3.33z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_live_tv_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_live_tv_black_24dp.xml
new file mode 100644
index 0000000..ca255f9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_live_tv_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,6h-7.59l3.29,-3.29L16,2l-4,4 -4,-4 -0.71,0.71L10.59,6L3,6c-1.1,0 -2,0.89 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.11 -0.9,-2 -2,-2zM21,20L3,20L3,8h18v12zM9,10v8l7,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_mms_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_mms_black_24dp.xml
new file mode 100644
index 0000000..f66e4af
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_mms_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM5,14l3.5,-4.5 2.5,3.01L14.5,8l4.5,6H5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_more_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_more_black_24dp.xml
new file mode 100644
index 0000000..4f35646
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_more_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.97,0.89 1.66,0.89L22,21c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM9,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM14,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_check_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_check_black_24dp.xml
new file mode 100644
index 0000000..a8381d5
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_check_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.9,5c-0.17,0 -0.32,0.09 -0.41,0.23l-0.07,0.15 -5.18,11.65c-0.16,0.29 -0.26,0.61 -0.26,0.96 0,1.11 0.9,2.01 2.01,2.01 0.96,0 1.77,-0.68 1.96,-1.59l0.01,-0.03L16.4,5.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM1,9l2,2c2.88,-2.88 6.79,-4.08 10.53,-3.62l1.19,-2.68C9.89,3.84 4.74,5.27 1,9zM21,11l2,-2c-1.64,-1.64 -3.55,-2.82 -5.59,-3.57l-0.53,2.82c1.5,0.62 2.9,1.53 4.12,2.75zM17,15l2,-2c-0.8,-0.8 -1.7,-1.42 -2.66,-1.89l-0.55,2.92c0.42,0.27 0.83,0.59 1.21,0.97zM5,13l2,2c1.13,-1 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_locked_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_locked_black_24dp.xml
new file mode 100644
index 0000000..33bca18
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_locked_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.5,10c0.17,0 0.33,0.03 0.5,0.05L20,1L1,20h13v-3c0,-0.89 0.39,-1.68 1,-2.23v-0.27c0,-2.48 2.02,-4.5 4.5,-4.5zM22,16v-1.5c0,-1.38 -1.12,-2.5 -2.5,-2.5S17,13.12 17,14.5L17,16c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zM21,16h-3v-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5L21,16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_no_encryption_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_no_encryption_black_24dp.xml
new file mode 100644
index 0000000..bf1acc4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_no_encryption_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,21.78L4.22,5 3,6.22l2.04,2.04C4.42,8.6 4,9.25 4,10v10c0,1.1 0.9,2 2,2h12c0.23,0 0.45,-0.05 0.66,-0.12L19.78,23 21,21.78zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2H9.66L20,18.34V10c0,-1.1 -0.9,-2 -2,-2h-1V6c0,-2.76 -2.24,-5 -5,-5 -2.56,0 -4.64,1.93 -4.94,4.4L8.9,7.24V6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_ondemand_video_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_ondemand_video_black_24dp.xml
new file mode 100644
index 0000000..b556865
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_ondemand_video_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v12c0,1.1 0.89,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.11 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12zM16,11l-7,4L9,7z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_personal_video_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_personal_video_black_24dp.xml
new file mode 100644
index 0000000..30eef0d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_personal_video_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v12c0,1.1 0.89,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.11 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_bluetooth_speaker_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_bluetooth_speaker_black_24dp.xml
new file mode 100644
index 0000000..9fb950c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_bluetooth_speaker_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.71,9.5L17,7.21L17,11h0.5l2.85,-2.85L18.21,6l2.15,-2.15L17.5,1L17,1v3.79L14.71,2.5l-0.71,0.71L16.79,6 14,8.79l0.71,0.71zM18,2.91l0.94,0.94 -0.94,0.94L18,2.91zM18,7.21l0.94,0.94 -0.94,0.94L18,7.21zM20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_forwarded_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_forwarded_black_24dp.xml
new file mode 100644
index 0000000..6972452
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_forwarded_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,11l5,-5 -5,-5v3h-4v4h4v3zM20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_in_talk_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_in_talk_black_24dp.xml
new file mode 100644
index 0000000..b48b9ed
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_in_talk_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,12h2c0,-4.97 -4.03,-9 -9,-9v2c3.87,0 7,3.13 7,7zM15,12h2c0,-2.76 -2.24,-5 -5,-5v2c1.66,0 3,1.34 3,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_locked_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_locked_black_24dp.xml
new file mode 100644
index 0000000..67e7c49
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_locked_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM20,4v-0.5C20,2.12 18.88,1 17.5,1S15,2.12 15,3.5L15,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L21,5c0,-0.55 -0.45,-1 -1,-1zM19.2,4h-3.4v-0.5c0, [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_missed_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_missed_black_24dp.xml
new file mode 100644
index 0000000..b233831
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_missed_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.5,5.5L12,11l7,-7 -1,-1 -6,6 -4.5,-4.5L11,4.5L11,3L5,3v6h1.5L6.5,5.5zM23.71,16.67C20.66,13.78 16.54,12 12,12 7.46,12 3.34,13.78 0.29,16.67c-0.18,0.18 -0.29,0.43 -0.29,0.71s0.11,0.53 0.29,0.71l2.48,2.48c0.18,0.18 0.43,0.29 0.71,0.29 0.27,0 0.52,-0.11 0.7,-0.28 0.79,-0.74 1.69,-1.36 2.66,-1.85 0.33,-0.16 0.56,-0.5 0.56,-0.9v-3.1c1.45,-0.48 3,-0.73 4.6,-0.73 1.6,0 3.15,0.25 4.6,0.72v3.1c0,0.39 0.23,0.74 0.56,0.9 0.98,0.49 1.87,1.12 2.67,1.85 0.18,0.18 0.43,0.28 0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_paused_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_paused_black_24dp.xml
new file mode 100644
index 0000000..e5be52e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_paused_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,3h-2v7h2L17,3zM20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,3v7h2L21,3h-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_power_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_power_black_24dp.xml
new file mode 100644
index 0000000..51b929f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_power_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.01,7L16,3h-2v4h-4V3H8v4h-0.01C7,6.99 6,7.99 6,8.99v5.49L9.5,18v3h5v-3l3.5,-3.51v-5.5c0,-1 -1,-2 -1.99,-1.99z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_rv_hookup_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_rv_hookup_black_24dp.xml
new file mode 100644
index 0000000..4c9eeac
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_rv_hookup_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,17v-6c0,-1.1 -0.9,-2 -2,-2L7,9L7,7l-3,3 3,3v-2h4v3L4,14v3c0,1.1 0.9,2 2,2h2c0,1.66 1.34,3 3,3s3,-1.34 3,-3h8v-2h-2zM11,20c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,14h-4v-3h4v3zM17,2v2L9,4v2h8v2l3,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_sd_card_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sd_card_black_24dp.xml
new file mode 100644
index 0000000..f9ad72d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sd_card_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM12,8h-2L10,4h2v4zM15,8h-2L13,4h2v4zM18,8h-2L16,4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_sim_card_alert_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sim_card_alert_black_24dp.xml
new file mode 100644
index 0000000..3965487
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sim_card_alert_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM13,17h-2v-2h2v2zM13,13h-2L11,8h2v5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_black_24dp.xml
new file mode 100644
index 0000000..8b8ead2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_failed_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_failed_black_24dp.xml
new file mode 100644
index 0000000..29f9baa
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_failed_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_black_24dp.xml
new file mode 100644
index 0000000..ce8796c
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_disabled_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_disabled_black_24dp.xml
new file mode 100644
index 0000000..a2a1db3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_disabled_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,6.35L10,4.26c-0.8,0.21 -1.55,0.54 -2.23,0.96l1.46,1.46c0.25,-0.12 0.5,-0.24 0.77,-0.33zM2.86,5.41l2.36,2.36C4.45,8.99 4,10.44 4,12c0,2.21 0.91,4.2 2.36,5.64L4,20h6v-6l-2.24,2.24C6.68,15.15 6,13.66 6,12c0,-1 0.25,-1.94 0.68,-2.77l8.08,8.08c-0.25,0.13 -0.5,0.25 -0.77,0.34v2.09c0.8,-0.21 1.55,-0.54 2.23,-0.96l2.36,2.36 1.27,-1.27L4.14,4.14 2.86,5.41zM20,4h-6v6l2.24,-2.24C17.32,8.85 18,10.34 18,12c0,1 -0.25,1.94 -0.68,2.77l1.46,1.46C19.55,15.01 20,13.56 20,12c0, [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_problem_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_problem_black_24dp.xml
new file mode 100644
index 0000000..e361fb9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_problem_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,12c0,2.21 0.91,4.2 2.36,5.64L3,20h6v-6l-2.24,2.24C5.68,15.15 5,13.66 5,12c0,-2.61 1.67,-4.83 4,-5.65L9,4.26C5.55,5.15 3,8.27 3,12zM11,17h2v-2h-2v2zM21,4h-6v6l2.24,-2.24C18.32,8.85 19,10.34 19,12c0,2.61 -1.67,4.83 -4,5.65v2.09c3.45,-0.89 6,-4.01 6,-7.74 0,-2.21 -0.91,-4.2 -2.36,-5.64L21,4zM11,13h2L13,7h-2v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_system_update_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_system_update_black_24dp.xml
new file mode 100644
index 0000000..94605e0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_system_update_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM16,13h-3L13,8h-2v5L8,13l4,4 4,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_tap_and_play_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_tap_and_play_black_24dp.xml
new file mode 100644
index 0000000..267ab9e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_tap_and_play_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,16v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM2,20v3h3c0,-1.66 -1.34,-3 -3,-3zM2,12v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.92,-11 -11,-11zM17,1.01L7,1c-1.1,0 -2,0.9 -2,2v7.37c0.69,0.16 1.36,0.37 2,0.64L7,5h10v13h-3.03c0.52,1.25 0.84,2.59 0.95,4L17,22c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_time_to_leave_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_time_to_leave_black_24dp.xml
new file mode 100644
index 0000000..7df55f0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_time_to_leave_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.92,5.01C18.72,4.42 18.16,4 17.5,4h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,11v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,12 6.5,12s1.5,0.67 1.5,1.5S7.33,15 6.5,15zM17.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,10l1.5,-4.5h11L19,10L5,10z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_vibration_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_vibration_black_24dp.xml
new file mode 100644
index 0000000..669d5e3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_vibration_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,15h2L2,9L0,9v6zM3,17h2L5,7L3,7v10zM22,9v6h2L24,9h-2zM19,17h2L21,7h-2v10zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19L8,19L8,5h8v14z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_voice_chat_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_voice_chat_black_24dp.xml
new file mode 100644
index 0000000..df35c27
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_voice_chat_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14l-4,-3.2L14,14L6,14L6,6h8v3.2L18,6v8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_vpn_lock_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_vpn_lock_black_24dp.xml
new file mode 100644
index 0000000..7ecdb91
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_vpn_lock_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_wc_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_wc_black_24dp.xml
new file mode 100644
index 0000000..67dcddf
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_wc_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.5,22v-7.5L4,14.5L4,9c0,-1.1 0.9,-2 2,-2h3c1.1,0 2,0.9 2,2v5.5L9.5,14.5L9.5,22h-4zM18,22v-6h3l-2.54,-7.63C18.18,7.55 17.42,7 16.56,7h-0.12c-0.86,0 -1.63,0.55 -1.9,1.37L12,16h3v6h3zM7.5,6c1.11,0 2,-0.89 2,-2s-0.89,-2 -2,-2 -2,0.89 -2,2 0.89,2 2,2zM16.5,6c1.11,0 2,-0.89 2,-2s-0.89,-2 -2,-2 -2,0.89 -2,2 0.89,2 2,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/notification/ic_wifi_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/notification/ic_wifi_black_24dp.xml
new file mode 100644
index 0000000..69c81f1
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/notification/ic_wifi_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1,9l2,2c4.97,-4.97 13.03,-4.97 18,0l2,-2C16.93,2.93 7.08,2.93 1,9zM9,17l3,3 3,-3c-1.65,-1.66 -4.34,-1.66 -6,0zM5,13l2,2c2.76,-2.76 7.24,-2.76 10,0l2,-2C15.14,9.14 8.87,9.14 5,13z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_ac_unit_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_ac_unit_black_24dp.xml
new file mode 100644
index 0000000..a6fc395
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_ac_unit_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,11h-4.17l3.24,-3.24 -1.41,-1.42L15,11h-2V9l4.66,-4.66 -1.42,-1.41L13,6.17V2h-2v4.17L7.76,2.93 6.34,4.34 11,9v2H9L4.34,6.34 2.93,7.76 6.17,11H2v2h4.17l-3.24,3.24 1.41,1.42L9,13h2v2l-4.66,4.66 1.42,1.41L11,17.83V22h2v-4.17l3.24,3.24 1.42,-1.41L13,15v-2h2l4.66,4.66 1.41,-1.42L17.83,13H22z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_airport_shuttle_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_airport_shuttle_black_24dp.xml
new file mode 100644
index 0000000..5e0d552
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_airport_shuttle_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5L3,5c-1.1,0 -2,0.89 -2,2v9h2c0,1.65 1.34,3 3,3s3,-1.35 3,-3h5.5c0,1.65 1.34,3 3,3s3,-1.35 3,-3L23,16v-5l-6,-6zM3,11L3,7h4v4L3,11zM6,17.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM13,11L9,11L9,7h4v4zM17.5,17.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM15,11L15,7h1l4,4h-5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_all_inclusive_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_all_inclusive_black_24dp.xml
new file mode 100644
index 0000000..b40d240
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_all_inclusive_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.6,6.62c-1.44,0 -2.8,0.56 -3.77,1.53L12,10.66 10.48,12h0.01L7.8,14.39c-0.64,0.64 -1.49,0.99 -2.4,0.99 -1.87,0 -3.39,-1.51 -3.39,-3.38S3.53,8.62 5.4,8.62c0.91,0 1.76,0.35 2.44,1.03l1.13,1 1.51,-1.34L9.22,8.2C8.2,7.18 6.84,6.62 5.4,6.62 2.42,6.62 0,9.04 0,12s2.42,5.38 5.4,5.38c1.44,0 2.8,-0.56 3.77,-1.53l2.83,-2.5 0.01,0.01L13.52,12h-0.01l2.69,-2.39c0.64,-0.64 1.49,-0.99 2.4,-0.99 1.87,0 3.39,1.51 3.39,3.38s-1.52,3.38 -3.39,3.38c-0.9,0 -1.76,-0.35 -2.44,-1.03l- [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_beach_access_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_beach_access_black_24dp.xml
new file mode 100644
index 0000000..f2fd5e7
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_beach_access_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13.127,14.56l1.43,-1.43 6.44,6.443L19.57,21zM17.42,8.83l2.86,-2.86c-3.95,-3.95 -10.35,-3.96 -14.3,-0.02 3.93,-1.3 8.31,-0.25 11.44,2.88zM5.95,5.98c-3.94,3.95 -3.93,10.35 0.02,14.3l2.86,-2.86C5.7,14.29 4.65,9.91 5.95,5.98zM5.97,5.96l-0.01,0.01c-0.38,3.01 1.17,6.88 4.3,10.02l5.73,-5.73c-3.13,-3.13 -7.01,-4.68 -10.02,-4.3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_business_center_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_business_center_black_24dp.xml
new file mode 100644
index 0000000..1bc076d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_business_center_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,16v-1L3.01,15L3,19c0,1.11 0.89,2 2,2h14c1.11,0 2,-0.89 2,-2v-4h-7v1h-4zM20,7h-4.01L15.99,5l-2,-2h-4l-2,2v2L4,7c-1.1,0 -2,0.9 -2,2v3c0,1.11 0.89,2 2,2h6v-2h4v2h6c1.1,0 2,-0.9 2,-2L22,9c0,-1.1 -0.9,-2 -2,-2zM14,7h-4L10,5h4v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_casino_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_casino_black_24dp.xml
new file mode 100644
index 0000000..2dbf3f4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_casino_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM7.5,18c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,15 7.5,15s1.5,0.67 1.5,1.5S8.33,18 7.5,18zM7.5,9C6.67,9 6,8.33 6,7.5S6.67,6 7.5,6 9,6.67 9,7.5 8.33,9 7.5,9zM12,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM16.5,18c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM16.5,9c-0.83,0 -1.5,-0.67 -1.5,-1. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_child_care_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_child_care_black_24dp.xml
new file mode 100644
index 0000000..5af3925
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_child_care_black_24dp.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.5,10.5m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.5,10.5m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22.94,12.66c0.04,-0.21 0.06,-0.43 0.06,-0.66s-0.02,-0.45 -0.06,-0.66c-0.25,-1.51 -1.36,-2.74 -2.81,-3.17 -0.53,-1.12 -1.28,-2.1 -2.19,-2.91C16.36,3.85 14.28,3 12,3s-4.36,0.85 -5.94,2.26c-0.92,0.81 -1.67,1.8 -2.19,2.91 -1.45,0.43 -2.56,1.65 -2.81,3.17 -0.04,0.21 -0.06,0.43 -0.06,0.66s0.02,0.45 0.06,0.66c0.25,1.51 1.36,2.74 2.81,3.17 0.52,1.11 1.27,2.09 2.17,2.89C7.62,20.14 9.71,21 12,21s4.38,-0.86 5.97,-2.28c0.9,-0.8 1.65,-1.79 2.17,-2.89 1.44,-0.43 2.55,-1.65 2 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_child_friendly_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_child_friendly_black_24dp.xml
new file mode 100644
index 0000000..b7384f4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_child_friendly_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,2v8h8c0,-4.42 -3.58,-8 -8,-8zM19.32,15.89C20.37,14.54 21,12.84 21,11L6.44,11l-0.95,-2L2,9v2h2.22s1.89,4.07 2.12,4.42c-1.1,0.59 -1.84,1.75 -1.84,3.08C4.5,20.43 6.07,22 8,22c1.76,0 3.22,-1.3 3.46,-3h2.08c0.24,1.7 1.7,3 3.46,3 1.93,0 3.5,-1.57 3.5,-3.5 0,-1.04 -0.46,-1.97 -1.18,-2.61zM8,20c-0.83,0 -1.5,-0.67 -1.5,-1.5S7.17,17 8,17s1.5,0.67 1.5,1.5S8.83,20 8,20zM17,20c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.17,17 17,17s1.5,0.67 1.5,1.5S17.83,20 17,20z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_fitness_center_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_fitness_center_black_24dp.xml
new file mode 100644
index 0000000..846deb4
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_fitness_center_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.57,14.86L22,13.43 20.57,12 17,15.57 8.43,7 12,3.43 10.57,2 9.14,3.43 7.71,2 5.57,4.14 4.14,2.71 2.71,4.14l1.43,1.43L2,7.71l1.43,1.43L2,10.57 3.43,12 7,8.43 15.57,17 12,20.57 13.43,22l1.43,-1.43L16.29,22l2.14,-2.14 1.43,1.43 1.43,-1.43 -1.43,-1.43L22,16.29z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_free_breakfast_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_free_breakfast_black_24dp.xml
new file mode 100644
index 0000000..3d0272a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_free_breakfast_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3L4,3v10c0,2.21 1.79,4 4,4h6c2.21,0 4,-1.79 4,-4v-3h2c1.11,0 2,-0.9 2,-2L22,5c0,-1.11 -0.89,-2 -2,-2zM20,8h-2L18,5h2v3zM4,19h16v2L4,21z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_golf_course_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_golf_course_black_24dp.xml
new file mode 100644
index 0000000..4e24744
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_golf_course_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.5,19.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17,5.92L9,2v18H7v-1.73c-1.79,0.35 -3,0.99 -3,1.73 0,1.1 2.69,2 6,2s6,-0.9 6,-2c0,-0.99 -2.16,-1.81 -5,-1.97V8.98l6,-3.06z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_hot_tub_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_hot_tub_black_24dp.xml
new file mode 100644
index 0000000..ccb05e2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_hot_tub_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7,6m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.15,12c-0.31,-0.22 -0.59,-0.46 -0.82,-0.72l-1.4,-1.55c-0.19,-0.21 -0.43,-0.38 -0.69,-0.5 -0.29,-0.14 -0.62,-0.23 -0.96,-0.23h-0.03C6.01,9 5,10.01 5,11.25L5,12L2,12v8c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-8L11.15,12zM7,20L5,20v-6h2v6zM11,20L9,20v-6h2v6zM15,20h-2v-6h2v6zM19,20h-2v-6h2v6zM18.65,5.86l-0.07,-0.07c-0.57,-0.62 -0.82,-1.41 -0.67,-2.2L18,3h-1.89l-0.06,0.43c-0.2,1.36 0.27,2.71 1.3,3.72l0.07,0.06c0.57,0.62 0.82,1.41 0.67,2.2l-0.11,0.59h1.91l0.06,-0.43c0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_kitchen_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_kitchen_black_24dp.xml
new file mode 100644
index 0000000..b988eec
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_kitchen_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,2.01L6,2c-1.1,0 -2,0.89 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.11 -0.9,-1.99 -2,-1.99zM18,20L6,20v-9.02h12L18,20zM18,9L6,9L6,4h12v5zM8,5h2v3L8,8zM8,12h2v5L8,17z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_pool_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_pool_black_24dp.xml
new file mode 100644
index 0000000..e5a4635
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_pool_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,21c-1.11,0 -1.73,-0.37 -2.18,-0.64 -0.37,-0.22 -0.6,-0.36 -1.15,-0.36 -0.56,0 -0.78,0.13 -1.15,0.36 -0.46,0.27 -1.07,0.64 -2.18,0.64s-1.73,-0.37 -2.18,-0.64c-0.37,-0.22 -0.6,-0.36 -1.15,-0.36 -0.56,0 -0.78,0.13 -1.15,0.36 -0.46,0.27 -1.08,0.64 -2.19,0.64 -1.11,0 -1.73,-0.37 -2.18,-0.64 -0.37,-0.23 -0.6,-0.36 -1.15,-0.36s-0.78,0.13 -1.15,0.36c-0.46,0.27 -1.08,0.64 -2.19,0.64v-2c0.56,0 0.78,-0.13 1.15,-0.36 0.46,-0.27 1.08,-0.64 2.19,-0.64s1.73,0.37 2.18,0.64c [...]
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,5.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_room_service_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_room_service_black_24dp.xml
new file mode 100644
index 0000000..295bd49
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_room_service_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,17h20v2L2,19zM13.84,7.79c0.1,-0.24 0.16,-0.51 0.16,-0.79 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2c0,0.28 0.06,0.55 0.16,0.79C6.25,8.6 3.27,11.93 3,16h18c-0.27,-4.07 -3.25,-7.4 -7.16,-8.21z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_smoke_free_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_smoke_free_black_24dp.xml
new file mode 100644
index 0000000..f764d2e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_smoke_free_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,6l6.99,7L2,13v3h9.99l7,7 1.26,-1.25 -17,-17zM20.5,13L22,13v3h-1.5zM18,13h1.5v3L18,16zM18.85,4.88c0.62,-0.61 1,-1.45 1,-2.38h-1.5c0,1.02 -0.83,1.85 -1.85,1.85v1.5c2.24,0 4,1.83 4,4.07L20.5,12L22,12L22,9.92c0,-2.23 -1.28,-4.15 -3.15,-5.04zM14.5,8.7h1.53c1.05,0 1.97,0.74 1.97,2.05L18,12h1.5v-1.59c0,-1.8 -1.6,-3.16 -3.47,-3.16L14.5,7.25c-1.02,0 -1.85,-0.98 -1.85,-2s0.83,-1.75 1.85,-1.75L14.5,2c-1.85,0 -3.35,1.5 -3.35,3.35s1.5,3.35 3.35,3.35zM17,15.93L17,13h-2.93z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_smoking_rooms_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_smoking_rooms_black_24dp.xml
new file mode 100644
index 0000000..9a32e88
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_smoking_rooms_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,16h15v3L2,19zM20.5,16L22,16v3h-1.5zM18,16h1.5v3L18,19zM18.85,7.73c0.62,-0.61 1,-1.45 1,-2.38C19.85,3.5 18.35,2 16.5,2v1.5c1.02,0 1.85,0.83 1.85,1.85S17.52,7.2 16.5,7.2v1.5c2.24,0 4,1.83 4,4.07L20.5,15L22,15v-2.24c0,-2.22 -1.28,-4.14 -3.15,-5.03zM16.03,10.2L14.5,10.2c-1.02,0 -1.85,-0.98 -1.85,-2s0.83,-1.75 1.85,-1.75v-1.5c-1.85,0 -3.35,1.5 -3.35,3.35s1.5,3.35 3.35,3.35h1.53c1.05,0 1.97,0.74 1.97,2.05L18,15h1.5v-1.64c0,-1.81 -1.6,-3.16 -3.47,-3.16z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/places/ic_spa_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/places/ic_spa_black_24dp.xml
new file mode 100644
index 0000000..cff1f2d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/places/ic_spa_black_24dp.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M8.55,12c-1.07,-0.71 -2.25,-1.27 -3.53,-1.61 1.28,0.34 2.46,0.9 3.53,1.61zM18.98,10.39c-1.29,0.34 -2.49,0.91 -3.57,1.64 1.08,-0.73 2.28,-1.3 3.57,-1.64z"
+ android:fillColor="#607D8B"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.49,9.63c-0.18,-2.79 -1.31,-5.51 -3.43,-7.63 -2.14,2.14 -3.32,4.86 -3.55,7.63 1.28,0.68 2.46,1.56 3.49,2.63 1.03,-1.06 2.21,-1.94 3.49,-2.63zM8.99,12.28c-0.14,-0.1 -0.3,-0.19 -0.45,-0.29 0.15,0.11 0.31,0.19 0.45,0.29zM15.41,12.03c-0.13,0.09 -0.27,0.16 -0.4,0.26 0.13,-0.1 0.27,-0.17 0.4,-0.26zM12,15.45C9.85,12.17 6.18,10 2,10c0,5.32 3.36,9.82 8.03,11.49 0.63,0.23 1.29,0.4 1.97,0.51 0.68,-0.12 1.33,-0.29 1.97,-0.51C18.64,19.82 22,15.32 22,10c-4.18,0 -7.85,2.17 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_cake_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_cake_black_24dp.xml
new file mode 100644
index 0000000..ac0f504
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_cake_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,6c1.11,0 2,-0.9 2,-2 0,-0.38 -0.1,-0.73 -0.29,-1.03L12,0l-1.71,2.97c-0.19,0.3 -0.29,0.65 -0.29,1.03 0,1.1 0.9,2 2,2zM16.6,15.99l-1.07,-1.07 -1.08,1.07c-1.3,1.3 -3.58,1.31 -4.89,0l-1.07,-1.07 -1.09,1.07C6.75,16.64 5.88,17 4.96,17c-0.73,0 -1.4,-0.23 -1.96,-0.61L3,21c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-4.61c-0.56,0.38 -1.23,0.61 -1.96,0.61 -0.92,0 -1.79,-0.36 -2.44,-1.01zM18,9h-5L13,7h-2v2L6,9c-1.66,0 -3,1.34 -3,3v1.54c0,1.08 0.88,1.96 1.96,1.96 0.52,0 1. [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_domain_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_domain_black_24dp.xml
new file mode 100644
index 0000000..8924cc8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_domain_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,7L12,3L2,3v18h20L22,7L12,7zM6,19L4,19v-2h2v2zM6,15L4,15v-2h2v2zM6,11L4,11L4,9h2v2zM6,7L4,7L4,5h2v2zM10,19L8,19v-2h2v2zM10,15L8,15v-2h2v2zM10,11L8,11L8,9h2v2zM10,7L8,7L8,5h2v2zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2L12,9h8v10zM18,11h-2v2h2v-2zM18,15h-2v2h2v-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_group_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_group_add_black_24dp.xml
new file mode 100644
index 0000000..b3a2fb3
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_group_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,10L5,10L5,7L3,7v3L0,10v2h3v3h2v-3h3v-2zM18,11c1.66,0 2.99,-1.34 2.99,-3S19.66,5 18,5c-0.32,0 -0.63,0.05 -0.91,0.14 0.57,0.81 0.9,1.79 0.9,2.86s-0.34,2.04 -0.9,2.86c0.28,0.09 0.59,0.14 0.91,0.14zM13,11c1.66,0 2.99,-1.34 2.99,-3S14.66,5 13,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM19.62,13.16c0.83,0.73 1.38,1.66 1.38,2.84v2h3v-2c0,-1.54 -2.37,-2.49 -4.38,-2.84zM13,13c-2,0 -6,1 -6,3v2h12v-2c0,-2 -4,-3 -6,-3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_group_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_group_black_24dp.xml
new file mode 100644
index 0000000..4cfd869
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_group_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_location_city_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_location_city_black_24dp.xml
new file mode 100644
index 0000000..a7c688f
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_location_city_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,11L15,5l-3,-3 -3,3v2L3,7v14h18L21,11h-6zM7,19L5,19v-2h2v2zM7,15L5,15v-2h2v2zM7,11L5,11L5,9h2v2zM13,19h-2v-2h2v2zM13,15h-2v-2h2v2zM13,11h-2L11,9h2v2zM13,7h-2L11,5h2v2zM19,19h-2v-2h2v2zM19,15h-2v-2h2v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_bad_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_bad_black_24dp.xml
new file mode 100644
index 0000000..d511379
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_bad_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,14c-2.33,0 -4.31,1.46 -5.11,3.5h10.22c-0.8,-2.04 -2.78,-3.5 -5.11,-3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_black_24dp.xml
new file mode 100644
index 0000000..43d5552
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_active_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_active_black_24dp.xml
new file mode 100644
index 0000000..be9f836
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_active_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_black_24dp.xml
new file mode 100644
index 0000000..7009a67
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_none_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_none_black_24dp.xml
new file mode 100644
index 0000000..a4543fd
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_none_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_off_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_off_black_24dp.xml
new file mode 100644
index 0000000..415f558
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_off_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18.69L7.84,6.14 5.27,3.49 4,4.76l2.8,2.8v0.01c-0.52,0.99 -0.8,2.16 -0.8,3.42v5l-2,2v1h13.73l2,2L21,19.72l-1,-1.03zM12,22c1.11,0 2,-0.89 2,-2h-4c0,1.11 0.89,2 2,2zM18,14.68L18,11c0,-3.08 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68c-0.15,0.03 -0.29,0.08 -0.42,0.12 -0.1,0.03 -0.2,0.07 -0.3,0.11h-0.01c-0.01,0 -0.01,0 -0.02,0.01 -0.23,0.09 -0.46,0.2 -0.68,0.31 0,0 -0.01,0 -0.01,0.01L18,14.68z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_paused_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_paused_black_24dp.xml
new file mode 100644
index 0000000..198e53a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_paused_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.93 6,11v5l-2,2v1h16v-1l-2,-2zM14.5,9.8l-2.8,3.4h2.8L14.5,15h-5v-1.8l2.8,-3.4L9.5,9.8L9.5,8h5v1.8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_pages_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_pages_black_24dp.xml
new file mode 100644
index 0000000..53ac64e
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_pages_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5v6h5L7,7l4,1L11,3L5,3c-1.1,0 -2,0.9 -2,2zM8,13L3,13v6c0,1.1 0.9,2 2,2h6v-5l-4,1 1,-4zM17,17l-4,-1v5h6c1.1,0 2,-0.9 2,-2v-6h-5l1,4zM19,3h-6v5l4,-1 -1,4h5L21,5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_party_mode_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_party_mode_black_24dp.xml
new file mode 100644
index 0000000..0dde9b9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_party_mode_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,4h-3.17L15,2L9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,7c1.63,0 3.06,0.79 3.98,2L12,9c-1.66,0 -3,1.34 -3,3 0,0.35 0.07,0.69 0.18,1L7.1,13c-0.06,-0.32 -0.1,-0.66 -0.1,-1 0,-2.76 2.24,-5 5,-5zM12,17c-1.63,0 -3.06,-0.79 -3.98,-2L12,15c1.66,0 3,-1.34 3,-3 0,-0.35 -0.07,-0.69 -0.18,-1h2.08c0.07,0.32 0.1,0.66 0.1,1 0,2.76 -2.24,5 -5,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_people_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_people_black_24dp.xml
new file mode 100644
index 0000000..4cfd869
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_people_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_people_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_people_outline_black_24dp.xml
new file mode 100644
index 0000000..15d27f9
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_people_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,13c-1.2,0 -3.07,0.34 -4.5,1 -1.43,-0.67 -3.3,-1 -4.5,-1C5.33,13 1,14.08 1,16.25L1,19h22v-2.75c0,-2.17 -4.33,-3.25 -6.5,-3.25zM12.5,17.5h-10v-1.25c0,-0.54 2.56,-1.75 5,-1.75s5,1.21 5,1.75v1.25zM21.5,17.5L14,17.5v-1.25c0,-0.46 -0.2,-0.86 -0.52,-1.22 0.88,-0.3 1.96,-0.53 3.02,-0.53 2.44,0 5,1.21 5,1.75v1.25zM7.5,12c1.93,0 3.5,-1.57 3.5,-3.5S9.43,5 7.5,5 4,6.57 4,8.5 5.57,12 7.5,12zM7.5,6.5c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM16.5,12c1.93,0 [...]
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_person_add_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_person_add_black_24dp.xml
new file mode 100644
index 0000000..225ae0a
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_person_add_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM6,10L6,7L4,7v3L1,10v2h3v3h2v-3h3v-2L6,10zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_person_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_person_black_24dp.xml
new file mode 100644
index 0000000..b2cb337
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_person_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_person_outline_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_person_outline_black_24dp.xml
new file mode 100644
index 0000000..f182b8d
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_person_outline_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_plus_one_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_plus_one_black_24dp.xml
new file mode 100644
index 0000000..3bd2f2b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_plus_one_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,8L8,8v4L4,12v2h4v4h2v-4h4v-2h-4zM14.5,6.08L14.5,7.9l2.5,-0.5L17,18h2L19,5z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_poll_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_poll_black_24dp.xml
new file mode 100644
index 0000000..75d0dde
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_poll_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_public_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_public_black_24dp.xml
new file mode 100644
index 0000000..d976b42
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_public_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_school_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_school_black_24dp.xml
new file mode 100644
index 0000000..30d83f8
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_school_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5,13.18v4L12,21l7,-3.82v-4L12,17l-7,-3.82zM12,3L1,9l11,6 9,-4.91V17h2V9L12,3z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_share_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_share_black_24dp.xml
new file mode 100644
index 0000000..e3fe874
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_share_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/social/ic_whatshot_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/social/ic_whatshot_black_24dp.xml
new file mode 100644
index 0000000..1cbc037
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/social/ic_whatshot_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13.5,0.67s0.74,2.65 0.74,4.8c0,2.06 -1.35,3.73 -3.41,3.73 -2.07,0 -3.63,-1.67 -3.63,-3.73l0.03,-0.36C5.21,7.51 4,10.62 4,14c0,4.42 3.58,8 8,8s8,-3.58 8,-8C20,8.61 17.41,3.8 13.5,0.67zM11.71,19c-1.78,0 -3.22,-1.4 -3.22,-3.14 0,-1.62 1.05,-2.76 2.81,-3.12 1.77,-0.36 3.6,-1.21 4.62,-2.58 0.39,1.29 0.59,2.65 0.59,4.04 0,2.65 -2.15,4.8 -4.8,4.8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_black_24dp.xml
new file mode 100644
index 0000000..9948171
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_outline_blank_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_outline_blank_black_24dp.xml
new file mode 100644
index 0000000..cf8bfa2
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_outline_blank_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_indeterminate_check_box_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_indeterminate_check_box_black_24dp.xml
new file mode 100644
index 0000000..77865a6
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_indeterminate_check_box_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM17,13L7,13v-2h10v2z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_checked_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_checked_black_24dp.xml
new file mode 100644
index 0000000..a5025ae
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_checked_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_unchecked_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_unchecked_black_24dp.xml
new file mode 100644
index 0000000..f61549b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_unchecked_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_black_24dp.xml
new file mode 100644
index 0000000..a87ca09
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_border_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_border_black_24dp.xml
new file mode 100644
index 0000000..b36536b
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_border_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
diff --git a/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_half_black_24dp.xml b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_half_black_24dp.xml
new file mode 100644
index 0000000..8274dc0
--- /dev/null
+++ b/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_half_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4V6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
diff --git a/base/asset-studio/src/main/java/images/notification_stencil/hdpi.png b/asset-studio/src/main/java/images/notification_stencil/hdpi.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/notification_stencil/hdpi.png
rename to asset-studio/src/main/java/images/notification_stencil/hdpi.png
diff --git a/base/asset-studio/src/main/java/images/notification_stencil/mdpi.png b/asset-studio/src/main/java/images/notification_stencil/mdpi.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/notification_stencil/mdpi.png
rename to asset-studio/src/main/java/images/notification_stencil/mdpi.png
diff --git a/base/asset-studio/src/main/java/images/notification_stencil/xhdpi.png b/asset-studio/src/main/java/images/notification_stencil/xhdpi.png
similarity index 100%
rename from base/asset-studio/src/main/java/images/notification_stencil/xhdpi.png
rename to asset-studio/src/main/java/images/notification_stencil/xhdpi.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java
rename to asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/BitmapGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/BitmapGeneratorTest.java
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/BitmapGeneratorTest.java
rename to asset-studio/src/test/java/com/android/assetstudiolib/BitmapGeneratorTest.java
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java
rename to asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/LauncherIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/LauncherIconGeneratorTest.java
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/LauncherIconGeneratorTest.java
rename to asset-studio/src/test/java/com/android/assetstudiolib/LauncherIconGeneratorTest.java
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java
rename to asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java
rename to asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java
rename to asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_dark.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_dark.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_dark.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_dark.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_light.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_light.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_light.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-hdpi/ic_action_light.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_dark.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_dark.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_dark.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_dark.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_light.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_light.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_light.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-mdpi/ic_action_light.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_dark.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_dark.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_dark.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_dark.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_light.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_light.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_light.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xhdpi/ic_action_light.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_dark.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_dark.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_dark.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_dark.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_light.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_light.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_light.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_light.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/red_simple_circle-web.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/red_simple_circle-web.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/red_simple_circle-web.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/red_simple_circle-web.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-hdpi/red_simple_circle.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-hdpi/red_simple_circle.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-hdpi/red_simple_circle.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-hdpi/red_simple_circle.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-mdpi/red_simple_circle.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-mdpi/red_simple_circle.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-mdpi/red_simple_circle.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-mdpi/red_simple_circle.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xhdpi/red_simple_circle.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xhdpi/red_simple_circle.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xhdpi/red_simple_circle.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xhdpi/red_simple_circle.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxhdpi/red_simple_circle.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxhdpi/red_simple_circle.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxhdpi/red_simple_circle.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxhdpi/red_simple_circle.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxxhdpi/red_simple_circle.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxxhdpi/red_simple_circle.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxxhdpi/red_simple_circle.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/launcher/res/mipmap-xxxhdpi/red_simple_circle.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-hdpi/ic_menu_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-hdpi/ic_menu_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-hdpi/ic_menu_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-hdpi/ic_menu_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-mdpi/ic_menu_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-mdpi/ic_menu_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-mdpi/ic_menu_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-mdpi/ic_menu_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xhdpi/ic_menu_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xhdpi/ic_menu_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xhdpi/ic_menu_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xhdpi/ic_menu_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xxhdpi/ic_menu_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xxhdpi/ic_menu_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xxhdpi/ic_menu_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xxhdpi/ic_menu_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-hdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-hdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-hdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-hdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-mdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-mdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-mdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-mdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xhdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xhdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xhdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xxhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xxhdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xxhdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xxhdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-hdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-mdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xhdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v9/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v9/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v9/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi-v9/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-hdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v9/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v9/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v9/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi-v9/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-mdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v9/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v9/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v9/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi-v9/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xhdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v11/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v11/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v11/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v9/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v9/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v9/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v9/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi/ic_stat_1.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi/ic_stat_1.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi/ic_stat_1.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-hdpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-ldpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-mdpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xhdpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_dark.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_dark.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_dark.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_dark.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_light.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_light.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_light.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_light.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi-v5/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-hdpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi-v5/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-mdpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi-v5/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xhdpi/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_unselected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_selected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_selected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_selected.png
diff --git a/base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_unselected.png
similarity index 100%
rename from base/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_unselected.png
rename to asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_unselected.png
diff --git a/base/.eclipse/dictionary.txt b/base/.eclipse/dictionary.txt
deleted file mode 100644
index e42c75d..0000000
--- a/base/.eclipse/dictionary.txt
+++ /dev/null
@@ -1,407 +0,0 @@
-aapt
-accessor
-accessors
-adb
-addon
-adt
-aidl
-alt
-android
-anim
-antialias
-antialiased
-aosp
-apache
-api
-apk
-app
-apps
-arg
-async
-attr
-attrs
-avd
-avds
-backfill
-backport
-backported
-basename
-basenames
-bitmask
-blockquote
-borderless
-breadcrumb
-builtin
-bytecode
-cairo
-callback
-callbacks
-carlo
-cf
-changeset
-charset
-charsets
-checkbox
-checkboxes
-classloader
-classpath
-classpathentry
-clickable
-clinit
-clipart
-clipboard
-clipboards
-closeables
-clueless
-codebase
-codename
-codenames
-colspan
-combo
-combobox
-combos
-compositor
-config
-configs
-configurability
-congrats
-coords
-credentials
-cyclical
-dalvik
-ddms
-deactivated
-debuggable
-dedent
-defstyle
-deprecated
-deselect
-deselects
-designtime
-dev
-dex
-dexified
-diff
-diffs
-dimen
-dir
-dirs
-discoverable
-ditto
-div
-docs
-dp
-dpi
-drawable
-drawables
-dropdown
-easymock
-ed
-editable
-em
-endian
-endpoint
-enqueued
-enum
-enums
-env
-equidistant
-exec
-fallback
-filenames
-flux
-flyout
-foo
-foreach
-fqcn
-framelayout
-froyo
-gen
-gif
-git
-glob
-globbing
-gradle
-groovy
-guava
-guide's
-guillemets
-hardcode
-hardcoded
-hardcodes
-hardcoding
-hdpi
-helvetica
-highlighter
-holo
-hotfix
-href
-html
-http
-hyperlink
-hyperlinks
-ids
-ie
-iff
-img
-inflater
-infos
-init
-inits
-inline
-inlined
-innerclass
-inset
-insn
-instanceof
-instantiatable
-int
-inter
-interpolator
-interpolators
-iterable
-javac
-javadoc
-jpeg
-jpg
-junit
-keystore
-landroid
-layoutlib
-layoutopt
-ldpi
-leaky
-levenshtein
-lexing
-lib
-licensable
-licensor
-lifecycle
-linebreaks
-linestyle
-linkable
-linux
-ljava
-locale
-locales
-logo
-lombok
-lopsided
-lowercase
-luminance
-mac
-macs
-makefile
-malformed
-markup
-marquee
-maven
-mdpi
-memento
-metadata
-micro
-min
-mipmap
-misaligned
-monospace
-monte
-ms
-msg
-multi
-multimap
-multimaps
-multipart
-namespace
-namespaces
-nano
-newfound
-nexus
-ninepatch
-nodpi
-nulling
-num
-objectweb
-ok
-op
-opcode
-opcodes
-os
-palette
-param
-params
-parented
-pings
-placeholder
-placeholders
-plugin
-plugin's
-plugins
-png
-popup
-popups
-pre
-precompiler
-precompute
-precomputing
-pref
-prefs
-preload
-preloaded
-preloads
-prepending
-primordial
-printf
-pristine
-programmatic
-programmatically
-proguard
-propertysheet
-proxies
-proxy
-pulldown
-px
-quickfix
-quickstart
-recompilation
-rect
-recurse
-redo
-refactor
-refactored
-refactoring
-refactorings
-regex
-regexp
-regexps
-registry
-reindent
-reindenting
-remap
-renderable
-reparse
-reparses
-rescales
-residual
-resilient
-resizability
-resizable
-rev
-risky
-rollback
-rowspan
-rowspans
-sans
-scrollable
-scrollbar
-scrollbars
-scrollview
-scrubbing
-sdcard
-sdk
-sdklib
-sdks
-se
-searchable
-selectable
-semi
-serializer
-settable
-severities
-snip
-spec
-sqrt
-src
-standalone
-stash
-stat
-stateful
-stateless
-stderr
-stdout
-stretchable
-stretchiness
-struct
-styleable
-styleables
-stylesheet
-subclassed
-subclassing
-subfolder
-submenu
-submenus
-subregion
-superclasses
-superset
-supertype
-symlinks
-synced
-syncs
-synthetically
-tabbed
-tad
-temp
-templated
-testroot
-textfield
-textfields
-thematically
-themed
-thumbnail
-thumbnails
-timestamp
-timestamps
-tmp
-toolbar
-toolbars
-tooltip
-tooltips
-traceview
-translucency
-trig
-txt
-typo
-ui
-unary
-uncomment
-undescribed
-undoable
-unfiltered
-unformatted
-unhide
-unicode
-uninstall
-uninstallation
-uninstalling
-unlocalized
-unset
-unweighted
-upcoming
-uppercase
-uri
-url
-urls
-utf
-validator
-validators
-varargs
-verbosity
-viewport
-vs
-wakelock
-wakelocks
-wallpaper
-webkit
-webtools
-whilst
-wikipedia
-wildcard
-workflow
-writeable
-xdpi
-xhdpi
-xlarge
-xml
-xmlns
-ydpi
-zip
-zipalign
diff --git a/base/.gitignore b/base/.gitignore
deleted file mode 100644
index 0cbe9c4..0000000
--- a/base/.gitignore
+++ /dev/null
@@ -1,17 +0,0 @@
-*~
-*.bak
-*.pyc
-Thumbs.db
-*.class
-*.DS_Store
-.gradle
-cygdrive
-/build
-/out
-/repo
-.idea/workspace.xml
-.idea/dictionaries/tnorbye.xml
-misc/distrib_plugins/build
-misc/distrib_plugins/buildSrc/build
-misc/distrib_plugins/buildSrc/out
-misc/distrib_plugins/buildSrc/buildSrc.i*
\ No newline at end of file
diff --git a/base/.gradle/2.9/taskArtifacts/cache.properties b/base/.gradle/2.9/taskArtifacts/cache.properties
deleted file mode 100644
index 435559d..0000000
--- a/base/.gradle/2.9/taskArtifacts/cache.properties
+++ /dev/null
@@ -1 +0,0 @@
-#Wed Jan 20 15:44:32 CET 2016
diff --git a/base/.gradle/2.9/taskArtifacts/cache.properties.lock b/base/.gradle/2.9/taskArtifacts/cache.properties.lock
deleted file mode 100644
index 5c79d90..0000000
Binary files a/base/.gradle/2.9/taskArtifacts/cache.properties.lock and /dev/null differ
diff --git a/base/.gradle/2.9/taskArtifacts/fileHashes.bin b/base/.gradle/2.9/taskArtifacts/fileHashes.bin
deleted file mode 100644
index 4802381..0000000
Binary files a/base/.gradle/2.9/taskArtifacts/fileHashes.bin and /dev/null differ
diff --git a/base/.gradle/2.9/taskArtifacts/fileSnapshots.bin b/base/.gradle/2.9/taskArtifacts/fileSnapshots.bin
deleted file mode 100644
index 5714310..0000000
Binary files a/base/.gradle/2.9/taskArtifacts/fileSnapshots.bin and /dev/null differ
diff --git a/base/.gradle/2.9/taskArtifacts/outputFileStates.bin b/base/.gradle/2.9/taskArtifacts/outputFileStates.bin
deleted file mode 100644
index 4390e05..0000000
Binary files a/base/.gradle/2.9/taskArtifacts/outputFileStates.bin and /dev/null differ
diff --git a/base/.gradle/2.9/taskArtifacts/taskArtifacts.bin b/base/.gradle/2.9/taskArtifacts/taskArtifacts.bin
deleted file mode 100644
index b4ad283..0000000
Binary files a/base/.gradle/2.9/taskArtifacts/taskArtifacts.bin and /dev/null differ
diff --git a/base/.idea/.name b/base/.idea/.name
deleted file mode 100644
index a0d39d8..0000000
--- a/base/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-adt
\ No newline at end of file
diff --git a/base/.idea/ant.xml b/base/.idea/ant.xml
deleted file mode 100644
index f6e673a..0000000
--- a/base/.idea/ant.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4" />
-
diff --git a/base/.idea/codeStyleSettings.xml b/base/.idea/codeStyleSettings.xml
deleted file mode 100755
index 67bbdc4..0000000
--- a/base/.idea/codeStyleSettings.xml
+++ /dev/null
@@ -1,151 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ProjectCodeStyleSettingsManager">
- <option name="PER_PROJECT_SETTINGS">
- <value>
- <option name="LINE_SEPARATOR" value="
" />
- <option name="FIELD_NAME_PREFIX" value="m" />
- <option name="STATIC_FIELD_NAME_PREFIX" value="m" />
- <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
- <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
- <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
- <value />
- </option>
- <option name="IMPORT_LAYOUT_TABLE">
- <value>
- <package name="" withSubpackages="true" static="true" />
- <emptyLine />
- <package name="com" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="junit" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="net" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="org" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="android" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="java" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="javax" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="" withSubpackages="true" static="false" />
- <emptyLine />
- </value>
- </option>
- <option name="RIGHT_MARGIN" value="100" />
- <option name="JD_P_AT_EMPTY_LINES" value="false" />
- <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
- <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
- <option name="JD_KEEP_EMPTY_RETURN" value="false" />
- <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
- <option name="BLANK_LINES_AROUND_FIELD" value="1" />
- <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
- <option name="ALIGN_MULTILINE_FOR" value="false" />
- <option name="CALL_PARAMETERS_WRAP" value="1" />
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
- <option name="EXTENDS_LIST_WRAP" value="1" />
- <option name="THROWS_LIST_WRAP" value="1" />
- <option name="EXTENDS_KEYWORD_WRAP" value="1" />
- <option name="THROWS_KEYWORD_WRAP" value="1" />
- <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
- <option name="BINARY_OPERATION_WRAP" value="1" />
- <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
- <option name="TERNARY_OPERATION_WRAP" value="1" />
- <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
- <option name="FOR_STATEMENT_WRAP" value="1" />
- <option name="ARRAY_INITIALIZER_WRAP" value="1" />
- <option name="ASSIGNMENT_WRAP" value="1" />
- <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
- <option name="WRAP_COMMENTS" value="true" />
- <option name="IF_BRACE_FORCE" value="3" />
- <option name="DOWHILE_BRACE_FORCE" value="3" />
- <option name="WHILE_BRACE_FORCE" value="3" />
- <option name="FOR_BRACE_FORCE" value="3" />
- <GroovyCodeStyleSettings>
- <option name="USE_FQ_CLASS_NAMES_IN_JAVADOC" value="false" />
- <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
- <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
- <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
- <value />
- </option>
- <option name="IMPORT_LAYOUT_TABLE">
- <value>
- <package name="" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="javax" withSubpackages="true" static="false" />
- <package name="java" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="" withSubpackages="true" static="true" />
- <emptyLine />
- </value>
- </option>
- </GroovyCodeStyleSettings>
- <XML>
- <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
- </XML>
- <codeStyleSettings language="Groovy">
- <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
- <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
- <option name="BLANK_LINES_AROUND_FIELD" value="1" />
- <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
- <option name="ALIGN_MULTILINE_FOR" value="false" />
- <option name="CALL_PARAMETERS_WRAP" value="1" />
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
- <option name="EXTENDS_LIST_WRAP" value="1" />
- <option name="THROWS_LIST_WRAP" value="1" />
- <option name="EXTENDS_KEYWORD_WRAP" value="1" />
- <option name="THROWS_KEYWORD_WRAP" value="1" />
- <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
- <option name="BINARY_OPERATION_WRAP" value="1" />
- <option name="TERNARY_OPERATION_WRAP" value="1" />
- <option name="FOR_STATEMENT_WRAP" value="1" />
- <option name="ASSIGNMENT_WRAP" value="1" />
- <option name="IF_BRACE_FORCE" value="3" />
- <option name="WHILE_BRACE_FORCE" value="3" />
- <option name="FOR_BRACE_FORCE" value="3" />
- <option name="PARENT_SETTINGS_INSTALLED" value="true" />
- </codeStyleSettings>
- <codeStyleSettings language="JAVA">
- <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
- <option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
- <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
- <option name="BLANK_LINES_AROUND_FIELD" value="1" />
- <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
- <option name="ALIGN_MULTILINE_FOR" value="false" />
- <option name="CALL_PARAMETERS_WRAP" value="1" />
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
- <option name="EXTENDS_LIST_WRAP" value="1" />
- <option name="THROWS_LIST_WRAP" value="1" />
- <option name="EXTENDS_KEYWORD_WRAP" value="1" />
- <option name="THROWS_KEYWORD_WRAP" value="1" />
- <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
- <option name="BINARY_OPERATION_WRAP" value="1" />
- <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
- <option name="TERNARY_OPERATION_WRAP" value="1" />
- <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
- <option name="FOR_STATEMENT_WRAP" value="1" />
- <option name="ARRAY_INITIALIZER_WRAP" value="1" />
- <option name="ASSIGNMENT_WRAP" value="1" />
- <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
- <option name="IF_BRACE_FORCE" value="3" />
- <option name="DOWHILE_BRACE_FORCE" value="3" />
- <option name="WHILE_BRACE_FORCE" value="3" />
- <option name="FOR_BRACE_FORCE" value="3" />
- <option name="PARENT_SETTINGS_INSTALLED" value="true" />
- </codeStyleSettings>
- <codeStyleSettings language="JSON">
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
- <option name="PARENT_SETTINGS_INSTALLED" value="true" />
- </codeStyleSettings>
- </value>
- </option>
- <option name="USE_PER_PROJECT_SETTINGS" value="true" />
- </component>
-</project>
\ No newline at end of file
diff --git a/base/.idea/compiler.xml b/base/.idea/compiler.xml
deleted file mode 100644
index dd73d93..0000000
--- a/base/.idea/compiler.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="CompilerConfiguration">
- <option name="DEFAULT_COMPILER" value="Javac" />
- <resourceExtensions />
- <wildcardResourcePatterns>
- <entry name="?*.properties" />
- <entry name="?*.xml" />
- <entry name="?*.gif" />
- <entry name="?*.png" />
- <entry name="?*.jpeg" />
- <entry name="?*.jpg" />
- <entry name="?*.html" />
- <entry name="?*.dtd" />
- <entry name="?*.tld" />
- <entry name="?*.ftl" />
- <entry name="?*.xsd" />
- <entry name="?*.css" />
- <entry name="?*.trace" />
- </wildcardResourcePatterns>
- <annotationProcessing>
- <profile default="true" name="Default" enabled="false">
- <processorPath useClasspath="true" />
- </profile>
- </annotationProcessing>
- </component>
-</project>
-
diff --git a/base/.idea/copyright/aosp.xml b/base/.idea/copyright/aosp.xml
deleted file mode 100644
index 077aec6..0000000
--- a/base/.idea/copyright/aosp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="CopyrightManager">
- <copyright>
- <option name="notice" value="Copyright (C) $today.year 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 [...]
- <option name="keyword" value="Copyright" />
- <option name="allowReplaceKeyword" value="" />
- <option name="myName" value="aosp" />
- <option name="myLocal" value="true" />
- </copyright>
-</component>
\ No newline at end of file
diff --git a/base/.idea/copyright/profiles_settings.xml b/base/.idea/copyright/profiles_settings.xml
deleted file mode 100644
index fc5a2c3..0000000
--- a/base/.idea/copyright/profiles_settings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<component name="CopyrightManager">
- <settings default="">
- <module2copyright>
- <element module="All" copyright="aosp" />
- </module2copyright>
- </settings>
-</component>
\ No newline at end of file
diff --git a/base/.idea/dictionaries/adt.xml b/base/.idea/dictionaries/adt.xml
deleted file mode 100644
index ecf4830..0000000
--- a/base/.idea/dictionaries/adt.xml
+++ /dev/null
@@ -1,319 +0,0 @@
-<component name="ProjectDictionaryState">
- <dictionary name="adt">
- <words>
- <w>&</w>
- <w>aapt</w>
- <w>aarrggbb</w>
- <w>abis</w>
- <w>accessors</w>
- <w>actionbar</w>
- <w>adb's</w>
- <w>adbhost</w>
- <w>addon</w>
- <w>addons</w>
- <w>addr</w>
- <w>aidl</w>
- <w>aload</w>
- <w>androiddebugkey</w>
- <w>anim</w>
- <w>aosp</w>
- <w>apkbuilder</w>
- <w>apos</w>
- <w>appname</w>
- <w>areturn</w>
- <w>argb</w>
- <w>argv</w>
- <w>armeabi</w>
- <w>ast's</w>
- <w>async</w>
- <w>attrs</w>
- <w>autofocus</w>
- <w>automator</w>
- <w>avd's</w>
- <w>avdname</w>
- <w>backfill</w>
- <w>basename</w>
- <w>basenames</w>
- <w>bicubic</w>
- <w>bilinear</w>
- <w>bindir</w>
- <w>bipush</w>
- <w>bitcodelib</w>
- <w>bitmask</w>
- <w>blockquote</w>
- <w>bluetooth</w>
- <w>booleans</w>
- <w>bootloader</w>
- <w>borderless</w>
- <w>bytecode</w>
- <w>calloc</w>
- <w>charsets</w>
- <w>checkable</w>
- <w>chksum</w>
- <w>classfile</w>
- <w>classname</w>
- <w>classpathentry</w>
- <w>clinit</w>
- <w>clipart</w>
- <w>closeables</w>
- <w>codenames</w>
- <w>combineaccessrules</w>
- <w>compat</w>
- <w>configurables</w>
- <w>conv</w>
- <w>crlf</w>
- <w>crypto</w>
- <w>cygpath</w>
- <w>cygwin</w>
- <w>dalvik</w>
- <w>ddmlib</w>
- <w>ddms</w>
- <w>debuggable</w>
- <w>densityless</w>
- <w>designtime</w>
- <w>dest</w>
- <w>dexdump</w>
- <w>digester</w>
- <w>dimen</w>
- <w>dirname</w>
- <w>disp</w>
- <w>donottranslate</w>
- <w>dpad</w>
- <w>drawables</w>
- <w>dropdown</w>
- <w>dumpsys</w>
- <w>eabi</w>
- <w>easymock</w>
- <w>emdash</w>
- <w>endian</w>
- <w>enqueued</w>
- <w>errno</w>
- <w>executables</w>
- <w>expectedly</w>
- <w>filenames</w>
- <w>filepath</w>
- <w>fixme</w>
- <w>fqcn</w>
- <w>framebuffer</w>
- <w>frameworkdir</w>
- <w>froyo</w>
- <w>fwvga</w>
- <w>gbdserver</w>
- <w>gdbserver</w>
- <w>genkey</w>
- <w>getenv</w>
- <w>getfield</w>
- <w>getprop</w>
- <w>getstatic</w>
- <w>globbing</w>
- <w>goto</w>
- <w>gradle</w>
- <w>guide's</w>
- <w>guillemets</w>
- <w>hardcode</w>
- <w>hardcoding</w>
- <w>hashcode</w>
- <w>hdpi</w>
- <w>holo</w>
- <w>horiz</w>
- <w>httpclient</w>
- <w>hvga</w>
- <w>iaload</w>
- <w>iastore</w>
- <w>iconst</w>
- <w>inflater</w>
- <w>infos</w>
- <w>inlined</w>
- <w>innerclass</w>
- <w>inputfile</w>
- <w>insn</w>
- <w>instantiatable</w>
- <w>instrumentations</w>
- <w>interpolator</w>
- <w>ints</w>
- <w>invokestatic</w>
- <w>invokevirtual</w>
- <w>ireturn</w>
- <w>iterable</w>
- <w>jarfile</w>
- <w>jarlist</w>
- <w>jarpath</w>
- <w>javabuilder</w>
- <w>javanature</w>
- <w>jdwp</w>
- <w>jpeg</w>
- <w>junit</w>
- <w>keepclasseswithmembernames</w>
- <w>keepclasseswithmembers</w>
- <w>keygen</w>
- <w>keypass</w>
- <w>keysexposed</w>
- <w>keyshidden</w>
- <w>keyssoft</w>
- <w>keystore</w>
- <w>keytool</w>
- <w>kxml</w>
- <w>landroid</w>
- <w>layoublib</w>
- <w>layoutlib</w>
- <w>layoutlibs</w>
- <w>layoutopt</w>
- <w>layoutrules</w>
- <w>ldltr</w>
- <w>ldpi</w>
- <w>ldrtl</w>
- <w>levenshtein</w>
- <w>lexing</w>
- <w>libc</w>
- <w>licensable</w>
- <w>licensor</w>
- <w>linenumber</w>
- <w>ljava</w>
- <w>lombok</w>
- <w>lookups</w>
- <w>lookupswitch</w>
- <w>looper</w>
- <w>lpackage</w>
- <w>makefile</w>
- <w>malloc</w>
- <w>manifestmerger</w>
- <w>manifmerger</w>
- <w>mdpi</w>
- <w>memalign</w>
- <w>minsdk</w>
- <w>mipmap</w>
- <w>mkdirs</w>
- <w>monospace</w>
- <w>moreunit</w>
- <w>multipart</w>
- <w>multitouch</w>
- <w>nano</w>
- <w>nativelib</w>
- <w>navexposed</w>
- <w>navhidden</w>
- <w>ninepatch</w>
- <w>nodpi</w>
- <w>nokeys</w>
- <w>nonav</w>
- <w>notlong</w>
- <w>notnight</w>
- <w>notouch</w>
- <w>nullable</w>
- <w>nulling</w>
- <w>nullness</w>
- <w>objectweb</w>
- <w>opcode</w>
- <w>opcodes</w>
- <w>pathname</w>
- <w>picasa</w>
- <w>plugin's</w>
- <w>png's</w>
- <w>prebuilts</w>
- <w>prefs</w>
- <w>preload</w>
- <w>prepending</w>
- <w>printf</w>
- <w>println</w>
- <w>proc</w>
- <w>prog</w>
- <w>progdir</w>
- <w>programmatically</w>
- <w>proguard</w>
- <w>prolog</w>
- <w>qemu</w>
- <w>quickfix</w>
- <w>quickstart</w>
- <w>qvga</w>
- <w>rasterize</w>
- <w>rasterizer</w>
- <w>realloc</w>
- <w>recurse</w>
- <w>reflow</w>
- <w>reformats</w>
- <w>renderscript</w>
- <w>reparse</w>
- <w>resampling</w>
- <w>resizability</w>
- <w>resized</w>
- <w>rrggbb</w>
- <w>sccs</w>
- <w>screenshot</w>
- <w>scroller</w>
- <w>scrollview</w>
- <w>sdcard</w>
- <w>sdk's</w>
- <w>sdkbin</w>
- <w>sdklib</w>
- <w>sdkman</w>
- <w>sdks</w>
- <w>serialnumber</w>
- <w>severities</w>
- <w>sparsearray</w>
- <w>sqrt</w>
- <w>stacktrace</w>
- <w>stderr</w>
- <w>stdout</w>
- <w>stopship</w>
- <w>storepass</w>
- <w>storetype</w>
- <w>styleable</w>
- <w>subclassed</w>
- <w>subclassing</w>
- <w>subfolder</w>
- <w>submenu</w>
- <w>superset</w>
- <w>sysdir</w>
- <w>sysimg</w>
- <w>targetsdk</w>
- <w>testcase</w>
- <w>testroot</w>
- <w>testsuite</w>
- <w>tmpdir</w>
- <w>traceview</w>
- <w>tuple</w>
- <w>tvdpi</w>
- <w>uiautomator</w>
- <w>uname</w>
- <w>unarchive</w>
- <w>unary</w>
- <w>uncapitalized</w>
- <w>uncompressing</w>
- <w>unescape</w>
- <w>unescaping</w>
- <w>unformatted</w>
- <w>unlocalized</w>
- <w>unmanages</w>
- <w>unregister</w>
- <w>unregisters</w>
- <w>userdata</w>
- <w>versioncode</w>
- <w>vert</w>
- <w>vmsig</w>
- <w>waitable</w>
- <w>wakelock</w>
- <w>wakelocks</w>
- <w>wakeup</w>
- <w>webkit</w>
- <w>wikipedia</w>
- <w>workdir</w>
- <w>wqvga</w>
- <w>writeable</w>
- <w>wvga</w>
- <w>xdpi</w>
- <w>xerces</w>
- <w>xfermode</w>
- <w>xhdpi</w>
- <w>xhigh</w>
- <w>xlarge</w>
- <w>xliff</w>
- <w>xmlns</w>
- <w>xxhdpi</w>
- <w>xxhigh</w>
- <w>xxxhdpi</w>
- <w>xxxhigh</w>
- <w>ydpi</w>
- <w>yyyy</w>
- <w>zipalign</w>
- </words>
- </dictionary>
-</component>
diff --git a/base/.idea/encodings.xml b/base/.idea/encodings.xml
deleted file mode 100644
index e206d70..0000000
--- a/base/.idea/encodings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
-</project>
-
diff --git a/base/.idea/gradle.xml b/base/.idea/gradle.xml
deleted file mode 100644
index f6e673a..0000000
--- a/base/.idea/gradle.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4" />
-
diff --git a/base/.idea/inspectionProfiles/Project_Default.xml b/base/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 51d9659..0000000
--- a/base/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,213 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<component name="InspectionProjectProfileManager">
- <profile version="1.0" is_locked="false">
- <option name="myName" value="Project Default" />
- <option name="myLocal" value="false" />
- <inspection_tool class="AndroidDomInspection" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidElementNotAllowed" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintAdapterViewChildren" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintAlwaysShowAction" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintButtonCase" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintButtonOrder" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintDisableBaselineAlignment" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintDrawAllocation" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintDuplicateIds" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintDuplicateIncludedIds" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintEnforceUTF8" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintExportedService" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintExtraText" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintExtraTranslation" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintGifUsage" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintGrantAllUris" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintGridLayout" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintHardcodedDebugMode" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintHardcodedText" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintIconDensities" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintIconDipSize" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintIconDuplicates" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintIconDuplicatesConfig" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintIconLocation" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintIconMissingDensityFolder" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintIconNoDpi" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintInconsistentArrays" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintInefficientWeight" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintLibraryCustomView" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintManifestOrder" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintMergeRootFrame" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintMissingPrefix" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintMissingTranslation" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintMultipleUsesSdk" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintNestedScrolling" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintNestedWeights" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintObsoleteLayoutParam" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintOverdraw" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintPrivateResource" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintProguard" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintProguardSplitConfig" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintPxUsage" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintResourceAsColor" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintScrollViewCount" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintScrollViewSize" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintSdCardPath" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintSparseArray" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintStateListReachable" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintStringFormatCount" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintStringFormatInvalid" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintStringFormatMatches" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintStyleCycle" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintSuspiciousImport" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTextFields" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTextViewEdits" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTooDeepLayout" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTooManyViews" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTypographyDashes" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTypographyEllipsis" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTypographyFractions" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintTypographyOther" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUnknownId" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUnknownIdInLayout" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUnusedResources" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUseCompoundDrawables" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUseValueOf" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUselessLeaf" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUselessParent" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintUsesMinSdkAttributes" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintWorldWriteableFiles" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AndroidLintWrongViewCast" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidNonConstantResIdsInSwitch" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AndroidUnknownAttribute" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="AntDuplicateTargetsInspection" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AntMissingPropertiesFileInspection" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AntResolveInspection" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="BooleanMethodNameMustStartWithQuestion" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="ignoreBooleanMethods" value="false" />
- <option name="ignoreInAnnotationInterface" value="true" />
- <option name="onlyWarnOnBaseMethods" value="true" />
- <option name="questionString" value="is,can,has,should,could,will,shall,contains,equals,add,put,remove,startsWith,endsWith" />
- </inspection_tool>
- <inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="insideTryAllowed" value="false" />
- </inspection_tool>
- <inspection_tool class="CheckDtdRefs" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="CheckEmptyScriptTag" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="CheckTagEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="CheckValidXmlInScriptTagBody" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="CheckXmlFileWithXercesValidator" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="CovariantCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="CovariantEquals" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="DeprecatedClassUsageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="DivideByZero" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="DollarSignInName" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="FileEqualsUsage" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="GroovyAssignabilityCheck" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="GtkPreferredJComboBoxRenderer" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="HtmlExtraClosingTag" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="HtmlUnknownAttribute" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="myValues">
- <value>
- <list size="0" />
- </value>
- </option>
- <option name="myCustomValuesEnabled" value="true" />
- </inspection_tool>
- <inspection_tool class="HtmlUnknownTag" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="myValues">
- <value>
- <list size="6">
- <item index="0" class="java.lang.String" itemvalue="nobr" />
- <item index="1" class="java.lang.String" itemvalue="noembed" />
- <item index="2" class="java.lang.String" itemvalue="comment" />
- <item index="3" class="java.lang.String" itemvalue="noscript" />
- <item index="4" class="java.lang.String" itemvalue="embed" />
- <item index="5" class="java.lang.String" itemvalue="script" />
- </list>
- </value>
- </option>
- <option name="myCustomValuesEnabled" value="true" />
- </inspection_tool>
- <inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
- <option name="insideTryAllowed" value="false" />
- </inspection_tool>
- <inspection_tool class="InnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="InstanceVariableNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="m_regex" value="m[A-Z][A-Za-z\d]*" />
- <option name="m_minLength" value="3" />
- <option name="m_maxLength" value="32" />
- </inspection_tool>
- <inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="InstantiationOfUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="IteratorHasNextCallsIteratorNext" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="JavaLangImport" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="m_onlyPrivateOrFinal" value="false" />
- <option name="m_ignoreEmptyMethods" value="true" />
- </inspection_tool>
- <inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MissingOverrideAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
- <option name="ignoreObjectMethods" value="true" />
- <option name="ignoreAnonymousClassMethods" value="false" />
- </inspection_tool>
- <inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="m_requireAnnotationsFirst" value="true" />
- </inspection_tool>
- <inspection_tool class="MisspelledCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MisspelledEquals" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MisspelledHashcode" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MisspelledTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MisspelledToString" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="MultipleTypedDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="NonBooleanMethodNameMayNotStartWithQuestion" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="questionString" value="is,can,has,should,could,will,shall,contains,equals,startsWith,endsWith" />
- <option name="ignoreBooleanMethods" value="false" />
- <option name="onlyWarnOnBaseMethods" value="true" />
- </inspection_tool>
- <inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="PointlessArithmeticExpression" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="m_ignoreExpressionsContainingConstants" value="false" />
- </inspection_tool>
- <inspection_tool class="PointlessIndexOfComparison" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="PointlessNullCheck" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="RequiredAttributes" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="myAdditionalRequiredHtmlAttributes" value="" />
- </inspection_tool>
- <inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="StaticVariableNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="checkMutableFinals" value="false" />
- <option name="m_regex" value="s[A-Z][A-Za-z\d]*" />
- <option name="m_minLength" value="3" />
- <option name="m_maxLength" value="32" />
- </inspection_tool>
- <inspection_tool class="StringBufferField" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="TextLabelInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="UndesirableClassUsage" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="UnnecessaryInheritDoc" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="UnsafeVfsRecursion" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="UpperCaseFieldNameNotConstant" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
- <inspection_tool class="XmlDuplicatedId" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="XmlPathReference" enabled="false" level="ERROR" enabled_by_default="false" />
- <inspection_tool class="XmlUnboundNsPrefix" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="XmlUnusedNamespaceDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
- <inspection_tool class="XmlWrongRootElement" enabled="false" level="ERROR" enabled_by_default="false" />
- </profile>
-</component>
\ No newline at end of file
diff --git a/base/.idea/inspectionProfiles/profiles_settings.xml b/base/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 3b31283..0000000
--- a/base/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<component name="InspectionProjectProfileManager">
- <settings>
- <option name="PROJECT_PROFILE" value="Project Default" />
- <option name="USE_PROJECT_PROFILE" value="true" />
- <version value="1.0" />
- </settings>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/JUnit3.xml b/base/.idea/libraries/JUnit3.xml
deleted file mode 100644
index 87f2497..0000000
--- a/base/.idea/libraries/JUnit3.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="libraryTable">
- <library name="JUnit3">
- <CLASSES>
- <root url="jar://$APPLICATION_HOME_DIR$/lib/junit.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/JUnit4.xml b/base/.idea/libraries/JUnit4.xml
deleted file mode 100644
index 463c02b..0000000
--- a/base/.idea/libraries/JUnit4.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<component name="libraryTable">
- <library name="JUnit4">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/Trove4j.xml b/base/.idea/libraries/Trove4j.xml
deleted file mode 100644
index 98576bf..0000000
--- a/base/.idea/libraries/Trove4j.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<component name="libraryTable">
- <library name="Trove4j">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/trove4j/trove4j/1.1/trove4j-1.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/trove4j/trove4j/1.1/trove4j-1.1-sources.jar!/core/src" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/trove4j/trove4j/1.1/trove4j-1.1-sources.jar!/generated/src" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/ant.xml b/base/.idea/libraries/ant.xml
deleted file mode 100644
index d6acead..0000000
--- a/base/.idea/libraries/ant.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="libraryTable">
- <library name="ant">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/ant/ant/1.8.0/ant-1.8.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/asm_tools.xml b/base/.idea/libraries/asm_tools.xml
deleted file mode 100644
index 45ed4c3..0000000
--- a/base/.idea/libraries/asm_tools.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<component name="libraryTable">
- <library name="asm-tools">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/ow2/asm/asm/5.0.3/asm-5.0.3.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/ow2/asm/asm-analysis/5.0.3/asm-analysis-5.0.3.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/ow2/asm/asm-tree/5.0.3/asm-tree-5.0.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/ow2/asm/asm/5.0.3/asm-5.0.3-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/ow2/asm/asm-analysis/5.0.3/asm-analysis-5.0.3-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/ow2/asm/asm-tree/5.0.3/asm-tree-5.0.3-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/bouncy_castle.xml b/base/.idea/libraries/bouncy_castle.xml
deleted file mode 100644
index cf5a14f..0000000
--- a/base/.idea/libraries/bouncy_castle.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<component name="libraryTable">
- <library name="bouncy-castle">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/bouncycastle/bcpkix-jdk15on/1.48/bcpkix-jdk15on-1.48-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/builder_model.xml b/base/.idea/libraries/builder_model.xml
deleted file mode 100644
index 24e07b5..0000000
--- a/base/.idea/libraries/builder_model.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<component name="libraryTable">
- <library name="builder-model">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/offline-m2/com/android/tools/build/builder-model/1.3.0/builder-model-1.3.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/offline-m2/com/android/tools/build/builder-model/1.3.0/builder-model-1.3.0-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/commons_compress.xml b/base/.idea/libraries/commons_compress.xml
deleted file mode 100644
index 93d0dff..0000000
--- a/base/.idea/libraries/commons_compress.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="commons-compress">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/commons/commons-compress/1.0/commons-compress-1.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/commons/commons-compress/1.0/commons-compress-1.0-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/easymock_tools.xml b/base/.idea/libraries/easymock_tools.xml
deleted file mode 100644
index 08e58ef..0000000
--- a/base/.idea/libraries/easymock_tools.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<component name="libraryTable">
- <library name="easymock-tools">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/easymock/easymock/3.1/easymock-3.1.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/objenesis/objenesis/2.1/objenesis-2.1.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/cglib/cglib-nodep/3.1/cglib-nodep-3.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/easymock/easymock/3.1/easymock-3.1-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/objenesis/objenesis/2.1/objenesis-2.1-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/cglib/cglib-nodep/3.1/cglib-nodep-3.1-sources.jar!/src/proxy" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/cglib/cglib-nodep/3.1/cglib-nodep-3.1-sources.jar!/src/test" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/ecj.xml b/base/.idea/libraries/ecj.xml
deleted file mode 100644
index d92fa51..0000000
--- a/base/.idea/libraries/ecj.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="ecj">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/eclipse/jdt/core/compiler/ecj/4.4.2/ecj-4.4.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/eclipse/jdt/core/compiler/ecj/4.4.2/ecj-4.4.2-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/gradle_tooling_api_1_9.xml b/base/.idea/libraries/gradle_tooling_api_1_9.xml
deleted file mode 100644
index 964794b..0000000
--- a/base/.idea/libraries/gradle_tooling_api_1_9.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="gradle-tooling-api-1.9">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/gradle/gradle-tooling-api/1.9/gradle-tooling-api-1.9.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/gradle/gradle-tooling-api/1.9/gradle-tooling-api-1.9-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/groovy.xml b/base/.idea/libraries/groovy.xml
deleted file mode 100644
index 9e160b0..0000000
--- a/base/.idea/libraries/groovy.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="libraryTable">
- <library name="groovy">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../out/gradle-dist-link/lib/groovy-all-2.3.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/gson.xml b/base/.idea/libraries/gson.xml
deleted file mode 100644
index b860b43..0000000
--- a/base/.idea/libraries/gson.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="gson">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/code/gson/gson/2.2.4/gson-2.2.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/code/gson/gson/2.2.4/gson-2.2.4-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/guava_tools.xml b/base/.idea/libraries/guava_tools.xml
deleted file mode 100644
index b150775..0000000
--- a/base/.idea/libraries/guava_tools.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<component name="libraryTable">
- <library name="guava-tools">
- <ANNOTATIONS>
- <root url="file://$PROJECT_DIR$/../../prebuilts/tools/common/guava-tools/annotations" />
- </ANNOTATIONS>
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/guava/guava/17.0/guava-17.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/guava/guava/17.0/guava-17.0-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/http_client.xml b/base/.idea/libraries/http_client.xml
deleted file mode 100644
index e4ca5a6..0000000
--- a/base/.idea/libraries/http_client.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<component name="libraryTable">
- <library name="http-client">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/commons-codec/commons-codec/1.4/commons-codec-1.4.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpclient/4.1.1/httpclient-4.1.1.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpcore/4.1/httpcore-4.1.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpmime/4.1/httpmime-4.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/commons-codec/commons-codec/1.4/commons-codec-1.4-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpclient/4.1.1/httpclient-4.1.1-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpcore/4.1/httpcore-4.1-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/apache/httpcomponents/httpmime/4.1/httpmime-4.1-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/icu4j.xml b/base/.idea/libraries/icu4j.xml
deleted file mode 100644
index bb8886e..0000000
--- a/base/.idea/libraries/icu4j.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="icu4j">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/ibm/icu/icu4j/54.1.1/icu4j-54.1.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/ibm/icu/icu4j/54.1.1/icu4j-54.1.1-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/intellij_annotations.xml b/base/.idea/libraries/intellij_annotations.xml
deleted file mode 100644
index 5c70ad3..0000000
--- a/base/.idea/libraries/intellij_annotations.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="intellij-annotations">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/intellij/annotations/12.0/annotations-12.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/intellij/annotations/12.0/annotations-12.0-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/jacoco.xml b/base/.idea/libraries/jacoco.xml
deleted file mode 100644
index 6ba79ce..0000000
--- a/base/.idea/libraries/jacoco.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="jacoco">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/jacoco/org.jacoco.agent/0.7.4.201502262128/org.jacoco.agent-0.7.4.201502262128.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/jacoco/org.jacoco.agent/0.7.4.201502262128/org.jacoco.agent-0.7.4.201502262128-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/javawriter.xml b/base/.idea/libraries/javawriter.xml
deleted file mode 100644
index a62619d..0000000
--- a/base/.idea/libraries/javawriter.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="javawriter">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/squareup/javawriter/2.5.0/javawriter-2.5.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/squareup/javawriter/2.5.0/javawriter-2.5.0-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/jsr_305.xml b/base/.idea/libraries/jsr_305.xml
deleted file mode 100644
index 788b3ab..0000000
--- a/base/.idea/libraries/jsr_305.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="jsr-305">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/kxml2.xml b/base/.idea/libraries/kxml2.xml
deleted file mode 100644
index cb844c8..0000000
--- a/base/.idea/libraries/kxml2.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="kxml2">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/lombok_ast.xml b/base/.idea/libraries/lombok_ast.xml
deleted file mode 100644
index c1468f3..0000000
--- a/base/.idea/libraries/lombok_ast.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<component name="libraryTable">
- <library name="lombok-ast">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.3/lombok-ast-0.2.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.3/lombok-ast-0.2.3-sources.jar!/build/lombok.ast_generatedSource" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.3/lombok-ast-0.2.3-sources.jar!/src/ecjTransformer" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.3/lombok-ast-0.2.3-sources.jar!/src/printer" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/android/tools/external/lombok/lombok-ast/0.2.3/lombok-ast-0.2.3-sources.jar!/src/template" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/mockito.xml b/base/.idea/libraries/mockito.xml
deleted file mode 100644
index 7113f6f..0000000
--- a/base/.idea/libraries/mockito.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<component name="libraryTable">
- <library name="mockito">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/proguard_gradle.xml b/base/.idea/libraries/proguard_gradle.xml
deleted file mode 100644
index 3016c83..0000000
--- a/base/.idea/libraries/proguard_gradle.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<component name="libraryTable">
- <library name="proguard-gradle">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/5.2.1/proguard-base-5.2.1.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/5.2.1/proguard-gradle-5.2.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/5.2.1/proguard-base-5.2.1-sources.jar!/" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/5.2.1/proguard-gradle-5.2.1-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/slf4j_api.xml b/base/.idea/libraries/slf4j_api.xml
deleted file mode 100644
index 730c553..0000000
--- a/base/.idea/libraries/slf4j_api.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="slf4j-api">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-api/1.7.2/slf4j-api-1.7.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-api/1.7.2/slf4j-api-1.7.2-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/slf4j_simple.xml b/base/.idea/libraries/slf4j_simple.xml
deleted file mode 100644
index 2181dac..0000000
--- a/base/.idea/libraries/slf4j_simple.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="slf4j-simple">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-simple/1.7.2/slf4j-simple-1.7.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/org/slf4j/slf4j-simple/1.7.2/slf4j-simple-1.7.2-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/libraries/truth.xml b/base/.idea/libraries/truth.xml
deleted file mode 100644
index 97137f6..0000000
--- a/base/.idea/libraries/truth.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="truth">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/truth/truth/0.26/truth-0.26.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/google/truth/truth/0.26/truth-0.26-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/base/.idea/misc.xml b/base/.idea/misc.xml
deleted file mode 100644
index 8ec0917..0000000
--- a/base/.idea/misc.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ASMPluginConfiguration">
- <asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
- <groovy codeStyle="LEGACY" />
- </component>
- <component name="EntryPointsManager">
- <entry_points version="2.0" />
- <list size="11">
- <item index="0" class="java.lang.String" itemvalue="org.gradle.api.tasks.TaskAction" />
- <item index="1" class="java.lang.String" itemvalue="org.gradle.model.Defaults" />
- <item index="2" class="java.lang.String" itemvalue="org.gradle.model.Finalize" />
- <item index="3" class="java.lang.String" itemvalue="org.gradle.model.Model" />
- <item index="4" class="java.lang.String" itemvalue="org.gradle.model.Mutate" />
- <item index="5" class="java.lang.String" itemvalue="org.gradle.model.Validate" />
- <item index="6" class="java.lang.String" itemvalue="org.gradle.platform.base.BinaryTasks" />
- <item index="7" class="java.lang.String" itemvalue="org.gradle.platform.base.BinaryType" />
- <item index="8" class="java.lang.String" itemvalue="org.gradle.platform.base.ComponentBinaries" />
- <item index="9" class="java.lang.String" itemvalue="org.gradle.platform.base.ComponentType" />
- <item index="10" class="java.lang.String" itemvalue="org.gradle.platform.base.LanguageType" />
- </list>
- </component>
- <component name="FrameworkDetectionExcludesConfiguration">
- <type id="android" />
- </component>
- <component name="IdProvider" IDEtalkID="DCEF6D5FAEEAA0E5B2200920077168C7" />
- <component name="NullableNotNullManager">
- <option name="myDefaultNullable" value="com.android.annotations.Nullable" />
- <option name="myDefaultNotNull" value="com.android.annotations.NonNull" />
- <option name="myNullables">
- <value>
- <list size="4">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
- <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
- <item index="3" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
- </list>
- </value>
- </option>
- <option name="myNotNulls">
- <value>
- <list size="4">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
- <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
- <item index="3" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
- </list>
- </value>
- </option>
- </component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
- <output url="file://$PROJECT_DIR$/out" />
- </component>
-</project>
\ No newline at end of file
diff --git a/base/.idea/modules.xml b/base/.idea/modules.xml
deleted file mode 100644
index 5d2d335..0000000
--- a/base/.idea/modules.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ProjectModuleManager">
- <modules>
- <module fileurl="file://$PROJECT_DIR$/adt.iml" filepath="$PROJECT_DIR$/adt.iml" />
- <module fileurl="file://$PROJECT_DIR$/annotations/android-annotations.iml" filepath="$PROJECT_DIR$/annotations/android-annotations.iml" />
- <module fileurl="file://$PROJECT_DIR$/legacy/ant-tasks/ant-tasks.iml" filepath="$PROJECT_DIR$/legacy/ant-tasks/ant-tasks.iml" />
- <module fileurl="file://$PROJECT_DIR$/asset-studio/assetstudio-base.iml" filepath="$PROJECT_DIR$/asset-studio/assetstudio-base.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/builder/builder.iml" filepath="$PROJECT_DIR$/build-system/builder/builder.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/builder-model/builder-model.iml" filepath="$PROJECT_DIR$/build-system/builder-model/builder-model.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/builder-test-api/builder-test-api.iml" filepath="$PROJECT_DIR$/build-system/builder-test-api/builder-test-api.iml" />
- <module fileurl="file://$PROJECT_DIR$/chartlib/chartlib.iml" filepath="$PROJECT_DIR$/chartlib/chartlib.iml" />
- <module fileurl="file://$PROJECT_DIR$/common/common.iml" filepath="$PROJECT_DIR$/common/common.iml" />
- <module fileurl="file://$PROJECT_DIR$/ddmlib/ddmlib.iml" filepath="$PROJECT_DIR$/ddmlib/ddmlib.iml" />
- <module fileurl="file://$PROJECT_DIR$/draw9patch/draw9patch.iml" filepath="$PROJECT_DIR$/draw9patch/draw9patch.iml" />
- <module fileurl="file://$PROJECT_DIR$/device_validator/dvlib/dvlib.iml" filepath="$PROJECT_DIR$/device_validator/dvlib/dvlib.iml" />
- <module fileurl="file://$PROJECT_DIR$/sdk-common/generate-locale-data/generate-locale-data.iml" filepath="$PROJECT_DIR$/sdk-common/generate-locale-data/generate-locale-data.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/google-services/google-services.iml" filepath="$PROJECT_DIR$/build-system/google-services/google-services.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/gradle/gradle.iml" filepath="$PROJECT_DIR$/build-system/gradle/gradle.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/gradle-core/gradle-core.iml" filepath="$PROJECT_DIR$/build-system/gradle-core/gradle-core.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/gradle-experimental/gradle-experimental.iml" filepath="$PROJECT_DIR$/build-system/gradle-experimental/gradle-experimental.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/integration-test/integration-test.iml" filepath="$PROJECT_DIR$/build-system/integration-test/integration-test.iml" />
- <module fileurl="file://$PROJECT_DIR$/jack/jack-api/jack-api.iml" filepath="$PROJECT_DIR$/jack/jack-api/jack-api.iml" />
- <module fileurl="file://$PROJECT_DIR$/jack/jill-api/jill-api.iml" filepath="$PROJECT_DIR$/jack/jill-api/jill-api.iml" />
- <module fileurl="file://$PROJECT_DIR$/layoutlib-api/layoutlib-api-base.iml" filepath="$PROJECT_DIR$/layoutlib-api/layoutlib-api-base.iml" />
- <module fileurl="file://$PROJECT_DIR$/lint/libs/lint-api/lint-api-base.iml" filepath="$PROJECT_DIR$/lint/libs/lint-api/lint-api-base.iml" />
- <module fileurl="file://$PROJECT_DIR$/lint/libs/lint-checks/lint-checks-base.iml" filepath="$PROJECT_DIR$/lint/libs/lint-checks/lint-checks-base.iml" />
- <module fileurl="file://$PROJECT_DIR$/lint/cli/lint-cli.iml" filepath="$PROJECT_DIR$/lint/cli/lint-cli.iml" />
- <module fileurl="file://$PROJECT_DIR$/lint/libs/lint-tests/lint-tests.iml" filepath="$PROJECT_DIR$/lint/libs/lint-tests/lint-tests.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/manifest-merger/manifest-merger-base.iml" filepath="$PROJECT_DIR$/build-system/manifest-merger/manifest-merger-base.iml" />
- <module fileurl="file://$PROJECT_DIR$/ninepatch/ninepatch.iml" filepath="$PROJECT_DIR$/ninepatch/ninepatch.iml" />
- <module fileurl="file://$PROJECT_DIR$/perflib/perflib.iml" filepath="$PROJECT_DIR$/perflib/perflib.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/profile/profile.iml" filepath="$PROJECT_DIR$/build-system/profile/profile.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/project-test/project-test.iml" filepath="$PROJECT_DIR$/build-system/project-test/project-test.iml" />
- <module fileurl="file://$PROJECT_DIR$/build-system/project-test-lib/project-test-lib.iml" filepath="$PROJECT_DIR$/build-system/project-test-lib/project-test-lib.iml" />
- <module fileurl="file://$PROJECT_DIR$/rule-api/rule-api.iml" filepath="$PROJECT_DIR$/rule-api/rule-api.iml" />
- <module fileurl="file://$PROJECT_DIR$/sdk-common/sdk-common-base.iml" filepath="$PROJECT_DIR$/sdk-common/sdk-common-base.iml" />
- <module fileurl="file://$PROJECT_DIR$/sdklib/sdklib-base.iml" filepath="$PROJECT_DIR$/sdklib/sdklib-base.iml" />
- <module fileurl="file://$PROJECT_DIR$/testutils/testutils.iml" filepath="$PROJECT_DIR$/testutils/testutils.iml" />
- </modules>
- </component>
-</project>
\ No newline at end of file
diff --git a/base/.idea/projectCodeStyle.xml b/base/.idea/projectCodeStyle.xml
deleted file mode 100644
index bf4248b..0000000
--- a/base/.idea/projectCodeStyle.xml
+++ /dev/null
@@ -1,135 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<code_scheme name="AndroidStyle">
- <option name="JAVA_INDENT_OPTIONS">
- <value>
- <option name="INDENT_SIZE" value="4" />
- <option name="CONTINUATION_INDENT_SIZE" value="8" />
- <option name="TAB_SIZE" value="8" />
- <option name="USE_TAB_CHARACTER" value="false" />
- <option name="SMART_TABS" value="false" />
- <option name="LABEL_INDENT_SIZE" value="0" />
- <option name="LABEL_INDENT_ABSOLUTE" value="false" />
- <option name="USE_RELATIVE_INDENTS" value="false" />
- </value>
- </option>
- <option name="FIELD_NAME_PREFIX" value="m" />
- <option name="STATIC_FIELD_NAME_PREFIX" value="m" />
- <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
- <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
- <option name="IMPORT_LAYOUT_TABLE">
- <value>
- <package name="com.google" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="com" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="junit" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="net" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="org" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="android" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="java" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="javax" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="" withSubpackages="true" static="true" />
- </value>
- </option>
- <option name="RIGHT_MARGIN" value="100" />
- <option name="JD_P_AT_EMPTY_LINES" value="false" />
- <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
- <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
- <option name="JD_KEEP_EMPTY_RETURN" value="false" />
- <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
- <option name="BLANK_LINES_AROUND_FIELD" value="1" />
- <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
- <option name="ALIGN_MULTILINE_FOR" value="false" />
- <option name="CALL_PARAMETERS_WRAP" value="1" />
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
- <option name="EXTENDS_LIST_WRAP" value="1" />
- <option name="THROWS_LIST_WRAP" value="1" />
- <option name="EXTENDS_KEYWORD_WRAP" value="1" />
- <option name="THROWS_KEYWORD_WRAP" value="1" />
- <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
- <option name="BINARY_OPERATION_WRAP" value="1" />
- <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
- <option name="TERNARY_OPERATION_WRAP" value="1" />
- <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
- <option name="FOR_STATEMENT_WRAP" value="1" />
- <option name="ARRAY_INITIALIZER_WRAP" value="1" />
- <option name="ASSIGNMENT_WRAP" value="1" />
- <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
- <option name="WRAP_COMMENTS" value="true" />
- <option name="IF_BRACE_FORCE" value="3" />
- <option name="DOWHILE_BRACE_FORCE" value="3" />
- <option name="WHILE_BRACE_FORCE" value="3" />
- <option name="FOR_BRACE_FORCE" value="3" />
- <XML>
- <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
- </XML>
- <codeStyleSettings language="Groovy">
- <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
- <option name="BLANK_LINES_AROUND_FIELD" value="1" />
- <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
- <option name="ALIGN_MULTILINE_FOR" value="false" />
- <option name="CALL_PARAMETERS_WRAP" value="1" />
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
- <option name="EXTENDS_LIST_WRAP" value="1" />
- <option name="THROWS_LIST_WRAP" value="1" />
- <option name="EXTENDS_KEYWORD_WRAP" value="1" />
- <option name="THROWS_KEYWORD_WRAP" value="1" />
- <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
- <option name="BINARY_OPERATION_WRAP" value="1" />
- <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
- <option name="TERNARY_OPERATION_WRAP" value="1" />
- <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
- <option name="FOR_STATEMENT_WRAP" value="1" />
- <option name="ARRAY_INITIALIZER_WRAP" value="1" />
- <option name="ASSIGNMENT_WRAP" value="1" />
- <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
- <option name="WRAP_COMMENTS" value="true" />
- <option name="IF_BRACE_FORCE" value="3" />
- <option name="DOWHILE_BRACE_FORCE" value="3" />
- <option name="WHILE_BRACE_FORCE" value="3" />
- <option name="FOR_BRACE_FORCE" value="3" />
- <option name="PARENT_SETTINGS_INSTALLED" value="true" />
- </codeStyleSettings>
- <codeStyleSettings language="JAVA">
- <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
- <option name="BLANK_LINES_AROUND_FIELD" value="1" />
- <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
- <option name="ALIGN_MULTILINE_FOR" value="false" />
- <option name="CALL_PARAMETERS_WRAP" value="1" />
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
- <option name="EXTENDS_LIST_WRAP" value="1" />
- <option name="THROWS_LIST_WRAP" value="1" />
- <option name="EXTENDS_KEYWORD_WRAP" value="1" />
- <option name="THROWS_KEYWORD_WRAP" value="1" />
- <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
- <option name="BINARY_OPERATION_WRAP" value="1" />
- <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
- <option name="TERNARY_OPERATION_WRAP" value="1" />
- <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
- <option name="FOR_STATEMENT_WRAP" value="1" />
- <option name="ARRAY_INITIALIZER_WRAP" value="1" />
- <option name="ASSIGNMENT_WRAP" value="1" />
- <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
- <option name="WRAP_COMMENTS" value="true" />
- <option name="IF_BRACE_FORCE" value="3" />
- <option name="DOWHILE_BRACE_FORCE" value="3" />
- <option name="WHILE_BRACE_FORCE" value="3" />
- <option name="FOR_BRACE_FORCE" value="3" />
- <option name="PARENT_SETTINGS_INSTALLED" value="true" />
- </codeStyleSettings>
-</code_scheme>
-
diff --git a/base/.idea/scopes/custom_scope.xml b/base/.idea/scopes/custom_scope.xml
deleted file mode 100644
index 1acfd41..0000000
--- a/base/.idea/scopes/custom_scope.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<component name="DependencyValidationManager">
- <scope name="custom_scope" pattern="(src:*..*||test:*..*)&&!test[manifest-merger-base]:com.android.manifmerger.data..*&&!test[manifest-merger-base]:com.android.manifmerger.data2..*&&!test[lint-cli]:com.android.tools.lint.checks.data..*&&!test[sdk-common-base]:testData..*&&!test[builder]:testData..*&&!test[assetstudio-base]:com.android.assetstudiolib.testdata..*&&!src[assetstudio-base]:images..*" />
-</component>
\ No newline at end of file
diff --git a/base/.idea/scopes/scope_settings.xml b/base/.idea/scopes/scope_settings.xml
deleted file mode 100644
index 922003b..0000000
--- a/base/.idea/scopes/scope_settings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<component name="DependencyValidationManager">
- <state>
- <option name="SKIP_IMPORT_STATEMENTS" value="false" />
- </state>
-</component>
\ No newline at end of file
diff --git a/base/.idea/uiDesigner.xml b/base/.idea/uiDesigner.xml
deleted file mode 100644
index 3b00020..0000000
--- a/base/.idea/uiDesigner.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="Palette2">
- <group name="Swing">
- <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
- </item>
- <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
- </item>
- <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
- </item>
- <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
- <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
- </item>
- <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
- <initial-values>
- <property name="text" value="Button" />
- </initial-values>
- </item>
- <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
- <initial-values>
- <property name="text" value="RadioButton" />
- </initial-values>
- </item>
- <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
- <initial-values>
- <property name="text" value="CheckBox" />
- </initial-values>
- </item>
- <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
- <initial-values>
- <property name="text" value="Label" />
- </initial-values>
- </item>
- <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
- <preferred-size width="150" height="-1" />
- </default-constraints>
- </item>
- <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
- <preferred-size width="150" height="-1" />
- </default-constraints>
- </item>
- <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
- <preferred-size width="150" height="-1" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
- </item>
- <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
- <preferred-size width="200" height="200" />
- </default-constraints>
- </item>
- <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
- <preferred-size width="200" height="200" />
- </default-constraints>
- </item>
- <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
- </item>
- <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
- </item>
- <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
- </item>
- <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
- </item>
- <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
- <preferred-size width="-1" height="20" />
- </default-constraints>
- </item>
- <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
- </item>
- <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
- </item>
- </group>
- </component>
-</project>
-
diff --git a/base/.idea/vcs.xml b/base/.idea/vcs.xml
deleted file mode 100644
index 275077f..0000000
--- a/base/.idea/vcs.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="VcsDirectoryMappings">
- <mapping directory="" vcs="Git" />
- </component>
-</project>
-
diff --git a/base/adt.iml b/base/adt.iml
deleted file mode 100644
index b56f4fb..0000000
--- a/base/adt.iml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- <excludeFolder url="file://$MODULE_DIR$/.eclipse" />
- <excludeFolder url="file://$MODULE_DIR$/.idea" />
- <excludeFolder url="file://$MODULE_DIR$/apps" />
- <excludeFolder url="file://$MODULE_DIR$/asset-studio/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- <excludeFolder url="file://$MODULE_DIR$/common/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/ddmlib/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/files" />
- <excludeFolder url="file://$MODULE_DIR$/jobb" />
- <excludeFolder url="file://$MODULE_DIR$/layoutlib-api/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/manifest-merger/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/misc" />
- <excludeFolder url="file://$MODULE_DIR$/rule-api/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/rule-api/bin" />
- <excludeFolder url="file://$MODULE_DIR$/sdk-common/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/sdklib/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/sdkstats/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/templates" />
- <excludeFolder url="file://$MODULE_DIR$/testutils/.settings" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- </component>
-</module>
-
diff --git a/base/annotations/build.gradle b/base/annotations/build.gradle
deleted file mode 100644
index dbfcea6..0000000
--- a/base/annotations/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'sdk-java-lib'
-
-group = 'com.android.tools'
-archivesBaseName = 'annotations'
-version = rootProject.ext.baseVersion
-
-project.ext.pomName = 'Android Tools Annotations library'
-project.ext.pomDesc = 'annotations used throughout the Android tools libraries.'
-
-apply from: "$rootDir/buildSrc/base/publish.gradle"
-apply from: "$rootDir/buildSrc/base/bintray.gradle"
-apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/base/apps/DeviceConfig/.classpath b/base/apps/DeviceConfig/.classpath
deleted file mode 100644
index d57ec02..0000000
--- a/base/apps/DeviceConfig/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
- <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="src" path="gen"/>
- <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
- <classpathentry kind="output" path="bin/classes"/>
-</classpath>
diff --git a/base/apps/DeviceConfig/.gitignore b/base/apps/DeviceConfig/.gitignore
deleted file mode 100644
index 021b420..0000000
--- a/base/apps/DeviceConfig/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-gen
-bin
diff --git a/base/apps/DeviceConfig/.project b/base/apps/DeviceConfig/.project
deleted file mode 100644
index ca72013..0000000
--- a/base/apps/DeviceConfig/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>DeviceConfig</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- <buildCommand>
- <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- <buildCommand>
- <name>com.android.ide.eclipse.adt.ApkBuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/base/apps/DeviceConfig/.settings/org.eclipse.jdt.core.prefs b/base/apps/DeviceConfig/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index d9295f6..0000000
--- a/base/apps/DeviceConfig/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,5 +0,0 @@
-#Tue May 22 15:51:27 PDT 2012
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
-org.eclipse.jdt.core.compiler.compliance=1.5
-org.eclipse.jdt.core.compiler.source=1.5
diff --git a/base/apps/DeviceConfig/AndroidManifest.xml b/base/apps/DeviceConfig/AndroidManifest.xml
deleted file mode 100644
index b81f58e..0000000
--- a/base/apps/DeviceConfig/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.deviceconfig"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-permission android:name="android.permission.CAMERA" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-feature android:name="android.hardware.camera" />
-
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="15" />
-
- <application
- android:icon="@drawable/icon"
- android:label="@string/app_name" >
- <activity
- android:name=".MyActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/apps/DeviceConfig/build.xml b/base/apps/DeviceConfig/build.xml
deleted file mode 100644
index a48344c..0000000
--- a/base/apps/DeviceConfig/build.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="MyActivity" default="help">
-
- <!-- The local.properties file is created and updated by the 'android' tool.
- It contains the path to the SDK. It should *NOT* be checked into
- Version Control Systems. -->
- <property file="local.properties" />
-
- <!-- The ant.properties file can be created by you. It is only edited by the
- 'android' tool to add properties to it.
- This is the place to change some Ant specific build properties.
- Here are some properties you may want to change/update:
-
- source.dir
- The name of the source directory. Default is 'src'.
- out.dir
- The name of the output directory. Default is 'bin'.
-
- For other overridable properties, look at the beginning of the rules
- files in the SDK, at tools/ant/build.xml
-
- Properties related to the SDK location or the project target should
- be updated using the 'android' tool with the 'update' action.
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems.
-
- -->
- <property file="ant.properties" />
-
- <!-- The project.properties file is created and updated by the 'android'
- tool, as well as ADT.
-
- This contains project specific properties such as project target, and library
- dependencies. Lower level build properties are stored in ant.properties
- (or in .classpath for Eclipse projects).
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems. -->
- <loadproperties srcFile="project.properties" />
-
- <!-- if sdk.dir was not set from one of the property file, then
- get it from the ANDROID_HOME env var. -->
- <property environment="env" />
- <condition property="sdk.dir" value="${env.ANDROID_HOME}">
- <isset property="env.ANDROID_HOME" />
- </condition>
-
- <!-- quick check on sdk.dir -->
- <fail
- message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
- unless="sdk.dir"
- />
-
- <!--
- Import per project custom build rules if present at the root of the project.
- This is the place to put custom intermediary targets such as:
- -pre-build
- -pre-compile
- -post-compile (This is typically used for code obfuscation.
- Compiled code location: ${out.classes.absolute.dir}
- If this is not done in place, override ${out.dex.input.absolute.dir})
- -post-package
- -post-build
- -pre-clean
- -->
- <import file="custom_rules.xml" optional="true" />
-
- <!-- Import the actual build file.
-
- To customize existing targets, there are two options:
- - Customize only one target:
- - copy/paste the target into this file, *before* the
- <import> task.
- - customize it to your needs.
- - Customize the whole content of build.xml
- - copy/paste the content of the rules files (minus the top node)
- into this file, replacing the <import> task.
- - customize to your needs.
-
- ***********************
- ****** IMPORTANT ******
- ***********************
- In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
- in order to avoid having your file be overridden by tools such as "android update project"
- -->
- <!-- version-tag: 1 -->
- <import file="${sdk.dir}/tools/ant/build.xml" />
-
-</project>
diff --git a/base/apps/DeviceConfig/project.properties b/base/apps/DeviceConfig/project.properties
deleted file mode 100644
index 73fc661..0000000
--- a/base/apps/DeviceConfig/project.properties
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system use,
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-
-# Project target.
-target=android-18
diff --git a/base/apps/DeviceConfig/src/com/example/android/deviceconfig/ConfigGenerator.java b/base/apps/DeviceConfig/src/com/example/android/deviceconfig/ConfigGenerator.java
deleted file mode 100644
index 3dbdfdb..0000000
--- a/base/apps/DeviceConfig/src/com/example/android/deviceconfig/ConfigGenerator.java
+++ /dev/null
@@ -1,673 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.deviceconfig;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.os.Build;
-import android.os.Environment;
-import android.os.StatFs;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.ViewConfiguration;
-import android.widget.Toast;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Text;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Locale;
-
-import javax.xml.XMLConstants;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
-public class ConfigGenerator {
- private Context mCtx;
- private String mExtensions;
-
- public static final String NS_DEVICES_XSD = "http://schemas.android.com/sdk/devices/1";
-
- /**
- * The "devices" element is the root element of this schema.
- *
- * It must contain one or more "device" elements that each define the
- * hardware, software, and states for a given device.
- */
- public static final String NODE_DEVICES = "devices";
-
- /**
- * A "device" element contains a "hardware" element, a "software" element
- * for each API version it supports, and a "state" element for each possible
- * state the device could be in.
- */
- public static final String NODE_DEVICE = "device";
-
- /**
- * The "hardware" element contains all of the hardware information for a
- * given device.
- */
- public static final String NODE_HARDWARE = "hardware";
-
- /**
- * The "software" element contains all of the software information for an
- * API version of the given device.
- */
- public static final String NODE_SOFTWARE = "software";
-
- /**
- * The "state" element contains all of the parameters for a given state of
- * the device. It's also capable of redefining hardware configurations if
- * they change based on state.
- */
-
- public static final String NODE_STATE = "state";
-
- public static final String NODE_KEYBOARD = "keyboard";
- public static final String NODE_TOUCH = "touch";
- public static final String NODE_GL_EXTENSIONS = "gl-extensions";
- public static final String NODE_GL_VERSION = "gl-version";
- public static final String NODE_NETWORKING = "networking";
- public static final String NODE_REMOVABLE_STORAGE = "removable-storage";
- public static final String NODE_FLASH = "flash";
- public static final String NODE_LIVE_WALLPAPER_SUPPORT = "live-wallpaper-support";
- public static final String NODE_BUTTONS = "buttons";
- public static final String NODE_CAMERA = "camera";
- public static final String NODE_LOCATION = "location";
- public static final String NODE_GPU = "gpu";
- public static final String NODE_DOCK = "dock";
- public static final String NODE_YDPI = "ydpi";
- public static final String NODE_POWER_TYPE = "power-type";
- public static final String NODE_Y_DIMENSION = "y-dimension";
- public static final String NODE_SCREEN_RATIO = "screen-ratio";
- public static final String NODE_NAV_STATE = "nav-state";
- public static final String NODE_MIC = "mic";
- public static final String NODE_RAM = "ram";
- public static final String NODE_XDPI = "xdpi";
- public static final String NODE_DIMENSIONS = "dimensions";
- public static final String NODE_ABI = "abi";
- public static final String NODE_MECHANISM = "mechanism";
- public static final String NODE_MULTITOUCH = "multitouch";
- public static final String NODE_NAV = "nav";
- public static final String NODE_PIXEL_DENSITY = "pixel-density";
- public static final String NODE_SCREEN_ORIENTATION = "screen-orientation";
- public static final String NODE_AUTOFOCUS = "autofocus";
- public static final String NODE_SCREEN_SIZE = "screen-size";
- public static final String NODE_DESCRIPTION = "description";
- public static final String NODE_BLUETOOTH_PROFILES = "bluetooth-profiles";
- public static final String NODE_SCREEN = "screen";
- public static final String NODE_SENSORS = "sensors";
- public static final String NODE_DIAGONAL_LENGTH = "diagonal-length";
- public static final String NODE_SCREEN_TYPE = "screen-type";
- public static final String NODE_KEYBOARD_STATE = "keyboard-state";
- public static final String NODE_X_DIMENSION = "x-dimension";
- public static final String NODE_CPU = "cpu";
- public static final String NODE_INTERNAL_STORAGE = "internal-storage";
- public static final String NODE_NAME = "name";
- public static final String NODE_MANUFACTURER = "manufacturer";
- public static final String NODE_API_LEVEL = "api-level";
- public static final String ATTR_DEFAULT = "default";
- public static final String ATTR_UNIT = "unit";
- public static final String UNIT_BYTES = "B";
- public static final String UNIT_KIBIBYTES = "KiB";
- public static final String UNIT_MEBIBYTES = "MiB";
- public static final String UNIT_GIBIBYTES = "GiB";
- public static final String UNIT_TEBIBYTES = "TiB";
- public static final String LOCAL_NS = "d";
- public static final String PREFIX = LOCAL_NS + ":";
-
- private static final String TAG = "ConfigGenerator";
-
- public ConfigGenerator(Context context, String extensions) {
- mCtx = context;
- mExtensions = extensions;
- }
-
- @SuppressLint("WorldReadableFiles")
- public String generateConfig() {
- Resources resources = mCtx.getResources();
- PackageManager packageMgr = mCtx.getPackageManager();
- DisplayMetrics metrics = resources.getDisplayMetrics();
- Configuration config = resources.getConfiguration();
-
- try {
- Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
-
- Element devices = doc.createElement(PREFIX + NODE_DEVICES);
- devices.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":xsi",
- XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
- devices.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + LOCAL_NS, NS_DEVICES_XSD);
- doc.appendChild(devices);
-
- Element device = doc.createElement(PREFIX + NODE_DEVICE);
- devices.appendChild(device);
-
- Element name = doc.createElement(PREFIX + NODE_NAME);
- device.appendChild(name);
- name.appendChild(doc.createTextNode(android.os.Build.MODEL));
- Element manufacturer = doc.createElement(PREFIX + NODE_MANUFACTURER);
- device.appendChild(manufacturer);
- manufacturer.appendChild(doc.createTextNode(android.os.Build.MANUFACTURER));
-
- Element hardware = doc.createElement(PREFIX + NODE_HARDWARE);
- device.appendChild(hardware);
-
- Element screen = doc.createElement(PREFIX + NODE_SCREEN);
- hardware.appendChild(screen);
-
- Element screenSize = doc.createElement(PREFIX + NODE_SCREEN_SIZE);
- screen.appendChild(screenSize);
- Text screenSizeText;
- switch (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) {
- case Configuration.SCREENLAYOUT_SIZE_SMALL:
- screenSizeText = doc.createTextNode("small");
- break;
- case Configuration.SCREENLAYOUT_SIZE_NORMAL:
- screenSizeText = doc.createTextNode("normal");
- break;
- case Configuration.SCREENLAYOUT_SIZE_LARGE:
- screenSizeText = doc.createTextNode("large");
- break;
- case Configuration.SCREENLAYOUT_SIZE_XLARGE:
- screenSizeText = doc.createTextNode("xlarge");
- break;
- default:
- screenSizeText = doc.createTextNode(" ");
- break;
- }
- screenSize.appendChild(screenSizeText);
-
- Element diagonalLength = doc.createElement(PREFIX + NODE_DIAGONAL_LENGTH);
- screen.appendChild(diagonalLength);
- double xin = metrics.widthPixels / metrics.xdpi;
- double yin = metrics.heightPixels / metrics.ydpi;
- double diag = Math.sqrt(Math.pow(xin, 2) + Math.pow(yin, 2));
- diagonalLength.appendChild(doc.createTextNode(
- String.format(Locale.US, "%1$.2f", diag)));
-
- Element pixelDensity = doc.createElement(PREFIX + NODE_PIXEL_DENSITY);
- screen.appendChild(pixelDensity);
- Text pixelDensityText;
- switch (metrics.densityDpi) {
- case DisplayMetrics.DENSITY_LOW:
- pixelDensityText = doc.createTextNode("ldpi");
- break;
- case DisplayMetrics.DENSITY_MEDIUM:
- pixelDensityText = doc.createTextNode("mdpi");
- break;
- case DisplayMetrics.DENSITY_TV:
- pixelDensityText = doc.createTextNode("tvdpi");
- break;
- case DisplayMetrics.DENSITY_HIGH:
- pixelDensityText = doc.createTextNode("hdpi");
- break;
- case DisplayMetrics.DENSITY_XHIGH:
- pixelDensityText = doc.createTextNode("xhdpi");
- break;
- default:
- pixelDensityText = doc.createTextNode(" ");
- }
- pixelDensity.appendChild(pixelDensityText);
-
- Element screenRatio = doc.createElement(PREFIX + NODE_SCREEN_RATIO);
- screen.appendChild(screenRatio);
- Text screenRatioText;
- switch (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) {
- case Configuration.SCREENLAYOUT_LONG_YES:
- screenRatioText = doc.createTextNode("long");
- break;
- case Configuration.SCREENLAYOUT_LONG_NO:
- screenRatioText = doc.createTextNode("notlong");
- break;
- default:
- screenRatioText = doc.createTextNode(" ");
- break;
- }
- screenRatio.appendChild(screenRatioText);
-
- Element dimensions = doc.createElement(PREFIX + NODE_DIMENSIONS);
- screen.appendChild(dimensions);
-
- Element xDimension = doc.createElement(PREFIX + NODE_X_DIMENSION);
- dimensions.appendChild(xDimension);
- xDimension.appendChild(doc.createTextNode(Integer.toString(metrics.widthPixels)));
-
- Element yDimension = doc.createElement(PREFIX + NODE_Y_DIMENSION);
- dimensions.appendChild(yDimension);
- yDimension.appendChild(doc.createTextNode(Integer.toString(metrics.heightPixels)));
-
- Element xdpi = doc.createElement(PREFIX + NODE_XDPI);
- screen.appendChild(xdpi);
- xdpi.appendChild(doc.createTextNode(Double.toString(metrics.xdpi)));
-
- Element ydpi = doc.createElement(PREFIX + NODE_YDPI);
- screen.appendChild(ydpi);
- ydpi.appendChild(doc.createTextNode(Double.toString(metrics.ydpi)));
-
- Element touch = doc.createElement(PREFIX + NODE_TOUCH);
- screen.appendChild(touch);
-
- Element multitouch = doc.createElement(PREFIX + NODE_MULTITOUCH);
- touch.appendChild(multitouch);
- Text multitouchText;
- if (packageMgr
- .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND)) {
- multitouchText = doc.createTextNode("jazz-hands");
- } else if (packageMgr
- .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
- multitouchText = doc.createTextNode("distinct");
- } else if (packageMgr.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) {
- multitouchText = doc.createTextNode("basic");
- } else {
- multitouchText = doc.createTextNode("none");
- }
- multitouch.appendChild(multitouchText);
-
- Element mechanism = doc.createElement(PREFIX + NODE_MECHANISM);
- touch.appendChild(mechanism);
- Text mechanismText;
- switch (config.touchscreen) {
- case Configuration.TOUCHSCREEN_STYLUS:
- mechanismText = doc.createTextNode("stylus");
- case Configuration.TOUCHSCREEN_FINGER:
- mechanismText = doc.createTextNode("finger");
- case Configuration.TOUCHSCREEN_NOTOUCH:
- mechanismText = doc.createTextNode("notouch");
- default:
- mechanismText = doc.createTextNode(" ");
- }
- mechanism.appendChild(mechanismText);
-
- // Create an empty place holder node for screen-type since we can't
- // actually determine it
-
- Element screenType = doc.createElement(PREFIX + NODE_SCREEN_TYPE);
- touch.appendChild(screenType);
- screenType.appendChild(doc.createTextNode(" "));
-
- Element networking = doc.createElement(PREFIX + NODE_NETWORKING);
- hardware.appendChild(networking);
- Text networkingText = doc.createTextNode("");
- networking.appendChild(networkingText);
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- networkingText.appendData("\nWifi");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
- networkingText.appendData("\nBluetooth");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_NFC)) {
- networkingText.appendData("\nNFC");
- }
-
- Element sensors = doc.createElement(PREFIX + NODE_SENSORS);
- hardware.appendChild(sensors);
- Text sensorsText = doc.createTextNode("");
- sensors.appendChild(sensorsText);
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)) {
- sensorsText.appendData("\nAccelerometer");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_BAROMETER)) {
- sensorsText.appendData("\nBarometer");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_COMPASS)) {
- sensorsText.appendData("\nCompass");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) {
- sensorsText.appendData("\nGPS");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_GYROSCOPE)) {
- sensorsText.appendData("\nGyroscope");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT)) {
- sensorsText.appendData("\nLightSensor");
- }
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_SENSOR_PROXIMITY)) {
- sensorsText.appendData("\nProximitySensor");
- }
-
- Element mic = doc.createElement(PREFIX + NODE_MIC);
- hardware.appendChild(mic);
- Text micText;
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
- micText = doc.createTextNode("true");
- } else {
- micText = doc.createTextNode("false");
- }
- mic.appendChild(micText);
-
- if (android.os.Build.VERSION.SDK_INT >= 9){
- List<Element> cameras = getCameraElements(doc);
- for (Element cam : cameras){
- hardware.appendChild(cam);
- }
- } else {
- Camera c = Camera.open();
- Element camera = doc.createElement(PREFIX + NODE_CAMERA);
- hardware.appendChild(camera);
- Element location = doc.createElement(PREFIX + NODE_LOCATION);
- camera.appendChild(location);
- // All camera's before API 9 were on the back
- location.appendChild(doc.createTextNode("back"));
- Camera.Parameters cParams = c.getParameters();
- Element autofocus = doc.createElement(PREFIX + NODE_AUTOFOCUS);
- camera.appendChild(autofocus);
- List<String> foci = cParams.getSupportedFocusModes();
- if (foci == null) {
- autofocus.appendChild(doc.createTextNode(" "));
- } else if (foci.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
- autofocus.appendChild(doc.createTextNode("true"));
- } else {
- autofocus.appendChild(doc.createTextNode("false"));
- }
-
- Element flash = doc.createElement(PREFIX + NODE_FLASH);
- camera.appendChild(flash);
- List<String> flashes = cParams.getSupportedFlashModes();
- if (flashes == null || !flashes.contains(Camera.Parameters.FLASH_MODE_ON)) {
- flash.appendChild(doc.createTextNode("false"));
- } else {
- flash.appendChild(doc.createTextNode("true"));
- }
- c.release();
- }
-
-
- Element keyboard = doc.createElement(PREFIX + NODE_KEYBOARD);
- hardware.appendChild(keyboard);
- Text keyboardText;
- switch (config.keyboard) {
- case Configuration.KEYBOARD_NOKEYS:
- keyboardText = doc.createTextNode("nokeys");
- break;
- case Configuration.KEYBOARD_12KEY:
- keyboardText = doc.createTextNode("12key");
- break;
- case Configuration.KEYBOARD_QWERTY:
- keyboardText = doc.createTextNode("qwerty");
- break;
- default:
- keyboardText = doc.createTextNode(" ");
- }
- keyboard.appendChild(keyboardText);
-
- Element nav = doc.createElement(PREFIX + NODE_NAV);
- hardware.appendChild(nav);
- Text navText;
- switch (config.navigation) {
- case Configuration.NAVIGATION_DPAD:
- navText = doc.createTextNode("dpad");
- case Configuration.NAVIGATION_TRACKBALL:
- navText = doc.createTextNode("trackball");
- case Configuration.NAVIGATION_WHEEL:
- navText = doc.createTextNode("wheel");
- case Configuration.NAVIGATION_NONAV:
- navText = doc.createTextNode("nonav");
- default:
- navText = doc.createTextNode(" ");
- }
- nav.appendChild(navText);
-
- Element ram = doc.createElement(PREFIX + NODE_RAM);
- hardware.appendChild(ram);
- // totalMemory given in bytes, divide by 1048576 to get RAM in MiB
- String line;
- long ramAmount = 0;
- String unit = UNIT_BYTES;
- try {
- BufferedReader meminfo = new BufferedReader(new FileReader("/proc/meminfo"));
- while ((line = meminfo.readLine()) != null) {
- String[] vals = line.split("[\\s]+");
- if (vals[0].equals("MemTotal:")) {
- try {
- /*
- * We're going to want it as a string eventually,
- * but parsing it lets us validate it's actually a
- * number and something strange isn't going on
- */
- ramAmount = Long.parseLong(vals[1]);
- unit = vals[2];
- break;
- } catch (NumberFormatException e) {
- // Ignore
- }
- }
- }
- meminfo.close();
- } catch (FileNotFoundException e) {
- // Ignore
- }
- if (ramAmount > 0) {
- if (unit.equals("B")) {
- unit = UNIT_BYTES;
- } else if (unit.equals("kB")) {
- unit = UNIT_KIBIBYTES;
- } else if (unit.equals("MB")) {
- unit = UNIT_MEBIBYTES;
- } else if (unit.equals("GB")) {
- unit = UNIT_GIBIBYTES;
- } else {
- unit = " ";
- }
- }
- ram.setAttribute(ATTR_UNIT, unit);
- ram.appendChild(doc.createTextNode(Long.toString(ramAmount)));
-
- Element buttons = doc.createElement(PREFIX + NODE_BUTTONS);
- hardware.appendChild(buttons);
- Text buttonsText;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- buttonsText = doc.createTextNode(getButtonsType());
- } else {
- buttonsText = doc.createTextNode("hard");
- }
- buttons.appendChild(buttonsText);
-
- Element internalStorage = doc.createElement(PREFIX + NODE_INTERNAL_STORAGE);
- hardware.appendChild(internalStorage);
- StatFs rootStat = new StatFs(Environment.getRootDirectory().getAbsolutePath());
- long bytesAvailable = rootStat.getBlockSize() * rootStat.getBlockCount();
- long internalStorageSize = bytesAvailable / (1024 * 1024);
- internalStorage.appendChild(doc.createTextNode(Long.toString(internalStorageSize)));
- internalStorage.setAttribute(ATTR_UNIT, UNIT_MEBIBYTES);
-
- Element externalStorage = doc.createElement(PREFIX + NODE_REMOVABLE_STORAGE);
- hardware.appendChild(externalStorage);
- externalStorage.appendChild(doc.createTextNode(" "));
-
-
- // Don't know CPU, GPU types
- Element cpu = doc.createElement(PREFIX + NODE_CPU);
- hardware.appendChild(cpu);
- cpu.appendChild(doc.createTextNode(" "));
- Element gpu = doc.createElement(PREFIX + NODE_GPU);
- hardware.appendChild(gpu);
- gpu.appendChild(doc.createTextNode(" "));
-
- Element abi = doc.createElement(PREFIX + NODE_ABI);
- hardware.appendChild(abi);
- Text abiText = doc.createTextNode("");
- abi.appendChild(abiText);
- abiText.appendData("\n" + android.os.Build.CPU_ABI);
- abiText.appendData("\n" + android.os.Build.CPU_ABI2);
-
- // Don't know about either the dock or plugged-in element
- Element dock = doc.createElement(PREFIX + NODE_DOCK);
- hardware.appendChild(dock);
- dock.appendChild(doc.createTextNode(" "));
-
- Element pluggedIn = doc.createElement(PREFIX + NODE_POWER_TYPE);
- hardware.appendChild(pluggedIn);
- pluggedIn.appendChild(doc.createTextNode(" "));
-
- Element software = doc.createElement(PREFIX + NODE_SOFTWARE);
- device.appendChild(software);
-
- Element apiLevel = doc.createElement(PREFIX + NODE_API_LEVEL);
- software.appendChild(apiLevel);
- apiLevel.appendChild(doc.createTextNode(Integer
- .toString(android.os.Build.VERSION.SDK_INT)));
-
- Element liveWallpaperSupport = doc.createElement(PREFIX + NODE_LIVE_WALLPAPER_SUPPORT);
- software.appendChild(liveWallpaperSupport);
- if (packageMgr.hasSystemFeature(PackageManager.FEATURE_LIVE_WALLPAPER)) {
- liveWallpaperSupport.appendChild(doc.createTextNode("true"));
- } else {
- liveWallpaperSupport.appendChild(doc.createTextNode("flase"));
- }
-
- Element bluetoothProfiles = doc.createElement(PREFIX + NODE_BLUETOOTH_PROFILES);
- software.appendChild(bluetoothProfiles);
- bluetoothProfiles.appendChild(doc.createTextNode(" "));
-
- Element glVersion = doc.createElement(PREFIX + NODE_GL_VERSION);
- software.appendChild(glVersion);
- String glVersionString = " ";
-
- FeatureInfo[] features = packageMgr.getSystemAvailableFeatures();
- for (FeatureInfo feature : features) {
- if (feature.reqGlEsVersion > 0) {
- glVersionString = feature.getGlEsVersion();
- break;
- }
- }
-
- glVersion.appendChild(doc.createTextNode(glVersionString));
-
- Element glExtensions = doc.createElement(PREFIX + NODE_GL_EXTENSIONS);
- software.appendChild(glExtensions);
- if (mExtensions != null && !mExtensions.trim().equals("")) {
- glExtensions.appendChild(doc.createTextNode(mExtensions));
- } else {
- glExtensions.appendChild(doc.createTextNode(" "));
- }
-
- Transformer tf = TransformerFactory.newInstance().newTransformer();
- tf.setOutputProperty(OutputKeys.INDENT, "yes");
- tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
- DOMSource source = new DOMSource(doc);
- String filename = String.format("devices_%1$tm_%1$td_%1$ty.xml", Calendar.getInstance()
- .getTime());
- File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- File outFile = new File(dir, filename);
- FileOutputStream out = new FileOutputStream(new File(dir, filename));
- StreamResult result = new StreamResult(out);
- tf.transform(source, result);
- out.flush();
- out.close();
- return outFile.getAbsolutePath();
- } catch (ParserConfigurationException e) {
- error("Parser config exception", e);
- } catch (TransformerConfigurationException e) {
- error("Transformer config exception", e);
- } catch (TransformerFactoryConfigurationError e) {
- error("TransformerFactory config exception", e);
- } catch (TransformerException e) {
- error("Error transforming", e);
- } catch (IOException e) {
- error("I/O Error", e);
- }
- return null;
- }
-
- @TargetApi(9)
- private List<Element> getCameraElements(Document doc) {
- List<Element> cList = new ArrayList<Element>();
- for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
- Element camera = doc.createElement(PREFIX + NODE_CAMERA);
- cList.add(camera);
- Element location = doc.createElement(PREFIX + NODE_LOCATION);
- camera.appendChild(location);
- Text locationText;
- Camera.CameraInfo cInfo = new Camera.CameraInfo();
- Camera.getCameraInfo(i, cInfo);
- if (cInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
- locationText = doc.createTextNode("front");
- } else if (cInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- locationText = doc.createTextNode("back");
- } else {
- locationText = doc.createTextNode(" ");
- }
- location.appendChild(locationText);
-
- Camera c = Camera.open(i);
- Camera.Parameters cParams = c.getParameters();
-
- Element autofocus = doc.createElement(PREFIX + NODE_AUTOFOCUS);
- camera.appendChild(autofocus);
- List<String> foci = cParams.getSupportedFocusModes();
- if (foci == null) {
- autofocus.appendChild(doc.createTextNode(" "));
- } else if (foci.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
- autofocus.appendChild(doc.createTextNode("true"));
- } else {
- autofocus.appendChild(doc.createTextNode("false"));
- }
-
- Element flash = doc.createElement(PREFIX + NODE_FLASH);
- camera.appendChild(flash);
- List<String> flashes = cParams.getSupportedFlashModes();
- if (flashes == null || !flashes.contains(Camera.Parameters.FLASH_MODE_ON)) {
- flash.appendChild(doc.createTextNode("false"));
- } else {
- flash.appendChild(doc.createTextNode("true"));
- }
- c.release();
- }
- return cList;
- }
-
- @TargetApi(14)
- private String getButtonsType() {
- ViewConfiguration vConfig = ViewConfiguration.get(mCtx);
-
- if (vConfig.hasPermanentMenuKey()) {
- return "hard";
- } else {
- return "soft";
- }
- }
-
- private void error(String err, Throwable e) {
- Toast.makeText(mCtx, "Error Generating Configuration", Toast.LENGTH_SHORT).show();
- Log.e(TAG, err);
- Log.e(TAG, e.getLocalizedMessage());
- }
-}
diff --git a/base/apps/DeviceConfig/src/com/example/android/deviceconfig/MyActivity.java b/base/apps/DeviceConfig/src/com/example/android/deviceconfig/MyActivity.java
deleted file mode 100644
index 38a78cd..0000000
--- a/base/apps/DeviceConfig/src/com/example/android/deviceconfig/MyActivity.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.deviceconfig;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.opengl.GLSurfaceView;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.io.File;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-public class MyActivity extends Activity implements OnClickListener {
-
- public static final String TAG = "DeviceConfig";
- private static GLView mGl;
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.main);
- // Instantiate a GL surface view so we can get extensions information
- mGl = new GLView(this);
- LinearLayout vg = (LinearLayout) findViewById(R.id.buttonHolder);
- // If we set the layout to be 0, it just won't render
- ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(1, 1);
- mGl.setLayoutParams(params);
- vg.addView(mGl);
-
- Button btn = (Button) findViewById(R.id.generateConfigButton);
- btn.setOnClickListener(this);
- Configuration config = getResources().getConfiguration();
-
- TextView tv = (TextView) findViewById(R.id.keyboard_state_api);
- if (tv != null) {
- String separator = config.orientation == Configuration.ORIENTATION_PORTRAIT ? "\n" : "";
- String foo = "keyboardHidden=" + separator;
- if (config.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) {
- foo += "EXPOSED";
- } else if (config.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
- foo += "HIDDEN";
- } else if (config.keyboardHidden == Configuration.KEYBOARDHIDDEN_UNDEFINED) {
- foo += "UNDEFINED";
- } else {
- foo += "?";
- }
- foo += "\nhardKeyboardHidden=" + separator;
- if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
- foo = foo + "EXPOSED";
- } else if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
- foo = foo + "HIDDEN";
- } else if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_UNDEFINED) {
- foo = foo + "UNDEFINED";
- } else {
- foo = "?";
- }
-
- tv.setText(foo);
- }
-
- tv = (TextView) findViewById(R.id.nav_state_api);
- if (tv != null) {
- if (config.navigationHidden == Configuration.NAVIGATIONHIDDEN_NO) {
- tv.setText("EXPOSED");
- } else if (config.navigationHidden == Configuration.NAVIGATIONHIDDEN_YES) {
- tv.setText("HIDDEN");
- } else if (config.navigationHidden == Configuration.NAVIGATIONHIDDEN_UNDEFINED) {
- tv.setText("UNDEFINED");
- } else {
- tv.setText("??");
- }
- }
-
- DisplayMetrics metrics = getResources().getDisplayMetrics();
-
-
- tv = (TextView) findViewById(R.id.size_api);
- if (tv != null) {
- int a = metrics.heightPixels;
- int b = metrics.widthPixels;
- tv.setText(b + "x" + a);
- }
-
- tv = (TextView) findViewById(R.id.xdpi);
- if (tv != null) {
- tv.setText(String.format("%f", metrics.xdpi));
- }
- tv = (TextView) findViewById(R.id.ydpi);
- if (tv != null) {
- tv.setText(String.format("%f", metrics.ydpi));
- }
-
- tv = (TextView) findViewById(R.id.scaled_density);
- if (tv != null) {
- tv.setText(String.format("%f", metrics.scaledDensity));
- }
-
- tv = (TextView) findViewById(R.id.font_scale);
- if (tv != null) {
- tv.setText(String.format("%f", config.fontScale));
- }
-
- }
-
- public void onClick(View v) {
- ConfigGenerator configGen = new ConfigGenerator(this, mGl.getExtensions());
- final String filename = configGen.generateConfig();
- if (filename != null) {
- Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
- emailIntent.setType("text/xml");
- File devicesXml = new File(filename);
- emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Device XML: " + devicesXml.getName());
- emailIntent.putExtra(Intent.EXTRA_TEXT, "Note: This is intended to generate a base "
- + "XML description. After running this, you should double check the generated "
- + "information and add all of the missing fields.");
- emailIntent.putExtra(Intent.EXTRA_STREAM,
- Uri.parse("file://" + devicesXml.getAbsolutePath()));
- startActivity(emailIntent);
- }
- }
-
- private static class GLView extends GLSurfaceView {
- private GlRenderer mRenderer;
-
- public GLView(Context context) {
- super(context);
- setEGLContextClientVersion(2);
- mRenderer = new GlRenderer();
- setRenderer(mRenderer);
- requestRender();
- }
-
- public String getExtensions() {
- return mRenderer.extensions;
- }
-
- }
-
- private static class GlRenderer implements GLSurfaceView.Renderer {
- public String extensions = "";
-
- public void onDrawFrame(GL10 gl) {
- }
-
- public void onSurfaceChanged(GL10 gl, int width, int height) {
- gl.glViewport(0, 0, 0, 0);
- }
-
- public void onSurfaceCreated(GL10 gl, EGLConfig config) {
- if (extensions.equals("")) {
- String extensions10 = gl.glGetString(GL10.GL_EXTENSIONS);
- if(extensions10 != null) {
- extensions += extensions10;
- }
- }
- }
- }
-}
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_3d_rotation_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_3d_rotation_24dp.xml
deleted file mode 100644
index 8a08cf2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_3d_rotation_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.52,21.48C4.25,19.94 1.91,16.76 1.55,13H0.05C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32zm0.89,-6.52c-0.19,0 -0.37,-0.03 -0.52,-0.08 -0.16,-0.06 -0.29,-0.13 -0.4,-0.24 -0.11,-0.1 -0.2,-0.22 -0.26,-0.37 -0.06,-0.14 -0.09,-0.3 -0.09,-0.47h-1.3c0,0.36 0.07,0.68 0.21,0.95 0.14,0.27 0.33,0.5 0.56,0.69 0.24,0.18 0.51,0.32 0.82,0.41 0.3,0.1 0.62,0.15 0.96,0.15 0.37,0 0.72,-0.05 1.03,-0.15 0.32,-0.1 0.6,-0.25 0.83,-0.44s0.42,-0.43 0.55,-0.72c0.13,-0.29 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_accessibility_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_accessibility_24dp.xml
deleted file mode 100644
index 075be55..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_accessibility_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zm9,7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_24dp.xml
deleted file mode 100644
index c3a8755..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,10v7h3v-7H4zm6,0v7h3v-7h-3zM2,22h19v-3H2v3zm14,-12v7h3v-7h-3zm-4.5,-9L2,6v2h19V6l-9.5,-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_wallet_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_wallet_24dp.xml
deleted file mode 100644
index 6b570ff..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_balance_wallet_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2H5c-1.11,0 -2,-0.9 -2,-2V5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zm-9,-2h10V8H12v8zm4,-2.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_box_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_box_24dp.xml
deleted file mode 100644
index 13ed54c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_box_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2H5c-1.11,0 -2,0.9 -2,2zm12,4c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3zm-9,8c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1H6v-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_child_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_child_24dp.xml
deleted file mode 100644
index c40ad49..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_child_24dp.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,13.49m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,2.5c1.24,0 2.25,1.01 2.25,2.25S13.24,9 12,9 9.75,7.99 9.75,6.75 10.76,4.5 12,4.5zm5,10.56v2.5c-0.45,0.41 -0.96,0.77 -1.5,1.05v-0.68c0,-0.34 -0.17,-0.65 -0.46,-0.92 -0.65,-0.62 -1.89,-1.02 -3.04,-1.02 -0.96,0 -1.96,0.28 -2.65,0.73l-0.17,0.12 -0.21,0.17c0.78,0.47 1.63,0.72 2.54,0.82l1.33,0.15c0.37,0.04 0.66,0.36 0.66,0.75 0,0.29 -0.16,0.53 -0.4,0.66 -0.28,0.15 -0.64,0.09 -0.95,0.09 -0.35,0 -0.6 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_circle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_circle_24dp.xml
deleted file mode 100644
index 1f209f7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_account_circle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,3c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm0,14.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_add_shopping_cart_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_add_shopping_cart_24dp.xml
deleted file mode 100644
index 281dd33..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_add_shopping_cart_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,9h2V6h3V4h-3V1h-2v3H8v2h3v3zm-4,9c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zm10,0c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm-9.83,-3.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.86,-7.01L19.42,4h-0.01l-1.1,2 -2.76,5H8.53l-0.13,-0.27L6.16,6l-0.95,-2 -0.94,-2H1v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2H7.42c-0.13,0 -0.25,-0.11 -0.25,-0.25z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_24dp.xml
deleted file mode 100644
index 7918b03..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12.5,8H11v6l4.75,2.85 0.75,-1.23 -4,-2.37V8zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm0,16c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_add_24dp.xml
deleted file mode 100644
index 5e0d986..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm0,16c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zm1,-11h-2v3H8v2h3v3h2v-3h3v-2h-3V9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_off_24dp.xml
deleted file mode 100644
index b041418..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,6c3.87,0 7,3.13 7,7 0,0.84 -0.16,1.65 -0.43,2.4l1.52,1.52c0.58,-1.19 0.91,-2.51 0.91,-3.92 0,-4.97 -4.03,-9 -9,-9 -1.41,0 -2.73,0.33 -3.92,0.91L9.6,6.43C10.35,6.16 11.16,6 12,6zm10,-0.28l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM2.92,2.29L1.65,3.57 2.98,4.9l-1.11,0.93 1.42,1.42 1.11,-0.94 0.8,0.8C3.83,8.69 3,10.75 3,13c0,4.97 4.02,9 9,9 2.25,0 4.31,-0.83 5.89,-2.2l2.2,2.2 1.27,-1.27L3.89,3.27l-0.97,-0.98zm13.55,16.1C15.26,19.39 13.7,20 12,20c-3.87,0 -7,-3.13 - [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_on_24dp.xml
deleted file mode 100644
index c8e3f7a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_alarm_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm0,16c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zm-1.46,-5.47L8.41,12.4l-1.06,1.06 3.18,3.18 6,-6 -1.06,-1.06 -4.93,4.95z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_android_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_android_24dp.xml
deleted file mode 100644
index 5aaace3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_android_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5V19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5V19h1c0.55,0 1,-0.45 1,-1V8H6v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zm17,0c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zm-4.97,-5.84l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1.23 12.95,1 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_announcement_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_announcement_24dp.xml
deleted file mode 100644
index da2bb25..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_announcement_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-7,9h-2V5h2v6zm0,4h-2v-2h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_aspect_ratio_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_aspect_ratio_24dp.xml
deleted file mode 100644
index a6892ce..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_aspect_ratio_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3V7H5v5h2V9zm14,-6H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16.01H3V4.99h18v14.02z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assessment_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assessment_24dp.xml
deleted file mode 100644
index 1d393b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assessment_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM9,17H7v-7h2v7zm4,0h-2V7h2v10zm4,0h-2v-4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_24dp.xml
deleted file mode 100644
index 0717c5f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-7,0c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm2,14H7v-2h7v2zm3,-4H7v-2h10v2zm0,-4H7V7h10v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_ind_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_ind_24dp.xml
deleted file mode 100644
index 0e0db8d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_ind_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-7,0c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm0,4c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H6v-1.4c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1V19z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_late_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_late_24dp.xml
deleted file mode 100644
index cbf3474..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_late_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-6,15h-2v-2h2v2zm0,-4h-2V8h2v6zm-1,-9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_return_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_return_24dp.xml
deleted file mode 100644
index f320ca4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_return_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-7,0c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm4,12h-4v3l-5,-5 5,-5v3h4v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_returned_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_returned_24dp.xml
deleted file mode 100644
index 094cc9d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_returned_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-7,0c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm0,15l-5,-5h3V9h4v4h3l-5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_turned_in_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_turned_in_24dp.xml
deleted file mode 100644
index 818d8f1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_assignment_turned_in_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-7,0c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm-2,14l-4,-4 1.41,-1.41L10,14.17l6.59,-6.59L18,9l-8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_autorenew_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_autorenew_24dp.xml
deleted file mode 100644
index 25da1c4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_autorenew_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zm6.76,1.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_backup_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_backup_24dp.xml
deleted file mode 100644
index 9a79995..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_backup_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_book_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_book_24dp.xml
deleted file mode 100644
index 005d746..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_book_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,2H6c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_24dp.xml
deleted file mode 100644
index 61d6f32..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_outline_24dp.xml
deleted file mode 100644
index 9605ee6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bookmark_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2zm0,15l-5,-2.18L7,18V5h10v13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bug_report_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bug_report_24dp.xml
deleted file mode 100644
index 70894fb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_bug_report_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8H4v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3H20v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1H20V8zm-6,8h-4v-2h4v2zm0,-4h-4v-2h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_cached_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_cached_24dp.xml
deleted file mode 100644
index a987172..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_cached_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,8l-4,4h3c0,3.31 -2.69,6 -6,6 -1.01,0 -1.97,-0.25 -2.8,-0.7l-1.46,1.46C8.97,19.54 10.43,20 12,20c4.42,0 8,-3.58 8,-8h3l-4,-4zM6,12c0,-3.31 2.69,-6 6,-6 1.01,0 1.97,0.25 2.8,0.7l1.46,-1.46C15.03,4.46 13.57,4 12,4c-4.42,0 -8,3.58 -8,8H1l4,4 4,-4H6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_check_circle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_check_circle_24dp.xml
deleted file mode 100644
index 0ad8603..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_check_circle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm-2,15l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_class_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_class_24dp.xml
deleted file mode 100644
index 005d746..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_class_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,2H6c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_credit_card_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_credit_card_24dp.xml
deleted file mode 100644
index 3f58e10..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_credit_card_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6c0,-1.11 -0.89,-2 -2,-2zm0,14H4v-6h16v6zm0,-10H4V6h16v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_dashboard_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_dashboard_24dp.xml
deleted file mode 100644
index c9c8fa6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_dashboard_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,13h8V3H3v10zm0,8h8v-6H3v6zm10,0h8V11h-8v10zm0,-18v6h8V3h-8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_delete_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_delete_24dp.xml
deleted file mode 100644
index fc0a9fb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_delete_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_description_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_description_24dp.xml
deleted file mode 100644
index 9edc43e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_description_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,2H6c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2H18c1.1,0 2,-0.9 2,-2V8l-6,-6zm2,16H8v-2h8v2zm0,-4H8v-2h8v2zm-3,-5V3.5L18.5,9H13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_dns_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_dns_24dp.xml
deleted file mode 100644
index b72f863..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_dns_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,13H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zM7,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM20,3H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1V4c0,-0.55 -0.45,-1 -1,-1zM7,9c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_done_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_done_24dp.xml
deleted file mode 100644
index 02e8321..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_done_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_done_all_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_done_all_24dp.xml
deleted file mode 100644
index d910df1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_done_all_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zm4.24,-1.41L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_event_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_event_24dp.xml
deleted file mode 100644
index e0030fd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_event_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,12h-5v5h5v-5zM16,1v2H8V1H6v2H5c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2h-1V1h-2zm3,18H5V8h14v11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_exit_to_app_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_exit_to_app_24dp.xml
deleted file mode 100644
index 4aa81b0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_exit_to_app_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_explore_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_explore_24dp.xml
deleted file mode 100644
index affc1d7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_explore_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm2.19,12.19L6,18l3.81,-8.19L18,6l-3.81,8.19z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_extension_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_extension_24dp.xml
deleted file mode 100644
index 57a615d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_extension_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_face_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_face_24dp.xml
deleted file mode 100644
index 70e4ff1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_face_24dp.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.69,17.1c-0.74,0.58 -1.7,0.9 -2.69,0.9s-1.95,-0.32 -2.69,-0.9c-0.22,-0.17 -0.53,-0.13 -0.7,0.09 -0.17,0.22 -0.13,0.53 0.09,0.7 0.91,0.72 2.09,1.11 3.3,1.11s2.39,-0.39 3.31,-1.1c0.22,-0.17 0.26,-0.48 0.09,-0.7 -0.17,-0.23 -0.49,-0.26 -0.71,-0.1z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M8.5,12.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,0C5.37,0 0,5.37 0,12s5.37,12 12,12 12,-5.37 12,-12S18.63,0 12,0zm7.96,14.82c-1.09,3.74 -4.27,6.46 -8.04,6.46 -3.78,0 -6.96,-2.72 -8.04,-6.47 -1.19,-0.11 -2.13,-1.18 -2.13,-2.52 0,-1.27 0.85,-2.31 1.97,-2.5 2.09,-1.46 3.8,-3.49 4.09,-5.05v-0.01c1.35,2.63 6.3,5.19 11.83,5.06l0.3,-0.03c1.28,0 2.31,1.14 2.31,2.54 0,1.38 -1.02,2.51 -2.29,2.52z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.5,12.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_24dp.xml
deleted file mode 100644
index 1e38193..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_outline_24dp.xml
deleted file mode 100644
index 3a5916c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_favorite_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zm-4.4,15.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_find_in_page_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_find_in_page_24dp.xml
deleted file mode 100644
index 2a9c2fe..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_find_in_page_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,19.59V8l-6,-6H6c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2H18c0.45,0 0.85,-0.15 1.19,-0.4l-4.43,-4.43c-0.8,0.52 -1.74,0.83 -2.76,0.83 -2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5c0,1.02 -0.31,1.96 -0.83,2.75L20,19.59zM9,13c0,1.66 1.34,3 3,3s3,-1.34 3,-3 -1.34,-3 -3,-3 -3,1.34 -3,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_find_replace_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_find_replace_24dp.xml
deleted file mode 100644
index f11608e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_find_replace_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,6c1.38,0 2.63,0.56 3.54,1.46L12,10h6V4l-2.05,2.05C14.68,4.78 12.93,4 11,4c-3.53,0 -6.43,2.61 -6.92,6H6.1c0.46,-2.28 2.48,-4 4.9,-4zm5.64,9.14c0.66,-0.9 1.12,-1.97 1.28,-3.14H15.9c-0.46,2.28 -2.48,4 -4.9,4 -1.38,0 -2.63,-0.56 -3.54,-1.46L10,12H4v6l2.05,-2.05C7.32,17.22 9.07,18 11,18c1.55,0 2.98,-0.51 4.14,-1.36L20,21.49 21.49,20l-4.85,-4.86z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_back_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_back_24dp.xml
deleted file mode 100644
index 0fadaf5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_back_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,7H7v2h2V7zm0,4H7v2h2v-2zm0,-8c-1.11,0 -2,0.9 -2,2h2V3zm4,12h-2v2h2v-2zm6,-12v2h2c0,-1.1 -0.9,-2 -2,-2zm-6,0h-2v2h2V3zM9,17v-2H7c0,1.1 0.89,2 2,2zm10,-4h2v-2h-2v2zm0,-4h2V7h-2v2zm0,8c1.1,0 2,-0.9 2,-2h-2v2zM5,7H3v12c0,1.1 0.89,2 2,2h12v-2H5V7zm10,-2h2V3h-2v2zm0,12h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_front_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_front_24dp.xml
deleted file mode 100644
index 878e1ec..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_flip_to_front_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,13h2v-2H3v2zm0,4h2v-2H3v2zm2,4v-2H3c0,1.1 0.89,2 2,2zM3,9h2V7H3v2zm12,12h2v-2h-2v2zm4,-18H9c-1.11,0 -2,0.9 -2,2v10c0,1.1 0.89,2 2,2h10c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,12H9V5h10v10zm-8,6h2v-2h-2v2zm-4,0h2v-2H7v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_get_app_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_get_app_24dp.xml
deleted file mode 100644
index 9c90028..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_get_app_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_grade_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_grade_24dp.xml
deleted file mode 100644
index e2c53b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_grade_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_group_work_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_group_work_24dp.xml
deleted file mode 100644
index f7d2245..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_group_work_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM8,17.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM9.5,8c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5S9.5,9.38 9.5,8zm6.5,9.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_help_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_help_24dp.xml
deleted file mode 100644
index 5b524e3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_help_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,17h-2v-2h2v2zm2.07,-7.75l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2H8c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_highlight_remove_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_highlight_remove_24dp.xml
deleted file mode 100644
index e1302a7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_highlight_remove_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_history_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_history_24dp.xml
deleted file mode 100644
index a290938..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_history_24dp.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,3c-4.97,0 -9,4.03 -9,9H1l3.89,3.89 0.07,0.14L9,12H6c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm-1,5v5l4.28,2.54 0.72,-1.21 -3.5,-2.08V8H12z"
- android:fillAlpha=".9"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_home_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_home_24dp.xml
deleted file mode 100644
index 2a8e066..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_home_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_https_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_https_24dp.xml
deleted file mode 100644
index 9154ab9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_https_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zm-6,9c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zm3.1,-9H8.9V6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_info_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_info_24dp.xml
deleted file mode 100644
index cea2f8d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_info_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_info_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_info_outline_24dp.xml
deleted file mode 100644
index f500013..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_info_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,17h2v-6h-2v6zm1,-15C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2V7h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_input_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_input_24dp.xml
deleted file mode 100644
index 6b10c4e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_input_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,3.01H3c-1.1,0 -2,0.9 -2,2V9h2V4.99h18v14.03H3V15H1v4.01c0,1.1 0.9,1.98 2,1.98h18c1.1,0 2,-0.88 2,-1.98v-14c0,-1.11 -0.9,-2 -2,-2zM11,16l4,-4 -4,-4v3H1v2h10v3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_invert_colors_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_invert_colors_24dp.xml
deleted file mode 100644
index 8d98986..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_invert_colors_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.66,7.93L12,2.27 6.34,7.93c-3.12,3.12 -3.12,8.19 0,11.31C7.9,20.8 9.95,21.58 12,21.58c2.05,0 4.1,-0.78 5.66,-2.34 3.12,-3.12 3.12,-8.19 0,-11.31zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59s0.62,-3.11 1.76,-4.24L12,5.1v14.49z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_label_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_label_24dp.xml
deleted file mode 100644
index 7958502..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_label_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_label_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_label_outline_24dp.xml
deleted file mode 100644
index 06069cb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_label_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16zM16,17H5V7h11l3.55,5L16,17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_language_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_language_24dp.xml
deleted file mode 100644
index 6e17188..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_language_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zm6.93,6h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2H4.26zm0.82,2h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zm2.95,-8H5.08c0.96,-1.66 2.49,-2. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_launch_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_launch_24dp.xml
deleted file mode 100644
index c8b33be..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_launch_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_list_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_list_24dp.xml
deleted file mode 100644
index 689c602..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_list_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,13h2v-2H3v2zm0,4h2v-2H3v2zm0,-8h2V7H3v2zm4,4h14v-2H7v2zm0,4h14v-2H7v2zM7,7v2h14V7H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_24dp.xml
deleted file mode 100644
index 9154ab9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zm-6,9c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zm3.1,-9H8.9V6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_open_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_open_24dp.xml
deleted file mode 100644
index bcd6b79..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_open_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zm6,-9h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zm0,12H6V10h12v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_outline_24dp.xml
deleted file mode 100644
index 10e2fcf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_lock_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zm-6,-5.1c1.71,0 3.1,1.39 3.1,3.1v2H9V6h-0.1c0,-1.71 1.39,-3.1 3.1,-3.1zM18,20H6V10h12v10zm-6,-3c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_loyalty_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_loyalty_24dp.xml
deleted file mode 100644
index 9a92666..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_loyalty_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.41,11.58l-9,-9C12.05,2.22 11.55,2 11,2H4c-1.1,0 -2,0.9 -2,2v7c0,0.55 0.22,1.05 0.59,1.42l9,9c0.36,0.36 0.86,0.58 1.41,0.58 0.55,0 1.05,-0.22 1.41,-0.59l7,-7c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-0.55 -0.23,-1.06 -0.59,-1.42zM5.5,7C4.67,7 4,6.33 4,5.5S4.67,4 5.5,4 7,4.67 7,5.5 6.33,7 5.5,7zm11.77,8.27L13,19.54l-4.27,-4.27C8.28,14.81 8,14.19 8,13.5c0,-1.38 1.12,-2.5 2.5,-2.5 0.69,0 1.32,0.28 1.77,0.74l0.73,0.72 0.73,-0.73c0.45,-0.45 1.08,-0.73 1.77,-0.73 1.38,0 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_markunread_mailbox_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_markunread_mailbox_24dp.xml
deleted file mode 100644
index b947d8f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_markunread_mailbox_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6H10v6H8V4h6V0H6v6H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_note_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_note_add_24dp.xml
deleted file mode 100644
index 20a348e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_note_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,2H6c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2H18c1.1,0 2,-0.9 2,-2V8l-6,-6zm2,14h-3v3h-2v-3H8v-2h3v-3h2v3h3v2zm-3,-7V3.5L18.5,9H13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_browser_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_browser_24dp.xml
deleted file mode 100644
index 26ca0e3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_browser_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,4H5c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2H5V8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.89,-2 -2,-2zm-7,6l-4,4h3v6h2v-6h3l-4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_new_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_new_24dp.xml
deleted file mode 100644
index c8b33be..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_in_new_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_with_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_with_24dp.xml
deleted file mode 100644
index 9b115d9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_open_with_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,9h4V6h3l-5,-5 -5,5h3v3zm-1,1H6V7l-5,5 5,5v-3h3v-4zm14,2l-5,-5v3h-3v4h3v3l5,-5zm-9,3h-4v3H7l5,5 5,-5h-3v-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_pageview_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_pageview_24dp.xml
deleted file mode 100644
index 7eadee8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_pageview_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,8c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zm8,-5H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-1.41,16l-3.83,-3.83c-0.8,0.52 -1.74,0.83 -2.76,0.83 -2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5c0,1.02 -0.31,1.96 -0.83,2.75L19,17.59 17.59,19z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_payment_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_payment_24dp.xml
deleted file mode 100644
index 3f58e10..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_payment_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6c0,-1.11 -0.89,-2 -2,-2zm0,14H4v-6h16v6zm0,-10H4V6h16v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_camera_mic_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_camera_mic_24dp.xml
deleted file mode 100644
index fc8a6cb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_camera_mic_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,5h-3.17L15,3H9L7.17,5H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v-2.09c-2.83,-0.48 -5,-2.94 -5,-5.91h2c0,2.21 1.79,4 4,4s4,-1.79 4,-4h2c0,2.97 -2.17,5.43 -5,5.91V21h7c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2zm-6,8c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2V9c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_contact_cal_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_contact_cal_24dp.xml
deleted file mode 100644
index 10dfe40..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_contact_cal_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-1V1h-2v2H8V1H6v2H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-7,3c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H6v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_data_setting_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_data_setting_24dp.xml
deleted file mode 100644
index f48b14b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_data_setting_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.99,11.5c0.34,0 0.67,0.03 1,0.07L20,0 0,20h11.56c-0.04,-0.33 -0.07,-0.66 -0.07,-1 0,-4.14 3.36,-7.5 7.5,-7.5zm3.71,7.99c0.02,-0.16 0.04,-0.32 0.04,-0.49 0,-0.17 -0.01,-0.33 -0.04,-0.49l1.06,-0.83c0.09,-0.08 0.12,-0.21 0.06,-0.32l-1,-1.73c-0.06,-0.11 -0.19,-0.15 -0.31,-0.11l-1.24,0.5c-0.26,-0.2 -0.54,-0.37 -0.85,-0.49l-0.19,-1.32c-0.01,-0.12 -0.12,-0.21 -0.24,-0.21h-2c-0.12,0 -0.23,0.09 -0.25,0.21l-0.19,1.32c-0.3,0.13 -0.59,0.29 -0.85,0.49l-1.24,-0.5c-0.11,-0. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_device_info_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_device_info_24dp.xml
deleted file mode 100644
index 54536e4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_device_info_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,7h-2v2h2V7zm0,4h-2v6h2v-6zm4,-9.99L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_identity_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_identity_24dp.xml
deleted file mode 100644
index a34bd73..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_identity_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1H5.9V17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm0,9c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_media_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_media_24dp.xml
deleted file mode 100644
index e225808..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_media_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,6H0v5h0.01L0,20c0,1.1 0.9,2 2,2h18v-2H2V6zm20,-2h-8l-2,-2H6c-1.1,0 -1.99,0.9 -1.99,2L4,16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zM7,15l4.5,-6 3.5,4.51 2.5,-3.01L21,15H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_phone_msg_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_phone_msg_24dp.xml
deleted file mode 100644
index 842b965..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_phone_msg_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.58l2.2,-2.21c0.28,-0.27 0.36,-0.66 0.25,-1.01C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM12,3v10l3,-3h6V3h-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_scan_wifi_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_scan_wifi_24dp.xml
deleted file mode 100644
index 6a82875..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_perm_scan_wifi_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,3C6.95,3 3.15,4.85 0,7.23L12,22 24,7.25C20.85,4.87 17.05,3 12,3zm1,13h-2v-6h2v6zm-2,-8V6h2v2h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_24dp.xml
deleted file mode 100644
index 9b5d81e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_picture_in_picture_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,7h-8v6h8V7zm2,-4H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,1.98 2,1.98h18c1.1,0 2,-0.88 2,-1.98V5c0,-1.1 -0.9,-2 -2,-2zm0,16.01H3V4.98h18v14.03z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_polymer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_polymer_24dp.xml
deleted file mode 100644
index bee03f9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_polymer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,4h-4L7.11,16.63 4.5,12 9,4H5L0.5,12 5,20h4l7.89,-12.63L19.5,12 15,20h4l4.5,-8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_print_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_print_24dp.xml
deleted file mode 100644
index 9bfb690..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_print_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,8H5c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3 -3,-3zm-3,11H8v-5h8v5zm3,-7c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zm-1,-9H6v4h12V3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_query_builder_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_query_builder_24dp.xml
deleted file mode 100644
index cbbd47e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_query_builder_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_question_answer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_question_answer_24dp.xml
deleted file mode 100644
index 5e7d985..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_question_answer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,6h-2v9H6v2c0,0.55 0.45,1 1,1h11l4,4V7c0,-0.55 -0.45,-1 -1,-1zm-4,6V3c0,-0.55 -0.45,-1 -1,-1H3c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_receipt_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_receipt_24dp.xml
deleted file mode 100644
index 5a02c79..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_receipt_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,17H6v-2h12v2zm0,-4H6v-2h12v2zm0,-4H6V7h12v2zM3,22l1.5,-1.5L6,22l1.5,-1.5L9,22l1.5,-1.5L12,22l1.5,-1.5L15,22l1.5,-1.5L18,22l1.5,-1.5L21,22V2l-1.5,1.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2 7.5,3.5 6,2 4.5,3.5 3,2v20z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_redeem_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_redeem_24dp.xml
deleted file mode 100644
index 7f78847..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_redeem_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-2.18c0.11,-0.31 0.18,-0.65 0.18,-1 0,-1.66 -1.34,-3 -3,-3 -1.05,0 -1.96,0.54 -2.5,1.35l-0.5,0.67 -0.5,-0.68C10.96,2.54 10.05,2 9,2 7.34,2 6,3.34 6,5c0,0.35 0.07,0.69 0.18,1H4c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8c0,-1.11 -0.89,-2 -2,-2zm-5,-2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM9,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm11,15H4v-2h16v2zm0,-5H4V8h5.08L7,10.83 8.62,12 11,8.76l [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_reorder_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_reorder_24dp.xml
deleted file mode 100644
index 78be3e9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_reorder_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,15h18v-2H3v2zm0,4h18v-2H3v2zm0,-8h18V9H3v2zm0,-6v2h18V5H3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_report_problem_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_report_problem_24dp.xml
deleted file mode 100644
index 70ad19a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_report_problem_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M1,21h22L12,2 1,21zm12,-3h-2v-2h2v2zm0,-4h-2v-4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_restore_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_restore_24dp.xml
deleted file mode 100644
index 9963c56..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_restore_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,3c-4.97,0 -9,4.03 -9,9H1l3.89,3.89 0.07,0.14L9,12H6c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm-1,5v5l4.28,2.54 0.72,-1.21 -3.5,-2.08V8H12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_room_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_room_24dp.xml
deleted file mode 100644
index 04b8734..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_room_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zm0,9.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_schedule_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_schedule_24dp.xml
deleted file mode 100644
index 7bf67c6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_schedule_24dp.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
- android:fillAlpha=".9"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_search_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_search_24dp.xml
deleted file mode 100644
index ed83d8e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_search_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zm-6,0C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_24dp.xml
deleted file mode 100644
index e92e5b4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.6 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_applications_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_applications_24dp.xml
deleted file mode 100644
index 19e3442..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_applications_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm7,-7H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2V5c0,-1.1 -0.89,-2 -2,-2zm-1.75,9c0,0.23 -0.02,0.46 -0.05,0.68l1.48,1.16c0.13,0.11 0.17,0.3 0.08,0.45l-1.4,2.42c-0.09,0.15 -0.27,0.21 -0.43,0.15l-1.74,-0.7c-0.36,0.28 -0.76,0.51 -1.18,0.69l-0.26,1.85c-0.03,0.17 -0.18,0.3 -0.35,0.3h-2.8c-0.17,0 -0.32,-0.13 -0.35,-0.29l-0.26,-1.85c-0.43,-0.18 -0.82,-0.41 -1.18,-0.69l-1.74,0.7c-0.16,0.0 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_backup_restore_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_backup_restore_24dp.xml
deleted file mode 100644
index 874ab3b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_backup_restore_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zm-2,-9c-4.97,0 -9,4.03 -9,9H0l4,4 4,-4H5c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_bluetooth_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_bluetooth_24dp.xml
deleted file mode 100644
index 23ad97e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_bluetooth_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,24h2v-2h-2v2zm-4,0h2v-2H7v2zm8,0h2v-2h-2v2zm2.71,-18.29L12,0h-1v7.59L6.41,3 5,4.41 10.59,10 5,15.59 6.41,17 11,12.41V20h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,3.83l1.88,1.88L13,7.59V3.83zm1.88,10.46L13,16.17v-3.76l1.88,1.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_cell_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_cell_24dp.xml
deleted file mode 100644
index 89fdea7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_cell_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,24h2v-2H7v2zm4,0h2v-2h-2v2zm4,0h2v-2h-2v2zM16,0.01L8,0C6.9,0 6,0.9 6,2v16c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V2c0,-1.1 -0.9,-1.99 -2,-1.99zM16,16H8V4h8v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_display_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_display_24dp.xml
deleted file mode 100644
index 65bd27e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_display_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,3H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16.01H3V4.99h18v14.02zM8,16h2.5l1.5,1.5 1.5,-1.5H16v-2.5l1.5,-1.5 -1.5,-1.5V8h-2.5L12,6.5 10.5,8H8v2.5L6.5,12 8,13.5V16zm4,-7c1.66,0 3,1.34 3,3s-1.34,3 -3,3V9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_ethernet_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_ethernet_24dp.xml
deleted file mode 100644
index 57be67f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_ethernet_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.77,6.76L6.23,5.48 0.82,12l5.41,6.52 1.54,-1.28L3.42,12l4.35,-5.24zM7,13h2v-2H7v2zm10,-2h-2v2h2v-2zm-6,2h2v-2h-2v2zm6.77,-7.52l-1.54,1.28L20.58,12l-4.35,5.24 1.54,1.28L23.18,12l-5.41,-6.52z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_antenna_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_antenna_24dp.xml
deleted file mode 100644
index d73b70d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_antenna_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,5c-3.87,0 -7,3.13 -7,7h2c0,-2.76 2.24,-5 5,-5s5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zm1,9.29c0.88,-0.39 1.5,-1.26 1.5,-2.29 0,-1.38 -1.12,-2.5 -2.5,-2.5S9.5,10.62 9.5,12c0,1.02 0.62,1.9 1.5,2.29v3.3L7.59,21 9,22.41l3,-3 3,3L16.41,21 13,17.59v-3.3zM12,1C5.93,1 1,5.93 1,12h2c0,-4.97 4.03,-9 9,-9s9,4.03 9,9h2c0,-6.07 -4.93,-11 -11,-11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_component_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_component_24dp.xml
deleted file mode 100644
index bf8dcc5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_component_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4H1v6h6V6H5V2zm4,14c0,1.3 0.84,2.4 2,2.82V23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2H9v2zm-8,0c0,1.3 0.84,2.4 2,2.82V23h2v-4.18C6.16,18.4 7,17.3 7,16v-2H1v2zM21,6V2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4h-2v6h6V6h-2zm-8,-4c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4H9v6h6V6h-2V2zm4,14c0,1.3 0.84,2.4 2,2.82V23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2h-6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_composite_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_composite_24dp.xml
deleted file mode 100644
index bf8dcc5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_composite_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4H1v6h6V6H5V2zm4,14c0,1.3 0.84,2.4 2,2.82V23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2H9v2zm-8,0c0,1.3 0.84,2.4 2,2.82V23h2v-4.18C6.16,18.4 7,17.3 7,16v-2H1v2zM21,6V2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4h-2v6h6V6h-2zm-8,-4c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4H9v6h6V6h-2V2zm4,14c0,1.3 0.84,2.4 2,2.82V23h2v-4.18c1.16,-0.41 2,-1.51 2,-2.82v-2h-6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_hdmi_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_hdmi_24dp.xml
deleted file mode 100644
index a6c8cfb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_hdmi_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,7V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v3H5v6l3,6v3h8v-3l3,-6V7h-1zM8,4h8v3h-2V5h-1v2h-2V5h-1v2H8V4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_svideo_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_svideo_24dp.xml
deleted file mode 100644
index a062b66..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_input_svideo_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,11.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S5,10.67 5,11.5 5.67,13 6.5,13 8,12.33 8,11.5zm7,-5c0,-0.83 -0.67,-1.5 -1.5,-1.5h-3C9.67,5 9,5.67 9,6.5S9.67,8 10.5,8h3c0.83,0 1.5,-0.67 1.5,-1.5zM8.5,15c-0.83,0 -1.5,0.67 -1.5,1.5S7.67,18 8.5,18s1.5,-0.67 1.5,-1.5S9.33,15 8.5,15zM12,1C5.93,1 1,5.93 1,12s4.93,11 11,11 11,-4.93 11,-11S18.07,1 12,1zm0,20c-4.96,0 -9,-4.04 -9,-9s4.04,-9 9,-9 9,4.04 9,9 -4.04,9 -9,9zm5.5,-11c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_overscan_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_overscan_24dp.xml
deleted file mode 100644
index 2e0b0dc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_overscan_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.01,5.5L10,8h4l-1.99,-2.5zM18,10v4l2.5,-1.99L18,10zM6,10l-2.5,2.01L6,14v-4zm8,6h-4l2.01,2.5L14,16zm7,-13H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16.01H3V4.99h18v14.02z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_phone_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_phone_24dp.xml
deleted file mode 100644
index eb7881b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_phone_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,9h-2v2h2V9zm4,0h-2v2h2V9zm3,6.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.58l2.2,-2.21c0.28,-0.27 0.36,-0.66 0.25,-1.01C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,9v2h2V9h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_power_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_power_24dp.xml
deleted file mode 100644
index 8d64e7f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_power_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,24h2v-2H7v2zm4,0h2v-2h-2v2zm2,-22h-2v10h2V2zm3.56,2.44l-1.45,1.45C16.84,6.94 18,8.83 18,11c0,3.31 -2.69,6 -6,6s-6,-2.69 -6,-6c0,-2.17 1.16,-4.06 2.88,-5.12L7.44,4.44C5.36,5.88 4,8.28 4,11c0,4.42 3.58,8 8,8s8,-3.58 8,-8c0,-2.72 -1.36,-5.12 -3.44,-6.56zM15,24h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_remote_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_remote_24dp.xml
deleted file mode 100644
index ed5c7cc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_remote_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,9H9c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1V10c0,-0.55 -0.45,-1 -1,-1zm-3,6c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM7.05,6.05l1.41,1.41C9.37,6.56 10.62,6 12,6s2.63,0.56 3.54,1.46l1.41,-1.41C15.68,4.78 13.93,4 12,4s-3.68,0.78 -4.95,2.05zM12,0C8.96,0 6.21,1.23 4.22,3.22l1.41,1.41C7.26,3.01 9.51,2 12,2s4.74,1.01 6.36,2.64l1.41,-1.41C17.79,1.23 15.04,0 12,0z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_voice_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_voice_24dp.xml
deleted file mode 100644
index a3857ec..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_settings_voice_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,24h2v-2H7v2zm5,-11c1.66,0 2.99,-1.34 2.99,-3L15,4c0,-1.66 -1.34,-3 -3,-3S9,2.34 9,4v6c0,1.66 1.34,3 3,3zm-1,11h2v-2h-2v2zm4,0h2v-2h-2v2zm4,-14h-1.7c0,3 -2.54,5.1 -5.3,5.1S6.7,13 6.7,10H5c0,3.41 2.72,6.23 6,6.72V20h2v-3.28c3.28,-0.49 6,-3.31 6,-6.72z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_24dp.xml
deleted file mode 100644
index 97e4e62..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,6V4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2H2v13c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6h-6zm-6,-2h4v2h-4V4zM9,18V9l7.5,4L9,18z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_two_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_two_24dp.xml
deleted file mode 100644
index 8634fb3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shop_two_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,9H1v11c0,1.11 0.89,2 2,2h14c1.11,0 2,-0.89 2,-2H3V9zm15,-4V3c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2H5v11c0,1.11 0.89,2 2,2h14c1.11,0 2,-0.89 2,-2V5h-5zm-6,-2h4v2h-4V3zm0,12V8l5.5,3 -5.5,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_basket_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_basket_24dp.xml
deleted file mode 100644
index 2207e4f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_basket_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.21,9l-4.38,-6.56c-0.19,-0.28 -0.51,-0.42 -0.83,-0.42 -0.32,0 -0.64,0.14 -0.83,0.43L6.79,9H2c-0.55,0 -1,0.45 -1,1 0,0.09 0.01,0.18 0.04,0.27l2.54,9.27c0.23,0.84 1,1.46 1.92,1.46h13c0.92,0 1.69,-0.62 1.93,-1.46l2.54,-9.27L23,10c0,-0.55 -0.45,-1 -1,-1h-4.79zM9,9l3,-4.4L15,9H9zm3,8c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_cart_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_cart_24dp.xml
deleted file mode 100644
index f28015e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_shopping_cart_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2H7.42c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1H5.21l-0.94,-2H1zm16,16c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_speaker_notes_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_speaker_notes_24dp.xml
deleted file mode 100644
index de7f2bc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_speaker_notes_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM8,14H6v-2h2v2zm0,-3H6V9h2v2zm0,-3H6V6h2v2zm7,6h-5v-2h5v2zm3,-3h-8V9h8v2zm0,-3h-8V6h8v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_spellcheck_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_spellcheck_24dp.xml
deleted file mode 100644
index a61fef1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_spellcheck_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.45,16h2.09L9.43,3H7.57L2.46,16h2.09l1.12,-3h5.64l1.14,3zm-6.02,-5L8.5,5.48 10.57,11H6.43zm15.16,0.59l-8.09,8.09L9.83,16l-1.41,1.41 5.09,5.09L23,13l-1.41,-1.41z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_star_rate_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_star_rate_24dp.xml
deleted file mode 100644
index 44811e6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_star_rate_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,14.3l3.71,2.7 -1.42,-4.36L18,10h-4.55L12,5.5 10.55,10H6l3.71,2.64L8.29,17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_stars_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_stars_24dp.xml
deleted file mode 100644
index 94f4a56..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_stars_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zm4.24,16L12,15.45 7.77,18l1.12,-4.81 -3.73,-3.23 4.92,-0.42L12,5l1.92,4.53 4.92,0.42 -3.73,3.23L16.23,18z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_store_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_store_24dp.xml
deleted file mode 100644
index cef78e8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_store_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4v2h16V4zm1,10v-2l-1,-5H4l-1,5v2h1v6h10v-6h4v6h2v-6h1zm-9,4H6v-4h6v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_subject_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_subject_24dp.xml
deleted file mode 100644
index 0049c9c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_subject_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,17H4v2h10v-2zm6,-8H4v2h16V9zM4,15h16v-2H4v2zM4,5v2h16V5H4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_supervisor_account_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_supervisor_account_24dp.xml
deleted file mode 100644
index e775937..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_supervisor_account_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.5,12c1.38,0 2.49,-1.12 2.49,-2.5S17.88,7 16.5,7C15.12,7 14,8.12 14,9.5s1.12,2.5 2.5,2.5zM9,11c1.66,0 2.99,-1.34 2.99,-3S10.66,5 9,5C7.34,5 6,6.34 6,8s1.34,3 3,3zm7.5,3c-1.83,0 -5.5,0.92 -5.5,2.75V19h11v-2.25c0,-1.83 -3.67,-2.75 -5.5,-2.75zM9,13c-2.33,0 -7,1.17 -7,3.5V19h7v-2.25c0,-0.85 0.33,-2.34 2.37,-3.47C10.5,13.1 9.66,13 9,13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_horiz_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_horiz_24dp.xml
deleted file mode 100644
index de4cb80..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_horiz_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_24dp.xml
deleted file mode 100644
index d7374f4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,17.01V10h-2v7.01h-3L15,21l4,-3.99h-3zM9,3L5,6.99h3V14h2V6.99h3L9,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_circle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_circle_24dp.xml
deleted file mode 100644
index 89dca6e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_swap_vert_circle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM6.5,9L10,5.5 13.5,9H11v4H9V9H6.5zm11,6L14,18.5 10.5,15H13v-4h2v4h2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_system_update_tv_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_system_update_tv_24dp.xml
deleted file mode 100644
index 191c10a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_system_update_tv_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,16.5l4,-4h-3v-9h-2v9H8l4,4zm9,-13h-6v1.99h6v14.03H3V5.49h6V3.5H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2v-14c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_24dp.xml
deleted file mode 100644
index 77cbd77..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,3H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H3V5h10v4h8v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_unselected_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_unselected_24dp.xml
deleted file mode 100644
index e7a1cc9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_tab_unselected_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M1,9h2V7H1v2zm0,4h2v-2H1v2zm0,-8h2V3c-1.1,0 -2,0.9 -2,2zm8,16h2v-2H9v2zm-8,-4h2v-2H1v2zm2,4v-2H1c0,1.1 0.9,2 2,2zM21,3h-8v6h10V5c0,-1.1 -0.9,-2 -2,-2zm0,14h2v-2h-2v2zM9,5h2V3H9v2zM5,21h2v-2H5v2zM5,5h2V3H5v2zm16,16c1.1,0 2,-0.9 2,-2h-2v2zm0,-8h2v-2h-2v2zm-8,8h2v-2h-2v2zm4,0h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_theaters_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_theaters_24dp.xml
deleted file mode 100644
index 7c662e7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_theaters_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8,17H6v-2h2v2zm0,-4H6v-2h2v2zm0,-4H6V7h2v2zm10,8h-2v-2h2v2zm0,-4h-2v-2h2v2zm0,-4h-2V7h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_down_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_down_24dp.xml
deleted file mode 100644
index 85a2639..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_down_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,3H6c-0.83,0 -1.54,0.5 -1.84,1.22l-3.02,7.05c-0.09,0.23 -0.14,0.47 -0.14,0.73v1.91l0.01,0.01L1,14c0,1.1 0.9,2 2,2h6.31l-0.95,4.57 -0.03,0.32c0,0.41 0.17,0.79 0.44,1.06L9.83,23l6.59,-6.59c0.36,-0.36 0.58,-0.86 0.58,-1.41V5c0,-1.1 -0.9,-2 -2,-2zm4,0v12h4V3h-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_up_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_up_24dp.xml
deleted file mode 100644
index 05d5fd3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumb_up_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M1,21h4V9H1v12zm22,-11c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-1.91l-0.01,-0.01L23,10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumbs_up_down_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumbs_up_down_24dp.xml
deleted file mode 100644
index 9b574e6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_thumbs_up_down_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,6c0,-0.55 -0.45,-1 -1,-1H5.82l0.66,-3.18 0.02,-0.23c0,-0.31 -0.13,-0.59 -0.33,-0.8L5.38,0 0.44,4.94C0.17,5.21 0,5.59 0,6v6.5c0,0.83 0.67,1.5 1.5,1.5h6.75c0.62,0 1.15,-0.38 1.38,-0.91l2.26,-5.29c0.07,-0.17 0.11,-0.36 0.11,-0.55V6zm10.5,4h-6.75c-0.62,0 -1.15,0.38 -1.38,0.91l-2.26,5.29c-0.07,0.17 -0.11,0.36 -0.11,0.55V18c0,0.55 0.45,1 1,1h5.18l-0.66,3.18 -0.02,0.24c0,0.31 0.13,0.59 0.33,0.8l0.79,0.78 4.94,-4.94c0.27,-0.27 0.44,-0.65 0.44,-1.06v-6.5c0,-0.83 -0.6 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_toc_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_toc_24dp.xml
deleted file mode 100644
index c9f8b1b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_toc_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,9h14V7H3v2zm0,4h14v-2H3v2zm0,4h14v-2H3v2zm16,0h2v-2h-2v2zm0,-10v2h2V7h-2zm0,6h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_today_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_today_24dp.xml
deleted file mode 100644
index ddf162e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_today_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3h-1V1h-2v2H8V1H6v2H5c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V8h14v11zM7,10h5v5H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_track_changes_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_track_changes_24dp.xml
deleted file mode 100644
index d402da3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_track_changes_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M19.07,4.93l-1.41,1.41C19.1,7.79 20,9.79 20,12c0,4.42 -3.58,8 -8,8s-8,-3.58 -8,-8c0,-4.08 3.05,-7.44 7,-7.93v2.02C8.16,6.57 6,9.03 6,12c0,3.31 2.69,6 6,6s6,-2.69 6,-6c0,-1.66 -0.67,-3.16 -1.76,-4.24l-1.41,1.41C15.55,9.9 16,10.9 16,12c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4c0,-1.86 1.28,-3.41 3,-3.86v2.14c-0.6,0.35 -1,0.98 -1,1.72 0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-0.74 -0.4,-1.38 -1,-1.72V2h-1C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10c0,-2.76 -1.12,-5.26 -2.93,-7.07z"
- android:fillColor="#231F20"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_translate_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_translate_24dp.xml
deleted file mode 100644
index 592b094..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_translate_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zm-2.62,7l1.62,-4.33L19.12,17h-3.24z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_down_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_down_24dp.xml
deleted file mode 100644
index 3a6ba95..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_down_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,18l2.29,-2.29 -4.88,-4.88 -4,4L2,7.41 3.41,6l6,6 4,-4 6.3,6.29L22,12v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_neutral_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_neutral_24dp.xml
deleted file mode 100644
index f5b62c6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_neutral_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,12l-4,-4v3H3v2h15v3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_up_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_up_24dp.xml
deleted file mode 100644
index cd5e654..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_trending_up_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,6l2.29,2.29 -4.88,4.88 -4,-4L2,16.59 3.41,18l6,-6 4,4 6.3,-6.29L22,12V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_24dp.xml
deleted file mode 100644
index 61d6f32..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_not_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_not_24dp.xml
deleted file mode 100644
index 9605ee6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_turned_in_not_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2zm0,15l-5,-2.18L7,18V5h10v13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_verified_user_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_verified_user_24dp.xml
deleted file mode 100644
index 0893b4d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_verified_user_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12V5l-9,-4zm-2,16l-4,-4 1.41,-1.41L10,14.17l6.59,-6.59L18,9l-8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_agenda_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_agenda_24dp.xml
deleted file mode 100644
index 3f40bc6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_agenda_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,13H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zm0,-10H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1V4c0,-0.55 -0.45,-1 -1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_array_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_array_24dp.xml
deleted file mode 100644
index c90c3f9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_array_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,18h3V5H4v13zM18,5v13h3V5h-3zM8,18h9V5H8v13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_carousel_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_carousel_24dp.xml
deleted file mode 100644
index 4df06be..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_carousel_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,19h10V4H7v15zm-5,-2h4V6H2v11zM18,6v11h4V6h-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_column_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_column_24dp.xml
deleted file mode 100644
index b9dc907..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_column_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,18h5V5h-5v13zm-6,0h5V5H4v13zM16,5v13h5V5h-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_day_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_day_24dp.xml
deleted file mode 100644
index 0734594..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_day_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,21h19v-3H2v3zM20,8H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zM2,3v3h19V3H2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_headline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_headline_24dp.xml
deleted file mode 100644
index 512155c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_headline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,15h17v-2H4v2zm0,4h17v-2H4v2zm0,-8h17V9H4v2zm0,-6v2h17V5H4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_list_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_list_24dp.xml
deleted file mode 100644
index 089b28e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_list_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,14h4v-4H4v4zm0,5h4v-4H4v4zM4,9h4V5H4v4zm5,5h12v-4H9v4zm0,5h12v-4H9v4zM9,5v4h12V5H9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_module_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_module_24dp.xml
deleted file mode 100644
index 701708c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_module_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,11h5V5H4v6zm0,7h5v-6H4v6zm6,0h5v-6h-5v6zm6,0h5v-6h-5v6zm-6,-7h5V5h-5v6zm6,-6v6h5V5h-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_quilt_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_quilt_24dp.xml
deleted file mode 100644
index e2f1072..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_quilt_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,18h5v-6h-5v6zm-6,0h5V5H4v13zm12,0h5v-6h-5v6zM10,5v6h11V5H10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_stream_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_stream_24dp.xml
deleted file mode 100644
index 5b8c90f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_stream_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,18h17v-6H4v6zM4,5v6h17V5H4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_week_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_week_24dp.xml
deleted file mode 100644
index 9c0d632..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_view_week_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,5H3c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1V6c0,-0.55 -0.45,-1 -1,-1zm14,0h-3c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1V6c0,-0.55 -0.45,-1 -1,-1zm-7,0h-3c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1V6c0,-0.55 -0.45,-1 -1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_24dp.xml
deleted file mode 100644
index 46a571c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zm0,-8c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_off_24dp.xml
deleted file mode 100644
index 209bdec..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_visibility_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_giftcard_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_giftcard_24dp.xml
deleted file mode 100644
index 7f78847..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_giftcard_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-2.18c0.11,-0.31 0.18,-0.65 0.18,-1 0,-1.66 -1.34,-3 -3,-3 -1.05,0 -1.96,0.54 -2.5,1.35l-0.5,0.67 -0.5,-0.68C10.96,2.54 10.05,2 9,2 7.34,2 6,3.34 6,5c0,0.35 0.07,0.69 0.18,1H4c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8c0,-1.11 -0.89,-2 -2,-2zm-5,-2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM9,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm11,15H4v-2h16v2zm0,-5H4V8h5.08L7,10.83 8.62,12 11,8.76l [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_membership_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_membership_24dp.xml
deleted file mode 100644
index 00af65d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_membership_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.11,0 -2,0.89 -2,2v11c0,1.11 0.89,2 2,2h4v5l4,-2 4,2v-5h4c1.11,0 2,-0.89 2,-2V4c0,-1.11 -0.89,-2 -2,-2zm0,13H4v-2h16v2zm0,-5H4V4h16v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_travel_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_travel_24dp.xml
deleted file mode 100644
index 7ee959d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_wallet_travel_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-3V4c0,-1.11 -0.89,-2 -2,-2H9c-1.11,0 -2,0.89 -2,2v2H4c-1.11,0 -2,0.89 -2,2v11c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8c0,-1.11 -0.89,-2 -2,-2zM9,4h6v2H9V4zm11,15H4v-2h16v2zm0,-5H4V8h3v2h2V8h6v2h2V8h3v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_work_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/action/ic_work_24dp.xml
deleted file mode 100644
index 363b68b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/action/ic_work_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-4V4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2H4c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8c0,-1.11 -0.89,-2 -2,-2zm-6,0h-4V4h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_24dp.xml
deleted file mode 100644
index ef1a8f4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/alert/ic_error_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-2h2v2zm0,-4h-2V7h2v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/alert/ic_warning_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/alert/ic_warning_24dp.xml
deleted file mode 100644
index 70ad19a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/alert/ic_warning_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M1,21h22L12,2 1,21zm12,-3h-2v-2h2v2zm0,-4h-2v-4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_album_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_album_24dp.xml
deleted file mode 100644
index 8b3ecb9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_album_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,14.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zm0,-5.5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_av_timer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_av_timer_24dp.xml
deleted file mode 100644
index f954282..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_av_timer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,17c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1zm0,-14v4h2V5.08c3.39,0.49 6,3.39 6,6.92 0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-1.68 0.59,-3.22 1.58,-4.42L12,13l1.41,-1.41 -6.8,-6.8v0.02C4.42,6.45 3,9.05 3,12c0,4.97 4.02,9 9,9 4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9h-1zm7,9c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1 0.45,1 1,1 1,-0.45 1,-1zM6,12c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_closed_caption_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_closed_caption_24dp.xml
deleted file mode 100644
index 1cc3ea4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_closed_caption_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,4H5c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm-8,7H9.5v-0.5h-2v3h2V13H11v1c0,0.55 -0.45,1 -1,1H7c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1zm7,0h-1.5v-0.5h-2v3h2V13H18v1c0,0.55 -0.45,1 -1,1h-3c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_equalizer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_equalizer_24dp.xml
deleted file mode 100644
index d94c27d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_equalizer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,20h4V4h-4v16zm-6,0h4v-8H4v8zM16,9v11h4V9h-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_explicit_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_explicit_24dp.xml
deleted file mode 100644
index 7dcc5af..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_explicit_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-4,6h-4v2h4v2h-4v2h4v2H9V7h6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_forward_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_forward_24dp.xml
deleted file mode 100644
index 248556a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_forward_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,18l8.5,-6L4,6v12zm9,-12v12l8.5,-6L13,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_rewind_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_rewind_24dp.xml
deleted file mode 100644
index bd09f10..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_fast_rewind_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,18V6l-8.5,6 8.5,6zm0.5,-6l8.5,6V6l-8.5,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_games_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_games_24dp.xml
deleted file mode 100644
index 8891427..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_games_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,7.5V2H9v5.5l3,3 3,-3zM7.5,9H2v6h5.5l3,-3 -3,-3zM9,16.5V22h6v-5.5l-3,-3 -3,3zM16.5,9l-3,3 3,3H22V9h-5.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_hearing_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_hearing_24dp.xml
deleted file mode 100644
index f696b00..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_hearing_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_high_quality_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_high_quality_24dp.xml
deleted file mode 100644
index 31c633d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_high_quality_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,4H5c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm-8,11H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7,-1c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5V15H14c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zm-3.5,-0.5h2v-3h-2v3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_loop_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_loop_24dp.xml
deleted file mode 100644
index 5ad1561..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_loop_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,4V1L8,5l4,4V6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zm0,14c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_24dp.xml
deleted file mode 100644
index e2e95d5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zm5.3,-3c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_none_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_none_24dp.xml
deleted file mode 100644
index ce4d384..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_none_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zm-1.2,-9.1c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2l-0.01,6.2c0,0.66 -0.53,1.2 -1.19,1.2 -0.66,0 -1.2,-0.54 -1.2,-1.2V4.9zm6.5,6.1c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_off_24dp.xml
deleted file mode 100644
index 73bf970..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_mic_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,11h-1.7c0,0.74 -0.16,1.43 -0.43,2.05l1.23,1.23c0.56,-0.98 0.9,-2.09 0.9,-3.28zm-4.02,0.17c0,-0.06 0.02,-0.11 0.02,-0.17V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v0.18l5.98,5.99zM4.27,3L3,4.27l6.01,6.01V11c0,1.66 1.33,3 2.99,3 0.22,0 0.44,-0.03 0.65,-0.08l1.66,1.66c-0.71,0.33 -1.5,0.52 -2.31,0.52 -2.76,0 -5.3,-2.1 -5.3,-5.1H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c0.91,-0.13 1.77,-0.45 2.54,-0.9L19.73,21 21,19.73 4.27,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_movie_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_movie_24dp.xml
deleted file mode 100644
index 6d4c52e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_movie_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,4l2,4h-3l-2,-4h-2l2,4h-3l-2,-4H8l2,4H7L5,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4h-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_add_24dp.xml
deleted file mode 100644
index 78eaca1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-1,9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_books_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_books_24dp.xml
deleted file mode 100644
index aa446b2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_books_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-1,9H9V9h10v2zm-4,4H9v-2h6v2zm4,-8H9V5h10v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_music_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_music_24dp.xml
deleted file mode 100644
index 0465e98..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_my_library_music_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-2,5h-3v5.5c0,1.38 -1.12,2.5 -2.5,2.5S10,13.88 10,12.5s1.12,-2.5 2.5,-2.5c0.57,0 1.08,0.19 1.5,0.51V5h4v2zM4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_new_releases_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_new_releases_24dp.xml
deleted file mode 100644
index 8fbe033..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_new_releases_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M23,12l-2.44,-2.78 0.34,-3.68 -3.61,-0.82 -1.89,-3.18L12,3 8.6,1.54 6.71,4.72l-3.61,0.81 0.34,3.68L1,12l2.44,2.78 -0.34,3.69 3.61,0.82 1.89,3.18L12,21l3.4,1.46 1.89,-3.18 3.61,-0.82 -0.34,-3.68L23,12zm-10,5h-2v-2h2v2zm0,-4h-2V7h2v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_not_interested_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_not_interested_24dp.xml
deleted file mode 100644
index 90eae2a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_not_interested_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8 0,-1.85 0.63,-3.55 1.69,-4.9L16.9,18.31C15.55,19.37 13.85,20 12,20zm6.31,-3.1L7.1,5.69C8.45,4.63 10.15,4 12,4c4.42,0 8,3.58 8,8 0,1.85 -0.63,3.55 -1.69,4.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_24dp.xml
deleted file mode 100644
index c843731..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,19h4V5H6v14zm8,-14v14h4V5h-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_fill_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_fill_24dp.xml
deleted file mode 100644
index a91240e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_fill_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm-1,14H9V8h2v8zm4,0h-2V8h2v8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_outline_24dp.xml
deleted file mode 100644
index f9232e0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_pause_circle_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,16h2V8H9v8zm3,-14C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zm1,-4h2V8h-2v8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_arrow_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_arrow_24dp.xml
deleted file mode 100644
index f9f849e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_arrow_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,5v14l11,-7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_fill_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_fill_24dp.xml
deleted file mode 100644
index 9c6d722..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_fill_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm-2,14.5v-9l6,4.5 -6,4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_outline_24dp.xml
deleted file mode 100644
index 65f6993..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_circle_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_shopping_bag_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_shopping_bag_24dp.xml
deleted file mode 100644
index 97e4e62..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_play_shopping_bag_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,6V4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2H2v13c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6h-6zm-6,-2h4v2h-4V4zM9,18V9l7.5,4L9,18z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_24dp.xml
deleted file mode 100644
index a2ddbbb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_playlist_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,10H2v2h12v-2zm0,-4H2v2h12V6zm4,8v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2H2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_24dp.xml
deleted file mode 100644
index 78eaca1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-1,9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_music_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_music_24dp.xml
deleted file mode 100644
index 7169362..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_queue_music_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,6H3v2h12V6zm0,4H3v2h12v-2zM3,16h8v-2H3v2zM17,6v8.18c-0.31,-0.11 -0.65,-0.18 -1,-0.18 -1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3V8h3V6h-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_radio_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_radio_24dp.xml
deleted file mode 100644
index ac67acc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_radio_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.24,6.15C2.51,6.43 2,7.17 2,8v12c0,1.1 0.89,2 2,2h16c1.11,0 2,-0.9 2,-2V8c0,-1.11 -0.89,-2 -2,-2H8.3l8.26,-3.34L15.88,1 3.24,6.15zM7,20c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zm13,-8h-2v-2h-2v2H4V8h16v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_recent_actors_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_recent_actors_24dp.xml
deleted file mode 100644
index 3d93f4c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_recent_actors_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,5v14h2V5h-2zm-4,14h2V5h-2v14zM14,5H2c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1V6c0,-0.55 -0.45,-1 -1,-1zM8,7.75c1.24,0 2.25,1.01 2.25,2.25S9.24,12.25 8,12.25 5.75,11.24 5.75,10 6.76,7.75 8,7.75zM12.5,17h-9v-0.75c0,-1.5 3,-2.25 4.5,-2.25s4.5,0.75 4.5,2.25V17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_24dp.xml
deleted file mode 100644
index 0362e27..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,7h10v3l4,-4 -4,-4v3H5v6h2V7zm10,10H7v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_one_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_one_24dp.xml
deleted file mode 100644
index bf42eaf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_repeat_one_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,7h10v3l4,-4 -4,-4v3H5v6h2V7zm10,10H7v-3l-4,4 4,4v-3h12v-6h-2v4zm-4,-2V9h-1l-2,1v1h1.5v4H13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_24dp.xml
deleted file mode 100644
index 176e43a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_replay_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6 -6,-2.69 -6,-6H4c0,4.42 3.58,8 8,8s8,-3.58 8,-8 -3.58,-8 -8,-8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_shuffle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_shuffle_24dp.xml
deleted file mode 100644
index 9940b5b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_shuffle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.59,9.17L5.41,4 4,5.41l5.17,5.17 1.42,-1.41zM14.5,4l2.04,2.04L4,18.59 5.41,20 17.96,7.46 20,9.5V4h-5.5zm0.33,9.41l-1.41,1.41 3.13,3.13L14.5,20H20v-5.5l-2.04,2.04 -3.13,-3.13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_next_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_next_24dp.xml
deleted file mode 100644
index 6ccd524..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_next_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_previous_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_previous_24dp.xml
deleted file mode 100644
index 9b3d6f8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_skip_previous_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,6h2v12H6zm3.5,6l8.5,6V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_snooze_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_snooze_24dp.xml
deleted file mode 100644
index 09fa95b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_snooze_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm0,16c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zm-3,-9h3.63L9,15.2V17h6v-2h-3.63L15,10.8V9H9v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_stop_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_stop_24dp.xml
deleted file mode 100644
index 0bd2ca4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_stop_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,6h12v12H6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_subtitles_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_subtitles_24dp.xml
deleted file mode 100644
index 45cdb1e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_subtitles_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zM4,12h4v2H4v-2zm10,6H4v-2h10v2zm6,0h-4v-2h4v2zm0,-4H10v-2h10v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_surround_sound_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_surround_sound_24dp.xml
deleted file mode 100644
index bb19563..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_surround_sound_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zM7.76,16.24l-1.41,1.41C4.78,16.1 4,14.05 4,12c0,-2.05 0.78,-4.1 2.34,-5.66l1.41,1.41C6.59,8.93 6,10.46 6,12s0.59,3.07 1.76,4.24zM12,16c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zm5.66,1.66l-1.41,-1.41C17.41,15.07 18,13.54 18,12s-0.59,-3.07 -1.76,-4.24l1.41,-1.41C19.22,7.9 20,9.95 20,12c0,2.05 -0.78,4.1 -2.34,5.66zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_video_collection_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_video_collection_24dp.xml
deleted file mode 100644
index 681053d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_video_collection_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_24dp.xml
deleted file mode 100644
index a51b841..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,10.5V7c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_off_24dp.xml
deleted file mode 100644
index b6dc40c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_videocam_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,6.5l-4,4V7c0,-0.55 -0.45,-1 -1,-1H9.82L21,17.18V6.5zM3.27,2L2,3.27 4.73,6H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.21,0 0.39,-0.08 0.54,-0.18L19.73,21 21,19.73 3.27,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_down_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_down_24dp.xml
deleted file mode 100644
index 847964d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_down_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM5,9v6h4l5,5V4L9,9H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_mute_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_mute_24dp.xml
deleted file mode 100644
index 6b12444..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_mute_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,9v6h4l5,5V4l-5,5H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_off_24dp.xml
deleted file mode 100644
index 634a7c7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zm2.5,0c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9H3v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18V4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_up_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_up_24dp.xml
deleted file mode 100644
index c8c4271..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_volume_up_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,9v6h4l5,5V4L7,9H3zm13.5,3c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_web_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/av/ic_web_24dp.xml
deleted file mode 100644
index 621df45..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/av/ic_web_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm-5,14H4v-4h11v4zm0,-5H4V9h11v4zm5,5h-4V9h4v9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_business_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_business_24dp.xml
deleted file mode 100644
index 62bc39f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_business_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,7V3H2v18h20V7H12zM6,19H4v-2h2v2zm0,-4H4v-2h2v2zm0,-4H4V9h2v2zm0,-4H4V5h2v2zm4,12H8v-2h2v2zm0,-4H8v-2h2v2zm0,-4H8V9h2v2zm0,-4H8V5h2v2zm10,12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2,-8h-2v2h2v-2zm0,4h-2v2h2v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_24dp.xml
deleted file mode 100644
index 3a3ba47..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_end_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_end_24dp.xml
deleted file mode 100644
index 0f3a0ec..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_end_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_made_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_made_24dp.xml
deleted file mode 100644
index 1048f49..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_made_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,5v2h6.59L4,18.59 5.41,20 17,8.41V15h2V5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_merge_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_merge_24dp.xml
deleted file mode 100644
index 4eee325..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_merge_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,20.41L18.41,19 15,15.59 13.59,17 17,20.41zM7.5,8H11v5.59L5.59,19 7,20.41l6,-6V8h3.5L12,3.5 7.5,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_24dp.xml
deleted file mode 100644
index a462e9f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_missed_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.59,7L12,14.59 6.41,9H11V7H3v8h2v-4.59l7,7 9,-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_received_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_received_24dp.xml
deleted file mode 100644
index ac65fd2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_received_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,5.41L18.59,4 7,15.59V9H5v10h10v-2H8.41z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_split_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_split_24dp.xml
deleted file mode 100644
index c34b117..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_call_split_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,4l2.29,2.29 -2.88,2.88 1.42,1.42 2.88,-2.88L20,10V4zm-4,0H4v6l2.29,-2.29 4.71,4.7V20h2v-8.41l-5.29,-5.3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_24dp.xml
deleted file mode 100644
index c28e3cf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_chat_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2H6V9zm8,5H6v-2h8v2zm4,-6H6V6h12v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_clear_all_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_clear_all_24dp.xml
deleted file mode 100644
index c0829b4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_clear_all_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,13h14v-2H5v2zm-2,4h14v-2H3v2zM7,7v2h14V7H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_comment_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_comment_24dp.xml
deleted file mode 100644
index e73d2c8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_comment_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14H6v-2h12v2zm0,-3H6V9h12v2zm0,-3H6V6h12v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_contacts_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_contacts_24dp.xml
deleted file mode 100644
index 8a2b3e8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_contacts_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,0H4v2h16V0zM4,24h16v-2H4v2zM20,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm-8,2.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17H7v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5V17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialer_sip_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialer_sip_24dp.xml
deleted file mode 100644
index 787f93a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialer_sip_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3h-1v5h1V3zm-2,2h-2V4h2V3h-3v3h2v1h-2v1h3V5zm3,-2v5h1V6h2V3h-3zm2,2h-1V4h1v1zm0,10.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.01,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.27,-0.26 0.35,-0.65 0.24,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialpad_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialpad_24dp.xml
deleted file mode 100644
index faa4d8b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dialpad_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,19c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm0,6c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm0,6c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm12,-8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zm-6,8c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm6,0c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm0,-6c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dnd_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dnd_on_24dp.xml
deleted file mode 100644
index 90eae2a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_dnd_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8 0,-1.85 0.63,-3.55 1.69,-4.9L16.9,18.31C15.55,19.37 13.85,20 12,20zm6.31,-3.1L7.1,5.69C8.45,4.63 10.15,4 12,4c4.42,0 8,3.58 8,8 0,1.85 -0.63,3.55 -1.69,4.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_email_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_email_24dp.xml
deleted file mode 100644
index bf2ac7d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_email_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,4l-8,5 -8,-5V6l8,5 8,-5v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_forum_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_forum_24dp.xml
deleted file mode 100644
index 5e7d985..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_forum_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,6h-2v9H6v2c0,0.55 0.45,1 1,1h11l4,4V7c0,-0.55 -0.45,-1 -1,-1zm-4,6V3c0,-0.55 -0.45,-1 -1,-1H3c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_export_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_export_24dp.xml
deleted file mode 100644
index e0004d3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_import_export_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,3L5,6.99h3V14h2V6.99h3L9,3zm7,14.01V10h-2v7.01h-3L15,21l4,-3.99h-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_off_24dp.xml
deleted file mode 100644
index 0052ad8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.65,20.87l-2.35,-2.35 -6.3,-6.29 -3.56,-3.57 -1.42,-1.41L4.27,4.5 3,5.77l2.78,2.78c-2.55,3.14 -2.36,7.76 0.56,10.69C7.9,20.8 9.95,21.58 12,21.58c1.79,0 3.57,-0.59 5.03,-1.78l2.7,2.7L21,21.23l-0.35,-0.36zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59c0,-1.32 0.43,-2.57 1.21,-3.6L12,14.77v4.82zM12,5.1v4.58l7.25,7.26c1.37,-2.96 0.84,-6.57 -1.6,-9.01L12,2.27l-3.7,3.7 1.41,1.41L12,5.1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_on_24dp.xml
deleted file mode 100644
index 8d98986..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_invert_colors_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.66,7.93L12,2.27 6.34,7.93c-3.12,3.12 -3.12,8.19 0,11.31C7.9,20.8 9.95,21.58 12,21.58c2.05,0 4.1,-0.78 5.66,-2.34 3.12,-3.12 3.12,-8.19 0,-11.31zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59s0.62,-3.11 1.76,-4.24L12,5.1v14.49z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_live_help_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_live_help_24dp.xml
deleted file mode 100644
index de9b40a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_live_help_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,2H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-6,16h-2v-2h2v2zm2.07,-7.75l-0.9,0.92C13.45,11.9 13,12.5 13,14h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2H8c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_off_24dp.xml
deleted file mode 100644
index 09a700a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,6.5c1.38,0 2.5,1.12 2.5,2.5 0,0.74 -0.33,1.39 -0.83,1.85l3.63,3.63c0.98,-1.86 1.7,-3.8 1.7,-5.48 0,-3.87 -3.13,-7 -7,-7 -1.98,0 -3.76,0.83 -5.04,2.15l3.19,3.19c0.46,-0.52 1.11,-0.84 1.85,-0.84zm4.37,9.6l-4.63,-4.63 -0.11,-0.11L3.27,3 2,4.27l3.18,3.18C5.07,7.95 5,8.47 5,9c0,5.25 7,13 7,13s1.67,-1.85 3.38,-4.35L18.73,21 20,19.73l-3.63,-3.63z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_on_24dp.xml
deleted file mode 100644
index 04b8734..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_location_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zm0,9.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_message_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_message_24dp.xml
deleted file mode 100644
index afbdf64..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_message_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-2,12H6v-2h12v2zm0,-3H6V9h12v2zm0,-3H6V6h12v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_messenger_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_messenger_24dp.xml
deleted file mode 100644
index 5134282..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_messenger_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_no_sim_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_no_sim_24dp.xml
deleted file mode 100644
index b36e505..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_no_sim_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.99,5c0,-1.1 -0.89,-2 -1.99,-2h-7L7.66,5.34 19,16.68 18.99,5zM3.65,3.88L2.38,5.15 5,7.77V19c0,1.1 0.9,2 2,2h10.01c0.35,0 0.67,-0.1 0.96,-0.26l1.88,1.88 1.27,-1.27L3.65,3.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_phone_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_phone_24dp.xml
deleted file mode 100644
index 3a3ba47..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_phone_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_portable_wifi_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_portable_wifi_off_24dp.xml
deleted file mode 100644
index 6bb84dd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_portable_wifi_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.56,14.24c0.28,-0.69 0.44,-1.45 0.44,-2.24 0,-3.31 -2.69,-6 -6,-6 -0.79,0 -1.55,0.16 -2.24,0.44l1.62,1.62c0.2,-0.03 0.41,-0.06 0.62,-0.06 2.21,0 4,1.79 4,4 0,0.21 -0.02,0.42 -0.05,0.63l1.61,1.61zM12,4c4.42,0 8,3.58 8,8 0,1.35 -0.35,2.62 -0.95,3.74l1.47,1.47C21.46,15.69 22,13.91 22,12c0,-5.52 -4.48,-10 -10,-10 -1.91,0 -3.69,0.55 -5.21,1.47l1.46,1.46C9.37,4.34 10.65,4 12,4zM3.27,2.5L2,3.77l2.1,2.1C2.79,7.57 2,9.69 2,12c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,17. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_quick_contacts_dialer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_quick_contacts_dialer_24dp.xml
deleted file mode 100644
index 43baea0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_quick_contacts_dialer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,3H2C0.9,3 0,3.9 0,5v14c0,1.1 0.9,2 2,2h20c1.1,0 1.99,-0.9 1.99,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM8,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H2v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1zm3.85,-4h1.64L21,16l-1.99,1.99c-1.31,-0.98 -2.28,-2.38 -2.73,-3.99 -0.18,-0.64 -0.28,-1.31 -0.28,-2s0.1,-1.36 0.28,-2c0.45,-1.62 1.42,-3.01 2.73,-3.99L21,8l-1.51,2h-1.64c-0.22,0.63 -0.35,1.3 -0.35,2s0.13,1.37 0.35,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_quick_contacts_mail_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_quick_contacts_mail_24dp.xml
deleted file mode 100644
index fea2a54..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_quick_contacts_mail_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,8V7l-3,2 -3,-2v1l3,2 3,-2zm1,-5H2C0.9,3 0,3.9 0,5v14c0,1.1 0.9,2 2,2h20c1.1,0 1.99,-0.9 1.99,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM8,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H2v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1zm8,-6h-8V6h8v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_ring_volume_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_ring_volume_24dp.xml
deleted file mode 100644
index c76a7c2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_ring_volume_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M23.71,16.67C20.66,13.78 16.54,12 12,12 7.46,12 3.34,13.78 0.29,16.67c-0.18,0.18 -0.29,0.43 -0.29,0.71 0,0.28 0.11,0.53 0.29,0.71l2.48,2.48c0.18,0.18 0.43,0.29 0.71,0.29 0.27,0 0.52,-0.11 0.7,-0.28 0.79,-0.74 1.69,-1.36 2.66,-1.85 0.33,-0.16 0.56,-0.5 0.56,-0.9v-3.1c1.45,-0.48 3,-0.73 4.6,-0.73s3.15,0.25 4.6,0.72v3.1c0,0.39 0.23,0.74 0.56,0.9 0.98,0.49 1.87,1.12 2.66,1.85 0.18,0.18 0.43,0.28 0.7,0.28 0.28,0 0.53,-0.11 0.71,-0.29l2.48,-2.48c0.18,-0.18 0.29,-0.43 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_landscape_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_landscape_24dp.xml
deleted file mode 100644
index e765296..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_landscape_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M1.01,7L1,17c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -1.99,0.9 -1.99,2zM19,7v10H5V7h14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_portrait_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_portrait_24dp.xml
deleted file mode 100644
index 5b22214..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_current_portrait_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_landscape_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_landscape_24dp.xml
deleted file mode 100644
index e765296..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_landscape_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M1.01,7L1,17c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -1.99,0.9 -1.99,2zM19,7v10H5V7h14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_portrait_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_portrait_24dp.xml
deleted file mode 100644
index 5b22214..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_stay_primary_portrait_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_swap_calls_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_swap_calls_24dp.xml
deleted file mode 100644
index 723a66d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_swap_calls_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,4l-4,4h3v7c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2V8c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8v7H2l4,4 4,-4H7V8c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v7c0,2.21 1.79,4 4,4s4,-1.79 4,-4V8h3l-4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_textsms_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_textsms_24dp.xml
deleted file mode 100644
index 796a028..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_textsms_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM9,11H7V9h2v2zm4,0h-2V9h2v2zm4,0h-2V9h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_voicemail_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_voicemail_24dp.xml
deleted file mode 100644
index 92a8765..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_voicemail_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.5,6C15.46,6 13,8.46 13,11.5c0,1.33 0.47,2.55 1.26,3.5H9.74c0.79,-0.95 1.26,-2.17 1.26,-3.5C11,8.46 8.54,6 5.5,6S0,8.46 0,11.5 2.46,17 5.5,17h13c3.04,0 5.5,-2.46 5.5,-5.5S21.54,6 18.5,6zm-13,9C3.57,15 2,13.43 2,11.5S3.57,8 5.5,8 9,9.57 9,11.5 7.43,15 5.5,15zm13,0c-1.93,0 -3.5,-1.57 -3.5,-3.5S16.57,8 18.5,8 22,9.57 22,11.5 20.43,15 18.5,15z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_vpn_key_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_vpn_key_24dp.xml
deleted file mode 100644
index fe9c93f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/communication/ic_vpn_key_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_24dp.xml
deleted file mode 100644
index 059be44..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_box_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_box_24dp.xml
deleted file mode 100644
index df3fa3a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_box_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-2,10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_24dp.xml
deleted file mode 100644
index f4d87c1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm5,11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_outline_24dp.xml
deleted file mode 100644
index 3eaea09..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_add_circle_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1,-5C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_archive_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_archive_24dp.xml
deleted file mode 100644
index 5aa321c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_archive_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_backspace_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_backspace_24dp.xml
deleted file mode 100644
index 7bc3f93..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_backspace_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,3H7c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-3,12.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_block_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_block_24dp.xml
deleted file mode 100644
index 02bb4aa..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_block_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zm8,8c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_clear_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_clear_24dp.xml
deleted file mode 100644
index 88f04ed..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_clear_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_copy_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_copy_24dp.xml
deleted file mode 100644
index baa0796..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_copy_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,1H4c-1.1,0 -2,0.9 -2,2v14h2V3h12V1zm3,4H8c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2zm0,16H8V7h11v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_cut_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_cut_24dp.xml
deleted file mode 100644
index 82c28fa..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_cut_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9.64,7.64c0.23,-0.5 0.36,-1.05 0.36,-1.64 0,-2.21 -1.79,-4 -4,-4S2,3.79 2,6s1.79,4 4,4c0.59,0 1.14,-0.13 1.64,-0.36L10,12l-2.36,2.36C7.14,14.13 6.59,14 6,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4c0,-0.59 -0.13,-1.14 -0.36,-1.64L12,14l7,7h3v-1L9.64,7.64zM6,8c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zm0,12c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zm6,-7.5c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5 0.5,0.22 0.5,0.5 -0.22,0. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_paste_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_paste_24dp.xml
deleted file mode 100644
index cc248b5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_content_paste_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,2h-4.18C14.4,0.84 13.3,0 12,0c-1.3,0 -2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-7,0c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm7,18H5V4h2v3h10V4h2v16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_create_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_create_24dp.xml
deleted file mode 100644
index 0ff336a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_create_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_drafts_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_drafts_24dp.xml
deleted file mode 100644
index 679321c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_drafts_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.99,8c0,-0.72 -0.37,-1.35 -0.94,-1.7L12,1 2.95,6.3C2.38,6.65 2,7.28 2,8v10c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2l-0.01,-10zM12,13L3.74,7.84 12,3l8.26,4.84L12,13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_filter_list_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_filter_list_24dp.xml
deleted file mode 100644
index 16ca2a1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_filter_list_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,18h4v-2h-4v2zM3,6v2h18V6H3zm3,7h12v-2H6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_flag_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_flag_24dp.xml
deleted file mode 100644
index 581ba88..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_flag_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.4,6L14,4H5v17h2v-7h5.6l0.4,2h7V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_forward_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_forward_24dp.xml
deleted file mode 100644
index 76d901e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_forward_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,8V4l8,8 -8,8v-4H4V8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_gesture_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_gesture_24dp.xml
deleted file mode 100644
index a8e4c17..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_gesture_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4.59,6.89c0.7,-0.71 1.4,-1.35 1.71,-1.22 0.5,0.2 0,1.03 -0.3,1.52 -0.25,0.42 -2.86,3.89 -2.86,6.31 0,1.28 0.48,2.34 1.34,2.98 0.75,0.56 1.74,0.73 2.64,0.46 1.07,-0.31 1.95,-1.4 3.06,-2.77 1.21,-1.49 2.83,-3.44 4.08,-3.44 1.63,0 1.65,1.01 1.76,1.79 -3.78,0.64 -5.38,3.67 -5.38,5.37 0,1.7 1.44,3.09 3.21,3.09 1.63,0 4.29,-1.33 4.69,-6.1H21v-2.5h-2.47c-0.15,-1.65 -1.09,-4.2 -4.03,-4.2 -2.25,0 -4.18,1.91 -4.94,2.84 -0.58,0.73 -2.06,2.48 -2.29,2.72 -0.25,0.3 -0.68,0.8 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_inbox_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_inbox_24dp.xml
deleted file mode 100644
index e2b49cd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_inbox_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H4.99c-1.1,0 -1.98,0.9 -1.98,2L3,19c0,1.1 0.89,2 1.99,2H19c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,12h-4c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3H4.99V5H19v10zm-3,-5h-2V7h-4v3H8l4,4 4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_link_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_link_24dp.xml
deleted file mode 100644
index 1b8f436..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_link_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4V7H7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9H7c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2H8v2zm9,-6h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4V17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_mail_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_mail_24dp.xml
deleted file mode 100644
index bf2ac7d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_mail_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,4l-8,5 -8,-5V6l8,5 8,-5v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_markunread_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_markunread_24dp.xml
deleted file mode 100644
index bf2ac7d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_markunread_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,4l-8,5 -8,-5V6l8,5 8,-5v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_redo_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_redo_24dp.xml
deleted file mode 100644
index 1beacbb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_redo_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_24dp.xml
deleted file mode 100644
index 8757791..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,13H5v-2h14v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_24dp.xml
deleted file mode 100644
index 98ef63c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm5,11H7v-2h10v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_outline_24dp.xml
deleted file mode 100644
index 42d36f9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_remove_circle_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,11v2h10v-2H7zm5,-9C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_24dp.xml
deleted file mode 100644
index 92ec9e1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_all_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_all_24dp.xml
deleted file mode 100644
index e351767..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_reply_all_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,8V5l-7,7 7,7v-3l-4,-4 4,-4zm6,1V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_report_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_report_24dp.xml
deleted file mode 100644
index 09d5660..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_report_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.73,3H8.27L3,8.27v7.46L8.27,21h7.46L21,15.73V8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zm1,-4.3h-2V7h2v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_save_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_save_24dp.xml
deleted file mode 100644
index d36b668..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_save_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V7l-4,-4zm-5,16c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zm3,-10H5V5h10v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_select_all_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_select_all_24dp.xml
deleted file mode 100644
index 2dde2d4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_select_all_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5h2V3c-1.1,0 -2,0.9 -2,2zm0,8h2v-2H3v2zm4,8h2v-2H7v2zM3,9h2V7H3v2zm10,-6h-2v2h2V3zm6,0v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2H3c0,1.1 0.9,2 2,2zm-2,-4h2v-2H3v2zM9,3H7v2h2V3zm2,18h2v-2h-2v2zm8,-8h2v-2h-2v2zm0,8c1.1,0 2,-0.9 2,-2h-2v2zm0,-12h2V7h-2v2zm0,8h2v-2h-2v2zm-4,4h2v-2h-2v2zm0,-16h2V3h-2v2zM7,17h10V7H7v10zm2,-8h6v6H9V9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_send_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_send_24dp.xml
deleted file mode 100644
index f9dc3a1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_send_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_sort_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_sort_24dp.xml
deleted file mode 100644
index 1605498..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_sort_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,18h6v-2H3v2zM3,6v2h18V6H3zm0,7h12v-2H3v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_text_format_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_text_format_24dp.xml
deleted file mode 100644
index 09b3888..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_text_format_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,17v2h14v-2H5zm4.5,-4.2h5l0.9,2.2h2.1L12.75,4h-1.5L6.5,15h2.1l0.9,-2.2zM12,5.98L13.87,11h-3.74L12,5.98z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_undo_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/content/ic_undo_24dp.xml
deleted file mode 100644
index 36bbbcf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/content/ic_undo_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarm_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarm_24dp.xml
deleted file mode 100644
index 7918b03..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarm_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12.5,8H11v6l4.75,2.85 0.75,-1.23 -4,-2.37V8zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm0,16c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarms_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarms_24dp.xml
deleted file mode 100644
index 479b5c1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_alarms_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,5.7l-4.6,-3.9 -1.3,1.5 4.6,3.9L22,5.7zM7.9,3.4L6.6,1.9 2,5.7l1.3,1.5 4.6,-3.8zM12.5,8H11v6l4.7,2.9 0.8,-1.2 -4,-2.4V8zM12,4c-5,0 -9,4 -9,9s4,9 9,9 9,-4 9,-9 -4,-9 -9,-9zm0,16c-3.9,0 -7,-3.1 -7,-7s3.1,-7 7,-7 7,3.1 7,7 -3.1,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_time_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_time_24dp.xml
deleted file mode 100644
index 7bf67c6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_access_time_24dp.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
- android:fillAlpha=".9"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_add_alarm_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_add_alarm_24dp.xml
deleted file mode 100644
index 5e0d986..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_add_alarm_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm0,16c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zm1,-11h-2v3H8v2h3v3h2v-3h3v-2h-3V9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_off_24dp.xml
deleted file mode 100644
index 0dfb747..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,9V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v3.68l7.83,7.83L21,16v-2l-8,-5zM3,5.27l4.99,4.99L2,14v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-3.73L18.73,21 20,19.73 4.27,4 3,5.27z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_on_24dp.xml
deleted file mode 100644
index 3aa5af7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_airplanemode_on_24dp.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.18,9"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,16v-2l-8,-5V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5V9l-8,5v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-5.5l8,2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_20_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_20_24dp.xml
deleted file mode 100644
index 81ebec3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_20_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,17v3.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V17H7z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V17h10V5.33z"
- android:fillAlpha=".3"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_30_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_30_24dp.xml
deleted file mode 100644
index 46f602b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_30_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V15h10V5.33z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,15v5.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V15H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_50_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_50_24dp.xml
deleted file mode 100644
index b4390e3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_50_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V13h10V5.33z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,13v7.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V13H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_60_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_60_24dp.xml
deleted file mode 100644
index e23a724..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_60_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V11h10V5.33z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,11v9.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V11H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_80_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_80_24dp.xml
deleted file mode 100644
index 246e394..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_80_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V9h10V5.33z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,9v11.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V9H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_90_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_90_24dp.xml
deleted file mode 100644
index 05efad9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_90_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V8h10V5.33z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,8v12.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V8H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_alert_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_alert_24dp.xml
deleted file mode 100644
index aeca8fb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_alert_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM13,18h-2v-2h2v2zm0,-4h-2V9h2v5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_20_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_20_24dp.xml
deleted file mode 100644
index 9018f25..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_20_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,20v-3H7v3.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V17h-4.4L11,20z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V17h4v-2.5H9L13,7v5.5h2L12.6,17H17V5.33C17,4.6 16.4,4 15.67,4z"
- android:fillAlpha=".3"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_30_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_30_24dp.xml
deleted file mode 100644
index 40753fc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_30_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v9.17h2L13,7v5.5h2l-1.07,2H17V5.33C17,4.6 16.4,4 15.67,4z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,20v-5.5H7v6.17C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V14.5h-3.07L11,20z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_50_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_50_24dp.xml
deleted file mode 100644
index ecd86f9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_50_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.47,13.5L11,20v-5.5H9l0.53,-1H7v7.17C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V13.5h-2.53z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v8.17h2.53L13,7v5.5h2l-0.53,1H17V5.33C17,4.6 16.4,4 15.67,4z"
- android:fillAlpha=".3"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_60_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_60_24dp.xml
deleted file mode 100644
index 1893c50..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_60_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V11h3.87L13,7v4h4V5.33C17,4.6 16.4,4 15.67,4z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,12.5h2L11,20v-5.5H9l1.87,-3.5H7v9.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V11h-4v1.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_80_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_80_24dp.xml
deleted file mode 100644
index 8e1e8b5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_80_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V9h4.93L13,7v2h4V5.33C17,4.6 16.4,4 15.67,4z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,12.5h2L11,20v-5.5H9L11.93,9H7v11.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V9h-4v3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_90_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_90_24dp.xml
deleted file mode 100644
index 105f0ed..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_90_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V8h5.47L13,7v1h4V5.33C17,4.6 16.4,4 15.67,4z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,12.5h2L11,20v-5.5H9L12.47,8H7v12.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V8h-4v4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_full_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_full_24dp.xml
deleted file mode 100644
index e2ca7a6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_charging_full_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM11,20v-5.5H9L13,7v5.5h2L11,20z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_full_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_full_24dp.xml
deleted file mode 100644
index 92f9db6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_full_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_std_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_std_24dp.xml
deleted file mode 100644
index 92f9db6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_std_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_unknown_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_unknown_24dp.xml
deleted file mode 100644
index 4b48da8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_battery_unknown_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zm-2.72,13.95h-1.9v-1.9h1.9v1.9zm1.35,-5.26s-0.38,0.42 -0.67,0.71c-0.48,0.48 -0.83,1.15 -0.83,1.6h-1.6c0,-0.83 0.46,-1.52 0.93,-2l0.93,-0.94c0.27,-0.27 0.44,-0.65 0.44,-1.06 0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5H9c0,-1.66 1.34,-3 3,-3s3,1.34 3,3c0,0.66 -0.27,1.26 -0.7,1.69z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_24dp.xml
deleted file mode 100644
index 6ce13d9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59V5.83zm1.88,10.46L13,18.17v-3.76l1.88,1.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_connected_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_connected_24dp.xml
deleted file mode 100644
index 27a3001..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_connected_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,12l-2,-2 -2,2 2,2 2,-2zm10.71,-4.29L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59V5.83zm1.88,10.46L13,18.17v-3.76l1.88,1.88zM19,10l-2,2 2,2 2,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_disabled_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_disabled_24dp.xml
deleted file mode 100644
index 02d7bc8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_disabled_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,5.83l1.88,1.88 -1.6,1.6 1.41,1.41 3.02,-3.02L12,2h-1v5.03l2,2v-3.2zM5.41,4L4,5.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l4.29,-4.29 2.3,2.29L20,18.59 5.41,4zM13,18.17v-3.76l1.88,1.88L13,18.17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_searching_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_searching_24dp.xml
deleted file mode 100644
index d09fb6f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_bluetooth_searching_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zm5.29,-5.3l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zm-3.82,1L10,2H9v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41V22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59V5.83zm1.88,10.46L11,18.17v-3.76l1.88,1.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_auto_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_auto_24dp.xml
deleted file mode 100644
index a7495b9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_auto_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.85,12.65h2.3L12,9l-1.15,3.65zM20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM14.3,16l-0.7,-2h-3.2l-0.7,2H7.8L11,7h2l3.2,9h-1.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_high_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_high_24dp.xml
deleted file mode 100644
index c254041..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_high_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zm0,-10c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_low_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_low_24dp.xml
deleted file mode 100644
index 406a52e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_low_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_medium_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_medium_24dp.xml
deleted file mode 100644
index cc63bac..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_brightness_medium_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18V6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_data_usage_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_data_usage_24dp.xml
deleted file mode 100644
index 69bc3ec..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_data_usage_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_developer_mode_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_developer_mode_24dp.xml
deleted file mode 100644
index 93288cb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_developer_mode_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,5h10v2h2V3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2V5zm8.41,11.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19H7v-2H5v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_devices_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_devices_24dp.xml
deleted file mode 100644
index e54c722..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_devices_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,6h18V4H4c-1.1,0 -2,0.9 -2,2v11H0v3h14v-3H4V6zm19,2h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zm-1,9h-4v-7h4v7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_dvr_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_dvr_24dp.xml
deleted file mode 100644
index 61a2bc6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_dvr_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,3H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zm0,14H3V5h18v12zm-2,-9H8v2h11V8zm0,4H8v2h11v-2zM7,8H5v2h2V8zm0,4H5v2h2v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_fixed_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_fixed_24dp.xml
deleted file mode 100644
index 2a7817e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_fixed_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm8.94,3c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_not_fixed_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_not_fixed_24dp.xml
deleted file mode 100644
index d8fb3b4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_not_fixed_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_off_24dp.xml
deleted file mode 100644
index 68db2a9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_gps_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15H23v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zm13.27,13.27C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.55,-3.09 1.46 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_location_disabled_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_location_disabled_24dp.xml
deleted file mode 100644
index 68db2a9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_location_disabled_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15H23v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zm13.27,13.27C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.55,-3.09 1.46 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_location_searching_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_location_searching_24dp.xml
deleted file mode 100644
index d8fb3b4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_location_searching_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_multitrack_audio_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_multitrack_audio_24dp.xml
deleted file mode 100644
index 252e560..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_multitrack_audio_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,18h2V6H7v12zm4,4h2V2h-2v20zm-8,-8h2v-4H3v4zm12,4h2V6h-2v12zm4,-8v4h2v-4h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_network_cell_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_network_cell_24dp.xml
deleted file mode 100644
index bc6f3ca..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_network_cell_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,22h20V2z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,7L2,22h15z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_network_wifi_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_network_wifi_24dp.xml
deleted file mode 100644
index 3a1e2e5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_network_wifi_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_nfc_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_nfc_24dp.xml
deleted file mode 100644
index 83c3ee6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_nfc_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm0,18H4V4h16v16zM18,6h-5c-1.1,0 -2,0.9 -2,2v2.28c-0.6,0.35 -1,0.98 -1,1.72 0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-0.74 -0.4,-1.38 -1,-1.72V8h3v8H8V8h2V6H6v12h12V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_now_wallpaper_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_now_wallpaper_24dp.xml
deleted file mode 100644
index 6e1973e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_now_wallpaper_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,4h7V2H4c-1.1,0 -2,0.9 -2,2v7h2V4zm6,9l-4,5h12l-3,-4 -2.03,2.71L10,13zm7,-4.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S14,7.67 14,8.5s0.67,1.5 1.5,1.5S17,9.33 17,8.5zM20,2h-7v2h7v7h2V4c0,-1.1 -0.9,-2 -2,-2zm0,18h-7v2h7c1.1,0 2,-0.9 2,-2v-7h-2v7zM4,13H2v7c0,1.1 0.9,2 2,2h7v-2H4v-7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_now_widgets_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_now_widgets_24dp.xml
deleted file mode 100644
index 02996e0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_now_widgets_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,13v8h8v-8h-8zM3,21h8v-8H3v8zM3,3v8h8V3H3zm13.66,-1.31L11,7.34 16.66,13l5.66,-5.66 -5.66,-5.65z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_landscape_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_landscape_24dp.xml
deleted file mode 100644
index 1e0f44a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_landscape_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,5H3c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2zm-2,12H5V7h14v10zm-9,-1h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zm0.8,-6c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_portrait_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_portrait_24dp.xml
deleted file mode 100644
index d35bc48..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_portrait_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,16h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zm0.8,-6c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1zM17,1H7c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,18H7V5h10v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_rotation_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_rotation_24dp.xml
deleted file mode 100644
index 2e364cd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_lock_rotation_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M23.25,12.77l-2.57,-2.57 -1.41,1.41 2.22,2.22 -5.66,5.66L4.51,8.17l5.66,-5.66 2.1,2.1 1.41,-1.41L11.23,0.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L2.75,7.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12zM8.47,20.48C5.2,18.94 2.86,15.76 2.5,12H1c0.51,6.16 5.66,11 11.95,11l0.66,-0.03 -3.81,-3.82 -1.33,1.33zM16,9h5c0.55,0 1,-0.45 1,-1V4c0,-0.55 -0.45,-1 -1,-1v-0.5C21,1.12 19.88,0 18.5,0S16,1.12 16,2.5V3c-0.55, [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_rotation_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_rotation_24dp.xml
deleted file mode 100644
index ab4d9ce..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_screen_rotation_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zm-6.25,-0.77c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zm4.6,19.44L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zm-7.31,0.29C4.25,19.94 1.91,16.76 1.55,13H0.05C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_sd_storage_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_sd_storage_24dp.xml
deleted file mode 100644
index 74e0e41..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_sd_storage_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-6,6h-2V4h2v4zm3,0h-2V4h2v4zm3,0h-2V4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_settings_system_daydream_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_settings_system_daydream_24dp.xml
deleted file mode 100644
index 0882c81..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_settings_system_daydream_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,16h6.5c1.38,0 2.5,-1.12 2.5,-2.5S16.88,11 15.5,11h-0.05c-0.24,-1.69 -1.69,-3 -3.45,-3 -1.4,0 -2.6,0.83 -3.16,2.02h-0.16C7.17,10.18 6,11.45 6,13c0,1.66 1.34,3 3,3zM21,3H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16.01H3V4.99h18v14.02z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_0_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_0_bar_24dp.xml
deleted file mode 100644
index b385c9b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_0_bar_24dp.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,22h20V2z"
- android:fillAlpha=".3"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_1_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_1_bar_24dp.xml
deleted file mode 100644
index 4fe8b5a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_1_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,22h20V2z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,12L2,22h10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_2_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_2_bar_24dp.xml
deleted file mode 100644
index 6fb8da3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_2_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,22h20V2z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,10L2,22h12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_3_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_3_bar_24dp.xml
deleted file mode 100644
index bc6f3ca..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_3_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,22h20V2z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,7L2,22h15z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_4_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_4_bar_24dp.xml
deleted file mode 100644
index 13e38ee..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_4_bar_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,22h20V2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_0_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_0_bar_24dp.xml
deleted file mode 100644
index 9f74cf5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_0_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,8V2L2,22h16V8z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,22h2v-2h-2v2zm0,-12v8h2v-8h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_1_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_1_bar_24dp.xml
deleted file mode 100644
index 877cad6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_1_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,8V2L2,22h16V8z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,10v8h2v-8h-2zm-8,12V12L2,22h10zm8,0h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_2_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_2_bar_24dp.xml
deleted file mode 100644
index 3d15914..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_2_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,8V2L2,22h16V8z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,22V10L2,22h12zm6,-12v8h2v-8h-2zm0,12h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_3_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_3_bar_24dp.xml
deleted file mode 100644
index 1fc9c2b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_3_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,8V2L2,22h16V8z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,22V7L2,22h15zm3,-12v8h2v-8h-2zm0,12h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_4_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_4_bar_24dp.xml
deleted file mode 100644
index e836247..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_connected_no_internet_4_bar_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,18h2v-8h-2v8zm0,4h2v-2h-2v2zM2,22h16V8h4V2L2,22z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_no_sim_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_no_sim_24dp.xml
deleted file mode 100644
index b36e505..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_no_sim_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.99,5c0,-1.1 -0.89,-2 -1.99,-2h-7L7.66,5.34 19,16.68 18.99,5zM3.65,3.88L2.38,5.15 5,7.77V19c0,1.1 0.9,2 2,2h10.01c0.35,0 0.67,-0.1 0.96,-0.26l1.88,1.88 1.27,-1.27L3.65,3.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_null_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_null_24dp.xml
deleted file mode 100644
index f152649..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_null_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6.83V20H6.83L20,6.83M22,2L2,22h20V2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_off_24dp.xml
deleted file mode 100644
index 343e9b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_cellular_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,1l-8.59,8.59L21,18.18V1zM4.77,4.5L3.5,5.77l6.36,6.36L1,21h17.73l2,2L22,21.73 4.77,4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_0_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_0_bar_24dp.xml
deleted file mode 100644
index 5e45e6d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_0_bar_24dp.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
- android:fillAlpha=".3"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_24dp.xml
deleted file mode 100644
index 2fcb74a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_1_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.67,14.86L12,21.49v0.01l0.01,-0.01 5.33,-6.63C17.06,14.65 15.03,13 12,13s-5.06,1.65 -5.33,1.86z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_24dp.xml
deleted file mode 100644
index ac24574..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_2_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M4.79,12.52l7.2,8.98H12l0.01,-0.01 7.2,-8.98C18.85,12.24 16.1,10 12,10s-6.85,2.24 -7.21,2.52z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_24dp.xml
deleted file mode 100644
index 3a1e2e5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_3_bar_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_24dp.xml
deleted file mode 100644
index f47a229..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_4_bar_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_off_24dp.xml
deleted file mode 100644
index d9eaa7a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zm-6.6,8.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_1_bar_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_1_bar_26x24px.xml
deleted file mode 100644
index 830fb67..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_1_bar_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,21.99l5.66,-7.05C18.44,14.78 16.27,13 13,13s-5.44,1.78 -5.66,1.95L13,21.99z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.01,21.99L25.58,6.32C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01z"
- android:fillAlpha=".3"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_2_bar_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_2_bar_26x24px.xml
deleted file mode 100644
index 57aab89..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_2_bar_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.01,21.99L25.58,6.32C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.01,21.99l7.54,-9.4C20.26,12.38 17.36,10 13,10c-4.36,0 -7.26,2.38 -7.55,2.59l7.54,9.4h0.02z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_3_bar_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_3_bar_26x24px.xml
deleted file mode 100644
index c0b7b4a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_3_bar_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.01,21.99l9.43,-11.75C22.07,9.97 18.44,7 13,7c-5.44,0 -9.07,2.97 -9.44,3.24l9.43,11.75h0.02z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.01,21.99L25.58,6.32C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01z"
- android:fillAlpha=".3"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_4_bar_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_4_bar_26x24px.xml
deleted file mode 100644
index 6cc9ebe..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_4_bar_26x24px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.01,21.99L25.58,6.32C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_1_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_1_26x24px.xml
deleted file mode 100644
index 70bd006..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_1_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M24.24,8l1.35,-1.68C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01L20,13.28V8h4.24z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.34,14.95L13,21.99V22v-0.01l5.66,-7.05C18.44,14.78 16.27,13 13,13s-5.44,1.78 -5.66,1.95zM22,22h2v-2h-2v2zm0,-12v8h2v-8h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_26x24px.xml
deleted file mode 100644
index 910bf9d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M24.24,8l1.35,-1.68C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01L20,13.28V8h4.24z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,22h2v-2h-2v2zm0,-12v8h2v-8h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_2_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_2_26x24px.xml
deleted file mode 100644
index e158ddf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_2_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M24.24,8l1.35,-1.68C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01L20,13.28V8h4.24z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M5.45,12.59l7.54,9.4 0.01,0.01 0.01,-0.01L20,13.28v-1.09c-1.07,-0.73 -3.59,-2.19 -7,-2.19 -4.36,0 -7.26,2.38 -7.55,2.59zM22,10v8h2v-8h-2zm0,12h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_3_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_3_26x24px.xml
deleted file mode 100644
index c242b36..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_3_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M24.24,8l1.35,-1.68C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01L20,13.28V8h4.24z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,13.28V8.71C18.35,7.87 15.94,7 13,7c-5.44,0 -9.07,2.97 -9.44,3.24l9.43,11.75 0.01,0.01 0.01,-0.01L20,13.28zM22,22h2v-2h-2v2zm0,-12v8h2v-8h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_4_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_4_26x24px.xml
deleted file mode 100644
index d5f15af..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_connected_no_internet_4_26x24px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,22h2v-2h-2v2zM13,2C5.74,2 0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01L20,13.28V8h4.24l1.35,-1.68C25.1,5.96 20.26,2 13,2zm9,16h2v-8h-2v8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_not_connected_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_not_connected_26x24px.xml
deleted file mode 100644
index a654e58..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_not_connected_26x24px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,8.5c0.85,0 1.64,0.23 2.34,0.62l2.24,-2.79C25.1,5.96 20.26,2 13,2S0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01 4.21,-5.24c-0.76,-0.87 -1.22,-2 -1.22,-3.25 0,-2.76 2.24,-5 5,-5z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,10c-1.93,0 -3.5,1.57 -3.5,3.5h1.75c0,-0.97 0.78,-1.75 1.75,-1.75s1.75,0.78 1.75,1.75c0,0.48 -0.2,0.92 -0.51,1.24l-1.09,1.1c-0.63,0.63 -1.02,1.51 -1.02,2.47v0.44h1.75c0,-1.31 0.39,-1.84 1.03,-2.47l0.78,-0.8c0.5,-0.5 0.82,-1.2 0.82,-1.97C24.5,11.57 22.93,10 21,10zm-0.95,11.95h1.9v-1.9h-1.9v1.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_null_26x24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_null_26x24px.xml
deleted file mode 100644
index 9951921..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_signal_wifi_statusbar_null_26x24px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26dp"
- android:height="24dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,4c4.25,0 7.62,1.51 9.68,2.75L13,18.8 3.33,6.75C5.38,5.51 8.75,4 13,4m0,-2C5.74,2 0.9,5.96 0.42,6.32l12.57,15.66 0.01,0.02 0.01,-0.01L25.58,6.32C25.1,5.96 20.26,2 13,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_storage_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_storage_24dp.xml
deleted file mode 100644
index 8d18d84..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_storage_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,20h20v-4H2v4zm2,-3h2v2H4v-2zM2,4v4h20V4H2zm4,3H4V5h2v2zm-4,7h20v-4H2v4zm2,-3h2v2H4v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_usb_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_usb_24dp.xml
deleted file mode 100644
index ae726d8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_usb_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,7v4h1v2h-3V5h2l-3,-4 -3,4h2v8H8v-2.07c0.7,-0.37 1.2,-1.08 1.2,-1.93 0,-1.21 -0.99,-2.2 -2.2,-2.2 -1.21,0 -2.2,0.99 -2.2,2.2 0,0.85 0.5,1.56 1.2,1.93V13c0,1.11 0.89,2 2,2h3v3.05c-0.71,0.37 -1.2,1.1 -1.2,1.95 0,1.22 0.99,2.2 2.2,2.2 1.21,0 2.2,-0.98 2.2,-2.2 0,-0.85 -0.49,-1.58 -1.2,-1.95V15h3c1.11,0 2,-0.89 2,-2v-2h1V7h-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_lock_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_lock_24dp.xml
deleted file mode 100644
index ca9ce19..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_lock_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.5,9.5c0.28,0 0.55,0.04 0.81,0.08L24,6c-3.34,-2.51 -7.5,-4 -12,-4S3.34,3.49 0,6l12,16 3.5,-4.67V14.5c0,-2.76 2.24,-5 5,-5zM23,16v-1.5c0,-1.38 -1.12,-2.5 -2.5,-2.5S18,13.12 18,14.5V16c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zm-1,0h-3v-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5V16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_tethering_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_tethering_24dp.xml
deleted file mode 100644
index 7fc86a5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/device/ic_wifi_tethering_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm6,2c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.48 -0.81,2.75 -2,3.45l1,1.74c1.79,-1.04 3,-2.97 3,-5.19zM12,3C6.48,3 2,7.48 2,13c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,18.53 4,15.96 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.96 -1.61,5.53 -4,6.92l1,1.73c2.99,-1.73 5,-4.95 5,-8.65 0,-5.52 -4.48,-10 -10,-10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_file_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_file_24dp.xml
deleted file mode 100644
index ed1aad3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_file_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.5,6v11.5c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4V5c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5v10.5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V6H10v9.5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5V5c0,-2.21 -1.79,-4 -4,-4S7,2.79 7,5v12.5c0,3.04 2.46,5.5 5.5,5.5s5.5,-2.46 5.5,-5.5V6h-1.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_money_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_money_24dp.xml
deleted file mode 100644
index 4a0cd0e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_attach_money_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_all_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_all_24dp.xml
deleted file mode 100644
index 7a5ee14..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_all_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,3v18h18V3H3zm8,16H5v-6h6v6zm0,-8H5V5h6v6zm8,8h-6v-6h6v6zm0,-8h-6V5h6v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_bottom_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_bottom_24dp.xml
deleted file mode 100644
index 4538725..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_bottom_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,11H7v2h2v-2zm4,4h-2v2h2v-2zM9,3H7v2h2V3zm4,8h-2v2h2v-2zM5,3H3v2h2V3zm8,4h-2v2h2V7zm4,4h-2v2h2v-2zm-4,-8h-2v2h2V3zm4,0h-2v2h2V3zm2,10h2v-2h-2v2zm0,4h2v-2h-2v2zM5,7H3v2h2V7zm14,-4v2h2V3h-2zm0,6h2V7h-2v2zM5,11H3v2h2v-2zM3,21h18v-2H3v2zm2,-6H3v2h2v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_clear_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_clear_24dp.xml
deleted file mode 100644
index 0d9c07f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_clear_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,5h2V3H7v2zm0,8h2v-2H7v2zm0,8h2v-2H7v2zm4,-4h2v-2h-2v2zm0,4h2v-2h-2v2zm-8,0h2v-2H3v2zm0,-4h2v-2H3v2zm0,-4h2v-2H3v2zm0,-4h2V7H3v2zm0,-4h2V3H3v2zm8,8h2v-2h-2v2zm8,4h2v-2h-2v2zm0,-4h2v-2h-2v2zm0,8h2v-2h-2v2zm0,-12h2V7h-2v2zm-8,0h2V7h-2v2zm8,-6v2h2V3h-2zm-8,2h2V3h-2v2zm4,16h2v-2h-2v2zm0,-8h2v-2h-2v2zm0,-8h2V3h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_color_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_color_24dp.xml
deleted file mode 100644
index c164764..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_color_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.75,7L14,3.25l-10,10V17h3.75l10,-10zm2.96,-2.96c0.39,-0.39 0.39,-1.02 0,-1.41L18.37,0.29c-0.39,-0.39 -1.02,-0.39 -1.41,0L15,2.25 18.75,6l1.96,-1.96z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M0,20h24v4H0z"
- android:fillAlpha=".36"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_horizontal_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_horizontal_24dp.xml
deleted file mode 100644
index caedc37..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_horizontal_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,21h2v-2H3v2zM5,7H3v2h2V7zM3,17h2v-2H3v2zm4,4h2v-2H7v2zM5,3H3v2h2V3zm4,0H7v2h2V3zm8,0h-2v2h2V3zm-4,4h-2v2h2V7zm0,-4h-2v2h2V3zm6,14h2v-2h-2v2zm-8,4h2v-2h-2v2zm-8,-8h18v-2H3v2zM19,3v2h2V3h-2zm0,6h2V7h-2v2zm-8,8h2v-2h-2v2zm4,4h2v-2h-2v2zm4,0h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_inner_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_inner_24dp.xml
deleted file mode 100644
index da4372a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_inner_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,21h2v-2H3v2zm4,0h2v-2H7v2zM5,7H3v2h2V7zM3,17h2v-2H3v2zM9,3H7v2h2V3zM5,3H3v2h2V3zm12,0h-2v2h2V3zm2,6h2V7h-2v2zm0,-6v2h2V3h-2zm-4,18h2v-2h-2v2zM13,3h-2v8H3v2h8v8h2v-8h8v-2h-8V3zm6,18h2v-2h-2v2zm0,-4h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_left_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_left_24dp.xml
deleted file mode 100644
index 6d2e070..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_left_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,21h2v-2h-2v2zm0,-4h2v-2h-2v2zm0,-12h2V3h-2v2zm0,4h2V7h-2v2zm0,4h2v-2h-2v2zm-4,8h2v-2H7v2zM7,5h2V3H7v2zm0,8h2v-2H7v2zm-4,8h2V3H3v18zM19,9h2V7h-2v2zm-4,12h2v-2h-2v2zm4,-4h2v-2h-2v2zm0,-14v2h2V3h-2zm0,10h2v-2h-2v2zm0,8h2v-2h-2v2zm-4,-8h2v-2h-2v2zm0,-8h2V3h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_outer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_outer_24dp.xml
deleted file mode 100644
index 5addeec..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_outer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,7h-2v2h2V7zm0,4h-2v2h2v-2zm4,0h-2v2h2v-2zM3,3v18h18V3H3zm16,16H5V5h14v14zm-6,-4h-2v2h2v-2zm-4,-4H7v2h2v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_right_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_right_24dp.xml
deleted file mode 100644
index 4a9b068..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_right_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,21h2v-2H7v2zM3,5h2V3H3v2zm4,0h2V3H7v2zm0,8h2v-2H7v2zm-4,8h2v-2H3v2zm8,0h2v-2h-2v2zm-8,-8h2v-2H3v2zm0,4h2v-2H3v2zm0,-8h2V7H3v2zm8,8h2v-2h-2v2zm4,-4h2v-2h-2v2zm4,-10v18h2V3h-2zm-4,18h2v-2h-2v2zm0,-16h2V3h-2v2zm-4,8h2v-2h-2v2zm0,-8h2V3h-2v2zm0,4h2V7h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_style_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_style_24dp.xml
deleted file mode 100644
index 5079af0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_style_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,21h2v-2h-2v2zm4,0h2v-2h-2v2zM7,21h2v-2H7v2zm4,0h2v-2h-2v2zm8,-4h2v-2h-2v2zm0,-4h2v-2h-2v2zM3,3v18h2V5h16V3H3zm16,6h2V7h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_top_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_top_24dp.xml
deleted file mode 100644
index f38ac53..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_top_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,21h2v-2H7v2zm0,-8h2v-2H7v2zm4,0h2v-2h-2v2zm0,8h2v-2h-2v2zm-8,-4h2v-2H3v2zm0,4h2v-2H3v2zm0,-8h2v-2H3v2zm0,-4h2V7H3v2zm8,8h2v-2h-2v2zm8,-8h2V7h-2v2zm0,4h2v-2h-2v2zM3,3v2h18V3H3zm16,14h2v-2h-2v2zm-4,4h2v-2h-2v2zM11,9h2V7h-2v2zm8,12h2v-2h-2v2zm-4,-8h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_vertical_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_vertical_24dp.xml
deleted file mode 100644
index 45f9f94..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_border_vertical_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,9h2V7H3v2zm0,-4h2V3H3v2zm4,16h2v-2H7v2zm0,-8h2v-2H7v2zm-4,0h2v-2H3v2zm0,8h2v-2H3v2zm0,-4h2v-2H3v2zM7,5h2V3H7v2zm12,12h2v-2h-2v2zm-8,4h2V3h-2v18zm8,0h2v-2h-2v2zm0,-8h2v-2h-2v2zm0,-10v2h2V3h-2zm0,6h2V7h-2v2zm-4,-4h2V3h-2v2zm0,16h2v-2h-2v2zm0,-8h2v-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_center_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_center_24dp.xml
deleted file mode 100644
index 10649b2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_center_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,15v2h10v-2H7zm-4,6h18v-2H3v2zm0,-8h18v-2H3v2zm4,-6v2h10V7H7zM3,3v2h18V3H3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_justify_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_justify_24dp.xml
deleted file mode 100644
index bddb3af..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_justify_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,21h18v-2H3v2zm0,-4h18v-2H3v2zm0,-4h18v-2H3v2zm0,-4h18V7H3v2zm0,-6v2h18V3H3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_left_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_left_24dp.xml
deleted file mode 100644
index 39cdf41..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_left_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,15H3v2h12v-2zm0,-8H3v2h12V7zM3,13h18v-2H3v2zm0,8h18v-2H3v2zM3,3v2h18V3H3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_right_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_right_24dp.xml
deleted file mode 100644
index 59473f3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_align_right_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,21h18v-2H3v2zm6,-4h12v-2H9v2zm-6,-4h18v-2H3v2zm6,-4h12V7H9v2zM3,3v2h18V3H3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_bold_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_bold_24dp.xml
deleted file mode 100644
index 8b6f679..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_bold_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.6,10.79c0.97,-0.67 1.65,-1.77 1.65,-2.79 0,-2.26 -1.75,-4 -4,-4H7v14h7.04c2.09,0 3.71,-1.7 3.71,-3.79 0,-1.52 -0.86,-2.82 -2.15,-3.42zM10,6.5h3c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5h-3v-3zm3.5,9H10v-3h3.5c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_clear_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_clear_24dp.xml
deleted file mode 100644
index 54e3092..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_clear_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.27,5L2,6.27l6.97,6.97L6.5,19h3l1.57,-3.66L16.73,21 18,19.73 3.55,5.27 3.27,5zM6,5v0.18L8.82,8h2.4l-0.72,1.68 2.1,2.1L14.21,8H20V5H6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_fill_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_fill_24dp.xml
deleted file mode 100644
index 99e6f6a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_fill_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.56,8.94L7.62,0 6.21,1.41l2.38,2.38 -5.15,5.15c-0.59,0.59 -0.59,1.54 0,2.12l5.5,5.5c0.29,0.29 0.68,0.44 1.06,0.44s0.77,-0.15 1.06,-0.44l5.5,-5.5c0.59,-0.58 0.59,-1.53 0,-2.12zM5.21,10L10,5.21 14.79,10H5.21zM19,11.5s-2,2.17 -2,3.5c0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-1.33 -2,-3.5 -2,-3.5z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M0,20h24v4H0z"
- android:fillAlpha=".36"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_reset_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_reset_24dp.xml
deleted file mode 100644
index f1153fd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_reset_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,14c0,-4 -6,-10.8 -6,-10.8s-1.33,1.51 -2.73,3.52l8.59,8.59c0.09,-0.42 0.14,-0.86 0.14,-1.31zm-0.88,3.12L12.5,12.5 5.27,5.27 4,6.55l3.32,3.32C6.55,11.32 6,12.79 6,14c0,3.31 2.69,6 6,6 1.52,0 2.9,-0.57 3.96,-1.5l2.63,2.63 1.27,-1.27 -2.74,-2.74z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_text_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_text_24dp.xml
deleted file mode 100644
index abc0c96..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_color_text_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M0,20h24v4H0z"
- android:fillAlpha=".36"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,3L5.5,17h2.25l1.12,-3h6.25l1.12,3h2.25L13,3h-2zm-1.38,9L12,5.67 14.38,12H9.62z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_decrease_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_decrease_24dp.xml
deleted file mode 100644
index 3cdf4fd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_decrease_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,17h10v-2H11v2zm-8,-5l4,4V8l-4,4zm0,9h18v-2H3v2zM3,3v2h18V3H3zm8,6h10V7H11v2zm0,4h10v-2H11v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_increase_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_increase_24dp.xml
deleted file mode 100644
index d4545b8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_indent_increase_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,21h18v-2H3v2zM3,8v8l4,-4 -4,-4zm8,9h10v-2H11v2zM3,3v2h18V3H3zm8,6h10V7H11v2zm0,4h10v-2H11v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_italic_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_italic_24dp.xml
deleted file mode 100644
index 95cdea6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_italic_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_line_spacing_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_line_spacing_24dp.xml
deleted file mode 100644
index 4bf031a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_line_spacing_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,7h2.5L5,3.5 1.5,7H4v10H1.5L5,20.5 8.5,17H6V7zm4,-2v2h12V5H10zm0,14h12v-2H10v2zm0,-6h12v-2H10v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_bulleted_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_bulleted_24dp.xml
deleted file mode 100644
index 6386300..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_bulleted_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zm0,-6c-0.83,0 -1.5,0.67 -1.5,1.5S3.17,7.5 4,7.5 5.5,6.83 5.5,6 4.83,4.5 4,4.5zm0,12.17c-0.74,0 -1.33,0.6 -1.33,1.33s0.6,1.33 1.33,1.33 1.33,-0.6 1.33,-1.33 -0.59,-1.33 -1.33,-1.33zM7,19h14v-2H7v2zm0,-6h14v-2H7v2zm0,-8v2h14V5H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_numbered_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_numbered_24dp.xml
deleted file mode 100644
index 1452f02..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_list_numbered_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,17h2v0.5H3v1h1v0.5H2v1h3v-4H2v1zm1,-9h1V4H2v1h1v3zm-1,3h1.8L2,13.1v0.9h3v-1H3.2L5,10.9V10H2v1zm5,-6v2h14V5H7zm0,14h14v-2H7v2zm0,-6h14v-2H7v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_paint_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_paint_24dp.xml
deleted file mode 100644
index c906069..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_paint_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,4V3c0,-0.55 -0.45,-1 -1,-1H5c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1V6h1v4H9v11c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-9h8V4h-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_quote_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_quote_24dp.xml
deleted file mode 100644
index 7e5e327..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_quote_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,17h3l2,-4V7H5v6h3zm8,0h3l2,-4V7h-6v6h3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_size_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_size_24dp.xml
deleted file mode 100644
index d8d8f35..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_size_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,4v3h5v12h3V7h5V4H9zm-6,8h3v7h3v-7h3V9H3v3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_strikethrough_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_strikethrough_24dp.xml
deleted file mode 100644
index a31a531..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_strikethrough_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,19h4v-3h-4v3zM5,4v3h5v3h4V7h5V4H5zM3,14h18v-2H3v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_l_to_r_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_l_to_r_24dp.xml
deleted file mode 100644
index 67b9e4f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_l_to_r_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,10v5h2V4h2v11h2V4h2V2H9C6.79,2 5,3.79 5,6s1.79,4 4,4zm12,8l-4,-4v3H5v2h12v3l4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_r_to_l_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_r_to_l_24dp.xml
deleted file mode 100644
index 7fa296a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_textdirection_r_to_l_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,10v5h2V4h2v11h2V4h2V2h-8C7.79,2 6,3.79 6,6s1.79,4 4,4zm-2,7v-3l-4,4 4,4v-3h12v-2H8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_underline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_underline_24dp.xml
deleted file mode 100644
index 0509b3e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_format_underline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,17c3.31,0 6,-2.69 6,-6V3h-2.5v8c0,1.93 -1.57,3.5 -3.5,3.5S8.5,12.93 8.5,11V3H6v8c0,3.31 2.69,6 6,6zm-7,2v2h14v-2H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_functions_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_functions_24dp.xml
deleted file mode 100644
index 0351ae8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_functions_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,4H6v2l6.5,6L6,18v2h12v-3h-7l5,-5 -5,-5h7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_chart_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_chart_24dp.xml
deleted file mode 100644
index 1d393b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_chart_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM9,17H7v-7h2v7zm4,0h-2V7h2v10zm4,0h-2v-4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_comment_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_comment_24dp.xml
deleted file mode 100644
index 44c6f6d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_comment_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4V4c0,-1.1 -0.9,-2 -2,-2zm-2,12H6v-2h12v2zm0,-3H6V9h12v2zm0,-3H6V6h12v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_drive_file_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_drive_file_24dp.xml
deleted file mode 100644
index 08e825b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_drive_file_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2H18c1.1,0 2,-0.9 2,-2V8l-6,-6H6zm7,7V3.5L18.5,9H13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_emoticon_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_emoticon_24dp.xml
deleted file mode 100644
index 1ef868a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_emoticon_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zm3.5,-9c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zm-7,0c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zm3.5,6.5c2.33,0 4.31,-1.46 5.11,-3.5H6.89c0.8,2.04 2.78,3.5 5.11,3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_invitation_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_invitation_24dp.xml
deleted file mode 100644
index e0030fd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_invitation_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,12h-5v5h5v-5zM16,1v2H8V1H6v2H5c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2h-1V1h-2zm3,18H5V8h14v11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_link_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_link_24dp.xml
deleted file mode 100644
index 1b8f436..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_link_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4V7H7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9H7c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2H8v2zm9,-6h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4V17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_photo_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_photo_24dp.xml
deleted file mode 100644
index 61dcb63..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_insert_photo_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_merge_type_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_merge_type_24dp.xml
deleted file mode 100644
index 4eee325..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_merge_type_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,20.41L18.41,19 15,15.59 13.59,17 17,20.41zM7.5,8H11v5.59L5.59,19 7,20.41l6,-6V8h3.5L12,3.5 7.5,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_comment_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_comment_24dp.xml
deleted file mode 100644
index b275ad9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_comment_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_edit_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_edit_24dp.xml
deleted file mode 100644
index 0ff336a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_mode_edit_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_publish_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_publish_24dp.xml
deleted file mode 100644
index b0651fc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_publish_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,4v2h14V4H5zm0,10h4v6h6v-6h4l-7,-7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_bottom_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_bottom_24dp.xml
deleted file mode 100644
index 877284e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_bottom_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,13h-3V3h-2v10H8l4,4 4,-4zM4,19v2h16v-2H4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_center_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_center_24dp.xml
deleted file mode 100644
index 65df54e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_center_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,19h3v4h2v-4h3l-4,-4 -4,4zm8,-14h-3V1h-2v4H8l4,4 4,-4zM4,11v2h16v-2H4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_top_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_top_24dp.xml
deleted file mode 100644
index 518a9f2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_vertical_align_top_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,11h3v10h2V11h3l-4,-4 -4,4zM4,3v2h16V3H4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_wrap_text_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_wrap_text_24dp.xml
deleted file mode 100644
index ec233dc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/editor/ic_wrap_text_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,19h6v-2H4v2zM20,5H4v2h16V5zm-3,6H4v2h13.25c1.1,0 2,0.9 2,2s-0.9,2 -2,2H15v-2l-3,3 3,3v-2h2c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_attachment_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_attachment_24dp.xml
deleted file mode 100644
index be5b538..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_attachment_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.5,18C4.46,18 2,15.54 2,12.5S4.46,7 7.5,7H18c2.21,0 4,1.79 4,4s-1.79,4 -4,4H9.5C8.12,15 7,13.88 7,12.5S8.12,10 9.5,10H17v1.5H9.5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1H18c1.38,0 2.5,-1.12 2.5,-2.5S19.38,8.5 18,8.5H7.5c-2.21,0 -4,1.79 -4,4s1.79,4 4,4H17V18H7.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_24dp.xml
deleted file mode 100644
index 41b533d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_circle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_circle_24dp.xml
deleted file mode 100644
index 0d6e8c3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_circle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm4.5,14H8c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3l0.14,0.01C8.58,8.28 10.13,7 12,7c2.21,0 4,1.79 4,4h0.5c1.38,0 2.5,1.12 2.5,2.5S17.88,16 16.5,16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_done_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_done_24dp.xml
deleted file mode 100644
index ad6129a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_done_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM10,17l-3.5,-3.5 1.41,-1.41L10,14.17 15.18,9l1.41,1.41L10,17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_download_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_download_24dp.xml
deleted file mode 100644
index d76c1f6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_download_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_off_24dp.xml
deleted file mode 100644
index cff2984..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4c-1.48,0 -2.85,0.43 -4.01,1.17l1.46,1.46C10.21,6.23 11.08,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3 0,1.13 -0.64,2.11 -1.56,2.62l1.45,1.45C23.16,18.16 24,16.68 24,15c0,-2.64 -2.05,-4.78 -4.65,-4.96zM3,5.27l2.75,2.74C2.56,8.15 0,10.77 0,14c0,3.31 2.69,6 6,6h11.73l2,2L21,20.73 4.27,4 3,5.27zM7.73,10l8,8H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h1.73z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_queue_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_queue_24dp.xml
deleted file mode 100644
index 104ec97..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_queue_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h0.71C7.37,7.69 9.48,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3s-1.34,3 -3,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_upload_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_upload_24dp.xml
deleted file mode 100644
index 9a79995..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_cloud_upload_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_file_download_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_file_download_24dp.xml
deleted file mode 100644
index 9c90028..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_file_download_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_file_upload_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_file_upload_24dp.xml
deleted file mode 100644
index e362b93..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_file_upload_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zm-4,2h14v2H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_24dp.xml
deleted file mode 100644
index 520679b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_open_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_open_24dp.xml
deleted file mode 100644
index 851069c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_open_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-8l-2,-2H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zm0,12H4V8h16v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_shared_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_shared_24dp.xml
deleted file mode 100644
index c4d91c6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/file/ic_folder_shared_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-8l-2,-2H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zm-5,3c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zm4,8h-8v-1c0,-1.33 2.67,-2 4,-2s4,0.67 4,2v1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_24dp.xml
deleted file mode 100644
index 2082de1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,3H3c-1.1,0 -2,0.9 -2,2v3h2V5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zm0,-4v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zm0,-4v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_connected_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_connected_24dp.xml
deleted file mode 100644
index af4a654..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_cast_connected_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M1,18v3h3c0,-1.66 -1.34,-3 -3,-3zm0,-4v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zm18,-7H5v1.63c3.96,1.28 7.09,4.41 8.37,8.37H19V7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11zm20,-7H3c-1.1,0 -2,0.9 -2,2v3h2V5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_computer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_computer_24dp.xml
deleted file mode 100644
index 60406f4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_computer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,18c1.1,0 1.99,-0.9 1.99,-2L22,6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_mac_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_mac_24dp.xml
deleted file mode 100644
index 0d51046..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_mac_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,2H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7l-2,3v1h8v-1l-2,-3h7c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm0,12H3V4h18v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_windows_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_windows_24dp.xml
deleted file mode 100644
index f4f1f3b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_desktop_windows_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,2H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2H8v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm0,14H3V4h18v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_dock_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_dock_24dp.xml
deleted file mode 100644
index e923ece..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_dock_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,23h8v-2H8v2zm8,-21.99L8,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM16,15H8V5h8v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_gamepad_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_gamepad_24dp.xml
deleted file mode 100644
index 8891427..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_gamepad_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,7.5V2H9v5.5l3,3 3,-3zM7.5,9H2v6h5.5l3,-3 -3,-3zM9,16.5V22h6v-5.5l-3,-3 -3,3zM16.5,9l-3,3 3,3H22V9h-5.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_24dp.xml
deleted file mode 100644
index 320d262..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7c0,-4.97 -4.03,-9 -9,-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_mic_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_mic_24dp.xml
deleted file mode 100644
index a8e775a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_headset_mic_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 -9,-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_24dp.xml
deleted file mode 100644
index 63c10e5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,5H4c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2zm-9,3h2v2h-2V8zm0,3h2v2h-2v-2zM8,8h2v2H8V8zm0,3h2v2H8v-2zm-1,2H5v-2h2v2zm0,-3H5V8h2v2zm9,7H8v-2h8v2zm0,-4h-2v-2h2v2zm0,-3h-2V8h2v2zm3,3h-2v-2h2v2zm0,-3h-2V8h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_alt_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_alt_24dp.xml
deleted file mode 100644
index 98a8966..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_alt_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.5,10c0.83,0 1.5,-0.67 1.5,-1.5S16.33,7 15.5,7 14,7.67 14,8.5s0.67,1.5 1.5,1.5zm-7,0c0.83,0 1.5,-0.67 1.5,-1.5S9.33,7 8.5,7 7,7.67 7,8.5 7.67,10 8.5,10zm3.5,7c2.61,0 4.83,-1.67 5.65,-4H6.35c0.82,2.33 3.04,4 5.65,4zm-0.01,-16C6.47,1 2,5.48 2,11s4.47,10 9.99,10C17.52,21 22,16.52 22,11S17.52,1 11.99,1zM12,19c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_down_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_down_24dp.xml
deleted file mode 100644
index d5c447b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_down_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_left_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_left_24dp.xml
deleted file mode 100644
index 7f27205..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_left_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14,5.5l-6,6 6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_right_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_right_24dp.xml
deleted file mode 100644
index 4bae6bb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_right_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_up_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_up_24dp.xml
deleted file mode 100644
index ebf42c3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_arrow_up_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_backspace_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_backspace_24dp.xml
deleted file mode 100644
index 8ada65f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_backspace_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,11H6.83l3.58,-3.59L9,6l-6,6 6,6 1.41,-1.41L6.83,13H21z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_capslock_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_capslock_24dp.xml
deleted file mode 100644
index 1c26f8a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_capslock_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,8.41L16.59,13 18,11.59l-6,-6 -6,6L7.41,13 12,8.41zM6,18h12v-2H6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_control_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_control_24dp.xml
deleted file mode 100644
index 74c9367..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_control_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm12,0c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm-6,0c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_hide_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_hide_24dp.xml
deleted file mode 100644
index 6ae3110..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_hide_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,3H4c-1.1,0 -1.99,0.9 -1.99,2L2,15c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-9,3h2v2h-2V6zm0,3h2v2h-2V9zM8,6h2v2H8V6zm0,3h2v2H8V9zm-1,2H5V9h2v2zm0,-3H5V6h2v2zm9,7H8v-2h8v2zm0,-4h-2V9h2v2zm0,-3h-2V6h2v2zm3,3h-2V9h2v2zm0,-3h-2V6h2v2zm-7,15l4,-4H8l4,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_return_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_return_24dp.xml
deleted file mode 100644
index f74dfc7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_return_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,7v4H5.83l3.58,-3.59L8,6l-6,6 6,6 1.41,-1.41L5.83,13H21V7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_tab_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_tab_24dp.xml
deleted file mode 100644
index 37c5692..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_tab_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.59,7.41L15.17,11H1v2h14.17l-3.59,3.59L13,18l6,-6 -6,-6 -1.41,1.41zM20,6v12h2V6h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_voice_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_voice_24dp.xml
deleted file mode 100644
index a41eedd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_keyboard_voice_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,15c1.66,0 2.99,-1.34 2.99,-3L15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6v6c0,1.66 1.34,3 3,3zm5.3,-3c0,3 -2.54,5.1 -5.3,5.1S6.7,15 6.7,12H5c0,3.42 2.72,6.23 6,6.72V22h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_24dp.xml
deleted file mode 100644
index 87ebf54..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,18c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_chromebook_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_chromebook_24dp.xml
deleted file mode 100644
index 78101d9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_chromebook_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,18V3H2v15H0v2h24v-2h-2zm-8,0h-4v-1h4v1zm6,-3H4V5h16v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_mac_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_mac_24dp.xml
deleted file mode 100644
index 471cf10..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_mac_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,18c1.1,0 1.99,-0.9 1.99,-2L22,5c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v11c0,1.1 0.9,2 2,2H0c0,1.1 0.9,2 2,2h20c1.1,0 2,-0.9 2,-2h-4zM4,5h16v11H4V5zm8,14c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_windows_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_windows_24dp.xml
deleted file mode 100644
index e4beebb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_laptop_windows_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,18v-1c1.1,0 1.99,-0.9 1.99,-2L22,5c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2v1H0v2h24v-2h-4zM4,5h16v10H4V5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_memory_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_memory_24dp.xml
deleted file mode 100644
index 53a7f92..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_memory_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,9H9v6h6V9zm-2,4h-2v-2h2v2zm8,-2V9h-2V7c0,-1.1 -0.9,-2 -2,-2h-2V3h-2v2h-2V3H9v2H7c-1.1,0 -2,0.9 -2,2v2H3v2h2v2H3v2h2v2c0,1.1 0.9,2 2,2h2v2h2v-2h2v2h2v-2h2c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2zm-4,6H7V7h10v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_mouse_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_mouse_24dp.xml
deleted file mode 100644
index 83281a8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_mouse_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,1.07V9h7c0,-4.08 -3.05,-7.44 -7,-7.93zM4,15c0,4.42 3.58,8 8,8s8,-3.58 8,-8v-4H4v4zm7,-13.93C7.05,1.56 4,4.92 4,9h7V1.07z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_android_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_android_24dp.xml
deleted file mode 100644
index 94b46f0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_android_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,1H8C6.34,1 5,2.34 5,4v16c0,1.66 1.34,3 3,3h8c1.66,0 3,-1.34 3,-3V4c0,-1.66 -1.34,-3 -3,-3zm-2,20h-4v-1h4v1zm3.25,-3H6.75V4h10.5v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_iphone_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_iphone_24dp.xml
deleted file mode 100644
index d234325..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phone_iphone_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.5,1h-8C6.12,1 5,2.12 5,3.5v17C5,21.88 6.12,23 7.5,23h8c1.38,0 2.5,-1.12 2.5,-2.5v-17C18,2.12 16.88,1 15.5,1zm-4,21c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm4.5,-4H7V4h9v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_24dp.xml
deleted file mode 100644
index e54c722..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,6h18V4H4c-1.1,0 -2,0.9 -2,2v11H0v3h14v-3H4V6zm19,2h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zm-1,9h-4v-7h4v7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_off_24dp.xml
deleted file mode 100644
index 91b57e8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_phonelink_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,6V4H6.82l2,2H22zM1.92,1.65L0.65,2.92l1.82,1.82C2.18,5.08 2,5.52 2,6v11H0v3h17.73l2.35,2.35 1.27,-1.27L3.89,3.62 1.92,1.65zM4,6.27L14.73,17H4V6.27zM23,8h-6c-0.55,0 -1,0.45 -1,1v4.18l2,2V10h4v7h-2.18l3,3H23c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_security_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_security_24dp.xml
deleted file mode 100644
index 5110f1e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_security_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12V5l-9,-4zm0,10.99h7c-0.53,4.12 -3.28,7.79 -7,8.94V12H5V6.3l7,-3.11v8.8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_sim_card_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_sim_card_24dp.xml
deleted file mode 100644
index de928fc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_sim_card_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.99,4c0,-1.1 -0.89,-2 -1.99,-2h-8L4,8v12c0,1.1 0.9,2 2,2h12.01c1.1,0 1.99,-0.9 1.99,-2l-0.01,-16zM9,19H7v-2h2v2zm8,0h-2v-2h2v2zm-8,-4H7v-4h2v4zm4,4h-2v-4h2v4zm0,-6h-2v-2h2v2zm4,2h-2v-4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_smartphone_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_smartphone_24dp.xml
deleted file mode 100644
index 8b9235f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_smartphone_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_24dp.xml
deleted file mode 100644
index f0c2944..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_speaker_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,2H7c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-5,2c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zm0,16c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zm0,-8c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_24dp.xml
deleted file mode 100644
index 2f97c67..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,4H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 1.99,-0.9 1.99,-2L23,6c0,-1.1 -0.9,-2 -2,-2zm-2,14H5V6h14v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_android_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_android_24dp.xml
deleted file mode 100644
index 7a63df9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_android_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,0H6C4.34,0 3,1.34 3,3v18c0,1.66 1.34,3 3,3h12c1.66,0 3,-1.34 3,-3V3c0,-1.66 -1.34,-3 -3,-3zm-4,22h-4v-1h4v1zm5.25,-3H4.75V3h14.5v16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_mac_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_mac_24dp.xml
deleted file mode 100644
index 3318071..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tablet_mac_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.5,0h-14C3.12,0 2,1.12 2,2.5v19C2,22.88 3.12,24 4.5,24h14c1.38,0 2.5,-1.12 2.5,-2.5v-19C21,1.12 19.88,0 18.5,0zm-7,23c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm7.5,-4H4V3h15v16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tv_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tv_24dp.xml
deleted file mode 100644
index 245642a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_tv_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,3H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zm0,14H3V5h18v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_watch_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_watch_24dp.xml
deleted file mode 100644
index e90cf2d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/hardware/ic_watch_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,12c0,-2.54 -1.19,-4.81 -3.04,-6.27L16,0H8l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12zM6,12c0,-3.31 2.69,-6 6,-6s6,2.69 6,6 -2.69,6 -6,6 -6,-2.69 -6,-6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_delivery_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_delivery_36px.xml
deleted file mode 100644
index 244971a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_delivery_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2.5,16.875L4.375,15H6.25v3H2.5V16.875z M7,26.5c0,1.932,1.556,3.5,3.491,3.5 C12.432,30,14,28.432,14,26.5c0,-1.933,-1.568,-3.5,-3.509,-3.5C8.556,23,7,24.567,7,26.5z M14,23l2,2h9l2,-2H14z M27,26.5 c0,1.932,1.566,3.5,3.5,3.5c1.933,0,3.5,-1.568,3.5,-3.5c0,-1.933,-1.567,-3.5,-3.5,-3.5C28.566,23,27,24.567,27,26.5z M9,13v9 c-0.488,0,-3,0,-3,2l-2.833,0.083L1,23l0.01,-6.5L4,13H9z M10,21V10c0,-2.128,2 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_flight_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_flight_36px.xml
deleted file mode 100644
index 33b3aac..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_flight_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.964,33l-3.234,-6.728L3,23.035l1.421,-1.422c0.173,-0.168,0.407,-0.233,0.627,-0.188l5.707,0.828l5.769,-6.188 L4.421,10.226l1.942,-1.939c0.176,-0.176,0.465,-0.28,0.688,-0.23l13.032,3.593l5.87,-5.871c1.6,-1.601,3.732,-1.604,4.805,-0.535 c1.066,1.071,1.066,3.205,-0.537,4.805l-5.873,5.87l3.592,13.034c0.055,0.223,-0.051,0.508,-0.227,0.684l-1.939,1.941l-5.836,-12.1 l-6.191,5.766l0.826,5.71c0.049,0.217,-0.016,0.451,-0.1 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_hangout_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_hangout_36px.xml
deleted file mode 100644
index 1483f09..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_hangout_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.25,3C10.209,3,4.5,8.709,4.5,15.75S10.209,28.5,17.25,28.5H18v5.25c7.289,-3.516,12,-11.25,12,-18 C30,8.709,24.291,3,17.25,3z M16.5,16.5l-1.5,3h-2.25l1.5,-3H12V12h4.5V16.5z M22.5,16.5l-1.5,3h-2.25l1.5,-3H18V12h4.5V16.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_location_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_location_36px.xml
deleted file mode 100644
index 95a390c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_location_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,3C12.202,3,7.5,7.701,7.5,13.5C7.5,21.375,18,33,18,33s10.5,-11.625,10.5,-19.5C28.5,7.701,23.798,3,18,3z M18,17.25 c-2.071,0,-3.75,-1.679,-3.75,-3.75S15.929,9.75,18,9.75s3.75,1.679,3.75,3.75S20.071,17.25,18,17.25z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_purchase_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_purchase_36px.xml
deleted file mode 100644
index c8cea24..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_purchase_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M32.736,13h-7.056l-6.454,-9.345c-0.452,-0.677,-1.367,-0.859,-2.044,-0.407C17.016,3.36,16.881,3.5,16.776,3.656l-0.003,-0.001 L16.77,3.663l-0.002,0.001L10.319,13H3.264c-0.814,0,-1.475,0.976,-1.475,1.79c0,0.137,0.019,0.269,0.054,0.395L5.58,28.843 C5.926,30.087,7.066,31,8.421,31h19.157c1.355,0,2.496,-0.913,2.842,-2.157l3.737,-13.658c0.034,-0.126,0.053,-0.258,0.053,-0.395 C34.21,13.976,33.551,13,32.736,13z M18,25c-1.657,0,-3,-1.343 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_restaurant_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_restaurant_36px.xml
deleted file mode 100644
index 7e041e7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_restaurant_36px.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M0.0,0.0 h36.0 v36.0 h-36.0z"
- android:fillAlpha="0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,10c0,0.55,-0.45,1,-1,1l0,0c-0.55,0,-1,-0.45,-1,-1V3c0,-0.55,-0.45,-1,-1,-1l0,0c-0.55,0,-1,0.45,-1,1v7c0,0.55,-0.45,1,-1,1l0,0 c-0.55,0,-1,-0.45,-1,-1V3c0,-0.55,-0.45,-1,-1,-1l0,0C7.45,2,7,2.45,7,3v9c0,0.55,0.232,1.386,0.514,1.857l1.971,3.285 c0.283,0.472,0.486,1.307,0.452,1.856L9.062,33.002C9.028,33.551,9.45,34,10,34h4c0.55,0,0.972,-0.449,0.938,-0.998l-0.875,-14.004 c-0.034,-0.549,0.169,-1.384,0.452,-1.856l1.971,-3.285C16. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_ticket_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_ticket_36px.xml
deleted file mode 100644
index 150e96d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_ticket_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M32,15v-5c0,-1.656,-1.344,-3,-3,-3H7c-1.656,0,-3,1.344,-3,3v5c1.656,0,3,1.344,3,3s-1.344,3,-3,3v5c0,1.656,1.344,3,3,3h22 c1.656,0,3,-1.344,3,-3v-5c-1.656,0,-3,-1.344,-3,-3S30.344,15,32,15z M22.695,24.75L18,21.917l-4.695,2.833l1.242,-5.341L10.5,15.75 l5.363,-0.404L18,10.5l2.137,4.846L25.5,15.75l-4.047,3.659L22.695,24.75z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_wallet_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_wallet_36px.xml
deleted file mode 100644
index 352a520..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_wallet_36px.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.797,5.986c-0.082,-0.053,-0.154,-0.115,-0.238,-0.168c-0.525,-0.327,-1.203,-0.421,-1.881,-0.451l-0.156,0.062 c-0.104,0,-0.129,0,-0.152,0c-0.559,0,-1.119,0.058,-1.637,0.342c-1.576,0.868,-2.115,2.757,-1.207,4.263 c1.26,2.076,1.979,4.347,2.178,6.667c-0.025,-0.033,-0.057,-0.072,-0.08,-0.107c0.496,4.996,-1.418,8.639,-2.539,11.144 c0.004,0.035,0.014,0.068,0.016,0.104c0.094,0.988,0.666,1.92,1.637,2.456c0.518,0.285,1.086 [...]
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.623,16.585c-1.924,-2.673,-4.453,-4.963,-7.469,-6.643C6.633,9.651,6.061,9.514,5.498,9.514 c-1.137,0,-2.238,0.56,-2.85,1.562c-0.918,1.499,-0.387,3.427,1.186,4.3c4.678,2.607,7.75,7.22,8.25,12.356 C13.205,25.228,15.119,21.581,14.623,16.585z"
- android:fillAlpha="0.5"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M30.631,3.937C30.051,2.718,28.799,2,27.488,2c-0.477,0,-0.957,0.093,-1.416,0.292c-1.736,0.746,-2.508,2.694,-1.725,4.348 c1.248,2.64,2.033,5.42,2.369,8.238c-0.025,-0.043,-0.062,-0.081,-0.09,-0.124c0.754,7.063,-1.365,12.161,-2.576,15.625 c-0.002,0.054,0,0.105,-0.002,0.159c-0.072,1.313,0.689,2.596,2.023,3.168C26.531,33.907,27.012,34,27.484,34 c1.314,0,2.566,-0.721,3.146,-1.938C34.857,23.125,34.857,12.873,30.631,3.937z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M26.627,14.754c-2.207,-3.453,-5.199,-6.465,-8.83,-8.768c3.148,3.236,3.961,8.321,3.549,13.183 c1.916,3.388,2.893,7.253,2.705,11.21C25.262,26.915,27.381,21.817,26.627,14.754z"
- android:fillAlpha="0.5"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_weather_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_weather_36px.xml
deleted file mode 100644
index 7d347c9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_weather_36px.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9.067,15.237c0.064,0,0.129,0.012,0.194,0.012c0.706,-2.286,2.23,-4.253,4.303,-5.558 c-1.011,-1.79,-2.902,-3.017,-5.104,-3.017c-3.257,0,-5.896,2.64,-5.896,5.896c0,1.783,0.814,3.357,2.066,4.437 c0.011,-0.011,0.021,-0.022,0.032,-0.033C5.915,15.841,7.477,15.237,9.067,15.237z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M29.026,18.433c-0.454,0,-0.891,0.068,-1.306,0.186c-0.336,-4.412,-3.927,-7.886,-8.313,-7.886c-4.487,0,-8.145,3.634,-8.335,8.191 c-0.636,-0.312,-1.342,-0.492,-2.095,-0.492c-2.693,0,-4.878,2.02,-4.878,4.784S6.284,28,8.978,28c2.041,0,18.21,0,20.049,0 c2.691,0,4.874,-2.02,4.874,-4.783S31.718,18.433,29.026,18.433z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_youtube_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_act_youtube_36px.xml
deleted file mode 100644
index 394ad76..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_act_youtube_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M30.003,6.601C29.101,6.305,23.552,6,18,6C12.451,6,6.902,6.281,6,6.576C3.653,7.351,3,12.606,3,18 s0.653,10.649,3,11.424C6.902,29.72,12.451,30,18,30c5.552,0,11.101,-0.28,12.003,-0.576c2.344,-0.775,2.982,-6.031,2.982,-11.424 S32.347,7.374,30.003,6.601z M15,24V12l8.25,6L15,24z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_add_cluster_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_add_cluster_36px.xml
deleted file mode 100644
index 4a70cf1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_add_cluster_36px.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="35dp"
- android:viewportWidth="36.0"
- android:viewportHeight="35.998">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.0,8.0 h3.0 v21.0 h-3.0z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.0,17.0 h21.0 v3.0 h-21.0z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_check_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_check_24dp.xml
deleted file mode 100644
index 065f56e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_check_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,16.17l-4.17,-4.17 -1.42,1.41 5.59,5.59 12,-12 -1.41,-1.41z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_chevron_down_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_chevron_down_36px.xml
deleted file mode 100644
index af6708a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_chevron_down_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M24.879,12.879l-6.879000,6.879000 -6.879000,-6.879000 -2.121000,2.121000 9.000000,9.000000 9.000000,-9.000000z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_compose_popout_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_compose_popout_36px.xml
deleted file mode 100644
index 6019979..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_compose_popout_36px.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M0.0,0.0 h36.0 v36.0 h-36.0z"
- android:fillAlpha="0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.41,24.832l1.094000,1.171999 6.496000,-6.004000 -6.496000,-6.000000 -1.094000,1.168000 3.086000,2.832000 -17.496000,0.000000 0.000000,4.000000 17.500000,0.000000z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,3v11h3v-4h19v20H11v-4H8v7h25V3H8z M30,8H11V6h19V8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_custom_cluster_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_custom_cluster_36px_clr.xml
deleted file mode 100644
index 50a2c2b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_custom_cluster_36px_clr.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M31.954,8.458L13.313,6.02c-1.288,-0.168,-2.467,0.74,-2.637,2.029l-0.527,4.041l12.641,-1.374 c1.959,-0.212,3.708,1.218,3.917,3.157l1.157,10.673l1.961,0.257c1.287,0.169,2.465,-0.741,2.632,-2.029l1.523,-11.676 C34.148,9.809,33.242,8.627,31.954,8.458z"
- android:fillAlpha="0.6"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M22.382,12.2L4.057,14.163c-1.265,0.136,-2.181,1.256,-2.042,2.507l1.242,11.301c0.137,1.248,1.274,2.151,2.54,2.016 l18.325,-1.963c1.266,-0.137,2.18,-1.259,2.042,-2.505l-1.243,-11.303C24.784,12.967,23.647,12.064,22.382,12.2 M22.425,16.516 l-7.41,6.525v-0.002c-0.18,0.171,-0.412,0.286,-0.678,0.315c-0.263,0.027,-0.518,-0.036,-0.726,-0.166l-0.001,0.004l-8.657,-4.805 c-0.282,-0.18,-0.484,-0.48,-0.525,-0.835c-0.068,-0.626,0 [...]
- android:fillAlpha="0.6"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_finance_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_finance_36px_clr.xml
deleted file mode 100644
index 2b736e1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_finance_36px_clr.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M0.0,0.0 h36.0 v36.0 h-36.0z"
- android:fillAlpha="0"
- android:fillColor="#689F38"/>
- <path
- android:pathData="M6.0,20.0 h6.0 v13.0 h-6.0z"
- android:fillColor="#689F38"/>
- <path
- android:pathData="M15.0,21.0 h6.0 v12.0 h-6.0z"
- android:fillColor="#689F38"/>
- <path
- android:pathData="M24.0,14.0 h6.0 v19.0 h-6.0z"
- android:fillColor="#689F38"/>
- <path
- android:pathData="M3.707,18.707l-1.414000,-1.414001 8.207000,-8.206999 6.000000,6.000000 11.500000,-11.500000 1.414000,1.414000 -12.914000,12.914000 -6.000000,-6.000000z"
- android:fillColor="#689F38"/>
- <path
- android:pathData="M24.535,5.0l3.465000,0.000000 0.000000,3.464000 2.000000,-1.628000 0.000000,-3.836000 -3.836000,0.000000z"
- android:fillColor="#689F38"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_forums_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_forums_36px_clr.xml
deleted file mode 100644
index b742c9b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_forums_36px_clr.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M24,7.111C24,5.393,22.605,4,20.889,4H7.111C5.395,4,4.016,5.393,4.016,7.111L4,25l6.223,-5H20 c2.375,0,4,-2,4,-4V7.111z M12,22v1.889C12,25.607,13.395,27,15.111,27h10.666L32,33V14.111C32,12.393,30.605,11,28.889,11H26 l0.01,5c0,4,-1.979,6,-5.01,6H12z"
- android:fillColor="#3F51B5"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_history_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_history_24dp.xml
deleted file mode 100644
index 4174bf2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_history_24dp.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,3c-4.97,0 -9,4.03 -9,9h-3l3.89,3.89 0.07,0.14 4.04,-4.03h-3c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42c1.63,1.63 3.87,2.64 6.36,2.64 4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm-1,5v5l4.28,2.54 0.72,-1.21 -3.5,-2.08v-4.25h-1.5z"
- android:fillAlpha=".9"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_inbox_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_inbox_36px_clr.xml
deleted file mode 100644
index 5f77a5c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_inbox_36px_clr.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M30,6H6C4.344,6,3.015,7.343,3.015,9L3,27c0,1.657,1.344,3,3,3h24c1.656,0,3,-1.343,3,-3V9 C33,7.343,31.656,6,30,6z M29.44,13.168L18.92,20.685v-0.002C18.665,20.881,18.346,21,18,21s-0.665,-0.119,-0.917,-0.317 l-0.003,0.002L6.56,13.168C6.22,12.892,6,12.471,6,12c0,-0.83,0.671,-1.5,1.5,-1.5c0.328,0,0.63,0.104,0.876,0.283L18,17.628 l9.624,-6.845C27.87,10.604,28.172,10.5,28.5,10.5c0.829,0,1.5,0.67,1.5,1.5C30,12.471,29.783,12.892,29.44,13.168"
- android:fillColor="#4285F4"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_low_priority_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_low_priority_36px.xml
deleted file mode 100644
index ad29907..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_low_priority_36px.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4.457,25.029L0,29h14V15l-4.113,4.572C5.264,14.97,3.648,8.091,4.556,2.556C0.88,8.1,-1.096,17.663,4.457,25.029z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M35,10c0,0.55,-0.45,1,-1,1H18c-0.55,0,-1,-0.45,-1,-1V7c0,-0.55,0.45,-1,1,-1h16c0.55,0,1,0.45,1,1V10z"
- android:fillAlpha="0.6"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M35,19c0,0.55,-0.45,1,-1,1H18c-0.55,0,-1,-0.45,-1,-1v-3c0,-0.55,0.45,-1,1,-1h16c0.55,0,1,0.45,1,1V19z"
- android:fillAlpha="0.6"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M35,28c0,0.55,-0.45,1,-1,1H18c-0.55,0,-1,-0.45,-1,-1v-3c0,-0.55,0.45,-1,1,-1h16c0.55,0,1,0.45,1,1V28z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_multiselect_check_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_multiselect_check_36px.xml
deleted file mode 100644
index 0e03c3c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_multiselect_check_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M30.0,9.0l-2.551001,-1.726000 -12.448999,16.726000 -7.131000,-5.250000 -1.869000,2.540001 9.750000,7.209999z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_offers_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_offers_36px_clr.xml
deleted file mode 100644
index 02fe5e1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_offers_36px_clr.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M4,28.889C4,30.605,5.395,32,7.111,32h7.777L4,21.111V28.889z M31.089,17.362L18.645,4.912 c-0.565,-0.562,-1.344,-0.91,-2.2,-0.912H7.889C5.741,4,4,5.741,4,7.889v8.556c0,0.86,0.35,1.636,0.911,2.2v0.001l12.451,12.443 C17.924,31.65,18.702,32,19.562,32c0.86,0,1.638,-0.348,2.2,-0.91l9.324,-9.326C31.65,21.202,32,20.421,32,19.562 S31.65,17.924,31.089,17.362 M9.444,12.167c-1.507,0,-2.722,-1.221,-2.722,-2.723s1.215,-2.723,2.72 [...]
- android:fillColor="#00BCD4"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_pin_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_pin_36px.xml
deleted file mode 100644
index 6fcad41..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_pin_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M24,7.091c0,0,3,-1.092,3,-3.091V2H10v2c0,1.999,3,3.091,3,3.091V18c-2.463,0,-4,1.46,-4,4h8l0.075,10.545 C17.114,33.179,17.787,34,18.5,34s1.384,-0.821,1.425,-1.455L20,22h8c0,-2.54,-1.546,-4,-4,-4V7.091z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_purchase_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_purchase_36px_clr.xml
deleted file mode 100644
index f014a95..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_purchase_36px_clr.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M31.799,11H10.939c-0.029,0,-0.05,0.015,-0.078,0.016L9.068,5H3.49C2.666,5,2,5.671,2,6.5 S2.666,8,3.49,8h3.36l4.151,13.931C11.195,22.581,11.787,23,12.43,23H29.15c0.817,0,1.636,-0.909,1.813,-1.715l2.002,-8.945 C33.143,11.534,32.617,11,31.799,11z"
- android:fillColor="#795548"/>
- <path
- android:pathData="M27.5,29.0 m-3.0, 0 a 3.0,3.0 0 1,1 6.0,0 a3.0,3.0 0 1,1 -6.0,0"
- android:fillColor="#795548"/>
- <path
- android:pathData="M14.0,29.0 m-3.0, 0 a 3.0,3.0 0 1,1 6.0,0 a3.0,3.0 0 1,1 -6.0,0"
- android:fillColor="#795548"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_reminder_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_reminder_36px_clr.xml
deleted file mode 100644
index ecfe620..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_reminder_36px_clr.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M22,8v0.668C22.548,8.839,23.496,9,24.524,9c2.225,0,4.809,-0.756,4.44,-3.802 C28.771,3.583,27.944,3,26.896,3C25.291,3,23.168,4.365,22,5.49V4c0,-1.104,-0.896,-2,-2,-2s-2,0.896,-2,2v1.49 C16.832,4.365,14.709,3,13.104,3c-1.048,0,-1.876,0.583,-2.069,2.198C10.667,8.244,13.251,9,15.476,9 C16.503,9,17.452,8.839,18,8.668V8H22z M22.017,6.859C22.306,6.197,25.045,3.9,26.896,3.9c0.353,0,1.007,0,1.176,1.406 c0.09 [...]
- android:fillColor="#3C80F5"/>
- <path
- android:pathData="M29,17c-2.277,1.139,-3.736,2.739,-4.469,3.875c-0.906,1.406,-1.25,0.828,-1.25,0.828 s-0.344,-0.391,-0.719,-1.328S22,17,22,17V8.609h-4v4.641c0,-1.1,-0.9,-1.25,-2,-1.25s-2,0.9,-2,2v0.25c0,-1.1,-0.9,-1.25,-2,-1.25s-2,0.9,-2,2 v1.25c0,-1.1,-1.969,-1.562,-2.844,-0.688c-0.748,0.748,-1.031,1.744,-1.031,2.844L6,22c0,4.375,2.875,11,6.369,11 c1.631,0,5.818,0,5.818,0c3.438,0,5.23,-3,5.23,-3l2.676,-3.594c0.562,-0.547,0.634,-0.8 [...]
- android:fillColor="#3C80F5"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_reply_all_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_reply_all_36px.xml
deleted file mode 100644
index c2075dc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_reply_all_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.828,11.672v-4.5L0,18l10.859,10.859v-4.5L4.5,18L10.828,11.672z M20,13.5v-6L9,18l11,10.5v-6.149 c7.5,0,12.25,2.399,16,7.649C34.5,22.5,30.5,15,20,13.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_rotate_24_01.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_rotate_24_01.xml
deleted file mode 100644
index fbbd84d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_rotate_24_01.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.25,1.75c-0.6,-0.6,-1.5,-0.6,-2.1,0l-6.4,6.4c-0.6,0.6,-0.6,1.5,0,2.1l12,12c0.6,0.6,1.5,0.6,2.1,0l6.4,-6.4 c0.6,-0.6,0.6,-1.5,0,-2.1L10.25,1.75z M14.85,21.25l-12,-12l6.4,-6.4l12,12L14.85,21.25z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.55,2.5c3.3,1.5,5.6,4.7,6,8.5h1.5c-0.6,-6.2,-5.7,-11,-12,-11c-0.2,0,-0.4,0,-0.7,0l3.8,3.8L16.55,2.5z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.55,21.5c-3.3,-1.5,-5.6,-4.7,-6,-8.5h-1.4c0.5,6.2,5.6,11,11.9,11c0.2,0,0.4,0,0.7,0l-3.8,-3.8L7.55,21.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_search_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_search_24dp.xml
deleted file mode 100644
index 6cc717d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_search_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.5,14h-0.79l-0.28,-0.27c0.98,-1.14 1.57,-2.62 1.57,-4.23 0,-3.59 -2.91,-6.5 -6.5,-6.5s-6.5,2.91 -6.5,6.5 2.91,6.5 6.5,6.5c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99 1.49,-1.49 -4.99,-5zm-6,0c-2.49,0 -4.5,-2.01 -4.5,-4.5s2.01,-4.5 4.5,-4.5 4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_social_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_social_36px_clr.xml
deleted file mode 100644
index 5c0b9f4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_social_36px_clr.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M12.314,23.667c-0.92,-0.513,-1.747,-0.437,-2.871,-0.437C5.879,23.23,3.015,27,3.015,29h8.142 C11.189,27,11.459,25.13,12.314,23.667z"
- android:fillColor="#DB4437"/>
- <path
- android:pathData="M23.25,20c-4.549,0,-9.75,3,-9.75,9H33C33,23,27.803,20,23.25,20z"
- android:fillColor="#DB4437"/>
- <path
- android:pathData="M23.0,12.0 m-5.0, 0 a 5.0,5.0 0 1,1 10.0,0 a5.0,5.0 0 1,1 -10.0,0"
- android:fillColor="#DB4437"/>
- <path
- android:pathData="M10.0,16.0 m-4.0, 0 a 4.0,4.0 0 1,1 8.0,0 a4.0,4.0 0 1,1 -8.0,0"
- android:fillColor="#DB4437"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_speed_dial_48px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_speed_dial_48px_clr.xml
deleted file mode 100644
index 7968925..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_speed_dial_48px_clr.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="48dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
- <path
- android:pathData="M38,22H26V10c0,-1.1,-0.898,-2,-2,-2l0,0c-1.098,0,-2,0.9,-2,2v12H10c-1.098,0,-2,0.9,-2,2l0,0 c0,1.1,0.902,2,2,2h12v12c0,1.1,0.902,2,2,2l0,0c1.102,0,2,-0.9,2,-2V26h12c1.102,0,2,-0.9,2,-2l0,0C40,22.9,39.102,22,38,22z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M38,21H26V9c0,-1.1,-0.898,-2,-2,-2l0,0c-1.098,0,-2,0.9,-2,2v12H10c-1.098,0,-2,0.9,-2,2l0,0 c0,1.1,0.902,2,2,2h12v12c0,1.1,0.902,2,2,2l0,0c1.102,0,2,-0.9,2,-2V25h12c1.102,0,2,-0.9,2,-2l0,0C40,21.9,39.102,21,38,21z"
- android:fillAlpha="0.15"
- android:fillColor="#070707"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_sys_tty_24px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_sys_tty_24px.xml
deleted file mode 100644
index 53b32cf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_sys_tty_24px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22.1,4.2H1.9C1,4.2,0.2,5,0.2,5.9v12.1c0,1,0.8,1.8,1.8,1.8h20.1c1,0,1.8,-0.8,1.8,-1.8V5.9C23.8,5,23,4.2,22.1,4.2z M8,9.3H5.5V16H3.9V9.3H1.5V8.1H8V9.3z M15.3,9.3h-2.5v6.6h-1.6V9.3H8.7V8.1h6.5V9.3z M19.7,13.2V16h-1.6v-2.8l-2.7,-5h1.7 l1.7,3.7l1.7,-3.7h1.7L19.7,13.2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_travel_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_travel_36px_clr.xml
deleted file mode 100644
index 54d2477..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_travel_36px_clr.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M12.964,33l-3.234,-6.728L3,23.035l1.421,-1.422c0.173,-0.168,0.407,-0.233,0.627,-0.188l5.707,0.828 l5.769,-6.188L4.421,10.226l1.942,-1.939c0.176,-0.176,0.465,-0.28,0.688,-0.23l13.032,3.593l5.87,-5.871 c1.6,-1.601,3.732,-1.604,4.805,-0.535c1.066,1.071,1.066,3.205,-0.537,4.805l-5.873,5.87l3.592,13.034 c0.055,0.223,-0.051,0.508,-0.227,0.684l-1.939,1.941l-5.836,-12.1l-6.191,5.766l0.826,5.71c0.049,0.217,-0.016,0.451,-0.1 [...]
- android:fillColor="#9C27B0"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_upcoming_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_upcoming_36px_clr.xml
deleted file mode 100644
index 33f29ed..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_upcoming_36px_clr.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M18,5.5c6.893,0,12.5,5.607,12.5,12.5S24.893,30.5,18,30.5S5.5,24.892,5.5,18S11.107,5.5,18,5.5 M18,3C9.715,3,3,9.714,3,18c0,8.285,6.715,15,15,15s15,-6.715,15,-15C33,9.714,26.285,3,18,3L18,3z"
- android:fillColor="#ECA403"/>
- <path
- android:pathData="M24.4,24.248L16,19.2V9h1.699l1.242,9l6.249,5.25L24.4,24.248z"
- android:fillColor="#ECA403"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_updates_36px_clr.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_updates_36px_clr.xml
deleted file mode 100644
index 4c01b8f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_updates_36px_clr.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:pathData="M11,4.307v15.321c6.665,-0.811,13,3.372,20,-0.307V4C24.332,7.997,17.665,3.496,11,4.307"
- android:fillColor="#FF5722"/>
- <path
- android:pathData="M9,31.5C9,32.329,8.328,33,7.5,33l0,0C6.672,33,6,32.329,6,31.5v-27C6,3.671,6.672,3,7.5,3l0,0 C8.328,3,9,3.671,9,4.5V31.5z"
- android:fillColor="#FF5722"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_vasquette_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_vasquette_36px.xml
deleted file mode 100644
index 5501f62..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_vasquette_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,12h6V6H6V12z M6,21h6v-6H6V21z M15,30h6v-6h-6V30z M6,30h6v-6H6V30z M15,12h6V6h-6V12z M24,21h6v-6h-6V21z M24,6v6h6 V6H24z M15,21h6v-6h-6V21z M24,30h6v-6h-6V30z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/ic_volume_off.xml b/base/asset-studio/src/main/java/images/material_design_icons/ic_volume_off.xml
deleted file mode 100644
index fbccb29..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/ic_volume_off.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="48dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M33,24c0,-3.53 -2.04,-6.58 -5,-8.05v4.42l4.91,4.91c0.06,-0.42 0.09,-0.85 0.09,-1.28zm5,0c0,1.88 -0.41,3.65 -1.08,5.28l3.03,3.03C41.25,29.82 42,27 42,24c0,-8.56 -5.99,-15.72 -14,-17.54v4.13c5.78,1.72 10,7.07 10,13.41zM8.55,6L6,8.55 15.45,18H6v12h8l10,10V26.55l8.51,8.51c-1.34,1.03 -2.85,1.86 -4.51,2.36v4.13c2.75,-0.63 5.26,-1.89 7.37,-3.62L39.45,42 42,39.45l-18,-18L8.55,6zM24,8l-4.18,4.18L24,16.36V8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_add_to_photos_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_add_to_photos_24dp.xml
deleted file mode 100644
index 78eaca1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_add_to_photos_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-1,9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_adjust_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_adjust_24dp.xml
deleted file mode 100644
index 14ea87e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_adjust_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zm3,-8c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_photo_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_photo_24dp.xml
deleted file mode 100644
index 581ba88..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_assistant_photo_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.4,6L14,4H5v17h2v-7h5.6l0.4,2h7V6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_audiotrack_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_audiotrack_24dp.xml
deleted file mode 100644
index ac5d072..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_audiotrack_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_circular_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_circular_24dp.xml
deleted file mode 100644
index a29dbd2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_circular_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zm0,4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zM7,9.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zm3,7c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zm-3,-3c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zm3,-6c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 - [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_linear_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_linear_24dp.xml
deleted file mode 100644
index c52651c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_linear_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,17.5c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5 -1.5,0.67 -1.5,1.5 0.67,1.5 1.5,1.5zM9,13c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zm0,-4c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zM3,21h18v-2H3v2zM5,9.5c0.83,0 1.5,-0.67 1.5,-1.5S5.83,6.5 5,6.5 3.5,7.17 3.5,8 4.17,9.5 5,9.5zm0,4c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5 -1.5,0.67 -1.5,1.5 0.67,1.5 1.5,1.5zM9,17c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1, [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_off_24dp.xml
deleted file mode 100644
index 8511153..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,7c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1 -1,0.45 -1,1 0.45,1 1,1zm-0.2,4.48l0.2,0.02c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5 -1.5,0.67 -1.5,1.5l0.02,0.2c0.09,0.67 0.61,1.19 1.28,1.28zM14,3.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zm-4,0c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zm11,7c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM10,7c0 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_on_24dp.xml
deleted file mode 100644
index 000f86e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_blur_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zm0,4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zm0,-8c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zm-3,0.5c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5 0.5,-0.22 0.5,-0.5 -0.22,-0.5 -0.5,-0.5zM6,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1zm15,5.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5 -0.5,0.22 -0.5,0.5 0.22,0.5 0.5,0.5zM14,7c0.55,0 1,-0.45 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_1_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_1_24dp.xml
deleted file mode 100644
index cb98cb8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_1_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_2_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_2_24dp.xml
deleted file mode 100644
index 194732f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_2_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,2c-1.82,0 -3.53,0.5 -5,1.35C7.99,5.08 10,8.3 10,12s-2.01,6.92 -5,8.65C6.47,21.5 8.18,22 10,22c5.52,0 10,-4.48 10,-10S15.52,2 10,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_3_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_3_24dp.xml
deleted file mode 100644
index c2f2587..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_3_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,2c-1.05,0 -2.05,0.16 -3,0.46 4.06,1.27 7,5.06 7,9.54 0,4.48 -2.94,8.27 -7,9.54 0.95,0.3 1.95,0.46 3,0.46 5.52,0 10,-4.48 10,-10S14.52,2 9,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_4_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_4_24dp.xml
deleted file mode 100644
index 92016a1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_4_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-0.89,0 -1.74,-0.2 -2.5,-0.55C11.56,16.5 13,14.42 13,12s-1.44,-4.5 -3.5,-5.45C10.26,6.2 11.11,6 12,6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_5_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_5_24dp.xml
deleted file mode 100644
index 406a52e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_5_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_6_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_6_24dp.xml
deleted file mode 100644
index cc63bac..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_6_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18V6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_7_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_7_24dp.xml
deleted file mode 100644
index c254041..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brightness_7_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zm0,-10c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brush_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brush_24dp.xml
deleted file mode 100644
index 3e58fe1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_brush_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,14c-1.66,0 -3,1.34 -3,3 0,1.31 -1.16,2 -2,2 0.92,1.22 2.49,2 4,2 2.21,0 4,-1.79 4,-4 0,-1.66 -1.34,-3 -3,-3zm13.71,-9.37l-1.34,-1.34c-0.39,-0.39 -1.02,-0.39 -1.41,0L9,12.25 11.75,15l8.96,-8.96c0.39,-0.39 0.39,-1.02 0,-1.41z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_24dp.xml
deleted file mode 100644
index 880b204..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zm0.26,1h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zm-6.08,3c0.92,2.92 3.15,5.26 6,6.34L12.12,15H2.46zm11.27,0l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_alt_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_alt_24dp.xml
deleted file mode 100644
index 8404ea5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_alt_24dp.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_front_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_front_24dp.xml
deleted file mode 100644
index 3c55d50..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_front_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,20H5v2h5v2l3,-3 -3,-3v2zm4,0v2h5v-2h-5zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -1.99,0.9 -1.99,2S10.9,8 12,8zm5,-8H7C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V2c0,-1.1 -0.9,-2 -2,-2zM7,2h10v10.5c0,-1.67 -3.33,-2.5 -5,-2.5s-5,0.83 -5,2.5V2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_rear_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_rear_24dp.xml
deleted file mode 100644
index 397bbbd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_rear_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,20H5v2h5v2l3,-3 -3,-3v2zm4,0v2h5v-2h-5zm3,-20H7C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V2c0,-1.1 -0.9,-2 -2,-2zm-5,6c-1.11,0 -2,-0.9 -2,-2s0.89,-2 1.99,-2 2,0.9 2,2C14,5.1 13.1,6 12,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_roll_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_roll_24dp.xml
deleted file mode 100644
index de29724..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_camera_roll_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,5c0,-1.1 -0.9,-2 -2,-2h-1V2c0,-0.55 -0.45,-1 -1,-1H6c-0.55,0 -1,0.45 -1,1v1H4c-1.1,0 -2,0.9 -2,2v15c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2h8V5h-8zm-2,13h-2v-2h2v2zm0,-9h-2V7h2v2zm4,9h-2v-2h2v2zm0,-9h-2V7h2v2zm4,9h-2v-2h2v2zm0,-9h-2V7h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_strong_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_strong_24dp.xml
deleted file mode 100644
index 81c29e2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_strong_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm-7,7H3v4c0,1.1 0.9,2 2,2h4v-2H5v-4zM5,5h4V3H5c-1.1,0 -2,0.9 -2,2v4h2V5zm14,-2h-4v2h4v4h2V5c0,-1.1 -0.9,-2 -2,-2zm0,16h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_weak_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_weak_24dp.xml
deleted file mode 100644
index 1a57582..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_center_focus_weak_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,15H3v4c0,1.1 0.9,2 2,2h4v-2H5v-4zM5,5h4V3H5c-1.1,0 -2,0.9 -2,2v4h2V5zm14,-2h-4v2h4v4h2V5c0,-1.1 -0.9,-2 -2,-2zm0,16h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm0,6c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_24dp.xml
deleted file mode 100644
index bf9a0b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_collections_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_color_lens_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_color_lens_24dp.xml
deleted file mode 100644
index 42e5dc3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_color_lens_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5H16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zm-5.5,9c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zm3,-4C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zm5,0c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zm3,4c-0.83,0 -1.5,-0.67 -1.5,-1 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_colorize_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_colorize_24dp.xml
deleted file mode 100644
index 1e95d1d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_colorize_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.71,5.63l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-3.12,3.12 -1.93,-1.91 -1.41,1.41 1.42,1.42L3,16.25V21h4.75l8.92,-8.92 1.42,1.42 1.41,-1.41 -1.92,-1.92 3.12,-3.12c0.4,-0.4 0.4,-1.03 0.01,-1.42zM6.92,19L5,17.08l8.06,-8.06 1.92,1.92L6.92,19z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_compare_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_compare_24dp.xml
deleted file mode 100644
index cdd3c90..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_compare_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h5v2h2V1h-2v2zm0,15H5l5,-6v6zm9,-15h-5v2h5v13l-5,-6v9h5c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_24dp.xml
deleted file mode 100644
index b378ea8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1,-5C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_duplicate_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_duplicate_24dp.xml
deleted file mode 100644
index 167b1f9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_control_point_duplicate_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,8h-2v3h-3v2h3v3h2v-3h3v-2h-3zM2,12c0,-2.79 1.64,-5.2 4.01,-6.32V3.52C2.52,4.76 0,8.09 0,12s2.52,7.24 6.01,8.48v-2.16C3.64,17.2 2,14.79 2,12zm13,-9c-4.96,0 -9,4.04 -9,9s4.04,9 9,9 9,-4.04 9,-9 -4.04,-9 -9,-9zm0,16c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_16_9_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_16_9_24dp.xml
deleted file mode 100644
index 3965d0a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_16_9_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,6H5c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zm0,10H5V8h14v8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_24dp.xml
deleted file mode 100644
index 9c3175a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,15h2V7c0,-1.1 -0.9,-2 -2,-2H9v2h8v8zM7,17V1H5v4H1v2h4v10c0,1.1 0.9,2 2,2h10v4h2v-4h4v-2H7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_3_2_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_3_2_24dp.xml
deleted file mode 100644
index b78df03..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_3_2_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,4H5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,14H5V6h14v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_5_4_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_5_4_24dp.xml
deleted file mode 100644
index 78bd307..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_5_4_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,5H5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2zm0,12H5V7h14v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_7_5_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_7_5_24dp.xml
deleted file mode 100644
index dd5b920..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_7_5_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,7H5c-1.1,0 -2,0.9 -2,2v6c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V9c0,-1.1 -0.9,-2 -2,-2zm0,8H5V9h14v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_din_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_din_24dp.xml
deleted file mode 100644
index 987f14c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_din_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V5h14v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_free_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_free_24dp.xml
deleted file mode 100644
index 6876754..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_free_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5v4h2V5h4V3H5c-1.1,0 -2,0.9 -2,2zm2,10H3v4c0,1.1 0.9,2 2,2h4v-2H5v-4zm14,4h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zm0,-16h-4v2h4v4h2V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_landscape_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_landscape_24dp.xml
deleted file mode 100644
index 78bd307..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_landscape_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,5H5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2zm0,12H5V7h14v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_original_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_original_24dp.xml
deleted file mode 100644
index d789be6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_original_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V5h14v14zm-5.04,-6.71l-2.75,3.54 -1.96,-2.36L6.5,17h11l-3.54,-4.71z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_portrait_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_portrait_24dp.xml
deleted file mode 100644
index 1e0e6bd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_portrait_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H7V5h10v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_square_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_square_24dp.xml
deleted file mode 100644
index 2cef016..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_crop_square_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,4H6c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,14H6V6h12v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_dehaze_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_dehaze_24dp.xml
deleted file mode 100644
index 88956e4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_dehaze_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,15.5v2h20v-2H2zm0,-5v2h20v-2H2zm0,-5v2h20v-2H2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_details_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_details_24dp.xml
deleted file mode 100644
index 931e15e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_details_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,4l9,16 9,-16H3zm3.38,2h11.25L12,16 6.38,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_edit_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_edit_24dp.xml
deleted file mode 100644
index 0ff336a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_edit_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_24dp.xml
deleted file mode 100644
index b40b8bd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,17v2h2v-2h2v-2h-2v-2h-2v2h-2v2h2zm5,-15H4c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM5,5h6v2H5V5zm15,15H4L20,4v16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_minus_1_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_minus_1_24dp.xml
deleted file mode 100644
index bb35968..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_minus_1_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,11v2h8v-2H4zm15,7h-2V7.38L14,8.4V6.7L18.7,5h0.3v13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_minus_2_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_minus_2_24dp.xml
deleted file mode 100644
index b434d4b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_minus_2_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.05,16.29l2.86,-3.07c0.38,-0.39 0.72,-0.79 1.04,-1.18 0.32,-0.39 0.59,-0.78 0.82,-1.17 0.23,-0.39 0.41,-0.78 0.54,-1.17s0.19,-0.79 0.19,-1.18c0,-0.53 -0.09,-1.02 -0.27,-1.46 -0.18,-0.44 -0.44,-0.81 -0.78,-1.11 -0.34,-0.31 -0.77,-0.54 -1.26,-0.71 -0.51,-0.16 -1.08,-0.24 -1.72,-0.24 -0.69,0 -1.31,0.11 -1.85,0.32 -0.54,0.21 -1,0.51 -1.36,0.88 -0.37,0.37 -0.65,0.8 -0.84,1.3 -0.18,0.47 -0.27,0.97 -0.28,1.5h2.14c0.01,-0.31 0.05,-0.6 0.13,-0.87 0.09,-0.29 0.23,-0.54 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_1_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_1_24dp.xml
deleted file mode 100644
index 3ba74f7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_1_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,7H8v4H4v2h4v4h2v-4h4v-2h-4V7zm10,11h-2V7.38L15,8.4V6.7L19.7,5h0.3v13z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_2_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_2_24dp.xml
deleted file mode 100644
index 97d7424..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_plus_2_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.05,16.29l2.86,-3.07c0.38,-0.39 0.72,-0.79 1.04,-1.18 0.32,-0.39 0.59,-0.78 0.82,-1.17 0.23,-0.39 0.41,-0.78 0.54,-1.17 0.13,-0.39 0.19,-0.79 0.19,-1.18 0,-0.53 -0.09,-1.02 -0.27,-1.46 -0.18,-0.44 -0.44,-0.81 -0.78,-1.11 -0.34,-0.31 -0.77,-0.54 -1.26,-0.71 -0.51,-0.16 -1.08,-0.24 -1.72,-0.24 -0.69,0 -1.31,0.11 -1.85,0.32 -0.54,0.21 -1,0.51 -1.36,0.88 -0.37,0.37 -0.65,0.8 -0.84,1.3 -0.18,0.47 -0.27,0.97 -0.28,1.5h2.14c0.01,-0.31 0.05,-0.6 0.13,-0.87 0.09,-0.29 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_zero_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_zero_24dp.xml
deleted file mode 100644
index 8ae50ac..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_exposure_zero_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.14,12.5c0,1 -0.1,1.85 -0.3,2.55 -0.2,0.7 -0.48,1.27 -0.83,1.7 -0.36,0.44 -0.79,0.75 -1.3,0.95 -0.51,0.2 -1.07,0.3 -1.7,0.3 -0.62,0 -1.18,-0.1 -1.69,-0.3 -0.51,-0.2 -0.95,-0.51 -1.31,-0.95 -0.36,-0.44 -0.65,-1.01 -0.85,-1.7 -0.2,-0.7 -0.3,-1.55 -0.3,-2.55v-2.04c0,-1 0.1,-1.85 0.3,-2.55 0.2,-0.7 0.48,-1.26 0.84,-1.69 0.36,-0.43 0.8,-0.74 1.31,-0.93C10.81,5.1 11.38,5 12,5c0.63,0 1.19,0.1 1.7,0.29 0.51,0.19 0.95,0.5 1.31,0.93 0.36,0.43 0.64,0.99 0.84,1.69 0.2,0. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_1_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_1_24dp.xml
deleted file mode 100644
index e108b9e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_1_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm11,10h2V5h-4v2h2v8zm7,-14H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_24dp.xml
deleted file mode 100644
index 667505c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.96,10.29l-2.75,3.54 -1.96,-2.36L8.5,15h11l-3.54,-4.71zM3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm18,-4H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_2_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_2_24dp.xml
deleted file mode 100644
index 957c4b6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_2_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm18,-4H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14zm-4,-4h-4v-2h2c1.1,0 2,-0.89 2,-2V7c0,-1.11 -0.9,-2 -2,-2h-4v2h4v2h-2c-1.1,0 -2,0.89 -2,2v4h6v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_3_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_3_24dp.xml
deleted file mode 100644
index aaf5f01..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_3_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,1H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14zM3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm14,8v-1.5c0,-0.83 -0.67,-1.5 -1.5,-1.5 0.83,0 1.5,-0.67 1.5,-1.5V7c0,-1.11 -0.9,-2 -2,-2h-4v2h4v2h-2v2h2v2h-4v2h4c1.1,0 2,-0.89 2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_4_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_4_24dp.xml
deleted file mode 100644
index 86e0431..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_4_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm12,10h2V5h-2v4h-2V5h-2v6h4v4zm6,-14H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_5_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_5_24dp.xml
deleted file mode 100644
index 5e6a677..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_5_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,1H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14zM3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm14,8v-2c0,-1.11 -0.9,-2 -2,-2h-2V7h4V5h-6v6h4v2h-4v2h4c1.1,0 2,-0.89 2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_6_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_6_24dp.xml
deleted file mode 100644
index c347253..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_6_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm18,-4H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14zm-8,-2h2c1.1,0 2,-0.89 2,-2v-2c0,-1.11 -0.9,-2 -2,-2h-2V7h4V5h-4c-1.1,0 -2,0.89 -2,2v6c0,1.11 0.9,2 2,2zm0,-4h2v2h-2v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_7_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_7_24dp.xml
deleted file mode 100644
index 0045c2c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_7_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm18,-4H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14zm-8,-2l4,-8V5h-6v2h4l-4,8h2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_8_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_8_24dp.xml
deleted file mode 100644
index 8916519..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_8_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm18,-4H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14zm-8,-2h2c1.1,0 2,-0.89 2,-2v-1.5c0,-0.83 -0.67,-1.5 -1.5,-1.5 0.83,0 1.5,-0.67 1.5,-1.5V7c0,-1.11 -0.9,-2 -2,-2h-2c-1.1,0 -2,0.89 -2,2v1.5c0,0.83 0.67,1.5 1.5,1.5 -0.83,0 -1.5,0.67 -1.5,1.5V13c0,1.11 0.9,2 2,2zm0,-8h2v2h-2V7zm0,4h2v2h-2v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_24dp.xml
deleted file mode 100644
index 670eb1b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm18,-4H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14zM15,5h-2c-1.1,0 -2,0.89 -2,2v2c0,1.11 0.9,2 2,2h2v2h-4v2h4c1.1,0 2,-0.89 2,-2V7c0,-1.11 -0.9,-2 -2,-2zm0,4h-2V7h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_plus_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_plus_24dp.xml
deleted file mode 100644
index 8dc1f04..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_9_plus_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm11,7V8c0,-1.11 -0.9,-2 -2,-2h-1c-1.1,0 -2,0.89 -2,2v1c0,1.11 0.9,2 2,2h1v1H9v2h3c1.1,0 2,-0.89 2,-2zm-3,-3V8h1v1h-1zm10,-8H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,8h-2V7h-2v2h-2v2h2v2h2v-2h2v6H7V3h14v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_b_and_w_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_b_and_w_24dp.xml
deleted file mode 100644
index 0fdc812..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_b_and_w_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16l-7,-8v8H5l7,-8V5h7v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_center_focus_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_center_focus_24dp.xml
deleted file mode 100644
index 8a185cd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_center_focus_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,15H3v4c0,1.1 0.9,2 2,2h4v-2H5v-4zM5,5h4V3H5c-1.1,0 -2,0.9 -2,2v4h2V5zm14,-2h-4v2h4v4h2V5c0,-1.1 -0.9,-2 -2,-2zm0,16h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_drama_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_drama_24dp.xml
deleted file mode 100644
index 695c11b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_drama_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.61,5.64 5.36,8.04 2.35,8.36 0,10.9 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4h2c0,-2.76 -1.86,-5.08 -4.4,-5.78C8.61,6.88 10.2,6 12,6c3.03,0 5.5,2.47 5.5,5.5v0.5H19c1.65,0 3,1.35 3,3s-1.35,3 -3,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_frames_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_frames_24dp.xml
deleted file mode 100644
index ffcab6a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_frames_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4h-4l-4,-4 -4,4H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,16H4V6h4.52l3.52,-3.5L15.52,6H20v14zM18,8H6v10h12"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_hdr_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_hdr_24dp.xml
deleted file mode 100644
index c3ad94b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_hdr_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_none_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_none_24dp.xml
deleted file mode 100644
index b1d1542..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_none_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5H1v16c0,1.1 0.9,2 2,2h16v-2H3V5zm18,-4H7c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2zm0,16H7V3h14v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_tilt_shift_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_tilt_shift_24dp.xml
deleted file mode 100644
index 7b4116a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_tilt_shift_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,4.07V2.05c-2.01,0.2 -3.84,1 -5.32,2.21L7.1,5.69c1.11,-0.86 2.44,-1.44 3.9,-1.62zm7.32,0.19C16.84,3.05 15.01,2.25 13,2.05v2.02c1.46,0.18 2.79,0.76 3.9,1.62l1.42,-1.43zM19.93,11h2.02c-0.2,-2.01 -1,-3.84 -2.21,-5.32L18.31,7.1c0.86,1.11 1.44,2.44 1.62,3.9zM5.69,7.1L4.26,5.68C3.05,7.16 2.25,8.99 2.05,11h2.02c0.18,-1.46 0.76,-2.79 1.62,-3.9zM4.07,13H2.05c0.2,2.01 1,3.84 2.21,5.32l1.43,-1.43c-0.86,-1.1 -1.44,-2.43 -1.62,-3.89zM15,12c0,-1.66 -1.34,-3 -3,-3s-3,1.34 - [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_vintage_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_vintage_24dp.xml
deleted file mode 100644
index 1fdbd6a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_filter_vintage_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.7,12.4c-0.28,-0.16 -0.57,-0.29 -0.86,-0.4 0.29,-0.11 0.58,-0.24 0.86,-0.4 1.92,-1.11 2.99,-3.12 3,-5.19 -1.79,-1.03 -4.07,-1.11 -6,0 -0.28,0.16 -0.54,0.35 -0.78,0.54 0.05,-0.31 0.08,-0.63 0.08,-0.95 0,-2.22 -1.21,-4.15 -3,-5.19C10.21,1.85 9,3.78 9,6c0,0.32 0.03,0.64 0.08,0.95 -0.24,-0.2 -0.5,-0.39 -0.78,-0.55 -1.92,-1.11 -4.2,-1.03 -6,0 0,2.07 1.07,4.08 3,5.19 0.28,0.16 0.57,0.29 0.86,0.4 -0.29,0.11 -0.58,0.24 -0.86,0.4 -1.92,1.11 -2.99,3.12 -3,5.19 1.79,1.0 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flare_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flare_24dp.xml
deleted file mode 100644
index 9827247..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flare_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,11H1v2h6v-2zm2.17,-3.24L7.05,5.64 5.64,7.05l2.12,2.12 1.41,-1.41zM13,1h-2v6h2V1zm5.36,6.05l-1.41,-1.41 -2.12,2.12 1.41,1.41 2.12,-2.12zM17,11v2h6v-2h-6zm-5,-2c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zm2.83,7.24l2.12,2.12 1.41,-1.41 -2.12,-2.12 -1.41,1.41zm-9.19,0.71l1.41,1.41 2.12,-2.12 -1.41,-1.41 -2.12,2.12zM11,23h2v-6h-2v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_auto_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_auto_24dp.xml
deleted file mode 100644
index e422d4c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_auto_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,2v12h3v9l7,-12H9l4,-9H3zm16,0h-2l-3.2,9h1.9l0.7,-2h3.2l0.7,2h1.9L19,2zm-2.15,5.65L18,4l1.15,3.65h-2.3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_off_24dp.xml
deleted file mode 100644
index 04d719e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.27,3L2,4.27l5,5V13h3v9l3.58,-6.14L17.73,20 19,18.73 3.27,3zM17,10h-4l4,-8H7v2.18l8.46,8.46L17,10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_on_24dp.xml
deleted file mode 100644
index 8bf924f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flash_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flip_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flip_24dp.xml
deleted file mode 100644
index ef0bbde..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_flip_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,21h2v-2h-2v2zm4,-12h2V7h-2v2zM3,5v14c0,1.1 0.9,2 2,2h4v-2H5V5h4V3H5c-1.1,0 -2,0.9 -2,2zm16,-2v2h2c0,-1.1 -0.9,-2 -2,-2zm-8,20h2V1h-2v22zm8,-6h2v-2h-2v2zM15,5h2V3h-2v2zm4,8h2v-2h-2v2zm0,8c1.1,0 2,-0.9 2,-2h-2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_gradient_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_gradient_24dp.xml
deleted file mode 100644
index 2d9fb13..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_gradient_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,9h2v2h-2zm-2,2h2v2H9zm4,0h2v2h-2zm2,-2h2v2h-2zM7,9h2v2H7zm12,-6H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM9,18H7v-2h2v2zm4,0h-2v-2h2v2zm4,0h-2v-2h2v2zm2,-7h-2v2h2v2h-2v-2h-2v2h-2v-2h-2v2H9v-2H7v2H5v-2h2v-2H5V5h14v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grain_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grain_24dp.xml
deleted file mode 100644
index 185e80d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grain_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,12c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,8c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm0,8c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm12,-8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zm-4,8c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm4,-4c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm-4,-4c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm-4,-4c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,- [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_off_24dp.xml
deleted file mode 100644
index 1ab4ca5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,4v1.45l2,2V4h4v4h-3.45l2,2H14v1.45l2,2V10h4v4h-3.45l2,2H20v1.45l2,2V4c0,-1.1 -0.9,-2 -2,-2H4.55l2,2H8zm8,0h4v4h-4V4zM1.27,1.27L0,2.55l2,2V20c0,1.1 0.9,2 2,2h15.46l2,2 1.27,-1.27L1.27,1.27zM10,12.55L11.45,14H10v-1.45zm-6,-6L5.45,8H4V6.55zM8,20H4v-4h4v4zm0,-6H4v-4h3.45l0.55,0.55V14zm6,6h-4v-4h3.45l0.55,0.54V20zm2,0v-1.46L17.46,20H16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_on_24dp.xml
deleted file mode 100644
index 6cc5c86..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_grid_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM8,20H4v-4h4v4zm0,-6H4v-4h4v4zm0,-6H4V4h4v4zm6,12h-4v-4h4v4zm0,-6h-4v-4h4v4zm0,-6h-4V4h4v4zm6,12h-4v-4h4v4zm0,-6h-4v-4h4v4zm0,-6h-4V4h4v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_off_24dp.xml
deleted file mode 100644
index dc47ada..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,17L3.27,2.27 2,3.55l4,4V11H4V7H2v10h2v-4h2v4h2V9.55l1,1V17h4c0.67,0 1.26,-0.33 1.62,-0.84l6.34,6.34 1.27,-1.27L18,17zm-5,-2h-2v-2.45l2,2V15zm5,-2h1l0.82,3.27 0.73,0.73H22l-1.19,-4.17c0.7,-0.31 1.19,-1.01 1.19,-1.83V9c0,-1.1 -0.9,-2 -2,-2h-4v5.45l2,2V13zm0,-4h2v2h-2V9zm-3,2.45V9c0,-1.1 -0.9,-2 -2,-2h-2.45L15,11.45z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_on_24dp.xml
deleted file mode 100644
index 891f5bd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,11H4V7H2v10h2v-4h2v4h2V7H6v4zm7,-4H9v10h4c1.1,0 2,-0.9 2,-2V9c0,-1.1 -0.9,-2 -2,-2zm0,8h-2V9h2v6zm9,-4V9c0,-1.1 -0.9,-2 -2,-2h-4v10h2v-4h1l1,4h2l-1.19,-4.17c0.7,-0.31 1.19,-1.01 1.19,-1.83zm-2,0h-2V9h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_strong_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_strong_24dp.xml
deleted file mode 100644
index c37d1dc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_strong_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zM5,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm0,6c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_weak_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_weak_24dp.xml
deleted file mode 100644
index bd64aee..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_hdr_weak_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm12,-2c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zm0,10c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_healing_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_healing_24dp.xml
deleted file mode 100644
index a3e5418..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_healing_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.73,12.02l3.98,-3.98c0.39,-0.39 0.39,-1.02 0,-1.41l-4.34,-4.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-3.98,3.98L8,2.29C7.8,2.1 7.55,2 7.29,2c-0.25,0 -0.51,0.1 -0.7,0.29L2.25,6.63c-0.39,0.39 -0.39,1.02 0,1.41l3.98,3.98L2.25,16c-0.39,0.39 -0.39,1.02 0,1.41l4.34,4.34c0.39,0.39 1.02,0.39 1.41,0l3.98,-3.98 3.98,3.98c0.2,0.2 0.45,0.29 0.71,0.29 0.26,0 0.51,-0.1 0.71,-0.29l4.34,-4.34c0.39,-0.39 0.39,-1.02 0,-1.41l-3.99,-3.98zM12,9c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 - [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_image_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_image_24dp.xml
deleted file mode 100644
index 61dcb63..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_image_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_image_aspect_ratio_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_image_aspect_ratio_24dp.xml
deleted file mode 100644
index a59eb87..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_image_aspect_ratio_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,10h-2v2h2v-2zm0,4h-2v2h2v-2zm-8,-4H6v2h2v-2zm4,0h-2v2h2v-2zm8,-6H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,14H4V6h16v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_iso_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_iso_24dp.xml
deleted file mode 100644
index 33fffdc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_iso_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM5.5,7.5h2v-2H9v2h2V9H9v2H7.5V9h-2V7.5zM19,19H5L19,5v14zm-2,-2v-1.5h-5V17h5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_landscape_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_landscape_24dp.xml
deleted file mode 100644
index c3ad94b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_landscape_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_add_24dp.xml
deleted file mode 100644
index f6ada4e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,3H3v3c1.66,0 3,-1.34 3,-3zm8,0h-2c0,4.97 -4.03,9 -9,9v2c6.08,0 11,-4.93 11,-11zm-4,0H8c0,2.76 -2.24,5 -5,5v2c3.87,0 7,-3.13 7,-7zm0,18h2c0,-4.97 4.03,-9 9,-9v-2c-6.07,0 -11,4.93 -11,11zm8,0h3v-3c-1.66,0 -3,1.34 -3,3zm-4,0h2c0,-2.76 2.24,-5 5,-5v-2c-3.87,0 -7,3.13 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_remove_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_remove_24dp.xml
deleted file mode 100644
index 1612b52..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_leak_remove_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,3H8c0,0.37 -0.04,0.72 -0.12,1.06l1.59,1.59C9.81,4.84 10,3.94 10,3zM3,4.27l2.84,2.84C5.03,7.67 4.06,8 3,8v2c1.61,0 3.09,-0.55 4.27,-1.46L8.7,9.97C7.14,11.24 5.16,12 3,12v2c2.71,0 5.19,-0.99 7.11,-2.62l2.5,2.5C10.99,15.81 10,18.29 10,21h2c0,-2.16 0.76,-4.14 2.03,-5.69l1.43,1.43C14.55,17.91 14,19.39 14,21h2c0,-1.06 0.33,-2.03 0.89,-2.84L19.73,21 21,19.73 4.27,3 3,4.27zM14,3h-2c0,1.5 -0.37,2.91 -1.02,4.16l1.46,1.46C13.42,6.98 14,5.06 14,3zm5.94,13.12c0.34,-0.08 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_lens_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_lens_24dp.xml
deleted file mode 100644
index 5093856..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_lens_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_24dp.xml
deleted file mode 100644
index e242ded..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,10c-3.86,0 -7,3.14 -7,7h2c0,-2.76 2.24,-5 5,-5s5,2.24 5,5h2c0,-3.86 -3.14,-7 -7,-7zm0,-4C5.93,6 1,10.93 1,17h2c0,-4.96 4.04,-9 9,-9s9,4.04 9,9h2c0,-6.07 -4.93,-11 -11,-11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_3_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_3_24dp.xml
deleted file mode 100644
index d7c35c9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_3_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.01,3h-14c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-4,7.5c0,0.83 -0.67,1.5 -1.5,1.5 0.83,0 1.5,0.67 1.5,1.5V15c0,1.11 -0.9,2 -2,2h-4v-2h4v-2h-2v-2h2V9h-4V7h4c1.1,0 2,0.89 2,2v1.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_4_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_4_24dp.xml
deleted file mode 100644
index d35d6b2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_4_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-4,14h-2v-4H9V7h2v4h2V7h2v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_5_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_5_24dp.xml
deleted file mode 100644
index a19ee0b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_5_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-4,6h-4v2h2c1.1,0 2,0.89 2,2v2c0,1.11 -0.9,2 -2,2H9v-2h4v-2H9V7h6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_6_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_6_24dp.xml
deleted file mode 100644
index 94eeb9c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_6_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,15h2v-2h-2v2zm8,-12H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-4,6h-4v2h2c1.1,0 2,0.89 2,2v2c0,1.11 -0.9,2 -2,2h-2c-1.1,0 -2,-0.89 -2,-2V9c0,-1.11 0.9,-2 2,-2h4v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_one_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_one_24dp.xml
deleted file mode 100644
index 1b731ab..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_one_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-5,14h-2V9h-2V7h4v10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_two_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_two_24dp.xml
deleted file mode 100644
index 96d3dfd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_looks_two_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-4,8c0,1.11 -0.9,2 -2,2h-2v2h4v2H9v-4c0,-1.11 0.9,-2 2,-2h2V9H9V7h4c1.1,0 2,0.89 2,2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_loupe_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_loupe_24dp.xml
deleted file mode 100644
index 7677b3b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_loupe_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1,-5C6.49,2 2,6.49 2,12s4.49,10 10,10h8c1.1,0 2,-0.9 2,-2v-8c0,-5.51 -4.49,-10 -10,-10zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_creation_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_creation_24dp.xml
deleted file mode 100644
index 6d4c52e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_movie_creation_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,4l2,4h-3l-2,-4h-2l2,4h-3l-2,-4H8l2,4H7L5,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4h-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_24dp.xml
deleted file mode 100644
index 6239dbe..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,16.12c3.47,-0.41 6.17,-3.36 6.17,-6.95 0,-3.87 -3.13,-7 -7,-7s-7,3.13 -7,7c0,3.47 2.52,6.34 5.83,6.89V20H5v2h14v-2h-6v-3.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_people_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_people_24dp.xml
deleted file mode 100644
index caeb451..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_nature_people_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22.17,9.17c0,-3.87 -3.13,-7 -7,-7s-7,3.13 -7,7c0,3.47 2.52,6.34 5.83,6.89V20H6v-3h1v-4c0,-0.55 -0.45,-1 -1,-1H3c-0.55,0 -1,0.45 -1,1v4h1v5h16v-2h-3v-3.88c3.47,-0.41 6.17,-3.36 6.17,-6.95zM4.5,11c0.83,0 1.5,-0.67 1.5,-1.5S5.33,8 4.5,8 3,8.67 3,9.5 3.67,11 4.5,11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_before_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_before_24dp.xml
deleted file mode 100644
index ed84de6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_before_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_next_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_next_24dp.xml
deleted file mode 100644
index 2ff2653..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_navigate_next_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_palette_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_palette_24dp.xml
deleted file mode 100644
index 42e5dc3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_palette_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5H16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zm-5.5,9c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zm3,-4C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zm5,0c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zm3,4c-0.83,0 -1.5,-0.67 -1.5,-1 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_24dp.xml
deleted file mode 100644
index 297abbd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M23,18V6c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2zM8.5,12.5l2.5,3.01L14.5,11l4.5,6H5l3.5,-4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_fisheye_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_fisheye_24dp.xml
deleted file mode 100644
index 671946f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_fisheye_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_horizontal_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_horizontal_24dp.xml
deleted file mode 100644
index 7fe1598..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_horizontal_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6.54v10.91c-2.6,-0.77 -5.28,-1.16 -8,-1.16 -2.72,0 -5.4,0.39 -8,1.16V6.54c2.6,0.77 5.28,1.16 8,1.16 2.72,0.01 5.4,-0.38 8,-1.16M21.43,4c-0.1,0 -0.2,0.02 -0.31,0.06C18.18,5.16 15.09,5.7 12,5.7c-3.09,0 -6.18,-0.55 -9.12,-1.64 -0.11,-0.04 -0.22,-0.06 -0.31,-0.06 -0.34,0 -0.57,0.23 -0.57,0.63v14.75c0,0.39 0.23,0.62 0.57,0.62 0.1,0 0.2,-0.02 0.31,-0.06 2.94,-1.1 6.03,-1.64 9.12,-1.64 3.09,0 6.18,0.55 9.12,1.64 0.11,0.04 0.21,0.06 0.31,0.06 0.33,0 0.57,-0.23 0.57, [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_vertical_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_vertical_24dp.xml
deleted file mode 100644
index f5282ec..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_vertical_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.94,21.12c-1.1,-2.94 -1.64,-6.03 -1.64,-9.12 0,-3.09 0.55,-6.18 1.64,-9.12 0.04,-0.11 0.06,-0.22 0.06,-0.31 0,-0.34 -0.23,-0.57 -0.63,-0.57H4.63c-0.4,0 -0.63,0.23 -0.63,0.57 0,0.1 0.02,0.2 0.06,0.31C5.16,5.82 5.71,8.91 5.71,12c0,3.09 -0.55,6.18 -1.64,9.12 -0.05,0.11 -0.07,0.22 -0.07,0.31 0,0.33 0.23,0.57 0.63,0.57h14.75c0.39,0 0.63,-0.24 0.63,-0.57 -0.01,-0.1 -0.03,-0.2 -0.07,-0.31zM6.54,20c0.77,-2.6 1.16,-5.28 1.16,-8 0,-2.72 -0.39,-5.4 -1.16,-8h10.91c-0.77, [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_wide_angle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_wide_angle_24dp.xml
deleted file mode 100644
index eebf829..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_panorama_wide_angle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,6c2.45,0 4.71,0.2 7.29,0.64 0.47,1.78 0.71,3.58 0.71,5.36 0,1.78 -0.24,3.58 -0.71,5.36 -2.58,0.44 -4.84,0.64 -7.29,0.64s-4.71,-0.2 -7.29,-0.64C4.24,15.58 4,13.78 4,12c0,-1.78 0.24,-3.58 0.71,-5.36C7.29,6.2 9.55,6 12,6m0,-2c-2.73,0 -5.22,0.24 -7.95,0.72l-0.93,0.16 -0.25,0.9C2.29,7.85 2,9.93 2,12s0.29,4.15 0.87,6.22l0.25,0.89 0.93,0.16c2.73,0.49 5.22,0.73 7.95,0.73s5.22,-0.24 7.95,-0.72l0.93,-0.16 0.25,-0.89c0.58,-2.08 0.87,-4.16 0.87,-6.23s-0.29,-4.15 -0.87,- [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_24dp.xml
deleted file mode 100644
index 61dcb63..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_album_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_album_24dp.xml
deleted file mode 100644
index f8ff7dc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_album_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,2H6c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4zm0,15l3,-3.86 2.14,2.58 3,-3.86L18,19H6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_camera_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_camera_24dp.xml
deleted file mode 100644
index 8404ea5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_camera_24dp.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_library_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_library_24dp.xml
deleted file mode 100644
index bf9a0b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_photo_library_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_portrait_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_portrait_24dp.xml
deleted file mode 100644
index dd73aee..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_portrait_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,12.25c1.24,0 2.25,-1.01 2.25,-2.25S13.24,7.75 12,7.75 9.75,8.76 9.75,10s1.01,2.25 2.25,2.25zm4.5,4c0,-1.5 -3,-2.25 -4.5,-2.25s-4.5,0.75 -4.5,2.25V17h9v-0.75zM19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V5h14v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_remove_red_eye_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_remove_red_eye_24dp.xml
deleted file mode 100644
index 46a571c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_remove_red_eye_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zm0,-8c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_left_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_left_24dp.xml
deleted file mode 100644
index 3f0a0eb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_left_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.11,8.53L5.7,7.11C4.8,8.27 4.24,9.61 4.07,11h2.02c0.14,-0.87 0.49,-1.72 1.02,-2.47zM6.09,13H4.07c0.17,1.39 0.72,2.73 1.62,3.89l1.41,-1.42c-0.52,-0.75 -0.87,-1.59 -1.01,-2.47zm1.01,5.32c1.16,0.9 2.51,1.44 3.9,1.61V17.9c-0.87,-0.15 -1.71,-0.49 -2.46,-1.03L7.1,18.32zM13,4.07V1L8.45,5.55 13,10V6.09c2.84,0.48 5,2.94 5,5.91s-2.16,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93s-3.05,-7.44 -7,-7.93z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_right_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_right_24dp.xml
deleted file mode 100644
index d45fea1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_rotate_right_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.55,5.55L11,1v3.07C7.06,4.56 4,7.92 4,12s3.05,7.44 7,7.93v-2.02c-2.84,-0.48 -5,-2.94 -5,-5.91s2.16,-5.43 5,-5.91V10l4.55,-4.45zM19.93,11c-0.17,-1.39 -0.72,-2.73 -1.62,-3.89l-1.42,1.42c0.54,0.75 0.88,1.6 1.02,2.47h2.02zM13,17.9v2.02c1.39,-0.17 2.74,-0.71 3.9,-1.61l-1.44,-1.44c-0.75,0.54 -1.59,0.89 -2.46,1.03zm3.89,-2.42l1.42,1.41c0.9,-1.16 1.45,-2.5 1.62,-3.89h-2.02c-0.14,0.87 -0.48,1.72 -1.02,2.48z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_slideshow_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_slideshow_24dp.xml
deleted file mode 100644
index 9b26770..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_slideshow_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,8v8l5,-4 -5,-4zm9,-5H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V5h14v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_straighten_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_straighten_24dp.xml
deleted file mode 100644
index f10b101..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_straighten_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,6H3c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zm0,10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_style_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_style_24dp.xml
deleted file mode 100644
index 14d351e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_style_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2.53,19.65l1.34,0.56v-9.03l-2.43,5.86c-0.41,1.02 0.08,2.19 1.09,2.61zm19.5,-3.7L17.07,3.98c-0.31,-0.75 -1.04,-1.21 -1.81,-1.23 -0.26,0 -0.53,0.04 -0.79,0.15L7.1,5.95c-0.75,0.31 -1.21,1.03 -1.23,1.8 -0.01,0.27 0.04,0.54 0.15,0.8l4.96,11.97c0.31,0.76 1.05,1.22 1.83,1.23 0.26,0 0.52,-0.05 0.77,-0.15l7.36,-3.05c1.02,-0.42 1.51,-1.59 1.09,-2.6zM7.88,8.75c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zm-2,11c0,1.1 0.9,2 2,2h1.45l-3.45,-8.34v6.34z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_camera_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_camera_24dp.xml
deleted file mode 100644
index f36ba70..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_camera_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4h-3.17L15,2H9L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm-5,11.5V13H9v2.5L5.5,12 9,8.5V11h6V8.5l3.5,3.5 -3.5,3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_video_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_video_24dp.xml
deleted file mode 100644
index b29205f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_switch_video_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,9.5V6c0,-0.55 -0.45,-1 -1,-1H3c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h14c0.55,0 1,-0.45 1,-1v-3.5l4,4v-13l-4,4zm-5,6V13H7v2.5L3.5,12 7,8.5V11h6V8.5l3.5,3.5 -3.5,3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tag_faces_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tag_faces_24dp.xml
deleted file mode 100644
index 1ef868a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tag_faces_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zm3.5,-9c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zm-7,0c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zm3.5,6.5c2.33,0 4.31,-1.46 5.11,-3.5H6.89c0.8,2.04 2.78,3.5 5.11,3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_texture_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_texture_24dp.xml
deleted file mode 100644
index 098a5a5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_texture_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.51,3.08L3.08,19.51c0.09,0.34 0.27,0.65 0.51,0.9 0.25,0.24 0.56,0.42 0.9,0.51L20.93,4.49c-0.19,-0.69 -0.73,-1.23 -1.42,-1.41zM11.88,3L3,11.88v2.83L14.71,3h-2.83zM5,3c-1.1,0 -2,0.9 -2,2v2l4,-4H5zm14,18c0.55,0 1.05,-0.22 1.41,-0.59 0.37,-0.36 0.59,-0.86 0.59,-1.41v-2l-4,4h2zm-9.71,0h2.83L21,12.12V9.29L9.29,21z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timelapse_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timelapse_24dp.xml
deleted file mode 100644
index 20d4ee6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timelapse_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.24,7.76C15.07,6.59 13.54,6 12,6v6l-4.24,4.24c2.34,2.34 6.14,2.34 8.49,0 2.34,-2.34 2.34,-6.14 -0.01,-8.48zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_10_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_10_24dp.xml
deleted file mode 100644
index ce73d5d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_10_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M0,7.72V9.4l3,-1V18h2V6h-0.25L0,7.72zm23.78,6.65c-0.14,-0.28 -0.35,-0.53 -0.63,-0.74 -0.28,-0.21 -0.61,-0.39 -1.01,-0.53s-0.85,-0.27 -1.35,-0.38c-0.35,-0.07 -0.64,-0.15 -0.87,-0.23 -0.23,-0.08 -0.41,-0.16 -0.55,-0.25 -0.14,-0.09 -0.23,-0.19 -0.28,-0.3 -0.05,-0.11 -0.08,-0.24 -0.08,-0.39 0,-0.14 0.03,-0.28 0.09,-0.41 0.06,-0.13 0.15,-0.25 0.27,-0.34 0.12,-0.1 0.27,-0.18 0.45,-0.24s0.4,-0.09 0.64,-0.09c0.25,0 0.47,0.04 0.66,0.11 0.19,0.07 0.35,0.17 0.48,0.29 0.13, [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_24dp.xml
deleted file mode 100644
index 0f1425c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,1H9v2h6V1zm-4,13h2V8h-2v6zm8.03,-6.61l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_3_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_3_24dp.xml
deleted file mode 100644
index a9b95fe..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_3_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.61,12.97c-0.16,-0.24 -0.36,-0.46 -0.62,-0.65 -0.25,-0.19 -0.56,-0.35 -0.93,-0.48 0.3,-0.14 0.57,-0.3 0.8,-0.5 0.23,-0.2 0.42,-0.41 0.57,-0.64 0.15,-0.23 0.27,-0.46 0.34,-0.71 0.08,-0.24 0.11,-0.49 0.11,-0.73 0,-0.55 -0.09,-1.04 -0.28,-1.46 -0.18,-0.42 -0.44,-0.77 -0.78,-1.06 -0.33,-0.28 -0.73,-0.5 -1.2,-0.64 -0.45,-0.13 -0.97,-0.2 -1.53,-0.2 -0.55,0 -1.06,0.08 -1.52,0.24 -0.47,0.17 -0.87,0.4 -1.2,0.69 -0.33,0.29 -0.6,0.63 -0.78,1.03 -0.2,0.39 -0.29,0.83 -0.2 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_auto_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_auto_24dp.xml
deleted file mode 100644
index 25160fe..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_auto_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm0,10c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.67 -5.33,-4 -8,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_off_24dp.xml
deleted file mode 100644
index 9b58d04..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_timer_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.04,4.55l-1.42,1.42C16.07,4.74 14.12,4 12,4c-1.83,0 -3.53,0.55 -4.95,1.48l1.46,1.46C9.53,6.35 10.73,6 12,6c3.87,0 7,3.13 7,7 0,1.27 -0.35,2.47 -0.94,3.49l1.45,1.45C20.45,16.53 21,14.83 21,13c0,-2.12 -0.74,-4.07 -1.97,-5.61l1.42,-1.42 -1.41,-1.42zM15,1H9v2h6V1zm-4,8.44l2,2V8h-2v1.44zM3.02,4L1.75,5.27 4.5,8.03C3.55,9.45 3,11.16 3,13c0,4.97 4.02,9 9,9 1.84,0 3.55,-0.55 4.98,-1.5l2.5,2.5 1.27,-1.27 -7.71,-7.71L3.02,4zM12,20c-3.87,0 -7,-3.13 -7,-7 0,-1.28 0.35,-2. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tonality_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tonality_24dp.xml
deleted file mode 100644
index ece3cb2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tonality_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm-1,17.93c-3.94,-0.49 -7,-3.85 -7,-7.93s3.05,-7.44 7,-7.93v15.86zm2,-15.86c1.03,0.13 2,0.45 2.87,0.93H13v-0.93zM13,7h5.24c0.25,0.31 0.48,0.65 0.68,1H13V7zm0,3h6.74c0.08,0.33 0.15,0.66 0.19,1H13v-1zm0,9.93V19h2.87c-0.87,0.48 -1.84,0.8 -2.87,0.93zM18.24,17H13v-1h5.92c-0.2,0.35 -0.43,0.69 -0.68,1zm1.5,-3H13v-1h6.93c-0.04,0.34 -0.11,0.67 -0.19,1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_transform_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_transform_24dp.xml
deleted file mode 100644
index ac407b7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_transform_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,18v-2H8V4h2L7,1 4,4h2v2H2v2h4v8c0,1.1 0.9,2 2,2h8v2h-2l3,3 3,-3h-2v-2h4zM10,8h6v6h2V8c0,-1.1 -0.9,-2 -2,-2h-6v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tune_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tune_24dp.xml
deleted file mode 100644
index 553b593..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_tune_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,17v2h6v-2H3zM3,5v2h10V5H3zm10,16v-2h8v-2h-8v-2h-2v6h2zM7,9v2H3v2h4v2h2V9H7zm14,4v-2H11v2h10zm-6,-4h2V7h4V5h-4V3h-2v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_auto_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_auto_24dp.xml
deleted file mode 100644
index 15887e7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_auto_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.85,12.65h2.3L8,9l-1.15,3.65zM22,7l-1.2,6.29L19.3,7h-1.6l-1.49,6.29L15,7h-0.76C12.77,5.17 10.53,4 8,4c-4.42,0 -8,3.58 -8,8s3.58,8 8,8c3.13,0 5.84,-1.81 7.15,-4.43l0.1,0.43H17l1.5,-6.1L20,16h1.75l2.05,-9H22zm-11.7,9l-0.7,-2H6.4l-0.7,2H3.8L7,7h2l3.2,9h-1.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_cloudy_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_cloudy_24dp.xml
deleted file mode 100644
index 3502765..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_cloudy_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.36,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.64,-4.96z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_incandescent_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_incandescent_24dp.xml
deleted file mode 100644
index e82fd89..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_incandescent_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3.55,18.54l1.41,1.41 1.79,-1.8 -1.41,-1.41 -1.79,1.8zM11,22.45h2V19.5h-2v2.95zM4,10.5H1v2h3v-2zm11,-4.19V1.5H9v4.81C7.21,7.35 6,9.28 6,11.5c0,3.31 2.69,6 6,6s6,-2.69 6,-6c0,-2.22 -1.21,-4.15 -3,-5.19zm5,4.19v2h3v-2h-3zm-2.76,7.66l1.79,1.8 1.41,-1.41 -1.8,-1.79 -1.4,1.4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_irradescent_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_irradescent_24dp.xml
deleted file mode 100644
index a943060..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_irradescent_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,14.5h14v-6H5v6zM11,0.55V3.5h2V0.55h-2zm8.04,2.5l-1.79,1.79 1.41,1.41 1.8,-1.79 -1.42,-1.41zM13,22.45V19.5h-2v2.95h2zm7.45,-3.91l-1.8,-1.79 -1.41,1.41 1.79,1.8 1.42,-1.42zM3.55,4.46l1.79,1.79 1.41,-1.41 -1.79,-1.79 -1.41,1.41zm1.41,15.49l1.79,-1.8 -1.41,-1.41 -1.79,1.79 1.41,1.42z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_sunny_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_sunny_24dp.xml
deleted file mode 100644
index b8c40fb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/image/ic_wb_sunny_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.76,4.84l-1.8,-1.79 -1.41,1.41 1.79,1.79 1.42,-1.41zM4,10.5H1v2h3v-2zm9,-9.95h-2V3.5h2V0.55zm7.45,3.91l-1.41,-1.41 -1.79,1.79 1.41,1.41 1.79,-1.79zm-3.21,13.7l1.79,1.8 1.41,-1.41 -1.8,-1.79 -1.4,1.4zM20,10.5v2h3v-2h-3zm-8,-5c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zm-1,16.95h2V19.5h-2v2.95zm-7.45,-3.91l1.41,1.41 1.79,-1.8 -1.41,-1.41 -1.79,1.8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_beenhere_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_beenhere_24dp.xml
deleted file mode 100644
index bd622df..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_beenhere_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,1H5c-1.1,0 -1.99,0.9 -1.99,2L3,15.93c0,0.69 0.35,1.3 0.88,1.66L12,23l8.11,-5.41c0.53,-0.36 0.88,-0.97 0.88,-1.66L21,3c0,-1.1 -0.9,-2 -2,-2zm-9,15l-5,-5 1.41,-1.41L10,13.17l7.59,-7.59L19,7l-9,9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_24dp.xml
deleted file mode 100644
index ced648c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bike_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bike_24dp.xml
deleted file mode 100644
index d7c0992..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bike_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,4.8c0.99,0 1.8,-0.81 1.8,-1.8s-0.81,-1.8 -1.8,-1.8c-1,0 -1.8,0.81 -1.8,1.8S15,4.8 16,4.8zm3,7.2c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zm0,8.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5zM14.8,10H19V8.2h-3.2l-1.93,-3.27c-0.3,-0.5 -0.84,-0.83 -1.46,-0.83 -0.47,0 -0.89,0.19 -1.2,0.5l-3.7,3.7c-0.32,0.3 -0.51,0.73 -0.51,1.2 0,0.63 0.33,1.16 0.85,1.47L11.2,13v5H13v-6.48l-2.25,-1.67 2.32,-2.33L14.8,10zM5, [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bus_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bus_24dp.xml
deleted file mode 100644
index ca91981..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_bus_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,16c0,0.88 0.39,1.67 1,2.22V20c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h8v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1.78c0.61,-0.55 1,-1.34 1,-2.22V6c0,-3.5 -3.58,-4 -8,-4s-8,0.5 -8,4v10zm3.5,1c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,14 7.5,14s1.5,0.67 1.5,1.5S8.33,17 7.5,17zm9,0c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm1.5,-6H6V6h12v5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_car_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_car_24dp.xml
deleted file mode 100644
index 252a94f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_car_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zm11,0c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_ferry_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_ferry_24dp.xml
deleted file mode 100644
index c304e8a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_ferry_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,21c-1.39,0 -2.78,-0.47 -4,-1.32 -2.44,1.71 -5.56,1.71 -8,0C6.78,20.53 5.39,21 4,21H2v2h2c1.38,0 2.74,-0.35 4,-0.99 2.52,1.29 5.48,1.29 8,0 1.26,0.65 2.62,0.99 4,0.99h2v-2h-2zM3.95,19H4c1.6,0 3.02,-0.88 4,-2 0.98,1.12 2.4,2 4,2s3.02,-0.88 4,-2c0.98,1.12 2.4,2 4,2h0.05l1.89,-6.68c0.08,-0.26 0.06,-0.54 -0.06,-0.78s-0.34,-0.42 -0.6,-0.5L20,10.62V6c0,-1.1 -0.9,-2 -2,-2h-3V1H9v3H6c-1.1,0 -2,0.9 -2,2v4.62l-1.29,0.42c-0.26,0.08 -0.48,0.26 -0.6,0.5s-0.15,0.52 -0.06,0 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_subway_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_subway_24dp.xml
deleted file mode 100644
index a40cea8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_subway_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2c-4.42,0 -8,0.5 -8,4v9.5C4,17.43 5.57,19 7.5,19L6,20.5v0.5h12v-0.5L16.5,19c1.93,0 3.5,-1.57 3.5,-3.5V6c0,-3.5 -3.58,-4 -8,-4zM7.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,14 7.5,14s1.5,0.67 1.5,1.5S8.33,17 7.5,17zm3.5,-6H6V6h5v5zm5.5,6c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm1.5,-6h-5V6h5v5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_train_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_train_24dp.xml
deleted file mode 100644
index a029014..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_train_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,15.5C4,17.43 5.57,19 7.5,19L6,20.5v0.5h12v-0.5L16.5,19c1.93,0 3.5,-1.57 3.5,-3.5V5c0,-3.5 -3.58,-4 -8,-4s-8,0.5 -8,4v10.5zm8,1.5c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zm6,-7H6V5h12v5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_transit_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_transit_24dp.xml
deleted file mode 100644
index a40cea8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_transit_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2c-4.42,0 -8,0.5 -8,4v9.5C4,17.43 5.57,19 7.5,19L6,20.5v0.5h12v-0.5L16.5,19c1.93,0 3.5,-1.57 3.5,-3.5V6c0,-3.5 -3.58,-4 -8,-4zM7.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,14 7.5,14s1.5,0.67 1.5,1.5S8.33,17 7.5,17zm3.5,-6H6V6h5v5zm5.5,6c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm1.5,-6h-5V6h5v5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_walk_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_walk_24dp.xml
deleted file mode 100644
index 3b92462..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_directions_walk_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,3.8c0.99,0 1.8,-0.81 1.8,-1.8 0,-1 -0.81,-1.8 -1.8,-1.8 -1,0 -1.8,0.81 -1.8,1.8S13,3.8 14,3.8zm0.12,6.2H19V8.2h-3.62l-2,-3.33c-0.3,-0.5 -0.84,-0.83 -1.46,-0.83 -0.17,0 -0.34,0.03 -0.49,0.07L6,5.8V11h1.8V7.33l2.11,-0.66L6,22h1.8l2.87,-8.11L13,17v5h1.8v-6.41l-2.49,-4.54 0.73,-2.87L14.12,10z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_flight_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_flight_24dp.xml
deleted file mode 100644
index 3aa5af7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_flight_24dp.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.18,9"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,16v-2l-8,-5V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5V9l-8,5v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-5.5l8,2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_hotel_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_hotel_24dp.xml
deleted file mode 100644
index 3d2e1a0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_hotel_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,13c1.66,0 3,-1.34 3,-3S8.66,7 7,7s-3,1.34 -3,3 1.34,3 3,3zm12,-6h-8v7H3V5H1v15h2v-3h18v3h2v-9c0,-2.21 -1.79,-4 -4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_24dp.xml
deleted file mode 100644
index 430ec18..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_clear_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_clear_24dp.xml
deleted file mode 100644
index 8516033..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_layers_clear_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.81,14.99l1.19,-0.92 -1.43,-1.43 -1.19,0.92 1.43,1.43zm-0.45,-4.72L21,9l-9,-7 -2.91,2.27 7.87,7.88 2.4,-1.88zM3.27,1L2,2.27l4.22,4.22L3,9l1.63,1.27L12,16l2.1,-1.63 1.43,1.43L12,18.54l-7.37,-5.73L3,14.07l9,7 4.95,-3.85L20.73,21 22,19.73 3.27,1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_airport_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_airport_24dp.xml
deleted file mode 100644
index c63e2de..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_airport_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,16v-2l-8,-5V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5V9l-8,5v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-5.5l8,2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_atm_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_atm_24dp.xml
deleted file mode 100644
index eb1dc04..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_atm_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,17h2v-1h1c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-1h4V8h-2V7h-2v1h-1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1h3v1H9v2h2v1zm9,-13H4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6c0,-1.11 -0.89,-2 -2,-2zm0,14H4V6h16v12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_attraction_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_attraction_24dp.xml
deleted file mode 100644
index a75c432..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_attraction_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,12c0,-1.1 0.9,-2 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -1.99,0.9 -1.99,2v4c1.1,0 1.99,0.9 1.99,2s-0.89,2 -2,2v4c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-4c-1.1,0 -2,-0.9 -2,-2zm-4.42,4.8L12,14.5l-3.58,2.3 1.08,-4.12 -3.29,-2.69 4.24,-0.25L12,5.8l1.54,3.95 4.24,0.25 -3.29,2.69 1.09,4.11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_bar_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_bar_24dp.xml
deleted file mode 100644
index e6eba39..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_bar_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11,13v6H6v2h12v-2h-5v-6l8,-8V3H3v2l8,8zM7.5,7l-2,-2h13l-2,2h-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_cafe_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_cafe_24dp.xml
deleted file mode 100644
index 1b0a6d6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_cafe_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,3H4v10c0,2.21 1.79,4 4,4h6c2.21,0 4,-1.79 4,-4v-3h2c1.11,0 2,-0.89 2,-2V5c0,-1.11 -0.89,-2 -2,-2zm0,5h-2V5h2v3zM2,21h18v-2H2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_car_wash_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_car_wash_24dp.xml
deleted file mode 100644
index f94b7ff..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_car_wash_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,5c0.83,0 1.5,-0.67 1.5,-1.5 0,-1 -1.5,-2.7 -1.5,-2.7s-1.5,1.7 -1.5,2.7c0,0.83 0.67,1.5 1.5,1.5zm-5,0c0.83,0 1.5,-0.67 1.5,-1.5 0,-1 -1.5,-2.7 -1.5,-2.7s-1.5,1.7 -1.5,2.7c0,0.83 0.67,1.5 1.5,1.5zM7,5c0.83,0 1.5,-0.67 1.5,-1.5C8.5,2.5 7,0.8 7,0.8S5.5,2.5 5.5,3.5C5.5,4.33 6.17,5 7,5zm11.92,3.01C18.72,7.42 18.16,7 17.5,7h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,14v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_convenience_store_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_convenience_store_24dp.xml
deleted file mode 100644
index b1259d2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_convenience_store_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,7V4H5v3H2v13h8v-4h4v4h8V7h-3zm-8,3H9v1h2v1H8V9h2V8H8V7h3v3zm5,2h-1v-2h-2V7h1v2h1V7h1v5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_drink_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_drink_24dp.xml
deleted file mode 100644
index 36a980a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_drink_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,2l2.01,18.23C5.13,21.23 5.97,22 7,22h10c1.03,0 1.87,-0.77 1.99,-1.77L21,2H3zm9,17c-1.66,0 -3,-1.34 -3,-3 0,-2 3,-5.4 3,-5.4s3,3.4 3,5.4c0,1.66 -1.34,3 -3,3zm6.33,-11H5.67l-0.44,-4h13.53l-0.43,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_florist_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_florist_24dp.xml
deleted file mode 100644
index 0932396..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_florist_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,22c4.97,0 9,-4.03 9,-9 -4.97,0 -9,4.03 -9,9zM5.6,10.25c0,1.38 1.12,2.5 2.5,2.5 0.53,0 1.01,-0.16 1.42,-0.44l-0.02,0.19c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5l-0.02,-0.19c0.4,0.28 0.89,0.44 1.42,0.44 1.38,0 2.5,-1.12 2.5,-2.5 0,-1 -0.59,-1.85 -1.43,-2.25 0.84,-0.4 1.43,-1.25 1.43,-2.25 0,-1.38 -1.12,-2.5 -2.5,-2.5 -0.53,0 -1.01,0.16 -1.42,0.44l0.02,-0.19C14.5,2.12 13.38,1 12,1S9.5,2.12 9.5,3.5l0.02,0.19c-0.4,-0.28 -0.89,-0.44 -1.42,-0.44 -1.38,0 -2.5,1.12 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_gas_station_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_gas_station_24dp.xml
deleted file mode 100644
index 9c10a2c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_gas_station_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.77,7.23l0.01,-0.01 -3.72,-3.72L15,4.56l2.11,2.11c-0.94,0.36 -1.61,1.26 -1.61,2.33 0,1.38 1.12,2.5 2.5,2.5 0.36,0 0.69,-0.08 1,-0.21v7.21c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V14c0,-1.1 -0.9,-2 -2,-2h-1V5c0,-1.1 -0.9,-2 -2,-2H6c-1.1,0 -2,0.9 -2,2v16h10v-7.5h1.5v5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5V9c0,-0.69 -0.28,-1.32 -0.73,-1.77zM12,10H6V5h6v5zm6,0c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_grocery_store_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_grocery_store_24dp.xml
deleted file mode 100644
index f28015e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_grocery_store_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2H7.42c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1H5.21l-0.94,-2H1zm16,16c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hospital_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hospital_24dp.xml
deleted file mode 100644
index 5ae2b34..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hospital_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm-1,11h-4v4h-4v-4H6v-4h4V6h4v4h4v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hotel_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hotel_24dp.xml
deleted file mode 100644
index 3d2e1a0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_hotel_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,13c1.66,0 3,-1.34 3,-3S8.66,7 7,7s-3,1.34 -3,3 1.34,3 3,3zm12,-6h-8v7H3V5H1v15h2v-3h18v3h2v-9c0,-2.21 -1.79,-4 -4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_laundry_service_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_laundry_service_24dp.xml
deleted file mode 100644
index 7f03ce3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_laundry_service_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9.17,16.83c1.56,1.56 4.1,1.56 5.66,0 1.56,-1.56 1.56,-4.1 0,-5.66l-5.66,5.66zM18,2.01L6,2c-1.11,0 -2,0.89 -2,2v16c0,1.11 0.89,2 2,2h12c1.11,0 2,-0.89 2,-2V4c0,-1.11 -0.89,-1.99 -2,-1.99zM10,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM7,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zm5,16c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_library_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_library_24dp.xml
deleted file mode 100644
index a396913..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_library_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,11.55C9.64,9.35 6.48,8 3,8v11c3.48,0 6.64,1.35 9,3.55 2.36,-2.19 5.52,-3.55 9,-3.55V8c-3.48,0 -6.64,1.35 -9,3.55zM12,8c1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3 1.34,3 3,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_mall_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_mall_24dp.xml
deleted file mode 100644
index bce7f6f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_mall_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,6h-2c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6H5c-1.1,0 -1.99,0.9 -1.99,2L3,20c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zm-7,-3c1.66,0 3,1.34 3,3H9c0,-1.66 1.34,-3 3,-3zm0,10c-2.76,0 -5,-2.24 -5,-5h2c0,1.66 1.34,3 3,3s3,-1.34 3,-3h2c0,2.76 -2.24,5 -5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_movies_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_movies_24dp.xml
deleted file mode 100644
index 7c662e7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_movies_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8,17H6v-2h2v2zm0,-4H6v-2h2v2zm0,-4H6V7h2v2zm10,8h-2v-2h2v2zm0,-4h-2v-2h2v2zm0,-4h-2V7h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_offer_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_offer_24dp.xml
deleted file mode 100644
index 7b14cab..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_offer_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.41,11.58l-9,-9C12.05,2.22 11.55,2 11,2H4c-1.1,0 -2,0.9 -2,2v7c0,0.55 0.22,1.05 0.59,1.42l9,9c0.36,0.36 0.86,0.58 1.41,0.58 0.55,0 1.05,-0.22 1.41,-0.59l7,-7c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-0.55 -0.23,-1.06 -0.59,-1.42zM5.5,7C4.67,7 4,6.33 4,5.5S4.67,4 5.5,4 7,4.67 7,5.5 6.33,7 5.5,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_parking_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_parking_24dp.xml
deleted file mode 100644
index 145b78f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_parking_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,3H6v18h4v-6h3c3.31,0 6,-2.69 6,-6s-2.69,-6 -6,-6zm0.2,8H10V7h3.2c1.1,0 2,0.9 2,2s-0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pharmacy_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pharmacy_24dp.xml
deleted file mode 100644
index 68b81de..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pharmacy_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21,5h-2.64l1.14,-3.14L17.15,1l-1.46,4H3v2l2,6 -2,6v2h18v-2l-2,-6 2,-6V5zm-5,9h-3v3h-2v-3H8v-2h3V9h2v3h3v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_phone_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_phone_24dp.xml
deleted file mode 100644
index 3a3ba47..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_phone_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pizza_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pizza_24dp.xml
deleted file mode 100644
index 15efc5e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_pizza_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C8.43,2 5.23,3.54 3.01,6L12,22l8.99,-16C18.78,3.55 15.57,2 12,2zM7,7c0,-1.1 0.9,-2 2,-2s2,0.9 2,2 -0.9,2 -2,2 -2,-0.9 -2,-2zm5,8c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_play_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_play_24dp.xml
deleted file mode 100644
index a75c432..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_play_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,12c0,-1.1 0.9,-2 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -1.99,0.9 -1.99,2v4c1.1,0 1.99,0.9 1.99,2s-0.89,2 -2,2v4c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-4c-1.1,0 -2,-0.9 -2,-2zm-4.42,4.8L12,14.5l-3.58,2.3 1.08,-4.12 -3.29,-2.69 4.24,-0.25L12,5.8l1.54,3.95 4.24,0.25 -3.29,2.69 1.09,4.11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_post_office_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_post_office_24dp.xml
deleted file mode 100644
index bf2ac7d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_post_office_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm0,4l-8,5 -8,-5V6l8,5 8,-5v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_print_shop_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_print_shop_24dp.xml
deleted file mode 100644
index 9bfb690..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_print_shop_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,8H5c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3 -3,-3zm-3,11H8v-5h8v5zm3,-7c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zm-1,-9H6v4h12V3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_restaurant_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_restaurant_24dp.xml
deleted file mode 100644
index 4c41998..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_restaurant_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8.1,13.34l2.83,-2.83L3.91,3.5c-1.56,1.56 -1.56,4.09 0,5.66l4.19,4.18zm6.78,-1.81c1.53,0.71 3.68,0.21 5.27,-1.38 1.91,-1.91 2.28,-4.65 0.81,-6.12 -1.46,-1.46 -4.2,-1.1 -6.12,0.81 -1.59,1.59 -2.09,3.74 -1.38,5.27L3.7,19.87l1.41,1.41L12,14.41l6.88,6.88 1.41,-1.41L13.41,13l1.47,-1.47z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_see_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_see_24dp.xml
deleted file mode 100644
index 8404ea5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_see_24dp.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_shipping_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_shipping_24dp.xml
deleted file mode 100644
index 57d8b29..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_shipping_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,8h-3V4H3c-1.1,0 -2,0.9 -2,2v11h2c0,1.66 1.34,3 3,3s3,-1.34 3,-3h6c0,1.66 1.34,3 3,3s3,-1.34 3,-3h2v-5l-3,-4zM6,18.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm13.5,-9l1.96,2.5H17V9.5h2.5zm-1.5,9c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_taxi_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_taxi_24dp.xml
deleted file mode 100644
index bf051cf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_local_taxi_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5H15V3H9v2H6.5c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zm11,0c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_location_history_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_location_history_24dp.xml
deleted file mode 100644
index f72c0cd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_location_history_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,2H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-7,3.3c1.49,0 2.7,1.21 2.7,2.7 0,1.49 -1.21,2.7 -2.7,2.7 -1.49,0 -2.7,-1.21 -2.7,-2.7 0,-1.49 1.21,-2.7 2.7,-2.7zM18,16H6v-0.9c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v0.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_map_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_map_24dp.xml
deleted file mode 100644
index da420ca..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_map_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_my_location_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_my_location_24dp.xml
deleted file mode 100644
index 2a7817e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_my_location_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm8.94,3c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_navigation_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_navigation_24dp.xml
deleted file mode 100644
index bb6a1de..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_navigation_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2L4.5,20.29l0.71,0.71L12,18l6.79,3 0.71,-0.71z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_pin_drop_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_pin_drop_24dp.xml
deleted file mode 100644
index f2c728d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_pin_drop_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,8c0,-3.31 -2.69,-6 -6,-6S6,4.69 6,8c0,4.5 6,11 6,11s6,-6.5 6,-11zm-8,0c0,-1.1 0.9,-2 2,-2s2,0.9 2,2 -0.89,2 -2,2c-1.1,0 -2,-0.9 -2,-2zM5,20v2h14v-2H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_place_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_place_24dp.xml
deleted file mode 100644
index 04b8734..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_place_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zm0,9.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_rate_review_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_rate_review_24dp.xml
deleted file mode 100644
index 740409d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_rate_review_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,14v-2.47l6.88,-6.88c0.2,-0.2 0.51,-0.2 0.71,0l1.77,1.77c0.2,0.2 0.2,0.51 0,0.71L8.47,14H6zm12,0h-7.5l2,-2H18v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_restaurant_menu_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_restaurant_menu_24dp.xml
deleted file mode 100644
index 4c41998..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_restaurant_menu_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8.1,13.34l2.83,-2.83L3.91,3.5c-1.56,1.56 -1.56,4.09 0,5.66l4.19,4.18zm6.78,-1.81c1.53,0.71 3.68,0.21 5.27,-1.38 1.91,-1.91 2.28,-4.65 0.81,-6.12 -1.46,-1.46 -4.2,-1.1 -6.12,0.81 -1.59,1.59 -2.09,3.74 -1.38,5.27L3.7,19.87l1.41,1.41L12,14.41l6.88,6.88 1.41,-1.41L13.41,13l1.47,-1.47z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_satellite_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_satellite_24dp.xml
deleted file mode 100644
index 1f89d3c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_satellite_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM5,4.99h3C8,6.65 6.66,8 5,8V4.99zM5,12v-2c2.76,0 5,-2.25 5,-5.01h2C12,8.86 8.87,12 5,12zm0,6l3.5,-4.5 2.5,3.01L14.5,12l4.5,6H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_store_mall_directory_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_store_mall_directory_24dp.xml
deleted file mode 100644
index cef78e8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_store_mall_directory_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4H4v2h16V4zm1,10v-2l-1,-5H4l-1,5v2h1v6h10v-6h4v6h2v-6h1zm-9,4H6v-4h6v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_terrain_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_terrain_24dp.xml
deleted file mode 100644
index c3ad94b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_terrain_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_traffic_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_traffic_24dp.xml
deleted file mode 100644
index 61f7523..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/maps/ic_traffic_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,10h-3V8.86c1.72,-0.45 3,-2 3,-3.86h-3V4c0,-0.55 -0.45,-1 -1,-1H8c-0.55,0 -1,0.45 -1,1v1H4c0,1.86 1.28,3.41 3,3.86V10H4c0,1.86 1.28,3.41 3,3.86V15H4c0,1.86 1.28,3.41 3,3.86V20c0,0.55 0.45,1 1,1h8c0.55,0 1,-0.45 1,-1v-1.14c1.72,-0.45 3,-2 3,-3.86h-3v-1.14c1.72,-0.45 3,-2 3,-3.86zm-8,9c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2c1.1,0 2,0.9 2,2s-0.89,2 -2,2zm0,-5c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2c1.1,0 2,0.9 2,2s-0.89,2 -2,2zm0,-5c-1.11,0 -2,-0.9 -2,-2 0,-1.11 0.89,-2 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/microphone.xml b/base/asset-studio/src/main/java/images/material_design_icons/microphone.xml
deleted file mode 100644
index b999099..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/microphone.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="m19,11l-1.7,0c0,0.74 -0.16,1.43 -0.43,2.05l1.23,1.23c0.56,-0.98 0.9,-2.09 0.9,-3.28zm-4.02,0.17c0,-0.06 0.02,-0.11 0.02,-0.17l0,-6c0,-1.66 -1.34,-3 -3,-3s-3,1.34 -3,3l0,0.18l5.98,5.99zm-10.71,-8.17l-1.27,1.27l6.01,6.01l0,0.72c0,1.66 1.33,3 2.99,3c0.22,0 0.44,-0.03 0.65,-0.08l1.66,1.66c-0.71,0.33 -1.5,0.52 -2.31,0.52c-2.76,0 -5.3,-2.1 -5.3,-5.1l-1.7,0c0,3.41 2.72,6.23 6,6.72l0,3.28l2,0l0,-3.28c0.91,-0.13 1.77,-0.45 2.54,-0.9l4.19,4.18l1.27,-1.27l-16.73,-16.73z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_24dp.xml
deleted file mode 100644
index 9bb7960..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,8h4V4H4v4zm6,12h4v-4h-4v4zm-6,0h4v-4H4v4zm0,-6h4v-4H4v4zm6,0h4v-4h-4v4zm6,-10v4h4V4h-4zm-6,4h4V4h-4v4zm6,6h4v-4h-4v4zm0,6h4v-4h-4v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_36px.xml
deleted file mode 100644
index b0a62d1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_apps_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,12h6V6H6v6zm9,18h6v-6h-6v6zm-9,0h6v-6H6v6zm0,-9h6v-6H6v6zm9,0h6v-6h-6v6zm9,-15v6h6V6h-6zm-9,6h6V6h-6v6zm9,9h6v-6h-6v6zm0,9h6v-6h-6v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_24dp.xml
deleted file mode 100644
index 2ac850b..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_36px.xml
deleted file mode 100644
index 97f4c9e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_back_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M30,16.5H11.74l8.38,-8.38L18,6 6,18l12,12 2.12,-2.12 -8.38,-8.38H30v-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_24dp.xml
deleted file mode 100644
index 4ec66aa..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,10l5,5 5,-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_36px.xml
deleted file mode 100644
index 1a6474a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.5,15l7.5,7.5 7.5,-7.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_circle_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_circle_24dp.xml
deleted file mode 100644
index beb8569..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_down_circle_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,12l-4,-4h8l-4,4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_24dp.xml
deleted file mode 100644
index 33c90ee..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,14l5,-5 5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_36px.xml
deleted file mode 100644
index 0c2f255..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_drop_up_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10.5,21l7.5,-7.5 7.5,7.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_24dp.xml
deleted file mode 100644
index 4087dcd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_36px.xml
deleted file mode 100644
index a2aa943..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_arrow_forward_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,6l-2.12,2.12 8.38,8.38H6v3h18.26l-8.38,8.38L18,30l12,-12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_24dp.xml
deleted file mode 100644
index 8ea1d98..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zm5,13.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_36px.xml
deleted file mode 100644
index c682aef..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_cancel_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,3C9.71,3 3,9.71 3,18s6.71,15 15,15 15,-6.71 15,-15S26.29,3 18,3zm7.5,20.38l-2.12,2.12L18,20.12l-5.38,5.38 -2.12,-2.12L15.88,18l-5.38,-5.38 2.12,-2.12L18,15.88l5.38,-5.38 2.12,2.12L20.12,18l5.38,5.38z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_24dp.xml
deleted file mode 100644
index 02e8321..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_36px.xml
deleted file mode 100644
index 8b6a857..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_check_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.5,24.26L7.24,18l-2.12,2.12 8.38,8.38 18,-18 -2.12,-2.12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_24dp.xml
deleted file mode 100644
index ed84de6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_36px.xml
deleted file mode 100644
index bd4dcaa..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_left_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M23.12,11.12L21,9l-9,9 9,9 2.12,-2.12L16.24,18z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_24dp.xml
deleted file mode 100644
index 2ff2653..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_36px.xml
deleted file mode 100644
index c6a6a74..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_chevron_right_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,9l-2.12,2.12L19.76,18l-6.88,6.88L15,27l9,-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_24dp.xml
deleted file mode 100644
index 88f04ed..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_36px.xml
deleted file mode 100644
index ad67228..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_close_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M28.5,9.62L26.38,7.5 18,15.88 9.62,7.5 7.5,9.62 15.88,18 7.5,26.38l2.12,2.12L18,20.12l8.38,8.38 2.12,-2.12L20.12,18z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_24dp.xml
deleted file mode 100644
index 6f1c0d5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_36px.xml
deleted file mode 100644
index 12bcbc0..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_less_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,12l-9,9 2.12,2.12L18,16.24l6.88,6.88L27,21z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_24dp.xml
deleted file mode 100644
index 98b7d7c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_36px.xml
deleted file mode 100644
index e9d17e7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_expand_more_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M24.88,12.88L18,19.76l-6.88,-6.88L9,15l9,9 9,-9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_24dp.xml
deleted file mode 100644
index e35574f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,14H5v5h5v-2H7v-3zm-2,-4h2V7h3V5H5v5zm12,7h-3v2h5v-5h-2v3zM14,5v2h3v3h2V5h-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_36px.xml
deleted file mode 100644
index f0d60c4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,21H7v8h8v-3h-5v-5zm-3,-6h3v-5h5V7H7v8zm19,11h-5v3h8v-8h-3v5zM21,7v3h5v5h3V7h-8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_24dp.xml
deleted file mode 100644
index ad11915..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,16h3v3h2v-5H5v2zm3,-8H5v2h5V5H8v3zm6,11h2v-3h3v-2h-5v5zm2,-11V5h-2v5h5V8h-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_36px.xml
deleted file mode 100644
index 9e779e1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_fullscreen_exit_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7,24h5v5h3v-8H7v3zm5,-12H7v3h8V7h-3v5zm9,17h3v-5h5v-3h-8v8zm3,-17V7h-3v8h8v-3h-5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_24dp.xml
deleted file mode 100644
index 60e9e71..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,18h18v-2H3v2zm0,-5h18v-2H3v2zm0,-7v2h18V6H3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_36px.xml
deleted file mode 100644
index b90b53a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_menu_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M4,27h28v-3H4v3zm0,-8h28v-3H4v3zM4,8v3h28V8H4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_24dp.xml
deleted file mode 100644
index 74c9367..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm12,0c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm-6,0c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_36px.xml
deleted file mode 100644
index 6a3165e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_horiz_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zm18,0c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zm-9,0c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_24dp.xml
deleted file mode 100644
index 9c7516e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zm0,2c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zm0,6c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_36px.xml
deleted file mode 100644
index 43afde6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_more_vert_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,12c1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3 1.34,3 3,3zm0,3c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zm0,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_24dp.xml
deleted file mode 100644
index 19991a9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_36px.xml
deleted file mode 100644
index a4d1eed..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_refresh_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M26.47,9.53C24.3,7.35 21.32,6 18,6 11.37,6 6,11.37 6,18s5.37,12 12,12c5.94,0 10.85,-4.33 11.81,-10h-3.04c-0.91,4.01 -4.49,7 -8.77,7 -4.97,0 -9,-4.03 -9,-9s4.03,-9 9,-9c2.49,0 4.71,1.03 6.34,2.66L20,16h10V6l-3.53,3.53z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_24dp.xml
deleted file mode 100644
index 459c655..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zm9.18,-13.18L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_36px.xml
deleted file mode 100644
index d7cf4e9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_less_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.12,27.88L13.24,30 18,25.24 22.76,30l2.12,-2.12L18,21l-6.88,6.88zM24.88,8.12L22.76,6 18,10.76 13.24,6l-2.12,2.12L18,15l6.88,-6.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_24dp.xml
deleted file mode 100644
index b912880..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zm0,12.34L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_36px.xml b/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_36px.xml
deleted file mode 100644
index 61f093a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/navigation/ic_unfold_more_36px.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="36.0"
- android:viewportHeight="36.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,9.24L22.76,14l2.12,-2.12L18,5l-6.88,6.88L13.24,14 18,9.24zm0,17.52L13.24,22l-2.12,2.12L18,31l6.88,-6.88L22.76,22 18,26.76z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_adb_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_adb_24dp.xml
deleted file mode 100644
index b34e4d4..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_adb_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,16c0,3.87 3.13,7 7,7s7,-3.13 7,-7v-4H5v4zM16.12,4.37l2.1,-2.1 -0.82,-0.83 -2.3,2.31C14.16,3.28 13.12,3 12,3s-2.16,0.28 -3.09,0.75L6.6,1.44l-0.82,0.83 2.1,2.1C6.14,5.64 5,7.68 5,10v1h14v-1c0,-2.32 -1.14,-4.36 -2.88,-5.63zM9,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zm6,0c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_bluetooth_audio_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_bluetooth_audio_24dp.xml
deleted file mode 100644
index d09fb6f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_bluetooth_audio_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zm5.29,-5.3l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zm-3.82,1L10,2H9v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41V22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59V5.83zm1.88,10.46L11,18.17v-3.76l1.88,1.88z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_disc_full_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_disc_full_24dp.xml
deleted file mode 100644
index 1bc6710..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_disc_full_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,16h2v-2h-2v2zm0,-9v5h2V7h-2zM10,4c-4.42,0 -8,3.58 -8,8s3.58,8 8,8 8,-3.58 8,-8 -3.58,-8 -8,-8zm0,10c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_dnd_forwardslash_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_dnd_forwardslash_24dp.xml
deleted file mode 100644
index b8dafd2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_dnd_forwardslash_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM4,12c0,-4.4 3.6,-8 8,-8 1.8,0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4,13.8 4,12zm8,8c-1.8,0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20,10.2 20,12c0,4.4 -3.6,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_24dp.xml
deleted file mode 100644
index 90eae2a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_do_not_disturb_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8 0,-1.85 0.63,-3.55 1.69,-4.9L16.9,18.31C15.55,19.37 13.85,20 12,20zm6.31,-3.1L7.1,5.69C8.45,4.63 10.15,4 12,4c4.42,0 8,3.58 8,8 0,1.85 -0.63,3.55 -1.69,4.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_drive_eta_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_drive_eta_24dp.xml
deleted file mode 100644
index 8550cf2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_drive_eta_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.92,5.01C18.72,4.42 18.16,4 17.5,4h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,11v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,12 6.5,12s1.5,0.67 1.5,1.5S7.33,15 6.5,15zm11,0c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,10l1.5,-4.5h11L19,10H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_available_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_available_24dp.xml
deleted file mode 100644
index 3071e64..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_available_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.53,11.06L15.47,10l-4.88,4.88 -2.12,-2.12 -1.06,1.06L10.59,17l5.94,-5.94zM19,3h-1V1h-2v2H8V1H6v2H5c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V8h14v11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_busy_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_busy_24dp.xml
deleted file mode 100644
index 6fb38ce..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_busy_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M9.31,17l2.44,-2.44L14.19,17l1.06,-1.06 -2.44,-2.44 2.44,-2.44L14.19,10l-2.44,2.44L9.31,10l-1.06,1.06 2.44,2.44 -2.44,2.44L9.31,17zM19,3h-1V1h-2v2H8V1H6v2H5c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V8h14v11z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_note_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_note_24dp.xml
deleted file mode 100644
index 7aea2ac..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_event_note_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,10H7v2h10v-2zm2,-7h-1V1h-2v2H8V1H6v2H5c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V8h14v11zm-5,-5H7v2h7v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_folder_special_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_folder_special_24dp.xml
deleted file mode 100644
index d62bb4f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_folder_special_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-8l-2,-2H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zm-6.42,12L10,15.9 6.42,18l0.95,-4.07 -3.16,-2.74 4.16,-0.36L10,7l1.63,3.84 4.16,0.36 -3.16,2.74 0.95,4.06z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_mms_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_mms_24dp.xml
deleted file mode 100644
index 4f8b65f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_mms_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM5,14l3.5,-4.5 2.5,3.01L14.5,8l4.5,6H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_more_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_more_24dp.xml
deleted file mode 100644
index 060b18d..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_more_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,3H7c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.97,0.89 1.66,0.89H22c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM9,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm5,0c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zm5,0c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_locked_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_locked_24dp.xml
deleted file mode 100644
index cb260cc..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_network_locked_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19.5,10c0.17,0 0.33,0.03 0.5,0.05V1L1,20h13v-3c0,-0.89 0.39,-1.68 1,-2.23v-0.27c0,-2.48 2.02,-4.5 4.5,-4.5zm2.5,6v-1.5c0,-1.38 -1.12,-2.5 -2.5,-2.5S17,13.12 17,14.5V16c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zm-1,0h-3v-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5V16z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_bluetooth_speaker_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_bluetooth_speaker_24dp.xml
deleted file mode 100644
index 58978a5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_bluetooth_speaker_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.71,9.5L17,7.21V11h0.5l2.85,-2.85L18.21,6l2.15,-2.15L17.5,1H17v3.79L14.71,2.5l-0.71,0.71L16.79,6 14,8.79l0.71,0.71zM18,2.91l0.94,0.94 -0.94,0.94V2.91zm0,4.3l0.94,0.94 -0.94,0.94V7.21zm2,8.29c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_forwarded_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_forwarded_24dp.xml
deleted file mode 100644
index 7a544b5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_forwarded_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,11l5,-5 -5,-5v3h-4v4h4v3zm2,4.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_in_talk_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_in_talk_24dp.xml
deleted file mode 100644
index 6e22cac..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_in_talk_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,12h2c0,-4.97 -4.03,-9 -9,-9v2c3.87,0 7,3.13 7,7zm-4,0h2c0,-2.76 -2.24,-5 -5,-5v2c1.66,0 3,1.34 3,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_locked_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_locked_24dp.xml
deleted file mode 100644
index 236bef8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_locked_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM20,4v-0.5C20,2.12 18.88,1 17.5,1S15,2.12 15,3.5V4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1V5c0,-0.55 -0.45,-1 -1,-1zm-0.8,0h-3.4v-0.5c0,-0.94 0. [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_missed_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_missed_24dp.xml
deleted file mode 100644
index c03efaf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_missed_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.5,5.5L12,11l7,-7 -1,-1 -6,6 -4.5,-4.5H11V3H5v6h1.5V5.5zm17.21,11.17C20.66,13.78 16.54,12 12,12 7.46,12 3.34,13.78 0.29,16.67c-0.18,0.18 -0.29,0.43 -0.29,0.71s0.11,0.53 0.29,0.71l2.48,2.48c0.18,0.18 0.43,0.29 0.71,0.29 0.27,0 0.52,-0.11 0.7,-0.28 0.79,-0.74 1.69,-1.36 2.66,-1.85 0.33,-0.16 0.56,-0.5 0.56,-0.9v-3.1c1.45,-0.48 3,-0.73 4.6,-0.73 1.6,0 3.15,0.25 4.6,0.72v3.1c0,0.39 0.23,0.74 0.56,0.9 0.98,0.49 1.87,1.12 2.67,1.85 0.18,0.18 0.43,0.28 0.7,0.28 0.28, [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_paused_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_paused_24dp.xml
deleted file mode 100644
index b4d51d8..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_phone_paused_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,3h-2v7h2V3zm3,12.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,3v7h2V3h-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_play_download_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_play_download_24dp.xml
deleted file mode 100644
index f18f833..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_play_download_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-4V4l-2,-2h-4L8,4v2H4c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8c0,-1.11 -0.89,-2 -2,-2zM10,4h4v2h-4V4zm2,15l-5,-5h3v-4h4v4h3l-5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_play_install_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_play_install_24dp.xml
deleted file mode 100644
index a14f860..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_play_install_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,6h-4V4l-2,-2h-4L8,4v2H4c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8c0,-1.11 -0.89,-2 -2,-2zM10,4h4v2h-4V4zm0.5,13.5L7,14l1.41,-1.41 2.09,2.09 5.18,-5.18 1.41,1.41 -6.59,6.59z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sd_card_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sd_card_24dp.xml
deleted file mode 100644
index 74e0e41..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sd_card_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-6,6h-2V4h2v4zm3,0h-2V4h2v4zm3,0h-2V4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sim_card_alert_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sim_card_alert_24dp.xml
deleted file mode 100644
index ef5a545..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sim_card_alert_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-5,15h-2v-2h2v2zm0,-4h-2V8h2v5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_24dp.xml
deleted file mode 100644
index 796a028..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM9,11H7V9h2v2zm4,0h-2V9h2v2zm4,0h-2V9h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_failed_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_failed_24dp.xml
deleted file mode 100644
index d9adbb3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sms_failed_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-7,12h-2v-2h2v2zm0,-4h-2V6h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_24dp.xml
deleted file mode 100644
index 5ad1561..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,4V1L8,5l4,4V6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zm0,14c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_disabled_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_disabled_24dp.xml
deleted file mode 100644
index 14e2932..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_disabled_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,6.35V4.26c-0.8,0.21 -1.55,0.54 -2.23,0.96l1.46,1.46c0.25,-0.12 0.5,-0.24 0.77,-0.33zm-7.14,-0.94l2.36,2.36C4.45,8.99 4,10.44 4,12c0,2.21 0.91,4.2 2.36,5.64L4,20h6v-6l-2.24,2.24C6.68,15.15 6,13.66 6,12c0,-1 0.25,-1.94 0.68,-2.77l8.08,8.08c-0.25,0.13 -0.5,0.25 -0.77,0.34v2.09c0.8,-0.21 1.55,-0.54 2.23,-0.96l2.36,2.36 1.27,-1.27L4.14,4.14 2.86,5.41zM20,4h-6v6l2.24,-2.24C17.32,8.85 18,10.34 18,12c0,1 -0.25,1.94 -0.68,2.77l1.46,1.46C19.55,15.01 20,13.56 20,12c0,- [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_problem_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_problem_24dp.xml
deleted file mode 100644
index 53a6dcd..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_sync_problem_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,12c0,2.21 0.91,4.2 2.36,5.64L3,20h6v-6l-2.24,2.24C5.68,15.15 5,13.66 5,12c0,-2.61 1.67,-4.83 4,-5.65V4.26C5.55,5.15 3,8.27 3,12zm8,5h2v-2h-2v2zM21,4h-6v6l2.24,-2.24C18.32,8.85 19,10.34 19,12c0,2.61 -1.67,4.83 -4,5.65v2.09c3.45,-0.89 6,-4.01 6,-7.74 0,-2.21 -0.91,-4.2 -2.36,-5.64L21,4zm-10,9h2V7h-2v6z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_system_update_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_system_update_24dp.xml
deleted file mode 100644
index 9f2103e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_system_update_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14zm-1,-6h-3V8h-2v5H8l4,4 4,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_tap_and_play_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_tap_and_play_24dp.xml
deleted file mode 100644
index df5e24a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_tap_and_play_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M2,16v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zm0,4v3h3c0,-1.66 -1.34,-3 -3,-3zm0,-8v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.92,-11 -11,-11zM17,1.01L7,1c-1.1,0 -2,0.9 -2,2v7.37c0.69,0.16 1.36,0.37 2,0.64V5h10v13h-3.03c0.52,1.25 0.84,2.59 0.95,4H17c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_time_to_leave_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_time_to_leave_24dp.xml
deleted file mode 100644
index 8550cf2..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_time_to_leave_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.92,5.01C18.72,4.42 18.16,4 17.5,4h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,11v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,12 6.5,12s1.5,0.67 1.5,1.5S7.33,15 6.5,15zm11,0c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,10l1.5,-4.5h11L19,10H5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_vibration_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_vibration_24dp.xml
deleted file mode 100644
index ce1d2c5..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_vibration_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M0,15h2V9H0v6zm3,2h2V7H3v10zm19,-8v6h2V9h-2zm-3,8h2V7h-2v10zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19H8V5h8v14z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_voice_chat_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_voice_chat_24dp.xml
deleted file mode 100644
index 51c8fb6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_voice_chat_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,2H4c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-2,12l-4,-3.2V14H6V6h8v3.2L18,6v8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_vpn_lock_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_vpn_lock_24dp.xml
deleted file mode 100644
index 8a4cff7..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/notification/ic_vpn_lock_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5V4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1V5c0,-0.55 -0.45,-1 -1,-1zm-0.8,0h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7V4zm-2.28,8c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1H7v-2h2c0.55,0 1,-0.45 1,-1V8h2c1.1,0 2,-0.9 2,-2V3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_cake_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_cake_24dp.xml
deleted file mode 100644
index 19280f1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_cake_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,6c1.11,0 2,-0.9 2,-2 0,-0.38 -0.1,-0.73 -0.29,-1.03L12,0l-1.71,2.97c-0.19,0.3 -0.29,0.65 -0.29,1.03 0,1.1 0.9,2 2,2zm4.6,9.99l-1.07,-1.07 -1.08,1.07c-1.3,1.3 -3.58,1.31 -4.89,0l-1.07,-1.07 -1.09,1.07C6.75,16.64 5.88,17 4.96,17c-0.73,0 -1.4,-0.23 -1.96,-0.61V21c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-4.61c-0.56,0.38 -1.23,0.61 -1.96,0.61 -0.92,0 -1.79,-0.36 -2.44,-1.01zM18,9h-5V7h-2v2H6c-1.66,0 -3,1.34 -3,3v1.54c0,1.08 0.88,1.96 1.96,1.96 0.52,0 1.02,-0.2 1 [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_domain_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_domain_24dp.xml
deleted file mode 100644
index 62bc39f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_domain_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,7V3H2v18h20V7H12zM6,19H4v-2h2v2zm0,-4H4v-2h2v2zm0,-4H4V9h2v2zm0,-4H4V5h2v2zm4,12H8v-2h2v2zm0,-4H8v-2h2v2zm0,-4H8V9h2v2zm0,-4H8V5h2v2zm10,12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2,-8h-2v2h2v-2zm0,4h-2v2h2v-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_group_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_group_24dp.xml
deleted file mode 100644
index 8b0643f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_group_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zm-8,0c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zm0,2c-2.33,0 -7,1.17 -7,3.5V19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zm8,0c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45V19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_group_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_group_add_24dp.xml
deleted file mode 100644
index ba42e71..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_group_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M8,10H5V7H3v3H0v2h3v3h2v-3h3v-2zm10,1c1.66,0 2.99,-1.34 2.99,-3S19.66,5 18,5c-0.32,0 -0.63,0.05 -0.91,0.14 0.57,0.81 0.9,1.79 0.9,2.86s-0.34,2.04 -0.9,2.86c0.28,0.09 0.59,0.14 0.91,0.14zm-5,0c1.66,0 2.99,-1.34 2.99,-3S14.66,5 13,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zm6.62,2.16c0.83,0.73 1.38,1.66 1.38,2.84v2h3v-2c0,-1.54 -2.37,-2.49 -4.38,-2.84zM13,13c-2,0 -6,1 -6,3v2h12v-2c0,-2 -4,-3 -6,-3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_location_city_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_location_city_24dp.xml
deleted file mode 100644
index 0b0e88f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_location_city_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,11V5l-3,-3 -3,3v2H3v14h18V11h-6zm-8,8H5v-2h2v2zm0,-4H5v-2h2v2zm0,-4H5V9h2v2zm6,8h-2v-2h2v2zm0,-4h-2v-2h2v2zm0,-4h-2V9h2v2zm0,-4h-2V5h2v2zm6,12h-2v-2h2v2zm0,-4h-2v-2h2v2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_24dp.xml
deleted file mode 100644
index 1ef868a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_mood_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zm3.5,-9c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zm-7,0c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zm3.5,6.5c2.33,0 4.31,-1.46 5.11,-3.5H6.89c0.8,2.04 2.78,3.5 5.11,3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_24dp.xml
deleted file mode 100644
index 8902d2a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_none_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_none_24dp.xml
deleted file mode 100644
index 6fbff40..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_none_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2zm-2,1H7v-6.5C7,8.01 9.01,6 11.5,6S16,8.01 16,10.5V17z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_off_24dp.xml
deleted file mode 100644
index 86c52d9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,10.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-0.51,0.12 -0.99,0.32 -1.45,0.56L18,14.18V10.5zm-0.27,8.5l2,2L21,19.73 4.27,3 3,4.27l2.92,2.92C5.34,8.16 5,9.29 5,10.5V16l-2,2v1h14.73z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_on_24dp.xml
deleted file mode 100644
index d484b8a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M6.58,3.58L5.15,2.15C2.76,3.97 1.18,6.8 1.03,10h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10h2c-0.15,-3.2 -1.73,-6.03 -4.13,-7.85l-1.43,1.43c2.05,1.45 3.41,3.77 3.56,6.42zm-1.97,0.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2v-5.5zM11.5,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.13 1.19,-0.58 1.44,-1.18 0.1,-0.24 0.16,-0.5 0.16,-0.78h-4c0,1.1 0.9,2 2,2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_paused_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_paused_24dp.xml
deleted file mode 100644
index 5a4d741..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_notifications_paused_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2zm-4,-6.2l-2.8,3.4H14V15H9v-1.8l2.8,-3.4H9V8h5v1.8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_pages_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_pages_24dp.xml
deleted file mode 100644
index 65b27bf..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_pages_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M3,5v6h5L7,7l4,1V3H5c-1.1,0 -2,0.9 -2,2zm5,8H3v6c0,1.1 0.9,2 2,2h6v-5l-4,1 1,-4zm9,4l-4,-1v5h6c1.1,0 2,-0.9 2,-2v-6h-5l1,4zm2,-14h-6v5l4,-1 -1,4h5V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_party_mode_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_party_mode_24dp.xml
deleted file mode 100644
index cfb41d9..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_party_mode_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20,4h-3.17L15,2H9L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zm-8,3c1.63,0 3.06,0.79 3.98,2H12c-1.66,0 -3,1.34 -3,3 0,0.35 0.07,0.69 0.18,1H7.1c-0.06,-0.32 -0.1,-0.66 -0.1,-1 0,-2.76 2.24,-5 5,-5zm0,10c-1.63,0 -3.06,-0.79 -3.98,-2H12c1.66,0 3,-1.34 3,-3 0,-0.35 -0.07,-0.69 -0.18,-1h2.08c0.07,0.32 0.1,0.66 0.1,1 0,2.76 -2.24,5 -5,5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_people_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_people_24dp.xml
deleted file mode 100644
index 8b0643f..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_people_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zm-8,0c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zm0,2c-2.33,0 -7,1.17 -7,3.5V19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zm8,0c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45V19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_people_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_people_outline_24dp.xml
deleted file mode 100644
index 6cea149..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_people_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M16.5,13c-1.2,0 -3.07,0.34 -4.5,1 -1.43,-0.67 -3.3,-1 -4.5,-1C5.33,13 1,14.08 1,16.25V19h22v-2.75c0,-2.17 -4.33,-3.25 -6.5,-3.25zm-4,4.5h-10v-1.25c0,-0.54 2.56,-1.75 5,-1.75s5,1.21 5,1.75v1.25zm9,0H14v-1.25c0,-0.46 -0.2,-0.86 -0.52,-1.22 0.88,-0.3 1.96,-0.53 3.02,-0.53 2.44,0 5,1.21 5,1.75v1.25zM7.5,12c1.93,0 3.5,-1.57 3.5,-3.5S9.43,5 7.5,5 4,6.57 4,8.5 5.57,12 7.5,12zm0,-5.5c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zm9,5.5c1.93,0 3.5,-1.57 3.5,-3.5S [...]
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_24dp.xml
deleted file mode 100644
index 587e14c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zm0,2c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_add_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_add_24dp.xml
deleted file mode 100644
index e43603c..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_add_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zm-9,-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9,4c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_outline_24dp.xml
deleted file mode 100644
index a34bd73..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_person_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1H5.9V17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zm0,9c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_plus_one_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_plus_one_24dp.xml
deleted file mode 100644
index 9b8344a..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_plus_one_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M10,8H8v4H4v2h4v4h2v-4h4v-2h-4zm4.5,-1.92V7.9l2.5,-0.5V18h2V5z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_poll_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_poll_24dp.xml
deleted file mode 100644
index 1d393b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_poll_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM9,17H7v-7h2v7zm4,0h-2V7h2v10zm4,0h-2v-4h2v4z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_public_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_public_24dp.xml
deleted file mode 100644
index d6e43bb..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_public_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm-1,17.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zm6.9,-2.54c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1H8v-2h2c0.55,0 1,-0.45 1,-1V7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_school_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_school_24dp.xml
deleted file mode 100644
index 4ed1f28..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_school_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5,13.18v4L12,21l7,-3.82v-4L12,17l-7,-3.82zM12,3L1,9l11,6 9,-4.91V17h2V9L12,3z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_share_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_share_24dp.xml
deleted file mode 100644
index 737da72..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_share_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_whatshot_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/social/ic_whatshot_24dp.xml
deleted file mode 100644
index b2dd494..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/social/ic_whatshot_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.5,0.67s0.74,2.65 0.74,4.8c0,2.06 -1.35,3.73 -3.41,3.73 -2.07,0 -3.63,-1.67 -3.63,-3.73l0.03,-0.36C5.21,7.51 4,10.62 4,14c0,4.42 3.58,8 8,8s8,-3.58 8,-8C20,8.61 17.41,3.8 13.5,0.67zM11.71,19c-1.78,0 -3.22,-1.4 -3.22,-3.14 0,-1.62 1.05,-2.76 2.81,-3.12 1.77,-0.36 3.6,-1.21 4.62,-2.58 0.39,1.29 0.59,2.65 0.59,4.04 0,2.65 -2.15,4.8 -4.8,4.8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/test_path_01.xml b/base/asset-studio/src/main/java/images/material_design_icons/test_path_01.xml
deleted file mode 100644
index 5ec99ab..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/test_path_01.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="800dp"
- android:height="800dp"
- android:viewportWidth="800.0"
- android:viewportHeight="800.0">
- <path
- android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0t-200,299"
- android:fillColor="#00000000"
- android:strokeColor="#FF0000"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/test_path_02.xml b/base/asset-studio/src/main/java/images/material_design_icons/test_path_02.xml
deleted file mode 100644
index 7db7f33..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/test_path_02.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="800dp"
- android:height="800dp"
- android:viewportWidth="800.0"
- android:viewportHeight="800.0">
- <path
- android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0T-200,299"
- android:fillColor="#00000000"
- android:strokeColor="#FF0000"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/test_qt_01.xml b/base/asset-studio/src/main/java/images/material_design_icons/test_qt_01.xml
deleted file mode 100644
index 3d1e007..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/test_qt_01.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="800dp"
- android:height="600dp"
- android:viewportWidth="800.0"
- android:viewportHeight="600.0">
- <path
- android:pathData="m0,200q200,250 200,0t200,0Q500,400,600,200T800,200"
- android:fillColor="#000000"
- android:strokeColor="#000000"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_24dp.xml
deleted file mode 100644
index 7c34d3e..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2V5c0,-1.1 -0.89,-2 -2,-2zm-9,14l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_outline_blank_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_outline_blank_24dp.xml
deleted file mode 100644
index ce7d078..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_check_box_outline_blank_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_off_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_off_24dp.xml
deleted file mode 100644
index 3ea75d6..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_off_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_on_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_on_24dp.xml
deleted file mode 100644
index 7eb8099..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_radio_button_on_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zm0,-5C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_24dp.xml
deleted file mode 100644
index e2c53b1..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_half_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_half_24dp.xml
deleted file mode 100644
index 80f11db..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_half_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,9.74l-7.19,-0.62L12,2.5 9.19,9.13 2,9.74l5.46,4.73 -1.64,7.03L12,17.77l6.18,3.73 -1.63,-7.03L22,9.74zM12,15.9V6.6l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.9z"/>
-</vector>
diff --git a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_outline_24dp.xml b/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_outline_24dp.xml
deleted file mode 100644
index 4ca7ae3..0000000
--- a/base/asset-studio/src/main/java/images/material_design_icons/toggle/ic_star_outline_24dp.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
-</vector>
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java
deleted file mode 100644
index df682b1..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Options for aapt.
- */
-public interface AaptOptions {
- /**
- * Returns the value for the --ignore-assets option, or null
- */
- String getIgnoreAssets();
-
- /**
- * Returns the list of values for the -0 (disabled compression) option, or null
- */
- Collection<String> getNoCompress();
-
- /**
- * passes the --error-on-missing-config-entry parameter to the aapt command, by default false.
- */
- boolean getFailOnMissingConfigEntry();
-
- /**
- * Returns the list of additional parameters to pass.
- * @return
- */
- List<String> getAdditionalParameters();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java
deleted file mode 100644
index 4350079..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * An Android Artifact.
- *
- * This is the entry point for the output of a {@link Variant}. This can be more than one
- * output in the case of multi-apk where more than one APKs are generated from the same set
- * of sources.
- *
- */
-public interface AndroidArtifact extends BaseArtifact {
-
- @NonNull
- Collection<AndroidArtifactOutput> getOutputs();
-
- /**
- * Returns whether the output file is signed. This is always false for the main artifact
- * of a library project.
- *
- * @return true if the app is signed.
- */
- boolean isSigned();
-
- /**
- * Returns the name of the {@link SigningConfig} used for the signing. If none are setup or
- * if this is the main artifact of a library project, then this is null.
- *
- * @return the name of the setup signing config.
- */
- @Nullable
- String getSigningConfigName();
-
- /**
- * Returns the application id of this artifact.
- *
- * @return the application id.
- */
- @NonNull
- String getApplicationId();
-
- /**
- * Returns the name of the task used to generate the source code. The actual value might
- * depend on the build system front end.
- *
- * @return the name of the code generating task.
- */
- @NonNull
- String getSourceGenTaskName();
-
- /**
- * Returns all the source folders that are generated. This is typically folders for the R,
- * the aidl classes, and the renderscript classes.
- *
- * Deprecated, as of 1.2, present in super interface.
- *
- * @return a list of folders.
- */
- @NonNull
- @Override
- Collection<File> getGeneratedSourceFolders();
-
- /**
- * Returns all the resource folders that are generated. This is typically the renderscript
- * output and the merged resources.
- *
- * @return a list of folder.
- */
- @NonNull
- Collection<File> getGeneratedResourceFolders();
-
- /**
- * Returns the ABI filters associated with the artifact, or null if there are no filters.
- *
- * If the list contains values, then the artifact only contains these ABIs and excludes
- * others.
- */
- @Nullable
- Set<String> getAbiFilters();
-
- /**
- * Returns the native libraries associated with the artifact.
- */
- @Nullable
- Collection<NativeLibrary> getNativeLibraries();
-
- /**
- * Map of Build Config Fields where the key is the field name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- @NonNull
- Map<String, ClassField> getBuildConfigFields();
-
- /**
- * Map of generated res values where the key is the res name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- @NonNull
- Map<String, ClassField> getResValues();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
deleted file mode 100644
index 068f365..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.io.File;
-import java.util.Collection;
-
-/**
- * Entry point for the model of the Android Projects. This models a single module, whether
- * the module is an app project or a library project.
- */
-public interface AndroidProject {
- // Injectable properties to use with -P
- // Sent by Studio 1.0 ONLY
- String PROPERTY_BUILD_MODEL_ONLY = "android.injected.build.model.only";
- // Sent by Studio 1.1+
- String PROPERTY_BUILD_MODEL_ONLY_ADVANCED = "android.injected.build.model.only.advanced";
-
- String PROPERTY_INVOKED_FROM_IDE = "android.injected.invoked.from.ide";
-
- String PROPERTY_SIGNING_STORE_FILE = "android.injected.signing.store.file";
- String PROPERTY_SIGNING_STORE_PASSWORD = "android.injected.signing.store.password";
- String PROPERTY_SIGNING_KEY_ALIAS = "android.injected.signing.key.alias";
- String PROPERTY_SIGNING_KEY_PASSWORD = "android.injected.signing.key.password";
- String PROPERTY_SIGNING_STORE_TYPE = "android.injected.signing.store.type";
-
- String PROPERTY_APK_LOCATION = "android.injected.apk.location";
-
- String ARTIFACT_MAIN = "_main_";
- String ARTIFACT_ANDROID_TEST = "_android_test_";
- String ARTIFACT_UNIT_TEST = "_unit_test_";
-
- String FD_INTERMEDIATES = "intermediates";
- String FD_OUTPUTS = "outputs";
- String FD_GENERATED = "generated";
-
-
- /**
- * Returns the model version. This is a string in the format X.Y.Z
- *
- * @return a string containing the model version.
- */
- @NonNull
- String getModelVersion();
-
- /**
- * Returns the model api version.
- * <p/>
- * This is different from {@link #getModelVersion()} in a way that new model
- * version might increment model version but keep existing api. That means that
- * code which was built against particular 'api version' might be safely re-used for all
- * new model versions as long as they don't change the api.
- * <p/>
- * Every new model version is assumed to return an 'api version' value which
- * is equal or greater than the value used by the previous model version.
- *
- * @return model's api version
- */
- int getApiVersion();
-
- /**
- * Returns the name of the module.
- *
- * @return the name of the module.
- */
- @NonNull
- String getName();
-
- /**
- * Returns whether this is a library.
- * @return true for a library module.
- */
- boolean isLibrary();
-
- /**
- * Returns the {@link ProductFlavorContainer} for the 'main' default config.
- *
- * @return the product flavor.
- */
- @NonNull
- ProductFlavorContainer getDefaultConfig();
-
- /**
- * Returns a list of all the {@link BuildType} in their container.
- *
- * @return a list of build type containers.
- */
- @NonNull
- Collection<BuildTypeContainer> getBuildTypes();
-
- /**
- * Returns a list of all the {@link ProductFlavor} in their container.
- *
- * @return a list of product flavor containers.
- */
- @NonNull
- Collection<ProductFlavorContainer> getProductFlavors();
-
- /**
- * Returns a list of all the variants.
- *
- * This does not include test variant. Test variants are additional artifacts in their
- * respective variant info.
- *
- * @return a list of the variants.
- */
- @NonNull
- Collection<Variant> getVariants();
-
- /**
- * Returns a list of all the flavor dimensions, may be empty.
- *
- * @return a list of the flavor dimensions.
- */
- @NonNull
- Collection<String> getFlavorDimensions();
-
- /**
- * Returns a list of extra artifacts meta data. This does not include the main artifact.
- *
- * @return a list of extra artifacts
- */
- @NonNull
- Collection<ArtifactMetaData> getExtraArtifacts();
-
- /**
- * Returns the compilation target as a string. This is the full extended target hash string.
- * (see com.android.sdklib.IAndroidTarget#hashString())
- *
- * @return the target hash string
- */
- @NonNull
- String getCompileTarget();
-
- /**
- * Returns the boot classpath matching the compile target. This is typically android.jar plus
- * other optional libraries.
- *
- * @return a list of jar files.
- */
- @NonNull
- Collection<String> getBootClasspath();
-
- /**
- * Returns a list of folders or jar files that contains the framework source code.
- */
- @NonNull
- Collection<File> getFrameworkSources();
-
- /**
- * Returns the collection of toolchains used to create any native libraries.
- *
- * @return collection of toolchains.
- */
- @NonNull
- Collection<NativeToolchain> getNativeToolchains();
-
- /**
- * Returns a list of {@link SigningConfig}.
- */
- @NonNull
- Collection<SigningConfig> getSigningConfigs();
-
- /**
- * Returns the aapt options.
- */
- @NonNull
- AaptOptions getAaptOptions();
-
- /**
- * Returns the lint options.
- */
- @NonNull
- LintOptions getLintOptions();
-
- /**
- * Returns the dependencies that were not successfully resolved. The returned list gets
- * populated only if the system property {@link #PROPERTY_BUILD_MODEL_ONLY} has been
- * set to {@code true}.
- * <p>
- * Each value of the collection has the format group:name:version, for example:
- * com.google.guava:guava:15.0.2
- *
- * @return the dependencies that were not successfully resolved.
- * @deprecated use {@link #getSyncIssues()}
- */
- @Deprecated
- @NonNull
- Collection<String> getUnresolvedDependencies();
-
- /**
- * Returns issues found during sync. The returned list gets
- * populated only if the system property {@link #PROPERTY_BUILD_MODEL_ONLY} has been
- * set to {@code true}.
- */
- @NonNull
- Collection<SyncIssue> getSyncIssues();
-
- /**
- * Returns the compile options for Java code.
- */
- @NonNull
- JavaCompileOptions getJavaCompileOptions();
-
- /**
- * Returns the build folder of this project.
- */
- @NonNull
- File getBuildFolder();
-
- /**
- * Returns the resource prefix to use, if any. This is an optional prefix which can
- * be set and which is used by the defaults to automatically choose new resources
- * with a certain prefix, warn if resources are not using the given prefix, etc.
- * This helps work with resources in the app namespace where there could otherwise
- * be unintentional duplicated resource names between unrelated libraries.
- *
- * @return the optional resource prefix, or null if not set
- */
- @Nullable
- String getResourcePrefix();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java
deleted file mode 100644
index aa6da40..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Base config object for Build Type and Product flavor.
- */
-public interface BaseConfig {
-
- @NonNull
- String getName();
-
- /**
- * Map of Build Config Fields where the key is the field name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- @NonNull
- Map<String, ClassField> getBuildConfigFields();
-
- /**
- * Map of generated res values where the key is the res name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- @NonNull
- Map<String, ClassField> getResValues();
-
- /**
- * Returns the collection of proguard rule files.
- *
- * <p>These files are only applied to the production code.
- *
- * @return a non-null collection of files.
- * @see #getTestProguardFiles()
- */
- @NonNull
- Collection<File> getProguardFiles();
-
- /**
- * Returns the collection of proguard rule files for consumers of the library to use.
- *
- * @return a non-null collection of files.
- */
- @NonNull
- Collection<File> getConsumerProguardFiles();
-
- /**
- * Returns the collection of proguard rule files to use for the test APK.
- *
- * @return a non-null collection of files.
- */
- @NonNull
- Collection<File> getTestProguardFiles();
-
- /**
- * Returns the map of key value pairs for placeholder substitution in the android manifest file.
- *
- * This map will be used by the manifest merger.
- * @return the map of key value pairs.
- */
- @NonNull
- Map<String, Object> getManifestPlaceholders();
-
- /**
- * Returns whether multi-dex is enabled.
- *
- * This can be null if the flag is not set, in which case the default value is used.
- */
- @Nullable
- Boolean getMultiDexEnabled();
-
- @Nullable
- File getMultiDexKeepFile();
-
- @Nullable
- File getMultiDexKeepProguard();
-
- /**
- * Returns the optional jarjar rule files, or empty if jarjar should be skipped.
- * If more than one file is provided, the rule files will be merged in order with last one
- * win in case of rule redefinition.
- * Can only be used with Jack toolchain.
- * @return the optional jarjar rule file.
- */
- @NonNull
- List<File> getJarJarRuleFiles();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java
deleted file mode 100644
index 0ae6ca3..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-/**
- * a Build Type. This is only the configuration of the build type.
- *
- * It does not include the sources or the dependencies. Those are available on the container
- * or in the artifact info.
- *
- * @see BuildTypeContainer
- * @see AndroidArtifact#getDependencies()
- */
-public interface BuildType extends BaseConfig {
-
- /**
- * Returns the name of the build type.
- *
- * @return the name of the build type.
- */
- @Override
- @NonNull
- String getName();
-
- /**
- * Returns whether the build type is configured to generate a debuggable apk.
- *
- * @return true if the apk is debuggable
- */
- boolean isDebuggable();
-
- /**
- * Returns whether the build type is configured to be build with support for code coverage.
- *
- * @return true if code coverage is enabled.
- */
- boolean isTestCoverageEnabled();
-
- /**
- * Returns whether the build type is configured to be build with support for pseudolocales.
- *
- * @return true if code coverage is enabled.
- */
- boolean isPseudoLocalesEnabled();
-
- /**
- * Returns whether the build type is configured to generate an apk with debuggable native code.
- *
- * @return true if the apk is debuggable
- */
- boolean isJniDebuggable();
-
- /**
- * Returns whether the build type is configured to generate an apk with debuggable
- * renderscript code.
- *
- * @return true if the apk is debuggable
- */
- boolean isRenderscriptDebuggable();
-
- /**
- * Returns the optimization level of the renderscript compilation.
- *
- * @return the optimization level.
- */
- int getRenderscriptOptimLevel();
-
- /**
- * Returns the application id suffix applied to this build type.
- * To get the final application id, use {@link AndroidArtifact#getApplicationId()}.
- *
- * @return the application id
- */
- @Nullable
- String getApplicationIdSuffix();
-
- /**
- * Returns the version name suffix.
- *
- * @return the version name suffix.
- */
- @Nullable
- String getVersionNameSuffix();
-
- /**
- * Returns whether minification is enabled for this build type.
- *
- * @return true if minification is enabled.
- */
- boolean isMinifyEnabled();
-
- /**
- * Return whether zipalign is enabled for this build type.
- *
- * @return true if zipalign is enabled.
- */
- boolean isZipAlignEnabled();
-
- /**
- * Returns whether the variant embeds the micro app.
- */
- boolean isEmbedMicroApp();
-
- /**
- * Returns the associated signing config or null if none are set on the build type.
- */
- @Nullable
- SigningConfig getSigningConfig();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java
deleted file mode 100644
index 8eb3864..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * A Java library.
- */
-public interface JavaLibrary extends Library {
- /**
- * Returns the library's jar file.
- */
- @NonNull
- File getJarFile();
-
- /**
- * Returns the direct dependencies of this library.
- */
- @NonNull
- List<? extends JavaLibrary> getDependencies();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
deleted file mode 100644
index daeffe5..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Options for lint.
- * Example:
- *
- * <pre>
- *
- * android {
- * lintOptions {
- * // set to true to turn off analysis progress reporting by lint
- * quiet true
- * // if true, stop the gradle build if errors are found
- * abortOnError false
- * // set to true to have all release builds run lint on issues with severity=fatal
- * // and abort the build (controlled by abortOnError above) if fatal issues are found
- * checkReleaseBuilds true
- * // if true, only report errors
- * ignoreWarnings true
- * // if true, emit full/absolute paths to files with errors (true by default)
- * //absolutePaths true
- * // if true, check all issues, including those that are off by default
- * checkAllWarnings true
- * // if true, treat all warnings as errors
- * warningsAsErrors true
- * // turn off checking the given issue id's
- * disable 'TypographyFractions','TypographyQuotes'
- * // turn on the given issue id's
- * enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
- * // check *only* the given issue id's
- * check 'NewApi', 'InlinedApi'
- * // if true, don't include source code lines in the error output
- * noLines true
- * // if true, show all locations for an error, do not truncate lists, etc.
- * showAll true
- * // whether lint should include full issue explanations in the text error output
- * explainIssues false
- * // Fallback lint configuration (default severities, etc.)
- * lintConfig file("default-lint.xml")
- * // if true, generate a text report of issues (false by default)
- * textReport true
- * // location to write the output; can be a file or 'stdout' or 'stderr'
- * //textOutput 'stdout'
- * textOutput file("lint-results.txt")
- * // if true, generate an XML report for use by for example Jenkins
- * xmlReport true
- * // file to write report to (if not specified, defaults to lint-results.xml)
- * xmlOutput file("lint-report.xml")
- * // if true, generate an HTML report (with issue explanations, sourcecode, etc)
- * htmlReport true
- * // optional path to report (default will be lint-results.html in the builddir)
- * htmlOutput file("lint-report.html")
- * // Set the severity of the given issues to fatal (which means they will be
- * // checked during release builds (even if the lint target is not included)
- * fatal 'NewApi', 'InlineApi'
- * // Set the severity of the given issues to error
- * error 'Wakelock', 'TextViewEdits'
- * // Set the severity of the given issues to warning
- * warning 'ResourceAsColor'
- * // Set the severity of the given issues to ignore (same as disabling the check)
- * ignore 'TypographyQuotes'
- * }
- * }
- * </pre>
- */
-public interface LintOptions {
- /**
- * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
- * To suppress a given issue, add the lint issue id to the returned set.
- */
- @NonNull
- Set<String> getDisable();
-
- /**
- * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
- * To enable a given issue, add the lint issue id to the returned set.
- */
- @NonNull
- Set<String> getEnable();
-
- /**
- * Returns the exact set of issues to check, or null to run the issues that are enabled
- * by default plus any issues enabled via {@link #getEnable} and without issues disabled
- * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
- */
- @Nullable
- Set<String> getCheck();
-
- /** Whether lint should abort the build if errors are found */
- boolean isAbortOnError();
-
- /**
- * Whether lint should display full paths in the error output. By default the paths
- * are relative to the path lint was invoked from.
- */
- boolean isAbsolutePaths();
-
- /**
- * Whether lint should include the source lines in the output where errors occurred
- * (true by default)
- */
- boolean isNoLines();
-
- /**
- * Returns whether lint should be quiet (for example, not write informational messages
- * such as paths to report files written)
- */
- boolean isQuiet();
-
- /** Returns whether lint should check all warnings, including those off by default */
- boolean isCheckAllWarnings();
-
- /** Returns whether lint will only check for errors (ignoring warnings) */
- boolean isIgnoreWarnings();
-
- /** Returns whether lint should treat all warnings as errors */
- boolean isWarningsAsErrors();
-
- /** Returns whether lint should include explanations for issue errors. (Note that
- * HTML and XML reports intentionally do this unconditionally, ignoring this setting.) */
- boolean isExplainIssues();
-
- /**
- * Returns whether lint should include all output (e.g. include all alternate
- * locations, not truncating long messages, etc.)
- */
- boolean isShowAll();
-
- /**
- * Returns an optional path to a lint.xml configuration file
- */
- @Nullable
- File getLintConfig();
-
- /** Whether we should write an text report. Default false. The location can be
- * controlled by {@link #getTextOutput()}. */
- boolean getTextReport();
-
- /**
- * The optional path to where a text report should be written. The special value
- * "stdout" can be used to point to standard output.
- */
- @Nullable
- File getTextOutput();
-
- /** Whether we should write an HTML report. Default true. The location can be
- * controlled by {@link #getHtmlOutput()}. */
- boolean getHtmlReport();
-
- /** The optional path to where an HTML report should be written */
- @Nullable
- File getHtmlOutput();
-
- /** Whether we should write an XML report. Default true. The location can be
- * controlled by {@link #getXmlOutput()}. */
- boolean getXmlReport();
-
- /** The optional path to where an XML report should be written */
- @Nullable
- File getXmlOutput();
-
- /**
- * Returns whether lint should check for fatal errors during release builds. Default is true.
- * If issues with severity "fatal" are found, the release build is aborted.
- */
- boolean isCheckReleaseBuilds();
-
- /**
- * An optional map of severity overrides. The map maps from issue id's to the corresponding
- * severity to use, which must be "fatal", "error", "warning", or "ignore".
- *
- * @return a map of severity overrides, or null. The severities are one of the constants
- * {@link #SEVERITY_FATAL}, {@link #SEVERITY_ERROR}, {@link #SEVERITY_WARNING},
- * {@link #SEVERITY_INFORMATIONAL}, {@link #SEVERITY_IGNORE}
- */
- @Nullable
- Map<String, Integer> getSeverityOverrides();
-
- /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#FATAL */
- int SEVERITY_FATAL = 1;
- /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#ERROR */
- int SEVERITY_ERROR = 2;
- /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#WARNING */
- int SEVERITY_WARNING = 3;
- /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#INFORMATIONAL */
- int SEVERITY_INFORMATIONAL = 4;
- /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#IGNORE */
- int SEVERITY_IGNORE = 5;
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/NativeToolchain.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/NativeToolchain.java
deleted file mode 100644
index e1c387c..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/NativeToolchain.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-
-import java.io.File;
-
-/**
- * Toolchain for building a native library.
- */
-public interface NativeToolchain {
-
- /**
- * Returns the name of the toolchain.
- *
- * e.g. "x86_64", "arm-linux-androideabi"
- *
- * @return name of the toolchain.
- */
- @NonNull
- String getName();
-
- /**
- * Returns the full path of the C compiler.
- *
- * @return the C compiler path.
- */
- @NonNull
- File getCCompilerExecutable();
-
- /**
- * Returns the full path of the C++ compiler.
- *
- * @return the C++ compiler path.
- */
- @NonNull
- File getCppCompilerExecutable();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java
deleted file mode 100644
index 04f408a..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * a Product Flavor. This is only the configuration of the flavor.
- *
- * It does not include the sources or the dependencies. Those are available on the container
- * or in the artifact info.
- *
- * @see ProductFlavorContainer
- * @see BaseArtifact#getDependencies()
- */
-public interface ProductFlavor extends BaseConfig, DimensionAware {
-
- /**
- * Returns the name of the flavor.
- *
- * @return the name of the flavor.
- */
- @Override
- @NonNull
- String getName();
-
- /**
- * Returns the name of the product flavor. This is only the value set on this product flavor.
- * To get the final application id name, use {@link AndroidArtifact#getApplicationId()}.
- *
- * @return the application id.
- */
- @Nullable
- String getApplicationId();
-
- /**
- * Returns the version code associated with this flavor or null if none have been set.
- * This is only the value set on this product flavor, not necessarily the actual
- * version code used.
- *
- * @return the version code, or null if not specified
- */
- @Nullable
- Integer getVersionCode();
-
- /**
- * Returns the version name. This is only the value set on this product flavor.
- * To get the final value, use {@link Variant#getMergedFlavor()} as well as
- * {@link BuildType#getVersionNameSuffix()}
- *
- * @return the version name.
- */
- @Nullable
- String getVersionName();
-
- /**
- * Returns the minSdkVersion. This is only the value set on this product flavor.
- *
- * @return the minSdkVersion, or null if not specified
- */
- @Nullable
- ApiVersion getMinSdkVersion();
-
- /**
- * Returns the targetSdkVersion. This is only the value set on this product flavor.
- *
- * @return the targetSdkVersion, or null if not specified
- */
- @Nullable
- ApiVersion getTargetSdkVersion();
-
- /**
- * Returns the maxSdkVersion. This is only the value set on this produce flavor.
- *
- * @return the maxSdkVersion, or null if not specified
- */
- @Nullable
- Integer getMaxSdkVersion();
-
- /**
- * Returns the renderscript target api. This is only the value set on this product flavor.
- * TODO: make final renderscript target api available through the model
- *
- * @return the renderscript target api, or null if not specified
- */
- @Nullable
- Integer getRenderscriptTargetApi();
-
- /**
- * Returns whether the renderscript code should be compiled in support mode to
- * make it compatible with older versions of Android.
- *
- * @return true if support mode is enabled, false if not, and null if not specified.
- */
- @Nullable
- Boolean getRenderscriptSupportModeEnabled();
-
- /**
- * Returns whether the renderscript code should be compiled to generate C/C++ bindings.
- * @return true for C/C++ generation, false for Java, null if not specified.
- */
- @Nullable
- Boolean getRenderscriptNdkModeEnabled();
-
- /**
- * Returns the test application id. This is only the value set on this product flavor.
- * To get the final value, use {@link Variant#getExtraAndroidArtifacts()} with
- * {@link AndroidProject#ARTIFACT_ANDROID_TEST} and then
- * {@link AndroidArtifact#getApplicationId()}
- *
- * @return the test package name.
- */
- @Nullable
- String getTestApplicationId();
-
- /**
- * Returns the test instrumentation runner. This is only the value set on this product flavor.
- * TODO: make test instrumentation runner available through the model.
- *
- * @return the test package name.
- */
- @Nullable
- String getTestInstrumentationRunner();
-
- /**
- * Returns the arguments for the test instrumentation runner.
- */
- @NonNull
- Map<String, String> getTestInstrumentationRunnerArguments();
-
- /**
- * Returns the handlingProfile value. This is only the value set on this product flavor.
- *
- * @return the handlingProfile value.
- */
- @Nullable
- Boolean getTestHandleProfiling();
-
- /**
- * Returns the functionalTest value. This is only the value set on this product flavor.
- *
- * @return the functionalTest value.
- */
- @Nullable
- Boolean getTestFunctionalTest();
-
- /**
- * Returns the resource configuration for this variant.
- *
- * This is the list of -c parameters for aapt.
- *
- * @return the resource configuration options.
- */
- @NonNull
- Collection<String> getResourceConfigurations();
-
- /**
- * Returns the associated signing config or null if none are set on the product flavor.
- */
- @Nullable
- SigningConfig getSigningConfig();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/SyncIssue.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/SyncIssue.java
deleted file mode 100644
index 85f0d1e..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/SyncIssue.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-
-/**
- * Class representing a sync issue.
- * The goal is to make these issues not fail the sync but instead report them at the end
- * of a successful sync.
- */
-public interface SyncIssue {
- int SEVERITY_WARNING = 1;
- int SEVERITY_ERROR = 2;
-
- int TYPE_NONE = 0;
-
- // data is expiration data
- int TYPE_PLUGIN_OBSOLETE = 1;
-
- // data is dependency coordinate
- int TYPE_UNRESOLVED_DEPENDENCY = 2;
-
- // data is dependency coordinate
- int TYPE_DEPENDENCY_IS_APK = 3;
-
- // data is dependency coordinate
- int TYPE_DEPENDENCY_IS_APKLIB = 4;
-
- // data is local file
- int TYPE_NON_JAR_LOCAL_DEP = 5;
-
- // data is dependency coordinate/path
- int TYPE_NON_JAR_PACKAGE_DEP = 6;
-
- // data is dependency coordinate/path
- int TYPE_NON_JAR_PROVIDED_DEP = 7;
-
- // data is dependency coordinate/path
- int TYPE_JAR_DEPEND_ON_AAR = 8;
-
- /**
- * Mismatch dependency version between tested and test
- * app. Data is dep coordinate without the version (groupId:artifactId)
- */
- int TYPE_MISMATCH_DEP = 9;
-
- // data is dependency coordinate
- int TYPE_OPTIONAL_LIB_NOT_FOUND = 10;
-
- int TYPE_MAX = 11; // increment when adding new types.
-
- /**
- * Returns the severity of the issue.
- */
- int getSeverity();
-
- /**
- * Returns the type of the issue.
- */
- int getType();
-
- /**
- * Returns the data of the issue.
- *
- * This is a machine-readable string used by the IDE for known issue types.
- */
- @NonNull
- String getData();
-
- /**
- * Returns the a user-readable message for the issue.
- *
- * This is used by IDEs that do not recognize the issue type (ie older IDE released before
- * the type was added to the plugin).
- */
- @NonNull
- String getMessage();
-}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java b/base/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java
deleted file mode 100644
index c285897..0000000
--- a/base/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.model;
-
-import com.android.annotations.NonNull;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * A build Variant.
- *
- * This is the combination of a Build Type and 0+ Product Flavors (exactly one for each existing
- * Flavor Dimension).
- *
- * Build Types and Flavors both contribute source folders, so this Variant is the direct
- * representation of a set of source folders (and configuration parameters) used to build something.
- *
- * However the output of a Variant is not a single item.
- *
- * First there can be several artifacts.
- * - Main Artifact: this is the main Android output(s). The app or the library being generated.
- * - Extra Android Artifacts: these are ancillary artifacts, most likely test app(s).
- * - Extra Java artifacts: these are pure-Java ancillary artifacts (junit support for instance).
- */
-public interface Variant {
-
- /**
- * Returns the name of the variant. It is made up of the build type and flavors (if applicable)
- *
- * @return the name of the variant.
- */
- @NonNull
- String getName();
-
- /**
- * Returns a display name for the variant. It is made up of the build type and flavors
- * (if applicable)
- *
- * @return the name.
- */
- @NonNull
- String getDisplayName();
-
- /**
- * Returns the main artifact for this variant.
- *
- * @return the artifact.
- */
- @NonNull
- AndroidArtifact getMainArtifact();
-
- @NonNull
- Collection<AndroidArtifact> getExtraAndroidArtifacts();
-
- @NonNull
- Collection<JavaArtifact> getExtraJavaArtifacts();
-
- /**
- * Returns the build type. All variants have a build type, so this is never null.
- *
- * @return the name of the build type.
- */
- @NonNull
- String getBuildType();
-
- /**
- * Returns the flavors for this variants. This can be empty if no flavors are configured.
- *
- * @return a list of flavors which can be empty.
- */
- @NonNull
- List<String> getProductFlavors();
-
- /**
- * The result of the merge of all the flavors and of the main default config. If no flavors
- * are defined then this is the same as the default config.
- *
- * This is directly a ProductFlavor instance of a ProductFlavorContainer since this a composite
- * of existing ProductFlavors.
- *
- * @return the merged flavors.
- *
- * @see AndroidProject#getDefaultConfig()
- */
- @NonNull
- ProductFlavor getMergedFlavor();
-}
diff --git a/base/build-system/builder/build.gradle b/base/build-system/builder/build.gradle
deleted file mode 100644
index 050a4a1..0000000
--- a/base/build-system/builder/build.gradle
+++ /dev/null
@@ -1,105 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'clone-artifacts'
-apply plugin: 'jacoco'
-
-evaluationDependsOn(':base:builder-model')
-evaluationDependsOn(':base:builder-test-api')
-
-dependencies {
- compile project(':base:builder-model')
- compile project(':base:builder-test-api')
-
- compile project(':base:sdklib')
- compile project(':base:sdk-common')
- compile project(':base:common')
- compile project(':base:manifest-merger')
- compile project(':base:ddmlib')
-
- compile project(':base:jack:jack-api')
- compile project(':base:jack:jill-api')
-
- compile 'com.squareup:javawriter:2.5.0'
- compile 'org.bouncycastle:bcpkix-jdk15on:1.48'
- compile 'org.bouncycastle:bcprov-jdk15on:1.48'
- compile 'org.ow2.asm:asm:5.0.3'
- compile 'org.ow2.asm:asm-tree:5.0.3'
-
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-all:1.9.5'
- testCompile project(':base:testutils')
- testCompile project(':base:sdklib').sourceSets.test.output
-}
-
-test {
- maxParallelForks = Runtime.runtime.availableProcessors() / 2
-}
-
-group = 'com.android.tools.build'
-archivesBaseName = 'builder'
-version = rootProject.ext.buildVersion
-
-project.ext.pomName = 'Android Builder library'
-project.ext.pomDesc = 'Library to build Android applications.'
-
-apply from: "$rootDir/buildSrc/base/publish.gradle"
-apply from: "$rootDir/buildSrc/base/bintray.gradle"
-apply from: "$rootDir/buildSrc/base/javadoc.gradle"
-
-jar.manifest.attributes("Builder-Version": version)
-
-def generated = new File("${project.buildDir}/generated/java")
-
-sourceSets {
- main {
- java {
- srcDir generated
- }
- }
-}
-
-task generateVersionConstantsJava {
- inputs.property("apiVersion", apiVersion)
- inputs.property("version", version)
- ext.versionFile = new File(generated, "com/android/builder/Version.java")
- outputs.file(versionFile)
-}
-generateVersionConstantsJava << {
- versionFile.parentFile.mkdirs()
- versionFile.text = """
-package com.android.builder;
-
-public final class Version {
- private Version() {}
- public static final String ANDROID_GRADLE_PLUGIN_VERSION = "$version";
- public static final int BUILDER_MODEL_API_VERSION = $apiVersion;
-}
-"""
-}
-
-tasks.compileJava.dependsOn generateVersionConstantsJava
-
-configurations {
- provided
- sourcesProvided
-}
-
-dependencies {
- provided(project(':base:profile')) {
- transitive = false
- }
- sourcesProvided(project(path:':base:profile', configuration:'sourcesOnly')) {
- transitive = false
- }
-}
-
-sourceSets.main.compileClasspath += [configurations.provided]
-sourceSets.test.runtimeClasspath += [configurations.provided]
-tasks.compileJava.dependsOn(configurations.provided)
-tasks.sourcesJar.dependsOn(configurations.sourcesProvided)
-
-tasks.jar {
- from({zipTree(configurations.provided.singleFile)})
-}
-tasks.sourcesJar {
- from({zipTree(configurations.sourcesProvided.singleFile)})
-}
\ No newline at end of file
diff --git a/base/build-system/builder/builder.iml b/base/build-system/builder/builder.iml
deleted file mode 100644
index 0484483..0000000
--- a/base/build-system/builder/builder.iml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module relativePaths="true" type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$/../../../../out/build/base/builder/build/generated/java">
- <sourceFolder url="file://$MODULE_DIR$/../../../../out/build/base/builder/build/generated/java" isTestSource="false" />
- </content>
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module" module-name="profile" exported="" />
- <orderEntry type="module" module-name="builder-test-api" exported="" />
- <orderEntry type="module" module-name="builder-model" exported="" />
- <orderEntry type="module" module-name="testutils" scope="TEST" />
- <orderEntry type="module" module-name="ddmlib" exported="" />
- <orderEntry type="library" exported="" name="javawriter" level="project" />
- <orderEntry type="library" exported="" name="bouncy-castle" level="project" />
- <orderEntry type="library" name="mockito" level="project" />
- <orderEntry type="module" module-name="sdk-common-base" />
- <orderEntry type="module" module-name="manifest-merger-base" />
- <orderEntry type="library" exported="" name="asm-tools" level="project" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="module" module-name="assetstudio-base" />
- <orderEntry type="module" module-name="jack-api" exported="" />
- <orderEntry type="module" module-name="jill-api" exported="" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/AaptPackageProcessBuilder.java b/base/build-system/builder/src/main/java/com/android/builder/core/AaptPackageProcessBuilder.java
deleted file mode 100644
index b64fb82..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/AaptPackageProcessBuilder.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.dependency.SymbolFileProvider;
-import com.android.builder.model.AaptOptions;
-import com.android.ide.common.process.ProcessEnvBuilder;
-import com.android.ide.common.process.ProcessInfo;
-import com.android.ide.common.process.ProcessInfoBuilder;
-import com.android.resources.Density;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.utils.ILogger;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Builds the ProcessInfo necessary for an aapt package invocation
- */
-public class AaptPackageProcessBuilder extends ProcessEnvBuilder<AaptPackageProcessBuilder> {
-
- @NonNull private final File mManifestFile;
- @NonNull private final AaptOptions mOptions;
- @Nullable private File mResFolder;
- @Nullable private File mAssetsFolder;
- private boolean mVerboseExec = false;
- @Nullable private String mSourceOutputDir;
- @Nullable private String mSymbolOutputDir;
- @Nullable private List<? extends SymbolFileProvider> mLibraries;
- @Nullable private String mResPackageOutput;
- @Nullable private String mProguardOutput;
- @Nullable private VariantType mType;
- private boolean mDebuggable = false;
- private boolean mPseudoLocalesEnabled = false;
- @Nullable private Collection<String> mResourceConfigs;
- @Nullable Collection<String> mSplits;
- @Nullable String mPackageForR;
- @Nullable String mPreferredDensity;
-
- /**
- *
- * @param manifestFile the location of the manifest file
- * @param options the {@link com.android.builder.model.AaptOptions}
- */
- public AaptPackageProcessBuilder(
- @NonNull File manifestFile,
- @NonNull AaptOptions options) {
- checkNotNull(manifestFile, "manifestFile cannot be null.");
- checkNotNull(options, "options cannot be null.");
- mManifestFile = manifestFile;
- mOptions = options;
- }
-
- @NonNull
- public File getManifestFile() {
- return mManifestFile;
- }
-
- /**
- * @param resFolder the merged res folder
- * @return itself
- */
- public AaptPackageProcessBuilder setResFolder(@NonNull File resFolder) {
- if (!resFolder.isDirectory()) {
- throw new RuntimeException("resFolder parameter is not a directory");
- }
- mResFolder = resFolder;
- return this;
- }
-
- /**
- * @param assetsFolder the merged asset folder
- * @return itself
- */
- public AaptPackageProcessBuilder setAssetsFolder(@NonNull File assetsFolder) {
- if (!assetsFolder.isDirectory()) {
- throw new RuntimeException("assetsFolder parameter is not a directory");
- }
- mAssetsFolder = assetsFolder;
- return this;
- }
-
- /**
- * @param sourceOutputDir optional source folder to generate R.java
- * @return itself
- */
- public AaptPackageProcessBuilder setSourceOutputDir(@Nullable String sourceOutputDir) {
- mSourceOutputDir = sourceOutputDir;
- return this;
- }
-
- @Nullable
- public String getSourceOutputDir() {
- return mSourceOutputDir;
- }
-
- /**
- * @param symbolOutputDir the folder to write symbols into
- * @ itself
- */
- public AaptPackageProcessBuilder setSymbolOutputDir(@Nullable String symbolOutputDir) {
- mSymbolOutputDir = symbolOutputDir;
- return this;
- }
-
- @Nullable
- public String getSymbolOutputDir() {
- return mSymbolOutputDir;
- }
-
- /**
- * @param libraries the flat list of libraries
- * @return itself
- */
- public AaptPackageProcessBuilder setLibraries(
- @NonNull List<? extends SymbolFileProvider> libraries) {
- mLibraries = libraries;
- return this;
- }
-
- @NonNull
- public List<? extends SymbolFileProvider> getLibraries() {
- return mLibraries == null ? ImmutableList.<SymbolFileProvider>of() : mLibraries;
- }
-
- /**
- * @param resPackageOutput optional filepath for packaged resources
- * @return itself
- */
- public AaptPackageProcessBuilder setResPackageOutput(@Nullable String resPackageOutput) {
- mResPackageOutput = resPackageOutput;
- return this;
- }
-
- /**
- * @param proguardOutput optional filepath for proguard file to generate
- * @return itself
- */
- public AaptPackageProcessBuilder setProguardOutput(@Nullable String proguardOutput) {
- mProguardOutput = proguardOutput;
- return this;
- }
-
- /**
- * @param type the type of the variant being built
- * @return itself
- */
- public AaptPackageProcessBuilder setType(@NonNull VariantType type) {
- this.mType = type;
- return this;
- }
-
- @Nullable
- public VariantType getType() {
- return mType;
- }
-
- /**
- * @param debuggable whether the app is debuggable
- * @return itself
- */
- public AaptPackageProcessBuilder setDebuggable(boolean debuggable) {
- this.mDebuggable = debuggable;
- return this;
- }
-
- /**
- * @param resourceConfigs a list of resource config filters to pass to aapt.
- * @return itself
- */
- public AaptPackageProcessBuilder setResourceConfigs(@NonNull Collection<String> resourceConfigs) {
- this.mResourceConfigs = resourceConfigs;
- return this;
- }
-
- /**
- * @param splits optional list of split dimensions values (like a density or an abi). This
- * will be used by aapt to generate the corresponding pure split apks.
- * @return itself
- */
- public AaptPackageProcessBuilder setSplits(@NonNull Collection<String> splits) {
- this.mSplits = splits;
- return this;
- }
-
-
- public AaptPackageProcessBuilder setVerbose() {
- mVerboseExec = true;
- return this;
- }
-
- /**
- * @param packageForR Package override to generate the R class in a different package.
- * @return itself
- */
- public AaptPackageProcessBuilder setPackageForR(@NonNull String packageForR) {
- this.mPackageForR = packageForR;
- return this;
- }
-
- public AaptPackageProcessBuilder setPseudoLocalesEnabled(boolean pseudoLocalesEnabled) {
- mPseudoLocalesEnabled = pseudoLocalesEnabled;
- return this;
- }
-
- /**
- * Specifies a preference for a particular density. Resources that do not match this density
- * and have variants that are a closer match are removed.
- * @param density the preferred density
- * @return itself
- */
- public AaptPackageProcessBuilder setPreferredDensity(String density) {
- mPreferredDensity = density;
- return this;
- }
-
- @Nullable
- String getPackageForR() {
- return mPackageForR;
- }
-
- public ProcessInfo build(
- @NonNull BuildToolInfo buildToolInfo,
- @NonNull IAndroidTarget target,
- @NonNull ILogger logger) {
-
- // if both output types are empty, then there's nothing to do and this is an error
- checkArgument(mSourceOutputDir != null || mResPackageOutput != null,
- "No output provided for aapt task");
- if (mSymbolOutputDir != null || mSourceOutputDir != null) {
- checkNotNull(mLibraries,
- "libraries cannot be null if symbolOutputDir or sourceOutputDir is non-null");
- }
-
- // check resConfigs and split settings coherence.
- checkResConfigsVersusSplitSettings(logger);
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
- builder.addEnvironments(mEnvironment);
-
- String aapt = buildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
- if (aapt == null || !new File(aapt).isFile()) {
- throw new IllegalStateException("aapt is missing");
- }
-
- builder.setExecutable(aapt);
- builder.addArgs("package");
-
- if (mVerboseExec) {
- builder.addArgs("-v");
- }
-
- builder.addArgs("-f");
- builder.addArgs("--no-crunch");
-
- // inputs
- builder.addArgs("-I", target.getPath(IAndroidTarget.ANDROID_JAR));
-
- builder.addArgs("-M", mManifestFile.getAbsolutePath());
-
- if (mResFolder != null) {
- builder.addArgs("-S", mResFolder.getAbsolutePath());
- }
-
- if (mAssetsFolder != null) {
- builder.addArgs("-A", mAssetsFolder.getAbsolutePath());
- }
-
- // outputs
- if (mSourceOutputDir != null) {
- builder.addArgs("-m");
- builder.addArgs("-J", mSourceOutputDir);
- }
-
- if (mResPackageOutput != null) {
- builder.addArgs("-F", mResPackageOutput);
- }
-
- if (mProguardOutput != null) {
- builder.addArgs("-G", mProguardOutput);
- }
-
- if (mSplits != null) {
- for (String split : mSplits) {
-
- builder.addArgs("--split", split);
- }
- }
-
- // options controlled by build variants
-
- if (mDebuggable) {
- builder.addArgs("--debug-mode");
- }
-
- if (mType != VariantType.ANDROID_TEST) {
- if (mPackageForR != null) {
- builder.addArgs("--custom-package", mPackageForR);
- logger.verbose("Custom package for R class: '%s'", mPackageForR);
- }
- }
-
- if (mPseudoLocalesEnabled) {
- if (buildToolInfo.getRevision().getMajor() >= 21) {
- builder.addArgs("--pseudo-localize");
- } else {
- throw new RuntimeException(
- "Pseudolocalization is only available since Build Tools version 21.0.0,"
- + " please upgrade or turn it off.");
- }
- }
-
- // library specific options
- if (mType == VariantType.LIBRARY) {
- builder.addArgs("--non-constant-id");
- }
-
- // AAPT options
- String ignoreAssets = mOptions.getIgnoreAssets();
- if (ignoreAssets != null) {
- builder.addArgs("--ignore-assets", ignoreAssets);
- }
-
- if (mOptions.getFailOnMissingConfigEntry()) {
- if (buildToolInfo.getRevision().getMajor() > 20) {
- builder.addArgs("--error-on-missing-config-entry");
- } else {
- throw new IllegalStateException("aaptOptions:failOnMissingConfigEntry cannot be used"
- + " with SDK Build Tools revision earlier than 21.0.0");
- }
- }
-
- // never compress apks.
- builder.addArgs("-0", "apk");
-
- // add custom no-compress extensions
- Collection<String> noCompressList = mOptions.getNoCompress();
- if (noCompressList != null) {
- for (String noCompress : noCompressList) {
- builder.addArgs("-0", noCompress);
- }
- }
- List<String> additionalParameters = mOptions.getAdditionalParameters();
- if (!isNullOrEmpty(additionalParameters)) {
- builder.addArgs(additionalParameters);
- }
-
- List<String> resourceConfigs = new ArrayList<String>();
- if (!isNullOrEmpty(mResourceConfigs)) {
- resourceConfigs.addAll(mResourceConfigs);
- }
- if (buildToolInfo.getRevision().getMajor() < 21 && mPreferredDensity != null) {
- resourceConfigs.add(mPreferredDensity);
- // when adding a density filter, also always add the nodpi option.
- resourceConfigs.add(Density.NODPI.getResourceValue());
- }
-
-
- // separate the density and language resource configs, since starting in 21, the
- // density resource configs should be passed with --preferred-density to ensure packaging
- // of scalable resources when no resource for the preferred density is present.
- List<String> otherResourceConfigs = new ArrayList<String>();
- List<String> densityResourceConfigs = new ArrayList<String>();
- if (!resourceConfigs.isEmpty()) {
- if (buildToolInfo.getRevision().getMajor() >= 21) {
- for (String resourceConfig : resourceConfigs) {
- if (Density.getEnum(resourceConfig) != null) {
- densityResourceConfigs.add(resourceConfig);
- } else {
- otherResourceConfigs.add(resourceConfig);
- }
- }
- } else {
- // before 21, everything is passed with -c option.
- otherResourceConfigs = resourceConfigs;
- }
- }
- if (!otherResourceConfigs.isEmpty()) {
- Joiner joiner = Joiner.on(',');
- builder.addArgs("-c", joiner.join(otherResourceConfigs));
- }
- for (String densityResourceConfig : densityResourceConfigs) {
- builder.addArgs("--preferred-density", densityResourceConfig);
- }
-
- if (buildToolInfo.getRevision().getMajor() >= 21 && mPreferredDensity != null) {
- if (!isNullOrEmpty(mResourceConfigs)) {
- Collection<String> densityResConfig = getDensityResConfigs(mResourceConfigs);
- if (!densityResConfig.isEmpty()) {
- throw new RuntimeException(String.format(
- "When using splits in tools 21 and above, resConfigs should not contain "
- + "any densities. Right now, it contains \"%1$s\"\n"
- + "Suggestion: remove these from resConfigs from build.gradle",
- Joiner.on("\",\"").join(densityResConfig)));
- }
- }
- builder.addArgs("--preferred-density", mPreferredDensity);
- }
-
- if (buildToolInfo.getRevision().getMajor() < 21 && mPreferredDensity != null) {
- logger.warning(String.format("Warning : Project is building density based multiple APKs"
- + " but using tools version %1$s, you should upgrade to build-tools 21 or above"
- + " to ensure proper packaging of resources.",
- buildToolInfo.getRevision().getMajor()));
- }
-
- if (mSymbolOutputDir != null &&
- (mType == VariantType.LIBRARY || !mLibraries.isEmpty())) {
- builder.addArgs("--output-text-symbols", mSymbolOutputDir);
- }
-
- return builder.createProcess();
- }
-
- private void checkResConfigsVersusSplitSettings(ILogger logger) {
- if (isNullOrEmpty(mResourceConfigs) || isNullOrEmpty(mSplits)) {
- return;
- }
-
- // only consider the Density related resConfig settings.
- Collection<String> resConfigs = getDensityResConfigs(mResourceConfigs);
- List<String> splits = new ArrayList<String>(mSplits);
- splits.removeAll(resConfigs);
- if (!splits.isEmpty()) {
- // some splits are required, yet the resConfigs do not contain the split density value
- // which mean that the resulting split file would be empty, flag this as an error.
- throw new RuntimeException(String.format(
- "Splits for densities \"%1$s\" were configured, yet the resConfigs settings does"
- + " not include such splits. The resulting split APKs would be empty.\n"
- + "Suggestion : exclude those splits in your build.gradle : \n"
- + "splits {\n"
- + " density {\n"
- + " enable true\n"
- + " exclude \"%2$s\"\n"
- + " }\n"
- + "}\n"
- + "OR add them to the resConfigs list.",
- Joiner.on(",").join(splits),
- Joiner.on("\",\"").join(splits)));
- }
- resConfigs.removeAll(mSplits);
- if (!resConfigs.isEmpty()) {
- // there are densities present in the resConfig but not in splits, which mean that those
- // densities will be packaged in the main APK
- throw new RuntimeException(String.format(
- "Inconsistent density configuration, with \"%1$s\" present on "
- + "resConfig settings, while only \"%2$s\" densities are requested "
- + "in splits APK density settings.\n"
- + "Suggestion : remove extra densities from the resConfig : \n"
- + "defaultConfig {\n"
- + " resConfigs \"%2$s\"\n"
- + "}\n"
- + "OR remove such densities from the split's exclude list.\n",
- Joiner.on(",").join(resConfigs),
- Joiner.on("\",\"").join(mSplits)));
- }
- }
-
- private static boolean isNullOrEmpty(@Nullable Collection<?> collection) {
- return collection == null || collection.isEmpty();
- }
-
- private static Collection<String> getDensityResConfigs(Collection<String> resourceConfigs) {
- return Collections2.filter(new ArrayList<String>(resourceConfigs),
- new Predicate<String>() {
- @Override
- public boolean apply(@Nullable String input) {
- return Density.getEnum(input) != null;
- }
- });
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java b/base/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java
deleted file mode 100644
index ce77d74..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java
+++ /dev/null
@@ -1,1844 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import static com.android.SdkConstants.DOT_DEX;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.FD_RES_XML;
-import static com.android.builder.core.BuilderConstants.ANDROID_WEAR;
-import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK;
-import static com.android.manifmerger.ManifestMerger2.Invoker;
-import static com.android.manifmerger.ManifestMerger2.SystemProperty;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.compiling.DependencyFileProcessor;
-import com.android.builder.core.BuildToolsServiceLoader.BuildToolServiceLoader;
-import com.android.builder.dependency.ManifestDependency;
-import com.android.builder.dependency.SymbolFileProvider;
-import com.android.builder.internal.ClassFieldImpl;
-import com.android.builder.internal.SymbolLoader;
-import com.android.builder.internal.SymbolWriter;
-import com.android.builder.internal.TestManifestGenerator;
-import com.android.builder.internal.compiler.AidlProcessor;
-import com.android.builder.internal.compiler.JackConversionCache;
-import com.android.builder.internal.compiler.LeafFolderGatherer;
-import com.android.builder.internal.compiler.PreDexCache;
-import com.android.builder.internal.compiler.RenderScriptProcessor;
-import com.android.builder.internal.compiler.SourceSearcher;
-import com.android.builder.internal.incremental.DependencyData;
-import com.android.builder.internal.packaging.Packager;
-import com.android.builder.model.ClassField;
-import com.android.builder.model.PackagingOptions;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.model.SyncIssue;
-import com.android.builder.packaging.DuplicateFileException;
-import com.android.builder.packaging.PackagerException;
-import com.android.builder.packaging.SealedPackageException;
-import com.android.builder.packaging.SigningException;
-import com.android.builder.sdk.SdkInfo;
-import com.android.builder.sdk.TargetInfo;
-import com.android.builder.signing.SignedJarBuilder;
-import com.android.ide.common.internal.AaptCruncher;
-import com.android.ide.common.internal.LoggedErrorException;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.ide.common.process.CachedProcessOutputHandler;
-import com.android.ide.common.process.JavaProcessExecutor;
-import com.android.ide.common.process.JavaProcessInfo;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessExecutor;
-import com.android.ide.common.process.ProcessInfo;
-import com.android.ide.common.process.ProcessInfoBuilder;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.ide.common.process.ProcessResult;
-import com.android.ide.common.signing.CertificateInfo;
-import com.android.ide.common.signing.KeystoreHelper;
-import com.android.ide.common.signing.KeytoolException;
-import com.android.jack.api.ConfigNotSupportedException;
-import com.android.jack.api.JackProvider;
-import com.android.jack.api.v01.Api01CompilationTask;
-import com.android.jack.api.v01.Api01Config;
-import com.android.jack.api.v01.CompilationException;
-import com.android.jack.api.v01.ConfigurationException;
-import com.android.jack.api.v01.MultiDexKind;
-import com.android.jack.api.v01.ReporterKind;
-import com.android.jack.api.v01.UnrecoverableException;
-import com.android.jill.api.JillProvider;
-import com.android.jill.api.v01.Api01TranslationTask;
-import com.android.jill.api.v01.TranslationException;
-import com.android.manifmerger.ManifestMerger2;
-import com.android.manifmerger.MergingReport;
-import com.android.manifmerger.PlaceholderEncoder;
-import com.android.manifmerger.PlaceholderHandler;
-import com.android.manifmerger.XmlDocument;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.IAndroidTarget.OptionalLibrary;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.android.utils.Pair;
-import com.google.common.base.Charsets;
-import com.google.common.base.Optional;
-import com.google.common.base.Splitter;
-import com.google.common.base.Strings;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * This is the main builder class. It is given all the data to process the build (such as
- * {@link DefaultProductFlavor}s, {@link DefaultBuildType} and dependencies) and use them when doing specific
- * build steps.
- *
- * To use:
- * create a builder with {@link #AndroidBuilder(String, String, ProcessExecutor, JavaProcessExecutor, ErrorReporter, ILogger, boolean)}
- *
- * then build steps can be done with
- * {@link #mergeManifests(File, List, List, String, int, String, String, String, Integer, String, String, ManifestMerger2.MergeType, Map, File)}
- * {@link #processTestManifest(String, String, String, String, String, Boolean, Boolean, File, List, Map, File, File)}
- * {@link #processResources(AaptPackageProcessBuilder, boolean, ProcessOutputHandler)}
- * {@link #compileAllAidlFiles(List, File, File, List, DependencyFileProcessor, ProcessOutputHandler)}
- * {@link #convertByteCode(Collection, Collection, File, boolean, File, DexOptions, List, File, boolean, boolean, ProcessOutputHandler)}
- * {@link #packageApk(String, File, Collection, Collection, String, Collection, File, Set, boolean, SigningConfig, PackagingOptions, SignedJarBuilder.IZipEntryFilter, String)}
- *
- * Java compilation is not handled but the builder provides the bootclasspath with
- * {@link #getBootClasspath()}.
- */
-public class AndroidBuilder {
-
- private static final FullRevision MIN_BUILD_TOOLS_REV = new FullRevision(19, 1, 0);
- private static final DependencyFileProcessor sNoOpDependencyFileProcessor = new DependencyFileProcessor() {
- @Override
- public DependencyData processFile(@NonNull File dependencyFile) {
- return null;
- }
- };
-
- @NonNull
- private final String mProjectId;
- @NonNull
- private final ILogger mLogger;
-
- @NonNull
- private final ProcessExecutor mProcessExecutor;
- @NonNull
- private final JavaProcessExecutor mJavaProcessExecutor;
- @NonNull
- private final ErrorReporter mErrorReporter;
-
- private final boolean mVerboseExec;
-
- @Nullable private String mCreatedBy;
-
-
- private SdkInfo mSdkInfo;
- private TargetInfo mTargetInfo;
-
- private List<File> mBootClasspath;
- @NonNull
- private List<LibraryRequest> mLibraryRequests = ImmutableList.of();
-
- /**
- * Creates an AndroidBuilder.
- * <p/>
- * <var>verboseExec</var> is needed on top of the ILogger due to remote exec tools not being
- * able to output info and verbose messages separately.
- *
- * @param createdBy the createdBy String for the apk manifest.
- * @param logger the Logger
- * @param verboseExec whether external tools are launched in verbose mode
- */
- public AndroidBuilder(
- @NonNull String projectId,
- @Nullable String createdBy,
- @NonNull ProcessExecutor processExecutor,
- @NonNull JavaProcessExecutor javaProcessExecutor,
- @NonNull ErrorReporter errorReporter,
- @NonNull ILogger logger,
- boolean verboseExec) {
- mProjectId = checkNotNull(projectId);
- mCreatedBy = createdBy;
- mProcessExecutor = checkNotNull(processExecutor);
- mJavaProcessExecutor = checkNotNull(javaProcessExecutor);
- mErrorReporter = checkNotNull(errorReporter);
- mLogger = checkNotNull(logger);
- mVerboseExec = verboseExec;
- }
-
- /**
- * Sets the SdkInfo and the targetInfo on the builder. This is required to actually
- * build (some of the steps).
- *
- * @param sdkInfo the SdkInfo
- * @param targetInfo the TargetInfo
- *
- * @see com.android.builder.sdk.SdkLoader
- */
- public void setTargetInfo(
- @NonNull SdkInfo sdkInfo,
- @NonNull TargetInfo targetInfo,
- @NonNull Collection<LibraryRequest> libraryRequests) {
- mSdkInfo = sdkInfo;
- mTargetInfo = targetInfo;
-
- if (mTargetInfo.getBuildTools().getRevision().compareTo(MIN_BUILD_TOOLS_REV) < 0) {
- throw new IllegalArgumentException(String.format(
- "The SDK Build Tools revision (%1$s) is too low for project '%2$s'. Minimum required is %3$s",
- mTargetInfo.getBuildTools().getRevision(), mProjectId, MIN_BUILD_TOOLS_REV));
- }
-
- mLibraryRequests = ImmutableList.copyOf(libraryRequests);
- }
-
- /**
- * Returns the SdkInfo, if set.
- */
- @Nullable
- public SdkInfo getSdkInfo() {
- return mSdkInfo;
- }
-
- /**
- * Returns the TargetInfo, if set.
- */
- @Nullable
- public TargetInfo getTargetInfo() {
- return mTargetInfo;
- }
-
- @NonNull
- public ILogger getLogger() {
- return mLogger;
- }
-
- @NonNull
- public ErrorReporter getErrorReporter() {
- return mErrorReporter;
- }
-
- /**
- * Returns the compilation target, if set.
- */
- @Nullable
- public IAndroidTarget getTarget() {
- checkState(mTargetInfo != null,
- "Cannot call getTarget() before setTargetInfo() is called.");
- return mTargetInfo.getTarget();
- }
-
- /**
- * Returns whether the compilation target is a preview.
- */
- public boolean isPreviewTarget() {
- checkState(mTargetInfo != null,
- "Cannot call isTargetAPreview() before setTargetInfo() is called.");
- return mTargetInfo.getTarget().getVersion().isPreview();
- }
-
- public String getTargetCodename() {
- checkState(mTargetInfo != null,
- "Cannot call getTargetCodename() before setTargetInfo() is called.");
- return mTargetInfo.getTarget().getVersion().getCodename();
- }
-
- @NonNull
- public File getDxJar() {
- checkState(mTargetInfo != null,
- "Cannot call getDxJar() before setTargetInfo() is called.");
- return new File(mTargetInfo.getBuildTools().getPath(BuildToolInfo.PathId.DX_JAR));
- }
-
- /**
- * Helper method to get the boot classpath to be used during compilation.
- */
- @NonNull
- public List<File> getBootClasspath() {
- if (mBootClasspath == null) {
- checkState(mTargetInfo != null,
- "Cannot call getBootClasspath() before setTargetInfo() is called.");
-
- List<File> classpath = Lists.newArrayList();
-
- IAndroidTarget target = mTargetInfo.getTarget();
-
- for (String p : target.getBootClasspath()) {
- classpath.add(new File(p));
- }
-
- List<LibraryRequest> requestedLibs = Lists.newArrayList(mLibraryRequests);
-
- // add additional libraries if any
- List<OptionalLibrary> libs = target.getAdditionalLibraries();
- for (OptionalLibrary lib : libs) {
- // add it always for now
- classpath.add(lib.getJar());
-
- // remove from list of requested if match
- LibraryRequest requestedLib = findMatchingLib(lib.getName(), requestedLibs);
- if (requestedLib != null) {
- requestedLibs.remove(requestedLib);
- }
- }
-
- // add optional libraries if needed.
- List<OptionalLibrary> optionalLibraries = target.getOptionalLibraries();
- for (OptionalLibrary lib : optionalLibraries) {
- // search if requested
- LibraryRequest requestedLib = findMatchingLib(lib.getName(), requestedLibs);
- if (requestedLib != null) {
- // add to classpath
- classpath.add(lib.getJar());
-
- // remove from requested list.
- requestedLibs.remove(requestedLib);
- }
- }
-
- // look for not found requested libraries.
- for (LibraryRequest library : requestedLibs) {
- mErrorReporter.handleSyncError(
- library.getName(),
- SyncIssue.TYPE_OPTIONAL_LIB_NOT_FOUND,
- "Unable to find optional library: " + library.getName());
- }
-
- // add annotations.jar if needed.
- if (target.getVersion().getApiLevel() <= 15) {
- classpath.add(mSdkInfo.getAnnotationsJar());
- }
-
- mBootClasspath = ImmutableList.copyOf(classpath);
- }
-
- return mBootClasspath;
- }
-
- @Nullable
- private static LibraryRequest findMatchingLib(@NonNull String name, @NonNull List<LibraryRequest> libraries) {
- for (LibraryRequest library : libraries) {
- if (name.equals(library.getName())) {
- return library;
- }
- }
-
- return null;
- }
-
- /**
- * Helper method to get the boot classpath to be used during compilation.
- */
- @NonNull
- public List<String> getBootClasspathAsStrings() {
- List<File> classpath = getBootClasspath();
-
- // convert to Strings.
- List<String> results = Lists.newArrayListWithCapacity(classpath.size());
- for (File f : classpath) {
- results.add(f.getAbsolutePath());
- }
-
- return results;
- }
-
- /**
- * Returns the jar file for the renderscript mode.
- *
- * This may return null if the SDK has not been loaded yet.
- *
- * @return the jar file, or null.
- *
- * @see #setTargetInfo(SdkInfo, TargetInfo, Collection)
- */
- @Nullable
- public File getRenderScriptSupportJar() {
- if (mTargetInfo != null) {
- return RenderScriptProcessor.getSupportJar(
- mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
- }
-
- return null;
- }
-
- /**
- * Returns the compile classpath for this config. If the config tests a library, this
- * will include the classpath of the tested config.
- *
- * If the SDK was loaded, this may include the renderscript support jar.
- *
- * @return a non null, but possibly empty set.
- */
- @NonNull
- public Set<File> getCompileClasspath(@NonNull VariantConfiguration<?,?,?> variantConfiguration) {
- Set<File> compileClasspath = variantConfiguration.getCompileClasspath();
-
- if (variantConfiguration.getRenderscriptSupportModeEnabled()) {
- File renderScriptSupportJar = getRenderScriptSupportJar();
-
- Set<File> fullJars = Sets.newHashSetWithExpectedSize(compileClasspath.size() + 1);
- fullJars.addAll(compileClasspath);
- if (renderScriptSupportJar != null) {
- fullJars.add(renderScriptSupportJar);
- }
- compileClasspath = fullJars;
- }
-
- return compileClasspath;
- }
-
- /**
- * Returns the list of packaged jars for this config. If the config tests a library, this
- * will include the jars of the tested config
- *
- * If the SDK was loaded, this may include the renderscript support jar.
- *
- * @return a non null, but possibly empty list.
- */
- @NonNull
- public Set<File> getPackagedJars(@NonNull VariantConfiguration<?,?,?> variantConfiguration) {
- Set<File> packagedJars = Sets.newHashSet(variantConfiguration.getPackagedJars());
-
- if (variantConfiguration.getRenderscriptSupportModeEnabled()) {
- File renderScriptSupportJar = getRenderScriptSupportJar();
-
- if (renderScriptSupportJar != null) {
- packagedJars.add(renderScriptSupportJar);
- }
- }
-
- return packagedJars;
- }
-
- /**
- * Returns the native lib folder for the renderscript mode.
- *
- * This may return null if the SDK has not been loaded yet.
- *
- * @return the folder, or null.
- *
- * @see #setTargetInfo(SdkInfo, TargetInfo, Collection)
- */
- @Nullable
- public File getSupportNativeLibFolder() {
- if (mTargetInfo != null) {
- return RenderScriptProcessor.getSupportNativeLibFolder(
- mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
- }
-
- return null;
- }
-
- /**
- * Returns an {@link PngCruncher} using aapt underneath
- * @return an PngCruncher object
- */
- @NonNull
- public PngCruncher getAaptCruncher(ProcessOutputHandler processOutputHandler) {
- checkState(mTargetInfo != null,
- "Cannot call getAaptCruncher() before setTargetInfo() is called.");
- return new AaptCruncher(
- mTargetInfo.getBuildTools().getPath(BuildToolInfo.PathId.AAPT),
- mProcessExecutor,
- processOutputHandler);
- }
-
- @NonNull
- public ProcessExecutor getProcessExecutor() {
- return mProcessExecutor;
- }
-
- @NonNull
- public ProcessResult executeProcess(@NonNull ProcessInfo processInfo,
- @NonNull ProcessOutputHandler handler) {
- return mProcessExecutor.execute(processInfo, handler);
- }
-
- @NonNull
- public static ClassField createClassField(@NonNull String type, @NonNull String name, @NonNull String value) {
- return new ClassFieldImpl(type, name, value);
- }
-
- /**
- * Invoke the Manifest Merger version 2.
- */
- public void mergeManifests(
- @NonNull File mainManifest,
- @NonNull List<File> manifestOverlays,
- @NonNull List<? extends ManifestDependency> libraries,
- String packageOverride,
- int versionCode,
- String versionName,
- @Nullable String minSdkVersion,
- @Nullable String targetSdkVersion,
- @Nullable Integer maxSdkVersion,
- @NonNull String outManifestLocation,
- @Nullable String outAaptSafeManifestLocation,
- ManifestMerger2.MergeType mergeType,
- Map<String, String> placeHolders,
- @Nullable File reportFile) {
-
- try {
- Invoker manifestMergerInvoker =
- ManifestMerger2.newMerger(mainManifest, mLogger, mergeType)
- .setPlaceHolderValues(placeHolders)
- .addFlavorAndBuildTypeManifests(
- manifestOverlays.toArray(new File[manifestOverlays.size()]))
- .addLibraryManifests(collectLibraries(libraries))
- .setMergeReportFile(reportFile);
-
- if (mergeType == ManifestMerger2.MergeType.APPLICATION) {
- manifestMergerInvoker.withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
- }
-
- setInjectableValues(manifestMergerInvoker,
- packageOverride, versionCode, versionName,
- minSdkVersion, targetSdkVersion, maxSdkVersion);
-
- MergingReport mergingReport = manifestMergerInvoker.merge();
- mLogger.info("Merging result:" + mergingReport.getResult());
- switch (mergingReport.getResult()) {
- case WARNING:
- mergingReport.log(mLogger);
- // fall through since these are just warnings.
- case SUCCESS:
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- try {
- String annotatedDocument = mergingReport.getActions().blame(xmlDocument);
- mLogger.verbose(annotatedDocument);
- } catch (Exception e) {
- mLogger.error(e, "cannot print resulting xml");
- }
- save(xmlDocument, new File(outManifestLocation));
- if (outAaptSafeManifestLocation != null) {
- new PlaceholderEncoder().visit(xmlDocument);
- save(xmlDocument, new File(outAaptSafeManifestLocation));
- }
- mLogger.info("Merged manifest saved to " + outManifestLocation);
- break;
- case ERROR:
- mergingReport.log(mLogger);
- throw new RuntimeException(mergingReport.getReportString());
- default:
- throw new RuntimeException("Unhandled result type : "
- + mergingReport.getResult());
- }
- } catch (ManifestMerger2.MergeFailureException e) {
- // TODO: unacceptable.
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Sets the {@link com.android.manifmerger.ManifestMerger2.SystemProperty} that can be injected
- * in the manifest file.
- */
- private static void setInjectableValues(
- ManifestMerger2.Invoker<?> invoker,
- String packageOverride,
- int versionCode,
- String versionName,
- @Nullable String minSdkVersion,
- @Nullable String targetSdkVersion,
- @Nullable Integer maxSdkVersion) {
-
- if (!Strings.isNullOrEmpty(packageOverride)) {
- invoker.setOverride(SystemProperty.PACKAGE, packageOverride);
- }
- if (versionCode > 0) {
- invoker.setOverride(SystemProperty.VERSION_CODE,
- String.valueOf(versionCode));
- }
- if (!Strings.isNullOrEmpty(versionName)) {
- invoker.setOverride(SystemProperty.VERSION_NAME, versionName);
- }
- if (!Strings.isNullOrEmpty(minSdkVersion)) {
- invoker.setOverride(SystemProperty.MIN_SDK_VERSION, minSdkVersion);
- }
- if (!Strings.isNullOrEmpty(targetSdkVersion)) {
- invoker.setOverride(SystemProperty.TARGET_SDK_VERSION, targetSdkVersion);
- }
- if (maxSdkVersion != null) {
- invoker.setOverride(SystemProperty.MAX_SDK_VERSION, maxSdkVersion.toString());
- }
- }
-
- /**
- * Saves the {@link com.android.manifmerger.XmlDocument} to a file in UTF-8 encoding.
- * @param xmlDocument xml document to save.
- * @param out file to save to.
- */
- private void save(XmlDocument xmlDocument, File out) {
- try {
- Files.write(xmlDocument.prettyPrint(), out, Charsets.UTF_8);
- } catch(IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Collect the list of libraries' manifest files.
- * @param libraries declared dependencies
- * @return a list of files and names for the libraries' manifest files.
- */
- private static ImmutableList<Pair<String, File>> collectLibraries(
- List<? extends ManifestDependency> libraries) {
-
- ImmutableList.Builder<Pair<String, File>> manifestFiles = ImmutableList.builder();
- if (libraries != null) {
- collectLibraries(libraries, manifestFiles);
- }
- return manifestFiles.build();
- }
-
- /**
- * recursively calculate the list of libraries to merge the manifests files from.
- * @param libraries the dependencies
- * @param manifestFiles list of files and names identifiers for the libraries' manifest files.
- */
- private static void collectLibraries(List<? extends ManifestDependency> libraries,
- ImmutableList.Builder<Pair<String, File>> manifestFiles) {
-
- for (ManifestDependency library : libraries) {
- manifestFiles.add(Pair.of(library.getName(), library.getManifest()));
- List<? extends ManifestDependency> manifestDependencies = library
- .getManifestDependencies();
- if (!manifestDependencies.isEmpty()) {
- collectLibraries(manifestDependencies, manifestFiles);
- }
- }
- }
-
- /**
- * Creates the manifest for a test variant
- *
- * @param testApplicationId the application id of the test application
- * @param minSdkVersion the minSdkVersion of the test application
- * @param targetSdkVersion the targetSdkVersion of the test application
- * @param testedApplicationId the application id of the tested application
- * @param instrumentationRunner the name of the instrumentation runner
- * @param handleProfiling whether or not the Instrumentation object will turn profiling on and off
- * @param functionalTest whether or not the Instrumentation class should run as a functional test
- * @param testManifestFile optionally user provided AndroidManifest.xml for testing application
- * @param libraries the library dependency graph
- * @param outManifest the output location for the merged manifest
- *
- * @see VariantConfiguration#getApplicationId()
- * @see VariantConfiguration#getTestedConfig()
- * @see VariantConfiguration#getMinSdkVersion()
- * @see VariantConfiguration#getTestedApplicationId()
- * @see VariantConfiguration#getInstrumentationRunner()
- * @see VariantConfiguration#getHandleProfiling()
- * @see VariantConfiguration#getFunctionalTest()
- * @see VariantConfiguration#getDirectLibraries()
- */
- public void processTestManifest(
- @NonNull String testApplicationId,
- @Nullable String minSdkVersion,
- @Nullable String targetSdkVersion,
- @NonNull String testedApplicationId,
- @NonNull String instrumentationRunner,
- @NonNull Boolean handleProfiling,
- @NonNull Boolean functionalTest,
- @Nullable File testManifestFile,
- @NonNull List<? extends ManifestDependency> libraries,
- @NonNull Map<String, Object> manifestPlaceholders,
- @NonNull File outManifest,
- @NonNull File tmpDir) {
- checkNotNull(testApplicationId, "testApplicationId cannot be null.");
- checkNotNull(testedApplicationId, "testedApplicationId cannot be null.");
- checkNotNull(instrumentationRunner, "instrumentationRunner cannot be null.");
- checkNotNull(handleProfiling, "handleProfiling cannot be null.");
- checkNotNull(functionalTest, "functionalTest cannot be null.");
- checkNotNull(libraries, "libraries cannot be null.");
- checkNotNull(outManifest, "outManifestLocation cannot be null.");
-
- try {
- tmpDir.mkdirs();
- File generatedTestManifest = libraries.isEmpty() && testManifestFile == null
- ? outManifest : File.createTempFile("manifestMerger", ".xml", tmpDir);
-
- mLogger.verbose("Generating in %1$s", generatedTestManifest.getAbsolutePath());
- generateTestManifest(
- testApplicationId,
- minSdkVersion,
- targetSdkVersion.equals("-1") ? null : targetSdkVersion,
- testedApplicationId,
- instrumentationRunner,
- handleProfiling,
- functionalTest,
- generatedTestManifest);
-
- if (testManifestFile != null) {
- File mergedTestManifest = File.createTempFile("manifestMerger", ".xml", tmpDir);
- mLogger.verbose("Merging user supplied manifest in %1$s",
- generatedTestManifest.getAbsolutePath());
- Invoker invoker = ManifestMerger2.newMerger(
- testManifestFile, mLogger, ManifestMerger2.MergeType.APPLICATION)
- .setOverride(SystemProperty.PACKAGE, testApplicationId)
- .setPlaceHolderValues(manifestPlaceholders)
- .setPlaceHolderValue(PlaceholderHandler.INSTRUMENTATION_RUNNER,
- instrumentationRunner)
- .addLibraryManifests(generatedTestManifest);
- if (minSdkVersion != null) {
- invoker.setOverride(SystemProperty.MIN_SDK_VERSION, minSdkVersion);
- }
- if (!targetSdkVersion.equals("-1")) {
- invoker.setOverride(SystemProperty.TARGET_SDK_VERSION, targetSdkVersion);
- }
- MergingReport mergingReport = invoker.merge();
- if (libraries.isEmpty()) {
- handleMergingResult(mergingReport, outManifest);
- } else {
- handleMergingResult(mergingReport, mergedTestManifest);
- generatedTestManifest = mergedTestManifest;
- }
- }
-
- if (!libraries.isEmpty()) {
- MergingReport mergingReport = ManifestMerger2.newMerger(
- generatedTestManifest, mLogger, ManifestMerger2.MergeType.APPLICATION)
- .withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)
- .setOverride(SystemProperty.PACKAGE, testApplicationId)
- .addLibraryManifests(collectLibraries(libraries))
- .setPlaceHolderValues(manifestPlaceholders)
- .merge();
-
- handleMergingResult(mergingReport, outManifest);
- }
- } catch(Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private void handleMergingResult(@NonNull MergingReport mergingReport, @NonNull File outFile) {
- switch (mergingReport.getResult()) {
- case WARNING:
- mergingReport.log(mLogger);
- // fall through since these are just warnings.
- case SUCCESS:
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- try {
- String annotatedDocument = mergingReport.getActions().blame(xmlDocument);
- mLogger.verbose(annotatedDocument);
- } catch (Exception e) {
- mLogger.error(e, "cannot print resulting xml");
- }
- save(xmlDocument, outFile);
- mLogger.info("Merged manifest saved to " + outFile);
- break;
- case ERROR:
- mergingReport.log(mLogger);
- throw new RuntimeException(mergingReport.getReportString());
- default:
- throw new RuntimeException("Unhandled result type : "
- + mergingReport.getResult());
- }
- }
-
- private static void generateTestManifest(
- @NonNull String testApplicationId,
- @Nullable String minSdkVersion,
- @Nullable String targetSdkVersion,
- @NonNull String testedApplicationId,
- @NonNull String instrumentationRunner,
- @NonNull Boolean handleProfiling,
- @NonNull Boolean functionalTest,
- @NonNull File outManifestLocation) {
- TestManifestGenerator generator = new TestManifestGenerator(
- outManifestLocation,
- testApplicationId,
- minSdkVersion,
- targetSdkVersion,
- testedApplicationId,
- instrumentationRunner,
- handleProfiling,
- functionalTest);
- try {
- generator.generate();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Process the resources and generate R.java and/or the packaged resources.
- *
- * @param aaptCommand aapt command invocation parameters.
- * @param enforceUniquePackageName if true method will fail if some libraries share the same
- * package name
- *
- * @throws IOException
- * @throws InterruptedException
- * @throws ProcessException
- */
- public void processResources(
- @NonNull AaptPackageProcessBuilder aaptCommand,
- boolean enforceUniquePackageName,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws IOException, InterruptedException, ProcessException {
-
- checkState(mTargetInfo != null,
- "Cannot call processResources() before setTargetInfo() is called.");
-
- // launch aapt: create the command line
- ProcessInfo processInfo = aaptCommand.build(
- mTargetInfo.getBuildTools(), mTargetInfo.getTarget(), mLogger);
-
- ProcessResult result = mProcessExecutor.execute(processInfo, processOutputHandler);
- result.rethrowFailure().assertNormalExitValue();
-
- // now if the project has libraries, R needs to be created for each libraries,
- // but only if the current project is not a library.
- if (aaptCommand.getSourceOutputDir() != null
- && aaptCommand.getType() != VariantType.LIBRARY
- && !aaptCommand.getLibraries().isEmpty()) {
- SymbolLoader fullSymbolValues = null;
-
- // First pass processing the libraries, collecting them by packageName,
- // and ignoring the ones that have the same package name as the application
- // (since that R class was already created).
- String appPackageName = aaptCommand.getPackageForR();
- if (appPackageName == null) {
- appPackageName = VariantConfiguration.getManifestPackage(aaptCommand.getManifestFile());
- }
-
- // list of all the symbol loaders per package names.
- Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
-
- for (SymbolFileProvider lib : aaptCommand.getLibraries()) {
- if (lib.isOptional()) {
- continue;
- }
- String packageName = VariantConfiguration.getManifestPackage(lib.getManifest());
- if (appPackageName == null) {
- continue;
- }
-
- if (appPackageName.equals(packageName)) {
- if (enforceUniquePackageName) {
- String msg = String.format(
- "Error: A library uses the same package as this project: %s",
- packageName);
- throw new RuntimeException(msg);
- }
-
- // ignore libraries that have the same package name as the app
- continue;
- }
-
- File rFile = lib.getSymbolFile();
- // if the library has no resource, this file won't exist.
- if (rFile.isFile()) {
-
- // load the full values if that's not already been done.
- // Doing it lazily allow us to support the case where there's no
- // resources anywhere.
- if (fullSymbolValues == null) {
- fullSymbolValues = new SymbolLoader(new File(aaptCommand.getSymbolOutputDir(), "R.txt"),
- mLogger);
- fullSymbolValues.load();
- }
-
- SymbolLoader libSymbols = new SymbolLoader(rFile, mLogger);
- libSymbols.load();
-
-
- // store these symbols by associating them with the package name.
- libMap.put(packageName, libSymbols);
- }
- }
-
- // now loop on all the package name, merge all the symbols to write, and write them
- for (String packageName : libMap.keySet()) {
- Collection<SymbolLoader> symbols = libMap.get(packageName);
-
- if (enforceUniquePackageName && symbols.size() > 1) {
- String msg = String.format(
- "Error: more than one library with package name '%s'\n" +
- "You can temporarily disable this error with android.enforceUniquePackageName=false\n" +
- "However, this is temporary and will be enforced in 1.0", packageName);
- throw new RuntimeException(msg);
- }
-
- SymbolWriter writer = new SymbolWriter(aaptCommand.getSourceOutputDir(), packageName,
- fullSymbolValues);
- for (SymbolLoader symbolLoader : symbols) {
- writer.addSymbolsToWrite(symbolLoader);
- }
- writer.write();
- }
- }
- }
-
- public void generateApkData(
- @NonNull File apkFile,
- @NonNull File outResFolder,
- @NonNull String mainPkgName,
- @NonNull String resName) throws ProcessException, IOException {
-
- // need to run aapt to get apk information
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
-
- String aapt = buildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
- if (aapt == null) {
- throw new IllegalStateException(
- "Unable to get aapt location from Build Tools " + buildToolInfo.getRevision());
- }
-
- ApkInfoParser parser = new ApkInfoParser(new File(aapt), mProcessExecutor);
- ApkInfoParser.ApkInfo apkInfo = parser.parseApk(apkFile);
-
- if (!apkInfo.getPackageName().equals(mainPkgName)) {
- throw new RuntimeException("The main and the micro apps do not have the same package name.");
- }
-
- String content = String.format(
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<wearableApp package=\"%1$s\">\n" +
- " <versionCode>%2$s</versionCode>\n" +
- " <versionName>%3$s</versionName>\n" +
- " <rawPathResId>%4$s</rawPathResId>\n" +
- "</wearableApp>",
- apkInfo.getPackageName(),
- apkInfo.getVersionCode(),
- apkInfo.getVersionName(),
- resName);
-
- // xml folder
- File resXmlFile = new File(outResFolder, FD_RES_XML);
- resXmlFile.mkdirs();
-
- Files.write(content,
- new File(resXmlFile, ANDROID_WEAR_MICRO_APK + DOT_XML),
- Charsets.UTF_8);
- }
-
- public static void generateApkDataEntryInManifest(
- int minSdkVersion,
- int targetSdkVersion,
- @NonNull File manifestFile)
- throws InterruptedException, LoggedErrorException, IOException {
-
- StringBuilder content = new StringBuilder();
- content.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
- .append("<manifest package=\"\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n")
- .append(" <uses-sdk android:minSdkVersion=\"")
- .append(minSdkVersion).append("\"");
- if (targetSdkVersion != -1) {
- content.append(" android:targetSdkVersion=\"").append(targetSdkVersion).append("\"");
- }
- content.append("/>\n");
- content.append(" <application>\n")
- .append(" <meta-data android:name=\"" + ANDROID_WEAR + "\"\n")
- .append(" android:resource=\"@xml/" + ANDROID_WEAR_MICRO_APK)
- .append("\" />\n")
- .append(" </application>\n")
- .append("</manifest>\n");
-
- Files.write(content, manifestFile, Charsets.UTF_8);
- }
-
- /**
- * Compiles all the aidl files found in the given source folders.
- *
- * @param sourceFolders all the source folders to find files to compile
- * @param sourceOutputDir the output dir in which to generate the source code
- * @param importFolders import folders
- * @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
- * of the compilation.
- * @throws IOException
- * @throws InterruptedException
- * @throws LoggedErrorException
- */
- public void compileAllAidlFiles(@NonNull List<File> sourceFolders,
- @NonNull File sourceOutputDir,
- @Nullable File parcelableOutputDir,
- @NonNull List<File> importFolders,
- @Nullable DependencyFileProcessor dependencyFileProcessor,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws IOException, InterruptedException, LoggedErrorException, ProcessException {
- checkNotNull(sourceFolders, "sourceFolders cannot be null.");
- checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
- checkNotNull(importFolders, "importFolders cannot be null.");
- checkState(mTargetInfo != null,
- "Cannot call compileAllAidlFiles() before setTargetInfo() is called.");
-
- IAndroidTarget target = mTargetInfo.getTarget();
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
-
- String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
- if (aidl == null || !new File(aidl).isFile()) {
- throw new IllegalStateException("aidl is missing");
- }
-
- List<File> fullImportList = Lists.newArrayListWithCapacity(
- sourceFolders.size() + importFolders.size());
- fullImportList.addAll(sourceFolders);
- fullImportList.addAll(importFolders);
-
- AidlProcessor processor = new AidlProcessor(
- aidl,
- target.getPath(IAndroidTarget.ANDROID_AIDL),
- fullImportList,
- sourceOutputDir,
- parcelableOutputDir,
- dependencyFileProcessor != null ?
- dependencyFileProcessor : sNoOpDependencyFileProcessor,
- mProcessExecutor,
- processOutputHandler);
-
- SourceSearcher searcher = new SourceSearcher(sourceFolders, "aidl");
- searcher.setUseExecutor(true);
- searcher.search(processor);
- }
-
- /**
- * Compiles the given aidl file.
- *
- * @param aidlFile the AIDL file to compile
- * @param sourceOutputDir the output dir in which to generate the source code
- * @param importFolders all the import folders, including the source folders.
- * @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
- * of the compilation.
- * @throws IOException
- * @throws InterruptedException
- * @throws LoggedErrorException
- */
- public void compileAidlFile(@NonNull File sourceFolder,
- @NonNull File aidlFile,
- @NonNull File sourceOutputDir,
- @Nullable File parcelableOutputDir,
- @NonNull List<File> importFolders,
- @Nullable DependencyFileProcessor dependencyFileProcessor,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws IOException, InterruptedException, LoggedErrorException, ProcessException {
- checkNotNull(aidlFile, "aidlFile cannot be null.");
- checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
- checkNotNull(importFolders, "importFolders cannot be null.");
- checkState(mTargetInfo != null,
- "Cannot call compileAidlFile() before setTargetInfo() is called.");
-
- IAndroidTarget target = mTargetInfo.getTarget();
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
-
- String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
- if (aidl == null || !new File(aidl).isFile()) {
- throw new IllegalStateException("aidl is missing");
- }
-
- AidlProcessor processor = new AidlProcessor(
- aidl,
- target.getPath(IAndroidTarget.ANDROID_AIDL),
- importFolders,
- sourceOutputDir,
- parcelableOutputDir,
- dependencyFileProcessor != null ?
- dependencyFileProcessor : sNoOpDependencyFileProcessor,
- mProcessExecutor,
- processOutputHandler);
-
- processor.processFile(sourceFolder, aidlFile);
- }
-
- /**
- * Compiles all the renderscript files found in the given source folders.
- *
- * Right now this is the only way to compile them as the renderscript compiler requires all
- * renderscript files to be passed for all compilation.
- *
- * Therefore whenever a renderscript file or header changes, all must be recompiled.
- *
- * @param sourceFolders all the source folders to find files to compile
- * @param importFolders all the import folders.
- * @param sourceOutputDir the output dir in which to generate the source code
- * @param resOutputDir the output dir in which to generate the bitcode file
- * @param targetApi the target api
- * @param debugBuild whether the build is debug
- * @param optimLevel the optimization level
- * @param ndkMode
- * @param supportMode support mode flag to generate .so files.
- * @param abiFilters ABI filters in case of support mode
- *
- * @throws IOException
- * @throws InterruptedException
- * @throws LoggedErrorException
- */
- public void compileAllRenderscriptFiles(@NonNull List<File> sourceFolders,
- @NonNull List<File> importFolders,
- @NonNull File sourceOutputDir,
- @NonNull File resOutputDir,
- @NonNull File objOutputDir,
- @NonNull File libOutputDir,
- int targetApi,
- boolean debugBuild,
- int optimLevel,
- boolean ndkMode,
- boolean supportMode,
- @Nullable Set<String> abiFilters,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws InterruptedException, ProcessException, LoggedErrorException, IOException {
- checkNotNull(sourceFolders, "sourceFolders cannot be null.");
- checkNotNull(importFolders, "importFolders cannot be null.");
- checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
- checkNotNull(resOutputDir, "resOutputDir cannot be null.");
- checkState(mTargetInfo != null,
- "Cannot call compileAllRenderscriptFiles() before setTargetInfo() is called.");
-
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
-
- String renderscript = buildToolInfo.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
- if (renderscript == null || !new File(renderscript).isFile()) {
- throw new IllegalStateException("llvm-rs-cc is missing");
- }
-
- RenderScriptProcessor processor = new RenderScriptProcessor(
- sourceFolders,
- importFolders,
- sourceOutputDir,
- resOutputDir,
- objOutputDir,
- libOutputDir,
- buildToolInfo,
- targetApi,
- debugBuild,
- optimLevel,
- ndkMode,
- supportMode,
- abiFilters);
- processor.build(mProcessExecutor, processOutputHandler);
- }
-
- /**
- * Computes and returns the leaf folders based on a given file extension.
- *
- * This looks through all the given root import folders, and recursively search for leaf
- * folders containing files matching the given extensions. All the leaf folders are gathered
- * and returned in the list.
- *
- * @param extension the extension to search for.
- * @param importFolders an array of list of root folders.
- * @return a list of leaf folder, never null.
- */
- @NonNull
- public List<File> getLeafFolders(@NonNull String extension, List<File>... importFolders) {
- List<File> results = Lists.newArrayList();
-
- if (importFolders != null) {
- for (List<File> folders : importFolders) {
- SourceSearcher searcher = new SourceSearcher(folders, extension);
- searcher.setUseExecutor(false);
- LeafFolderGatherer processor = new LeafFolderGatherer();
- try {
- searcher.search(processor);
- } catch (InterruptedException e) {
- // wont happen as we're not using the executor, and our processor
- // doesn't throw those.
- } catch (IOException e) {
- // wont happen as we're not using the executor, and our processor
- // doesn't throw those.
- } catch (LoggedErrorException e) {
- // wont happen as we're not using the executor, and our processor
- // doesn't throw those.
- } catch (ProcessException e) {
- // wont happen as we're not using the executor, and our processor
- // doesn't throw those.
- }
-
- results.addAll(processor.getFolders());
- }
- }
-
- return results;
- }
-
- /**
- * Converts the bytecode to Dalvik format
- * @param inputs the input files
- * @param preDexedLibraries the list of pre-dexed libraries
- * @param outDexFolder the location of the output folder
- * @param dexOptions dex options
- * @param additionalParameters list of additional parameters to give to dx
- * @param incremental true if it should attempt incremental dex if applicable
- *
- * @throws IOException
- * @throws InterruptedException
- * @throws ProcessException
- */
- public void convertByteCode(
- @NonNull Collection<File> inputs,
- @NonNull Collection<File> preDexedLibraries,
- @NonNull File outDexFolder,
- boolean multidex,
- @Nullable File mainDexList,
- @NonNull DexOptions dexOptions,
- @Nullable List<String> additionalParameters,
- @NonNull File tmpFolder,
- boolean incremental,
- boolean optimize,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws IOException, InterruptedException, ProcessException {
- checkNotNull(inputs, "inputs cannot be null.");
- checkNotNull(preDexedLibraries, "preDexedLibraries cannot be null.");
- checkNotNull(outDexFolder, "outDexFolder cannot be null.");
- checkNotNull(dexOptions, "dexOptions cannot be null.");
- checkNotNull(tmpFolder, "tmpFolder cannot be null");
- checkArgument(outDexFolder.isDirectory(), "outDexFolder must be a folder");
- checkArgument(tmpFolder.isDirectory(), "tmpFolder must be a folder");
- checkState(mTargetInfo != null,
- "Cannot call convertByteCode() before setTargetInfo() is called.");
-
- ImmutableList.Builder<File> verifiedInputs = ImmutableList.builder();
- for (File input : inputs) {
- if (checkLibraryClassesJar(input)) {
- verifiedInputs.add(input);
- }
- }
-
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
- DexProcessBuilder builder = new DexProcessBuilder(outDexFolder);
-
- builder.setVerbose(mVerboseExec)
- .setIncremental(incremental)
- .setNoOptimize(!optimize)
- .setMultiDex(multidex)
- .setMainDexList(mainDexList)
- .addInputs(preDexedLibraries)
- .addInputs(verifiedInputs.build());
-
- if (additionalParameters != null) {
- builder.additionalParameters(additionalParameters);
- }
-
- JavaProcessInfo javaProcessInfo = builder.build(buildToolInfo, dexOptions);
-
- ProcessResult result = mJavaProcessExecutor.execute(javaProcessInfo, processOutputHandler);
- result.rethrowFailure().assertNormalExitValue();
- }
-
- public Set<String> createMainDexList(
- @NonNull File allClassesJarFile,
- @NonNull File jarOfRoots) throws ProcessException {
-
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
-
- String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
- if (dx == null || !new File(dx).isFile()) {
- throw new IllegalStateException("dx.jar is missing");
- }
-
- builder.setClasspath(dx);
- builder.setMain("com.android.multidex.ClassReferenceListBuilder");
-
- builder.addArgs(jarOfRoots.getAbsolutePath());
- builder.addArgs(allClassesJarFile.getAbsolutePath());
-
- CachedProcessOutputHandler processOutputHandler = new CachedProcessOutputHandler();
-
- mJavaProcessExecutor.execute(builder.createJavaProcess(), processOutputHandler)
- .rethrowFailure()
- .assertNormalExitValue();
-
- String content = processOutputHandler.getProcessOutput().getStandardOutputAsString();
-
- return Sets.newHashSet(Splitter.on('\n').split(content));
- }
-
- /**
- * Converts the bytecode to Dalvik format
- * @param inputFile the input file
- * @param outFile the output file or folder if multi-dex is enabled.
- * @param multiDex whether multidex is enabled.
- * @param dexOptions dex options
- *
- * @throws IOException
- * @throws InterruptedException
- * @throws ProcessException
- */
- public void preDexLibrary(
- @NonNull File inputFile,
- @NonNull File outFile,
- boolean multiDex,
- @NonNull DexOptions dexOptions,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws IOException, InterruptedException, ProcessException {
- checkState(mTargetInfo != null,
- "Cannot call preDexLibrary() before setTargetInfo() is called.");
-
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
-
- PreDexCache.getCache().preDexLibrary(
- inputFile,
- outFile,
- multiDex,
- dexOptions,
- buildToolInfo,
- mVerboseExec,
- mJavaProcessExecutor,
- processOutputHandler);
- }
-
- /**
- * Converts the bytecode to Dalvik format
- *
- * @param inputFile the input file
- * @param outFile the output file or folder if multi-dex is enabled.
- * @param multiDex whether multidex is enabled.
- * @param dexOptions the dex options
- * @param buildToolInfo the build tools info
- * @param verbose verbose flag
- * @param processExecutor the java process executor
- * @return the list of generated files.
- * @throws ProcessException
- */
- @NonNull
- public static ImmutableList<File> preDexLibrary(
- @NonNull File inputFile,
- @NonNull File outFile,
- boolean multiDex,
- @NonNull DexOptions dexOptions,
- @NonNull BuildToolInfo buildToolInfo,
- boolean verbose,
- @NonNull JavaProcessExecutor processExecutor,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws ProcessException {
- checkNotNull(inputFile, "inputFile cannot be null.");
- checkNotNull(outFile, "outFile cannot be null.");
- checkNotNull(dexOptions, "dexOptions cannot be null.");
-
-
-
- try {
- if (!checkLibraryClassesJar(inputFile)) {
- return ImmutableList.of();
- }
- } catch(IOException e) {
- throw new RuntimeException("Exception while checking library jar", e);
- }
- DexProcessBuilder builder = new DexProcessBuilder(outFile);
-
- builder.setVerbose(verbose)
- .setMultiDex(multiDex)
- .addInput(inputFile);
-
- JavaProcessInfo javaProcessInfo = builder.build(buildToolInfo, dexOptions);
-
- ProcessResult result = processExecutor.execute(javaProcessInfo, processOutputHandler);
- result.rethrowFailure().assertNormalExitValue();
-
- if (multiDex) {
- File[] files = outFile.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File file, String name) {
- return name.endsWith(DOT_DEX);
- }
- });
-
- if (files == null || files.length == 0) {
- throw new RuntimeException("No dex files created at " + outFile.getAbsolutePath());
- }
-
- return ImmutableList.copyOf(files);
- } else {
- return ImmutableList.of(outFile);
- }
- }
-
- /**
- * Returns true if the library (jar or folder) contains class files, false otherwise.
- */
- private static boolean checkLibraryClassesJar(@NonNull File input) throws IOException {
-
- if (!input.exists()) {
- return false;
- }
-
- if (input.isDirectory()) {
- return checkFolder(input);
- }
-
- ZipFile zipFile = null;
- try {
- zipFile = new ZipFile(input);
- Enumeration<? extends ZipEntry> entries = zipFile.entries();
- while(entries.hasMoreElements()) {
- if (entries.nextElement().getName().endsWith(".class")) {
- return true;
- }
- }
- return false;
- } finally {
- if (zipFile != null) {
- zipFile.close();
- }
- }
- }
-
- /**
- * Returns true if this folder or one of its subfolder contains a class file, false otherwise.
- */
- private static boolean checkFolder(@NonNull File folder) {
- File[] subFolders = folder.listFiles();
- if (subFolders != null) {
- for (File childFolder : subFolders) {
- if (childFolder.isFile() && childFolder.getName().endsWith(".class")) {
- return true;
- }
- if (childFolder.isDirectory()) {
- // if childFolder returns false, continue search otherwise return success.
- if (checkFolder(childFolder)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- /**
- * Converts java source code into android byte codes using the jack integration APIs.
- * Jack will run in memory.
- */
- public boolean convertByteCodeUsingJackApis(
- @NonNull File dexOutputFolder,
- @NonNull File jackOutputFile,
- @NonNull Collection<File> classpath,
- @NonNull Collection<File> packagedLibraries,
- @NonNull Collection<File> sourceFiles,
- @Nullable Collection<File> proguardFiles,
- @Nullable File mappingFile,
- @NonNull Collection<File> jarJarRulesFiles,
- @Nullable File incrementalDir,
- @Nullable File javaResourcesFolder,
- boolean multiDex,
- int minSdkVersion) {
-
- BuildToolServiceLoader buildToolServiceLoader
- = BuildToolsServiceLoader.INSTANCE.forVersion(mTargetInfo.getBuildTools());
-
- Api01CompilationTask compilationTask = null;
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- try {
- Optional<JackProvider> jackProvider = buildToolServiceLoader
- .getSingleService(getLogger(), BuildToolsServiceLoader.JACK);
- if (jackProvider.isPresent()) {
- Api01Config config;
-
- // Get configuration object
- try {
- config = jackProvider.get().createConfig(Api01Config.class);
-
- config.setClasspath(new ArrayList<File>(classpath));
- config.setOutputDexDir(dexOutputFolder);
- config.setOutputJackFile(jackOutputFile);
- config.setImportedJackLibraryFiles(new ArrayList<File>(packagedLibraries));
-
- if (proguardFiles != null) {
- config.setProguardConfigFiles(new ArrayList<File>(proguardFiles));
- }
-
- if (!jarJarRulesFiles.isEmpty()) {
- config.setJarJarConfigFiles(ImmutableList.copyOf(jarJarRulesFiles));
- }
-
- if (multiDex) {
- if (minSdkVersion < BuildToolInfo.SDK_LEVEL_FOR_MULTIDEX_NATIVE_SUPPORT) {
- config.setMultiDexKind(MultiDexKind.LEGACY);
- } else {
- config.setMultiDexKind(MultiDexKind.NATIVE);
- }
- }
-
- config.setSourceEntries(new ArrayList<File>(sourceFiles));
- if (mappingFile != null) {
- config.setProperty("jack.obfuscation.mapping.dump", "true");
- config.setObfuscationMappingOutputFile(mappingFile);
- }
-
- config.setProperty("jack.import.resource.policy", "keep-first");
-
- config.setReporter(ReporterKind.DEFAULT, outputStream);
-
- // set the incremental dir if set and either already exists or can be created.
- if (incrementalDir != null) {
- if (!incrementalDir.exists() && !incrementalDir.mkdirs()) {
- mLogger.warning("Cannot create %1$s directory, "
- + "jack incremental support disabled", incrementalDir);
- }
- if (incrementalDir.exists()) {
- config.setIncrementalDir(incrementalDir);
- }
- }
- if (javaResourcesFolder != null) {
- ArrayList<File> folders = Lists.newArrayListWithExpectedSize(3);
- folders.add(javaResourcesFolder);
- config.setResourceDirs(folders);
- }
-
- compilationTask = config.getTask();
- } catch (ConfigNotSupportedException e1) {
- mLogger.warning("Jack APIs v01 not supported");
- } catch (ConfigurationException e) {
- mLogger.error(e,
- "Jack APIs v01 configuration failed, reverting to native process");
- }
- }
-
- if (compilationTask == null) {
- return false;
- }
-
- // Run the compilation
- try {
- compilationTask.run();
- mLogger.info(outputStream.toString());
- return true;
- } catch (CompilationException e) {
- mLogger.error(e, outputStream.toString());
- } catch (UnrecoverableException e) {
- mLogger.error(e,
- "Something out of Jack control has happened: " + e.getMessage());
- } catch (ConfigurationException e) {
- mLogger.error(e, outputStream.toString());
- }
- } catch (ClassNotFoundException e) {
- getLogger().warning("Cannot load Jack APIs v01 " + e.getMessage());
- getLogger().warning("Reverting to native process invocation");
- }
- return false;
- }
-
- public void convertByteCodeWithJack(
- @NonNull File dexOutputFolder,
- @NonNull File jackOutputFile,
- @NonNull String classpath,
- @NonNull Collection<File> packagedLibraries,
- @NonNull File ecjOptionFile,
- @Nullable Collection<File> proguardFiles,
- @Nullable File mappingFile,
- @NonNull Collection<File> jarJarRuleFiles,
- boolean multiDex,
- int minSdkVersion,
- boolean debugLog,
- String javaMaxHeapSize,
- @NonNull ProcessOutputHandler processOutputHandler) throws ProcessException {
- JackProcessBuilder builder = new JackProcessBuilder();
-
- builder.setDebugLog(debugLog)
- .setVerbose(mVerboseExec)
- .setJavaMaxHeapSize(javaMaxHeapSize)
- .setClasspath(classpath)
- .setDexOutputFolder(dexOutputFolder)
- .setJackOutputFile(jackOutputFile)
- .addImportFiles(packagedLibraries)
- .setEcjOptionFile(ecjOptionFile);
-
- if (proguardFiles != null) {
- builder.addProguardFiles(proguardFiles).setMappingFile(mappingFile);
- }
-
- if (multiDex) {
- builder.setMultiDex(true).setMinSdkVersion(minSdkVersion);
- }
-
- if (jarJarRuleFiles != null) {
- builder.setJarJarRuleFiles(jarJarRuleFiles);
- }
-
- mJavaProcessExecutor.execute(
- builder.build(mTargetInfo.getBuildTools()), processOutputHandler)
- .rethrowFailure().assertNormalExitValue();
- }
-
- /**
- * Converts the bytecode of a library to the jack format
- * @param inputFile the input file
- * @param outFile the location of the output classes.dex file
- * @param dexOptions dex options
- *
- * @throws ProcessException
- * @throws IOException
- * @throws InterruptedException
- */
- public void convertLibraryToJack(
- @NonNull File inputFile,
- @NonNull File outFile,
- @NonNull DexOptions dexOptions,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws ProcessException, IOException, InterruptedException {
- checkState(mTargetInfo != null,
- "Cannot call preJackLibrary() before setTargetInfo() is called.");
-
- BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
-
- JackConversionCache.getCache().convertLibrary(
- inputFile,
- outFile,
- dexOptions,
- buildToolInfo,
- mVerboseExec,
- mJavaProcessExecutor,
- processOutputHandler,
- mLogger);
- }
-
- public static List<File> convertLibaryToJackUsingApis(
- @NonNull File inputFile,
- @NonNull File outFile,
- @NonNull DexOptions dexOptions,
- @NonNull BuildToolInfo buildToolInfo,
- boolean verbose,
- @NonNull JavaProcessExecutor processExecutor,
- @NonNull ProcessOutputHandler processOutputHandler,
- @NonNull ILogger logger) throws ProcessException {
-
- BuildToolServiceLoader buildToolServiceLoader = BuildToolsServiceLoader.INSTANCE
- .forVersion(buildToolInfo);
- if (System.getenv("USE_JACK_API") != null) {
- try {
- Optional<JillProvider> jillProviderOptional = buildToolServiceLoader
- .getSingleService(logger, BuildToolsServiceLoader.JILL);
-
- if (jillProviderOptional.isPresent()) {
- com.android.jill.api.v01.Api01Config config =
- jillProviderOptional.get().createConfig(
- com.android.jill.api.v01.Api01Config.class);
-
- config.setInputJavaBinaryFile(inputFile);
- config.setOutputJackFile(outFile);
- config.setVerbose(verbose);
-
- Api01TranslationTask translationTask = config.getTask();
- translationTask.run();
-
- return ImmutableList.of(outFile);
- }
-
- } catch (ClassNotFoundException e) {
- logger.warning("Cannot find the jill tool in the classpath, reverting to native");
- } catch (com.android.jill.api.ConfigNotSupportedException e) {
- logger.warning(e.getMessage() + ", reverting to native");
- } catch (com.android.jill.api.v01.ConfigurationException e) {
- logger.warning(e.getMessage() + ", reverting to native");
- } catch (TranslationException e) {
- logger.error(e, "In process translation failed, reverting to native, file a bug");
- }
- }
- return convertLibraryToJack(inputFile, outFile, dexOptions, buildToolInfo, verbose,
- processExecutor, processOutputHandler, logger);
- }
-
- public static List<File> convertLibraryToJack(
- @NonNull File inputFile,
- @NonNull File outFile,
- @NonNull DexOptions dexOptions,
- @NonNull BuildToolInfo buildToolInfo,
- boolean verbose,
- @NonNull JavaProcessExecutor processExecutor,
- @NonNull ProcessOutputHandler processOutputHandler,
- @NonNull ILogger logger)
- throws ProcessException {
- checkNotNull(inputFile, "inputFile cannot be null.");
- checkNotNull(outFile, "outFile cannot be null.");
- checkNotNull(dexOptions, "dexOptions cannot be null.");
-
- // launch dx: create the command line
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
-
- String jill = buildToolInfo.getPath(BuildToolInfo.PathId.JILL);
- if (jill == null || !new File(jill).isFile()) {
- throw new IllegalStateException("jill.jar is missing");
- }
-
- builder.setClasspath(jill);
- builder.setMain("com.android.jill.Main");
-
- if (dexOptions.getJavaMaxHeapSize() != null) {
- builder.addJvmArg("-Xmx" + dexOptions.getJavaMaxHeapSize());
- }
- builder.addArgs(inputFile.getAbsolutePath());
- builder.addArgs("--output");
- builder.addArgs(outFile.getAbsolutePath());
-
- if (verbose) {
- builder.addArgs("--verbose");
- }
-
- logger.verbose(builder.toString());
- JavaProcessInfo javaProcessInfo = builder.createJavaProcess();
- ProcessResult result = processExecutor.execute(javaProcessInfo, processOutputHandler);
- result.rethrowFailure().assertNormalExitValue();
-
- return Collections.singletonList(outFile);
- }
-
- /**
- * Packages the apk.
- *
- * @param androidResPkgLocation the location of the packaged resource file
- * @param dexFolder the folder with the dex file.
- * @param dexedLibraries optional collection of additional dex files to put in the apk.
- * @param packagedJars the jars that are packaged (libraries + jar dependencies)
- * @param javaResourcesLocation the processed Java resource folder
- * @param jniLibsFolders the folders containing jni shared libraries
- * @param mergingFolder folder to contain files that are being merged
- * @param abiFilters optional ABI filter
- * @param jniDebugBuild whether the app should include jni debug data
- * @param signingConfig the signing configuration
- * @param packagingOptions the packaging options
- * @param outApkLocation location of the APK.
- * @throws DuplicateFileException
- * @throws FileNotFoundException if the store location was not found
- * @throws KeytoolException
- * @throws PackagerException
- * @throws SigningException when the key cannot be read from the keystore
- *
- * @see VariantConfiguration#getPackagedJars()
- */
- public void packageApk(
- @NonNull String androidResPkgLocation,
- @Nullable File dexFolder,
- @NonNull Collection<File> dexedLibraries,
- @NonNull Collection<File> packagedJars,
- @Nullable String javaResourcesLocation,
- @Nullable Collection<File> jniLibsFolders,
- @NonNull File mergingFolder,
- @Nullable Set<String> abiFilters,
- boolean jniDebugBuild,
- @Nullable SigningConfig signingConfig,
- @Nullable PackagingOptions packagingOptions,
- @Nullable SignedJarBuilder.IZipEntryFilter packagingOptionsFilter,
- @NonNull String outApkLocation)
- throws DuplicateFileException, FileNotFoundException,
- KeytoolException, PackagerException, SigningException {
- checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
- checkNotNull(outApkLocation, "outApkLocation cannot be null.");
-
- CertificateInfo certificateInfo = null;
- if (signingConfig != null && signingConfig.isSigningReady()) {
- //noinspection ConstantConditions - isSigningReady() called above.
- certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
- signingConfig.getStoreFile(), signingConfig.getStorePassword(),
- signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
- if (certificateInfo == null) {
- throw new SigningException("Failed to read key from keystore");
- }
- }
-
- try {
- Packager packager = new Packager(
- outApkLocation, androidResPkgLocation, mergingFolder,
- certificateInfo, mCreatedBy, packagingOptions, packagingOptionsFilter, mLogger);
-
- // add dex folder to the apk root.
- if (dexFolder != null) {
- if (!dexFolder.isDirectory()) {
- throw new IllegalArgumentException("dexFolder must be a directory");
- }
- packager.addDexFiles(dexFolder, dexedLibraries);
- }
-
- packager.setJniDebugMode(jniDebugBuild);
-
- if (javaResourcesLocation != null && !packagedJars.isEmpty()) {
- throw new PackagerException("javaResourcesLocation and packagedJars both provided");
- }
- if (javaResourcesLocation != null || !packagedJars.isEmpty()) {
- packager.addResources(javaResourcesLocation != null
- ? new File(javaResourcesLocation) : Iterables.getOnlyElement(packagedJars));
- }
-
- // also add resources from library projects and jars
- if (jniLibsFolders != null) {
- for (File jniFolder : jniLibsFolders) {
- if (jniFolder.isDirectory()) {
- packager.addNativeLibraries(jniFolder, abiFilters);
- }
- }
- }
-
- packager.sealApk();
- } catch (SealedPackageException e) {
- // shouldn't happen since we control the package from start to end.
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Signs a single jar file using the passed {@link SigningConfig}.
- * @param in the jar file to sign.
- * @param signingConfig the signing configuration
- * @param out the file path for the signed jar.
- * @throws IOException
- * @throws KeytoolException
- * @throws SigningException
- * @throws NoSuchAlgorithmException
- * @throws SignedJarBuilder.IZipEntryFilter.ZipAbortException
- * @throws com.android.builder.signing.SigningException
- */
- public void signApk(File in, SigningConfig signingConfig, File out)
- throws IOException, KeytoolException, SigningException, NoSuchAlgorithmException,
- SignedJarBuilder.IZipEntryFilter.ZipAbortException,
- com.android.builder.signing.SigningException {
-
- CertificateInfo certificateInfo = null;
- if (signingConfig != null && signingConfig.isSigningReady()) {
- //noinspection ConstantConditions - isSigningReady() called above.
- certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
- signingConfig.getStoreFile(), signingConfig.getStorePassword(),
- signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
- if (certificateInfo == null) {
- throw new SigningException("Failed to read key from keystore");
- }
- }
-
- SignedJarBuilder signedJarBuilder = new SignedJarBuilder(
- new FileOutputStream(out),
- certificateInfo != null ? certificateInfo.getKey() : null,
- certificateInfo != null ? certificateInfo.getCertificate() : null,
- Packager.getLocalVersion(), mCreatedBy);
-
-
- signedJarBuilder.writeZip(new FileInputStream(in));
- signedJarBuilder.close();
-
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/BuildToolsServiceLoader.java b/base/build-system/builder/src/main/java/com/android/builder/core/BuildToolsServiceLoader.java
deleted file mode 100644
index bdbafb9..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/BuildToolsServiceLoader.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.jack.api.JackProvider;
-import com.android.jill.api.JillProvider;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ServiceLoader;
-
-/**
- * {@link ServiceLoader} helpers for tools located in the SDK's build-tools folders.
- *
- * This utility will cache {@link ServiceLoader} instances per build-tools version and per target
- * service type.
- */
-public enum BuildToolsServiceLoader {
-
- /**
- * Singleton instance to request {@link ServiceLoader} instances from.
- */
- INSTANCE;
-
- /**
- * private cache data for a particular build-tool version.
- */
- private static final class LoadedBuildTool {
- private final FullRevision version;
- private final BuildToolServiceLoader serviceLoader;
-
- private LoadedBuildTool(FullRevision version,
- BuildToolServiceLoader serviceLoader) {
- this.version = version;
- this.serviceLoader = serviceLoader;
- }
- }
-
- private final List<LoadedBuildTool> loadedBuildTools = new ArrayList<LoadedBuildTool>();
-
- /**
- * Load a built-tools version specific {@link ServiceLoader} helper.
- * @param buildToolInfo the requested build-tools information
- * @return an initialized {@link BuildToolsServiceLoader.BuildToolServiceLoader} to get
- * instances of {@link ServiceLoader} from.
- */
- @NonNull
- public synchronized BuildToolServiceLoader forVersion(BuildToolInfo buildToolInfo) {
-
- Optional<LoadedBuildTool> loadedBuildToolOptional =
- findVersion(buildToolInfo.getRevision());
-
- if (loadedBuildToolOptional.isPresent()) {
- return loadedBuildToolOptional.get().serviceLoader;
- }
-
- LoadedBuildTool loadedBuildTool = new LoadedBuildTool(buildToolInfo.getRevision(),
- new BuildToolServiceLoader(buildToolInfo));
- loadedBuildTools.add(loadedBuildTool);
- return loadedBuildTool.serviceLoader;
- }
-
- @NonNull
- private Optional<LoadedBuildTool> findVersion(FullRevision version) {
- for (LoadedBuildTool loadedBuildTool : loadedBuildTools) {
- if (loadedBuildTool.version.equals(version)) {
- return Optional.of(loadedBuildTool);
- }
- }
- return Optional.absent();
- }
-
- /**
- * Abstract notion of what a service is. A service must be declared in one of the classpath
- * provided jar files. The service declaration must conforms to {@link ServiceLoader} contract.
- *
- * @param <T> the type of service.
- */
- public static class Service<T> {
-
- private final Collection<String> classpath;
- private final Class<T> serviceClass;
-
- protected Service(Collection<String> classpath, Class<T> serviceClass) {
- this.classpath = classpath;
- this.serviceClass = serviceClass;
- }
-
- public Collection<String> getClasspath() {
- return classpath;
- }
- public Class<T> getServiceClass() {
- return serviceClass;
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this)
- .add("serviceClass", serviceClass)
- .add("classpath", Joiner.on(",").join(classpath))
- .toString();
- }
- }
-
- /**
- * Jack service description.
- */
- public static final Service<JackProvider> JACK =
- new Service<JackProvider>(ImmutableList.of("jack.jar"), JackProvider.class);
-
- /**
- * Jill service description.
- */
- public static final Service<JillProvider> JILL =
- new Service<JillProvider>(ImmutableList.of("jill.jar"), JillProvider.class);
-
- /**
- * build-tools version specific {@link ServiceLoader} helper.
- */
- public static final class BuildToolServiceLoader {
-
- /**
- * private cache data for a single {@link ServiceLoader} instance.
- * @param <T> the service loader type.
- */
- private static final class LoadedServiceLoader<T> {
- private final Class<T> serviceType;
- private final ServiceLoader<T> serviceLoader;
-
- private LoadedServiceLoader(Class<T> serviceType, ServiceLoader<T> serviceLoader) {
- this.serviceType = serviceType;
- this.serviceLoader = serviceLoader;
- }
- }
-
- private final BuildToolInfo buildToolInfo;
- private final List<LoadedServiceLoader> loadedServicesLoaders =
- new ArrayList<LoadedServiceLoader>();
-
- private BuildToolServiceLoader(BuildToolInfo buildToolInfo) {
- this.buildToolInfo = buildToolInfo;
- }
-
- /**
- * Returns a newly allocated or existing {@link ServiceLoader} instance for the passed
- * {@link com.android.builder.core.BuildToolsServiceLoader.Service} type in the context
- * of the build-tools version this instance was created for.
- *
- * @param serviceType the requested service type encapsulation.
- * @param <T> the type of service
- * @return a {@link ServiceLoader} instance for the T service type.
- * @throws ClassNotFoundException
- */
- @NonNull
- public synchronized <T> ServiceLoader<T> getServiceLoader(Service<T> serviceType)
- throws ClassNotFoundException {
-
- Optional<ServiceLoader<T>> serviceLoaderOptional =
- getLoadedServiceLoader(serviceType.getServiceClass());
- if (serviceLoaderOptional.isPresent()) {
- return serviceLoaderOptional.get();
- }
-
- File buildToolLocation = buildToolInfo.getLocation();
- if (System.getenv("USE_JACK_LOCATION") != null) {
- buildToolLocation = new File(System.getenv("USE_JACK_LOCATION"));
- }
- URL[] urls = new URL[serviceType.classpath.size()];
- int i = 0;
- for (String classpathItem : serviceType.getClasspath()) {
- File jarFile = new File(buildToolLocation, classpathItem);
- try {
- urls[i++] = jarFile.toURI().toURL();
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
- }
- }
- ClassLoader cl = new URLClassLoader(urls, serviceType.getServiceClass().getClassLoader());
- ServiceLoader<T> serviceLoader = ServiceLoader.load(serviceType.getServiceClass(), cl);
- loadedServicesLoaders.add(new LoadedServiceLoader<T>(
- serviceType.getServiceClass(), serviceLoader));
- return serviceLoader;
- }
-
- /**
- * Return the first service instance for the requested service type or
- * {@link Optional#absent()} if none exist.
- * @param logger to log resolution.
- * @param serviceType the requested service type encapsulation.
- * @param <T> the requested service class type.
- * @return the instance of T or null of none exist in this context.
- * @throws ClassNotFoundException
- */
- @NonNull
- public synchronized <T> Optional<T> getSingleService(
- ILogger logger,
- Service<T> serviceType) throws ClassNotFoundException {
- logger.verbose("Looking for %1$s", serviceType);
- ServiceLoader<T> serviceLoader = getServiceLoader(serviceType);
- logger.verbose("Got a serviceLoader %1$d",
- Integer.toHexString(System.identityHashCode(serviceLoader)));
- Iterator<T> serviceIterator = serviceLoader.iterator();
- logger.verbose("Service Iterator = %1$s ", serviceIterator);
- if (serviceIterator.hasNext()) {
- T service = serviceIterator.next();
- logger.verbose("Got it from %1$s, loaded service = %2$s, type = %3$s",
- serviceIterator, service, service.getClass());
- return Optional.of(service);
- } else {
- logger.info("Cannot find service implementation %1$s" + serviceType);
- return Optional.absent();
- }
- }
-
- @NonNull
- @SuppressWarnings("unchecked")
- private <T> Optional<ServiceLoader<T>> getLoadedServiceLoader(Class<T> serviceType) {
- for (LoadedServiceLoader<?> loadedServiceLoader : loadedServicesLoaders) {
- if (loadedServiceLoader.serviceType.equals(serviceType)) {
- return Optional.of((ServiceLoader<T>) loadedServiceLoader.serviceLoader);
- }
- }
- return Optional.absent();
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java b/base/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java
deleted file mode 100644
index 3fb7a3d..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.ApiVersion;
-
-/**
- * Basic implementation of ApiVersion
- */
-public class DefaultApiVersion implements ApiVersion {
-
- private final int mApiLevel;
-
- @Nullable
- private final String mCodename;
-
- public DefaultApiVersion(int apiLevel, @Nullable String codename) {
- mApiLevel = apiLevel;
- mCodename = codename;
- }
-
- public DefaultApiVersion(int apiLevel) {
- this(apiLevel, null);
- }
-
- public DefaultApiVersion(@NonNull String codename) {
- this(1, codename);
- }
-
- @NonNull
- public static ApiVersion create(@NonNull Object value) {
- if (value instanceof Integer) {
- return new DefaultApiVersion((Integer) value, null);
- } else if (value instanceof String) {
- return new DefaultApiVersion(1, (String) value);
- }
-
- return new DefaultApiVersion(1, null);
- }
-
- @Override
- public int getApiLevel() {
- return mApiLevel;
- }
-
- @Nullable
- @Override
- public String getCodename() {
- return mCodename;
- }
-
- @NonNull
- @Override
- public String getApiString() {
- return mCodename != null ? mCodename : Integer.toString(mApiLevel);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- DefaultApiVersion that = (DefaultApiVersion) o;
-
- if (mApiLevel != that.mApiLevel) {
- return false;
- }
- if (mCodename != null ? !mCodename.equals(that.mCodename) : that.mCodename != null) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = mApiLevel;
- result = 31 * result + (mCodename != null ? mCodename.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "ApiVersionImpl{" +
- "mApiLevel=" + mApiLevel +
- ", mCodename='" + mCodename + '\'' +
- '}';
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java b/base/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java
deleted file mode 100644
index 412d460..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.internal.BaseConfigImpl;
-import com.android.builder.model.BuildType;
-import com.android.builder.model.SigningConfig;
-import com.google.common.base.Objects;
-
-public class DefaultBuildType extends BaseConfigImpl implements BuildType {
- private static final long serialVersionUID = 1L;
-
- private final String mName;
- private boolean mDebuggable = false;
- private boolean mPseudoLocalesEnabled = false;
- private boolean mTestCoverageEnabled = false;
- private boolean mJniDebuggable = false;
- private boolean mRenderscriptDebuggable = false;
- private int mRenderscriptOptimLevel = 3;
- private String mApplicationIdSuffix = null;
- private String mVersionNameSuffix = null;
- private boolean mMinifyEnabled = false;
- private SigningConfig mSigningConfig = null;
- private boolean mEmbedMicroApp = true;
-
- private boolean mZipAlignEnabled = true;
-
- public DefaultBuildType(@NonNull String name) {
- mName = name;
- }
-
- public DefaultBuildType initWith(DefaultBuildType that) {
- _initWith(that);
-
- setDebuggable(that.isDebuggable());
- setTestCoverageEnabled(that.isTestCoverageEnabled());
- setJniDebuggable(that.isJniDebuggable());
- setRenderscriptDebuggable(that.isRenderscriptDebuggable());
- setRenderscriptOptimLevel(that.getRenderscriptOptimLevel());
- setApplicationIdSuffix(that.getApplicationIdSuffix());
- setVersionNameSuffix(that.getVersionNameSuffix());
- setMinifyEnabled(that.isMinifyEnabled() );
- setZipAlignEnabled(that.isZipAlignEnabled());
- setSigningConfig(that.getSigningConfig());
- setEmbedMicroApp(that.isEmbedMicroApp());
- setPseudoLocalesEnabled(that.isPseudoLocalesEnabled());
-
- return this;
- }
-
- /**
- * Name of this build type.
- */
- @Override
- @NonNull
- public String getName() {
- return mName;
- }
-
- /** Whether this build type should generate a debuggable apk. */
- @NonNull
- public BuildType setDebuggable(boolean debuggable) {
- mDebuggable = debuggable;
- return this;
- }
-
- /** Whether this build type should generate a debuggable apk. */
- @Override
- public boolean isDebuggable() {
- // Accessing coverage data requires a debuggable package.
- return mDebuggable || mTestCoverageEnabled;
- }
-
-
- public void setTestCoverageEnabled(boolean testCoverageEnabled) {
- mTestCoverageEnabled = testCoverageEnabled;
- }
-
- /**
- * Whether test coverage is enabled for this build type.
- *
- * <p>If enabled this uses Jacoco to capture coverage and creates a report in the build
- * directory.
- *
- * <p>The version of Jacoco can be configured with:
- * <pre>
- * android {
- * jacoco {
- * version = '0.6.2.201302030002'
- * }
- * }
- * </pre>
- *
- */
- @Override
- public boolean isTestCoverageEnabled() {
- return mTestCoverageEnabled;
- }
-
- public void setPseudoLocalesEnabled(boolean pseudoLocalesEnabled) {
- mPseudoLocalesEnabled = pseudoLocalesEnabled;
- }
-
- /**
- * Whether to generate pseudo locale in the APK.
- *
- * <p>If enabled, 2 fake pseudo locales (en-XA and ar-XB) will be added to the APK to help
- * test internationalization support in the app.
- */
- @Override
- public boolean isPseudoLocalesEnabled() {
- return mPseudoLocalesEnabled;
- }
-
- /**
- * Whether this build type is configured to generate an APK with debuggable native code.
- */
- @NonNull
- public BuildType setJniDebuggable(boolean jniDebugBuild) {
- mJniDebuggable = jniDebugBuild;
- return this;
- }
-
- /**
- * Whether this build type is configured to generate an APK with debuggable native code.
- */
- @Override
- public boolean isJniDebuggable() {
- return mJniDebuggable;
- }
-
- /**
- * Whether the build type is configured to generate an apk with debuggable RenderScript code.
- */
- @Override
- public boolean isRenderscriptDebuggable() {
- return mRenderscriptDebuggable;
- }
-
- /**
- * Whether the build type is configured to generate an apk with debuggable RenderScript code.
- */
- public BuildType setRenderscriptDebuggable(boolean renderscriptDebugBuild) {
- mRenderscriptDebuggable = renderscriptDebugBuild;
- return this;
- }
-
- /**
- * Optimization level to use by the renderscript compiler.
- */
- @Override
- public int getRenderscriptOptimLevel() {
- return mRenderscriptOptimLevel;
- }
-
- /** Optimization level to use by the renderscript compiler. */
- public void setRenderscriptOptimLevel(int renderscriptOptimLevel) {
- mRenderscriptOptimLevel = renderscriptOptimLevel;
- }
-
- /**
- * Application id suffix applied to this build type.
- */
- @NonNull
- public BuildType setApplicationIdSuffix(@Nullable String applicationIdSuffix) {
- mApplicationIdSuffix = applicationIdSuffix;
- return this;
- }
-
- /**
- * Application id suffix applied to this build type.
- */
- @Override
- @Nullable
- public String getApplicationIdSuffix() {
- return mApplicationIdSuffix;
- }
-
- /** Version name suffix. */
- @NonNull
- public BuildType setVersionNameSuffix(@Nullable String versionNameSuffix) {
- mVersionNameSuffix = versionNameSuffix;
- return this;
- }
-
- /** Version name suffix. */
- @Override
- @Nullable
- public String getVersionNameSuffix() {
- return mVersionNameSuffix;
- }
-
- /** Whether Minify is enabled for this build type. */
- @NonNull
- public BuildType setMinifyEnabled(boolean enabled) {
- mMinifyEnabled = enabled;
- return this;
- }
-
- /** Whether Minify is enabled for this build type. */
- @Override
- public boolean isMinifyEnabled() {
- return mMinifyEnabled;
- }
-
-
- /** Whether zipalign is enabled for this build type. */
- @NonNull
- public BuildType setZipAlignEnabled(boolean zipAlign) {
- mZipAlignEnabled = zipAlign;
- return this;
- }
-
- /** Whether zipalign is enabled for this build type. */
- @Override
- public boolean isZipAlignEnabled() {
- return mZipAlignEnabled;
- }
-
- /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
- @NonNull
- public BuildType setSigningConfig(@Nullable SigningConfig signingConfig) {
- mSigningConfig = signingConfig;
- return this;
- }
-
- /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
- @Override
- @Nullable
- public SigningConfig getSigningConfig() {
- return mSigningConfig;
- }
-
- /**
- * Whether a linked Android Wear app should be embedded in variant using this build type.
- *
- * <p>Wear apps can be linked with the following code:
- *
- * <pre>
- * dependencies {
- * freeWearApp project(:wear:free') // applies to variant using the free flavor
- * wearApp project(':wear:base') // applies to all other variants
- * }
- * </pre>
- */
- @Override
- public boolean isEmbedMicroApp() {
- return mEmbedMicroApp;
- }
-
- public void setEmbedMicroApp(boolean embedMicroApp) {
- mEmbedMicroApp = embedMicroApp;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
-
- DefaultBuildType buildType = (DefaultBuildType) o;
-
- return Objects.equal(mName, buildType.mName) &&
- mDebuggable == buildType.mDebuggable &&
- mTestCoverageEnabled == buildType.mTestCoverageEnabled &&
- mJniDebuggable == buildType.mJniDebuggable &&
- mPseudoLocalesEnabled == buildType.mPseudoLocalesEnabled &&
- mRenderscriptDebuggable == buildType.mRenderscriptDebuggable &&
- mRenderscriptOptimLevel == buildType.mRenderscriptOptimLevel &&
- mMinifyEnabled == buildType.mMinifyEnabled &&
- mZipAlignEnabled == buildType.mZipAlignEnabled &&
- mEmbedMicroApp == buildType.mEmbedMicroApp &&
- Objects.equal(mApplicationIdSuffix, buildType.mApplicationIdSuffix) &&
- Objects.equal(mVersionNameSuffix, buildType.mVersionNameSuffix) &&
- Objects.equal(mSigningConfig, buildType.mSigningConfig);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(
- super.hashCode(),
- mName,
- mDebuggable,
- mTestCoverageEnabled,
- mJniDebuggable,
- mPseudoLocalesEnabled,
- mRenderscriptDebuggable,
- mRenderscriptOptimLevel,
- mApplicationIdSuffix,
- mVersionNameSuffix,
- mMinifyEnabled,
- mZipAlignEnabled,
- mSigningConfig,
- mEmbedMicroApp);
- }
-
- @Override
- @NonNull
- public String toString() {
- return Objects.toStringHelper(this)
- .add("name", mName)
- .add("debuggable", mDebuggable)
- .add("testCoverageEnabled", mTestCoverageEnabled)
- .add("jniDebuggable", mJniDebuggable)
- .add("pseudoLocalesEnabled", mPseudoLocalesEnabled)
- .add("renderscriptDebuggable", mRenderscriptDebuggable)
- .add("renderscriptOptimLevel", mRenderscriptOptimLevel)
- .add("applicationIdSuffix", mApplicationIdSuffix)
- .add("versionNameSuffix", mVersionNameSuffix)
- .add("minifyEnabled", mMinifyEnabled)
- .add("zipAlignEnabled", mZipAlignEnabled)
- .add("signingConfig", mSigningConfig)
- .add("embedMicroApp", mEmbedMicroApp)
- .add("mBuildConfigFields", getBuildConfigFields())
- .add("mResValues", getResValues())
- .add("mProguardFiles", getProguardFiles())
- .add("mConsumerProguardFiles", getConsumerProguardFiles())
- .add("mManifestPlaceholders", getManifestPlaceholders())
- .toString();
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java b/base/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java
deleted file mode 100644
index 0d1f9f1..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.io.FileWrapper;
-import com.android.io.StreamException;
-import com.android.utils.XmlUtils;
-import com.android.xml.AndroidManifest;
-import com.android.xml.AndroidXPathFactory;
-import com.google.common.base.Optional;
-
-import org.apache.http.annotation.ThreadSafe;
-import org.xml.sax.InputSource;
-
-import java.io.File;
-import java.io.IOException;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpressionException;
-
- at ThreadSafe
-class DefaultManifestParser implements ManifestParser {
-
- Optional<Object> mMinSdkVersion;
- Optional<Object> mTargetSdkVersion;
- Optional<Integer> mVersionCode;
- Optional<String> mPackage;
- Optional<String> mVersionName;
-
- @Nullable
- @Override
- public synchronized String getPackage(@NonNull File manifestFile) {
- if (mPackage == null) {
- mPackage = Optional.fromNullable(getStringValue(manifestFile, "/manifest/@package"));
- }
- return mPackage.orNull();
- }
-
- @Nullable
- @Override
- public synchronized String getVersionName(@NonNull File manifestFile) {
- if (mVersionName == null) {
- mVersionName = Optional.fromNullable(
- getStringValue(manifestFile, "/manifest/@android:versionName"));
- }
- return mVersionName.orNull();
- }
-
- @Override
- @NonNull
- public synchronized int getVersionCode(@NonNull File manifestFile) {
- if (mVersionCode == null) {
- mVersionCode = Optional.absent();
- try {
- String value = getStringValue(manifestFile, "/manifest/@android:versionCode");
- if (value != null) {
- mVersionCode = Optional.of(Integer.valueOf(value));
- }
- } catch (NumberFormatException ignored) {
- }
- }
- return mVersionCode.or(-1);
- }
-
- @Override
- @NonNull
- public synchronized Object getMinSdkVersion(@NonNull File manifestFile) {
- if (mMinSdkVersion == null) {
- try {
- mMinSdkVersion = Optional.fromNullable(
- AndroidManifest.getMinSdkVersion(new FileWrapper(manifestFile)));
- } catch (XPathExpressionException e) {
- throw new RuntimeException(e);
- } catch (StreamException e) {
- throw new RuntimeException(e);
- }
- }
- return mMinSdkVersion.or(1);
- }
-
- @Override
- @NonNull
- public Object getTargetSdkVersion(@NonNull File manifestFile) {
- if (mTargetSdkVersion == null) {
- try {
- mTargetSdkVersion =
- Optional.fromNullable(AndroidManifest.getTargetSdkVersion(
- new FileWrapper(manifestFile)));
- } catch (XPathExpressionException e) {
- return new RuntimeException(e);
- } catch (StreamException e) {
- throw new RuntimeException(e);
- }
- }
- return mTargetSdkVersion.or(-1);
- }
-
- private static String getStringValue(@NonNull File file, @NonNull String xPath) {
- XPath xpath = AndroidXPathFactory.newXPath();
-
- try {
- InputSource source = new InputSource(XmlUtils.getUtfReader(file));
- return xpath.evaluate(xPath, source);
- } catch (XPathExpressionException e) {
- // won't happen.
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- return null;
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java b/base/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java
deleted file mode 100644
index 6bed799..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java
+++ /dev/null
@@ -1,659 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.internal.BaseConfigImpl;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SigningConfig;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * The configuration of a product flavor.
- *
- * This is also used to describe the default configuration of all builds, even those that
- * do not contain any flavors.
- */
-public class DefaultProductFlavor extends BaseConfigImpl implements ProductFlavor {
- private static final long serialVersionUID = 1L;
-
- private final String mName;
- @Nullable
- private String mDimension;
- @Nullable
- private ApiVersion mMinSdkVersion;
- @Nullable
- private ApiVersion mTargetSdkVersion;
- @Nullable
- private Integer mMaxSdkVersion;
- @Nullable
- private Integer mRenderscriptTargetApi;
- @Nullable
- private Boolean mRenderscriptSupportModeEnabled;
- @Nullable
- private Boolean mRenderscriptNdkModeEnabled;
- @Nullable
- private Integer mVersionCode;
- @Nullable
- private String mVersionName;
- @Nullable
- private String mApplicationId;
- @Nullable
- private String mTestApplicationId;
- @Nullable
- private String mTestInstrumentationRunner;
- @NonNull
- private Map<String, String> mTestInstrumentationRunnerArguments = Maps.newHashMap();
- @Nullable
- private Boolean mTestHandleProfiling;
- @Nullable
- private Boolean mTestFunctionalTest;
- @Nullable
- private SigningConfig mSigningConfig;
- @Nullable
- private Set<String> mResourceConfiguration;
-
- /**
- * Creates a ProductFlavor with a given name.
- *
- * Names can be important when dealing with flavor groups.
- * @param name the name of the flavor.
- *
- * @see BuilderConstants#MAIN
- */
- public DefaultProductFlavor(@NonNull String name) {
- mName = name;
- }
-
- @Override
- @NonNull
- public String getName() {
- return mName;
- }
-
- public void setDimension(@NonNull String dimension) {
- mDimension = dimension;
- }
-
- /** Name of the dimension this product flavor belongs to. */
- @Nullable
- @Override
- public String getDimension() {
- return mDimension;
- }
-
- /**
- * Sets the application id.
- */
- @NonNull
- public ProductFlavor setApplicationId(String applicationId) {
- mApplicationId = applicationId;
- return this;
- }
-
- /**
- * Returns the application ID.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename">ApplicationId versus PackageName</a>
- */
- @Override
- @Nullable
- public String getApplicationId() {
- return mApplicationId;
- }
-
- /**
- * Sets the version code.
- *
- * @param versionCode the version code
- * @return the flavor object
- */
- @NonNull
- public ProductFlavor setVersionCode(Integer versionCode) {
- mVersionCode = versionCode;
- return this;
- }
-
- /**
- * Version code.
- *
- * <p>See <a href="http://developer.android.com/tools/publishing/versioning.html">Versioning Your Application</a>
- */
- @Override
- @Nullable
- public Integer getVersionCode() {
- return mVersionCode;
- }
-
- /**
- * Sets the version name.
- *
- * @param versionName the version name
- * @return the flavor object
- */
- @NonNull
- public ProductFlavor setVersionName(String versionName) {
- mVersionName = versionName;
- return this;
- }
-
- /**
- * Version name.
- *
- * <p>See <a href="http://developer.android.com/tools/publishing/versioning.html">Versioning Your Application</a>
- */
- @Override
- @Nullable
- public String getVersionName() {
- return mVersionName;
- }
-
- /**
- * Sets the minSdkVersion to the given value.
- */
- @NonNull
- public ProductFlavor setMinSdkVersion(ApiVersion minSdkVersion) {
- mMinSdkVersion = minSdkVersion;
- return this;
- }
-
- /**
- * Min SDK version.
- */
- @Nullable
- @Override
- public ApiVersion getMinSdkVersion() {
- return mMinSdkVersion;
- }
-
- /** Sets the targetSdkVersion to the given value. */
- @NonNull
- public ProductFlavor setTargetSdkVersion(@Nullable ApiVersion targetSdkVersion) {
- mTargetSdkVersion = targetSdkVersion;
- return this;
- }
-
- /**
- * Target SDK version.
- */
- @Nullable
- @Override
- public ApiVersion getTargetSdkVersion() {
- return mTargetSdkVersion;
- }
-
- @NonNull
- public ProductFlavor setMaxSdkVersion(Integer maxSdkVersion) {
- mMaxSdkVersion = maxSdkVersion;
- return this;
- }
-
- @Nullable
- @Override
- public Integer getMaxSdkVersion() {
- return mMaxSdkVersion;
- }
-
- @Override
- @Nullable
- public Integer getRenderscriptTargetApi() {
- return mRenderscriptTargetApi;
- }
-
- /** Sets the renderscript target API to the given value. */
- public void setRenderscriptTargetApi(Integer renderscriptTargetApi) {
- mRenderscriptTargetApi = renderscriptTargetApi;
- }
-
- @Override
- @Nullable
- public Boolean getRenderscriptSupportModeEnabled() {
- return mRenderscriptSupportModeEnabled;
- }
-
- /**
- * Sets whether the renderscript code should be compiled in support mode to make it compatible
- * with older versions of Android.
- */
- public ProductFlavor setRenderscriptSupportModeEnabled(Boolean renderscriptSupportMode) {
- mRenderscriptSupportModeEnabled = renderscriptSupportMode;
- return this;
- }
-
- @Override
- @Nullable
- public Boolean getRenderscriptNdkModeEnabled() {
- return mRenderscriptNdkModeEnabled;
- }
-
-
- /** Sets whether the renderscript code should be compiled to generate C/C++ bindings. */
- public ProductFlavor setRenderscriptNdkModeEnabled(Boolean renderscriptNdkMode) {
- mRenderscriptNdkModeEnabled = renderscriptNdkMode;
- return this;
- }
-
- /** Sets the test application ID. */
- @NonNull
- public ProductFlavor setTestApplicationId(String applicationId) {
- mTestApplicationId = applicationId;
- return this;
- }
-
- /**
- * Test application ID.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename">ApplicationId versus PackageName</a>
- */
- @Override
- @Nullable
- public String getTestApplicationId() {
- return mTestApplicationId;
- }
-
- /** Sets the test instrumentation runner to the given value. */
- @NonNull
- public ProductFlavor setTestInstrumentationRunner(String testInstrumentationRunner) {
- mTestInstrumentationRunner = testInstrumentationRunner;
- return this;
- }
-
- /**
- * Test instrumentation runner class name.
- *
- * <p>This is a fully qualified class name of the runner, e.g.
- * <code>android.test.InstrumentationTestRunner</code>
- *
- * <p>See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
- * instrumentation</a>.
- */
- @Override
- @Nullable
- public String getTestInstrumentationRunner() {
- return mTestInstrumentationRunner;
- }
-
- /** Sets the test instrumentation runner custom arguments. */
- @NonNull
- public ProductFlavor setTestInstrumentationRunnerArguments(
- @NonNull Map<String, String> testInstrumentationRunnerArguments) {
- mTestInstrumentationRunnerArguments = checkNotNull(testInstrumentationRunnerArguments);
- return this;
- }
-
- /**
- * Test instrumentation runner custom arguments.
- *
- * e.g. <code>[key: "value"]</code> will give
- * <code>adb shell am instrument -w <b>-e key value</b> com.example</code>...".
- *
- * <p>See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
- * instrumentation</a>.
- *
- * <p>Test runner arguments can also be specified from the command line:
- *
- * <p><pre>
- * INSTRUMENTATION_TEST_RUNNER_ARGS=size=medium,foo=bar ./gradlew connectedAndroidTest
- * ./gradlew connectedAndroidTest -Pcom.android.tools.instrumentationTestRunnerArgs=size=medium,foo=bar
- * </pre>
- */
- @Override
- @NonNull
- public Map<String, String> getTestInstrumentationRunnerArguments() {
- return mTestInstrumentationRunnerArguments;
- }
-
- /**
- * See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
- * instrumentation</a>.
- */
- @Override
- @Nullable
- public Boolean getTestHandleProfiling() {
- return mTestHandleProfiling;
- }
-
- @NonNull
- public ProductFlavor setTestHandleProfiling(boolean handleProfiling) {
- mTestHandleProfiling = handleProfiling;
- return this;
- }
-
- /**
- * See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
- * instrumentation</a>.
- */
- @Override
- @Nullable
- public Boolean getTestFunctionalTest() {
- return mTestFunctionalTest;
- }
-
- @NonNull
- public ProductFlavor setTestFunctionalTest(boolean functionalTest) {
- mTestFunctionalTest = functionalTest;
- return this;
- }
-
- /**
- * Signing config used by this product flavor.
- */
- @Override
- @Nullable
- public SigningConfig getSigningConfig() {
- return mSigningConfig;
- }
-
- /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
- @NonNull
- public ProductFlavor setSigningConfig(SigningConfig signingConfig) {
- mSigningConfig = signingConfig;
- return this;
- }
-
- /**
- * Adds a res config filter (for instance 'hdpi')
- */
- public void addResourceConfiguration(@NonNull String configuration) {
- if (mResourceConfiguration == null) {
- mResourceConfiguration = Sets.newHashSet();
- }
-
- mResourceConfiguration.add(configuration);
- }
-
- /**
- * Adds a res config filter (for instance 'hdpi')
- */
- public void addResourceConfigurations(@NonNull String... configurations) {
- if (mResourceConfiguration == null) {
- mResourceConfiguration = Sets.newHashSet();
- }
-
- mResourceConfiguration.addAll(Arrays.asList(configurations));
- }
-
- /**
- * Adds a res config filter (for instance 'hdpi')
- */
- public void addResourceConfigurations(@NonNull Collection<String> configurations) {
- if (mResourceConfiguration == null) {
- mResourceConfiguration = Sets.newHashSet();
- }
-
- mResourceConfiguration.addAll(configurations);
- }
-
- /**
- * Adds a res config filter (for instance 'hdpi')
- */
- @NonNull
- @Override
- public Collection<String> getResourceConfigurations() {
- if (mResourceConfiguration == null) {
- mResourceConfiguration = Sets.newHashSet();
- }
-
- return mResourceConfiguration;
- }
-
- /**
- * Merges two flavors on top of one another and returns a new object with the result.
- *
- * The behavior is that if a value is present in the overlay, then it is used, otherwise
- * we use the value from the base.
- *
- * @param base the flavor to merge on top of
- * @param overlay the flavor to apply on top of the base.
- *
- * @return a new ProductFlavor that represents the merge.
- */
- @NonNull
- static ProductFlavor mergeFlavors(@NonNull ProductFlavor base, @NonNull ProductFlavor overlay) {
- DefaultProductFlavor flavor = new DefaultProductFlavor("");
-
- flavor.mMinSdkVersion = chooseNotNull(
- overlay.getMinSdkVersion(),
- base.getMinSdkVersion());
- flavor.mTargetSdkVersion = chooseNotNull(
- overlay.getTargetSdkVersion(),
- base.getTargetSdkVersion());
- flavor.mMaxSdkVersion = chooseNotNull(
- overlay.getMaxSdkVersion(),
- base.getMaxSdkVersion());
-
- flavor.mRenderscriptTargetApi = chooseNotNull(
- overlay.getRenderscriptTargetApi(),
- base.getRenderscriptTargetApi());
- flavor.mRenderscriptSupportModeEnabled = chooseNotNull(
- overlay.getRenderscriptSupportModeEnabled(),
- base.getRenderscriptSupportModeEnabled());
- flavor.mRenderscriptNdkModeEnabled = chooseNotNull(
- overlay.getRenderscriptNdkModeEnabled(),
- base.getRenderscriptNdkModeEnabled());
-
- flavor.mVersionCode = chooseNotNull(overlay.getVersionCode(), base.getVersionCode());
- flavor.mVersionName = chooseNotNull(overlay.getVersionName(), base.getVersionName());
-
- flavor.mApplicationId = chooseNotNull(overlay.getApplicationId(), base.getApplicationId());
-
- flavor.mTestApplicationId = chooseNotNull(
- overlay.getTestApplicationId(),
- base.getTestApplicationId());
- flavor.mTestInstrumentationRunner = chooseNotNull(
- overlay.getTestInstrumentationRunner(),
- base.getTestInstrumentationRunner());
-
- flavor.mTestInstrumentationRunnerArguments.putAll(
- base.getTestInstrumentationRunnerArguments());
- flavor.mTestInstrumentationRunnerArguments.putAll(
- overlay.getTestInstrumentationRunnerArguments());
-
- flavor.mTestHandleProfiling = chooseNotNull(
- overlay.getTestHandleProfiling(),
- base.getTestHandleProfiling());
-
- flavor.mTestFunctionalTest = chooseNotNull(
- overlay.getTestFunctionalTest(),
- base.getTestFunctionalTest());
-
- flavor.mSigningConfig = chooseNotNull(
- overlay.getSigningConfig(),
- base.getSigningConfig());
-
- flavor.addResourceConfigurations(base.getResourceConfigurations());
- flavor.addResourceConfigurations(overlay.getResourceConfigurations());
-
- flavor.addManifestPlaceholders(base.getManifestPlaceholders());
- flavor.addManifestPlaceholders(overlay.getManifestPlaceholders());
-
- flavor.addResValues(base.getResValues());
- flavor.addResValues(overlay.getResValues());
-
- flavor.addBuildConfigFields(base.getBuildConfigFields());
- flavor.addBuildConfigFields(overlay.getBuildConfigFields());
-
- flavor.setMultiDexEnabled(chooseNotNull(
- overlay.getMultiDexEnabled(), base.getMultiDexEnabled()));
-
- flavor.setMultiDexKeepFile(chooseNotNull(
- overlay.getMultiDexKeepFile(), base.getMultiDexKeepFile()));
-
- flavor.setMultiDexKeepProguard(chooseNotNull(
- overlay.getMultiDexKeepProguard(), base.getMultiDexKeepProguard()));
-
- flavor.setJarJarRuleFiles(ImmutableList.<File>builder()
- .addAll(overlay.getJarJarRuleFiles())
- .addAll(base.getJarJarRuleFiles())
- .build());
-
- return flavor;
- }
-
- /**
- * Clone a given product flavor.
- *
- * @param productFlavor the flavor to clone.
- *
- * @return a new instance that is a clone of the flavor.
- */
- @NonNull
- static ProductFlavor clone(@NonNull ProductFlavor productFlavor) {
- DefaultProductFlavor flavor = new DefaultProductFlavor(productFlavor.getName());
- flavor._initWith(productFlavor);
- flavor.mDimension = productFlavor.getDimension();
- flavor.mMinSdkVersion = productFlavor.getMinSdkVersion();
- flavor.mTargetSdkVersion = productFlavor.getTargetSdkVersion();
- flavor.mMaxSdkVersion = productFlavor.getMaxSdkVersion();
- flavor.mRenderscriptTargetApi = productFlavor.getRenderscriptTargetApi();
- flavor.mRenderscriptSupportModeEnabled = productFlavor.getRenderscriptSupportModeEnabled();
- flavor.mRenderscriptNdkModeEnabled = productFlavor.getRenderscriptNdkModeEnabled();
-
- flavor.mVersionCode = productFlavor.getVersionCode();
- flavor.mVersionName = productFlavor.getVersionName();
-
- flavor.mApplicationId = productFlavor.getApplicationId();
-
- flavor.mTestApplicationId = productFlavor.getTestApplicationId();
- flavor.mTestInstrumentationRunner = productFlavor.getTestInstrumentationRunner();
- flavor.mTestInstrumentationRunnerArguments = productFlavor.getTestInstrumentationRunnerArguments();
- flavor.mTestHandleProfiling = productFlavor.getTestHandleProfiling();
- flavor.mTestFunctionalTest = productFlavor.getTestFunctionalTest();
-
- flavor.mSigningConfig = productFlavor.getSigningConfig();
-
- flavor.addResourceConfigurations(productFlavor.getResourceConfigurations());
- flavor.addManifestPlaceholders(productFlavor.getManifestPlaceholders());
-
- flavor.addResValues(productFlavor.getResValues());
- flavor.addBuildConfigFields(productFlavor.getBuildConfigFields());
-
- flavor.setMultiDexEnabled(productFlavor.getMultiDexEnabled());
-
- flavor.setMultiDexKeepFile(productFlavor.getMultiDexKeepFile());
- flavor.setMultiDexKeepProguard(productFlavor.getMultiDexKeepProguard());
- flavor.setJarJarRuleFiles(ImmutableList.copyOf(productFlavor.getJarJarRuleFiles()));
-
- return flavor;
- }
-
- private static <T> T chooseNotNull(T overlay, T base) {
- return overlay != null ? overlay : base;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
-
- DefaultProductFlavor that = (DefaultProductFlavor) o;
-
- return Objects.equal(mDimension, that.mDimension) &&
- Objects.equal(mApplicationId, that.mApplicationId) &&
- Objects.equal(mMaxSdkVersion, that.mMaxSdkVersion) &&
- Objects.equal(mMinSdkVersion, that.mMinSdkVersion) &&
- Objects.equal(mName, that.mName) &&
- Objects.equal(mRenderscriptNdkModeEnabled, that.mRenderscriptNdkModeEnabled) &&
- Objects.equal(mRenderscriptSupportModeEnabled,
- that.mRenderscriptSupportModeEnabled) &&
- Objects.equal(mRenderscriptTargetApi, that.mRenderscriptTargetApi) &&
- Objects.equal(mResourceConfiguration, that.mResourceConfiguration) &&
- Objects.equal(mSigningConfig, that.mSigningConfig) &&
- Objects.equal(mTargetSdkVersion, that.mTargetSdkVersion) &&
- Objects.equal(mTestApplicationId, that.mTestApplicationId) &&
- Objects.equal(mTestFunctionalTest, that.mTestFunctionalTest) &&
- Objects.equal(mTestHandleProfiling, that.mTestHandleProfiling) &&
- Objects.equal(mTestInstrumentationRunner, that.mTestInstrumentationRunner) &&
- Objects.equal(mTestInstrumentationRunnerArguments,
- that.mTestInstrumentationRunnerArguments) &&
- Objects.equal(mVersionCode, that.mVersionCode) &&
- Objects.equal(mVersionName, that.mVersionName);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(
- super.hashCode(),
- mName,
- mDimension,
- mMinSdkVersion,
- mTargetSdkVersion,
- mMaxSdkVersion,
- mRenderscriptTargetApi,
- mRenderscriptSupportModeEnabled,
- mRenderscriptNdkModeEnabled,
- mVersionCode,
- mVersionName,
- mApplicationId,
- mTestApplicationId,
- mTestInstrumentationRunner,
- mTestInstrumentationRunnerArguments,
- mTestHandleProfiling,
- mTestFunctionalTest,
- mSigningConfig,
- mResourceConfiguration);
- }
-
- @Override
- @NonNull
- public String toString() {
- return Objects.toStringHelper(this)
- .add("name", mName)
- .add("dimension", mDimension)
- .add("minSdkVersion", mMinSdkVersion)
- .add("targetSdkVersion", mTargetSdkVersion)
- .add("renderscriptTargetApi", mRenderscriptTargetApi)
- .add("renderscriptSupportModeEnabled", mRenderscriptSupportModeEnabled)
- .add("renderscriptNdkModeEnabled", mRenderscriptNdkModeEnabled)
- .add("versionCode", mVersionCode)
- .add("versionName", mVersionName)
- .add("applicationId", mApplicationId)
- .add("testApplicationId", mTestApplicationId)
- .add("testInstrumentationRunner", mTestInstrumentationRunner)
- .add("testInstrumentationRunnerArguments", mTestInstrumentationRunnerArguments)
- .add("testHandleProfiling", mTestHandleProfiling)
- .add("testFunctionalTest", mTestFunctionalTest)
- .add("signingConfig", mSigningConfig)
- .add("resConfig", mResourceConfiguration)
- .add("mBuildConfigFields", getBuildConfigFields())
- .add("mResValues", getResValues())
- .add("mProguardFiles", getProguardFiles())
- .add("mConsumerProguardFiles", getConsumerProguardFiles())
- .add("mManifestPlaceholders", getManifestPlaceholders())
- .toString();
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java b/base/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java
deleted file mode 100644
index beaad19..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.Nullable;
-
-public interface DexOptions {
-
- boolean getIncremental();
- boolean getPreDexLibraries();
- boolean getJumboMode();
- @Nullable
- String getJavaMaxHeapSize();
- @Nullable
- Integer getThreadCount();
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/DexProcessBuilder.java b/base/build-system/builder/src/main/java/com/android/builder/core/DexProcessBuilder.java
deleted file mode 100644
index c9d8475..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/DexProcessBuilder.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.process.JavaProcessInfo;
-import com.android.ide.common.process.ProcessEnvBuilder;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessInfoBuilder;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.base.Charsets;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A builder to create a dex-specific ProcessInfoBuilder
- */
-public class DexProcessBuilder extends ProcessEnvBuilder<DexProcessBuilder> {
- private static final FullRevision MIN_BUILD_TOOLS_REVISION_FOR_DEX_INPUT_LIST = new FullRevision(21, 0, 0);
- private static final FullRevision MIN_MULTIDEX_BUILD_TOOLS_REV = new FullRevision(21, 0, 0);
- private static final FullRevision MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV = new FullRevision(22, 0, 2);
-
- @NonNull
- private final File mOutputFile;
- private boolean mVerbose = false;
- private boolean mIncremental = false;
- private boolean mNoOptimize = false;
- private boolean mMultiDex = false;
- private File mMainDexList = null;
- private Set<File> mInputs = Sets.newHashSet();
- private File mTempInputFolder = null;
- private List<String> mAdditionalParams = null;
-
- public DexProcessBuilder(@NonNull File outputFile) {
- mOutputFile = outputFile;
- }
-
- @NonNull
- public DexProcessBuilder setVerbose(boolean verbose) {
- mVerbose = verbose;
- return this;
- }
-
- @NonNull
- public DexProcessBuilder setIncremental(boolean incremental) {
- mIncremental = incremental;
- return this;
- }
-
- @NonNull
- public DexProcessBuilder setNoOptimize(boolean noOptimize) {
- mNoOptimize = noOptimize;
- return this;
- }
-
- @NonNull
- public DexProcessBuilder setMultiDex(boolean multiDex) {
- mMultiDex = multiDex;
- return this;
- }
-
- @NonNull
- public DexProcessBuilder setMainDexList(File mainDexList) {
- mMainDexList = mainDexList;
- return this;
- }
-
- @NonNull
- public DexProcessBuilder addInput(File input) {
- mInputs.add(input);
- return this;
- }
-
- @NonNull
- public DexProcessBuilder addInputs(@NonNull Collection<File> inputs) {
- mInputs.addAll(inputs);
- return this;
- }
-
- @NonNull
- public DexProcessBuilder setTempInputFolder(File tempInputFolder) {
- mTempInputFolder = tempInputFolder;
- return this;
- }
-
- @NonNull
- public DexProcessBuilder additionalParameters(@NonNull List<String> params) {
- if (mAdditionalParams == null) {
- mAdditionalParams = Lists.newArrayListWithExpectedSize(params.size());
- }
-
- mAdditionalParams.addAll(params);
-
- return this;
- }
-
- @NonNull
- public JavaProcessInfo build(
- @NonNull BuildToolInfo buildToolInfo,
- @NonNull DexOptions dexOptions) throws ProcessException {
-
- checkState(
- !mMultiDex
- || buildToolInfo.getRevision().compareTo(MIN_MULTIDEX_BUILD_TOOLS_REV) >= 0,
- "Multi dex requires Build Tools " +
- MIN_MULTIDEX_BUILD_TOOLS_REV.toString() +
- " / Current: " +
- buildToolInfo.getRevision().toShortString());
-
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
- builder.addEnvironments(mEnvironment);
-
- String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
- if (dx == null || !new File(dx).isFile()) {
- throw new IllegalStateException("dx.jar is missing");
- }
-
- builder.setClasspath(dx);
- builder.setMain("com.android.dx.command.Main");
-
- if (dexOptions.getJavaMaxHeapSize() != null) {
- builder.addJvmArg("-Xmx" + dexOptions.getJavaMaxHeapSize());
- } else {
- builder.addJvmArg("-Xmx1024M");
- }
-
- builder.addArgs("--dex");
-
- if (mVerbose) {
- builder.addArgs("--verbose");
- }
-
- if (dexOptions.getJumboMode()) {
- builder.addArgs("--force-jumbo");
- }
-
- if (mIncremental) {
- builder.addArgs("--incremental", "--no-strict");
- }
-
- if (mNoOptimize) {
- builder.addArgs("--no-optimize");
- }
-
- // only change thread count is build tools is 22.0.2+
- if (buildToolInfo.getRevision().compareTo(MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV) >= 0) {
- Integer threadCount = dexOptions.getThreadCount();
- if (threadCount == null) {
- builder.addArgs("--num-threads=4");
- } else {
- builder.addArgs("--num-threads=" + threadCount);
- }
- }
-
- if (mMultiDex) {
- builder.addArgs("--multi-dex");
-
- if (mMainDexList != null ) {
- builder.addArgs("--main-dex-list", mMainDexList.getAbsolutePath());
- }
- }
-
- if (mAdditionalParams != null) {
- for (String arg : mAdditionalParams) {
- builder.addArgs(arg);
- }
- }
-
-
- builder.addArgs("--output", mOutputFile.getAbsolutePath());
-
- // input
- builder.addArgs(getFilesToAdd(buildToolInfo));
-
- return builder.createJavaProcess();
- }
-
- @NonNull
- private List<String> getFilesToAdd(@NonNull BuildToolInfo buildToolInfo) throws
- ProcessException {
- // remove non-existing files.
- Set<File> existingFiles = Sets.filter(mInputs, new Predicate<File>() {
- @Override
- public boolean apply(@Nullable File input) {
- return input != null && input.exists();
- }
- });
-
- if (existingFiles.isEmpty()) {
- throw new ProcessException("No files to pass to dex.");
- }
-
- // sort the inputs
- List<File> sortedList = Lists.newArrayList(existingFiles);
- Collections.sort(sortedList, new Comparator<File>() {
- @Override
- public int compare(File file, File file2) {
- boolean file2IsDir = file2.isDirectory();
- if (file.isDirectory()) {
- return file2IsDir ? 0 : -1;
- } else if (file2IsDir) {
- return 1;
- }
-
- long diff = file.length() - file2.length();
- return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
- }
- });
-
- // convert to String-based paths.
- List<String> filePathList = Lists.newArrayListWithCapacity(sortedList.size());
- for (File f : sortedList) {
- filePathList.add(f.getAbsolutePath());
- }
-
- if (mTempInputFolder != null && buildToolInfo.getRevision()
- .compareTo(MIN_BUILD_TOOLS_REVISION_FOR_DEX_INPUT_LIST) >= 0) {
- File inputListFile = new File(mTempInputFolder, "inputList.txt");
- // Write each library line by line to file
- try {
- Files.asCharSink(inputListFile, Charsets.UTF_8).writeLines(filePathList);
- } catch (IOException e) {
- throw new ProcessException(e);
- }
- return Collections.singletonList("--input-list=" + inputListFile.getAbsolutePath());
- } else {
- return filePathList;
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/ErrorReporter.java b/base/build-system/builder/src/main/java/com/android/builder/core/ErrorReporter.java
deleted file mode 100644
index 359920f..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/ErrorReporter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.SyncIssue;
-import com.android.ide.common.blame.MessageReceiver;
-
-/**
- * An error reporter for project evaluation and execution.
- *
- * The behavior of the reporter must vary depending on the evaluation mode
- * ({@link ErrorReporter.EvaluationMode}), indicating whether
- * the IDE is querying the project or not.
- */
-public abstract class ErrorReporter implements MessageReceiver {
-
- public enum EvaluationMode {
- /** Standard mode, errors should be breaking */
- STANDARD,
- /**
- * IDE mode. Errors should not be breaking and should generate a SyncIssue instead.
- */
- IDE,
- /** Legacy IDE mode (Studio 1.0), where SyncIssue are not understood by the IDE. */
- IDE_LEGACY
- }
-
- @NonNull
- private final EvaluationMode mMode;
-
- protected ErrorReporter(@NonNull EvaluationMode mode) {
- mMode = mode;
- }
-
- @NonNull
- public EvaluationMode getMode() {
- return mMode;
- }
-
- @NonNull
- public abstract SyncIssue handleSyncError(@NonNull String data, int type, @NonNull String msg);
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/JackProcessBuilder.java b/base/build-system/builder/src/main/java/com/android/builder/core/JackProcessBuilder.java
deleted file mode 100644
index 6d6cbff..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/JackProcessBuilder.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.process.JavaProcessInfo;
-import com.android.ide.common.process.ProcessEnvBuilder;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessInfoBuilder;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * A builder to create a Jack-specific ProcessInfoBuilder
- */
-public class JackProcessBuilder extends ProcessEnvBuilder<JackProcessBuilder> {
-
- static final FullRevision JACK_MIN_REV = new FullRevision(21, 1, 0);
-
- private boolean mDebugLog = false;
- private boolean mVerbose = false;
- private String mClasspath = null;
- private File mDexOutputFolder = null;
- private File mJackOutputFile = null;
- private List<File> mImportFiles = null;
- private List<File> mProguardFiles = null;
- private String mJavaMaxHeapSize = null;
- private File mMappingFile = null;
- private boolean mMultiDex = false;
- private int mMinSdkVersion = 21;
- private File mEcjOptionFile = null;
- private Collection<File> mJarJarRuleFiles = null;
- private File mIncrementalDir = null;
-
- public JackProcessBuilder() {
- }
-
- @NonNull
- public JackProcessBuilder setDebugLog(boolean debugLog) {
- mDebugLog = debugLog;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setVerbose(boolean verbose) {
- mVerbose = verbose;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setJavaMaxHeapSize(String javaMaxHeapSize) {
- mJavaMaxHeapSize = javaMaxHeapSize;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setClasspath(String classpath) {
- mClasspath = classpath;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setDexOutputFolder(File dexOutputFolder) {
- mDexOutputFolder = dexOutputFolder;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setJackOutputFile(File jackOutputFile) {
- mJackOutputFile = jackOutputFile;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder addImportFiles(@NonNull Collection<File> importFiles) {
- if (mImportFiles == null) {
- mImportFiles = Lists.newArrayListWithExpectedSize(importFiles.size());
- }
-
- mImportFiles.addAll(importFiles);
- return this;
- }
-
- @NonNull
- public JackProcessBuilder addProguardFiles(@NonNull Collection<File> proguardFiles) {
- if (mProguardFiles == null) {
- mProguardFiles = Lists.newArrayListWithExpectedSize(proguardFiles.size());
- }
-
- mProguardFiles.addAll(proguardFiles);
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setMappingFile(File mappingFile) {
- mMappingFile = mappingFile;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setMultiDex(boolean multiDex) {
- mMultiDex = multiDex;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setMinSdkVersion(int minSdkVersion) {
- mMinSdkVersion = minSdkVersion;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setEcjOptionFile(File ecjOptionFile) {
- mEcjOptionFile = ecjOptionFile;
- return this;
- }
-
- @NonNull
- public JackProcessBuilder setJarJarRuleFiles(@NonNull Collection<File> jarJarRuleFiles) {
- mJarJarRuleFiles = jarJarRuleFiles;
- return this;
- }
-
- @NonNull
- public JavaProcessInfo build(@NonNull BuildToolInfo buildToolInfo) throws ProcessException {
-
- FullRevision revision = buildToolInfo.getRevision();
- if (revision.compareTo(JACK_MIN_REV) < 0) {
- throw new ProcessException(
- "Jack requires Build Tools " + JACK_MIN_REV.toString() +
- " or later");
- }
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
- builder.addEnvironments(mEnvironment);
-
- String jackJar = buildToolInfo.getPath(BuildToolInfo.PathId.JACK);
- if (jackJar == null || !new File(jackJar).isFile()) {
- throw new IllegalStateException("jack.jar is missing");
- }
-
- builder.setClasspath(jackJar);
- builder.setMain("com.android.jack.Main");
-
- if (mJavaMaxHeapSize != null) {
- builder.addJvmArg("-Xmx" + mJavaMaxHeapSize);
- } else {
- builder.addJvmArg("-Xmx1024M");
- }
-
- if (mDebugLog) {
- builder.addArgs("--verbose", "debug");
- } else if (mVerbose) {
- builder.addArgs("--verbose", "info");
- }
-
- builder.addArgs("--classpath", mClasspath);
-
- if (mImportFiles != null) {
- for (File lib : mImportFiles) {
- builder.addArgs("--import", lib.getAbsolutePath());
- }
- }
-
- builder.addArgs("--output-dex", mDexOutputFolder.getAbsolutePath());
-
- builder.addArgs("--output-jack", mJackOutputFile.getAbsolutePath());
-
- builder.addArgs("-D", "jack.import.resource.policy=keep-first");
-
- builder.addArgs("-D", "jack.reporter=sdk");
-
- if (mProguardFiles != null && !mProguardFiles.isEmpty()) {
- for (File file : mProguardFiles) {
- builder.addArgs("--config-proguard", file.getAbsolutePath());
- }
- }
-
- if (mMappingFile != null) {
- builder.addArgs("-D", "jack.obfuscation.mapping.dump=true");
- builder.addArgs("-D", "jack.obfuscation.mapping.dump.file=" + mMappingFile.getAbsolutePath());
- }
-
- if (mMultiDex) {
- builder.addArgs("--multi-dex");
- if (mMinSdkVersion < 21) {
- builder.addArgs("legacy");
- } else {
- builder.addArgs("native");
- }
- }
-
- if (mJarJarRuleFiles != null) {
- for (File jarjarRuleFile : mJarJarRuleFiles) {
- builder.addArgs("--config-jarjar", jarjarRuleFile.getAbsolutePath());
- }
- }
-
- builder.addArgs("@" + mEcjOptionFile.getAbsolutePath());
-
- return builder.createJavaProcess();
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java b/base/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java
deleted file mode 100644
index afa5e43..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java
+++ /dev/null
@@ -1,1855 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.builder.dependency.DependencyContainer;
-import com.android.builder.dependency.JarDependency;
-import com.android.builder.dependency.LibraryDependency;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.BuildType;
-import com.android.builder.model.ClassField;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.model.SourceProvider;
-import com.android.ide.common.res2.AssetSet;
-import com.android.ide.common.res2.ResourceSet;
-import com.android.utils.StringHelper;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A Variant configuration.
- *
- * Variants are made from the combination of:
- *
- * - a build type (base interface BuildType), and its associated sources.
- * - a default configuration (base interface ProductFlavor), and its associated sources.
- * - a optional list of product flavors (base interface ProductFlavor) and their associated sources.
- * - dependencies (both jar and aar).
- *
- * @param <T> the type used for the Build Type.
- * @param <D> the type used for the default config
- * @param <F> the type used for the product flavors.
- */
-public class VariantConfiguration<T extends BuildType, D extends ProductFlavor, F extends ProductFlavor> {
-
- // per variant, as is caches some manifest data specific to this variant.
- private final ManifestParser sManifestParser = new DefaultManifestParser();
-
- /**
- * Full, unique name of the variant in camel case, including BuildType and Flavors (and Test)
- */
- private String mFullName;
- /**
- * Flavor Name of the variant, including all flavors in camel case (starting with a lower
- * case).
- */
- private String mFlavorName;
- /**
- * Full, unique name of the variant, including BuildType, flavors and test, dash separated.
- * (similar to full name but with dashes)
- */
- private String mBaseName;
- /**
- * Unique directory name (can include multiple folders) for the variant, based on build type,
- * flavor and test.
- * This always uses forward slashes ('/') as separator on all platform.
- *
- */
- private String mDirName;
-
- @NonNull
- private final D mDefaultConfig;
- @NonNull
- private final SourceProvider mDefaultSourceProvider;
-
- @NonNull
- private final T mBuildType;
- /** SourceProvider for the BuildType. Can be null */
- @Nullable
- private final SourceProvider mBuildTypeSourceProvider;
-
- private final List<String> mFlavorDimensionNames = Lists.newArrayList();
- private final List<F> mFlavors = Lists.newArrayList();
- private final List<SourceProvider> mFlavorSourceProviders = Lists.newArrayList();
-
- /** Variant specific source provider, may be null */
- @Nullable
- private SourceProvider mVariantSourceProvider;
-
- /** MultiFlavors specific source provider, may be null */
- @Nullable
- private SourceProvider mMultiFlavorSourceProvider;
-
- @NonNull
- private final VariantType mType;
-
- /**
- * Optional tested config in case this variant is used for testing another variant.
- *
- * @see VariantType#isForTesting()
- */
- private final VariantConfiguration mTestedConfig;
-
- /**
- * An optional output that is only valid if the type is Type#LIBRARY so that the test
- * for the library can use the library as if it was a normal dependency.
- */
- private LibraryDependency mOutput;
-
- @NonNull
- private ProductFlavor mMergedFlavor;
-
- /**
- * External/Jar dependencies.
- */
- private final Set<JarDependency> mExternalJars = Sets.newHashSet();
-
- /**
- * Local Jar dependencies.
- */
- private final Set<JarDependency> mLocalJars = Sets.newHashSet();
-
- /**
- * List of direct library dependencies. Each object defines its own dependencies.
- */
- private final List<LibraryDependency> mDirectLibraries = Lists.newArrayList();
-
- /**
- * List of all library dependencies in a flat list.
- *
- * <p>The order is based on the order needed to call aapt: earlier libraries override resources
- * of latter ones.
- */
- private final List<LibraryDependency> mFlatLibraries = Lists.newArrayList();
-
- /**
- * Variant-specific build Config fields.
- */
- private final Map<String, ClassField> mBuildConfigFields = Maps.newTreeMap();
-
- /**
- * Variant-specific res values.
- */
- private final Map<String, ClassField> mResValues = Maps.newTreeMap();
-
- /**
- * Signing Override to be used instead of any signing config provided by Build Type or
- * Product Flavors.
- */
- private final SigningConfig mSigningConfigOverride;
-
-
- /**
- * Parses the manifest file and return the package name.
- * @param manifestFile the manifest file
- * @return the package name found or null
- */
- @Nullable
- public static String getManifestPackage(@NonNull File manifestFile) {
- return new DefaultManifestParser().getPackage(manifestFile);
- }
-
- /**
- * Creates the configuration with the base source sets for a given {@link VariantType}. Meant
- * for non-testing variants.
- *
- * @param defaultConfig the default configuration. Required.
- * @param defaultSourceProvider the default source provider. Required
- * @param buildType the build type for this variant. Required.
- * @param buildTypeSourceProvider the source provider for the build type.
- * @param type the type of the project.
- * @param signingConfigOverride an optional Signing override to be used for signing.
- */
- public VariantConfiguration(
- @NonNull D defaultConfig,
- @NonNull SourceProvider defaultSourceProvider,
- @NonNull T buildType,
- @Nullable SourceProvider buildTypeSourceProvider,
- @NonNull VariantType type,
- @Nullable SigningConfig signingConfigOverride) {
- this(
- defaultConfig, defaultSourceProvider,
- buildType, buildTypeSourceProvider,
- type, null /*testedConfig*/, signingConfigOverride);
- }
-
- /**
- * Creates the configuration with the base source sets, and an optional tested variant.
- *
- * @param defaultConfig the default configuration. Required.
- * @param defaultSourceProvider the default source provider. Required
- * @param buildType the build type for this variant. Required.
- * @param buildTypeSourceProvider the source provider for the build type.
- * @param type the type of the project.
- * @param testedConfig the reference to the tested project. Required if type is Type.ANDROID_TEST
- * @param signingConfigOverride an optional Signing override to be used for signing.
- */
- public VariantConfiguration(
- @NonNull D defaultConfig,
- @NonNull SourceProvider defaultSourceProvider,
- @NonNull T buildType,
- @Nullable SourceProvider buildTypeSourceProvider,
- @NonNull VariantType type,
- @Nullable VariantConfiguration testedConfig,
- @Nullable SigningConfig signingConfigOverride) {
- checkNotNull(defaultConfig);
- checkNotNull(defaultSourceProvider);
- checkNotNull(buildType);
- checkNotNull(type);
- checkArgument(
- !type.isForTesting() || testedConfig != null,
- "You have to specify the tested variant for this variant type.");
- checkArgument(
- type.isForTesting() || testedConfig == null,
- "This variant type doesn't need a tested variant.");
-
- mDefaultConfig = checkNotNull(defaultConfig);
- mDefaultSourceProvider = checkNotNull(defaultSourceProvider);
- mBuildType = checkNotNull(buildType);
- mBuildTypeSourceProvider = buildTypeSourceProvider;
- mType = checkNotNull(type);
- mTestedConfig = testedConfig;
- mSigningConfigOverride = signingConfigOverride;
- mMergedFlavor = DefaultProductFlavor.clone(mDefaultConfig);
- }
-
- /**
- * Returns the full, unique name of the variant in camel case (starting with a lower case),
- * including BuildType, Flavors and Test (if applicable).
- *
- * @return the name of the variant
- */
- @NonNull
- public String getFullName() {
- if (mFullName == null) {
- StringBuilder sb = new StringBuilder();
- String flavorName = getFlavorName();
- if (!flavorName.isEmpty()) {
- sb.append(flavorName);
- sb.append(StringHelper.capitalize(mBuildType.getName()));
- } else {
- sb.append(mBuildType.getName());
- }
-
- if (mType.isForTesting()) {
- sb.append(mType.getSuffix());
- }
-
- mFullName = sb.toString();
- }
-
- return mFullName;
- }
-
- /**
- * Returns a full name that includes the given splits name.
- * @param splitName the split name
- * @return a unique name made up of the variant and split names.
- */
- @NonNull
- public String computeFullNameWithSplits(@NonNull String splitName) {
- StringBuilder sb = new StringBuilder();
- String flavorName = getFlavorName();
- if (!flavorName.isEmpty()) {
- sb.append(flavorName);
- sb.append(StringHelper.capitalize(splitName));
- } else {
- sb.append(splitName);
- }
-
- sb.append(StringHelper.capitalize(mBuildType.getName()));
-
- if (mType.isForTesting()) {
- sb.append(mType.getSuffix());
- }
-
- return sb.toString();
- }
-
- /**
- * Returns the flavor name of the variant, including all flavors in camel case (starting
- * with a lower case). If the variant has no flavor, then an empty string is returned.
- *
- * @return the flavor name or an empty string.
- */
- @NonNull
- public String getFlavorName() {
- if (mFlavorName == null) {
- if (mFlavors.isEmpty()) {
- mFlavorName = "";
- } else {
- StringBuilder sb = new StringBuilder();
- boolean first = true;
- for (F flavor : mFlavors) {
- sb.append(first ? flavor.getName() : StringHelper.capitalize(flavor.getName()));
- first = false;
- }
-
- mFlavorName = sb.toString();
- }
- }
-
- return mFlavorName;
- }
-
- /**
- * Returns the full, unique name of the variant, including BuildType, flavors and test,
- * dash separated. (similar to full name but with dashes)
- *
- * @return the name of the variant
- */
- @NonNull
- public String getBaseName() {
- if (mBaseName == null) {
- StringBuilder sb = new StringBuilder();
-
- if (!mFlavors.isEmpty()) {
- for (ProductFlavor pf : mFlavors) {
- sb.append(pf.getName()).append('-');
- }
- }
-
- sb.append(mBuildType.getName());
-
- if (mType.isForTesting()) {
- sb.append('-').append(mType.getPrefix());
- }
-
- mBaseName = sb.toString();
- }
-
- return mBaseName;
- }
-
- /**
- * Returns a base name that includes the given splits name.
- * @param splitName the split name
- * @return a unique name made up of the variant and split names.
- */
- @NonNull
- public String computeBaseNameWithSplits(@NonNull String splitName) {
- StringBuilder sb = new StringBuilder();
-
- if (!mFlavors.isEmpty()) {
- for (ProductFlavor pf : mFlavors) {
- sb.append(pf.getName()).append('-');
- }
- }
-
- sb.append(splitName).append('-');
- sb.append(mBuildType.getName());
-
- if (mType.isForTesting()) {
- sb.append('-').append(mType.getPrefix());
- }
-
- return sb.toString();
- }
-
- /**
- * Returns a unique directory name (can include multiple folders) for the variant,
- * based on build type, flavor and test.
- *
- * <p>This always uses forward slashes ('/') as separator on all platform.
- *
- * @return the directory name for the variant
- */
- @NonNull
- public String getDirName() {
- if (mDirName == null) {
- StringBuilder sb = new StringBuilder();
-
- if (mType.isForTesting()) {
- sb.append(mType.getPrefix()).append("/");
- }
-
- if (!mFlavors.isEmpty()) {
- boolean first = true;
- for (F flavor : mFlavors) {
- sb.append(first ? flavor.getName() : StringHelper.capitalize(flavor.getName()));
- first = false;
- }
-
- sb.append('/').append(mBuildType.getName());
-
- } else {
- sb.append(mBuildType.getName());
- }
-
- mDirName = sb.toString();
-
- }
-
- return mDirName;
- }
-
- /**
- * Returns a unique directory name (can include multiple folders) for the variant,
- * based on build type, flavor and test, and splits.
- *
- * <p>This always uses forward slashes ('/') as separator on all platform.
- *
- * @return the directory name for the variant
- */
- @NonNull
- public String computeDirNameWithSplits(@NonNull String... splitNames) {
- StringBuilder sb = new StringBuilder();
-
- if (mType.isForTesting()) {
- sb.append(mType.getPrefix()).append("/");
- }
-
- if (!mFlavors.isEmpty()) {
- for (F flavor : mFlavors) {
- sb.append(flavor.getName());
- }
-
- sb.append('/');
- }
-
- for (String splitName : splitNames) {
- if (splitName != null) {
- sb.append(splitName).append('/');
- }
- }
-
- sb.append(mBuildType.getName());
-
- return sb.toString();
- }
-
- /**
- * Return the names of the applied flavors.
- *
- * The list contains the dimension names as well.
- *
- * @return the list, possibly empty if there are no flavors.
- */
- @NonNull
- public List<String> getFlavorNamesWithDimensionNames() {
- if (mFlavors.isEmpty()) {
- return Collections.emptyList();
- }
-
- List<String> names;
- int count = mFlavors.size();
-
- if (count > 1) {
- names = Lists.newArrayListWithCapacity(count * 2);
-
- for (int i = 0 ; i < count ; i++) {
- names.add(mFlavors.get(i).getName());
- names.add(mFlavorDimensionNames.get(i));
- }
-
- } else {
- names = Collections.singletonList(mFlavors.get(0).getName());
- }
-
- return names;
- }
-
-
- /**
- * Add a new configured ProductFlavor.
- *
- * If multiple flavors are added, the priority follows the order they are added when it
- * comes to resolving Android resources overlays (ie earlier added flavors supersedes
- * latter added ones).
- *
- * @param productFlavor the configured product flavor
- * @param sourceProvider the source provider for the product flavor
- * @param dimensionName the name of the dimension associated with the flavor
- *
- * @return the config object
- */
- @NonNull
- public VariantConfiguration addProductFlavor(
- @NonNull F productFlavor,
- @NonNull SourceProvider sourceProvider,
- @NonNull String dimensionName) {
-
- mFlavors.add(productFlavor);
- mFlavorSourceProviders.add(sourceProvider);
- mFlavorDimensionNames.add(dimensionName);
-
- mMergedFlavor = DefaultProductFlavor.mergeFlavors(mMergedFlavor, productFlavor);
-
- return this;
- }
-
- /**
- * Sets the variant-specific source provider.
- * @param sourceProvider the source provider for the product flavor
- *
- * @return the config object
- */
- public VariantConfiguration setVariantSourceProvider(@Nullable SourceProvider sourceProvider) {
- mVariantSourceProvider = sourceProvider;
- return this;
- }
-
- /**
- * Sets the variant-specific source provider.
- * @param sourceProvider the source provider for the product flavor
- *
- * @return the config object
- */
- public VariantConfiguration setMultiFlavorSourceProvider(@Nullable SourceProvider sourceProvider) {
- mMultiFlavorSourceProvider = sourceProvider;
- return this;
- }
-
- /**
- * Returns the variant specific source provider
- * @return the source provider or null if none has been provided.
- */
- @Nullable
- public SourceProvider getVariantSourceProvider() {
- return mVariantSourceProvider;
- }
-
- @Nullable
- public SourceProvider getMultiFlavorSourceProvider() {
- return mMultiFlavorSourceProvider;
- }
-
- /**
- * Sets the dependencies
- *
- * @param container a DependencyContainer.
- * @return the config object
- */
- @NonNull
- public VariantConfiguration setDependencies(@NonNull DependencyContainer container) {
- // Output of mTestedConfig will not be initialized until the tasks for the tested config are
- // created. If library output has never been added to mDirectLibraries, checked the output
- // of the mTestedConfig to see if the tasks are now created.
- if (mTestedConfig != null &&
- mTestedConfig.mType == VariantType.LIBRARY &&
- mTestedConfig.mOutput != null &&
- !mDirectLibraries.contains(mTestedConfig.mOutput)) {
- mDirectLibraries.add(mTestedConfig.mOutput);
- }
-
- mDirectLibraries.addAll(container.getAndroidDependencies());
- mExternalJars.addAll(container.getJarDependencies());
- mLocalJars.addAll(container.getLocalDependencies());
-
- resolveIndirectLibraryDependencies(mDirectLibraries, mFlatLibraries);
-
- return this;
- }
-
- /**
- * Returns the list of external/module jar dependencies
- * @return a non null collection of Jar dependencies.
- */
- @NonNull
- public Collection<JarDependency> getExternalJarDependencies() {
- return mExternalJars;
- }
-
- /**
- * Returns the list of local jar dependencies
- * @return a non null collection of Jar dependencies.
- */
- @NonNull
- public Collection<JarDependency> getLocalJarDependencies() {
- return mLocalJars;
- }
-
- /**
- * Sets the output of this variant. This is required when the variant is a library so that
- * the variant that tests this library can properly include the tested library in its own
- * package.
- *
- * @param output the output of the library as an LibraryDependency that will provides the
- * location of all the created items.
- * @return the config object
- */
- @NonNull
- public VariantConfiguration setOutput(LibraryDependency output) {
- mOutput = output;
- return this;
- }
-
- /**
- * Returns the {@link LibraryDependency} that this library variant produces. Used so that
- * related test variants can use it as a dependency. Returns null if this is not a library
- * variant.
- *
- * @see #mOutput
- */
- @Nullable
- public LibraryDependency getOutput() {
- return mOutput;
- }
-
- @NonNull
- public D getDefaultConfig() {
- return mDefaultConfig;
- }
-
- @NonNull
- public SourceProvider getDefaultSourceSet() {
- return mDefaultSourceProvider;
- }
-
- @NonNull
- public ProductFlavor getMergedFlavor() {
- return mMergedFlavor;
- }
-
- @NonNull
- public T getBuildType() {
- return mBuildType;
- }
-
- /**
- * The SourceProvider for the BuildType. Can be null.
- */
- @Nullable
- public SourceProvider getBuildTypeSourceSet() {
- return mBuildTypeSourceProvider;
- }
-
- public boolean hasFlavors() {
- return !mFlavors.isEmpty();
- }
-
- /**
- * Returns the product flavors. Items earlier in the list override later items.
- */
- @NonNull
- public List<F> getProductFlavors() {
- return mFlavors;
- }
-
- /**
- * Returns the list of SourceProviders for the flavors.
- *
- * The list is ordered from higher priority to lower priority.
- *
- * @return the list of Source Providers for the flavors. Never null.
- */
- @NonNull
- public List<SourceProvider> getFlavorSourceProviders() {
- return mFlavorSourceProviders;
- }
-
- /**
- * Returns the direct library dependencies
- */
- @NonNull
- public List<LibraryDependency> getDirectLibraries() {
- return mDirectLibraries;
- }
-
- /**
- * Returns all the library dependencies, direct and transitive.
- */
- @NonNull
- public List<LibraryDependency> getAllLibraries() {
- return mFlatLibraries;
- }
-
- @NonNull
- public VariantType getType() {
- return mType;
- }
-
- @Nullable
- public VariantConfiguration getTestedConfig() {
- return mTestedConfig;
- }
-
- /**
- * Resolves a given list of libraries, finds out if they depend on other libraries, and
- * returns a flat list of all the direct and indirect dependencies in the proper order (first
- * is higher priority when calling aapt).
- * @param directDependencies the libraries to resolve
- * @param outFlatDependencies where to store all the libraries.
- */
- @VisibleForTesting
- static void resolveIndirectLibraryDependencies(List<LibraryDependency> directDependencies,
- List<LibraryDependency> outFlatDependencies) {
- if (directDependencies == null) {
- return;
- }
- // loop in the inverse order to resolve dependencies on the libraries, so that if a library
- // is required by two higher level libraries it can be inserted in the correct place
- for (int i = directDependencies.size() - 1 ; i >= 0 ; i--) {
- LibraryDependency library = directDependencies.get(i);
-
- // get its libraries
- Collection<LibraryDependency> dependencies = library.getDependencies();
- List<LibraryDependency> depList = Lists.newArrayList(dependencies);
-
- // resolve the dependencies for those libraries
- resolveIndirectLibraryDependencies(depList, outFlatDependencies);
-
- // and add the current one (if needed) in front (higher priority)
- if (!outFlatDependencies.contains(library)) {
- outFlatDependencies.add(0, library);
- }
- }
- }
-
- /**
- * Returns the original application ID before any overrides from flavors.
- * If the variant is a test variant, then the application ID is the one coming from the
- * configuration of the tested variant, and this call is similar to {@link #getApplicationId()}
- * @return the original application ID
- */
- @Nullable
- public String getOriginalApplicationId() {
- if (mType.isForTesting()) {
- return getApplicationId();
- }
-
- return getPackageFromManifest();
- }
-
- /**
- * Returns the application ID for this variant. This could be coming from the manifest or
- * could be overridden through the product flavors and/or the build type.
- * @return the application ID
- */
- @NonNull
- public String getApplicationId() {
- String id;
-
- if (mType.isForTesting()) {
- checkState(mTestedConfig != null);
-
- id = mMergedFlavor.getTestApplicationId();
- String testedPackage = mTestedConfig.getApplicationId();
- if (id == null) {
- id = testedPackage + ".test";
- } else {
- if (id.equals(testedPackage)) {
- throw new RuntimeException(String.format("Application and test application id "
- + "cannot be the same: both are '%s' for %s",
- id, getFullName()));
- }
- }
-
- } else {
- // first get package override.
- id = getIdOverride();
- // if it's null, this means we just need the default package
- // from the manifest since both flavor and build type do nothing.
- if (id == null) {
- id = getPackageFromManifest();
- }
- }
-
- if (id == null) {
- throw new RuntimeException("Failed to get application id for " + getFullName());
- }
-
- return id;
- }
-
- @Nullable
- public String getTestedApplicationId() {
- if (mType.isForTesting()) {
- checkState(mTestedConfig != null);
- if (mTestedConfig.mType == VariantType.LIBRARY) {
- return getApplicationId();
- } else {
- return mTestedConfig.getApplicationId();
- }
- }
-
- return null;
- }
-
- /**
- * Returns the application id override value coming from the Product Flavor and/or the
- * Build Type. If the package/id is not overridden then this returns null.
- *
- * @return the id override or null
- */
- @Nullable
- public String getIdOverride() {
- String idName = mMergedFlavor.getApplicationId();
- String idSuffix = mBuildType.getApplicationIdSuffix();
-
- if (idSuffix != null && !idSuffix.isEmpty()) {
- if (idName == null) {
- idName = getPackageFromManifest();
- }
-
- if (idSuffix.charAt(0) == '.') {
- idName = idName + idSuffix;
- } else {
- idName = idName + '.' + idSuffix;
- }
- }
-
- return idName;
- }
-
- /**
- * Returns the version name for this variant. This could be coming from the manifest or
- * could be overridden through the product flavors, and can have a suffix specified by
- * the build type.
- *
- * @return the version name
- */
- @Nullable
- public String getVersionName() {
- String versionName = mMergedFlavor.getVersionName();
- String versionSuffix = mBuildType.getVersionNameSuffix();
-
- if (versionName == null && !mType.isForTesting()) {
- versionName = getVersionNameFromManifest();
- }
-
- if (versionSuffix != null && !versionSuffix.isEmpty()) {
- versionName = Strings.nullToEmpty(versionName) + versionSuffix;
- }
-
- return versionName;
- }
-
- /**
- * Returns the version code for this variant. This could be coming from the manifest or
- * could be overridden through the product flavors, and can have a suffix specified by
- * the build type.
- *
- * @return the version code or -1 if there was non defined.
- */
- public int getVersionCode() {
- int versionCode = mMergedFlavor.getVersionCode() != null ?
- mMergedFlavor.getVersionCode() : -1;
-
- if (versionCode == -1 && !mType.isForTesting()) {
- versionCode = getVersionCodeFromManifest();
- }
-
- return versionCode;
- }
-
- private static final String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner";
- private static final Boolean DEFAULT_HANDLE_PROFILING = false;
- private static final Boolean DEFAULT_FUNCTIONAL_TEST = false;
-
- /**
- * Returns the instrumentationRunner to use to test this variant, or if the
- * variant is a test, the one to use to test the tested variant.
- * @return the instrumentation test runner name
- */
- @NonNull
- public String getInstrumentationRunner() {
- VariantConfiguration config = this;
- if (mType.isForTesting()) {
- config = getTestedConfig();
- checkState(config != null);
- }
- String runner = config.mMergedFlavor.getTestInstrumentationRunner();
- return runner != null ? runner : DEFAULT_TEST_RUNNER;
- }
-
- /**
- * Returns the instrumentationRunner arguments to use to test this variant, or if the
- * variant is a test, the ones to use to test the tested variant
- */
- @NonNull
- public Map<String, String> getInstrumentationRunnerArguments() {
- VariantConfiguration config = this;
- if (mType.isForTesting()) {
- config = getTestedConfig();
- checkState(config != null);
- }
- return config.mMergedFlavor.getTestInstrumentationRunnerArguments();
- }
-
- /**
- * Returns handleProfiling value to use to test this variant, or if the
- * variant is a test, the one to use to test the tested variant.
- * @return the handleProfiling value
- */
- @NonNull
- public Boolean getHandleProfiling() {
- VariantConfiguration config = this;
- if (mType.isForTesting()) {
- config = getTestedConfig();
- checkState(config != null);
- }
- Boolean handleProfiling = config.mMergedFlavor.getTestHandleProfiling();
- return handleProfiling != null ? handleProfiling : DEFAULT_HANDLE_PROFILING;
- }
-
- /**
- * Returns functionalTest value to use to test this variant, or if the
- * variant is a test, the one to use to test the tested variant.
- * @return the functionalTest value
- */
- @NonNull
- public Boolean getFunctionalTest() {
- VariantConfiguration config = this;
- if (mType.isForTesting()) {
- config = getTestedConfig();
- checkState(config != null);
- }
- Boolean functionalTest = config.mMergedFlavor.getTestFunctionalTest();
- return functionalTest != null ? functionalTest : DEFAULT_FUNCTIONAL_TEST;
- }
-
- /**
- * Reads the package name from the manifest. This is unmodified by the build type.
- */
- @Nullable
- public String getPackageFromManifest() {
- checkState(!mType.isForTesting());
-
- File manifestLocation = mDefaultSourceProvider.getManifestFile();
- String packageName = sManifestParser.getPackage(manifestLocation);
- if (packageName == null) {
- throw new RuntimeException(String.format("Cannot read packageName from %1$s",
- mDefaultSourceProvider.getManifestFile().getAbsolutePath()));
- }
- return packageName;
- }
-
- /**
- * Reads the version name from the manifest.
- */
- @Nullable
- public String getVersionNameFromManifest() {
- File manifestLocation = mDefaultSourceProvider.getManifestFile();
- return sManifestParser.getVersionName(manifestLocation);
- }
-
- /**
- * Reads the version code from the manifest.
- */
- public int getVersionCodeFromManifest() {
- File manifestLocation = mDefaultSourceProvider.getManifestFile();
- return sManifestParser.getVersionCode(manifestLocation);
- }
-
- /**
- * Return the minSdkVersion for this variant.
- *
- * This uses both the value from the manifest (if present), and the override coming
- * from the flavor(s) (if present).
- * @return the minSdkVersion
- */
- @NonNull
- public ApiVersion getMinSdkVersion() {
- if (mTestedConfig != null) {
- return mTestedConfig.getMinSdkVersion();
- }
-
- ApiVersion minSdkVersion = mMergedFlavor.getMinSdkVersion();
- if (minSdkVersion == null) {
- // read it from the main manifest
- File manifestLocation = mDefaultSourceProvider.getManifestFile();
- minSdkVersion = DefaultApiVersion.create(
- sManifestParser.getMinSdkVersion(manifestLocation));
- }
-
- return minSdkVersion;
- }
-
- /**
- * Return the targetSdkVersion for this variant.
- *
- * This uses both the value from the manifest (if present), and the override coming
- * from the flavor(s) (if present).
- * @return the targetSdkVersion
- */
- @NonNull
- public ApiVersion getTargetSdkVersion() {
- if (mTestedConfig != null) {
- return mTestedConfig.getTargetSdkVersion();
- }
- ApiVersion targetSdkVersion = mMergedFlavor.getTargetSdkVersion();
- if (targetSdkVersion == null) {
- // read it from the main manifest
- File manifestLocation = mDefaultSourceProvider.getManifestFile();
- targetSdkVersion = DefaultApiVersion.create(
- sManifestParser.getTargetSdkVersion(manifestLocation));
- }
-
- return targetSdkVersion;
- }
-
- @Nullable
- public File getMainManifest() {
- File defaultManifest = mDefaultSourceProvider.getManifestFile();
-
- // this could not exist in a test project.
- if (defaultManifest.isFile()) {
- return defaultManifest;
- }
-
- return null;
- }
-
- /**
- * Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier
- * items are meant to be overridden by later items.
- *
- * @return a list of source provider
- */
- @NonNull
- public List<SourceProvider> getSortedSourceProviders() {
- List<SourceProvider> providers = Lists.newArrayList();
-
- // first the default source provider
- providers.add(mDefaultSourceProvider);
-
- // the list of flavor must be reversed to use the right overlay order.
- for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
- providers.add(mFlavorSourceProviders.get(n));
- }
-
- // multiflavor specific overrides flavor
- if (mMultiFlavorSourceProvider != null) {
- providers.add(mMultiFlavorSourceProvider);
- }
-
- // build type overrides flavors
- if (mBuildTypeSourceProvider != null) {
- providers.add(mBuildTypeSourceProvider);
- }
-
- // variant specific overrides all
- if (mVariantSourceProvider != null) {
- providers.add(mVariantSourceProvider);
- }
-
- return providers;
- }
-
- @NonNull
- public List<File> getManifestOverlays() {
- List<File> inputs = Lists.newArrayList();
-
- if (mVariantSourceProvider != null) {
- File variantLocation = mVariantSourceProvider.getManifestFile();
- if (variantLocation.isFile()) {
- inputs.add(variantLocation);
- }
- }
-
- if (mBuildTypeSourceProvider != null) {
- File typeLocation = mBuildTypeSourceProvider.getManifestFile();
- if (typeLocation.isFile()) {
- inputs.add(typeLocation);
- }
- }
-
- if (mMultiFlavorSourceProvider != null) {
- File variantLocation = mMultiFlavorSourceProvider.getManifestFile();
- if (variantLocation.isFile()) {
- inputs.add(variantLocation);
- }
- }
-
- for (SourceProvider sourceProvider : mFlavorSourceProviders) {
- File f = sourceProvider.getManifestFile();
- if (f.isFile()) {
- inputs.add(f);
- }
- }
-
- return inputs;
- }
-
- /**
- * Returns the dynamic list of {@link ResourceSet} based on the configuration, its dependencies,
- * as well as tested config if applicable (test of a library).
- *
- * The list is ordered in ascending order of importance, meaning the first set is meant to be
- * overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
- * {@link com.android.ide.common.res2.ResourceMerger}.
- *
- * @param generatedResFolders a list of generated res folders
- * @param includeDependencies whether to include in the result the resources of the dependencies
- *
- * @return a list ResourceSet.
- */
- @NonNull
- public List<ResourceSet> getResourceSets(@NonNull List<File> generatedResFolders,
- boolean includeDependencies) {
- List<ResourceSet> resourceSets = Lists.newArrayList();
-
- // the list of dependency must be reversed to use the right overlay order.
- if (includeDependencies) {
- for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
- LibraryDependency dependency = mFlatLibraries.get(n);
- if (!dependency.isOptional()) {
- File resFolder = dependency.getResFolder();
- if (resFolder.isDirectory()) {
- ResourceSet resourceSet = new ResourceSet(dependency.getFolder().getName());
- resourceSet.addSource(resFolder);
- resourceSets.add(resourceSet);
- }
- }
- }
- }
-
- Collection<File> mainResDirs = mDefaultSourceProvider.getResDirectories();
-
- // the main + generated res folders are in the same ResourceSet
- ResourceSet resourceSet = new ResourceSet(BuilderConstants.MAIN);
- resourceSet.addSources(mainResDirs);
- if (!generatedResFolders.isEmpty()) {
- for (File generatedResFolder : generatedResFolders) {
- resourceSet.addSource(generatedResFolder);
-
- }
- }
- resourceSets.add(resourceSet);
-
- // the list of flavor must be reversed to use the right overlay order.
- for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
- SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
-
- Collection<File> flavorResDirs = sourceProvider.getResDirectories();
- // we need the same of the flavor config, but it's in a different list.
- // This is fine as both list are parallel collections with the same number of items.
- resourceSet = new ResourceSet(sourceProvider.getName());
- resourceSet.addSources(flavorResDirs);
- resourceSets.add(resourceSet);
- }
-
- // multiflavor specific overrides flavor
- if (mMultiFlavorSourceProvider != null) {
- Collection<File> variantResDirs = mMultiFlavorSourceProvider.getResDirectories();
- resourceSet = new ResourceSet(getFlavorName());
- resourceSet.addSources(variantResDirs);
- resourceSets.add(resourceSet);
- }
-
- // build type overrides the flavors
- if (mBuildTypeSourceProvider != null) {
- Collection<File> typeResDirs = mBuildTypeSourceProvider.getResDirectories();
- resourceSet = new ResourceSet(mBuildType.getName());
- resourceSet.addSources(typeResDirs);
- resourceSets.add(resourceSet);
- }
-
- // variant specific overrides all
- if (mVariantSourceProvider != null) {
- Collection<File> variantResDirs = mVariantSourceProvider.getResDirectories();
- resourceSet = new ResourceSet(getFullName());
- resourceSet.addSources(variantResDirs);
- resourceSets.add(resourceSet);
- }
-
- return resourceSets;
- }
-
- /**
- * Returns the dynamic list of {@link AssetSet} based on the configuration, its dependencies,
- * as well as tested config if applicable (test of a library).
- *
- * The list is ordered in ascending order of importance, meaning the first set is meant to be
- * overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
- * {@link com.android.ide.common.res2.AssetMerger}.
- *
- * @return a list ResourceSet.
- */
- @NonNull
- public List<AssetSet> getAssetSets(@NonNull List<File> generatedResFolders,
- boolean includeDependencies) {
- List<AssetSet> assetSets = Lists.newArrayList();
-
- if (includeDependencies) {
- // the list of dependency must be reversed to use the right overlay order.
- for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
- LibraryDependency dependency = mFlatLibraries.get(n);
- File assetFolder = dependency.getAssetsFolder();
- if (assetFolder.isDirectory()) {
- AssetSet assetSet = new AssetSet(dependency.getFolder().getName());
- assetSet.addSource(assetFolder);
- assetSets.add(assetSet);
- }
- }
- }
-
- Collection<File> mainResDirs = mDefaultSourceProvider.getAssetsDirectories();
-
- // the main + generated asset folders are in the same AssetSet
- AssetSet assetSet = new AssetSet(BuilderConstants.MAIN);
- assetSet.addSources(mainResDirs);
- if (!generatedResFolders.isEmpty()) {
- for (File generatedResFolder : generatedResFolders) {
- assetSet.addSource(generatedResFolder);
- }
- }
- assetSets.add(assetSet);
-
- // the list of flavor must be reversed to use the right overlay order.
- for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
- SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
-
- Collection<File> flavorResDirs = sourceProvider.getAssetsDirectories();
- // we need the same of the flavor config, but it's in a different list.
- // This is fine as both list are parallel collections with the same number of items.
- assetSet = new AssetSet(mFlavors.get(n).getName());
- assetSet.addSources(flavorResDirs);
- assetSets.add(assetSet);
- }
-
- // multiflavor specific overrides flavor
- if (mMultiFlavorSourceProvider != null) {
- Collection<File> variantResDirs = mMultiFlavorSourceProvider.getAssetsDirectories();
- assetSet = new AssetSet(getFlavorName());
- assetSet.addSources(variantResDirs);
- assetSets.add(assetSet);
- }
-
- // build type overrides flavors
- if (mBuildTypeSourceProvider != null) {
- Collection<File> typeResDirs = mBuildTypeSourceProvider.getAssetsDirectories();
- assetSet = new AssetSet(mBuildType.getName());
- assetSet.addSources(typeResDirs);
- assetSets.add(assetSet);
- }
-
- // variant specific overrides all
- if (mVariantSourceProvider != null) {
- Collection<File> variantResDirs = mVariantSourceProvider.getAssetsDirectories();
- assetSet = new AssetSet(getFullName());
- assetSet.addSources(variantResDirs);
- assetSets.add(assetSet);
- }
-
- return assetSets;
- }
-
- @NonNull
- public List<File> getLibraryJniFolders() {
- List<File> list = Lists.newArrayListWithExpectedSize(mFlatLibraries.size());
-
- for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
- LibraryDependency dependency = mFlatLibraries.get(n);
- File jniFolder = dependency.getJniFolder();
- if (jniFolder.isDirectory()) {
- list.add(jniFolder);
- }
- }
-
- return list;
- }
-
- /**
- * Returns all the renderscript import folder that are outside of the current project.
- */
- @NonNull
- public List<File> getRenderscriptImports() {
- List<File> list = Lists.newArrayList();
-
- for (LibraryDependency lib : mFlatLibraries) {
- File rsLib = lib.getRenderscriptFolder();
- if (rsLib.isDirectory()) {
- list.add(rsLib);
- }
- }
-
- return list;
- }
-
- /**
- * Returns all the renderscript source folder from the main config, the flavors and the
- * build type.
- *
- * @return a list of folders.
- */
- @NonNull
- public List<File> getRenderscriptSourceList() {
- List<SourceProvider> providers = getSortedSourceProviders();
-
- List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
-
- for (SourceProvider provider : providers) {
- sourceList.addAll(provider.getRenderscriptDirectories());
- }
-
- return sourceList;
- }
-
- /**
- * Returns all the aidl import folder that are outside of the current project.
- */
- @NonNull
- public List<File> getAidlImports() {
- List<File> list = Lists.newArrayList();
-
- for (LibraryDependency lib : mFlatLibraries) {
- File aidlLib = lib.getAidlFolder();
- if (aidlLib.isDirectory()) {
- list.add(aidlLib);
- }
- }
-
- return list;
- }
-
- @NonNull
- public List<File> getAidlSourceList() {
- List<SourceProvider> providers = getSortedSourceProviders();
-
- List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
-
- for (SourceProvider provider : providers) {
- sourceList.addAll(provider.getAidlDirectories());
- }
-
- return sourceList;
- }
-
- @NonNull
- public List<File> getJniSourceList() {
- List<SourceProvider> providers = getSortedSourceProviders();
-
- List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
-
- for (SourceProvider provider : providers) {
- sourceList.addAll(provider.getCDirectories());
- }
-
- return sourceList;
- }
-
- @NonNull
- public List<File> getJniLibsList() {
- List<SourceProvider> providers = getSortedSourceProviders();
-
- List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
-
- for (SourceProvider provider : providers) {
- sourceList.addAll(provider.getJniLibsDirectories());
- }
-
- return sourceList;
- }
-
- /**
- * Returns the compile classpath for this config. If the config tests a library, this
- * will include the classpath of the tested config
- *
- * @return a non null, but possibly empty set.
- */
- @NonNull
- public Set<File> getCompileClasspath() {
- Set<File> classpath = Sets.newHashSetWithExpectedSize(
- mExternalJars.size() + mLocalJars.size() + mFlatLibraries.size());
-
- for (LibraryDependency lib : mFlatLibraries) {
- classpath.add(lib.getJarFile());
- for (File jarFile : lib.getLocalJars()) {
- classpath.add(jarFile);
- }
- }
-
- for (JarDependency jar : mExternalJars) {
- if (jar.isCompiled()) {
- classpath.add(jar.getJarFile());
- }
- }
-
- for (JarDependency jar : mLocalJars) {
- if (jar.isCompiled()) {
- classpath.add(jar.getJarFile());
- }
- }
-
- return classpath;
- }
-
- /**
- * Returns the list of packaged jars for this config. If the config tests a library, this
- * will include the jars of the tested config
- *
- * @return a non null, but possibly empty list.
- */
- @NonNull
- public Set<File> getPackagedJars() {
- Set<File> jars = Sets.newHashSetWithExpectedSize(
- mExternalJars.size() + mLocalJars.size() + mFlatLibraries.size());
-
- for (JarDependency jar : mExternalJars) {
- File jarFile = jar.getJarFile();
- if (jar.isPackaged() && jarFile.exists()) {
- jars.add(jarFile);
- }
- }
-
- for (JarDependency jar : mLocalJars) {
- File jarFile = jar.getJarFile();
- if (jar.isPackaged() && jarFile.exists()) {
- jars.add(jarFile);
- }
- }
-
- for (LibraryDependency libraryDependency : mFlatLibraries) {
- if (!libraryDependency.isOptional()) {
- File libJar = libraryDependency.getJarFile();
- if (libJar.exists()) {
- jars.add(libJar);
- }
- for (File jarFile : libraryDependency.getLocalJars()) {
- if (jarFile.isFile()) {
- jars.add(jarFile);
- }
- }
- }
- }
-
- return jars;
- }
-
- /**
- * Returns the list of provided-only jars for this config.
- *
- * @return a non null, but possibly empty list.
- */
- @NonNull
- public List<File> getProvidedOnlyJars() {
- Set<File> jars = Sets.newHashSetWithExpectedSize(mExternalJars.size() + mLocalJars.size());
-
- for (JarDependency jar : mExternalJars) {
- File jarFile = jar.getJarFile();
- if (jar.isCompiled() && !jar.isPackaged() && jarFile.exists()) {
- jars.add(jarFile);
- }
- }
-
- for (JarDependency jar : mLocalJars) {
- File jarFile = jar.getJarFile();
- if (jar.isCompiled() && !jar.isPackaged() && jarFile.exists()) {
- jars.add(jarFile);
- }
- }
-
- for (LibraryDependency libraryDependency : mFlatLibraries) {
- if (libraryDependency.isOptional()) {
- File libJar = libraryDependency.getJarFile();
- if (libJar.exists()) {
- jars.add(libJar);
- }
- for (File jarFile : libraryDependency.getLocalJars()) {
- if (jarFile.isFile()) {
- jars.add(jarFile);
- }
- }
- }
- }
-
-
- return Lists.newArrayList(jars);
- }
-
- /**
- * Adds a variant-specific BuildConfig field.
- * @param type the type of the field
- * @param name the name of the field
- * @param value the value of the field
- */
- public void addBuildConfigField(@NonNull String type, @NonNull String name, @NonNull String value) {
- ClassField classField = AndroidBuilder.createClassField(type, name, value);
- mBuildConfigFields.put(name, classField);
- }
-
- /**
- * Adds a variant-specific res value.
- * @param type the type of the field
- * @param name the name of the field
- * @param value the value of the field
- */
- public void addResValue(@NonNull String type, @NonNull String name, @NonNull String value) {
- ClassField classField = AndroidBuilder.createClassField(type, name, value);
- mResValues.put(name, classField);
- }
-
- /**
- * Returns a list of items for the BuildConfig class.
- *
- * Items can be either fields (instance of {@link com.android.builder.model.ClassField})
- * or comments (instance of String).
- *
- * @return a list of items.
- */
- @NonNull
- public List<Object> getBuildConfigItems() {
- List<Object> fullList = Lists.newArrayList();
-
- // keep track of the names already added. This is because we show where the items
- // come from so we cannot just put everything a map and let the new ones override the
- // old ones.
- Set<String> usedFieldNames = Sets.newHashSet();
-
- Collection<ClassField> list = mBuildConfigFields.values();
- if (!list.isEmpty()) {
- fullList.add("Fields from the variant");
- fillFieldList(fullList, usedFieldNames, list);
- }
-
- list = mBuildType.getBuildConfigFields().values();
- if (!list.isEmpty()) {
- fullList.add("Fields from build type: " + mBuildType.getName());
- fillFieldList(fullList, usedFieldNames, list);
- }
-
- for (F flavor : mFlavors) {
- list = flavor.getBuildConfigFields().values();
- if (!list.isEmpty()) {
- fullList.add("Fields from product flavor: " + flavor.getName());
- fillFieldList(fullList, usedFieldNames, list);
- }
- }
-
- list = mDefaultConfig.getBuildConfigFields().values();
- if (!list.isEmpty()) {
- fullList.add("Fields from default config.");
- fillFieldList(fullList, usedFieldNames, list);
- }
-
- return fullList;
- }
-
- /**
- * Return the merged build config fields for the variant.
- *
- * This is made of of the variant-specific fields overlayed on top of the build type ones,
- * the flavors ones, and the default config ones.
- *
- * @return a map of merged fields
- */
- @NonNull
- public Map<String, ClassField> getMergedBuildConfigFields() {
- Map<String, ClassField> mergedMap = Maps.newHashMap();
-
- // start from the lowest priority and just add it all. Higher priority fields
- // will replace lower priority ones.
-
- mergedMap.putAll(mDefaultConfig.getBuildConfigFields());
- for (int i = mFlavors.size() - 1; i >= 0 ; i--) {
- mergedMap.putAll(mFlavors.get(i).getBuildConfigFields());
- }
-
- mergedMap.putAll(mBuildType.getBuildConfigFields());
- mergedMap.putAll(mBuildConfigFields);
-
- return mergedMap;
- }
-
- /**
- * Return the merged res values for the variant.
- *
- * This is made of of the variant-specific fields overlayed on top of the build type ones,
- * the flavors ones, and the default config ones.
- *
- * @return a map of merged fields
- */
- @NonNull
- public Map<String, ClassField> getMergedResValues() {
- Map<String, ClassField> mergedMap = Maps.newHashMap();
-
- // start from the lowest priority and just add it all. Higher priority fields
- // will replace lower priority ones.
-
- mergedMap.putAll(mDefaultConfig.getResValues());
- for (int i = mFlavors.size() - 1; i >= 0 ; i--) {
- mergedMap.putAll(mFlavors.get(i).getResValues());
- }
-
- mergedMap.putAll(mBuildType.getResValues());
- mergedMap.putAll(mResValues);
-
- return mergedMap;
- }
-
- /**
- * Fills a list of Object from a given list of ClassField only if the name isn't in a set.
- * Each new item added adds its name to the list.
- * @param outList the out list
- * @param usedFieldNames the list of field names already in the list
- * @param list the list to copy items from
- */
- private static void fillFieldList(
- @NonNull List<Object> outList,
- @NonNull Set<String> usedFieldNames,
- @NonNull Collection<ClassField> list) {
- for (ClassField f : list) {
- String name = f.getName();
- if (!usedFieldNames.contains(name)) {
- usedFieldNames.add(f.getName());
- outList.add(f);
- }
- }
- }
-
- /**
- * Returns a list of generated resource values.
- *
- * Items can be either fields (instance of {@link com.android.builder.model.ClassField})
- * or comments (instance of String).
- *
- * @return a list of items.
- */
- @NonNull
- public List<Object> getResValues() {
- List<Object> fullList = Lists.newArrayList();
-
- // keep track of the names already added. This is because we show where the items
- // come from so we cannot just put everything a map and let the new ones override the
- // old ones.
- Set<String> usedFieldNames = Sets.newHashSet();
-
- Collection<ClassField> list = mResValues.values();
- if (!list.isEmpty()) {
- fullList.add("Values from the variant");
- fillFieldList(fullList, usedFieldNames, list);
- }
-
- list = mBuildType.getResValues().values();
- if (!list.isEmpty()) {
- fullList.add("Values from build type: " + mBuildType.getName());
- fillFieldList(fullList, usedFieldNames, list);
- }
-
- for (F flavor : mFlavors) {
- list = flavor.getResValues().values();
- if (!list.isEmpty()) {
- fullList.add("Values from product flavor: " + flavor.getName());
- fillFieldList(fullList, usedFieldNames, list);
- }
- }
-
- list = mDefaultConfig.getResValues().values();
- if (!list.isEmpty()) {
- fullList.add("Values from default config.");
- fillFieldList(fullList, usedFieldNames, list);
- }
-
- return fullList;
- }
-
- @Nullable
- public SigningConfig getSigningConfig() {
- if (mSigningConfigOverride != null) {
- return mSigningConfigOverride;
- }
-
- SigningConfig signingConfig = mBuildType.getSigningConfig();
- if (signingConfig != null) {
- return signingConfig;
- }
- return mMergedFlavor.getSigningConfig();
- }
-
- public boolean isSigningReady() {
- SigningConfig signingConfig = getSigningConfig();
- return signingConfig != null && signingConfig.isSigningReady();
- }
-
- /**
- * Returns the proguard config files coming from the project but also from the dependencies.
- *
- * Note that if the method is set to include config files coming from libraries, they will
- * only be included if the aars have already been unzipped.
- *
- * @param includeLibraries whether to include the library dependencies.
- * @return a non null list of proguard files.
- */
- @NonNull
- public List<File> getProguardFiles(boolean includeLibraries, List<File> defaultProguardConfig) {
- List<File> fullList = Lists.newArrayList();
-
- // add the config files from the build type, main config and flavors
- fullList.addAll(mDefaultConfig.getProguardFiles());
- fullList.addAll(mBuildType.getProguardFiles());
-
- for (F flavor : mFlavors) {
- fullList.addAll(flavor.getProguardFiles());
- }
-
- if (fullList.isEmpty()) {
- fullList.addAll(defaultProguardConfig);
- }
-
- // now add the one coming from the library dependencies
- if (includeLibraries) {
- for (LibraryDependency libraryDependency : mFlatLibraries) {
- File proguardRules = libraryDependency.getProguardRules();
- if (proguardRules.exists()) {
- fullList.add(proguardRules);
- }
- }
- }
-
- return fullList;
- }
-
- /**
- * Returns the proguard config files to be used for the test APK.
- */
- @NonNull
- public List<File> getTestProguardFiles() {
- List<File> fullList = Lists.newArrayList();
-
- // add the config files from the build type, main config and flavors
- fullList.addAll(mDefaultConfig.getTestProguardFiles());
- fullList.addAll(mBuildType.getTestProguardFiles());
-
- for (F flavor : mFlavors) {
- fullList.addAll(flavor.getTestProguardFiles());
- }
-
- return fullList;
- }
-
- @NonNull
- public List<Object> getConsumerProguardFiles() {
- List<Object> fullList = Lists.newArrayList();
-
- // add the config files from the build type, main config and flavors
- fullList.addAll(mDefaultConfig.getConsumerProguardFiles());
- fullList.addAll(mBuildType.getConsumerProguardFiles());
-
- for (F flavor : mFlavors) {
- fullList.addAll(flavor.getConsumerProguardFiles());
- }
-
- return fullList;
- }
-
- public boolean isTestCoverageEnabled() {
- return mBuildType.isTestCoverageEnabled();
- }
-
- /**
- * Returns the merged manifest placeholders. All product flavors are merged first, then build
- * type specific placeholders are added and potentially overrides product flavors values.
- * @return the merged manifest placeholders for a build variant.
- */
- @NonNull
- public Map<String, Object> getManifestPlaceholders() {
- Map<String, Object> mergedFlavorsPlaceholders = mMergedFlavor.getManifestPlaceholders();
- // so far, blindly override the build type placeholders
- mergedFlavorsPlaceholders.putAll(mBuildType.getManifestPlaceholders());
- return mergedFlavorsPlaceholders;
- }
-
- public boolean isMultiDexEnabled() {
- Boolean value = mBuildType.getMultiDexEnabled();
- if (value != null) {
- return value;
- }
-
- value = mMergedFlavor.getMultiDexEnabled();
- if (value != null) {
- return value;
- }
-
- return false;
- }
-
- public File getMultiDexKeepFile() {
- File value = mBuildType.getMultiDexKeepFile();
- if (value != null) {
- return value;
- }
-
- value = mMergedFlavor.getMultiDexKeepFile();
- if (value != null) {
- return value;
- }
-
- return null;
- }
-
- public File getMultiDexKeepProguard() {
- File value = mBuildType.getMultiDexKeepProguard();
- if (value != null) {
- return value;
- }
-
- value = mMergedFlavor.getMultiDexKeepProguard();
- if (value != null) {
- return value;
- }
-
- return null;
- }
-
- public boolean isLegacyMultiDexMode() {
- return isMultiDexEnabled() && getMinSdkVersion().getApiLevel() < 21;
- }
-
- /**
- * Returns the renderscript support mode.
- */
- public boolean getRenderscriptSupportModeEnabled() {
- Boolean value = mMergedFlavor.getRenderscriptSupportModeEnabled();
- if (value != null) {
- return value;
- }
-
- // default is false.
- return false;
- }
-
- /**
- * Returns the renderscript NDK mode.
- */
- public boolean getRenderscriptNdkModeEnabled() {
- Boolean value = mMergedFlavor.getRenderscriptNdkModeEnabled();
- if (value != null) {
- return value;
- }
-
- // default is false.
- return false;
- }
-
- public Collection<File> getJarJarRuleFiles() {
-
- ImmutableList.Builder<File> jarjarRuleFiles = ImmutableList.builder();
- jarjarRuleFiles.addAll(getMergedFlavor().getJarJarRuleFiles());
- jarjarRuleFiles.addAll(mBuildType.getJarJarRuleFiles());
- return jarjarRuleFiles.build();
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/VariantType.java b/base/build-system/builder/src/main/java/com/android/builder/core/VariantType.java
deleted file mode 100644
index a49f138..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/core/VariantType.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ArtifactMetaData;
-import com.google.common.collect.ImmutableList;
-
-/**
- * Type of a variant.
- */
-public enum VariantType {
- DEFAULT,
- LIBRARY,
- ANDROID_TEST(
- "androidTest",
- "AndroidTest",
- true,
- AndroidProject.ARTIFACT_ANDROID_TEST,
- ArtifactMetaData.TYPE_ANDROID),
- UNIT_TEST(
- "test",
- "UnitTest",
- false,
- AndroidProject.ARTIFACT_UNIT_TEST,
- ArtifactMetaData.TYPE_JAVA),
- ;
-
- public static ImmutableList<VariantType> getTestingTypes() {
- ImmutableList.Builder<VariantType> result = ImmutableList.builder();
- for (VariantType variantType : values()) {
- if (variantType.isForTesting()) {
- result.add(variantType);
- }
- }
- return result.build();
- }
-
- private final boolean mIsForTesting;
- private final String mPrefix;
- private final String mSuffix;
- private final boolean isSingleBuildType;
- private final String mArtifactName;
- private final int mArtifactType;
-
- /** App or library variant. */
- VariantType() {
- this.mIsForTesting = false;
- this.mPrefix = "";
- this.mSuffix = "";
- this.mArtifactName = AndroidProject.ARTIFACT_MAIN;
- this.mArtifactType = ArtifactMetaData.TYPE_ANDROID;
- this.isSingleBuildType = false;
- }
-
- /** Testing variant. */
- VariantType(
- String prefix,
- String suffix,
- boolean isSingleBuildType,
- String artifactName,
- int artifactType) {
- this.mArtifactName = artifactName;
- this.mArtifactType = artifactType;
- this.mIsForTesting = true;
- this.mPrefix = prefix;
- this.mSuffix = suffix;
- this.isSingleBuildType = isSingleBuildType;
- }
-
- /**
- * Returns true if the variant is automatically generated for testing purposed, false
- * otherwise.
- */
- public boolean isForTesting() {
- return mIsForTesting;
- }
-
- /**
- * Returns prefix used for naming source directories. This is an empty string in
- * case of non-testing variants and a camel case string otherwise, e.g. "androidTest".
- */
- @NonNull
- public String getPrefix() {
- return mPrefix;
- }
-
- /**
- * Returns suffix used for naming Gradle tasks. This is an empty string in
- * case of non-testing variants and a camel case string otherwise, e.g. "AndroidTest".
- */
- @NonNull
- public String getSuffix() {
- return mSuffix;
- }
-
- /**
- * Returns the name used in the builder model for artifacts that correspond to this variant
- * type.
- */
- @NonNull
- public String getArtifactName() {
- return mArtifactName;
- }
-
- /**
- * Returns the artifact type used in the builder model.
- */
- public int getArtifactType() {
- return mArtifactType;
- }
-
- /**
- * Whether the artifact type supports only a single build type.
- */
- public boolean isSingleBuildType() {
- return isSingleBuildType;
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java b/base/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java
deleted file mode 100644
index 52298f4..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.BaseConfig;
-import com.android.builder.model.ClassField;
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.List;
-import java.util.Map;
-
-/**
- * An object that contain a BuildConfig configuration
- */
-public abstract class BaseConfigImpl implements Serializable, BaseConfig {
- private static final long serialVersionUID = 1L;
-
- private final Map<String, ClassField> mBuildConfigFields = Maps.newTreeMap();
- private final Map<String, ClassField> mResValues = Maps.newTreeMap();
- private final List<File> mProguardFiles = Lists.newArrayList();
- private final List<File> mConsumerProguardFiles = Lists.newArrayList();
- private final List<File> mTestProguardFiles = Lists.newArrayList();
- private final Map<String, Object> mManifestPlaceholders = Maps.newHashMap();
- @Nullable
- private Boolean mMultiDexEnabled;
-
- @Nullable
- private File mMultiDexKeepProguard;
-
- @Nullable
- private File mMultiDexKeepFile;
-
- @NonNull
- private List<File> mJarJarRuleFiles = Lists.newArrayList();
-
- /**
- * Adds a BuildConfig field.
- */
- public void addBuildConfigField(@NonNull ClassField field) {
- mBuildConfigFields.put(field.getName(), field);
- }
-
- /**
- * Adds a generated resource value.
- */
- public void addResValue(@NonNull ClassField field) { mResValues.put(field.getName(), field);
- }
-
- /**
- * Adds a generated resource value.
- */
- public void addResValues(@NonNull Map<String, ClassField> values) {
- mResValues.putAll(values);
- }
-
- /**
- * Returns the BuildConfig fields.
- */
- @Override
- @NonNull
- public Map<String, ClassField> getBuildConfigFields() {
- return mBuildConfigFields;
- }
-
- /**
- * Adds BuildConfig fields.
- */
- public void addBuildConfigFields(@NonNull Map<String, ClassField> fields) {
- mBuildConfigFields.putAll(fields);
- }
-
- /**
- * Returns the generated resource values.
- */
- @NonNull
- @Override
- public Map<String, ClassField> getResValues() {
- return mResValues;
- }
-
- /**
- * Returns ProGuard configuration files to be used.
- *
- * <p>There are 2 default rules files
- * <ul>
- * <li>proguard-android.txt
- * <li>proguard-android-optimize.txt
- * </ul>
- * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
- * full path to the files. They are identical except for enabling optimizations.
- *
- * <p>See similarly named methods to specify the files.
- */
- @Override
- @NonNull
- public List<File> getProguardFiles() {
- return mProguardFiles;
- }
-
- /**
- * ProGuard rule files to be included in the published AAR.
- *
- * <p>These proguard rule files will then be used by any application project that consumes the
- * AAR (if ProGuard is enabled).
- *
- * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
- *
- * <p>This is only valid for Library project. This is ignored in Application project.
- */
- @Override
- @NonNull
- public List<File> getConsumerProguardFiles() {
- return mConsumerProguardFiles;
- }
-
- @NonNull
- @Override
- public List<File> getTestProguardFiles() {
- return mTestProguardFiles;
- }
-
- /**
- * Returns the manifest placeholders.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger#TOC-Placeholder-support">
- * Manifest merger</a>.
- */
- @NonNull
- @Override
- public Map<String, Object> getManifestPlaceholders() {
- return mManifestPlaceholders;
- }
-
- /**
- * Adds manifest placeholders.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger#TOC-Placeholder-support">
- * Manifest merger</a>.
- */
- public void addManifestPlaceholders(@NonNull Map<String, Object> manifestPlaceholders) {
- mManifestPlaceholders.putAll(manifestPlaceholders);
- }
-
- /**
- * Sets a new set of manifest placeholders.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger#TOC-Placeholder-support">
- * Manifest merger</a>.
- */
- public void setManifestPlaceholders(@NonNull Map<String, Object> manifestPlaceholders) {
- mManifestPlaceholders.clear();
- this.mManifestPlaceholders.putAll(manifestPlaceholders);
- }
-
- protected void _initWith(@NonNull BaseConfig that) {
- setBuildConfigFields(that.getBuildConfigFields());
- setResValues(that.getResValues());
-
- mProguardFiles.clear();
- mProguardFiles.addAll(that.getProguardFiles());
-
- mConsumerProguardFiles.clear();
- mConsumerProguardFiles.addAll(that.getConsumerProguardFiles());
-
- mTestProguardFiles.clear();
- mTestProguardFiles.addAll(that.getTestProguardFiles());
-
- mManifestPlaceholders.clear();
- mManifestPlaceholders.putAll(that.getManifestPlaceholders());
-
- mMultiDexEnabled = that.getMultiDexEnabled();
-
- mMultiDexKeepFile = that.getMultiDexKeepFile();
- mMultiDexKeepProguard = that.getMultiDexKeepProguard();
-
- mJarJarRuleFiles = that.getJarJarRuleFiles();
- }
-
- private void setBuildConfigFields(@NonNull Map<String, ClassField> fields) {
- mBuildConfigFields.clear();
- mBuildConfigFields.putAll(fields);
- }
-
- private void setResValues(@NonNull Map<String, ClassField> fields) {
- mResValues.clear();
- mResValues.putAll(fields);
- }
-
- /**
- * Whether Multi-Dex is enabled for this variant.
- */
- @Override
- @Nullable
- public Boolean getMultiDexEnabled() {
- return mMultiDexEnabled;
- }
-
- public void setMultiDexEnabled(@Nullable Boolean multiDex) {
- mMultiDexEnabled = multiDex;
- }
-
- @Override
- @Nullable
- public File getMultiDexKeepFile() {
- return mMultiDexKeepFile;
- }
-
- public void setMultiDexKeepFile(@Nullable File file) {
- mMultiDexKeepFile = file;
- }
-
- @Override
- @Nullable
- public File getMultiDexKeepProguard() {
- return mMultiDexKeepProguard;
- }
-
- public void setMultiDexKeepProguard(@Nullable File file) {
- mMultiDexKeepProguard = file;
- }
-
- public void setJarJarRuleFiles(@NonNull List<File> files) {
- mJarJarRuleFiles = files;
- }
-
- @NonNull
- @Override
- public List<File> getJarJarRuleFiles() {
- return mJarJarRuleFiles;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof BaseConfigImpl)) {
- return false;
- }
-
- BaseConfigImpl that = (BaseConfigImpl) o;
-
- return Objects.equal(mBuildConfigFields, that.mBuildConfigFields) &&
- Objects.equal(mConsumerProguardFiles, that.mConsumerProguardFiles) &&
- Objects.equal(mManifestPlaceholders, that.mManifestPlaceholders) &&
- Objects.equal(mMultiDexEnabled, that.mMultiDexEnabled) &&
- Objects.equal(mMultiDexKeepFile, that.mMultiDexKeepFile) &&
- Objects.equal(mMultiDexKeepProguard, that.mMultiDexKeepProguard) &&
- Objects.equal(mProguardFiles, that.mProguardFiles) &&
- Objects.equal(mResValues, that.mResValues) &&
- Objects.equal(mJarJarRuleFiles, that.mJarJarRuleFiles);
-
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(
- mBuildConfigFields,
- mResValues,
- mProguardFiles,
- mConsumerProguardFiles,
- mManifestPlaceholders,
- mMultiDexEnabled,
- mMultiDexKeepFile,
- mMultiDexKeepProguard,
- mJarJarRuleFiles);
- }
-
- @Override
- public String toString() {
- return "BaseConfigImpl{" +
- "mBuildConfigFields=" + mBuildConfigFields +
- ", mResValues=" + mResValues +
- ", mProguardFiles=" + mProguardFiles +
- ", mConsumerProguardFiles=" + mConsumerProguardFiles +
- ", mManifestPlaceholders=" + mManifestPlaceholders +
- ", mMultiDexEnabled=" + mMultiDexEnabled +
- ", mMultiDexKeepFile=" + mMultiDexKeepFile +
- ", mMultiDexKeepProguard=" + mMultiDexKeepProguard +
- ", mJarJarRuleFiles=" + mJarJarRuleFiles +
- '}';
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java b/base/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java
deleted file mode 100644
index 2338c95..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.utils.SparseArray;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Fake IAndroidTarget used for SDK prebuilts in the Android source tree.
- */
-public class FakeAndroidTarget implements IAndroidTarget {
- private final String mSdkLocation;
- private final SparseArray<String> mPaths = new SparseArray<String>();
- private final List<String> mBootClasspath = Lists.newArrayListWithExpectedSize(2);
- private final int mApiLevel;
-
- public FakeAndroidTarget(String sdkLocation, String target) {
- mSdkLocation = sdkLocation;
- mApiLevel = getApiLevel(target);
-
- if ("unstubbed".equals(target)) {
- mBootClasspath.add(mSdkLocation +
- "/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar");
- mBootClasspath.add(mSdkLocation +
- "/out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar");
-
- // pre-build the path to the platform components
- mPaths.put(ANDROID_JAR, mSdkLocation + "/prebuilts/sdk/current/" +
- SdkConstants.FN_FRAMEWORK_LIBRARY);
- mPaths.put(ANDROID_AIDL, mSdkLocation + "/prebuilts/sdk/renderscript/" +
- SdkConstants.FN_FRAMEWORK_AIDL);
- } else {
- String apiPrebuilts;
-
- if ("current".equals(target)) {
- apiPrebuilts = mSdkLocation + "/prebuilts/sdk/current/";
- } else {
- apiPrebuilts = mSdkLocation + "/prebuilts/sdk/" + Integer.toString(mApiLevel) + "/";
- }
-
- // pre-build the path to the platform components
- mBootClasspath.add(apiPrebuilts + SdkConstants.FN_FRAMEWORK_LIBRARY);
- mPaths.put(ANDROID_JAR, apiPrebuilts + SdkConstants.FN_FRAMEWORK_LIBRARY);
- mPaths.put(ANDROID_AIDL, apiPrebuilts + SdkConstants.FN_FRAMEWORK_AIDL);
- }
- }
-
- private int getApiLevel(String target) {
- if (target.startsWith("android-")) {
- return Integer.parseInt(target.substring("android-".length()));
- }
-
- // We don't actually know the API level at this point since the mode is "current"
- // or "unstubbed". This API is only called to check if annotations.jar needs to be
- // added to the classpath, so by putting a large value we make sure annotations.jar
- // isn't used.
- return 99;
- }
-
- @Override
- public String getPath(int pathId) {
- return mPaths.get(pathId);
- }
-
- @Override
- public File getFile(int pathId) {
- return new File(getPath(pathId));
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- // this is not used internally since we properly query for the right Build Tools from
- // the SdkManager.
- return null;
- }
-
- @Override @NonNull
- public List<String> getBootClasspath() {
- return mBootClasspath;
- }
-
- @Override
- public String getLocation() {
- return mSdkLocation;
- }
-
- @Override
- public String getVendor() {
- return "android";
- }
-
- @Override
- public String getName() {
- return "android";
- }
-
- @Override
- public String getFullName() {
- return "android";
- }
-
- @Override
- public String getClasspathName() {
- return "android";
- }
-
- @Override
- public String getShortClasspathName() {
- return "android";
- }
-
- @Override
- public String getDescription() {
- return "android";
- }
-
- @NonNull
- @Override
- public AndroidVersion getVersion() {
- return new AndroidVersion(mApiLevel, null);
- }
-
- @Override
- public String getVersionName() {
- return "Android API level " + mApiLevel;
- }
-
- @Override
- public int getRevision() {
- return 1;
- }
-
- @Override
- public boolean isPlatform() {
- return true;
- }
-
- @Override
- public IAndroidTarget getParent() {
- return null;
- }
-
- @Override
- public boolean hasRenderingLibrary() {
- return false;
- }
-
- @NonNull
- @Override
- public File[] getSkins() {
- return new File[0];
- }
-
- @Override
- public File getDefaultSkin() {
- return null;
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getAdditionalLibraries() {
- return ImmutableList.of();
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getOptionalLibraries() {
- return ImmutableList.of();
- }
-
- @Override
- public String[] getPlatformLibraries() {
- return new String[0];
- }
-
- @Override
- public String getProperty(String name) {
- return null;
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- return null;
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- return null;
- }
-
- @Override
- public Map<String, String> getProperties() {
- return null;
- }
-
- @Override
- public int getUsbVendorId() {
- return 0;
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- return new ISystemImage[0];
- }
-
- @Override
- public ISystemImage getSystemImage(IdDisplay tag, String abiType) {
- return null;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- return false;
- }
-
- @Override
- public String hashString() {
- return "android-" + mApiLevel;
- }
-
- @Override
- public int compareTo(IAndroidTarget iAndroidTarget) {
- FakeAndroidTarget that = (FakeAndroidTarget) iAndroidTarget;
- return mSdkLocation.compareTo(that.mSdkLocation);
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java b/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java
deleted file mode 100644
index 6c1bda2..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal.compiler;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.compiling.DependencyFileProcessor;
-import com.android.builder.internal.incremental.DependencyData;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessExecutor;
-import com.android.ide.common.process.ProcessInfoBuilder;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.ide.common.process.ProcessResult;
-import com.android.sdklib.io.FileOp;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * A Source File processor for AIDL files. This compiles each aidl file found by the SourceSearcher.
- */
-public class AidlProcessor implements SourceSearcher.SourceFileProcessor {
-
- @NonNull
- private final String mAidlExecutable;
- @NonNull
- private final String mFrameworkLocation;
- @NonNull
- private final List<File> mImportFolders;
- @NonNull
- private final File mSourceOutputDir;
- @Nullable
- private final File mParcelableOutputDir;
- @NonNull
- private final DependencyFileProcessor mDependencyFileProcessor;
- @NonNull
- private final ProcessExecutor mProcessExecutor;
- @NonNull
- private final ProcessOutputHandler mProcessOutputHandler;
-
- public AidlProcessor(
- @NonNull String aidlExecutable,
- @NonNull String frameworkLocation,
- @NonNull List<File> importFolders,
- @NonNull File sourceOutputDir,
- @Nullable File parcelableOutputDir,
- @NonNull DependencyFileProcessor dependencyFileProcessor,
- @NonNull ProcessExecutor processExecutor,
- @NonNull ProcessOutputHandler processOutputHandler) {
- mAidlExecutable = aidlExecutable;
- mFrameworkLocation = frameworkLocation;
- mImportFolders = importFolders;
- mSourceOutputDir = sourceOutputDir;
- mParcelableOutputDir = parcelableOutputDir;
- mDependencyFileProcessor = dependencyFileProcessor;
- mProcessExecutor = processExecutor;
- mProcessOutputHandler = processOutputHandler;
- }
-
- @Override
- public void processFile(@NonNull File sourceFolder, @NonNull File sourceFile)
- throws ProcessException, IOException {
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
-
- builder.setExecutable(mAidlExecutable);
-
- builder.addArgs("-p" + mFrameworkLocation);
- builder.addArgs("-o" + mSourceOutputDir.getAbsolutePath());
-
- // add all the library aidl folders to access parcelables that are in libraries
- for (File f : mImportFolders) {
- builder.addArgs("-I" + f.getAbsolutePath());
- }
-
- // create a temp file for the dependency
- File depFile = File.createTempFile("aidl", ".d");
- builder.addArgs("-d" + depFile.getAbsolutePath());
-
- builder.addArgs(sourceFile.getAbsolutePath());
-
- ProcessResult result = mProcessExecutor.execute(
- builder.createProcess(), mProcessOutputHandler);
- result.rethrowFailure().assertNormalExitValue();
-
- // send the dependency file to the processor.
- DependencyData data = mDependencyFileProcessor.processFile(depFile);
-
- if (mParcelableOutputDir != null && data != null && data.getOutputFiles().isEmpty()) {
- // looks like a parcelable. Store it in the 2ndary output of the DependencyData object.
-
- String relative = FileOp.makeRelative(sourceFolder, sourceFile);
-
- File destFile = new File(mParcelableOutputDir, relative);
- destFile.getParentFile().mkdirs();
- Files.copy(sourceFile, destFile);
- data.addSecondaryOutputFile(destFile.getPath());
- }
-
- depFile.delete();
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexKey.java b/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexKey.java
deleted file mode 100644
index 0311fc5..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexKey.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal.compiler;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.concurrency.Immutable;
-import com.android.sdklib.repository.FullRevision;
-
-import java.io.File;
-
-/**
- * Key to store Item/StoredItem in maps.
- * The key contains the element that are used for the dex call:
- * - source file
- * - build tools revision
- * - jumbo mode
- */
- at Immutable
-class DexKey extends PreProcessCache.Key {
- private final boolean mJumboMode;
-
- static DexKey of(@NonNull File sourceFile, @NonNull FullRevision buildToolsRevision,
- boolean jumboMode) {
- return new DexKey(sourceFile, buildToolsRevision, jumboMode);
- }
-
- private DexKey(@NonNull File sourceFile, @NonNull FullRevision buildToolsRevision,
- boolean jumboMode) {
- super(sourceFile, buildToolsRevision);
- mJumboMode = jumboMode;
- }
-
- boolean isJumboMode() {
- return mJumboMode;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof DexKey)) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
-
- DexKey dexKey = (DexKey) o;
-
- if (mJumboMode != dexKey.mJumboMode) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- result = 31 * result + (mJumboMode ? 1 : 0);
- return result;
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/JackConversionCache.java b/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/JackConversionCache.java
deleted file mode 100644
index 8adbcc3..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/JackConversionCache.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal.compiler;
-
-import com.android.annotations.NonNull;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.DexOptions;
-import com.android.ide.common.process.JavaProcessExecutor;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.android.utils.Pair;
-import com.google.common.io.Files;
-
-import org.w3c.dom.NamedNodeMap;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Cache for jar -> jack conversion, using the Jill tool.
- *
- * Since we cannot yet have a single task for each library that needs to be run through Jill
- * (because there is no task-level parallelization), this class allows reusing the output of
- * the jill process for a library in a project in other projects.
- *
- * Because different project could use different build-tools, both the library to be converted
- * and the version of the build tools are used as keys in the cache.
- *
- * The API is fairly simple, just call {@link #convertLibrary(File, File, DexOptions, BuildToolInfo, boolean, JavaProcessExecutor, ProcessOutputHandler)}
- *
- * The call will be blocking until the conversion happened, either through actually running Jill or
- * through copying the output of a previous Jill run.
- *
- * After a build a call to {@link #clear(java.io.File, com.android.utils.ILogger)} with a file
- * will allow saving the known converted libraries for future reuse.
- */
-public class JackConversionCache extends PreProcessCache<PreProcessCache.Key> {
-
- private static final JackConversionCache sSingleton = new JackConversionCache();
-
- public static JackConversionCache getCache() {
- return sSingleton;
- }
-
- @NonNull
- @Override
- protected KeyFactory<Key> getKeyFactory() {
- return new KeyFactory<Key>() {
- @Override
- public Key of(@NonNull File sourceFile, @NonNull FullRevision revision,
- @NonNull NamedNodeMap attrMap) {
- return Key.of(sourceFile, revision);
- }
- };
- }
-
- /**
- * Converts a given library to a given output with Jill, using a specific version of the
- * build-tools.
- *
- * @param inputFile the jar to pre-dex
- * @param outFile the output file.
- * @param dexOptions the dex options to run pre-dex
- * @param buildToolInfo the build tools info
- * @param verbose verbose flag
- * @param processExecutor the java process executor.
- * @throws ProcessException
- */
- public void convertLibrary(
- @NonNull File inputFile,
- @NonNull File outFile,
- @NonNull DexOptions dexOptions,
- @NonNull BuildToolInfo buildToolInfo,
- boolean verbose,
- @NonNull JavaProcessExecutor processExecutor,
- @NonNull ProcessOutputHandler processOutputHandler,
- @NonNull ILogger logger)
- throws ProcessException, InterruptedException, IOException {
-
- Key itemKey = Key.of(inputFile, buildToolInfo.getRevision());
-
- Pair<PreProcessCache.Item, Boolean> pair = getItem(itemKey);
- Item item = pair.getFirst();
-
- // if this is a new item
- if (pair.getSecond()) {
- try {
- // haven't process this file yet so do it and record it.
- List<File> files = AndroidBuilder.convertLibaryToJackUsingApis(
- inputFile,
- outFile,
- dexOptions,
- buildToolInfo,
- verbose,
- processExecutor,
- processOutputHandler,
- logger);
- item.getOutputFiles().addAll(files);
-
- incrementMisses();
- } catch (ProcessException exception) {
- // in case of error, delete (now obsolete) output file
- outFile.delete();
- // and rethrow the error
- throw exception;
- } finally {
- // enable other threads to use the output of this pre-dex.
- // if something was thrown they'll handle the missing output file.
- item.getLatch().countDown();
- }
- } else {
- // wait until the file is pre-dexed by the first thread.
- item.getLatch().await();
-
- // check that the generated file actually exists
- // while the api allow for 2+ files, there's only ever one in this case.
- File fromFile = item.getOutputFiles().get(0);
-
- if (fromFile.isFile()) {
- // file already pre-dex, just copy the output.
- // while the api allow for 2+ files, there's only ever one in this case.
- Files.copy(fromFile, outFile);
- incrementHits();
- }
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java b/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java
deleted file mode 100644
index fe4009b..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal.compiler;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.DexOptions;
-import com.android.ide.common.process.JavaProcessExecutor;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.Pair;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.logging.Logger;
-
-/**
- * Pre Dexing cache.
- *
- * Since we cannot yet have a single task for each library that needs to be pre-dexed (because
- * there is no task-level parallelization), this class allows reusing the output of the pre-dexing
- * of a library in a project to write the output of the pre-dexing of the same library in
- * a different project.
- *
- * Because different project could use different build-tools, both the library to pre-dex and the
- * version of the build tools are used as keys in the cache.
- *
- * The API is fairly simple, just call {@link #preDexLibrary(File, File, boolean, DexOptions, BuildToolInfo, boolean, JavaProcessExecutor, ProcessOutputHandler)}
- *
- * The call will be blocking until the pre-dexing happened, either through actual pre-dexing or
- * through copying the output of a previous pre-dex run.
- *
- * After a build a call to {@link #clear(java.io.File, com.android.utils.ILogger)} with a file
- * will allow saving the known pre-dexed libraries for future reuse.
- */
-public class PreDexCache extends PreProcessCache<DexKey> {
-
- private static final String ATTR_JUMBO_MODE = "jumboMode";
-
- private static final PreDexCache sSingleton = new PreDexCache();
-
- public static PreDexCache getCache() {
- return sSingleton;
- }
-
- @Override
- @NonNull
- protected KeyFactory<DexKey> getKeyFactory() {
- return new KeyFactory<DexKey>() {
- @Override
- public DexKey of(@NonNull File sourceFile, @NonNull FullRevision revision,
- @NonNull NamedNodeMap attrMap) {
- return DexKey.of(sourceFile, revision,
- Boolean.parseBoolean(attrMap.getNamedItem(ATTR_JUMBO_MODE).getNodeValue()));
- }
- };
- }
-
- /**
- * Pre-dex a given library to a given output with a specific version of the build-tools.
- * @param inputFile the jar to pre-dex
- * @param outFile the output file or folder (if multi-dex is enabled). must exist
- * @param multiDex whether mutli-dex is enabled.
- * @param dexOptions the dex options to run pre-dex
- * @param buildToolInfo the build tools info
- * @param verbose verbose flag
- * @param processExecutor the process executor
- * @throws IOException
- * @throws ProcessException
- * @throws InterruptedException
- */
- public void preDexLibrary(
- @NonNull File inputFile,
- @NonNull File outFile,
- boolean multiDex,
- @NonNull DexOptions dexOptions,
- @NonNull BuildToolInfo buildToolInfo,
- boolean verbose,
- @NonNull JavaProcessExecutor processExecutor,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws IOException, ProcessException, InterruptedException {
- checkState(!multiDex || outFile.isDirectory());
-
- DexKey itemKey = DexKey.of(
- inputFile,
- buildToolInfo.getRevision(),
- dexOptions.getJumboMode());
-
- Pair<Item, Boolean> pair = getItem(itemKey);
- Item item = pair.getFirst();
-
- // if this is a new item
- if (pair.getSecond()) {
- try {
- // haven't process this file yet so do it and record it.
- List<File> files = AndroidBuilder.preDexLibrary(
- inputFile,
- outFile,
- multiDex,
- dexOptions,
- buildToolInfo,
- verbose,
- processExecutor,
- processOutputHandler);
-
- item.getOutputFiles().clear();
- item.getOutputFiles().addAll(files);
-
- incrementMisses();
- } catch (ProcessException exception) {
- // in case of error, delete (now obsolete) output file
- outFile.delete();
- // and rethrow the error
- throw exception;
- } finally {
- // enable other threads to use the output of this pre-dex.
- // if something was thrown they'll handle the missing output file.
- item.getLatch().countDown();
- }
- } else {
- // wait until the file is pre-dexed by the first thread.
- item.getLatch().await();
-
- // check that the generated file actually exists
- if (item.areOutputFilesPresent()) {
- if (multiDex) {
- // output should be a folder
- for (File sourceFile : item.getOutputFiles()) {
- File destFile = new File(outFile, sourceFile.getName());
- checkSame(sourceFile, destFile);
- Files.copy(sourceFile, destFile);
- }
-
- } else {
- // file already pre-dex, just copy the output.
- if (item.getOutputFiles().isEmpty()) {
- throw new RuntimeException(item.toString());
- }
- checkSame(item.getOutputFiles().get(0), outFile);
- Files.copy(item.getOutputFiles().get(0), outFile);
- }
- incrementHits();
-
- }
- }
- }
-
- @Nullable
- @Override
- protected Node createItemNode(
- @NonNull Document document,
- @NonNull DexKey itemKey,
- @NonNull BaseItem item) throws IOException {
- Node itemNode = super.createItemNode(document, itemKey, item);
-
- if (itemNode != null) {
- Attr attr = document.createAttribute(ATTR_JUMBO_MODE);
- attr.setValue(Boolean.toString(itemKey.isJumboMode()));
- itemNode.getAttributes().setNamedItem(attr);
- }
-
- return itemNode;
- }
-
- private static void checkSame(@NonNull File source, @NonNull File dest) {
- if (source.equals(dest)) {
- Logger.getAnonymousLogger().info(
- String.format("%s l:%d ts:%d", source, source.length(), source.lastModified()));
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreProcessCache.java b/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreProcessCache.java
deleted file mode 100644
index e3bd385..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreProcessCache.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal.compiler;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.concurrency.GuardedBy;
-import com.android.annotations.concurrency.Immutable;
-import com.android.ide.common.xml.XmlPrettyPrinter;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.android.utils.Pair;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Charsets;
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.hash.HashCode;
-import com.google.common.hash.Hashing;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.logging.Logger;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- */
-public abstract class PreProcessCache<T extends PreProcessCache.Key> {
-
- private static final String NODE_ITEMS = "items";
- private static final String NODE_ITEM = "item";
- private static final String NODE_DEX = "dex";
- private static final String ATTR_VERSION = "version";
- private static final String ATTR_JAR = "jar";
- private static final String ATTR_DEX = "dex";
- private static final String ATTR_SHA1 = "sha1";
- private static final String ATTR_REVISION = "revision";
-
- private static final String XML_VERSION = "2";
-
- protected interface BaseItem {
- @NonNull
- File getSourceFile();
-
- @NonNull
- List<File> getOutputFiles();
-
- @Nullable
- HashCode getSourceHash();
-
- boolean areOutputFilesPresent();
-
- }
-
- /**
- * Items representing jar/dex files that have been processed during a build.
- */
- @Immutable
- protected static class Item implements BaseItem {
- @NonNull
- private final File mSourceFile;
- @NonNull
- private final List<File> mOutputFiles;
- @NonNull
- private final CountDownLatch mLatch;
-
- Item(
- @NonNull File sourceFile,
- @NonNull List<File> outputFiles,
- @NonNull CountDownLatch latch) {
- mSourceFile = sourceFile;
- mOutputFiles = Lists.newArrayList(outputFiles);
- mLatch = latch;
- }
-
- Item(
- @NonNull File sourceFile,
- @NonNull CountDownLatch latch) {
- mSourceFile = sourceFile;
- mOutputFiles = Lists.newArrayList();
- mLatch = latch;
- }
-
- @Override
- @NonNull
- public File getSourceFile() {
- return mSourceFile;
- }
-
- @Override
- @NonNull
- public List<File> getOutputFiles() {
- return mOutputFiles;
- }
-
- @Nullable
- @Override
- public HashCode getSourceHash() {
- return null;
- }
-
- @NonNull
- protected CountDownLatch getLatch() {
- return mLatch;
- }
-
- @Override
- public boolean areOutputFilesPresent() {
- boolean filesOk = !mOutputFiles.isEmpty();
- for (File outputFile : mOutputFiles) {
- filesOk &= outputFile.isFile();
- }
- return filesOk;
- }
-
- @Override
- public String toString() {
- return "Item{" +
- "mOutputFiles=" + mOutputFiles +
- ", mSourceFile=" + mSourceFile +
- '}';
- }
- }
-
- /**
- * Items representing jar/dex files that have been processed in a previous build, then were
- * stored in a cache file and then reloaded during the current build.
- */
- @Immutable
- protected static class StoredItem implements BaseItem {
- @NonNull
- private final File mSourceFile;
- @NonNull
- private final List<File> mOutputFiles;
- @NonNull
- private final HashCode mSourceHash;
-
- StoredItem(
- @NonNull File sourceFile,
- @NonNull List<File> outputFiles,
- @NonNull HashCode sourceHash) {
- mSourceFile = sourceFile;
- mOutputFiles = Lists.newArrayList(outputFiles);
- mSourceHash = sourceHash;
- }
-
- @Override
- @NonNull
- public File getSourceFile() {
- return mSourceFile;
- }
-
- @Override
- @NonNull
- public List<File> getOutputFiles() {
- return mOutputFiles;
- }
-
- @Override
- @NonNull
- public HashCode getSourceHash() {
- return mSourceHash;
- }
-
- @Override
- public boolean areOutputFilesPresent() {
- boolean filesOk = !mOutputFiles.isEmpty();
- for (File outputFile : mOutputFiles) {
- filesOk &= outputFile.isFile();
- }
- return filesOk;
- }
-
- @Override
- public String toString() {
- return "StoredItem{" +
- "mSourceFile=" + mSourceFile +
- ", mOutputFiles=" + mOutputFiles +
- ", mSourceHash=" + mSourceHash +
- '}';
- }
- }
-
- /**
- * Key to store Item/StoredItem in maps.
- * The key contains the element that are used for the dex call:
- * - source file
- * - build tools revision
- * - jumbo mode
- */
- @Immutable
- protected static class Key {
- @NonNull
- private final File mSourceFile;
- @NonNull
- private final FullRevision mBuildToolsRevision;
-
- public static Key of(@NonNull File sourceFile, @NonNull FullRevision buildToolsRevision) {
- return new Key(sourceFile, buildToolsRevision);
- }
-
- protected Key(@NonNull File sourceFile, @NonNull FullRevision buildToolsRevision) {
- mSourceFile = sourceFile;
- mBuildToolsRevision = buildToolsRevision;
- }
-
- @NonNull
- public FullRevision getBuildToolsRevision() {
- return mBuildToolsRevision;
- }
-
- @NonNull
- public File getSourceFile() {
- return mSourceFile;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof Key)) {
- return false;
- }
-
- Key key = (Key) o;
-
- if (!mBuildToolsRevision.equals(key.mBuildToolsRevision)) {
- return false;
- }
- if (!mSourceFile.equals(key.mSourceFile)) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mSourceFile, mBuildToolsRevision);
- }
- }
-
- protected interface KeyFactory<T> {
- T of(@NonNull File sourceFile, @NonNull FullRevision revision, @NonNull NamedNodeMap attrMap);
- }
-
- @GuardedBy("this")
- private boolean mLoaded = false;
-
- @GuardedBy("this")
- private final Map<T, Item> mMap = Maps.newHashMap();
- @GuardedBy("this")
- private final Map<T, StoredItem> mStoredItems = Maps.newHashMap();
-
- @GuardedBy("this")
- private int mMisses = 0;
- @GuardedBy("this")
- private int mHits = 0;
-
- @NonNull
- protected abstract KeyFactory<T> getKeyFactory();
-
- /**
- * Loads the stored item. This can be called several times (per subproject), so only
- * the first call should do something.
- */
- public synchronized void load(@NonNull File itemStorage) {
- if (mLoaded) {
- return;
- }
-
- loadItems(itemStorage);
-
- mLoaded = true;
- }
-
- /**
- * Returns an {@link Item} loaded from the cache. If no item can be found this, throws an
- * exception.
- *
- * @param itemKey the key of the item
- * @return a pair of item, boolean
- */
- protected synchronized Pair<Item, Boolean> getItem(@NonNull T itemKey) {
-
- // get the item
- Item item = mMap.get(itemKey);
-
- boolean newItem = false;
-
- if (item == null) {
- // check if we have a stored version.
- StoredItem storedItem = mStoredItems.get(itemKey);
-
- File inputFile = itemKey.getSourceFile();
-
- if (storedItem != null) {
- // check the sha1 is still valid, and the pre-dex files are still there.
- if (storedItem.areOutputFilesPresent() &&
- storedItem.getSourceHash().equals(getHash(inputFile))) {
-
- Logger.getAnonymousLogger().info("Cached result for getItem(" + inputFile + "): "
- + storedItem.getOutputFiles());
- for (File f : storedItem.getOutputFiles()) {
- Logger.getAnonymousLogger().info(
- String.format("%s l:%d ts:%d", f, f.length(), f.lastModified()));
- }
-
- // create an item where the outFile is the one stored since it
- // represent the pre-dexed library already.
- // Next time this lib needs to be pre-dexed, we'll use the item
- // rather than the stored item, allowing us to not compute the sha1 again.
- // Use a 0-count latch since there is nothing to do.
- item = new Item(inputFile, storedItem.getOutputFiles(), new CountDownLatch(0));
- }
- }
-
- // if we didn't find a valid stored item, create a new one.
- if (item == null) {
- item = new Item(inputFile, new CountDownLatch(1));
- newItem = true;
- }
-
- mMap.put(itemKey, item);
- }
-
- return Pair.of(item, newItem);
- }
-
- @Nullable
- private static HashCode getHash(@NonNull File file) {
- try {
- return Files.hash(file, Hashing.sha1());
- } catch (IOException ignored) {
- }
-
- return null;
- }
-
- public synchronized void clear(@Nullable File itemStorage, @Nullable ILogger logger) throws
- IOException {
- if (!mMap.isEmpty()) {
- if (itemStorage != null) {
- saveItems(itemStorage);
- }
-
- if (logger != null) {
- logger.info("PREDEX CACHE HITS: " + mHits);
- logger.info("PREDEX CACHE MISSES: " + mMisses);
- }
- }
-
- mMap.clear();
- mStoredItems.clear();
- mHits = 0;
- mMisses = 0;
- }
-
- private synchronized void loadItems(@NonNull File itemStorage) {
- if (!itemStorage.isFile()) {
- return;
- }
-
- try {
- Document document = XmlUtils.parseUtfXmlFile(itemStorage, true);
-
- // get the root node
- Node rootNode = document.getDocumentElement();
- if (rootNode == null || !NODE_ITEMS.equals(rootNode.getLocalName())) {
- return;
- }
-
-
- // check the version of the XML
- NamedNodeMap rootAttrMap = rootNode.getAttributes();
- Node versionAttr = rootAttrMap.getNamedItem(ATTR_VERSION);
- if (versionAttr == null || !XML_VERSION.equals(versionAttr.getNodeValue())) {
- return;
- }
-
- NodeList nodes = rootNode.getChildNodes();
-
- for (int i = 0, n = nodes.getLength(); i < n; i++) {
- Node node = nodes.item(i);
-
- if (node.getNodeType() != Node.ELEMENT_NODE ||
- !NODE_ITEM.equals(node.getLocalName())) {
- continue;
- }
-
- NamedNodeMap attrMap = node.getAttributes();
-
- File sourceFile = new File(attrMap.getNamedItem(ATTR_JAR).getNodeValue());
- FullRevision revision = FullRevision.parseRevision(attrMap.getNamedItem(
- ATTR_REVISION).getNodeValue());
-
- List<File> outputFiles = Lists.newArrayList();
- NodeList dexNodes = node.getChildNodes();
- for (int j = 0, m = dexNodes.getLength(); j < m; j++) {
- Node dexNode = dexNodes.item(j);
-
- if (dexNode.getNodeType() != Node.ELEMENT_NODE ||
- !NODE_DEX.equals(dexNode.getLocalName())) {
- continue;
- }
-
- NamedNodeMap dexAttrMap = dexNode.getAttributes();
- outputFiles.add(new File(dexAttrMap.getNamedItem(ATTR_DEX).getNodeValue()));
- }
-
- StoredItem item = new StoredItem(
- sourceFile,
- outputFiles,
- HashCode.fromString(attrMap.getNamedItem(ATTR_SHA1).getNodeValue()));
-
- T key = getKeyFactory().of(sourceFile, revision, attrMap);
-
- mStoredItems.put(key, item);
- }
- } catch (Exception ignored) {
- // if we fail to read parts or any of the file, all it'll do is fail to reuse an
- // already pre-dexed library, so that's not a super big deal.
- }
- }
-
- protected synchronized void saveItems(@NonNull File itemStorage) throws IOException {
- // write "compact" blob
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- factory.setIgnoringComments(true);
- DocumentBuilder builder;
-
- try {
- builder = factory.newDocumentBuilder();
- Document document = builder.newDocument();
-
- Node rootNode = document.createElement(NODE_ITEMS);
- document.appendChild(rootNode);
-
- // Set the version
- Attr attr = document.createAttribute(ATTR_VERSION);
- attr.setValue(XML_VERSION);
- rootNode.getAttributes().setNamedItem(attr);
-
- Set<T> keys = Sets.newHashSetWithExpectedSize(mMap.size() + mStoredItems.size());
- keys.addAll(mMap.keySet());
- keys.addAll(mStoredItems.keySet());
-
- for (T key : keys) {
- Item item = mMap.get(key);
-
- if (item != null) {
-
- Node itemNode = createItemNode(document,
- key,
- item);
- if (itemNode != null) {
- rootNode.appendChild(itemNode);
- }
-
- } else {
- StoredItem storedItem = mStoredItems.get(key);
- // check that the source file still exists in order to avoid
- // storing libraries that are gone.
- if (storedItem != null &&
- storedItem.getSourceFile().isFile() &&
- storedItem.areOutputFilesPresent()) {
- Node itemNode = createItemNode(document,
- key,
- storedItem);
- if (itemNode != null) {
- rootNode.appendChild(itemNode);
- }
- }
- }
- }
-
- String content = XmlPrettyPrinter.prettyPrint(document, true);
-
- itemStorage.getParentFile().mkdirs();
- Files.write(content, itemStorage, Charsets.UTF_8);
- } catch (ParserConfigurationException e) {
- }
- }
-
- @Nullable
- protected Node createItemNode(
- @NonNull Document document,
- @NonNull T itemKey,
- @NonNull BaseItem item) throws IOException {
- if (!item.areOutputFilesPresent()) {
- return null;
- }
-
- Node itemNode = document.createElement(NODE_ITEM);
-
- Attr attr = document.createAttribute(ATTR_JAR);
- attr.setValue(item.getSourceFile().getPath());
- itemNode.getAttributes().setNamedItem(attr);
-
- attr = document.createAttribute(ATTR_REVISION);
- attr.setValue(itemKey.getBuildToolsRevision().toString());
- itemNode.getAttributes().setNamedItem(attr);
-
- HashCode hashCode = item.getSourceHash();
- if (hashCode == null) {
- try {
- hashCode = Files.hash(item.getSourceFile(), Hashing.sha1());
- } catch (IOException ex) {
- // If we can't compute the hash for whatever reason, simply skip this entry.
- return null;
- }
- }
- attr = document.createAttribute(ATTR_SHA1);
- attr.setValue(hashCode.toString());
- itemNode.getAttributes().setNamedItem(attr);
-
- for (File dexFile : item.getOutputFiles()) {
-
- Node dexNode = document.createElement(NODE_DEX);
- itemNode.appendChild(dexNode);
-
- attr = document.createAttribute(ATTR_DEX);
- attr.setValue(dexFile.getPath());
- dexNode.getAttributes().setNamedItem(attr);
- }
-
- return itemNode;
- }
-
- protected synchronized void incrementMisses() {
- mMisses++;
- }
-
- protected synchronized void incrementHits() {
- mHits++;
- }
-
- @VisibleForTesting
- /*package*/ synchronized int getMisses() {
- return mMisses;
- }
-
- @VisibleForTesting
- /*package*/ synchronized int getHits() {
- return mHits;
- }
-
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java b/base/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java
deleted file mode 100644
index f3db39d..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * Copyright (C) 2010 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.builder.internal.packaging;
-
-import static com.android.SdkConstants.FN_APK_CLASSES_N_DEX;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.internal.packaging.JavaResourceProcessor.IArchiveBuilder;
-import com.android.builder.model.PackagingOptions;
-import com.android.builder.packaging.DuplicateFileException;
-import com.android.builder.packaging.PackagerException;
-import com.android.builder.packaging.SealedPackageException;
-import com.android.builder.signing.SignedJarBuilder;
-import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter;
-import com.android.ide.common.signing.CertificateInfo;
-import com.android.utils.FileUtils;
-import com.android.utils.ILogger;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
-import java.util.regex.Pattern;
-
-/**
- * Class making the final app package.
- * The inputs are:
- * - packaged resources (output of aapt)
- * - code file (ouput of dx)
- * - Java resources coming from the project, its libraries, and its jar files
- * - Native libraries from the project or its library.
- *
- */
-public final class Packager implements IArchiveBuilder {
-
- private static final Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$",
- Pattern.CASE_INSENSITIVE);
-
- /**
- * Filter to detect duplicate entries
- *
- */
- private final class DuplicateZipFilter implements IZipEntryFilter {
- private File mInputFile;
-
- void reset(File inputFile) {
- mInputFile = inputFile;
- }
-
- @Override
- public boolean checkEntry(String archivePath) throws ZipAbortException {
- mLogger.verbose("=> %s", archivePath);
-
- File duplicate = checkFileForDuplicate(archivePath);
- if (duplicate != null) {
- // we have a duplicate but it might be the same source file, in this case,
- // we just ignore the duplicate, and of course, we don't add it again.
- File potentialDuplicate = new File(mInputFile, archivePath);
- if (!duplicate.getAbsolutePath().equals(potentialDuplicate.getAbsolutePath())) {
- throw new DuplicateFileException(archivePath, duplicate, mInputFile);
- }
- return false;
- } else {
- mAddedFiles.put(archivePath, mInputFile);
- }
-
- return true;
- }
- }
-
- /**
- * A filter to filter out binary files like .class
- */
- private static final class NoBinaryZipFilter implements IZipEntryFilter {
- @NonNull
- private final IZipEntryFilter parentFilter;
-
- private NoBinaryZipFilter(@NonNull IZipEntryFilter parentFilter) {
- this.parentFilter = parentFilter;
- }
-
-
- @Override
- public boolean checkEntry(String archivePath) throws ZipAbortException {
- return parentFilter.checkEntry(archivePath) && !archivePath.endsWith(".class");
- }
- }
-
- private SignedJarBuilder mBuilder = null;
- private final ILogger mLogger;
- private boolean mJniDebugMode = false;
- private boolean mIsSealed = false;
-
- private final DuplicateZipFilter mNoDuplicateFilter = new DuplicateZipFilter();
- private final NoBinaryZipFilter mNoBinaryZipFilter = new NoBinaryZipFilter(mNoDuplicateFilter);
- @Nullable private final SignedJarBuilder.IZipEntryFilter mPackagingOptionsFilter;
- private final HashMap<String, File> mAddedFiles = new HashMap<String, File>();
- private final HashMap<String, File> mMergeFiles = new HashMap<String, File>();
-
- /**
- * Creates a new instance.
- *
- * This creates a new builder that will create the specified output file, using the two
- * mandatory given input files.
- *
- * An optional debug keystore can be provided. If set, it is expected that the store password
- * is 'android' and the key alias and password are 'androiddebugkey' and 'android'.
- *
- * An optional {@link ILogger} can also be provided for verbose output. If null, there will
- * be no output.
- *
- * @param apkLocation the file to create
- * @param resLocation the file representing the packaged resource file.
- * @param mergingFolder the folder to store files that are being merged.
- * @param certificateInfo the signing information used to sign the package. Optional the OS path to the debug keystore, if needed or null.
- * @param logger the logger.
- * @throws com.android.builder.packaging.PackagerException
- */
- public Packager(
- @NonNull String apkLocation,
- @NonNull String resLocation,
- @NonNull File mergingFolder,
- CertificateInfo certificateInfo,
- @Nullable String createdBy,
- @Nullable PackagingOptions packagingOptions,
- @Nullable SignedJarBuilder.IZipEntryFilter packagingOptionsFilter,
- ILogger logger) throws PackagerException {
-
- try {
- File apkFile = new File(apkLocation);
- checkOutputFile(apkFile);
-
- File resFile = new File(resLocation);
- checkInputFile(resFile);
-
- checkMergingFolder(mergingFolder);
- if (packagingOptions != null) {
- for (String merge : packagingOptions.getMerges()) {
- mMergeFiles.put(merge, new File(mergingFolder, merge));
- }
- }
-
- mPackagingOptionsFilter = packagingOptionsFilter;
- mLogger = logger;
-
- mBuilder = new SignedJarBuilder(
- new FileOutputStream(apkFile, false /* append */),
- certificateInfo != null ? certificateInfo.getKey() : null,
- certificateInfo != null ? certificateInfo.getCertificate() : null,
- getLocalVersion(),
- createdBy);
-
- mLogger.verbose("Packaging %s", apkFile.getName());
-
- // add the resources
- addZipFile(resFile);
-
- } catch (PackagerException e) {
- if (mBuilder != null) {
- mBuilder.cleanUp();
- }
- throw e;
- } catch (Exception e) {
- if (mBuilder != null) {
- mBuilder.cleanUp();
- }
- throw new PackagerException(e);
- }
- }
-
- public void addDexFiles(@NonNull File mainDexFolder, @NonNull Collection<File> extraDexFiles)
- throws DuplicateFileException, SealedPackageException, PackagerException {
-
- File[] mainDexFiles = mainDexFolder.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File file, String name) {
- return name.endsWith(SdkConstants.DOT_DEX);
- }
- });
-
- if (mainDexFiles != null && mainDexFiles.length > 0) {
- // Never rename the dex files in the main dex folder, in case we are in legacy mode
- // we requires the main dex files to not be renamed.
- for (File dexFile : mainDexFiles) {
- addFile(dexFile, dexFile.getName());
- }
-
- // prepare the index for the next files.
- int dexIndex = mainDexFiles.length + 1;
-
- for (File dexFile : extraDexFiles) {
- addFile(dexFile, String.format(FN_APK_CLASSES_N_DEX, dexIndex++));
- }
- }
- }
-
- /**
- * Sets the JNI debug mode. In debug mode, when native libraries are present, the packaging
- * will also include one or more copies of gdbserver in the final APK file.
- *
- * These are used for debugging native code, to ensure that gdbserver is accessible to the
- * application.
- *
- * There will be one version of gdbserver for each ABI supported by the application.
- *
- * the gbdserver files are placed in the libs/abi/ folders automatically by the NDK.
- *
- * @param jniDebugMode the jni-debug mode flag.
- */
- public void setJniDebugMode(boolean jniDebugMode) {
- mJniDebugMode = jniDebugMode;
- }
-
- /**
- * Adds a file to the APK at a given path
- * @param file the file to add
- * @param archivePath the path of the file inside the APK archive.
- * @throws PackagerException if an error occurred
- * @throws com.android.builder.packaging.SealedPackageException if the APK is already sealed.
- * @throws DuplicateFileException if a file conflicts with another already added to the APK
- * at the same location inside the APK archive.
- */
- @Override
- public void addFile(File file, String archivePath) throws PackagerException,
- SealedPackageException, DuplicateFileException {
- if (mIsSealed) {
- throw new SealedPackageException("APK is already sealed");
- }
-
- try {
- doAddFile(file, archivePath);
- } catch (DuplicateFileException e) {
- mBuilder.cleanUp();
- throw e;
- } catch (Exception e) {
- mBuilder.cleanUp();
- throw new PackagerException(e, "Failed to add %s", file);
- }
- }
-
- /**
- * Adds the content from a zip file.
- * All file keep the same path inside the archive.
- * @param zipFile the zip File.
- * @throws PackagerException if an error occurred
- * @throws SealedPackageException if the APK is already sealed.
- * @throws DuplicateFileException if a file conflicts with another already added to the APK
- * at the same location inside the APK archive.
- */
- void addZipFile(File zipFile) throws PackagerException, SealedPackageException,
- DuplicateFileException {
- if (mIsSealed) {
- throw new SealedPackageException("APK is already sealed");
- }
-
- FileInputStream fis = null;
- try {
- mLogger.verbose("%s:", zipFile);
-
- // reset the filter with this input.
- mNoDuplicateFilter.reset(zipFile);
-
- // ask the builder to add the content of the file.
- fis = new FileInputStream(zipFile);
- mBuilder.writeZip(fis, mNoDuplicateFilter, null /* ZipEntryExtractor */);
- } catch (DuplicateFileException e) {
- mBuilder.cleanUp();
- throw e;
- } catch (Exception e) {
- mBuilder.cleanUp();
- throw new PackagerException(e, "Failed to add %s", zipFile);
- } finally {
- try {
- Closeables.close(fis, true /* swallowIOException */);
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- /**
- * Adds all resources from a merged folder or jar file. There cannot be any duplicates and all
- * files present must be added unless it is a "binary" file like a .class or .dex (jack
- * produces the classes.dex in the same location as the obfuscated resources).
- * @param jarFileOrDirectory a jar file or directory reference.
- * @throws PackagerException could not add an entry to the package.
- * @throws DuplicateFileException if an entry with the same name was already present in the
- * package being built while adding the jarFileOrDirectory content.
- */
- public void addResources(@NonNull File jarFileOrDirectory)
- throws PackagerException, DuplicateFileException {
-
- mNoDuplicateFilter.reset(jarFileOrDirectory);
- InputStream fis = null;
- try {
- if (jarFileOrDirectory.isDirectory()) {
- addResourcesFromDirectory(jarFileOrDirectory, "");
- } else {
- fis = new BufferedInputStream(new FileInputStream(jarFileOrDirectory));
- mBuilder.writeZip(fis, mNoBinaryZipFilter, null /* ZipEntryExtractor */);
- }
- } catch (DuplicateFileException e) {
- mBuilder.cleanUp();
- throw e;
- } catch (Exception e) {
- mBuilder.cleanUp();
- throw new PackagerException(e, "Failed to add %s", jarFileOrDirectory);
- } finally {
- try {
- if (fis != null) {
- Closeables.close(fis, true /* swallowIOException */);
- }
- } catch (IOException e) {
- // ignore.
- }
- }
- }
-
- private void addResourcesFromDirectory(@NonNull File directory, String path)
- throws IOException, IZipEntryFilter.ZipAbortException {
- File[] directoryFiles = directory.listFiles();
- if (directoryFiles == null) {
- return;
- }
- for (File file : directoryFiles) {
- String entryName = path.isEmpty() ? file.getName() : path + "/" + file.getName();
- if (file.isDirectory()) {
- addResourcesFromDirectory(file, entryName);
- } else {
- doAddFile(file, entryName);
- }
- }
- }
-
- /**
- * Adds the native libraries from the top native folder.
- * The content of this folder must be the various ABI folders.
- *
- * This may or may not copy gdbserver into the apk based on whether the debug mode is set.
- *
- * @param nativeFolder the root folder containing the abi folders which contain the .so
- * @param abiFilters a list of abi filters to include. If null or empty, all abis are included.
- *
- * @throws PackagerException if an error occurred
- * @throws SealedPackageException if the APK is already sealed.
- * @throws DuplicateFileException if a file conflicts with another already added to the APK
- * at the same location inside the APK archive.
- *
- * @see #setJniDebugMode(boolean)
- */
- public void addNativeLibraries(@NonNull File nativeFolder, @Nullable Set<String> abiFilters)
- throws PackagerException, SealedPackageException, DuplicateFileException {
- if (mIsSealed) {
- throw new SealedPackageException("APK is already sealed");
- }
-
- if (!nativeFolder.isDirectory()) {
- // not a directory? check if it's a file or doesn't exist
- if (nativeFolder.exists()) {
- throw new PackagerException("%s is not a folder", nativeFolder);
- } else {
- throw new PackagerException("%s does not exist", nativeFolder);
- }
- }
-
- File[] abiList = nativeFolder.listFiles();
-
- mLogger.verbose("Native folder: %s", nativeFolder);
-
- if (abiList != null) {
- for (File abi : abiList) {
- if (abiFilters != null && !abiFilters.isEmpty() && !abiFilters.contains(abi.getName())) {
- continue;
- }
-
- if (abi.isDirectory()) { // ignore files
-
- File[] libs = abi.listFiles();
- if (libs != null) {
- for (File lib : libs) {
- // only consider files that are .so or, if in debug mode, that
- // are gdbserver executables
- String libName = lib.getName();
- if (lib.isFile() &&
- (PATTERN_NATIVELIB_EXT.matcher(lib.getName()).matches() ||
- (mJniDebugMode &&
- (SdkConstants.FN_GDBSERVER.equals(libName) ||
- SdkConstants.FN_GDB_SETUP.equals(libName))))) {
-
- String path =
- SdkConstants.FD_APK_NATIVE_LIBS + "/" +
- abi.getName() + "/" + libName;
-
- try {
-
- if (mPackagingOptionsFilter == null
- || mPackagingOptionsFilter.checkEntry(path)) {
- doAddFile(lib, path);
- }
- } catch (Exception e) {
- mBuilder.cleanUp();
- throw new PackagerException(e, "Failed to add %s", lib);
- }
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Seals the APK, and signs it if necessary.
- *
- * @throws PackagerException if an error occurred
- * @throws SealedPackageException if the APK is already sealed.
- */
- public void sealApk() throws PackagerException, SealedPackageException {
- if (mIsSealed) {
- throw new SealedPackageException("APK is already sealed");
- }
-
- // Add all the merged files that are pending to be packaged.
- for (Map.Entry<String, File>entry : mMergeFiles.entrySet()) {
- File inputFile = entry.getValue();
- String archivePath = entry.getKey();
- try {
- if (inputFile.exists()) {
- mBuilder.writeFile(inputFile, archivePath);
- }
- } catch (IOException e) {
- mBuilder.cleanUp();
- throw new PackagerException(e, "Failed to add merged file %s", inputFile);
- }
- }
-
- // close and sign the application package.
- try {
- mBuilder.close();
- mIsSealed = true;
- } catch (Exception e) {
- throw new PackagerException(e, "Failed to seal APK");
- } finally {
- mBuilder.cleanUp();
- }
- }
-
- private void doAddFile(File file, String archivePath) throws IZipEntryFilter.ZipAbortException,
- IOException {
-
- // If a file has to be merged, write it to a file in the merging folder and add it later.
- if (mMergeFiles.keySet().contains(archivePath)) {
- File mergingFile = mMergeFiles.get(archivePath);
- Files.createParentDirs(mergingFile);
- FileOutputStream fos = new FileOutputStream(mMergeFiles.get(archivePath), true);
- try {
- fos.write(Files.toByteArray(file));
- } finally {
- fos.close();
- }
- } else {
- if (!mNoBinaryZipFilter.checkEntry(archivePath)) {
- return;
- }
- mAddedFiles.put(archivePath, file);
- mBuilder.writeFile(file, archivePath);
- }
- }
-
- /**
- * Checks if the given path in the APK archive has not already been used and if it has been,
- * then returns a {@link File} object for the source of the duplicate
- * @param archivePath the archive path to test.
- * @return A File object of either a file at the same location or an archive that contains a
- * file that was put at the same location.
- */
- private File checkFileForDuplicate(String archivePath) {
- return mAddedFiles.get(archivePath);
- }
-
- /**
- * Checks an output {@link File} object.
- * This checks the following:
- * - the file is not an existing directory.
- * - if the file exists, that it can be modified.
- * - if it doesn't exists, that a new file can be created.
- * @param file the File to check
- * @throws PackagerException If the check fails
- */
- private static void checkOutputFile(File file) throws PackagerException {
- if (file.isDirectory()) {
- throw new PackagerException("%s is a directory!", file);
- }
-
- if (file.exists()) { // will be a file in this case.
- if (!file.canWrite()) {
- throw new PackagerException("Cannot write %s", file);
- }
- } else {
- try {
- if (!file.createNewFile()) {
- throw new PackagerException("Failed to create %s", file);
- }
- } catch (IOException e) {
- throw new PackagerException(
- "Failed to create '%1$ss': %2$s", file, e.getMessage());
- }
- }
- }
-
- /**
- * Checks the merger folder is:
- * - a directory.
- * - if the folder exists, that it can be modified.
- * - if it doesn't exists, that a new folder can be created.
- * @param file the File to check
- * @throws PackagerException If the check fails
- */
- private static void checkMergingFolder(File file) throws PackagerException {
- if (file.isFile()) {
- throw new PackagerException("%s is a file!", file);
- }
-
- if (file.exists()) { // will be a directory in this case.
- if (!file.canWrite()) {
- throw new PackagerException("Cannot write %s", file);
- }
- }
-
- try {
- FileUtils.emptyFolder(file);
- } catch (IOException e) {
- throw new PackagerException(e);
- }
- }
-
- /**
- * Checks an input {@link File} object.
- * This checks the following:
- * - the file is not an existing directory.
- * - that the file exists (if <var>throwIfDoesntExist</var> is <code>false</code>) and can
- * be read.
- * @param file the File to check
- * @throws FileNotFoundException if the file is not here.
- * @throws PackagerException If the file is a folder or a file that cannot be read.
- */
- private static void checkInputFile(File file) throws FileNotFoundException, PackagerException {
- if (file.isDirectory()) {
- throw new PackagerException("%s is a directory!", file);
- }
-
- if (file.exists()) {
- if (!file.canRead()) {
- throw new PackagerException("Cannot read %s", file);
- }
- } else {
- throw new FileNotFoundException(String.format("%s does not exist", file));
- }
- }
-
- public static String getLocalVersion() {
- Class clazz = Packager.class;
- String className = clazz.getSimpleName() + ".class";
- String classPath = clazz.getResource(className).toString();
- if (!classPath.startsWith("jar")) {
- // Class not from JAR, unlikely
- return null;
- }
- try {
- String manifestPath = classPath.substring(0, classPath.lastIndexOf('!') + 1) +
- "/META-INF/MANIFEST.MF";
-
- URLConnection jarConnection = new URL(manifestPath).openConnection();
- jarConnection.setUseCaches(false);
- InputStream jarInputStream = jarConnection.getInputStream();
- Attributes attr = new Manifest(jarInputStream).getMainAttributes();
- jarInputStream.close();
- return attr.getValue("Builder-Version");
- } catch (MalformedURLException ignored) {
- } catch (IOException ignored) {
- }
-
- return null;
- }
-
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java b/base/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java
deleted file mode 100644
index 46cbc13..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal.testing;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.testing.TestData;
-import com.android.builder.testing.api.DeviceConnector;
-import com.android.builder.testing.api.DeviceException;
-import com.android.ddmlib.InstallException;
-import com.android.ddmlib.MultiLineReceiver;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestRunResult;
-import com.android.utils.ILogger;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Basic Callable to run tests on a given {@link DeviceConnector} using
- * {@link RemoteAndroidTestRunner}.
- *
- * The boolean return value is true if success.
- */
-public class SimpleTestCallable implements Callable<Boolean> {
-
- public static final String FILE_COVERAGE_EC = "coverage.ec";
-
- @NonNull
- private final String projectName;
- @NonNull
- private final DeviceConnector device;
- @NonNull
- private final String flavorName;
- @NonNull
- private final TestData testData;
- @NonNull
- private final File resultsDir;
- @NonNull
- private final File coverageDir;
- @NonNull
- private final File testApk;
- @NonNull
- private final List<File> testedApks;
- @NonNull
- private final ILogger logger;
-
- private final int timeoutInMs;
-
- public SimpleTestCallable(
- @NonNull DeviceConnector device,
- @NonNull String projectName,
- @NonNull String flavorName,
- @NonNull File testApk,
- @NonNull List<File> testedApks,
- @NonNull TestData testData,
- @NonNull File resultsDir,
- @NonNull File coverageDir,
- int timeoutInMs,
- @NonNull ILogger logger) {
- this.projectName = projectName;
- this.device = device;
- this.flavorName = flavorName;
- this.resultsDir = resultsDir;
- this.coverageDir = coverageDir;
- this.testApk = testApk;
- this.testedApks = testedApks;
- this.testData = testData;
- this.timeoutInMs = timeoutInMs;
- this.logger = logger;
- }
-
- @Override
- public Boolean call() throws Exception {
- String deviceName = device.getName();
- boolean isInstalled = false;
-
- CustomTestRunListener runListener = new CustomTestRunListener(
- deviceName, projectName, flavorName, logger);
- runListener.setReportDir(resultsDir);
-
- long time = System.currentTimeMillis();
- boolean success = false;
-
- String coverageFile = "/data/data/" + testData.getTestedApplicationId() + "/" + FILE_COVERAGE_EC;
-
- try {
- device.connect(timeoutInMs, logger);
-
- if (!testedApks.isEmpty()) {
- logger.verbose("DeviceConnector '%s': installing %s", deviceName,
- Joiner.on(',').join(testedApks));
- if (testedApks.size() > 1 && device.getApiLevel() < 21) {
- throw new InstallException("Internal error, file a bug, multi-apk applications"
- + " require a device with API level 21+");
- }
- if (device.getApiLevel() >= 21) {
- device.installPackages(testedApks,
- ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger);
- } else {
- device.installPackage(testedApks.get(0),
- ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger);
- }
- }
-
- logger.verbose("DeviceConnector '%s': installing %s", deviceName, testApk);
- if (device.getApiLevel() >= 21) {
- device.installPackages(ImmutableList.of(testApk),
- ImmutableList.<String>of() /* installOptions */,timeoutInMs, logger);
- } else {
- device.installPackage(testApk,
- ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger);
- }
- isInstalled = true;
-
- RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(
- testData.getApplicationId(),
- testData.getInstrumentationRunner(),
- device);
-
- for (Map.Entry<String, String> argument:
- testData.getInstrumentationRunnerArguments().entrySet()) {
- runner.addInstrumentationArg(argument.getKey(), argument.getValue());
- }
-
- if (testData.isTestCoverageEnabled()) {
- runner.addInstrumentationArg("coverage", "true");
- runner.addInstrumentationArg("coverageFile", coverageFile);
- }
-
- runner.setRunName(deviceName);
- runner.setMaxtimeToOutputResponse(timeoutInMs);
-
- runner.run(runListener);
-
- TestRunResult testRunResult = runListener.getRunResult();
-
- success = true;
-
- // for now throw an exception if no tests.
- // TODO return a status instead of allow merging of multi-variants/multi-device reports.
- if (testRunResult.getNumTests() == 0) {
- CustomTestRunListener fakeRunListener = new CustomTestRunListener(
- deviceName, projectName, flavorName, logger);
- fakeRunListener.setReportDir(resultsDir);
-
- // create a fake test output
- Map<String, String> emptyMetrics = Collections.emptyMap();
- TestIdentifier fakeTest = new TestIdentifier(device.getClass().getName(), "No tests found.");
- fakeRunListener.testStarted(fakeTest);
- fakeRunListener.testFailed(
- fakeTest,
- "No tests found. This usually means that your test classes are"
- + " not in the form that your test runner expects (e.g. don't inherit from"
- + " TestCase or lack @Test annotations).");
- fakeRunListener.testEnded(fakeTest, emptyMetrics);
-
- // end the run to generate the XML file.
- fakeRunListener.testRunEnded(System.currentTimeMillis() - time, emptyMetrics);
- return false;
- }
-
- return !testRunResult.hasFailedTests();
- } catch (Exception e) {
- Map<String, String> emptyMetrics = Collections.emptyMap();
-
- // create a fake test output
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- PrintWriter pw = new PrintWriter(baos, true);
- e.printStackTrace(pw);
- TestIdentifier fakeTest = new TestIdentifier(device.getClass().getName(), "runTests");
- runListener.testStarted(fakeTest);
- runListener.testFailed(fakeTest , baos.toString());
- runListener.testEnded(fakeTest, emptyMetrics);
-
- // end the run to generate the XML file.
- runListener.testRunEnded(System.currentTimeMillis() - time, emptyMetrics);
-
- // and throw
- throw e;
- } finally {
- if (isInstalled) {
- // Get the coverage if needed.
- if (success && testData.isTestCoverageEnabled()) {
- String temporaryCoverageCopy = "/data/local/tmp/"
- + testData.getTestedApplicationId() + "." + FILE_COVERAGE_EC;
-
- MultiLineReceiver outputReceiver = new MultiLineReceiver() {
- @Override
- public void processNewLines(String[] lines) {
- for (String line : lines) {
- logger.info(line);
- }
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
- };
-
- logger.verbose("DeviceConnector '%s': fetching coverage data from %s",
- deviceName, coverageFile);
- device.executeShellCommand("run-as " + testData.getTestedApplicationId()
- + " cat " + coverageFile + " | cat > " + temporaryCoverageCopy,
- outputReceiver,
- 30, TimeUnit.SECONDS);
- device.pullFile(
- temporaryCoverageCopy,
- new File(coverageDir, FILE_COVERAGE_EC).getPath());
- device.executeShellCommand("rm " + temporaryCoverageCopy,
- outputReceiver,
- 30, TimeUnit.SECONDS);
- }
-
- // uninstall the apps
- // This should really not be null, because if it was the build
- // would have broken before.
- uninstall(testApk, testData.getApplicationId(), deviceName);
-
- if (!testedApks.isEmpty()) {
- for (File testedApk : testedApks) {
- uninstall(testedApk, testData.getTestedApplicationId(), deviceName);
- }
- }
- }
-
- device.disconnect(timeoutInMs, logger);
- }
- }
-
- private void uninstall(@NonNull File apkFile, @Nullable String packageName,
- @NonNull String deviceName)
- throws DeviceException {
- if (packageName != null) {
- logger.verbose("DeviceConnector '%s': uninstalling %s", deviceName, packageName);
- device.uninstallPackage(packageName, timeoutInMs, logger);
- } else {
- logger.verbose("DeviceConnector '%s': unable to uninstall %s: unable to get package name",
- deviceName, apkFile);
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java b/base/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
deleted file mode 100644
index bb25521..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2010 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.builder.packaging;
-
-import com.android.annotations.NonNull;
-import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
-
-import java.io.File;
-
-/**
- * An exception thrown during packaging of an APK file.
- */
-public final class DuplicateFileException extends ZipAbortException {
- private static final long serialVersionUID = 1L;
- private final String mArchivePath;
- private final File mFile1;
- private final File mFile2;
-
- public DuplicateFileException(@NonNull String archivePath, @NonNull File file1,
- @NonNull File file2) {
- super();
- mArchivePath = archivePath;
- mFile1 = file1;
- mFile2 = file2;
- }
-
- public String getArchivePath() {
- return mArchivePath;
- }
-
- public File getFile1() {
- return mFile1;
- }
-
- public File getFile2() {
- return mFile2;
- }
-
- @Override
- public String getMessage() {
- StringBuilder sb = new StringBuilder();
-
- sb.append("Duplicate files copied in APK ").append(mArchivePath).append('\n');
- sb.append("\tFile 1: ").append(mFile1).append('\n');
- sb.append("\tFile 2: ").append(mFile2).append('\n');
-
- return sb.toString();
- }
-}
\ No newline at end of file
diff --git a/base/build-system/builder/src/main/java/com/android/builder/png/AaptProcess.java b/base/build-system/builder/src/main/java/com/android/builder/png/AaptProcess.java
deleted file mode 100644
index e6c1b4d..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/png/AaptProcess.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.png;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.tasks.BooleanLatch;
-import com.android.builder.tasks.Job;
-import com.android.utils.GrabProcessOutput;
-import com.android.utils.ILogger;
-import com.google.common.base.Objects;
-import com.google.common.base.Strings;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * interface to the aapt long running process.
- */
-public class AaptProcess {
-
- private static final int DEFAULT_SLAVE_APPT_TIMEOUT_IN_SECONDS = 5;
- private static final int SLAVE_AAPT_TIMEOUT_IN_SECONDS =
- System.getenv("SLAVE_AAPT_TIMEOUT") == null
- ? DEFAULT_SLAVE_APPT_TIMEOUT_IN_SECONDS
- : Integer.parseInt(System.getenv("SLAVE_AAPT_TIMEOUT"));
-
- private final Process mProcess;
- private final ILogger mLogger;
-
- private final ProcessOutputFacade mProcessOutputFacade = new ProcessOutputFacade();
- private final List<String> mMessages = new ArrayList<String>();
- private final AtomicBoolean mReady = new AtomicBoolean(false);
- private final BooleanLatch mReadyLatch = new BooleanLatch();
- private final OutputStreamWriter mWriter;
-
- private AaptProcess(@NonNull Process process, @NonNull ILogger iLogger)
- throws InterruptedException {
- mProcess = process;
- mLogger = iLogger;
- GrabProcessOutput.grabProcessOutput(process, GrabProcessOutput.Wait.ASYNC,
- mProcessOutputFacade);
- mWriter = new OutputStreamWriter(mProcess.getOutputStream());
- }
-
- /**
- * Notifies the slave process of a new crunching request, do not block on completion, the
- * notification will be issued through the job parameter's
- * {@link com.android.builder.tasks.Job#finished()} or
- * {@link com.android.builder.tasks.Job#error()}
- * functions.
- *
- * @param in the source file to crunch
- * @param out where to place the crunched file
- * @param job the job to notify when the crunching is finished successfully or not.
- * @throws IOException
- */
- public void crunch(@NonNull File in, @NonNull File out, @NonNull Job<AaptProcess> job)
- throws IOException {
-
- mLogger.verbose("Process(" + mProcess.hashCode() + ")" + in.getName() +
- "job: " + job.toString());
- if (!mReady.get()) {
- throw new RuntimeException("AAPT process not ready to receive commands");
- }
- NotifierProcessOutput notifier =
- new NotifierProcessOutput(job, mProcessOutputFacade, mLogger);
-
- mProcessOutputFacade.setNotifier(notifier);
- mWriter.write("s\n");
- mWriter.write(in.getAbsolutePath());
- mWriter.write("\n");
- mWriter.write(out.getAbsolutePath());
- mWriter.write("\n");
- mWriter.flush();
- mLogger.verbose("Processed(" + mProcess.hashCode() + ")" + in.getName() +
- "job: " + job.toString());
- mMessages.add("Process(" + mProcess.hashCode() + ") processed " + in.getName() +
- "job: " + job.toString());
- }
-
- public void waitForReady() throws InterruptedException {
- if (!mReadyLatch.await(TimeUnit.NANOSECONDS.convert(
- SLAVE_AAPT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS))) {
- throw new RuntimeException("Timed out while waiting for slave aapt process, "
- + "try setting environment variable SLAVE_AAPT_TIMEOUT to a value bigger than "
- + SLAVE_AAPT_TIMEOUT_IN_SECONDS + " seconds");
- }
-
- mLogger.info("Slave %1$s is ready", hashCode());
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this)
- .add("ready", mReady.get())
- .add("process", mProcess.hashCode())
- .toString();
- }
-
- /**
- * Shutdowns the slave process and release all resources.
- *
- * @throws IOException
- * @throws InterruptedException
- */
- public void shutdown() throws IOException, InterruptedException {
-
- mReady.set(false);
- mWriter.write("quit\n");
- mWriter.flush();
- mProcess.waitFor();
- mLogger.verbose("Process (%1$s) processed %2$s files", mProcess.hashCode(),
- mMessages.size());
- for (String message : mMessages) {
- mLogger.verbose(message);
- }
- }
-
- public static class Builder {
- private final String mAaptLocation;
- private final ILogger mLogger;
- public Builder(@NonNull String aaptPath, @NonNull ILogger iLogger) {
- mAaptLocation = aaptPath;
- mLogger = iLogger;
- }
-
- public AaptProcess start() throws IOException, InterruptedException {
- String[] command = new String[] {
- mAaptLocation,
- "m",
- };
-
- mLogger.verbose("Trying to start %1$s", command[0]);
- Process process = new ProcessBuilder(command).start();
- mLogger.verbose("Started %1$d", process.hashCode());
- return new AaptProcess(process, mLogger);
- }
- }
-
- private class ProcessOutputFacade implements GrabProcessOutput.IProcessOutput {
- @Nullable NotifierProcessOutput notifier = null;
- AtomicBoolean ready = new AtomicBoolean(false);
-
- synchronized void setNotifier(@NonNull NotifierProcessOutput notifierProcessOutput) {
- if (notifier != null) {
- throw new RuntimeException("Notifier already set, threading issue");
- }
- notifier = notifierProcessOutput;
- }
-
- synchronized void reset() {
- notifier = null;
- }
-
- @Nullable
- synchronized NotifierProcessOutput getNotifier() {
- return notifier;
- }
-
- @Override
- public synchronized void out(@Nullable String line) {
-
- // an empty message or aapt startup message are ignored.
- if (Strings.isNullOrEmpty(line)) {
- return;
- }
- if (line.equals("Ready")) {
- AaptProcess.this.mReady.set(true);
- AaptProcess.this.mReadyLatch.signal();
- return;
- }
- NotifierProcessOutput delegate = getNotifier();
- mLogger.verbose("AAPT out(%1$s): %2$s", mProcess.hashCode(), line);
- if (delegate != null) {
- mLogger.verbose("AAPT out(%1$s): -> %2$s", mProcess.hashCode(), delegate.mJob);
- delegate.out(line);
- } else {
- mLogger.error(null, "AAPT out(%1$s) : No Delegate set : lost message:%2$s",
- mProcess.hashCode(), line);
- }
- }
-
- @Override
- public synchronized void err(@Nullable String line) {
-
- if (Strings.isNullOrEmpty(line)) {
- return;
- }
- NotifierProcessOutput delegate = getNotifier();
- if (delegate != null) {
- mLogger.verbose("AAPT err(%1$s): %2$s -> %3$s", mProcess.hashCode(), line,
- delegate.mJob);
- delegate.err(line);
- } else {
- if (!mReady.get()) {
- if (line.equals("ERROR: Unknown command 'm'")) {
- throw new RuntimeException("Invalid aapt version, version 21 or above is required");
- }
- mLogger.error(null, "AAPT err(%1$s): %2$s", mProcess.hashCode(), line);
- } else {
- mLogger.error(null, "AAPT err(%1$s) : No Delegate set : lost message:%2$s",
- mProcess.hashCode(), line);
- }
- }
- }
-
- Process getProcess() {
- return mProcess;
- }
- }
-
- private static class NotifierProcessOutput implements GrabProcessOutput.IProcessOutput {
-
- @NonNull private final Job<AaptProcess> mJob;
- @NonNull private final ProcessOutputFacade mOwner;
- @NonNull private final ILogger mLogger;
-
- NotifierProcessOutput(
- @NonNull Job<AaptProcess> job,
- @NonNull ProcessOutputFacade owner,
- @NonNull ILogger iLogger) {
- mOwner = owner;
- mJob = job;
- mLogger = iLogger;
- }
-
- @Override
- public void out(@Nullable String line) {
- if (line != null) {
- mLogger.verbose("AAPT notify(%1$s): %2$s", mJob, line);
- if (line.equalsIgnoreCase("Done")) {
- mOwner.reset();
- mJob.finished();
- } else if (line.equalsIgnoreCase("Error")) {
- mOwner.reset();
- mJob.error();
- } else {
- mLogger.verbose("AAPT(%1$s) discarded: %2$s", mJob, line);
- }
- }
- }
-
- @Override
- public void err(@Nullable String line) {
- if (line != null) {
- mLogger.verbose("AAPT warning(%1$s), Job(%2$s): %3$s",
- mOwner.getProcess().hashCode(), mJob, line);
- mLogger.warning("AAPT: %3$s",
- mOwner.getProcess().hashCode(), mJob, line);
-
- }
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/png/QueuedCruncher.java b/base/build-system/builder/src/main/java/com/android/builder/png/QueuedCruncher.java
deleted file mode 100644
index ee852ce..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/png/QueuedCruncher.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.png;
-
-import com.android.annotations.NonNull;
-import com.android.builder.tasks.Job;
-import com.android.builder.tasks.JobContext;
-import com.android.builder.tasks.QueueThreadContext;
-import com.android.builder.tasks.Task;
-import com.android.builder.tasks.WorkQueue;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.ide.common.internal.PngException;
-import com.android.utils.ILogger;
-import com.google.common.base.Objects;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * implementation of {@link com.android.ide.common.internal.PngCruncher} that queues request and
- * use a pool or aapt server processes to serve those.
- */
-public class QueuedCruncher implements PngCruncher {
-
- // use an enum to ensure singleton.
- public enum Builder {
- INSTANCE;
-
- private final Map<String, QueuedCruncher> sInstances =
- new ConcurrentHashMap<String, QueuedCruncher>();
- private final Object sLock = new Object();
-
- /**
- * Creates a new {@link com.android.builder.png.QueuedCruncher} or return an existing one
- * based on the underlying AAPT executable location.
- * @param aaptLocation the APPT executable location.
- * @param logger the logger to use
- * @return a new of existing instance of the {@link com.android.builder.png.QueuedCruncher}
- */
- public QueuedCruncher newCruncher(
- @NonNull String aaptLocation,
- @NonNull ILogger logger) {
- synchronized (sLock) {
- logger.info("QueuedCruncher is using %1$s", aaptLocation);
- if (!sInstances.containsKey(aaptLocation)) {
- QueuedCruncher queuedCruncher = new QueuedCruncher(aaptLocation, logger);
- sInstances.put(aaptLocation, queuedCruncher);
- }
- return sInstances.get(aaptLocation);
- }
- }
- }
-
-
- @NonNull private final String mAaptLocation;
- @NonNull private final ILogger mLogger;
- // Queue responsible for handling all passed jobs with a pool of worker threads.
- @NonNull private final WorkQueue<AaptProcess> mCrunchingRequests;
- // list of outstanding jobs.
- @NonNull private final Map<Integer, ConcurrentLinkedQueue<Job<AaptProcess>>> mOutstandingJobs =
- new ConcurrentHashMap<Integer, ConcurrentLinkedQueue<Job<AaptProcess>>>();
- // ref count of active users, if it drops to zero, that means there are no more active users
- // and the queue should be shutdown.
- @NonNull private final AtomicInteger refCount = new AtomicInteger(0);
-
- // per process unique key provider to remember which users enlisted which requests.
- @NonNull private final AtomicInteger keyProvider = new AtomicInteger(0);
-
-
- private QueuedCruncher(
- @NonNull String aaptLocation,
- @NonNull ILogger iLogger) {
- mAaptLocation = aaptLocation;
- mLogger = iLogger;
- QueueThreadContext<AaptProcess> queueThreadContext = new QueueThreadContext<AaptProcess>() {
-
- // move this to a TLS, but do not store instances of AaptProcess in it.
- @NonNull private final Map<String, AaptProcess> mAaptProcesses =
- new ConcurrentHashMap<String, AaptProcess>();
-
- @Override
- public void creation(@NonNull Thread t) throws IOException {
- try {
- mLogger.verbose("Thread(%1$s): create aapt slave",
- Thread.currentThread().getName());
- AaptProcess aaptProcess = new AaptProcess.Builder(mAaptLocation, mLogger).start();
- assert aaptProcess != null;
- aaptProcess.waitForReady();
- mAaptProcesses.put(t.getName(), aaptProcess);
- } catch (InterruptedException e) {
- mLogger.error(e, "Cannot start slave process");
- e.printStackTrace();
- }
- }
-
- @Override
- public void runTask(@NonNull Job<AaptProcess> job) throws Exception {
- job.runTask(
- new JobContext<AaptProcess>(
- mAaptProcesses.get(Thread.currentThread().getName())));
- mOutstandingJobs.get(((QueuedJob) job).key).remove(job);
- }
-
- @Override
- public void destruction(@NonNull Thread t) throws IOException, InterruptedException {
-
- AaptProcess aaptProcess = mAaptProcesses.get(Thread.currentThread().getName());
- if (aaptProcess != null) {
- mLogger.verbose("Thread(%1$s): notify aapt slave shutdown",
- Thread.currentThread().getName());
- aaptProcess.shutdown();
- mAaptProcesses.remove(t.getName());
- mLogger.verbose("Thread(%1$s): after shutdown queue_size=%2$d",
- Thread.currentThread().getName(), mAaptProcesses.size());
- }
- }
-
- @Override
- public void shutdown() {
- if (!mAaptProcesses.isEmpty()) {
- mLogger.warning("Process list not empty");
- for (Map.Entry<String, AaptProcess> aaptProcessEntry : mAaptProcesses
- .entrySet()) {
- mLogger.warning("Thread(%1$s): queue not cleaned", aaptProcessEntry.getKey());
- try {
- aaptProcessEntry.getValue().shutdown();
- } catch (Exception e) {
- mLogger.error(e, "while shutting down" + aaptProcessEntry.getKey());
- }
- }
- }
- mAaptProcesses.clear();
- }
- };
- mCrunchingRequests = new WorkQueue<AaptProcess>(
- mLogger, queueThreadContext, "png-cruncher", 5, 2f);
- }
-
- private static final class QueuedJob extends Job<AaptProcess> {
-
- private final int key;
- public QueuedJob(int key, String jobTile, Task<AaptProcess> task) {
- super(jobTile, task);
- this.key = key;
- }
- }
-
- @Override
- public void crunchPng(int key, @NonNull final File from, @NonNull final File to)
- throws PngException {
-
- try {
- final Job<AaptProcess> aaptProcessJob = new QueuedJob(
- key,
- "Cruncher " + from.getName(),
- new Task<AaptProcess>() {
- @Override
- public void run(@NonNull Job<AaptProcess> job,
- @NonNull JobContext<AaptProcess> context) throws IOException {
- mLogger.verbose("Thread(%1$s): begin executing job %2$s",
- Thread.currentThread().getName(), job.getJobTitle());
- context.getPayload().crunch(from, to, job);
- mLogger.verbose("Thread(%1$s): done executing job %2$s",
- Thread.currentThread().getName(), job.getJobTitle());
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this)
- .add("from", from.getAbsolutePath())
- .add("to", to.getAbsolutePath())
- .toString();
- }
- });
- mOutstandingJobs.get(key).add(aaptProcessJob);
- mCrunchingRequests.push(aaptProcessJob);
- } catch (InterruptedException e) {
- // Restore the interrupted status
- Thread.currentThread().interrupt();
- throw new PngException(e);
- }
- }
-
- private void waitForAll(int key) throws InterruptedException {
- mLogger.verbose("Thread(%1$s): begin waitForAll", Thread.currentThread().getName());
- ConcurrentLinkedQueue<Job<AaptProcess>> jobs = mOutstandingJobs.get(key);
- Job<AaptProcess> aaptProcessJob = jobs.poll();
- while (aaptProcessJob != null) {
- mLogger.verbose("Thread(%1$s) : wait for {%2$s)", Thread.currentThread().getName(),
- aaptProcessJob.toString());
- if (!aaptProcessJob.await()) {
- throw new RuntimeException(
- "Crunching " + aaptProcessJob.getJobTitle() + " failed, see logs");
- }
- aaptProcessJob = jobs.poll();
- }
- mLogger.verbose("Thread(%1$s): end waitForAll", Thread.currentThread().getName());
- }
-
- @Override
- public synchronized int start() {
- // increment our reference count.
- refCount.incrementAndGet();
- // get a unique key for the lifetime of this process.
- int key = keyProvider.incrementAndGet();
- mOutstandingJobs.put(key, new ConcurrentLinkedQueue<Job<AaptProcess>>());
- return key;
- }
-
- @Override
- public synchronized void end(int key) throws InterruptedException {
- long startTime = System.currentTimeMillis();
- try {
- waitForAll(key);
- mOutstandingJobs.get(key).clear();
- mLogger.verbose("Job finished in %1$d", System.currentTimeMillis() - startTime);
- } finally {
- // even if we have failures, we need to shutdown property the sub processes.
- if (refCount.decrementAndGet() == 0) {
- mCrunchingRequests.shutdown();
- mLogger.verbose("Shutdown finished in %1$d",
- System.currentTimeMillis() - startTime);
- }
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/png/VectorDrawableRenderer.java b/base/build-system/builder/src/main/java/com/android/builder/png/VectorDrawableRenderer.java
deleted file mode 100644
index 83db810..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/png/VectorDrawableRenderer.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.png;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.res2.ResourcePreprocessor;
-import com.android.ide.common.resources.configuration.DensityQualifier;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.VersionQualifier;
-import com.android.ide.common.vectordrawable.VdPreview;
-import com.android.resources.Density;
-import com.android.resources.ResourceFolderType;
-import com.android.utils.ILogger;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-
-import javax.imageio.ImageIO;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-/**
- * Generates PNG images (and XML copies) from VectorDrawable files.
- */
- at SuppressWarnings("MethodMayBeStatic")
-public class VectorDrawableRenderer implements ResourcePreprocessor {
- /** Projects with minSdk set to this or higher don't need to generate PNGs. */
- public static final int MIN_SDK_WITH_VECTOR_SUPPORT = 21;
-
- private final ILogger mLogger;
- private final File mOutputDir;
- private final Collection<Density> mDensities;
-
- public VectorDrawableRenderer(File outputDir, Collection<Density> densities, ILogger logger) {
- mOutputDir = outputDir;
- mDensities = densities;
- mLogger = logger;
- }
-
- public Collection<File> createPngFiles(
- @NonNull File inputXmlFile,
- @NonNull File outputResDirectory,
- @NonNull Collection<Density> densities) throws IOException {
- throw new RuntimeException("Replaced by the new way to generate PNGs");
- }
-
- @Override
- public boolean needsPreprocessing(File resourceFile) {
- return isXml(resourceFile)
- && isInDrawable(resourceFile)
- && getEffectiveVersion(resourceFile) < MIN_SDK_WITH_VECTOR_SUPPORT
- && isRootVector(resourceFile);
- }
-
- @Override
- public Collection<File> getFilesToBeGenerated(File inputXmlFile) {
- FolderConfiguration originalConfiguration = getFolderConfiguration(inputXmlFile);
-
- // Create all the PNG files and duplicate the XML into folders with the version qualifier.
- Collection<File> filesToBeGenerated = Lists.newArrayList();
- for (Density density : mDensities) {
- FolderConfiguration newConfiguration = FolderConfiguration.copyOf(originalConfiguration);
- newConfiguration.setDensityQualifier(new DensityQualifier(density));
-
- File directory = new File(
- mOutputDir,
- newConfiguration.getFolderName(ResourceFolderType.DRAWABLE));
- File pngFile = new File(
- directory,
- inputXmlFile.getName().replace(".xml", ".png"));
-
- filesToBeGenerated.add(pngFile);
-
- newConfiguration.setVersionQualifier(new VersionQualifier(MIN_SDK_WITH_VECTOR_SUPPORT));
- File destination = new File(
- mOutputDir,
- newConfiguration.getFolderName(ResourceFolderType.DRAWABLE));
- File xmlCopy = new File(destination, inputXmlFile.getName());
- filesToBeGenerated.add(xmlCopy);
- }
-
- return filesToBeGenerated;
- }
-
- @Override
- public void generateFile(File toBeGenerated, File original) throws IOException {
- Files.createParentDirs(toBeGenerated);
-
- if (isXml(toBeGenerated)) {
- Files.copy(original, toBeGenerated);
- } else {
- mLogger.info(
- "Generating PNG: [%s] from [%s]",
- toBeGenerated.getAbsolutePath(),
- original.getAbsolutePath());
-
- FolderConfiguration folderConfiguration = getFolderConfiguration(toBeGenerated);
- checkState(folderConfiguration.getDensityQualifier() != null);
- Density density = folderConfiguration.getDensityQualifier().getValue();
-
- String xmlContent = Files.toString(original, Charsets.UTF_8);
- float scaleFactor = density.getDpiValue() / (float) Density.MEDIUM.getDpiValue();
- if (scaleFactor <= 0) {
- scaleFactor = 1.0f;
- }
-
- final VdPreview.TargetSize imageSize = VdPreview.TargetSize.createSizeFromScale(scaleFactor);
- BufferedImage image = VdPreview.getPreviewFromVectorXml(imageSize, xmlContent, null);
- checkState(image != null, "Generating the image failed.");
- ImageIO.write(image, "png", toBeGenerated);
- }
- }
-
- @NonNull
- private FolderConfiguration getFolderConfiguration(@NonNull File inputXmlFile) {
- String parentName = inputXmlFile.getParentFile().getName();
- FolderConfiguration originalConfiguration =
- FolderConfiguration.getConfigForFolder(parentName);
- checkArgument(
- originalConfiguration != null,
- "Invalid resource folder name [%s].",
- parentName);
- return originalConfiguration;
- }
-
- private boolean isInDrawable(@NonNull File inputXmlFile) {
- ResourceFolderType folderType =
- ResourceFolderType.getFolderType(inputXmlFile.getParentFile().getName());
-
- return folderType == ResourceFolderType.DRAWABLE;
- }
-
- /**
- * Parse the root element of the file, return true if it is a vector.
- * TODO: Combine this with the drawing code, such that we don't parse the file twice.
- */
- private boolean isRootVector(File resourceFile) {
- DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
- boolean result = false;
- try {
- DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
- Document doc = dBuilder.parse(resourceFile);
- Element root = doc.getDocumentElement();
- if (root != null && root.getNodeName().equalsIgnoreCase("vector")) {
- result = true;
- }
- } catch (Exception e) {
- mLogger.error(e, "Exception in parsing the XML resource" + e.getMessage());
- }
-
- return result;
- }
-
- private boolean isXml(File resourceFile) {
- return resourceFile.getPath().endsWith(".xml");
- }
-
- private int getEffectiveVersion(File resourceFile) {
- FolderConfiguration configuration = getFolderConfiguration(resourceFile);
- if (configuration.getVersionQualifier() == null) {
- configuration.createDefault();
- }
- //noinspection ConstantConditions - handled above.
- return configuration.getVersionQualifier().getVersion();
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java b/base/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java
deleted file mode 100644
index 5a2fe7f..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.sdk;
-
-import static com.android.SdkConstants.FD_EXTRAS;
-import static com.android.SdkConstants.FD_M2_REPOSITORY;
-import static com.android.SdkConstants.FD_PLATFORM_TOOLS;
-import static com.android.SdkConstants.FD_SUPPORT;
-import static com.android.SdkConstants.FD_TOOLS;
-import static com.android.SdkConstants.FN_ADB;
-import static com.android.SdkConstants.FN_ANNOTATIONS_JAR;
-import static com.android.SdkConstants.FN_SOURCE_PROP;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.utils.ILogger;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.io.Closeables;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.List;
-import java.util.Properties;
-
-/**
- * Singleton-based implementation of SdkLoader for a standard SDK
- */
-public class DefaultSdkLoader implements SdkLoader {
-
- private static DefaultSdkLoader sLoader;
-
- @NonNull
- private final File mSdkLocation;
- private SdkManager mSdkManager;
- private SdkInfo mSdkInfo;
- private final ImmutableList<File> mRepositories;
-
- public static synchronized SdkLoader getLoader(
- @NonNull File sdkLocation) {
- if (sLoader == null) {
- sLoader = new DefaultSdkLoader(sdkLocation);
- } else if (!sdkLocation.equals(sLoader.mSdkLocation)) {
- throw new IllegalStateException("Already created an SDK Loader with different SDK Path");
- }
-
- return sLoader;
- }
-
- public static synchronized void unload() {
- sLoader = null;
- }
-
- @Override
- @NonNull
- public TargetInfo getTargetInfo(
- @NonNull String targetHash,
- @NonNull FullRevision buildToolRevision,
- @NonNull ILogger logger) {
- init(logger);
-
- IAndroidTarget target = mSdkManager.getTargetFromHashString(targetHash);
- if (target == null) {
- throw new IllegalStateException("failed to find target with hash string '" + targetHash + "' in: " + mSdkLocation);
- }
-
- BuildToolInfo buildToolInfo = mSdkManager.getBuildTool(buildToolRevision);
- if (buildToolInfo == null) {
- throw new IllegalStateException("failed to find Build Tools revision "
- + buildToolRevision.toString());
- }
-
- return new TargetInfo(target, buildToolInfo);
- }
-
- @Override
- @NonNull
- public SdkInfo getSdkInfo(@NonNull ILogger logger) {
- init(logger);
- return mSdkInfo;
- }
-
- @Override
- @NonNull
- public ImmutableList<File> getRepositories() {
- return mRepositories;
- }
-
- private DefaultSdkLoader(@NonNull File sdkLocation) {
- mSdkLocation = sdkLocation;
- mRepositories = computeRepositories();
- }
-
- private synchronized void init(@NonNull ILogger logger) {
- if (mSdkManager == null) {
- mSdkManager = SdkManager.createManager(mSdkLocation.getPath(), logger);
-
- if (mSdkManager == null) {
- throw new IllegalStateException("failed to parse SDK! Check console for details");
- }
-
- File toolsFolder = new File(mSdkLocation, FD_TOOLS);
- File supportToolsFolder = new File(toolsFolder, FD_SUPPORT);
- File platformTools = new File(mSdkLocation, FD_PLATFORM_TOOLS);
-
- mSdkInfo = new SdkInfo(
- new File(supportToolsFolder, FN_ANNOTATIONS_JAR),
- new File(platformTools, FN_ADB));
- }
- }
-
- @Nullable
- private FullRevision getPlatformToolsRevision(@NonNull File platformToolsFolder) {
- if (!platformToolsFolder.isDirectory()) {
- return null;
- }
-
- Reader reader = null;
- try {
- reader = new InputStreamReader(
- new FileInputStream(new File(platformToolsFolder, FN_SOURCE_PROP)),
- Charsets.UTF_8);
- Properties props = new Properties();
- props.load(reader);
-
- String value = props.getProperty(PkgProps.PKG_REVISION);
-
- return FullRevision.parseRevision(value);
-
- } catch (FileNotFoundException ignore) {
- // return null below.
- } catch (IOException ignore) {
- // return null below.
- } catch (NumberFormatException ignore) {
- // return null below.
- } finally {
- try {
- Closeables.close(reader, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- }
-
- return null;
- }
-
- @NonNull
- public ImmutableList<File> computeRepositories() {
- List<File> repositories = Lists.newArrayListWithExpectedSize(2);
-
- File androidRepo = new File(mSdkLocation, FD_EXTRAS + File.separator + "android"
- + File.separator + FD_M2_REPOSITORY);
- if (androidRepo.isDirectory()) {
- repositories.add(androidRepo);
- }
-
- File googleRepo = new File(mSdkLocation, FD_EXTRAS + File.separator + "google"
- + File.separator + FD_M2_REPOSITORY);
- if (googleRepo.isDirectory()) {
- repositories.add(googleRepo);
- }
-
- return ImmutableList.copyOf(repositories);
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java b/base/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java
deleted file mode 100644
index 53449a1..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.sdk;
-
-import static com.android.SdkConstants.FN_AAPT;
-import static com.android.SdkConstants.FN_AIDL;
-import static com.android.SdkConstants.FN_BCC_COMPAT;
-import static com.android.SdkConstants.FN_RENDERSCRIPT;
-import static com.android.SdkConstants.FN_ZIPALIGN;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.builder.internal.FakeAndroidTarget;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-
-/**
- * Singleton-based implementation of SdkLoader for a platform-based SDK.
- *
- * Platform-based SDK are in the Android source tree in AOSP, using a different
- * folder layout for all the files.
- */
-public class PlatformLoader implements SdkLoader {
-
- private static PlatformLoader sLoader;
-
- @NonNull
- private final File mTreeLocation;
-
- private File mHostToolsFolder;
- private SdkInfo mSdkInfo;
- @NonNull
- private final ImmutableList<File> mRepositories;
-
- public static synchronized SdkLoader getLoader(
- @NonNull File treeLocation) {
- if (sLoader == null) {
- sLoader = new PlatformLoader(treeLocation);
- } else if (!treeLocation.equals(sLoader.mTreeLocation)) {
- throw new IllegalStateException("Already created an SDK Loader with different SDK Path");
- }
-
- return sLoader;
- }
-
- public static synchronized void unload() {
- sLoader = null;
- }
-
- @NonNull
- @Override
- public TargetInfo getTargetInfo(@NonNull String targetHash,
- @NonNull FullRevision buildToolRevision, @NonNull ILogger logger) {
- init(logger);
-
- IAndroidTarget androidTarget = new FakeAndroidTarget(mTreeLocation.getPath(), targetHash);
-
- File hostTools = getHostToolsFolder();
-
- BuildToolInfo buildToolInfo = new BuildToolInfo(
- buildToolRevision,
- mTreeLocation,
- new File(hostTools, FN_AAPT),
- new File(hostTools, FN_AIDL),
- new File(mTreeLocation, "prebuilts/sdk/tools/dx"),
- new File(mTreeLocation, "prebuilts/sdk/tools/lib/dx.jar"),
- new File(hostTools, FN_RENDERSCRIPT),
- new File(mTreeLocation, "prebuilts/sdk/renderscript/include"),
- new File(mTreeLocation, "prebuilts/sdk/renderscript/clang-include"),
- new File(hostTools, FN_BCC_COMPAT),
- new File(hostTools, "arm-linux-androideabi-ld"),
- new File(hostTools, "i686-linux-android-ld"),
- new File(hostTools, "mipsel-linux-android-ld"),
- new File(hostTools, FN_ZIPALIGN));
-
- return new TargetInfo(androidTarget, buildToolInfo);
- }
-
- @NonNull
- @Override
- public SdkInfo getSdkInfo(@NonNull ILogger logger) {
- init(logger);
- return mSdkInfo;
- }
-
- @Override
- @NonNull
- public ImmutableList<File> getRepositories() {
- return mRepositories;
- }
-
- private PlatformLoader(@NonNull File treeLocation) {
- mTreeLocation = treeLocation;
- mRepositories = ImmutableList.of(new File(mTreeLocation + "/prebuilts/sdk/m2repository"));
- }
-
- private synchronized void init(@NonNull ILogger logger) {
- if (mSdkInfo == null) {
- String host;
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
- host = "darwin-x86";
- } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
- host = "linux-x86";
- } else {
- throw new IllegalStateException(
- "Windows is not supported for platform development");
- }
-
- mSdkInfo = new SdkInfo(
- new File(mTreeLocation, "out/host/" + host + "/framework/annotations.jar"),
- new File(mTreeLocation, "out/host/" + host + "/bin/adb"));
- }
- }
-
- @NonNull
- private synchronized File getHostToolsFolder() {
- if (mHostToolsFolder == null) {
- File tools = new File(mTreeLocation, "prebuilts/sdk/tools");
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
- mHostToolsFolder = new File(tools, "darwin/bin");
- } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
- mHostToolsFolder = new File(tools, "linux/bin");
- } else {
- throw new IllegalStateException(
- "Windows is not supported for platform development");
- }
-
- if (!mHostToolsFolder.isDirectory()) {
- throw new IllegalStateException("Host tools folder missing: " +
- mHostToolsFolder.getAbsolutePath());
- }
- }
-
- return mHostToolsFolder;
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java b/base/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java
deleted file mode 100644
index e169fc1..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.sdk;
-
-import com.android.annotations.NonNull;
-
-import java.io.File;
-
-/**
- * General information about the SDK.
- */
-public class SdkInfo {
-
- @NonNull
- private final File mAnnotationJar;
- @NonNull
- private final File mAdb;
-
- SdkInfo(@NonNull File annotationJar,
- @NonNull File adb) {
- mAnnotationJar = annotationJar;
- mAdb = adb;
- }
-
- /**
- * Returns the location of the annotations jar for compilation targets that are <= 15.
- */
- @NonNull
- public File getAnnotationsJar() {
- return mAnnotationJar;
- }
-
- /**
- * Returns the revision of the installed platform tools component.
- *
- * @return the FullRevision or null if the revision couldn't not be found
- */
-// @Nullable
-// public FullRevision getPlatformToolsRevision() {
-//
-// }
-
- /**
- * Returns the location of the adb tool.
- */
- @NonNull
- public File getAdb() {
- return mAdb;
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java b/base/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java
deleted file mode 100644
index 0fadfc5..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.sdk;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-
-/**
- * A loader for the SDK. It's able to provide general SDK information
- * ({@link #getSdkInfo(com.android.utils.ILogger)}, or {@link #getRepositories()}), or
- * target-specific information
- * ({@link #getTargetInfo(String, com.android.sdklib.repository.FullRevision, com.android.utils.ILogger)}).
- */
-public interface SdkLoader {
-
- /**
- * Returns information about a build target.
- *
- * This requires loading/parsing the SDK.
- *
- * @param targetHash the compilation target hash string.
- * @param buildToolRevision the build tools revision.
- * @param logger a logger to output messages.
- * @return the target info.
- */
- @NonNull
- TargetInfo getTargetInfo(
- @NonNull String targetHash,
- @NonNull FullRevision buildToolRevision,
- @NonNull ILogger logger);
-
- /**
- * Returns generic SDK information.
- *
- * This requires loading/parsing the SDK.
- *
- * @param logger a logger to output messages.
- * @return the sdk info.
- */
- @NonNull
- SdkInfo getSdkInfo(@NonNull ILogger logger);
-
- /**
- * Returns the location of artifact repositories built-in the SDK.
- * @return a non null list of repository folders.
- */
- @NonNull
- ImmutableList<File> getRepositories();
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java b/base/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java
deleted file mode 100644
index b41c9ff..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Copyright (C) 2008 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.builder.signing;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.cert.jcajce.JcaCertStore;
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.cms.CMSProcessableByteArray;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.CMSSignedDataGenerator;
-import org.bouncycastle.cms.CMSTypedData;
-import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
-import org.bouncycastle.util.encoders.Base64;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.security.DigestOutputStream;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.jar.Attributes;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * A Jar file builder with signature support.
- */
-public class SignedJarBuilder {
- private static final String DIGEST_ALGORITHM = "SHA1";
- private static final String DIGEST_ATTR = "SHA1-Digest";
- private static final String DIGEST_MANIFEST_ATTR = "SHA1-Digest-Manifest";
-
- /** Write to another stream and track how many bytes have been
- * written.
- */
- private static class CountOutputStream extends FilterOutputStream {
- private int mCount = 0;
-
- public CountOutputStream(OutputStream out) {
- super(out);
- mCount = 0;
- }
-
- @Override
- public void write(int b) throws IOException {
- super.write(b);
- mCount++;
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- super.write(b, off, len);
- mCount += len;
- }
-
- public int size() {
- return mCount;
- }
- }
-
- private JarOutputStream mOutputJar;
- private PrivateKey mKey;
- private X509Certificate mCertificate;
- private Manifest mManifest;
- private MessageDigest mMessageDigest;
-
- private byte[] mBuffer = new byte[4096];
-
- /**
- * Classes which implement this interface provides a method to check whether a file should
- * be added to a Jar file.
- */
- public interface IZipEntryFilter {
-
- /**
- * An exception thrown during packaging of a zip file into APK file.
- * This is typically thrown by implementations of
- * {@link IZipEntryFilter#checkEntry(String)}.
- */
- class ZipAbortException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public ZipAbortException() {
- super();
- }
-
- public ZipAbortException(String format, Object... args) {
- super(String.format(format, args));
- }
-
- public ZipAbortException(Throwable cause, String format, Object... args) {
- super(String.format(format, args), cause);
- }
-
- public ZipAbortException(Throwable cause) {
- super(cause);
- }
- }
-
-
- /**
- * Checks a file for inclusion in a Jar archive.
- * @param archivePath the archive file path of the entry
- * @return <code>true</code> if the file should be included.
- * @throws ZipAbortException if writing the file should be aborted.
- */
- boolean checkEntry(String archivePath) throws ZipAbortException;
- }
-
-
- /**
- * Classes which implement this interface provides a method to check whether a file should
- * be merged and extracted from the zip.
- */
- public interface ZipEntryExtractor {
-
- boolean checkEntry(String archivePath);
-
- void extract(String archivePath, InputStream zis) throws IOException;
- }
-
- /**
- * Creates a {@link SignedJarBuilder} with a given output stream, and signing information.
- * <p/>If either <code>key</code> or <code>certificate</code> is <code>null</code> then
- * the archive will not be signed.
- * @param out the {@link OutputStream} where to write the Jar archive.
- * @param key the {@link PrivateKey} used to sign the archive, or <code>null</code>.
- * @param certificate the {@link X509Certificate} used to sign the archive, or
- * <code>null</code>.
- * @throws IOException
- * @throws NoSuchAlgorithmException
- */
- public SignedJarBuilder(@NonNull OutputStream out,
- @Nullable PrivateKey key,
- @Nullable X509Certificate certificate,
- @Nullable String builtBy,
- @Nullable String createdBy)
- throws IOException, NoSuchAlgorithmException {
- mOutputJar = new JarOutputStream(new BufferedOutputStream(out));
- mOutputJar.setLevel(9);
- mKey = key;
- mCertificate = certificate;
-
- if (mKey != null && mCertificate != null) {
- mManifest = new Manifest();
- Attributes main = mManifest.getMainAttributes();
- main.putValue("Manifest-Version", "1.0");
- if (builtBy != null) {
- main.putValue("Built-By", builtBy);
- }
- if (createdBy != null) {
- main.putValue("Created-By", createdBy);
- }
-
- mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
- }
- }
-
- /**
- * Writes a new {@link File} into the archive.
- * @param inputFile the {@link File} to write.
- * @param jarPath the filepath inside the archive.
- * @throws IOException
- */
- public void writeFile(File inputFile, String jarPath) throws IOException {
- // Get an input stream on the file.
- FileInputStream fis = new FileInputStream(inputFile);
- try {
-
- // create the zip entry
- JarEntry entry = new JarEntry(jarPath);
- entry.setTime(inputFile.lastModified());
-
- writeEntry(fis, entry);
- } finally {
- // close the file stream used to read the file
- fis.close();
- }
- }
-
- /**
- * Copies the content of a Jar/Zip archive into the receiver archive.
- * @param input the {@link InputStream} for the Jar/Zip to copy.
- * @throws IOException
- * @throws ZipAbortException if the {@link IZipEntryFilter} filter indicated that the write
- * must be aborted.
- */
- public void writeZip(InputStream input) throws IOException, ZipAbortException {
- writeZip(input, null, null);
- }
-
- /**
- * Copies the content of a Jar/Zip archive into the receiver archive.
- * <p/>An optional {@link IZipEntryFilter} allows to selectively choose which files
- * to copy over.
- * @param input the {@link InputStream} for the Jar/Zip to copy.
- * @param filter the filter or <code>null</code>
- * @throws IOException
- * @throws ZipAbortException if the {@link IZipEntryFilter} filter indicated that the write
- * must be aborted.
- */
- public void writeZip(InputStream input, IZipEntryFilter filter, ZipEntryExtractor extractor)
- throws IOException, ZipAbortException {
- ZipInputStream zis = new ZipInputStream(input);
-
- try {
- // loop on the entries of the intermediary package and put them in the final package.
- ZipEntry entry;
- while ((entry = zis.getNextEntry()) != null) {
- String name = entry.getName();
-
- // do not take directories or anything inside a potential META-INF folder.
- if (entry.isDirectory()) {
- continue;
- }
-
- // ignore some of the content in META-INF/ but not all
- if (name.startsWith("META-INF/")) {
- // ignore the manifest file.
- String subName = name.substring(9);
- if ("MANIFEST.MF".equals(subName)) {
- continue;
- }
-
- // special case for Maven meta-data because we really don't care about them in apks.
- if (name.startsWith("META-INF/maven/")) {
- continue;
- }
-
-
- // check for subfolder
- int index = subName.indexOf('/');
- if (index == -1) {
- // no sub folder, ignores signature files.
- if (subName.endsWith(".SF") || name.endsWith(".RSA") || name.endsWith(".DSA")) {
- continue;
- }
- }
- }
-
- // if we have a filter, we check the entry to see if it's a file that should be extracted.
- if (extractor != null && extractor.checkEntry(name)) {
- extractor.extract(name, zis);
- continue;
- }
-
- // if we have a filter, we check the entry against it
- if (filter != null && !filter.checkEntry(name)) {
- continue;
- }
-
- JarEntry newEntry;
-
- // Preserve the STORED method of the input entry.
- if (entry.getMethod() == JarEntry.STORED) {
- newEntry = new JarEntry(entry);
- } else {
- // Create a new entry so that the compressed len is recomputed.
- newEntry = new JarEntry(name);
- }
-
- writeEntry(zis, newEntry);
-
- zis.closeEntry();
- }
- } finally {
- zis.close();
- }
- }
-
- /**
- * Closes the Jar archive by creating the manifest, and signing the archive.
- * @throws IOException
- * @throws SigningException
- */
- public void close() throws IOException, SigningException {
- if (mManifest != null) {
- // write the manifest to the jar file
- mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
- mManifest.write(mOutputJar);
-
- try {
- // CERT.SF
- Signature signature = Signature.getInstance("SHA1with" + mKey.getAlgorithm());
- signature.initSign(mKey);
- mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeSignatureFile(baos);
- byte[] signedData = baos.toByteArray();
- mOutputJar.write(signedData);
-
- // CERT.*
- mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm()));
- writeSignatureBlock(new CMSProcessableByteArray(signedData), mCertificate, mKey);
- } catch (Exception e) {
- throw new SigningException(e);
- }
- }
-
- mOutputJar.close();
- mOutputJar = null;
- }
-
- /**
- * Clean up of the builder for interrupted workflow.
- * This does nothing if {@link #close()} was called successfully.
- */
- public void cleanUp() {
- if (mOutputJar != null) {
- try {
- mOutputJar.close();
- } catch (IOException e) {
- // pass
- }
- }
- }
-
- /**
- * Adds an entry to the output jar, and write its content from the {@link InputStream}
- * @param input The input stream from where to write the entry content.
- * @param entry the entry to write in the jar.
- * @throws IOException
- */
- private void writeEntry(InputStream input, JarEntry entry) throws IOException {
- // add the entry to the jar archive
- mOutputJar.putNextEntry(entry);
-
- // read the content of the entry from the input stream, and write it into the archive.
- int count;
- while ((count = input.read(mBuffer)) != -1) {
- mOutputJar.write(mBuffer, 0, count);
-
- // update the digest
- if (mMessageDigest != null) {
- mMessageDigest.update(mBuffer, 0, count);
- }
- }
-
- // close the entry for this file
- mOutputJar.closeEntry();
-
- if (mManifest != null) {
- // update the manifest for this entry.
- Attributes attr = mManifest.getAttributes(entry.getName());
- if (attr == null) {
- attr = new Attributes();
- mManifest.getEntries().put(entry.getName(), attr);
- }
- attr.putValue(DIGEST_ATTR,
- new String(Base64.encode(mMessageDigest.digest()), "ASCII"));
- }
- }
-
- /** Writes a .SF file with a digest to the manifest. */
- private void writeSignatureFile(OutputStream out)
- throws IOException, GeneralSecurityException {
- Manifest sf = new Manifest();
- Attributes main = sf.getMainAttributes();
- main.putValue("Signature-Version", "1.0");
- main.putValue("Created-By", "1.0 (Android)");
-
- MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
- PrintStream print = new PrintStream(
- new DigestOutputStream(new ByteArrayOutputStream(), md),
- true, SdkConstants.UTF_8);
-
- // Digest of the entire manifest
- mManifest.write(print);
- print.flush();
- main.putValue(DIGEST_MANIFEST_ATTR, new String(Base64.encode(md.digest()), "ASCII"));
-
- Map<String, Attributes> entries = mManifest.getEntries();
- for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
- // Digest of the manifest stanza for this entry.
- print.print("Name: " + entry.getKey() + "\r\n");
- for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
- print.print(att.getKey() + ": " + att.getValue() + "\r\n");
- }
- print.print("\r\n");
- print.flush();
-
- Attributes sfAttr = new Attributes();
- sfAttr.putValue(DIGEST_ATTR, new String(Base64.encode(md.digest()), "ASCII"));
- sf.getEntries().put(entry.getKey(), sfAttr);
- }
- CountOutputStream cout = new CountOutputStream(out);
- sf.write(cout);
-
- // A bug in the java.util.jar implementation of Android platforms
- // up to version 1.6 will cause a spurious IOException to be thrown
- // if the length of the signature file is a multiple of 1024 bytes.
- // As a workaround, add an extra CRLF in this case.
- if ((cout.size() % 1024) == 0) {
- cout.write('\r');
- cout.write('\n');
- }
- }
-
- /** Write the certificate file with a digital signature. */
- private void writeSignatureBlock(CMSTypedData data, X509Certificate publicKey,
- PrivateKey privateKey)
- throws IOException,
- CertificateEncodingException,
- OperatorCreationException,
- CMSException {
-
- ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
- certList.add(publicKey);
- JcaCertStore certs = new JcaCertStore(certList);
-
- CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
- ContentSigner sha1Signer = new JcaContentSignerBuilder(
- "SHA1with" + privateKey.getAlgorithm())
- .build(privateKey);
- gen.addSignerInfoGenerator(
- new JcaSignerInfoGeneratorBuilder(
- new JcaDigestCalculatorProviderBuilder()
- .build())
- .setDirectSignature(true)
- .build(sha1Signer, publicKey));
- gen.addCertificates(certs);
- CMSSignedData sigData = gen.generate(data, false);
-
- ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
- DEROutputStream dos = new DEROutputStream(mOutputJar);
- dos.writeObject(asn1.readObject());
-
- dos.flush();
- dos.close();
- asn1.close();
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java b/base/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java
deleted file mode 100644
index 4bca0d9..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.testing;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.testing.api.DeviceConfig;
-import com.android.builder.testing.api.DeviceConnector;
-import com.android.builder.testing.api.DeviceException;
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.IShellOutputReceiver;
-import com.android.ddmlib.MultiLineReceiver;
-import com.android.ddmlib.ShellCommandUnresponsiveException;
-import com.android.ddmlib.SyncException;
-import com.android.ddmlib.TimeoutException;
-import com.android.utils.ILogger;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Local device connected to with ddmlib. This is a wrapper around {@link IDevice}.
- */
-public class ConnectedDevice extends DeviceConnector {
-
- private final IDevice iDevice;
-
- public ConnectedDevice(@NonNull IDevice iDevice) {
- this.iDevice = iDevice;
- }
-
- @NonNull
- @Override
- public String getName() {
- String version = iDevice.getProperty(IDevice.PROP_BUILD_VERSION);
- boolean emulator = iDevice.isEmulator();
-
- String name;
- if (emulator) {
- name = iDevice.getAvdName() != null ?
- iDevice.getAvdName() + "(AVD)" :
- iDevice.getSerialNumber();
- } else {
- String model = iDevice.getProperty(IDevice.PROP_DEVICE_MODEL);
- name = model != null ? model : iDevice.getSerialNumber();
- }
-
- return version != null ? name + " - " + version : name;
- }
-
- @Override
- public void connect(int timeout, ILogger logger) throws TimeoutException {
- // nothing to do here
- }
-
- @Override
- public void disconnect(int timeout, ILogger logger) throws TimeoutException {
- // nothing to do here
- }
-
- @Override
- public void installPackage(@NonNull File apkFile,
- @NonNull Collection<String> options,
- int timeout,
- ILogger logger) throws DeviceException {
- try {
- iDevice.installPackage(apkFile.getAbsolutePath(), true /*reinstall*/,
- options.isEmpty() ? null : options.toArray(new String[options.size()]));
- } catch (Exception e) {
- logger.error(e, "Unable to install " + apkFile.getAbsolutePath());
- throw new DeviceException(e);
- }
- }
-
- @Override
- public void installPackages(@NonNull List<File> splitApkFiles,
- @NonNull Collection<String> options,
- int timeoutInMs,
- ILogger logger)
- throws DeviceException {
-
- List<String> apkFileNames = Lists.transform(splitApkFiles, new Function<File, String>() {
- @Override
- public String apply(@Nullable File input) {
- return input != null ? input.getAbsolutePath() : null;
- }
- });
- try {
- iDevice.installPackages(apkFileNames, timeoutInMs, true /*reinstall*/,
- options.isEmpty() ? null : options.toArray(new String[options.size()]));
- } catch (Exception e) {
- logger.error(e, "Unable to install " + Joiner.on(',').join(apkFileNames));
- throw new DeviceException(e);
- }
- }
-
- @Override
- public void uninstallPackage(@NonNull String packageName, int timeout, ILogger logger) throws DeviceException {
- try {
- iDevice.uninstallPackage(packageName);
- } catch (Exception e) {
- logger.error(e, "Unable to uninstall " + packageName);
- throw new DeviceException(e);
- }
- }
-
- @Override
- public void executeShellCommand(String command, IShellOutputReceiver receiver,
- long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
- throws TimeoutException, AdbCommandRejectedException,
- ShellCommandUnresponsiveException, IOException {
- iDevice.executeShellCommand(command, receiver, maxTimeToOutputResponse, maxTimeUnits);
- }
-
- @NonNull
- @Override
- public Future<String> getSystemProperty(@NonNull String name) {
- return iDevice.getSystemProperty(name);
- }
-
- @Override
- public void pullFile(String remote, String local) throws IOException {
- try {
- iDevice.pullFile(remote, local);
-
- } catch (TimeoutException e) {
- throw new IOException(String.format("Failed to pull %s from device", remote), e);
- } catch (AdbCommandRejectedException e) {
- throw new IOException(String.format("Failed to pull %s from device", remote), e);
- } catch (SyncException e) {
- throw new IOException(String.format("Failed to pull %s from device", remote), e);
- }
- }
-
- @NonNull
- @Override
- public String getSerialNumber() {
- return iDevice.getSerialNumber();
- }
-
- @Override
- public int getApiLevel() {
- String sdkVersion = iDevice.getProperty(IDevice.PROP_BUILD_API_LEVEL);
- if (sdkVersion != null) {
- try {
- return Integer.valueOf(sdkVersion);
- } catch (NumberFormatException e) {
-
- }
- }
-
- // can't get it, return 0.
- return 0;
- }
-
- @Override
- public String getApiCodeName() {
- String codeName = iDevice.getProperty(IDevice.PROP_BUILD_CODENAME);
- if (codeName != null) {
- // if this is a release platform return null.
- if ("REL".equals(codeName)) {
- return null;
- }
-
- // else return the codename
- return codeName;
- }
-
- // can't get it, return 0.
- return null;
- }
-
- @Nullable
- @Override
- public IDevice.DeviceState getState() {
- return iDevice.getState();
- }
-
- @NonNull
- @Override
- public List<String> getAbis() {
- return iDevice.getAbis();
- }
-
- @Override
- public int getDensity() {
- return iDevice.getDensity();
- }
-
- @Override
- public int getHeight() {
- return 0; //To change body of implemented methods use File | Settings | File Templates.
- }
-
- @Override
- public int getWidth() {
- return 0; //To change body of implemented methods use File | Settings | File Templates.
- }
-
- @Override
- public String getLanguage() {
- return iDevice.getLanguage();
- }
-
- @Override
- public String getRegion() {
- return iDevice.getRegion();
- }
-
- @Override
- @NonNull
- public String getProperty(@NonNull String propertyName) {
- return iDevice.getProperty(propertyName);
- }
-
- @NonNull
- @Override
- public DeviceConfig getDeviceConfig() throws DeviceException {
- final List<String> output = new ArrayList<String>();
- final MultiLineReceiver receiver = new MultiLineReceiver() {
- @Override
- public void processNewLines(String[] lines) {
- output.addAll(Arrays.asList(lines));
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
- };
- try {
- executeShellCommand("am get-config", receiver, 5, TimeUnit.SECONDS);
- return DeviceConfig.Builder.parse(output);
- } catch (Exception e) {
- throw new DeviceException(e);
- }
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java b/base/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java
deleted file mode 100644
index b1371d3..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.testing;
-
-import com.android.annotations.NonNull;
-import com.android.builder.testing.api.DeviceConnector;
-import com.android.builder.testing.api.DeviceException;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.ddmlib.AndroidDebugBridge;
-import com.android.ddmlib.IDevice;
-import com.android.utils.ILogger;
-import com.google.common.base.Joiner;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * DeviceProvider for locally connected devices. Basically returns the list of devices that
- * are currently connected at the time {@link #init()} is called.
- */
-public class ConnectedDeviceProvider extends DeviceProvider {
-
- @NonNull
- private final File adbLocation;
- @NonNull
- private final ILogger iLogger;
-
- @NonNull
- private final List<ConnectedDevice> localDevices = Lists.newArrayList();
-
- public ConnectedDeviceProvider(@NonNull File adbLocation, @NonNull ILogger logger) {
- this.adbLocation = adbLocation;
- iLogger = logger;
- }
-
- @Override
- @NonNull
- public String getName() {
- return "connected";
- }
-
- @Override
- @NonNull
- public List<? extends DeviceConnector> getDevices() {
- return localDevices;
- }
-
- @Override
- public void init() throws DeviceException {
- AndroidDebugBridge.initIfNeeded(false /*clientSupport*/);
-
- AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
- adbLocation.getAbsolutePath(), false /*forceNewBridge*/);
-
- long timeOut = 30000; // 30 sec
- int sleepTime = 1000;
- while (!bridge.hasInitialDeviceList() && timeOut > 0) {
- try {
- Thread.sleep(sleepTime);
- } catch (InterruptedException e) {
- throw new DeviceException(e);
- }
- timeOut -= sleepTime;
- }
-
- if (timeOut <= 0 && !bridge.hasInitialDeviceList()) {
- throw new DeviceException("Timeout getting device list.");
- }
-
- IDevice[] devices = bridge.getDevices();
-
- if (devices.length == 0) {
- throw new DeviceException("No connected devices!");
- }
-
- final String androidSerialsEnv = System.getenv("ANDROID_SERIAL");
- final boolean isValidSerial = androidSerialsEnv != null && !androidSerialsEnv.isEmpty();
-
- final Set<String> serials;
- if (isValidSerial) {
- serials = Sets.newHashSet(Splitter.on(',').split(androidSerialsEnv));
- } else {
- serials = Collections.emptySet();
- }
-
- final List<IDevice> filteredDevices = Lists.newArrayListWithCapacity(devices.length);
- for (IDevice iDevice : devices) {
- if (!isValidSerial || serials.contains(iDevice.getSerialNumber())) {
- serials.remove(iDevice.getSerialNumber());
- filteredDevices.add(iDevice);
- }
- }
-
- if (!serials.isEmpty()) {
- throw new DeviceException(String.format(
- "Connected device with serial%s '%s' not found!",
- serials.size() == 1 ? "" : "s",
- Joiner.on("', '").join(serials)));
- }
-
- for (IDevice device : filteredDevices) {
- if (device.getState() == IDevice.DeviceState.ONLINE) {
- localDevices.add(new ConnectedDevice(device));
- } else {
- iLogger.info(
- "Skipping device '%s' (%s): Device is %s%s.",
- device.getName(), device.getSerialNumber(), device.getState(),
- device.getState() == IDevice.DeviceState.UNAUTHORIZED ? ",\n"
- + " see http://d.android.com/tools/help/adb.html#Enabling" : "");
- }
- }
-
- if (localDevices.isEmpty()) {
- if (isValidSerial) {
- throw new DeviceException(String.format(
- "Connected device with serial $1%s is not online.",
- androidSerialsEnv));
- } else {
- throw new DeviceException("No online devices found.");
- }
- }
- }
-
- @Override
- public void terminate() throws DeviceException {
- // nothing to be done here.
- }
-
- @Override
- public int getTimeoutInMs() {
- return 0;
- }
-
- @Override
- public boolean isConfigured() {
- return true;
- }
-}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/testing/MockableJarGenerator.java b/base/build-system/builder/src/main/java/com/android/builder/testing/MockableJarGenerator.java
deleted file mode 100644
index 76118b8..0000000
--- a/base/build-system/builder/src/main/java/com/android/builder/testing/MockableJarGenerator.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.testing;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.io.ByteStreams;
-
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.InnerClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.InsnNode;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.TypeInsnNode;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.util.Collections;
-import java.util.List;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.zip.ZipEntry;
-
-/**
- * Given a "standard" android.jar, creates a "mockable" version, where all classes and methods
- * are not final. Optionally makes all methods return "default" values, instead of throwing the
- * infamous "Stub!" exceptions.
- */
-public class MockableJarGenerator {
- private static final int EMPTY_FLAGS = 0;
- private static final String CONSTRUCTOR = "<init>";
- private static final String CLASS_CONSTRUCTOR = "<clinit>";
-
- private static final ImmutableSet<String> ENUM_METHODS = ImmutableSet.of(
- CLASS_CONSTRUCTOR, "valueOf", "values");
-
- private static final ImmutableSet<Type> INTEGER_LIKE_TYPES = ImmutableSet.of(
- Type.INT_TYPE, Type.BYTE_TYPE, Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.SHORT_TYPE);
-
- private final boolean returnDefaultValues;
- private final ImmutableSet<String> prefixesToSkip = ImmutableSet.of(
- "java.", "javax.", "org.xml.", "org.w3c.", "junit.", "org.apache.commons.logging");
-
- public MockableJarGenerator(boolean returnDefaultValues) {
- this.returnDefaultValues = returnDefaultValues;
- }
-
- public void createMockableJar(File input, File output) throws IOException {
- Preconditions.checkState(
- output.createNewFile(),
- "Output file [%s] already exists.",
- output.getAbsolutePath());
-
- JarFile androidJar = null;
- JarOutputStream outputStream = null;
- try {
- androidJar = new JarFile(input);
- outputStream = new JarOutputStream(new FileOutputStream(output));
-
- for (JarEntry entry : Collections.list(androidJar.entries())) {
- InputStream inputStream = androidJar.getInputStream(entry);
-
- if (entry.getName().endsWith(".class")) {
- if (!skipClass(entry.getName().replace("/", "."))) {
- rewriteClass(entry, inputStream, outputStream);
- }
- } else {
- outputStream.putNextEntry(entry);
- ByteStreams.copy(inputStream, outputStream);
- }
-
- inputStream.close();
- }
- } finally {
- if (androidJar != null) {
- androidJar.close();
- }
- if (outputStream != null) {
- outputStream.close();
- }
- }
- }
-
- private boolean skipClass(String className) {
- for (String prefix : prefixesToSkip) {
- if (className.startsWith(prefix)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Writes a modified *.class file to the output JAR file.
- */
- private void rewriteClass(
- JarEntry entry,
- InputStream inputStream,
- JarOutputStream outputStream) throws IOException {
- ClassReader classReader = new ClassReader(inputStream);
- ClassNode classNode = new ClassNode(Opcodes.ASM5);
-
- classReader.accept(classNode, EMPTY_FLAGS);
-
- modifyClass(classNode);
-
- ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
- classNode.accept(classWriter);
-
- outputStream.putNextEntry(new ZipEntry(entry.getName()));
- outputStream.write(classWriter.toByteArray());
- }
-
- /**
- * Modifies a {@link ClassNode} to clear final flags and rewrite byte code.
- */
- @SuppressWarnings("unchecked")
- private void modifyClass(ClassNode classNode) {
- // Make the class not final.
- classNode.access &= ~Opcodes.ACC_FINAL;
-
- List<MethodNode> methodNodes = classNode.methods;
- for (MethodNode methodNode : methodNodes) {
- methodNode.access &= ~Opcodes.ACC_FINAL;
- fixMethodBody(methodNode, classNode);
- }
-
- List<FieldNode> fieldNodes = classNode.fields;
- for (FieldNode fieldNode : fieldNodes) {
- // Make public instance fields non-final. This is needed e.g. to "mock" SyncResult.stats.
- if ((fieldNode.access & Opcodes.ACC_PUBLIC) != 0 &&
- (fieldNode.access & Opcodes.ACC_STATIC) == 0) {
- fieldNode.access &= ~Opcodes.ACC_FINAL;
- }
- }
-
- List<InnerClassNode> innerClasses = classNode.innerClasses;
- for (InnerClassNode innerClassNode : innerClasses) {
- innerClassNode.access &= ~Opcodes.ACC_FINAL;
- }
- }
-
- /**
- * Rewrites the method bytecode to remove the "Stub!" exception.
- */
- private void fixMethodBody(MethodNode methodNode, ClassNode classNode) {
- if ((methodNode.access & Opcodes.ACC_NATIVE) != 0
- || (methodNode.access & Opcodes.ACC_ABSTRACT) != 0) {
- // Abstract and native method don't have bodies to rewrite.
- return;
- }
-
- if ((classNode.access & Opcodes.ACC_ENUM) != 0 && ENUM_METHODS.contains(methodNode.name)) {
- // Don't break enum classes.
- return;
- }
-
- Type returnType = Type.getReturnType(methodNode.desc);
- InsnList instructions = methodNode.instructions;
-
- if (methodNode.name.equals(CONSTRUCTOR)) {
- // Keep the call to parent constructor, delete the exception after that.
-
- boolean deadCode = false;
- for (AbstractInsnNode instruction : instructions.toArray()) {
- if (!deadCode) {
- if (instruction.getOpcode() == Opcodes.INVOKESPECIAL) {
- instructions.insert(instruction, new InsnNode(Opcodes.RETURN));
- // Start removing all following instructions.
- deadCode = true;
- }
- } else {
- instructions.remove(instruction);
- }
- }
- } else {
- instructions.clear();
-
- if (returnDefaultValues || methodNode.name.equals(CLASS_CONSTRUCTOR)) {
- if (INTEGER_LIKE_TYPES.contains(returnType)) {
- instructions.add(new InsnNode(Opcodes.ICONST_0));
- } else if (returnType.equals(Type.LONG_TYPE)) {
- instructions.add(new InsnNode(Opcodes.LCONST_0));
- } else if (returnType.equals(Type.FLOAT_TYPE)) {
- instructions.add(new InsnNode(Opcodes.FCONST_0));
- } else if (returnType.equals(Type.DOUBLE_TYPE)) {
- instructions.add(new InsnNode(Opcodes.DCONST_0));
- } else {
- instructions.add(new InsnNode(Opcodes.ACONST_NULL));
- }
-
- instructions.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
- } else {
- instructions.insert(throwExceptionsList(methodNode, classNode));
- }
- }
- }
-
- private static InsnList throwExceptionsList(MethodNode methodNode, ClassNode classNode) {
- try {
- String runtimeException = Type.getInternalName(RuntimeException.class);
- Constructor<RuntimeException> constructor =
- RuntimeException.class.getConstructor(String.class);
-
- InsnList instructions = new InsnList();
- instructions.add(
- new TypeInsnNode(Opcodes.NEW, runtimeException));
- instructions.add(new InsnNode(Opcodes.DUP));
-
- String className = classNode.name.replace('/', '.');
- instructions.add(new LdcInsnNode("Method " + methodNode.name + " in " + className
- + " not mocked. "
- + "See http://g.co/androidstudio/not-mocked for details."));
- instructions.add(new MethodInsnNode(
- Opcodes.INVOKESPECIAL,
- runtimeException,
- CONSTRUCTOR,
- Type.getType(constructor).getDescriptor(),
- false));
- instructions.add(new InsnNode(Opcodes.ATHROW));
-
- return instructions;
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/core/AaptPackageProcessBuilderTest.java b/base/build-system/builder/src/test/java/com/android/builder/core/AaptPackageProcessBuilderTest.java
deleted file mode 100644
index e4b8435..0000000
--- a/base/build-system/builder/src/test/java/com/android/builder/core/AaptPackageProcessBuilderTest.java
+++ /dev/null
@@ -1,695 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.core;
-
-import com.android.annotations.NonNull;
-import com.android.builder.dependency.SymbolFileProvider;
-import com.android.builder.model.AaptOptions;
-import com.android.ide.common.process.ProcessInfo;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-import com.google.common.collect.ImmutableList;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Tests for {@link AaptPackageProcessBuilder} class
- */
-public class AaptPackageProcessBuilderTest extends TestCase {
-
- @Mock
- AaptOptions mAaptOptions;
-
- BuildToolInfo mBuildToolInfo;
- IAndroidTarget mIAndroidTarget;
-
- ILogger mLogger = new StdLogger(StdLogger.Level.VERBOSE);
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- mBuildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("21"));
- if (mBuildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 21");
- }
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() == 21) {
- mIAndroidTarget = iAndroidTarget;
- }
- }
- if (mIAndroidTarget == null) {
- throw new RuntimeException("Test requires android-21");
- }
- }
-
- public void testAndroidManifestPackaging() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir");
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(mBuildToolInfo, mIAndroidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertTrue("/path/to/non/existent/file".equals(command.get(command.indexOf("-M") + 1)));
- assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
- assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
- }
-
- public void testResourcesPackaging() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT);
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(mBuildToolInfo, mIAndroidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertTrue("/path/to/non/existent/file".equals(command.get(command.indexOf("-M") + 1)));
- assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
- assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
- assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
- assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
- assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
- assertTrue("com.example.package.forR".equals(command.get(command.indexOf("--custom-package") + 1)));
-
- assertTrue(command.indexOf("-f") != -1);
- assertTrue(command.indexOf("--no-crunch") != -1);
- assertTrue(command.indexOf("-0") != -1);
- assertTrue(command.indexOf("apk") != -1);
- }
-
- public void testResourcesPackagingForTest() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.ANDROID_TEST);
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(mBuildToolInfo, mIAndroidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertTrue("/path/to/non/existent/file".equals(command.get(command.indexOf("-M") + 1)));
- assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
- assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
- assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
- assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
- assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
- assertTrue(command.indexOf("--custom-package") == -1);
-
- assertTrue(command.indexOf("-f") != -1);
- assertTrue(command.indexOf("--no-crunch") != -1);
- assertTrue(command.indexOf("-0") != -1);
- assertTrue(command.indexOf("apk") != -1);
- }
-
- public void testResourcesPackagingForLibrary() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.LIBRARY);
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(mBuildToolInfo, mIAndroidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertTrue("/path/to/non/existent/file".equals(command.get(command.indexOf("-M") + 1)));
- assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
- assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
- assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
- assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
- assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
-
- assertTrue(command.indexOf("--non-constant-id") != -1);
-
- assertTrue(command.indexOf("-f") != -1);
- assertTrue(command.indexOf("--no-crunch") != -1);
- assertTrue(command.indexOf("-0") != -1);
- assertTrue(command.indexOf("apk") != -1);
- }
-
-
- public void testSplitResourcesPackaging() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setSplits(ImmutableList.of("mdpi", "hdpi"));
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(mBuildToolInfo, mIAndroidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertTrue("/path/to/non/existent/file".equals(command.get(command.indexOf("-M") + 1)));
- assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
- assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
- assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
- assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
- assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
- assertTrue("com.example.package.forR".equals(command.get(command.indexOf("--custom-package") + 1)));
-
- assertTrue(command.indexOf("-f") != -1);
- assertTrue(command.indexOf("--no-crunch") != -1);
- assertTrue(command.indexOf("-0") != -1);
- assertTrue(command.indexOf("apk") != -1);
-
- assertTrue("--split".equals(command.get(command.indexOf("mdpi") - 1)));
- assertTrue("--split".equals(command.get(command.indexOf("hdpi") - 1)));
- assertTrue(command.indexOf("--preferred-density") == -1);
- }
-
- public void testPre21ResourceConfigsAndPreferredDensity() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setResourceConfigs(ImmutableList.of("res1", "res2"))
- .setPreferredDensity("xhdpi");
-
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("20"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 20");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() < 20) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android-21");
- }
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(buildToolInfo, androidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertTrue("res1,res2,xhdpi,nodpi".equals(command.get(command.indexOf("-c") + 1)));
- assertTrue(command.indexOf("--preferred-density") == -1);
- }
-
- public void testPost21ResourceConfigsAndPreferredDensity() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setResourceConfigs(ImmutableList.of("res1", "res2"))
- .setPreferredDensity("xhdpi");
-
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("21"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 21");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() < 21) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android-21");
- }
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(buildToolInfo, androidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertEquals("res1,res2", command.get(command.indexOf("-c") + 1));
- assertEquals("xhdpi", command.get(command.indexOf("--preferred-density") + 1));
- }
-
- public void testResConfigAndSplitConflict() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setResourceConfigs(
- ImmutableList.of("nodpi", "en", "fr", "mdpi", "hdpi", "xxhdpi", "xxxhdpi"))
- .setSplits(ImmutableList.of("xhdpi"))
- .setPreferredDensity("xhdpi");
-
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("21"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 21");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() < 21) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android-21");
- }
-
- try {
- aaptPackageProcessBuilder.build(buildToolInfo, androidTarget, mLogger);
- } catch(Exception expected) {
- assertEquals("Splits for densities \"xhdpi\" were configured, yet the resConfigs settings does not include such splits. The resulting split APKs would be empty.\n"
- + "Suggestion : exclude those splits in your build.gradle : \n"
- + "splits {\n"
- + " density {\n"
- + " enable true\n"
- + " exclude \"xhdpi\"\n"
- + " }\n"
- + "}\n"
- + "OR add them to the resConfigs list.", expected.getMessage());
- }
- }
-
- public void testResConfigAndSplitConflict2() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setResourceConfigs(ImmutableList.of("xxxhdpi"))
- .setSplits(ImmutableList.of("hdpi", "mdpi", "xxhdpi"))
- .setPreferredDensity("xhdpi");
-
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("21"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 21");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() < 21) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android-21");
- }
-
- try {
- aaptPackageProcessBuilder.build(buildToolInfo, androidTarget, mLogger);
- } catch(Exception expected) {
- assertEquals("Splits for densities \"hdpi,mdpi,xxhdpi\" were configured, yet the "
- + "resConfigs settings does not include such splits. The resulting split APKs "
- + "would be empty.\n"
- + "Suggestion : exclude those splits in your build.gradle : \n"
- + "splits {\n"
- + " density {\n"
- + " enable true\n"
- + " exclude \"hdpi\",\"mdpi\",\"xxhdpi\"\n"
- + " }\n"
- + "}\n"
- + "OR add them to the resConfigs list.", expected.getMessage());
- }
- }
-
- public void testResConfigAndSplitNoConflict() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setResourceConfigs(ImmutableList
- .of("en", "fr", "es", "de", "it", "mdpi", "hdpi", "xhdpi", "xxhdpi"))
- .setSplits(ImmutableList.of("mdpi", "hdpi", "xhdpi", "xxhdpi"));
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("20"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 20");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() >= 20) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android 20");
- }
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(buildToolInfo, androidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertEquals("en,fr,es,de,it,mdpi,hdpi,xhdpi,xxhdpi",
- command.get(command.indexOf("-c") + 1));
- assertTrue("--split".equals(command.get(command.indexOf("mdpi") - 1)));
- assertTrue("--split".equals(command.get(command.indexOf("hdpi") - 1)));
- assertTrue("--split".equals(command.get(command.indexOf("xhdpi") - 1)));
- assertTrue("--split".equals(command.get(command.indexOf("xxhdpi") - 1)));
- assertEquals(-1, command.indexOf("xxxhdpi"));
- }
-
- public void testResConfigWithPreferredDensityFlags() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setResourceConfigs(ImmutableList
- .of("en", "fr", "es", "de", "it", "mdpi", "hdpi", "xhdpi", "xxhdpi"));
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("21"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 21");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() >= 21) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android-21");
- }
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(buildToolInfo, androidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertEquals("en,fr,es,de,it",
- command.get(command.indexOf("-c") + 1));
- assertTrue("--preferred-density".equals(command.get(command.indexOf("mdpi") - 1)));
- assertTrue("--preferred-density".equals(command.get(command.indexOf("hdpi") -1 )));
- assertTrue("--preferred-density".equals(command.get(command.indexOf("xhdpi") -1 )));
- assertTrue("--preferred-density".equals(command.get(command.indexOf("xxhdpi") -1 )));
- assertEquals(-1, command.indexOf("xxxhdpi"));
- }
-
-
- public void testResConfigAndPreferredDensityConflict() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- .setResourceConfigs(ImmutableList.of( "en", "fr", "es", "de", "it", "mdpi", "hdpi", "xhdpi", "xxhdpi"))
- .setSplits(ImmutableList.of("mdpi", "hdpi", "xhdpi", "xxhdpi"))
- .setPreferredDensity("hdpi");
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("21"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 21");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() >= 21) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android-21");
- }
-
- try {
- aaptPackageProcessBuilder.build(buildToolInfo, androidTarget, mLogger);
- } catch (Exception expected) {
- assertEquals("When using splits in tools 21 and above, resConfigs should not contain "
- + "any densities. Right now, it contains \"mdpi\",\"hdpi\",\"xhdpi\",\"xxhdpi\"\n"
- + "Suggestion: remove these from resConfigs from build.gradle", expected.getMessage());
- }
- }
-
- public void testResConfigAndPreferredDensityNoConflict() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
- File assetsFolder = Mockito.mock(File.class);
- Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
- Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
- File resFolder = Mockito.mock(File.class);
- Mockito.when(resFolder.isDirectory()).thenReturn(true);
- Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
- .setAssetsFolder(assetsFolder)
- .setResFolder(resFolder)
- .setPackageForR("com.example.package.forR")
- .setSourceOutputDir("path/to/source/output/dir")
- .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
- .setType(VariantType.DEFAULT)
- // only languages, no density...
- .setResourceConfigs(ImmutableList.of("en", "fr", "es", "de", "it"))
- .setPreferredDensity("hdpi");
-
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), mLogger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision("21"));
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools 21");
- }
- IAndroidTarget androidTarget = null;
- for (IAndroidTarget iAndroidTarget : sdkManager.getTargets()) {
- if (iAndroidTarget.getVersion().getApiLevel() >= 21) {
- androidTarget = iAndroidTarget;
- break;
- }
- }
- if (androidTarget == null) {
- throw new RuntimeException("Test requires pre android-21");
- }
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(buildToolInfo, androidTarget, mLogger);
-
- List<String> command = processInfo.getArgs();
-
- assertEquals("en,fr,es,de,it", command.get(command.indexOf("-c") + 1));
- }
-
- public void testEnvironment() {
- File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
-
- AaptPackageProcessBuilder aaptPackageProcessBuilder =
- new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
- aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir");
-
- // add an env to the builder
- aaptPackageProcessBuilder.addEnvironment("foo", "bar");
-
- ProcessInfo processInfo = aaptPackageProcessBuilder
- .build(mBuildToolInfo, mIAndroidTarget, mLogger);
-
- Map<String, Object> env = processInfo.getEnvironment();
- assertEquals(1, env.size());
- assertNotNull(env.get("foo"));
- assertEquals("bar", env.get("foo"));
- }
-
- /**
- * Returns the SDK folder as built from the Android source tree.
- * @return the SDK
- */
- @NonNull
- protected File getSdkDir() {
- String androidHome = System.getenv("ANDROID_HOME");
- if (androidHome != null) {
- File f = new File(androidHome);
- if (f.isDirectory()) {
- return f;
- }
- }
-
- throw new IllegalStateException("SDK not defined with ANDROID_HOME");
- }
-}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java b/base/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java
deleted file mode 100644
index 98794cc..0000000
--- a/base/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.internal.compiler;
-
-import static com.android.SdkConstants.FN_AAPT;
-import static com.android.SdkConstants.FN_AIDL;
-import static com.android.SdkConstants.FN_BCC_COMPAT;
-import static com.android.SdkConstants.FN_DX;
-import static com.android.SdkConstants.FN_DX_JAR;
-import static com.android.SdkConstants.FN_RENDERSCRIPT;
-import static com.android.SdkConstants.FN_ZIPALIGN;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.core.DexOptions;
-import com.android.ide.common.process.JavaProcessExecutor;
-import com.android.ide.common.process.JavaProcessInfo;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessOutput;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.ide.common.process.ProcessResult;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.zip.ZipEntry;
-
-public class PreDexCacheTest extends TestCase {
-
- private static final String DEX_DATA = "**";
-
- /**
- * implement a fake java process executor to intercept the call to dex and replace it
- * with something else.
- */
- private static class FakeJavaProcessExecutor implements JavaProcessExecutor {
-
- @NonNull
- @Override
- public ProcessResult execute(
- @NonNull JavaProcessInfo javaProcessInfo,
- @NonNull ProcessOutputHandler processOutputHandler) {
-
- List<String> command = javaProcessInfo.getArgs();
-
- ProcessException processException = null;
-
- try {
- // small delay to test multi-threading.
- Thread.sleep(1000);
-
- // input file is the last file in the command
- File input = new File(command.get(command.size() - 1));
- if (!input.isFile()) {
- throw new FileNotFoundException(input.getPath());
- }
-
- // loop on the command to find --output
- String output = null;
- for (int i = 0; i < command.size(); i++) {
- if ("--output".equals(command.get(i))) {
- output = command.get(i + 1);
- break;
- }
- }
-
- if (output == null) {
- throw new IOException("Failed to find output in dex commands");
- }
-
- // read the source content
- JarFile jarFile = new JarFile(input);
- JarEntry jarEntry = jarFile.getJarEntry("content.class");
- assert jarEntry != null;
- InputStream contentStream = jarFile.getInputStream(jarEntry);
- byte[] content = new byte[256];
- int read = contentStream.read(content);
- contentStream.close();
- jarFile.close();
-
- String line = new String(content, 0, read, Charsets.UTF_8);
-
- // write it
- Files.write(DEX_DATA + line + DEX_DATA, new File(output), Charsets.UTF_8);
- } catch (Exception e) {
- //noinspection ThrowableInstanceNeverThrown
- processException = new ProcessException(null, e);
- }
-
- final ProcessException rethrow = processException;
- return new ProcessResult() {
- @Override
- public ProcessResult assertNormalExitValue() throws ProcessException {
- return this;
- }
-
- @Override
- public int getExitValue() {
- return 0;
- }
-
- @Override
- public ProcessResult rethrowFailure() throws ProcessException {
- if (rethrow != null) {
- throw rethrow;
- }
- return this;
- }
- };
- }
- }
-
- /**
- * Fake executor that fails to execute
- */
- private static class FailingExecutor implements JavaProcessExecutor {
-
- @NonNull
- @Override
- public ProcessResult execute(@NonNull JavaProcessInfo javaProcessInfo,
- @NonNull ProcessOutputHandler processOutputHandler) {
- try {
- Thread.sleep(1000);
- throw new IOException("foo");
- } catch (final Exception e) {
- return new ProcessResult() {
- @Override
- public ProcessResult assertNormalExitValue() throws ProcessException {
- return this;
- }
-
- @Override
- public int getExitValue() {
- return 0;
- }
-
- @Override
- public ProcessResult rethrowFailure() throws ProcessException {
- throw new ProcessException(null, e);
- }
- };
- }
- }
- }
-
- private static class FakeProcessOutputHandler implements ProcessOutputHandler {
-
- @NonNull
- @Override
- public ProcessOutput createOutput() {
- return null;
- }
-
- @Override
- public void handleOutput(@NonNull ProcessOutput processOutput) throws ProcessException {
-
- }
- }
-
- private static class FakeDexOptions implements DexOptions {
-
- @Override
- public boolean getIncremental() {
- return false;
- }
-
- @Override
- public boolean getPreDexLibraries() {
- return false;
- }
-
- @Override
- public boolean getJumboMode() {
- return false;
- }
-
- @Override
- @Nullable
- public String getJavaMaxHeapSize() {
- return null;
- }
-
- @Override
- @Nullable
- public Integer getThreadCount() {
- return null;
- }
- }
-
- private BuildToolInfo mBuildToolInfo;
-
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mBuildToolInfo = getBuildToolInfo();
- }
-
- @Override
- protected void tearDown() throws Exception {
- File toolFolder = mBuildToolInfo.getLocation();
- deleteFolder(toolFolder);
-
- PreDexCache.getCache().clear(null, null);
-
- super.tearDown();
- }
-
- public void testSinglePreDexLibrary() throws IOException, ProcessException, InterruptedException {
- String content = "Some Content";
- File input = createInputFile(content);
-
- File output = File.createTempFile("predex", ".jar");
- output.deleteOnExit();
-
- PreDexCache.getCache().preDexLibrary(
- input, output,
- false /*multidex*/,
- new FakeDexOptions(), mBuildToolInfo,
- false /*verbose*/, new FakeJavaProcessExecutor(), new FakeProcessOutputHandler());
-
- checkOutputFile(content, output);
- }
-
- public void testThreadedPreDexLibrary() throws IOException, InterruptedException {
- String content = "Some Content";
- final File input = createInputFile(content);
- input.deleteOnExit();
-
- Thread[] threads = new Thread[3];
- final File[] outputFiles = new File[threads.length];
-
- final JavaProcessExecutor javaProcessExecutor = new FakeJavaProcessExecutor();
- final DexOptions dexOptions = new FakeDexOptions();
-
- for (int i = 0 ; i < threads.length ; i++) {
- final int ii = i;
- threads[i] = new Thread() {
- @Override
- public void run() {
- try {
- File output = File.createTempFile("predex", ".jar");
- output.deleteOnExit();
- outputFiles[ii] = output;
-
- PreDexCache.getCache().preDexLibrary(
- input,
- output,
- false /*multidex*/,
- dexOptions,
- mBuildToolInfo,
- false /*verbose*/,
- javaProcessExecutor,
- new FakeProcessOutputHandler());
- } catch (Exception ignored) {
-
- }
- }
- };
-
- threads[i].start();
- }
-
- // wait on the threads.
- for (Thread thread : threads) {
- thread.join();
- }
-
- // check the output.
- for (File outputFile : outputFiles) {
- checkOutputFile(content, outputFile);
- }
-
- // now check the cache
- PreDexCache cache = PreDexCache.getCache();
- assertEquals(1, cache.getMisses());
- assertEquals(threads.length - 1, cache.getHits());
- }
-
- public void testThreadedPreDexLibraryWithError() throws IOException, InterruptedException {
- String content = "Some Content";
- final File input = createInputFile(content);
- input.deleteOnExit();
-
- Thread[] threads = new Thread[3];
- final File[] outputFiles = new File[threads.length];
-
- final JavaProcessExecutor javaProcessExecutor = new FakeJavaProcessExecutor();
- final JavaProcessExecutor javaProcessExecutorWithError = new FailingExecutor();
- final DexOptions dexOptions = new FakeDexOptions();
-
- final AtomicInteger threadDoneCount = new AtomicInteger();
-
- for (int i = 0 ; i < threads.length ; i++) {
- final int ii = i;
- threads[i] = new Thread() {
- @Override
- public void run() {
- try {
- File output = File.createTempFile("predex", ".jar");
- output.deleteOnExit();
- outputFiles[ii] = output;
-
- PreDexCache.getCache().preDexLibrary(
- input,
- output,
- false /*multidex*/,
- dexOptions,
- mBuildToolInfo,
- false /*verbose*/,
- ii == 0 ? javaProcessExecutorWithError : javaProcessExecutor,
- new FakeProcessOutputHandler());
- } catch (Exception ignored) {
-
- }
- threadDoneCount.incrementAndGet();
- }
- };
-
- threads[i].start();
- }
-
- // wait on the threads, long enough but stop after a while
- for (Thread thread : threads) {
- thread.join(5000);
- }
-
- // if the test fail, we'll have two threads still blocked on the countdownlatch.
- assertEquals(3, threadDoneCount.get());
- }
-
-
- public void testReload() throws IOException, ProcessException, InterruptedException {
- final JavaProcessExecutor javaProcessExecutor = new FakeJavaProcessExecutor();
- final DexOptions dexOptions = new FakeDexOptions();
-
- // convert one file.
- String content = "Some Content";
- File input = createInputFile(content);
-
- File output = File.createTempFile("predex", ".jar");
- output.deleteOnExit();
-
- PreDexCache.getCache().preDexLibrary(
- input,
- output,
- false /*multidex*/,
- dexOptions,
- mBuildToolInfo,
- false /*verbose*/,
- javaProcessExecutor,
- new FakeProcessOutputHandler());
-
- checkOutputFile(content, output);
-
- // store the cache
- File cacheXml = File.createTempFile("predex", ".xml");
- cacheXml.deleteOnExit();
- PreDexCache.getCache().clear(cacheXml, null);
-
- // reload.
- PreDexCache.getCache().load(cacheXml);
-
- // re-pre-dex into another file.
- File output2 = File.createTempFile("predex", ".jar");
- output2.deleteOnExit();
-
- PreDexCache.getCache().preDexLibrary(
- input,
- output2,
- false /*multidex*/,
- dexOptions,
- mBuildToolInfo,
- false /*verbose*/,
- javaProcessExecutor,
- new FakeProcessOutputHandler());
-
- // check the output
- checkOutputFile(content, output2);
-
- // check the hit/miss
- PreDexCache cache = PreDexCache.getCache();
- assertEquals(0, cache.getMisses());
- assertEquals(1, cache.getHits());
- }
-
- private static File createInputFile(String content) throws IOException {
- File input = File.createTempFile("predex", ".jar");
- input.deleteOnExit();
-
- JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(input));
- try {
- jarOutputStream.putNextEntry(new ZipEntry("content.class"));
- jarOutputStream.write(content.getBytes(Charsets.UTF_8));
- jarOutputStream.closeEntry();
- } finally {
- jarOutputStream.close();
- }
-
- return input;
- }
-
- private static void checkOutputFile(String content, File output) throws IOException {
- List<String> lines = Files.readLines(output, Charsets.UTF_8);
-
- assertEquals(1, lines.size());
- assertEquals(DEX_DATA + content + DEX_DATA, lines.get(0));
- }
-
- /**
- * Create a fake build tool info where the dx tool actually exists (even if it's not used).
- */
- private static BuildToolInfo getBuildToolInfo() throws IOException {
- File toolDir = Files.createTempDir();
-
- // create a dx.jar file.
- File dx = new File(toolDir, FN_DX_JAR);
- Files.write("dx!", dx, Charsets.UTF_8);
-
- return new BuildToolInfo(
- new FullRevision(1),
- toolDir,
- new File(toolDir, FN_AAPT),
- new File(toolDir, FN_AIDL),
- new File(toolDir, FN_DX),
- dx,
- new File(toolDir, FN_RENDERSCRIPT),
- new File(toolDir, "include"),
- new File(toolDir, "clang-include"),
- new File(toolDir, FN_BCC_COMPAT),
- new File(toolDir, "arm-linux-androideabi-ld"),
- new File(toolDir, "i686-linux-android-ld"),
- new File(toolDir, "mipsel-linux-android-ld"),
- new File(toolDir, FN_ZIPALIGN));
- }
-
- private static void deleteFolder(File folder) {
- File[] files = folder.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- deleteFolder(file);
- } else {
- file.delete();
- }
- }
- }
-
- folder.delete();
- }
-}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTest.java b/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTest.java
deleted file mode 100644
index b53d5b0..0000000
--- a/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.png;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.internal.AaptCruncher;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.ide.common.internal.PngException;
-import com.android.ide.common.process.DefaultProcessExecutor;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.process.ProcessExecutor;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-import com.google.common.collect.Maps;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.zip.DataFormatException;
-
- at RunWith(Parameterized.class)
-public class NinePatchAaptProcessorTest {
-
- private static Map<File, File> mSourceAndCrunchedFiles;
-
- private static AtomicLong sClassStartTime = new AtomicLong();
- private static final AtomicInteger sCruncherKey = new AtomicInteger();
- private static final PngCruncher sCruncher = getCruncher();
-
- private final File mFile;
-
- public NinePatchAaptProcessorTest(File file, String testName) {
- mFile = file;
- }
-
- @BeforeClass
- public static void setup() {
- mSourceAndCrunchedFiles = Maps.newHashMap();
- sCruncherKey.set(sCruncher.start());
- }
-
- @Test
- public void run() throws PngException, IOException {
- File outFile = NinePatchAaptProcessorTestUtils.crunchFile(
- sCruncherKey.get(), mFile, sCruncher);
- mSourceAndCrunchedFiles.put(mFile, outFile);
- }
-
-
- @AfterClass
- public static void tearDownAndCheck() throws IOException, DataFormatException {
- NinePatchAaptProcessorTestUtils.tearDownAndCheck(
- sCruncherKey.get(), mSourceAndCrunchedFiles, sCruncher, sClassStartTime);
- mSourceAndCrunchedFiles = null;
- }
-
- @NonNull
- private static PngCruncher getCruncher() {
- ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
- ProcessExecutor processExecutor = new DefaultProcessExecutor(logger);
- ProcessOutputHandler processOutputHandler = new LoggedProcessOutputHandler(logger);
- File aapt = NinePatchAaptProcessorTestUtils.getAapt(FullRevision.parseRevision("21"));
- return new AaptCruncher(aapt.getAbsolutePath(), processExecutor, processOutputHandler);
- }
-
- @Parameterized.Parameters(name = "{1}")
- public static Collection<Object[]> getNinePatches() {
- Collection<Object[]> params = NinePatchAaptProcessorTestUtils.getNinePatches();
- sClassStartTime.set(System.currentTimeMillis());
- return params;
- }
-}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTestUtils.java b/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTestUtils.java
deleted file mode 100644
index ddbe625..0000000
--- a/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTestUtils.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.png;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertEquals;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.ide.common.internal.PngException;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.FullRevision;
-import com.android.testutils.TestUtils;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
-import com.google.common.io.Files;
-
-import org.junit.Assert;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.zip.DataFormatException;
-
-import javax.imageio.ImageIO;
-
-/**
- * Utilities common to tests for both the synchronous and the asynchronous Aapt processor.
- */
-public class NinePatchAaptProcessorTestUtils {
-
- /**
- * Signature of a PNG file.
- */
- public static final byte[] SIGNATURE = new byte[]{
- (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
-
- /**
- * Returns the lastest build tools that's at least the passed version.
- * @param fullRevision the minimum required build tools version.
- * @return the latest build tools.
- * @throws RuntimeException if the latest build tools is older than fullRevision.
- */
- static File getAapt(FullRevision fullRevision) {
- ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
- SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), logger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getLatestBuildTool();
- if (buildToolInfo == null || buildToolInfo.getRevision().compareTo(fullRevision) < 0) {
- throw new RuntimeException("Test requires build-tools " + fullRevision.toShortString());
- }
- return new File(buildToolInfo.getPath(BuildToolInfo.PathId.AAPT));
- }
-
-
- public static void tearDownAndCheck(int cruncherKey, Map<File, File> sourceAndCrunchedFiles,
- PngCruncher cruncher, AtomicLong classStartTime)
- throws IOException, DataFormatException {
- long startTime = System.currentTimeMillis();
- try {
- cruncher.end(cruncherKey);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(
- "waiting for requests completion : " + (System.currentTimeMillis() - startTime));
- System.out.println("total time : " + (System.currentTimeMillis() - classStartTime.get()));
- System.out.println("Comparing crunched files");
- long comparisonStartTime = System.currentTimeMillis();
- for (Map.Entry<File, File> sourceAndCrunched : sourceAndCrunchedFiles.entrySet()) {
- System.out.println(sourceAndCrunched.getKey().getName());
- File crunched = new File(sourceAndCrunched.getKey().getParent(),
- sourceAndCrunched.getKey().getName() + getControlFileSuffix());
-
- //copyFile(sourceAndCrunched.getValue(), crunched);
- Map<String, Chunk> testedChunks = compareChunks(crunched, sourceAndCrunched.getValue());
-
- try {
- compareImageContent(crunched, sourceAndCrunched.getValue(), false);
- } catch (Throwable e) {
- throw new RuntimeException("Failed with " + testedChunks.get("IHDR"), e);
- }
- }
- System.out.println("Done comparing crunched files " + (System.currentTimeMillis()
- - comparisonStartTime));
- }
-
- protected static String getControlFileSuffix() {
- return ".crunched.aapt";
- }
-
- private static void copyFile(File source, File dest)
- throws IOException {
- FileChannel inputChannel = null;
- FileChannel outputChannel = null;
- try {
- inputChannel = new FileInputStream(source).getChannel();
- outputChannel = new FileOutputStream(dest).getChannel();
- outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
- } finally {
- inputChannel.close();
- outputChannel.close();
- }
- }
-
-
- @NonNull
- static File crunchFile(int crunchKey, @NonNull File file, PngCruncher aaptCruncher)
- throws PngException, IOException {
- File outFile = File.createTempFile("pngWriterTest", ".png");
- outFile.deleteOnExit();
- try {
- aaptCruncher.crunchPng(crunchKey, file, outFile);
- } catch (PngException e) {
- e.printStackTrace();
- throw e;
- }
- System.out.println("crunch " + file.getPath());
- return outFile;
- }
-
-
- private static Map<String, Chunk> compareChunks(@NonNull File original, @NonNull File tested)
- throws
- IOException, DataFormatException {
- Map<String, Chunk> originalChunks = readChunks(original);
- Map<String, Chunk> testedChunks = readChunks(tested);
-
- compareChunk(originalChunks, testedChunks, "IHDR");
- compareChunk(originalChunks, testedChunks, "npLb");
- compareChunk(originalChunks, testedChunks, "npTc");
-
- return testedChunks;
- }
-
- private static void compareChunk(
- @NonNull Map<String, Chunk> originalChunks,
- @NonNull Map<String, Chunk> testedChunks,
- @NonNull String chunkType) {
- assertEquals(originalChunks.get(chunkType), testedChunks.get(chunkType));
- }
-
- public static Collection<Object[]> getNinePatches() {
- File pngFolder = getPngFolder();
- File ninePatchFolder = new File(pngFolder, "ninepatch");
-
- File[] files = ninePatchFolder.listFiles(new FileFilter() {
- @Override
- public boolean accept(File file) {
- return file.getPath().endsWith(SdkConstants.DOT_9PNG);
- }
- });
- if (files != null) {
- ImmutableList.Builder<Object[]> params = ImmutableList.builder();
- for (File file : files) {
- params.add(new Object[]{file, file.getName()});
- }
- return params.build();
- }
-
- return ImmutableList.of();
- }
-
- protected static void compareImageContent(@NonNull File originalFile, @NonNull File createdFile,
- boolean is9Patch)
- throws IOException {
- BufferedImage originalImage = ImageIO.read(originalFile);
- BufferedImage createdImage = ImageIO.read(createdFile);
-
- int originalWidth = originalImage.getWidth();
- int originalHeight = originalImage.getHeight();
-
- int createdWidth = createdImage.getWidth();
- int createdHeight = createdImage.getHeight();
-
- // compare sizes taking into account if the image is a 9-patch
- // in which case the original is bigger by 2 since it has the patch area still.
- Assert.assertEquals(originalWidth, createdWidth + (is9Patch ? 2 : 0));
- Assert.assertEquals(originalHeight, createdHeight + (is9Patch ? 2 : 0));
-
- // get the file content
- // always use the created Size. And for the original image, if 9-patch, just take
- // the image minus the 1-pixel border all around.
- int[] originalContent = new int[createdWidth * createdHeight];
- if (is9Patch) {
- originalImage
- .getRGB(1, 1, createdWidth, createdHeight, originalContent, 0, createdWidth);
- } else {
- originalImage
- .getRGB(0, 0, createdWidth, createdHeight, originalContent, 0, createdWidth);
- }
-
- int[] createdContent = new int[createdWidth * createdHeight];
- createdImage.getRGB(0, 0, createdWidth, createdHeight, createdContent, 0, createdWidth);
-
- for (int y = 0; y < createdHeight; y++) {
- for (int x = 0; x < createdWidth; x++) {
- int originalRGBA = originalContent[y * createdWidth + x];
- int createdRGBA = createdContent[y * createdWidth + x];
- Assert.assertEquals(
- String.format("%dx%d: 0x%08x : 0x%08x", x, y, originalRGBA, createdRGBA),
- originalRGBA,
- createdRGBA);
- }
- }
- }
-
- @NonNull
- protected static Map<String, Chunk> readChunks(@NonNull File file) throws IOException {
- Map<String, Chunk> chunks = Maps.newHashMap();
-
- byte[] fileBuffer = Files.toByteArray(file);
- ByteBuffer buffer = ByteBuffer.wrap(fileBuffer);
-
- byte[] sig = new byte[8];
- buffer.get(sig);
-
- assertTrue(Arrays.equals(sig, SIGNATURE));
-
- byte[] data, type;
- int len;
- int crc32;
-
- while (buffer.hasRemaining()) {
- len = buffer.getInt();
-
- type = new byte[4];
- buffer.get(type);
-
- data = new byte[len];
- buffer.get(data);
-
- // crc
- crc32 = buffer.getInt();
-
- Chunk chunk = new Chunk(type, data, crc32);
- chunks.put(chunk.getTypeAsString(), chunk);
- }
-
- return chunks;
- }
-
- /**
- * Returns the SDK folder as built from the Android source tree.
- *
- * @return the SDK
- */
- @NonNull
- protected static File getSdkDir() {
- String androidHome = System.getenv("ANDROID_HOME");
- if (androidHome != null) {
- File f = new File(androidHome);
- if (f.isDirectory()) {
- return f;
- }
- }
-
- throw new IllegalStateException("SDK not defined with ANDROID_HOME");
- }
-
- @NonNull
- protected static File getFile(@NonNull String name) {
- return new File(getPngFolder(), name);
- }
-
- @NonNull
- protected static File getPngFolder() {
- File folder = TestUtils.getRoot("png");
- assertTrue(folder.isDirectory());
- return folder;
- }
-}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAsyncAaptProcessTest.java b/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAsyncAaptProcessTest.java
deleted file mode 100644
index ab5a3f1..0000000
--- a/base/build-system/builder/src/test/java/com/android/builder/png/NinePatchAsyncAaptProcessTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.png;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.ide.common.internal.PngException;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-import com.google.common.collect.Maps;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.zip.DataFormatException;
-
-/**
- * Asynchronous version of the aapt cruncher test.
- */
- at RunWith(Parameterized.class)
-public class NinePatchAsyncAaptProcessTest {
-
- private static Map<File, File> mSourceAndCrunchedFiles;
-
- private static final AtomicLong sClassStartTime = new AtomicLong();
- private static final AtomicInteger sCruncherKey = new AtomicInteger();
- private static final PngCruncher sCruncher = getCruncher();
-
- private final File mFile;
-
- public NinePatchAsyncAaptProcessTest(File file, String testName) {
- mFile = file;
- }
-
- @BeforeClass
- public static void setup() {
- mSourceAndCrunchedFiles = Maps.newHashMap();
- }
-
- @Test
- public void run() throws PngException, IOException {
- File outFile = NinePatchAaptProcessorTestUtils.crunchFile(
- sCruncherKey.get(), mFile, sCruncher);
- mSourceAndCrunchedFiles.put(mFile, outFile);
- }
-
- @AfterClass
- public static void tearDownAndCheck()
- throws IOException, DataFormatException, InterruptedException {
-
- NinePatchAaptProcessorTestUtils.tearDownAndCheck(
- sCruncherKey.get(), mSourceAndCrunchedFiles, sCruncher, sClassStartTime);
- mSourceAndCrunchedFiles = null;
- }
-
- @NonNull
- private static PngCruncher getCruncher() {
- ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
- File aapt = NinePatchAaptProcessorTestUtils.getAapt(FullRevision.parseRevision("22.0.1"));
- return QueuedCruncher.Builder.INSTANCE.newCruncher(aapt.getAbsolutePath(), logger);
- }
-
- @Parameters(name = "{1}")
- public static Collection<Object[]> getNinePatches() {
- Collection<Object[]> params = NinePatchAaptProcessorTestUtils.getNinePatches();
- sClassStartTime.set(System.currentTimeMillis());
- sCruncherKey.set(sCruncher.start());
- return params;
- }
-}
\ No newline at end of file
diff --git a/base/build-system/changelog.txt b/base/build-system/changelog.txt
deleted file mode 100644
index 10ec3ec..0000000
--- a/base/build-system/changelog.txt
+++ /dev/null
@@ -1,649 +0,0 @@
-1.3.0
-- By default, "LICENSE" and "LICENSE.txt" are excluded when creating an APK.
- This can be changed from the DSL:
-
- android {
- packagingOptions.excludes = []
- }
-
-- New sourceSets task for inspecting the set of all available source sets.
-- Unit tests recognize multi-flavor and per-variant source folders (e.g.
- testDemoDebug). Android tests recognized multi-flavor source folders.
-- Unit testing improvements
- * Run javac on main and test sources, even if useJack is true.
- * Correctly recognize per-build-type dependencies.
-- It's now possible to specify instrumentation test runner arguments in
- build.gradle (in defaultConfig or per flavor):
-
- android {
- defaultConfig {
- testInstrumentationRunnerArguments size: "medium"
- }
-
- productFlavors {
- foo {
- testInstrumentationRunnerArguments foo: "bar"
- }
- }
- }
-
- or from the command line:
-
- ./gradlew cC \
- -Pandroid.testInstrumentationRunnerArguments.size=medium \
- -Pandroid.testInstrumentationRunnerArguments.class=TestA,TestB
-
-- Arbitrary additional AAPT parameters can be set in build.gradle:
- android {
- aaptOptions {
- additionalParameters "--custom_option", "value"
- }
- }
-- Resource names are validated before they are merged.
-- When building aar, do not provide automatic @{applicationId} placeholder
- in manifest merger. Use a different placeholder like @{libApplicationId}
- and provide a value for it if applicationIds should be baked in the library.
-- Introduce support for incremental compilation support with Jill and Jack. Change is purely
- internal and does not require DSL change nor can it be disabled.
-
-1.2.0
-- Unit testing improvements
- * Fixed task dependencies for library projects, so test classes should now
- be up-to-date when running tests.
- * Java-style resources are now put on the class path when running unit tests
- through Gradle.
- * Unit test configurations (e.g. testCompile) can now depend on AAR
- artifacts.
- * Fixes to mockable-android.jar: correct handling of enums, stripping the
- final modifier of public instance fields.
- * DSL: new code block for configuring the test tasks:
- android {
- testOptions {
- unitTests.all {
- jvmArgs '-XX:MaxPermSize=256m' // Or any other gradle option.
- }
- }
- }
-
- * Variants API: unit-testing variants are now exposed in the API and can be
- manipulated (e.g. by calling addJavaSourceFoldersToModel).
- android {
- unitTestVariants.all { ... }
- applicationVariants.all { v -> v.unitTestVariant }
- }
-- Test-only ProGuard files. When running instrumentation tests (i.e. connectedCheck) against
- a minified variant, the test APK needs to be processed by ProGuard to rename references to code
- in the main APK. Flags for this ProGuard run (mostly for silencing warnings) can now be specified
- like this:
- android {
- testBuildType = "minified"
- buildTypes {
- minified.initWith(buildTypes.debug)
- minified {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard-rules.pro"
- testProguardFile "test-proguard-rules.pro"
- }
- }
- }
-
-1.1.0
-- Unit testing support. Unit testing code is run on the local JVM, against a
- special version of android.jar that is compatible with popular mocking
- frameworks (e.g. Mockito).
- * New tasks: test, testDebug/testRelease, testMyFlavorDebug (when using flavors).
- * New source folders recognized as unit tests:
- src/test/java, src/testDebug/java, src/testMyFlavor/java etc.
- * New configurations for adding test-only dependencies, e.g.
- testCompile 'junit:junit:4.11'
- testMyFlavorCompile 'some:library:1.0'
- * New option, android.testOptions.unitTests.returnDefaultValues to control
- the behaviour of the "mockable" android.jar.
-- Task names that used to contain 'Test', e.g. 'assembleDebugTest' now use
- 'AndroidTest', e.g. 'assembleDebugAndroidTest'. This is to distinguish them
- from the unit test tasks, e.g. 'assembleDebugUnitTest'.
-- ProGuard configuration files are no longer applied to the test APK. If
- minification is enabled, the test APK will be processed by ProGuard only to apply
- the mapping file generated when minifying the main APK.
-- Fixes and changes to the dependency management:
- * Properly handle 'provided' and 'package' scopes to do what they should be doing.
- * 'provided' and 'package' cannot be used with Android Libraries, and will generate an error
- * sync tested and test dependency trees:
- - if the same version of an artifact is present in both, it'll get skipped in the test app.
- - if the version is different it'll generate a build error. Gradle provides mechanism to resolve this.
-- Made queue based cruncher the default png cruncher which should bring significant performance
- improvement when crunching multiple png files.
- To turn it off :
- android {
- aaptOptions {
- useNewCruncher false
- }
- }
-- Improved DSL reference. See http://developer.android.com/tools/building/plugin-for-gradle.html
-
-1.0.0
-- Final 1.0.0 version
-
-1.0.0-rc2
-- Enhanced manifest merger logging by specifying library coordinates.
-- Allow manifest placeholder to be of any type as long as toString() is implemented.
-- Fixed issue where a library with a low targetSdk would add permissions due to a declared permission in a different manifest.
-- Better fix for issue where embedding a micro app could add new permissions to the main app manifest.
-- Added check for conflict between density splits and resConfig property.
-- test applications are now not using multi-dexing, unless they test a library project.
-- Fixed lint issues 80872, 80834, 60416, 80837
-
-1.0.0-rc1
-- Fixed issue in resources shrinking
-- Fixed issue in publishNonDefault
-- Install task on 21+ devices now does a reinstall again.
-- Density split using aapt 21+ now use --preferred-density allowing for missing density version of some bitmaps.
-- hasProperty() will now work again on read-only wrapper returned by the variant API.
-- Setting applicationId(Suffix) in a Library project will now properly fail.
-- Fixed issue where embedding a micro app could add new permissions to the main app manifest.
-
-0.14.3
-- Variant Specific BuildConfigField/resValue
- applicationVariants.all { variant ->
- variant.buildConfigField "int", "VALUE", "1"
- variant.resValue "string", "name", "value"
- }
-- Variant (and multi-flavor) specific dependency configuration
- multi-flavor is all the flavors without the build Type. Only exists for 2+ dimensions of Flavors.
- Current limitation: Requires defining the configuration manually first:
- configurations {
- fooDebugCompile
- }
-
- android {
- productFlavors {
- foo { ... }
- }
- }
-
- dependencies {
- fooDebugCompile '...'
- }
-
-- BuildType/Flavor/Variant configuration for embedding wear app (<name>WearApp)
-- Upgrade to Proguard 5.1
-- Almost 1.0: removed deprecated properties/methods
- * BuildConfig.PACKAGE_NAME (use new field name)
- * android.flavorGroups (use new property names)
- * ProductFlavor.packageName/flavorGroup/testPackageName/renderscriptSupportMode (use new property name)
- * BuildType.runProguard/packageNameSuffix/jniDebugBuild/renderscriptDebugBuild/zipAlign (use the new property name)
- * Variant.packageApplication/zipAlign/createZipAlignTask/outputFile/processResources/processManifest (use the variant output)
-
-0.14.2
-- Fix versionNameSuffix support
-- Fix BuildType.initWith to copy shrinkResources flag
-- setup default proguard rule file if none are provided (SDK/tools/proguard/proguard-android.txt)
-- BuildType.pseudoLocalesEnabled flag to include fake locales in apk.
-
-
-0.14.1
-- Fix coverage support.
-- Fix resource shrinking for style references
-- Exclude embedded Wear micro-app from resource shrinking.
-
-0.14.0
-- Proguard and code coverage can now work together
-- Support for pulling coverage data from Android 5.0 devices
-- Env var ANDROID_SERIAL (if present) restrict installation/execution of tests to device matching the serial number
-- Multi-Dex support.
- * Requires Build-Tools 21.1.0, and Support repository rev 8.
- * multiDexEnabled = true on defaultConfig, ProductFlavor, or BuildType
- * Works for minSdkVersion 21+ (native) or <21 (legacy mode, with automatic dependency on com.android.support:multidex:1.0.0)
- * See multidex samples.
-- Support for automatic removal of unused resources
- * Off by default for now, enable by setting shrinkResources to true in your
- release build types. Requires minifyEnabled as well.
-- DSL/API changes:
- * Renamed a few properties to make things more consistent.
- - BuildType.runProguard -> minifyEnabled
- - BuildType.zipAlign -> zipAlignEnabled
- - BuildType.jniDebugBuild -> jniDebuggable
- - BuildType.renderscriptDebug -> renderscriptDebuggable
- - ProductFlavor.renderscriptSupportMode -> renderscriptSupportModeEnabled
- - ProductFlavor.renderscriptNdkMode -> renderscriptNdkModeEnabled
- * BuildType/ProductFlavor/SigningConfig queried through the variant and variantFilter API are now read-only.
- - These objects have always been global and changing them would have side effects in other variants
- - Merged flavor is still per-variant and can me modified
- * Variant / VariantOutput API change
- - Getting the value of the density or ABI filter is done with
- output.getFilter(com.android.build.OutputFile.DENSITY)
- output.getFilter(com.android.build.OutputFile.ABI)
- - See densitySplit sample
-
-0.13.3
-- Added support for selectively allowing dependencies on libraries with incompatible uses-sdk
-- Fixed race condition in lint's resource folder cache which could trigger a build failure
-
-0.13.2
-- Fixed issue in manifest merger that could put wrong uses-sdk node in the manifest.
-
-0.13.1
-- Added ability to merge Instrumentation element from test
-- Fix uninstallAll task
-- Fix issue where bad configuration could lead to no outputs on variants which would prevent evaluation of the project.
-- connectedCheck will now fail if no tests are found.
-
-0.13.0 (2014/09/18)
-- Requires Gradle 2.1
-- It is now possible to provide a manifest for test apps (src/androidTest/AndroidManifest.xml)
-- AndroidManifest files in Library project can now include placeholders. If they cannot be resolved
- in the libraries, they'll be resolved in the consuming project.
-- AndroidManifest placeholder can now be setup on Product Flavors and Build Types.
-- Variant.getMappingFile() API now allow querying for the proguard mapping file.
-- New Split mechanism for Density and ABI driven multi-apk.
-- Bug fixes:
- * Fix issue where consumer proguard file (from aars) are ignored on first build
- * Fixed aar output names so that variants do not overwrite each other
- * Properly merge declare-styleable to contain all attrs.
- * Fix whitespace issue in resource strings during resource merge.
-
-0.12.2 (2014/07/16)
-- Fix packaging of wear application
-- Fix issue with ${applicationId} placeholder when build.gradle doesn't customize it.
-- Custom Java code generation steps now part of the source generation steps (fix IDE integration).
-- Move unzipped aar back in each project as a temporary fix for a possible race condition.
-
-0.12.1 (2014/07/01)
-- Fix merging of the package attribute in the manifest.
-
-0.12.0 (2014/06/25)
-- New IDE Model, requires Studio Beta 0.8
-- Fixes in the manifest mergers.
-
-0.11.1:
-- Fix issue with artifact depending on android.jar artifact on MavenCentral.
-- Fix issue with missing custom namespace declaration in generated manifest.
-- Fix issue with validation of permission group in manifest merger.
-
-0.11.0:
-- Updated IDE model, requires Studio 0.6
-- New Manifest merger is now the default merger.
- - lots of fixes
- - added ability to add custom placeholders for merger.
-
-- Replaced the various DSL properties used to define the "package
- name" with an "application ID", to decouple the persistent ID of the
- application from the implementation package used to contain for
- example the R and BuildConfig classes.
- packageName => applicationId
- packageNameSuffix => applicationIdSuffix
- testPackageName => testApplicationId
- testedPackageName => testedApplicationId
-- min/targetSdkVersion on ProductFlavor is now a ApiVersion which contains both an integer and a string.
-- DSL impact: cannot use setter: flavor {minSdkVersion = 9}, must use method: flavor { minSdkVersion 9}, due to a groovy limitation preventing overloaded setters.
-
-- Moved files and folders around in the buildDir for better IDE integration.
-- Generated APK can now be published. Same configuration as libraries with defaultPublishConfig and publishNonDefault flags.
-
-
-0.10.2:
-
-- More fixes on the Manifest merger, including better handling of minSdkVersion.
-- More lint fixes.
-- Fixed incremental dex support (still needs to be enabled)
-
-0.10.1:
-
-- fixed some issues with the new manifest merger. Please keep sending us feedback.
-- fixed issue with uninstall task.
-- lots of lint fixes and new checks. For instance you can use lint to enforce resource prefix in your library.
-
-0.10.0:
-- New manifest merger
-- test code coverage support with Jacoco
-- Pre-dex cache (in rootProject/build). Shared across modules and variants
-- Exploded aar are extracted in a single location (under rootProject/build) to share across all modules using it.
-- Upgraded to Proguard 4.11. Fixed incremental issues.
-- Fixed incremental issues with aidl files.
-
-0.9.2:
-- Aapt-based PNG processor is now default again while we investigate some issues with the old one.
-- flavorGroups have been renamed flavorDimensions and the DSL has been updated. The old DSL is still available until 1.0 at which time it'll be removed.
-
-0.9.1:
-- It's now possible to include a file when there's a conflict during packaging:
- android.packagingOptions {
- pickFirst 'META-INF/foo.txt'
- }
-- New PNG processor.
- * Should be much faster when processing many files
- * Fix issue where crunched png are bigger than original file
- * To revert to the old cruncher: android.aaptOptions.useAaptPngCruncher = true
-- The plugin now enforces that all library dependencies have a unique package name.
- To disable this you can use android.enforceUniquePackageName = false
- WARNING: The ability to disable enforcement will disappear in 1.0
-- Fixes:
- * Generated POM files now have the proper dependencies even if the pom object is manipulated in build.gradle
- * libraryVariant API now gives access to the list of flavors.
- * fixed issue where changes to the manifests of libraries didn't trigger a new manifest merge.
- * BuildConfig.VERSION_NAME is always generated even if the value is not set in the current variant.
- * BuildConfig is now package in the library. This requires that all your libraries have a unique package name.
- If you are disabling enforcement of package name, then you should disable packaging of BuildConfig with:
- android.packageBuildConfig = false
- WARNING: the ability to disable packaging will disappear in 1.0
-
-0.9.0:
-- Compatible with Gradle 1.10 and 1.11
-- BREAKING CHANGES:
- * DSL for Library Projects is now the same as for app projects, meaning you can create more Build Types, as well as ProductFlavors.
- * instrumentTest (both default folders and DSL objects) renamed androidTest
-
-- In preparation for a final variant publishing mechanism, flavors in Libraries can be published alongside the default configuration.
- The default publishing configuration is configured with
- android.defaultPublishConfig
- Default Value is "release", but can be changed to be the name of any variant.
- To enable publication all the variants, use:
- android.publishNonDefault = true
- To use from another project:
- compile project(path: ':project', configuration: 'flavor1Debug')
- See 'FlavoredLib' sample.
- Note that this does not really solve the issue with library being published with 'release' mode always. This is because you would have to manually
- specify which variant you want to reference in each of the configuration of the app project. A better mechanism will come later.
-- Ability to skip some variants. Create a closure to control which variants should be created.
- android.variantFilter { variant ->
- ...
- }
-
- The object passed to the closure implements the following methods:
- public void setIgnore(boolean ignore);
- @NonNull
- public ProductFlavor getDefaultConfig();
- @NonNull
- public BuildType getBuildType();
- @NonNull
- public List<ProductFlavor> getFlavors();
- To skip a variant, call setIgnore(false)
-- Library dependency scopes are now 'provided', 'compile', 'publish'.
- The 'publish' and 'apk' configurations don't extend 'compile' anymore but the composite configurations are still properly setup.
-- Fix issue where variant specific source folders where not used for java compilation.
-- Fix for some Renderscript support mode compatibility issues. Requires Build Tools 19.0.3
-- Lots of misc fixes.
-
-0.8.3:
-
-- Fix Studio integration regression.
-
-0.8.2:
-- Fix incremental issue with build config fields and generated res values.
-
-0.8.1:
-- Added the ability to create resource values through the DSL.
- You can now use 'resValue <type>,<name>,<value>' on build types and product flavors
- the same way you can use buildConfigField.
-- Fixed package renaming in activity-alias:targetActivity
-- Variant API improvements:
- * packageName returns the variant's package name
- * versionCode returns the (app/test) variant's versionCode
- * versionName returns the (app/test/) variant's versionName. Can return null.
-
-0.8.0
-
-- Support for Gradle 1.10
-- Requires Build-Tools 19.0.0+
-- Fixed issue 64302: Add renderscript support mode jar to the dependencies in the IDE model.
-- Fixed issue 64094: buildConfigField can now replace previous values inside the same type/flavors.
-- Add support for NDK prebuilts in library projects.
-- Parallelize pre-dexing to speed up clean builds.
-- Incremental dexing re-enabled (though it'll be automatically disabled in some builds for some cases that dx doesn't support yet.)
-- Added 'provided' dependency scope for compile only (not packaged) dependencies.
- Additional scope per buildtype and flavors are also available (debugProvided, myFlavorProvided,etc...)
-- Fix NDK on windows.
-- Variant API improvements:
- * getPreBuild() returns the prebuild task for the variant
- * getSourceSets() returns the sorted sourcesets for the task, from lower to higher priority
- * createZipAlignTask(String taskName, File inputFile, File outputFile)
- This creates and return a new zipalign task. Useful if you have a custom plugin providing custom signing of APKs.
- This also makes the assemble task depend on the new zipalign task, and wires variant.getOutputFile() to return the result of the zipalign task.
- * project.android.registerJavaArtifact() now receives a Configuration object to pass the dependencies to the IDE. See artifactApi sample.
-- New "lintVital" task, run automatically as part of assembling release variants, which checks only fatal-severity issues
-- Replace Java parser in lint with ECJ; much faster and fixes bug where lint could hang on certain source constructs
-- Lint HTML report now writes links to source files and images as URLs relative to the report location
-
-0.7.3
-
-- Rebuild 0.7.2 to work with Java6
-
-0.7.2
-
-- Fix issue with Proguard.
-- Add packagingOptions support in Library projects.
-- Solve issue with local jar when testing library projects.
-- Fix bug with variant.addJavaSourceFoldersToModel
-- Add jniLibs folder to source sets for prebuilt .so files.
-- Lint fixes:
- * fix RTL detector
- * fix HTML report to have valid HTML
-
-0.7.1
-
-- DSL to exclude some files coming from jar dependencies
- android {
- packagingOptions {
- exclude 'META-INF/LICENSE.txt'
- }
- }
-
-
-0.7.0
-- Requires Gradle 1.9
-- You can now have a variant specific source folder if you have flavors.
- Only for app (not library or test). Name is src/flavorDebug/... or src/flavor1Flavor2Debug/
- (note the camelcase naming, with lower case for first letter).
- Its components (res, manifest, etc...) have higher priority than components from build type
- or flavors.
- There is also a "flavor combination" source folder available when more than one
- flavor dimension is used.
- For instance src/flavor1Flavor2/
- Note that this is for all combinations of *all* dimensions.
-- Build config improvements and DSL changes.
- The previous DSL proprety:
- buildConfigLine "<value>"
- has changed to
- buildConfigField "<type>", "<name>", "<value>"
- You can only add a single field at a time.
- This allows override a field (see 'basic' sample)
- Also, BuildConfig now automatically contains constants for
- PACKAGE_NAME, VERSION_CODE, VERSION_NAME, BUILD_TYPE, FLAVOR as well as FLAVOR_<group> if there are several flavor dimensions.
-- Switch to ProGuard 4.10
- - Added ability to test proguarded (obfuscated) apps.
-- New option on product Flavor (and defaultConfig) allow filtering of resources through the -c option of aapt
- You can pass single or multiple values through the DSL. All values from the default config and flavors get combined and passed to aapt.
- The DSL is
- resConfig "en"
- or
- resConfigs "nodpi","hdpi"
-
-- Jar files are now pre-dexed for faster dexing.
- Incremental dexing is disabled by default as it can lead to increased dex file size.
-- First pass at NDK integration. See the samples.
-- API to add new generated source folders:
- variant.addJavaSourceFoldersToModel(sourceFolder1, sourceFolders2,...)
- This adds the source folder to the model (for IDE support).
- Another API:
- variant.registerJavaGeneratingTask(task, sourceFolder1, sourceFolders2,...)
- This automatically adds the dependency on the task, sets up the JavaCompile task inputs and propagates
- the folders to the model for IDE integration.
- See sample 'genFolderApi'
-- API to add extra artifacts on variants. This will allow to register Java or Android artifacts, for instance
- for alternative test artifacts.
- See sample 'artifactApi' for the API (sample is not meant to be used, it's for testing).
-- Revamped lint integration. Lint is now run as part of the check task, and will analyze all variants and then
- merge the results and create a report which lists which variants each error applies to (unless an error
- applies to all variants). You can also run lint on a specific variant, e.g. lintDebug or lintFreeRelease.
- Lint will no longer report errors in AAR libraries. This version of the plugin also picks up some new lint
- checks.
- A new DSL allows configuration of lint from build.gradle. This is read and used in Studio
-- Fixed issue with parentActivityName when handling different package name in the manifest merger.
-- Allow files inside META-INF/ from jars to be packaged in the APK.
-- Disabled incremental dx mode as it can lead to broken dex files.
-
-0.6.3
-- Fixed ClassNotFoundException:MergingException introduced in 0.6.2
-
-0.6.2
-- Lint now picks up the SDK home from sdk.dir in local.properties
-- Error message shown when using an unsupported version of Gradle now explains how to update the Gradle wrapper
-- Merged resource files no longer place their source markers into the R file as comments
-- Project path can contain '--' (two dashes)
-- Internal changes to improve integration with Android Studio
-
-0.6.1
-
-- Fixed issues with lint task found in 0.6.0
-
-0.6.0
-
-- Enabled support for Gradle 1.8
-- Gradle 1.8 is now the minimum supported version
-- Default encoding for compiling Java code is UTF-8
-- Users can now specify the encoding to use to compile Java code
-- Fixed Gradle 1.8-specific bugs
- - Importing projects with missing dependencies was broken
- - Compiling projects with AIDL files was broken
-
-0.5.7
-
-- Proguard support for libraries.
- Note the current DSL property 'proguardFiles' for library now sets the proguard rule file used when proguarding the library code.
- The new property 'consumerProguardFiles' is used to package a rule file inside an aar.
-- Improved IDE support, including loading project with broken dependencies and anchor task to generate Java code
-- New hook tasks: preBuild and prebuild<VariantName>
-- First lint integration. This is a work in progress and therefore the lint task is not added to the check task.
-- Enable compatibility with 1.8
-
-0.5.6
-
-- Enabled support for 1.7
-
-0.5.5
-
-- Fix issue preventing to use Build Tools 18.0.1
-- access to the variants container don't force creating the task.
- This means android.[application|Library|Test]Variants will be empty
- during the evaluation phase. To use it, use .all instead of .each
-- Only package a library's own resources in its aar.
-- Fix incremental issues in the resource merger.
-- Misc bug fixes.
-
-0.5.4
-
-- Fixed incremental compilation issue with declare-styleable
-
-0.5.3
-
-- Fixed a crashing bug in PrepareDependenciesTask
-
-0.5.2
-
-- Better error reporting for cmd line tools, especially
- if run in parallel in spawned threads
-- Fixed an issue due to windows path in merged resource files.
-
-0.5.1
-
-- Fixed issue in the dependency checker.
-
-0.5.0:
-
-- IDE Model is changed and is not compatible with earlier version! A new IDE
- will required.
-- Fixed IDE model to contain the output file even if it's customized
- through the DSL. Also fixed the DSL to get/set the output file on the
- variant object so that it's not necessary to use variant.packageApplication
- or variant.zipAlign
-- Fixed dependency resolution so that we resolved the combination of (default config,
- build types, flavor(s)) together instead of separately.
-- Fixed dependency for tests of library project to properly include all the dependencies
- of the library itself.
-- Fixed case where two dependencies have the same leaf name.
-- Fixed issue where proguard rules file cannot be applied on flavors.
-
-0.4.3:
-
-- Enabled crunching for all png files, not just .9.png
-- Fixed dealing with non resource files in res/ and assets/
-- Fixed crash when doing incremental aidl compilation due to broken method name (ah the joy of Groovy...)
-- Cleaned older R classes when the app package name has changed.
-
-0.4.2
-
-* Fixed incremental support for resource merging.
-* Fixed issue where all pngs would be processed in parallel with no limit
- on the number of thread used, leading to failure to run aapt.
-* Fixed ignoreAsset support in aaptOptions
-* Added more logging on failure to merge manifests.
-* Added flavor names to the TestServer API.
-
-0.4.1:
-
-* Renamed 'package' scope to 'apk'
- - variants are 'debugApk', 'releaseApk', 'flavor1Apk', etc...
- - Now properly supported at build to allow package-only dependencies.
-* Only Jar dependencies can be package-only. Library projects must be added to the compile scope.
-* Fixed [application|library|test]Variants API (always returned empty on 0.4)
-* Fixed issue in Proguard where it would complain about duplicate Manifests.
-
-0.4
-
-* System requirements:
- - Gradle 1.6+
- - Android Build Tools 16.0.2+
-* Rename deviceCheck into connectedDevice
-* API for 3rd party Device Providers and Test Servers to run and deploy tests. API is @Beta
-* Support for ProGuard 4.9
- - enable with BuildType.runProguard
- - add proguard config files with BuiltType.proguardFile or ProductFlavor.proguardFile
- - default proguard files accessible through android.getDefaultProguardFile(name) with name
- being 'proguard-android.txt' or 'proguard-android-optimize.txt'
-* Implements Gradle 1.6 custom model for IDE Tooling support
-* Fixes:
- - Fix support for subfolders in assets/
- - Fix cases where Android Libraries have local Jars dependencies
- - Fix renaming of package through DSL to ensure resources are compiled in the new namespace
- - Fix DSL to add getSourceSets on the "android" extension.
- - DSL to query variants has changed to applicationVariants and libraryVariants (depending on the plugin)
- Also both plugin have testVariants (tests are not included in the default collection).
-
-0.3
-
-* System requirements:
- - Gradle 1.3+ (tested on 1.3/1.4)
- - Android Platform Tools 16.0.2+
-* New Features:
- - Renderscript support.
- - Support for multi resource folders. See 'multires' sample.
- * PNG crunch is now done incrementally and in parallel.
- - Support for multi asset folders.
- - Support for asset folders in Library Projects.
- - Support for versionName suffix provided by the BuildType.
- - Testing
- * Default sourceset for tests now src/instrumentTest (instrumentTest<Name> for flavors)
- * Instrumentation tests now:
- - started from "deviceCheck" instead of "check"
- - run on all connected devices in parallel.
- - break the build if any test fails.
- - generate an HTML report for each flavor/project, but also aggregated.
- * New plugin 'android-reporting' to aggregate android test results across projects. See 'flavorlib' sample.
- - Improved DSL:
- * replaced android.target with android.compileSdkVersion to make it less confusing with targetSdkVersion
- * signing information now a SigningConfig object reusable across BuildType and ProductFlavor
- * ability to relocate a full sourceSet. See 'migrated' sample.
- * API to manipulate Build Variants.
-* Fixes:
- - Default Java compile target set to 1.6.
- - Fix generation of R classes in case libraries share same package name as the app project.
-
-0.2
-
-* Fixed support for windows.
-* Added support for customized sourceset. (http://tools.android.com/tech-docs/new-build-system/using-the-new-build-system#TOC-Working-with-and-Customizing-SourceSets)
-* Added support for dependency per configuration.
-* Fixed support for dependency on local jar files.
-* New samples "migrated" and "flavorlib"
-
-0.1: initial release
diff --git a/base/build-system/docs/build.gradle b/base/build-system/docs/build.gradle
deleted file mode 100755
index 87c0f3f..0000000
--- a/base/build-system/docs/build.gradle
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * 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.
- */
-
-import org.apache.tools.ant.filters.ReplaceTokens
-import org.gradle.build.docs.AssembleSamplesDocTask
-import org.gradle.build.docs.Docbook2Xhtml
-import org.gradle.build.docs.UserGuideTransformTask
-import org.gradle.build.docs.dsl.docbook.AssembleDslDocTask
-import org.gradle.build.docs.dsl.source.ExtractDslMetaDataTask
-import org.gradle.build.docs.dsl.source.GenerateDefaultImportsTask
-import org.gradle.build.docs.releasenotes.*
-import org.gradle.build.docs.releasenotes.checks.*
-
-evaluationDependsOn(':base:gradle')
-evaluationDependsOn(':base:gradle-core')
-evaluationDependsOn(':base:builder')
-
-apply plugin: 'groovy'
-apply plugin: 'base'
-apply plugin: 'pegdown'
-apply plugin: 'jsoup'
-apply plugin: 'javascript-base'
-
-def generatedResourcesDir = file("$buildDir/generated-resources/main")
-
-version = rootProject.buildVersion
-
-repositories {
- maven { url 'https://repo.gradle.org/gradle/libs' }
-
- javaScript.googleApis()
-
- ivy {
- name "Google Fonts"
- url "http://themes.googleusercontent.com/static/fonts/"
- layout 'pattern', {
- artifact '[organisation]/v[revision](/[classifier])(.[ext])'
- ivy '[organisation]/v[revision]/ivy(.[ext])'
- }
- }
-
- maven { url 'https://repo.gradle.org/gradle/gradle-build-internal' }
-}
-
-configurations {
- groovydocGroovy {}
- userGuideStyleSheets
- userGuideTask
- fonts
-}
-
-dependencies {
- userGuideTask 'xalan:xalan:2.7.1', 'org.codehaus.groovy:groovy-all:2.3.6'
- userGuideTask module('xhtmlrenderer:xhtmlrenderer:R8rc1') {
- dependency 'itext:itext:2.0.8 at jar'
- }
- userGuideTask 'xslthl:xslthl:2.0.1 at jar'
-
- userGuideStyleSheets 'docbook:docbook-xsl:1.75.2 at zip'
-
- fonts \
- "lato:regular:6:v0SdcGFAl2aezM9Vq_aFTQ at ttf",
- "lato:regular-italic:6:LqowQDslGv4DmUBAfWa2Vw at ttf",
- "lato:bold:6:DvlFBScY1r-FMtZSYIYoYw at ttf",
- "lato:bold-italic:6:HkF_qI1x_noxlxhrhMQYEKCWcynf_cDxXwCLxiixG1c at ttf",
- "roboto:regular:14:zN7GBFwfMP4uA6AR0HCoLQ at ttf",
- "ubuntumono:regular:3:ViZhet7Ak-LRXZMXzuAfkZ0EAVxt0G0biEntp43Qt6E at ttf",
- "ubuntumono:regular-italic:3:KAKuHXAHZOeECOWAHsRKA-LrC4Du4e_yfTJ8Ol60xk0 at ttf",
- "ubuntumono:bold:3:ceqTZGKHipo8pJj4molytp_TkvowlIOtbR7ePgFOpF4 at ttf",
- "ubuntumono:bold-italic:3:n_d8tv_JOIiYyMXR4eaV9WsGzsqhEorxQDpu60nfWEc at ttf"
-
- groovydocGroovy 'org.codehaus.groovy:groovy-all:2.3.6'
-
- testCompile 'org.codehaus.groovy:groovy-all:2.3.6'
- testCompile "org.pegdown:pegdown:1.1.0"
- testCompile 'org.jsoup:jsoup:1.6.3'
- testCompile "org.gebish:geb-spock:0.9.3"
- testCompile 'org.seleniumhq.selenium:selenium-htmlunit-driver:2.42.2'
- testCompile project(":base:gradle"), project(":base:gradle-core"), project(":base:builder")
-}
-
-ext {
- srcDocsDir = file('src/fromGradle/docs')
- userguideSrcDir = new File(srcDocsDir, 'userguide')
- dslSrcDir = new File(srcDocsDir, 'dsl')
- docsDir = file("$buildDir/docs")
- userguideDir = new File(docsDir, 'userguide')
- distDocsDir = new File(buildDir, 'distDocs')
- docbookSrc = new File(project.buildDir, 'src')
-}
-
-ext.outputs = [:]
-outputs.distDocs = files(distDocsDir) {
- builtBy 'distDocs'
-}
-outputs.docs = files(docsDir) {
- builtBy 'javadocAll', 'groovydocAll', 'userguide', 'dslHtml', 'releaseNotes'
-}
-
-tasks.withType(Docbook2Xhtml) {
- dependsOn userguideStyleSheets
- classpath = configurations.userGuideTask
- stylesheetsDir = userguideStyleSheets.destinationDir
-}
-tasks.withType(UserGuideTransformTask) {
- dependsOn dslDocbook
- linksFile = dslDocbook.linksFile
- websiteUrl = 'http://www.gradle.org'
-
- if (name in ["pdfUserguideDocbook", "userguideFragmentSrc"]) {
- // These will only be valid for releases, but that's ok
- javadocUrl = "http://www.gradle.org/docs/${->version}/javadoc"
- groovydocUrl = "http://www.gradle.org/docs/${->version}/groovydoc"
- dsldocUrl = "http://www.gradle.org/docs/${->version}/dsl"
- } else {
- javadocUrl = '../javadoc'
- groovydocUrl = '../groovydoc'
- dsldocUrl = '../dsl'
- }
-}
-tasks.withType(AssembleDslDocTask) {
- classDocbookDir = dslSrcDir
-}
-
-task configureCss << {
- def images = fileTree(dir: "$srcDocsDir/css/images", include: "*.*").files.collectEntries {
- [it.name, it.bytes.encodeBase64().toString()]
- }
-
- def fonts = configurations.fonts.resolvedConfiguration.resolvedArtifacts.collectEntries {
- def id = it.moduleVersion.id
- ["${id.group}-${id.name}".toString(), it.file.bytes.encodeBase64().toString()]
- }
-
- ext.tokens = images + fonts
- css.inputs.property 'tokens', tokens
- css.filter ReplaceTokens, tokens: tokens
-}
-
-task css(type: Sync, dependsOn: configureCss) {
- into "$buildDir/css"
- from "$srcDocsDir/css"
- include "*.css"
-}
-
-ext.cssFiles = fileTree(css.destinationDir) {
- builtBy css
-}
-
-task userguideStyleSheets(type: Copy) {
- File stylesheetsDir = new File(srcDocsDir, 'stylesheets')
- into new File(buildDir, 'stylesheets')
- from(stylesheetsDir) {
- include '*.xsl'
- }
- from(cssFiles)
- from({ zipTree(configurations.userGuideStyleSheets.singleFile) }) {
- // Remove the prefix
- eachFile { fcd -> fcd.path = fcd.path.replaceFirst('^docbook-xsl-[0-9\\.]+/', '') }
- }
-}
-
-task samplesDocbook(type: AssembleSamplesDocTask) {
- include '**/readme.xml'
- destFile = new File(docbookSrc, 'samplesList.xml')
-}
-
-task samplesDocs(type: Docbook2Xhtml) {
- source samplesDocbook
- stylesheetName = 'standaloneHtml.xsl'
-}
-
-task dslMetaData(type: ExtractDslMetaDataTask) {
- source { groovydocAll.source }
- destFile = new File(docbookSrc, 'dsl-meta-data.bin')
-}
-
-task dslDocbook(type: AssembleDslDocTask, dependsOn: [dslMetaData]) {
- inputs.files fileTree(dir: dslSrcDir, includes: ['*.xml'])
- sourceFile = new File(dslSrcDir, 'dsl.xml')
- classMetaDataFile = dslMetaData.destFile
-
- pluginsMetaDataFile = new File(dslSrcDir, 'plugins.xml')
- destFile = new File(docbookSrc, 'dsl.xml')
- linksFile = new File(docbookSrc, 'api-links.bin')
-}
-
-task dslStandaloneDocbook(type: UserGuideTransformTask, dependsOn: [dslDocbook]) {
- sourceFile = dslDocbook.destFile
- snippetsDir = buildDir
- destFile = new File(docbookSrc, 'dsl-standalone.xml')
- dsldocUrl = '.'
-}
-
-task defaultImports(type: GenerateDefaultImportsTask, dependsOn: dslMetaData) {
- metaDataFile = dslMetaData.destFile
- destFile = new File(generatedResourcesDir, "default-imports.txt")
- // These are part of the API, but not the DSL
- excludePackage 'org.gradle.tooling.**'
- excludePackage 'org.gradle.testfixtures.**'
-
- // Tweak the imports due to some inconsistencies introduced before we automated the default-imports generation
- excludePackage 'org.gradle.plugins.ide.eclipse.model'
- excludePackage 'org.gradle.plugins.ide.idea.model'
- excludePackage 'org.gradle.api.tasks.testing.logging'
- extraPackage 'org.gradle.util'
-
- // TODO - rename some incubating types to remove collisions and then remove these exclusions
- excludePackage 'org.gradle.plugins.binaries.model'
- excludePackage 'org.gradle.ide.cdt.model'
-}
-
-task dslHtml(type: Docbook2Xhtml) {
- source dslStandaloneDocbook
- destDir = new File(docsDir, 'dsl')
- stylesheetName = 'dslHtml.xsl'
- resources = cssFiles + fileTree(dslSrcDir) {
- include '*.js'
- }
-}
-
-task dslHtmlZip(type: Zip) {
- dependsOn dslHtml
- from dslHtml.destDir
- destinationDir docsDir
- baseName 'gradle-dsl'
-}
-
-def javaApiUrl = "http://docs.oracle.com/javase/1.6.0/docs/api/"
-def groovyApiUrl = "http://groovy.codehaus.org/gapi/"
-def publicGroovyProjects = [
- project(":base:gradle"),
- project(":base:gradle-core"),
- project(":base:builder")
-]
-
-task javadocAll(type: Javadoc) {
-// ext.stylesheetFile = file("src/docs/css/javadoc.css")
-// inputs.file stylesheetFile
-
- group = 'documentation'
- options.encoding = 'utf-8'
- options.docEncoding = 'utf-8'
- options.charSet = 'utf-8'
- if (JavaVersion.current().isJava8Compatible()) {
- options.addStringOption 'Xdoclint:none', '-quiet'
- }
-// options.addStringOption "stylesheetfile", stylesheetFile.absolutePath
- source publicGroovyProjects.collect {project -> project.sourceSets.main.allJava }
- destinationDir = new File(docsDir, 'javadoc')
- classpath = files(publicGroovyProjects.collect {project -> [project.sourceSets.main.compileClasspath, project.sourceSets.main.output] })
-// include 'org/gradle/*'
-// include 'org/gradle/api/**'
- include 'com/android/build/gradle/**'
- include 'com/android/build/gradle/internal/dsl/**'
- include 'com/android/builder/**'
-// exclude '**/internal/**'
- options.links(javaApiUrl, groovyApiUrl, "http://maven.apache.org/ref/2.2.1/maven-core/apidocs",
- "http://maven.apache.org/ref/2.2.1/maven-model/apidocs")
- doFirst {
- title = "Android plugin API $version"
- }
-}
-
-task configureGroovydocAll {
- doFirst {
- project.configure(groovydocAll) {
- [javaApiUrl, groovyApiUrl].each {
- link(it, *(new URL("$it/package-list").text.tokenize("\n")))
- }
- docTitle = "Gradle API $version"
- windowTitle = "Gradle API $version"
- footer = "Gradle API $version"
- }
- }
-}
-
-task groovydocAll(type: Groovydoc, dependsOn: configureGroovydocAll) {
- group = 'documentation'
- source publicGroovyProjects.collect {project ->
- def main = project.sourceSets.main
- try {
- main.groovy + main.java
- } catch (MissingPropertyException e) {
- main.java
- }
- }
- destinationDir = new File(docsDir, 'groovydoc')
-
- // Groovydoc runs static initializers, and at least ProjectBuilder's initializers depend on runtime classes
- // http://jira.codehaus.org/browse/GROOVY-7096
- classpath = files(publicGroovyProjects.collect {project -> [project.sourceSets.main.runtimeClasspath, project.sourceSets.main.output] })
-
- includes = javadocAll.includes
- excludes = javadocAll.excludes
- doFirst {
- windowTitle = "Gradle API $version"
- docTitle = windowTitle
- }
- groovyClasspath = configurations.groovydocGroovy
- doLast {
- def index = new File(destinationDir, "index.html")
- index.text = index.text.replace("{todo.title}", windowTitle) // workaround groovydoc bug
- }
-}
-
-task docsAll {
- dependsOn groovydocAll, dslHtml
- description = 'Generates all documentation'
- group = 'documentation'
-}
-
-// Make sure all the references are valid and up-to-date. Hopefully this will mark the build
-// red in Jenkins if they get out of sync.
-check.dependsOn dslHtml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.BaseExtension.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.BaseExtension.xml
deleted file mode 100644
index b182893..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.BaseExtension.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>compileSdkVersion</td></tr>
- <tr><td>buildToolsVersion</td></tr>
- <tr><td>defaultConfig</td></tr>
- <tr><td>sourceSets</td></tr>
- <tr><td>defaultPublishConfig</td></tr>
- <tr><td>publishNonDefault</td></tr>
- <tr><td>compileSdkVersion</td></tr>
- <tr><td>testOptions</td></tr>
- <tr><td>buildTypes</td></tr>
- <tr><td>productFlavors</td></tr>
- <tr><td>signingConfigs</td></tr>
- <tr><td>aaptOptions</td></tr>
- <tr><td>lintOptions</td></tr>
- <tr><td>dexOptions</td></tr>
- <tr><td>compileOptions</td></tr>
- <tr><td>packagingOptions</td></tr>
- <tr><td>jacoco</td></tr>
- <tr><td>splits</td></tr>
- <tr><td>resourcePrefix</td></tr>
- <tr><td>variantFilter</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>sourceSets</td></tr>
- <tr><td>defaultConfig</td></tr>
- <tr><td>buildTypes</td></tr>
- <tr><td>testOptions</td></tr>
- <tr><td>productFlavors</td></tr>
- <tr><td>signingConfigs</td></tr>
- <tr><td>aaptOptions</td></tr>
- <tr><td>lintOptions</td></tr>
- <tr><td>dexOptions</td></tr>
- <tr><td>compileOptions</td></tr>
- <tr><td>packagingOptions</td></tr>
- <tr><td>jacoco</td></tr>
- <tr><td>splits</td></tr>
- <tr><td>adbOptions</td></tr>
- <tr><td>useLibrary</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceSet.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceSet.xml
deleted file mode 100644
index 07993ac..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceSet.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>name</td></tr>
- <tr><td>resources</td></tr>
- <tr><td>manifest</td></tr>
- <tr><td>java</td></tr>
- <tr><td>compileConfigurationName</td></tr>
- <tr><td>packageConfigurationName</td></tr>
- <tr><td>providedConfigurationName</td></tr>
- <tr><td>manifest</td></tr>
- <tr><td>res</td></tr>
- <tr><td>assets</td></tr>
- <tr><td>aidl</td></tr>
- <tr><td>renderscript</td></tr>
- <tr><td>jni</td></tr>
- <tr><td>jniLibs</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>resources</td></tr>
- <tr><td>manifest</td></tr>
- <tr><td>java</td></tr>
- <tr><td>manifest</td></tr>
- <tr><td>res</td></tr>
- <tr><td>assets</td></tr>
- <tr><td>aidl</td></tr>
- <tr><td>renderscript</td></tr>
- <tr><td>jni</td></tr>
- <tr><td>jniLibs</td></tr>
- <tr><td>setRoot</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.CompileOptions.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.CompileOptions.xml
deleted file mode 100644
index 470b5ea..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.CompileOptions.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>sourceCompatibility</td></tr>
- <tr><td>targetCompatibility</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.api.VariantFilter.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.api.VariantFilter.xml
deleted file mode 100644
index 7069afe..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.api.VariantFilter.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>ignore</td></tr>
- <tr><td>defaultConfig</td></tr>
- <tr><td>buildType</td></tr>
- <tr><td>flavors</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AaptOptions.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AaptOptions.xml
deleted file mode 100644
index 293a81f..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AaptOptions.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>ignoreAssets</td></tr>
- <tr><td>noCompress</td></tr>
- <tr><td>failOnMissingConfigEntry</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>noCompress</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.BuildType.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.BuildType.xml
deleted file mode 100644
index 14afb53..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.BuildType.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>applicationIdSuffix</td></tr>
- <tr><td>debuggable</td></tr>
- <tr><td>embedMicroApp</td></tr>
- <tr><td>jniDebuggable</td></tr>
- <tr><td>minifyEnabled</td></tr>
- <tr><td>multiDexEnabled</td></tr>
- <tr><td>name</td></tr>
- <tr><td>pseudoLocalesEnabled</td></tr>
- <tr><td>renderscriptDebuggable</td></tr>
- <tr><td>renderscriptOptimLevel</td></tr>
- <tr><td>shrinkResources</td></tr>
- <tr><td>signingConfig</td></tr>
- <tr><td>testCoverageEnabled</td></tr>
- <tr><td>useJack</td></tr>
- <tr><td>versionNameSuffix</td></tr>
- <tr><td>zipAlignEnabled</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>buildConfigField</td></tr>
- <tr><td>resValue</td></tr>
- <tr><td>proguardFile</td></tr>
- <tr><td>proguardFiles</td></tr>
- <tr><td>resValue</td></tr>
- <tr><td>setProguardFiles</td></tr>
- <tr><td>shrinkResources</td></tr>
- <tr><td>useJack</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DensitySplitOptions.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DensitySplitOptions.xml
deleted file mode 100644
index da862a7..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DensitySplitOptions.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>compatibleScreens</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DexOptions.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DexOptions.xml
deleted file mode 100644
index 4c14197..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DexOptions.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>incremental</td></tr>
- <tr><td>preDexLibraries</td></tr>
- <tr><td>jumboMode</td></tr>
- <tr><td>javaMaxHeapSize</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LintOptions.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LintOptions.xml
deleted file mode 100644
index 29bb131..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LintOptions.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>disable</td></tr>
- <tr><td>enable</td></tr>
- <tr><td>check</td></tr>
- <tr><td>abortOnError</td></tr>
- <tr><td>absolutePaths</td></tr>
- <tr><td>noLines</td></tr>
- <tr><td>quiet</td></tr>
- <tr><td>checkAllWarnings</td></tr>
- <tr><td>ignoreWarnings</td></tr>
- <tr><td>warningsAsErrors</td></tr>
- <tr><td>explainIssues</td></tr>
- <tr><td>showAll</td></tr>
- <tr><td>lintConfig</td></tr>
- <tr><td>textReport</td></tr>
- <tr><td>textOutput</td></tr>
- <tr><td>htmlReport</td></tr>
- <tr><td>htmlOutput</td></tr>
- <tr><td>xmlReport</td></tr>
- <tr><td>xmlOutput</td></tr>
- <tr><td>checkReleaseBuilds</td></tr>
- <tr><td>severityOverrides</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>check</td></tr>
- <tr><td>enable</td></tr>
- <tr><td>disable</td></tr>
- <tr><td>ignore</td></tr>
- <tr><td>error</td></tr>
- <tr><td>fatal</td></tr>
- <tr><td>warning</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.PackagingOptions.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.PackagingOptions.xml
deleted file mode 100644
index aabcbe8..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.PackagingOptions.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>excludes</td></tr>
- <tr><td>pickFirsts</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>exclude</td></tr>
- <tr><td>pickFirst</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.ProductFlavor.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.ProductFlavor.xml
deleted file mode 100644
index 72abcb7..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.ProductFlavor.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>applicationId</td></tr>
- <tr><td>dimension</td></tr>
- <tr><td>flavorDimension</td></tr>
- <tr><td>multiDexEnabled</td></tr>
- <tr><td>signingConfig</td></tr>
- <tr><td>testApplicationId</td></tr>
- <tr><td>testFunctionalTest</td></tr>
- <tr><td>testHandleProfiling</td></tr>
- <tr><td>testInstrumentationRunner</td></tr>
- <tr><td>testInstrumentationRunnerArguments</td></tr>
- <tr><td>useJack</td></tr>
- <tr><td>versionCode</td></tr>
- <tr><td>versionCode</td></tr>
- <tr><td>versionName</td></tr>
- <tr><td>versionName</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>maxSdkVersion</td></tr>
- <tr><td>minSdkVersion</td></tr>
- <tr><td>proguardFile</td></tr>
- <tr><td>proguardFiles</td></tr>
- <tr><td>resConfig</td></tr>
- <tr><td>resConfigs</td></tr>
- <tr><td>resValue</td></tr>
- <tr><td>setProguardFiles</td></tr>
- <tr><td>targetSdkVersion</td></tr>
- <tr><td>testInstrumentationRunnerArgument</td></tr>
- <tr><td>testInstrumentationRunnerArguments</td></tr>
- <tr><td>useJack</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.Splits.xml b/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.Splits.xml
deleted file mode 100644
index b01ddd8..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.Splits.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<section>
- <section>
- <title>Properties</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>density</td></tr>
- <tr><td>densityFilters</td></tr>
- <tr><td>abi</td></tr>
- <tr><td>abiFilters</td></tr>
- </table>
- </section>
- <section>
- <title>Methods</title>
- <table>
- <thead>
- <tr><td>Name</td></tr>
- </thead>
- <tr><td>abi</td></tr>
- <tr><td>density</td></tr>
- </table>
- </section>
-</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/dsl.xml b/base/build-system/docs/src/fromGradle/docs/dsl/dsl.xml
deleted file mode 100644
index edb4014..0000000
--- a/base/build-system/docs/src/fromGradle/docs/dsl/dsl.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<book id="dsl">
- <bookinfo>
- <title>Android Plugin DSL Reference</title>
- </bookinfo>
-
- <section>
- <title>Introduction</title>
- <para>This is the DSL reference for Android Gradle Plugin.</para>
- </section>
-
- <!--
- -
- - 1. Adding new types:
- - There are 2 ways to include a new types to this guide:
- - * Types referenced by a property are automatically included, if there is a corresponding ${typename}.xml in the DSL source directory.
- - * Types listed in one of the following tables are included. There must be a corresponding ${typename}.xml in the DSL source directory.
- -
- - 2. Adding new sections:
- - The section title should end with 'types' (see AssembleDslDocTask.mergeContent)
- -->
-
- <section>
- <title>Android configuration structure</title>
- <table>
- <title>Android configuration blocks</title>
- <tr><td>aaptOptions</td></tr>
- <tr><td>buildTypes</td></tr>
- <tr><td>compileOptions</td></tr>
- <tr><td>defaultConfig</td></tr>
- <tr><td>dexOptions</td></tr>
- <tr><td>jacoco</td></tr>
- <tr><td>lintOptions</td></tr>
- <tr><td>packagingOptions</td></tr>
- <tr><td>productFlavors</td></tr>
- <tr><td>signingConfigs</td></tr>
- <tr><td>sourceSets</td></tr>
- <tr><td>splits</td></tr>
- <tr><td>testOptions</td></tr>
- </table>
- </section>
-
- <section>
- <title>DSL types</title>
- <para>Listed below are some of the central types which are used in the Android plugin:</para>
- <table>
- <title>DSL types</title>
- <tr><td>com.android.build.gradle.AppExtension</td></tr>
- <tr><td>com.android.build.gradle.BaseExtension</td></tr>
- <tr><td>com.android.build.gradle.LibraryExtension</td></tr>
- <tr><td>com.android.build.gradle.TestExtension</td></tr>
- <tr><td>com.android.build.gradle.api.AndroidSourceDirectorySet</td></tr>
- <tr><td>com.android.build.gradle.api.AndroidSourceFile</td></tr>
- <tr><td>com.android.build.gradle.api.AndroidSourceSet</td></tr>
- <tr><td>com.android.build.gradle.internal.CompileOptions</td></tr>
- <tr><td>com.android.build.gradle.internal.api.VariantFilter</td></tr>
- <tr><td>com.android.build.gradle.internal.coverage.JacocoExtension</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.AaptOptions</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.BuildType</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.DexOptions</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.LintOptions</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.PackagingOptions</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.ProductFlavor</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.SigningConfig</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.Splits</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions</td></tr>
- <tr><td>com.android.build.gradle.internal.dsl.TestOptions</td></tr>
- </table>
- </section>
-</book>
diff --git a/base/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesPlugin.groovy b/base/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesPlugin.groovy
deleted file mode 100644
index 00b8c79..0000000
--- a/base/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesPlugin.groovy
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.gms.googleservices
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-
-class GoogleServicesPlugin implements Plugin<Project> {
-
- public final static String JSON_FILE_NAME = 'google-services.json'
-
- @Override
- void apply(Project project) {
- // setup this plugin no matter the order.
- if (!checkForKnownPlugins(project)) {
- project.plugins.whenPluginAdded {
- checkForKnownPlugins(project)
- }
- }
- }
-
- private void setupPlugin(Project project, boolean isLibrary) {
- if (isLibrary) {
- project.android.libraryVariants.all { variant ->
- handleVariant(project, variant)
- }
- } else {
- project.android.applicationVariants.all { variant ->
- handleVariant(project, variant)
- }
- }
- }
-
- private static void handleVariant(Project project, def variant) {
- File quickstartFile = project.file(JSON_FILE_NAME)
- File outputDir = project.file("$project.buildDir/generated/res/google-services/$variant.dirName")
-
- GoogleServicesTask task = project.tasks.create("process${variant.name.capitalize()}GoogleServices", GoogleServicesTask)
-
- task.quickstartFile = quickstartFile
- task.intermediateDir = outputDir
- task.packageName = variant.applicationId
-
- variant.registerResGeneratingTask(task, outputDir)
- }
-
- private boolean checkForKnownPlugins(Project project) {
- if (project.plugins.hasPlugin("android") ||
- project.plugins.hasPlugin("com.android.application")) {
- // this is a bit fragile but since this is internal usage this is ok
- // (another plugin could declare itself to be 'android')
- setupPlugin(project, false)
- return true
- } else if (project.plugins.hasPlugin("android-library") ||
- project.plugins.hasPlugin("com.android.library")) {
- // this is a bit fragile but since this is internal usage this is ok
- // (another plugin could declare itself to be 'android-library')
- setupPlugin(project, true)
- return true
- }
- return false
- }
-}
diff --git a/base/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesTask.java b/base/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesTask.java
deleted file mode 100644
index aadd5bb..0000000
--- a/base/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesTask.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.gms.googleservices;
-
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonPrimitive;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.GradleException;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- */
-public class GoogleServicesTask extends DefaultTask {
-
- private static final String STATUS_DISABLED = "1";
- private static final String STATUS_ENABLED = "2";
-
- /**
- * The input is not technically optional but we want to control the error message.
- * Without @Optional, Gradle will complain itself the file is missing.
- */
- @InputFile @Optional
- public File quickstartFile;
-
- @OutputDirectory
- public File intermediateDir;
-
- @Input
- public String packageName;
-
- @TaskAction
- public void action() throws IOException {
- if (!quickstartFile.isFile()) {
- getLogger().warn("File " + quickstartFile.getName() + " is missing from module root folder." +
- " The Google Quickstart Plugin cannot function without it.");
-
- // Skip the rest of the actions because it would not make sense if `quickstartFile` is missing.
- return;
- }
-
- // delete content of outputdir.
- deleteFolder(intermediateDir);
- if (!intermediateDir.mkdirs()) {
- throw new GradleException("Failed to create folder: " + intermediateDir);
- }
-
- JsonElement root = new JsonParser().parse(Files.newReader(quickstartFile, Charsets.UTF_8));
-
- if (!root.isJsonObject()) {
- throw new GradleException("Malformed root json");
- }
-
- JsonObject rootObject = root.getAsJsonObject();
-
- Map<String, String> resValues = new TreeMap<String, String>();
-
- handleProjectNumber(rootObject, resValues);
-
- JsonObject clientObject = getClientForPackageName(rootObject);
-
- if (clientObject != null) {
- handleAnalytics(clientObject, resValues);
- handleAdsService(clientObject, resValues);
- } else {
- getLogger().warn("No matching client found for package name '" + packageName + "'");
- }
-
- // write the values file.
- File values = new File(intermediateDir, "values");
- if (!values.exists() && !values.mkdirs()) {
- throw new GradleException("Failed to create folder: " + values);
- }
-
- Files.write(getValuesContent(resValues), new File(values, "values.xml"), Charsets.UTF_8);
- }
-
- /**
- * Handle project_info/project_number for @string/gcm_defaultSenderId, and fill the res map with the read value.
- * @param rootObject the root Json object.
- * @throws IOException
- */
- private void handleProjectNumber(JsonObject rootObject, Map<String, String> resValues)
- throws IOException {
- JsonObject projectInfo = rootObject.getAsJsonObject("project_info");
- if (projectInfo == null) {
- throw new GradleException("Missing project_info object");
- }
-
- JsonPrimitive projectNumber = projectInfo.getAsJsonPrimitive("project_number");
- if (projectNumber == null) {
- throw new GradleException("Missing project_info/project_number object");
- }
-
- resValues.put("gcm_defaultSenderId", projectNumber.getAsString());
- }
-
- /**
- * Handle a client object for analytics (@xml/global_tracker)
- * @param clientObject the client Json object.
- * @throws IOException
- */
- private void handleAnalytics(JsonObject clientObject, Map<String, String> resValues)
- throws IOException {
- JsonObject analyticsService = getServiceByName(clientObject, "analytics_service");
- if (analyticsService == null) return;
-
- JsonObject analyticsProp = analyticsService.getAsJsonObject("analytics_property");
- if (analyticsProp == null) return;
-
- JsonPrimitive trackingId = analyticsProp.getAsJsonPrimitive("tracking_id");
- if (trackingId == null) return;
-
- resValues.put("ga_trackingId", trackingId.getAsString());
-
- File xml = new File(intermediateDir, "xml");
- if (!xml.exists() && !xml.mkdirs()) {
- throw new GradleException("Failed to create folder: " + xml);
- }
-
- Files.write(getGlobalTrackerContent(
- trackingId.getAsString()),
- new File(xml, "global_tracker.xml"),
- Charsets.UTF_8);
- }
-
- /**
- * Handle a client object for analytics (@xml/global_tracker)
- * @param clientObject the client Json object.
- * @throws IOException
- */
- private void handleAdsService(JsonObject clientObject, Map<String, String> resValues)
- throws IOException {
- JsonObject adsService = getServiceByName(clientObject, "ads_service");
- if (adsService == null) return;
-
- findStringByName(adsService, "test_banner_ad_unit_id", resValues);
- findStringByName(adsService, "test_interstitial_ad_unit_id", resValues);
- }
-
- private static void findStringByName(JsonObject jsonObject, String stringName,
- Map<String, String> resValues) {
- JsonPrimitive id = jsonObject.getAsJsonPrimitive(stringName);
- if (id != null) {
- resValues.put(stringName, id.getAsString());
- }
- }
-
- /**
- * find an item in the "client" array that match the package name of the app
- * @param jsonObject the root json object.
- * @return a JsonObject representing the client entry or null if no match is found.
- */
- private JsonObject getClientForPackageName(JsonObject jsonObject) {
- JsonArray array = jsonObject.getAsJsonArray("client");
- if (array != null) {
- final int count = array.size();
- for (int i = 0 ; i < count ; i++) {
- JsonElement clientElement = array.get(i);
- if (clientElement == null || !clientElement.isJsonObject()) {
- continue;
- }
-
- JsonObject clientObject = clientElement.getAsJsonObject();
-
- JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
- if (clientInfo == null) continue;
-
- JsonObject androidClientInfo = clientInfo.getAsJsonObject("android_client_info");
- if (androidClientInfo == null) continue;
-
- JsonPrimitive clientPackageName = androidClientInfo.getAsJsonPrimitive("package_name");
- if (clientPackageName == null) continue;
-
- if (packageName.equals(clientPackageName.getAsString())) {
- return clientObject;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Finds a service by name in the client object. Returns null if the service is not found
- * or if the service is disabled.
- *
- * @param clientObject the json object that represents the client.
- * @param serviceName the service name
- * @return the service if found.
- */
- private JsonObject getServiceByName(JsonObject clientObject, String serviceName) {
- JsonObject services = clientObject.getAsJsonObject("services");
- if (services == null) return null;
-
- JsonObject service = services.getAsJsonObject(serviceName);
- if (service == null) return null;
-
- JsonPrimitive status = service.getAsJsonPrimitive("status");
- if (status == null) return null;
-
- String statusStr = status.getAsString();
-
- if (STATUS_DISABLED.equals(statusStr)) return null;
- if (!STATUS_ENABLED.equals(statusStr)) {
- getLogger().warn(String.format("Status with value '%1$s' for service '%2$s' is unknown",
- statusStr,
- serviceName));
- return null;
- }
-
- return service;
- }
-
-
- private static String getGlobalTrackerContent(String ga_trackingId) {
- return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<resources>\n" +
- " <string name=\"ga_trackingId\">" + ga_trackingId + "</string>\n" +
- "</resources>\n";
- }
-
- private static String getValuesContent(Map<String, String> entries) {
- StringBuilder sb = new StringBuilder(256);
-
- sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<resources>\n");
-
- for (Map.Entry<String, String> entry : entries.entrySet()) {
- sb.append(" <string name=\"").append(entry.getKey()).append("\">")
- .append(entry.getValue()).append("</string>\n");
- }
-
- sb.append("</resources>\n");
-
- return sb.toString();
- }
-
- private static void deleteFolder(final File folder) {
- if (!folder.exists()) {
- return;
- }
- File[] files = folder.listFiles();
- if (files != null) {
- for (final File file : files) {
- if (file.isDirectory()) {
- deleteFolder(file);
- } else {
- if (!file.delete()) {
- throw new GradleException("Failed to delete: " + file);
- }
- }
- }
- }
- if (!folder.delete()) {
- throw new GradleException("Failed to delete: " + folder);
- }
- }
-}
diff --git a/base/build-system/gradle-core/build.gradle b/base/build-system/gradle-core/build.gradle
deleted file mode 100644
index 2505e6d..0000000
--- a/base/build-system/gradle-core/build.gradle
+++ /dev/null
@@ -1,79 +0,0 @@
-apply plugin: 'groovy'
-apply plugin: 'jacoco'
-apply plugin: 'clone-artifacts'
-
-configurations {
- provided
-}
-
-sourceSets {
- main {
- groovy.srcDirs = ['src/main/groovy', 'src/fromGradle/groovy']
- resources.srcDirs = ['src/main/resources', 'src/fromGradle/resources']
- compileClasspath += configurations.provided
- }
-}
-
-ext.proguardVersion = "5.2.1"
-
-dependencies {
- compile project(':base:builder')
- compile project(':base:lint')
- compile "net.sf.proguard:proguard-gradle:${project.ext.proguardVersion}"
-
- // Add gradleApi to classpath for compilation, but use provided configuration so that groovy is
- // not exposed as a runtime dependency.
- provided gradleApi()
- testCompile gradleApi()
-
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-all:1.9.5'
- testCompile project(':base:project-test-lib')
- testCompile project(':base:testutils')
-}
-
-tasks.compileJava.dependsOn ":setupGradleInIde"
-
-group = 'com.android.tools.build'
-archivesBaseName = 'gradle-core'
-version = rootProject.ext.buildVersion
-
-project.ext.pomName = 'Core Library for Android Gradle Plug-in'
-project.ext.pomDesc = 'Core library to build Android Gradle plugin.'
-
-apply from: "$rootDir/buildSrc/base/publish.gradle"
-apply from: "$rootDir/buildSrc/base/bintray.gradle"
-
-test {
- environment("CUSTOM_REPO", rootProject.file("../out/repo"))
-
- testLogging {
- events "failed"
- }
-
- maxParallelForks = Runtime.runtime.availableProcessors() / 2
-}
-
-groovydoc {
- exclude "**/internal/**"
- includePrivate false
-
- docTitle "Gradle Plugin for Android"
- header ""
- footer "Copyright (C) 2012 The Android Open Source Project"
- overview ""
-
- groovyClasspath = configurations.provided
-}
-
-task javadocJar(type: Jar, dependsOn:groovydoc) {
- classifier 'javadoc'
- from groovydoc.destinationDir
-}
-
-// Only package JavaDoc if using --init-script=buildSrc/base/release.gradle
-if (project.has("release")) {
- artifacts {
- archives javadocJar
- }
-}
diff --git a/base/build-system/gradle-core/gradle-core.iml b/base/build-system/gradle-core/gradle-core.iml
deleted file mode 100644
index 708e4f6..0000000
--- a/base/build-system/gradle-core/gradle-core.iml
+++ /dev/null
@@ -1,1213 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module relativePaths="true" type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/groovy" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/groovy" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/groovy" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
- <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/resources" type="java-resource" />
- <excludeFolder url="file://$MODULE_DIR$/.gradle" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" exported="" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="library" exported="" name="proguard-gradle" level="project" />
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-core-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-model-core-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-model-groovy-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/asm-all-5.0.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ant-1.9.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-collections-3.2.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-io-1.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-lang-2.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/logback-core-1.0.13.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/logback-classic-1.0.13.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/guava-jdk5-17.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jcip-annotations-1.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jul-to-slf4j-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jarjar-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/javax.inject-1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/slf4j-api-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/log4j-over-slf4j-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jcl-over-slf4j-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ant-launcher-1.9.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-collections-3.2.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-io-1.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-lang-2.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-docs-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-base-services-groovy-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-base-services-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-resources-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-cli-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-native-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jna-3.2.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jansi-1.2.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-osx-i386-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-osx-amd64-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-linux-amd64-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-linux-i386-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-windows-amd64-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-windows-i386-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-messaging-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/kryo-2.20.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/reflectasm-1.07-shaded.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/minlog-1.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/objenesis-1.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-settings-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-repository-metadata-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-container-default-1.5.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-aether-provider-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-wagon-provider-api-2.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-cipher-1.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-interpolation-1.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-utils-2.0.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-classworlds-2.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-plugin-api-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-model-builder-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-sec-dispatcher-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-plexus-component-annotations-1.5.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-connector-wagon-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-compat-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-wagon-http-2.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-api-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-settings-builder-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-spi-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-core-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-wagon-http-shared4-2.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-util-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-artifact-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-maven-model-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jarjar-aether-impl-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/maven-ant-tasks-2.1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/nekohtml-1.9.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xbean-reflect-3.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xml-apis-1.3.04.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xercesImpl-2.9.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-tooling-api-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-plugins-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/junit-4.11.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/testng-6.3.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/commons-cli-1.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bsh-2.0b4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/snakeyaml-1.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/hamcrest-core-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-code-quality-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-jetty-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-util-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/servlet-api-2.5-20081211.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-plus-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jsp-2.1-6.1.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-annotations-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/geronimo-annotation_1.0_spec-1.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-naming-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jsp-api-2.1-6.1.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-antlr-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/ant-antlr-1.9.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/antlr-2.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-wrapper-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-osgi-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bndlib-2.1.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-maven-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/pmaven-common-0.8-20100325.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/pmaven-groovy-0.8-20100325.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/plexus-component-annotations-1.5.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ide-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-announce-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-scala-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-sonar-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/sonar-batch-bootstrapper-2.9.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-signing-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bcpg-jdk15-1.46.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bcprov-jdk15-1.46.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ear-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-javascript-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/rhino-1.7R3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gson-2.2.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/simple-4.1.21.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-build-comparison-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-diagnostics-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-reporting-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jatl-0.2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-publish-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ivy-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-jacoco-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-build-init-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-jvm-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module" module-name="builder" exported="" />
- <orderEntry type="module" module-name="lint-cli" exported="" />
- <orderEntry type="module-library" scope="TEST">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="library" name="groovy" level="project" />
- <orderEntry type="module" module-name="project-test-lib" scope="TEST" />
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-java-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-native-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-platform-jvm-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-platform-base-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-dependency-management-2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module" module-name="sdk-common-base" />
- <orderEntry type="module" module-name="lint-api-base" />
- <orderEntry type="module" module-name="lint-checks-base" />
- <orderEntry type="library" exported="" name="jacoco" level="project" />
- <orderEntry type="library" name="mockito" level="project" />
- <orderEntry type="library" name="ecj" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidConfig.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidConfig.java
deleted file mode 100644
index ec7373f..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidConfig.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.api.AndroidSourceSet;
-import com.android.build.gradle.api.VariantFilter;
-import com.android.build.gradle.internal.CompileOptions;
-import com.android.build.gradle.internal.coverage.JacocoExtension;
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.dsl.AdbOptions;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.build.gradle.internal.dsl.DexOptions;
-import com.android.build.gradle.internal.dsl.LintOptions;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.dsl.PreprocessingOptions;
-import com.android.build.gradle.internal.dsl.Splits;
-import com.android.build.gradle.internal.dsl.TestOptions;
-import com.android.builder.core.LibraryRequest;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.builder.testing.api.TestServer;
-import com.android.sdklib.repository.FullRevision;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectContainer;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * User configuration settings for all android plugins.
- */
-public interface AndroidConfig {
-
- /** Build tool version */
- String getBuildToolsVersion();
-
- /** Compile SDK version */
- String getCompileSdkVersion();
-
- /** Build tool revisions */
- FullRevision getBuildToolsRevision();
-
- /** Name of the variant to publish */
- String getDefaultPublishConfig();
-
- /** Whether to also publish non-default variants */
- boolean getPublishNonDefault();
-
- /** Filter to determine which variants to build */
- Action<VariantFilter> getVariantFilter();
-
- /** Adb options */
- AdbOptions getAdbOptions();
-
- /** A prefix to be used when creating new resources. Used by Studio */
- String getResourcePrefix();
-
- /** List of flavor dimensions */
- List<String> getFlavorDimensionList();
-
- /** Whether to generate pure splits or multi apk */
- boolean getGeneratePureSplits();
-
- /** Preprocessing Options */
- PreprocessingOptions getPreprocessingOptions();
-
- @Deprecated
- boolean getEnforceUniquePackageName();
-
- /** Default config, shared by all flavors. */
- CoreProductFlavor getDefaultConfig();
-
- /** Options for aapt, tool for packaging resources. */
- AaptOptions getAaptOptions();
-
- /** Compile options */
- CompileOptions getCompileOptions();
-
- /** Dex options. */
- DexOptions getDexOptions();
-
- /** JaCoCo options. */
- JacocoExtension getJacoco();
-
- /** Lint options. */
- LintOptions getLintOptions();
-
- /** Packaging options. */
- PackagingOptions getPackagingOptions();
-
- /** APK splits */
- Splits getSplits();
-
- /** Options for running tests. */
- TestOptions getTestOptions();
-
- /** List of device providers */
- @NonNull
- List<DeviceProvider> getDeviceProviders();
-
- /** List of remote CI servers */
- @NonNull
- List<TestServer> getTestServers();
-
- /** All product flavors used by this project. */
- Collection<? extends CoreProductFlavor> getProductFlavors();
-
- /** Build types used by this project. */
- Collection<? extends CoreBuildType> getBuildTypes();
-
- /** Signing configs used by this project. */
- Collection<? extends SigningConfig> getSigningConfigs();
-
- /** Source sets for all variants */
- NamedDomainObjectContainer<AndroidSourceSet> getSourceSets();
-
- /** Whether to package build config class file */
- Boolean getPackageBuildConfig();
-
- Collection<LibraryRequest> getLibraryRequests();
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java
deleted file mode 100644
index 7d71dd3..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.google.common.collect.Maps;
-
-import org.gradle.api.Project;
-
-import java.util.Map;
-
-/**
- * Determines if various options, triggered from the command line or environment, are set.
- */
-public class AndroidGradleOptions {
-
- private static final String PROPERTY_TEST_RUNNER_ARGS =
- "android.testInstrumentationRunnerArguments.";
-
- // TODO: Drop the "com." prefix, for consistency.
- private static final String PROPERTY_BENCHMARK_NAME = "com.android.benchmark.name";
- private static final String PROPERTY_BENCHMARK_MODE = "com.android.benchmark.mode";
-
- @NonNull
- public static Map<String, String> getExtraInstrumentationTestRunnerArgs(@NonNull Project project) {
- Map<String, String> argsMap = Maps.newHashMap();
- for (Map.Entry<String, ?> entry : project.getProperties().entrySet()) {
- if (entry.getKey().startsWith(PROPERTY_TEST_RUNNER_ARGS)) {
- String argName = entry.getKey().substring(PROPERTY_TEST_RUNNER_ARGS.length());
- String argValue = entry.getValue().toString();
-
- argsMap.put(argName, argValue);
- }
- }
-
- return argsMap;
- }
-
- @Nullable
- public static String getBenchmarkName(@NonNull Project project) {
- return getString(project, PROPERTY_BENCHMARK_NAME);
- }
-
- @Nullable
- public static String getBenchmarkMode(@NonNull Project project) {
- return getString(project, PROPERTY_BENCHMARK_MODE);
- }
-
- public static boolean invokedFromIde(@NonNull Project project) {
- return getBoolean(project, AndroidProject.PROPERTY_INVOKED_FROM_IDE);
- }
-
- public static boolean buildModelOnly(@NonNull Project project) {
- return getBoolean(project, AndroidProject.PROPERTY_BUILD_MODEL_ONLY);
- }
-
- public static boolean buildModelOnlyAdvanced(@NonNull Project project) {
- return getBoolean(project, AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED);
- }
-
- @Nullable
- public static String getApkLocation(@NonNull Project project) {
- return getString(project, AndroidProject.PROPERTY_APK_LOCATION);
- }
-
- @Nullable
- public static SigningOptions getSigningOptions(@NonNull Project project) {
- String signingStoreFile =
- getString(project, AndroidProject.PROPERTY_SIGNING_STORE_FILE);
- String signingStorePassword =
- getString(project, AndroidProject.PROPERTY_SIGNING_STORE_PASSWORD);
- String signingKeyAlias =
- getString(project, AndroidProject.PROPERTY_SIGNING_KEY_ALIAS);
- String signingKeyPassword =
- getString(project, AndroidProject.PROPERTY_SIGNING_KEY_PASSWORD);
-
- if (signingStoreFile != null
- && signingStorePassword != null
- && signingKeyAlias != null
- && signingKeyPassword != null) {
- String signingStoreType =
- getString(project, AndroidProject.PROPERTY_SIGNING_STORE_TYPE);
-
- return new SigningOptions(
- signingStoreFile,
- signingStorePassword,
- signingKeyAlias,
- signingKeyPassword,
- signingStoreType);
- }
-
- return null;
- }
-
- @Nullable
- private static String getString(@NonNull Project project, String propertyName) {
- return (String) project.getProperties().get(propertyName);
- }
-
- private static boolean getBoolean(
- @NonNull Project project,
- @NonNull String propertyName) {
- if (project.hasProperty(propertyName)) {
- Object value = project.getProperties().get(propertyName);
- if (value instanceof String) {
- return Boolean.parseBoolean((String) value);
- }
- }
-
- return false;
- }
-
- public static class SigningOptions {
- @NonNull public final String storeFile;
- @NonNull public final String storePassword;
- @NonNull public final String keyAlias;
- @NonNull public final String keyPassword;
- @Nullable public final String storeType;
-
- public SigningOptions(
- @NonNull String storeFile,
- @NonNull String storePassword,
- @NonNull String keyAlias,
- @NonNull String keyPassword,
- @Nullable String storeType) {
- this.storeFile = storeFile;
- this.storeType = storeType;
- this.storePassword = storePassword;
- this.keyAlias = keyAlias;
- this.keyPassword = keyPassword;
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy
deleted file mode 100644
index 9871cf7..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle
-import com.android.build.gradle.internal.tasks.AndroidReportTask
-import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
-import com.android.build.gradle.internal.dsl.TestOptions
-import com.android.build.gradle.internal.test.report.ReportType
-import org.gradle.api.Project
-import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.tasks.TaskCollection
-
-import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS
-import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS
-import static com.android.builder.core.BuilderConstants.FD_REPORTS
-/**
- * Gradle plugin class for 'reporting' projects.
- *
- * This is mostly used to aggregate reports from subprojects.
- *
- */
-class ReportingPlugin implements org.gradle.api.Plugin<Project> {
-
- private TestOptions extension
-
- @Override
- void apply(Project project) {
- // make sure this project depends on the evaluation of all sub projects so that
- // it's evaluated last.
- project.evaluationDependsOnChildren()
-
- extension = project.extensions.create('android', TestOptions)
-
- AndroidReportTask mergeReportsTask = project.tasks.create("mergeAndroidReports",
- AndroidReportTask)
- mergeReportsTask.group = JavaBasePlugin.VERIFICATION_GROUP
- mergeReportsTask.description = "Merges all the Android test reports from the sub projects."
- mergeReportsTask.reportType = ReportType.MULTI_PROJECT
- mergeReportsTask.setVariantName("")
-
- mergeReportsTask.conventionMapping.resultsDir = {
- String location = extension.resultsDir != null ?
- extension.resultsDir : "$project.buildDir/$FD_ANDROID_RESULTS"
-
- project.file(location)
- }
- mergeReportsTask.conventionMapping.reportsDir = {
- String location = extension.reportDir != null ?
- extension.reportDir : "$project.buildDir/$FD_REPORTS/$FD_ANDROID_TESTS"
-
- project.file(location)
- }
-
- // gather the subprojects
- project.afterEvaluate {
- project.subprojects.each { p ->
- TaskCollection<AndroidReportTask> tasks = p.tasks.withType(AndroidReportTask)
- for (AndroidReportTask task : tasks) {
- mergeReportsTask.addTask(task)
- }
- TaskCollection<DeviceProviderInstrumentTestTask> tasks2 = p.tasks.withType(DeviceProviderInstrumentTestTask)
- for (DeviceProviderInstrumentTestTask task : tasks2) {
- mergeReportsTask.addTask(task)
- }
- }
- }
-
- // If gradle is launched with --continue, we want to run all tests and generate an
- // aggregate report (to help with the fact that we may have several build variants).
- // To do that, the "mergeAndroidReports" task (which does the aggregation) must always
- // run even if one of its dependent task (all the testFlavor tasks) fails, so we make
- // them ignore their error.
- // We cannot do that always: in case the test task is not going to run, we do want the
- // individual testFlavor tasks to fail.
- if (mergeReportsTask != null && project.gradle.startParameter.continueOnFailure) {
- project.gradle.taskGraph.whenReady { taskGraph ->
- if (taskGraph.hasTask(mergeReportsTask)) {
- mergeReportsTask.setWillRun()
- }
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java
deleted file mode 100644
index 16722e3..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.api;
-
-import com.android.annotations.NonNull;
-
-import org.gradle.api.file.FileTree;
-import org.gradle.api.tasks.util.PatternFilterable;
-
-import java.io.File;
-import java.util.Set;
-
-/**
- * An AndroidSourceDirectorySet represents a lit of directory input for an Android project.
- */
-public interface AndroidSourceDirectorySet extends PatternFilterable {
-
- /**
- * A concise name for the source directory (typically used to identify it in a collection).
- */
- @NonNull
- String getName();
-
- /**
- * Adds the given source directory to this set.
- *
- * @param srcDir The source directory. This is evaluated as for
- * {@link org.gradle.api.Project#file(Object)}
- * @return this
- */
- @NonNull
- AndroidSourceDirectorySet srcDir(Object srcDir);
-
- /**
- * Adds the given source directories to this set.
- *
- * @param srcDirs The source directories. These are evaluated as for
- * {@link org.gradle.api.Project#files(Object...)}
- * @return this
- */
- @NonNull
- AndroidSourceDirectorySet srcDirs(Object... srcDirs);
-
- /**
- * Sets the source directories for this set.
- *
- * @param srcDirs The source directories. These are evaluated as for
- * {@link org.gradle.api.Project#files(Object...)}
- * @return this
- */
- @NonNull
- AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs);
-
- /**
- * Returns the list of source files as a {@link org.gradle.api.file.FileTree}
- *
- * @return a non null {@link FileTree} for all the source files in this set.
- */
- @NonNull
- FileTree getSourceFiles();
-
- /**
- * Returns the filter used to select the source from the source directories.
- *
- * @return a non null {@link org.gradle.api.tasks.util.PatternFilterable}
- */
- @NonNull
- PatternFilterable getFilter();
-
-
- /**
- * Returns the resolved directories.
- *
- * <p>Setter can be called with a collection of {@link Object}s, just like
- * Gradle's {@code project.file(...)}.
- *
- * @return a non null set of File objects.
- */
- @NonNull
- Set<File> getSrcDirs();
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariant.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariant.java
deleted file mode 100644
index dd90a8a..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariant.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.tasks.Dex;
-import com.android.builder.model.SigningConfig;
-
-import org.gradle.api.DefaultTask;
-
-import java.io.File;
-import java.util.Collection;
-
-/**
- * A Build variant and all its public data.
- */
-public interface ApkVariant extends BaseVariant {
-
- /**
- * Return the app versionCode. Even the value is not found, then 1 is returned as this
- * is the implicit value that the platform would use.
- *
- * If not output define its own variant override then this is used for all outputs.
- */
- int getVersionCode();
-
- /**
- * Return the app versionName or null if none found.
- */
- @Nullable
- String getVersionName();
-
- /**
- * Returns the {@link SigningConfig} for this build variant,
- * if one has been specified.
- */
- @Nullable
- SigningConfig getSigningConfig();
-
- /**
- * Returns true if this variant has the information it needs to create a signed APK.
- */
- boolean isSigningReady();
-
- /**
- * Returns the Dex task.
- */
- @Nullable
- Dex getDex();
-
- /**
- * Returns the list of jar files that are on the compile classpath. This does not include
- * the runtime.
- */
- @NonNull
- Collection<File> getCompileLibraries();
-
- /**
- * Returns the list of jar files that are packaged in the APK.
- */
- @NonNull
- Collection<File> getApkLibraries();
-
- /**
- * Returns the install task for the variant.
- */
- @Nullable
- DefaultTask getInstall();
-
- /**
- * Returns the uninstallation task.
- *
- * For non-library project this is always true even if the APK is not created because
- * signing isn't setup.
- */
- @Nullable
- DefaultTask getUninstall();
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariant.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariant.java
deleted file mode 100644
index 786c395..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariant.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.tasks.AidlCompile;
-import com.android.build.gradle.tasks.GenerateBuildConfig;
-import com.android.build.gradle.tasks.MergeAssets;
-import com.android.build.gradle.tasks.MergeResources;
-import com.android.build.gradle.tasks.NdkCompile;
-import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.builder.model.BuildType;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SourceProvider;
-
-import org.gradle.api.Task;
-import org.gradle.api.tasks.AbstractCopyTask;
-import org.gradle.api.tasks.compile.AbstractCompile;
-import org.gradle.api.tasks.compile.JavaCompile;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-
-/**
- * A Build variant and all its public data. This is the base class for items common to apps,
- * test apps, and libraries
- */
-public interface BaseVariant {
-
- /**
- * Returns the name of the variant. Guaranteed to be unique.
- */
- @NonNull
- String getName();
-
- /**
- * Returns a description for the build variant.
- */
- @NonNull
- String getDescription();
-
- /**
- * Returns a subfolder name for the variant. Guaranteed to be unique.
- *
- * This is usually a mix of build type and flavor(s) (if applicable).
- * For instance this could be:
- * "debug"
- * "debug/myflavor"
- * "release/Flavor1Flavor2"
- */
- @NonNull
- String getDirName();
-
- /**
- * Returns the base name for the output of the variant. Guaranteed to be unique.
- */
- @NonNull
- String getBaseName();
-
- /**
- * Returns the flavor name of the variant. This is a concatenation of all the
- * applied flavors
- * @return the name of the flavors, or an empty string if there is not flavors.
- */
- @NonNull
- String getFlavorName();
-
- /**
- * Returns the variant outputs. There should always be at least one output.
- * @return a non-null list of variants.
- */
- @NonNull
- List<BaseVariantOutput> getOutputs();
-
- /**
- * Returns the {@link com.android.builder.core.DefaultBuildType} for this build variant.
- */
- @NonNull
- BuildType getBuildType();
-
- /**
- * Returns a {@link com.android.builder.core.DefaultProductFlavor} that represents the merging
- * of the default config and the flavors of this build variant.
- */
- @NonNull
- ProductFlavor getMergedFlavor();
-
- /**
- * Returns the list of {@link com.android.builder.core.DefaultProductFlavor} for this build variant.
- *
- * This is always non-null but could be empty.
- */
- @NonNull
- List<ProductFlavor> getProductFlavors();
-
- /**
- * Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier
- * items are meant to be overridden by later items.
- *
- * @return a list of source provider
- */
- @NonNull
- List<SourceProvider> getSourceSets();
-
- /**
- * Returns the applicationId of the variant.
- */
- @NonNull
- String getApplicationId();
-
- /**
- * Returns the pre-build anchor task
- */
- @NonNull
- Task getPreBuild();
-
- /**
- * Returns the check manifest task.
- */
- @NonNull
- Task getCheckManifest();
-
- /**
- * Returns the AIDL compilation task.
- */
- @NonNull
- AidlCompile getAidlCompile();
-
- /**
- * Returns the Renderscript compilation task.
- */
- @NonNull
- RenderscriptCompile getRenderscriptCompile();
-
- /**
- * Returns the resource merging task.
- */
- @Nullable
- MergeResources getMergeResources();
-
- /**
- * Returns the asset merging task.
- */
- @Nullable
- MergeAssets getMergeAssets();
-
- /**
- * Returns the BuildConfig generation task.
- */
- @Nullable
- GenerateBuildConfig getGenerateBuildConfig();
-
- /**
- * Returns the Java Compilation task if javac was configured to compile the source files.
- * @deprecated prefer {@link #getJavaCompiler} which always return the java compiler task
- * irrespective of which tool chain (javac or jack) used.
- */
- @Nullable
- @Deprecated
- JavaCompile getJavaCompile() throws IllegalStateException;
-
- /**
- * Returns the Java Compiler task which can be either javac or jack depending on the project
- * configuration.
- */
- @NonNull
- AbstractCompile getJavaCompiler();
-
- /**
- * Returns the NDK Compilation task.
- */
- @NonNull
- NdkCompile getNdkCompile();
-
- /**
- * Returns the obfuscation task. This can be null if obfuscation is not enabled.
- */
- @Nullable
- Task getObfuscation();
-
- /**
- * Returns the obfuscation mapping file. This can be null if obfuscation is not enabled.
- */
- @Nullable
- File getMappingFile();
-
- /**
- * Returns the Java resource processing task.
- */
- @NonNull
- AbstractCopyTask getProcessJavaResources();
-
- /**
- * Returns the assemble task for all this variant's output
- */
- @Nullable
- Task getAssemble();
-
- /**
- * Adds new Java source folders to the model.
- *
- * These source folders will not be used for the default build
- * system, but will be passed along the default Java source folders
- * to whoever queries the model.
- *
- * @param sourceFolders the source folders where the generated source code is.
- */
- void addJavaSourceFoldersToModel(@NonNull File... sourceFolders);
-
- /**
- * Adds new Java source folders to the model.
- *
- * These source folders will not be used for the default build
- * system, but will be passed along the default Java source folders
- * to whoever queries the model.
- *
- * @param sourceFolders the source folders where the generated source code is.
- */
- void addJavaSourceFoldersToModel(@NonNull Collection<File> sourceFolders);
-
- /**
- * Adds to the variant a task that generates Java source code.
- *
- * This will make the generate[Variant]Sources task depend on this task and add the
- * new source folders as compilation inputs.
- *
- * The new source folders are also added to the model.
- *
- * @param task the task
- * @param sourceFolders the source folders where the generated source code is.
- */
- void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
-
- /**
- * Adds to the variant a task that generates Java source code.
- *
- * This will make the generate[Variant]Sources task depend on this task and add the
- * new source folders as compilation inputs.
- *
- * The new source folders are also added to the model.
- *
- * @param task the task
- * @param sourceFolders the source folders where the generated source code is.
- */
- void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> sourceFolders);
-
- /**
- * Adds to the variant a task that generates Resources.
- *
- * This will make the generate[Variant]Resources task depend on this task and add the
- * new Resource folders as Resource merge inputs.
- *
- * The Resource folders are also added to the model.
- *
- * @param task the task
- * @param resFolders the folders where the generated resources are.
- */
- void registerResGeneratingTask(@NonNull Task task, @NonNull File... resFolders);
-
- /**
- * Adds to the variant a task that generates Resources.
- *
- * This will make the generate[Variant]Resources task depend on this task and add the
- * new Resource folders as Resource merge inputs.
- *
- * The Resource folders are also added to the model.
- *
- * @param task the task
- * @param resFolders the folders where the generated resources are.
- */
- void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> resFolders);
-
- /**
- * Adds a variant-specific BuildConfig field.
- * @param type the type of the field
- * @param name the name of the field
- * @param value the value of the field
- */
- void buildConfigField(@NonNull String type, @NonNull String name, @NonNull String value);
-
- /**
- * Adds a variant-specific res value.
- * @param type the type of the field
- * @param name the name of the field
- * @param value the value of the field
- */
- void resValue(@NonNull String type, @NonNull String name, @NonNull String value);
-
- /**
- * If true, variant outputs will be considered signed. Only set if you manually set the outputs
- * to point to signed files built by other tasks.
- */
- void setOutputsAreSigned(boolean isSigned);
-
- /**
- * @see #setOutputsAreSigned(boolean)
- */
- boolean getOutputsAreSigned();
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ApplicationTaskManager.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ApplicationTaskManager.java
deleted file mode 100644
index 649c43e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ApplicationTaskManager.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.scope.AndroidTask;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.ApplicationVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.profile.ExecutionType;
-import com.android.builder.profile.Recorder;
-import com.android.builder.profile.ThreadRecorder;
-
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.tasks.compile.JavaCompile;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-/**
- * TaskManager for creating tasks in an Android application project.
- */
-public class ApplicationTaskManager extends TaskManager {
-
- public ApplicationTaskManager(
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- super(project, androidBuilder, extension, sdkHandler, dependencyManager, toolingRegistry);
- }
-
- @Override
- public void createTasksForVariantData(
- @NonNull final TaskFactory tasks,
- @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
- assert variantData instanceof ApplicationVariantData;
- final ApplicationVariantData appVariantData = (ApplicationVariantData) variantData;
-
- final VariantScope variantScope = variantData.getScope();
-
- createAnchorTasks(tasks, variantScope);
- createCheckManifestTask(tasks, variantScope);
-
- handleMicroApp(tasks, variantScope);
-
- // Add a task to process the manifest(s)
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createMergeAppManifestsTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to create the res values
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createGenerateResValuesTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to compile renderscript files.
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createRenderscriptTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to merge the resource folders
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createMergeResourcesTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to merge the asset folders
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createMergeAssetsTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to create the BuildConfig class
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createBuildConfigTask(tasks, variantScope);
- return null;
- }
- });
-
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- // Add a task to process the Android Resources and generate source files
- createProcessResTask(tasks, variantScope, true /*generateResourcePackage*/);
-
- // Add a task to process the java resources
- createProcessJavaResTasks(tasks, variantScope);
- return null;
- }
- });
-
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_AIDL_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createAidlTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a compile task
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_COMPILE_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- AndroidTask<JavaCompile> javacTask = createJavacTask(tasks, variantScope);
-
- if (variantData.getVariantConfiguration().getUseJack()) {
- createJackTask(tasks, variantScope);
- } else {
- setJavaCompilerTask(javacTask, tasks, variantScope);
- createJarTask(tasks, variantScope);
- createPostCompilationTasks(tasks, variantScope);
- }
- return null;
- }
- });
-
- // Add NDK tasks
- if (isNdkTaskNeeded) {
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_NDK_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createNdkTasks(variantScope);
- return null;
- }
- });
- } else {
- if (variantData.compileTask != null) {
- variantData.compileTask.dependsOn(getNdkBuildable(variantData));
- } else {
- variantScope.getCompileTask().dependsOn(tasks, getNdkBuildable(variantData));
- }
- }
- variantScope.setNdkBuildable(getNdkBuildable(variantData));
-
- if (variantData.getSplitHandlingPolicy().equals(
- BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY)) {
- if (getExtension().getBuildToolsRevision().getMajor() < 21) {
- throw new RuntimeException("Pure splits can only be used with buildtools 21 and later");
- }
-
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_SPLIT_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createSplitResourcesTasks(variantScope);
- createSplitAbiTasks(variantScope);
- return null;
- }
- });
- }
-
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_PACKAGING_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createPackagingTask(tasks, variantScope, true /*publishApk*/);
- return null;
- }
- });
-
- // create the lint tasks.
- ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_LINT_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createLintTasks(tasks, variantScope);
- return null;
- }
- });
- }
-
- /**
- * Configure variantData to generate embedded wear application.
- */
- private void handleMicroApp(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- if (variantData.getVariantConfiguration().getBuildType().isEmbedMicroApp()) {
- // get all possible configurations for the variant. We'll take the highest priority
- // of them that have a file.
- List<String> wearConfigNames = variantData.getWearConfigNames();
-
- for (String configName : wearConfigNames) {
- Configuration config = project.getConfigurations().findByName(
- configName);
- // this shouldn't happen, but better safe.
- if (config == null) {
- continue;
- }
-
- Set<File> file = config.getFiles();
-
- int count = file.size();
- if (count == 1) {
- createGenerateMicroApkDataTask(tasks, scope, config);
- // found one, bail out.
- return;
- } else if (count > 1) {
- throw new RuntimeException(String.format(
- "Configuration '%s' resolves to more than one apk.", configName));
- }
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/CompileOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/CompileOptions.java
deleted file mode 100644
index e8e2403..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/CompileOptions.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Charsets;
-import org.gradle.api.JavaVersion;
-
-import java.util.Locale;
-
-/**
- * Compilation options.
- */
-public class CompileOptions {
- @Nullable
- private JavaVersion sourceCompatibility;
-
- @Nullable
- private JavaVersion targetCompatibility;
-
- private String encoding = Charsets.UTF_8.name();
-
- /**
- * Default Java version that will be used if the source and target compatibility levels will
- * not be set explicitly.
- */
- private JavaVersion defaultJavaVersion = JavaVersion.VERSION_1_6;
-
- private boolean ndkCygwinMode = false;
-
- /**
- * Language level of the source code.
- *
- * <p>Formats supported are :
- * "1.6"
- * 1.6
- * JavaVersion.Version_1_6
- * "Version_1_6"
- */
- public void setSourceCompatibility(@NonNull Object sourceCompatibility) {
- this.sourceCompatibility = convert(sourceCompatibility);
- }
-
- /**
- * Language level of the source code.
- *
- * <p>Similar to what <a href="http://www.gradle.org/docs/current/userguide/java_plugin.html">
- * Gradle Java plugin</a> uses.
- */
- @NonNull
- public JavaVersion getSourceCompatibility() {
- return sourceCompatibility != null ? sourceCompatibility : defaultJavaVersion;
- }
-
- /**
- * Language level of the target code.
- *
- * <p>Formats supported are :
- * "1.6"
- * 1.6
- * JavaVersion.Version_1_6
- * "Version_1_6"
- */
- public void setTargetCompatibility(@NonNull Object targetCompatibility) {
- this.targetCompatibility = convert(targetCompatibility);
- }
-
- /**
- * Version of the generated Java bytecode.
- *
- * <p>Similar to what <a href="http://www.gradle.org/docs/current/userguide/java_plugin.html">
- * Gradle Java plugin</a> uses.
- */
- @NonNull
- public JavaVersion getTargetCompatibility() {
- return targetCompatibility != null ? targetCompatibility : defaultJavaVersion;
- }
-
- public void setEncoding(String encoding) {
- this.encoding = encoding;
- }
-
- public String getEncoding() {
- return encoding;
- }
-
- public void setDefaultJavaVersion(JavaVersion defaultJavaVersion) {
- this.defaultJavaVersion = defaultJavaVersion;
- }
-
- public JavaVersion getDefaultJavaVersion() {
- return defaultJavaVersion;
- }
-
-
- private static final String VERSION_PREFIX = "VERSION_";
- /**
- * Convert all possible supported way of specifying a Java version to {@link JavaVersion}
- * @param version the user provided java version.
- * @return {@link JavaVersion}
- * @throws RuntimeException if it cannot be converted.
- */
- @NonNull
- private static JavaVersion convert(@NonNull Object version) {
- // for backward version reasons, we support setting strings like 'Version_1_6'
- if (version instanceof String) {
- final String versionString = (String) version;
- if (versionString.toUpperCase(Locale.ENGLISH).startsWith(VERSION_PREFIX)) {
- version = versionString.substring(VERSION_PREFIX.length()).replace('_', '.');
- }
- }
- return JavaVersion.toVersion(version);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java
deleted file mode 100644
index 31a79dd..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.model.JavaLibraryImpl;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.JavaLibrary;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.artifacts.Configuration;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-
-/**
- * Implementation of {@link com.android.builder.model.Dependencies} over a Gradle
- * Configuration object. This is used to lazily query the list of files from the config object.
- */
-public class ConfigurationDependencies implements Dependencies {
-
- @NonNull
- private final Configuration configuration;
-
- public ConfigurationDependencies(@NonNull Configuration configuration) {
-
- this.configuration = configuration;
- }
-
- @NonNull
- @Override
- public Collection<AndroidLibrary> getLibraries() {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public Collection<JavaLibrary> getJavaLibraries() {
- Set<File> files = configuration.getFiles();
- if (files.isEmpty()) {
- return Collections.emptySet();
- }
- Set<JavaLibrary> javaLibraries = Sets.newHashSet();
- for (File file : files) {
- javaLibraries.add(new JavaLibraryImpl(file, null, null));
- }
- return javaLibraries;
- }
-
- @NonNull
- @Override
- public Collection<String> getProjects() {
- return Collections.emptyList();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/DependencyManager.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/DependencyManager.java
deleted file mode 100644
index c8c6df9..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/DependencyManager.java
+++ /dev/null
@@ -1,1096 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.SdkConstants.DOT_JAR;
-import static com.android.SdkConstants.EXT_ANDROID_PACKAGE;
-import static com.android.SdkConstants.EXT_JAR;
-import static com.android.builder.core.BuilderConstants.EXT_LIB_ARCHIVE;
-import static com.android.builder.core.ErrorReporter.EvaluationMode.STANDARD;
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.dependency.JarInfo;
-import com.android.build.gradle.internal.dependency.LibInfo;
-import com.android.build.gradle.internal.dependency.LibraryDependencyImpl;
-import com.android.build.gradle.internal.dependency.ManifestDependencyImpl;
-import com.android.build.gradle.internal.dependency.VariantDependencies;
-import com.android.build.gradle.internal.model.MavenCoordinatesImpl;
-import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
-import com.android.build.gradle.internal.tasks.PrepareLibraryTask;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.builder.dependency.DependencyContainer;
-import com.android.builder.dependency.JarDependency;
-import com.android.builder.dependency.LibraryDependency;
-import com.android.builder.model.MavenCoordinates;
-import com.android.builder.model.SyncIssue;
-import com.android.utils.ILogger;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.CircularReferenceException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.UnknownProjectException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.SelfResolvingDependency;
-import org.gradle.api.artifacts.component.ComponentIdentifier;
-import org.gradle.api.artifacts.component.ComponentSelector;
-import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
-import org.gradle.api.artifacts.result.DependencyResult;
-import org.gradle.api.artifacts.result.ResolvedComponentResult;
-import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
-import org.gradle.api.logging.Logging;
-import org.gradle.api.plugins.JavaBasePlugin;
-import org.gradle.api.specs.Specs;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A manager to resolve configuration dependencies.
- */
-public class DependencyManager {
- protected static final boolean DEBUG_DEPENDENCY = false;
-
- private Project project;
- private ExtraModelInfo extraModelInfo;
- private ILogger logger;
-
- final Map<LibraryDependencyImpl, PrepareLibraryTask> prepareTaskMap = Maps.newHashMap();
-
- public DependencyManager(Project project, ExtraModelInfo extraModelInfo) {
- this.project = project;
- this.extraModelInfo = extraModelInfo;
- logger = new LoggerWrapper(Logging.getLogger(DependencyManager.class));
- }
-
- /**
- * Returns the list of packaged local jars.
- */
- public static List<File> getPackagedLocalJarFileList(DependencyContainer dependencyContainer) {
- List<JarDependency> jarDependencyList = dependencyContainer.getLocalDependencies();
- Set<File> files = Sets.newHashSetWithExpectedSize(jarDependencyList.size());
- for (JarDependency jarDependency : jarDependencyList) {
- if (jarDependency.isPackaged()) {
- files.add(jarDependency.getJarFile());
- }
- }
-
- return Lists.newArrayList(files);
- }
-
- public void addDependencyToPrepareTask(
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
- @NonNull PrepareDependenciesTask prepareDependenciesTask,
- @NonNull LibraryDependencyImpl lib) {
- PrepareLibraryTask prepareLibTask = prepareTaskMap.get(lib.getNonTransitiveRepresentation());
- if (prepareLibTask != null) {
- prepareDependenciesTask.dependsOn(prepareLibTask);
- prepareLibTask.dependsOn(variantData.preBuildTask);
- }
-
- for (LibraryDependency childLib : lib.getDependencies()) {
- addDependencyToPrepareTask(
- variantData,
- prepareDependenciesTask,
- (LibraryDependencyImpl) childLib);
- }
- }
-
- public void resolveDependencies(
- @NonNull VariantDependencies variantDeps,
- @Nullable VariantDependencies testedVariantDeps,
- @Nullable String testedProjectPath) {
- Multimap<LibraryDependency, VariantDependencies> reverseMap = ArrayListMultimap.create();
-
- resolveDependencyForConfig(variantDeps, testedVariantDeps, testedProjectPath, reverseMap);
- processLibraries(variantDeps.getLibraries(), reverseMap);
- }
-
- private void processLibraries(
- @NonNull Collection<LibraryDependencyImpl> libraries,
- @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
- for (LibraryDependencyImpl lib : libraries) {
- setupPrepareLibraryTask(lib, reverseMap);
- //noinspection unchecked
- processLibraries(
- (Collection<LibraryDependencyImpl>) (List<?>) lib.getDependencies(),
- reverseMap);
- }
- }
-
- private void setupPrepareLibraryTask(
- @NonNull LibraryDependencyImpl libDependency,
- @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
- Task task = maybeCreatePrepareLibraryTask(libDependency, project);
-
- // Use the reverse map to find all the configurations that included this android
- // library so that we can make sure they are built.
- // TODO fix, this is not optimum as we bring in more dependencies than we should.
- Collection<VariantDependencies> configDepList = reverseMap.get(libDependency);
- if (configDepList != null && !configDepList.isEmpty()) {
- for (VariantDependencies configDependencies: configDepList) {
- task.dependsOn(configDependencies.getCompileConfiguration().getBuildDependencies());
- }
- }
-
- // check if this library is created by a parent (this is based on the
- // output file.
- // TODO Fix this as it's fragile
- /*
- This is a somewhat better way but it doesn't work in some project with
- weird setups...
- Project parentProject = DependenciesImpl.getProject(library.getBundle(), projects)
- if (parentProject != null) {
- String configName = library.getProjectVariant()
- if (configName == null) {
- configName = "default"
- }
-
- prepareLibraryTask.dependsOn parentProject.getPath() + ":assemble${configName.capitalize()}"
- }
-*/
-
- }
-
- /**
- * Handles the library and returns a task to "prepare" the library (ie unarchive it). The task
- * will be reused for all projects using the same library.
- *
- * @param library the library.
- * @param project the project
- * @return the prepare task.
- */
- private PrepareLibraryTask maybeCreatePrepareLibraryTask(
- @NonNull LibraryDependencyImpl library,
- @NonNull Project project) {
-
- // create proper key for the map. library here contains all the dependencies which
- // are not relevant for the task (since the task only extract the aar which does not
- // include the dependencies.
- // However there is a possible case of a rewritten dependencies (with resolution strategy)
- // where the aar here could have different dependencies, in which case we would still
- // need the same task.
- // So we extract a LibraryBundle (no dependencies) from the LibraryDependencyImpl to
- // make the map key that doesn't take into account the dependencies.
- LibraryDependencyImpl key = library.getNonTransitiveRepresentation();
-
- PrepareLibraryTask prepareLibraryTask = prepareTaskMap.get(key);
-
- if (prepareLibraryTask == null) {
- String bundleName = GUtil.toCamelCase(library.getName().replaceAll("\\:", " "));
-
- prepareLibraryTask = project.getTasks().create(
- "prepare" + bundleName + "Library", PrepareLibraryTask.class);
-
- prepareLibraryTask.setDescription("Prepare " + library.getName());
- prepareLibraryTask.setBundle(library.getBundle());
- prepareLibraryTask.setExplodedDir(library.getBundleFolder());
- prepareLibraryTask.setVariantName("");
-
- prepareTaskMap.put(key, prepareLibraryTask);
- }
-
- return prepareLibraryTask;
- }
-
- private void resolveDependencyForConfig(
- @NonNull VariantDependencies variantDeps,
- @Nullable VariantDependencies testedVariantDeps,
- @Nullable String testedProjectPath,
- @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
-
- Configuration compileClasspath = variantDeps.getCompileConfiguration();
- Configuration packageClasspath = variantDeps.getPackageConfiguration();
-
- // TODO - shouldn't need to do this - fix this in Gradle
- ensureConfigured(compileClasspath);
- ensureConfigured(packageClasspath);
-
- if (DEBUG_DEPENDENCY) {
- System.out.println(">>>>>>>>>>");
- System.out.println(
- project.getName() + ":" +
- compileClasspath.getName() + "/" +
- packageClasspath.getName());
- }
-
- Set<String> currentUnresolvedDependencies = Sets.newHashSet();
-
- // TODO - defer downloading until required -- This is hard to do as we need the info to build the variant config.
- Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts = Maps.newHashMap();
- collectArtifacts(compileClasspath, artifacts);
- collectArtifacts(packageClasspath, artifacts);
-
- // --- Handle the external/module dependencies ---
- // keep a map of modules already processed so that we don't go through sections of the
- // graph that have been seen elsewhere.
- Map<ModuleVersionIdentifier, List<LibInfo>> foundLibraries = Maps.newHashMap();
- Map<ModuleVersionIdentifier, List<JarInfo>> foundJars = Maps.newHashMap();
-
- // first get the compile dependencies. Note that in both case the libraries and the
- // jars are a graph. The list only contains the first level of dependencies, and
- // they themselves contain transitive dependencies (libraries can contain both, jars only
- // contains jars)
- List<LibInfo> compiledAndroidLibraries = Lists.newArrayList();
- List<JarInfo> compiledJars = Lists.newArrayList();
-
- Set<? extends DependencyResult> dependencyResultSet = compileClasspath.getIncoming()
- .getResolutionResult().getRoot().getDependencies();
-
- for (DependencyResult dependencyResult : dependencyResultSet) {
- if (dependencyResult instanceof ResolvedDependencyResult) {
- addDependency(
- ((ResolvedDependencyResult) dependencyResult).getSelected(),
- variantDeps,
- compiledAndroidLibraries,
- compiledJars,
- foundLibraries,
- foundJars,
- artifacts,
- reverseMap,
- currentUnresolvedDependencies,
- testedProjectPath,
- Collections.<String>emptyList(),
- 0);
- } else if (dependencyResult instanceof UnresolvedDependencyResult) {
- ComponentSelector attempted = ((UnresolvedDependencyResult) dependencyResult).getAttempted();
- if (attempted != null) {
- currentUnresolvedDependencies.add(attempted.toString());
- }
- }
- }
-
- // then the packaged ones.
- List<LibInfo> packagedAndroidLibraries = Lists.newArrayList();
- List<JarInfo> packagedJars = Lists.newArrayList();
-
- dependencyResultSet = packageClasspath.getIncoming()
- .getResolutionResult().getRoot().getDependencies();
-
- for (DependencyResult dependencyResult : dependencyResultSet) {
- if (dependencyResult instanceof ResolvedDependencyResult) {
- addDependency(
- ((ResolvedDependencyResult) dependencyResult).getSelected(),
- variantDeps,
- packagedAndroidLibraries,
- packagedJars,
- foundLibraries,
- foundJars,
- artifacts,
- reverseMap,
- currentUnresolvedDependencies,
- testedProjectPath,
- Collections.<String>emptyList(),
- 0);
- } else if (dependencyResult instanceof UnresolvedDependencyResult) {
- ComponentSelector attempted = ((UnresolvedDependencyResult) dependencyResult)
- .getAttempted();
- if (attempted != null) {
- currentUnresolvedDependencies.add(attempted.toString());
- }
- }
- }
-
- // now look through both results.
- // 1. Handle the compile and package list of Libraries.
- // For Libraries:
- // Only library projects can support provided aar.
- // However, package(publish)-only are still not supported (they don't make sense).
- // For now, provided only dependencies will be kept normally in the compile-graph.
- // However we'll want to not include them in the resource merging.
- // For Applications:
- // All Android libraries must be in both lists.
- // ---
- // Since we reuse the same instance of LibInfo for identical modules
- // we can simply run through each list and look for libs that are in only one.
- // While the list of library is actually a graph, it's fine to look only at the
- // top level ones since the transitive ones are in the same scope as the direct libraries.
- List<LibInfo> copyOfPackagedLibs = Lists.newArrayList(packagedAndroidLibraries);
- boolean isLibrary = extraModelInfo.isLibrary();
-
- for (LibInfo lib : compiledAndroidLibraries) {
- if (!copyOfPackagedLibs.contains(lib)) {
- if (isLibrary || lib.isOptional()) {
- lib.setIsOptional(true);
- } else {
- //noinspection ConstantConditions
- variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
- lib.getResolvedCoordinates().toString(),
- SyncIssue.TYPE_NON_JAR_PROVIDED_DEP,
- String.format(
- "Project %s: provided dependencies can only be jars. %s is an Android Library.",
- project.getName(), lib.getResolvedCoordinates())));
- }
- } else {
- copyOfPackagedLibs.remove(lib);
- }
- }
- // at this stage copyOfPackagedLibs should be empty, if not, error.
- for (LibInfo lib : copyOfPackagedLibs) {
- //noinspection ConstantConditions
- variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
- lib.getResolvedCoordinates().toString(),
- SyncIssue.TYPE_NON_JAR_PACKAGE_DEP,
- String.format(
- "Project %s: apk dependencies can only be jars. %s is an Android Library.",
- project.getName(), lib.getResolvedCoordinates())));
- }
-
- // 2. merge jar dependencies with a single list where items have packaged/compiled properties.
- // since we reuse the same instance of a JarInfo for identical modules, we can use an
- // Identity set (ie both compiledJars and packagedJars will contain the same instance
- // if it's both compiled and packaged)
- Set<JarInfo> jarInfoSet = Sets.newIdentityHashSet();
-
- // go through the graphs of dependencies (jars and libs) and gather all the transitive
- // jar dependencies.
- // At the same this we set the compiled/packaged properties.
- gatherJarDependencies(jarInfoSet, compiledJars, true /*compiled*/, false /*packaged*/);
- gatherJarDependencies(jarInfoSet, packagedJars, false /*compiled*/, true /*packaged*/);
- // at this step, we know that libraries have been checked and libraries can only
- // be in both compiled and packaged scope.
- gatherJarDependenciesFromLibraries(jarInfoSet, compiledAndroidLibraries);
-
- // the final list of JarDependency, created from the list of JarInfo.
- List<JarDependency> jars = Lists.newArrayListWithCapacity(jarInfoSet.size());
-
- // if this is a test dependencies (ie tested dependencies is non null), override
- // packaged attributes for jars that are already in the tested dependencies in order to
- // not package them twice (since the VM loads the classes of both APKs in the same
- // classpath and refuses to load the same class twice)
- if (testedVariantDeps != null) {
- List<JarDependency> jarDependencies = testedVariantDeps.getJarDependencies();
-
- // gather the tested dependencies
- Map<String, String> testedDeps = Maps.newHashMapWithExpectedSize(jarDependencies.size());
-
- for (JarDependency jar : jarDependencies) {
- if (jar.isPackaged()) {
- MavenCoordinates coordinates = jar.getResolvedCoordinates();
- //noinspection ConstantConditions
- testedDeps.put(
- computeVersionLessCoordinateKey(coordinates),
- coordinates.getVersion());
- }
- }
-
- // now go through all the test dependencies and check we don't have the same thing.
- // Skip the ones that are already in the tested variant, and convert the rest
- // to the final immutable instance
- for (JarInfo jar : jarInfoSet) {
- if (jar.isPackaged()) {
- MavenCoordinates coordinates = jar.getResolvedCoordinates();
-
- String testedVersion = testedDeps.get(
- computeVersionLessCoordinateKey(coordinates));
- if (testedVersion != null) {
- // same artifact, skip packaging of the dependency in the test app,
- // whether the version is a match or not.
-
- // if the dependency is present in both tested and test artifact,
- // verify that they are the same version
- if (!testedVersion.equals(coordinates.getVersion())) {
- String artifactInfo = coordinates.getGroupId() + ":" + coordinates.getArtifactId();
- variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
- artifactInfo,
- SyncIssue.TYPE_MISMATCH_DEP,
- String.format(
- "Conflict with dependency '%s'. Resolved versions for app (%s) and test app (%s) differ.",
- artifactInfo,
- testedVersion,
- coordinates.getVersion())));
-
- } else {
- logger.info(String.format(
- "Removed '%s' from packaging of %s: Already in tested package.",
- coordinates,
- variantDeps.getName()));
- }
- } else {
- // new artifact, convert it.
- jars.add(jar.createJarDependency());
- }
- }
- }
- } else {
- // just convert all of them to JarDependency
- for (JarInfo jarInfo : jarInfoSet) {
- jars.add(jarInfo.createJarDependency());
- }
- }
-
- // --- Handle the local jar dependencies ---
-
- // also need to process local jar files, as they are not processed by the
- // resolvedConfiguration result. This only includes the local jar files for this project.
- Set<File> localCompiledJars = Sets.newHashSet();
- for (Dependency dependency : compileClasspath.getAllDependencies()) {
- if (dependency instanceof SelfResolvingDependency &&
- !(dependency instanceof ProjectDependency)) {
- Set<File> files = ((SelfResolvingDependency) dependency).resolve();
- for (File f : files) {
- if (DEBUG_DEPENDENCY) {
- System.out.println("LOCAL compile: " + f.getName());
- }
- // only accept local jar, no other types.
- if (!f.getName().toLowerCase(Locale.getDefault()).endsWith(DOT_JAR)) {
- variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
- f.getAbsolutePath(),
- SyncIssue.TYPE_NON_JAR_LOCAL_DEP,
- String.format(
- "Project %s: Only Jar-type local dependencies are supported. Cannot handle: %s",
- project.getName(), f.getAbsolutePath())));
- } else {
- localCompiledJars.add(f);
- }
- }
- }
- }
-
- Set<File> localPackagedJars = Sets.newHashSet();
- for (Dependency dependency : packageClasspath.getAllDependencies()) {
- if (dependency instanceof SelfResolvingDependency &&
- !(dependency instanceof ProjectDependency)) {
- Set<File> files = ((SelfResolvingDependency) dependency).resolve();
- for (File f : files) {
- if (DEBUG_DEPENDENCY) {
- System.out.println("LOCAL package: " + f.getName());
- }
- // only accept local jar, no other types.
- if (!f.getName().toLowerCase(Locale.getDefault()).endsWith(DOT_JAR)) {
- variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
- f.getAbsolutePath(),
- SyncIssue.TYPE_NON_JAR_LOCAL_DEP,
- String.format(
- "Project %s: Only Jar-type local dependencies are supported. Cannot handle: %s",
- project.getName(), f.getAbsolutePath())));
- } else {
- localPackagedJars.add(f);
- }
- }
- }
- }
-
- // loop through both the compiled and packaged jar to compute the list
- // of jars that are: compile-only, package-only, or both.
- Map<File, JarDependency> localJars = Maps.newHashMap();
- for (File file : localCompiledJars) {
- localJars.put(file, new JarDependency(
- file,
- true /*compiled*/,
- localPackagedJars.contains(file) /*packaged*/,
- null /*resolvedCoordinates*/,
- null /*projectPath*/));
- }
-
- for (File file : localPackagedJars) {
- if (!localCompiledJars.contains(file)) {
- localJars.put(file, new JarDependency(
- file,
- false /*compiled*/,
- true /*packaged*/,
- null /*resolvedCoordinates*/,
- null /*projectPath*/));
- }
- }
-
- if (extraModelInfo.getMode() != STANDARD &&
- compileClasspath.getResolvedConfiguration().hasError()) {
- for (String dependency : currentUnresolvedDependencies) {
- extraModelInfo.handleSyncError(
- dependency,
- SyncIssue.TYPE_UNRESOLVED_DEPENDENCY,
- String.format(
- "Unable to resolve dependency '%s'",
- dependency));
- }
- }
-
- // convert the LibInfo in LibraryDependencyImpl and update the reverseMap
- // with the converted keys
- List<LibraryDependencyImpl> libList = convertLibraryInfoIntoDependency(
- compiledAndroidLibraries, reverseMap);
-
- if (DEBUG_DEPENDENCY) {
- for (LibraryDependency lib : libList) {
- System.out.println("LIB: " + lib);
- }
- for (JarDependency jar : jars) {
- System.out.println("JAR: " + jar);
- }
- for (JarDependency jar : localJars.values()) {
- System.out.println("LOCAL-JAR: " + jar);
- }
- }
-
- variantDeps.addLibraries(libList);
- variantDeps.addJars(jars);
- variantDeps.addLocalJars(localJars.values());
-
- configureBuild(variantDeps);
-
- if (DEBUG_DEPENDENCY) {
- System.out.println(project.getName() + ":" + compileClasspath.getName() + "/" +packageClasspath.getName());
- System.out.println("<<<<<<<<<<");
- }
-
- }
-
- private static List<LibraryDependencyImpl> convertLibraryInfoIntoDependency(
- @NonNull List<LibInfo> libInfos,
- @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
- List<LibraryDependencyImpl> list = Lists.newArrayListWithCapacity(libInfos.size());
-
- // since the LibInfos is a graph and the previous "foundLibraries" map ensure we reuse
- // instance where applicable, we'll create a map to keep track of what we have already
- // converted.
- Map<LibInfo, LibraryDependencyImpl> convertedMap = Maps.newIdentityHashMap();
-
- for (LibInfo libInfo : libInfos) {
- list.add(convertLibInfo(libInfo, reverseMap, convertedMap));
- }
-
- return list;
- }
-
- private static LibraryDependencyImpl convertLibInfo(
- @NonNull LibInfo libInfo,
- @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap,
- @NonNull Map<LibInfo, LibraryDependencyImpl> convertedMap) {
- LibraryDependencyImpl convertedLib = convertedMap.get(libInfo);
- if (convertedLib == null) {
- // first, convert the children.
- @SuppressWarnings("unchecked")
- List<LibInfo> children = (List<LibInfo>) (List<?>) libInfo.getDependencies();
- List<LibraryDependency> convertedChildren = Lists.newArrayListWithCapacity(children.size());
-
- for (LibInfo child : children) {
- convertedChildren.add(convertLibInfo(child, reverseMap, convertedMap));
- }
-
- // now convert the libInfo
- convertedLib = new LibraryDependencyImpl(
- libInfo.getBundle(),
- libInfo.getFolder(),
- convertedChildren,
- libInfo.getName(),
- libInfo.getProjectVariant(),
- libInfo.getProject(),
- libInfo.getRequestedCoordinates(),
- libInfo.getResolvedCoordinates(),
- libInfo.isOptional());
-
- // add it to the map
- convertedMap.put(libInfo, convertedLib);
-
- // and update the reversemap
- // get the items associated with the libInfo. Put in a fresh list as the returned
- // collection is backed by the content of the map.
- Collection<VariantDependencies> values = Lists.newArrayList(reverseMap.get(libInfo));
- reverseMap.removeAll(libInfo);
- reverseMap.putAll(convertedLib, values);
- }
-
- return convertedLib;
- }
-
- private static void gatherJarDependencies(
- Set<JarInfo> outJarInfos,
- Collection<JarInfo> inJarInfos,
- boolean compiled,
- boolean packaged) {
- for (JarInfo jarInfo : inJarInfos) {
- if (!outJarInfos.contains(jarInfo)) {
- outJarInfos.add(jarInfo);
- }
-
- if (compiled) {
- jarInfo.setCompiled(true);
- }
- if (packaged) {
- jarInfo.setPackaged(true);
- }
-
- gatherJarDependencies(outJarInfos, jarInfo.getDependencies(), compiled, packaged);
- }
- }
-
- private static void gatherJarDependenciesFromLibraries(
- Set<JarInfo> outJarInfos,
- Collection<LibInfo> inLibraryDependencies) {
- for (LibInfo libInfo : inLibraryDependencies) {
- gatherJarDependencies(outJarInfos, libInfo.getJarDependencies(),
- true, !libInfo.isOptional());
-
- gatherJarDependenciesFromLibraries(
- outJarInfos,
- libInfo.getLibInfoDependencies());
- }
- }
-
- private void ensureConfigured(Configuration config) {
- for (Dependency dependency : config.getAllDependencies()) {
- if (dependency instanceof ProjectDependency) {
- ProjectDependency projectDependency = (ProjectDependency) dependency;
- project.evaluationDependsOn(projectDependency.getDependencyProject().getPath());
- try {
- ensureConfigured(projectDependency.getProjectConfiguration());
- } catch (Throwable e) {
- throw new UnknownProjectException(String.format(
- "Cannot evaluate module %s : %s",
- projectDependency.getName(), e.getMessage()),
- e);
- }
- }
- }
- }
-
- private void collectArtifacts(
- Configuration configuration,
- Map<ModuleVersionIdentifier,
- List<ResolvedArtifact>> artifacts) {
-
- Set<ResolvedArtifact> allArtifacts;
- if (extraModelInfo.getMode() != STANDARD) {
- allArtifacts = configuration.getResolvedConfiguration().getLenientConfiguration().getArtifacts(
- Specs.satisfyAll());
- } else {
- allArtifacts = configuration.getResolvedConfiguration().getResolvedArtifacts();
- }
-
- for (ResolvedArtifact artifact : allArtifacts) {
- ModuleVersionIdentifier id = artifact.getModuleVersion().getId();
- List<ResolvedArtifact> moduleArtifacts = artifacts.get(id);
-
- if (moduleArtifacts == null) {
- moduleArtifacts = Lists.newArrayList();
- artifacts.put(id, moduleArtifacts);
- }
-
- if (!moduleArtifacts.contains(artifact)) {
- moduleArtifacts.add(artifact);
- }
- }
- }
-
- private static void printIndent(int indent, @NonNull String message) {
- for (int i = 0 ; i < indent ; i++) {
- System.out.print("\t");
- }
-
- System.out.println(message);
- }
-
- private void addDependency(
- @NonNull ResolvedComponentResult resolvedComponentResult,
- @NonNull VariantDependencies configDependencies,
- @NonNull Collection<LibInfo> outLibraries,
- @NonNull List<JarInfo> outJars,
- @NonNull Map<ModuleVersionIdentifier, List<LibInfo>> alreadyFoundLibraries,
- @NonNull Map<ModuleVersionIdentifier, List<JarInfo>> alreadyFoundJars,
- @NonNull Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
- @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap,
- @NonNull Set<String> currentUnresolvedDependencies,
- @Nullable String testedProjectPath,
- @NonNull List<String> projectChain,
- int indent) {
-
- ModuleVersionIdentifier moduleVersion = resolvedComponentResult.getModuleVersion();
- if (configDependencies.getChecker().excluded(moduleVersion)) {
- return;
- }
-
- if (moduleVersion.getName().equals("support-annotations") &&
- moduleVersion.getGroup().equals("com.android.support")) {
- configDependencies.setAnnotationsPresent(true);
- }
-
- List<LibInfo> libsForThisModule = alreadyFoundLibraries.get(moduleVersion);
- List<JarInfo> jarsForThisModule = alreadyFoundJars.get(moduleVersion);
-
- if (libsForThisModule != null) {
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "FOUND LIB: " + moduleVersion.getName());
- }
- outLibraries.addAll(libsForThisModule);
-
- for (LibInfo lib : libsForThisModule) {
- reverseMap.put(lib, configDependencies);
- }
-
- } else if (jarsForThisModule != null) {
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "FOUND JAR: " + moduleVersion.getName());
- }
- outJars.addAll(jarsForThisModule);
- }
- else {
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "NOT FOUND: " + moduleVersion.getName());
- }
- // new module! Might be a jar or a library
-
- // get the nested components first.
- List<LibInfo> nestedLibraries = Lists.newArrayList();
- List<JarInfo> nestedJars = Lists.newArrayList();
-
- Set<? extends DependencyResult> dependencies = resolvedComponentResult.getDependencies();
- for (DependencyResult dependencyResult : dependencies) {
- if (dependencyResult instanceof ResolvedDependencyResult) {
- ResolvedComponentResult selected =
- ((ResolvedDependencyResult) dependencyResult).getSelected();
-
- List<String> newProjectChain = projectChain;
-
- ComponentIdentifier identifier = selected.getId();
- if (identifier instanceof ProjectComponentIdentifier) {
- String projectPath =
- ((ProjectComponentIdentifier) identifier).getProjectPath();
-
- int index = projectChain.indexOf(projectPath);
- if (index != -1) {
- projectChain.add(projectPath);
- String path = Joiner
- .on(" -> ")
- .join(projectChain.subList(index, projectChain.size()));
-
- throw new CircularReferenceException(
- "Circular reference between projects: " + path);
- }
-
- newProjectChain = Lists.newArrayList();
- newProjectChain.addAll(projectChain);
- newProjectChain.add(projectPath);
- }
-
- addDependency(
- selected,
- configDependencies,
- nestedLibraries,
- nestedJars,
- alreadyFoundLibraries,
- alreadyFoundJars,
- artifacts,
- reverseMap,
- currentUnresolvedDependencies,
- testedProjectPath,
- newProjectChain,
- indent + 1);
- } else if (dependencyResult instanceof UnresolvedDependencyResult) {
- ComponentSelector attempted = ((UnresolvedDependencyResult) dependencyResult).getAttempted();
- if (attempted != null) {
- currentUnresolvedDependencies.add(attempted.toString());
- }
- }
- }
-
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "BACK2: " + moduleVersion.getName());
- printIndent(indent, "NESTED LIBS: " + nestedLibraries.size());
- printIndent(indent, "NESTED JARS: " + nestedJars.size());
- }
-
- // now loop on all the artifact for this modules.
- List<ResolvedArtifact> moduleArtifacts = artifacts.get(moduleVersion);
-
- ComponentIdentifier id = resolvedComponentResult.getId();
- String gradlePath = (id instanceof ProjectComponentIdentifier) ?
- ((ProjectComponentIdentifier) id).getProjectPath() : null;
-
- if (moduleArtifacts != null) {
- for (ResolvedArtifact artifact : moduleArtifacts) {
- if (EXT_LIB_ARCHIVE.equals(artifact.getExtension())) {
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "TYPE: AAR");
- }
- if (libsForThisModule == null) {
- libsForThisModule = Lists.newArrayList();
- alreadyFoundLibraries.put(moduleVersion, libsForThisModule);
- }
-
- String path = computeArtifactPath(moduleVersion, artifact);
- String name = computeArtifactName(moduleVersion, artifact);
-
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "NAME: " + name);
- printIndent(indent, "PATH: " + path);
- }
-
- //def explodedDir = project.file("$project.rootProject.buildDir/${FD_INTERMEDIATES}/exploded-aar/$path")
- File explodedDir = project.file(project.getBuildDir() + "/" + FD_INTERMEDIATES + "/exploded-aar/" + path);
- @SuppressWarnings("unchecked")
- LibInfo libInfo = new LibInfo(
- artifact.getFile(),
- explodedDir,
- (List<LibraryDependency>) (List<?>) nestedLibraries,
- nestedJars,
- name,
- artifact.getClassifier(),
- gradlePath,
- null /*requestedCoordinates*/,
- new MavenCoordinatesImpl(artifact));
-
- libsForThisModule.add(libInfo);
- outLibraries.add(libInfo);
- reverseMap.put(libInfo, configDependencies);
-
- } else if (EXT_JAR.equals(artifact.getExtension())) {
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "TYPE: JAR");
- }
- // check this jar does not have a dependency on an library, as this would not work.
- if (!nestedLibraries.isEmpty()) {
- if (testedProjectPath != null && testedProjectPath.equals(gradlePath)) {
- // TODO: make sure this is a direct dependency and not a transitive one.
- // add nested libs as optional somehow...
- for (LibInfo lib : nestedLibraries) {
- lib.setIsOptional(true);
- }
- outLibraries.addAll(nestedLibraries);
-
- } else {
- configDependencies.getChecker()
- .addSyncIssue(extraModelInfo.handleSyncError(
- new MavenCoordinatesImpl(artifact).toString(),
- SyncIssue.TYPE_JAR_DEPEND_ON_AAR,
- String.format(
- "Module '%s' depends on one or more Android Libraries but is a jar",
- moduleVersion)));
- }
- }
-
- if (jarsForThisModule == null) {
- jarsForThisModule = Lists.newArrayList();
- alreadyFoundJars.put(moduleVersion, jarsForThisModule);
- }
-
- JarInfo jarInfo = new JarInfo(
- artifact.getFile(),
- new MavenCoordinatesImpl(artifact),
- gradlePath,
- nestedJars);
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "JAR-INFO: " + jarInfo.toString());
- }
-
- jarsForThisModule.add(jarInfo);
- outJars.add(jarInfo);
-
- } else if (EXT_ANDROID_PACKAGE.equals(artifact.getExtension())) {
- String name = computeArtifactName(moduleVersion, artifact);
-
- configDependencies.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
- name,
- SyncIssue.TYPE_DEPENDENCY_IS_APK,
- String.format(
- "Dependency %s on project %s resolves to an APK archive " +
- "which is not supported as a compilation dependency. File: %s",
- name, project.getName(), artifact.getFile())));
- } else if ("apklib".equals(artifact.getExtension())) {
- String name = computeArtifactName(moduleVersion, artifact);
-
- configDependencies.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
- name,
- SyncIssue.TYPE_DEPENDENCY_IS_APKLIB,
- String.format(
- "Packaging for dependency %s is 'apklib' and is not supported. " +
- "Only 'aar' libraries are supported.", name)));
- } else {
- String name = computeArtifactName(moduleVersion, artifact);
-
- logger.warning(String.format(
- "Unrecognized dependency: '%s' (type: '%s', extension: '%s')",
- name, artifact.getType(), artifact.getExtension()));
- }
- }
- }
-
- if (DEBUG_DEPENDENCY) {
- printIndent(indent, "DONE: " + moduleVersion.getName());
- }
- }
- }
-
- @NonNull
- private String computeArtifactPath(
- @NonNull ModuleVersionIdentifier moduleVersion,
- @NonNull ResolvedArtifact artifact) {
- StringBuilder pathBuilder = new StringBuilder();
-
- pathBuilder.append(normalize(logger, moduleVersion, moduleVersion.getGroup()))
- .append('/')
- .append(normalize(logger, moduleVersion, moduleVersion.getName()))
- .append('/')
- .append(normalize(logger, moduleVersion,
- moduleVersion.getVersion()));
-
- if (artifact.getClassifier() != null && !artifact.getClassifier().isEmpty()) {
- pathBuilder.append('/').append(normalize(logger, moduleVersion,
- artifact.getClassifier()));
- }
-
- return pathBuilder.toString();
- }
-
- @NonNull
- private static String computeArtifactName(
- @NonNull ModuleVersionIdentifier moduleVersion,
- @NonNull ResolvedArtifact artifact) {
- StringBuilder nameBuilder = new StringBuilder();
-
- nameBuilder.append(moduleVersion.getGroup())
- .append(':')
- .append(moduleVersion.getName())
- .append(':')
- .append(moduleVersion.getVersion());
-
- if (artifact.getClassifier() != null && !artifact.getClassifier().isEmpty()) {
- nameBuilder.append(':').append(artifact.getClassifier());
- }
-
- return nameBuilder.toString();
- }
-
- /**
- * Normalize a path to remove all illegal characters for all supported operating systems.
- * {@see http://en.wikipedia.org/wiki/Filename#Comparison%5Fof%5Ffile%5Fname%5Flimitations}
- *
- * @param id the module coordinates that generated this path
- * @param path the proposed path name
- * @return the normalized path name
- */
- static String normalize(ILogger logger, ModuleVersionIdentifier id, String path) {
- if (path == null || path.isEmpty()) {
- logger.info(String.format(
- "When unzipping library '%s:%s:%s, either group, name or version is empty",
- id.getGroup(), id.getName(), id.getVersion()));
- return path;
- }
- // list of illegal characters
- String normalizedPath = path.replaceAll("[%<>:\"/?*\\\\]", "@");
- if (normalizedPath == null || normalizedPath.isEmpty()) {
- // if the path normalization failed, return the original path.
- logger.info(String.format(
- "When unzipping library '%s:%s:%s, the normalized '%s' is empty",
- id.getGroup(), id.getName(), id.getVersion(), path));
- return path;
- }
- try {
- int pathPointer = normalizedPath.length() - 1;
- // do not end your path with either a dot or a space.
- String suffix = "";
- while (pathPointer >= 0 && (normalizedPath.charAt(pathPointer) == '.'
- || normalizedPath.charAt(pathPointer) == ' ')) {
- pathPointer--;
- suffix += "@";
- }
- if (pathPointer < 0) {
- throw new RuntimeException(String.format(
- "When unzipping library '%s:%s:%s, " +
- "the path '%s' cannot be transformed into a valid directory name",
- id.getGroup(), id.getName(), id.getVersion(), path));
- }
- return normalizedPath.substring(0, pathPointer + 1) + suffix;
- } catch (Exception e) {
- logger.error(e, String.format(
- "When unzipping library '%s:%s:%s', " +
- "Path normalization failed for input %s",
- id.getGroup(), id.getName(), id.getVersion(), path));
- return path;
- }
- }
-
- private void configureBuild(VariantDependencies configurationDependencies) {
- addDependsOnTaskInOtherProjects(
- project.getTasks().getByName(JavaBasePlugin.BUILD_NEEDED_TASK_NAME), true,
- JavaBasePlugin.BUILD_NEEDED_TASK_NAME, "compile");
- addDependsOnTaskInOtherProjects(
- project.getTasks().getByName(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME), false,
- JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, "compile");
- }
-
- @NonNull
- public static List<ManifestDependencyImpl> getManifestDependencies(
- List<LibraryDependency> libraries) {
-
- List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size());
-
- for (LibraryDependency lib : libraries) {
- // get the dependencies
- List<ManifestDependencyImpl> children = getManifestDependencies(lib.getDependencies());
- list.add(new ManifestDependencyImpl(lib.getName(), lib.getManifest(), children));
- }
-
- return list;
- }
-
- /**
- * Adds a dependency on tasks with the specified name in other projects. The other projects
- * are determined from project lib dependencies using the specified configuration name.
- * These may be projects this project depends on or projects that depend on this project
- * based on the useDependOn argument.
- *
- * @param task Task to add dependencies to
- * @param useDependedOn if true, add tasks from projects this project depends on, otherwise
- * use projects that depend on this one.
- * @param otherProjectTaskName name of task in other projects
- * @param configurationName name of configuration to use to find the other projects
- */
- private static void addDependsOnTaskInOtherProjects(final Task task, boolean useDependedOn,
- String otherProjectTaskName,
- String configurationName) {
- Project project = task.getProject();
- final Configuration configuration = project.getConfigurations().getByName(
- configurationName);
- task.dependsOn(configuration.getTaskDependencyFromProjectDependency(
- useDependedOn, otherProjectTaskName));
- }
-
- /**
- * Compute a version-less key representing the given coordinates.
- * @param coordinates the coordinate
- * @return the key.
- */
- @NonNull
- private static String computeVersionLessCoordinateKey(@NonNull MavenCoordinates coordinates) {
- StringBuilder sb = new StringBuilder(coordinates.getGroupId());
- sb.append(':').append(coordinates.getArtifactId());
- if (coordinates.getClassifier() != null) {
- sb.append(':').append(coordinates.getClassifier());
- }
- return sb.toString();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExecutionConfigurationUtil.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExecutionConfigurationUtil.java
deleted file mode 100644
index e9ddda3..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExecutionConfigurationUtil.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.ide.common.internal.ExecutorSingleton;
-
-import org.gradle.api.Project;
-
-public class ExecutionConfigurationUtil {
-
- private static final String THREAD_POOL_SIZE_PROPERTY = "com.android.build.threadPoolSize";
-
- public static void setThreadPoolSize(Project project) {
- if (!project.hasProperty(THREAD_POOL_SIZE_PROPERTY)) {
- return;
- }
-
- String threadPoolSizeProperty = project.property(THREAD_POOL_SIZE_PROPERTY).toString();
-
- try {
- ExecutorSingleton.setThreadPoolSize(Integer.parseInt(threadPoolSizeProperty));
- } catch (NumberFormatException e) {
- project.getLogger().error("com.android.threadPoolSize should be an integer.");
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExtraModelInfo.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExtraModelInfo.java
deleted file mode 100644
index af28ddf..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExtraModelInfo.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY;
-import static com.android.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED;
-import static com.android.builder.model.AndroidProject.PROPERTY_INVOKED_FROM_IDE;
-import static com.android.ide.common.blame.parser.JsonEncodedGradleMessageParser.STDOUT_ERROR_TAG;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.AndroidGradleOptions;
-import com.android.build.gradle.api.BaseVariant;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.build.gradle.internal.model.ArtifactMetaDataImpl;
-import com.android.build.gradle.internal.model.JavaArtifactImpl;
-import com.android.build.gradle.internal.model.SyncIssueImpl;
-import com.android.build.gradle.internal.model.SyncIssueKey;
-import com.android.build.gradle.internal.variant.DefaultSourceProviderContainer;
-import com.android.builder.core.ErrorReporter;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.ArtifactMetaData;
-import com.android.builder.model.JavaArtifact;
-import com.android.builder.model.SourceProvider;
-import com.android.builder.model.SourceProviderContainer;
-import com.android.builder.model.SyncIssue;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.MessageJsonSerializer;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Maps;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * For storing additional model information.
- */
-public class ExtraModelInfo extends ErrorReporter {
-
- @NonNull
- private final Project project;
- private final boolean isLibrary;
-
- @NonNull
- private final ErrorFormatMode errorFormatMode;
-
- private final Map<SyncIssueKey, SyncIssue> syncIssues = Maps.newHashMap();
-
- private final Map<String, ArtifactMetaData> extraArtifactMap = Maps.newHashMap();
- private final ListMultimap<String, AndroidArtifact> extraAndroidArtifacts = ArrayListMultimap.create();
- private final ListMultimap<String, JavaArtifact> extraJavaArtifacts = ArrayListMultimap.create();
-
- private final ListMultimap<String, SourceProviderContainer> extraBuildTypeSourceProviders = ArrayListMultimap.create();
- private final ListMultimap<String, SourceProviderContainer> extraProductFlavorSourceProviders = ArrayListMultimap.create();
- private final ListMultimap<String, SourceProviderContainer> extraMultiFlavorSourceProviders = ArrayListMultimap.create();
-
- @Nullable
- private final Gson mGson;
-
- public ExtraModelInfo(@NonNull Project project, boolean isLibrary) {
- super(computeModelQueryMode(project));
- this.project = project;
- this.isLibrary = isLibrary;
- errorFormatMode = computeErrorFormatMode(project);
- if (errorFormatMode == ErrorFormatMode.MACHINE_PARSABLE) {
- GsonBuilder gsonBuilder = new GsonBuilder();
- MessageJsonSerializer.registerTypeAdapters(gsonBuilder);
- mGson = gsonBuilder.create();
- } else {
- mGson = null;
- }
- }
-
- public boolean isLibrary() {
- return isLibrary;
- }
-
- public Map<SyncIssueKey, SyncIssue> getSyncIssues() {
- return syncIssues;
- }
-
- @Override
- @NonNull
- public SyncIssue handleSyncError(@NonNull String data, int type, @NonNull String msg) {
- SyncIssue issue;
- switch (getMode()) {
- case STANDARD:
- if (!isDependencyIssue(type)) {
- throw new GradleException(msg);
- }
- // if it's a dependency issue we don't throw right away. we'll
- // throw during build instead.
- // but we do log.
- project.getLogger().warn("WARNING: " + msg);
- issue = new SyncIssueImpl(type, SyncIssue.SEVERITY_ERROR, data, msg);
- break;
- case IDE_LEGACY:
- // compat mode for the only issue supported before the addition of SyncIssue
- // in the model.
- if (type != SyncIssue.TYPE_UNRESOLVED_DEPENDENCY) {
- throw new GradleException(msg);
- }
- // intended fall-through
- case IDE:
- // new IDE, able to support SyncIssue.
- issue = new SyncIssueImpl(type, SyncIssue.SEVERITY_ERROR, data, msg);
- syncIssues.put(SyncIssueKey.from(issue), issue);
- break;
- default:
- throw new RuntimeException("Unknown SyncIssue type");
- }
-
- return issue;
- }
-
- private static boolean isDependencyIssue(int type) {
- switch (type) {
- case SyncIssue.TYPE_UNRESOLVED_DEPENDENCY:
- case SyncIssue.TYPE_DEPENDENCY_IS_APK:
- case SyncIssue.TYPE_DEPENDENCY_IS_APKLIB:
- case SyncIssue.TYPE_NON_JAR_LOCAL_DEP:
- case SyncIssue.TYPE_NON_JAR_PACKAGE_DEP:
- case SyncIssue.TYPE_NON_JAR_PROVIDED_DEP:
- case SyncIssue.TYPE_JAR_DEPEND_ON_AAR:
- case SyncIssue.TYPE_MISMATCH_DEP:
- return true;
- }
-
- return false;
-
- }
-
- @Override
- public void receiveMessage(@NonNull Message message) {
- StringBuilder errorStringBuilder = new StringBuilder();
- if (errorFormatMode == ErrorFormatMode.HUMAN_READABLE) {
- for (SourceFilePosition pos : message.getSourceFilePositions()) {
- errorStringBuilder.append(pos.toString());
- errorStringBuilder.append(' ');
- }
- if (errorStringBuilder.length() > 0) {
- errorStringBuilder.append(": ");
- }
- errorStringBuilder.append(message.getText()).append("\n");
-
- } else {
- //noinspection ConstantConditions mGson != null when errorFormatMode == MACHINE_PARSABLE
- errorStringBuilder.append(STDOUT_ERROR_TAG)
- .append(mGson.toJson(message)).append("\n");
- }
-
- String messageString = errorStringBuilder.toString();
-
- switch (message.getKind()) {
- case ERROR:
- project.getLogger().error(messageString);
- break;
- case WARNING:
- project.getLogger().warn(messageString);
- break;
- case INFO:
- project.getLogger().info(messageString);
- break;
- case STATISTICS:
- project.getLogger().trace(messageString);
- break;
- case UNKNOWN:
- project.getLogger().debug(messageString);
- break;
- case SIMPLE:
- project.getLogger().info(messageString);
- break;
- }
- }
-
- public Collection<ArtifactMetaData> getExtraArtifacts() {
- return extraArtifactMap.values();
- }
-
- public Collection<AndroidArtifact> getExtraAndroidArtifacts(@NonNull String variantName) {
- return extraAndroidArtifacts.get(variantName);
- }
-
- public Collection<JavaArtifact> getExtraJavaArtifacts(@NonNull String variantName) {
- return extraJavaArtifacts.get(variantName);
- }
-
- public Collection<SourceProviderContainer> getExtraFlavorSourceProviders(
- @NonNull String flavorName) {
- return extraProductFlavorSourceProviders.get(flavorName);
- }
-
- public Collection<SourceProviderContainer> getExtraBuildTypeSourceProviders(
- @NonNull String buildTypeName) {
- return extraBuildTypeSourceProviders.get(buildTypeName);
- }
-
- public void registerArtifactType(@NonNull String name,
- boolean isTest,
- int artifactType) {
-
- if (extraArtifactMap.get(name) != null) {
- throw new IllegalArgumentException(
- String.format("Artifact with name %1$s already registered.", name));
- }
-
- extraArtifactMap.put(name, new ArtifactMetaDataImpl(name, isTest, artifactType));
- }
-
- public void registerBuildTypeSourceProvider(@NonNull String name,
- @NonNull CoreBuildType buildType,
- @NonNull SourceProvider sourceProvider) {
- if (extraArtifactMap.get(name) == null) {
- throw new IllegalArgumentException(String.format(
- "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
- name));
- }
-
- extraBuildTypeSourceProviders.put(buildType.getName(),
- new DefaultSourceProviderContainer(name, sourceProvider));
-
- }
-
- public void registerProductFlavorSourceProvider(@NonNull String name,
- @NonNull CoreProductFlavor productFlavor,
- @NonNull SourceProvider sourceProvider) {
- if (extraArtifactMap.get(name) == null) {
- throw new IllegalArgumentException(String.format(
- "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
- name));
- }
-
- extraProductFlavorSourceProviders.put(productFlavor.getName(),
- new DefaultSourceProviderContainer(name, sourceProvider));
-
- }
-
- public void registerMultiFlavorSourceProvider(@NonNull String name,
- @NonNull String flavorName,
- @NonNull SourceProvider sourceProvider) {
- if (extraArtifactMap.get(name) == null) {
- throw new IllegalArgumentException(String.format(
- "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
- name));
- }
-
- extraMultiFlavorSourceProviders.put(flavorName,
- new DefaultSourceProviderContainer(name, sourceProvider));
- }
-
- public void registerJavaArtifact(
- @NonNull String name,
- @NonNull BaseVariant variant,
- @NonNull String assembleTaskName,
- @NonNull String javaCompileTaskName,
- @NonNull Collection<File> generatedSourceFolders,
- @NonNull Iterable<String> ideSetupTaskNames,
- @NonNull Configuration configuration,
- @NonNull File classesFolder,
- @NonNull File javaResourcesFolder,
- @Nullable SourceProvider sourceProvider) {
- ArtifactMetaData artifactMetaData = extraArtifactMap.get(name);
- if (artifactMetaData == null) {
- throw new IllegalArgumentException(String.format(
- "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
- name));
- }
- if (artifactMetaData.getType() != ArtifactMetaData.TYPE_JAVA) {
- throw new IllegalArgumentException(
- String.format("Artifact with name %1$s is not of type JAVA", name));
- }
-
- JavaArtifact artifact = new JavaArtifactImpl(
- name, assembleTaskName, javaCompileTaskName, ideSetupTaskNames,
- generatedSourceFolders, classesFolder, javaResourcesFolder, null,
- new ConfigurationDependencies(configuration), sourceProvider, null);
-
- extraJavaArtifacts.put(variant.getName(), artifact);
- }
-
- /**
- * Returns whether we are just trying to build a model for the IDE instead of building. This
- * means we will attempt to resolve dependencies even if some are broken/unsupported to avoid
- * failing the import in the IDE.
- */
- private static EvaluationMode computeModelQueryMode(@NonNull Project project) {
- if (AndroidGradleOptions.buildModelOnlyAdvanced(project)) {
- return EvaluationMode.IDE;
- }
-
- if (AndroidGradleOptions.buildModelOnly(project)) {
- return EvaluationMode.IDE_LEGACY;
- }
-
- return EvaluationMode.STANDARD;
- }
-
- private static ErrorFormatMode computeErrorFormatMode(@NonNull Project project) {
- if (AndroidGradleOptions.invokedFromIde(project)) {
- return ErrorFormatMode.MACHINE_PARSABLE;
- } else {
- return ErrorFormatMode.HUMAN_READABLE;
- }
- }
-
- public enum ErrorFormatMode {
- MACHINE_PARSABLE, HUMAN_READABLE
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryCache.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryCache.groovy
deleted file mode 100644
index 05c5901..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryCache.groovy
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal
-
-import com.android.annotations.NonNull
-import com.android.annotations.concurrency.GuardedBy
-import com.google.common.collect.Maps
-import org.gradle.api.Project
-import org.gradle.api.file.FileCopyDetails
-import org.gradle.api.file.RelativePath
-
-import java.util.concurrent.CountDownLatch
-
-import static com.android.SdkConstants.FD_JARS
-/**
- * Cache to library prepareTask.
- *
- * Each project creates its own version of LibraryDependencyImpl, but they all represent the
- * same library. This creates a single task that will unarchive the aar so that this is done only
- * once even for multi-module projects where 2+ modules depend on the same library.
- *
- * The prepareTask is created in the root project always.
- */
-public class LibraryCache {
-
- @NonNull
- private static final LibraryCache sCache = new LibraryCache()
-
- @NonNull
- public static LibraryCache getCache() {
- return sCache
- }
-
- public synchronized unload() {
- bundleLatches.clear();
- }
-
- @GuardedBy("this")
- private final Map<String, CountDownLatch> bundleLatches = Maps.newHashMap()
-
- public void unzipLibrary(
- @NonNull String taskName,
- @NonNull Project project,
- @NonNull File bundle,
- @NonNull File folderOut) {
-
- // only synchronize access to the latch so that unzipping 2+ different
- // libraries in parallel will work.
- boolean newItem = false;
- CountDownLatch latch;
- synchronized (this) {
- String path = bundle.getCanonicalPath()
- latch = bundleLatches.get(path)
- if (latch == null) {
- latch = new CountDownLatch(1)
- bundleLatches.put(path, latch)
- newItem = true
- }
- }
-
- if (newItem) {
- try {
- project.logger.debug("$taskName: ERASE ${folderOut.getPath()}")
-
- unzipAar(bundle, folderOut, project)
-
- project.logger.debug("$taskName: UNZIP ${bundle.getPath()} -> ${folderOut.getPath()}")
- } finally {
- latch.countDown()
- }
- } else {
- latch.await()
- }
- }
-
- public static void unzipAar(File bundle, File folderOut, Project project) {
- folderOut.deleteDir()
- folderOut.mkdirs()
-
- project.copy {
- from project.zipTree(bundle)
- into folderOut
- filesMatching('**/*.jar') { FileCopyDetails details ->
- details.relativePath = new RelativePath(false, FD_JARS).plus(details.relativePath)
- }
- }
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryTaskManager.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryTaskManager.java
deleted file mode 100644
index 2f2c7a3..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryTaskManager.java
+++ /dev/null
@@ -1,613 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.SdkConstants.FN_ANNOTATIONS_ZIP;
-import static com.android.SdkConstants.LIBS_FOLDER;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.internal.scope.AndroidTask;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.MergeFileTask;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.internal.variant.LibVariantOutputData;
-import com.android.build.gradle.internal.variant.LibraryVariantData;
-import com.android.build.gradle.internal.variant.VariantHelper;
-import com.android.build.gradle.tasks.ExtractAnnotations;
-import com.android.build.gradle.tasks.MergeResources;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.dependency.LibraryBundle;
-import com.android.builder.dependency.LibraryDependency;
-import com.android.builder.dependency.ManifestDependency;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.MavenCoordinates;
-import com.android.builder.profile.ExecutionType;
-import com.android.builder.profile.Recorder;
-import com.android.builder.profile.ThreadRecorder;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.file.ConfigurableFileCollection;
-import org.gradle.api.plugins.BasePlugin;
-import org.gradle.api.tasks.Copy;
-import org.gradle.api.tasks.Sync;
-import org.gradle.api.tasks.bundling.Jar;
-import org.gradle.api.tasks.bundling.Zip;
-import org.gradle.api.tasks.compile.JavaCompile;
-import org.gradle.tooling.BuildException;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * TaskManager for creating tasks in an Android library project.
- */
-public class LibraryTaskManager extends TaskManager {
-
- private static final String ANNOTATIONS = "annotations";
-
- private Task assembleDefault;
-
- public LibraryTaskManager (
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- super(project, androidBuilder, extension, sdkHandler, dependencyManager, toolingRegistry);
- }
-
- @Override
- public void createTasksForVariantData(
- @NonNull final TaskFactory tasks,
- @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
- final LibraryVariantData libVariantData = (LibraryVariantData) variantData;
- final GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
- CoreBuildType buildType = variantConfig.getBuildType();
-
- final VariantScope variantScope = variantData.getScope();
-
- final String dirName = variantConfig.getDirName();
-
- createAnchorTasks(tasks, variantScope);
-
- createCheckManifestTask(tasks, variantScope);
-
- // Add a task to create the res values
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createGenerateResValuesTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to process the manifest(s)
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createMergeLibManifestsTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to compile renderscript files.
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createRenderscriptTask(tasks, variantScope);
- return null;
- }
- });
-
- AndroidTask<MergeResources> packageRes = ThreadRecorder.get().record(
- ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
- new Recorder.Block<AndroidTask<MergeResources>>() {
- @Override
- public AndroidTask<MergeResources> call() throws Exception {
- // Create a merge task to only merge the resources from this library and not
- // the dependencies. This is what gets packaged in the aar.
- AndroidTask<MergeResources> mergeResourceTask =
- basicCreateMergeResourcesTask(
- tasks,
- variantScope,
- "package",
- new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + variantScope
- .getVariantConfiguration().getDirName() +
- "/res"),
- false /*includeDependencies*/,
- false /*process9Patch*/);
-
- if (variantData.getVariantDependency().hasNonOptionalLibraries()) {
- // Add a task to merge the resource folders, including the libraries, in order to
- // generate the R.txt file with all the symbols, including the ones from
- // the dependencies.
- createMergeResourcesTask(tasks, variantScope);
- }
-
- mergeResourceTask.configure(tasks,
- new Action<Task>() {
- @Override
- public void execute(Task task) {
- MergeResources mergeResourcesTask = (MergeResources) task;
- mergeResourcesTask.setPublicFile(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName + "/" +
- SdkConstants.FN_PUBLIC_TXT));
- }
- });
-
- return mergeResourceTask;
- }
- });
-
- // Add a task to merge the assets folders
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- createMergeAssetsTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a task to create the BuildConfig class
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createBuildConfigTask(tasks, variantScope);
- return null;
- }
- });
-
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- // Add a task to generate resource source files, directing the location
- // of the r.txt file to be directly in the bundle.
- createProcessResTask(tasks, variantScope,
- new File(variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName),
- false /*generateResourcePackage*/);
-
- // process java resources
- createProcessJavaResTasks(tasks, variantScope);
- return null;
- }
- });
-
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_AIDL_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createAidlTask(tasks, variantScope);
- return null;
- }
- });
-
- // Add a compile task
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_COMPILE_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- AndroidTask<JavaCompile> javacTask = createJavacTask(tasks, variantScope);
- TaskManager.setJavaCompilerTask(javacTask, tasks, variantScope);
- return null;
- }
- });
-
- // package the prebuilt native libs into the bundle folder
- final Sync packageJniLibs = project.getTasks().create(
- variantScope.getTaskName("package", "JniLibs"),
- Sync.class);
-
- // Add dependencies on NDK tasks if NDK plugin is applied.
- if (isNdkTaskNeeded) {
- // Add NDK tasks
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_NDK_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createNdkTasks(variantScope);
- packageJniLibs.dependsOn(variantData.ndkCompileTask);
- packageJniLibs.from(variantData.ndkCompileTask.getSoFolder())
- .include("**/*.so");
- return null;
- }
- });
- } else {
- if (variantData.compileTask != null) {
- variantData.compileTask.dependsOn(getNdkBuildable(variantData));
- } else {
- variantScope.getCompileTask().dependsOn(tasks, getNdkBuildable(variantData));
- }
- }
-
- Sync packageRenderscript = ThreadRecorder.get().record(
- ExecutionType.LIB_TASK_MANAGER_CREATE_PACKAGING_TASK,
- new Recorder.Block<Sync>() {
- @Override
- public Sync call() throws Exception {
- // package from 2 sources.
- packageJniLibs.from(variantConfig.getJniLibsList())
- .include("**/*.so");
- packageJniLibs.into(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName + "/jni"));
-
- // package the renderscript header files files into the bundle folder
- Sync packageRenderscript = project.getTasks().create(
- variantScope.getTaskName("package", "Renderscript"), Sync.class);
- // package from 3 sources. the order is important to make sure the override works well.
- packageRenderscript.from(variantConfig.getRenderscriptSourceList())
- .include("**/*.rsh");
- packageRenderscript.into(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName + "/" + SdkConstants.FD_RENDERSCRIPT));
- return packageRenderscript;
- }
- });
-
- // merge consumer proguard files from different build types and flavors
- MergeFileTask mergeProGuardFileTask = ThreadRecorder.get().record(
- ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_PROGUARD_FILE_TASK,
- new Recorder.Block<MergeFileTask>() {
- @Override
- public MergeFileTask call() throws Exception {
- MergeFileTask mergeProGuardFileTask = project.getTasks().create(
- variantScope.getTaskName("merge", "ProguardFiles"),
- MergeFileTask.class);
- mergeProGuardFileTask.setVariantName(variantConfig.getFullName());
- mergeProGuardFileTask.setInputFiles(
- project.files(variantConfig.getConsumerProguardFiles())
- .getFiles());
- mergeProGuardFileTask.setOutputFile(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName + "/" + LibraryBundle.FN_PROGUARD_TXT));
- return mergeProGuardFileTask;
- }
-
- });
-
- // copy lint.jar into the bundle folder
- Copy lintCopy = project.getTasks().create(
- variantScope.getTaskName("copy", "Lint"), Copy.class);
- lintCopy.dependsOn(LINT_COMPILE);
- lintCopy.from(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- "lint/lint.jar"));
- lintCopy.into(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName));
-
- final Zip bundle = project.getTasks().create(variantScope.getTaskName("bundle"), Zip.class);
-
- if (variantData.getVariantDependency().isAnnotationsPresent()) {
- libVariantData.generateAnnotationsTask =
- createExtractAnnotations(project, variantData);
- }
- if (libVariantData.generateAnnotationsTask != null) {
- bundle.dependsOn(libVariantData.generateAnnotationsTask);
- }
-
- final boolean instrumented = variantConfig.getBuildType().isTestCoverageEnabled();
-
- // data holding dependencies and input for the dex. This gets updated as new
- // post-compilation steps are inserted between the compilation and dx.
- final PostCompilationData pcDataTemp = new PostCompilationData();
-
- final PostCompilationData pcData = ThreadRecorder.get().record(
- ExecutionType.LIB_TASK_MANAGER_CREATE_POST_COMPILATION_TASK,
- new Recorder.Block<PostCompilationData>() {
- @Override
- public PostCompilationData call() throws Exception {
- pcDataTemp.setClassGeneratingTasks(Collections.singletonList(
- variantScope.getJavacTask().getName()));
- pcDataTemp.setLibraryGeneratingTasks(Collections.singletonList(
- variantData.getVariantDependency().getPackageConfiguration()
- .getBuildDependencies()));
- pcDataTemp.setInputFilesCallable(new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- return new ArrayList<File>(
- variantData.javacTask.getOutputs().getFiles().getFiles());
- }
-
- });
- pcDataTemp.setInputDir(variantScope.getJavaOutputDir());
- pcDataTemp.setInputLibraries(Collections.<File>emptyList());
-
- // if needed, instrument the code
- if (instrumented) {
- return createJacocoTask(tasks, variantScope, pcDataTemp);
- }
- return pcDataTemp;
- }
- });
- checkState(pcData != null);
-
- if (buildType.isMinifyEnabled()) {
- // run proguard on output of compile task
- ThreadRecorder.get().record(
- ExecutionType.LIB_TASK_MANAGER_CREATE_PROGUARD_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- File outFile = maybeCreateProguardTasks(tasks, variantScope,
- pcData);
- checkNotNull(outFile);
- pcData.setInputFiles(Collections.singletonList(outFile));
- pcData.setInputDirCallable(null);
- pcData.setInputLibraries(Collections.<File>emptyList());
- return null;
- }
- });
- } else {
- // package the local jar in libs/
- ThreadRecorder.get().record(
- ExecutionType.LIB_TASK_MANAGER_CREATE_PACKAGE_LOCAL_JAR,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- Sync packageLocalJar = project.getTasks().create(
- variantScope.getTaskName("package", "LocalJar"), Sync.class);
- packageLocalJar.from(
- DependencyManager
- .getPackagedLocalJarFileList(
- variantData.getVariantDependency())
- .toArray());
- packageLocalJar.into(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName + "/" + LIBS_FOLDER));
-
- // add the input libraries. This is only going to be the agent jar if applicable
- // due to how inputLibraries is initialized.
- // TODO: clean this.
- packageLocalJar.from(pcData.getInputLibrariesCallable());
- TaskManager.optionalDependsOn(
- packageLocalJar,
- pcData.getLibraryGeneratingTasks());
- pcData.setLibraryGeneratingTasks(
- Collections.singletonList(packageLocalJar));
-
- // jar the classes.
- Jar jar = project.getTasks().create(
- variantScope.getTaskName("package", "Jar"), Jar.class);
- jar.dependsOn(variantScope.getMergeJavaResourcesTask().getName());
-
- // add the class files (whether they are instrumented or not.
- jar.from(pcData.getInputDirCallable());
- TaskManager.optionalDependsOn(jar, pcData.getClassGeneratingTasks());
- pcData.setClassGeneratingTasks(Collections.singletonList(jar));
-
- jar.from(variantScope.getJavaResourcesDestinationDir());
-
- jar.setDestinationDir(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName));
- jar.setArchiveName("classes.jar");
-
- String packageName = variantConfig.getPackageFromManifest();
- if (packageName == null) {
- throw new BuildException("Failed to read manifest", null);
- }
-
- packageName = packageName.replace(".", "/");
-
- jar.exclude(packageName + "/R.class");
- jar.exclude(packageName + "/R$*.class");
- if (!getExtension().getPackageBuildConfig()) {
- jar.exclude(packageName + "/Manifest.class");
- jar.exclude(packageName + "/Manifest$*.class");
- jar.exclude(packageName + "/BuildConfig.class");
- }
-
- if (libVariantData.generateAnnotationsTask != null) {
- // In case extract annotations strips out private typedef annotation classes
- jar.dependsOn(libVariantData.generateAnnotationsTask);
- }
- return null;
- }
- });
- }
-
- bundle.dependsOn(packageRes.getName(), packageRenderscript, lintCopy, packageJniLibs,
- mergeProGuardFileTask);
- TaskManager.optionalDependsOn(bundle, pcData.getClassGeneratingTasks());
- TaskManager.optionalDependsOn(bundle, pcData.getLibraryGeneratingTasks());
-
- bundle.setDescription("Assembles a bundle containing the library in " +
- variantConfig.getFullName() + ".");
- bundle.setDestinationDir(new File(variantScope.getGlobalScope().getOutputsDir(), "aar"));
- bundle.setArchiveName(project.getName() + "-" + variantConfig.getBaseName() + "."
- + BuilderConstants.EXT_LIB_ARCHIVE);
- bundle.setExtension(BuilderConstants.EXT_LIB_ARCHIVE);
- bundle.from(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName));
- bundle.from(new File(
- variantScope.getGlobalScope().getIntermediatesDir(),
- ANNOTATIONS + "/" + dirName));
-
- // get the single output for now, though that may always be the case for a library.
- LibVariantOutputData variantOutputData = libVariantData.getOutputs().get(0);
- variantOutputData.packageLibTask = bundle;
-
- variantData.assembleVariantTask.dependsOn(bundle);
- variantOutputData.assembleTask = variantData.assembleVariantTask;
-
- if (getExtension().getDefaultPublishConfig().equals(variantConfig.getFullName())) {
- VariantHelper.setupDefaultConfig(project,
- variantData.getVariantDependency().getPackageConfiguration());
-
- // add the artifact that will be published
- project.getArtifacts().add("default", bundle);
-
- getAssembleDefault().dependsOn(variantData.assembleVariantTask);
- }
-
- // also publish the artifact with its full config name
- if (getExtension().getPublishNonDefault()) {
- project.getArtifacts().add(
- variantData.getVariantDependency().getPublishConfiguration().getName(), bundle);
- bundle.setClassifier(
- variantData.getVariantDependency().getPublishConfiguration().getName());
- }
-
- // configure the variant to be testable.
- variantConfig.setOutput(new LibraryBundle(
- bundle.getArchivePath(),
- new File(variantScope.getGlobalScope().getIntermediatesDir(),
- DIR_BUNDLES + "/" + dirName),
- variantData.getName(),
- project.getPath()) {
- @Override
- @Nullable
- public String getProjectVariant() {
- return variantData.getName();
- }
-
- @NonNull
- @Override
- public List<LibraryDependency> getDependencies() {
- return variantConfig.getDirectLibraries();
- }
-
- @NonNull
- @Override
- public List<? extends AndroidLibrary> getLibraryDependencies() {
- return variantConfig.getDirectLibraries();
- }
-
- @NonNull
- @Override
- public List<? extends ManifestDependency> getManifestDependencies() {
- return variantConfig.getDirectLibraries();
- }
-
- @Override
- @Nullable
- public MavenCoordinates getRequestedCoordinates() {
- return null;
- }
-
- @Override
- @Nullable
- public MavenCoordinates getResolvedCoordinates() {
- return null;
- }
-
- @Override
- @NonNull
- protected File getJarsRootFolder() {
- return getFolder();
- }
-
- @Override
- public boolean isOptional() {
- return false;
- }
-
- });
-
- ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_LINT_TASK,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createLintTasks(tasks, variantScope);
- return null;
- }
- });
- }
-
- public ExtractAnnotations createExtractAnnotations(
- final Project project,
- final BaseVariantData variantData) {
- final GradleVariantConfiguration config = variantData.getVariantConfiguration();
-
- final ExtractAnnotations task = project.getTasks().create(
- variantData.getScope().getTaskName("extract", "Annotations"),
- ExtractAnnotations.class);
- task.setDescription(
- "Extracts Android annotations for the " + variantData.getVariantConfiguration()
- .getFullName()
- + " variant into the archive file");
- task.setGroup(BasePlugin.BUILD_GROUP);
- task.variant = variantData;
- task.setDestinationDir(new File(
- variantData.getScope().getGlobalScope().getIntermediatesDir(),
- ANNOTATIONS + "/" + config.getDirName()));
- task.output = new File(task.getDestinationDir(), FN_ANNOTATIONS_ZIP);
- task.classDir = new File(variantData.getScope().getGlobalScope().getIntermediatesDir(),
- "classes/" + variantData.getVariantConfiguration().getDirName());
- task.setSource(variantData.getJavaSources());
- task.encoding = getExtension().getCompileOptions().getEncoding();
- task.setSourceCompatibility(
- getExtension().getCompileOptions().getSourceCompatibility().toString());
- ConventionMappingHelper.map(task, "classpath", new Callable<ConfigurableFileCollection>() {
- @Override
- public ConfigurableFileCollection call() throws Exception {
- return project.files(androidBuilder.getCompileClasspath(config));
- }
- });
- task.dependsOn(variantData.getScope().getJavacTask().getName());
-
- // Setup the boot classpath just before the task actually runs since this will
- // force the sdk to be parsed. (Same as in compileTask)
- task.doFirst(new Action<Task>() {
- @Override
- public void execute(Task task) {
- if (task instanceof ExtractAnnotations) {
- ExtractAnnotations extractAnnotations = (ExtractAnnotations) task;
- extractAnnotations.bootClasspath = androidBuilder.getBootClasspathAsStrings();
- }
- }
- });
-
- return task;
- }
-
- private Task getAssembleDefault() {
- if (assembleDefault == null) {
- assembleDefault = project.getTasks().findByName("assembleDefault");
- }
- return assembleDefault;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
deleted file mode 100644
index 34d2240..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-import static java.io.File.separator;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.LintCliClient;
-import com.android.tools.lint.LintCliFlags;
-import com.android.tools.lint.Warning;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.LintRequest;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Project;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-public class LintGradleClient extends LintCliClient {
- private final AndroidProject mModelProject;
- private final String mVariantName;
- private final org.gradle.api.Project mGradleProject;
- private List<File> mCustomRules = Lists.newArrayList();
- private File mSdkHome;
-
- public LintGradleClient(
- @NonNull IssueRegistry registry,
- @NonNull LintCliFlags flags,
- @NonNull org.gradle.api.Project gradleProject,
- @NonNull AndroidProject modelProject,
- @Nullable File sdkHome,
- @Nullable String variantName) {
- super(flags);
- mGradleProject = gradleProject;
- mModelProject = modelProject;
- mVariantName = variantName;
- mSdkHome = sdkHome;
- mRegistry = registry;
- }
-
- public void setCustomRules(List<File> customRules) {
- mCustomRules = customRules;
- }
-
- @NonNull
- @Override
- public List<File> findRuleJars(@NonNull Project project) {
- return mCustomRules;
- }
-
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- // Should not be called by lint since we supply an explicit set of projects
- // to the LintRequest
- throw new IllegalStateException();
- }
-
- @Override
- public File getSdkHome() {
- if (mSdkHome != null) {
- return mSdkHome;
- }
- return super.getSdkHome();
- }
-
- @Override
- @Nullable
- public File getCacheDir(boolean create) {
- File dir = new File(mGradleProject.getRootProject().getBuildDir(),
- FD_INTERMEDIATES + separator + "lint-cache"); //$NON-NLS-1$
- if (dir.exists() || create && dir.mkdirs()) {
- return dir;
- }
-
- return super.getCacheDir(create);
- }
-
- @Override
- @NonNull
- protected LintRequest createLintRequest(@NonNull List<File> files) {
- return new LintGradleRequest(this, mModelProject, mGradleProject, mVariantName, files);
- }
-
- /** Run lint with the given registry and return the resulting warnings */
- @NonNull
- public List<Warning> run(@NonNull IssueRegistry registry) throws IOException {
- run(registry, Collections.<File>emptyList());
- return mWarnings;
- }
-
- /**
- * Given a list of results from separate variants, merge them into a single
- * list of warnings, and mark their
- * @param warningMap a map from variant to corresponding warnings
- * @param project the project model
- * @return a merged list of issues
- */
- @NonNull
- public static List<Warning> merge(
- @NonNull Map<Variant,List<Warning>> warningMap,
- @NonNull AndroidProject project) {
- // Easy merge?
- if (warningMap.size() == 1) {
- return warningMap.values().iterator().next();
- }
- int maxCount = 0;
- for (List<Warning> warnings : warningMap.values()) {
- int size = warnings.size();
- maxCount = Math.max(size, maxCount);
- }
- if (maxCount == 0) {
- return Collections.emptyList();
- }
-
- int totalVariantCount = project.getVariants().size();
-
- List<Warning> merged = Lists.newArrayListWithExpectedSize(2 * maxCount);
-
- // Map fro issue to message to line number to file name to canonical warning
- Map<Issue,Map<String, Map<Integer, Map<String, Warning>>>> map =
- Maps.newHashMapWithExpectedSize(2 * maxCount);
-
- for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
- Variant variant = entry.getKey();
- List<Warning> warnings = entry.getValue();
- for (Warning warning : warnings) {
- Map<String,Map<Integer,Map<String,Warning>>> messageMap = map.get(warning.issue);
- if (messageMap == null) {
- messageMap = Maps.newHashMap();
- map.put(warning.issue, messageMap);
- }
- Map<Integer, Map<String, Warning>> lineMap = messageMap.get(warning.message);
- if (lineMap == null) {
- lineMap = Maps.newHashMap();
- messageMap.put(warning.message, lineMap);
- }
- Map<String, Warning> fileMap = lineMap.get(warning.line);
- if (fileMap == null) {
- fileMap = Maps.newHashMap();
- lineMap.put(warning.line, fileMap);
- }
- String fileName = warning.file != null ? warning.file.getName() : "<unknown>";
- Warning canonical = fileMap.get(fileName);
- if (canonical == null) {
- canonical = warning;
- fileMap.put(fileName, canonical);
- canonical.variants = Sets.newHashSet();
- canonical.gradleProject = project;
- merged.add(canonical);
- }
- canonical.variants.add(variant);
- }
- }
-
- // Clear out variants on any nodes that define all
- for (Warning warning : merged) {
- if (warning.variants != null && warning.variants.size() == totalVariantCount) {
- // If this error is present in all variants, just clear it out
- warning.variants = null;
- }
-
- }
-
- Collections.sort(merged);
- return merged;
- }
-
- @Override
- protected void addProgressPrinter() {
- // No progress printing from the Gradle lint task; gradle tasks
- // do not really do that, even for long-running jobs.
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
deleted file mode 100644
index 8193a8e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
+++ /dev/null
@@ -1,658 +0,0 @@
-package com.android.build.gradle.internal;
-
-import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT;
-import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
-import static java.io.File.separatorChar;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.BuildTypeContainer;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.JavaLibrary;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.SourceProvider;
-import com.android.builder.model.SourceProviderContainer;
-import com.android.builder.model.Variant;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Project;
-import com.android.utils.Pair;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * An implementation of Lint's {@link Project} class wrapping a Gradle model (project or
- * library)
- */
-public class LintGradleProject extends Project {
- protected AndroidVersion mMinSdkVersion;
- protected AndroidVersion mTargetSdkVersion;
-
- private LintGradleProject(
- @NonNull LintGradleClient client,
- @NonNull File dir,
- @NonNull File referenceDir,
- @NonNull File manifest) {
- super(client, dir, referenceDir);
- mGradleProject = true;
- mMergeManifests = true;
- mDirectLibraries = Lists.newArrayList();
- readManifest(manifest);
- }
-
- /**
- * Creates a {@link com.android.build.gradle.internal.LintGradleProject} from
- * the given {@link com.android.builder.model.AndroidProject} definition for
- * a given {@link com.android.builder.model.Variant}, and returns it along with
- * a set of lint custom rule jars applicable for the given model project.
- *
- * @param client the client
- * @param project the model project
- * @param variant the variant
- * @param gradleProject the gradle project
- * @return a pair of new project and list of custom rule jars
- */
- @NonNull
- public static Pair<LintGradleProject, List<File>> create(
- @NonNull LintGradleClient client,
- @NonNull AndroidProject project,
- @NonNull Variant variant,
- @NonNull org.gradle.api.Project gradleProject) {
- File dir = gradleProject.getProjectDir();
- AppGradleProject lintProject = new AppGradleProject(client, dir,
- dir, project, variant);
-
- List<File> customRules = Lists.newArrayList();
- File appLintJar = new File(gradleProject.getBuildDir(),
- "lint" + separatorChar + "lint.jar");
- if (appLintJar.exists()) {
- customRules.add(appLintJar);
- }
-
- Set<AndroidLibrary> libraries = Sets.newHashSet();
- Dependencies dependencies = variant.getMainArtifact().getDependencies();
- for (AndroidLibrary library : dependencies.getLibraries()) {
- lintProject.addDirectLibrary(createLibrary(client, library, libraries, customRules));
- }
-
- return Pair.<LintGradleProject,List<File>>of(lintProject, customRules);
- }
-
- @Override
- protected void initialize() {
- // Deliberately not calling super; that code is for ADT compatibility
- }
-
- protected void readManifest(File manifest) {
- if (manifest.exists()) {
- try {
- String xml = Files.toString(manifest, Charsets.UTF_8);
- Document document = XmlUtils.parseDocumentSilently(xml, true);
- if (document != null) {
- readManifest(document);
- }
- } catch (IOException e) {
- mClient.log(e, "Could not read manifest %1$s", manifest);
- }
- }
- }
-
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- protected static boolean dependsOn(@NonNull Dependencies dependencies,
- @NonNull String artifact) {
- for (AndroidLibrary library : dependencies.getLibraries()) {
- if (dependsOn(library, artifact)) {
- return true;
- }
- }
- return false;
- }
-
- protected static boolean dependsOn(@NonNull AndroidLibrary library, @NonNull String artifact) {
- if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
- if (library.getJarFile().getName().startsWith("support-v4-")) {
- return true;
- }
-
- } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
- File bundle = library.getBundle();
- if (bundle.getName().startsWith("appcompat-v7-")) {
- return true;
- }
- }
-
- for (AndroidLibrary dependency : library.getLibraryDependencies()) {
- if (dependsOn(dependency, artifact)) {
- return true;
- }
- }
-
- return false;
- }
-
- void addDirectLibrary(@NonNull Project project) {
- mDirectLibraries.add(project);
- }
-
- @NonNull
- private static LibraryProject createLibrary(@NonNull LintGradleClient client,
- @NonNull AndroidLibrary library,
- @NonNull Set<AndroidLibrary> seen, List<File> customRules) {
- seen.add(library);
- File dir = library.getFolder();
- LibraryProject project = new LibraryProject(client, dir, dir, library);
-
- File ruleJar = library.getLintJar();
- if (ruleJar.exists()) {
- customRules.add(ruleJar);
- }
-
- for (AndroidLibrary dependent : library.getLibraryDependencies()) {
- if (!seen.contains(dependent)) {
- project.addDirectLibrary(createLibrary(client, dependent, seen, customRules));
- }
- }
-
- return project;
- }
-
- private static class AppGradleProject extends LintGradleProject {
- private AndroidProject mProject;
- private Variant mVariant;
- private List<SourceProvider> mProviders;
- private List<SourceProvider> mTestProviders;
-
- private AppGradleProject(
- @NonNull LintGradleClient client,
- @NonNull File dir,
- @NonNull File referenceDir,
- @NonNull AndroidProject project,
- @NonNull Variant variant) {
- //TODO FIXME: handle multi-apk
- super(client, dir, referenceDir,
- variant.getMainArtifact().getOutputs().iterator().next().getGeneratedManifest());
-
- mProject = project;
- mVariant = variant;
- }
-
- @Override
- public boolean isLibrary() {
- return mProject.isLibrary();
- }
-
- @Override
- public AndroidProject getGradleProjectModel() {
- return mProject;
- }
-
- @Override
- public Variant getCurrentVariant() {
- return mVariant;
- }
-
- private List<SourceProvider> getSourceProviders() {
- if (mProviders == null) {
- List<SourceProvider> providers = Lists.newArrayList();
- AndroidArtifact mainArtifact = mVariant.getMainArtifact();
-
- providers.add(mProject.getDefaultConfig().getSourceProvider());
-
- for (String flavorName : mVariant.getProductFlavors()) {
- for (ProductFlavorContainer flavor : mProject.getProductFlavors()) {
- if (flavorName.equals(flavor.getProductFlavor().getName())) {
- providers.add(flavor.getSourceProvider());
- break;
- }
- }
- }
-
- SourceProvider multiProvider = mainArtifact.getMultiFlavorSourceProvider();
- if (multiProvider != null) {
- providers.add(multiProvider);
- }
-
- String buildTypeName = mVariant.getBuildType();
- for (BuildTypeContainer buildType : mProject.getBuildTypes()) {
- if (buildTypeName.equals(buildType.getBuildType().getName())) {
- providers.add(buildType.getSourceProvider());
- break;
- }
- }
-
- SourceProvider variantProvider = mainArtifact.getVariantSourceProvider();
- if (variantProvider != null) {
- providers.add(variantProvider);
- }
-
- mProviders = providers;
- }
-
- return mProviders;
- }
-
- private List<SourceProvider> getTestSourceProviders() {
- if (mTestProviders == null) {
- List<SourceProvider> providers = Lists.newArrayList();
-
- ProductFlavorContainer defaultConfig = mProject.getDefaultConfig();
- for (SourceProviderContainer extra : defaultConfig.getExtraSourceProviders()) {
- String artifactName = extra.getArtifactName();
- if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
- providers.add(extra.getSourceProvider());
- }
- }
-
- for (String flavorName : mVariant.getProductFlavors()) {
- for (ProductFlavorContainer flavor : mProject.getProductFlavors()) {
- if (flavorName.equals(flavor.getProductFlavor().getName())) {
- for (SourceProviderContainer extra : flavor.getExtraSourceProviders()) {
- String artifactName = extra.getArtifactName();
- if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
- providers.add(extra.getSourceProvider());
- }
- }
- }
- }
- }
-
- String buildTypeName = mVariant.getBuildType();
- for (BuildTypeContainer buildType : mProject.getBuildTypes()) {
- if (buildTypeName.equals(buildType.getBuildType().getName())) {
- for (SourceProviderContainer extra : buildType.getExtraSourceProviders()) {
- String artifactName = extra.getArtifactName();
- if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
- providers.add(extra.getSourceProvider());
- }
- }
- }
- }
-
- mTestProviders = providers;
- }
-
- return mTestProviders;
- }
-
- @NonNull
- @Override
- public List<File> getManifestFiles() {
- if (mManifestFiles == null) {
- mManifestFiles = Lists.newArrayList();
- for (SourceProvider provider : getSourceProviders()) {
- File manifestFile = provider.getManifestFile();
- if (manifestFile.exists()) { // model returns path whether or not it exists
- mManifestFiles.add(manifestFile);
- }
- }
- }
-
- return mManifestFiles;
- }
-
- @NonNull
- @Override
- public List<File> getProguardFiles() {
- if (mProguardFiles == null) {
- ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
- mProguardFiles = Lists.newArrayList();
- for (File file : flavor.getProguardFiles()) {
- if (file.exists()) {
- mProguardFiles.add(file);
- }
- }
- try {
- for (File file : flavor.getConsumerProguardFiles()) {
- if (file.exists()) {
- mProguardFiles.add(file);
- }
- }
- } catch (Throwable t) {
- // On some models, this threw
- // org.gradle.tooling.model.UnsupportedMethodException:
- // Unsupported method: BaseConfig.getConsumerProguardFiles().
- // Playing it safe for a while.
- }
- }
-
- return mProguardFiles;
- }
-
- @NonNull
- @Override
- public List<File> getResourceFolders() {
- if (mResourceFolders == null) {
- mResourceFolders = Lists.newArrayList();
- for (SourceProvider provider : getSourceProviders()) {
- Collection<File> resDirs = provider.getResDirectories();
- for (File res : resDirs) {
- if (res.exists()) { // model returns path whether or not it exists
- mResourceFolders.add(res);
- }
- }
- }
-
- for (File file : mVariant.getMainArtifact().getGeneratedResourceFolders()) {
- if (file.exists()) {
- mResourceFolders.add(file);
- }
- }
-
- }
-
- return mResourceFolders;
- }
-
- @NonNull
- @Override
- public List<File> getJavaSourceFolders() {
- if (mJavaSourceFolders == null) {
- mJavaSourceFolders = Lists.newArrayList();
- for (SourceProvider provider : getSourceProviders()) {
- Collection<File> srcDirs = provider.getJavaDirectories();
- for (File srcDir : srcDirs) {
- if (srcDir.exists()) { // model returns path whether or not it exists
- mJavaSourceFolders.add(srcDir);
- }
- }
- }
-
- for (File file : mVariant.getMainArtifact().getGeneratedSourceFolders()) {
- if (file.exists()) {
- mJavaSourceFolders.add(file);
- }
- }
- }
-
- return mJavaSourceFolders;
- }
-
- @NonNull
- @Override
- public List<File> getTestSourceFolders() {
- if (mTestSourceFolders == null) {
- mTestSourceFolders = Lists.newArrayList();
- for (SourceProvider provider : getTestSourceProviders()) {
- Collection<File> srcDirs = provider.getJavaDirectories();
- for (File srcDir : srcDirs) {
- if (srcDir.exists()) { // model returns path whether or not it exists
- mTestSourceFolders.add(srcDir);
- }
- }
- }
- }
-
- return mTestSourceFolders;
- }
-
- @NonNull
- @Override
- public List<File> getJavaClassFolders() {
- if (mJavaClassFolders == null) {
- mJavaClassFolders = new ArrayList<File>(1);
- File outputClassFolder = mVariant.getMainArtifact().getClassesFolder();
- if (outputClassFolder.exists()) {
- mJavaClassFolders.add(outputClassFolder);
- }
- }
-
- return mJavaClassFolders;
- }
-
- @NonNull
- @Override
- public List<File> getJavaLibraries() {
- if (mJavaLibraries == null) {
- Collection<JavaLibrary> libs = mVariant.getMainArtifact().getDependencies().getJavaLibraries();
- mJavaLibraries = Lists.newArrayListWithExpectedSize(libs.size());
- for (JavaLibrary lib : libs) {
- File jar = lib.getJarFile();
- if (jar.exists()) {
- mJavaLibraries.add(jar);
- }
- }
- }
- return mJavaLibraries;
- }
-
- @Nullable
- @Override
- public String getPackage() {
- // For now, lint only needs the manifest package; not the potentially variant specific
- // package. As part of the Gradle work on the Lint API we should make two separate
- // package lookup methods -- one for the manifest package, one for the build package
- if (mPackage == null) { // only used as a fallback in case manifest somehow is null
- String packageName = mProject.getDefaultConfig().getProductFlavor().getApplicationId();
- if (packageName != null) {
- return packageName;
- }
- }
-
- return mPackage; // from manifest
- }
-
- @Override
- @NonNull
- public AndroidVersion getMinSdkVersion() {
- if (mMinSdkVersion == null) {
- ApiVersion minSdk = mVariant.getMergedFlavor().getMinSdkVersion();
- if (minSdk == null) {
- ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
- minSdk = flavor.getMinSdkVersion();
- }
- if (minSdk != null) {
- mMinSdkVersion = LintUtils.convertVersion(minSdk, mClient.getTargets());
- } else {
- mMinSdkVersion = super.getMinSdkVersion(); // from manifest
- }
- }
-
- return mMinSdkVersion;
- }
-
- @Override
- @NonNull
- public AndroidVersion getTargetSdkVersion() {
- if (mTargetSdkVersion == null) {
- ApiVersion targetSdk = mVariant.getMergedFlavor().getTargetSdkVersion();
- if (targetSdk == null) {
- ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
- targetSdk = flavor.getTargetSdkVersion();
- }
- if (targetSdk != null) {
- mTargetSdkVersion = LintUtils.convertVersion(targetSdk, mClient.getTargets());
- } else {
- mTargetSdkVersion = super.getTargetSdkVersion(); // from manifest
- }
- }
-
- return mTargetSdkVersion;
- }
-
- @Override
- public int getBuildSdk() {
- String compileTarget = mProject.getCompileTarget();
- AndroidVersion version = AndroidTargetHash.getPlatformVersion(compileTarget);
- if (version != null) {
- return version.getApiLevel();
- }
-
- return super.getBuildSdk();
- }
-
- @Nullable
- @Override
- public Boolean dependsOn(@NonNull String artifact) {
- if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
- if (mSupportLib == null) {
- Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
- mSupportLib = dependsOn(dependencies, artifact);
- }
- return mSupportLib;
- } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
- if (mAppCompat == null) {
- Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
- mAppCompat = dependsOn(dependencies, artifact);
- }
- return mAppCompat;
- } else {
- return super.dependsOn(artifact);
- }
- }
- }
-
- private static class LibraryProject extends LintGradleProject {
- private AndroidLibrary mLibrary;
-
- private LibraryProject(
- @NonNull LintGradleClient client,
- @NonNull File dir,
- @NonNull File referenceDir,
- @NonNull AndroidLibrary library) {
- super(client, dir, referenceDir, library.getManifest());
- mLibrary = library;
-
- // TODO: Make sure we don't use this project for any source library projects!
- mReportIssues = false;
- }
-
- @Override
- public boolean isLibrary() {
- return true;
- }
-
- @Override
- public AndroidLibrary getGradleLibraryModel() {
- return mLibrary;
- }
-
- @Override
- public Variant getCurrentVariant() {
- return null;
- }
-
- @NonNull
- @Override
- public List<File> getManifestFiles() {
- if (mManifestFiles == null) {
- File manifest = mLibrary.getManifest();
- if (manifest.exists()) {
- mManifestFiles = Collections.singletonList(manifest);
- } else {
- mManifestFiles = Collections.emptyList();
- }
- }
-
- return mManifestFiles;
- }
-
- @NonNull
- @Override
- public List<File> getProguardFiles() {
- if (mProguardFiles == null) {
- File proguardRules = mLibrary.getProguardRules();
- if (proguardRules.exists()) {
- mProguardFiles = Collections.singletonList(proguardRules);
- } else {
- mProguardFiles = Collections.emptyList();
- }
- }
-
- return mProguardFiles;
- }
-
- @NonNull
- @Override
- public List<File> getResourceFolders() {
- if (mResourceFolders == null) {
- File folder = mLibrary.getResFolder();
- if (folder.exists()) {
- mResourceFolders = Collections.singletonList(folder);
- } else {
- mResourceFolders = Collections.emptyList();
- }
- }
-
- return mResourceFolders;
- }
-
- @NonNull
- @Override
- public List<File> getJavaSourceFolders() {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public List<File> getTestSourceFolders() {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public List<File> getJavaClassFolders() {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public List<File> getJavaLibraries() {
- if (mJavaLibraries == null) {
- mJavaLibraries = Lists.newArrayList();
- File jarFile = mLibrary.getJarFile();
- if (jarFile.exists()) {
- mJavaLibraries.add(jarFile);
- }
-
- for (File local : mLibrary.getLocalJars()) {
- if (local.exists()) {
- mJavaLibraries.add(local);
- }
- }
- }
-
- return mJavaLibraries;
- }
-
- @Nullable
- @Override
- public Boolean dependsOn(@NonNull String artifact) {
- if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
- if (mSupportLib == null) {
- mSupportLib = dependsOn(mLibrary, artifact);
- }
- return mSupportLib;
- } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
- if (mAppCompat == null) {
- mAppCompat = dependsOn(mLibrary, artifact);
- }
- return mAppCompat;
- } else {
- return super.dependsOn(artifact);
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java
deleted file mode 100644
index 8a13bcb..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.client.api.LintRequest;
-import com.android.tools.lint.detector.api.Project;
-import com.android.utils.Pair;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-class LintGradleRequest extends LintRequest {
- @NonNull private final LintGradleClient mLintClient;
- @NonNull private final org.gradle.api.Project mGradleProject;
- @Nullable private final String mVariantName;
- @NonNull private final AndroidProject mModelProject;
-
- public LintGradleRequest(
- @NonNull LintGradleClient client,
- @NonNull AndroidProject modelProject,
- @NonNull org.gradle.api.Project gradleProject,
- @Nullable String variantName,
- @NonNull List<File> files) {
- super(client, files);
- mLintClient = client;
- mModelProject = modelProject;
- mGradleProject = gradleProject;
- mVariantName = variantName;
- }
-
- @Nullable
- @Override
- public Collection<Project> getProjects() {
- if (mProjects == null) {
- Variant variant = findVariant(mModelProject, mVariantName);
- if (variant == null) {
- mProjects = Collections.emptyList();
- return mProjects;
- }
- Pair<LintGradleProject,List<File>> result = LintGradleProject.create(
- mLintClient, mModelProject, variant, mGradleProject);
- mProjects = Collections.<Project>singletonList(result.getFirst());
- mLintClient.setCustomRules(result.getSecond());
- }
-
- return mProjects;
- }
-
- private static Variant findVariant(@NonNull AndroidProject project,
- @Nullable String variantName) {
- if (variantName != null) {
- for (Variant variant : project.getVariants()) {
- if (variantName.equals(variant.getName())) {
- return variant;
- }
- }
- }
-
- if (!project.getVariants().isEmpty()) {
- return project.getVariants().iterator().next();
- }
-
- return null;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.java
deleted file mode 100644
index c9f4ed2..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.res2.MergingException;
-import com.android.utils.ILogger;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logger;
-
-/**
- * Implementation of Android's {@link ILogger} over Gradle's {@link Logger}.
- */
-public class LoggerWrapper implements ILogger {
-
- private final Logger logger;
-
- private final LogLevel infoLogLevel;
-
- public LoggerWrapper(@NonNull Logger logger) {
- this(logger, LogLevel.INFO);
- }
-
- /**
- * Allow {@link ILogger} info() level messages to be remapped to e.g. {@link
- * LogLevel}.LIFECYCLE} rather than INFO
- *
- * This is useful for installs and uninstalls, so install details can be shown without the user
- * having to use the noisy INFO log level, while not flagging informational log output as
- * warnings or errors.
- */
- public LoggerWrapper(@NonNull Logger logger, @NonNull LogLevel infoLogLevel) {
- this.logger = logger;
- this.infoLogLevel = infoLogLevel;
- }
-
- @Override
- public void error(Throwable throwable, String s, Object... objects) {
- if (throwable instanceof MergingException) {
- // MergingExceptions have a known cause: they aren't internal errors, they
- // are errors in the user's code, so a full exception is not helpful (and
- // these exceptions should include a pointer to the user's error right in
- // the message).
- //
- // Furthermore, these exceptions are already caught by the MergeResources
- // and MergeAsset tasks, so don't duplicate the output
- return;
- }
-
- if (!logger.isEnabled(LogLevel.ERROR)) {
- return;
- }
-
- if (objects != null && objects.length > 0) {
- s = String.format(s, objects);
- }
-
- if (throwable == null) {
- logger.log(LogLevel.ERROR, s);
-
- } else {
- logger.log(LogLevel.ERROR, s, throwable);
- }
- }
-
- @Override
- public void warning(@NonNull String s, Object... objects) {
- if (!logger.isEnabled(LogLevel.WARN)) {
- return;
- }
- if (objects == null || objects.length == 0) {
- logger.log(LogLevel.WARN, s);
- } else {
- logger.log(LogLevel.WARN, String.format(s, objects));
- }
- }
-
- @Override
- public void info(@NonNull String s, Object... objects) {
- if (!logger.isEnabled(infoLogLevel)) {
- return;
- }
- if (objects == null || objects.length == 0) {
- logger.log(infoLogLevel, s);
- } else {
- logger.log(infoLogLevel, String.format(s, objects));
- }
- }
-
- @Override
- public void verbose(@NonNull String s, Object... objects) {
- if (!logger.isEnabled(LogLevel.DEBUG)) {
- return;
- }
- if (objects == null || objects.length == 0) {
- logger.log(LogLevel.DEBUG, s);
-
- } else {
- logger.log(LogLevel.DEBUG, String.format(s, objects));
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/NdkHandler.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/NdkHandler.java
deleted file mode 100644
index dca6ee0..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/NdkHandler.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.core.Toolchain;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.PreciseRevision;
-import com.android.utils.Pair;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.Closeables;
-
-import org.gradle.api.InvalidUserDataException;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Handles NDK related information.
- */
-public class NdkHandler {
-
- @Nullable
- private String compileSdkVersion;
- private boolean resolvedSdkVersion;
- private final Toolchain toolchain;
- private final String toolchainVersion;
- private final File ndkDirectory;
-
- private Map<Pair<Toolchain, Abi>, PreciseRevision> defaultToolchainVersions = Maps.newHashMap();
-
-
- public NdkHandler(
- @NonNull File projectDir,
- @Nullable String compileSdkVersion,
- @NonNull String toolchainName,
- @NonNull String toolchainVersion) {
- if (compileSdkVersion != null) {
- setCompileSdkVersion(compileSdkVersion);
- }
- this.toolchain = Toolchain.getByName(toolchainName);
- this.toolchainVersion = toolchainVersion;
- ndkDirectory = findNdkDirectory(projectDir);
- }
-
- @Nullable
- public String getCompileSdkVersion() {
- if (!resolvedSdkVersion) {
- resolveCompileSdkVersion();
- }
- return compileSdkVersion;
- }
-
- /**
- * Retrieve the newest supported version if it is not the specified version is not supported.
- *
- * An older NDK may not support the specified compiledSdkVersion. In that case, determine what
- * is the newest supported version and modify compileSdkVersion.
- */
- private void resolveCompileSdkVersion() {
- if (compileSdkVersion == null) {
- return;
- }
- File platformFolder = new File(ndkDirectory, "/platforms/" + compileSdkVersion);
- if (!platformFolder.exists()) {
- int targetVersion;
- try {
- targetVersion = Integer.parseInt(compileSdkVersion.substring("android-".length()));
- } catch (NumberFormatException ignore) {
- // If the targetVerison is not a number, most likely it is a preview version.
- // In that case, assume we are using the highest available version.
- targetVersion = Integer.MAX_VALUE;
- }
-
- File[] platformFolders = new File(ndkDirectory, "/platforms/").listFiles(
- new FileFilter() {
- @Override
- public boolean accept(File file) {
- return file.isDirectory();
- }
- });
- int highestVersion = 0;
- for(File platform :platformFolders) {
- if (platform.getName().startsWith("android-")) {
- try {
- int version = Integer.parseInt(
- platform.getName().substring("android-".length()));
- if (version > highestVersion && version < targetVersion) {
- highestVersion = version;
- compileSdkVersion = "android-" + version;
- }
- } catch(NumberFormatException ignore) {
- }
- }
- }
- }
- resolvedSdkVersion = true;
- }
-
- public void setCompileSdkVersion(@NonNull String compileSdkVersion) {
- // Ensure compileSdkVersion is in platform hash string format (e.g. "android-21").
- AndroidVersion androidVersion = AndroidTargetHash.getVersionFromHash(compileSdkVersion);
- if (androidVersion == null) {
- this.compileSdkVersion = null;
- } else {
- this.compileSdkVersion = AndroidTargetHash.getPlatformHashString(androidVersion);
- }
- resolvedSdkVersion = false;
- }
-
- public Toolchain getToolchain() {
- return toolchain;
- }
-
- public String getToolchainVersion() {
- return toolchainVersion;
- }
-
- /**
- * Determine the location of the NDK directory.
- *
- * The NDK directory can be set in the local.properties file or using the ANDROID_NDK_HOME
- * environment variable.
- */
- private static File findNdkDirectory(File projectDir) {
- File localProperties = new File(projectDir, FN_LOCAL_PROPERTIES);
-
- if (localProperties.isFile()) {
-
- Properties properties = new Properties();
- InputStreamReader reader = null;
- try {
- //noinspection IOResourceOpenedButNotSafelyClosed
- FileInputStream fis = new FileInputStream(localProperties);
- reader = new InputStreamReader(fis, Charsets.UTF_8);
- properties.load(reader);
- } catch (FileNotFoundException ignored) {
- // ignore since we check up front and we don't want to fail on it anyway
- // in case there's an env var.
- } catch (IOException e) {
- throw new RuntimeException(String.format("Unable to read %1$s.", localProperties), e);
- } finally {
- try {
- Closeables.close(reader, true /* swallowIOException */);
- } catch (IOException e) {
- // ignore.
- }
- }
-
- String ndkDirProp = properties.getProperty("ndk.dir");
- if (ndkDirProp != null) {
- return new File(ndkDirProp);
- }
-
- } else {
- String envVar = System.getenv("ANDROID_NDK_HOME");
- if (envVar != null) {
- return new File(envVar);
- }
- }
- return null;
- }
-
- /**
- * Returns the directory of the NDK.
- */
- @Nullable
- public File getNdkDirectory() {
- return ndkDirectory;
- }
-
- /**
- * Return true if NDK directory is configured.
- */
- public boolean isNdkDirConfigured() {
- return ndkDirectory != null;
- }
-
- private static String getToolchainPrefix(Toolchain toolchain, Abi abi) {
- if (toolchain == Toolchain.GCC) {
- return abi.getGccToolchainPrefix();
- } else {
- return "llvm";
- }
- }
-
- /**
- * Return the directory containing the toolchain.
- *
- * @param toolchain toolchain to use.
- * @param toolchainVersion toolchain version to use.
- * @param abi target ABI of the toolchaina
- * @return a directory that contains the executables.
- */
- private File getToolchainPath(
- Toolchain toolchain,
- String toolchainVersion,
- Abi abi) {
- String version = toolchainVersion.isEmpty()
- ? getDefaultToolchainVersion(toolchain, abi).toString()
- : toolchainVersion;
-
- File prebuiltFolder = new File(
- ndkDirectory,
- "toolchains/" + getToolchainPrefix(toolchain, abi) + "-" + version + "/prebuilt");
-
- String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
- String hostOs;
- if (osName.contains("windows")) {
- hostOs = "windows";
- } else if (osName.contains("mac")) {
- hostOs = "darwin";
- } else {
- hostOs = "linux";
- }
-
- // There should only be one directory in the prebuilt folder. If there are more than one
- // attempt to determine the right one based on the operating system.
- File[] toolchainPaths = prebuiltFolder.listFiles(
- new FileFilter() {
- @Override
- public boolean accept(File file) {
- return file.isDirectory();
- }
- });
-
- if (toolchainPaths == null) {
- throw new InvalidUserDataException("Unable to find toolchain: " + prebuiltFolder);
- }
- if (toolchainPaths.length == 1) {
- return toolchainPaths[0];
- }
-
- // Use 64-bit toolchain if available.
- File toolchainPath = new File(prebuiltFolder, hostOs + "-x86_64");
- if (toolchainPath.isDirectory()) {
- return toolchainPath;
- }
-
- // Fallback to 32-bit if we can't find the 64-bit toolchain.
- String osString = (osName.equals("windows")) ? hostOs : hostOs + "-x86";
- toolchainPath = new File(prebuiltFolder, osString);
- if (toolchainPath.isDirectory()) {
- return toolchainPath;
- } else {
- throw new InvalidUserDataException("Unable to find toolchain prebuilt folder in: "
- + prebuiltFolder);
- }
- }
-
- /**
- * Returns the sysroot directory for the toolchain.
- */
- public String getSysroot(Abi abi) {
- if (getCompileSdkVersion() == null) {
- return "";
- } else {
- return ndkDirectory + "/platforms/" + getCompileSdkVersion() + "/arch-"
- + abi.getArchitecture();
- }
- }
-
- /**
- * Return the directory containing prebuilt binaries such as gdbserver.
- */
- public File getPrebuiltDirectory(Abi abi) {
- return new File(ndkDirectory, "prebuilt/android-" + abi.getArchitecture());
- }
-
- /**
- * Return true if compiledSdkVersion supports 64 bits ABI.
- */
- public boolean supports64Bits() {
- if (getCompileSdkVersion() == null) {
- return false;
- }
- String targetString = getCompileSdkVersion().replace("android-", "");
- try {
- return Integer.parseInt(targetString) >= 20;
- } catch (NumberFormatException ignored) {
- // "android-L" supports 64-bits.
- return true;
- }
- }
-
- /**
- * Return the default version of the specified toolchain for a target abi.
- *
- * The default version is the highest version found in the NDK for the specified toolchain and
- * ABI. The result is cached for performance.
- */
- private PreciseRevision getDefaultToolchainVersion(Toolchain toolchain, final Abi abi) {
- PreciseRevision defaultVersion = defaultToolchainVersions.get(Pair.of(toolchain, abi));
- if (defaultVersion != null) {
- return defaultVersion;
- }
-
- final String toolchainPrefix = getToolchainPrefix(toolchain, abi);
- File toolchains = new File(ndkDirectory, "toolchains");
- File[] toolchainsForAbi = toolchains.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.startsWith(toolchainPrefix);
- }
- });
- if (toolchainsForAbi == null || toolchainsForAbi.length == 0) {
- throw new RuntimeException(
- "No toolchains found in the NDK toolchains folder for ABI with prefix: "
- + toolchainPrefix);
- }
-
- // Once we have a list of toolchains, we look the highest version
- PreciseRevision bestRevision = null;
- for (File toolchainFolder : toolchainsForAbi) {
- String folderName = toolchainFolder.getName();
- String version = folderName.substring(toolchainPrefix.length() + 1);
- try {
- PreciseRevision revision = PreciseRevision.parseRevision(version);
- if (bestRevision == null || revision.compareTo(bestRevision) > 0) {
- bestRevision = revision;
- }
- } catch (NumberFormatException ignore) {
- }
- }
- defaultToolchainVersions.put(Pair.of(toolchain, abi), bestRevision);
- if (bestRevision == null) {
- throw new RuntimeException("Unable to find a valid toolchain in " + toolchains);
- }
- return bestRevision;
- }
-
- /**
- * Return the version of gcc that will be used by the NDK.
- *
- * Gcc is used by clang for linking. It also contains gnu-libstdc++.
- *
- * If the gcc toolchain is used, then it's simply the toolchain version requested by the user.
- * If clang is used, then it depends the target abi.
- */
- public String getGccToolchainVersion(Abi abi) {
- return (toolchain == Toolchain.GCC && !toolchainVersion.isEmpty())
- ? toolchainVersion
- : getDefaultToolchainVersion(Toolchain.GCC, abi).toString();
- }
-
- /**
- * Return the folder containing gcc that will be used by the NDK.
- */
- public File getDefaultGccToolchainPath(Abi abi) {
- return getToolchainPath(Toolchain.GCC, getGccToolchainVersion(abi), abi);
- }
-
- /**
- * Returns a list of all ABI.
- */
- public static Collection<Abi> getAbiList() {
- return ImmutableList.copyOf(Abi.values());
- }
-
- /**
- * Returns a list of 32-bits ABI.
- */
- public static Collection<Abi> getAbiList32() {
- ImmutableList.Builder<Abi> builder = ImmutableList.builder();
- for (Abi abi : Abi.values()) {
- if (!abi.supports64Bits()) {
- builder.add(abi);
- }
- }
- return builder.build();
- }
-
- /**
- * Returns a list of supported ABI.
- */
- public Collection<Abi> getSupportedAbis() {
- return supports64Bits() ? getAbiList() : getAbiList32();
- }
-
- /**
- * Return the executable for compiling C code.
- */
- public File getCCompiler(Abi abi) {
- String compiler = toolchain == Toolchain.CLANG ? "clang" : abi.getGccExecutablePrefix() + "-gcc";
- return new File(getToolchainPath(toolchain, toolchainVersion, abi), "bin/" + compiler);
- }
-
- /**
- * Return the executable for compiling C++ code.
- */
- public File getCppCompiler(Abi abi) {
- String compiler = toolchain == Toolchain.CLANG ? "clang++" : abi.getGccExecutablePrefix() + "-g++";
- return new File(getToolchainPath(toolchain, toolchainVersion, abi), "bin/" + compiler);
- }
-
- /**
- * Return the executable for removing debug symbols from a shared object.
- */
- public File getStripCommand(Abi abi) {
- String strip = toolchain == Toolchain.CLANG ? "ndk-strip" : abi.getGccExecutablePrefix() + "-strip";
- return new File(getToolchainPath(toolchain, toolchainVersion, abi), "bin/" + strip);
- }
-
- /**
- * Return a list of include directories for an STl.
- */
- public List<File> getStlIncludes(@Nullable String stlName, @NonNull Abi abi) {
- File stlBaseDir = new File(ndkDirectory, "sources/cxx-stl/");
- if (stlName == null || stlName.isEmpty()) {
- stlName = "system";
- } else if (stlName.contains("_")) {
- stlName = stlName.substring(0, stlName.indexOf('_'));
- }
-
- List<File> includeDirs = Lists.newArrayList();
- if (stlName.equals("system")) {
- includeDirs.add(new File(stlBaseDir, "system/include"));
- } else if (stlName.equals("stlport")) {
- includeDirs.add(new File(stlBaseDir, "stlport/stlport"));
- includeDirs.add(new File(stlBaseDir, "gabi++/include"));
- } else if (stlName.equals("gnustl")) {
- String gccToolchainVersion = getGccToolchainVersion(abi);
- includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + gccToolchainVersion + "/include"));
- includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + gccToolchainVersion +
- "/libs/" + abi.getName() + "/include"));
- includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + gccToolchainVersion +
- "/include/backward"));
- } else if (stlName.equals("gabi++")) {
- includeDirs.add(new File(stlBaseDir, "gabi++/include"));
- } else if (stlName.equals("c++")) {
- includeDirs.add(new File(stlBaseDir, "llvm-libc++/libcxx/include"));
- includeDirs.add(new File(stlBaseDir, "gabi++/include"));
- includeDirs.add(new File(stlBaseDir, "../android/support/include"));
- }
-
- return includeDirs;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/PostCompilationData.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/PostCompilationData.java
deleted file mode 100644
index e914086..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/PostCompilationData.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.Callables;
-
-import java.io.File;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * Class to hold data to setup the many optional post-compilation steps.
- */
-public class PostCompilationData {
-
- @Nullable
- private List<?> classGeneratingTasks;
-
- @Nullable
- private List<?> libraryGeneratingTasks;
-
- @Nullable
- private Callable<List<File>> inputFiles;
-
- @Nullable
- private Callable<File> inputDir;
-
- @Nullable
- private Callable<File> javaResourcesInputDir;
-
- @Nullable
- private Callable<List<File>> inputLibraries;
-
- @NonNull
- public List<?> getClassGeneratingTasks() {
- Preconditions.checkState(classGeneratingTasks != null);
- return classGeneratingTasks;
- }
-
- public void setClassGeneratingTasks(@NonNull List<?> classGeneratingTasks) {
- this.classGeneratingTasks = classGeneratingTasks;
- }
-
- @NonNull
- public List<?> getLibraryGeneratingTasks() {
- Preconditions.checkState(libraryGeneratingTasks != null);
- return libraryGeneratingTasks;
- }
-
- public void setLibraryGeneratingTasks(@NonNull List<?> libraryGeneratingTasks) {
- this.libraryGeneratingTasks = libraryGeneratingTasks;
- }
-
- @Nullable
- public Callable<List<File>> getInputFilesCallable() {
- return inputFiles;
- }
-
- public void setInputFiles(@Nullable List<File> inputFiles) {
- this.inputFiles = Callables.returning(inputFiles);
- }
-
- public void setInputFilesCallable(@Nullable Callable<List<File>> inputFiles) {
- this.inputFiles = inputFiles;
- }
-
- @Nullable
- public Callable<File> getInputDirCallable() {
- return inputDir;
- }
-
- public void setInputDir(@NonNull File inputDir) {
- this.inputDir = Callables.returning(inputDir);
- }
-
- public void setInputDirCallable(@Nullable Callable<File> inputDir) {
- this.inputDir = inputDir;
- }
-
- @Nullable
- public Callable<File> getJavaResourcesInputDirCallable() {
- return javaResourcesInputDir;
- }
-
- public void setJavaResourcesInputDir(@NonNull File javaResourcesInputDir) {
- this.javaResourcesInputDir = Callables.returning(javaResourcesInputDir);
- }
-
- public void setJavaResourcesInputDirCallable(@Nullable Callable<File> javaResourcesInputDir) {
- this.javaResourcesInputDir = javaResourcesInputDir;
- }
-
- @Nullable
- public Callable<List<File>> getInputLibrariesCallable() {
- return inputLibraries;
- }
-
- public void setInputLibraries(@NonNull List<File> inputLibraries) {
- this.inputLibraries = Callables.returning(inputLibraries);
- }
-
- public void setInputLibrariesCallable(@Nullable Callable<List<File>> inputLibraries) {
- this.inputLibraries = inputLibraries;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorCombo.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorCombo.java
deleted file mode 100644
index 561612f..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorCombo.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.DimensionAware;
-import com.android.builder.model.ProductFlavor;
-import com.android.utils.StringHelper;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.Named;
-
-import java.util.List;
-
-/**
- * A combination of product flavors for a variant, each belonging to a different flavor dimension.
- */
-public class ProductFlavorCombo<T extends DimensionAware & Named> {
- private String name;
-
- @NonNull
- private final List<T> flavorList;
-
- /**
- * Create a ProductFlavorCombo.
- * @param flavors Lists of ProductFlavor.
- */
- public ProductFlavorCombo(@NonNull T... flavors) {
- flavorList = ImmutableList.copyOf(flavors);
- }
-
- public ProductFlavorCombo(@NonNull Iterable<T> flavors) {
- flavorList = ImmutableList.copyOf(flavors);
- }
-
- @NonNull
- public String getName() {
- if (name == null) {
- boolean first = true;
- StringBuilder sb = new StringBuilder();
- for (T flavor : flavorList) {
- if (first) {
- sb.append(flavor.getName());
- first = false;
- } else {
- sb.append(StringHelper.capitalize(flavor.getName()));
- }
- }
- name = sb.toString();
- }
- return name;
- }
-
- @NonNull
- public List<T> getFlavorList() {
- return flavorList;
- }
-
- /**
- * Creates a list containing all combinations of ProductFlavors of the given dimensions.
- * @param flavorDimensions The dimensions each product flavor can belong to.
- * @param productFlavors An iterable of all ProductFlavors in the project..
- * @return A list of ProductFlavorCombo representing all combinations of ProductFlavors.
- */
- @NonNull
- public static <S extends DimensionAware & Named> List<ProductFlavorCombo<S>> createCombinations(
- @Nullable List<String> flavorDimensions,
- @NonNull Iterable<S> productFlavors) {
-
- List <ProductFlavorCombo<S>> result = Lists.newArrayList();
- if (flavorDimensions == null || flavorDimensions.isEmpty()) {
- for (S flavor : productFlavors) {
- result.add(new ProductFlavorCombo<S>(ImmutableList.of(flavor)));
- }
- } else {
- // need to group the flavor per dimension.
- // First a map of dimension -> list(ProductFlavor)
- ArrayListMultimap<String, S> map = ArrayListMultimap.create();
- for (S flavor : productFlavors) {
- String flavorDimension = flavor.getDimension();
-
- if (flavorDimension == null) {
- throw new RuntimeException(String.format(
- "Flavor '%1$s' has no flavor dimension.", flavor.getName()));
- }
- if (!flavorDimensions.contains(flavorDimension)) {
- throw new RuntimeException(String.format(
- "Flavor '%1$s' has unknown dimension '%2$s'.",
- flavor.getName(), flavor.getDimension()));
- }
-
- map.put(flavorDimension, flavor);
- }
-
- createProductFlavorCombinations(result,
- Lists.<S>newArrayListWithCapacity(flavorDimensions.size()),
- 0, flavorDimensions, map);
- }
- return result;
- }
-
- /**
- * Remove all null reference from an array and create an ImmutableList it.
- */
- private static ImmutableList<ProductFlavor> filterNullFromArray(ProductFlavor[] flavors) {
- ImmutableList.Builder<ProductFlavor> builder = ImmutableList.builder();
- for (ProductFlavor flavor : flavors) {
- if (flavor != null) {
- builder.add(flavor);
- }
- }
- return builder.build();
- }
-
- private static <S extends DimensionAware & Named> void createProductFlavorCombinations(
- List<ProductFlavorCombo<S>> flavorGroups,
- List<S> group,
- int index,
- List<String> flavorDimensionList,
- ListMultimap<String, S> map) {
- if (index == flavorDimensionList.size()) {
- flavorGroups.add(new ProductFlavorCombo<S>(Iterables.filter(group, Predicates.notNull())));
- return;
- }
-
- // fill the array at the current index.
- // get the dimension name that matches the index we are filling.
- String dimension = flavorDimensionList.get(index);
-
- // from our map, get all the possible flavors in that dimension.
- List<S> flavorList = map.get(dimension);
-
- // loop on all the flavors to add them to the current index and recursively fill the next
- // indices.
- if (flavorList.isEmpty()) {
- throw new RuntimeException(String.format(
- "No flavor is associated with flavor dimension '%1$s'.", dimension));
- } else {
- for (S flavor : flavorList) {
- group.add(flavor);
- createProductFlavorCombinations(
- flavorGroups, group, index + 1, flavorDimensionList, map);
- group.remove(group.size() - 1);
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java
deleted file mode 100644
index f106613..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.LibraryRequest;
-import com.android.builder.sdk.DefaultSdkLoader;
-import com.android.builder.sdk.PlatformLoader;
-import com.android.builder.sdk.SdkInfo;
-import com.android.builder.sdk.SdkLoader;
-import com.android.builder.sdk.TargetInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.google.common.base.Charsets;
-import com.google.common.base.Preconditions;
-import com.google.common.io.Closeables;
-
-import org.gradle.api.Project;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.Collection;
-import java.util.Properties;
-
-/**
- * Handles the all things SDK for the Gradle plugin. There is one instance per project, around
- * a singleton {@link com.android.builder.sdk.SdkLoader}.
- */
-public class SdkHandler {
-
- // Used for injecting SDK location in tests.
- public static File sTestSdkFolder;
-
- @NonNull
- private final ILogger logger;
-
- private SdkLoader sdkLoader;
- private File sdkFolder;
- private File ndkFolder;
- private boolean isRegularSdk = true;
-
- public static void setTestSdkFolder(File testSdkFolder) {
- sTestSdkFolder = testSdkFolder;
- }
-
- public SdkHandler(@NonNull Project project,
- @NonNull ILogger logger) {
- this.logger = logger;
- findLocation(project);
- }
-
- public SdkInfo getSdkInfo() {
- SdkLoader sdkLoader = getSdkLoader();
- return sdkLoader.getSdkInfo(logger);
- }
-
- public void initTarget(
- @NonNull String targetHash,
- @NonNull FullRevision buildToolRevision,
- @NonNull Collection<LibraryRequest> usedLibraries,
- @NonNull AndroidBuilder androidBuilder) {
- Preconditions.checkNotNull(targetHash, "android.compileSdkVersion is missing!");
- Preconditions.checkNotNull(buildToolRevision, "android.buildToolsVersion is missing!");
-
- SdkLoader sdkLoader = getSdkLoader();
-
- SdkInfo sdkInfo = sdkLoader.getSdkInfo(logger);
- TargetInfo targetInfo = sdkLoader.getTargetInfo(targetHash, buildToolRevision, logger);
-
- androidBuilder.setTargetInfo(sdkInfo, targetInfo, usedLibraries);
- }
-
- @Nullable
- public File getSdkFolder() {
- return sdkFolder;
- }
-
- @Nullable
- public File getAndCheckSdkFolder() {
- if (sdkFolder == null) {
- throw new RuntimeException(
- "SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.");
- }
-
- return sdkFolder;
- }
-
- public synchronized SdkLoader getSdkLoader() {
- if (sdkLoader == null) {
- if (isRegularSdk) {
- getAndCheckSdkFolder();
-
- // check if the SDK folder actually exist.
- // For internal test we provide a fake SDK location through
- // setTestSdkFolder in order to have an SDK, even though we don't use it
- // so in this case we ignore the check.
- if (sTestSdkFolder == null && !sdkFolder.isDirectory()) {
- throw new RuntimeException(String.format(
- "The SDK directory '%1$s' does not exist.", sdkFolder));
- }
-
- sdkLoader = DefaultSdkLoader.getLoader(sdkFolder);
- } else {
- sdkLoader = PlatformLoader.getLoader(sdkFolder);
- }
- }
-
- return sdkLoader;
- }
-
- public synchronized void unload() {
- if (sdkLoader != null) {
- if (isRegularSdk) {
- DefaultSdkLoader.unload();
- } else {
- PlatformLoader.unload();
- }
-
- sdkLoader = null;
- }
- }
-
- @Nullable
- public File getNdkFolder() {
- return ndkFolder;
- }
-
- private void findSdkLocation(@NonNull Properties properties, @NonNull File rootDir) {
- String sdkDirProp = properties.getProperty("sdk.dir");
- if (sdkDirProp != null) {
- sdkFolder = new File(sdkDirProp);
- if (!sdkFolder.isAbsolute()) {
- sdkFolder = new File(rootDir, sdkDirProp);
- }
- return;
- }
-
- sdkDirProp = properties.getProperty("android.dir");
- if (sdkDirProp != null) {
- sdkFolder = new File(rootDir, sdkDirProp);
- isRegularSdk = false;
- return;
- }
-
- String envVar = System.getenv("ANDROID_HOME");
- if (envVar != null) {
- sdkFolder = new File(envVar);
- return;
- }
-
- String property = System.getProperty("android.home");
- if (property != null) {
- sdkFolder = new File(property);
- }
- }
-
- private void findNdkLocation(@NonNull Properties properties) {
- String ndkDirProp = properties.getProperty("ndk.dir");
- if (ndkDirProp != null) {
- ndkFolder = new File(ndkDirProp);
- return;
- }
-
- String envVar = System.getenv("ANDROID_NDK_HOME");
- if (envVar != null) {
- ndkFolder = new File(envVar);
- }
- }
-
- private void findLocation(@NonNull Project project) {
- if (sTestSdkFolder != null) {
- sdkFolder = sTestSdkFolder;
- return;
- }
-
- File rootDir = project.getRootDir();
- File localProperties = new File(rootDir, FN_LOCAL_PROPERTIES);
- Properties properties = new Properties();
-
- if (localProperties.isFile()) {
- InputStreamReader reader = null;
- try {
- //noinspection IOResourceOpenedButNotSafelyClosed
- FileInputStream fis = new FileInputStream(localProperties);
- reader = new InputStreamReader(fis, Charsets.UTF_8);
- properties.load(reader);
- } catch (FileNotFoundException ignored) {
- // ignore since we check up front and we don't want to fail on it anyway
- // in case there's an env var.
- } catch (IOException e) {
- throw new RuntimeException(
- String.format("Unable to read %1$s.", localProperties.getAbsolutePath()),
- e);
- } finally {
- try {
- Closeables.close(reader, true /* swallowIOException */);
- } catch (IOException e) {
- // ignore.
- }
- }
- }
-
- findSdkLocation(properties, rootDir);
- findNdkLocation(properties);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskFactory.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskFactory.java
deleted file mode 100644
index 2f691c0..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskFactory.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.Nullable;
-
-import org.gradle.api.Action;
-import org.gradle.api.Task;
-
-/**
- * Interface for a container that can create Task.
- */
-public interface TaskFactory {
-
- /**
- * Returns true if this collection contains an item with the given name.
- */
- boolean containsKey(String name);
-
- /**
- * Creates a task with the given name.
- */
- void create(String name);
-
- /**
- * Creates a task and initialize it with the given configAction.
- */
- void create(String name, Action<? super Task> configAction);
-
- /**
- * Creates a task with the given name and type.
- */
- <S extends Task> void create(String name, Class<S> type);
-
- /**
- * Creates a task the given name and type, and initialize it with the given configAction.
- */
- <S extends Task> void create(String name, Class<S> type, Action<? super S> configAction);
-
- /**
- * Applies the given configAction to the task with given name.
- */
- void named(String name, Action<? super Task> configAction);
-
- /**
- * Returns the {@link Task} named name from the current set of defined tasks.
- * @param name the name of the requested {@link Task}
- * @return the {@link Task} instance or null if not found.
- */
- @Nullable
- Task named(String name);
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java
deleted file mode 100644
index 4c646b5..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java
+++ /dev/null
@@ -1,2459 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.build.OutputFile.DENSITY;
-import static com.android.builder.core.BuilderConstants.CONNECTED;
-import static com.android.builder.core.BuilderConstants.DEVICE;
-import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS;
-import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS;
-import static com.android.builder.core.BuilderConstants.FD_FLAVORS_ALL;
-import static com.android.builder.core.VariantType.ANDROID_TEST;
-import static com.android.builder.core.VariantType.DEFAULT;
-import static com.android.builder.core.VariantType.UNIT_TEST;
-import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.OutputFile;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.AndroidGradleOptions;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.coverage.JacocoInstrumentTask;
-import com.android.build.gradle.internal.coverage.JacocoPlugin;
-import com.android.build.gradle.internal.coverage.JacocoReportTask;
-import com.android.build.gradle.internal.dependency.LibraryDependencyImpl;
-import com.android.build.gradle.internal.dependency.ManifestDependencyImpl;
-import com.android.build.gradle.internal.dependency.VariantDependencies;
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.dsl.AbiSplitOptions;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.publishing.ApkPublishArtifact;
-import com.android.build.gradle.internal.publishing.MappingPublishArtifact;
-import com.android.build.gradle.internal.publishing.MetadataPublishArtifact;
-import com.android.build.gradle.internal.scope.AndroidTask;
-import com.android.build.gradle.internal.scope.AndroidTaskRegistry;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.GlobalScope;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantOutputScope;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.AndroidReportTask;
-import com.android.build.gradle.internal.tasks.CheckManifest;
-import com.android.build.gradle.internal.tasks.DependencyReportTask;
-import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
-import com.android.build.gradle.internal.tasks.ExtractJavaResourcesTask;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.tasks.GenerateApkDataTask;
-import com.android.build.gradle.internal.tasks.InstallVariantTask;
-import com.android.build.gradle.internal.tasks.MergeJavaResourcesTask;
-import com.android.build.gradle.internal.tasks.MockableAndroidJarTask;
-import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
-import com.android.build.gradle.internal.tasks.SigningReportTask;
-import com.android.build.gradle.internal.tasks.SourceSetsTask;
-import com.android.build.gradle.internal.tasks.TestServerTask;
-import com.android.build.gradle.internal.tasks.UninstallTask;
-import com.android.build.gradle.internal.tasks.multidex.CreateMainDexList;
-import com.android.build.gradle.internal.tasks.multidex.CreateManifestKeepList;
-import com.android.build.gradle.internal.tasks.multidex.JarMergingTask;
-import com.android.build.gradle.internal.tasks.multidex.RetraceMainDexList;
-import com.android.build.gradle.internal.test.TestDataImpl;
-import com.android.build.gradle.internal.test.report.ReportType;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.internal.variant.ApkVariantOutputData;
-import com.android.build.gradle.internal.variant.ApplicationVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.internal.variant.LibraryVariantData;
-import com.android.build.gradle.internal.variant.TestVariantData;
-import com.android.build.gradle.tasks.AidlCompile;
-import com.android.build.gradle.tasks.AndroidJarTask;
-import com.android.build.gradle.tasks.AndroidProGuardTask;
-import com.android.build.gradle.tasks.CompatibleScreensManifest;
-import com.android.build.gradle.tasks.Dex;
-import com.android.build.gradle.tasks.GenerateBuildConfig;
-import com.android.build.gradle.tasks.GenerateResValues;
-import com.android.build.gradle.tasks.GenerateSplitAbiRes;
-import com.android.build.gradle.tasks.JackTask;
-import com.android.build.gradle.tasks.JavaResourcesProvider;
-import com.android.build.gradle.tasks.JillTask;
-import com.android.build.gradle.tasks.Lint;
-import com.android.build.gradle.tasks.MergeAssets;
-import com.android.build.gradle.tasks.MergeManifests;
-import com.android.build.gradle.tasks.MergeResources;
-import com.android.build.gradle.tasks.NdkCompile;
-import com.android.build.gradle.tasks.PackageApplication;
-import com.android.build.gradle.tasks.PackageSplitAbi;
-import com.android.build.gradle.tasks.PackageSplitRes;
-import com.android.build.gradle.tasks.PreDex;
-import com.android.build.gradle.tasks.ProcessAndroidResources;
-import com.android.build.gradle.tasks.ProcessManifest;
-import com.android.build.gradle.tasks.ProcessTestManifest;
-import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.build.gradle.tasks.ShrinkResources;
-import com.android.build.gradle.tasks.SplitZipAlign;
-import com.android.build.gradle.tasks.ZipAlign;
-import com.android.build.gradle.tasks.factory.JavaCompileConfigAction;
-import com.android.build.gradle.tasks.factory.ProGuardTaskConfigAction;
-import com.android.build.gradle.tasks.factory.ProcessJavaResConfigAction;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.VariantConfiguration;
-import com.android.builder.core.VariantType;
-import com.android.builder.dependency.LibraryDependency;
-import com.android.builder.internal.testing.SimpleTestCallable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.sdk.TargetInfo;
-import com.android.builder.signing.SignedJarBuilder;
-import com.android.builder.testing.ConnectedDeviceProvider;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.builder.testing.api.TestServer;
-import com.android.sdklib.IAndroidTarget;
-import com.android.utils.StringHelper;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.execution.TaskExecutionGraph;
-import org.gradle.api.file.ConfigurableFileCollection;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.api.plugins.BasePlugin;
-import org.gradle.api.plugins.JavaBasePlugin;
-import org.gradle.api.plugins.JavaPlugin;
-import org.gradle.api.reporting.ConfigurableReport;
-import org.gradle.api.tasks.Copy;
-import org.gradle.api.tasks.bundling.Jar;
-import org.gradle.api.tasks.compile.AbstractCompile;
-import org.gradle.api.tasks.compile.JavaCompile;
-import org.gradle.api.tasks.testing.Test;
-import org.gradle.api.tasks.testing.TestTaskReports;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import groovy.lang.Closure;
-import proguard.gradle.ProGuardTask;
-
-/**
- * Manages tasks creation.
- */
-public abstract class TaskManager {
-
- public static final String FILE_JACOCO_AGENT = "jacocoagent.jar";
-
- public static final String DEFAULT_PROGUARD_CONFIG_FILE = "proguard-android.txt";
-
- public static final String DIR_BUNDLES = "bundles";
-
- public static final String INSTALL_GROUP = "Install";
-
- public static final String BUILD_GROUP = BasePlugin.BUILD_GROUP;
-
- public static final String ANDROID_GROUP = "Android";
-
- protected Project project;
-
- protected AndroidBuilder androidBuilder;
-
- private DependencyManager dependencyManager;
-
- protected SdkHandler sdkHandler;
-
- protected AndroidConfig extension;
-
- protected ToolingModelBuilderRegistry toolingRegistry;
-
- private final GlobalScope globalScope;
-
- private AndroidTaskRegistry androidTasks = new AndroidTaskRegistry();
-
- private Logger logger;
-
- protected boolean isNdkTaskNeeded = true;
-
- // Task names
- // TODO: Convert to AndroidTask.
- private static final String MAIN_PREBUILD = "preBuild";
-
- private static final String UNINSTALL_ALL = "uninstallAll";
-
- private static final String DEVICE_CHECK = "deviceCheck";
-
- protected static final String CONNECTED_CHECK = "connectedCheck";
-
- private static final String ASSEMBLE_ANDROID_TEST = "assembleAndroidTest";
-
- private static final String SOURCE_SETS = "sourceSets";
-
- private static final String LINT = "lint";
-
- protected static final String LINT_COMPILE = "compileLint";
-
- // Tasks
- private Copy jacocoAgentTask;
-
- public MockableAndroidJarTask createMockableJar;
-
- public TaskManager(
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- this.project = project;
- this.androidBuilder = androidBuilder;
- this.sdkHandler = sdkHandler;
- this.extension = extension;
- this.toolingRegistry = toolingRegistry;
- this.dependencyManager = dependencyManager;
- logger = Logging.getLogger(this.getClass());
-
- globalScope = new GlobalScope(
- project,
- androidBuilder,
- checkNotNull((String) project.getProperties().get("archivesBaseName")),
- extension,
- sdkHandler,
- toolingRegistry);
- }
-
- private boolean isVerbose() {
- return project.getLogger().isEnabled(LogLevel.INFO);
- }
-
- private boolean isDebugLog() {
- return project.getLogger().isEnabled(LogLevel.DEBUG);
- }
-
- /**
- * Creates the tasks for a given BaseVariantData.
- */
- public abstract void createTasksForVariantData(@NonNull TaskFactory tasks,
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData);
-
- public GlobalScope getGlobalScope() {
- return globalScope;
- }
-
- /**
- * Returns a collection of buildables that creates native object.
- *
- * A buildable is considered to be any object that can be used as the argument to
- * Task.dependsOn. This could be a Task or a BuildableModelElement (e.g. BinarySpec).
- */
- protected Collection<Object> getNdkBuildable(BaseVariantData variantData) {
- return Collections.<Object>singleton(variantData.ndkCompileTask);
- }
-
- /**
- * Override to configure NDK data in the scope.
- */
- public void configureScopeForNdk(@NonNull VariantScope scope) {
- final BaseVariantData variantData = scope.getVariantData();
- scope.setNdkSoFolder(Collections.singleton(new File(
- scope.getGlobalScope().getIntermediatesDir(),
- "ndk/" + variantData.getVariantConfiguration().getDirName() + "/lib")));
- File objFolder = new File(scope.getGlobalScope().getIntermediatesDir(),
- "ndk/" + variantData.getVariantConfiguration().getDirName() + "/obj");
- scope.setNdkObjFolder(objFolder);
- for (Abi abi : NdkHandler.getAbiList()) {
- scope.addNdkDebuggableLibraryFolders(abi,
- new File(objFolder, "local/" + abi.getName()));
- }
-
- }
-
- protected AndroidConfig getExtension() {
- return extension;
- }
-
- public void resolveDependencies(
- @NonNull VariantDependencies variantDeps,
- @Nullable VariantDependencies testedVariantDeps,
- @Nullable String testedProjectPath) {
- dependencyManager.resolveDependencies(variantDeps, testedVariantDeps, testedProjectPath);
- }
-
- /**
- * Create tasks before the evaluation (on plugin apply). This is useful for tasks that
- * could be referenced by custom build logic.
- */
- public void createTasksBeforeEvaluate(@NonNull TaskFactory tasks) {
- tasks.create(UNINSTALL_ALL, new Action<Task>() {
- @Override
- public void execute(Task uninstallAllTask) {
- uninstallAllTask.setDescription("Uninstall all applications.");
- uninstallAllTask.setGroup(INSTALL_GROUP);
- }
- });
-
- tasks.create(DEVICE_CHECK, new Action<Task>() {
- @Override
- public void execute(Task deviceCheckTask) {
- deviceCheckTask.setDescription(
- "Runs all device checks using Device Providers and Test Servers.");
- deviceCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- }
- });
-
- tasks.create(CONNECTED_CHECK, new Action<Task>() {
- @Override
- public void execute(Task connectedCheckTask) {
- connectedCheckTask.setDescription(
- "Runs all device checks on currently connected devices.");
- connectedCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- }
- });
-
- tasks.create(MAIN_PREBUILD);
-
- tasks.create(SOURCE_SETS, SourceSetsTask.class, new Action<SourceSetsTask>() {
- @Override
- public void execute(SourceSetsTask sourceSetsTask) {
- sourceSetsTask.setConfig(extension);
- sourceSetsTask.setDescription(
- "Prints out all the source sets defined in this project.");
- sourceSetsTask.setGroup(ANDROID_GROUP);
- }
- });
-
- tasks.create(ASSEMBLE_ANDROID_TEST, new Action<Task>() {
- @Override
- public void execute(Task assembleAndroidTestTask) {
- assembleAndroidTestTask.setGroup(BasePlugin.BUILD_GROUP);
- assembleAndroidTestTask.setDescription("Assembles all the Test applications.");
- }
- });
-
- tasks.create(LINT, Lint.class, new Action<Lint>() {
- @Override
- public void execute(Lint lintTask) {
- lintTask.setDescription("Runs lint on all variants.");
- lintTask.setVariantName("");
- lintTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- lintTask.setLintOptions(getExtension().getLintOptions());
- lintTask.setSdkHome(sdkHandler.getSdkFolder());
- lintTask.setToolingRegistry(toolingRegistry);
- }
- });
- tasks.named(JavaBasePlugin.CHECK_TASK_NAME, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(LINT);
- }
- });
- createLintCompileTask(tasks);
- }
-
- public void createMockableJarTask() {
- createMockableJar =
- project.getTasks().create("mockableAndroidJar", MockableAndroidJarTask.class);
- createMockableJar.setGroup(BUILD_GROUP);
- createMockableJar.setDescription(
- "Creates a version of android.jar that's suitable for unit tests.");
- createMockableJar.setReturnDefaultValues(
- extension.getTestOptions().getUnitTests().isReturnDefaultValues());
-
- ConventionMappingHelper.map(createMockableJar, "androidJar", new Callable<File>() {
- @Override
- public File call() throws Exception {
- checkNotNull(androidBuilder.getTarget(), "ensureTargetSetup not called");
- return new File(androidBuilder.getTarget().getPath(IAndroidTarget.ANDROID_JAR));
- }
- });
-
- ConventionMappingHelper.map(createMockableJar, "outputFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- // Since the file ends up in $rootProject.buildDir, it will survive clean
- // operations - projects generated by AS don't have a top-level clean task that
- // would delete the top-level build directory. This means that the name has to
- // encode all the necessary information, otherwise the task will be UP-TO-DATE
- // even if the file should be regenerated. That's why we put the SDK version and
- // "default-values" in there, so if one project uses the returnDefaultValues flag,
- // it will just generate a new file and not change the semantics for other
- // sub-projects. There's an implicit "v1" there as well, if we ever change the
- // generator logic, the names will have to be changed.
- String fileExt;
- if (createMockableJar.getReturnDefaultValues()) {
- fileExt = ".default-values.jar";
- } else {
- fileExt = ".jar";
- }
- File outDir = new File(
- project.getRootProject().getBuildDir(),
- AndroidProject.FD_GENERATED);
-
- CharMatcher safeCharacters =
- CharMatcher.JAVA_LETTER_OR_DIGIT.or(CharMatcher.anyOf("-."));
- String sdkName =
- safeCharacters.negate().replaceFrom(extension.getCompileSdkVersion(), '-');
-
- return new File(outDir, "mockable-" + sdkName + fileExt);
- }
- });
- }
-
- public void createMergeAppManifestsTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope variantScope) {
-
- ApplicationVariantData appVariantData =
- (ApplicationVariantData) variantScope.getVariantData();
- Set<String> screenSizes = appVariantData.getCompatibleScreens();
-
- // loop on all outputs. The only difference will be the name of the task, and location
- // of the generated manifest
- for (final BaseVariantOutputData vod : appVariantData.getOutputs()) {
- VariantOutputScope scope = vod.getScope();
-
- AndroidTask<CompatibleScreensManifest> csmTask = null;
- if (vod.getMainOutputFile().getFilter(DENSITY) != null) {
- csmTask = androidTasks.create(tasks,
- new CompatibleScreensManifest.ConfigAction(scope, screenSizes));
- scope.setCompatibleScreensManifestTask(csmTask);
- }
-
- scope.setManifestProcessorTask(androidTasks.create(tasks,
- new MergeManifests.ConfigAction(scope)));
-
- if (csmTask != null) {
- scope.getManifestProcessorTask().dependsOn(tasks, csmTask);
- }
- }
- }
-
- public void createMergeLibManifestsTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
-
- AndroidTask<ProcessManifest> processManifest = androidTasks.create(tasks,
- new ProcessManifest.ConfigAction(scope));
-
- processManifest.dependsOn(tasks, scope.getVariantData().prepareDependenciesTask);
-
- BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
- variantOutputData.getScope().setManifestProcessorTask(processManifest);
- }
-
- protected void createProcessTestManifestTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
-
- AndroidTask<ProcessTestManifest> processTestManifestTask = androidTasks.create(tasks,
- new ProcessTestManifest.ConfigAction(scope));
-
- processTestManifestTask.dependsOn(tasks, scope.getVariantData().prepareDependenciesTask);
-
- BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
- variantOutputData.getScope().setManifestProcessorTask(processTestManifestTask);
- }
-
- public void createRenderscriptTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
- scope.setRenderscriptCompileTask(
- androidTasks.create(tasks, new RenderscriptCompile.ConfigAction(scope)));
-
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- GradleVariantConfiguration config = variantData.getVariantConfiguration();
- // get single output for now.
- BaseVariantOutputData variantOutputData = variantData.getOutputs().get(0);
-
- scope.getRenderscriptCompileTask().dependsOn(tasks, variantData.prepareDependenciesTask);
- if (config.getType().isForTesting()) {
- scope.getRenderscriptCompileTask().dependsOn(tasks,
- variantOutputData.getScope().getManifestProcessorTask());
- } else {
- scope.getRenderscriptCompileTask().dependsOn(tasks, scope.getCheckManifestTask());
- }
-
- scope.getResourceGenTask().dependsOn(tasks, scope.getRenderscriptCompileTask());
- // only put this dependency if rs will generate Java code
- if (!config.getRenderscriptNdkModeEnabled()) {
- scope.getSourceGenTask().dependsOn(tasks, scope.getRenderscriptCompileTask());
- }
-
- }
-
- public AndroidTask<MergeResources> createMergeResourcesTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
- return basicCreateMergeResourcesTask(
- tasks,
- scope,
- "merge",
- null /*outputLocation*/,
- true /*includeDependencies*/,
- true /*process9patch*/);
- }
-
- public AndroidTask<MergeResources> basicCreateMergeResourcesTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope,
- @NonNull String taskNamePrefix,
- @Nullable File outputLocation,
- final boolean includeDependencies,
- final boolean process9Patch) {
- AndroidTask<MergeResources> mergeResourcesTask = androidTasks.create(tasks,
- new MergeResources.ConfigAction(
- scope,
- taskNamePrefix,
- outputLocation,
- includeDependencies,
- process9Patch));
- mergeResourcesTask.dependsOn(tasks,
- scope.getVariantData().prepareDependenciesTask,
- scope.getResourceGenTask());
- scope.setMergeResourcesTask(mergeResourcesTask);
- scope.setResourceOutputDir(
- Objects.firstNonNull(outputLocation, scope.getDefaultMergeResourcesOutputDir()));
- return scope.getMergeResourcesTask();
- }
-
-
- public void createMergeAssetsTask(TaskFactory tasks, VariantScope scope) {
- AndroidTask<MergeAssets> mergeAssetsTask = androidTasks.create(tasks, new MergeAssets.ConfigAction(scope));
- mergeAssetsTask.dependsOn(tasks,
- scope.getVariantData().prepareDependenciesTask,
- scope.getAssetGenTask());
- scope.setMergeAssetsTask(mergeAssetsTask);
- }
-
- public void createBuildConfigTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
- AndroidTask<GenerateBuildConfig> generateBuildConfigTask =
- androidTasks.create(tasks, new GenerateBuildConfig.ConfigAction(scope));
- scope.setGenerateBuildConfigTask(generateBuildConfigTask);
- scope.getSourceGenTask().dependsOn(tasks, generateBuildConfigTask.getName());
- if (scope.getVariantConfiguration().getType().isForTesting()) {
- // in case of a test project, the manifest is generated so we need to depend
- // on its creation.
-
- // For test apps there should be a single output, so we get it.
- BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
-
- generateBuildConfigTask.dependsOn(
- tasks, variantOutputData.getScope().getManifestProcessorTask());
- } else {
- generateBuildConfigTask.dependsOn(tasks, scope.getCheckManifestTask());
- }
- }
-
- public void createGenerateResValuesTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
- AndroidTask<GenerateResValues> generateResValuesTask = androidTasks.create(
- tasks, new GenerateResValues.ConfigAction(scope));
- scope.getResourceGenTask().dependsOn(tasks, generateResValuesTask);
- }
-
- public void createProcessResTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope,
- boolean generateResourcePackage) {
- createProcessResTask(
- tasks,
- scope,
- new File(globalScope.getIntermediatesDir(),
- "symbols/" + scope.getVariantData().getVariantConfiguration().getDirName()),
- generateResourcePackage);
- }
-
- public void createProcessResTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope,
- @Nullable File symbolLocation,
- boolean generateResourcePackage) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
-
- variantData.calculateFilters(scope.getGlobalScope().getExtension().getSplits());
-
- // loop on all outputs. The only difference will be the name of the task, and location
- // of the generated data.
- for (BaseVariantOutputData vod : variantData.getOutputs()) {
- final VariantOutputScope variantOutputScope = vod.getScope();
-
- variantOutputScope.setProcessResourcesTask(androidTasks.create(tasks,
- new ProcessAndroidResources.ConfigAction(variantOutputScope, symbolLocation,
- generateResourcePackage)));
- variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
- variantOutputScope.getManifestProcessorTask(),
- scope.getMergeResourcesTask(),
- scope.getMergeAssetsTask());
-
- if (vod.getMainOutputFile().getFilter(DENSITY) == null) {
- scope.setGenerateRClassTask(variantOutputScope.getProcessResourcesTask());
- scope.getSourceGenTask().optionalDependsOn(tasks,
- variantOutputScope.getProcessResourcesTask());
- }
-
- }
-
- }
-
- /**
- * Creates the split resources packages task if necessary. AAPT will produce split packages for
- * all --split provided parameters. These split packages should be signed and moved unchanged to
- * the APK build output directory.
- */
- public void createSplitResourcesTasks(@NonNull VariantScope scope) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
-
- checkState(variantData.getSplitHandlingPolicy().equals(
- BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY),
- "Can only create split resources tasks for pure splits.");
-
- final VariantConfiguration config = variantData.getVariantConfiguration();
- Set<String> densityFilters = variantData.getFilters(OutputFile.FilterType.DENSITY);
- Set<String> abiFilters = variantData.getFilters(OutputFile.FilterType.ABI);
- Set<String> languageFilters = variantData.getFilters(OutputFile.FilterType.LANGUAGE);
-
- List<? extends BaseVariantOutputData> outputs = variantData.getOutputs();
- if (outputs.size() != 1) {
- throw new RuntimeException(
- "In release 21 and later, there can be only one main APK, " +
- "found " + outputs.size());
- }
-
- final BaseVariantOutputData variantOutputData = outputs.get(0);
- VariantOutputScope variantOutputScope = variantOutputData.getScope();
- variantOutputData.packageSplitResourcesTask = project.getTasks().create(
- scope.getTaskName("package", "SplitResources"),
- PackageSplitRes.class);
- variantOutputData.packageSplitResourcesTask.setInputDirectory(
- variantOutputScope.getProcessResourcePackageOutputFile().getParentFile());
- variantOutputData.packageSplitResourcesTask.setDensitySplits(densityFilters);
- variantOutputData.packageSplitResourcesTask.setLanguageSplits(languageFilters);
- variantOutputData.packageSplitResourcesTask.setOutputBaseName(config.getBaseName());
- variantOutputData.packageSplitResourcesTask.setSigningConfig(config.getSigningConfig());
- variantOutputData.packageSplitResourcesTask.setOutputDirectory(new File(
- scope.getGlobalScope().getIntermediatesDir(), "splits/" + config.getDirName()));
- variantOutputData.packageSplitResourcesTask.setAndroidBuilder(androidBuilder);
- variantOutputData.packageSplitResourcesTask.setVariantName(config.getFullName());
- variantOutputData.packageSplitResourcesTask.dependsOn(
- variantOutputScope.getProcessResourcesTask().getName());
-
- SplitZipAlign zipAlign = project.getTasks().create(
- scope.getTaskName("zipAlign", "SplitPackages"),
- SplitZipAlign.class);
- zipAlign.setVariantName(config.getFullName());
- ConventionMappingHelper.map(zipAlign, "zipAlignExe", new Callable<File>() {
- @Override
- public File call() throws Exception {
- final TargetInfo info = androidBuilder.getTargetInfo();
- if (info == null) {
- return null;
- }
- String path = info.getBuildTools().getPath(ZIP_ALIGN);
- if (path == null) {
- return null;
- }
- return new File(path);
- }
- });
-
- zipAlign.setOutputDirectory(new File(scope.getGlobalScope().getBuildDir(), "outputs/apk"));
- ConventionMappingHelper.map(zipAlign, "densityOrLanguageInputFiles",
- new Callable<List<File>>() {
- @Override
- public List<File> call() {
- return variantOutputData.packageSplitResourcesTask.getOutputFiles();
- }
- });
- zipAlign.setOutputBaseName(config.getBaseName());
- zipAlign.setAbiFilters(abiFilters);
- zipAlign.setLanguageFilters(languageFilters);
- zipAlign.setDensityFilters(densityFilters);
- File metadataDirectory = new File(zipAlign.getOutputDirectory().getParentFile(),
- "metadata");
- zipAlign.setApkMetadataFile(new File(metadataDirectory, config.getFullName() + ".mtd"));
- ((ApkVariantOutputData) variantOutputData).splitZipAlign = zipAlign;
- zipAlign.dependsOn(variantOutputData.packageSplitResourcesTask);
- }
-
- public void createSplitAbiTasks(@NonNull final VariantScope scope) {
- ApplicationVariantData variantData = (ApplicationVariantData) scope.getVariantData();
-
- checkState(variantData.getSplitHandlingPolicy().equals(
- BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY),
- "split ABI tasks are only compatible with pure splits.");
-
- final VariantConfiguration config = variantData.getVariantConfiguration();
- Set<String> filters = AbiSplitOptions.getAbiFilters(
- getExtension().getSplits().getAbiFilters());
- if (filters.isEmpty()) {
- return;
- }
-
- List<ApkVariantOutputData> outputs = variantData.getOutputs();
- if (outputs.size() != 1) {
- throw new RuntimeException(
- "In release 21 and later, there can be only one main APK, " +
- "found " + outputs.size());
- }
-
- BaseVariantOutputData variantOutputData = outputs.get(0);
- // first create the split APK resources.
- GenerateSplitAbiRes generateSplitAbiRes = project.getTasks().create(
- scope.getTaskName("generate", "SplitAbiRes"),
- GenerateSplitAbiRes.class);
- generateSplitAbiRes.setAndroidBuilder(androidBuilder);
- generateSplitAbiRes.setVariantName(config.getFullName());
-
- generateSplitAbiRes.setOutputDirectory(new File(
- scope.getGlobalScope().getIntermediatesDir(), "abi/" + config.getDirName()));
- generateSplitAbiRes.setSplits(filters);
- generateSplitAbiRes.setOutputBaseName(config.getBaseName());
- generateSplitAbiRes.setApplicationId(config.getApplicationId());
- generateSplitAbiRes.setVersionCode(config.getVersionCode());
- generateSplitAbiRes.setVersionName(config.getVersionName());
- ConventionMappingHelper.map(generateSplitAbiRes, "debuggable", new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return config.getBuildType().isDebuggable();
- }
- });
- ConventionMappingHelper.map(generateSplitAbiRes, "aaptOptions",
- new Callable<AaptOptions>() {
- @Override
- public AaptOptions call() throws Exception {
- return getExtension().getAaptOptions();
- }
- });
- generateSplitAbiRes.dependsOn(
- variantOutputData.getScope().getProcessResourcesTask().getName());
-
- // then package those resources with the appropriate JNI libraries.
- variantOutputData.packageSplitAbiTask = project.getTasks().create(
- scope.getTaskName("package", "SplitAbi"), PackageSplitAbi.class);
- variantOutputData.packageSplitAbiTask.setInputFiles(generateSplitAbiRes.getOutputFiles());
- variantOutputData.packageSplitAbiTask.setSplits(filters);
- variantOutputData.packageSplitAbiTask.setOutputBaseName(config.getBaseName());
- variantOutputData.packageSplitAbiTask.setSigningConfig(config.getSigningConfig());
- variantOutputData.packageSplitAbiTask.setOutputDirectory(new File(
- scope.getGlobalScope().getIntermediatesDir(), "splits/" + config.getDirName()));
- variantOutputData.packageSplitAbiTask.setMergingFolder(
- new File(scope.getGlobalScope().getIntermediatesDir(),
- "package-merge/" + variantOutputData.getDirName()));
- variantOutputData.packageSplitAbiTask.setAndroidBuilder(androidBuilder);
- variantOutputData.packageSplitAbiTask.setVariantName(config.getFullName());
- variantOutputData.packageSplitAbiTask.dependsOn(generateSplitAbiRes);
- variantOutputData.packageSplitAbiTask.dependsOn(scope.getNdkBuildable());
-
- ConventionMappingHelper.map(variantOutputData.packageSplitAbiTask, "jniFolders",
- new Callable<Set<File>>() {
- @Override
- public Set<File> call() throws Exception {
- return getJniFolders(scope);
- }
-
- });
- ConventionMappingHelper.map(variantOutputData.packageSplitAbiTask, "jniDebuggable",
- new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return config.getBuildType().isJniDebuggable();
- }
- });
- ConventionMappingHelper.map(variantOutputData.packageSplitAbiTask, "packagingOptions",
- new Callable<PackagingOptions>() {
- @Override
- public PackagingOptions call() throws Exception {
- return getExtension().getPackagingOptions();
- }
- });
- ConventionMappingHelper.map(variantOutputData.packageSplitAbiTask, "packagingOptionsFilter",
- new Callable<SignedJarBuilder.IZipEntryFilter>() {
- @Override
- public SignedJarBuilder.IZipEntryFilter call() throws Exception {
- return scope.getPackagingOptionsFilter();
- }
- });
-
- ((ApkVariantOutputData) variantOutputData).splitZipAlign.getAbiInputFiles().addAll(
- variantOutputData.packageSplitAbiTask.getOutputFiles());
-
- ((ApkVariantOutputData) variantOutputData).splitZipAlign.dependsOn(
- variantOutputData.packageSplitAbiTask);
- }
-
- /**
- * Calculate the list of folders that can contain jni artifacts for this variant.
- *
- * @return a potentially empty list of directories that exist or not and that may contains
- * native resources.
- */
- @NonNull
- public Set<File> getJniFolders(@NonNull VariantScope scope) {
-
- BaseVariantData variantData = scope.getVariantData();
- VariantConfiguration config = variantData.getVariantConfiguration();
- // for now only the project's compilation output.
- Set<File> set = Sets.newHashSet();
- addAllIfNotNull(set, scope.getNdkSoFolder());
- set.add(variantData.renderscriptCompileTask.getLibOutputDir());
- //noinspection unchecked
- addAllIfNotNull(set, config.getLibraryJniFolders());
- //noinspection unchecked
- addAllIfNotNull(set, config.getJniLibsList());
-
- if (Boolean.TRUE.equals(config.getMergedFlavor().getRenderscriptSupportModeEnabled())) {
- File rsLibs = androidBuilder.getSupportNativeLibFolder();
- if (rsLibs != null && rsLibs.isDirectory()) {
- set.add(rsLibs);
- }
-
- }
-
- return set;
- }
-
- private static <T> void addAllIfNotNull(@NonNull Collection<T> main, @Nullable Collection<T> toAdd) {
- if (toAdd != null) {
- main.addAll(toAdd);
- }
- }
-
- /**
- * Creates the java resources processing tasks.
- *
- * The java processing will happen in three steps :
- * <ul>{@link ExtractJavaResourcesTask} will extract all java resources from packaged jar files
- * dependencies. Each jar file will be extracted in a separate folder. Each folder will be
- * located under {@link VariantScope#getPackagedJarsJavaResDestinationDir()}</ul>
- * <ul>{@link ProcessJavaResConfigAction} will sync all source folders into a single folder
- * identified by {@link VariantScope#getSourceFoldersJavaResDestinationDir()}</ul>
- * <ul>{@link MergeJavaResourcesTask} will take all these folders and will create a single
- * merged folder with the {@link PackagingOptions} settings applied. The folder is located at
- * {@link VariantScope#getJavaResourcesDestinationDir()}</ul>
- *
- * the result of 3 is the final set of java resources to can be either directly embedded in
- * the resulting APK or fed into the obfuscation tool to produce obfuscated resources.
- *
- * @param tasks tasks factory to create tasks.
- * @param scope the variant scope we are operating under.
- */
- public void createProcessJavaResTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
-
- // first create the incremental task that will extract all libraries java resources
- // in separate folders.
- AndroidTask<ExtractJavaResourcesTask> extractJavaResourcesTask = androidTasks
- .create(tasks, new ExtractJavaResourcesTask.Config(scope));
-
- // now copy the source folders java resources into the temporary location, mainly to
- // maintain the PluginDsl COPY semantics.
- scope.setProcessJavaResourcesTask(
- androidTasks.create(tasks, new ProcessJavaResConfigAction(scope)));
-
- // and create the merge tasks that will merge everything.
- AndroidTask<MergeJavaResourcesTask> mergeJavaResourcesTask = androidTasks
- .create(tasks, new MergeJavaResourcesTask.Config(scope));
- // the merge task is the official provider for merged java resources to be bundled in the
- // final variant specific APK, this may change if obfuscation is turned on.
- scope.setJavaResourcesProvider(
- JavaResourcesProvider.Adapter.build(tasks, mergeJavaResourcesTask));
-
- // set the dependencies.
- extractJavaResourcesTask.dependsOn(tasks, variantData.prepareDependenciesTask);
- scope.getProcessJavaResourcesTask().dependsOn(tasks, extractJavaResourcesTask);
- mergeJavaResourcesTask.dependsOn(tasks, scope.getProcessJavaResourcesTask());
-
- scope.setMergeJavaResourcesTask(mergeJavaResourcesTask);
-
- }
-
- public void createAidlTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
- scope.setAidlCompileTask(androidTasks.create(tasks, new AidlCompile.ConfigAction(scope)));
- scope.getSourceGenTask().dependsOn(tasks, scope.getAidlCompileTask());
- scope.getAidlCompileTask().dependsOn(tasks, scope.getVariantData().prepareDependenciesTask);
- }
-
- /**
- * Creates the task for creating *.class files using javac. These tasks are created regardless
- * of whether Jack is used or not, but assemble will not depend on them if it is. They are
- * always used when running unit tests.
- */
- public AndroidTask<JavaCompile> createJavacTask(
- @NonNull final TaskFactory tasks,
- @NonNull final VariantScope scope) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- final AndroidTask<JavaCompile> javacTask = androidTasks.create(tasks,
- new JavaCompileConfigAction(scope));
- scope.setJavacTask(javacTask);
-
- javacTask.optionalDependsOn(tasks, scope.getSourceGenTask());
- javacTask.dependsOn(tasks,
- scope.getVariantData().prepareDependenciesTask,
- scope.getMergeJavaResourcesTask());
-
- // TODO - dependency information for the compile classpath is being lost.
- // Add a temporary approximation
- javacTask.dependsOn(tasks,
- scope.getVariantData().getVariantDependency().getCompileConfiguration()
- .getBuildDependencies());
-
- if (variantData.getType().isForTesting()) {
- BaseVariantData testedVariantData =
- (BaseVariantData) ((TestVariantData) variantData).getTestedVariantData();
- final JavaCompile testedJavacTask = testedVariantData.javacTask;
- javacTask.dependsOn(tasks,
- testedJavacTask != null ? testedJavacTask :
- testedVariantData.getScope().getJavacTask());
- }
-
- // Create jar task for uses by external modules.
- if (variantData.getVariantDependency().getClassesConfiguration() != null) {
- tasks.create(scope.getTaskName("package", "JarArtifact"), Jar.class, new Action<Jar>() {
- @Override
- public void execute(Jar jar) {
- variantData.classesJarTask = jar;
- jar.dependsOn(javacTask.getName());
-
- // add the class files (whether they are instrumented or not.
- jar.from(scope.getJavaOutputDir());
-
- jar.setDestinationDir(new File(
- scope.getGlobalScope().getIntermediatesDir(),
- "classes-jar/" +
- variantData.getVariantConfiguration().getDirName()));
- jar.setArchiveName("classes.jar");
- }
- });
- }
-
- return javacTask;
- }
-
- /**
- * Makes the given task the one used by top-level "compile" task.
- */
- public static void setJavaCompilerTask(
- @NonNull AndroidTask<? extends AbstractCompile> javaCompilerTask,
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
- scope.getCompileTask().dependsOn(tasks, javaCompilerTask);
- scope.setJavaCompilerTask(javaCompilerTask);
-
- // TODO: Get rid of it once we stop keeping tasks in variant data.
- //noinspection VariableNotUsedInsideIf
- if (scope.getVariantData().javacTask != null) {
- // This is not the experimental plugin, let's update variant data, so Variants API
- // keeps working.
- scope.getVariantData().javaCompilerTask = (AbstractCompile) tasks.named(javaCompilerTask.getName());
- }
-
- }
-
- public void createGenerateMicroApkDataTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope,
- @NonNull Configuration config) {
- AndroidTask<GenerateApkDataTask> generateMicroApkTask = androidTasks.create(tasks,
- new GenerateApkDataTask.ConfigAction(scope, config));
- generateMicroApkTask.dependsOn(tasks, config);
-
- // the merge res task will need to run after this one.
- scope.getResourceGenTask().dependsOn(tasks, generateMicroApkTask);
- }
-
- public void createNdkTasks(@NonNull VariantScope scope) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- NdkCompile ndkCompile = project.getTasks().create(
- scope.getTaskName("compile", "Ndk"),
- NdkCompile.class);
-
- ndkCompile.dependsOn(variantData.preBuildTask);
-
- ndkCompile.setAndroidBuilder(androidBuilder);
- ndkCompile.setVariantName(variantData.getName());
- ndkCompile.setNdkDirectory(sdkHandler.getNdkFolder());
- ndkCompile.setIsForTesting(variantData.getType().isForTesting());
- variantData.ndkCompileTask = ndkCompile;
- variantData.compileTask.dependsOn(variantData.ndkCompileTask);
-
- final GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
-
- if (Boolean.TRUE.equals(variantConfig.getMergedFlavor().getRenderscriptNdkModeEnabled())) {
- ndkCompile.setNdkRenderScriptMode(true);
- ndkCompile.dependsOn(variantData.renderscriptCompileTask);
- } else {
- ndkCompile.setNdkRenderScriptMode(false);
- }
-
- ConventionMappingHelper.map(ndkCompile, "sourceFolders", new Callable<List<File>>() {
- @Override
- public List<File> call() {
- List<File> sourceList = variantConfig.getJniSourceList();
- if (Boolean.TRUE.equals(
- variantConfig.getMergedFlavor().getRenderscriptNdkModeEnabled())) {
- sourceList.add(variantData.renderscriptCompileTask.getSourceOutputDir());
- }
-
- return sourceList;
- }
- });
-
- ndkCompile.setGeneratedMakefile(new File(scope.getGlobalScope().getIntermediatesDir(),
- "ndk/" + variantData.getVariantConfiguration().getDirName() + "/Android.mk"));
-
- ConventionMappingHelper.map(ndkCompile, "ndkConfig", new Callable<CoreNdkOptions>() {
- @Override
- public CoreNdkOptions call() {
- return variantConfig.getNdkConfig();
- }
- });
-
- ConventionMappingHelper.map(ndkCompile, "debuggable", new Callable<Boolean>() {
- @Override
- public Boolean call() {
- return variantConfig.getBuildType().isJniDebuggable();
- }
- });
-
- ndkCompile.setObjFolder(new File(scope.getGlobalScope().getIntermediatesDir(),
- "ndk/" + variantData.getVariantConfiguration().getDirName() + "/obj"));
-
- Collection<File> ndkSoFolder = scope.getNdkSoFolder();
- if (ndkSoFolder != null && !ndkSoFolder.isEmpty()) {
- ndkCompile.setSoFolder(ndkSoFolder.iterator().next());
- }
- }
-
- /**
- * Creates the tasks to build unit tests.
- */
- public void createUnitTestVariantTasks(
- @NonNull TaskFactory tasks,
- @NonNull TestVariantData variantData) {
- variantData.assembleVariantTask.dependsOn(createMockableJar);
- VariantScope variantScope = variantData.getScope();
-
- createPreBuildTasks(variantScope);
- createProcessJavaResTasks(tasks, variantScope);
- createCompileAnchorTask(tasks, variantScope);
- AndroidTask<JavaCompile> javacTask = createJavacTask(tasks, variantScope);
- setJavaCompilerTask(javacTask, tasks, variantScope);
- createUnitTestTask(tasks, variantData);
-
- // This hides the assemble unit test task from the task list.
- variantData.assembleVariantTask.setGroup(null);
- }
-
- /**
- * Creates the tasks to build android tests.
- */
- public void createAndroidTestVariantTasks(@NonNull TaskFactory tasks,
- @NonNull TestVariantData variantData) {
- VariantScope variantScope = variantData.getScope();
-
- // get single output for now (though this may always be the case for tests).
- final BaseVariantOutputData variantOutputData = variantData.getOutputs().get(0);
-
- final BaseVariantData<BaseVariantOutputData> baseTestedVariantData =
- (BaseVariantData<BaseVariantOutputData>) variantData.getTestedVariantData();
- final BaseVariantOutputData testedVariantOutputData =
- baseTestedVariantData.getOutputs().get(0);
-
- createAnchorTasks(tasks, variantScope);
-
- // Add a task to process the manifest
- createProcessTestManifestTask(tasks, variantScope);
-
- // Add a task to create the res values
- createGenerateResValuesTask(tasks, variantScope);
-
- // Add a task to compile renderscript files.
- createRenderscriptTask(tasks, variantScope);
-
- // Add a task to merge the resource folders
- createMergeResourcesTask(tasks, variantScope);
-
- // Add a task to merge the assets folders
- createMergeAssetsTask(tasks, variantScope);
-
- if (variantData.getTestedVariantData().getVariantConfiguration().getType().equals(
- VariantType.LIBRARY)) {
- // in this case the tested library must be fully built before test can be built!
- if (testedVariantOutputData.assembleTask != null) {
- variantOutputData.getScope().getManifestProcessorTask().dependsOn(
- tasks, testedVariantOutputData.assembleTask);
- variantScope.getMergeResourcesTask().dependsOn(
- tasks, testedVariantOutputData.assembleTask);
- }
- }
-
- // Add a task to create the BuildConfig class
- createBuildConfigTask(tasks, variantScope);
-
- // Add a task to generate resource source files
- createProcessResTask(tasks, variantScope, true /*generateResourcePackage*/);
-
- // process java resources
- createProcessJavaResTasks(tasks, variantScope);
-
- createAidlTask(tasks, variantScope);
-
- // Add NDK tasks
- if (isNdkTaskNeeded) {
- createNdkTasks(variantScope);
- }
-
- variantScope.setNdkBuildable(getNdkBuildable(variantData));
-
- // Add a task to compile the test application
- if (variantData.getVariantConfiguration().getUseJack()) {
- createJackTask(tasks, variantScope);
- } else {
- AndroidTask<JavaCompile> javacTask = createJavacTask(tasks, variantScope);
- setJavaCompilerTask(javacTask, tasks, variantScope);
- createPostCompilationTasks(tasks, variantScope);
- }
-
- createPackagingTask(tasks, variantScope, false /*publishApk*/);
-
- tasks.named(ASSEMBLE_ANDROID_TEST, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(variantOutputData.assembleTask);
- }
- });
-
- createConnectedTestForVariant(tasks, variantScope);
- }
-
- // TODO - should compile src/lint/java from src/lint/java and jar it into build/lint/lint.jar
- private void createLintCompileTask(TaskFactory tasks) {
-
- // TODO: move doFirst into dedicated task class.
- tasks.create(LINT_COMPILE, Task.class,
- new Action<Task>() {
- @Override
- public void execute(Task lintCompile) {
- final File outputDir =
- new File(getGlobalScope().getIntermediatesDir(), "lint");
-
- lintCompile.doFirst(new Action<Task>() {
- @Override
- public void execute(Task task) {
- // create the directory for lint output if it does not exist.
- if (!outputDir.exists()) {
- boolean mkdirs = outputDir.mkdirs();
- if (!mkdirs) {
- throw new GradleException(
- "Unable to create lint output directory.");
- }
- }
- }
- });
- }
- });
- }
-
- /**
- * Is the given variant relevant for lint?
- */
- private static boolean isLintVariant(
- @NonNull BaseVariantData<? extends BaseVariantOutputData> baseVariantData) {
- // Only create lint targets for variants like debug and release, not debugTest
- VariantConfiguration config = baseVariantData.getVariantConfiguration();
- return !config.getType().isForTesting();
- }
-
- /**
- * Add tasks for running lint on individual variants. We've already added a
- * lint task earlier which runs on all variants.
- */
- public void createLintTasks(TaskFactory tasks, final VariantScope scope) {
- final BaseVariantData<? extends BaseVariantOutputData> baseVariantData =
- scope.getVariantData();
- if (!isLintVariant(baseVariantData)) {
- return;
- }
-
- // wire the main lint task dependency.
- tasks.named(LINT, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(LINT_COMPILE);
- it.dependsOn(scope.getJavacTask().getName());
- }
- });
-
- AndroidTask<Lint> variantLintCheck = androidTasks.create(
- tasks, new Lint.ConfigAction(scope));
- variantLintCheck.dependsOn(tasks, LINT_COMPILE, scope.getJavacTask());
- }
-
- private void createLintVitalTask(@NonNull ApkVariantData variantData) {
- checkState(getExtension().getLintOptions().isCheckReleaseBuilds());
- // TODO: re-enable with Jack when possible
- if (!variantData.getVariantConfiguration().getBuildType().isDebuggable() &&
- !variantData.getVariantConfiguration().getUseJack()) {
- String variantName = variantData.getVariantConfiguration().getFullName();
- String capitalizedVariantName = StringHelper.capitalize(variantName);
- String taskName = "lintVital" + capitalizedVariantName;
- final Lint lintReleaseCheck = project.getTasks().create(taskName, Lint.class);
- // TODO: Make this task depend on lintCompile too (resolve initialization order first)
- optionalDependsOn(lintReleaseCheck, variantData.javacTask);
- lintReleaseCheck.setLintOptions(getExtension().getLintOptions());
- lintReleaseCheck.setSdkHome(sdkHandler.getSdkFolder());
- lintReleaseCheck.setVariantName(variantName);
- lintReleaseCheck.setToolingRegistry(toolingRegistry);
- lintReleaseCheck.setFatalOnly(true);
- lintReleaseCheck.setDescription(
- "Runs lint on just the fatal issues in the " + capitalizedVariantName
- + " build.");
- //variantData.assembleVariantTask.dependsOn lintReleaseCheck
-
- // If lint is being run, we do not need to run lint vital.
- // TODO: Find a better way to do this.
- project.getGradle().getTaskGraph().whenReady(new Closure<Void>(this, this) {
- public void doCall(TaskExecutionGraph taskGraph) {
- if (taskGraph.hasTask(LINT)) {
- lintReleaseCheck.setEnabled(false);
- }
- }
- });
- }
- }
-
- private void createUnitTestTask(@NonNull TaskFactory tasks,
- @NonNull final TestVariantData variantData) {
- final BaseVariantData testedVariantData =
- (BaseVariantData) variantData.getTestedVariantData();
-
- final Test runTestsTask = project.getTasks().create(
- variantData.getScope().getTaskName(UNIT_TEST.getPrefix()),
- Test.class);
- runTestsTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- runTestsTask.setDescription(
- "Run unit tests for the " +
- testedVariantData.getVariantConfiguration().getFullName() + " build.");
-
- fixTestTaskSources(runTestsTask);
-
- runTestsTask.dependsOn(variantData.assembleVariantTask);
-
- final AbstractCompile testCompileTask = variantData.javacTask;
- runTestsTask.setTestClassesDir(testCompileTask.getDestinationDir());
-
- ConventionMappingHelper.map(runTestsTask, "classpath",
- new Callable<ConfigurableFileCollection>() {
- @Override
- public ConfigurableFileCollection call() throws Exception {
- Iterable<File> filteredBootClasspath = Iterables.filter(
- androidBuilder.getBootClasspath(),
- new Predicate<File>() {
- @Override
- public boolean apply(@Nullable File file) {
- return file != null &&
- !SdkConstants.FN_FRAMEWORK_LIBRARY
- .equals(file.getName());
- }
- });
-
- return project.files(
- testCompileTask.getClasspath(),
- testCompileTask.getOutputs().getFiles(),
- variantData.processJavaResourcesTask.getOutputs(),
- testedVariantData.processJavaResourcesTask.getOutputs(),
- filteredBootClasspath,
- // Mockable JAR is last, to make sure you can shadow the classes
- // withdependencies.
- createMockableJar.getOutputFile());
- }
- });
-
- // Put the variant name in the report path, so that different testing tasks don't
- // overwrite each other's reports.
- TestTaskReports testTaskReports = runTestsTask.getReports();
-
- for (ConfigurableReport report : new ConfigurableReport[] {
- testTaskReports.getJunitXml(), testTaskReports.getHtml()}) {
- report.setDestination(new File(report.getDestination(), testedVariantData.getName()));
- }
-
- tasks.named(JavaPlugin.TEST_TASK_NAME, new Action<Task>() {
- @Override
- public void execute(Task test) {
- test.dependsOn(runTestsTask);
- }
-
- });
-
- extension.getTestOptions().getUnitTests().applyConfiguration(runTestsTask);
- }
-
- private static void fixTestTaskSources(@NonNull Test testTask) {
- // We are running in afterEvaluate, so the JavaBasePlugin has already added a
- // callback to add test classes to the list of source files of the newly created task.
- // The problem is that we haven't configured the test classes yet (JavaBasePlugin
- // assumes all Test tasks are fully configured at this point), so we have to remove the
- // "directory null" entry from source files and add the right value.
- //
- // This is an ugly hack, since we assume sourceFiles is an instance of
- // DefaultConfigurableFileCollection.
- ((DefaultConfigurableFileCollection) testTask.getInputs().getSourceFiles()).getFrom().clear();
- }
-
- public void createTopLevelTestTasks(final TaskFactory tasks, boolean hasFlavors) {
- final List<String> reportTasks = Lists.newArrayListWithExpectedSize(2);
-
- List<DeviceProvider> providers = getExtension().getDeviceProviders();
-
- final String connectedRootName = CONNECTED + ANDROID_TEST.getSuffix();
- final String defaultReportsDir = getGlobalScope().getReportsDir().getAbsolutePath()
- + "/" + FD_ANDROID_TESTS;
- final String defaultResultsDir = getGlobalScope().getOutputsDir().getAbsolutePath()
- + "/" + FD_ANDROID_RESULTS;
-
- // If more than one flavor, create a report aggregator task and make this the parent
- // task for all new connected tasks. Otherwise, create a top level connectedAndroidTest
- // DefaultTask.
- if (hasFlavors) {
- tasks.create(connectedRootName, AndroidReportTask.class,
- new Action<AndroidReportTask>() {
- @Override
- public void execute(AndroidReportTask mainConnectedTask) {
- mainConnectedTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- mainConnectedTask.setDescription("Installs and runs instrumentation "
- + "tests for all flavors on connected devices.");
- mainConnectedTask.setReportType(ReportType.MULTI_FLAVOR);
- mainConnectedTask.setVariantName("");
- ConventionMappingHelper.map(mainConnectedTask, "resultsDir",
- new Callable<File>() {
- @Override
- public File call() {
- final String dir =
- extension.getTestOptions().getResultsDir();
- String rootLocation = dir != null && !dir.isEmpty()
- ? dir : defaultResultsDir;
- return project.file(rootLocation + "/connected/"
- + FD_FLAVORS_ALL);
- }
- });
- ConventionMappingHelper.map(mainConnectedTask, "reportsDir",
- new Callable<File>() {
- @Override
- public File call() {
- final String dir =
- extension.getTestOptions().getReportDir();
- String rootLocation = dir != null && !dir.isEmpty()
- ? dir : defaultReportsDir;
- return project.file(rootLocation + "/connected/"
- + FD_FLAVORS_ALL);
- }
- });
-
- }
-
- });
- reportTasks.add(connectedRootName);
- } else {
- tasks.create(connectedRootName, new Action<Task>() {
- @Override
- public void execute(Task connectedTask) {
- connectedTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- connectedTask.setDescription(
- "Installs and runs instrumentation tests for all flavors on connected devices.");
- }
-
- });
- }
-
- tasks.named(CONNECTED_CHECK, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(connectedRootName);
- }
-
- });
-
- final String mainProviderTaskName = DEVICE + ANDROID_TEST.getSuffix();
- // if more than one provider tasks, either because of several flavors, or because of
- // more than one providers, then create an aggregate report tasks for all of them.
- if (providers.size() > 1 || hasFlavors) {
- tasks.create(mainProviderTaskName, AndroidReportTask.class,
- new Action<AndroidReportTask>() {
- @Override
- public void execute(AndroidReportTask mainProviderTask) {
- mainProviderTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- mainProviderTask.setDescription(
- "Installs and runs instrumentation tests using all Device Providers.");
- mainProviderTask.setReportType(ReportType.MULTI_FLAVOR);
-
- ConventionMappingHelper.map(mainProviderTask, "resultsDir",
- new Callable<File>() {
- @Override
- public File call() throws Exception {
- final String dir =
- extension.getTestOptions().getResultsDir();
- String rootLocation = dir != null && !dir.isEmpty()
- ? dir : defaultResultsDir;
-
- return project.file(rootLocation + "/devices/"
- + FD_FLAVORS_ALL);
- }
- });
- ConventionMappingHelper.map(mainProviderTask, "reportsDir",
- new Callable<File>() {
- @Override
- public File call() throws Exception {
- final String dir =
- extension.getTestOptions().getReportDir();
- String rootLocation = dir != null && !dir.isEmpty()
- ? dir : defaultReportsDir;
- return project.file(rootLocation + "/devices/"
- + FD_FLAVORS_ALL);
- }
- });
- }
-
- });
- reportTasks.add(mainProviderTaskName);
- } else {
- tasks.create(mainProviderTaskName, new Action<Task>() {
- @Override
- public void execute(Task providerTask) {
- providerTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- providerTask.setDescription(
- "Installs and runs instrumentation tests using all Device Providers.");
- }
- });
- }
-
- tasks.named(DEVICE_CHECK, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(mainProviderTaskName);
- }
- });
-
- // Create top level unit test tasks.
- tasks.create(JavaPlugin.TEST_TASK_NAME, new Action<Task>() {
- @Override
- public void execute(Task unitTestTask) {
- unitTestTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- unitTestTask.setDescription("Run unit tests for all variants.");
- }
-
- });
- tasks.named(JavaBasePlugin.CHECK_TASK_NAME,
- new Action<Task>() {
- @Override
- public void execute(Task check) {
- check.dependsOn(JavaPlugin.TEST_TASK_NAME);
- }
- });
-
- // If gradle is launched with --continue, we want to run all tests and generate an
- // aggregate report (to help with the fact that we may have several build variants, or
- // or several device providers).
- // To do that, the report tasks must run even if one of their dependent tasks (flavor
- // or specific provider tasks) fails, when --continue is used, and the report task is
- // meant to run (== is in the task graph).
- // To do this, we make the children tasks ignore their errors (ie they won't fail and
- // stop the build).
- if (!reportTasks.isEmpty() && project.getGradle().getStartParameter()
- .isContinueOnFailure()) {
- project.getGradle().getTaskGraph().whenReady(new Closure<Void>(this, this) {
- public void doCall(TaskExecutionGraph taskGraph) {
- for (String reportTask : reportTasks) {
- if (taskGraph.hasTask(reportTask)) {
- tasks.named(reportTask, new Action<Task>() {
- @Override
- public void execute(Task task) {
- ((AndroidReportTask) task).setWillRun();
- }
- });
- }
- }
- }
- });
- }
- }
-
- protected void createConnectedTestForVariant(
- @NonNull TaskFactory tasks,
- @NonNull final VariantScope variantScope) {
- final BaseVariantData<? extends BaseVariantOutputData> baseVariantData =
- variantScope.getTestedVariantData();
- final TestVariantData testVariantData = (TestVariantData) variantScope.getVariantData();
-
- // get single output for now
- final BaseVariantOutputData variantOutputData = baseVariantData.getOutputs().get(0);
- final BaseVariantOutputData testVariantOutputData = testVariantData.getOutputs().get(0);
-
- String connectedRootName = CONNECTED + ANDROID_TEST.getSuffix();
-
- TestDataImpl testData = new TestDataImpl(testVariantData);
- testData.setExtraInstrumentationTestRunnerArgs(
- AndroidGradleOptions.getExtraInstrumentationTestRunnerArgs(project));
-
- // create the check tasks for this test
- // first the connected one.
- ImmutableList<Task> artifactsTasks = ImmutableList.of(
- testVariantData.getOutputs().get(0).assembleTask,
- baseVariantData.assembleVariantTask);
-
- final AndroidTask<DeviceProviderInstrumentTestTask> connectedTask = androidTasks.create(
- tasks,
- new DeviceProviderInstrumentTestTask.ConfigAction(
- testVariantData.getScope(),
- new ConnectedDeviceProvider(sdkHandler.getSdkInfo().getAdb(),
- new LoggerWrapper(logger)), testData));
-
- connectedTask.dependsOn(tasks, artifactsTasks);
-
- tasks.named(connectedRootName, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(connectedTask.getName());
- }
- });
-
- if (baseVariantData.getVariantConfiguration().getBuildType().isTestCoverageEnabled()
- && !baseVariantData.getVariantConfiguration().getUseJack()) {
- final JacocoReportTask reportTask = project.getTasks().create(
- variantScope.getTaskName("create", "CoverageReport"),
- JacocoReportTask.class);
- reportTask.setReportName(baseVariantData.getVariantConfiguration().getFullName());
- ConventionMappingHelper.map(reportTask, "jacocoClasspath",
- new Callable<FileCollection>() {
- @Override
- public FileCollection call() throws Exception {
- return project.getConfigurations().getAt(
- JacocoPlugin.ANT_CONFIGURATION_NAME);
- }
- });
- ConventionMappingHelper.map(reportTask, "coverageFile", new Callable<File>() {
- @Override
- public File call() {
- return new File(((TestVariantData) testVariantData.getScope()
- .getVariantData()).connectedTestTask.getCoverageDir(),
- SimpleTestCallable.FILE_COVERAGE_EC);
- }
- });
- ConventionMappingHelper.map(reportTask, "classDir", new Callable<File>() {
- @Override
- public File call() {
- return baseVariantData.javacTask.getDestinationDir();
- }
- });
- ConventionMappingHelper.map(reportTask, "sourceDir", new Callable<List<File>>() {
- @Override
- public List<File> call() {
- return baseVariantData.getJavaSourceFoldersForCoverage();
- }
- });
-
- ConventionMappingHelper.map(reportTask, "reportDir", new Callable<File>() {
- @Override
- public File call() {
- return new File(variantScope.getGlobalScope().getReportsDir(),
- "/coverage/" + baseVariantData.getVariantConfiguration().getDirName());
- }
- });
-
- reportTask.dependsOn(connectedTask.getName());
- tasks.named(connectedRootName, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(reportTask);
- }
- });
- }
-
- String mainProviderTaskName = DEVICE + ANDROID_TEST.getSuffix();
-
- List<DeviceProvider> providers = getExtension().getDeviceProviders();
-
- boolean hasFlavors = baseVariantData.getVariantConfiguration().hasFlavors();
-
- // now the providers.
- for (DeviceProvider deviceProvider : providers) {
-
- final AndroidTask<DeviceProviderInstrumentTestTask> providerTask = androidTasks
- .create(tasks, new DeviceProviderInstrumentTestTask.ConfigAction(
- testVariantData.getScope(), deviceProvider, testData));
-
- tasks.named(mainProviderTaskName, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(providerTask.getName());
- }
- });
- providerTask.dependsOn(tasks, artifactsTasks);
- }
-
- // now the test servers
- List<TestServer> servers = getExtension().getTestServers();
- for (TestServer testServer : servers) {
- final TestServerTask serverTask = project.getTasks().create(
- hasFlavors ?
- baseVariantData.getScope().getTaskName(testServer.getName() + "Upload")
- :
- testServer.getName() + ("Upload"),
- TestServerTask.class);
-
- serverTask.setDescription(
- "Uploads APKs for Build \'" + baseVariantData.getVariantConfiguration()
- .getFullName() + "\' to Test Server \'" +
- StringHelper.capitalize(testServer.getName()) + "\'.");
- serverTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- serverTask.setVariantName(
- baseVariantData.getScope().getVariantConfiguration().getFullName());
- serverTask.dependsOn(testVariantOutputData.assembleTask,
- variantOutputData.assembleTask);
-
- serverTask.setTestServer(testServer);
-
- ConventionMappingHelper.map(serverTask, "testApk", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return testVariantOutputData.getOutputFile();
- }
- });
- if (!(baseVariantData instanceof LibraryVariantData)) {
- ConventionMappingHelper.map(serverTask, "testedApk", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return variantOutputData.getOutputFile();
- }
- });
- }
-
- ConventionMappingHelper.map(serverTask, "variantName", new Callable<String>() {
- @Override
- public String call() throws Exception {
- return baseVariantData.getVariantConfiguration().getFullName();
- }
- });
-
- tasks.named(DEVICE_CHECK, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(serverTask);
- }
- });
-
- if (!testServer.isConfigured()) {
- serverTask.setEnabled(false);
- }
- }
- }
-
- public static void createJarTask(@NonNull TaskFactory tasks, @NonNull final VariantScope scope) {
- final BaseVariantData variantData = scope.getVariantData();
-
- final GradleVariantConfiguration config = variantData.getVariantConfiguration();
- tasks.create(
- scope.getTaskName("jar", "Classes"),
- AndroidJarTask.class,
- new Action<AndroidJarTask>() {
- @Override
- public void execute(AndroidJarTask jarTask) {
- // AndroidJarTask jarTask = project.tasks.create(
- // "jar${config.fullName.capitalize()}Classes",
- // AndroidJarTask)
-
- jarTask.setArchiveName("classes.jar");
- jarTask.setDestinationDir(new File(
- scope.getGlobalScope().getIntermediatesDir(),
- "packaged/" + config.getDirName() + "/"));
- jarTask.from(scope.getJavaOutputDir());
- jarTask.dependsOn(scope.getJavacTask().getName());
- variantData.binayFileProviderTask = jarTask;
- }
-
- });
- }
-
- /**
- * Creates the post-compilation tasks for the given Variant.
- *
- * These tasks create the dex file from the .class files, plus optional intermediary steps like
- * proguard and jacoco
- *
- */
- public void createPostCompilationTasks(TaskFactory tasks, @NonNull final VariantScope scope) {
- checkNotNull(scope.getJavacTask());
-
- final ApkVariantData variantData = (ApkVariantData) scope.getVariantData();
- final GradleVariantConfiguration config = variantData.getVariantConfiguration();
-
- // data holding dependencies and input for the dex. This gets updated as new
- // post-compilation steps are inserted between the compilation and dx.
- PostCompilationData pcData = new PostCompilationData();
- pcData.setClassGeneratingTasks(Collections.singletonList(scope.getJavacTask().getName()));
- pcData.setLibraryGeneratingTasks(ImmutableList.of(variantData.prepareDependenciesTask,
- variantData.getVariantDependency().getPackageConfiguration()
- .getBuildDependencies()));
- pcData.setInputFilesCallable(new Callable<List<File>>() {
- @Override
- public List<File> call() {
- return new ArrayList<File>(
- variantData.javacTask.getOutputs().getFiles().getFiles());
- }
-
- });
- pcData.setInputDir(scope.getJavaOutputDir());
-
- pcData.setJavaResourcesInputDir(scope.getJavaResourcesDestinationDir());
-
- pcData.setInputLibrariesCallable(new Callable<List<File>>() {
- @Override
- public List<File> call() {
- return new ArrayList<File>(
- scope.getGlobalScope().getAndroidBuilder().getPackagedJars(config));
- }
-
- });
-
- // ---- Code Coverage first -----
- boolean isTestCoverageEnabled = config.getBuildType().isTestCoverageEnabled() && !config
- .getType().isForTesting();
- if (isTestCoverageEnabled) {
- pcData = createJacocoTask(tasks, scope, pcData);
- }
-
- boolean isTestForApp = config.getType().isForTesting() &&
- ((TestVariantData) variantData).getTestedVariantData().getVariantConfiguration()
- .getType().equals(DEFAULT);
- boolean isMinifyEnabled = config.isMinifyEnabled();
- boolean isMultiDexEnabled = config.isMultiDexEnabled() && !isTestForApp;
- boolean isLegacyMultiDexMode = config.isLegacyMultiDexMode();
-
- // ----- Minify next ----
- File outFile = maybeCreateProguardTasks(tasks, scope, pcData);
- if (outFile != null) {
- pcData.setInputFiles(Collections.singletonList(outFile));
- pcData.setInputLibraries(Collections.<File>emptyList());
- } else if ((getExtension().getDexOptions().getPreDexLibraries() && !isMultiDexEnabled) || (
- isMultiDexEnabled && !isLegacyMultiDexMode)) {
-
- AndroidTask<PreDex> preDexTask = androidTasks
- .create(tasks, new PreDex.ConfigAction(scope, pcData));
-
- // update dependency.
- preDexTask.dependsOn(tasks, pcData.getLibraryGeneratingTasks());
- pcData.setLibraryGeneratingTasks(Collections.singletonList(preDexTask.getName()));
-
- // update inputs
- if (isMultiDexEnabled) {
- pcData.setInputLibraries(Collections.<File>emptyList());
-
- } else {
- pcData.setInputLibrariesCallable(new Callable<List<File>>() {
- @Override
- public List<File> call() {
- return new ArrayList<File>(
- project.fileTree(scope.getPreDexOutputDir()).getFiles());
- }
-
- });
- }
- }
-
- AndroidTask<CreateMainDexList> createMainDexListTask = null;
- AndroidTask<RetraceMainDexList> retraceTask = null;
-
- // ----- Multi-Dex support
- if (isMultiDexEnabled && isLegacyMultiDexMode) {
- if (!isMinifyEnabled) {
- // create a task that will convert the output of the compilation
- // into a jar. This is needed by the multi-dex input.
- AndroidTask<JarMergingTask> jarMergingTask = androidTasks.create(tasks,
- new JarMergingTask.ConfigAction(scope, pcData));
-
- // update dependencies
- jarMergingTask.optionalDependsOn(tasks,
- pcData.getClassGeneratingTasks(),
- pcData.getLibraryGeneratingTasks());
- pcData.setLibraryGeneratingTasks(
- Collections.singletonList(jarMergingTask.getName()));
- pcData.setClassGeneratingTasks(Collections.singletonList(jarMergingTask.getName()));
-
- // Update the inputs
- pcData.setInputFiles(Collections.singletonList(scope.getJarMergingOutputFile()));
- pcData.setInputDirCallable(null);
- pcData.setInputLibraries(Collections.<File>emptyList());
- }
-
- // ----------
- // Create a task to collect the list of manifest entry points which are
- // needed in the primary dex
- AndroidTask<CreateManifestKeepList> manifestKeepListTask = androidTasks.create(tasks,
- new CreateManifestKeepList.ConfigAction(scope, pcData));
- manifestKeepListTask.dependsOn(tasks,
- variantData.getOutputs().get(0).getScope().getManifestProcessorTask());
-
- // ----------
- // Create a proguard task to shrink the classes to manifest components
- AndroidTask<ProGuardTask> proguardComponentsTask =
- androidTasks.create(tasks, new ProGuardTaskConfigAction(scope, pcData));
-
- // update dependencies
- proguardComponentsTask.dependsOn(tasks, manifestKeepListTask);
- proguardComponentsTask.optionalDependsOn(tasks,
- pcData.getClassGeneratingTasks(),
- pcData.getLibraryGeneratingTasks());
-
- // ----------
- // Compute the full list of classes for the main dex file
- createMainDexListTask =
- androidTasks.create(tasks, new CreateMainDexList.ConfigAction(scope, pcData));
- createMainDexListTask.dependsOn(tasks, proguardComponentsTask);
- //createMainDexListTask.dependsOn { proguardMainDexTask }
-
- // ----------
- // If proguard is enabled, create a de-obfuscated list to aid debugging.
- if (isMinifyEnabled) {
- retraceTask = androidTasks.create(tasks,
- new RetraceMainDexList.ConfigAction(scope, pcData));
- retraceTask.dependsOn(tasks, scope.getObfuscationTask(), createMainDexListTask);
- }
-
- }
-
- AndroidTask<Dex> dexTask = androidTasks.create(tasks, new Dex.ConfigAction(scope, pcData));
- scope.setDexTask(dexTask);
-
- // dependencies, some of these could be null
- dexTask.optionalDependsOn(tasks,
- pcData.getClassGeneratingTasks(),
- pcData.getLibraryGeneratingTasks(),
- createMainDexListTask,
- retraceTask);
- }
-
- public PostCompilationData createJacocoTask(
- @NonNull TaskFactory tasks,
- @NonNull final VariantScope scope,
- @NonNull final PostCompilationData pcData) {
- AndroidTask<JacocoInstrumentTask> jacocoTask =
- androidTasks.create(tasks, new JacocoInstrumentTask.ConfigAction(scope, pcData));
-
- jacocoTask.optionalDependsOn(tasks, pcData.getClassGeneratingTasks());
-
- final Copy agentTask = getJacocoAgentTask();
- jacocoTask.dependsOn(tasks, agentTask);
-
- // update dependency.
- PostCompilationData pcData2 = new PostCompilationData();
- pcData2.setClassGeneratingTasks(Collections.singletonList(jacocoTask.getName()));
- pcData2.setLibraryGeneratingTasks(
- Arrays.asList(pcData.getLibraryGeneratingTasks(), agentTask));
-
- // update inputs
- pcData2.setInputFilesCallable(new Callable<List<File>>() {
- @Override
- public List<File> call() {
- return new ArrayList<File>(
- project.files(scope.getVariantData().jacocoInstrumentTask.getOutputDir())
- .getFiles());
- }
-
- });
- pcData2.setInputDirCallable(new Callable<File>() {
- @Override
- public File call() {
- return scope.getVariantData().jacocoInstrumentTask.getOutputDir();
- }
-
- });
- pcData2.setInputLibrariesCallable(new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- List<File> files = null;
- files = new ArrayList<File>(pcData.getInputLibrariesCallable().call());
- files.add(new File(agentTask.getDestinationDir(), FILE_JACOCO_AGENT));
- return files;
- }
-
- });
-
- return pcData2;
- }
-
- public void createJackTask(
- @NonNull TaskFactory tasks,
- @NonNull VariantScope scope) {
-
- // ----- Create Jill tasks -----
- final AndroidTask<JillTask> jillRuntimeTask = androidTasks.create(tasks,
- new JillTask.RuntimeTaskConfigAction(scope));
-
- final AndroidTask<JillTask> jillPackagedTask = androidTasks.create(tasks,
- new JillTask.PackagedConfigAction(scope));
-
- jillPackagedTask.dependsOn(tasks,
- scope.getVariantData().getVariantDependency().getPackageConfiguration()
- .getBuildDependencies());
-
- // ----- Create Jack Task -----
- AndroidTask<JackTask> jackTask = androidTasks.create(tasks,
- new JackTask.ConfigAction(scope, isVerbose(), isDebugLog()));
-
-
- // Jack is compiling and also providing the binary and mapping files.
- setJavaCompilerTask(jackTask, tasks, scope);
- jackTask.dependsOn(tasks, scope.getMergeJavaResourcesTask());
- jackTask.dependsOn(tasks,
- scope.getVariantData().sourceGenTask,
- jillRuntimeTask,
- jillPackagedTask,
- // TODO - dependency information for the compile classpath is being lost.
- // Add a temporary approximation
- scope.getVariantData().getVariantDependency().getCompileConfiguration()
- .getBuildDependencies());
-
- }
-
- /**
- * Creates the final packaging task, and optionally the zipalign task (if the variant is signed)
- *
- * @param publishApk if true the generated APK gets published.
- */
- public void createPackagingTask(@NonNull TaskFactory tasks, @NonNull VariantScope variantScope,
- boolean publishApk) {
- final ApkVariantData variantData = (ApkVariantData) variantScope.getVariantData();
-
- GradleVariantConfiguration config = variantData.getVariantConfiguration();
- boolean signedApk = variantData.isSigned();
- File apkLocation = new File(variantScope.getGlobalScope().getApkLocation());
-
- boolean multiOutput = variantData.getOutputs().size() > 1;
-
- // loop on all outputs. The only difference will be the name of the task, and location
- // of the generated data.
- for (final ApkVariantOutputData variantOutputData : variantData.getOutputs()) {
- VariantOutputScope variantOutputScope = variantOutputData.getScope();
-
- final String outputName = variantOutputData.getFullName();
-
- // When shrinking resources, rather than having the packaging task
- // directly map to the packageOutputFile of ProcessAndroidResources,
- // we insert the ShrinkResources task into the chain, such that its
- // input is the ProcessAndroidResources packageOutputFile, and its
- // output is what the PackageApplication task reads.
- AndroidTask<ShrinkResources> shrinkTask = null;
-
- if (config.isMinifyEnabled() && config.getBuildType().isShrinkResources() &&
- !config.getUseJack()) {
- shrinkTask = androidTasks.create(
- tasks, new ShrinkResources.ConfigAction(variantOutputScope));
- shrinkTask.dependsOn(tasks, variantScope.getObfuscationTask(),
- variantOutputScope.getManifestProcessorTask(),
- variantOutputScope.getProcessResourcesTask());
- }
-
- AndroidTask<PackageApplication> packageApp = androidTasks.create(
- tasks, new PackageApplication.ConfigAction(variantOutputScope));
-
- packageApp.dependsOn(tasks, variantOutputScope.getProcessResourcesTask(),
- variantOutputScope.getVariantScope().getMergeJavaResourcesTask(),
- variantOutputScope.getVariantScope().getNdkBuildable());
-
- packageApp.optionalDependsOn(
- tasks,
- shrinkTask,
- // TODO: When Jack is converted, add activeDexTask to VariantScope.
- variantOutputScope.getVariantScope().getDexTask(),
- variantOutputScope.getVariantScope().getJavaCompilerTask(),
- variantData.javaCompilerTask, // TODO: Remove when Jack is converted to AndroidTask.
- variantOutputData.packageSplitResourcesTask,
- variantOutputData.packageSplitAbiTask);
-
- AndroidTask appTask = packageApp;
-
- if (signedApk) {
- if (variantData.getZipAlignEnabled()) {
- AndroidTask<ZipAlign> zipAlignTask = androidTasks.create(
- tasks, new ZipAlign.ConfigAction(variantOutputScope));
- zipAlignTask.dependsOn(tasks, packageApp);
- if (variantOutputData.splitZipAlign != null) {
- zipAlignTask.dependsOn(tasks, variantOutputData.splitZipAlign);
- }
-
- appTask = zipAlignTask;
- }
-
- }
-
- checkState(variantData.assembleVariantTask != null);
-
- // Add an assemble task
- if (multiOutput) {
- // create a task for this output
- variantOutputData.assembleTask = createAssembleTask(variantOutputData);
-
- // variant assemble task depends on each output assemble task.
- variantData.assembleVariantTask.dependsOn(variantOutputData.assembleTask);
- } else {
- // single output
- variantOutputData.assembleTask = variantData.assembleVariantTask;
- }
-
- if (!signedApk && variantOutputData.packageSplitResourcesTask != null) {
- // in case we are not signing the resulting APKs and we have some pure splits
- // we should manually copy them from the intermediate location to the final
- // apk location unmodified.
- Copy copyTask = project.getTasks().create(
- variantOutputScope.getTaskName("copySplit"), Copy.class);
- copyTask.setDestinationDir(apkLocation);
- copyTask.from(variantOutputData.packageSplitResourcesTask.getOutputDirectory());
- variantOutputData.assembleTask.dependsOn(copyTask);
- copyTask.mustRunAfter(appTask.getName());
- }
-
- variantOutputData.assembleTask.dependsOn(appTask.getName());
-
- if (publishApk) {
- final String projectBaseName = globalScope.getProjectBaseName();
-
- // if this variant is the default publish config or we also should publish non
- // defaults, proceed with declaring our artifacts.
- if (getExtension().getDefaultPublishConfig().equals(outputName)) {
- appTask.configure(tasks, new Action<Task>() {
- @Override
- public void execute(Task packageTask) {
- project.getArtifacts().add("default",
- new ApkPublishArtifact(projectBaseName,
- null,
- (FileSupplier) packageTask));
- }
-
- });
-
- for (FileSupplier outputFileProvider :
- variantOutputData.getSplitOutputFileSuppliers()) {
- project.getArtifacts().add("default",
- new ApkPublishArtifact(projectBaseName, null, outputFileProvider));
- }
-
- try {
- if (variantOutputData.getMetadataFile() != null) {
- project.getArtifacts().add("default-metadata",
- new MetadataPublishArtifact(projectBaseName, null,
- variantOutputData.getMetadataFile()));
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- if (variantData.getMappingFileProvider() != null) {
- project.getArtifacts().add("default-mapping",
- new MappingPublishArtifact(projectBaseName, null,
- variantData.getMappingFileProvider()));
- }
- }
-
- if (getExtension().getPublishNonDefault()) {
- appTask.configure(tasks, new Action<Task>() {
- @Override
- public void execute(Task packageTask) {
- project.getArtifacts().add(
- variantData.getVariantDependency().getPublishConfiguration().getName(),
- new ApkPublishArtifact(
- projectBaseName,
- null,
- (FileSupplier) packageTask));
- }
-
- });
-
- for (FileSupplier outputFileProvider :
- variantOutputData.getSplitOutputFileSuppliers()) {
- project.getArtifacts().add(
- variantData.getVariantDependency().getPublishConfiguration().getName(),
- new ApkPublishArtifact(
- projectBaseName,
- null,
- outputFileProvider));
- }
-
- try {
- if (variantOutputData.getMetadataFile() != null) {
- project.getArtifacts().add(
- variantData.getVariantDependency().getMetadataConfiguration().getName(),
- new MetadataPublishArtifact(
- projectBaseName,
- null,
- variantOutputData.getMetadataFile()));
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- if (variantData.getMappingFileProvider() != null) {
- project.getArtifacts().add(
- variantData.getVariantDependency().getMappingConfiguration().getName(),
- new MappingPublishArtifact(
- projectBaseName,
- null,
- variantData.getMappingFileProvider()));
- }
-
- if (variantData.classesJarTask != null) {
- project.getArtifacts().add(
- variantData.getVariantDependency().getClassesConfiguration().getName(),
- variantData.classesJarTask);
- }
- }
- }
- }
-
- // create install task for the variant Data. This will deal with finding the
- // right output if there are more than one.
- // Add a task to install the application package
- if (signedApk) {
- AndroidTask<InstallVariantTask> installTask = androidTasks.create(
- tasks, new InstallVariantTask.ConfigAction(variantScope));
- installTask.dependsOn(tasks, variantData.assembleVariantTask);
- }
-
- if (getExtension().getLintOptions().isCheckReleaseBuilds()) {
- createLintVitalTask(variantData);
- }
-
- // add an uninstall task
- final AndroidTask<UninstallTask> uninstallTask = androidTasks.create(
- tasks, new UninstallTask.ConfigAction(variantScope));
-
- tasks.named(UNINSTALL_ALL, new Action<Task>() {
- @Override
- public void execute(Task it) {
- it.dependsOn(uninstallTask.getName());
- }
- });
- }
-
- public Task createAssembleTask(@NonNull final BaseVariantOutputData variantOutputData) {
- Task assembleTask =
- project.getTasks().create(variantOutputData.getScope().getTaskName("assemble"));
- return assembleTask;
- }
-
- public Task createAssembleTask(TaskFactory tasks,
- @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
- Task assembleTask =
- project.getTasks().create(variantData.getScope().getTaskName("assemble"));
- return assembleTask;
- }
-
- public Copy getJacocoAgentTask() {
- if (jacocoAgentTask == null) {
- jacocoAgentTask = project.getTasks().create("unzipJacocoAgent", Copy.class);
- jacocoAgentTask.from(new Callable<List<FileTree>>() {
- @Override
- public List<FileTree> call() throws Exception {
- return Lists.newArrayList(Iterables.transform(
- project.getConfigurations().getByName(
- JacocoPlugin.AGENT_CONFIGURATION_NAME),
- new Function<Object, FileTree>() {
- @Override
- public FileTree apply(@Nullable Object it) {
- return project.zipTree(it);
- }
- }));
- }
- });
- jacocoAgentTask.include(FILE_JACOCO_AGENT);
- jacocoAgentTask.into(new File(getGlobalScope().getIntermediatesDir(), "jacoco"));
- }
-
- return jacocoAgentTask;
- }
-
- /**
- * creates a zip align. This does not use convention mapping, and is meant to let other plugin
- * create zip align tasks.
- *
- * @param name the name of the task
- * @param inputFile the input file
- * @param outputFile the output file
- * @return the task
- */
- @NonNull
- public ZipAlign createZipAlignTask(
- @NonNull String name,
- @NonNull File inputFile,
- @NonNull File outputFile) {
- // Add a task to zip align application package
- ZipAlign zipAlignTask = project.getTasks().create(name, ZipAlign.class);
-
- zipAlignTask.setInputFile(inputFile);
- zipAlignTask.setOutputFile(outputFile);
- ConventionMappingHelper.map(zipAlignTask, "zipAlignExe", new Callable<File>() {
- @Override
- public File call() throws Exception {
- final TargetInfo info = androidBuilder.getTargetInfo();
- if (info != null) {
- String path = info.getBuildTools().getPath(ZIP_ALIGN);
- if (path != null) {
- return new File(path);
- }
- }
-
- return null;
- }
- });
-
- return zipAlignTask;
- }
-
- /**
- * Creates the proguarding task for the given Variant if necessary.
- *
- * @return null if the proguard task was not created, otherwise the expected outputFile.
- */
- @Nullable
- public File maybeCreateProguardTasks(
- @NonNull final TaskFactory tasks,
- @NonNull VariantScope scope,
- @NonNull final PostCompilationData pcData) {
- if (!scope.getVariantData().getVariantConfiguration().isMinifyEnabled()) {
- return null;
- }
-
- final AndroidTask<AndroidProGuardTask> proguardTask = androidTasks.create(
- tasks, new AndroidProGuardTask.ConfigAction(scope, pcData));
- scope.setObfuscationTask(proguardTask);
- scope.setJavaResourcesProvider(JavaResourcesProvider.Adapter.build(tasks, proguardTask));
-
- // update dependency.
- proguardTask.optionalDependsOn(tasks, pcData.getClassGeneratingTasks(),
- pcData.getLibraryGeneratingTasks());
- pcData.setLibraryGeneratingTasks(Collections.singletonList(proguardTask.getName()));
- pcData.setClassGeneratingTasks(Collections.singletonList(proguardTask.getName()));
-
- // Return output file.
- return scope.getProguardOutputFile();
- }
-
- public void createReportTasks(
- List<BaseVariantData<? extends BaseVariantOutputData>> variantDataList) {
- DependencyReportTask dependencyReportTask =
- project.getTasks().create("androidDependencies", DependencyReportTask.class);
- dependencyReportTask.setDescription("Displays the Android dependencies of the project.");
- dependencyReportTask.setVariants(variantDataList);
- dependencyReportTask.setGroup(ANDROID_GROUP);
-
- SigningReportTask signingReportTask =
- project.getTasks().create("signingReport", SigningReportTask.class);
- signingReportTask.setDescription("Displays the signing info for each variant.");
- signingReportTask.setVariants(variantDataList);
- signingReportTask.setGroup(ANDROID_GROUP);
- }
-
- public void createAnchorTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
- createPreBuildTasks(scope);
-
- // also create sourceGenTask
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- scope.setSourceGenTask(androidTasks.create(tasks,
- scope.getTaskName("generate", "Sources"),
- Task.class,
- new Action<Task>() {
- @Override
- public void execute(Task task) {
- variantData.sourceGenTask = task;
- }
- }));
- // and resGenTask
- scope.setResourceGenTask(androidTasks.create(tasks,
- scope.getTaskName("generate", "Resources"),
- Task.class,
- new Action<Task>() {
- @Override
- public void execute(Task task) {
- variantData.resourceGenTask = task;
- }
-
- }));
-
- scope.setAssetGenTask(androidTasks.create(tasks,
- scope.getTaskName("generate", "Assets"),
- Task.class,
- new Action<Task>() {
- @Override
- public void execute(Task task) {
- variantData.assetGenTask = task;
- }
- }));
-
- // and compile task
- createCompileAnchorTask(tasks, scope);
- }
-
- private void createPreBuildTasks(@NonNull VariantScope scope) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- variantData.preBuildTask = project.getTasks().create(scope.getTaskName("pre", "Build"));
- variantData.preBuildTask.dependsOn(MAIN_PREBUILD);
-
- PrepareDependenciesTask prepareDependenciesTask = project.getTasks().create(
- scope.getTaskName("prepare", "Dependencies"), PrepareDependenciesTask.class);
-
- variantData.prepareDependenciesTask = prepareDependenciesTask;
- prepareDependenciesTask.dependsOn(variantData.preBuildTask);
-
- prepareDependenciesTask.setAndroidBuilder(androidBuilder);
- prepareDependenciesTask.setVariantName(scope.getVariantConfiguration().getFullName());
- prepareDependenciesTask.setVariant(variantData);
-
- // for all libraries required by the configurations of this variant, make this task
- // depend on all the tasks preparing these libraries.
- VariantDependencies configurationDependencies = variantData.getVariantDependency();
- prepareDependenciesTask.addChecker(configurationDependencies.getChecker());
-
- for (LibraryDependencyImpl lib : configurationDependencies.getLibraries()) {
- dependencyManager.addDependencyToPrepareTask(variantData, prepareDependenciesTask, lib);
- }
- }
-
- private void createCompileAnchorTask(@NonNull TaskFactory tasks, @NonNull final VariantScope scope) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- scope.setCompileTask(androidTasks.create(tasks, new TaskConfigAction<Task>() {
- @Override
- public String getName() {
- return scope.getTaskName("compile", "Sources");
- }
-
- @Override
- public Class<Task> getType() {
- return Task.class;
- }
-
- @Override
- public void execute(Task task) {
- variantData.compileTask = task;
- variantData.compileTask.setGroup(BUILD_GROUP);
- }
- }));
- variantData.assembleVariantTask.dependsOn(scope.getCompileTask().getName());
- }
-
- public void createCheckManifestTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- final String name = variantData.getVariantConfiguration().getFullName();
- scope.setCheckManifestTask(androidTasks.create(tasks,
- scope.getTaskName("check", "Manifest"),
- CheckManifest.class,
- new Action<CheckManifest>() {
- @Override
- public void execute(CheckManifest checkManifestTask) {
- variantData.checkManifestTask = checkManifestTask;
- checkManifestTask.setVariantName(name);
- ConventionMappingHelper.map(checkManifestTask, "manifest",
- new Callable<File>() {
- @Override
- public File call() throws Exception {
- return variantData.getVariantConfiguration()
- .getDefaultSourceSet().getManifestFile();
- }
- });
- }
-
- }));
- scope.getCheckManifestTask().dependsOn(tasks, variantData.preBuildTask);
- variantData.prepareDependenciesTask.dependsOn(scope.getCheckManifestTask().getName());
- }
-
- public static void optionalDependsOn(@NonNull Task main, Task... dependencies) {
- for (Task dependency : dependencies) {
- if (dependency != null) {
- main.dependsOn(dependency);
- }
-
- }
-
- }
-
- public static void optionalDependsOn(@NonNull Task main, @NonNull List<?> dependencies) {
- for (Object dependency : dependencies) {
- if (dependency != null) {
- main.dependsOn(dependency);
- }
-
- }
-
- }
-
- @NonNull
- private static List<ManifestDependencyImpl> getManifestDependencies(
- List<LibraryDependency> libraries) {
-
- List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size());
-
- for (LibraryDependency lib : libraries) {
- // get the dependencies
- List<ManifestDependencyImpl> children = getManifestDependencies(lib.getDependencies());
- list.add(new ManifestDependencyImpl(lib.getName(), lib.getManifest(), children));
- }
-
- return list;
- }
-
- @NonNull
- protected Logger getLogger() {
- return logger;
- }
-
- @NonNull
- protected AndroidTaskRegistry getAndroidTasks() {
- return androidTasks;
- }
-
- private File getDefaultProguardFile(String name) {
- File sdkDir = sdkHandler.getAndCheckSdkFolder();
- return new File(sdkDir,
- SdkConstants.FD_TOOLS + File.separatorChar + SdkConstants.FD_PROGUARD
- + File.separatorChar + name);
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TestApplicationTaskManager.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TestApplicationTaskManager.java
deleted file mode 100644
index ed65971..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TestApplicationTaskManager.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.TestAndroidConfig;
-import com.android.build.gradle.internal.scope.AndroidTask;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
-import com.android.build.gradle.internal.test.TestApplicationTestData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.internal.variant.LibraryVariantData;
-import com.android.build.gradle.tasks.TestModuleProGuardTask;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.testing.ConnectedDeviceProvider;
-import com.android.builder.testing.TestData;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.dsl.DependencyHandler;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.io.File;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import proguard.ParseException;
-
-/**
- * TaskManager for standalone test application that lives in a separate module from the tested
- * application.
- */
-public class TestApplicationTaskManager extends ApplicationTaskManager {
-
-
- public TestApplicationTaskManager(Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- super(project, androidBuilder, extension, sdkHandler, dependencyManager, toolingRegistry);
- }
-
- @Override
- public void createTasksForVariantData(@NonNull TaskFactory tasks,
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
-
- super.createTasksForVariantData(tasks, variantData);
-
- // create a new configuration with the target application coordinates.
- final Configuration testTarget = project.getConfigurations().create("testTarget");
-
- DependencyHandler dependencyHandler = project.getDependencies();
- TestAndroidConfig testExtension = (TestAndroidConfig) extension;
- dependencyHandler.add("testTarget",
- dependencyHandler.project(
- ImmutableMap.of(
- "path", testExtension.getTargetProjectPath(),
- "configuration", testExtension.getTargetVariant())));
-
- // and create the configuration for the project's metadata.
- final Configuration testTargetMetadata = project.getConfigurations().create("testTargetMetadata");
-
- dependencyHandler.add("testTargetMetadata", dependencyHandler.project(
- ImmutableMap.of(
- "path", testExtension.getTargetProjectPath(),
- "configuration", testExtension.getTargetVariant() + "-metadata"
- )));
-
- TestData testData = new TestApplicationTestData(
- variantData, testTarget, testTargetMetadata, androidBuilder);
-
- // create the test connected check task.
- AndroidTask<DeviceProviderInstrumentTestTask> testConnectedCheck =
- getAndroidTasks().create(
- tasks,
- new DeviceProviderInstrumentTestTask.ConfigAction(
- variantData.getScope(),
- new ConnectedDeviceProvider(
- sdkHandler.getSdkInfo().getAdb(),
- new LoggerWrapper(getLogger())),
- testData));
-
- // make the test application connectedCheck depends on the configuration added above so
- // we can retrieve its artifacts
-
- testConnectedCheck.dependsOn(tasks,
- testTarget,
- testTargetMetadata,
- variantData.assembleVariantTask);
-
- // make the main ConnectedCheck task depends on this test connectedCheck
- Task connectedCheck = tasks.named(CONNECTED_CHECK);
- if (connectedCheck != null) {
- connectedCheck.dependsOn(testConnectedCheck.getName());
- }
- }
-
- @Override
- @Nullable
- public File maybeCreateProguardTasks(
- @NonNull final TaskFactory tasks,
- @NonNull final VariantScope scope,
- @NonNull final PostCompilationData pcData) {
- DependencyHandler dependencyHandler = project.getDependencies();
- TestAndroidConfig testExtension = (TestAndroidConfig) extension;
- Configuration testTargetMapping = project.getConfigurations().create("testTargetMapping");
-
- dependencyHandler.add("testTargetMapping", dependencyHandler.project(
- ImmutableMap.of(
- "path", testExtension.getTargetProjectPath(),
- "configuration", testExtension.getTargetVariant() + "-mapping"
- )));
-
- if (testTargetMapping.getFiles().isEmpty()
- || scope.getVariantConfiguration().getProvidedOnlyJars().isEmpty()) {
- return null;
- }
-
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
-
- final TestModuleProGuardTask proguardTask = project.getTasks().create(
- scope.getTaskName("proguard"),
- TestModuleProGuardTask.class);
-
- variantData.obfuscationTask = proguardTask;
- proguardTask.setLogger(getLogger());
-
- // and create the configuration for the project's mapping file.
- // Input the mapping from the tested app so that we can deal with obfuscated code.
- proguardTask.setMappingConfiguration(testTargetMapping);
- proguardTask.setVariantConfiguration(scope.getVariantConfiguration());
-
- // --- Proguard Config ---
-
- // Don't remove any code in tested app.
- proguardTask.dontshrink();
- proguardTask.dontoptimize();
-
- // We can't call dontobfuscate, since that would make ProGuard ignore the mapping file.
- try {
- proguardTask.keep("class * {*;}");
- proguardTask.keep("interface * {*;}");
- proguardTask.keep("enum * {*;}");
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
-
- // libraryJars: the runtime jars. Do this in doFirst since the boot classpath isn't
- // available until the SDK is loaded in the prebuild task
- proguardTask.doFirst(new Action<Task>() {
- @Override
- public void execute(Task task) {
- for (String runtimeJar : androidBuilder.getBootClasspathAsStrings()) {
- try {
- proguardTask.libraryjars(runtimeJar);
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
- }
- }
- });
-
- try {
-
- // injar: the compilation output
- proguardTask.injars(pcData.getInputDirCallable());
- if (pcData.getJavaResourcesInputDirCallable() != null) {
- proguardTask.injars(pcData.getJavaResourcesInputDirCallable());
- }
-
- // injar: the packaged dependencies
- LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(1);
- map.put("filter", "!META-INF/MANIFEST.MF");
- proguardTask.injars(map, new Callable<Set<File>>() {
- @Override
- public Set<File> call() throws Exception {
- return scope.getVariantConfiguration().getPackagedJars();
- }
- });
-
- proguardTask.libraryjars(new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- return scope.getVariantConfiguration().getProvidedOnlyJars();
- }
- });
-
-
- // All -dontwarn rules for test dependencies should go in here:
- proguardTask.configuration(
- variantData.getVariantConfiguration().getTestProguardFiles());
-
-
-
- // --- Out files ---
-
- proguardTask.outjars(scope.getProguardOutputFile());
-
- final File proguardOut = project.file(
- new File(project.getBuildDir(),
- FD_OUTPUTS
- + File.separatorChar
- + "mapping"
- + File.separatorChar
- + variantData.getVariantConfiguration().getDirName()));
-
- proguardTask.dump(new File(proguardOut, "dump.txt"));
- proguardTask.printseeds(new File(proguardOut, "seeds.txt"));
- proguardTask.printusage(new File(proguardOut, "usage.txt"));
- proguardTask.printmapping(new File(proguardOut, "mapping.txt"));
-
- // proguard doesn't verify that the seed/mapping/usage folders exist and will fail
- // if they don't so create them.
- proguardTask.doFirst(new Action<Task>() {
- @Override
- public void execute(Task task) {
- if (!proguardOut.mkdirs()) {
- throw new RuntimeException(
- "Cannot create proguard output folder " + proguardOut);
- }
- }
- });
-
- // update dependency.
- optionalDependsOn(proguardTask, pcData.getClassGeneratingTasks());
- optionalDependsOn(proguardTask, pcData.getLibraryGeneratingTasks());
- pcData.setLibraryGeneratingTasks(ImmutableList.of(proguardTask));
- pcData.setClassGeneratingTasks(ImmutableList.of(proguardTask));
-
- // Update the inputs
- return scope.getProguardOutputFile();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantManager.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantManager.java
deleted file mode 100644
index fd2e2d1..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantManager.java
+++ /dev/null
@@ -1,828 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static com.android.builder.core.BuilderConstants.LINT;
-import static com.android.builder.core.VariantType.ANDROID_TEST;
-import static com.android.builder.core.VariantType.LIBRARY;
-import static com.android.builder.core.VariantType.UNIT_TEST;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.AndroidGradleOptions;
-import com.android.build.gradle.TestAndroidConfig;
-import com.android.build.gradle.TestedAndroidConfig;
-import com.android.build.gradle.api.AndroidSourceSet;
-import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
-import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
-import com.android.build.gradle.internal.api.VariantFilter;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dependency.VariantDependencies;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.build.gradle.internal.profile.SpanRecorders;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.internal.variant.TestVariantData;
-import com.android.build.gradle.internal.variant.TestedVariantData;
-import com.android.build.gradle.internal.variant.VariantFactory;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.VariantType;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.profile.ExecutionType;
-import com.android.builder.profile.Recorder;
-import com.android.builder.profile.ThreadRecorder;
-import com.android.utils.StringHelper;
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.internal.reflect.Instantiator;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Class to create, manage variants.
- */
-public class VariantManager implements VariantModel {
-
- protected static final String COM_ANDROID_SUPPORT_MULTIDEX =
- "com.android.support:multidex:1.0.1";
-
- @NonNull
- private final Project project;
- @NonNull
- private final AndroidBuilder androidBuilder;
- @NonNull
- private final AndroidConfig extension;
- @NonNull
- private final VariantFactory variantFactory;
- @NonNull
- private final TaskManager taskManager;
- @NonNull
- private final Instantiator instantiator;
- @NonNull
- private ProductFlavorData<CoreProductFlavor> defaultConfigData;
- @NonNull
- private final Map<String, BuildTypeData> buildTypes = Maps.newHashMap();
- @NonNull
- private final Map<String, ProductFlavorData<CoreProductFlavor>> productFlavors = Maps.newHashMap();
- @NonNull
- private final Map<String, SigningConfig> signingConfigs = Maps.newHashMap();
-
- @NonNull
- private final ReadOnlyObjectProvider readOnlyObjectProvider = new ReadOnlyObjectProvider();
- @NonNull
- private final VariantFilter variantFilter = new VariantFilter(readOnlyObjectProvider);
-
- @NonNull
- private final List<BaseVariantData<? extends BaseVariantOutputData>> variantDataList = Lists.newArrayList();
- @Nullable
- private SigningConfig signingOverride;
-
- public VariantManager(
- @NonNull Project project,
- @NonNull AndroidBuilder androidBuilder,
- @NonNull AndroidConfig extension,
- @NonNull VariantFactory variantFactory,
- @NonNull TaskManager taskManager,
- @NonNull Instantiator instantiator) {
- this.extension = extension;
- this.androidBuilder = androidBuilder;
- this.project = project;
- this.variantFactory = variantFactory;
- this.taskManager = taskManager;
- this.instantiator = instantiator;
-
- DefaultAndroidSourceSet mainSourceSet =
- (DefaultAndroidSourceSet) extension.getSourceSets().getByName(extension.getDefaultConfig().getName());
-
- DefaultAndroidSourceSet androidTestSourceSet = null;
- DefaultAndroidSourceSet unitTestSourceSet = null;
- if (variantFactory.hasTestScope()) {
- androidTestSourceSet =
- (DefaultAndroidSourceSet) extension.getSourceSets()
- .getByName(ANDROID_TEST.getPrefix());
- unitTestSourceSet =
- (DefaultAndroidSourceSet) extension.getSourceSets()
- .getByName(UNIT_TEST.getPrefix());
- }
-
- defaultConfigData = new ProductFlavorData<CoreProductFlavor>(
- extension.getDefaultConfig(), mainSourceSet,
- androidTestSourceSet, unitTestSourceSet, project);
- signingOverride = createSigningOverride();
- }
-
- @NonNull
- @Override
- public ProductFlavorData<CoreProductFlavor> getDefaultConfig() {
- return defaultConfigData;
- }
-
- @Override
- @NonNull
- public Map<String, BuildTypeData> getBuildTypes() {
- return buildTypes;
- }
-
- @Override
- @NonNull
- public Map<String, ProductFlavorData<CoreProductFlavor>> getProductFlavors() {
- return productFlavors;
- }
-
- @Override
- @NonNull
- public Map<String, SigningConfig> getSigningConfigs() {
- return signingConfigs;
- }
-
- public void addSigningConfig(@NonNull SigningConfig signingConfig) {
- signingConfigs.put(signingConfig.getName(), signingConfig);
- }
-
- /**
- * Adds new BuildType, creating a BuildTypeData, and the associated source set,
- * and adding it to the map.
- * @param buildType the build type.
- */
- public void addBuildType(@NonNull CoreBuildType buildType) {
- String name = buildType.getName();
- checkName(name, "BuildType");
-
- if (productFlavors.containsKey(name)) {
- throw new RuntimeException("BuildType names cannot collide with ProductFlavor names");
- }
-
- DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSets().maybeCreate(name);
-
- DefaultAndroidSourceSet unitTestSourceSet = null;
- if (variantFactory.hasTestScope()) {
- unitTestSourceSet = (DefaultAndroidSourceSet) extension
- .getSourceSets().maybeCreate(
- computeSourceSetName(buildType.getName(), UNIT_TEST));
- }
-
- BuildTypeData buildTypeData = new BuildTypeData(
- buildType, project, mainSourceSet, unitTestSourceSet);
-
- buildTypes.put(name, buildTypeData);
- }
-
- /**
- * Adds a new ProductFlavor, creating a ProductFlavorData and associated source sets,
- * and adding it to the map.
- *
- * @param productFlavor the product flavor
- */
- public void addProductFlavor(@NonNull CoreProductFlavor productFlavor) {
- String name = productFlavor.getName();
- checkName(name, "ProductFlavor");
-
- if (buildTypes.containsKey(name)) {
- throw new RuntimeException("ProductFlavor names cannot collide with BuildType names");
- }
-
- DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSets().maybeCreate(
- productFlavor.getName());
-
- DefaultAndroidSourceSet androidTestSourceSet = null;
- DefaultAndroidSourceSet unitTestSourceSet = null;
- if (variantFactory.hasTestScope()) {
- androidTestSourceSet = (DefaultAndroidSourceSet) extension
- .getSourceSets().maybeCreate(
- computeSourceSetName(productFlavor.getName(), ANDROID_TEST));
- unitTestSourceSet = (DefaultAndroidSourceSet) extension
- .getSourceSets().maybeCreate(
- computeSourceSetName(productFlavor.getName(), UNIT_TEST));
- }
-
- ProductFlavorData<CoreProductFlavor> productFlavorData =
- new ProductFlavorData<CoreProductFlavor>(
- productFlavor,
- mainSourceSet,
- androidTestSourceSet,
- unitTestSourceSet,
- project);
-
- productFlavors.put(productFlavor.getName(), productFlavorData);
- }
-
- /**
- * Return a list of all created VariantData.
- */
- @NonNull
- public List<BaseVariantData<? extends BaseVariantOutputData>> getVariantDataList() {
- return variantDataList;
- }
-
- /**
- * Variant/Task creation entry point.
- */
- public void createAndroidTasks() {
- variantFactory.validateModel(this);
- variantFactory.preVariantWork(project);
-
- final TaskFactory tasks = new TaskContainerAdaptor(project.getTasks());
- if (variantDataList.isEmpty()) {
- ThreadRecorder.get().record(ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- populateVariantDataList();
- return null;
- }
- });
- }
-
- // Create top level test tasks.
- ThreadRecorder.get().record(ExecutionType.VARIANT_MANAGER_CREATE_TESTS_TASKS,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- taskManager.createTopLevelTestTasks(tasks, !productFlavors.isEmpty());
- return null;
- }
- });
-
- for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {
-
- SpanRecorders.record(project, ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createTasksForVariantData(tasks, variantData);
- return null;
- }
- },
- new Recorder.Property(SpanRecorders.VARIANT, variantData.getName()));
- }
-
- taskManager.createReportTasks(variantDataList);
- }
-
- /**
- * Create assemble task for VariantData.
- */
- private void createAssembleTaskForVariantData(
- TaskFactory tasks,
- final BaseVariantData<?> variantData) {
- if (variantData.getType().isForTesting()) {
- variantData.assembleVariantTask = taskManager.createAssembleTask(tasks, variantData);
- } else {
- BuildTypeData buildTypeData =
- buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName());
-
- if (productFlavors.isEmpty()) {
- // Reuse assemble task for build type if there is no product flavor.
- variantData.assembleVariantTask = buildTypeData.getAssembleTask();
- } else {
- variantData.assembleVariantTask = taskManager.createAssembleTask(tasks, variantData);
-
- // setup the task dependencies
- // build type
- buildTypeData.getAssembleTask().dependsOn(variantData.assembleVariantTask);
-
- // each flavor
- GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
- for (CoreProductFlavor flavor : variantConfig.getProductFlavors()) {
- productFlavors.get(flavor.getName()).getAssembleTask()
- .dependsOn(variantData.assembleVariantTask);
- }
-
- // assembleTask for this flavor(dimension), created on demand if needed.
- if (variantConfig.getProductFlavors().size() > 1) {
- final String name = StringHelper.capitalize(variantConfig.getFlavorName());
- final String variantAssembleTaskName = "assemble" + name;
- if (!tasks.containsKey(variantAssembleTaskName)) {
- tasks.create(variantAssembleTaskName, new Action<Task>() {
- @Override
- public void execute(Task task) {
- task.setDescription(
- "Assembles all builds for flavor combination: " + name);
- task.setGroup("Build");
- task.dependsOn(variantData.assembleVariantTask);
-
- }
- });
- }
- tasks.named("assemble", new Action<Task>() {
- @Override
- public void execute(Task task) {
- task.dependsOn(variantAssembleTaskName);
- }
- });
- }
- }
- }
- }
-
- /**
- * Create tasks for the specified variantData.
- */
- public void createTasksForVariantData(
- final TaskFactory tasks,
- final BaseVariantData<? extends BaseVariantOutputData> variantData) {
-
- // Add dependency of assemble task on assemble build type task.
- tasks.named("assemble", new Action<Task>() {
- @Override
- public void execute(Task task) {
- BuildTypeData buildTypeData = buildTypes.get(
- variantData.getVariantConfiguration().getBuildType().getName());
- task.dependsOn(buildTypeData.getAssembleTask());
- }
- });
-
-
- VariantType variantType = variantData.getType();
-
- createAssembleTaskForVariantData(tasks, variantData);
- if (variantType.isForTesting()) {
- final GradleVariantConfiguration testVariantConfig = variantData.getVariantConfiguration();
- final BaseVariantData testedVariantData = (BaseVariantData) ((TestVariantData) variantData)
- .getTestedVariantData();
-
- // Add the container of dependencies, the order of the libraries is important.
- // In descending order: build type (only for unit test), flavors, defaultConfig.
- List<ConfigurationProvider> testVariantProviders = Lists.newArrayListWithExpectedSize(
- 2 + testVariantConfig.getProductFlavors().size());
-
- ConfigurationProvider buildTypeConfigurationProvider =
- buildTypes.get(testVariantConfig.getBuildType().getName())
- .getTestConfigurationProvider(variantType);
- if (buildTypeConfigurationProvider != null) {
- testVariantProviders.add(buildTypeConfigurationProvider);
- }
-
- for (CoreProductFlavor productFlavor : testVariantConfig.getProductFlavors()) {
- ProductFlavorData<CoreProductFlavor> data =
- productFlavors.get(productFlavor.getName());
- testVariantProviders.add(data.getTestConfigurationProvider(variantType));
- }
-
- // now add the default config
- testVariantProviders.add(defaultConfigData.getTestConfigurationProvider(variantType));
-
- assert(testVariantConfig.getTestedConfig() != null);
- VariantDependencies parentVariant = null;
- if (testVariantConfig.getTestedConfig().getType() == VariantType.LIBRARY) {
- parentVariant = testedVariantData.getVariantDependency();
- }
-
- // If the variant being tested is a library variant, VariantDependencies must be
- // computed after the tasks for the tested variant is created. Therefore, the
- // VariantDependencies is computed here instead of when the VariantData was created.
- final VariantDependencies variantDep = VariantDependencies.compute(
- project, testVariantConfig.getFullName(),
- false /*publishVariant*/,
- variantType,
- parentVariant,
- testVariantProviders.toArray(
- new ConfigurationProvider[testVariantProviders.size()]));
- variantData.setVariantDependency(variantDep);
-
- SpanRecorders.record(project, ExecutionType.RESOLVE_DEPENDENCIES,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- taskManager.resolveDependencies(variantDep,
- testVariantConfig.getTestedConfig().getType() == VariantType.LIBRARY
- ? null
- : testedVariantData.getVariantDependency(),
- null /*testedProjectPath*/);
- return null;
- }
- },
- new Recorder.Property(SpanRecorders.VARIANT, testVariantConfig.getFullName()));
- testVariantConfig.setDependencies(variantDep);
- switch (variantType) {
- case ANDROID_TEST:
- taskManager.createAndroidTestVariantTasks(tasks, (TestVariantData) variantData);
- break;
- case UNIT_TEST:
- taskManager.createUnitTestVariantTasks(tasks, (TestVariantData) variantData);
- break;
- default:
- throw new IllegalArgumentException("Unknown test type " + variantType);
- }
- } else {
- taskManager.createTasksForVariantData(tasks, variantData);
- }
- }
-
- /**
- * Create all variants.
- */
- public void populateVariantDataList() {
- if (productFlavors.isEmpty()) {
- createVariantDataForProductFlavors(Collections.<ProductFlavor>emptyList());
- } else {
- List<String> flavorDimensionList = extension.getFlavorDimensionList();
-
- // Create iterable to get GradleProductFlavor from ProductFlavorData.
- Iterable<CoreProductFlavor> flavorDsl =
- Iterables.transform(
- productFlavors.values(),
- new Function<ProductFlavorData<CoreProductFlavor>, CoreProductFlavor>() {
- @Override
- public CoreProductFlavor apply(
- ProductFlavorData<CoreProductFlavor> data) {
- return data.getProductFlavor();
- }
- });
-
- // Get a list of all combinations of product flavors.
- List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =
- ProductFlavorCombo.createCombinations(
- flavorDimensionList,
- flavorDsl);
-
- for (ProductFlavorCombo<CoreProductFlavor> flavorCombo : flavorComboList) {
- //noinspection unchecked
- createVariantDataForProductFlavors(
- (List<ProductFlavor>) (List) flavorCombo.getFlavorList());
- }
- }
- }
-
- /**
- * Create a VariantData for a specific combination of BuildType and ProductFlavor list.
- */
- public BaseVariantData<? extends BaseVariantOutputData> createVariantData(
- @NonNull com.android.builder.model.BuildType buildType,
- @NonNull List<? extends ProductFlavor> productFlavorList) {
- BuildTypeData buildTypeData = buildTypes.get(buildType.getName());
-
- GradleVariantConfiguration variantConfig = new GradleVariantConfiguration(
- defaultConfigData.getProductFlavor(),
- defaultConfigData.getSourceSet(),
- buildTypeData.getBuildType(),
- buildTypeData.getSourceSet(),
- variantFactory.getVariantConfigurationType(),
- signingOverride);
-
- if (variantConfig.getType() == LIBRARY && variantConfig.getUseJack()) {
- project.getLogger().warn(
- "{}, {}: Jack compiler is not supported in library projects, falling back to javac.",
- project.getPath(),
- variantConfig.getFullName());
- }
-
- // sourceSetContainer in case we are creating variant specific sourceSets.
- NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer = extension
- .getSourceSets();
-
- // We must first add the flavors to the variant config, in order to get the proper
- // variant-specific and multi-flavor name as we add/create the variant providers later.
- for (ProductFlavor productFlavor : productFlavorList) {
- ProductFlavorData<CoreProductFlavor> data = productFlavors.get(
- productFlavor.getName());
-
- String dimensionName = productFlavor.getDimension();
- if (dimensionName == null) {
- dimensionName = "";
- }
-
- variantConfig.addProductFlavor(
- data.getProductFlavor(),
- data.getSourceSet(),
- dimensionName);
- }
-
- createCompoundSourceSets(productFlavorList, variantConfig, sourceSetsContainer);
-
- // Add the container of dependencies.
- // The order of the libraries is important, in descending order:
- // variant-specific, build type, multi-flavor, flavor1, flavor2, ..., defaultConfig.
- // variant-specific if the full combo of flavors+build type. Does not exist if no flavors.
- // multi-flavor is the combination of all flavor dimensions. Does not exist if <2 dimension.
- final List<ConfigurationProvider> variantProviders =
- Lists.newArrayListWithExpectedSize(productFlavorList.size() + 4);
-
- // 1. add the variant-specific if applicable.
- if (!productFlavorList.isEmpty()) {
- variantProviders.add(
- new ConfigurationProviderImpl(
- project,
- (DefaultAndroidSourceSet) variantConfig.getVariantSourceProvider()));
- }
-
- // 2. the build type.
- variantProviders.add(buildTypeData.getMainProvider());
-
- // 3. the multi-flavor combination
- if (productFlavorList.size() > 1) {
- variantProviders.add(
- new ConfigurationProviderImpl(
- project,
- (DefaultAndroidSourceSet) variantConfig.getMultiFlavorSourceProvider()));
- }
-
- // 4. the flavors.
- for (ProductFlavor productFlavor : productFlavorList) {
- variantProviders.add(productFlavors.get(productFlavor.getName()).getMainProvider());
- }
-
- // 5. The defaultConfig
- variantProviders.add(defaultConfigData.getMainProvider());
-
- // Done. Create the variant and get its internal storage object.
- BaseVariantData<?> variantData =
- variantFactory.createVariantData(variantConfig, taskManager);
-
- final VariantDependencies variantDep = VariantDependencies.compute(
- project, variantConfig.getFullName(),
- isVariantPublished(),
- variantData.getType(),
- null,
- variantProviders.toArray(new ConfigurationProvider[variantProviders.size()]));
- variantData.setVariantDependency(variantDep);
-
- if (variantConfig.isMultiDexEnabled() && variantConfig.isLegacyMultiDexMode()) {
- project.getDependencies().add(
- variantDep.getCompileConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
- project.getDependencies().add(
- variantDep.getPackageConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
- }
-
- final String testedProjectPath = extension instanceof TestAndroidConfig ?
- ((TestAndroidConfig) extension).getTargetProjectPath() :
- null;
-
- SpanRecorders.record(project, ExecutionType.RESOLVE_DEPENDENCIES,
- new Recorder.Block<Void>() {
- @Override
- public Void call() {
- taskManager.resolveDependencies(
- variantDep,
- null /*testedVariantDeps*/,
- testedProjectPath);
- return null;
- }
- }, new Recorder.Property(SpanRecorders.VARIANT, variantConfig.getFullName()));
-
- variantConfig.setDependencies(variantDep);
-
- return variantData;
- }
-
- private static void createCompoundSourceSets(
- @NonNull List<? extends ProductFlavor> productFlavorList,
- GradleVariantConfiguration variantConfig,
- NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer) {
- if (!productFlavorList.isEmpty() && !variantConfig.getType().isSingleBuildType()) {
- DefaultAndroidSourceSet variantSourceSet =
- (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
- computeSourceSetName(
- variantConfig.getFullName(),
- variantConfig.getType()));
- variantConfig.setVariantSourceProvider(variantSourceSet);
- }
-
- if (productFlavorList.size() > 1) {
- DefaultAndroidSourceSet multiFlavorSourceSet =
- (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
- computeSourceSetName(
- variantConfig.getFlavorName(),
- variantConfig.getType()));
- variantConfig.setMultiFlavorSourceProvider(multiFlavorSourceSet);
- }
- }
-
- /**
- * Turns a string into a valid source set name for the given {@link VariantType}, e.g.
- * "fooBarUnitTest" becomes "testFooBar".
- */
- @NonNull
- private static String computeSourceSetName(
- @NonNull String name,
- @NonNull VariantType variantType) {
- if (name.endsWith(variantType.getSuffix())) {
- name = name.substring(0, name.length() - variantType.getSuffix().length());
- }
-
- if (!variantType.getPrefix().isEmpty()) {
- name = variantType.getPrefix() + StringHelper.capitalize(name);
- }
-
- return name;
- }
-
- /**
- * Create a TestVariantData for the specified testedVariantData.
- */
- public TestVariantData createTestVariantData(
- BaseVariantData testedVariantData,
- VariantType type) {
- CoreProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
- CoreBuildType buildType = testedVariantData.getVariantConfiguration().getBuildType();
- BuildTypeData buildTypeData = buildTypes.get(buildType.getName());
-
- GradleVariantConfiguration testedConfig = testedVariantData.getVariantConfiguration();
- List<? extends CoreProductFlavor> productFlavorList = testedConfig.getProductFlavors();
-
- // handle test variant
- // need a suppress warning because ProductFlavor.getTestSourceSet(type) is annotated
- // to return @Nullable and the constructor is @NonNull on this parameter,
- // but it's never the case on defaultConfigData
- // The constructor does a runtime check on the instances so we should be safe.
- @SuppressWarnings("ConstantConditions")
- GradleVariantConfiguration testVariantConfig = new GradleVariantConfiguration(
- testedVariantData.getVariantConfiguration(),
- defaultConfig,
- defaultConfigData.getTestSourceSet(type),
- buildType,
- buildTypeData.getTestSourceSet(type),
- type,
- signingOverride);
-
- for (CoreProductFlavor productFlavor : productFlavorList) {
- ProductFlavorData<CoreProductFlavor> data = productFlavors
- .get(productFlavor.getName());
-
- String dimensionName = productFlavor.getDimension();
- if (dimensionName == null) {
- dimensionName = "";
- }
- // same supress warning here.
- //noinspection ConstantConditions
- testVariantConfig.addProductFlavor(
- data.getProductFlavor(),
- data.getTestSourceSet(type),
- dimensionName);
- }
-
- createCompoundSourceSets(
- productFlavorList,
- testVariantConfig,
- extension.getSourceSets());
-
- // create the internal storage for this variant.
- TestVariantData testVariantData = new TestVariantData(
- extension, taskManager, testVariantConfig, (TestedVariantData) testedVariantData);
- // link the testVariant to the tested variant in the other direction
- ((TestedVariantData) testedVariantData).setTestVariantData(testVariantData, type);
-
- return testVariantData;
- }
-
- /**
- * Creates VariantData for a specified list of product flavor.
- *
- * This will create VariantData for all build types of the given flavors.
- *
- * @param productFlavorList the flavor(s) to build.
- */
- private void createVariantDataForProductFlavors(
- @NonNull List<ProductFlavor> productFlavorList) {
-
- BuildTypeData testBuildTypeData = null;
- if (extension instanceof TestedAndroidConfig) {
- TestedAndroidConfig testedExtension = (TestedAndroidConfig) extension;
-
- testBuildTypeData = buildTypes.get(testedExtension.getTestBuildType());
- if (testBuildTypeData == null) {
- throw new RuntimeException(String.format(
- "Test Build Type '%1$s' does not exist.", testedExtension.getTestBuildType()));
- }
- }
-
- BaseVariantData variantForAndroidTest = null;
-
- CoreProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
-
- Action<com.android.build.gradle.api.VariantFilter> variantFilterAction =
- extension.getVariantFilter();
-
- for (BuildTypeData buildTypeData : buildTypes.values()) {
- boolean ignore = false;
- if (variantFilterAction != null) {
- variantFilter.reset(defaultConfig, buildTypeData.getBuildType(), productFlavorList);
- variantFilterAction.execute(variantFilter);
- ignore = variantFilter.isIgnore();
- }
-
- if (!ignore) {
- BaseVariantData<?> variantData = createVariantData(
- buildTypeData.getBuildType(),
- productFlavorList);
- variantDataList.add(variantData);
-
- GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
- ThreadRecorder.get().record(
- ExecutionType.VARIANT_CONFIG,
- Recorder.EmptyBlock,
- new Recorder.Property(
- "project",
- project.getName()),
- new Recorder.Property(
- "variant",
- variantData.getName()),
- new Recorder.Property(
- "use_jack",
- Boolean.toString(variantConfig.getUseJack())),
- new Recorder.Property(
- "use_minify",
- Boolean.toString(variantConfig.isMinifyEnabled())),
- new Recorder.Property(
- "use_multi_dex",
- Boolean.toString(variantConfig.isMultiDexEnabled())),
- new Recorder.Property(
- "multi_dex_legacy",
- Boolean.toString(variantConfig.isLegacyMultiDexMode())));
-
-
- if (variantFactory.hasTestScope()) {
- TestVariantData unitTestVariantData = createTestVariantData(
- variantData,
- UNIT_TEST);
- variantDataList.add(unitTestVariantData);
-
- if (buildTypeData == testBuildTypeData) {
- if (variantConfig.isMinifyEnabled() && variantConfig.getUseJack()) {
- throw new RuntimeException(
- "Cannot test obfuscated variants when compiling with jack.");
- }
- variantForAndroidTest = variantData;
- }
- }
- }
- }
-
- if (variantForAndroidTest != null) {
- TestVariantData androidTestVariantData = createTestVariantData(
- variantForAndroidTest,
- ANDROID_TEST);
- variantDataList.add(androidTestVariantData);
- }
- }
-
- private boolean isVariantPublished() {
- return extension.getPublishNonDefault();
- }
-
- private static void checkName(@NonNull String name, @NonNull String displayName) {
- checkPrefix(name, displayName, ANDROID_TEST.getPrefix());
- checkPrefix(name, displayName, UNIT_TEST.getPrefix());
-
- if (LINT.equals(name)) {
- throw new RuntimeException(String.format(
- "%1$s names cannot be %2$s", displayName, LINT));
- }
- }
-
- private static void checkPrefix(String name, String displayName, String prefix) {
- if (name.startsWith(prefix)) {
- throw new RuntimeException(String.format(
- "%1$s names cannot start with '%2$s'", displayName, prefix));
- }
- }
-
- private SigningConfig createSigningOverride() {
- AndroidGradleOptions.SigningOptions signingOptions =
- AndroidGradleOptions.getSigningOptions(project);
- if (signingOptions != null) {
- com.android.build.gradle.internal.dsl.SigningConfig signingConfigDsl =
- new com.android.build.gradle.internal.dsl.SigningConfig("externalOverride");
-
- signingConfigDsl.setStoreFile(new File(signingOptions.storeFile));
- signingConfigDsl.setStorePassword(signingOptions.keyPassword);
- signingConfigDsl.setKeyAlias(signingOptions.keyAlias);
- signingConfigDsl.setKeyPassword(signingOptions.keyPassword);
-
- if (signingOptions.storeType != null) {
- signingConfigDsl.setStoreType(signingOptions.storeType);
- }
-
- return signingConfigDsl;
- }
- return null;
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java
deleted file mode 100644
index 2137af8..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.api.ApkVariant;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.tasks.Dex;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.model.SigningConfig;
-
-import org.gradle.api.DefaultTask;
-
-import java.io.File;
-import java.util.Collection;
-
-/**
- * Implementation of the apk-generating variant.
- *
- * This is a wrapper around the internal data model, in order to control what is accessible
- * through the external API.
- */
-public abstract class ApkVariantImpl extends BaseVariantImpl implements ApkVariant {
-
- protected ApkVariantImpl(@NonNull AndroidBuilder androidBuilder,
- @NonNull ReadOnlyObjectProvider immutableObjectProvider) {
- super(androidBuilder, immutableObjectProvider);
- }
-
- @NonNull
- protected abstract ApkVariantData getApkVariantData();
-
- @Override
- @Nullable
- public String getVersionName() {
- return getApkVariantData().getVariantConfiguration().getVersionName();
- }
-
- @Override
- public int getVersionCode() {
- return getApkVariantData().getVariantConfiguration().getVersionCode();
- }
-
- @Override
- public Dex getDex() {
- return getApkVariantData().dexTask;
- }
-
- @Override
- public DefaultTask getUninstall() {
- return getApkVariantData().uninstallTask;
- }
-
- @Override
- public SigningConfig getSigningConfig() {
- return readOnlyObjectProvider.getSigningConfig(
- getApkVariantData().getVariantConfiguration().getSigningConfig());
- }
-
- @Override
- public boolean isSigningReady() {
- return getApkVariantData().isSigned();
- }
-
- @Override
- @NonNull
- public Collection<File> getCompileLibraries() {
- return androidBuilder.getCompileClasspath(
- getVariantData().getVariantConfiguration());
- }
-
- @Override
- @NonNull
- public Collection<File> getApkLibraries() {
- return androidBuilder.getPackagedJars(getVariantData().getVariantConfiguration());
- }
-
- @Override
- public DefaultTask getInstall() {
- return getApkVariantData().installTask;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java
deleted file mode 100644
index 3355eb0..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.api.BaseVariant;
-import com.android.build.gradle.api.BaseVariantOutput;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.tasks.AidlCompile;
-import com.android.build.gradle.tasks.GenerateBuildConfig;
-import com.android.build.gradle.tasks.MergeAssets;
-import com.android.build.gradle.tasks.MergeResources;
-import com.android.build.gradle.tasks.NdkCompile;
-import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.model.BuildType;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SourceProvider;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.Task;
-import org.gradle.api.tasks.Sync;
-import org.gradle.api.tasks.compile.AbstractCompile;
-import org.gradle.api.tasks.compile.JavaCompile;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Base class for variants.
- *
- * This is a wrapper around the internal data model, in order to control what is accessible
- * through the external API.
- */
-abstract class BaseVariantImpl implements BaseVariant {
-
- @NonNull
- protected AndroidBuilder androidBuilder;
-
- @NonNull
- protected ReadOnlyObjectProvider readOnlyObjectProvider;
-
- protected List<BaseVariantOutput> outputs = Lists.newArrayListWithExpectedSize(1);
-
- BaseVariantImpl(
- @NonNull AndroidBuilder androidBuilder,
- @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
- this.androidBuilder = androidBuilder;
- this.readOnlyObjectProvider = readOnlyObjectProvider;
- }
-
- @NonNull
- protected abstract BaseVariantData<?> getVariantData();
-
- public void addOutputs(@NonNull List<BaseVariantOutput> outputs) {
- this.outputs.addAll(outputs);
- }
-
- @Override
- @NonNull
- public String getName() {
- return getVariantData().getVariantConfiguration().getFullName();
- }
-
- @Override
- @NonNull
- public String getDescription() {
- return getVariantData().getDescription();
- }
-
- @Override
- @NonNull
- public String getDirName() {
- return getVariantData().getVariantConfiguration().getDirName();
- }
-
- @Override
- @NonNull
- public String getBaseName() {
- return getVariantData().getVariantConfiguration().getBaseName();
- }
-
- @NonNull
- @Override
- public String getFlavorName() {
- return getVariantData().getVariantConfiguration().getFlavorName();
- }
-
- @NonNull
- @Override
- public List<BaseVariantOutput> getOutputs() {
- return outputs;
- }
-
- @Override
- @NonNull
- public BuildType getBuildType() {
- return readOnlyObjectProvider.getBuildType(
- getVariantData().getVariantConfiguration().getBuildType());
- }
-
- @Override
- @NonNull
- public List<ProductFlavor> getProductFlavors() {
- return new ImmutableFlavorList(
- getVariantData().getVariantConfiguration().getProductFlavors(),
- readOnlyObjectProvider);
- }
-
- @Override
- @NonNull
- public ProductFlavor getMergedFlavor() {
- return getVariantData().getVariantConfiguration().getMergedFlavor();
- }
-
- @NonNull
- @Override
- public List<SourceProvider> getSourceSets() {
- return getVariantData().getVariantConfiguration().getSortedSourceProviders();
- }
-
- @Override
- @NonNull
- public String getApplicationId() {
- return getVariantData().getApplicationId();
- }
-
- @Override
- @NonNull
- public Task getPreBuild() {
- return getVariantData().preBuildTask;
- }
-
- @Override
- @NonNull
- public Task getCheckManifest() {
- return getVariantData().checkManifestTask;
- }
-
- @Override
- @NonNull
- public AidlCompile getAidlCompile() {
- return getVariantData().aidlCompileTask;
- }
-
- @Override
- @NonNull
- public RenderscriptCompile getRenderscriptCompile() {
- return getVariantData().renderscriptCompileTask;
- }
-
- @Override
- public MergeResources getMergeResources() {
- return getVariantData().mergeResourcesTask;
- }
-
- @Override
- public MergeAssets getMergeAssets() {
- return getVariantData().mergeAssetsTask;
- }
-
- @Override
- public GenerateBuildConfig getGenerateBuildConfig() {
- return getVariantData().generateBuildConfigTask;
- }
-
- @Override
- @Nullable
- public JavaCompile getJavaCompile() {
- return getVariantData().javacTask;
- }
-
- @NonNull
- @Override
- public AbstractCompile getJavaCompiler() {
- return getVariantData().javaCompilerTask;
- }
-
- @NonNull
- @Override
- public NdkCompile getNdkCompile() {
- return getVariantData().ndkCompileTask;
- }
-
- @Nullable
- @Override
- public Task getObfuscation() {
- return getVariantData().obfuscationTask;
- }
-
- @Nullable
- @Override
- public File getMappingFile() {
- return getVariantData().getMappingFile();
- }
-
- @Override
- @NonNull
- public Sync getProcessJavaResources() {
- return getVariantData().processJavaResourcesTask;
- }
-
- @Override
- @Nullable
- public Task getAssemble() {
- return getVariantData().assembleVariantTask;
- }
-
- @Override
- public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
- getVariantData().addJavaSourceFoldersToModel(generatedSourceFolders);
- }
-
- @Override
- public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
- getVariantData().addJavaSourceFoldersToModel(generatedSourceFolders);
- }
-
- @Override
- public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders) {
- getVariantData().registerJavaGeneratingTask(task, sourceFolders);
- }
-
- @Override
- public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> sourceFolders) {
- getVariantData().registerJavaGeneratingTask(task, sourceFolders);
- }
-
- @Override
- public void registerResGeneratingTask(@NonNull Task task, @NonNull File... generatedResFolders) {
- getVariantData().registerResGeneratingTask(task, generatedResFolders);
- }
-
- @Override
- public void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedResFolders) {
- getVariantData().registerResGeneratingTask(task, generatedResFolders);
- }
-
- @Override
- public void buildConfigField(@NonNull String type, @NonNull String name,
- @NonNull String value) {
- getVariantData().getVariantConfiguration().addBuildConfigField(type, name, value);
- }
-
- @Override
- public void resValue(@NonNull String type, @NonNull String name, @NonNull String value) {
- getVariantData().getVariantConfiguration().addResValue(type, name, value);
- }
-
- @Override
- public void setOutputsAreSigned(boolean isSigned) {
- getVariantData().outputsAreSigned = isSigned;
- }
-
- @Override
- public boolean getOutputsAreSigned() {
- return getVariantData().outputsAreSigned;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java
deleted file mode 100644
index 97f4738..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.api;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.api.AndroidSourceDirectorySet;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.Project;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.file.FileTreeElement;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.util.PatternFilterable;
-import org.gradle.api.tasks.util.PatternSet;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import groovy.lang.Closure;
-
-/**
- * Default implementation of the AndroidSourceDirectorySet.
- */
-public class DefaultAndroidSourceDirectorySet implements AndroidSourceDirectorySet {
-
- private final String name;
- private final Project project;
- private List<Object> source = Lists.newArrayList();
- private final PatternSet filter = new PatternSet();
-
- public DefaultAndroidSourceDirectorySet(@NonNull String name,
- @NonNull Project project) {
- this.name = name;
- this.project = project;
- }
-
- @Override
- @NonNull
- public String getName() {
- return name;
- }
-
- @Override
- @NonNull
- public AndroidSourceDirectorySet srcDir(Object srcDir) {
- source.add(srcDir);
- return this;
- }
-
- @Override
- @NonNull
- public AndroidSourceDirectorySet srcDirs(Object... srcDirs) {
- Collections.addAll(source, srcDirs);
- return this;
- }
-
- @Override
- @NonNull
- public AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs) {
- source.clear();
- for (Object srcDir : srcDirs) {
- source.add(srcDir);
- }
- return this;
- }
-
- @Override
- @NonNull
- public FileTree getSourceFiles() {
- FileTree src = null;
- Set<File> sources = getSrcDirs();
- if (!sources.isEmpty()) {
- src = project.files(new ArrayList<Object>(sources)).getAsFileTree().matching(filter);
- }
- return src == null ? project.files().getAsFileTree() : src;
- }
-
- @Override
- @NonNull
- public Set<File> getSrcDirs() {
- return project.files(source.toArray()).getFiles();
- }
-
- @Override
- @NonNull
- public PatternFilterable getFilter() {
- return filter;
- }
-
-
- @Override
- @NonNull
- public String toString() {
- return source.toString();
- }
-
- @Override
- public Set<String> getIncludes() {
- return filter.getIncludes();
- }
-
- @Override
- public Set<String> getExcludes() {
- return filter.getExcludes();
- }
-
- @Override
- public PatternFilterable setIncludes(Iterable<String> includes) {
- filter.setIncludes(includes);
- return this;
- }
-
- @Override
- public PatternFilterable setExcludes(Iterable<String> excludes) {
- filter.setExcludes(excludes);
- return this;
- }
-
- @Override
- public PatternFilterable include(String... includes) {
- filter.include(includes);
- return this;
- }
-
- @Override
- public PatternFilterable include(Iterable<String> includes) {
- filter.include(includes);
- return this;
- }
-
- @Override
- public PatternFilterable include(Spec<FileTreeElement> includeSpec) {
- filter.include(includeSpec);
- return this;
- }
-
- @Override
- public PatternFilterable include(Closure includeSpec) {
- filter.include(includeSpec);
- return this;
- }
-
- @Override
- public PatternFilterable exclude(Iterable<String> excludes) {
- filter.exclude(excludes);
- return this;
- }
-
- @Override
- public PatternFilterable exclude(String... excludes) {
- filter.exclude(excludes);
- return this;
- }
-
- @Override
- public PatternFilterable exclude(Spec<FileTreeElement> excludeSpec) {
- filter.exclude(excludeSpec);
- return this;
- }
-
- @Override
- public PatternFilterable exclude(Closure excludeSpec) {
- filter.exclude(excludeSpec);
- return this;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBaseConfig.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBaseConfig.groovy
deleted file mode 100644
index ac6a905..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBaseConfig.groovy
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.api
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.builder.model.BaseConfig
-import com.android.builder.model.ClassField
-import com.google.common.collect.ImmutableList
-import com.google.common.collect.ImmutableMap
-/**
- * Read-only version of the BaseConfig wrapping another BaseConfig.
- *
- * In the variant API, it is important that the objects returned by the variants
- * are read-only.
- *
- * However, even though the API is defined to use the base interfaces as return
- * type (which all contain only getters), the dynamics of Groovy makes it easy to
- * actually use the setters of the implementation classes.
- *
- * This wrapper ensures that the returned instance is actually just a strict implementation
- * of the base interface and is read-only.
- */
-abstract class ReadOnlyBaseConfig implements BaseConfig {
-
- @NonNull
- private BaseConfig baseConfig
-
- protected ReadOnlyBaseConfig(@NonNull BaseConfig baseConfig) {
- this.baseConfig = baseConfig
- }
-
- @NonNull
- @Override
- public String getName() {
- return baseConfig.getName()
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getBuildConfigFields() {
- // TODO: cache immutable map?
- return ImmutableMap.copyOf(baseConfig.getBuildConfigFields())
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getResValues() {
- return ImmutableMap.copyOf(baseConfig.getResValues())
- }
-
- @NonNull
- @Override
- public Collection<File> getProguardFiles() {
- return ImmutableList.copyOf(baseConfig.getProguardFiles())
- }
-
- @NonNull
- @Override
- public Collection<File> getConsumerProguardFiles() {
- return ImmutableList.copyOf(baseConfig.getConsumerProguardFiles())
- }
-
- @NonNull
- @Override
- Collection<File> getTestProguardFiles() {
- return ImmutableList.copyOf(baseConfig.getTestProguardFiles())
- }
-
- @NonNull
- @Override
- public Map<String, Object> getManifestPlaceholders() {
- return ImmutableMap.copyOf(baseConfig.getManifestPlaceholders())
- }
-
- @Nullable
- @Override
- public Boolean getMultiDexEnabled() {
- return baseConfig.getMultiDexEnabled()
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepFile() {
- return baseConfig.getMultiDexKeepFile()
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepProguard() {
- return baseConfig.getMultiDexKeepProguard()
- }
-
- /**
- * Some build scripts add dynamic properties to flavors declaration (and others) and expect
- * to retrieve such properties values through this model. Delegate any property we don't
- * know about to the {@see BaseConfig} groovy object which hopefully will know about the
- * dynamic property.
- * @param name the property name
- * @return the property value if exists or an exception will be thrown.
- */
- def propertyMissing(String name) {
- try {
- baseConfig."$name"
- } catch(MissingPropertyException e) {
- // do not leak implementation types, replace the delegate with ourselves in the message
- throw new MissingPropertyException("Could not find ${name} on ${this}")
- }
- }
-
- /**
- * Do not authorize setting dynamic properties values and provide a meaningful error message.
- */
- def propertyMissing(String name, value) {
- throw new RuntimeException("Cannot set property @{name} on read-only ${baseConfig.class}")
- }
-
- /**
- * Provide dynamic properties refective access.
- * @param name a property name
- * @return true if this object of {@link #baseConfig} supports the passed property name
- */
- def hasProperty(String name) {
- if (super.hasProperty(name)) {
- return true
- }
- return baseConfig.hasProperty(name)
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBuildType.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBuildType.java
deleted file mode 100644
index 9399907..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBuildType.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.BuildType;
-import com.android.builder.model.SigningConfig;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Read-only version of the BuildType wrapping another BuildType.
- *
- * In the variant API, it is important that the objects returned by the variants
- * are read-only.
- *
- * However, even though the API is defined to use the base interfaces as return
- * type (which all contain only getters), the dynamics of Groovy makes it easy to
- * actually use the setters of the implementation classes.
- *
- * This wrapper ensures that the returned instance is actually just a strict implementation
- * of the base interface and is read-only.
- */
-public class ReadOnlyBuildType extends ReadOnlyBaseConfig implements BuildType {
-
- @NonNull
- private final BuildType buildType;
-
- @NonNull
- private final ReadOnlyObjectProvider readOnlyObjectProvider;
-
- public ReadOnlyBuildType(
- @NonNull BuildType buildType,
- @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
- super(buildType);
- this.buildType = buildType;
- this.readOnlyObjectProvider = readOnlyObjectProvider;
- }
-
- @Override
- public boolean isDebuggable() {
- return buildType.isDebuggable();
- }
-
- @Override
- public boolean isTestCoverageEnabled() {
- return buildType.isTestCoverageEnabled();
- }
-
- @Override
- public boolean isJniDebuggable() {
- return buildType.isJniDebuggable();
- }
-
- @Override
- public boolean isPseudoLocalesEnabled() {
- return buildType.isPseudoLocalesEnabled();
- }
-
- @Override
- public boolean isRenderscriptDebuggable() {
- return buildType.isRenderscriptDebuggable();
- }
-
- @Override
- public int getRenderscriptOptimLevel() {
- return buildType.getRenderscriptOptimLevel();
- }
-
- @Nullable
- @Override
- public String getApplicationIdSuffix() {
- return buildType.getApplicationIdSuffix();
- }
-
- @Nullable
- @Override
- public String getVersionNameSuffix() {
- return buildType.getVersionNameSuffix();
- }
-
- @Override
- public boolean isMinifyEnabled() {
- return buildType.isMinifyEnabled();
- }
-
- @Override
- public boolean isZipAlignEnabled() {
- return buildType.isZipAlignEnabled();
- }
-
- @Override
- public boolean isEmbedMicroApp() {
- return buildType.isEmbedMicroApp();
- }
-
- @Nullable
- @Override
- public SigningConfig getSigningConfig() {
- return readOnlyObjectProvider.getSigningConfig(buildType.getSigningConfig());
- }
-
- @NonNull
- @Override
- public List<File> getJarJarRuleFiles() {
- return buildType.getJarJarRuleFiles();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyProductFlavor.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyProductFlavor.java
deleted file mode 100644
index 2c0a489..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyProductFlavor.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SigningConfig;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * read-only version of the ProductFlavor wrapping another ProductFlavor.
- *
- * In the variant API, it is important that the objects returned by the variants
- * are read-only.
- *
- * However, even though the API is defined to use the base interfaces as return
- * type (which all contain only getters), the dynamics of Groovy makes it easy to
- * actually use the setters of the implementation classes.
- *
- * This wrapper ensures that the returned instance is actually just a strict implementation
- * of the base interface and is read-only.
- */
-public class ReadOnlyProductFlavor extends ReadOnlyBaseConfig implements ProductFlavor {
-
- @NonNull
- /*package*/ final ProductFlavor productFlavor;
-
- @NonNull
- private final ReadOnlyObjectProvider readOnlyObjectProvider;
-
- ReadOnlyProductFlavor(
- @NonNull ProductFlavor productFlavor,
- @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
- super(productFlavor);
- this.productFlavor = productFlavor;
- this.readOnlyObjectProvider = readOnlyObjectProvider;
- }
-
- @Nullable
- @Override
- public String getApplicationId() {
- return productFlavor.getApplicationId();
- }
-
- @Nullable
- @Override
- public Integer getVersionCode() {
- return productFlavor.getVersionCode();
- }
-
- @Nullable
- @Override
- public String getVersionName() {
- return productFlavor.getVersionName();
- }
-
- @Nullable
- @Override
- public ApiVersion getMinSdkVersion() {
- return productFlavor.getMinSdkVersion();
- }
-
- @Nullable
- @Override
- public ApiVersion getTargetSdkVersion() {
- return productFlavor.getTargetSdkVersion();
- }
-
- @Nullable
- @Override
- public Integer getMaxSdkVersion() {
- return productFlavor.getMaxSdkVersion();
- }
-
- @Nullable
- @Override
- public Integer getRenderscriptTargetApi() {
- return productFlavor.getRenderscriptTargetApi();
- }
-
- @Nullable
- @Override
- public Boolean getRenderscriptSupportModeEnabled() {
- return productFlavor.getRenderscriptSupportModeEnabled();
- }
-
- @Nullable
- @Override
- public Boolean getRenderscriptNdkModeEnabled() {
- return productFlavor.getRenderscriptNdkModeEnabled();
- }
-
- @Nullable
- @Override
- public String getTestApplicationId() {
- return productFlavor.getTestApplicationId();
- }
-
- @Nullable
- @Override
- public String getTestInstrumentationRunner() {
- return productFlavor.getTestInstrumentationRunner();
- }
-
- @NonNull
- @Override
- public Map<String, String> getTestInstrumentationRunnerArguments() {
- return productFlavor.getTestInstrumentationRunnerArguments();
- }
-
- @Nullable
- @Override
- public Boolean getTestHandleProfiling() {
- return productFlavor.getTestHandleProfiling();
- }
-
- @Nullable
- @Override
- public Boolean getTestFunctionalTest() {
- return productFlavor.getTestFunctionalTest();
- }
-
- @NonNull
- @Override
- public Collection<String> getResourceConfigurations() {
- return productFlavor.getResourceConfigurations();
- }
-
- @Nullable
- @Override
- public SigningConfig getSigningConfig() {
- return readOnlyObjectProvider.getSigningConfig(productFlavor.getSigningConfig());
- }
-
- @Nullable
- @Override
- public String getDimension() {
- return productFlavor.getDimension();
- }
-
- @NonNull
- @Override
- public List<File> getJarJarRuleFiles() {
- return productFlavor.getJarJarRuleFiles();
- }
-
- @Nullable
- @Deprecated
- public String getFlavorDimension() {
- return productFlavor.getDimension();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/GradleVariantConfiguration.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/GradleVariantConfiguration.java
deleted file mode 100644
index 0aa6fe5..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/GradleVariantConfiguration.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.core;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.builder.core.VariantConfiguration;
-import com.android.builder.core.VariantType;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.model.SourceProvider;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Version of {@link com.android.builder.core.VariantConfiguration} that uses the specific
- * types used in the Gradle plugins.
- *
- * It also adds support for Ndk support that is not ready to go in the builder library.
- */
-public class GradleVariantConfiguration extends VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> {
-
- private final MergedNdkConfig mMergedNdkConfig = new MergedNdkConfig();
-
- /**
- * Creates a {@link GradleVariantConfiguration} for a normal (non-test) variant.
- */
- public GradleVariantConfiguration(
- @NonNull CoreProductFlavor defaultConfig,
- @NonNull SourceProvider defaultSourceProvider,
- @NonNull CoreBuildType buildType,
- @Nullable SourceProvider buildTypeSourceProvider,
- @NonNull VariantType type,
- @Nullable SigningConfig signingConfigOverride) {
- super(defaultConfig, defaultSourceProvider, buildType, buildTypeSourceProvider, type,
- signingConfigOverride);
- computeNdkConfig();
- }
-
- /**
- * Creates a {@link GradleVariantConfiguration} for a testing variant.
- */
- public GradleVariantConfiguration(
- @Nullable VariantConfiguration testedConfig,
- @NonNull CoreProductFlavor defaultConfig,
- @NonNull SourceProvider defaultSourceProvider,
- @NonNull CoreBuildType buildType,
- @Nullable SourceProvider buildTypeSourceProvider,
- @NonNull VariantType type,
- @Nullable SigningConfig signingConfigOverride) {
- super(defaultConfig, defaultSourceProvider, buildType, buildTypeSourceProvider, type,
- testedConfig, signingConfigOverride);
- computeNdkConfig();
- }
-
- @NonNull
- @Override
- public VariantConfiguration addProductFlavor(
- @NonNull CoreProductFlavor productFlavor,
- @NonNull SourceProvider sourceProvider,
- @NonNull String dimensionName) {
- checkNotNull(productFlavor);
- checkNotNull(sourceProvider);
- checkNotNull(dimensionName);
- super.addProductFlavor(productFlavor, sourceProvider, dimensionName);
- computeNdkConfig();
- return this;
- }
-
- @NonNull
- public CoreNdkOptions getNdkConfig() {
- return mMergedNdkConfig;
- }
-
- /**
- * Returns the ABI filters associated with the artifact, or null if there are no filters.
- *
- * If the list contains values, then the artifact only contains these ABIs and excludes
- * others.
- */
- @Nullable
- public Set<String> getSupportedAbis() {
- return mMergedNdkConfig.getAbiFilters();
- }
-
- /**
- * Returns whether the configuration has minification enabled.
- */
- public boolean isMinifyEnabled() {
- VariantType type = getType();
- // if type == test then getTestedConfig always returns non-null
- //noinspection ConstantConditions
- return getBuildType().isMinifyEnabled() &&
- (!type.isForTesting() || (getTestedConfig().getType() != VariantType.LIBRARY));
- }
-
- public boolean getUseJack() {
- Boolean value = getBuildType().getUseJack();
- if (value != null) {
- return value;
- }
-
- // cant use merge flavor as useJack is not a prop on the base class.
- for (CoreProductFlavor productFlavor : getProductFlavors()) {
- value = productFlavor.getUseJack();
- if (value != null) {
- return value;
- }
- }
-
- value = getDefaultConfig().getUseJack();
- if (value != null) {
- return value;
- }
-
- return false;
- }
-
- private void computeNdkConfig() {
- mMergedNdkConfig.reset();
-
- if (getDefaultConfig().getNdkConfig() != null) {
- mMergedNdkConfig.append(getDefaultConfig().getNdkConfig());
- }
-
- final List<CoreProductFlavor> flavors = getProductFlavors();
- for (int i = flavors.size() - 1 ; i >= 0 ; i--) {
- CoreNdkOptions ndkConfig = flavors.get(i).getNdkConfig();
- if (ndkConfig != null) {
- mMergedNdkConfig.append(ndkConfig);
- }
- }
-
- if (getBuildType().getNdkConfig() != null && !getType().isForTesting()) {
- mMergedNdkConfig.append(getBuildType().getNdkConfig());
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoExtension.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoExtension.groovy
deleted file mode 100644
index b650411..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoExtension.groovy
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.coverage
-
-/**
- * DSL object for configuring JaCoCo settings.
- */
-class JacocoExtension {
- /**
- * The version of jacoco to use.
- */
- String version = '0.7.4.201502262128'
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoInstrumentTask.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoInstrumentTask.groovy
deleted file mode 100644
index 8a36055..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoInstrumentTask.groovy
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.coverage
-
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.PostCompilationData
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.FileCollection
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
-
-/**
- * Simple Jacoco instrument task that calls the Ant version.
- */
-public class JacocoInstrumentTask extends DefaultTask {
-
- @InputDirectory
- File inputDir
-
- @OutputDirectory
- File outputDir
-
- /**
- * Classpath containing Jacoco classes for use by the task.
- */
- @InputFiles
- FileCollection jacocoClasspath
-
- @TaskAction
- void instrument() {
- File outDir = getOutputDir()
- outDir.deleteDir()
- outDir.mkdirs()
-
- getAnt().taskdef(name: 'instrumentWithJacoco',
- classname: 'org.jacoco.ant.InstrumentTask',
- classpath: getJacocoClasspath().asPath)
- getAnt().instrumentWithJacoco(destdir: outDir) {
- fileset(dir: getInputDir())
- }
- }
-
- public static class ConfigAction implements TaskConfigAction<JacocoInstrumentTask> {
-
- VariantScope scope;
-
- PostCompilationData pcData;
-
- ConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope
- this.pcData = pcData
- }
-
- @Override
- String getName() {
- return scope.getTaskName("instrument");
- }
-
- @Override
- Class<JacocoInstrumentTask> getType() {
- return JacocoInstrumentTask.class
- }
-
- @Override
- void execute(JacocoInstrumentTask jacocoTask) {
-
- ConventionMappingHelper.map(jacocoTask, "jacocoClasspath") {
- scope.globalScope.project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME]
- }
- // can't directly use the existing inputFiles closure as we need the dir instead :\
- ConventionMappingHelper.map(jacocoTask, "inputDir", pcData.inputDirCallable)
- ConventionMappingHelper.map(jacocoTask, "outputDir") {
- new File("${scope.globalScope.buildDir}/${FD_INTERMEDIATES}/coverage-instrumented-classes/${scope.variantConfiguration.dirName}")
- }
- scope.variantData.jacocoInstrumentTask = jacocoTask
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.groovy
deleted file mode 100644
index 1852665..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.groovy
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.coverage
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-
-/**
- * Jacoco plugin. This is very similar to the built-in support for Jacoco but we dup it in order
- * to control it as we need our own offline instrumentation.
- *
- * This may disappear if we can ever reuse the built-in support.
- *
- */
-class JacocoPlugin implements Plugin<Project> {
- public static final String ANT_CONFIGURATION_NAME = 'androidJacocoAnt'
- public static final String AGENT_CONFIGURATION_NAME = 'androidJacocoAgent'
-
- private Project project;
-
- @Override
- void apply(Project project) {
- this.project = project
-
- addJacocoConfigurations()
- configureAgentDependencies()
- configureTaskClasspathDefaults()
- }
-
- /**
- * Creates the configurations used by plugin.
- * @param project the project to add the configurations to
- */
- private void addJacocoConfigurations() {
- this.project.configurations.create(AGENT_CONFIGURATION_NAME).with {
- visible = false
- transitive = true
- description = 'The Jacoco agent to use to get coverage data.'
- }
- this.project.configurations.create(ANT_CONFIGURATION_NAME).with {
- visible = false
- transitive = true
- description = 'The Jacoco ant tasks to use to get execute Gradle tasks.'
- }
- }
-
- /**
- * Configures the agent dependencies using the 'jacocoAnt' configuration.
- * Uses the version declared in 'toolVersion' of the Jacoco extension if no dependencies are explicitly declared.
- */
- private void configureAgentDependencies() {
- def config = project.configurations[AGENT_CONFIGURATION_NAME]
- config.incoming.beforeResolve {
- if (config.dependencies.empty) {
- config.dependencies.add(project.dependencies.create("org.jacoco:org.jacoco.agent:${project.android.jacoco.version}"))
- }
- }
- }
-
- /**
- * Configures the classpath for Jacoco tasks using the 'jacocoAnt' configuration.
- * Uses the version information declared in 'toolVersion' of the Jacoco extension if no dependencies are explicitly declared.
- */
- private void configureTaskClasspathDefaults() {
- def config = project.configurations[ANT_CONFIGURATION_NAME]
- config.incoming.beforeResolve {
- if (config.dependencies.empty) {
- config.dependencies.add(project.dependencies.create("org.jacoco:org.jacoco.ant:${project.android.jacoco.version}"))
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.groovy
deleted file mode 100644
index 68a653a..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.groovy
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dependency
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.gradle.internal.ConfigurationProvider
-import com.android.builder.core.VariantType
-import com.android.builder.dependency.DependencyContainer
-import com.android.builder.dependency.JarDependency
-import com.android.builder.dependency.LibraryDependency
-import com.google.common.base.Objects
-import com.google.common.collect.Sets
-import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-
-/**
- * Object that represents the dependencies of a "config", in the sense of defaultConfigs, build
- * type and flavors.
- *
- * The dependencies are expressed as composite Gradle configuration objects that extends
- * all the configuration objects of the "configs".
- *
- * It optionally contains the dependencies for a test config for the given config.
- */
-public class VariantDependencies implements DependencyContainer {
-
- final String name
-
- @NonNull
- private final Configuration compileConfiguration
- @NonNull
- private final Configuration packageConfiguration
- @NonNull
- private final Configuration publishConfiguration
-
- @Nullable
- private final Configuration mappingConfiguration
- @Nullable
- private final Configuration classesConfiguration
- @Nullable
- private final Configuration metadataConfiguration
-
- @NonNull
- private final List<LibraryDependencyImpl> libraries = []
- @NonNull
- private final List<JarDependency> jars = []
- @NonNull
- private final List<JarDependency> localJars = []
-
- /**
- * Whether we have a direct dependency on com.android.support:support-annotations; this
- * is used to drive whether we extract annotations when building libraries for example
- */
- boolean annotationsPresent
-
- DependencyChecker checker
-
- static VariantDependencies compute(
- @NonNull Project project,
- @NonNull String name,
- boolean publishVariant,
- @NonNull VariantType variantType,
- @Nullable VariantDependencies parentVariant,
- @NonNull ConfigurationProvider... providers) {
- Set<Configuration> compileConfigs = Sets.newHashSetWithExpectedSize(providers.length * 2)
- Set<Configuration> apkConfigs = Sets.newHashSetWithExpectedSize(providers.length)
-
- for (ConfigurationProvider provider : providers) {
- if (provider != null) {
- compileConfigs.add(provider.compileConfiguration)
- if (provider.providedConfiguration != null) {
- compileConfigs.add(provider.providedConfiguration)
- }
-
- apkConfigs.add(provider.compileConfiguration)
- apkConfigs.add(provider.packageConfiguration)
- }
- }
-
- if (parentVariant != null) {
- compileConfigs.add(parentVariant.getCompileConfiguration())
- apkConfigs.add(parentVariant.getPackageConfiguration())
- }
-
- Configuration compile = project.configurations.maybeCreate("_${name}Compile")
- compile.visible = false
- compile.description = "## Internal use, do not manually configure ##"
- compile.setExtendsFrom(compileConfigs)
-
- Configuration apk = project.configurations.maybeCreate(variantType == VariantType.LIBRARY
- ? "_${name}Publish"
- : "_${name}Apk")
-
- apk.visible = false
- apk.description = "## Internal use, do not manually configure ##"
- apk.setExtendsFrom(apkConfigs)
-
- Configuration publish = null, mapping = null, classes = null, metadata = null;
- if (publishVariant) {
- publish = project.configurations.maybeCreate(name)
- publish.description = "Published Configuration for Variant ${name}"
- // if the variant is not a library, then the publishing configuration should
- // not extend from the apkConfigs. It's mostly there to access the artifact from
- // another project but it shouldn't bring any dependencies with it.
- if (variantType == VariantType.LIBRARY) {
- publish.setExtendsFrom(apkConfigs)
- }
-
- // create configuration for -metadata.
- metadata = project.configurations.create("$name-metadata")
- metadata.description = "Published APKs metadata for Variant $name"
-
- // create configuration for -mapping and -classes.
- mapping = project.configurations.maybeCreate("$name-mapping")
- mapping.description = "Published mapping configuration for Variant $name"
-
- classes = project.configurations.maybeCreate("$name-classes")
- classes.description = "Published classes configuration for Variant $name"
- // because we need the transitive dependencies for the classes, extend the compile config.
- classes.setExtendsFrom(compileConfigs)
- }
-
- return new VariantDependencies(
- name,
- compile,
- apk,
- publish,
- mapping,
- classes,
- metadata,
- variantType != VariantType.UNIT_TEST);
- }
-
- private VariantDependencies(@NonNull String name,
- @NonNull Configuration compileConfiguration,
- @NonNull Configuration packageConfiguration,
- @Nullable Configuration publishConfiguration,
- @Nullable Configuration mappingConfiguration,
- @Nullable Configuration classesConfiguration,
- @Nullable Configuration metadataConfiguration,
- boolean skipClassesInAndroid) {
- this.name = name
- this.compileConfiguration = compileConfiguration
- this.packageConfiguration = packageConfiguration
- this.publishConfiguration = publishConfiguration
- this.mappingConfiguration = mappingConfiguration
- this.classesConfiguration = classesConfiguration
- this.metadataConfiguration = metadataConfiguration
- this.checker = new DependencyChecker(this, skipClassesInAndroid)
- }
-
- public String getName() {
- return name
- }
-
- @NonNull
- Configuration getCompileConfiguration() {
- return compileConfiguration
- }
-
- @NonNull
- Configuration getPackageConfiguration() {
- return packageConfiguration
- }
-
- @Nullable
- Configuration getPublishConfiguration() {
- return publishConfiguration
- }
-
- @Nullable
- Configuration getMappingConfiguration() {
- return mappingConfiguration
- }
-
- @Nullable
- Configuration getClassesConfiguration() {
- return classesConfiguration
- }
-
- @Nullable
- Configuration getMetadataConfiguration() {
- return metadataConfiguration
- }
-
- void addLibraries(@NonNull List<LibraryDependencyImpl> list) {
- libraries.addAll(list)
- }
-
- void addJars(@NonNull Collection<JarDependency> list) {
- jars.addAll(list)
- }
-
- void addLocalJars(@NonNull Collection<JarDependency> list) {
- localJars.addAll(list)
- }
-
- @NonNull
- List<LibraryDependencyImpl> getLibraries() {
- return libraries
- }
-
- @NonNull
- @Override
- List<? extends LibraryDependency> getAndroidDependencies() {
- return libraries
- }
-
- @NonNull
- @Override
- List<JarDependency> getJarDependencies() {
- return jars
- }
-
- @NonNull
- @Override
- List<JarDependency> getLocalDependencies() {
- return localJars
- }
-
- public boolean hasNonOptionalLibraries() {
- for (LibraryDependency libraryDependency : libraries) {
- if (!libraryDependency.isOptional()) {
- return true;
- }
- }
-
- return false;
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this)
- .add("name", name)
- .toString();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptions.java
deleted file mode 100644
index 3c3bdc4..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptions.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Optional;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * DSL object for configuring aapt options.
- */
-public class AaptOptions implements com.android.builder.model.AaptOptions {
-
- @Nullable
- private String ignoreAssetsPattern;
-
- @Nullable
- private List<String> noCompressList;
-
- private boolean useNewCruncher = true;
-
- private boolean cruncherEnabled = true;
-
- private boolean failOnMissingConfigEntry = false;
-
- @Nullable
- private List<String> additionalParameters;
-
- public void setIgnoreAssetsPattern(@Nullable String ignoreAssetsPattern) {
- this.ignoreAssetsPattern = ignoreAssetsPattern;
- }
-
- /**
- * Pattern describing assets to be ignore.
- *
- * <p>See <code>aapt --help</code>
- */
- @Override
- @Optional
- @Input
- public String getIgnoreAssets() {
- return ignoreAssetsPattern;
- }
-
- public void setNoCompress(String noCompress) {
- noCompressList = Collections.singletonList(noCompress);
- }
-
- public void setNoCompress(String... noCompress) {
- noCompressList = Arrays.asList(noCompress);
- }
-
- /**
- * Extensions of files that will not be stored compressed in the APK.
- *
- * <p>Equivalent of the -0 flag. See <code>aapt --help</code>
- */
- @Override
- @Optional
- @Input
- public Collection<String> getNoCompress() {
- return noCompressList;
- }
-
- public void useNewCruncher(boolean value) {
- useNewCruncher = value;
- }
-
- public void setUseNewCruncher(boolean value) {
- useNewCruncher = value;
- }
-
- /**
- * Enables or disables PNG crunching.
- */
- public void setCruncherEnabled(boolean value) {
- cruncherEnabled = value;
- }
-
- /**
- * Returns true if the PNGs should be crunched, false otherwise.
- */
- @Input
- public boolean getCruncherEnabled() {
- return cruncherEnabled;
- }
-
- /**
- * Whether to use the new cruncher.
- */
- @Input
- public boolean getUseNewCruncher() {
- return useNewCruncher;
- }
-
- public void failOnMissingConfigEntry(boolean value) {
- failOnMissingConfigEntry = value;
- }
-
- public void setFailOnMissingConfigEntry(boolean value) {
- failOnMissingConfigEntry = value;
- }
-
- /**
- * Forces aapt to return an error if it fails to find an entry for a configuration.
- *
- * <p>See <code>aapt --help</code>
- */
- @Override
- @Input
- public boolean getFailOnMissingConfigEntry() {
- return failOnMissingConfigEntry;
- }
-
- // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
-
- /**
- * Sets extensions of files that will not be stored compressed in the APK.
- *
- * <p>Equivalent of the -0 flag. See <code>aapt --help</code>
- */
- public void noCompress(String noCompress) {
- noCompressList = Collections.singletonList(noCompress);
- }
-
- /**
- * Sets extensions of files that will not be stored compressed in the APK.
- *
- * <p>Equivalent of the -0 flag. See <code>aapt --help</code>
- */
- public void noCompress(String... noCompress) {
- noCompressList = Arrays.asList(noCompress);
- }
-
- public void additionalParameters(@NonNull String param) {
- additionalParameters = Collections.singletonList(param);
- }
-
- public void additionalParameters(String... params) {
- additionalParameters = Arrays.asList(params);
- }
-
- public void setAdditionalParameters(@Nullable List<String> parameters) {
- additionalParameters = parameters;
- }
-
- @Nullable
- @Override
- @Optional
- @Input
- public List<String> getAdditionalParameters() {
- return additionalParameters;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AdbOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AdbOptions.java
deleted file mode 100644
index 8a89755..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AdbOptions.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Options for the adb tool.
- */
-public class AdbOptions implements com.android.builder.model.AdbOptions {
-
- int timeOutInMs;
-
- List<String> installOptions;
-
- @Override
- public int getTimeOutInMs() {
- return timeOutInMs;
- }
-
- public void setTimeOutInMs(int timeOutInMs) {
- this.timeOutInMs = timeOutInMs;
- }
-
- public void timeOutInMs(int timeOutInMs) {
- setTimeOutInMs(timeOutInMs);
- }
-
- @Override
- public Collection<String> getInstallOptions() {
- return installOptions;
- }
-
- public void setInstallOptions(String option) {
- installOptions = ImmutableList.of(option);
- }
-
- public void setInstallOptions(String... options) {
- installOptions = ImmutableList.copyOf(options);
- }
-
- public void installOptions(String option) {
- installOptions = ImmutableList.of(option);
- }
-
- public void installOptions(String... options) {
- installOptions = ImmutableList.copyOf(options);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildType.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildType.java
deleted file mode 100644
index 9b7274d..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildType.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.core.DefaultBuildType;
-import com.android.builder.model.BaseConfig;
-import com.android.builder.model.ClassField;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.logging.Logger;
-import org.gradle.internal.reflect.Instantiator;
-
-import java.io.Serializable;
-
-/**
- * DSL object to configure build types.
- */
-public class BuildType extends DefaultBuildType implements CoreBuildType, Serializable {
-
- private static final long serialVersionUID = 1L;
-
- @NonNull
- private final Project project;
-
- @NonNull
- private final Logger logger;
-
- @Nullable
- private final NdkOptions ndkConfig;
-
- @Nullable
- private Boolean useJack;
-
- private boolean shrinkResources = false; // opt-in for now until we've validated it in the field
-
- public BuildType(@NonNull String name,
- @NonNull Project project,
- @NonNull Instantiator instantiator,
- @NonNull Logger logger) {
- super(name);
- this.project = project;
- this.logger = logger;
- ndkConfig = instantiator.newInstance(NdkOptions.class);
- }
-
- @VisibleForTesting
- BuildType(@NonNull String name,
- @NonNull Project project,
- @NonNull Logger logger) {
- super(name);
- this.project = project;
- this.logger = logger;
- ndkConfig = null;
- }
-
- @Override
- @Nullable
- public CoreNdkOptions getNdkConfig() {
- return ndkConfig;
- }
-
- /**
- * Initialize the DSL object. Not meant to be used from the build scripts.
- */
- public void init(SigningConfig debugSigningConfig) {
- if (BuilderConstants.DEBUG.equals(getName())) {
- setDebuggable(true);
- setEmbedMicroApp(false);
-
- assert debugSigningConfig != null;
- setSigningConfig(debugSigningConfig);
- } else if (BuilderConstants.RELEASE.equals(getName())) {
- // no config needed for now.
- }
- }
-
- /** The signing configuration. */
- @Override
- @Nullable
- public SigningConfig getSigningConfig() {
- return (SigningConfig) super.getSigningConfig();
- }
-
- @Override
- protected void _initWith(@NonNull BaseConfig that) {
- super._initWith(that);
- BuildType thatBuildType = (BuildType) that;
- shrinkResources = thatBuildType.isShrinkResources();
- useJack = thatBuildType.getUseJack();
- }
-
- public int hashCode() {
- int result = super.hashCode();
- result = 31 * result + (useJack != null ? useJack.hashCode() : 0);
- result = 31 * result + (shrinkResources ? 1 : 0);
- return result;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof BuildType)) return false;
- if (!super.equals(o)) return false;
- BuildType other = (BuildType) o;
- if (useJack != other.getUseJack()) return false;
- if (shrinkResources != other.isShrinkResources()) return false;
-
- return true;
- }
-
- // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
-
- /**
- * Adds a new field to the generated BuildConfig class.
- *
- * <p>The field is generated as: <code><type> <name> = <value>;</code>
- *
- * <p>This means each of these must have valid Java content. If the type is a String, then the
- * value should include quotes.
- *
- * @param type the type of the field
- * @param name the name of the field
- * @param value the value of the field
- */
- public void buildConfigField(
- @NonNull String type,
- @NonNull String name,
- @NonNull String value) {
- ClassField alreadyPresent = getBuildConfigFields().get(name);
- if (alreadyPresent != null) {
- logger.info("BuildType({}): buildConfigField '{}' value is being replaced: {} -> {}",
- getName(), name, alreadyPresent.getValue(), value);
- }
- addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
- }
-
- /**
- * Adds a new generated resource.
- *
- * @param type the type of the resource
- * @param name the name of the resource
- * @param value the value of the resource
- */
- public void resValue(
- @NonNull String type,
- @NonNull String name,
- @NonNull String value) {
- ClassField alreadyPresent = getResValues().get(name);
- if (alreadyPresent != null) {
- logger.info("BuildType({}): resValue '{}' value is being replaced: {} -> {}",
- getName(), name, alreadyPresent.getValue(), value);
- }
- addResValue(AndroidBuilder.createClassField(type, name, value));
- }
-
- /**
- * Adds a new ProGuard configuration file.
- *
- * <p><code>proguardFile getDefaultProguardFile('proguard-android.txt')</code></p>
- *
- * <p>There are 2 default rules files
- * <ul>
- * <li>proguard-android.txt
- * <li>proguard-android-optimize.txt
- * </ul>
- * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code>
- * will return the full path to the files. They are identical except for enabling optimizations.
- */
- @NonNull
- public BuildType proguardFile(@NonNull Object proguardFile) {
- getProguardFiles().add(project.file(proguardFile));
- return this;
- }
-
- /**
- * Adds new ProGuard configuration files.
- */
- @NonNull
- public BuildType proguardFiles(@NonNull Object... proguardFileArray) {
- getProguardFiles().addAll(project.files(proguardFileArray).getFiles());
- return this;
- }
-
- /**
- * Sets the ProGuard configuration files.
- */
- @NonNull
- public BuildType setProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
- getProguardFiles().clear();
- for (Object proguardFile : proguardFileIterable) {
- getProguardFiles().add(project.file(proguardFile));
- }
- return this;
- }
-
- /**
- * Specifies a proguard rule file to be included in the published AAR.
- *
- * This proguard rule file will then be used by any application project that consume the AAR
- * (if proguard is enabled).
- *
- * This allows AAR to specify shrinking or obfuscation exclude rules.
- *
- * This is only valid for Library project. This is ignored in Application project.
- */
- @NonNull
- public BuildType testProguardFile(@NonNull Object proguardFile) {
- getTestProguardFiles().add(project.file(proguardFile));
- return this;
- }
-
- /**
- * Adds new ProGuard configuration files.
- */
- @NonNull
- public BuildType testProguardFiles(@NonNull Object... proguardFileArray) {
- getTestProguardFiles().addAll(project.files(proguardFileArray).getFiles());
- return this;
- }
-
- @NonNull
- public BuildType consumerProguardFiles(@NonNull Object... proguardFileArray) {
- getConsumerProguardFiles().addAll(project.files(proguardFileArray).getFiles());
- return this;
- }
-
- /**
- * Specifies a proguard rule file to be included in the published AAR.
- *
- * This proguard rule file will then be used by any application project that consume the AAR
- * (if proguard is enabled).
- *
- * This allows AAR to specify shrinking or obfuscation exclude rules.
- *
- * This is only valid for Library project. This is ignored in Application project.
- */
- @NonNull
- public BuildType setConsumerProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
- getConsumerProguardFiles().clear();
- for (Object proguardFile : proguardFileIterable) {
- getConsumerProguardFiles().add(project.file(proguardFile));
- }
- return this;
- }
-
- public void ndk(@NonNull Action<NdkOptions> action) {
- action.execute(ndkConfig);
- }
-
- /**
- * Whether the experimental Jack toolchain should be used.
- */
- @Override
- @Nullable
- public Boolean getUseJack() {
- return useJack;
- }
-
- /**
- * Whether the experimental Jack toolchain should be used.
- */
- public void setUseJack(@Nullable Boolean useJack) {
- this.useJack = useJack;
- }
-
- /**
- * Whether the experimental Jack toolchain should be used.
- */
- public void useJack(@Nullable Boolean useJack) {
- setUseJack(useJack);
- }
-
- /**
- * Whether shrinking of unused resources is enabled.
- *
- * Default is false;
- */
- @Override
- public boolean isShrinkResources() {
- return shrinkResources;
- }
-
- public void setShrinkResources(boolean shrinkResources) {
- this.shrinkResources = shrinkResources;
- }
-
- /**
- * Whether shrinking of unused resources is enabled.
- *
- * Default is false;
- */
- public void shrinkResources(boolean flag) {
- this.shrinkResources = flag;
- }
-
- public void jarJarRuleFile(@NonNull Object file) {
- getJarJarRuleFiles().add(project.file(file));
- }
-
- public void jarJarRuleFiles(@NonNull Object... files) {
- getJarJarRuleFiles().clear();
- for (Object file : files) {
- getJarJarRuleFiles().add(project.file(file));
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreBuildType.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreBuildType.java
deleted file mode 100644
index 25ff6c5..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreBuildType.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.Nullable;
-import com.android.builder.model.BuildType;
-
-/**
- * A build type with addition properties for building with Gradle plugin.
- */
-public interface CoreBuildType extends BuildType {
-
- @Nullable
- CoreNdkOptions getNdkConfig();
-
- @Nullable
- Boolean getUseJack();
-
- boolean isShrinkResources();
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptions.java
deleted file mode 100644
index fd14504..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptions.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.NonNull;
-import com.android.resources.Density;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * DSL object for configuring per-density splits options.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
- */
-public class DensitySplitOptions extends SplitOptions {
-
- private boolean strict = true;
- private boolean auto = false;
- private Set<String> compatibleScreens;
-
- @Override
- protected Set<String> getDefaultValues() {
- Density[] values = Density.values();
- Set<String> fullList = Sets.newHashSetWithExpectedSize(values.length - 2);
- for (Density value : values) {
- if (value != Density.NODPI && value != Density.ANYDPI && value.isRecommended()) {
- fullList.add(value.getResourceValue());
- }
- }
-
- return fullList;
- }
-
- @Override
- protected ImmutableSet<String> getAllowedValues() {
- ImmutableSet.Builder<String> builder = ImmutableSet.builder();
-
- for (Density value : Density.values()) {
- if (value != Density.NODPI && value != Density.ANYDPI) {
- builder.add(value.getResourceValue());
- }
- }
-
- return builder.build();
- }
-
- /**
- * TODO: Document.
- */
- public boolean isStrict() {
- return strict;
- }
-
- public void setStrict(boolean strict) {
- this.strict = strict;
- }
-
- public void setCompatibleScreens(@NonNull List<String> sizes) {
- compatibleScreens = Sets.newHashSet(sizes);
- }
-
- /**
- * Adds a new compatible screen.
- *
- * <p>See {@link #getCompatibleScreens()}.
- */
- public void compatibleScreens(@NonNull String... sizes) {
- if (compatibleScreens == null) {
- compatibleScreens = Sets.newHashSet(sizes);
- return;
- }
-
- compatibleScreens.addAll(Arrays.asList(sizes));
- }
-
- /**
- * A list of compatible screens.
- *
- * <p>This will inject a matching <code><compatible-screens><screen ...></code>
- * node in the manifest. This is optional.
- */
- @NonNull
- public Set<String> getCompatibleScreens() {
- if (compatibleScreens == null) {
- return Collections.emptySet();
- }
- return compatibleScreens;
- }
-
- /**
- * Sets whether the build system should determine the splits based on the "language-*" folders
- * in the resources. If the auto mode is set to true, the include list will be ignored.
- * @param auto true to automatically set the splits list based on the folders presence, false
- * to use the include list.
- */
- public void setAuto(boolean auto) {
- this.auto = auto;
- }
-
- /**
- * Returns whether to use the automatic discovery mechanism for supported languages (true) or
- * the manual include list (false).
- * @return true for automatic, false for manual mode.
- */
- public boolean isAuto() {
- return auto;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptions.java
deleted file mode 100644
index 8ff13db..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptions.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Optional;
-import com.android.annotations.Nullable;
-
-/**
- * DSL object for configuring dx options.
- */
-public class DexOptions implements com.android.builder.core.DexOptions {
-
- private boolean isIncrementalFlag = false;
-
- private boolean isPreDexLibrariesFlag = true;
-
- private boolean isJumboModeFlag = false;
-
- private Integer threadCount = null;
-
- private String javaMaxHeapSize;
-
- public void setIncremental(boolean isIncremental) {
- isIncrementalFlag = isIncremental;
- }
-
- /**
- * Whether to enable the incremental mode for dx. This has many limitations and may not
- * work. Use carefully.
- */
- @Override
- @Input
- public boolean getIncremental() {
- return isIncrementalFlag;
- }
-
- /**
- * Whether to pre-dex libraries. This can improve incremental builds, but clean builds may
- * be slower.
- */
- @Override
- @Input
- public boolean getPreDexLibraries() {
- return isPreDexLibrariesFlag;
- }
-
- void setPreDexLibraries(boolean flag) {
- isPreDexLibrariesFlag = flag;
- }
-
- public void setJumboMode(boolean flag) {
- isJumboModeFlag = flag;
- }
-
- /**
- * Enable jumbo mode in dx (--force-jumbo).
- */
- @Override
- @Input
- public boolean getJumboMode() {
- return isJumboModeFlag;
- }
-
- public void setJavaMaxHeapSize(String theJavaMaxHeapSize) {
- if (theJavaMaxHeapSize.matches("\\d+[kKmMgGtT]?")) {
- javaMaxHeapSize = theJavaMaxHeapSize;
- } else {
- throw new IllegalArgumentException(
- "Invalid max heap size DexOption. See `man java` for valid -Xmx arguments.");
- }
- }
-
- /**
- * Sets the -JXmx* value when calling dx. Format should follow the 1024M pattern.
- */
- @Override
- @Optional @Input
- @Nullable
- public String getJavaMaxHeapSize() {
- return javaMaxHeapSize;
- }
-
- public void setThreadCount(int threadCount) {
- this.threadCount = threadCount;
- }
-
- /**
- * Sets the number of threads to use when running dx
- */
- @Override
- @Nullable
- public Integer getThreadCount() {
- return threadCount;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptions.java
deleted file mode 100644
index eff135e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptions.java
+++ /dev/null
@@ -1,798 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
-import static com.android.tools.lint.detector.api.Severity.ERROR;
-import static com.android.tools.lint.detector.api.Severity.FATAL;
-import static com.android.tools.lint.detector.api.Severity.IGNORE;
-import static com.android.tools.lint.detector.api.Severity.INFORMATIONAL;
-import static com.android.tools.lint.detector.api.Severity.WARNING;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.HtmlReporter;
-import com.android.tools.lint.LintCliClient;
-import com.android.tools.lint.LintCliFlags;
-import com.android.tools.lint.TextReporter;
-import com.android.tools.lint.XmlReporter;
-import com.android.tools.lint.checks.BuiltinIssueRegistry;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.io.Writer;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * DSL object for configuring lint options.
- */
-public class LintOptions implements com.android.builder.model.LintOptions, Serializable {
- public static final String STDOUT = "stdout";
- public static final String STDERR = "stderr";
- private static final long serialVersionUID = 1L;
-
- @NonNull
- private Set<String> disable = Sets.newHashSet();
- @NonNull
- private Set<String> enable = Sets.newHashSet();
- @Nullable
- private Set<String> check = Sets.newHashSet();
- private boolean abortOnError = true;
- private boolean absolutePaths = true;
- private boolean noLines;
- private boolean quiet;
- private boolean checkAllWarnings;
- private boolean ignoreWarnings;
- private boolean warningsAsErrors;
- private boolean showAll;
- private boolean checkReleaseBuilds = true;
- private boolean explainIssues = true;
- @Nullable
- private File lintConfig;
- private boolean textReport;
- @Nullable
- private File textOutput;
- private boolean htmlReport = true;
- @Nullable
- private File htmlOutput;
- private boolean xmlReport = true;
- @Nullable
- private File xmlOutput;
-
- private Map<String,Severity> severities = Maps.newHashMap();
-
- public LintOptions() {
- }
-
- public LintOptions(
- @NonNull Set<String> disable,
- @NonNull Set<String> enable,
- @Nullable Set<String> check,
- @Nullable File lintConfig,
- boolean textReport,
- @Nullable File textOutput,
- boolean htmlReport,
- @Nullable File htmlOutput,
- boolean xmlReport,
- @Nullable File xmlOutput,
- boolean abortOnError,
- boolean absolutePaths,
- boolean noLines,
- boolean quiet,
- boolean checkAllWarnings,
- boolean ignoreWarnings,
- boolean warningsAsErrors,
- boolean showAll,
- boolean explainIssues,
- boolean checkReleaseBuilds,
- @Nullable Map<String,Integer> severityOverrides) {
- this.disable = disable;
- this.enable = enable;
- this.check = check;
- this.lintConfig = lintConfig;
- this.textReport = textReport;
- this.textOutput = textOutput;
- this.htmlReport = htmlReport;
- this.htmlOutput = htmlOutput;
- this.xmlReport = xmlReport;
- this.xmlOutput = xmlOutput;
- this.abortOnError = abortOnError;
- this.absolutePaths = absolutePaths;
- this.noLines = noLines;
- this.quiet = quiet;
- this.checkAllWarnings = checkAllWarnings;
- this.ignoreWarnings = ignoreWarnings;
- this.warningsAsErrors = warningsAsErrors;
- this.showAll = showAll;
- this.explainIssues = explainIssues;
- this.checkReleaseBuilds = checkReleaseBuilds;
-
- if (severityOverrides != null) {
- for (Map.Entry<String,Integer> entry : severityOverrides.entrySet()) {
- severities.put(entry.getKey(), convert(entry.getValue()));
- }
- }
- }
-
- @NonNull
- public static com.android.builder.model.LintOptions create(@NonNull com.android.builder.model.LintOptions source) {
- return new LintOptions(
- source.getDisable(),
- source.getEnable(),
- source.getCheck(),
- source.getLintConfig(),
- source.getTextReport(),
- source.getTextOutput(),
- source.getHtmlReport(),
- source.getHtmlOutput(),
- source.getXmlReport(),
- source.getXmlOutput(),
- source.isAbortOnError(),
- source.isAbsolutePaths(),
- source.isNoLines(),
- source.isQuiet(),
- source.isCheckAllWarnings(),
- source.isIgnoreWarnings(),
- source.isWarningsAsErrors(),
- source.isShowAll(),
- source.isExplainIssues(),
- source.isCheckReleaseBuilds(),
- source.getSeverityOverrides()
- );
- }
-
- /**
- * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
- */
- @Override
- @NonNull
- @Input
- public Set<String> getDisable() {
- return disable;
- }
-
- /**
- * Sets the set of issue id's to suppress. Callers are allowed to modify this collection.
- * Note that these ids add to rather than replace the given set of ids.
- */
- public void setDisable(@Nullable Set<String> ids) {
- disable.addAll(ids);
- }
-
- /**
- * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
- * To enable a given issue, add the issue ID to the returned set.
- */
- @Override
- @NonNull
- @Input
- public Set<String> getEnable() {
- return enable;
- }
-
- /**
- * Sets the set of issue id's to enable. Callers are allowed to modify this collection.
- * Note that these ids add to rather than replace the given set of ids.
- */
- public void setEnable(@Nullable Set<String> ids) {
- enable.addAll(ids);
- }
-
- /**
- * Returns the exact set of issues to check, or null to run the issues that are enabled
- * by default plus any issues enabled via {@link #getEnable} and without issues disabled
- * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
- */
- @Override
- @Nullable
- @Optional
- @Input
- public Set<String> getCheck() {
- return check;
- }
-
- /**
- * Sets the <b>exact</b> set of issues to check.
- * @param ids the set of issue id's to check
- */
- public void setCheck(@NonNull Set<String> ids) {
- check.addAll(ids);
- }
-
- /** Whether lint should set the exit code of the process if errors are found */
- @Override
- @Input
- public boolean isAbortOnError() {
- return abortOnError;
- }
-
- /** Sets whether lint should set the exit code of the process if errors are found */
- public void setAbortOnError(boolean abortOnError) {
- this.abortOnError = abortOnError;
- }
-
- /**
- * Whether lint should display full paths in the error output. By default the paths
- * are relative to the path lint was invoked from.
- */
- @Override
- @Input
- public boolean isAbsolutePaths() {
- return absolutePaths;
- }
-
- /**
- * Sets whether lint should display full paths in the error output. By default the paths
- * are relative to the path lint was invoked from.
- */
- public void setAbsolutePaths(boolean absolutePaths) {
- this.absolutePaths = absolutePaths;
- }
-
- /**
- * Whether lint should include the source lines in the output where errors occurred
- * (true by default)
- */
- @Override
- @Input
- public boolean isNoLines() {
- return this.noLines;
- }
-
- /**
- * Sets whether lint should include the source lines in the output where errors occurred
- * (true by default)
- */
- public void setNoLines(boolean noLines) {
- this.noLines = noLines;
- }
-
- /**
- * Returns whether lint should be quiet (for example, not write informational messages
- * such as paths to report files written)
- */
- @Override
- @Input
- public boolean isQuiet() {
- return quiet;
- }
-
- /**
- * Sets whether lint should be quiet (for example, not write informational messages
- * such as paths to report files written)
- */
- public void setQuiet(boolean quiet) {
- this.quiet = quiet;
- }
-
- /** Returns whether lint should check all warnings, including those off by default */
- @Override
- @Input
- public boolean isCheckAllWarnings() {
- return checkAllWarnings;
- }
-
- /** Sets whether lint should check all warnings, including those off by default */
- public void setCheckAllWarnings(boolean warnAll) {
- this.checkAllWarnings = warnAll;
- }
-
- /** Returns whether lint will only check for errors (ignoring warnings) */
- @Override
- @Input
- public boolean isIgnoreWarnings() {
- return ignoreWarnings;
- }
-
- /** Sets whether lint will only check for errors (ignoring warnings) */
- public void setIgnoreWarnings(boolean noWarnings) {
- this.ignoreWarnings = noWarnings;
- }
-
- /** Returns whether lint should treat all warnings as errors */
- @Override
- @Input
- public boolean isWarningsAsErrors() {
- return warningsAsErrors;
- }
-
- /** Sets whether lint should treat all warnings as errors */
- public void setWarningsAsErrors(boolean allErrors) {
- this.warningsAsErrors = allErrors;
- }
-
- /** Returns whether lint should include explanations for issue errors. (Note that
- * HTML and XML reports intentionally do this unconditionally, ignoring this setting.) */
- @Override
- @Input
- public boolean isExplainIssues() {
- return explainIssues;
- }
-
- public void setExplainIssues(boolean explainIssues) {
- this.explainIssues = explainIssues;
- }
-
- /**
- * Returns whether lint should include all output (e.g. include all alternate
- * locations, not truncating long messages, etc.)
- */
- @Override
- @Input
- public boolean isShowAll() {
- return showAll;
- }
-
- /**
- * Sets whether lint should include all output (e.g. include all alternate
- * locations, not truncating long messages, etc.)
- */
- public void setShowAll(boolean showAll) {
- this.showAll = showAll;
- }
-
- /**
- * Returns whether lint should check for fatal errors during release builds. Default is true.
- * If issues with severity "fatal" are found, the release build is aborted.
- */
- @Override
- @Input
- public boolean isCheckReleaseBuilds() {
- return checkReleaseBuilds;
- }
-
- public void setCheckReleaseBuilds(boolean checkReleaseBuilds) {
- this.checkReleaseBuilds = checkReleaseBuilds;
- }
-
- /**
- * Returns the default configuration file to use as a fallback
- */
- @Override
- @Optional @InputFile
- public File getLintConfig() {
- return lintConfig;
- }
-
- /** Whether we should write an text report. Default false. The location can be
- * controlled by {@link #getTextOutput()}. */
- @Override
- @Input
- public boolean getTextReport() {
- return textReport;
- }
-
- public void setTextReport(boolean textReport) {
- this.textReport = textReport;
- }
-
- public void setHtmlReport(boolean htmlReport) {
- this.htmlReport = htmlReport;
- }
-
- public void setHtmlOutput(@NonNull File htmlOutput) {
- this.htmlOutput = htmlOutput;
- }
-
- public void setXmlReport(boolean xmlReport) {
- this.xmlReport = xmlReport;
- }
-
- public void setXmlOutput(@NonNull File xmlOutput) {
- this.xmlOutput = xmlOutput;
- }
-
- /**
- * The optional path to where a text report should be written. The special value
- * "stdout" can be used to point to standard output.
- */
- @Override
- @Nullable
- @Optional
- @Input
- public File getTextOutput() {
- return textOutput;
- }
-
- /** Whether we should write an HTML report. Default true. The location can be
- * controlled by {@link #getHtmlOutput()}. */
- @Override
- @Input
- public boolean getHtmlReport() {
- return htmlReport;
- }
-
- /** The optional path to where an HTML report should be written */
- @Override
- @Nullable
- @Optional
- @OutputFile
- public File getHtmlOutput() {
- return htmlOutput;
- }
-
- /** Whether we should write an XML report. Default true. The location can be
- * controlled by {@link #getXmlOutput()}. */
- @Override
- @Input
- public boolean getXmlReport() {
- return xmlReport;
- }
-
- /** The optional path to where an XML report should be written */
- @Override
- @Nullable
- @Optional
- @OutputFile
- public File getXmlOutput() {
- return xmlOutput;
- }
-
- /**
- * Sets the default config file to use as a fallback. This corresponds to a {@code lint.xml}
- * file with severities etc to use when a project does not have more specific information.
- */
- public void setLintConfig(@NonNull File lintConfig) {
- this.lintConfig = lintConfig;
- }
-
- public void syncTo(
- @NonNull LintCliClient client,
- @NonNull LintCliFlags flags,
- @Nullable String variantName,
- @Nullable org.gradle.api.Project project,
- boolean report) {
- if (disable != null) {
- flags.getSuppressedIds().addAll(disable);
- }
- if (enable != null) {
- flags.getEnabledIds().addAll(enable);
- }
- if (check != null && !check.isEmpty()) {
- flags.setExactCheckedIds(check);
- }
- flags.setSetExitCode(this.abortOnError);
- flags.setFullPath(absolutePaths);
- flags.setShowSourceLines(!noLines);
- flags.setQuiet(quiet);
- flags.setCheckAllWarnings(checkAllWarnings);
- flags.setIgnoreWarnings(ignoreWarnings);
- flags.setWarningsAsErrors(warningsAsErrors);
- flags.setShowEverything(showAll);
- flags.setDefaultConfiguration(lintConfig);
- flags.setSeverityOverrides(severities);
- flags.setExplainIssues(explainIssues);
-
- if (report || flags.isFatalOnly() && this.abortOnError) {
- if (textReport || flags.isFatalOnly()) {
- File output = textOutput;
- if (output == null) {
- output = new File(flags.isFatalOnly() ? STDERR: STDOUT);
- } else if (!output.isAbsolute() && !isStdOut(output) && !isStdErr(output)) {
- output = project.file(output.getPath());
- }
- output = validateOutputFile(output);
-
- Writer writer;
- File file = null;
- boolean closeWriter;
- if (isStdOut(output)) {
- writer = new PrintWriter(System.out, true);
- closeWriter = false;
- } else if (isStdErr(output)) {
- writer = new PrintWriter(System.err, true);
- closeWriter = false;
- } else {
- file = output;
- try {
- writer = new BufferedWriter(new FileWriter(output));
- } catch (IOException e) {
- throw new org.gradle.api.GradleException("Text invalid argument.", e);
- }
- closeWriter = true;
- }
- flags.getReporters().add(new TextReporter(client, flags, file, writer,
- closeWriter));
- }
- if (htmlReport) {
- File output = htmlOutput;
- if (output == null || flags.isFatalOnly()) {
- output = createOutputPath(project, variantName, ".html", flags.isFatalOnly());
- } else if (!output.isAbsolute()) {
- output = project.file(output.getPath());
- }
- output = validateOutputFile(output);
- try {
- flags.getReporters().add(new HtmlReporter(client, output));
- } catch (IOException e) {
- throw new GradleException("HTML invalid argument.", e);
- }
- }
- if (xmlReport) {
- File output = xmlOutput;
- if (output == null || flags.isFatalOnly()) {
- output = createOutputPath(project, variantName, DOT_XML, flags.isFatalOnly());
- } else if (!output.isAbsolute()) {
- output = project.file(output.getPath());
- }
- output = validateOutputFile(output);
- try {
- flags.getReporters().add(new XmlReporter(client, output));
- } catch (IOException e) {
- throw new org.gradle.api.GradleException("XML invalid argument.", e);
- }
- }
- }
- }
-
- private static boolean isStdOut(@NonNull File output) {
- return STDOUT.equals(output.getPath());
- }
-
- private static boolean isStdErr(@NonNull File output) {
- return STDERR.equals(output.getPath());
- }
-
- @NonNull
- private static File validateOutputFile(@NonNull File output) {
- if (isStdOut(output) || isStdErr(output)) {
- return output;
- }
-
- File parent = output.getParentFile();
- if (!parent.exists()) {
- parent.mkdirs();
- }
-
- output = output.getAbsoluteFile();
- if (output.exists()) {
- boolean delete = output.delete();
- if (!delete) {
- throw new org.gradle.api.GradleException("Could not delete old " + output);
- }
- }
- if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
- throw new org.gradle.api.GradleException("Cannot write output file " + output);
- }
-
- return output;
- }
-
- private static File createOutputPath(
- @NonNull org.gradle.api.Project project,
- @NonNull String variantName,
- @NonNull String extension,
- boolean fatalOnly) {
- StringBuilder base = new StringBuilder();
- base.append(FD_OUTPUTS);
- base.append(File.separator);
- base.append("lint-results");
- if (variantName != null) {
- base.append("-");
- base.append(variantName);
- }
- if (fatalOnly) {
- base.append("-fatal");
- }
- base.append(extension);
- return new File(project.getBuildDir(), base.toString());
- }
-
- /**
- * An optional map of severity overrides. The map maps from issue id's to the corresponding
- * severity to use, which must be "fatal", "error", "warning", or "ignore".
- *
- * @return a map of severity overrides, or null. The severities are one of the constants
- * {@link #SEVERITY_FATAL}, {@link #SEVERITY_ERROR}, {@link #SEVERITY_WARNING},
- * {@link #SEVERITY_INFORMATIONAL}, {@link #SEVERITY_IGNORE}
- */
- @Override
- @Nullable
- public Map<String, Integer> getSeverityOverrides() {
- if (severities == null || severities.isEmpty()) {
- return null;
- }
-
- Map<String, Integer> map =
- Maps.newHashMapWithExpectedSize(severities.size());
- for (Map.Entry<String,Severity> entry : severities.entrySet()) {
- map.put(entry.getKey(), convert(entry.getValue()));
- }
-
- return map;
- }
-
- // -- DSL Methods.
-
- /**
- * Adds the id to the set of issues to check.
- */
- public void check(String id) {
- check.add(id);
- }
-
- /**
- * Adds the ids to the set of issues to check.
- */
- public void check(String... ids) {
- for (String id : ids) {
- check(id);
- }
- }
-
- /**
- * Adds the id to the set of issues to enable.
- */
- public void enable(String id) {
- enable.add(id);
- Issue issue = new BuiltinIssueRegistry().getIssue(id);
- severities.put(id, issue != null ? issue.getDefaultSeverity() : WARNING);
- }
-
- /**
- * Adds the ids to the set of issues to enable.
- */
- public void enable(String... ids) {
- for (String id : ids) {
- enable(id);
- }
- }
-
- /**
- * Adds the id to the set of issues to enable.
- */
- public void disable(String id) {
- disable.add(id);
- severities.put(id, IGNORE);
- }
-
- /**
- * Adds the ids to the set of issues to enable.
- */
- public void disable(String... ids) {
- for (String id : ids) {
- disable(id);
- }
- }
-
- // For textOutput 'stdout' or 'stderr' (normally a file)
- public void textOutput(String textOutput) {
- this.textOutput = new File(textOutput);
- }
-
- // For textOutput file()
- public void textOutput(File textOutput) {
- this.textOutput = textOutput;
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void fatal(String id) {
- severities.put(id, FATAL);
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void fatal(String... ids) {
- for (String id : ids) {
- fatal(id);
- }
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void error(String id) {
- severities.put(id, ERROR);
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void error(String... ids) {
- for (String id : ids) {
- error(id);
- }
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void warning(String id) {
- severities.put(id, WARNING);
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void warning(String... ids) {
- for (String id : ids) {
- warning(id);
- }
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void ignore(String id) {
- severities.put(id, IGNORE);
- }
-
- /**
- * Adds a severity override for the given issues.
- */
- public void ignore(String... ids) {
- for (String id : ids) {
- ignore(id);
- }
- }
-
- // Without these qualifiers, Groovy compilation will fail with "Apparent variable
- // 'SEVERITY_FATAL' was found in a static scope but doesn't refer to a local variable,
- // static field or class"
- //@SuppressWarnings("UnnecessaryQualifiedReference")
- private static int convert(Severity s) {
- switch (s) {
- case FATAL:
- return com.android.builder.model.LintOptions.SEVERITY_FATAL;
- case ERROR:
- return com.android.builder.model.LintOptions.SEVERITY_ERROR;
- case WARNING:
- return com.android.builder.model.LintOptions.SEVERITY_WARNING;
- case INFORMATIONAL:
- return com.android.builder.model.LintOptions.SEVERITY_INFORMATIONAL;
- case IGNORE:
- default:
- return com.android.builder.model.LintOptions.SEVERITY_IGNORE;
- }
- }
-
- //@SuppressWarnings("UnnecessaryQualifiedReference")
- private static Severity convert(int s) {
- switch (s) {
- case com.android.builder.model.LintOptions.SEVERITY_FATAL:
- return FATAL;
- case com.android.builder.model.LintOptions.SEVERITY_ERROR:
- return ERROR;
- case com.android.builder.model.LintOptions.SEVERITY_WARNING:
- return WARNING;
- case com.android.builder.model.LintOptions.SEVERITY_INFORMATIONAL:
- return INFORMATIONAL;
- case com.android.builder.model.LintOptions.SEVERITY_IGNORE:
- default:
- return IGNORE;
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptions.java
deleted file mode 100644
index 2b86f65..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptions.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.NonNull;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.tasks.Input;
-
-import java.util.Set;
-
-/**
- * DSL objects for configuring APK packaging options.
- */
-public class PackagingOptions implements com.android.builder.model.PackagingOptions {
-
- private Set<String> excludes = Sets.newHashSet("LICENSE.txt", "LICENSE");
- private Set<String> pickFirsts = Sets.newHashSet();
- private Set<String> merges = Sets.newHashSet();
-
- /**
- * Returns the list of excluded paths.
- *
- * <p>Contains "LICENSE.txt" and "LICENSE" by default, since they often cause
- * packaging conflicts.
- */
- @Override
- @NonNull
- @Input
- public Set<String> getExcludes() {
- return Sets.newHashSet(excludes);
- }
-
- public void setExcludes(Set<String> excludes) {
- this.excludes = Sets.newHashSet(excludes);
- }
-
- /**
- * Adds an excluded paths.
- * @param path the path, as packaged in the APK
- */
- public void exclude(String path) {
- excludes.add(path);
- }
-
- /**
- * Returns the list of paths where the first occurrence is packaged in the APK.
- */
- @Override
- @NonNull
- @Input
- public Set<String> getPickFirsts() {
- return Sets.newHashSet(pickFirsts);
- }
-
- /**
- * Adds a firstPick path. First pick paths do get packaged in the APK, but only the first
- * occurrence gets packaged.
- * @param path the path to add.
- */
- public void pickFirst(String path) {
- pickFirsts.add(path);
- }
-
- public void setPickFirsts(Set<String> pickFirsts) {
- this.pickFirsts = Sets.newHashSet(pickFirsts);
- }
-
- /**
- * Returns the list of paths where all occurrences are concatenated and packaged in the APK.
- */
- @Override
- @NonNull
- @Input
- public Set<String> getMerges() {
- return Sets.newHashSet(merges);
- }
-
- public void setMerges(Set<String> merges) {
- this.merges = Sets.newHashSet(merges);
- }
-
- /**
- * Adds a merge path.
- * @param path the path, as packaged in the APK
- */
- public void merge(String path) {
- merges.add(path);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PreprocessingOptions.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PreprocessingOptions.java
deleted file mode 100644
index 72a9211..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PreprocessingOptions.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-import com.android.resources.Density;
-import com.google.common.collect.Sets;
-
-import java.util.EnumSet;
-import java.util.Set;
-
-/**
- * Resource preprocessing options.
- */
-public class PreprocessingOptions {
- private EnumSet<Density> densities;
- private boolean enabled;
-
- public PreprocessingOptions() {
- this.enabled = true;
-
- // TODO: What are the right default values?
- this.densities = EnumSet.of(
- Density.MEDIUM,
- Density.HIGH,
- Density.XHIGH,
- Density.XXHIGH);
- }
-
- /**
- * Whether to enable resources pre-processing. This is disabled by default.
- *
- * <p>If resources pre-processing is enabled, the build process will create two copies of the
- * resource tree: one with a merged view of resources for a given variant and one with the
- * preprocessed files, ready to be packaged. This may slow down your clean builds.
- */
- public boolean getEnabled() {
- return enabled;
- }
-
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
-
- /**
- * Set of screen densities for which PNG files should be generated based on vector drawable
- * resource files.
- *
- * <p>Default to {@code ["mdpi", "hdpi", "xhdpi", "xxhdpi"]}.
- */
- public Set<String> getDensities() {
- Set<String> result = Sets.newHashSet();
- for (Density density : densities) {
- result.add(density.getResourceValue());
- }
- return result;
- }
-
- public void setDensities(Set<String> densities) {
- EnumSet<Density> newValue = EnumSet.noneOf(Density.class);
- for (String density : densities) {
- Density typedValue = Density.getEnum(density);
- checkArgument(typedValue != null, "Unrecognized density %s", density);
- newValue.add(typedValue);
- }
- this.densities = newValue;
- }
-
- /**
- * Returns the densities to generate as the underlying enum values.
- *
- * <p>Not meant to be used in build scripts.
- */
- public Set<Density> getTypedDensities() {
- return densities;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavor.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavor.java
deleted file mode 100644
index 5733ced..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavor.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.LoggingUtil;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.core.DefaultApiVersion;
-import com.android.builder.core.DefaultProductFlavor;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.ClassField;
-import com.google.common.base.Strings;
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.logging.Logger;
-import org.gradle.internal.reflect.Instantiator;
-
-import static com.android.build.gradle.tasks.NdkCompile.USE_DEPRECATED_NDK;
-
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * DSL object used to configure product flavors.
- */
-public class ProductFlavor extends DefaultProductFlavor implements CoreProductFlavor {
-
- @NonNull
- protected final Project project;
-
- @NonNull
- protected final Logger logger;
-
- @NonNull
- private final NdkOptions ndkConfig;
-
- @Nullable
- private Boolean useJack;
-
- public ProductFlavor(@NonNull String name,
- @NonNull Project project,
- @NonNull Instantiator instantiator,
- @NonNull Logger logger) {
- super(name);
- this.project = project;
- this.logger = logger;
- ndkConfig = instantiator.newInstance(NdkOptions.class);
- }
-
- @Override
- @Nullable
- public CoreNdkOptions getNdkConfig() {
- return ndkConfig;
- }
-
- public void setMinSdkVersion(int minSdkVersion) {
- setMinSdkVersion(new DefaultApiVersion(minSdkVersion));
- }
-
- /**
- * Sets minimum SDK version.
- *
- * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
- * uses-sdk element documentation</a>.
- */
- public void minSdkVersion(int minSdkVersion) {
- setMinSdkVersion(minSdkVersion);
- }
-
- public void setMinSdkVersion(@Nullable String minSdkVersion) {
- setMinSdkVersion(getApiVersion(minSdkVersion));
- }
-
- /**
- * Sets minimum SDK version.
- *
- * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
- * uses-sdk element documentation</a>.
- */
- public void minSdkVersion(@Nullable String minSdkVersion) {
- setMinSdkVersion(minSdkVersion);
- }
-
- @NonNull
- public com.android.builder.model.ProductFlavor setTargetSdkVersion(int targetSdkVersion) {
- setTargetSdkVersion(new DefaultApiVersion(targetSdkVersion));
- return this;
- }
-
- /**
- * Sets the target SDK version to the given value.
- *
- * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
- * uses-sdk element documentation</a>.
- */
- public void targetSdkVersion(int targetSdkVersion) {
- setTargetSdkVersion(targetSdkVersion);
- }
-
- public void setTargetSdkVersion(@Nullable String targetSdkVersion) {
- setTargetSdkVersion(getApiVersion(targetSdkVersion));
- }
-
- /**
- * Sets the target SDK version to the given value.
- *
- * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
- * uses-sdk element documentation</a>.
- */
- public void targetSdkVersion(@Nullable String targetSdkVersion) {
- setTargetSdkVersion(targetSdkVersion);
- }
-
- /**
- * Sets the maximum SDK version to the given value.
- *
- * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
- * uses-sdk element documentation</a>.
- */
- public void maxSdkVersion(int targetSdkVersion) {
- setMaxSdkVersion(targetSdkVersion);
- }
-
- @Nullable
- private static ApiVersion getApiVersion(@Nullable String value) {
- if (!Strings.isNullOrEmpty(value)) {
- if (Character.isDigit(value.charAt(0))) {
- try {
- int apiLevel = Integer.valueOf(value);
- return new DefaultApiVersion(apiLevel);
- } catch (NumberFormatException e) {
- throw new RuntimeException("'" + value + "' is not a valid API level. ", e);
- }
- }
-
- return new DefaultApiVersion(value);
- }
-
- return null;
- }
-
- /**
- * Adds a custom argument to the test instrumentation runner, e.g:
- *
- * <p><pre>testInstrumentationRunnerArgument "size", "medium"</pre>
- *
- * <p>Test runner arguments can also be specified from the command line:
- *
- * <p><pre>
- * INSTRUMENTATION_TEST_RUNNER_ARGS=size=medium,foo=bar ./gradlew connectedAndroidTest
- * ./gradlew connectedAndroidTest -Pcom.android.tools.instrumentationTestRunnerArgs=size=medium,foo=bar
- * </pre>
- */
- public void testInstrumentationRunnerArgument(@NonNull String key, @NonNull String value) {
- getTestInstrumentationRunnerArguments().put(key, value);
- }
-
- /**
- * Adds custom arguments to the test instrumentation runner, e.g:
- *
- * <p><pre>testInstrumentationRunnerArguments(size: "medium", foo: "bar")</pre>
- *
- * <p>Test runner arguments can also be specified from the command line:
- *
- * <p><pre>
- * INSTRUMENTATION_TEST_RUNNER_ARGS=size=medium,foo=bar ./gradlew connectedAndroidTest
- * ./gradlew connectedAndroidTest -Pcom.android.tools.instrumentationTestRunnerArgs=size=medium,foo=bar
- * </pre>
- */
- public void testInstrumentationRunnerArguments(@NonNull Map<String, String> args) {
- getTestInstrumentationRunnerArguments().putAll(args);
- }
-
- /**
- * Signing config used by this product flavor.
- */
- @Override
- @Nullable
- public SigningConfig getSigningConfig() {
- return (SigningConfig) super.getSigningConfig();
- }
-
-// -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
-
- /**
- * Adds a new field to the generated BuildConfig class.
- *
- * The field is generated as:
- *
- * <type> <name> = <value>;
- *
- * This means each of these must have valid Java content. If the type is a String, then the
- * value should include quotes.
- *
- * @param type the type of the field
- * @param name the name of the field
- * @param value the value of the field
- */
- public void buildConfigField(
- @NonNull String type,
- @NonNull String name,
- @NonNull String value) {
- ClassField alreadyPresent = getBuildConfigFields().get(name);
- if (alreadyPresent != null) {
- String flavorName = getName();
- if (BuilderConstants.MAIN.equals(flavorName)) {
- logger.info(
- "DefaultConfig: buildConfigField '{}' value is being replaced: {} -> {}",
- name, alreadyPresent.getValue(), value);
- } else {
- logger.info(
- "ProductFlavor({}): buildConfigField '{}' "
- + "value is being replaced: {} -> {}",
- flavorName, name, alreadyPresent.getValue(), value);
- }
- }
- addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
- }
-
- /**
- * Adds a new generated resource.
- *
- * <p>This is equivalent to specifying a resource in res/values.
- *
- * <p>See <a href="http://developer.android.com/guide/topics/resources/available-resources.html">Resource Types</a>.
- *
- * @param type the type of the resource
- * @param name the name of the resource
- * @param value the value of the resource
- */
- public void resValue(
- @NonNull String type,
- @NonNull String name,
- @NonNull String value) {
- ClassField alreadyPresent = getResValues().get(name);
- if (alreadyPresent != null) {
- String flavorName = getName();
- if (BuilderConstants.MAIN.equals(flavorName)) {
- logger.info(
- "DefaultConfig: resValue '{}' value is being replaced: {} -> {}",
- name, alreadyPresent.getValue(), value);
- } else {
- logger.info(
- "ProductFlavor({}): resValue '{}' value is being replaced: {} -> {}",
- flavorName, name, alreadyPresent.getValue(), value);
- }
- }
- addResValue(AndroidBuilder.createClassField(type, name, value));
- }
-
- /**
- * Adds a new ProGuard configuration file.
- *
- * <p><code>proguardFile getDefaultProguardFile('proguard-android.txt')</code></p>
- *
- * <p>There are 2 default rules files
- * <ul>
- * <li>proguard-android.txt
- * <li>proguard-android-optimize.txt
- * </ul>
- * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
- * full path to the files. They are identical except for enabling optimizations.
- */
- public void proguardFile(@NonNull Object proguardFile) {
- getProguardFiles().add(project.file(proguardFile));
- }
-
- /**
- * Adds new ProGuard configuration files.
- *
- * <p>There are 2 default rules files
- * <ul>
- * <li>proguard-android.txt
- * <li>proguard-android-optimize.txt
- * </ul>
- * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
- * full path to the files. They are identical except for enabling optimizations.
- */
- public void proguardFiles(@NonNull Object... proguardFileArray) {
- getProguardFiles().addAll(project.files(proguardFileArray).getFiles());
- }
-
- /**
- * Sets the ProGuard configuration files.
- *
- * <p>There are 2 default rules files
- * <ul>
- * <li>proguard-android.txt
- * <li>proguard-android-optimize.txt
- * </ul>
- * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
- * full path to the files. They are identical except for enabling optimizations.
- */
- public void setProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
- getProguardFiles().clear();
- for (Object proguardFile : proguardFileIterable) {
- getProguardFiles().add(project.file(proguardFile));
- }
- }
-
- /**
- * Specifies a proguard rule file to be included in the published AAR.
- *
- * <p>This proguard rule file will then be used by any application project that consume the AAR
- * (if proguard is enabled).
- *
- * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
- *
- * <p>This is only valid for Library project. This is ignored in Application project.
- */
- public void testProguardFile(@NonNull Object proguardFile) {
- getTestProguardFiles().add(project.file(proguardFile));
- }
-
- /**
- * Adds new ProGuard configuration files.
- */
- public void testProguardFiles(Object... proguardFileArray) {
- getTestProguardFiles().addAll(project.files(proguardFileArray).getFiles());
- }
-
- public void consumerProguardFiles(Object... proguardFileArray) {
- getConsumerProguardFiles().addAll(project.files(proguardFileArray).getFiles());
- }
-
- /**
- * Specifies a proguard rule file to be included in the published AAR.
- *
- * <p>This proguard rule file will then be used by any application project that consume the AAR
- * (if proguard is enabled).
- *
- * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
- *
- * <p>This is only valid for Library project. This is ignored in Application project.
- */
- public void setConsumerProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
- getConsumerProguardFiles().clear();
- for (Object proguardFile : proguardFileIterable) {
- getConsumerProguardFiles().add(project.file(proguardFile));
- }
- }
-
- public void ndk(Action<NdkOptions> action) {
- action.execute(ndkConfig);
- if (!project.hasProperty(USE_DEPRECATED_NDK)) {
- throw new RuntimeException(
- "Error: NDK integration is deprecated in the current plugin. Consider trying " +
- "the new experimental plugin. For details, see " +
- "http://tools.android.com/tech-docs/new-build-system/gradle-experimental. " +
- "Set \"" + USE_DEPRECATED_NDK + "=true\" in gradle.properties to " +
- "continue using the current NDK integration.");
- }
- }
-
- /**
- * Adds a resource configuration filter.
- *
- * <p>If a qualifier value is passed, then all other resources using a qualifier of the same type
- * but of different value will be ignored from the final packaging of the APK.
- *
- * <p>For instance, specifying 'hdpi', will ignore all resources using mdpi, xhdpi, etc...
- */
- public void resConfig(@NonNull String config) {
- addResourceConfiguration(config);
- }
-
- /**
- * Adds several resource configuration filters.
- *
- * <p>If a qualifier value is passed, then all other resources using a qualifier of the same type
- * but of different value will be ignored from the final packaging of the APK.
- *
- * <p>For instance, specifying 'hdpi', will ignore all resources using mdpi, xhdpi, etc...
- */
- public void resConfigs(@NonNull String... config) {
- addResourceConfigurations(config);
- }
-
- /**
- * Adds several resource configuration filters.
- *
- * <p>If a qualifier value is passed, then all other resources using a qualifier of the same type
- * but of different value will be ignored from the final packaging of the APK.
- *
- * <p>For instance, specifying 'hdpi', will ignore all resources using mdpi, xhdpi, etc...
- */
- public void resConfigs(@NonNull Collection<String> config) {
- addResourceConfigurations(config);
- }
-
- /**
- * Whether the experimental Jack toolchain should be used.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/jackandjill">Jack and Jill</a>
- */
- @Override
- @Nullable
- public Boolean getUseJack() {
- return useJack;
- }
-
- /**
- * Whether the experimental Jack toolchain should be used.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/jackandjill">Jack and Jill</a>
- */
- public void setUseJack(Boolean useJack) {
- this.useJack = useJack;
- }
-
- /**
- * Whether the experimental Jack toolchain should be used.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/jackandjill">Jack and Jill</a>
- */
- public void useJack(Boolean useJack) {
- setUseJack(useJack);
- }
-
- @Deprecated
- public void setFlavorDimension(String dimension) {
- LoggingUtil.displayDeprecationWarning(logger, project,
- "'flavorDimension' will be removed by Android Gradle Plugin 2.0, " +
- "it has been replaced by 'dimension'.");
- setDimension(dimension);
- }
-
- /**
- * Name of the dimension this product flavor belongs to. Has been replaced by
- * <code>dimension</code>
- */
- @Deprecated
- public String getFlavorDimension() {
- LoggingUtil.displayDeprecationWarning(logger, project,
- "'flavorDimension' will be removed by Android Gradle Plugin 2.0, " +
- "it has been replaced by 'dimension'.");
- return getDimension();
- }
-
- public void jarJarRuleFile(Object file) {
- getJarJarRuleFiles().add(project.file(file));
- }
-
- public void jarJarRuleFiles(Object ...files) {
- getJarJarRuleFiles().clear();
- for (Object file : files) {
- getJarJarRuleFiles().add(project.file(file));
- }
- }
-}
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/Splits.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/Splits.java
deleted file mode 100644
index cd86795..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/Splits.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.NonNull;
-
-import org.gradle.api.Action;
-import org.gradle.internal.reflect.Instantiator;
-
-import java.util.Set;
-
-/**
- * DSL object for configuring APK Splits options.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
- */
-public class Splits {
-
- private final DensitySplitOptions density;
- private final AbiSplitOptions abi;
- private final LanguageSplitOptions language;
-
- public Splits(@NonNull Instantiator instantiator) {
- density = instantiator.newInstance(DensitySplitOptions.class);
- abi = instantiator.newInstance(AbiSplitOptions.class);
- language = instantiator.newInstance(LanguageSplitOptions.class);
- }
-
- /**
- * Density settings.
- */
- public DensitySplitOptions getDensity() {
- return density;
- }
-
- /**
- * Configures density split settings.
- */
- public void density(Action<DensitySplitOptions> action) {
- action.execute(density);
- }
-
- /**
- * ABI settings.
- */
- public AbiSplitOptions getAbi() {
- return abi;
- }
-
- /**
- * Configures ABI split settings.
- */
- public void abi(Action<AbiSplitOptions> action) {
- action.execute(abi);
- }
-
- /**
- * Language settings.
- */
- public LanguageSplitOptions getLanguage() {
- return language;
- }
-
- /**
- * Configures the language split settings.
- */
- public void language(Action<LanguageSplitOptions> action) {
- action.execute(language);
- }
-
- /**
- * Returns the list of Density filters used for multi-apk.
- *
- * <p>null value is allowed, indicating the need to generate an apk with all densities.
- *
- * @return a set of filters.
- */
- @NonNull
- public Set<String> getDensityFilters() {
- return density.getApplicableFilters();
- }
-
- /**
- * Returns the list of ABI filters used for multi-apk.
- *
- * <p>null value is allowed, indicating the need to generate an apk with all abis.
- *
- * @return a set of filters.
- */
- @NonNull
- public Set<String> getAbiFilters() {
- return abi.getApplicableFilters();
- }
-
- /**
- * Returns the list of language filters used for multi-apk.
- *
- * <>null value is allowed, indicating the need to generate an apk with all languages.
- *
- * @return a set of language filters.
- */
- @NonNull
- public Set<String> getLanguageFilters() {
- return language.getApplicationFilters();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java
deleted file mode 100644
index e1ca92e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidArtifactOutput;
-import com.android.builder.model.ClassField;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.NativeLibrary;
-import com.android.builder.model.SourceProvider;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Implementation of AndroidArtifact that is serializable
- */
-public class AndroidArtifactImpl extends BaseArtifactImpl implements AndroidArtifact, Serializable {
- private static final long serialVersionUID = 1L;
-
- @NonNull
- private final Collection<AndroidArtifactOutput> outputs;
- private final boolean isSigned;
- @Nullable
- private final String signingConfigName;
- @NonNull
- private final String applicationId;
- @NonNull
- private final String sourceGenTaskName;
-
- @NonNull
- private final List<File> generatedResourceFolders;
- @Nullable
- private final Set<String> abiFilters;
- @NonNull
- private final Collection<NativeLibrary> nativeLibraries;
- @NonNull
- private final Map<String, ClassField> buildConfigFields;
- @NonNull
- private final Map<String, ClassField> resValues;
-
- AndroidArtifactImpl(
- @NonNull String name,
- @NonNull Collection<AndroidArtifactOutput> outputs,
- @NonNull String assembleTaskName,
- boolean isSigned,
- @Nullable String signingConfigName,
- @NonNull String applicationId,
- @NonNull String sourceGenTaskName,
- @NonNull String compileTaskName,
- @NonNull List<File> generatedSourceFolders,
- @NonNull List<File> generatedResourceFolders,
- @NonNull File classesFolder,
- @NonNull File javaResourcesFolder,
- @NonNull Dependencies dependencies,
- @Nullable SourceProvider variantSourceProvider,
- @Nullable SourceProvider multiFlavorSourceProviders,
- @Nullable Set<String> abiFilters,
- @NonNull Collection<NativeLibrary> nativeLibraries,
- @NonNull Map<String,ClassField> buildConfigFields,
- @NonNull Map<String,ClassField> resValues) {
- super(name, assembleTaskName, compileTaskName, classesFolder, javaResourcesFolder,
- dependencies, variantSourceProvider, multiFlavorSourceProviders,
- generatedSourceFolders);
-
- this.outputs = outputs;
- this.isSigned = isSigned;
- this.signingConfigName = signingConfigName;
- this.applicationId = applicationId;
- this.sourceGenTaskName = sourceGenTaskName;
- this.generatedResourceFolders = generatedResourceFolders;
- this.abiFilters = abiFilters;
- this.nativeLibraries = nativeLibraries;
- this.buildConfigFields = buildConfigFields;
- this.resValues = resValues;
- }
-
- @NonNull
- @Override
- public Collection<AndroidArtifactOutput> getOutputs() {
- return outputs;
- }
-
- @Override
- public boolean isSigned() {
- return isSigned;
- }
-
- @Nullable
- @Override
- public String getSigningConfigName() {
- return signingConfigName;
- }
-
- @NonNull
- @Override
- public String getApplicationId() {
- return applicationId;
- }
-
- @NonNull
- @Override
- public String getSourceGenTaskName() {
- return sourceGenTaskName;
- }
-
- @NonNull
- @Override
- public Set<String> getIdeSetupTaskNames() {
- return Sets.newHashSet(getSourceGenTaskName());
- }
-
- @NonNull
- @Override
- public List<File> getGeneratedResourceFolders() {
- return generatedResourceFolders;
- }
-
- @Nullable
- @Override
- public Set<String> getAbiFilters() {
- return abiFilters;
- }
-
- @NonNull
- @Override
- public Collection<NativeLibrary> getNativeLibraries() {
- return nativeLibraries;
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getBuildConfigFields() {
- return buildConfigFields;
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getResValues() {
- return resValues;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseConfigImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseConfigImpl.java
deleted file mode 100644
index b57c225..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseConfigImpl.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.BaseConfig;
-import com.android.builder.model.ClassField;
-import com.google.common.collect.ImmutableMap;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * An implementation of BaseConfig specifically for sending as part of the Android model
- * through the Gradle tooling API.
- */
-abstract class BaseConfigImpl implements BaseConfig, Serializable {
- private static final long serialVersionUID = 1L;
-
- @NonNull
- private final Map<String, Object> mManifestPlaceholders;
- @NonNull
- private final Map<String, ClassField> mBuildConfigFields;
- @NonNull
- private final Map<String, ClassField> mResValues;
- @Nullable
- private Boolean mMultiDexEnabled;
- @Nullable
- private File mMultiDexKeepFile;
- @Nullable
- private File mMultiDexKeepProguard;
- @Nullable
- private List<File> mJarJarRuleFiles;
-
- protected BaseConfigImpl(@NonNull BaseConfig baseConfig) {
- mManifestPlaceholders = ImmutableMap.copyOf(baseConfig.getManifestPlaceholders());
- mBuildConfigFields = ImmutableMap.copyOf(baseConfig.getBuildConfigFields());
- mResValues = ImmutableMap.copyOf(baseConfig.getResValues());
- mMultiDexEnabled = baseConfig.getMultiDexEnabled();
- mMultiDexKeepFile = baseConfig.getMultiDexKeepFile();
- mMultiDexKeepProguard = baseConfig.getMultiDexKeepProguard();
- mJarJarRuleFiles = baseConfig.getJarJarRuleFiles();
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getBuildConfigFields() {
- return mBuildConfigFields;
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getResValues() {
- return mResValues;
- }
-
- @NonNull
- @Override
- public List<File> getProguardFiles() {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public List<File> getConsumerProguardFiles() {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public Collection<File> getTestProguardFiles() {
- return Collections.emptyList();
- }
-
- @Override
- @NonNull
- public Map<String, Object> getManifestPlaceholders() {
- return mManifestPlaceholders;
- }
-
- @Override
- @Nullable
- public Boolean getMultiDexEnabled() {
- return mMultiDexEnabled;
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepFile() {
- return mMultiDexKeepFile;
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepProguard() {
- return mMultiDexKeepProguard;
- }
-
- @NonNull
- @Override
- public List<File> getJarJarRuleFiles() {
- return mJarJarRuleFiles;
- }
-
- @Override
- public String toString() {
- return "BaseConfigImpl{" +
- "mManifestPlaceholders=" + mManifestPlaceholders +
- ", mBuildConfigFields=" + mBuildConfigFields +
- ", mResValues=" + mResValues +
- ", mMultiDexEnabled=" + mMultiDexEnabled +
- ", mJarJarRuleFiles=" + mJarJarRuleFiles +
- '}';
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java
deleted file mode 100644
index a646b4c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.BuildType;
-import com.android.builder.model.SigningConfig;
-
-import java.io.Serializable;
-
-/**
- * Implementation of BuildType that is serializable. Objects used in the DSL cannot be
- * serialized.
- */
-class BuildTypeImpl extends BaseConfigImpl implements BuildType, Serializable {
- private static final long serialVersionUID = 1L;
-
- private String name;
- private boolean debuggable;
- private boolean testCoverageEnabled;
- private boolean jniDebuggable;
- private boolean pseudoLocalesEnabled;
- private boolean renderscriptDebuggable;
- private int renderscriptOptimLevel;
- private String applicationIdSuffix;
- private String versionNameSuffix;
- private boolean minifyEnabled;
- private boolean zipAlignEnabled;
- private boolean embedMicroApp;
-
- @NonNull
- static BuildTypeImpl cloneBuildType(@NonNull BuildType buildType) {
- BuildTypeImpl clonedBuildType = new BuildTypeImpl(buildType);
-
- clonedBuildType.name = buildType.getName();
- clonedBuildType.debuggable = buildType.isDebuggable();
- clonedBuildType.testCoverageEnabled = buildType.isTestCoverageEnabled();
- clonedBuildType.jniDebuggable = buildType.isJniDebuggable();
- clonedBuildType.renderscriptDebuggable = buildType.isRenderscriptDebuggable();
- clonedBuildType.renderscriptOptimLevel = buildType.getRenderscriptOptimLevel();
- clonedBuildType.applicationIdSuffix = buildType.getApplicationIdSuffix();
- clonedBuildType.versionNameSuffix = buildType.getVersionNameSuffix();
- clonedBuildType.minifyEnabled = buildType.isMinifyEnabled();
- clonedBuildType.zipAlignEnabled = buildType.isZipAlignEnabled();
- clonedBuildType.embedMicroApp = buildType.isEmbedMicroApp();
- clonedBuildType.pseudoLocalesEnabled = buildType.isPseudoLocalesEnabled();
-
- return clonedBuildType;
- }
-
- private BuildTypeImpl(@NonNull BuildType buildType) {
- super(buildType);
- }
-
- @NonNull
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public boolean isDebuggable() {
- return debuggable;
- }
-
- @Override
- public boolean isTestCoverageEnabled() {
- return testCoverageEnabled;
- }
-
- @Override
- public boolean isJniDebuggable() {
- return jniDebuggable;
- }
-
- @Override
- public boolean isRenderscriptDebuggable() {
- return renderscriptDebuggable;
- }
-
- @Override
- public boolean isPseudoLocalesEnabled() {
- return pseudoLocalesEnabled;
- }
-
- @Override
- public int getRenderscriptOptimLevel() {
- return renderscriptOptimLevel;
- }
-
- @Nullable
- @Override
- public String getApplicationIdSuffix() {
- return applicationIdSuffix;
- }
-
- @Nullable
- @Override
- public String getVersionNameSuffix() {
- return versionNameSuffix;
- }
-
- @Override
- public boolean isMinifyEnabled() {
- return minifyEnabled;
- }
-
- @Override
- public boolean isZipAlignEnabled() {
- return zipAlignEnabled;
- }
-
- @Override
- public boolean isEmbedMicroApp() {
- return embedMicroApp;
- }
-
- @Nullable
- @Override
- public SigningConfig getSigningConfig() {
- return null;
- }
-
- @Override
- public String toString() {
- return "BuildTypeImpl{" +
- "name='" + name + '\'' +
- ", debuggable=" + debuggable +
- ", testCoverageEnabled=" + testCoverageEnabled +
- ", jniDebuggable=" + jniDebuggable +
- ", renderscriptDebuggable=" + renderscriptDebuggable +
- ", renderscriptOptimLevel=" + renderscriptOptimLevel +
- ", applicationIdSuffix='" + applicationIdSuffix + '\'' +
- ", versionNameSuffix='" + versionNameSuffix + '\'' +
- ", minifyEnabled=" + minifyEnabled +
- ", zipAlignEnabled=" + zipAlignEnabled +
- ", embedMicroApp=" + embedMicroApp +
- "} " + super.toString();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
deleted file mode 100644
index 4528821..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.CompileOptions;
-import com.android.builder.model.AaptOptions;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ArtifactMetaData;
-import com.android.builder.model.BuildTypeContainer;
-import com.android.builder.model.JavaCompileOptions;
-import com.android.builder.model.LintOptions;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.model.SyncIssue;
-import com.android.builder.model.NativeToolchain;
-import com.android.builder.model.Variant;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * Implementation of the AndroidProject model object.
- */
-class DefaultAndroidProject implements AndroidProject, Serializable {
- private static final long serialVersionUID = 1L;
-
- @NonNull
- private final String modelVersion;
- @NonNull
- private final String name;
- @NonNull
- private final String compileTarget;
- @NonNull
- private final Collection<String> bootClasspath;
- @NonNull
- private final Collection<File> frameworkSource;
- @NonNull
- private final Collection<SigningConfig> signingConfigs;
- @NonNull
- private final AaptOptions aaptOptions;
- @NonNull
- private final Collection<ArtifactMetaData> extraArtifacts;
- @NonNull
- private final Collection<String> unresolvedDependencies;
- @NonNull
- private final Collection<SyncIssue> syncIssues;
- @NonNull
- private final JavaCompileOptions javaCompileOptions;
- @NonNull
- private final LintOptions lintOptions;
- @NonNull
- private final File buildFolder;
- @Nullable
- private final String resourcePrefix;
- @NonNull
- private final Collection<NativeToolchain> nativeToolchains;
- private final boolean isLibrary;
- private final int apiVersion;
-
- private final Collection<BuildTypeContainer> buildTypes = Lists.newArrayList();
- private final Collection<ProductFlavorContainer> productFlavors = Lists.newArrayList();
- private final Collection<Variant> variants = Lists.newArrayList();
-
- private ProductFlavorContainer defaultConfig;
-
- @NonNull
- private final Collection<String> flavorDimensions;
-
- DefaultAndroidProject(
- @NonNull String modelVersion,
- @NonNull String name,
- @NonNull Collection<String> flavorDimensions,
- @NonNull String compileTarget,
- @NonNull Collection<String> bootClasspath,
- @NonNull Collection<File> frameworkSource,
- @NonNull Collection<SigningConfig> signingConfigs,
- @NonNull AaptOptions aaptOptions,
- @NonNull Collection<ArtifactMetaData> extraArtifacts,
- @NonNull Collection<String> unresolvedDependencies,
- @NonNull Collection<SyncIssue> syncIssues,
- @NonNull CompileOptions compileOptions,
- @NonNull LintOptions lintOptions,
- @NonNull File buildFolder,
- @Nullable String resourcePrefix,
- @NonNull Collection<NativeToolchain> nativeToolchains,
- boolean isLibrary,
- int apiVersion) {
- this.modelVersion = modelVersion;
- this.name = name;
- this.flavorDimensions = flavorDimensions;
- this.compileTarget = compileTarget;
- this.bootClasspath = bootClasspath;
- this.frameworkSource = frameworkSource;
- this.signingConfigs = signingConfigs;
- this.aaptOptions = aaptOptions;
- this.extraArtifacts = extraArtifacts;
- this.unresolvedDependencies = unresolvedDependencies;
- this.syncIssues = syncIssues;
- javaCompileOptions = new DefaultJavaCompileOptions(compileOptions);
- this.lintOptions = lintOptions;
- this.buildFolder = buildFolder;
- this.resourcePrefix = resourcePrefix;
- this.isLibrary = isLibrary;
- this.apiVersion = apiVersion;
- this.nativeToolchains = nativeToolchains;
- }
-
- @NonNull
- DefaultAndroidProject setDefaultConfig(@NonNull ProductFlavorContainer defaultConfigContainer) {
- defaultConfig = defaultConfigContainer;
- return this;
- }
-
- @NonNull
- DefaultAndroidProject addBuildType(@NonNull BuildTypeContainer buildTypeContainer) {
- buildTypes.add(buildTypeContainer);
- return this;
- }
-
- @NonNull
- DefaultAndroidProject addProductFlavors(
- @NonNull ProductFlavorContainer productFlavorContainer) {
- productFlavors.add(productFlavorContainer);
- return this;
- }
-
- @NonNull
- DefaultAndroidProject addVariant(@NonNull VariantImpl variant) {
- variants.add(variant);
- return this;
- }
-
- @Override
- @NonNull
- public String getModelVersion() {
- return modelVersion;
- }
-
- @Override
- public int getApiVersion() {
- return apiVersion;
- }
-
- @Override
- @NonNull
- public String getName() {
- return name;
- }
-
- @Override
- @NonNull
- public ProductFlavorContainer getDefaultConfig() {
- return defaultConfig;
- }
-
- @Override
- @NonNull
- public Collection<BuildTypeContainer> getBuildTypes() {
- return buildTypes;
- }
-
- @Override
- @NonNull
- public Collection<ProductFlavorContainer> getProductFlavors() {
- return productFlavors;
- }
-
- @Override
- @NonNull
- public Collection<Variant> getVariants() {
- return variants;
- }
-
- @NonNull
- @Override
- public Collection<String> getFlavorDimensions() {
- return flavorDimensions;
- }
-
- @NonNull
- @Override
- public Collection<ArtifactMetaData> getExtraArtifacts() {
- return extraArtifacts;
- }
-
- @Override
- public boolean isLibrary() {
- return isLibrary;
- }
-
- @Override
- @NonNull
- public String getCompileTarget() {
- return compileTarget;
- }
-
- @Override
- @NonNull
- public Collection<String> getBootClasspath() {
- return bootClasspath;
- }
-
- @Override
- @NonNull
- public Collection<File> getFrameworkSources() {
- return frameworkSource;
- }
-
- @Override
- @NonNull
- public Collection<SigningConfig> getSigningConfigs() {
- return signingConfigs;
- }
-
- @Override
- @NonNull
- public AaptOptions getAaptOptions() {
- return aaptOptions;
- }
-
- @Override
- @NonNull
- public LintOptions getLintOptions() {
- return lintOptions;
- }
-
- @Override
- @NonNull
- public Collection<String> getUnresolvedDependencies() {
- return unresolvedDependencies;
- }
-
- @NonNull
- @Override
- public Collection<SyncIssue> getSyncIssues() {
- return syncIssues;
- }
-
- @Override
- @NonNull
- public JavaCompileOptions getJavaCompileOptions() {
- return javaCompileOptions;
- }
-
- @Override
- @NonNull
- public File getBuildFolder() {
- return buildFolder;
- }
-
- @Override
- @Nullable
- public String getResourcePrefix() {
- return resourcePrefix;
- }
-
- @NonNull
- @Override
- public Collection<NativeToolchain> getNativeToolchains() {
- return nativeToolchains;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java
deleted file mode 100644
index f4c37df..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import static com.android.SdkConstants.DOT_JAR;
-import static com.android.SdkConstants.FD_JARS;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dependency.LibraryDependencyImpl;
-import com.android.build.gradle.internal.dependency.VariantDependencies;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.dependency.JarDependency;
-import com.android.builder.dependency.LibraryDependency;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.JavaLibrary;
-import com.android.ide.common.caching.CreatingCache;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- */
-public class DependenciesImpl implements Dependencies, Serializable {
- private static final long serialVersionUID = 1L;
-
- private static final CreatingCache<LibraryDependency, AndroidLibrary> sCache
- = new CreatingCache<LibraryDependency, AndroidLibrary>(
- new CreatingCache.ValueFactory<LibraryDependency, AndroidLibrary>() {
- @Override
- @NonNull
- public AndroidLibrary create(@NonNull LibraryDependency key) {
- return convertAndroidLibrary(key);
- }
- });
-
- @NonNull
- private final List<AndroidLibrary> libraries;
- @NonNull
- private final List<JavaLibrary> javaLibraries;
- @NonNull
- private final List<String> projects;
-
- public static void clearCaches() {
- sCache.clear();
- }
-
- @NonNull
- static DependenciesImpl cloneDependenciesForJavaArtifacts(@NonNull Dependencies dependencies) {
- List<AndroidLibrary> libraries = Collections.emptyList();
- List<JavaLibrary> javaLibraries = Lists.newArrayList(dependencies.getJavaLibraries());
- List<String> projects = Collections.emptyList();
-
- return new DependenciesImpl(libraries, javaLibraries, projects);
- }
-
- @NonNull
- static DependenciesImpl cloneDependencies(
- @NonNull BaseVariantData variantData,
- @NonNull AndroidBuilder androidBuilder) {
- VariantDependencies variantDependencies = variantData.getVariantDependency();
-
- List<AndroidLibrary> libraries;
- List<JavaLibrary> javaLibraries;
- List<String> projects;
-
- List<LibraryDependencyImpl> libs = variantDependencies.getLibraries();
- libraries = Lists.newArrayListWithCapacity(libs.size());
- for (LibraryDependencyImpl libImpl : libs) {
- AndroidLibrary clonedLib = sCache.get(libImpl);
- if (clonedLib != null) {
- libraries.add(clonedLib);
- }
- }
-
- List<JarDependency> jarDeps = variantDependencies.getJarDependencies();
- List<JarDependency> localDeps = variantDependencies.getLocalDependencies();
-
- javaLibraries = Lists.newArrayListWithExpectedSize(jarDeps.size() + localDeps.size());
- projects = Lists.newArrayList();
-
- for (JarDependency jarDep : jarDeps) {
- // don't include package-only dependencies
- if (jarDep.isCompiled()) {
- boolean customArtifact = jarDep.getResolvedCoordinates() != null &&
- jarDep.getResolvedCoordinates().getClassifier() != null;
-
- File jarFile = jarDep.getJarFile();
- if (!customArtifact && jarDep.getProjectPath() != null) {
- projects.add(jarDep.getProjectPath());
- } else {
- javaLibraries.add(
- new JavaLibraryImpl(jarFile, null, jarDep.getResolvedCoordinates()));
- }
- }
- }
-
- for (JarDependency jarDep : localDeps) {
- // don't include package-only dependencies
- if (jarDep.isCompiled()) {
- javaLibraries.add(
- new JavaLibraryImpl(
- jarDep.getJarFile(),
- null,
- jarDep.getResolvedCoordinates()));
- }
- }
-
- GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
-
- if (variantConfig.getRenderscriptSupportModeEnabled()) {
- File supportJar = androidBuilder.getRenderScriptSupportJar();
- if (supportJar != null) {
- javaLibraries.add(new JavaLibraryImpl(supportJar, null, null));
- }
- }
-
- return new DependenciesImpl(libraries, javaLibraries, projects);
- }
-
- public DependenciesImpl(@NonNull Set<JavaLibrary> javaLibraries) {
- this.javaLibraries = Lists.newArrayList(javaLibraries);
- this.libraries = Collections.emptyList();
- this.projects = Collections.emptyList();
- }
-
- private DependenciesImpl(@NonNull List<AndroidLibrary> libraries,
- @NonNull List<JavaLibrary> javaLibraries,
- @NonNull List<String> projects) {
- this.libraries = libraries;
- this.javaLibraries = javaLibraries;
- this.projects = projects;
- }
-
- @NonNull
- @Override
- public Collection<AndroidLibrary> getLibraries() {
- return libraries;
- }
-
- @NonNull
- @Override
- public Collection<JavaLibrary> getJavaLibraries() {
- return javaLibraries;
- }
-
- @NonNull
- @Override
- public List<String> getProjects() {
- return projects;
- }
-
- @NonNull
- private static AndroidLibrary convertAndroidLibrary(
- @NonNull LibraryDependency libraryDependency) {
- List<LibraryDependency> deps = libraryDependency.getDependencies();
- List<AndroidLibrary> clonedDeps = Lists.newArrayListWithCapacity(deps.size());
- for (LibraryDependency child : deps) {
- AndroidLibrary clonedLib = sCache.get(child);
- if (clonedLib != null) {
- clonedDeps.add(clonedLib);
- }
- }
-
- // compute local jar even if the bundle isn't exploded.
- Collection<File> localJarOverride = findLocalJar(libraryDependency);
-
- return new AndroidLibraryImpl(
- libraryDependency,
- clonedDeps,
- localJarOverride,
- libraryDependency.getProject(),
- libraryDependency.getProjectVariant(),
- libraryDependency.getRequestedCoordinates(),
- libraryDependency.getResolvedCoordinates());
- }
-
- /**
- * Finds the local jar for an aar.
- *
- * Since the model can be queried before the aar are exploded, we attempt to get them
- * from inside the aar.
- *
- * @param library the library.
- * @return its local jars.
- */
- @NonNull
- private static Collection<File> findLocalJar(LibraryDependency library) {
- // if the library is exploded, just use the normal method.
- File explodedFolder = library.getFolder();
- if (explodedFolder.isDirectory()) {
- return library.getLocalJars();
- }
-
- // if the aar file is present, search inside it for jar files under libs/
- File aarFile = library.getBundle();
- if (aarFile.isFile()) {
- List<File> jarList = Lists.newArrayList();
-
- File jarsFolder = new File(explodedFolder, FD_JARS);
-
- ZipFile zipFile = null;
- try {
- //noinspection IOResourceOpenedButNotSafelyClosed
- zipFile = new ZipFile(aarFile);
-
- for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
- ZipEntry zipEntry = e.nextElement();
- String name = zipEntry.getName();
- if (name.startsWith("libs/") && name.endsWith(DOT_JAR)) {
- jarList.add(new File(jarsFolder, name.replace('/', File.separatorChar)));
- }
- }
-
- return jarList;
- } catch (FileNotFoundException ignored) {
- // should not happen since we check ahead of time
- } catch (IOException e) {
- // we'll return an empty list below
- } finally {
- if (zipFile != null) {
- try {
- zipFile.close();
- } catch (IOException ignored) {
- }
- }
- }
- }
-
- return Collections.emptyList();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java
deleted file mode 100644
index 160b9a7..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.JavaArtifact;
-import com.android.builder.model.SourceProvider;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * Implementation of JavaArtifact that is serializable
- */
-public class JavaArtifactImpl extends BaseArtifactImpl implements JavaArtifact, Serializable {
- private static final long serialVersionUID = 1L;
-
- private final Set<String> ideSetupTaskNames;
-
- @Nullable
- private final File mockablePlatformJar;
-
- public static JavaArtifactImpl clone(@NonNull JavaArtifact javaArtifact) {
- SourceProvider variantSP = javaArtifact.getVariantSourceProvider();
- SourceProvider flavorsSP = javaArtifact.getMultiFlavorSourceProvider();
-
- return new JavaArtifactImpl(
- javaArtifact.getName(),
- javaArtifact.getAssembleTaskName(),
- javaArtifact.getCompileTaskName(),
- javaArtifact.getIdeSetupTaskNames(),
- javaArtifact.getGeneratedSourceFolders(),
- javaArtifact.getClassesFolder(),
- javaArtifact.getJavaResourcesFolder(),
- javaArtifact.getMockablePlatformJar(),
- DependenciesImpl.cloneDependenciesForJavaArtifacts(javaArtifact.getDependencies()),
- variantSP != null ? SourceProviderImpl.cloneProvider(variantSP) : null,
- flavorsSP != null ? SourceProviderImpl.cloneProvider(flavorsSP) : null);
- }
-
- public JavaArtifactImpl(@NonNull String name,
- @NonNull String assembleTaskName,
- @NonNull String compileTaskName,
- @NonNull Iterable<String> ideSetupTaskNames,
- @NonNull Collection<File> generatedSourceFolders,
- @NonNull File classesFolder,
- @NonNull File javaResourcesFolder,
- @Nullable File mockablePlatformJar,
- @NonNull Dependencies dependencies,
- @Nullable SourceProvider variantSourceProvider,
- @Nullable SourceProvider multiFlavorSourceProviders) {
- super(name, assembleTaskName, compileTaskName, classesFolder, javaResourcesFolder,
- dependencies,
- variantSourceProvider, multiFlavorSourceProviders, generatedSourceFolders);
- this.ideSetupTaskNames = Sets.newHashSet(ideSetupTaskNames);
- this.mockablePlatformJar = mockablePlatformJar;
- }
-
- @NonNull
- @Override
- public Set<String> getIdeSetupTaskNames() {
- return ideSetupTaskNames;
- }
-
- @Override
- @Nullable
- public File getMockablePlatformJar() {
- return mockablePlatformJar;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java
deleted file mode 100644
index d40955c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.JavaLibrary;
-import com.android.builder.model.MavenCoordinates;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-public class JavaLibraryImpl extends LibraryImpl implements JavaLibrary, Serializable {
- private final File jarFile;
-
- public JavaLibraryImpl(
- @NonNull File jarFile,
- @Nullable MavenCoordinates requestedCoordinates,
- @Nullable MavenCoordinates resolvedCoordinates) {
- super(requestedCoordinates, resolvedCoordinates);
- this.jarFile = jarFile;
- }
-
- @NonNull
- @Override
- public File getJarFile() {
- return jarFile;
- }
-
- @NonNull
- @Override
- public List<? extends JavaLibrary> getDependencies() {
- return Collections.emptyList();
- }
-
- @Override
- public String toString() {
- final StringBuffer sb = new StringBuffer("JavaLibraryImpl{");
- sb.append("jarFile=").append(jarFile);
- sb.append('}');
- return sb.toString();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.java
deleted file mode 100644
index b6e18ee..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import static com.android.builder.model.AndroidProject.ARTIFACT_MAIN;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.OutputFile;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.api.ApkOutputFile;
-import com.android.build.gradle.internal.BuildTypeData;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.ProductFlavorData;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.VariantManager;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.ApkVariantOutputData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.internal.variant.TestVariantData;
-import com.android.build.gradle.internal.variant.TestedVariantData;
-import com.android.builder.Version;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.VariantType;
-import com.android.builder.model.AaptOptions;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidArtifactOutput;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.ArtifactMetaData;
-import com.android.builder.model.JavaArtifact;
-import com.android.builder.model.LintOptions;
-import com.android.builder.model.NativeLibrary;
-import com.android.builder.model.NativeToolchain;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.model.SourceProvider;
-import com.android.builder.model.SourceProviderContainer;
-import com.android.builder.model.SyncIssue;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.Project;
-import org.gradle.tooling.provider.model.ToolingModelBuilder;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Builder for the custom Android model.
- */
-public class ModelBuilder implements ToolingModelBuilder {
-
- @NonNull
- private final AndroidBuilder androidBuilder;
- @NonNull
- private final AndroidConfig config;
- @NonNull
- private final ExtraModelInfo extraModelInfo;
- @NonNull
- private final VariantManager variantManager;
- @NonNull
- private final TaskManager taskManager;
- @NonNull
- private final NdkHandler ndkHandler;
- @NonNull
- private Map<Abi, NativeToolchain> toolchains;
- @NonNull
- private NativeLibraryFactory nativeLibFactory;
-
- private final boolean isLibrary;
-
- public ModelBuilder(
- @NonNull AndroidBuilder androidBuilder,
- @NonNull VariantManager variantManager,
- @NonNull TaskManager taskManager,
- @NonNull AndroidConfig config,
- @NonNull ExtraModelInfo extraModelInfo,
- @NonNull NdkHandler ndkHandler,
- @NonNull NativeLibraryFactory nativeLibraryFactory,
- boolean isLibrary) {
- this.androidBuilder = androidBuilder;
- this.config = config;
- this.extraModelInfo = extraModelInfo;
- this.variantManager = variantManager;
- this.taskManager = taskManager;
- this.ndkHandler = ndkHandler;
- this.nativeLibFactory = nativeLibraryFactory;
- this.isLibrary = isLibrary;
- }
-
- public static void clearCaches() {
- DependenciesImpl.clearCaches();
- }
-
- @Override
- public boolean canBuild(String modelName) {
- // The default name for a model is the name of the Java interface.
- return modelName.equals(AndroidProject.class.getName());
- }
-
- @Override
- public Object buildAll(String modelName, Project project) {
- Collection<? extends SigningConfig> signingConfigs = config.getSigningConfigs();
-
- // Get the boot classpath. This will ensure the target is configured.
- List<String> bootClasspath = androidBuilder.getBootClasspathAsStrings();
-
- List<File> frameworkSource = Collections.emptyList();
-
- // List of extra artifacts, with all test variants added.
- List<ArtifactMetaData> artifactMetaDataList = Lists.newArrayList(
- extraModelInfo.getExtraArtifacts());
-
- for (VariantType variantType : VariantType.getTestingTypes()) {
- artifactMetaDataList.add(new ArtifactMetaDataImpl(
- variantType.getArtifactName(),
- true /*isTest*/,
- variantType.getArtifactType()));
- }
-
- LintOptions lintOptions = com.android.build.gradle.internal.dsl.LintOptions.create(
- config.getLintOptions());
-
- AaptOptions aaptOptions = AaptOptionsImpl.create(config.getAaptOptions());
-
- List<SyncIssue> syncIssues = Lists.newArrayList(extraModelInfo.getSyncIssues().values());
-
- List<String> flavorDimensionList = (config.getFlavorDimensionList() != null ?
- config.getFlavorDimensionList() : Lists.<String>newArrayList());
-
- toolchains = createNativeToolchainModelMap(ndkHandler);
-
- DefaultAndroidProject androidProject = new DefaultAndroidProject(
- Version.ANDROID_GRADLE_PLUGIN_VERSION,
- project.getName(),
- flavorDimensionList,
- androidBuilder.getTarget() != null ? androidBuilder.getTarget().hashString() : "",
- bootClasspath,
- frameworkSource,
- cloneSigningConfigs(config.getSigningConfigs()),
- aaptOptions,
- artifactMetaDataList,
- findUnresolvedDependencies(syncIssues),
- syncIssues,
- config.getCompileOptions(),
- lintOptions,
- project.getBuildDir(),
- config.getResourcePrefix(),
- ImmutableList.copyOf(toolchains.values()),
- isLibrary,
- Version.BUILDER_MODEL_API_VERSION);
-
- androidProject.setDefaultConfig(ProductFlavorContainerImpl.createProductFlavorContainer(
- variantManager.getDefaultConfig(),
- extraModelInfo.getExtraFlavorSourceProviders(
- variantManager.getDefaultConfig().getProductFlavor().getName())));
-
- for (BuildTypeData btData : variantManager.getBuildTypes().values()) {
- androidProject.addBuildType(BuildTypeContainerImpl.create(
- btData,
- extraModelInfo.getExtraBuildTypeSourceProviders(btData.getBuildType().getName())));
- }
- for (ProductFlavorData pfData : variantManager.getProductFlavors().values()) {
- androidProject.addProductFlavors(ProductFlavorContainerImpl.createProductFlavorContainer(
- pfData,
- extraModelInfo.getExtraFlavorSourceProviders(pfData.getProductFlavor().getName())));
- }
-
- for (BaseVariantData<? extends BaseVariantOutputData> variantData : variantManager.getVariantDataList()) {
- if (!variantData.getType().isForTesting()) {
- androidProject.addVariant(createVariant(variantData));
- }
- }
-
- return androidProject;
- }
-
- /**
- * Create a map of ABI to NativeToolchain
- */
- public static Map<Abi, NativeToolchain> createNativeToolchainModelMap(
- @NonNull NdkHandler ndkHandler) {
- if (!ndkHandler.isNdkDirConfigured()) {
- return ImmutableMap.of();
- }
-
- Map<Abi, NativeToolchain> toolchains = Maps.newHashMap();
-
- for (Abi abi : ndkHandler.getSupportedAbis()) {
- toolchains.put(
- abi,
- new NativeToolchainImpl(
- ndkHandler.getToolchain().getName() + "-" + abi.getName(),
- ndkHandler.getCCompiler(abi),
- ndkHandler.getCppCompiler(abi)));
- }
- return toolchains;
- }
-
- @NonNull
- private VariantImpl createVariant(
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
- AndroidArtifact mainArtifact = createAndroidArtifact(ARTIFACT_MAIN, variantData);
-
- GradleVariantConfiguration variantConfiguration = variantData.getVariantConfiguration();
-
- String variantName = variantConfiguration.getFullName();
-
- List<AndroidArtifact> extraAndroidArtifacts = Lists.newArrayList(
- extraModelInfo.getExtraAndroidArtifacts(variantName));
- // Make sure all extra artifacts are serializable.
- Collection<JavaArtifact> extraJavaArtifacts = extraModelInfo.getExtraJavaArtifacts(
- variantName);
- List<JavaArtifact> clonedExtraJavaArtifacts = Lists.newArrayListWithCapacity(
- extraJavaArtifacts.size());
- for (JavaArtifact javaArtifact : extraJavaArtifacts) {
- clonedExtraJavaArtifacts.add(JavaArtifactImpl.clone(javaArtifact));
- }
-
- if (variantData instanceof TestedVariantData) {
- for (VariantType variantType : VariantType.getTestingTypes()) {
- TestVariantData testVariantData = ((TestedVariantData) variantData).getTestVariantData(variantType);
- if (testVariantData != null) {
- VariantType type = testVariantData.getType();
- if (type != null) {
- switch (type) {
- case ANDROID_TEST:
- extraAndroidArtifacts.add(createAndroidArtifact(
- variantType.getArtifactName(),
- testVariantData));
- break;
- case UNIT_TEST:
- clonedExtraJavaArtifacts.add(createUnitTestsJavaArtifact(
- variantType,
- testVariantData));
- break;
- default:
- throw new IllegalArgumentException(
- "Unsupported test variant type ${variantType}.");
- }
- }
- }
- }
- }
-
- // if the target is a codename, override the model value.
- ApiVersion sdkVersionOverride = null;
-
- // we know the getTargetInfo won't return null here.
- @SuppressWarnings("ConstantConditions")
- IAndroidTarget androidTarget = androidBuilder.getTargetInfo().getTarget();
-
- AndroidVersion version = androidTarget.getVersion();
- if (version.getCodename() != null) {
- sdkVersionOverride = ApiVersionImpl.clone(version);
- }
-
- return new VariantImpl(
- variantName,
- variantConfiguration.getBaseName(),
- variantConfiguration.getBuildType().getName(),
- getProductFlavorNames(variantData),
- ProductFlavorImpl.cloneFlavor(
- variantConfiguration.getMergedFlavor(),
- sdkVersionOverride,
- sdkVersionOverride),
- mainArtifact,
- extraAndroidArtifacts,
- clonedExtraJavaArtifacts);
- }
-
- private JavaArtifactImpl createUnitTestsJavaArtifact(
- @NonNull VariantType variantType,
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
- SourceProviders sourceProviders = determineSourceProviders(variantData);
- DependenciesImpl dependencies = DependenciesImpl.cloneDependencies(variantData,
- androidBuilder);
-
- List<File> extraGeneratedSourceFolders = variantData.getExtraGeneratedSourceFolders();
- return new JavaArtifactImpl(
- variantType.getArtifactName(),
- variantData.assembleVariantTask.getName(),
- variantData.compileTask.getName(),
- Sets.newHashSet(variantData.prepareDependenciesTask.getName(),
- taskManager.createMockableJar.getName()),
- extraGeneratedSourceFolders != null ? extraGeneratedSourceFolders : Collections.<File>emptyList(),
- (variantData.javacTask != null) ?
- variantData.javacTask.getDestinationDir() :
- variantData.getScope().getJavaOutputDir(),
- variantData.getScope().getJavaResourcesDestinationDir(),
- taskManager.createMockableJar.getOutputFile(),
- dependencies,
- sourceProviders.variantSourceProvider,
- sourceProviders.multiFlavorSourceProvider);
- }
-
- /**
- * Create a NativeLibrary for each ABI.
- */
- private Collection<NativeLibrary> createNativeLibraries(
- @NonNull Collection<Abi> abis,
- @NonNull VariantScope scope) {
- Collection<NativeLibrary> nativeLibraries = Lists.newArrayListWithCapacity(abis.size());
- for (Abi abi : abis) {
- NativeToolchain toolchain = toolchains.get(abi);
- if (toolchain == null) {
- continue;
- }
- Optional<NativeLibrary> lib = nativeLibFactory.create(scope, toolchain.getName(), abi);
- if (lib.isPresent()) {
- nativeLibraries.add(lib.get());
- }
- }
- return nativeLibraries;
- }
-
- private AndroidArtifact createAndroidArtifact(
- @NonNull String name,
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
- VariantScope scope = variantData.getScope();
- GradleVariantConfiguration variantConfiguration = variantData.getVariantConfiguration();
-
- SigningConfig signingConfig = variantConfiguration.getSigningConfig();
- String signingConfigName = null;
- if (signingConfig != null) {
- signingConfigName = signingConfig.getName();
- }
-
- SourceProviders sourceProviders = determineSourceProviders(variantData);
-
- // get the outputs
- List<? extends BaseVariantOutputData> variantOutputs = variantData.getOutputs();
- List<AndroidArtifactOutput> outputs = Lists.newArrayListWithCapacity(variantOutputs.size());
-
- CoreNdkOptions ndkConfig = variantData.getVariantConfiguration().getNdkConfig();
- Collection<NativeLibrary> nativeLibraries = ImmutableList.of();
- if (ndkHandler.getNdkDirectory() != null) {
- if (config.getSplits().getAbi().isEnable()) {
- nativeLibraries = createNativeLibraries(
- config.getSplits().getAbi().isUniversalApk()
- ? ndkHandler.getSupportedAbis()
- : createAbiList(config.getSplits().getAbiFilters()),
- scope);
- } else {
- if (ndkConfig.getAbiFilters() == null || ndkConfig.getAbiFilters().isEmpty()) {
- nativeLibraries = createNativeLibraries(
- ndkHandler.getSupportedAbis(),
- scope);
- } else {
- nativeLibraries = createNativeLibraries(
- createAbiList(ndkConfig.getAbiFilters()),
- scope);
- }
- }
- }
-
- for (BaseVariantOutputData variantOutputData : variantOutputs) {
- int intVersionCode;
- if (variantOutputData instanceof ApkVariantOutputData) {
- intVersionCode = variantOutputData.getVersionCode();
- } else {
- Integer versionCode = variantConfiguration.getMergedFlavor().getVersionCode();
- intVersionCode = versionCode != null ? versionCode : 1;
- }
-
- ImmutableCollection.Builder<OutputFile> outputFiles = ImmutableList.builder();
-
- // add the main APK
- outputFiles.add(new OutputFileImpl(
- variantOutputData.getMainOutputFile().getFilters(),
- variantOutputData.getMainOutputFile().getType().name(),
- variantOutputData.getOutputFile()));
-
- for (ApkOutputFile splitApk : variantOutputData.getOutputs()) {
- if (splitApk.getType() == OutputFile.OutputType.SPLIT) {
- outputFiles.add(new OutputFileImpl(
- splitApk.getFilters(), OutputFile.SPLIT, splitApk.getOutputFile()));
- }
- }
-
- // add the main APK.
- outputs.add(new AndroidArtifactOutputImpl(
- outputFiles.build(),
- "assemble" + variantOutputData.getFullName(),
- variantOutputData.getScope().getManifestOutputFile(),
- intVersionCode));
- }
-
- return new AndroidArtifactImpl(
- name,
- outputs,
- variantData.assembleVariantTask == null ? scope.getTaskName("assemble") : variantData.assembleVariantTask.getName(),
- variantConfiguration.isSigningReady() || variantData.outputsAreSigned,
- signingConfigName,
- variantConfiguration.getApplicationId(),
- // TODO: Need to determine the tasks' name when the tasks may not be created
- // in component plugin.
- scope.getSourceGenTask() == null ? scope.getTaskName("generate", "Sources") : scope.getSourceGenTask().getName(),
- scope.getCompileTask() == null ? scope.getTaskName("compile", "Sources") : scope.getCompileTask().getName(),
- getGeneratedSourceFolders(variantData),
- getGeneratedResourceFolders(variantData),
- (variantData.javacTask != null) ?
- variantData.javacTask.getDestinationDir() :
- scope.getJavaOutputDir(),
- scope.getJavaResourcesDestinationDir(),
- DependenciesImpl.cloneDependencies(variantData, androidBuilder),
- sourceProviders.variantSourceProvider,
- sourceProviders.multiFlavorSourceProvider,
- variantConfiguration.getSupportedAbis(),
- nativeLibraries,
- variantConfiguration.getMergedBuildConfigFields(),
- variantConfiguration.getMergedResValues());
- }
-
- private static Collection<Abi> createAbiList(Collection<String> abiNames) {
- ImmutableList.Builder<Abi> builder = ImmutableList.builder();
- for (String abiName : abiNames) {
- builder.add(Abi.getByName(abiName));
- }
- return builder.build();
- }
-
- private static SourceProviders determineSourceProviders(
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
- SourceProvider variantSourceProvider =
- variantData.getVariantConfiguration().getVariantSourceProvider();
- SourceProvider multiFlavorSourceProvider =
- variantData.getVariantConfiguration().getMultiFlavorSourceProvider();
-
- return new SourceProviders(
- variantSourceProvider != null ?
- SourceProviderImpl.cloneProvider(variantSourceProvider) :
- null,
- multiFlavorSourceProvider != null ?
- SourceProviderImpl.cloneProvider(multiFlavorSourceProvider) :
- null);
- }
-
- @NonNull
- private static List<String> getProductFlavorNames(
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
- List<CoreProductFlavor> productFlavors = variantData.getVariantConfiguration()
- .getProductFlavors();
-
- List<String> flavorNames = Lists.newArrayListWithCapacity(productFlavors.size());
-
- for (ProductFlavor flavor : productFlavors) {
- flavorNames.add(flavor.getName());
- }
-
- return flavorNames;
- }
-
- @NonNull
- private static List<File> getGeneratedSourceFolders(
- @Nullable BaseVariantData<? extends BaseVariantOutputData> variantData) {
- if (variantData == null) {
- return Collections.emptyList();
- }
-
- List<File> extraFolders = variantData.getExtraGeneratedSourceFolders();
-
- List<File> folders;
- if (extraFolders != null) {
- folders = Lists.newArrayListWithExpectedSize(5 + extraFolders.size());
- folders.addAll(extraFolders);
- } else {
- folders = Lists.newArrayListWithExpectedSize(5);
- }
-
- VariantScope scope = variantData.getScope();
-
- // The R class is only generated by the first output.
- folders.add(scope.getRClassSourceOutputDir());
-
- folders.add(scope.getAidlSourceOutputDir());
- folders.add(scope.getBuildConfigSourceOutputDir());
- Boolean ndkMode = variantData.getVariantConfiguration().getMergedFlavor().getRenderscriptNdkModeEnabled();
- if (ndkMode == null || !ndkMode) {
- folders.add(scope.getRenderscriptSourceOutputDir());
- }
-
- return folders;
- }
-
- @NonNull
- private static List<File> getGeneratedResourceFolders(
- @Nullable BaseVariantData<? extends BaseVariantOutputData> variantData) {
- if (variantData == null) {
- return Collections.emptyList();
- }
-
- List<File> result;
-
- List<File> extraFolders = variantData.getExtraGeneratedResFolders();
- if (extraFolders != null && !extraFolders.isEmpty()) {
- result = Lists.newArrayListWithCapacity(extraFolders.size() + 2);
-
- result.addAll(extraFolders);
- } else {
- result = Lists.newArrayListWithCapacity(2);
- }
-
- VariantScope scope = variantData.getScope();
-
- result.add(scope.getRenderscriptResOutputDir());
- result.add(scope.getGeneratedResOutputDir());
-
- return result;
- }
-
- @NonNull
- private static Collection<SigningConfig> cloneSigningConfigs(
- @NonNull Collection<? extends SigningConfig> signingConfigs) {
- Collection<SigningConfig> results = Lists.newArrayListWithCapacity(signingConfigs.size());
-
- for (SigningConfig signingConfig : signingConfigs) {
- results.add(SigningConfigImpl.createSigningConfig(signingConfig));
- }
-
- return results;
- }
-
- @Nullable
- private static SourceProviderContainer getSourceProviderContainer(
- @NonNull Collection<SourceProviderContainer> items,
- @NonNull String name) {
- for (SourceProviderContainer item : items) {
- if (name.equals(item.getArtifactName())) {
- return item;
- }
- }
-
- return null;
- }
-
- private static class SourceProviders {
- protected SourceProviderImpl variantSourceProvider;
- protected SourceProviderImpl multiFlavorSourceProvider;
-
- public SourceProviders(
- SourceProviderImpl variantSourceProvider,
- SourceProviderImpl multiFlavorSourceProvider) {
- this.variantSourceProvider = variantSourceProvider;
- this.multiFlavorSourceProvider = multiFlavorSourceProvider;
- }
- }
-
- /**
- * Return the unresolved dependencies in SyncIssues
- */
- private static Collection<String> findUnresolvedDependencies(
- @NonNull Collection<SyncIssue> syncIssues) {
- List<String> unresolvedDependencies = Lists.newArrayList();
-
- for (SyncIssue issue : syncIssues) {
- if (issue.getType() == SyncIssue.TYPE_UNRESOLVED_DEPENDENCY) {
- unresolvedDependencies.add(issue.getData());
- }
- }
- return unresolvedDependencies;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeToolchainImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeToolchainImpl.java
deleted file mode 100644
index 5e7be5e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeToolchainImpl.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.NativeToolchain;
-
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * Implementation of NativeToolchain that is serializable.
- */
-public class NativeToolchainImpl implements NativeToolchain, Serializable {
-
- @NonNull
- String name;
-
- @NonNull
- File cCompilerExecutable;
-
- @NonNull
- File cppCompilerExecutable;
-
- public NativeToolchainImpl(@NonNull String name, @NonNull File cCompilerExecutable,
- @NonNull File cppCompilerExecutable) {
- this.name = name;
- this.cCompilerExecutable = cCompilerExecutable;
- this.cppCompilerExecutable = cppCompilerExecutable;
- }
-
- @NonNull
- @Override
- public String getName() {
- return name;
- }
-
- @NonNull
- @Override
- public File getCCompilerExecutable() {
- return cCompilerExecutable;
- }
-
- @NonNull
- @Override
- public File getCppCompilerExecutable() {
- return cppCompilerExecutable;
- }
-
- @Override
- public String toString() {
- return "ToolchainImpl{" +
- "name='" + name + '\'' +
- ", cCompilerExecutable=" + cCompilerExecutable +
- ", cppCompilerExecutable=" + cppCompilerExecutable +
- '}';
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java
deleted file mode 100644
index da1648c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.SigningConfig;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Implementation of ProductFlavor that is serializable. Objects used in the DSL cannot be
- * serialized.
- **/
-class ProductFlavorImpl extends BaseConfigImpl implements ProductFlavor, Serializable {
- private static final long serialVersionUID = 1L;
-
- private String name = null;
- private String mDimension = null;
- private ApiVersion mMinSdkVersion = null;
- private ApiVersion mTargetSdkVersion = null;
- private Integer mMaxSdkVersion = null;
- private Integer mRenderscriptTargetApi = null;
- private Boolean mRenderscriptSupportMode = null;
- private Boolean mRenderscriptNdkMode = null;
- private Integer mVersionCode = null;
- private String mVersionName = null;
- private String mApplicationId = null;
- private String mTestApplicationId = null;
- private String mTestInstrumentationRunner = null;
- private Map<String, String> mTestInstrumentationRunnerArguments = Maps.newHashMap();
- private Boolean mTestHandleProfiling = null;
- private Boolean mTestFunctionalTest = null;
- private Set<String> mResourceConfigurations = null;
-
-
- @NonNull
- static ProductFlavorImpl cloneFlavor(
- @NonNull ProductFlavor productFlavor,
- @Nullable ApiVersion minSdkVersionOverride,
- @Nullable ApiVersion targetSdkVersionOverride) {
- ProductFlavorImpl clonedFlavor = new ProductFlavorImpl(productFlavor);
- clonedFlavor.name = productFlavor.getName();
- clonedFlavor.mDimension = productFlavor.getDimension();
- clonedFlavor.mMinSdkVersion = minSdkVersionOverride != null
- ? minSdkVersionOverride
- : ApiVersionImpl.clone(productFlavor.getMinSdkVersion());
- clonedFlavor.mTargetSdkVersion = targetSdkVersionOverride != null
- ? targetSdkVersionOverride
- : ApiVersionImpl.clone(productFlavor.getTargetSdkVersion());
- clonedFlavor.mMaxSdkVersion = targetSdkVersionOverride != null
- ? null /* we remove the maxSdkVersion when dealing with a preview release */
- : productFlavor.getMaxSdkVersion();
- clonedFlavor.mRenderscriptTargetApi = productFlavor.getRenderscriptTargetApi();
- clonedFlavor.mRenderscriptSupportMode = productFlavor.getRenderscriptSupportModeEnabled();
- clonedFlavor.mRenderscriptNdkMode = productFlavor.getRenderscriptNdkModeEnabled();
-
- clonedFlavor.mVersionCode = productFlavor.getVersionCode();
- clonedFlavor.mVersionName = productFlavor.getVersionName();
-
- clonedFlavor.mApplicationId = productFlavor.getApplicationId();
-
- clonedFlavor.mTestApplicationId = productFlavor.getTestApplicationId();
- clonedFlavor.mTestInstrumentationRunner = productFlavor.getTestInstrumentationRunner();
- clonedFlavor.mTestHandleProfiling = productFlavor.getTestHandleProfiling();
- clonedFlavor.mTestFunctionalTest = productFlavor.getTestFunctionalTest();
-
- clonedFlavor.mResourceConfigurations = ImmutableSet.copyOf(
- productFlavor.getResourceConfigurations());
-
- clonedFlavor.mTestInstrumentationRunnerArguments = Maps.newHashMap(
- productFlavor.getTestInstrumentationRunnerArguments());
-
- return clonedFlavor;
- }
-
- private ProductFlavorImpl(@NonNull ProductFlavor productFlavor) {
- super(productFlavor);
- }
-
- @Override
- @NonNull
- public String getName() {
- return name;
- }
-
- @Override
- @Nullable
- public String getApplicationId() {
- return mApplicationId;
- }
-
- @Override
- @Nullable
- public Integer getVersionCode() {
- return mVersionCode;
- }
-
- @Override
- @Nullable
- public String getVersionName() {
- return mVersionName;
- }
-
- @Override
- @Nullable
- public ApiVersion getMinSdkVersion() {
- return mMinSdkVersion;
- }
-
- @Override
- @Nullable
- public ApiVersion getTargetSdkVersion() {
- return mTargetSdkVersion;
- }
-
- @Override
- @Nullable
- public Integer getMaxSdkVersion() { return mMaxSdkVersion; }
-
- @Override
- @Nullable
- public Integer getRenderscriptTargetApi() {
- return mRenderscriptTargetApi;
- }
-
- @Override
- @Nullable
- public Boolean getRenderscriptSupportModeEnabled() {
- return mRenderscriptSupportMode;
- }
-
- @Override
- @Nullable
- public Boolean getRenderscriptNdkModeEnabled() {
- return mRenderscriptNdkMode;
- }
-
- @Nullable
- @Override
- public String getTestApplicationId() {
- return mTestApplicationId;
- }
-
- @Nullable
- @Override
- public String getTestInstrumentationRunner() {
- return mTestInstrumentationRunner;
- }
-
- @NonNull
- @Override
- public Map<String, String> getTestInstrumentationRunnerArguments() {
- return mTestInstrumentationRunnerArguments;
- }
-
- @Nullable
- @Override
- public Boolean getTestHandleProfiling() {
- return mTestHandleProfiling;
- }
-
- @Nullable
- @Override
- public Boolean getTestFunctionalTest() {
- return mTestFunctionalTest;
- }
-
- @NonNull
- @Override
- public Collection<String> getResourceConfigurations() {
- return mResourceConfigurations;
- }
-
- @Nullable
- @Override
- public SigningConfig getSigningConfig() {
- return null;
- }
-
- @Nullable
- @Override
- public String getDimension() {
- return mDimension;
- }
-
- @Override
- public String toString() {
- return "ProductFlavorImpl{" +
- "name='" + name + '\'' +
- ", mDimension='" + mDimension + '\'' +
- ", mMinSdkVersion=" + mMinSdkVersion +
- ", mTargetSdkVersion=" + mTargetSdkVersion +
- ", mMaxSdkVersion=" + mMaxSdkVersion +
- ", mRenderscriptTargetApi=" + mRenderscriptTargetApi +
- ", mRenderscriptSupportMode=" + mRenderscriptSupportMode +
- ", mRenderscriptNdkMode=" + mRenderscriptNdkMode +
- ", mVersionCode=" + mVersionCode +
- ", mVersionName='" + mVersionName + '\'' +
- ", mApplicationId='" + mApplicationId + '\'' +
- ", mTestApplicationId='" + mTestApplicationId + '\'' +
- ", mTestInstrumentationRunner='" + mTestInstrumentationRunner + '\'' +
- ", mTestHandleProfiling=" + mTestHandleProfiling +
- ", mTestFunctionalTest=" + mTestFunctionalTest +
- ", mResourceConfigurations=" + mResourceConfigurations +
- "} " + super.toString();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueImpl.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueImpl.java
deleted file mode 100644
index c5caec3..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueImpl.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.SyncIssue;
-
-import java.io.Serializable;
-
-/**
- * An implementation of BaseConfig specifically for sending as part of the Android model
- * through the Gradle tooling API.
- */
-public class SyncIssueImpl implements SyncIssue, Serializable {
- private static final long serialVersionUID = 1L;
-
- private final int type;
- private final int severity;
- private final String data;
- private final String message;
-
- public SyncIssueImpl(int type, int severity, @NonNull String data, @NonNull String message) {
- this.type = type;
- this.severity = severity;
- this.data = data;
- this.message = message;
- }
-
- @Override
- public int getSeverity() {
- return severity;
- }
-
- @Override
- public int getType() {
- return type;
- }
-
- @NonNull
- @Override
- public String getData() {
- return data;
- }
-
- @NonNull
- @Override
- public String getMessage() {
- return message;
- }
-
- @Override
- public String toString() {
- return "SyncIssueImpl{" +
- "type=" + type +
- ", severity=" + severity +
- ", data='" + data + '\'' +
- ", message='" + message + '\'' +
- '}';
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueKey.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueKey.java
deleted file mode 100644
index 5ed4fc9..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueKey.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.model;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.SyncIssue;
-
-/**
- * Creates a key from a SyncIssue to use in a map.
- */
-public class SyncIssueKey {
-
- private final int type;
- @NonNull
- private final String data;
-
- public static SyncIssueKey from(@NonNull SyncIssue syncIssue) {
- return new SyncIssueKey(syncIssue.getType(), syncIssue.getData());
- }
-
- private SyncIssueKey(int type, @NonNull String data) {
- this.type = type;
- this.data = data;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- SyncIssueKey that = (SyncIssueKey) o;
-
- return type == that.type && data.equals(that.data);
- }
-
- @Override
- public int hashCode() {
- int result = type;
- result = 31 * result + data.hashCode();
- return result;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleJavaProcessExecutor.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleJavaProcessExecutor.java
deleted file mode 100644
index fff68d8..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleJavaProcessExecutor.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.process;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.process.JavaProcessExecutor;
-import com.android.ide.common.process.JavaProcessInfo;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessOutput;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.ide.common.process.ProcessResult;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.process.ExecResult;
-import org.gradle.process.JavaExecSpec;
-
-import java.io.File;
-
-/**
- * Implementation of JavaProcessExecutor that uses Gradle's mechanism to execute external java
- * processes.
- */
-public class GradleJavaProcessExecutor implements JavaProcessExecutor {
-
- @NonNull
- private final Project project;
-
- public GradleJavaProcessExecutor(@NonNull Project project) {
- this.project = project;
- }
-
- @NonNull
- @Override
- public ProcessResult execute(
- @NonNull JavaProcessInfo javaProcessInfo,
- @NonNull ProcessOutputHandler processOutputHandler) {
- ProcessOutput output = processOutputHandler.createOutput();
-
- ExecResult result = project.javaexec(new ExecAction(javaProcessInfo, output));
-
- try {
- processOutputHandler.handleOutput(output);
- } catch (ProcessException e) {
- return new OutputHandlerFailedGradleProcessResult(e);
- }
-
- return new GradleProcessResult(result);
- }
-
- private static class ExecAction implements Action<JavaExecSpec> {
-
- @NonNull
- private final JavaProcessInfo javaProcessInfo;
-
- @NonNull
- private final ProcessOutput processOutput;
-
- private ExecAction(@NonNull JavaProcessInfo javaProcessInfo,
- @NonNull ProcessOutput processOutput) {
- this.javaProcessInfo = javaProcessInfo;
- this.processOutput = processOutput;
- }
-
- @Override
- public void execute(JavaExecSpec javaExecSpec) {
- javaExecSpec.classpath(new File(javaProcessInfo.getClasspath()));
- javaExecSpec.setMain(javaProcessInfo.getMainClass());
- javaExecSpec.args(javaProcessInfo.getArgs());
- javaExecSpec.jvmArgs(javaProcessInfo.getJvmArgs());
- javaExecSpec.environment(javaProcessInfo.getEnvironment());
- javaExecSpec.setStandardOutput(processOutput.getStandardOutput());
- javaExecSpec.setErrorOutput(processOutput.getErrorOutput());
-
- // we want the caller to be able to do its own thing.
- javaExecSpec.setIgnoreExitValue(true);
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessResult.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessResult.java
deleted file mode 100644
index 35f57c3..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessResult.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.process;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessResult;
-
-import org.gradle.process.ExecResult;
-import org.gradle.process.internal.ExecException;
-
-/**
- */
-class GradleProcessResult implements ProcessResult {
-
- @NonNull
- private final ExecResult result;
-
- GradleProcessResult(@NonNull ExecResult result) {
- this.result = result;
- }
-
- @Override
- public ProcessResult assertNormalExitValue() throws ProcessException {
- try {
- result.assertNormalExitValue();
- } catch (ExecException e) {
- throw new ProcessException(e);
- }
-
- return this;
- }
-
- @Override
- public int getExitValue() {
- return result.getExitValue();
- }
-
- @Override
- public ProcessResult rethrowFailure() throws ProcessException {
- try {
- result.rethrowFailure();
- } catch (ExecException e) {
- throw new ProcessException(e);
- }
- return this;
- }
-}
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/OutputHandlerFailedGradleProcessResult.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/OutputHandlerFailedGradleProcessResult.java
deleted file mode 100644
index 575402c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/OutputHandlerFailedGradleProcessResult.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.process;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessResult;
-
-public class OutputHandlerFailedGradleProcessResult implements ProcessResult {
- @NonNull
- private final ProcessException failure;
-
- OutputHandlerFailedGradleProcessResult(@NonNull ProcessException failure) {
- this.failure = failure;
- }
-
- @Override
- public ProcessResult assertNormalExitValue() throws ProcessException {
- throw failure;
- }
-
- @Override
- public int getExitValue() {
- return -1;
- }
-
- @Override
- public ProcessResult rethrowFailure() throws ProcessException {
- throw failure;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/RecordingBuildListener.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/RecordingBuildListener.java
deleted file mode 100644
index 4195665..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/RecordingBuildListener.java
+++ /dev/null
@@ -1,108 +0,0 @@
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.profile;
-
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask;
-import com.android.builder.profile.ExecutionRecord;
-import com.android.builder.profile.ExecutionType;
-import com.android.builder.profile.Recorder;
-import com.google.common.base.CaseFormat;
-import org.gradle.api.Task;
-import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.tasks.TaskState;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Implementation of the {@link TaskExecutionListener} that records the execution span of
- * tasks execution and records such spans using the {@link Recorder} facilities.
- */
-public class RecordingBuildListener implements TaskExecutionListener {
-
- private static final class TaskRecord {
-
- private final long recordId;
- private final long startTime;
-
- TaskRecord(long recordId, long startTime) {
- this.startTime = startTime;
- this.recordId = recordId;
- }
- }
-
- private final Recorder mRecorder;
-
- public RecordingBuildListener(Recorder recorder) {
- mRecorder = recorder;
- }
-
- // map of outstanding tasks executing, keyed by their name.
- final Map<String, TaskRecord> taskRecords = new ConcurrentHashMap<String, TaskRecord>();
-
- @Override
- public void beforeExecute(Task task) {
- taskRecords.put(task.getName(), new TaskRecord(
- mRecorder.allocationRecordId(), System.currentTimeMillis()));
- }
-
- @Override
- public void afterExecute(Task task, TaskState taskState) {
-
- // find the right ExecutionType.
- String taskImpl = task.getClass().getSimpleName();
- if (taskImpl.endsWith("_Decorated")) {
- taskImpl = taskImpl.substring(0, taskImpl.length() - "_Decorated".length());
- }
- String potentialExecutionTypeName = "TASK_" +
- CaseFormat.LOWER_CAMEL.converterTo(CaseFormat.UPPER_UNDERSCORE).
- convert(taskImpl);
- ExecutionType executionType;
- try {
- executionType = ExecutionType.valueOf(potentialExecutionTypeName);
- } catch (IllegalArgumentException ignored) {
- executionType = ExecutionType.GENERIC_TASK_EXECUTION;
- }
-
- List<Recorder.Property> properties = new ArrayList<Recorder.Property>();
- properties.add(new Recorder.Property("project", task.getProject().getName()));
- properties.add(new Recorder.Property("task", task.getName()));
-
- if (task instanceof DefaultAndroidTask) {
- String variantName = ((DefaultAndroidTask) task).getVariantName();
- if (variantName == null) {
- throw new IllegalStateException("Task with type " + task.getClass().getName() +
- " does not include a variantName");
- }
- if (!variantName.isEmpty()) {
- properties.add(new Recorder.Property("variant", variantName));
- }
- }
-
- TaskRecord taskRecord = taskRecords.get(task.getName());
- mRecorder.closeRecord(new ExecutionRecord(
- taskRecord.recordId,
- 0 /* parentId */,
- taskRecord.startTime,
- System.currentTimeMillis() - taskRecord.startTime,
- executionType,
- properties));
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistence.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistence.java
deleted file mode 100644
index e729588..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistence.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.publishing;
-
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.tasks.SplitFileSupplier;
-import com.google.common.collect.ImmutableList;
-import com.google.common.reflect.TypeToken;
-import com.google.gson.Gson;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.Writer;
-import java.lang.reflect.Type;
-import java.util.List;
-
-/**
- * Persistence utility to save metadata about split files generated by the build. This metadata
- * will be the split type, the split identifier and the resulting split APK file name.
- */
-public class FilterDataPersistence {
-
- public static class Record {
- public final String filterType;
- public final String filterIdentifier;
- public final String splitFileName;
-
- private Record(String filterType, String filterIdentifier, String splitFileName) {
- this.filterType = filterType;
- this.filterIdentifier = filterIdentifier;
- this.splitFileName = splitFileName;
- }
- }
-
- public void persist(List<FileSupplier> fileSuppliers, Writer writer) throws IOException {
- Gson gson = new Gson();
- ImmutableList.Builder<Record> records = ImmutableList.builder();
- for (FileSupplier fileSupplier : fileSuppliers) {
- if (fileSupplier instanceof SplitFileSupplier) {
- records.add(new Record(
- ((SplitFileSupplier) fileSupplier).getFilterData().getFilterType(),
- ((SplitFileSupplier) fileSupplier).getFilterData().getIdentifier(),
- fileSupplier.get().getName()));
- }
- }
- String recordsAsString = gson.toJson(records.build());
- try {
- writer.append(recordsAsString);
- } finally {
- writer.close();
- }
- }
-
- public List<Record> load(Reader reader) throws IOException {
- Gson gson = new Gson();
- Type recordType = new TypeToken<List<Record>>() {}.getType();
- return gson.fromJson(reader, recordType);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTask.java
deleted file mode 100644
index db175a6..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTask.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.scope;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.TaskFactory;
-
-import org.gradle.api.Action;
-import org.gradle.api.Task;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Handle for a {@link Task} that may not yet been created.
- * For tasks created using ModelMap<Task>, they are usually not actually created until
- * Gradle decides those tasks needs to be executed. This class contains information about those
- * tasks and allow dependencies to be specified.
- */
-public class AndroidTask<T extends Task> {
- @NonNull
- private String name;
- @NonNull
- private final Class<T> taskType;
- @NonNull
- private final List<AndroidTask<? extends Task>> upstreamTasks;
- @NonNull
- private final List<AndroidTask<? extends Task>> downstreamTasks;
-
- public AndroidTask(@NonNull String name, @NonNull Class<T> taskType) {
- this.name = name;
- this.taskType = taskType;
- upstreamTasks = new ArrayList<AndroidTask<? extends Task>>();
- downstreamTasks = new ArrayList<AndroidTask<? extends Task>>();
- }
-
- /**
- * The name of Task this represents.
- */
- @NonNull
- public String getName() {
- return name;
- }
-
- /**
- * The type of Task this represents.
- */
- @NonNull
- public Class<T> getTaskType() {
- return taskType;
- }
-
- /**
- * Return all the AndroidTask this depends on.
- */
- @NonNull
- public List<AndroidTask<? extends Task>> getUpstreamTasks() {
- return upstreamTasks;
- }
-
- /**
- * Return all the AndroidTask that depends on this.
- */
- @NonNull
- public List<AndroidTask<? extends Task>> getDownstreamTasks() {
- return downstreamTasks;
- }
-
- /**
- * Add dependency on another AndroidTask.
- * @param taskFactory TaskFactory used to configure the task for dependencies.
- * @param other The task that this depends on.
- */
- public void dependsOn(final TaskFactory taskFactory, final AndroidTask<?> other) {
- taskFactory.named(name, new Action<Task>() {
- @Override
- public void execute(Task task) {
- task.dependsOn(other.name);
- }
- });
- upstreamTasks.add(other);
- other.addDependent(this);
- }
-
- /**
- * Add dependency on objects.
- * This method adds dependencies on any objects accepted by {@link Task#dependsOn} and is
- * needed for compatibility until all tasks are trasitioned to AndroidTask.
- * @param taskFactory TaskFactory used to configure the task for dependencies.
- * @param dependencies Objects accepted by {@link Task#dependsOn}.
- */
- public void dependsOn(final TaskFactory taskFactory, final Object... dependencies) {
- taskFactory.named(name, new Action<Task>() {
- @Override
- public void execute(Task task) {
- for (Object dependency : dependencies) {
- if (dependency instanceof AndroidTask) {
- task.dependsOn(((AndroidTask) dependency).getName());
- } else {
- task.dependsOn(dependency);
- }
- }
- }
- });
- }
-
- /**
- * Add dependency on other objects if the object is not null.
- * This method adds dependencies on any objects accepted by {@link Task#dependsOn} and is
- * needed for compatibility until all tasks are trasitioned to AndroidTask.
- * @param taskFactory TaskFactory used to configure the task for dependencies.
- * @param dependencies Objects accepted by {@link Task#dependsOn}.
- */
- public void optionalDependsOn(final TaskFactory taskFactory, final Object... dependencies) {
- for (Object dependency : dependencies) {
- if (dependency != null) {
- if (dependency instanceof AndroidTask) {
- dependsOn(taskFactory, ((AndroidTask) dependency).getName());
- } else {
- dependsOn(taskFactory, dependency);
- }
- }
- }
- }
-
-
- public void optionalDependsOn(final TaskFactory taskFactory, @NonNull List<?> dependencies) {
- for (Object dependency : dependencies) {
- if (dependency != null) {
- dependsOn(taskFactory, dependency);
- }
- }
- }
-
- private void addDependent(AndroidTask<? extends Task> tAndroidTask) {
- downstreamTasks.add(tAndroidTask);
- }
-
- /**
- * Add a configuration action for this task.
- * @param taskFactory TaskFactory used to configure the task.
- * @param configAction An Action to be executed.
- */
- public void configure(TaskFactory taskFactory, Action<? super Task> configAction) {
- taskFactory.named(name, configAction);
- }
-
- /**
- * Potentially instantiates and return the task. Should only be called once the task is
- * configured.
- * @param taskFactory the factory for tasks
- * @return the task instance.
- */
- @SuppressWarnings("unchecked")
- public T get(TaskFactory taskFactory) {
- return (T) taskFactory.named(name);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTaskRegistry.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTaskRegistry.java
deleted file mode 100644
index c20330d..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTaskRegistry.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.scope;
-
-import com.android.build.gradle.internal.TaskFactory;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.Task;
-import org.gradle.api.internal.ClosureBackedAction;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import groovy.lang.Closure;
-
-/**
- * Registry for creating and storing AndroidTask.
- */
-public class AndroidTaskRegistry {
-
- private final Map<String, AndroidTask> tasks = new HashMap<String, AndroidTask>();
-
- public synchronized < T extends Task> AndroidTask<T> create(
- TaskFactory taskFactory,
- String taskName,
- Class<T> taskClass,
- Action<T> configAction) {
-
- taskFactory.create(taskName, taskClass, configAction);
- final AndroidTask<T> newTask = new AndroidTask<T>(taskName, taskClass);
- tasks.put(taskName, newTask);
-
- return newTask;
- }
-
- public synchronized AndroidTask<Task> create(
- TaskFactory taskFactory,
- String taskName,
- Closure configAction) {
-
- taskFactory.create(taskName, DefaultTask.class, new ClosureBackedAction<Task>(configAction));
- final AndroidTask<Task> newTask = new AndroidTask<Task>(taskName, Task.class);
- tasks.put(taskName, newTask);
-
- return newTask;
- }
-
- public synchronized <T extends Task> AndroidTask<T> create(
- TaskFactory taskFactory,
- String taskName,
- Class<T> taskClass,
- Closure configAction) {
-
- taskFactory.create(taskName, taskClass, new ClosureBackedAction<T>(configAction));
- final AndroidTask<T> newTask = new AndroidTask<T>(taskName, taskClass);
- tasks.put(taskName, newTask);
-
- return newTask;
- }
-
- public <T extends Task> AndroidTask<T> create(
- TaskFactory taskFactory,
- TaskConfigAction<T> configAction) {
- return create(taskFactory, configAction.getName(), configAction.getType(), configAction);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/ConventionMappingHelper.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/ConventionMappingHelper.groovy
deleted file mode 100644
index 92824c2..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/ConventionMappingHelper.groovy
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.scope
-
-import org.gradle.api.Task
-
-import java.util.concurrent.Callable
-
-/**
- * Helper class to dynamically access conventionMapping of a task.
- */
-class ConventionMappingHelper {
- static void map(Task task, String key, Closure<?> value) {
- task.conventionMapping.map(key, value);
- }
-
- static void map(Task task, String key, Callable<?> value) {
- task.conventionMapping.map(key, value);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/GlobalScope.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/GlobalScope.java
deleted file mode 100644
index 9e5ac12..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/GlobalScope.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.scope;
-
-import static com.android.builder.core.BuilderConstants.FD_REPORTS;
-import static com.android.builder.model.AndroidProject.FD_GENERATED;
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.AndroidGradleOptions;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.builder.core.AndroidBuilder;
-import com.google.common.base.Objects;
-
-import org.gradle.api.Project;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.io.File;
-
-/**
- * A scope containing data for the Android plugin.
- */
-public class GlobalScope {
- @NonNull
- private Project project;
- @NonNull
- private AndroidBuilder androidBuilder;
- @NonNull
- private String projectBaseName;
- @NonNull
- private AndroidConfig extension;
- @NonNull
- private SdkHandler sdkHandler;
- @NonNull
- private ToolingModelBuilderRegistry toolingRegistry;
-
- @NonNull
- private final File intermediatesDir;
- @NonNull
- private final File generatedDir;
- @NonNull
- private final File reportsDir;
- @NonNull
- private final File outputsDir;
-
- public GlobalScope(
- @NonNull Project project,
- @NonNull AndroidBuilder androidBuilder,
- @NonNull String projectBaseName,
- @NonNull AndroidConfig extension,
- @NonNull SdkHandler sdkHandler,
- @NonNull ToolingModelBuilderRegistry toolingRegistry) {
- this.project = project;
- this.androidBuilder = androidBuilder;
- this.projectBaseName = projectBaseName;
- this.extension = extension;
- this.sdkHandler = sdkHandler;
- this.toolingRegistry = toolingRegistry;
- intermediatesDir = new File(getBuildDir(), FD_INTERMEDIATES);
- generatedDir = new File(getBuildDir(), FD_GENERATED);
- reportsDir = new File(getBuildDir(), FD_REPORTS);
- outputsDir = new File(getBuildDir(), FD_OUTPUTS);
- }
-
- @NonNull
- public Project getProject() {
- return project;
- }
-
- @NonNull
- public AndroidConfig getExtension() {
- return extension;
- }
-
- @NonNull
- public AndroidBuilder getAndroidBuilder() {
- return androidBuilder;
- }
-
- @NonNull
- public String getProjectBaseName() {
- return projectBaseName;
- }
-
- @NonNull
- public SdkHandler getSdkHandler() {
- return sdkHandler;
- }
-
- @NonNull
- public ToolingModelBuilderRegistry getToolingRegistry() {
- return toolingRegistry;
- }
-
- @NonNull
- public File getBuildDir() {
- return project.getBuildDir();
- }
-
- @NonNull
- public File getIntermediatesDir() {
- return intermediatesDir;
- }
-
- @NonNull
- public File getGeneratedDir() {
- return generatedDir;
- }
-
- @NonNull
- public File getReportsDir() {
- return reportsDir;
- }
-
- @NonNull
- public File getOutputsDir() {
- return outputsDir;
- }
-
- @NonNull
- public String getDefaultApkLocation() {
- return getBuildDir() + "/" + FD_OUTPUTS + "/apk";
- }
-
- @NonNull
- public String getApkLocation() {
- return Objects.firstNonNull(
- AndroidGradleOptions.getApkLocation(project),
- getDefaultApkLocation());
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/TaskConfigAction.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/TaskConfigAction.java
deleted file mode 100644
index e0e5e38..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/TaskConfigAction.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.scope;
-
-import org.gradle.api.Action;
-
-/**
- * Interface of Task configuration Actions.
- */
-public interface TaskConfigAction<T> extends Action<T> {
-
- /**
- * Return the name of the task to be configured.
- */
- String getName();
-
- /**
- * Return the class type of the task to be configured.
- */
- Class<T> getType();
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantOutputScope.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantOutputScope.java
deleted file mode 100644
index dd897be..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantOutputScope.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.scope;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.tasks.CompatibleScreensManifest;
-import com.android.build.gradle.tasks.ManifestProcessorTask;
-import com.android.build.gradle.tasks.ProcessAndroidResources;
-import com.android.utils.StringHelper;
-
-import java.io.File;
-
-/**
- * A scope containing data for a specific variant.
- */
-public class VariantOutputScope {
-
- @NonNull
- private VariantScope variantScope;
- @NonNull
- private BaseVariantOutputData variantOutputData;
-
- // Tasks
- private AndroidTask<CompatibleScreensManifest> compatibleScreensManifestTask;
-
- private AndroidTask<? extends ManifestProcessorTask> manifestProcessorTask;
-
- private AndroidTask<ProcessAndroidResources> processResourcesTask;
-
- public VariantOutputScope(
- @NonNull VariantScope variantScope,
- @NonNull BaseVariantOutputData variantOutputData) {
- this.variantScope = variantScope;
- this.variantOutputData = variantOutputData;
- }
-
- @NonNull
- public GlobalScope getGlobalScope() {
- return variantScope.getGlobalScope();
- }
-
- @NonNull
- public VariantScope getVariantScope() {
- return variantScope;
- }
-
- @NonNull
- public BaseVariantOutputData getVariantOutputData() {
- return variantOutputData;
- }
-
- @NonNull
- public String getTaskName(@NonNull String prefix) {
- return getTaskName(prefix, "");
- }
-
- @NonNull
- public String getTaskName(@NonNull String prefix, @NonNull String suffix) {
- return prefix + StringHelper.capitalize(getVariantOutputData().getFullName()) + suffix;
- }
-
- @NonNull
- public File getPackageApk() {
- ApkVariantData apkVariantData = (ApkVariantData) variantScope.getVariantData();
-
- boolean signedApk = apkVariantData.isSigned();
- String apkName = signedApk ?
- getGlobalScope().getProjectBaseName() + "-" + variantOutputData.getBaseName() + "-unaligned.apk" :
- getGlobalScope().getProjectBaseName() + "-" + variantOutputData.getBaseName() + "-unsigned.apk";
-
- // if this is the final task then the location is
- // the potentially overridden one.
- if (!signedApk || !apkVariantData.getZipAlignEnabled()) {
- return getGlobalScope().getProject().file(
- getGlobalScope().getApkLocation() + "/" + apkName);
- } else {
- // otherwise default one.
- return getGlobalScope().getProject().file(getGlobalScope().getDefaultApkLocation() + "/" + apkName);
- }
- }
-
- @NonNull
- public File getCompressedResourceFile() {
- return new File(getGlobalScope().getIntermediatesDir(), "/res/" +
- "resources-" + variantOutputData.getBaseName() + "-stripped.ap_");
- }
-
- @NonNull
- public File getCompatibleScreensManifestFile() {
- return new File(getGlobalScope().getIntermediatesDir(),
- "/manifests/density/" + variantOutputData.getDirName() + "/AndroidManifest.xml");
-
- }
-
- @NonNull
- public File getManifestOutputFile() {
- switch(variantScope.getVariantConfiguration().getType()) {
- case DEFAULT:
- return new File(getGlobalScope().getIntermediatesDir(),
- "/manifests/full/" + variantOutputData.getDirName()
- + "/AndroidManifest.xml");
- case LIBRARY:
- return new File(getGlobalScope().getIntermediatesDir(),
- TaskManager.DIR_BUNDLES + "/"
- + getVariantScope().getVariantConfiguration().getDirName()
- + "/AndroidManifest.xml");
- case ANDROID_TEST:
- return new File(getGlobalScope().getIntermediatesDir(),
- "manifest/" + variantScope.getVariantConfiguration().getDirName()
- + "/AndroidManifest.xml");
- default:
- throw new RuntimeException(
- "getManifestOutputFile called for an unexpected variant.");
- }
- }
-
- @NonNull
- public File getProcessResourcePackageOutputFile() {
- return new File(getGlobalScope().getIntermediatesDir(),
- "res/resources-" + variantOutputData.getBaseName() + ".ap_");
- }
-
- // Tasks
- @Nullable
- public AndroidTask<CompatibleScreensManifest> getCompatibleScreensManifestTask() {
- return compatibleScreensManifestTask;
- }
-
- public void setCompatibleScreensManifestTask(
- @Nullable AndroidTask<CompatibleScreensManifest> compatibleScreensManifestTask) {
- this.compatibleScreensManifestTask = compatibleScreensManifestTask;
- }
-
- public AndroidTask<? extends ManifestProcessorTask> getManifestProcessorTask() {
- return manifestProcessorTask;
- }
-
- public void setManifestProcessorTask(
- AndroidTask<? extends ManifestProcessorTask> manifestProcessorTask) {
- this.manifestProcessorTask = manifestProcessorTask;
- }
-
- public AndroidTask<ProcessAndroidResources> getProcessResourcesTask() {
- return processResourcesTask;
- }
-
- public void setProcessResourcesTask(
- AndroidTask<ProcessAndroidResources> processResourcesTask) {
- this.processResourcesTask = processResourcesTask;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScope.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScope.java
deleted file mode 100644
index 5bcc4f8..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScope.java
+++ /dev/null
@@ -1,723 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.scope;
-
-import static com.android.build.gradle.internal.TaskManager.DIR_BUNDLES;
-import static com.android.builder.model.AndroidProject.FD_GENERATED;
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.coverage.JacocoInstrumentTask;
-import com.android.build.gradle.internal.tasks.CheckManifest;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.tasks.MergeJavaResourcesTask;
-import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.internal.variant.LibraryVariantData;
-import com.android.build.gradle.internal.variant.TestVariantData;
-import com.android.build.gradle.tasks.AidlCompile;
-import com.android.build.gradle.tasks.BinaryFileProviderTask;
-import com.android.build.gradle.tasks.Dex;
-import com.android.build.gradle.tasks.GenerateBuildConfig;
-import com.android.build.gradle.tasks.GenerateResValues;
-import com.android.build.gradle.tasks.JackTask;
-import com.android.build.gradle.tasks.JavaResourcesProvider;
-import com.android.build.gradle.tasks.MergeAssets;
-import com.android.build.gradle.tasks.MergeResources;
-import com.android.build.gradle.tasks.NdkCompile;
-import com.android.build.gradle.tasks.ProcessAndroidResources;
-import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.builder.core.VariantConfiguration;
-import com.android.builder.core.VariantType;
-import com.android.builder.signing.SignedJarBuilder;
-import com.android.utils.FileUtils;
-import com.android.utils.StringHelper;
-import com.google.common.base.Objects;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.Task;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.tasks.Sync;
-import org.gradle.api.tasks.compile.AbstractCompile;
-import org.gradle.api.tasks.compile.JavaCompile;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A scope containing data for a specific variant.
- */
-public class VariantScope {
-
- @NonNull
- private GlobalScope globalScope;
- @NonNull
- private BaseVariantData<? extends BaseVariantOutputData> variantData;
-
- @Nullable
- private Collection<Object> ndkBuildable;
- @Nullable
- private Collection<File> ndkSoFolder;
- @Nullable
- private File ndkObjFolder;
- @NonNull
- private Map<Abi, File> ndkDebuggableLibraryFolders = Maps.newHashMap();
-
- @Nullable
- private File mergeResourceOutputDir;
-
- // Tasks
- private AndroidTask<Task> preBuildTask;
- private AndroidTask<PrepareDependenciesTask> prepareDependenciesTask;
- private AndroidTask<ProcessAndroidResources> generateRClassTask;
-
- private AndroidTask<Task> sourceGenTask;
- private AndroidTask<Task> resourceGenTask;
- private AndroidTask<Task> assetGenTask;
- private AndroidTask<CheckManifest> checkManifestTask;
-
- private AndroidTask<RenderscriptCompile> renderscriptCompileTask;
- private AndroidTask<AidlCompile> aidlCompileTask;
- @Nullable
- private AndroidTask<MergeResources> mergeResourcesTask;
- @Nullable
- private AndroidTask<MergeAssets> mergeAssetsTask;
- private AndroidTask<GenerateBuildConfig> generateBuildConfigTask;
- private AndroidTask<GenerateResValues> generateResValuesTask;
-
- @Nullable
- private AndroidTask<Dex> dexTask;
- @Nullable
- private AndroidTask jacocoIntrumentTask;
-
- private AndroidTask<Sync> processJavaResourcesTask;
- private AndroidTask<MergeJavaResourcesTask> mergeJavaResourcesTask;
- private JavaResourcesProvider javaResourcesProvider;
- private AndroidTask<NdkCompile> ndkCompileTask;
-
- /** @see BaseVariantData#javaCompilerTask */
- @Nullable
- private AndroidTask<? extends AbstractCompile> javaCompilerTask;
- @Nullable
- private AndroidTask<JavaCompile> javacTask;
- @Nullable
- private AndroidTask<JackTask> jackTask;
-
- // empty anchor compile task to set all compilations tasks as dependents.
- private AndroidTask<Task> compileTask;
- private AndroidTask<JacocoInstrumentTask> jacocoInstrumentTask;
-
- private FileSupplier mappingFileProviderTask;
- private AndroidTask<BinaryFileProviderTask> binayFileProviderTask;
-
- // TODO : why is Jack not registered as the obfuscationTask ???
- private AndroidTask<? extends Task> obfuscationTask;
-
- private File resourceOutputDir;
-
-
- public VariantScope(
- @NonNull GlobalScope globalScope,
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
- this.globalScope = globalScope;
- this.variantData = variantData;
- }
-
- @NonNull
- public GlobalScope getGlobalScope() {
- return globalScope;
- }
-
- @NonNull
- public BaseVariantData<? extends BaseVariantOutputData> getVariantData() {
- return variantData;
- }
-
- @NonNull
- public GradleVariantConfiguration getVariantConfiguration() {
- return variantData.getVariantConfiguration();
- }
-
- @NonNull
- public String getTaskName(@NonNull String prefix) {
- return getTaskName(prefix, "");
- }
-
- @NonNull
- public String getTaskName(@NonNull String prefix, @NonNull String suffix) {
- return prefix + StringHelper.capitalize(getVariantConfiguration().getFullName()) + suffix;
- }
-
- @Nullable
- public Collection<Object> getNdkBuildable() {
- return ndkBuildable;
- }
-
- public void setNdkBuildable(@NonNull Collection<Object> ndkBuildable) {
- this.ndkBuildable = ndkBuildable;
- }
-
- @Nullable
- public Collection<File> getNdkSoFolder() {
- return ndkSoFolder;
- }
-
- public void setNdkSoFolder(@NonNull Collection<File> ndkSoFolder) {
- this.ndkSoFolder = ndkSoFolder;
- }
-
- @Nullable
- public File getNdkObjFolder() {
- return ndkObjFolder;
- }
-
- public void setNdkObjFolder(@NonNull File ndkObjFolder) {
- this.ndkObjFolder = ndkObjFolder;
- }
-
- /**
- * Return the folder containing the shared object with debugging symbol for the specified ABI.
- */
- @Nullable
- public File getNdkDebuggableLibraryFolders(@NonNull Abi abi) {
- return ndkDebuggableLibraryFolders.get(abi);
- }
-
- public void addNdkDebuggableLibraryFolders(@NonNull Abi abi, @NonNull File searchPath) {
- this.ndkDebuggableLibraryFolders.put(abi, searchPath);
- }
-
- @NonNull
- public Set<File> getJniFolders() {
- assert getNdkSoFolder() != null;
-
- VariantConfiguration config = getVariantConfiguration();
- ApkVariantData apkVariantData = (ApkVariantData) variantData;
- // for now only the project's compilation output.
- Set<File> set = Sets.newHashSet();
- set.addAll(getNdkSoFolder());
- set.add(getRenderscriptLibOutputDir());
- set.addAll(config.getLibraryJniFolders());
- set.addAll(config.getJniLibsList());
-
- if (config.getMergedFlavor().getRenderscriptSupportModeEnabled() != null &&
- config.getMergedFlavor().getRenderscriptSupportModeEnabled()) {
- File rsLibs = globalScope.getAndroidBuilder().getSupportNativeLibFolder();
- if (rsLibs != null && rsLibs.isDirectory()) {
- set.add(rsLibs);
- }
- }
- return set;
- }
-
- @Nullable
- public BaseVariantData getTestedVariantData() {
- return variantData instanceof TestVariantData ?
- (BaseVariantData) ((TestVariantData) variantData).getTestedVariantData() :
- null;
- }
-
-
- // Precomputed file paths.
-
- @NonNull
- public File getDexOutputFolder() {
- return new File(globalScope.getIntermediatesDir(), "/dex/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public FileCollection getJavaClasspath() {
- return getGlobalScope().getProject().files(
- getGlobalScope().getAndroidBuilder().getCompileClasspath(
- getVariantData().getVariantConfiguration()));
- }
-
- @NonNull
- public File getJavaOutputDir() {
- return new File(globalScope.getIntermediatesDir(), "/classes/" +
- variantData.getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getJavaDependencyCache() {
- return new File(globalScope.getIntermediatesDir(), "/dependency-cache/" +
- variantData.getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getPreDexOutputDir() {
- return new File(globalScope.getIntermediatesDir(), "/pre-dexed/" +
- getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getProguardOutputFile() {
- return (variantData instanceof LibraryVariantData) ?
- new File(globalScope.getIntermediatesDir(),
- DIR_BUNDLES + "/" + getVariantConfiguration().getDirName()
- + "/classes.jar") :
- new File(globalScope.getIntermediatesDir(),
- "/classes-proguard/" + getVariantConfiguration().getDirName()
- + "/classes.jar");
- }
-
- @NonNull
- public File getProguardComponentsJarFile() {
- return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
- + "/componentClasses.jar");
- }
-
- @NonNull
- public File getJarMergingOutputFile() {
- return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
- + "/allclasses.jar");
- }
-
- @NonNull
- public File getManifestKeepListFile() {
- return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
- + "/manifest_keep.txt");
- }
-
- @NonNull
- public File getMainDexListFile() {
- return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
- + "/maindexlist.txt");
- }
-
- @NonNull
- public File getRenderscriptSourceOutputDir() {
- return new File(globalScope.getGeneratedDir(),
- "source/rs/" + variantData.getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getRenderscriptLibOutputDir() {
- return new File(globalScope.getGeneratedDir(),
- "rs/" + variantData.getVariantConfiguration().getDirName() + "/lib");
- }
-
- @NonNull
- public File getSymbolLocation() {
- return new File(globalScope.getIntermediatesDir() + "/symbols/" +
- variantData.getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getFinalResourcesDir() {
- return Objects.firstNonNull(resourceOutputDir, getDefaultMergeResourcesOutputDir());
- }
-
- public void setResourceOutputDir(@NonNull File resourceOutputDir) {
- this.resourceOutputDir = resourceOutputDir;
- }
-
- @NonNull
- public File getDefaultMergeResourcesOutputDir() {
- return new File(globalScope.getIntermediatesDir(),
- "/res/merged/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getMergeResourcesOutputDir() {
- if (mergeResourceOutputDir == null) {
- return getDefaultMergeResourcesOutputDir();
- }
- return mergeResourceOutputDir;
- }
-
- public void setMergeResourceOutputDir(@Nullable File mergeResourceOutputDir) {
- this.mergeResourceOutputDir = mergeResourceOutputDir;
- }
-
- @NonNull
- public File getMergeAssetsOutputDir() {
- return getVariantConfiguration().getType() == VariantType.LIBRARY ?
- new File(globalScope.getIntermediatesDir(),
- DIR_BUNDLES + "/" + getVariantConfiguration().getDirName() +
- "/assets") :
- new File(globalScope.getIntermediatesDir(),
- "/assets/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getBuildConfigSourceOutputDir() {
- return new File(globalScope.getBuildDir() + "/" + FD_GENERATED + "/source/buildConfig/"
- + variantData.getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getGeneratedResourcesDir(String name) {
- return FileUtils.join(
- globalScope.getGeneratedDir(),
- "res",
- name,
- getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getGeneratedResOutputDir() {
- return getGeneratedResourcesDir("resValues");
- }
-
- @NonNull
- public File getGeneratedPngsOutputDir() {
- return getGeneratedResourcesDir("pngs");
- }
-
- @NonNull
- public File getRenderscriptResOutputDir() {
- return getGeneratedResourcesDir("rs");
- }
-
- @NonNull
- public File getPackagedJarsJavaResDestinationDir() {
- return new File(globalScope.getIntermediatesDir(),
- "packagedJarsJavaResources/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getSourceFoldersJavaResDestinationDir() {
- return new File(globalScope.getIntermediatesDir(),
- "sourceFolderJavaResources/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getJavaResourcesDestinationDir() {
- return new File(globalScope.getIntermediatesDir(),
- "javaResources/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getRClassSourceOutputDir() {
- return new File(globalScope.getGeneratedDir(),
- "source/r/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getAidlSourceOutputDir() {
- return new File(globalScope.getGeneratedDir(),
- "source/aidl/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getAidlIncrementalDir() {
- return new File(globalScope.getIntermediatesDir(),
- "incremental/aidl/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getAidlParcelableDir() {
- return new File(globalScope.getIntermediatesDir(),
- DIR_BUNDLES + "/" + getVariantConfiguration().getDirName() + "/aidl");
- }
-
- /**
- * Returns the location of an intermediate directory that can be used by the Jack toolchain
- * to store states necessary to support incremental compilation.
- * @return a variant specific directory.
- */
- @NonNull
- public File getJackIncrementalDir() {
- return new File(globalScope.getIntermediatesDir(),
- "incremental/jack/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getJackTempDir() {
- return new File(globalScope.getIntermediatesDir(),
- "tmp/jack/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getJillPackagedLibrariesDir() {
- return new File(globalScope.getIntermediatesDir(),
- "jill/" + getVariantConfiguration().getDirName() + "/packaged");
- }
-
- @NonNull
- public File getJillRuntimeLibrariesDir() {
- return new File(globalScope.getIntermediatesDir(),
- "jill/" + getVariantConfiguration().getDirName() + "/runtime");
- }
-
- @NonNull
- public File getJackDestinationDir() {
- return new File(globalScope.getIntermediatesDir(),
- "dex/" + getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getJackClassesZip() {
- return new File(globalScope.getIntermediatesDir(),
- "packaged/" + getVariantConfiguration().getDirName() + "/classes.zip");
- }
-
- @NonNull
- public File getProguardOutputFolder() {
- return new File(globalScope.getBuildDir(), "/" + FD_OUTPUTS + "/mapping/" +
- getVariantConfiguration().getDirName());
- }
-
- @NonNull
- public File getProcessAndroidResourcesProguardOutputFile() {
- return new File(globalScope.getIntermediatesDir(),
- "/proguard-rules/" + getVariantConfiguration().getDirName() + "/aapt_rules.txt");
- }
-
- public File getMappingFile() {
- return new File(globalScope.getOutputsDir(),
- "/mapping/" + getVariantConfiguration().getDirName() + "/mapping.txt");
- }
-
- // Tasks getters/setters.
-
- public AndroidTask<Task> getPreBuildTask() {
- return preBuildTask;
- }
-
- public void setPreBuildTask(
- AndroidTask<Task> preBuildTask) {
- this.preBuildTask = preBuildTask;
- }
-
- public AndroidTask<PrepareDependenciesTask> getPrepareDependenciesTask() {
- return prepareDependenciesTask;
- }
-
- public void setPrepareDependenciesTask(
- AndroidTask<PrepareDependenciesTask> prepareDependenciesTask) {
- this.prepareDependenciesTask = prepareDependenciesTask;
- }
-
- public AndroidTask<ProcessAndroidResources> getGenerateRClassTask() {
- return generateRClassTask;
- }
-
- public void setGenerateRClassTask(
- AndroidTask<ProcessAndroidResources> generateRClassTask) {
- this.generateRClassTask = generateRClassTask;
- }
-
- public AndroidTask<Task> getSourceGenTask() {
- return sourceGenTask;
- }
-
- public void setSourceGenTask(
- AndroidTask<Task> sourceGenTask) {
- this.sourceGenTask = sourceGenTask;
- }
-
- public AndroidTask<Task> getResourceGenTask() {
- return resourceGenTask;
- }
-
- public void setResourceGenTask(
- AndroidTask<Task> resourceGenTask) {
- this.resourceGenTask = resourceGenTask;
- }
-
- public AndroidTask<Task> getAssetGenTask() {
- return assetGenTask;
- }
-
- public void setAssetGenTask(
- AndroidTask<Task> assetGenTask) {
- this.assetGenTask = assetGenTask;
- }
-
- public AndroidTask<CheckManifest> getCheckManifestTask() {
- return checkManifestTask;
- }
-
- public void setCheckManifestTask(
- AndroidTask<CheckManifest> checkManifestTask) {
- this.checkManifestTask = checkManifestTask;
- }
-
- public AndroidTask<RenderscriptCompile> getRenderscriptCompileTask() {
- return renderscriptCompileTask;
- }
-
- public void setRenderscriptCompileTask(
- AndroidTask<RenderscriptCompile> renderscriptCompileTask) {
- this.renderscriptCompileTask = renderscriptCompileTask;
- }
-
- public AndroidTask<AidlCompile> getAidlCompileTask() {
- return aidlCompileTask;
- }
-
- public void setAidlCompileTask(
- AndroidTask<AidlCompile> aidlCompileTask) {
- this.aidlCompileTask = aidlCompileTask;
- }
-
- @Nullable
- public AndroidTask<MergeResources> getMergeResourcesTask() {
- return mergeResourcesTask;
- }
-
- public void setMergeResourcesTask(
- @Nullable AndroidTask<MergeResources> mergeResourcesTask) {
- this.mergeResourcesTask = mergeResourcesTask;
- }
-
- @Nullable
- public AndroidTask<MergeAssets> getMergeAssetsTask() {
- return mergeAssetsTask;
- }
-
- public void setMergeAssetsTask(
- @Nullable AndroidTask<MergeAssets> mergeAssetsTask) {
- this.mergeAssetsTask = mergeAssetsTask;
- }
-
- public AndroidTask<GenerateBuildConfig> getGenerateBuildConfigTask() {
- return generateBuildConfigTask;
- }
-
- public void setGenerateBuildConfigTask(
- AndroidTask<GenerateBuildConfig> generateBuildConfigTask) {
- this.generateBuildConfigTask = generateBuildConfigTask;
- }
-
- public AndroidTask<GenerateResValues> getGenerateResValuesTask() {
- return generateResValuesTask;
- }
-
- public void setGenerateResValuesTask(
- AndroidTask<GenerateResValues> generateResValuesTask) {
- this.generateResValuesTask = generateResValuesTask;
- }
-
- @Nullable
- public AndroidTask<Dex> getDexTask() {
- return dexTask;
- }
-
- public void setDexTask(@Nullable AndroidTask<Dex> dexTask) {
- this.dexTask = dexTask;
- }
-
- public AndroidTask<Sync> getProcessJavaResourcesTask() {
- return processJavaResourcesTask;
- }
-
- public void setProcessJavaResourcesTask(
- AndroidTask<Sync> processJavaResourcesTask) {
- this.processJavaResourcesTask = processJavaResourcesTask;
- }
-
- SignedJarBuilder.IZipEntryFilter packagingOptionsFilter;
-
- public void setPackagingOptionsFilter(SignedJarBuilder.IZipEntryFilter filter) {
- this.packagingOptionsFilter = filter;
- }
-
- /**
- * Returns the {@link SignedJarBuilder.IZipEntryFilter} instance
- * that manages all resources inclusion in the final APK following the rules defined in
- * {@link com.android.builder.model.PackagingOptions} settings.
- */
- public SignedJarBuilder.IZipEntryFilter getPackagingOptionsFilter() {
- return packagingOptionsFilter;
- }
-
- public void setMergeJavaResourcesTask(AndroidTask<MergeJavaResourcesTask> mergeJavaResourcesTask) {
- this.mergeJavaResourcesTask = mergeJavaResourcesTask;
- }
-
- /**
- * Returns the task extracting java resources from libraries and merging those with java
- * resources coming from the variant's source folders.
- * @return the task merging resources.
- */
- public AndroidTask<MergeJavaResourcesTask> getMergeJavaResourcesTask() {
- return mergeJavaResourcesTask;
- }
-
- public void setJavaResourcesProvider(JavaResourcesProvider javaResourcesProvider) {
- this.javaResourcesProvider = javaResourcesProvider;
- }
-
- /**
- * Returns the {@link JavaResourcesProvider} responsible for providing final merged and possibly
- * obfuscated java resources for inclusion in the final APK. The provider might change during
- * the variant build process.
- * @return the java resources provider.
- */
- public JavaResourcesProvider getJavaResourcesProvider() {
- return javaResourcesProvider;
- }
-
- @Nullable
- public AndroidTask<? extends AbstractCompile> getJavaCompilerTask() {
- return javaCompilerTask;
- }
-
- @Nullable
- public AndroidTask<JackTask> getJackTask() {
- return jackTask;
- }
-
- public void setJackTask(
- @Nullable AndroidTask<JackTask> jackTask) {
- this.jackTask = jackTask;
- }
-
- @Nullable
- public AndroidTask<JavaCompile> getJavacTask() {
- return javacTask;
- }
-
- public void setJavacTask(
- @Nullable AndroidTask<JavaCompile> javacTask) {
- this.javacTask = javacTask;
- }
-
- public void setJavaCompilerTask(
- @NonNull AndroidTask<? extends AbstractCompile> javaCompileTask) {
- this.javaCompilerTask = javaCompileTask;
- }
-
- public AndroidTask<Task> getCompileTask() {
- return compileTask;
- }
-
- public void setCompileTask(
- AndroidTask<Task> compileTask) {
- this.compileTask = compileTask;
- }
-
- public AndroidTask<? extends Task> getObfuscationTask() {
- return obfuscationTask;
- }
-
- public void setObfuscationTask(
- AndroidTask<? extends Task> obfuscationTask) {
- this.obfuscationTask = obfuscationTask;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java
deleted file mode 100644
index 38bb68c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import static com.android.utils.FileUtils.copyFile;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.test.report.ReportType;
-import com.android.build.gradle.internal.test.report.TestReport;
-import com.android.utils.FileUtils;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.logging.ConsoleRenderer;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Task doing test report aggregation.
- */
-
-public class AndroidReportTask extends BaseTask implements AndroidTestTask {
-
- private final List<AndroidTestTask> subTasks = Lists.newArrayList();
-
- private ReportType reportType;
-
- private boolean ignoreFailures;
-
- private boolean testFailed;
-
- private File reportsDir;
-
- private File resultsDir;
-
-
- @OutputDirectory
- public File getReportsDir() {
- return reportsDir;
- }
-
- public void setReportsDir(@NonNull File reportsDir) {
- this.reportsDir = reportsDir;
- }
-
- @Override
- @OutputDirectory
- public File getResultsDir() {
- return resultsDir;
- }
-
- public void setResultsDir(@NonNull File resultsDir) {
- this.resultsDir = resultsDir;
- }
-
- @Override
- public boolean getTestFailed() {
- return testFailed;
- }
-
- @Override
- public boolean getIgnoreFailures() {
- return ignoreFailures;
- }
-
- @Override
- public void setIgnoreFailures(boolean ignoreFailures) {
- this.ignoreFailures = ignoreFailures;
- }
-
- public ReportType getReportType() {
- return reportType;
- }
-
- public void setReportType(ReportType reportType) {
- this.reportType = reportType;
- }
-
- public void addTask(AndroidTestTask task) {
- subTasks.add(task);
- this.dependsOn(task);
- }
-
- @InputFiles
- public List<File> getResultInputs() {
- List<File> list = Lists.newArrayList();
-
- for (AndroidTestTask task : subTasks) {
- list.add(task.getResultsDir());
- }
-
- return list;
- }
-
- /**
- * Sets that this current task will run and therefore needs to tell its children
- * class to not stop on failures.
- */
- public void setWillRun() {
- for (AndroidTestTask task : subTasks) {
- task.setIgnoreFailures(true);
- }
- }
-
- @TaskAction
- public void createReport() throws IOException {
- File resultsOutDir = getResultsDir();
- File reportOutDir = getReportsDir();
-
- // empty the folders
- FileUtils.emptyFolder(resultsOutDir);
- FileUtils.emptyFolder(reportOutDir);
-
- // do the copy.
- copyResults(resultsOutDir);
-
- // create the report.
- TestReport report = new TestReport(reportType, resultsOutDir, reportOutDir);
- report.generateReport();
-
- // fail if any of the tasks failed.
- for (AndroidTestTask task : subTasks) {
- if (task.getTestFailed()) {
- testFailed = true;
- String reportUrl = new ConsoleRenderer().asClickableFileUrl(
- new File(reportOutDir, "index.html"));
- String message = "There were failing tests. See the report at: " + reportUrl;
-
- if (getIgnoreFailures()) {
- getLogger().warn(message);
- } else {
- throw new GradleException(message);
- }
-
- break;
- }
- }
- }
-
- private void copyResults(File reportOutDir) throws IOException {
- List<File> inputs = getResultInputs();
-
- for (File input : inputs) {
- File[] children = input.listFiles();
- if (children != null) {
- for (File child : children) {
- copyFile(child, reportOutDir);
- }
- }
- }
- }
-
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.java
deleted file mode 100644
index bdcfc8c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import com.android.annotations.NonNull;
-
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-
-/**
- * Class that checks the presence of the manifest.
- */
-public class CheckManifest extends DefaultAndroidTask {
-
- private File manifest;
-
- private String variantName;
-
- @InputFile
- public File getManifest() {
- return manifest;
- }
-
- public void setManifest(@NonNull File manifest) {
- this.manifest = manifest;
- }
-
- public String getVariantName() {
- return variantName;
- }
-
- public void setVariantName(@NonNull String variantName) {
- this.variantName = variantName;
- }
-
- @TaskAction
- void check() {
- // use getter to resolve convention mapping
- File f = getManifest();
- if (!f.isFile()) {
- throw new IllegalArgumentException(String.format(
- "Main Manifest missing for variant %s. Expected path: ",
- getVariantName(), getManifest().getAbsolutePath()));
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.java
deleted file mode 100644
index fcb4291..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.tasks;
-
-import static com.android.builder.core.BuilderConstants.CONNECTED;
-import static com.android.builder.core.BuilderConstants.DEVICE;
-import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS;
-import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS;
-import static com.android.builder.core.BuilderConstants.FD_FLAVORS;
-import static com.android.builder.core.BuilderConstants.FD_REPORTS;
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
-import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT;
-
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.test.report.ReportType;
-import com.android.build.gradle.internal.test.report.TestReport;
-import com.android.build.gradle.internal.variant.TestVariantData;
-import com.android.builder.internal.testing.SimpleTestCallable;
-import com.android.builder.sdk.SdkInfo;
-import com.android.builder.sdk.TargetInfo;
-import com.android.builder.testing.ConnectedDeviceProvider;
-import com.android.builder.testing.SimpleTestRunner;
-import com.android.builder.testing.TestData;
-import com.android.builder.testing.TestRunner;
-import com.android.builder.testing.api.DeviceException;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.builder.testing.api.TestException;
-import com.android.ide.common.process.ProcessExecutor;
-import com.android.utils.FileUtils;
-import com.android.utils.StringHelper;
-import com.google.common.collect.ImmutableList;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.Nullable;
-import org.gradle.api.plugins.JavaBasePlugin;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.logging.ConsoleRenderer;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.concurrent.Callable;
-
-/**
- * Run instrumentation tests for a given variant
- */
-public class DeviceProviderInstrumentTestTask extends BaseTask implements AndroidTestTask {
-
- private File reportsDir;
- private File resultsDir;
- private File coverageDir;
-
- private String flavorName;
-
- @Nullable
- private Collection<String> installOptions;
-
- private DeviceProvider deviceProvider;
- private TestData testData;
-
- private File adbExec;
- @Nullable
- private File splitSelectExec;
- private ProcessExecutor processExecutor;
-
- private boolean ignoreFailures;
- private boolean testFailed;
-
- @TaskAction
- protected void runTests() throws DeviceException, IOException, InterruptedException,
- TestRunner.NoAuthorizedDeviceFoundException, TestException {
-
- File resultsOutDir = getResultsDir();
- FileUtils.emptyFolder(resultsOutDir);
-
- File coverageOutDir = getCoverageDir();
- FileUtils.emptyFolder(coverageOutDir);
-
- boolean success = false;
- // If there are tests to run, and the test runner returns with no results, we fail (since
- // this is most likely a problem with the device setup). If no, the task will succeed.
- if (!testsFound()) {
- getLogger().info("No tests found, nothing to do.");
- // If we don't create the coverage file, createXxxCoverageReport task will fail.
- File emptyCoverageFile = new File(coverageOutDir, SimpleTestCallable.FILE_COVERAGE_EC);
- emptyCoverageFile.createNewFile();
- success = true;
- } else {
- File testApk = testData.getTestApk();
- String flavor = getFlavorName();
- TestRunner testRunner = new SimpleTestRunner(
- getSplitSelectExec(),
- getProcessExecutor());
- deviceProvider.init();
-
- Collection<String> extraArgs = installOptions == null || installOptions.isEmpty()
- ? ImmutableList.<String>of() : installOptions;
- try {
- success = testRunner.runTests(getProject().getName(), flavor,
- testApk,
- testData,
- deviceProvider.getDevices(),
- deviceProvider.getMaxThreads(),
- deviceProvider.getTimeoutInMs(),
- extraArgs,
- resultsOutDir,
- coverageOutDir,
- getILogger());
- } finally {
- deviceProvider.terminate();
- }
-
- }
-
- // run the report from the results.
- File reportOutDir = getReportsDir();
- FileUtils.emptyFolder(reportOutDir);
-
- TestReport report = new TestReport(ReportType.SINGLE_FLAVOR, resultsOutDir, reportOutDir);
- report.generateReport();
-
- if (!success) {
- testFailed = true;
- String reportUrl = new ConsoleRenderer().asClickableFileUrl(
- new File(reportOutDir, "index.html"));
- String message = "There were failing tests. See the report at: " + reportUrl;
- if (getIgnoreFailures()) {
- getLogger().warn(message);
- return;
-
- } else {
- throw new GradleException(message);
- }
- }
-
- testFailed = false;
- }
-
- /**
- * Determines if there are any tests to run.
- *
- * @return true if there are some tests to run, false otherwise
- */
- private boolean testsFound() {
- // For now we check if there are any test sources. We could inspect the test classes and
- // apply JUnit logic to see if there's something to run, but that would not catch the case
- // where user makes a typo in a test name or forgets to inherit from a JUnit class
- return !getProject().files(testData.getTestDirectories()).getAsFileTree().isEmpty();
- }
-
- public File getReportsDir() {
- return reportsDir;
- }
-
- public void setReportsDir(File reportsDir) {
- this.reportsDir = reportsDir;
- }
-
- @Override
- public File getResultsDir() {
- return resultsDir;
- }
-
- public void setResultsDir(File resultsDir) {
- this.resultsDir = resultsDir;
- }
-
- public File getCoverageDir() {
- return coverageDir;
- }
-
- public void setCoverageDir(File coverageDir) {
- this.coverageDir = coverageDir;
- }
-
- public String getFlavorName() {
- return flavorName;
- }
-
- public void setFlavorName(String flavorName) {
- this.flavorName = flavorName;
- }
-
- public Collection<String> getInstallOptions() {
- return installOptions;
- }
-
- public void setInstallOptions(Collection<String> installOptions) {
- this.installOptions = installOptions;
- }
-
- public DeviceProvider getDeviceProvider() {
- return deviceProvider;
- }
-
- public void setDeviceProvider(DeviceProvider deviceProvider) {
- this.deviceProvider = deviceProvider;
- }
-
- public TestData getTestData() {
- return testData;
- }
-
- public void setTestData(TestData testData) {
- this.testData = testData;
- }
-
- public File getAdbExec() {
- return adbExec;
- }
-
- public void setAdbExec(File adbExec) {
- this.adbExec = adbExec;
- }
-
- public File getSplitSelectExec() {
- return splitSelectExec;
- }
-
- public void setSplitSelectExec(File splitSelectExec) {
- this.splitSelectExec = splitSelectExec;
- }
-
- public ProcessExecutor getProcessExecutor() {
- return processExecutor;
- }
-
- public void setProcessExecutor(ProcessExecutor processExecutor) {
- this.processExecutor = processExecutor;
- }
-
- @Override
- public boolean getIgnoreFailures() {
- return ignoreFailures;
- }
-
- @Override
- public void setIgnoreFailures(boolean ignoreFailures) {
- this.ignoreFailures = ignoreFailures;
- }
-
- @Override
- public boolean getTestFailed() {
- return testFailed;
- }
-
-
- public static class ConfigAction implements TaskConfigAction<DeviceProviderInstrumentTestTask> {
-
- private final VariantScope scope;
- private final DeviceProvider deviceProvider;
- private final TestData testData;
-
- public ConfigAction(VariantScope scope, DeviceProvider deviceProvider, TestData testData) {
- this.scope = scope;
- this.deviceProvider = deviceProvider;
- this.testData = testData;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName(deviceProvider.getName());
- }
-
- @Override
- public Class<DeviceProviderInstrumentTestTask> getType() {
- return DeviceProviderInstrumentTestTask.class;
- }
-
- @Override
- public void execute(DeviceProviderInstrumentTestTask task) {
- final boolean connected = deviceProvider instanceof ConnectedDeviceProvider;
- String variantName = scope.getTestedVariantData() != null ?
- scope.getTestedVariantData().getName() : scope.getVariantData().getName();
- if (connected) {
- task.setDescription("Installs and runs the tests for " + variantName +
- " on connected devices.");
- } else {
- task.setDescription("Installs and runs the tests for " + variantName +
- " using provider: " + StringHelper.capitalize(deviceProvider.getName()));
-
- }
- task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
- task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- task.setVariantName(variantName);
- task.setTestData(testData);
- task.setFlavorName(testData.getFlavorName());
- task.setDeviceProvider(deviceProvider);
- task.setInstallOptions(scope.getGlobalScope().getExtension().getAdbOptions().getInstallOptions());
- task.setProcessExecutor(scope.getGlobalScope().getAndroidBuilder().getProcessExecutor());
-
- String flavorFolder = testData.getFlavorName();
- if (!flavorFolder.isEmpty()) {
- flavorFolder = FD_FLAVORS + "/" + flavorFolder;
- }
- String providerFolder = connected ? CONNECTED : DEVICE + "/" + deviceProvider.getName();
- final String subFolder = "/" + providerFolder + "/" + flavorFolder;
-
- ConventionMappingHelper.map(task, "adbExec", new Callable<File>() {
- @Override
- public File call() {
- final SdkInfo info = scope.getGlobalScope().getSdkHandler()
- .getSdkInfo();
- return (info == null ? null : info.getAdb());
- }
- });
- ConventionMappingHelper.map(task, "splitSelectExec", new Callable<File>() {
- @Override
- public File call() throws Exception {
- final TargetInfo info = scope.getGlobalScope().getAndroidBuilder().getTargetInfo();
- String path = info == null ? null : info.getBuildTools().getPath(SPLIT_SELECT);
- if (path != null) {
- File splitSelectExe = new File(path);
- return splitSelectExe.exists() ? splitSelectExe : null;
- } else {
- return null;
- }
- }
- });
-
- ConventionMappingHelper.map(task, "resultsDir", new Callable<File>() {
- @Override
- public File call() {
- String rootLocation = scope.getGlobalScope().getExtension().getTestOptions().getResultsDir();
- if (rootLocation == null) {
- rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
- FD_OUTPUTS + "/" + FD_ANDROID_RESULTS;
- }
- return scope.getGlobalScope().getProject().file(rootLocation + subFolder);
- }
- });
-
- ConventionMappingHelper.map(task, "reportsDir", new Callable<File>() {
- @Override
- public File call() {
- String rootLocation = scope.getGlobalScope().getExtension().getTestOptions().getReportDir();
- if (rootLocation == null) {
- rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
- FD_REPORTS + "/" + FD_ANDROID_TESTS;
- }
- return scope.getGlobalScope().getProject().file(rootLocation + subFolder);
- }
- });
-
- String rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
- FD_OUTPUTS + "/code-coverage";
- task.setCoverageDir(scope.getGlobalScope().getProject().file(rootLocation + subFolder));
-
- if (scope.getVariantData() instanceof TestVariantData) {
- TestVariantData testVariantData = (TestVariantData) scope.getVariantData();
- if (connected) {
- testVariantData.connectedTestTask = task;
- } else {
- testVariantData.providerTestTaskList.add(task);
- }
- }
-
- task.setEnabled(deviceProvider.isConfigured());
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ExtractJavaResourcesTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ExtractJavaResourcesTask.java
deleted file mode 100644
index 9e4bc57..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ExtractJavaResourcesTask.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.ide.common.packaging.PackagingUtils;
-import com.android.utils.FileUtils;
-import com.google.common.io.ByteStreams;
-
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
-import org.gradle.api.tasks.incremental.InputFileDetails;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Enumeration;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-/**
- * Extract all packaged jar files java resources into a directory. Each jar file will be extracted
- * in a jar specific folder, and only java resources are extracted.
- */
-public class ExtractJavaResourcesTask extends DefaultAndroidTask {
-
- // the fact we use a SET is not right, we should have an ordered list of jars...
- // VariantConfiguration.getPackaged|ProvidedJars should use List<>
- @InputFiles
- public Set<File> jarInputFiles;
-
- @OutputDirectory
- public File outputDir;
-
- @InputFiles
- public Set<File> getJarInputFiles() {
- return jarInputFiles;
- }
-
- @TaskAction
- public void extractJavaResources(final IncrementalTaskInputs incrementalTaskInputs) {
-
- incrementalTaskInputs.outOfDate(new org.gradle.api.Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails inputFileDetails) {
- File inputJar = inputFileDetails.getFile();
- String folderName = inputJar.getName() +
- inputJar.getPath().hashCode();
-
- File outputFolder = new File(outputDir, folderName);
- if (outputFolder.exists()) {
- try {
- FileUtils.deleteFolder(outputFolder);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- if (!outputFolder.mkdirs()) {
- throw new RuntimeException(
- "Cannot create folder to extract java resources in for "
- + inputJar.getAbsolutePath());
- }
-
- // create the jar file visitor that will check for out-dated resources.
-
- JarFile jarFile = null;
- try {
- jarFile = new JarFile(inputJar);
- Enumeration<JarEntry> entries = jarFile.entries();
- while (entries.hasMoreElements()) {
- JarEntry jarEntry = entries.nextElement();
- if (!jarEntry.isDirectory()) {
- processJarEntry(jarFile, jarEntry, outputFolder);
- }
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- if (jarFile != null) {
- try {
- jarFile.close();
- } catch (IOException e) {
- // ignore.
- }
- }
- }
- }
- });
-
- incrementalTaskInputs.removed(new org.gradle.api.Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails inputFileDetails) {
- File deletedJar = inputFileDetails.getFile();
- String folderName = deletedJar.getName() +
- deletedJar.getPath().hashCode();
- File outputFolder = new File(outputDir, folderName);
- if (outputFolder.exists()) {
- try {
- FileUtils.deleteFolder(outputFolder);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
- });
- }
-
- /**
- * process one jar entry in an input jar file and optionally stores the entry in the output
- * folder.
- * @param jarFile the input jar file
- * @param jarEntry the jar entry in the jarFile to process
- * @param outputDir the output folder to use to copy/merge the entry in.
- * @throws IOException
- */
- private static void processJarEntry(JarFile jarFile, JarEntry jarEntry, File outputDir) throws IOException {
- File outputFile = new File(outputDir, jarEntry.getName());
- Action action = getAction(jarEntry.getName());
- if (action == Action.COPY) {
- if (!outputFile.getParentFile().exists() &&
- !outputFile.getParentFile().mkdirs()) {
- throw new RuntimeException("Cannot create directory " + outputFile.getParent());
- }
- if (!outputFile.exists() || outputFile.lastModified()
- < jarEntry.getTime()) {
- InputStream inputStream = null;
- OutputStream outputStream = null;
- try {
- inputStream = jarFile.getInputStream(jarEntry);
- if (inputStream != null) {
- outputStream = new BufferedOutputStream(
- new FileOutputStream(outputFile));
- ByteStreams.copy(inputStream, outputStream);
- outputStream.flush();
- } else {
- throw new RuntimeException("Cannot copy " + jarEntry.getName());
- }
- } finally {
- try {
- if (outputStream != null) {
- outputStream.close();
- }
- } finally {
- if (inputStream != null) {
- inputStream.close();
- }
- }
- }
- }
- }
- }
-
- /**
- * Define all possible actions for a Jar file entry.
- */
- enum Action {
- /**
- * Copy the file to the output destination.
- */
- COPY,
- /**
- * Ignore the file.
- */
- IGNORE
- }
-
- /**
- * Provides an {@link Action} for the archive entry.
- * @param archivePath the archive entry path in the archive.
- * @return the action to implement.
- */
- @NonNull
- public static Action getAction(@NonNull String archivePath) {
- // Manifest files are never merged.
- if (JarFile.MANIFEST_NAME.equals(archivePath)) {
- return Action.IGNORE;
- }
-
- // split the path into segments.
- String[] segments = archivePath.split("/");
-
- // empty path? skip to next entry.
- if (segments.length == 0) {
- return Action.IGNORE;
- }
-
- // Check each folders to make sure they should be included.
- // Folders like CVS, .svn, etc.. should already have been excluded from the
- // jar file, but we need to exclude some other folder (like /META-INF) so
- // we check anyway.
- for (int i = 0 ; i < segments.length - 1; i++) {
- if (!PackagingUtils.checkFolderForPackaging(segments[i])) {
- return Action.IGNORE;
- }
- }
-
- // get the file name from the path
- String fileName = segments[segments.length-1];
-
- return PackagingUtils.checkFileForPackaging(fileName)
- ? Action.COPY
- : Action.IGNORE;
- }
-
- public static class Config implements TaskConfigAction<ExtractJavaResourcesTask> {
-
- private final VariantScope scope;
-
- public Config(VariantScope scope) {
- this.scope = scope;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("extract", "PackagedLibrariesJavaResources");
- }
-
- @Override
- public Class<ExtractJavaResourcesTask> getType() {
- return ExtractJavaResourcesTask.class;
- }
-
- @Override
- public void execute(ExtractJavaResourcesTask extractJavaResourcesTask) {
- ConventionMappingHelper.map(extractJavaResourcesTask, "jarInputFiles",
- new Callable<Set<File>>() {
-
- @Override
- public Set<File> call() throws Exception {
- return scope.getVariantConfiguration().getPackagedJars();
- }
- });
- extractJavaResourcesTask.outputDir = scope.getPackagedJarsJavaResDestinationDir();
- extractJavaResourcesTask.setVariantName(scope.getVariantConfiguration().getFullName());
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.java
deleted file mode 100644
index 2fb3017..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import static com.android.SdkConstants.DOT_ANDROID_PACKAGE;
-import static com.android.SdkConstants.FD_RES_RAW;
-import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
-import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.builder.core.AndroidBuilder;
-import com.android.ide.common.internal.LoggedErrorException;
-import com.android.ide.common.process.ProcessException;
-import com.android.utils.FileUtils;
-import com.google.common.io.Files;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.concurrent.Callable;
-
-/**
- * Task to generate micro app data res file.
- */
-public class GenerateApkDataTask extends BaseTask {
-
- private File apkFile;
-
- private File resOutputDir;
-
- private File manifestFile;
-
- private String mainPkgName;
-
- private int minSdkVersion;
-
- private int targetSdkVersion;
-
- @Input
- String getBuildToolsVersion() {
- return getBuildTools().getRevision().toString();
- }
-
- @TaskAction
- void generate() throws IOException, ProcessException, LoggedErrorException,
- InterruptedException {
- // always empty output dir.
- File outDir = getResOutputDir();
- FileUtils.emptyFolder(outDir);
-
- File apk = getApkFile();
- // copy the file into the destination, by sanitizing the name first.
- File rawDir = new File(outDir, FD_RES_RAW);
- rawDir.mkdirs();
-
- File to = new File(rawDir, ANDROID_WEAR_MICRO_APK + DOT_ANDROID_PACKAGE);
- Files.copy(apk, to);
-
- // now create the matching XML and the manifest entry.
- AndroidBuilder builder = getBuilder();
-
- builder.generateApkData(apk, outDir, getMainPkgName(), ANDROID_WEAR_MICRO_APK);
- AndroidBuilder.generateApkDataEntryInManifest(getMinSdkVersion(),
- getTargetSdkVersion(),
- getManifestFile());
- }
-
- @OutputDirectory
- public File getResOutputDir() {
- return resOutputDir;
- }
-
- public void setResOutputDir(File resOutputDir) {
- this.resOutputDir = resOutputDir;
- }
-
- @InputFile
- public File getApkFile() {
- return apkFile;
- }
-
- public void setApkFile(File apkFile) {
- this.apkFile = apkFile;
- }
-
- @Input
- public String getMainPkgName() {
- return mainPkgName;
- }
-
- public void setMainPkgName(String mainPkgName) {
- this.mainPkgName = mainPkgName;
- }
-
- @Input
- public int getMinSdkVersion() {
- return minSdkVersion;
- }
-
- public void setMinSdkVersion(int minSdkVersion) {
- this.minSdkVersion = minSdkVersion;
- }
-
- @Input
- public int getTargetSdkVersion() {
- return targetSdkVersion;
- }
-
- public void setTargetSdkVersion(int targetSdkVersion) {
- this.targetSdkVersion = targetSdkVersion;
- }
-
- @OutputFile
- public File getManifestFile() {
- return manifestFile;
- }
-
- public void setManifestFile(File manifestFile) {
- this.manifestFile = manifestFile;
- }
-
- public static class ConfigAction implements TaskConfigAction<GenerateApkDataTask> {
-
- @NonNull
- VariantScope scope;
-
- @NonNull
- Configuration config;
-
- public ConfigAction(@NonNull VariantScope scope, @NonNull Configuration config) {
- this.scope = scope;
- this.config = config;
- }
-
- @Override
- @NonNull
- public String getName() {
- return scope.getTaskName("handle", "MicroApk");
- }
-
- @Override
- @NonNull
- public Class<GenerateApkDataTask> getType() {
- return GenerateApkDataTask.class;
- }
-
- @Override
- public void execute(GenerateApkDataTask task) {
- final ApkVariantData variantData = (ApkVariantData) scope.getVariantData();
- variantData.generateApkDataTask = task;
-
- task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- task.setVariantName(scope.getVariantConfiguration().getFullName());
- ConventionMappingHelper.map(task, "resOutputDir", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return new File(
- scope.getGlobalScope().getGeneratedDir(),
- "/res/microapk/"
- + variantData.getVariantConfiguration().getDirName());
- }
- });
- ConventionMappingHelper.map(task, "apkFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- // only care about the first one. There shouldn't be more anyway.
- return config.getFiles().iterator().next();
- }
- });
- ConventionMappingHelper.map(task, "manifestFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return new File(
- scope.getGlobalScope().getGeneratedDir(),
- "/manifests/microapk/"
- + scope.getVariantData().getVariantConfiguration().getDirName()
- + "/" + FN_ANDROID_MANIFEST_XML);
- }
- });
- ConventionMappingHelper.map(task, "mainPkgName", new Callable<String>() {
- @Override
- public String call() throws Exception {
- return variantData.getVariantConfiguration().getApplicationId();
- }
- });
-
- ConventionMappingHelper.map(task, "minSdkVersion", new Callable<Integer>() {
- @Override
- public Integer call() throws Exception {
- return variantData.getVariantConfiguration().getMinSdkVersion().getApiLevel();
- }
- });
-
- ConventionMappingHelper.map(task, "targetSdkVersion", new Callable<Integer>() {
- @Override
- public Integer call() throws Exception {
- return variantData.getVariantConfiguration().getTargetSdkVersion().getApiLevel();
- }
- });
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java
deleted file mode 100644
index f28be32..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.tasks;
-import com.android.ide.common.res2.FileStatus;
-import com.android.ide.common.res2.SourceSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import org.gradle.api.Action;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
-import org.gradle.api.tasks.incremental.InputFileDetails;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-
-public abstract class IncrementalTask extends BaseTask {
-
- private File incrementalFolder;
-
- public void setIncrementalFolder(File incrementalFolder) {
- this.incrementalFolder = incrementalFolder;
- }
-
- @OutputDirectory @Optional
- public File getIncrementalFolder() {
- return incrementalFolder;
- }
-
- /**
- * Whether this task can support incremental update.
- *
- * @return whether this task can support incremental update.
- */
- protected boolean isIncremental() {
- return false;
- }
-
- /**
- * Actual task action. This is called when a full run is needed, which is always the case if
- * {@link #isIncremental()} returns false.
- *
- */
- protected abstract void doFullTaskAction() throws IOException;
-
- /**
- * Optional incremental task action.
- * Only used if {@link #isIncremental()} returns true.
- *
- * @param changedInputs the changed input files.
- */
- protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
- // do nothing.
- }
-
- /**
- * Actual entry point for the action.
- * Calls out to the doTaskAction as needed.
- */
- @TaskAction
- void taskAction(IncrementalTaskInputs inputs) throws IOException {
- if (!isIncremental()) {
- doFullTaskAction();
- return;
- }
-
- if (!inputs.isIncremental()) {
- getProject().getLogger().info("Unable do incremental execution: full task run");
- doFullTaskAction();
- return;
- }
-
- final Map<File, FileStatus> changedInputs = Maps.newHashMap();
- inputs.outOfDate(new Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails change) {
- changedInputs.put(change.getFile(), change.isAdded() ? FileStatus.NEW : FileStatus.CHANGED);
- }
- });
-
- inputs.removed(new Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails change) {
-
- changedInputs.put(change.getFile(), FileStatus.REMOVED);
- }
- });
-
- doIncrementalTaskAction(changedInputs);
- }
-
- public static List<File> flattenSourceSets(List<? extends SourceSet> resourceSets) {
- List<File> list = Lists.newArrayList();
-
- for (SourceSet sourceSet : resourceSets) {
- list.addAll(sourceSet.getSourceFiles());
- }
-
- return list;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/InstallVariantTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/InstallVariantTask.java
deleted file mode 100644
index 8e44598..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/InstallVariantTask.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.tasks;
-
-import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT;
-
-import com.android.build.gradle.internal.LoggerWrapper;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.builder.core.VariantConfiguration;
-import com.android.builder.internal.InstallUtils;
-import com.android.builder.sdk.SdkInfo;
-import com.android.builder.sdk.TargetInfo;
-import com.android.builder.testing.ConnectedDeviceProvider;
-import com.android.builder.testing.api.DeviceConfigProviderImpl;
-import com.android.builder.testing.api.DeviceConnector;
-import com.android.builder.testing.api.DeviceException;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.ide.common.build.SplitOutputMatcher;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessExecutor;
-import com.android.utils.FileUtils;
-import com.android.utils.ILogger;
-import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableList;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.Task;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * Task installing an app variant. It looks at connected device and install the best matching
- * variant output on each device.
- */
-public class InstallVariantTask extends BaseTask {
-
- private File adbExe;
-
- private File splitSelectExe;
-
- private ProcessExecutor processExecutor;
-
- private String projectName;
-
- private int timeOutInMs = 0;
-
- private Collection<String> installOptions;
-
- private BaseVariantData<? extends BaseVariantOutputData> variantData;
-
- public InstallVariantTask() {
- this.getOutputs().upToDateWhen(new Spec<Task>() {
- @Override
- public boolean isSatisfiedBy(Task task) {
- getLogger().debug("Install task is always run.");
- return false;
- }
- });
- }
-
- @TaskAction
- public void install() throws DeviceException, ProcessException, InterruptedException {
- final ILogger iLogger = new LoggerWrapper(getLogger(), LogLevel.LIFECYCLE);
- DeviceProvider deviceProvider = new ConnectedDeviceProvider(getAdbExe(), iLogger);
- deviceProvider.init();
-
- VariantConfiguration variantConfig = variantData.getVariantConfiguration();
- String variantName = variantConfig.getFullName();
-
- int successfulInstallCount = 0;
- List<? extends DeviceConnector> devices = deviceProvider.getDevices();
- for (final DeviceConnector device : devices) {
- if (InstallUtils.checkDeviceApiLevel(
- device, variantConfig.getMinSdkVersion(), iLogger, projectName, variantName)) {
- // When InstallUtils.checkDeviceApiLevel returns false, it logs the reason.
- final List<File> apkFiles = SplitOutputMatcher.computeBestOutput(processExecutor,
- getSplitSelectExe(),
- new DeviceConfigProviderImpl(device),
- variantData.getOutputs(),
- variantData.getVariantConfiguration().getSupportedAbis());
-
- if (apkFiles.isEmpty()) {
- getLogger().lifecycle(
- "Skipping device '{}' for '{}:{}': Could not find build of variant " +
- "which supports density {} and an ABI in {}",
- device.getName(), projectName, variantName,
- device.getDensity(), Joiner.on(", ").join(device.getAbis()));
- } else {
- getLogger().lifecycle(
- "Installing APK '{}' on '{}' for {}:{}",
- FileUtils.getNamesAsCommaSeparatedList(apkFiles),
- device.getName(),
- projectName,
- variantName);
-
- final Collection<String> extraArgs =
- Objects.firstNonNull(installOptions, ImmutableList.<String>of());
-
- if (apkFiles.size() > 1 || device.getApiLevel() >= 21) {
- device.installPackages(apkFiles, extraArgs,
- getTimeOutInMs(), getILogger());
- successfulInstallCount++;
- } else {
- device.installPackage(apkFiles.get(0), extraArgs,
- getTimeOutInMs(),
- getILogger());
- successfulInstallCount++;
- }
- }
- }
- }
-
- if (successfulInstallCount == 0) {
- throw new GradleException("Failed to install on any devices.");
- } else {
- getLogger().quiet("Installed on {} {}.",
- successfulInstallCount,
- successfulInstallCount==1 ? "device" : "devices");
- }
- }
-
- @InputFile
- public File getAdbExe() {
- return adbExe;
- }
-
- public void setAdbExe(File adbExe) {
- this.adbExe = adbExe;
- }
-
- @InputFile
- @Optional
- public File getSplitSelectExe() {
- return splitSelectExe;
- }
-
- public void setSplitSelectExe(File splitSelectExe) {
- this.splitSelectExe = splitSelectExe;
- }
-
- public ProcessExecutor getProcessExecutor() {
- return processExecutor;
- }
-
- public void setProcessExecutor(ProcessExecutor processExecutor) {
- this.processExecutor = processExecutor;
- }
-
- public String getProjectName() {
- return projectName;
- }
-
- public void setProjectName(String projectName) {
- this.projectName = projectName;
- }
-
- @Input
- public int getTimeOutInMs() {
- return timeOutInMs;
- }
-
- public void setTimeOutInMs(int timeOutInMs) {
- this.timeOutInMs = timeOutInMs;
- }
-
- @Input
- @Optional
- public Collection<String> getInstallOptions() {
- return installOptions;
- }
-
- public void setInstallOptions(Collection<String> installOptions) {
- this.installOptions = installOptions;
- }
-
- public BaseVariantData<? extends BaseVariantOutputData> getVariantData() {
- return variantData;
- }
-
- public void setVariantData(
- BaseVariantData<? extends BaseVariantOutputData> variantData) {
- this.variantData = variantData;
- }
-
- public static class ConfigAction implements TaskConfigAction<InstallVariantTask> {
-
- private final VariantScope scope;
-
- public ConfigAction(VariantScope scope) {
- this.scope = scope;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("install");
- }
-
- @Override
- public Class<InstallVariantTask> getType() {
- return InstallVariantTask.class;
- }
-
- @Override
- public void execute(InstallVariantTask installTask) {
- installTask.setDescription(
- "Installs the " + scope.getVariantData().getDescription() + ".");
- installTask.setVariantName(scope.getVariantConfiguration().getFullName());
- installTask.setGroup(TaskManager.INSTALL_GROUP);
- installTask.setProjectName(scope.getGlobalScope().getProject().getName());
- installTask.setVariantData(scope.getVariantData());
- installTask.setTimeOutInMs(
- scope.getGlobalScope().getExtension().getAdbOptions().getTimeOutInMs());
- installTask.setInstallOptions(
- scope.getGlobalScope().getExtension().getAdbOptions().getInstallOptions());
- installTask.setProcessExecutor(
- scope.getGlobalScope().getAndroidBuilder().getProcessExecutor());
- ConventionMappingHelper.map(installTask, "adbExe", new Callable<File>() {
- @Override
- public File call() throws Exception {
- final SdkInfo info = scope.getGlobalScope().getSdkHandler().getSdkInfo();
- return (info == null ? null : info.getAdb());
- }
- });
- ConventionMappingHelper.map(installTask, "splitSelectExe", new Callable<File>() {
- @Override
- public File call() throws Exception {
- final TargetInfo info =
- scope.getGlobalScope().getAndroidBuilder().getTargetInfo();
- String path = info == null ? null : info.getBuildTools().getPath(SPLIT_SELECT);
- if (path != null) {
- File splitSelectExe = new File(path);
- return splitSelectExe.exists() ? splitSelectExe : null;
- } else {
- return null;
- }
- }
- });
- ((ApkVariantData) scope.getVariantData()).installTask = installTask;
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.java
deleted file mode 100644
index 523100a..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.tasks;
-
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * Task to merge files. This appends all the files together into an output file.
- */
-public class MergeFileTask extends DefaultAndroidTask {
-
- private Set<File> mInputFiles;
-
- private File mOutputFile;
-
- @TaskAction
- public void mergeFiles() throws IOException {
-
- Set<File> files = getInputFiles();
- File output = getOutputFile();
-
- if (files.size() == 1) {
- Files.copy(files.iterator().next(), output);
- return;
- }
-
- // first delete the current file
- output.delete();
-
- // no input? done.
- if (files.isEmpty()) {
- return;
- }
-
- // otherwise put the all the files together
- for (File file : files) {
- String content = Files.toString(file, Charsets.UTF_8);
- Files.append(content, output, Charsets.UTF_8);
- Files.append("\n", output, Charsets.UTF_8);
- }
- }
-
- @InputFiles
- public Set<File> getInputFiles() {
- return mInputFiles;
- }
-
- public void setInputFiles(Set<File> inputFiles) {
- mInputFiles = inputFiles;
- }
-
- @OutputFile
- public File getOutputFile() {
- return mOutputFile;
- }
-
- public void setOutputFile(File outputFile) {
- mOutputFile = outputFile;
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeJavaResourcesTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeJavaResourcesTask.java
deleted file mode 100644
index b0aa93f..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeJavaResourcesTask.java
+++ /dev/null
@@ -1,549 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.tasks.JavaResourcesProvider;
-import com.android.builder.model.PackagingOptions;
-import com.android.builder.signing.SignedJarBuilder;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.io.Files;
-
-import org.gradle.api.tasks.InputDirectory;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
-import org.gradle.api.tasks.incremental.InputFileDetails;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-/**
- * Merges java resources from temporary expansion folders (created from the packaged jars
- * resources and source folder java resources) into a single output directory that can be used
- * during obfuscation and packaging.
- *
- * This task is the default {@link JavaResourcesProvider} to provide merged java resources to
- * the final variant packaging step. However, if the variant obfuscation is turned on, some of
- * these resources packages might need to be adapted to match the obfuscated code. In such
- * a scenario, the {@link JavaResourcesProvider} will become the task responsible for obfuscation.
- */
- at ParallelizableTask
-public class MergeJavaResourcesTask extends DefaultAndroidTask implements JavaResourcesProvider {
-
- @Nested
- @Optional
- @Nullable
- public PackagingOptions packagingOptions;
-
- @InputDirectory
- @Optional
- @Nullable
- public File getSourceJavaResourcesFolder() {
- return sourceJavaResourcesFolder;
- }
-
- @InputDirectory
- @Optional
- @Nullable
- public File getPackagedJarsJavaResourcesFolder() {
- return packagedJarsJavaResourcesFolder;
- }
-
- @Nullable
- private FileFilter packagingOptionsFilter;
-
- @SuppressWarnings({"UnusedDeclaration"})
- @Nullable
- private File sourceJavaResourcesFolder;
- @SuppressWarnings({"UnusedDeclaration"})
- @Nullable
- private File packagedJarsJavaResourcesFolder;
-
- @SuppressWarnings({"UnusedDeclaration"})
- @Nullable
- private File outputDir;
-
- @OutputDirectory
- @Nullable
- public File getOutputDir() {
- return outputDir;
- }
-
- public List<File> getExpandedFolders() {
- ImmutableList.Builder<File> builder = ImmutableList.builder();
- if (getSourceJavaResourcesFolder() != null) {
- builder.add(getSourceJavaResourcesFolder());
- }
- if (getPackagedJarsJavaResourcesFolder() != null) {
- builder.add(getPackagedJarsJavaResourcesFolder());
- }
- return builder.build();
- }
-
- @TaskAction
- void extractJavaResources(IncrementalTaskInputs incrementalTaskInputs) {
-
- if (packagingOptionsFilter == null || getOutputDir() == null) {
- throw new RuntimeException(
- "Internal error, packagingOptionsFilter or outputDir is null");
- }
- incrementalTaskInputs.outOfDate(new org.gradle.api.Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails inputFileDetails) {
- try {
- packagingOptionsFilter.handleChanged(
- getOutputDir(), inputFileDetails.getFile());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- });
-
- incrementalTaskInputs.removed(new org.gradle.api.Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails inputFileDetails) {
- try {
- packagingOptionsFilter.handleRemoved
- (getOutputDir(), inputFileDetails.getFile());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- });
-
- }
-
- /**
- * Defines a file filter contract which will use {@link PackagingOptions} to take appropriate
- * action.
- */
- @VisibleForTesting
- static final class FileFilter implements SignedJarBuilder.IZipEntryFilter {
-
- /**
- * User's setting for a particular archive entry. This is expressed in the build.gradle
- * DSL and used by this filter to determine file merging behaviors.
- */
- private enum PackagingOption {
- /**
- * no action was described for archive entry.
- */
- NONE,
- /**
- * merge all archive entries with the same archive path.
- */
- MERGE,
- /**
- * pick to first archive entry with that archive path (not stable).
- */
- PICK_FIRST,
- /**
- * exclude all archive entries with that archive path.
- */
- EXCLUDE
- }
-
- @Nullable
- private final PackagingOptions packagingOptions;
- @NonNull
- private final Set<String> excludes;
- @NonNull
- private final Set<String> pickFirsts;
- @NonNull
- private final MergeJavaResourcesTask owner;
-
- public FileFilter(@NonNull MergeJavaResourcesTask owner,
- @Nullable PackagingOptions packagingOptions) {
- this.owner = owner;
- this.packagingOptions = packagingOptions;
- excludes = this.packagingOptions != null ? this.packagingOptions.getExcludes() :
- Collections.<String>emptySet();
- pickFirsts = this.packagingOptions != null ? this.packagingOptions.getPickFirsts() :
- Collections.<String>emptySet();
- }
-
- /**
- * Implementation of the {@link SignedJarBuilder.IZipEntryFilter} contract which only
- * cares about copying or ignoring files since merging is handled differently.
- * @param archivePath the archive file path of the entry
- * @return true if the archive entry satisfies the filter, false otherwise.
- * @throws ZipAbortException
- */
- @Override
- public boolean checkEntry(@NonNull String archivePath)
- throws ZipAbortException {
- PackagingOption packagingOption = getPackagingAction(archivePath);
- switch(packagingOption) {
- case EXCLUDE:
- return false;
- case PICK_FIRST:
- List<File> allFiles = getAllFiles(archivePath);
- return allFiles.isEmpty();
- case MERGE:
- case NONE:
- return true;
- default:
- throw new RuntimeException("Unhandled PackagingOption " + packagingOption);
- }
- }
-
- /**
- * Notification of an incremental file changed since last successful run of the task.
- *
- * Usually, we just copy the changed file into the merged folder. However, if the user
- * specified {@link PackagingOption#PICK_FIRST}, the file will only be copied if it the
- * first pick. Also, if the user specified {@link PackagingOption#MERGE}, all the files
- * with the same entry archive path will be re-merged.
- *
- * @param outputDir merged resources folder.
- * @param changedFile changed file located in a temporary expansion folder
- * @throws IOException
- */
- void handleChanged(@NonNull File outputDir, @NonNull File changedFile)
- throws IOException {
- String archivePath = getArchivePath(changedFile);
- PackagingOption packagingOption = getPackagingAction(archivePath);
- switch (packagingOption) {
- case EXCLUDE:
- return;
- case MERGE:
- // one of the merged file has changed, re-merge all of them.
- mergeAll(outputDir, archivePath);
- return;
- case PICK_FIRST:
- copy(changedFile, outputDir, archivePath);
- return;
- case NONE:
- copy(changedFile, outputDir, archivePath);
- }
- }
-
- /**
- * Notification of a file removal.
- *
- * file was removed, we need to check that it was not a pickFirst item (since we
- * may now need to pick another one) or a merged item since we would need to re-merge
- * all remaining items.
- *
- * @param outputDir expected merged output directory.
- * @param removedFile removed file from the temporary resources folders.
- * @throws IOException
- */
- public void handleRemoved(@NonNull File outputDir, @NonNull File removedFile)
- throws IOException {
-
-
- String archivePath = getArchivePath(removedFile);
- // first delete the output file, it will be eventually replaced.
- File outFile = new File(outputDir, archivePath);
- if (outFile.exists()) {
- if (!outFile.delete()) {
- throw new IOException("Cannot delete " + outFile.getAbsolutePath());
- }
- }
- FileFilter.PackagingOption itemPackagingOption = getPackagingAction(archivePath);
-
- switch(itemPackagingOption) {
- case PICK_FIRST:
- // this was a picked up item, make sure we copy the first still available
- com.google.common.base.Optional<File> firstPick = getFirstPick(archivePath);
- if (firstPick.isPresent()) {
- copy(firstPick.get(), outputDir, archivePath);
- }
- return;
- case MERGE:
- // re-merge all
- mergeAll(outputDir, archivePath);
- return;
- case EXCLUDE:
- case NONE:
- // do nothing
- return;
- default:
- throw new RuntimeException("Unhandled package option"
- + itemPackagingOption);
-
- }
- }
-
- private static void copy(@NonNull File inputFile,
- @NonNull File outputDir,
- @NonNull String archivePath) throws IOException {
-
- File outputFile = new File(outputDir, archivePath);
- createParentFolderIfNecessary(outputFile);
- Files.copy(inputFile, outputFile);
- }
-
- private void mergeAll(@NonNull File outputDir, @NonNull String archivePath)
- throws IOException {
-
- File outputFile = new File(outputDir, archivePath);
- if (outputFile.exists() && !outputFile.delete()) {
- throw new RuntimeException("Cannot delete " + outputFile);
- }
- createParentFolderIfNecessary(outputFile);
- List<File> allFiles = getAllFiles(archivePath);
- if (!allFiles.isEmpty()) {
- OutputStream os = null;
- try {
- os = new BufferedOutputStream(new FileOutputStream(outputFile));
- // take each file in order and merge them.
- for (File file : allFiles) {
- Files.copy(file, os);
- }
- } finally {
- if (os != null) {
- os.close();
- }
- }
- }
- }
-
- private static void createParentFolderIfNecessary(@NonNull File outputFile) {
- File parentFolder = outputFile.getParentFile();
- if (!parentFolder.exists()) {
- if (!parentFolder.mkdirs()) {
- throw new RuntimeException("Cannot create folder " + parentFolder);
- }
- }
- }
-
- /**
- * Return the first file from the temporary expansion folders that satisfy the archive path.
- * @param archivePath the entry archive path.
- * @return the first file reference of {@link com.google.common.base.Optional#absent()} if
- * none exist in any temporary expansion folders.
- */
- @NonNull
- private com.google.common.base.Optional<File> getFirstPick(
- @NonNull final String archivePath) {
-
- return com.google.common.base.Optional.fromNullable(
- forEachExpansionFolder(new FolderAction() {
- @Nullable
- @Override
- public File on(File folder) {
- File expandedFile = new File(folder, archivePath);
- if (expandedFile.exists()) {
- return expandedFile;
- }
- return null;
- }
- }));
- }
-
- /**
- * Returns all files from temporary expansion folders with the same archive path.
- * @param archivePath the entry archive path.
- * @return a list possibly empty of {@link File}s that satisfy the archive path.
- */
- @NonNull
- private List<File> getAllFiles(@NonNull final String archivePath) {
- final ImmutableList.Builder<File> matchingFiles = ImmutableList.builder();
- forEachExpansionFolder(new FolderAction() {
- @Nullable
- @Override
- public File on(File folder) {
- File expandedFile = new File(folder, archivePath);
- if (expandedFile.exists()) {
- matchingFiles.add(expandedFile);
- }
- return null;
- }
- });
- return matchingFiles.build();
- }
-
- /**
- * An action on a folder.
- */
- private interface FolderAction {
-
- /**
- * Perform an action on a folder and stop the processing if something is returned
- * @param folder the folder to perform the action on.
- * @return a file to stop processing or null to continue to the next expansion folder
- * if any.
- */
- @Nullable
- File on(File folder);
- }
-
- /**
- * Perform the passed action on each expansion folder.
- * @param action the action to perform on each folder.
- * @return a file if any action returned a value, or null if none returned a value.
- */
- @Nullable
- private File forEachExpansionFolder(@NonNull FolderAction action) {
- for (File expansionParentFolder : owner.getExpandedFolders()) {
- File[] expansionFolders = expansionParentFolder.listFiles();
- if (expansionFolders != null) {
- for (File expansionFolder : expansionFolders) {
- if (expansionFolder.isDirectory()) {
- File value = action.on(expansionFolder);
- if (value != null) {
- return value;
- }
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Returns the expansion folder for an expanded file. This represents the location
- * where the packaged jar our source directories java resources were extracted into.
- * @param expandedFile the java resource file.
- * @return the expansion folder used to extract the java resource into.
- */
- @NonNull
- private File getExpansionFolder(@NonNull final File expandedFile) {
- File expansionFolder = forEachExpansionFolder(new FolderAction() {
- @Nullable
- @Override
- public File on(File folder) {
- return expandedFile.getAbsolutePath().startsWith(folder.getAbsolutePath())
- ? folder : null;
- }
- });
- if (expansionFolder == null) {
- throw new RuntimeException("Cannot determine expansion folder for " + expandedFile
- + " with folders " + Joiner.on(",").join(owner.getExpandedFolders()));
- }
- return expansionFolder;
- }
-
- /**
- * Determines the archive entry path relative to its expansion folder. The archive entry
- * path is the path that was used to save the entry in the original .jar file that got
- * expanded in the expansion folder.
- * @param expandedFile the expanded file to find the relative archive entry from.
- * @return the expanded file relative path to its expansion folder.
- */
- @NonNull
- private String getArchivePath(@NonNull File expandedFile) {
- File expansionFolder = getExpansionFolder(expandedFile);
- return expandedFile.getAbsolutePath()
- .substring(expansionFolder.getAbsolutePath().length() + 1);
- }
-
- /**
- * Determine the user's intention for a particular archive entry.
- * @param archivePath the archive entry
- * @return a {@link FileFilter.PackagingOption} as provided by the user in the build.gradle
- */
- @NonNull
- private PackagingOption getPackagingAction(@NonNull String archivePath) {
- if (packagingOptions != null) {
- if (pickFirsts.contains(archivePath)) {
- return PackagingOption.PICK_FIRST;
- }
- if (packagingOptions.getMerges().contains(archivePath)) {
- return PackagingOption.MERGE;
- }
- if (excludes.contains(archivePath)) {
- return PackagingOption.EXCLUDE;
- }
- }
- return PackagingOption.NONE;
- }
- }
-
- @NonNull
- @Override
- public ImmutableList<JavaResourcesLocation> getJavaResourcesLocations() {
- return ImmutableList.of(new JavaResourcesLocation(Type.FOLDER, getOutputDir()));
- }
-
- public static class Config implements TaskConfigAction<MergeJavaResourcesTask> {
-
- private final VariantScope scope;
-
- public Config(VariantScope variantScope) {
- this.scope = variantScope;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("merge", "JavaResources");
- }
-
- @Override
- public Class<MergeJavaResourcesTask> getType() {
- return MergeJavaResourcesTask.class;
- }
-
- @Override
- public void execute(MergeJavaResourcesTask mergeJavaResourcesTask) {
- mergeJavaResourcesTask.setVariantName(scope.getVariantConfiguration().getFullName());
-
- ConventionMappingHelper.map(mergeJavaResourcesTask, "sourceJavaResourcesFolder",
- new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getSourceFoldersJavaResDestinationDir().exists()
- ? scope.getSourceFoldersJavaResDestinationDir()
- : null;
- }
- });
-
- ConventionMappingHelper.map(mergeJavaResourcesTask, "packagedJarsJavaResourcesFolder",
- new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getPackagedJarsJavaResDestinationDir().exists()
- ? scope.getPackagedJarsJavaResDestinationDir()
- : null;
- }
- });
-
- File outputDir = scope.getJavaResourcesDestinationDir();
- if (!outputDir.exists() && !outputDir.mkdirs()) {
- throw new RuntimeException("Cannot create output directory " + outputDir);
- }
- mergeJavaResourcesTask.outputDir = outputDir;
-
- PackagingOptions packagingOptions =
- scope.getGlobalScope().getExtension().getPackagingOptions();
- mergeJavaResourcesTask.packagingOptionsFilter =
- new FileFilter(mergeJavaResourcesTask, packagingOptions);
- mergeJavaResourcesTask.packagingOptions = packagingOptions;
- scope.setPackagingOptionsFilter(mergeJavaResourcesTask.packagingOptionsFilter);
- }
- }
-}
-
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MockableAndroidJarTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MockableAndroidJarTask.java
deleted file mode 100644
index bf70530..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MockableAndroidJarTask.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import com.android.builder.testing.MockableJarGenerator;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Task for generating a mockable android.jar
- */
-public class MockableAndroidJarTask extends DefaultTask {
-
- private File mAndroidJar;
-
- private File mOutputFile;
-
- /**
- * Whether the generated jar should return default values from all methods or throw exceptions.
- */
- private boolean mReturnDefaultValues;
-
- @TaskAction
- public void createMockableJar() throws IOException {
- MockableJarGenerator generator = new MockableJarGenerator(getReturnDefaultValues());
- getOutputFile().delete();
- getLogger().info(String.format("Creating %s from $s.", getOutputFile().getAbsolutePath(),
- getAndroidJar().getAbsolutePath()));
- generator.createMockableJar(getAndroidJar(), getOutputFile());
- }
-
- @Input
- public boolean getReturnDefaultValues() {
- return mReturnDefaultValues;
- }
-
- public void setReturnDefaultValues(boolean returnDefaultValues) {
- mReturnDefaultValues = returnDefaultValues;
- }
-
- @OutputFile
- public File getOutputFile() {
- return mOutputFile;
- }
-
- public void setOutputFile(File outputFile) {
- mOutputFile = outputFile;
- }
-
- @InputFile
- public File getAndroidJar() {
- return mAndroidJar;
- }
-
- public void setAndroidJar(File androidJar) {
- mAndroidJar = androidJar;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.java
deleted file mode 100755
index e605002..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.tasks;
-
-import com.android.build.gradle.internal.dependency.DependencyChecker;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.SyncIssue;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.utils.StringHelper;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.tasks.TaskAction;
-
-import java.util.List;
-import java.util.Map;
-
-public class PrepareDependenciesTask extends BaseTask {
-
- private BaseVariantData variant;
- private final List<DependencyChecker> checkers = Lists.newArrayList();
-
- @TaskAction
- protected void prepare() {
- ApiVersion minSdkVersion = variant.getVariantConfiguration().getMinSdkVersion();
- int minSdk = 1;
- if (minSdkVersion.getCodename() != null) {
- minSdk = SdkVersionInfo.getApiByBuildCode(minSdkVersion.getCodename(), true);
- } else {
- minSdk = minSdkVersion.getApiLevel();
- }
-
- boolean foundError = false;
-
- for (DependencyChecker checker : checkers) {
- for (Map.Entry<ModuleVersionIdentifier, Integer> entry :
- checker.getLegacyApiLevels().entrySet()) {
- ModuleVersionIdentifier mavenVersion = entry.getKey();
- int api = entry.getValue();
- if (api > minSdk) {
- foundError = true;
- String configurationName = checker.getConfigurationDependencies().getName();
- getLogger().error(
- "Variant {} has a dependency on version {} of the legacy {} Maven " +
- "artifact, which corresponds to API level {}. This is not " +
- "compatible with min SDK of this module, which is {}. " +
- "Please use the 'gradle dependencies' task to debug your " +
- "dependencies graph.",
- StringHelper.capitalize(configurationName),
- mavenVersion.getVersion(),
- mavenVersion.getGroup(),
- api,
- minSdk);
- }
- }
-
- for (SyncIssue syncIssue : checker.getSyncIssues()) {
- foundError = true;
- getLogger().error(syncIssue.getMessage());
- }
- }
-
- if (foundError) {
- throw new GradleException("Dependency Error. See console for details.");
- }
-
- }
-
- public void addChecker(DependencyChecker checker) {
- checkers.add(checker);
- }
-
- public BaseVariantData getVariant() {
- return variant;
- }
-
- public void setVariant(BaseVariantData variant) {
- this.variant = variant;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.java
deleted file mode 100644
index 1ac6372..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.tasks;
-
-import com.android.build.gradle.internal.LibraryCache;
-
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-
-public class PrepareLibraryTask extends DefaultAndroidTask {
- private File bundle;
- private File explodedDir;
-
- @TaskAction
- public void prepare() {
- //LibraryCache.getCache().unzipLibrary(this.name, project, getBundle(), getExplodedDir())
- LibraryCache.unzipAar(getBundle(), getExplodedDir(), getProject());
- }
-
- @InputFile
- public File getBundle() {
- return bundle;
- }
-
- @OutputDirectory
- public File getExplodedDir() {
- return explodedDir;
- }
-
- public void setBundle(File bundle) {
- this.bundle = bundle;
- }
-
- public void setExplodedDir(File explodedDir) {
- this.explodedDir = explodedDir;
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.java
deleted file mode 100644
index 9e248e4..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.builder.testing.api.TestServer;
-import com.google.common.base.Preconditions;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-
-/**
- * Task sending APKs out to a {@link TestServer}
- */
-public class TestServerTask extends DefaultAndroidTask {
-
- private File testApk;
-
- File testedApk;
-
- TestServer testServer;
-
- @TaskAction
- public void sendToServer() {
- testServer.uploadApks(getVariantName(), getTestApk(), getTestedApk());
- }
-
- @InputFile
- public File getTestApk() {
- return testApk;
- }
-
- public void setTestApk(File testApk) {
- this.testApk = testApk;
- }
-
- @InputFile @Optional
- public File getTestedApk() {
- return testedApk;
- }
-
- public void setTestedApk(File testedApk) {
- this.testedApk = testedApk;
- }
-
- @NonNull
- @Override
- @Input
- public String getVariantName() {
- return Preconditions.checkNotNull(super.getVariantName(),
- "Test server task must have a variant name.");
- }
-
- public TestServer getTestServer() {
- return testServer;
- }
-
- public void setTestServer(TestServer testServer) {
- this.testServer = testServer;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.java
deleted file mode 100644
index 73adc95..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.tasks;
-
-import com.android.build.gradle.internal.LoggerWrapper;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.builder.sdk.SdkInfo;
-import com.android.builder.testing.ConnectedDeviceProvider;
-import com.android.builder.testing.api.DeviceConnector;
-import com.android.builder.testing.api.DeviceException;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.utils.ILogger;
-import com.android.utils.StringHelper;
-
-import org.gradle.api.Task;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-public class UninstallTask extends BaseTask {
-
- private BaseVariantData variant;
-
- private int mTimeOutInMs = 0;
-
- public UninstallTask() {
- this.getOutputs().upToDateWhen(new Spec<Task>() {
- @Override
- public boolean isSatisfiedBy(Task task) {
- getLogger().debug("Uninstall task is always run.");
- return false;
- }
- });
- }
-
- @TaskAction
- public void uninstall() throws DeviceException {
- final Logger logger = getLogger();
- final String applicationId = variant.getApplicationId();
-
- logger.info("Uninstalling app: {}", applicationId);
-
- final ILogger lifecycleLogger = new LoggerWrapper(getLogger(), LogLevel.LIFECYCLE);
- final DeviceProvider deviceProvider =
- new ConnectedDeviceProvider(getAdbExe(), lifecycleLogger);
-
- deviceProvider.init();
- final List<? extends DeviceConnector> devices = deviceProvider.getDevices();
-
- for (DeviceConnector device : devices) {
- device.uninstallPackage(applicationId, getTimeOutInMs(), getILogger());
- logger.lifecycle(
- "Uninstalling {} (from {}:{}) from device '{}' ({}).",
- applicationId, getProject().getName(),
- variant.getVariantConfiguration().getFullName(),
- device.getName(), device.getSerialNumber());
- }
-
- int n = devices.size();
- logger.quiet("Uninstalled {} from {} device{}.",
- applicationId, n, n == 1 ? "" : "s");
-
- }
-
-
- @InputFile
- public File getAdbExe() {
- SdkInfo sdkInfo = getBuilder().getSdkInfo();
- if (sdkInfo == null) {
- return null;
- }
- return sdkInfo.getAdb();
- }
-
- public BaseVariantData getVariant() {
- return variant;
- }
-
- public void setVariant(BaseVariantData variant) {
- this.variant = variant;
- }
-
- @Input
- public int getTimeOutInMs() {
- return mTimeOutInMs;
- }
-
- public void setTimeOutInMs(int timeoutInMs) {
- mTimeOutInMs = timeoutInMs;
- }
-
- public static class ConfigAction implements TaskConfigAction<UninstallTask> {
-
- private final VariantScope scope;
-
- public ConfigAction(VariantScope scope) {
- this.scope = scope;
- }
-
- @Override
- public String getName() {
- return "uninstall"
- + StringHelper.capitalize(scope.getVariantConfiguration().getFullName());
- }
-
- @Override
- public Class<UninstallTask> getType() {
- return UninstallTask.class;
- }
-
- @Override
- public void execute(UninstallTask uninstallTask) {
-
- uninstallTask.setDescription(
- "Uninstalls the " + scope.getVariantData().getDescription() + ".");
- uninstallTask.setGroup(TaskManager.INSTALL_GROUP);
- uninstallTask.setVariant(scope.getVariantData());
- uninstallTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- uninstallTask.setVariantName(scope.getVariantConfiguration().getFullName());
- uninstallTask.setTimeOutInMs(
- scope.getGlobalScope().getExtension().getAdbOptions().getTimeOutInMs());
-
- ConventionMappingHelper.map(uninstallTask, "adbExe", new Callable<File>() {
- @Override
- public File call() throws Exception {
- final SdkInfo info = scope.getGlobalScope().getSdkHandler().getSdkInfo();
- return (info == null ? null : info.getAdb());
- }
- });
-
- ((ApkVariantData) scope.getVariantData()).uninstallTask = uninstallTask;
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.java
deleted file mode 100644
index 0c3c898..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.builder.model.SigningConfig;
-import com.android.ide.common.signing.KeystoreHelper;
-import com.android.ide.common.signing.KeytoolException;
-import com.android.prefs.AndroidLocation;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.tooling.BuildException;
-
-import java.io.File;
-
-/**
- * A validate task that creates the debug keystore if it's missing.
- * It only creates it if it's in the default debug keystore location.
- *
- * It's linked to a given SigningConfig
- *
- */
-public class ValidateSigningTask extends BaseTask {
-
- private SigningConfig signingConfig;
-
- public void setSigningConfig(SigningConfig signingConfig) {
- this.signingConfig = signingConfig;
- }
-
- public SigningConfig getSigningConfig() {
- return signingConfig;
- }
-
- /**
- * Annotated getter for task input.
- *
- * This is an Input and not an InputFile because the file might not exist.
- * This is not actually used by the task, this is only for Gradle to check inputs.
- *
- * @return the path of the keystore.
- */
- @Input @Optional
- public String getStoreLocation() {
- File f = signingConfig.getStoreFile();
- if (f != null) {
- return f.getAbsolutePath();
- }
- return null;
- }
-
- @TaskAction
- public void validate() throws AndroidLocation.AndroidLocationException, KeytoolException {
- File storeFile = signingConfig.getStoreFile();
- if (storeFile == null) {
- throw new IllegalArgumentException(
- "Keystore file not set for signing config " + signingConfig.getName());
- }
- if (!storeFile.exists()) {
- if (KeystoreHelper.defaultDebugKeystoreLocation().equals(storeFile.getAbsolutePath())) {
- checkState(signingConfig.isSigningReady(), "Debug signing config not ready.");
-
- getLogger().info(
- "Creating default debug keystore at {}",
- storeFile.getAbsolutePath());
-
- //noinspection ConstantConditions - isSigningReady() called above
- if (!KeystoreHelper.createDebugStore(
- signingConfig.getStoreType(), signingConfig.getStoreFile(),
- signingConfig.getStorePassword(), signingConfig.getKeyPassword(),
- signingConfig.getKeyAlias(), getILogger())) {
- throw new BuildException("Unable to recreate missing debug keystore.", null);
- }
- } else {
- throw new IllegalArgumentException(
- String.format(
- "Keystore file %s not found for signing config '%s'.",
- storeFile.getAbsolutePath(),
- signingConfig.getName()));
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateMainDexList.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateMainDexList.groovy
deleted file mode 100644
index c5bf7a4..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateMainDexList.groovy
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.internal.tasks.multidex
-
-import com.android.build.gradle.internal.PostCompilationData
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.BaseTask
-import com.google.common.base.Charsets
-import com.google.common.base.Joiner
-import com.google.common.io.Files
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-
-import java.util.concurrent.Callable
-
-/**
- * Task to create the main (non-obfuscated) list of classes to keep.
- * It uses a jar containing all the classes, as well as a shrinked jar file created by proguard.
- *
- * Optionally, it can use a manual list of classes/jars to keep.
- */
-public class CreateMainDexList extends BaseTask {
- @InputFile @Optional
- File allClassesJarFile
-
- @InputFile
- File componentsJarFile
-
- @OutputFile
- File outputFile
-
- @InputFile @Optional
- File includeInMainDexJarFile
-
- @InputFile @Optional
- File mainDexListFile
-
- @InputFile
- File getDxJar() {
- return builder.getDxJar()
- }
-
- @TaskAction
- void output() {
- if (getAllClassesJarFile() == null) {
- throw new NullPointerException("No input file")
- }
-
- // manifest components plus immediate dependencies must be in the main dex.
- File _allClassesJarFile = getAllClassesJarFile()
- Set<String> mainDexClasses = callDx(_allClassesJarFile, getComponentsJarFile())
-
- // add additional classes specified via a jar file.
- File _includeInMainDexJarFile = getIncludeInMainDexJarFile()
- if (_includeInMainDexJarFile != null) {
- // proguard shrinking is overly aggressive when it comes to removing
- // interface classes: even if an interface is implemented by a concrete
- // class, if no code actually references the interface class directly
- // (i.e., code always references the concrete class), proguard will
- // remove the interface class when shrinking. This is problematic,
- // as the runtime verifier still needs the interface class to be
- // present, or the concrete class won't be valid. Use a
- // ClassReferenceListBuilder here (only) to pull in these missing
- // interface classes. Note that doing so brings in other unnecessary
- // stuff, too; next time we're low on main dex space, revisit this!
- mainDexClasses.addAll(callDx(_allClassesJarFile, _includeInMainDexJarFile))
- }
-
- if (mainDexListFile != null) {
- Set<String> mainDexList = new HashSet<String>(Files.readLines(mainDexListFile, Charsets.UTF_8))
- mainDexClasses.addAll(mainDexList)
- }
-
- String fileContent = Joiner.on(System.getProperty("line.separator")).join(mainDexClasses)
-
- Files.write(fileContent, getOutputFile(), Charsets.UTF_8)
- }
-
- private Set<String> callDx(File allClassesJarFile, File jarOfRoots) {
- return getBuilder().createMainDexList(allClassesJarFile, jarOfRoots)
- }
-
- public static class ConfigAction implements TaskConfigAction<CreateMainDexList> {
-
- VariantScope scope;
-
- private Callable<List<File>> inputFiles
-
- ConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope
- inputFiles = pcData.inputFilesCallable;
- }
-
- @Override
- String getName() {
- return scope.getTaskName("create", "MainDexClassList");
- }
-
- @Override
- Class<CreateMainDexList> getType() {
- return CreateMainDexList
- }
-
- @Override
- void execute(CreateMainDexList createMainDexList) {
- createMainDexList.androidBuilder = scope.globalScope.androidBuilder
- createMainDexList.setVariantName(scope.getVariantConfiguration().getFullName())
-
- def files = inputFiles
- createMainDexList.allClassesJarFile = files.call().first()
- ConventionMappingHelper.map(createMainDexList, "componentsJarFile") {
- scope.getProguardComponentsJarFile()
- }
- // ConventionMappingHelper.map(createMainDexListTask, "includeInMainDexJarFile") { mainDexJarFile }
- createMainDexList.mainDexListFile = scope.manifestKeepListFile
- createMainDexList.outputFile = scope.getMainDexListFile()
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.groovy
deleted file mode 100644
index 894352f..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.groovy
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.internal.tasks.multidex
-
-import com.android.build.gradle.internal.PostCompilationData
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.google.common.base.Charsets
-import com.google.common.io.Files
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import org.xml.sax.Attributes
-import org.xml.sax.helpers.DefaultHandler
-
-import javax.xml.parsers.SAXParser
-import javax.xml.parsers.SAXParserFactory
-
-
-class CreateManifestKeepList extends DefaultAndroidTask {
-
- @InputFile
- File manifest
-
- @OutputFile
- File outputFile
-
- @InputFile @Optional
- File proguardFile
-
- Closure filter
-
- @TaskAction
- void generateKeepListFromManifest() {
- SAXParser parser = SAXParserFactory.newInstance().newSAXParser()
-
- Writer out = new BufferedWriter(new FileWriter(getOutputFile()))
- try {
- parser.parse(getManifest(), new ManifestHandler(out))
-
- // add a couple of rules that cannot be easily parsed from the manifest.
- out.write(
-"""-keep public class * extends android.app.backup.BackupAgent {
- <init>();
-}
--keep public class * extends java.lang.annotation.Annotation {
- *;
-}
-""")
-
- if (proguardFile != null) {
- out.write(Files.toString(proguardFile, Charsets.UTF_8))
- }
- } finally {
- out.close()
- }
- }
-
- private static String DEFAULT_KEEP_SPEC = "{ <init>(); }"
- private static Map<String, String> KEEP_SPECS = [
- 'application' : """{
- <init>();
- void attachBaseContext(android.content.Context);
-}""",
- 'activity' : DEFAULT_KEEP_SPEC,
- 'service' : DEFAULT_KEEP_SPEC,
- 'receiver' : DEFAULT_KEEP_SPEC,
- 'provider' : DEFAULT_KEEP_SPEC,
- 'instrumentation' : DEFAULT_KEEP_SPEC,
- ]
-
- private class ManifestHandler extends DefaultHandler {
- private Writer out
-
- ManifestHandler(Writer out) {
- this.out = out
- }
-
- @Override
- void startElement(String uri, String localName, String qName, Attributes attr) {
- String keepSpec = (String)CreateManifestKeepList.KEEP_SPECS[qName]
- if (keepSpec) {
-
- boolean keepIt = true
- if (CreateManifestKeepList.this.filter) {
- // for ease of use, turn 'attr' into a simple map
- Map<String, String> attrMap = [:]
- for (int i = 0; i < attr.getLength(); i++) {
- attrMap[attr.getQName(i)] = attr.getValue(i)
- }
- keepIt = CreateManifestKeepList.this.filter(qName, attrMap)
- }
-
- if (keepIt) {
- out.write((String)"-keep class ${attr.getValue('android:name')} $keepSpec\n")
- }
- }
- }
- }
-
- public static class ConfigAction implements TaskConfigAction<CreateManifestKeepList> {
-
- VariantScope scope;
- PostCompilationData pcData;
-
- ConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope
- this.pcData = pcData
- }
-
- @Override
- String getName() {
- return scope.getTaskName("collect", "MultiDexComponents");
- }
-
- @Override
- Class<CreateManifestKeepList> getType() {
- return CreateManifestKeepList
- }
-
- @Override
- void execute(CreateManifestKeepList manifestKeepListTask) {
- manifestKeepListTask.setVariantName(scope.getVariantConfiguration().getFullName())
-
- // since all the output have the same manifest, besides the versionCode,
- // we can take any of the output and use that.
- final BaseVariantOutputData output = scope.variantData.outputs.get(0)
- ConventionMappingHelper.map(manifestKeepListTask, "manifest") {
- output.getScope().getManifestOutputFile()
- }
-
- manifestKeepListTask.proguardFile = scope.variantConfiguration.getMultiDexKeepProguard()
- manifestKeepListTask.outputFile = scope.getManifestKeepListFile();
-
- //variant.ext.collectMultiDexComponents = manifestKeepListTask
- }
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/JarMergingTask.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/JarMergingTask.groovy
deleted file mode 100644
index 0a81415..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/JarMergingTask.groovy
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks.multidex
-
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask
-import com.android.build.gradle.internal.PostCompilationData
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.google.common.collect.Sets
-import com.google.common.hash.Hashing
-import com.google.common.io.Files
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-
-import java.util.concurrent.Callable
-import java.util.jar.JarEntry
-import java.util.jar.JarOutputStream
-import java.util.zip.ZipEntry
-import java.util.zip.ZipInputStream
-
-/**
- * Custom Jar task that can merge other jars.
- * This ignores all non .class files since this is strictly to
- * handle code.
- */
-class JarMergingTask extends DefaultAndroidTask {
-
- // could be files (jar) or folders of classes.
- @InputFiles
- Collection<File> inputJars
-
- @InputDirectory @Optional
- File inputDir
-
- @OutputFile
- File jarFile
-
- @TaskAction
- void createJar() {
- jarFile.delete()
-
- FileOutputStream fos = new FileOutputStream(jarFile)
- JarOutputStream jos = new JarOutputStream(fos)
-
- final byte[] buffer = new byte[8192]
- Collection<File> jars = getInputJars()
-
- final Set<String> hashs = Sets.newHashSetWithExpectedSize(jars.size())
-
- for (File file : jars) {
-
- // TODO remove once we can properly add a library as a dependency of its test.
- String hash = Files.hash(file, Hashing.sha1()).toString()
- if (hashs.contains(hash)) {
- continue
- }
- hashs.add(hash)
-
- logger.info("INPUT: " + file)
- processJarFile(jos, file, buffer)
- }
-
- File _inputDir = getInputDir()
- if (_inputDir != null) {
- processFolder(jos, "", _inputDir, buffer)
- }
-
- jos.close()
- }
-
- private void processFolder(JarOutputStream jos, String path, File folder, byte[] buffer) {
- for (File file : folder.listFiles()) {
- if (file.isFile()) {
- // new entry
- jos.putNextEntry(new JarEntry(path + file.getName()))
-
- // put the file content
- FileInputStream fis = new FileInputStream(file)
- int count
- while ((count = fis.read(buffer)) != -1) {
- jos.write(buffer, 0, count)
- }
- fis.close()
-
- // close the entry
- jos.closeEntry()
- } else if (file.isDirectory()) {
- processFolder(jos, path + file.getName() + "/", file, buffer)
- }
- }
- }
-
- private static void processJarFile(JarOutputStream jos, File file, byte[] buffer) {
- FileInputStream fis = new FileInputStream(file)
- ZipInputStream zis = new ZipInputStream(fis)
-
- try {
- // loop on the entries of the jar file package and put them in the final jar
- ZipEntry entry
- while ((entry = zis.getNextEntry()) != null) {
- // do not take directories or anything inside a potential META-INF folder.
- if (entry.isDirectory()) {
- continue
- }
-
- String name = entry.getName()
- if (!name.endsWith(".class")) {
- continue
- }
-
- JarEntry newEntry
-
- // Preserve the STORED method of the input entry.
- if (entry.getMethod() == JarEntry.STORED) {
- newEntry = new JarEntry(entry)
- } else {
- // Create a new entry so that the compressed len is recomputed.
- newEntry = new JarEntry(name)
- }
-
- // add the entry to the jar archive
- jos.putNextEntry(newEntry)
-
- // read the content of the entry from the input stream, and write it into the archive.
- int count
- while ((count = zis.read(buffer)) != -1) {
- jos.write(buffer, 0, count)
- }
-
- // close the entries for this file
- jos.closeEntry()
- zis.closeEntry()
- }
- } finally {
- zis.close()
- }
-
- fis.close()
- }
-
- public static class ConfigAction implements TaskConfigAction<JarMergingTask> {
-
- private VariantScope scope
-
- private Callable<File> inputDir;
-
- private Callable<List<File>> inputLibraries;
-
- ConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope
- inputDir = pcData.inputDirCallable
- inputLibraries = pcData.inputLibrariesCallable
- }
-
- @Override
- String getName() {
- return scope.getTaskName("packageAll", "ClassesForMultiDex")
- }
-
- @Override
- Class<JarMergingTask> getType() {
- return JarMergingTask
- }
-
- @Override
- void execute(JarMergingTask jarMergingTask) {
- jarMergingTask.setVariantName(scope.getVariantConfiguration().getFullName())
- ConventionMappingHelper.map(jarMergingTask, "inputJars", inputLibraries)
- ConventionMappingHelper.map(jarMergingTask, "inputDir", inputDir)
-
- jarMergingTask.jarFile = scope.getJarMergingOutputFile()
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/RetraceMainDexList.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/RetraceMainDexList.groovy
deleted file mode 100644
index 23fdade..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/RetraceMainDexList.groovy
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.internal.tasks.multidex
-
-import com.android.build.gradle.internal.PostCompilationData
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask
-import com.google.common.base.Charsets
-import com.google.common.base.Joiner
-import com.google.common.collect.Maps
-import com.google.common.collect.Sets
-import com.google.common.io.Files
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
-
-/**
- * Take a list of classes for the main dex (that was computed before obfuscation),
- * a proguard-generated mapping file and create a new list of classes with the new
- * obfuscated names.
- */
-class RetraceMainDexList extends DefaultAndroidTask {
-
- @InputFile
- File mainDexListFile
-
- @OutputFile
- File outputFile
-
- /**
- * Gradle doesn't really handle optional inputs as being a file that
- * doesn't exist. Optional means the task field is null. So we do some
- * custom logic to return null if the file doesn't exist since we cannot
- * know ahead of time without parsing the proguard config rule files.
- */
- @InputFile @Optional
- File getMappingFileInput() {
- File file = getMappingFile()
- if (file != null && file.isFile()) {
- return file
- }
-
- return null
- }
-
- File mappingFile
-
- @TaskAction
- void retrace() {
-
- File mapping = getMappingFile()
- // if there is no mapping file or if it doesn't exist, then we just copy from the main
- // dex list ot the output.
- if (mapping == null || !mapping.isFile()) {
- Files.copy(getMainDexListFile(), getOutputFile())
- return
- }
-
- // load the main class names
- List<String> classes = Files.readLines(getMainDexListFile(), Charsets.UTF_8)
-
- // load the mapping file and create a dictionary
- List<String> mappingLines = Files.readLines(mapping, Charsets.UTF_8)
- Map<String, String> map = createDict(mappingLines)
-
- // create the deobfuscated class list. This is a set to detect dups
- // You can have the same class coming from the non-obfuscated list and from
- // the shrinked jar that have different names until we remap them.
- Set<String> deobfuscatedClasses = Sets.newHashSetWithExpectedSize(classes.size())
-
- for (String clazz : classes) {
- String fullName = map.get(clazz)
-
- deobfuscatedClasses.add(fullName != null ? fullName : clazz)
- }
-
- String fileContent =
- Joiner.on(System.getProperty("line.separator")).join(deobfuscatedClasses)
- Files.write(fileContent, getOutputFile(), Charsets.UTF_8)
- }
-
- static Map<String, String> createDict(List<String> lines) {
- Map<String, String> map = Maps.newHashMap()
-
- for (String line : lines) {
- if (line.startsWith(" ")) {
- continue
- }
-
- int pos = line.indexOf(" -> ")
- if (pos == -1) {
- throw new RuntimeException("unable to read mapping file.")
- }
-
- String fullName = line.substring(0, pos)
- String obfuscatedName = line.substring(pos + 4, line.size() - 1)
-
- map.put(obfuscatedName.replace(".", "/") + ".class",
- fullName.replace(".", "/") + ".class")
- }
-
- return map
- }
-
-
- public static class ConfigAction implements TaskConfigAction<RetraceMainDexList> {
-
- VariantScope scope;
-
- PostCompilationData pcData;
-
- ConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope
- this.pcData = pcData
- }
-
- @Override
- String getName() {
- return scope.getTaskName("retrace", "MainDexClassList");
- }
-
- @Override
- Class<RetraceMainDexList> getType() {
- return RetraceMainDexList.class
- }
-
- @Override
- void execute(RetraceMainDexList retraceTask) {
- retraceTask.setVariantName(scope.getVariantConfiguration().getFullName())
- ConventionMappingHelper.map(retraceTask, "mainDexListFile") { scope.getMainDexListFile() }
- ConventionMappingHelper.map(retraceTask, "mappingFile") {
- scope.variantData.getMappingFile()
- }
- retraceTask.outputFile = new File(
- "${scope.globalScope.buildDir}/${FD_INTERMEDIATES}/multi-dex/" +
- "${scope.variantConfiguration.dirName}/maindexlist_deobfuscated.txt")
-
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java
deleted file mode 100644
index 28edd3e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.tasks.Dex;
-import com.android.build.gradle.tasks.PreDex;
-
-import org.gradle.api.DefaultTask;
-
-import java.util.Collection;
-
-/**
- * Base data about a variant that generates an APK file.
- */
-public abstract class ApkVariantData extends BaseVariantData<ApkVariantOutputData> {
-
- public PreDex preDexTask;
- public Dex dexTask;
- public DefaultTask installTask;
- public DefaultTask uninstallTask;
-
- protected ApkVariantData(
- @NonNull AndroidConfig androidConfig,
- @NonNull TaskManager taskManager,
- @NonNull GradleVariantConfiguration config) {
- super(androidConfig, taskManager, config);
- }
-
- @Override
- @NonNull
- protected ApkVariantOutputData doCreateOutput(
- OutputFile.OutputType outputType,
- Collection<FilterData> filters) {
- return new ApkVariantOutputData(outputType, filters, this, taskManager);
- }
-
- @Override
- @NonNull
- public String getDescription() {
- if (getVariantConfiguration().hasFlavors()) {
- return String.format("%s%s build",
- getCapitalizedBuildTypeName(),
- getCapitalizedFlavorName());
- } else {
- return String.format("%s build", getCapitalizedBuildTypeName());
- }
- }
-
- public boolean isSigned() {
- return getVariantConfiguration().isSigningReady();
- }
-
- public boolean getZipAlignEnabled() {
- return getVariantConfiguration().getBuildType().isZipAlignEnabled();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantOutputData.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantOutputData.java
deleted file mode 100644
index 9cc1804..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantOutputData.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.api.ApkOutputFile;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.tasks.PackageApplication;
-import com.android.build.gradle.tasks.SplitZipAlign;
-import com.android.build.gradle.tasks.ZipAlign;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableList;
-
-import org.gradle.api.Task;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Base output data for a variant that generates an APK file.
- */
-public class ApkVariantOutputData extends BaseVariantOutputData {
-
- public PackageApplication packageApplicationTask;
- public ZipAlign zipAlignTask;
- public SplitZipAlign splitZipAlign;
-
- private TaskManager taskManager;
- private int versionCodeOverride = -1;
- private String versionNameOverride = null;
-
- public ApkVariantOutputData(
- @NonNull OutputFile.OutputType outputType,
- @NonNull Collection<FilterData> filters,
- @NonNull BaseVariantData variantData,
- @NonNull TaskManager taskManager) {
- super(outputType, filters, variantData);
- this.taskManager = taskManager;
- }
-
- @Override
- public void setOutputFile(@NonNull File file) {
- if (zipAlignTask != null) {
- zipAlignTask.setOutputFile(file);
- } else {
- packageApplicationTask.setOutputFile(file);
- }
- }
-
- @Nullable
- @Override
- public File getOutputFile() {
- if (zipAlignTask != null) {
- return zipAlignTask.getOutputFile();
- }
-
- return packageApplicationTask == null ? null : packageApplicationTask.getOutputFile();
- }
-
- @NonNull
- @Override
- public ImmutableList<ApkOutputFile> getOutputs() {
- ImmutableList.Builder<ApkOutputFile> outputs = ImmutableList.builder();
- outputs.add(getMainOutputFile());
- if (splitZipAlign != null) {
- outputs.addAll(splitZipAlign.getOutputSplitFiles());
- } else {
- if (packageSplitResourcesTask != null) {
- outputs.addAll(packageSplitResourcesTask.getOutputSplitFiles());
- }
- }
- return outputs.build();
- }
-
- @NonNull
- public ZipAlign createZipAlignTask(@NonNull String taskName, @NonNull File inputFile,
- @NonNull File outputFile) {
- //noinspection VariableNotUsedInsideIf
- if (zipAlignTask != null) {
- throw new RuntimeException(String.format(
- "ZipAlign task for variant '%s' already exists.", variantData.getName()));
- }
-
- zipAlignTask = taskManager.createZipAlignTask(taskName, inputFile, outputFile);
-
- // setup dependencies
- assembleTask.dependsOn(zipAlignTask);
-
- return zipAlignTask;
- }
-
- @Override
- public int getVersionCode() {
- if (versionCodeOverride > 0) {
- return versionCodeOverride;
- }
-
- return variantData.getVariantConfiguration().getVersionCode();
- }
-
- @NonNull
- @Override
- public File getSplitFolder() {
- return getOutputFile().getParentFile();
- }
-
- public String getVersionName() {
- if (versionNameOverride != null) {
- return versionNameOverride;
- }
-
- return variantData.getVariantConfiguration().getVersionName();
- }
-
- public void setVersionCodeOverride(int versionCodeOverride) {
- this.versionCodeOverride = versionCodeOverride;
- }
-
- public int getVersionCodeOverride() {
- return versionCodeOverride;
- }
-
- public void setVersionNameOverride(String versionNameOverride) {
- this.versionNameOverride = versionNameOverride;
- }
-
- public String getVersionNameOverride() {
- return versionNameOverride;
- }
-
- /**
- * Returns the list of {@link Supplier} for this variant. Some variant can produce more
- * than one file when dealing with pure splits.
- * @return the complete list of tasks producing an APK for this variant.
- */
- public List<FileSupplier> getSplitOutputFileSuppliers() {
- ImmutableList.Builder<FileSupplier> tasks = ImmutableList.builder();
- if (splitZipAlign != null || packageSplitResourcesTask != null) {
- tasks.addAll(splitZipAlign == null ? packageSplitResourcesTask.getOutputFileSuppliers()
- : splitZipAlign.getOutputFileSuppliers());
- }
- // ABI splits zip are aligned together with the other densities in the splitZipAlign task
- // so only add the ABI splits from the package task if there was no splitZipAlign task.
- if (packageSplitAbiTask != null && splitZipAlign == null) {
- tasks.addAll(packageSplitAbiTask.getOutputFileSuppliers());
- }
- return tasks.build();
- }
-
- @Nullable
- public FileSupplier getMetadataFile() throws IOException {
-
- if (splitZipAlign == null) {
- return null;
- }
- return new FileSupplier() {
- @NonNull
- @Override
- public Task getTask() {
- return splitZipAlign;
- }
-
- @Override
- public File get() {
- return splitZipAlign.getApkMetadataFile();
- }
- };
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java
deleted file mode 100644
index 99251f1..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.builder.core.VariantType;
-import com.google.common.collect.Maps;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Data about a variant that produce an application APK
- */
-public class ApplicationVariantData extends ApkVariantData implements TestedVariantData {
- private final Map<VariantType, TestVariantData> testVariants;
- private Set<String> compatibleScreens = null;
-
- public ApplicationVariantData(
- @NonNull AndroidConfig androidConfig,
- @NonNull GradleVariantConfiguration config,
- @NonNull TaskManager taskManager) {
- super(androidConfig, taskManager, config);
- testVariants = Maps.newEnumMap(VariantType.class);
- }
-
-
- public void setCompatibleScreens(Set<String> compatibleScreens) {
- this.compatibleScreens = compatibleScreens;
- }
-
- @NonNull
- public Set<String> getCompatibleScreens() {
- if (compatibleScreens == null) {
- return Collections.emptySet();
- }
-
- return compatibleScreens;
- }
-
- @Override
- public void setTestVariantData(
- @NonNull TestVariantData testVariantData,
- @NonNull VariantType type) {
- testVariants.put(type, testVariantData);
- }
-
- @Nullable
- @Override
- public TestVariantData getTestVariantData(@NonNull VariantType type) {
- return testVariants.get(type);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java
deleted file mode 100644
index 7b42dd1..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.variant;
-
-import static com.android.build.OutputFile.NO_FILTER;
-import static com.android.builder.core.BuilderConstants.DEBUG;
-import static com.android.builder.core.BuilderConstants.RELEASE;
-
-import com.android.annotations.NonNull;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.api.ApplicationVariant;
-import com.android.build.gradle.api.BaseVariantOutput;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.VariantModel;
-import com.android.build.gradle.internal.api.ApkVariantImpl;
-import com.android.build.gradle.internal.api.ApkVariantOutputImpl;
-import com.android.build.gradle.internal.api.ApplicationVariantImpl;
-import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.build.gradle.internal.model.FilterDataImpl;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.VariantType;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Project;
-import org.gradle.internal.reflect.Instantiator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * An implementation of VariantFactory for a project that generates APKs.
- *
- * This can be an app project, or a test-only project, though the default
- * behavior is app.
- */
-public class ApplicationVariantFactory implements VariantFactory {
-
- Instantiator instantiator;
- @NonNull
- protected final AndroidConfig extension;
- @NonNull
- private final AndroidBuilder androidBuilder;
-
- public ApplicationVariantFactory(
- @NonNull Instantiator instantiator,
- @NonNull AndroidBuilder androidBuilder,
- @NonNull AndroidConfig extension) {
- this.instantiator = instantiator;
- this.androidBuilder = androidBuilder;
- this.extension = extension;
- }
-
- @Override
- @NonNull
- public BaseVariantData createVariantData(
- @NonNull GradleVariantConfiguration variantConfiguration,
- @NonNull TaskManager taskManager) {
- ApplicationVariantData variant =
- new ApplicationVariantData(extension, variantConfiguration, taskManager);
-
- variant.calculateFilters(extension.getSplits());
-
- Set<String> densities = variant.getFilters(OutputFile.FilterType.DENSITY);
- Set<String> abis = variant.getFilters(OutputFile.FilterType.ABI);
-
- if (!densities.isEmpty()) {
- variant.setCompatibleScreens(extension.getSplits().getDensity()
- .getCompatibleScreens());
- }
-
- // create its outputs
- if (variant.getSplitHandlingPolicy() ==
- BaseVariantData.SplitHandlingPolicy.PRE_21_POLICY) {
-
- // Always dd an entry with no filter for universal and add it FIRST,
- // since code assume that the first variant output will be the universal one.
- List<String> orderedDensities = new ArrayList<String>();
- orderedDensities.add(NO_FILTER);
- orderedDensities.addAll(densities);
-
- List<String> orderedAbis = new ArrayList<String>();
- // if the abi list is empty or we must generate a universal apk, add a NO_FILTER
- if (abis.isEmpty() || (extension.getSplits().getAbi().isEnable() &&
- extension.getSplits().getAbi().isUniversalApk())) {
- orderedAbis.add(NO_FILTER);
- }
- orderedAbis.addAll(abis);
-
- // create its outputs
- for (String density : orderedDensities) {
- for (String abi : orderedAbis) {
- ImmutableList.Builder<FilterData> builder = ImmutableList.builder();
- if (density != null) {
- builder.add(FilterDataImpl.build(OutputFile.DENSITY, density));
- }
- if (abi != null) {
- builder.add(FilterDataImpl.build(OutputFile.ABI, abi));
- }
- variant.createOutput(
- OutputFile.OutputType.FULL_SPLIT,
- builder.build());
- }
- }
- } else {
- variant.createOutput(OutputFile.OutputType.MAIN,
- Collections.<FilterData>emptyList());
- }
-
- return variant;
- }
-
- @Override
- @NonNull
- public ApplicationVariant createVariantApi(
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
- @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
- // create the base variant object.
- ApplicationVariantImpl variant = instantiator.newInstance(
- ApplicationVariantImpl.class,
- variantData,
- androidBuilder,
- readOnlyObjectProvider);
-
- // now create the output objects
- createApkOutputApiObjects(instantiator, variantData, variant);
-
- return variant;
- }
-
- public static void createApkOutputApiObjects(
- @NonNull Instantiator instantiator,
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
- @NonNull ApkVariantImpl variant) {
- List<? extends BaseVariantOutputData> outputList = variantData.getOutputs();
- List<BaseVariantOutput> apiOutputList = Lists.newArrayListWithCapacity(outputList.size());
-
- for (BaseVariantOutputData variantOutputData : outputList) {
- ApkVariantOutputData apkOutput = (ApkVariantOutputData) variantOutputData;
-
- ApkVariantOutputImpl output = instantiator.newInstance(
- ApkVariantOutputImpl.class, apkOutput);
-
- apiOutputList.add(output);
- }
-
- variant.addOutputs(apiOutputList);
- }
-
- @NonNull
- @Override
- public VariantType getVariantConfigurationType() {
- return VariantType.DEFAULT;
- }
-
- @Override
- public boolean isLibrary() {
- return false;
- }
-
- @Override
- public boolean hasTestScope() {
- return true;
- }
-
- @Override
- public void validateModel(@NonNull VariantModel model){
- // No additional checks for ApplicationVariantFactory, so just return.
- }
-
- @Override
- public void preVariantWork(Project project) {
- // nothing to be done here.
- }
-
- @Override
- public void createDefaultComponents(
- @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
- @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
- @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
- // must create signing config first so that build type 'debug' can be initialized
- // with the debug signing config.
- signingConfigs.create(DEBUG);
- buildTypes.create(DEBUG);
- buildTypes.create(RELEASE);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java
deleted file mode 100644
index d154358..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java
+++ /dev/null
@@ -1,654 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.api.AndroidSourceSet;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.coverage.JacocoInstrumentTask;
-import com.android.build.gradle.internal.dependency.VariantDependencies;
-import com.android.build.gradle.internal.dsl.Splits;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.CheckManifest;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.tasks.GenerateApkDataTask;
-import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
-import com.android.build.gradle.tasks.AidlCompile;
-import com.android.build.gradle.tasks.BinaryFileProviderTask;
-import com.android.build.gradle.tasks.GenerateBuildConfig;
-import com.android.build.gradle.tasks.GenerateResValues;
-import com.android.build.gradle.tasks.JackTask;
-import com.android.build.gradle.tasks.MergeAssets;
-import com.android.build.gradle.tasks.MergeResources;
-import com.android.build.gradle.tasks.NdkCompile;
-import com.android.build.gradle.tasks.ProcessAndroidResources;
-import com.android.build.gradle.tasks.RenderscriptCompile;
-import com.android.builder.core.VariantType;
-import com.android.builder.model.SourceProvider;
-import com.android.ide.common.res2.ResourceSet;
-import com.android.utils.StringHelper;
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.Task;
-import org.gradle.api.logging.Logging;
-import org.gradle.api.tasks.Copy;
-import org.gradle.api.tasks.Sync;
-import org.gradle.api.tasks.bundling.Jar;
-import org.gradle.api.tasks.compile.AbstractCompile;
-import org.gradle.api.tasks.compile.JavaCompile;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Base data about a variant.
- */
-public abstract class BaseVariantData<T extends BaseVariantOutputData> {
-
- public enum SplitHandlingPolicy {
- /**
- * Any release before L will create fake splits where each split will be the entire
- * application with the split specific resources.
- */
- PRE_21_POLICY,
-
- /**
- * Android L and after, the splits are pure splits where splits only contain resources
- * specific to the split characteristics.
- */
- RELEASE_21_AND_AFTER_POLICY
- }
-
-
- @NonNull
- protected final AndroidConfig androidConfig;
- @NonNull
- protected final TaskManager taskManager;
- @NonNull
- private final GradleVariantConfiguration variantConfiguration;
-
- private VariantDependencies variantDependency;
-
- // Needed for ModelBuilder. Should be removed once VariantScope can replace BaseVariantData.
- @NonNull
- private final VariantScope scope;
-
- public Task preBuildTask;
- public PrepareDependenciesTask prepareDependenciesTask;
- public ProcessAndroidResources generateRClassTask;
-
- public Task sourceGenTask;
- public Task resourceGenTask;
- public Task assetGenTask;
- public CheckManifest checkManifestTask;
-
- public RenderscriptCompile renderscriptCompileTask;
- public AidlCompile aidlCompileTask;
- public MergeResources mergeResourcesTask;
- public MergeAssets mergeAssetsTask;
- public GenerateBuildConfig generateBuildConfigTask;
- public GenerateResValues generateResValuesTask;
- public Copy copyApkTask;
- public GenerateApkDataTask generateApkDataTask;
-
- public Sync processJavaResourcesTask;
- public NdkCompile ndkCompileTask;
-
- /** Can be JavaCompile or JackTask depending on user's settings. */
- public AbstractCompile javaCompilerTask;
- public JavaCompile javacTask;
- public JackTask jackTask;
- public Jar classesJarTask;
- // empty anchor compile task to set all compilations tasks as dependents.
- public Task compileTask;
- public JacocoInstrumentTask jacocoInstrumentTask;
-
- public FileSupplier mappingFileProviderTask;
- public BinaryFileProviderTask binayFileProviderTask;
-
- // TODO : why is Jack not registered as the obfuscationTask ???
- public Task obfuscationTask;
-
- // Task to assemble the variant and all its output.
- public Task assembleVariantTask;
-
- private Object[] javaSources;
-
- private List<File> extraGeneratedSourceFolders;
- private List<File> extraGeneratedResFolders;
-
- private final List<T> outputs = Lists.newArrayListWithExpectedSize(4);
-
- private Set<String> densityFilters;
- private Set<String> languageFilters;
- private Set<String> abiFilters;
-
- /**
- * If true, variant outputs will be considered signed. Only set if you manually set the outputs
- * to point to signed files built by other tasks.
- */
- public boolean outputsAreSigned = false;
-
- private SplitHandlingPolicy mSplitHandlingPolicy;
-
-
- public BaseVariantData(
- @NonNull AndroidConfig androidConfig,
- @NonNull TaskManager taskManager,
- @NonNull GradleVariantConfiguration variantConfiguration) {
- this.androidConfig = androidConfig;
- this.variantConfiguration = variantConfiguration;
- this.taskManager = taskManager;
-
- // eventually, this will require a more open ended comparison.
- mSplitHandlingPolicy =
- androidConfig.getGeneratePureSplits()
- && variantConfiguration.getMinSdkVersion().getApiLevel() >= 21
- ? SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY
- : SplitHandlingPolicy.PRE_21_POLICY;
-
- // warn the user in case we are forced to ignore the generatePureSplits flag.
- if (androidConfig.getGeneratePureSplits()
- && mSplitHandlingPolicy != SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY) {
- Logging.getLogger(BaseVariantData.class).warn(
- String.format("Variant %s, MinSdkVersion %s is too low (<21) "
- + "to support pure splits, reverting to full APKs",
- variantConfiguration.getFullName(),
- variantConfiguration.getMinSdkVersion().getApiLevel()));
- }
- scope = new VariantScope(taskManager.getGlobalScope(), this);
- taskManager.configureScopeForNdk(scope);
- }
-
-
- public SplitHandlingPolicy getSplitHandlingPolicy() {
- return mSplitHandlingPolicy;
- }
-
- @NonNull
- protected abstract T doCreateOutput(
- OutputFile.OutputType outputType,
- Collection<FilterData> filters);
-
- @NonNull
- public T createOutput(OutputFile.OutputType outputType,
- Collection<FilterData> filters) {
- T data = doCreateOutput(outputType, filters);
-
- // if it's the first time we add an output, mark previous output as part of a multi-output
- // setup.
- if (outputs.size() == 1) {
- outputs.get(0).setMultiOutput(true);
- data.setMultiOutput(true);
- } else if (outputs.size() > 1) {
- data.setMultiOutput(true);
- }
-
- outputs.add(data);
- return data;
- }
-
- @NonNull
- public List<T> getOutputs() {
- return outputs;
- }
-
- @NonNull
- public GradleVariantConfiguration getVariantConfiguration() {
- return variantConfiguration;
- }
-
- public void setVariantDependency(@NonNull VariantDependencies variantDependency) {
- this.variantDependency = variantDependency;
- }
-
- @NonNull
- public VariantDependencies getVariantDependency() {
- return variantDependency;
- }
-
- @NonNull
- public abstract String getDescription();
-
- @NonNull
- public String getApplicationId() {
- return variantConfiguration.getApplicationId();
- }
-
- @NonNull
- protected String getCapitalizedBuildTypeName() {
- return StringHelper.capitalize(variantConfiguration.getBuildType().getName());
- }
-
- @NonNull
- protected String getCapitalizedFlavorName() {
- return StringHelper.capitalize(variantConfiguration.getFlavorName());
- }
-
- public VariantType getType() {
- return variantConfiguration.getType();
- }
-
- @NonNull
- public String getName() {
- return variantConfiguration.getFullName();
- }
-
- @Nullable
- public List<File> getExtraGeneratedSourceFolders() {
- return extraGeneratedSourceFolders;
- }
-
- @Nullable
- public List<File> getExtraGeneratedResFolders() {
- return extraGeneratedResFolders;
- }
-
- public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
- if (extraGeneratedSourceFolders == null) {
- extraGeneratedSourceFolders = Lists.newArrayList();
- }
-
- Collections.addAll(extraGeneratedSourceFolders, generatedSourceFolders);
- }
-
- public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
- if (extraGeneratedSourceFolders == null) {
- extraGeneratedSourceFolders = Lists.newArrayList();
- }
-
- extraGeneratedSourceFolders.addAll(generatedSourceFolders);
- }
-
- public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... generatedSourceFolders) {
- sourceGenTask.dependsOn(task);
-
- for (File f : generatedSourceFolders) {
- javacTask.source(f);
-
- // Jack task is not always created.
- if (jackTask != null) {
- jackTask.source(f);
- }
- }
-
- addJavaSourceFoldersToModel(generatedSourceFolders);
- }
-
- public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedSourceFolders) {
- sourceGenTask.dependsOn(task);
-
- for (File f : generatedSourceFolders) {
- javacTask.source(f);
-
- // Jack task is not always created.
- if (jackTask != null) {
- jackTask.source(f);
- }
- }
-
- addJavaSourceFoldersToModel(generatedSourceFolders);
- }
-
- public void registerResGeneratingTask(@NonNull Task task, @NonNull File... generatedResFolders) {
- // no need add the folders anywhere, the convention mapping closure for the MergeResources
- // action will pick them up from here
- resourceGenTask.dependsOn(task);
-
- if (extraGeneratedResFolders == null) {
- extraGeneratedResFolders = Lists.newArrayList();
- }
-
- Collections.addAll(extraGeneratedResFolders, generatedResFolders);
- }
-
- public void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedResFolders) {
- // no need add the folders anywhere, the convention mapping closure for the MergeResources
- // action will pick them up from here
- resourceGenTask.dependsOn(task);
-
- if (extraGeneratedResFolders == null) {
- extraGeneratedResFolders = Lists.newArrayList();
- }
-
- extraGeneratedResFolders.addAll(generatedResFolders);
- }
-
- /**
- * Calculates the filters for this variant. The filters can either be manually specified by
- * the user within the build.gradle or can be automatically discovered using the variant
- * specific folders.
- *
- * This method must be called before {@link #getFilters(OutputFile.FilterType)}.
- *
- * @param splits the splits configuration from the build.gradle.
- */
- public void calculateFilters(Splits splits) {
-
- List<ResourceSet> resourceSets = variantConfiguration
- .getResourceSets(getGeneratedResFolders(), false);
- densityFilters = getFilters(resourceSets, DiscoverableFilterType.DENSITY, splits);
- languageFilters = getFilters(resourceSets, DiscoverableFilterType.LANGUAGE, splits);
- abiFilters = getFilters(resourceSets, DiscoverableFilterType.ABI, splits);
- }
-
- /**
- * Returns the filters values (as manually specified or automatically discovered) for a
- * particular {@link com.android.build.OutputFile.FilterType}
- * @param filterType the type of filter in question
- * @return a possibly empty set of filter values.
- * @throws IllegalStateException if {@link #calculateFilters(Splits)} has not been called prior
- * to invoking this method.
- */
- @NonNull
- public Set<String> getFilters(OutputFile.FilterType filterType) {
- if (densityFilters == null || languageFilters == null || abiFilters == null) {
- throw new IllegalStateException("calculateFilters method not called");
- }
- switch(filterType) {
- case DENSITY:
- return densityFilters;
- case LANGUAGE:
- return languageFilters;
- case ABI:
- return abiFilters;
- default:
- throw new RuntimeException("Unhandled filter type");
- }
- }
-
- /**
- * Returns the list of generated res folders for this variant.
- */
- private List<File> getGeneratedResFolders() {
- List<File> generatedResFolders = Lists.newArrayList(
- scope.getRenderscriptResOutputDir(),
- scope.getGeneratedResOutputDir());
- if (extraGeneratedResFolders != null) {
- generatedResFolders.addAll(extraGeneratedResFolders);
- }
- if (generateApkDataTask != null &&
- getVariantConfiguration().getBuildType().isEmbedMicroApp()) {
- generatedResFolders.add(generateApkDataTask.getResOutputDir());
- }
- return generatedResFolders;
- }
-
- @NonNull
- public List<String> discoverListOfResourceConfigs() {
- List<String> resFoldersOnDisk = new ArrayList<String>();
- List<ResourceSet> resourceSets = variantConfiguration.getResourceSets(
- getGeneratedResFolders(), false /* no libraries resources */);
- resFoldersOnDisk.addAll(getAllFilters(
- resourceSets,
- DiscoverableFilterType.LANGUAGE.folderPrefix,
- DiscoverableFilterType.DENSITY.folderPrefix));
- return resFoldersOnDisk;
- }
-
- /**
- * Defines the discoverability attributes of filters.
- */
- private enum DiscoverableFilterType {
-
- DENSITY("drawable-") {
- @NonNull
- @Override
- Collection<String> getConfiguredFilters(@NonNull Splits splits) {
- return splits.getDensityFilters();
- }
-
- @Override
- boolean isAuto(@NonNull Splits splits) {
- return splits.getDensity().isAuto();
- }
-
- }, LANGUAGE("values-") {
- @NonNull
- @Override
- Collection<String> getConfiguredFilters(@NonNull Splits splits) {
- return splits.getLanguageFilters();
- }
-
- @Override
- boolean isAuto(@NonNull Splits splits) {
- return splits.getLanguage().isAuto();
- }
- }, ABI("") {
- @NonNull
- @Override
- Collection<String> getConfiguredFilters(@NonNull Splits splits) {
- return splits.getAbiFilters();
- }
-
- @Override
- boolean isAuto(@NonNull Splits splits) {
- // so far, we never auto-discover abi filters.
- return false;
- }
- };
-
- /**
- * Sets the folder prefix that filter specific resources must start with.
- */
- private String folderPrefix;
-
- DiscoverableFilterType(String folderPrefix) {
- this.folderPrefix = folderPrefix;
- }
-
- /**
- * Returns the applicable filters configured in the build.gradle for this filter type.
- * @param splits the build.gradle splits configuration
- * @return a list of filters.
- */
- @NonNull
- abstract Collection<String> getConfiguredFilters(@NonNull Splits splits);
-
- /**
- * Returns true if the user wants the build system to auto discover the splits for this
- * split type.
- * @param splits the build.gradle splits configuration.
- * @return true to use auto-discovery, false to use the build.gradle configuration.
- */
- abstract boolean isAuto(@NonNull Splits splits);
- }
-
- /**
- * Gets the list of filter values for a filter type either from the user specified build.gradle
- * settings or through a discovery mechanism using folders names.
- * @param resourceSets the list of source folders to discover from.
- * @param filterType the filter type
- * @param splits the variant's configuration for splits.
- * @return a possibly empty list of filter value for this filter type.
- */
- @NonNull
- private static Set<String> getFilters(
- @NonNull List<ResourceSet> resourceSets,
- @NonNull DiscoverableFilterType filterType,
- @NonNull Splits splits) {
-
- Set<String> filtersList = new HashSet<String>();
- if (filterType.isAuto(splits)) {
- filtersList.addAll(getAllFilters(resourceSets, filterType.folderPrefix));
- } else {
- filtersList.addAll(filterType.getConfiguredFilters(splits));
- }
- return filtersList;
- }
-
- /**
- * Discover all sub-folders of all the {@link ResourceSet#getSourceFiles()} which names are
- * starting with one of the provided prefixes.
- * @param resourceSets the list of sources {@link ResourceSet}
- * @param prefixes the list of prefixes to look for folders.
- * @return a possibly empty list of folders.
- */
- @NonNull
- private static List<String> getAllFilters(List<ResourceSet> resourceSets, String... prefixes) {
- List<String> providedResFolders = new ArrayList<String>();
- for (ResourceSet resourceSet : resourceSets) {
- for (File resFolder : resourceSet.getSourceFiles()) {
- File[] subResFolders = resFolder.listFiles();
- if (subResFolders != null) {
- for (File subResFolder : subResFolders) {
- for (String prefix : prefixes) {
- if (subResFolder.getName().startsWith(prefix)) {
- providedResFolders
- .add(subResFolder.getName().substring(prefix.length()));
- }
- }
- }
- }
- }
- }
- return providedResFolders;
- }
-
-
- /**
- * Computes the Java sources to use for compilation. This Object[] contains
- * {@link org.gradle.api.file.FileCollection} and {@link File} instances
- */
- @NonNull
- public Object[] getJavaSources() {
- if (javaSources == null) {
- // Build the list of source folders.
- List<Object> sourceList = Lists.newArrayList();
-
- // First the actual source folders.
- List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
- for (SourceProvider provider : providers) {
- sourceList.add(((AndroidSourceSet) provider).getJava().getSourceFiles());
- }
-
- // then all the generated src folders.
- if (getScope().getGenerateRClassTask() != null) {
- sourceList.add(getScope().getRClassSourceOutputDir());
- }
-
- // for the other, there's no duplicate so no issue.
- if (getScope().getGenerateBuildConfigTask() != null) {
- sourceList.add(scope.getBuildConfigSourceOutputDir());
- }
-
- if (getScope().getAidlCompileTask() != null) {
- sourceList.add(scope.getAidlSourceOutputDir());
- }
-
- if (!variantConfiguration.getRenderscriptNdkModeEnabled()
- && getScope().getRenderscriptCompileTask() != null) {
- sourceList.add(scope.getRenderscriptSourceOutputDir());
- }
-
- javaSources = sourceList.toArray();
- }
-
- return javaSources;
- }
-
- /**
- * Returns the Java folders needed for code coverage report.
- *
- * This includes all the source folders except for the ones containing R and buildConfig.
- */
- @NonNull
- public List<File> getJavaSourceFoldersForCoverage() {
- // Build the list of source folders.
- List<File> sourceFolders = Lists.newArrayList();
-
- // First the actual source folders.
- List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
- for (SourceProvider provider : providers) {
- for (File sourceFolder : provider.getJavaDirectories()) {
- if (sourceFolder.isDirectory()) {
- sourceFolders.add(sourceFolder);
- }
- }
- }
-
- File sourceFolder;
- // then all the generated src folders, except the ones for the R/Manifest and
- // BuildConfig classes.
- sourceFolder = aidlCompileTask.getSourceOutputDir();
- if (sourceFolder.isDirectory()) {
- sourceFolders.add(sourceFolder);
- }
-
- if (!variantConfiguration.getRenderscriptNdkModeEnabled()) {
- sourceFolder = renderscriptCompileTask.getSourceOutputDir();
- if (sourceFolder.isDirectory()) {
- sourceFolders.add(sourceFolder);
- }
- }
-
- return sourceFolders;
- }
-
- /**
- * Returns a list of configuration name for wear connection, from highest to lowest priority.
- * @return list of config.
- */
- @NonNull
- public List<String> getWearConfigNames() {
- List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
-
- // this is the wrong order, so let's reverse it as we gather the names.
- final int count = providers.size();
- List<String> names = Lists.newArrayListWithCapacity(count);
- for (int i = count - 1 ; i >= 0; i--) {
- DefaultAndroidSourceSet sourceSet = (DefaultAndroidSourceSet) providers.get(i);
-
- names.add(sourceSet.getWearAppConfigurationName());
- }
-
- return names;
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this)
- .addValue(variantConfiguration.getFullName())
- .toString();
- }
-
- @Nullable
- public FileSupplier getMappingFileProvider() {
- return mappingFileProviderTask;
- }
-
- @Nullable
- public File getMappingFile() {
- return mappingFileProviderTask != null ? mappingFileProviderTask.get() : null;
- }
-
- @NonNull
- public VariantScope getScope() {
- return scope;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java
deleted file mode 100644
index 2aeb1d6..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.tasks.ExtractAnnotations;
-import com.android.builder.core.VariantType;
-import com.google.common.collect.Maps;
-
-import org.gradle.api.Task;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Data about a variant that produce a Library bundle (.aar)
- */
-public class LibraryVariantData extends BaseVariantData<LibVariantOutputData> implements TestedVariantData {
-
- private final Map<VariantType, TestVariantData> testVariants;
-
- @Nullable
- public ExtractAnnotations generateAnnotationsTask = null;
-
- public LibraryVariantData(
- @NonNull AndroidConfig androidConfig,
- @NonNull TaskManager taskManager,
- @NonNull GradleVariantConfiguration config) {
- super(androidConfig, taskManager, config);
- testVariants = Maps.newEnumMap(VariantType.class);
-
- // create default output
- createOutput(OutputFile.OutputType.MAIN,
- Collections.<FilterData>emptyList());
- }
-
- @NonNull
- @Override
- protected LibVariantOutputData doCreateOutput(
- OutputFile.OutputType splitOutput,
- Collection<FilterData> filters) {
- return new LibVariantOutputData(splitOutput, filters, this);
- }
-
- @Override
- @NonNull
- public String getDescription() {
- if (getVariantConfiguration().hasFlavors()) {
- return String.format("%s build for flavor %s",
- getCapitalizedBuildTypeName(),
- getCapitalizedFlavorName());
- } else {
- return String.format("%s build", getCapitalizedBuildTypeName());
- }
- }
-
- @Nullable
- @Override
- public TestVariantData getTestVariantData(@NonNull VariantType type) {
- return testVariants.get(type);
- }
-
- @Override
- public void setTestVariantData(
- @NonNull TestVariantData testVariantData,
- VariantType type) {
- testVariants.put(type, testVariantData);
- }
-
- // Overridden to add source folders to a generateAnnotationsTask, if it exists.
- @Override
- public void registerJavaGeneratingTask(
- @NonNull Task task, @NonNull File... generatedSourceFolders) {
- super.registerJavaGeneratingTask(task, generatedSourceFolders);
- if (generateAnnotationsTask != null) {
- for (File f : generatedSourceFolders) {
- generateAnnotationsTask.source(f);
- }
- }
- }
-
- // Overridden to add source folders to a generateAnnotationsTask, if it exists.
- @Override
- public void registerJavaGeneratingTask(
- @NonNull Task task, @NonNull Collection<File> generatedSourceFolders) {
- super.registerJavaGeneratingTask(task, generatedSourceFolders);
- if (generateAnnotationsTask != null) {
- for (File f : generatedSourceFolders) {
- generateAnnotationsTask.source(f);
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.java
deleted file mode 100644
index 636c1d0..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.api.BaseVariantOutput;
-import com.android.build.gradle.api.LibraryVariant;
-import com.android.build.gradle.internal.BuildTypeData;
-import com.android.build.gradle.internal.ProductFlavorData;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.VariantModel;
-import com.android.build.gradle.internal.api.LibraryVariantImpl;
-import com.android.build.gradle.internal.api.LibraryVariantOutputImpl;
-import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.VariantType;
-import com.google.common.collect.Lists;
-import org.gradle.api.GradleException;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Project;
-import org.gradle.internal.reflect.Instantiator;
-
-import static com.android.builder.core.BuilderConstants.DEBUG;
-import static com.android.builder.core.BuilderConstants.RELEASE;
-
-import java.util.List;
-
-public class LibraryVariantFactory implements VariantFactory {
-
- @NonNull
- private Instantiator instantiator;
- @NonNull
- private final AndroidConfig extension;
- @NonNull
- private final AndroidBuilder androidBuilder;
-
- public LibraryVariantFactory(
- @NonNull Instantiator instantiator,
- @NonNull AndroidBuilder androidBuilder,
- @NonNull AndroidConfig extension) {
- this.instantiator = instantiator;
- this.androidBuilder = androidBuilder;
- this.extension = extension;
- }
-
- @Override
- @NonNull
- public BaseVariantData createVariantData(
- @NonNull GradleVariantConfiguration variantConfiguration,
- @NonNull TaskManager taskManager) {
- return new LibraryVariantData(extension, taskManager, variantConfiguration);
- }
-
- @Override
- @NonNull
- public LibraryVariant createVariantApi(
- @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
- @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
- LibraryVariantImpl variant = instantiator.newInstance(
- LibraryVariantImpl.class, variantData, androidBuilder, readOnlyObjectProvider);
-
- // now create the output objects
- List<? extends BaseVariantOutputData> outputList = variantData.getOutputs();
- List<BaseVariantOutput> apiOutputList = Lists.newArrayListWithCapacity(outputList.size());
-
- for (BaseVariantOutputData variantOutputData : outputList) {
- LibVariantOutputData libOutput = (LibVariantOutputData) variantOutputData;
-
- LibraryVariantOutputImpl output = instantiator.newInstance(
- LibraryVariantOutputImpl.class, libOutput);
-
- apiOutputList.add(output);
- }
-
- variant.addOutputs(apiOutputList);
-
- return variant;
- }
-
- @NonNull
- @Override
- public VariantType getVariantConfigurationType() {
- return VariantType.LIBRARY;
- }
-
- @Override
- public boolean isLibrary() {
- return true;
- }
-
- @Override
- public boolean hasTestScope() {
- return true;
- }
-
- /***
- * Prevent customization of applicationId or applicationIdSuffix.
- */
- @Override
- public void validateModel(@NonNull VariantModel model) {
- if (model.getDefaultConfig().getProductFlavor().getApplicationId() != null) {
- throw new GradleException("Library projects cannot set applicationId. " +
- "applicationId is set to '" +
- model.getDefaultConfig().getProductFlavor().getApplicationId() +
- "' in default config.");
- }
-
- for (BuildTypeData buildType : model.getBuildTypes().values()) {
- if (buildType.getBuildType().getApplicationIdSuffix() != null) {
- throw new GradleException("Library projects cannot set applicationId. " +
- "applicationIdSuffix is set to '" +
- buildType.getBuildType().getApplicationIdSuffix() +
- "' in build type '" + buildType.getBuildType().getName() + "'.");
- }
- }
- for (ProductFlavorData productFlavor : model.getProductFlavors().values()) {
- if (productFlavor.getProductFlavor().getApplicationId() != null) {
- throw new GradleException("Library projects cannot set applicationId. " +
- "applicationId is set to '" +
- productFlavor.getProductFlavor().getApplicationId() + "' in flavor '" +
- productFlavor.getProductFlavor().getName() + "'.");
- }
- }
-
- }
-
- @Override
- public void preVariantWork(Project project) {
- // nothing to be done here.
- }
-
- @Override
- public void createDefaultComponents(
- @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
- @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
- @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
- // must create signing config first so that build type 'debug' can be initialized
- // with the debug signing config.
- signingConfigs.create(DEBUG);
- buildTypes.create(DEBUG);
- buildTypes.create(RELEASE);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java
deleted file mode 100644
index 49c31a9..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
-import com.google.common.collect.Lists;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Data about a variant that produce a test APK
- */
-public class TestVariantData extends ApkVariantData {
-
- public DeviceProviderInstrumentTestTask connectedTestTask;
- public final List<DeviceProviderInstrumentTestTask> providerTestTaskList = Lists.newArrayList();
- @NonNull
- private final TestedVariantData testedVariantData;
-
- public TestVariantData(
- @NonNull AndroidConfig androidConfig,
- @NonNull TaskManager taskManager,
- @NonNull GradleVariantConfiguration config,
- @NonNull TestedVariantData testedVariantData) {
- super(androidConfig, taskManager, config);
- this.testedVariantData = testedVariantData;
-
- // create default output
- createOutput(OutputFile.OutputType.MAIN,
- Collections.<FilterData>emptyList());
- }
-
- @NonNull
- public TestedVariantData getTestedVariantData() {
- return testedVariantData;
- }
-
- @Override
- @NonNull
- public String getDescription() {
- String prefix;
- switch (getType()) {
- case ANDROID_TEST:
- prefix = "android (on device) tests";
- break;
- case UNIT_TEST:
- prefix = "unit tests";
- break;
- default:
- throw new IllegalStateException("Unknown test variant type.");
- }
- if (getVariantConfiguration().hasFlavors()) {
- return String.format("%s for the %s%s build", prefix,
- getCapitalizedFlavorName(), getCapitalizedBuildTypeName());
- } else {
- return String.format("%s for the %s build", prefix,
- getCapitalizedBuildTypeName());
- }
- }
-
- @Override
- public boolean getZipAlignEnabled() {
- return false;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantFactory.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantFactory.java
deleted file mode 100644
index 60c9443..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantFactory.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.variant;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.TestAndroidConfig;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.BuilderConstants;
-import com.google.common.collect.ImmutableMap;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.dsl.DependencyHandler;
-import org.gradle.internal.reflect.Instantiator;
-
-/**
- * Customization of ApplcationVariantFactory for test-only projects.
- */
-public class TestVariantFactory extends ApplicationVariantFactory {
-
- public TestVariantFactory(
- @NonNull Instantiator instantiator,
- @NonNull AndroidBuilder androidBuilder,
- @NonNull AndroidConfig extension) {
- super(instantiator, androidBuilder, extension);
- }
-
- @Override
- public boolean hasTestScope() {
- return false;
- }
-
- @Override
- public void preVariantWork(final Project project) {
- final TestAndroidConfig testExtension = (TestAndroidConfig) extension;
-
- String path = testExtension.getTargetProjectPath();
- if (path == null) {
- throw new GradleException(
- "targetProjectPath cannot be null in test project " + project.getName());
- }
-
- if (testExtension.getTargetVariant() == null) {
- throw new GradleException(
- "targetVariant cannot be null in test project " + project.getName());
- }
-
- DependencyHandler handler = project.getDependencies();
- handler.add("provided", handler.project(ImmutableMap.of(
- "path", path,
- "configuration", testExtension.getTargetVariant() + "-classes"
- )));
- }
-
- @Override
- public void createDefaultComponents(
- @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
- @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
- @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
- // don't call super as we don't want the default app version.
- // must create signing config first so that build type 'debug' can be initialized
- // with the debug signing config.
- signingConfigs.create(BuilderConstants.DEBUG);
- buildTypes.create(BuilderConstants.DEBUG);
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.java
deleted file mode 100644
index bb98123..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.GuardedBy;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.IncrementalTask;
-import com.android.builder.compiling.DependencyFileProcessor;
-import com.android.builder.core.VariantConfiguration;
-import com.android.builder.core.VariantType;
-import com.android.builder.internal.incremental.DependencyData;
-import com.android.builder.internal.incremental.DependencyDataStore;
-import com.android.ide.common.internal.LoggedErrorException;
-import com.android.ide.common.internal.WaitableExecutor;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.ide.common.res2.FileStatus;
-import com.android.utils.FileUtils;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-
-import org.gradle.api.file.FileTree;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.util.PatternSet;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
-/**
- * Task to compile aidl files. Supports incremental update.
- */
-public class AidlCompile extends IncrementalTask {
-
- private static final String DEPENDENCY_STORE = "dependency.store";
- private static final PatternSet PATTERN_SET = new PatternSet().include("**/*.aidl");
-
- // ----- PUBLIC TASK API -----
- private File sourceOutputDir;
- private File aidlParcelableDir;
-
- // ----- PRIVATE TASK API -----
- @Input
- String getBuildToolsVersion() {
- return getBuildTools().getRevision().toString();
- }
- private List<File> sourceDirs;
- private List<File> importDirs;
-
- @InputFiles
- FileTree getSourceFiles() {
- FileTree src = null;
- List<File> sources = getSourceDirs();
- if (!sources.isEmpty()) {
- src = getProject().files(sources).getAsFileTree().matching(PATTERN_SET);
- }
- return src == null ? getProject().files().getAsFileTree() : src;
- }
-
- private static class DepFileProcessor implements DependencyFileProcessor {
-
- @GuardedBy("this")
- List<DependencyData> dependencyDataList = Lists.newArrayList();
-
- List<DependencyData> getDependencyDataList() {
- return dependencyDataList;
- }
-
- @Override
- public DependencyData processFile(@NonNull File dependencyFile) throws IOException {
- DependencyData data = DependencyData.parseDependencyFile(dependencyFile);
- if (data != null) {
- synchronized (this) {
- dependencyDataList.add(data);
- }
- }
-
- return data;
- }
- }
-
- @Override
- protected boolean isIncremental() {
- // TODO fix once dep file parsing is resolved.
- return false;
- }
-
- /**
- * Action methods to compile all the files.
- *
- * The method receives a {@link DependencyFileProcessor} to be used by the
- * {@link com.android.builder.internal.compiler.SourceSearcher.SourceFileProcessor} during
- * the compilation.
- *
- * @param dependencyFileProcessor a DependencyFileProcessor
- */
- private void compileAllFiles(DependencyFileProcessor dependencyFileProcessor)
- throws InterruptedException, ProcessException, LoggedErrorException, IOException {
- getBuilder().compileAllAidlFiles(
- getSourceDirs(),
- getSourceOutputDir(),
- getAidlParcelableDir(),
- getImportDirs(),
- dependencyFileProcessor,
- new LoggedProcessOutputHandler(getILogger()));
- }
-
- /**
- * Returns the import folders.
- */
- @NonNull
- private List<File> getImportFolders() {
- List<File> fullImportDir = Lists.newArrayList();
- fullImportDir.addAll(getImportDirs());
- fullImportDir.addAll(getSourceDirs());
-
- return fullImportDir;
- }
-
- /**
- * Compiles a single file.
- * @param sourceFolder the file to compile.
- * @param file the file to compile.
- * @param importFolders the import folders.
- * @param dependencyFileProcessor a DependencyFileProcessor
- */
- private void compileSingleFile(
- @NonNull File sourceFolder,
- @NonNull File file,
- @Nullable List<File> importFolders,
- @NonNull DependencyFileProcessor dependencyFileProcessor,
- @NonNull ProcessOutputHandler processOutputHandler)
- throws InterruptedException, ProcessException, LoggedErrorException, IOException {
- getBuilder().compileAidlFile(
- sourceFolder,
- file,
- getSourceOutputDir(),
- getAidlParcelableDir(),
- importFolders,
- dependencyFileProcessor,
- processOutputHandler);
- }
-
- @Override
- protected void doFullTaskAction() throws IOException {
- // this is full run, clean the previous output
- File destinationDir = getSourceOutputDir();
- File parcelableDir = getAidlParcelableDir();
- FileUtils.emptyFolder(destinationDir);
- if (parcelableDir != null) {
- FileUtils.emptyFolder(parcelableDir);
- }
-
-
- DepFileProcessor processor = new DepFileProcessor();
-
- try {
- compileAllFiles(processor);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- List<DependencyData> dataList = processor.getDependencyDataList();
-
- DependencyDataStore store = new DependencyDataStore();
- store.addData(dataList);
-
- try {
- store.saveTo(new File(getIncrementalFolder(), DEPENDENCY_STORE));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
- File incrementalData = new File(getIncrementalFolder(), DEPENDENCY_STORE);
- DependencyDataStore store = new DependencyDataStore();
- Multimap<String, DependencyData> inputMap;
- try {
- inputMap = store.loadFrom(incrementalData);
- } catch (Exception ignored) {
- incrementalData.delete();
- getProject().getLogger().info(
- "Failed to read dependency store: full task run!");
- doFullTaskAction();
- return;
- }
-
- final List<File> importFolders = getImportFolders();
- final DepFileProcessor processor = new DepFileProcessor();
- final ProcessOutputHandler processOutputHandler =
- new LoggedProcessOutputHandler(getILogger());
-
- // use an executor to parallelize the compilation of multiple files.
- WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
-
- Map<String,DependencyData> mainFileMap = store.getMainFileMap();
-
- for (final Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
- FileStatus status = entry.getValue();
-
- switch (status) {
- case NEW:
- executor.execute(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- File file = entry.getKey();
- compileSingleFile(getSourceFolder(file), file, importFolders,
- processor, processOutputHandler);
- return null;
- }
- });
- break;
- case CHANGED:
- Collection<DependencyData> impactedData =
- inputMap.get(entry.getKey().getAbsolutePath());
- if (impactedData != null) {
- for (final DependencyData data: impactedData) {
- executor.execute(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- File file = new File(data.getMainFile());
- compileSingleFile(getSourceFolder(file), file,
- importFolders, processor, processOutputHandler);
- return null;
- }
- });
- }
- }
- break;
- case REMOVED:
- final DependencyData data2 = mainFileMap.get(entry.getKey().getAbsolutePath());
- if (data2 != null) {
- executor.execute(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- cleanUpOutputFrom(data2);
- return null;
- }
- });
- store.remove(data2);
- }
- break;
- }
- }
-
- try {
- executor.waitForTasksWithQuickFail(true /*cancelRemaining*/);
- } catch (Throwable t) {
- incrementalData.delete();
- throw new RuntimeException(t);
- }
-
- // get all the update data for the recompiled objects
- store.updateAll(processor.getDependencyDataList());
-
- try {
- store.saveTo(incrementalData);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private File getSourceFolder(@NonNull File file) {
- File parentDir = file;
- while ((parentDir = parentDir.getParentFile()) != null) {
- for (File folder : getSourceDirs()) {
- if (parentDir.equals(folder)) {
- return folder;
- }
- }
- }
-
- throw new IllegalArgumentException(String.format("File '%s' is not in a source dir", file));
- }
-
- private static void cleanUpOutputFrom(@NonNull DependencyData dependencyData) {
- for (String output : dependencyData.getOutputFiles()) {
- new File(output).delete();
- }
- for (String output : dependencyData.getSecondaryOutputFiles()) {
- new File(output).delete();
- }
- }
-
- @OutputDirectory
- public File getSourceOutputDir() {
- return sourceOutputDir;
- }
-
- public void setSourceOutputDir(File sourceOutputDir) {
- this.sourceOutputDir = sourceOutputDir;
- }
-
- @OutputDirectory @Optional
- public File getAidlParcelableDir() {
- return aidlParcelableDir;
- }
-
- public void setAidlParcelableDir(File aidlParcelableDir) {
- this.aidlParcelableDir = aidlParcelableDir;
- }
-
- public List<File> getSourceDirs() {
- return sourceDirs;
- }
-
- public void setSourceDirs(List<File> sourceDirs) {
- this.sourceDirs = sourceDirs;
- }
-
- @InputFiles
- public List<File> getImportDirs() {
- return importDirs;
- }
-
- public void setImportDirs(List<File> importDirs) {
- this.importDirs = importDirs;
- }
-
- public static class ConfigAction implements TaskConfigAction<AidlCompile> {
-
- @NonNull
- VariantScope scope;
-
- public ConfigAction(@NonNull VariantScope scope) {
- this.scope = scope;
- }
-
- @Override
- @NonNull
- public String getName() {
- return scope.getTaskName("compile", "Aidl");
- }
-
- @Override
- @NonNull
- public Class<AidlCompile> getType() {
- return AidlCompile.class;
- }
-
- @Override
- public void execute(AidlCompile compileTask) {
- final VariantConfiguration<?,?,?> variantConfiguration = scope.getVariantConfiguration();
-
- scope.getVariantData().aidlCompileTask = compileTask;
-
- compileTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- compileTask.setVariantName(scope.getVariantConfiguration().getFullName());
- compileTask.setIncrementalFolder(scope.getAidlIncrementalDir());
-
- ConventionMappingHelper.map(compileTask, "sourceDirs", new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- return variantConfiguration.getAidlSourceList();
- }
- });
- ConventionMappingHelper.map(compileTask, "importDirs", new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- return variantConfiguration.getAidlImports();
- }
- });
-
- ConventionMappingHelper.map(compileTask, "sourceOutputDir", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getAidlSourceOutputDir();
- }
- });
-
- if (variantConfiguration.getType() == VariantType.LIBRARY) {
- compileTask.setAidlParcelableDir(scope.getAidlParcelableDir());
- }
- }
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidJarTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidJarTask.java
deleted file mode 100644
index e25870e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidJarTask.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-
-import org.gradle.api.tasks.bundling.Jar;
-
-/**
- * Decorated {@link Jar} task with android specific behaviors.
- */
-public class AndroidJarTask extends Jar implements BinaryFileProviderTask {
-
- @Override
- @NonNull
- public Artifact getArtifact() {
- return new Artifact(BinaryArtifactType.JAR, getArchivePath());
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidProGuardTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidProGuardTask.java
deleted file mode 100644
index 19c4fd2..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidProGuardTask.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.DependencyManager;
-import com.android.build.gradle.internal.PostCompilationData;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.internal.variant.LibraryVariantData;
-import com.android.builder.core.VariantConfiguration;
-import com.android.builder.tasks.Job;
-import com.android.builder.tasks.JobContext;
-import com.android.ide.common.packaging.PackagingUtils;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-
-import org.gradle.api.Action;
-import org.gradle.api.Task;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.tooling.BuildException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import groovy.lang.Closure;
-import proguard.ParseException;
-import proguard.gradle.ProGuardTask;
-
-/**
- * Decoration for the {@link ProGuardTask} so it implements shared interfaces with our custom
- * tasks.
- */
-public class AndroidProGuardTask extends ProGuardTask implements FileSupplier, JavaResourcesProvider {
-
- /**
- * resulting obfuscation mapping file.
- */
- @Nullable
- @InputFile
- @Optional
- File mappingFile;
-
- /**
- * if this is a test related proguard task, this will point to tested application mapping file
- * which can be absent in case the tested application did not request obfuscation.
- */
- @Nullable
- @InputFile
- @Optional
- File testedAppMappingFile;
-
- File obfuscatedClassesJar;
-
- @Override
- public void printmapping(Object printMapping) throws ParseException {
- mappingFile = (File) printMapping;
- super.printmapping(printMapping);
- }
-
- @Override
- public void applymapping(Object applyMapping) throws ParseException {
- testedAppMappingFile = (File) applyMapping;
- }
-
- @Override
- public File get() {
- return mappingFile;
- }
-
- @NonNull
- @Override
- public Task getTask() {
- return this;
- }
-
- @Override
- @TaskAction
- public void proguard() throws IOException, ParseException {
- final Job<Void> job = new Job<Void>(getName(),
- new com.android.builder.tasks.Task<Void>() {
- @Override
- public void run(@NonNull Job<Void> job,
- @NonNull JobContext<Void> context) throws IOException {
- try {
- AndroidProGuardTask.this.doMinification();
- } catch (ParseException e) {
- throw new IOException(e);
- }
- }
- });
- try {
- SimpleWorkQueue.push(job);
-
- // wait for the task completion.
- job.await();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException(e);
- }
-
- }
-
- public void doMinification() throws ParseException, IOException {
- // only set the tested application mapping file if it exists (it must at this point or that
- // means the tested application did not request obfuscation).
- if (testedAppMappingFile != null && testedAppMappingFile.exists()) {
- super.applymapping(testedAppMappingFile);
- }
- super.proguard();
- }
-
- @NonNull
- @Override
- public ImmutableList<JavaResourcesLocation> getJavaResourcesLocations() {
- return ImmutableList.of(new JavaResourcesLocation(Type.JAR, obfuscatedClassesJar));
- }
-
- public static class ConfigAction implements TaskConfigAction<AndroidProGuardTask> {
-
- private VariantScope scope;
-
- private Callable<File> inputDir;
- private Callable<File> javaResourcesInputDir;
-
- private Callable<List<File>> inputLibraries;
-
- public ConfigAction(VariantScope scope, final PostCompilationData pcData) {
- this.scope = scope;
- inputDir = pcData.getInputDirCallable();
- javaResourcesInputDir = pcData.getJavaResourcesInputDirCallable();
- inputLibraries = pcData.getInputLibrariesCallable();
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("proguard");
- }
-
- @Override
- public Class<AndroidProGuardTask> getType() {
- return AndroidProGuardTask.class;
- }
-
- @Override
- public void execute(final AndroidProGuardTask proguardTask) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- final VariantConfiguration variantConfig = scope.getVariantData().getVariantConfiguration();
- final BaseVariantData testedVariantData = scope.getTestedVariantData();
-
- // use single output for now.
- final BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
-
- if (testedVariantData != null) {
- proguardTask.dependsOn(testedVariantData.getScope().getObfuscationTask().getName());
- }
-
- variantData.obfuscationTask = proguardTask;
- variantData.mappingFileProviderTask = proguardTask;
-
- // --- Output File ---
-
- proguardTask.obfuscatedClassesJar = scope.getProguardOutputFile();
-
- // --- Proguard Config ---
-
- try {
-
- if (testedVariantData != null) {
- // Don't remove any code in tested app.
- proguardTask.dontshrink();
- proguardTask.dontoptimize();
-
- // We can't call dontobfuscate, since that would make ProGuard ignore the mapping file.
- proguardTask.keep("class * {*;}");
- proguardTask.keep("interface * {*;}");
- proguardTask.keep("enum * {*;}");
- proguardTask.keepattributes();
-
- // Input the mapping from the tested app so that we can deal with obfuscated code.
- proguardTask.applymapping(testedVariantData.getMappingFile());
-
- // All -dontwarn rules for test dependencies should go in here:
- proguardTask.configuration(
- testedVariantData.getVariantConfiguration().getTestProguardFiles());
- } else {
- if (variantConfig.isTestCoverageEnabled()) {
- // when collecting coverage, don't remove the JaCoCo runtime
- proguardTask.keep("class com.vladium.** {*;}");
- proguardTask.keep("class org.jacoco.** {*;}");
- proguardTask.keep("interface org.jacoco.** {*;}");
- proguardTask.dontwarn("org.jacoco.**");
- }
-
- proguardTask.configuration(new Callable<Collection<File>>() {
- @Override
- public Collection<File> call() throws Exception {
- List<File> proguardFiles = variantConfig.getProguardFiles(true,
- Collections.singletonList(getDefaultProguardFile(
- TaskManager.DEFAULT_PROGUARD_CONFIG_FILE)));
- proguardFiles.add(
- variantOutputData.processResourcesTask.getProguardOutputFile());
- return proguardFiles;
- }
- });
- }
-
- // --- InJars / LibraryJars ---
-
- if (variantData instanceof LibraryVariantData) {
- String packageName = variantConfig.getPackageFromManifest();
- if (packageName == null) {
- throw new BuildException("Failed to read manifest", null);
- }
-
- packageName = packageName.replace(".", "/");
-
- // injar: the compilation output
- // exclude R files and such from output
- String exclude = "!" + packageName + "/R.class";
- exclude += (", !" + packageName + "/R$*.class");
- if (!scope.getGlobalScope().getExtension().getPackageBuildConfig()) {
- exclude += (", !" + packageName + "/Manifest.class");
- exclude += (", !" + packageName + "/Manifest$*.class");
- exclude += (", !" + packageName + "/BuildConfig.class");
- }
-
- proguardTask.injars(ImmutableMap.of("filter", exclude), inputDir);
-
- // include R files and such for compilation
- String include = exclude.replace("!", "");
- LinkedHashMap<String, Object> map1 = new LinkedHashMap<String, Object>(1);
- map1.put("filter", include);
- proguardTask.libraryjars(map1, inputDir);
-
- // injar: the local dependencies
- Callable inJars = new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- return DependencyManager
- .getPackagedLocalJarFileList(variantData.getVariantDependency());
- }
- };
-
- proguardTask.injars(ImmutableMap.of("filter", "!META-INF/MANIFEST.MF"), inJars);
-
- // libjar: the library dependencies. In this case we take all the compile-scope
- // dependencies
- Callable libJars = new Callable<Iterable<File>>() {
- @Override
- public Iterable<File> call() throws Exception {
- // get all the compiled jar.
- Set<File> compiledJars = scope.getGlobalScope().getAndroidBuilder()
- .getCompileClasspath(variantConfig);
- // and remove local jar that are also packaged
- final List<File> localJars = DependencyManager
- .getPackagedLocalJarFileList(variantData.getVariantDependency());
-
- return Iterables.filter(compiledJars, new Predicate<File>() {
- @Override
- public boolean apply(File file) {
- return !localJars.contains(file);
- }
- });
- }
- };
-
- proguardTask.libraryjars(ImmutableMap.of("filter", "!META-INF/MANIFEST.MF"), libJars);
-
- // ensure local jars keep their package names
- proguardTask.keeppackagenames();
- } else {
- // injar: the compilation output
- proguardTask.injars(inputDir);
- if (javaResourcesInputDir != null) {
- proguardTask.injars(javaResourcesInputDir);
- }
-
- // injar: the packaged dependencies
- LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(1);
-
- // add a filter to explicitly add files of the following extensions to the
- // resulting proguarded classes.jar by only including the extensions that were
- // not filtered by the libraries resource extraction task.
- String filter = Joiner.on(", **/*.").join(
- PackagingUtils.NON_RESOURCES_EXTENSIONS);
-
- map.put("filter", "!META-INF/MANIFEST.MF" + (Strings.isNullOrEmpty(filter)
- ? ""
- : ", **/*." + filter));
- proguardTask.injars(map, inputLibraries);
-
- // the provided-only jars as libraries.
- Callable<List<File>> libJars = new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- return variantData.getVariantConfiguration().getProvidedOnlyJars();
- }
- };
-
- proguardTask.libraryjars(libJars);
- }
-
- // libraryJars: the runtime jars. Do this in doFirst since the boot classpath isn't
- // available until the SDK is loaded in the prebuild task
- proguardTask.doFirst(new Action<Task>() {
- @Override
- public void execute(Task proguardTask) {
- for (String runtimeJar : scope.getGlobalScope().getAndroidBuilder()
- .getBootClasspathAsStrings()) {
- try {
- ((AndroidProGuardTask)proguardTask).libraryjars(runtimeJar);
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
- }
- }
- });
-
- if (testedVariantData != null) {
- // input the tested app as library
- proguardTask.libraryjars(testedVariantData.javacTask.getDestinationDir());
- // including its dependencies
- Callable testedPackagedJars = new Callable<Set<File>>() {
- @Override
- public Set<File> call() throws Exception {
- return scope.getGlobalScope().getAndroidBuilder()
- .getPackagedJars(testedVariantData.getVariantConfiguration());
- }
- };
-
- LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(1);
- map.put("filter", "!META-INF/MANIFEST.MF");
- proguardTask.libraryjars(map, testedPackagedJars);
- }
-
- // --- Out files ---
-
- proguardTask.outjars(scope.getProguardOutputFile());
-
- final File proguardOut = new File(
- String.valueOf(scope.getGlobalScope().getBuildDir()) + "/" + FD_OUTPUTS
- + "/mapping/" + variantData.getVariantConfiguration().getDirName());
-
- proguardTask.dump(new File(proguardOut, "dump.txt"));
- proguardTask.printseeds(new File(proguardOut, "seeds.txt"));
- proguardTask.printusage(new File(proguardOut, "usage.txt"));
- proguardTask.printmapping(new File(proguardOut, "mapping.txt"));
-
- // proguard doesn't verify that the seed/mapping/usage folders exist and will fail
- // if they don't so create them.
- proguardTask.doFirst(new Closure<Boolean>(this, this) {
- public Boolean doCall(Task it) {
- return proguardOut.mkdirs();
- }
-
- public Boolean doCall() {
- return doCall(null);
- }
-
- });
- } catch (ParseException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private File getDefaultProguardFile(String name) {
- File sdkDir = scope.getGlobalScope().getSdkHandler().getAndCheckSdkFolder();
- return new File(sdkDir,
- SdkConstants.FD_TOOLS + File.separatorChar
- + SdkConstants.FD_PROGUARD + File.separatorChar
- + name);
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/CompatibleScreensManifest.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/CompatibleScreensManifest.groovy
deleted file mode 100644
index ca05d0c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/CompatibleScreensManifest.groovy
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantOutputScope
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask
-import com.android.resources.Density
-import com.google.common.base.Charsets
-import com.google.common.io.Files
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-
-/**
- * Task to generate a manifest snippet that just contains a compatible-screens
- * node with the given density and the given list of screen sizes.
-
- */
-class CompatibleScreensManifest extends DefaultAndroidTask {
-
- @Input
- String screenDensity
-
- @Input
- Set<String> screenSizes
-
- @OutputFile
- File manifestFile
-
- @TaskAction
- void generate() {
- StringBuilder content = new StringBuilder(
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " package=\"\">\n" +
- "\n" +
- " <compatible-screens>\n")
-
- String density = getScreenDensity()
-
- // convert unsupported values to numbers.
- density = convert(density, Density.XXHIGH, Density.XXXHIGH);
-
- for (String size : getScreenSizes()) {
- content.append(
- " <screen android:screenSize=\"$size\" android:screenDensity=\"$density\" />\n")
- }
-
- content.append(
- " </compatible-screens>\n" +
- "</manifest>")
-
- Files.write(content.toString(), getManifestFile(), Charsets.UTF_8);
- }
-
- private static String convert(@NonNull String density, @NonNull Density... densitiesToConvert) {
- for (Density densityToConvert : densitiesToConvert) {
- if (densityToConvert.getResourceValue().equals(density)) {
- return Integer.toString(densityToConvert.dpiValue);
- }
- }
-
- return density;
- }
-
- public static class ConfigAction implements TaskConfigAction<CompatibleScreensManifest> {
-
- @NonNull
- VariantOutputScope scope
- @NonNull
- Set<String> screenSizes
-
- ConfigAction(
- @NonNull VariantOutputScope scope,
- @NonNull Set<String> screenSizes) {
- this.scope = scope
- this.screenSizes = screenSizes
- }
-
- @Override
- String getName() {
- return scope.getTaskName("create", "CompatibleScreenManifest")
- }
-
- @Override
- Class<CompatibleScreensManifest> getType() {
- return CompatibleScreensManifest.class
- }
-
- @Override
- void execute(CompatibleScreensManifest csmTask) {
- csmTask.setVariantName(scope.getVariantScope().getVariantConfiguration().getFullName())
-
- csmTask.screenDensity = scope.variantOutputData.getMainOutputFile().getFilter(
- com.android.build.OutputFile.DENSITY)
- csmTask.screenSizes = screenSizes
-
- ConventionMappingHelper.map(csmTask, "manifestFile") {
- scope.getCompatibleScreensManifestFile()
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Dex.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Dex.groovy
deleted file mode 100644
index fbd5ea0..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Dex.groovy
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks
-
-import com.android.SdkConstants
-import com.android.annotations.Nullable
-import com.android.build.gradle.internal.core.GradleVariantConfiguration
-import com.android.build.gradle.internal.dsl.DexOptions
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.BaseTask
-import com.android.build.gradle.internal.variant.ApkVariantData
-import com.android.build.gradle.internal.variant.TestVariantData
-import com.android.ide.common.process.LoggedProcessOutputHandler
-import com.android.utils.FileUtils
-import org.codehaus.groovy.runtime.DefaultGroovyMethods
-import com.android.build.gradle.internal.PostCompilationData
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Nested
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.incremental.IncrementalTaskInputs
-
-import java.util.concurrent.Callable
-import java.util.concurrent.atomic.AtomicBoolean
-
-import static com.android.builder.core.VariantType.DEFAULT
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
-
-public class Dex extends BaseTask {
-
- // ----- PUBLIC TASK API -----
-
- @OutputDirectory
- File outputFolder
-
- @Input @Optional
- List<String> additionalParameters
-
- boolean enableIncremental = true
-
- // ----- PRIVATE TASK API -----
- @Input
- String getBuildToolsVersion() {
- getBuildTools().getRevision()
- }
-
- @InputFiles @Optional
- Collection<File> inputFiles
- @InputDirectory @Optional
- File inputDir
-
- @InputFiles
- Collection<File> libraries
-
- @Nested
- DexOptions dexOptions
-
- @Input
- boolean multiDexEnabled = false
-
- @Input
- boolean optimize = true
-
- @InputFile @Optional
- File mainDexListFile
-
- File tmpFolder
-
- /**
- * Actual entry point for the action.
- * Calls out to the doTaskAction as needed.
- */
- @TaskAction
- void taskAction(IncrementalTaskInputs inputs) {
- Collection<File> _inputFiles = getInputFiles()
- File _inputDir = getInputDir()
- if (_inputFiles == null && _inputDir == null) {
- throw new RuntimeException("Dex task '${getName()}: inputDir and inputFiles cannot both be null");
- }
-
- if (!dexOptions.incremental || !enableIncremental) {
- doTaskAction(_inputFiles, _inputDir, false /*incremental*/)
- return
- }
-
- if (!inputs.isIncremental()) {
- project.logger.info("Unable to do incremental execution: full task run.")
- doTaskAction(_inputFiles, _inputDir, false /*incremental*/)
- return
- }
-
- AtomicBoolean forceFullRun = new AtomicBoolean()
-
- //noinspection GroovyAssignabilityCheck
- inputs.outOfDate { change ->
- // force full dx run if existing jar file is modified
- // New jar files are fine.
- if (change.isModified() && change.file.path.endsWith(SdkConstants.DOT_JAR)) {
- project.logger.info("Force full dx run: Found updated ${change.file}")
- forceFullRun.set(true)
- }
- }
-
- //noinspection GroovyAssignabilityCheck
- inputs.removed { change ->
- // force full dx run if existing jar file is removed
- if (change.file.path.endsWith(SdkConstants.DOT_JAR)) {
- project.logger.info("Force full dx run: Found removed ${change.file}")
- forceFullRun.set(true)
- }
- }
-
- doTaskAction(_inputFiles, _inputDir, !forceFullRun.get())
- }
-
- private void doTaskAction(
- @Nullable Collection<File> inputFiles,
- @Nullable File inputDir,
- boolean incremental) {
- File outFolder = getOutputFolder()
- if (!incremental) {
- FileUtils.emptyFolder(outFolder)
- }
-
- File tmpFolder = getTmpFolder()
- tmpFolder.mkdirs()
-
- // if some of our .jar input files exist, just reset the inputDir to null
- for (File inputFile : inputFiles) {
- if (inputFile.exists()) {
- inputDir = null;
- }
- }
- if (inputDir != null) {
- inputFiles = project.files(inputDir).files
- }
-
- getBuilder().convertByteCode(
- inputFiles,
- getLibraries(),
- outFolder,
- getMultiDexEnabled(),
- getMainDexListFile(),
- getDexOptions(),
- getAdditionalParameters(),
- tmpFolder,
- incremental,
- getOptimize(),
- new LoggedProcessOutputHandler(getILogger()))
- }
-
-
- public static class ConfigAction implements TaskConfigAction<Dex> {
-
- private final VariantScope scope;
-
- private final PostCompilationData pcData;
-
- public ConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope;
- this.pcData = pcData;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("dex")
- }
-
- @Override
- public Class<Dex> getType() {
- return Dex.class;
- }
-
- @Override
- public void execute(Dex dexTask) {
- ApkVariantData variantData = (ApkVariantData) scope.getVariantData();
- final GradleVariantConfiguration config = variantData.getVariantConfiguration();
-
- boolean isTestForApp = config.getType().isForTesting() && (DefaultGroovyMethods
- .asType(variantData, TestVariantData.class)).getTestedVariantData()
- .getVariantConfiguration().getType().equals(DEFAULT);
-
- boolean isMultiDexEnabled = config.isMultiDexEnabled() && !isTestForApp;
- boolean isLegacyMultiDexMode = config.isLegacyMultiDexMode();
-
- variantData.dexTask = dexTask;
- dexTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder())
- dexTask.setVariantName(config.getFullName())
- ConventionMappingHelper.map(dexTask, "outputFolder", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getDexOutputFolder();
- }
- });
- dexTask.setTmpFolder(new File(
- String.valueOf(scope.getGlobalScope().getBuildDir()) + "/" + FD_INTERMEDIATES
- + "/tmp/dex/" + config.getDirName()));
- dexTask.setDexOptions(scope.getGlobalScope().getExtension().getDexOptions());
- dexTask.setMultiDexEnabled(isMultiDexEnabled);
- // dx doesn't work with receving --no-optimize in debug so we disable it for now.
- dexTask.setOptimize(true);//!variantData.variantConfiguration.buildType.debuggable
-
- // inputs
- if (pcData.getInputDirCallable() != null) {
- ConventionMappingHelper.map(dexTask, "inputDir", pcData.getInputDirCallable());
- }
- ConventionMappingHelper.map(dexTask, "inputFiles", pcData.getInputFilesCallable());
- ConventionMappingHelper.map(dexTask, "libraries", pcData.getInputLibrariesCallable());
-
- if (isMultiDexEnabled && isLegacyMultiDexMode) {
- // configure the dex task to receive the generated class list.
- ConventionMappingHelper.map(dexTask, "mainDexListFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getMainDexListFile();
- }
- });
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy
deleted file mode 100644
index 4867946..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.tasks.AbstractAndroidCompile
-import com.android.build.gradle.internal.variant.BaseVariantData
-import com.android.build.gradle.tasks.annotations.ApiDatabase
-import com.android.build.gradle.tasks.annotations.Extractor
-import com.android.tools.lint.EcjParser
-import com.android.utils.Pair
-import com.google.common.collect.Lists
-import com.google.common.collect.Maps
-import org.eclipse.jdt.core.compiler.IProblem
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration
-import org.eclipse.jdt.internal.compiler.batch.CompilationUnit
-import org.eclipse.jdt.internal.compiler.env.ICompilationUnit
-import org.eclipse.jdt.internal.compiler.env.INameEnvironment
-import org.eclipse.jdt.internal.compiler.impl.CompilerOptions
-import org.eclipse.jdt.internal.compiler.util.Util
-import org.gradle.api.file.EmptyFileVisitor
-import org.gradle.api.file.FileVisitDetails
-import org.gradle.api.logging.LogLevel
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import org.gradle.tooling.BuildException
-
-import static com.android.SdkConstants.DOT_JAVA
-import static com.android.SdkConstants.UTF_8
-
-/**
- * Task which extracts annotations from the source files, and writes them to one of
- * two possible destinations:
- * <ul>
- * <li> A "external annotations" file (pointed to by {@link ExtractAnnotations#output})
- * which records the annotations in a zipped XML format for use by the IDE and by
- * lint to associate the (source retention) annotations back with the compiled code</li>
- * <li> For any {@code Keep} annotated elements, a Proguard keep file (pointed to by
- * {@link ExtractAnnotations#proguard}, which lists APIs (classes, methods and fields)
- * that should not be removed even if no references in code are found to those APIs.</li>
- * <p>
- * We typically only extract external annotations when building libraries; ProGuard annotations
- * are extracted when building libraries (to record in the AAR), <b>or</b> when building an
- * app module where ProGuarding is enabled.
- * </ul>
- */
-class ExtractAnnotations extends AbstractAndroidCompile {
- public BaseVariantData variant
-
- /** Boot classpath: typically android.jar */
- @Input
- public List<String> bootClasspath
-
- /** The output .zip file to write the annotations database to, if any */
- @Optional
- @OutputFile
- public File output
-
- /** The output proguard file to write any @Keep rules into, if any */
- @Optional
- @OutputFile
- public File proguard
-
- /**
- * An optional pointer to an API file to filter the annotations by (any annotations
- * not found in the API file are considered hidden/not exposed.) This is in the same
- * format as the api-versions.xml file found in the SDK.
- */
- @Optional
- @InputFile
- public File apiFilter
-
- /**
- * A list of existing annotation zip files (or dirs) to merge in. This can be used to merge in
- * a hardcoded set of annotations that are not present in the source code, such as
- * {@code @Contract} annotations we'd like to record without actually having a dependency
- * on the IDEA annotations library.
- */
- @Optional
- @InputFile
- public List<File> mergeJars
-
- /**
- * The encoding to use when reading source files. The output file will ignore this and
- * will always be a UTF-8 encoded .xml file inside the annotations zip file.
- */
- @Optional
- @Input
- public String encoding
-
- /**
- * Location of class files. If set, any non-public typedef source retention annotations
- * will be removed prior to .jar packaging.
- */
- @Optional
- @InputFile
- public File classDir
-
- /** Whether we allow extraction even in the presence of symbol resolution errors */
- @InputFile
- public boolean allowErrors = true
-
- @Override
- @TaskAction
- protected void compile() {
- if (!hasAndroidAnnotations()) {
- return
- }
-
- if (encoding == null) {
- encoding = UTF_8
- }
-
- Pair<Collection<CompilationUnitDeclaration>, INameEnvironment> result = parseSources()
- def parsedUnits = result.first
- def environment = result.second
-
- try {
- if (!allowErrors) {
- for (CompilationUnitDeclaration unit : parsedUnits) {
- // so maybe I don't need my map!!
- def problems = unit.compilationResult().allProblems
- for (IProblem problem : problems) {
- if (problem.error) {
- println "Not extracting annotations (compilation problems encountered)";
- println "Error: " + problem.getOriginatingFileName() + ":" +
- problem.getSourceLineNumber() + ": " + problem.getMessage()
- // TODO: Consider whether we abort the build at this point!
- return
- }
- }
- }
- }
-
- // API definition file
- ApiDatabase database = null;
- if (apiFilter != null && apiFilter.exists()) {
- try {
- database = new ApiDatabase(apiFilter);
- } catch (IOException e) {
- throw new BuildException("Could not open API database " + apiFilter, e)
- }
- }
-
-
- def displayInfo = project.logger.isEnabled(LogLevel.INFO)
- def includeClassRetentionAnnotations = false
- def sortAnnotations = false
-
- Extractor extractor = new Extractor(database, classDir, displayInfo,
- includeClassRetentionAnnotations, sortAnnotations);
- extractor.extractFromProjectSource(parsedUnits)
- if (mergeJars != null) {
- for (File jar : mergeJars) {
- extractor.mergeExisting(jar);
- }
- }
- extractor.export(output, proguard)
- extractor.removeTypedefClasses();
- } finally {
- if (environment != null) {
- environment.cleanup()
- }
- }
- }
-
- @Input
- public boolean hasAndroidAnnotations() {
- return variant.variantDependency.annotationsPresent
- }
-
- @NonNull
- private Pair<Collection<CompilationUnitDeclaration>,INameEnvironment> parseSources() {
- List<ICompilationUnit> sourceUnits = Lists.newArrayListWithExpectedSize(100);
-
- source.visit(new EmptyFileVisitor() {
- @Override
- void visitFile(FileVisitDetails fileVisitDetails) {
- def file = fileVisitDetails.file;
- def path = file.getPath()
- if (path.endsWith(DOT_JAVA) && file.isFile()) {
- char[] contents = Util.getFileCharContent(file, encoding);
- ICompilationUnit unit = new CompilationUnit(contents, path, encoding);
- sourceUnits.add(unit);
- }
- }
- })
-
- Map<ICompilationUnit, CompilationUnitDeclaration> outputMap = Maps.
- newHashMapWithExpectedSize(sourceUnits.size())
- List<String> jars = Lists.newArrayList();
- if (bootClasspath != null) {
- jars.addAll(bootClasspath)
- }
- if (classpath != null) {
- for (File jar : classpath) {
- jars.add(jar.getPath());
- }
- }
-
- CompilerOptions options = EcjParser.createCompilerOptions();
- options.docCommentSupport = true; // So I can find @hide
-
- // Note: We can *not* set options.ignoreMethodBodies=true because it disables
- // type attribution!
-
- def level = getLanguageLevel(sourceCompatibility)
- options.sourceLevel = level
- options.complianceLevel = options.sourceLevel
- // We don't generate code, but just in case the parser consults this flag
- // and makes sure that it's not greater than the source level:
- options.targetJDK = options.sourceLevel
- options.originalComplianceLevel = options.sourceLevel;
- options.originalSourceLevel = options.sourceLevel;
- options.inlineJsrBytecode = true; // >= 1.5
-
- def environment = EcjParser.parse(options, sourceUnits, jars, outputMap, null);
- Collection<CompilationUnitDeclaration> parsedUnits = outputMap.values()
- Pair.of(parsedUnits, environment);
- }
-
- private static long getLanguageLevel(String version) {
- if ("1.6".equals(version)) {
- return EcjParser.getLanguageLevel(1, 6);
- } else if ("1.7".equals(version)) {
- return EcjParser.getLanguageLevel(1, 7);
- } else if ("1.5") {
- return EcjParser.getLanguageLevel(1, 5);
- } else {
- return EcjParser.getLanguageLevel(1, 7);
- }
- }
-
- private def addSources(List<ICompilationUnit> sourceUnits, File file) {
- if (file.isDirectory()) {
- def files = file.listFiles();
- if (files != null) {
- for (File sub : files) {
- addSources(sourceUnits, sub);
- }
- }
- } else if (file.getPath().endsWith(DOT_JAVA) && file.isFile()) {
- char[] contents = Util.getFileCharContent(file, encoding);
- ICompilationUnit unit = new CompilationUnit(contents, file.getPath(), encoding);
- sourceUnits.add(unit);
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy
deleted file mode 100644
index 816c4c5..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.BaseTask
-import com.android.build.gradle.internal.variant.BaseVariantData
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.android.builder.compiling.BuildConfigGenerator
-import com.android.builder.core.VariantConfiguration
-import com.android.builder.model.ClassField
-import com.android.utils.FileUtils
-import com.google.common.base.Strings
-import com.google.common.collect.Lists
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.ParallelizableTask
-import org.gradle.api.tasks.TaskAction
-
- at ParallelizableTask
-public class GenerateBuildConfig extends BaseTask {
-
- // ----- PUBLIC TASK API -----
-
- @OutputDirectory
- File sourceOutputDir
-
- // ----- PRIVATE TASK API -----
-
- @Input
- String buildConfigPackageName
-
- @Input
- String appPackageName
-
- @Input
- boolean debuggable
-
- @Input
- String flavorName
-
- @Input
- List<String> flavorNamesWithDimensionNames
-
- @Input
- String buildTypeName
-
- @Input
- @Optional
- String versionName
-
- @Input
- int versionCode
-
- List<Object> items;
-
- @Input
- List<String> getItemValues() {
- List<Object> resolvedItems = getItems()
- List<String> list = Lists.newArrayListWithCapacity(resolvedItems.size() * 3)
-
- for (Object object : resolvedItems) {
- if (object instanceof String) {
- list.add((String) object)
- } else if (object instanceof ClassField) {
- ClassField field = (ClassField) object
- list.add(field.type)
- list.add(field.name)
- list.add(field.value)
- }
- }
-
- return list
- }
-
- @TaskAction
- void generate() throws IOException {
- // must clear the folder in case the packagename changed, otherwise,
- // there'll be two classes.
- File destinationDir = getSourceOutputDir()
- FileUtils.emptyFolder(destinationDir)
-
- BuildConfigGenerator generator = new BuildConfigGenerator(
- getSourceOutputDir(),
- getBuildConfigPackageName());
-
- // Hack (see IDEA-100046): We want to avoid reporting "condition is always true"
- // from the data flow inspection, so use a non-constant value. However, that defeats
- // the purpose of this flag (when not in debug mode, if (BuildConfig.DEBUG && ...) will
- // be completely removed by the compiler), so as a hack we do it only for the case
- // where debug is true, which is the most likely scenario while the user is looking
- // at source code.
- //map.put(PH_DEBUG, Boolean.toString(mDebug));
- generator.addField("boolean", "DEBUG",
- getDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
- .addField("String", "APPLICATION_ID", "\"${getAppPackageName()}\"")
- .addField("String", "BUILD_TYPE", "\"${getBuildTypeName()}\"")
- .addField("String", "FLAVOR", "\"${getFlavorName()}\"")
- .addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
- .addField("String", "VERSION_NAME", "\"${Strings.nullToEmpty(getVersionName())}\"")
- .addItems(getItems())
-
- List<String> flavors = getFlavorNamesWithDimensionNames()
- int count = flavors.size()
- if (count > 1) {
- for (int i = 0; i < count; i += 2) {
- generator.
- addField("String", "FLAVOR_${flavors.get(i + 1)}", "\"${flavors.get(i)}\"")
- }
- }
-
- generator.generate()
- }
-
- // ----- Config Action -----
-
- public static class ConfigAction implements TaskConfigAction<GenerateBuildConfig> {
-
- @NonNull
- VariantScope scope
-
- ConfigAction(@NonNull VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- @NonNull
- String getName() {
- return scope.getTaskName("generate", "BuildConfig");
- }
-
- @Override
- @NonNull
- Class<GenerateBuildConfig> getType() {
- return GenerateBuildConfig
- }
-
-
- @Override
- void execute(GenerateBuildConfig generateBuildConfigTask) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
-
- variantData.generateBuildConfigTask = generateBuildConfigTask
-
- VariantConfiguration variantConfiguration = variantData.variantConfiguration
-
- generateBuildConfigTask.androidBuilder = scope.globalScope.androidBuilder
- generateBuildConfigTask.setVariantName(scope.getVariantConfiguration().getFullName())
-
- ConventionMappingHelper.map(generateBuildConfigTask, "buildConfigPackageName") {
- variantConfiguration.originalApplicationId
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "appPackageName") {
- variantConfiguration.applicationId
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "versionName") {
- variantConfiguration.versionName
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "versionCode") {
- variantConfiguration.versionCode
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "debuggable") {
- variantConfiguration.buildType.isDebuggable()
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "buildTypeName") {
- variantConfiguration.buildType.name
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "flavorName") {
- variantConfiguration.flavorName
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "flavorNamesWithDimensionNames") {
- variantConfiguration.flavorNamesWithDimensionNames
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "items") {
- variantConfiguration.buildConfigItems
- }
-
- ConventionMappingHelper.map(generateBuildConfigTask, "sourceOutputDir") {
- scope.getBuildConfigSourceOutputDir()
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy
deleted file mode 100644
index f55f769..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.BaseTask
-import com.android.builder.compiling.ResValueGenerator
-import com.android.builder.core.VariantConfiguration
-import com.android.builder.model.ClassField
-import com.google.common.collect.Lists
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.ParallelizableTask
-import org.gradle.api.tasks.TaskAction
-
- at ParallelizableTask
-public class GenerateResValues extends BaseTask {
-
- // ----- PUBLIC TASK API -----
-
- @OutputDirectory
- File resOutputDir
-
- // ----- PRIVATE TASK API -----
-
- List<Object> items
-
- @Input
- List<String> getItemValues() {
- List<Object> resolvedItems = getItems()
- List<String> list = Lists.newArrayListWithCapacity(resolvedItems.size() * 3)
-
- for (Object object : resolvedItems) {
- if (object instanceof String) {
- list.add((String) object)
- } else if (object instanceof ClassField) {
- ClassField field = (ClassField) object
- list.add(field.type)
- list.add(field.name)
- list.add(field.value)
- }
- }
-
- return list
- }
-
- @TaskAction
- void generate() {
- File folder = getResOutputDir()
- List<Object> resolvedItems = getItems()
-
- if (resolvedItems.isEmpty()) {
- folder.deleteDir()
- } else {
- ResValueGenerator generator = new ResValueGenerator(folder)
- generator.addItems(getItems())
-
- generator.generate()
- }
- }
-
-
- public static class ConfigAction implements TaskConfigAction<GenerateResValues> {
-
- @NonNull
- VariantScope scope
-
- ConfigAction(@NonNull VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- String getName() {
- return scope.getTaskName("generate", "ResValues");
- }
-
- @Override
- Class getType() {
- return GenerateResValues.class
- }
-
- @Override
- void execute(GenerateResValues generateResValuesTask) {
- scope.variantData.generateResValuesTask = generateResValuesTask
-
- VariantConfiguration variantConfiguration = scope.variantData.variantConfiguration
-
- generateResValuesTask.androidBuilder = scope.globalScope.androidBuilder
- generateResValuesTask.setVariantName(variantConfiguration.getFullName())
-
- ConventionMappingHelper.map(generateResValuesTask, "items") {
- variantConfiguration.resValues
- }
-
- ConventionMappingHelper.map(generateResValuesTask, "resOutputDir") {
- new File(scope.globalScope.generatedDir,
- "res/resValues/${scope.variantData.variantConfiguration.dirName}")
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateSplitAbiRes.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateSplitAbiRes.java
deleted file mode 100644
index 7ce9a68..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateSplitAbiRes.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.tasks.BaseTask;
-import com.android.builder.core.AaptPackageProcessBuilder;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.process.ProcessException;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFiles;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Generates all metadata (like AndroidManifest.xml) necessary for a ABI dimension split APK.
- */
- at ParallelizableTask
-public class GenerateSplitAbiRes extends BaseTask {
-
- private String applicationId;
-
- private int versionCode;
-
- private String versionName;
-
- private String outputBaseName;
-
- private Set<String> splits;
-
- private File outputDirectory;
-
- private boolean debuggable;
-
- private AaptOptions aaptOptions;
-
- @OutputFiles
- public List<File> getOutputFiles() {
- List<File> outputFiles = new ArrayList<File>();
- for (String split : getSplits()) {
- outputFiles.add(getOutputFileForSplit(split));
- }
-
- return outputFiles;
- }
-
- @TaskAction
- protected void doFullTaskAction() throws IOException, InterruptedException, ProcessException {
-
- for (String split : getSplits()) {
- String resPackageFileName = getOutputFileForSplit(split).getAbsolutePath();
-
- File tmpDirectory = new File(getOutputDirectory(), getOutputBaseName());
- tmpDirectory.mkdirs();
-
- File tmpFile = new File(tmpDirectory, "AndroidManifest.xml");
-
- String versionNameToUse = getVersionName();
- if (versionNameToUse == null) {
- versionNameToUse = String.valueOf(getVersionCode());
- }
-
- OutputStreamWriter fileWriter = new OutputStreamWriter(new FileOutputStream(tmpFile), "UTF-8");
- try {
- fileWriter.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"" + getApplicationId() + "\"\n"
- + " android:versionCode=\"" + getVersionCode() + "\"\n"
- + " android:versionName=\"" + versionNameToUse + "\"\n"
- + " split=\"lib_" + getOutputBaseName() + "\">\n"
- + " <uses-sdk android:minSdkVersion=\"21\"/>\n" + "</manifest> ");
- fileWriter.flush();
- } finally {
- fileWriter.close();
- }
-
- AaptPackageProcessBuilder aaptPackageCommandBuilder =
- new AaptPackageProcessBuilder(tmpFile, getAaptOptions())
- .setDebuggable(isDebuggable())
- .setResPackageOutput(resPackageFileName);
-
- getBuilder().processResources(
- aaptPackageCommandBuilder,
- false /* enforceUniquePackageName */,
- new LoggedProcessOutputHandler(getILogger()));
- }
- }
-
- private File getOutputFileForSplit(final String split) {
- return new File(getOutputDirectory(),
- "resources-" + getOutputBaseName() + "-" + split + ".ap_");
- }
-
- @Input
- public String getApplicationId() {
- return applicationId;
- }
-
- public void setApplicationId(String applicationId) {
- this.applicationId = applicationId;
- }
-
- @Input
- public int getVersionCode() {
- return versionCode;
- }
-
- public void setVersionCode(int versionCode) {
- this.versionCode = versionCode;
- }
-
- @Input
- @Optional
- public String getVersionName() {
- return versionName;
- }
-
- public void setVersionName(String versionName) {
- this.versionName = versionName;
- }
-
- @Input
- public String getOutputBaseName() {
- return outputBaseName;
- }
-
- public void setOutputBaseName(String outputBaseName) {
- this.outputBaseName = outputBaseName;
- }
-
- @Input
- public Set<String> getSplits() {
- return splits;
- }
-
- public void setSplits(Set<String> splits) {
- this.splits = splits;
- }
-
- public File getOutputDirectory() {
- return outputDirectory;
- }
-
- public void setOutputDirectory(File outputDirectory) {
- this.outputDirectory = outputDirectory;
- }
-
- @Input
- public boolean isDebuggable() {
- return debuggable;
- }
-
- public void setDebuggable(boolean debuggable) {
- this.debuggable = debuggable;
- }
-
- @Nested
- public AaptOptions getAaptOptions() {
- return aaptOptions;
- }
-
- public void setAaptOptions(AaptOptions aaptOptions) {
- this.aaptOptions = aaptOptions;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/InvokeManifestMerger.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/InvokeManifestMerger.groovy
deleted file mode 100644
index 754fddd..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/InvokeManifestMerger.groovy
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.build.gradle.internal.LoggerWrapper
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask
-import com.android.manifmerger.ManifestMerger2
-import com.android.manifmerger.MergingReport
-import com.android.utils.ILogger
-import com.google.common.base.Supplier
-import org.apache.tools.ant.BuildException
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.ParallelizableTask
-import org.gradle.api.tasks.TaskAction
-
-/**
- * Simple task to invoke the new Manifest Merger without any injection, features, system properties
- * or overlay manifests
- */
- at ParallelizableTask
-class InvokeManifestMerger extends DefaultAndroidTask implements Supplier<File> {
-
- @InputFile
- File mainManifestFile;
-
- @InputFiles
- List<File> secondaryManifestFiles
-
- @OutputFile
- File outputFile
-
- @TaskAction
- protected void doFullTaskAction() {
- ILogger iLogger = new LoggerWrapper(getLogger());
- ManifestMerger2.Invoker mergerInvoker = ManifestMerger2.
- newMerger(getMainManifestFile(), iLogger, ManifestMerger2.MergeType.APPLICATION)
- mergerInvoker.addLibraryManifests(secondaryManifestFiles.toArray(new File[secondaryManifestFiles.size()]))
- MergingReport mergingReport = mergerInvoker.merge();
- if (mergingReport.result.isError()) {
- getLogger().error(mergingReport.reportString);
- mergingReport.log(iLogger);
- throw new BuildException(mergingReport.reportString);
- }
- FileWriter fileWriter = null;
- try {
- fileWriter = new FileWriter(getOutputFile())
- fileWriter.append(mergingReport.getMergedDocument().get().prettyPrint())
- } finally {
- if (fileWriter != null) {
- fileWriter.close()
- }
- }
- }
-
- @Override
- File get() {
- return getOutputFile()
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JackTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JackTask.java
deleted file mode 100644
index 6f6848f..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JackTask.java
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.GlobalScope;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.AbstractAndroidCompile;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.variant.ApplicationVariantData;
-import com.android.build.gradle.tasks.factory.AbstractCompilesUtil;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.tasks.Job;
-import com.android.builder.tasks.JobContext;
-import com.android.builder.tasks.Task;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.process.ProcessException;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.base.Charsets;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-
-import org.gradle.api.Project;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * Jack task.
- */
- at ParallelizableTask
-public class JackTask extends AbstractAndroidCompile
- implements FileSupplier, BinaryFileProviderTask, JavaResourcesProvider {
-
- public static final FullRevision JACK_MIN_REV = new FullRevision(21, 1, 0);
-
- private AndroidBuilder androidBuilder;
-
- private boolean isVerbose;
- private boolean isDebugLog;
-
- private Collection<File> packagedLibraries;
- private Collection<File> proguardFiles;
- private Collection<File> jarJarRuleFiles;
-
- private boolean debug;
-
- private File tempFolder;
- private File jackFile;
- private File javaResourcesFolder;
-
- private File mappingFile;
-
- private boolean multiDexEnabled;
-
- private int minSdkVersion;
-
- private String javaMaxHeapSize;
-
- private File incrementalDir;
-
- @Override
- @TaskAction
- public void compile() {
- final Job<Void> job = new Job<Void>(getName(), new Task<Void>() {
- @Override
- public void run(@NonNull Job<Void> job, @NonNull JobContext<Void> context)
- throws IOException {
- try {
- JackTask.this.doMinification();
- } catch (ProcessException e) {
- throw new IOException(e);
- }
- }
-
- });
- try {
- SimpleWorkQueue.push(job);
-
- // wait for the task completion.
- job.await();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException(e);
- }
-
- }
-
- private void doMinification() throws ProcessException, IOException {
-
- if (System.getenv("USE_JACK_API") != null) {
- androidBuilder.convertByteCodeUsingJackApis(
- getDestinationDir(),
- getJackFile(),
- getClasspath().getFiles(),
- getPackagedLibraries(),
- getSource().getFiles(),
- getProguardFiles(),
- getMappingFile(),
- getJarJarRuleFiles(),
- getIncrementalDir(),
- getJavaResourcesFolder(),
- isMultiDexEnabled(),
- getMinSdkVersion());
- } else {
- // no incremental support through command line so far.
- androidBuilder.convertByteCodeWithJack(
- getDestinationDir(),
- getJackFile(),
- computeBootClasspath(),
- getPackagedLibraries(),
- computeEcjOptionFile(),
- getProguardFiles(),
- getMappingFile(),
- getJarJarRuleFiles(),
- isMultiDexEnabled(),
- getMinSdkVersion(),
- isDebugLog,
- getJavaMaxHeapSize(),
- new LoggedProcessOutputHandler(androidBuilder.getLogger()));
- }
-
- }
-
- private File computeEcjOptionFile() throws IOException {
- File folder = getTempFolder();
- //noinspection ResultOfMethodCallIgnored
- folder.mkdirs();
- File file = new File(folder, "ecj-options.txt");
-
- StringBuilder sb = new StringBuilder();
-
- for (File sourceFile : getSource().getFiles()) {
- sb.append(sourceFile.getAbsolutePath()).append("\n");
- }
-
- //noinspection ResultOfMethodCallIgnored
- file.getParentFile().mkdirs();
-
- Files.write(sb.toString(), file, Charsets.UTF_8);
-
- return file;
- }
-
- private String computeBootClasspath() {
- return Joiner.on(':').join(
- Iterables.transform(getClasspath().getFiles(), GET_ABSOLUTE_PATH));
- }
-
- private static final Function<File, String> GET_ABSOLUTE_PATH = new Function<File, String>() {
- @Override
- public String apply(File file) {
- return file.getAbsolutePath();
- }
- };
-
-
- @InputFile
- public File getJackExe() {
- return new File(
- androidBuilder.getTargetInfo().getBuildTools().getPath(BuildToolInfo.PathId.JACK));
- }
-
- public AndroidBuilder getAndroidBuilder() {
- return androidBuilder;
- }
-
- public void setAndroidBuilder(AndroidBuilder androidBuilder) {
- this.androidBuilder = androidBuilder;
- }
-
- public boolean getIsVerbose() {
- return isVerbose;
- }
-
- public void setIsVerbose(boolean isVerbose) {
- this.isVerbose = isVerbose;
- }
-
- public boolean getIsDebugLog() {
- return isDebugLog;
- }
-
- public void setIsDebugLog(boolean isDebugLog) {
- this.isDebugLog = isDebugLog;
- }
-
- @InputFiles
- public Collection<File> getPackagedLibraries() {
- return packagedLibraries;
- }
-
- public void setPackagedLibraries(Collection<File> packagedLibraries) {
- this.packagedLibraries = packagedLibraries;
- }
-
- @InputFiles
- @Optional
- public Collection<File> getProguardFiles() {
- return proguardFiles;
- }
-
- public void setProguardFiles(Collection<File> proguardFiles) {
- this.proguardFiles = proguardFiles;
- }
-
- @InputFiles
- @Optional
- public Collection<File> getJarJarRuleFiles() {
- return jarJarRuleFiles;
- }
-
- public void setJarJarRuleFiles(Collection<File> jarJarRuleFiles) {
- this.jarJarRuleFiles = jarJarRuleFiles;
- }
-
- @Input
- public boolean getDebug() {
- return debug;
- }
-
- public void setDebug(boolean debug) {
- this.debug = debug;
- }
-
- public File getTempFolder() {
- return tempFolder;
- }
-
- public void setTempFolder(File tempFolder) {
- this.tempFolder = tempFolder;
- }
-
- @OutputFile
- public File getJackFile() {
- return jackFile;
- }
-
- public void setJackFile(File jackFile) {
- this.jackFile = jackFile;
- }
-
- @OutputFile
- @Optional
- public File getMappingFile() {
- return mappingFile;
- }
-
- public void setMappingFile(File mappingFile) {
- this.mappingFile = mappingFile;
- }
-
- @Input
- public boolean isMultiDexEnabled() {
- return multiDexEnabled;
- }
-
- public void setMultiDexEnabled(boolean multiDexEnabled) {
- this.multiDexEnabled = multiDexEnabled;
- }
-
- @Input
- public int getMinSdkVersion() {
- return minSdkVersion;
- }
-
- public void setMinSdkVersion(int minSdkVersion) {
- this.minSdkVersion = minSdkVersion;
- }
-
- @Input
- @Optional
- public String getJavaMaxHeapSize() {
- return javaMaxHeapSize;
- }
-
- public void setJavaMaxHeapSize(String javaMaxHeapSize) {
- this.javaMaxHeapSize = javaMaxHeapSize;
- }
-
- @Input
- @Optional
- public File getIncrementalDir() {
- return incrementalDir;
- }
-
- public void setIncrementalDir(File incrementalDir) {
- this.incrementalDir = incrementalDir;
- }
-
- @Input
- @Optional
- public File getJavaResourcesFolder() {
- return javaResourcesFolder;
- }
-
- public void setJavaResourcesFolder(File javaResourcesFolder) {
- this.javaResourcesFolder = javaResourcesFolder;
- }
-
- @NonNull
- @Override
- public ImmutableList<JavaResourcesLocation> getJavaResourcesLocations() {
- return ImmutableList.of(
- new JavaResourcesLocation(Type.FOLDER, getDestinationDir()));
- }
-
- @Override
- @NonNull
- public BinaryFileProviderTask.Artifact getArtifact() {
- return new BinaryFileProviderTask.Artifact(
- BinaryFileProviderTask.BinaryArtifactType.JACK,
- getJackFile());
- }
-
- // ----- FileSupplierTask ----
- @NonNull
- @Override
- public org.gradle.api.Task getTask() {
- return this;
- }
-
- @Override
- public File get() {
- return getMappingFile();
- }
-
- public static class ConfigAction implements TaskConfigAction<JackTask> {
-
- private final VariantScope scope;
- private final boolean isVerbose;
- private final boolean isDebugLog;
-
- public ConfigAction(VariantScope scope, boolean isVerbose, boolean isDebugLog) {
- this.scope = scope;
- this.isVerbose = isVerbose;
- this.isDebugLog = isDebugLog;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("compile", "JavaWithJack");
- }
-
- @Override
- public Class<JackTask> getType() {
- return JackTask.class;
- }
-
- @Override
- public void execute(JackTask jackTask) {
- jackTask.setIsVerbose(isVerbose);
- jackTask.setIsDebugLog(isDebugLog);
-
- GlobalScope globalScope = scope.getGlobalScope();
-
- jackTask.androidBuilder = globalScope.getAndroidBuilder();
- jackTask.setJavaMaxHeapSize(
- globalScope.getExtension().getDexOptions().getJavaMaxHeapSize());
-
- jackTask.setSource(scope.getVariantData().getJavaSources());
-
- final GradleVariantConfiguration config = scope.getVariantData().getVariantConfiguration();
- jackTask.setMultiDexEnabled(config.isMultiDexEnabled());
- jackTask.setMinSdkVersion(config.getMinSdkVersion().getApiLevel());
- jackTask.incrementalDir = scope.getJackIncrementalDir();
-
- // if the tested variant is an app, add its classpath. For the libraries,
- // it's done automatically since the classpath includes the library output as a normal
- // dependency.
- if (scope.getTestedVariantData() instanceof ApplicationVariantData) {
- ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() {
- @Override
- public FileCollection call() throws Exception {
- Project project = scope.getGlobalScope().getProject();
- return project.fileTree(scope.getJillRuntimeLibrariesDir()).plus(
- project.fileTree(
- scope.getTestedVariantData().getScope()
- .getJillRuntimeLibrariesDir())).plus(
- project.fileTree(
- scope.getTestedVariantData().getScope().getJackClassesZip()
- ));
- }
- });
- } else {
- ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() {
- @Override
- public FileCollection call() throws Exception {
- return scope.getGlobalScope().getProject().fileTree(
- scope.getJillRuntimeLibrariesDir());
- }
- });
- }
-
- ConventionMappingHelper.map(jackTask, "packagedLibraries", new Callable<Collection<File>>() {
- @Override
- public Collection<File> call() throws Exception {
- return scope.getGlobalScope().getProject()
- .fileTree(scope.getJillPackagedLibrariesDir()).getFiles();
- }
- });
-
- jackTask.setDestinationDir(scope.getJackDestinationDir());
- jackTask.setJackFile(scope.getJackClassesZip());
- jackTask.setTempFolder(new File(scope.getGlobalScope().getIntermediatesDir(),
- "/tmp/jack/" + scope.getVariantConfiguration().getDirName()));
-
- jackTask.setJavaResourcesFolder(scope.getJavaResourcesDestinationDir());
- scope.setJavaResourcesProvider(jackTask);
-
- if (config.isMinifyEnabled()) {
- ConventionMappingHelper.map(jackTask, "proguardFiles", new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- // since all the output use the same resources, we can use the first output
- // to query for a proguard file.
- File sdkDir = scope.getGlobalScope().getSdkHandler().getAndCheckSdkFolder();
- File defaultProguardFile = new File(sdkDir,
- SdkConstants.FD_TOOLS + File.separatorChar
- + SdkConstants.FD_PROGUARD + File.separatorChar
- + TaskManager.DEFAULT_PROGUARD_CONFIG_FILE);
-
- List<File> proguardFiles = config.getProguardFiles(true /*includeLibs*/,
- ImmutableList.of(defaultProguardFile));
- File proguardResFile = scope.getProcessAndroidResourcesProguardOutputFile();
- proguardFiles.add(proguardResFile);
- // for tested app, we only care about their aapt config since the base
- // configs are the same files anyway.
- if (scope.getTestedVariantData() != null) {
- proguardResFile = scope.getTestedVariantData().getScope()
- .getProcessAndroidResourcesProguardOutputFile();
- proguardFiles.add(proguardResFile);
- }
-
- return proguardFiles;
- }
- });
-
- jackTask.mappingFile = new File(scope.getProguardOutputFolder(), "mapping.txt");
- }
-
-
- ConventionMappingHelper.map(jackTask, "jarJarRuleFiles", new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- List<File> jarJarRuleFiles = Lists.newArrayListWithCapacity(
- config.getJarJarRuleFiles().size());
- Project project = scope.getGlobalScope().getProject();
- for (File file: config.getJarJarRuleFiles()) {
- jarJarRuleFiles.add(project.file(file));
- }
- return jarJarRuleFiles;
- }
- });
-
- AbstractCompilesUtil.configureLanguageLevel(
- jackTask,
- scope.getGlobalScope().getExtension().getCompileOptions(),
- scope.getGlobalScope().getExtension().getCompileSdkVersion()
- );
-
- scope.getVariantData().jackTask = jackTask;
- scope.getVariantData().javaCompilerTask = jackTask;
- scope.getVariantData().mappingFileProviderTask = jackTask;
- scope.getVariantData().binayFileProviderTask = jackTask;
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JavaResourcesProvider.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JavaResourcesProvider.java
deleted file mode 100644
index 99c2963..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JavaResourcesProvider.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.TaskFactory;
-import com.android.build.gradle.internal.scope.AndroidTask;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-
-/**
- * Denotes a provider for the java resources ready to be packaged in the final variant APK.
- *
- * These java resources can be the merged results of all the packaged libraries resources as well
- * as obfuscated resources (that match obfuscated java code loading such resources).
- *
- * Depending on the configuration of the variant, the actual provider can be the obfuscation task
- * or the resource merging task or something else.
- */
-public interface JavaResourcesProvider {
-
- /**
- * Denotes how the java resources are provided (as a jar or as folder).
- */
- enum Type { JAR, FOLDER }
-
- final class JavaResourcesLocation {
- final Type type;
- final File location;
-
- public JavaResourcesLocation(Type type, File location) {
- this.type = type;
- this.location = location;
- }
- }
-
- @NonNull
- ImmutableList<JavaResourcesLocation> getJavaResourcesLocations();
-
- /**
- * Adapter for tasks that are not created yet.
- */
- class Adapter {
- @NonNull
- public static JavaResourcesProvider build(@NonNull final TaskFactory tasks,
- @NonNull final AndroidTask<? extends JavaResourcesProvider> androidTask) {
- return new JavaResourcesProvider() {
- @NonNull
- @Override
- public ImmutableList<JavaResourcesLocation> getJavaResourcesLocations() {
- return androidTask.get(tasks).getJavaResourcesLocations();
- }
- };
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JillTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JillTask.java
deleted file mode 100644
index 5340a5c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JillTask.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.dsl.DexOptions;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.GlobalScope;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.BaseTask;
-import com.android.builder.core.AndroidBuilder;
-import com.android.ide.common.internal.LoggedErrorException;
-import com.android.ide.common.internal.WaitableExecutor;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.FileUtils;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.hash.HashCode;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hashing;
-import com.google.common.io.Files;
-
-import org.gradle.api.Action;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
-import org.gradle.api.tasks.incremental.InputFileDetails;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
- at ParallelizableTask
-public class JillTask extends BaseTask {
-
- private Collection<File> inputLibs;
-
- private File outputFolder;
-
- private DexOptions dexOptions;
-
- @TaskAction
- public void taskAction(IncrementalTaskInputs taskInputs)
- throws LoggedErrorException, InterruptedException, IOException {
- FullRevision revision = getBuilder().getTargetInfo().getBuildTools().getRevision();
- if (revision.compareTo(JackTask.JACK_MIN_REV) < 0) {
- throw new RuntimeException(
- "Jack requires Build Tools " + JackTask.JACK_MIN_REV.toString()
- + " or later");
- }
-
- final File outFolder = getOutputFolder();
-
- // if we are not in incremental mode, then outOfDate will contain
- // all th files, but first we need to delete the previous output
- if (!taskInputs.isIncremental()) {
- FileUtils.emptyFolder(outFolder);
- }
-
- final Set<String> hashs = Sets.newHashSet();
- final WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
- final List<File> inputFileDetails = Lists.newArrayList();
-
- final AndroidBuilder builder = getBuilder();
-
- taskInputs.outOfDate(new Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails change) {
- inputFileDetails.add(change.getFile());
- }
- });
-
- for (final File file : inputFileDetails) {
- Callable<Void> action = new JillCallable(this, file, hashs, outFolder, builder);
- executor.execute(action);
- }
-
- taskInputs.removed(new Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails change) {
- File jackFile = getJackFileName(outFolder, ((InputFileDetails) change).getFile());
- //noinspection ResultOfMethodCallIgnored
- jackFile.delete();
- }
- });
-
- executor.waitForTasksWithQuickFail(false);
- }
-
- @Input
- public String getBuildToolsVersion() {
- return getBuildTools().getRevision().toString();
- }
-
- @InputFiles
- public Collection<File> getInputLibs() {
- return inputLibs;
- }
-
- public void setInputLibs(Collection<File> inputLibs) {
- this.inputLibs = inputLibs;
- }
-
- @OutputDirectory
- public File getOutputFolder() {
- return outputFolder;
- }
-
- public void setOutputFolder(File outputFolder) {
- this.outputFolder = outputFolder;
- }
-
- @Nested
- public DexOptions getDexOptions() {
- return dexOptions;
- }
-
- public void setDexOptions(DexOptions dexOptions) {
- this.dexOptions = dexOptions;
- }
-
- private final class JillCallable implements Callable<Void> {
-
- @NonNull
- private final File fileToProcess;
-
- @NonNull
- private final Set<String> hashs;
-
- @NonNull
- private final com.android.builder.core.DexOptions options = getDexOptions();
-
- @NonNull
- private final File outFolder;
-
- @NonNull
- private final AndroidBuilder builder;
-
- private JillCallable(JillTask enclosing, @NonNull File file, @NonNull Set<String> hashs,
- @NonNull File outFolder, @NonNull AndroidBuilder builder) {
- this.fileToProcess = file;
- this.hashs = hashs;
- this.outFolder = outFolder;
- this.builder = builder;
- }
-
- @Override
- public Void call() throws Exception {
- // TODO remove once we can properly add a library as a dependency of its test.
- String hash = getFileHash(fileToProcess);
-
- synchronized (hashs) {
- if (hashs.contains(hash)) {
- return null;
- }
-
- hashs.add(hash);
- }
-
- //noinspection GroovyAssignabilityCheck
- File jackFile = getJackFileName(outFolder, fileToProcess);
- //noinspection GroovyAssignabilityCheck
- builder.convertLibraryToJack(fileToProcess, jackFile, options,
- new LoggedProcessOutputHandler(builder.getLogger()));
-
- return null;
- }
-
- @NonNull
- public final File getOutFolder() {
- return outFolder;
- }
- }
-
- /**
- * Returns the hash of a file.
- *
- * @param file the file to hash
- */
- private static String getFileHash(@NonNull File file) throws IOException {
- HashCode hashCode = Files.hash(file, Hashing.sha1());
- return hashCode.toString();
- }
-
- /**
- * Returns a unique File for the converted library, even if there are 2 libraries with the same
- * file names (but different paths)
- *
- * @param outFolder the output folder.
- * @param inputFile the library
- */
- @NonNull
- public static File getJackFileName(@NonNull File outFolder, @NonNull File inputFile) {
- // get the filename
- String name = inputFile.getName();
- // remove the extension
- int pos = name.lastIndexOf('.');
- if (pos != -1) {
- name = name.substring(0, pos);
- }
-
- // add a hash of the original file path.
- String input = inputFile.getAbsolutePath();
- HashFunction hashFunction = Hashing.sha1();
- HashCode hashCode = hashFunction.hashString(input, Charsets.UTF_16LE);
-
- return new File(outFolder, name + "-" + hashCode.toString() + SdkConstants.DOT_JAR);
- }
-
- public static class RuntimeTaskConfigAction implements TaskConfigAction<JillTask> {
-
- private final VariantScope variantScope;
-
- // TODO: If task can be shared between variants, change to GlobalScope.
- public RuntimeTaskConfigAction(VariantScope scope) {
- this.variantScope = scope;
- }
-
- @Override
- public String getName() {
- return variantScope.getTaskName("jill", "RuntimeLibraries");
- }
-
- @Override
- public Class<JillTask> getType() {
- return JillTask.class;
- }
-
- @Override
- public void execute(JillTask jillTask) {
- final GlobalScope globalScope = variantScope.getGlobalScope();
- final AndroidBuilder androidBuilder = globalScope.getAndroidBuilder();
-
- jillTask.setAndroidBuilder(androidBuilder);
- jillTask.setVariantName(variantScope.getVariantConfiguration().getFullName());
- jillTask.setDexOptions(globalScope.getExtension().getDexOptions());
-
- ConventionMappingHelper.map(jillTask, "inputLibs", new Callable<List<File>>() {
- @Override
- public List<File> call() throws Exception {
- return androidBuilder.getBootClasspath();
- }
- });
-
- jillTask.setOutputFolder(variantScope.getJillRuntimeLibrariesDir());
- }
- }
-
- public static class PackagedConfigAction implements TaskConfigAction<JillTask> {
-
- private final VariantScope variantScope;
-
- public PackagedConfigAction(VariantScope scope) {
- this.variantScope = scope;
- }
-
- @Override
- public String getName() {
- return variantScope.getTaskName("jill", "PackagedLibraries");
- }
-
- @Override
- public Class<JillTask> getType() {
- return JillTask.class;
- }
-
- @Override
- public void execute(JillTask jillTask) {
- final GlobalScope globalScope = variantScope.getGlobalScope();
- final AndroidBuilder androidBuilder = globalScope.getAndroidBuilder();
-
- jillTask.setAndroidBuilder(androidBuilder);
- jillTask.setVariantName(variantScope.getVariantConfiguration().getFullName());
- jillTask.setDexOptions(globalScope.getExtension().getDexOptions());
-
- ConventionMappingHelper.map(jillTask, "inputLibs", new Callable<Set<File>>() {
- @Override
- public Set<File> call() throws Exception {
- return androidBuilder.getPackagedJars(variantScope.getVariantConfiguration());
- }
- });
-
- jillTask.setOutputFolder(variantScope.getJillPackagedLibrariesDir());
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
deleted file mode 100644
index d3b4e42..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.gradle.internal.LintGradleClient
-import com.android.build.gradle.internal.dsl.LintOptions
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.android.tools.lint.LintCliFlags
-import com.android.tools.lint.Reporter
-import com.android.tools.lint.Warning
-import com.android.tools.lint.checks.BuiltinIssueRegistry
-import com.android.tools.lint.checks.GradleDetector
-import com.android.tools.lint.client.api.IssueRegistry
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.Severity
-import com.google.common.collect.Maps
-import org.gradle.api.GradleException
-import org.gradle.api.Project
-import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.tasks.ParallelizableTask
-import org.gradle.api.tasks.TaskAction
-import org.gradle.tooling.provider.model.ToolingModelBuilder
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
-
- at ParallelizableTask
-public class Lint extends DefaultAndroidTask {
- @NonNull private LintOptions mLintOptions
- @Nullable private File mSdkHome
- private boolean mFatalOnly
- private ToolingModelBuilderRegistry mToolingRegistry
-
- public void setLintOptions(@NonNull LintOptions lintOptions) {
- mLintOptions = lintOptions
- }
-
- public void setSdkHome(@NonNull File sdkHome) {
- mSdkHome = sdkHome
- }
-
- void setToolingRegistry(ToolingModelBuilderRegistry toolingRegistry) {
- mToolingRegistry = toolingRegistry
- }
-
- public void setFatalOnly(boolean fatalOnly) {
- mFatalOnly = fatalOnly
- }
-
- @SuppressWarnings("GroovyUnusedDeclaration")
- @TaskAction
- public void lint() {
- def modelProject = createAndroidProject(project)
- if (getVariantName() != null) {
- lintSingleVariant(modelProject, getVariantName())
- } else {
- lintAllVariants(modelProject)
- }
- }
-
- /**
- * Runs lint individually on all the variants, and then compares the results
- * across variants and reports these
- */
- public void lintAllVariants(@NonNull AndroidProject modelProject) {
- Map<Variant,List<Warning>> warningMap = Maps.newHashMap()
- for (Variant variant : modelProject.getVariants()) {
- try {
- List<Warning> warnings = runLint(modelProject, variant.getName(), false)
- warningMap.put(variant, warnings)
- } catch (IOException e) {
- throw new GradleException("Invalid arguments.", e)
- }
- }
-
- // Compute error matrix
- def quiet = mLintOptions.quiet
-
-
- for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
- def variant = entry.getKey()
- def warnings = entry.getValue()
- if (!mFatalOnly && !quiet) {
- println "Ran lint on variant " + variant.getName() + ": " + warnings.size() +
- " issues found"
- }
- }
-
- List<Warning> mergedWarnings = LintGradleClient.merge(warningMap, modelProject)
- int errorCount = 0
- int warningCount = 0
- for (Warning warning : mergedWarnings) {
- if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) {
- errorCount++
- } else if (warning.severity == Severity.WARNING) {
- warningCount++
- }
- }
-
- IssueRegistry registry = new BuiltinIssueRegistry()
- LintCliFlags flags = new LintCliFlags()
- LintGradleClient client = new LintGradleClient(registry, flags, project, modelProject,
- mSdkHome, null)
- syncOptions(mLintOptions, client, flags, null, project, true, mFatalOnly)
-
- for (Reporter reporter : flags.getReporters()) {
- reporter.write(errorCount, warningCount, mergedWarnings)
- }
-
- if (flags.isSetExitCode() && errorCount > 0) {
- abort()
- }
- }
-
- private void abort() {
- def message;
- if (mFatalOnly) {
- message = "" +
- "Lint found fatal errors while assembling a release target.\n" +
- "\n" +
- "To proceed, either fix the issues identified by lint, or modify your build script as follows:\n" +
- "...\n" +
- "android {\n" +
- " lintOptions {\n" +
- " checkReleaseBuilds false\n" +
- " // Or, if you prefer, you can continue to check for errors in release builds,\n" +
- " // but continue the build even when errors are found:\n" +
- " abortOnError false\n" +
- " }\n" +
- "}\n" +
- "..."
- ""
- } else {
- message = "" +
- "Lint found errors in the project; aborting build.\n" +
- "\n" +
- "Fix the issues identified by lint, or add the following to your build script to proceed with errors:\n" +
- "...\n" +
- "android {\n" +
- " lintOptions {\n" +
- " abortOnError false\n" +
- " }\n" +
- "}\n" +
- "..."
- }
- throw new GradleException(message);
- }
-
- /**
- * Runs lint on a single specified variant
- */
- public void lintSingleVariant(@NonNull AndroidProject modelProject, String variantName) {
- runLint(modelProject, variantName, true)
- }
-
- /** Runs lint on the given variant and returns the set of warnings */
- private List<Warning> runLint(
- @NonNull AndroidProject modelProject,
- @NonNull String variantName,
- boolean report) {
- IssueRegistry registry = createIssueRegistry()
- LintCliFlags flags = new LintCliFlags()
- LintGradleClient client = new LintGradleClient(registry, flags, project, modelProject,
- mSdkHome, variantName)
- if (mFatalOnly) {
- if (!mLintOptions.isCheckReleaseBuilds()) {
- return
- }
- flags.setFatalOnly(true)
- }
- syncOptions(mLintOptions, client, flags, variantName, project, report, mFatalOnly)
- if (!report || mFatalOnly) {
- flags.setQuiet(true)
- }
-
- List<Warning> warnings;
- try {
- warnings = client.run(registry)
- } catch (IOException e) {
- throw new GradleException("Invalid arguments.", e)
- }
-
- if (report && client.haveErrors() && flags.isSetExitCode()) {
- abort()
- }
-
- return warnings;
- }
-
- private static syncOptions(
- @NonNull LintOptions options,
- @NonNull LintGradleClient client,
- @NonNull LintCliFlags flags,
- @NonNull String variantName,
- @NonNull Project project,
- boolean report,
- boolean fatalOnly) {
- options.syncTo(client, flags, variantName, project, report)
-
- if (fatalOnly || flags.quiet) {
- for (Reporter reporter : flags.getReporters()) {
- reporter.setDisplayEmpty(false)
- }
- }
- }
-
- private AndroidProject createAndroidProject(@NonNull Project gradleProject) {
- String modelName = AndroidProject.class.getName()
- ToolingModelBuilder modelBuilder = mToolingRegistry.getBuilder(modelName)
- assert modelBuilder != null
- return (AndroidProject) modelBuilder.buildAll(modelName, gradleProject)
- }
-
- private static BuiltinIssueRegistry createIssueRegistry() {
- return new LintGradleIssueRegistry()
- }
-
- // Issue registry when Lint is run inside Gradle: we replace the Gradle
- // detector with a local implementation which directly references Groovy
- // for parsing. In Studio on the other hand, the implementation is replaced
- // by a PSI-based check. (This is necessary for now since we don't have a
- // tool-agnostic API for the Groovy AST and we don't want to add a 6.3MB dependency
- // on Groovy itself quite yet.
- public static class LintGradleIssueRegistry extends BuiltinIssueRegistry {
- private boolean mInitialized;
-
- public LintGradleIssueRegistry() {
- }
-
- @NonNull
- @Override
- public List<Issue> getIssues() {
- List<Issue> issues = super.getIssues();
- if (!mInitialized) {
- mInitialized = true;
- for (Issue issue : issues) {
- if (issue.getImplementation().getDetectorClass() == GradleDetector.class) {
- issue.setImplementation(GroovyGradleDetector.IMPLEMENTATION);
- }
- }
- }
-
- return issues;
- }
- }
-
- public static class ConfigAction implements TaskConfigAction<Lint> {
-
- @NonNull
- VariantScope scope
-
- ConfigAction(@NonNull VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- @NonNull
- String getName() {
- return scope.getTaskName("lint")
- }
-
- @Override
- @NonNull
- Class<Lint> getType() {
- return Lint
- }
-
- @Override
- void execute(Lint lint) {
- lint.setLintOptions(scope.globalScope.getExtension().lintOptions)
- lint.setSdkHome(scope.globalScope.sdkHandler.getSdkFolder())
- lint.setVariantName(scope.variantConfiguration.fullName)
- lint.setToolingRegistry(scope.globalScope.toolingRegistry)
- lint.description = "Runs lint on the " + scope.variantConfiguration.fullName.capitalize() + " build."
- lint.group = JavaBasePlugin.VERIFICATION_GROUP
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.groovy
deleted file mode 100644
index 75de433..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks
-import com.android.build.gradle.internal.tasks.IncrementalTask
-import com.google.common.base.Function
-import com.google.common.base.Joiner
-import com.google.common.collect.Iterables
-import com.google.common.collect.Lists
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-/**
- * A task that processes the manifest
- */
-public abstract class ManifestProcessorTask extends IncrementalTask {
-
- // ----- PUBLIC TASK API -----
-
- /**
- * The processed Manifest.
- */
- @OutputFile
- File manifestOutputFile
-
- /**
- * The aapt friendly processed Manifest. In case we are processing a library manifest, some
- * placeholders may not have been resolved (and will be when the library is merged into the
- * importing application). However, such placeholders keys are not friendly to aapt which
- * flags some illegal characters. Such characters are replaced/encoded in this version.
- */
- @OutputFile @Optional
- File aaptFriendlyManifestOutputFile
-
- /**
- * Serialize a map key+value pairs into a comma separated list. Map elements are sorted to
- * ensure stability between instances.
- * @param mapToSerialize the map to serialize.
- */
- protected String serializeMap(Map<String, String> mapToSerialize) {
- Joiner keyValueJoiner = Joiner.on(":");
- // transform the map on a list of key:value items, sort it and concatenate it.
- return Joiner.on(",").join(
- Lists.newArrayList(Iterables.transform(
- mapToSerialize.entrySet(),
- new Function<Map.Entry<String, String>, String>() {
-
- @Override
- public String apply(final Map.Entry<String, String> input) {
- return keyValueJoiner.join(input.getKey(), input.getValue());
- }
- })).sort())
- }
-
- /**
- * Returns the manifest processing output file. if an aapt friendly version was requested,
- * return that otherwise return the actual output of the manifest merger tool directly.
- */
- public File getOutputFile() {
- getAaptFriendlyManifestOutputFile() == null ? getManifestOutputFile()
- : getAaptFriendlyManifestOutputFile();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeAssets.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeAssets.groovy
deleted file mode 100644
index ab6d2dc..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeAssets.groovy
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.IncrementalTask
-import com.android.build.gradle.internal.variant.BaseVariantData
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.android.builder.core.VariantConfiguration
-import com.android.builder.core.VariantType
-import com.android.builder.model.AndroidProject
-import com.android.ide.common.res2.AssetMerger
-import com.android.ide.common.res2.AssetSet
-import com.android.ide.common.res2.FileStatus
-import com.android.ide.common.res2.FileValidity
-import com.android.ide.common.res2.MergedAssetWriter
-import com.android.ide.common.res2.MergingException
-import com.android.utils.FileUtils
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.ParallelizableTask
-
- at ParallelizableTask
-public class MergeAssets extends IncrementalTask {
-
- // ----- PUBLIC TASK API -----
-
- @OutputDirectory
- File outputDir
-
- // ----- PRIVATE TASK API -----
-
- // fake input to detect changes. Not actually used by the task
- @InputFiles
- Iterable<File> getRawInputFolders() {
- return flattenSourceSets(getInputAssetSets())
- }
-
- // actual inputs
- List<AssetSet> inputAssetSets
-
- private final FileValidity<AssetSet> fileValidity = new FileValidity<AssetSet>();
-
- @Override
- protected boolean isIncremental() {
- return true
- }
-
- @Override
- protected void doFullTaskAction() {
- // this is full run, clean the previous output
- File destinationDir = getOutputDir()
- FileUtils.emptyFolder(destinationDir)
-
- List<AssetSet> assetSets = getInputAssetSets()
-
- // create a new merger and populate it with the sets.
- AssetMerger merger = new AssetMerger()
-
- try {
- for (AssetSet assetSet : assetSets) {
- // set needs to be loaded.
- assetSet.loadFromFiles(getILogger())
- merger.addDataSet(assetSet)
- }
-
- // get the merged set and write it down.
- MergedAssetWriter writer = new MergedAssetWriter(destinationDir)
-
- merger.mergeData(writer, false /*doCleanUp*/)
-
- // No exception? Write the known state.
- merger.writeBlobTo(getIncrementalFolder(), writer)
- } catch (MergingException e) {
- println e.getMessage()
- merger.cleanBlob(getIncrementalFolder())
- throw new ResourceException(e.getMessage(), e)
- }
- }
-
- @Override
- protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
- // create a merger and load the known state.
- AssetMerger merger = new AssetMerger()
- try {
- if (!merger.loadFromBlob(getIncrementalFolder(), true /*incrementalState*/)) {
- doFullTaskAction()
- return
- }
-
- // compare the known state to the current sets to detect incompatibility.
- // This is in case there's a change that's too hard to do incrementally. In this case
- // we'll simply revert to full build.
- List<AssetSet> assetSets = getInputAssetSets()
-
- if (!merger.checkValidUpdate(assetSets)) {
- project.logger.info("Changed Asset sets: full task run!")
- doFullTaskAction()
- return
- }
-
- // The incremental process is the following:
- // Loop on all the changed files, find which ResourceSet it belongs to, then ask
- // the resource set to update itself with the new file.
- for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
- File changedFile = entry.getKey()
-
- merger.findDataSetContaining(changedFile, fileValidity)
- if (fileValidity.status == FileValidity.FileStatus.UNKNOWN_FILE) {
- doFullTaskAction()
- return
- } else if (fileValidity.status == FileValidity.FileStatus.VALID_FILE) {
- if (!fileValidity.dataSet.updateWith(
- fileValidity.sourceFile, changedFile, entry.getValue(), getILogger())) {
- project.logger.info(
- String.format("Failed to process %s event! Full task run",
- entry.getValue()))
- doFullTaskAction()
- return
- }
- }
- }
-
- MergedAssetWriter writer = new MergedAssetWriter(getOutputDir())
-
- merger.mergeData(writer, false /*doCleanUp*/)
-
- // No exception? Write the known state.
- merger.writeBlobTo(getIncrementalFolder(), writer)
- } catch (MergingException e) {
- println e.getMessage()
- merger.cleanBlob(getIncrementalFolder())
- throw new ResourceException(e.getMessage(), e)
- } finally {
- // some clean up after the task to help multi variant/module builds.
- fileValidity.clear();
- }
- }
-
-
- public static class ConfigAction implements TaskConfigAction<MergeAssets> {
-
- @NonNull
- VariantScope scope
-
- ConfigAction(VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- String getName() {
- return scope.getTaskName("merge", "Assets");
- }
-
- @Override
- Class<MergeAssets> getType() {
- return MergeAssets
- }
-
- @Override
- void execute(MergeAssets mergeAssetsTask) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
- VariantConfiguration variantConfig = variantData.variantConfiguration
- boolean includeDependencies = variantConfig.type != VariantType.LIBRARY
-
- variantData.mergeAssetsTask = mergeAssetsTask
-
- mergeAssetsTask.androidBuilder = scope.globalScope.androidBuilder
- mergeAssetsTask.setVariantName(variantConfig.getFullName())
- mergeAssetsTask.incrementalFolder =
- new File(
- "$scope.globalScope.buildDir/${AndroidProject.FD_INTERMEDIATES}/incremental/mergeAssets/${variantConfig.dirName}")
-
- ConventionMappingHelper.map(mergeAssetsTask, "inputAssetSets") {
- def generatedAssets = []
- if (variantData.copyApkTask != null) {
- generatedAssets.add(variantData.copyApkTask.destinationDir)
- }
- variantConfig.getAssetSets(generatedAssets, includeDependencies)
- }
- ConventionMappingHelper.map(mergeAssetsTask, "outputDir") {
- scope.getMergeAssetsOutputDir()
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.groovy
deleted file mode 100644
index 277638c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.groovy
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantOutputScope
-import com.android.build.gradle.internal.variant.ApkVariantOutputData
-import com.android.build.gradle.internal.variant.BaseVariantData
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.android.builder.core.VariantConfiguration
-import com.android.builder.dependency.LibraryDependency
-import com.android.manifmerger.ManifestMerger2
-import com.google.common.collect.Lists
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.ParallelizableTask
-
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS
-
-/**
- * A task that processes the manifest
- */
- at ParallelizableTask
-public class MergeManifests extends ManifestProcessorTask {
-
- // ----- PRIVATE TASK API -----
- @InputFile
- File getMainManifest() {
- return variantConfiguration.getMainManifest();
- }
-
- @InputFiles
- List<File> getManifestOverlays() {
- return variantConfiguration.getManifestOverlays();
- }
-
- @Input @Optional
- String getPackageOverride() {
- return variantConfiguration.getIdOverride();
- }
-
- @Input
- int getVersionCode() {
- if (variantOutputData!= null) {
- return variantOutputData.versionCode
- }
-
- return variantConfiguration.versionCode;
- }
-
- @Input @Optional
- String getVersionName() {
- if (variantOutputData!= null) {
- return variantOutputData.versionName
- }
- return variantConfiguration.getVersionName();
- }
-
- @Input @Optional
- String minSdkVersion
-
- @Input @Optional
- String targetSdkVersion
-
- @Input @Optional
- Integer maxSdkVersion
-
- @Input @Optional
- File reportFile
-
- /**
- * Return a serializable version of our map of key value pairs for placeholder substitution.
- * This serialized form is only used by gradle to compare past and present tasks to determine
- * whether a task need to be re-run or not.
- */
- @Input @Optional
- String getManifestPlaceholders() {
- return serializeMap(variantConfiguration.getManifestPlaceholders());
- }
-
- VariantConfiguration variantConfiguration
- ApkVariantOutputData variantOutputData
- List<ManifestDependencyImpl> libraries
-
- /**
- * since libraries above can't return it's input files (@Nested doesn't
- * work on lists), so do a method that will gather them and return them.
- */
- @InputFiles
- List<File> getLibraryManifests() {
- List<ManifestDependencyImpl> libs = getLibraries()
- if (libs == null || libs.isEmpty()) {
- return Collections.emptyList();
- }
-
- List<File> files = Lists.newArrayListWithCapacity(libs.size() * 2)
- for (ManifestDependencyImpl mdi : libs) {
- files.addAll(mdi.getAllManifests())
- }
-
- return files;
- }
-
- @Override
- protected void doFullTaskAction() {
-
- getBuilder().mergeManifests(
- getMainManifest(),
- getManifestOverlays(),
- getLibraries(),
- getPackageOverride(),
- getVersionCode(),
- getVersionName(),
- getMinSdkVersion(),
- getTargetSdkVersion(),
- getMaxSdkVersion(),
- getManifestOutputFile().absolutePath,
- // no appt friendly merged manifest file necessary for applications.
- null /* aaptFriendlyManifestOutputFile */ ,
- ManifestMerger2.MergeType.APPLICATION,
- variantConfiguration.getManifestPlaceholders(),
- getReportFile())
- }
-
- // ----- ConfigAction -----
-
- public static class ConfigAction implements TaskConfigAction<MergeManifests> {
-
- VariantOutputScope scope
-
- ConfigAction(VariantOutputScope scope) {
- this.scope = scope
- }
-
- @Override
- String getName() {
- return scope.getTaskName("process", "Manifest")
- }
-
- @Override
- Class<MergeManifests> getType() {
- return MergeManifests
- }
-
- @Override
- void execute(MergeManifests processManifestTask) {
- BaseVariantOutputData variantOutputData = scope.variantOutputData
-
- BaseVariantData<? extends BaseVariantOutputData> variantData =
- scope.variantScope.variantData
- VariantConfiguration config = variantData.getVariantConfiguration()
-
- variantOutputData.manifestProcessorTask = processManifestTask
-
- processManifestTask.androidBuilder = scope.globalScope.androidBuilder
- processManifestTask.setVariantName(config.getFullName())
-
- processManifestTask.dependsOn variantData.prepareDependenciesTask
- if (variantData.generateApkDataTask != null) {
- processManifestTask.dependsOn variantData.generateApkDataTask
- }
- if (scope.compatibleScreensManifestTask != null) {
- processManifestTask.dependsOn scope.compatibleScreensManifestTask.name
- }
-
- processManifestTask.variantConfiguration = config
- if (variantOutputData instanceof ApkVariantOutputData) {
- processManifestTask.variantOutputData =
- variantOutputData as ApkVariantOutputData
- }
-
- ConventionMappingHelper.map(processManifestTask, "libraries") {
- List<ManifestDependencyImpl> manifests =
- getManifestDependencies(config.directLibraries)
-
- if (variantData.generateApkDataTask != null &&
- variantData.getVariantConfiguration().getBuildType().
- isEmbedMicroApp()) {
- manifests.add(new ManifestDependencyImpl(
- variantData.generateApkDataTask.getManifestFile(), []))
- }
-
- if (scope.compatibleScreensManifestTask != null) {
- manifests.add(new ManifestDependencyImpl(
- scope.getCompatibleScreensManifestFile(), []))
- }
-
- return manifests
- }
-
- ConventionMappingHelper.map(processManifestTask, "minSdkVersion") {
- if (scope.globalScope.androidBuilder.isPreviewTarget()) {
- return scope.globalScope.androidBuilder.getTargetCodename()
- }
-
- config.mergedFlavor.minSdkVersion?.apiString
- }
-
- ConventionMappingHelper.map(processManifestTask, "targetSdkVersion") {
- if (scope.globalScope.androidBuilder.isPreviewTarget()) {
- return scope.globalScope.androidBuilder.getTargetCodename()
- }
-
- return config.mergedFlavor.targetSdkVersion?.apiString
- }
-
- ConventionMappingHelper.map(processManifestTask, "maxSdkVersion") {
- if (scope.globalScope.androidBuilder.isPreviewTarget()) {
- return null
- }
-
- return config.mergedFlavor.maxSdkVersion
- }
-
- ConventionMappingHelper.map(processManifestTask, "manifestOutputFile") {
- scope.getManifestOutputFile()
- }
-
- ConventionMappingHelper.map(processManifestTask, "reportFile") {
- new File(
- "${scope.getGlobalScope().getBuildDir()}/${FD_OUTPUTS}/logs/manifest-merger-${config.baseName}-report.txt")
- }
-
- }
-
- @NonNull
- private static List<ManifestDependencyImpl> getManifestDependencies(
- List<LibraryDependency> libraries) {
-
- List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size())
-
- for (LibraryDependency lib : libraries) {
- // get the dependencies
- List<ManifestDependencyImpl> children = getManifestDependencies(lib.dependencies)
- list.add(new ManifestDependencyImpl(lib.getName(), lib.manifest, children))
- }
-
- return list
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java
deleted file mode 100644
index 5805f27..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.IncrementalTask;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.png.QueuedCruncher;
-import com.android.builder.png.VectorDrawableRenderer;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.res2.FileStatus;
-import com.android.ide.common.res2.FileValidity;
-import com.android.ide.common.res2.GeneratedResourceSet;
-import com.android.ide.common.res2.MergedResourceWriter;
-import com.android.ide.common.res2.MergingException;
-import com.android.ide.common.res2.ResourceMerger;
-import com.android.ide.common.res2.ResourcePreprocessor;
-import com.android.ide.common.res2.ResourceSet;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.FileUtils;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.StopExecutionException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
- at ParallelizableTask
-public class MergeResources extends IncrementalTask {
-
- /**
- * The first version of build tools that normalizes resources when packaging the APK.
- *
- * <p>This means that e.g. drawable-hdpi becomes drawable-hdpi-v4 to make it clear it was not
- * available before API 4.
- */
- public static final FullRevision NORMALIZE_RESOURCES_BUILD_TOOLS = new FullRevision(21, 0, 0);
-
- // ----- PUBLIC TASK API -----
-
- /**
- * Directory to write the merged resources to
- */
- private File outputDir;
-
- // ----- PRIVATE TASK API -----
-
- /**
- * Optional file to write any publicly imported resource types and names to
- */
- private File publicFile;
-
- private boolean process9Patch;
-
- private boolean crunchPng;
-
- private boolean useNewCruncher;
-
- private boolean insertSourceMarkers = true;
-
- private boolean normalizeResources;
-
- private ResourcePreprocessor preprocessor;
-
- // actual inputs
- private List<ResourceSet> inputResourceSets;
-
- private final FileValidity<ResourceSet> fileValidity = new FileValidity<ResourceSet>();
-
- // fake input to detect changes. Not actually used by the task
- @InputFiles
- public Iterable<File> getRawInputFolders() {
- return flattenSourceSets(getInputResourceSets());
- }
-
- @Input
- public String getBuildToolsVersion() {
- return getBuildTools().getRevision().toString();
- }
-
- @Override
- protected boolean isIncremental() {
- return true;
- }
-
- private PngCruncher getCruncher() {
- if (getUseNewCruncher()) {
- if (getBuilder().getTargetInfo().getBuildTools().getRevision().getMajor() >= 22) {
- return QueuedCruncher.Builder.INSTANCE.newCruncher(
- getBuilder().getTargetInfo().getBuildTools().getPath(
- BuildToolInfo.PathId.AAPT), getILogger());
- }
- getLogger().info("New PNG cruncher will be enabled with build tools 22 and above.");
- }
- return getBuilder().getAaptCruncher(new LoggedProcessOutputHandler(getBuilder().getLogger()));
- }
-
- @Override
- protected void doFullTaskAction() throws IOException {
- // this is full run, clean the previous output
- File destinationDir = getOutputDir();
- FileUtils.emptyFolder(destinationDir);
-
- List<ResourceSet> resourceSets = getConfiguredResourceSets();
-
- // create a new merger and populate it with the sets.
- ResourceMerger merger = new ResourceMerger();
-
- try {
- for (ResourceSet resourceSet : resourceSets) {
- resourceSet.loadFromFiles(getILogger());
- merger.addDataSet(resourceSet);
- }
-
- // get the merged set and write it down.
- MergedResourceWriter writer = new MergedResourceWriter(
- destinationDir, getCruncher(),
- getCrunchPng(), getProcess9Patch(), getPublicFile(), preprocessor);
- writer.setInsertSourceMarkers(getInsertSourceMarkers());
-
- merger.mergeData(writer, false /*doCleanUp*/);
-
- // No exception? Write the known state.
- merger.writeBlobTo(getIncrementalFolder(), writer);
- throw new StopExecutionException("Stop for now.");
- } catch (MergingException e) {
- System.out.println(e.getMessage());
- merger.cleanBlob(getIncrementalFolder());
- throw new ResourceException(e.getMessage(), e);
- }
- }
-
- @Override
- protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
- // create a merger and load the known state.
- ResourceMerger merger = new ResourceMerger();
- try {
- if (!merger.loadFromBlob(getIncrementalFolder(), true /*incrementalState*/)) {
- doFullTaskAction();
- return;
- }
-
- for (ResourceSet resourceSet : merger.getDataSets()) {
- resourceSet.setNormalizeResources(normalizeResources);
- resourceSet.setPreprocessor(preprocessor);
- }
-
- List<ResourceSet> resourceSets = getConfiguredResourceSets();
-
- // compare the known state to the current sets to detect incompatibility.
- // This is in case there's a change that's too hard to do incrementally. In this case
- // we'll simply revert to full build.
- if (!merger.checkValidUpdate(resourceSets)) {
- getLogger().info("Changed Resource sets: full task run!");
- doFullTaskAction();
- return;
- }
-
- // The incremental process is the following:
- // Loop on all the changed files, find which ResourceSet it belongs to, then ask
- // the resource set to update itself with the new file.
- for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
- File changedFile = entry.getKey();
-
- merger.findDataSetContaining(changedFile, fileValidity);
- if (fileValidity.getStatus() == FileValidity.FileStatus.UNKNOWN_FILE) {
- doFullTaskAction();
- return;
- } else if (fileValidity.getStatus() == FileValidity.FileStatus.VALID_FILE) {
- if (!fileValidity.getDataSet().updateWith(
- fileValidity.getSourceFile(), changedFile, entry.getValue(),
- getILogger())) {
- getLogger().info(
- String.format("Failed to process %s event! Full task run",
- entry.getValue()));
- doFullTaskAction();
- return;
- }
- }
- }
-
- MergedResourceWriter writer = new MergedResourceWriter(
- getOutputDir(), getCruncher(),
- getCrunchPng(), getProcess9Patch(), getPublicFile(), preprocessor);
- writer.setInsertSourceMarkers(getInsertSourceMarkers());
- merger.mergeData(writer, false /*doCleanUp*/);
- // No exception? Write the known state.
- merger.writeBlobTo(getIncrementalFolder(), writer);
- } catch (MergingException e) {
- merger.cleanBlob(getIncrementalFolder());
- throw new ResourceException(e.getMessage(), e);
- } finally {
- // some clean up after the task to help multi variant/module builds.
- fileValidity.clear();
- }
- }
-
- @NonNull
- private List<ResourceSet> getConfiguredResourceSets() {
- List<ResourceSet> resourceSets = Lists.newArrayList(getInputResourceSets());
- List<ResourceSet> generatedSets = Lists.newArrayListWithCapacity(resourceSets.size());
-
- for (ResourceSet resourceSet : resourceSets) {
- resourceSet.setNormalizeResources(normalizeResources);
- resourceSet.setPreprocessor(preprocessor);
- ResourceSet generatedSet = new GeneratedResourceSet(resourceSet);
- resourceSet.setGeneratedSet(generatedSet);
- generatedSets.add(generatedSet);
- }
-
- // Put all generated sets at the start of the list.
- resourceSets.addAll(0, generatedSets);
- return resourceSets;
- }
-
- @Input
- public boolean isProcess9Patch() {
- return process9Patch;
- }
-
- @Input
- public boolean isCrunchPng() {
- return crunchPng;
- }
-
- @Input
- public boolean isUseNewCruncher() {
- return useNewCruncher;
- }
-
- @Input
- public boolean isInsertSourceMarkers() {
- return insertSourceMarkers;
- }
-
- @Input
- public boolean isNormalizeResources() {
- return normalizeResources;
- }
-
- public void setNormalizeResources(boolean normalizeResources) {
- this.normalizeResources = normalizeResources;
- }
-
- public List<ResourceSet> getInputResourceSets() {
- return inputResourceSets;
- }
-
- public void setInputResourceSets(
- List<ResourceSet> inputResourceSets) {
- this.inputResourceSets = inputResourceSets;
- }
-
- public boolean getUseNewCruncher() {
- return useNewCruncher;
- }
-
- public void setUseNewCruncher(boolean useNewCruncher) {
- this.useNewCruncher = useNewCruncher;
- }
-
- @OutputDirectory
- public File getOutputDir() {
- return outputDir;
- }
-
- public void setOutputDir(File outputDir) {
- this.outputDir = outputDir;
- }
-
- public boolean getCrunchPng() {
- return crunchPng;
- }
-
- public void setCrunchPng(boolean crunchPng) {
- this.crunchPng = crunchPng;
- }
-
- public boolean getProcess9Patch() {
- return process9Patch;
- }
-
- public void setProcess9Patch(boolean process9Patch) {
- this.process9Patch = process9Patch;
- }
-
- @Optional
- @OutputFile
- public File getPublicFile() {
- return publicFile;
- }
-
- public void setPublicFile(File publicFile) {
- this.publicFile = publicFile;
- }
-
- public boolean getInsertSourceMarkers() {
- return insertSourceMarkers;
- }
-
- public void setInsertSourceMarkers(boolean insertSourceMarkers) {
- this.insertSourceMarkers = insertSourceMarkers;
- }
-
- public static class ConfigAction implements TaskConfigAction<MergeResources> {
-
- @NonNull
- private VariantScope scope;
-
- @NonNull
- private String taskNamePrefix;
-
- @Nullable
- private File outputLocation;
-
- private boolean includeDependencies;
-
- private boolean process9Patch;
-
- public ConfigAction(
- @NonNull VariantScope scope,
- @NonNull String taskNamePrefix,
- @Nullable File outputLocation,
- boolean includeDependencies,
- boolean process9Patch) {
- this.scope = scope;
- this.taskNamePrefix = taskNamePrefix;
- this.outputLocation = outputLocation;
- this.includeDependencies = includeDependencies;
- this.process9Patch = process9Patch;
-
- scope.setMergeResourceOutputDir(outputLocation);
- }
-
- @Override
- public String getName() {
- return scope.getTaskName(taskNamePrefix, "Resources");
- }
-
- @Override
- public Class<MergeResources> getType() {
- return MergeResources.class;
- }
-
- @Override
- public void execute(MergeResources mergeResourcesTask) {
- final BaseVariantData<? extends BaseVariantOutputData> variantData =
- scope.getVariantData();
- final AndroidConfig extension = scope.getGlobalScope().getExtension();
-
- mergeResourcesTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- mergeResourcesTask.setVariantName(scope.getVariantConfiguration().getFullName());
- mergeResourcesTask.setIncrementalFolder(new File(
- scope.getGlobalScope().getBuildDir() + "/" + AndroidProject.FD_INTERMEDIATES +
- "/incremental/" + taskNamePrefix + "Resources/" +
- variantData.getVariantConfiguration().getDirName()));
-
- mergeResourcesTask.process9Patch = process9Patch;
- mergeResourcesTask.crunchPng = extension.getAaptOptions()
- .getCruncherEnabled();
- mergeResourcesTask.normalizeResources =
- extension.getBuildToolsRevision()
- .compareTo(NORMALIZE_RESOURCES_BUILD_TOOLS) < 0;
-
- // Only one pre-processor for now. The code will need slight changes when we add more.
- mergeResourcesTask.preprocessor = new VectorDrawableRenderer(
- scope.getGeneratedPngsOutputDir(),
- extension.getPreprocessingOptions().getTypedDensities(),
- mergeResourcesTask.getILogger());
-
- ConventionMappingHelper.map(mergeResourcesTask, "useNewCruncher",
- new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return extension.getAaptOptions()
- .getUseNewCruncher();
- }
- });
-
- ConventionMappingHelper.map(mergeResourcesTask, "inputResourceSets",
- new Callable<List<ResourceSet>>() {
- @Override
- public List<ResourceSet> call() throws Exception {
- List<File> generatedResFolders = Lists.newArrayList(
- scope.getRenderscriptResOutputDir(),
- scope.getGeneratedResOutputDir());
- if (variantData.getExtraGeneratedResFolders() != null) {
- generatedResFolders.addAll(
- variantData.getExtraGeneratedResFolders());
- }
- if (variantData.generateApkDataTask != null &&
- variantData.getVariantConfiguration().getBuildType()
- .isEmbedMicroApp()) {
- generatedResFolders.add(
- variantData.generateApkDataTask.getResOutputDir());
- }
- return variantData.getVariantConfiguration()
- .getResourceSets(generatedResFolders, includeDependencies);
- }
- });
- ConventionMappingHelper.map(mergeResourcesTask, "outputDir", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return outputLocation != null ? outputLocation
- : scope.getDefaultMergeResourcesOutputDir();
- }
- });
-
- variantData.mergeResourcesTask = mergeResourcesTask;
- }
- }
-}
-
-
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy
deleted file mode 100644
index c06a19d..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.dsl.CoreNdkOptions
-import com.android.build.gradle.internal.tasks.NdkTask
-import com.android.ide.common.process.LoggedProcessOutputHandler
-import com.android.ide.common.process.ProcessInfoBuilder
-import com.android.ide.common.process.ProcessOutputHandler
-import com.android.sdklib.IAndroidTarget
-import com.android.utils.FileUtils
-import com.google.common.base.Charsets
-import com.google.common.base.Joiner
-import com.google.common.collect.Lists
-import com.google.common.io.Files
-import org.gradle.api.GradleException
-import org.gradle.api.file.FileTree
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.SkipWhenEmpty
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.incremental.IncrementalTaskInputs
-import org.gradle.api.tasks.util.PatternSet
-
-import static com.android.SdkConstants.CURRENT_PLATFORM
-import static com.android.SdkConstants.PLATFORM_WINDOWS
-
-class NdkCompile extends NdkTask {
-
- public static String USE_DEPRECATED_NDK = "android.useDeprecatedNdk";
-
- List<File> sourceFolders
-
- @OutputFile
- File generatedMakefile
-
- @Input
- boolean debuggable
-
- @OutputDirectory
- File soFolder
-
- @OutputDirectory
- File objFolder
-
- @Optional
- @Input
- File ndkDirectory
-
- @Input
- boolean ndkRenderScriptMode
-
- @Input
- boolean ndkCygwinMode
-
- @Input
- boolean isForTesting
-
- @SkipWhenEmpty
- @InputFiles
- FileTree getSource() {
- FileTree src = null
- List<File> sources = getSourceFolders()
- if (!sources.isEmpty()) {
- src = getProject().files(new ArrayList<Object>(sources)).getAsFileTree()
- }
- return src == null ? getProject().files().getAsFileTree() : src
- }
-
- @TaskAction
- void taskAction(IncrementalTaskInputs inputs) {
- if (!project.hasProperty(USE_DEPRECATED_NDK)) {
- // Normally, we would catch the user when they try to configure the NDK, but NDK do
- // not need to be configured by default. Throw this exception during task execution in
- // case we miss it.
- throw new RuntimeException(
- "Error: NDK integration is deprecated in the current plugin. Consider trying " +
- "the new experimental plugin. For details, see " +
- "http://tools.android.com/tech-docs/new-build-system/gradle-experimental. " +
- "Set \"$USE_DEPRECATED_NDK=true\" in gradle.properties to " +
- "continue using the current NDK integration.");
- }
-
-
- if (isNdkOptionUnset()) {
- logger.warn("Warning: Native C/C++ source code is found, but it seems that NDK " +
- "option is not configured. Note that if you have an Android.mk, it is not " +
- "used for compilation. The recommended workaround is to remove the default " +
- "jni source code directory by adding: \n " +
- "android {\n" +
- " sourceSets {\n" +
- " main {\n" +
- " jni.srcDirs = []\n" +
- " }\n" +
- " }\n" +
- "}\n" +
- "to build.gradle, manually compile the code with ndk-build, " +
- "and then place the resulting shared object in src/main/jniLibs.");
- }
-
- FileTree sourceFileTree = getSource()
- Set<File> sourceFiles = sourceFileTree.matching(new PatternSet().exclude("**/*.h")).files
- File makefile = getGeneratedMakefile()
-
- if (sourceFiles.isEmpty()) {
- makefile.delete()
- FileUtils.emptyFolder(getSoFolder())
- FileUtils.emptyFolder(getObjFolder())
- return
- }
-
- if (ndkDirectory == null || !ndkDirectory.isDirectory()) {
- throw new GradleException(
- "NDK not configured.\n" +
- "Download the NDK from http://developer.android.com/tools/sdk/ndk/." +
- "Then add ndk.dir=path/to/ndk in local.properties.\n" +
- "(On Windows, make sure you escape backslashes, e.g. C:\\\\ndk rather than C:\\ndk)");
- }
-
- boolean generateMakefile = false
-
- if (!inputs.isIncremental()) {
- project.logger.info("Unable do incremental execution: full task run")
- generateMakefile = true
- FileUtils.emptyFolder(getSoFolder())
- FileUtils.emptyFolder(getObjFolder())
- } else {
- // look for added or removed files *only*
-
- //noinspection GroovyAssignabilityCheck
- inputs.outOfDate { change ->
- if (change.isAdded()) {
- generateMakefile = true
- }
- }
-
- //noinspection GroovyAssignabilityCheck
- inputs.removed { change ->
- generateMakefile = true
- }
- }
-
- if (generateMakefile) {
- writeMakefile(sourceFiles, makefile)
- }
-
- // now build
- runNdkBuild(ndkDirectory, makefile)
- }
-
- private void writeMakefile(@NonNull Set<File> sourceFiles, @NonNull File makefile) {
- CoreNdkOptions ndk = getNdkConfig()
-
- StringBuilder sb = new StringBuilder()
-
- sb.append(
- 'LOCAL_PATH := $(call my-dir)\n' +
- 'include \$(CLEAR_VARS)\n\n')
-
- String moduleName = ndk.moduleName != null ? ndk.moduleName : project.name
- if (isForTesting) {
- moduleName = moduleName + "_test"
- }
-
- sb.append('LOCAL_MODULE := ').append(moduleName).append('\n')
-
- if (ndk.cFlags != null) {
- sb.append('LOCAL_CFLAGS := ').append(ndk.cFlags).append('\n')
- }
-
- // To support debugging from Android Studio.
- sb.append("LOCAL_LDFLAGS := -Wl,--build-id\n")
-
- List<String> fullLdlibs = Lists.newArrayList()
- if (ndk.ldLibs != null) {
- fullLdlibs.addAll(ndk.ldLibs)
- }
- if (getNdkRenderScriptMode()) {
- fullLdlibs.add("dl")
- fullLdlibs.add("log")
- fullLdlibs.add("jnigraphics")
- fullLdlibs.add("RScpp_static")
- fullLdlibs.add("cutils")
- }
-
- if (!fullLdlibs.isEmpty()) {
- sb.append('LOCAL_LDLIBS := \\\n')
- for (String lib : fullLdlibs) {
- sb.append('\t-l') .append(lib).append(' \\\n')
- }
- sb.append('\n')
- }
-
- sb.append('LOCAL_SRC_FILES := \\\n')
- for (File sourceFile : sourceFiles) {
- sb.append('\t').append(sourceFile.absolutePath).append(' \\\n')
- }
- sb.append('\n')
-
- for (File sourceFolder : getSourceFolders()) {
- sb.append("LOCAL_C_INCLUDES += ${sourceFolder.absolutePath}\n")
- }
-
- if (getNdkRenderScriptMode()) {
- sb.append('LOCAL_LDFLAGS += -L$(call host-path,$(TARGET_C_INCLUDES)/../lib/rs)\n')
-
- sb.append('LOCAL_C_INCLUDES += $(TARGET_C_INCLUDES)/rs/cpp\n')
- sb.append('LOCAL_C_INCLUDES += $(TARGET_C_INCLUDES)/rs\n')
- sb.append('LOCAL_C_INCLUDES += $(TARGET_OBJS)/$(LOCAL_MODULE)\n')
- }
-
- sb.append(
- '\ninclude \$(BUILD_SHARED_LIBRARY)\n')
-
- Files.write(sb.toString(), makefile, Charsets.UTF_8)
- }
-
- private void runNdkBuild(@NonNull File ndkLocation, @NonNull File makefile) {
- CoreNdkOptions ndk = getNdkConfig()
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder()
-
- String exe = ndkLocation.absolutePath + File.separator + "ndk-build"
- if (CURRENT_PLATFORM == PLATFORM_WINDOWS && !ndkCygwinMode) {
- exe += ".cmd"
- }
- builder.setExecutable(exe)
-
- builder.addArgs(
- "NDK_PROJECT_PATH=null",
- "APP_BUILD_SCRIPT=" + makefile.absolutePath)
-
- // target
- IAndroidTarget target = getBuilder().getTarget()
- if (!target.isPlatform()) {
- target = target.parent
- }
- builder.addArgs("APP_PLATFORM=" + target.hashString())
-
- // temp out
- builder.addArgs("NDK_OUT=" + getObjFolder().absolutePath)
-
- // libs out
- builder.addArgs("NDK_LIBS_OUT=" + getSoFolder().absolutePath)
-
- // debug builds
- if (getDebuggable()) {
- builder.addArgs("NDK_DEBUG=1")
- }
-
- if (ndk.getStl() != null) {
- builder.addArgs("APP_STL=" + ndk.getStl())
- }
-
- Set<String> abiFilters = ndk.abiFilters
- if (abiFilters != null && !abiFilters.isEmpty()) {
- if (abiFilters.size() == 1) {
- builder.addArgs("APP_ABI=" + abiFilters.iterator().next())
- } else {
- Joiner joiner = Joiner.on(',').skipNulls()
- builder.addArgs("APP_ABI=" + joiner.join(abiFilters.iterator()))
- }
- } else {
- builder.addArgs("APP_ABI=all")
- }
-
- if (ndk.getJobs() != null) {
- builder.addArgs("-j" + ndk.getJobs());
- }
-
- ProcessOutputHandler handler = new LoggedProcessOutputHandler(getBuilder().getLogger());
- getBuilder().executeProcess(builder.createProcess(), handler)
- .rethrowFailure().assertNormalExitValue()
- }
-
- private boolean isNdkOptionUnset() {
- // If none of the NDK options are set, then it is likely that NDK is not configured.
- return (getModuleName() == null &&
- getcFlags() == null &&
- getLdLibs() == null &&
- getAbiFilters() == null &&
- getStl() == null);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.java
deleted file mode 100644
index 16ab90c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.java
+++ /dev/null
@@ -1,479 +0,0 @@
-package com.android.build.gradle.tasks;
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.annotations.ApkFile;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dsl.AbiSplitOptions;
-import com.android.build.gradle.internal.dsl.CoreSigningConfig;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantOutputScope;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.tasks.IncrementalTask;
-import com.android.build.gradle.internal.tasks.ValidateSigningTask;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.internal.variant.ApkVariantOutputData;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.builder.packaging.DuplicateFileException;
-import com.android.builder.signing.SignedJarBuilder;
-import com.android.utils.StringHelper;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-
-import org.codehaus.groovy.runtime.StringGroovyMethods;
-import org.gradle.api.Task;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputDirectory;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.tooling.BuildException;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
- at ParallelizableTask
-public class PackageApplication extends IncrementalTask implements FileSupplier {
-
- // ----- PUBLIC TASK API -----
-
- @InputFile
- public File getResourceFile() {
- return resourceFile;
- }
-
- public void setResourceFile(File resourceFile) {
- this.resourceFile = resourceFile;
- }
-
- @InputDirectory
- public File getDexFolder() {
- return dexFolder;
- }
-
- public void setDexFolder(File dexFolder) {
- this.dexFolder = dexFolder;
- }
-
- @InputFiles
- public Collection<File> getDexedLibraries() {
- return dexedLibraries;
- }
-
- public void setDexedLibraries(Collection<File> dexedLibraries) {
- this.dexedLibraries = dexedLibraries;
- }
-
- @InputDirectory
- @Optional
- public File getJavaResourceDir() {
- return javaResourceDir;
- }
-
- public void setJavaResourceDir(File javaResourceDir) {
- this.javaResourceDir = javaResourceDir;
- }
-
- public Set<File> getJniFolders() {
- return jniFolders;
- }
-
- public void setJniFolders(Set<File> jniFolders) {
- this.jniFolders = jniFolders;
- }
-
- public File getMergingFolder() {
- return mergingFolder;
- }
-
- public void setMergingFolder(File mergingFolder) {
- this.mergingFolder = mergingFolder;
- }
-
- @OutputFile
- public File getOutputFile() {
- return outputFile;
- }
-
- public void setOutputFile(File outputFile) {
- this.outputFile = outputFile;
- }
-
- @Input
- @Optional
- public Set<String> getAbiFilters() {
- return abiFilters;
- }
-
- public void setAbiFilters(Set<String> abiFilters) {
- this.abiFilters = abiFilters;
- }
-
- // ----- PRIVATE TASK API -----
-
- private File resourceFile;
-
- private File dexFolder;
-
- private Collection<File> dexedLibraries;
-
- private File javaResourceDir;
-
- private Set<File> jniFolders;
-
- private File mergingFolder;
-
- @ApkFile
- private File outputFile;
-
- private Set<String> abiFilters;
-
- private Set<File> packagedJars;
-
- private boolean jniDebugBuild;
-
- private CoreSigningConfig signingConfig;
-
- private PackagingOptions packagingOptions;
-
- private SignedJarBuilder.IZipEntryFilter packagingOptionsFilter;
-
- @InputFiles
- public Set<File> getPackagedJars() {
- return packagedJars;
- }
-
- public void setPackagedJars(Set<File> packagedJars) {
- this.packagedJars = packagedJars;
- }
-
- @Input
- public boolean getJniDebugBuild() {
- return jniDebugBuild;
- }
-
- public boolean isJniDebugBuild() {
- return jniDebugBuild;
- }
-
- public void setJniDebugBuild(boolean jniDebugBuild) {
- this.jniDebugBuild = jniDebugBuild;
- }
-
- @Nested
- @Optional
- public CoreSigningConfig getSigningConfig() {
- return signingConfig;
- }
-
- public void setSigningConfig(CoreSigningConfig signingConfig) {
- this.signingConfig = signingConfig;
- }
-
- @Nested
- public PackagingOptions getPackagingOptions() {
- return packagingOptions;
- }
-
- public void setPackagingOptions(PackagingOptions packagingOptions) {
- this.packagingOptions = packagingOptions;
- }
-
- public SignedJarBuilder.IZipEntryFilter getPackagingOptionsFilter() {
- return packagingOptionsFilter;
- }
-
- public void setPackagingOptionsFilter(SignedJarBuilder.IZipEntryFilter filter) {
- this.packagingOptionsFilter = filter;
- }
-
- @InputFiles
- public FileTree getNativeLibraries() {
- FileTree src = null;
- Set<File> folders = getJniFolders();
- if (!folders.isEmpty()) {
- src = getProject().files(new ArrayList<Object>(folders)).getAsFileTree();
- }
-
- return src == null ? getProject().files().getAsFileTree() : src;
- }
-
- @Override
- protected void doFullTaskAction() {
- try {
- final File dir = getJavaResourceDir();
- getBuilder().packageApk(getResourceFile().getAbsolutePath(), getDexFolder(),
- getDexedLibraries(), getPackagedJars(),
- (dir == null ? null : dir.getAbsolutePath()), getJniFolders(),
- getMergingFolder(), getAbiFilters(), getJniDebugBuild(), getSigningConfig(),
- getPackagingOptions(),
- getPackagingOptionsFilter(),
- getOutputFile().getAbsolutePath());
- } catch (DuplicateFileException e) {
- Logger logger = getLogger();
- logger.error("Error: duplicate files during packaging of APK " + getOutputFile()
- .getAbsolutePath());
- logger.error("\tPath in archive: " + e.getArchivePath());
- logger.error("\tOrigin 1: " + e.getFile1());
- logger.error("\tOrigin 2: " + e.getFile2());
- logger.error("You can ignore those files in your build.gradle:");
- logger.error("\tandroid {");
- logger.error("\t packagingOptions {");
- logger.error("\t exclude \'" + e.getArchivePath() + "\'");
- logger.error("\t }");
- logger.error("\t}");
- throw new BuildException(e.getMessage(), e);
- } catch (Exception e) {
- throw new BuildException(e.getMessage(), e);
- }
-
- }
-
- // ----- FileSupplierTask -----
-
- @Override
- public File get() {
- return getOutputFile();
- }
-
- @NonNull
- @Override
- public Task getTask() {
- return this;
- }
-
- // ----- ConfigAction -----
-
- public static class ConfigAction implements TaskConfigAction<PackageApplication> {
-
- private VariantOutputScope scope;
-
- public ConfigAction(VariantOutputScope scope) {
- this.scope = scope;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("package");
- }
-
- @Override
- public Class<PackageApplication> getType() {
- return PackageApplication.class;
- }
-
- @Override
- public void execute(PackageApplication packageApp) {
- final ApkVariantData variantData = (ApkVariantData) scope.getVariantScope().getVariantData();
- final ApkVariantOutputData variantOutputData = (ApkVariantOutputData) scope
- .getVariantOutputData();
- final GradleVariantConfiguration config = scope.getVariantScope().getVariantConfiguration();
-
- variantOutputData.packageApplicationTask = packageApp;
- packageApp.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- packageApp.setVariantName(
- scope.getVariantScope().getVariantConfiguration().getFullName());
-
- if (config.isMinifyEnabled() && config.getBuildType().isShrinkResources() && !config
- .getUseJack()) {
- ConventionMappingHelper.map(packageApp, "resourceFile", new Callable<File>() {
- @Override
- public File call() {
- return scope.getCompressedResourceFile();
- }
- });
- } else {
- ConventionMappingHelper.map(packageApp, "resourceFile", new Callable<File>() {
- @Override
- public File call() {
- return variantOutputData.processResourcesTask.getPackageOutputFile();
- }
- });
- }
-
- ConventionMappingHelper.map(packageApp, "dexFolder", new Callable<File>() {
- @Override
- public File call() {
- return scope.getVariantScope().getDexOutputFolder();
- }
- });
- ConventionMappingHelper.map(packageApp, "dexedLibraries",
- new Callable<Collection<File>>() {
- @Override
- public Collection<File> call() {
- if (config.isMultiDexEnabled() && !config.isLegacyMultiDexMode()
- && variantData.preDexTask != null) {
- return scope.getGlobalScope().getProject()
- .fileTree(variantData.preDexTask.getOutputFolder())
- .getFiles();
- }
-
- return Collections.emptyList();
- }
- });
- scope.getVariantScope().getJavaResourcesProvider();
- ConventionMappingHelper.map(packageApp, "packagedJars", new Callable<Set<File>>() {
- @Override
- public Set<File> call() {
- ImmutableSet.Builder<File> jarFiles = ImmutableSet.builder();
- for (JavaResourcesProvider.JavaResourcesLocation javaResourcesProvider :
- scope.getVariantScope().getJavaResourcesProvider().getJavaResourcesLocations()) {
- if (javaResourcesProvider.type == JavaResourcesProvider.Type.JAR) {
- jarFiles.add(javaResourcesProvider.location);
- }
- }
- return jarFiles.build();
- }
- });
- ConventionMappingHelper.map(packageApp, "javaResourceDir", new Callable<File>() {
- @Override
- public File call() {
- ImmutableSet.Builder<File> folders = ImmutableSet.builder();
- for (JavaResourcesProvider.JavaResourcesLocation javaResourcesProvider :
- scope.getVariantScope().getJavaResourcesProvider()
- .getJavaResourcesLocations()) {
- if (javaResourcesProvider.type == JavaResourcesProvider.Type.FOLDER) {
- folders.add(javaResourcesProvider.location);
- }
- }
- ImmutableSet<File> resourceFolders = folders.build();
- if (resourceFolders.size() > 1) {
- throw new RuntimeException("More than one java resources folders : " +
- Joiner.on(",").join(resourceFolders));
- }
- return resourceFolders.isEmpty() ? null : resourceFolders.iterator().next();
- }
- });
-
- packageApp.setMergingFolder(new File(scope.getGlobalScope().getIntermediatesDir(),
- variantOutputData.getFullName() + "/merging"));
-
-
- ConventionMappingHelper.map(packageApp, "jniFolders", new Callable<Set<File>>() {
- @Override
- public Set<File> call() {
-
- if (variantData.getSplitHandlingPolicy() ==
- BaseVariantData.SplitHandlingPolicy.PRE_21_POLICY) {
- return scope.getVariantScope().getJniFolders();
- }
- Set<String> filters = AbiSplitOptions.getAbiFilters(
- scope.getGlobalScope().getExtension().getSplits().getAbiFilters());
- return filters.isEmpty() ? scope.getVariantScope().getJniFolders() : Collections.<File>emptySet();
-
- }
- });
-
- ConventionMappingHelper.map(packageApp, "abiFilters", new Callable<Set<String>>() {
- @Override
- public Set<String> call() throws Exception {
- if (variantOutputData.getMainOutputFile().getFilter(com.android.build.OutputFile.ABI) != null) {
- return ImmutableSet.of(
- variantOutputData.getMainOutputFile()
- .getFilter(com.android.build.OutputFile.ABI));
- }
- return config.getSupportedAbis();
- }
- });
- ConventionMappingHelper.map(packageApp, "jniDebugBuild", new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return config.getBuildType().isJniDebuggable();
- }
- });
-
- CoreSigningConfig sc = (CoreSigningConfig) config.getSigningConfig();
- packageApp.setSigningConfig(sc);
- if (sc != null) {
- String validateSigningTaskName = "validate" + StringHelper.capitalize(sc.getName()) + "Signing";
- ValidateSigningTask validateSigningTask =
- (ValidateSigningTask) scope.getGlobalScope().getProject().getTasks().findByName(validateSigningTaskName);
- if (validateSigningTask == null) {
- validateSigningTask =
- scope.getGlobalScope().getProject().getTasks().create(
- "validate" + StringHelper.capitalize(sc.getName()) + "Signing",
- ValidateSigningTask.class);
- validateSigningTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- validateSigningTask.setVariantName(
- scope.getVariantScope().getVariantConfiguration().getFullName());
- validateSigningTask.setSigningConfig(sc);
- }
-
- packageApp.dependsOn(validateSigningTask);
- }
-
- ConventionMappingHelper.map(packageApp, "packagingOptions", new Callable<PackagingOptions>() {
- @Override
- public PackagingOptions call() throws Exception {
- return scope.getGlobalScope().getExtension().getPackagingOptions();
- }
- });
-
- ConventionMappingHelper.map(packageApp, "packagingOptionsFilter",
- new Callable<SignedJarBuilder.IZipEntryFilter>() {
- @Override
- public SignedJarBuilder.IZipEntryFilter call() throws Exception {
- return scope.getVariantScope().getPackagingOptionsFilter();
- }
- });
-
- ConventionMappingHelper.map(packageApp, "outputFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getPackageApk();
- }
- });
- }
-
- private ShrinkResources createShrinkResourcesTask(
- final ApkVariantOutputData variantOutputData) {
- BaseVariantData<?> variantData = (BaseVariantData<?>) variantOutputData.variantData;
- ShrinkResources task = scope.getGlobalScope().getProject().getTasks()
- .create("shrink" + StringGroovyMethods
- .capitalize(variantOutputData.getFullName())
- + "Resources", ShrinkResources.class);
- task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- task.setVariantName(scope.getVariantScope().getVariantConfiguration().getFullName());
- task.variantOutputData = variantOutputData;
-
- final String outputBaseName = variantOutputData.getBaseName();
- task.setCompressedResources(new File(
- scope.getGlobalScope().getBuildDir() + "/" + FD_INTERMEDIATES + "/res/" +
- "resources-" + outputBaseName + "-stripped.ap_"));
-
- ConventionMappingHelper.map(task, "uncompressedResources", new Callable<File>() {
- @Override
- public File call() {
- return variantOutputData.processResourcesTask.getPackageOutputFile();
- }
- });
-
- task.dependsOn(
- scope.getVariantScope().getObfuscationTask().getName(),
- scope.getManifestProcessorTask().getName(),
- variantOutputData.processResourcesTask);
-
- return task;
- }
-
- private static File getOptionalDir(File dir) {
- if (dir.isDirectory()) {
- return dir;
- }
-
- return null;
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitAbi.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitAbi.java
deleted file mode 100644
index 17c92f7..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitAbi.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.api.ApkOutputFile;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.model.FilterDataImpl;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.packaging.DuplicateFileException;
-import com.android.builder.packaging.PackagerException;
-import com.android.builder.packaging.SigningException;
-import com.android.builder.signing.SignedJarBuilder;
-import com.android.ide.common.signing.KeytoolException;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Callables;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFiles;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Package a abi dimension specific split APK
- */
- at ParallelizableTask
-public class PackageSplitAbi extends SplitRelatedTask {
-
- private ImmutableList<ApkOutputFile> outputFiles;
-
- private Collection<File> inputFiles;
-
- private File outputDirectory;
-
- private Set<String> splits;
-
- private String outputBaseName;
-
- private boolean jniDebuggable;
-
- private SigningConfig signingConfig;
-
- private PackagingOptions packagingOptions;
-
- private Collection<File> jniFolders;
-
- private File mergingFolder;
-
- private SignedJarBuilder.IZipEntryFilter packagingOptionsFilter;
-
- @OutputFiles
- public List<File> getOutputFiles() {
- ImmutableList.Builder<File> builder = ImmutableList.builder();
- for (ApkOutputFile apk : getOutputSplitFiles()) {
- builder.add(apk.getOutputFile());
- }
- return builder.build();
- }
-
- @Override
- @Nullable
- public File getApkMetadataFile() {
- return null;
- }
-
- @Override
- @NonNull
- public synchronized ImmutableList<ApkOutputFile> getOutputSplitFiles() {
-
- if (outputFiles == null) {
- ImmutableList.Builder<ApkOutputFile> builder = ImmutableList.builder();
- for (String split : splits) {
- String apkName = getApkName(split);
- ApkOutputFile apkOutput = new ApkOutputFile(
- OutputFile.OutputType.SPLIT,
- ImmutableList.of(FilterDataImpl.build(OutputFile.ABI, apkName)),
- Callables.returning(new File(outputDirectory, apkName)));
- builder.add(apkOutput);
- }
-
- outputFiles = builder.build();
- }
- return outputFiles;
- }
-
- private boolean isAbiSplit(String fileName) {
- for (String abi : getSplits()) {
- if (fileName.contains(abi)) {
- return true;
- }
- }
- return false;
- }
-
- @TaskAction
- protected void doFullTaskAction() throws FileNotFoundException, SigningException,
- KeytoolException, DuplicateFileException, PackagerException {
-
- // resources- and .ap_ should be shared in a setting somewhere. see BasePlugin:1206
- final Pattern pattern = Pattern.compile("resources-" + getOutputBaseName() + "-(.*).ap_");
- List<String> unprocessedSplits = Lists.newArrayList(splits);
- for (File file : inputFiles) {
- Matcher matcher = pattern.matcher(file.getName());
- if (matcher.matches() && isAbiSplit(file.getName())) {
- String apkName = getApkName(matcher.group(1));
-
- File outFile = new File(getOutputDirectory(), apkName);
- getBuilder().packageApk(
- file.getAbsolutePath(),
- null, /* dexFolder */
- ImmutableList.<File>of(), /* dexedLibraries */
- ImmutableList.<File>of(),
- null, /* getJavaResourceDir */
- getJniFolders(),
- getMergingFolder(),
- ImmutableSet.of(matcher.group(1)),
- isJniDebuggable(),
- getSigningConfig(),
- getPackagingOptions(),
- getPackagingOptionsFilter(),
- outFile.getAbsolutePath());
- unprocessedSplits.remove(matcher.group(1));
- }
- }
- if (!unprocessedSplits.isEmpty()) {
- String message = "Could not find resource package for "
- + Joiner.on(',').join(unprocessedSplits);
- getLogger().error(message);
- throw new IllegalStateException(message);
- }
- }
-
- @Override
- public List<FilterData> getSplitsData() {
- ImmutableList.Builder<FilterData> filterDataBuilder = ImmutableList.builder();
- SplitRelatedTask.addAllFilterData(filterDataBuilder, splits, OutputFile.FilterType.ABI);
- return filterDataBuilder.build();
- }
-
- private String getApkName(final String split) {
- String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
- String apkName = archivesBaseName + "-" + getOutputBaseName() + "_" + split;
- return apkName + (getSigningConfig() == null ? "-unsigned.apk" : "-unaligned.apk");
- }
-
- public void setOutputFiles(ImmutableList<ApkOutputFile> outputFiles) {
- this.outputFiles = outputFiles;
- }
-
- @InputFiles
- public Collection<File> getInputFiles() {
- return inputFiles;
- }
-
- public void setInputFiles(Collection<File> inputFiles) {
- this.inputFiles = inputFiles;
- }
-
- public File getOutputDirectory() {
- return outputDirectory;
- }
-
- public void setOutputDirectory(File outputDirectory) {
- this.outputDirectory = outputDirectory;
- }
-
- @Input
- public Set<String> getSplits() {
- return splits;
- }
-
- public void setSplits(Set<String> splits) {
- this.splits = splits;
- }
-
- @Input
- public String getOutputBaseName() {
- return outputBaseName;
- }
-
- public void setOutputBaseName(String outputBaseName) {
- this.outputBaseName = outputBaseName;
- }
-
- @Input
- public boolean isJniDebuggable() {
- return jniDebuggable;
- }
-
- public void setJniDebuggable(boolean jniDebuggable) {
- this.jniDebuggable = jniDebuggable;
- }
-
- @Nested
- @Optional
- public SigningConfig getSigningConfig() {
- return signingConfig;
- }
-
- public void setSigningConfig(SigningConfig signingConfig) {
- this.signingConfig = signingConfig;
- }
-
- @Nested
- public PackagingOptions getPackagingOptions() {
- return packagingOptions;
- }
-
- public void setPackagingOptions(PackagingOptions packagingOptions) {
- this.packagingOptions = packagingOptions;
- }
-
- @Input
- public Collection<File> getJniFolders() {
- return jniFolders;
- }
-
- public void setJniFolders(Collection<File> jniFolders) {
- this.jniFolders = jniFolders;
- }
-
- public File getMergingFolder() {
- return mergingFolder;
- }
-
- public void setMergingFolder(File mergingFolder) {
- this.mergingFolder = mergingFolder;
- }
-
- public SignedJarBuilder.IZipEntryFilter getPackagingOptionsFilter() {
- return packagingOptionsFilter;
- }
-
- public void setPackagingOptionsFilter(SignedJarBuilder.IZipEntryFilter packagingOptionsFilter) {
- this.packagingOptionsFilter = packagingOptionsFilter;
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitRes.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitRes.java
deleted file mode 100644
index 7f8bcfb..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitRes.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.build.gradle.api.ApkOutputFile;
-import com.android.build.gradle.internal.model.FilterDataImpl;
-import com.android.builder.model.SigningConfig;
-import com.android.builder.packaging.SigningException;
-import com.android.builder.signing.SignedJarBuilder;
-import com.android.ide.common.signing.KeytoolException;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Callables;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFiles;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Package each split resources into a specific signed apk file.
- */
- at ParallelizableTask
-public class PackageSplitRes extends SplitRelatedTask {
-
- private Set<String> densitySplits;
-
- private Set<String> languageSplits;
-
- private String outputBaseName;
-
- private SigningConfig signingConfig;
-
- /**
- * This directories are not officially input/output to the task as they are shared among tasks.
- * To be parallelizable, we must only define our I/O in terms of files...
- */
- private File inputDirectory;
-
- private File outputDirectory;
-
- @InputFiles
- public List<File> getInputFiles() {
- final ImmutableList.Builder<File> builder = ImmutableList.builder();
- forEachInputFile(new SplitFileHandler() {
- @Override
- public void execute(String split, File file) {
- builder.add(file);
- }
- });
- return builder.build();
- }
-
- @OutputFiles
- public List<File> getOutputFiles() {
- ImmutableList.Builder<File> builder = ImmutableList.builder();
- for (ApkOutputFile apk : getOutputSplitFiles()) {
- builder.add(apk.getOutputFile());
- }
- return builder.build();
- }
-
- @Override
- public File getApkMetadataFile() {
- return null;
- }
-
- /**
- * Calculates the list of output files, coming from the list of input files, mangling the output
- * file name.
- */
- @Override
- public List<ApkOutputFile> getOutputSplitFiles() {
- final ImmutableList.Builder<ApkOutputFile> builder = ImmutableList.builder();
- forEachInputFile(new SplitFileHandler() {
- @Override
- public void execute(String split, File file) {
- // find the split identification, if null, the split is not requested any longer.
- FilterData filterData = null;
- for (String density : densitySplits) {
- if (split.startsWith(density)) {
- filterData = FilterDataImpl.build(
- OutputFile.FilterType.DENSITY.toString(), density);
- }
-
- }
-
- if (languageSplits.contains(unMangleSplitName(split))) {
- filterData = FilterDataImpl.build(
- OutputFile.FilterType.LANGUAGE.toString(), unMangleSplitName(split));
- }
- if (filterData != null) {
- builder.add(new ApkOutputFile(
- OutputFile.OutputType.SPLIT,
- ImmutableList.of(filterData),
- Callables.returning(
- new File(outputDirectory, getOutputFileNameForSplit(split)))));
- }
-
- }
- });
- return builder.build();
- }
-
- @TaskAction
- protected void doFullTaskAction() {
- forEachInputFile(
- new SplitFileHandler() {
- @Override
- public void execute(String split, File file) {
- File outFile = new File(outputDirectory,
- getOutputFileNameForSplit(split));
- try {
- getBuilder().signApk(file, signingConfig, outFile);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } catch (KeytoolException e) {
- throw new RuntimeException(e);
- } catch (SigningException e) {
- throw new RuntimeException(e);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- } catch (SignedJarBuilder.IZipEntryFilter.ZipAbortException e) {
- throw new RuntimeException(e);
- } catch (com.android.builder.signing.SigningException e) {
- throw new RuntimeException(e);
- }
- }
- });
- }
-
- private interface SplitFileHandler {
- void execute(String split, File file);
- }
-
- /**
- * Runs the handler for each task input file, providing the split identifier (possibly with a
- * suffix generated by aapt) and the input file handle.
- */
- private void forEachInputFile(SplitFileHandler handler) {
- Pattern resourcePattern = Pattern.compile("resources-" + outputBaseName + ".ap__(.*)");
-
- // make a copy of the expected densities and languages filters.
- List<String> densitiesCopy = Lists.newArrayList(densitySplits);
- List<String> languagesCopy = Lists.newArrayList(languageSplits);
-
- // resources- and .ap_ should be shared in a setting somewhere. see BasePlugin:1206
- File[] fileLists = inputDirectory.listFiles();
- if (fileLists != null) {
- for (File file : fileLists) {
- Matcher match = resourcePattern.matcher(file.getName());
- // each time we match, we remove the associated filter from our copies.
- if (match.matches() && !match.group(1).isEmpty()
- && isValidSplit(densitiesCopy, languagesCopy, match.group(1))) {
- handler.execute(match.group(1), file);
- }
- }
- }
- // manually invoke the handler for filters we did not find associated files, apply best
- // guess on the actual file names.
- for (String density : densitiesCopy) {
- handler.execute(density,
- new File(inputDirectory, "resources-" + outputBaseName + ".ap__" + density));
- }
- for (String language : languagesCopy) {
- handler.execute(language,
- new File(inputDirectory, "resources-" + outputBaseName + ".ap__" + language));
-
- }
- }
-
- /**
- * Returns true if the passed split identifier is a valid identifier (valid mean it is a
- * requested split for this task). A density split identifier can be suffixed with characters
- * added by aapt.
- */
- private static boolean isValidSplit(
- List<String> densities,
- List<String> languages,
- @NonNull String splitWithOptionalSuffix) {
- for (String density : densities) {
- if (splitWithOptionalSuffix.startsWith(density)) {
- densities.remove(density);
- return true;
- }
- }
- String mangledName = unMangleSplitName(splitWithOptionalSuffix);
- if (languages.contains(mangledName)) {
- languages.remove(mangledName);
- return true;
- }
- return false;
- }
-
- public String getOutputFileNameForSplit(final String split) {
- String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
- String apkName = archivesBaseName + "-" + outputBaseName + "_" + split;
- return apkName + (signingConfig == null ? "-unsigned.apk" : "-unaligned.apk");
- }
-
- @Override
- public List<FilterData> getSplitsData() {
- ImmutableList.Builder<FilterData> filterDataBuilder = ImmutableList.builder();
- addAllFilterData(filterDataBuilder, densitySplits, OutputFile.FilterType.DENSITY);
- addAllFilterData(filterDataBuilder, languageSplits, OutputFile.FilterType.LANGUAGE);
- return filterDataBuilder.build();
- }
-
- /**
- * Un-mangle a split name as created by the aapt tool to retrieve a split name as configured in
- * the project's build.gradle.
- *
- * when dealing with several split language in a single split, each language (+ optional region)
- * will be seperated by an underscore.
- *
- * note that there is currently an aapt bug, remove the 'r' in the region so for instance,
- * fr-rCA becomes fr-CA, temporarily put it back until it is fixed.
- *
- * @param splitWithOptionalSuffix the mangled split name.
- */
- public static String unMangleSplitName(String splitWithOptionalSuffix) {
- String mangledName = splitWithOptionalSuffix.replaceAll("_", ",");
- return mangledName.contains("-r") ? mangledName : mangledName.replace("-", "-r");
- }
-
- @Input
- public Set<String> getDensitySplits() {
- return densitySplits;
- }
-
- public void setDensitySplits(Set<String> densitySplits) {
- this.densitySplits = densitySplits;
- }
-
- @Input
- public Set<String> getLanguageSplits() {
- return languageSplits;
- }
-
- public void setLanguageSplits(Set<String> languageSplits) {
- this.languageSplits = languageSplits;
- }
-
- @Input
- public String getOutputBaseName() {
- return outputBaseName;
- }
-
- public void setOutputBaseName(String outputBaseName) {
- this.outputBaseName = outputBaseName;
- }
-
- @Nested
- @Optional
- public SigningConfig getSigningConfig() {
- return signingConfig;
- }
-
- public void setSigningConfig(SigningConfig signingConfig) {
- this.signingConfig = signingConfig;
- }
-
- public File getInputDirectory() {
- return inputDirectory;
- }
-
- public void setInputDirectory(File inputDirectory) {
- this.inputDirectory = inputDirectory;
- }
-
- public File getOutputDirectory() {
- return outputDirectory;
- }
-
- public void setOutputDirectory(File outputDirectory) {
- this.outputDirectory = outputDirectory;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PreCompilationVerificationTask.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PreCompilationVerificationTask.groovy
deleted file mode 100644
index 96d9494..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PreCompilationVerificationTask.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.build.gradle.internal.tasks.DefaultAndroidTask
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.TaskAction
-
-/**
- * Runs some sanity checks that our tool chain configuration is correct.
- */
-class PreCompilationVerificationTask extends DefaultAndroidTask {
-
- @Input
- boolean useJack
-
- @Input
- Object[] testSourceFiles
-
- @TaskAction
- void verify() {
-
- // we are asked to set up a compilation task against unit tests, we should make sure
- // the tested variant was not built using Jack as the .class files are not available
- // in that case.
- if (useJack && !getProject().files(testSourceFiles).files.isEmpty()) {
- throw new RuntimeException("Unit tests are not yet supported when Jack is used to " +
- "compile the variant")
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PreDex.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PreDex.java
deleted file mode 100644
index 24b7d25..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PreDex.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks;
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.tasks.BaseTask;
-import com.android.build.gradle.internal.variant.ApkVariantData;
-import com.android.build.gradle.internal.variant.TestVariantData;
-import com.android.build.gradle.internal.PostCompilationData;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.DexOptions;
-import com.android.builder.core.VariantConfiguration;
-import com.android.builder.core.VariantType;
-import com.android.ide.common.internal.LoggedErrorException;
-import com.android.ide.common.internal.WaitableExecutor;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.utils.FileUtils;
-import com.google.common.base.Charsets;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.hash.HashCode;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hashing;
-import com.google.common.io.Files;
-
-import org.gradle.api.Action;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
-import org.gradle.api.tasks.incremental.InputFileDetails;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
- at ParallelizableTask
-public class PreDex extends BaseTask {
-
- @Input
- public String getBuildToolsVersion() {
- return getBuildTools().getRevision().toString();
- }
-
- private Collection<File> inputFiles;
-
- private File outputFolder;
-
- private com.android.build.gradle.internal.dsl.DexOptions dexOptions;
-
- private boolean multiDex;
-
- @TaskAction
- void taskAction(IncrementalTaskInputs taskInputs)
- throws IOException, LoggedErrorException, InterruptedException {
-
- final boolean multiDexEnabled = isMultiDex();
-
- final File outFolder = getOutputFolder();
-
- boolean incremental = taskInputs.isIncremental();
- // if we are not in incremental mode, then outOfDate will contain
- // all the files, but first we need to delete the previous output
- if (!incremental) {
- FileUtils.emptyFolder(outFolder);
- }
-
- final Set<String> hashs = Sets.newHashSet();
- final WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
- final List<File> inputFileDetails = Lists.newArrayList();
-
- taskInputs.outOfDate(new Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails change) {
- inputFileDetails.add(change.getFile());
- }
- });
-
- ProcessOutputHandler outputHandler = new LoggedProcessOutputHandler(getILogger());
- for (final File file : inputFileDetails) {
- Callable<Void> action = new PreDexTask(outFolder, file, hashs,
- multiDexEnabled, outputHandler);
- executor.execute(action);
- }
-
- if (incremental) {
- taskInputs.removed(new Action<InputFileDetails>() {
- @Override
- public void execute(InputFileDetails change) {
- File preDexedFile = getDexFileName(outFolder, change.getFile());
-
- try {
- FileUtils.deleteFolder(preDexedFile);
- } catch (IOException e) {
- getLogger().info("Could not delete {}\n{}",
- preDexedFile, Throwables.getStackTraceAsString(e));
- }
- }
- });
- }
-
- executor.waitForTasksWithQuickFail(false);
- }
-
- private final class PreDexTask implements Callable<Void> {
- private final File outFolder;
- private final File fileToProcess;
- private final Set<String> hashs;
- private final boolean multiDexEnabled;
- private final DexOptions options = getDexOptions();
- private final AndroidBuilder builder = getBuilder();
- private final ProcessOutputHandler mOutputHandler;
-
-
- private PreDexTask(
- File outFolder,
- File file,
- Set<String> hashs,
- boolean multiDexEnabled,
- ProcessOutputHandler outputHandler) {
- this.mOutputHandler = outputHandler;
- this.outFolder = outFolder;
- this.fileToProcess = file;
- this.hashs = hashs;
- this.multiDexEnabled = multiDexEnabled;
- }
-
- @Override
- public Void call() throws Exception {
- // TODO remove once we can properly add a library as a dependency of its test.
- String hash = getFileHash(fileToProcess);
-
- synchronized (hashs) {
- if (hashs.contains(hash)) {
- return null;
- }
-
- hashs.add(hash);
- }
-
- File preDexedFile = getDexFileName(outFolder, fileToProcess);
-
- if (multiDexEnabled) {
- preDexedFile.mkdirs();
- }
-
- builder.preDexLibrary(
- fileToProcess, preDexedFile, multiDexEnabled, options, mOutputHandler);
-
- return null;
- }
- }
-
- // this is used automatically by Gradle, even though nothing
- // in the class uses it.
- @SuppressWarnings("unused")
- @InputFiles
- public Collection<File> getInputFiles() {
- return inputFiles;
- }
-
- public void setInputFiles(Collection<File> inputFiles) {
- this.inputFiles = inputFiles;
- }
-
- @OutputDirectory
- public File getOutputFolder() {
- return outputFolder;
- }
-
- public void setOutputFolder(File outputFolder) {
- this.outputFolder = outputFolder;
- }
-
- @Nested
- public com.android.build.gradle.internal.dsl.DexOptions getDexOptions() {
- return dexOptions;
- }
-
- public void setDexOptions(com.android.build.gradle.internal.dsl.DexOptions dexOptions) {
- this.dexOptions = dexOptions;
- }
-
- @Input
- public boolean isMultiDex() {
- return multiDex;
- }
-
- public void setMultiDex(boolean multiDex) {
- this.multiDex = multiDex;
- }
-
- /**
- * Returns the hash of a file.
- * @param file the file to hash
- */
- private static String getFileHash(@NonNull File file) throws IOException {
- HashCode hashCode = Files.hash(file, Hashing.sha1());
- return hashCode.toString();
- }
-
- /**
- * Returns a unique File for the pre-dexed library, even
- * if there are 2 libraries with the same file names (but different
- * paths)
- *
- * If multidex is enabled the return File is actually a folder.
- *
- * @param outFolder the output folder.
- * @param inputFile the library.
- */
- @NonNull
- static File getDexFileName(@NonNull File outFolder, @NonNull File inputFile) {
- // get the filename
- String name = inputFile.getName();
- // remove the extension
- int pos = name.lastIndexOf('.');
- if (pos != -1) {
- name = name.substring(0, pos);
- }
-
- // add a hash of the original file path.
- String input = inputFile.getAbsolutePath();
- HashFunction hashFunction = Hashing.sha1();
- HashCode hashCode = hashFunction.hashString(input, Charsets.UTF_16LE);
-
- return new File(outFolder, name + "-" + hashCode.toString() + SdkConstants.DOT_JAR);
- }
-
- public static class ConfigAction implements TaskConfigAction<PreDex> {
-
- private VariantScope scope;
-
- private Callable<List<File>> inputLibraries;
-
- public ConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope;
- this.inputLibraries = pcData.getInputLibrariesCallable();
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("preDex");
- }
-
- @Override
- public Class<PreDex> getType() {
- return PreDex.class;
- }
-
- @Override
- public void execute(PreDex preDexTask) {
- ApkVariantData variantData = (ApkVariantData) scope.getVariantData();
- VariantConfiguration config = variantData.getVariantConfiguration();
-
- boolean isTestForApp = config.getType().isForTesting() &&
- ((TestVariantData) variantData).getTestedVariantData()
- .getVariantConfiguration().getType() == VariantType.DEFAULT;
- boolean isMultiDexEnabled = config.isMultiDexEnabled() && !isTestForApp;
-
- variantData.preDexTask = preDexTask;
- preDexTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- preDexTask.setVariantName(config.getFullName());
- preDexTask.dexOptions = scope.getGlobalScope().getExtension().getDexOptions();
- preDexTask.multiDex = isMultiDexEnabled;
-
- ConventionMappingHelper.map(preDexTask, "inputFiles", inputLibraries);
- ConventionMappingHelper.map(preDexTask, "outputFolder", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getPreDexOutputDir();
- }
- });
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.java
deleted file mode 100644
index 638286c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.java
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.LoggingUtil;
-import com.android.build.gradle.internal.core.GradleVariantConfiguration;
-import com.android.build.gradle.internal.dependency.SymbolFileProviderImpl;
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantOutputScope;
-import com.android.build.gradle.internal.tasks.IncrementalTask;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.builder.core.AaptPackageProcessBuilder;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.VariantType;
-import com.android.builder.dependency.LibraryDependency;
-import com.android.ide.common.blame.ParsingProcessOutputHandler;
-import com.android.ide.common.blame.parser.ToolOutputParser;
-import com.android.ide.common.blame.parser.aapt.AaptOutputParser;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessOutputHandler;
-import com.android.utils.FileUtils;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.logging.Logging;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputDirectory;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.ParallelizableTask;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
- at ParallelizableTask
-public class ProcessAndroidResources extends IncrementalTask {
-
- private File manifestFile;
-
- private File resDir;
-
- private File assetsDir;
-
- private File sourceOutputDir;
-
- private File textSymbolOutputDir;
-
- private File packageOutputFile;
-
- private File proguardOutputFile;
-
- private Collection<String> resourceConfigs;
-
- private String preferredDensity;
-
- private List<SymbolFileProviderImpl> libraries;
-
- private String packageForR;
-
- private Collection<String> splits;
-
- private boolean enforceUniquePackageName;
-
- private VariantType type;
-
- private boolean debuggable;
-
- private boolean pseudoLocalesEnabled;
-
- private AaptOptions aaptOptions;
-
-
- @Override
- protected void doFullTaskAction() throws IOException {
- // we have to clean the source folder output in case the package name changed.
- File srcOut = getSourceOutputDir();
- if (srcOut != null) {
- FileUtils.emptyFolder(srcOut);
- }
-
- File resOutBaseNameFile = getPackageOutputFile();
-
- // we have to check the resource output folder in case some splits were removed, we should
- // manually remove them.
- File packageOutputFolder = getResDir();
- if (resOutBaseNameFile != null) {
- for (File file : packageOutputFolder.listFiles()) {
- if (!isSplitPackage(file, resOutBaseNameFile)) {
- //noinspection ResultOfMethodCallIgnored
- file.delete();
- }
- }
- }
-
- AaptPackageProcessBuilder aaptPackageCommandBuilder =
- new AaptPackageProcessBuilder(getManifestFile(), getAaptOptions())
- .setAssetsFolder(getAssetsDir())
- .setResFolder(getResDir())
- .setLibraries(getLibraries())
- .setPackageForR(getPackageForR())
- .setSourceOutputDir(absolutePath(srcOut))
- .setSymbolOutputDir(absolutePath(getTextSymbolOutputDir()))
- .setResPackageOutput(absolutePath(resOutBaseNameFile))
- .setProguardOutput(absolutePath(getProguardOutputFile()))
- .setType(getType())
- .setDebuggable(getDebuggable())
- .setPseudoLocalesEnabled(getPseudoLocalesEnabled())
- .setResourceConfigs(getResourceConfigs())
- .setSplits(getSplits())
- .setPreferredDensity(getPreferredDensity());
-
- @NonNull
- AndroidBuilder builder = getBuilder();
- ProcessOutputHandler processOutputHandler = new ParsingProcessOutputHandler(
- new ToolOutputParser(new AaptOutputParser(), getILogger()),
- builder.getErrorReporter());
- try {
- builder.processResources(
- aaptPackageCommandBuilder,
- getEnforceUniquePackageName(),
- processOutputHandler);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (ProcessException e) {
- throw new RuntimeException(e);
- }
- }
-
- private boolean isSplitPackage(File file, File resBaseName) {
- if (file.getName().startsWith(resBaseName.getName())) {
- for (String split : splits) {
- if (file.getName().contains(split)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Nullable
- private static String absolutePath(@Nullable File file) {
- return file == null ? null : file.getAbsolutePath();
- }
-
- public static class ConfigAction implements TaskConfigAction<ProcessAndroidResources> {
-
- private VariantOutputScope scope;
- private File symbolLocation;
- private boolean generateResourcePackage;
-
- public ConfigAction(
- VariantOutputScope scope, File symbolLocation, boolean generateResourcePackage) {
- this.scope = scope;
- this.symbolLocation = symbolLocation;
- this.generateResourcePackage = generateResourcePackage;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("process", "Resources");
- }
-
- @Override
- public Class<ProcessAndroidResources> getType() {
- return ProcessAndroidResources.class;
- }
-
- @Override
- public void execute(ProcessAndroidResources processResources) {
- final BaseVariantOutputData variantOutputData = scope.getVariantOutputData();
- final BaseVariantData<? extends BaseVariantOutputData> variantData =
- scope.getVariantScope().getVariantData();
- variantOutputData.processResourcesTask = processResources;
- final GradleVariantConfiguration config = variantData.getVariantConfiguration();
-
- processResources.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- processResources.setVariantName(config.getFullName());
-
- if (variantData.getSplitHandlingPolicy() ==
- BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY) {
- Set<String> allFilters = new HashSet<String>();
- allFilters.addAll(
- variantData.getFilters(com.android.build.OutputFile.FilterType.DENSITY));
- allFilters.addAll(
- variantData.getFilters(com.android.build.OutputFile.FilterType.LANGUAGE));
- processResources.splits = allFilters;
- }
-
- // only generate code if the density filter is null, and if we haven't generated
- // it yet (if you have abi + density splits, then several abi output will have no
- // densityFilter)
- if (variantOutputData.getMainOutputFile()
- .getFilter(com.android.build.OutputFile.DENSITY) == null
- && variantData.generateRClassTask == null) {
- variantData.generateRClassTask = processResources;
- processResources.enforceUniquePackageName = scope.getGlobalScope().getExtension()
- .getEnforceUniquePackageName();
-
- ConventionMappingHelper.map(processResources, "libraries",
- new Callable<List<SymbolFileProviderImpl>>() {
- @Override
- public List<SymbolFileProviderImpl> call() throws Exception {
- return getTextSymbolDependencies(config.getAllLibraries());
- }
- });
- ConventionMappingHelper.map(processResources, "packageForR",
- new Callable<String>() {
- @Override
- public String call() throws Exception {
- return config.getOriginalApplicationId();
- }
- });
-
- // TODO: unify with generateBuilderConfig, compileAidl, and library packaging somehow?
- processResources
- .setSourceOutputDir(scope.getVariantScope().getRClassSourceOutputDir());
- processResources.setTextSymbolOutputDir(symbolLocation);
-
- if (config.getBuildType().isMinifyEnabled()) {
- if (config.getBuildType().isShrinkResources() && config.getUseJack()) {
- LoggingUtil.displayWarning(Logging.getLogger(getClass()),
- scope.getGlobalScope().getProject(),
- "shrinkResources does not yet work with useJack=true");
- }
- processResources.setProguardOutputFile(
- scope.getVariantScope().getProcessAndroidResourcesProguardOutputFile());
-
- } else if (config.getBuildType().isShrinkResources()) {
- LoggingUtil.displayWarning(Logging.getLogger(getClass()),
- scope.getGlobalScope().getProject(),
- "To shrink resources you must also enable ProGuard");
- }
- }
-
- ConventionMappingHelper.map(processResources, "manifestFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return variantOutputData.manifestProcessorTask.getOutputFile();
- }
- });
-
- ConventionMappingHelper.map(processResources, "resDir", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getVariantScope().getFinalResourcesDir();
- }
- });
-
- ConventionMappingHelper.map(processResources, "assetsDir", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return variantData.mergeAssetsTask.getOutputDir();
- }
- });
-
- if (generateResourcePackage) {
- processResources.setPackageOutputFile(scope.getProcessResourcePackageOutputFile());
- }
-
- processResources.setType(config.getType());
- processResources.setDebuggable(config.getBuildType().isDebuggable());
- processResources.setAaptOptions(scope.getGlobalScope().getExtension().getAaptOptions());
- processResources
- .setPseudoLocalesEnabled(config.getBuildType().isPseudoLocalesEnabled());
-
- ConventionMappingHelper.map(processResources, "resourceConfigs",
- new Callable<Collection<String>>() {
- @Override
- public Collection<String> call() throws Exception {
- Collection<String> resConfigs =
- config.getMergedFlavor().getResourceConfigurations();
- if (resConfigs.size() == 1 &&
- Iterators.getOnlyElement(resConfigs.iterator())
- .equals("auto")) {
- return variantData.discoverListOfResourceConfigs();
- }
- return config.getMergedFlavor().getResourceConfigurations();
- }
- });
-
- ConventionMappingHelper.map(processResources, "preferredDensity",
- new Callable<String>() {
- @Override
- public String call() throws Exception {
- return variantOutputData.getMainOutputFile()
- .getFilter(com.android.build.OutputFile.DENSITY);
- }
- });
-
-
- }
-
- @NonNull
- private static List<SymbolFileProviderImpl> getTextSymbolDependencies(
- List<LibraryDependency> libraries) {
-
- List<SymbolFileProviderImpl> list = Lists.newArrayListWithCapacity(libraries.size());
-
- for (LibraryDependency lib : libraries) {
- list.add(new SymbolFileProviderImpl(lib));
- }
-
- return list;
- }
- }
-
- @InputFile
- public File getManifestFile() {
- return manifestFile;
- }
-
- public void setManifestFile(File manifestFile) {
- this.manifestFile = manifestFile;
- }
-
- @NonNull
- @InputDirectory
- public File getResDir() {
- return resDir;
- }
-
- public void setResDir(@NonNull File resDir) {
- this.resDir = resDir;
- }
-
- @OutputDirectory
- @Optional
- public File getAssetsDir() {
- return assetsDir;
- }
-
- public void setAssetsDir(File assetsDir) {
- this.assetsDir = assetsDir;
- }
-
- @OutputDirectory
- @Optional
- public File getSourceOutputDir() {
- return sourceOutputDir;
- }
-
- public void setSourceOutputDir(File sourceOutputDir) {
- this.sourceOutputDir = sourceOutputDir;
- }
-
- @OutputDirectory
- @Optional
- public File getTextSymbolOutputDir() {
- return textSymbolOutputDir;
- }
-
- public void setTextSymbolOutputDir(File textSymbolOutputDir) {
- this.textSymbolOutputDir = textSymbolOutputDir;
- }
-
- @OutputFile
- @Optional
- public File getPackageOutputFile() {
- return packageOutputFile;
- }
-
- public void setPackageOutputFile(File packageOutputFile) {
- this.packageOutputFile = packageOutputFile;
- }
-
- @OutputFile
- @Optional
- public File getProguardOutputFile() {
- return proguardOutputFile;
- }
-
- public void setProguardOutputFile(File proguardOutputFile) {
- this.proguardOutputFile = proguardOutputFile;
- }
-
- @Input
- public Collection<String> getResourceConfigs() {
- return resourceConfigs;
- }
-
- public void setResourceConfigs(Collection<String> resourceConfigs) {
- this.resourceConfigs = resourceConfigs;
- }
-
- @Input
- @Optional
- public String getPreferredDensity() {
- return preferredDensity;
- }
-
- public void setPreferredDensity(String preferredDensity) {
- this.preferredDensity = preferredDensity;
- }
-
- @Input
- String getBuildToolsVersion() {
- return getBuildTools().getRevision().toString();
- }
-
- @Nested
- @Optional
- public List<SymbolFileProviderImpl> getLibraries() {
- return libraries;
- }
-
- public void setLibraries(
- List<SymbolFileProviderImpl> libraries) {
- this.libraries = libraries;
- }
-
- @Input
- @Optional
- public String getPackageForR() {
- return packageForR;
- }
-
- public void setPackageForR(String packageForR) {
- this.packageForR = packageForR;
- }
-
- @Nested
- @Optional
- public Collection<String> getSplits() {
- return splits;
- }
-
- public void setSplits(Collection<String> splits) {
- this.splits = splits;
- }
-
- @Input
- public boolean getEnforceUniquePackageName() {
- return enforceUniquePackageName;
- }
-
- public void setEnforceUniquePackageName(boolean enforceUniquePackageName) {
- this.enforceUniquePackageName = enforceUniquePackageName;
- }
-
- /** Does not change between incremental builds, so does not need to be @Input. */
- public VariantType getType() {
- return type;
- }
-
- public void setType(VariantType type) {
- this.type = type;
- }
-
- @Input
- public boolean getDebuggable() {
- return debuggable;
- }
-
- public void setDebuggable(boolean debuggable) {
- this.debuggable = debuggable;
- }
-
- @Input
- public boolean getPseudoLocalesEnabled() {
- return pseudoLocalesEnabled;
- }
-
- public void setPseudoLocalesEnabled(boolean pseudoLocalesEnabled) {
- this.pseudoLocalesEnabled = pseudoLocalesEnabled;
- }
-
- @Nested
- public AaptOptions getAaptOptions() {
- return aaptOptions;
- }
-
- public void setAaptOptions(AaptOptions aaptOptions) {
- this.aaptOptions = aaptOptions;
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.groovy
deleted file mode 100644
index ce492fe..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.groovy
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-import com.android.build.gradle.internal.TaskManager
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.android.builder.core.AndroidBuilder
-import com.android.builder.core.VariantConfiguration
-import com.android.builder.model.ProductFlavor
-import com.android.manifmerger.ManifestMerger2
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.ParallelizableTask
-/**
- * a Task that only merge a single manifest with its overlays.
- */
- at ParallelizableTask
-class ProcessManifest extends ManifestProcessorTask {
-
- // ----- PUBLIC TASK API -----
- @Input @Optional
- String minSdkVersion
-
- @Input @Optional
- String targetSdkVersion
-
- @Input @Optional
- Integer maxSdkVersion
-
- VariantConfiguration variantConfiguration
-
- @InputFile
- File getMainManifest() {
- return variantConfiguration.getMainManifest();
- }
-
- @Input @Optional
- String getPackageOverride() {
- return variantConfiguration.getApplicationId();
- }
-
- @Input
- int getVersionCode() {
- variantConfiguration.getVersionCode();
- }
-
- @Input @Optional
- String getVersionName() {
- variantConfiguration.getVersionName();
- }
-
- @InputFiles
- List<File> getManifestOverlays() {
- return variantConfiguration.getManifestOverlays();
- }
-
- @Input @Optional
- File reportFile;
-
- /**
- * Return a serializable version of our map of key value pairs for placeholder substitution.
- * This serialized form is only used by gradle to compare past and present tasks to determine
- * whether a task need to be re-run or not.
- */
- @Input @Optional
- String getManifestPlaceholders() {
- return serializeMap(variantConfiguration.getManifestPlaceholders());
- }
-
- @Override
- protected void doFullTaskAction() {
-
- getBuilder().mergeManifests(
- getMainManifest(),
- getManifestOverlays(),
- Collections.emptyList(),
- getPackageOverride(),
- getVersionCode(),
- getVersionName(),
- getMinSdkVersion(),
- getTargetSdkVersion(),
- getMaxSdkVersion(),
- getManifestOutputFile().absolutePath,
- getAaptFriendlyManifestOutputFile()?.absolutePath,
- ManifestMerger2.MergeType.LIBRARY,
- variantConfiguration.getManifestPlaceholders(),
- getReportFile())
- }
-
- public static class ConfigAction implements TaskConfigAction<ProcessManifest> {
-
- VariantScope scope
-
- ConfigAction(VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- String getName() {
- return scope.getTaskName("process", "Manifest")
- }
-
- @Override
- Class<ProcessManifest> getType() {
- return ProcessManifest
- }
-
- @Override
- void execute(ProcessManifest processManifest) {
- VariantConfiguration config = scope.variantConfiguration
- AndroidBuilder androidBuilder = scope.globalScope.androidBuilder
-
- // get single output for now.
- BaseVariantOutputData variantOutputData = scope.variantData.outputs.get(0)
-
- variantOutputData.manifestProcessorTask = processManifest
- processManifest.androidBuilder = androidBuilder
- processManifest.setVariantName(config.getFullName())
-
- processManifest.variantConfiguration = config
-
- ProductFlavor mergedFlavor = config.mergedFlavor
-
- ConventionMappingHelper.map(processManifest, "minSdkVersion") {
- if (androidBuilder.isPreviewTarget()) {
- return androidBuilder.getTargetCodename()
- }
- return mergedFlavor.minSdkVersion?.apiString
- }
-
- ConventionMappingHelper.map(processManifest, "targetSdkVersion") {
- if (androidBuilder.isPreviewTarget()) {
- return androidBuilder.getTargetCodename()
- }
-
- return mergedFlavor.targetSdkVersion?.apiString
- }
-
- ConventionMappingHelper.map(processManifest, "maxSdkVersion") {
- if (androidBuilder.isPreviewTarget()) {
- return null
- }
-
- return mergedFlavor.maxSdkVersion
- }
-
- ConventionMappingHelper.map(processManifest, "manifestOutputFile") {
- variantOutputData.getScope().getManifestOutputFile()
- }
-
- ConventionMappingHelper.map(processManifest, "aaptFriendlyManifestOutputFile") {
- new File(scope.globalScope.getIntermediatesDir(),
- "$TaskManager.DIR_BUNDLES/${config.dirName}/aapt/AndroidManifest.xml")
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy
deleted file mode 100644
index 1ee359b..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks
-import com.android.build.gradle.internal.DependencyManager
-import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.android.builder.core.VariantConfiguration
-import com.google.common.collect.Lists
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.ParallelizableTask
-/**
- * A task that processes the manifest
- */
- at ParallelizableTask
-public class ProcessTestManifest extends ManifestProcessorTask {
-
- @InputFile
- @Optional
- File testManifestFile
-
- File tmpDir
-
- // ----- PRIVATE TASK API -----
-
- @Input
- String testApplicationId
-
- @Input @Optional
- String minSdkVersion
-
- @Input @Optional
- String targetSdkVersion
-
- @Input
- String testedApplicationId
-
- @Input
- String instrumentationRunner
-
- @Input
- Boolean handleProfiling;
-
- @Input
- Boolean functionalTest;
-
- @Input
- Map<String, Object> placeholdersValues;
-
- List<ManifestDependencyImpl> libraries
-
- /*
- * since libraries above can't return it's input files (@Nested doesn't
- * work on lists), so do a method that will gather them and return them.
- */
- @InputFiles
- List<File> getLibraryManifests() {
- List<ManifestDependencyImpl> libs = getLibraries()
- if (libs == null || libs.isEmpty()) {
- return Collections.emptyList();
- }
-
- List<File> files = Lists.newArrayListWithCapacity(libs.size() * 2)
- for (ManifestDependencyImpl mdi : libs) {
- files.addAll(mdi.getAllManifests())
- }
-
- return files;
- }
-
- @Override
- protected void doFullTaskAction() {
- getBuilder().processTestManifest(
- getTestApplicationId(),
- getMinSdkVersion(),
- getTargetSdkVersion(),
- getTestedApplicationId(),
- getInstrumentationRunner(),
- getHandleProfiling(),
- getFunctionalTest(),
- getTestManifestFile(),
- getLibraries(),
- getPlaceholdersValues(),
- getManifestOutputFile(),
- getTmpDir())
- }
-
- // ----- ConfigAction -----
-
-
- public static class ConfigAction implements TaskConfigAction<ProcessTestManifest> {
-
- VariantScope scope
-
- ConfigAction(VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- String getName() {
- return scope.getTaskName("process", "Manifest")
- }
-
- @Override
- Class<ProcessTestManifest> getType() {
- return ProcessTestManifest
- }
-
- @Override
- void execute(ProcessTestManifest processTestManifestTask) {
-
- VariantConfiguration config = scope.variantConfiguration
- ConventionMappingHelper.map(processTestManifestTask, "testManifestFile") {
- config.getMainManifest()
- }
- ConventionMappingHelper.map(processTestManifestTask, "tmpDir") {
- new File(scope.globalScope.getIntermediatesDir(), "manifest/tmp")
- }
-
- // get single output for now.
- BaseVariantOutputData variantOutputData = scope.variantData.outputs.get(0)
-
- variantOutputData.manifestProcessorTask = processTestManifestTask
-
- processTestManifestTask.androidBuilder = scope.globalScope.androidBuilder
- processTestManifestTask.setVariantName(config.getFullName())
-
- ConventionMappingHelper.map(processTestManifestTask, "testApplicationId") {
- config.applicationId
- }
- ConventionMappingHelper.map(processTestManifestTask, "minSdkVersion") {
- if (scope.globalScope.androidBuilder.isPreviewTarget()) {
- return scope.globalScope.androidBuilder.getTargetCodename()
- }
-
- config.minSdkVersion?.apiString
- }
- ConventionMappingHelper.map(processTestManifestTask, "targetSdkVersion") {
- if (scope.globalScope.androidBuilder.isPreviewTarget()) {
- return scope.globalScope.androidBuilder.getTargetCodename()
- }
-
- return config.targetSdkVersion?.apiString
- }
- ConventionMappingHelper.map(processTestManifestTask, "testedApplicationId") {
- config.testedApplicationId
- }
- ConventionMappingHelper.map(processTestManifestTask, "instrumentationRunner") {
- config.instrumentationRunner
- }
- ConventionMappingHelper.map(processTestManifestTask, "handleProfiling") {
- config.handleProfiling
- }
- ConventionMappingHelper.map(processTestManifestTask, "functionalTest") {
- config.functionalTest
- }
- ConventionMappingHelper.map(processTestManifestTask, "libraries") {
- DependencyManager.getManifestDependencies(config.directLibraries)
- }
- ConventionMappingHelper.map(processTestManifestTask, "manifestOutputFile") {
- variantOutputData.getScope().getManifestOutputFile()
- }
- ConventionMappingHelper.map(processTestManifestTask, "placeholdersValues") {
- config.getManifestPlaceholders()
- }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy
deleted file mode 100644
index 18e581c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-import com.android.annotations.NonNull
-import com.android.build.gradle.internal.core.GradleVariantConfiguration
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.build.gradle.internal.tasks.NdkTask
-import com.android.build.gradle.internal.variant.BaseVariantData
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.android.builder.model.ApiVersion
-import com.android.builder.model.ProductFlavor
-import com.android.ide.common.process.LoggedProcessOutputHandler
-import com.android.sdklib.SdkVersionInfo
-import com.android.utils.FileUtils
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-
-import static com.android.builder.model.AndroidProject.FD_GENERATED
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
-/**
- * Task to compile Renderscript files. Supports incremental update.
- */
-public class RenderscriptCompile extends NdkTask {
-
- // ----- PUBLIC TASK API -----
-
- @OutputDirectory
- File sourceOutputDir
-
- @OutputDirectory
- File resOutputDir
-
- @OutputDirectory
- File objOutputDir
-
- @OutputDirectory
- File libOutputDir
-
-
- // ----- PRIVATE TASK API -----
- @Input
- String getBuildToolsVersion() {
- getBuildTools().getRevision()
- }
-
- @InputFiles
- List<File> sourceDirs
-
- @InputFiles
- List<File> importDirs
-
- @Input
- Integer targetApi
-
- @Input
- boolean supportMode
-
- @Input
- int optimLevel
-
- @Input
- boolean debugBuild
-
- @Input
- boolean ndkMode
-
- @TaskAction
- void taskAction() throws IOException {
- // this is full run (always), clean the previous outputs
- File sourceDestDir = getSourceOutputDir()
- FileUtils.emptyFolder(sourceDestDir)
-
- File resDestDir = getResOutputDir()
- FileUtils.emptyFolder(resDestDir)
-
- File objDestDir = getObjOutputDir()
- FileUtils.emptyFolder(objDestDir)
-
- File libDestDir = getLibOutputDir()
- FileUtils.emptyFolder(libDestDir)
-
- // get the import folders. If the .rsh files are not directly under the import folders,
- // we need to get the leaf folders, as this is what llvm-rs-cc expects.
- List<File> importFolders = getBuilder().getLeafFolders("rsh",
- getImportDirs(), getSourceDirs())
-
- getBuilder().compileAllRenderscriptFiles(
- getSourceDirs(),
- importFolders,
- sourceDestDir,
- resDestDir,
- objDestDir,
- libDestDir,
- getTargetApi(),
- getDebugBuild(),
- getOptimLevel(),
- getNdkMode(),
- getSupportMode(),
- getNdkConfig()?.abiFilters,
- new LoggedProcessOutputHandler(getILogger()))
- }
-
- // ----- ConfigAction -----
-
- public static class ConfigAction implements TaskConfigAction<RenderscriptCompile> {
-
- @NonNull
- VariantScope scope
-
- ConfigAction(VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- String getName() {
- return scope.getTaskName("compile", "Renderscript");
- }
-
- @Override
- Class<RenderscriptCompile> getType() {
- return RenderscriptCompile
- }
-
- @Override
- void execute(RenderscriptCompile renderscriptTask) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
- GradleVariantConfiguration config = variantData.variantConfiguration
-
- variantData.renderscriptCompileTask = renderscriptTask
- ProductFlavor mergedFlavor = config.mergedFlavor
- boolean ndkMode = config.renderscriptNdkModeEnabled
- renderscriptTask.androidBuilder = scope.globalScope.androidBuilder
- renderscriptTask.setVariantName(config.getFullName())
-
- ConventionMappingHelper.map(renderscriptTask, "targetApi") {
- int targetApi = mergedFlavor.renderscriptTargetApi != null ?
- mergedFlavor.renderscriptTargetApi : -1
- ApiVersion apiVersion = config.getMinSdkVersion()
- if (apiVersion != null) {
- int minSdk = apiVersion.apiLevel
- if (apiVersion.codename != null) {
- minSdk = SdkVersionInfo.getApiByBuildCode(apiVersion.codename, true)
- }
-
- return targetApi > minSdk ? targetApi : minSdk
- }
-
- return targetApi
- }
-
- renderscriptTask.supportMode = config.renderscriptSupportModeEnabled
- renderscriptTask.ndkMode = ndkMode
- renderscriptTask.debugBuild = config.buildType.renderscriptDebuggable
- renderscriptTask.optimLevel = config.buildType.renderscriptOptimLevel
-
- ConventionMappingHelper.map(renderscriptTask, "sourceDirs") { config.renderscriptSourceList }
- ConventionMappingHelper.map(renderscriptTask, "importDirs") { config.renderscriptImports }
-
- ConventionMappingHelper.map(renderscriptTask, "sourceOutputDir") {
- new File(
- "$scope.globalScope.buildDir/${FD_GENERATED}/source/rs/${variantData.variantConfiguration.dirName}")
- }
- ConventionMappingHelper.map(renderscriptTask, "resOutputDir") {
- scope.getRenderscriptResOutputDir()
- }
- ConventionMappingHelper.map(renderscriptTask, "objOutputDir") {
- new File(
- "$scope.globalScope.buildDir/${FD_INTERMEDIATES}/rs/${variantData.variantConfiguration.dirName}/obj")
- }
- ConventionMappingHelper.map(renderscriptTask, "libOutputDir") {
- new File(
- "$scope.globalScope.buildDir/${FD_INTERMEDIATES}/rs/${variantData.variantConfiguration.dirName}/lib")
- }
- ConventionMappingHelper.map(renderscriptTask, "ndkConfig") { config.ndkConfig }
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzer.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzer.java
deleted file mode 100644
index 649a8d0..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzer.java
+++ /dev/null
@@ -1,2568 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PARENT;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.DOT_CLASS;
-import static com.android.SdkConstants.DOT_GIF;
-import static com.android.SdkConstants.DOT_JPEG;
-import static com.android.SdkConstants.DOT_JPG;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.FD_RES_VALUES;
-import static com.android.SdkConstants.PREFIX_ANDROID;
-import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_RESOURCES;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.utils.SdkUtils.endsWith;
-import static com.android.utils.SdkUtils.endsWithIgnoreCase;
-import static com.google.common.base.Charsets.UTF_8;
-import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
-import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.ide.common.resources.configuration.DensityQualifier;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.ResourceQualifier;
-import com.android.ide.common.xml.XmlPrettyPrinter;
-import com.android.resources.FolderTypeRelationship;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.checks.StringFormatDetector;
-import com.android.tools.lint.client.api.DefaultConfiguration;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Joiner;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Class responsible for searching through a Gradle built tree (after resource merging,
- * compilation and ProGuarding has been completed, but before final .apk assembly), which
- * figures out which resources if any are unused, and removes them.
- * <p>
- * It does this by examining
- * <ul>
- * <li>The merged manifest, to find root resource references (such as drawables
- * used for activity icons)</li>
- * <li>The merged R class (to find the actual integer constants assigned to resources)</li>
- * <li>The ProGuard log files (to find the mapping from original symbol names to
- * short names)</li>*
- * <li>The merged resources (to find which resources reference other resources, e.g.
- * drawable state lists including other drawables, or layouts including other
- * layouts, or styles referencing other drawables, or menus items including action
- * layouts, etc.)</li>
- * <li>The ProGuard output classes (to find resource references in code that are
- * actually reachable)</li>
- * </ul>
- * From all this, it builds up a reference graph, and based on the root references (e.g.
- * from the manifest and from the remaining code) it computes which resources are actually
- * reachable in the app, and anything that is not reachable is then marked for deletion.
- * <p>
- * A resource is referenced in code if either the field R.type.name is referenced (which
- * is the case for non-final resource references, e.g. in libraries), or if the corresponding
- * int value is referenced (for final resource values). We check this by looking at the
- * ProGuard output classes with an ASM visitor. One complication is that code can also
- * call {@code Resources#getIdentifier(String,String,String)} where they can pass in the names
- * of resources to look up. To handle this scenario, we use the ClassVisitor to see if
- * there are any calls to the specific {@code Resources#getIdentifier} method. If not,
- * great, the usage analysis is completely accurate. If we <b>do</b> find one, we check
- * <b>all</b> the string constants found anywhere in the app, and look to see if any look
- * relevant. For example, if we find the string "string/foo" or "my.pkg:string/foo", we
- * will then mark the string resource named foo (if any) as potentially used. Similarly,
- * if we find just "foo" or "/foo", we will mark <b>all</b> resources named "foo" as
- * potentially used. However, if the string is "bar/foo" or " foo " these strings are
- * ignored. This means we can potentially miss resources usages where the resource name
- * is completed computed (e.g. by concatenating individual characters or taking substrings
- * of strings that do not look like resource names), but that seems extremely unlikely
- * to be a real-world scenario.
- * <p>
- * For now, for reasons detailed in the code, this only applies to file-based resources
- * like layouts, menus and drawables, not value-based resources like strings and dimensions.
- */
-public class ResourceUsageAnalyzer {
- private static final String ANDROID_RES = "android_res/";
-
- /**
- Whether we support running aapt twice, to regenerate the resources.arsc file
- such that we can strip out value resources as well. We don't do this yet, for
- reasons detailed in the ShrinkResources task
-
- We have two options:
- (1) Copy the resource files over to a new destination directory, filtering out
- removed file resources and rewriting value resource files by stripping out
- the declarations for removed value resources. We then re-run aapt on this
- new destination directory.
-
- The problem with this approach is that when we re-run aapt it will assign new
- id's to all the resources, so we have to create dummy placeholders for all the
- removed resources. (The alternative would be to then run compilation one more
- time -- regenerating classes.jar, regenerating .dex) -- this would really slow
- down builds.)
-
- A cleaner solution than this is to get aapt to support using a predefined set
- of id's. It can emit R.txt symbol files now; if we can get it to read R.txt
- and use those numbers in its assignment, we can solve this cleanly. This request
- is tracked in https://code.google.com/p/android/issues/detail?id=70869
-
- (2) Just rewrite the .ap_ file directly. It's just a .zip file which contains
- (a) binary files for bitmaps and XML file resources such as layouts and menus
- (b) a binary file, resources.arsc, containing all the values.
- The resources.arsc format is opaque to us. However, MOST of the resource bulk
- comes from the bitmap and other resource files.
-
- So here we don't even need to run aapt a second time; we simply rewrite the
- .ap_ zip file directly, filtering out res/ files we know to be unused.
-
- Approach #2 gives us most of the space savings without the risk of #1 (running aapt
- a second time introduces the possibility of aapt compilation errors if we haven't
- been careful enough to insert resource aliases for all necessary items (such as
- inline @+id declarations), or if we haven't carefully not created aliases for items
- already defined in other value files as aliases, and perhaps most importantly,
- introduces risk that aapt will pick a different resource order anyway, which we can
- only guard against by doing a full compilation over again.
-
- Therefore, for now the below code uses #2, but since we can solve #1 with support
- from aapt), we're preserving all the code to rewrite resource files since that will
- give additional space savings, particularly for apps with a lot of strings or a lot
- of translations.
- */
- @SuppressWarnings("SpellCheckingInspection") // arsc
- public static final boolean TWO_PASS_AAPT = false;
- public static final int TYPICAL_RESOURCE_COUNT = 200;
-
- /** Name of keep attribute in XML */
- private static final String ATTR_KEEP = "keep";
- /** Name of discard attribute in XML (to mark resources as not referenced, despite guesses) */
- private static final String ATTR_DISCARD = "discard";
- /** Name of attribute in XML to control whether we should guess resources to keep */
- private static final String ATTR_SHRINK_MODE = "shrinkMode";
- /** @{linkplain #ATTR_SHRINK_MODE} value to only shrink explicitly encountered resources */
- private static final String VALUE_STRICT = "strict";
- /** @{linkplain #ATTR_SHRINK_MODE} value to keep possibly referenced resources */
- private static final String VALUE_SAFE = "safe";
-
- /** Special marker regexp which does not match a resource name */
- static final String NO_MATCH = "-nomatch-";
-
- private final File mResourceClassDir;
- private final File mProguardMapping;
- private final File mClassesJar;
- private final File mMergedManifest;
- private final File mMergedResourceDir;
-
- private boolean mVerbose;
- private boolean mDebug;
- private boolean mDryRun;
-
- /** The computed set of unused resources */
- private List<Resource> mUnused;
-
- /** List of all known resources (parsed from R.java) */
- private List<Resource> mResources = Lists.newArrayListWithExpectedSize(TYPICAL_RESOURCE_COUNT);
- /** Map from R field value to corresponding resource */
- private Map<Integer, Resource> mValueToResource =
- Maps.newHashMapWithExpectedSize(TYPICAL_RESOURCE_COUNT);
- /** Map from resource type to map from resource name to resource object */
- private Map<ResourceType, Map<String, Resource>> mTypeToName =
- Maps.newEnumMap(ResourceType.class);
- /** Map from resource class owners (VM format class) to corresponding resource types.
- * This will typically be the fully qualified names of the R classes, as well as
- * any renamed versions of those discovered in the mapping.txt file from ProGuard */
- private Map<String, ResourceType> mResourceClassOwners = Maps.newHashMapWithExpectedSize(20);
-
- /**
- * Whether we should attempt to guess resources that should be kept based on looking
- * at the string pool and assuming some of the strings can be used to dynamically construct
- * the resource names. Can be turned off via {@code tools:guessKeep="false"}.
- */
- private boolean mGuessKeep = true;
-
- public ResourceUsageAnalyzer(
- @NonNull File rDir,
- @NonNull File classesJar,
- @NonNull File manifest,
- @Nullable File mapping,
- @NonNull File resources) {
- mResourceClassDir = rDir;
- mProguardMapping = mapping;
- mClassesJar = classesJar;
- mMergedManifest = manifest;
- mMergedResourceDir = resources;
- }
-
- public void analyze() throws IOException, ParserConfigurationException, SAXException {
- gatherResourceValues(mResourceClassDir);
- recordMapping(mProguardMapping);
- recordUsages(mClassesJar);
- recordManifestUsages(mMergedManifest);
- recordResources(mMergedResourceDir);
- keepPossiblyReferencedResources();
- dumpReferences();
- findUnused();
- }
-
- public boolean isDryRun() {
- return mDryRun;
- }
-
- public void setDryRun(boolean dryRun) {
- mDryRun = dryRun;
- }
-
- public boolean isVerbose() {
- return mVerbose;
- }
-
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
-
- public boolean isDebug() {
- return mDebug;
- }
-
- public void setDebug(boolean verbose) {
- mDebug = verbose;
- }
-
- /**
- * "Removes" resources from an .ap_ file by writing it out while filtering out
- * unused resources. This won't touch the values XML data (resources.arsc) but
- * will remove the individual file-based resources, which is where most of
- * the data is anyway (usually in drawable bitmaps)
- *
- * @param source the .ap_ file created by aapt
- * @param dest a new .ap_ file with unused file-based resources removed
- */
- public void rewriteResourceZip(@NonNull File source, @NonNull File dest)
- throws IOException {
- if (dest.exists()) {
- boolean deleted = dest.delete();
- if (!deleted) {
- throw new IOException("Could not delete " + dest);
- }
- }
-
- JarInputStream zis = null;
- try {
- FileInputStream fis = new FileInputStream(source);
- try {
- FileOutputStream fos = new FileOutputStream(dest);
- zis = new JarInputStream(fis);
- JarOutputStream zos = new JarOutputStream(fos);
- try {
- // Rather than using Deflater.DEFAULT_COMPRESSION we use 9 here,
- // since that seems to match the compressed sizes we observe in source
- // .ap_ files encountered by the resource shrinker:
- zos.setLevel(9);
-
- ZipEntry entry = zis.getNextEntry();
- while (entry != null) {
- String name = entry.getName();
- boolean directory = entry.isDirectory();
- Resource resource = getResourceByJarPath(name);
- if (resource == null || resource.reachable) {
- // We can't just compress all files; files that are not
- // compressed in the source .ap_ file must be left uncompressed
- // here, since for example RAW files need to remain uncompressed in
- // the APK such that they can be mmap'ed at runtime.
- // Preserve the STORED method of the input entry.
- JarEntry outEntry;
- if (entry.getMethod() == JarEntry.STORED) {
- outEntry = new JarEntry(entry);
- } else {
- // Create a new entry so that the compressed len is recomputed.
- outEntry = new JarEntry(name);
- if (entry.getTime() != -1L) {
- outEntry.setTime(entry.getTime());
- }
- }
-
- zos.putNextEntry(outEntry);
-
- if (!directory) {
- byte[] bytes = ByteStreams.toByteArray(zis);
- if (bytes != null) {
- zos.write(bytes);
- }
- }
-
- zos.closeEntry();
- } else if (isVerbose()) {
- System.out.println("Skipped unused resource " + name + ": "
- + entry.getSize() + " bytes");
- }
- entry = zis.getNextEntry();
- }
- zos.flush();
- } finally {
- Closeables.close(zos, false);
- }
- } finally {
- Closeables.close(fis, true);
- }
- } finally {
- Closeables.close(zis, false);
- }
- }
-
- /**
- * Remove resources (already identified by {@link #analyze()}).
- *
- * This task will copy all remaining used resources over from the full resource
- * directory to a new reduced resource directory. However, it can't just
- * delete the resources, because it has no way to tell aapt to continue to use
- * the same id's for the resources. When we re-run aapt on the stripped resource
- * directory, it will assign new id's to some of the resources (to fill the gaps)
- * which means the resource id's no longer match the constants compiled into the
- * dex files, and as a result, the app crashes at runtime.
- * <p>
- * Therefore, it needs to preserve all id's by actually keeping all the resource
- * names. It can still save a lot of space by making these resources tiny; e.g.
- * all strings are set to empty, all styles, arrays and plurals are set to not contain
- * any children, and most importantly, all file based resources like bitmaps and
- * layouts are replaced by simple resource aliases which just point to @null.
- *
- * @param destination directory to copy resources into; if null, delete resources in place
- * @throws IOException
- * @throws ParserConfigurationException
- * @throws SAXException
- */
- public void removeUnused(@Nullable File destination) throws IOException,
- ParserConfigurationException, SAXException {
- if (TWO_PASS_AAPT) {
- assert mUnused != null; // should always call analyze() first
-
- int resourceCount = mUnused.size()
- * 4; // *4: account for some resource folder repetition
- boolean inPlace = destination == null;
- Set<File> skip = inPlace ? null : Sets.<File>newHashSetWithExpectedSize(resourceCount);
- Set<File> rewrite = Sets.newHashSetWithExpectedSize(resourceCount);
- for (Resource resource : mUnused) {
- if (resource.declarations != null) {
- for (File file : resource.declarations) {
- String folder = file.getParentFile().getName();
- ResourceFolderType folderType = ResourceFolderType.getFolderType(folder);
- if (folderType != null && folderType != ResourceFolderType.VALUES) {
- if (isVerbose()) {
- System.out.println("Deleted unused resource " + file);
- }
- if (inPlace) {
- if (!isDryRun()) {
- boolean delete = file.delete();
- if (!delete) {
- System.err.println("Could not delete " + file);
- }
- }
- } else {
- assert skip != null;
- skip.add(file);
- }
- } else {
- // Can't delete values immediately; there can be many resources
- // in this file, so we have to process them all
- rewrite.add(file);
- }
- }
- }
- }
-
- // Special case the base values.xml folder
- File values = new File(mMergedResourceDir,
- FD_RES_VALUES + File.separatorChar + "values.xml");
- boolean valuesExists = values.exists();
- if (valuesExists) {
- rewrite.add(values);
- }
-
- Map<File, String> rewritten = Maps.newHashMapWithExpectedSize(rewrite.size());
-
- // Delete value resources: Must rewrite the XML files
- for (File file : rewrite) {
- String xml = Files.toString(file, UTF_8);
- Document document = XmlUtils.parseDocument(xml, true);
- Element root = document.getDocumentElement();
- if (root != null && TAG_RESOURCES.equals(root.getTagName())) {
- List<String> removed = Lists.newArrayList();
- stripUnused(root, removed);
- if (isVerbose()) {
- System.out.println("Removed " + removed.size() +
- " unused resources from " + file + ":\n " +
- Joiner.on(", ").join(removed));
- }
-
- String formatted = XmlPrettyPrinter.prettyPrint(document, xml.endsWith("\n"));
- rewritten.put(file, formatted);
- }
- }
-
- if (isDryRun()) {
- return;
- }
-
- if (valuesExists) {
- String xml = rewritten.get(values);
- if (xml == null) {
- xml = Files.toString(values, UTF_8);
- }
- Document document = XmlUtils.parseDocument(xml, true);
- Element root = document.getDocumentElement();
-
- for (Resource resource : mResources) {
- if (resource.type == ResourceType.ID && !resource.hasDefault) {
- Element item = document.createElement(TAG_ITEM);
- item.setAttribute(ATTR_TYPE, resource.type.getName());
- item.setAttribute(ATTR_NAME, resource.name);
- root.appendChild(item);
- } else if (!resource.reachable
- && !resource.hasDefault
- && resource.type != ResourceType.DECLARE_STYLEABLE
- && resource.type != ResourceType.STYLE
- && resource.type != ResourceType.PLURALS
- && resource.type != ResourceType.ARRAY
- && resource.isRelevantType()) {
- Element item = document.createElement(TAG_ITEM);
- item.setAttribute(ATTR_TYPE, resource.type.getName());
- item.setAttribute(ATTR_NAME, resource.name);
- root.appendChild(item);
- String s = "@null";
- item.appendChild(document.createTextNode(s));
- }
- }
-
- String formatted = XmlPrettyPrinter.prettyPrint(document, xml.endsWith("\n"));
- rewritten.put(values, formatted);
- }
-
- if (inPlace) {
- for (Map.Entry<File, String> entry : rewritten.entrySet()) {
- File file = entry.getKey();
- String formatted = entry.getValue();
- Files.write(formatted, file, UTF_8);
- }
- } else {
- filteredCopy(mMergedResourceDir, destination, skip, rewritten);
- }
- } else {
- assert false;
- }
- }
-
- /**
- * Copies one resource directory tree into another; skipping some files, replacing
- * the contents of some, and passing everything else through unmodified
- */
- private static void filteredCopy(File source, File destination, Set<File> skip,
- Map<File, String> replace) throws IOException {
- if (TWO_PASS_AAPT) {
- if (source.isDirectory()) {
- File[] children = source.listFiles();
- if (children != null) {
- if (!destination.exists()) {
- boolean success = destination.mkdirs();
- if (!success) {
- throw new IOException("Could not create " + destination);
- }
- }
- for (File child : children) {
- filteredCopy(child, new File(destination, child.getName()), skip, replace);
- }
- }
- } else if (!skip.contains(source) && source.isFile()) {
- String contents = replace.get(source);
- if (contents != null) {
- Files.write(contents, destination, UTF_8);
- } else {
- Files.copy(source, destination);
- }
- }
- } else {
- assert false;
- }
- }
-
- private void stripUnused(Element element, List<String> removed) {
- if (TWO_PASS_AAPT) {
- ResourceType type = getResourceType(element);
- if (type == ResourceType.ATTR) {
- // Not yet properly handled
- return;
- }
-
- Resource resource = getResource(element);
- if (resource != null) {
- if (resource.type == ResourceType.DECLARE_STYLEABLE ||
- resource.type == ResourceType.ATTR) {
- // Don't strip children of declare-styleable; we're not correctly
- // tracking field references of the R_styleable_attr fields yet
- return;
- }
-
- if (!resource.reachable &&
- (resource.type == ResourceType.STYLE ||
- resource.type == ResourceType.PLURALS ||
- resource.type == ResourceType.ARRAY)) {
- NodeList children = element.getChildNodes();
- for (int i = children.getLength() - 1; i >= 0; i--) {
- Node child = children.item(i);
- element.removeChild(child);
- }
- return;
- }
- }
-
- NodeList children = element.getChildNodes();
- for (int i = children.getLength() - 1; i >= 0; i--) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- stripUnused((Element) child, removed);
- }
- }
-
- if (resource != null && !resource.reachable) {
- if (mVerbose) {
- removed.add(resource.getUrl());
- }
- // for themes etc where .'s have been replaced by _'s
- String name = element.getAttribute(ATTR_NAME);
- if (name.isEmpty()) {
- name = resource.name;
- }
- Node nextSibling = element.getNextSibling();
- Node parent = element.getParentNode();
- NodeList oldChildren = element.getChildNodes();
- parent.removeChild(element);
- Document document = element.getOwnerDocument();
- element = document.createElement("item");
- for (int i = 0; i < oldChildren.getLength(); i++) {
- element.appendChild(oldChildren.item(i));
- }
-
- element.setAttribute(ATTR_NAME, name);
- element.setAttribute(ATTR_TYPE, resource.type.getName());
- String text = null;
- switch (resource.type) {
- case BOOL:
- text = "true";
- break;
- case DIMEN:
- text = "0dp";
- break;
- case INTEGER:
- text = "0";
- break;
- }
- element.setTextContent(text);
- parent.insertBefore(element, nextSibling);
- }
- } else {
- assert false;
- }
- }
-
- private static String getFieldName(Element element) {
- return getFieldName(element.getAttribute(ATTR_NAME));
- }
-
- @Nullable
- private Resource getResource(Element element) {
- ResourceType type = getResourceType(element);
- if (type != null) {
- String name = getFieldName(element);
- return getResource(type, name);
- }
-
- return null;
- }
-
- @Nullable
- private Resource getResourceByJarPath(String path) {
- // Jars use forward slash paths, not File.separator
- if (path.startsWith("res/")) {
- int folderStart = 4; // "res/".length
- int folderEnd = path.indexOf('/', folderStart);
- if (folderEnd != -1) {
- String folderName = path.substring(folderStart, folderEnd);
- ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
- if (folderType != null) {
- int nameStart = folderEnd + 1;
- int nameEnd = path.indexOf('.', nameStart);
- if (nameEnd != -1) {
- String name = path.substring(nameStart, nameEnd);
- List<ResourceType> types =
- FolderTypeRelationship.getRelatedResourceTypes(folderType);
- for (ResourceType type : types) {
- if (type != ResourceType.ID) {
- Resource resource = getResource(type, name);
- if (resource != null) {
- return resource;
- }
- }
- }
- }
- }
- }
- }
-
- return null;
- }
-
- private static ResourceType getResourceType(Element element) {
- String tagName = element.getTagName();
- if (tagName.equals(TAG_ITEM)) {
- String typeName = element.getAttribute(ATTR_TYPE);
- if (!typeName.isEmpty()) {
- return ResourceType.getEnum(typeName);
- }
- } else if ("string-array".equals(tagName) || "integer-array".equals(tagName)) {
- return ResourceType.ARRAY;
- } else {
- return ResourceType.getEnum(tagName);
- }
- return null;
- }
-
- private void findUnused() {
- List<Resource> roots = Lists.newArrayList();
-
- for (Resource resource : mResources) {
- if (resource.reachable && resource.type != ResourceType.ID
- && resource.type != ResourceType.ATTR) {
- roots.add(resource);
- }
- }
-
- if (mDebug) {
- System.out.println("The root reachable resources are:\n" +
- Joiner.on(",\n ").join(roots));
- }
-
- Map<Resource,Boolean> seen = new IdentityHashMap<Resource,Boolean>(mResources.size());
- for (Resource root : roots) {
- visit(root, seen);
- }
-
- List<Resource> unused = Lists.newArrayListWithExpectedSize(mResources.size());
- for (Resource resource : mResources) {
- if (!resource.reachable && resource.isRelevantType()) {
- unused.add(resource);
- }
- }
-
- mUnused = unused;
-
- if (mDebug) {
- System.out.println(dumpResourceModel());
- }
- }
-
- private static void visit(Resource root, Map<Resource, Boolean> seen) {
- if (seen.containsKey(root)) {
- return;
- }
- seen.put(root, Boolean.TRUE);
- root.reachable = true;
- if (root.references != null) {
- for (Resource referenced : root.references) {
- visit(referenced, seen);
- }
- }
- }
-
- private void dumpReferences() {
- if (mDebug) {
- System.out.println("Resource Reference Graph:");
- for (Resource resource : mResources) {
- if (resource.references != null) {
- System.out.println(resource + " => " + resource.references);
- }
- }
- }
- }
-
- private void keepPossiblyReferencedResources() {
- if ((!mFoundGetIdentifier && !mFoundWebContent) || mStrings == null) {
- // No calls to android.content.res.Resources#getIdentifier; no need
- // to worry about string references to resources
- return;
- }
-
- if (!mGuessKeep) {
- // User specifically asked for us not to guess resources to keep; they will
- // explicitly mark them as kept if necessary instead
- return;
- }
-
- if (mDebug) {
- List<String> strings = new ArrayList<String>(mStrings);
- Collections.sort(strings);
- System.out.println("android.content.res.Resources#getIdentifier present: "
- + mFoundGetIdentifier);
- System.out.println("Web content present: " + mFoundWebContent);
- System.out.println("Referenced Strings:");
- for (String s : strings) {
- s = s.trim().replace("\n", "\\n");
- if (s.length() > 40) {
- s = s.substring(0, 37) + "...";
- } else if (s.isEmpty()) {
- continue;
- }
- System.out.println(" " + s);
- }
- }
-
- int shortest = Integer.MAX_VALUE;
- Set<String> names = Sets.newHashSetWithExpectedSize(50);
- for (Map<String, Resource> map : mTypeToName.values()) {
- for (String name : map.keySet()) {
- names.add(name);
- int length = name.length();
- if (length < shortest) {
- shortest = length;
- }
- }
- }
-
- for (String string : mStrings) {
- if (string.length() < shortest) {
- continue;
- }
-
- // Check whether the string looks relevant
- // We consider four types of strings:
- // (1) simple resource names, e.g. "foo" from @layout/foo
- // These might be the parameter to a getIdentifier() call, or could
- // be composed into a fully qualified resource name for the getIdentifier()
- // method. We match these for *all* resource types.
- // (2) Relative source names, e.g. layout/foo, from @layout/foo
- // These might be composed into a fully qualified resource name for
- // getIdentifier().
- // (3) Fully qualified resource names of the form package:type/name.
- // (4) If mFoundWebContent is true, look for android_res/ URL strings as well
-
- if (mFoundWebContent) {
- Resource resource = getResourceFromFilePath(string);
- if (resource != null) {
- markReachable(resource);
- continue;
- } else {
- int start = 0;
- int slash = string.lastIndexOf('/');
- if (slash != -1) {
- start = slash + 1;
- }
- int dot = string.indexOf('.', start);
- String name = string.substring(start, dot != -1 ? dot : string.length());
- if (names.contains(name)) {
- for (Map<String, Resource> map : mTypeToName.values()) {
- resource = map.get(name);
- if (mDebug && resource != null) {
- System.out.println("Marking " + resource + " used because it "
- + "matches string pool constant " + string);
- }
- markReachable(resource);
- }
- }
- }
- }
-
- // Look for normal getIdentifier resource URLs
- int n = string.length();
- boolean justName = true;
- boolean formatting = false;
- boolean haveSlash = false;
- for (int i = 0; i < n; i++) {
- char c = string.charAt(i);
- if (c == '/') {
- haveSlash = true;
- justName = false;
- } else if (c == '.' || c == ':' || c == '%') {
- justName = false;
- if (c == '%') {
- formatting = true;
- }
- } else if (!Character.isJavaIdentifierPart(c)) {
- // This shouldn't happen; we've filtered out these strings in
- // the {@link #referencedString} method
- assert false : string;
- break;
- }
- }
-
- String name;
- if (justName) {
- // Check name (below)
- name = string;
-
- // Check for a simple prefix match, e.g. as in
- // getResources().getIdentifier("ic_video_codec_" + codecName, "drawable", ...)
- for (Map<String, Resource> map : mTypeToName.values()) {
- for (Resource resource : map.values()) {
- if (resource.name.startsWith(name)) {
- if (mDebug) {
- System.out.println("Marking " + resource + " used because its "
- + "prefix matches string pool constant " + string);
- }
- markReachable(resource);
- }
- }
- }
- } else if (!haveSlash) {
- if (formatting) {
- // Possibly a formatting string, e.g.
- // String name = String.format("my_prefix_%1d", index);
- // int res = getContext().getResources().getIdentifier(name, "drawable", ...)
-
- try {
- Pattern pattern = Pattern.compile(convertFormatStringToRegexp(string));
- for (Map<String, Resource> map : mTypeToName.values()) {
- for (Resource resource : map.values()) {
- if (pattern.matcher(resource.name).matches()) {
- if (mDebug) {
- System.out.println("Marking " + resource + " used because "
- + "it format-string matches string pool constant "
- + string);
- }
- markReachable(resource);
- }
- }
- }
- } catch (PatternSyntaxException ignored) {
- // Might not have been a formatting string after all!
- }
- }
-
- // If we have more than just a symbol name, we expect to also see a slash
- //noinspection UnnecessaryContinue
- continue;
- } else {
- // Try to pick out the resource name pieces; if we can find the
- // resource type unambiguously; if not, just match on names
- int slash = string.indexOf('/');
- assert slash != -1; // checked with haveSlash above
- name = string.substring(slash + 1);
- if (name.isEmpty() || !names.contains(name)) {
- continue;
- }
- // See if have a known specific resource type
- if (slash > 0) {
- int colon = string.indexOf(':');
- String typeName = string.substring(colon != -1 ? colon + 1 : 0, slash);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type == null) {
- continue;
- }
- Resource resource = getResource(type, name);
- if (mDebug && resource != null) {
- System.out.println("Marking " + resource + " used because it "
- + "matches string pool constant " + string);
- }
- markReachable(resource);
- continue;
- }
-
- // fall through and check the name
- }
-
- if (names.contains(name)) {
- for (Map<String, Resource> map : mTypeToName.values()) {
- Resource resource = map.get(name);
- if (mDebug && resource != null) {
- System.out.println("Marking " + resource + " used because it "
- + "matches string pool constant " + string);
- }
- markReachable(resource);
- }
- } else if (Character.isDigit(name.charAt(0))) {
- // Just a number? There are cases where it calls getIdentifier by
- // a String number; see for example SuggestionsAdapter in the support
- // library which reports supporting a string like "2130837524" and
- // "android.resource://com.android.alarmclock/2130837524".
- try {
- int id = Integer.parseInt(name);
- if (id != 0) {
- markReachable(mValueToResource.get(id));
- }
- } catch (NumberFormatException e) {
- // pass
- }
- }
- }
- }
-
- @VisibleForTesting
- static String convertFormatStringToRegexp(String formatString) {
- StringBuilder regexp = new StringBuilder();
- int from = 0;
- boolean hasEscapedLetters = false;
- Matcher matcher = StringFormatDetector.FORMAT.matcher(formatString);
- int length = formatString.length();
- while (matcher.find(from)) {
- int start = matcher.start();
- int end = matcher.end();
- if (start == 0 && end == length) {
- // Don't match if the entire string literal starts with % and ends with
- // the a formatting character, such as just "%d": this just matches absolutely
- // everything and is unlikely to be used in a resource lookup
- return NO_MATCH;
- }
- if (start > from) {
- hasEscapedLetters |= appendEscapedPattern(formatString, regexp, from, start);
- }
- // If the wildcard follows a previous wildcard, just skip it
- // (e.g. don't convert %s%s into .*.*; .* is enough.
- int regexLength = regexp.length();
- if (regexLength < 2
- || regexp.charAt(regexLength - 1) != '*'
- || regexp.charAt(regexLength - 2) != '.') {
- regexp.append(".*");
- }
- from = end;
- }
-
- if (from < length) {
- hasEscapedLetters |= appendEscapedPattern(formatString, regexp, from, length);
- }
-
- if (!hasEscapedLetters) {
- // If the regexp contains *only* formatting characters, e.g. "%.0f%d", or
- // if it contains only formatting characters and punctuation, e.g. "%s_%d",
- // don't treat this as a possible resource name pattern string: it is unlikely
- // to be intended for actual resource names, and has the side effect of matching
- // most names.
- return NO_MATCH;
- }
-
- return regexp.toString();
- }
-
- /**
- * Appends the characters in the range [from,to> from formatString as escaped
- * regexp characters into the given string builder. Returns true if there were
- * any letters in the appended text.
- */
- private static boolean appendEscapedPattern(@NonNull String formatString,
- @NonNull StringBuilder regexp, int from, int to) {
- regexp.append(Pattern.quote(formatString.substring(from, to)));
-
- for (int i = from; i < to; i++) {
- if (Character.isLetter(formatString.charAt(i))) {
- return true;
- }
- }
-
- return false;
- }
-
- private void recordResources(File resDir)
- throws IOException, SAXException, ParserConfigurationException {
- File[] resourceFolders = resDir.listFiles();
- if (resourceFolders != null) {
- for (File folder : resourceFolders) {
- ResourceFolderType folderType = ResourceFolderType.getFolderType(folder.getName());
- if (folderType != null) {
- recordResources(folderType, folder);
- }
- }
- }
- }
-
- private void recordResources(@NonNull ResourceFolderType folderType, File folder)
- throws ParserConfigurationException, SAXException, IOException {
- File[] files = folder.listFiles();
- FolderConfiguration config = FolderConfiguration.getConfigForFolder(folder.getName());
- boolean isDefaultFolder = false;
- if (config != null) {
- isDefaultFolder = true;
- for (int i = 0, n = FolderConfiguration.getQualifierCount(); i < n; i++) {
- ResourceQualifier qualifier = config.getQualifier(i);
- // Densities are special: even if they're present in just (say) drawable-hdpi
- // we'll match it on any other density
- if (qualifier != null && !(qualifier instanceof DensityQualifier)) {
- isDefaultFolder = false;
- break;
- }
- }
- }
-
- if (files != null) {
- for (File file : files) {
- String path = file.getPath();
- boolean isXml = endsWithIgnoreCase(path, DOT_XML);
-
- Resource from = null;
- // Record resource for the whole file
- if (folderType != ResourceFolderType.VALUES
- && (isXml
- || endsWith(path, DOT_PNG) //also true for endsWith(name, DOT_9PNG)
- || endsWith(path, DOT_JPG)
- || endsWith(path, DOT_GIF)
- || endsWith(path, DOT_JPEG))) {
- List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
- folderType);
- ResourceType type = types.get(0);
- assert type != ResourceType.ID : folderType;
- String name = file.getName();
- name = name.substring(0, name.indexOf('.'));
- Resource resource = getResource(type, name);
- if (resource != null) {
- resource.addLocation(file);
- if (isDefaultFolder) {
- resource.hasDefault = true;
- }
- from = resource;
- }
- }
-
- if (isXml) {
- // For value files, and drawables and colors etc also pull in resource
- // references inside the file
- recordXmlResourcesUsages(file, isDefaultFolder, from);
- if (folderType == ResourceFolderType.XML) {
- tokenizeUnknownText(Files.toString(file, UTF_8));
- }
- } else if (folderType == ResourceFolderType.RAW) {
- // Is this an HTML, CSS or JavaScript document bundled with the app?
- // If so tokenize and look for resource references.
- if (endsWithIgnoreCase(path, ".html") || endsWithIgnoreCase(path, ".htm")) {
- tokenizeHtml(from, Files.toString(file, UTF_8));
- } else if (endsWithIgnoreCase(path, ".css")) {
- tokenizeCss(from, Files.toString(file, UTF_8));
- } else if (endsWithIgnoreCase(path, ".js")) {
- tokenizeJs(from, Files.toString(file, UTF_8));
- } else if (file.isFile() && !LintUtils.isBitmapFile(file)) {
- tokenizeUnknownBinary(file);
- }
- }
- }
- }
- }
-
- private void recordMapping(@Nullable File mapping) throws IOException {
- if (mapping == null || !mapping.exists()) {
- return;
- }
- final String ARROW = " -> ";
- final String RESOURCE = ".R$";
- for (String line : Files.readLines(mapping, UTF_8)) {
- if (line.startsWith(" ") || line.startsWith("\t")) {
- continue;
- }
- int index = line.indexOf(RESOURCE);
- if (index == -1) {
- continue;
- }
- int arrow = line.indexOf(ARROW, index + 3);
- if (arrow == -1) {
- continue;
- }
- String typeName = line.substring(index + RESOURCE.length(), arrow);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type == null) {
- continue;
- }
- int end = line.indexOf(':', arrow + ARROW.length());
- if (end == -1) {
- end = line.length();
- }
- String target = line.substring(arrow + ARROW.length(), end).trim();
- String ownerName = target.replace('.', '/');
- mResourceClassOwners.put(ownerName, type);
- }
- }
-
- private void recordManifestUsages(File manifest)
- throws IOException, ParserConfigurationException, SAXException {
- String xml = Files.toString(manifest, UTF_8);
- Document document = XmlUtils.parseDocument(xml, true);
- recordManifestUsages(document.getDocumentElement());
- }
-
- private void recordXmlResourcesUsages(@NonNull File file, boolean isDefaultFolder,
- @Nullable Resource from)
- throws IOException, ParserConfigurationException, SAXException {
- String xml = Files.toString(file, UTF_8);
- Document document = XmlUtils.parseDocument(xml, true);
- recordResourceReferences(file, isDefaultFolder, document.getDocumentElement(), from);
- }
-
- private void tokenizeHtml(@Nullable Resource from, @NonNull String html) {
- // Look for
- // (1) URLs of the form /android_res/drawable/foo.ext
- // which we will use to keep R.drawable.foo
- // and
- // (2) Filenames. If the web content is loaded with something like
- // WebView.loadDataWithBaseURL("file:///android_res/drawable/", ...)
- // this is similar to Resources#getIdentifier handling where all
- // *potentially* aliased filenames are kept to play it safe.
-
- // Simple HTML tokenizer
- int length = html.length();
- final int STATE_TEXT = 1;
- final int STATE_SLASH = 2;
- final int STATE_ATTRIBUTE_NAME = 3;
- final int STATE_BEFORE_TAG = 4;
- final int STATE_IN_TAG = 5;
- final int STATE_BEFORE_ATTRIBUTE = 6;
- final int STATE_ATTRIBUTE_BEFORE_EQUALS = 7;
- final int STATE_ATTRIBUTE_AFTER_EQUALS = 8;
- final int STATE_ATTRIBUTE_VALUE_NONE = 9;
- final int STATE_ATTRIBUTE_VALUE_SINGLE = 10;
- final int STATE_ATTRIBUTE_VALUE_DOUBLE = 11;
- final int STATE_CLOSE_TAG = 12;
-
- int state = STATE_TEXT;
- int offset = 0;
- int valueStart = 0;
- int tagStart = 0;
- String tag = null;
- String attribute = null;
- int attributeStart = 0;
- int prev = -1;
- while (offset < length) {
- if (offset == prev) {
- // Purely here to prevent potential bugs in the state machine from looping
- // infinitely
- offset++;
- }
- prev = offset;
-
-
- char c = html.charAt(offset);
-
- // MAke sure I handle doctypes properly.
- // Make sure I handle cdata properly.
- // Oh and what about <style> tags? tokenize everything inside as CSS!
- // ANd <script> tag content as js!
- switch (state) {
- case STATE_TEXT: {
- if (c == '<') {
- state = STATE_SLASH;
- offset++;
- continue;
- }
-
- // Other text is just ignored
- offset++;
- break;
- }
-
- case STATE_SLASH: {
- if (c == '!') {
- if (html.startsWith("!--", offset)) {
- // Comment
- int end = html.indexOf("-->", offset + 3);
- if (end == -1) {
- offset = length;
- break;
- }
- offset = end + 3;
- continue;
- } else if (html.startsWith("![CDATA[", offset)) {
- // Skip CDATA text content; HTML text is irrelevant to this tokenizer
- // anyway
- int end = html.indexOf("]]>", offset + 8);
- if (end == -1) {
- offset = length;
- break;
- }
- offset = end + 3;
- continue;
- }
- } else if (c == '/') {
- state = STATE_CLOSE_TAG;
- offset++;
- continue;
- } else if (c == '?') {
- // XML Prologue
- int end = html.indexOf('>', offset + 2);
- if (end == -1) {
- offset = length;
- break;
- }
- offset = end + 1;
- continue;
- }
- state = STATE_IN_TAG;
- tagStart = offset;
- break;
- }
-
- case STATE_CLOSE_TAG: {
- if (c == '>') {
- state = STATE_TEXT;
- }
- offset++;
- break;
- }
-
- case STATE_BEFORE_TAG: {
- if (!Character.isWhitespace(c)) {
- state = STATE_IN_TAG;
- tagStart = offset;
- }
- // (For an end tag we'll include / in the tag name here)
- offset++;
- break;
- }
- case STATE_IN_TAG: {
- if (Character.isWhitespace(c)) {
- state = STATE_BEFORE_ATTRIBUTE;
- tag = html.substring(tagStart, offset).trim();
- } else if (c == '>') {
- tag = html.substring(tagStart, offset).trim();
- endHtmlTag(from, html, offset, tag);
- state = STATE_TEXT;
- }
- offset++;
- break;
- }
- case STATE_BEFORE_ATTRIBUTE: {
- if (c == '>') {
- endHtmlTag(from, html, offset, tag);
- state = STATE_TEXT;
- } else //noinspection StatementWithEmptyBody
- if (c == '/') {
- // we expect an '>' next to close the tag
- } else if (!Character.isWhitespace(c)) {
- state = STATE_ATTRIBUTE_NAME;
- attributeStart = offset;
- }
- offset++;
- break;
- }
- case STATE_ATTRIBUTE_NAME: {
- if (c == '>') {
- endHtmlTag(from, html, offset, tag);
- state = STATE_TEXT;
- } else if (c == '=') {
- attribute = html.substring(attributeStart, offset);
- state = STATE_ATTRIBUTE_AFTER_EQUALS;
- } else if (Character.isWhitespace(c)) {
- attribute = html.substring(attributeStart, offset);
- state = STATE_ATTRIBUTE_BEFORE_EQUALS;
- }
- offset++;
- break;
- }
- case STATE_ATTRIBUTE_BEFORE_EQUALS: {
- if (c == '=') {
- state = STATE_ATTRIBUTE_AFTER_EQUALS;
- } else if (c == '>') {
- endHtmlTag(from, html, offset, tag);
- state = STATE_TEXT;
- } else if (!Character.isWhitespace(c)) {
- // Attribute value not specified (used for some boolean attributes)
- state = STATE_ATTRIBUTE_NAME;
- attributeStart = offset;
- }
- offset++;
- break;
- }
-
- case STATE_ATTRIBUTE_AFTER_EQUALS: {
- if (c == '\'') {
- // a='b'
- state = STATE_ATTRIBUTE_VALUE_SINGLE;
- valueStart = offset + 1;
- } else if (c == '"') {
- // a="b"
- state = STATE_ATTRIBUTE_VALUE_DOUBLE;
- valueStart = offset + 1;
- } else if (!Character.isWhitespace(c)) {
- // a=b
- state = STATE_ATTRIBUTE_VALUE_NONE;
- valueStart = offset + 1;
- }
- offset++;
- break;
- }
-
- case STATE_ATTRIBUTE_VALUE_SINGLE: {
- if (c == '\'') {
- state = STATE_BEFORE_ATTRIBUTE;
- recordHtmlAttributeValue(from, tag, attribute,
- html.substring(valueStart, offset));
- }
- offset++;
- break;
- }
- case STATE_ATTRIBUTE_VALUE_DOUBLE: {
- if (c == '"') {
- state = STATE_BEFORE_ATTRIBUTE;
- recordHtmlAttributeValue(from, tag, attribute,
- html.substring(valueStart, offset));
- }
- offset++;
- break;
- }
- case STATE_ATTRIBUTE_VALUE_NONE: {
- if (c == '>') {
- recordHtmlAttributeValue(from, tag, attribute,
- html.substring(valueStart, offset));
- endHtmlTag(from, html, offset, tag);
- state = STATE_TEXT;
- } else if (Character.isWhitespace(c)) {
- state = STATE_BEFORE_ATTRIBUTE;
- recordHtmlAttributeValue(from, tag, attribute,
- html.substring(valueStart, offset));
- }
- offset++;
- break;
- }
- default:
- assert false : state;
- }
- }
- }
-
- private void endHtmlTag(@Nullable Resource from, @NonNull String html, int offset,
- @Nullable String tag) {
- if ("script".equals(tag)) {
- int end = html.indexOf("</script>", offset + 1);
- if (end != -1) {
- // Attempt to tokenize the text as JavaScript
- String js = html.substring(offset + 1, end);
- tokenizeJs(from, js);
- }
- } else if ("style".equals(tag)) {
- int end = html.indexOf("</style>", offset + 1);
- if (end != -1) {
- // Attempt to tokenize the text as CSS
- String css = html.substring(offset + 1, end);
- tokenizeCss(from, css);
- }
- }
- }
-
- private void tokenizeJs(@Nullable Resource from, @NonNull String js) {
- // Simple JavaScript tokenizer: only looks for literal strings,
- // and records those as string references
- int length = js.length();
- final int STATE_INIT = 1;
- final int STATE_SLASH = 2;
- final int STATE_STRING_DOUBLE = 3;
- final int STATE_STRING_DOUBLE_QUOTED = 4;
- final int STATE_STRING_SINGLE = 5;
- final int STATE_STRING_SINGLE_QUOTED = 6;
-
- int state = STATE_INIT;
- int offset = 0;
- int stringStart = 0;
- int prev = -1;
- while (offset < length) {
- if (offset == prev) {
- // Purely here to prevent potential bugs in the state machine from looping
- // infinitely
- offset++;
- }
- prev = offset;
-
- char c = js.charAt(offset);
- switch (state) {
- case STATE_INIT: {
- if (c == '/') {
- state = STATE_SLASH;
- } else if (c == '"') {
- stringStart = offset + 1;
- state = STATE_STRING_DOUBLE;
- } else if (c == '\'') {
- stringStart = offset + 1;
- state = STATE_STRING_SINGLE;
- }
- offset++;
- break;
- }
- case STATE_SLASH: {
- if (c == '*') {
- // Comment block
- state = STATE_INIT;
- int end = js.indexOf("*/", offset + 1);
- if (end == -1) {
- offset = length; // unterminated
- break;
- }
- offset = end + 2;
- continue;
- } else if (c == '/') {
- // Line comment
- state = STATE_INIT;
- int end = js.indexOf('\n', offset + 1);
- if (end == -1) {
- offset = length;
- break;
- }
- offset = end + 1;
- continue;
- } else {
- // division - just continue
- state = STATE_INIT;
- offset++;
- break;
- }
- }
- case STATE_STRING_DOUBLE: {
- if (c == '"') {
- recordJsString(js.substring(stringStart, offset));
- state = STATE_INIT;
- } else if (c == '\\') {
- state = STATE_STRING_DOUBLE_QUOTED;
- }
- offset++;
- break;
- }
- case STATE_STRING_DOUBLE_QUOTED: {
- state = STATE_STRING_DOUBLE;
- offset++;
- break;
- }
- case STATE_STRING_SINGLE: {
- if (c == '\'') {
- recordJsString(js.substring(stringStart, offset));
- state = STATE_INIT;
- } else if (c == '\\') {
- state = STATE_STRING_SINGLE_QUOTED;
- }
- offset++;
- break;
- }
- case STATE_STRING_SINGLE_QUOTED: {
- state = STATE_STRING_SINGLE;
- offset++;
- break;
- }
- default:
- assert false : state;
- }
- }
- }
-
- private void tokenizeCss(@Nullable Resource from, @NonNull String css) {
- // Simple CSS tokenizer: Only looks for URL references, and records those
- // filenames. Skips everything else (unrelated to images).
- int length = css.length();
- final int STATE_INIT = 1;
- final int STATE_SLASH = 2;
- int state = STATE_INIT;
- int offset = 0;
- int prev = -1;
- while (offset < length) {
- if (offset == prev) {
- // Purely here to prevent potential bugs in the state machine from looping
- // infinitely
- offset++;
- }
- prev = offset;
-
- char c = css.charAt(offset);
- switch (state) {
- case STATE_INIT: {
- if (c == '/') {
- state = STATE_SLASH;
- } else if (c == 'u' && css.startsWith("url(", offset) && offset > 0) {
- char prevChar = css.charAt(offset-1);
- if (Character.isWhitespace(prevChar) || prevChar == ':') {
- int end = css.indexOf(')', offset);
- offset += 4; // skip url(
- while (offset < length && Character.isWhitespace(css.charAt(offset))) {
- offset++;
- }
- if (end != -1 && end > offset + 1) {
- while (end > offset
- && Character.isWhitespace(css.charAt(end - 1))) {
- end--;
- }
- if ((css.charAt(offset) == '"'
- && css.charAt(end - 1) == '"')
- || (css.charAt(offset) == '\''
- && css.charAt(end - 1) == '\'')) {
- // Strip " or '
- offset++;
- end--;
- }
- recordCssUrl(from, css.substring(offset, end).trim());
- }
- offset = end + 1;
- continue;
- }
-
- }
- offset++;
- break;
- }
- case STATE_SLASH: {
- if (c == '*') {
- // CSS comment? Skip the whole block rather than staying within the
- // character tokenizer.
- int end = css.indexOf("*/", offset + 1);
- if (end == -1) {
- offset = length;
- break;
- }
- offset = end + 2;
- continue;
- }
- state = STATE_INIT;
- offset++;
- break;
- }
- default:
- assert false : state;
- }
- }
- }
-
- private static byte[] sAndroidResBytes;
-
- /** Look through binary/unknown files looking for resource URLs */
- private void tokenizeUnknownBinary(@NonNull File file) {
- try {
- if (sAndroidResBytes == null) {
- sAndroidResBytes = ANDROID_RES.getBytes(SdkConstants.UTF_8);
- }
- byte[] bytes = Files.toByteArray(file);
- int index = 0;
- while (index != -1) {
- index = indexOf(bytes, sAndroidResBytes, index);
- if (index != -1) {
- index += sAndroidResBytes.length;
-
- // Find the end of the URL
- int begin = index;
- int end = begin;
- for (; end < bytes.length; end++) {
- byte c = bytes[end];
- if (c != '/' && !Character.isJavaIdentifierPart((char)c)) {
- // android_res/raw/my_drawable.png => @raw/my_drawable
- String url = "@" + new String(bytes, begin, end - begin, UTF_8);
- markReachable(getResourceFromUrl(url));
- break;
- }
- }
- }
- }
- } catch (IOException e) {
- // Ignore
- }
- }
-
- /**
- * Returns the index of the given target array in the first array, looking from the given
- * index
- */
- private static int indexOf(byte[] array, byte[] target, int fromIndex) {
- outer:
- for (int i = fromIndex; i < array.length - target.length + 1; i++) {
- for (int j = 0; j < target.length; j++) {
- if (array[i + j] != target[j]) {
- continue outer;
- }
- }
- return i;
- }
- return -1;
- }
-
- /** Look through text files of unknown structure looking for resource URLs */
- private void tokenizeUnknownText(@NonNull String text) {
- int index = 0;
- while (index != -1) {
- index = text.indexOf(ANDROID_RES, index);
- if (index != -1) {
- index += ANDROID_RES.length();
-
- // Find the end of the URL
- int begin = index;
- int end = begin;
- int length = text.length();
- for (; end < length; end++) {
- char c = text.charAt(end);
- if (c != '/' && !Character.isJavaIdentifierPart(c)) {
- // android_res/raw/my_drawable.png => @raw/my_drawable
- markReachable(getResourceFromUrl("@" + text.substring(begin, end)));
- break;
- }
- }
- }
- }
- }
-
- private void recordCssUrl(@Nullable Resource from, @NonNull String value) {
- if (!referencedUrl(from, value)) {
- referencedString(value);
- mFoundWebContent = true;
- }
- }
-
- /**
- * See if the given URL is a URL that we can resolve to a specific resource; if so,
- * record it and return true, otherwise returns false.
- */
- private boolean referencedUrl(@Nullable Resource from, @NonNull String url) {
- Resource resource = getResourceFromFilePath(url);
- if (resource != null) {
- if (from != null) {
- from.addReference(resource);
- } else {
- // We don't have an inclusion context, so just assume this resource is reachable
- markReachable(resource);
- }
- return true;
- }
-
- return false;
- }
-
- private void recordHtmlAttributeValue(@Nullable Resource from, @Nullable String tagName,
- @Nullable String attribute, @NonNull String value) {
- if ("href".equals(attribute) || "src".equals(attribute)) {
- // In general we'd need to unescape the HTML here (e.g. remove entities) but
- // those wouldn't be valid characters in the resource name anyway
- if (!referencedUrl(from, value)) {
- referencedString(value);
- mFoundWebContent = true;
- }
-
- // If this document includes another, record the reachability of that script/resource
- if (from != null) {
- from.addReference(getResourceFromFilePath(attribute));
- }
- }
- }
-
- private void recordJsString(@NonNull String string) {
- referencedString(string);
- }
-
- @Nullable
- private Resource getResource(@NonNull ResourceType type, @NonNull String name) {
- Map<String, Resource> nameMap = mTypeToName.get(type);
- if (nameMap != null) {
- return nameMap.get(getFieldName(name));
- }
- return null;
- }
-
- @Nullable
- private Resource getResourceFromUrl(@NonNull String possibleUrlReference) {
- ResourceUrl url = ResourceUrl.parse(possibleUrlReference);
- if (url != null && !url.framework) {
- return getResource(url.type, url.name);
- }
-
- return null;
- }
-
- @Nullable
- private Resource getResourceFromFilePath(@NonNull String url) {
- int nameSlash = url.lastIndexOf('/');
- if (nameSlash == -1) {
- return null;
- }
-
- // Look for
- // (1) a full resource URL: /android_res/type/name.ext
- // (2) a partial URL that uniquely identifies a given resource: drawable/name.ext
- // e.g. file:///android_res/drawable/bar.png
- int androidRes = url.indexOf(ANDROID_RES);
- if (androidRes != -1) {
- androidRes += ANDROID_RES.length();
- int slash = url.indexOf('/', androidRes);
- if (slash != -1) {
- String folderName = url.substring(androidRes, slash);
- ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
- if (folderType != null) {
- List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
- folderType);
- if (!types.isEmpty()) {
- ResourceType type = types.get(0);
- int nameBegin = slash + 1;
- int dot = url.indexOf('.', nameBegin);
- String name = url.substring(nameBegin, dot != -1 ? dot : url.length());
- return getResource(type, name);
- }
- }
- }
- }
-
- // Some other relative path. Just look from the end:
- int typeSlash = url.lastIndexOf('/', nameSlash - 1);
- ResourceType type = ResourceType.getEnum(url.substring(typeSlash + 1, nameSlash));
- if (type != null) {
- int nameBegin = nameSlash + 1;
- int dot = url.indexOf('.', nameBegin);
- String name = url.substring(nameBegin, dot != -1 ? dot : url.length());
- return getResource(type, name);
- }
-
- return null;
- }
-
- private void recordManifestUsages(Node node) {
- short nodeType = node.getNodeType();
- if (nodeType == Node.ELEMENT_NODE) {
- Element element = (Element) node;
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attr = (Attr) attributes.item(i);
- markReachable(getResourceFromUrl(attr.getValue()));
- }
- } else if (nodeType == Node.TEXT_NODE) {
- // Does this apply to any manifests??
- String text = node.getNodeValue().trim();
- markReachable(getResourceFromUrl(text));
- }
-
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- recordManifestUsages(child);
- }
- }
-
-
- private void recordResourceReferences(@NonNull File file, boolean isDefaultFolder,
- @NonNull Node node, @Nullable Resource from) {
- short nodeType = node.getNodeType();
- if (nodeType == Node.ELEMENT_NODE) {
- Element element = (Element) node;
- if (from != null) {
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attr = (Attr) attributes.item(i);
-
- // Ignore tools: namespace attributes, unless it's
- // a keep attribute
- if (TOOLS_URI.equals(attr.getNamespaceURI())) {
- handleToolsAttribute(attr);
- // Skip all other tools: attributes
- continue;
- }
-
- Resource resource = getResourceFromUrl(attr.getValue());
- if (resource != null) {
- from.addReference(resource);
- }
- }
-
- // Android Wear. We *could* limit ourselves to only doing this in files
- // referenced from a manifest meta-data element, e.g.
- // <meta-data android:name="com.google.android.wearable.beta.app"
- // android:resource="@xml/wearable_app_desc"/>
- // but given that that property has "beta" in the name, it seems likely
- // to change and therefore hardcoding it for that key risks breakage
- // in the future.
- if ("rawPathResId".equals(element.getTagName())) {
- StringBuilder sb = new StringBuilder();
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Element.TEXT_NODE
- || child.getNodeType() == Element.CDATA_SECTION_NODE) {
- sb.append(child.getNodeValue());
- }
- }
- if (sb.length() > 0) {
- Resource resource = getResource(ResourceType.RAW, sb.toString().trim());
- from.addReference(resource);
- }
- }
- } else {
- // Look for keep attributes everywhere else since they don't require a source
- handleToolsAttribute(element.getAttributeNodeNS(TOOLS_URI, ATTR_KEEP));
- handleToolsAttribute(element.getAttributeNodeNS(TOOLS_URI, ATTR_DISCARD));
- handleToolsAttribute(element.getAttributeNodeNS(TOOLS_URI, ATTR_SHRINK_MODE));
- }
-
- Resource definition = getResource(element);
- if (definition != null) {
- from = definition;
- definition.addLocation(file);
- if (isDefaultFolder) {
- definition.hasDefault = true;
- }
- }
-
- String tagName = element.getTagName();
- if (TAG_STYLE.equals(tagName)) {
- if (element.hasAttribute(ATTR_PARENT)) {
- String parent = element.getAttribute(ATTR_PARENT);
- if (!parent.isEmpty() && !parent.startsWith(ANDROID_STYLE_RESOURCE_PREFIX) &&
- !parent.startsWith(PREFIX_ANDROID)) {
- String parentStyle = parent;
- if (!parentStyle.startsWith(STYLE_RESOURCE_PREFIX)) {
- parentStyle = STYLE_RESOURCE_PREFIX + parentStyle;
- }
- Resource ps = getResourceFromUrl(getFieldName(parentStyle));
- if (ps != null && definition != null) {
- definition.addReference(ps);
- }
- }
- } else {
- // Implicit parent styles by name
- String name = getFieldName(element);
- while (true) {
- int index = name.lastIndexOf('_');
- if (index != -1) {
- name = name.substring(0, index);
- Resource ps = getResourceFromUrl(
- STYLE_RESOURCE_PREFIX + getFieldName(name));
- if (ps != null && definition != null) {
- definition.addReference(ps);
- }
- } else {
- break;
- }
- }
- }
- }
-
- if (TAG_ITEM.equals(tagName)) {
- // In style? If so the name: attribute can be a reference
- if (element.getParentNode() != null
- && element.getParentNode().getNodeName().equals(TAG_STYLE)) {
- String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (!name.isEmpty() && !name.startsWith("android:")) {
- Resource resource = getResource(ResourceType.ATTR, name);
- if (definition == null) {
- Element style = (Element) element.getParentNode();
- definition = getResource(style);
- if (definition != null) {
- from = definition;
- definition.addReference(resource);
- }
- }
- }
- }
- }
- } else if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
- String text = node.getNodeValue().trim();
- Resource textResource = getResourceFromUrl(getFieldName(text));
- if (textResource != null && from != null) {
- from.addReference(textResource);
- }
- }
-
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- recordResourceReferences(file, isDefaultFolder, child, from);
- }
- }
-
- private void handleToolsAttribute(@Nullable Attr attr) {
- if (attr == null) {
- return;
- }
- String localName = attr.getLocalName();
- String value = attr.getValue();
- if (ATTR_KEEP.equals(localName)) {
- handleKeepAttribute(value);
- } else if (ATTR_DISCARD.equals(localName)) {
- handleRemoveAttribute(value);
- } else if (ATTR_SHRINK_MODE.equals(localName)) {
- if (VALUE_STRICT.equals(value)) {
- mGuessKeep = false;
- } else if (VALUE_SAFE.equals(value)) {
- mGuessKeep = true;
- } else if (mDebug) {
- System.out.println("Ignoring unknown " + ATTR_SHRINK_MODE + " " + value);
- }
- if (mDebug) {
- System.out.println("Setting shrink mode to " + value);
- }
- }
- }
-
- public static String getFieldName(@NonNull String styleName) {
- return styleName.replace('.', '_').replace('-', '_').replace(':', '_');
- }
-
- /**
- * Marks the given resource (if non-null) as reachable, and returns true if
- * this is the first time the resource is marked reachable
- */
- private static boolean markReachable(@Nullable Resource resource) {
- if (resource != null) {
- boolean wasReachable = resource.reachable;
- resource.reachable = true;
- return !wasReachable;
- }
-
- return false;
- }
-
- private static void markUnreachable(@Nullable Resource resource) {
- if (resource != null) {
- resource.reachable = false;
- }
- }
-
- /**
- * Called for a tools:keep attribute containing a resource URL where that resource name
- * is not referencing a known resource
- *
- * @param value The keep value
- */
- private void handleKeepAttribute(@NonNull String value) {
- // Handle comma separated lists of URLs and globs
- if (value.indexOf(',') != -1) {
- for (String portion : Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
- handleKeepAttribute(portion);
- }
- return;
- }
-
- ResourceUrl url = ResourceUrl.parse(value);
- if (url == null || url.framework) {
- return;
- }
-
- Resource resource = getResource(url.type, url.name);
- if (resource != null) {
- if (mDebug) {
- System.out.println("Marking " + resource + " used because it "
- + "matches keep attribute " + value);
- }
- markReachable(resource);
- } else if (url.name.contains("*") || url.name.contains("?")) {
- // Look for globbing patterns
- String regexp = DefaultConfiguration.globToRegexp(getFieldName(url.name));
- try {
- Pattern pattern = Pattern.compile(regexp);
- Map<String, Resource> nameMap = mTypeToName.get(url.type);
- if (nameMap != null) {
- for (Resource r : nameMap.values()) {
- if (pattern.matcher(r.name).matches()) {
- if (mDebug) {
- System.out.println("Marking " + r + " used because it "
- + "matches keep globbing pattern " + url.name);
- }
-
- markReachable(r);
- }
- }
- }
- } catch (PatternSyntaxException ignored) {
- if (mDebug) {
- System.out.println("Could not compute keep globbing pattern for " +
- url.name + ": tried regexp " + regexp + "(" + ignored + ")");
- }
- }
- }
- }
-
- private void handleRemoveAttribute(@NonNull String value) {
- // Handle comma separated lists of URLs and globs
- if (value.indexOf(',') != -1) {
- for (String portion : Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
- handleRemoveAttribute(portion);
- }
- return;
- }
-
- ResourceUrl url = ResourceUrl.parse(value);
- if (url == null || url.framework) {
- return;
- }
-
- Resource resource = getResource(url.type, url.name);
- if (resource != null) {
- if (mDebug) {
- System.out.println("Marking " + resource + " used because it "
- + "matches remove attribute " + value);
- }
- markUnreachable(resource);
- } else if (url.name.contains("*") || url.name.contains("?")) {
- // Look for globbing patterns
- String regexp = DefaultConfiguration.globToRegexp(getFieldName(url.name));
- try {
- Pattern pattern = Pattern.compile(regexp);
- Map<String, Resource> nameMap = mTypeToName.get(url.type);
- if (nameMap != null) {
- for (Resource r : nameMap.values()) {
- if (pattern.matcher(r.name).matches()) {
- if (mDebug) {
- System.out.println("Marking " + r + " used because it "
- + "matches remove globbing pattern " + url.name);
- }
-
- markUnreachable(r);
- }
- }
- }
- } catch (PatternSyntaxException ignored) {
- if (mDebug) {
- System.out.println("Could not compute remove globbing pattern for " +
- url.name + ": tried regexp " + regexp + "(" + ignored + ")");
- }
- }
- }
- }
-
- private Set<String> mStrings;
- private boolean mFoundGetIdentifier;
- private boolean mFoundWebContent;
-
- private void referencedString(@NonNull String string) {
- // See if the string is at all eligible; ignore strings that aren't
- // identifiers (has java identifier chars and nothing but .:/), or are empty or too long
- // We also allow "%", used for formatting strings.
- if (string.isEmpty() || string.length() > 80) {
- return;
- }
- boolean haveIdentifierChar = false;
- for (int i = 0, n = string.length(); i < n; i++) {
- char c = string.charAt(i);
- boolean identifierChar = Character.isJavaIdentifierPart(c);
- if (!identifierChar && c != '.' && c != ':' && c != '/' && c != '%') {
- // .:/ are for the fully qualified resource names, or for resource URLs or
- // relative file names
- return;
- } else if (identifierChar) {
- haveIdentifierChar = true;
- }
- }
- if (!haveIdentifierChar) {
- return;
- }
-
- if (mStrings == null) {
- mStrings = Sets.newHashSetWithExpectedSize(300);
- }
- mStrings.add(string);
-
- if (!mFoundWebContent && string.contains(ANDROID_RES)) {
- mFoundWebContent = true;
- }
- }
-
- private void recordUsages(File jarFile) throws IOException {
- if (!jarFile.exists()) {
- return;
- }
- ZipInputStream zis = null;
- try {
- FileInputStream fis = new FileInputStream(jarFile);
- try {
- zis = new ZipInputStream(fis);
- ZipEntry entry = zis.getNextEntry();
- while (entry != null) {
- String name = entry.getName();
- if (name.endsWith(DOT_CLASS) &&
- // Skip resource type classes like R$drawable; they will
- // reference the integer id's we're looking for, but these aren't
- // actual usages we need to track; if somebody references the
- // field elsewhere, we'll catch that
- !isResourceClass(name)) {
- byte[] bytes = ByteStreams.toByteArray(zis);
- if (bytes != null) {
- ClassReader classReader = new ClassReader(bytes);
- classReader.accept(new UsageVisitor(jarFile, name),
- SKIP_DEBUG | SKIP_FRAMES);
- }
- }
-
- entry = zis.getNextEntry();
- }
- } finally {
- Closeables.close(fis, true);
- }
- } finally {
- Closeables.close(zis, true);
- }
- }
-
- /** Returns whether the given class path points to an aapt-generated compiled R class */
- @VisibleForTesting
- static boolean isResourceClass(@NonNull String name) {
- assert name.endsWith(DOT_CLASS) : name;
- int index = name.lastIndexOf('/');
- if (index != -1 && name.startsWith("R$", index + 1)) {
- String typeName = name.substring(index + 3, name.length() - DOT_CLASS.length());
- return ResourceType.getEnum(typeName) != null;
- }
-
- return false;
- }
-
- private void gatherResourceValues(File file) throws IOException {
- if (file.isDirectory()) {
- File[] children = file.listFiles();
- if (children != null) {
- for (File child : children) {
- gatherResourceValues(child);
- }
- }
- } else if (file.isFile() && file.getName().equals(SdkConstants.FN_RESOURCE_CLASS)) {
- parseResourceClass(file);
- }
- }
-
- // TODO: Use Lombok/ECJ here
- private void parseResourceClass(File file) throws IOException {
- String s = Files.toString(file, UTF_8);
- // Simple parser which handles only aapt's special R output
- String pkg = null;
- int index = s.indexOf("package ");
- if (index != -1) {
- int end = s.indexOf(';', index);
- pkg = s.substring(index + "package ".length(), end).trim().replace('.', '/');
- }
- index = 0;
- int length = s.length();
- String classDeclaration = "public static final class ";
- while (true) {
- index = s.indexOf(classDeclaration, index);
- if (index == -1) {
- break;
- }
- int start = index + classDeclaration.length();
- int end = s.indexOf(' ', start);
- if (end == -1) {
- break;
- }
- String typeName = s.substring(start, end);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type == null) {
- break;
- }
-
- if (pkg != null) {
- mResourceClassOwners.put(pkg + "/R$" + type.getName(), type);
- }
-
- index = end;
-
- // Find next declaration
- for (; index < length - 1; index++) {
- char c = s.charAt(index);
- if (Character.isWhitespace(c)) {
- //noinspection UnnecessaryContinue
- continue;
- } else if (c == '/') {
- char next = s.charAt(index + 1);
- if (next == '*') {
- // Scan forward to comment end
- end = index + 2;
- while (end < length -2) {
- c = s.charAt(end);
- if (c == '*' && s.charAt(end + 1) == '/') {
- end++;
- break;
- } else {
- end++;
- }
- }
- index = end;
- } else if (next == '/') {
- // Scan forward to next newline
- assert false : s.substring(index - 1, index + 50); // we don't put line comments in R files
- } else {
- assert false : s.substring(index - 1, index + 50); // unexpected division
- }
- } else if (c == 'p' && s.startsWith("public ", index)) {
- if (type == ResourceType.STYLEABLE) {
- start = s.indexOf(" int", index);
- if (s.startsWith(" int[] ", start)) {
- end = s.indexOf('=', start);
- assert end != -1;
- String styleable = s.substring(start, end).trim();
- addResource(ResourceType.DECLARE_STYLEABLE, styleable, null);
-
- // TODO: Read in all the action bar ints!
- // For now, we're simply treating all R.attr fields as used
- } else if (s.startsWith(" int ")) {
- // Read these fields in and correlate with the attr R's. Actually
- // we don't need this for anything; the local attributes are
- // found by the R attr thing. I just need to record the class
- // (style).
- // public static final int ActionBar_background = 10;
- // ignore - jump to end
- index = s.indexOf(';', index);
- if (index == -1) {
- break;
- }
- // For now, we're simply treating all R.attr fields as used
- }
- } else {
- start = s.indexOf(" int ", index);
- if (start != -1) {
- start += " int ".length();
- // e.g. abc_fade_in=0x7f040000;
- end = s.indexOf('=', start);
- assert end != -1;
- String name = s.substring(start, end).trim();
- start = end + 1;
- end = s.indexOf(';', start);
- assert end != -1;
- String value = s.substring(start, end).trim();
- addResource(type, name, value);
- }
- }
- } else if (c == '}') {
- // Done with resource class
- break;
- }
- }
- }
- }
-
- private void addResource(@NonNull ResourceType type, @NonNull String name,
- @Nullable String value) {
- int realValue = value != null ? Integer.decode(value) : -1;
- Resource resource = getResource(type, name);
- if (resource != null) {
- //noinspection VariableNotUsedInsideIf
- if (value != null) {
- if (resource.value == -1) {
- resource.value = realValue;
- } else {
- assert realValue == resource.value;
- }
- }
- return;
- }
-
- resource = new Resource(type, name, realValue);
- mResources.add(resource);
- if (realValue != -1) {
- mValueToResource.put(realValue, resource);
- }
- Map<String, Resource> nameMap = mTypeToName.get(type);
- if (nameMap == null) {
- nameMap = Maps.newHashMapWithExpectedSize(30);
- mTypeToName.put(type, nameMap);
- }
- nameMap.put(name, resource);
-
- // TODO: Assert that we don't set the same resource multiple times to different values.
- // Could happen if you pass in stale data!
- }
-
- public int getUnusedResourceCount() {
- return mUnused.size();
- }
-
- @VisibleForTesting
- List<Resource> getAllResources() {
- return mResources;
- }
-
- public static class Resource {
- /** Type of resource */
- public ResourceType type;
- /** Name of resource */
- public String name;
- /** Integer id location */
- public int value;
- /** Whether this resource can be reached from one of the roots (manifest, code) */
- public boolean reachable;
- /** Whether this resource has a default definition (e.g. present in a resource folder
- * with no qualifiers). For id references, an inline definition (@+id) does not count as
- * a default definition.*/
- public boolean hasDefault;
- /** Resources this resource references. For example, a layout can reference another via
- * an include; a style reference in a layout references that layout style, and so on. */
- public List<Resource> references;
- public final List<File> declarations = Lists.newArrayList();
-
- private Resource(ResourceType type, String name, int value) {
- this.type = type;
- this.name = name;
- this.value = value;
- }
-
- @Override
- public String toString() {
- return type + ":" + name + ":" + value;
- }
-
- @SuppressWarnings("RedundantIfStatement") // Generated by IDE
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- Resource resource = (Resource) o;
-
- if (name != null ? !name.equals(resource.name) : resource.name != null) {
- return false;
- }
- if (type != resource.type) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = type != null ? type.hashCode() : 0;
- result = 31 * result + (name != null ? name.hashCode() : 0);
- return result;
- }
-
- public void addLocation(@NonNull File file) {
- declarations.add(file);
- }
-
- public void addReference(@Nullable Resource resource) {
- if (resource != null) {
- if (references == null) {
- references = Lists.newArrayList();
- } else if (references.contains(resource)) {
- return;
- }
- references.add(resource);
- }
- }
-
- public String getUrl() {
- return '@' + type.getName() + '/' + name;
- }
-
- public boolean isRelevantType() {
- return type != ResourceType.ID; // && getFolderType() != ResourceFolderType.VALUES;
- }
- }
-
- /**
- * Class visitor responsible for looking for resource references in code.
- * It looks for R.type.name references (as well as inlined constants for these,
- * in the case of non-library code), as well as looking both for Resources#getIdentifier
- * calls and recording string literals, used to handle dynamic lookup of resources.
- */
- private class UsageVisitor extends ClassVisitor {
- private final File mJarFile;
- private final String mCurrentClass;
-
- public UsageVisitor(File jarFile, String name) {
- super(Opcodes.ASM5);
- mJarFile = jarFile;
- mCurrentClass = name;
- }
-
- @Override
- public MethodVisitor visitMethod(int access, final String name,
- String desc, String signature, String[] exceptions) {
- return new MethodVisitor(Opcodes.ASM5) {
- @Override
- public void visitLdcInsn(Object cst) {
- handleCodeConstant(cst, "ldc");
- }
-
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- if (opcode == Opcodes.GETSTATIC) {
- ResourceType type = mResourceClassOwners.get(owner);
- if (type != null) {
- Resource resource = getResource(type, name);
- if (resource != null) {
- markReachable(resource);
- }
- }
- }
- }
-
- @Override
- public void visitMethodInsn(int opcode, String owner, String name,
- String desc, boolean itf) {
- super.visitMethodInsn(opcode, owner, name, desc, itf);
- if (owner.equals("android/content/res/Resources")
- && name.equals("getIdentifier")
- && desc.equals(
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I")) {
- mFoundGetIdentifier = true;
- // TODO: Check previous instruction and see if we can find a literal
- // String; if so, we can more accurately dispatch the resource here
- // rather than having to check the whole string pool!
- }
- if (owner.equals("android/webkit/WebView") && name.startsWith("load")) {
- mFoundWebContent = true;
- }
- }
-
- @Override
- public AnnotationVisitor visitAnnotationDefault() {
- return new AnnotationUsageVisitor();
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- return new AnnotationUsageVisitor();
- }
-
- @Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
- boolean visible) {
- return new AnnotationUsageVisitor();
- }
- };
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- return new AnnotationUsageVisitor();
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc, String signature,
- Object value) {
- handleCodeConstant(value, "field");
- return new FieldVisitor(Opcodes.ASM5) {
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- return new AnnotationUsageVisitor();
- }
- };
- }
-
- private class AnnotationUsageVisitor extends AnnotationVisitor {
- public AnnotationUsageVisitor() {
- super(Opcodes.ASM5);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- return new AnnotationUsageVisitor();
- }
-
- @Override
- public AnnotationVisitor visitArray(String name) {
- return new AnnotationUsageVisitor();
- }
-
- @Override
- public void visit(String name, Object value) {
- handleCodeConstant(value, "annotation");
- super.visit(name, value);
- }
- }
-
- /** Invoked when an ASM visitor encounters a constant: record corresponding reference */
- private void handleCodeConstant(@Nullable Object cst, @NonNull String context) {
- if (cst instanceof Integer) {
- Integer value = (Integer) cst;
- Resource resource = mValueToResource.get(value);
- if (markReachable(resource) && mDebug) {
- System.out.println("Marking " + resource + " reachable: referenced from " +
- context + " in " + mJarFile + ":" + mCurrentClass);
- }
- } else if (cst instanceof int[]) {
- int[] values = (int[]) cst;
- for (int value : values) {
- Resource resource = mValueToResource.get(value);
- if (markReachable(resource) && mDebug) {
- System.out.println("Marking " + resource + " reachable: referenced from " +
- context + " in " + mJarFile + ":" + mCurrentClass);
- }
- }
- } else if (cst instanceof String) {
- String string = (String) cst;
- referencedString(string);
- }
- }
- }
-
- @VisibleForTesting
- String dumpResourceModel() {
- StringBuilder sb = new StringBuilder(1000);
- Collections.sort(mResources, new Comparator<Resource>() {
- @Override
- public int compare(Resource resource1,
- Resource resource2) {
- int delta = resource1.type.compareTo(resource2.type);
- if (delta != 0) {
- return delta;
- }
- return resource1.name.compareTo(resource2.name);
- }
- });
-
- for (Resource resource : mResources) {
- sb.append(resource.getUrl()).append(" : reachable=").append(resource.reachable);
- sb.append("\n");
- if (resource.references != null) {
- for (Resource referenced : resource.references) {
- sb.append(" ");
- sb.append(referenced.getUrl());
- sb.append("\n");
- }
- }
- }
-
- return sb.toString();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ShrinkResources.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ShrinkResources.groovy
deleted file mode 100644
index 4031e6c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ShrinkResources.groovy
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantOutputScope
-import com.android.build.gradle.internal.tasks.BaseTask
-import com.android.build.gradle.internal.variant.BaseVariantData
-import com.android.build.gradle.internal.variant.BaseVariantOutputData
-import com.android.builder.core.AaptPackageProcessBuilder
-import com.android.ide.common.process.LoggedProcessOutputHandler
-import org.gradle.api.logging.LogLevel
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.ParallelizableTask
-import org.gradle.api.tasks.TaskAction
-
-import java.util.concurrent.Callable
-
-/**
- * Task which strips out unused resources
- * <p>
- * The process works as follows:
- * <ul>
- * <li> Collect R id <b>values</b> from the final merged R class, which incorporates
- * the final id's of all the libraries (if ProGuard hasn't inlined these,
- * we don't need to do this; we can look for actual R.id's instead!)
- * <li> Collect <b>used<b> R values from all the .class files, and R.x.y references too!
- * <li> Compute the set of remaining/used id’s
- * <li> Add in any found in the manifest
- * <li> Look through all resources and produce a graph of reachable resources
- * <li> Compute unused resources by visiting all resources and ignoring those that
- * were reachable
- * <li> In addition, if we find a call to Resources#getIdentifier(), we collect all
- * strings in the class files, and also mark as used any resources that match
- * potential string lookups
- * </ul>
- */
- at ParallelizableTask
-public class ShrinkResources extends BaseTask {
- /**
- * Associated variant data that the strip task will be run against. Used to locate
- * not only locations the task needs (e.g. for resources and generated R classes)
- * but also to obtain the resource merging task, since we will run it a second time
- * here to generate a new .ap_ file with fewer resources
- */
- public BaseVariantOutputData variantOutputData
-
- @InputFile
- File uncompressedResources
-
- @OutputFile
- File compressedResources
-
- /** Whether we've already warned about how to turn off shrinking. Used to avoid
- * repeating the same multi-line message for every repeated abi split. */
- private static ourWarned;
-
- @SuppressWarnings("GroovyUnusedDeclaration")
- @TaskAction
- void shrink() {
- def variantData = variantOutputData.variantData
- try {
- def processResourcesTask = variantData.generateRClassTask
- File sourceDir = processResourcesTask.sourceOutputDir
- File resourceDir = variantData.getScope().getFinalResourcesDir()
- File mergedManifest = variantOutputData.manifestProcessorTask.manifestOutputFile
-
- // Analyze resources and usages and strip out unused
- def analyzer = new ResourceUsageAnalyzer(
- sourceDir,
- variantData.getScope().getProguardOutputFile(),
- mergedManifest,
- variantData.getMappingFile(),
- resourceDir)
- analyzer.verbose = project.logger.isEnabled(LogLevel.INFO)
- analyzer.debug = project.logger.isEnabled(LogLevel.DEBUG)
- analyzer.analyze();
-
- //noinspection GroovyConstantIfStatement
- if (ResourceUsageAnalyzer.TWO_PASS_AAPT) {
- // This is currently not working; we need support from aapt to be able
- // to assign a stable set of resources that it should use.
- def destination = new File(resourceDir.parentFile, resourceDir.name + "-stripped")
- analyzer.removeUnused(destination)
-
- File sourceOutputs = processResourcesTask.getSourceOutputDir();
- sourceOutputs = new File(sourceOutputs.getParentFile(),
- sourceOutputs.getName() + "-stripped")
- sourceOutputs.mkdirs()
-
- // We don't need to emit R files again, but we can do this here such that
- // we can *verify* that the R classes generated in the second aapt pass
- // matches those we saw the first time around.
- //String sourceOutputPath = sourceOutputs?.getAbsolutePath();
- String sourceOutputPath = null
-
- // Repackage the resources:
- AaptPackageProcessBuilder aaptPackageCommandBuilder =
- new AaptPackageProcessBuilder(processResourcesTask.getManifestFile(),
- processResourcesTask.getAaptOptions())
- .setAssetsFolder(processResourcesTask.getAssetsDir())
- .setResFolder(destination)
- .setLibraries(processResourcesTask.getLibraries())
- .setPackageForR(processResourcesTask.getPackageForR())
- .setSourceOutputDir(sourceOutputPath)
- .setResPackageOutput(getCompressedResources().absolutePath)
- .setType(processResourcesTask.getType())
- .setDebuggable(processResourcesTask.getDebuggable())
- .setResourceConfigs(processResourcesTask.getResourceConfigs())
- .setSplits(processResourcesTask.getSplits())
-
- getBuilder().processResources(
- aaptPackageCommandBuilder,
- processResourcesTask.getEnforceUniquePackageName(),
- new LoggedProcessOutputHandler(getBuilder().getLogger())
- )
- } else {
- // Just rewrite the .ap_ file to strip out the res/ files for unused resources
- analyzer.rewriteResourceZip(getUncompressedResources(), getCompressedResources())
- }
-
- // Dump some stats
- int unused = analyzer.getUnusedResourceCount()
- if (unused > 0) {
- StringBuilder sb = new StringBuilder(200);
- sb.append("Removed unused resources")
-
- // This is a bit misleading until we can strip out all resource types:
- //int total = analyzer.getTotalResourceCount()
- //sb.append("(" + unused + "/" + total + ")")
-
- int before = getUncompressedResources().length()
- int after = getCompressedResources().length()
- int percent = (before - after) * 100 / before
- sb.append(": Binary resource data reduced from ").
- append(toKbString(before)).
- append("KB to ").
- append(toKbString(after)).
- append("KB: Removed " + percent + "%");
- if (!ourWarned) {
- ourWarned = true;
- sb.append(
- "\nNote: If necessary, you can disable resource shrinking by adding\n" +
- "android {\n" +
- " buildTypes {\n" +
- " " + variantData.variantConfiguration.buildType.name + " {\n" +
- " shrinkResources false\n" +
- " }\n" +
- " }\n" +
- "}")
- }
-
- println sb.toString();
- }
-
- } catch (Exception e) {
- println 'Failed to shrink resources: ' + e.toString() + '; ignoring'
- logger.quiet("Failed to shrink resources: ignoring", e)
- }
- }
-
- private static String toKbString(long size) {
- return Integer.toString((int)size/1024);
- }
-
- public static class ConfigAction implements TaskConfigAction<ShrinkResources> {
-
- private VariantOutputScope scope;
-
- public ConfigAction(VariantOutputScope scope) {
- this.scope = scope;
- }
-
- @Override
- String getName() {
- return scope.getTaskName("shrink", "Resources");
- }
-
- @Override
- Class<ShrinkResources> getType() {
- return ShrinkResources.class
- }
-
- @Override
- void execute(ShrinkResources task) {
- BaseVariantData<? extends BaseVariantOutputData> variantData =
- scope.variantScope.variantData
- task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
- task.setVariantName(scope.getVariantScope().getVariantConfiguration().getFullName());
- task.variantOutputData = scope.variantOutputData;
-
- final String outputBaseName = scope.variantOutputData.getBaseName();
- task.setCompressedResources(scope.getCompressedResourceFile());
-
- ConventionMappingHelper.map(task, "uncompressedResources", new Callable<File>() {
- @Override
- public File call() {
- return scope.variantOutputData.processResourcesTask.getPackageOutputFile();
- }
- });
-
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SimpleWorkQueue.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SimpleWorkQueue.java
deleted file mode 100644
index f54f286..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SimpleWorkQueue.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.builder.tasks.Job;
-import com.android.builder.tasks.JobContext;
-import com.android.builder.tasks.QueueThreadContextAdapter;
-import com.android.builder.tasks.WorkQueue;
-import com.android.utils.StdLogger;
-
-/**
- * Common utilities to use a simple shared instance of {@link WorkQueue}.
- * The context for job will be empty, and it is the responsibility of the
- * {@link com.android.builder.tasks.WorkQueue.QueueTask} to have enough context to run.
- */
-public class SimpleWorkQueue {
-
- /**
- * Simple {@link WorkQueue} context implementation that simply runs the proguard job.
- */
- private static class EmptyThreadContext extends
- QueueThreadContextAdapter<Void> {
-
- @Override
- public void runTask(@NonNull Job<Void> job) throws Exception {
- job.runTask(new JobContext<Void>(null /* payload */));
- job.finished();
- }
- }
-
- /**
- * singleton work queue for all proguard invocations.
- */
- private static final WorkQueue<Void> WORK_QUEUE =
- new WorkQueue<Void>(
- new StdLogger(StdLogger.Level.VERBOSE),
- new EmptyThreadContext(), "Tasks limiter", 4);
-
-
- static void push(Job<Void> job) throws InterruptedException {
- WORK_QUEUE.push(job);
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitRelatedTask.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitRelatedTask.groovy
deleted file mode 100644
index d1854bd..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitRelatedTask.groovy
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.FilterData
-import com.android.build.OutputFile
-import com.android.build.gradle.api.ApkOutputFile
-import com.android.build.gradle.internal.model.FilterDataImpl
-import com.android.build.gradle.internal.publishing.FilterDataPersistence
-import com.android.build.gradle.internal.tasks.BaseTask
-import com.android.build.gradle.internal.tasks.FileSupplier
-import com.android.build.gradle.internal.tasks.SplitFileSupplier
-import com.google.common.base.Supplier
-import com.google.common.collect.ImmutableList
-import org.gradle.api.Task
-
-/**
- * Common code for all split related tasks
- */
-abstract class SplitRelatedTask extends BaseTask {
-
- @Nullable
- public abstract File getApkMetadataFile();
-
- /**
- * Calculates the list of output files, coming from the list of input files, mangling the
- * output file name.
- */
- public abstract List<ApkOutputFile> getOutputSplitFiles()
-
- /**
- * Returns the list of split information for this task. Each split is a unique combination of
- * filter type and identifier.
- */
- abstract List<FilterData> getSplitsData()
-
- /**
- * Returns a list of {@link Supplier<File>} for each split APK file
- */
- List<SplitFileSupplier> getOutputFileSuppliers() {
- ImmutableList.Builder<SplitFileSupplier> suppliers = ImmutableList.builder();
- for (FilterData filterData : getSplitsData()) {
-
- ApkOutputFile outputFile = getOutputSplitFiles().find {
- filterData.identifier.equals(it.getFilter(filterData.filterType))
- }
- if (outputFile != null) {
- // make final references to not confused the groovy runtime...
- final File file = outputFile.getOutputFile();
- final data = filterData;
-
- suppliers.add(new SplitFileSupplier() {
-
- @Override
- File get() {
- return file;
- }
-
- @NonNull
- @Override
- Task getTask() {
- return SplitRelatedTask.this
- }
-
- @NonNull
- @Override
- FilterData getFilterData() {
- return data;
- }
- })
- }
- }
- return suppliers.build()
- }
-
- /**
- * Saves the APK metadata to the configured file.
- */
- protected void saveApkMetadataFile() throws IOException {
-
- File metadataFile = getApkMetadataFile();
- if (metadataFile == null) {
- return;
- }
- FileWriter fileWriter = null;
- try {
- metadataFile.getParentFile().mkdirs();
- fileWriter = new FileWriter(metadataFile);
- FilterDataPersistence persistence = new FilterDataPersistence();
- persistence.persist(outputFileSuppliers, fileWriter);
-
- } finally {
- if (fileWriter != null) {
- fileWriter.close();
- }
- }
- }
-
- /**
- * Creates a new FilterData for each identifiers for a particular {@link FilterType} and store
- * it in the to builder.
- * @param to the builder to store the new FilterData instances in.
- * @param identifiers the list of filter identifiers
- * @param filterType the filter type.
- */
- protected static void addAllFilterData(ImmutableList.Builder<FilterData> to,
- Collection<String> identifiers,
- OutputFile.FilterType filterType) {
- for (String identifier : identifiers) {
- to.add(FilterDataImpl.build(filterType.toString(), identifier))
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitZipAlign.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitZipAlign.groovy
deleted file mode 100644
index 0220fe9..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitZipAlign.groovy
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks
-
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.FilterData
-import com.android.build.OutputFile.FilterType
-import com.android.build.OutputFile.OutputType
-import com.android.build.gradle.api.ApkOutputFile
-import com.android.build.gradle.internal.model.FilterDataImpl
-import com.google.common.collect.ImmutableList
-import com.google.common.util.concurrent.Callables
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.OutputFiles
-import org.gradle.api.tasks.ParallelizableTask
-import org.gradle.api.tasks.TaskAction
-
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-
-/**
- * Task to zip align all the splits
- */
- at ParallelizableTask
-class SplitZipAlign extends SplitRelatedTask {
-
- @InputFiles
- List<File> densityOrLanguageInputFiles = new ArrayList<>();
-
- @InputFiles
- List<File> abiInputFiles = new ArrayList<>()
-
- @Input
- String outputBaseName;
-
- @Input
- Set<String> densityFilters;
-
- @Input
- Set<String> abiFilters;
-
- @Input
- Set<String> languageFilters;
-
- File outputDirectory;
-
- @InputFile
- File zipAlignExe
-
- @OutputFiles
- public List<File> getOutputFiles() {
- getOutputSplitFiles()*.getOutputFile()
- }
-
- @OutputFile
- @Nullable
- File apkMetadataFile
-
- @NonNull
- List<File> getInputFiles() {
- return getDensityOrLanguageInputFiles() + getAbiInputFiles();
- }
-
- @NonNull
- public synchronized ImmutableList<ApkOutputFile> getOutputSplitFiles() {
-
- ImmutableList.Builder<ApkOutputFile> outputFiles = ImmutableList.builder();
- Closure addingLogic = { String split, File file ->
- outputFiles.add(new ApkOutputFile(OutputType.SPLIT,
- ImmutableList.<FilterData>of(
- FilterDataImpl.build(
- getFilterType(split).toString(), getFilter(split))),
- Callables.<File>returning(
- new File(outputDirectory,
- "${project.archivesBaseName}-${outputBaseName}_${split}.apk"))))
- }
-
- forEachUnalignedInput(addingLogic)
- forEachUnsignedInput(addingLogic)
- return outputFiles.build()
- }
-
- FilterType getFilterType(String filter) {
- String languageName = PackageSplitRes.unMangleSplitName(filter);
- if (languageFilters.contains(languageName)) {
- return FilterType.LANGUAGE
- }
- if (abiFilters.contains(filter)) {
- return FilterType.ABI
- }
- return FilterType.DENSITY
- }
-
- String getFilter(String filterWithPossibleSuffix) {
- FilterType type = getFilterType(filterWithPossibleSuffix)
- if (type == FilterType.DENSITY) {
- for (String density : densityFilters) {
- if (filterWithPossibleSuffix.startsWith(density)) {
- return density
- }
- }
- }
- if (type == FilterType.LANGUAGE) {
- return PackageSplitRes.unMangleSplitName(filterWithPossibleSuffix)
- }
- return filterWithPossibleSuffix
- }
-
- /**
- * Returns true if the passed string is one of the filter we must process potentially followed
- * by a prefix (some density filters get V4, V16, etc... appended).
- */
- boolean isFilter(String potentialFilterWithSuffix) {
- for (String density : densityFilters) {
- if (potentialFilterWithSuffix.startsWith(density)) {
- return true
- }
- }
- if (abiFilters.contains(potentialFilterWithSuffix)) {
- return true
- }
- if (languageFilters.contains(
- PackageSplitRes.unMangleSplitName(potentialFilterWithSuffix))) {
- return true
- }
- return false
- }
-
- private void forEachUnalignedInput(Closure closure) {
- Pattern unalignedPattern = Pattern.compile(
- "${project.archivesBaseName}-${outputBaseName}_(.*)-unaligned.apk")
-
- for (File file : getInputFiles()) {
- Matcher unaligned = unalignedPattern.matcher(file.getName())
- if (unaligned.matches() && isFilter(unaligned.group(1))) {
- closure(unaligned.group(1), file);
- }
- }
- }
-
- private void forEachUnsignedInput(Closure closure) {
- Pattern unsignedPattern = Pattern.compile(
- "${project.archivesBaseName}-${outputBaseName}_(.*)-unsigned.apk")
-
- for (File file : getInputFiles()) {
- Matcher unsigned = unsignedPattern.matcher(file.getName())
- if (unsigned.matches() && isFilter(unsigned.group(1))) {
- closure(unsigned.group(1), file)
- }
- }
- }
-
- @TaskAction
- void splitZipAlign() {
-
- Closure zipAlignIt = { String split, File file ->
- File out = new File(getOutputDirectory(),
- "${project.archivesBaseName}-${outputBaseName}_${split}.apk")
- project.exec {
- executable = getZipAlignExe()
- args '-f', '4'
- args file.absolutePath
- args out
- }
- }
- forEachUnalignedInput(zipAlignIt)
- forEachUnsignedInput(zipAlignIt)
- saveApkMetadataFile()
- }
-
- @Override
- List<FilterData> getSplitsData() {
- ImmutableList.Builder<FilterData> filterDataBuilder = ImmutableList.builder();
- addAllFilterData(filterDataBuilder, densityFilters, FilterType.DENSITY);
- addAllFilterData(filterDataBuilder, languageFilters, FilterType.LANGUAGE);
- addAllFilterData(filterDataBuilder, abiFilters, FilterType.ABI);
- return filterDataBuilder.build();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/TestModuleProGuardTask.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/TestModuleProGuardTask.java
deleted file mode 100644
index 136db9e..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/TestModuleProGuardTask.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.builder.core.VariantConfiguration;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.IOException;
-
-import proguard.ParseException;
-import proguard.gradle.ProGuardTask;
-
-/**
- * Specialization of the {@link ProGuardTask} that can use {@link Configuration} objects to retrieve
- * input files like the tested application classes and the tested application mapping file.
- */
-public class TestModuleProGuardTask extends ProGuardTask {
- private Logger logger;
- private Configuration mappingConfiguration;
- private VariantConfiguration variantConfiguration;
-
-
- /**
- * Sets the {@link Configuration} to later retrieve the tested application mapping file
- */
- public void setMappingConfiguration(Configuration configuration) {
- this.mappingConfiguration = configuration;
- dependsOn(configuration);
- }
-
- /**
- * Sets the {@link Configuration} to later retrieve the test application classes jar file.
- */
- public void setClassesConfiguration(Configuration configuration) {
- dependsOn(configuration);
- }
-
-
- public void setVariantConfiguration(
- VariantConfiguration variantConfiguration) {
- this.variantConfiguration = variantConfiguration;
- }
-
- public void setLogger(Logger logger) {
- this.logger = logger;
- }
-
- @Override
- @TaskAction
- public void proguard() throws ParseException, IOException {
- if (logger.isEnabled(LogLevel.INFO)) {
- logger.info("test module mapping file " + mappingConfiguration.getSingleFile());
- for (Object file : variantConfiguration.getPackagedJars()) {
- logger.info("test module proguard input " + file);
-
- }
- for (Object file : variantConfiguration.getProvidedOnlyJars()) {
- logger.info("test module proguard library " + file);
- }
- }
-
- if (mappingConfiguration.getSingleFile().isFile()) {
- applymapping(mappingConfiguration.getSingleFile());
- }
- super.proguard();
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.java
deleted file mode 100644
index 6df5096..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.android.build.gradle.tasks;
-
-import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.annotations.ApkFile;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantOutputScope;
-import com.android.build.gradle.internal.tasks.FileSupplier;
-import com.android.build.gradle.internal.variant.ApkVariantOutputData;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.Task;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.process.ExecSpec;
-
-import java.io.File;
-import java.util.concurrent.Callable;
-
- at ParallelizableTask
-public class ZipAlign extends DefaultTask implements FileSupplier {
-
- // ----- PUBLIC TASK API -----
-
- @OutputFile
- public File getOutputFile() {
- return outputFile;
- }
-
- public void setOutputFile(File outputFile) {
- this.outputFile = outputFile;
- }
-
- @InputFile
- public File getInputFile() {
- return inputFile;
- }
-
- public void setInputFile(File inputFile) {
- this.inputFile = inputFile;
- }
-
- // ----- PRIVATE TASK API -----
-
- private File outputFile;
- @ApkFile
- private File inputFile;
- @ApkFile
- private File zipAlignExe;
-
- @InputFile
- public File getZipAlignExe() {
- return zipAlignExe;
- }
-
- public void setZipAlignExe(File zipAlignExe) {
- this.zipAlignExe = zipAlignExe;
- }
-
- @TaskAction
- public void zipAlign() {
- getProject().exec(new Action<ExecSpec>() {
- @Override
- public void execute(ExecSpec execSpec) {
- execSpec.executable(getZipAlignExe());
- execSpec.args("-f", "4");
- execSpec.args(getInputFile());
- execSpec.args(getOutputFile());
- }
- });
- }
-
- // ----- FileSupplierTask -----
-
- @Override
- public File get() {
- return getOutputFile();
- }
-
- @NonNull
- @Override
- public Task getTask() {
- return this;
- }
-
- // ----- ConfigAction -----
-
- public static class ConfigAction implements TaskConfigAction<ZipAlign> {
-
- private final VariantOutputScope scope;
-
- @Override
- public String getName() {
- return scope.getTaskName("zipalign");
- }
-
- @Override
- public Class<ZipAlign> getType() {
- return ZipAlign.class;
- }
-
- public ConfigAction(VariantOutputScope scope) {
- this.scope = scope;
- }
-
- @Override
- public void execute(ZipAlign zipAlign) {
- ((ApkVariantOutputData) scope.getVariantOutputData()).zipAlignTask = zipAlign;
- ConventionMappingHelper.map(zipAlign, "inputFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getPackageApk();
- }
- });
- ConventionMappingHelper.map(zipAlign, "outputFile", new Callable<File>() {
- @Override
- public File call() throws Exception {
- return scope.getGlobalScope().getProject().file(
- scope.getGlobalScope().getApkLocation() + "/" +
- scope.getGlobalScope().getProjectBaseName() + "-" +
- scope.getVariantOutputData().getBaseName() + ".apk");
- }
- });
- ConventionMappingHelper.map(zipAlign, "zipAlignExe", new Callable<File>() {
- @Override
- public File call() throws Exception {
- String path = scope.getGlobalScope().getAndroidBuilder().getTargetInfo()
- .getBuildTools().getPath(ZIP_ALIGN);
- if (path != null) {
- return new File(path);
- }
- return null;
- }
- });
- }
- }
-
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java
deleted file mode 100644
index 78ec03d..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks.annotations;
-
-import static com.android.SdkConstants.DOT_JAVA;
-import static java.io.File.pathSeparator;
-import static java.io.File.pathSeparatorChar;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.EcjParser;
-import com.android.utils.Pair;
-import com.google.common.base.Charsets;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.Files;
-
-import org.eclipse.jdt.core.compiler.IProblem;
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
-import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
-import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
-import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
-import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
-import org.eclipse.jdt.internal.compiler.util.Util;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The extract annotations driver is a command line interface to extracting annotations
- * from a source tree. It's similar to the gradle
- * {@link com.android.build.gradle.tasks.ExtractAnnotations} task,
- * but usable from the command line and outside Gradle, for example
- * to extract annotations from the Android framework itself (which is not built with
- * Gradle). It also allows other options only interesting for extracting
- * platform annotations, such as filtering all APIs and constants through an
- * API white-list (such that we for example can pull annotations from the master
- * branch which has the latest metadata, but only expose APIs that are actually in
- * a released platform), as well as translating android.annotation annotations into
- * android.support.annotations.
- */
-public class ExtractAnnotationsDriver {
- public static void main(String[] args) {
- new ExtractAnnotationsDriver().run(args);
- }
-
- private static void usage(PrintStream output) {
- output.println("Usage: " + ExtractAnnotationsDriver.class.getSimpleName() + " <flags>");
- output.println(" --sources <paths> : Source directories to extract annotations from. ");
- output.println(" Separate paths with " + pathSeparator + ", and you can use @ ");
- output.println(" as a filename prefix to have the filenames fed from a file");
- output.println("--classpath <paths> : Directories and .jar files to resolve symbols from");
- output.println("--output <zip path> : The .zip file to write the extracted annotations to, if any");
- output.println("--proguard <path> : The proguard.cfg file to write the keep rules to, if any");
- output.println();
- output.println("Optional flags:");
- output.println("--merge-zips <paths> : Existing external annotation files to merge in");
- output.println("--quiet : Don't print summary information");
- output.println("--rmtypedefs <folder> : Remove typedef classes found in the given folder");
- output.println("--allow-missing-types : Don't fail even if some types can't be resolved");
- output.println("--allow-errors : Don't fail even if there are some compiler errors");
- output.println("--encoding <encoding> : Encoding (defaults to utf-8)");
- output.println("--language-level <level> : Java source language level, typically 1.6 (default) or 1.7");
- output.println("--api-filter <api.txt> : A framework API definition to restrict included APIs to");
- output.println("--hide-filtered : If filtering out non-APIs, supply this flag to hide listing matches");
- output.println("--skip-class-retention : Don't extract annotations that have class retention");
- System.exit(-1);
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- public void run(@NonNull String[] args) {
- List<String> classpath = Lists.newArrayList();
- List<File> sources = Lists.newArrayList();
- List<File> mergePaths = Lists.newArrayList();
- List<File> apiFilters = null;
- File rmTypeDefs = null;
- boolean verbose = true;
- boolean allowMissingTypes = false;
- boolean allowErrors = false;
- boolean listFiltered = true;
- boolean skipClassRetention = false;
-
- String encoding = Charsets.UTF_8.name();
- File output = null;
- File proguard = null;
- long languageLevel = EcjParser.getLanguageLevel(1, 7);
- if (args.length == 1 && "--help".equals(args[0])) {
- usage(System.out);
- }
- if (args.length < 2) {
- usage(System.err);
- }
- for (int i = 0, n = args.length; i < n; i++) {
- String flag = args[i];
-
- if (flag.equals("--quiet")) {
- verbose = false;
- continue;
- } else if (flag.equals("--allow-missing-types")) {
- allowMissingTypes = true;
- continue;
- } else if (flag.equals("--allow-errors")) {
- allowErrors = true;
- continue;
- } else if (flag.equals("--hide-filtered")) {
- listFiltered = false;
- continue;
- } else if (flag.equals("--skip-class-retention")) {
- skipClassRetention = true;
- continue;
- }
- if (i == n - 1) {
- usage(System.err);
- }
- String value = args[i + 1];
- i++;
-
- if (flag.equals("--sources")) {
- sources = getFiles(value);
- } else if (flag.equals("--classpath")) {
- classpath = getPaths(value);
- } else if (flag.equals("--merge-zips")) {
- mergePaths = getFiles(value);
- } else if (flag.equals("--output")) {
- output = new File(value);
- if (output.exists()) {
- if (output.isDirectory()) {
- abort(output + " is a directory");
- }
- boolean deleted = output.delete();
- if (!deleted) {
- abort("Could not delete previous version of " + output);
- }
- } else if (output.getParentFile() != null && !output.getParentFile().exists()) {
- abort(output.getParentFile() + " does not exist");
- }
- } else if (flag.equals("--proguard")) {
- proguard = new File(value);
- if (proguard.exists()) {
- if (proguard.isDirectory()) {
- abort(proguard + " is a directory");
- }
- boolean deleted = proguard.delete();
- if (!deleted) {
- abort("Could not delete previous version of " + proguard);
- }
- } else if (proguard.getParentFile() != null && !proguard.getParentFile().exists()) {
- abort(proguard.getParentFile() + " does not exist");
- }
- } else if (flag.equals("--encoding")) {
- encoding = value;
- } else if (flag.equals("--api-filter")) {
- if (apiFilters == null) {
- apiFilters = Lists.newArrayList();
- }
- for (String path : Splitter.on(",").omitEmptyStrings().split(value)) {
- File apiFilter = new File(path);
- if (!apiFilter.isFile()) {
- String message = apiFilter + " does not exist or is not a file";
- abort(message);
- }
- apiFilters.add(apiFilter);
- }
- } else if (flag.equals("--language-level")) {
- if ("1.6".equals(value)) {
- languageLevel = EcjParser.getLanguageLevel(1, 6);
- } else if ("1.7".equals(value)) {
- languageLevel = EcjParser.getLanguageLevel(1, 7);
- } else {
- abort("Unsupported language level " + value);
- }
- } else if (flag.equals("--rmtypedefs")) {
- rmTypeDefs = new File(value);
- if (!rmTypeDefs.isDirectory()) {
- abort(rmTypeDefs + " is not a directory");
- }
- } else {
- System.err.println("Unknown flag " + flag + ": Use --help for usage information");
- }
- }
-
- if (sources.isEmpty()) {
- abort("Must specify at least one source path");
- }
- if (classpath.isEmpty()) {
- abort("Must specify classpath pointing to at least android.jar or the framework");
- }
- if (output == null && proguard == null) {
- abort("Must specify output path with --output or a proguard path with --proguard");
- }
-
- // API definition files
- ApiDatabase database = null;
- if (apiFilters != null && !apiFilters.isEmpty()) {
- try {
- List<String> lines = Lists.newArrayList();
- for (File file : apiFilters) {
- lines.addAll(Files.readLines(file, Charsets.UTF_8));
- }
- database = new ApiDatabase(lines);
- } catch (IOException e) {
- abort("Could not open API database " + apiFilters + ": " + e.getLocalizedMessage());
- }
- }
-
- Extractor extractor = new Extractor(database, rmTypeDefs, verbose, !skipClassRetention,
- true);
- extractor.setListIgnored(listFiltered);
-
- try {
- Pair<Collection<CompilationUnitDeclaration>, INameEnvironment>
- pair = parseSources(sources, classpath, encoding, languageLevel);
- Collection<CompilationUnitDeclaration> units = pair.getFirst();
-
- boolean abort = false;
- int errorCount = 0;
- for (CompilationUnitDeclaration unit : units) {
- // so maybe I don't need my map!!
- IProblem[] problems = unit.compilationResult().getAllProblems();
- if (problems != null) {
- for (IProblem problem : problems) {
- if (problem.isError()) {
- errorCount++;
- String message = problem.getMessage();
- if (allowMissingTypes) {
- if (message.contains("cannot be resolved")) {
- continue;
- }
- }
-
- System.out.println("Error: " +
- new String(problem.getOriginatingFileName()) + ":" +
- problem.getSourceLineNumber() + ": " + message);
- abort = !allowErrors;
- }
- }
- }
- }
- if (errorCount > 0) {
- System.err.println("Found " + errorCount + " errors");
- }
- if (abort) {
- abort("Not extracting annotations (compilation problems encountered)");
- }
-
- INameEnvironment environment = pair.getSecond();
- extractor.extractFromProjectSource(units);
-
- if (mergePaths != null) {
- for (File jar : mergePaths) {
- extractor.mergeExisting(jar);
- }
- }
-
- extractor.export(output, proguard);
-
- // Remove typedefs?
- //noinspection VariableNotUsedInsideIf
- if (rmTypeDefs != null) {
- extractor.removeTypedefClasses();
- }
-
- environment.cleanup();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private static void abort(@NonNull String message) {
- System.err.println(message);
- System.exit(-1);
- }
-
- private static List<File> getFiles(String value) {
- List<File> files = Lists.newArrayList();
- Splitter splitter = Splitter.on(pathSeparatorChar).omitEmptyStrings().trimResults();
- for (String path : splitter.split(value)) {
- if (path.startsWith("@")) {
- // Special syntax for providing files in a list
- File sourcePath = new File(path.substring(1));
- if (!sourcePath.exists()) {
- abort(sourcePath + " does not exist");
- }
- try {
- for (String line : Files.readLines(sourcePath, Charsets.UTF_8)) {
- line = line.trim();
- if (!line.isEmpty()) {
- File file = new File(line);
- if (!file.exists()) {
- System.err.println("Warning: Could not find file " + line +
- " listed in " + sourcePath);
- }
- files.add(file);
- }
- }
- continue;
- } catch (IOException e) {
- e.printStackTrace();
- System.exit(-1);
- }
- }
- File file = new File(path);
- if (!file.exists()) {
- abort(file + " does not exist");
- }
- files.add(file);
- }
-
- return files;
- }
-
- private static List<String> getPaths(String value) {
- List<File> files = getFiles(value);
- List<String> paths = Lists.newArrayListWithExpectedSize(files.size());
- for (File file : files) {
- paths.add(file.getPath());
- }
- return paths;
- }
-
- private static void addJavaSources(List<File> list, File file) {
- if (file.isDirectory()) {
- File[] files = file.listFiles();
- if (files != null) {
- for (File child : files) {
- addJavaSources(list, child);
- }
- }
- } else {
- if (file.isFile() && file.getName().endsWith(DOT_JAVA)) {
- list.add(file);
- }
- }
- }
-
- private static List<File> gatherJavaSources(List<File> sourcePath) {
- List<File> sources = Lists.newArrayList();
- for (File file : sourcePath) {
- addJavaSources(sources, file);
- }
- return sources;
- }
-
- @NonNull
- private static Pair<Collection<CompilationUnitDeclaration>,INameEnvironment> parseSources(
- @NonNull List<File> sourcePaths,
- @NonNull List<String> classpath,
- @NonNull String encoding,
- long languageLevel)
- throws IOException {
- List<ICompilationUnit> sourceUnits = Lists.newArrayListWithExpectedSize(100);
-
- for (File source : gatherJavaSources(sourcePaths)) {
- char[] contents = Util.getFileCharContent(source, encoding);
- ICompilationUnit unit = new CompilationUnit(contents, source.getPath(), encoding);
- sourceUnits.add(unit);
- }
-
- Map<ICompilationUnit, CompilationUnitDeclaration> outputMap = Maps.newHashMapWithExpectedSize(
- sourceUnits.size());
-
- CompilerOptions options = EcjParser.createCompilerOptions();
- options.docCommentSupport = true; // So I can find @hide
-
- // Note: We can *not* set options.ignoreMethodBodies=true because it disables
- // type attribution!
-
- options.sourceLevel = languageLevel;
- options.complianceLevel = options.sourceLevel;
- // We don't generate code, but just in case the parser consults this flag
- // and makes sure that it's not greater than the source level:
- options.targetJDK = options.sourceLevel;
- options.originalComplianceLevel = options.sourceLevel;
- options.originalSourceLevel = options.sourceLevel;
- options.inlineJsrBytecode = true; // >= 1.5
-
- INameEnvironment environment = EcjParser.parse(options, sourceUnits, classpath,
- outputMap, null);
- Collection<CompilationUnitDeclaration> parsedUnits = outputMap.values();
- return Pair.of(parsedUnits, environment);
- }
-}
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
deleted file mode 100644
index 5924f4c..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks.annotations;
-
-import com.android.annotations.NonNull;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import org.eclipse.jdt.internal.compiler.ASTVisitor;
-import org.eclipse.jdt.internal.compiler.ast.Annotation;
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.Javadoc;
-import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
-import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
-import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
-import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
-import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/** Gathers information about typedefs (@IntDef and @StringDef */
-public class TypedefCollector extends ASTVisitor {
- private Map<String,List<Annotation>> mMap = Maps.newHashMap();
-
- private final boolean mRequireHide;
- private final boolean mRequireSourceRetention;
- private CompilationUnitDeclaration mCurrentUnit;
- private List<String> mTypedefClasses = Lists.newArrayList();
-
- public TypedefCollector(
- @NonNull Collection<CompilationUnitDeclaration> units,
- boolean requireHide,
- boolean requireSourceRetention) {
- mRequireHide = requireHide;
- mRequireSourceRetention = requireSourceRetention;
-
- for (CompilationUnitDeclaration unit : units) {
- mCurrentUnit = unit;
- unit.traverse(this, unit.scope);
- mCurrentUnit = null;
- }
- }
-
- public List<String> getNonPublicTypedefClasses() {
- return mTypedefClasses;
- }
-
- public Map<String,List<Annotation>> getTypedefs() {
- return mMap;
- }
-
- @Override
- public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
- return recordTypedefs(memberTypeDeclaration);
-
- }
-
- @Override
- public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
- return recordTypedefs(typeDeclaration);
- }
-
- private boolean recordTypedefs(TypeDeclaration declaration) {
- SourceTypeBinding binding = declaration.binding;
- if (binding == null) {
- return false;
- }
- Annotation[] annotations = declaration.annotations;
- if (annotations != null) {
- if (declaration.binding.isAnnotationType()) {
- for (Annotation annotation : annotations) {
- String typeName = Extractor.getFqn(annotation);
- if (typeName == null) {
- continue;
- }
-
- if (Extractor.isNestedAnnotation(typeName)) {
- String fqn = new String(binding.readableName());
-
- List<Annotation> list = mMap.get(fqn);
- if (list == null) {
- list = new ArrayList<Annotation>(2);
- mMap.put(fqn, list);
- }
- list.add(annotation);
-
- if (mRequireHide) {
- Javadoc javadoc = declaration.javadoc;
- if (javadoc != null) {
- StringBuffer stringBuffer = new StringBuffer(200);
- javadoc.print(0, stringBuffer);
- String documentation = stringBuffer.toString();
- if (!documentation.contains("@hide")) {
- Extractor.warning(getFileName()
- + ": The typedef annotation " + fqn
- + " should specify @hide in a doc comment");
- }
- }
- }
- if (mRequireSourceRetention
- && !Extractor.hasSourceRetention(annotations)) {
- Extractor.warning(getFileName()
- + ": The typedef annotation " + fqn
- + " should have @Retention(RetentionPolicy.SOURCE)");
- }
- if (declaration.binding != null
- && (declaration.modifiers & ClassFileConstants.AccPublic) == 0) {
- StringBuilder sb = new StringBuilder(100);
- for (char c : declaration.binding.qualifiedPackageName()) {
- if (c == '.') {
- sb.append('/');
- } else {
- sb.append(c);
- }
- }
- sb.append(File.separatorChar);
- for (char c : declaration.binding.qualifiedSourceName()) {
- if (c == '.') {
- sb.append('$');
- } else {
- sb.append(c);
- }
- }
- mTypedefClasses.add(sb.toString());
- }
- }
- }
- }
- }
- return true;
- }
-
- private String getFileName() {
- return new String(mCurrentUnit.getFileName());
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/JavaCompileConfigAction.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/JavaCompileConfigAction.java
deleted file mode 100644
index 69c93ca..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/JavaCompileConfigAction.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.android.build.gradle.tasks.factory;
-
-import static com.android.builder.core.VariantType.LIBRARY;
-import static com.android.builder.core.VariantType.UNIT_TEST;
-
-import com.android.build.gradle.internal.CompileOptions;
-import com.android.build.gradle.internal.scope.ConventionMappingHelper;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.builder.dependency.LibraryDependency;
-import com.google.common.base.Joiner;
-
-import org.gradle.api.Project;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.tasks.compile.JavaCompile;
-
-import java.io.File;
-import java.util.concurrent.Callable;
-
-/**
- * Configuration Action for a JavaCompile task.
- */
-public class JavaCompileConfigAction implements TaskConfigAction<JavaCompile> {
-
- private VariantScope scope;
-
- public JavaCompileConfigAction(VariantScope scope) {
- this.scope = scope;
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("compile", "JavaWithJavac");
- }
-
- @Override
- public Class<JavaCompile> getType() {
- return JavaCompile.class;
- }
-
- @Override
- public void execute(final JavaCompile javacTask) {
- final BaseVariantData testedVariantData = scope.getTestedVariantData();
- scope.getVariantData().javacTask = javacTask;
-
- javacTask.setSource(scope.getVariantData().getJavaSources());
-
- ConventionMappingHelper.map(javacTask, "classpath", new Callable<FileCollection>() {
- @Override
- public FileCollection call() {
- FileCollection classpath = scope.getJavaClasspath();
- Project project = scope.getGlobalScope().getProject();
-
- if (testedVariantData != null) {
- // For libraries, the classpath from androidBuilder includes the library
- // output (bundle/classes.jar) as a normal dependency. In unit tests we
- // don't want to package the jar at every run, so we use the *.class
- // files instead.
- if (!testedVariantData.getType().equals(LIBRARY)
- || scope.getVariantData().getType().equals(UNIT_TEST)) {
- classpath = classpath.plus(project.files(
- testedVariantData.getScope().getJavaClasspath(),
- testedVariantData.getScope().getJavaOutputDir(),
- testedVariantData.getScope().getJavaDependencyCache()));
- }
-
- if (scope.getVariantData().getType().equals(UNIT_TEST)
- && testedVariantData.getType().equals(LIBRARY)) {
- // The bundled classes.jar may exist, but it's probably old. Don't
- // use it, we already have the *.class files in the classpath.
- LibraryDependency libraryDependency =
- testedVariantData.getVariantConfiguration().getOutput();
- if (libraryDependency != null) {
- File jarFile = libraryDependency.getJarFile();
- classpath = classpath.minus(project.files(jarFile));
- }
- }
- }
-
- return classpath;
- }
- });
-
- javacTask.setDestinationDir(scope.getJavaOutputDir());
-
- javacTask.setDependencyCacheDir(scope.getJavaDependencyCache());
-
- CompileOptions compileOptions = scope.getGlobalScope().getExtension().getCompileOptions();
-
- AbstractCompilesUtil.configureLanguageLevel(
- javacTask,
- compileOptions,
- scope.getGlobalScope().getExtension().getCompileSdkVersion()
- );
-
- javacTask.getOptions().setEncoding(compileOptions.getEncoding());
-
- javacTask.getOptions().setBootClasspath(
- Joiner.on(File.pathSeparator).join(
- scope.getGlobalScope().getAndroidBuilder().getBootClasspathAsStrings()));
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProGuardTaskConfigAction.java b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProGuardTaskConfigAction.java
deleted file mode 100644
index 10a13b9..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProGuardTaskConfigAction.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package com.android.build.gradle.tasks.factory;
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-
-import com.android.build.gradle.internal.PostCompilationData;
-import com.android.build.gradle.internal.scope.TaskConfigAction;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.google.common.base.Preconditions;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-import proguard.ParseException;
-import proguard.gradle.ProGuardTask;
-
-/**
- * Configuration Action for a ProGuardTask task.
- */
-public class ProGuardTaskConfigAction implements TaskConfigAction<ProGuardTask> {
-
- private VariantScope scope;
-
- private Callable<List<File>> inputFiles;
-
- public ProGuardTaskConfigAction(VariantScope scope, PostCompilationData pcData) {
- this.scope = scope;
- this.inputFiles = pcData.getInputFilesCallable();
- }
-
- @Override
- public String getName() {
- return scope.getTaskName("shrink", "MultiDexComponents");
- }
-
- @Override
- public Class<ProGuardTask> getType() {
- return ProGuardTask.class;
- }
-
- @Override
- public void execute(ProGuardTask proguardComponentsTask) {
- proguardComponentsTask.dontobfuscate();
- proguardComponentsTask.dontoptimize();
- proguardComponentsTask.dontpreverify();
- proguardComponentsTask.dontwarn();
- proguardComponentsTask.forceprocessing();
-
- try {
- proguardComponentsTask.configuration(scope.getManifestKeepListFile());
-
- proguardComponentsTask.libraryjars(new Callable<File>() {
- @Override
- public File call() throws Exception {
- Preconditions.checkNotNull(
- scope.getGlobalScope().getAndroidBuilder().getTargetInfo());
- File shrinkedAndroid = new File(
- scope.getGlobalScope().getAndroidBuilder().getTargetInfo()
- .getBuildTools()
- .getLocation(),
- "lib" + File.separatorChar + "shrinkedAndroid.jar");
-
- // TODO remove in 1.0
- // STOPSHIP
- if (!shrinkedAndroid.isFile()) {
- shrinkedAndroid = new File(
- scope.getGlobalScope().getAndroidBuilder().getTargetInfo()
- .getBuildTools().getLocation(),
- "multidex" + File.separatorChar + "shrinkedAndroid.jar");
- }
-
- return shrinkedAndroid;
- }
- });
-
- proguardComponentsTask.injars(new Callable<File>() {
- @Override
- public File call() throws Exception {
- return inputFiles.call().iterator().next();
- }
- });
-
- proguardComponentsTask.outjars(scope.getProguardComponentsJarFile());
-
- proguardComponentsTask.printconfiguration(
- scope.getGlobalScope().getBuildDir() + "/" + FD_INTERMEDIATES
- + "/multi-dex/" + scope.getVariantConfiguration().getDirName()
- + "/components.flags");
- } catch (ParseException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProcessJavaResConfigAction.groovy b/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProcessJavaResConfigAction.groovy
deleted file mode 100644
index a3d5ae2..0000000
--- a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProcessJavaResConfigAction.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks.factory
-
-import com.android.build.gradle.api.AndroidSourceSet
-import com.android.build.gradle.internal.scope.ConventionMappingHelper
-import com.android.build.gradle.internal.scope.TaskConfigAction
-import com.android.build.gradle.internal.scope.VariantScope
-import com.android.builder.model.SourceProvider
-import org.gradle.api.tasks.Sync
-
-import static com.android.builder.core.VariantType.ANDROID_TEST
-
-/**
- * Configuration Action for a ProcessJavaRes task.
- */
-class ProcessJavaResConfigAction implements TaskConfigAction<Sync> {
-
- VariantScope scope;
-
- ProcessJavaResConfigAction(VariantScope scope) {
- this.scope = scope
- }
-
- @Override
- String getName() {
- return scope.getTaskName("process", "JavaRes");
- }
-
- @Override
- Class<Sync> getType() {
- return Sync.class
- }
-
- @Override
- void execute(Sync processResources) {
- scope.variantData.processJavaResourcesTask = processResources
-
- // set the input
- processResources.from(((AndroidSourceSet) scope.variantConfiguration.defaultSourceSet).resources.
- getSourceFiles())
-
- if (scope.variantConfiguration.type != ANDROID_TEST) {
- processResources.from(
- ((AndroidSourceSet) scope.variantConfiguration.buildTypeSourceSet).resources.
- getSourceFiles())
- }
- if (scope.variantConfiguration.hasFlavors()) {
- for (SourceProvider flavorSourceSet : scope.variantConfiguration.flavorSourceProviders) {
- processResources.
- from(((AndroidSourceSet) flavorSourceSet).resources.getSourceFiles())
- }
- }
-
- ConventionMappingHelper.map(processResources, "destinationDir") {
- new File(scope.getSourceFoldersJavaResDestinationDir(), "src");
- }
-
- }
-}
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/CompileOptionsTest.java b/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/CompileOptionsTest.java
deleted file mode 100644
index 37e7a86..0000000
--- a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/CompileOptionsTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import static org.junit.Assert.assertEquals;
-
-import org.gradle.api.JavaVersion;
-import org.junit.Test;
-
-/**
- * Tests for {@link CompileOptions}
- */
-public class CompileOptionsTest {
-
- @Test
- public void sourceCompatibilityTest() {
- CompileOptions options = new CompileOptions();
-
- assertEquals(options.getDefaultJavaVersion(), options.getSourceCompatibility());
-
- options.setSourceCompatibility("1.6");
- assertEquals(JavaVersion.VERSION_1_6, options.getSourceCompatibility());
-
- options.setSourceCompatibility(1.6);
- assertEquals(JavaVersion.VERSION_1_6, options.getSourceCompatibility());
-
- options.setSourceCompatibility(JavaVersion.VERSION_1_7);
- assertEquals(JavaVersion.VERSION_1_7, options.getSourceCompatibility());
-
- options.setSourceCompatibility("Version_1_7");
- assertEquals(JavaVersion.VERSION_1_7, options.getSourceCompatibility());
-
- options.setSourceCompatibility("VERSION_1_7");
- assertEquals(JavaVersion.VERSION_1_7, options.getSourceCompatibility());
- }
-
- @Test
- public void targetCompatibilityTest() {
- CompileOptions options = new CompileOptions();
-
- assertEquals(options.getDefaultJavaVersion(), options.getTargetCompatibility());
-
- options.setTargetCompatibility("1.6");
- assertEquals(JavaVersion.VERSION_1_6, options.getTargetCompatibility());
-
- options.setTargetCompatibility(1.6);
- assertEquals(JavaVersion.VERSION_1_6, options.getTargetCompatibility());
-
- options.setTargetCompatibility(JavaVersion.VERSION_1_7);
- assertEquals(JavaVersion.VERSION_1_7, options.getTargetCompatibility());
-
- options.setTargetCompatibility("Version_1_7");
- assertEquals(JavaVersion.VERSION_1_7, options.getTargetCompatibility());
-
- options.setTargetCompatibility("VERSION_1_7");
- assertEquals(JavaVersion.VERSION_1_7, options.getTargetCompatibility());
- }
-}
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/tasks/MergeJavaResourcesTaskTest.java b/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/tasks/MergeJavaResourcesTaskTest.java
deleted file mode 100644
index 712c949..0000000
--- a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/tasks/MergeJavaResourcesTaskTest.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.PackagingOptions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.io.Files;
-
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.Charset;
-
-/**
- * Tests for {@link MergeJavaResourcesTaskTest}
- */
-public class MergeJavaResourcesTaskTest {
-
- @Mock
- PackagingOptions packagingOptions;
-
- @Mock
- MergeJavaResourcesTask task;
-
- MergeJavaResourcesTask.FileFilter fileFilter;
-
- private static File sPackagedJarExpansionFolder, sMergedFolder;
- private static File sExpandedJar1, sExpandedJar2, sExpandedJar3;
-
- /**
- * Create temporary folders to simulate expanded folders with java resources embedded
- */
- @BeforeClass
- public static void prepareFolders() throws IOException {
- sPackagedJarExpansionFolder = createTmpFolder(null /* parent */);
-
- sExpandedJar1 = createTmpFolder(sPackagedJarExpansionFolder);
- sExpandedJar2 = createTmpFolder(sPackagedJarExpansionFolder);
- sExpandedJar3 = createTmpFolder(sPackagedJarExpansionFolder);
-
- sMergedFolder = createTmpFolder(null /* parent */);
- }
-
- /**
- * After each test, all folders are cleaned.
- */
- @After
- public void cleanUp() {
- cleanContents(sExpandedJar1);
- cleanContents(sExpandedJar2);
- cleanContents(sExpandedJar3);
- cleanContents(sMergedFolder);
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(task.getExpandedFolders()).thenReturn(ImmutableList.of(sPackagedJarExpansionFolder));
- assertTrue(listFiles(sMergedFolder).length == 0);
- }
-
- @Test
- public void testSimpleCopy() throws IOException {
-
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- assertFalse(new File(sMergedFolder, "foo/text.properties").exists());
- File changedFile = createFile(sExpandedJar1, "foo/text.properties");
- fileFilter.handleChanged(sMergedFolder, changedFile);
- assertTrue(new File(sMergedFolder, "foo/text.properties").exists());
- }
-
- @Test
- public void testSimpleExclusion() throws IOException {
-
- when(packagingOptions.getExcludes()).thenReturn(
- ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
- assertTrue(listFiles(sMergedFolder).length == 0);
- }
-
- @Test
- public void testExclusionFromMultipleFiles() throws IOException {
- when(packagingOptions.getExcludes()).thenReturn(
- ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "foo/text.properties"));
- assertTrue(listFiles(sMergedFolder).length == 0);
- }
-
- @Test
- public void testMultipleExclusions() throws IOException {
- when(packagingOptions.getExcludes()).thenReturn(
- ImmutableSet.of("foo/text.properties", "bar/other.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "bar/other.properties"));
- assertTrue(listFiles(sMergedFolder).length == 0);
- }
-
- @Test
- public void textNonExclusion() throws IOException {
- when(packagingOptions.getExcludes()).thenReturn(
- ImmutableSet.of("foo/text.properties", "bar/other.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar1, "foo/text.properties"));
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "bar/other.properties"));
- // this one should be copied over.
- fileFilter.handleChanged(sMergedFolder, createFile(sExpandedJar2, "bar/foo.properties"));
- assertTrue(listFiles(sMergedFolder).length == 1);
- assertTrue(new File(sMergedFolder, "bar/foo.properties").exists());
- }
-
- @Test
- public void testSingleMerge() throws IOException {
- when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- createFile(sExpandedJar1, "foo/text.properties", "one");
- File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
-
- // one has changed...
- fileFilter.handleChanged(sMergedFolder, secondFile);
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "two"));
- }
-
- @Test
- public void testMultipleMerges() throws IOException {
- when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- createFile(sExpandedJar1, "foo/text.properties", "one");
- File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
- createFile(sExpandedJar3, "foo/text.properties", "three");
-
- // one has changed...
- fileFilter.handleChanged(sMergedFolder, secondFile);
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "two", "three"));
- }
-
- @Test
- public void testMergeAddon() throws IOException {
- when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- createFile(sExpandedJar1, "foo/text.properties", "one");
- File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
-
- // simulate one has changed to create initial version
- fileFilter.handleChanged(sMergedFolder, secondFile);
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "two"));
-
- // add a new one.
- File thirdFile = createFile(sExpandedJar3, "foo/text.properties", "three");
- fileFilter.handleChanged(sMergedFolder, thirdFile);
-
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "two", "three"));
- }
-
- @Test
- public void testMergeUpdate() throws IOException {
- when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- createFile(sExpandedJar1, "foo/text.properties", "one");
- File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
- createFile(sExpandedJar3, "foo/text.properties", "three");
-
- // simulate one has changed to create initial version
- fileFilter.handleChanged(sMergedFolder, secondFile);
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "two", "three"));
-
- // change one...
- assertTrue(secondFile.delete());
- secondFile = createFile(sExpandedJar2, "foo/text.properties", "deux");
-
- fileFilter.handleChanged(sMergedFolder, secondFile);
-
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "deux", "three"));
- }
-
- @Test
- public void testMergeRemoval() throws IOException {
- when(packagingOptions.getMerges()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- createFile(sExpandedJar1, "foo/text.properties", "one");
- File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
- createFile(sExpandedJar3, "foo/text.properties", "three");
-
- // simulate one has changed to create initial version
- fileFilter.handleChanged(sMergedFolder, secondFile);
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "two", "three"));
-
- // remove one...
- assertTrue(secondFile.delete());
-
- fileFilter.handleRemoved(sMergedFolder, secondFile);
-
- assertTrue(mergedFile.exists());
- assertContentInAnyOrder(
- Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
- , ImmutableList.of("one", "three"));
- }
-
- @Test
- public void testPickFirst() throws IOException {
- when(packagingOptions.getPickFirsts()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- // simulate the three elements were added.
- fileFilter.handleChanged(sMergedFolder,
- createFile(sExpandedJar1, "foo/text.properties", "one"));
- fileFilter.handleChanged(sMergedFolder,
- createFile(sExpandedJar2, "foo/text.properties", "two"));
- fileFilter.handleChanged(sMergedFolder,
- createFile(sExpandedJar3, "foo/text.properties", "three"));
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
- assertTrue(mergedContent.equals("one")
- || mergedContent.equals("two")
- || mergedContent.equals("three"));
- }
-
- @Test
- public void testPickFirstUpdate() throws IOException {
- when(packagingOptions.getPickFirsts()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- File firstFile = createFile(sExpandedJar1, "foo/text.properties", "one");
- File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
- File thirdFile = createFile(sExpandedJar3, "foo/text.properties", "three");
-
- // simulate the three elements were added.
- fileFilter.handleChanged(sMergedFolder, firstFile);
- fileFilter.handleChanged(sMergedFolder, secondFile);
- fileFilter.handleChanged(sMergedFolder, thirdFile);
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
- if (mergedContent.equals("one")) {
- assertTrue(firstFile.delete());
- createFile(sExpandedJar1, "foo/text.properties", "un");
- fileFilter.handleChanged(sMergedFolder, firstFile);
- assertEquals("un", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
- }
- if (mergedContent.equals("two")) {
- assertTrue(thirdFile.delete());
- createFile(sExpandedJar2, "foo/text.properties", "deux");
- fileFilter.handleChanged(sMergedFolder, secondFile);
- assertEquals("deux", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
- }
- if (mergedContent.equals("three")) {
- assertTrue(thirdFile.delete());
- createFile(sExpandedJar3, "foo/text.properties", "trois");
- fileFilter.handleChanged(sMergedFolder, thirdFile);
- assertEquals("trois", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
- }
- }
-
- @Test
- public void testPickFirstRemoval() throws IOException {
- when(packagingOptions.getPickFirsts()).thenReturn(ImmutableSet.of("foo/text.properties"));
- fileFilter = new MergeJavaResourcesTask.FileFilter(task, packagingOptions);
-
- File firstFile = createFile(sExpandedJar1, "foo/text.properties", "one");
- File secondFile = createFile(sExpandedJar2, "foo/text.properties", "two");
- File thirdFile = createFile(sExpandedJar3, "foo/text.properties", "three");
-
- // simulate the three elements were added.
- fileFilter.handleChanged(sMergedFolder, firstFile);
- fileFilter.handleChanged(sMergedFolder, secondFile);
- fileFilter.handleChanged(sMergedFolder, thirdFile);
-
- File mergedFile = new File(sMergedFolder, "foo/text.properties");
- assertTrue(mergedFile.exists());
- String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
- if (mergedContent.equals("one")) {
- assertTrue(firstFile.delete());
- fileFilter.handleRemoved(sMergedFolder, firstFile);
- mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
- assertTrue(mergedContent.equals("two") || mergedContent.equals("three"));
- }
- if (mergedContent.equals("two")) {
- assertTrue(thirdFile.delete());
- fileFilter.handleRemoved(sMergedFolder, secondFile);
- mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
- assertTrue(mergedContent.equals("one") || mergedContent.equals("three"));
- }
- if (mergedContent.equals("three")) {
- assertTrue(thirdFile.delete());
- fileFilter.handleRemoved(sMergedFolder, thirdFile);
- mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
- assertTrue(mergedContent.equals("one") || mergedContent.equals("two"));
- }
- }
-
- private static void assertContentInAnyOrder(String content, Iterable<String> subStrings) {
- int length = 0;
- for (String subString : subStrings) {
- length += subString.length();
- assertTrue(content.contains(subString));
- }
- assertEquals(length, content.length());
- }
-
- private static File createTmpFolder(@Nullable File parent) throws IOException {
- File folder = File.createTempFile("tmp", "dir");
- assertTrue(folder.delete());
- if (parent != null) {
- folder = new File(parent, folder.getName());
- }
- assertTrue(folder.mkdirs());
- return folder;
- }
-
- @NonNull private static File[] listFiles(@NonNull File folder) {
- File[] files = folder.listFiles();
- if (files == null) {
- return new File[0];
- }
- return files;
- }
-
- private static void cleanContents(File folder) {
- for (File f : listFiles(folder)) {
- if (f.isDirectory()) {
- cleanContents(f);
- }
- assertTrue(f.delete());
- }
- }
-
- @NonNull private static File createFile(
- @NonNull File parent,
- @NonNull String archivePath) throws IOException {
- return createFile(parent, archivePath, null /* content */);
- }
-
- @NonNull private static File createFile(
- @NonNull File parent,
- @NonNull String archivePath,
- @Nullable String content) throws IOException {
-
- File newFile = new File(parent, archivePath);
- if (!newFile.getParentFile().exists()) {
- assertTrue(newFile.getParentFile().mkdirs());
- }
- String fileContent = content == null ? "test!" : content;
- Files.append(fileContent, newFile, Charset.defaultCharset());
- return newFile;
- }
-}
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/tasks/multidex/RetraceMainDexListTest.java b/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/tasks/multidex/RetraceMainDexListTest.java
deleted file mode 100644
index ebe9353..0000000
--- a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/tasks/multidex/RetraceMainDexListTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.tasks.multidex;
-
-import com.google.common.collect.Lists;
-
-import junit.framework.TestCase;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.List;
-import java.util.Map;
-
-/**
- */
-public class RetraceMainDexListTest extends TestCase {
-
- public void testCreateDict() throws IOException {
- InputStream stream = getClass().getResourceAsStream("mapping.txt");
- assertNotNull(stream);
-
- BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
- try {
- List<String> lines = Lists.newArrayList();
-
- String line;
- while ((line = reader.readLine()) != null) {
- lines.add(line);
- }
-
- Map<String, String> map = RetraceMainDexList.createDict(lines);
-
- assertEquals("android/support/multidex/MultiDex.class", map.get("android/support/a/a.class"));
- assertEquals("android/support/multidex/MultiDex$V4.class", map.get("android/support/a/d.class"));
- } finally {
- reader.close();
- }
- }
-}
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/profiling/RecordingBuildListenerTest.groovy b/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/profiling/RecordingBuildListenerTest.groovy
deleted file mode 100644
index b949f5c..0000000
--- a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/profiling/RecordingBuildListenerTest.groovy
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.profiling
-
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.gradle.internal.profile.RecordingBuildListener
-import com.android.builder.profile.ExecutionRecord
-import com.android.builder.profile.ExecutionType
-import com.android.builder.profile.ProcessRecorder
-import com.android.builder.profile.ProcessRecorderFactory
-import com.android.builder.profile.Recorder
-import com.android.builder.profile.ThreadRecorder
-import com.android.utils.ILogger
-import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.api.tasks.TaskState
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-import java.util.logging.Logger
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.concurrent.CopyOnWriteArrayList
-import java.util.concurrent.atomic.AtomicLong
-
-import static org.junit.Assert.assertNotNull
-import static org.mockito.Mockito.when
-
-/**
- * Tests for {@link RecordingBuildListener}
- */
-class RecordingBuildListenerTest {
-
- @Mock
- Task mTask
-
- @Mock
- TaskState mTaskState
-
- @Mock
- Project mProject
-
- @Mock
- ILogger logger;
-
- private static final class TestRecorder implements Recorder {
-
- final AtomicLong recordId = new AtomicLong(0);
- final List<ExecutionRecord> records = new CopyOnWriteArrayList<>();
-
- @Override
- def <T> T record(@NonNull ExecutionType executionType, @NonNull Recorder.Block<T> block,
- Recorder.Property... properties) {
- throw new UnsupportedOperationException("record method was not supposed to be called.")
- }
-
- @Override
- def <T> T record(@NonNull ExecutionType executionType, @NonNull Recorder.Block<T> block,
- @NonNull List<Recorder.Property> properties) {
- throw new UnsupportedOperationException("record method was not supposed to be called.")
- }
-
- @Override
- long allocationRecordId() {
- return recordId.incrementAndGet()
- }
-
- @Override
- void closeRecord(ExecutionRecord record) {
- records.add(record);
- }
- }
-
- private static final class TestExecutionRecordWriter implements ProcessRecorder.ExecutionRecordWriter {
-
- final List<ExecutionRecord> records = new CopyOnWriteArrayList<>()
-
- @Override
- void write(@NonNull ExecutionRecord executionRecord) throws IOException {
- records.add(executionRecord)
- }
-
- List<ExecutionRecord> getRecords() {
- return records
- }
-
- @Override
- void close() throws IOException {
- }
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mTask.getName()).thenReturn("taskName")
- when(mTask.getProject()).thenReturn(mProject)
- when(mProject.getName()).thenReturn("projectName")
- }
-
- @Test
- public void "single thread invocation"() {
- def recorder = new TestRecorder();
- RecordingBuildListener listener = new RecordingBuildListener(recorder);
-
- listener.beforeExecute(mTask);
- listener.afterExecute(mTask, mTaskState)
- assertEquals(1, recorder.records.size())
- ExecutionRecord record = recorder.records.get(0)
- assertEquals(1, record.id)
- assertEquals(0, record.parentId)
- assertEquals(2, record.attributes.size())
- ensurePropertyValue(record.attributes, "task", "taskName")
- ensurePropertyValue(record.attributes, "project", "projectName")
- }
-
- @Test
- public void "single thread with multiple spans invocation"() {
-
- TestExecutionRecordWriter recordWriter = new TestExecutionRecordWriter()
- ProcessRecorderFactory.initializeForTests(recordWriter)
-
- RecordingBuildListener listener = new RecordingBuildListener(ThreadRecorder.get());
-
- listener.beforeExecute(mTask);
- ThreadRecorder.get().record(ExecutionType.SOME_RANDOM_PROCESSING) {
- Logger.getAnonymousLogger().finest("useless block")
- }
- listener.afterExecute(mTask, mTaskState)
- ProcessRecorderFactory.shutdown()
-
- assertEquals(4, recordWriter.getRecords().size())
- ExecutionRecord record = getRecordForId(recordWriter.getRecords(), 2)
- assertEquals(2, record.id)
- assertEquals(0, record.parentId)
- assertEquals(2, record.attributes.size())
- ensurePropertyValue(record.attributes, "task", "taskName")
- ensurePropertyValue(record.attributes, "project", "projectName")
-
- record = getRecordForId(recordWriter.getRecords(), 3)
- assertNotNull(record);
- assertEquals(3, record.id)
- assertEquals(2, record.parentId)
- assertEquals(0, record.attributes.size())
- assertEquals(ExecutionType.SOME_RANDOM_PROCESSING, record.type)
- }
-
- @Test
- public void "test initial and final records."() {
-
- TestExecutionRecordWriter recordWriter = new TestExecutionRecordWriter()
- ProcessRecorderFactory.initializeForTests(recordWriter)
-
- ProcessRecorderFactory.shutdown()
-
- assertEquals(2, recordWriter.getRecords().size())
- for (ExecutionRecord record : recordWriter.getRecords()) {
- System.out.println(record);
- }
- ExecutionRecord record = getRecordForId(recordWriter.getRecords(), 1)
- assertEquals(1, record.id)
- assertEquals(0, record.parentId)
- assertEquals(6, record.attributes.size())
- assertEquals(ExecutionType.INITIAL_METADATA, record.type)
- ensurePropertyValue(record.attributes, "os_name", System.getProperty("os.name"))
-
- record = getRecordForId(recordWriter.getRecords(), 2)
- assertNotNull(record);
- assertEquals(2, record.id)
- assertEquals(0, record.parentId)
- assertEquals(3, record.attributes.size())
- assertEquals(ExecutionType.FINAL_METADATA, record.type)
- }
-
- @Test
- public void "multiple threads invocation"() {
- def recorder = new TestRecorder();
- RecordingBuildListener listener = new RecordingBuildListener(recorder)
- Task secondTask = Mockito.mock(Task.class)
- when(secondTask.getName()).thenReturn("secondTaskName")
- when(secondTask.getProject()).thenReturn(mProject)
-
- // first thread start
- listener.beforeExecute(mTask);
-
- // now second threads start
- listener.beforeExecute(secondTask)
-
- // first thread finishes
- listener.afterExecute(mTask, mTaskState)
-
- // and second thread finishes
- listener.afterExecute(secondTask, mTaskState)
-
- assertEquals(2, recorder.records.size())
- ExecutionRecord record = getRecordForId(recorder.records, 1)
- assertEquals(1, record.id)
- assertEquals(0, record.parentId)
- assertEquals(2, record.attributes.size())
- ensurePropertyValue(record.attributes, "task", "taskName")
- ensurePropertyValue(record.attributes, "project", "projectName")
-
- record = getRecordForId(recorder.records, 2)
- assertEquals(2, record.id)
- assertEquals(0, record.parentId)
- assertEquals(2, record.attributes.size())
- ensurePropertyValue(record.attributes, "task", "secondTaskName")
- ensurePropertyValue(record.attributes, "project", "projectName")
- }
-
- @Test
- public void "multiple threads order invocation"() {
- def recorder = new TestRecorder();
- RecordingBuildListener listener = new RecordingBuildListener(recorder)
- Task secondTask = Mockito.mock(Task.class)
- when(secondTask.getName()).thenReturn("secondTaskName")
- when(secondTask.getProject()).thenReturn(mProject)
-
- // first thread start
- listener.beforeExecute(mTask);
-
- // now second threads start
- listener.beforeExecute(secondTask)
-
- // second thread finishes
- listener.afterExecute(secondTask, mTaskState)
-
- // and first thread finishes
- listener.afterExecute(mTask, mTaskState)
-
- assertEquals(2, recorder.records.size())
- ExecutionRecord record = getRecordForId(recorder.records, 1)
- assertEquals(1, record.id)
- assertEquals(0, record.parentId)
- assertEquals(2, record.attributes.size())
- ensurePropertyValue(record.attributes, "task", "taskName")
- ensurePropertyValue(record.attributes, "project", "projectName")
-
- record = getRecordForId(recorder.records, 2)
- assertEquals(2, record.id)
- assertEquals(0, record.parentId)
- assertEquals(2, record.attributes.size())
- ensurePropertyValue(record.attributes, "task", "secondTaskName")
- ensurePropertyValue(record.attributes, "project", "projectName")
- }
-
- private static void ensurePropertyValue(
- List<Recorder.Property> properties, String name, String value) {
-
- for (Recorder.Property property : properties) {
- if (property.getName().equals(name)){
- assertEquals(value, property.getValue())
- }
- }
- }
-
- @Nullable
- private static ExecutionRecord getRecordForId(List<ExecutionRecord> records, long recordId) {
- for (ExecutionRecord record : records) {
- if (record.id == recordId) {
- return record
- }
- }
- return null;
- }
-}
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzerTest.java b/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzerTest.java
deleted file mode 100644
index 262af32..0000000
--- a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzerTest.java
+++ /dev/null
@@ -1,1022 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import static com.android.build.gradle.tasks.ResourceUsageAnalyzer.NO_MATCH;
-import static com.android.build.gradle.tasks.ResourceUsageAnalyzer.Resource;
-import static com.android.build.gradle.tasks.ResourceUsageAnalyzer.convertFormatStringToRegexp;
-import static java.io.File.separatorChar;
-
-import com.android.annotations.NonNull;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-/** TODO: Test Resources#getIdentifier() handling */
- at SuppressWarnings("SpellCheckingInspection")
-public class ResourceUsageAnalyzerTest extends TestCase {
-
- public void testObfuscatedInPlace() throws Exception {
- check(true, true);
- }
-
- public void testObfuscatedCopy() throws Exception {
- check(true, false);
- }
-
- public void testNoProGuardInPlace() throws Exception {
- check(false, true);
- }
-
- public void testNoProGuardCopy() throws Exception {
- check(false, false);
- }
-
- private static void check(boolean useProguard, boolean inPlace) throws Exception {
- File dir = Files.createTempDir();
-
- File mapping;
- File classesJar;
- if (useProguard) {
- classesJar = createProguardedClasses(dir);
- mapping = createMappingFile(dir);
- } else {
- classesJar = createUnproguardedClasses(dir);
- mapping = null;
- }
- File rDir = createResourceClassFolder(dir);
- File mergedManifest = createMergedManifest(dir);
- File resources = createResourceFolder(dir);
-
- ResourceUsageAnalyzer analyzer = new ResourceUsageAnalyzer(rDir, classesJar,
- mergedManifest, mapping, resources);
- analyzer.analyze();
- checkState(analyzer);
- assertEquals(""
- + "@attr/myAttr1 : reachable=false\n"
- + "@attr/myAttr2 : reachable=false\n"
- + "@dimen/activity_horizontal_margin : reachable=true\n"
- + "@dimen/activity_vertical_margin : reachable=true\n"
- + "@drawable/ic_launcher : reachable=true\n"
- + "@drawable/unused : reachable=false\n"
- + "@id/action_settings : reachable=true\n"
- + "@layout/activity_main : reachable=true\n"
- + " @dimen/activity_vertical_margin\n"
- + " @dimen/activity_horizontal_margin\n"
- + " @string/hello_world\n"
- + " @style/MyStyle_Child\n"
- + "@menu/main : reachable=true\n"
- + " @id/action_settings\n"
- + " @string/action_settings\n"
- + "@raw/android_wear_micro_apk : reachable=true\n"
- + "@raw/index1 : reachable=false\n"
- + "@raw/my_js : reachable=false\n"
- + "@raw/my_used_raw_drawable : reachable=true\n"
- + "@raw/styles2 : reachable=false\n"
- + "@string/action_settings : reachable=true\n"
- + "@string/alias : reachable=false\n"
- + " @string/app_name\n"
- + "@string/app_name : reachable=true\n"
- + "@string/hello_world : reachable=true\n"
- + "@style/AppTheme : reachable=false\n"
- + "@style/MyStyle : reachable=true\n"
- + "@style/MyStyle_Child : reachable=true\n"
- + " @style/MyStyle\n"
- + "@xml/android_wear_micro_apk : reachable=true\n"
- + " @raw/android_wear_micro_apk\n",
- analyzer.dumpResourceModel());
-
- File unusedBitmap = new File(resources, "drawable-xxhdpi" + separatorChar + "unused.png");
- assertTrue(unusedBitmap.exists());
-
- if (ResourceUsageAnalyzer.TWO_PASS_AAPT) {
- File destination = inPlace ? null : Files.createTempDir();
-
- analyzer.setDryRun(true);
- analyzer.removeUnused(destination);
- assertTrue(unusedBitmap.exists());
-
- analyzer.setDryRun(false);
- analyzer.removeUnused(destination);
-
- if (inPlace) {
- assertFalse(unusedBitmap.exists());
- } else {
- assertTrue(unusedBitmap.exists());
- assertTrue(new File(destination, "values" + separatorChar + "values.xml").exists());
- assertFalse(new File(destination, "drawable-xxhdpi" + separatorChar +
- "unused.png").exists());
- }
- File values = new File(inPlace ? resources : destination,
- "values" + separatorChar + "values.xml");
- assertTrue(values.exists());
- assertEquals(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + "\n"
- + " <attr name=\"myAttr1\" format=\"integer\" />\n"
- + " <attr name=\"myAttr2\" format=\"boolean\" />\n"
- + "\n"
- + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
- + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n"
- + "\n"
- + " <string name=\"action_settings\">Settings</string>\n"
- + " <string name=\"app_name\">ShrinkUnitTest</string>\n"
- + " <string name=\"hello_world\">Hello world!</string>\n"
- + "\n"
- + "</resources>",
-
- Files.toString(values, Charsets.UTF_8));
- if (destination != null) {
- deleteDir(destination);
- }
- } else {
- List<File> files = Lists.newArrayList();
- addFiles(resources, files);
- Collections.sort(files, new Comparator<File>() {
-
- @Override
- public int compare(File file, File file2) {
- return file.getPath().compareTo(file2.getPath());
- }
- });
-
- // Generate a .zip file from a directory
- File uncompressedFile = File.createTempFile("uncompressed", ".ap_");
- String prefix = resources.getPath() + File.separatorChar;
- FileOutputStream fos = new FileOutputStream(uncompressedFile);
- ZipOutputStream zos = new ZipOutputStream(fos);
- for (File file : files) {
- if (file.equals(resources)) {
- continue;
- }
- assertTrue(file.getPath().startsWith(prefix));
- String relative = "res/" + file.getPath().substring(prefix.length())
- .replace(File.separatorChar, '/');
- boolean isValuesFile = relative.equals("res/values/values.xml");
- if (isValuesFile) {
- relative = "resources.arsc";
- }
- ZipEntry ze = new ZipEntry(relative);
- zos.putNextEntry(ze);
- if (!file.isDirectory() && !isValuesFile) {
- byte[] bytes = Files.toByteArray(file);
- zos.write(bytes);
- }
- zos.closeEntry();
- }
- zos.close();
- fos.close();
-
- assertEquals(""
- + "res/drawable-hdpi\n"
- + "res/drawable-hdpi/ic_launcher.png\n"
- + "res/drawable-mdpi\n"
- + "res/drawable-mdpi/ic_launcher.png\n"
- + "res/drawable-xxhdpi\n"
- + "res/drawable-xxhdpi/ic_launcher.png\n"
- + "res/drawable-xxhdpi/unused.png\n"
- + "res/layout\n"
- + "res/layout/activity_main.xml\n"
- + "res/menu\n"
- + "res/menu/main.xml\n"
- + "res/raw\n"
- + "res/raw/android_wear_micro_apk.apk\n"
- + "res/raw/index1.html\n"
- + "res/raw/my_js.js\n"
- + "res/raw/styles2.css\n"
- + "res/values\n"
- + "resources.arsc\n"
- + "res/xml\n"
- + "res/xml/android_wear_micro_apk.xml\n",
- dumpZipContents(uncompressedFile));
-
- System.out.println(uncompressedFile);
-
-
- File compressedFile = File.createTempFile("compressed", ".ap_");
-
- analyzer.rewriteResourceZip(uncompressedFile, compressedFile);
-
- // Check contents
- assertEquals(""
- + "res/drawable-hdpi\n"
- + "res/drawable-hdpi/ic_launcher.png\n"
- + "res/drawable-mdpi\n"
- + "res/drawable-mdpi/ic_launcher.png\n"
- + "res/drawable-xxhdpi\n"
- + "res/drawable-xxhdpi/ic_launcher.png\n"
- + "res/layout\n"
- + "res/layout/activity_main.xml\n"
- + "res/menu\n"
- + "res/menu/main.xml\n"
- + "res/raw\n"
- + "res/raw/android_wear_micro_apk.apk\n"
- + "res/values\n"
- + "resources.arsc\n"
- + "res/xml\n"
- + "res/xml/android_wear_micro_apk.xml\n",
- dumpZipContents(compressedFile));
-
- uncompressedFile.delete();
- compressedFile.delete();
- }
-
- deleteDir(dir);
- }
-
- private static String dumpZipContents(File zipFile) throws IOException {
- StringBuilder sb = new StringBuilder();
-
- FileInputStream fis = new FileInputStream(zipFile);
- try {
- ZipInputStream zis = new ZipInputStream(fis);
- try {
- ZipEntry entry = zis.getNextEntry();
- while (entry != null) {
- sb.append(entry.getName());
- sb.append('\n');
- entry = zis.getNextEntry();
- }
- } finally {
- zis.close();
- }
- } finally {
- fis.close();
- }
-
- return sb.toString();
- }
-
- private static void addFiles(File file, List<File> files) {
- files.add(file);
- if (file.isDirectory()) {
- File[] list = file.listFiles();
- if (list != null) {
- for (File f : list) {
- addFiles(f, files);
- }
- }
- }
- }
-
- private static File createResourceFolder(File dir) throws IOException {
- File resources = new File(dir, "app/build/res/all/release".replace('/', separatorChar));
- //noinspection ResultOfMethodCallIgnored
- resources.mkdirs();
-
- createFile(resources, "drawable-hdpi/ic_launcher.png", new byte[0]);
- createFile(resources, "drawable-mdpi/ic_launcher.png", new byte[0]);
- createFile(resources, "drawable-xxhdpi/ic_launcher.png", new byte[0]);
- createFile(resources, "drawable-xxhdpi/unused.png", new byte[0]);
-
- createFile(resources, "layout/activity_main.xml", ""
- + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
- + " android:layout_width=\"match_parent\"\n"
- + " android:layout_height=\"match_parent\"\n"
- + " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n"
- + " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n"
- + " android:paddingTop=\"@dimen/activity_vertical_margin\"\n"
- + " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n"
- + " tools:context=\".MainActivity\">\n"
- + "\n"
- + " <TextView\n"
- + " style=\"@style/MyStyle.Child\"\n"
- + " android:text=\"@string/hello_world\"\n"
- + " android:layout_width=\"wrap_content\"\n"
- + " android:layout_height=\"wrap_content\" />\n"
- + "\n"
- + "</RelativeLayout>");
-
- createFile(resources, "menu/main.xml", ""
- + "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
- + " tools:context=\".MainActivity\" >\n"
- + " <item android:id=\"@+id/action_settings\"\n"
- + " android:title=\"@string/action_settings\"\n"
- + " android:orderInCategory=\"100\"\n"
- + " android:showAsAction=\"never\" />\n"
- + "</menu>");
-
- createFile(resources, "values/values.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + "\n"
- + " <attr name=\"myAttr1\" format=\"integer\" />\n"
- + " <attr name=\"myAttr2\" format=\"boolean\" />\n"
- + "\n"
- + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
- + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n"
- + "\n"
- + " <string name=\"action_settings\">Settings</string>\n"
- + " <string name=\"alias\"> @string/app_name </string>\n"
- + " <string name=\"app_name\">ShrinkUnitTest</string>\n"
- + " <string name=\"hello_world\">Hello world!</string>\n"
- + "\n"
- + " <style name=\"AppTheme\" parent=\"android:Theme.Holo\"></style>\n"
- + "\n"
- + " <style name=\"MyStyle\">\n"
- + " <item name=\"myAttr1\">50</item>\n"
- + " </style>\n"
- + "\n"
- + " <style name=\"MyStyle.Child\">\n"
- + " <item name=\"myAttr2\">true</item>\n"
- + " </style>\n"
- + "\n"
- + "</resources>");
-
- createFile(resources, "raw/android_wear_micro_apk.apk",
- "<binary data>");
-
- createFile(resources, "xml/android_wear_micro_apk.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<wearableApp package=\"com.example.shrinkunittest.app\">\n"
- + " <versionCode>1</versionCode>\n"
- + " <versionName>1.0' platformBuildVersionName='5.0-1521886</versionName>\n"
- + " <rawPathResId>android_wear_micro_apk</rawPathResId>\n"
- + "</wearableApp>");
-
- // RAW content for HTML/web
- createFile(resources, "raw/index1.html", ""
- // TODO: Test single quotes, attribute without quotes, spaces around = etc, prologue, xhtml
- + "<!DOCTYPE html>\n"
- + "<html>\n"
- + "<!--\n"
- + " Blah blah\n"
- + "-->\n"
- + "<head>\n"
- + " <meta charset=\"utf-8\">\n"
- + " <link href=\"http://fonts.googleapis.com/css?family=Alegreya:400italic,900italic|Alegreya+Sans:300\" rel=\"stylesheet\">\n"
- + " <link href=\"http://yui.yahooapis.com/2.8.0r4/build/reset/reset-min.css\" rel=\"stylesheet\">\n"
- + " <link href=\"static/landing.css\" rel=\"stylesheet\">\n"
- + " <script src=\"http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js\"></script>\n"
- + " <script src=\"static/modernizr.custom.14469.js\"></script>\n"
- + " <meta name=\"viewport\" content=\"width=690\">\n"
- + " <style type=\"text/css\">\n"
- + "html, body {\n"
- + " margin: 0;\n"
- + " height: 100%;\n"
- + " background-image: url(file:///android_res/raw/my_used_raw_drawable);\n"
- + "}\n"
- + "</style>"
- + "</head>\n"
- + "<body>\n"
- + "\n"
- + "<div id=\"container\">\n"
- + "\n"
- + " <div id=\"logo\"></div>\n"
- + "\n"
- + " <div id=\"text\">\n"
- + " <p>\n"
- + " More ignored text here\n"
- + " </p>\n"
- + " </div>\n"
- + "\n"
- + " <a id=\"playlink\" href=\"file/foo.png\"> </a>\n"
- + "</div>\n"
- + "<script>\n"
- + "\n"
- + "if (Modernizr.cssanimations &&\n"
- + " Modernizr.svg &&\n"
- + " Modernizr.csstransforms3d &&\n"
- + " Modernizr.csstransitions) {\n"
- + "\n"
- + " // progressive enhancement\n"
- + " $('#device-screen').css('display', 'block');\n"
- + " $('#device-frame').css('background-image', 'url( 'drawable-mdpi/tilted.png')' );\n"
- + " $('#opentarget').css('visibility', 'visible');\n"
- + " $('body').addClass('withvignette');\n"
- + "</script>\n"
- + "\n"
- + "</body>\n"
- + "</html>");
-
- createFile(resources, "raw/styles2.css", ""
- + "/**\n"
- + " * Copyright 2014 Google Inc.\n"
- + " */\n"
- + "\n"
- + "html, body {\n"
- + " margin: 0;\n"
- + " height: 100%;\n"
- + " -webkit-font-smoothing: antialiased;\n"
- + "}\n"
- + "#logo {\n"
- + " position: absolute;\n"
- + " left: 0;\n"
- + " top: 60px;\n"
- + " width: 250px;\n"
- + " height: 102px;\n"
- + " background-image: url(img2.png);\n"
- + " background-repeat: no-repeat;\n"
- + " background-size: contain;\n"
- + " opacity: 0.7;\n"
- + " z-index: 100;\n"
- + "}\n"
- + "device-frame {\n"
- + " position: absolute;\n"
- + " right: -70px;\n"
- + " top: 0;\n"
- + " width: 420px;\n"
- + " height: 500px;\n"
- + " background-image: url(tilted_fallback.jpg);\n"
- + " background-size: cover;\n"
- + " -webkit-user-select: none;\n"
- + " -moz-user-select: none;\n"
- + "}");
-
- createFile(resources, "raw/my_js.js", ""
- + "function $(id) {\n"
- + " return document.getElementById(id);\n"
- + "}\n"
- + "\n"
- + "/* Ignored block comment: \"ignore me\" */\n"
- + "function show(id) {\n"
- + " $(id).style.display = \"block\";\n"
- + "}\n"
- + "\n"
- + "function hide(id) {\n"
- + " $(id).style.display = \"none\";\n"
- + "}\n"
- + "// Line comment\n"
- + "function onStatusBoxFocus(elt) {\n"
- + " elt.value = '';\n"
- + " elt.style.color = \"#000\";\n"
- + " show('status_submit');\n"
- + "}\n");
-
- return resources;
- }
-
- private static File createMergedManifest(File dir) throws IOException {
- return createFile(dir, "app/build/manifests/release/AndroidManifest.xml", ""
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" android:versionCode=\"1\" android:versionName=\"1.0\" package=\"com.example.shrinkunittest.app\">\n"
- + " <uses-sdk android:minSdkVersion=\"20\" android:targetSdkVersion=\"19\"/>\n"
- + "\n"
- + " <application android:allowBackup=\"true\" android:icon=\"@drawable/ic_launcher\" android:label=\"@string/app_name\">\n"
- + " <activity android:label=\"@string/app_name\" android:name=\"com.example.shrinkunittest.app.MainActivity\">\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\"/>\n"
- + "\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " <meta-data\n"
- + " android:name=\"com.google.android.wearable.beta.app\"\n"
- + " android:resource=\"@xml/android_wear_micro_apk\" />"
- + " </application>\n"
- + "\n"
- + "</manifest>");
- }
-
- private static File createResourceClassFolder(File dir) throws IOException {
- File rDir = new File(dir, "app/build/source/r/release".replace('/', separatorChar));
- //noinspection ResultOfMethodCallIgnored
- rDir.mkdirs();
-
- createFile(rDir, "com/example/shrinkunittest/app/R.java", ""
- + "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
- + " *\n"
- + " * This class was automatically generated by the\n"
- + " * aapt tool from the resource data it found. It\n"
- + " * should not be modified by hand.\n"
- + " */\n"
- + "\n"
- + "package com.example.shrinkunittest.app;\n"
- + "\n"
- + "public final class R {\n"
- + " public static final class attr {\n"
- + " /** <p>Must be an integer value, such as \"<code>100</code>\".\n"
- + "<p>This may also be a reference to a resource (in the form\n"
- + "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
- + "theme attribute (in the form\n"
- + "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
- + "containing a value of this type.\n"
- + " */\n"
- + " public static final int myAttr1=0x7f010000;\n"
- + " /** <p>Must be a boolean value, either \"<code>true</code>\" or \"<code>false</code>\".\n"
- + "<p>This may also be a reference to a resource (in the form\n"
- + "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
- + "theme attribute (in the form\n"
- + "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
- + "containing a value of this type.\n"
- + " */\n"
- + " public static final int myAttr2=0x7f010001;\n"
- + " }\n"
- + " public static final class dimen {\n"
- + " public static final int activity_horizontal_margin=0x7f040000;\n"
- + " public static final int activity_vertical_margin=0x7f040001;\n"
- + " }\n"
- + " public static final class drawable {\n"
- + " public static final int ic_launcher=0x7f020000;\n"
- + " public static final int unused=0x7f020001;\n"
- + " }\n"
- + " public static final class id {\n"
- + " public static final int action_settings=0x7f080000;\n"
- + " }\n"
- + " public static final class layout {\n"
- + " public static final int activity_main=0x7f030000;\n"
- + " }\n"
- + " public static final class menu {\n"
- + " public static final int main=0x7f070000;\n"
- + " }\n"
- + " public static final class raw {\n"
- + " public static final int android_wear_micro_apk=0x7f090000;\n"
- + " public static final int index1=0x7f090001;\n"
- + " public static final int styles2=0x7f090002;\n"
- + " public static final int my_js=0x7f090003;\n"
- + " public static final int my_used_raw_drawable=0x7f090004;\n"
- + " }"
- + " public static final class string {\n"
- + " public static final int action_settings=0x7f050000;\n"
- + " public static final int alias=0x7f050001;\n"
- + " public static final int app_name=0x7f050002;\n"
- + " public static final int hello_world=0x7f050003;\n"
- + " }\n"
- + " public static final class style {\n"
- + " public static final int AppTheme=0x7f060000;\n"
- + " public static final int MyStyle=0x7f060001;\n"
- + " public static final int MyStyle_Child=0x7f060002;\n"
- + " }\n"
- + " public static final class xml {\n"
- + " public static final int android_wear_micro_apk=0x7f0a0000;\n"
- + " }"
- + "}");
- return rDir;
- }
-
- private static File createProguardedClasses(File dir) throws IOException {
- byte[] bytecode = new byte[] {
- (byte)80, (byte)75, (byte)3, (byte)4, (byte)20, (byte)0, (byte)8, (byte)0,
- (byte)8, (byte)0, (byte)12, (byte)88, (byte)-62, (byte)68, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)49, (byte)0, (byte)0, (byte)0, (byte)99, (byte)111,
- (byte)109, (byte)47, (byte)101, (byte)120, (byte)97, (byte)109, (byte)112, (byte)108,
- (byte)101, (byte)47, (byte)115, (byte)104, (byte)114, (byte)105, (byte)110, (byte)107,
- (byte)117, (byte)110, (byte)105, (byte)116, (byte)116, (byte)101, (byte)115, (byte)116,
- (byte)47, (byte)97, (byte)112, (byte)112, (byte)47, (byte)77, (byte)97, (byte)105,
- (byte)110, (byte)65, (byte)99, (byte)116, (byte)105, (byte)118, (byte)105, (byte)116,
- (byte)121, (byte)46, (byte)99, (byte)108, (byte)97, (byte)115, (byte)115, (byte)117,
- (byte)-110, (byte)-53, (byte)78, (byte)-37, (byte)64, (byte)20, (byte)-122, (byte)-1,
- (byte)-63, (byte)14, (byte)14, (byte)97, (byte)32, (byte)36, (byte)36, (byte)-31,
- (byte)82, (byte)110, (byte)-31, (byte)-46, (byte)58, (byte)-15, (byte)-62, (byte)82,
- (byte)-73, (byte)-127, (byte)74, (byte)-112, (byte)-107, (byte)-91, (byte)-94, (byte)46,
- (byte)-112, (byte)88, (byte)-80, (byte)-77, (byte)-30, (byte)1, (byte)70, (byte)36,
- (byte)-29, (byte)40, (byte)-98, (byte)-92, (byte)101, (byte)-59, (byte)-85, (byte)-16,
- (byte)0, (byte)108, (byte)-70, (byte)73, (byte)-44, (byte)46, (byte)-6, (byte)0,
- (byte)60, (byte)20, (byte)-22, (byte)-103, (byte)-60, (byte)80, (byte)110, (byte)-99,
- (byte)-111, (byte)-50, (byte)57, (byte)-93, (byte)-1, (byte)59, (byte)23, (byte)-23,
- (byte)-52, (byte)-3, (byte)-61, (byte)-17, (byte)63, (byte)0, (byte)62, (byte)-61,
- (byte)-77, (byte)110, (byte)44, (byte)-64, (byte)-70, (byte)113, (byte)-116, (byte)-55,
- (byte)2, (byte)14, (byte)-74, (byte)28, (byte)84, (byte)29, (byte)108, (byte)59,
- (byte)-40, (byte)-55, (byte)-63, (byte)70, (byte)-34, (byte)-104, (byte)69, (byte)99,
- (byte)74, (byte)57, (byte)100, (byte)80, (byte)-52, (byte)17, (byte)81, (byte)48,
- (byte)-90, (byte)60, (byte)-117, (byte)105, (byte)44, (byte)112, (byte)108, (byte)96,
- (byte)-103, (byte)99, (byte)23, (byte)21, (byte)-114, (byte)61, (byte)44, (byte)113,
- (byte)124, (byte)-60, (byte)42, (byte)-57, (byte)39, (byte)124, (byte)-32, (byte)-88,
- (byte)97, (byte)-99, (byte)-93, (byte)-114, (byte)21, (byte)6, (byte)-53, (byte)-83,
- (byte)5, (byte)12, (byte)-21, (byte)110, (byte)-19, (byte)107, (byte)-88, (byte)-94,
- (byte)94, (byte)44, (byte)35, (byte)127, (byte)32, (byte)-59, (byte)119, (byte)-1,
- (byte)88, (byte)-88, (byte)126, (byte)-96, (byte)-50, (byte)-37, (byte)-95, (byte)22,
- (byte)-67, (byte)-58, (byte)-104, (byte)58, (byte)101, (byte)-80, (byte)-35, (byte)-64,
- (byte)-72, (byte)37, (byte)55, (byte)120, (byte)11, (byte)55, (byte)-116, (byte)82,
- (byte)113, (byte)-97, (byte)-124, (byte)56, (byte)-15, (byte)-113, (byte)-6, (byte)42,
- (byte)106, (byte)-117, (byte)-41, (byte)-62, (byte)-77, (byte)-116, (byte)51, (byte)-122,
- (byte)-43, (byte)119, (byte)-124, (byte)64, (byte)-117, (byte)-50, (byte)88, (byte)-100,
- (byte)-34, (byte)-105, (byte)74, (byte)-22, (byte)47, (byte)-44, (byte)-72, (byte)25,
- (byte)71, (byte)-126, (byte)-95, (byte)-12, (byte)-120, (byte)-122, (byte)-35, (byte)-82,
- (byte)127, (byte)-40, (byte)-46, (byte)114, (byte)32, (byte)-11, (byte)53, (byte)-61,
- (byte)-54, (byte)127, (byte)39, (byte)103, (byte)40, (byte)-65, (byte)91, (byte)-99,
- (byte)-63, (byte)107, (byte)-59, (byte)29, (byte)95, (byte)-4, (byte)8, (byte)59,
- (byte)-35, (byte)-74, (byte)-16, (byte)-109, (byte)-53, (byte)-98, (byte)84, (byte)87,
- (byte)125, (byte)-22, (byte)-91, (byte)69, (byte)-94, (byte)-57, (byte)-43, (byte)-113,
- (byte)67, (byte)-87, (byte)-2, (byte)117, (byte)-104, (byte)-71, (byte)16, (byte)-38,
- (byte)-28, (byte)5, (byte)17, (byte)67, (byte)-98, (byte)-30, (byte)-105, (byte)61,
- (byte)28, (byte)57, (byte)9, (byte)25, (byte)-78, (byte)-79, (byte)106, (byte)-10,
- (byte)-60, (byte)56, (byte)92, (byte)124, (byte)12, (byte)-65, (byte)117, (byte)-75,
- (byte)-116, (byte)85, (byte)98, (byte)82, (byte)104, (byte)-100, (byte)88, (byte)-91,
- (byte)111, (byte)83, (byte)-18, (byte)68, (byte)-76, (byte)69, (byte)75, (byte)11,
- (byte)42, (byte)58, (byte)-97, (byte)8, (byte)-35, (byte)-116, (byte)-107, (byte)22,
- (byte)74, (byte)-97, (byte)-46, (byte)-96, (byte)-88, (byte)-46, (byte)18, (byte)109,
- (byte)-104, (byte)99, (byte)-125, (byte)-103, (byte)53, (byte)-110, (byte)-35, (byte)-92,
- (byte)87, (byte)-127, (byte)60, (byte)35, (byte)-97, (byte)-87, (byte)-113, (byte)-112,
- (byte)-3, (byte)-103, (byte)2, (byte)-76, (byte)-47, (byte)84, (byte)94, (byte)-58,
- (byte)20, (byte)93, (byte)-128, (byte)-41, (byte)-67, (byte)17, (byte)102, (byte)-22,
- (byte)69, (byte)54, (byte)-60, (byte)-36, (byte)-124, (byte)98, (byte)112, (byte)-79,
- (byte)-10, (byte)68, (byte)89, (byte)41, (byte)53, (byte)4, (byte)47, (byte)78,
- (byte)121, (byte)67, (byte)-52, (byte)-38, (byte)119, (byte)41, (byte)69, (byte)31,
- (byte)35, (byte)-91, (byte)-86, (byte)-60, (byte)-48, (byte)-17, (byte)67, (byte)-39,
- (byte)-5, (byte)-123, (byte)121, (byte)-122, (byte)-125, (byte)-75, (byte)-94, (byte)117,
- (byte)-117, (byte)-116, (byte)125, (byte)103, (byte)74, (byte)-25, (byte)38, (byte)56,
- (byte)-2, (byte)2, (byte)80, (byte)75, (byte)7, (byte)8, (byte)39, (byte)-48,
- (byte)-69, (byte)-98, (byte)-101, (byte)1, (byte)0, (byte)0, (byte)-86, (byte)2,
- (byte)0, (byte)0, (byte)80, (byte)75, (byte)1, (byte)2, (byte)20, (byte)0,
- (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)12, (byte)88,
- (byte)-62, (byte)68, (byte)39, (byte)-48, (byte)-69, (byte)-98, (byte)-101, (byte)1,
- (byte)0, (byte)0, (byte)-86, (byte)2, (byte)0, (byte)0, (byte)49, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120, (byte)97, (byte)109,
- (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104, (byte)114, (byte)105,
- (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116, (byte)116, (byte)101,
- (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112, (byte)47, (byte)77,
- (byte)97, (byte)105, (byte)110, (byte)65, (byte)99, (byte)116, (byte)105, (byte)118,
- (byte)105, (byte)116, (byte)121, (byte)46, (byte)99, (byte)108, (byte)97, (byte)115,
- (byte)115, (byte)80, (byte)75, (byte)5, (byte)6, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)1, (byte)0, (byte)1, (byte)0, (byte)95, (byte)0, (byte)0,
- (byte)0, (byte)-6, (byte)1, (byte)0, (byte)0, (byte)0, (byte)0
- };
- return createFile(dir, "app/build/classes-proguard/release/classes.jar", bytecode);
- }
-
- private static File createUnproguardedClasses(File dir) throws IOException {
- byte [] bytecode = new byte[] {
- (byte)80, (byte)75, (byte)3, (byte)4, (byte)20, (byte)0, (byte)8, (byte)0,
- (byte)8, (byte)0, (byte)-11, (byte)-127, (byte)-61, (byte)68, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)9, (byte)0, (byte)4, (byte)0, (byte)77, (byte)69,
- (byte)84, (byte)65, (byte)45, (byte)73, (byte)78, (byte)70, (byte)47, (byte)-2,
- (byte)-54, (byte)0, (byte)0, (byte)3, (byte)0, (byte)80, (byte)75, (byte)7,
- (byte)8, (byte)0, (byte)0, (byte)0, (byte)0, (byte)2, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)80, (byte)75, (byte)3,
- (byte)4, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-11,
- (byte)-127, (byte)-61, (byte)68, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)20,
- (byte)0, (byte)0, (byte)0, (byte)77, (byte)69, (byte)84, (byte)65, (byte)45,
- (byte)73, (byte)78, (byte)70, (byte)47, (byte)77, (byte)65, (byte)78, (byte)73,
- (byte)70, (byte)69, (byte)83, (byte)84, (byte)46, (byte)77, (byte)70, (byte)-13,
- (byte)77, (byte)-52, (byte)-53, (byte)76, (byte)75, (byte)45, (byte)46, (byte)-47,
- (byte)13, (byte)75, (byte)45, (byte)42, (byte)-50, (byte)-52, (byte)-49, (byte)-77,
- (byte)82, (byte)48, (byte)-44, (byte)51, (byte)-32, (byte)-27, (byte)114, (byte)46,
- (byte)74, (byte)77, (byte)44, (byte)73, (byte)77, (byte)-47, (byte)117, (byte)-86,
- (byte)4, (byte)9, (byte)-104, (byte)-23, (byte)25, (byte)-60, (byte)-101, (byte)-103,
- (byte)42, (byte)104, (byte)56, (byte)22, (byte)20, (byte)-28, (byte)-92, (byte)42,
- (byte)120, (byte)-26, (byte)37, (byte)-21, (byte)105, (byte)-14, (byte)114, (byte)-15,
- (byte)114, (byte)1, (byte)0, (byte)80, (byte)75, (byte)7, (byte)8, (byte)127,
- (byte)71, (byte)56, (byte)-57, (byte)60, (byte)0, (byte)0, (byte)0, (byte)60,
- (byte)0, (byte)0, (byte)0, (byte)80, (byte)75, (byte)3, (byte)4, (byte)20,
- (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-49, (byte)-127, (byte)-61,
- (byte)68, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)49, (byte)0, (byte)0,
- (byte)0, (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120, (byte)97,
- (byte)109, (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104, (byte)114,
- (byte)105, (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116, (byte)116,
- (byte)101, (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112, (byte)47,
- (byte)77, (byte)97, (byte)105, (byte)110, (byte)65, (byte)99, (byte)116, (byte)105,
- (byte)118, (byte)105, (byte)116, (byte)121, (byte)46, (byte)99, (byte)108, (byte)97,
- (byte)115, (byte)115, (byte)-107, (byte)-109, (byte)-51, (byte)82, (byte)19, (byte)65,
- (byte)16, (byte)-57, (byte)-1, (byte)-109, (byte)4, (byte)-106, (byte)-124, (byte)13,
- (byte)-127, (byte)64, (byte)-126, (byte)32, (byte)72, (byte)16, (byte)-112, (byte)124,
- (byte)8, (byte)43, (byte)-8, (byte)45, (byte)-96, (byte)66, (byte)4, (byte)-115,
- (byte)5, (byte)90, (byte)37, (byte)22, (byte)7, (byte)47, (byte)-44, (byte)-110,
- (byte)-116, (byte)48, (byte)-78, (byte)-103, (byte)77, (byte)101, (byte)39, (byte)81,
- (byte)-34, (byte)-58, (byte)7, (byte)-32, (byte)34, (byte)7, (byte)40, (byte)61,
- (byte)-8, (byte)0, (byte)62, (byte)-109, (byte)101, (byte)-39, (byte)-77, (byte)89,
- (byte)80, (byte)-85, (byte)66, (byte)-91, (byte)-36, (byte)-61, (byte)-12, (byte)108,
- (byte)79, (byte)-9, (byte)111, (byte)122, (byte)-2, (byte)-45, (byte)-13, (byte)-29,
- (byte)-41, (byte)-73, (byte)-17, (byte)0, (byte)22, (byte)-15, (byte)50, (byte)6,
- (byte)19, (byte)51, (byte)122, (byte)-72, (byte)17, (byte)-59, (byte)44, (byte)-78,
- (byte)49, (byte)-12, (byte)34, (byte)-89, (byte)-121, (byte)124, (byte)20, (byte)5,
- (byte)-36, (byte)-116, (byte)97, (byte)14, (byte)-13, (byte)-67, (byte)-80, (byte)112,
- (byte)43, (byte)-118, (byte)5, (byte)44, (byte)-22, (byte)-72, (byte)-37, (byte)6,
- (byte)-18, (byte)24, (byte)-72, (byte)-53, (byte)-48, (byte)-67, (byte)44, (byte)-92,
- (byte)80, (byte)-113, (byte)25, (byte)-62, (byte)-39, (byte)-36, (byte)14, (byte)67,
- (byte)-92, (byte)-24, (byte)86, (byte)56, (byte)67, (byte)98, (byte)83, (byte)72,
- (byte)-2, (byte)-86, (byte)81, (byte)-35, (byte)-29, (byte)-11, (byte)-73, (byte)-10,
- (byte)-98, (byte)67, (byte)-98, (byte)-28, (byte)-90, (byte)91, (byte)-74, (byte)-99,
- (byte)29, (byte)-69, (byte)46, (byte)-12, (byte)127, (byte)-32, (byte)-116, (byte)-88,
- (byte)3, (byte)-31, (byte)49, (byte)-52, (byte)109, (byte)-106, (byte)-35, (byte)-86,
- (byte)-59, (byte)63, (byte)-39, (byte)-43, (byte)-102, (byte)-61, (byte)45, (byte)-17,
- (byte)-96, (byte)46, (byte)-28, (byte)97, (byte)-125, (byte)-88, (byte)-118, (byte)123,
- (byte)-54, (byte)-78, (byte)107, (byte)53, (byte)107, (byte)-53, (byte)22, (byte)114,
- (byte)-75, (byte)-84, (byte)68, (byte)83, (byte)-88, (byte)-93, (byte)37, (byte)-122,
- (byte)30, (byte)87, (byte)22, (byte)-21, (byte)-36, (byte)86, (byte)68, (byte)72,
- (byte)103, (byte)55, (byte)109, (byte)89, (byte)-87, (byte)-69, (byte)-94, (byte)98,
- (byte)-71, (byte)-98, (byte)-75, (byte)-42, (byte)-112, (byte)21, (byte)-121, (byte)47,
- (byte)-23, (byte)66, (byte)-110, (byte)-98, (byte)-35, (byte)-28, (byte)-107, (byte)-110,
- (byte)-12, (byte)-108, (byte)45, (byte)-53, (byte)124, (byte)91, (byte)-7, (byte)-47,
- (byte)-125, (byte)109, (byte)-126, (byte)-55, (byte)123, (byte)-114, (byte)123, (byte)93,
- (byte)83, (byte)-62, (byte)-107, (byte)-34, (byte)22, (byte)-105, (byte)-115, (byte)127,
- (byte)-56, (byte)77, (byte)-63, (byte)63, (byte)90, (byte)-38, (byte)-69, (byte)-108,
- (byte)123, (byte)71, (byte)69, (byte)87, (byte)-3, (byte)-11, (byte)-63, (byte)54,
- (byte)-53, (byte)12, (byte)41, (byte)87, (byte)6, (byte)-108, (byte)-110, (byte)-30,
- (byte)-43, (byte)109, (byte)-18, (byte)-16, (byte)-78, (byte)-30, (byte)21, (byte)-122,
- (byte)-47, (byte)54, (byte)52, (byte)29, (byte)-47, (byte)34, (byte)10, (byte)-102,
- (byte)49, (byte)12, (byte)95, (byte)18, (byte)-62, (byte)16, (byte)18, (byte)-124,
- (byte)96, (byte)37, (byte)-122, (byte)56, (byte)29, (byte)-92, (byte)124, (byte)-72,
- (byte)101, (byte)-41, (byte)2, (byte)1, (byte)99, (byte)-37, (byte)110, (byte)-93,
- (byte)94, (byte)-26, (byte)27, (byte)66, (byte)-1, (byte)12, (byte)-4, (byte)45,
- (byte)-45, (byte)-4, (byte)7, (byte)-69, (byte)105, (byte)-101, (byte)-120, (byte)-93,
- (byte)-49, (byte)-60, (byte)16, (byte)82, (byte)6, (byte)-18, (byte)-101, (byte)120,
- (byte)-124, (byte)73, (byte)19, (byte)75, (byte)88, (byte)54, (byte)-79, (byte)-126,
- (byte)-57, (byte)6, (byte)-98, (byte)-104, (byte)120, (byte)-118, (byte)73, (byte)3,
- (byte)-85, (byte)38, (byte)-42, (byte)80, (byte)52, (byte)-16, (byte)-52, (byte)-60,
- (byte)58, (byte)54, (byte)12, (byte)60, (byte)55, (byte)-15, (byte)66, (byte)71,
- (byte)-114, (byte)97, (byte)-100, (byte)-95, (byte)-16, (byte)31, (byte)87, (byte)-61,
- (byte)48, (byte)116, (byte)126, (byte)2, (byte)-67, (byte)116, (byte)-18, (byte)54,
- (byte)64, (byte)-107, (byte)-49, (byte)118, (byte)-32, (byte)-68, (byte)-103, (byte)118,
- (byte)-20, (byte)35, (byte)-73, (byte)-95, (byte)-88, (byte)-93, (byte)-50, (byte)39,
- (byte)102, (byte)73, (byte)74, (byte)94, (byte)47, (byte)58, (byte)-74, (byte)-25,
- (byte)113, (byte)-22, (byte)-110, (byte)-72, (byte)29, (byte)-16, (byte)118, (byte)-85,
- (byte)-76, (byte)39, (byte)67, (byte)-97, (byte)-57, (byte)85, (byte)-47, (byte)-107,
- (byte)-118, (byte)75, (byte)-75, (byte)67, (byte)122, (byte)-111, (byte)-116, (byte)-39,
- (byte)-110, (byte)-66, (byte)-7, (byte)-60, (byte)62, (byte)87, (byte)-66, (byte)118,
- (byte)-14, (byte)-67, (byte)67, (byte)-105, (byte)90, (byte)103, (byte)24, (byte)-49,
- (byte)-26, (byte)-38, (byte)72, (byte)27, (byte)44, (byte)-109, (byte)-68, (byte)51,
- (byte)29, (byte)107, (byte)107, (byte)93, (byte)121, (byte)-92, (byte)-75, (byte)-15,
- (byte)-56, (byte)-91, (byte)44, (byte)6, (byte)67, (byte)-76, (byte)-90, (byte)116,
- (byte)-101, (byte)-39, (byte)82, (byte)-69, (byte)6, (byte)-94, (byte)2, (byte)83,
- (byte)109, (byte)-81, (byte)-103, (byte)33, (byte)74, (byte)-123, (byte)-21, (byte)89,
- (byte)-87, (byte)-30, (byte)-65, (byte)38, (byte)18, (byte)109, (byte)-86, (byte)99,
- (byte)97, (byte)-70, (byte)49, (byte)18, (byte)90, (byte)24, (byte)87, (byte)-18,
- (byte)-110, (byte)30, (byte)74, (byte)-56, (byte)125, (byte)-110, (byte)42, (byte)-45,
- (byte)41, (byte)15, (byte)-109, (byte)-12, (byte)-72, (byte)77, (byte)-24, (byte)47,
- (byte)2, (byte)-90, (byte)-69, (byte)-124, (byte)-58, (byte)4, (byte)-3, (byte)89,
- (byte)100, (byte)25, (byte)-39, (byte)-82, (byte)-4, (byte)25, (byte)-40, (byte)23,
- (byte)-102, (byte)-124, (byte)-48, (byte)79, (byte)99, (byte)-73, (byte)-17, (byte)-116,
- (byte)98, (byte)-128, (byte)70, (byte)-77, (byte)21, (byte)-128, (byte)36, (byte)6,
- (byte)-3, (byte)116, (byte)-22, (byte)-82, (byte)32, (byte)-71, (byte)68, (byte)-47,
- (byte)33, (byte)-78, (byte)-15, (byte)124, (byte)-31, (byte)12, (byte)-95, (byte)-4,
- (byte)9, (byte)-62, (byte)-89, (byte)-120, (byte)-4, (byte)-127, (byte)-12, (byte)33,
- (byte)-84, (byte)23, (byte)41, (byte)-75, (byte)-113, (byte)32, (byte)9, (byte)31,
- (byte)-106, (byte)110, (byte)37, (byte)4, (byte)48, (byte)61, (byte)75, (byte)99,
- (byte)-40, (byte)-81, (byte)-31, (byte)10, (byte)70, (byte)2, (byte)-20, (byte)58,
- (byte)-27, (byte)-75, (byte)-80, (byte)-89, (byte)-24, (byte)58, (byte)65, (byte)119,
- (byte)-31, (byte)20, (byte)70, (byte)-28, (byte)-8, (byte)2, (byte)27, (byte)-13,
- (byte)23, (byte)83, (byte)116, (byte)-96, (byte)-12, (byte)37, (byte)-56, (byte)81,
- (byte)92, (byte)-11, (byte)-111, (byte)-44, (byte)-48, (byte)1, (byte)-46, (byte)-95,
- (byte)24, (byte)93, (byte)76, (byte)-70, (byte)-16, (byte)21, (byte)61, (byte)12,
- (byte)43, (byte)99, (byte)39, (byte)-120, (byte)126, (byte)70, (byte)87, (byte)-28,
- (byte)88, (byte)87, (byte)30, (byte)-45, (byte)-20, (byte)-80, (byte)-49, (byte)78,
- (byte)-46, (byte)-7, (byte)-128, (byte)107, (byte)48, (byte)48, (byte)65, (byte)69,
- (byte)103, (byte)-56, (byte)119, (byte)-35, (byte)-33, (byte)35, (byte)-45, (byte)-54,
- (byte)-66, (byte)-40, (byte)35, (byte)77, (byte)49, (byte)19, (byte)-60, (byte)54,
- (byte)-120, (byte)-98, (byte)33, (byte)113, (byte)67, (byte)20, (byte)-25, (byte)-85,
- (byte)-10, (byte)19, (byte)-3, (byte)-12, (byte)124, (byte)49, (byte)-27, (byte)87,
- (byte)59, (byte)-115, (byte)-121, (byte)100, (byte)71, (byte)41, (byte)119, (byte)22,
- (byte)-9, (byte)-16, (byte)-128, (byte)14, (byte)88, (byte)32, (byte)59, (byte)74,
- (byte)118, (byte)-127, (byte)108, (byte)6, (byte)35, (byte)-65, (byte)1, (byte)80,
- (byte)75, (byte)7, (byte)8, (byte)-67, (byte)119, (byte)-82, (byte)40, (byte)-35,
- (byte)2, (byte)0, (byte)0, (byte)-111, (byte)5, (byte)0, (byte)0, (byte)80,
- (byte)75, (byte)3, (byte)4, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8,
- (byte)0, (byte)-49, (byte)-127, (byte)-61, (byte)68, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)48, (byte)0, (byte)0, (byte)0, (byte)99, (byte)111, (byte)109,
- (byte)47, (byte)101, (byte)120, (byte)97, (byte)109, (byte)112, (byte)108, (byte)101,
- (byte)47, (byte)115, (byte)104, (byte)114, (byte)105, (byte)110, (byte)107, (byte)117,
- (byte)110, (byte)105, (byte)116, (byte)116, (byte)101, (byte)115, (byte)116, (byte)47,
- (byte)97, (byte)112, (byte)112, (byte)47, (byte)66, (byte)117, (byte)105, (byte)108,
- (byte)100, (byte)67, (byte)111, (byte)110, (byte)102, (byte)105, (byte)103, (byte)46,
- (byte)99, (byte)108, (byte)97, (byte)115, (byte)115, (byte)-115, (byte)81, (byte)-37,
- (byte)110, (byte)-45, (byte)64, (byte)16, (byte)61, (byte)-45, (byte)92, (byte)-20,
- (byte)4, (byte)-105, (byte)-74, (byte)-127, (byte)2, (byte)-31, (byte)82, (byte)40,
- (byte)-41, (byte)36, (byte)20, (byte)-101, (byte)-10, (byte)-107, (byte)10, (byte)41,
- (byte)23, (byte)-73, (byte)-118, (byte)72, (byte)-109, (byte)-86, (byte)105, (byte)45,
- (byte)-47, (byte)-105, (byte)104, (byte)-29, (byte)44, (byte)-83, (byte)-117, (byte)99,
- (byte)71, (byte)-66, (byte)32, (byte)126, (byte)11, (byte)-15, (byte)0, (byte)-22,
- (byte)3, (byte)31, (byte)-64, (byte)71, (byte)33, (byte)-58, (byte)38, (byte)74,
- (byte)-115, (byte)120, (byte)-31, (byte)97, (byte)103, (byte)-9, (byte)-52, (byte)-50,
- (byte)57, (byte)51, (byte)123, (byte)-10, (byte)-25, (byte)-81, (byte)-53, (byte)31,
- (byte)0, (byte)118, (byte)-96, (byte)-105, (byte)81, (byte)-60, (byte)35, (byte)21,
- (byte)-101, (byte)101, (byte)60, (byte)-58, (byte)-109, (byte)18, (byte)10, (byte)120,
- (byte)-86, (byte)-32, (byte)-103, (byte)-126, (byte)-25, (byte)-124, (byte)66, (byte)-57,
- (byte)108, (byte)-99, (byte)-20, (byte)19, (byte)-24, (byte)-108, (byte)-96, (byte)29,
- (byte)54, (byte)-37, (byte)-17, (byte)-102, (byte)-5, (byte)-26, (byte)-88, (byte)-33,
- (byte)60, (byte)48, (byte)9, (byte)-107, (byte)-34, (byte)-123, (byte)-8, (byte)36,
- (byte)12, (byte)87, (byte)120, (byte)103, (byte)-58, (byte)48, (byte)10, (byte)28,
- (byte)-17, (byte)-20, (byte)13, (byte)97, (byte)-71, (byte)-19, (byte)123, (byte)97,
- (byte)36, (byte)-68, (byte)-56, (byte)18, (byte)110, (byte)44, (byte)85, (byte)-68,
- (byte)32, (byte)-108, (byte)91, (byte)39, (byte)-35, (byte)94, (byte)103, (byte)116,
- (byte)-4, (byte)-2, (byte)-48, (byte)84, (byte)81, (byte)35, (byte)20, (byte)-9,
- (byte)122, (byte)77, (byte)107, (byte)112, (byte)-92, (byte)-94, (byte)-50, (byte)-110,
- (byte)-106, (byte)121, (byte)52, (byte)-20, (byte)14, (byte)-6, (byte)-93, (byte)-10,
- (byte)-96, (byte)-61, (byte)-110, (byte)-44, (byte)-51, (byte)-15, (byte)64, (byte)-108,
- (byte)-55, (byte)39, (byte)-83, (byte)84, (byte)52, (byte)-104, (byte)-75, (byte)-21,
- (byte)120, (byte)78, (byte)-12, (byte)-106, (byte)-112, (byte)-85, (byte)-43, (byte)45,
- (byte)66, (byte)-66, (byte)-19, (byte)79, (byte)36, (byte)97, (byte)-91, (byte)-25,
- (byte)120, (byte)-78, (byte)31, (byte)79, (byte)-57, (byte)50, (byte)56, (byte)22,
- (byte)99, (byte)87, (byte)38, (byte)83, (byte)-7, (byte)-74, (byte)112, (byte)45,
- (byte)17, (byte)56, (byte)9, (byte)-98, (byte)39, (byte)-13, (byte)-47, (byte)-71,
- (byte)19, (byte)18, (byte)-74, (byte)122, (byte)-74, (byte)63, (byte)53, (byte)-28,
- (byte)103, (byte)49, (byte)-99, (byte)-71, (byte)-46, (byte)8, (byte)-49, (byte)121,
- (byte)-26, (byte)-113, (byte)49, (byte)-85, (byte)70, (byte)50, (byte)-116, (byte)12,
- (byte)49, (byte)-101, (byte)25, (byte)-83, (byte)-40, (byte)113, (byte)39, (byte)-4,
- (byte)-126, (byte)15, (byte)78, (byte)-14, (byte)22, (byte)117, (byte)-41, (byte)118,
- (byte)-25, (byte)77, (byte)-53, (byte)67, (byte)63, (byte)14, (byte)108, (byte)-71,
- (byte)-25, (byte)36, (byte)106, (byte)-85, (byte)-103, (byte)50, (byte)61, (byte)-15,
- (byte)64, (byte)-61, (byte)45, (byte)-36, (byte)78, (byte)-70, (byte)4, (byte)-79,
- (byte)84, (byte)-16, (byte)82, (byte)-61, (byte)22, (byte)94, (byte)105, (byte)80,
- (byte)-96, (byte)18, (byte)26, (byte)-1, (byte)-33, (byte)-111, (byte)-123, (byte)-81,
- (byte)12, (byte)29, (byte)-116, (byte)47, (byte)-92, (byte)29, (byte)17, (byte)54,
- (byte)-104, (byte)-81, (byte)-49, (byte)-7, (byte)-6, (byte)-33, (byte)124, (byte)-99,
- (byte)-7, (byte)-4, (byte)65, (byte)19, (byte)57, (byte)-114, (byte)-103, (byte)11,
- (byte)118, (byte)102, (byte)91, (byte)127, (byte)77, (byte)88, (byte)-69, (byte)18,
- (byte)105, (byte)-7, (byte)-66, (byte)43, (byte)-123, (byte)-57, (byte)118, (byte)-50,
- (byte)68, (byte)16, (byte)-54, (byte)5, (byte)92, (byte)-81, (byte)-3, (byte)-5,
- (byte)117, (byte)-11, (byte)83, (byte)108, (byte)-13, (byte)-57, (byte)23, (byte)-39,
- (byte)-1, (byte)34, (byte)-86, (byte)-55, (byte)-16, (byte)124, (byte)-86, (byte)-94,
- (byte)-124, (byte)50, (byte)43, (byte)95, (byte)-29, (byte)-13, (byte)18, (byte)52,
- (byte)-58, (byte)-53, (byte)25, (byte)124, (byte)-99, (byte)-15, (byte)74, (byte)6,
- (byte)-81, (byte)50, (byte)94, (byte)67, (byte)101, (byte)-127, (byte)111, (byte)48,
- (byte)-66, (byte)-103, (byte)-71, (byte)95, (byte)-25, (byte)69, (byte)-119, (byte)85,
- (byte)28, (byte)-17, (byte)112, (byte)-58, (byte)-32, (byte)-99, (byte)7, (byte)71,
- (byte)-95, (byte)-15, (byte)13, (byte)-12, (byte)37, (byte)45, (byte)-87, (byte)-90,
- (byte)-19, (byte)41, (byte)-115, (byte)119, (byte)57, (byte)106, (byte)127, (byte)10,
- (byte)112, (byte)15, (byte)-9, (byte)121, (byte)87, (byte)-15, (byte)96, (byte)65,
- (byte)-34, (byte)76, (byte)111, (byte)-128, (byte)82, (byte)101, (byte)-23, (byte)59,
- (byte)114, (byte)95, (byte)-111, (byte)79, (byte)4, (byte)40, (byte)35, (byte)-96,
- (byte)112, (byte)-36, (byte)72, (byte)69, (byte)31, (byte)-2, (byte)6, (byte)80,
- (byte)75, (byte)7, (byte)8, (byte)-81, (byte)-102, (byte)-119, (byte)99, (byte)-46,
- (byte)1, (byte)0, (byte)0, (byte)-23, (byte)2, (byte)0, (byte)0, (byte)80,
- (byte)75, (byte)1, (byte)2, (byte)20, (byte)0, (byte)20, (byte)0, (byte)8,
- (byte)0, (byte)8, (byte)0, (byte)-11, (byte)-127, (byte)-61, (byte)68, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)2, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)9, (byte)0, (byte)4, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)77, (byte)69, (byte)84,
- (byte)65, (byte)45, (byte)73, (byte)78, (byte)70, (byte)47, (byte)-2, (byte)-54,
- (byte)0, (byte)0, (byte)80, (byte)75, (byte)1, (byte)2, (byte)20, (byte)0,
- (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-11, (byte)-127,
- (byte)-61, (byte)68, (byte)127, (byte)71, (byte)56, (byte)-57, (byte)60, (byte)0,
- (byte)0, (byte)0, (byte)60, (byte)0, (byte)0, (byte)0, (byte)20, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)61, (byte)0, (byte)0, (byte)0,
- (byte)77, (byte)69, (byte)84, (byte)65, (byte)45, (byte)73, (byte)78, (byte)70,
- (byte)47, (byte)77, (byte)65, (byte)78, (byte)73, (byte)70, (byte)69, (byte)83,
- (byte)84, (byte)46, (byte)77, (byte)70, (byte)80, (byte)75, (byte)1, (byte)2,
- (byte)20, (byte)0, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0,
- (byte)-49, (byte)-127, (byte)-61, (byte)68, (byte)-67, (byte)119, (byte)-82, (byte)40,
- (byte)-35, (byte)2, (byte)0, (byte)0, (byte)-111, (byte)5, (byte)0, (byte)0,
- (byte)49, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)-69, (byte)0,
- (byte)0, (byte)0, (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120,
- (byte)97, (byte)109, (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104,
- (byte)114, (byte)105, (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116,
- (byte)116, (byte)101, (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112,
- (byte)47, (byte)77, (byte)97, (byte)105, (byte)110, (byte)65, (byte)99, (byte)116,
- (byte)105, (byte)118, (byte)105, (byte)116, (byte)121, (byte)46, (byte)99, (byte)108,
- (byte)97, (byte)115, (byte)115, (byte)80, (byte)75, (byte)1, (byte)2, (byte)20,
- (byte)0, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-49,
- (byte)-127, (byte)-61, (byte)68, (byte)-81, (byte)-102, (byte)-119, (byte)99, (byte)-46,
- (byte)1, (byte)0, (byte)0, (byte)-23, (byte)2, (byte)0, (byte)0, (byte)48,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)-9, (byte)3, (byte)0,
- (byte)0, (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120, (byte)97,
- (byte)109, (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104, (byte)114,
- (byte)105, (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116, (byte)116,
- (byte)101, (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112, (byte)47,
- (byte)66, (byte)117, (byte)105, (byte)108, (byte)100, (byte)67, (byte)111, (byte)110,
- (byte)102, (byte)105, (byte)103, (byte)46, (byte)99, (byte)108, (byte)97, (byte)115,
- (byte)115, (byte)80, (byte)75, (byte)5, (byte)6, (byte)0, (byte)0, (byte)0,
- (byte)0, (byte)4, (byte)0, (byte)4, (byte)0, (byte)58, (byte)1, (byte)0,
- (byte)0, (byte)39, (byte)6, (byte)0, (byte)0, (byte)0, (byte)0,
- };
- return createFile(dir, "app/build/intermediates/classes/debug/classes.jar", bytecode);
- }
-
- private static File createMappingFile(File dir) throws IOException {
- return createFile(dir, "app/build/proguard/release/mapping.txt", ""
- + "com.example.shrinkunittest.app.MainActivity -> com.example.shrinkunittest.app.MainActivity:\n"
- + " void onCreate(android.os.Bundle) -> onCreate\n"
- + " boolean onCreateOptionsMenu(android.view.Menu) -> onCreateOptionsMenu\n"
- + " boolean onOptionsItemSelected(android.view.MenuItem) -> onOptionsItemSelected");
- }
-
- public void testFormatStringRegexp() {
- assertEquals(NO_MATCH, convertFormatStringToRegexp(""));
- assertEquals("\\Qfoo_\\E", convertFormatStringToRegexp("foo_"));
- assertEquals("\\Qfoo\\E.*\\Q_\\E.*\\Qend\\E", convertFormatStringToRegexp("foo%s_%1$send"));
- assertEquals("\\Qescape!.()\\E", convertFormatStringToRegexp("escape!.()"));
-
- assertEquals(NO_MATCH, convertFormatStringToRegexp("%c%c%c%d"));
- assertEquals(NO_MATCH, convertFormatStringToRegexp("%d%s"));
- assertEquals(NO_MATCH, convertFormatStringToRegexp("%s%s"));
- assertEquals(NO_MATCH, convertFormatStringToRegexp("%d_%d"));
- assertEquals(NO_MATCH, convertFormatStringToRegexp("%s%s%s%s"));
- assertEquals(NO_MATCH, convertFormatStringToRegexp("%s_%s_%s"));
- assertEquals(NO_MATCH, convertFormatStringToRegexp("%.0f%s"));
- assertEquals(".*\\Qabc\\E", convertFormatStringToRegexp("%sabc"));
- assertEquals("\\Qa\\E.*", convertFormatStringToRegexp("a%d%s"));
-
- assertTrue("foo_".matches(convertFormatStringToRegexp("foo_")));
- assertTrue("fooA_BBend".matches(convertFormatStringToRegexp("foo%s_%1$send")));
- assertFalse("A_BBend".matches(convertFormatStringToRegexp("foo%s_%1$send")));
- }
-
- /** Utility method to generate byte array literal dump (used by classesJarBytecode above) */
- @SuppressWarnings("UnusedDeclaration") // Utility for future .class/.jar additions
- public static void dumpBytes(File file) throws IOException {
- byte[] bytes = Files.toByteArray(file);
- int count = 0;
- for (byte b : bytes) {
- System.out.print("(byte)" + Byte.toString(b) + ", ");
- count++;
- if (count == 8) {
- count = 0;
- System.out.println();
- }
- }
-
- System.out.println();
- }
-
- @SuppressWarnings("ResultOfMethodCallIgnored")
- protected static void deleteDir(File root) {
- if (root.exists()) {
- File[] files = root.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- deleteDir(file);
- } else {
- file.delete();
- }
- }
- }
- root.delete();
- }
- }
-
- @SuppressWarnings("ResultOfMethodCallIgnored")
- @NonNull
- private static File createFile(File dir, String relative) throws IOException {
- File file = new File(dir, relative.replace('/', separatorChar));
- file.getParentFile().mkdirs();
- return file;
- }
-
-
- @NonNull
- private static File createFile(File dir, String relative, String contents) throws IOException {
- File file = createFile(dir, relative);
- Files.write(contents, file, Charsets.UTF_8);
- return file;
- }
-
-
- @NonNull
- private static File createFile(File dir, String relative, byte[] contents) throws IOException {
- File file = createFile(dir, relative);
- Files.write(contents, file);
- return file;
- }
-
- private static void checkState(ResourceUsageAnalyzer analyzer) {
- List<Resource> resources = analyzer.getAllResources();
- Collections.sort(resources, new Comparator<Resource>() {
- @Override
- public int compare(Resource resource1, Resource resource2) {
- int delta = resource1.type.compareTo(resource2.type);
- if (delta != 0) {
- return delta;
- }
- return resource1.name.compareTo(resource2.name);
- }
- });
-
- // Ensure unique
- Resource prev = null;
- for (Resource resource : resources) {
- assertTrue(resource + " and " + prev, prev == null
- || resource.type != prev.type
- || !resource.name.equals(prev.name));
- prev = resource;
- }
- }
-
- public void testIsResourceClass() {
- assertTrue(ResourceUsageAnalyzer.isResourceClass("android/support/v7/appcompat/R$attr.class"));
- assertTrue(ResourceUsageAnalyzer.isResourceClass("android/support/v7/appcompat/R$attr.class"));
- assertTrue(ResourceUsageAnalyzer.isResourceClass("android/support/v7/appcompat/R$bool.class"));
- assertFalse(ResourceUsageAnalyzer.isResourceClass("android/support/v7/appcompat/R.class"));
- assertFalse(ResourceUsageAnalyzer.isResourceClass("com/google/samples/apps/iosched/ui/BrowseSessionsActivity.class"));
- }
-}
\ No newline at end of file
diff --git a/base/build-system/gradle-experimental/build.gradle b/base/build-system/gradle-experimental/build.gradle
deleted file mode 100644
index 9e9154a..0000000
--- a/base/build-system/gradle-experimental/build.gradle
+++ /dev/null
@@ -1,75 +0,0 @@
-apply plugin: 'groovy'
-apply plugin: 'clone-artifacts'
-apply plugin: 'idea'
-apply plugin: 'jacoco'
-
-// Extract gradle libraries to ensure gradle-core is compatible with older version.
-String gradleVersion = "2.5"
-File gradleBinary = file("$rootProject.projectDir/external/gradle/gradle-$gradleVersion-all.zip")
-File gradleLib = file("$rootProject.ext.androidHostOut/alternate-gradle/gradle-$gradleVersion/lib")
-
-task extractGradleLibs(type: Copy) {
- from zipTree(gradleBinary)
- into gradleLib.parentFile.parentFile
-}
-
-compileJava.dependsOn extractGradleLibs
-
-task setupGradleInIde {
- dependsOn extractGradleLibs
-}
-
-dependencies {
- compile fileTree(dir:gradleLib)
- compile project(':base:gradle-core')
-
- testCompile 'junit:junit:4.12'
- testCompile project(':base:project-test-lib')
-}
-
-group = 'com.android.tools.build'
-archivesBaseName = 'gradle-experimental'
-version = rootProject.ext.experimentalVersion
-
-project.ext.pomName = 'Gradle Plug-in for Android Using Component Model'
-project.ext.pomDesc = 'Gradle plug-in to build Android applications.'
-
-apply from: "$rootDir/buildSrc/base/publish.gradle"
-apply from: "$rootDir/buildSrc/base/bintray.gradle"
-
-jar.manifest.attributes("Plugin-Version": version)
-jar.manifest.attributes("Inception-Date":"${Calendar.getInstance().get(Calendar.YEAR)}:" +
- "${Calendar.getInstance().get(Calendar.MONTH)}:${Calendar.getInstance().get(Calendar.DATE)}")
-
-
-test {
- environment("CUSTOM_REPO", rootProject.file("../out/repo"))
-
- testLogging {
- events "failed"
- }
-
- maxParallelForks = Runtime.runtime.availableProcessors() / 2
-}
-
-groovydoc {
- exclude "**/internal/**"
- includePrivate false
-
- docTitle "Gradle Plugin for Android"
- header ""
- footer "Copyright (C) 2012 The Android Open Source Project"
- overview ""
-}
-
-task javadocJar(type: Jar, dependsOn:groovydoc) {
- classifier 'javadoc'
- from groovydoc.destinationDir
-}
-
-// Only package JavaDoc if using --init-script=buildSrc/base/release.gradle
-if (project.has("release")) {
- artifacts {
- archives javadocJar
- }
-}
diff --git a/base/build-system/gradle-experimental/gradle-experimental.iml b/base/build-system/gradle-experimental/gradle-experimental.iml
deleted file mode 100644
index 9ab34f7..0000000
--- a/base/build-system/gradle-experimental/gradle-experimental.iml
+++ /dev/null
@@ -1,1256 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module relativePaths="true" type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/groovy" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/groovy" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/groovy" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/device-test/groovy" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/build-test/groovy" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/integ-test/groovy" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
- <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/resources" type="java-resource" />
- <excludeFolder url="file://$MODULE_DIR$/../../../../out/host/gradle/tools/base/gradle" />
- <excludeFolder url="file://$MODULE_DIR$/.gradle" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" exported="" name="proguard-gradle" level="project" />
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-core-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-model-core-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-model-groovy-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/asm-all-5.0.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/ant-1.9.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/commons-collections-3.2.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/commons-io-1.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/commons-lang-2.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/logback-core-1.0.13.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/logback-classic-1.0.13.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/guava-jdk5-17.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/jcip-annotations-1.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/jul-to-slf4j-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/jarjar-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/javax.inject-1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/slf4j-api-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/log4j-over-slf4j-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/jcl-over-slf4j-1.7.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/ant-launcher-1.9.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/commons-collections-3.2.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/commons-io-1.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/commons-lang-2.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-docs-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-launcher-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-base-services-groovy-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-base-services-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-resources-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-cli-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-native-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-open-api-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-ui-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/jna-3.2.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/native-platform-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/jansi-1.2.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/native-platform-osx-i386-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/native-platform-osx-amd64-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/native-platform-linux-amd64-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/native-platform-linux-i386-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/native-platform-windows-amd64-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/native-platform-windows-i386-0.10.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-messaging-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/kryo-2.20.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/reflectasm-1.07-shaded.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/minlog-1.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/objenesis-1.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-settings-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-repository-metadata-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-plexus-container-default-1.5.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-aether-provider-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-wagon-provider-api-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-plexus-cipher-1.7.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-plexus-interpolation-1.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-plexus-utils-2.0.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-plexus-classworlds-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-plugin-api-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-model-builder-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-plexus-sec-dispatcher-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-plexus-component-annotations-1.5.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-aether-connector-wagon-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-compat-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-wagon-http-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-aether-api-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-settings-builder-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-aether-spi-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-core-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-wagon-http-shared4-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-aether-util-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-artifact-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-maven-model-3.0.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jarjar-aether-impl-1.13.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/maven-ant-tasks-2.1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/nekohtml-1.9.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/xbean-reflect-3.4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/xml-apis-1.3.04.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/xercesImpl-2.9.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-tooling-api-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-plugins-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/junit-4.11.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/testng-6.3.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/commons-cli-1.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/bsh-2.0b4.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/snakeyaml-1.6.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/hamcrest-core-1.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-code-quality-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-jetty-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jetty-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jetty-util-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/servlet-api-2.5-20081211.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jetty-plus-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jsp-2.1-6.1.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jetty-annotations-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/geronimo-annotation_1.0_spec-1.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jetty-naming-6.1.25.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/core-3.1.1.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jsp-api-2.1-6.1.14.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-antlr-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-dependency-management-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-ide-native-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-language-groovy-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-language-java-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-language-jvm-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-language-native-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-platform-base-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-platform-jvm-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-platform-native-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-plugin-development-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-plugin-use-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/gradle-wrapper-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-osgi-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/bndlib-2.1.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-maven-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/pmaven-common-0.8-20100325.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/pmaven-groovy-0.8-20100325.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/plexus-component-annotations-1.5.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-ide-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-announce-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-scala-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-sonar-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/sonar-batch-bootstrapper-2.9.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-signing-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-ear-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-javascript-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/rhino-1.7R3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gson-2.2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/simple-4.1.21.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-build-comparison-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-diagnostics-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-reporting-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/jatl-0.2.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-publish-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-ivy-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-jacoco-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-build-init-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module-library" exported="">
- <library>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.5/lib/plugins/gradle-language-jvm-2.5.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
- <orderEntry type="module" module-name="builder" exported="" />
- <orderEntry type="module" module-name="lint-cli" exported="" />
- <orderEntry type="library" name="groovy" level="project" />
- <orderEntry type="module" module-name="gradle-core" />
- <orderEntry type="module" module-name="sdk-common-base" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- </component>
-</module>
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/internal/AndroidConfigHelper.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/internal/AndroidConfigHelper.java
deleted file mode 100644
index fa03d57..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/internal/AndroidConfigHelper.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.api.AndroidSourceSet;
-import com.android.build.gradle.internal.coverage.JacocoExtension;
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.dsl.AdbOptions;
-import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory;
-import com.android.build.gradle.internal.dsl.DexOptions;
-import com.android.build.gradle.internal.dsl.LintOptions;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.dsl.PreprocessingOptions;
-import com.android.build.gradle.internal.dsl.Splits;
-import com.android.build.gradle.internal.dsl.TestOptions;
-import com.android.build.gradle.managed.AndroidConfig;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.core.LibraryRequest;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.builder.testing.api.TestServer;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.internal.reflect.Instantiator;
-
-/**
- * Utility functions for initializing an AndroidConfig.
- */
-public class AndroidConfigHelper {
- public static void configure(
- @NonNull AndroidConfig model,
- @NonNull Instantiator instantiator) {
- model.setDefaultPublishConfig(BuilderConstants.RELEASE);
- model.setPublishNonDefault(false);
- model.setGeneratePureSplits(false);
- model.setPreProcessingOptions(instantiator.newInstance(PreprocessingOptions.class));
- model.setDeviceProviders(Lists.<DeviceProvider>newArrayList());
- model.setTestServers(Lists.<TestServer>newArrayList());
- model.setAaptOptions(instantiator.newInstance(AaptOptions.class));
- model.setDexOptions(instantiator.newInstance(DexOptions.class));
- model.setLintOptions(instantiator.newInstance(LintOptions.class));
- model.setTestOptions(instantiator.newInstance(TestOptions.class));
- model.setCompileOptions(instantiator.newInstance(CompileOptions.class));
- model.setPackagingOptions(instantiator.newInstance(PackagingOptions.class));
- model.setJacoco(instantiator.newInstance(JacocoExtension.class));
- model.setAdbOptions(instantiator.newInstance(AdbOptions.class));
- model.setSplits(instantiator.newInstance(Splits.class, instantiator));
- model.setLibraryRequests(Lists.<LibraryRequest>newArrayList());
- }
-
-
- public static NamedDomainObjectContainer<AndroidSourceSet> createSourceSetsContainer(
- @NonNull final Project project,
- @NonNull Instantiator instantiator,
- final boolean isLibrary) {
- NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer = project.container(
- AndroidSourceSet.class,
- new AndroidSourceSetFactory(instantiator, project, isLibrary));
-
- sourceSetsContainer.whenObjectAdded(new Action<AndroidSourceSet>() {
- @Override
- public void execute(AndroidSourceSet sourceSet) {
- ConfigurationContainer configurations = project.getConfigurations();
-
- createConfiguration(
- configurations,
- sourceSet.getCompileConfigurationName(),
- "Classpath for compiling the ${sourceSet.name} sources.");
-
- String packageConfigDescription;
- if (isLibrary) {
- packageConfigDescription
- = "Classpath only used when publishing '${sourceSet.name}'.";
- } else {
- packageConfigDescription
- = "Classpath packaged with the compiled '${sourceSet.name}' classes.";
- }
- createConfiguration(
- configurations,
- sourceSet.getPackageConfigurationName(),
- packageConfigDescription);
-
- createConfiguration(
- configurations,
- sourceSet.getProvidedConfigurationName(),
- "Classpath for only compiling the ${sourceSet.name} sources.");
-
- createConfiguration(
- configurations,
- sourceSet.getWearAppConfigurationName(),
- "Link to a wear app to embed for object '${sourceSet.name}'.");
-
- sourceSet.setRoot(String.format("src/%s", sourceSet.getName()));
-
- }
- });
- return sourceSetsContainer;
- }
-
- private static void createConfiguration(
- @NonNull ConfigurationContainer configurations,
- @NonNull String configurationName,
- @NonNull String configurationDescription) {
- Configuration configuration = configurations.findByName(configurationName);
- if (configuration == null) {
- configuration = configurations.create(configurationName);
- }
-
- // Disable modification to configurations as this causes issues when accessed through the
- // tooling-api. Check that it works with Studio's ImportProjectAction before re-enabling
- // them.
- //configuration.setVisible(false);
- //configuration.setDescription(configurationDescription);
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/internal/NdkOptionsHelper.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/internal/NdkOptionsHelper.java
deleted file mode 100644
index ee22699..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/internal/NdkOptionsHelper.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.build.gradle.managed.NdkBuildType;
-import com.android.build.gradle.managed.NdkOptions;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-/**
- * Helper functions for configuring an NdkOptions.
- */
-public class NdkOptionsHelper {
-
- /**
- * Initialize an NdkOptions.
- *
- * Should be unnecessary when Gradle supports managed List of String.
- */
- public static void init(NdkOptions ndk) {
- ndk.setCFlags(Lists.<String>newArrayList());
- ndk.setCppFlags(Lists.<String>newArrayList());
- ndk.setLdFlags(Lists.<String>newArrayList());
- ndk.setLdLibs(Lists.<String>newArrayList());
- ndk.setAbiFilters(Sets.<String>newHashSet());
- }
-
- /**
- * Merge one NdkOptions to another.
- * @param base NdkOptions to merge to. base is mutated to contain the merged result.
- * @param other NdkOptions to merge. Options in other has priority over base if both are set.
- */
- public static void merge(NdkOptions base, NdkOptions other) {
- if (other.getModuleName() != null) {
- base.setModuleName(other.getModuleName());
- }
-
- base.getAbiFilters().addAll(other.getAbiFilters());
- base.getCFlags().addAll(other.getCFlags());
- base.getCppFlags().addAll(other.getCppFlags());
- base.getLdFlags().addAll(other.getLdFlags());
- base.getLdLibs().addAll(other.getLdLibs());
-
- if (other.getStl() != null) {
- base.setStl(other.getStl());
- }
- if (other.getRenderscriptNdkMode() != null) {
- base.setRenderscriptNdkMode(other.getRenderscriptNdkMode());
- }
- }
-
- /**
- * Merge one NdkBuildType to another
- * @param base NdkBuildType to merge to. base is mutated to contain the merged result.
- * @param other NdkBuildType to merge. Options in other has priority over base if both are set.
- */
- public static void merge(NdkBuildType base, NdkBuildType other) {
- merge(base, (NdkOptions) other);
- if (other.getDebuggable() != null) {
- base.setDebuggable(other.getDebuggable());
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/AndroidConfig.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/AndroidConfig.java
deleted file mode 100644
index 8ec1eca..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/AndroidConfig.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import com.android.build.gradle.api.VariantFilter;
-import com.android.build.gradle.internal.CompileOptions;
-import com.android.build.gradle.internal.coverage.JacocoExtension;
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.dsl.AdbOptions;
-import com.android.build.gradle.internal.dsl.DexOptions;
-import com.android.build.gradle.internal.dsl.LintOptions;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.dsl.PreprocessingOptions;
-import com.android.build.gradle.internal.dsl.Splits;
-import com.android.build.gradle.internal.dsl.TestOptions;
-import com.android.build.gradle.model.AndroidComponentModelSourceSet;
-import com.android.builder.core.LibraryRequest;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.builder.testing.api.TestServer;
-import com.android.sdklib.repository.FullRevision;
-
-import org.gradle.api.Action;
-import org.gradle.model.Managed;
-import org.gradle.model.ModelMap;
-import org.gradle.model.Unmanaged;
-
-import java.util.Collection;
-import java.util.List;
-
-import groovy.lang.Closure;
-
-/**
- * Component model for all Android plugin.
- */
- at Managed
-public interface AndroidConfig {
-
- /** Build tool version */
- String getBuildToolsVersion();
- void setBuildToolsVersion(String buildToolsVersion);
-
- /** Compile SDK version */
- String getCompileSdkVersion();
- void setCompileSdkVersion(String compileSdkVersion);
-
- /** Build tool revisions */
- @Unmanaged
- FullRevision getBuildToolsRevision();
- void setBuildToolsRevision(FullRevision fullRevision);
-
- /** Default config, shared by all flavors. */
- ProductFlavor getDefaultConfig();
-
- /** List of device providers */
- @Unmanaged
- List<DeviceProvider> getDeviceProviders();
- void setDeviceProviders(List<DeviceProvider> providers);
-
- /** List of remote CI servers */
- @Unmanaged
- List<TestServer> getTestServers();
- void setTestServers(List<TestServer> providers);
-
- /** Name of the variant to publish */
- String getDefaultPublishConfig();
- void setDefaultPublishConfig(String defaultPublishConfig);
-
- /** Whether to also publish non-default variants */
- Boolean getPublishNonDefault();
- void setPublishNonDefault(Boolean publishNonDefault);
-
- /** Filter to determine which variants to build */
- @Unmanaged
- Action<VariantFilter> getVariantFilter();
- void setVariantFilter(Action<VariantFilter> filter);
-
- /** A prefix to be used when creating new resources. Used by Studio */
- String getResourcePrefix();
- void setResourcePrefix(String resourcePrefix);
-
- /** Whether to generate pure splits or multi apk */
- Boolean getGeneratePureSplits();
- void setGeneratePureSplits(Boolean generateSplits);
-
- /** Whether to preprocess resources */
- @Unmanaged
- PreprocessingOptions getPreProcessingOptions();
- void setPreProcessingOptions(PreprocessingOptions preprocessingOptions);
-
- /** Build types used by this project. */
- ModelMap<BuildType> getBuildTypes();
-
- /** All product flavors used by this project. */
- ModelMap<ProductFlavor> getProductFlavors();
-
- /** Signing configs used by this project. */
- ModelMap<SigningConfig> getSigningConfigs();
-
- @Unmanaged
- AndroidComponentModelSourceSet getSources();
- void setSources(AndroidComponentModelSourceSet sources);
-
- NdkConfig getNdk();
-
- /** Adb options */
- @Unmanaged
- AdbOptions getAdbOptions();
- void setAdbOptions(AdbOptions adbOptions);
-
- /** Options for aapt, tool for packaging resources. */
- @Unmanaged
- AaptOptions getAaptOptions();
- void setAaptOptions(AaptOptions aaptOptions);
-
- /** Compile options */
- @Unmanaged
- CompileOptions getCompileOptions();
- void setCompileOptions(CompileOptions compileOptions);
-
- /** Dex options. */
- @Unmanaged
- DexOptions getDexOptions();
- void setDexOptions(DexOptions dexOptions);
-
- /** JaCoCo options. */
- @Unmanaged
- JacocoExtension getJacoco();
- void setJacoco(JacocoExtension jacoco);
-
- /** Lint options. */
- @Unmanaged
- LintOptions getLintOptions();
- void setLintOptions(LintOptions lintOptions);
-
- /** Packaging options. */
- @Unmanaged
- PackagingOptions getPackagingOptions();
- void setPackagingOptions(PackagingOptions packagingOptions);
-
- /** Options for running tests. */
- @Unmanaged
- TestOptions getTestOptions();
- void setTestOptions(TestOptions testOptions);
-
- /** APK splits */
- @Unmanaged
- Splits getSplits();
- void setSplits(Splits splits);
-
- @Unmanaged
- Collection<LibraryRequest> getLibraryRequests();
- void setLibraryRequests(Collection<LibraryRequest> libraryRequests);
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/BuildType.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/BuildType.java
deleted file mode 100644
index f1adf89..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/BuildType.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import com.android.builder.model.AndroidArtifact;
-
-import org.gradle.api.Named;
-import org.gradle.model.Managed;
-import org.gradle.model.ModelSet;
-import org.gradle.model.Unmanaged;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A Managed build type.
- *
- * TODO: Convert Unmanaged Collection to Managed type when Gradle provides ModelSet for basic class.
- */
- at Managed
-public interface BuildType extends Named {
-
- /**
- * Map of Build Config Fields where the key is the field name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- ModelSet<ClassField> getBuildConfigFields();
-
- /**
- * Map of generated res values where the key is the res name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- ModelSet<ClassField> getResValues();
-
- /**
- * Returns the collection of proguard rule files.
- *
- * <p>These files are only applied to the production code.
- *
- * @return a non-null collection of files.
- * @see #getTestProguardFiles()
- */
- @Unmanaged
- Set<File> getProguardFiles();
- void setProguardFiles(Set<File> files);
-
- /**
- * Returns the collection of proguard rule files for consumers of the library to use.
- *
- * @return a non-null collection of files.
- */
- @Unmanaged
- Set<File> getConsumerProguardFiles();
- void setConsumerProguardFiles(Set<File> files);
-
- /**
- * Returns the collection of proguard rule files to use for the test APK.
- *
- * @return a non-null collection of files.
- */
- @Unmanaged
- Set<File> getTestProguardFiles();
- void setTestProguardFiles(Set<File> files);
-
- /**
- * Returns the map of key value pairs for placeholder substitution in the android manifest file.
- *
- * This map will be used by the manifest merger.
- * @return the map of key value pairs.
- */
- // TODO: Add the commented fields.
- //Map<String, Object> getManifestPlaceholders();
-
- /**
- * Returns whether multi-dex is enabled.
- *
- * This can be null if the flag is not set, in which case the default value is used.
- */
- Boolean getMultiDexEnabled();
- void setMultiDexEnabled(Boolean multiDexEnabled);
-
- String getMultiDexKeepFile();
- void setMultiDexKeepFile(String multiDexKeepFile);
-
- String getMultiDexKeepProguard();
- void setMultiDexKeepProguard(String multiDexKeepProguard);
-
- /**
- * Returns the optional jarjar rule files, or empty if jarjar should be skipped.
- *
- * <p>If more than one file is provided, the rule files will be merged in order with last one
- * win in case of rule redefinition.
- *
- * <p>Can only be used with Jack toolchain.
- *
- * @return the optional jarjar rule file.
- */
- @Unmanaged
- List<File> getJarJarRuleFiles();
- void setJarJarRuleFiles(List<File> jarJarRuleFiles);
-
- /**
- * Returns whether the build type is configured to generate a debuggable apk.
- *
- * @return true if the apk is debuggable
- */
- Boolean getDebuggable();
- void setDebuggable(Boolean isDebuggable);
-
- /**
- * Returns whether the build type is configured to be build with support for code coverage.
- *
- * @return true if code coverage is enabled.
- */
- Boolean getTestCoverageEnabled();
- void setTestCoverageEnabled(Boolean isTestCoverageEnabled);
-
- /**
- * Returns whether the build type is configured to be build with support for pseudolocales.
- *
- * @return true if code coverage is enabled.
- */
- Boolean getPseudoLocalesEnabled();
- void setPseudoLocalesEnabled(Boolean isPseudoLocalesEnabled);
-
- /**
- * Returns whether the build type is configured to generate an apk with debuggable
- * renderscript code.
- *
- * @return true if the apk is debuggable
- */
- Boolean getRenderscriptDebuggable();
- void setRenderscriptDebuggable(Boolean isRenderscriptDebuggable);
-
- /**
- * Returns the optimization level of the renderscript compilation.
- *
- * @return the optimization level.
- */
- Integer getRenderscriptOptimLevel();
- void setRenderscriptOptimLevel(Integer renderscriptOptimLevel);
-
- /**
- * Returns the application id suffix applied to this build type.
- * To get the final application id, use {@link AndroidArtifact#getApplicationId()}.
- *
- * @return the application id
- */
- String getApplicationIdSuffix();
- void setApplicationIdSuffix(String applicationIdSuffix);
-
- /**
- * Returns the version name suffix.
- *
- * @return the version name suffix.
- */
- String getVersionNameSuffix();
- void setVersionNameSuffix(String versionNameSuffix);
-
- /**
- * Returns whether minification is enabled for this build type.
- *
- * @return true if minification is enabled.
- */
- Boolean getMinifyEnabled();
- void setMinifyEnabled(Boolean isMinifyEnabled);
-
- /**
- * Return whether zipalign is enabled for this build type.
- *
- * @return true if zipalign is enabled.
- */
- Boolean getZipAlignEnabled();
- void setZipAlignEnabled(Boolean isZipAlignEnabled);
-
- /**
- * Returns whether the variant embeds the micro app.
- */
- Boolean getEmbedMicroApp();
- void setEmbedMicroApp(Boolean isEmbedMicroApp);
-
- /**
- * Returns the associated signing config or null if none are set on the build type.
- */
- SigningConfig getSigningConfig();
- void setSigningConfig(SigningConfig signingConfig);
-
- Boolean getUseJack();
- void setUseJack(Boolean useJack);
-
- Boolean getShrinkResources();
- void setShrinkResources(Boolean shrinkResources);
-
- NdkBuildType getNdk();
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ClassField.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ClassField.java
deleted file mode 100644
index f04492d..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ClassField.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import org.gradle.model.Managed;
-import org.gradle.model.Unmanaged;
-
-import java.util.Set;
-
-/**
- * A Managed ClassField.
- */
- at Managed
-public interface ClassField {
-
- String getType();
- void setType(String type);
-
- String getName();
- void setName(String name);
-
- String getValue();
- void setValue(String value);
-
- String getDocumentation();
- void setDocumentation(String documentation);
-
- @Unmanaged
- Set<String> getAnnotations();
- void setAnnotations(Set<String> annotations);
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/FilePattern.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/FilePattern.java
deleted file mode 100644
index a710e27..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/FilePattern.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import org.gradle.model.Managed;
-import org.gradle.model.ModelSet;
-
-/**
- * A Managed interface for FilterablePattern.
- */
- at Managed
-public interface FilePattern {
-
- ModelSet<ManagedString> getIncludes();
-
- ModelSet<ManagedString> getExcludes();
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ManagedString.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ManagedString.java
deleted file mode 100644
index 0354dcd..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ManagedString.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import org.gradle.model.Managed;
-
-/**
- * A Managed wrapper of String.
- *
- * This really should not be necessary, but at the moment, there is no way to create a ModelSet of
- * String or equivalent.
- */
- at Managed
-public interface ManagedString {
- String getValue();
- void setValue(String value);
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkConfig.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkConfig.java
deleted file mode 100644
index 5240e3e..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkConfig.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.android.build.gradle.managed;
-
-import com.android.annotations.NonNull;
-
-import org.gradle.model.Managed;
-import org.gradle.model.Unmanaged;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Root configuration model for android-ndk plugin.
- */
- at Managed
-public interface NdkConfig extends NdkBuildType {
-
- /**
- * The toolchain version.
- * Support "gcc" or "clang" (default: "gcc").
- */
- String getToolchain();
- void setToolchain(@NonNull String toolchain);
-
- /**
- * The toolchain version.
- * Set as empty to use the default version for the toolchain.
- */
- String getToolchainVersion();
- void setToolchainVersion(@NonNull String toolchainVersion);
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkOptions.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkOptions.java
deleted file mode 100644
index 344c3ca..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkOptions.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import com.android.annotations.NonNull;
-
-import org.gradle.model.Managed;
-import org.gradle.model.Unmanaged;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * DSL object for variant specific NDK-related settings.
- */
- at Managed
-public interface NdkOptions {
-
- /**
- * The module name.
- * The resulting shared object will be named "lib${getModuleName()}.so".
- */
- String getModuleName();
- void setModuleName(@NonNull String moduleName);
-
- /**
- * The ABI Filters. Leave empty to include all supported ABI.
- */
- @Unmanaged
- Set<String> getAbiFilters();
- void setAbiFilters(@NonNull Set<String> filters);
-
- /**
- * The C Flags
- */
- @Unmanaged
- List<String> getCFlags();
- void setCFlags(@NonNull List<String> cFlags);
-
- /**
- * The C++ Flags
- */
- @Unmanaged
- List<String> getCppFlags();
- void setCppFlags(@NonNull List<String> cppFlags);
-
- /**
- * The linker flags
- */
- @Unmanaged
- List<String> getLdFlags();
- void setLdFlags(@NonNull List<String> ldFlags);
-
- /**
- * The LD Libs
- */
- @Unmanaged
- List<String> getLdLibs();
- void setLdLibs(@NonNull List<String> ldLibs);
-
- /**
- * The STL.
- *
- * Supported values are:
- * - system (default)
- * - gabi++_static
- * - gabi++_shared
- * - stlport_static
- * - stlport_shared
- * - gnustl_static
- * - gnustl_shared
- */
- String getStl();
- void setStl(@NonNull String stl);
-
- Boolean getRenderscriptNdkMode();
- void setRenderscriptNdkMode(Boolean renderscriptNdkMode);
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ProductFlavor.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ProductFlavor.java
deleted file mode 100644
index b3d15ef..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ProductFlavor.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.DimensionAware;
-import com.android.builder.model.Variant;
-
-import org.gradle.api.Named;
-import org.gradle.model.Managed;
-import org.gradle.model.ModelSet;
-import org.gradle.model.Unmanaged;
-
-import java.util.List;
-import java.util.Set;
-
-import java.io.File;
-
-/**
- * A Managed product flavor.
- *
- * TODO: Convert Unmanaged Collection to Managed type when Gradle provides ModelSet for basic class.
- */
- at Managed
-public interface ProductFlavor extends Named, DimensionAware {
-
- /**
- * Map of Build Config Fields where the key is the field name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- @NonNull
- ModelSet<ClassField> getBuildConfigFields();
-
- /**
- * Map of generated res values where the key is the res name.
- *
- * @return a non-null map of class fields (possibly empty).
- */
- @NonNull
- ModelSet<ClassField> getResValues();
-
- /**
- * Returns the collection of proguard rule files.
- *
- * <p>These files are only applied to the production code.
- *
- * @return a non-null collection of files.
- * @see #getTestProguardFiles()
- */
- @Unmanaged
- Set<File> getProguardFiles();
- void setProguardFiles(Set<File> files);
-
- /**
- * Returns the collection of proguard rule files for consumers of the library to use.
- *
- * @return a non-null collection of files.
- */
- @Unmanaged
- Set<File> getConsumerProguardFiles();
- void setConsumerProguardFiles(Set<File> files);
-
- /**
- * Returns the collection of proguard rule files to use for the test APK.
- *
- * @return a non-null collection of files.
- */
- @Unmanaged
- Set<File> getTestProguardFiles();
- void setTestProguardFiles(Set<File> files);
-
- /**
- * Returns the map of key value pairs for placeholder substitution in the android manifest file.
- *
- * This map will be used by the manifest merger.
- * @return the map of key value pairs.
- */
- // TODO: Add the commented fields.
- //Map<String, Object> getManifestPlaceholders();
-
- /**
- * Returns whether multi-dex is enabled.
- *
- * This can be null if the flag is not set, in which case the default value is used.
- */
- @Nullable
- Boolean getMultiDexEnabled();
- void setMultiDexEnabled(Boolean multiDexEnabled);
-
- @Nullable
- File getMultiDexKeepFile();
- void setMultiDexKeepFile(File multiDexKeepFile);
-
- @Nullable
- File getMultiDexKeepProguard();
- void setMultiDexKeepProguard(File multiDexKeepProguard);
-
- /**
- * Returns the optional jarjar rule files, or empty if jarjar should be skipped.
- *
- * <p>If more than one file is provided, the rule files will be merged in order with last one
- * win in case of rule redefinition.
- *
- * <p>Can only be used with Jack toolchain.
- *
- * @return the optional jarjar rule file.
- */
- @Unmanaged
- List<File> getJarJarRuleFiles();
- void setJarJarRuleFiles(List<File> jarJarRuleFiles);
-
- /**
- * Returns the flavor dimension or null if not applicable.
- */
- @Override
- @Nullable
- String getDimension();
- void setDimension(String dimension);
-
- /**
- * Returns the name of the product flavor. This is only the value set on this product flavor.
- * To get the final application id name, use {@link AndroidArtifact#getApplicationId()}.
- *
- * @return the application id.
- */
- @Nullable
- String getApplicationId();
- void setApplicationId(String applicationId);
-
- /**
- * Returns the version code associated with this flavor or null if none have been set.
- * This is only the value set on this product flavor, not necessarily the actual
- * version code used.
- *
- * @return the version code, or null if not specified
- */
- @Nullable
- Integer getVersionCode();
- void setVersionCode(Integer versionCode);
-
- /**
- * Returns the version name. This is only the value set on this product flavor.
- * To get the final value, use {@link Variant#getMergedFlavor()} as well as
- * {@link BuildType#getVersionNameSuffix()}
- *
- * @return the version name.
- */
- @Nullable
- String getVersionName();
- void setVersionName(String versionName);
-
- /**
- * Returns the minSdkVersion. This is only the value set on this product flavor.
- *
- * @return the minSdkVersion, or null if not specified
- */
- @Nullable
- ApiVersion getMinSdkVersion();
-
- /**
- * Returns the targetSdkVersion. This is only the value set on this product flavor.
- *
- * @return the targetSdkVersion, or null if not specified
- */
- @Nullable
- ApiVersion getTargetSdkVersion();
-
- /**
- * Returns the maxSdkVersion. This is only the value set on this produce flavor.
- *
- * @return the maxSdkVersion, or null if not specified
- */
- @Nullable
- Integer getMaxSdkVersion();
- void setMaxSdkVersion(Integer maxSdkVersion);
-
- /**
- * Returns the renderscript target api. This is only the value set on this product flavor.
- * TODO: make final renderscript target api available through the model
- *
- * @return the renderscript target api, or null if not specified
- */
- @Nullable
- Integer getRenderscriptTargetApi();
- void setRenderscriptTargetApi(Integer renderscriptTargetApi);
-
- /**
- * Returns whether the renderscript code should be compiled in support mode to
- * make it compatible with older versions of Android.
- *
- * @return true if support mode is enabled, false if not, and null if not specified.
- */
- @Nullable
- Boolean getRenderscriptSupportModeEnabled();
- void setRenderscriptSupportModeEnabled(Boolean renderscriptSupportModeEnabled);
-
- /**
- * Returns whether the renderscript code should be compiled to generate C/C++ bindings.
- * @return true for C/C++ generation, false for Java, null if not specified.
- */
- @Nullable
- Boolean getRenderscriptNdkModeEnabled();
- void setRenderscriptNdkModeEnabled(Boolean renderscriptNdkModeEnabled);
-
- /**
- * Returns the test application id. This is only the value set on this product flavor.
- * To get the final value, use {@link Variant#getExtraAndroidArtifacts()} with
- * {@link AndroidProject#ARTIFACT_ANDROID_TEST} and then
- * {@link AndroidArtifact#getApplicationId()}
- *
- * @return the test package name.
- */
- @Nullable
- String getTestApplicationId();
- void setTestApplicationId(String testApplicationId);
-
- /**
- * Returns the test instrumentation runner. This is only the value set on this product flavor.
- * TODO: make test instrumentation runner available through the model.
- *
- * @return the test package name.
- */
- @Nullable
- String getTestInstrumentationRunner();
- void setTestInstrumentationRunner(String testInstrumentationRunner);
-
- /**
- * Returns the handlingProfile value. This is only the value set on this product flavor.
- *
- * @return the handlingProfile value.
- */
- @Nullable
- Boolean getTestHandleProfiling();
- void setTestHandleProfiling(Boolean testHandleProfiling);
-
- /**
- * Returns the functionalTest value. This is only the value set on this product flavor.
- *
- * @return the functionalTest value.
- */
- @Nullable
- Boolean getTestFunctionalTest();
- void setTestFunctionalTest(Boolean testFunctionalTest);
-
- /**
- * Returns the resource configuration for this variant.
- *
- * This is the list of -c parameters for aapt.
- *
- * @return the resource configuration options.
- */
- @Unmanaged
- @Nullable
- Set<String> getResourceConfigurations();
- void setResourceConfigurations(Set<String> resourceConfigurations);
-
- /**
- * Returns the associated signing config or null if none are set on the product flavor.
- */
- SigningConfig getSigningConfig();
- void setSigningConfig(SigningConfig signingConfig);
-
- Boolean getUseJack();
- void setUseJack(Boolean useJack);
-
- NdkOptions getNdk();
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/SigningConfig.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/SigningConfig.java
deleted file mode 100644
index 627f5b6..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/SigningConfig.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed;
-
-import org.gradle.api.Named;
-import org.gradle.model.Managed;
-
-/**
- * A Managed SigningConfig.
- */
- at Managed
-public interface SigningConfig extends Named {
-
- String getStoreFile();
- void setStoreFile(String storeFile);
-
- String getStorePassword();
- void setStorePassword(String storePassword);
-
- String getKeyAlias();
- void setKeyAlias(String keyAlias);
-
- String getKeyPassword();
- void setKeyPassword(String keyPassword);
-
- String getStoreType();
- void setStoreType(String storeType);
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/AndroidConfigAdaptor.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/AndroidConfigAdaptor.java
deleted file mode 100644
index f2f118e..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/AndroidConfigAdaptor.java
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed.adaptor;
-
-import static com.android.builder.core.VariantType.ANDROID_TEST;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.api.AndroidSourceDirectorySet;
-import com.android.build.gradle.api.AndroidSourceFile;
-import com.android.build.gradle.api.AndroidSourceSet;
-import com.android.build.gradle.api.VariantFilter;
-import com.android.build.gradle.internal.BuildTypeData;
-import com.android.build.gradle.internal.CompileOptions;
-import com.android.build.gradle.internal.ProductFlavorData;
-import com.android.build.gradle.internal.VariantManager;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.coverage.JacocoExtension;
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.dsl.AdbOptions;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.build.gradle.internal.dsl.DexOptions;
-import com.android.build.gradle.internal.dsl.LintOptions;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.dsl.PreprocessingOptions;
-import com.android.build.gradle.internal.dsl.Splits;
-import com.android.build.gradle.internal.dsl.TestOptions;
-import com.android.build.gradle.managed.BuildType;
-import com.android.build.gradle.managed.ProductFlavor;
-import com.android.build.gradle.managed.SigningConfig;
-import com.android.build.gradle.model.AndroidComponentModelSourceSet;
-import com.android.build.gradle.managed.AndroidConfig;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.core.LibraryRequest;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.builder.testing.api.TestServer;
-import com.android.ide.common.rendering.api.ActionBarCallback;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.file.SourceDirectorySet;
-import org.gradle.language.base.FunctionalSourceSet;
-import org.gradle.language.base.LanguageSourceSet;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-import groovy.lang.Closure;
-
-/**
- * An adaptor to convert a managed.AndroidConfig to an model.AndroidConfig.
- */
-public class AndroidConfigAdaptor implements com.android.build.gradle.AndroidConfig {
-
- private final AndroidConfig model;
- private NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer;
-
- public AndroidConfigAdaptor(
- AndroidConfig model,
- NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer) {
- this.model = model;
- this.sourceSetsContainer = sourceSetsContainer;
- applyProjectSourceSet();
- }
-
- @Override
- public String getBuildToolsVersion() {
- return model.getBuildToolsVersion();
- }
-
- @Override
- public String getCompileSdkVersion() {
- return model.getCompileSdkVersion();
- }
-
- @Override
- public FullRevision getBuildToolsRevision() {
- return model.getBuildToolsRevision();
- }
-
- @Override
- public boolean getEnforceUniquePackageName() {
- return false;
- }
-
- @Override
- public CoreProductFlavor getDefaultConfig() {
- return new ProductFlavorAdaptor(model.getDefaultConfig());
- }
-
- @Override
- @NonNull
- public List<DeviceProvider> getDeviceProviders() {
- return model.getDeviceProviders() == null ?
- Lists.<DeviceProvider>newArrayList() :
- model.getDeviceProviders();
- }
-
- @Override
- @NonNull
- public List<TestServer> getTestServers() {
- return model.getTestServers();
- }
-
- @Override
- public String getDefaultPublishConfig() {
- return model.getDefaultPublishConfig();
- }
-
- @Override
- public boolean getPublishNonDefault() {
- return model.getPublishNonDefault();
- }
-
- @Override
- public Action<VariantFilter> getVariantFilter() {
- return model.getVariantFilter();
- }
-
- @Override
- public String getResourcePrefix() {
- return model.getResourcePrefix();
- }
-
- @Override
- public List<String> getFlavorDimensionList() {
- return null;
- }
-
- @Override
- public boolean getGeneratePureSplits() {
- return model.getGeneratePureSplits();
- }
-
- @Override
- public PreprocessingOptions getPreprocessingOptions() {
- return model.getPreProcessingOptions();
- }
-
- @Override
- public Collection<CoreBuildType> getBuildTypes() {
- return ImmutableList.copyOf(Iterables.transform(model.getBuildTypes().values(),
- new Function<BuildType, CoreBuildType>() {
- @Override
- public CoreBuildType apply(BuildType buildType) {
- return new BuildTypeAdaptor(buildType);
- }
- }));
- }
-
- @Override
- public Collection<CoreProductFlavor> getProductFlavors() {
- return ImmutableList.copyOf(Iterables.transform(model.getProductFlavors().values(),
- new Function<ProductFlavor, CoreProductFlavor>() {
- @Override
- public CoreProductFlavor apply(ProductFlavor flavor) {
- return new ProductFlavorAdaptor(flavor);
- }
- }));
- }
-
- @Override
- public Collection<com.android.builder.model.SigningConfig> getSigningConfigs() {
- return ImmutableList.copyOf(Iterables.transform(model.getSigningConfigs().values(),
- new Function<SigningConfig, com.android.builder.model.SigningConfig>() {
- @Override
- public com.android.builder.model.SigningConfig apply(SigningConfig signingConfig) {
- return new SigningConfigAdaptor(signingConfig);
- }
- }));
- }
-
- @Override
- public NamedDomainObjectContainer<AndroidSourceSet> getSourceSets() {
- return sourceSetsContainer;
- }
-
- @Override
- public Boolean getPackageBuildConfig() {
- return true;
- }
-
- public AndroidComponentModelSourceSet getSources() {
- return model.getSources();
- }
-
- public void setSources(AndroidComponentModelSourceSet sources) {
- model.setSources(sources);
- }
-
- public CoreNdkOptions getNdk() {
- return new NdkOptionsAdaptor(model.getNdk());
- }
-
- @Override
- public AdbOptions getAdbOptions() {
- return model.getAdbOptions();
- }
-
- @Override
- public AaptOptions getAaptOptions() {
- return model.getAaptOptions();
- }
-
- @Override
- public CompileOptions getCompileOptions() {
- return model.getCompileOptions();
- }
-
- @Override
- public DexOptions getDexOptions() {
- return model.getDexOptions();
- }
-
- @Override
- public JacocoExtension getJacoco() {
- return model.getJacoco();
- }
-
- @Override
- public LintOptions getLintOptions() {
- return model.getLintOptions();
- }
-
- @Override
- public PackagingOptions getPackagingOptions() {
- return model.getPackagingOptions();
- }
-
-
- @Override
- public TestOptions getTestOptions() {
- return model.getTestOptions();
- }
-
- @Override
- public Splits getSplits() {
- return model.getSplits();
- }
-
- @Override
- public Collection<LibraryRequest> getLibraryRequests() {
- return model.getLibraryRequests();
- }
-
- private void applyProjectSourceSet() {
- for (FunctionalSourceSet source : getSources()) {
- String name = source.getName();
- AndroidSourceSet androidSource = name.equals(BuilderConstants.MAIN) ?
- sourceSetsContainer.maybeCreate(getDefaultConfig().getName()) :
- sourceSetsContainer.maybeCreate(name);
-
- convertSourceFile(androidSource.getManifest(), source, "manifest");
- convertSourceSet(androidSource.getResources(), source, "resource");
- convertSourceSet(androidSource.getJava(), source, "java");
- convertSourceSet(androidSource.getRes(), source, "res");
- convertSourceSet(androidSource.getAssets(), source, "assets");
- convertSourceSet(androidSource.getAidl(), source, "aidl");
- convertSourceSet(androidSource.getRenderscript(), source, "renderscript");
- convertSourceSet(androidSource.getJni(), source, "jni");
- convertSourceSet(androidSource.getJniLibs(), source, "jniLibs");
- }
- }
-
- @Nullable
- private static AndroidSourceSet findAndroidSourceSet(
- VariantManager variantManager,
- String name) {
- BuildTypeData buildTypeData = variantManager.getBuildTypes().get(name);
- if (buildTypeData != null) {
- return buildTypeData.getSourceSet();
- }
-
- boolean isTest = name.startsWith(ANDROID_TEST.getPrefix());
- name = name.replaceFirst(ANDROID_TEST.getPrefix(), "");
- ProductFlavorData productFlavorData = variantManager.getProductFlavors().get(name);
- if (productFlavorData != null) {
- return isTest ? productFlavorData.getTestSourceSet(ANDROID_TEST) : productFlavorData.getSourceSet();
- }
- return null;
- }
-
- /**
- * Convert a FunctionalSourceSet to an AndroidSourceFile.
- */
- private static void convertSourceFile(
- AndroidSourceFile androidFile,
- FunctionalSourceSet source,
- String sourceName) {
- LanguageSourceSet languageSourceSet = source.findByName(sourceName);
- if (languageSourceSet == null) {
- return;
- }
- SourceDirectorySet dir = languageSourceSet.getSource();
- if (dir == null) {
- return;
- }
- // We use the first file in the file tree until Gradle has a way to specify one source file
- // instead of an entire source set.
- Set<File> files = dir.getAsFileTree().getFiles();
- if (!files.isEmpty()) {
- androidFile.srcFile(Iterables.getOnlyElement(files));
- }
- }
-
- /**
- * Convert a FunctionalSourceSet to an AndroidSourceDirectorySet.
- */
- private static void convertSourceSet(
- AndroidSourceDirectorySet androidDir,
- FunctionalSourceSet source,
- String sourceName) {
- LanguageSourceSet languageSourceSet = source.findByName(sourceName);
- if (languageSourceSet == null) {
- return;
- }
- SourceDirectorySet dir = languageSourceSet.getSource();
- if (dir == null) {
- return;
- }
- androidDir.setSrcDirs(dir.getSrcDirs());
- androidDir.include(dir.getIncludes());
- androidDir.exclude(dir.getExcludes());
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/BuildTypeAdaptor.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/BuildTypeAdaptor.java
deleted file mode 100644
index b6f364d..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/BuildTypeAdaptor.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed.adaptor;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.managed.BuildType;
-import com.android.builder.internal.ClassFieldImpl;
-import com.android.builder.model.ClassField;
-import com.android.builder.model.SigningConfig;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * An adaptor to convert a BuildType to a CoreBuildType.
- */
-public class BuildTypeAdaptor implements CoreBuildType {
- @NonNull
- private final BuildType buildType;
-
- public BuildTypeAdaptor(@NonNull BuildType buildType) {
- this.buildType = buildType;
- }
-
- @NonNull
- @Override
- public String getName() {
- return buildType.getName();
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getBuildConfigFields() {
- ImmutableMap.Builder<String, ClassField> builder = ImmutableMap.builder();
- for (com.android.build.gradle.managed.ClassField cf : buildType.getBuildConfigFields()) {
- builder.put(
- cf.getName(),
- new ClassFieldImpl(
- cf.getType(),
- cf.getName(),
- cf.getValue(),
- Objects.firstNonNull(cf.getAnnotations(), ImmutableSet.<String>of()),
- Objects.firstNonNull(cf.getDocumentation(), "")));
- }
- return builder.build();
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getResValues() {
- ImmutableMap.Builder<String, ClassField> builder = ImmutableMap.builder();
- for (com.android.build.gradle.managed.ClassField cf : buildType.getResValues()) {
- builder.put(
- cf.getName(),
- new ClassFieldImpl(
- cf.getType(),
- cf.getName(),
- cf.getValue(),
- Objects.firstNonNull(cf.getAnnotations(), ImmutableSet.<String>of()),
- Objects.firstNonNull(cf.getDocumentation(), "")));
- }
- return builder.build();
- }
-
- @NonNull
- @Override
- public Collection<File> getProguardFiles() {
- return buildType.getProguardFiles();
- }
-
- @NonNull
- @Override
- public Collection<File> getConsumerProguardFiles() {
- return buildType.getConsumerProguardFiles();
- }
-
- @NonNull
- @Override
- public Collection<File> getTestProguardFiles() {
- return buildType.getTestProguardFiles();
- }
-
- @NonNull
- @Override
- public Map<String, Object> getManifestPlaceholders() {
- // TODO: To be implemented
- return Maps.newHashMap();
- }
-
- @Nullable
- @Override
- public Boolean getMultiDexEnabled() {
- return null;
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepFile() {
- return null;
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepProguard() {
- return null;
- }
-
- @Override
- public boolean isDebuggable() {
- return buildType.getDebuggable();
- }
-
- @Override
- public boolean isTestCoverageEnabled() {
- return buildType.getTestCoverageEnabled();
- }
-
- @Override
- public boolean isJniDebuggable() {
- return Objects.firstNonNull(buildType.getNdk().getDebuggable(), false);
- }
-
- @Override
- public boolean isPseudoLocalesEnabled() {
- return buildType.getPseudoLocalesEnabled();
- }
-
- @Override
- public boolean isRenderscriptDebuggable() {
- return buildType.getRenderscriptDebuggable();
- }
-
- @Override
- public int getRenderscriptOptimLevel() {
- return buildType.getRenderscriptOptimLevel();
- }
-
- @Nullable
- @Override
- public String getApplicationIdSuffix() {
- return buildType.getApplicationIdSuffix();
- }
-
- @Nullable
- @Override
- public String getVersionNameSuffix() {
- return buildType.getVersionNameSuffix();
- }
-
- @Override
- public boolean isMinifyEnabled() {
- return buildType.getMinifyEnabled();
- }
-
- @Override
- public boolean isZipAlignEnabled() {
- return buildType.getZipAlignEnabled();
- }
-
- @Override
- public boolean isEmbedMicroApp() {
- return buildType.getEmbedMicroApp();
- }
-
- @Nullable
- @Override
- public SigningConfig getSigningConfig() {
- return buildType.getSigningConfig() == null ? null : new SigningConfigAdaptor(buildType.getSigningConfig());
- }
-
- @Override
- public CoreNdkOptions getNdkConfig() {
- return new NdkOptionsAdaptor(buildType.getNdk());
- }
-
- @Override
- public Boolean getUseJack() {
- return buildType.getUseJack();
- }
-
- @Override
- public boolean isShrinkResources() {
- return buildType.getShrinkResources();
- }
-
- @NonNull
- @Override
- public List<File> getJarJarRuleFiles() {
- return buildType.getJarJarRuleFiles();
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/NdkOptionsAdaptor.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/NdkOptionsAdaptor.java
deleted file mode 100644
index 4643423..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/NdkOptionsAdaptor.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed.adaptor;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.managed.NdkOptions;
-import com.google.common.base.Joiner;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * An adaptor to convert a NdkConfig to NdkConfig.
- */
-public class NdkOptionsAdaptor implements CoreNdkOptions {
-
- NdkOptions ndkOptions;
-
- public NdkOptionsAdaptor(@NonNull NdkOptions ndkOptions) {
- this.ndkOptions = ndkOptions;
- }
-
- @Nullable
- @Override
- public String getModuleName() {
- return ndkOptions.getModuleName();
- }
-
- @Nullable
- @Override
- public String getcFlags() {
- return Joiner.on(' ').join(ndkOptions.getCFlags());
- }
-
- @Nullable
- @Override
- public List<String> getLdLibs() {
- return ndkOptions.getLdLibs();
- }
-
- @Nullable
- @Override
- public Set<String> getAbiFilters() {
- return ndkOptions.getAbiFilters();
- }
-
- @Nullable
- @Override
- public String getStl() {
- return ndkOptions.getStl();
- }
-
- @Nullable
- @Override
- public Integer getJobs() {
- return null;
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/ProductFlavorAdaptor.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/ProductFlavorAdaptor.java
deleted file mode 100644
index 68639f7..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/ProductFlavorAdaptor.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed.adaptor;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.build.gradle.managed.ProductFlavor;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.internal.ClassFieldImpl;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.ClassField;
-import com.android.builder.model.SigningConfig;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * An adaptor to convert a ProductFlavor to CoreProductFlavor.
- */
-public class ProductFlavorAdaptor implements CoreProductFlavor {
-
- @NonNull
- protected final ProductFlavor productFlavor;
-
- public ProductFlavorAdaptor(@NonNull ProductFlavor productFlavor) {
- this.productFlavor = productFlavor;
- }
-
- @NonNull
- @Override
- public String getName() {
- return productFlavor.getName().equals("defaultConfig") ? BuilderConstants.MAIN : productFlavor.getName();
- }
-
- @Nullable
- @Override
- public String getDimension() {
- return productFlavor.getDimension();
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getBuildConfigFields() {
- ImmutableMap.Builder<String, ClassField> builder = ImmutableMap.builder();
- for (com.android.build.gradle.managed.ClassField cf : productFlavor.getBuildConfigFields()) {
- builder.put(
- cf.getName(),
- new ClassFieldImpl(
- cf.getType(),
- cf.getName(),
- cf.getValue(),
- Objects.firstNonNull(cf.getAnnotations(), ImmutableSet.<String>of()),
- Objects.firstNonNull(cf.getDocumentation(), "")));
- }
- return builder.build();
- }
-
- @NonNull
- @Override
- public Map<String, ClassField> getResValues() {
- ImmutableMap.Builder<String, ClassField> builder = ImmutableMap.builder();
- for (com.android.build.gradle.managed.ClassField cf : productFlavor.getResValues()) {
- builder.put(
- cf.getName(),
- new ClassFieldImpl(
- cf.getType(),
- cf.getName(),
- cf.getValue(),
- Objects.firstNonNull(cf.getAnnotations(), ImmutableSet.<String>of()),
- Objects.firstNonNull(cf.getDocumentation(), "")));
- }
- return builder.build();
- }
-
- @NonNull
- @Override
- public Collection<File> getProguardFiles() {
- return productFlavor.getProguardFiles();
- }
-
- @NonNull
- @Override
- public Collection<File> getConsumerProguardFiles() {
- return productFlavor.getConsumerProguardFiles();
- }
-
- @NonNull
- @Override
- public Collection<File> getTestProguardFiles() {
- return productFlavor.getTestProguardFiles();
- }
-
- @NonNull
- @Override
- public Map<String, Object> getManifestPlaceholders() {
- // TODO: To be implemented
- return Maps.newHashMap();
- }
-
- @Nullable
- @Override
- public Boolean getMultiDexEnabled() {
- return productFlavor.getMultiDexEnabled();
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepFile() {
- return productFlavor.getMultiDexKeepFile();
- }
-
- @Nullable
- @Override
- public File getMultiDexKeepProguard() {
- return productFlavor.getMultiDexKeepProguard();
- }
-
- @Nullable
- @Override
- public String getApplicationId() {
- return productFlavor.getApplicationId();
- }
-
- @Nullable
- @Override
- public Integer getVersionCode() {
- return productFlavor.getVersionCode();
- }
-
- @Nullable
- @Override
- public String getVersionName() {
- return productFlavor.getVersionName();
- }
-
- @Nullable
- @Override
- public ApiVersion getMinSdkVersion() {
- return ApiVersionAdaptor.isEmpty(productFlavor.getMinSdkVersion()) ?
- null :
- new ApiVersionAdaptor(productFlavor.getMinSdkVersion());
- }
-
- @Nullable
- @Override
- public ApiVersion getTargetSdkVersion() {
- return ApiVersionAdaptor.isEmpty(productFlavor.getTargetSdkVersion()) ?
- null :
- new ApiVersionAdaptor(productFlavor.getTargetSdkVersion());
- }
-
- @Nullable
- @Override
- public Integer getMaxSdkVersion() {
- return productFlavor.getMaxSdkVersion();
- }
-
- @Nullable
- @Override
- public Integer getRenderscriptTargetApi() {
- return productFlavor.getRenderscriptTargetApi();
- }
-
- @Nullable
- @Override
- public Boolean getRenderscriptSupportModeEnabled() {
- return productFlavor.getRenderscriptSupportModeEnabled();
- }
-
- @Nullable
- @Override
- public Boolean getRenderscriptNdkModeEnabled() {
- return productFlavor.getRenderscriptNdkModeEnabled();
- }
-
- @Nullable
- @Override
- public String getTestApplicationId() {
- return productFlavor.getTestApplicationId();
- }
-
- @Nullable
- @Override
- public String getTestInstrumentationRunner() {
- return productFlavor.getTestInstrumentationRunner();
- }
-
- @NonNull
- @Override
- public Map<String, String> getTestInstrumentationRunnerArguments() {
- // TODO: To be implemented.
- return Maps.newHashMap();
- }
-
- @Nullable
- @Override
- public Boolean getTestHandleProfiling() {
- return productFlavor.getTestHandleProfiling();
- }
-
- @Nullable
- @Override
- public Boolean getTestFunctionalTest() {
- return productFlavor.getTestFunctionalTest();
- }
-
- @NonNull
- @Override
- public Collection<String> getResourceConfigurations() {
- return productFlavor.getResourceConfigurations();
- }
-
- @Nullable
- @Override
- public SigningConfig getSigningConfig() {
- return productFlavor.getSigningConfig() == null ?
- null :
- new SigningConfigAdaptor(productFlavor.getSigningConfig());
- }
-
- @Override
- public CoreNdkOptions getNdkConfig() {
- return new NdkOptionsAdaptor(productFlavor.getNdk());
- }
-
- @Override
- public Boolean getUseJack() {
- return productFlavor.getUseJack();
- }
-
- @NonNull
- @Override
- public List<File> getJarJarRuleFiles() {
- return productFlavor.getJarJarRuleFiles();
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/SigningConfigAdaptor.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/SigningConfigAdaptor.java
deleted file mode 100644
index ddf199c..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/SigningConfigAdaptor.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.managed.adaptor;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.internal.dsl.CoreSigningConfig;
-import com.android.build.gradle.managed.SigningConfig;
-
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-
-import java.io.File;
-
-/**
- * An adaptor to convert a managed.SigningConfig to a model.SigningConfig.
- */
-public class SigningConfigAdaptor implements CoreSigningConfig {
-
- @NonNull
- private final SigningConfig signingConfig;
-
- public SigningConfigAdaptor(@NonNull SigningConfig signingConfig) {
- this.signingConfig = signingConfig;
- }
-
- @NonNull
- @Override
- public String getName() {
- return signingConfig.getName();
- }
-
- @Nullable
- @Override
- @InputFile @Optional
- public File getStoreFile() {
- return signingConfig.getStoreFile() == null ? null : new File(signingConfig.getStoreFile());
- }
-
- @Nullable
- @Override
- @Input
- public String getStorePassword() {
- return signingConfig.getStorePassword();
- }
-
- @Nullable
- @Override
- @Input
- public String getKeyAlias() {
- return signingConfig.getKeyAlias();
- }
-
- @Nullable
- @Override
- public String getKeyPassword() {
- return signingConfig.getKeyPassword();
- }
-
- @Nullable
- @Override
- @Input
- public String getStoreType() {
- return signingConfig.getStoreType();
- }
-
- @Override
- public boolean isSigningReady() {
- return signingConfig.getStoreFile() != null &&
- signingConfig.getStorePassword() != null &&
- signingConfig.getKeyAlias() != null &&
- signingConfig.getKeyPassword() != null;
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelPlugin.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelPlugin.java
deleted file mode 100644
index e680dad..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelPlugin.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import static com.android.builder.core.VariantType.ANDROID_TEST;
-import static com.android.builder.core.VariantType.UNIT_TEST;
-
-import com.android.build.gradle.internal.ProductFlavorCombo;
-import com.android.build.gradle.managed.AndroidConfig;
-import com.android.build.gradle.managed.BuildType;
-import com.android.build.gradle.managed.ProductFlavor;
-import com.android.builder.core.BuilderConstants;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.StringHelper;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.primitives.Ints;
-
-import org.gradle.api.Action;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.language.base.ProjectSourceSet;
-import org.gradle.language.base.internal.registry.LanguageRegistration;
-import org.gradle.language.base.internal.registry.LanguageRegistry;
-import org.gradle.language.base.plugins.ComponentModelBasePlugin;
-import org.gradle.model.Defaults;
-import org.gradle.model.Finalize;
-import org.gradle.model.Model;
-import org.gradle.model.ModelMap;
-import org.gradle.model.Mutate;
-import org.gradle.model.Path;
-import org.gradle.model.RuleSource;
-import org.gradle.platform.base.BinaryType;
-import org.gradle.platform.base.BinaryTypeBuilder;
-import org.gradle.platform.base.ComponentBinaries;
-import org.gradle.platform.base.ComponentType;
-import org.gradle.platform.base.ComponentTypeBuilder;
-import org.gradle.platform.base.LanguageType;
-import org.gradle.platform.base.LanguageTypeBuilder;
-import org.gradle.tooling.BuildException;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Plugin to set up infrastructure for other android plugins.
- */
-public class AndroidComponentModelPlugin implements Plugin<Project> {
-
- /**
- * The name of ComponentSpec created with android component model plugin.
- */
- public static final String COMPONENT_NAME = "android";
-
- //public static final Pattern GRADLE_ACCEPTABLE_VERSIONS = Pattern.compile("2\\.5.*");
- public static final String GRADLE_ACCEPTABLE_VERSION = "2.5";
-
- private static final String GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY =
- "com.android.build.gradle.overrideVersionCheck";
-
- @Override
- public void apply(Project project) {
- checkGradleVersion(project);
- project.getPlugins().apply(ComponentModelBasePlugin.class);
- }
-
- private static void checkGradleVersion(Project project) {
- String gradleVersion = project.getGradle().getGradleVersion();
- if (!gradleVersion.startsWith(GRADLE_ACCEPTABLE_VERSION)) {
- boolean allowNonMatching = Boolean.getBoolean(GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
- File file = new File("gradle" + File.separator + "wrapper" + File.separator +
- "gradle-wrapper.properties");
- String errorMessage = String.format(
- "Gradle version %s is required. Current version is %s. " +
- "If using the gradle wrapper, try editing the distributionUrl in %s " +
- "to gradle-%s-all.zip",
- GRADLE_ACCEPTABLE_VERSION, gradleVersion, file.getAbsolutePath(),
- GRADLE_ACCEPTABLE_VERSION);
- if (allowNonMatching) {
- project.getLogger().warn(errorMessage);
- project.getLogger().warn("As %s is set, continuing anyways.",
- GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
- } else {
- throw new BuildException(errorMessage, null);
- }
- }
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- public static class Rules extends RuleSource {
-
- @LanguageType
- public void registerLanguage(LanguageTypeBuilder<AndroidLanguageSourceSet> builder) {
- builder.setLanguageName("android");
- builder.defaultImplementation(AndroidLanguageSourceSet.class);
- }
-
- /**
- * Create "android" model block.
- */
- @Model("android")
- public void android(AndroidConfig androidModel) {
- }
-
- @Defaults
- public void androidModelSources(AndroidConfig androidModel,
- @Path("androidSources") AndroidComponentModelSourceSet sources) {
- androidModel.setSources(sources);
- }
-
- @Finalize
- public void finalizeAndroidModel(AndroidConfig androidModel) {
- if (androidModel.getBuildToolsRevision() == null
- && androidModel.getBuildToolsVersion() != null) {
- androidModel.setBuildToolsRevision(
- FullRevision.parseRevision(androidModel.getBuildToolsVersion()));
- }
-
- if (androidModel.getCompileSdkVersion() != null
- && !androidModel.getCompileSdkVersion().startsWith("android-")
- && Ints.tryParse(androidModel.getCompileSdkVersion()) != null) {
- androidModel.setCompileSdkVersion("android-" + androidModel.getCompileSdkVersion());
- }
-
- }
-
- @Defaults
- public void createDefaultBuildTypes(
- @Path("android.buildTypes") ModelMap<BuildType> buildTypes) {
- buildTypes.create(BuilderConstants.DEBUG, new Action<BuildType>() {
- @Override
- public void execute(BuildType buildType) {
- buildType.setDebuggable(true);
- buildType.setEmbedMicroApp(false);
- }
- });
- buildTypes.create(BuilderConstants.RELEASE);
- }
-
- @Model
- public List<ProductFlavorCombo<ProductFlavor>> createProductFlavorCombo(
- @Path("android.productFlavors") ModelMap<ProductFlavor> productFlavors) {
- // TODO: Create custom product flavor container to manually configure flavor dimensions.
- Set<String> flavorDimensionList = Sets.newHashSet();
- for (ProductFlavor flavor : productFlavors.values()) {
- if (flavor.getDimension() != null) {
- flavorDimensionList.add(flavor.getDimension());
- }
- }
-
- return ProductFlavorCombo.createCombinations(
- Lists.newArrayList(flavorDimensionList),
- productFlavors.values());
- }
-
- @ComponentType
- public void defineComponentType(ComponentTypeBuilder<AndroidComponentSpec> builder) {
- builder.defaultImplementation(DefaultAndroidComponentSpec.class);
- }
-
- @Mutate
- public void createAndroidComponents(ModelMap<AndroidComponentSpec> androidComponents) {
- androidComponents.create(COMPONENT_NAME);
- }
-
- @Model
- public AndroidComponentModelSourceSet androidSources(ServiceRegistry serviceRegistry) {
- Instantiator instantiator = serviceRegistry.get(Instantiator.class);
- return new AndroidComponentModelSourceSet(instantiator);
- }
-
- /**
- * Create all source sets for each AndroidBinary.
- */
- @Mutate
- public void createVariantSourceSet(
- @Path("android.sources") final AndroidComponentModelSourceSet sources,
- @Path("android.buildTypes") final ModelMap<BuildType> buildTypes,
- @Path("android.productFlavors") ModelMap<ProductFlavor> flavors,
- List<ProductFlavorCombo<ProductFlavor>> flavorGroups, ProjectSourceSet projectSourceSet,
- LanguageRegistry languageRegistry) {
- sources.setProjectSourceSet(projectSourceSet);
- for (LanguageRegistration languageRegistration : languageRegistry) {
- sources.registerLanguage(languageRegistration);
- }
-
- // Create main source set.
- sources.create("main");
- sources.create(ANDROID_TEST.getPrefix());
- sources.create(UNIT_TEST.getPrefix());
-
- for (BuildType buildType : buildTypes.values()) {
- sources.maybeCreate(buildType.getName());
-
- for (ProductFlavorCombo group: flavorGroups) {
- sources.maybeCreate(group.getName());
- if (!group.getFlavorList().isEmpty()) {
- sources.maybeCreate(
- group.getName() + StringHelper.capitalize(buildType.getName()));
- }
-
- }
-
- }
- if (flavorGroups.size() != flavors.size()) {
- // If flavorGroups and flavors are the same size, there is at most 1 flavor
- // dimension. So we don't need to reconfigure the source sets for flavorGroups.
- for (ProductFlavor flavor: flavors.values()) {
- sources.maybeCreate(flavor.getName());
- }
- }
- }
-
- @Finalize
- public void setDefaultSrcDir(
- @Path("android.sources") AndroidComponentModelSourceSet sourceSet) {
- sourceSet.setDefaultSrcDir();
- }
-
- @BinaryType
- public void defineBinaryType(BinaryTypeBuilder<AndroidBinary> builder) {
- builder.defaultImplementation(DefaultAndroidBinary.class);
- }
-
- @ComponentBinaries
- public void createBinaries(
- final ModelMap<AndroidBinary> binaries,
- @Path("android") final AndroidConfig androidConfig,
- @Path("android.buildTypes") final ModelMap<BuildType> buildTypes,
- final List<ProductFlavorCombo<ProductFlavor>> flavorCombos,
- final AndroidComponentSpec spec) {
- if (flavorCombos.isEmpty()) {
- flavorCombos.add(new ProductFlavorCombo<ProductFlavor>());
- }
-
- for (final BuildType buildType : buildTypes.values()) {
- for (final ProductFlavorCombo<ProductFlavor> flavorCombo : flavorCombos) {
- binaries.create(getBinaryName(buildType, flavorCombo),
- new Action<AndroidBinary>() {
- @Override
- public void execute(AndroidBinary androidBinary) {
- DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary;
- binary.setBuildType(buildType);
- binary.setProductFlavors(flavorCombo.getFlavorList());
- }
- });
- }
- }
- }
-
- private static String getBinaryName(BuildType buildType, ProductFlavorCombo flavorCombo) {
- if (flavorCombo.getFlavorList().isEmpty()) {
- return buildType.getName();
- } else {
- return flavorCombo.getName() + StringHelper.capitalize(buildType.getName());
- }
-
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelSourceSet.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelSourceSet.java
deleted file mode 100644
index 7e555d9..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelSourceSet.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.file.SourceDirectorySet;
-import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.language.base.FunctionalSourceSet;
-import org.gradle.language.base.LanguageSourceSet;
-import org.gradle.language.base.ProjectSourceSet;
-import org.gradle.language.base.internal.DefaultFunctionalSourceSet;
-import org.gradle.language.base.internal.registry.LanguageRegistration;
-
-/**
- * Collection of source sets for each build type, product flavor or variant.
- *
- * Until Gradle provide a way to create and store source sets to use between multiple binaries, we
- * need to create a container for such source sets.
- */
-// TODO: Remove dependencies on internal Gradle class.
-public class AndroidComponentModelSourceSet
- extends AbstractNamedDomainObjectContainer<FunctionalSourceSet>
- implements NamedDomainObjectContainer<FunctionalSourceSet> {
- ProjectSourceSet projectSourceSet;
-
- public AndroidComponentModelSourceSet (Instantiator instantiator) {
- super(FunctionalSourceSet.class, instantiator);
- }
-
- public <T extends LanguageSourceSet> void registerLanguage(final LanguageRegistration<T> languageRegistration) {
- // Hardcoding registered language sets and default source sets for now.
- all(new Action<FunctionalSourceSet>() {
- @Override
- public void execute(final FunctionalSourceSet functionalSourceSet) {
- functionalSourceSet.registerFactory(
- languageRegistration.getSourceSetType(),
- languageRegistration.getSourceSetFactory(functionalSourceSet.getName()));
- }
- });
- }
-
- /**
- * Setter for projectSourceSet.
- * Having a setter avoid the need for ProjectSourceSet to be part of the constructor, which can cause
- * cyclic rule dependenecy issues.
- */
- public void setProjectSourceSet(ProjectSourceSet projectSourceSet) {
- this.projectSourceSet = projectSourceSet;
- }
-
- @Override
- protected FunctionalSourceSet doCreate(String name) {
- return getInstantiator().newInstance(
- DefaultFunctionalSourceSet.class,
- name,
- getInstantiator(),
- projectSourceSet);
- }
-
- public void addDefaultSourceSet(final String sourceSetName, final Class<? extends LanguageSourceSet> type) {
- all(new Action<FunctionalSourceSet>() {
- @Override
- public void execute(FunctionalSourceSet functionalSourceSet) {
- functionalSourceSet.maybeCreate(sourceSetName, type);
- }
- });
- }
-
- /**
- * Set the default directory for each source sets if it is empty.
- */
- public void setDefaultSrcDir() {
- all(new Action<FunctionalSourceSet>() {
- @Override
- public void execute(final FunctionalSourceSet functionalSourceSet) {
- functionalSourceSet.all(
- new Action<LanguageSourceSet>() {
- @Override
- public void execute(LanguageSourceSet languageSourceSet) {
- SourceDirectorySet source = languageSourceSet.getSource();
- if (source.getSrcDirs().isEmpty()) {
- source.srcDir("src/" + functionalSourceSet.getName() + "/" + languageSourceSet.getName());
- }
- }
- });
- }
- });
- }
-}
\ No newline at end of file
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelTestPlugin.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelTestPlugin.java
deleted file mode 100644
index 187c0a0..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentModelTestPlugin.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
-import static com.android.builder.core.VariantType.ANDROID_TEST;
-
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.VariantManager;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.TestVariantData;
-import com.android.builder.core.BuilderConstants;
-import com.google.common.base.Preconditions;
-
-import org.gradle.api.Action;
-import org.gradle.api.Task;
-import org.gradle.model.ModelMap;
-import org.gradle.model.Mutate;
-import org.gradle.model.RuleSource;
-import org.gradle.platform.base.BinaryContainer;
-
-/**
- * Plugin for creating test tasks for AndroidBinary.
- */
- at SuppressWarnings("MethodMayBeStatic")
-public class AndroidComponentModelTestPlugin extends RuleSource {
-
- @Mutate
- public void createConnectedTestTasks(
- final ModelMap<Task> tasks,
- BinaryContainer binaries,
- TaskManager taskManager,
- ModelMap<AndroidComponentSpec> specs) {
- final VariantManager variantManager =
- ((DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME)).getVariantManager();
- binaries.withType(AndroidBinary.class, new Action<AndroidBinary>() {
- @Override
- public void execute(AndroidBinary androidBinary) {
- DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary;
-
- // TODO: compare against testBuildType instead of BuilderConstants.DEBUG.
- if (!binary.getBuildType().getName().equals(BuilderConstants.DEBUG)) {
- return;
-
- }
-
- // Create test tasks.
- BaseVariantData testedVariantData = binary.getVariantData();
-
- Preconditions.checkState(testedVariantData != null,
- "Internal error: tested variant must be created before test variant.");
-
- TestVariantData testVariantData =
- variantManager.createTestVariantData(testedVariantData, ANDROID_TEST);
- variantManager.getVariantDataList().add(testVariantData);
- variantManager.createTasksForVariantData(
- new TaskModelMapAdaptor(tasks),
- testVariantData);
- }
- });
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidLanguageSourceSet.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidLanguageSourceSet.java
deleted file mode 100644
index a0870de..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidLanguageSourceSet.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import org.gradle.language.base.LanguageSourceSet;
-import org.gradle.language.base.sources.BaseLanguageSourceSet;
-
-/**
- * Implementation of LanguageSourceSet for Android's sources.
- */
-public class AndroidLanguageSourceSet extends BaseLanguageSourceSet implements LanguageSourceSet {
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidObject.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidObject.java
deleted file mode 100644
index 2a48526..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidObject.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import org.gradle.platform.base.TransformationFileType;
-
-/**
- * Output of an Android LanguageTransform.
- */
-public class AndroidObject implements TransformationFileType {
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AppComponentModelPlugin.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AppComponentModelPlugin.java
deleted file mode 100644
index 0fd6ac7..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AppComponentModelPlugin.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import static com.android.build.gradle.model.ModelConstants.IS_APPLICATION;
-import static com.android.build.gradle.model.ModelConstants.TASK_MANAGER;
-
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.DependencyManager;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.variant.ApplicationVariantFactory;
-import com.android.build.gradle.internal.variant.VariantFactory;
-import com.android.builder.core.AndroidBuilder;
-
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.model.Model;
-import org.gradle.model.RuleSource;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-/**
- * Gradle component model plugin class for 'application' projects.
- */
-public class AppComponentModelPlugin implements Plugin<Project> {
-
- @Override
- public void apply(Project project) {
- project.getPluginManager().apply(BaseComponentModelPlugin.class);
- project.getPluginManager().apply(AndroidComponentModelTestPlugin.class);
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- public static class Rules extends RuleSource {
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- @Model(IS_APPLICATION)
- public Boolean isApplication() {
- return true;
- }
-
- @Model(TASK_MANAGER)
- public TaskManager createTaskManager(
- AndroidConfig androidExtension,
- Project project,
- AndroidBuilder androidBuilder,
- SdkHandler sdkHandler,
- ExtraModelInfo extraModelInfo,
- ToolingModelBuilderRegistry toolingRegistry) {
- DependencyManager dependencyManager = new DependencyManager(project, extraModelInfo);
-
- return new ApplicationComponentTaskManager(
- project,
- androidBuilder,
- androidExtension,
- sdkHandler,
- dependencyManager,
- toolingRegistry);
- }
-
- @Model
- public VariantFactory createVariantFactory(
- ServiceRegistry serviceRegistry,
- AndroidBuilder androidBuilder,
- AndroidConfig extension) {
- Instantiator instantiator = serviceRegistry.get(Instantiator.class);
- return new ApplicationVariantFactory(instantiator, androidBuilder, extension);
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ApplicationComponentTaskManager.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ApplicationComponentTaskManager.java
deleted file mode 100644
index d191c7a..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ApplicationComponentTaskManager.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.ApplicationTaskManager;
-import com.android.build.gradle.internal.DependencyManager;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.builder.core.AndroidBuilder;
-import com.google.common.collect.ImmutableList;
-
-import org.gradle.api.Project;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.util.Collection;
-
-/**
- * TaskManager for creating tasks in an Android application project with component model plugin.
- */
-public class ApplicationComponentTaskManager extends ApplicationTaskManager {
-
- public ApplicationComponentTaskManager (
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- super(project, androidBuilder, extension, sdkHandler, dependencyManager, toolingRegistry);
- isNdkTaskNeeded = false;
- }
-
- @Override
- protected Collection<Object> getNdkBuildable(BaseVariantData variantData) {
- NdkComponentModelPlugin plugin = project.getPlugins().getPlugin(NdkComponentModelPlugin.class);
- return ImmutableList.<Object>copyOf(plugin.getBinaries(variantData.getVariantConfiguration()));
- }
-
- @Override
- public void configureScopeForNdk(@NonNull VariantScope scope) {
- NdkComponentModelPlugin.configureScopeForNdk(scope);
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/BaseComponentModelPlugin.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/BaseComponentModelPlugin.java
deleted file mode 100644
index 2144cd7..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/BaseComponentModelPlugin.java
+++ /dev/null
@@ -1,576 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
-import static com.android.build.gradle.model.ModelConstants.ANDROID_BUILDER;
-import static com.android.build.gradle.model.ModelConstants.ANDROID_CONFIG_ADAPTOR;
-import static com.android.build.gradle.model.ModelConstants.EXTRA_MODEL_INFO;
-import static com.android.builder.core.BuilderConstants.DEBUG;
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.AndroidGradleOptions;
-import com.android.build.gradle.internal.AndroidConfigHelper;
-import com.android.build.gradle.internal.ExecutionConfigurationUtil;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.LibraryCache;
-import com.android.build.gradle.internal.LoggerWrapper;
-import com.android.build.gradle.internal.NdkOptionsHelper;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.VariantManager;
-import com.android.build.gradle.internal.coverage.JacocoPlugin;
-import com.android.build.gradle.internal.process.GradleJavaProcessExecutor;
-import com.android.build.gradle.internal.process.GradleProcessExecutor;
-import com.android.build.gradle.internal.profile.RecordingBuildListener;
-import com.android.build.gradle.internal.tasks.DependencyReportTask;
-import com.android.build.gradle.internal.tasks.SigningReportTask;
-import com.android.build.gradle.internal.variant.VariantFactory;
-import com.android.build.gradle.managed.AndroidConfig;
-import com.android.build.gradle.managed.BuildType;
-import com.android.build.gradle.managed.ClassField;
-import com.android.build.gradle.managed.NdkConfig;
-import com.android.build.gradle.managed.NdkOptions;
-import com.android.build.gradle.managed.ProductFlavor;
-import com.android.build.gradle.managed.SigningConfig;
-import com.android.build.gradle.managed.adaptor.AndroidConfigAdaptor;
-import com.android.build.gradle.managed.adaptor.BuildTypeAdaptor;
-import com.android.build.gradle.managed.adaptor.ProductFlavorAdaptor;
-import com.android.build.gradle.tasks.JillTask;
-import com.android.build.gradle.tasks.PreDex;
-import com.android.builder.Version;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.internal.compiler.JackConversionCache;
-import com.android.builder.internal.compiler.PreDexCache;
-import com.android.builder.profile.ProcessRecorderFactory;
-import com.android.builder.profile.Recorder;
-import com.android.builder.profile.ThreadRecorder;
-import com.android.builder.sdk.TargetInfo;
-import com.android.builder.signing.DefaultSigningConfig;
-import com.android.ide.common.internal.ExecutorSingleton;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.signing.KeystoreHelper;
-import com.android.prefs.AndroidLocation;
-import com.android.utils.ILogger;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import org.gradle.api.Action;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.execution.TaskExecutionGraph;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.plugins.JavaBasePlugin;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.language.base.FunctionalSourceSet;
-import org.gradle.language.base.LanguageSourceSet;
-import org.gradle.model.Defaults;
-import org.gradle.model.Model;
-import org.gradle.model.ModelMap;
-import org.gradle.model.Mutate;
-import org.gradle.model.Path;
-import org.gradle.model.RuleSource;
-import org.gradle.model.internal.core.ModelCreators;
-import org.gradle.model.internal.core.ModelReference;
-import org.gradle.model.internal.registry.ModelRegistry;
-import org.gradle.platform.base.BinaryContainer;
-import org.gradle.platform.base.ComponentSpecContainer;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.io.File;
-import java.io.IOException;
-import java.security.KeyStore;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import groovy.lang.Closure;
-
-public class BaseComponentModelPlugin implements Plugin<Project> {
-
- private ToolingModelBuilderRegistry toolingRegistry;
-
- private ModelRegistry modelRegistry;
-
- @Inject
- protected BaseComponentModelPlugin(ToolingModelBuilderRegistry toolingRegistry,
- ModelRegistry modelRegistry) {
- this.toolingRegistry = toolingRegistry;
- this.modelRegistry = modelRegistry;
- }
-
- /**
- * Replace BasePlugin's apply method for component model.
- */
- @Override
- public void apply(Project project) {
- ExecutionConfigurationUtil.setThreadPoolSize(project);
- try {
- List<Recorder.Property> propertyList = Lists.newArrayList(
- new Recorder.Property("plugin_version", Version.ANDROID_GRADLE_PLUGIN_VERSION),
- new Recorder.Property("next_gen_plugin", "true"),
- new Recorder.Property("gradle_version", project.getGradle().getGradleVersion())
- );
- String benchmarkName = AndroidGradleOptions.getBenchmarkName(project);
- if (benchmarkName != null) {
- propertyList.add(new Recorder.Property("benchmark_name", benchmarkName));
- }
- String benchmarkMode = AndroidGradleOptions.getBenchmarkMode(project);
- if (benchmarkMode != null) {
- propertyList.add(new Recorder.Property("benchmark_mode", benchmarkMode));
- }
-
- ProcessRecorderFactory.initialize(
- new LoggerWrapper(project.getLogger()),
- project.getRootProject()
- .file("profiler" + System.currentTimeMillis() + ".json"),
- propertyList);
- } catch (IOException e) {
- throw new RuntimeException("Unable to initialize ProcessRecorderFactory");
- }
- project.getGradle().addListener(new RecordingBuildListener(ThreadRecorder.get()));
-
- project.getPlugins().apply(AndroidComponentModelPlugin.class);
- project.getPlugins().apply(JavaBasePlugin.class);
- project.getPlugins().apply(JacocoPlugin.class);
-
- // TODO: Create configurations for build types and flavors, or migrate to new dependency
- // management if it's ready.
- ConfigurationContainer configurations = project.getConfigurations();
- createConfiguration(configurations, "compile", "Classpath for default sources.");
- createConfiguration(configurations, "default-metadata", "Metadata for published APKs");
- createConfiguration(configurations, "default-mapping", "Metadata for published APKs");
-
- project.getPlugins().apply(NdkComponentModelPlugin.class);
-
- // Remove this when our models no longer depends on Project.
- modelRegistry.create(ModelCreators
- .bridgedInstance(ModelReference.of("projectModel", Project.class), project)
- .descriptor("Model of project.").build());
-
- toolingRegistry.register(new ComponentModelBuilder(modelRegistry));
-
- // Inserting the ToolingModelBuilderRegistry into the model so that it can be use to create
- // TaskManager in child classes.
- modelRegistry.create(ModelCreators.bridgedInstance(
- ModelReference.of("toolingRegistry", ToolingModelBuilderRegistry.class),
- toolingRegistry).descriptor("Tooling model builder model registry.").build());
- }
-
- private static void createConfiguration(@NonNull ConfigurationContainer configurations,
- @NonNull String configurationName, @NonNull String configurationDescription) {
- Configuration configuration = configurations.findByName(configurationName);
- if (configuration == null) {
- configuration = configurations.create(configurationName);
- }
-
- configuration.setVisible(false);
- configuration.setDescription(configurationDescription);
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- public static class Rules extends RuleSource {
-
- @Defaults
- public void configureAndroidModel(
- AndroidConfig androidModel,
- ServiceRegistry serviceRegistry) {
- Instantiator instantiator = serviceRegistry.get(Instantiator.class);
- AndroidConfigHelper.configure(androidModel, instantiator);
-
- androidModel.getSigningConfigs().create(DEBUG, new Action<SigningConfig>() {
- @Override
- public void execute(SigningConfig signingConfig) {
- try {
- signingConfig.setStoreFile(KeystoreHelper.defaultDebugKeystoreLocation());
- signingConfig.setStorePassword(DefaultSigningConfig.DEFAULT_PASSWORD);
- signingConfig.setKeyAlias(DefaultSigningConfig.DEFAULT_ALIAS);
- signingConfig.setKeyPassword(DefaultSigningConfig.DEFAULT_PASSWORD);
- signingConfig.setStoreType(KeyStore.getDefaultType());
- } catch (AndroidLocation.AndroidLocationException e) {
- throw new RuntimeException(e);
- }
- }
- });
- }
-
- // com.android.build.gradle.AndroidConfig do not contain an NdkConfig. Copy it to the
- // defaultConfig for now.
- @Defaults
- public void copyNdkConfig(
- @Path("android.defaultConfig.ndk") NdkOptions defaultNdkConfig,
- @Path("android.ndk") NdkConfig pluginNdkConfig) {
- NdkOptionsHelper.init(defaultNdkConfig);
- NdkOptionsHelper.merge(defaultNdkConfig, pluginNdkConfig);
- }
-
- // TODO: Remove code duplicated from BasePlugin.
- @Model(EXTRA_MODEL_INFO)
- public ExtraModelInfo createExtraModelInfo(
- Project project,
- @NonNull @Path("isApplication") Boolean isApplication) {
- return new ExtraModelInfo(project, isApplication);
- }
-
- @Model
- public SdkHandler createSdkHandler(final Project project) {
- final ILogger logger = new LoggerWrapper(project.getLogger());
- final SdkHandler sdkHandler = new SdkHandler(project, logger);
-
- // call back on execution. This is called after the whole build is done (not
- // after the current project is done).
- // This is will be called for each (android) projects though, so this should support
- // being called 2+ times.
- project.getGradle().buildFinished(new Closure<Object>(this, this) {
- public void doCall(Object it) {
- ExecutorSingleton.shutdown();
- sdkHandler.unload();
- try {
- PreDexCache.getCache().clear(project.getRootProject()
- .file(String.valueOf(project.getRootProject().getBuildDir()) + "/"
- + FD_INTERMEDIATES + "/dex-cache/cache.xml"), logger);
- JackConversionCache.getCache().clear(project.getRootProject()
- .file(String.valueOf(project.getRootProject().getBuildDir()) + "/"
- + FD_INTERMEDIATES + "/jack-cache/cache.xml"), logger);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- LibraryCache.getCache().unload();
- }
-
- public void doCall() {
- doCall(null);
- }
- });
-
- project.getGradle().getTaskGraph().whenReady(new Closure<Void>(this, this) {
- public void doCall(TaskExecutionGraph taskGraph) {
- for (Task task : taskGraph.getAllTasks()) {
- if (task instanceof PreDex) {
- PreDexCache.getCache().load(project.getRootProject()
- .file(String.valueOf(project.getRootProject().getBuildDir())
- + "/" + FD_INTERMEDIATES + "/dex-cache/cache.xml"));
- break;
- } else if (task instanceof JillTask) {
- JackConversionCache.getCache().load(project.getRootProject()
- .file(String.valueOf(project.getRootProject().getBuildDir())
- + "/" + FD_INTERMEDIATES + "/jack-cache/cache.xml"));
- break;
- }
- }
- }
- });
-
- // setup SDK repositories.
- for (final File file : sdkHandler.getSdkLoader().getRepositories()) {
- project.getRepositories().maven(new Action<MavenArtifactRepository>() {
- @Override
- public void execute(MavenArtifactRepository repo) {
- repo.setUrl(file.toURI());
-
- }
- });
- }
- return sdkHandler;
- }
-
- @Model(ANDROID_BUILDER)
- public AndroidBuilder createAndroidBuilder(Project project, ExtraModelInfo extraModelInfo) {
- String creator = "Android Gradle";
- ILogger logger = new LoggerWrapper(project.getLogger());
-
- return new AndroidBuilder(project.equals(project.getRootProject()) ? project.getName()
- : project.getPath(), creator, new GradleProcessExecutor(project),
- new GradleJavaProcessExecutor(project),
- extraModelInfo, logger, project.getLogger().isEnabled(LogLevel.INFO));
-
- }
-
- @Mutate
- public void initDebugBuildTypes(
- @Path("android.buildTypes") ModelMap<BuildType> buildTypes,
- @Path("android.signingConfigs") final ModelMap<SigningConfig> signingConfigs) {
- buildTypes.beforeEach(new Action<BuildType>() {
- @Override
- public void execute(BuildType buildType) {
- initBuildType(buildType);
- }
- });
-
- buildTypes.named(DEBUG, new Action<BuildType>() {
- @Override
- public void execute(BuildType buildType) {
- buildType.setSigningConfig(signingConfigs.get(DEBUG));
- }
- });
- }
-
- private static void initBuildType(@NonNull BuildType buildType) {
- buildType.setDebuggable(false);
- buildType.setTestCoverageEnabled(false);
- buildType.setPseudoLocalesEnabled(false);
- buildType.setRenderscriptDebuggable(false);
- buildType.setRenderscriptOptimLevel(3);
- buildType.setMinifyEnabled(false);
- buildType.setZipAlignEnabled(true);
- buildType.setEmbedMicroApp(true);
- buildType.setUseJack(false);
- buildType.setShrinkResources(false);
- buildType.setProguardFiles(Sets.<File>newHashSet());
- buildType.setConsumerProguardFiles(Sets.<File>newHashSet());
- buildType.setTestProguardFiles(Sets.<File>newHashSet());
- }
-
- @Mutate
- public void initDefaultConfig(@Path("android.defaultConfig") ProductFlavor defaultConfig) {
- initProductFlavor(defaultConfig);
- }
-
- @Mutate
- public void initProductFlavors(
- @Path("android.productFlavors") final ModelMap<ProductFlavor> productFlavors) {
- productFlavors.beforeEach(new Action<ProductFlavor>() {
- @Override
- public void execute(ProductFlavor productFlavor) {
- initProductFlavor(productFlavor);
- }
- });
- }
-
- private void initProductFlavor(ProductFlavor productFlavor) {
- productFlavor.setProguardFiles(Sets.<File>newHashSet());
- productFlavor.setConsumerProguardFiles(Sets.<File>newHashSet());
- productFlavor.setTestProguardFiles(Sets.<File>newHashSet());
- productFlavor.setResourceConfigurations(Sets.<String>newHashSet());
- productFlavor.setJarJarRuleFiles(Lists.<File>newArrayList());
- productFlavor.getBuildConfigFields().beforeEach(new Action<ClassField>() {
- @Override
- public void execute(ClassField classField) {
- classField.setAnnotations(Sets.<String>newHashSet());
- }
- });
- productFlavor.getResValues().beforeEach(new Action<ClassField>() {
- @Override
- public void execute(ClassField classField) {
- classField.setAnnotations(Sets.<String>newHashSet());
- }
- });
- }
-
- @Mutate
- public void addDefaultAndroidSourceSet(
- @Path("android.sources") AndroidComponentModelSourceSet sources) {
- sources.addDefaultSourceSet("resources", AndroidLanguageSourceSet.class);
- sources.addDefaultSourceSet("java", AndroidLanguageSourceSet.class);
- sources.addDefaultSourceSet("manifest", AndroidLanguageSourceSet.class);
- sources.addDefaultSourceSet("res", AndroidLanguageSourceSet.class);
- sources.addDefaultSourceSet("assets", AndroidLanguageSourceSet.class);
- sources.addDefaultSourceSet("aidl", AndroidLanguageSourceSet.class);
- sources.addDefaultSourceSet("renderscript", AndroidLanguageSourceSet.class);
- sources.addDefaultSourceSet("jniLibs", AndroidLanguageSourceSet.class);
-
- sources.all(new Action<FunctionalSourceSet>() {
- @Override
- public void execute(FunctionalSourceSet functionalSourceSet) {
- LanguageSourceSet manifest = functionalSourceSet.getByName("manifest");
- manifest.getSource().setIncludes(ImmutableList.of("AndroidManifest.xml"));
- }
- });
- }
-
- @Model(ANDROID_CONFIG_ADAPTOR)
- public com.android.build.gradle.AndroidConfig createModelAdaptor(
- ServiceRegistry serviceRegistry,
- AndroidConfig androidExtension,
- Project project,
- @Path("isApplication") Boolean isApplication) {
- Instantiator instantiator = serviceRegistry.get(Instantiator.class);
- return new AndroidConfigAdaptor(androidExtension, AndroidConfigHelper
- .createSourceSetsContainer(project, instantiator, !isApplication));
- }
-
- @Mutate
- public void createAndroidComponents(
- ComponentSpecContainer androidSpecs,
- ServiceRegistry serviceRegistry, AndroidConfig androidExtension,
- com.android.build.gradle.AndroidConfig adaptedModel,
- @Path("android.buildTypes") ModelMap<BuildType> buildTypes,
- @Path("android.productFlavors") ModelMap<ProductFlavor> productFlavors,
- @Path("android.signingConfigs") ModelMap<SigningConfig> signingConfigs,
- VariantFactory variantFactory,
- TaskManager taskManager,
- Project project,
- AndroidBuilder androidBuilder,
- SdkHandler sdkHandler,
- ExtraModelInfo extraModelInfo,
- @Path("isApplication") Boolean isApplication) {
- Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-
- // check if the target has been set.
- TargetInfo targetInfo = androidBuilder.getTargetInfo();
- if (targetInfo == null) {
- sdkHandler.initTarget(androidExtension.getCompileSdkVersion(),
- androidExtension.getBuildToolsRevision(),
- androidExtension.getLibraryRequests(), androidBuilder);
- }
-
- VariantManager variantManager = new VariantManager(project, androidBuilder,
- adaptedModel, variantFactory, taskManager, instantiator);
-
- for (BuildType buildType : buildTypes.values()) {
- variantManager.addBuildType(new BuildTypeAdaptor(buildType));
- }
-
- for (ProductFlavor productFlavor : productFlavors.values()) {
- variantManager.addProductFlavor(new ProductFlavorAdaptor(productFlavor));
- }
-
- DefaultAndroidComponentSpec spec =
- (DefaultAndroidComponentSpec) androidSpecs.get(COMPONENT_NAME);
- spec.setExtension(androidExtension);
- spec.setVariantManager(variantManager);
- }
-
- @Mutate
- public void createVariantData(
- ModelMap<AndroidBinary> binaries,
- ModelMap<AndroidComponentSpec> specs,
- TaskManager taskManager) {
- final VariantManager variantManager =
- ((DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME)).getVariantManager();
- binaries.afterEach(new Action<AndroidBinary>() {
- @Override
- public void execute(AndroidBinary androidBinary) {
- DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary;
- List<ProductFlavorAdaptor> adaptedFlavors = Lists.newArrayList();
- for (ProductFlavor flavor : binary.getProductFlavors()) {
- adaptedFlavors.add(new ProductFlavorAdaptor(flavor));
- }
- binary.setVariantData(
- variantManager.createVariantData(
- new BuildTypeAdaptor(binary.getBuildType()),
- adaptedFlavors));
- variantManager.getVariantDataList().add(binary.getVariantData());
- }
- });
- }
-
- @Mutate
- public void createLifeCycleTasks(ModelMap<Task> tasks, TaskManager taskManager) {
- taskManager.createTasksBeforeEvaluate(new TaskModelMapAdaptor(tasks));
- }
-
- @Mutate
- public void createAndroidTasks(
- ModelMap<Task> tasks,
- ModelMap<AndroidComponentSpec> androidSpecs,
- TaskManager taskManager,
- SdkHandler sdkHandler,
- Project project, AndroidComponentModelSourceSet androidSources) {
- // setup SDK repositories.
- for (final File file : sdkHandler.getSdkLoader().getRepositories()) {
- project.getRepositories().maven(new Action<MavenArtifactRepository>() {
- @Override
- public void execute(MavenArtifactRepository repo) {
- repo.setUrl(file.toURI());
- }
- });
- }
- // TODO: determine how to provide functionalities of variant API objects.
- }
-
- // TODO: Use @BinaryTasks after figuring how to configure non-binary specific tasks.
- @Mutate
- public void createBinaryTasks(
- final ModelMap<Task> tasks,
- BinaryContainer binaries,
- ModelMap<AndroidComponentSpec> specs,
- TaskManager taskManager) {
- final VariantManager variantManager =
- ((DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME)).getVariantManager();
- binaries.withType(AndroidBinary.class, new Action<AndroidBinary>() {
- @Override
- public void execute(AndroidBinary androidBinary) {
- DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary;
- variantManager.createTasksForVariantData(
- new TaskModelMapAdaptor(tasks),
- binary.getVariantData());
- }
- });
- }
-
- /**
- * Create tasks that must be created after other tasks for variants are created.
- */
- @Mutate
- public void createRemainingTasks(
- ModelMap<Task> tasks,
- TaskManager taskManager,
- ModelMap<AndroidComponentSpec> spec) {
- VariantManager variantManager =
- ((DefaultAndroidComponentSpec)spec.get(COMPONENT_NAME)).getVariantManager();
-
- // create the test tasks.
- taskManager.createTopLevelTestTasks(new TaskModelMapAdaptor(tasks),
- !variantManager.getProductFlavors().isEmpty());
- }
-
- @Mutate
- public void createReportTasks(
- ModelMap<Task> tasks,
- ModelMap<AndroidComponentSpec> specs) {
- final VariantManager variantManager =
- ((DefaultAndroidComponentSpec)specs.get(COMPONENT_NAME)).getVariantManager();
-
- tasks.create("androidDependencies", DependencyReportTask.class,
- new Action<DependencyReportTask>() {
- @Override
- public void execute(DependencyReportTask dependencyReportTask) {
- dependencyReportTask.setDescription(
- "Displays the Android dependencies of the project");
- dependencyReportTask.setVariants(variantManager.getVariantDataList());
- dependencyReportTask.setGroup("Android");
- }
- });
-
- tasks.create("signingReport", SigningReportTask.class,
- new Action<SigningReportTask>() {
- @Override
- public void execute(SigningReportTask signingReportTask) {
- signingReportTask
- .setDescription("Displays the signing info for each variant");
- signingReportTask.setVariants(variantManager.getVariantDataList());
- signingReportTask.setGroup("Android");
-
- }
- });
- }
-
- @Mutate
- public void modifyAssembleTaskDescription(@Path("tasks.assemble") Task assembleTask) {
- assembleTask.setDescription(
- "Assembles all variants of all applications and secondary packages.");
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ComponentModelBuilder.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ComponentModelBuilder.java
deleted file mode 100644
index 81256fd..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ComponentModelBuilder.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import static com.android.build.gradle.model.ModelConstants.ANDROID_BUILDER;
-import static com.android.build.gradle.model.ModelConstants.ANDROID_CONFIG_ADAPTOR;
-import static com.android.build.gradle.model.ModelConstants.BINARIES;
-import static com.android.build.gradle.model.ModelConstants.COMPONENTS;
-import static com.android.build.gradle.model.ModelConstants.EXTRA_MODEL_INFO;
-import static com.android.build.gradle.model.ModelConstants.IS_APPLICATION;
-import static com.android.build.gradle.model.ModelConstants.NDK_HANDLER;
-import static com.android.build.gradle.model.ModelConstants.TASK_MANAGER;
-
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.VariantManager;
-import com.android.build.gradle.internal.model.ModelBuilder;
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.model.AndroidProject;
-
-import org.gradle.api.Project;
-import org.gradle.model.internal.core.ModelPath;
-import org.gradle.model.internal.registry.ModelRegistry;
-import org.gradle.model.internal.type.ModelType;
-import org.gradle.platform.base.BinaryContainer;
-import org.gradle.platform.base.ComponentSpecContainer;
-import org.gradle.tooling.provider.model.ToolingModelBuilder;
-
-/**
- * A ToolingModelBuilder for creating AndroidProject in the component model plugin.
- *
- * It retrieves models from ModelRegistry and uses ModelBuilder to create AndroidProject.
- *
- * This model builder uses Gradle's internal API as a public API for create tooling model from
- * component model is not yet available.
- */
-public class ComponentModelBuilder implements ToolingModelBuilder {
-
- ModelBuilder modelBuilder;
- ModelRegistry registry;
-
-
- public ComponentModelBuilder(ModelRegistry registry) {
- this.registry = registry;
- }
-
- @Override
- public boolean canBuild(String modelName) {
- return modelName.equals(AndroidProject.class.getName());
- }
-
- @Override
- public Object buildAll(String modelName, Project project) {
- if (modelBuilder == null) {
- modelBuilder = createModelBuilder();
- }
- return modelBuilder.buildAll(modelName, project);
- }
-
- private ModelBuilder createModelBuilder() {
- AndroidBuilder androidBuilder = registry.realize(
- new ModelPath(ANDROID_BUILDER),
- ModelType.of(AndroidBuilder.class));
- DefaultAndroidComponentSpec componentSpec = (DefaultAndroidComponentSpec) registry.realize(
- new ModelPath(COMPONENTS),
- ModelType.of(ComponentSpecContainer.class))
- .get(AndroidComponentModelPlugin.COMPONENT_NAME);
- VariantManager variantManager = componentSpec.getVariantManager();
- TaskManager taskManager = registry.realize(
- new ModelPath(TASK_MANAGER),
- ModelType.of(TaskManager.class));
- AndroidConfig extension = registry.realize(
- new ModelPath(ANDROID_CONFIG_ADAPTOR),
- ModelType.of(AndroidConfig.class));
- ExtraModelInfo extraModelInfo = registry.realize(
- new ModelPath(EXTRA_MODEL_INFO),
- ModelType.of(ExtraModelInfo.class));
- Boolean isApplication = registry.realize(
- new ModelPath(IS_APPLICATION),
- ModelType.of(Boolean.class));
- NdkHandler ndkHandler = registry.realize(
- new ModelPath(NDK_HANDLER),
- ModelType.of(NdkHandler.class));
- BinaryContainer binaries = registry.realize(
- new ModelPath(BINARIES),
- ModelType.of(BinaryContainer.class));
-
- return new ModelBuilder(
- androidBuilder, variantManager, taskManager,
- extension, extraModelInfo, ndkHandler,
- new ComponentNativeLibraryFactory(binaries, ndkHandler),
- !isApplication);
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ComponentNativeLibraryFactory.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ComponentNativeLibraryFactory.java
deleted file mode 100644
index 5497b62..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ComponentNativeLibraryFactory.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.model.NativeLibraryFactory;
-import com.android.build.gradle.internal.model.NativeLibraryImpl;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.ndk.internal.BinaryToolHelper;
-import com.android.builder.model.NativeLibrary;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
-import org.gradle.nativeplatform.NativeLibraryBinarySpec;
-import org.gradle.platform.base.BinaryContainer;
-
-import java.io.File;
-import java.util.Collections;
-
-/**
- * Implementation of NativeLibraryFactory from in the component model plugin.
- *
- * The library extract information directly from the binaries.
- */
-public class ComponentNativeLibraryFactory implements NativeLibraryFactory {
-
- BinaryContainer binaries;
-
- NdkHandler ndkHandler;
-
- public ComponentNativeLibraryFactory(BinaryContainer binaries,
- NdkHandler ndkHandler) {
- this.binaries = binaries;
- this.ndkHandler = ndkHandler;
- }
-
- @NonNull
- @Override
- public Optional<NativeLibrary> create(
- @NonNull VariantScope scope,
- @NonNull String toolchainName,
- @NonNull final Abi abi) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
-
- DefaultAndroidBinary androidBinary =
- (DefaultAndroidBinary) binaries.findByName(variantData.getName());
-
- if (androidBinary == null) {
- // Binaries are not created for test variants.
- return Optional.absent();
- }
-
- @SuppressWarnings("ConstantConditions")
- Optional<NativeLibraryBinarySpec> nativeBinary =
- Iterables.tryFind(androidBinary.getNativeBinaries(),
- new Predicate<NativeLibraryBinarySpec>() {
- @Override
- public boolean apply(NativeLibraryBinarySpec binary) {
- return binary.getTargetPlatform().getName().equals(abi.getName());
- }
- });
-
- if (!nativeBinary.isPresent()) {
- // We don't have native binaries.
- return Optional.absent();
- }
-
- CoreNdkOptions ndkConfig = variantData.getVariantConfiguration().getNdkConfig();
- // The DSL currently do not support all options available in the model such as the
- // include dirs and the defines. Therefore, just pass an empty collection for now.
- return Optional.<NativeLibrary>of(new NativeLibraryImpl(
- ndkConfig.getModuleName(),
- toolchainName,
- abi.getName(),
- Collections.<File>emptyList(), /*cIncludeDirs*/
- Collections.<File>emptyList(), /*cppIncludeDirs*/
- Collections.<File>emptyList(), /*cSystemIncludeDirs*/
- ndkHandler.getStlIncludes(ndkConfig.getStl(), abi),
- Collections.<String>emptyList(), /*cDefines*/
- Collections.<String>emptyList(), /*cppDefines*/
- BinaryToolHelper.getCCompiler(nativeBinary.get()).getArgs(),
- BinaryToolHelper.getCppCompiler(nativeBinary.get()).getArgs(),
- ImmutableList.of(variantData.getScope().getNdkDebuggableLibraryFolders(abi))));
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/DefaultAndroidBinary.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/DefaultAndroidBinary.java
deleted file mode 100644
index 5f61693..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/DefaultAndroidBinary.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import com.android.build.gradle.internal.NdkOptionsHelper;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.managed.BuildType;
-import com.android.build.gradle.managed.NdkConfig;
-import com.android.build.gradle.managed.NdkOptions;
-import com.android.build.gradle.managed.ProductFlavor;
-import com.google.common.collect.Lists;
-
-import org.gradle.nativeplatform.NativeLibraryBinarySpec;
-import org.gradle.platform.base.binary.BaseBinarySpec;
-
-import java.util.List;
-
-/**
- * Binary for Android.
- */
-public class DefaultAndroidBinary extends BaseBinarySpec implements AndroidBinary {
-
- private BuildType buildType;
-
- private List<ProductFlavor> productFlavors;
-
- private NdkConfig mergedNdkConfig = new NdkConfigImpl();
-
- private BaseVariantData variantData;
-
- private List<NativeLibraryBinarySpec> nativeBinaries = Lists.newArrayList();
-
- private List<String> targetAbi = Lists.newArrayList();
-
- @Override
- public BuildType getBuildType() {
- return buildType;
- }
-
- public void setBuildType(BuildType buildType) {
- this.buildType = buildType;
- }
-
- @Override
- public List<ProductFlavor> getProductFlavors() {
- return productFlavors;
- }
-
- public void setProductFlavors(List<ProductFlavor> productFlavors) {
- this.productFlavors = productFlavors;
- }
-
- public NdkConfig getMergedNdkConfig() {
- return mergedNdkConfig;
- }
-
- public BaseVariantData getVariantData() {
- return variantData;
- }
-
- public void setVariantData(BaseVariantData variantData) {
- this.variantData = variantData;
- }
-
- public List<NativeLibraryBinarySpec> getNativeBinaries() {
- return nativeBinaries;
- }
-
- public List<String> getTargetAbi() {
- return targetAbi;
- }
-
- public void computeMergedNdk(
- NdkConfig ndkConfig,
- List<com.android.build.gradle.managed.ProductFlavor> flavors,
- com.android.build.gradle.managed.BuildType buildType) {
-
-
- if (ndkConfig != null) {
- NdkOptionsHelper.merge(mergedNdkConfig, ndkConfig);
- }
-
- for (int i = flavors.size() - 1 ; i >= 0 ; i--) {
- NdkOptions ndkOptions = flavors.get(i).getNdk();
- if (ndkOptions != null) {
- NdkOptionsHelper.merge(mergedNdkConfig, ndkOptions);
- }
- }
-
- if (buildType.getNdk() != null) {
- NdkOptionsHelper.merge(mergedNdkConfig, buildType.getNdk());
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/DefaultAndroidComponentSpec.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/DefaultAndroidComponentSpec.java
deleted file mode 100644
index a9c198f..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/DefaultAndroidComponentSpec.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import com.android.build.gradle.internal.VariantManager;
-import com.android.build.gradle.managed.AndroidConfig;
-import com.android.builder.model.SigningConfig;
-
-import org.gradle.nativeplatform.NativeLibrarySpec;
-import org.gradle.platform.base.component.BaseComponentSpec;
-
-/**
- * Implementation for Android component spec.
- */
-public class DefaultAndroidComponentSpec extends BaseComponentSpec implements AndroidComponentSpec{
- AndroidConfig extension;
-
- VariantManager variantManager;
-
- SigningConfig signingOverride;
-
- NativeLibrarySpec nativeLibrary;
-
- @Override
- public AndroidConfig getExtension() {
- return extension;
- }
-
- public void setExtension(AndroidConfig extension) {
- this.extension = extension;
- }
-
- public VariantManager getVariantManager() {
-
- return variantManager;
- }
-
- public void setVariantManager(VariantManager variantManager) {
- this.variantManager = variantManager;
- }
-
- public SigningConfig getSigningOverride() {
- return signingOverride;
- }
-
- public void setSigningOverride(SigningConfig signingOverride) {
- this.signingOverride = signingOverride;
- }
-
- public NativeLibrarySpec getNativeLibrary() {
- return nativeLibrary;
- }
-
- public void setNativeLibrary(NativeLibrarySpec nativeLibrary) {
- this.nativeLibrary = nativeLibrary;
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/LibraryComponentModelPlugin.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/LibraryComponentModelPlugin.java
deleted file mode 100644
index af05716..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/LibraryComponentModelPlugin.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import static com.android.build.gradle.model.ModelConstants.IS_APPLICATION;
-import static com.android.build.gradle.model.ModelConstants.TASK_MANAGER;
-
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.DependencyManager;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.variant.LibraryVariantFactory;
-import com.android.build.gradle.internal.variant.VariantFactory;
-import com.android.builder.core.AndroidBuilder;
-
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.model.Model;
-import org.gradle.model.RuleSource;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-/**
- * Gradle component model plugin class for 'application' projects.
- */
-public class LibraryComponentModelPlugin implements Plugin<Project> {
- @Override
- public void apply(Project project) {
- project.getPluginManager().apply(BaseComponentModelPlugin.class);
- project.getTasks().create("assembleDefault");
- project.getPluginManager().apply(AndroidComponentModelTestPlugin.class);
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- public static class Rules extends RuleSource {
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- @Model(IS_APPLICATION)
- public Boolean isApplication() {
- return false;
- }
-
- @Model(TASK_MANAGER)
- public TaskManager createTaskManager(
- AndroidConfig androidExtension,
- Project project,
- AndroidBuilder androidBuilder,
- SdkHandler sdkHandler,
- ExtraModelInfo extraModelInfo,
- ToolingModelBuilderRegistry toolingRegistry) {
- DependencyManager dependencyManager = new DependencyManager(project, extraModelInfo);
-
- return new LibraryComponentTaskManager(
- project,
- androidBuilder,
- androidExtension,
- sdkHandler,
- dependencyManager,
- toolingRegistry);
- }
-
- @Model
- public VariantFactory createVariantFactory(
- ServiceRegistry serviceRegistry,
- AndroidBuilder androidBuilder,
- AndroidConfig extension) {
- Instantiator instantiator = serviceRegistry.get(Instantiator.class);
- return new LibraryVariantFactory(instantiator, androidBuilder, extension);
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/LibraryComponentTaskManager.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/LibraryComponentTaskManager.java
deleted file mode 100644
index f1b79ec..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/LibraryComponentTaskManager.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.AndroidConfig;
-import com.android.build.gradle.internal.DependencyManager;
-import com.android.build.gradle.internal.LibraryTaskManager;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.builder.core.AndroidBuilder;
-import com.google.common.collect.ImmutableList;
-
-import org.gradle.api.Project;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.util.Collection;
-
-/**
- * TaskManager for creating tasks in an Android library project with component model plugin.
- */
-public class LibraryComponentTaskManager extends LibraryTaskManager {
-
- public LibraryComponentTaskManager(
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- super(project, androidBuilder, extension, sdkHandler, dependencyManager, toolingRegistry);
- isNdkTaskNeeded = false;
- }
-
- @Override
- protected Collection<Object> getNdkBuildable(BaseVariantData variantData) {
- NdkComponentModelPlugin plugin = project.getPlugins().getPlugin(NdkComponentModelPlugin.class);
- return ImmutableList.<Object>copyOf(plugin.getBinaries(variantData.getVariantConfiguration()));
- }
-
- @Override
- public void configureScopeForNdk(@NonNull VariantScope scope) {
- NdkComponentModelPlugin.configureScopeForNdk(scope);
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ModelConstants.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ModelConstants.java
deleted file mode 100644
index 039c3c6..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/ModelConstants.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-/**
- * Component model path names.
- */
-public class ModelConstants {
-
- public static final String ANDROID_BUILDER = "androidBuilder";
-
- public static final String ANDROID_CONFIG_ADAPTOR = "androidConfigAdaptor";
-
- public static final String BINARIES = "binaries";
-
- public static final String COMPONENTS = "components";
-
- public static final String EXTRA_MODEL_INFO = "extraModelInfo";
-
- public static final String IS_APPLICATION = "isApplication";
-
- public static final String NDK_HANDLER = "ndkHandler";
-
- public static final String TASK_MANAGER = "taskManager";
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/NdkComponentModelPlugin.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/NdkComponentModelPlugin.java
deleted file mode 100644
index a727529..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/NdkComponentModelPlugin.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
-
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.NdkOptionsHelper;
-import com.android.build.gradle.internal.ProductFlavorCombo;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.managed.BuildType;
-import com.android.build.gradle.managed.NdkConfig;
-import com.android.build.gradle.managed.ProductFlavor;
-import com.android.build.gradle.ndk.internal.NdkConfiguration;
-import com.android.build.gradle.ndk.internal.NdkExtensionConvention;
-import com.android.build.gradle.ndk.internal.NdkNamingScheme;
-import com.android.build.gradle.ndk.internal.ToolchainConfiguration;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.core.VariantConfiguration;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-
-import org.gradle.api.Action;
-import org.gradle.api.BuildableModelElement;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.internal.project.ProjectIdentifier;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.TaskContainer;
-import org.gradle.language.c.plugins.CPlugin;
-import org.gradle.language.cpp.plugins.CppPlugin;
-import org.gradle.model.Defaults;
-import org.gradle.model.Finalize;
-import org.gradle.model.Model;
-import org.gradle.model.ModelMap;
-import org.gradle.model.Mutate;
-import org.gradle.model.Path;
-import org.gradle.model.RuleSource;
-import org.gradle.model.Validate;
-import org.gradle.nativeplatform.BuildTypeContainer;
-import org.gradle.nativeplatform.FlavorContainer;
-import org.gradle.nativeplatform.NativeBinarySpec;
-import org.gradle.nativeplatform.NativeLibraryBinarySpec;
-import org.gradle.nativeplatform.NativeLibrarySpec;
-import org.gradle.nativeplatform.SharedLibraryBinarySpec;
-import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
-import org.gradle.platform.base.BinaryContainer;
-import org.gradle.platform.base.BinarySpec;
-import org.gradle.platform.base.ComponentSpecContainer;
-import org.gradle.platform.base.PlatformContainer;
-import org.gradle.platform.base.binary.BaseBinarySpec;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Plugin for Android NDK applications.
- */
-public class NdkComponentModelPlugin implements Plugin<Project> {
- private Project project;
-
- @Override
- public void apply(Project project) {
- this.project = project;
-
- project.getPluginManager().apply(AndroidComponentModelPlugin.class);
- project.getPluginManager().apply(CPlugin.class);
- project.getPluginManager().apply(CppPlugin.class);
- }
-
- @SuppressWarnings({"MethodMayBeStatic", "unused"})
- public static class Rules extends RuleSource {
-
- @Mutate
- public void initializeNdkConfig(@Path("android.ndk") NdkConfig ndk) {
- NdkOptionsHelper.init(ndk);
- ndk.setModuleName("");
- ndk.setToolchain("");
- ndk.setToolchainVersion("");
- ndk.setStl("");
- ndk.setRenderscriptNdkMode(false);
- }
-
- @Finalize
- public void setDefaultNdkExtensionValue(@Path("android.ndk") NdkConfig ndkConfig) {
- NdkExtensionConvention.setExtensionDefault(ndkConfig);
- }
-
- @Validate
- public void checkNdkDir(NdkHandler ndkHandler, @Path("android.ndk") NdkConfig ndkConfig) {
- if (!ndkConfig.getModuleName().isEmpty() && !ndkHandler.isNdkDirConfigured()) {
- throw new InvalidUserDataException(
- "NDK location not found. Define location with ndk.dir in the "
- + "local.properties file or with an ANDROID_NDK_HOME environment "
- + "variable.");
- }
- if (ndkHandler.isNdkDirConfigured()) {
- if (!ndkHandler.getNdkDirectory().exists()) {
- throw new InvalidUserDataException(
- "Specified NDK location does not exists. Please ensure ndk.dir in "
- + "local.properties file or ANDROID_NDK_HOME is configured "
- + "correctly.");
-
- }
- }
- }
-
- @Mutate
- public void addDefaultNativeSourceSet(
- @Path("android.sources") AndroidComponentModelSourceSet sources) {
- sources.addDefaultSourceSet("jni", AndroidLanguageSourceSet.class);
- }
-
- @Model(ModelConstants.NDK_HANDLER)
- public NdkHandler ndkHandler(
- ProjectIdentifier projectId,
- @Path("android.compileSdkVersion") String compileSdkVersion,
- @Path("android.ndk") NdkConfig ndkConfig) {
- while (projectId.getParentIdentifier() != null) {
- projectId = projectId.getParentIdentifier();
- }
-
- return new NdkHandler(projectId.getProjectDir(), compileSdkVersion,
- ndkConfig.getToolchain(), ndkConfig.getToolchainVersion());
- }
-
- @Defaults
- public void initBuildTypeNdk(@Path("android.buildTypes") ModelMap<BuildType> buildTypes) {
- buildTypes.beforeEach(new Action<BuildType>() {
- @Override
- public void execute(BuildType buildType) {
- NdkOptionsHelper.init(buildType.getNdk());
- }
- });
-
- buildTypes.named(
- BuilderConstants.DEBUG,
- new Action<BuildType>() {
- @Override
- public void execute(BuildType buildType) {
- if (buildType.getNdk().getDebuggable() == null) {
- buildType.getNdk().setDebuggable(true);
- }
- }
- });
- }
-
- @Defaults
- public void initProductFlavorNdk(
- @Path("android.productFlavors") ModelMap<ProductFlavor> productFlavors) {
- productFlavors.beforeEach(new Action<ProductFlavor>() {
- @Override
- public void execute(ProductFlavor productFlavor) {
- NdkOptionsHelper.init(productFlavor.getNdk());
- }
- });
- }
-
- @Mutate
- public void createAndroidPlatforms(PlatformContainer platforms, NdkHandler ndkHandler) {
- if (!ndkHandler.isNdkDirConfigured()) {
- return;
- }
- // Create android platforms.
- ToolchainConfiguration.configurePlatforms(platforms, ndkHandler);
- }
-
- @Mutate
- public void createToolchains(
- NativeToolChainRegistry toolchainRegistry,
- @Path("android.ndk") NdkConfig ndkConfig,
- NdkHandler ndkHandler) {
- if (!ndkHandler.isNdkDirConfigured()) {
- return;
- }
- // Create toolchain for each ABI.
- ToolchainConfiguration.configureToolchain(
- toolchainRegistry,
- ndkConfig.getToolchain(),
- ndkHandler);
- }
-
- @Mutate
- public void createNativeBuildTypes(BuildTypeContainer nativeBuildTypes,
- @Path("android.buildTypes") ModelMap<BuildType> androidBuildTypes) {
- for (BuildType buildType : androidBuildTypes.values()) {
- nativeBuildTypes.maybeCreate(buildType.getName());
- }
- }
-
- @Mutate
- public void createNativeFlavors(FlavorContainer nativeFlavors,
- List<ProductFlavorCombo<ProductFlavor>> androidFlavorGroups) {
- if (androidFlavorGroups.isEmpty()) {
- // Create empty native flavor to override Gradle's default name.
- nativeFlavors.maybeCreate("");
- } else {
- for (ProductFlavorCombo group : androidFlavorGroups) {
- nativeFlavors.maybeCreate(group.getName());
- }
- }
- }
-
- @Mutate
- public void createNativeLibrary(
- final ComponentSpecContainer specs,
- @Path("android.ndk") final NdkConfig ndkConfig,
- final NdkHandler ndkHandler,
- @Path("android.sources") final AndroidComponentModelSourceSet sources,
- @Path("buildDir") final File buildDir) {
- if (!ndkHandler.isNdkDirConfigured()) {
- return;
- }
- if (!ndkConfig.getModuleName().isEmpty()) {
- specs.create(
- ndkConfig.getModuleName(),
- NativeLibrarySpec.class,
- new Action<NativeLibrarySpec>() {
- @Override
- public void execute(final NativeLibrarySpec nativeLib) {
- ((DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME))
- .setNativeLibrary(nativeLib);
- NdkConfiguration.configureProperties(
- nativeLib,
- sources,
- buildDir,
- ndkHandler);
- }
- });
- DefaultAndroidComponentSpec androidSpecs =
- (DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME);
- androidSpecs.setNativeLibrary(
- (NativeLibrarySpec) specs.get(ndkConfig.getModuleName()));
- }
- }
-
- @Mutate
- public void createAdditionalTasksForNatives(
- final ModelMap<Task> tasks,
- ModelMap<AndroidComponentSpec> specs,
- @Path("android.ndk") final NdkConfig ndkConfig,
- final NdkHandler ndkHandler,
- BinaryContainer binaries,
- @Path("buildDir") final File buildDir) {
- if (!ndkHandler.isNdkDirConfigured()) {
- return;
- }
- final DefaultAndroidComponentSpec androidSpec =
- (DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME);
- if (androidSpec.getNativeLibrary() != null) {
- binaries.withType(DefaultAndroidBinary.class, new Action<DefaultAndroidBinary>() {
- @Override
- public void execute(DefaultAndroidBinary binary) {
- for (NativeBinarySpec nativeBinary : binary.getNativeBinaries()) {
- NdkConfiguration.createTasks(
- tasks,
- (SharedLibraryBinarySpec) nativeBinary,
- buildDir,
- binary.getMergedNdkConfig(),
- ndkHandler);
- }
- }
- });
- }
- }
-
- @Mutate
- public void configureNativeBinary(
- BinaryContainer binaries,
- ComponentSpecContainer specs,
- @Path("android.ndk") final NdkConfig ndkConfig,
- @Path("buildDir") final File buildDir,
- final NdkHandler ndkHandler) {
- if (!ndkConfig.getModuleName().isEmpty()) {
- final NativeLibrarySpec library = specs.withType(NativeLibrarySpec.class)
- .get(ndkConfig.getModuleName());
- binaries.withType(
- DefaultAndroidBinary.class,
- new Action<DefaultAndroidBinary>() {
- @Override
- public void execute(DefaultAndroidBinary binary) {
- binary.computeMergedNdk(
- ndkConfig,
- binary.getProductFlavors(),
- binary.getBuildType());
-
- Collection<SharedLibraryBinarySpec> nativeBinaries =
- getNativeBinaries(
- library,
- binary.getBuildType(),
- binary.getProductFlavors());
- for (SharedLibraryBinarySpec nativeBin : nativeBinaries) {
- if (binary.getMergedNdkConfig().getAbiFilters().isEmpty() ||
- binary.getMergedNdkConfig().getAbiFilters().contains(
- nativeBin.getTargetPlatform().getName())) {
- NdkConfiguration.configureBinary(
- nativeBin,
- buildDir,
- binary.getMergedNdkConfig(),
- ndkHandler);
- binary.getNativeBinaries().add(nativeBin);
- }
- }
- }
- });
- }
- }
-
- @Finalize
- public void attachNativeTasksToAndroidBinary(ModelMap<AndroidBinary> binaries) {
- binaries.afterEach(new Action<AndroidBinary>() {
- @Override
- public void execute(AndroidBinary androidBinary) {
- DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary;
- for (NativeLibraryBinarySpec nativeBinary : binary.getNativeBinaries()) {
- if (binary.getTargetAbi().isEmpty() || binary.getTargetAbi().contains(
- nativeBinary.getTargetPlatform().getName())) {
- binary.getBuildTask().dependsOn(NdkNamingScheme.getNdkBuildTaskName(nativeBinary));
- }
- }
- }
- });
- }
-
- @Mutate
- public void removeNativeBinaryFromAssembleTask(ModelMap<AndroidComponentSpec> components) {
- // Setting each native binary to not buildable to prevent the native tasks to be
- // automatically added to the "assemble" task.
- components.afterEach(new Action<AndroidComponentSpec>() {
- @Override
- public void execute(AndroidComponentSpec spec) {
- NativeLibrarySpec nativeLibrary =
- ((DefaultAndroidComponentSpec)spec).getNativeLibrary();
- if (nativeLibrary != null) {
- nativeLibrary.getBinaries().afterEach(
- new Action<BinarySpec>() {
- @Override
- public void execute(BinarySpec binary) {
- ((BaseBinarySpec) binary).setBuildable(false);
- }
- });
- }
- }
- });
- }
-
- /**
- * Remove unintended tasks created by Gradle native plugin from task list.
- *
- * Gradle native plugins creates static library tasks automatically. This method removes
- * them to avoid cluttering the task list.
- */
- @Mutate
- public void hideNativeTasks(TaskContainer tasks, BinaryContainer binaries) {
- // Gradle do not support a way to remove created tasks. The best workaround is to clear the
- // group of the task and have another task depends on it. Therefore, we have to create
- // a dummy task to depend on all the tasks that we do not want to show up on the task
- // list. The dummy task dependsOn itself, effectively making it non-executable and
- // invisible unless the --all option is use.
- final Task nonExecutableTask = tasks.create("nonExecutableTask");
- nonExecutableTask.dependsOn(nonExecutableTask);
- nonExecutableTask
- .setDescription("Dummy task to hide other unwanted tasks in the task list.");
-
- binaries.withType(NativeLibraryBinarySpec.class, new Action<NativeLibraryBinarySpec>() {
- @Override
- public void execute(NativeLibraryBinarySpec binary) {
- Task buildTask = binary.getBuildTask();
- nonExecutableTask.dependsOn(buildTask);
- buildTask.setGroup(null);
- }
- });
- }
- }
-
-
- public static void configureScopeForNdk(VariantScope scope) {
- VariantConfiguration config = scope.getVariantConfiguration();
- ImmutableSet.Builder<File> builder = ImmutableSet.builder();
- for (Abi abi : NdkHandler.getAbiList()) {
- scope.addNdkDebuggableLibraryFolders(
- abi,
- new File(
- scope.getGlobalScope().getBuildDir(),
- NdkNamingScheme.getDebugLibraryDirectoryName(
- config.getBuildType().getName(),
- config.getFlavorName(),
- abi.getName())));
-
- // Return the parent directory of the binaries' output.
- // If output directory is "/path/to/lib/platformName". We want to return
- // "/path/to/lib".
- builder.add(new File(
- scope.getGlobalScope().getBuildDir(),
- NdkNamingScheme.getOutputDirectoryName(
- config.getBuildType().getName(),
- config.getFlavorName(),
- abi.getName())).getParentFile());
- }
- scope.setNdkSoFolder(builder.build());
- }
-
-
- private static Collection<SharedLibraryBinarySpec> getNativeBinaries(
- NativeLibrarySpec library,
- final BuildType buildType,
- final List<ProductFlavor> productFlavors) {
- final ProductFlavorCombo<ProductFlavor> flavorGroup =
- new ProductFlavorCombo<ProductFlavor>(productFlavors);
- return ImmutableList.copyOf(Iterables.filter(
- library.getBinaries().withType(SharedLibraryBinarySpec.class).values(),
- new Predicate<SharedLibraryBinarySpec>() {
- @Override
- public boolean apply(SharedLibraryBinarySpec binary) {
- return binary.getBuildType().getName().equals(buildType.getName())
- && (binary.getFlavor().getName().equals(flavorGroup.getName())
- || (productFlavors.isEmpty()
- && binary.getFlavor().getName().equals("default")));
- }
- }));
- }
-
- /**
- * Return library binaries for a VariantConfiguration.
- */
- public Collection<? extends BuildableModelElement> getBinaries(final VariantConfiguration variantConfig) {
- if (variantConfig.getType().isForTesting()) {
- // Do not return binaries for test variants as test source set is not supported at the
- // moment.
- return Collections.emptyList();
- }
- BinaryContainer binaries = (BinaryContainer) project.getExtensions().getByName("binaries");
- return binaries.withType(AndroidBinary.class).matching(
- new Spec<AndroidBinary>() {
- @Override
- public boolean isSatisfiedBy(AndroidBinary binary) {
- return (binary.getName().equals(variantConfig.getFullName()));
- }
- }
- );
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/NdkConfigImpl.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/NdkConfigImpl.java
deleted file mode 100644
index 1d37a60..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/NdkConfigImpl.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.model;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.managed.NdkConfig;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Implementation of NdkConfig.
- * Used in AndroidBinary, which is currently not a Managed type.
- */
-public class NdkConfigImpl implements NdkConfig {
-
- String moduleName;
-
- String toolchain;
-
- String toolchainVersion;
-
- Set<String> abiFilters = Sets.newHashSet();
-
- List<String> cFlags = Lists.newArrayList();
-
- List<String> cppFlags = Lists.newArrayList();
-
- List<String> ldFlags = Lists.newArrayList();
-
- List<String> ldLibs = Lists.newArrayList();
-
- String stl;
-
- Boolean isDebuggable;
-
- Boolean renderscriptNdkMode;
-
- @Override
- public String getModuleName() {
- return moduleName;
- }
-
- @Override
- public void setModuleName(@NonNull String moduleName) {
- this.moduleName = moduleName;
- }
-
- @Override
- public String getToolchain() {
- return toolchain;
- }
-
- @Override
- public void setToolchain(@NonNull String toolchain) {
- this.toolchain = toolchain;
- }
-
- @Override
- public String getToolchainVersion() {
- return toolchainVersion;
- }
-
- @Override
- public void setToolchainVersion(@NonNull String toolchainVersion) {
- this.toolchainVersion = toolchainVersion;
- }
-
- @Override
- public Set<String> getAbiFilters() {
- return abiFilters;
- }
-
- @Override
- public void setAbiFilters(@NonNull Set<String> abiFilters) {
- throw new UnsupportedOperationException("Field should not be set.");
- }
-
- @Override
- public List<String> getCFlags() {
- return cFlags;
- }
-
- @Override
- public void setCFlags(@NonNull List<String> cFlags) {
- throw new UnsupportedOperationException("Field should not be set.");
- }
-
- @Override
- public List<String> getCppFlags() {
- return cppFlags;
- }
-
- @Override
- public void setCppFlags(@NonNull List<String> cppFlags) {
- throw new UnsupportedOperationException("Field should not be set.");
- }
-
- @Override
- public List<String> getLdFlags() {
- return ldFlags;
- }
-
- @Override
- public void setLdFlags(@NonNull List<String> ldFlags) {
- throw new UnsupportedOperationException("Field should not be set.");
- }
-
- @Override
- public List<String> getLdLibs() {
- return ldLibs;
- }
-
- @Override
- public void setLdLibs(@NonNull List<String> ldLibs) {
- throw new UnsupportedOperationException("Field should not be set.");
- }
-
- @Override
- public String getStl() {
- return stl;
- }
-
- @Override
- public void setStl(@NonNull String stl) {
- this.stl = stl;
- }
-
- @Override
- public Boolean getDebuggable() {
- return isDebuggable;
- }
-
- @Override
- public void setDebuggable(Boolean isDebuggable) {
- this.isDebuggable = isDebuggable;
- }
-
- @Override
- public Boolean getRenderscriptNdkMode() {
- return renderscriptNdkMode;
- }
-
- @Override
- public void setRenderscriptNdkMode(Boolean renderscriptNdkMode) {
- this.renderscriptNdkMode = renderscriptNdkMode;
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/AbstractNativeToolSpecification.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/AbstractNativeToolSpecification.java
deleted file mode 100644
index 47a037b..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/AbstractNativeToolSpecification.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import static com.android.build.gradle.ndk.internal.BinaryToolHelper.getCCompiler;
-import static com.android.build.gradle.ndk.internal.BinaryToolHelper.getCppCompiler;
-
-import org.gradle.nativeplatform.NativeBinarySpec;
-
-/**
- * An abstract class for NativeToolSpecification.
- */
-public abstract class AbstractNativeToolSpecification implements NativeToolSpecification {
- /**
- * Configure a native binary with this specification.
- *
- * @param binary The binary to be configured. It is assumed the 'c' and 'cpp' plugin is applied
- * such that the binary contains the cCompiler and cppCompiler extensions.
- */
- @Override
- public void apply(NativeBinarySpec binary) {
- for (String arg : getCFlags()) {
- getCCompiler(binary).args(arg);
- }
-
- for (String arg : getCppFlags()) {
- getCppCompiler(binary).args(arg);
- }
-
- for (String arg : getLdFlags()) {
- binary.getLinker().args(arg);
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/BinaryToolHelper.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/BinaryToolHelper.java
deleted file mode 100644
index 82aa470..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/BinaryToolHelper.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import org.gradle.api.plugins.ExtensionAware;
-import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
-import org.gradle.platform.base.BinarySpec;
-
-/**
- * Gradle's LanguageRegistration dynamically add the cCompiler and cppCompiler tool to the library.
- *
- * In Java, we can't call those functions dynamically, but can cast the binaries to ExternsionAware
- * and access those tools through the extension container.
- *
- * This helper class hide the details for accessing the tools.
- */
-public class BinaryToolHelper {
- public static DefaultPreprocessingTool getCCompiler(BinarySpec binary) {
- return (DefaultPreprocessingTool) ((ExtensionAware) binary).getExtensions().getByName("cCompiler");
- }
-
- public static DefaultPreprocessingTool getCppCompiler(BinarySpec binary) {
- return (DefaultPreprocessingTool) ((ExtensionAware) binary).getExtensions().getByName("cppCompiler");
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/ClangNativeToolSpecification.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/ClangNativeToolSpecification.java
deleted file mode 100644
index 6b7b1e5..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/ClangNativeToolSpecification.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import com.android.SdkConstants;
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.builder.core.BuilderConstants;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
-
-import org.gradle.nativeplatform.BuildType;
-import org.gradle.nativeplatform.platform.NativePlatform;
-
-import java.util.Map;
-
-/**
- * Flag configuration for Clang toolchain.
- */
-public class ClangNativeToolSpecification extends AbstractNativeToolSpecification {
-
- private NdkHandler ndkHandler;
-
- private NativePlatform platform;
-
- private boolean isDebugBuild;
-
- private static final Map<String, String> TARGET_TRIPLE = ImmutableMap.<String, String>builder()
- .put(SdkConstants.ABI_INTEL_ATOM, "i686-none-linux-android")
- .put(SdkConstants.ABI_INTEL_ATOM64, "x86_64-none-linux-android")
- .put(SdkConstants.ABI_ARMEABI, "armv5-none-linux-android")
- .put(SdkConstants.ABI_ARMEABI_V7A, "armv7-none-linux-android")
- .put(SdkConstants.ABI_ARM64_V8A, "aarch64-none-linux-android")
- .put(SdkConstants.ABI_MIPS, "mipsel-none-linux-android")
- .put(SdkConstants.ABI_MIPS64, "mips64el-none-linux-android")
- .build();
-
-
- private static final ListMultimap<String, String> RELEASE_CFLAGS =
- ImmutableListMultimap.<String, String>builder()
- .putAll(SdkConstants.ABI_ARMEABI, ImmutableList.of(
- "-fpic",
- "-ffunction-sections",
- "-funwind-tables",
- "-fstack-protector",
- "-no-canonical-prefixes",
- "-march=armv5te",
- "-mtune=xscale",
- "-msoft-float",
- "-mthumb",
- "-Os",
- "-DNDEBUG",
- "-fomit-frame-pointer",
- "-fstrict-aliasing"))
- .putAll(SdkConstants.ABI_ARMEABI_V7A, ImmutableList.of(
- "-fpic",
- "-ffunction-sections",
- "-funwind-tables",
- "-fstack-protector",
- "-no-canonical-prefixes",
- "-march=armv7-a",
- "-mfloat-abi=softfp",
- "-mfpu=vfpv3-d16",
- "-mthumb",
- "-Os",
- "-DNDEBUG",
- "-fomit-frame-pointer",
- "-fstrict-aliasing"))
- .putAll(SdkConstants.ABI_ARM64_V8A, ImmutableList.of(
- "-fpic",
- "-ffunction-sections",
- "-funwind-tables",
- "-fstack-protector",
- "-no-canonical-prefixes",
- "-O2",
- "-DNDEBUG",
- "-fomit-frame-pointer",
- "-fstrict-aliasing"))
- .putAll(SdkConstants.ABI_INTEL_ATOM, ImmutableList.of(
- "-ffunction-sections",
- "-funwind-tables",
- "-fstack-protector",
- "-fPIC",
- "-no-canonical-prefixes",
- "-O2",
- "-DNDEBUG",
- "-fomit-frame-pointer",
- "-fstrict-aliasing"))
- .putAll(SdkConstants.ABI_INTEL_ATOM64, ImmutableList.of(
- "-ffunction-sections",
- "-funwind-tables",
- "-fstack-protector",
- "-fPIC",
- "-no-canonical-prefixes",
- "-O2",
- "-DNDEBUG",
- "-fomit-frame-pointer",
- "-fstrict-aliasing"))
- .putAll(SdkConstants.ABI_MIPS, ImmutableList.of(
- "-fpic",
- "-fno-strict-aliasing",
- "-finline-functions",
- "-ffunction-sections",
- "-funwind-tables",
- "-fmessage-length=0",
- "-no-canonical-prefixes",
- "-O2",
- "-g",
- "-DNDEBUG",
- "-fomit-frame-pointer"))
- .putAll(SdkConstants.ABI_MIPS64, ImmutableList.of(
- "-fpic",
- "-fno-strict-aliasing",
- "-finline-functions",
- "-ffunction-sections",
- "-funwind-tables",
- "-fmessage-length=0",
- "-no-canonical-prefixes",
- "-O2",
- "-g",
- "-DNDEBUG",
- "-fomit-frame-pointer"))
- .build();
-
- private static final ListMultimap<String, String> DEBUG_CFLAGS =
- ImmutableListMultimap.<String, String>builder()
- .putAll(SdkConstants.ABI_ARMEABI, ImmutableList.of(
- "-O0",
- "-UNDEBUG",
- "-marm",
- "-fno-strict-aliasing",
- "-fno-limit-debug-info"))
- .putAll(SdkConstants.ABI_ARMEABI_V7A, ImmutableList.of(
- "-O0",
- "-UNDEBUG",
- "-marm",
- "-fno-strict-aliasing",
- "-fno-limit-debug-info"))
- .putAll(SdkConstants.ABI_ARM64_V8A, ImmutableList.of(
- "-O0",
- "-UNDEBUG",
- "-fno-omit-frame-pointer",
- "-fno-strict-aliasing",
- "-fno-limit-debug-info"))
- .putAll(SdkConstants.ABI_INTEL_ATOM, ImmutableList.of(
- "-O0",
- "-UNDEBUG",
- "-fno-omit-frame-pointer",
- "-fno-strict-aliasing",
- "-fno-limit-debug-info"))
- .putAll(SdkConstants.ABI_INTEL_ATOM64, ImmutableList.of(
- "-O0",
- "-UNDEBUG",
- "-fno-omit-frame-pointer",
- "-fno-strict-aliasing",
- "-fno-limit-debug-info"))
- .putAll(SdkConstants.ABI_MIPS, ImmutableList.of(
- "-O0",
- "-UNDEBUG",
- "-fno-omit-frame-pointer",
- "-fno-limit-debug-info"))
- .putAll(SdkConstants.ABI_MIPS64, ImmutableList.of(
- "-O0",
- "-UNDEBUG",
- "-fno-omit-frame-pointer",
- "-fno-limit-debug-info"))
- .build();
-
- public ClangNativeToolSpecification(
- NdkHandler ndkHandler,
- NativePlatform platform,
- boolean isDebugBuild) {
- this.ndkHandler = ndkHandler;
- this.isDebugBuild = isDebugBuild;
- this.platform = platform;
- }
-
- @Override
- public Iterable<String> getCFlags() {
- return Iterables.concat(
- getTargetFlags(),
- RELEASE_CFLAGS.get(platform.getName()),
- isDebugBuild ? DEBUG_CFLAGS.get(platform.getName()) : ImmutableList.<String>of());
- }
-
- @Override
- public Iterable<String> getCppFlags() {
- return getCFlags();
- }
-
- @Override
- public Iterable<String> getLdFlags() {
- return Iterables.concat(
- getTargetFlags(),
- platform.getName().equals(SdkConstants.ABI_ARMEABI_V7A)
- ? ImmutableList.of("-Wl,--fix-cortex-a8")
- : ImmutableList.<String>of());
- }
-
- private Iterable<String> getTargetFlags() {
- return ImmutableList.of(
- "-gcc-toolchain",
- ndkHandler.getDefaultGccToolchainPath(Abi.getByName(platform.getName())).toString(),
- "-target",
- TARGET_TRIPLE.get(platform.getName()));
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkConfiguration.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkConfiguration.java
deleted file mode 100644
index 13d907c..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkConfiguration.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import static com.android.build.gradle.ndk.internal.BinaryToolHelper.getCCompiler;
-import static com.android.build.gradle.ndk.internal.BinaryToolHelper.getCppCompiler;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.managed.NdkConfig;
-import com.android.build.gradle.model.AndroidComponentModelSourceSet;
-import com.android.build.gradle.tasks.GdbSetupTask;
-import com.android.build.gradle.tasks.StripDebugSymbolTask;
-import com.android.utils.StringHelper;
-import com.google.common.base.Objects;
-
-import org.gradle.api.Action;
-import org.gradle.api.PolymorphicDomainObjectContainer;
-import org.gradle.api.Task;
-import org.gradle.api.tasks.Copy;
-import org.gradle.language.base.FunctionalSourceSet;
-import org.gradle.language.base.LanguageSourceSet;
-import org.gradle.language.c.CSourceSet;
-import org.gradle.language.c.tasks.CCompile;
-import org.gradle.language.cpp.CppSourceSet;
-import org.gradle.language.cpp.tasks.CppCompile;
-import org.gradle.model.ModelMap;
-import org.gradle.nativeplatform.NativeBinarySpec;
-import org.gradle.nativeplatform.NativeLibrarySpec;
-import org.gradle.nativeplatform.SharedLibraryBinarySpec;
-import org.gradle.platform.base.BinarySpec;
-
-import java.io.File;
-
-/**
- * Configure settings used by the native binaries.
- */
-public class NdkConfiguration {
-
- public static void configureProperties(
- NativeLibrarySpec library,
- final AndroidComponentModelSourceSet sources,
- final File buildDir,
- final NdkHandler ndkHandler) {
- for (Abi abi : ndkHandler.getSupportedAbis()) {
- library.targetPlatform(abi.getName());
- }
-
- library.getBinaries()
- .withType(SharedLibraryBinarySpec.class, new Action<SharedLibraryBinarySpec>() {
- @Override
- public void execute(final SharedLibraryBinarySpec binary) {
- sourceIfExist(binary, sources, "main");
- sourceIfExist(binary, sources, binary.getFlavor().getName());
- sourceIfExist(binary, sources, binary.getBuildType().getName());
- sourceIfExist(binary, sources,
- binary.getFlavor().getName()
- + StringHelper.capitalize(binary.getBuildType().getName()));
-
- getCCompiler(binary).define("ANDROID");
- getCppCompiler(binary).define("ANDROID");
- getCCompiler(binary).define("ANDROID_NDK");
- getCppCompiler(binary).define("ANDROID_NDK");
-
- // Replace output directory of compile tasks.
- binary.getTasks().withType(CCompile.class, new Action<CCompile>() {
- @Override
- public void execute(CCompile task) {
- String sourceSetName = task.getObjectFileDir().getName();
- task.setObjectFileDir(
- NdkNamingScheme.getObjectFilesOutputDirectory(
- binary,
- buildDir,
- sourceSetName));
- }
- });
- binary.getTasks().withType(CppCompile.class, new Action<CppCompile>() {
- @Override
- public void execute(CppCompile task) {
- String sourceSetName = task.getObjectFileDir().getName();
- task.setObjectFileDir(
- NdkNamingScheme.getObjectFilesOutputDirectory(
- binary,
- buildDir,
- sourceSetName));
- }
- });
-
- new DefaultNativeToolSpecification().apply(binary);
-
- String sysroot = ndkHandler.getSysroot(
- Abi.getByName(binary.getTargetPlatform().getName()));
-
- getCCompiler(binary).args("--sysroot=" + sysroot);
- getCppCompiler(binary).args("--sysroot=" + sysroot);
- binary.getLinker().args("--sysroot=" + sysroot);
- binary.getLinker().args("-Wl,--build-id");
-
- }
-
- });
- }
-
- /**
- * Configure native binary with variant specific options.
- */
- public static void configureBinary(
- SharedLibraryBinarySpec binary,
- final File buildDir,
- final NdkConfig ndkConfig,
- final NdkHandler ndkHandler) {
- // Set output library filename.
- binary.setSharedLibraryFile(
- new File(
- buildDir,
- NdkNamingScheme.getDebugLibraryDirectoryName(binary)
- + "/"
- + NdkNamingScheme.getSharedLibraryFileName(
- ndkConfig.getModuleName())));
-
- String sysroot = ndkHandler.getSysroot(
- Abi.getByName(binary.getTargetPlatform().getName()));
-
- if (ndkConfig.getRenderscriptNdkMode()) {
- getCCompiler(binary).args("-I" + sysroot + "/usr/include/rs");
- getCCompiler(binary).args("-I" + sysroot + "/usr/include/rs/cpp");
- getCppCompiler(binary).args("-I" + sysroot + "/usr/include/rs");
- getCppCompiler(binary).args("-I" + sysroot + "/usr/include/rs/cpp");
- binary.getLinker().args("-L" + sysroot + "/usr/lib/rs");
- }
-
- // STL flags must be applied before user defined flags to resolve possible undefined symbols
- // in the STL library.
- StlNativeToolSpecification stlConfig = new StlNativeToolSpecification(
- ndkHandler,
- ndkConfig.getStl(),
- binary.getTargetPlatform());
- stlConfig.apply(binary);
-
- NativeToolSpecificationFactory.create(
- ndkHandler,
- binary.getTargetPlatform(),
- Objects.firstNonNull(ndkConfig.getDebuggable(), false)).apply(
- binary);
-
- // Add flags defined in NdkConfig
- for (String flag : ndkConfig.getCFlags()) {
- getCCompiler(binary).args(flag.trim());
- }
-
- for (String flag : ndkConfig.getCppFlags()) {
- getCppCompiler(binary).args(flag.trim());
- }
-
- for (String flag : ndkConfig.getLdFlags()) {
- binary.getLinker().args(flag.trim());
- }
-
- for (String ldLib : ndkConfig.getLdLibs()) {
- binary.getLinker().args("-l" + ldLib.trim());
- }
- }
-
- public static void createTasks(
- @NonNull ModelMap<Task> tasks,
- @NonNull SharedLibraryBinarySpec binary,
- @NonNull File buildDir,
- @NonNull NdkConfig ndkConfig,
- @NonNull NdkHandler ndkHandler) {
- String compileNdkTaskName = NdkNamingScheme.getNdkBuildTaskName(binary);
- tasks.create(compileNdkTaskName);
-
- StlConfiguration.createStlCopyTask(tasks, binary, buildDir, ndkHandler,
- ndkConfig.getStl(), compileNdkTaskName);
-
- if (Boolean.TRUE.equals(ndkConfig.getDebuggable())) {
- // TODO: Use AndroidTaskRegistry and scopes to create tasks in experimental plugin.
- setupNdkGdbDebug(tasks, binary, buildDir, ndkConfig, ndkHandler, compileNdkTaskName);
- }
- createStripDebugTask(tasks, binary, buildDir, ndkHandler, compileNdkTaskName);
- }
-
- /**
- * Add the sourceSet with the specified name to the binary if such sourceSet is defined.
- */
- private static void sourceIfExist(
- BinarySpec binary,
- AndroidComponentModelSourceSet projectSourceSet,
- final String sourceSetName) {
- FunctionalSourceSet sourceSet = projectSourceSet.findByName(sourceSetName);
- if (sourceSet != null) {
- final LanguageSourceSet jni = sourceSet.getByName("jni");
- binary.sources(new Action<PolymorphicDomainObjectContainer<LanguageSourceSet>>() {
- @Override
- public void execute(
- PolymorphicDomainObjectContainer<LanguageSourceSet> languageSourceSets) {
- // Hardcode the acceptable extension until we find a suitable DSL for user to
- // modify.
- languageSourceSets.create(
- sourceSetName + "C",
- CSourceSet.class,
- new Action<LanguageSourceSet>() {
- @Override
- public void execute(LanguageSourceSet source) {
- source.getSource().setSrcDirs(jni.getSource().getSrcDirs());
- source.getSource().include("**/*.c");
- source.getSource().exclude(jni.getSource().getExcludes());
- }
- });
- languageSourceSets.create(
- sourceSetName + "Cpp",
- CppSourceSet.class,
- new Action<LanguageSourceSet>() {
- @Override
- public void execute(LanguageSourceSet source) {
- source.getSource().setSrcDirs(jni.getSource().getSrcDirs());
- source.getSource().include("**/*.C");
- source.getSource().include("**/*.CPP");
- source.getSource().include("**/*.c++");
- source.getSource().include("**/*.cc");
- source.getSource().include("**/*.cp");
- source.getSource().include("**/*.cpp");
- source.getSource().include("**/*.cxx");
- source.getSource().exclude(jni.getSource().getExcludes());
- }
- });
- }
- });
- }
- }
-
- /**
- * Setup tasks to create gdb.setup and copy gdbserver for NDK debugging.
- */
- private static void setupNdkGdbDebug(
- @NonNull ModelMap<Task> tasks,
- @NonNull final NativeBinarySpec binary,
- @NonNull final File buildDir,
- @NonNull final NdkConfig ndkConfig,
- @NonNull final NdkHandler handler,
- @NonNull String buildTaskName) {
- final String copyGdbServerTaskName = NdkNamingScheme.getTaskName(binary, "copy", "GdbServer");
- tasks.create(copyGdbServerTaskName, Copy.class, new Action<Copy>() {
- @Override
- public void execute(Copy task) {
- task.from(new File(handler.getPrebuiltDirectory(
- Abi.getByName(binary.getTargetPlatform().getName())),
- "gdbserver/gdbserver"));
- task.into(new File(buildDir, NdkNamingScheme.getOutputDirectoryName(binary)));
- }
- });
-
- final String createGdbSetupTaskName = NdkNamingScheme.getTaskName(binary, "create", "Gdbsetup");
- tasks.create(createGdbSetupTaskName, GdbSetupTask.class, new Action<GdbSetupTask>() {
- @Override
- public void execute(GdbSetupTask task) {
- task.setNdkHandler(handler);
- task.setExtension(ndkConfig);
- task.setBinary(binary);
- task.setOutputDir(
- new File(buildDir, NdkNamingScheme.getOutputDirectoryName(binary)));
- }
- });
-
- tasks.named(buildTaskName, new Action<Task>() {
- @Override
- public void execute(Task task) {
- task.dependsOn(copyGdbServerTaskName);
- task.dependsOn(createGdbSetupTaskName);
- }
- });
- }
-
- private static void createStripDebugTask(
- ModelMap<Task> tasks,
- final SharedLibraryBinarySpec binary,
- final File buildDir,
- final NdkHandler handler,
- String buildTaskName) {
-
- final String taskName = NdkNamingScheme.getTaskName(binary, "stripSymbols");
- tasks.create(
- taskName,
- StripDebugSymbolTask.class,
- new StripDebugSymbolTask.ConfigAction(binary, buildDir, handler));
- tasks.named(buildTaskName, new Action<Task>() {
- @Override
- public void execute(Task task) {
- task.dependsOn(taskName);
- }
- });
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkExtensionConvention.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkExtensionConvention.java
deleted file mode 100644
index 90b691f..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkExtensionConvention.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.build.gradle.ndk.internal;
-
-import com.android.build.gradle.internal.core.Toolchain;
-import com.android.build.gradle.managed.NdkConfig;
-
-import org.gradle.api.InvalidUserDataException;
-
-/**
- * Action to setup default values for NdkExtension.
- */
-public class NdkExtensionConvention {
-
- public static final String DEFAULT_STL = "system";
-
- /**
- * Validate the NdkExtension and provide default values.
- */
- public static void setExtensionDefault(NdkConfig ndkConfig) {
- if (ndkConfig.getToolchain().isEmpty()) {
- ndkConfig.setToolchain(Toolchain.getDefault().getName());
- } else {
- if (!ndkConfig.getToolchain().equals("gcc") &&
- !ndkConfig.getToolchain().equals("clang")) {
- throw new InvalidUserDataException(String.format(
- "Invalid toolchain '%s'. Supported toolchains are 'gcc' and 'clang'.",
- ndkConfig.getToolchain()));
- }
- }
-
- if (ndkConfig.getStl().isEmpty()) {
- ndkConfig.setStl(DEFAULT_STL);
- } else {
- StlConfiguration.checkStl(ndkConfig.getStl());
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkNamingScheme.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkNamingScheme.java
deleted file mode 100644
index 63127c5..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NdkNamingScheme.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.utils.StringHelper;
-import com.google.common.base.Joiner;
-
-import org.gradle.nativeplatform.NativeBinarySpec;
-
-import java.io.File;
-
-/**
- * Naming scheme for NdkPlugin's outputs.
- */
-public class NdkNamingScheme {
-
- public static File getObjectFilesOutputDirectory(
- NativeBinarySpec binary,
- File buildDir,
- String sourceSetName) {
- return new File(
- buildDir,
- String.format(
- "%s/objectFiles/%s/%s",
- FD_INTERMEDIATES ,
- binary.getName(),
- sourceSetName));
- }
-
- public static String getTaskName(NativeBinarySpec binary, @Nullable String verb) {
- return getTaskName(binary, verb, null);
- }
-
- public static String getTaskName(
- NativeBinarySpec binary,
- @Nullable String verb,
- @Nullable String target) {
- StringBuilder sb = new StringBuilder();
- appendCamelCase(sb, verb);
- appendCamelCase(sb, binary.getName());
- appendCamelCase(sb, target);
- return sb.toString();
- }
-
- private static void appendCamelCase(StringBuilder sb, @Nullable String word) {
- if (word != null) {
- if (sb.length() == 0) {
- sb.append(word);
- } else {
- sb.append(StringHelper.capitalize(word));
- }
- }
- }
-
- public static String getNdkBuildTaskName(@NonNull NativeBinarySpec binary) {
- return getTaskName(binary, "ndkBuild");
- }
-
- /**
- * Return the name of the directory that will contain the final output of the native binary.
- */
- public static String getOutputDirectoryName(String buildType, String productFlavor, String abi) {
- return Joiner.on(File.separator).join(
- FD_INTERMEDIATES,
- "binaries",
- buildType,
- productFlavor,
- "lib",
- abi);
- }
-
- public static String getOutputDirectoryName(NativeBinarySpec binary) {
- return getOutputDirectoryName(
- binary.getBuildType().getName(),
- binary.getFlavor().getName(),
- binary.getTargetPlatform().getName());
- }
-
- /**
- * Return the name of the directory that will contain the native library with debug symbols.
- */
- public static String getDebugLibraryDirectoryName(String buildType, String productFlavor, String abi) {
- return Joiner.on(File.separator).join(
- FD_INTERMEDIATES,
- "binaries",
- buildType,
- productFlavor,
- "obj",
- abi);
- }
-
- public static String getDebugLibraryDirectoryName(NativeBinarySpec binary) {
- return getDebugLibraryDirectoryName(
- binary.getBuildType().getName(),
- binary.getFlavor().getName(),
- binary.getTargetPlatform().getName());
- }
-
- public static String getSharedLibraryFileName(String moduleName) {
- return "lib" + moduleName + ".so";
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/StlConfiguration.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/StlConfiguration.java
deleted file mode 100644
index d9da1ab..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/StlConfiguration.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.NdkHandler;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ListMultimap;
-
-import org.gradle.api.Action;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Task;
-import org.gradle.api.tasks.Copy;
-import org.gradle.model.ModelMap;
-import org.gradle.nativeplatform.NativeBinarySpec;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Configuration to setup STL for NDK.
- */
-public class StlConfiguration {
-
- private static final String DEFAULT_STL = "system";
-
- private static final List<String> VALID_STL = ImmutableList.of(
- "system",
- "stlport_static",
- "stlport_shared",
- "gnustl_static",
- "gnustl_shared",
- "gabi++_static",
- "gabi++_shared",
- "c++_static",
- "c++_shared");
-
- private static final ListMultimap<String, String> STL_SOURCES =
- ImmutableListMultimap.<String, String>builder()
- .putAll("system", ImmutableList.of(
- "system/include"))
- .putAll("stlport", ImmutableList.of(
- "stlport/stlport",
- "gabi++/include"))
- .putAll("gnustl", ImmutableList.of(
- "gnu-libstdc++",
- "gnu-libstdc++/4.6/include",
- "gnu-libstdc++/4.6/libs/armeabi-v7a/include",
- "gnu-libstdc++/4.6/include/backward"))
- .putAll("gabi++", ImmutableList.of(
- "gabi++",
- "gabi++/include"))
- .putAll("c++", ImmutableList.of(
- "../android/support/include",
- "llvm-libc++",
- "../android/compiler-rt",
- "llvm-libc++/libcxx/include",
- "gabi++/include",
- "../android/support/include"))
- .build();
-
- public static File getStlBaseDirectory(NdkHandler ndkHandler) {
- return new File(ndkHandler.getNdkDirectory(), "sources/cxx-stl/");
- }
-
- public static Collection<String> getStlSources(NdkHandler ndkHandler, String stl) {
- final File stlBase = getStlBaseDirectory(ndkHandler);
- String stlName = stl.equals("system") ? "system" : stl.substring(0, stl.indexOf('_'));
-
- ImmutableList.Builder<String> builder = ImmutableList.builder();
- for (String sourceDir : STL_SOURCES.get(stlName)) {
- builder.add(stlBase.toString() + "/" + sourceDir);
- }
- return builder.build();
- }
-
- public static void checkStl(String stl) {
- if (!VALID_STL.contains(stl)) {
- throw new InvalidUserDataException("Invalid STL: " + stl);
- }
- }
-
- public static void createStlCopyTask(
- @NonNull ModelMap<Task> tasks,
- @NonNull final NativeBinarySpec binary,
- @NonNull final File buildDir,
- @NonNull NdkHandler ndkHandler,
- @NonNull String stl,
- @NonNull String buildTaskName) {
- if (stl.endsWith("_shared")) {
- final StlNativeToolSpecification stlConfig = new StlNativeToolSpecification(ndkHandler,
- stl, binary.getTargetPlatform());
-
- final String copyTaskName = NdkNamingScheme.getTaskName(binary, "copy", "StlSo");
- tasks.create(copyTaskName, Copy.class, new Action<Copy>() {
- @Override
- public void execute(Copy copy) {
- copy.from(stlConfig.getStlLib(binary.getTargetPlatform().getName()));
- copy.into(new File(buildDir, NdkNamingScheme.getOutputDirectoryName(binary)));
-
- }
- });
- tasks.named(buildTaskName, new Action<Task>() {
- @Override
- public void execute(Task task) {
- task.dependsOn(copyTaskName);
- }
- });
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/StlNativeToolSpecification.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/StlNativeToolSpecification.java
deleted file mode 100644
index 758b0e2..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/StlNativeToolSpecification.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.core.Abi;
-import com.google.common.collect.Lists;
-
-import org.gradle.nativeplatform.platform.NativePlatform;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Compiler flags to configure STL.
- */
-public class StlNativeToolSpecification extends AbstractNativeToolSpecification {
- private NdkHandler ndkHandler;
- private String stl;
- private String stlName;
- private Boolean isStatic;
- private NativePlatform platform;
-
- StlNativeToolSpecification(NdkHandler ndkHandler, String stl, NativePlatform platform) {
- this.ndkHandler = ndkHandler;
- this.stl = stl;
- this.stlName = stl.equals("system") ? stl : stl.substring(0, stl.indexOf('_'));
- this.isStatic = stl.endsWith("_static");
- this.platform = platform;
- }
-
-
- @Override
- public Iterable<String> getCFlags() {
- return Collections.emptyList();
- }
-
- @Override
- public Iterable<String> getCppFlags() {
-
- List<String> cppFlags = Lists.newArrayList();
-
- if (stlName.equals("c++")) {
- cppFlags.add("-std=c++11");
- }
-
- List<File> includeDirs = ndkHandler.getStlIncludes(stlName, Abi.getByName(
- platform.getName()));
- for (File dir : includeDirs) {
- cppFlags.add("-I" + dir.toString());
- }
- return cppFlags;
- }
-
- @Override
- public Iterable<String> getLdFlags() {
- if (stl.equals("system")) {
- return Collections.emptyList();
- }
- List<String> flags = Lists.newArrayList();
- flags.add(getStlLib(platform.getName()).toString());
- return flags;
- }
-
- public File getStlLib(String abi) {
- String stlLib;
- if (stlName.equals("stlport")) {
- stlLib = "stlport";
- } else if (stlName.equals("gnustl")) {
- stlLib = "gnu-libstdc++/" + ndkHandler.getGccToolchainVersion(Abi.getByName(abi));
- } else if (stlName.equals("gabi++")) {
- stlLib = "gabi++";
- } else if (stlName.equals("c++")) {
- stlLib = "llvm-libc++";
- } else {
- throw new AssertionError(
- "Unreachable. Either stl is invalid or stl is \"system\", " +
- "in which case there is no library file and getStlLib should not be called.");
- }
- return new File(
- StlConfiguration.getStlBaseDirectory(ndkHandler),
- stlLib + "/libs/" + platform.getName() + "/lib" + stl + (isStatic ? ".a" : ".so"));
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/ToolchainConfiguration.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/ToolchainConfiguration.java
deleted file mode 100644
index b500610..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/ToolchainConfiguration.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.ndk.internal;
-
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.core.Toolchain;
-
-import org.gradle.api.Action;
-import org.gradle.nativeplatform.platform.NativePlatform;
-import org.gradle.nativeplatform.toolchain.Clang;
-import org.gradle.nativeplatform.toolchain.Gcc;
-import org.gradle.nativeplatform.toolchain.GccCompatibleToolChain;
-import org.gradle.nativeplatform.toolchain.GccPlatformToolChain;
-import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
-import org.gradle.platform.base.PlatformContainer;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Action to configure toolchain for native binaries.
- */
-public class ToolchainConfiguration {
-
- public static void configurePlatforms(PlatformContainer platforms, NdkHandler ndkHandler) {
- for (Abi abi : ndkHandler.getSupportedAbis()) {
- NativePlatform platform = platforms.maybeCreate(abi.getName(), NativePlatform.class);
-
- // All we care is the name of the platform. It doesn't matter what the
- // architecture is, but it must be set to non-x86 so that it does not match
- // the default supported platform.
- platform.architecture("ppc");
- platform.operatingSystem("linux");
- }
-
- }
-
- /**
- * Configure toolchain for a platform.
- */
- public static void configureToolchain(
- NativeToolChainRegistry toolchainRegistry,
- final String toolchainName,
- final NdkHandler ndkHandler) {
- final Toolchain ndkToolchain = Toolchain.getByName(toolchainName);
- toolchainRegistry.create("ndk-" + toolchainName,
- toolchainName.equals("gcc") ? Gcc.class : Clang.class,
- new Action<GccCompatibleToolChain>() {
- @Override
- public void execute(GccCompatibleToolChain toolchain) {
- // Configure each platform.
- for (Abi it : ndkHandler.getSupportedAbis()) {
- final Abi abi = it;
- toolchain.target(abi.getName(), new Action<GccPlatformToolChain>() {
- @Override
- public void execute(GccPlatformToolChain targetPlatform) {
- if (Toolchain.GCC.equals(ndkToolchain)) {
- String gccPrefix = abi.getGccExecutablePrefix();
- targetPlatform.getcCompiler()
- .setExecutable(gccPrefix + "-gcc");
- targetPlatform.getCppCompiler()
- .setExecutable(gccPrefix + "-g++");
- targetPlatform.getLinker()
- .setExecutable(gccPrefix + "-g++");
- targetPlatform.getAssembler()
- .setExecutable(gccPrefix + "-as");
- targetPlatform.getStaticLibArchiver()
- .setExecutable(gccPrefix + "-ar");
- }
-
- // By default, gradle will use -Xlinker to pass arguments to the linker.
- // Removing it as it prevents -sysroot from being properly set.
- targetPlatform.getLinker().withArguments(
- new Action<List<String>>() {
- @Override
- public void execute(List<String> args) {
- args.removeAll(Collections.singleton("-Xlinker"));
- }
- });
- }
-
- });
- toolchain.path(ndkHandler.getCCompiler(abi).getParentFile());
- }
- }
- });
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/tasks/GdbSetupTask.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/tasks/GdbSetupTask.java
deleted file mode 100644
index 3e3866e..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/tasks/GdbSetupTask.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle.tasks;
-
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.managed.NdkConfig;
-import com.android.build.gradle.ndk.internal.StlConfiguration;
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.language.c.CSourceSet;
-import org.gradle.language.cpp.CppSourceSet;
-import org.gradle.nativeplatform.NativeBinarySpec;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * Task to create gdb.setup for native code debugging.
- */
-public class GdbSetupTask extends DefaultTask {
-
- private NdkHandler ndkHandler;
-
- private NdkConfig extension;
-
- private NativeBinarySpec binary;
-
- private File outputDir;
-
-
- // ----- PUBLIC TASK API -----
-
- @Input
- public File getOutputDir() {
- return outputDir;
- }
-
- public void setOutputDir(File outputDir) {
- this.outputDir = outputDir;
- }
-
- // ----- PRIVATE TASK API -----
-
- @Input
- public NdkHandler getNdkHandler() {
- return ndkHandler;
- }
-
- public void setNdkHandler(NdkHandler ndkHandler) {
- this.ndkHandler = ndkHandler;
- }
-
- @Input
- public NdkConfig getExtension() {
- return extension;
- }
-
- public void setExtension(NdkConfig extension) {
- this.extension = extension;
- }
-
- @Input
- public NativeBinarySpec getBinary() {
- return binary;
- }
-
- public void setBinary(NativeBinarySpec binary) {
- this.binary = binary;
- }
-
- @TaskAction
- public void taskAction() {
- File gdbSetupFile = new File(outputDir, "gdb.setup");
-
- StringBuilder sb = new StringBuilder();
-
- sb.append("set solib-search-path ")
- .append(outputDir.toString())
- .append("\n")
- .append("directory ")
- .append(ndkHandler.getSysroot(Abi.getByName(binary.getTargetPlatform().getName())))
- .append("/usr/include ");
-
- final Set<String> sources = Sets.newHashSet();
- binary.getSource().withType(CSourceSet.class, new Action<CSourceSet>() {
- @Override
- public void execute(CSourceSet sourceSet) {
- for (File src : sourceSet.getSource().getSrcDirs()) {
- sources.add(src.toString());
- }
- }
- });
- binary.getSource().withType(CppSourceSet.class, new Action<CppSourceSet>() {
- @Override
- public void execute(CppSourceSet sourceSet) {
- for (File src : sourceSet.getSource().getSrcDirs()) {
- sources.add(src.toString());
- }
- }
- });
- sources.addAll(StlConfiguration.getStlSources(ndkHandler, extension.getStl()));
- sb.append(Joiner.on(' ').join(sources));
-
- if (!outputDir.exists()) {
- outputDir.mkdirs();
- }
-
- try {
- Files.write(sb.toString(), gdbSetupFile, Charsets.UTF_8);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/tasks/StripDebugSymbolTask.java b/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/tasks/StripDebugSymbolTask.java
deleted file mode 100644
index ff5b96b..0000000
--- a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/tasks/StripDebugSymbolTask.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.LoggerWrapper;
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.process.GradleProcessExecutor;
-import com.android.build.gradle.ndk.internal.NdkNamingScheme;
-import com.android.ide.common.process.LoggedProcessOutputHandler;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessInfoBuilder;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.nativeplatform.SharedLibraryBinarySpec;
-
-import java.io.File;
-
-/**
- * Task to remove debug symbols from a native library.
- */
-public class StripDebugSymbolTask extends DefaultTask {
-
- private File stripCommand;
-
- private File inputFile;
-
- private File outputFile;
-
- // ----- PUBLIC API -----
-
- @Input
- public File getStripCommand() {
- return stripCommand;
- }
-
- public void setStripCommand(File stripCommand) {
- this.stripCommand = stripCommand;
- }
-
- @Optional
- @InputFile
- public File getInputFile() {
- // If source set is empty, the file debuggable library is not generated.
- return inputFile.exists() ? inputFile : null;
- }
-
- public void setInputFile(File inputFile) {
- this.inputFile = inputFile;
- }
-
- @OutputFile
- public File getOutputFile() {
- return outputFile;
- }
-
- public void setOutputFile(File outputFile) {
- this.outputFile = outputFile;
- }
-
- // ----- PRIVATE API -----
-
- @TaskAction
- void taskAction() throws ProcessException {
- if (getInputFile() == null) {
- return;
- }
-
- if (!outputFile.getParentFile().exists()) {
- outputFile.getParentFile().mkdirs();
- }
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
- builder.setExecutable(stripCommand);
- builder.addArgs("--strip-unneeded");
- builder.addArgs("-o");
- builder.addArgs(outputFile.toString());
- builder.addArgs(inputFile.toString());
- new GradleProcessExecutor(getProject()).execute(
- builder.createProcess(),
- new LoggedProcessOutputHandler(new LoggerWrapper(getLogger())));
- }
-
- // ----- ConfigAction -----
-
- public static class ConfigAction implements Action<StripDebugSymbolTask> {
- @NonNull
- private final SharedLibraryBinarySpec binary;
- @NonNull
- private final File buildDir;
- @NonNull
- private final NdkHandler handler;
-
- public ConfigAction(
- @NonNull SharedLibraryBinarySpec binary,
- @NonNull File buildDir,
- @NonNull NdkHandler handler) {
- this.binary = binary;
- this.buildDir = buildDir;
- this.handler = handler;
- }
-
- @Override
- public void execute(@NonNull StripDebugSymbolTask task) {
- File debugLib = binary.getSharedLibraryFile();
- task.setInputFile(debugLib);
- task.setOutputFile(new File(
- buildDir,
- NdkNamingScheme.getOutputDirectoryName(binary) + "/"
- + debugLib.getName()));
- task.setStripCommand(handler.getStripCommand(
- Abi.getByName(binary.getTargetPlatform().getName())));
- task.dependsOn(binary);
- }
- }
-}
diff --git a/base/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.native.properties b/base/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.native.properties
deleted file mode 100644
index 5e67a34..0000000
--- a/base/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.native.properties
+++ /dev/null
@@ -1 +0,0 @@
-implementation-class=com.android.build.gradle.model.NdkComponentModelPlugin
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.java b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.java
deleted file mode 100644
index 35bde14..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.android.build.gradle;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.api.ApplicationVariant;
-import com.android.build.gradle.api.BaseVariant;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.builder.core.AndroidBuilder;
-
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.internal.reflect.Instantiator;
-
-/**
- * 'android' extension for 'com.android.application' project.
- */
-public class AppExtension extends TestedExtension {
-
- private final DefaultDomainObjectSet<ApplicationVariant> applicationVariantList
- = new DefaultDomainObjectSet<ApplicationVariant>(ApplicationVariant.class);
-
- public AppExtension(@NonNull ProjectInternal project, @NonNull Instantiator instantiator,
- @NonNull AndroidBuilder androidBuilder, @NonNull SdkHandler sdkHandler,
- @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
- @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
- @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
- @NonNull ExtraModelInfo extraModelInfo, boolean isLibrary) {
- super(project, instantiator, androidBuilder, sdkHandler, buildTypes, productFlavors,
- signingConfigs, extraModelInfo, isLibrary);
- }
-
- /**
- * Returns the list of Application variants. Since the collections is built after evaluation, it
- * should be used with Gradle's <code>all</code> iterator to process future items.
- */
- public DefaultDomainObjectSet<ApplicationVariant> getApplicationVariants() {
- return applicationVariantList;
- }
-
- @Override
- public void addVariant(BaseVariant variant) {
- applicationVariantList.add((ApplicationVariant) variant);
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
deleted file mode 100644
index aba323e..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle
-
-import com.android.build.gradle.internal.ApplicationTaskManager
-import com.android.build.gradle.internal.DependencyManager
-import com.android.build.gradle.internal.SdkHandler
-import com.android.build.gradle.internal.TaskManager
-import com.android.build.gradle.internal.variant.ApplicationVariantFactory
-import com.android.build.gradle.internal.variant.VariantFactory
-import com.android.builder.core.AndroidBuilder
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.internal.reflect.Instantiator
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
-
-import javax.inject.Inject
-
-/**
- * Gradle plugin class for 'application' projects.
- */
-class AppPlugin extends BasePlugin implements Plugin<Project> {
- @Inject
- public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
- super(instantiator, registry)
- }
-
- @Override
- protected Class<? extends BaseExtension> getExtensionClass() {
- return AppExtension.class
- }
-
- @Override
- protected TaskManager createTaskManager(
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- return new ApplicationTaskManager(
- project,
- androidBuilder,
- extension,
- sdkHandler,
- dependencyManager,
- toolingRegistry)
- }
-
- @Override
- void apply(Project project) {
- super.apply(project)
- }
-
- @Override
- protected VariantFactory createVariantFactory() {
- return new ApplicationVariantFactory(instantiator, androidBuilder, extension)
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.java b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.java
deleted file mode 100644
index b4f4bb0..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.java
+++ /dev/null
@@ -1,799 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.api.AndroidSourceSet;
-import com.android.build.gradle.api.BaseVariant;
-import com.android.build.gradle.api.VariantFilter;
-import com.android.build.gradle.internal.CompileOptions;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.LoggingUtil;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.SourceSetSourceProviderWrapper;
-import com.android.build.gradle.internal.coverage.JacocoExtension;
-import com.android.build.gradle.internal.dsl.AaptOptions;
-import com.android.build.gradle.internal.dsl.AdbOptions;
-import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.CoreBuildType;
-import com.android.build.gradle.internal.dsl.CoreProductFlavor;
-import com.android.build.gradle.internal.dsl.DexOptions;
-import com.android.build.gradle.internal.dsl.LintOptions;
-import com.android.build.gradle.internal.dsl.PackagingOptions;
-import com.android.build.gradle.internal.dsl.PreprocessingOptions;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.build.gradle.internal.dsl.Splits;
-import com.android.build.gradle.internal.dsl.TestOptions;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.core.LibraryRequest;
-import com.android.builder.model.SourceProvider;
-import com.android.builder.sdk.TargetInfo;
-import com.android.builder.testing.api.DeviceProvider;
-import com.android.builder.testing.api.TestServer;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Lists;
-
-import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.api.tasks.SourceSet;
-import org.gradle.internal.reflect.Instantiator;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Base 'android' extension for all android plugins.
- *
- * <p>This is never used directly. Instead,
- *<ul>
- * <li>Plugin <code>com.android.application</code> uses {@link AppExtension}</li>
- * <li>Plugin <code>com.android.library</code> uses {@link LibraryExtension}</li>
- * <li>Plugin <code>com.android.test</code> uses {@link TestedExtension}</li>
- * </ul>
- */
- at SuppressWarnings("UnnecessaryInheritDoc")
-public abstract class BaseExtension implements AndroidConfig {
-
- private String target;
- private FullRevision buildToolsRevision;
- private List<LibraryRequest> libraryRequests = Lists.newArrayList();
-
- /** Default config, shared by all flavors. */
- final ProductFlavor defaultConfig;
-
- /** Options for aapt, tool for packaging resources. */
- final AaptOptions aaptOptions;
-
- /** Lint options. */
- final LintOptions lintOptions;
-
- /** Dex options. */
- final DexOptions dexOptions;
-
- /** Options for running tests. */
- final TestOptions testOptions;
-
- /** Compile options */
- final CompileOptions compileOptions;
-
- /** Packaging options. */
- final PackagingOptions packagingOptions;
-
- /** Options to control resources preprocessing. Not finalized yet.*/
- final PreprocessingOptions preprocessingOptions;
-
- /** JaCoCo options. */
- final JacocoExtension jacoco;
-
- /**
- * APK splits options.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
- */
- final Splits splits;
-
- /** All product flavors used by this project. */
- final NamedDomainObjectContainer<CoreProductFlavor> productFlavors;
-
- /** Build types used by this project. */
- final NamedDomainObjectContainer<BuildType> buildTypes;
-
- /** Signing configs used by this project. */
- final NamedDomainObjectContainer<SigningConfig> signingConfigs;
-
- private ExtraModelInfo extraModelInfo;
-
- protected Project project;
-
- /** Adb options */
- final AdbOptions adbOptions;
-
- /** A prefix to be used when creating new resources. Used by Studio */
- String resourcePrefix;
-
- List<String> flavorDimensionList;
-
- private String defaultPublishConfig = "release";
- private boolean publishNonDefault = false;
-
- private Action<VariantFilter> variantFilter;
-
- private final List<DeviceProvider> deviceProviderList = Lists.newArrayList();
- private final List<TestServer> testServerList = Lists.newArrayList();
-
- private final AndroidBuilder androidBuilder;
-
- private final SdkHandler sdkHandler;
-
- protected Logger logger;
-
- private boolean isWritable = true;
-
- /**
- * The source sets container.
- */
- final NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer;
-
- BaseExtension(
- @NonNull final ProjectInternal project,
- @NonNull Instantiator instantiator,
- @NonNull AndroidBuilder androidBuilder,
- @NonNull SdkHandler sdkHandler,
- @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
- @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
- @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
- @NonNull ExtraModelInfo extraModelInfo,
- final boolean isLibrary) {
- this.androidBuilder = androidBuilder;
- this.sdkHandler = sdkHandler;
- this.buildTypes = buildTypes;
- //noinspection unchecked
- this.productFlavors = (NamedDomainObjectContainer) productFlavors;
- this.signingConfigs = signingConfigs;
- this.extraModelInfo = extraModelInfo;
- this.project = project;
-
- logger = Logging.getLogger(this.getClass());
-
- defaultConfig = instantiator.newInstance(ProductFlavor.class, BuilderConstants.MAIN,
- project, instantiator, project.getLogger());
-
- aaptOptions = instantiator.newInstance(AaptOptions.class);
- dexOptions = instantiator.newInstance(DexOptions.class);
- lintOptions = instantiator.newInstance(LintOptions.class);
- testOptions = instantiator.newInstance(TestOptions.class);
- compileOptions = instantiator.newInstance(CompileOptions.class);
- packagingOptions = instantiator.newInstance(PackagingOptions.class);
- preprocessingOptions = instantiator.newInstance(PreprocessingOptions.class);
- jacoco = instantiator.newInstance(JacocoExtension.class);
- adbOptions = instantiator.newInstance(AdbOptions.class);
- splits = instantiator.newInstance(Splits.class, instantiator);
-
- sourceSetsContainer = project.container(AndroidSourceSet.class,
- new AndroidSourceSetFactory(instantiator, project, isLibrary));
-
- sourceSetsContainer.whenObjectAdded(new Action<AndroidSourceSet>() {
- @Override
- public void execute(AndroidSourceSet sourceSet) {
- ConfigurationContainer configurations = project.getConfigurations();
-
- createConfiguration(
- configurations,
- sourceSet.getCompileConfigurationName(),
- "Classpath for compiling the " + sourceSet.getName() + " sources.");
-
- String packageConfigDescription;
- if (isLibrary) {
- packageConfigDescription
- = "Classpath only used when publishing '" + sourceSet.getName() + "'.";
- } else {
- packageConfigDescription
- = "Classpath packaged with the compiled '" + sourceSet.getName() + "' classes.";
- }
- createConfiguration(
- configurations,
- sourceSet.getPackageConfigurationName(),
- packageConfigDescription);
-
- createConfiguration(
- configurations,
- sourceSet.getProvidedConfigurationName(),
- "Classpath for only compiling the " + sourceSet.getName() + " sources.");
-
- createConfiguration(
- configurations,
- sourceSet.getWearAppConfigurationName(),
- "Link to a wear app to embed for object '" + sourceSet.getName() + "'.");
-
- sourceSet.setRoot(String.format("src/%s", sourceSet.getName()));
- }
- });
-
- sourceSetsContainer.create(defaultConfig.getName());
- }
-
- /**
- * Disallow further modification on the extension.
- */
- public void disableWrite() {
- isWritable = false;
- }
-
- protected void checkWritability() {
- if (!isWritable) {
- throw new GradleException(
- "Android tasks have already been created.\n" +
- "This happens when calling android.applicationVariants,\n" +
- "android.libraryVariants or android.testVariants.\n" +
- "Once these methods are called, it is not possible to\n" +
- "continue configuring the model.");
- }
- }
-
- protected void createConfiguration(
- @NonNull ConfigurationContainer configurations,
- @NonNull String configurationName,
- @NonNull String configurationDescription) {
- logger.info("Creating configuration {}", configurationName);
-
- Configuration configuration = configurations.findByName(configurationName);
- if (configuration == null) {
- configuration = configurations.create(configurationName);
- }
- configuration.setVisible(false);
- configuration.setDescription(configurationDescription);
- }
-
- /**
- * Sets the compile SDK version, based on full SDK version string, e.g.
- * <code>android-21</code> for Lollipop.
- */
- public void compileSdkVersion(String version) {
- checkWritability();
- this.target = version;
- }
-
- /**
- * Sets the compile SDK version, based on API level, e.g. 21 for Lollipop.
- */
- public void compileSdkVersion(int apiLevel) {
- compileSdkVersion("android-" + apiLevel);
- }
-
- public void setCompileSdkVersion(int apiLevel) {
- compileSdkVersion(apiLevel);
- }
-
- public void setCompileSdkVersion(String target) {
- compileSdkVersion(target);
- }
-
- /**
- * Request the use a of Library. The library is then added to the classpath.
- * @param name the name of the library.
- */
- public void useLibrary(String name) {
- useLibrary(name, true);
- }
-
- /**
- * Request the use a of Library. The library is then added to the classpath.
- * @param name the name of the library.
- * @param required if using the library requires a manifest entry, the entry will
- * indicate that the library is not required.
- */
- public void useLibrary(String name, boolean required) {
- libraryRequests.add(new LibraryRequest(name, required));
- }
-
- public void buildToolsVersion(String version) {
- checkWritability();
- buildToolsRevision = FullRevision.parseRevision(version);
- }
-
- /**
- * <strong>Required.</strong> Version of the build tools to use.
- *
- * <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
- * back may give a slightly different string.
- */
- @Override
- public String getBuildToolsVersion() {
- return buildToolsRevision.toString();
- }
-
- public void setBuildToolsVersion(String version) {
- buildToolsVersion(version);
- }
-
- /**
- * Configures the build types.
- */
- public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
- checkWritability();
- action.execute(buildTypes);
- }
-
- /**
- * Configures the product flavors.
- */
- public void productFlavors(Action<? super NamedDomainObjectContainer<CoreProductFlavor>> action) {
- checkWritability();
- action.execute(productFlavors);
- }
-
- /**
- * Configures the signing configs.
- */
- public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
- checkWritability();
- action.execute(signingConfigs);
- }
-
- public void flavorDimensions(String... dimensions) {
- checkWritability();
- flavorDimensionList = Arrays.asList(dimensions);
- }
-
- /**
- * Configures the source sets. Note that the Android plugin uses its own implementation of
- * source sets, {@link AndroidSourceSet}.
- */
- public void sourceSets(Action<NamedDomainObjectContainer<AndroidSourceSet>> action) {
- checkWritability();
- action.execute(sourceSetsContainer);
- }
-
- /**
- * All source sets. Note that the Android plugin uses its own implementation of
- * source sets, {@link AndroidSourceSet}.
- */
- @Override
- public NamedDomainObjectContainer<AndroidSourceSet> getSourceSets() {
- return sourceSetsContainer;
- }
-
- /**
- * The default configuration, inherited by all build flavors (if any are defined).
- */
- public void defaultConfig(Action<ProductFlavor> action) {
- checkWritability();
- action.execute(defaultConfig);
- }
-
- /**
- * Configures aapt options.
- */
- public void aaptOptions(Action<AaptOptions> action) {
- checkWritability();
- action.execute(aaptOptions);
- }
-
- /**
- * Configures dex options.
- */
- public void dexOptions(Action<DexOptions> action) {
- checkWritability();
- action.execute(dexOptions);
- }
-
- /**
- * Configure lint options.
- */
- public void lintOptions(Action<LintOptions> action) {
- checkWritability();
- action.execute(lintOptions);
- }
-
- /** Configures the test options. */
- public void testOptions(Action<TestOptions> action) {
- checkWritability();
- action.execute(testOptions);
- }
-
- /**
- * Configures compile options.
- */
- public void compileOptions(Action<CompileOptions> action) {
- checkWritability();
- action.execute(compileOptions);
- }
-
- /**
- * Configures packaging options.
- */
- public void packagingOptions(Action<PackagingOptions> action) {
- checkWritability();
- action.execute(packagingOptions);
- }
-
- /**
- * Configures preprocessing options.
- */
- public void preprocessingOptions(Action<PreprocessingOptions> action) {
- checkWritability();
- action.execute(preprocessingOptions);
- }
-
- /**
- * Configures JaCoCo options.
- */
- public void jacoco(Action<JacocoExtension> action) {
- checkWritability();
- action.execute(jacoco);
- }
-
- /**
- * Configures adb options.
- */
- public void adbOptions(Action<AdbOptions> action) {
- checkWritability();
- action.execute(adbOptions);
- }
-
- /**
- * Configures APK splits.
- */
- public void splits(Action<Splits> action) {
- checkWritability();
- action.execute(splits);
- }
-
- public void deviceProvider(DeviceProvider deviceProvider) {
- checkWritability();
- deviceProviderList.add(deviceProvider);
- }
-
- @Override
- @NonNull
- public List<DeviceProvider> getDeviceProviders() {
- return deviceProviderList;
- }
-
- public void testServer(TestServer testServer) {
- checkWritability();
- testServerList.add(testServer);
- }
-
- @Override
- @NonNull
- public List<TestServer> getTestServers() {
- return testServerList;
- }
-
- /** {@inheritDoc} */
- @Override
- public Collection<? extends CoreProductFlavor> getProductFlavors() {
- return productFlavors;
- }
-
- /** {@inheritDoc} */
- @Override
- public Collection<? extends CoreBuildType> getBuildTypes() {
- return buildTypes;
- }
-
- /** {@inheritDoc} */
- @Override
- public Collection<? extends com.android.builder.model.SigningConfig> getSigningConfigs() {
- return signingConfigs;
- }
-
- public void defaultPublishConfig(String value) {
- setDefaultPublishConfig(value);
- }
-
- public void publishNonDefault(boolean value) {
- publishNonDefault = value;
- }
-
- /**
- * Name of the configuration used to build the default artifact of this project.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
- * Referencing a Library</a>
- */
- @Override
- public String getDefaultPublishConfig() {
- return defaultPublishConfig;
- }
-
- public void setDefaultPublishConfig(String value) {
- defaultPublishConfig = value;
- }
-
- /**
- * Whether to publish artifacts for all configurations, not just the default one.
- *
- * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
- * Referencing a Library</a>
- */
- @Override
- public boolean getPublishNonDefault() {
- return publishNonDefault;
- }
-
- public void variantFilter(Action<VariantFilter> filter) {
- setVariantFilter(filter);
- }
-
- public void setVariantFilter(Action<VariantFilter> filter) {
- variantFilter = filter;
- }
-
- /**
- * A variant filter to control which variants are excluded.
- * <p>The filter is an {@link Action} which is passed a single object of type
- * {@link com.android.build.gradle.internal.api.VariantFilter}. It should set the
- * {@link VariantFilter#setIgnore(boolean)} flag to filter out the given variant.
- */
- @Override
- public Action<VariantFilter> getVariantFilter() {
- return variantFilter;
- }
-
- @Override
- public AdbOptions getAdbOptions() {
- return adbOptions;
- }
-
- /** {@inheritDoc} */
- @Override
- public String getResourcePrefix() {
- return resourcePrefix;
- }
-
- @Override
- public List<String> getFlavorDimensionList() {
- return flavorDimensionList;
- }
-
- @Override
- public boolean getGeneratePureSplits() {
- return generatePureSplits;
- }
-
- /** {@inheritDoc} */
- @Override
- @Beta
- public PreprocessingOptions getPreprocessingOptions() {
- return preprocessingOptions;
- }
-
- public void resourcePrefix(String prefix) {
- resourcePrefix = prefix;
- }
-
- public abstract void addVariant(BaseVariant variant);
-
- public void registerArtifactType(@NonNull String name,
- boolean isTest,
- int artifactType) {
- extraModelInfo.registerArtifactType(name, isTest, artifactType);
- }
-
- public void registerBuildTypeSourceProvider(
- @NonNull String name,
- @NonNull BuildType buildType,
- @NonNull SourceProvider sourceProvider) {
- extraModelInfo.registerBuildTypeSourceProvider(name, buildType, sourceProvider);
- }
-
- public void registerProductFlavorSourceProvider(
- @NonNull String name,
- @NonNull CoreProductFlavor productFlavor,
- @NonNull SourceProvider sourceProvider) {
- extraModelInfo.registerProductFlavorSourceProvider(name, productFlavor, sourceProvider);
- }
-
- public void registerJavaArtifact(
- @NonNull String name,
- @NonNull BaseVariant variant,
- @NonNull String assembleTaskName,
- @NonNull String javaCompileTaskName,
- @NonNull Collection<File> generatedSourceFolders,
- @NonNull Iterable<String> ideSetupTaskNames,
- @NonNull Configuration configuration,
- @NonNull File classesFolder,
- @NonNull File javaResourceFolder,
- @Nullable SourceProvider sourceProvider) {
- extraModelInfo.registerJavaArtifact(name, variant, assembleTaskName,
- javaCompileTaskName, generatedSourceFolders, ideSetupTaskNames,
- configuration, classesFolder, javaResourceFolder, sourceProvider);
- }
-
- public void registerMultiFlavorSourceProvider(
- @NonNull String name,
- @NonNull String flavorName,
- @NonNull SourceProvider sourceProvider) {
- extraModelInfo.registerMultiFlavorSourceProvider(name, flavorName, sourceProvider);
- }
-
- @NonNull
- public SourceProvider wrapJavaSourceSet(@NonNull SourceSet sourceSet) {
- return new SourceSetSourceProviderWrapper(sourceSet);
- }
-
- /**
- * <strong>Required.</strong> Compile SDK version.
- *
- * <p>Your code will be compiled against the android.jar from this API level. You should
- * generally use the most up-to-date SDK version here. Use the Lint tool to make sure you don't
- * use APIs not available in earlier platform version without checking.
- *
- * <p>Setter can be called with a string like "android-21" or a number.
- *
- * <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
- * back may give a slightly different string.
- */
- @Override
- public String getCompileSdkVersion() {
- return target;
- }
-
- @Override
- public FullRevision getBuildToolsRevision() {
- return buildToolsRevision;
- }
-
- @Override
- public Collection<LibraryRequest> getLibraryRequests() {
- return libraryRequests;
- }
-
- public File getSdkDirectory() {
- return sdkHandler.getSdkFolder();
- }
-
- public File getNdkDirectory() {
- return sdkHandler.getNdkFolder();
- }
-
- public List<File> getBootClasspath() {
- ensureTargetSetup();
- return androidBuilder.getBootClasspath();
- }
-
- public File getAdbExe() {
- return sdkHandler.getSdkInfo().getAdb();
- }
-
- public File getDefaultProguardFile(String name) {
- File sdkDir = sdkHandler.getAndCheckSdkFolder();
- return new File(sdkDir,
- SdkConstants.FD_TOOLS + File.separatorChar
- + SdkConstants.FD_PROGUARD + File.separatorChar
- + name);
- }
-
- // ---------------
- // TEMP for compatibility
-
- // by default, we do not generate pure splits
- boolean generatePureSplits = false;
-
- public void generatePureSplits(boolean flag) {
- if (flag) {
- logger.warn("Pure splits are not supported by PlayStore yet.");
- }
- this.generatePureSplits = flag;
- }
-
- private boolean enforceUniquePackageName = true;
-
- public void enforceUniquePackageName(boolean value) {
- if (!value) {
- LoggingUtil.displayDeprecationWarning(logger, project, "Support for libraries with same package name is deprecated and will be removed in a future release.");
- }
- enforceUniquePackageName = value;
- }
-
- public void setEnforceUniquePackageName(boolean value) {
- enforceUniquePackageName(value);
- }
-
- @Override
- public boolean getEnforceUniquePackageName() {
- return enforceUniquePackageName;
- }
-
- /** {@inheritDoc} */
- @Override
- public CoreProductFlavor getDefaultConfig() {
- return defaultConfig;
- }
-
- /** {@inheritDoc} */
- @Override
- public AaptOptions getAaptOptions() {
- return aaptOptions;
- }
-
- /** {@inheritDoc} */
- @Override
- public CompileOptions getCompileOptions() {
- return compileOptions;
- }
-
- /** {@inheritDoc} */
- @Override
- public DexOptions getDexOptions() {
- return dexOptions;
- }
-
- /** {@inheritDoc} */
- @Override
- public JacocoExtension getJacoco() {
- return jacoco;
- }
-
- /** {@inheritDoc} */
- @Override
- public LintOptions getLintOptions() {
- return lintOptions;
- }
-
- /** {@inheritDoc} */
- @Override
- public PackagingOptions getPackagingOptions() {
- return packagingOptions;
- }
-
- /** {@inheritDoc} */
- @Override
- public Splits getSplits() {
- return splits;
- }
-
- /** {@inheritDoc} */
- @Override
- public TestOptions getTestOptions() {
- return testOptions;
- }
-
- private void ensureTargetSetup() {
- // check if the target has been set.
- TargetInfo targetInfo = androidBuilder.getTargetInfo();
- if (targetInfo == null) {
- sdkHandler.initTarget(
- getCompileSdkVersion(),
- buildToolsRevision,
- libraryRequests,
- androidBuilder);
- }
- }
-
- // For compatibility with LibraryExtension.
- @Override
- public Boolean getPackageBuildConfig() {
- throw new GradleException("packageBuildConfig is not supported.");
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.java b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.java
deleted file mode 100755
index 85bb8c6..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.java
+++ /dev/null
@@ -1,714 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle;
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
-import static com.google.common.base.Preconditions.checkState;
-import static java.io.File.separator;
-
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.build.gradle.internal.ApiObjectFactory;
-import com.android.build.gradle.internal.BadPluginException;
-import com.android.build.gradle.internal.DependencyManager;
-import com.android.build.gradle.internal.ExecutionConfigurationUtil;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.LibraryCache;
-import com.android.build.gradle.internal.LoggerWrapper;
-import com.android.build.gradle.internal.NativeLibraryFactoryImpl;
-import com.android.build.gradle.internal.NdkHandler;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.TaskContainerAdaptor;
-import com.android.build.gradle.internal.TaskManager;
-import com.android.build.gradle.internal.VariantManager;
-import com.android.build.gradle.internal.coverage.JacocoPlugin;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.BuildTypeFactory;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.ProductFlavorFactory;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.build.gradle.internal.dsl.SigningConfigFactory;
-import com.android.build.gradle.internal.model.ModelBuilder;
-import com.android.build.gradle.internal.process.GradleJavaProcessExecutor;
-import com.android.build.gradle.internal.process.GradleProcessExecutor;
-import com.android.build.gradle.internal.profile.RecordingBuildListener;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.VariantFactory;
-import com.android.build.gradle.tasks.JillTask;
-import com.android.build.gradle.tasks.PreDex;
-import com.android.builder.Version;
-import com.android.builder.core.AndroidBuilder;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.internal.compiler.JackConversionCache;
-import com.android.builder.internal.compiler.PreDexCache;
-import com.android.builder.profile.ExecutionType;
-import com.android.builder.profile.ProcessRecorderFactory;
-import com.android.builder.profile.Recorder;
-import com.android.builder.profile.ThreadRecorder;
-import com.android.builder.sdk.TargetInfo;
-import com.android.ide.common.internal.ExecutorSingleton;
-import com.android.utils.ILogger;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import org.gradle.BuildListener;
-import org.gradle.BuildResult;
-import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.execution.TaskExecutionGraph;
-import org.gradle.api.execution.TaskExecutionGraphListener;
-import org.gradle.api.initialization.Settings;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.plugins.JavaBasePlugin;
-import org.gradle.api.plugins.JavaPlugin;
-import org.gradle.api.tasks.StopExecutionException;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.tooling.BuildException;
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.jar.Manifest;
-import java.util.regex.Pattern;
-
-/**
- * Base class for all Android plugins
- */
-public abstract class BasePlugin {
-
- private static final String GRADLE_MIN_VERSION = "2.2";
- public static final Pattern GRADLE_ACCEPTABLE_VERSIONS = Pattern.compile("2\\.[2-9].*");
- private static final String GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY =
- "com.android.build.gradle.overrideVersionCheck";
- private static final String SKIP_PATH_CHECK_PROPERTY =
- "com.android.build.gradle.overridePathCheck";
- /** default retirement age in days since its inception date for RC or beta versions. */
- private static final int DEFAULT_RETIREMENT_AGE_FOR_NON_RELEASE_IN_DAYS = 40;
-
-
- protected BaseExtension extension;
-
- protected VariantManager variantManager;
-
- protected TaskManager taskManager;
-
- protected Project project;
-
- protected SdkHandler sdkHandler;
-
- private NdkHandler ndkHandler;
-
- protected AndroidBuilder androidBuilder;
-
- protected Instantiator instantiator;
-
- protected VariantFactory variantFactory;
-
- private ToolingModelBuilderRegistry registry;
-
- private JacocoPlugin jacocoPlugin;
-
- private LoggerWrapper loggerWrapper;
-
- private ExtraModelInfo extraModelInfo;
-
- private String creator;
-
- private boolean hasCreatedTasks = false;
-
- protected BasePlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
- this.instantiator = instantiator;
- this.registry = registry;
- creator = "Android Gradle " + Version.ANDROID_GRADLE_PLUGIN_VERSION;
- verifyRetirementAge();
-
- ModelBuilder.clearCaches();
- }
-
- /**
- * Verify that this plugin execution is within its public time range.
- */
- private void verifyRetirementAge() {
-
- Manifest manifest;
- URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
- try {
- URL url = cl.findResource("META-INF/MANIFEST.MF");
- manifest = new Manifest(url.openStream());
- } catch (IOException ignore) {
- return;
- }
-
- String inceptionDateAttr = manifest.getMainAttributes().getValue("Inception-Date");
- // when running in unit tests, etc... the manifest entries are absent.
- if (inceptionDateAttr == null) {
- return;
- }
- List<String> items = ImmutableList.copyOf(Splitter.on(':').split(inceptionDateAttr));
- GregorianCalendar inceptionDate = new GregorianCalendar(Integer.parseInt(items.get(0)),
- Integer.parseInt(items.get(1)), Integer.parseInt(items.get(2)));
-
- int retirementAgeInDays =
- getRetirementAgeInDays(manifest.getMainAttributes().getValue("Plugin-Version"));
-
- if (retirementAgeInDays == -1) {
- return;
- }
- Calendar now = GregorianCalendar.getInstance();
- long nowTimestamp = now.getTimeInMillis();
- long inceptionTimestamp = inceptionDate.getTimeInMillis();
- long days = TimeUnit.DAYS.convert(nowTimestamp - inceptionTimestamp, TimeUnit.MILLISECONDS);
- if (days > retirementAgeInDays) {
- // this plugin is too old.
- String dailyOverride = System.getenv("ANDROID_DAILY_OVERRIDE");
- final MessageDigest crypt;
- try {
- crypt = MessageDigest.getInstance("SHA-1");
- } catch (NoSuchAlgorithmException e) {
- return;
- }
- crypt.reset();
- // encode the day, not the current time.
- try {
- crypt.update(String.format("%1$s:%2$s:%3$s",
- now.get(Calendar.YEAR),
- now.get(Calendar.MONTH),
- now.get(Calendar.DATE)).getBytes("utf8"));
- } catch (UnsupportedEncodingException e) {
- return;
- }
- String overrideValue = new BigInteger(1, crypt.digest()).toString(16);
- if (dailyOverride == null) {
- String message = "Plugin is too old, please update to a more recent version, or " +
- "set ANDROID_DAILY_OVERRIDE environment variable to \"" + overrideValue + '"';
- System.err.println(message);
- throw new RuntimeException(message);
- } else {
- if (!dailyOverride.equals(overrideValue)) {
- String message = "Plugin is too old and ANDROID_DAILY_OVERRIDE value is " +
- "also outdated, please use new value :\"" + overrideValue + '"';
- System.err.println(message);
- throw new RuntimeException(message);
- }
- }
- }
- }
-
- private static int getRetirementAgeInDays(@Nullable String version) {
- if (version == null || version.contains("rc") || version.contains("beta")
- || version.contains("alpha")) {
- return DEFAULT_RETIREMENT_AGE_FOR_NON_RELEASE_IN_DAYS;
- }
- return -1;
- }
-
- protected abstract Class<? extends BaseExtension> getExtensionClass();
- protected abstract VariantFactory createVariantFactory();
- protected abstract TaskManager createTaskManager(
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry);
-
- /**
- * Return whether this plugin creates Android library. Should be overridden if true.
- */
- protected boolean isLibrary() {
- return false;
- }
-
- @VisibleForTesting
- VariantManager getVariantManager() {
- return variantManager;
- }
-
- protected ILogger getLogger() {
- if (loggerWrapper == null) {
- loggerWrapper = new LoggerWrapper(project.getLogger());
- }
-
- return loggerWrapper;
- }
-
-
- protected void apply(Project project) throws IOException {
- this.project = project;
-
- ExecutionConfigurationUtil.setThreadPoolSize(project);
- checkPathForErrors();
- checkModulesForErrors();
-
- List<Recorder.Property> propertyList = Lists.newArrayList(
- new Recorder.Property("plugin_version", Version.ANDROID_GRADLE_PLUGIN_VERSION),
- new Recorder.Property("next_gen_plugin", "false"),
- new Recorder.Property("gradle_version", project.getGradle().getGradleVersion())
- );
- String benchmarkName = AndroidGradleOptions.getBenchmarkName(project);
- if (benchmarkName != null) {
- propertyList.add(new Recorder.Property("benchmark_name", benchmarkName));
- }
- String benchmarkMode = AndroidGradleOptions.getBenchmarkMode(project);
- if (benchmarkMode != null) {
- propertyList.add(new Recorder.Property("benchmark_mode", benchmarkMode));
- }
-
- ProcessRecorderFactory.initialize(
- getLogger(),
- project.getRootProject().file("profiler" + System.currentTimeMillis() + ".json"),
- propertyList);
- project.getGradle().addListener(new RecordingBuildListener(ThreadRecorder.get()));
-
-
- ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- configureProject();
- return null;
- }
- }, new Recorder.Property("project", project.getName()));
-
- ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSTION_CREATION,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createExtension();
- return null;
- }
- }, new Recorder.Property("project", project.getName()));
-
- ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createTasks();
- return null;
- }
- }, new Recorder.Property("project", project.getName()));
- }
-
- protected void configureProject() {
- checkGradleVersion();
- extraModelInfo = new ExtraModelInfo(project, isLibrary());
- sdkHandler = new SdkHandler(project, getLogger());
- androidBuilder = new AndroidBuilder(
- project == project.getRootProject() ? project.getName() : project.getPath(),
- creator,
- new GradleProcessExecutor(project),
- new GradleJavaProcessExecutor(project),
- extraModelInfo,
- getLogger(),
- isVerbose());
-
- project.getPlugins().apply(JavaBasePlugin.class);
-
- jacocoPlugin = project.getPlugins().apply(JacocoPlugin.class);
-
- project.getTasks().getByName("assemble").setDescription(
- "Assembles all variants of all applications and secondary packages.");
-
- // call back on execution. This is called after the whole build is done (not
- // after the current project is done).
- // This is will be called for each (android) projects though, so this should support
- // being called 2+ times.
- project.getGradle().addBuildListener(new BuildListener() {
- @Override
- public void buildStarted(Gradle gradle) { }
-
- @Override
- public void settingsEvaluated(Settings settings) { }
-
- @Override
- public void projectsLoaded(Gradle gradle) { }
-
- @Override
- public void projectsEvaluated(Gradle gradle) { }
-
- @Override
- public void buildFinished(BuildResult buildResult) {
- ExecutorSingleton.shutdown();
- sdkHandler.unload();
- ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
- new Recorder.Block() {
- @Override
- public Void call() throws Exception {
- PreDexCache.getCache().clear(
- new File(project.getRootProject().getBuildDir(),
- FD_INTERMEDIATES + "/dex-cache/cache.xml"),
- getLogger());
- JackConversionCache.getCache().clear(
- new File(project.getRootProject().getBuildDir(),
- FD_INTERMEDIATES + "/jack-cache/cache.xml"),
- getLogger());
- LibraryCache.getCache().unload();
- return null;
- }
- }, new Recorder.Property("project", project.getName()));
-
- try {
- ProcessRecorderFactory.shutdown();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- });
- project.getGradle().getTaskGraph().addTaskExecutionGraphListener(
- new TaskExecutionGraphListener() {
- @Override
- public void graphPopulated(TaskExecutionGraph taskGraph) {
- for (Task task : taskGraph.getAllTasks()) {
- if (task instanceof PreDex) {
- PreDexCache.getCache().load(
- new File(project.getRootProject().getBuildDir(),
- FD_INTERMEDIATES + "/dex-cache/cache.xml"));
- break;
- } else if (task instanceof JillTask) {
- JackConversionCache.getCache().load(
- new File(project.getRootProject().getBuildDir(),
- FD_INTERMEDIATES + "/jack-cache/cache.xml"));
- break;
- }
- }
- }
- });
- }
-
-
- private void createExtension() {
- final NamedDomainObjectContainer<BuildType> buildTypeContainer = project.container(
- BuildType.class,
- new BuildTypeFactory(instantiator, project, project.getLogger()));
- final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer = project.container(
- ProductFlavor.class,
- new ProductFlavorFactory(instantiator, project, project.getLogger()));
- final NamedDomainObjectContainer<SigningConfig> signingConfigContainer = project.container(
- SigningConfig.class,
- new SigningConfigFactory(instantiator));
-
- extension = project.getExtensions().create("android", getExtensionClass(),
- project, instantiator, androidBuilder, sdkHandler,
- buildTypeContainer, productFlavorContainer, signingConfigContainer,
- extraModelInfo, isLibrary());
-
- // create the default mapping configuration.
- project.getConfigurations().create("default-mapping")
- .setDescription("Configuration for default mapping artifacts.");
- project.getConfigurations().create("default-metadata")
- .setDescription("Metadata for the produced APKs.");
-
- DependencyManager dependencyManager = new DependencyManager(project, extraModelInfo);
- taskManager = createTaskManager(
- project,
- androidBuilder,
- extension,
- sdkHandler,
- dependencyManager,
- registry);
-
- variantFactory = createVariantFactory();
- variantManager = new VariantManager(
- project,
- androidBuilder,
- extension,
- variantFactory,
- taskManager,
- instantiator);
-
- ndkHandler = new NdkHandler(
- project.getRootDir(),
- null, /* compileSkdVersion, this will be set in afterEvaluate */
- "gcc",
- "" /*toolchainVersion*/);
-
- // Register a builder for the custom tooling model
- ModelBuilder modelBuilder = new ModelBuilder(
- androidBuilder,
- variantManager,
- taskManager,
- extension,
- extraModelInfo,
- ndkHandler,
- new NativeLibraryFactoryImpl(ndkHandler),
- isLibrary());
- registry.register(modelBuilder);
-
- // map the whenObjectAdded callbacks on the containers.
- signingConfigContainer.whenObjectAdded(new Action<SigningConfig>() {
- @Override
- public void execute(SigningConfig signingConfig) {
- variantManager.addSigningConfig(signingConfig);
- }
- });
-
-
- buildTypeContainer.whenObjectAdded(new Action<BuildType>() {
- @Override
- public void execute(BuildType buildType) {
- SigningConfig signingConfig = signingConfigContainer.findByName(BuilderConstants.DEBUG);
- buildType.init(signingConfig);
- variantManager.addBuildType(buildType);
- }
- });
-
- productFlavorContainer.whenObjectAdded(new Action<ProductFlavor>() {
- @Override
- public void execute(ProductFlavor productFlavor) {
- variantManager.addProductFlavor(productFlavor);
- }
- });
-
- // map whenObjectRemoved on the containers to throw an exception.
- signingConfigContainer.whenObjectRemoved(
- new UnsupportedAction("Removing signingConfigs is not supported."));
- buildTypeContainer.whenObjectRemoved(
- new UnsupportedAction("Removing build types is not supported."));
- productFlavorContainer.whenObjectRemoved(
- new UnsupportedAction("Removing product flavors is not supported."));
-
- // create default Objects, signingConfig first as its used by the BuildTypes.
- variantFactory.createDefaultComponents(
- buildTypeContainer, productFlavorContainer, signingConfigContainer);
- }
-
- private static class UnsupportedAction implements Action<Object> {
-
- private final String message;
-
- UnsupportedAction(String message) {
- this.message = message;
- }
-
- @Override
- public void execute(Object o) {
- throw new UnsupportedOperationException(message);
- }
- }
-
- private void createTasks() {
- ThreadRecorder.get().record(ExecutionType.TASK_MANAGER_CREATE_TASKS,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- taskManager.createTasksBeforeEvaluate(
- new TaskContainerAdaptor(project.getTasks()));
- return null;
- }
- },
- new Recorder.Property("project", project.getName()));
-
- project.afterEvaluate(new Action<Project>() {
- @Override
- public void execute(Project project) {
- ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- createAndroidTasks(false);
- return null;
- }
- },
- new Recorder.Property("project", project.getName()));
- }
- });
- }
-
- private void checkGradleVersion() {
- if (!GRADLE_ACCEPTABLE_VERSIONS.matcher(project.getGradle().getGradleVersion()).matches()) {
- boolean allowNonMatching = Boolean.getBoolean(GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
- File file = new File("gradle" + separator + "wrapper" + separator +
- "gradle-wrapper.properties");
- String errorMessage = String.format(
- "Gradle version %s is required. Current version is %s. " +
- "If using the gradle wrapper, try editing the distributionUrl in %s " +
- "to gradle-%s-all.zip",
- GRADLE_MIN_VERSION, project.getGradle().getGradleVersion(), file.getAbsolutePath(),
- GRADLE_MIN_VERSION);
- if (allowNonMatching) {
- getLogger().warning(errorMessage);
- getLogger().warning("As %s is set, continuing anyways.",
- GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
- } else {
- throw new BuildException(errorMessage, null);
- }
- }
- }
-
- @VisibleForTesting
- final void createAndroidTasks(boolean force) {
- // Make sure unit tests set the required fields.
- checkState(extension.getBuildToolsRevision() != null, "buildToolsVersion is not specified.");
- checkState(extension.getCompileSdkVersion() != null, "compileSdkVersion is not specified.");
-
- ndkHandler.setCompileSdkVersion(extension.getCompileSdkVersion());
-
- // get current plugins and look for the default Java plugin.
- if (project.getPlugins().hasPlugin(JavaPlugin.class)) {
- throw new BadPluginException(
- "The 'java' plugin has been applied, but it is not compatible with the Android plugins.");
- }
-
- ensureTargetSetup();
-
- // don't do anything if the project was not initialized.
- // Unless TEST_SDK_DIR is set in which case this is unit tests and we don't return.
- // This is because project don't get evaluated in the unit test setup.
- // See AppPluginDslTest
- if (!force
- && (!project.getState().getExecuted() || project.getState().getFailure()!= null)
- && SdkHandler.sTestSdkFolder == null) {
- return;
- }
-
- if (hasCreatedTasks) {
- return;
- }
- hasCreatedTasks = true;
-
- extension.disableWrite();
-
- ThreadRecorder.get().record(
- ExecutionType.GENERAL_CONFIG,
- Recorder.EmptyBlock,
- new Recorder.Property("build_tools_version",
- extension.getBuildToolsRevision().toString()));
-
- // setup SDK repositories.
- for (final File file : sdkHandler.getSdkLoader().getRepositories()) {
- project.getRepositories().maven(new Action<MavenArtifactRepository>() {
- @Override
- public void execute(MavenArtifactRepository mavenArtifactRepository) {
- mavenArtifactRepository.setUrl(file.toURI());
- }
- });
- }
-
- taskManager.createMockableJarTask();
- ThreadRecorder.get().record(ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
- new Recorder.Block<Void>() {
- @Override
- public Void call() throws Exception {
- variantManager.createAndroidTasks();
- ApiObjectFactory apiObjectFactory = new ApiObjectFactory(
- androidBuilder, extension, variantFactory, instantiator);
- for (BaseVariantData variantData : variantManager.getVariantDataList()) {
- apiObjectFactory.create(variantData);
- }
- return null;
- }
- }, new Recorder.Property("project", project.getName()));
- }
-
- private boolean isVerbose() {
- return project.getLogger().isEnabled(LogLevel.INFO);
- }
-
- private void ensureTargetSetup() {
- // check if the target has been set.
- TargetInfo targetInfo = androidBuilder.getTargetInfo();
- if (targetInfo == null) {
- if (extension.getCompileOptions() == null) {
- throw new GradleException("Calling getBootClasspath before compileSdkVersion");
- }
-
- sdkHandler.initTarget(
- extension.getCompileSdkVersion(),
- extension.getBuildToolsRevision(),
- extension.getLibraryRequests(),
- androidBuilder);
- }
- }
-
- /**
- * Check the sub-projects structure :
- * So far, checks that 2 modules do not have the same identification (group+name).
- */
- private void checkModulesForErrors() {
- Project rootProject = project.getRootProject();
- Map<String, Project> subProjectsById = new HashMap<String, Project>();
- for (Project subProject : rootProject.getAllprojects()) {
- String id = subProject.getGroup().toString() + ":" + subProject.getName();
- if (subProjectsById.containsKey(id)) {
- String message = String.format(
- "Your project contains 2 or more modules with the same " +
- "identification %1$s\n" +
- "at \"%2$s\" and \"%3$s\".\n" +
- "You must use different identification (either name or group) for " +
- "each modules.",
- id,
- subProjectsById.get(id).getPath(),
- subProject.getPath() );
- throw new StopExecutionException(message);
- } else {
- subProjectsById.put(id, subProject);
- }
- }
- }
-
- private void checkPathForErrors() {
- // See if the user disabled the check:
- if (Boolean.getBoolean(SKIP_PATH_CHECK_PROPERTY)) {
- return;
- }
-
- if (project.hasProperty(SKIP_PATH_CHECK_PROPERTY)
- && project.property(SKIP_PATH_CHECK_PROPERTY) instanceof String
- && Boolean.valueOf((String) project.property(SKIP_PATH_CHECK_PROPERTY))) {
- return;
- }
-
- // See if we're on Windows:
- if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
- return;
- }
-
- // See if the path contains non-ASCII characters.
- if (CharMatcher.ASCII.matchesAllOf(project.getRootDir().getAbsolutePath())) {
- return;
- }
-
- String message = "Your project path contains non-ASCII characters. This will most likely " +
- "cause the build to fail on Windows. Please move your project to a different " +
- "directory. See http://b.android.com/95744 for details. " +
- "This warning can be disabled by using the command line flag -D" +
- SKIP_PATH_CHECK_PROPERTY + "=true, or adding the line " +
- SKIP_PATH_CHECK_PROPERTY + "=true' to gradle.properties file " +
- "in the project directory.";
-
- throw new StopExecutionException(message);
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.java b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.java
deleted file mode 100644
index bb48a2d..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.android.build.gradle;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.api.BaseVariant;
-import com.android.build.gradle.api.LibraryVariant;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.LoggingUtil;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.builder.core.AndroidBuilder;
-
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.internal.reflect.Instantiator;
-
-/**
- * 'android' extension for 'com.android.library' project.
- */
-public class LibraryExtension extends TestedExtension {
-
- private final DefaultDomainObjectSet<LibraryVariant> libraryVariantList
- = new DefaultDomainObjectSet<LibraryVariant>(LibraryVariant.class);
-
- private boolean packageBuildConfig = true;
-
- public LibraryExtension(@NonNull ProjectInternal project, @NonNull Instantiator instantiator,
- @NonNull AndroidBuilder androidBuilder, @NonNull SdkHandler sdkHandler,
- @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
- @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
- @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
- @NonNull ExtraModelInfo extraModelInfo, boolean isLibrary) {
- super(project, instantiator, androidBuilder, sdkHandler, buildTypes, productFlavors,
- signingConfigs, extraModelInfo, isLibrary);
- }
-
- /**
- * Returns the list of library variants. Since the collections is built after evaluation, it
- * should be used with Gradle's <code>all</code> iterator to process future items.
- */
- public DefaultDomainObjectSet<LibraryVariant> getLibraryVariants() {
- return libraryVariantList;
- }
-
- @Override
- public void addVariant(BaseVariant variant) {
- libraryVariantList.add((LibraryVariant) variant);
- }
-
- // ---------------
- // TEMP for compatibility
- // STOPSHIP Remove in 1.0
-
- public void packageBuildConfig(boolean value) {
- if (!value) {
- LoggingUtil.displayDeprecationWarning(logger, project,
- "Support for not packaging BuildConfig is deprecated and will be removed in 1.0");
- }
-
- packageBuildConfig = value;
- }
-
- public void setPackageBuildConfig(boolean value) {
- packageBuildConfig(value);
- }
-
- @Override
- public Boolean getPackageBuildConfig() {
- return packageBuildConfig;
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
deleted file mode 100644
index df69746..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.build.gradle
-
-import com.android.build.gradle.internal.DependencyManager
-import com.android.build.gradle.internal.LibraryTaskManager
-import com.android.build.gradle.internal.SdkHandler
-import com.android.build.gradle.internal.TaskManager
-import com.android.build.gradle.internal.variant.LibraryVariantFactory
-import com.android.build.gradle.internal.variant.VariantFactory
-import com.android.builder.core.AndroidBuilder
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.internal.reflect.Instantiator
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
-
-import javax.inject.Inject
-
-/**
- * Gradle plugin class for 'library' projects.
- */
-public class LibraryPlugin extends BasePlugin implements Plugin<Project> {
-
- /**
- * Default assemble task for the default-published artifact. this is needed for
- * the prepare task on the consuming project.
- */
- Task assembleDefault
-
- @Inject
- public LibraryPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
- super(instantiator, registry)
- }
-
- @Override
- public Class<? extends BaseExtension> getExtensionClass() {
- return LibraryExtension.class
- }
-
- @Override
- protected VariantFactory createVariantFactory() {
- return new LibraryVariantFactory(
- instantiator,
- androidBuilder,
- (LibraryExtension) extension);
- }
-
- @Override
- protected boolean isLibrary() {
- return true;
- }
-
- @Override
- protected TaskManager createTaskManager(
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- return new LibraryTaskManager(
- project,
- androidBuilder,
- extension,
- sdkHandler,
- dependencyManager,
- toolingRegistry)
- }
-
- @Override
- void apply(Project project) {
- super.apply(project)
-
- assembleDefault = project.tasks.create("assembleDefault")
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestExtension.java b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestExtension.java
deleted file mode 100644
index d17a3ca..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestExtension.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.android.build.gradle;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.api.ApplicationVariant;
-import com.android.build.gradle.api.BaseVariant;
-import com.android.build.gradle.internal.ExtraModelInfo;
-import com.android.build.gradle.internal.SdkHandler;
-import com.android.build.gradle.internal.dsl.BuildType;
-import com.android.build.gradle.internal.dsl.ProductFlavor;
-import com.android.build.gradle.internal.dsl.SigningConfig;
-import com.android.builder.core.AndroidBuilder;
-
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.internal.reflect.Instantiator;
-
-/**
- * 'android' extension for 'com.android.test' project.
- */
-public class TestExtension extends BaseExtension implements TestAndroidConfig {
-
- private final DefaultDomainObjectSet<ApplicationVariant> applicationVariantList
- = new DefaultDomainObjectSet<ApplicationVariant>(ApplicationVariant.class);
-
- private String targetProjectPath = null;
-
- private String targetVariant = "debug";
-
- public TestExtension(@NonNull ProjectInternal project, @NonNull Instantiator instantiator,
- @NonNull AndroidBuilder androidBuilder, @NonNull SdkHandler sdkHandler,
- @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
- @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
- @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
- @NonNull ExtraModelInfo extraModelInfo, boolean isLibrary) {
- super(project, instantiator, androidBuilder, sdkHandler, buildTypes, productFlavors,
- signingConfigs, extraModelInfo, isLibrary);
- }
-
- /**
- * Returns the list of Application variants. Since the collections is built after evaluation, it
- * should be used with Gradle's <code>all</code> iterator to process future items.
- */
- public DefaultDomainObjectSet<ApplicationVariant> getApplicationVariants() {
- return applicationVariantList;
- }
-
- @Override
- public void addVariant(BaseVariant variant) {
- applicationVariantList.add((ApplicationVariant) variant);
- }
-
- /**
- * Returns the Gradle path of the project that this test project tests.
- */
- @Override
- public String getTargetProjectPath() {
- return targetProjectPath;
- }
-
- public void setTargetProjectPath(String targetProjectPath) {
- checkWritability();
- this.targetProjectPath = targetProjectPath;
- }
-
- public void targetProjectPath(String targetProjectPath) {
- setTargetProjectPath(targetProjectPath);
- }
-
- /**
- * Returns the variant of the tested project.
- *
- * Default is 'debug'
- */
- @Override
- public String getTargetVariant() {
- return targetVariant;
- }
-
- public void setTargetVariant(String targetVariant) {
- checkWritability();
- this.targetVariant = targetVariant;
- }
-
- public void targetVariant(String targetVariant) {
- setTargetVariant(targetVariant);
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestPlugin.groovy b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestPlugin.groovy
deleted file mode 100644
index 01bbd24..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestPlugin.groovy
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle
-
-import com.android.build.gradle.internal.DependencyManager
-import com.android.build.gradle.internal.SdkHandler
-import com.android.build.gradle.internal.TaskManager
-import com.android.build.gradle.internal.TestApplicationTaskManager
-import com.android.build.gradle.internal.variant.TestVariantFactory
-import com.android.build.gradle.internal.variant.VariantFactory
-import com.android.builder.core.AndroidBuilder
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.internal.reflect.Instantiator
-import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
-
-import javax.inject.Inject
-
-/**
- * Gradle plugin class for 'application' projects.
- */
-class TestPlugin extends BasePlugin implements Plugin<Project> {
- @Inject
- public TestPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
- super(instantiator, registry)
- }
-
- @Override
- protected Class<? extends BaseExtension> getExtensionClass() {
- return TestExtension.class
- }
-
- @Override
- protected TaskManager createTaskManager(
- Project project,
- AndroidBuilder androidBuilder,
- AndroidConfig extension,
- SdkHandler sdkHandler,
- DependencyManager dependencyManager,
- ToolingModelBuilderRegistry toolingRegistry) {
- return new TestApplicationTaskManager (
- project,
- androidBuilder,
- extension,
- sdkHandler,
- dependencyManager,
- toolingRegistry)
- }
-
- @Override
- void apply(Project project) {
- super.apply(project)
- }
-
- @Override
- protected VariantFactory createVariantFactory() {
- return new TestVariantFactory(instantiator, androidBuilder, extension)
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/NativeLibraryFactoryImpl.java b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/NativeLibraryFactoryImpl.java
deleted file mode 100644
index ccbaeba..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/NativeLibraryFactoryImpl.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal;
-
-import com.android.annotations.NonNull;
-import com.android.build.gradle.internal.core.Abi;
-import com.android.build.gradle.internal.dsl.CoreNdkOptions;
-import com.android.build.gradle.internal.model.NativeLibraryFactory;
-import com.android.build.gradle.internal.model.NativeLibraryImpl;
-import com.android.build.gradle.internal.scope.VariantScope;
-import com.android.build.gradle.internal.variant.BaseVariantData;
-import com.android.build.gradle.internal.variant.BaseVariantOutputData;
-import com.android.build.gradle.tasks.NdkCompile;
-import com.android.builder.model.NativeLibrary;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Implementation of NativeLibraryFactory for gradle plugin.
- */
-public class NativeLibraryFactoryImpl implements NativeLibraryFactory {
-
- @NonNull
- final NdkHandler ndkHandler;
-
- public NativeLibraryFactoryImpl(
- @NonNull NdkHandler ndkHandler) {
- this.ndkHandler = ndkHandler;
- }
-
- @NonNull
- @Override
- public Optional<NativeLibrary> create(
- @NonNull VariantScope scope,
- @NonNull String toolchainName, @NonNull Abi abi) {
- BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
- if (!scope.getGlobalScope().getProject().hasProperty(NdkCompile.USE_DEPRECATED_NDK)) {
- return Optional.absent();
- }
-
- CoreNdkOptions ndkConfig = variantData.getVariantConfiguration().getNdkConfig();
-
- String sysrootFlag = "--sysroot=" + ndkHandler.getSysroot(abi);
- List<String> cFlags = ndkConfig.getcFlags() == null
- ? ImmutableList.of(sysrootFlag)
- : ImmutableList.of(sysrootFlag, ndkConfig.getcFlags());
-
- // The DSL currently do not support all options available in the model such as the
- // include dirs and the defines. Therefore, just pass an empty collection for now.
- return Optional.<NativeLibrary>of(new NativeLibraryImpl(
- ndkConfig.getModuleName(),
- toolchainName,
- abi.getName(),
- Collections.<File>emptyList(), /*cIncludeDirs*/
- Collections.<File>emptyList(), /*cppIncludeDirs*/
- Collections.<File>emptyList(), /*cSystemIncludeDirs*/
- ndkHandler.getStlIncludes(ndkConfig.getStl(), abi),
- Collections.<String>emptyList(), /*cDefines*/
- Collections.<String>emptyList(), /*cppDefines*/
- cFlags,
- cFlags, // TODO: NdkConfig should allow cppFlags to be set separately.
- ImmutableList.of(scope.getNdkDebuggableLibraryFolders(abi))));
- }
-}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorFactory.java b/base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorFactory.java
deleted file mode 100644
index bb5ed02..0000000
--- a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorFactory.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.internal.dsl;
-
-import com.android.annotations.NonNull;
-
-import org.gradle.api.NamedDomainObjectFactory;
-import org.gradle.api.Project;
-import org.gradle.api.logging.Logger;
-import org.gradle.internal.reflect.Instantiator;
-
-/**
- * Factory to create ProductFlavor object using an {@link Instantiator} to add
- * the DSL methods.
- */
-public class ProductFlavorFactory implements NamedDomainObjectFactory<ProductFlavor> {
-
- @NonNull
- private final Instantiator instantiator;
- @NonNull
- private final Project project;
- @NonNull
- private final Logger logger;
-
- public ProductFlavorFactory(@NonNull Instantiator instantiator,
- @NonNull Project project,
- @NonNull Logger logger) {
- this.instantiator = instantiator;
- this.project = project;
- this.logger = logger;
- }
-
- @Override
- public ProductFlavor create(String name) {
- return instantiator.newInstance(ProductFlavor.class,
- name, project, instantiator, logger);
- }
-}
diff --git a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy b/base/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy
deleted file mode 100644
index 66a133c..0000000
--- a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy
+++ /dev/null
@@ -1,687 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle
-import com.android.annotations.NonNull
-import com.android.build.gradle.api.ApkVariant
-import com.android.build.gradle.api.ApkVariantOutput
-import com.android.build.gradle.api.ApplicationVariant
-import com.android.build.gradle.api.BaseVariantOutput
-import com.android.build.gradle.api.TestVariant
-import com.android.build.gradle.internal.SdkHandler
-import com.android.build.gradle.internal.core.GradleVariantConfiguration
-import com.android.build.gradle.internal.test.BaseTest
-import org.gradle.api.JavaVersion
-import org.gradle.api.Project
-import org.gradle.testfixtures.ProjectBuilder
-
-import static com.android.build.gradle.DslTestUtil.DEFAULT_VARIANTS
-import static com.android.build.gradle.DslTestUtil.countVariants
-/**
- * Tests for the public DSL of the App plugin ("android")
- */
-public class AppPluginDslTest extends BaseTest {
-
- @Override
- protected void setUp() throws Exception {
- SdkHandler.testSdkFolder = new File(System.getenv("ANDROID_HOME"))
- }
-
- public void testBasic() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- plugin.createAndroidTasks(false)
- assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
-
- // we can now call this since the variants/tasks have been created
- Set<ApplicationVariant> variants = project.android.applicationVariants
- assertEquals(2, variants.size())
-
- Set<TestVariant> testVariants = project.android.testVariants
- assertEquals(1, testVariants.size())
-
- checkTestedVariant("debug", "debugAndroidTest", variants, testVariants)
- checkNonTestedVariant("release", variants)
- }
-
- /**
- * Same as Basic but with a slightly different DSL.
- */
- public void testBasic2() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- plugin.createAndroidTasks(false)
- assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
-
- // we can now call this since the variants/tasks have been created
- Set<ApplicationVariant> variants = project.android.applicationVariants
- assertEquals(2, variants.size())
-
- Set<TestVariant> testVariants = project.android.testVariants
- assertEquals(1, testVariants.size())
-
- checkTestedVariant("debug", "debugAndroidTest", variants, testVariants)
- checkNonTestedVariant("release", variants)
- }
-
- public void testBasicWithStringTarget() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion "android-" + COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- plugin.createAndroidTasks(false)
- assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
-
- // we can now call this since the variants/tasks have been created
- Set<ApplicationVariant> variants = project.android.applicationVariants
- assertEquals(2, variants.size())
-
- Set<TestVariant> testVariants = project.android.testVariants
- assertEquals(1, testVariants.size())
-
- checkTestedVariant("debug", "debugAndroidTest", variants, testVariants)
- checkNonTestedVariant("release", variants)
- }
-
- public void testMultiRes() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/multires")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- sourceSets {
- main {
- res {
- srcDirs 'src/main/res1', 'src/main/res2'
- }
- }
- }
- }
-
- // nothing to be done here. If the DSL fails, it'll throw an exception
- }
-
- public void testBuildTypes() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
- testBuildType "staging"
-
- buildTypes {
- staging {
- signingConfig signingConfigs.debug
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- plugin.createAndroidTasks(false)
- assertEquals(
- countVariants(appVariants: 3, unitTest: 3, androidTests: 1),
- plugin.variantManager.variantDataList.size())
-
- // we can now call this since the variants/tasks have been created
-
- // does not include tests
- Set<ApplicationVariant> variants = project.android.applicationVariants
- assertEquals(3, variants.size())
-
- Set<TestVariant> testVariants = project.android.testVariants
- assertEquals(1, testVariants.size())
-
- checkTestedVariant("staging", "stagingAndroidTest", variants, testVariants)
-
- checkNonTestedVariant("debug", variants)
- checkNonTestedVariant("release", variants)
- }
-
- public void testFlavors() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- productFlavors {
- flavor1 {
-
- }
- flavor2 {
-
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- plugin.createAndroidTasks(false)
- assertEquals(
- countVariants(appVariants: 4, unitTest: 4, androidTests: 2),
- plugin.variantManager.variantDataList.size())
-
- // we can now call this since the variants/tasks have been created
-
- // does not include tests
- Set<ApplicationVariant> variants = project.android.applicationVariants
- assertEquals(4, variants.size())
-
- Set<TestVariant> testVariants = project.android.testVariants
- assertEquals(2, testVariants.size())
-
- checkTestedVariant("flavor1Debug", "flavor1DebugAndroidTest", variants, testVariants)
- checkTestedVariant("flavor2Debug", "flavor2DebugAndroidTest", variants, testVariants)
-
- checkNonTestedVariant("flavor1Release", variants)
- checkNonTestedVariant("flavor2Release", variants)
- }
-
- public void testMultiFlavors() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- flavorDimensions "dimension1", "dimension2"
-
- productFlavors {
- f1 {
- flavorDimension "dimension1"
- }
- f2 {
- flavorDimension "dimension1"
- }
-
- fa {
- flavorDimension "dimension2"
- }
- fb {
- flavorDimension "dimension2"
- }
- fc {
- flavorDimension "dimension2"
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- plugin.createAndroidTasks(false)
- assertEquals(
- countVariants(appVariants: 12, unitTest: 12, androidTests: 6),
- plugin.variantManager.variantDataList.size())
-
- // we can now call this since the variants/tasks have been created
-
- // does not include tests
- Set<ApplicationVariant> variants = project.android.applicationVariants
- assertEquals(12, variants.size())
-
- Set<TestVariant> testVariants = project.android.testVariants
- assertEquals(6, testVariants.size())
-
- checkTestedVariant("f1FaDebug", "f1FaDebugAndroidTest", variants, testVariants)
- checkTestedVariant("f1FbDebug", "f1FbDebugAndroidTest", variants, testVariants)
- checkTestedVariant("f1FcDebug", "f1FcDebugAndroidTest", variants, testVariants)
- checkTestedVariant("f2FaDebug", "f2FaDebugAndroidTest", variants, testVariants)
- checkTestedVariant("f2FbDebug", "f2FbDebugAndroidTest", variants, testVariants)
- checkTestedVariant("f2FcDebug", "f2FcDebugAndroidTest", variants, testVariants)
-
- checkNonTestedVariant("f1FaRelease", variants)
- checkNonTestedVariant("f1FbRelease", variants)
- checkNonTestedVariant("f1FcRelease", variants)
- checkNonTestedVariant("f2FaRelease", variants)
- checkNonTestedVariant("f2FbRelease", variants)
- checkNonTestedVariant("f2FcRelease", variants)
- }
-
- public void testSourceSetsApi() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
- }
-
- // query the sourceSets, will throw if missing
- println project.android.sourceSets.main.java.srcDirs
- println project.android.sourceSets.main.resources.srcDirs
- println project.android.sourceSets.main.manifest.srcFile
- println project.android.sourceSets.main.res.srcDirs
- println project.android.sourceSets.main.assets.srcDirs
- }
-
- public void testObfuscationMappingFile() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- buildTypes {
- release {
- minifyEnabled true
- proguardFile getDefaultProguardFile('proguard-android.txt')
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- plugin.createAndroidTasks(false)
- assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
-
- // we can now call this since the variants/tasks have been created
-
- // does not include tests
- Set<ApplicationVariant> variants = project.android.applicationVariants
- assertEquals(2, variants.size())
-
- for (ApplicationVariant variant : variants) {
- if ("release".equals(variant.getBuildType().getName())) {
- assertNotNull(variant.getMappingFile())
- } else {
- assertNull(variant.getMappingFile())
- }
- }
- }
-
- public void testProguardDsl() throws Exception {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- buildTypes {
- release {
- proguardFile 'file1.1'
- proguardFiles 'file1.2', 'file1.3'
- }
-
- custom {
- proguardFile 'file3.1'
- proguardFiles 'file3.2', 'file3.3'
- proguardFiles = ['file3.1']
- }
- }
-
- productFlavors {
- f1 {
- proguardFile 'file2.1'
- proguardFiles 'file2.2', 'file2.3'
- }
-
- f2 {
-
- }
-
- f3 {
- proguardFile 'file4.1'
- proguardFiles 'file4.2', 'file4.3'
- proguardFiles = ['file4.1']
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(false)
-
- def variantsData = plugin.variantManager.variantDataList
- Map<String, GradleVariantConfiguration> variantMap =
- variantsData.collectEntries {[it.name, it.variantConfiguration]}
-
- def expectedFiles = [
- f1Release: ["file1.1", "file1.2", "file1.3", "file2.1", "file2.2", "file2.3"],
- f1Debug: ["file2.1", "file2.2", "file2.3"],
- f2Release: ["file1.1", "file1.2", "file1.3"],
- f2Debug: [],
- f2Custom: ["file3.1"],
- f3Custom: ["file3.1", "file4.1"],
- ]
-
- expectedFiles.each { name, expected ->
- def actual = variantMap[name].getProguardFiles(false, [])
- assert (actual*.name as Set) == (expected as Set), name
- }
- }
-
- public void testProguardDsl_initWith() throws Exception {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- buildTypes {
- common {
- testProguardFile 'file1.1'
- }
-
- custom1.initWith(buildTypes.common)
- custom2.initWith(buildTypes.common)
-
- custom1 {
- testProguardFile 'file2.1'
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(false)
-
- def variantsData = plugin.variantManager.variantDataList
- Map<String, GradleVariantConfiguration> variantMap =
- variantsData.collectEntries {[it.name, it.variantConfiguration]}
-
- def expectedFiles = [
- common: ["file1.1"],
- custom1: ["file1.1", "file2.1"],
- custom2: ["file1.1"],
- ]
-
- expectedFiles.each { name, expected ->
- List<File> actual = variantMap[name].testProguardFiles
- assert (actual*.name as Set) == (expected as Set), name
- }
- }
-
- public void testSettingLanguageLevelFromCompileSdk() {
- def testLanguageLevel = { version, expectedLanguageLevel, useJack ->
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
- project.android {
- compileSdkVersion version
- buildToolsVersion '20.0.0'
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(false)
-
- assertEquals(
- "target compatibility for ${version}",
- expectedLanguageLevel.toString(),
- project.compileReleaseJavaWithJavac.targetCompatibility)
- assertEquals(
- "source compatibility for ${version}",
- expectedLanguageLevel.toString(),
- project.compileReleaseJavaWithJavac.sourceCompatibility)
- }
-
- for (useJack in [true, false]) {
- def propName = 'java.specification.version'
- String originalVersion = System.getProperty(propName)
- try{
- System.setProperty(propName, '1.7')
- testLanguageLevel('android-15', JavaVersion.VERSION_1_6, useJack)
- testLanguageLevel('android-21', JavaVersion.VERSION_1_7, useJack)
- testLanguageLevel('android-21', JavaVersion.VERSION_1_7, useJack)
- testLanguageLevel('Google Inc.:Google APIs:22', JavaVersion.VERSION_1_7, useJack)
-
- System.setProperty(propName, '1.6')
- testLanguageLevel(15, JavaVersion.VERSION_1_6, useJack)
- testLanguageLevel(21, JavaVersion.VERSION_1_6, useJack)
- testLanguageLevel('android-21', JavaVersion.VERSION_1_6, useJack)
- testLanguageLevel('Google Inc.:Google APIs:22', JavaVersion.VERSION_1_6, useJack)
- } finally {
- System.setProperty(propName, originalVersion)
- }
- }
- }
-
- public void testSettingLanguageLevelFromCompileSdk_dontOverride() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_6
- targetCompatibility JavaVersion.VERSION_1_6
- }
- }
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(false)
-
- assertEquals(
- JavaVersion.VERSION_1_6.toString(),
- project.compileReleaseJavaWithJavac.targetCompatibility)
- assertEquals(
- JavaVersion.VERSION_1_6.toString(),
- project.compileReleaseJavaWithJavac.sourceCompatibility)
- }
-
- public void testMockableJarName() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion "Google Inc.:Google APIs:" + COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(false)
-
- def mockableJarFile = plugin.taskManager.createMockableJar.outputFile
- assertFalse(mockableJarFile.absolutePath.contains(":"))
- assertEquals("mockable-Google-Inc.-Google-APIs-21.jar", mockableJarFile.name)
- }
-
- public void testEncoding() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- compileOptions {
- encoding "foo"
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(false)
-
- assertEquals(
- "foo",
- project.compileReleaseJavaWithJavac.options.encoding)
- }
-
- public void testInstrumentationRunnerArguments_merging() throws Exception {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion '20.0.0'
-
- defaultConfig {
- testInstrumentationRunnerArguments(value: "default", size: "small")
- }
-
- productFlavors {
- f1 {
- }
-
- f2 {
- testInstrumentationRunnerArgument "value", "f2"
- }
-
- f3 {
- testInstrumentationRunnerArguments["otherValue"] = "f3"
- }
-
- f4 {
- testInstrumentationRunnerArguments(otherValue: "f4.1")
- testInstrumentationRunnerArguments = [otherValue: "f4.2"]
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(false)
-
- def variantsData = plugin.variantManager.variantDataList
- Map<String, GradleVariantConfiguration> variantMap =
- variantsData.collectEntries {[it.name, it.variantConfiguration]}
-
- def expectedArgs = [
- f1Debug: [value: "default", size: "small"],
- f2Debug: [value: "f2", size: "small"],
- f3Debug: [value: "default", size: "small", otherValue: "f3"],
- f4Debug: [value: "default", size: "small", otherValue: "f4.2"],
- ]
-
- expectedArgs.each { name, expected ->
- assert expected == variantMap[name].instrumentationRunnerArguments
- }
- }
-
- private static void checkTestedVariant(@NonNull String variantName,
- @NonNull String testedVariantName,
- @NonNull Collection<ApplicationVariant> variants,
- @NonNull Set<TestVariant> testVariants) {
- ApplicationVariant variant = findNamedItem(variants, variantName, "variantData")
- assertNotNull(variant.testVariant)
- assertEquals(testedVariantName, variant.testVariant.name)
- assertEquals(variant.testVariant, findNamedItemMaybe(testVariants, testedVariantName))
- checkTasks(variant)
- checkTasks(variant.testVariant)
- }
-
- private static void checkNonTestedVariant(@NonNull String variantName,
- @NonNull Set<ApplicationVariant> variants) {
- ApplicationVariant variant = findNamedItem(variants, variantName, "variantData")
- assertNull(variant.testVariant)
- checkTasks(variant)
- }
-
- private static void checkTasks(@NonNull ApkVariant variant) {
- boolean isTestVariant = variant instanceof TestVariant;
-
- assertNotNull(variant.aidlCompile)
- assertNotNull(variant.mergeResources)
- assertNotNull(variant.mergeAssets)
- assertNotNull(variant.generateBuildConfig)
- assertNotNull(variant.javaCompile)
- assertNotNull(variant.processJavaResources)
- assertNotNull(variant.dex)
- assertNotNull(variant.assemble)
- assertNotNull(variant.uninstall)
-
- assertFalse(variant.outputs.isEmpty())
-
- for (BaseVariantOutput baseVariantOutput : variant.outputs) {
- assertTrue(baseVariantOutput instanceof ApkVariantOutput)
- ApkVariantOutput apkVariantOutput = (ApkVariantOutput) baseVariantOutput
-
- assertNotNull(apkVariantOutput.processManifest)
- assertNotNull(apkVariantOutput.processResources)
- assertNotNull(apkVariantOutput.packageApplication)
- }
-
- if (variant.isSigningReady()) {
- assertNotNull(variant.install)
-
- for (BaseVariantOutput baseVariantOutput : variant.outputs) {
- ApkVariantOutput apkVariantOutput = (ApkVariantOutput) baseVariantOutput
-
- // tested variant are never zipAligned.
- if (!isTestVariant && variant.buildType.zipAlignEnabled) {
- assertNotNull(apkVariantOutput.zipAlign)
- } else {
- assertNull(apkVariantOutput.zipAlign)
- }
- }
- } else {
- assertNull(variant.install)
- }
-
- if (isTestVariant) {
- TestVariant testVariant = variant as TestVariant
- assertNotNull(testVariant.connectedInstrumentTest)
- assertNotNull(testVariant.testedVariant)
- }
- }
-}
diff --git a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy b/base/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy
deleted file mode 100644
index d189740..0000000
--- a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle
-
-import com.android.build.gradle.internal.BadPluginException
-import com.android.build.gradle.internal.SdkHandler
-import com.android.build.gradle.internal.test.BaseTest
-import com.android.build.gradle.internal.variant.BaseVariantData
-import com.android.builder.core.BuilderConstants
-import com.android.builder.core.DefaultBuildType
-import com.android.builder.model.SigningConfig
-import com.android.ide.common.signing.KeystoreHelper
-import com.android.utils.ILogger
-import com.android.utils.StdLogger
-import org.gradle.api.Project
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
-import org.gradle.testfixtures.ProjectBuilder
-
-import static com.android.build.gradle.DslTestUtil.DEFAULT_VARIANTS
-import static com.android.build.gradle.DslTestUtil.countVariants
-
-/**
- * Tests for the internal workings of the app plugin ("android")
- */
-public class AppPluginInternalTest extends BaseTest {
-
- @Override
- protected void setUp() throws Exception {
- SdkHandler.testSdkFolder = new File(System.getenv("ANDROID_HOME"))
- }
-
- public void testBasic() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-1
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(true /*force*/)
-
- assertEquals(2, plugin.variantManager.buildTypes.size())
- assertNotNull(plugin.variantManager.buildTypes.get(BuilderConstants.DEBUG))
- assertNotNull(plugin.variantManager.buildTypes.get(BuilderConstants.RELEASE))
- assertEquals(0, plugin.variantManager.productFlavors.size())
-
-
- List<BaseVariantData> variants = plugin.variantManager.variantDataList
- assertEquals(DEFAULT_VARIANTS.size(), variants.size()) // includes the test variant(s)
-
- findNamedItem(variants, "debug", "variantData")
- findNamedItem(variants, "release", "variantData")
- findNamedItem(variants, "debugAndroidTest", "variantData")
- }
-
- public void testDefaultConfig() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
-
- signingConfigs {
- fakeConfig {
- storeFile project.file("aa")
- storePassword "bb"
- keyAlias "cc"
- keyPassword "dd"
- }
- }
-
- defaultConfig {
- versionCode 1
- versionName "2.0"
- minSdkVersion 2
- targetSdkVersion 3
-
- signingConfig signingConfigs.fakeConfig
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(true /*force*/)
-
- assertEquals(1, plugin.extension.defaultConfig.versionCode)
- assertNotNull(plugin.extension.defaultConfig.minSdkVersion)
- assertEquals(2, plugin.extension.defaultConfig.minSdkVersion.apiLevel)
- assertNotNull(plugin.extension.defaultConfig.targetSdkVersion)
- assertEquals(3, plugin.extension.defaultConfig.targetSdkVersion.apiLevel)
- assertEquals("2.0", plugin.extension.defaultConfig.versionName)
-
- assertEquals(new File(project.projectDir, "aa"),
- plugin.extension.defaultConfig.signingConfig.storeFile)
- assertEquals("bb", plugin.extension.defaultConfig.signingConfig.storePassword)
- assertEquals("cc", plugin.extension.defaultConfig.signingConfig.keyAlias)
- assertEquals("dd", plugin.extension.defaultConfig.signingConfig.keyPassword)
- }
-
- public void testBuildTypes() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
- testBuildType "staging"
-
- buildTypes {
- staging {
- signingConfig signingConfigs.debug
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(true /*force*/)
-
- assertEquals(3, plugin.variantManager.buildTypes.size())
-
- List<BaseVariantData> variants = plugin.variantManager.variantDataList
- assertEquals(countVariants(appVariants: 3, unitTests: 3, androidTests: 1), variants.size())
-
- String[] variantNames = [
- "debug", "release", "staging"]
-
- for (String variantName : variantNames) {
- findNamedItem(variants, variantName, "variantData")
- }
-
- BaseVariantData testVariant = findNamedItem(variants, "stagingAndroidTest", "variantData")
- assertEquals("staging", testVariant.variantConfiguration.buildType.name)
- }
-
- public void testFlavors() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
-
- productFlavors {
- flavor1 {
-
- }
- flavor2 {
-
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(true /*force*/)
-
- assertEquals(2, plugin.variantManager.productFlavors.size())
-
- List<BaseVariantData> variants = plugin.variantManager.variantDataList
- assertEquals(countVariants(appVariants: 4, unitTests: 4, androidTests: 2), variants.size())
-
- String[] variantNames = [
- "flavor1Debug", "flavor1Release", "flavor1DebugAndroidTest",
- "flavor2Debug", "flavor2Release", "flavor2DebugAndroidTest"]
-
- for (String variantName : variantNames) {
- findNamedItem(variants, variantName, "variantData")
- }
- }
-
- public void testMultiFlavors() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
-
- flavorDimensions "dimension1", "dimension2"
-
- productFlavors {
- f1 {
- flavorDimension "dimension1"
- }
- f2 {
- flavorDimension "dimension1"
- }
-
- fa {
- flavorDimension "dimension2"
- }
- fb {
- flavorDimension "dimension2"
- }
- fc {
- flavorDimension "dimension2"
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(true /*force*/)
-
- assertEquals(5, plugin.variantManager.productFlavors.size())
-
- List<BaseVariantData> variants = plugin.variantManager.variantDataList
- assertEquals(countVariants(appVariants: 12, unitTests: 12, androidTests: 6), variants.size())
-
- String[] variantNames = [
- "f1FaDebug",
- "f1FbDebug",
- "f1FcDebug",
- "f2FaDebug",
- "f2FbDebug",
- "f2FcDebug",
- "f1FaRelease",
- "f1FbRelease",
- "f1FcRelease",
- "f2FaRelease",
- "f2FbRelease",
- "f2FcRelease",
- "f1FaDebugAndroidTest",
- "f1FbDebugAndroidTest",
- "f1FcDebugAndroidTest",
- "f2FaDebugAndroidTest",
- "f2FbDebugAndroidTest",
- "f2FcDebugAndroidTest"];
-
- for (String variantName : variantNames) {
- findNamedItem(variants, variantName, "variantData");
- }
- }
-
- public void testSigningConfigs() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
-
- signingConfigs {
- one {
- storeFile project.file("a1")
- storePassword "b1"
- keyAlias "c1"
- keyPassword "d1"
- }
- two {
- storeFile project.file("a2")
- storePassword "b2"
- keyAlias "c2"
- keyPassword "d2"
- }
- three {
- storeFile project.file("a3")
- storePassword "b3"
- keyAlias "c3"
- keyPassword "d3"
- }
- }
-
- defaultConfig {
- versionCode 1
- versionName "2.0"
- minSdkVersion 2
- targetSdkVersion 3
- }
-
- buildTypes {
- debug {
- }
- staging {
- }
- release {
- signingConfig owner.signingConfigs.three
- }
- }
-
- productFlavors {
- flavor1 {
- }
- flavor2 {
- signingConfig owner.signingConfigs.one
- }
- }
-
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- plugin.createAndroidTasks(true /*force*/)
-
- List<BaseVariantData> variants = plugin.variantManager.variantDataList
- assertEquals(countVariants(appVariants: 6, unitTests: 6, androidTests: 2), variants.size())
-
- BaseVariantData variant
- SigningConfig signingConfig
-
- variant = findNamedItem(variants, "flavor1Debug", "variantData")
- signingConfig = variant.variantConfiguration.signingConfig
- assertNotNull(signingConfig)
- assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeFile?.absolutePath)
-
- variant = findNamedItem(variants, "flavor1Staging", "variantData")
- signingConfig = variant.variantConfiguration.signingConfig
- assertNull(signingConfig)
-
- variant = findNamedItem(variants, "flavor1Release", "variantData")
- signingConfig = variant.variantConfiguration.signingConfig
- assertNotNull(signingConfig)
- assertEquals(new File(project.projectDir, "a3"), signingConfig.storeFile)
-
- variant = findNamedItem(variants, "flavor2Debug", "variantData")
- signingConfig = variant.variantConfiguration.signingConfig
- assertNotNull(signingConfig)
- assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeFile?.absolutePath)
-
- variant = findNamedItem(variants, "flavor2Staging", "variantData")
- signingConfig = variant.variantConfiguration.signingConfig
- assertNotNull(signingConfig)
- assertEquals(new File(project.projectDir, "a1"), signingConfig.storeFile)
-
- variant = findNamedItem(variants, "flavor2Release", "variantData")
- signingConfig = variant.variantConfiguration.signingConfig
- assertNotNull(signingConfig)
- assertEquals(new File(project.projectDir, "a3"), signingConfig.storeFile)
- }
-
- /**
- * test that debug build type maps to the SigningConfig object as the signingConfig container
- * @throws Exception
- */
- public void testDebugSigningConfig() throws Exception {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
-
- signingConfigs {
- debug {
- storePassword = "foo"
- }
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- // check that the debug buildType has the updated debug signing config.
- DefaultBuildType buildType = plugin.variantManager.buildTypes.get(BuilderConstants.DEBUG).buildType
- SigningConfig signingConfig = buildType.signingConfig
- assertEquals(plugin.variantManager.signingConfigs.get(BuilderConstants.DEBUG), signingConfig)
- assertEquals("foo", signingConfig.storePassword)
- }
-
- public void testSigningConfigInitWith() throws Exception {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
-
- signingConfigs {
- foo.initWith(owner.signingConfigs.debug)
- }
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
-
- SigningConfig debugSC = plugin.variantManager.signingConfigs.get(BuilderConstants.DEBUG)
- SigningConfig fooSC = plugin.variantManager.signingConfigs.get("foo")
-
- assertNotNull(fooSC);
-
- assertEquals(debugSC.getStoreFile(), fooSC.getStoreFile());
- assertEquals(debugSC.getStorePassword(), fooSC.getStorePassword());
- assertEquals(debugSC.getKeyAlias(), fooSC.getKeyAlias());
- assertEquals(debugSC.getKeyPassword(), fooSC.getKeyPassword());
- }
-
- public void testPluginDetection() {
- Project project = ProjectBuilder.builder().withProjectDir(
- new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
-
- project.apply plugin: 'com.android.application'
- project.apply plugin: 'java'
-
- project.android {
- compileSdkVersion COMPILE_SDK_VERSION
- buildToolsVersion BUILD_TOOL_VERSION
- }
-
- AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
- Exception recordedException = null;
- try {
- plugin.createAndroidTasks(true /*force*/)
- } catch (Exception e) {
- recordedException = e;
- }
-
- assertNotNull(recordedException)
- assertEquals(BadPluginException.class, recordedException.getClass())
- }
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/build.gradle b/base/build-system/integration-test/build.gradle
deleted file mode 100644
index 36a3527..0000000
--- a/base/build-system/integration-test/build.gradle
+++ /dev/null
@@ -1,141 +0,0 @@
-apply plugin: 'groovy'
-apply plugin: 'jacoco'
-
-repositories {
- maven { url = uri(rootProject.cloneArtifacts.repository) }
-}
-
-dependencies {
- compile gradleApi()
- compile localGroovy()
- testCompile project(':base:builder-model')
- testCompile project(':base:builder')
- testCompile project(':base:sdk-common')
- testCompile 'com.google.truth:truth:0.26'
- testCompile 'org.jacoco:org.jacoco.agent:0.7.4.201502262128'
-
- // Add dependency on plugin code. Exclude transitive dependencies to avoid conflict due to
- // Groovy versions.
- testCompile(project(':base:gradle-core')) {
- transitive = false
- }
- testCompile(project(':base:gradle')) {
- transitive = false
- }
- testCompile(project(':base:gradle-experimental')) {
- transitive = false
- }
-}
-
-def testEnvironment = [
- PROJECT_BUILD_DIR: project.buildDir,
- CUSTOM_REPO: rootProject.file("../out/repo"),
- CUSTOM_GRADLE: System.env.CUSTOM_GRADLE ?: rootProject.ext.buildVersion,
- CUSTOM_EXPERIMENTAL_GRADLE: System.env.CUSTOM_EXPERIMENTAL_GRADLE ?:rootProject.ext.experimentalVersion,
- ANDROID_HOME: System.env.ANDROID_HOME,
- ANDROID_NDK_HOME: System.env.ANDROID_NDK_HOME,
- CUSTOM_BUILDTOOLS: System.env.CUSTOM_BUILDTOOLS,
- CUSTOM_JACK: System.env.CUSTOM_JACK,
- DEBUG_INNER_TEST: System.env.DEBUG_INNER_TEST,
- RECORD_SPANS: System.env.RECORD_SPANS,
-].findAll { it.value != null }
-
-// These tasks will not depend on publishLocal, so they will run integration
-// tests against whatever version of the plugin is in ../../../out/repo. This
-// allows us to run integration tests with different versions of Java, without
-// rebuilding the plugin.
-task testPrebuilts(type: Test)
-task connectedIntegrationTestPrebuilts(type: Test)
-
-configure([test, testPrebuilts]) {
- description =
- "Runs the project integration tests. This requires an SDK either from the Android " +
- "source tree, under out/..., or an env var ANDROID_HOME."
- systemProperties['jar.path'] = jar.archivePath
- environment = testEnvironment
-
- // Always run the task, when requested.
- outputs.upToDateWhen { false }
-
- forkEvery = 1
- maxParallelForks = Runtime.runtime.availableProcessors() / 2
-
- useJUnit {
- if (System.properties['test.includeCategories'] != null) {
- def categories = System.properties['test.includeCategories'].split(',')
- String defaultPackage = "com.android.build.gradle.integration.common.category."
- categories = categories.collect { it.charAt(0).isUpperCase() ? defaultPackage + it : it }
- includeCategories categories as String[]
- }
- excludeCategories "com.android.build.gradle.integration.common.category.DeviceTests"
- }
- exclude "com/android/build/gradle/integration/performance/**"
-}
-
-task connectedIntegrationTest(type: Test)
-
-configure([connectedIntegrationTest, connectedIntegrationTestPrebuilts]) {
- testClassesDir = sourceSets.test.output.classesDir
- classpath = sourceSets.test.runtimeClasspath
-
- description =
- "Runs the project integration tests with device tests. This requires an SDK either " +
- "from the Android source tree, under out/..., or an env var ANDROID_HOME " +
- "and a device."
- group = "verification"
- systemProperties['jar.path'] = jar.archivePath
- // Add to, rather than replace the environment, so that TEST_CLASSPATH_DEPENDENCY,
- // REMOTE_TEST_PROVIDER, ADDITIONAL_TEST_CUSTOM_REPO and any dependencies of the remote test
- // provider are present in the test environment only for these tests.
- environment testEnvironment
-
- // Always run the task, when requested.
- outputs.upToDateWhen { false }
-
- forkEvery= 1
- maxParallelForks = System.env.containsKey("REMOTE_TEST_PROVIDER") ? Runtime.runtime.availableProcessors() : 1
-
- useJUnit {
- includeCategories "com.android.build.gradle.integration.common.category.DeviceTests"
- }
- exclude "com/android/build/gradle/integration/performance/**"
-}
-
-task performanceTest(type: Test) {
- include "com/android/build/gradle/integration/performance/**"
-
- testClassesDir = sourceSets.test.output.classesDir
- classpath = sourceSets.test.runtimeClasspath
-
- description =
- "Runs the project performance tests. This requires an SDK either " +
- "from the Android source tree, under out/..., or an env var ANDROID_HOME."
- group = "verification"
- systemProperties['jar.path'] = jar.archivePath
- environment = testEnvironment
-
- reports {
- junitXml.destination "${project.buildDir}/perf-results"
- }
-}
-
-test.dependsOn ':publishLocal'
-connectedIntegrationTest.dependsOn ':publishLocal'
-performanceTest.dependsOn ':publishLocal'
-
-jacocoTestReport {
- sourceSets project(':base:gradle-experimental').sourceSets.main
- sourceSets project(':base:gradle').sourceSets.main
- sourceSets project(':base:gradle-core').sourceSets.main
- sourceSets project(':base:builder').sourceSets.main
- sourceSets project(':base:builder-model').sourceSets.main
- sourceSets project(':base:builder-test-api').sourceSets.main
-}
-
-// Due to memory constraints, apply jacoco only when jacocoTestReport is invoked. Make sure to
-// rerun tests when generating report jacoco.
-gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
- if (taskGraph.hasTask(jacocoTestReport)) {
- test.environment("ATTACH_JACOCO_AGENT", "yes")
- }
-}
diff --git a/base/build-system/integration-test/integration-test.iml b/base/build-system/integration-test/integration-test.iml
deleted file mode 100644
index 7438448..0000000
--- a/base/build-system/integration-test/integration-test.iml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/test/groovy" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="groovy" level="project" />
- <orderEntry type="module" module-name="builder" />
- <orderEntry type="module" module-name="gradle" />
- <orderEntry type="module" module-name="sdk-common-base" />
- <orderEntry type="library" name="jacoco" level="project" />
- <orderEntry type="library" name="truth" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AaptOptionsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AaptOptionsTest.groovy
deleted file mode 100644
index 49bacce..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AaptOptionsTest.groovy
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * General Model tests
- */
- at CompileStatic
-class AaptOptionsTest {
-
- public static final AndroidTestApp helloWorldApp = new HelloWorldApp();
- static {
- helloWorldApp.addFile(new TestSourceFile("src/main/res/raw", "data", "test"));
- }
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(helloWorldApp)
- .create()
-
- @Before
- public void setUp() {
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
-}
-"""
- }
-
- @Test
- public void "test aaptOptions flags"() {
- project.getBuildFile() << """
-android {
- aaptOptions {
- additionalParameters "--ignore-assets", "!data"
- }
-}
-"""
- project.execute("assembleDebug")
- assertThatZip(project.getApk("debug")).doesNotContain("res/raw/data")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AbiPureSplits.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AbiPureSplits.groovy
deleted file mode 100644
index c88394e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AbiPureSplits.groovy
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.OutputFile
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidArtifactOutput
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.google.common.collect.Sets
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static com.android.builder.core.BuilderConstants.DEBUG
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * Test drive for the abiPureSplits samples test.
- */
-class AbiPureSplits {
- static AndroidProject model
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("abiPureSplits")
- .addGradleProperties("android.useDeprecatedNdk=true")
- .create()
-
- @BeforeClass
- static void setup() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- public void "test abi pure splits"() throws Exception {
-
- // Load the custom model for the project
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 2 , variants.size())
-
- // get the main artifact of the debug artifact
- Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
- assertNotNull("debug Variant null-check", debugVariant)
- AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
- assertNotNull("Debug main info null-check", debugMainArtifact)
-
- // get the outputs.
- Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
- assertNotNull(debugOutputs)
-
- // build a set of expected outputs
- Set<String> expected = Sets.newHashSetWithExpectedSize(5)
- expected.add("mips")
- expected.add("x86")
- expected.add("armeabi-v7a")
-
- assertEquals(1, debugOutputs.size())
- AndroidArtifactOutput output = debugOutputs.iterator().next()
- assertEquals(4, output.getOutputs().size())
- for (OutputFile outputFile : output.getOutputs()) {
- String filter = ModelHelper.getFilter(outputFile, OutputFile.ABI)
- assertEquals(filter == null ? OutputFile.MAIN : OutputFile.SPLIT,
- outputFile.getOutputType())
-
- // with pure splits, all split have the same version code.
- assertEquals(123, output.getVersionCode())
- if (filter != null) {
- expected.remove(filter)
-
- if (outputFile.getFilterTypes().contains(OutputFile.ABI)) {
- // if this is an ABI split, ensure the .so file presence (and only one)
- assertThatZip(outputFile.getOutputFile()).entries("lib/.*")
- .containsExactly("lib/" + filter + "/libhello-jni.so");
- }
-
- } else {
- // main file should not have any lib/ entries.
- assertThatZip(outputFile.getOutputFile()).entries("lib/.*").isEmpty()
- // assert that our resources got packaged in the main file.
- assertThatZip(outputFile.getOutputFile()).entries("res/.*").hasSize(5)
- }
- }
-
- // this checks we didn't miss any expected output.
- assertTrue(expected.isEmpty())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidManifestInTestTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidManifestInTestTest.groovy
deleted file mode 100644
index ef7d49d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidManifestInTestTest.groovy
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ApkHelper
-import com.android.build.gradle.integration.common.utils.SdkHelper
-import com.android.ide.common.process.ProcessInfoBuilder
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.fail
-/**
- * Assemble tests for androidManifestInTest.
- */
- at CompileStatic
-class AndroidManifestInTestTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("androidManifestInTest")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebugAndroidTest")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- public void testUserProvidedTestAndroidManifest() throws Exception {
- File testApk = project.getApk("debug", "androidTest", "unaligned")
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder()
- builder.setExecutable(SdkHelper.getAapt())
-
- builder.addArgs("l", "-a", testApk.getPath())
-
- List<String> output = ApkHelper.runAndGetOutput(builder.createProcess())
-
- System.out.println("Beginning dump");
- boolean foundPermission = false;
- boolean foundMetadata = false;
- for (String line : output) {
- if (line.contains("foo.permission-group.COST_MONEY")) {
- foundPermission = true
- }
- if (line.contains("meta-data")) {
- foundMetadata = true
- }
- }
- if (!foundPermission) {
- fail("Could not find user-specified permission group.")
- }
- if (!foundMetadata) {
- fail("Could not find meta-data under instrumentation ")
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidTestResourcesTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidTestResourcesTest.groovy
deleted file mode 100644
index 2f8918e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidTestResourcesTest.groovy
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.android.builder.model.AndroidProject
-import com.google.common.base.Joiner
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static org.junit.Assert.assertTrue
-
-/**
- * Check resources in androidTest are available in the generated R.java.
- */
- at CompileStatic
-class AndroidTestResourcesTest {
- private static AndroidTestApp testApp = new HelloWorldApp()
- static {
-
- testApp.addFile(new TestSourceFile("src/androidTest/res/layout", "test_layout_1.xml", """\
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- <TextView android:id="@+id/test_layout_1_textview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello, I am a TextView" />
- </LinearLayout>
- """.stripIndent()))
-
- // This class exists to prevent the resource from being automatically removed,
- // if we start filtering test resources by default.
- testApp.addFile(new TestSourceFile("src/androidTest/java/com/example/helloworld",
- "HelloWorldResourceTest.java", """\
- package com.example.helloworld;
- import android.test.ActivityInstrumentationTestCase2;
- import android.test.suitebuilder.annotation.MediumTest;
- import android.widget.TextView;
-
- public class HelloWorldResourceTest extends
- ActivityInstrumentationTestCase2<HelloWorld> {
- private TextView mTextView;
-
- public HelloWorldResourceTest() {
- super("com.example.helloworld", HelloWorld.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final HelloWorld a = getActivity();
- mTextView = (TextView) a.findViewById(
- com.example.helloworld.test.R.id.test_layout_1_textview);
- }
-
- @MediumTest
- public void testPreconditions() {
- assertNull("Shouldn't find test_layout_1_textview.", mTextView);
- }
- }
- """.stripIndent()))
- }
-
- @ClassRule
- public static GradleTestProject appProject = GradleTestProject.builder()
- .withName("application")
- .fromTestApp(testApp)
- .create()
-
- @ClassRule
- public static GradleTestProject libProject = GradleTestProject.builder()
- .withName("library")
- .fromTestApp(testApp)
- .create()
-
- /**
- * Use the test app to create an application and a library project.
- */
- @BeforeClass
- static void setUp() {
- appProject.getBuildFile() << """
- apply plugin: 'com.android.application'
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- """.stripIndent()
- appProject.execute("assembleDebugAndroidTest")
-
- libProject.getBuildFile() << """
- apply plugin: 'com.android.library'
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- """.stripIndent()
- libProject.execute("assembleDebugAndroidTest")
- }
-
- @AfterClass
- static void cleanUp() {
- appProject = null
- libProject = null
- testApp = null
- }
-
- @Test
- public void "check test layout file listed in test R.java when compiled as an application"() {
- checkLayoutInR(appProject)
- }
-
- @Test
- public void "check test layout file listed in test R.java when compiled as a library"() {
- checkLayoutInR(libProject)
- }
-
- @Test
- @Category(DeviceTests.class)
- public void "check test layout can be used in device tests"() {
- appProject.executeConnectedCheck()
- }
-
-
- private void checkLayoutInR(GradleTestProject fixture) {
- def rFile = fixture.file(Joiner.on(File.separatorChar).join(
- "build", AndroidProject.FD_GENERATED, "source", "r",
- "androidTest", "debug", "com", "example", "helloworld", "test", "R.java"))
- assertTrue("Should have generated R file", rFile.exists())
- def rFileContents = rFile.getText("UTF-8")
-
- assertTrue("Test/debug R file [${rFile.absolutePath}] should contain test_layout_1",
- rFileContents.contains('test_layout_1'))
- assertTrue("Test/debug R file [${rFile.absolutePath}] " +
- "should contain test_layout_1_textview",
- rFileContents.contains('test_layout_1_textview'))
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdInLibsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdInLibsTest.groovy
deleted file mode 100644
index 9b5dcaa..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdInLibsTest.groovy
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ApkHelper
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidArtifactOutput
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-
-/**
- * Tests for @{applicationId} placeholder presence in library manifest files.
- * Such placeholders should be left intact until the library is merged into a consuming application
- * with a known application Id.
- */
- at CompileStatic
-class ApplicationIdInLibsTest {
- static Map<String, AndroidProject> models
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("applicationIdInLibsTest")
- .create()
-
- @BeforeClass
- static void setup() {
- models = project.executeAndReturnMultiModel("clean",
- ":examplelibrary:generateDebugAndroidTestSources", "app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- public void "test library placeholder substitution in final apk"() throws Exception {
-
- // Load the custom model for the project
- Collection<Variant> variants = models.get(":app").getVariants()
- assertEquals("Variant Count", 2 , variants.size())
-
- // get the main artifact of the debug artifact
- Variant debugVariant = ModelHelper.getVariant(variants, "flavorDebug")
- assertNotNull("debug Variant null-check", debugVariant)
- AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
- assertNotNull("Debug main info null-check", debugMainArtifact)
-
- // get the outputs.
- Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
- assertNotNull(debugOutputs)
-
- assertEquals(1, debugOutputs.size())
- AndroidArtifactOutput output = debugOutputs.iterator().next()
- assertEquals(1, output.getOutputs().size())
-
- List<String> apkBadging =
- ApkHelper.getApkBadging(output.getOutputs().iterator().next().getOutputFile());
-
- for (String line : apkBadging) {
- if (line.contains("uses-permission: name=" +
- "'com.example.manifest_merger_example.flavor.permission.C2D_MESSAGE'")) {
- return;
- }
- }
- Assert.fail("failed to find the permission with the right placeholder substitution.")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArchivesBaseNameTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArchivesBaseNameTest.groovy
deleted file mode 100644
index 707d562..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArchivesBaseNameTest.groovy
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-/**
- * Ensures that archivesBaseName setting on android project is used when choosing the apk file
- * names
- */
- at CompileStatic
-class ArchivesBaseNameTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("basic")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-
-android {
- archivesBaseName = 'random_apk_name'
-}
-"""
- models = project.getAllModels()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check model failed to load"() {
- File outputFile = models.get(":").getVariants().iterator().next()
- .getMainArtifact()
- .getOutputs().iterator().next()
- .getMainOutputFile()
- .getOutputFile()
-
- assertThat(outputFile.getName().startsWith("random_apk_name"))
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArtifactApiTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArtifactApiTest.groovy
deleted file mode 100644
index 64019fe..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArtifactApiTest.groovy
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.ArtifactMetaData
-import com.android.builder.model.BuildTypeContainer
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaArtifact
-import com.android.builder.model.ProductFlavorContainer
-import com.android.builder.model.SourceProvider
-import com.android.builder.model.SourceProviderContainer
-import com.android.builder.model.Variant
-import com.android.utils.FileUtils
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static com.google.common.truth.Truth.assertThat
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-/**
- * Assemble tests for artifactApi.
- */
- at CompileStatic
-class ArtifactApiTest {
- // Unit test variants produce an extra Java artifact.
- private static final int DEFAULT_EXTRA_JAVA_ARTIFACTS = 1
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("artifactApi")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.getSingleModel()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check metadata info in model"() {
- // check the Artifact Meta Data
- Collection<ArtifactMetaData> extraArtifacts = model.getExtraArtifacts()
- assertNotNull("Extra artifact collection null-check", extraArtifacts)
- assertThat(extraArtifacts).hasSize(DEFAULT_EXTRA_JAVA_ARTIFACTS + 2)
-
- assertNotNull("instrument test metadata null-check",
- ModelHelper.getArtifactMetaData(extraArtifacts, ARTIFACT_ANDROID_TEST))
-
- // get the custom one.
- ArtifactMetaData extraArtifactMetaData = ModelHelper.getArtifactMetaData(
- extraArtifacts, "__test__")
- assertNotNull("custom extra metadata null-check", extraArtifactMetaData)
- assertFalse("custom extra meta data is Test check", extraArtifactMetaData.isTest())
- assertEquals("custom extra meta data type check", ArtifactMetaData.TYPE_JAVA,
- extraArtifactMetaData.getType())
- }
-
- @Test
- void "check build types contain extra source provider artifact is in model"() {
- // check the extra source provider on the build Types.
- for (BuildTypeContainer btContainer : model.getBuildTypes()) {
- String name = btContainer.getBuildType().getName()
- Collection<SourceProviderContainer> extraSourceProviderContainers = btContainer.getExtraSourceProviders()
- assertNotNull(
- "Extra source provider containers for build type '" + name + "' null-check",
- extraSourceProviderContainers)
- assertEquals(
- "Extra source provider containers for build type size '" + name + "' check",
- DEFAULT_EXTRA_JAVA_ARTIFACTS + 1,
- extraSourceProviderContainers.size())
-
- SourceProviderContainer sourceProviderContainer = extraSourceProviderContainers.iterator().next()
- assertNotNull(
- "Extra artifact source provider for " + name + " null check",
- sourceProviderContainer)
-
- assertEquals(
- "Extra artifact source provider for " + name + " name check",
- "__test__",
- sourceProviderContainer.getArtifactName())
-
- assertEquals(
- "Extra artifact source provider for " + name + " value check",
- "buildType:" + name,
- sourceProviderContainer.getSourceProvider().getManifestFile().getPath())
- }
- }
-
- @Test
- void "check product flavors contain extra source provider artifact is in model"() {
- // check the extra source provider on the product flavors.
- for (ProductFlavorContainer pfContainer : model.getProductFlavors()) {
- String name = pfContainer.getProductFlavor().getName()
- Collection<SourceProviderContainer> extraSourceProviderContainers = pfContainer.
- getExtraSourceProviders()
- assertNotNull(
- "Extra source provider container for product flavor '" + name + "' null-check",
- extraSourceProviderContainers)
- assertEquals(
- "Extra artifact source provider container for product flavor size '" + name +
- "' check",
- 3, // unit test, android test, extra provider from the API
- extraSourceProviderContainers.size())
-
- assertNotNull(
- "Extra source provider container for product flavor '" + name +
- "': instTest check",
- ModelHelper.getSourceProviderContainer(extraSourceProviderContainers,
- ARTIFACT_ANDROID_TEST))
-
-
- SourceProviderContainer sourceProviderContainer = ModelHelper.
- getSourceProviderContainer(
- extraSourceProviderContainers, "__test__")
- assertNotNull(
- "Custom source provider container for " + name + " null check",
- sourceProviderContainer)
-
- assertEquals(
- "Custom artifact source provider for " + name + " name check",
- "__test__",
- sourceProviderContainer.getArtifactName())
-
- assertEquals(
- "Extra artifact source provider for " + name + " value check",
- "productFlavor:" + name,
- sourceProviderContainer.getSourceProvider().getManifestFile().getPath())
- }
- }
-
- @Test
- void "check extra artifact is in variants"() {
- for (Variant variant : model.getVariants()) {
- String name = variant.getName()
- Collection<JavaArtifact> javaArtifacts = variant.getExtraJavaArtifacts()
- assertThat(javaArtifacts).hasSize(DEFAULT_EXTRA_JAVA_ARTIFACTS + 1)
- JavaArtifact javaArtifact = javaArtifacts.find {it.name == "__test__"}
- assertEquals("assemble:" + name, javaArtifact.getAssembleTaskName())
- assertEquals("compile:" + name, javaArtifact.getCompileTaskName())
- assertEquals(new File("classesFolder:" + name), javaArtifact.getClassesFolder())
-
- SourceProvider variantSourceProvider = javaArtifact.getVariantSourceProvider()
- assertNotNull(variantSourceProvider)
- assertEquals("provider:" + name, variantSourceProvider.getManifestFile().getPath())
-
- Dependencies deps = javaArtifact.getDependencies()
- assertNotNull("java artifact deps null-check", deps)
- assertFalse(deps.getJavaLibraries().isEmpty())
- }
- }
-
- @Test
- public void backwardsCompatible() throws Exception {
- // ATTENTION Author and Reviewers - please make sure required changes to the build file
- // are backwards compatible before updating this test.
- assertThat(FileUtils.sha1(project.file("build.gradle")))
- .isEqualTo("cf6fa23a32f342718b1f342fc97846f56665a155")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoDensitySplitTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoDensitySplitTest.groovy
deleted file mode 100644
index 0b5932e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoDensitySplitTest.groovy
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidArtifactOutput
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static org.junit.Assert.assertEquals
-
-/**
- * MultiAPK test where densities are obtained automatically.
- */
- at CompileStatic
-class AutoDensitySplitTest {
- static AndroidProject model
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("densitySplit")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """android {
- splits {
- density {
- enable true
- auto true
- compatibleScreens 'small', 'normal', 'large', 'xlarge'
- }
- }
- }"""
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void testPackaging() {
- for (Variant variant : model.getVariants()) {
- AndroidArtifact mainArtifact = variant.getMainArtifact()
- if (!variant.getBuildType().equalsIgnoreCase("Debug")) {
- continue
- }
- assertEquals(5, mainArtifact.getOutputs().size())
-
- File mdpiApk = project.getApk("mdpi", "debug")
- assertThatZip(mdpiApk).contains("res/drawable-mdpi-v4/other.png")
- }
- }
-
- @Test
- void "check version code in apk"() {
- File universalApk = project.getApk("universal", "debug")
- assertThatApk(universalApk).hasVersionCode(112)
- assertThatApk(universalApk).hasVersionName("version 112")
-
- File mdpiApk = project.getApk("mdpi", "debug")
- assertThatApk(mdpiApk).hasVersionCode(212)
- assertThatApk(mdpiApk).hasVersionName("version 212")
-
- File hdpiApk = project.getApk("hdpi", "debug")
- assertThatApk(hdpiApk).hasVersionCode(312)
- assertThatApk(hdpiApk).hasVersionName("version 312")
-
- File xhdpiApk = project.getApk("xhdpi", "debug")
- assertThatApk(xhdpiApk).hasVersionCode(412)
- assertThatApk(xhdpiApk).hasVersionName("version 412")
-
- File xxhdiApk = project.getApk("xxhdpi", "debug")
- assertThatApk(xxhdiApk).hasVersionCode(512)
- assertThatApk(xxhdiApk).hasVersionName("version 512")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest.groovy
deleted file mode 100644
index 7b41d85..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest.groovy
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.category.SmokeTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.build.gradle.integration.common.utils.SigningConfigHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.JavaCompileOptions
-import com.android.builder.model.SigningConfig
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.builder.core.BuilderConstants.DEBUG
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertNull
-import static org.junit.Assert.assertTrue
-
-/**
- * Assemble tests for basic.
- */
- at Category(SmokeTests.class)
- at CompileStatic
-class BasicTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("basic")
- .withoutNdk()
- .create()
-
- static public AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void report() {
- project.execute("androidDependencies", "signingReport")
- }
-
- @Test
- void basicModel() {
- assertFalse("Library Project", model.isLibrary())
- assertEquals("Compile Target", "android-21", model.getCompileTarget())
- assertFalse("Non empty bootclasspath", model.getBootClasspath().isEmpty())
-
- assertNotNull("aaptOptions not null", model.getAaptOptions())
- assertEquals("aaptOptions noCompress", 1, model.getAaptOptions().getNoCompress().size())
- assertTrue("aaptOptions noCompress", model.getAaptOptions().getNoCompress().contains("txt"))
- assertEquals(
- "aaptOptions ignoreAssetsPattern",
- "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~",
- model.getAaptOptions().getIgnoreAssets())
- assertFalse(
- "aaptOptions getFailOnMissingConfigEntry",
- model.getAaptOptions().getFailOnMissingConfigEntry())
-
- JavaCompileOptions javaCompileOptions = model.getJavaCompileOptions()
- // since source and target compatibility are not explicitly set in the build.gradle,
- // the default value should be the JDK version used to build against.
- assertEquals(System.getProperty("java.specification.version"),
- javaCompileOptions.getSourceCompatibility())
- assertEquals(System.getProperty("java.specification.version"),
- javaCompileOptions.getTargetCompatibility())
- assertEquals("UTF-8", javaCompileOptions.getEncoding())
- }
-
- @Test
- public void sourceProvidersModel() {
- ModelHelper.testDefaultSourceSets(model, project.getTestDir())
-
- // test the source provider for the artifacts
- for (Variant variant : model.getVariants()) {
- AndroidArtifact artifact = variant.getMainArtifact()
- assertNull(artifact.getVariantSourceProvider())
- assertNull(artifact.getMultiFlavorSourceProvider())
- }
- }
-
- @Test
- public void signingConfigsModel() {
- Collection<SigningConfig> signingConfigs = model.getSigningConfigs()
- assertNotNull("SigningConfigs null-check", signingConfigs)
- assertEquals("Number of signingConfig", 2, signingConfigs.size())
-
- SigningConfig debugSigningConfig = ModelHelper.getSigningConfig(signingConfigs, DEBUG)
- assertNotNull("debug signing config null-check", debugSigningConfig)
- new SigningConfigHelper(debugSigningConfig, DEBUG, true).test()
-
- SigningConfig mySigningConfig = ModelHelper.getSigningConfig(signingConfigs, "myConfig")
- assertNotNull("myConfig signing config null-check", mySigningConfig)
- new SigningConfigHelper(mySigningConfig, "myConfig", true)
- .setStoreFile(new File(project.getTestDir(), "debug.keystore"))
- .test()
- }
-
- @Test
- void "check custom signing"() throws Exception {
- Collection<Variant> variants = model.getVariants()
-
- for (Variant variant : variants) {
- // Release variant doesn't specify the signing config, so it should not be considered
- // signed.
- if (variant.getName().equals("release")) {
- assertFalse(variant.getMainArtifact().isSigned())
- }
-
- // customSigning is identical to release, but overrides the signing check.
- if (variant.getName().equals("customSigning")) {
- assertTrue(variant.getMainArtifact().isSigned())
- }
- }
- }
-
- @Test
- void "check debug and release output have different names"() {
- ModelHelper.compareDebugAndReleaseOutput(model)
- }
-
- @Test
- void "we don't fail on LICENSE.txt when packaging dependencies"() {
- project.execute("assembleAndroidTest")
- }
-
- @Test
- @Category(DeviceTests.class)
- void install() {
- GradleTestProject.assumeLocalDevice();
- project.execute("installDebug", "uninstallAll")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest2.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest2.groovy
deleted file mode 100644
index df29fac..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest2.groovy
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.build.gradle.integration.common.utils.ProductFlavorHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidArtifactOutput
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.BuildTypeContainer
-import com.android.builder.model.ClassField
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.MavenCoordinates
-import com.android.builder.model.ProductFlavor
-import com.android.builder.model.Variant
-import com.google.common.collect.Maps
-import com.google.common.collect.Sets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.builder.core.BuilderConstants.DEBUG
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertNull
-import static org.junit.Assert.assertTrue
-
-/**
- * Assemble tests for basic that loads the model but doesn't build.
- */
- at CompileStatic
-class BasicTest2 {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("basic")
- .create()
-
- static public AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- public void "check variant details"() throws Exception {
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 2 , variants.size())
-
- // debug variant
- Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
- assertNotNull("debug Variant null-check", debugVariant)
- new ProductFlavorHelper(debugVariant.getMergedFlavor(), "Debug Merged Flavor")
- .setVersionCode(12)
- .setVersionName("2.0")
- .setMinSdkVersion(16)
- .setTargetSdkVersion(16)
- .setTestInstrumentationRunner("android.test.InstrumentationTestRunner")
- .setTestHandleProfiling(Boolean.FALSE)
- .setTestFunctionalTest(null)
- .test()
-
- // debug variant, tested.
- AndroidArtifact debugMainInfo = debugVariant.getMainArtifact()
- assertNotNull("Debug main info null-check", debugMainInfo)
- assertEquals("Debug package name", "com.android.tests.basic.debug",
- debugMainInfo.getApplicationId())
- assertTrue("Debug signed check", debugMainInfo.isSigned())
- assertEquals("Debug signingConfig name", "myConfig", debugMainInfo.getSigningConfigName())
- assertEquals("Debug sourceGenTask", "generateDebugSources", debugMainInfo.getSourceGenTaskName())
- assertEquals("Debug compileTask", "compileDebugSources", debugMainInfo.getCompileTaskName())
-
- Collection<AndroidArtifactOutput> debugMainOutputs = debugMainInfo.getOutputs()
- assertNotNull("Debug main output null-check", debugMainOutputs)
- assertEquals("Debug main output size", 1, debugMainOutputs.size())
- AndroidArtifactOutput debugMainOutput = debugMainOutputs.iterator().next()
- assertNotNull(debugMainOutput)
- assertNotNull(debugMainOutput.getMainOutputFile())
- assertNotNull(debugMainOutput.getAssembleTaskName())
- assertNotNull(debugMainOutput.getGeneratedManifest())
- assertEquals(12, debugMainOutput.getVersionCode())
-
- // check debug dependencies
- Dependencies debugDependencies = debugMainInfo.getDependencies()
- assertNotNull(debugDependencies)
- Collection<AndroidLibrary> debugLibraries = debugDependencies.getLibraries()
- assertNotNull(debugLibraries)
- assertEquals(1, debugLibraries.size())
- assertTrue(debugDependencies.getProjects().isEmpty())
-
- AndroidLibrary androidLibrary = debugLibraries.iterator().next()
- assertNotNull(androidLibrary)
- assertNotNull(androidLibrary.getBundle())
- assertNotNull(androidLibrary.getFolder())
- MavenCoordinates coord = androidLibrary.getResolvedCoordinates()
- assertNotNull(coord)
- assertEquals("com.google.android.gms:play-services:3.1.36",
- coord.getGroupId() + ":" + coord.getArtifactId() + ":" + coord.getVersion())
-
-
- Collection<JavaLibrary> javaLibraries = debugDependencies.getJavaLibraries()
- assertNotNull(javaLibraries)
- assertEquals(2, javaLibraries.size())
-
- Set<String> javaLibs = Sets.newHashSet(
- "com.android.support:support-v13:13.0.0",
- "com.android.support:support-v4:13.0.0"
- )
-
- for (JavaLibrary javaLib : javaLibraries) {
- coord = javaLib.getResolvedCoordinates()
- assertNotNull(coord)
- String lib = coord.getGroupId() + ":" + coord.getArtifactId() + ":" + coord.getVersion()
- assertTrue(javaLibs.contains(lib))
- javaLibs.remove(lib)
- }
-
- // this variant is tested.
- Collection<AndroidArtifact> debugExtraAndroidArtifacts = debugVariant.getExtraAndroidArtifacts()
- AndroidArtifact debugTestInfo = ModelHelper.getAndroidArtifact(debugExtraAndroidArtifacts,
- ARTIFACT_ANDROID_TEST)
- assertNotNull("Test info null-check", debugTestInfo)
- assertEquals("Test package name", "com.android.tests.basic.debug.test",
- debugTestInfo.getApplicationId())
- assertTrue("Test signed check", debugTestInfo.isSigned())
- assertEquals("Test signingConfig name", "myConfig", debugTestInfo.getSigningConfigName())
- assertEquals("Test sourceGenTask", "generateDebugAndroidTestSources", debugTestInfo.getSourceGenTaskName())
- assertEquals("Test compileTask", "compileDebugAndroidTestSources", debugTestInfo.getCompileTaskName())
-
- Collection<File> generatedResFolders = debugTestInfo.getGeneratedResourceFolders()
- assertNotNull(generatedResFolders)
- // size 2 = rs output + resValue output
- assertEquals(2, generatedResFolders.size())
-
- Collection<AndroidArtifactOutput> debugTestOutputs = debugTestInfo.getOutputs()
- assertNotNull("Debug test output null-check", debugTestOutputs)
- assertEquals("Debug test output size", 1, debugTestOutputs.size())
- AndroidArtifactOutput debugTestOutput = debugTestOutputs.iterator().next()
- assertNotNull(debugTestOutput)
- assertNotNull(debugTestOutput.getMainOutputFile())
- assertNotNull(debugTestOutput.getAssembleTaskName())
- assertNotNull(debugTestOutput.getGeneratedManifest())
-
- // test the resValues and buildConfigFields.
- ProductFlavor defaultConfig = model.getDefaultConfig().getProductFlavor()
- Map<String, ClassField> buildConfigFields = defaultConfig.getBuildConfigFields()
- assertNotNull(buildConfigFields)
- assertEquals(2, buildConfigFields.size())
-
- assertEquals("true", buildConfigFields.get("DEFAULT").getValue())
- assertEquals("\"foo2\"", buildConfigFields.get("FOO").getValue())
-
- Map<String, ClassField> resValues = defaultConfig.getResValues()
- assertNotNull(resValues)
- assertEquals(1, resValues.size())
-
- assertEquals("foo", resValues.get("foo").getValue())
-
- // test on the debug build type.
- Collection<BuildTypeContainer> buildTypes = model.getBuildTypes()
- for (BuildTypeContainer buildTypeContainer : buildTypes) {
- if (buildTypeContainer.getBuildType().getName().equals(DEBUG)) {
- buildConfigFields = buildTypeContainer.getBuildType().getBuildConfigFields()
- assertNotNull(buildConfigFields)
- assertEquals(1, buildConfigFields.size())
-
- assertEquals("\"bar\"", buildConfigFields.get("FOO").getValue())
-
- resValues = buildTypeContainer.getBuildType().getResValues()
- assertNotNull(resValues)
- assertEquals(1, resValues.size())
-
- assertEquals("foo2", resValues.get("foo").getValue())
- }
- }
-
- // now test the merged flavor
- ProductFlavor mergedFlavor = debugVariant.getMergedFlavor()
-
- buildConfigFields = mergedFlavor.getBuildConfigFields()
- assertNotNull(buildConfigFields)
- assertEquals(2, buildConfigFields.size())
-
- assertEquals("true", buildConfigFields.get("DEFAULT").getValue())
- assertEquals("\"foo2\"", buildConfigFields.get("FOO").getValue())
-
- resValues = mergedFlavor.getResValues()
- assertNotNull(resValues)
- assertEquals(1, resValues.size())
-
- assertEquals("foo", resValues.get("foo").getValue())
-
-
- // release variant, not tested.
- Variant releaseVariant = ModelHelper.getVariant(variants, "release")
- assertNotNull("release Variant null-check", releaseVariant)
-
- AndroidArtifact relMainInfo = releaseVariant.getMainArtifact()
- assertNotNull("Release main info null-check", relMainInfo)
- assertEquals("Release package name", "com.android.tests.basic",
- relMainInfo.getApplicationId())
- assertFalse("Release signed check", relMainInfo.isSigned())
- assertNull("Release signingConfig name", relMainInfo.getSigningConfigName())
- assertEquals("Release sourceGenTask", "generateReleaseSources", relMainInfo.getSourceGenTaskName())
- assertEquals("Release javaCompileTask", "compileReleaseSources", relMainInfo.getCompileTaskName())
-
- Collection<AndroidArtifactOutput> relMainOutputs = relMainInfo.getOutputs()
- assertNotNull("Rel Main output null-check", relMainOutputs)
- assertEquals("Rel Main output size", 1, relMainOutputs.size())
- AndroidArtifactOutput relMainOutput = relMainOutputs.iterator().next()
- assertNotNull(relMainOutput)
- assertNotNull(relMainOutput.getMainOutputFile())
- assertNotNull(relMainOutput.getAssembleTaskName())
- assertNotNull(relMainOutput.getGeneratedManifest())
- assertEquals(13, relMainOutput.getVersionCode())
-
-
- Collection<AndroidArtifact> releaseExtraAndroidArtifacts = releaseVariant.getExtraAndroidArtifacts()
- AndroidArtifact relTestInfo = ModelHelper.getAndroidArtifact(releaseExtraAndroidArtifacts, ARTIFACT_ANDROID_TEST)
- assertNull("Release test info null-check", relTestInfo)
-
- // check release dependencies
- Dependencies releaseDependencies = relMainInfo.getDependencies()
- assertNotNull(releaseDependencies)
- Collection<AndroidLibrary> releaseLibraries = releaseDependencies.getLibraries()
- assertNotNull(releaseLibraries)
- assertEquals(3, releaseLibraries.size())
-
- // map for each aar we expect to find and how many local jars they each have.
- Map<String, Integer> aarLibs = Maps.newHashMapWithExpectedSize(3)
- aarLibs.put("com.android.support:support-v13:21.0.0", 1)
- aarLibs.put("com.android.support:support-v4:21.0.0", 1)
- aarLibs.put("com.google.android.gms:play-services:3.1.36", 0)
- for (AndroidLibrary androidLib : releaseLibraries) {
- assertNotNull(androidLib.getBundle())
- assertNotNull(androidLib.getFolder())
- coord = androidLib.getResolvedCoordinates()
- assertNotNull(coord)
- String lib = coord.getGroupId() + ":" + coord.getArtifactId() + ":" + coord.getVersion()
-
- Integer localJarCount = aarLibs.get(lib)
- assertNotNull("Check presense of " + lib, localJarCount)
- assertEquals("Check local jar count for " + lib,
- localJarCount.intValue(), androidLib.getLocalJars().size())
- System.out.println(">>" + androidLib.getLocalJars())
- aarLibs.remove(lib)
- }
-
- assertTrue("check for missing libs", aarLibs.isEmpty())
- }
-
-
- @Test
- @Category(DeviceTests.class)
- void install() {
- GradleTestProject.assumeLocalDevice();
- project.execute("installDebug", "uninstallAll")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BootClasspathTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BootClasspathTest.groovy
deleted file mode 100644
index 54e6e2d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BootClasspathTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * Test BaseExtension.getBootClasspath can be use before afterEvaluate.
- */
- at CompileStatic
-class BootClasspathTest {
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-task checkBootClasspath {
- assert android.getBootClasspath() != null
-}
-"""
- }
-
- @Test
- public void "check getBootClasspath can be called"() {
- project.execute("checkBootClasspath")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildConfigTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildConfigTest.groovy
deleted file mode 100644
index 30eec74..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildConfigTest.groovy
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.ClassField
-import com.android.builder.model.Variant
-import com.google.common.base.Charsets
-import com.google.common.collect.Maps
-import com.google.common.io.Files
-import groovy.transform.CompileStatic
-import org.junit.Assert
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-
-/**
- * Test for BuildConfig field declared in build type, flavors, and variant and how they
- * override each other
- */
- at CompileStatic
-class BuildConfigTest {
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- private static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
- apply plugin: 'com.android.application'
-
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- defaultConfig {
- buildConfigField "int", "VALUE_DEFAULT", "1"
- buildConfigField "int", "VALUE_DEBUG", "1"
- buildConfigField "int", "VALUE_FLAVOR", "1"
- buildConfigField "int", "VALUE_VARIANT", "1"
- }
-
- buildTypes {
- debug {
- buildConfigField "int", "VALUE_DEBUG", "100"
- buildConfigField "int", "VALUE_VARIANT", "100"
- }
- }
-
- productFlavors {
- flavor1 {
- buildConfigField "int", "VALUE_DEBUG", "10"
- buildConfigField "int", "VALUE_FLAVOR", "10"
- buildConfigField "int", "VALUE_VARIANT", "10"
- }
- flavor2 {
- buildConfigField "int", "VALUE_DEBUG", "20"
- buildConfigField "int", "VALUE_FLAVOR", "20"
- buildConfigField "int", "VALUE_VARIANT", "20"
- }
- }
-
- applicationVariants.all { variant ->
- if (variant.buildType.name == "debug") {
- variant.buildConfigField "int", "VALUE_VARIANT", "1000"
- }
- }
- }
- """.stripIndent()
-
- model = project.executeAndReturnModel(
- 'clean',
- 'generateFlavor1DebugBuildConfig',
- 'generateFlavor1ReleaseBuildConfig',
- 'generateFlavor2DebugBuildConfig',
- 'generateFlavor2ReleaseBuildConfig')
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void builFlavor1Debug() {
- String expected =
-"""/**
- * Automatically generated file. DO NOT MODIFY
- */
-package com.example.helloworld;
-
-public final class BuildConfig {
- public static final boolean DEBUG = Boolean.parseBoolean("true");
- public static final String APPLICATION_ID = "com.example.helloworld";
- public static final String BUILD_TYPE = "debug";
- public static final String FLAVOR = "flavor1";
- public static final int VERSION_CODE = 1;
- public static final String VERSION_NAME = "1.0";
- // Fields from the variant
- public static final int VALUE_VARIANT = 1000;
- // Fields from build type: debug
- public static final int VALUE_DEBUG = 100;
- // Fields from product flavor: flavor1
- public static final int VALUE_FLAVOR = 10;
- // Fields from default config.
- public static final int VALUE_DEFAULT = 1;
-}
-"""
- doCheckBuildConfig(expected, 'flavor1/debug')
- }
-
- @Test
- void modelFlavor1Debug() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '10')
- map.put('VALUE_DEBUG', '100')
- map.put('VALUE_VARIANT', '1000')
- checkVariant(model.getVariants(), 'flavor1Debug', map)
- }
-
- @Test
- void buildFlavor2Debug() {
- String expected =
-"""/**
- * Automatically generated file. DO NOT MODIFY
- */
-package com.example.helloworld;
-
-public final class BuildConfig {
- public static final boolean DEBUG = Boolean.parseBoolean("true");
- public static final String APPLICATION_ID = "com.example.helloworld";
- public static final String BUILD_TYPE = "debug";
- public static final String FLAVOR = "flavor2";
- public static final int VERSION_CODE = 1;
- public static final String VERSION_NAME = "1.0";
- // Fields from the variant
- public static final int VALUE_VARIANT = 1000;
- // Fields from build type: debug
- public static final int VALUE_DEBUG = 100;
- // Fields from product flavor: flavor2
- public static final int VALUE_FLAVOR = 20;
- // Fields from default config.
- public static final int VALUE_DEFAULT = 1;
-}
-"""
- doCheckBuildConfig(expected, 'flavor2/debug')
- }
-
- @Test
- void modelFlavor2Debug() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '20')
- map.put('VALUE_DEBUG', '100')
- map.put('VALUE_VARIANT', '1000')
- checkVariant(model.getVariants(), 'flavor2Debug', map)
- }
-
- @Test
- void buildFlavor1Release() {
- String expected =
- """/**
- * Automatically generated file. DO NOT MODIFY
- */
-package com.example.helloworld;
-
-public final class BuildConfig {
- public static final boolean DEBUG = false;
- public static final String APPLICATION_ID = "com.example.helloworld";
- public static final String BUILD_TYPE = "release";
- public static final String FLAVOR = "flavor1";
- public static final int VERSION_CODE = 1;
- public static final String VERSION_NAME = "1.0";
- // Fields from product flavor: flavor1
- public static final int VALUE_DEBUG = 10;
- public static final int VALUE_FLAVOR = 10;
- public static final int VALUE_VARIANT = 10;
- // Fields from default config.
- public static final int VALUE_DEFAULT = 1;
-}
-"""
- doCheckBuildConfig(expected, 'flavor1/release')
- }
-
- @Test
- void modelFlavor1Release() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '10')
- map.put('VALUE_DEBUG', '10')
- map.put('VALUE_VARIANT', '10')
- checkVariant(model.getVariants(), 'flavor1Release', map)
- }
-
- @Test
- void buildFlavor2Release() {
- String expected =
- """/**
- * Automatically generated file. DO NOT MODIFY
- */
-package com.example.helloworld;
-
-public final class BuildConfig {
- public static final boolean DEBUG = false;
- public static final String APPLICATION_ID = "com.example.helloworld";
- public static final String BUILD_TYPE = "release";
- public static final String FLAVOR = "flavor2";
- public static final int VERSION_CODE = 1;
- public static final String VERSION_NAME = "1.0";
- // Fields from product flavor: flavor2
- public static final int VALUE_DEBUG = 20;
- public static final int VALUE_FLAVOR = 20;
- public static final int VALUE_VARIANT = 20;
- // Fields from default config.
- public static final int VALUE_DEFAULT = 1;
-}
-"""
- doCheckBuildConfig(expected, 'flavor2/release')
- }
-
- @Test
- void modelFlavor2Release() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '20')
- map.put('VALUE_DEBUG', '20')
- map.put('VALUE_VARIANT', '20')
- checkVariant(model.getVariants(), 'flavor2Release', map)
- }
-
- private static void doCheckBuildConfig(@NonNull String expected, @NonNull String variantDir) {
- checkBuildConfig(project, expected, variantDir)
- }
-
- static void checkBuildConfig(
- @NonNull GradleTestProject project,
- @NonNull String expected,
- @NonNull String variantDir) {
- File outputFile = new File(project.getTestDir(),
- "build/generated/source/buildConfig/$variantDir/com/example/helloworld/BuildConfig.java")
- Assert.assertTrue("Missing file: " + outputFile, outputFile.isFile())
- assertEquals(expected, Files.asByteSource(outputFile).asCharSource(Charsets.UTF_8).read())
- }
-
- private static void checkVariant(
- @NonNull Collection<Variant> variants,
- @NonNull String variantName,
- @Nullable Map<String, String> valueMap) {
- Variant variant = ModelHelper.findVariantByName(variants, variantName)
- assertNotNull("${variantName} variant null-check", variant)
-
- AndroidArtifact artifact = variant.getMainArtifact()
- assertNotNull("${variantName} main artifact null-check", artifact)
-
- Map<String, ClassField> value = artifact.getBuildConfigFields()
- assertNotNull(value)
-
- // check the map against the expected one.
- assertEquals(valueMap.keySet(), value.keySet())
- for (String key : valueMap.keySet()) {
- ClassField field = value.get(key)
- assertNotNull("${variantName}: expected field ${key}", field)
- assertEquals(
- "${variantName}: check Value of ${key}",
- valueMap.get(key),
- field.getValue())
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildToolsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildToolsTest.groovy
deleted file mode 100644
index ac203e4..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildToolsTest.groovy
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.google.common.collect.ImmutableList
-import com.google.common.collect.Sets
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-
-import static com.google.common.truth.Truth.assert_
-
- at CompileStatic
-/**
- * Tests to ensure that changing the build tools version in the build.gradle will trigger
- * re-execution of some tasks even if no source file change was detected.
- */
-class BuildToolsTest {
-
- private static final Pattern UP_TO_DATE_PATTERN = ~/:(\S+)\s+UP-TO-DATE/
-
- private static final Pattern INPUT_CHANGED_PATTERN =
- ~/Value of input property 'buildToolsVersion' has changed for task ':(\S+)'/
-
- private static final String[] COMMON_TASKS = [
- "compileDebugAidl", "compileDebugRenderscript",
- "mergeDebugResources", "processDebugResources",
- "compileReleaseAidl", "compileReleaseRenderscript",
- "mergeReleaseResources", "processReleaseResources"
- ]
-
- private static final List<String> JAVAC_TASKS = ImmutableList.builder().add(COMMON_TASKS)
- .add("preDexDebug").add("dexDebug").add("preDexRelease").add("dexRelease").build()
- private static final List<String> JACK_TASKS = ImmutableList.builder().add(COMMON_TASKS)
- .add("jillDebugRuntimeLibraries").add("jillDebugPackagedLibraries")
- .add("jillReleaseRuntimeLibraries").add("jillReleasePackagedLibraries").build()
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .captureStdOut(true)
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-"""
- }
-
- @Test
- public void nullBuild() {
- project.execute("assemble")
- project.stdout.reset()
- project.execute("assemble")
-
- Set<String> skippedTasks = getTasksMatching(UP_TO_DATE_PATTERN, project.stdout)
- assert_().withFailureMessage("Expecting tasks to be UP-TO-DATE").that(skippedTasks)
- .containsAllIn(GradleTestProject.USE_JACK ? JACK_TASKS : JAVAC_TASKS)
- }
-
- @Test
- public void invalidateBuildTools() {
- project.execute("assemble")
- // Change our build tools version to 22.0.1 unless it is already the current version,
- // in that case, downgrade to 21.1.2.
- // The point is, change the build tools version from what it was when the "assemble" task
- // was executed right before this comment.
- String newBuildToolVersion =
- GradleTestProject.DEFAULT_BUILD_TOOL_VERSION.contentEquals("22.0.1") ?
- "21.1.2" : "22.0.1"
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion '$newBuildToolVersion'
-}
-"""
-
- project.stdout.reset()
- project.execute("assemble")
- Set<String> affectedTasks = getTasksMatching(INPUT_CHANGED_PATTERN, project.stdout)
- assert_().withFailureMessage("Expecting tasks to be invalidated").that(affectedTasks)
- .containsAllIn(GradleTestProject.USE_JACK ? JACK_TASKS : JAVAC_TASKS)
- }
-
- private static Set<String> getTasksMatching(Pattern pattern, ByteArrayOutputStream output) {
- Set<String> result = Sets.newHashSet()
- Matcher matcher = (output.toString("UTF-8") =~ pattern)
- while (matcher.find()) {
- result.add(matcher.group(1))
- }
- result
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitInLTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitInLTest.groovy
deleted file mode 100644
index 38e7a28..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitInLTest.groovy
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.OutputFile
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidArtifactOutput
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.google.common.collect.Sets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.builder.core.BuilderConstants.DEBUG
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * Assemble tests for class densitySplitInL
- .
- */
-class DensitySplitInLTest {
-
- static AndroidProject model
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("densitySplitInL")
- .create()
-
- @BeforeClass
- static void setUp() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check split outputs"() throws Exception {
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 2 , variants.size())
-
- // get the main artifact of the debug artifact
- Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
- assertNotNull("debug Variant null-check", debugVariant)
- AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
- assertNotNull("Debug main info null-check", debugMainArtifact)
-
- // get the outputs.
- Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
- assertNotNull(debugOutputs)
-
- // build a set of expected outputs
- Set<String> expected = Sets.newHashSetWithExpectedSize(5)
- expected.add(null)
- expected.add("mdpi")
- expected.add("hdpi")
- expected.add("xhdpi")
- expected.add("xxhdpi")
-
- assertEquals(1, debugOutputs.size())
- AndroidArtifactOutput output = debugOutputs.iterator().next()
- assertEquals(5, output.getOutputs().size())
- for (OutputFile outputFile : output.getOutputs()) {
- String densityFilter = ModelHelper.getFilter(outputFile, OutputFile.DENSITY)
- assertEquals(densityFilter == null ? OutputFile.MAIN : OutputFile.SPLIT,
- outputFile.getOutputType())
-
- // with pure splits, all split have the same version code.
- assertEquals(12, output.getVersionCode())
- expected.remove(densityFilter)
- }
-
- // this checks we didn't miss any expected output.
- assertTrue(expected.isEmpty())
- }
-
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitTest.groovy
deleted file mode 100644
index b930382..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitTest.groovy
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.OutputFile
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidArtifactOutput
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.google.common.collect.Maps
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static com.android.builder.core.BuilderConstants.DEBUG
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * Assemble tests for densitySplit.
- */
- at CompileStatic
-class DensitySplitTest {
-
- static AndroidProject model
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("densitySplit")
- .create()
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void testPackaging() {
- for (Variant variant : model.getVariants()) {
- AndroidArtifact mainArtifact = variant.getMainArtifact()
- if (!variant.getBuildType().equalsIgnoreCase("Debug")) {
- continue
- }
- assertEquals(5, mainArtifact.getOutputs().size())
-
- File mdpiApk = project.getApk("mdpi", "debug")
- assertThatZip(mdpiApk).contains("res/drawable-mdpi-v4/other.png")
- }
- }
-
- @Test
- void "check version code in apk"() {
- File universalApk = project.getApk("universal", "debug")
- assertThatApk(universalApk).hasVersionCode(112)
- assertThatApk(universalApk).hasVersionName("version 112")
-
- File mdpiApk = project.getApk("mdpi", "debug")
- assertThatApk(mdpiApk).hasVersionCode(212)
- assertThatApk(mdpiApk).hasVersionName("version 212")
-
- File hdpiApk = project.getApk("hdpi", "debug")
- assertThatApk(hdpiApk).hasVersionCode(312)
- assertThatApk(hdpiApk).hasVersionName("version 312")
-
- File xhdpiApk = project.getApk("xhdpi", "debug")
- assertThatApk(xhdpiApk).hasVersionCode(412)
- assertThatApk(xhdpiApk).hasVersionName("version 412")
-
- File xxhdiApk = project.getApk("xxhdpi", "debug")
- assertThatApk(xxhdiApk).hasVersionCode(512)
- assertThatApk(xxhdiApk).hasVersionName("version 512")
- }
-
- @Test
- void "check version code in model"() {
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 2 , variants.size())
-
- // get the main artifact of the debug artifact
- Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
- assertNotNull("debug Variant null-check", debugVariant)
- AndroidArtifact debugMainArficat = debugVariant.getMainArtifact()
- assertNotNull("Debug main info null-check", debugMainArficat)
-
- // get the outputs.
- Collection<AndroidArtifactOutput> debugOutputs = debugMainArficat.getOutputs()
- assertNotNull(debugOutputs)
- assertEquals(5, debugOutputs.size())
-
- // build a map of expected outputs and their versionCode
- Map<String, Integer> expected = Maps.newHashMapWithExpectedSize(5)
- expected.put(null, 112)
- expected.put("mdpi", 212)
- expected.put("hdpi", 312)
- expected.put("xhdpi", 412)
- expected.put("xxhdpi", 512)
-
- assertEquals(5, debugOutputs.size())
- for (AndroidArtifactOutput output : debugOutputs) {
- assertEquals(OutputFile.FULL_SPLIT, output.getMainOutputFile().getOutputType())
- Collection<? extends OutputFile> outputFiles = output.getOutputs()
- assertEquals(1, outputFiles.size())
- assertNotNull(output.getMainOutputFile())
-
- String densityFilter = ModelHelper.getFilter(output.getMainOutputFile(), OutputFile.DENSITY)
- Integer value = expected.get(densityFilter)
- // this checks we're not getting an unexpected output.
- assertNotNull("Check Valid output: " + (densityFilter == null ? "universal"
- : densityFilter),
- value)
-
- assertEquals(value.intValue(), output.getVersionCode())
- expected.remove(densityFilter)
- }
-
- // this checks we didn't miss any expected output.
- assertTrue(expected.isEmpty())
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitWithPublishNonDefaultTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitWithPublishNonDefaultTest.groovy
deleted file mode 100644
index 8377beb..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitWithPublishNonDefaultTest.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
- at CompileStatic
-class DensitySplitWithPublishNonDefaultTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- publishNonDefault true
-
- splits {
- density {
- enable true
- exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
- compatibleScreens 'small', 'normal', 'large', 'xlarge'
- }
- }
-}
-"""
- // build the release for publication (though debug is published too)
- project.execute("assembleRelease")
- }
-
- @Test
- public void "build and publish"() {
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesTest.groovy
deleted file mode 100644
index d5ca959..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for dependencies.
- */
- at CompileStatic
-class DependenciesTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("dependencies")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesWithVariantsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesWithVariantsTest.groovy
deleted file mode 100644
index e81aab5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesWithVariantsTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Assemble tests for dependenciesWithVariants.
- */
- at CompileStatic
-class DependenciesWithVariantsTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("dependenciesWithVariants")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug", "assembleAndroidTest")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCheckerTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCheckerTest.groovy
deleted file mode 100644
index ad17ddf..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCheckerTest.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.gradle.tooling.BuildException
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static org.junit.Assert.fail
-
-/**
- * Assemble tests for dependencyChecker.
- */
- at CompileStatic
-class DependencyCheckerTest {
- @ClassRule
- static public GradleTestProject httpClientProject = GradleTestProject.builder()
- .fromTestProject("dependencyChecker")
- .captureStdOut(true)
- .create()
-
- @ClassRule
- static public GradleTestProject minSdkProject = GradleTestProject.builder()
- .fromTestProject("dependencyCheckerComGoogleAndroidJar")
- .captureStdOut(true)
- .captureStdErr(true)
- .create()
-
- @AfterClass
- static void cleanUp() {
- httpClientProject = null
- minSdkProject = null
- }
-
- @Test
- public void "org.apache.httpcomponents is ignored"() throws Exception {
- httpClientProject.execute("clean", "assembleDebug")
- assertThat(httpClientProject.stdout.toString())
- .contains("Dependency org.apache.httpcomponents:httpclient:4.1.1 is ignored")
- }
-
- @Test
- void lint() {
- httpClientProject.execute("lint")
- }
-
- /**
- * See {@link PrepareDependenciesTask} for the expected output.
- */
- @Test
- public void "com.google.android API version is checked"() throws Exception {
- try {
- minSdkProject.execute("clean", "assemble")
- fail("should throw")
- } catch (BuildException e) {
- // expected.
- }
-
- String stdOut = minSdkProject.stderr.toString()
- assertThat(stdOut).contains("corresponds to API level 15")
- // Picked up from com.google.android
- assertThat(stdOut).contains("which is 14") // Declared in Gradle.
- assertThat(stdOut).contains("com.google.android")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/EmptySplitTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/EmptySplitTest.groovy
deleted file mode 100644
index 218957b..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/EmptySplitTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Assemble tests for emptySplit.
- */
- at CompileStatic
-class EmptySplitTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("emptySplit")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExternalTestProjectTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExternalTestProjectTest.groovy
deleted file mode 100644
index 356024b..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExternalTestProjectTest.groovy
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.gradle.tooling.BuildException
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-import static org.junit.Assert.fail
-/**
- * Check that a project can depend on a jar dependency published by another app project.
- */
- at CompileStatic
-class ExternalTestProjectTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder().captureStdErr(true).create()
-
- private File app2BuildFile
-
- @Before
- public void setUp() {
- File rootFile = project.getTestDir()
- new File(rootFile, "settings.gradle") << """
-include ':app1'
-include ':app2'
-"""
- // app1 module
- File app1 = new File(rootFile, "app1")
- new HelloWorldApp().write(app1, null)
- new File(app1, "build.gradle") << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-task testJar(type: Jar, dependsOn: 'assembleRelease') {
-
-}
-
-configurations {
- testLib
-}
-
-artifacts {
- testLib testJar
-}
-
-"""
- // app2 module
- File app2 = new File(rootFile, "app2")
- new HelloWorldApp().write(app2, null)
- app2BuildFile = new File(app2, "build.gradle")
- }
-
- @Test
- public void testExtraJarDependency() {
- app2BuildFile << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- compile project(path: ':app1', configuration: 'testLib')
-}
-"""
-
- project.execute('clean', 'app2:assembleDebug')
- }
-
- @Test
- void testApkDependencyInBuild() {
- app2BuildFile << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- compile project(path: ':app1')
-}
-"""
- try {
- project.execute('clean', 'app2:assembleDebug')
- fail('Broken build file did not throw exception')
- } catch (BuildException e) {
- Throwable t = e
- while (t.getCause() != null) {
- t = t.getCause()
- }
-
- // looks like we can't actually test the instance t against GradleException
- // due to it coming through the tooling API from a different class loader.
- assertEquals("org.gradle.api.GradleException", t.getClass().canonicalName)
- assertEquals("Dependency Error. See console for details.", t.getMessage())
- }
-
- // check there is a version of the error, after the task name:
- ByteArrayOutputStream stderr = project.stderr
- String log = stderr.toString()
-
- assertTrue("stderr contains error", log.contains(
- "Dependency project:app1:unspecified on project app2 resolves to an APK archive which is not supported as a compilation dependency. File:"))
-
- }
-
- @Test
- void testApkDependencyInModel() {
- app2BuildFile << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- compile project(path: ':app1')
-}
-"""
-
- Map<String, AndroidProject> modelMap = project.getAllModelsIgnoringSyncIssues()
-
- AndroidProject model = modelMap.get(':app2')
- assertNotNull(model)
-
- SyncIssue issue = assertThat(model).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_DEPENDENCY_IS_APK,
- 'project:app1:unspecified')
-
- String expectedMsg = "Dependency project:app1:unspecified on project app2 resolves to an APK archive which is not supported as a compilation dependency. File:"
- assertThat(issue.getMessage()).startsWith(expectedMsg)
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExtractAnnotationTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExtractAnnotationTest.groovy
deleted file mode 100644
index d40c63b..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExtractAnnotationTest.groovy
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.google.common.base.Charsets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * Integration test for extracting annotations.
- * <p>
- * Tip: To execute just this test after modifying the annotations extraction code:
- * <pre>
- * $ cd tools
- * $ ./gradlew :base:i:test -Dtest.single=ExtractAnnotationTest
- * </pre>
- */
- at CompileStatic
-class ExtractAnnotationTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("extractAnnotations")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check extract annotation"() {
- File debugFileOutput = project.file("build/$AndroidProject.FD_INTERMEDIATES/annotations/debug")
- File classesJar = project.file("build/$AndroidProject.FD_INTERMEDIATES/bundles/debug/classes.jar")
- File file = new File(debugFileOutput, "annotations.zip")
-
- //noinspection SpellCheckingInspection
- String expectedContent = (""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"com.android.tests.extractannotations.ExtractTest int getVisibility()\">\n"
- + " <annotation name=\"android.support.annotation.IntDef\">\n"
- + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.VISIBLE, com.android.tests.extractannotations.ExtractTest.INVISIBLE, com.android.tests.extractannotations.ExtractTest.GONE, 5, 17, com.android.tests.extractannotations.Constants.CONSTANT_1}\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"com.android.tests.extractannotations.ExtractTest java.lang.String getStringMode(int)\">\n"
- + " <annotation name=\"android.support.annotation.StringDef\">\n"
- + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.STRING_1, com.android.tests.extractannotations.ExtractTest.STRING_2, "literalValue", "concatenated"}\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"com.android.tests.extractannotations.ExtractTest java.lang.String getStringMode(int) 0\">\n"
- + " <annotation name=\"android.support.annotation.IntDef\">\n"
- + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.VISIBLE, com.android.tests.extractannotations.ExtractTest.INVISIBLE, com.android.tests.extractannotations.ExtractTest.GONE, 5, 17, com.android.tests.extractannotations.Constants.CONSTANT_1}\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"com.android.tests.extractannotations.ExtractTest void checkForeignTypeDef(int) 0\">\n"
- + " <annotation name=\"android.support.annotation.IntDef\">\n"
- + " <val name=\"flag\" val=\"true\" />\n"
- + " <val name=\"value\" val=\"{com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_2}\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"com.android.tests.extractannotations.ExtractTest void testMask(int) 0\">\n"
- + " <annotation name=\"android.support.annotation.IntDef\">\n"
- + " <val name=\"flag\" val=\"true\" />\n"
- + " <val name=\"value\" val=\"{0, com.android.tests.extractannotations.Constants.FLAG_VALUE_1, com.android.tests.extractannotations.Constants.FLAG_VALUE_2}\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"com.android.tests.extractannotations.ExtractTest void testNonMask(int) 0\">\n"
- + " <annotation name=\"android.support.annotation.IntDef\">\n"
- + " <val name=\"flag\" val=\"false\" />\n"
- + " <val name=\"value\" val=\"{0, com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_3}\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"com.android.tests.extractannotations.TopLevelTypeDef\">\n"
- + " <annotation name=\"android.support.annotation.IntDef\">\n"
- + " <val name=\"flag\" val=\"true\" />\n"
- + " <val name=\"value\" val=\"{com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_2}\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + "</root>\n")
-
- assertThatZip(file).containsFileWithContent(
- "com/android/tests/extractannotations/annotations.xml", expectedContent)
-
- // check the resulting .aar file to ensure annotations.zip inclusion.
- assertThatZip(project.getAar("debug")).contains("annotations.zip")
-
- // Check typedefs removals:
-
- // public typedef: should be present
- assertThatZip(classesJar).contains(
- "com/android/tests/extractannotations/ExtractTest\$Visibility.class")
-
- // private/protected typedefs: should have been removed
- assertThatZip(classesJar).doesNotContain(
- "com/android/tests/extractannotations/ExtractTest\$Mask.class")
- assertThatZip(classesJar).doesNotContain(
- "com/android/tests/extractannotations/ExtractTest\$NonMaskType.class")
-
- // Make sure the NonMask symbol (from a private typedef) is completely gone from the
- // outer class
- assertThatZip(classesJar).containsFileWithoutContent(
- "com/android/tests/extractannotations/ExtractTest.class",
- "NonMaskType".getBytes(Charsets.UTF_8));
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutBuildTypeTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutBuildTypeTest.groovy
deleted file mode 100644
index 157bc12..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutBuildTypeTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-
-/**
- * Assemble tests for filteredOutBuildType.
- */
- at CompileStatic
-class FilteredOutBuildTypeTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("filteredOutBuildType")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check filtered out variant isn't in model"() {
- // Load the custom model for the project
- assertEquals("Variant Count", 1, model.getVariants().size())
- Variant variant = model.getVariants().iterator().next()
- assertEquals("Variant name", "release", variant.getBuildType())
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutVariantsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutVariantsTest.groovy
deleted file mode 100644
index a322801..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutVariantsTest.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-
-/**
- * Assemble tests for filteredOutVariants.
- */
- at CompileStatic
-class FilteredOutVariantsTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("filteredOutVariants")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check filtered out variant isn't in model"() {
- Collection<Variant> variants = model.getVariants()
- // check we have the right number of variants:
- // arm/cupcake, arm/gingerbread, x86/gingerbread, mips/gingerbread
- // all 4 in release and debug
- assertEquals("Variant Count", 8, variants.size())
-
- for (Variant variant : variants) {
- List<String> flavors = variant.getProductFlavors()
- assertFalse("check ignored x86/cupcake",
- flavors.contains("x68") && flavors.contains("cupcake"))
- assertFalse("check ignored mips/cupcake",
- flavors.contains("mips") && flavors.contains("cupcake"))
- }
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavoredTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavoredTest.groovy
deleted file mode 100644
index d700363..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavoredTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for flavored.
- */
- at CompileStatic
-class FlavoredTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("flavored")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavorsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavorsTest.groovy
deleted file mode 100644
index ef71787..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavorsTest.groovy
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.build.gradle.integration.common.utils.ProductFlavorHelper
-import com.android.build.gradle.integration.common.utils.SourceProviderHelper
-import com.android.build.gradle.integration.common.utils.VariantHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.BuildTypeContainer
-import com.android.builder.model.ProductFlavor
-import com.android.builder.model.ProductFlavorContainer
-import com.android.builder.model.SourceProviderContainer
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.builder.core.VariantType.ANDROID_TEST
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Assemble tests for flavors.
- */
- at CompileStatic
-class FlavorsTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("flavors")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check flavors show up in model"() throws Exception {
- File projectDir = project.getTestDir()
-
- assertFalse("Library Project", model.isLibrary())
-
- assertThat(model.getFlavorDimensions()).containsExactly("group1", "group2")
-
- ProductFlavorContainer defaultConfig = model.getDefaultConfig()
-
- new SourceProviderHelper(model.getName(), projectDir,
- "main", defaultConfig.getSourceProvider())
- .test()
-
- SourceProviderContainer testSourceProviderContainer = ModelHelper.getSourceProviderContainer(
- defaultConfig.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST)
- assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer)
-
- new SourceProviderHelper(model.getName(), projectDir,
- ANDROID_TEST.prefix, testSourceProviderContainer.getSourceProvider())
- .test()
-
- Collection<BuildTypeContainer> buildTypes = model.getBuildTypes()
- assertEquals("Build Type Count", 2, buildTypes.size())
-
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 8, variants.size())
-
- Collection<ProductFlavorContainer> flavorContainers = model.getProductFlavors();
- assertThat(flavorContainers).hasSize(4);
- Map expected = [f1:"group1", f2: "group1", fa: "group2", fb: "group2"]
- for (ProductFlavorContainer flavorContainer: flavorContainers) {
- ProductFlavor flavor = flavorContainer.getProductFlavor();
- assertEquals(expected.get(flavor.name), flavor.dimension)
- }
-
- Variant f1faDebugVariant = ModelHelper.getVariant(variants, "f1FaDebug")
- assertNotNull("f1faDebug Variant null-check", f1faDebugVariant)
- assertThat(f1faDebugVariant.getProductFlavors()).containsExactly("f1","fa")
- new ProductFlavorHelper(f1faDebugVariant.getMergedFlavor(), "F1faDebug Merged Flavor")
- .test()
- new VariantHelper(f1faDebugVariant, projectDir, "flavors-f1-fa-debug.apk").test()
- }
-
- @Test
- public void "compound source sets are in the model"() throws Exception {
- for (variant in model.variants) {
- assert variant.extraJavaArtifacts.every { it.multiFlavorSourceProvider != null }
- assert variant.extraJavaArtifacts.every { it.variantSourceProvider != null }
- assert variant.extraAndroidArtifacts.every { it.multiFlavorSourceProvider != null }
- // No per-variant source providers for android tests.
- assert variant.extraAndroidArtifacts.every { it.variantSourceProvider == null }
- }
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApi2Test.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApi2Test.groovy
deleted file mode 100644
index 7f2f7f1..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApi2Test.groovy
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.JavaArtifact
-import com.android.builder.model.Variant
-import com.android.utils.FileUtils
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * Assemble tests for genFolderApi2.
- */
- at CompileStatic
-class GenFolderApi2Test {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("genFolderApi2")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.getSingleModel()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check Java Folder in Model"() throws Exception {
- File projectDir = project.testDir
-
- File buildDir = new File(projectDir, "build")
-
- for (Variant variant : model.variants) {
-
- AndroidArtifact mainInfo = variant.mainArtifact
- assertNotNull(
- "Null-check on mainArtifactInfo for " + variant.displayName,
- mainInfo)
-
- // Get the generated source folders.
- Collection<File> genSourceFolder = mainInfo.generatedSourceFolders
-
- // We're looking for a custom folder.
- String sourceFolderStart = new File(buildDir, "customCode").absolutePath + File.separatorChar
- assertTrue("custom generated source folder check", genSourceFolder.any {
- it.absolutePath.startsWith(sourceFolderStart)
- })
-
- // Unit testing artifact:
- assertThat(variant.getExtraJavaArtifacts()).hasSize(1)
- JavaArtifact unitTestArtifact = variant.extraJavaArtifacts.first()
- def sortedFolders = unitTestArtifact.generatedSourceFolders.sort()
- assertThat(sortedFolders).hasSize(2)
- assertThat(sortedFolders[0].absolutePath).startsWith(sourceFolderStart)
- assertThat(sortedFolders[0].absolutePath).endsWith("-1")
- assertThat(sortedFolders[1].absolutePath).startsWith(sourceFolderStart)
- assertThat(sortedFolders[1].absolutePath).endsWith("-2")
- }
- }
-
- @Test
- public void backwardsCompatible() throws Exception {
- // ATTENTION Author and Reviewers - please make sure required changes to the build file
- // are backwards compatible before updating this test.
- assertThat(FileUtils.sha1(project.file("build.gradle")))
- .isEqualTo("93b7507ce31a087a4efa4cae66474ad320e25b6c")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApiTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApiTest.groovy
deleted file mode 100644
index 4a0240d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApiTest.groovy
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.android.utils.FileUtils
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * Assemble tests for genFolderApi.
- */
- at CompileStatic
-class GenFolderApiTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("genFolderApi")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check the custom java generation task ran"() throws Exception {
- assertThatApk(project.getApk("debug")).containsClass("Lcom/custom/Foo;")
- }
-
- @Test
- void "check the custom res generation task ran"() throws Exception {
- assertThatZip(project.getApk("debug")).contains("res/xml/generated.xml")
- }
-
- @Test
- void "check Java folder in Model"() throws Exception {
- File projectDir = project.getTestDir()
-
- File buildDir = new File(projectDir, "build")
-
- for (Variant variant : model.getVariants()) {
-
- AndroidArtifact mainInfo = variant.getMainArtifact()
- assertNotNull(
- "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
- mainInfo)
-
- // get the generated source folders.
- Collection<File> genSourceFolder = mainInfo.getGeneratedSourceFolders()
-
- // We're looking for a custom folder
- String sourceFolderStart = new File(buildDir, "customCode").getAbsolutePath() + File.separatorChar
- boolean found = false
- for (File f : genSourceFolder) {
- if (f.getAbsolutePath().startsWith(sourceFolderStart)) {
- found = true
- break
- }
- }
-
- assertTrue("custom generated source folder check", found)
- }
- }
-
- @Test
- void "check Res Folder in Model"() throws Exception {
- File projectDir = project.getTestDir()
-
- File buildDir = new File(projectDir, "build")
-
- for (Variant variant : model.getVariants()) {
-
- AndroidArtifact mainInfo = variant.getMainArtifact()
- assertNotNull(
- "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
- mainInfo)
-
- // get the generated res folders.
- Collection<File> genResFolder = mainInfo.getGeneratedResourceFolders()
- String resFolderStart = new File(buildDir, "customRes").getAbsolutePath() + File.separatorChar
- boolean found = false
- for (File f : genResFolder) {
- if (f.getAbsolutePath().startsWith(resFolderStart)) {
- found = true
- break
- }
- }
-
- assertTrue("custom generated res folder check", found)
- }
- }
-
- @Test
- public void backwardsCompatible() throws Exception {
- // ATTENTION Author and Reviewers - please make sure required changes to the build file
- // are backwards compatible before updating this test.
- assertThat(FileUtils.sha1(project.file("build.gradle")))
- .isEqualTo("0d39ec75ccdc0f4adbc95b69a639117246bc0574")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InvalidResourceDirectoryTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InvalidResourceDirectoryTest.groovy
deleted file mode 100644
index 8faffa2..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InvalidResourceDirectoryTest.groovy
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.google.common.base.Throwables
-import groovy.transform.CompileStatic
-import org.gradle.tooling.BuildException
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
- at CompileStatic
-class InvalidResourceDirectoryTest {
-
- public static AndroidTestApp app = new HelloWorldApp()
-
- static {
- app.addFile(new TestSourceFile(INVALID_LAYOUT_FOLDER, "main.xml",
- """<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
-<TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="hello invalid layout world!"
- android:id="@+id/text"
- />
-</LinearLayout>
-"""));
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder().fromTestApp(app).create()
-
- public static final String INVALID_LAYOUT_FOLDER = "src/main/res/layout-hdpi-land"
-
-
- @BeforeClass
- public static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-"""
- }
-
- @Test
- public void "check build failure on invalid resource directory"() {
- try {
- project.execute("assembleRelease");
- } catch (BuildException e) {
- Throwable rootCause = Throwables.getRootCause(e);
- assert rootCause.message.contains(
- new File(project.testDir, INVALID_LAYOUT_FOLDER).absolutePath)
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JackTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JackTest.groovy
deleted file mode 100644
index fe15388..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JackTest.groovy
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.google.common.collect.ImmutableList
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-/**
- * Test Jack integration.
- */
- at CompileStatic
-class JackTest {
- private final static List<String> JACK_OPTIONS = ImmutableList.of(
- "-PCUSTOM_JACK=1",
- "-PCUSTOM_BUILDTOOLS=21.1.0")
-
- @ClassRule
- static public GradleTestProject basic = GradleTestProject.builder()
- .withName("basic")
- .fromTestProject("basic")
- .create()
-
- @ClassRule
- static public GradleTestProject minify = GradleTestProject.builder()
- .withName("minify")
- .fromTestProject("minify")
- .create()
-
- @ClassRule
- static public GradleTestProject multiDex = GradleTestProject.builder()
- .withName("multiDex")
- .fromTestProject("multiDex")
- .create()
-
- @BeforeClass
- static void setUp() {
- GradleTestProject.assumeBuildToolsAtLeast(21, 1, 0)
- basic.execute(JACK_OPTIONS, "clean", "assembleDebug")
- minify.execute(JACK_OPTIONS, "clean", "assembleDebug")
- multiDex.execute(JACK_OPTIONS, "clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- basic = null
- minify = null
- multiDex = null
- }
-
- @Test
- void assembleDebug() {
- // Empty test to ensure setup succeeds if DeviceTests are not run.
- }
-
- @Test
- @Category(DeviceTests.class)
- void "basic connectedCheck"() {
- basic.executeConnectedCheck(JACK_OPTIONS)
- }
-
- @Test
- @Category(DeviceTests.class)
- void "multiDex connectedCheck"() {
- multiDex.executeConnectedCheck(JACK_OPTIONS)
- }
-
- @Test
- void "minify unitTests with Javac"() {
- minify.execute("testMinified")
- }
-
- @Test
- void "minify unitTests with Jack"() {
- minify.execute(JACK_OPTIONS, "clean", "testMinified")
-
- // Make sure javac was run.
- File classesDir = new File(minify.testDir, "/build/intermediates/classes/minified")
- assert classesDir.exists()
-
- // Make sure jack was not run.
- File jillDir = new File(minify.testDir, "/build/intermediates/jill")
- assert !jillDir.exists()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JarJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JarJarTest.groovy
deleted file mode 100644
index ad7f519..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JarJarTest.groovy
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.truth.ApkSubject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.google.common.collect.Iterators;
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
-import static com.android.builder.core.BuilderConstants.DEBUG;
-import static org.junit.Assert.assertEquals;
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidArtifactOutput;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Variant;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import static org.junit.Assert.assertNotNull;
-
-/**
- * Test for the jarjar integration.
- */
-public class JarJarTest {
-
- static AndroidProject model
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject(GradleTestProject.USE_JACK ? "jarjarWithJack" : "jarjarIntegration")
- .create()
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check repackaged gson library"() {
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 2, variants.size())
-
- // get the main artifact of the debug artifact
- Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
- assertNotNull("debug Variant null-check", debugVariant)
- AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
- assertNotNull("Debug main info null-check", debugMainArtifact)
-
- // get the outputs.
- Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
- assertNotNull(debugOutputs)
- assertEquals(1, debugOutputs.size())
-
- // make sure the Gson library has been renamed and the original one is not present.
- File outputFile = Iterators.getOnlyElement(debugOutputs.iterator()).mainOutputFile.
- getOutputFile()
- assertThatApk(outputFile).containsClass("Lcom/google/repacked/gson/Gson;");
- assertThatApk(outputFile).doesNotContainClass("Lcom/google/gson/Gson;")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MessageRewriteTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MessageRewriteTest.groovy
deleted file mode 100644
index 6c90eb6..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MessageRewriteTest.groovy
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
-import com.android.builder.model.AndroidProject
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.google.common.base.Charsets
-import com.google.common.io.Files
-import groovy.transform.CompileStatic
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Tests the error message rewriting logic.
- */
- at CompileStatic
-class MessageRewriteTest {
-
- private static List<String> INVOKED_FROM_IDE_ARGS =
- Collections.singletonList("-P" + AndroidProject.PROPERTY_INVOKED_FROM_IDE + "=true")
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("flavored")
- .withoutNdk()
- .captureStdOut(true)
- .captureStdErr(true)
- .create()
-
- @BeforeClass
- public static void assemble() {
- project.execute('assembleDebug')
- }
-
- @Test
- public void "invalid layout file"() {
- TemporaryProjectModification.doTest(project) {
- it.replaceInFile("src/main/res/layout/main.xml", "</LinearLayout>", "");
- project.getStderr().reset()
- project.executeExpectingFailure(INVOKED_FROM_IDE_ARGS, 'assembleF1Debug')
- String err = project.getStderr().toString()
- assertThat(err).contains("src/main/res/layout/main.xml")
- }
-
- project.execute('assembleDebug')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MigratedTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MigratedTest.groovy
deleted file mode 100644
index 03443a2..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MigratedTest.groovy
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.build.gradle.integration.common.utils.SourceProviderHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.ProductFlavorContainer
-import com.android.builder.model.SourceProviderContainer
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.builder.core.VariantType.ANDROID_TEST
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-
-/**
- * Assemble tests for migrated.
- */
- at CompileStatic
-class MigratedTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("migrated")
- .create()
-
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check model reflect migrated source providers"() throws Exception {
- File projectDir = project.getTestDir()
-
- assertFalse("Library Project", model.isLibrary())
-
- ProductFlavorContainer defaultConfig = model.getDefaultConfig()
-
- new SourceProviderHelper(model.getName(), projectDir,
- "main", defaultConfig.getSourceProvider())
- .setJavaDir("src")
- .setResourcesDir("src")
- .setAidlDir("src")
- .setRenderscriptDir("src")
- .setResDir("res")
- .setAssetsDir("assets")
- .setManifestFile("AndroidManifest.xml")
- .test()
-
- SourceProviderContainer testSourceProviderContainer = ModelHelper.getSourceProviderContainer(
- defaultConfig.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST)
- assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer)
-
- new SourceProviderHelper(model.getName(), projectDir,
- ANDROID_TEST.prefix, testSourceProviderContainer.getSourceProvider())
- .setJavaDir("tests/java")
- .setResourcesDir("tests/resources")
- .setAidlDir("tests/aidl")
- .setJniDir("tests/jni")
- .setRenderscriptDir("tests/rs")
- .setResDir("tests/res")
- .setAssetsDir("tests/assets")
- .setManifestFile("tests/AndroidManifest.xml")
- .test()
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyLibAndAppWithJavaResTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyLibAndAppWithJavaResTest.groovy
deleted file mode 100644
index 669f194..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyLibAndAppWithJavaResTest.groovy
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application;
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.truth.ApkSubject;
-import com.android.builder.model.AndroidProject;
-
-import org.junit.AfterClass
-import org.junit.Assume;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import groovy.transform.CompileStatic
-
-import static org.junit.Assert.assertNotNull;
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-
-/**
- * Tests that ensure that java resources files accessed with a relative or absolute path are
- * packaged correctly.
- */
- at CompileStatic
-public class MinifyLibAndAppWithJavaResTest {
-
- static Map<String, AndroidProject> models
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("minifyLibWithJavaRes")
- .create()
-
- @BeforeClass
- static void setUp() {
- models = project.executeAndReturnMultiModel("clean", "assemble")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void testDebugPackaging() {
- File debugApk = project.getSubproject("app").getApk("debug")
- assertNotNull(debugApk)
- ApkSubject apkSubject = assertThatApk(debugApk);
- // check that resources with relative path lookup code have a matching obfuscated package
- // name.
- apkSubject.contains("com/android/tests/util/resources.properties")
- apkSubject.contains("com/android/tests/other/resources.properties")
- // check that resources with absolute path lookup remain in the original package name.
- apkSubject.contains("com/android/tests/util/another.properties")
- apkSubject.contains("com/android/tests/other/some.xml")
- apkSubject.contains("com/android/tests/other/another.properties")
- }
-
- @Test
- void testReleasePackaging() {
- Assume.assumeFalse("Ignore until Jack fixed proguard confusion", GradleTestProject.USE_JACK)
- File releaseApk = project.getSubproject("app").getApk("release")
- assertNotNull(releaseApk)
- ApkSubject apkSubject = assertThatApk(releaseApk);
- // check that resources with relative path lookup code have a matching obfuscated package
- // name.
- apkSubject.contains("com/android/tests/b/resources.properties")
- apkSubject.contains("com/android/tests/a/resources.properties")
- // check that resources with absolute path lookup remain in the original package name.
- apkSubject.contains("com/android/tests/util/another.properties")
- apkSubject.contains("com/android/tests/other/some.xml")
- apkSubject.contains("com/android/tests/other/another.properties")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyTest.groovy
deleted file mode 100644
index 3c0a87a..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyTest.groovy
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.truth.TruthHelper
-import com.android.build.gradle.integration.common.truth.ZipFileSubject
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileDynamic
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.Opcodes
-import org.objectweb.asm.Type
-import org.objectweb.asm.tree.ClassNode
-import org.objectweb.asm.tree.FieldNode
-
-import java.util.jar.JarFile
-
-import static com.google.common.truth.Truth.assertThat
-/**
- * Assemble tests for minify.
- */
- at CompileStatic
-class MinifyTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("minify")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleMinified",
- "assembleMinifiedAndroidTest", "jarDebugClasses")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-
- @Test
- void 'App APK is minified'() throws Exception {
- JarFile minifiedJar = new JarFile(project.file(
- "build/$AndroidProject.FD_INTERMEDIATES/classes-proguard/minified/classes.jar"))
-
- def appClassFiles = minifiedJar.entries().toSet().collect { it.name }
- // Ignore JaCoCo stuff.
- appClassFiles.removeAll { it =~ /org.jacoco/ }
- appClassFiles.removeAll(["about.html", "com/vladium/emma/rt/RT.class"])
-
- assertThat(appClassFiles).containsExactly(
- "com/android/tests/basic/a.class", // Renamed StringProvider.
- "com/android/tests/basic/Main.class",
- "com/android/tests/basic/IndirectlyReferencedClass.class", // Kept by ProGuard rules.
- // No entry for UnusedClass, it gets removed.
- )
- }
-
- @Test
- void 'Test APK is not minified, but mappings are applied'() throws Exception {
- JarFile minifiedJar = new JarFile(project.file(
- "build/$AndroidProject.FD_INTERMEDIATES/classes-proguard/androidTest/minified/classes.jar"))
-
- def testClassFiles = minifiedJar.entries()
- .toSet()
- .collect { it.name }
- .findAll { !it.startsWith("org/hamcrest") }
-
- assertThat(testClassFiles).containsExactly(
- "com/android/tests/basic/MainTest.class",
- "com/android/tests/basic/UnusedTestClass.class",
- "com/android/tests/basic/UsedTestClass.class",
- "com/android/tests/basic/test/BuildConfig.class",
- )
-
- checkClassFile(minifiedJar)
- }
-
- @Test
- void 'Test classes.jar is present for non Jack enabled variants'() throws Exception {
- ZipFileSubject classes = TruthHelper.assertThatZip(project.file(
- "build/$AndroidProject.FD_INTERMEDIATES/packaged/debug/classes.jar"))
-
- classes.contains("com/android/tests/basic/Main.class")
- classes.doesNotContain("com/android/tests/basic/MainTest.class")
- }
-
- @CompileDynamic
- static def checkClassFile(JarFile minifiedJar) {
- def mainTestBytes = minifiedJar.getInputStream(
- minifiedJar.getEntry("com/android/tests/basic/MainTest.class"))
- def classReader = new ClassReader(mainTestBytes)
- def mainTestClassNode = new ClassNode(Opcodes.ASM5)
- classReader.accept(mainTestClassNode, 0)
-
- // Make sure bytecode got rewritten to point to renamed classes.
- FieldNode stringProviderField = mainTestClassNode.fields.find { it.name == "stringProvider" }
- assert Type.getType(stringProviderField.desc).className == "com.android.tests.basic.a"
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ModelTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ModelTest.groovy
deleted file mode 100644
index cc983d5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ModelTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * General Model tests
- */
- at CompileStatic
-class ModelTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-"""
- }
-
- @Test
- public void unresolvedDependencies() {
- project.getBuildFile() << """
-dependencies {
- compile 'foo:bar:1.2.3'
-}
-"""
- AndroidProject model = project.getSingleModelIgnoringSyncIssues()
- assertThat(model).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_UNRESOLVED_DEPENDENCY,
- 'foo:bar:1.2.3')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiDexTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiDexTest.groovy
deleted file mode 100644
index 6528a3f..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiDexTest.groovy
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.google.common.io.Files
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
-
-/**
- * Assemble tests for multiDex.
- */
- at CompileStatic
-class MultiDexTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("multiDex")
- .create()
-
- @BeforeClass
- static void setUp() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check classes.dex"() {
- // manually inspcet the apk to ensure that the classes.dex that was created is the same
- // one in the apk. This tests that the packaging didn't rename the multiple dex files
- // around when we packaged them.
- File classesDex = project.file("build/" + FD_INTERMEDIATES + "/dex/ics/debug/classes.dex")
- File apk = project.getApk("ics", "debug")
-
- assertThatZip(apk).containsFileWithContent("classes.dex", Files.toByteArray(classesDex))
- }
-
- @Test
- void "check multidex without obfuscate"() {
- project.execute("assembleIcsProguard")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiresTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiresTest.groovy
deleted file mode 100644
index 392e6c7..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiresTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for multires.
- */
- at CompileStatic
-class MultiresTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("multires")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NdkJniPureSplitLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NdkJniPureSplitLibTest.groovy
deleted file mode 100644
index 6c696eb..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NdkJniPureSplitLibTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-/**
- * Assemble tests for ndkJniPureSplitLib.
- */
- at CompileStatic
-class NdkJniPureSplitLibTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("ndkJniPureSplitLib")
- .addGradleProperties("android.useDeprecatedNdk=true")
- .create()
-
- @BeforeClass
- static void setUp() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- project.execute("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check version code"() {
- GradleTestProject app = project.getSubproject("app")
- assertThatApk(app.getApk("free", "debug_armeabi-v7a")).hasVersionCode(123)
- assertThatApk(app.getApk("free", "debug_mips")).hasVersionCode(123)
- assertThatApk(app.getApk("free", "debug_x86")).hasVersionCode(123)
- assertThatApk(app.getApk("paid", "debug_armeabi-v7a")).hasVersionCode(123)
- assertThatApk(app.getApk("paid", "debug_mips")).hasVersionCode(123)
- assertThatApk(app.getApk("paid", "debug_x86")).hasVersionCode(123)
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NoPreDexTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NoPreDexTest.groovy
deleted file mode 100644
index e19be55..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NoPreDexTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Assemble tests for noPreDex.
- */
- at CompileStatic
-class NoPreDexTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("noPreDex")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryTest.groovy
deleted file mode 100644
index 6fb08be..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryTest.groovy
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import com.android.sdklib.IAndroidTarget
-import com.android.sdklib.SdkManager
-import com.android.utils.NullLogger
-import groovy.transform.CompileStatic
-import org.junit.After
-import org.junit.Assume
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.SdkConstants.FN_FRAMEWORK_LIBRARY
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * Test for BuildConfig field declared in build type, flavors, and variant and how they
- * override each other
- */
- at CompileStatic
-class OptionalLibraryTest {
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @After
- void cleanUp() {
- project = null
- }
-
- @Test
- void "test unknown useLibrary trigger sync issue"() {
- Assume.assumeNotNull("Next platform missing", System.getenv("ANDROID_NEXT_PLATFORM"));
-
- project.getBuildFile() << """
- apply plugin: 'com.android.application'
-
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- useLibrary 'foo'
-
- }
- """.stripIndent()
-
- AndroidProject project = project.getSingleModelIgnoringSyncIssues()
-
- assertThat(project).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_OPTIONAL_LIB_NOT_FOUND,
- 'foo');
- }
-
- @Test
- void "test using optional library"() {
- Assume.assumeNotNull("Next platform missing", System.getenv("ANDROID_NEXT_PLATFORM"));
-
- project.getBuildFile() << """
- apply plugin: 'com.android.application'
-
- android {
- compileSdkVersion 23
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- useLibrary 'org.apache.http.legacy'
-
- }
- """.stripIndent()
-
- AndroidProject project = project.getSingleModel()
-
- // get the SDK folder
- File sdkLocation = new File(System.getenv("ANDROID_HOME"))
- SdkManager sdkManager = SdkManager.createManager(
- sdkLocation.getAbsolutePath(),
- new NullLogger())
- IAndroidTarget target = sdkManager.getTargetFromHashString('android-23')
-
- File targetLocation = new File(target.getLocation())
-
- assertThat(project.getBootClasspath()).containsExactly(
- new File(targetLocation, FN_FRAMEWORK_LIBRARY).getAbsolutePath(),
- new File(targetLocation, "optional/org.apache.http.legacy.jar").getAbsolutePath())
- }
-
- @Test
- void "test not using optional library"() {
- Assume.assumeNotNull("Next platform missing", System.getenv("ANDROID_NEXT_PLATFORM"));
-
- project.getBuildFile() << """
- apply plugin: 'com.android.application'
-
- android {
- compileSdkVersion 23
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- """.stripIndent()
-
- AndroidProject project = project.getSingleModel()
-
- // get the SDK folder
- File sdkLocation = new File(System.getenv("ANDROID_HOME"))
- SdkManager sdkManager = SdkManager.createManager(
- sdkLocation.getAbsolutePath(),
- new NullLogger())
- IAndroidTarget target = sdkManager.getTargetFromHashString('android-23')
-
- File targetLocation = new File(target.getLocation())
-
- assertThat(project.getBootClasspath()).containsExactly(
- new File(targetLocation, FN_FRAMEWORK_LIBRARY).getAbsolutePath())
- }
-
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay1Test.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay1Test.groovy
deleted file mode 100644
index cfa0e28..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay1Test.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ImageHelper
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for overlay1.
- */
- at CompileStatic
-class Overlay1Test {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("overlay1")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check image color"() {
- int GREEN = ImageHelper.GREEN
- File drawableOutput = project.
- file("build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/debug/drawable")
- ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "type_overlay.png", GREEN)
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay2Test.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay2Test.groovy
deleted file mode 100644
index 5ddb6d9..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay2Test.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ImageHelper
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for overlay2.
- */
- at CompileStatic
-class Overlay2Test {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("overlay2")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check image color"() {
- int GREEN = ImageHelper.GREEN
- File drawableOutput = project.file(
- "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/one/debug/drawable")
-
- ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "type_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "flavor_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "type_flavor_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "variant_type_flavor_overlay.png", GREEN)
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay3Test.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay3Test.groovy
deleted file mode 100644
index 3206c65..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay3Test.groovy
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ImageHelper
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for overlay3.
- */
- at CompileStatic
-class Overlay3Test {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("overlay3")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check image color"() {
- int GREEN = ImageHelper.GREEN
- int RED = ImageHelper.RED
-
- File drawableOutput = project.file(
- "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/freeBeta/debug/drawable")
-
- ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "debug_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "beta_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "free_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "free_beta_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "free_beta_debug_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "free_normal_overlay.png", RED)
-
- drawableOutput = project.file(
- "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/freeNormal/debug/drawable")
-
- ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "debug_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "beta_overlay.png", RED)
- ImageHelper.checkImageColor(drawableOutput, "free_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "free_beta_overlay.png", RED)
- ImageHelper.checkImageColor(drawableOutput, "free_beta_debug_overlay.png", RED)
- ImageHelper.checkImageColor(drawableOutput, "free_normal_overlay.png", GREEN)
-
- drawableOutput = project.file(
- "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/paidBeta/debug/drawable")
-
- ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "debug_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "beta_overlay.png", GREEN)
- ImageHelper.checkImageColor(drawableOutput, "free_overlay.png", RED)
- ImageHelper.checkImageColor(drawableOutput, "free_beta_overlay.png", RED)
- ImageHelper.checkImageColor(drawableOutput, "free_beta_debug_overlay.png", RED)
- ImageHelper.checkImageColor(drawableOutput, "free_normal_overlay.png", RED)
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PackagingOptionsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PackagingOptionsTest.groovy
deleted file mode 100644
index d07f43c..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PackagingOptionsTest.groovy
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.google.common.io.Files
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-/**
- * Assemble tests for packagingOptions.
- *
- * Creates two jar files and test various packaging options.
- */
- at CompileStatic
-class PackagingOptionsTest {
-
- // Projects to create jar files.
- private static AndroidTestApp jarProject1 = new EmptyAndroidTestApp()
- static {
- jarProject1.addFile(new TestSourceFile("", "build.gradle", "apply plugin: 'java'"))
- jarProject1.addFile(new TestSourceFile("src/main/resources", "conflict.txt", "foo"))
- }
- private static AndroidTestApp jarProject2 = new EmptyAndroidTestApp()
- static {
- jarProject2.addFile(new TestSourceFile("", "build.gradle", "apply plugin: 'java'"))
- jarProject2.addFile(new TestSourceFile("src/main/resources", "conflict.txt", "foo"))
- // add an extra file so that jar1 is different from jar2.
- jarProject2.addFile(new TestSourceFile("src/main/resources", "dummy2.txt", "bar"))
- }
-
- @ClassRule
- public static GradleTestProject jar1 = GradleTestProject.builder()
- .fromTestApp(jarProject1)
- .withName("jar1")
- .create()
- @ClassRule
- public static GradleTestProject jar2 = GradleTestProject.builder()
- .fromTestApp(jarProject2)
- .withName("jar2")
- .create()
-
- @BeforeClass
- static void createJars() {
- jar1.execute("assemble")
- jar2.execute("assemble")
- }
-
-
- // Main test project.
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- void setUp() {
- Files.copy(jar1.file("build/libs/jar1.jar"), project.file("jar1.jar"))
- Files.copy(jar2.file("build/libs/jar2.jar"), project.file("jar2.jar"))
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-"""
-
- }
-
- @Test
- void "check pickFirst"() {
- project.getBuildFile() << """
-android {
- packagingOptions {
- pickFirst 'conflict.txt'
- }
-}
-
-dependencies {
- compile files('jar1.jar')
- compile files('jar2.jar')
-}
-"""
- project.execute("clean", "assembleDebug")
- assertThatZip(project.getApk("debug")).contains("conflict.txt")
- }
-
- @Test
- void "check exclude"() {
- project.getBuildFile() << """
-android {
- packagingOptions {
- exclude 'conflict.txt'
- }
-}
-
-dependencies {
- compile files('jar1.jar')
- compile files('jar2.jar')
-}
-"""
- project.execute("clean", "assembleDebug")
- assertThatZip(project.getApk("debug")).doesNotContain("conflict.txt")
- }
-
- @Test
- void "check exclude on direct files"() {
- project.getBuildFile() << """
-android {
- packagingOptions {
- exclude 'lib/x86/libconflict.so'
- exclude 'conflict.txt'
- }
-}
-"""
- createFile('src/main/jniLibs/x86/libconflict.so')
- createFile('src/main/resources/conflict.txt')
- project.execute("clean", "assembleDebug")
- assertThatZip(project.getApk("debug")).doesNotContain('lib/x86/libconflict.so')
- assertThatZip(project.getApk("debug")).doesNotContain('conflict.txt')
- }
-
- @Test
- void "check merge on jar entries"() {
- project.getBuildFile() << """
-android {
- packagingOptions {
- merge 'conflict.txt'
- }
-}
-
-dependencies {
- compile files('jar1.jar')
- compile files('jar2.jar')
-}
-"""
- project.execute("clean", "assembleDebug")
-
- assertThatZip(project.getApk("debug")).containsFileWithContent("conflict.txt", "foofoo")
- }
-
- @Test
- void "check merge on direct files"() {
- project.getBuildFile() << """
-android {
- packagingOptions {
- // Doesn't make sense to merge native library, but it should work.
- merge 'lib/x86/libconflict.so'
- }
-}
-"""
- createFile('src/main/jniLibs/x86/libconflict.so') << "foo"
- createFile('src/debug/jniLibs/x86/libconflict.so') << "foo"
- project.execute("clean", "assembleDebug")
- assertThatZip(project.getApk("debug")).containsFileWithContent("lib/x86/libconflict.so", "foofoo")
- }
-
- @Test
- void "check merge on a direct file and a jar entry"() {
- project.getBuildFile() << """
-android {
- packagingOptions {
- merge 'conflict.txt'
- }
-}
-
-dependencies {
- compile files('jar1.jar')
-}
-"""
- createFile('src/main/resources/conflict.txt') << "foo"
- project.execute("clean", "assembleDebug")
- assertThatZip(project.getApk("debug")).containsFileWithContent("conflict.txt", "foofoo")
- }
-
- /**
- * Create a new empty file including its directories.
- */
- private File createFile(String filename) {
- File newFile = project.file(filename)
- newFile.getParentFile().mkdirs()
- newFile.createNewFile()
- assertThat(newFile).exists()
- return newFile
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PkgOverrideTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PkgOverrideTest.groovy
deleted file mode 100644
index 4c0263f..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PkgOverrideTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for pkgOverride.
- */
- at CompileStatic
-class PkgOverrideTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("pkgOverride")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PrivateResourceTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PrivateResourceTest.groovy
deleted file mode 100644
index 1bdf40d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PrivateResourceTest.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * Assemble tests for privateResources.
- */
- at CompileStatic
-class PrivateResourceTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("privateResources")
- .create()
-
- @BeforeClass
- static void setup() {
- project.execute("clean", "assemble");
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check private resources resources"() {
- String expected = """\
-string mylib_app_name
-string mylib_public_string
-string shared_name
-id shared_name
-"""
- assertThatZip(project.getSubproject('mylibrary').getAar("release")).containsFileWithContent('public.txt', expected);
- assertThatZip(project.getSubproject('mylibrary').getAar("debug")).containsFileWithContent('public.txt', expected);
-
- // No public resources: file should exist but be empty
- assertThatZip(project.getSubproject('mylibrary2').getAar("debug")).containsFileWithContent('public.txt', "");
- assertThatZip(project.getSubproject('mylibrary2').getAar("release")).containsFileWithContent('public.txt', "");
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PseudoLocalizationTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PseudoLocalizationTest.groovy
deleted file mode 100644
index 1916c2f..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PseudoLocalizationTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-/**
- * Test for pseudolocalized.
- */
- at CompileStatic
-class PseudoLocalizationTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("pseudolocalized")
- .create()
-
- @BeforeClass
- static void setUp() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- public void testPseudolocalization() throws Exception {
- assertThatApk(project.getApk("debug")).locales().containsAllOf("en-XA", "ar-XB")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenamedApkTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenamedApkTest.groovy
deleted file mode 100644
index 2340faf..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenamedApkTest.groovy
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidArtifactOutput
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-
-/**
- * Assemble tests for renamedApk.
- */
- at CompileStatic
-class RenamedApkTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("renamedApk")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check model reflects renamed apk"() throws Exception {
- File projectDir = project.getTestDir()
-
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 2 , variants.size())
-
- File buildDir = new File(projectDir, "build")
-
- for (Variant variant : variants) {
- AndroidArtifact mainInfo = variant.getMainArtifact()
- assertNotNull(
- "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
- mainInfo)
-
- AndroidArtifactOutput output = mainInfo.getOutputs().iterator().next()
-
- assertEquals("Output file for " + variant.getName(),
- new File(buildDir, variant.getName() + ".apk"),
- output.getMainOutputFile().getOutputFile())
- }
- }
-
- @Test
- void "check renamed apk"() {
- File debugApk = project.file("build/debug.apk")
- assertTrue("Check output file: " + debugApk, debugApk.isFile())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptMultiSrcTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptMultiSrcTest.groovy
deleted file mode 100644
index 5145c9a..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptMultiSrcTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Assemble tests for renderscriptMultiSrc.
- */
- at CompileStatic
-class RenderscriptMultiSrcTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("renderscriptMultiSrc")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptTest.groovy
deleted file mode 100644
index 4ae1e5f..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Assemble tests for renderscript.
- */
- at CompileStatic
-class RenderscriptTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("renderscript")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTest.groovy
deleted file mode 100644
index 155f81e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTest.groovy
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.ClassField
-import com.android.builder.model.Variant
-import com.google.common.base.Charsets
-import com.google.common.collect.Maps
-import com.google.common.io.Files
-import groovy.transform.CompileStatic
-import org.junit.Assert
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-
-/**
- * Test for Res Values declared in build type, flavors, and variant and how they
- * override each other
- */
- at CompileStatic
-class ResValueTest {
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- private static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
- apply plugin: 'com.android.application'
-
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- defaultConfig {
- resValue "string", "VALUE_DEFAULT", "1"
- resValue "string", "VALUE_DEBUG", "1"
- resValue "string", "VALUE_FLAVOR", "1"
- resValue "string", "VALUE_VARIANT", "1"
- }
-
- buildTypes {
- debug {
- resValue "string", "VALUE_DEBUG", "100"
- resValue "string", "VALUE_VARIANT", "100"
- }
- }
-
- productFlavors {
- flavor1 {
- resValue "string", "VALUE_DEBUG", "10"
- resValue "string", "VALUE_FLAVOR", "10"
- resValue "string", "VALUE_VARIANT", "10"
- }
- flavor2 {
- resValue "string", "VALUE_DEBUG", "20"
- resValue "string", "VALUE_FLAVOR", "20"
- resValue "string", "VALUE_VARIANT", "20"
- }
- }
-
- applicationVariants.all { variant ->
- if (variant.buildType.name == "debug") {
- variant.resValue "string", "VALUE_VARIANT", "1000"
- }
- }
- }
- """.stripIndent()
-
- model = project.executeAndReturnModel(
- 'clean',
- 'generateFlavor1DebugResValue',
- 'generateFlavor1ReleaseResValue',
- 'generateFlavor2DebugResValue',
- 'generateFlavor2ReleaseResValue')
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void builFlavor1Debug() {
- String expected =
-"""<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <!-- Automatically generated file. DO NOT MODIFY -->
-
- <!-- Values from the variant -->
- <string name="VALUE_VARIANT" translatable="false">1000</string>
- <!-- Values from build type: debug -->
- <string name="VALUE_DEBUG" translatable="false">100</string>
- <!-- Values from product flavor: flavor1 -->
- <string name="VALUE_FLAVOR" translatable="false">10</string>
- <!-- Values from default config. -->
- <string name="VALUE_DEFAULT" translatable="false">1</string>
-
-</resources>
-"""
- checkBuildConfig(expected, 'flavor1/debug')
- }
-
- @Test
- void modelFlavor1Debug() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '10')
- map.put('VALUE_DEBUG', '100')
- map.put('VALUE_VARIANT', '1000')
- checkVariant(model.getVariants(), 'flavor1Debug', map)
- }
-
- @Test
- void buildFlavor2Debug() {
- String expected =
-"""<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <!-- Automatically generated file. DO NOT MODIFY -->
-
- <!-- Values from the variant -->
- <string name="VALUE_VARIANT" translatable="false">1000</string>
- <!-- Values from build type: debug -->
- <string name="VALUE_DEBUG" translatable="false">100</string>
- <!-- Values from product flavor: flavor2 -->
- <string name="VALUE_FLAVOR" translatable="false">20</string>
- <!-- Values from default config. -->
- <string name="VALUE_DEFAULT" translatable="false">1</string>
-
-</resources>
-"""
- checkBuildConfig(expected, 'flavor2/debug')
- }
-
- @Test
- void modelFlavor2Debug() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '20')
- map.put('VALUE_DEBUG', '100')
- map.put('VALUE_VARIANT', '1000')
- checkVariant(model.getVariants(), 'flavor2Debug', map)
- }
-
- @Test
- void buildFlavor1Release() {
- String expected =
-"""<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <!-- Automatically generated file. DO NOT MODIFY -->
-
- <!-- Values from product flavor: flavor1 -->
- <string name="VALUE_DEBUG" translatable="false">10</string>
- <string name="VALUE_FLAVOR" translatable="false">10</string>
- <string name="VALUE_VARIANT" translatable="false">10</string>
- <!-- Values from default config. -->
- <string name="VALUE_DEFAULT" translatable="false">1</string>
-
-</resources>
-"""
- checkBuildConfig(expected, 'flavor1/release')
- }
-
- @Test
- void modelFlavor1Release() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '10')
- map.put('VALUE_DEBUG', '10')
- map.put('VALUE_VARIANT', '10')
- checkVariant(model.getVariants(), 'flavor1Release', map)
- }
-
- @Test
- void buildFlavor2Release() {
- String expected =
-"""<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <!-- Automatically generated file. DO NOT MODIFY -->
-
- <!-- Values from product flavor: flavor2 -->
- <string name="VALUE_DEBUG" translatable="false">20</string>
- <string name="VALUE_FLAVOR" translatable="false">20</string>
- <string name="VALUE_VARIANT" translatable="false">20</string>
- <!-- Values from default config. -->
- <string name="VALUE_DEFAULT" translatable="false">1</string>
-
-</resources>
-"""
- checkBuildConfig(expected, 'flavor2/release')
- }
-
- @Test
- void modelFlavor2Release() {
- Map<String, String> map = Maps.newHashMap()
- map.put('VALUE_DEFAULT', '1')
- map.put('VALUE_FLAVOR', '20')
- map.put('VALUE_DEBUG', '20')
- map.put('VALUE_VARIANT', '20')
- checkVariant(model.getVariants(), 'flavor2Release', map)
- }
-
- private static void checkBuildConfig(@NonNull String expected, @NonNull String variantDir) {
- File outputFile = new File(project.getTestDir(),
- "build/generated/res/resValues/$variantDir/values/generated.xml")
- Assert.assertTrue("Missing file: " + outputFile, outputFile.isFile())
- assertEquals(expected, Files.asByteSource(outputFile).asCharSource(Charsets.UTF_8).read())
- }
-
- private static void checkVariant(
- @NonNull Collection<Variant> variants,
- @NonNull String variantName,
- @Nullable Map<String, String> valueMap) {
- Variant variant = ModelHelper.findVariantByName(variants, variantName)
- assertNotNull("${variantName} variant null-check", variant)
-
- AndroidArtifact artifact = variant.getMainArtifact()
- assertNotNull("${variantName} main artifact null-check", artifact)
-
- Map<String, ClassField> value = artifact.getResValues()
- assertNotNull(value)
-
- // check the map against the expected one.
- assertEquals(valueMap.keySet(), value.keySet())
- for (String key : valueMap.keySet()) {
- ClassField field = value.get(key)
- assertNotNull("${variantName}: expected field ${key}", field)
- assertEquals(
- "${variantName}: check Value of ${key}",
- valueMap.get(key),
- field.getValue())
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTypeTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTypeTest.groovy
deleted file mode 100644
index 917087a..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTypeTest.groovy
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertTrue
-
-/**
- * Test resValue for string type is treated as String.
- */
- at CompileStatic
-class ResValueTypeTest {
- static AndroidTestApp app = new HelloWorldApp()
- static {
- app.removeFile(app.getFile("HelloWorldTest.java"))
- app.addFile(new TestSourceFile("src/androidTest/java/com/example/helloworld", "ResValueTest.java",
-"""
-package com.example.helloworld;
-
-import android.test.AndroidTestCase;
-
-public class ResValueTest extends AndroidTestCase {
- public void testResValue() {
- assertEquals("00", getContext().getString(R.string.resString));
- }
-}
-"""))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(app)
- .create()
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- defaultConfig {
- resValue "array", "resArray", "foo"
- resValue "attr", "resAttr", "foo"
- resValue "bool", "resBool", "true"
- resValue "color", "resColor", "#ffffff"
- resValue "declare-styleable", "resDeclareStyleable", "foo"
- resValue "dimen", "resDimen", "42px"
- resValue "fraction", "resFraction", "42%"
- resValue "id", "resId", "42"
- resValue "integer", "resInteger", "42"
- resValue "plurals", "resPlurals", "s"
- resValue "string", "resString", "00" // resString becomes "0" if it is incorrectly treated as int.
- resValue "style", "resStyle", "foo"
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- app = null
- }
-
- @Test
- void "check <string> tag is used in generated.xml" () {
- project.execute("clean", "generateDebugResValue")
- File outputFile = project.file("build/generated/res/resValues/debug/values/generated.xml")
- assertTrue("Missing file: " + outputFile, outputFile.isFile())
- assertEquals(
-"""<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <!-- Automatically generated file. DO NOT MODIFY -->
-
- <!-- Values from default config. -->
- <array name="resArray">foo</array>
-
- <attr name="resAttr">foo</attr>
-
- <bool name="resBool">true</bool>
-
- <color name="resColor">#ffffff</color>
-
- <declare-styleable name="resDeclareStyleable">foo</declare-styleable>
-
- <dimen name="resDimen">42px</dimen>
-
- <fraction name="resFraction">42%</fraction>
-
- <item name="resId" type="id">42</item>
-
- <integer name="resInteger">42</integer>
-
- <plurals name="resPlurals">s</plurals>
-
- <string name="resString" translatable="false">00</string>
-
- <style name="resStyle">foo</style>
-
-</resources>
-""",
- outputFile.getText("UTF-8"))
- }
-
- @Test
- @Category(DeviceTests.class)
- void "check resValue is treated as string"() {
- project.execute("clean")
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsSupportModeTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsSupportModeTest.groovy
deleted file mode 100644
index 01b3d5f..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsSupportModeTest.groovy
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.SdkConstants
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-
-/**
- * Assemble tests for rsSupportMode.
- */
- at CompileStatic
-class RsSupportModeTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("rsSupportMode")
- .addGradleProperties("android.useDeprecatedNdk=true")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- model =project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void testRsSupportMode() throws Exception {
- Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "x86Debug")
- assertNotNull("x86Debug variant null-check", debugVariant)
-
- AndroidArtifact mainArtifact = debugVariant.getMainArtifact()
- Dependencies dependencies = mainArtifact.getDependencies()
-
- assertFalse(dependencies.getJavaLibraries().isEmpty())
-
- boolean foundSupportJar = false
- for (JavaLibrary lib : dependencies.getJavaLibraries()) {
- File file = lib.getJarFile()
- if (SdkConstants.FN_RENDERSCRIPT_V8_JAR.equals(file.getName())) {
- foundSupportJar = true
- break
- }
- }
-
- assertTrue("Found suppport jar check", foundSupportJar)
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ShrinkTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ShrinkTest.groovy
deleted file mode 100644
index 93f22bd..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ShrinkTest.groovy
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.google.common.base.Joiner
-import com.google.common.collect.Lists
-import com.google.common.io.ByteStreams
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import java.util.jar.JarInputStream
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-import java.util.zip.ZipEntry
-import java.util.zip.ZipInputStream
-
-import static java.io.File.separator
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertTrue
-
-/**
- * Assemble tests for shrink.
- */
- at CompileStatic
-class ShrinkTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("shrink")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleRelease", "assembleDebug", "assembleProguardNoShrink")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check shrink resources"() {
- File intermediates = project.file("build/" + AndroidProject.FD_INTERMEDIATES)
-
- // The release target has shrinking enabled.
- // The proguardNoShrink target has proguard but no shrinking enabled.
- // The debug target has neither proguard nor shrinking enabled.
-
- File apkRelease = project.getApk("release", "unsigned")
- File apkDebug = project.getApk("debug")
- File apkProguardOnly = project.getApk("proguardNoShrink", "unsigned")
-
- assertTrue(apkDebug.toString() + " is not a file", apkDebug.isFile())
- assertTrue(apkRelease.toString() + " is not a file", apkRelease.isFile())
- assertTrue(apkProguardOnly.toString() + " is not a file", apkProguardOnly.isFile())
-
- File compressed = new File(intermediates,
- "res" + separator + "resources-release-stripped.ap_")
- File uncompressed =
- new File(intermediates, "res" + separator + "resources-release.ap_")
- assertTrue(compressed.toString() + " is not a file", compressed.isFile())
- assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
-
- // Check that there is no shrinking in the other two targets:
- assertTrue(new File(intermediates,
- "res" + separator + "resources-debug.ap_").exists())
- assertFalse(new File(intermediates,
- "res" + separator + "resources-debug-stripped.ap_").exists())
- assertTrue(new File(intermediates,
- "res" + separator + "resources-proguardNoShrink.ap_").exists())
- assertFalse(new File(intermediates,
- "res" + separator + "resources-proguardNoShrink-stripped.ap_").exists())
-
- String expectedUnstrippedApk = """\
-AndroidManifest.xml
-classes.dex
-res/raw/keep.xml
-res/layout/l_used_a.xml
-res/layout/l_used_b2.xml
-res/layout/l_used_c.xml
-res/layout/lib_unused.xml
-res/layout/prefix_3_suffix.xml
-res/layout/prefix_used_1.xml
-res/layout/prefix_used_2.xml
-resources.arsc
-res/layout/unused1.xml
-res/layout/unused2.xml
-res/drawable/unused9.xml
-res/drawable/unused10.xml
-res/drawable/unused11.xml
-res/menu/unused12.xml
-res/layout/unused13.xml
-res/layout/unused14.xml
-res/layout/used1.xml
-res/layout/used2.xml
-res/layout/used3.xml
-res/layout/used4.xml
-res/layout/used5.xml
-res/layout/used6.xml
-res/layout/used7.xml
-res/layout/used8.xml
-res/drawable/used9.xml
-res/drawable/used10.xml
-res/drawable/used11.xml
-res/drawable/used12.xml
-res/menu/used13.xml
-res/layout/used14.xml
-res/drawable/used15.xml
-res/layout/used16.xml
-res/layout/used17.xml
-res/layout/used18.xml
-res/layout/used19.xml
-res/layout/used20.xml
-res/layout/used21.xml"""
-
- String expectedStrippedApkContents = """\
-AndroidManifest.xml
-classes.dex
-res/layout/l_used_a.xml
-res/layout/l_used_b2.xml
-res/layout/l_used_c.xml
-res/layout/prefix_3_suffix.xml
-res/layout/prefix_used_1.xml
-res/layout/prefix_used_2.xml
-resources.arsc
-res/layout/used1.xml
-res/layout/used2.xml
-res/layout/used3.xml
-res/layout/used4.xml
-res/layout/used5.xml
-res/layout/used6.xml
-res/layout/used7.xml
-res/layout/used8.xml
-res/drawable/used9.xml
-res/drawable/used10.xml
-res/drawable/used11.xml
-res/drawable/used12.xml
-res/menu/used13.xml
-res/layout/used14.xml
-res/drawable/used15.xml
-res/layout/used16.xml
-res/layout/used17.xml
-res/layout/used18.xml
-res/layout/used19.xml
-res/layout/used20.xml
-res/layout/used21.xml"""
-
- // Should not have any unused resources in the compressed list
- assertFalse(expectedStrippedApkContents, expectedStrippedApkContents.contains("unused"))
- // Should have *all* the used resources, currently 1-21
- for (int i = 1; i <= 21; i++) {
- assertTrue("Missing used" + i + " in " + expectedStrippedApkContents,
- expectedStrippedApkContents.contains("/used" + i + "."))
- }
-
- // Check that the uncompressed resources (.ap_) for the release target have everything
- // we expect
- String expectedUncompressed = expectedUnstrippedApk.replace("classes.dex\n", "")
- assertEquals(expectedUncompressed, dumpZipContents(uncompressed).trim())
-
- // The debug target should have everything there in the APK
- assertEquals(expectedUnstrippedApk, dumpZipContents(apkDebug))
- assertEquals(expectedUnstrippedApk, dumpZipContents(apkProguardOnly))
-
- // Check the compressed .ap_:
- String actualCompressed = dumpZipContents(compressed)
- String expectedCompressed = expectedStrippedApkContents.replace("classes.dex\n", "")
- assertEquals(expectedCompressed, actualCompressed)
- assertFalse(expectedCompressed, expectedCompressed.contains("unused"))
- assertEquals(expectedStrippedApkContents, dumpZipContents(apkRelease))
-
- // Check splits -- just sample one of them
- //noinspection SpellCheckingInspection
- compressed = project.file(
- "abisplits/build/intermediates/res/resources-arm64-v8a-release-stripped.ap_")
- //noinspection SpellCheckingInspection
- uncompressed =
- project.file(
- "abisplits/build/intermediates/res/resources-arm64-v8a-release.ap_")
- assertTrue(compressed.toString() + " is not a file", compressed.isFile())
- assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
- //noinspection SpellCheckingInspection
- assertEquals(""
- + "AndroidManifest.xml\n"
- + "resources.arsc\n"
- + "res/layout/used.xml",
- dumpZipContents(compressed))
- //noinspection SpellCheckingInspection
- assertEquals(""
- + "AndroidManifest.xml\n"
- + "resources.arsc\n"
- + "res/layout/unused.xml\n"
- + "res/layout/used.xml",
- dumpZipContents(uncompressed))
-
- // Check WebView string handling (android_res strings etc)
-
- //noinspection SpellCheckingInspection
- uncompressed = project.file("webview/build/intermediates/res/resources-release.ap_")
- //noinspection SpellCheckingInspection
- compressed = project.file("webview/build/intermediates/res/resources-release-stripped.ap_")
- assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
- assertTrue(compressed.toString() + " is not a file", compressed.isFile())
-
- //noinspection SpellCheckingInspection
- assertEquals(""
- + "AndroidManifest.xml\n"
- + "res/xml/my_xml.xml\n"
- + "resources.arsc\n"
- + "res/raw/unknown\n"
- + "res/raw/unused_icon.png\n"
- + "res/raw/unused_index.html\n"
- + "res/drawable/used1.xml\n"
- + "res/raw/used_icon.png\n"
- + "res/raw/used_icon2.png\n"
- + "res/raw/used_index.html\n"
- + "res/raw/used_index2.html\n"
- + "res/raw/used_index3.html\n"
- + "res/layout/used_layout1.xml\n"
- + "res/layout/used_layout2.xml\n"
- + "res/layout/used_layout3.xml\n"
- + "res/raw/used_script.js\n"
- + "res/raw/used_styles.css\n"
- + "res/layout/webview.xml",
- dumpZipContents(uncompressed))
-
- //noinspection SpellCheckingInspection
- assertEquals(""
- + "AndroidManifest.xml\n"
- + "resources.arsc\n"
- + "res/raw/unknown\n"
- + "res/drawable/used1.xml\n"
- + "res/raw/used_icon.png\n"
- + "res/raw/used_icon2.png\n"
- + "res/raw/used_index.html\n"
- + "res/raw/used_index2.html\n"
- + "res/raw/used_index3.html\n"
- + "res/layout/used_layout1.xml\n"
- + "res/layout/used_layout2.xml\n"
- + "res/layout/used_layout3.xml\n"
- + "res/raw/used_script.js\n"
- + "res/raw/used_styles.css\n"
- + "res/layout/webview.xml",
- dumpZipContents(compressed))
-
- // Check stored vs deflated state:
- // This is the state of the original source _ap file:
- assertEquals(""
- + " stored resources.arsc\n"
- + "deflated AndroidManifest.xml\n"
- + "deflated res/xml/my_xml.xml\n"
- + "deflated res/raw/unknown\n"
- + " stored res/raw/unused_icon.png\n"
- + "deflated res/raw/unused_index.html\n"
- + "deflated res/drawable/used1.xml\n"
- + " stored res/raw/used_icon.png\n"
- + " stored res/raw/used_icon2.png\n"
- + "deflated res/raw/used_index.html\n"
- + "deflated res/raw/used_index2.html\n"
- + "deflated res/raw/used_index3.html\n"
- + "deflated res/layout/used_layout1.xml\n"
- + "deflated res/layout/used_layout2.xml\n"
- + "deflated res/layout/used_layout3.xml\n"
- + "deflated res/raw/used_script.js\n"
- + "deflated res/raw/used_styles.css\n"
- + "deflated res/layout/webview.xml",
- dumpZipContents(uncompressed, true))
-
- // This is the state of the rewritten ap_ file: the zip states should match
- assertEquals(""
- + " stored resources.arsc\n"
- + "deflated AndroidManifest.xml\n"
- + "deflated res/raw/unknown\n"
- + "deflated res/drawable/used1.xml\n"
- + " stored res/raw/used_icon.png\n"
- + " stored res/raw/used_icon2.png\n"
- + "deflated res/raw/used_index.html\n"
- + "deflated res/raw/used_index2.html\n"
- + "deflated res/raw/used_index3.html\n"
- + "deflated res/layout/used_layout1.xml\n"
- + "deflated res/layout/used_layout2.xml\n"
- + "deflated res/layout/used_layout3.xml\n"
- + "deflated res/raw/used_script.js\n"
- + "deflated res/raw/used_styles.css\n"
- + "deflated res/layout/webview.xml",
- dumpZipContents(compressed, true))
-
- // Make sure the (remaining) binary contents of the files in the compressed APK are
- // identical to the ones in uncompressed:
- FileInputStream fis1 = new FileInputStream(compressed)
- JarInputStream zis1 = new JarInputStream(fis1)
- FileInputStream fis2 = new FileInputStream(uncompressed)
- JarInputStream zis2 = new JarInputStream(fis2)
-
- ZipEntry entry1 = zis1.getNextEntry()
- ZipEntry entry2 = zis2.getNextEntry()
- while (entry1 != null) {
- String name1 = entry1.getName()
- String name2 = entry2.getName()
- while (!name1.equals(name2)) {
- // uncompressed should contain a superset of all the names in compressed
- entry2 = zis2.getNextJarEntry()
- name2 = entry2.getName()
- }
- assertEquals(name1, name2)
- if (!entry1.isDirectory()) {
- byte[] bytes1 = ByteStreams.toByteArray(zis1)
- byte[] bytes2 = ByteStreams.toByteArray(zis2)
- assertTrue(name1, Arrays.equals(bytes1, bytes2))
- } else {
- assertTrue(entry2.isDirectory())
- }
- entry1 = zis1.getNextEntry()
- entry2 = zis2.getNextEntry()
- }
-
- zis1.close()
- zis2.close()
-
- //noinspection SpellCheckingInspection
- uncompressed = project.file("keep/build/intermediates/res/resources-release.ap_")
- //noinspection SpellCheckingInspection
- compressed =
- project.file("keep/build/intermediates/res/resources-release-stripped.ap_")
- assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
- assertTrue(compressed.toString() + " is not a file", compressed.isFile())
-
- //noinspection SpellCheckingInspection
- assertEquals(""
- + "AndroidManifest.xml\n"
- + "res/raw/keep.xml\n"
- + "resources.arsc\n"
- + "res/layout/unused1.xml\n"
- + "res/layout/unused2.xml\n"
- + "res/layout/used1.xml",
- dumpZipContents(uncompressed))
-
- //noinspection SpellCheckingInspection
- assertEquals(""
- + "AndroidManifest.xml\n"
- + "resources.arsc\n"
- + "res/layout/used1.xml",
- dumpZipContents(compressed))
- }
-
- private static List<String> getZipPaths(File zipFile, boolean includeMethod)
- throws IOException {
- List<String> lines = Lists.newArrayList()
- FileInputStream fis = new FileInputStream(zipFile)
- try {
- ZipInputStream zis = new ZipInputStream(fis)
- try {
- ZipEntry entry = zis.getNextEntry()
- while (entry != null) {
- String path = entry.getName()
- if (includeMethod) {
- String method
- switch (entry.getMethod()) {
- case ZipEntry.STORED: method = " stored"; break
- case ZipEntry.DEFLATED: method = "deflated"; break
- default: method = " unknown"; break
- }
- path = method + " " + path
- }
- lines.add(path)
- entry = zis.getNextEntry()
- }
- } finally {
- zis.close()
- }
- } finally {
- fis.close()
- }
-
- return lines
- }
-
- private static String dumpZipContents(File zipFile) throws IOException {
- return dumpZipContents(zipFile, false)
- }
-
- private static String dumpZipContents(File zipFile, final boolean includeMethod)
- throws IOException {
- List<String> lines = getZipPaths(zipFile, includeMethod)
-
- // Remove META-INF statements
- ListIterator<String> iterator = lines.listIterator()
- while (iterator.hasNext()) {
- if (iterator.next().startsWith("META-INF/")) {
- iterator.remove()
- }
- }
-
- // Sort by base name (and numeric sort such that unused10 comes after unused9)
- final Pattern pattern = Pattern.compile("(.*[^\\d])(\\d+)(\\..+)?")
- Collections.sort(lines, new Comparator<String>() {
-
- @Override
- public int compare(String line1, String line2) {
- String name1 = line1.substring(line1.lastIndexOf('/') + 1)
- String name2 = line2.substring(line2.lastIndexOf('/') + 1)
- int delta = name1.compareTo(name2)
- if (delta != 0) {
- // Try to do numeric sort
- Matcher match1 = pattern.matcher(name1)
- if (match1.matches()) {
- Matcher match2 = pattern.matcher(name2)
- //noinspection ConstantConditions
- if (match2.matches() && match1.group(1).equals(match2.group(1))) {
- //noinspection ConstantConditions
- int num1 = Integer.parseInt(match1.group(2))
- //noinspection ConstantConditions
- int num2 = Integer.parseInt(match2.group(2))
- if (num1 != num2) {
- return num1 - num2
- }
- }
- }
- return delta
- }
-
- if (includeMethod) {
- line1 = line1.substring(10)
- line2 = line2.substring(10)
- }
- return line1.compareTo(line2)
- }
- })
-
- return Joiner.on('\n').join(lines)
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/SigningConfigTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/SigningConfigTest.groovy
deleted file mode 100644
index 16b7af7..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/SigningConfigTest.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.google.common.collect.ImmutableList
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_ALIAS
-import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_PASSWORD
-import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_FILE
-import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_PASSWORD
-
-/**
- * Integration test with signing overrider.
- */
- at CompileStatic
-class SigningConfigTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("basic")
- .create()
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check assemble with injected signing config"() {
- // add prop args for signing override.
- List<String> args = ImmutableList.of(
- "-P" + PROPERTY_SIGNING_STORE_FILE + "=" + project.file("debug.keystore").getPath(),
- "-P" + PROPERTY_SIGNING_STORE_PASSWORD + "=android",
- "-P" + PROPERTY_SIGNING_KEY_ALIAS + "=AndroidDebugKey",
- "-P" + PROPERTY_SIGNING_KEY_PASSWORD + "=android")
-
- project.execute(args, "clean", "assembleRelease")
-
- // Check for signing file inside the archive.
- assertThatZip(project.getApk("release")).contains("META-INF/CERT.RSA")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TictactoeTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TictactoeTest.groovy
deleted file mode 100644
index ba713a2..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TictactoeTest.groovy
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.builder.core.BuilderConstants.DEBUG
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * Assemble tests for tictactoe.
- */
- at CompileStatic
-class TictactoeTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("tictactoe")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- models = project.executeAndReturnMultiModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- public void testModel() throws Exception {
- AndroidProject libModel = models.get(":lib")
- assertNotNull("lib module model null-check", libModel)
- assertTrue("lib module library flag", libModel.isLibrary())
-
- AndroidProject appModel = models.get(":app")
- assertNotNull("app module model null-check", appModel)
-
- Collection<Variant> variants = appModel.getVariants()
- Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
- assertNotNull("debug variant null-check", debugVariant)
-
- Dependencies dependencies = debugVariant.getMainArtifact().getDependencies()
- assertNotNull(dependencies)
-
- Collection<AndroidLibrary> libs = dependencies.getLibraries()
- assertNotNull(libs)
- assertEquals(1, libs.size())
-
- AndroidLibrary androidLibrary = libs.iterator().next()
- assertNotNull(androidLibrary)
-
- assertEquals("Dependency project path", ":lib", androidLibrary.getProject())
-
- // TODO: right now we can only test the folder name efficiently
- String path = androidLibrary.getFolder().getPath()
- assertTrue(path, path.endsWith("/tictactoe/lib/unspecified"))
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/UnitTestingModelTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/UnitTestingModelTest.groovy
deleted file mode 100644
index a15808a..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/UnitTestingModelTest.groovy
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.ArtifactMetaData
-import com.android.builder.model.JavaArtifact
-import groovy.transform.CompileStatic
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.builder.model.AndroidProject.ARTIFACT_UNIT_TEST
-import static com.google.common.truth.Truth.assertThat
-/**
- * Tests for the unit-tests related parts of the builder model.
- */
- at CompileStatic
-class UnitTestingModelTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("unitTestingComplexProject")
- .create();
-
- @Test
- public void "Unit testing artifacts are included in the model"() {
- AndroidProject model = project.allModels[":app"]
-
- assertThat(model.extraArtifacts*.name).containsExactly(
- AndroidProject.ARTIFACT_ANDROID_TEST,
- ARTIFACT_UNIT_TEST)
-
- def unitTestMetadata = model.extraArtifacts.find { it.name == ARTIFACT_UNIT_TEST }
-
- assert unitTestMetadata.isTest()
- assert unitTestMetadata.type == ArtifactMetaData.TYPE_JAVA
-
- for (variant in model.variants) {
- def unitTestArtifacts = variant.extraJavaArtifacts.findAll {
- it.name == ARTIFACT_UNIT_TEST
- }
- assert unitTestArtifacts.size() == 1
-
- JavaArtifact unitTestArtifact = unitTestArtifacts.first()
- assert unitTestArtifact.name == ARTIFACT_UNIT_TEST
- assertThat(unitTestArtifact.assembleTaskName).contains("UnitTest")
- assertThat(unitTestArtifact.assembleTaskName).contains(variant.name.capitalize())
- assertThat(unitTestArtifact.compileTaskName).contains("UnitTest")
- assertThat(unitTestArtifact.compileTaskName).contains(variant.name.capitalize())
-
- // No per-variant source code.
- assertThat(unitTestArtifact.variantSourceProvider).isNull()
- assertThat(unitTestArtifact.multiFlavorSourceProvider).isNull()
-
- assertThat(variant.mainArtifact.javaResourcesFolder.path)
- .endsWith("intermediates/javaResources/" + variant.name)
- assertThat(unitTestArtifact.javaResourcesFolder.path)
- .endsWith("intermediates/javaResources/test/" + variant.name)
- }
-
- def sourceProvider = model.defaultConfig
- .extraSourceProviders
- .find { it.artifactName == ARTIFACT_UNIT_TEST }
- .sourceProvider
-
- assertThat(sourceProvider.javaDirectories).hasSize(1)
- assertThat(sourceProvider.javaDirectories.first().absolutePath).endsWith("test/java")
- }
-
- @Test
- public void flavors() throws Exception {
- project.getSubproject("app").buildFile << """
-android {
- productFlavors { paid; free }
-}
-"""
- AndroidProject model = project.allModels[":app"]
-
- assertThat(model.productFlavors).hasSize(2)
-
- for (flavor in model.productFlavors) {
- def sourceProvider = flavor.extraSourceProviders
- .find { it.artifactName == ARTIFACT_UNIT_TEST }
- .sourceProvider
-
- assertThat(sourceProvider.javaDirectories).hasSize(1)
- assertThat(sourceProvider.javaDirectories.first().absolutePath)
- .endsWith("test${flavor.productFlavor.name.capitalize()}/java")
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.groovy
deleted file mode 100644
index 9b39669..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.groovy
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.utils.FileUtils
-import com.google.common.io.Files
-import groovy.transform.CompileStatic
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertWithMessage
-import static com.google.common.base.Charsets.UTF_8
-/**
- * Tests for the PNG generation feature.
- */
- at CompileStatic
-class VectorDrawableTest {
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("vectorDrawables")
- .create()
-
- @BeforeClass
- public static void checkBuildTools() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- }
-
- @Test
- public void "vector file is moved and PNGs are generated"() throws Exception {
- project.execute("clean", "assembleDebug")
- File apk = project.getApk("debug")
- assertThatApk(apk).containsResource("drawable/icon.png")
- assertThatApk(apk).doesNotContainResource("drawable/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml")
- assertThatApk(apk).containsResource("drawable-hdpi-v21/heart.xml")
- assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png")
- assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart.png")
- assertThatApk(apk).containsResource("drawable-xhdpi-v21/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v22/no_need.png")
- assertThatApk(apk).containsResource("drawable-v22/no_need.xml")
-
- // Check HDPI. Test project contains the hdpi png, it should be used instead of the
- // generated one.
- File originalPng = new File(
- project.testDir,
- "src/main/res/drawable-hdpi/special_heart.png")
- File generatedPng = new File(
- project.testDir,
- "build/generated/res/pngs/debug/drawable-hdpi/special_heart.png")
- File pngToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable-hdpi/special_heart.png")
-
- assertThat(generatedPng).doesNotExist()
- assertWithMessage("Wrong file used.")
- .that(FileUtils.sha1(pngToUse))
- .isEqualTo(FileUtils.sha1(originalPng))
-
- // Check XHDPI.
- generatedPng = new File(
- project.testDir,
- "build/generated/res/pngs/debug/drawable-xhdpi/special_heart.png")
- pngToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable-xhdpi/special_heart.png")
-
- assertWithMessage("Wrong file used.")
- .that(FileUtils.sha1(pngToUse))
- .isEqualTo(FileUtils.sha1(generatedPng))
-
- // Check interactions with other qualifiers.
- assertThatApk(apk).containsResource("drawable-fr-hdpi-v21/french_heart.xml")
- assertThatApk(apk).containsResource("drawable-fr-xhdpi-v21/french_heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/french_heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-fr/french_heart.xml")
- assertThatApk(apk).containsResource("drawable-fr-hdpi-v4/french_heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi/french_heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/french_heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-fr/french_heart.png")
-
- assertThatApk(apk).containsResource("drawable-hdpi-v21/modern_heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-v16/modern_heart.xml")
- assertThatApk(apk).containsResource("drawable-hdpi-v16/modern_heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-v16/modern_heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/modern_heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi/modern_heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/modern_heart.png")
- }
-
- @Test
- public void "incremental build: add xml"() throws Exception {
- project.execute("assembleDebug")
-
- File heartXml = new File(project.testDir, "src/main/res/drawable/heart.xml")
- File heartXmlCopy = new File(project.testDir, "src/main/res/drawable/heart_copy.xml")
- Files.copy(heartXml, heartXmlCopy)
-
- project.execute("assembleDebug")
- checkIncrementalBuild()
-
- File apk = project.getApk("debug")
- assertThatApk(apk).containsResource("drawable/icon.png")
- assertThatApk(apk).doesNotContainResource("drawable/heart_copy.xml")
- assertThatApk(apk).containsResource("drawable-hdpi-v21/heart_copy.xml")
- assertThatApk(apk).containsResource("drawable-xhdpi-v21/heart_copy.xml")
- assertThatApk(apk).containsResource("drawable-hdpi-v4/heart_copy.png")
- assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart_copy.png")
- }
-
- @Test
- public void "incremental build: delete xml"() throws Exception {
- project.execute("assembleDebug")
-
- File heartXml = new File(project.testDir, "src/main/res/drawable/heart.xml")
- heartXml.delete()
-
- project.execute("assembleDebug")
- checkIncrementalBuild()
-
- File apk = project.getApk("debug")
- assertThatApk(apk).containsResource("drawable/icon.png")
- assertThatApk(apk).doesNotContainResource("drawable/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-xhdpi/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png")
- }
-
- @Test
- public void "incremental build: delete png"() throws Exception {
- project.execute("assembleDebug")
-
- File generatedPng = new File(
- project.testDir,
- "build/generated/res/pngs/debug/drawable-hdpi/special_heart.png")
- File originalPng = new File(
- project.testDir,
- "src/main/res/drawable-hdpi/special_heart.png")
- File pngToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable-hdpi/special_heart.png")
-
- assertThat(generatedPng).doesNotExist()
- assertWithMessage("Wrong file used.")
- .that(FileUtils.sha1(pngToUse))
- .isEqualTo(FileUtils.sha1(originalPng))
-
- originalPng.delete()
-
- project.execute("assembleDebug")
- checkIncrementalBuild()
-
- assertWithMessage("Wrong file used.")
- .that(FileUtils.sha1(pngToUse))
- .isEqualTo(FileUtils.sha1(generatedPng))
- }
-
- @Test
- public void "incremental build: add png"() throws Exception {
- project.execute("assembleDebug")
-
- File generatedPng = new File(
- project.testDir,
- "build/generated/res/pngs/debug/drawable-xhdpi/special_heart.png")
- File pngToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable-xhdpi/special_heart.png")
-
- assertWithMessage("Wrong file used.")
- .that(FileUtils.sha1(pngToUse))
- .isEqualTo(FileUtils.sha1(generatedPng))
-
- // Create a PNG file for XHDPI. It should be used instead of the generated one.
- File hdpiPng = new File(project.testDir, "src/main/res/drawable-hdpi/special_heart.png")
- File xhdpiPng = new File(project.testDir, "src/main/res/drawable-xhdpi/special_heart.png")
- Files.createParentDirs(xhdpiPng)
- Files.copy(hdpiPng, xhdpiPng)
-
- project.execute("assembleDebug")
- checkIncrementalBuild()
-
- assertWithMessage("Wrong file used.")
- .that(FileUtils.sha1(pngToUse))
- .isNotEqualTo(FileUtils.sha1(generatedPng))
-
- assertWithMessage("Wrong file used.")
- .that(FileUtils.sha1(pngToUse))
- .isEqualTo(FileUtils.sha1(xhdpiPng))
- }
-
- @Test
- public void "incremental build: modify xml"() throws Exception {
- project.execute("assembleDebug")
-
- File heartPngToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable-hdpi/heart.png")
- File iconPngToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable/icon.png")
-
- String oldHashCode = FileUtils.sha1(heartPngToUse)
- long heartPngModified = heartPngToUse.lastModified()
- long iconPngModified = iconPngToUse.lastModified()
-
- File heartXml = new File(project.testDir, "src/main/res/drawable/heart.xml")
- String content = Files.toString(heartXml, UTF_8)
- // Change the heart to blue.
- Files.write(content.replace("ff0000", "0000ff"), heartXml, UTF_8)
-
- project.execute("assembleDebug")
- checkIncrementalBuild()
-
- assertThat(iconPngToUse.lastModified()).isEqualTo(iconPngModified)
- assertThat(heartPngToUse.lastModified()).isNotEqualTo(heartPngModified)
- assertWithMessage("XML file change not reflected in PNG.")
- .that(FileUtils.sha1(heartPngToUse))
- .isNotEqualTo(oldHashCode)
- }
-
- @Test
- public void "incremental build: replace vector drawable with bitmap alias"() throws Exception {
- project.execute("assembleDebug")
-
- File heartXml = new File(project.testDir, "src/main/res/drawable/heart.xml")
- Files.write(
- "<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\" " +
- "android:src=\"@drawable/icon\" />",
- heartXml,
- UTF_8)
-
- project.execute("assembleDebug")
- checkIncrementalBuild()
-
- File apk = project.getApk("debug")
- assertThatApk(apk).containsResource("drawable/icon.png")
- assertThatApk(apk).containsResource("drawable/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-xhdpi/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png")
-
- File heartXmlToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable/heart.xml")
-
- // They won't be equal, because of the source marker added in the XML.
- assertThat(Files.toString(heartXmlToUse, UTF_8)).contains(Files.toString(heartXml, UTF_8))
- }
-
- @Test
- public void "incremental build: replace bitmap alias with vector drawable"() throws Exception {
- File heartXml = new File(project.testDir, "src/main/res/drawable/heart.xml")
-
- String vectorDrawable = Files.toString(heartXml, UTF_8)
-
- Files.write(
- "<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\" " +
- "android:src=\"@drawable/icon\" />",
- heartXml,
- UTF_8)
-
- project.execute("clean", "assembleDebug")
-
- File apk = project.getApk("debug")
- assertThatApk(apk).containsResource("drawable/icon.png")
- assertThatApk(apk).containsResource("drawable/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-xhdpi/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png")
- assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png")
-
- File heartXmlToUse = new File(
- project.testDir,
- "build/intermediates/res/merged/debug/drawable/heart.xml")
-
- // They won't be equal, because of the source marker added in the XML.
- assertThat(Files.toString(heartXmlToUse, UTF_8)).contains(Files.toString(heartXml, UTF_8))
-
- Files.write(vectorDrawable, heartXml, UTF_8)
- project.execute("assembleDebug")
- checkIncrementalBuild()
-
- assertThatApk(apk).doesNotContainResource("drawable/heart.xml")
- assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml")
- assertThatApk(apk).containsResource("drawable-hdpi-v21/heart.xml")
- assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png")
- assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart.png")
- assertThatApk(apk).containsResource("drawable-xhdpi-v21/heart.xml")
- }
-
- private void checkIncrementalBuild() {
- // Do nothing for now, the incremental marker was removed.
- // TODO: remove the method or re-enable incremental markers.
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearVariantTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearVariantTest.groovy
deleted file mode 100644
index eb176a4..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearVariantTest.groovy
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.application
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ZipHelper
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.SdkConstants.DOT_ANDROID_PACKAGE
-import static com.android.SdkConstants.FD_RES
-import static com.android.SdkConstants.FD_RES_RAW
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertNull
-/**
- * Assemble tests for embedded.
- */
- at CompileStatic
-class WearVariantTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("embedded")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", ":main:assemble")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check embedded"() {
- String embeddedApkPath = FD_RES + '/' + FD_RES_RAW + '/' + ANDROID_WEAR_MICRO_APK +
- DOT_ANDROID_PACKAGE
-
- // each micro app has a different version name to distinguish them from one another.
- // here we record what we expect from which.
- def variantData = [
- //Output apk name Version name
- //--------------- ------------
- [ "flavor1-release-unsigned", "flavor1" ],
- [ "flavor2-release-unsigned", "default" ],
- [ "flavor1-custom-unsigned", "custom" ],
- [ "flavor2-custom-unsigned", "custom" ],
- [ "flavor1-debug", null ],
- [ "flavor2-debug", null ]
- ]
-
- for (List<String> data : variantData) {
- File fullApk = project.getSubproject("main").getApk(data[0])
- File embeddedApk = ZipHelper.extractFile(fullApk, embeddedApkPath)
-
- if (data[1] == null) {
- assertNull("Expected no embedded app for " + data[0], embeddedApk)
- break
- }
-
- assertNotNull("Failed to find embedded micro app for " + data[0], embeddedApk)
-
- // check for the versionName
- assertThatApk(embeddedApk).hasVersionName(data[1])
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearWithCustomApplicationIdTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearWithCustomApplicationIdTest.groovy
deleted file mode 100644
index 02d1d7d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearWithCustomApplicationIdTest.groovy
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.application
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.google.common.base.Throwables
-import groovy.transform.CompileStatic
-import org.gradle.tooling.BuildException
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static org.junit.Assert.fail
-
-/**
- * Debug builds with a wearApp with applicationId that does not match that of the main application
- * should fail.
- */
- at CompileStatic
-class WearWithCustomApplicationIdTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("embedded")
- .create()
-
- @BeforeClass
- static void setUp() {
- def mainAppBuildGradle = project.file("main/build.gradle");
-
- mainAppBuildGradle.text = mainAppBuildGradle.text.replaceFirst(
- /flavor1 \{/,
- "flavor1 {\n" +
- " applicationId \"com.example.change.application.id.breaks.embed\"")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- public void "build should fail on applicationId mismatch"() {
- try {
- project.execute("clean", ":main:assembleFlavor1Release")
- fail("Build should fail: applicationId of wear app does not match the main application")
- } catch (BuildException e) {
- assert Throwables.getRootCause(e).message.contains(
- "The main and the micro apps do not have the same package name");
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetAndroidModelAction.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetAndroidModelAction.java
deleted file mode 100644
index faac9d4..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetAndroidModelAction.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.fixture;
-
-import com.android.builder.model.AndroidProject;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import org.gradle.tooling.BuildAction;
-import org.gradle.tooling.BuildController;
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.gradle.BasicGradleProject;
-import org.gradle.tooling.model.gradle.GradleBuild;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * a Build Action that returns all the models for all the Gradle projects
- */
-public class GetAndroidModelAction implements BuildAction<Map<String, AndroidProject>> {
-
- private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
-
- @Override
- public Map<String, AndroidProject> execute(BuildController buildController) {
-
- long t1 = System.currentTimeMillis();
- GradleBuild gradleBuild = buildController.getBuildModel();
- DomainObjectSet<? extends BasicGradleProject> projects = gradleBuild.getProjects();
-
- final int projectCount = projects.size();
- Map<String, AndroidProject> modelMap = Maps.newHashMapWithExpectedSize(projectCount);
-
- List<BasicGradleProject> projectList = Lists.newArrayList(projects);
- List<Thread> threads = Lists.newArrayListWithCapacity(CPU_COUNT);
- List<ModelQuery> queries = Lists.newArrayListWithCapacity(CPU_COUNT);
-
- for (int i = 0 ; i < CPU_COUNT ; i++) {
- ModelQuery modelQuery = new ModelQuery(
- projectList,
- buildController);
- queries.add(modelQuery);
- Thread t = new Thread(modelQuery);
- threads.add(t);
- t.start();
- }
-
- for (int i = 0 ; i < CPU_COUNT ; i++) {
- try {
- threads.get(i).join();
- ModelQuery modelQuery = queries.get(i);
- modelMap.putAll(modelQuery.getModels());
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- long t2 = System.currentTimeMillis();
- System.out.println("GetAndroidModelAction: " + (t2-t1) + "ms");
-
- return modelMap;
- }
-
- // index used by threads to get the new project to query.
- private volatile int currentIndex = 0;
-
- protected synchronized int getNextIndex() {
- return currentIndex++;
- }
-
- class ModelQuery implements Runnable {
-
- private final Map<String, AndroidProject> models;
- private final List<BasicGradleProject> projects;
- private final BuildController buildController;
-
- public ModelQuery(
- List<BasicGradleProject> projects,
- BuildController buildController) {
- this.projects = projects;
- this.buildController = buildController;
-
- models = Maps.newHashMapWithExpectedSize(projects.size() / CPU_COUNT);
- }
-
- public Map<String, AndroidProject> getModels() {
- return models;
- }
-
- @Override
- public void run() {
- final int count = projects.size();
-
- int index;
- while ((index = getNextIndex()) < count) {
- BasicGradleProject project = projects.get(index);
-
- AndroidProject model = buildController.findModel(project, AndroidProject.class);
- if (model != null) {
- models.put(project.getPath(), model);
- }
- }
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GradleTestProject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GradleTestProject.java
deleted file mode 100644
index d957ce3..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GradleTestProject.java
+++ /dev/null
@@ -1,1206 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.fixture;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.integration.common.fixture.app.AbstractAndroidTestApp;
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile;
-import com.android.build.gradle.integration.common.utils.FileHelper;
-import com.android.build.gradle.integration.common.utils.JacocoAgent;
-import com.android.build.gradle.integration.common.utils.SdkHelper;
-import com.android.builder.core.BuilderConstants;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.SyncIssue;
-import com.android.builder.model.Version;
-import com.android.io.StreamException;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-
-import org.gradle.tooling.BuildAction;
-import org.gradle.tooling.BuildActionExecuter;
-import org.gradle.tooling.BuildLauncher;
-import org.gradle.tooling.GradleConnectionException;
-import org.gradle.tooling.GradleConnector;
-import org.gradle.tooling.LongRunningOperation;
-import org.gradle.tooling.ProjectConnection;
-import org.gradle.tooling.ResultHandler;
-import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
-import org.gradle.tooling.model.GradleProject;
-import org.gradle.tooling.model.GradleTask;
-import org.junit.Assume;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.security.CodeSource;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-/**
- * JUnit4 test rule for integration test.
- *
- * This rule create a gradle project in a temporary directory.
- * It can be use with the @Rule or @ClassRule annotations. Using this class with @Rule will create
- * a gradle project in separate directories for each unit test, whereas using it with @ClassRule
- * creates a single gradle project.
- *
- * The test directory is always deleted if it already exists at the start of the test to ensure a
- * clean environment.
- */
-public class GradleTestProject implements TestRule {
-
- public static final File TEST_RES_DIR = new File("src/test/resources");
-
- public static final int DEFAULT_COMPILE_SDK_VERSION = 21;
- public static final String DEFAULT_BUILD_TOOL_VERSION;
- public static final String REMOTE_TEST_PROVIDER = System.getenv().get("REMOTE_TEST_PROVIDER");
-
- public static final String DEVICE_PROVIDER_NAME = REMOTE_TEST_PROVIDER != null ?
- REMOTE_TEST_PROVIDER : BuilderConstants.CONNECTED;
-
- public static final String GRADLE_TEST_VERSION = "2.2.1";
- public static final String GRADLE_EXP_TEST_VERSION = "2.5";
-
- public static final String ANDROID_GRADLE_PLUGIN_VERSION;
-
- public static final String CUSTOM_JACK;
- public static final boolean USE_JACK;
-
- private static final String RECORD_BENCHMARK_NAME = "com.android.benchmark.name";
- private static final String RECORD_BENCHMARK_MODE = "com.android.benchmark.mode";
-
- public enum BenchmarkMode {
- EVALUATION, SYNC, BUILD_FULL, BUILD_INC_JAVA, BUILD_INC_RES_EDIT, BUILD_INC_RES_ADD
- }
-
- static {
- String envBuildToolVersion = System.getenv("CUSTOM_BUILDTOOLS");
- DEFAULT_BUILD_TOOL_VERSION = !Strings.isNullOrEmpty(envBuildToolVersion) ?
- envBuildToolVersion : "22.0.1";
- String envVersion = System.getenv().get("CUSTOM_GRADLE");
- ANDROID_GRADLE_PLUGIN_VERSION = !Strings.isNullOrEmpty(envVersion) ? envVersion
- : Version.ANDROID_GRADLE_PLUGIN_VERSION;
- String envJack = System.getenv().get("CUSTOM_JACK");
- CUSTOM_JACK = !Strings.isNullOrEmpty(envJack) ? envJack : "false";
- USE_JACK = Boolean.parseBoolean(CUSTOM_JACK);
- }
-
- private static final String COMMON_HEADER = "commonHeader.gradle";
- private static final String COMMON_LOCAL_REPO = "commonLocalRepo.gradle";
- private static final String COMMON_BUILD_SCRIPT = "commonBuildScript.gradle";
- private static final String COMMON_BUILD_SCRIPT_EXP = "commonBuildScriptExperimental.gradle";
- private static final String COMMON_GRADLE_PLUGIN_VERSION = "commonGradlePluginVersion.gradle";
- private static final String DEFAULT_TEST_PROJECT_NAME = "project";
-
- public static class Builder {
- private static final File SAMPLE_PROJECT_DIR = new File("samples");
- private static final File TEST_PROJECT_DIR = new File("test-projects");
-
- @Nullable
- private String name;
-
- @Nullable
- private TestProject testProject = null;
-
- @Nullable
- File sdkDir = SdkHelper.findSdkDir();
- @Nullable
- File ndkDir = findNdkDir();
- boolean captureStdOut = false;
- boolean captureStdErr = false;
- boolean experimentalMode = false;
- @Nullable
- private String targetGradleVersion;
-
- boolean useJack = false;
- boolean useMinify = false;
- @NonNull
- private List<String> gradleProperties = Lists.newArrayList();
- @Nullable
- private String heapSize;
-
- /**
- * Create a GradleTestProject.
- */
- public GradleTestProject create() {
- if (targetGradleVersion == null) {
- targetGradleVersion =
- experimentalMode ? GRADLE_EXP_TEST_VERSION : GRADLE_TEST_VERSION;
- }
- return new GradleTestProject(
- name,
- testProject,
- experimentalMode,
- useMinify,
- useJack,
- targetGradleVersion,
- captureStdOut,
- captureStdErr,
- sdkDir,
- ndkDir,
- gradleProperties,
- heapSize);
- }
-
- /**
- * Set the name of the project.
- *
- * Necessary if you have multiple projects in a test class.
- */
- public Builder withName(@NonNull String name) {
- this.name = name;
- return this;
- }
-
- public Builder captureStdOut(boolean captureStdOut) {
- this.captureStdOut = captureStdOut;
- return this;
- }
-
- public Builder captureStdErr(boolean captureStdErr) {
- this.captureStdErr = captureStdErr;
- return this;
- }
-
- /**
- * Use experimental plugin for the test project.
- */
- public Builder forExpermimentalPlugin(boolean mode) {
- this.experimentalMode = mode;
- return this;
- }
-
- /**
- * Use the gradle version for experimental plugin, but the test project do not necessarily
- * have to use experimental plugin.
- */
- public Builder useExperimentalGradleVersion(boolean mode) {
- if (mode) {
- targetGradleVersion = GRADLE_EXP_TEST_VERSION;
- }
- return this;
- }
-
- /**
- * Use the gradle version specified, e.g. "2.4".
- */
- public Builder useGradleVersion(String targetGradleVersion) {
- this.targetGradleVersion = targetGradleVersion;
- return this;
- }
-
- /**
- * Create a project without setting ndk.dir in local.properties.
- */
- public Builder withoutNdk() {
- this.ndkDir = null;
- return this;
- }
-
- /**
- * Create GradleTestProject from a TestProject.
- */
- public Builder fromTestApp(@NonNull TestProject testProject) {
- this.testProject = testProject;
- return this;
- }
-
- /**
- * Create GradleTestProject from an existing test project.
- */
- public Builder fromTestProject(@NonNull String project) {
- AndroidTestApp app = new EmptyTestApp();
- name = project;
- File projectDir = new File(TEST_PROJECT_DIR, project);
- addAllFiles(app, projectDir);
- return fromTestApp(app);
- }
-
- /**
- * Create GradleTestProject from an existing test project.
- */
- public Builder fromExternalProject(@NonNull String project) throws IOException {
- AndroidTestApp app = new EmptyTestApp();
- name = project;
- // compute the root folder of the checkout, based on test-projects.
- File parentDir = TEST_PROJECT_DIR.getCanonicalFile().getParentFile().getParentFile()
- .getParentFile().getParentFile().getParentFile();
- parentDir = new File(parentDir, "external");
- File projectDir = new File(parentDir, project);
- addAllFiles(app, projectDir);
- return fromTestApp(app);
- }
-
- /**
- * Add gradle properties.
- */
- public Builder addGradleProperties(@NonNull String property) {
- gradleProperties.add(property);
- return this;
- }
-
- /**
- * Sets the test heap size requirement. Example values : 1024m, 2048m...
- *
- * @param heapSize the heap size in a format understood by the -Xmx JVM parameter
- * @return itself.
- */
- public Builder withHeap(String heapSize) {
- this.heapSize = heapSize;
- return this;
- }
-
- public Builder withJack(boolean useJack) {
- this.useJack = useJack;
- return this;
- }
-
- public Builder withMinify(boolean useMinify) {
- this.useMinify = useMinify;
- return this;
- }
-
- private static class EmptyTestApp extends AbstractAndroidTestApp {
- @Override
- public boolean containsFullBuildScript() {
- return true;
- }
- }
- }
-
- private final String name;
- private final File outDir;
- private File testDir;
- private File sourceDir;
- private File buildFile;
- private final File ndkDir;
- private final File sdkDir;
-
- private final ByteArrayOutputStream stdout;
- private final ByteArrayOutputStream stderr;
-
- private final Collection<String> gradleProperties;
-
- @Nullable
- private final TestProject testProject;
-
- private final boolean experimentalMode;
- private final String targetGradleVersion;
-
- private final boolean useJack;
- private final boolean minifyEnabled;
-
- @Nullable
- private String heapSize;
-
- private GradleTestProject(
- @Nullable String name,
- @Nullable TestProject testProject,
- boolean experimentalMode,
- boolean minifyEnabled,
- boolean useJack,
- String targetGradleVersion,
- boolean captureStdOut,
- boolean captureStdErr,
- @Nullable File sdkDir,
- @Nullable File ndkDir,
- @NonNull Collection<String> gradleProperties,
- @Nullable String heapSize) {
- String buildDir = System.getenv("PROJECT_BUILD_DIR");
- outDir = (buildDir == null) ? new File("build/tests") : new File(buildDir, "tests");
- testDir = null;
- buildFile = sourceDir = null;
- this.name = (name == null) ? DEFAULT_TEST_PROJECT_NAME : name;
- this.experimentalMode = experimentalMode;
- this.minifyEnabled = minifyEnabled;
- this.useJack = useJack;
- this.targetGradleVersion = targetGradleVersion;
- this.testProject = testProject;
- stdout = captureStdOut ? new ByteArrayOutputStream() : null;
- stderr = captureStdErr ? new ByteArrayOutputStream() : null;
- this.sdkDir = sdkDir;
- this.ndkDir = ndkDir;
- this.heapSize = heapSize;
- this.gradleProperties = gradleProperties;
- }
-
- /**
- * Create a GradleTestProject representing a subProject of another GradleTestProject.
- * @param subProject name of the subProject.
- * @param rootProject root GradleTestProject.
- */
- private GradleTestProject(
- @NonNull String subProject,
- @NonNull GradleTestProject rootProject) {
- name = subProject;
- outDir = rootProject.outDir;
-
- testDir = new File(rootProject.testDir, subProject);
- assertTrue("No subproject dir at " + testDir.toString(), testDir.isDirectory());
-
- buildFile = new File(testDir, "build.gradle");
- sourceDir = new File(testDir, "src");
- ndkDir = rootProject.ndkDir;
- sdkDir = rootProject.sdkDir;
- stdout = rootProject.stdout;
- stderr = rootProject.stdout;
- gradleProperties = ImmutableList.of();
- testProject = null;
- experimentalMode = rootProject.isExperimentalMode();
- targetGradleVersion = rootProject.getTargetGradleVersion();
- minifyEnabled = false;
- useJack = false;
- }
-
- String getTargetGradleVersion() {
- return targetGradleVersion;
- }
-
- boolean isExperimentalMode() {
- return experimentalMode;
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- /**
- * Recursively delete directory or file.
- *
- * @param root directory to delete
- */
- private static void deleteRecursive(File root) {
- if (root.exists()) {
- if (root.isDirectory()) {
- File files[] = root.listFiles();
- if (files != null) {
- for (File file : files) {
- deleteRecursive(file);
- }
- }
- }
- assertTrue(root.delete());
- }
- }
-
- /**
- * Add all files in a directory to an AndroidTestApp.
- */
- private static void addAllFiles(AndroidTestApp app, File projectDir) {
- for (String filePath : FileHelper.listFiles(projectDir)) {
- File file = new File(filePath);
- try {
- app.addFile(
- new TestSourceFile(
- file.getParent(),
- file.getName(),
- Files.toByteArray(new File(projectDir, filePath))));
- } catch (IOException e) {
- fail(e.toString());
- }
- }
- }
-
- @Override
- public Statement apply(final Statement base, Description description) {
- testDir = new File(outDir, description.getTestClass().getName());
-
- // Create separate directory based on test method name if @Rule is used.
- // getMethodName() is null if this rule is used as a @ClassRule.
- if (description.getMethodName() != null) {
- String dirName = description.getMethodName();
- dirName = dirName.replaceAll("[^a-zA-Z0-9_]", "_");
- testDir = new File(testDir, dirName);
- }
- testDir = new File(testDir, name);
-
- buildFile = new File(testDir, "build.gradle");
- sourceDir = new File(testDir, "src");
-
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- if (testDir.exists()) {
- deleteRecursive(testDir);
- }
- assertTrue(testDir.mkdirs());
- assertTrue(sourceDir.mkdirs());
-
- Files.copy(
- new File(Builder.TEST_PROJECT_DIR, COMMON_HEADER),
- new File(testDir.getParent(), COMMON_HEADER));
- Files.copy(
- new File(Builder.TEST_PROJECT_DIR, COMMON_LOCAL_REPO),
- new File(testDir.getParent(), COMMON_LOCAL_REPO));
- Files.copy(
- new File(Builder.TEST_PROJECT_DIR, COMMON_BUILD_SCRIPT),
- new File(testDir.getParent(), COMMON_BUILD_SCRIPT));
- Files.copy(
- new File(Builder.TEST_PROJECT_DIR, COMMON_BUILD_SCRIPT_EXP),
- new File(testDir.getParent(), COMMON_BUILD_SCRIPT_EXP));
- Files.copy(
- new File(Builder.TEST_PROJECT_DIR, COMMON_GRADLE_PLUGIN_VERSION),
- new File(testDir.getParent(), COMMON_GRADLE_PLUGIN_VERSION));
-
- if (testProject != null) {
- testProject.write(
- testDir,
- testProject.containsFullBuildScript() ? "" :getGradleBuildscript());
- } else {
- Files.write(
- getGradleBuildscript(),
- buildFile,
- Charsets.UTF_8);
- }
-
- createLocalProp(testDir, sdkDir, ndkDir);
- createGradleProp();
- base.evaluate();
- }
- };
- }
-
- /**
- * Create a GradleTestProject representing a subproject.
- */
- public GradleTestProject getSubproject(String name) {
- return new GradleTestProject(name, this);
- }
-
- /**
- * Return the name of the test project.
- */
- public String getName() {
- return name;
- }
-
- /**
- * Return the directory containing the test project.
- */
- public File getTestDir() {
- return testDir;
- }
-
- /**
- * Return the build.gradle of the test project.
- */
- public File getBuildFile() {
- return buildFile;
- }
-
- /**
- * Return the directory containing the source files of the test project.
- */
- public File getSourceDir() {
- return sourceDir;
- }
-
- /**
- * Return the output directory from Android plugins.
- */
- public File getOutputDir() {
- return new File(testDir,
- Joiner.on(File.separator).join("build", AndroidProject.FD_OUTPUTS));
- }
-
- /**
- * Return a File under the output directory from Android plugins.
- */
- public File getOutputFile(String path) {
- return new File(getOutputDir(), path);
- }
-
- /**
- * Return the output apk File from the application plugin for the given dimension.
- *
- * Expected dimensions orders are:
- * - product flavors
- * - build type
- * - other modifiers (e.g. "unsigned", "aligned")
- */
- public File getApk(String ... dimensions) {
- // TODO: Add overload for tests and splits.
- List<String> dimensionList = Lists.newArrayListWithExpectedSize(1 + dimensions.length);
- dimensionList.add(getName());
- dimensionList.addAll(Arrays.asList(dimensions));
- return getOutputFile(
- "apk/" + Joiner.on("-").join(dimensionList) + SdkConstants.DOT_ANDROID_PACKAGE);
- }
-
- /**
- * Return the output aar File from the library plugin for the given dimension.
- *
- * Expected dimensions orders are:
- * - product flavors
- * - build type
- * - other modifiers (e.g. "unsigned", "aligned")
- */
- public File getAar(String ... dimensions) {
- // TODO: Add overload for tests and splits.
- List<String> dimensionList = Lists.newArrayListWithExpectedSize(1 + dimensions.length);
- dimensionList.add(getName());
- dimensionList.addAll(Arrays.asList(dimensions));
- return getOutputFile("aar/" + Joiner.on("-").join(dimensionList) + SdkConstants.DOT_AAR);
- }
-
- /**
- * Returns the SDK dir
- */
- public File getSdkDir() {
- return sdkDir;
- }
-
- /**
- * Returns the NDK dir
- */
- public File getNdkDir() {
- return ndkDir;
- }
-
- /**
- * Return the directory of the repository containing the necessary plugins for testing.
- */
- private File getRepoDir() {
- CodeSource source = getClass().getProtectionDomain().getCodeSource();
- assert (source != null);
- URL location = source.getLocation();
- try {
- File dir = new File(location.toURI());
- assertTrue(dir.getPath(), dir.exists());
-
- File f = dir.getParentFile().getParentFile().getParentFile().getParentFile()
- .getParentFile().getParentFile().getParentFile();
- return new File(f, "out" + File.separator + "repo");
- } catch (URISyntaxException e) {
- fail(e.getLocalizedMessage());
- }
- return null;
- }
-
- /**
- * Returns a string that contains the gradle buildscript content
- */
- public String getGradleBuildscript() {
- return "apply from: \"../commonHeader.gradle\"\n" +
- "buildscript { apply from: \"../commonBuildScript" +
- (experimentalMode ? "Experimental" : "") + ".gradle\", to: buildscript }\n" +
- "\n" +
- "apply from: \"../commonLocalRepo.gradle\"\n";
- }
-
- /**
- * Return a list of all task names of the project.
- */
- public List<String> getTaskList() {
- ProjectConnection connection = getProjectConnection();
- try {
- GradleProject project = connection.getModel(GradleProject.class);
- List<String> tasks = Lists.newArrayList();
- for (GradleTask gradleTask : project.getTasks()) {
- tasks.add(gradleTask.getName());
- }
- return tasks;
- } finally {
- connection.close();
- }
-
- }
-
- /**
- * Runs gradle on the project. Throws exception on failure.
- *
- * @param tasks Variadic list of tasks to execute.
- */
- public void execute(String ... tasks) {
- execute(Collections.<String>emptyList(), false, false, ExpectedBuildResult.SUCCESS, tasks);
- }
-
- public void execute(@NonNull List<String> arguments, String ... tasks) {
- execute(arguments, false, false, ExpectedBuildResult.SUCCESS, tasks);
- }
-
- public void executeWithBenchmark(
- @NonNull String benchmarkName,
- @NonNull BenchmarkMode benchmarkMode,
- String ... tasks) {
- List<String> arguments = ImmutableList.of(
- "-P" + RECORD_BENCHMARK_NAME + "=" + benchmarkName,
- "-P" + RECORD_BENCHMARK_MODE + "=" + benchmarkMode.name().toLowerCase(Locale.US)
- );
- execute(arguments, false, false, ExpectedBuildResult.SUCCESS, tasks);
- }
-
- public void executeExpectingFailure(String... tasks) {
- executeExpectingFailure(Collections.<String>emptyList(), tasks);
- }
-
- public void executeExpectingFailure(@NonNull List<String> arguments, String... tasks) {
- execute(
- arguments,
- false /*returnModel*/,
- false /*emulateStudio_1_0*/,
- ExpectedBuildResult.FAILURE,
- tasks);
- }
-
- public void executeConnectedCheck() {
- executeConnectedCheck(Collections.<String>emptyList());
- }
-
- public void executeConnectedCheck(List<String> arguments) {
- execute(arguments, REMOTE_TEST_PROVIDER == null ? "connectedCheck" : "deviceCheck");
- }
-
- /**
- * Runs gradle on the project, and returns the project model. Throws exception on failure.
- *
- * @param tasks Variadic list of tasks to execute.
- *
- * @return the AndroidProject model for the project.
- */
- @NonNull
- public AndroidProject executeAndReturnModel(String ... tasks) {
- return executeAndReturnModel(false, tasks);
- }
-
- /**
- * Runs gradle on the project, and returns the project model. Throws exception on failure.
- *
- * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
- * @param tasks Variadic list of tasks to execute.
- *
- * @return the AndroidProject model for the project.
- */
- @NonNull
- public AndroidProject executeAndReturnModel(boolean emulateStudio_1_0, String ... tasks) {
- //noinspection ConstantConditions
- return execute(Collections.<String>emptyList(), true, emulateStudio_1_0,
- ExpectedBuildResult.SUCCESS, tasks);
- }
-
- /**
- * Runs gradle on the project, and returns a project model for each sub-project.
- * Throws exception on failure.
- *
- * @param tasks Variadic list of tasks to execute.
- *
- * @return the AndroidProject model for the project.
- */
- @NonNull
- public Map<String, AndroidProject> executeAndReturnMultiModel(String ... tasks) {
- return executeAndReturnMultiModel(false, tasks);
- }
-
- /**
- * Runs gradle on the project, and returns a project model for each sub-project.
- * Throws exception on failure.
- *
- * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
- * @param tasks Variadic list of tasks to execute.
- *
- * @return the AndroidProject model for the project.
- */
- @NonNull
- public Map<String, AndroidProject> executeAndReturnMultiModel(boolean emulateStudio_1_0, String ... tasks) {
- ProjectConnection connection = getProjectConnection();
- try {
- executeBuild(Collections.<String>emptyList(), connection, tasks,
- ExpectedBuildResult.SUCCESS);
-
- return buildModel(connection, new GetAndroidModelAction(), emulateStudio_1_0, null, null);
-
- } finally {
- connection.close();
- }
- }
-
- /**
- * Returns the project model without building.
- *
- * This will fail if the project is a multi-project setup or if there are any sync issues
- * while loading the project.
- */
- @NonNull
- public AndroidProject getSingleModel() {
- return getSingleModel(false /* emulateStudio_1_0 */, true /*assertNodSyncIssues */);
- }
-
- /**
- * Returns the project model without building, querying it the way Studio 1.0 does.
- *
- * This will fail if the project is a multi-project setup or if there are any sync issues
- * while loading the project.
- */
- @NonNull
- public AndroidProject getSingleModelAsStudio1() {
- return getSingleModel(true /* emulateStudio_1_0 */, true /*assertNodSyncIssues */);
- }
-
- /**
- * Returns the project model without building.
- *
- * This will fail if the project is a multi-project setup.
- */
- @NonNull
- public AndroidProject getSingleModelIgnoringSyncIssues() {
- return getSingleModel(false /* emulateStudio_1_0 */, false /*assertNodSyncIssues */);
- }
-
- /**
- * Returns the project model without building, querying it the way Studio 1.0 does.
- *
- * This will fail if the project is a multi-project setup.
- */
- @NonNull
- public AndroidProject getSingleModelIgnoringSyncIssuesAsStudio1() {
- return getSingleModel(true /* emulateStudio_1_0 */, false /*assertNodSyncIssues */);
- }
-
- /**
- * Returns the project model without building.
- *
- * This will fail if the project is a multi-project setup.
- *
- * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
- * @param assertNoSyncIssues true if the presence of sync issues during the model evaluation
- * should raise a {@link AssertionError}s
- */
- @NonNull
- private AndroidProject getSingleModel(boolean emulateStudio_1_0, boolean assertNoSyncIssues) {
- ProjectConnection connection = getProjectConnection();
- try {
- Map<String, AndroidProject> modelMap = buildModel(
- connection,
- new GetAndroidModelAction(),
- emulateStudio_1_0,
- null,
- null);
-
- // ensure there was only one project
- assertEquals("Quering GradleTestProject.getModel() with multi-project settings",
- 1, modelMap.size());
-
- AndroidProject androidProject = modelMap.get(":");
- if (assertNoSyncIssues) {
- assertNoSyncIssues(androidProject);
- }
- return androidProject;
- } finally {
- connection.close();
- }
- }
-
- /**
- * Returns a project model for each sub-project without building generating a
- * {@link AssertionError} if any sync issue is raised during the model loading.
- */
- @NonNull
- public Map<String, AndroidProject> getAllModels() {
- return getAllModelsWithBenchmark(null, null);
- }
-
- /**
- * Returns a project model for each sub-project without building generating a
- * @param benchmarkName optional benchmark name to pass to Gradle
- * @param benchmarkMode optional benchmark mode to pass to gradle.
-
- * {@link AssertionError} if any sync issue is raised during the model loading.
- */
- @NonNull
- public Map<String, AndroidProject> getAllModelsWithBenchmark(
- @Nullable String benchmarkName,
- @Nullable BenchmarkMode benchmarkMode) {
- Map<String, AndroidProject> allModels = getAllModels(new GetAndroidModelAction(), false, benchmarkName, benchmarkMode);
- for (AndroidProject project : allModels.values()) {
- assertNoSyncIssues(project);
- }
- return allModels;
- }
-
-
- private static void assertNoSyncIssues(AndroidProject project) {
- if (!project.getSyncIssues().isEmpty()) {
- StringBuilder msg = new StringBuilder();
- msg.append("Project ")
- .append(project.getName())
- .append(" had sync issues :\n");
- for (SyncIssue syncIssue : project.getSyncIssues()) {
- msg.append(syncIssue);
- msg.append("\n");
- }
- fail(msg.toString());
- }
- }
-
- /**
- * Returns a project model for each sub-project without building and ignoring potential sync
- * issues. Sync issues will still be returned for each {@link AndroidProject} that failed to
- * sync properly.
- */
- @NonNull
- public Map<String, AndroidProject> getAllModelsIgnoringSyncIssues() {
- return getAllModels(new GetAndroidModelAction(), false, null, null);
- }
-
- /**
- * Returns a project model for each sub-project without building.
- *
- * @param action the build action to gather the model
- */
- @NonNull
- public <K, V> Map<K, V> getAllModels(@NonNull BuildAction<Map<K, V>> action) {
- return getAllModels(action, false, null, null);
- }
-
- /**
- * Returns a project model for each sub-project without building.
- *
- * @param action the build action to gather the model
- * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
- * @param benchmarkName optional benchmark name to pass to Gradle
- * @param benchmarkMode optional benchmark mode to pass to gradle.
- */
- @NonNull
- public <K, V> Map<K, V> getAllModels(
- @NonNull BuildAction<Map<K, V>> action,
- boolean emulateStudio_1_0,
- @Nullable String benchmarkName,
- @Nullable BenchmarkMode benchmarkMode) {
- ProjectConnection connection = getProjectConnection();
- try {
- return buildModel(connection, action, emulateStudio_1_0, benchmarkName, benchmarkMode);
-
- } finally {
- connection.close();
- }
- }
-
- /**
- * Runs gradle on the project. Throws exception on failure.
- *
- * @param arguments List of arguments for the gradle command.
- * @param returnModel whether the model should be queried and returned.
- * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
- * @param expectedBuildResult the expected result. If the build status does not match the
- * expected failure, then an exception will be thrown.
- * @param tasks Variadic list of tasks to execute.
- *
- * @return the model, if <var>returnModel</var> was true, null otherwise
- */
- @Nullable
- private AndroidProject execute(
- @NonNull List<String> arguments,
- boolean returnModel,
- boolean emulateStudio_1_0,
- ExpectedBuildResult expectedBuildResult,
- @NonNull String ... tasks) {
- ProjectConnection connection = getProjectConnection();
- try {
- executeBuild(arguments, connection, tasks, expectedBuildResult);
-
- if (returnModel) {
- Map<String, AndroidProject> modelMap = buildModel(
- connection,
- new GetAndroidModelAction(),
- emulateStudio_1_0,
- null,
- null);
-
- // ensure there was only one project
- assertEquals("Quering GradleTestProject.getModel() with multi-project settings",
- 1, modelMap.size());
-
- return modelMap.get(":");
- }
- } finally {
- connection.close();
- }
-
- return null;
- }
-
- private enum ExpectedBuildResult {
- SUCCESS,
- FAILURE
- }
-
- private void executeBuild(final List<String> arguments, ProjectConnection connection,
- final String[] tasks, ExpectedBuildResult expectedBuildResult) {
- List<String> args = Lists.newArrayListWithCapacity(5 + arguments.size());
- args.add("-i");
- args.add("-u");
- args.add("-Pcom.android.build.gradle.integratonTest.useJack=" + Boolean.toString(useJack));
- args.add("-Pcom.android.build.gradle.integratonTest.minifyEnabled=" +
- Boolean.toString(minifyEnabled));
- args.add("-Pcom.android.build.gradle.integratonTest.useComponentModel=" +
- Boolean.toString(experimentalMode));
- args.addAll(arguments);
-
- BuildLauncher launcher = connection.newBuild()
- .forTasks(tasks)
- .withArguments(Iterables.toArray(args, String.class));
-
- setJvmArguments(launcher);
-
- if (stdout != null) {
- launcher.setStandardOutput(stdout);
- } else {
- launcher.setStandardOutput(System.out);
- }
- if (stderr != null) {
- launcher.setStandardError(stderr);
- } else {
- launcher.setStandardError(System.err);
- }
- if (expectedBuildResult == ExpectedBuildResult.SUCCESS) {
- launcher.run();
- } else {
- launcher.run(new ResultHandler<Void>() {
- @Override
- public void onComplete(Void aVoid) {
- throw new AssertionError(
- String.format(
- "Expecting build to fail:\n" +
- " Tasks: %s\n" +
- " Arguments: %s",
- Joiner.on(' ').join(tasks),
- Joiner.on(' ').join(arguments)));
- }
-
- @Override
- public void onFailure(GradleConnectionException e) {
- // Ignore, the test expects this build to fail.
- }
- });
- }
- }
-
- private void setJvmArguments(LongRunningOperation launcher) {
- List<String> jvmArguments = new ArrayList<String>();
-
- if (!Strings.isNullOrEmpty(heapSize)) {
- jvmArguments.add("-Xmx" + heapSize);
- }
-
- jvmArguments.add("-XX:MaxPermSize=1024m");
-
- String debugIntegrationTest = System.getenv("DEBUG_INNER_TEST");
- if (!Strings.isNullOrEmpty(debugIntegrationTest)) {
- jvmArguments.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5006");
- }
-
- if (JacocoAgent.isJacocoEnabled()) {
- jvmArguments.add(JacocoAgent.getJvmArg());
- }
-
- launcher.setJvmArguments(Iterables.toArray(jvmArguments, String.class));
- }
-
- /**
- * Returns a project model for each sub-project without building.
- * @param connection the opened ProjectConnection
- * @param action the build action to gather the model
- * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
- * @param benchmarkName optional benchmark name to pass to Gradle
- * @param benchmarkMode optional benchmark mode to pass to gradle.
- */
- @NonNull
- private <K,V> Map<K, V> buildModel(
- @NonNull ProjectConnection connection,
- @NonNull BuildAction<Map<K, V>> action,
- boolean emulateStudio_1_0,
- @Nullable String benchmarkName,
- @Nullable BenchmarkMode benchmarkMode) {
-
- BuildActionExecuter<Map<K, V>> executor = connection.action(action);
-
- List<String> arguments = Lists.newArrayListWithCapacity(5);
- arguments.add("-P" + AndroidProject.PROPERTY_BUILD_MODEL_ONLY + "=true");
- arguments.add("-P" + AndroidProject.PROPERTY_INVOKED_FROM_IDE + "=true");
- if (!emulateStudio_1_0) {
- arguments.add("-P" + AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED + "=true");
- }
- if (benchmarkName != null) {
- arguments.add("-P" + RECORD_BENCHMARK_NAME + "=" + benchmarkName);
- if (benchmarkMode != null) {
- arguments.add("-P" + RECORD_BENCHMARK_MODE + "=" + benchmarkMode.name().toLowerCase(Locale.US));
- }
- }
-
- setJvmArguments(executor);
-
- executor.withArguments(Iterables.toArray(arguments, String.class));
-
- executor.setStandardOutput(System.out);
- executor.setStandardError(System.err);
-
- return executor.run();
- }
-
- /**
- * Return the stdout from all execute command.
- */
- public ByteArrayOutputStream getStdout() {
- return stdout;
- }
-
- /**
- * Return the stderr from all execute command.
- */
- public ByteArrayOutputStream getStderr() {
- return stderr;
- }
-
- /**
- * Create a File object. getTestDir will be the base directory if a relative path is supplied.
- *
- * @param path Full path of the file. May be a relative path.
- */
- public File file(String path) {
- File result = new File(path);
- if (result.isAbsolute()) {
- return result;
- } else {
- return new File(testDir, path);
- }
- }
-
- /**
- * Returns the NDK folder as built from the Android source tree.
- */
- private static File findNdkDir() {
- String androidHome = System.getenv("ANDROID_NDK_HOME");
- if (androidHome != null) {
- File f = new File(androidHome);
- if (f.isDirectory()) {
- return f;
- } else {
- System.out.println("Failed to find NDK in ANDROID_NDK_HOME=" + androidHome);
- }
- }
- return null;
- }
-
- /**
- * Returns a Gradle project Connection
- */
- @NonNull
- private ProjectConnection getProjectConnection() {
- GradleConnector connector = GradleConnector.newConnector();
-
- // Limit daemon idle time for tests. 10 seconds is enough for another test
- // to start and reuse the daemon.
- ((DefaultGradleConnector) connector).daemonMaxIdleTime(10, TimeUnit.SECONDS);
-
- return connector
- .useGradleVersion(targetGradleVersion)
- .forProjectDirectory(testDir)
- .connect();
- }
-
- private static File createLocalProp(
- @NonNull File project,
- @NonNull File sdkDir,
- @Nullable File ndkDir) throws IOException, StreamException {
- ProjectPropertiesWorkingCopy localProp = ProjectProperties.create(
- project.getAbsolutePath(), ProjectProperties.PropertyType.LOCAL);
- localProp.setProperty(ProjectProperties.PROPERTY_SDK, sdkDir.getAbsolutePath());
- if (ndkDir != null) {
- localProp.setProperty(ProjectProperties.PROPERTY_NDK, ndkDir.getAbsolutePath());
- }
- localProp.save();
-
- return (File) localProp.getFile();
- }
-
- private void createGradleProp() throws IOException {
- if (gradleProperties.isEmpty()) {
- return;
- }
- File propertyFile = file("gradle.properties");
- Files.write(Joiner.on('\n').join(gradleProperties), propertyFile, Charset.defaultCharset());
- }
-
-
- public static void assumeLocalDevice() {
- Assume.assumeTrue(
- "Install task not run against device provider",
- GradleTestProject.REMOTE_TEST_PROVIDER == null);
- }
-
- public static void assumeBuildToolsAtLeast(int major) {
- assumeBuildToolsAtLeast(
- major, FullRevision.IMPLICIT_MINOR_REV, FullRevision.IMPLICIT_MICRO_REV);
- }
-
- public static void assumeBuildToolsAtLeast(int major, int minor, int micro) {
- FullRevision currentVersion = FullRevision.parseRevision(DEFAULT_BUILD_TOOL_VERSION);
- Assume.assumeTrue("Test is only applicable to build tools > " + major,
- new FullRevision(major, minor, micro).compareTo(currentVersion) < 0);
- }
-
- /**
- * Replace a line from a file with another line.
- * @param relativePath the relative path of the file from the root of the project
- * @param lineNumber the line number, starting at 1
- * @param line the line to replace with.
- * @throws IOException
- */
- public void replaceLine(
- String relativePath,
- int lineNumber,
- String line) throws IOException {
- File file = new File(testDir, relativePath.replace("/", File.separator));
-
- List<String> lines = Files.readLines(file, Charsets.UTF_8);
-
- lines.add(lineNumber, line);
- lines.remove(lineNumber - 1);
-
- Files.write(
- Joiner.on(System.getProperty("line.separator")).join(lines),
- file,
- Charsets.UTF_8);
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TemporaryProjectModification.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TemporaryProjectModification.java
deleted file mode 100644
index 316361e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TemporaryProjectModification.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.fixture;
-
-import static org.junit.Assert.assertTrue;
-
-import com.android.annotations.NonNull;
-import com.google.common.base.Charsets;
-import com.google.common.base.Function;
-import com.google.common.io.Files;
-
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.MultipleFailureException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import groovy.lang.Closure;
-
-/**
- * Allows project files to be modified, but stores their original content, so it can be restored for
- * the next test.
- */
-public class TemporaryProjectModification {
-
- private final GradleTestProject mTestProject;
-
- private final Map<String, String> mFileContentToRestore = new HashMap<String, String>();
-
- private TemporaryProjectModification(GradleTestProject testProject) {
- mTestProject = testProject;
- }
-
- /**
- * Runs a test that mutates the project in a reversible way, and returns the project to its
- * original state after the callback has been run.
- *
- * @param project The project to modify.
- * @param test The test to run.
- * @throws InitializationError if the project modification infrastructure fails.
- * @throws Exception passed through if the test throws an exception.
- */
- public static void doTest(GradleTestProject project, ModifiedProjectTest test) throws
- Exception {
- TemporaryProjectModification modifiedProject = new TemporaryProjectModification(project);
- try {
- test.runTest(modifiedProject);
- } finally {
- modifiedProject.close();
- }
- }
-
- public interface ModifiedProjectTest {
- void runTest(TemporaryProjectModification modifiedProject) throws Exception;
- }
-
- public void replaceFile(
- @NonNull String relativePath,
- @NonNull final String content) throws InitializationError {
- modifyFile(relativePath, new Function<String, String>() {
- @Override
- public String apply(String input) {
- return content;
- }
- });
- }
-
- public void replaceInFile(
- @NonNull String relativePath,
- @NonNull final String search,
- @NonNull final String replace) throws InitializationError {
- modifyFile(relativePath, new Function<String, String>() {
- @Override
- public String apply(String input) {
- return input.replaceAll(search, replace);
- }
- });
-
- }
-
- public void modifyFile(
- @NonNull String relativePath,
- @NonNull Function<String, String> modification) throws InitializationError {
- File file = mTestProject.file(relativePath);
-
- String currentContent = null;
- try {
- currentContent = Files.toString(file, Charsets.UTF_8);
- } catch (IOException e) {
- throw new InitializationError(e);
- }
-
- // We can modify multiple times, but we only want to store the original.
- if (!mFileContentToRestore.containsKey(relativePath)) {
- mFileContentToRestore.put(relativePath, currentContent);
- }
-
- String newContent = modification.apply(currentContent);
-
- if (newContent == null) {
- assertTrue("File should have been deleted", file.delete());
- } else {
- try {
- Files.write(newContent, file, Charsets.UTF_8);
- } catch (IOException e) {
- throw new InitializationError(e);
- }
- }
- }
-
- /**
- * Returns the project back to its original state.
- */
- public void close() throws InitializationError {
- try {
- for (Map.Entry<String, String> entry : mFileContentToRestore.entrySet()) {
- Files.write(entry.getValue(), mTestProject.file(entry.getKey()), Charsets.UTF_8);
- }
- } catch (IOException e) {
- throw new InitializationError(e);
- }
- mFileContentToRestore.clear();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AbstractAndroidTestApp.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AbstractAndroidTestApp.java
deleted file mode 100644
index 90573c9..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AbstractAndroidTestApp.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.fixture.app;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Collection;
-import java.util.NoSuchElementException;
-
-/**
- * Abstract class implementing AndroidTestApp.
- */
-public abstract class AbstractAndroidTestApp implements AndroidTestApp {
-
- private Multimap<String, TestSourceFile> sourceFiles = ArrayListMultimap.create();
-
- protected void addFiles(TestSourceFile... files) {
- for (TestSourceFile file : files) {
- sourceFiles.put(file.getName(), file);
- }
- }
-
- @Override
- public TestSourceFile getFile(String filename) {
- Collection<TestSourceFile> files = sourceFiles.get(filename);
- if (files.isEmpty()) {
- throw new NoSuchElementException("Unable to file source file: " + filename + ".");
- } else if (files.size() > 1) {
- throw new IllegalArgumentException(
- "Multiple source files named '" + filename + "'. Specify the path to get one "
- + "of the following files: \n"
- + Joiner.on('\n').join(files));
- }
- return files.iterator().next();
- }
-
- @Override
- public TestSourceFile getFile(String filename, final String path) {
- Collection<TestSourceFile> files = sourceFiles.get(filename);
- return Iterables.find(files, new Predicate<TestSourceFile>() {
- @Override
- public boolean apply(TestSourceFile testSourceFile) {
- return path.equals(testSourceFile.getPath());
- }
- });
- }
-
- @Override
- public void addFile(TestSourceFile file) {
- sourceFiles.put(file.getName(), file);
- }
-
- @Override
- public boolean removeFile(TestSourceFile file) {
- return sourceFiles.remove(file.getName(), file);
- }
-
- @Override
- public Collection<TestSourceFile> getAllSourceFiles() {
- return sourceFiles.values();
- }
-
- @Override
- public void write(@NonNull File projectDir, @Nullable String buildScriptContent)
- throws IOException {
- // Create build.gradle.
- if (buildScriptContent != null) {
- Files.write(
- buildScriptContent,
- new File(projectDir, "build.gradle"),
- Charset.defaultCharset());
- }
-
- for (TestSourceFile srcFile : getAllSourceFiles()) {
- srcFile.writeToDir(projectDir);
- }
- }
-
- @Override
- public boolean containsFullBuildScript() {
- return false;
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldApp.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldApp.groovy
deleted file mode 100644
index 486aaba..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldApp.groovy
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.common.fixture.app
-/**
- * Simple test application that prints "hello world!".
- */
-public class HelloWorldApp extends AbstractAndroidTestApp implements AndroidTestApp {
-
- static private final TestSourceFile javaSource =
- new TestSourceFile("src/main/java/com/example/helloworld", "HelloWorld.java",
- """
-package com.example.helloworld;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class HelloWorld extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
-}
-""");
-
- static private final TestSourceFile resValuesSource =
- new TestSourceFile("src/main/res/values", "strings.xml",
-"""<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">HelloWorld</string>
-</resources>
-""");
-
- static private final TestSourceFile resLayoutSource =
- new TestSourceFile("src/main/res/layout", "main.xml",
-"""<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
-<TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="hello world!"
- android:id="@+id/text"
- />
-</LinearLayout>
-""");
-
- static private final TestSourceFile manifest =
- new TestSourceFile("src/main", "AndroidManifest.xml",
-"""<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.helloworld"
- android:versionCode="1"
- android:versionName="1.0">
-
- <uses-sdk android:minSdkVersion="3" />
- <application android:label="@string/app_name">
- <activity android:name=".HelloWorld"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
-""");
-
-
- static private final TestSourceFile androidTestSource =
- new TestSourceFile("src/androidTest/java/com/example/helloworld", "HelloWorldTest.java",
-"""
-package com.example.helloworld;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class HelloWorldTest extends ActivityInstrumentationTestCase2<HelloWorld> {
- private TextView mTextView;
-
- public HelloWorldTest() {
- super("com.example.helloworld", HelloWorld.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final HelloWorld a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
-
- }
-
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-}
-""");
-
- public HelloWorldApp() {
- addFiles(javaSource, resValuesSource, resLayoutSource, manifest, androidTestSource);
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldJniApp.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldJniApp.groovy
deleted file mode 100644
index fbb410d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldJniApp.groovy
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.fixture.app
-/**
- * Simple test application that uses JNI to print a "hello world!".
- *
- * NOTE: Android project must create an NDK module named "hello-jni".
- */
-public class HelloWorldJniApp extends AbstractAndroidTestApp implements AndroidTestApp {
-
- static private final TestSourceFile javaSource =
- new TestSourceFile("src/main/java/com/example/hellojni", "HelloJni.java",
- """
-package com.example.hellojni;
-
-import android.app.Activity;
-import android.widget.TextView;
-import android.os.Bundle;
-
-public class HelloJni extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Create a TextView and set its content from a native function.
- TextView tv = new TextView(this);
- tv.setText( stringFromJNI() );
- setContentView(tv);
- }
-
- // A native method that is implemented by the 'hello-jni' native library.
- public native String stringFromJNI();
-
- static {
- System.loadLibrary("hello-jni");
- }
-}
-""");
-
- // JNI Implementation in C.
- static private final TestSourceFile cSource =
- new TestSourceFile("src/main/jni", "hello-jni.c",
-"""
-#include <string.h>
-#include <jni.h>
-
-// This is a trivial JNI example where we use a native method
-// to return a new VM String.
-jstring
-Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
-{
- return (*env)->NewStringUTF(env, "hello world!");
-}
-""");
-
- // JNI Implementation in C++.
- static private final TestSourceFile cppSource =
- new TestSourceFile("src/main/jni", "hello-jni.cpp",
-"""
-#include <string.h>
-#include <jni.h>
-#include <cctype>
-
-// This is a trivial JNI example where we use a native method
-// to return a new VM String.
-extern "C"
-jstring
-Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
-{
- char greeting[] = "HELLO WORLD!";
- char* ptr = greeting;
- while (*ptr) {
- *ptr = std::tolower(*ptr);
- ++ptr;
- }
- return env->NewStringUTF(greeting);
-}
-""");
-
-
- static private final TestSourceFile resSource =
- new TestSourceFile("src/main/res/values", "strings.xml",
- """<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">HelloJni</string>
-</resources>
-""");
-
- static private final TestSourceFile manifest =
- new TestSourceFile("src/main", "AndroidManifest.xml",
-"""<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.hellojni"
- android:versionCode="1"
- android:versionName="1.0">
-
- <uses-sdk android:minSdkVersion="3" />
- <application android:label="@string/app_name">
- <activity android:name=".HelloJni"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
-""");
-
- static private final TestSourceFile androidTestSource =
- new TestSourceFile("src/androidTest/java/com/example/hellojni", "HelloJniTest.java",
-"""
-package com.example.hellojni;
-
-import android.test.ActivityInstrumentationTestCase;
-
-/**
- * This is a simple framework for a test of an Application. See
- * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
- * how to write and extend Application tests.
- * <p/>
- * To run this test, you can type:
- * adb shell am instrument -w \
- * -e class com.example.hellojni.HelloJniTest \
- * com.example.hellojni.tests/android.test.InstrumentationTestRunner
- */
-public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
-
- public HelloJniTest() {
- super("com.example.hellojni", HelloJni.class);
- }
-
-
- public void testJniName() {
- final HelloJni a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
-
- assertTrue("hello world!".equals(a.stringFromJNI()));
- }
-}
-""");
-
- public HelloWorldJniApp(Map args = [:]) {
- def defaultArgs = [jniDir: "jni", useCppSource: false]
- defaultArgs << args
- TestSourceFile jniSource = defaultArgs.useCppSource ? cppSource : cSource
- addFiles(
- javaSource,
- new TestSourceFile("src/main/$defaultArgs.jniDir", jniSource.name, jniSource.content),
- resSource,
- manifest,
- androidTestSource);
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldLibraryApp.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldLibraryApp.groovy
deleted file mode 100644
index b6a97c0..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldLibraryApp.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.fixture.app
-
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.TestProject
-
-/**
- * Simple test application with an Android library that prints "hello world!".
- */
-class HelloWorldLibraryApp extends MultiModuleTestProject implements TestProject {
- public HelloWorldLibraryApp() {
- super(":app" : new EmptyAndroidTestApp(), ":lib" : new HelloWorldApp());
-
- AndroidTestApp app = (AndroidTestApp) getSubproject(":app");
-
- // Create AndroidManifest.xml that uses the Activity from the library.
- app.addFile(new TestSourceFile("src/main", "AndroidManifest.xml",
-"""<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.app"
- android:versionCode="1"
- android:versionName="1.0">
-
- <uses-sdk android:minSdkVersion="3" />
- <application android:label="@string/app_name">
- <activity
- android:name="com.example.helloworld.HelloWorld"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
-"""));
-
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/MultiModuleTestProject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/MultiModuleTestProject.java
deleted file mode 100644
index 56ce805..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/MultiModuleTestProject.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.android.build.gradle.integration.common.fixture.app;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.gradle.integration.common.fixture.TestProject;
-import com.google.common.collect.Maps;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Map;
-
-/**
- * A TestProject containing multiple TestProject as modules.
- */
-public class MultiModuleTestProject implements TestProject {
-
- private Map<String, TestProject> subprojects;
-
- /**
- * Creates a MultiModuleTestProject.
- *
- * @param subprojects a map with gradle project path as key and the corresponding TestProject as
- * value.
- */
- public MultiModuleTestProject(Map<String, TestProject> subprojects) {
- this.subprojects = Maps.newHashMap(subprojects);
- }
-
- /**
- * Creates a MultiModuleTestProject with multiple subproject of the same TestProject.
- *
- * @param baseName Base name of the subproject. Actual project name will be baseName + index.
- * @param subproject A TestProject.
- * @param count Number of subprojects to create.
- */
- public MultiModuleTestProject(String baseName, TestProject subproject, int count) {
- subprojects = Maps.newHashMapWithExpectedSize(count);
- for (int i = 0; i < count; i++) {
- subprojects.put(baseName + i, subproject);
- }
- }
-
- /**
- * Return the test project with the given project path.
- */
- public TestProject getSubproject(String subprojectPath) {
- return subprojects.get(subprojectPath);
- }
-
- @Override
- public void write(
- @NonNull final File projectDir,
- @Nullable final String buildScriptContent) throws IOException {
- for (Map.Entry<String, ? extends TestProject> entry : subprojects.entrySet()) {
- String subprojectPath = entry.getKey();
- TestProject subproject = entry.getValue();
- File subprojectDir = new File(projectDir, convertGradlePathToDirectory(subprojectPath));
- if (!subprojectDir.exists()) {
- subprojectDir.mkdirs();
- assert subprojectDir.isDirectory();
- }
- subproject.write(subprojectDir, null);
- }
-
- StringBuilder builder = new StringBuilder();
- for (String subprojectName : subprojects.keySet()) {
- builder.append("include '").append(subprojectName).append("'\n");
- }
- Files.write(builder.toString(),
- new File(projectDir, "settings.gradle"),
- Charset.defaultCharset());
-
- Files.write(buildScriptContent,
- new File(projectDir, "build.gradle"),
- Charset.defaultCharset());
- }
-
- @Override
- public boolean containsFullBuildScript() {
- return false;
- }
-
- private static String convertGradlePathToDirectory(String gradlePath) {
- return gradlePath.replace(":", "/");
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/AllTests.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/AllTests.java
deleted file mode 100644
index 03a6931..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/AllTests.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.runner;
-
-import com.android.build.gradle.integration.common.utils.FileHelper;
-import com.google.common.collect.Lists;
-
-import org.junit.Test;
-import org.junit.runners.Suite;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.RunnerBuilder;
-
-import java.io.File;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.List;
-
-/**
- * JUnit runner that includes all JUnit4 tests.
- */
-public class AllTests extends Suite {
- private static final String DEFAULT_CLASSPATH_PROPERTY = "java.class.path";
-
- public AllTests(Class<?> clazz, RunnerBuilder builder) throws InitializationError {
- super(builder, clazz, findTestClasses());
- }
-
- /**
- * Find all test classes.
- *
- * Inspect all classes in classpath and include all classes that contains methods annotated with
- * <code>@Test</code>.
- */
- private static Class<?>[] findTestClasses() {
- String classPaths = System.getProperty(DEFAULT_CLASSPATH_PROPERTY);
- final String separator = System.getProperty("path.separator");
- List<Class<?>> classes = Lists.newArrayList();
- for (String classPath : classPaths.split(separator)) {
- File classPathDir = new File(classPath);
- // Currently only support classes in .class files. Add support for .jar if necessary.
- if (classPathDir.isDirectory()) {
- findTestClassesInDirectory(classes, classPathDir);
- }
- }
- return classes.toArray(new Class<?>[classes.size()]);
- }
-
- /**
- * Find all test classes in a directory.
- */
- private static void findTestClassesInDirectory(List<Class<?>> classes,File base) {
- for (String filename : FileHelper.listFiles(base)) {
- if (!filename.endsWith(".class")) {
- continue;
- }
- String className = getClassNameFromFile(filename);
- try {
- Class<?> testClass = Class.forName(className);
- if (isJUnit4Test(testClass)) {
- classes.add(testClass);
- }
- } catch (ClassNotFoundException ignore) {
- }
- }
- }
-
- private static String getClassNameFromFile(String classFileName) {
- // convert /a/b.class to a.b
- String className = classFileName
- .substring(0, classFileName.length() - ".class".length()) // remove .class
- .replace(File.separatorChar, '.'); // replace '/' with '.'
- if (className.startsWith("."))
- return className.substring(1);
- return className;
- }
-
- private static boolean isJUnit4Test(Class<?> testClass) {
- // Check testClass is not abstract.
- if ((testClass.getModifiers() & Modifier.ABSTRACT) != 0) {
- return false;
- }
-
- for (Method method : testClass.getMethods()) {
- if (method.isAnnotationPresent(Test.class)) {
- return true;
- }
- }
- return false;
- }
-
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AarSubject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AarSubject.java
deleted file mode 100644
index 8554fae..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AarSubject.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.process.ProcessException;
-import com.google.common.base.Charsets;
-import com.google.common.io.CharStreams;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.StringSubject;
-import com.google.common.truth.SubjectFactory;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * Truth support for aar files.
- */
-public class AarSubject extends AbstractAndroidSubject<AarSubject> {
-
- static class Factory extends SubjectFactory<AarSubject, File> {
- @NonNull
- public static Factory get() {
- return new Factory();
- }
-
- private Factory() {}
-
- @Override
- public AarSubject getSubject(
- @NonNull FailureStrategy failureStrategy,
- @NonNull File subject) {
- return new AarSubject(failureStrategy, subject);
- }
- }
-
- public AarSubject(@NonNull FailureStrategy failureStrategy, @NonNull File subject) {
- super(failureStrategy, subject);
- }
-
- @NonNull
- public StringSubject textSymbolFile() throws IOException {
- InputStream stream = getInputStream("R.txt");
-
- return new StringSubject(failureStrategy,
- CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8)));
- }
-
- /**
- * Returns true if the provided class is present in the file.
- * @param expectedClassName the class name in the format Lpkg1/pk2/Name;
- */
- @Override
- protected boolean checkForClass(
- @NonNull String expectedClassName)
- throws ProcessException, IOException {
- InputStream stream = getInputStream("classes.jar");
-
- ZipInputStream zis = new ZipInputStream(stream);
- try {
- ZipEntry zipEntry;
- while ((zipEntry = zis.getNextEntry()) != null) {
- if (expectedClassName.equals(zipEntry.getName())) {
- return true;
- }
- }
-
- // didn't find the class.
- return false;
- } finally {
- zis.close();
- }
- }
-
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractAndroidSubject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractAndroidSubject.java
deleted file mode 100644
index 8c83940..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractAndroidSubject.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.process.ProcessException;
-import com.google.common.truth.FailureStrategy;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.zip.ZipFile;
-
-/**
- * Base Truth support for android archives (aar and apk)
- */
-public abstract class AbstractAndroidSubject<T extends AbstractZipSubject<T>> extends AbstractZipSubject<T> {
-
- public AbstractAndroidSubject(@NonNull FailureStrategy failureStrategy, @NonNull File subject) {
- super(failureStrategy, subject);
- }
-
- /**
- * Returns true if the provided class is present in the file.
- * @param expectedClassName the class name in the format Lpkg1/pk2/Name;
- */
- protected abstract boolean checkForClass(
- @NonNull String expectedClassName)
- throws ProcessException, IOException;
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void containsClass(String className) throws IOException, ProcessException {
- if (!checkForClass(className)) {
- failWithRawMessage("'%s' does not contain '%s'", getDisplaySubject(), className);
- }
- }
-
- public void doesNotContainClass(String className) throws IOException, ProcessException {
- if (checkForClass(className)) {
- failWithRawMessage("'%s' unexpectedly contains '%s'", getDisplaySubject(), className);
- }
- }
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void containsResource(String name) throws IOException, ProcessException {
- if (!checkForResource(name)) {
- failWithRawMessage("'%s' does not contain resource '%s'", getDisplaySubject(), name);
- }
- }
-
- public void doesNotContainResource(String name) throws IOException, ProcessException {
- if (checkForResource(name)) {
- failWithRawMessage("'%s' unexpectedly contains resource '%s'", getDisplaySubject(), name);
- }
- }
-
- @Override
- protected String getDisplaySubject() {
- String name = (internalCustomName() == null) ? "" : "\"" + internalCustomName() + "\" ";
- return name + "<" + getSubject().getName() + ">";
- }
-
- private boolean checkForResource(String name) throws IOException {
- ZipFile zipFile = null;
- try {
- zipFile = new ZipFile(getSubject());
- return zipFile.getEntry("res/" + name) != null;
- } finally {
- if (zipFile != null) {
- zipFile.close();
- }
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractZipSubject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractZipSubject.java
deleted file mode 100644
index 3566744..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractZipSubject.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
-
-import com.android.annotations.NonNull;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.io.ByteStreams;
-import com.google.common.primitives.Bytes;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.IterableSubject;
-import com.google.common.truth.Subject;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Truth support for zip files.
- */
-public abstract class AbstractZipSubject<T extends Subject<T, File>> extends Subject<T, File> {
- private ZipFile zip;
-
- public AbstractZipSubject(@NonNull FailureStrategy failureStrategy, @NonNull File subject) {
- super(failureStrategy, subject);
- new FileSubject(failureStrategy, subject).exists();
- try {
- zip = new ZipFile(subject);
- } catch (IOException e) {
- failWithRawMessage("IOException thrown when creating ZipFile: '%s'.", e.toString());
- }
- }
-
- /**
- * Asserts the zip file contains a file with the specified path.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void contains(@NonNull String path) {
- if (zip.getEntry(path) == null) {
- failWithRawMessage("'%s' does not contain '%s'", zip.getName(), path);
- }
- }
-
- /**
- * Asserts the zip file does not contains a file with the specified path.
- */
- public void doesNotContain(@NonNull String path) {
- if (zip.getEntry(path) != null) {
- failWithRawMessage("'%s' unexpectedly contains '%s'", zip.getName(), path);
- }
- }
-
- /**
- * Returns a {@link IterableSubject} of all the Zip entries which name matches the passed
- * regular expression.
- *
- * @param conformingTo a regular expression to match entries we are interested in.
- * @return a {@link IterableSubject} propositions for matching entries.
- * @throws IOException of the zip file cannot be opened.
- */
- public IterableSubject<? extends IterableSubject<?, String, List<String>>, String, List<String>> entries(
- @NonNull String conformingTo) throws IOException {
-
- ImmutableList.Builder<String> entries = ImmutableList.builder();
- Pattern pattern = Pattern.compile(conformingTo);
- ZipFile zipFile = new ZipFile(getSubject());
- try {
- Enumeration<? extends ZipEntry> zipFileEntries = zipFile.entries();
- while (zipFileEntries.hasMoreElements()) {
- ZipEntry zipEntry = zipFileEntries.nextElement();
- if (pattern.matcher(zipEntry.getName()).matches()) {
- entries.add(zipEntry.getName());
- }
- }
- } finally {
- zipFile.close();
- }
- return assertThat(entries.build());
- }
-
- /**
- * Asserts the zip file contains a file with the specified String content.
- *
- * Content is trimmed when compared.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void containsFileWithContent(@NonNull String path, @NonNull String content) {
- assertThat(extractContentAsString(path).trim()).named(path).isEqualTo(content.trim());
- }
-
- /**
- * Asserts the zip file contains a file with the specified byte array content.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void containsFileWithContent(@NonNull String path, @NonNull byte[] content) {
- assertThat(extractContentAsByte(path)).named(path).isEqualTo(content);
- }
-
- /**
- * Asserts the zip file contains a file <b>without</b> the specified byte sequence
- * <b>anywhere</b> in the file
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void containsFileWithoutContent(@NonNull String path, @NonNull byte[] sub) {
- byte[] contents = extractContentAsByte(path);
- int index = Bytes.indexOf(contents, sub);
- if (index != -1) {
- failWithRawMessage("Found byte sequence at " + index + " in class file " + path);
- }
- }
-
- protected String extractContentAsString(@NonNull String path) {
- InputStream stream = getInputStream(path);
- try {
- return new String(ByteStreams.toByteArray(stream), Charsets.UTF_8).trim();
- } catch (IOException e) {
- failWithRawMessage("IOException when extracting zip: %s", e.toString());
- return null;
- }
- }
-
- protected byte[] extractContentAsByte(@NonNull String path) {
- InputStream stream = getInputStream(path);
- try {
- return ByteStreams.toByteArray(stream);
- } catch (IOException e) {
- failWithRawMessage("IOException when extracting zip: %s", e.toString());
- return null;
- }
- }
-
- protected InputStream getInputStream(@NonNull String path) {
- ZipEntry entry = zip.getEntry(path);
- if (entry == null) {
- failWithRawMessage("'%s' does not contain '%s'", zip.getName(), path);
- return null;
- }
-
- if (entry.isDirectory()) {
- failWithRawMessage("Unable to compare content, '%s' is a directory.", path);
- }
-
- try {
- return zip.getInputStream(entry);
- } catch (IOException e) {
- failWithRawMessage("IOException when extracting zip: %s", e.toString());
- return null;
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubject.java
deleted file mode 100644
index 291ff13..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubject.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.build.gradle.integration.common.utils.ApkHelper;
-import com.android.build.gradle.integration.common.utils.SdkHelper;
-import com.android.builder.core.ApkInfoParser;
-import com.android.ide.common.process.DefaultProcessExecutor;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessExecutor;
-import com.android.ide.common.process.ProcessInfoBuilder;
-import com.android.utils.StdLogger;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.IterableSubject;
-import com.google.common.truth.SubjectFactory;
-import com.google.common.truth.Truth;
-
-import org.junit.Assert;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Truth support for apk files.
- */
-public class ApkSubject extends AbstractAndroidSubject<ApkSubject> {
-
- private static final Pattern PATTERN_CLASS_DESC = Pattern.compile(
- "^Class descriptor\\W*:\\W*'(L.+;)'$");
-
- private static final Pattern PATTERN_MAX_SDK_VERSION = Pattern.compile(
- "^maxSdkVersion\\W*:\\W*'(.+)'$");
-
- static class Factory extends SubjectFactory<ApkSubject, File> {
- @NonNull
- public static Factory get() {
- return new Factory();
- }
-
- private Factory() {}
-
- @Override
- public ApkSubject getSubject(
- @NonNull FailureStrategy failureStrategy,
- @NonNull File subject) {
- return new ApkSubject(failureStrategy, subject);
- }
- }
-
-
- public ApkSubject(
- @NonNull FailureStrategy failureStrategy,
- @NonNull File subject) {
- super(failureStrategy, subject);
- }
-
- @NonNull
- public IterableSubject<? extends IterableSubject<?, String, List<String>>, String, List<String>> locales() throws ProcessException {
- File apk = getSubject();
- List<String> locales = ApkHelper.getLocales(apk);
-
- if (locales == null) {
- Assert.fail(String.format("locales not found in badging output for %s", apk));
- }
-
- return Truth.assertThat(locales);
- }
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void hasVersionCode(int versionCode) throws ProcessException {
- File apk = getSubject();
-
- ApkInfoParser.ApkInfo apkInfo = getApkInfo(apk);
-
- Integer actualVersionCode = apkInfo.getVersionCode();
- if (actualVersionCode == null) {
- failWithRawMessage("Unable to query %s for versionCode", getDisplaySubject());
- }
-
- if (!apkInfo.getVersionCode().equals(versionCode)) {
- failWithBadResults("has versionCode", versionCode, "is", actualVersionCode);
- }
- }
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void hasVersionName(@NonNull String versionName) throws ProcessException {
- File apk = getSubject();
-
- ApkInfoParser.ApkInfo apkInfo = getApkInfo(apk);
-
- String actualVersionName = apkInfo.getVersionName();
- if (actualVersionName == null) {
- failWithRawMessage("Unable to query %s for versionName", getDisplaySubject());
- }
-
- if (!apkInfo.getVersionName().equals(versionName)) {
- failWithBadResults("has versionName", versionName, "is", actualVersionName);
- }
- }
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void hasMaxSdkVersion(int maxSdkVersion) throws ProcessException {
-
- List<String> output = ApkHelper.getApkBadging(getSubject());
-
- checkMaxSdkVersion(output, maxSdkVersion);
- }
-
- @Override
- protected String getDisplaySubject() {
- String name = (internalCustomName() == null) ? "" : "\"" + internalCustomName() + "\" ";
- return name + "<" + getSubject().getName() + ">";
- }
-
- /**
- * Returns true if the provided class is present in the file.
- * @param expectedClassName the class name in the format Lpkg1/pk2/Name;
- */
- @Override
- protected boolean checkForClass(
- @NonNull String expectedClassName)
- throws ProcessException, IOException {
- // get the dexdump exec
- File dexDump = SdkHelper.getDexDump();
-
- ProcessExecutor executor = new DefaultProcessExecutor(
- new StdLogger(StdLogger.Level.ERROR));
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
- builder.setExecutable(dexDump);
- builder.addArgs(getSubject().getAbsolutePath());
-
- List<String> output = ApkHelper.runAndGetOutput(builder.createProcess(), executor);
-
- for (String line : output) {
- Matcher m = PATTERN_CLASS_DESC.matcher(line.trim());
- if (m.matches()) {
- String className = m.group(1);
- if (expectedClassName.equals(className)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @NonNull
- private static ApkInfoParser.ApkInfo getApkInfo(@NonNull File apk) throws ProcessException {
- ProcessExecutor processExecutor = new DefaultProcessExecutor(
- new StdLogger(StdLogger.Level.ERROR));
- ApkInfoParser parser = new ApkInfoParser(SdkHelper.getAapt(), processExecutor);
- return parser.parseApk(apk);
- }
-
- @VisibleForTesting
- void checkMaxSdkVersion(@NonNull List<String> output, int maxSdkVersion) {
- for (String line : output) {
- Matcher m = PATTERN_MAX_SDK_VERSION.matcher(line.trim());
- if (m.matches()) {
- String actual = m.group(1);
- try {
- Integer i = Integer.parseInt(actual);
- if (!i.equals(maxSdkVersion)) {
- failWithBadResults("has maxSdkVersion", maxSdkVersion, "is", i);
- }
- return;
- } catch (NumberFormatException e) {
- failureStrategy.fail(
- String.format(
- "maxSdkVersion in badging for %s is not a number: %s",
- getDisplaySubject(), actual),
- e);
- }
- }
- }
-
- failWithRawMessage("maxSdkVersion not found in badging output for %s", getDisplaySubject());
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DependenciesSubject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DependenciesSubject.java
deleted file mode 100644
index b5c04b9..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DependenciesSubject.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.Dependencies;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
-
-import java.util.Collection;
-
-
-public class DependenciesSubject extends Subject<DependenciesSubject, Dependencies> {
-
- static class Factory extends
- SubjectFactory<DependenciesSubject, Dependencies> {
- @NonNull
- public static Factory get() {
- return new Factory();
- }
-
- private Factory() {}
-
- @Override
- public DependenciesSubject getSubject(
- @NonNull FailureStrategy failureStrategy,
- @NonNull Dependencies subject) {
- return new DependenciesSubject(failureStrategy, subject);
- }
- }
-
- public DependenciesSubject(
- @NonNull FailureStrategy failureStrategy,
- @NonNull Dependencies subject) {
- super(failureStrategy, subject);
- }
-
- /**
- * Checks that the dependencies has a single library.
- *
- * @return the library.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public AndroidLibrary hasOneLibrary() {
- Collection<AndroidLibrary> libs = getSubject().getLibraries();
-
- assertThat(libs).hasSize(1);
-
- return libs.iterator().next();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubject.java
deleted file mode 100644
index f9a6975..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubject.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.Subject;
-
-import java.io.File;
-
-/**
- * Truth support for validating File.
- */
-public class FileSubject extends Subject<FileSubject, File> {
- public FileSubject(FailureStrategy failureStrategy, File subject) {
- super(failureStrategy, subject);
- }
-
- public void exists() {
- if (!getSubject().exists()) {
- fail("exists");
- }
- }
-
- public void doesNotExist() {
- if (getSubject().exists()) {
- fail("does not exist");
- }
- }
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void isFile() {
- if (!getSubject().exists()) {
- fail("is a file");
- }
- }
-
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void isDirectory() {
- if (!getSubject().isDirectory()) {
- fail("is a directory");
- }
- }
-
- public FileSubject subFile(String path) {
- if (!getSubject().isDirectory()) {
- fail("is a directory");
- }
- return new FileSubject(failureStrategy, new File(getSubject(), path));
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ModelSubject.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ModelSubject.java
deleted file mode 100644
index 5736717..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ModelSubject.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.SyncIssue;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
-
-import java.util.Collection;
-
-/**
- * Truth support for AndroidProject.
- */
-public class ModelSubject extends Subject<ModelSubject, AndroidProject> {
-
- static class Factory extends SubjectFactory<ModelSubject, AndroidProject> {
-
- @NonNull
- public static Factory get() {
- return new Factory();
- }
-
- private Factory() {}
-
- @Override
- public ModelSubject getSubject(
- @NonNull FailureStrategy failureStrategy,
- @NonNull AndroidProject subject) {
- return new ModelSubject(failureStrategy, subject);
- }
- }
-
- public ModelSubject(
- @NonNull FailureStrategy failureStrategy,
- @NonNull AndroidProject subject) {
- super(failureStrategy, subject);
- }
-
-
- /**
- * Asserts that the issue collection has only a single element with the given properties.
- * Not specified properties are not tested and could have any value.
- *
- * @param severity the expected severity
- * @param type the expected type
- * @return the found issue for further testing.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public SyncIssue hasSingleIssue(int severity, int type) {
- Collection<SyncIssue> subject = getSubject().getSyncIssues();
-
- assertThat(subject).hasSize(1);
-
- SyncIssue issue = subject.iterator().next();
- assertThat(issue).isNotNull();
- assertThat(issue).hasSeverity(severity);
- assertThat(issue).hasType(type);
-
- return issue;
- }
-
- /**
- * Asserts that the issue collection has only a single element with the given properties.
- * Not specified properties are not tested and could have any value.
- *
- * @param severity the expected severity
- * @param type the expected type
- * @param data the expected data
- * @return the found issue for further testing.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public SyncIssue hasSingleIssue(int severity, int type, String data) {
- Collection<SyncIssue> subject = getSubject().getSyncIssues();
-
- assertThat(subject).hasSize(1);
-
- SyncIssue issue = subject.iterator().next();
- assertThat(issue).isNotNull();
- assertThat(issue).hasSeverity(severity);
- assertThat(issue).hasType(type);
- assertThat(issue).hasData(data);
-
- return issue;
- }
-
- /**
- * Asserts that the issue collection has only a single element with the given properties.
- *
- * @param severity the expected severity
- * @param type the expected type
- * @param data the expected data
- * @param message the expected message
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public void hasSingleIssue(int severity, int type, String data, String message) {
- Collection<SyncIssue> subject = getSubject().getSyncIssues();
-
- assertThat(subject).hasSize(1);
-
- SyncIssue issue = subject.iterator().next();
- assertThat(issue).isNotNull();
- assertThat(issue).hasSeverity(severity);
- assertThat(issue).hasType(type);
- assertThat(issue).hasData(data);
- assertThat(issue).hasMessage(message);
- }
-
- /**
- * Asserts that the issue collection has only an element with the given properties.
- * Not specified properties are not tested and could have any value.
- *
- * @param severity the expected severity
- * @param type the expected type
- * @return the found issue for further testing.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public SyncIssue hasIssue(int severity, int type) {
- Collection<SyncIssue> subject = getSubject().getSyncIssues();
-
- for (SyncIssue issue : subject) {
- if (severity == issue.getSeverity() &&
- type == issue.getType()) {
- return issue;
- }
- }
-
- failWithRawMessage("'%s' does not contain <%s / %s>", getDisplaySubject(),
- severity, type);
- // won't reach
- return null;
- }
-
- /**
- * Asserts that the issue collection has only an element with the given properties.
- * Not specified properties are not tested and could have any value.
- *
- * @param severity the expected severity
- * @param type the expected type
- * @param data the expected data
- * @return the found issue for further testing.
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- public SyncIssue hasIssue(int severity, int type, String data) {
- Collection<SyncIssue> subject = getSubject().getSyncIssues();
-
- for (SyncIssue issue : subject) {
- if (severity == issue.getSeverity() &&
- type == issue.getType() &&
- data.equals(issue.getData())) {
- return issue;
- }
- }
-
- failWithRawMessage("'%s' does not contain <%s / %s / %s>", getDisplaySubject(),
- severity, type, data);
- // won't reach
- return null;
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/TruthHelper.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/TruthHelper.java
deleted file mode 100644
index 58d992e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/TruthHelper.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.truth;
-
-import static com.google.common.truth.Truth.assert_;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.SyncIssue;
-import com.android.builder.model.Variant;
-import com.google.common.annotations.GwtIncompatible;
-import com.google.common.base.Optional;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multiset;
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.Table;
-import com.google.common.truth.BigDecimalSubject;
-import com.google.common.truth.BooleanSubject;
-import com.google.common.truth.ClassSubject;
-import com.google.common.truth.ComparableSubject;
-import com.google.common.truth.DefaultSubject;
-import com.google.common.truth.DoubleSubject;
-import com.google.common.truth.IntegerSubject;
-import com.google.common.truth.IterableSubject;
-import com.google.common.truth.ListMultimapSubject;
-import com.google.common.truth.LongSubject;
-import com.google.common.truth.MapSubject;
-import com.google.common.truth.MultimapSubject;
-import com.google.common.truth.MultisetSubject;
-import com.google.common.truth.ObjectArraySubject;
-import com.google.common.truth.OptionalSubject;
-import com.google.common.truth.PrimitiveBooleanArraySubject;
-import com.google.common.truth.PrimitiveByteArraySubject;
-import com.google.common.truth.PrimitiveCharArraySubject;
-import com.google.common.truth.PrimitiveDoubleArraySubject;
-import com.google.common.truth.PrimitiveFloatArraySubject;
-import com.google.common.truth.PrimitiveIntArraySubject;
-import com.google.common.truth.PrimitiveLongArraySubject;
-import com.google.common.truth.SetMultimapSubject;
-import com.google.common.truth.StringSubject;
-import com.google.common.truth.Subject;
-import com.google.common.truth.TableSubject;
-import com.google.common.truth.TestVerb;
-import com.google.common.truth.ThrowableSubject;
-
-import java.io.File;
-import java.math.BigDecimal;
-import java.util.Map;
-
-/**
- * Helper for custom Truth factories.
- */
-public class TruthHelper {
- @NonNull
- public static FileSubject assertThat(@NonNull File file) {
- return assert_().about(FileSubjectFactory.factory()).that(file);
- }
-
- @NonNull
- public static ApkSubject assertThatApk(@NonNull File apk) {
- return assert_().about(ApkSubject.Factory.get()).that(apk);
- }
-
- @NonNull
- public static AarSubject assertThatAar(@NonNull File aar) {
- return assert_().about(AarSubject.Factory.get()).that(aar);
- }
-
- @NonNull
- public static ZipFileSubject assertThatZip(@NonNull File file) {
- return assert_().about(ZipFileSubject.Factory.get()).that(file);
- }
-
- @NonNull
- public static ModelSubject assertThat(@NonNull AndroidProject androidProject) {
- return assert_().about(ModelSubject.Factory.get()).that(androidProject);
- }
-
- @NonNull
- public static IssueSubject assertThat(@NonNull SyncIssue issue) {
- return assert_().about(IssueSubject.Factory.get()).that(issue);
- }
-
- @NonNull
- public static VariantSubject assertThat(@NonNull Variant variant) {
- return assert_().about(VariantSubject.Factory.get()).that(variant);
- }
-
- @NonNull
- public static ArtifactSubject assertThat(@NonNull AndroidArtifact artifact) {
- return assert_().about(ArtifactSubject.Factory.get()).that(artifact);
- }
-
- @NonNull
- public static DependenciesSubject assertThat(@NonNull Dependencies dependencies) {
- return assert_().about(DependenciesSubject.Factory.get()).that(
- dependencies);
- }
-
- // ---- helper method from com.google.common.truth.Truth
- // this to allow a single static import of assertThat
-
- /**
- * Returns a {@link TestVerb} that will prepend the given message to the failure message in
- * the event of a test failure.
- */
- public static TestVerb assertWithMessage(String messageToPrepend) {
- return assert_().withFailureMessage(messageToPrepend);
- }
-
- public static <T extends Comparable<?>> ComparableSubject<?, T> assertThat(@Nullable T target) {
- return assert_().that(target);
- }
-
- public static BigDecimalSubject assertThat(@Nullable BigDecimal target) {
- return assert_().that(target);
- }
-
- public static Subject<DefaultSubject, Object> assertThat(@Nullable Object target) {
- return assert_().that(target);
- }
-
- @GwtIncompatible("ClassSubject.java")
- public static ClassSubject assertThat(@Nullable Class<?> target) {
- return assert_().that(target);
- }
-
- public static ThrowableSubject assertThat(@Nullable Throwable target) {
- return assert_().that(target);
- }
-
- public static LongSubject assertThat(@Nullable Long target) {
- return assert_().that(target);
- }
-
- public static DoubleSubject assertThat(@Nullable Double target) {
- return assert_().that(target);
- }
-
- public static IntegerSubject assertThat(@Nullable Integer target) {
- return assert_().that(target);
- }
-
- public static BooleanSubject assertThat(@Nullable Boolean target) {
- return assert_().that(target);
- }
-
- public static StringSubject assertThat(@Nullable String target) {
- return assert_().that(target);
- }
-
- public static <T, C extends Iterable<T>> IterableSubject<? extends IterableSubject<?, T, C>, T, C>
- assertThat(@Nullable Iterable<T> target) {
- return assert_().that(target);
- }
-
- public static <T> ObjectArraySubject<T> assertThat(@Nullable T[] target) {
- return assert_().that(target);
- }
-
- public static PrimitiveBooleanArraySubject assertThat(@Nullable boolean[] target) {
- return assert_().that(target);
- }
-
- public static PrimitiveIntArraySubject assertThat(@Nullable int[] target) {
- return assert_().that(target);
- }
-
- public static PrimitiveLongArraySubject assertThat(@Nullable long[] target) {
- return assert_().that(target);
- }
-
- public static PrimitiveByteArraySubject assertThat(@Nullable byte[] target) {
- return assert_().that(target);
- }
-
- public static PrimitiveCharArraySubject assertThat(@Nullable char[] target) {
- return assert_().that(target);
- }
-
- public static PrimitiveFloatArraySubject assertThat(@Nullable float[] target) {
- return assert_().that(target);
- }
-
- public static PrimitiveDoubleArraySubject assertThat(@Nullable double[] target) {
- return assert_().that(target);
- }
-
- public static <T> OptionalSubject<T> assertThat(@Nullable Optional<T> target) {
- return assert_().that(target);
- }
-
- public static MapSubject assertThat(@Nullable Map<?, ?> target) {
- return assert_().that(target);
- }
-
- public static <K, V, M extends Multimap<K, V>>
- MultimapSubject<? extends MultimapSubject<?, K, V, M>, K, V, M> assertThat(
- @Nullable Multimap<K, V> target) {
- return assert_().that(target);
- }
-
- public static <K, V, M extends ListMultimap<K, V>>
- ListMultimapSubject<? extends ListMultimapSubject<?, K, V, M>, K, V, M> assertThat(
- @Nullable ListMultimap<K, V> target) {
- return assert_().that(target);
- }
-
- public static <K, V, M extends SetMultimap<K, V>>
- SetMultimapSubject<? extends SetMultimapSubject<?, K, V, M>, K, V, M> assertThat(
- @Nullable SetMultimap<K, V> target) {
- return assert_().that(target);
- }
-
- public static <E, M extends Multiset<E>>
- MultisetSubject<? extends MultisetSubject<?, E, M>, E, M> assertThat(
- @Nullable Multiset<E> target) {
- return assert_().that(target);
- }
-
- public static TableSubject assertThat(@Nullable Table<?, ?, ?> target) {
- return assert_().that(target);
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ApkHelper.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ApkHelper.java
deleted file mode 100644
index ffa6cf3..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ApkHelper.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.utils;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.process.CachedProcessOutputHandler;
-import com.android.ide.common.process.DefaultProcessExecutor;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessExecutor;
-import com.android.ide.common.process.ProcessInfo;
-import com.android.ide.common.process.ProcessInfoBuilder;
-import com.android.utils.StdLogger;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Helper to help read/test the content of generated apk file.
- */
-public class ApkHelper {
-
- private static final Pattern PATTERN_LOCALES = Pattern.compile(
- "^locales\\W*:\\W*(.+)$");
-
- /**
- * Runs a process, and returns the output.
- *
- * @param processInfo the process info to run
- *
- * @return the output as a list of files.
- * @throws ProcessException
- */
- @NonNull
- public static List<String> runAndGetOutput(@NonNull ProcessInfo processInfo)
- throws ProcessException {
-
- ProcessExecutor executor = new DefaultProcessExecutor(
- new StdLogger(StdLogger.Level.ERROR));
- return runAndGetOutput(processInfo, executor);
- }
-
- /**
- * Runs a process, and returns the output.
- *
- * @param processInfo the process info to run
- * @param processExecutor the process executor
- *
- * @return the output as a list of files.
- * @throws ProcessException
- */
- @NonNull
- public static List<String> runAndGetOutput(
- @NonNull ProcessInfo processInfo,
- @NonNull ProcessExecutor processExecutor)
- throws ProcessException {
- CachedProcessOutputHandler handler = new CachedProcessOutputHandler();
- processExecutor.execute(processInfo, handler).rethrowFailure().assertNormalExitValue();
- return Splitter.on('\n').splitToList(handler.getProcessOutput().getStandardOutputAsString());
- }
-
- @NonNull
- public static List<String> getApkBadging(@NonNull File apk) throws ProcessException {
- File aapt = SdkHelper.getAapt();
-
- ProcessInfoBuilder builder = new ProcessInfoBuilder();
- builder.setExecutable(aapt);
- builder.addArgs("dump", "badging", apk.getPath());
-
- return ApkHelper.runAndGetOutput(builder.createProcess());
- }
-
- /**
- * Returns the locales of an apk as found in the badging information
- * @param apk the apk
- * @return the list of locales or null.
- * @throws ProcessException
- *
- * @see #getApkBadging(File)
- */
- @Nullable
- public static List<String> getLocales(@NonNull File apk) throws ProcessException {
- List<String> output = getApkBadging(apk);
-
- for (String line : output) {
- Matcher m = PATTERN_LOCALES.matcher(line.trim());
- if (m.matches()) {
- List<String> list = Splitter.on(' ').splitToList(m.group(1).trim());
- List<String> result = Lists.newArrayListWithCapacity(list.size());
- for (String local: list) {
- // remove the '' on each side.
- result.add(local.substring(1, local.length() - 1));
- }
-
- return result;
- }
- }
-
- return null;
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/FileHelper.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/FileHelper.java
deleted file mode 100644
index 2967398..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/FileHelper.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.utils;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Charsets;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Helper to help verify content of a file.
- */
-public class FileHelper {
-
- /**
- * Return a list of relative path of the files in a directory.
- */
- public static List<String> listFiles(@NonNull File base) {
- assertTrue(base.isDirectory());
-
- List<String> fileList = Lists.newArrayList();
- for (File file : Files.fileTreeTraverser().preOrderTraversal(base).filter(
- new Predicate<File>() {
- @Override
- public boolean apply(@Nullable File file) {
- // we want to skip directories and symlinks, so isFile is the best check.
- return file != null && file.isFile();
- }
- })) {
- assertThat(file.toString()).startsWith(base.toString());
- String fileName = file.toString().substring(base.toString().length());
- fileList.add(fileName);
-
- }
- return fileList;
- }
-
- public static void checkContent(File file, String expectedContent) throws IOException {
- checkContent(file, Collections.singleton(expectedContent));
- }
-
- public static void checkContent(File file, Iterable<String> expectedContents) throws IOException {
- assertTrue("File '" + file.getAbsolutePath() + "' does not exist.", file.isFile());
-
- String contents = Files.toString(file, Charsets.UTF_8);
- for (String expectedContent : expectedContents) {
- assertTrue("File '" + file.getAbsolutePath() + "' does not contain: " + expectedContent,
- contents.contains(expectedContent));
- }
- }
-
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SdkHelper.java b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SdkHelper.java
deleted file mode 100644
index ccc38dd..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SdkHelper.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.common.utils;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-
-import java.io.File;
-
-/**
- * Helper for SDK related functions.
- */
-public class SdkHelper {
-
- private static final String BUILD_TOOLS_VERSION = "21.1.1";
-
- /**
- * Returns the SDK folder as built from the Android source tree.
- */
- public static File findSdkDir() {
- String androidHome = System.getenv("ANDROID_HOME");
- if (androidHome != null) {
- File f = new File(androidHome);
- if (f.isDirectory()) {
- return f;
- } else {
- System.out.println("Failed to find SDK in ANDROID_HOME=" + androidHome);
- }
- }
- return null;
- }
-
- @NonNull
- public static File getAapt() {
- return getBuildTool(
- FullRevision.parseRevision(BUILD_TOOLS_VERSION),
- BuildToolInfo.PathId.AAPT);
- }
-
- @NonNull
- public static File getAapt(@NonNull FullRevision fullRevision) {
- return getBuildTool(fullRevision, BuildToolInfo.PathId.AAPT);
- }
-
- @NonNull
- public static File getDexDump() {
- return getBuildTool(
- FullRevision.parseRevision(BUILD_TOOLS_VERSION),
- BuildToolInfo.PathId.DEXDUMP);
- }
-
- @NonNull
- public static File getBuildTool(
- @NonNull FullRevision fullRevision,
- @NonNull BuildToolInfo.PathId pathId) {
- ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
- SdkManager sdkManager = SdkManager.createManager(findSdkDir().getAbsolutePath(), logger);
- assert sdkManager != null;
- BuildToolInfo buildToolInfo = sdkManager.getBuildTool(fullRevision);
- if (buildToolInfo == null) {
- throw new RuntimeException("Test requires build-tools " + fullRevision.toString());
- }
- return new File(buildToolInfo.getPath(pathId));
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AndroidComponentPluginTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AndroidComponentPluginTest.groovy
deleted file mode 100644
index fb15666..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AndroidComponentPluginTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Test AndroidComponentModelPlugin.
- */
- at CompileStatic
-class AndroidComponentPluginTest {
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .forExpermimentalPlugin(true)
- .create();
-
- @BeforeClass
- public static void setUp() {
-
- project.buildFile << """
-import com.android.build.gradle.model.AndroidComponentModelPlugin
-apply plugin: AndroidComponentModelPlugin
-
-model {
- android.buildTypes {
- create("custom")
- }
- android.productFlavors {
- create("flavor1")
- create("flavor2")
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void assemble() {
- project.execute("assemble")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AppComponentPluginTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AppComponentPluginTest.groovy
deleted file mode 100644
index 0556cdf..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AppComponentPluginTest.groovy
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.category.SmokeTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import groovy.transform.CompileStatic
-import com.android.builder.model.AndroidProject
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.google.common.truth.Truth.assertThat
-
-/**
- * Basic integration test for AppComponentModelPlugin.
- */
- at Category(SmokeTests.class)
- at CompileStatic
-class AppComponentPluginTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .forExpermimentalPlugin(true)
- .withoutNdk()
- .create();
-
- @Before
- public void setUp() {
- project.buildFile << """
-apply plugin: "com.android.model.application"
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
-}
-"""
- }
-
- @Test
- public void basicAssemble() {
- AndroidProject model = project.executeAndReturnModel("assemble");
- assertThat(model).isNotNull();
- assertThat(model.getName()).isEqualTo(project.name)
- assertThat(model.getBuildTypes()).hasSize(2)
- assertThat(model.getProductFlavors()).hasSize(0)
- assertThat(model.getVariants()).hasSize(2)
- }
-
- @Test
- public void flavors() {
- project.buildFile << """
-model {
- android.buildTypes {
- create("b1")
- }
- android.productFlavors {
- create("f1")
- create("f2")
- }
-}
-"""
- // Ensure all combinations of assemble* tasks are created.
- List<String> tasks = project.getTaskList()
- assertThat(tasks).containsAllOf(
- "assemble",
- "assembleB1",
- "assembleDebug",
- "assembleF1",
- "assembleF1B1",
- "assembleF1Debug",
- "assembleF1Release",
- "assembleF2",
- "assembleF2B1",
- "assembleF2Debug",
- "assembleF2Release",
- "assembleRelease",
- "assembleAndroidTest",
- "assembleF1DebugAndroidTest",
- "assembleF2DebugAndroidTest");
-
- AndroidProject model = project.executeAndReturnModel("assemble");
- assertThat(model).isNotNull();
- assertThat(model.getName()).isEqualTo(project.name)
- assertThat(model.getBuildTypes()).hasSize(3)
- assertThat(model.getProductFlavors()).hasSize(2)
- assertThat(model.getVariants()).hasSize(6)
- }
-
- @Test
- @Category(DeviceTests.class)
- public void connnectedAndroidTest() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/BasicNdkComponentTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/BasicNdkComponentTest.groovy
deleted file mode 100644
index 3f74612..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/BasicNdkComponentTest.groovy
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.category.SmokeTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * Basic integration test for native plugin.
- */
- at Category(SmokeTests.class)
- at CompileStatic
-class BasicNdkComponentTest {
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp(useCppSource: true))
- .forExpermimentalPlugin(true)
- .withHeap("20148m")
- .create();
-
- @BeforeClass
- public static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- public void assemble() {
- project.execute("assemble")
- }
-
- @Test
- public void assembleRelease() {
- project.execute("assembleRelease")
-
- // Verify .so are built for all platform.
- File apk = project.getApk("release", "unsigned")
- assertThatZip(apk).contains("lib/x86/libhello-jni.so")
- assertThatZip(apk).contains("lib/mips/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
- }
-
- @Test
- public void assembleDebug() {
- project.execute("assembleDebug")
-
- // Verify .so are built for all platform.
- File apk = project.getApk("debug")
- assertThatZip(apk).contains("lib/x86/libhello-jni.so")
- assertThatZip(apk).contains("lib/x86/gdbserver")
- assertThatZip(apk).contains("lib/x86/gdb.setup")
- assertThatZip(apk).contains("lib/mips/libhello-jni.so")
- assertThatZip(apk).contains("lib/mips/gdbserver")
- assertThatZip(apk).contains("lib/mips/gdb.setup")
- assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi/gdbserver")
- assertThatZip(apk).contains("lib/armeabi/gdb.setup")
- assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi-v7a/gdbserver")
- assertThatZip(apk).contains("lib/armeabi-v7a/gdb.setup")
- }
-
- @Test
- @Category(DeviceTests.class)
- public void connnectedAndroidTest() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentDslTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentDslTest.groovy
deleted file mode 100644
index f58e310..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentDslTest.groovy
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import com.android.build.gradle.integration.common.truth.TruthHelper
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-/**
- * Test various options can be set without necessarily using it.
- */
- at CompileStatic
-public class ComponentDslTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp())
- .forExpermimentalPlugin(true)
- .create();
-
- @Before
- public void setUp() {
- project.file("proguard.txt").createNewFile()
- project.buildFile << """
-apply plugin: "com.android.model.application"
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- defaultConfig.with {
- minSdkVersion.apiLevel = 7
- }
- }
- android.ndk {
- moduleName = "hello-jni"
- }
- android.productFlavors {
- create("f1") {
- proguardFiles += file("proguard.txt")
- buildConfigFields.create {
- type = "String"
- name = "foo"
- value = "\\"bar\\""
- }
- }
- create("f2")
- }
-}
-
-dependencies {
- compile 'com.android.support:appcompat-v7:22.2.0'
-}
-"""
- }
-
- @Test
- public void assemble() {
- AndroidProject model = project.executeAndReturnModel("assemble");
- assertThat(model).isNotNull();
- assertThat(model.getName()).isEqualTo(project.name)
- assertThat(model.getBuildTypes()).hasSize(2)
- assertThat(model.getProductFlavors()).hasSize(2)
- assertThat(model.getVariants()).hasSize(4)
- assertThat(project.getApk("f1", "debug")).exists()
- }
-
- @Test
- @Category(DeviceTests.class)
- public void connnectedAndroidTest() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSourceSetTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSourceSetTest.groovy
deleted file mode 100644
index 0158f17..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSourceSetTest.groovy
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * Integration tests for different configuration of source sets.
- */
- at CompileStatic
-class ComponentSourceSetTest {
-
- public static AndroidTestApp app = new HelloWorldJniApp()
-
- static {
- TestSourceFile manifest = app.getFile("AndroidManifest.xml")
- app.removeFile(manifest)
- app.addFile(new TestSourceFile("src", manifest.name, manifest.content));
-
- // Remove the main hello-jni.c and place it in different directories for different flavors.
- // Note that *not* all variant can be built.
- TestSourceFile cSource = app.getFile("hello-jni.c");
- app.removeFile(cSource);
- app.addFile(
- new TestSourceFile("src/release/jni", cSource.name, cSource.content))
- app.addFile(
- new TestSourceFile("src/flavor1/jni/hello-jni.c", cSource.name, cSource.content))
- app.addFile(new TestSourceFile("src/flavor2Debug/jni/hello-jni.c", cSource.name,
- cSource.content))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(app)
- .forExpermimentalPlugin(true)
- .create();
-
- @BeforeClass
- public static void setUp() {
-
- project.buildFile << """
-apply plugin: "com.android.model.application"
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- }
- android.productFlavors {
- create("flavor1")
- create("flavor2")
- create("flavor3")
- }
- android.sources {
- main {
- manifest {
- source {
- srcDir 'src'
- }
- }
- jni {
- source {
- exclude "**/fail.c"
- }
- }
- }
- flavor3 {
- jni {
- source {
- srcDir 'src/flavor1/jni'
- }
- }
- }
- }
-}
-"""
- project.file("src/main/jni").mkdirs()
- project.file("src/main/jni/fail.c") << """
-Un-compilable file to test exclude source works.
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void defaultBuildTypeSourceDirectory() {
- project.execute("assembleFlavor2Release");
- def apk = project.getApk("flavor2", "release", "unsigned")
- assertThatZip(apk).contains("lib/x86/libhello-jni.so");
- }
-
- @Test
- void defaultProductFlavorSourceDirectory() {
- project.execute("assembleFlavor1Debug");
- def apk = project.getApk("flavor1", "debug")
- assertThatZip(apk).contains("lib/x86/libhello-jni.so");
- }
-
- @Test
- void defaultVariantSourceDirectory() {
- project.execute("assembleFlavor2Debug");
- def apk = project.getApk("flavor2", "debug")
- assertThatZip(apk).contains("lib/x86/libhello-jni.so");
- }
-
- @Test
- void nonDefaultSourceDirectory() {
- project.execute("assembleFlavor3Debug");
- def apk = project.getApk("flavor3", "debug")
- assertThatZip(apk).contains("lib/x86/libhello-jni.so");
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/LibraryComponentPluginTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/LibraryComponentPluginTest.groovy
deleted file mode 100644
index f34e196..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/LibraryComponentPluginTest.groovy
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.SmokeTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldLibraryApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
-
-/**
- * Basic integration test for LibraryComponentModelPlugin.
- */
- at Category(SmokeTests.class)
- at CompileStatic
-class LibraryComponentPluginTest {
- private static HelloWorldLibraryApp testApp = new HelloWorldLibraryApp()
-
- static {
- AndroidTestApp app = (AndroidTestApp) testApp.getSubproject(":app")
- app.addFile(new TestSourceFile("", "build.gradle",
-"""
-apply plugin: "com.android.model.application"
-
-configurations {
- compile
-}
-
-dependencies {
- compile project(":lib")
-}
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
-}
-"""))
-
- AndroidTestApp lib = (AndroidTestApp) testApp.getSubproject(":lib")
- lib.addFile(new TestSourceFile("", "build.gradle",
-"""
-apply plugin: "com.android.model.library"
-
-dependencies {
- /* Depend on annotations to trigger the creation of the ExtractAnnotations task */
- compile 'com.android.support:support-annotations:22.2.0'
-}
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
-}
-"""))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(testApp)
- .forExpermimentalPlugin(true)
- .create();
-
- @BeforeClass
- public static void assemble() {
- project.execute("assemble")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- testApp = null
- }
-
- @Test
- void "check build config file is included"() {
- File releaseAar = project.getSubproject("lib").getAar("release");
- assertThatAar(releaseAar).containsClass("com/example/helloworld/BuildConfig.class");
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentModelTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentModelTest.groovy
deleted file mode 100644
index c8d83c7..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentModelTest.groovy
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.SdkConstants
-import com.android.build.gradle.integration.common.category.SmokeTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.NativeLibrary
-import com.android.builder.model.Variant
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-/**
- * Test the return model of the NDK.
- */
- at Category(SmokeTests.class)
-class NdkComponentModelTest {
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp())
- .forExpermimentalPlugin(true)
- .create()
-
- @Before
- void setUp() {
- project.buildFile <<
-"""
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- CFlags += "-DTEST_C_FLAG"
- cppFlags += "-DTEST_CPP_FLAG"
- toolchain = "clang"
- }
-}
-"""
- }
-
- @Test
- void "check native libraries in model"() {
- checkModel(
- debug : [
- SdkConstants.ABI_ARMEABI,
- SdkConstants.ABI_ARMEABI_V7A,
- SdkConstants.ABI_ARM64_V8A,
- SdkConstants.ABI_INTEL_ATOM,
- SdkConstants.ABI_INTEL_ATOM64,
- SdkConstants.ABI_MIPS,
- SdkConstants.ABI_MIPS64
- ]);
- }
-
- @Test
- void "check native libraries with splits"() {
- project.buildFile <<
-"""
-model {
- android {
- splits.with {
- abi {
- enable true
- reset()
- include 'x86', 'armeabi-v7a', 'mips'
- }
- }
- }
-}
-"""
- checkModel(
- debug: [SdkConstants.ABI_ARMEABI_V7A, SdkConstants.ABI_INTEL_ATOM, SdkConstants.ABI_MIPS]);
- }
-
- @Test
- void "check native libraries with splits and universalApk"() {
- project.buildFile <<
- """
-model {
- android {
- splits.with {
- abi {
- enable true
- reset()
- include 'x86', 'armeabi-v7a', 'mips'
- universalApk true
- }
- }
- }
-}
-"""
- checkModel(
- debug : [
- SdkConstants.ABI_ARMEABI,
- SdkConstants.ABI_ARMEABI_V7A,
- SdkConstants.ABI_ARM64_V8A,
- SdkConstants.ABI_INTEL_ATOM,
- SdkConstants.ABI_INTEL_ATOM64,
- SdkConstants.ABI_MIPS,
- SdkConstants.ABI_MIPS64
- ]);
- }
-
- @Test
- void "check native libraries with abiFilters"() {
- project.buildFile <<
- """
-model {
- android.productFlavors {
- create("x86") {
- ndk.abiFilters += "x86"
- }
- create("arm") {
- ndk.abiFilters += "armeabi-v7a"
- }
- create("mips") {
- ndk.abiFilters +="mips"
- }
- }
-}
-"""
- checkModel(
- x86Debug : [SdkConstants.ABI_INTEL_ATOM],
- armDebug : [SdkConstants.ABI_ARMEABI_V7A],
- mipsDebug : [SdkConstants.ABI_MIPS]);
- }
-
- @Test
- void "check variant specific flags"() {
- project.buildFile <<
- """
-model {
- android.buildTypes {
- debug {
- ndk.CFlags += "-DTEST_FLAG_DEBUG"
- }
- release {
- ndk.CFlags += "-DTEST_FLAG_RELEASE"
- }
- }
- android.productFlavors {
- create("f1") {
- ndk.CFlags += "-DTEST_FLAG_F1"
- }
- create("f2") {
- ndk.CFlags += "-DTEST_FLAG_F2"
- }
- }
-}
-"""
- AndroidProject model = project.executeAndReturnModel("assembleDebug")
- NativeLibrary f1Debug = ModelHelper.getVariant(model.getVariants(), "f1Debug").getMainArtifact()
- .getNativeLibraries().first()
- assertThat(f1Debug.getCCompilerFlags()).contains("-DTEST_FLAG_DEBUG")
- assertThat(f1Debug.getCCompilerFlags()).contains("-DTEST_FLAG_F1")
- NativeLibrary f2Release = ModelHelper.getVariant(model.getVariants(), "f2Release").getMainArtifact()
- .getNativeLibraries().first()
- assertThat(f2Release.getCCompilerFlags()).contains("-DTEST_FLAG_RELEASE")
- assertThat(f2Release.getCCompilerFlags()).contains("-DTEST_FLAG_F2")
- }
-
- @Test
- void "check using add on string for compileSdkVersion"() {
- project.buildFile <<
-"""
-model {
- android {
- compileSdkVersion = "Google Inc.:Google APIs:$GradleTestProject.DEFAULT_COMPILE_SDK_VERSION"
- }
-}
-"""
- AndroidProject model = project.executeAndReturnModel("assembleDebug")
- NativeLibrary lib = ModelHelper.getVariant(model.getVariants(), "debug").getMainArtifact()
- .getNativeLibraries().first()
- for (String flag : lib.getCCompilerFlags()) {
- if (flag.contains("sysroot")) {
- assertThat(flag).contains("android-${GradleTestProject.DEFAULT_COMPILE_SDK_VERSION}")
- }
- }
- }
-
- /**
- * Verify resulting model is as expected.
- *
- * @param variantToolchains map of variant name to array of expected toolchains.
- */
- private void checkModel(Map variantToolchains) {
-
- AndroidProject model = project.executeAndReturnModel("assembleDebug")
-
- Collection<Variant> variants = model.getVariants()
- for (Map.Entry entry : variantToolchains) {
- Variant variant = ModelHelper.getVariant(variants, (String) entry.getKey())
- AndroidArtifact mainArtifact = variant.getMainArtifact()
-
- assertThat(mainArtifact.getNativeLibraries()).hasSize(((Collection)entry.getValue()).size())
- for (NativeLibrary nativeLibrary : mainArtifact.getNativeLibraries()) {
- assertThat(nativeLibrary.getName()).isEqualTo("hello-jni")
- assertThat(nativeLibrary.getCCompilerFlags()).contains("-DTEST_C_FLAG");
- assertThat(nativeLibrary.getCCompilerFlags()).contains("-gcc-toolchain"); // check clang specific flags
- assertThat(nativeLibrary.getCppCompilerFlags()).contains("-DTEST_CPP_FLAG");
- assertThat(nativeLibrary.getCppCompilerFlags()).contains("-gcc-toolchain"); // check clang specific flags
- assertThat(nativeLibrary.getCSystemIncludeDirs()).isEmpty();
- assertThat(nativeLibrary.getCppSystemIncludeDirs()).isNotEmpty();
- File solibSearchPath = nativeLibrary.getDebuggableLibraryFolders().first()
- assertThat(new File(solibSearchPath, "libhello-jni.so")).exists()
- }
-
- Collection<String> expectedToolchainNames = entry.getValue().collect { "clang-" + it }
- Collection<String> toolchainNames = model.getNativeToolchains().collect { it.getName() }
- assertThat(toolchainNames).containsAllIn(expectedToolchainNames)
- Collection<String> nativeLibToolchains = mainArtifact.getNativeLibraries().
- collect { it.getToolchainName() }
- assertThat(nativeLibToolchains).containsExactlyElementsIn(expectedToolchainNames)
- }
-
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentPluginTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentPluginTest.groovy
deleted file mode 100644
index 7cf288d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentPluginTest.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-
-/**
- * Tests for NdkComponentModelPlugin
- */
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-/**
- * Basic integration test for ndk component plugin.
- */
- at CompileStatic
-class NdkComponentPluginTest {
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp())
- .forExpermimentalPlugin(true)
- .create()
-
- @BeforeClass
- public static void setUp() {
- project.getBuildFile() << """
-import com.android.build.gradle.model.NdkComponentModelPlugin
-apply plugin: NdkComponentModelPlugin
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- }
- android.ndk {
- moduleName = "hello-jni"
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- public void assemble() {
- project.execute("assemble");
- assertThat(project.file("build/intermediates/binaries/debug/obj/x86/libhello-jni.so")).exists()
- assertThat(project.file("build/intermediates/binaries/debug/lib/x86/libhello-jni.so")).exists()
- assertThat(project.file("build/intermediates/binaries/release/lib/x86/libhello-jni.so")).exists()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentSplitTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentSplitTest.groovy
deleted file mode 100644
index bc3b282..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentSplitTest.groovy
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * Integration test of the native plugin with multiple variants.
- */
- at CompileStatic
-class NdkComponentSplitTest {
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp())
- .forExpermimentalPlugin(true)
- .create()
-
- @BeforeClass
- public static void setUp() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- project.getBuildFile() << """
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- generatePureSplits = true
-
- defaultConfig.with {
- minSdkVersion.with {
- apiLevel = 21
- }
- }
-
- splits.with {
- abi {
- enable true
- reset()
- include "x86", "armeabi-v7a", "mips"
- }
- }
- }
- android.ndk {
- moduleName = "hello-jni"
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- public void assembleDebug() {
- // Ensure compileDebugSource creates the shared object.
- project.execute("compileDebugSources");
- assertThat(project.file("build/intermediates/binaries/debug/lib/x86/libhello-jni.so"))
- .exists();
-
- project.execute("assembleDebug");
-
- // Verify .so are built for all platform.
- File apk = project.getApk("debug")
- assertThatZip(apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/mips/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/x86/libhello-jni.so")
-
- File armApk = project.getApk("debug_armeabi-v7a")
- assertThatZip(armApk).contains("lib/armeabi-v7a/libhello-jni.so")
- assertThatZip(armApk).doesNotContain("lib/mips/libhello-jni.so")
- assertThatZip(armApk).doesNotContain("lib/x86/libhello-jni.so")
-
- File mipsApk = project.getApk("debug_mips")
- assertThatZip(mipsApk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
- assertThatZip(mipsApk).contains("lib/mips/libhello-jni.so")
- assertThatZip(mipsApk).doesNotContain("lib/x86/libhello-jni.so")
-
- File x86Apk = project.getApk("debug_x86")
- assertThatZip(x86Apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
- assertThatZip(x86Apk).doesNotContain("lib/mips/libhello-jni.so")
- assertThatZip(x86Apk).contains("lib/x86/libhello-jni.so")
- }
-
- @Test
- @Category(DeviceTests.class)
- public void connectedAndroidTest() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentVariantTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentVariantTest.groovy
deleted file mode 100644
index 779d25c..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentVariantTest.groovy
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import com.android.builder.core.BuilderConstants
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static com.google.common.truth.Truth.assertThat
-
-/**
- * Integration test of the native plugin with multiple variants.
- */
- at CompileStatic
-class NdkComponentVariantTest {
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp())
- .forExpermimentalPlugin(true)
- .create()
-
- @BeforeClass
- public static void setUp() {
-
- project.getBuildFile() << """
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- }
- android.buildTypes {
- create("jniDebug") {
- ndk.debuggable = true;
- }
- }
- android.productFlavors {
- create("x86") {
- ndk.abiFilters += "x86"
- }
- create("arm") {
- ndk.abiFilters += "armeabi-v7a"
- ndk.abiFilters += "armeabi"
- }
- create("mips") {
- ndk.abiFilters += "mips"
- }
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- public void "check old ndk tasks are not created"() {
- List<String> tasks = project.getTaskList()
- assertThat(tasks).containsNoneOf(
- "compileArmDebugNdk",
- "compileX86DebugNdk",
- "compileMipsDebugNdk",
- "compileArmReleaseNdk",
- "compileX86ReleaseNdk",
- "compileMipsReleaseNdk")
- }
-
- @Test
- public void assembleX86Debug() {
- project.execute("assembleX86Debug")
-
- // Verify .so are built for all platform.
- File apk = project.getApk("x86", "debug")
- assertThatZip(apk).contains("lib/x86/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/mips/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/armeabi/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
- }
-
- @Test
- public void assembleArmDebug() {
- project.execute("assembleArmDebug")
-
- // Verify .so are built for all platform.
- File apk = project.getApk("arm", "debug")
- assertThatZip(apk).doesNotContain("lib/x86/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/mips/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
- }
-
- @Test
- public void assembleMipsDebug() {
- project.execute("assembleMipsDebug")
-
- // Verify .so are built for all platform.
- File apk = project.getApk("mips", "debug")
- assertThatZip(apk).doesNotContain("lib/x86/libhello-jni.so")
- assertThatZip(apk).contains("lib/mips/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/armeabi/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
- }
-
- @Test
- public void "check setting isDebuggable generates gdbserver and gdb.setup"() {
- project.execute("assembleArmJniDebug")
-
- File apk = project.getApk("arm", "jniDebug", "unsigned")
- assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi/gdbserver")
- assertThatZip(apk).contains("lib/armeabi/gdb.setup")
- assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
- assertThatZip(apk).contains("lib/armeabi-v7a/gdbserver")
- assertThatZip(apk).contains("lib/armeabi-v7a/gdb.setup")
- }
-
- @Test
- public void "check release build does not contain gdbserver and gdb.setup"() {
- project.execute("assembleArmRelease")
-
- File apk = project.getApk("arm", "release", "unsigned")
- assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/armeabi/gdbserver")
- assertThatZip(apk).doesNotContain("lib/armeabi/gdb.setup")
- assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
- assertThatZip(apk).doesNotContain("lib/armeabi-v7a/gdbserver")
- assertThatZip(apk).doesNotContain("lib/armeabi-v7a/gdb.setup")
- }
-
- @Test
- @Category(DeviceTests.class)
- public void connectedAndroidTest() {
- if (GradleTestProject.DEVICE_PROVIDER_NAME.equals(BuilderConstants.CONNECTED)) {
- project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "ArmDebugAndroidTest")
- } else {
- project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "X86DebugAndroidTest")
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkFlagsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkFlagsTest.groovy
deleted file mode 100644
index d21d8cd..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkFlagsTest.groovy
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * Tests C/C++/ld flags in an NDK project.
- */
- at CompileStatic
-class NdkFlagsTest {
-
- static AndroidTestApp cApp = new HelloWorldJniApp()
- static {
- TestSourceFile orig = cApp.getFile("hello-jni.c")
- cApp.removeFile(orig)
- cApp.addFile(new TestSourceFile(orig.path, orig.name,
- """
-#include <string.h>
-#include <jni.h>
-
-// This is a trivial JNI example where we use a native method
-// to return a new VM String.
-jstring
-Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
-{
- return (*env)->NewStringUTF(env, HELLO_WORLD EXCLAMATION_MARK);
-}
-"""
- ))
- }
-
-
- @ClassRule
- public static GradleTestProject cProject = GradleTestProject.builder()
- .withName("c_project")
- .fromTestApp(cApp)
- .forExpermimentalPlugin(true)
- .create();
-
- static AndroidTestApp cppApp = new HelloWorldJniApp(useCppSource: true)
- static {
- TestSourceFile orig = cppApp.getFile("hello-jni.cpp")
- cppApp.removeFile(orig)
- cppApp.addFile(new TestSourceFile(orig.path, orig.name,
- """
-#include <string.h>
-#include <jni.h>
-
-// This is a trivial JNI example where we use a native method
-// to return a new VM String.
-extern "C"
-jstring
-Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
-{
- // HELLO_WORLD and EXCLAMATION_MARK must be defined as follows during compilation.
- // #define HELLO_WORLD "hello world"
- // #define EXCLAMATION_MARK "!"
- return env->NewStringUTF(HELLO_WORLD EXCLAMATION_MARK);
-}
-"""
- ))
-
- }
-
- @ClassRule
- public static GradleTestProject cppProject = GradleTestProject.builder()
- .withName("cpp_project")
- .fromTestApp(cppApp)
- .forExpermimentalPlugin(true)
- .create();
-
- static AndroidTestApp ldApp = new HelloWorldJniApp()
- static {
- ldApp.addFile(new TestSourceFile("src/main/jni", "log.c",
- """
-#include <android/log.h>
-
-// Simple function that uses function from an external library. Should fail unless -llog is set
-// when linking.
-void log() {
- __android_log_print(ANDROID_LOG_INFO, "hello-world", "Hello World!");
-}
-"""))
- }
-
- @ClassRule
- public static GradleTestProject ldProject = GradleTestProject.builder()
- .withName("ld_project")
- .fromTestApp(ldApp)
- .forExpermimentalPlugin(true)
- .create();
-
-
- @BeforeClass
- public static void setUp() {
- cProject.getBuildFile() << """
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- CFlags += ['-DHELLO_WORLD="hello world"', '-DEXCLAMATION_MARK="!"']
- CFlags += ' -DFLAG_WITH_LEADING_SPACE'
- }
-}
-"""
-
- cppProject.getBuildFile() << """
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- cppFlags = ['-DHELLO_WORLD="hello world"', '-DEXCLAMATION_MARK="!"']
- }
-}
-"""
-
- ldProject.getBuildFile() << """
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- ldFlags += "-llog"
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- cProject = null
- }
-
- @Test
- public void "assemble C project"() {
- cProject.execute("assembleDebug")
- assertThatZip(cProject.getApk("debug")).contains("lib/x86/libhello-jni.so")
- }
-
- @Test
- public void "assemble C++ project"() {
- cppProject.execute("assembleDebug")
- assertThatZip(cppProject.getApk("debug")).contains("lib/x86/libhello-jni.so")
- }
-
- @Test
- public void "assemble ld project"() {
- ldProject.execute("assembleDebug")
- assertThatZip(ldProject.getApk("debug")).contains("lib/x86/libhello-jni.so")
- }
-
- @Test
- @Category(DeviceTests.class)
- public void "connectedCheck C project"() {
- cProject.executeConnectedCheck();
- }
-
- @Test
- @Category(DeviceTests.class)
- public void "connectedCheck C++ project"() {
- cppProject.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkJniLib2Test.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkJniLib2Test.groovy
deleted file mode 100644
index caeef48..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkJniLib2Test.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for ndkJniLib2.
- */
- at Ignore("Test is disabled until new dependency model is ready.")
- at CompileStatic
-class NdkJniLib2Test {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("ndkJniLib2")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug");
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkSanAngeles2Test.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkSanAngeles2Test.groovy
deleted file mode 100644
index feca3d1..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkSanAngeles2Test.groovy
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.SdkConstants
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.NativeLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-/**
- * Assemble tests for ndkSanAngeles2.
- */
- at CompileStatic
-class NdkSanAngeles2Test {
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .forExpermimentalPlugin(true)
- .fromTestProject("ndkSanAngeles2")
- .create()
-
- private static AndroidProject model;
-
- @BeforeClass
- static void setUp() {
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void "check model"() {
- Collection<Variant> variants = model.getVariants()
- assertThat(variants).hasSize(8)
-
- Variant debugVariant = ModelHelper.getVariant(variants, "x86Debug")
- AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
- assertThat(debugMainArtifact.getNativeLibraries()).hasSize(1)
-
- NativeLibrary nativeLibrary = debugMainArtifact.getNativeLibraries().first()
- assertThat(nativeLibrary.getName()).isEqualTo("sanangeles")
- assertThat(nativeLibrary.getToolchainName()).isEqualTo("clang-x86")
- assertThat(nativeLibrary.getCCompilerFlags()).contains("-DDISABLE_IMPORTGL");
- assertThat(nativeLibrary.getCSystemIncludeDirs()).isEmpty();
- assertThat(nativeLibrary.getCppSystemIncludeDirs()).isNotEmpty()
- File solibSearchPath = nativeLibrary.getDebuggableLibraryFolders().first()
- assertThat(new File(solibSearchPath, "libsanangeles.so")).exists()
-
- Collection<String> toolchainNames = model.getNativeToolchains().collect { it.getName() }
- Collection<String> expectedToolchains = [
- SdkConstants.ABI_INTEL_ATOM,
- SdkConstants.ABI_ARMEABI_V7A,
- SdkConstants.ABI_ARMEABI,
- SdkConstants.ABI_MIPS].collect { "clang-" + it }
- assertThat(toolchainNames).containsAllIn(expectedToolchains)
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStandaloneSoTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStandaloneSoTest.groovy
deleted file mode 100644
index ff8938a..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStandaloneSoTest.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for ndkStandaloneSo.
- */
- at Ignore("Test is disabled until new dependency model is ready.")
- at CompileStatic
-class NdkStandaloneSoTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("ndkStandaloneSo")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug");
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlTest.groovy
deleted file mode 100644
index dfded44..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlTest.groovy
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import groovy.transform.CompileStatic
-import org.gradle.tooling.BuildException
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-import java.util.zip.ZipFile
-
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.fail
-
-/**
- * Integration test for STL containers.
- *
- * This unit test is parameterized and will be executed for various values of STL.
- */
- at RunWith(Parameterized.class)
- at CompileStatic
-public class NdkStlTest {
-
- @Parameterized.Parameters
- public static Collection<Object[]> data() {
- return [
- ["system"].toArray(),
- ["stlport_static"].toArray(),
- ["stlport_shared"].toArray(),
- ["gnustl_static"].toArray(),
- ["gnustl_shared"].toArray(),
- ["gabi++_static"].toArray(),
- ["gabi++_shared"].toArray(),
- ["c++_static"].toArray(),
- ["c++_shared"].toArray(),
- ["invalid"].toArray(),
- ];
- }
-
- private String stl;
-
- NdkStlTest(String stl) {
- this.stl = stl;
- }
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp(useCppSource: true))
- .forExpermimentalPlugin(true)
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
- android.ndk {
- moduleName = "hello-jni"
- }
-}
-"""
- }
-
- @Test
- public void buildAppWithStl() {
- project.getBuildFile() << """
-model {
- android.ndk {
- stl = "$stl"
- }
-}
-"""
- if (!stl.equals("invalid")) {
- project.execute("assembleDebug");
-
- ZipFile apk = new ZipFile(project.getApk("debug"));
- assertNotNull(apk.getEntry("lib/x86/libhello-jni.so"));
- assertNotNull(apk.getEntry("lib/mips/libhello-jni.so"));
- assertNotNull(apk.getEntry("lib/armeabi/libhello-jni.so"));
- assertNotNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"));
- } else {
- // Fail if it's invalid.
- try {
- project.execute("assembleDebug");
- fail();
- } catch (BuildException ignored) {
- }
- }
- }
-}
-
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkVariantsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkVariantsTest.groovy
deleted file mode 100644
index fe48955..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkVariantsTest.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.component
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for ndkVariants.
- */
- at CompileStatic
-class NdkVariantsTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .forExpermimentalPlugin(true)
- .fromTestProject("ndkVariants")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug");
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- public void connnectedAndroidTest() {
- project.executeConnectedCheck();
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithClassifierDepTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithClassifierDepTest.groovy
deleted file mode 100644
index 5918640..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithClassifierDepTest.groovy
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.After
-import org.junit.AfterClass
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-
-/**
- * test for same dependency with and without classifier.
- */
- at CompileStatic
-class AppWithClassifierDepTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithClassifierDep")
- .create()
- AndroidProject model
-
- @Before
- void setUp() {
- model = project.getSingleModel()
- }
-
- @After
- void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check debug dependencies in model"() {
- Variant variant = ModelHelper.getVariant(model.getVariants(), "debug")
- Dependencies dependencies = variant.getMainArtifact().getDependencies()
-
- Collection<JavaLibrary> javaLibs = dependencies.getJavaLibraries()
- assertNotNull(javaLibs)
- assertEquals(1, javaLibs.size())
-
- JavaLibrary javaLib = javaLibs.iterator().next()
- assertEquals(
- new File(project.getTestDir(), "repo/com/foo/sample/1.0/sample-1.0.jar"),
- javaLib.getJarFile());
- }
-
- @Test
- void "check androidTest dependencies in model"() {
- Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug")
-
- AndroidArtifact androidTestArtifact = ModelHelper.getAndroidArtifact(
- debugVariant.getExtraAndroidArtifacts(), ARTIFACT_ANDROID_TEST)
-
- Dependencies dependencies = androidTestArtifact.getDependencies()
-
- Collection<JavaLibrary> javaLibs = dependencies.getJavaLibraries()
- assertNotNull(javaLibs)
- assertEquals(1, javaLibs.size())
-
- JavaLibrary javaLib = javaLibs.iterator().next()
- assertEquals(
- new File(project.getTestDir(), "repo/com/foo/sample/1.0/sample-1.0-testlib.jar"),
- javaLib.getJarFile());
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileDirectJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileDirectJarTest.groovy
deleted file mode 100644
index f555928..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileDirectJarTest.groovy
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-/**
- * test for compile jar in app
- */
- at CompileStatic
-class AppWithCompileDirectJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- compile project(':jar')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check compiled jar is packaged"() {
- assertThatApk(project.getSubproject('app').getApk("debug"))
- .containsClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check compiled jar is in the model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-
- @Test
- void "check package jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check package jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileIndirectJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileIndirectJarTest.groovy
deleted file mode 100644
index e3df1df..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileIndirectJarTest.groovy
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-/**
- * test for compile jar in app through an aar dependency
- */
- at CompileStatic
-class AppWithCompileIndirectJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- compile project(':library')
-}
-"""
-
- project.getSubproject('library').getBuildFile() << """
-
-dependencies {
- compile project(':jar')
-}
-"""
-
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check compiled jar is packaged"() {
- File apk = project.getSubproject('app').getApk("debug")
-
- assertThatApk(apk).containsClass("Lcom/example/android/multiproject/person/People;")
- assertThatApk(apk).containsClass("Lcom/example/android/multiproject/library/PersonView;")
- }
-
- @Test
- void "check compiled jar is in the model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-
- @Test
- void "check package jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check package jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLibTest.groovy
deleted file mode 100644
index 6fdb4c5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLibTest.groovy
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-/**
- * test for compile library in app
- */
- at CompileStatic
-class AppWithCompileLibTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- compile project(':library')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check compiled library is packaged"() {
- File apk = project.getSubproject('app').getApk("debug")
-
- assertThatApk(apk).containsClass("Lcom/example/android/multiproject/library/PersonView;")
- }
-
- @Test
- void "check compiled library is in the model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<AndroidLibrary> libraryDeps = deps.getLibraries()
-
- assertEquals("Check there is 1 dependency", 1, libraryDeps.size())
- }
-
- @Test
- void "check compiled library is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check compiled library is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarFromOlderIdeTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarFromOlderIdeTest.groovy
deleted file mode 100644
index 9ba8f11..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarFromOlderIdeTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.gradle.tooling.BuildException
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * test for package (apk) local aar in app
- */
- at CompileStatic
-class AppWithCompileLocalAarFromOlderIdeTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- compile files('libs/baseLib-1.0.aar')
-}
-"""
-
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test(expected=BuildException.class)
- void "check model failed to load"() {
- project.getSingleModelAsStudio1()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarTest.groovy
deleted file mode 100644
index f4d74de..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarTest.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * test for package (apk) local aar in app
- */
- at CompileStatic
-class AppWithCompileLocalAarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- compile files('libs/baseLib-1.0.aar')
-}
-"""
-
- model = project.getSingleModelIgnoringSyncIssues()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check model failed to load"() {
- SyncIssue issue = assertThat(model).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_NON_JAR_LOCAL_DEP)
- assertThat(new File(issue.getData()).getName()).is('baseLib-1.0.aar')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithIvyDependencyTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithIvyDependencyTest.groovy
deleted file mode 100644
index 7b892f4..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithIvyDependencyTest.groovy
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-/**
- * test for Ivy dependencies.
- */
- at CompileStatic
-class AppWithIvyDependencyTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithIvyDependency")
- .create()
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check compilation depending on Ivy Jar file"() {
- project.execute("assembleDebug")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithJarDependOnLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithJarDependOnLibTest.groovy
deleted file mode 100644
index 18cb14e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithJarDependOnLibTest.groovy
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * test for dependency on a jar with a dependency on a library
- */
- at CompileStatic
-class AppWithJarDependOnLibTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- compile project(':jar')
-}
-"""
-
- project.getSubproject('jar').getBuildFile() << """
-
-dependencies {
- compile project(':library')
-}
-"""
- models = project.getAllModelsIgnoringSyncIssues()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check model failed to load"() {
- assertThat(models.get(':app')).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_JAR_DEPEND_ON_AAR,
- 'projectWithModules:jar:jar:unspecified')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithNonExistentResolutionStrategyForAarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithNonExistentResolutionStrategyForAarTest.groovy
deleted file mode 100644
index 7ddca09..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithNonExistentResolutionStrategyForAarTest.groovy
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static org.junit.Assert.assertTrue
-/**
- * test for flavored dependency on a different package.
- */
- at CompileStatic
-class AppWithNonExistentResolutionStrategyForAarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
-
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-subprojects {
- apply from: "\$rootDir/../commonLocalRepo.gradle"
-}
-"""
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- debugCompile project(':library')
- releaseCompile project(':library')
-}
-
-configurations { _debugCompile }
-
-configurations._debugCompile {
- resolutionStrategy {
- eachDependency { DependencyResolveDetails details ->
- if (details.requested.name == 'jdeferred-android-aar') {
- details.useVersion '-1.-1.-1'
- }
- }
- }
-}
-
-"""
-
- project.getSubproject('library').getBuildFile() << """
-
-dependencies {
- compile 'org.jdeferred:jdeferred-android-aar:1.2.3'
-}
-"""
-
- models = project.getAllModelsIgnoringSyncIssues()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check we received a sync issue"() {
- SyncIssue issue = assertThat(models.get(":app")).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_UNRESOLVED_DEPENDENCY)
- assertTrue(issue.message.contains("org.jdeferred:jdeferred-android-aar:-1.-1.-1"));
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageDirectJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageDirectJarTest.groovy
deleted file mode 100644
index 231c958..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageDirectJarTest.groovy
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertTrue
-/**
- * test for package (apk) jar in app
- */
- at CompileStatic
-class AppWithPackageDirectJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- apk project(':jar')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check package jar is packaged"() {
- assertThatApk(project.getSubproject('app').getApk("debug"))
- .containsClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check packaged jar is not in the model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertTrue("Check there is no dependency", projectDeps.isEmpty())
- }
-
- @Test
- void "check package jar is not in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check package jar is not in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLibTest.groovy
deleted file mode 100644
index 89eff2e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLibTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * test for package library in app
- */
- at CompileStatic
-class AppWithPackageLibTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- apk project(':library')
-}
-"""
- models = project.getAllModelsIgnoringSyncIssues()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check model failed to load"() {
- assertThat(models.get(':app')).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_NON_JAR_PACKAGE_DEP,
- 'projectWithModules:library:aar:unspecified')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalAarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalAarTest.groovy
deleted file mode 100644
index c7a7d18..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalAarTest.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * test for package (apk) local aar in app
- */
- at CompileStatic
-class AppWithPackageLocalAarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- apk files('libs/baseLib-1.0.aar')
-}
-"""
-
- model = project.getSingleModelIgnoringSyncIssues()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check model failed to load"() {
- SyncIssue issue = assertThat(model).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_NON_JAR_LOCAL_DEP)
- assertThat(new File(issue.getData()).getName()).is('baseLib-1.0.aar')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalJarTest.groovy
deleted file mode 100644
index 3440ec0..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalJarTest.groovy
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertTrue
-/**
- * test for package (apk) local jar in app
- */
- at CompileStatic
-class AppWithPackageLocalJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- apk files('libs/util-1.0.jar')
-}
-"""
-
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check packaged local jar is packaged"() {
- assertThatApk(project.getApk("debug"))
- .containsClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check packaged local jar is not in the model"() {
- Variant variant = ModelHelper.getVariant(model.getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<JavaLibrary> javaLibs = deps.getJavaLibraries()
-
- assertTrue("Check there is no dependency", javaLibs.isEmpty())
- }
-
- @Test
- void "check packaged local jar is not in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check packaged local jar is not in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedAarAsJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedAarAsJarTest.groovy
deleted file mode 100644
index 18ada51..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedAarAsJarTest.groovy
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-
-/**
- * test for provided jar in library where the jar comes from a library project.
- */
- at CompileStatic
-class AppWithProvidedAarAsJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- provided project(path: ':library', configuration: 'fakeJar')
-}
-"""
-
- project.getSubproject('library').getBuildFile() << """
-configurations {
- fakeJar
-}
-
-task makeFakeJar(type: Jar) {
- from 'src/main/java'
-}
-
-artifacts {
- fakeJar makeFakeJar
-}
-"""
-
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check provided jar is not packaged"() {
- assertThatApk(project.getSubproject('app').getApk("debug"))
- .doesNotContainClass("Lcom/example/android/multiproject/library/PersonView;")
- }
-
- @Test
- void "check provided jar is in the main artifact dependency"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-
- @Test
- void "check provided jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check provided jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedDirectJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedDirectJarTest.groovy
deleted file mode 100644
index 725bc4b..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedDirectJarTest.groovy
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-/**
- * test for provided jar in app
- */
- at CompileStatic
-class AppWithProvidedDirectJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- provided project(':jar')
-}
-"""
-
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check provided jar is not packaged"() {
- assertThatApk(project.getSubproject('app').getApk("debug"))
- .doesNotContainClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check provided jar is in the main artifact dependency"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-
- @Test
- void "check provided jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check provided jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLibTest.groovy
deleted file mode 100644
index 14ac3c6..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLibTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * test for provided library in app
- */
- at CompileStatic
-class AppWithProvidedLibTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- provided project(':library')
-}
-"""
- models = project.getAllModelsIgnoringSyncIssues()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check model failed to load"() {
- assertThat(models.get(':app')).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_NON_JAR_PROVIDED_DEP,
- 'projectWithModules:library:aar:unspecified')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalAarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalAarTest.groovy
deleted file mode 100644
index ccf23b5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalAarTest.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * test for provided (apk) local aar in app
- */
- at CompileStatic
-class AppWithProvidedLocalAarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- provided files('libs/baseLib-1.0.aar')
-}
-"""
-
- model = project.getSingleModelIgnoringSyncIssues()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check model failed to load"() {
- SyncIssue issue = assertThat(model).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_NON_JAR_LOCAL_DEP)
- assertThat(new File(issue.getData()).getName()).is('baseLib-1.0.aar')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalJarTest.groovy
deleted file mode 100644
index 916cc8e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalJarTest.groovy
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-/**
- * test for provided local jar in app
- */
- at CompileStatic
-class AppWithProvidedLocalJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- provided files('libs/util-1.0.jar')
-}
-"""
-
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check provided local jar is not packaged"() {
- assertThatApk(project.getApk("debug"))
- .doesNotContainClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check provided local jar is in the main artifact dependency"() {
- Variant variant = ModelHelper.getVariant(model.getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<JavaLibrary> javaLibs = deps.getJavaLibraries()
-
- assertEquals("Check there is 1 dependency", 1, javaLibs.size())
- }
-
- @Test
- void "check provided local jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check provided local jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForAarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForAarTest.groovy
deleted file mode 100644
index 88b88f6..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForAarTest.groovy
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * test for flavored dependency on a different package.
- */
- at CompileStatic
-class AppWithResolutionStrategyForAarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-subprojects {
- apply from: "\$rootDir/../commonLocalRepo.gradle"
-}
-"""
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- debugCompile project(':library')
- releaseCompile project(':library')
-}
-
-configurations { _debugCompile }
-
-configurations._debugCompile {
- resolutionStrategy {
- eachDependency { DependencyResolveDetails details ->
- if (details.requested.name == 'jdeferred-android-aar') {
- details.useVersion '1.2.2'
- }
- }
- }
-}
-
-"""
-
- project.getSubproject('library').getBuildFile() << """
-
-dependencies {
- compile 'org.jdeferred:jdeferred-android-aar:1.2.3'
-}
-"""
-
- models = project.getAllModels()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check model contain correct dependencies"() {
- AndroidProject appProject = models.get(':app')
- Collection<Variant> appVariants = appProject.getVariants()
-
- checkJarDependency(appVariants, 'debug', 'org.jdeferred:jdeferred-android-aar:aar:1.2.2')
- checkJarDependency(appVariants, 'release', 'org.jdeferred:jdeferred-android-aar:aar:1.2.3')
- }
-
- private static void checkJarDependency(
- @NonNull Collection<Variant> appVariants,
- @NonNull String variantName,
- @NonNull String aarCoodinate) {
- Variant appVariant = ModelHelper.getVariant(appVariants, variantName)
-
- AndroidArtifact appArtifact = appVariant.getMainArtifact()
- Dependencies artifactDependencies = appArtifact.getDependencies()
-
- Collection<AndroidLibrary> directLibraries = artifactDependencies.getLibraries()
- Assert.assertEquals(variantName, 1, directLibraries.size())
- AndroidLibrary directLibrary = directLibraries.iterator().next()
- Assert.assertEquals(variantName, ':library', directLibrary.getProject())
-
- List<? extends AndroidLibrary> transitiveLibraries = directLibrary.getLibraryDependencies()
- Assert.assertEquals(variantName, 1, transitiveLibraries.size())
- AndroidLibrary transitiveLibrary = transitiveLibraries.get(0)
- Assert.assertEquals(variantName, aarCoodinate, transitiveLibrary.getResolvedCoordinates().toString())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForJarTest.groovy
deleted file mode 100644
index 1e77223..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForJarTest.groovy
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-
-import com.android.annotations.NonNull
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.SyncIssue
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-/**
- * test for flavored dependency on a different package.
- */
- at CompileStatic
-class AppWithResolutionStrategyForJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-subprojects {
- apply from: "\$rootDir/../commonLocalRepo.gradle"
-}
-"""
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- debugCompile project(':library')
- releaseCompile project(':library')
-}
-
-configurations { _debugCompile }
-
-configurations._debugCompile {
- resolutionStrategy {
- eachDependency { DependencyResolveDetails details ->
- if (details.requested.name == 'guava') {
- details.useVersion '15.0'
- }
- }
- }
-}
-
-"""
-
- project.getSubproject('library').getBuildFile() << """
-
-dependencies {
- compile 'com.google.guava:guava:17.0'
-}
-"""
-
- models = project.getAllModels()
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check model contain correct dependencies"() {
- AndroidProject appProject = models.get(':app')
- Collection<Variant> appVariants = appProject.getVariants()
-
- checkJarDependency(appVariants, 'debug', 'com.google.guava:guava:jar:15.0')
- checkJarDependency(appVariants, 'release', 'com.google.guava:guava:jar:17.0')
- }
-
- private static void checkJarDependency(
- @NonNull Collection<Variant> appVariants,
- @NonNull String variantName,
- @NonNull String jarCoodinate) {
- Variant appVariant = ModelHelper.getVariant(appVariants, variantName)
-
- AndroidArtifact appArtifact = appVariant.getMainArtifact()
- Dependencies artifactDependencies = appArtifact.getDependencies()
-
- Collection<JavaLibrary> javaLibraries = artifactDependencies.getJavaLibraries()
- Assert.assertEquals(1, javaLibraries.size())
- JavaLibrary javaLibrary = javaLibraries.iterator().next()
- Assert.assertEquals(jarCoodinate, javaLibrary.getResolvedCoordinates().toString())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/DepOnLocalJarThroughAModuleTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/DepOnLocalJarThroughAModuleTest.groovy
deleted file mode 100644
index 0939d66..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/DepOnLocalJarThroughAModuleTest.groovy
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.utils.ModelHelper.getAndroidArtifact
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-/**
- * test for a dependency on a local jar through a module wrapper
- */
- at CompileStatic
-class DepOnLocalJarThroughAModuleTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- compile project(':localJarAsModule')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check jar is packaged"() {
- assertThatApk(project.getSubproject('app').getApk("debug", "unaligned"))
- .containsClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check jar module is in the test artifact model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Dependencies deps = variant.mainArtifact.dependencies
- Collection<String> projects = deps.projects
-
- assertFalse(projects.isEmpty())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithPackageLocalJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithPackageLocalJarTest.groovy
deleted file mode 100644
index bb1a157..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithPackageLocalJarTest.groovy
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import java.util.zip.ZipFile
-
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * test for package (publish) local jar in libs
- */
- at CompileStatic
-class LibWithPackageLocalJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- publish files('libs/util-1.0.jar')
-}
-"""
-
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check packaged local jar is packaged"() {
- ZipFile aar = new ZipFile(project.getAar("debug"))
- assertNotNull(aar.getEntry("libs/util-1.0.jar"))
- }
-
- @Test
- void "check packaged local jar is not in the model"() {
- Variant variant = ModelHelper.getVariant(model.getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<JavaLibrary> javaLibs = deps.getJavaLibraries()
-
- assertTrue("Check there is no dependency", javaLibs.isEmpty())
- }
-
- @Test
- void "check packaged local jar is not in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check packaged local jar is not in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedAarAsJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedAarAsJarTest.groovy
deleted file mode 100644
index 22fbecf..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedAarAsJarTest.groovy
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-
-/**
- * test for provided jar in library where the jar comes from a library project.
- */
- at CompileStatic
-class LibWithProvidedAarAsJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('library').getBuildFile() << """
-
-dependencies {
- provided project(path: ':library2', configuration: 'fakeJar')
-}
-"""
-
- project.getSubproject('library2').getBuildFile() << """
-configurations {
- fakeJar
-}
-
-task makeFakeJar(type: Jar) {
- from 'src/main/java'
-}
-
-artifacts {
- fakeJar makeFakeJar
-}
-"""
-
- models = project.executeAndReturnMultiModel("clean", ":library:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check provided jar is not packaged"() {
- assertThatAar(project.getSubproject('library').getAar("debug"))
- .doesNotContainClass("com/example/android/multiproject/library2/PersionView2.class")
- }
-
- @Test
- void "check provided jar is in the main artifact dependency"() {
- Variant variant = ModelHelper.getVariant(models.get(':library').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-
- @Test
- void "check provided jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check provided jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedDirectJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedDirectJarTest.groovy
deleted file mode 100644
index b737f86..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedDirectJarTest.groovy
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.truth.TruthHelper
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static org.junit.Assert.assertEquals
-/**
- * test for provided jar in library
- */
- at CompileStatic
-class LibWithProvidedDirectJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('library').getBuildFile() << """
-
-dependencies {
- provided project(':jar')
-}
-"""
-
- models = project.executeAndReturnMultiModel("clean", ":library:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check provided jar is not packaged"() {
- assertThatAar(project.getSubproject('library').getAar("debug"))
- .doesNotContainClass("com/example/android/multiproject/person/People.class")
- }
-
- @Test
- void "check provided jar is in the main artifact dependency"() {
- Variant variant = ModelHelper.getVariant(models.get(':library').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-
- @Test
- void "check provided jar is not in the published dependencies"() {
- Variant variant = ModelHelper.getVariant(models.get(':library').getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-
- @Test
- void "check provided jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check provided jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedLocalJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedLocalJarTest.groovy
deleted file mode 100644
index 569d14c..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedLocalJarTest.groovy
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.JavaLibrary
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import java.util.zip.ZipFile
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNull
-/**
- * test for provided local jar in libs
- */
- at CompileStatic
-class LibWithProvidedLocalJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithLocalDeps")
- .create()
- static AndroidProject model
-
- @BeforeClass
- static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-dependencies {
- provided files('libs/util-1.0.jar')
-}
-"""
-
- model = project.executeAndReturnModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- void "check provided local jar is not packaged"() {
- ZipFile aar = new ZipFile(project.getAar("debug"))
- assertNull(aar.getEntry("libs/util-1.0.jar"))
- }
-
- @Test
- void "check provided local jar is in the main artifact dependency"() {
- Variant variant = ModelHelper.getVariant(model.getVariants(), "debug")
-
- Dependencies deps = variant.getMainArtifact().getDependencies()
- Collection<JavaLibrary> javaLibs = deps.getJavaLibraries()
-
- assertEquals("Check there is 1 dependency", 1, javaLibs.size())
- }
-
- @Test
- void "check provided local jar is in the android test dependency"() {
- // TODO
- }
-
- @Test
- void "check provided local jar is in the unit test dependency"() {
- // TODO
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LocalJarInAarInModelTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LocalJarInAarInModelTest.groovy
deleted file mode 100644
index efa71de..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LocalJarInAarInModelTest.groovy
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.truth.TruthHelper
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static org.junit.Assert.assertTrue
-/**
- * test for the path of the local jars in aars before and after exploding them.
- */
-class LocalJarInAarInModelTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- defaultConfig {
- minSdkVersion 4
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:22.1.1'
-}
-"""
- }
-
- @After
- void cleanUp() {
- project = null
- }
-
- @Test
- void checkModelBeforeBuild() {
- //clean the project and get the model. The aar won't be exploded for this sync event.
- AndroidProject model = project.executeAndReturnModel("clean")
-
- Variant variant = ModelHelper.getVariant(model.getVariants(), "debug")
- Dependencies dependencies = variant.getMainArtifact().getDependencies()
- Collection<AndroidLibrary> libraries = dependencies.getLibraries();
-
- TruthHelper.assertThat(libraries).hasSize(1);
-
- // now build the project.
- project.execute("prepareDebugDependencies")
-
- // now check the model validity
- AndroidLibrary lib = libraries.iterator().next()
-
- File jarFile = lib.getJarFile()
- assertTrue("File doesn't exist: " + jarFile, jarFile.exists());
- for (File localJar : lib.getLocalJars()) {
- assertTrue("File doesn't exist: " + localJar, localJar.exists());
- }
- }
-
- @Test
- void checkModelAfterBuild() {
- //build the project and get the model. The aar is exploded for this sync event.
- AndroidProject model = project.executeAndReturnModel("clean", "prepareDebugDependencies")
-
- Variant variant = ModelHelper.getVariant(model.getVariants(), "debug")
- Dependencies dependencies = variant.getMainArtifact().getDependencies()
- Collection<AndroidLibrary> libraries = dependencies.getLibraries();
-
- TruthHelper.assertThat(libraries).hasSize(1);
-
- // now check the model validity
- AndroidLibrary lib = libraries.iterator().next()
-
- File jarFile = lib.getJarFile()
- assertTrue("File doesn't exist: " + jarFile, jarFile.exists());
- for (File localJar : lib.getLocalJars()) {
- assertTrue("File doesn't exist: " + localJar, localJar.exists());
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/OptionalAarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/OptionalAarTest.groovy
deleted file mode 100644
index 7faf203..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/OptionalAarTest.groovy
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-/**
- * test for optional aar (using the provided scope)
- */
- at CompileStatic
-class OptionalAarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- compile project(':library')
-}
-"""
- project.getSubproject('library').getBuildFile() << """
-
-dependencies {
- provided project(':library2')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check app doesn't contain provided lib's layout"() {
- File apk = project.getSubproject('app').getApk("debug")
-
- assertThatApk(apk).doesNotContainResource("layout/lib2layout.xml")
- }
-
- @Test
- void "check app doesn't contain provided lib's code"() {
- File apk = project.getSubproject('app').getApk("debug")
-
- assertThatApk(apk).doesNotContainClass("Lcom/example/android/multiproject/library2/PersonView2;")
- }
-
- @Test
- void "check lib doesn't contain provided lib's layout"() {
- File aar = project.getSubproject('library').getAar("release")
-
- assertThatAar(aar).doesNotContainResource("layout/lib2layout.xml")
- assertThatAar(aar).textSymbolFile().contains("int layout liblayout")
- assertThatAar(aar).textSymbolFile().doesNotContain("int layout lib2layout")
- }
-
- @Test
- void "check app model doesn't include optional library"() {
- Collection<Variant> variants = models.get(":app").getVariants()
-
- // get the main artifact of the debug artifact and its dependencies
- Variant variant = ModelHelper.getVariant(variants, "debug")
- AndroidArtifact artifact = variant.getMainArtifact()
- Dependencies dependencies = artifact.getDependencies()
- Collection<AndroidLibrary> libs = dependencies.getLibraries();
-
- assertThat(libs).hasSize(1);
-
- AndroidLibrary library = libs.first()
- assertThat(library.getProject()).isEqualTo(":library")
- assertThat(library.isOptional()).isFalse()
- }
-
- @Test
- void "check library model includes optional library"() {
- Collection<Variant> variants = models.get(":library").getVariants()
-
- // get the main artifact of the debug artifact and its dependencies
- Variant variant = ModelHelper.getVariant(variants, "debug")
- AndroidArtifact artifact = variant.getMainArtifact()
- Dependencies dependencies = artifact.getDependencies()
- Collection<AndroidLibrary> libs = dependencies.getLibraries();
-
- assertThat(libs).hasSize(1);
-
- AndroidLibrary library = libs.first()
- assertThat(library.getProject()).isEqualTo(":library2")
- assertThat(library.isOptional()).isTrue()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestLibraryWithDep.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestLibraryWithDep.groovy
deleted file mode 100644
index c8d094f..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestLibraryWithDep.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-
-/**
- * Created by jedo on 1/16/15.
- */
- at CompileStatic
-class TestLibraryWithDep {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("libTestDep")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.executeAndReturnMultiModel("clean", "assembleDebugAndroidTest")
- }
-
- @Test
- void "check lib dependency jar is packaged"() {
- assertThatApk(project.getApk("debug", "androidTest", "unaligned"))
- .containsClass("Lcom/google/common/base/Splitter;")
- }
-}
-
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileDirectJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileDirectJarTest.groovy
deleted file mode 100644
index edc0ee5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileDirectJarTest.groovy
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.utils.ModelHelper.getAndroidArtifact
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-/**
- * test for compile jar in a test app
- */
- at CompileStatic
-class TestWithCompileDirectJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- androidTestCompile project(':jar')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebugAndroidTest")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check compiled jar is packaged"() {
- assertThatApk(project.getSubproject('app').getApk("debug", "androidTest", "unaligned"))
- .containsClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check compiled jar is in the test artifact model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Collection<AndroidArtifact> androidArtifacts = variant.getExtraAndroidArtifacts()
- AndroidArtifact testArtifact = getAndroidArtifact(androidArtifacts, ARTIFACT_ANDROID_TEST)
- assertNotNull(testArtifact)
-
- Dependencies deps = testArtifact.getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileLibTest.groovy
deleted file mode 100644
index 9a934a3..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileLibTest.groovy
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.utils.ModelHelper.getAndroidArtifact
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-/**
- * test for compile library in a test app
- */
- at CompileStatic
-class TestWithCompileLibTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-dependencies {
- androidTestCompile project(':library')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleDebugAndroidTest")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check compiled library is packaged"() {
- assertThatApk(project.getSubproject('app').getApk("debug", "androidTest", "unaligned"))
- .containsClass("Lcom/example/android/multiproject/library/PersonView;")
- }
-
- @Test
- void "check compiled library is in the test artifact model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "debug")
-
- Collection<AndroidArtifact> androidArtifacts = variant.getExtraAndroidArtifacts()
- AndroidArtifact testArtifact = getAndroidArtifact(androidArtifacts, ARTIFACT_ANDROID_TEST)
- assertNotNull(testArtifact)
-
- Dependencies deps = testArtifact.getDependencies()
- Collection<AndroidLibrary> libraryDeps = deps.getLibraries()
-
- assertEquals("Check there is 1 dependency", 1, libraryDeps.size())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithFlavorsWithCompileDirectJarTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithFlavorsWithCompileDirectJarTest.groovy
deleted file mode 100644
index 90b048d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithFlavorsWithCompileDirectJarTest.groovy
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-import static com.android.build.gradle.integration.common.utils.ModelHelper.getAndroidArtifact
-import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-/**
- * test for compile jar in a test app
- */
- at CompileStatic
-class TestWithFlavorsWithCompileDirectJarTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("projectWithModules")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- project.getSubproject('app').getBuildFile() << """
-
-android {
- productFlavors {
- pro { }
- free { }
- }
-}
-
-dependencies {
- androidTestCompile project(':jar')
-}
-"""
- models = project.executeAndReturnMultiModel("clean", ":app:assembleFreeDebugAndroidTest")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check compiled jar is packaged"() {
- assertThatApk(project.getSubproject('app').getApk("free", "debug", "androidTest", "unaligned"))
- .containsClass("Lcom/example/android/multiproject/person/People;")
- }
-
- @Test
- void "check compiled jar is in the test artifact model"() {
- Variant variant = ModelHelper.getVariant(models.get(':app').getVariants(), "freeDebug")
-
- Collection<AndroidArtifact> androidArtifacts = variant.getExtraAndroidArtifacts()
- AndroidArtifact testArtifact = getAndroidArtifact(androidArtifacts, ARTIFACT_ANDROID_TEST)
- assertNotNull(testArtifact)
-
- Dependencies deps = testArtifact.getDependencies()
- Collection<String> projectDeps = deps.getProjects()
-
- assertEquals("Check there is 1 dependency", 1, projectDeps.size())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithMismatchDep.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithMismatchDep.groovy
deleted file mode 100644
index c32bfe8..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithMismatchDep.groovy
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.SyncIssue
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertTrue
-import static org.junit.Assert.fail
-/**
- * Tests the handling of test dependencies.
- */
- at CompileStatic
-class TestWithMismatchDep {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("testDependency")
- .captureStdOut(true)
- .captureStdErr(true)
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-dependencies {
- androidTestCompile 'com.google.guava:guava:15.0'
-}
-"""
- }
-
- private final static String ERROR_MSG = 'Conflict with dependency \'com.google.guava:guava\'. Resolved versions for app (17.0) and test app (15.0) differ.'
-
- @Test
- public void "Test mismatch dependency error is in model"() {
- // Query the model to get the mismatch dep sync error.
- AndroidProject model = project.getSingleModelIgnoringSyncIssues()
-
- assertThat(model).hasSingleIssue(
- SyncIssue.SEVERITY_ERROR,
- SyncIssue.TYPE_MISMATCH_DEP,
- 'com.google.guava:guava',
- ERROR_MSG)
- }
-
- @Test
- public void "Test mismatch dependency breaks test build"() {
- // want to check the log, so can't use Junit's expected exception mechanism.
-
- try {
- project.execute("assembleAndroidTest")
- fail("build succeeded");
- } catch (Exception e) {
- Throwable t = e
- while (t.getCause() != null) {
- t = t.getCause()
- }
-
- // looks like we can't actually test the instance t against GradleException
- // due to it coming through the tooling API from a different class loader.
- assertEquals("org.gradle.api.GradleException", t.getClass().canonicalName)
- assertEquals("Dependency Error. See console for details.", t.getMessage())
- }
-
- // check there is a version of the error, after the task name:
- ByteArrayOutputStream stderr = project.stderr
- String log = stderr.toString()
-
- assertTrue("stderr contains error", log.contains(ERROR_MSG))
- }
-
- public void "Test mismatch dependency doesn't break debug build"() {
- project.execute("assembleDebug")
-
- // check there is a log output
- ByteArrayOutputStream out = project.stdout
- String log = out.toString()
-
- assertTrue(log.contains(ERROR_MSG))
-
- }
-
- @Test
- public void "Test mismatch depenency can run non-build task"() {
- // it's important to be able to run the dependencies task to
- // investigate dependency issues.
- project.execute("dependencies")
-
- // check there is a log output
- ByteArrayOutputStream out = project.stdout
- String log = out.toString()
-
- assertTrue("stdout contains warning", log.contains(ERROR_MSG))
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsApp.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsApp.groovy
deleted file mode 100644
index 435b370..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsApp.groovy
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-/**
- * Tests the handling of test dependency.
- */
- at CompileStatic
-class TestWithSameDepAsApp {
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("testDependency")
- .create()
-
- @BeforeClass
- public static void setUp() {
- project.getBuildFile() << """
-dependencies {
- androidTestCompile 'com.google.guava:guava:17.0'
-}
-"""
-
- project.execute("clean", "assembleDebugAndroidTest")
- }
-
- @AfterClass
- public static void cleanUp() {
- project = null
- }
-
- @Test
- public void "Test with same dep version than Tested does NOT embed dependency"() {
- assertThatApk(project.getApk("debug", "androidTest", "unaligned"))
- .doesNotContainClass("Lcom/google/common/io/Files;")
- }
-
- @Test
- @Category(DeviceTests.class)
- void "run tests on devices"() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsAppWithProguard.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsAppWithProguard.groovy
deleted file mode 100644
index eaea1ad..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsAppWithProguard.groovy
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Tests the handling of test dependency.
- */
- at CompileStatic
-class TestWithSameDepAsAppWithProguard {
-
- private static AndroidTestApp testApp = new HelloWorldApp()
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(testApp)
- .create()
-
- @BeforeClass
- public static void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- defaultConfig {
- minSdkVersion 21
- }
-
- buildTypes {
- debug {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt')
- }
- }
-}
-
-dependencies {
- compile 'com.android.tools:annotations:+'
- androidTestCompile 'com.android.tools:annotations:+'
-}
-"""
-
- }
-
- @AfterClass
- public static void cleanUp() {
- project = null
- }
-
- @Test
- public void "Test proguard on test variant succeeds"() {
- project.execute("clean", "assembleDebugAndroidTest")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/VariantDependencyTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/VariantDependencyTest.groovy
deleted file mode 100644
index 36b2e20..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/VariantDependencyTest.groovy
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dependencies
-
-import com.android.annotations.NonNull
-import com.android.annotations.Nullable
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.build.gradle.integration.common.utils.ZipHelper
-import com.android.builder.core.ApkInfoParser
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.MavenCoordinates
-import com.android.builder.model.Variant
-import com.android.ide.common.process.DefaultProcessExecutor
-import com.android.ide.common.process.ProcessExecutor
-import com.android.utils.StdLogger
-import com.google.common.collect.Sets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-
- at CompileStatic
-class VariantDependencyTest {
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- private static AndroidProject model
- private static ApkInfoParser apkInfoParser
-
- @BeforeClass
- public static void setUp() {
- project.getBuildFile() << """
- apply plugin: 'com.android.application'
-
- configurations {
- freeLollipopDebugCompile
- paidIcsCompile
- }
-
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- flavorDimensions 'model', 'api'
- productFlavors {
- Lollipop {
- flavorDimension 'api'
- minSdkVersion 21
- }
- ics {
- flavorDimension 'api'
- minSdkVersion 15
- }
- free {
- flavorDimension 'model'
- }
- paid {
- flavorDimension 'model'
- }
- }
- }
-
- dependencies {
- freeLollipopDebugCompile 'com.android.support:leanback-v17:21.0.0'
- paidIcsCompile 'com.android.support:appcompat-v7:21.0.0'
- }
- """.stripIndent()
-
- project.execute('clean', 'assemble')
- model = project.getSingleModel()
-
- File aapt = new File(project.getSdkDir(), "build-tools/20.0.0/aapt")
- assertTrue("Test requires build-tools 20.0.0", aapt.isFile())
- ProcessExecutor processExecutor = new DefaultProcessExecutor(
- new StdLogger(StdLogger.Level.ERROR))
- apkInfoParser = new ApkInfoParser(aapt, processExecutor)
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- model = null
- apkInfoParser = null
- }
-
- @Test
- public void buildVariantSpecificDependency() {
- // check that the dependency was added by looking for a res file coming from the
- // dependency.
- checkApkForContent('freeLollipopDebug', 'res/drawable/lb_background.xml')
- }
-
- @Test
- public void buildMultiFlavorDependency() {
- // check that the dependency was added by looking for a res file coming from the
- // dependency.
- checkApkForContent('paidIcsDebug', 'res/anim/abc_fade_in.xml')
- checkApkForContent('paidIcsRelease', 'res/anim/abc_fade_in.xml')
- }
-
- @Test
- public void buildDefaultDependency() {
- // make sure that the other variants do not include any file from the variant-specific
- // and multi-flavor dependencies.
- Set<String> paths = Sets.newHashSet(
- 'res/anim/abc_fade_in.xml',
- 'res/drawable/lb_background.xml')
-
- checkApkForMissingContent('paidLollipopDebug', paths)
- checkApkForMissingContent('paidLollipopRelease', paths)
- checkApkForMissingContent('freeLollipopRelease', paths)
- checkApkForMissingContent('freeIcsDebug', paths)
- checkApkForMissingContent('freeIcsRelease', paths)
- }
-
- @Test
- public void modelVariantCount() {
- Collection<Variant> variants = model.getVariants()
- assertEquals("Variant Count", 8 , variants.size())
- }
-
- @Test
- public void modelVariantSpecificDependency() {
- Collection<Variant> variants = model.getVariants()
- String variantName = 'freeLollipopDebug'
- checkVariant(variants, variantName, 'com.android.support:leanback-v17:21.0.0')
- }
-
- @Test
- public void modelMultiFlavorDependency() {
- Collection<Variant> variants = model.getVariants()
-
- String variantName = 'paidIcsDebug'
- checkVariant(variants, variantName, 'com.android.support:appcompat-v7:21.0.0')
-
- variantName = 'paidIcsRelease'
- checkVariant(variants, variantName, 'com.android.support:appcompat-v7:21.0.0')
- }
-
- @Test
- public void modelDefaultDependency() {
- Collection<Variant> variants = model.getVariants()
-
- String variantName = 'paidLollipopDebug'
- checkVariant(variants, variantName, null)
-
- variantName = 'paidLollipopRelease'
- checkVariant(variants, variantName, null)
-
- variantName = 'freeLollipopRelease'
- checkVariant(variants, variantName, null)
-
- variantName = 'freeIcsDebug'
- checkVariant(variants, variantName, null)
-
- variantName = 'freeIcsRelease'
- checkVariant(variants, variantName, null)
- }
-
- private static void checkVariant(
- @NonNull Collection<Variant> variants,
- @NonNull String variantName,
- @Nullable String dependencyName) {
- Variant variant = ModelHelper.findVariantByName(variants, variantName)
- assertNotNull("${variantName} variant null-check", variant)
-
- AndroidArtifact artifact = variant.getMainArtifact()
- assertNotNull("${variantName} main artifact null-check", artifact)
-
- Dependencies dependencies = artifact.getDependencies()
- assertNotNull("${variantName} dependencies null-check", artifact)
-
- if (dependencyName != null) {
- assertFalse("${variantName} aar deps empty",
- dependencies.libraries.isEmpty())
-
- AndroidLibrary library = dependencies.libraries.iterator().next()
- assertNotNull("${variantName} first aar lib null-check", library)
-
- MavenCoordinates coordinates = library.resolvedCoordinates
- assertNotNull("${variantName} first aar lib coordinate null-check", coordinates)
- assertEquals("${variantName} first aar lib name check",
- dependencyName,
- "${coordinates.groupId}:${coordinates.artifactId}:${coordinates.version}".toString())
- } else {
- assertTrue("${variantName} aar deps empty",
- dependencies.libraries.isEmpty())
- }
- }
-
- private static void checkApkForContent(
- @NonNull String variantName,
- @NonNull String checkFilePath) {
- // use the model to get the output APK!
- File apk = ModelHelper.findOutputFileByVariantName(model.getVariants(), variantName)
-
- assertTrue("${variantName} output check", apk.isFile())
-
- assertThatZip(apk).contains(checkFilePath)
- }
-
- private static void checkApkForMissingContent(
- @NonNull String variantName,
- @NonNull Set<String> checkFilePath) {
- // use the model to get the output APK!
- File apk = ModelHelper.findOutputFileByVariantName(model.getVariants(), variantName)
-
- assertTrue("${variantName} output check", apk.isFile())
-
- ZipHelper.checkFileDoesNotExist(apk, checkFilePath)
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/DslTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/DslTest.groovy
deleted file mode 100644
index 24f84bc..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/DslTest.groovy
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dsl
-import com.android.build.gradle.integration.application.BuildConfigTest
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import groovy.util.slurpersupport.GPathResult
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertNotNull
-/**
- * General DSL tests
- */
-class DslTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- public void setUp() {
- new HelloWorldApp().write(project.testDir, null)
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-"""
- }
-
- @Test
- public void versionNameSuffix() {
- project.getBuildFile() << """
-android {
- defaultConfig {
- versionName 'foo'
- }
-
- buildTypes {
- debug {
- versionNameSuffix '-suffix'
- }
- }
-}
-"""
- // no need to do a full build. Let's just run the manifest task.
- project.execute("processDebugManifest")
-
- File manifestFile = project.file(
- "build/intermediates/manifests/full/debug/AndroidManifest.xml")
-
- GPathResult xml = new XmlSlurper().parse(manifestFile).declareNamespace(
- android: 'http://schemas.android.com/apk/res/android')
-
- String versionName = xml.'@android:versionName'.text()
-
- assertNotNull(versionName)
- assertEquals("foo-suffix", versionName)
- }
-
-
-
- @Test
- public void extraPropTest() {
- project.getBuildFile() << """
-android {
- buildTypes {
- debug {
- ext.foo = "bar"
- }
- }
-
- applicationVariants.all { variant ->
- if (variant.buildType.name == "debug") {
- def foo = variant.buildType.foo
- if (!foo.equals("bar")) {
- throw new RuntimeException("direct access to dynamic property failed, got " + foo)
- }
- def hasProperty = variant.buildType.hasProperty("foo")
- if (!hasProperty) {
- throw new RuntimeException("hasProperty not returning property value, got " + hasProperty)
- }
- }
- }
-}
-"""
- // no need to do a full build. Let's just run the tasks.
- project.execute("tasks")
-
- }
-
- @Test
- public void buildConfigEncoding() {
- project.getBuildFile() << """
-android {
- defaultConfig {
- buildConfigField 'String', 'test2', '"\\u0105"'
- }
-}
-"""
-
- project.execute("generateDebugBuildConfig")
-
- String expected =
-"""/**
- * Automatically generated file. DO NOT MODIFY
- */
-package com.example.helloworld;
-
-public final class BuildConfig {
- public static final boolean DEBUG = Boolean.parseBoolean("true");
- public static final String APPLICATION_ID = "com.example.helloworld";
- public static final String BUILD_TYPE = "debug";
- public static final String FLAVOR = "";
- public static final int VERSION_CODE = 1;
- public static final String VERSION_NAME = "1.0";
- // Fields from default config.
- public static final String test2 = "ą";
-}
-"""
- BuildConfigTest.checkBuildConfig(project, expected, 'debug')
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/TestedVariantTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/TestedVariantTest.groovy
deleted file mode 100644
index 25e0250..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/TestedVariantTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.dsl
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * Test that the test variant returns what it should.
- */
- at CompileStatic
-class TestedVariantTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-
-android.testVariants.all {
- assert it.testedVariant
-}
-"""
- }
-
- @Test
- public void testEvaluation() {
- // no need to do a full build, we just want evaluation.
- project.execute(":tasks")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/BasicTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/BasicTest.groovy
deleted file mode 100644
index ae60782..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/BasicTest.groovy
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.googleservices
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.google.common.base.Joiner
-import com.google.common.io.Files
-import com.google.common.truth.Truth
-import groovy.json.internal.Charsets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-/**
- * Basic test with gcm + ga
- */
- at CompileStatic
-class BasicTest {
-
- public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.free")
-
- private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "basic")
- static {
- File source = new File(resDataFolder, "example.json")
- helloWorldApp.addFile(new TestSourceFile(
- "",
- TestHelper.JSON_FILE_NAME,
- Files.toString(source, Charsets.UTF_8)))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(helloWorldApp)
- .create()
-
- public static AndroidProject model
- private static File generatedResFolder
-
- @BeforeClass
- public static void setUp() {
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.gms.google-services'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
-}
-"""
- model = project.executeAndReturnModel("clean", "assembleDebug")
-
- generatedResFolder = new File(project.getTestDir(),
- Joiner.on(File.separator).join("build", "generated", "res", "google-services", "debug"))
- }
-
- @AfterClass
- public static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- public void "test values res file is generated"() {
- File valuesFolder = new File(generatedResFolder, "values")
- File values = new File(valuesFolder, "values.xml")
- Assert.assertTrue(values.isFile())
-
- File goldenFile = new File(resDataFolder, "values.xml")
- Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
- .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
- }
-
- @Test
- public void "test ga res file is generated"() {
- File xmlFolder = new File(generatedResFolder, "xml")
- File global_tracker = new File(xmlFolder, "global_tracker.xml")
- Assert.assertTrue(global_tracker.isFile())
-
- File goldenFile = new File(resDataFolder, "global_tracker.xml")
- Truth.assert_().that(Files.toString(global_tracker, Charsets.UTF_8).trim())
- .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
- }
-
- @Test
- public void "test generated res folder is in model"() {
- Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug");
- Collection<File> generatedResFolders = debugVariant.getMainArtifact().getGeneratedResourceFolders()
-
- Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/DisabledServiceTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/DisabledServiceTest.groovy
deleted file mode 100644
index 055690e..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/DisabledServiceTest.groovy
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.googleservices
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.google.common.base.Joiner
-import com.google.common.io.Files
-import com.google.common.truth.Truth
-import groovy.json.internal.Charsets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-/**
- * Test for mostly empty json file.
- */
- at CompileStatic
-class DisabledServiceTest {
-
- public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.free")
-
- private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "disabledservice")
- static {
- File source = new File(resDataFolder, "example.json")
- helloWorldApp.addFile(new TestSourceFile(
- "",
- TestHelper.JSON_FILE_NAME,
- Files.toString(source, Charsets.UTF_8)))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(helloWorldApp)
- .create()
-
- public static AndroidProject model
- private static File generatedResFolder
-
- @BeforeClass
- public static void setUp() {
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.gms.google-services'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
-}
-"""
- model = project.executeAndReturnModel("clean", "assembleDebug")
- generatedResFolder = new File(project.getTestDir(),
- Joiner.on(File.separator).join("build", "generated", "res", "google-services", "debug"))
- }
-
- @AfterClass
- public static void cleanUp() {
- project = null
-
- }
- @Test
- public void "test values res file is generated"() {
- File valuesFolder = new File(generatedResFolder, "values")
- File values = new File(valuesFolder, "values.xml")
- Assert.assertTrue(values.isFile())
-
- File goldenFile = new File(resDataFolder, "values.xml")
- Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
- .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
- }
-
- @Test
- public void "test generated res folder is in model"() {
- Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug");
- Collection<File> generatedResFolders = debugVariant.getMainArtifact().getGeneratedResourceFolders()
-
- Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/FlavorTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/FlavorTest.groovy
deleted file mode 100644
index 4e727b0..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/FlavorTest.groovy
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.googleservices
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.google.common.base.Joiner
-import com.google.common.io.Files
-import com.google.common.truth.Truth
-import groovy.json.internal.Charsets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-/**
- * Basic test with gcm + ga
- */
- at CompileStatic
-class FlavorTest {
-
- public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app")
-
- private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "flavor")
- static {
- File source = new File(resDataFolder, "example.json")
- helloWorldApp.addFile(new TestSourceFile(
- "",
- TestHelper.JSON_FILE_NAME,
- Files.toString(source, Charsets.UTF_8)))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(helloWorldApp)
- .create()
-
- public static AndroidProject model
- private static File generatedFreeResFolder
- private static File generatedPaidResFolder
-
- @BeforeClass
- public static void setUp() {
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.gms.google-services'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- productFlavors {
- free {
- applicationId 'com.example.app.free'
- }
- paid {
- applicationId 'com.example.app.paid'
- }
- }
-
-}
-"""
- model = project.executeAndReturnModel("clean", "assembleDebug")
-
- generatedFreeResFolder = new File(project.getTestDir(),
- Joiner.on(File.separator).join("build", "generated", "res", "google-services", "free", "debug"))
- generatedPaidResFolder = new File(project.getTestDir(),
- Joiner.on(File.separator).join("build", "generated", "res", "google-services", "paid", "debug"))
- }
-
- @AfterClass
- public static void cleanUp() {
- project = null
- model = null
- }
-
- @Test
- public void "test values res file is generated"() {
- checkResValuesFile(generatedFreeResFolder, "free.values.xml")
- checkResValuesFile(generatedPaidResFolder, "paid.values.xml")
- }
-
- private static void checkResValuesFile(File generatedResFolder, String goldenFileName) {
- File valuesFolder = new File(generatedResFolder, "values")
- File values = new File(valuesFolder, "values.xml")
- Assert.assertTrue(values.isFile())
-
- File goldenFile = new File(resDataFolder, goldenFileName)
- Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
- .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
- }
-
- @Test
- public void "test ga res file is generated"() {
- checkGlobalTracker(generatedFreeResFolder, "free.global_tracker.xml")
- checkGlobalTracker(generatedPaidResFolder, "paid.global_tracker.xml")
- }
-
- private static void checkGlobalTracker(File generatedResFolder, String goldenFileName) {
- File xmlFolder = new File(generatedResFolder, "xml")
- File global_tracker = new File(xmlFolder, "global_tracker.xml")
- Assert.assertTrue(global_tracker.isFile())
-
- File goldenFile = new File(resDataFolder, goldenFileName)
- Truth.assert_().that(Files.toString(global_tracker, Charsets.UTF_8).trim())
- .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
- }
-
- @Test
- public void "test generated res folder is in model"() {
- checkModel("freeDebug", generatedFreeResFolder)
- checkModel("paidDebug", generatedPaidResFolder)
- }
-
- private static void checkModel(String variantName, File generatedResFolder) {
- Variant freeDebugVariant = ModelHelper.getVariant(model.getVariants(), variantName);
- Collection<File> generatedResFolders = freeDebugVariant.getMainArtifact().getGeneratedResourceFolders()
-
- Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoClientTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoClientTest.groovy
deleted file mode 100644
index 7419b44..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoClientTest.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.googleservices
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.google.common.io.Files
-import com.google.common.truth.Truth
-import groovy.json.internal.Charsets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-/**
- * Test with a mismatch json file vs the app package name.
- */
- at CompileStatic
-class NoClientTest {
-
- public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.typo")
-
- static {
- File source = new File(new File(GradleTestProject.TEST_RES_DIR, "basic"), "example.json")
- helloWorldApp.addFile(new TestSourceFile(
- "",
- TestHelper.JSON_FILE_NAME,
- Files.toString(source, Charsets.UTF_8)))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(helloWorldApp)
- .captureStdOut(true)
- .create()
-
- @BeforeClass
- public static void setUp() {
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.gms.google-services'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
-}
-"""
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- public static void cleanUp() {
- project = null
- }
-
- @Test
- public void "test warning is output"() {
- ByteArrayOutputStream stream = project.getStdout()
-
- Truth.assert_().that(stream.toString("UTF-8")).contains(
- "No matching client found for package name 'com.example.app.typo'")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoServiceTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoServiceTest.groovy
deleted file mode 100644
index 5772f19..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoServiceTest.groovy
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.googleservices
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Variant
-import com.google.common.base.Joiner
-import com.google.common.io.Files
-import com.google.common.truth.Truth
-import groovy.json.internal.Charsets
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Test for mostly empty json file.
- */
- at CompileStatic
-class NoServiceTest {
-
- public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.free")
-
- private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "noservice")
- static {
- File source = new File(resDataFolder, "no_services.json")
- helloWorldApp.addFile(new TestSourceFile(
- "",
- TestHelper.JSON_FILE_NAME,
- Files.toString(source, Charsets.UTF_8)))
- }
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(helloWorldApp)
- .create()
-
- public static AndroidProject model
- private static File generatedResFolder
-
- @BeforeClass
- public static void setUp() {
-
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.gms.google-services'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
-}
-"""
- model = project.executeAndReturnModel("clean", "assembleDebug")
- generatedResFolder = new File(project.getTestDir(),
- Joiner.on(File.separator).join("build", "generated", "res", "google-services", "debug"))
- }
-
- @AfterClass
- public static void cleanUp() {
- project = null
-
- }
- @Test
- public void "test values res file is generated"() {
- File valuesFolder = new File(generatedResFolder, "values")
- File values = new File(valuesFolder, "values.xml")
- Assert.assertTrue(values.isFile())
-
- File goldenFile = new File(resDataFolder, "values.xml")
- Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
- .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
- }
-
- @Test
- public void "test generated res folder is in model"() {
- Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug");
- Collection<File> generatedResFolders = debugVariant.getMainArtifact().getGeneratedResourceFolders()
-
- Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AidlTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AidlTest.groovy
deleted file mode 100644
index 9ccd2d6..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AidlTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Assemble tests for aidl.
- */
- at CompileStatic
-class AidlTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("aidl")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApiTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApiTest.groovy
deleted file mode 100644
index fc61bb2..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApiTest.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.utils.FileUtils
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-/**
- * Assemble tests for api.
- */
- at CompileStatic
-class ApiTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("api")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-
- @Test
- public void backwardsCompatible() throws Exception {
- // ATTENTION Author and Reviewers - please make sure required changes to the build file
- // are backwards compatible before updating this test.
- assertThat(FileUtils.sha1(project.file("app/build.gradle")))
- .isEqualTo("e20b70879b449c222ce9c0f9f17cb808d6899b06")
- assertThat(FileUtils.sha1(project.file("lib/build.gradle")))
- .isEqualTo("fbdd1f6d0d0e190412db885a1c281efee4ab0cac")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AssetsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AssetsTest.groovy
deleted file mode 100644
index fae6db3..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AssetsTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for assets.
- */
- at CompileStatic
-class AssetsTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("assets")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/DslTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/DslTest.groovy
deleted file mode 100644
index 5e9802c..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/DslTest.groovy
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import groovy.transform.CompileStatic
-import org.gradle.tooling.BuildException
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.fail
-
- at CompileStatic
-class DslTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .create()
-
- @Before
- public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-}
-"""
- }
-
- @Test
- public void applicationIdInDefaultConfig() {
- project.getBuildFile() << """
-android {
- defaultConfig {
- applicationId = 'foo'
- }
-}
-"""
- // Just need to run the 'tasks' task to trigger error
- try {
- project.execute("tasks")
- fail('Broken build file did not throw exception')
- } catch (BuildException e) {
- Throwable cause = e
- while (cause.getCause() != null) {
- cause = cause.getCause()
- }
- String expectedMsg = "Library projects cannot set applicationId. applicationId is set to 'foo' in default config."
- assertEquals(expectedMsg, cause.getMessage())
- }
- }
-
- @Test
- public void applicationIdSuffix() {
- project.getBuildFile() << """
-android {
- buildTypes {
- debug {
- applicationIdSuffix = 'foo'
- }
- }
-}
-"""
- // Just need to run the 'tasks' task to trigger error
- try {
- project.execute("tasks")
- fail('Broken build file did not throw exception')
- } catch (BuildException e) {
- Throwable cause = e
- while (cause.getCause() != null) {
- cause = cause.getCause()
- }
- String expectedMsg = "Library projects cannot set applicationId. applicationIdSuffix is set to 'foo' in build type 'debug'."
- assertEquals(expectedMsg, cause.getMessage())
- }
- }
-
- @Test
- public void applicationIdInProductFlavor() {
- project.getBuildFile() << """
-android {
- productFlavors {
- myFlavor {
- applicationId = 'foo'
- }
- }
-}
-"""
- // Just need to run the 'tasks' task to trigger error
- try {
- project.execute("tasks")
- fail('Broken build file did not throw exception')
- } catch (BuildException e) {
- Throwable cause = e
- while (cause.getCause() != null) {
- cause = cause.getCause()
- }
- String expectedMsg = "Library projects cannot set applicationId. applicationId is set to 'foo' in flavor 'myFlavor'."
- assertEquals(expectedMsg, cause.getMessage())
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavoredlibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavoredlibTest.groovy
deleted file mode 100644
index 55a81f5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavoredlibTest.groovy
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.ProductFlavorContainer
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-/**
- * Assemble tests for flavoredlib.
- */
- at CompileStatic
-class FlavoredlibTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("flavoredlib")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- models = project.executeAndReturnMultiModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void testModel() {
- AndroidProject appModel = models.get(":app")
- assertNotNull("Module app null-check", appModel)
-
- assertFalse("Library Project", appModel.isLibrary())
-
- Collection<Variant> variants = appModel.getVariants()
- Collection<ProductFlavorContainer> productFlavors = appModel.getProductFlavors()
-
- ProductFlavorContainer flavor1 = ModelHelper.getProductFlavor(productFlavors, "flavor1")
- assertNotNull(flavor1)
-
- Variant flavor1Debug = ModelHelper.getVariant(variants, "flavor1Debug")
- assertNotNull(flavor1Debug)
-
- Dependencies dependencies = flavor1Debug.getMainArtifact().getDependencies()
- assertNotNull(dependencies)
- Collection<AndroidLibrary> libs = dependencies.getLibraries()
- assertNotNull(libs)
- assertEquals(1, libs.size())
- AndroidLibrary androidLibrary = libs.iterator().next()
- assertNotNull(androidLibrary)
- assertEquals(":lib", androidLibrary.getProject())
- assertEquals("flavor1Release", androidLibrary.getProjectVariant())
- // TODO: right now we can only test the folder name efficiently
- String path = androidLibrary.getFolder().getPath()
- assertTrue(path, path.endsWith("/flavoredlib/lib/unspecified/flavor1Release"))
-
- ProductFlavorContainer flavor2 = ModelHelper.getProductFlavor(productFlavors, "flavor2")
- assertNotNull(flavor2)
-
- Variant flavor2Debug = ModelHelper.getVariant(variants, "flavor2Debug")
- assertNotNull(flavor2Debug)
-
- dependencies = flavor2Debug.getMainArtifact().getDependencies()
- assertNotNull(dependencies)
- libs = dependencies.getLibraries()
- assertNotNull(libs)
- assertEquals(1, libs.size())
- androidLibrary = libs.iterator().next()
- assertNotNull(androidLibrary)
- assertEquals(":lib", androidLibrary.getProject())
- assertEquals("flavor2Release", androidLibrary.getProjectVariant())
- // TODO: right now we can only test the folder name efficiently
- path = androidLibrary.getFolder().getPath()
- assertTrue(path, path.endsWith("/flavoredlib/lib/unspecified/flavor2Release"))
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavorlibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavorlibTest.groovy
deleted file mode 100644
index b12a565..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavorlibTest.groovy
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidLibrary
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.ProductFlavorContainer
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.Assert
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertTrue
-
-/**
- * Assemble tests for flavorlib.
- */
- at CompileStatic
-class FlavorlibTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("flavorlib")
- .create()
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- models = project.executeAndReturnMultiModel("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- void report() {
- project.execute("androidDependencies", "signingReport")
- }
-
- @Test
- void testModel() throws Exception {
-
- AndroidProject appProject = models.get(":app")
- assertNotNull("Module app null-check", appProject)
-
- assertFalse("Library Project", appProject.isLibrary())
-
- Collection<Variant> variants = appProject.getVariants()
- Collection<ProductFlavorContainer> productFlavors = appProject.getProductFlavors()
-
- ProductFlavorContainer flavor1 = ModelHelper.getProductFlavor(productFlavors, "flavor1")
- assertNotNull(flavor1)
-
- Variant flavor1Debug = ModelHelper.getVariant(variants, "flavor1Debug")
- assertNotNull(flavor1Debug)
-
- Dependencies dependencies = flavor1Debug.getMainArtifact().getDependencies()
- assertNotNull(dependencies)
- Collection<AndroidLibrary> libs = dependencies.getLibraries()
- assertNotNull(libs)
- assertEquals(1, libs.size())
- AndroidLibrary androidLibrary = libs.iterator().next()
- assertNotNull(androidLibrary)
- assertEquals(":lib1", androidLibrary.getProject())
- // TODO: right now we can only test the folder name efficiently
- String path = androidLibrary.getFolder().getPath()
- assertTrue(path, path.endsWith("/flavorlib/lib1/unspecified"))
-
- ProductFlavorContainer flavor2 = ModelHelper.getProductFlavor(productFlavors, "flavor2")
- assertNotNull(flavor2)
-
- Variant flavor2Debug = ModelHelper.getVariant(variants, "flavor2Debug")
- assertNotNull(flavor2Debug)
-
- dependencies = flavor2Debug.getMainArtifact().getDependencies()
- assertNotNull(dependencies)
- libs = dependencies.getLibraries()
- assertNotNull(libs)
- Assert.assertEquals(1, libs.size())
- androidLibrary = libs.iterator().next()
- assertNotNull(androidLibrary)
- assertEquals(":lib2", androidLibrary.getProject())
- // TODO: right now we can only test the folder name efficiently
- path = androidLibrary.getFolder().getPath()
- assertTrue(path, path.endsWith("/flavorlib/lib2/unspecified"))
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyLibDepTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyLibDepTest.groovy
deleted file mode 100644
index 06f97fa..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyLibDepTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for libMinifyLibDep.
- */
- at CompileStatic
-class LibMinifyLibDepTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("libMinifyLibDep")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyTest.groovy
deleted file mode 100644
index f2d552d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyTest.groovy
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.FileHelper
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-/**
- * Assemble tests for libMinify.
- */
- at CompileStatic
-class LibMinifyTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("libMinify")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "build")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check library has its fields obfuscated"() {
- // test whether a library project has its fields obfuscated
- FileHelper.checkContent(
- project.getOutputFile("mapping/release/mapping.txt"),
- "int obfuscatedInt -> a")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibProguardConsumerFilesTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibProguardConsumerFilesTest.groovy
deleted file mode 100644
index 7c85dc8..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibProguardConsumerFilesTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.FileHelper
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
-
-/**
- * Assemble tests for libProguarConsumerFiles.
- */
- at CompileStatic
-class LibProguardConsumerFilesTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("libProguardConsumerFiles")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "build")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "check proguard.txt has been correctly merged"() {
- File debugFileOutput = project.file("build/" + FD_INTERMEDIATES + "/bundles/debug/proguard.txt")
- File releaseFileOutput = project.file("build/" + FD_INTERMEDIATES + "/bundles/release/proguard.txt")
-
- FileHelper.checkContent(debugFileOutput, "A")
- FileHelper.checkContent(releaseFileOutput, ["A", "B", "C"])
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MinifyLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MinifyLibTest.groovy
deleted file mode 100644
index 8f10a52..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MinifyLibTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for minifyLib.
- */
- at CompileStatic
-class MinifyLibTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("minifyLib")
- .create()
-
- @BeforeClass
- static void setUp() {
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiDexWithLibTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiDexWithLibTest.groovy
deleted file mode 100644
index 3ff336f..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiDexWithLibTest.groovy
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.library
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-/**
- * Assemble tests for multiDexWithLib.
- */
- at CompileStatic
-class MultiDexWithLibTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("multiDexWithLib")
- .create()
-
- @BeforeClass
- static void setUp() {
- GradleTestProject.assumeBuildToolsAtLeast(21)
- project.execute("clean", "assembleDebug")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void lint() {
- project.execute("lint")
- }
-
- @Test
- @Category(DeviceTests.class)
- void connectedCheck() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ProguardAarPackagingTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ProguardAarPackagingTest.groovy
deleted file mode 100644
index 68a8600..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ProguardAarPackagingTest.groovy
+++ /dev/null
@@ -1,158 +0,0 @@
-package com.android.build.gradle.integration.library
-import com.android.SdkConstants
-import com.android.annotations.NonNull
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.google.common.base.Joiner
-import com.google.common.io.Files
-import org.apache.commons.io.FileUtils
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import java.util.zip.ZipEntry
-import java.util.zip.ZipFile
-
-import static org.junit.Assert.assertFalse
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertNull
-import static org.junit.Assert.assertTrue
-/**
- * Integration test to check that libraries included directly as jar files are correctly handled
- * when using proguard.
- */
-class ProguardAarPackagingTest {
-
- static public AndroidTestApp testApp = new HelloWorldApp()
- static public AndroidTestApp libraryInJar = new EmptyAndroidTestApp()
-
- static {
- TestSourceFile oldHelloWorld = testApp.getFile("HelloWorld.java")
- testApp.removeFile(oldHelloWorld)
- testApp.addFile(new TestSourceFile(oldHelloWorld.path, oldHelloWorld.name, """\
-package com.example.helloworld;
-
-import com.example.libinjar.LibInJar;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class HelloWorld extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- LibInJar.method();
- }
-}
-"""))
-
- // Create simple library jar.
- libraryInJar.addFile(new TestSourceFile(
- "src/main/java/com/example/libinjar","LibInJar.java", """\
-package com.example.libinjar;
-
-public class LibInJar {
- public static void method() {
- throw new UnsupportedOperationException("Not implemented");
- }
-}
-"""))
- }
-
- @ClassRule
- static public GradleTestProject androidProject =
- GradleTestProject.builder().withName("mainProject").fromTestApp(testApp).create()
- @ClassRule
- static public GradleTestProject libraryInJarProject =
- GradleTestProject.builder().withName("libInJar").fromTestApp(libraryInJar).create()
-
- @BeforeClass
- static public void setUp() {
- // Create android test application
- androidProject.getBuildFile() << """\
-apply plugin: 'com.android.library'
-
-dependencies {
- compile fileTree(dir: 'libs', include: '*.jar')
-}
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- buildTypes {
- release {
- minifyEnabled true
- }
- }
-}
-"""
-
- libraryInJarProject.buildFile << "apply plugin: 'java'"
- libraryInJarProject.execute("assemble")
-
- // Copy the generated jar into the android project.
- androidProject.file("libs").mkdirs()
- String libInJarName = Joiner.on(File.separatorChar)
- .join("build", "libs", libraryInJarProject.getName() + SdkConstants.DOT_JAR)
- FileUtils.copyFile(
- libraryInJarProject.file(libInJarName),
- androidProject.file("libs/libinjar.jar"))
- }
-
- @AfterClass
- static void cleanUp() {
- androidProject = null
- libraryInJarProject = null
- }
-
- @Test
- public void "check debug AAR packaging"() {
- androidProject.execute("assembleDebug")
- ZipFile aar = new ZipFile(androidProject.getAar("debug"))
-
- assertNotNull("debug build arr should contain libinjar",
- aar.getEntry("libs/libinjar.jar"))
- assertFalse("Classes.jar in debug AAR should not contain LibInJar",
- classesJarInAarContainsLibInJar(aar))
-
- }
-
- @Test
- public void "check release AAR packaging"() {
- androidProject.execute("assembleRelease")
- ZipFile aar = new ZipFile(androidProject.getAar("release"))
-
- assertNull("release build arr should not contain libinjar",
- aar.getEntry("libs/libinjar.jar"))
- assertTrue("Classes.jar in release AAR should contain some of LibInJar",
- classesJarInAarContainsLibInJar(aar))
- }
-
- private boolean classesJarInAarContainsLibInJar(@NonNull ZipFile aar) {
- // Extract the classes.jar from the aar file.
- File tempClassesJarFile = androidProject.file("temp-classes.jar")
- try {
- Files.asByteSink(tempClassesJarFile).writeFrom(
- aar.getInputStream(aar.getEntry("classes.jar")))
- ZipFile classesJar = new ZipFile(tempClassesJarFile)
- // Check whether it contains some of libinjar.
- for (ZipEntry entry : classesJar.entries()) {
- // Proguard can name the classes however it likes,
- // so this just checks for the package.
- if (entry.name.startsWith("com/example/libinjar")) {
- return true
- }
- } // Failed to find.
- return false
- } finally {
- tempClassesJarFile.delete()
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkModelTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkModelTest.groovy
deleted file mode 100644
index b261142..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkModelTest.groovy
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.ndk
-
-import com.android.SdkConstants
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.NativeLibrary
-import com.android.builder.model.Variant
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-
-/**
- * Test the return model of the NDK.
- */
-class NdkModelTest {
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp())
- .addGradleProperties("android.useDeprecatedNdk=true")
- .create()
-
- @Before
- void setUp() {
- project.buildFile <<
-"""
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- defaultConfig {
- ndk {
- moduleName "hello-jni"
- cFlags = "-DTEST_FLAG"
- }
- }
-}
-"""
- }
-
- @Test
- void "check native libraries in model"() {
- checkModel(
- debug : [
- SdkConstants.ABI_ARMEABI,
- SdkConstants.ABI_ARMEABI_V7A,
- SdkConstants.ABI_ARM64_V8A,
- SdkConstants.ABI_INTEL_ATOM,
- SdkConstants.ABI_INTEL_ATOM64,
- SdkConstants.ABI_MIPS,
- SdkConstants.ABI_MIPS64
- ]);
- }
-
- @Test
- void "check native libraries with splits"() {
- project.buildFile <<
-"""
-android {
- splits {
- abi {
- enable true
- reset()
- include 'x86', 'armeabi-v7a', 'mips'
- }
- }
-}
-"""
- checkModel(
- debug: [SdkConstants.ABI_ARMEABI_V7A, SdkConstants.ABI_INTEL_ATOM, SdkConstants.ABI_MIPS]);
- }
-
- @Test
- void "check native libraries with splits and universalApk"() {
- project.buildFile <<
- """
-android {
- splits {
- abi {
- enable true
- reset()
- include 'x86', 'armeabi-v7a', 'mips'
- universalApk true
- }
- }
-}
-"""
- checkModel(
- debug : [
- SdkConstants.ABI_ARMEABI,
- SdkConstants.ABI_ARMEABI_V7A,
- SdkConstants.ABI_ARM64_V8A,
- SdkConstants.ABI_INTEL_ATOM,
- SdkConstants.ABI_INTEL_ATOM64,
- SdkConstants.ABI_MIPS,
- SdkConstants.ABI_MIPS64
- ]);
- }
-
- @Test
- void "check native libraries with abiFilters"() {
- project.buildFile <<
- """
-android {
- productFlavors {
- x86 {
- ndk {
- abiFilter "x86"
- }
- }
- arm {
- ndk {
- abiFilters "armeabi-v7a"
- }
- }
- mips {
- ndk {
- abiFilter "mips"
- }
- }
- }
-}
-"""
- checkModel(
- x86Debug : [SdkConstants.ABI_INTEL_ATOM],
- armDebug : [SdkConstants.ABI_ARMEABI_V7A],
- mipsDebug : [SdkConstants.ABI_MIPS]);
- }
-
- @Test
- void "check using add on string for compileSdkVersion"() {
- project.buildFile <<
-"""
-android {
- compileSdkVersion "Google Inc.:Google APIs:$GradleTestProject.DEFAULT_COMPILE_SDK_VERSION"
-}
-"""
- AndroidProject model = project.executeAndReturnModel("assembleDebug")
- NativeLibrary lib = ModelHelper.getVariant(model.getVariants(), "debug").getMainArtifact()
- .getNativeLibraries().first()
- for (String flag : lib.getCCompilerFlags()) {
- if (flag.contains("sysroot")) {
- assertThat(flag).contains("android-${GradleTestProject.DEFAULT_COMPILE_SDK_VERSION}")
- }
- }
- }
-
- /**
- * Verify resulting model is as expected.
- *
- * @param variantToolchains map of variant name to array of expected toolchains.
- */
- private void checkModel(Map variantToolchains) {
-
- AndroidProject model = project.executeAndReturnModel("assembleDebug")
-
- Collection<Variant> variants = model.getVariants()
- for (Map.Entry entry : variantToolchains) {
- Variant variant = ModelHelper.getVariant(variants, (String) entry.getKey())
- AndroidArtifact mainArtifact = variant.getMainArtifact()
-
- assertThat(mainArtifact.getNativeLibraries()).hasSize(((Collection)entry.getValue()).size())
- for (NativeLibrary nativeLibrary : mainArtifact.getNativeLibraries()) {
- assertThat(nativeLibrary.getName()).isEqualTo("hello-jni")
- assertThat(nativeLibrary.getCCompilerFlags()).contains("-DTEST_FLAG");
- assertThat(nativeLibrary.getCppCompilerFlags()).contains("-DTEST_FLAG");
- assertThat(nativeLibrary.getCSystemIncludeDirs()).isEmpty();
- assertThat(nativeLibrary.getCppSystemIncludeDirs()).isNotEmpty();
- File solibSearchPath = nativeLibrary.getDebuggableLibraryFolders().first()
- assertThat(new File(solibSearchPath, "libhello-jni.so")).exists()
- }
-
- Collection<String> expectedToolchainNames = entry.getValue().collect { "gcc-" + it }
- Collection<String> toolchainNames = model.getNativeToolchains().collect { it.getName() }
- assertThat(toolchainNames).containsAllIn(expectedToolchainNames)
- Collection<String> nativeLibToolchains = mainArtifact.getNativeLibraries().
- collect { it.getToolchainName() }
- assertThat(nativeLibToolchains).containsExactlyElementsIn(expectedToolchainNames)
- }
-
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NoSplitNdkVariantsTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NoSplitNdkVariantsTest.groovy
deleted file mode 100644
index 85ec5be..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NoSplitNdkVariantsTest.groovy
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.ndk
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
-import com.android.builder.core.BuilderConstants
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import java.util.zip.ZipFile
-
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertNull
-
-/**
- * Integration test of the native plugin with multiple variants without using splits.
- */
- at CompileStatic
-class NoSplitNdkVariantsTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldJniApp())
- .addGradleProperties("android.useDeprecatedNdk=true")
- .create()
-
- @BeforeClass
- static public void setUp() {
- project.getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- defaultConfig {
- ndk {
- moduleName "hello-jni"
- }
- }
- buildTypes {
- release
- debug {
- jniDebuggable true
- }
- }
- productFlavors {
- x86 {
- ndk {
- abiFilter "x86"
- }
- }
- arm {
- ndk {
- abiFilters "armeabi-v7a", "armeabi"
- }
- }
- mips {
- ndk {
- abiFilter "mips"
- }
- }
- }
-}
-"""
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- public void assembleX86Release() {
- project.execute("assembleX86Release")
-
- // Verify .so are built for all platform.
- ZipFile apk = new ZipFile(project.getApk("x86", "release", "unsigned"))
- assertNotNull(apk.getEntry("lib/x86/libhello-jni.so"))
- assertNull(apk.getEntry("lib/mips/libhello-jni.so"))
- assertNull(apk.getEntry("lib/armeabi/libhello-jni.so"))
- assertNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"))
- }
-
- @Test
- public void assembleArmRelease() {
- project.execute("assembleArmRelease")
-
- // Verify .so are built for all platform.
- ZipFile apk = new ZipFile(project.getApk("arm", "release", "unsigned"))
- assertNull(apk.getEntry("lib/x86/libhello-jni.so"))
- assertNull(apk.getEntry("lib/mips/libhello-jni.so"))
- assertNotNull(apk.getEntry("lib/armeabi/libhello-jni.so"))
- assertNotNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"))
- }
-
- @Test
- public void assembleMipsRelease() {
- project.execute("assembleMipsRelease")
-
- // Verify .so are built for all platform.
- ZipFile apk = new ZipFile(project.getApk("mips", "release", "unsigned"))
- assertNull(apk.getEntry("lib/x86/libhello-jni.so"))
- assertNotNull(apk.getEntry("lib/mips/libhello-jni.so"))
- assertNull(apk.getEntry("lib/armeabi/libhello-jni.so"))
- assertNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"))
- }
-
- @Test
- @Category(DeviceTests.class)
- public void connectedAndroidTest() {
- if (GradleTestProject.DEVICE_PROVIDER_NAME.equals(BuilderConstants.CONNECTED)) {
- project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "ArmDebugAndroidTest")
- } else {
- project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "X86DebugAndroidTest")
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleCodeChangeTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleCodeChangeTest.groovy
deleted file mode 100644
index 203d742..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleCodeChangeTest.groovy
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_INC_JAVA
-/**
- * Performance test for full and incremental build on ioschedule 2014
- */
- at RunWith(Parameterized.class)
- at CompileStatic
-class IOScheduleCodeChangeTest {
-
- @Parameterized.Parameters(name="minify={0} jack={1}")
- public static Collection<Object[]> data() {
- // returns an array of boolean for all combinations of (proguard, jack).
- // Right now, only return the (false, false) and (false, true) cases.
- return [
-// [true, false].toArray(),
-// [true, true].toArray(),
- [false, false].toArray(),
- [false, true].toArray(),
- ];
- }
-
- private final boolean proguard
- private final boolean jack
-
- IOScheduleCodeChangeTest(boolean proguard, boolean jack) {
- this.proguard = proguard
- this.jack = jack
- }
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromExternalProject("iosched")
- .withJack(jack)
- .withMinify(proguard)
- .create()
-
- @Before
- public void setUp() {
- project.executeWithBenchmark("iosched2014", BUILD_FULL, "clean" , "assembleDebug")
- }
-
- @After
- void cleanUp() {
- project = null;
- }
-
- @Test
- void "Incremental Build on Java Change"() {
- project.replaceLine(
- "android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItem.java",
- 30,
- " public long startTime = 1;")
- project.executeWithBenchmark("iosched2014", BUILD_INC_JAVA, "assembleDebug")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleResChangeTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleResChangeTest.groovy
deleted file mode 100644
index 8f96bd4..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleResChangeTest.groovy
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_INC_RES_ADD
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_INC_RES_EDIT
-
-/**
- * Performance test for full and incremental build on ioschedule 2014
- */
- at CompileStatic
-class IOScheduleResChangeTest {
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromExternalProject("iosched")
- .create()
-
- @Before
- public void setUp() {
- project.executeWithBenchmark("iosched2014", BUILD_FULL, "clean" , "assembleDebug")
- }
-
- @After
- void cleanUp() {
- project = null;
- }
-
- @Test
- void "Incremental Build on Resource Edit Change"() {
- project.replaceLine(
- "android/src/main/res/values/strings.xml",
- 97,
- " <string name=\"app_name\">Google I/O 2015</string>")
-
- project.executeWithBenchmark("iosched2014", BUILD_INC_RES_EDIT, "assembleDebug")
- }
-
- @Test
- void "Incremental Build on Resource Add Change"() {
- project.replaceLine(
- "android/src/main/res/values/strings.xml",
- 97,
- " <string name=\"app_name\">Google I/O 2015</string><string name=\"aaaa\">aaa</string>")
-
- project.executeWithBenchmark("iosched2014", BUILD_INC_RES_ADD, "assembleDebug")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidComponentTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidComponentTest.groovy
deleted file mode 100644
index 8330926..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidComponentTest.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
-
-/**
- * Performance test on gradle experimantal plugin with a large number of variants
- */
-class LargeVariantAndroidComponentTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .forExpermimentalPlugin(true)
- .create()
-
-
- @BeforeClass
- static void setUp() {
- VariantBuildScriptGenerator generator = new VariantBuildScriptGenerator(
- buildTypes: VariantBuildScriptGenerator.LARGE_NUMBER,
- productFlavors: VariantBuildScriptGenerator.LARGE_NUMBER,
- """
- apply plugin: "com.android.model.application"
-
- model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
-
- android.buildTypes {
- \${buildTypes}
- }
-
- android.productFlavors {
- \${productFlavors}
- }
- }
- """.stripIndent())
- generator.addPostProcessor("buildTypes") { return (String) "create(\"$it\")" }
- generator.addPostProcessor("productFlavors") { return (String) "create(\"$it\")" }
-
- project.buildFile << generator.createBuildScript()
-
- // Execute before performance test to warm up the cache.
- project.execute("help");
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void performanceTest() {
- project.executeWithBenchmark("LargeVariantAndroid", EVALUATION, "projects")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidTest.groovy
deleted file mode 100644
index df629a5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidTest.groovy
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
-
-/**
- * Performance test on gradle plugin with a large number of variants
- */
-class LargeVariantAndroidTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(new HelloWorldApp())
- .useExperimentalGradleVersion(true)
- .create()
-
- @BeforeClass
- static void setUp() {
- project.buildFile << new VariantBuildScriptGenerator(
- buildTypes: VariantBuildScriptGenerator.LARGE_NUMBER,
- productFlavors: VariantBuildScriptGenerator.LARGE_NUMBER,
- """
- apply plugin: "com.android.application"
-
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- buildTypes {
- \${buildTypes}
- }
-
- productFlavors {
- \${productFlavors}
- }
- }
- """.stripIndent()).createBuildScript()
-
- // Execute before performance test to warm up the cache.
- project.execute("help");
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void performanceTest() {
- project.executeWithBenchmark("LargeVariantAndroid", EVALUATION, "projects")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentEvaluationTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentEvaluationTest.groovy
deleted file mode 100644
index f6a1ffc..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentEvaluationTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
-import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
-import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_BREADTH
-import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_DEPTH
-
-/**
- * test with ~120 projects that queries the IDE model
- */
- at CompileStatic
-class MediumAndroidComponentEvaluationTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(LargeTestProject.builder()
- .withModule(AndroidComponentGradleModule)
- .withDepth(MEDIUM_DEPTH)
- .withBreadth(MEDIUM_BREADTH)
- .create())
- .forExpermimentalPlugin(true)
- .withHeap("2048m")
- .create()
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "'projects' task run on 120 projects"() {
- project.executeWithBenchmark("MediumAndroid", EVALUATION, "projects")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentModelTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentModelTest.groovy
deleted file mode 100644
index f2095b2..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentModelTest.groovy
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
-import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Ignore
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.SYNC
-import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_BREADTH
-import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_DEPTH
-
-/**
- * test with ~120 projects that queries the IDE model
- */
- at CompileStatic
-class MediumAndroidComponentModelTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(LargeTestProject.builder()
- .withModule(AndroidComponentGradleModule)
- .withDepth(MEDIUM_DEPTH)
- .withBreadth(MEDIUM_BREADTH)
- .create())
- .forExpermimentalPlugin(true)
- .create()
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- @Ignore
- void "model query for 120 projects"() {
- Map<String, AndroidProject> models = project.getAllModelsWithBenchmark("MediumAndroid", SYNC)
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidComponentTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidComponentTest.groovy
deleted file mode 100644
index d995e2b..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidComponentTest.groovy
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.TestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
-
-/**
- * Performance test on gradle experimental plugin with multiple sub-projects and multiple variants.
- */
-class MultiProjectsAndroidComponentTest {
- public static AndroidTestApp app = new HelloWorldApp()
- static {
- VariantBuildScriptGenerator generator = new VariantBuildScriptGenerator(
- buildTypes: VariantBuildScriptGenerator.MEDIUM_NUMBER,
- productFlavors: VariantBuildScriptGenerator.MEDIUM_NUMBER,
- """
- apply plugin: "com.android.model.application"
-
- model {
- android {
- compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- }
-
- android.buildTypes {
- \${buildTypes}
- }
-
- android.productFlavors {
- \${productFlavors}
- }
- }
- """.stripIndent())
- generator.addPostProcessor("buildTypes") { return (String) "create(\"$it\")" }
- generator.addPostProcessor("productFlavors") { return (String) "create(\"$it\")" }
-
- app.addFile(new TestSourceFile("", "build.gradle", generator.createBuildScript()))
- }
-
- public static TestProject baseProject = new MultiModuleTestProject("app", app, 10)
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(baseProject)
- .forExpermimentalPlugin(true)
- .create()
-
- @BeforeClass
- static void setUp() {
- // Execute before performance test to warm up the cache.
- project.execute("help");
- }
-
- @AfterClass
- static void cleanUp() {
- app = null;
- baseProject = null;
- project = null;
- }
-
- @Test
- void "performance test - projects"() {
- project.executeWithBenchmark("MultiProjectsAndroid", EVALUATION, "projects")
- }
-
- @Test
- void "performance test - single variant"() {
- project.executeWithBenchmark("MultiProjectsAndroid", BUILD_FULL, ":app0:assembleProductFlavor0BuildType0")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidTest.groovy
deleted file mode 100644
index ede30b5..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidTest.groovy
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.TestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
-
-/**
- * Performance test on gradle plugin with multiple subprojects and multiple variants.
- */
-class MultiProjectsAndroidTest {
- public static AndroidTestApp app = new HelloWorldApp()
- static {
- app.addFile(new TestSourceFile("", "build.gradle",
- new VariantBuildScriptGenerator(
- buildTypes: VariantBuildScriptGenerator.MEDIUM_NUMBER,
- productFlavors: VariantBuildScriptGenerator.MEDIUM_NUMBER,
- """
- apply plugin: "com.android.application"
-
- android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
-
- buildTypes {
- \${buildTypes}
- }
-
- productFlavors {
- \${productFlavors}
- }
- }
- """.stripIndent()).createBuildScript())
- )
- }
-
- public static TestProject baseProject = new MultiModuleTestProject("app", app, 10)
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(baseProject)
- .useExperimentalGradleVersion(true)
- .create()
-
- @BeforeClass
- static void setUp() {
- // Execute before performance test to warm up the cache.
- project.execute("help");
- }
-
- @AfterClass
- static void cleanUp() {
- app = null;
- baseProject = null;
- project = null;
- }
-
- @Test
- void "performance test - projects"() {
- project.executeWithBenchmark("MultiProjectsAndroid", EVALUATION, "projects")
- }
-
- @Test
- void "performance test - single variant"() {
- project.executeWithBenchmark("MultiProjectsAndroid", BUILD_FULL, ":app0:assembleProductFlavor0BuildType0")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentEvaluationTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentEvaluationTest.groovy
deleted file mode 100644
index bd2a5c2..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentEvaluationTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
-import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
-import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.SMALL_BREADTH
-import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.SMALL_DEPTH
-
-/**
- * test with ~30 projects that queries the IDE model
- */
- at CompileStatic
-class SmallAndroidComponentEvaluationTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(LargeTestProject.builder()
- .withModule(AndroidComponentGradleModule)
- .withDepth(SMALL_DEPTH)
- .withBreadth(SMALL_BREADTH)
- .create())
- .forExpermimentalPlugin(true)
- .create()
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- void "'projects' task run on 30 projects"() {
- project.executeWithBenchmark("SmallAndroid", EVALUATION, "projects")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentModelTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentModelTest.groovy
deleted file mode 100644
index b29fcb8..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentModelTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.performance
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
-import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Ignore
-import org.junit.Test
-
-import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.SYNC
-
-/**
- * test with ~30 projects that queries the IDE model
- */
- at CompileStatic
-class SmallAndroidComponentModelTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(LargeTestProject.builder()
- .withModule(AndroidComponentGradleModule)
- .withDepth(LargeTestProject.SMALL_DEPTH)
- .withBreadth(LargeTestProject.SMALL_BREADTH)
- .create())
- .forExpermimentalPlugin(true)
- .create()
-
- @AfterClass
- static void cleanUp() {
- project = null
- }
-
- @Test
- @Ignore
- void "model query for 30 projects"() {
- Map<String, AndroidProject> models = project.getAllModelsWithBenchmark("SmallAndroid", SYNC)
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleTest.groovy
deleted file mode 100644
index 412d870..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleTest.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.test
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.builder.model.AndroidProject
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-/**
- * Test for setup with 2 modules: app and test-app
- */
- at CompileStatic
-class SeparateTestModuleTest {
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("separateTestModule")
- .create()
-
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setUp() {
- models = project.executeAndReturnMultiModel("assemble")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check model"() throws Exception {
- // check the content of the test model.
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithAarDependencyTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithAarDependencyTest.groovy
deleted file mode 100644
index db30337..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithAarDependencyTest.groovy
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.test
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.utils.ModelHelper
-import com.android.builder.model.AndroidArtifact
-import com.android.builder.model.AndroidProject
-import com.android.builder.model.Dependencies
-import com.android.builder.model.Variant
-import groovy.transform.CompileStatic
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
-/**
- * Test separate test module testing an app with aar dependencies.
- */
- at CompileStatic
-public class SeparateTestWithAarDependencyTest {
-
- @ClassRule
- static public GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("separateTestModule")
- .create()
-
- static Map<String, AndroidProject> models
-
- @BeforeClass
- static void setup() {
- project.getSubproject("app").getBuildFile() << """
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- publishNonDefault true
-
- defaultConfig {
- minSdkVersion 8
- }
-}
-dependencies {
- compile 'com.android.support:appcompat-v7:22.1.0'
-}
- """
-
- models = project.executeAndReturnMultiModel("clean", "assemble")
- }
-
- @AfterClass
- static void cleanUp() {
- project = null
- models = null
- }
-
- @Test
- void "check app doesn't contain test app's code"() {
- File apk = project.getSubproject('test').getApk("debug")
- assertThatApk(apk).doesNotContainClass("Lcom/android/tests/basic/Main;")
- }
-
- @Test
- void "check app doesn't contain test app's layout"() {
- File apk = project.getSubproject('test').getApk("debug")
- assertThatApk(apk).doesNotContainResource("layout/main.xml")
- }
-
- @Test
- void "check app doesn't contain test app's dependency lib's code"() {
- File apk = project.getSubproject('test').getApk("debug")
- assertThatApk(apk).doesNotContainClass("Landroid/support/v7/app/ActionBar;")
- }
-
- @Test
- void "check app doesn't contain test app's dependency lib's resources"() {
- File apk = project.getSubproject('test').getApk("debug")
- assertThatApk(apk).doesNotContainResource("layout/abc_action_bar_title_item.xml")
- }
-
- @Test
- void "check test model includes the tested app"() {
- Collection<Variant> variants = models.get(":test").getVariants()
-
- // get the main artifact of the debug artifact and its dependencies
- Variant variant = ModelHelper.getVariant(variants, "debug")
- AndroidArtifact artifact = variant.getMainArtifact()
- Dependencies dependencies = artifact.getDependencies()
-
- // check the app project shows up as a project dependency
- Collection<String> projects = dependencies.getProjects()
- assertThat(projects).containsExactly(":app")
-
- // and that nothing else shows up.
- // TODO: fix this.
-// Collection<JavaLibrary> javaLibs = dependencies.getJavaLibraries();
-// assertThat(javaLibs).hasSize(0);
-// Collection<AndroidLibrary> libs = dependencies.getLibraries();
-// assertThat(libs).hasSize(0);
- }
-
- @Test
- @Category(DeviceTests)
- void "connected check"() {
- GradleTestProject.assumeLocalDevice()
- project.execute("clean",":test:connectedCheck");
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithDependenciesTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithDependenciesTest.groovy
deleted file mode 100644
index a4d3577..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithDependenciesTest.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.test;
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject;
-import com.android.builder.model.AndroidProject;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import java.io.File;
-import java.util.Map;
-
-import groovy.transform.CompileStatic;
-
-/**
- * Test separate test module that tests an application with some complicated dependencies :
- * - the app imports a library importing a jar file itself.
- * - use minification.
- */
- at CompileStatic
-public class SeparateTestWithDependenciesTest {
-
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("separateTestModuleWithDependencies")
- .create()
-
- @BeforeClass
- static void setup() {
- project.execute("clean", "assemble")
- }
- @Test
- void "check app contains all dependent clases"() {
- project.execute("clean", "assemble")
- File apk = project.getSubproject('app').getApk("debug")
- assertThatApk(apk).containsClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
- }
-
-
- @Test
- void "check test app does not contain any minified application's dependent classes"() {
- project.execute("clean", "assemble")
- File apk = project.getSubproject('test').getApk("debug")
- assertThatApk(apk).doesNotContainClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithoutMinificationWithDependenciesTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithoutMinificationWithDependenciesTest.groovy
deleted file mode 100644
index 07f007d..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithoutMinificationWithDependenciesTest.groovy
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.test;
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
-
-import com.android.build.gradle.integration.common.fixture.GradleTestProject;
-
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * Test separate test module that tests an application with some complicated dependencies :
- * - the app imports a library importing a jar file itself.
- */
-public class SeparateTestWithoutMinificationWithDependenciesTest {
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("separateTestModuleWithDependencies")
- .create()
-
-
- @BeforeClass
- static void setup() {
- project.getSubproject("test").getBuildFile() << """
- apply plugin: 'com.android.test'
-
- android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- targetProjectPath ':app'
- targetVariant 'debug'
- }
- """
- project.execute("clean", "assemble")
- }
- @Test
- void "check app contains all dependent clases"() {
- File apk = project.getSubproject('app').getApk("debug")
- assertThatApk(apk).containsClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
- }
-
-
- @Test
- void "check test app does not contain any application's dependent classes"() {
- File apk = project.getSubproject('test').getApk("debug")
- assertThatApk(apk).doesNotContainClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/TestingSupportLibraryTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/TestingSupportLibraryTest.groovy
deleted file mode 100644
index 03124c8..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/TestingSupportLibraryTest.groovy
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.testing
-
-import com.android.build.gradle.integration.common.category.DeviceTests
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
-import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
-import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
-import groovy.transform.CompileStatic
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.experimental.categories.Category
-
-import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
-
-/**
- * Test project to cover the Android Gradle plugin's interaction with the testing support library.
- */
- at CompileStatic
-class TestingSupportLibraryTest {
-
- public static final AndroidTestApp helloWorldApp = new HelloWorldApp();
- static {
- /* Junit 4 now maps tests annotated with @Ignore and tests that throw
- AssumptionFailureExceptions as skipped. */
- helloWorldApp.addFile(new TestSourceFile("src/androidTest/java/com/example/helloworld", "FailureAssumptionTest.java",
- """
-package com.example.helloworld;
-
-import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
- at RunWith(AndroidJUnit4.class)
- at SmallTest
-public class FailureAssumptionTest {
- @Test
- public void checkAssumptionIsSkipped() {
- assumeTrue(false);
- fail("Tests with failing assumptions should be skipped");
- }
-
- @Test
- @Ignore
- public void checkIgnoreTestsArePossible() {
- fail("Tests with @Ignore annotation should be skipped");
- }
-
- @Test
- public void checkThisTestPasses() {
- System.err.println("Test executed");
- }
-}
-"""))
- helloWorldApp.addFile(new TestSourceFile("src/main", "AndroidManifest.xml",
- """<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.helloworld"
- android:versionCode="1"
- android:versionName="1.0">
-
- <uses-sdk android:minSdkVersion="18" />
- <application android:label="@string/app_name">
- <activity android:name=".HelloWorld"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
-"""))
- }
-
- @Rule
- public GradleTestProject project = GradleTestProject.builder()
- .fromTestApp(helloWorldApp)
- .create()
-
- @Before
- public void setUp() {
- project
- project.getBuildFile() << """
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
- buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
- defaultConfig {
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- dependencies {
- androidTestCompile 'com.android.support.test:runner:0.3'
- androidTestCompile 'com.android.support.test:rules:0.3'
- }
-}
-"""
- }
-
- @Test
- public void "check compile"() {
- project.execute("assembleDebugAndroidTest")
- }
-
- @Test
- @Category(DeviceTests.class)
- public void "test ignored tests are not run"() {
- project.executeConnectedCheck()
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingComplexProjectTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingComplexProjectTest.groovy
deleted file mode 100644
index 67e0db9..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingComplexProjectTest.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.testing
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Test
-/**
- * Runs tests in a big, complicated project.
- */
-class UnitTestingComplexProjectTest {
- @ClassRule
- public static GradleTestProject project = GradleTestProject.builder()
- .fromTestProject("unitTestingComplexProject")
- .create()
-
- @AfterClass
- public static void freeResources() throws Exception {
- project = null
- }
-
- @Test
- public void appProject() throws Exception {
- project.execute("clean", "test")
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingDefaultValuesTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingDefaultValuesTest.groovy
deleted file mode 100644
index 661ca2c..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingDefaultValuesTest.groovy
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.testing
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import groovy.transform.CompileStatic
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.testing.JUnitResults.Outcome.PASSED
-import static com.google.common.truth.Truth.assertThat
-/**
- * Meta-level tests for the app-level unit testing support. Tests default values mode.
- */
-class UnitTestingDefaultValuesTest {
- @ClassRule
- static public GradleTestProject simpleProject = GradleTestProject.builder()
- .fromTestProject("unitTestingDefaultValues")
- .create()
-
- @Test
- void testSimpleScenario() {
- simpleProject.execute("testDebug")
-
- def results = new JUnitResults(
- simpleProject.file("build/test-results/debug/TEST-com.android.tests.UnitTest.xml"))
-
- assertThat(results.allTestCases).containsExactly("defaultValues")
- assert results.outcome("defaultValues") == PASSED
- }
-}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingFlavorsSupportTest.groovy b/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingFlavorsSupportTest.groovy
deleted file mode 100644
index da07294..0000000
--- a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingFlavorsSupportTest.groovy
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.gradle.integration.testing
-import com.android.build.gradle.integration.common.fixture.GradleTestProject
-import com.google.common.base.Throwables
-import org.gradle.tooling.BuildException
-import org.junit.ClassRule
-import org.junit.Test
-
-import static com.android.build.gradle.integration.testing.JUnitResults.Outcome.FAILED
-import static com.android.build.gradle.integration.testing.JUnitResults.Outcome.PASSED
-import static org.junit.Assert.fail
-/**
- * Meta-level tests for the app-level unit testing support.
- */
-class UnitTestingFlavorsSupportTest {
- @ClassRule
- static public GradleTestProject flavorsProject = GradleTestProject.builder()
- .fromTestProject("unitTestingFlavors")
- .create()
-
- @Test
- public void 'Tests for a given flavor are only compiled against the flavor'() throws Exception {
- flavorsProject.execute("clean", "testBuildsPassesDebug")
-
- def results = new JUnitResults(
- flavorsProject.file("build/test-results/buildsPassesDebug/TEST-com.android.tests.PassingTest.xml"))
-
- assert results.outcome("referenceFlavorSpecificCode") == PASSED
-
- try {
- flavorsProject.execute("testDoesntBuildPassesDebug")
- fail()
- } catch (BuildException e) {
- assert Throwables.getRootCause(e)
- .exceptionClassName
- .endsWith("CompilationFailedException")
- }
- }
-
- @Test
- public void 'Task for a given flavor only runs the correct tests'() throws Exception {
- flavorsProject.execute("clean", "testBuildsPassesDebug")
-
- try {
- flavorsProject.execute("testBuildsFailsDebug")
- fail()
- } catch (BuildException e) {
- assert Throwables.getRootCause(e).message.startsWith("There were failing tests.")
-
- def results = new JUnitResults(
- flavorsProject.file("build/test-results/buildsFailsDebug/TEST-com.android.tests.FailingTest.xml"))
- assert results.outcome("failingTest") == FAILED
- }
- }
-}
diff --git a/base/build-system/integration-test/src/test/resources/basic/example.json b/base/build-system/integration-test/src/test/resources/basic/example.json
deleted file mode 100644
index 6bad0d3..0000000
--- a/base/build-system/integration-test/src/test/resources/basic/example.json
+++ /dev/null
@@ -1,90 +0,0 @@
-{
-
-"project_info": {
- "project_id": "red-ant-1",
- "project_number": "1234567890",
- "name": "My Awesome App"
-},
-
-"client": [{
- "client_info": {
- "client_type": 1,
- "android_client_info": {
- "package_name": "com.example.app.free"
- }
- },
-
- "oauth_client": [{
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.free",
- "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
- }
- },
- {
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.free",
- "certificate_hash": "AFDD9pi4klj5vQ6D8gab7UNawhw="
- }
- }],
-
- "services": {
- "analytics_service": {
- "status": 2,
- "analytics_property": {
- "tracking_id": "UA-123456789"
- }
- },
- "cloud_messaging_service": {
- "status": 2
- },
- "appinvite_service": {
- "status": 2
- },
- "google_signin_service": {
- "status": 2
- },
- "ads_service": {
- "status": 2,
- "test_banner_ad_unit_id": "ca-app-pub-3940256099942544\/1033173712",
- "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544\/6300978111"
- }
- }
-},
-
-{
- "client_info": {
- "client_type": 1,
- "android_client_info": {
- "package_name": "com.example.app.paid"
- }
- },
-
- "oauth_client": [{
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.paid",
- "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
- }
- }],
- "services": {
- "analytics_service": {
- "status": 2,
- "analytics_property": {
- "tracking_id": "UA-1923912413"
- }
- },
- "cloud_messaging_service": {
- "status": 2
- },
- "appinvite_service": {
- "status": 2
- },
- "google_signin_service": {
- "status": 2
- }
- }
-}]
-
-}
diff --git a/base/build-system/integration-test/src/test/resources/basic/global_tracker.xml b/base/build-system/integration-test/src/test/resources/basic/global_tracker.xml
deleted file mode 100644
index eda13b3..0000000
--- a/base/build-system/integration-test/src/test/resources/basic/global_tracker.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-123456789</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/basic/values.xml b/base/build-system/integration-test/src/test/resources/basic/values.xml
deleted file mode 100644
index 8ec1e20..0000000
--- a/base/build-system/integration-test/src/test/resources/basic/values.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-123456789</string>
- <string name="gcm_defaultSenderId">1234567890</string>
- <string name="test_banner_ad_unit_id">ca-app-pub-3940256099942544/1033173712</string>
- <string name="test_interstitial_ad_unit_id">ca-app-pub-3940256099942544/6300978111</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/disabledservice/example.json b/base/build-system/integration-test/src/test/resources/disabledservice/example.json
deleted file mode 100644
index 9d58065..0000000
--- a/base/build-system/integration-test/src/test/resources/disabledservice/example.json
+++ /dev/null
@@ -1,90 +0,0 @@
-{
-
-"project_info": {
- "project_id": "red-ant-1",
- "project_number": "1234567890",
- "name": "My Awesome App"
-},
-
-"client": [{
- "client_info": {
- "client_type": 1,
- "android_client_info": {
- "package_name": "com.example.app.free"
- }
- },
-
- "oauth_client": [{
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.free",
- "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
- }
- },
- {
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.free",
- "certificate_hash": "AFDD9pi4klj5vQ6D8gab7UNawhw="
- }
- }],
-
- "services": {
- "analytics_service": {
- "status": 2,
- "analytics_property": {
- "tracking_id": "UA-123456789"
- }
- },
- "cloud_messaging_service": {
- "status": 2
- },
- "appinvite_service": {
- "status": 2
- },
- "google_signin_service": {
- "status": 2
- },
- "ads_service": {
- "status": 1,
- "test_banner_ad_unit_id": "ca-app-pub-3940256099942544\/1033173712",
- "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544\/6300978111"
- }
- }
-},
-
-{
- "client_info": {
- "client_type": 1,
- "android_client_info": {
- "package_name": "com.example.app.paid"
- }
- },
-
- "oauth_client": [{
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.paid",
- "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
- }
- }],
- "services": {
- "analytics_service": {
- "status": 2,
- "analytics_property": {
- "tracking_id": "UA-1923912413"
- }
- },
- "cloud_messaging_service": {
- "status": 2
- },
- "appinvite_service": {
- "status": 2
- },
- "google_signin_service": {
- "status": 2
- }
- }
-}]
-
-}
diff --git a/base/build-system/integration-test/src/test/resources/disabledservice/global_tracker.xml b/base/build-system/integration-test/src/test/resources/disabledservice/global_tracker.xml
deleted file mode 100644
index eda13b3..0000000
--- a/base/build-system/integration-test/src/test/resources/disabledservice/global_tracker.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-123456789</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/disabledservice/values.xml b/base/build-system/integration-test/src/test/resources/disabledservice/values.xml
deleted file mode 100644
index e43783c..0000000
--- a/base/build-system/integration-test/src/test/resources/disabledservice/values.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-123456789</string>
- <string name="gcm_defaultSenderId">1234567890</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/flavor/example.json b/base/build-system/integration-test/src/test/resources/flavor/example.json
deleted file mode 100644
index 877c44b..0000000
--- a/base/build-system/integration-test/src/test/resources/flavor/example.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
-
-"project_info": {
- "project_id": "red-ant-1",
- "project_number": "1234567890",
- "name": "My Awesome App"
-},
-
-"client": [{
- "client_info": {
- "client_type": 1,
- "android_client_info": {
- "package_name": "com.example.app.free"
- }
- },
-
- "oauth_client": [{
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.free",
- "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
- }
- },
- {
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.free",
- "certificate_hash": "AFDD9pi4klj5vQ6D8gab7UNawhw="
- }
- }],
-
- "services": {
- "analytics_service": {
- "status": 2,
- "analytics_property": {
- "tracking_id": "UA-123456789"
- }
- },
- "cloud_messaging_service": {
- "status": 2
- },
- "appinvite_service": {
- "status": 2
- },
- "google_signin_service": {
- "status": 2
- }
- }
-},
-
-{
- "client_info": {
- "client_type": 1,
- "android_client_info": {
- "package_name": "com.example.app.paid"
- }
- },
-
- "oauth_client": [{
- "client_type": 1,
- "android_info": {
- "package_name": "com.example.app.paid",
- "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
- }
- }],
- "services": {
- "analytics_service": {
- "status": 2,
- "analytics_property": {
- "tracking_id": "UA-1923912413"
- }
- },
- "cloud_messaging_service": {
- "status": 2
- },
- "appinvite_service": {
- "status": 2
- },
- "google_signin_service": {
- "status": 2
- }
- }
-}]
-
-}
diff --git a/base/build-system/integration-test/src/test/resources/flavor/free.global_tracker.xml b/base/build-system/integration-test/src/test/resources/flavor/free.global_tracker.xml
deleted file mode 100644
index eda13b3..0000000
--- a/base/build-system/integration-test/src/test/resources/flavor/free.global_tracker.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-123456789</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/flavor/free.values.xml b/base/build-system/integration-test/src/test/resources/flavor/free.values.xml
deleted file mode 100644
index e43783c..0000000
--- a/base/build-system/integration-test/src/test/resources/flavor/free.values.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-123456789</string>
- <string name="gcm_defaultSenderId">1234567890</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/flavor/paid.global_tracker.xml b/base/build-system/integration-test/src/test/resources/flavor/paid.global_tracker.xml
deleted file mode 100644
index b17f063..0000000
--- a/base/build-system/integration-test/src/test/resources/flavor/paid.global_tracker.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-1923912413</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/flavor/paid.values.xml b/base/build-system/integration-test/src/test/resources/flavor/paid.values.xml
deleted file mode 100644
index ac047c1..0000000
--- a/base/build-system/integration-test/src/test/resources/flavor/paid.values.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-1923912413</string>
- <string name="gcm_defaultSenderId">1234567890</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/noservice/global_tracker.xml b/base/build-system/integration-test/src/test/resources/noservice/global_tracker.xml
deleted file mode 100644
index eda13b3..0000000
--- a/base/build-system/integration-test/src/test/resources/noservice/global_tracker.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ga_trackingId">UA-123456789</string>
-</resources>
diff --git a/base/build-system/integration-test/src/test/resources/noservice/no_services.json b/base/build-system/integration-test/src/test/resources/noservice/no_services.json
deleted file mode 100644
index e3ad31e..0000000
--- a/base/build-system/integration-test/src/test/resources/noservice/no_services.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-
-"project_info": {
- "project_id": "red-ant-1",
- "project_number": "1234567890",
- "name": "My Awesome App"
-},
-
-"client": []
-
-}
diff --git a/base/build-system/integration-test/src/test/resources/noservice/values.xml b/base/build-system/integration-test/src/test/resources/noservice/values.xml
deleted file mode 100644
index 77697c1..0000000
--- a/base/build-system/integration-test/src/test/resources/noservice/values.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="gcm_defaultSenderId">1234567890</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/build.gradle b/base/build-system/integration-test/test-projects/3rdPartyTests/app/build.gradle
deleted file mode 100644
index 5e2ea74..0000000
--- a/base/build-system/integration-test/test-projects/3rdPartyTests/app/build.gradle
+++ /dev/null
@@ -1,44 +0,0 @@
-apply plugin: 'com.android.application'
-
-project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
-project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
-
-apply from: "../../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- deviceProvider project.fakeProvider
- testServer project.fakeServer
-
- buildTypes {
- debug {
- testCoverageEnabled true
- }
- }
-}
-
-project.afterEvaluate {
- configure(fakeDebugAndroidTest) {
- doLast {
- String error = project.fakeProvider.isValid()
- if (error != null) {
- throw new GradleException("Failed DeviceProvider usage: " + error)
- }
- }
- }
-
- configure(fake2Upload) {
- doLast {
- String error = project.fakeServer.isValid()
- if (error != null) {
- throw new GradleException("Failed TestServer usage: " + error)
- }
- }
- }
-}
-
-dependencies {
- compile project(':lib')
-}
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/build.gradle b/base/build-system/integration-test/test-projects/3rdPartyTests/lib/build.gradle
deleted file mode 100644
index ea70169..0000000
--- a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-apply plugin: 'com.android.library'
-
-project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
-project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
-
-apply from: "../../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- deviceProvider project.fakeProvider
- testServer project.fakeServer
-
- buildTypes {
- debug {
- testCoverageEnabled true
- }
- }
-}
-
-project.afterEvaluate {
- configure(fakeDebugAndroidTest) {
- doLast {
- String error = project.fakeProvider.isValid()
- if (error != null) {
- throw new GradleException("Failed DeviceProvider usage: " + error)
- }
- }
- }
-
- configure(fake2Upload) {
- doLast {
- String error = project.fakeServer.isValid()
- if (error != null) {
- throw new GradleException("Failed TestServer usage: " + error)
- }
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/AndroidManifest.xml
deleted file mode 100644
index 26598f0..0000000
--- a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.testprojecttest.lib">
- <application>
- <activity
- android:name="com.android.tests.testprojecttest.lib.LibActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- <permission-group android:name="foo.permission-group.COST_MONEY"
- android:label="@string/app_name"
- android:description="@string/app_name" />
-
- <permission android:name="foo.permission.SEND_SMS"
- android:permissionGroup="foo.permission-group.COST_MONEY"
- android:label="@string/app_name"
- android:description="@string/app_name" />
-
- <permission android:name="foo.blah.SEND_SMS"
- android:permissionGroup="foo.permission-group.COST_MONEY"
- android:label="@string/app_name"
- android:description="@string/app_name" />
-
-
-</manifest>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/build.gradle b/base/build-system/integration-test/test-projects/abiPureSplits/build.gradle
deleted file mode 100644
index 2e9b44f..0000000
--- a/base/build-system/integration-test/test-projects/abiPureSplits/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
- generatePureSplits true
-
- defaultConfig {
- minSdkVersion 21
- ndk {
- moduleName "hello-jni"
- }
-
- // This actual the app version code. Giving ourselves 1,000,000 values
- versionCode = 123
-
- }
-
- splits {
- abi {
- enable true
- reset()
- include 'x86', 'armeabi-v7a', 'mips'
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/aidl/build.gradle b/base/build-system/integration-test/test-projects/aidl/build.gradle
deleted file mode 100644
index 0d350d5..0000000
--- a/base/build-system/integration-test/test-projects/aidl/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/aidl/src/main/AndroidManifest.xml
deleted file mode 100644
index 0aeb232..0000000
--- a/base/build-system/integration-test/test-projects/aidl/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- android:versionCode="1"
- android:versionName="1.0" package="com.android.tests.basicprojectwithaidl">
- <application android:label="@string/app_name" android:icon="@drawable/icon">
- <activity android:name="com.android.tests.basicprojectwithaidl.Main"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Main.java b/base/build-system/integration-test/test-projects/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Main.java
deleted file mode 100644
index eaed510..0000000
--- a/base/build-system/integration-test/test-projects/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Main.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.tests.basicprojectwithaidl;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class Main extends Activity
-{
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
-}
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Rect.java b/base/build-system/integration-test/test-projects/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Rect.java
deleted file mode 100644
index 8e16926..0000000
--- a/base/build-system/integration-test/test-projects/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Rect.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.android.tests.basicprojectwithaidl;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class Rect implements Parcelable {
- public int left;
- public int top;
- public int right;
- public int bottom;
-
- public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
- public Rect createFromParcel(Parcel in) {
- return new Rect(in);
- }
-
- public Rect[] newArray(int size) {
- return new Rect[size];
- }
- };
-
- public Rect() {
- }
-
- private Rect(Parcel in) {
- readFromParcel(in);
- }
-
- public void writeToParcel(Parcel out) {
- out.writeInt(left);
- out.writeInt(top);
- out.writeInt(right);
- out.writeInt(bottom);
- }
-
- public void readFromParcel(Parcel in) {
- left = in.readInt();
- top = in.readInt();
- right = in.readInt();
- bottom = in.readInt();
- }
-
- public int describeContents() {
- // TODO Auto-generated method stub
- return 0;
- }
-
- public void writeToParcel(Parcel arg0, int arg1) {
- // TODO Auto-generated method stub
-
- }
-}
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/res/layout/main.xml b/base/build-system/integration-test/test-projects/aidl/src/main/res/layout/main.xml
deleted file mode 100644
index 783e4a0..0000000
--- a/base/build-system/integration-test/test-projects/aidl/src/main/res/layout/main.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
-<TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="Basic Project"
- />
-</LinearLayout>
-
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/res/values/strings.xml b/base/build-system/integration-test/test-projects/aidl/src/main/res/values/strings.xml
deleted file mode 100644
index a7322d3..0000000
--- a/base/build-system/integration-test/test-projects/aidl/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">basicProject</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/build.gradle b/base/build-system/integration-test/test-projects/androidManifestInTest/build.gradle
deleted file mode 100644
index 6b4e65c..0000000
--- a/base/build-system/integration-test/test-projects/androidManifestInTest/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
- debugCompile 'com.android.support:support-v13:13.0.0'
-
- compile 'com.google.android.gms:play-services:3.1.36'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion rootProject.ext.buildToolsVersion
-
- testBuildType "debug"
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-}
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/build.gradle b/base/build-system/integration-test/test-projects/androidTestLibDep/build.gradle
deleted file mode 100644
index c080977..0000000
--- a/base/build-system/integration-test/test-projects/androidTestLibDep/build.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.library'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- androidTestCompile 'com.google.guava:guava:11.0.2'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/api/app/build.gradle b/base/build-system/integration-test/test-projects/api/app/build.gradle
deleted file mode 100644
index c6c66c8..0000000
--- a/base/build-system/integration-test/test-projects/api/app/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-// ATTENTION -- hash value of this file is checked in the corresponding
-// integration test. Please make sure any changes you make here are
-// backwards compatible.
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-// query for all (non-test) variants and inject a new step in the builds
-android.applicationVariants.all { variant ->
- // create a task that "handles" the compile classes
- // does some processing (or not)
- // and outputs a jar
- def jarTask = tasks.create(name: "jar${variant.name.capitalize()}", type: Jar) {
- from variant.javaCompile.destinationDir
- destinationDir file("${buildDir}/jars/${variant.dirName}")
- baseName "classes"
- }
-
- // this task depends on the compilation task
- jarTask.dependsOn variant.javaCompile
-
- // now make the dex task depend on it and use its output
- variant.dex.dependsOn jarTask
- variant.dex.inputFiles = files(jarTask.archivePath).files
-}
-
-project.afterEvaluate {
- if (android.applicationVariants.size() != 2) {
- throw new GradleException("Wrong number of app variants!")
- }
-
- if (android.testVariants.size() != 1) {
- throw new GradleException("Wrong number of test variants!")
- }
-}
diff --git a/base/build-system/integration-test/test-projects/api/lib/build.gradle b/base/build-system/integration-test/test-projects/api/lib/build.gradle
deleted file mode 100644
index 29843fd..0000000
--- a/base/build-system/integration-test/test-projects/api/lib/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-// ATTENTION -- hash value of this file is checked in the corresponding
-// integration test. Please make sure any changes you make here are
-// backwards compatible.
-
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-// query for all (non-test) variants and inject a new step in the builds
-android.libraryVariants.all { variant ->
- // create a task that copies some additional data in the library bundle
- def copyBlahTask = tasks.create(name: "copy${variant.name.capitalize()}Blah", type: Copy) {
- from file("$project.projectDir/blah")
- destinationDir file("${buildDir}/bundles/${variant.dirName}")
- }
-
- // now make the package task depend on it
- // only one output for libraries
- variant.outputs.get(0).packageLibrary.dependsOn copyBlahTask
-}
-
-project.afterEvaluate {
- if (android.libraryVariants.size() != 2) {
- throw new GradleException("Wrong number of app variants!")
- }
-}
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/build.gradle b/base/build-system/integration-test/test-projects/applibtest/app/build.gradle
deleted file mode 100644
index 2cf3c11..0000000
--- a/base/build-system/integration-test/test-projects/applibtest/app/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-//
-// A basic Android application split over a library and a main project.
-//
-dependencies {
- compile project(':lib')
-}
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/build.gradle b/base/build-system/integration-test/test-projects/applibtest/lib/build.gradle
deleted file mode 100644
index 32fcb51..0000000
--- a/base/build-system/integration-test/test-projects/applibtest/lib/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-apply plugin: 'com.android.library'
-
-apply from: "../../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- testApplicationId = "com.android.tests.testprojecttest.testlib"
- }
-
- buildTypes {
- debug {
- testCoverageEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl b/base/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
deleted file mode 100644
index 9b81031..0000000
--- a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.tests.basicprojectwithaidl;
-
-interface ITest {
- Rect getRect();
- int getInt();
-}
-
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl b/base/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
deleted file mode 100644
index 734cf77..0000000
--- a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.android.tests.basicprojectwithaidl;
-
-// Declare Rect so AIDL can find it and knows that it implements
-// the parcelable protocol.
-parcelable Rect;
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/build.gradle b/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/build.gradle
deleted file mode 100644
index 35636ce..0000000
--- a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.0"
-
- defaultConfig {
- applicationId "com.example.manifest_merger_example"
- minSdkVersion 15
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
- productFlavors {
- flavor {
- applicationId "com.example.manifest_merger_example.flavor"
- minSdkVersion 15
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
- }
- buildTypes {
- release {
- minifyEnabled false
- }
- }
-}
-
-dependencies {
- compile project(':examplelibrary')
- compile fileTree(dir: 'libs', include: ['*.jar'])
-}
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/build.gradle b/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/build.gradle
deleted file mode 100644
index fbf7f29..0000000
--- a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.0"
-
- defaultConfig {
- minSdkVersion 15
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
-}
-
-android.testVariants.all {
- it.mergedFlavor.manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example.flavor"]
-}
-
diff --git a/base/build-system/integration-test/test-projects/artifactApi/build.gradle b/base/build-system/integration-test/test-projects/artifactApi/build.gradle
deleted file mode 100644
index f8edf2d..0000000
--- a/base/build-system/integration-test/test-projects/artifactApi/build.gradle
+++ /dev/null
@@ -1,142 +0,0 @@
-// ATTENTION -- hash value of this file is checked in the corresponding
-// integration test. Please make sure any changes you make here are
-// backwards compatible.
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-import com.android.builder.model.ArtifactMetaData
-import com.android.builder.model.SourceProvider
-
-// create a configuration to allow dependencies specific to the Java artifact
-configurations {
- foo
-}
-
-// Register an artifact type and tie it to the name "__test__".
-// This name will show up in Studio. It must be unique
-android.registerArtifactType("__test__", false, ArtifactMetaData.TYPE_JAVA)
-
-// register a new SourceProvider per build type, and associate it with the artifact
-// registered above.
-android.buildTypes.all { type ->
- project.android.registerBuildTypeSourceProvider(
- "__test__", // registered name of the artifact type
- type, // associate it with a BuildType
- getProvider("buildType:$type.name")) // the source provider
-}
-
-// Same with ProductFlavor
-android.productFlavors.all { flavor ->
- project.android.registerProductFlavorSourceProvider(
- "__test__", // registered name of the artifact type
- flavor, // associate it with a ProductFlavor
- getProvider("productFlavor:$flavor.name")) // the source provider
-}
-
-// now register a new (java) artifact for each variant, still associated
-// with the artifact type registered above.
-android.applicationVariants.all { variant ->
- project.android.registerJavaArtifact(
- "__test__", // registered name of the artifact type
- variant, // associate it with a variant
- "assemble:$variant.name", // name of the assemble task for this artifact
- "compile:$variant.name", // name of the java compile task for this artifact
- [], // generated source folders
- [], // tasks to generate sources etc. in the IDE.
- configurations.foo,
- new File("classesFolder:$variant.name"), // location of the classes folder (compile output)
- new File("resources:$variant.name"), // location of the resources folder
- getProvider("provider:$variant.name")) // source provider specific to this variant for the artifact.
-}
-
-// after the artifact is created, add a dependency
-dependencies {
- foo files('libs/util-1.0.jar')
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- flavorDimensions "pricing", "releaseType"
-
- productFlavors {
-
- beta {
- flavorDimension "releaseType"
- }
-
- normal {
- flavorDimension "releaseType"
- }
-
- free {
- flavorDimension "pricing"
- }
-
- paid {
- flavorDimension "pricing"
- }
- }
-}
-
-public class SourceProviderImpl implements SourceProvider, Serializable {
- private static final long serialVersionUID = 1L;
-
- private final String name;
-
- SourceProviderImpl(String name) {
- this.name = name
- }
-
- String getName() {
- return name
- }
-
- File getManifestFile() {
- return new File(name)
- }
-
- Collection<File> getJavaDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getResourcesDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getAidlDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getRenderscriptDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getCDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getCppDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getJniLibsDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getResDirectories() {
- return Collections.emptyList()
- }
-
- Collection<File> getAssetsDirectories() {
- return Collections.emptyList()
- }
-}
-
-SourceProvider getProvider(String name) {
- return new SourceProviderImpl(name)
-}
diff --git a/base/build-system/integration-test/test-projects/assets/app/build.gradle b/base/build-system/integration-test/test-projects/assets/app/build.gradle
deleted file mode 100644
index b36aec7..0000000
--- a/base/build-system/integration-test/test-projects/assets/app/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-dependencies {
- compile project(':lib')
-}
diff --git a/base/build-system/integration-test/test-projects/assets/lib/build.gradle b/base/build-system/integration-test/test-projects/assets/lib/build.gradle
deleted file mode 100644
index 8e82aff..0000000
--- a/base/build-system/integration-test/test-projects/assets/lib/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/build.gradle b/base/build-system/integration-test/test-projects/attrOrder/app/build.gradle
deleted file mode 100644
index 39c51f6..0000000
--- a/base/build-system/integration-test/test-projects/attrOrder/app/build.gradle
+++ /dev/null
@@ -1,11 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-dependencies{
- compile project(":lib")
-}
-
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/build.gradle b/base/build-system/integration-test/test-projects/attrOrder/lib/build.gradle
deleted file mode 100644
index bee7821..0000000
--- a/base/build-system/integration-test/test-projects/attrOrder/lib/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
diff --git a/base/build-system/integration-test/test-projects/basic/build.gradle b/base/build-system/integration-test/test-projects/basic/build.gradle
deleted file mode 100644
index d2af2bd..0000000
--- a/base/build-system/integration-test/test-projects/basic/build.gradle
+++ /dev/null
@@ -1,129 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-apply from: "../commonLocalRepo.gradle"
-
-apply plugin: 'com.android.application'
-
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
- compile 'com.google.android.gms:play-services:3.1.36'
-
- debugCompile 'com.android.support:support-v13:13.0.0'
- releaseCompile 'com.android.support:support-v13:21.0.0'
-
- // hamcrest-library depends on hamcrest-core, both provide a /LICENSE.txt file
- // which used to cause packaging conflict. We added a special case for license files.
- androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- testBuildType "debug"
-
- signingConfigs {
- myConfig {
- storeFile file("debug.keystore")
- storePassword "android"
- keyAlias "androiddebugkey"
- keyPassword "android"
- }
- }
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
-
- testInstrumentationRunner "android.test.InstrumentationTestRunner"
- testInstrumentationRunnerArgument "size", "medium"
-
- testHandleProfiling false
-
- buildConfigField "boolean", "DEFAULT", "true"
- buildConfigField "String", "FOO", "\"foo\""
- buildConfigField "String", "FOO", "\"foo2\""
-
- resValue "string", "foo", "foo"
-
- resConfig "en"
- resConfigs "nodpi", "hdpi"
-
- manifestPlaceholders = [ "someKey":12 ]
- }
-
- buildTypes {
- debug {
- applicationIdSuffix ".debug"
- signingConfig signingConfigs.myConfig
-
- testCoverageEnabled true
-
- buildConfigField "String", "FOO", "\"bar1\""
- buildConfigField "String", "FOO", "\"bar\""
-
- resValue "string", "foo", "foo2"
-
- useJack project.ext.useJack
- }
- }
-
- aaptOptions {
- noCompress 'txt'
- ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
- }
-
- adbOptions {
- timeOutInMs 5000 // 5 seconds in ms.
- installOptions "-d","-t"
- }
-
- lintOptions {
- // set to true to turn off analysis progress reporting by lint
- quiet true
- // if true, stop the gradle build if errors are found
- abortOnError false
- // if true, only report errors
- ignoreWarnings true
- // if true, emit full/absolute paths to files with errors (true by default)
- //absolutePaths true
- // if true, check all issues, including those that are off by default
- checkAllWarnings true
- // if true, treat all warnings as errors
- warningsAsErrors true
- // turn off checking the given issue id's
- disable 'TypographyFractions','TypographyQuotes'
- // turn on the given issue id's
- enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
- // check *only* the given issue id's
- check 'NewApi', 'InlinedApi'
- // if true, don't include source code lines in the error output
- noLines true
- // if true, show all locations for an error, do not truncate lists, etc.
- showAll true
- // Fallback lint configuration (default severities, etc.)
- lintConfig file("default-lint.xml")
- // if true, generate a text report of issues (false by default)
- textReport true
- // location to write the output; can be a file or 'stdout'
- textOutput 'stdout'
- // if true, generate an XML report for use by for example Jenkins
- xmlReport false
- // file to write report to (if not specified, defaults to lint-results.xml)
- xmlOutput file("lint-report.xml")
- // if true, generate an HTML report (with issue explanations, sourcecode, etc)
- htmlReport true
- // optional path to report (default will be lint-results.html in the builddir)
- htmlOutput file("lint-report.html")
- }
-
- // Override the versionCode of the release version
- applicationVariants.all { variant ->
- if (variant.buildType.name == "release") {
- variant.outputs.get(0).versionCodeOverride = 13
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/build.gradle b/base/build-system/integration-test/test-projects/basicMultiFlavors/build.gradle
deleted file mode 100644
index 296fe58..0000000
--- a/base/build-system/integration-test/test-projects/basicMultiFlavors/build.gradle
+++ /dev/null
@@ -1,30 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- flavorDimensions "pricing", "releaseType"
-
- productFlavors {
-
- beta {
- flavorDimension "releaseType"
- }
-
- normal {
- flavorDimension "releaseType"
- }
-
- free {
- flavorDimension "pricing"
- }
-
- paid {
- flavorDimension "pricing"
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/build.gradle b/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/build.gradle
deleted file mode 100644
index 4c0c3ad..0000000
--- a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
- generatePureSplits true
-
- defaultConfig {
- minSdkVersion 21
- ndk {
- moduleName "hello-jni"
- }
-
- // This actual the app version code. Giving ourselves 1,000,000 values
- versionCode = 123
-
- }
-
- splits {
- abi {
- enable true
- reset()
- include 'x86', 'armeabi-v7a', 'mips'
- }
- density {
- enable true
- exclude "ldpi", "tvdpi", "xxxhdpi"
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/build.gradle b/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/build.gradle
deleted file mode 100644
index ff79982..0000000
--- a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
- generatePureSplits true
-
- defaultConfig {
- versionCode 12
- minSdkVersion 21
- targetSdkVersion 21
- }
-
- splits {
- density {
- enable true
- exclude "ldpi", "tvdpi", "xxxhdpi"
- }
- language {
- enable true
- include "fr,fr-rBE", "fr-rCA", "en"
- }
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/commonGradlePluginVersion.gradle b/base/build-system/integration-test/test-projects/commonGradlePluginVersion.gradle
deleted file mode 100644
index 5f38f00..0000000
--- a/base/build-system/integration-test/test-projects/commonGradlePluginVersion.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-gradleVersion = System.env.CUSTOM_GRADLE
-experimentalGradleVersion = System.env.CUSTOM_EXPERIMENTAL_GRADLE
-
-if (gradleVersion == null && System.env.CUSTOM_REPO != null) {
- // Extract the version from the top level buildSrc, relative to CUSTOM_REPO.
- // Have a fake environment for buildSrc/base/version.gradle can insert the version info into.
- def env = new Object() {
- Object project = new Object() {
- Object ext = new Object() {
- String buildVersion
- String baseVersion
- String apiVersion
- String experimentalVersion
- }
- }
- }
- apply from: "$System.env.CUSTOM_REPO/../../tools/buildSrc/base/version.gradle", to: env
- gradleVersion = env.project.ext.buildVersion
- experimentalGradleVersion = env.project.ext.experimentalVersion
-}
-
-if (gradleVersion == null) {
- throw new RuntimeException("Android Gradle plugin version for tests 'CUSTOM_GRADLE' not set.")
-}
diff --git a/base/build-system/integration-test/test-projects/commonHeader.gradle b/base/build-system/integration-test/test-projects/commonHeader.gradle
deleted file mode 100644
index 1499c89..0000000
--- a/base/build-system/integration-test/test-projects/commonHeader.gradle
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-ext {
- buildToolsVersion = [System.env.CUSTOM_BUILDTOOLS, project.properties["CUSTOM_BUILDTOOLS"], '22.0.1'].find()
- latestCompileSdk = 21
-
- useJack = System.env.CUSTOM_JACK ||
- Boolean.valueOf((String) project.properties[
- "com.android.build.gradle.integratonTest.useJack"])
-
- minifyEnabled = System.env.CUSTOM_MINIFY ||
- Boolean.valueOf((String) project.properties[
- "com.android.build.gradle.integratonTest.minifyEnabled"])
-
- useComponentModel = Boolean.valueOf((String) project.properties[
- "com.android.build.gradle.integratonTest.useComponentModel"])
-
- def remoteTestProvider = System.env.REMOTE_TEST_PROVIDER
- if (remoteTestProvider != null) {
- plugins.withId('com.android.application') {
- apply plugin: remoteTestProvider
- }
- plugins.withId('com.android.library') {
- apply plugin: remoteTestProvider
- }
- plugins.withId('com.android.model.application') {
- apply plugin: remoteTestProvider
- }
- plugins.withId('com.android.model.library') {
- apply plugin: remoteTestProvider
- }
- }
-}
-
-plugins.withId("com.android.application") { plugin ->
- plugin.extension.defaultConfig.useJack = ext.useJack
-}
-
-plugins.withId("com.android.library") { plugin ->
- plugin.extension.defaultConfig.useJack = ext.useJack
-}
diff --git a/base/build-system/integration-test/test-projects/commonLocalRepo.gradle b/base/build-system/integration-test/test-projects/commonLocalRepo.gradle
deleted file mode 100644
index 0374bb1..0000000
--- a/base/build-system/integration-test/test-projects/commonLocalRepo.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-repositories {
- if (System.env.CUSTOM_REPO != null) {
- maven { url System.env.CUSTOM_REPO }
- } else if (System.env.USE_EXTERNAL_REPO) {
- jcenter()
- } else {
- throw new RuntimeException("Neither CUSTOM_REPO nor USE_EXTERNAL_REPO set.")
- }
- if (System.env.ADDITIONAL_TEST_CUSTOM_REPO != null) {
- maven { url System.env.ADDITIONAL_TEST_CUSTOM_REPO }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/componentModel/build.gradle b/base/build-system/integration-test/test-projects/componentModel/build.gradle
deleted file mode 100644
index 55fb3e4..0000000
--- a/base/build-system/integration-test/test-projects/componentModel/build.gradle
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
-
-
-apply plugin: "com.android.model.application"
-
-model {
- android {
- compileSdkVersion 19
- buildToolsVersion '19.1.0'
- }
- android.productFlavors {
- create { name = "f1" }
- create { name = "f2" }
- }
-}
-
-sources {
- main {
- java {
- source {
- srcDir "src/main/java"
- }
- }
- }
- f1 {
- res {
-
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/build.gradle b/base/build-system/integration-test/test-projects/customArtifactDep/app/build.gradle
deleted file mode 100644
index e274846..0000000
--- a/base/build-system/integration-test/test-projects/customArtifactDep/app/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-dependencies {
- compile project(path: ':util', configuration: 'custom')
-}
diff --git a/base/build-system/integration-test/test-projects/customSigning/build.gradle b/base/build-system/integration-test/test-projects/customSigning/build.gradle
deleted file mode 100644
index d7e6615..0000000
--- a/base/build-system/integration-test/test-projects/customSigning/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- buildTypes {
- customSigning.initWith buildTypes.release
- }
-
- applicationVariants.all { variant ->
- if (variant.buildType.name == "customSigning") {
- variant.outputsAreSigned = true
- // Normally you would have something like this here:
- //
- // variant.outputs.first { output ->
- // output.outputFile = mySigningTask.outputFile
- // }
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/densitySplit/build.gradle b/base/build-system/integration-test/test-projects/densitySplit/build.gradle
deleted file mode 100644
index 64ab077..0000000
--- a/base/build-system/integration-test/test-projects/densitySplit/build.gradle
+++ /dev/null
@@ -1,45 +0,0 @@
-import com.android.build.OutputFile;
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-if (buildToolsVersion < '21.0.0') {
- println ("Warning : this sample requires build-tools version 21 or above")
-}
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- minSdkVersion 16
- targetSdkVersion 20
-
- buildConfigField "String", "FOO", "\"bar\""
- }
-
- splits {
- density {
- enable true
- exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
- compatibleScreens 'small', 'normal', 'large', 'xlarge'
- }
- }
-}
-
-// map for the version code
-ext.versionCodes = [all:1, mdpi:2, hdpi:3, xhdpi:4, xxhdpi:5]
-
-android.applicationVariants.all { variant ->
- // assign different version code for each output
- variant.outputs.each { output ->
- def key = output.getFilter(OutputFile.DENSITY) == null ? "all" : output.getFilter(OutputFile.DENSITY)
- def code = project.ext.versionCodes.get(key) * 100 + android.defaultConfig.versionCode
- output.versionCodeOverride = code
- output.versionNameOverride = "version ${code}"
- }
-}
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/build.gradle b/base/build-system/integration-test/test-projects/densitySplitInL/build.gradle
deleted file mode 100644
index 9094d33..0000000
--- a/base/build-system/integration-test/test-projects/densitySplitInL/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
- generatePureSplits true
-
- defaultConfig {
- versionCode 12
- minSdkVersion 21
- targetSdkVersion 21
- }
-
- splits {
- density {
- enable true
- exclude "ldpi", "tvdpi", "xxxhdpi"
- }
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/dependencies/build.gradle b/base/build-system/integration-test/test-projects/dependencies/build.gradle
deleted file mode 100644
index 7a31d35..0000000
--- a/base/build-system/integration-test/test-projects/dependencies/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-version='1.0'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- apk project(':jarProject')
- provided project(':jarProject2')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/build.gradle b/base/build-system/integration-test/test-projects/dependenciesWithVariants/build.gradle
deleted file mode 100644
index 2680ce0..0000000
--- a/base/build-system/integration-test/test-projects/dependenciesWithVariants/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
- productFlavors {
- flavor1
- }
-}
-
-dependencies {
- flavor1Compile project(':jarProject')
- androidTestFlavor1Compile project(':jarProject2')
-}
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/build.gradle b/base/build-system/integration-test/test-projects/dependencyChecker/build.gradle
deleted file mode 100644
index 1c20838..0000000
--- a/base/build-system/integration-test/test-projects/dependencyChecker/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'org.apache.httpcomponents:httpclient:4.1.1'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/build.gradle b/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/build.gradle
deleted file mode 100644
index 0db940f..0000000
--- a/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-repositories {
- // com.google.android:android is only on maven central.
- mavenCentral()
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- // Lower than com.google.android:android:4.1.1.4
- defaultConfig.minSdkVersion 14
-}
-
-dependencies {
- compile 'com.google.android:android:4.1.1.4'
-}
diff --git a/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/src/main/AndroidManifest.xml
deleted file mode 100644
index f0ccc91..0000000
--- a/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2015 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests">
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/src/main/java/com/android/tests/MainActivity.java b/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/src/main/java/com/android/tests/MainActivity.java
deleted file mode 100644
index b1be4ab..0000000
--- a/base/build-system/integration-test/test-projects/dependencyCheckerComGoogleAndroidJar/src/main/java/com/android/tests/MainActivity.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests;
-
-import android.app.Activity;
-
-public class MainActivity exteds Activity {}
diff --git a/base/build-system/integration-test/test-projects/duplicateNameImport/build.gradle b/base/build-system/integration-test/test-projects/duplicateNameImport/build.gradle
deleted file mode 100644
index 47e52c3..0000000
--- a/base/build-system/integration-test/test-projects/duplicateNameImport/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-allprojects {
-
- version = '0.1'
- group = 'a.b.c'
-
- plugins.withId('android-library') {
-
- android {
- buildToolsVersion rootProject.ext.buildToolsVersion
- compileSdkVersion 22
-
- defaultConfig {
- minSdkVersion 22
- targetSdkVersion 22
- }
-
- }
- }
-}
-
-
-
-
diff --git a/base/build-system/integration-test/test-projects/embedded/main/build.gradle b/base/build-system/integration-test/test-projects/embedded/main/build.gradle
deleted file mode 100644
index 4af903d..0000000
--- a/base/build-system/integration-test/test-projects/embedded/main/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- productFlavors {
- flavor1 {
-
- }
- flavor2 {
-
- }
- }
-
- buildTypes {
- custom.initWith(release)
- custom {
- applicationIdSuffix = '.custom'
- }
- }
-}
-
-dependencies {
- wearApp project(':micro-apps:default')
- flavor1WearApp project(':micro-apps:flavor1')
- customWearApp project(':micro-apps:custom')
-}
-
diff --git a/base/build-system/integration-test/test-projects/embedded/main/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/embedded/main/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/embedded/main/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/build.gradle b/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/build.gradle
deleted file mode 100644
index 23ceb28..0000000
--- a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 42
- versionName "custom"
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
-}
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/androidTest/java/com/android/tests/basic/custom/MainTest.java b/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/androidTest/java/com/android/tests/basic/custom/MainTest.java
deleted file mode 100644
index 2ef1876..0000000
--- a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/androidTest/java/com/android/tests/basic/custom/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic.custom;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/AndroidManifest.xml
deleted file mode 100644
index fbcbc96..0000000
--- a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic.custom">
- <application android:label="@string/app_name" android:icon="@drawable/icon">
- <activity android:name=".Main"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/build.gradle b/base/build-system/integration-test/test-projects/embedded/micro-apps/default/build.gradle
deleted file mode 100644
index fda3687..0000000
--- a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 42
- versionName "default"
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
-}
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/build.gradle b/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/build.gradle
deleted file mode 100644
index 3951b71..0000000
--- a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 42
- versionName "flavor1"
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
-}
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/emptySplit/build.gradle b/base/build-system/integration-test/test-projects/emptySplit/build.gradle
deleted file mode 100644
index 93df577..0000000
--- a/base/build-system/integration-test/test-projects/emptySplit/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- // create splits but remove all split values.
- splits {
- density {
- enable true
- reset()
- }
- abi {
- enable true
- reset()
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/extractAnnotations/build.gradle b/base/build-system/integration-test/test-projects/extractAnnotations/build.gradle
deleted file mode 100644
index 3ec69c9..0000000
--- a/base/build-system/integration-test/test-projects/extractAnnotations/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.library'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.android.support:support-annotations:+'
- compile 'com.android.support:support-v4:+'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-defaultTasks 'extractDebugAnnotations'
diff --git a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/build.gradle b/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/build.gradle
deleted file mode 100644
index e982939..0000000
--- a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/build.gradle
+++ /dev/null
@@ -1,22 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.library'
-
-dependencies {
- compile 'com.android.support:support-annotations:+'
-}
-
-android {
- resourcePrefix 'lib1_'
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 14
- targetSdkVersion 19
- renderscriptTargetApi 18
- renderscriptSupportModeEnabled true
- }
-}
diff --git a/base/build-system/integration-test/test-projects/filteredOutBuildType/build.gradle b/base/build-system/integration-test/test-projects/filteredOutBuildType/build.gradle
deleted file mode 100644
index 490c50e..0000000
--- a/base/build-system/integration-test/test-projects/filteredOutBuildType/build.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- variantFilter {
- if (it.buildType.name.equals("debug")) {
- it.ignore = true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/filteredOutBuildType/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/filteredOutBuildType/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/filteredOutBuildType/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/filteredOutVariants/build.gradle b/base/build-system/integration-test/test-projects/filteredOutVariants/build.gradle
deleted file mode 100644
index 5adb8ed..0000000
--- a/base/build-system/integration-test/test-projects/filteredOutVariants/build.gradle
+++ /dev/null
@@ -1,37 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- variantFilter {
- String abi = it.flavors.get(0).name
- if ("cupcake".equals(it.flavors.get(1).name) && ("x86".equals(abi) || "mips".equals(abi))) {
- it.ignore = true
- }
- }
-
- flavorDimensions "abi", "api"
-
- productFlavors {
- x86 {
- flavorDimension "abi"
- }
- mips {
- flavorDimension "abi"
- }
- arm {
- flavorDimension "abi"
- }
- cupcake {
- flavorDimension "api"
- }
- gingerbread {
- flavorDimension "api"
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/filteredOutVariants/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/filteredOutVariants/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/filteredOutVariants/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/flavored/build.gradle b/base/build-system/integration-test/test-projects/flavored/build.gradle
deleted file mode 100644
index cdf7fff..0000000
--- a/base/build-system/integration-test/test-projects/flavored/build.gradle
+++ /dev/null
@@ -1,50 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- testBuildType = "staging"
-
- defaultConfig {
- }
-
- productFlavors {
- f1 {
- applicationId = "com.android.tests.flavored.f1"
- versionName = "1.0.0-f1"
- ext.buildType = "F1PROD"
- }
- f2 {
- applicationId = "com.android.tests.flavored.f2"
- versionName = "1.0.0-f2"
- ext.buildType = "F2PROD"
- }
- }
-
- buildTypes {
- debug {
- applicationIdSuffix = ".debug"
- versionNameSuffix = ".D"
- }
- staging {
- applicationIdSuffix = ".staging"
- versionNameSuffix = ".S"
- signingConfig signingConfigs.debug
- }
- }
-
- // This is not part of the test per se, it tests that adding dynamic properties on the
- // flavor declaration can be retrieved later.
- applicationVariants.all { variant ->
- assert variant.productFlavors.size() == 1
- def buildType = variant.productFlavors[0].buildType
- if (!"F1PROD".equals(buildType)
- && !"F2PROD".equals(buildType)) {
- throw new RuntimeException("Invalid extension property value ${buildType}")
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/build.gradle b/base/build-system/integration-test/test-projects/flavoredlib/app/build.gradle
deleted file mode 100644
index ad5e4d5..0000000
--- a/base/build-system/integration-test/test-projects/flavoredlib/app/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- productFlavors {
- flavor1 {
- applicationId = "com.android.tests.flavorlib.app.flavor1"
- }
- flavor2 {
- applicationId = "com.android.tests.flavorlib.app.flavor2"
- }
- }
-
- testOptions {
- resultsDir = "$project.buildDir/foo/results"
- reportDir = "$project.buildDir/foo/report"
- }
-}
-
-dependencies {
- flavor1Compile project(path: ':lib', configuration: 'flavor1Release')
- flavor2Compile project(path: ':lib', configuration: 'flavor2Release')
-}
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/build.gradle b/base/build-system/integration-test/test-projects/flavoredlib/lib/build.gradle
deleted file mode 100644
index 3fcf98b..0000000
--- a/base/build-system/integration-test/test-projects/flavoredlib/lib/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultPublishConfig "flavor1Release"
- publishNonDefault true
-
- productFlavors {
- flavor1 { }
- flavor2 { }
- }
-
- libraryVariants.all { variant ->
- assert variant.productFlavors.size() != 0
- }
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/AndroidManifest.xml b/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/AndroidManifest.xml
deleted file mode 100644
index f6544e6..0000000
--- a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/AndroidManifest.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
- <application >
- <activity
- android:name=".flavor1.MainActivity"
- android:label="@string/lib_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/AndroidManifest.xml b/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/AndroidManifest.xml
deleted file mode 100644
index b7a5a25..0000000
--- a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/AndroidManifest.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application >
- <activity
- android:name=".flavor2.MainActivity"
- android:label="@string/lib_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/build.gradle b/base/build-system/integration-test/test-projects/flavorlib/app/build.gradle
deleted file mode 100644
index e9063bc..0000000
--- a/base/build-system/integration-test/test-projects/flavorlib/app/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- productFlavors {
- flavor1 {
- applicationId = "com.android.tests.flavorlib.app.flavor1"
- }
- flavor2 {
- applicationId = "com.android.tests.flavorlib.app.flavor2"
- }
- }
-
- testOptions {
- resultsDir = "$project.buildDir/foo/results"
- reportDir = "$project.buildDir/foo/report"
- }
-}
-
-dependencies {
- flavor1Compile project(':lib1')
- flavor2Compile project(':lib2')
-}
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/build.gradle b/base/build-system/integration-test/test-projects/flavorlib/lib1/build.gradle
deleted file mode 100644
index 8e82aff..0000000
--- a/base/build-system/integration-test/test-projects/flavorlib/lib1/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/build.gradle b/base/build-system/integration-test/test-projects/flavorlib/lib2/build.gradle
deleted file mode 100644
index 8e82aff..0000000
--- a/base/build-system/integration-test/test-projects/flavorlib/lib2/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/build.gradle b/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/build.gradle
deleted file mode 100644
index af4f29e..0000000
--- a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/build.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- productFlavors {
- flavor1 {
- applicationId = "com.android.tests.flavorlib.app.flavor1"
- }
- flavor2 {
- applicationId = "com.android.tests.flavorlib.app.flavor2"
- }
- }
-}
-
-dependencies {
- flavor1Compile project(':lib1')
- flavor2Compile project(':lib2')
-}
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/build.gradle b/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/build.gradle
deleted file mode 100644
index ded9657..0000000
--- a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml
deleted file mode 100644
index 44bc277..0000000
--- a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.flavorlib.lib"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/lib_name" >
- <activity
- android:name="MainActivity"
- android:label="@string/lib_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/build.gradle b/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/build.gradle
deleted file mode 100644
index ded9657..0000000
--- a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml
deleted file mode 100644
index 44bc277..0000000
--- a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.flavorlib.lib"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/lib_name" >
- <activity
- android:name="MainActivity"
- android:label="@string/lib_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavors/build.gradle b/base/build-system/integration-test/test-projects/flavors/build.gradle
deleted file mode 100644
index bc9e357..0000000
--- a/base/build-system/integration-test/test-projects/flavors/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- flavorDimensions "group1", "group2"
-
- productFlavors {
- f1 {
- flavorDimension "group1"
- }
- f2 {
- flavorDimension "group1"
- }
-
- fa {
- flavorDimension "group2"
- }
- fb {
- flavorDimension "group2"
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/genFolderApi/build.gradle b/base/build-system/integration-test/test-projects/genFolderApi/build.gradle
deleted file mode 100644
index b61482b..0000000
--- a/base/build-system/integration-test/test-projects/genFolderApi/build.gradle
+++ /dev/null
@@ -1,64 +0,0 @@
-// ATTENTION -- hash value of this file is checked in the corresponding
-// integration test. Please make sure any changes you make here are
-// backwards compatible.
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-public class GenerateCode extends DefaultTask {
- @Input
- String value
-
- @OutputFile
- File outputFile
-
- @TaskAction
- void taskAction() {
- getOutputFile().text =
- "package com.custom;\n" +
- "public class Foo {\n" +
- " public static String getBuildDate() { return \"${getValue()}\"; }\n" +
- "}\n";
- }
-}
-
-public class GenerateRes extends DefaultTask {
- @Input
- String value
-
- @OutputFile
- File outputFile
-
- @TaskAction
- void taskAction() {
- getOutputFile().text = "<xml>${getValue()}</xml>\n"
- }
-}
-
-android.applicationVariants.all { variant ->
-
- // create a task that generates a java class
- File sourceFolder = file("${buildDir}/customCode/${variant.dirName}")
- def javaGenerationTask = tasks.create(name: "generatedCodeFor${variant.name.capitalize()}", type: GenerateCode) {
- value new Date().format("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC"))
- outputFile file("${sourceFolder.absolutePath}/com/custom/Foo.java")
- }
-
- variant.registerJavaGeneratingTask(javaGenerationTask, sourceFolder)
-
- // create a task that generates an XML file class
- File resFolder = file("${buildDir}/customRes/${variant.dirName}")
- def resGenerationTask = tasks.create(name: "generatedResFor${variant.name.capitalize()}", type: GenerateRes) {
- value new Date().format("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC"))
- outputFile file("${resFolder.absolutePath}/xml/generated.xml")
- }
-
- variant.registerResGeneratingTask(resGenerationTask, resFolder)
-}
diff --git a/base/build-system/integration-test/test-projects/genFolderApi2/build.gradle b/base/build-system/integration-test/test-projects/genFolderApi2/build.gradle
deleted file mode 100644
index 6840b4c..0000000
--- a/base/build-system/integration-test/test-projects/genFolderApi2/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-// ATTENTION -- hash value of this file is checked in the corresponding
-// integration test. Please make sure any changes you make here are
-// backwards compatible.
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-android.applicationVariants.all { variant ->
- File sourceFolder = file("${buildDir}/customCode/${variant.dirName}")
- variant.addJavaSourceFoldersToModel(sourceFolder)
-
- def unitTestVariant = variant.unitTestVariant
- File unitTestSourceFolder = file("${buildDir}/customCode/${unitTestVariant.dirName}-1")
- unitTestVariant.addJavaSourceFoldersToModel(unitTestSourceFolder)
-}
-
-android.unitTestVariants.all { unitTestVariant ->
- File unitTestSourceFolder = file("${buildDir}/customCode/${unitTestVariant.dirName}-2")
- unitTestVariant.addJavaSourceFoldersToModel(unitTestSourceFolder)
-}
diff --git a/base/build-system/integration-test/test-projects/genFolderApi2/src/main/java/com/android/tests/basic/Main.java b/base/build-system/integration-test/test-projects/genFolderApi2/src/main/java/com/android/tests/basic/Main.java
deleted file mode 100644
index df05828..0000000
--- a/base/build-system/integration-test/test-projects/genFolderApi2/src/main/java/com/android/tests/basic/Main.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.android.tests.basic;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.TextView;
-
-public class Main extends Activity
-{
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- TextView tv = (TextView) findViewById(R.id.text);
- tv.setText(com.custom.Foo.getBuildDate());
- }
-}
diff --git a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/app/build.gradle b/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/app/build.gradle
deleted file mode 100644
index 96c0f64..0000000
--- a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/app/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-dependencies {
- // This is invalid and should cause the test to fail.
- compile project(':dependency-app')
-}
diff --git a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/build.gradle b/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/build.gradle
deleted file mode 100644
index a2a4ec5..0000000
--- a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
diff --git a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/dependency-app/build.gradle b/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/dependency-app/build.gradle
deleted file mode 100644
index d64a68b..0000000
--- a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/dependency-app/build.gradle
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/settings.gradle b/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/settings.gradle
deleted file mode 100644
index a46e4e3..0000000
--- a/base/build-system/integration-test/test-projects/invalidDependencyOnAppProject/settings.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-include 'app'
-include 'dependency-app'
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/build.gradle b/base/build-system/integration-test/test-projects/jarjarIntegration/build.gradle
deleted file mode 100644
index 08e0805..0000000
--- a/base/build-system/integration-test/test-projects/jarjarIntegration/build.gradle
+++ /dev/null
@@ -1,89 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-if (buildToolsVersion < '21.0.0') {
- println ("Warning : this sample requires build-tools version 21 or above")
-}
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- minSdkVersion 16
- targetSdkVersion 20
- }
-}
-
-repositories {
- maven { url System.env.CUSTOM_REPO }
-}
-
-dependencies {
- compile 'com.google.code.gson:gson:2.3'
-}
-
-// JarJar tasks and related configurations\
-configurations {
- // create new configuration for JarJar
- jarjar
-}
-
-dependencies {
- // add jarjar artifact to jarjar configuration
- jarjar 'com.googlecode.jarjar:jarjar:1.3'
-}
-
-android.applicationVariants.all { variant ->
- // create task name
- String jarJarTaskName = "jarJar${variant.name.capitalize()}";
-
- def workingDir = project.file('build/intermediates/jarjar');
- // input file are the compiled files and the gson lib.
- def inputDir = variant.javaCompiler.destinationDir
- def outputLibrary = new File(workingDir, 'classes.jar').getCanonicalFile()
-
- // define task
- def jarJarTask = task("${jarJarTaskName}") {
- logger.info "${jarJarTaskName} inputDir: ${inputDir}"
- logger.info "${jarJarTaskName} outputLibrary: ${outputLibrary}"
-
- inputs.file inputDir
- outputs.file outputLibrary
-
- doLast {
- // in Ant
- project.ant {
- // define jarjar task, for classpath is used path from jarjar configuration
- taskdef name: 'jarjar',
- classname: 'com.tonicsystems.jarjar.JarJarTask',
- classpath: configurations.jarjar.asPath
-
- // start jarjar task
- jarjar(jarfile: outputLibrary) {
- // input is our inputDir
- fileset(dir: inputDir)
- // this project has only ONE dependency, gson so this is easy but obviously, this is not
- // the best way to include compile dependencies.
- zipFileset(src: variant.compileLibraries.toArray()[0])
- // rule to repackage gson to new package
- rule pattern: 'com.google.gson.**', result: 'com.google.repacked.gson. at 1'
- }
- }
-
- // replace jar generated by compile with jar generated by JarJar
- variant.dex.inputFiles = [outputLibrary]
- // and remove our preDex'ed libraries.
- variant.dex.libraries = []
- }
- }
-
- // plan task to be started between Compile task and Dex task
- // if the task was using proguard, we would need to reset the proguard inputs rather than dex.
- variant.dex.dependsOn jarJarTask
- jarJarTask.dependsOn variant.javaCompiler
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/build.gradle b/base/build-system/integration-test/test-projects/jarjarWithJack/build.gradle
deleted file mode 100644
index 8cefaf9..0000000
--- a/base/build-system/integration-test/test-projects/jarjarWithJack/build.gradle
+++ /dev/null
@@ -1,37 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-if (buildToolsVersion < '21.0.0') {
- println ("Warning : this sample requires build-tools version 21 or above")
-}
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- minSdkVersion 16
- targetSdkVersion 20
- useJack true
- jarJarRuleFile 'jarjar.rules'
- }
-
- buildTypes {
- release {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt')
- }
- }
-}
-
-repositories {
- maven { url System.env.CUSTOM_REPO }
-}
-
-dependencies {
- compile 'com.google.code.gson:gson:2.3'
-}
diff --git a/base/build-system/integration-test/test-projects/libMinify/build.gradle b/base/build-system/integration-test/test-projects/libMinify/build.gradle
deleted file mode 100644
index 15950b7..0000000
--- a/base/build-system/integration-test/test-projects/libMinify/build.gradle
+++ /dev/null
@@ -1,23 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- proguardFile 'config.pro'
- }
- buildTypes {
- release {
- minifyEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/build.gradle b/base/build-system/integration-test/test-projects/libMinifyJarDep/app/build.gradle
deleted file mode 100644
index 1fceb0f..0000000
--- a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-apply plugin: 'com.android.application'
-
-apply from: "../../commonLocalRepo.gradle"
-
-dependencies {
- compile project(':lib')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- testBuildType "proguard"
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-
- buildTypes {
- proguard.initWith(buildTypes.debug)
- proguard {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'config.pro'
- }
- }
-
- dexOptions {
- incremental false
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/lib/build.gradle b/base/build-system/integration-test/test-projects/libMinifyJarDep/lib/build.gradle
deleted file mode 100644
index 075cb40..0000000
--- a/base/build-system/integration-test/test-projects/libMinifyJarDep/lib/build.gradle
+++ /dev/null
@@ -1,30 +0,0 @@
-apply plugin: 'com.android.library'
-
-apply from: "../../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.google.guava:guava:15.0'
- compile fileTree(dir: 'libs', include: '*.jar')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- proguardFile 'config.pro'
- consumerProguardFiles 'config.pro'
- }
- buildTypes {
- debug {
- minifyEnabled true
- }
- release {
- minifyEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/build.gradle b/base/build-system/integration-test/test-projects/libMinifyLibDep/app/build.gradle
deleted file mode 100644
index 3318e89..0000000
--- a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/build.gradle
+++ /dev/null
@@ -1,31 +0,0 @@
-apply plugin: 'com.android.application'
-
-dependencies {
- compile project(':lib')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- testBuildType "proguard"
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-
- buildTypes {
- proguard.initWith(buildTypes.debug)
- proguard {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'config.pro'
- }
- }
-
- dexOptions {
- incremental false
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9c0f279..0000000
--- a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-import java.lang.reflect.Method;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private Main mainActivity;
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mainActivity = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(mainActivity);
- mTextView = (TextView) mainActivity.findViewById(R.id.dateText);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- public void testNonObfuscatedMethod1() {
- // check we can call the method since it shouldn't be obfuscated.
- mainActivity.setUpTextView1();
-
- // then test we can actually find the lib method
- String className = "com.android.tests.basic.StringGetter";
- String methodName = "getString";
-
- searchMethod(className, methodName, true /*shouldExist*/);
- }
-
- public void testNonObfuscatedMethod2() {
- // check we can call the method since it shouldn't be obfuscated.
- mainActivity.setUpTextView2();
-
- // then test we cannot find the lib method since it should be
- // obfuscated by the app.
- String className = "com.android.tests.basic.StringGetter";
- String methodName = "getString2";
-
- searchMethod(className, methodName, false /*shouldExist*/);
- }
-
- /**
- * use reflection to get a method that should be obfuscated
- */
- public void testConsumerProguardRules() {
- String className = "com.android.tests.basic.StringGetter";
- String methodName = "getStringInternal";
-
- searchMethod(className, methodName, false /*shouldExist*/);
- }
-
- public void testLib2ObfuscatedClass() {
- // in this case the whole class has been obfuscated.
- String className = "com.android.tests.basic.StringProvider";
- try {
- Class<?> theClass = Class.forName(className);
- fail("Found " + className);
- } catch (ClassNotFoundException e) {
- // expected
- }
- }
-
- private void searchMethod(String className, String methodName, boolean shouldExist) {
- try {
- Class<?> theClass = Class.forName(className);
- Method method = theClass.getDeclaredMethod(methodName, int.class);
- if (!shouldExist) {
- fail("Found " + className + "." + methodName);
- }
- } catch (ClassNotFoundException e) {
- fail("Did not find " + className);
- } catch (NoSuchMethodException e) {
- if (shouldExist) {
- fail("Did not find " + className + "." + methodName);
- }
- }
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/build.gradle b/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/build.gradle
deleted file mode 100644
index 75cb138..0000000
--- a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-apply plugin: 'com.android.library'
-
-dependencies {
- compile project(':lib2')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- proguardFile 'config.pro'
- consumerProguardFiles 'consumerRules.pro'
- }
-
- buildTypes {
- debug {
- minifyEnabled true
- }
- release {
- minifyEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java b/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
deleted file mode 100644
index 0cbf626..0000000
--- a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.android.tests.basic;
-
-public class StringGetter{
-
- /**
- * Public method that will not get obfuscated
- */
- public static String getString(int foo) {
- return getStringInternal(foo);
- }
-
- /**
- * Public method that will get obfuscated by the app project.
- */
- public static String getString2(int foo) {
- return getStringInternal(foo);
- }
-
- /**
- * method that will get obfuscated by the library.
- */
- public static String getStringInternal(int foo) {
- return Integer.toString(foo);
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/build.gradle b/base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/build.gradle
deleted file mode 100644
index 6155f8f..0000000
--- a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/build.gradle
+++ /dev/null
@@ -1,23 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- proguardFile 'config.pro'
- }
-
- buildTypes {
- debug {
- minifyEnabled true
- }
- release {
- minifyEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/build.gradle b/base/build-system/integration-test/test-projects/libProguardConsumerFiles/build.gradle
deleted file mode 100644
index 1be2c19..0000000
--- a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- proguardFile 'config.pro'
- consumerProguardFiles 'A.txt'
- }
-
- buildTypes {
- debug {
- }
-
- release {
- minifyEnabled true
- consumerProguardFiles 'B.txt', 'C.txt'
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libTestDep/build.gradle b/base/build-system/integration-test/test-projects/libTestDep/build.gradle
deleted file mode 100644
index 53d7b4e..0000000
--- a/base/build-system/integration-test/test-projects/libTestDep/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.library'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.google.code.findbugs:jsr305:1.3.9'
- compile 'com.google.guava:guava:15.0'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/build.gradle b/base/build-system/integration-test/test-projects/libsTest/app/build.gradle
deleted file mode 100644
index 6dacc34..0000000
--- a/base/build-system/integration-test/test-projects/libsTest/app/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-//
-// A basic Android application split over a library and a main project.
-//
-dependencies {
- compile project(':lib1')
- compile project(':lib2b')
- compile project(':libapp')
-}
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/build.gradle b/base/build-system/integration-test/test-projects/libsTest/lib1/build.gradle
deleted file mode 100644
index cf71ca7..0000000
--- a/base/build-system/integration-test/test-projects/libsTest/lib1/build.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-apply plugin: 'com.android.library'
-
-dependencies {
- compile project(':lib2')
-}
-
-android {
- resourcePrefix 'lib1_'
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 14
- targetSdkVersion 15
- }
-}
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/build.gradle b/base/build-system/integration-test/test-projects/libsTest/lib2/build.gradle
deleted file mode 100644
index 10fec96..0000000
--- a/base/build-system/integration-test/test-projects/libsTest/lib2/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- resourcePrefix 'lib2_'
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 8
- }
-}
-
-dependencies {
- compile 'com.google.android.gms:play-services:3.1.36'
-}
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/build.gradle b/base/build-system/integration-test/test-projects/libsTest/lib2b/build.gradle
deleted file mode 100644
index 2e12c8a..0000000
--- a/base/build-system/integration-test/test-projects/libsTest/lib2b/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- resourcePrefix 'lib2b_'
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/build.gradle b/base/build-system/integration-test/test-projects/libsTest/libapp/build.gradle
deleted file mode 100644
index 124865a..0000000
--- a/base/build-system/integration-test/test-projects/libsTest/libapp/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- resourcePrefix 'libapp_'
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/build.gradle b/base/build-system/integration-test/test-projects/localAarTest/app/build.gradle
deleted file mode 100644
index 6bf2b92..0000000
--- a/base/build-system/integration-test/test-projects/localAarTest/app/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-//
-// A basic Android application split over a library and a main project.
-//
-dependencies {
- compile project(':lib1')
-}
diff --git a/base/build-system/integration-test/test-projects/localAarTest/lib1/build.gradle b/base/build-system/integration-test/test-projects/localAarTest/lib1/build.gradle
deleted file mode 100644
index 9cd5437..0000000
--- a/base/build-system/integration-test/test-projects/localAarTest/lib1/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-configurations.create("default")
-artifacts.add("default", file('lib1.aar'))
-
-/* uncomment to generate lib1.aar
-
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 14
- targetSdkVersion 15
- }
-}
-*/
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/localJars/app/build.gradle b/base/build-system/integration-test/test-projects/localJars/app/build.gradle
deleted file mode 100644
index 8a5de9a..0000000
--- a/base/build-system/integration-test/test-projects/localJars/app/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- packagingOptions {
- exclude 'META-INF/exclude.txt'
- exclude 'META-INF/LICENSE'
- }
-}
-
-dependencies {
- compile project(':library')
-}
diff --git a/base/build-system/integration-test/test-projects/localJars/baseLibrary/build.gradle b/base/build-system/integration-test/test-projects/localJars/baseLibrary/build.gradle
deleted file mode 100644
index c8a1436..0000000
--- a/base/build-system/integration-test/test-projects/localJars/baseLibrary/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- packagingOptions {
- exclude 'META-INF/exclude.txt'
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: '*.jar')
- compile 'com.google.guava:guava:15.0'
-}
diff --git a/base/build-system/integration-test/test-projects/localJars/library/build.gradle b/base/build-system/integration-test/test-projects/localJars/library/build.gradle
deleted file mode 100644
index c2c721e..0000000
--- a/base/build-system/integration-test/test-projects/localJars/library/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-dependencies {
- compile project(':baseLibrary')
-}
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/build.gradle b/base/build-system/integration-test/test-projects/mavenLocal/app/build.gradle
deleted file mode 100644
index 3796a27..0000000
--- a/base/build-system/integration-test/test-projects/mavenLocal/app/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-apply plugin: 'maven'
-
-repositories {
- mavenLocal()
-}
-
-dependencies {
- compile 'com.example.android.multiproject:lib:1.0'
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/baseLibrary/build.gradle b/base/build-system/integration-test/test-projects/mavenLocal/baseLibrary/build.gradle
deleted file mode 100644
index 9f3de61..0000000
--- a/base/build-system/integration-test/test-projects/mavenLocal/baseLibrary/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.library'
-apply plugin: 'maven'
-
-repositories {
- mavenLocal()
-}
-
-dependencies {
- compile 'com.example.android.multiproject:util:1.0'
- releaseCompile 'com.google.guava:guava:15.0'
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-group = 'com.example.android.multiproject'
-archivesBaseName = 'baseLib'
-version = '1.0'
-
-uploadArchives {
- repositories {
- mavenInstaller()
- }
-}
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/library/build.gradle b/base/build-system/integration-test/test-projects/mavenLocal/library/build.gradle
deleted file mode 100644
index c91967c..0000000
--- a/base/build-system/integration-test/test-projects/mavenLocal/library/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.library'
-apply plugin: 'maven'
-
-repositories {
- mavenLocal()
-}
-
-dependencies {
- compile 'com.example.android.multiproject:baseLib:1.0'
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-group = 'com.example.android.multiproject'
-archivesBaseName = 'lib'
-version = '2.0'
-
-uploadArchives {
- repositories {
- mavenInstaller()
- }
-}
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/build.gradle b/base/build-system/integration-test/test-projects/maxSdkVersion/build.gradle
deleted file mode 100644
index d55f394..0000000
--- a/base/build-system/integration-test/test-projects/maxSdkVersion/build.gradle
+++ /dev/null
@@ -1,52 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
- debugCompile 'com.android.support:support-v13:13.0.0'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- testBuildType "debug"
-
- signingConfigs {
- myConfig {
- storeFile file("debug.keystore")
- storePassword "android"
- keyAlias "androiddebugkey"
- keyPassword "android"
- }
- }
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- maxSdkVersion 19
- }
-
- productFlavors {
- f1 {
- maxSdkVersion 21
- applicationId = "com.android.tests.maxsdkversion.f1"
- }
- f2 {
- applicationId = "com.android.tests.maxsdkversion.f2"
- }
- }
-
- buildTypes {
- debug {
- applicationIdSuffix ".debug"
- signingConfig signingConfigs.myConfig
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/migrated/build.gradle b/base/build-system/integration-test/test-projects/migrated/build.gradle
deleted file mode 100644
index ce93250..0000000
--- a/base/build-system/integration-test/test-projects/migrated/build.gradle
+++ /dev/null
@@ -1,53 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- sourceSets {
- main {
- manifest {
- // there's only ever one file so srcFile replaces it.
- srcFile 'AndroidManifest.xml'
- }
- java {
- // writing:
- // srcDir 'src'
- // would *add* to the default folder so we use a different syntax
- srcDirs = ['src']
- exclude 'some/unwanted/packageName/**'
- }
- res {
- srcDirs = ['res']
- }
- assets {
- srcDirs = ['assets']
- }
- resources {
- srcDirs = ['src']
- }
- aidl {
- srcDirs = ['src']
- }
- renderscript {
- srcDirs = ['src']
- }
- }
-
- // this moves src/androidTest to tests so all folders follow:
- // tests/java, tests/res, tests/assets, ...
- // This is a *reset* so it replaces the default paths
- androidTest.setRoot('tests')
-
- // Could also be done with:
- //main.manifest.srcFile 'AndroidManifest.xml'
- //main.java.srcDir 'src'
- //main.res.srcDir 'res'
- //main.assets.srcDir 'assets'
- //main.resources.srcDir 'src'
- //androidTest.java.srcDir 'tests/src'
- }
-}
diff --git a/base/build-system/integration-test/test-projects/minify/build.gradle b/base/build-system/integration-test/test-projects/minify/build.gradle
deleted file mode 100644
index 99000d0..0000000
--- a/base/build-system/integration-test/test-projects/minify/build.gradle
+++ /dev/null
@@ -1,50 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-apply from: "../commonLocalRepo.gradle"
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- testBuildType rootProject.ext.useJack ? "debug" : "minified"
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-
- buildTypes {
- minified.initWith(buildTypes.debug)
- minified {
- minifyEnabled true
- useJack rootProject.ext.useJack
- proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard-rules.pro"
- testProguardFile "test-proguard-rules.pro"
- testCoverageEnabled true
- }
- }
-
- dexOptions {
- incremental false
- }
-
- lintOptions {
- abortOnError !rootProject.ext.useJack
- }
-
- // Included in both hamcrest-core and hamcrest-library.
- packagingOptions.exclude 'LICENSE.txt'
-}
-
-dependencies {
- testCompile 'junit:junit:4.12'
-
- // This library references java.beans classes that are not part of Android,
- // so ProGuard can't find references and fails without the rule from
- // test-proguard-rules.pro.
- androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
-}
diff --git a/base/build-system/integration-test/test-projects/minifyLib/app/build.gradle b/base/build-system/integration-test/test-projects/minifyLib/app/build.gradle
deleted file mode 100644
index 3d2ec91..0000000
--- a/base/build-system/integration-test/test-projects/minifyLib/app/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-apply plugin: 'com.android.application'
-
-dependencies {
- compile project(':lib')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- testBuildType rootProject.ext.useJack ? "debug" : "minified"
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-
- buildTypes {
- minified.initWith(buildTypes.debug)
- minified {
- minifyEnabled true
- useJack rootProject.ext.useJack
- proguardFile getDefaultProguardFile('proguard-android.txt')
- }
- }
-
- dexOptions {
- incremental false
- }
-}
diff --git a/base/build-system/integration-test/test-projects/minifyLib/lib/build.gradle b/base/build-system/integration-test/test-projects/minifyLib/lib/build.gradle
deleted file mode 100644
index 6f194df..0000000
--- a/base/build-system/integration-test/test-projects/minifyLib/lib/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- consumerProguardFiles 'config.pro'
- }
-}
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/build.gradle b/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/build.gradle
deleted file mode 100644
index 20b0991..0000000
--- a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-apply plugin: 'com.android.application'
-
-dependencies {
- compile project(':lib')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- signingConfigs {
- myConfig {
- storeFile file("debug.keystore")
- storePassword "android"
- keyAlias "androiddebugkey"
- keyPassword "android"
- }
- }
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- useJack rootProject.ext.useJack
- }
-
- buildTypes {
- release {
- minifyEnabled true
- signingConfig signingConfigs.myConfig
- proguardFile getDefaultProguardFile('proguard-android.txt')
- }
- }
-
- dexOptions {
- incremental false
- }
-}
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/debug.keystore b/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/debug.keystore
deleted file mode 100644
index 389278e..0000000
Binary files a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/debug.keystore and /dev/null differ
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/build.gradle b/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/build.gradle
deleted file mode 100644
index 6f194df..0000000
--- a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- consumerProguardFiles 'config.pro'
- }
-}
diff --git a/base/build-system/integration-test/test-projects/multiDex/build.gradle b/base/build-system/integration-test/test-projects/multiDex/build.gradle
deleted file mode 100644
index fae775b..0000000
--- a/base/build-system/integration-test/test-projects/multiDex/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- buildConfigField "String", "FOO", "\"foo\""
- multiDexEnabled = true
- useJack project.ext.useJack
- }
-
- productFlavors {
- ics {
- minSdkVersion 14
- }
- lollipop {
- minSdkVersion 21
- }
- }
-
- buildTypes {
- debug {
- buildConfigField "String", "FOO", "\"bar\""
- resValue "string", "foo", "foo2"
- }
- proguard {
- minifyEnabled true
- proguardFile file('proguard-android.txt')
- }
- }
-
- lintOptions {
- abortOnError !rootProject.ext.useJack
- }
-}
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/build.gradle b/base/build-system/integration-test/test-projects/multiDexWithLib/app/build.gradle
deleted file mode 100644
index cfe0a1b..0000000
--- a/base/build-system/integration-test/test-projects/multiDexWithLib/app/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- buildConfigField "String", "FOO", "\"foo\""
-
- multiDexEnabled = true
- useJack rootProject.ext.useJack
- }
-
- productFlavors {
- ics {
- minSdkVersion 14
- }
- lollipop {
- minSdkVersion 21
- }
- }
-
- buildTypes {
- debug {
- buildConfigField "String", "FOO", "\"bar\""
- resValue "string", "foo", "foo2"
- }
- }
-}
-
-dependencies {
- compile project(':lib')
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 7712fbf..0000000
--- a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
-
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/build.gradle b/base/build-system/integration-test/test-projects/multiDexWithLib/lib/build.gradle
deleted file mode 100644
index 64c2d78..0000000
--- a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/build.gradle
+++ /dev/null
@@ -1,22 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- useJack rootProject.ext.useJack
- minSdkVersion 4
- }
-
- buildTypes {
- debug {
- // needed for the test app.
- multiDexEnabled = true
- }
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: '*.jar')
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/build.gradle b/base/build-system/integration-test/test-projects/multiproject/app/build.gradle
deleted file mode 100644
index d84d88a..0000000
--- a/base/build-system/integration-test/test-projects/multiproject/app/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 8
- }
-}
-
-dependencies {
- compile project(':library')
-}
diff --git a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/build.gradle b/base/build-system/integration-test/test-projects/multiproject/baseLibrary/build.gradle
deleted file mode 100644
index 00841ce..0000000
--- a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 8
- }
-}
-
-dependencies {
- compile project(':util')
-}
diff --git a/base/build-system/integration-test/test-projects/multiproject/library/build.gradle b/base/build-system/integration-test/test-projects/multiproject/library/build.gradle
deleted file mode 100644
index 6be1cbe..0000000
--- a/base/build-system/integration-test/test-projects/multiproject/library/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 8
- }
-}
-
-dependencies {
- compile project(':baseLibrary')
- compile project(':util')
-}
diff --git a/base/build-system/integration-test/test-projects/multires/build.gradle b/base/build-system/integration-test/test-projects/multires/build.gradle
deleted file mode 100644
index 72aa72a..0000000
--- a/base/build-system/integration-test/test-projects/multires/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- sourceSets {
- main {
- res {
- srcDirs 'src/main/res1', 'src/main/res2'
- }
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/app/build.gradle b/base/build-system/integration-test/test-projects/ndkJniLib/app/build.gradle
deleted file mode 100644
index f7ab694..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniLib/app/build.gradle
+++ /dev/null
@@ -1,55 +0,0 @@
-apply plugin: 'com.android.application'
-
-import com.android.build.OutputFile;
-
-dependencies {
- compile project(':lib')
-}
-
-// map for the version code for ABIs.
-// x86 is more important because x86 devices also support arm.
-// Same for mips
-ext.versionCodes = ["armeabi-v7a":1, "mips":2, "x86":3, "all":0]
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- // This actual the app version code. Giving ourselves 100,000 values [0, 99999]
- defaultConfig.versionCode = 123
-
- productFlavors {
- gingerbread {
- minSdkVersion 10
- versionCode = 1
- }
- icecreamSandwich {
- minSdkVersion 14
- versionCode = 2
- }
- }
-
- splits {
- abi {
- enable = true
- universalApk = true
- exclude "x86_64", "mips64", "arm64-v8a", "armeabi"
- }
- }
-
- // make per-variant version code
- applicationVariants.all { variant ->
- // get the version code for the flavor
- def apiVersion = variant.productFlavors.get(0).versionCode
-
- // assign a composite version code for each output, based on the flavor above
- // and the density component.
- variant.outputs.each { output ->
- // get the key for the abi component
- def key = output.getFilter(OutputFile.ABI) == null ? "all" : output.getFilter(OutputFile.ABI)
-
- // set the versionCode on the output.
- output.versionCodeOverride = apiVersion * 1000000 + project.ext.versionCodes.get(key) * 100000 + defaultConfig.versionCode
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/lib/build.gradle b/base/build-system/integration-test/test-projects/ndkJniLib/lib/build.gradle
deleted file mode 100644
index 9ebc2a2..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniLib/lib/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- ndk {
- moduleName "hello-jni"
- }
- }
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/app/build.gradle b/base/build-system/integration-test/test-projects/ndkJniLib2/app/build.gradle
deleted file mode 100644
index edd3b6b..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniLib2/app/build.gradle
+++ /dev/null
@@ -1,55 +0,0 @@
-apply plugin: 'com.android.application'
-
-import com.android.build.OutputFile;
-
-dependencies {
- compile project(':lib')
-}
-
-// map for the version code for ABIs.
-// x86 is more important because x86 devices also support arm.
-// Same for mips
-ext.versionCodes = ["armeabi-v7a":1, "mips":2, "x86":3, "all":0]
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- // This actual the app version code. Giving ourselves 100,000 values [0, 99999]
- defaultConfig.versionCode = 123
-
- productFlavors {
- gingerbread {
- minSdkVersion 10
- versionCode = 1
- }
- icecreamSandwich {
- minSdkVersion 14
- versionCode = 2
- }
- }
-
- splits {
- abi {
- enable = true
- universalApk = true
- exclude "x86_64", "mips64", "arm64-v8a", "armeabi"
- }
- }
-
- // make per-variant version code
- applicationVariants.all { variant ->
- // get the version code for the flavor
- def apiVersion = variant.productFlavors.get(0).versionCode
-
- // assign a composite version code for each output, based on the flavor above
- // and the density component.
- variant.outputs.each { output ->
- // get the key for the abi component
- def key = output.getFilter(OutputFile.ABI) == null ? "all" : output.getFilter(OutputFile.ABI)
-
- // set the versionCode on the output.
- output.versionCodeOverride = apiVersion * 1000000 + project.ext.versionCodes.get(key) * 100000 + defaultConfig.versionCode
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/app/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/ndkJniLib2/app/src/main/AndroidManifest.xml
deleted file mode 100644
index aae7f79..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniLib2/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.hellojni.app"
- android:versionCode="1"
- android:versionName="1.0">
-
- <uses-sdk android:minSdkVersion="3" />
-
- <application android:label="@string/app_name">
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/build.gradle b/base/build-system/integration-test/test-projects/ndkJniLib2/lib/build.gradle
deleted file mode 100644
index 9e66cb0..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.model.library'
-
-model {
- android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.ext.buildToolsVersion
- }
-
- android.ndk {
- moduleName = "hello-jni"
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/AndroidManifest.xml
deleted file mode 100644
index ff4f566..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.hellojni.lib">
-
- <uses-sdk android:minSdkVersion="3" />
- <application>
- <activity android:name=".HelloJni"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/build.gradle b/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/build.gradle
deleted file mode 100644
index 66f0cfe..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/build.gradle
+++ /dev/null
@@ -1,30 +0,0 @@
-apply plugin: 'com.android.application'
-
-dependencies {
- compile project(':lib')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion rootProject.ext.buildToolsVersion
- generatePureSplits true
-
- // This actual the app version code. Giving ourselves 100,000 values [0, 99999]
- defaultConfig.versionCode = 123
-
- productFlavors {
- free {
- minSdkVersion 21
- }
- paid {
- minSdkVersion 21
- }
- }
-
- splits {
- abi {
- enable = true
- exclude "x86_64", "mips64", "arm64-v8a", "armeabi"
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/build.gradle b/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/build.gradle
deleted file mode 100644
index 9ebc2a2..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- ndk {
- moduleName "hello-jni"
- }
- }
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/jni/hello-jni.c b/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/jni/hello-jni.c
deleted file mode 100644
index 4ee252f..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/jni/hello-jni.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2009 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 <string.h>
-#include <jni.h>
-
-/* This is a trivial JNI example where we use a native method
- * to return a new VM String. See the corresponding Java source
- * file located at:
- *
- * apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
- */
-jstring
-Java_com_example_hellojni_lib_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
-{
-#if defined(__arm__)
- #if defined(__ARM_ARCH_7A__)
- #if defined(__ARM_NEON__)
- #define ABI "armeabi-v7a with NEON"
- #else
- #define ABI "armeabi-v7a"
- #endif
- #else
- #define ABI "armeabi"
- #endif
-#elif defined(__i386__)
- #define ABI "x86"
-#elif defined(__mips__)
- #define ABI "mips"
-#else
- #define ABI "unknown"
-#endif
-
- return (*env)->NewStringUTF(env, "Hello from JNI ! My ABI is " ABI ".");
-}
-
-jstring
-Java_com_example_hellojni_lib_HelloJni_jniNameFromJNI(JNIEnv* env, jobject thiz)
-{
-#if defined(__arm__)
- #if defined(__ARM_ARCH_7A__)
- #if defined(__ARM_NEON__)
- #define ABI "armeabi-v7a with NEON"
- #else
- #define ABI "armeabi-v7a"
- #endif
- #else
- #define ABI "armeabi"
- #endif
-#elif defined(__i386__)
- #define ABI "x86"
-#elif defined(__mips__)
- #define ABI "mips"
-#else
- #define ABI "unknown"
-#endif
-
- return (*env)->NewStringUTF(env, ABI);
-}
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/settings.gradle b/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/settings.gradle
deleted file mode 100644
index 7f37a58..0000000
--- a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include 'app', 'lib'
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/build.gradle b/base/build-system/integration-test/test-projects/ndkLibPrebuilts/build.gradle
deleted file mode 100644
index 6eca0a5..0000000
--- a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
deleted file mode 100644
index feadc72..0000000
--- a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.example.hellojni.lib;
-
-import android.test.ActivityInstrumentationTestCase;
-
-/**
- * This is a simple framework for a test of an Application. See
- * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
- * how to write and extend Application tests.
- * <p/>
- * To run this test, you can type:
- * adb shell am instrument -w \
- * -e class com.example.hellojni.HelloJniTest \
- * com.example.hellojni.tests/android.test.InstrumentationTestRunner
- */
-public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
-
- public HelloJniTest() {
- super("com.example.hellojni", HelloJni.class);
- }
-
-
- public void testJniName() {
- final HelloJni a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
-
- assertFalse("unknown".equals(a.jniNameFromJNI()));
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/res/values/strings.xml b/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/res/values/strings.xml
deleted file mode 100644
index c526073..0000000
--- a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">HelloJni</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/ndkPrebuilts/build.gradle b/base/build-system/integration-test/test-projects/ndkPrebuilts/build.gradle
deleted file mode 100644
index 0156525..0000000
--- a/base/build-system/integration-test/test-projects/ndkPrebuilts/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- productFlavors {
- x86 {
- ndk {
- abiFilter "x86"
- }
- }
- arm {
- ndk {
- abiFilters "armeabi-v7a", "armeabi"
- }
- }
- mips {
- ndk {
- abiFilter "mips"
- }
- }
- }
-
-}
diff --git a/base/build-system/integration-test/test-projects/ndkPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/base/build-system/integration-test/test-projects/ndkPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
deleted file mode 100644
index feadc72..0000000
--- a/base/build-system/integration-test/test-projects/ndkPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.example.hellojni.lib;
-
-import android.test.ActivityInstrumentationTestCase;
-
-/**
- * This is a simple framework for a test of an Application. See
- * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
- * how to write and extend Application tests.
- * <p/>
- * To run this test, you can type:
- * adb shell am instrument -w \
- * -e class com.example.hellojni.HelloJniTest \
- * com.example.hellojni.tests/android.test.InstrumentationTestRunner
- */
-public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
-
- public HelloJniTest() {
- super("com.example.hellojni", HelloJni.class);
- }
-
-
- public void testJniName() {
- final HelloJni a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
-
- assertFalse("unknown".equals(a.jniNameFromJNI()));
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java b/base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
deleted file mode 100644
index ff943d4..0000000
--- a/base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2009 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.example.hellojni.lib;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.TextView;
-
-
-public class HelloJni extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- /* Create a TextView and set its content.
- * the text is retrieved by calling a native
- * function.
- */
- TextView tv = new TextView(this);
- tv.setText( stringFromJNI() );
- setContentView(tv);
- }
-
- /* A native method that is implemented by the
- * 'hello-jni' native library, which is packaged
- * with this application.
- */
- public native String stringFromJNI();
-
- public native String jniNameFromJNI();
-
- /* this is used to load the 'hello-jni' library on application
- * startup. The library has already been unpacked into
- * /data/data/com.example.hellojni/lib/libhello-jni.so at
- * installation time by the package manager.
- */
- static {
- System.loadLibrary("hello-jni");
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/res/values/strings.xml b/base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/res/values/strings.xml
deleted file mode 100644
index c526073..0000000
--- a/base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">HelloJni</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/build.gradle b/base/build-system/integration-test/test-projects/ndkRsHelloCompute/build.gradle
deleted file mode 100644
index 0af0955..0000000
--- a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- renderscriptNdkMode true
- ndk {
- moduleName "libhellocomputendk"
- stl "stlport_shared"
- }
-
- }
-
- buildTypes.debug.jniDebuggable true
-
- productFlavors {
- x86 {
- ndk {
- abiFilter "x86"
- }
- }
- arm {
- ndk {
- abiFilter "armeabi-v7a"
- }
- }
- mips {
- ndk {
- abiFilter "mips"
- }
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/build.gradle b/base/build-system/integration-test/test-projects/ndkSanAngeles/build.gradle
deleted file mode 100644
index a69ffef..0000000
--- a/base/build-system/integration-test/test-projects/ndkSanAngeles/build.gradle
+++ /dev/null
@@ -1,45 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 8
- ndk {
- moduleName "sanangeles"
- cFlags "-DANDROID_NDK -DDISABLE_IMPORTGL"
- ldLibs "GLESv1_CM", "dl", "log"
- stl "stlport_static"
- jobs 4
- }
-
- // This actual the app version code. Giving ourselves 1,000,000 values
- versionCode = 123
-
- }
-
- buildTypes.debug.jniDebuggable true
-
- splits {
- abi {
- enable true
- reset()
- include 'x86', 'armeabi-v7a', 'mips'
- }
- }
-}
-
-// map for the version code. x86/mips must be higher than arm due to binary
-// code conversion libraries
-ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]
-
-android.applicationVariants.all { variant ->
- // assign different version code for each output
- variant.outputs.each { output ->
- output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/build.gradle b/base/build-system/integration-test/test-projects/ndkSanAngeles2/build.gradle
deleted file mode 100644
index bb0bb79..0000000
--- a/base/build-system/integration-test/test-projects/ndkSanAngeles2/build.gradle
+++ /dev/null
@@ -1,51 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = 19
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig.with {
- // This actual the app version code. Giving ourselves 1,000,000 values
- versionCode = 123
- }
- }
-
- android.ndk {
- moduleName = "sanangeles"
- CFlags += "-DDISABLE_IMPORTGL"
- ldLibs += "GLESv1_CM"
- ldLibs += "dl"
- ldLibs += "log"
- stl = "stlport_static"
- toolchain = "clang"
- }
-
- android.productFlavors {
- create("x86") {
- ndk.abiFilters += "x86"
- // this is the flavor part of the version code.
- // It must be higher than the arm one for devices supporting
- // both, as x86 is preferred.
- versionCode = 3
- }
- create("arm") {
- ndk.abiFilters += "armeabi-v7a"
- versionCode = 2
- }
- create("mips") {
- ndk.abiFilters += "mips"
- versionCode = 1
- }
- create("fat") {
- // fat binary, lowest version code to be
- // the last option
- versionCode = 0
- }
- }
-
-}
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/build.gradle b/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/build.gradle
deleted file mode 100644
index 665c95f..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.ext.buildToolsVersion
- ndkLib ":lib"
-}
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
deleted file mode 100644
index feadc72..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.example.hellojni.lib;
-
-import android.test.ActivityInstrumentationTestCase;
-
-/**
- * This is a simple framework for a test of an Application. See
- * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
- * how to write and extend Application tests.
- * <p/>
- * To run this test, you can type:
- * adb shell am instrument -w \
- * -e class com.example.hellojni.HelloJniTest \
- * com.example.hellojni.tests/android.test.InstrumentationTestRunner
- */
-public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
-
- public HelloJniTest() {
- super("com.example.hellojni", HelloJni.class);
- }
-
-
- public void testJniName() {
- final HelloJni a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
-
- assertFalse("unknown".equals(a.jniNameFromJNI()));
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 8c5dabd..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.hellojni.lib">
-
- <uses-sdk android:minSdkVersion="3" />
- <application>
- <activity android:name=".HelloJni"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/java/com/example/hellojni/lib/HelloJni.java b/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/java/com/example/hellojni/lib/HelloJni.java
deleted file mode 100644
index ff943d4..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/java/com/example/hellojni/lib/HelloJni.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2009 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.example.hellojni.lib;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.TextView;
-
-
-public class HelloJni extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- /* Create a TextView and set its content.
- * the text is retrieved by calling a native
- * function.
- */
- TextView tv = new TextView(this);
- tv.setText( stringFromJNI() );
- setContentView(tv);
- }
-
- /* A native method that is implemented by the
- * 'hello-jni' native library, which is packaged
- * with this application.
- */
- public native String stringFromJNI();
-
- public native String jniNameFromJNI();
-
- /* this is used to load the 'hello-jni' library on application
- * startup. The library has already been unpacked into
- * /data/data/com.example.hellojni/lib/libhello-jni.so at
- * installation time by the package manager.
- */
- static {
- System.loadLibrary("hello-jni");
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/res/values/strings.xml b/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/res/values/strings.xml
deleted file mode 100644
index c526073..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">HelloJni</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/build.gradle b/base/build-system/integration-test/test-projects/ndkStandaloneSo/build.gradle
deleted file mode 100644
index ff172d7..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/build.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/lib/build.gradle b/base/build-system/integration-test/test-projects/ndkStandaloneSo/lib/build.gradle
deleted file mode 100644
index 219efc9..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/lib/build.gradle
+++ /dev/null
@@ -1,8 +0,0 @@
-apply plugin: 'com.android.native'
-
-model {
- android.ndk {
- compileSdkVersion = 19
- moduleName = "hello-jni"
- }
-}
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/lib/src/main/c/hello-jni.c b/base/build-system/integration-test/test-projects/ndkStandaloneSo/lib/src/main/c/hello-jni.c
deleted file mode 100644
index 4ee252f..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/lib/src/main/c/hello-jni.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2009 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 <string.h>
-#include <jni.h>
-
-/* This is a trivial JNI example where we use a native method
- * to return a new VM String. See the corresponding Java source
- * file located at:
- *
- * apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
- */
-jstring
-Java_com_example_hellojni_lib_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
-{
-#if defined(__arm__)
- #if defined(__ARM_ARCH_7A__)
- #if defined(__ARM_NEON__)
- #define ABI "armeabi-v7a with NEON"
- #else
- #define ABI "armeabi-v7a"
- #endif
- #else
- #define ABI "armeabi"
- #endif
-#elif defined(__i386__)
- #define ABI "x86"
-#elif defined(__mips__)
- #define ABI "mips"
-#else
- #define ABI "unknown"
-#endif
-
- return (*env)->NewStringUTF(env, "Hello from JNI ! My ABI is " ABI ".");
-}
-
-jstring
-Java_com_example_hellojni_lib_HelloJni_jniNameFromJNI(JNIEnv* env, jobject thiz)
-{
-#if defined(__arm__)
- #if defined(__ARM_ARCH_7A__)
- #if defined(__ARM_NEON__)
- #define ABI "armeabi-v7a with NEON"
- #else
- #define ABI "armeabi-v7a"
- #endif
- #else
- #define ABI "armeabi"
- #endif
-#elif defined(__i386__)
- #define ABI "x86"
-#elif defined(__mips__)
- #define ABI "mips"
-#else
- #define ABI "unknown"
-#endif
-
- return (*env)->NewStringUTF(env, ABI);
-}
diff --git a/base/build-system/integration-test/test-projects/ndkStandaloneSo/settings.gradle b/base/build-system/integration-test/test-projects/ndkStandaloneSo/settings.gradle
deleted file mode 100644
index d164157..0000000
--- a/base/build-system/integration-test/test-projects/ndkStandaloneSo/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include 'app', 'lib'
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/build.gradle b/base/build-system/integration-test/test-projects/ndkVariants/build.gradle
deleted file mode 100644
index 535e44b..0000000
--- a/base/build-system/integration-test/test-projects/ndkVariants/build.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
-
-// This test ensures each variant compiles the correct source set with the appropriate NDK settings.
-
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion = 19
- buildToolsVersion = rootProject.ext.buildToolsVersion
- }
-
- android.ndk {
- moduleName = "simple-jni"
-
- // TODO: Include a way to set include directories the DSL.
- cppFlags += "-I$rootDir/src/include".toString()
- stl = "stlport_static"
- }
-
- android.productFlavors {
- create("free")
- create("premium")
- }
-
- // Set binary specific C++ flags.
- components.android {
- binaries.afterEach { binary ->
- binary.mergedNdkConfig.cppFlags.add("-DVARIANT=\"" + binary.name + "\"")
- }
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/noPngCrunch/build.gradle b/base/build-system/integration-test/test-projects/noPngCrunch/build.gradle
deleted file mode 100644
index 6a0039e..0000000
--- a/base/build-system/integration-test/test-projects/noPngCrunch/build.gradle
+++ /dev/null
@@ -1,22 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 21
- buildToolsVersion rootProject.ext.buildToolsVersion
-
- aaptOptions {
- cruncherEnabled = false
- }
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-}
diff --git a/base/build-system/integration-test/test-projects/noPreDex/build.gradle b/base/build-system/integration-test/test-projects/noPreDex/build.gradle
deleted file mode 100644
index 27b4b51..0000000
--- a/base/build-system/integration-test/test-projects/noPreDex/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- dexOptions.preDexLibraries = false
-}
diff --git a/base/build-system/integration-test/test-projects/noPreDex/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/noPreDex/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/noPreDex/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/overlay1/build.gradle b/base/build-system/integration-test/test-projects/overlay1/build.gradle
deleted file mode 100644
index f51dffb..0000000
--- a/base/build-system/integration-test/test-projects/overlay1/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/overlay2/build.gradle b/base/build-system/integration-test/test-projects/overlay2/build.gradle
deleted file mode 100644
index 0d1efc6..0000000
--- a/base/build-system/integration-test/test-projects/overlay2/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- productFlavors {
- one {}
- }
-}
diff --git a/base/build-system/integration-test/test-projects/overlay3/build.gradle b/base/build-system/integration-test/test-projects/overlay3/build.gradle
deleted file mode 100644
index 7f54e97..0000000
--- a/base/build-system/integration-test/test-projects/overlay3/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- flavorDimensions "pricing", "releaseType"
-
- sourceSets {
- beta.setRoot('movedSrc/beta')
- free.setRoot('movedSrc/free')
- debug.setRoot('movedSrc/debug')
- freeBeta.setRoot('movedSrc/freeBeta')
- freeBetaDebug.setRoot('movedSrc/freeBetaDebug')
- freeNormal.setRoot('movedSrc/freeNormal')
- }
-
- productFlavors {
-
- beta {
- flavorDimension "releaseType"
- }
-
- normal {
- flavorDimension "releaseType"
- }
-
- free {
- flavorDimension "pricing"
- }
-
- paid {
- flavorDimension "pricing"
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/build.gradle b/base/build-system/integration-test/test-projects/packagingOptions/build.gradle
deleted file mode 100644
index 55c0861..0000000
--- a/base/build-system/integration-test/test-projects/packagingOptions/build.gradle
+++ /dev/null
@@ -1,22 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- packagingOptions {
- exclude 'excluded.txt'
- pickFirst 'first_pick.txt'
- merge 'merge.txt'
-
- pickFirst 'lib/x86/libdummy.so'
- }
-}
-
-dependencies {
- compile files('jar1.jar')
- compile files('jar2.jar')
-}
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/build.gradle b/base/build-system/integration-test/test-projects/parentLibsTest/app/application/build.gradle
deleted file mode 100644
index 76ee266..0000000
--- a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-//
-// A basic Android application split over a library and a main project.
-//
-dependencies {
- compile project(':..:lib1')
-}
diff --git a/base/build-system/integration-test/test-projects/pkgOverride/build.gradle b/base/build-system/integration-test/test-projects/pkgOverride/build.gradle
deleted file mode 100644
index f70daab..0000000
--- a/base/build-system/integration-test/test-projects/pkgOverride/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- applicationId "com.android.tests.basic.foo"
- }
-}
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/build.gradle b/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/build.gradle
deleted file mode 100644
index 84470da..0000000
--- a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/build.gradle
+++ /dev/null
@@ -1,35 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.0"
-
- defaultConfig {
- applicationId "com.example.manifest_merger_example"
- manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example"]
- minSdkVersion 15
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
- productFlavors {
- flavor {
- applicationId "com.example.manifest_merger_example.flavor"
- manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example.flavor"]
- minSdkVersion 15
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
- }
- buildTypes {
- release {
- minifyEnabled false
- }
- }
-}
-
-dependencies {
- compile project(':examplelibrary')
- compile fileTree(dir: 'libs', include: ['*.jar'])
-}
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/build.gradle b/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/build.gradle
deleted file mode 100644
index fbf7f29..0000000
--- a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.0"
-
- defaultConfig {
- minSdkVersion 15
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
-}
-
-android.testVariants.all {
- it.mergedFlavor.manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example.flavor"]
-}
-
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/build.gradle b/base/build-system/integration-test/test-projects/privateResources/app/build.gradle
deleted file mode 100644
index ef870ae..0000000
--- a/base/build-system/integration-test/test-projects/privateResources/app/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.1"
-
- defaultConfig {
- applicationId "com.android.tools.test.publicsymbols"
- minSdkVersion 21
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
-}
-
-dependencies {
- compile project(':mylibrary')
- compile project(':mylibrary2')
-}
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary/build.gradle b/base/build-system/integration-test/test-projects/privateResources/mylibrary/build.gradle
deleted file mode 100644
index d31e68f..0000000
--- a/base/build-system/integration-test/test-projects/privateResources/mylibrary/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.1"
- resourcePrefix 'mylib_'
-
- defaultConfig {
- minSdkVersion 21
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
-}
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/layout/main_layout.xml b/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/layout/main_layout.xml
deleted file mode 100644
index 0844dda..0000000
--- a/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/layout/main_layout.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/shared_name"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/public.xml b/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/public.xml
deleted file mode 100644
index 0e702a9..0000000
--- a/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/public.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<resources>
- <public name="mylib_app_name" type="string"/>
- <public name="mylib_public_string" type="string"/>
- <public name="shared_name" type="string"/>
- <public name="shared_name" type="id"/>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/strings.xml b/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/strings.xml
deleted file mode 100644
index 606f0b8..0000000
--- a/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<resources>
- <string name="mylib_app_name">My Library</string>
- <string name="mylib_public_string">Public String in Library</string>
- <string name="mylib_private_string">Private String in Library</string>
- <string name="shared_name">String that has the same name as an ID</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary2/build.gradle b/base/build-system/integration-test/test-projects/privateResources/mylibrary2/build.gradle
deleted file mode 100644
index 5d8c74c..0000000
--- a/base/build-system/integration-test/test-projects/privateResources/mylibrary2/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.1"
- resourcePrefix 'mylib2_'
-
- defaultConfig {
- minSdkVersion 21
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
-}
diff --git a/base/build-system/integration-test/test-projects/projectWithClassifierDep/build.gradle b/base/build-system/integration-test/test-projects/projectWithClassifierDep/build.gradle
deleted file mode 100644
index b8e17ab..0000000
--- a/base/build-system/integration-test/test-projects/projectWithClassifierDep/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 22
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-repositories {
- maven { url 'repo' }
-}
-
-dependencies {
- compile group: 'com.foo', name: 'sample', version: '1.0'
- androidTestCompile group: 'com.foo', name: 'sample', version: '1.0', classifier: 'testlib'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/projectWithIvyDependency/build.gradle b/base/build-system/integration-test/test-projects/projectWithIvyDependency/build.gradle
deleted file mode 100644
index 8bed8f2..0000000
--- a/base/build-system/integration-test/test-projects/projectWithIvyDependency/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 22
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-repositories {
- ivy {
- url "ivy-repo/"
- layout "pattern", {
- ivy '[organisation]/[module]/[revision]/[module]-[revision].ivy'
- artifact '[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]'
- m2compatible = true
- }
- }
-}
-
-dependencies {
- compile 'com.foo:sample:1.0'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/build.gradle b/base/build-system/integration-test/test-projects/projectWithModules/app/build.gradle
deleted file mode 100644
index 8a8d000..0000000
--- a/base/build-system/integration-test/test-projects/projectWithModules/app/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 8
- }
-}
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/layout/main.xml b/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/layout/main.xml
deleted file mode 100644
index 0ab6c6c..0000000
--- a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/layout/main.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/button_send"
- android:id="@+id/foo"
- android:onClick="sendMessage" />
-</LinearLayout>
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library/build.gradle b/base/build-system/integration-test/test-projects/projectWithModules/library/build.gradle
deleted file mode 100644
index ded9657..0000000
--- a/base/build-system/integration-test/test-projects/projectWithModules/library/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library2/build.gradle b/base/build-system/integration-test/test-projects/projectWithModules/library2/build.gradle
deleted file mode 100644
index ded9657..0000000
--- a/base/build-system/integration-test/test-projects/projectWithModules/library2/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/pseudolocalized/build.gradle b/base/build-system/integration-test/test-projects/pseudolocalized/build.gradle
deleted file mode 100644
index caefb66..0000000
--- a/base/build-system/integration-test/test-projects/pseudolocalized/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = System.env.CUSTOM_BUILDTOOLS != null ? System.env.CUSTOM_BUILDTOOLS : '21.0.0'
-
- testBuildType "debug"
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-
- buildTypes {
- debug {
- pseudoLocalesEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/renamedApk/build.gradle b/base/build-system/integration-test/test-projects/renamedApk/build.gradle
deleted file mode 100644
index c66546b..0000000
--- a/base/build-system/integration-test/test-projects/renamedApk/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- buildTypes.debug {
- zipAlignEnabled true
- }
-}
-
-android.applicationVariants.all { variant ->
- variant.outputs[0].outputFile = file("$project.buildDir/${variant.name}.apk")
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/renderscript/build.gradle b/base/build-system/integration-test/test-projects/renderscript/build.gradle
deleted file mode 100644
index ca5a3ae..0000000
--- a/base/build-system/integration-test/test-projects/renderscript/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 17
- renderscriptTargetApi = 17
- }
-}
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/build.gradle b/base/build-system/integration-test/test-projects/renderscriptInLib/app/build.gradle
deleted file mode 100644
index da87e09..0000000
--- a/base/build-system/integration-test/test-projects/renderscriptInLib/app/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 15
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- renderscriptTargetApi = 11
- }
-}
-
-dependencies {
- compile project(':lib')
-}
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/lib/build.gradle b/base/build-system/integration-test/test-projects/renderscriptInLib/lib/build.gradle
deleted file mode 100644
index a3e4fba..0000000
--- a/base/build-system/integration-test/test-projects/renderscriptInLib/lib/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 15
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/build.gradle b/base/build-system/integration-test/test-projects/renderscriptMultiSrc/build.gradle
deleted file mode 100644
index 8214199..0000000
--- a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 15
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- renderscriptTargetApi = 11
- }
-}
diff --git a/base/build-system/integration-test/test-projects/repo/app/build.gradle b/base/build-system/integration-test/test-projects/repo/app/build.gradle
deleted file mode 100644
index 68a63da..0000000
--- a/base/build-system/integration-test/test-projects/repo/app/build.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-apply plugin: 'maven'
-
-repositories {
- maven { url '../testrepo' }
-}
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.example.android.multiproject:lib:1.0'
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
diff --git a/base/build-system/integration-test/test-projects/repo/baseLibrary/build.gradle b/base/build-system/integration-test/test-projects/repo/baseLibrary/build.gradle
deleted file mode 100644
index cdd594c..0000000
--- a/base/build-system/integration-test/test-projects/repo/baseLibrary/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.library'
-apply plugin: 'maven'
-
-repositories {
- maven { url '../testrepo' }
-}
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.example.android.multiproject:util:1.0'
- releaseCompile 'com.google.guava:guava:17.0'
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-group = 'com.example.android.multiproject'
-archivesBaseName = 'baseLib'
-version = '1.0'
-
-uploadArchives {
- repositories {
- mavenDeployer {
- repository(url: uri("../testrepo"))
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/repo/baseLibrary/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/repo/baseLibrary/src/main/AndroidManifest.xml
deleted file mode 100644
index 54d079c..0000000
--- a/base/build-system/integration-test/test-projects/repo/baseLibrary/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.multiproject.library.base">
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/repo/library/build.gradle b/base/build-system/integration-test/test-projects/repo/library/build.gradle
deleted file mode 100644
index fa6e86e..0000000
--- a/base/build-system/integration-test/test-projects/repo/library/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.library'
-apply plugin: 'maven'
-
-repositories {
- maven { url '../testrepo' }
-}
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.example.android.multiproject:baseLib:1.0'
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-}
-
-group = 'com.example.android.multiproject'
-archivesBaseName = 'lib'
-version = '1.0'
-
-uploadArchives {
- repositories {
- mavenDeployer {
- repository(url: uri("../testrepo"))
- pom.groupId = 'com.example.android.multiproject'
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/build.gradle b/base/build-system/integration-test/test-projects/rsSupportMode/build.gradle
deleted file mode 100644
index fab564f..0000000
--- a/base/build-system/integration-test/test-projects/rsSupportMode/build.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 8
- targetSdkVersion 16
- renderscriptTargetApi 18
- renderscriptSupportModeEnabled true
- }
-
- productFlavors {
- x86 {
- ndk {
- abiFilter "x86"
- }
- }
- arm {
- ndk {
- abiFilter "armeabi-v7a"
- }
- }
- mips {
- ndk {
- abiFilter "mips"
- }
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/build.gradle b/base/build-system/integration-test/test-projects/sameNamedLibs/app/build.gradle
deleted file mode 100644
index c579fbd..0000000
--- a/base/build-system/integration-test/test-projects/sameNamedLibs/app/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
-
-//
-// A basic Android application split over a library and a main project.
-//
-dependencies {
- compile project(':lib1:libs')
- compile project(':lib2b:libs')
- compile project(':libapp:libs')
-}
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
deleted file mode 100644
index 273c8d1..0000000
--- a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2008 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.tests.libstest.app;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-/**
- * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
- * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
- * launched and finished before and after each test. This also extends
- * {@link android.test.InstrumentationTestCase}, which provides
- * access to methods for sending events to the target activity, such as key and
- * touch events. See {@link #sendKeys}.
- *
- * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
- * are heavier weight functional tests available for end to end testing of your
- * user interface. When run via a {@link android.test.InstrumentationTestRunner},
- * the necessary {@link android.app.Instrumentation} will be injected for you to
- * user via {@link #getInstrumentation} in your tests.
- *
- * See {@link com.example.android.apis.AllTests} for documentation on running
- * all tests and individual tests in this application.
- */
-public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
-
- private TextView mAppTextView1;
- private TextView mAppTextView2;
- private TextView mLib1TextView1;
- private TextView mLib1TextView2;
- private TextView mLib2TextView1;
- private TextView mLib2TextView2;
- private TextView mLib2bTextView1;
- private TextView mLib2bTextView2;
- private TextView mLibappTextView1;
- private TextView mLibappTextView2;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
- */
- public MainActivityTest() {
- super(MainActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final MainActivity a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
-
- mAppTextView1 = (TextView) a.findViewById(R.id.app_text1);
- mAppTextView2 = (TextView) a.findViewById(R.id.app_text1);
- mLib1TextView1 = (TextView) a.findViewById(R.id.lib1_text1);
- mLib1TextView2 = (TextView) a.findViewById(R.id.lib1_text2);
- mLib2TextView1 = (TextView) a.findViewById(R.id.lib2_text1);
- mLib2TextView2 = (TextView) a.findViewById(R.id.lib2_text2);
- mLib2bTextView1 = (TextView) a.findViewById(R.id.lib2b_text1);
- mLib2bTextView2 = (TextView) a.findViewById(R.id.lib2b_text2);
- mLibappTextView1 = (TextView) a.findViewById(R.id.libapp_text1);
- mLibappTextView2 = (TextView) a.findViewById(R.id.libapp_text2);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mAppTextView1);
- assertNotNull(mAppTextView2);
- assertNotNull(mLib1TextView1);
- assertNotNull(mLib1TextView2);
- assertNotNull(mLib2TextView1);
- assertNotNull(mLib2TextView2);
- assertNotNull(mLib2bTextView1);
- assertNotNull(mLib2bTextView2);
- assertNotNull(mLibappTextView1);
- assertNotNull(mLibappTextView2);
- }
-
- @MediumTest
- public void testAndroidStrings() {
- assertEquals("SUCCESS-APP", mAppTextView1.getText().toString());
- assertEquals("SUCCESS-LIB1", mLib1TextView1.getText().toString());
- assertEquals("SUCCESS-LIB2", mLib2TextView1.getText().toString());
- assertEquals("SUCCESS-LIB2b", mLib2bTextView1.getText().toString());
- assertEquals("SUCCESS-LIBAPP", mLibappTextView1.getText().toString());
- }
-
- @MediumTest
- public void testJavaStrings() {
- assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
- assertEquals("SUCCESS-LIB1", mLib1TextView2.getText().toString());
- assertEquals("SUCCESS-LIB2", mLib2TextView2.getText().toString());
- assertEquals("SUCCESS-LIB2b", mLib2bTextView2.getText().toString());
- assertEquals("SUCCESS-LIBAPP", mLibappTextView2.getText().toString());
- }
-}
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/build.gradle b/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/build.gradle
deleted file mode 100644
index 1415fc7..0000000
--- a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.library'
-
-dependencies {
- compile project(':lib2:libs')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 14
- targetSdkVersion 15
- }
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/build.gradle b/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/build.gradle
deleted file mode 100644
index 8e82aff..0000000
--- a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/build.gradle b/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/build.gradle
deleted file mode 100644
index 8e82aff..0000000
--- a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/build.gradle b/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/build.gradle
deleted file mode 100644
index 8e82aff..0000000
--- a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/build.gradle b/base/build-system/integration-test/test-projects/separateTestModule/app/build.gradle
deleted file mode 100644
index 5a46ce3..0000000
--- a/base/build-system/integration-test/test-projects/separateTestModule/app/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- publishNonDefault true
-
- defaultConfig {
- minSdkVersion 8
- }
-}
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/test/build.gradle b/base/build-system/integration-test/test-projects/separateTestModule/test/build.gradle
deleted file mode 100644
index 4e79322..0000000
--- a/base/build-system/integration-test/test-projects/separateTestModule/test/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'com.android.test'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- targetProjectPath ':app'
- targetVariant 'debug'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/build.gradle b/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/build.gradle
deleted file mode 100644
index 3686af6..0000000
--- a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- publishNonDefault true
-
- defaultConfig {
- minSdkVersion 16
- }
-
- buildTypes {
- minified.initWith(buildTypes.debug)
- minified {
- minifyEnabled true
- useJack rootProject.ext.useJack
- proguardFiles getDefaultProguardFile('proguard-android.txt')
- }
- }
-}
-
-dependencies {
- compile project(':lib')
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/build.gradle b/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/build.gradle
deleted file mode 100644
index af57e6f..0000000
--- a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply plugin: 'com.android.library'
-
-dependencies {
- compile project(':jar')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-}
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/build.gradle b/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/build.gradle
deleted file mode 100644
index 4edf874..0000000
--- a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'com.android.test'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- targetProjectPath ':app'
- targetVariant 'minified'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/build.gradle b/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/build.gradle
deleted file mode 100644
index 79e4473..0000000
--- a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/build.gradle
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.application'
-
-apply from: "../../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.google.code.findbugs:jsr305:1.3.9'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- publishNonDefault true
-
- defaultConfig {
- minSdkVersion 8
- }
-
- buildTypes {
- minified.initWith(buildTypes.debug)
- minified {
- minifyEnabled true
- useJack rootProject.ext.useJack
- proguardFiles getDefaultProguardFile('proguard-android.txt')
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/build.gradle b/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/build.gradle
deleted file mode 100644
index 5b31aa0..0000000
--- a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.test'
-
-apply from: "../../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- targetProjectPath ':app'
- targetVariant 'minified'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/build.gradle b/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/build.gradle
deleted file mode 100644
index 5509d41..0000000
--- a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/build.gradle
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.application'
-
-apply from: "../../commonLocalRepo.gradle"
-
-dependencies {
- compile 'com.google.code.findbugs:jsr305:1.3.9'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- publishNonDefault true
-
- defaultConfig {
- minSdkVersion 8
- }
-
- buildTypes {
- minified.initWith(buildTypes.debug)
- minified {
- minifyEnabled true
- useJack rootProject.ext.useJack
- proguardFiles 'proguard.txt'
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/proguard.txt b/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/proguard.txt
deleted file mode 100644
index 30ccaf5..0000000
--- a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/proguard.txt
+++ /dev/null
@@ -1,2 +0,0 @@
--dontobfuscate
-
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/build.gradle b/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/build.gradle
deleted file mode 100644
index 5b31aa0..0000000
--- a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.test'
-
-apply from: "../../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- targetProjectPath ':app'
- targetVariant 'minified'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/shrink/abisplits/build.gradle b/base/build-system/integration-test/test-projects/shrink/abisplits/build.gradle
deleted file mode 100644
index 20888c6..0000000
--- a/base/build-system/integration-test/test-projects/shrink/abisplits/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- minSdkVersion 16
- targetSdkVersion 20
- }
-
- buildTypes {
- release {
- shrinkResources true
- minifyEnabled true
- }
- }
-
- splits {
- abi {
- enable true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/shrink/build.gradle b/base/build-system/integration-test/test-projects/shrink/build.gradle
deleted file mode 100644
index be57b40..0000000
--- a/base/build-system/integration-test/test-projects/shrink/build.gradle
+++ /dev/null
@@ -1,43 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion '19.1.0'
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-
- buildTypes {
- debug {
- shrinkResources false
- minifyEnabled false
- }
- proguardNoShrink {
- shrinkResources false
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- release {
- shrinkResources true
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-
- dexOptions {
- incremental false
- }
-}
-
-dependencies {
- compile project(':lib')
- compile 'com.android.support:support-annotations:+'
- compile 'com.android.support:support-v4:+'
-}
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/build.gradle b/base/build-system/integration-test/test-projects/shrink/keep/build.gradle
deleted file mode 100644
index d4aeb9c..0000000
--- a/base/build-system/integration-test/test-projects/shrink/keep/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- minSdkVersion 16
- targetSdkVersion 20
- }
-
- buildTypes {
- release {
- shrinkResources true
- minifyEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/shrink/lib/build.gradle b/base/build-system/integration-test/test-projects/shrink/lib/build.gradle
deleted file mode 100644
index 7423a91..0000000
--- a/base/build-system/integration-test/test-projects/shrink/lib/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- minSdkVersion 16
- targetSdkVersion 20
- }
-}
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/RootActivity.java b/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/RootActivity.java
deleted file mode 100644
index 317b24d..0000000
--- a/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/RootActivity.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.android.tests.shrink;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.ElementType.TYPE;
-
-import android.app.Activity;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.view.Menu;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-public class RootActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.used1);
- ResourceReferences.referenceResources(this);
- System.out.println(R.layout.used7);
- AnnotationInflation.createView(this, ScreenType1.class, null);
- AnnotationInflation.createView(this, ScreenType2.class, null);
-
- for (int id : layout_ids) {
- System.out.println(id);
- }
-
- dynamicResourceNames(1);
- }
-
- public void dynamicResourceNames(int version) {
- // Normal string concatenation:
- String versionNumber = String.valueOf(version);
- int res = getResources().getIdentifier("prefix_used_" + version, "layout",
- getPackageName());
- System.out.println(res);
-
- String name = String.format("prefix_used_%1d", version + 1);
- res = getResources().getIdentifier(name, "layout", getPackageName());
- System.out.println(res);
-
- name = String.format("prefix_%1$s_suffix", version + 2);
- res = getResources().getIdentifier(name, "layout", getPackageName());
- System.out.println(res);
- }
-
- public void unusedMethod() {
- Drawable drawable = getResources().getDrawable(R.drawable.unused10);
- System.out.println(drawable);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.used13, menu);
- return true;
- }
-
-
- @Layout(R.layout.used17)
- @Retention(RetentionPolicy.RUNTIME)
- @Target({METHOD, PARAMETER, TYPE, LOCAL_VARIABLE, FIELD})
- public @interface Indirect {
- int[] value();
- }
-
- @Layout(R.layout.used16)
- private static class ScreenType1 {
- }
-
- @Indirect(5)
- @Layouts({R.layout.used18,R.layout.used19})
- private static class ScreenType2 {
- }
-
- private static final int[] layout_ids = { R.layout.used20 };
-}
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/raw/keep.xml b/base/build-system/integration-test/test-projects/shrink/src/main/res/raw/keep.xml
deleted file mode 100644
index 5f2da52..0000000
--- a/base/build-system/integration-test/test-projects/shrink/src/main/res/raw/keep.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources xmlns:tools="http://schemas.android.com/tools"
- tools:keep="@layout/l_used*_c, at layout/l_used_a, at layout/l_used_b*"/>
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/build.gradle b/base/build-system/integration-test/test-projects/shrink/webview/build.gradle
deleted file mode 100644
index d4aeb9c..0000000
--- a/base/build-system/integration-test/test-projects/shrink/webview/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- minSdkVersion 16
- targetSdkVersion 20
- }
-
- buildTypes {
- release {
- shrinkResources true
- minifyEnabled true
- }
- }
-}
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/java/com/android/tests/shrink/webview/WebViewActivity.java b/base/build-system/integration-test/test-projects/shrink/webview/src/main/java/com/android/tests/shrink/webview/WebViewActivity.java
deleted file mode 100644
index e3d64ba..0000000
--- a/base/build-system/integration-test/test-projects/shrink/webview/src/main/java/com/android/tests/shrink/webview/WebViewActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.android.tests.shrink.webview;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.webkit.WebView;
-
-public class WebViewActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.webview);
-
- WebView webview = (WebView) findViewById(R.id.webview);
-
- // Should mark R.drawable.used1 as used
- webview.loadUrl("file:///android_res/drawable/used1.xml");
-
- String html = "<html><img src=\"used2.xml\"/></html>";
- // This call should make me whitelist all strings
- webview.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html",
- "utf-8", null);
-
- // Should mark R.drawable.used1 as used
- webview.loadUrl("file:///android_res/raw/used_index.html");
-
- }
-}
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/main/build.gradle b/base/build-system/integration-test/test-projects/simpleMicroApp/main/build.gradle
deleted file mode 100644
index 9133000..0000000
--- a/base/build-system/integration-test/test-projects/simpleMicroApp/main/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
-}
-
-dependencies {
- wearApp project(':wear')
-}
-
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/AndroidManifest.xml
deleted file mode 100644
index a34d937..0000000
--- a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
- <application android:label="@string/app_name" android:icon="@drawable/icon">
- <activity android:name=".Main"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/build.gradle b/base/build-system/integration-test/test-projects/simpleMicroApp/wear/build.gradle
deleted file mode 100644
index fda3687..0000000
--- a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 42
- versionName "default"
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:13.0.0'
-}
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index 9f4b2de..0000000
--- a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.tests.basic;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private TextView mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- mTextView = (TextView) a.findViewById(R.id.text);
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- }
-
- @MediumTest
- public void testBuildConfig() {
- assertEquals("bar", BuildConfig.FOO);
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/AndroidManifest.xml
deleted file mode 100644
index a34d937..0000000
--- a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
- <application android:label="@string/app_name" android:icon="@drawable/icon">
- <activity android:name=".Main"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/build.gradle b/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/build.gradle
deleted file mode 100644
index e092c90..0000000
--- a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/build.gradle
+++ /dev/null
@@ -1,23 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- publishNonDefault true
- generatePureSplits true
-
- defaultConfig {
- versionCode 12
- minSdkVersion 21
- targetSdkVersion 21
- }
-
- splits {
- density {
- enable true
- exclude "ldpi", "tvdpi", "xxxhdpi"
- }
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/build.gradle b/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/build.gradle
deleted file mode 100644
index 4e79322..0000000
--- a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'com.android.test'
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- targetProjectPath ':app'
- targetVariant 'debug'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/testDependency/build.gradle b/base/build-system/integration-test/test-projects/testDependency/build.gradle
deleted file mode 100644
index f5079ce..0000000
--- a/base/build-system/integration-test/test-projects/testDependency/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- versionCode 12
- versionName "2.0"
- minSdkVersion 16
- targetSdkVersion 16
- }
-}
-
-dependencies {
- compile 'com.google.guava:guava:17.0'
-
- // this is added by the test to control the version.
- //androidTestCompile 'com.google.guava:guava:x.y'
-}
diff --git a/base/build-system/integration-test/test-projects/testDependency/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/testDependency/src/main/AndroidManifest.xml
deleted file mode 100644
index a34d937..0000000
--- a/base/build-system/integration-test/test-projects/testDependency/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
- <application android:label="@string/app_name" android:icon="@drawable/icon">
- <activity android:name=".Main"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/testWithDep/build.gradle b/base/build-system/integration-test/test-projects/testWithDep/build.gradle
deleted file mode 100644
index 85c802c..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-dependencies {
- androidTestCompile 'com.google.guava:guava:17.0'
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java b/base/build-system/integration-test/test-projects/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java
deleted file mode 100644
index f55ccf3..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.android.tests.basic;
-
-import com.google.common.collect.ImmutableList;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.TextView;
-
-public class MainTest extends ActivityInstrumentationTestCase2<Main> {
-
- private ImmutableList<TextView> mTextView;
-
- /**
- * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
- */
- public MainTest() {
- super(Main.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- final Main a = getActivity();
- // ensure a valid handle to the activity has been returned
- assertNotNull(a);
- // Wrapped in an immutable list from guava, to check the dependency worked.
- mTextView = ImmutableList.of((TextView) a.findViewById(R.id.text))
- }
-
- @Override
- public void tearDown() throws Exception {
- }
-
- /**
- * The name 'test preconditions' is a convention to signal that if this
- * test doesn't pass, the test case was not set up properly and it might
- * explain any and all failures in other tests. This is not guaranteed
- * to run before other tests, as junit uses reflection to find the tests.
- */
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mTextView);
- assertEquals(1, mTextView.size());
- assertNotNull(mTextView.get(0));
- }
-}
-
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/main/AndroidManifest.xml b/base/build-system/integration-test/test-projects/testWithDep/src/main/AndroidManifest.xml
deleted file mode 100644
index 4f8d570..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.basic">
- <application android:label="@string/app_name" android:icon="@drawable/icon">
- <activity android:name=".Main"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- <uses-permission android:name="com.blah" />
-
- <permission-group android:name="foo.permission-group.COST_MONEY"
- android:label="@string/app_name"
- android:description="@string/app_name" />
-
- <permission android:name="foo.permission.SEND_SMS"
- android:permissionGroup="foo.permission-group.COST_MONEY"
- android:label="@string/app_name"
- android:description="@string/app_name" />
-
- <permission android:name="foo.blah.SEND_SMS"
- android:permissionGroup="foo.permission-group.COST_MONEY"
- android:label="@string/app_name"
- android:description="@string/app_name" />
-
-</manifest>
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/main/assets/notice.txt b/base/build-system/integration-test/test-projects/testWithDep/src/main/assets/notice.txt
deleted file mode 100644
index 33ff961..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/src/main/assets/notice.txt
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2005-2012, The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
-
- 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.
-
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/main/java/com/android/tests/basic/Main.java b/base/build-system/integration-test/test-projects/testWithDep/src/main/java/com/android/tests/basic/Main.java
deleted file mode 100644
index 2b0e698..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/src/main/java/com/android/tests/basic/Main.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.tests.basic;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class Main extends Activity
-{
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
-}
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/main/res/raw/notice.txt b/base/build-system/integration-test/test-projects/testWithDep/src/main/res/raw/notice.txt
deleted file mode 100644
index 33ff961..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/src/main/res/raw/notice.txt
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2005-2012, The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
-
- 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.
-
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/main/res/values/strings.xml b/base/build-system/integration-test/test-projects/testWithDep/src/main/res/values/strings.xml
deleted file mode 100644
index 60ea2d0..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">_Test-Basic</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/release/res/values/strings.xml b/base/build-system/integration-test/test-projects/testWithDep/src/release/res/values/strings.xml
deleted file mode 100644
index 532909c..0000000
--- a/base/build-system/integration-test/test-projects/testWithDep/src/release/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="app_name">_Test-Basic-Release</string>
-</resources>
diff --git a/base/build-system/integration-test/test-projects/tictactoe/app/build.gradle b/base/build-system/integration-test/test-projects/tictactoe/app/build.gradle
deleted file mode 100644
index 5043348..0000000
--- a/base/build-system/integration-test/test-projects/tictactoe/app/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'com.android.application'
-
-dependencies {
- compile project(':lib')
-}
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-}
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/build.gradle b/base/build-system/integration-test/test-projects/tictactoe/lib/build.gradle
deleted file mode 100644
index 1641170..0000000
--- a/base/build-system/integration-test/test-projects/tictactoe/lib/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 3
- }
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/unitTesting/build.gradle b/base/build-system/integration-test/test-projects/unitTesting/build.gradle
deleted file mode 100644
index 0ddab5d..0000000
--- a/base/build-system/integration-test/test-projects/unitTesting/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- testOptions {
- unitTests.all {
- systemProperty 'foo', 'bar'
- }
- }
-}
-
-dependencies {
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-core:1.9.5'
- testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
- testCompile 'commons-logging:commons-logging:1.1.1'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/build.gradle b/base/build-system/integration-test/test-projects/unitTestingBuildTypes/build.gradle
deleted file mode 100644
index 950757b..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- buildTypes {
- buildTypeWithResource
- }
-}
-
-dependencies {
- testCompile 'junit:junit:4.12'
- testCompile "org.mockito:mockito-core:1.9.5"
-
- testDebugCompile 'com.google.guava:guava:17.0'
-}
-
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/build.gradle b/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/build.gradle
deleted file mode 100644
index 66afaca..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.application'
-
-apply from: "../../commonLocalRepo.gradle"
-
-android {
- // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- testOptions {
- unitTests.all {
- systemProperty 'foo', 'bar'
- }
- }
-}
-
-dependencies {
- compile project(':lib')
-
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-core:1.9.5'
- testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
- testCompile 'commons-logging:commons-logging:1.1.1'
-}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/Foo.java b/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/Foo.java
deleted file mode 100644
index ff506f2..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/Foo.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests;
-
-import com.android.tests.lib.LibFoo;
-
-public class Foo {
- public String foo() {
- return "production code";
- }
-
- public String callLibFoo() {
- LibFoo libFoo = new LibFoo();
- return libFoo.foo();
- }
-}
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/build.gradle b/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/build.gradle
deleted file mode 100644
index 27abd09..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply plugin: 'com.android.library'
-
-apply from: "../../commonLocalRepo.gradle"
-
-android {
- // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- testOptions {
- unitTests.all {
- systemProperty 'foo', 'bar'
- }
- }
-}
-
-dependencies {
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-core:1.9.5'
- testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
- testCompile 'commons-logging:commons-logging:1.1.1'
-}
-
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/java/com/android/tests/lib/LibFoo.java b/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/java/com/android/tests/lib/LibFoo.java
deleted file mode 100644
index 3cdccde..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/java/com/android/tests/lib/LibFoo.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.lib;
-
-public class LibFoo {
- public String foo() {
- return "library code";
- }
-}
diff --git a/base/build-system/integration-test/test-projects/unitTestingDefaultValues/build.gradle b/base/build-system/integration-test/test-projects/unitTestingDefaultValues/build.gradle
deleted file mode 100644
index e73aec5..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingDefaultValues/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- testOptions {
- unitTests.returnDefaultValues = true
- }
-}
-
-dependencies {
- testCompile 'junit:junit:4.12'
- testCompile "org.mockito:mockito-core:1.9.5"
-}
-
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/build.gradle b/base/build-system/integration-test/test-projects/unitTestingFlavors/build.gradle
deleted file mode 100644
index 51546b2..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingFlavors/build.gradle
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- flavorDimensions "buildStatus", "testStatus"
-
- productFlavors {
- builds {
- flavorDimension "buildStatus"
- }
-
- doesntBuild {
- flavorDimension "buildStatus"
- }
-
- passes {
- flavorDimension "testStatus"
- }
-
- fails {
- flavorDimension "testStatus"
- }
- }
-}
-
-dependencies {
- testCompile 'junit:junit:4.12'
- testCompile "org.mockito:mockito-core:1.9.5"
-}
-
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/doesntBuild/java/com/android/tests/Broken.java b/base/build-system/integration-test/test-projects/unitTestingFlavors/src/doesntBuild/java/com/android/tests/Broken.java
deleted file mode 100644
index b6ad626..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/doesntBuild/java/com/android/tests/Broken.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests;
-
-public class Broken {
- this is broken
-}
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/build.gradle b/base/build-system/integration-test/test-projects/unitTestingLibraryModules/build.gradle
deleted file mode 100644
index 51a6314..0000000
--- a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/build.gradle
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.library'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
- compileSdkVersion 19
- buildToolsVersion = rootProject.buildToolsVersion
-
- testOptions {
- unitTests.all {
- systemProperty 'foo', 'bar'
- }
- }
-}
-
-dependencies {
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-core:1.9.5'
- testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
- testCompile 'commons-logging:commons-logging:1.1.1'
-}
-
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/build.gradle b/base/build-system/integration-test/test-projects/vectorDrawables/build.gradle
deleted file mode 100644
index 10a58e8..0000000
--- a/base/build-system/integration-test/test-projects/vectorDrawables/build.gradle
+++ /dev/null
@@ -1,23 +0,0 @@
-apply from: "../commonHeader.gradle"
-buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
-
-apply plugin: 'com.android.application'
-
-apply from: "../commonLocalRepo.gradle"
-
-android {
- compileSdkVersion 21
- buildToolsVersion = rootProject.buildToolsVersion
-
- defaultConfig {
- minSdkVersion 19
- }
-
- preprocessingOptions {
- densities = ["hdpi"]
- densities += "xhdpi"
- }
-
- // Don't modify files when merging.
- aaptOptions.cruncherEnabled = false
-}
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/gradle.properties b/base/build-system/integration-test/test-projects/vectorDrawables/gradle.properties
deleted file mode 100644
index bbf0dc8..0000000
--- a/base/build-system/integration-test/test-projects/vectorDrawables/gradle.properties
+++ /dev/null
@@ -1 +0,0 @@
-com.android.build.gradle.experimentalPreprocessResources=true
diff --git a/base/build-system/manifest-merger/manifest-merger-base.iml b/base/build-system/manifest-merger/manifest-merger-base.iml
deleted file mode 100644
index 6f94cd8..0000000
--- a/base/build-system/manifest-merger/manifest-merger-base.iml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="library" scope="TEST" name="mockito" level="project" />
- <orderEntry type="module" module-name="sdk-common-base" />
- <orderEntry type="library" name="gson" level="project" />
- <orderEntry type="module" module-name="sdklib-base" exported="" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java
deleted file mode 100644
index a194534..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.XmlNode.NodeKey;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.GuardedBy;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.google.common.collect.ImmutableMap;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Records all the actions taken by the merging tool.
- * <p>
- * Each action generates at least one {@link com.android.manifmerger.Actions.Record}
- * containing enough information to generate a machine or human readable report.
- * <p>
- *
- * The records are not organized in a temporal structure as the merging tool takes such decisions
- * but are keyed by xml elements and attributes. For each node (elements or attributes), a linked
- * list of actions that happened to the node is recorded to display all decisions that were made
- * for that particular node.
- * <p>
- *
- * This structure will permit displaying logs with co-located decisions records for each element,
- * for instance :
- * <pre>
- * activity:com.foo.bar.MyApp
- * Added from manifest.xml:31
- * Rejected from lib1_manifest.xml:65
- * </pre>
- *
- * <p>
- * Each record for a node (element or attribute) will contain the following metadata :
- * <p>
- *
- * <ul>
- * <li>{@link com.android.manifmerger.Actions.ActionType} to identify whether the action
- * applies to an attribute or an element.</li>
- * <li>{@link com.android.ide.common.blame.SourceFilePosition} to identify the source xml
- * location for the node.</li>
- * </ul>
- *
- * <p>
- * Elements will also contain:
- * <ul>
- * <li>Element name : a name composed of the element type and its key.</li>
- * <li>{@link NodeOperationType} the highest priority tool annotation justifying the merging
- * tool decision.</li>
- * </ul>
- *
- * <p>
- * While attributes will have:
- * <ul>
- * <li>element name</li>
- * <li>attribute name : the namespace aware xml name</li>
- * <li>{@link AttributeOperationType} the highest priority annotation justifying the merging
- * tool decision.</li>
- * </ul>
- */
-public class ActionRecorder {
-
- // defines all the records for the merging tool activity, indexed by element name+key.
- // iterator should be ordered by the key insertion order. This is not a concurrent map so we
- // will need to guard multi-threaded access when adding/removing elements.
- @GuardedBy("this")
- private final Map<NodeKey, Actions.DecisionTreeRecord> mRecords =
- new LinkedHashMap<NodeKey, Actions.DecisionTreeRecord>();
-
- /**
- * When the first xml file is loaded, there is nothing to merge with, however, each xml element
- * and attribute added to the initial merged file need to be recorded.
- *
- * @param xmlElement xml element added to the initial merged document.
- */
- void recordDefaultNodeAction(XmlElement xmlElement) {
- if (!mRecords.containsKey(xmlElement.getOriginalId())) {
- recordNodeAction(xmlElement, Actions.ActionType.ADDED);
- for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
- AttributeOperationType attributeOperation = xmlElement
- .getAttributeOperationType(xmlAttribute.getName());
- recordAttributeAction(
- xmlAttribute, Actions.ActionType.ADDED,
- attributeOperation);
- }
- for (XmlElement childNode : xmlElement.getMergeableElements()) {
- recordDefaultNodeAction(childNode);
- }
- }
- }
-
- /**
- * Record a node that was added due to an implicit presence in earlier SDK release but requires
- * an explicit declaration in the application targeted SDK.
- * @param xmlElement the implied element that was added to the resulting xml.
- * @param reason optional contextual information whey the implied element was added.
- */
- void recordImpliedNodeAction(XmlElement xmlElement, String reason) {
- NodeKey storageKey = xmlElement.getOriginalId();
- Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
- if (nodeDecisionTree == null) {
- nodeDecisionTree = new Actions.DecisionTreeRecord();
- mRecords.put(storageKey, nodeDecisionTree);
- }
- Actions.NodeRecord record = new Actions.NodeRecord(Actions.ActionType.IMPLIED,
- new SourceFilePosition(
- xmlElement.getDocument().getSourceFile(),
- xmlElement.getDocument().getRootNode().getPosition()),
- xmlElement.getOriginalId(),
- reason,
- xmlElement.getOperationType()
- );
- nodeDecisionTree.addNodeRecord(record);
- }
-
- /**
- * Record a node action taken by the merging tool.
- *
- * @param xmlElement the action's target xml element
- * @param actionType the action's type
- */
- synchronized void recordNodeAction(
- XmlElement xmlElement,
- Actions.ActionType actionType) {
- recordNodeAction(xmlElement, actionType, xmlElement);
- }
-
- /**
- * Record a node action taken by the merging tool.
- *
- * @param mergedElement the merged xml element
- * @param actionType the action's type
- * @param targetElement the action's target when the action is rejected or replaced, it
- * indicates what is the element being rejected or replaced.
- */
- synchronized void recordNodeAction(
- XmlElement mergedElement,
- Actions.ActionType actionType,
- XmlElement targetElement) {
-
- Actions.NodeRecord record = new Actions.NodeRecord(actionType,
- new SourceFilePosition(
- targetElement.getDocument().getSourceFile(),
- targetElement.getPosition()),
- targetElement.getOriginalId(),
- null, /* reason */
- mergedElement.getOperationType()
- );
- recordNodeAction(mergedElement, record);
- }
-
- /**
- * Records a {@link com.android.manifmerger.Actions.NodeRecord} action on a xml element.
- * @param mergedElement the target element of the action.
- * @param nodeRecord the record of the action.
- */
- synchronized void recordNodeAction(
- XmlElement mergedElement,
- Actions.NodeRecord nodeRecord) {
-
- NodeKey storageKey = mergedElement.getOriginalId();
- Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
- if (nodeDecisionTree == null) {
- nodeDecisionTree = new Actions.DecisionTreeRecord();
- mRecords.put(storageKey, nodeDecisionTree);
- }
- nodeDecisionTree.addNodeRecord(nodeRecord);
- }
-
- /**
- * Records an attribute action taken by the merging tool
- *
- * @param attribute the attribute in question.
- * @param actionType the action's type
- * @param attributeOperationType the original tool annotation leading to the merging tool
- * decision.
- */
- synchronized void recordAttributeAction(
- @NonNull XmlAttribute attribute,
- @NonNull Actions.ActionType actionType,
- @Nullable AttributeOperationType attributeOperationType) {
-
- recordAttributeAction(
- attribute, attribute.getPosition(), actionType, attributeOperationType);
- }
-
- /**
- * Records an attribute action taken by the merging tool
- *
- * @param attribute the attribute in question.
- * @param attributePosition the attribute's position.
- * @param actionType the action's type
- * @param attributeOperationType the original tool annotation leading to the merging tool
- * decision.
- */
- synchronized void recordAttributeAction(
- @NonNull XmlAttribute attribute,
- @NonNull SourcePosition attributePosition,
- @NonNull Actions.ActionType actionType,
- @Nullable AttributeOperationType attributeOperationType) {
-
- XmlElement originElement = attribute.getOwnerElement();
- Actions.AttributeRecord attributeRecord = new Actions.AttributeRecord(
- actionType,
- new SourceFilePosition(
- originElement.getDocument().getSourceFile(),
- attributePosition),
- attribute.getOriginalId(),
- null, /* reason */
- attributeOperationType
- );
- recordAttributeAction(attribute, attributeRecord);
- }
-
- /**
- * Record a {@link com.android.manifmerger.Actions.AttributeRecord} action for an attribute of
- * an xml element.
- * @param attribute the attribute in question.
- * @param attributeRecord the record of the action.
- */
- synchronized void recordAttributeAction(
- XmlAttribute attribute,
- Actions.AttributeRecord attributeRecord) {
-
- List<Actions.AttributeRecord> attributeRecords = getAttributeRecords(attribute);
- attributeRecords.add(attributeRecord);
- }
-
- /**
- * Records when a default value that should be merged was rejected due to a tools:replace
- * annotation.
- *
- * @param attribute the attribute which default value was ignored.
- * @param implicitAttributeOwner the element owning the implicit default value.
- */
- synchronized void recordImplicitRejection(
- @NonNull XmlAttribute attribute,
- @NonNull XmlElement implicitAttributeOwner) {
-
- List<Actions.AttributeRecord> attributeRecords = getAttributeRecords(attribute);
- Actions.AttributeRecord attributeRecord = new Actions.AttributeRecord(
- Actions.ActionType.REJECTED,
- new SourceFilePosition(
- implicitAttributeOwner.getDocument().getSourceFile(),
- implicitAttributeOwner.getPosition()),
- attribute.getOriginalId(),
- null, /* reason */
- AttributeOperationType.REPLACE
- );
- attributeRecords.add(attributeRecord);
- }
-
- /**
- * Returns the record for an attribute creation event. The attribute is "created" when it is
- * added for the first time into the resulting merged xml document.
- */
- @Nullable
- synchronized Actions.AttributeRecord getAttributeCreationRecord(XmlAttribute attribute) {
- for (Actions.AttributeRecord attributeRecord : getAttributeRecords(attribute)) {
- if (attributeRecord.getActionType() == Actions.ActionType.ADDED) {
- return attributeRecord;
- }
- }
- return null;
- }
-
- private List<Actions.AttributeRecord> getAttributeRecords(XmlAttribute attribute) {
- XmlElement originElement = attribute.getOwnerElement();
- NodeKey storageKey = originElement.getOriginalId();
- Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
- // by now the node should have been added for this element.
- assert (nodeDecisionTree != null);
- List<Actions.AttributeRecord> attributeRecords =
- nodeDecisionTree.mAttributeRecords.get(attribute.getName());
- if (attributeRecords == null) {
- attributeRecords = new ArrayList<Actions.AttributeRecord>();
- nodeDecisionTree.mAttributeRecords.put(attribute.getName(), attributeRecords);
- }
- return attributeRecords;
- }
-
- Actions build() {
- return new Actions(new ImmutableMap.Builder<NodeKey, Actions.DecisionTreeRecord>()
- .putAll(mRecords).build());
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java
deleted file mode 100644
index 79c8064..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.concurrency.Immutable;
-import com.android.ide.common.blame.MessageJsonSerializer;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.utils.ILogger;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.io.LineReader;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-
-import org.xml.sax.SAXException;
-
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Contains all actions taken during a merging invocation.
- */
- at Immutable
-public class Actions {
-
- // TODO: i18n
- @VisibleForTesting
- static final String HEADER = "-- Merging decision tree log ---\n";
-
- // defines all the records for the merging tool activity, indexed by element name+key.
- // iterator should be ordered by the key insertion order.
- private final Map<XmlNode.NodeKey, DecisionTreeRecord> mRecords;
-
- public Actions(Map<XmlNode.NodeKey, DecisionTreeRecord> records) {
- mRecords = records;
- }
-
- /**
- * Returns a {@link com.google.common.collect.ImmutableSet} of all the element's keys that have
- * at least one {@link NodeRecord}.
- */
- @NonNull
- public Set<XmlNode.NodeKey> getNodeKeys() {
- return mRecords.keySet();
- }
-
- /**
- * Returns an {@link ImmutableList} of {@link NodeRecord} for the element identified with the
- * passed key.
- */
- @NonNull
- public ImmutableList<NodeRecord> getNodeRecords(XmlNode.NodeKey key) {
- return mRecords.containsKey(key)
- ? mRecords.get(key).getNodeRecords()
- : ImmutableList.<NodeRecord>of();
- }
-
- /**
- * Returns a {@link ImmutableList} of all attributes names that have at least one record for
- * the element identified with the passed key.
- */
- @NonNull
- public ImmutableList<XmlNode.NodeName> getRecordedAttributeNames(XmlNode.NodeKey nodeKey) {
- DecisionTreeRecord decisionTreeRecord = mRecords.get(nodeKey);
- if (decisionTreeRecord == null) {
- return ImmutableList.of();
- }
- return decisionTreeRecord.getAttributesRecords().keySet().asList();
- }
-
- /**
- * Returns the {@link com.google.common.collect.ImmutableList} of {@link AttributeRecord} for
- * the attribute identified by attributeName of the element identified by elementKey.
- */
- @NonNull
- public ImmutableList<AttributeRecord> getAttributeRecords(XmlNode.NodeKey elementKey,
- XmlNode.NodeName attributeName) {
-
- DecisionTreeRecord decisionTreeRecord = mRecords.get(elementKey);
- if (decisionTreeRecord == null) {
- return ImmutableList.of();
- }
- return decisionTreeRecord.getAttributeRecords(attributeName);
- }
-
- /**
- * Initial dump of the merging tool actions, need to be refined and spec'ed out properly.
- * @param logger logger to log to at INFO level.
- */
- void log(ILogger logger) {
- logger.verbose(getLogs());
- }
-
- /**
- * Dump merging tool actions to a text file.
- * @param fileWriter the file to write all actions into.
- * @throws IOException
- */
- void log(FileWriter fileWriter) throws IOException {
- fileWriter.append(getLogs());
- }
-
- private String getLogs() {
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append(HEADER);
- for (Map.Entry<XmlNode.NodeKey, Actions.DecisionTreeRecord> record : mRecords.entrySet()) {
- stringBuilder.append(record.getKey()).append("\n");
- for (Actions.NodeRecord nodeRecord : record.getValue().getNodeRecords()) {
- nodeRecord.print(stringBuilder);
- stringBuilder.append('\n');
- }
- for (Map.Entry<XmlNode.NodeName, List<Actions.AttributeRecord>> attributeRecords :
- record.getValue().mAttributeRecords.entrySet()) {
- stringBuilder.append('\t').append(attributeRecords.getKey()).append('\n');
- for (Actions.AttributeRecord attributeRecord : attributeRecords.getValue()) {
- stringBuilder.append("\t\t");
- attributeRecord.print(stringBuilder);
- stringBuilder.append('\n');
- }
- }
- }
- return stringBuilder.toString();
- }
-
- /**
- * Defines all possible actions taken from the merging tool for an xml element or attribute.
- */
- enum ActionType {
- /**
- * The element was added into the resulting merged manifest.
- */
- ADDED,
- /**
- * The element was injected from the merger invocation parameters.
- */
- INJECTED,
- /**
- * The element was merged with another element into the resulting merged manifest.
- */
- MERGED,
- /**
- * The element was rejected.
- */
- REJECTED,
- /**
- * The implied element was added was added when importing a library that expected the
- * element to be present by default while targeted SDK requires its declaration.
- */
- IMPLIED,
- }
-
- /**
- * Defines an abstract record contain common metadata for elements and attributes actions.
- */
- public abstract static class Record {
-
- @NonNull protected final ActionType mActionType;
- @NonNull protected final SourceFilePosition mActionLocation;
- @NonNull protected final XmlNode.NodeKey mTargetId;
- @Nullable protected final String mReason;
-
- private Record(@NonNull ActionType actionType,
- @NonNull SourceFilePosition actionLocation,
- @NonNull XmlNode.NodeKey targetId,
- @Nullable String reason) {
- mActionType = Preconditions.checkNotNull(actionType);
- mActionLocation = Preconditions.checkNotNull(actionLocation);
- mTargetId = Preconditions.checkNotNull(targetId);
- mReason = reason;
- }
-
- public ActionType getActionType() {
- return mActionType;
- }
-
- public SourceFilePosition getActionLocation() {
- return mActionLocation;
- }
-
- public XmlNode.NodeKey getTargetId() {
- return mTargetId;
- }
-
- public void print(StringBuilder stringBuilder) {
- stringBuilder.append(mActionType)
- .append(" from ")
- .append(mActionLocation);
- if (mReason != null) {
- stringBuilder.append(" reason: ")
- .append(mReason);
- }
- }
- }
-
- /**
- * Defines a merging tool action for an xml element.
- */
- public static class NodeRecord extends Record {
-
- private final NodeOperationType mNodeOperationType;
-
- NodeRecord(@NonNull ActionType actionType,
- @NonNull SourceFilePosition actionLocation,
- @NonNull XmlNode.NodeKey targetId,
- @Nullable String reason,
- @NonNull NodeOperationType nodeOperationType) {
- super(actionType, actionLocation, targetId, reason);
- this.mNodeOperationType = Preconditions.checkNotNull(nodeOperationType);
- }
-
- @Override
- public String toString() {
- return "Id=" + mTargetId.toString() + " actionType=" + getActionType()
- + " location=" + getActionLocation()
- + " opType=" + mNodeOperationType;
- }
- }
-
- /**
- * Defines a merging tool action for an xml attribute
- */
- public static class AttributeRecord extends Record {
-
- // first in wins which should be fine, the first
- // operation type will be the highest priority one
- private final AttributeOperationType mOperationType;
-
- AttributeRecord(
- @NonNull ActionType actionType,
- @NonNull SourceFilePosition actionLocation,
- @NonNull XmlNode.NodeKey targetId,
- @Nullable String reason,
- @Nullable AttributeOperationType operationType) {
- super(actionType, actionLocation, targetId, reason);
- this.mOperationType = operationType;
- }
-
- @Nullable
- public AttributeOperationType getOperationType() {
- return mOperationType;
- }
-
- @Override
- public String toString() {
- return "Id=" + mTargetId + " actionType=" + getActionType()
- + " location=" + getActionLocation()
- + " opType=" + getOperationType();
- }
- }
-
- public String persist() throws IOException {
- GsonBuilder gson = new GsonBuilder().setPrettyPrinting();
- gson.enableComplexMapKeySerialization();
- MessageJsonSerializer.registerTypeAdapters(gson);
- return gson.create().toJson(this);
- }
-
- @Nullable
- public static Actions load(InputStream inputStream) throws IOException {
-
- return getGsonParser().fromJson(new InputStreamReader(inputStream), Actions.class);
- }
-
- private static class NodeNameDeserializer implements JsonDeserializer<XmlNode.NodeName> {
-
- @Override
- public XmlNode.NodeName deserialize(JsonElement json, Type typeOfT,
- JsonDeserializationContext context) throws JsonParseException {
- if (json.getAsJsonObject().get("mNamespaceURI") != null) {
- return context.deserialize(json, XmlNode.NamespaceAwareName.class);
- } else {
- return context.deserialize(json, XmlNode.Name.class);
- }
- }
- }
-
- @Nullable
- @SuppressWarnings("unchecked")
- public static Actions load(String xml) {
-
- return getGsonParser().fromJson(xml, Actions.class);
- }
-
- private static Gson getGsonParser() {
- GsonBuilder gsonBuilder = new GsonBuilder();
- gsonBuilder.enableComplexMapKeySerialization();
- gsonBuilder.registerTypeAdapter(XmlNode.NodeName.class, new NodeNameDeserializer());
- MessageJsonSerializer.registerTypeAdapters(gsonBuilder);
- return gsonBuilder.create();
- }
-
- public ImmutableMultimap<Integer, Record> getResultingSourceMapping(XmlDocument xmlDocument)
- throws ParserConfigurationException, SAXException, IOException {
-
- SourceFile inMemory = SourceFile.UNKNOWN;
-
- XmlDocument loadedWithLineNumbers = XmlLoader.load(
- xmlDocument.getSelectors(),
- xmlDocument.getSystemPropertyResolver(),
- inMemory,
- xmlDocument.prettyPrint(),
- XmlDocument.Type.MAIN,
- Optional.<String>absent() /* mainManifestPackageName */);
-
- ImmutableMultimap.Builder<Integer, Record> mappingBuilder = ImmutableMultimap.builder();
- for (XmlElement xmlElement : loadedWithLineNumbers.getRootNode().getMergeableElements()) {
- parse(xmlElement, mappingBuilder);
- }
- return mappingBuilder.build();
- }
-
- private void parse(XmlElement element,
- ImmutableMultimap.Builder<Integer, Record> mappings) {
- DecisionTreeRecord decisionTreeRecord = mRecords.get(element.getId());
- if (decisionTreeRecord != null) {
- Actions.NodeRecord nodeRecord = findNodeRecord(decisionTreeRecord);
- if (nodeRecord != null) {
- mappings.put(element.getPosition().getStartLine(), nodeRecord);
- }
- for (XmlAttribute xmlAttribute : element.getAttributes()) {
- Actions.AttributeRecord attributeRecord = findAttributeRecord(decisionTreeRecord,
- xmlAttribute);
- if (attributeRecord != null) {
- mappings.put(xmlAttribute.getPosition().getStartLine(), attributeRecord);
- }
- }
- }
- for (XmlElement xmlElement : element.getMergeableElements()) {
- parse(xmlElement, mappings);
- }
- }
-
- public String blame(XmlDocument xmlDocument)
- throws IOException, SAXException, ParserConfigurationException {
-
- ImmutableMultimap<Integer, Record> resultingSourceMapping =
- getResultingSourceMapping(xmlDocument);
- LineReader lineReader = new LineReader(
- new StringReader(xmlDocument.prettyPrint()));
-
- StringBuilder actualMappings = new StringBuilder();
- String line;
- int count = 0;
- while ((line = lineReader.readLine()) != null) {
- actualMappings.append(count + 1).append(line).append("\n");
- if (resultingSourceMapping.containsKey(count)) {
- for (Record record : resultingSourceMapping.get(count)) {
- actualMappings.append(count + 1).append("-->")
- .append(record.getActionLocation().toString())
- .append("\n");
- }
- }
- count++;
- }
- return actualMappings.toString();
- }
-
- @Nullable
- private static Actions.NodeRecord findNodeRecord(DecisionTreeRecord decisionTreeRecord) {
- for (Actions.NodeRecord nodeRecord : decisionTreeRecord.getNodeRecords()) {
- if (nodeRecord.getActionType() == Actions.ActionType.ADDED) {
- return nodeRecord;
- }
- }
- return null;
- }
-
- @Nullable
- private static Actions.AttributeRecord findAttributeRecord(
- DecisionTreeRecord decisionTreeRecord,
- XmlAttribute xmlAttribute) {
- for (Actions.AttributeRecord attributeRecord : decisionTreeRecord
- .getAttributeRecords(xmlAttribute.getName())) {
- if (attributeRecord.getActionType() == Actions.ActionType.ADDED) {
- return attributeRecord;
- }
- }
- return null;
- }
-
- /**
- * Internal structure on how {@link com.android.manifmerger.Actions.Record}s are kept for an
- * xml element.
- *
- * Each xml element should have an associated DecisionTreeRecord which keeps a list of
- * {@link com.android.manifmerger.Actions.NodeRecord} for all the node actions related
- * to this xml element.
- *
- * It will also contain a map indexed by attribute name on all the attribute actions related
- * to that particular attribute within the xml element.
- *
- */
- static class DecisionTreeRecord {
- // all other occurrences of the nodes decisions, in order of decisions.
- private final List<NodeRecord> mNodeRecords = new ArrayList<NodeRecord>();
-
- // all attributes decisions indexed by attribute name.
- final Map<XmlNode.NodeName, List<AttributeRecord>> mAttributeRecords =
- new HashMap<XmlNode.NodeName, List<AttributeRecord>>();
-
- ImmutableList<NodeRecord> getNodeRecords() {
- return ImmutableList.copyOf(mNodeRecords);
- }
-
- ImmutableMap<XmlNode.NodeName, List<AttributeRecord>> getAttributesRecords() {
- return ImmutableMap.copyOf(mAttributeRecords);
- }
-
- DecisionTreeRecord() {
- }
-
- void addNodeRecord(NodeRecord nodeRecord) {
- mNodeRecords.add(nodeRecord);
- }
-
- ImmutableList<AttributeRecord> getAttributeRecords(XmlNode.NodeName attributeName) {
- List<AttributeRecord> attributeRecords = mAttributeRecords.get(attributeName);
- return attributeRecords == null
- ? ImmutableList.<AttributeRecord>of()
- : ImmutableList.copyOf(attributeRecords);
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java
deleted file mode 100644
index 4bc6fca..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Joiner;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Describes an attribute characteristics like if it supports smart package name replacement, has
- * a default value and a validator for its values.
- */
-class AttributeModel {
-
- @NonNull private final XmlNode.NodeName mName;
- private final boolean mIsPackageDependent;
- @Nullable private final String mDefaultValue;
- @Nullable private final Validator mOnReadValidator;
- @Nullable private final Validator mOnWriteValidator;
- @NonNull private final MergingPolicy mMergingPolicy;
-
- /**
- * Define a new attribute with specific characteristics.
- *
- * @param name name of the attribute, so far assumed to be in the
- * {@link com.android.SdkConstants#ANDROID_URI} namespace.
- * @param isPackageDependent true if the attribute support smart substitution of package name.
- * @param defaultValue an optional default value.
- * @param onReadValidator an optional validator to validate values against.
- */
- private AttributeModel(@NonNull XmlNode.NodeName name,
- boolean isPackageDependent,
- @Nullable String defaultValue,
- @Nullable Validator onReadValidator,
- @Nullable Validator onWriteValidator,
- @NonNull MergingPolicy mergingPolicy) {
- mName = name;
- mIsPackageDependent = isPackageDependent;
- mDefaultValue = defaultValue;
- mOnReadValidator = onReadValidator;
- mOnWriteValidator = onWriteValidator;
- mMergingPolicy = mergingPolicy;
- }
-
- @NonNull
- XmlNode.NodeName getName() {
- return mName;
- }
-
- /**
- * Return true if the attribute support smart substitution of partially fully qualified
- * class names with package settings as provided by the manifest node's package attribute
- * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>}
- *
- * @return true if this attribute supports smart substitution or false if not.
- */
- boolean isPackageDependent() {
- return mIsPackageDependent;
- }
-
- /**
- * Returns the attribute's default value or null if none.
- */
- @Nullable
- String getDefaultValue() {
- return mDefaultValue;
- }
-
- /**
- * Returns the attribute's {@link com.android.manifmerger.AttributeModel.Validator} to
- * validate its value when read from xml files or null if no validation is necessary.
- */
- @Nullable
- public Validator getOnReadValidator() {
- return mOnReadValidator;
- }
-
- /**
- * Returns the attribute's {@link com.android.manifmerger.AttributeModel.Validator} to
- * validate its value when the merged file is about to be persisted.
- */
- @Nullable
- public Validator getOnWriteValidator() {
- return mOnWriteValidator;
- }
-
- /**
- * Returns the {@link com.android.manifmerger.AttributeModel.MergingPolicy} for this
- * attribute.
- */
- @NonNull
- public MergingPolicy getMergingPolicy() {
- return mMergingPolicy;
- }
-
- /**
- * Creates a new {@link Builder} to describe an attribute.
- * @param attributeName the to be described attribute name
- */
- static Builder newModel(String attributeName) {
- return new Builder(attributeName);
- }
-
-
- static class Builder {
-
- private final String mName;
- private boolean mIsPackageDependent = false;
- private String mDefaultValue;
- private Validator mOnReadValidator;
- private Validator mOnWriteValidator;
- private MergingPolicy mMergingPolicy = STRICT_MERGING_POLICY;
-
- Builder(String name) {
- this.mName = name;
- }
-
- /**
- * Sets the attribute support for smart substitution of partially fully qualified
- * class names with package settings as provided by the manifest node's package attribute
- * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>}
- */
- Builder setIsPackageDependent() {
- mIsPackageDependent = true;
- return this;
- }
-
- /**
- * Sets the attribute default value.
- */
- Builder setDefaultValue(String value) {
- mDefaultValue = value;
- return this;
- }
-
- /**
- * Sets a {@link com.android.manifmerger.AttributeModel.Validator} to validate the
- * attribute's values coming from xml files.
- */
- Builder setOnReadValidator(Validator validator) {
- mOnReadValidator = validator;
- return this;
- }
-
- /**
- * Sets a {@link com.android.manifmerger.AttributeModel.Validator} to validate values
- * before they are written to the final merged document.
- */
- Builder setOnWriteValidator(Validator validator) {
- mOnWriteValidator = validator;
- return this;
- }
-
- Builder setMergingPolicy(MergingPolicy mergingPolicy) {
- mMergingPolicy = mergingPolicy;
- return this;
- }
-
- /**
- * Build an immutable {@link com.android.manifmerger.AttributeModel}
- */
- AttributeModel build() {
- return new AttributeModel(
- XmlNode.fromXmlName("android:" + mName),
- mIsPackageDependent,
- mDefaultValue,
- mOnReadValidator,
- mOnWriteValidator,
- mMergingPolicy);
- }
- }
-
- /**
- * Defines a merging policy between two attribute values. Example of merging policies can be
- * strict when it is illegal to try to merge or override a value by another. Another example
- * is a OR merging policy on boolean attribute values.
- */
- interface MergingPolicy {
-
- /**
- * Returns true if it should be attempted to merge this attribute value with
- * the attribute default value when merging with a node that does not contain
- * the attribute declaration.
- */
- boolean shouldMergeDefaultValues();
-
- /**
- * Merges the two attributes values and returns the merged value. If the values cannot be
- * merged, return null.
- */
- @Nullable
- String merge(@NonNull String higherPriority, @NonNull String lowerPriority);
- }
-
- /**
- * Standard attribute value merging policy, generates an error unless both values are equal.
- */
- static final MergingPolicy STRICT_MERGING_POLICY = new MergingPolicy() {
-
- @Override
- public boolean shouldMergeDefaultValues() {
- return false;
- }
-
- @Nullable
- @Override
- public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
- // it's ok if the values are equal, otherwise it's not.
- return higherPriority.equals(lowerPriority)
- ? higherPriority
- : null;
- }
- };
-
- /**
- * Boolean OR merging policy.
- */
- static final MergingPolicy OR_MERGING_POLICY = new MergingPolicy() {
- @Override
- public boolean shouldMergeDefaultValues() {
- return true;
- }
-
- @Nullable
- @Override
- public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
- return Boolean.toString(BooleanValidator.isTrue(higherPriority) ||
- BooleanValidator.isTrue(lowerPriority));
- }
- };
-
- /**
- * Merging policy that will return the higher priority value regardless of the lower priority
- * value
- */
- static final MergingPolicy NO_MERGING_POLICY = new MergingPolicy() {
-
- @Override
- public boolean shouldMergeDefaultValues() {
- return true;
- }
-
- @Nullable
- @Override
- public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
- return higherPriority;
- }
- };
-
- /**
- * Decode a decimal or hexadecimal {@link String} into an {@link Integer}.
- * String starting with 0 will be considered decimal, not octal.
- */
- private static int decodeDecOrHexString(String s) {
- long decodedValue = s.startsWith("0x") || s.startsWith("0X")
- ? Long.decode(s)
- : Long.parseLong(s);
- if (decodedValue < 0xFFFFFFFFL) {
- return (int) decodedValue;
- } else {
- throw new IllegalArgumentException("Value " + s + " too big for 32 bits.");
- }
- }
-
- /**
- * Validates an attribute value.
- *
- * The validator can be called when xml documents are read to ensure the xml file contains
- * valid statements.
- *
- * This is a poor-mans replacement for not having a proper XML Schema do perform such
- * validations.
- */
- interface Validator {
-
- /**
- * Validates a value, issuing a warning or error in case of failure through the passed
- * merging report.
- * @param mergingReport to report validation warnings or error
- * @param attribute the attribute to validate.
- * @param value the proposed or existing attribute value.
- * @return true if the value is legal for this attribute.
- */
- boolean validates(@NonNull MergingReport.Builder mergingReport,
- @NonNull XmlAttribute attribute,
- @NonNull String value);
- }
-
- /**
- * Validates a boolean attribute type.
- */
- static class BooleanValidator implements Validator {
-
- // TODO: check with @xav where to find the acceptable values by runtime.
- private static final Pattern TRUE_PATTERN = Pattern.compile("true|True|TRUE");
- private static final Pattern FALSE_PATTERN = Pattern.compile("false|False|FALSE");
-
- private static boolean isTrue(String value) {
- return TRUE_PATTERN.matcher(value).matches();
- }
-
- @Override
- public boolean validates(@NonNull MergingReport.Builder mergingReport,
- @NonNull XmlAttribute attribute,
- @NonNull String value) {
- boolean matches = TRUE_PATTERN.matcher(value).matches() ||
- FALSE_PATTERN.matcher(value).matches();
- if (!matches) {
- attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
- String.format(
- "Attribute %1$s at %2$s has an illegal value=(%3$s), "
- + "expected 'true' or 'false'",
- attribute.getId(),
- attribute.printPosition(),
- value
- )
- );
- }
- return matches;
- }
- }
-
- /**
- * A {@link com.android.manifmerger.AttributeModel.Validator} for verifying that a proposed
- * value is part of the acceptable list of possible values.
- */
- static class MultiValueValidator implements Validator {
-
- private final String[] multiValues;
- private final String allValues;
-
- MultiValueValidator(String... multiValues) {
- this.multiValues = multiValues;
- allValues = Joiner.on(',').join(multiValues);
- }
-
- @Override
- public boolean validates(@NonNull MergingReport.Builder mergingReport,
- @NonNull XmlAttribute attribute, @NonNull String value) {
- for (String multiValue : multiValues) {
- if (multiValue.equals(value)) {
- return true;
- }
- }
- attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
- String.format(
- "Invalid value for attribute %1$s at %2$s, value=(%3$s), "
- + "acceptable values are (%4$s)",
- attribute.getId(),
- attribute.printPosition(),
- value,
- allValues
- )
- );
- return false;
- }
- }
-
- /**
- * A {@link com.android.manifmerger.AttributeModel.Validator} for verifying that a proposed
- * value is a numerical integer value.
- */
- static class IntegerValueValidator implements Validator {
-
- @Override
- public boolean validates(@NonNull MergingReport.Builder mergingReport,
- @NonNull XmlAttribute attribute, @NonNull String value) {
- try {
- return Integer.parseInt(value) > 0;
- } catch (NumberFormatException e) {
- attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
- String.format(
- "Attribute %1$s at %2$s must be an integer, found %3$s",
- attribute.getId(),
- attribute.printPosition(),
- value)
- );
- return false;
- }
- }
- }
-
- /**
- * A {@link com.android.manifmerger.AttributeModel.Validator} to validate that a string is
- * a valid 32 bits hexadecimal representation.
- */
- static class Hexadecimal32Bits implements Validator {
- protected static final Pattern PATTERN = Pattern.compile("0[xX]([0-9a-fA-F]+)");
-
- @Override
- public boolean validates(@NonNull MergingReport.Builder mergingReport,
- @NonNull XmlAttribute attribute, @NonNull String value) {
- Matcher matcher = PATTERN.matcher(value);
- boolean valid = matcher.matches() && matcher.group(1).length() <= 8;
- if (!valid) {
- attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
- String.format(
- "Attribute %1$s at %2$s is not a valid hexadecimal 32 bit value,"
- + " found %3$s",
- attribute.getId(),
- attribute.printPosition(),
- value
- ));
- }
- return valid;
- }
- }
-
- /**
- * A {@link com.android.manifmerger.AttributeModel.Validator} to validate that a string is
- * a valid 32 positive hexadecimal representation with a minimum value requirement.
- */
- static class Hexadecimal32BitsWithMinimumValue extends Hexadecimal32Bits {
-
- private final int mMinimumValue;
-
- Hexadecimal32BitsWithMinimumValue(int minimumValue) {
- mMinimumValue = minimumValue;
- }
-
- @Override
- public boolean validates(@NonNull MergingReport.Builder mergingReport,
- @NonNull XmlAttribute attribute, @NonNull String value) {
- boolean valid = super.validates(mergingReport, attribute, value);
- if (valid) {
- try {
- Long decodedValue = Long.decode(value);
- valid = decodedValue >= mMinimumValue && decodedValue < 0xFFFFFFFFL;
- } catch(NumberFormatException e) {
- valid = false;
- }
- if (!valid) {
- attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
- String.format(
- "Attribute %1$s at %2$s is not a valid hexadecimal value,"
- + " minimum is 0x%3$08X, maximum is 0x%4$08X, found %5$s",
- attribute.getId(),
- attribute.printPosition(),
- mMinimumValue,
- Integer.MAX_VALUE,
- value
- ));
- }
- return valid;
- }
- return false;
- }
- }
-}
\ No newline at end of file
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java
deleted file mode 100644
index d71cea3..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.SdkConstants.ANDROID_URI;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.xml.AndroidManifest;
-
-import org.w3c.dom.Attr;
-
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.TreeMap;
-
-/**
- * Trims the document from unwanted, repeated elements.
- */
-public class ElementsTrimmer {
-
- /**
- * Trims unwanted, duplicated elements from the merged document.
- * <p>
- * Current trimmed elements are :
- * <p>
- * <ul>
- * <li>uses-features with glEsVersion key</li>
- * <ul>
- * <li>The highest 1.x version element will be kept regardless of 'required' flag value</li>
- * <li>If the above element is present and has a 'false' required flag, there can be at most
- * one element of a lesser version with 'required' attribute set to true.</li>
- * <li>The highest 2.x or superior element will be kept regardless of 'required' flag value
- * </li>
- * <li>If the above element is present and has a 'false' required flag, there can be at
- * most one element of a lesser version (but higher than 2.0) with a 'required' attribute
- * set to true.</li>
- * </ul>
- * </ul>
- *
- * @param xmlDocument the xml document to trim.
- * @param mergingReport the report to log errors and actions.
- */
- public static void trim(
- @NonNull XmlDocument xmlDocument,
- @NonNull MergingReport.Builder mergingReport) {
-
- // I sort the glEsVersion declaration by value.
- NavigableMap<Integer, XmlElement> glEsVersionDeclarations = new TreeMap<Integer, XmlElement>();
-
- for (XmlElement childElement : xmlDocument.getRootNode().getMergeableElements()) {
- if (childElement.getType().equals(ManifestModel.NodeTypes.USES_FEATURE)) {
- Integer value = getGlEsVersion(childElement);
- if (value != null) {
- glEsVersionDeclarations.put(value, childElement);
- }
- }
- }
-
- // now eliminate all unwanted declarations, revert the sorted map, so we get the
- // higher elements first.
- glEsVersionDeclarations = glEsVersionDeclarations.descendingMap();
- boolean doneWithAboveTwoTrue = false;
- boolean doneWithAboveTwoFalse = false;
- boolean doneWithBelowTwoTrue = false;
- boolean doneWithBelowTwoFalse = false;
- for (Map.Entry<Integer, XmlElement> glEsVersionDeclaration :
- glEsVersionDeclarations.entrySet()) {
-
- boolean removeElement;
-
- Attr requiredAttribute = glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS(
- ANDROID_URI, AndroidManifest.ATTRIBUTE_REQUIRED);
-
- boolean isRequired = requiredAttribute == null ||
- Boolean.parseBoolean(requiredAttribute.getValue());
-
- if (glEsVersionDeclaration.getKey() < 0x20000) {
- // version one.
- removeElement = (doneWithBelowTwoFalse && doneWithBelowTwoTrue)
- || (isRequired && doneWithBelowTwoTrue)
- || (!isRequired && doneWithBelowTwoFalse);
-
- if (!removeElement) {
- doneWithBelowTwoFalse = true;
- doneWithBelowTwoTrue = isRequired;
- }
- } else {
- // version two or above.
- removeElement = (doneWithAboveTwoFalse && doneWithAboveTwoTrue)
- || (isRequired && doneWithAboveTwoTrue)
- || (!isRequired && doneWithAboveTwoFalse);
-
- if (!removeElement) {
- doneWithAboveTwoFalse = true;
- doneWithAboveTwoTrue = isRequired;
- }
- }
- if (removeElement) {
- // if the node only contains glEsVersion, then remove the entire node,
- // if it also contains android:name, just remove the glEsVersion attribute
- if (glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS(ANDROID_URI,
- SdkConstants.ATTR_NAME) != null) {
- glEsVersionDeclaration.getValue().getXml().removeAttributeNS(ANDROID_URI,
- AndroidManifest.ATTRIBUTE_GLESVERSION);
- mergingReport.getActionRecorder().recordAttributeAction(
- glEsVersionDeclaration.getValue().getAttribute(XmlNode.fromXmlName(
- "android:" + AndroidManifest.ATTRIBUTE_GLESVERSION)).get(),
- Actions.ActionType.REJECTED,
- null /* attributeOperationType */);
- } else {
- xmlDocument.getRootNode().getXml().removeChild(
- glEsVersionDeclaration.getValue().getXml());
- mergingReport.getActionRecorder().recordNodeAction(
- glEsVersionDeclaration.getValue(),
- Actions.ActionType.REJECTED);
-
- }
- }
-
- }
-
- }
-
- private static Integer getGlEsVersion(XmlElement xmlElement) {
- Attr glEsVersion = xmlElement.getXml()
- .getAttributeNodeNS(ANDROID_URI, AndroidManifest.ATTRIBUTE_GLESVERSION);
- if (glEsVersion == null) {
- return null;
- }
- return getHexValue(glEsVersion);
- }
-
- private static Integer getHexValue(Attr attribute) {
- return Integer.decode(attribute.getValue());
- }
-}
-
-
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java
deleted file mode 100644
index c75e715..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java
+++ /dev/null
@@ -1,1122 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.PlaceholderHandler.APPLICATION_ID;
-import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
-import static com.android.manifmerger.PlaceholderHandler.PACKAGE_NAME;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.Immutable;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.utils.ILogger;
-import com.android.utils.Pair;
-import com.android.utils.SdkUtils;
-import com.android.utils.StdLogger;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * merges android manifest files, idempotent.
- */
- at Immutable
-public class ManifestMerger2 {
-
- @NonNull
- private final File mManifestFile;
-
- @NonNull
- private final Map<String, Object> mPlaceHolderValues;
-
- @NonNull
- private final KeyBasedValueResolver<SystemProperty> mSystemPropertyResolver;
-
- private final ILogger mLogger;
- private final ImmutableList<Pair<String, File>> mLibraryFiles;
- private final ImmutableList<File> mFlavorsAndBuildTypeFiles;
- private final ImmutableList<Invoker.Feature> mOptionalFeatures;
- private final MergeType mMergeType;
- private final Optional<File> mReportFile;
-
- private ManifestMerger2(
- @NonNull ILogger logger,
- @NonNull File mainManifestFile,
- @NonNull ImmutableList<Pair<String, File>> libraryFiles,
- @NonNull ImmutableList<File> flavorsAndBuildTypeFiles,
- @NonNull ImmutableList<Invoker.Feature> optionalFeatures,
- @NonNull Map<String, Object> placeHolderValues,
- @NonNull KeyBasedValueResolver<SystemProperty> systemPropertiesResolver,
- @NonNull MergeType mergeType,
- @NonNull Optional<File> reportFile) {
- this.mSystemPropertyResolver = systemPropertiesResolver;
- this.mPlaceHolderValues = placeHolderValues;
- this.mManifestFile = mainManifestFile;
- this.mLogger = logger;
- this.mLibraryFiles = libraryFiles;
- this.mFlavorsAndBuildTypeFiles = flavorsAndBuildTypeFiles;
- this.mOptionalFeatures = optionalFeatures;
- this.mMergeType = mergeType;
- this.mReportFile = reportFile;
- }
-
- /**
- * Perform high level ordering of files merging and delegates actual merging to
- * {@link XmlDocument#merge(XmlDocument, com.android.manifmerger.MergingReport.Builder)}
- *
- * @return the merging activity report.
- * @throws MergeFailureException if the merging cannot be completed (for instance, if xml
- * files cannot be loaded).
- */
- private MergingReport merge() throws MergeFailureException {
- // initiate a new merging report
- MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
-
- SelectorResolver selectors = new SelectorResolver();
- // load all the libraries xml files up front to have a list of all possible node:selector
- // values.
- List<LoadedManifestInfo> loadedLibraryDocuments =
- loadLibraries(selectors, mergingReportBuilder);
-
- // load the main manifest file to do some checking along the way.
- LoadedManifestInfo loadedMainManifestInfo = load(
- new ManifestInfo(
- mManifestFile.getName(),
- mManifestFile,
- XmlDocument.Type.MAIN,
- Optional.<String>absent() /* mainManifestPackageName */),
- selectors,
- mergingReportBuilder);
-
- // first do we have a package declaration in the main manifest ?
- Optional<XmlAttribute> mainPackageAttribute =
- loadedMainManifestInfo.getXmlDocument().getPackage();
- if (!mainPackageAttribute.isPresent()) {
- mergingReportBuilder.addMessage(
- loadedMainManifestInfo.getXmlDocument().getSourceFile(),
- MergingReport.Record.Severity.ERROR,
- String.format(
- "Main AndroidManifest.xml at %1$s manifest:package attribute "
- + "is not declared",
- loadedMainManifestInfo.getXmlDocument().getSourceFile()
- .print(true)));
- return mergingReportBuilder.build();
- }
-
- // perform system property injection
- performSystemPropertiesInjection(mergingReportBuilder,
- loadedMainManifestInfo.getXmlDocument());
-
- // force the re-parsing of the xml as elements may have been added through system
- // property injection.
- loadedMainManifestInfo = new LoadedManifestInfo(loadedMainManifestInfo,
- loadedMainManifestInfo.getOriginalPackageName(),
- loadedMainManifestInfo.getXmlDocument().reparse());
-
- // invariant : xmlDocumentOptional holds the higher priority document and we try to
- // merge in lower priority documents.
- Optional<XmlDocument> xmlDocumentOptional = Optional.absent();
- for (File inputFile : mFlavorsAndBuildTypeFiles) {
- mLogger.info("Merging flavors and build manifest %s \n", inputFile.getPath());
- LoadedManifestInfo overlayDocument = load(
- new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY,
- Optional.of(mainPackageAttribute.get().getValue())),
- selectors,
- mergingReportBuilder);
-
- // check package declaration.
- Optional<XmlAttribute> packageAttribute =
- overlayDocument.getXmlDocument().getPackage();
- // if both files declare a package name, it should be the same.
- if (loadedMainManifestInfo.getOriginalPackageName().isPresent() &&
- packageAttribute.isPresent()
- && !loadedMainManifestInfo.getOriginalPackageName().get().equals(
- packageAttribute.get().getValue())) {
- // no suggestion for library since this is actually forbidden to change the
- // the package name per flavor.
- String message = mMergeType == MergeType.APPLICATION
- ? String.format(
- "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
- + "\thas a different value=(%3$s) "
- + "declared in main manifest at %4$s\n"
- + "\tSuggestion: remove the overlay declaration at %5$s "
- + "\tand place it in the build.gradle:\n"
- + "\t\tflavorName {\n"
- + "\t\t\tapplicationId = \"%2$s\"\n"
- + "\t\t}",
- packageAttribute.get().printPosition(),
- packageAttribute.get().getValue(),
- mainPackageAttribute.get().getValue(),
- mainPackageAttribute.get().printPosition(),
- packageAttribute.get().getSourceFile().print(true))
- : String.format(
- "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
- + "\thas a different value=(%3$s) "
- + "declared in main manifest at %4$s",
- packageAttribute.get().printPosition(),
- packageAttribute.get().getValue(),
- mainPackageAttribute.get().getValue(),
- mainPackageAttribute.get().printPosition());
- mergingReportBuilder.addMessage(
- overlayDocument.getXmlDocument().getSourceFile(),
- MergingReport.Record.Severity.ERROR,
- message);
- return mergingReportBuilder.build();
- }
-
- overlayDocument.getXmlDocument().getRootNode().getXml().setAttribute("package",
- mainPackageAttribute.get().getValue());
- xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder);
-
- if (!xmlDocumentOptional.isPresent()) {
- return mergingReportBuilder.build();
- }
- }
-
- mLogger.info("Merging main manifest %s\n", mManifestFile.getPath());
- xmlDocumentOptional =
- merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder);
-
- if (!xmlDocumentOptional.isPresent()) {
- return mergingReportBuilder.build();
- }
-
- // force main manifest package into resulting merged file when creating a library manifest.
- if (mMergeType == MergeType.LIBRARY) {
- // extract the package name...
- String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode()
- .getXml().getAttribute("package");
- // save it in the selector instance.
- if (!Strings.isNullOrEmpty(mainManifestPackageName)) {
- xmlDocumentOptional.get().getRootNode().getXml()
- .setAttribute("package", mainManifestPackageName);
- }
- }
- for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
- mLogger.info("Merging library manifest " + libraryDocument.getLocation());
- xmlDocumentOptional = merge(
- xmlDocumentOptional, libraryDocument, mergingReportBuilder);
- if (!xmlDocumentOptional.isPresent()) {
- return mergingReportBuilder.build();
- }
- }
-
- // done with proper merging phase, now we need to trim unwanted elements, placeholder
- // substitution and system properties injection.
- ElementsTrimmer.trim(xmlDocumentOptional.get(), mergingReportBuilder);
- if (mergingReportBuilder.hasErrors()) {
- return mergingReportBuilder.build();
- }
-
- if (!mOptionalFeatures.contains(Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)) {
- // do one last placeholder substitution, this is useful as we don't stop the build
- // when a library failed a placeholder substitution, but the element might have
- // been overridden so the problem was transient. However, with the final document
- // ready, all placeholders values must have been provided.
- KeyBasedValueResolver<String> placeHolderValueResolver =
- new MapBasedKeyBasedValueResolver<String>(mPlaceHolderValues);
- PlaceholderHandler placeholderHandler = new PlaceholderHandler();
- placeholderHandler.visit(
- mMergeType,
- xmlDocumentOptional.get(),
- placeHolderValueResolver,
- mergingReportBuilder);
- if (mergingReportBuilder.hasErrors()) {
- return mergingReportBuilder.build();
- }
- }
-
- // perform system property injection.
- performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());
-
- XmlDocument finalMergedDocument = xmlDocumentOptional.get();
- PostValidator.validate(finalMergedDocument, mergingReportBuilder);
- if (mergingReportBuilder.hasErrors()) {
- finalMergedDocument.getRootNode().addMessage(mergingReportBuilder,
- MergingReport.Record.Severity.WARNING,
- "Post merge validation failed");
- }
-
- // only remove tools annotations if we are packaging an application.
- if (mOptionalFeatures.contains(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)) {
- finalMergedDocument =
- ToolsInstructionsCleaner.cleanToolsReferences(finalMergedDocument, mLogger);
- }
-
- if (mOptionalFeatures.contains(Invoker.Feature.EXTRACT_FQCNS)) {
- extractFcqns(finalMergedDocument);
- }
-
- if (finalMergedDocument != null) {
- mergingReportBuilder.setMergedDocument(finalMergedDocument);
- }
-
- MergingReport mergingReport = mergingReportBuilder.build();
- StdLogger stdLogger = new StdLogger(StdLogger.Level.INFO);
- mergingReport.log(stdLogger);
- stdLogger.verbose(mergingReport.getMergedDocument().get().prettyPrint());
-
- if (mReportFile.isPresent()) {
- writeReport(mergingReport);
- }
-
- return mergingReport;
- }
-
- /**
- * Creates the merging report file.
- * @param mergingReport the merging activities report to serialize.
- */
- private void writeReport(MergingReport mergingReport) {
- FileWriter fileWriter = null;
- try {
- if (!mReportFile.get().getParentFile().exists()
- && !mReportFile.get().getParentFile().mkdirs()) {
- mLogger.warning(String.format(
- "Cannot create %1$s manifest merger report file,"
- + "build will continue but merging activities "
- + "will not be documented",
- mReportFile.get().getAbsolutePath()));
- } else {
- fileWriter = new FileWriter(mReportFile.get());
- mergingReport.getActions().log(fileWriter);
- }
- } catch (IOException e) {
- mLogger.warning(String.format(
- "Error '%1$s' while writing the merger report file, "
- + "build can continue but merging activities "
- + "will not be documented ",
- e.getMessage()));
- } finally {
- if (fileWriter != null) {
- try {
- fileWriter.close();
- } catch (IOException e) {
- mLogger.warning(String.format(
- "Error '%1$s' while closing the merger report file, "
- + "build can continue but merging activities "
- + "will not be documented ",
- e.getMessage()));
- }
- }
- }
- }
-
- /**
- * shorten all fully qualified class name that belong to the same package as the manifest's
- * package attribute value.
- * @param finalMergedDocument the AndroidManifest.xml document.
- */
- private void extractFcqns(XmlDocument finalMergedDocument) {
- extractFcqns(finalMergedDocument.getPackageName(), finalMergedDocument.getRootNode());
- }
-
- /**
- * shorten recursively all attributes that are package dependent of the passed nodes and all
- * its child nodes.
- * @param packageName the manifest package name.
- * @param xmlElement the xml element to process recursively.
- */
- private void extractFcqns(String packageName, XmlElement xmlElement) {
- for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
- if (xmlAttribute.getModel() !=null && xmlAttribute.getModel().isPackageDependent()) {
- String value = xmlAttribute.getValue();
- if (value != null && value.startsWith(packageName) &&
- value.charAt(packageName.length()) == '.') {
- xmlAttribute.getXml().setValue(value.substring(packageName.length()));
- }
- }
- }
- for (XmlElement child : xmlElement.getMergeableElements()) {
- extractFcqns(packageName, child);
- }
- }
-
- /**
- * Load an xml file and perform placeholder substitution
- * @param manifestInfo the android manifest information like if it is a library, an
- * overlay or a main manifest file.
- * @param selectors all the libraries selectors
- * @param mergingReportBuilder the merging report to store events and errors.
- * @return a loaded manifest info.
- * @throws MergeFailureException
- */
- private LoadedManifestInfo load(
- ManifestInfo manifestInfo,
- KeyResolver<String> selectors,
- MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
-
- XmlDocument xmlDocument;
- try {
- xmlDocument = XmlLoader.load(selectors,
- mSystemPropertyResolver,
- manifestInfo.mName,
- manifestInfo.mLocation,
- manifestInfo.getType(),
- manifestInfo.getMainManifestPackageName());
- } catch (Exception e) {
- throw new MergeFailureException(e);
- }
-
- String originalPackageName = xmlDocument.getPackageName();
- MergingReport.Builder builder = manifestInfo.getType() == XmlDocument.Type.MAIN
- ? mergingReportBuilder
- : new MergingReport.Builder(mergingReportBuilder.getLogger());
-
- builder.getActionRecorder().recordDefaultNodeAction(
- xmlDocument.getRootNode());
-
- // perform place holder substitution, this is necessary to do so early in case placeholders
- // are used in key attributes.
- performPlaceHolderSubstitution(manifestInfo, xmlDocument, builder);
-
- return new LoadedManifestInfo(manifestInfo,
- Optional.fromNullable(originalPackageName), xmlDocument);
- }
-
- private void performPlaceHolderSubstitution(ManifestInfo manifestInfo,
- XmlDocument xmlDocument,
- MergingReport.Builder mergingReportBuilder) {
-
- if (mOptionalFeatures.contains(Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)) {
- return;
- }
-
- // check for placeholders presence, switch first the packageName and application id if
- // it is not explicitly set.
- Map<String, Object> finalPlaceHolderValues = mPlaceHolderValues;
- if (!mPlaceHolderValues.containsKey(PlaceholderHandler.APPLICATION_ID)) {
- String packageName = manifestInfo.getMainManifestPackageName().isPresent()
- ? manifestInfo.getMainManifestPackageName().get()
- : xmlDocument.getPackageName();
- // add all existing placeholders except package name that will be swapped.
- ImmutableMap.Builder<String, Object> builder = ImmutableMap.<String, Object>builder();
- for (Map.Entry<String, Object> entry : mPlaceHolderValues.entrySet()) {
- if (!entry.getKey().equals(PlaceholderHandler.PACKAGE_NAME)) {
- builder.put(entry);
- }
- }
- builder.put(PlaceholderHandler.PACKAGE_NAME, packageName);
- if (mMergeType != MergeType.LIBRARY) {
- builder.put(PlaceholderHandler.APPLICATION_ID, packageName);
- }
- finalPlaceHolderValues = builder.build();
- }
-
- KeyBasedValueResolver<String> placeHolderValueResolver =
- new MapBasedKeyBasedValueResolver<String>(finalPlaceHolderValues);
- PlaceholderHandler placeholderHandler = new PlaceholderHandler();
- placeholderHandler.visit(
- mMergeType,
- xmlDocument,
- placeHolderValueResolver,
- mergingReportBuilder);
- }
-
- // merge the optionally existing xmlDocument with a lower priority xml file.
- private Optional<XmlDocument> merge(
- Optional<XmlDocument> xmlDocument,
- LoadedManifestInfo lowerPriorityDocument,
- MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
-
- MergingReport.Result validationResult = PreValidator
- .validate(mergingReportBuilder, lowerPriorityDocument.getXmlDocument());
- if (validationResult == MergingReport.Result.ERROR) {
- mergingReportBuilder.addMessage(
- lowerPriorityDocument.getXmlDocument().getSourceFile(),
- MergingReport.Record.Severity.ERROR,
- "Validation failed, exiting");
- return Optional.absent();
- }
- Optional<XmlDocument> result;
- if (xmlDocument.isPresent()) {
- result = xmlDocument.get().merge(
- lowerPriorityDocument.getXmlDocument(), mergingReportBuilder);
- } else {
- mergingReportBuilder.getActionRecorder().recordDefaultNodeAction(
- lowerPriorityDocument.getXmlDocument().getRootNode());
- result = Optional.of(lowerPriorityDocument.getXmlDocument());
- }
-
- // if requested, dump each intermediary merging stage into the report.
- if (mOptionalFeatures.contains(Invoker.Feature.KEEP_INTERMEDIARY_STAGES)
- && result.isPresent()) {
- mergingReportBuilder.addMergingStage(result.get().prettyPrint());
- }
-
- return result;
- }
-
- private List<LoadedManifestInfo> loadLibraries(SelectorResolver selectors,
- MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
-
- ImmutableList.Builder<LoadedManifestInfo> loadedLibraryDocuments = ImmutableList.builder();
- for (Pair<String, File> libraryFile : mLibraryFiles) {
- mLogger.info("Loading library manifest " + libraryFile.getSecond().getPath());
- ManifestInfo manifestInfo = new ManifestInfo(libraryFile.getFirst(),
- libraryFile.getSecond(),
- XmlDocument.Type.LIBRARY, Optional.<String>absent());
- XmlDocument libraryDocument;
- try {
- libraryDocument = XmlLoader.load(selectors,
- mSystemPropertyResolver,
- manifestInfo.mName, manifestInfo.mLocation,
- XmlDocument.Type.LIBRARY,
- Optional.<String>absent() /* mainManifestPackageName */);
- } catch (Exception e) {
- throw new MergeFailureException(e);
- }
- // extract the package name...
- String libraryPackage = libraryDocument.getRootNode().getXml().getAttribute("package");
- // save it in the selector instance.
- if (!Strings.isNullOrEmpty(libraryPackage)) {
- selectors.addSelector(libraryPackage, libraryFile.getFirst());
- }
-
- // perform placeholder substitution, this is useful when the library is using
- // a placeholder in a key element, we however do not need to record these
- // substitutions so feed it with a fake merging report.
- MergingReport.Builder builder = new MergingReport.Builder(mergingReportBuilder.getLogger());
- builder.getActionRecorder().recordDefaultNodeAction(libraryDocument.getRootNode());
- performPlaceHolderSubstitution(manifestInfo, libraryDocument, builder);
- if (builder.hasErrors()) {
- // we log the errors but continue, in case the error is of no consequence
- // to the application consuming the library.
- builder.build().log(mLogger);
- }
-
- loadedLibraryDocuments.add(new LoadedManifestInfo(manifestInfo,
- Optional.fromNullable(libraryDocument.getPackageName()),
- libraryDocument));
- }
- return loadedLibraryDocuments.build();
- }
-
- /**
- * Creates a new {@link com.android.manifmerger.ManifestMerger2.Invoker} instance to invoke
- * the merging tool to merge manifest files for an application.
- *
- * @param mainManifestFile application main manifest file.
- * @param logger the logger interface to use.
- * @return an {@link com.android.manifmerger.ManifestMerger2.Invoker} instance that will allow
- * further customization and trigger the merging tool.
- */
- public static Invoker newMerger(@NonNull File mainManifestFile,
- @NonNull ILogger logger,
- @NonNull MergeType mergeType) {
- return new Invoker(mainManifestFile, logger, mergeType);
- }
-
- /**
- * List of manifest files properties that can be directly overridden without using a
- * placeholder.
- */
- public enum SystemProperty implements AutoAddingProperty {
-
- /**
- * Allow setting the merged manifest file package name.
- */
- PACKAGE {
- @Override
- public void addTo(@NonNull ActionRecorder actionRecorder,
- @NonNull XmlDocument document,
- @NonNull String value) {
- addToElement(this, actionRecorder, value, document.getRootNode());
- }
- },
- /**
- * http://developer.android.com/guide/topics/manifest/manifest-element.html#vcode
- */
- VERSION_CODE {
- @Override
- public void addTo(@NonNull ActionRecorder actionRecorder,
- @NonNull XmlDocument document,
- @NonNull String value) {
- addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode());
- }
- },
- /**
- * http://developer.android.com/guide/topics/manifest/manifest-element.html#vname
- */
- VERSION_NAME {
- @Override
- public void addTo(@NonNull ActionRecorder actionRecorder,
- @NonNull XmlDocument document,
- @NonNull String value) {
- addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode());
- }
- },
- /**
- * http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#min
- */
- MIN_SDK_VERSION {
- @Override
- public void addTo(@NonNull ActionRecorder actionRecorder,
- @NonNull XmlDocument document,
- @NonNull String value) {
- addToElementInAndroidNS(this, actionRecorder, value,
- createOrGetUseSdk(actionRecorder, document));
- }
- },
- /**
- * http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#target
- */
- TARGET_SDK_VERSION {
- @Override
- public void addTo(@NonNull ActionRecorder actionRecorder,
- @NonNull XmlDocument document,
- @NonNull String value) {
- addToElementInAndroidNS(this, actionRecorder, value,
- createOrGetUseSdk(actionRecorder, document));
- }
- },
-
- MAX_SDK_VERSION {
- @Override
- public void addTo(@NonNull ActionRecorder actionRecorder,
- @NonNull XmlDocument document,
- @NonNull String value) {
- addToElementInAndroidNS(this, actionRecorder, value,
- createOrGetUseSdk(actionRecorder, document));
- }
- };
-
- public String toCamelCase() {
- return SdkUtils.constantNameToCamelCase(name());
- }
-
- // utility method to add an attribute which name is derived from the enum name().
- private static void addToElement(
- SystemProperty systemProperty,
- ActionRecorder actionRecorder,
- String value,
- XmlElement to) {
-
- to.getXml().setAttribute(systemProperty.toCamelCase(), value);
- XmlAttribute xmlAttribute = new XmlAttribute(to,
- to.getXml().getAttributeNode(systemProperty.toCamelCase()), null);
- actionRecorder.recordAttributeAction(xmlAttribute, new Actions.AttributeRecord(
- Actions.ActionType.INJECTED,
- new SourceFilePosition(to.getSourceFile(), SourcePosition.UNKNOWN),
- xmlAttribute.getId(),
- null, /* reason */
- null /* attributeOperationType */));
- }
-
- // utility method to add an attribute in android namespace which local name is derived from
- // the enum name().
- private static void addToElementInAndroidNS(
- SystemProperty systemProperty,
- ActionRecorder actionRecorder,
- String value,
- XmlElement to) {
-
- String toolsPrefix = getAndroidPrefix(to.getXml());
- to.getXml().setAttributeNS(SdkConstants.ANDROID_URI,
- toolsPrefix + XmlUtils.NS_SEPARATOR + systemProperty.toCamelCase(),
- value);
- Attr attr = to.getXml().getAttributeNodeNS(SdkConstants.ANDROID_URI,
- systemProperty.toCamelCase());
-
- XmlAttribute xmlAttribute = new XmlAttribute(to, attr, null);
- actionRecorder.recordAttributeAction(xmlAttribute,
- new Actions.AttributeRecord(
- Actions.ActionType.INJECTED,
- new SourceFilePosition(to.getSourceFile(), SourcePosition.UNKNOWN),
- xmlAttribute.getId(),
- null, /* reason */
- null /* attributeOperationType */
- )
- );
-
- }
-
- // utility method to create or get an existing use-sdk xml element under manifest.
- // this could be made more generic by adding more metadata to the enum but since there is
- // only one case so far, keep it simple.
- private static XmlElement createOrGetUseSdk(
- ActionRecorder actionRecorder, XmlDocument document) {
-
- Element manifest = document.getXml().getDocumentElement();
- NodeList usesSdks = manifest
- .getElementsByTagName(ManifestModel.NodeTypes.USES_SDK.toXmlName());
- if (usesSdks.getLength() == 0) {
- usesSdks = manifest
- .getElementsByTagNameNS(
- SdkConstants.ANDROID_URI,
- ManifestModel.NodeTypes.USES_SDK.toXmlName());
- }
- if (usesSdks.getLength() == 0) {
- // create it first.
- Element useSdk = manifest.getOwnerDocument().createElement(
- ManifestModel.NodeTypes.USES_SDK.toXmlName());
- manifest.appendChild(useSdk);
- XmlElement xmlElement = new XmlElement(useSdk, document);
- Actions.NodeRecord nodeRecord = new Actions.NodeRecord(
- Actions.ActionType.INJECTED,
- new SourceFilePosition(xmlElement.getSourceFile(),
- SourcePosition.UNKNOWN),
- xmlElement.getId(),
- "use-sdk injection requested",
- NodeOperationType.STRICT);
- actionRecorder.recordNodeAction(xmlElement, nodeRecord);
- return xmlElement;
- } else {
- return new XmlElement((Element) usesSdks.item(0), document);
- }
- }
- }
-
- private static String getAndroidPrefix(Element xml) {
- String toolsPrefix = XmlUtils.lookupNamespacePrefix(
- xml, SdkConstants.ANDROID_URI, SdkConstants.ANDROID_NS_NAME, false);
- if (!toolsPrefix.equals(SdkConstants.ANDROID_NS_NAME) && xml.getOwnerDocument()
- .getDocumentElement().getAttribute("xmlns:" + toolsPrefix) == null) {
- // this is weird, the document is using "android" prefix but it's not bound
- // to our namespace. Add the proper xmlns declaration.
- xml.setAttribute("xmlns:" + toolsPrefix, SdkConstants.ANDROID_URI);
- }
- return toolsPrefix;
- }
-
- /**
- * Defines the merging type expected from the tool.
- */
- public enum MergeType {
- /**
- * Application merging type is used when packaging an application with a set of imported
- * libraries. The resulting merged android manifest is final and is not expected to be
- * imported in another application.
- */
- APPLICATION,
-
- /**
- * Library merging typee is used when packaging a library. The resulting android manifest
- * file will not merge in all the imported libraries this library depends on. Also the tools
- * annotations will not be removed as they can be useful when later importing the resulting
- * merged android manifest into an application.
- */
- LIBRARY
- }
-
- /**
- * Defines a property that can add or override itself into an XML document.
- */
- public interface AutoAddingProperty {
-
- /**
- * Add itself (possibly just override the current value) with the passed value
- * @param actionRecorder to record actions.
- * @param document the xml document to add itself to.
- * @param value the value to set of this property.
- */
- void addTo(@NonNull ActionRecorder actionRecorder,
- @NonNull XmlDocument document,
- @NonNull String value);
- }
-
- /**
- * Perform {@link com.android.manifmerger.ManifestMerger2.SystemProperty} injection.
- * @param mergingReport to log actions and errors.
- * @param xmlDocument the xml document to inject into.
- */
- protected void performSystemPropertiesInjection(
- MergingReport.Builder mergingReport,
- XmlDocument xmlDocument) {
- for (SystemProperty systemProperty : SystemProperty.values()) {
- String propertyOverride = mSystemPropertyResolver.getValue(systemProperty);
- if (propertyOverride != null) {
- systemProperty.addTo(
- mergingReport.getActionRecorder(), xmlDocument, propertyOverride);
- }
- }
- }
-
- /**
- * This class will hold all invocation parameters for the manifest merging tool.
- *
- * There are broadly three types of input to the merging tool :
- * <ul>
- * <li>Build types and flavors overriding manifests</li>
- * <li>Application main manifest</li>
- * <li>Library manifest files</li></lib>
- * </ul>
- *
- * Only the main manifest file is a mandatory parameter.
- *
- * High level description of the merging will be as follow :
- * <ol>
- * <li>Build type and flavors will be merged first in the order they were added. Highest
- * priority file added first, lowest added last.</li>
- * <li>Resulting document is merged with lower priority application main manifest file.</li>
- * <li>Resulting document is merged with each library file manifest file in the order
- * they were added. Highest priority added first, lowest added last.</li>
- * <li>Resulting document is returned as results of the merging process.</li>
- * </ol>
- *
- */
- public static class Invoker<T extends Invoker<T>>{
-
- protected final File mMainManifestFile;
-
- protected final ImmutableMap.Builder<SystemProperty, Object> mSystemProperties =
- new ImmutableMap.Builder<SystemProperty, Object>();
-
- protected final ILogger mLogger;
-
- protected final ImmutableMap.Builder<String, Object> mPlaceholders =
- new ImmutableMap.Builder<String, Object>();
-
- private final ImmutableList.Builder<Pair<String, File>> mLibraryFilesBuilder =
- new ImmutableList.Builder<Pair<String, File>>();
- private final ImmutableList.Builder<File> mFlavorsAndBuildTypeFiles =
- new ImmutableList.Builder<File>();
- private final ImmutableList.Builder<Feature> mFeaturesBuilder =
- new ImmutableList.Builder<Feature>();
- private final MergeType mMergeType;
- @Nullable private File mReportFile;
-
- /**
- * Sets a value for a {@link com.android.manifmerger.ManifestMerger2.SystemProperty}
- * @param override the property to set
- * @param value the value for the property
- * @return itself.
- */
- public Invoker setOverride(SystemProperty override, String value) {
- mSystemProperties.put(override, value);
- return thisAsT();
- }
-
- /**
- * Adds placeholders names and associated values for substitution.
- * @return itself.
- */
- public Invoker setPlaceHolderValues(Map<String, String> keyValuePairs) {
- mPlaceholders.putAll(keyValuePairs);
- return thisAsT();
- }
-
- /**
- * Adds a new placeholder name and value for substitution.
- * @return itself.
- */
- public Invoker setPlaceHolderValue(String placeHolderName, String value) {
- mPlaceholders.put(placeHolderName, value);
- return thisAsT();
- }
-
- /**
- * Optional behavior of the merging tool can be turned on by setting these Feature.
- */
- public enum Feature {
-
- /**
- * Keep all intermediary merged files during the merging process. This is particularly
- * useful for debugging/tracing purposes.
- */
- KEEP_INTERMEDIARY_STAGES,
-
- /**
- * When logging file names, use {@link java.io.File#getName()} rather than
- * {@link java.io.File#getPath()}
- */
- PRINT_SIMPLE_FILENAMES,
-
- /**
- * Perform a sweep after all merging activities to remove all fully qualified class
- * names and replace them with the equivalent short version.
- */
- EXTRACT_FQCNS,
-
- /**
- * Perform a sweep after all merging activities to remove all tools: decorations.
- */
- REMOVE_TOOLS_DECLARATIONS,
-
- /**
- * Do no perform placeholders replacement.
- */
- NO_PLACEHOLDER_REPLACEMENT
- }
-
- /**
- * Creates a new builder with the mandatory main manifest file.
- * @param mainManifestFile application main manifest file.
- * @param logger the logger interface to use.
- */
- private Invoker(
- @NonNull File mainManifestFile,
- @NonNull ILogger logger,
- MergeType mergeType) {
- this.mMainManifestFile = Preconditions.checkNotNull(mainManifestFile);
- this.mLogger = logger;
- this.mMergeType = mergeType;
- }
-
- /**
- * Sets the file to use to write the merging report. If not called,
- * the merging process will not write a report.
- * @param mergeReport the file to write the report in.
- * @return itself.
- */
- public Invoker setMergeReportFile(@NonNull File mergeReport) {
- mReportFile = mergeReport;
- return this;
- }
-
- /**
- * Add one library file manifest, will be added last in the list of library files which will
- * make the parameter the lowest priority library manifest file.
- * @param file the library manifest file to add.
- * @return itself.
- */
- public Invoker addLibraryManifest(File file) {
- if (mMergeType == MergeType.LIBRARY) {
- throw new IllegalStateException(
- "Cannot add library dependencies manifests when creating a library");
- }
- mLibraryFilesBuilder.add(Pair.of(file.getName(), file));
- return thisAsT();
- }
-
- public Invoker addLibraryManifests(List<Pair<String, File>> namesAndFiles) {
- if (mMergeType == MergeType.LIBRARY && !namesAndFiles.isEmpty()) {
- throw new IllegalStateException(
- "Cannot add library dependencies manifests when creating a library");
- }
- mLibraryFilesBuilder.addAll(namesAndFiles);
- return thisAsT();
- }
-
- /**
- * Add several library file manifests at then end of the list which will make them the
- * lowest priority manifest files. The relative priority between all the files passed as
- * parameters will be respected.
- * @param files library manifest files to add last.
- * @return itself.
- */
- public Invoker addLibraryManifests(File... files) {
- for (File file : files) {
- addLibraryManifest(file);
- }
- return thisAsT();
- }
-
- /**
- * Add a flavor or build type manifest file last in the list.
- * @param file build type or flavor manifest file
- * @return itself.
- */
- public Invoker addFlavorAndBuildTypeManifest(File file) {
- this.mFlavorsAndBuildTypeFiles.add(file);
- return thisAsT();
- }
-
- /**
- * Add several flavor or build type manifest files last in the list. Relative priorities
- * between the passed files as parameters will be respected.
- * @param files build type of flavor manifest files to add.
- * @return itself.
- */
- public Invoker addFlavorAndBuildTypeManifests(File... files) {
- this.mFlavorsAndBuildTypeFiles.add(files);
- return thisAsT();
- }
-
- /**
- * Sets some optional features for the merge tool.
- *
- * @param features one to many features to set.
- * @return itself.
- */
- public Invoker withFeatures(Feature...features) {
- mFeaturesBuilder.add(features);
- return thisAsT();
- }
-
- /**
- * Perform the merging and return the result.
- *
- * @return an instance of {@link com.android.manifmerger.MergingReport} that will give
- * access to all the logging and merging records.
- *
- * This method can be invoked several time and will re-do the file merges.
- *
- * @throws com.android.manifmerger.ManifestMerger2.MergeFailureException if the merging
- * cannot be completed successfully.
- */
- public MergingReport merge() throws MergeFailureException {
-
- // provide some free placeholders values.
- ImmutableMap<SystemProperty, Object> systemProperties = mSystemProperties.build();
- if (systemProperties.containsKey(SystemProperty.PACKAGE)) {
- // if the package is provided, make it available for placeholder replacement.
- mPlaceholders.put(PACKAGE_NAME, systemProperties.get(SystemProperty.PACKAGE));
- // as well as applicationId since package system property overrides everything
- // but not when output is a library since only the final (application)
- // application Id should be used to replace libraries "applicationId" placeholders.
- if (mMergeType != MergeType.LIBRARY) {
- mPlaceholders.put(APPLICATION_ID, systemProperties.get(SystemProperty.PACKAGE));
- }
- }
-
- ManifestMerger2 manifestMerger =
- new ManifestMerger2(
- mLogger,
- mMainManifestFile,
- mLibraryFilesBuilder.build(),
- mFlavorsAndBuildTypeFiles.build(),
- mFeaturesBuilder.build(),
- mPlaceholders.build(),
- new MapBasedKeyBasedValueResolver<SystemProperty>(systemProperties),
- mMergeType,
- Optional.fromNullable(mReportFile));
- return manifestMerger.merge();
- }
-
- @SuppressWarnings("unchecked")
- private T thisAsT() {
- return (T) this;
- }
- }
-
- /**
- * Helper class for map based placeholders key value pairs.
- */
- public static class MapBasedKeyBasedValueResolver<T> implements KeyBasedValueResolver<T> {
-
- private final ImmutableMap<T, Object> keyValues;
-
- public MapBasedKeyBasedValueResolver(Map<T, Object> keyValues) {
- this.keyValues = ImmutableMap.copyOf(keyValues);
- }
-
- @Nullable
- @Override
- public String getValue(@NonNull T key) {
- Object value = keyValues.get(key);
- return value == null ? null : value.toString();
- }
- }
-
- private static class ManifestInfo {
-
- private ManifestInfo(
- String name,
- File location,
- XmlDocument.Type type,
- Optional<String> mainManifestPackageName) {
- mName = name;
- mLocation = location;
- mType = type;
- mMainManifestPackageName = mainManifestPackageName;
- }
-
- private final String mName;
- private final File mLocation;
- private final XmlDocument.Type mType;
- private final Optional<String> mMainManifestPackageName;
-
- File getLocation() {
- return mLocation;
- }
-
- XmlDocument.Type getType() {
- return mType;
- }
-
- Optional<String> getMainManifestPackageName() {
- return mMainManifestPackageName;
- }
- }
-
- private static class LoadedManifestInfo extends ManifestInfo {
-
- @NonNull private final XmlDocument mXmlDocument;
- @NonNull private final Optional<String> mOriginalPackageName;
-
- private LoadedManifestInfo(@NonNull ManifestInfo manifestInfo,
- @NonNull Optional<String> originalPackageName,
- @NonNull XmlDocument xmlDocument) {
- super(manifestInfo.mName,
- manifestInfo.mLocation,
- manifestInfo.mType,
- manifestInfo.getMainManifestPackageName());
- mXmlDocument = xmlDocument;
- mOriginalPackageName = originalPackageName;
- }
-
- @NonNull
- public XmlDocument getXmlDocument() {
- return mXmlDocument;
- }
-
- @NonNull
- public Optional<String> getOriginalPackageName() {
- return mOriginalPackageName;
- }
- }
-
- /**
- * Implementation a {@link com.android.manifmerger.KeyResolver} capable of resolving all
- * selectors value in the context of the passed libraries to this merging activities.
- */
- static class SelectorResolver implements KeyResolver<String> {
-
- private final Map<String, String> mSelectors = new HashMap<String, String>();
-
- protected void addSelector(String key, String value) {
- mSelectors.put(key, value);
- }
-
- @Nullable
- @Override
- public String resolve(String key) {
- return mSelectors.get(key);
- }
-
- @Override
- public Iterable<String> getKeys() {
- return mSelectors.keySet();
- }
- }
-
- // a wrapper exception to all sorts of failure exceptions that can be thrown during merging.
- public static class MergeFailureException extends Exception {
-
- protected MergeFailureException(Exception cause) {
- super(cause);
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java
deleted file mode 100644
index cc036e7..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java
+++ /dev/null
@@ -1,681 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.manifmerger.AttributeModel.Hexadecimal32BitsWithMinimumValue;
-import static com.android.manifmerger.AttributeModel.MultiValueValidator;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.Immutable;
-import com.android.utils.SdkUtils;
-import com.android.xml.AndroidManifest;
-import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Model for the manifest file merging activities.
- * <p>
- *
- * This model will describe each element that is eligible for merging and associated merging
- * policies. It is not reusable as most of its interfaces are private but a future enhancement
- * could easily make this more generic/reusable if we need to merge more than manifest files.
- *
- */
- at Immutable
-class ManifestModel {
-
- /**
- * Interface responsible for providing a key extraction capability from a xml element.
- * Some elements store their keys as an attribute, some as a sub-element attribute, some don't
- * have any key.
- */
- @Immutable
- interface NodeKeyResolver {
-
- /**
- * Returns the key associated with this xml element.
- * @param xmlElement the xml element to get the key from
- * @return the key as a string to uniquely identify xmlElement from similarly typed elements
- * in the xml document or null if there is no key.
- */
- @Nullable String getKey(Element xmlElement);
-
- /**
- * Returns the attribute(s) used to store the xml element key.
- * @return the key attribute(s) name(s) or null of this element does not have a key.
- */
- @NonNull
- ImmutableList<String> getKeyAttributesNames();
- }
-
- /**
- * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that do not
- * provide any key (the element has to be unique in the xml document).
- */
- private static class NoKeyNodeResolver implements NodeKeyResolver {
-
- @Override
- @Nullable
- public String getKey(Element xmlElement) {
- return null;
- }
-
- @NonNull
- @Override
- public ImmutableList<String> getKeyAttributesNames() {
- return ImmutableList.of();
- }
- }
-
- /**
- * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that uses an
- * attribute to resolve the key value.
- */
- private static class AttributeBasedNodeKeyResolver implements NodeKeyResolver {
-
- @Nullable private final String mNamespaceUri;
- private final String mAttributeName;
-
- /**
- * Build a new instance capable of resolving an xml element key from the passed attribute
- * namespace and local name.
- * @param namespaceUri optional namespace for the attribute name.
- * @param attributeName attribute name
- */
- private AttributeBasedNodeKeyResolver(@Nullable String namespaceUri,
- @NonNull String attributeName) {
- this.mNamespaceUri = namespaceUri;
- this.mAttributeName = Preconditions.checkNotNull(attributeName);
- }
-
- @Override
- @Nullable
- public String getKey(Element xmlElement) {
- String key = mNamespaceUri == null
- ? xmlElement.getAttribute(mAttributeName)
- : xmlElement.getAttributeNS(mNamespaceUri, mAttributeName);
- if (Strings.isNullOrEmpty(key)) return null;
- return key;
- }
-
- @NonNull
- @Override
- public ImmutableList<String> getKeyAttributesNames() {
- return ImmutableList.of(mAttributeName);
- }
- }
-
- /**
- * Subclass of {@link com.android.manifmerger.ManifestModel.AttributeBasedNodeKeyResolver} that
- * uses "android:name" as the attribute.
- */
- private static final NodeKeyResolver DEFAULT_NAME_ATTRIBUTE_RESOLVER =
- new AttributeBasedNodeKeyResolver(ANDROID_URI, SdkConstants.ATTR_NAME);
-
- private static final NoKeyNodeResolver DEFAULT_NO_KEY_NODE_RESOLVER = new NoKeyNodeResolver();
-
- /**
- * A {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} capable of extracting the
- * element key first in an "android:name" attribute and if not value found there, in the
- * "android:glEsVersion" attribute.
- */
- private static final NodeKeyResolver NAME_AND_GLESVERSION_KEY_RESOLVER = new NodeKeyResolver() {
- private final NodeKeyResolver nameAttrResolver = DEFAULT_NAME_ATTRIBUTE_RESOLVER;
- private final NodeKeyResolver glEsVersionResolver =
- new AttributeBasedNodeKeyResolver(ANDROID_URI,
- AndroidManifest.ATTRIBUTE_GLESVERSION);
-
- @Nullable
- @Override
- public String getKey(Element xmlElement) {
- String key = nameAttrResolver.getKey(xmlElement);
- return Strings.isNullOrEmpty(key)
- ? glEsVersionResolver.getKey(xmlElement)
- : key;
- }
-
- @NonNull
- @Override
- public ImmutableList<String> getKeyAttributesNames() {
- return ImmutableList.of(SdkConstants.ATTR_NAME, AndroidManifest.ATTRIBUTE_GLESVERSION);
- }
- };
-
- /**
- * Specific {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} for intent-filter
- * elements.
- * Intent filters do not have a proper key, therefore their identity is really carried by
- * the presence of the action and category sub-elements.
- * We concatenate such elements sub-keys (after sorting them to work around declaration order)
- * and use that for the intent-filter unique key.
- */
- private static final NodeKeyResolver INTENT_FILTER_KEY_RESOLVER = new NodeKeyResolver() {
- @Nullable
- @Override
- public String getKey(Element element) {
- OrphanXmlElement xmlElement = new OrphanXmlElement(element);
- assert(xmlElement.getType() == NodeTypes.INTENT_FILTER);
- // concatenate all actions and categories attribute names.
- List<String> allSubElementKeys = new ArrayList<String>();
- NodeList childNodes = element.getChildNodes();
- for (int i = 0; i < childNodes.getLength(); i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() != Node.ELEMENT_NODE) continue;
- OrphanXmlElement subElement = new OrphanXmlElement((Element) child);
- if (subElement.getType() == NodeTypes.ACTION
- || subElement.getType() == NodeTypes.CATEGORY) {
- Attr nameAttribute = subElement.getXml()
- .getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (nameAttribute != null) {
- allSubElementKeys.add(nameAttribute.getValue());
- }
- }
- }
- Collections.sort(allSubElementKeys);
- return Joiner.on('+').join(allSubElementKeys);
- }
-
- @NonNull
- @Override
- public ImmutableList<String> getKeyAttributesNames() {
- return ImmutableList.of("action#name", "category#name");
- }
- };
-
- /**
- * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that
- * combined two attributes values to create the key value.
- */
- private static final class TwoAttributesBasedKeyResolver implements NodeKeyResolver {
- private final NodeKeyResolver firstAttributeKeyResolver;
- private final NodeKeyResolver secondAttributeKeyResolver;
-
- private TwoAttributesBasedKeyResolver(NodeKeyResolver firstAttributeKeyResolver,
- NodeKeyResolver secondAttributeKeyResolver) {
- this.firstAttributeKeyResolver = firstAttributeKeyResolver;
- this.secondAttributeKeyResolver = secondAttributeKeyResolver;
- }
-
- @Nullable
- @Override
- public String getKey(Element xmlElement) {
- String firstKey = firstAttributeKeyResolver.getKey(xmlElement);
- String secondKey = secondAttributeKeyResolver.getKey(xmlElement);
-
- return Strings.isNullOrEmpty(firstKey)
- ? secondKey
- : Strings.isNullOrEmpty(secondKey)
- ? firstKey
- : firstKey + "+" + secondKey;
- }
-
- @NonNull
- @Override
- public ImmutableList<String> getKeyAttributesNames() {
- return ImmutableList.of(firstAttributeKeyResolver.getKeyAttributesNames().get(0),
- secondAttributeKeyResolver.getKeyAttributesNames().get(0));
- }
- }
-
- private static final AttributeModel.BooleanValidator BOOLEAN_VALIDATOR =
- new AttributeModel.BooleanValidator();
-
- private static final boolean MULTIPLE_DECLARATION_FOR_SAME_KEY_ALLOWED = true;
-
- /**
- * Definitions of the support node types in the Android Manifest file.
- * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-intro.html/>}
- * for more details about the xml format.
- *
- * There is no DTD or schema associated with the file type so this is best effort in providing
- * some metadata on the elements of the Android's xml file.
- *
- * Each xml element is defined as an enum value and for each node, extra metadata is added
- * <ul>
- * <li>{@link com.android.manifmerger.MergeType} to identify how the merging engine
- * should process this element.</li>
- * <li>{@link com.android.manifmerger.ManifestModel.NodeKeyResolver} to resolve the
- * element's key. Elements can have an attribute like "android:name", others can use
- * a sub-element, and finally some do not have a key and are meant to be unique.</li>
- * <li>List of attributes models with special behaviors :
- * <ul>
- * <li>Smart substitution of class names to fully qualified class names using the
- * document's package declaration. The list's size can be 0..n</li>
- * <li>Implicit default value when no defined on the xml element.</li>
- * <li>{@link AttributeModel.Validator} to validate attribute value against.</li>
- * </ul>
- * </ul>
- *
- * It is of the outermost importance to keep this model correct as it is used by the merging
- * engine to make all its decisions. There should not be special casing in the engine, all
- * decisions must be represented here.
- *
- * If you find yourself needing to extend the model to support future requirements, do it here
- * and modify the engine to make proper decision based on the added metadata.
- */
- enum NodeTypes {
-
- /**
- * Action (contained in intent-filter)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/action-element.html>
- * Action Xml documentation</a>}
- */
- ACTION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
-
- /**
- * Activity (contained in application)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/activity-element.html>
- * Activity Xml documentation</a>}
- */
- ACTIVITY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel("parentActivityName").setIsPackageDependent(),
- AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
-
- /**
- * Activity-alias (contained in application)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/activity-alias-element.html>
- * Activity-alias Xml documentation</a>}
- */
- ACTIVITY_ALIAS(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel("targetActivity").setIsPackageDependent(),
- AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
-
- /**
- * Application (contained in manifest)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/application-element.html>
- * Application Xml documentation</a>}
- */
- APPLICATION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER,
- AttributeModel.newModel("backupAgent").setIsPackageDependent(),
- AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
-
- /**
- * Category (contained in intent-filter)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/category-element.html>
- * Category Xml documentation</a>}
- */
- CATEGORY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
-
- /**
- * Compatible-screens (contained in manifest)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/compatible-screens-element.html>
- * Category Xml documentation</a>}
- */
- COMPATIBLE_SCREENS(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
-
- /**
- * Data (contained in intent-filter)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/data-element.html>
- * Category Xml documentation</a>}
- */
- DATA(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
-
- /**
- * Grant-uri-permission (contained in intent-filter)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/grant-uri-permission-element.html>
- * Category Xml documentation</a>}
- */
- GRANT_URI_PERMISSION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
-
- /**
- * Instrumentation (contained in intent-filter)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/instrumentation-element.html>
- * Instrunentation Xml documentation</a>}
- */
- INSTRUMENTATION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
-
- /**
- * Intent-filter (contained in activity, activity-alias, service, receiver)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/intent-filter-element.html>
- * Intent-filter Xml documentation</a>}
- */
- INTENT_FILTER(MergeType.ALWAYS, INTENT_FILTER_KEY_RESOLVER,
- MULTIPLE_DECLARATION_FOR_SAME_KEY_ALLOWED),
-
- /**
- * Manifest (top level node)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>
- * Manifest Xml documentation</a>}
- */
- MANIFEST(MergeType.MERGE_CHILDREN_ONLY, DEFAULT_NO_KEY_NODE_RESOLVER),
-
- /**
- * Meta-data (contained in activity, activity-alias, application, provider, receiver)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/meta-data-element.html>
- * Meta-data Xml documentation</a>}
- */
- META_DATA(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
-
- /**
- * Path-permission (contained in provider)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/path-permission-element.html>
- * Meta-data Xml documentation</a>}
- */
- PATH_PERMISSION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
-
- /**
- * Permission-group (contained in manifest).
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-group-element.html>
- * Permission-group Xml documentation</a>}
- *
- */
- PERMISSION_GROUP(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(SdkConstants.ATTR_NAME)),
-
- /**
- * Permission (contained in manifest).
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-element.html>
- * Permission Xml documentation</a>}
- *
- */
- PERMISSION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(SdkConstants.ATTR_NAME),
- AttributeModel.newModel("protectionLevel")
- .setDefaultValue("normal")
- // TODO : this will need to be populated from
- // sdk/platforms/android-19/data/res/values.attrs_manifest.xml
- .setOnReadValidator(new MultiValueValidator(
- "normal", "dangerous", "signature", "signatureOrSystem"))),
-
- /**
- * Permission-tree (contained in manifest).
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-tree-element.html>
- * Permission-tree Xml documentation</a>}
- *
- */
- PERMISSION_TREE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(SdkConstants.ATTR_NAME)),
-
- /**
- * Provider (contained in application)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/provider-element.html>
- * Provider Xml documentation</a>}
- */
- PROVIDER(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(SdkConstants.ATTR_NAME)
- .setIsPackageDependent()),
-
- /**
- * Receiver (contained in application)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/receiver-element.html>
- * Receiver Xml documentation</a>}
- */
- RECEIVER(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
-
- /**
- * Screen (contained in compatible-screens)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/compatible-screens-element.html>
- * Receiver Xml documentation</a>}
- */
- SCREEN(MergeType.MERGE, new TwoAttributesBasedKeyResolver(
- new AttributeBasedNodeKeyResolver(ANDROID_URI, "screenSize"),
- new AttributeBasedNodeKeyResolver(ANDROID_URI, "screenDensity"))),
-
- /**
- * Service (contained in application)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/application-element.html>
- * Service Xml documentation</a>}
- */
- SERVICE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
-
- /**
- * Supports-gl-texture (contained in manifest)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/supports-gl-texture-element.html>
- * Support-screens Xml documentation</a>}
- */
- SUPPORTS_GL_TEXTURE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
-
- /**
- * Support-screens (contained in manifest)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/supports-screens-element.html>
- * Support-screens Xml documentation</a>}
- */
- SUPPORTS_SCREENS(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
-
- /**
- * Uses-configuration (contained in manifest)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-configuration-element.html>
- * Support-screens Xml documentation</a>}
- */
- USES_CONFIGURATION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
-
- /**
- * Uses-feature (contained in manifest)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-feature-element.html>
- * Uses-feature Xml documentation</a>}
- */
- USES_FEATURE(MergeType.MERGE, NAME_AND_GLESVERSION_KEY_RESOLVER,
- AttributeModel.newModel(AndroidManifest.ATTRIBUTE_REQUIRED)
- .setDefaultValue(SdkConstants.VALUE_TRUE)
- .setOnReadValidator(BOOLEAN_VALIDATOR)
- .setMergingPolicy(AttributeModel.OR_MERGING_POLICY),
- AttributeModel.newModel(AndroidManifest.ATTRIBUTE_GLESVERSION)
- .setDefaultValue("0x00010000")
- .setOnReadValidator(new Hexadecimal32BitsWithMinimumValue(0x00010000))),
-
- /**
- * Use-library (contained in application)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-library-element.html>
- * Use-library Xml documentation</a>}
- */
- USES_LIBRARY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
- AttributeModel.newModel(AndroidManifest.ATTRIBUTE_REQUIRED)
- .setDefaultValue(SdkConstants.VALUE_TRUE)
- .setOnReadValidator(BOOLEAN_VALIDATOR)
- .setMergingPolicy(AttributeModel.OR_MERGING_POLICY)),
-
- /**
- * Uses-permission (contained in application)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-permission-element.html>
- * Uses-permission Xml documentation</a>}
- */
- USES_PERMISSION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
-
- /**
- * Uses-sdk (contained in manifest)
- * <br>
- * <b>See also : </b>
- * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-sdk-element.html>
- * Uses-sdk Xml documentation</a>}
- */
- USES_SDK(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER,
- AttributeModel.newModel("minSdkVersion")
- .setDefaultValue(SdkConstants.VALUE_1)
- .setMergingPolicy(AttributeModel.NO_MERGING_POLICY),
- AttributeModel.newModel("maxSdkVersion")
- .setMergingPolicy(AttributeModel.NO_MERGING_POLICY),
- // TODO : model target's default value is minSdkVersion value.
- AttributeModel.newModel("targetSdkVersion")
- .setMergingPolicy(AttributeModel.NO_MERGING_POLICY)
- ),
-
- /**
- * Custom tag for any application specific element
- */
- CUSTOM(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER);
-
-
- private final MergeType mMergeType;
- private final NodeKeyResolver mNodeKeyResolver;
- private final ImmutableList<AttributeModel> mAttributeModels;
- private final boolean mMultipleDeclarationAllowed;
-
- NodeTypes(
- @NonNull MergeType mergeType,
- @NonNull NodeKeyResolver nodeKeyResolver,
- @Nullable AttributeModel.Builder... attributeModelBuilders) {
- this(mergeType, nodeKeyResolver, false, attributeModelBuilders);
- }
-
- NodeTypes(
- @NonNull MergeType mergeType,
- @NonNull NodeKeyResolver nodeKeyResolver,
- boolean mutipleDeclarationAllowed,
- @Nullable AttributeModel.Builder... attributeModelBuilders) {
- this.mMergeType = Preconditions.checkNotNull(mergeType);
- this.mNodeKeyResolver = Preconditions.checkNotNull(nodeKeyResolver);
- ImmutableList.Builder<AttributeModel> attributeModels =
- new ImmutableList.Builder<AttributeModel>();
- if (attributeModelBuilders != null) {
- for (AttributeModel.Builder attributeModelBuilder : attributeModelBuilders) {
- attributeModels.add(attributeModelBuilder.build());
- }
- }
- this.mAttributeModels = attributeModels.build();
- this.mMultipleDeclarationAllowed = mutipleDeclarationAllowed;
- }
-
- @NonNull
- NodeKeyResolver getNodeKeyResolver() {
- return mNodeKeyResolver;
- }
-
- ImmutableList<AttributeModel> getAttributeModels() {
- return mAttributeModels.asList();
- }
-
- @Nullable
- AttributeModel getAttributeModel(XmlNode.NodeName attributeName) {
- // mAttributeModels could be replaced with a Map if the number of models grows.
- for (AttributeModel attributeModel : mAttributeModels) {
- if (attributeModel.getName().equals(attributeName)) {
- return attributeModel;
- }
- }
- return null;
- }
-
- /**
- * Returns the Xml name for this node type
- */
- String toXmlName() {
- return SdkUtils.constantNameToXmlName(this.name());
- }
-
- /**
- * Returns the {@link NodeTypes} instance from an xml element name (without namespace
- * decoration). For instance, an xml element
- * <pre>
- * {@code
- * <activity android:name="foo">
- * ...
- * </activity>}
- * </pre>
- * has a xml simple name of "activity" which will resolve to {@link NodeTypes#ACTIVITY} value.
- *
- * Note : a runtime exception will be generated if no mapping from the simple name to a
- * {@link com.android.manifmerger.ManifestModel.NodeTypes} exists.
- *
- * @param xmlSimpleName the xml (lower-hyphen separated words) simple name.
- * @return the {@link NodeTypes} associated with that element name.
- */
- static NodeTypes fromXmlSimpleName(String xmlSimpleName) {
- String constantName = SdkUtils.xmlNameToConstantName(xmlSimpleName);
-
- try {
- return NodeTypes.valueOf(constantName);
- } catch (IllegalArgumentException e) {
- // if this element name is not a known tag, we categorize it as 'custom' which will
- // be simply merged. It will prevent us from catching simple spelling mistakes but
- // extensibility is a must have feature.
- return NodeTypes.CUSTOM;
- }
- }
-
- MergeType getMergeType() {
- return mMergeType;
- }
-
- /**
- * Returns true if multiple declaration for the same type and key are allowed or false if
- * there must be only one declaration of this element for a particular key value.
- */
- boolean areMultipleDeclarationAllowed() {
- return mMultipleDeclarationAllowed;
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Merger.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Merger.java
deleted file mode 100644
index 6a6f0b7..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Merger.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.base.Strings;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.StringTokenizer;
-
-/**
- * Command line interface to the {@link ManifestMerger2}
- */
-public class Merger {
-
- public static void main(String[] args) {
- try {
- System.exit(new Merger().process(args));
- } catch (FileNotFoundException e) {
- System.exit(1);
- }
- System.exit(0);
- }
-
- public int process(String[] args) throws FileNotFoundException {
-
- Iterator<String> arguments = Arrays.asList(args).iterator();
- // first pass to get all mandatory parameters.
- String mainManifest = null;
- StdLogger.Level logLevel = StdLogger.Level.INFO;
- ILogger logger = new StdLogger(logLevel);
- while (arguments.hasNext()) {
- String selector = arguments.next();
- if (!selector.startsWith("--")) {
- logger.error(null /* throwable */,
- "Invalid parameter " + selector + ", expected a command switch");
- return 1;
- }
- if ("--usage".equals(selector)) {
- usage();
- return 0;
- }
- if (!arguments.hasNext()) {
- logger.error(null /* throwable */,
- "Command switch " + selector + " has no value associated");
- return 1;
- }
- String value = arguments.next();
-
- if ("--main".equals(selector)) {
- mainManifest = value;
- }
- if ("--log".equals(selector)) {
- logLevel = StdLogger.Level.valueOf(value);
- }
- }
-
- if (mainManifest == null) {
- System.err.println("--main command switch not provided.");
- return 1;
- }
-
- // recreate the logger with the provided log level for the rest of the processing.
- logger = createLogger(logLevel);
- File mainManifestFile = checkPath(mainManifest);
- ManifestMerger2.Invoker invoker = createInvoker(
- mainManifestFile, logger);
-
- // second pass, get optional parameters and store them in the invoker.
- arguments = Arrays.asList(args).iterator();
- File outFile = null;
-
- // first pass to get all mandatory parameters.
- while (arguments.hasNext()) {
- String selector = arguments.next();
- String value = arguments.next();
- if (Strings.isNullOrEmpty(value)) {
- logger.error(null /* throwable */,
- "Empty value for switch " + selector);
- return 1;
- }
- if ("--libs".equals(selector)) {
- StringTokenizer stringTokenizer = new StringTokenizer(value, File.pathSeparator);
- while (stringTokenizer.hasMoreElements()) {
- File library = checkPath(stringTokenizer.nextToken());
- invoker.addLibraryManifest(library);
- }
- }
- if ("--overlays".equals(selector)) {
- StringTokenizer stringTokenizer = new StringTokenizer(value, File.pathSeparator);
- while (stringTokenizer.hasMoreElements()) {
- File library = checkPath(stringTokenizer.nextToken());
- invoker.addFlavorAndBuildTypeManifest(library);
- }
-
- }
- if ("--property".equals(selector)) {
- if (!value.contains("=")) {
- logger.error(null /* throwable */,
- "Invalid property setting, should be NAME=VALUE format");
- return 1;
- }
- try {
- ManifestMerger2.SystemProperty systemProperty = ManifestMerger2.SystemProperty
- .valueOf(value.substring(0, value.indexOf('='))
- .toUpperCase(Locale.ENGLISH));
- invoker.setOverride(systemProperty, value.substring(value.indexOf('=') + 1));
- } catch (IllegalArgumentException e) {
- logger.error(e, "Invalid property name "+ value.substring(0, value.indexOf('='))
- + ", allowed properties are : " + Joiner
- .on(',').join(ManifestMerger2.SystemProperty.values()));
- return 1;
- }
- }
- if ("--placeholder".equals(selector)) {
- if (!value.contains("=")) {
- logger.error(null /* throwable */,
- "Invalid placeholder setting, should be NAME=VALUE format");
- return 1;
- }
- invoker.setPlaceHolderValue(value.substring(0, value.indexOf('=')),
- value.substring(value.indexOf('=') + 1));
- }
- if ("--out".equals(selector)) {
- outFile = new File(value);
- }
- }
- try {
- MergingReport merge = invoker.merge();
- if (merge.getResult().isSuccess()) {
- XmlDocument xmlDocument = merge.getMergedDocument().get();
- if (outFile != null) {
- try {
- Files.write(xmlDocument.prettyPrint(), outFile, Charsets.UTF_8);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- } else {
- System.out.println(xmlDocument.prettyPrint());
- }
- } else {
- for (MergingReport.Record record : merge.getLoggingRecords()) {
- System.err.println(record);
- }
- }
- } catch (ManifestMerger2.MergeFailureException e) {
- logger.error(e, "Exception while merging manifests");
- return 1;
- }
- return 0;
- }
-
- protected ManifestMerger2.Invoker createInvoker(File mainManifestFile,
- ILogger logger) {
- return ManifestMerger2.newMerger(mainManifestFile, logger, ManifestMerger2.MergeType.APPLICATION);
- }
-
- public static void usage() {
- System.out.println("Android Manifest Merger Tool Version 2\n");
- System.out.println("Usage:");
- System.out.println("Merger --main mainAndroidManifest.xml");
- System.out.println("\t--log [VERBOSE, INFO, WARNING, ERROR]");
- System.out.println("\t--libs [path separated list of lib's manifests]");
- System.out.println("\t--overlays [path separated list of overlay's manifests]");
- System.out.println("\t--property ["
- + Joiner.on(" | ").join(ManifestMerger2.SystemProperty.values())
- + "=value]");
- System.out.println("\t--placeholder [name=value]");
- System.out.println("\t--out [path of the output file]");
- }
-
-
- @VisibleForTesting
- protected File checkPath(String path) throws FileNotFoundException {
- File file = new File(path);
- if (!file.exists()) {
- System.err.println(path + " does not exist");
- throw new FileNotFoundException(path);
- }
- return file;
- }
-
- @VisibleForTesting
- protected ILogger createLogger(StdLogger.Level level) {
- return new StdLogger(level);
- }
-
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java
deleted file mode 100644
index 2f2d717..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.concurrency.Immutable;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.utils.ILogger;
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-
-/**
- * Contains the result of 2 files merging.
- *
- * TODO: more work necessary, this is pretty raw as it stands.
- */
- at Immutable
-public class MergingReport {
-
- private final Optional<XmlDocument> mMergedDocument;
- private final Result mResult;
- // list of logging events, ordered by their recording time.
- private final ImmutableList<Record> mRecords;
- private final ImmutableList<String> mIntermediaryStages;
- private final Actions mActions;
-
- private MergingReport(Optional<XmlDocument> mergedDocument,
- @NonNull Result result,
- @NonNull ImmutableList<Record> records,
- @NonNull ImmutableList<String> intermediaryStages,
- @NonNull Actions actions) {
- mMergedDocument = mergedDocument;
- mResult = result;
- mRecords = records;
- mIntermediaryStages = intermediaryStages;
- mActions = actions;
- }
-
- /**
- * dumps all logging records to a logger.
- */
- public void log(ILogger logger) {
- for (Record record : mRecords) {
- switch(record.mSeverity) {
- case WARNING:
- logger.warning(record.toString());
- break;
- case ERROR:
- logger.error(null /* throwable */, record.toString());
- break;
- case INFO:
- logger.verbose(record.toString());
- break;
- default:
- logger.error(null /* throwable */, "Unhandled record type " + record.mSeverity);
- }
- }
- mActions.log(logger);
-
- if (!mResult.isSuccess()) {
- logger.warning("\nSee http://g.co/androidstudio/manifest-merger for more information"
- + " about the manifest merger.\n");
- }
- }
-
- /**
- * Return the resulting merged document.
- */
- public Optional<XmlDocument> getMergedDocument() {
- return mMergedDocument;
- }
-
- /**
- * Returns all the merging intermediary stages if
- * {@link com.android.manifmerger.ManifestMerger2.Invoker.Feature#KEEP_INTERMEDIARY_STAGES}
- * is set.
- */
- public ImmutableList<String> getIntermediaryStages() {
- return mIntermediaryStages;
- }
-
- /**
- * Overall result of the merging process.
- */
- public enum Result {
- SUCCESS,
-
- WARNING,
-
- ERROR;
-
- public boolean isSuccess() {
- return this == SUCCESS || this == WARNING;
- }
-
- public boolean isWarning() {
- return this == WARNING;
- }
-
- public boolean isError() {
- return this == ERROR;
- }
- }
-
- @NonNull
- public Result getResult() {
- return mResult;
- }
-
- @NonNull
- public ImmutableList<Record> getLoggingRecords() {
- return mRecords;
- }
-
- @NonNull
- public Actions getActions() {
- return mActions;
- }
-
- @NonNull
- public String getReportString() {
- switch (mResult) {
- case SUCCESS:
- return "Manifest merger executed successfully";
- case WARNING:
- return mRecords.size() > 1
- ? "Manifest merger exited with warnings, see logs"
- : "Manifest merger warning : " + mRecords.get(0).mLog;
- case ERROR:
- return mRecords.size() > 1
- ? "Manifest merger failed with multiple errors, see logs"
- : "Manifest merger failed : " + mRecords.get(0).mLog;
- default:
- return "Manifest merger returned an invalid result " + mResult;
- }
- }
-
- /**
- * Log record. This is used to give users some information about what is happening and
- * what might have gone wrong.
- */
- public static class Record {
-
-
- public enum Severity {WARNING, ERROR, INFO }
-
- private final Severity mSeverity;
- private final String mLog;
- private final SourceFilePosition mSourceLocation;
-
- private Record(
- @NonNull SourceFilePosition sourceLocation,
- @NonNull Severity severity,
- @NonNull String mLog) {
- this.mSourceLocation = sourceLocation;
- this.mSeverity = severity;
- this.mLog = mLog;
- }
-
- public Severity getSeverity() {
- return mSeverity;
- }
-
- public String getMessage() {
- return mLog;
- }
-
- @Override
- public String toString() {
- return mSourceLocation.toString() // needs short string.
- + " "
- + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, mSeverity.toString())
- + ":\n\t"
- + mLog;
- }
- }
-
- /**
- * This builder is used to accumulate logging, action recording and intermediary results as
- * well as final result of the merging activity.
- *
- * Once the merging is finished, the {@link #build()} is called to return an immutable version
- * of itself with all the logging, action recordings and xml files obtainable.
- *
- */
- static class Builder {
-
- private Optional<XmlDocument> mMergedDocument = Optional.absent();
- private ImmutableList.Builder<Record> mRecordBuilder = new ImmutableList.Builder<Record>();
- private ImmutableList.Builder<String> mIntermediaryStages = new ImmutableList.Builder<String>();
- private boolean mHasWarnings = false;
- private boolean mHasErrors = false;
- private ActionRecorder mActionRecorder = new ActionRecorder();
- private final ILogger mLogger;
-
- Builder(ILogger logger) {
- mLogger = logger;
- }
-
-
- Builder setMergedDocument(@NonNull XmlDocument mergedDocument) {
- mMergedDocument = Optional.of(mergedDocument);
- return this;
- }
-
- @VisibleForTesting
- Builder addMessage(@NonNull SourceFile sourceFile,
- int line,
- int column,
- @NonNull Record.Severity severity,
- @NonNull String message) {
- // The line and column used are 1-based, but SourcePosition uses zero-based.
- return addMessage(
- new SourceFilePosition(sourceFile, new SourcePosition(line - 1, column -1, -1)),
- severity,
- message);
- }
-
- Builder addMessage(@NonNull SourceFile sourceFile,
- @NonNull Record.Severity severity,
- @NonNull String message) {
- return addMessage(
- new SourceFilePosition(sourceFile, SourcePosition.UNKNOWN),
- severity,
- message);
- }
-
- Builder addMessage(@NonNull SourceFilePosition sourceFilePosition,
- @NonNull Record.Severity severity,
- @NonNull String message) {
- switch (severity) {
- case ERROR:
- mHasErrors = true;
- break;
- case WARNING:
- mHasWarnings = true;
- break;
- }
- mRecordBuilder.add(new Record(sourceFilePosition, severity, message));
- return this;
- }
-
- Builder addMergingStage(String xml) {
- mIntermediaryStages.add(xml);
- return this;
- }
-
- /**
- * Returns true if some fatal errors were reported.
- */
- boolean hasErrors() {
- return mHasErrors;
- }
-
- ActionRecorder getActionRecorder() {
- return mActionRecorder;
- }
-
- MergingReport build() {
- Result result = mHasErrors
- ? Result.ERROR
- : mHasWarnings
- ? Result.WARNING
- : Result.SUCCESS;
-
- return new MergingReport(
- mMergedDocument,
- result,
- mRecordBuilder.build(),
- mIntermediaryStages.build(),
- mActionRecorder.build());
- }
-
- public ILogger getLogger() {
- return mLogger;
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java
deleted file mode 100644
index 0f12930..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.ManifestModel.NodeTypes;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-
-import org.w3c.dom.Element;
-
-/**
- * An xml element that does not belong to a {@link com.android.manifmerger.XmlDocument}
- */
-public class OrphanXmlElement extends XmlNode {
-
- @NonNull
- private final Element mXml;
-
- @NonNull
- private final NodeTypes mType;
-
- public OrphanXmlElement(@NonNull Element xml) {
-
- mXml = Preconditions.checkNotNull(xml);
- NodeTypes nodeType;
- String elementName = mXml.getNodeName();
- // this is bit more complicated than it should be. Look first if there is a namespace
- // prefix in the name, most elements don't. If they do, however, strip it off if it is the
- // android prefix, but if it's custom namespace prefix, classify the node as CUSTOM.
- int indexOfColon = elementName.indexOf(':');
- if (indexOfColon != -1) {
- String androidPrefix = XmlUtils.lookupNamespacePrefix(xml, SdkConstants.ANDROID_URI);
- if (androidPrefix.equals(elementName.substring(0, indexOfColon))) {
- nodeType = NodeTypes.fromXmlSimpleName(elementName.substring(indexOfColon + 1));
- } else {
- nodeType = NodeTypes.CUSTOM;
- }
- } else {
- nodeType = NodeTypes.fromXmlSimpleName(elementName);
- }
- mType = nodeType;
- }
-
- /**
- * Returns true if this xml element's {@link NodeTypes} is
- * the passed one.
- */
- public boolean isA(NodeTypes type) {
- return this.mType == type;
- }
-
- @NonNull
- @Override
- public Element getXml() {
- return mXml;
- }
-
-
- @Override
- public NodeKey getId() {
- return new NodeKey(Strings.isNullOrEmpty(getKey())
- ? getName().toString()
- : getName().toString() + "#" + getKey());
- }
-
- @Override
- public NodeName getName() {
- return XmlNode.unwrapName(mXml);
- }
-
- /**
- * Returns this xml element {@link NodeTypes}
- */
- @NonNull
- public NodeTypes getType() {
- return mType;
- }
-
- /**
- * Returns the unique key for this xml element within the xml file or null if there can be only
- * one element of this type.
- */
- @Nullable
- public String getKey() {
- return mType.getNodeKeyResolver().getKey(mXml);
- }
-
- @NonNull
- @Override
- public SourcePosition getPosition() {
- return SourcePosition.UNKNOWN;
- }
-
- @Override
- @NonNull
- public SourceFile getSourceFile() {
- return SourceFile.UNKNOWN;
- }
-}
-
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderEncoder.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderEncoder.java
deleted file mode 100644
index aafde5e..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderEncoder.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-
-import java.util.regex.Matcher;
-
-/**
- * encode all non resolved placeholders key names.
- */
-public class PlaceholderEncoder {
-
- /**
- * Visits a document's entire tree and check each attribute for a placeholder existence.
- * If one is found, encode its name so tools like aapt will not object invalid characters and
- * such.
- * <p>
- *
- * @param xmlDocument the xml document to visit
- */
- public void visit(@NonNull XmlDocument xmlDocument) {
-
- visit(xmlDocument.getRootNode());
- }
-
- private void visit(@NonNull XmlElement xmlElement) {
-
- for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
- Matcher matcher = PlaceholderHandler.PATTERN.matcher(xmlAttribute.getValue());
- if (matcher.matches()) {
- String encodedValue = "dollar_openBracket_" + matcher.group(2) + "_closeBracket";
- xmlAttribute.getXml().setValue(encodedValue);
- }
- }
- for (XmlElement childElement : xmlElement.getMergeableElements()) {
- visit(childElement);
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java
deleted file mode 100644
index 8b0356c..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourcePosition;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Replaces all placeholders of the form ${name} with a tool invocation provided value
- */
-public class PlaceholderHandler {
-
- // interesting placeholders names that are documented to be automatically provided.
- public static final String INSTRUMENTATION_RUNNER = "instrumentationRunner";
- public static final String PACKAGE_NAME = "packageName";
- public static final String APPLICATION_ID = "applicationId";
-
- // regular expression to recognize placeholders like ${name}, potentially surrounded by a
- // prefix and suffix string. this will split in 3 groups, the prefix, the placeholder name, and
- // the suffix.
- static final Pattern PATTERN = Pattern.compile("([^\\$]*)\\$\\{([^\\}]*)\\}(.*)");
-
- /**
- * Interface to provide a value for a placeholder key.
- * @param <T> the key type
- */
- public interface KeyBasedValueResolver<T> {
-
- /**
- * Returns a placeholder value for the placeholder key or null if none exists.
- */
- @Nullable
- String getValue(@NonNull T key);
- }
-
- /**
- * Returns true if the passed string is a placeholder value, false otherwise.
- */
- public static boolean isPlaceHolder(@NonNull String string) {
- return PATTERN.matcher(string).matches();
- }
-
- /**
- * Visits a document's entire tree and check each attribute for a placeholder existence.
- * If one is found, delegate to the provided {@link KeyBasedValueResolver} to provide a value
- * for the placeholder.
- * <p>
- * If no value is provided, an error will be generated.
- *
- * @param xmlDocument the xml document to visit
- * @param valueProvider the placeholder value provider.
- * @param mergingReportBuilder to report errors and log actions.
- */
- public void visit(
- @NonNull ManifestMerger2.MergeType mergeType,
- @NonNull XmlDocument xmlDocument,
- @NonNull KeyBasedValueResolver<String> valueProvider,
- @NonNull MergingReport.Builder mergingReportBuilder) {
-
- visit(mergeType, xmlDocument.getRootNode(), valueProvider, mergingReportBuilder);
- }
-
- private void visit(
- @NonNull ManifestMerger2.MergeType mergeType,
- @NonNull XmlElement xmlElement,
- @NonNull KeyBasedValueResolver<String> valueProvider,
- @NonNull MergingReport.Builder mergingReportBuilder) {
-
- for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
-
- StringBuilder resultString = new StringBuilder();
- String inputString = xmlAttribute.getValue();
- Matcher matcher = PATTERN.matcher(inputString);
- if (matcher.matches()) {
- while (matcher.matches()) {
- String placeholderValue = valueProvider.getValue(matcher.group(2));
- // whatever precedes the placeholder key is added back to the string.
- resultString.append(matcher.group(1));
- if (placeholderValue == null) {
- // if this is a library, ignore the failure
- MergingReport.Record.Severity severity =
- mergeType == ManifestMerger2.MergeType.LIBRARY
- ? MergingReport.Record.Severity.INFO
- : MergingReport.Record.Severity.ERROR;
-
- xmlAttribute.addMessage(mergingReportBuilder, severity,
- String.format(
- "Attribute %1$s at %2$s requires a placeholder substitution"
- + " but no value for <%3$s> is provided.",
- xmlAttribute.getId(),
- xmlAttribute.printPosition(),
- matcher.group(2)
- ));
- // we add back the placeholder key, since this is not an error for libraries
- resultString.append("${");
- resultString.append(matcher.group(2));
- resultString.append("}");
- } else {
- // record the attribute set
- mergingReportBuilder.getActionRecorder().recordAttributeAction(
- xmlAttribute,
- SourcePosition.UNKNOWN,
- Actions.ActionType.INJECTED,
- null /* attributeOperationType */);
-
- // substitute the placeholder key with its value.
- resultString.append(placeholderValue);
- }
- // the new input string is the tail of the previous match, as it may contain
- // more placeholders to substitute.
- inputString = matcher.group(3);
- // reset the pattern matching with that new string to test for more placeholders
- matcher = PATTERN.matcher(inputString);
- }
- // append the last remainder (without placeholders) in the result string.
- resultString.append(inputString);
- xmlAttribute.getXml().setValue(resultString.toString());
- }
- }
- for (XmlElement childElement : xmlElement.getMergeableElements()) {
- visit(mergeType, childElement, valueProvider, mergingReportBuilder);
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java
deleted file mode 100644
index 59dfed0..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.Actions.ActionType;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-
-import org.w3c.dom.Node;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Validator that runs post merging activities and verifies that all "tools:" instructions
- * triggered an action by the merging tool.
- * <p>
- *
- * This is primarily to catch situations like a user entered a tools:remove="foo" directory on one
- * of its elements and that particular attribute was never removed during the merges possibly
- * indicating an unforeseen change of configuration.
- * <p>
- *
- * Most of the output from this validation should be warnings.
- */
-public class PostValidator {
-
- /**
- * Post validation of the merged document. This will essentially check that all merging
- * instructions were applied at least once.
- *
- * @param xmlDocument merged document to check.
- * @param mergingReport report for errors and warnings.
- */
- public static void validate(
- @NonNull XmlDocument xmlDocument,
- @NonNull MergingReport.Builder mergingReport) {
-
- Preconditions.checkNotNull(xmlDocument);
- Preconditions.checkNotNull(mergingReport);
- enforceAndroidNamespaceDeclaration(xmlDocument);
- reOrderElements(xmlDocument.getRootNode());
- validate(xmlDocument.getRootNode(),
- mergingReport.getActionRecorder().build(),
- mergingReport);
- }
-
- /**
- * Enforces {@link com.android.SdkConstants#ANDROID_URI} declaration in the top level element.
- * It is possible that the original manifest file did not contain any attribute declaration,
- * therefore not requiring a xmlns: declaration. Yet the implicit elements handling may have
- * added attributes requiring the namespace declaration.
- */
- private static void enforceAndroidNamespaceDeclaration(@NonNull XmlDocument xmlDocument) {
- XmlElement manifest = xmlDocument.getRootNode();
- for (XmlAttribute xmlAttribute : manifest.getAttributes()) {
- if (xmlAttribute.getXml().getName().startsWith(SdkConstants.XMLNS) &&
- xmlAttribute.getValue().equals(SdkConstants.ANDROID_URI)) {
- return;
- }
- }
- // if we are here, we did not find the namespace declaration, add it.
- manifest.getXml().setAttribute(SdkConstants.XMLNS + ":" + "android",
- SdkConstants.ANDROID_URI);
- }
-
- /**
- * Reorder child elements :
- * <li>
- * <ul> <application> is moved last in the list of children
- * of the <manifest> element.
- * <ul> uses-sdk is moved first in the list of children of the <manifest> element </ul>
- * </li>
- * @param xmlElement the root element of the manifest document.
- */
- private static void reOrderElements(XmlElement xmlElement) {
-
- reOrderApplication(xmlElement);
- reOrderUsesSdk(xmlElement);
- }
-
- /**
- * Reorder application element
- *
- * @param xmlElement the root element of the manifest document.
- */
- private static void reOrderApplication(XmlElement xmlElement) {
-
- // look up application element.
- Optional<XmlElement> element = xmlElement
- .getNodeByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
- if (!element.isPresent()) {
- return;
- }
- XmlElement applicationElement = element.get();
-
- List<Node> comments = XmlElement.getLeadingComments(applicationElement.getXml());
-
- // move the application's comments if any.
- for (Node comment : comments) {
- xmlElement.getXml().removeChild(comment);
- xmlElement.getXml().appendChild(comment);
- }
- // remove the application element and add it back, it will be automatically placed last.
- xmlElement.getXml().removeChild(applicationElement.getXml());
- xmlElement.getXml().appendChild(applicationElement.getXml());
- }
-
- /**
- * Reorder uses-sdk element
- *
- * @param xmlElement the root element of the manifest document.
- */
- private static void reOrderUsesSdk(XmlElement xmlElement) {
-
- // look up application element.
- Optional<XmlElement> element = xmlElement
- .getNodeByTypeAndKey(ManifestModel.NodeTypes.USES_SDK, null);
- if (!element.isPresent()) {
- return;
- }
-
- XmlElement usesSdk = element.get();
- Node firstChild = xmlElement.getXml().getFirstChild();
- // already the first element ?
- if (firstChild == usesSdk.getXml()) {
- return;
- }
-
- List<Node> comments = XmlElement.getLeadingComments(usesSdk.getXml());
-
- // move the application's comments if any.
- for (Node comment : comments) {
- xmlElement.getXml().removeChild(comment);
- xmlElement.getXml().insertBefore(comment, firstChild);
- }
- // remove the application element and add it back, it will be automatically placed last.
- xmlElement.getXml().removeChild(usesSdk.getXml());
- xmlElement.getXml().insertBefore(usesSdk.getXml(), firstChild);
- }
-
- /**
- * Validate an xml element and recursively its children elements, ensuring that all merging
- * instructions were applied.
- *
- * @param xmlElement xml element to validate.
- * @param actions the actions recorded during the merging activities.
- * @param mergingReport report for errors and warnings.
- * instructions were applied once or {@link MergingReport.Result#WARNING} otherwise.
- */
- private static void validate(
- XmlElement xmlElement,
- Actions actions,
- MergingReport.Builder mergingReport) {
-
- NodeOperationType operationType = xmlElement.getOperationType();
- switch (operationType) {
- case REPLACE:
- // we should find at least one rejected twin.
- if (!isNodeOperationPresent(xmlElement, actions, ActionType.REJECTED)) {
- xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
- String.format(
- "%1$s was tagged at %2$s:%3$d to replace another declaration "
- + "but no other declaration present",
- xmlElement.getId(),
- xmlElement.getDocument().getSourceFile().print(true),
- xmlElement.getPosition().getStartLine() + 1
- ));
- }
- break;
- case REMOVE:
- case REMOVE_ALL:
- // we should find at least one rejected twin.
- if (!isNodeOperationPresent(xmlElement, actions, ActionType.REJECTED)) {
- xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
- String.format(
- "%1$s was tagged at %2$s:%3$d to remove other declarations "
- + "but no other declaration present",
- xmlElement.getId(),
- xmlElement.getDocument().getSourceFile().print(true),
- xmlElement.getPosition().getStartLine() + 1
- ));
- }
- break;
- }
- validateAttributes(xmlElement, actions, mergingReport);
- validateAndroidAttributes(xmlElement, mergingReport);
- for (XmlElement child : xmlElement.getMergeableElements()) {
- validate(child, actions, mergingReport);
- }
- }
-
-
- /**
- * Verifies that all merging attributes on a passed xml element were applied.
- */
- private static void validateAttributes(
- XmlElement xmlElement,
- Actions actions,
- MergingReport.Builder mergingReport) {
-
- Collection<Map.Entry<XmlNode.NodeName, AttributeOperationType>> attributeOperations
- = xmlElement.getAttributeOperations();
- for (Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperation :
- attributeOperations) {
- switch (attributeOperation.getValue()) {
- case REMOVE:
- if (!isAttributeOperationPresent(
- xmlElement, attributeOperation, actions, ActionType.REJECTED)) {
- xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
- String.format(
- "%1$s@%2$s was tagged at %3$s:%4$d to remove other"
- + " declarations but no other declaration present",
- xmlElement.getId(),
- attributeOperation.getKey(),
- xmlElement.getDocument().getSourceFile().print(true),
- xmlElement.getPosition().getStartLine() + 1
- ));
- }
- break;
- case REPLACE:
- if (!isAttributeOperationPresent(
- xmlElement, attributeOperation, actions, ActionType.REJECTED)) {
- xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
- String.format(
- "%1$s@%2$s was tagged at %3$s:%4$d to replace other"
- + " declarations but no other declaration present",
- xmlElement.getId(),
- attributeOperation.getKey(),
- xmlElement.getDocument().getSourceFile().print(true),
- xmlElement.getPosition().getStartLine() + 1
- ));
- }
- break;
- }
- }
-
- }
-
- /**
- * Check in our list of applied actions that a particular
- * {@link com.android.manifmerger.Actions.ActionType} action was recorded on the passed element.
- * @return true if it was applied, false otherwise.
- */
- private static boolean isNodeOperationPresent(XmlElement xmlElement,
- Actions actions,
- ActionType action) {
-
- for (Actions.NodeRecord nodeRecord : actions.getNodeRecords(xmlElement.getId())) {
- if (nodeRecord.getActionType() == action) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Check in our list of attribute actions that a particular
- * {@link com.android.manifmerger.Actions.ActionType} action was recorded on the passed element.
- * @return true if it was applied, false otherwise.
- */
- private static boolean isAttributeOperationPresent(XmlElement xmlElement,
- Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperation,
- Actions actions,
- ActionType action) {
-
- for (Actions.AttributeRecord attributeRecord : actions.getAttributeRecords(
- xmlElement.getId(), attributeOperation.getKey())) {
- if (attributeRecord.getActionType() == action) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Validates all {@link com.android.manifmerger.XmlElement} attributes belonging to the
- * {@link com.android.SdkConstants#ANDROID_URI} namespace.
- *
- * @param xmlElement xml element to check the attributes from.
- * @param mergingReport report for errors and warnings.
- */
- private static void validateAndroidAttributes(XmlElement xmlElement,
- MergingReport.Builder mergingReport) {
-
- for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
- if (xmlAttribute.getModel() != null) {
- AttributeModel.Validator onWriteValidator = xmlAttribute.getModel()
- .getOnWriteValidator();
- if (onWriteValidator != null) {
- onWriteValidator.validates(
- mergingReport, xmlAttribute, xmlAttribute.getValue());
- }
- }
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java
deleted file mode 100644
index 6199fde..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.MergingReport.Record.Severity.ERROR;
-import static com.android.manifmerger.MergingReport.Record.Severity.WARNING;
-import static com.android.manifmerger.XmlNode.NodeKey;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.xml.AndroidManifest;
-import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Validates a loaded {@link XmlDocument} and check for potential inconsistencies in the model due
- * to user error or omission.
- *
- * This is implemented as a separate class so it can be invoked by tools independently from the
- * merging process.
- *
- * This validator will check the state of the loaded xml document before any merging activity is
- * attempted. It verifies things like a "tools:replace="foo" attribute has a "android:foo"
- * attribute also declared on the same element (since we want to replace its value).
- */
-public class PreValidator {
-
- private PreValidator(){
- }
-
- /**
- * Validates a loaded {@link com.android.manifmerger.XmlDocument} and return a status of the
- * merging model.
- *
- * Will return one the following status :
- * <ul>
- * <li>{@link com.android.manifmerger.MergingReport.Result#SUCCESS} : the merging model is
- * correct, merging should be attempted</li>
- * <li>{@link com.android.manifmerger.MergingReport.Result#WARNING} : the merging model
- * contains non fatal error, user should be notified, merging can be attempted</li>
- * <li>{@link com.android.manifmerger.MergingReport.Result#ERROR} : the merging model
- * contains errors, user must be notified, merging should not be attempted</li>
- * </ul>
- *
- * A successful validation does not mean that the merging will be successful, it only means
- * that the {@link com.android.SdkConstants#TOOLS_URI} instructions are correct and consistent.
- *
- * @param mergingReport report to log warnings and errors.
- * @param xmlDocument the loaded xml part.
- * @return one the {@link com.android.manifmerger.MergingReport.Result} value.
- */
- @NonNull
- public static MergingReport.Result validate(
- @NonNull MergingReport.Builder mergingReport,
- @NonNull XmlDocument xmlDocument) {
-
- validateManifestAttribute(
- mergingReport, xmlDocument.getRootNode(), xmlDocument.getFileType());
- return validate(mergingReport, xmlDocument.getRootNode());
- }
-
- private static MergingReport.Result validate(MergingReport.Builder mergingReport,
- XmlElement xmlElement) {
-
- validateAttributeInstructions(mergingReport, xmlElement);
-
- validateAndroidAttributes(mergingReport, xmlElement);
-
- checkSelectorPresence(mergingReport, xmlElement);
-
- // create a temporary hash map of children indexed by key to ensure key uniqueness.
- Map<NodeKey, XmlElement> childrenKeys = new HashMap<NodeKey, XmlElement>();
- for (XmlElement childElement : xmlElement.getMergeableElements()) {
-
- // if this element is tagged with 'tools:node=removeAll', ensure it has no other
- // attributes.
- if (childElement.getOperationType() == NodeOperationType.REMOVE_ALL) {
- validateRemoveAllOperation(mergingReport, childElement);
- } else {
- if (checkKeyPresence(mergingReport, childElement)) {
- XmlElement twin = childrenKeys.get(childElement.getId());
- if (twin != null && !childElement.getType().areMultipleDeclarationAllowed()) {
- // we have 2 elements with the same identity, if they are equals,
- // issue a warning, if not, issue an error.
- String message = String.format(
- "Element %1$s at %2$s duplicated with element declared at %3$s",
- childElement.getId(),
- childElement.printPosition(),
- childrenKeys.get(childElement.getId()).printPosition());
- if (twin.compareTo(childElement).isPresent()) {
- childElement.addMessage(mergingReport, ERROR, message);
- } else {
- childElement.addMessage(mergingReport, WARNING, message);
- }
- }
- childrenKeys.put(childElement.getId(), childElement);
- }
- validate(mergingReport, childElement);
- }
- }
- return mergingReport.hasErrors()
- ? MergingReport.Result.ERROR : MergingReport.Result.SUCCESS;
- }
-
- /**
- * Validate an xml declaration with 'tools:node="removeAll" annotation. There should not
- * be any other attribute declaration on this element.
- */
- private static void validateRemoveAllOperation(MergingReport.Builder mergingReport,
- XmlElement element) {
-
- NamedNodeMap attributes = element.getXml().getAttributes();
- if (attributes.getLength() > 1) {
- List<String> extraAttributeNames = new ArrayList<String>();
- for (int i = 0; i < attributes.getLength(); i++) {
- Node item = attributes.item(i);
- if (!(SdkConstants.TOOLS_URI.equals(item.getNamespaceURI()) &&
- NodeOperationType.NODE_LOCAL_NAME.equals(item.getLocalName()))) {
- extraAttributeNames.add(item.getNodeName());
- }
- }
- String message = String.format(
- "Element %1$s at %2$s annotated with 'tools:node=\"removeAll\"' cannot "
- + "have other attributes : %3$s",
- element.getId(),
- element.printPosition(),
- Joiner.on(',').join(extraAttributeNames)
- );
- element.addMessage(mergingReport, ERROR, message);
- }
- }
-
- private static void checkSelectorPresence(MergingReport.Builder mergingReport,
- XmlElement element) {
-
- Attr selectorAttribute =
- element.getXml().getAttributeNodeNS(SdkConstants.TOOLS_URI, Selector.SELECTOR_LOCAL_NAME);
- if (selectorAttribute!=null && !element.supportsSelector()) {
- String message = String.format(
- "Unsupported tools:selector=\"%1$s\" found on node %2$s at %3$s",
- selectorAttribute.getValue(),
- element.getId(),
- element.printPosition());
- element.addMessage(mergingReport, ERROR, message);
- }
- }
-
- private static void validateManifestAttribute(
- MergingReport.Builder mergingReport, XmlElement manifest, XmlDocument.Type fileType) {
- Attr attributeNode = manifest.getXml().getAttributeNode(AndroidManifest.ATTRIBUTE_PACKAGE);
- // it's ok for an overlay to not have a package name, it's not ok for a main manifest
- // and it's a warning for a library.
- if (attributeNode == null && fileType != XmlDocument.Type.OVERLAY) {
- manifest.addMessage(mergingReport,
- fileType == XmlDocument.Type.MAIN ? ERROR : WARNING,
- String.format(
- "Missing 'package' declaration in manifest at %1$s",
- manifest.printPosition()));
- }
- }
-
- /**
- * Checks that an element which is supposed to have a key does have one.
- * @param mergingReport report to log warnings and errors.
- * @param xmlElement xml element to check for key presence.
- * @return true if the element has a valid key or false it does not need one or it is invalid.
- */
- private static boolean checkKeyPresence(
- MergingReport.Builder mergingReport,
- XmlElement xmlElement) {
- ManifestModel.NodeKeyResolver nodeKeyResolver = xmlElement.getType().getNodeKeyResolver();
- ImmutableList<String> keyAttributesNames = nodeKeyResolver.getKeyAttributesNames();
- if (keyAttributesNames.isEmpty()) {
- return false;
- }
- if (Strings.isNullOrEmpty(xmlElement.getKey())) {
- // we should have a key but we don't.
- String message = keyAttributesNames.size() > 1
- ? String.format(
- "Missing one of the key attributes '%1$s' on element %2$s at %3$s",
- Joiner.on(',').join(keyAttributesNames),
- xmlElement.getId(),
- xmlElement.printPosition())
- : String.format(
- "Missing '%1$s' key attribute on element %2$s at %3$s",
- keyAttributesNames.get(0),
- xmlElement.getId(),
- xmlElement.printPosition());
- xmlElement.addMessage(mergingReport, ERROR, message);
- return false;
- }
- return true;
- }
-
- /**
- * Validate attributes part of the {@link com.android.SdkConstants#ANDROID_URI}
- * @param mergingReport report to log warnings and errors.
- * @param xmlElement xml element to check its attributes.
- */
- private static void validateAndroidAttributes(MergingReport.Builder mergingReport,
- XmlElement xmlElement) {
- for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
- AttributeModel model = xmlAttribute.getModel();
- if (model != null && model.getOnReadValidator() != null) {
- model.getOnReadValidator().validates(
- mergingReport, xmlAttribute, xmlAttribute.getValue());
- }
- }
- }
-
- /**
- * Validates attributes part of the {@link com.android.SdkConstants#TOOLS_URI}
- * @param mergingReport report to log warnings and errors.
- * @param xmlElement xml element to check its attributes.
- */
- private static void validateAttributeInstructions(
- MergingReport.Builder mergingReport,
- XmlElement xmlElement) {
-
- for (Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperationTypeEntry :
- xmlElement.getAttributeOperations()) {
-
- Optional<XmlAttribute> attribute = xmlElement
- .getAttribute(attributeOperationTypeEntry.getKey());
- switch(attributeOperationTypeEntry.getValue()) {
- case STRICT:
- break;
- case REMOVE:
- // check we are not provided a new value.
- if (attribute.isPresent()) {
- // Add one to startLine so the first line is displayed as 1.
- xmlElement.addMessage(mergingReport, ERROR, String.format(
- "tools:remove specified at line:%d for attribute %s, but "
- + "attribute also declared at line:%d, "
- + "do you want to use tools:replace instead ?",
- xmlElement.getPosition().getStartLine() + 1,
- attributeOperationTypeEntry.getKey(),
- attribute.get().getPosition().getStartLine() + 1
- ));
- }
- break;
- case REPLACE:
- // check we are provided a new value
- if (!attribute.isPresent()) {
- // Add one to startLine so the first line is displayed as 1.
- xmlElement.addMessage(mergingReport, ERROR, String.format(
- "tools:replace specified at line:%d for attribute %s, but "
- + "no new value specified",
- xmlElement.getPosition().getStartLine() + 1,
- attributeOperationTypeEntry.getKey()
- ));
- }
- break;
- default:
- throw new IllegalStateException("Unhandled AttributeOperationType " +
- attributeOperationTypeEntry.getValue());
- }
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java
deleted file mode 100644
index 73ece80..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.concurrency.Immutable;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-
-/**
- * Represents a selector to be able to identify manifest file xml elements.
- */
- at Immutable
-public class Selector {
-
- /**
- * local name for tools:selector attributes.
- */
- public static final String SELECTOR_LOCAL_NAME = "selector";
-
- @NonNull private final String mPackageName;
-
- public Selector(@NonNull String packageName) {
- mPackageName = Preconditions.checkNotNull(packageName);
- }
-
- /**
- * Returns true if the passed element is "selected" by this selector. If so, any action this
- * selector decorated will be applied to the element.
- */
- boolean appliesTo(XmlElement element) {
- Optional<XmlAttribute> packageName = element.getDocument().getPackage();
- return packageName.isPresent() && mPackageName.equals(packageName.get().getValue());
- }
-
- /**
- * Returns true if the passed resolver can resolve this selector, false otherwise.
- */
- boolean isResolvable(KeyResolver<String> resolver) {
- return resolver.resolve(mPackageName) != null;
- }
-
- @Override
- public String toString() {
- return mPackageName;
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java
deleted file mode 100644
index b2ff2ab..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.MergingReport.Result.ERROR;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.Immutable;
-import com.android.utils.ILogger;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Removes all "tools:" statements from the resulting xml.
- *
- * All attributes belonging to the {@link com.android.SdkConstants#ANDROID_URI} namespace will be
- * removed. If an element contained a "tools:node=\"remove\"" attribute, the element will be
- * deleted.
- */
- at Immutable
-public class ToolsInstructionsCleaner {
-
- private static final String REMOVE_OPERATION_XML_MAME =
- NodeOperationType.REMOVE.toCamelCaseName();
- private static final String REMOVE_ALL_OPERATION_XML_MAME =
- NodeOperationType.REMOVE_ALL.toCamelCaseName();
-
- /**
- * Cleans all attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace.
- *
- * @param document the xml document to clean
- * @param logger logger to use in case of errors and warnings.
- * @return the cleaned document or null if an error occurred.
- */
- @Nullable
- public static XmlDocument cleanToolsReferences(
- @NonNull XmlDocument document,
- @NonNull ILogger logger) {
-
- document = Preconditions.checkNotNull(document);
- logger = Preconditions.checkNotNull(logger);
- MergingReport.Result result = cleanToolsReferences(document.getRootNode().getXml(),
- logger);
- return result == MergingReport.Result.SUCCESS
- ? document.reparse()
- : null;
- }
-
- private static MergingReport.Result cleanToolsReferences(
- Element element,
- ILogger logger) {
-
- NamedNodeMap namedNodeMap = element.getAttributes();
- if (namedNodeMap != null) {
- // make a copy of the original list of attributes as we will remove some during this
- // process.
- List<Node> attributes = new ArrayList<Node>();
- for (int i = 0; i < namedNodeMap.getLength(); i++) {
- attributes.add(namedNodeMap.item(i));
- }
- for (Node attribute : attributes) {
- if (SdkConstants.TOOLS_URI.equals(attribute.getNamespaceURI())) {
- // we need to special case when the element contained tools:node="remove"
- // since it also needs to be deleted unless it had a selector.
- // if this is ools:node="removeAll", we always delete the element whether or
- // not there is a tools:selector.
- boolean hasSelector = namedNodeMap.getNamedItemNS(
- SdkConstants.TOOLS_URI, "selector") != null;
- if (attribute.getLocalName().equals(NodeOperationType.NODE_LOCAL_NAME)
- && (attribute.getNodeValue().equals(REMOVE_ALL_OPERATION_XML_MAME)
- || (attribute.getNodeValue().equals(REMOVE_OPERATION_XML_MAME))
- && !hasSelector)) {
-
- if (element.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
- logger.error(null /* Throwable */,
- String.format(
- "tools:node=\"%1$s\" not allowed on top level %2$s element",
- attribute.getNodeValue(),
- XmlNode.unwrapName(element)));
- return ERROR;
- } else {
- element.getParentNode().removeChild(element);
- }
- } else {
- // anything else, we just clean the attribute.
- element.removeAttributeNS(
- attribute.getNamespaceURI(), attribute.getLocalName());
- }
- }
- // this could also be the xmlns:tools declaration.
- if (attribute.getNodeName().startsWith(SdkConstants.XMLNS_PREFIX)
- && SdkConstants.TOOLS_URI.equals(attribute.getNodeValue())) {
- element.removeAttribute(attribute.getNodeName());
- }
- }
- }
- // make a copy of the element children since we will be removing some during
- // this process, we don't want side effects.
- NodeList childNodes = element.getChildNodes();
- ImmutableList.Builder<Element> childElements = ImmutableList.builder();
- for (int i = 0; i < childNodes.getLength(); i++) {
- Node node = childNodes.item(i);
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- childElements.add((Element) node);
- }
- }
- for (Element childElement : childElements.build()) {
- if (cleanToolsReferences(childElement, logger) == ERROR) {
- return ERROR;
- }
- }
- return MergingReport.Result.SUCCESS;
- }
-
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java
deleted file mode 100644
index dd457e7..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableSet;
-
-import org.w3c.dom.Attr;
-
-/**
- * Defines an XML attribute inside a {@link XmlElement}.
- *
- * Basically a facade object on {@link Attr} objects with some added features like automatic
- * namespace handling, manifest merger friendly identifiers and smart replacement of shortened
- * full qualified class names using manifest node's package setting from the the owning Android's
- * document.
- */
-public class XmlAttribute extends XmlNode {
-
- private final XmlElement mOwnerElement;
- private final Attr mXml;
- @Nullable
- private final AttributeModel mAttributeModel;
-
- /**
- * Creates a new facade object to a {@link Attr} xml attribute in a
- * {@link XmlElement}.
- *
- * @param ownerElement the xml node object owning this attribute.
- * @param xml the xml definition of the attribute.
- */
- public XmlAttribute(
- @NonNull XmlElement ownerElement,
- @NonNull Attr xml,
- @Nullable AttributeModel attributeModel) {
- this.mOwnerElement = Preconditions.checkNotNull(ownerElement);
- this.mXml = Preconditions.checkNotNull(xml);
- this.mAttributeModel = attributeModel;
- if (mAttributeModel != null && mAttributeModel.isPackageDependent()) {
- String value = mXml.getValue();
- if (value == null || value.isEmpty()) return;
- // placeholders are never expanded.
- if (!PlaceholderHandler.isPlaceHolder(value)) {
- String pkg = mOwnerElement.getDocument().getPackageNameForAttributeExpansion();
- // We know it's a shortened FQCN if it starts with a dot
- // or does not contain any dot.
- if (value.indexOf('.') == -1 || value.charAt(0) == '.') {
- if (value.charAt(0) == '.') {
- value = pkg + value;
- } else {
- value = pkg + '.' + value;
- }
- mXml.setValue(value);
- }
- }
- }
- }
-
- /**
- * Returns the attribute's name, providing isolation from details like namespaces handling.
- */
- @Override
- public NodeName getName() {
- return XmlNode.unwrapName(mXml);
- }
-
- /**
- * Returns the attribute's value
- */
- public String getValue() {
- return mXml.getValue();
- }
-
- /**
- * Returns a display friendly identification string that can be used in machine and user
- * readable messages.
- */
- @Override
- public NodeKey getId() {
- // (Id of the parent element)@(my name)
- String myName = mXml.getNamespaceURI() == null ? mXml.getName() : mXml.getLocalName();
- return new NodeKey(mOwnerElement.getId() + "@" + myName);
- }
-
- @NonNull
- @Override
- public SourcePosition getPosition() {
- try {
- return mOwnerElement.getDocument().getNodePosition(this);
- } catch(Exception e) {
- return SourcePosition.UNKNOWN;
- }
- }
-
- @NonNull
- @Override
- public Attr getXml() {
- return mXml;
- }
-
- @Nullable
- public AttributeModel getModel() {
- return mAttributeModel;
- }
-
- XmlElement getOwnerElement() {
- return mOwnerElement;
- }
-
- void mergeInHigherPriorityElement(XmlElement higherPriorityElement,
- MergingReport.Builder mergingReport) {
-
- // does the higher priority has the same attribute as myself ?
- Optional<XmlAttribute> higherPriorityAttributeOptional =
- higherPriorityElement.getAttribute(getName());
-
- AttributeOperationType attributeOperationType =
- higherPriorityElement.getAttributeOperationType(getName());
-
- if (higherPriorityAttributeOptional.isPresent()) {
-
- XmlAttribute higherPriorityAttribute = higherPriorityAttributeOptional.get();
- handleBothAttributePresent(
- mergingReport, higherPriorityAttribute, attributeOperationType);
- return;
- }
-
- // it does not exist, verify if we are supposed to remove it.
- if (attributeOperationType == AttributeOperationType.REMOVE) {
- // record the fact the attribute was actively removed.
- mergingReport.getActionRecorder().recordAttributeAction(
- this,
- Actions.ActionType.REJECTED,
- AttributeOperationType.REMOVE);
- return;
- }
-
- // the node is not defined in the higher priority element, it's defined in this lower
- // priority element, we need to merge this lower priority attribute value with a potential
- // higher priority default value (implicitly set on the higher priority element).
- String mergedValue = mergeThisAndDefaultValue(mergingReport, higherPriorityElement);
- if (mergedValue == null) {
- return;
- }
-
- // ok merge it in the higher priority element.
- getName().addToNode(higherPriorityElement.getXml(), mergedValue);
-
- // and record the action.
- mergingReport.getActionRecorder().recordAttributeAction(
- this,
- Actions.ActionType.ADDED,
- getOwnerElement().getAttributeOperationType(getName()));
- }
-
- /**
- * Handles merging of two attributes value explicitly declared in xml elements.
- *
- * @param report report to log errors and actions.
- * @param higherPriority higher priority attribute we should merge this attribute with.
- * @param operationType user operation type optionally requested by the user.
- */
- private void handleBothAttributePresent(
- MergingReport.Builder report,
- XmlAttribute higherPriority,
- AttributeOperationType operationType) {
-
- // handles tools: attribute separately.
-
- if (getXml().getNamespaceURI() != null
- && getXml().getNamespaceURI().equals(SdkConstants.TOOLS_URI)) {
- handleBothToolsAttributePresent(higherPriority);
- return;
- }
-
- // the attribute is present on both elements, there are 2 possibilities :
- // 1. tools:replace was specified, replace the value.
- // 2. nothing was specified, the values should be equal or this is an error.
- if (operationType == AttributeOperationType.REPLACE) {
- // record the fact the lower priority attribute was rejected.
- report.getActionRecorder().recordAttributeAction(
- this,
- Actions.ActionType.REJECTED,
- AttributeOperationType.REPLACE);
- return;
- }
- // if the values are the same, then it's fine, otherwise flag the error.
- if (mAttributeModel != null) {
- String mergedValue = mAttributeModel.getMergingPolicy()
- .merge(higherPriority.getValue(), getValue());
- if (mergedValue != null) {
- higherPriority.mXml.setValue(mergedValue);
- } else {
- addConflictingValueMessage(report, higherPriority);
- }
- return;
- }
- // no merging policy, for now revert on checking manually for equality.
- if (!getValue().equals(higherPriority.getValue())) {
- addConflictingValueMessage(report, higherPriority);
- }
- }
-
- /**
- * Handles tools: namespace attributes presence in both documents.
- * @param higherPriority the higherPriority attribute
- */
- private void handleBothToolsAttributePresent(
- XmlAttribute higherPriority) {
-
- // do not merge tools:node attributes, the higher priority one wins.
- if (getName().getLocalName().equals(NodeOperationType.NODE_LOCAL_NAME)) {
- return;
- }
-
- // everything else should be merged, duplicates should be eliminated.
- Splitter splitter = Splitter.on(',');
- ImmutableSet.Builder<String> targetValues = ImmutableSet.builder();
- targetValues.addAll(splitter.split(higherPriority.getValue()));
- targetValues.addAll(splitter.split(getValue()));
- higherPriority.getXml().setValue(Joiner.on(',').join(targetValues.build()));
- }
-
- /**
- * Merge this attribute value (on a lower priority element) with a implicit default value
- * (implicitly declared on the implicitNode).
- * @param mergingReport report to log errors and actions.
- * @param implicitNode the lower priority node where the implicit attribute value resides.
- * @return the merged value that should be stored in the attribute or null if nothing should
- * be stored.
- */
- private String mergeThisAndDefaultValue(MergingReport.Builder mergingReport,
- XmlElement implicitNode) {
-
- String mergedValue = getValue();
- if (mAttributeModel == null || mAttributeModel.getDefaultValue() == null
- || !mAttributeModel.getMergingPolicy().shouldMergeDefaultValues()) {
- return mergedValue;
- }
- String defaultValue = mAttributeModel.getDefaultValue();
- if (defaultValue.equals(mergedValue)) {
- // even though the lower priority attribute is only declared and its value is the same
- // as the default value, ensure it gets added to the higher priority node.
- return mergedValue;
- } else {
- // ok, the default value and actual declaration are different, delegate to the
- // merging policy to figure out what value should be used if any.
- mergedValue = mAttributeModel.getMergingPolicy().merge(defaultValue, mergedValue);
- if (mergedValue == null) {
- addIllegalImplicitOverrideMessage(mergingReport, mAttributeModel, implicitNode);
- return null;
- }
- if (mergedValue.equals(defaultValue)) {
- // no need to forcefully add an attribute to the parent with its default value
- // since it was not declared to start with.
- return null;
- }
- }
- return mergedValue;
- }
-
- /**
- * Merge this attribute value with a lower priority node attribute default value.
- * The attribute is not explicitly set on the implicitNode, yet it exist on this attribute
- * {@link com.android.manifmerger.XmlElement} higher priority owner.
- *
- * @param mergingReport report to log errors and actions.
- * @param implicitNode the lower priority node where the implicit attribute value resides.
- */
- void mergeWithLowerPriorityDefaultValue(
- MergingReport.Builder mergingReport, XmlElement implicitNode) {
-
- if (mAttributeModel == null || mAttributeModel.getDefaultValue() == null
- || !mAttributeModel.getMergingPolicy().shouldMergeDefaultValues()) {
- return;
- }
- // if this value has been explicitly set to replace the implicit default value, just
- // log the action.
- if (mOwnerElement.getAttributeOperationType(getName()) == AttributeOperationType.REPLACE) {
- mergingReport.getActionRecorder().recordImplicitRejection(this, implicitNode);
- return;
- }
- String mergedValue = mAttributeModel.getMergingPolicy().merge(
- getValue(), mAttributeModel.getDefaultValue());
- if (mergedValue == null) {
- addIllegalImplicitOverrideMessage(mergingReport, mAttributeModel, implicitNode);
- } else {
- getXml().setValue(mergedValue);
- mergingReport.getActionRecorder().recordAttributeAction(
- this,
- Actions.ActionType.MERGED,
- null /* attributeOperationType */);
- }
- }
-
- private void addIllegalImplicitOverrideMessage(
- @NonNull MergingReport.Builder mergingReport,
- @NonNull AttributeModel attributeModel,
- @NonNull XmlElement implicitNode) {
- String error = String.format("Attribute %1$s value=(%2$s) at %3$s"
- + " cannot override implicit default value=(%4$s) at %5$s",
- getId(),
- getValue(),
- printPosition(),
- attributeModel.getDefaultValue(),
- implicitNode.printPosition());
- addMessage(mergingReport, MergingReport.Record.Severity.ERROR, error);
- }
-
- private void addConflictingValueMessage(
- MergingReport.Builder report,
- XmlAttribute higherPriority) {
-
- Actions.AttributeRecord attributeRecord = report.getActionRecorder()
- .getAttributeCreationRecord(higherPriority);
-
- String error;
- if (getOwnerElement().getType().getMergeType() == MergeType.MERGE_CHILDREN_ONLY) {
- error = String.format(
- "Attribute %1$s value=(%2$s) from %3$s\n"
- + "\tis also present at %4$s value=(%5$s).\n"
- + "\tAttributes of <%6$s> elements are not merged.",
- higherPriority.getId(),
- higherPriority.getValue(),
- attributeRecord != null
- ? attributeRecord.getActionLocation().print(true /*shortFormat*/)
- : "(unknown)",
- printPosition(),
- getValue(),
- getOwnerElement().getType().toXmlName());
- } else {
- error = String.format(
- "Attribute %1$s value=(%2$s) from %3$s\n"
- + "\tis also present at %4$s value=(%5$s).\n"
- + "\tSuggestion: add 'tools:replace=\"%6$s\"' to <%7$s> element "
- + "at %8$s to override.",
- higherPriority.getId(),
- higherPriority.getValue(),
- attributeRecord != null
- ? attributeRecord.getActionLocation().print(true /*shortFormat*/)
- : "(unknown)",
- printPosition(),
- getValue(),
- mXml.getName(),
- getOwnerElement().getType().toXmlName(),
- higherPriority.getOwnerElement().printPosition());
- }
- higherPriority.addMessage(report,
- attributeRecord != null
- ? attributeRecord.getActionLocation().getPosition()
- : SourcePosition.UNKNOWN,
- MergingReport.Record.Severity.ERROR, error);
- }
-
- void addMessage(MergingReport.Builder report,
- MergingReport.Record.Severity severity,
- String message) {
- addMessage(report, getPosition(), severity, message);
- }
-
- void addMessage(MergingReport.Builder report,
- SourcePosition position,
- MergingReport.Record.Severity severity,
- String message) {
- report.addMessage(
- new SourceFilePosition(getOwnerElement().getDocument().getSourceFile(), position),
- severity, message);
- }
-
- @NonNull
- @Override
- public SourceFile getSourceFile() {
- return getOwnerElement().getSourceFile();
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java
deleted file mode 100755
index 1ad2c65..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java
+++ /dev/null
@@ -1,576 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.ManifestMerger2.SystemProperty;
-import static com.android.manifmerger.ManifestModel.NodeTypes.USES_PERMISSION;
-import static com.android.manifmerger.ManifestModel.NodeTypes.USES_SDK;
-import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.ide.common.xml.XmlFormatPreferences;
-import com.android.ide.common.xml.XmlFormatStyle;
-import com.android.ide.common.xml.XmlPrettyPrinter;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.utils.Pair;
-import com.android.utils.PositionXmlParser;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Represents a loaded xml document.
- *
- * Has pointers to the root {@link XmlElement} element and provides services to persist the document
- * to an external format. Also provides abilities to be merged with other
- * {@link com.android.manifmerger.XmlDocument} as well as access to the line numbers for all
- * document's xml elements and attributes.
- *
- */
-public class XmlDocument {
-
- private static final String DEFAULT_SDK_VERSION = "1";
-
- /**
- * The document type.
- */
- enum Type {
- /**
- * A manifest overlay as found in the build types and variants.
- */
- OVERLAY,
- /**
- * The main android manifest file.
- */
- MAIN,
- /**
- * A library manifest that is imported in the application.
- */
- LIBRARY
- }
-
- private final Element mRootElement;
- // this is initialized lazily to avoid un-necessary early parsing.
- private final AtomicReference<XmlElement> mRootNode = new AtomicReference<XmlElement>(null);
- private final SourceFile mSourceFile;
- private final KeyResolver<String> mSelectors;
- private final KeyBasedValueResolver<SystemProperty> mSystemPropertyResolver;
- private final Type mType;
- private final Optional<String> mMainManifestPackageName;
-
- public XmlDocument(
- @NonNull SourceFile sourceLocation,
- @NonNull KeyResolver<String> selectors,
- @NonNull KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
- @NonNull Element element,
- @NonNull Type type,
- @NonNull Optional<String> mainManifestPackageName) {
- this.mSourceFile = Preconditions.checkNotNull(sourceLocation);
- this.mRootElement = Preconditions.checkNotNull(element);
- this.mSelectors = Preconditions.checkNotNull(selectors);
- this.mSystemPropertyResolver = Preconditions.checkNotNull(systemPropertyResolver);
- this.mType = type;
- this.mMainManifestPackageName = mainManifestPackageName;
- }
-
- public Type getFileType() {
- return mType;
- }
-
- /**
- * Returns a pretty string representation of this document.
- */
- public String prettyPrint() {
- return XmlPrettyPrinter.prettyPrint(
- getXml(),
- XmlFormatPreferences.defaults(),
- XmlFormatStyle.get(getRootNode().getXml()),
- null, /* endOfLineSeparator */
- false /* endWithNewLine */);
- }
-
- /**
- * merge this higher priority document with a higher priority document.
- * @param lowerPriorityDocument the lower priority document to merge in.
- * @param mergingReportBuilder the merging report to record errors and actions.
- * @return a new merged {@link com.android.manifmerger.XmlDocument} or
- * {@link Optional#absent()} if there were errors during the merging activities.
- */
- public Optional<XmlDocument> merge(
- XmlDocument lowerPriorityDocument,
- MergingReport.Builder mergingReportBuilder) {
-
- if (getFileType() == Type.MAIN) {
- mergingReportBuilder.getActionRecorder().recordDefaultNodeAction(getRootNode());
- }
-
- getRootNode().mergeWithLowerPriorityNode(
- lowerPriorityDocument.getRootNode(), mergingReportBuilder);
-
- addImplicitElements(lowerPriorityDocument, mergingReportBuilder);
-
- // force re-parsing as new nodes may have appeared.
- return mergingReportBuilder.hasErrors()
- ? Optional.<XmlDocument>absent()
- : Optional.of(reparse());
- }
-
- /**
- * Forces a re-parsing of the document
- * @return a new {@link com.android.manifmerger.XmlDocument} with up to date information.
- */
- public XmlDocument reparse() {
- return new XmlDocument(
- mSourceFile,
- mSelectors,
- mSystemPropertyResolver,
- mRootElement,
- mType,
- mMainManifestPackageName);
- }
-
- /**
- * Returns a {@link com.android.manifmerger.KeyResolver} capable of resolving all selectors
- * types
- */
- public KeyResolver<String> getSelectors() {
- return mSelectors;
- }
-
- /**
- * Returns the {@link com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver} capable
- * of resolving all injected {@link com.android.manifmerger.ManifestMerger2.SystemProperty}
- */
- public KeyBasedValueResolver<SystemProperty> getSystemPropertyResolver() {
- return mSystemPropertyResolver;
- }
-
- /**
- * Compares this document to another {@link com.android.manifmerger.XmlDocument} ignoring all
- * attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace.
- *
- * @param other the other document to compare against.
- * @return a {@link String} describing the differences between the two XML elements or
- * {@link Optional#absent()} if they are equals.
- */
- public Optional<String> compareTo(XmlDocument other) {
- return getRootNode().compareTo(other.getRootNode());
- }
-
- /**
- * Returns the position of the specified {@link XmlNode}.
- */
- @NonNull
- static SourcePosition getNodePosition(XmlNode node) {
- return getNodePosition(node.getXml());
- }
-
- /**
- * Returns the position of the specified {@link org.w3c.dom.Node}.
- */
- @NonNull
- static SourcePosition getNodePosition(Node xml) {
- return PositionXmlParser.getPosition(xml);
- }
-
- @NonNull
- public SourceFile getSourceFile() {
- return mSourceFile;
- }
-
- public synchronized XmlElement getRootNode() {
- if (mRootNode.get() == null) {
- this.mRootNode.set(new XmlElement(mRootElement, this));
- }
- return mRootNode.get();
- }
-
- public Optional<XmlElement> getByTypeAndKey(
- ManifestModel.NodeTypes type,
- @Nullable String keyValue) {
-
- return getRootNode().getNodeByTypeAndKey(type, keyValue);
- }
-
- /**
- * Package name for this android manifest which will be used to resolve
- * partial path. In the case of Overlays, this is absent and the main
- * manifest packageName must be used.
- * @return the package name to do partial class names resolution.
- */
- public String getPackageName() {
- return mMainManifestPackageName.or(mRootElement.getAttribute("package"));
- }
-
- /**
- * Returns the package name to use to expand the attributes values with the
- * document's package name
- * @return the package name to use for attribute expansion.
- */
- public String getPackageNameForAttributeExpansion() {
- String aPackage = mRootElement.getAttribute("package");
- if (aPackage != null) {
- return aPackage;
- }
- if (mMainManifestPackageName.isPresent()) {
- return mMainManifestPackageName.get();
- }
- throw new RuntimeException("No package present in overlay or main manifest file");
- }
-
- public Optional<XmlAttribute> getPackage() {
- Optional<XmlAttribute> packageAttribute =
- getRootNode().getAttribute(XmlNode.fromXmlName("package"));
- return packageAttribute.isPresent()
- ? packageAttribute
- : getRootNode().getAttribute(XmlNode.fromNSName(
- SdkConstants.ANDROID_URI, "android", "package"));
- }
-
- public Document getXml() {
- return mRootElement.getOwnerDocument();
- }
-
- /**
- * Returns the minSdk version specified in the uses_sdk element if present or the
- * default value.
- */
- private String getRawMinSdkVersion() {
- Optional<XmlElement> usesSdk = getByTypeAndKey(
- ManifestModel.NodeTypes.USES_SDK, null);
- if (usesSdk.isPresent()) {
- Optional<XmlAttribute> minSdkVersion = usesSdk.get()
- .getAttribute(XmlNode.fromXmlName("android:minSdkVersion"));
- if (minSdkVersion.isPresent()) {
- return minSdkVersion.get().getValue();
- }
- }
- return DEFAULT_SDK_VERSION;
- }
-
- /**
- * Returns the minSdk version for this manifest file. It can be injected from the outer
- * build.gradle or can be expressed in the uses_sdk element.
- */
- private String getMinSdkVersion() {
- // check for system properties.
- String injectedMinSdk = mSystemPropertyResolver.getValue(SystemProperty.MIN_SDK_VERSION);
- if (injectedMinSdk != null) {
- return injectedMinSdk;
- }
- return getRawMinSdkVersion();
- }
-
- /**
- * Returns the targetSdk version specified in the uses_sdk element if present or the
- * default value.
- */
- private String getRawTargetSdkVersion() {
-
- Optional<XmlElement> usesSdk = getByTypeAndKey(
- ManifestModel.NodeTypes.USES_SDK, null);
- if (usesSdk.isPresent()) {
- Optional<XmlAttribute> targetSdkVersion = usesSdk.get()
- .getAttribute(XmlNode.fromXmlName("android:targetSdkVersion"));
- if (targetSdkVersion.isPresent()) {
- return targetSdkVersion.get().getValue();
- }
- }
- return getRawMinSdkVersion();
- }
-
- /**
- * Returns the targetSdk version for this manifest file. It can be injected from the outer
- * build.gradle or can be expressed in the uses_sdk element.
- */
- private String getTargetSdkVersion() {
-
- // check for system properties.
- String injectedTargetVersion = mSystemPropertyResolver
- .getValue(SystemProperty.TARGET_SDK_VERSION);
- if (injectedTargetVersion != null) {
- return injectedTargetVersion;
- }
- return getRawTargetSdkVersion();
- }
-
- /**
- * Decodes a sdk version from either its decimal representation or from a platform code name.
- * @param attributeVersion the sdk version attribute as specified by users.
- * @return the integer representation of the platform level.
- */
- private static int getApiLevelFromAttribute(String attributeVersion) {
- Preconditions.checkArgument(!Strings.isNullOrEmpty(attributeVersion));
- if (Character.isDigit(attributeVersion.charAt(0))) {
- return Integer.parseInt(attributeVersion);
- }
- return SdkVersionInfo.getApiByPreviewName(attributeVersion, true);
- }
-
- /**
- * Add all implicit elements from the passed lower priority document that are
- * required in the target SDK.
- */
- @SuppressWarnings("unchecked") // compiler confused about varargs and generics.
- private void addImplicitElements(XmlDocument lowerPriorityDocument,
- MergingReport.Builder mergingReport) {
-
- // if this document is an overlay, tolerate the absence of uses-sdk and do not
- // assume implicit minimum versions.
- Optional<XmlElement> usesSdk = getByTypeAndKey(
- ManifestModel.NodeTypes.USES_SDK, null);
- if (mType == Type.OVERLAY && !usesSdk.isPresent()) {
- return;
- }
-
- // check that the uses-sdk element does not have any tools:node instruction.
- if (usesSdk.isPresent()) {
- XmlElement usesSdkElement = usesSdk.get();
- if (usesSdkElement.getOperationType() != NodeOperationType.MERGE) {
- mergingReport
- .addMessage(
- new SourceFilePosition(
- getSourceFile(),
- usesSdkElement.getPosition()),
- MergingReport.Record.Severity.ERROR,
- "uses-sdk element cannot have a \"tools:node\" attribute");
- return;
- }
- }
- int thisTargetSdk = getApiLevelFromAttribute(getTargetSdkVersion());
-
- // when we are importing a library, we should never use the build.gradle injected
- // values (only valid for overlay, main manifest) so use the raw versions coming from
- // the AndroidManifest.xml
- int libraryTargetSdk = getApiLevelFromAttribute(
- lowerPriorityDocument.getFileType() == Type.LIBRARY
- ? lowerPriorityDocument.getRawTargetSdkVersion()
- : lowerPriorityDocument.getTargetSdkVersion());
-
- // if library is using a code name rather than an API level, make sure this document target
- // sdk version is using the same code name.
- String libraryTargetSdkVersion = lowerPriorityDocument.getTargetSdkVersion();
- if (!Character.isDigit(libraryTargetSdkVersion.charAt(0))) {
- // this is a code name, ensure this document uses the same code name.
- if (!libraryTargetSdkVersion.equals(getTargetSdkVersion())) {
- mergingReport.addMessage(getSourceFile(), MergingReport.Record.Severity.ERROR,
- String.format(
- "uses-sdk:targetSdkVersion %1$s cannot be different than version "
- + "%2$s declared in library %3$s",
- getTargetSdkVersion(),
- libraryTargetSdkVersion,
- lowerPriorityDocument.getSourceFile().print(false)
- )
- );
- return;
- }
- }
- // same for minSdkVersion, if the library is using a code name, the application must
- // also be using the same code name.
- String libraryMinSdkVersion = lowerPriorityDocument.getRawMinSdkVersion();
- if (!Character.isDigit(libraryMinSdkVersion.charAt(0))) {
- // this is a code name, ensure this document uses the same code name.
- if (!libraryMinSdkVersion.equals(getMinSdkVersion())) {
- mergingReport.addMessage(getSourceFile(), MergingReport.Record.Severity.ERROR,
- String.format(
- "uses-sdk:minSdkVersion %1$s cannot be different than version "
- + "%2$s declared in library %3$s",
- getMinSdkVersion(),
- libraryMinSdkVersion,
- lowerPriorityDocument.getSourceFile().print(false)
- )
- );
- return;
- }
- }
-
- if (!checkUsesSdkMinVersion(lowerPriorityDocument, mergingReport)) {
- String error = String.format(
- "uses-sdk:minSdkVersion %1$s cannot be smaller than version "
- + "%2$s declared in library %3$s\n"
- + "\tSuggestion: use tools:overrideLibrary=\"%4$s\" to force usage",
- getMinSdkVersion(),
- lowerPriorityDocument.getRawMinSdkVersion(),
- lowerPriorityDocument.getSourceFile().print(false),
- lowerPriorityDocument.getPackageName());
- if (usesSdk.isPresent()) {
- mergingReport.addMessage(
- new SourceFilePosition(getSourceFile(), usesSdk.get().getPosition()),
- MergingReport.Record.Severity.ERROR,
- error);
- } else {
- mergingReport.addMessage(
- getSourceFile(), MergingReport.Record.Severity.ERROR, error);
- }
- return;
- }
-
- // if the merged document target SDK is equal or smaller than the library's, nothing to do.
- if (thisTargetSdk <= libraryTargetSdk) {
- return;
- }
-
- // There is no need to add any implied permissions when targeting an old runtime.
- if (thisTargetSdk < 4) {
- return;
- }
-
- boolean hasWriteToExternalStoragePermission =
- lowerPriorityDocument.getByTypeAndKey(
- USES_PERMISSION, permission("WRITE_EXTERNAL_STORAGE")).isPresent();
-
- if (libraryTargetSdk < 4) {
- addIfAbsent(mergingReport.getActionRecorder(),
- USES_PERMISSION,
- permission("WRITE_EXTERNAL_STORAGE"),
- lowerPriorityDocument.getPackageName() + " has a targetSdkVersion < 4");
- hasWriteToExternalStoragePermission = true;
-
- addIfAbsent(mergingReport.getActionRecorder(),
- USES_PERMISSION,
- permission("READ_PHONE_STATE"),
- lowerPriorityDocument.getPackageName() + " has a targetSdkVersion < 4");
- }
-
- // If the application has requested WRITE_EXTERNAL_STORAGE, we will
- // force them to always take READ_EXTERNAL_STORAGE as well. We always
- // do this (regardless of target API version) because we can't have
- // an app with write permission but not read permission.
- if (hasWriteToExternalStoragePermission) {
-
- addIfAbsent(mergingReport.getActionRecorder(),
- USES_PERMISSION,
- permission("READ_EXTERNAL_STORAGE"),
- lowerPriorityDocument.getPackageName() + " requested WRITE_EXTERNAL_STORAGE");
- }
-
- // Pre-JellyBean call log permission compatibility.
- if (thisTargetSdk >= 16 && libraryTargetSdk < 16) {
- if (lowerPriorityDocument.getByTypeAndKey(
- USES_PERMISSION, permission("READ_CONTACTS")).isPresent()) {
- addIfAbsent(mergingReport.getActionRecorder(),
- USES_PERMISSION, permission("READ_CALL_LOG"),
- lowerPriorityDocument.getPackageName()
- + " has targetSdkVersion < 16 and requested READ_CONTACTS");
- }
- if (lowerPriorityDocument.getByTypeAndKey(
- USES_PERMISSION, permission("WRITE_CONTACTS")).isPresent()) {
- addIfAbsent(mergingReport.getActionRecorder(),
- USES_PERMISSION, permission("WRITE_CALL_LOG"),
- lowerPriorityDocument.getPackageName()
- + " has targetSdkVersion < 16 and requested WRITE_CONTACTS");
- }
- }
- }
-
- /**
- * Returns true if the minSdkVersion of the application and the library are compatible, false
- * otherwise.
- */
- private boolean checkUsesSdkMinVersion(XmlDocument lowerPriorityDocument,
- MergingReport.Builder mergingReport) {
-
- int thisMinSdk = getApiLevelFromAttribute(getMinSdkVersion());
- int libraryMinSdk = getApiLevelFromAttribute(
- lowerPriorityDocument.getRawMinSdkVersion());
-
- // the merged document minSdk cannot be lower than a library
- if (thisMinSdk < libraryMinSdk) {
-
- // check if this higher priority document has any tools instructions for the node
- Optional<XmlElement> xmlElementOptional = getByTypeAndKey(USES_SDK, null);
- if (!xmlElementOptional.isPresent()) {
- return false;
- }
- XmlElement xmlElement = xmlElementOptional.get();
-
- // if we find a selector that applies to this library. the users wants to explicitly
- // allow this higher version library to be allowed.
- for (Selector selector : xmlElement.getOverrideUsesSdkLibrarySelectors()) {
- if (selector.appliesTo(lowerPriorityDocument.getRootNode())) {
- return true;
- }
- }
- return false;
- }
- return true;
- }
-
- /**
- * Adds a new element of type nodeType with a specific keyValue if the element is absent in this
- * document. Will also add attributes expressed through key value pairs.
- *
- * @param actionRecorder to records creation actions.
- * @param nodeType the node type to crete
- * @param keyValue the optional key for the element.
- * @param attributes the optional array of key value pairs for extra element attribute.
- * @return the Xml element whether it was created or existed or {@link Optional#absent()} if
- * it does not exist in this document.
- */
- private Optional<Element> addIfAbsent(
- @NonNull ActionRecorder actionRecorder,
- @NonNull ManifestModel.NodeTypes nodeType,
- @Nullable String keyValue,
- @Nullable String reason,
- @Nullable Pair<String, String>... attributes) {
-
- Optional<XmlElement> xmlElementOptional = getByTypeAndKey(nodeType, keyValue);
- if (xmlElementOptional.isPresent()) {
- return Optional.absent();
- }
- Element elementNS = getXml()
- .createElementNS(SdkConstants.ANDROID_URI, "android:" + nodeType.toXmlName());
-
-
- ImmutableList<String> keyAttributesNames = nodeType.getNodeKeyResolver()
- .getKeyAttributesNames();
- if (keyAttributesNames.size() == 1) {
- elementNS.setAttributeNS(
- SdkConstants.ANDROID_URI, "android:" + keyAttributesNames.get(0), keyValue);
- }
- if (attributes != null) {
- for (Pair<String, String> attribute : attributes) {
- elementNS.setAttributeNS(
- SdkConstants.ANDROID_URI, "android:" + attribute.getFirst(),
- attribute.getSecond());
- }
- }
-
- // record creation.
- XmlElement xmlElement = new XmlElement(elementNS, this);
- actionRecorder.recordImpliedNodeAction(xmlElement, reason);
-
- getRootNode().getXml().appendChild(elementNS);
- return Optional.of(elementNS);
- }
-
- private static String permission(String permissionName) {
- return "android.permission." + permissionName;
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java
deleted file mode 100644
index f14fb94..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java
+++ /dev/null
@@ -1,880 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.ide.common.res2.MergingException;
-import com.android.utils.ILogger;
-import com.android.utils.SdkUtils;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.w3c.dom.Text;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Xml {@link org.w3c.dom.Element} which is mergeable.
- *
- * A mergeable element can contains 3 types of children :
- * <ul>
- * <li>a child element, which itself may or may not be mergeable.</li>
- * <li>xml attributes which are related to the element.</li>
- * <li>tools oriented attributes to trigger specific behaviors from the merging tool</li>
- * </ul>
- *
- * The two main responsibilities of this class is to be capable of comparing itself against
- * another instance of the same type as well as providing XML element merging capabilities.
- */
-public class XmlElement extends OrphanXmlElement {
-
- @NonNull private final XmlDocument mDocument;
-
- private final NodeOperationType mNodeOperationType;
- // list of non tools related attributes.
- private final ImmutableList<XmlAttribute> mAttributes;
- // map of all tools related attributes keyed by target attribute name
- private final Map<NodeName, AttributeOperationType> mAttributesOperationTypes;
- // list of mergeable children elements.
- private final ImmutableList<XmlElement> mMergeableChildren;
- // optional selector declared on this xml element.
- @Nullable private final Selector mSelector;
- // optional list of libraries that we should ignore the minSdk version
- @NonNull private final List<Selector> mOverrideUsesSdkLibrarySelectors;
-
-
- public XmlElement(@NonNull Element xml, @NonNull XmlDocument document) {
- super(xml);
-
- mDocument = Preconditions.checkNotNull(document);
- Selector selector = null;
- List<Selector> overrideUsesSdkLibrarySelectors = ImmutableList.of();
-
- ImmutableMap.Builder<NodeName, AttributeOperationType> attributeOperationTypeBuilder =
- ImmutableMap.builder();
- ImmutableList.Builder<XmlAttribute> attributesListBuilder = ImmutableList.builder();
- NamedNodeMap namedNodeMap = getXml().getAttributes();
- NodeOperationType lastNodeOperationType = null;
- for (int i = 0; i < namedNodeMap.getLength(); i++) {
- Node attribute = namedNodeMap.item(i);
- if (SdkConstants.TOOLS_URI.equals(attribute.getNamespaceURI())) {
- String instruction = attribute.getLocalName();
- if (instruction.equals(NodeOperationType.NODE_LOCAL_NAME)) {
- // should we flag an error when there are more than one operation type on a node ?
- lastNodeOperationType = NodeOperationType.valueOf(
- SdkUtils.camelCaseToConstantName(
- attribute.getNodeValue()));
- } else if (instruction.equals(Selector.SELECTOR_LOCAL_NAME)) {
- selector = new Selector(attribute.getNodeValue());
- } else if (instruction.equals(NodeOperationType.OVERRIDE_USES_SDK)) {
- String nodeValue = attribute.getNodeValue();
- ImmutableList.Builder<Selector> builder = ImmutableList.builder();
- for (String selectorValue : Splitter.on(',').split(nodeValue)) {
- builder.add(new Selector(selectorValue.trim()));
- }
- overrideUsesSdkLibrarySelectors = builder.build();
- } else {
- AttributeOperationType attributeOperationType;
- try {
- attributeOperationType =
- AttributeOperationType.valueOf(
- SdkUtils.xmlNameToConstantName(instruction));
- } catch (IllegalArgumentException e) {
- try {
- // is this another tool's operation type that we do not care about.
- OtherOperationType.valueOf(instruction);
- break;
- } catch (IllegalArgumentException e1) {
-
- String errorMessage =
- String.format("Invalid instruction '%1$s', "
- + "valid instructions are : %2$s",
- instruction,
- Joiner.on(',').join(AttributeOperationType.values())
- );
- throw new RuntimeException(MergingException.wrapException(e)
- .withMessage(errorMessage)
- .withFile(mDocument.getSourceFile())
- .withPosition(mDocument.getNodePosition(xml)).build());
- }
- }
- for (String attributeName : Splitter.on(',').trimResults()
- .split(attribute.getNodeValue())) {
- if (attributeName.indexOf(XmlUtils.NS_SEPARATOR) == -1) {
- String toolsPrefix = XmlUtils
- .lookupNamespacePrefix(getXml(), SdkConstants.TOOLS_URI,
- SdkConstants.ANDROID_NS_NAME, false);
- // automatically provide the prefix.
- attributeName = toolsPrefix + XmlUtils.NS_SEPARATOR + attributeName;
- }
- NodeName nodeName = XmlNode.fromXmlName(attributeName);
- attributeOperationTypeBuilder.put(nodeName, attributeOperationType);
- }
- }
- }
- }
- mAttributesOperationTypes = attributeOperationTypeBuilder.build();
- for (int i = 0; i < namedNodeMap.getLength(); i++) {
- Node attribute = namedNodeMap.item(i);
- XmlAttribute xmlAttribute = new XmlAttribute(
- this, (Attr) attribute, getType().getAttributeModel(XmlNode.fromXmlName(
- ((Attr) attribute).getName())));
- attributesListBuilder.add(xmlAttribute);
- }
- mNodeOperationType = lastNodeOperationType;
- mAttributes = attributesListBuilder.build();
- mMergeableChildren = initMergeableChildren();
- mSelector = selector;
- mOverrideUsesSdkLibrarySelectors = overrideUsesSdkLibrarySelectors;
- }
-
- /**
- * Returns the owning {@link com.android.manifmerger.XmlDocument}
- */
- @NonNull
- public XmlDocument getDocument() {
- return mDocument;
- }
-
- /**
- * Returns the list of attributes for this xml element.
- */
- public List<XmlAttribute> getAttributes() {
- return mAttributes;
- }
-
- /**
- * Returns the {@link com.android.manifmerger.XmlAttribute} for an attribute present on this
- * xml element, or {@link com.google.common.base.Optional#absent} if not present.
- * @param attributeName the attribute name.
- */
- public Optional<XmlAttribute> getAttribute(NodeName attributeName) {
- for (XmlAttribute xmlAttribute : mAttributes) {
- if (xmlAttribute.getName().equals(attributeName)) {
- return Optional.of(xmlAttribute);
- }
- }
- return Optional.absent();
- }
-
- /**
- * Get the node operation type as optionally specified by the user. If the user did not
- * explicitly specify how conflicting elements should be handled, a
- * {@link com.android.manifmerger.NodeOperationType#MERGE} will be returned.
- */
- public NodeOperationType getOperationType() {
- return mNodeOperationType != null
- ? mNodeOperationType
- : NodeOperationType.MERGE;
- }
-
- /**
- * Get the attribute operation type as optionally specified by the user. If the user did not
- * explicitly specify how conflicting attributes should be handled, a
- * {@link AttributeOperationType#STRICT} will be returned.
- */
- public AttributeOperationType getAttributeOperationType(NodeName attributeName) {
- return mAttributesOperationTypes.containsKey(attributeName)
- ? mAttributesOperationTypes.get(attributeName)
- : AttributeOperationType.STRICT;
- }
-
- public Collection<Map.Entry<NodeName, AttributeOperationType>> getAttributeOperations() {
- return mAttributesOperationTypes.entrySet();
- }
-
- @NonNull
- public List<Selector> getOverrideUsesSdkLibrarySelectors() {
- return mOverrideUsesSdkLibrarySelectors;
- }
-
-
- @NonNull
- @Override
- public SourcePosition getPosition() {
- return mDocument.getNodePosition(this);
- }
-
- @NonNull
- @Override
- public SourceFile getSourceFile() {
- return mDocument.getSourceFile();
- }
-
-
- /**
- * Merge this xml element with a lower priority node.
- *
- * For now, attributes will be merged. If present on both xml elements, a warning will be
- * issued and the attribute merge will be rejected.
- *
- * @param lowerPriorityNode lower priority Xml element to merge with.
- * @param mergingReport the merging report to log errors and actions.
- */
- public void mergeWithLowerPriorityNode(
- XmlElement lowerPriorityNode,
- MergingReport.Builder mergingReport) {
-
-
- if (mSelector != null && !mSelector.isResolvable(getDocument().getSelectors())) {
- mergingReport.addMessage(getSourceFilePosition(),
- MergingReport.Record.Severity.ERROR,
- String.format("'tools:selector=\"%1$s\"' is not a valid library identifier, "
- + "valid identifiers are : %2$s",
- mSelector.toString(),
- Joiner.on(',').join(mDocument.getSelectors().getKeys())));
- return;
-
- }
- mergingReport.getLogger().info("Merging " + getId()
- + " with lower " + lowerPriorityNode.printPosition());
-
- // workaround for 0.12 release and overlay treatment of manifest entries. This will
- // need to be expressed in the model instead.
- MergeType mergeType = getType().getMergeType();
- // if element we are merging in is not a library (an overlay or an application), we should
- // always merge the <manifest> attributes otherwise, we do not merge the libraries
- // <manifest> attributes.
- if (isA(ManifestModel.NodeTypes.MANIFEST)
- && lowerPriorityNode.getDocument().getFileType() != XmlDocument.Type.LIBRARY) {
- mergeType = MergeType.MERGE;
- }
-
- if (mergeType != MergeType.MERGE_CHILDREN_ONLY) {
- // make a copy of all the attributes metadata, it will eliminate elements from this
- // list as it finds them explicitly defined in the lower priority node.
- // At the end of the explicit attributes processing, the remaining elements of this
- // list will need to be checked for default value that may clash with a locally
- // defined attribute.
- List<AttributeModel> attributeModels =
- new ArrayList<AttributeModel>(lowerPriorityNode.getType().getAttributeModels());
-
- // merge explicit attributes from lower priority node.
- for (XmlAttribute lowerPriorityAttribute : lowerPriorityNode.getAttributes()) {
- lowerPriorityAttribute.mergeInHigherPriorityElement(this, mergingReport);
- if (lowerPriorityAttribute.getModel() != null) {
- attributeModels.remove(lowerPriorityAttribute.getModel());
- }
- }
- // merge implicit default values from lower priority node when we have an explicit
- // attribute declared on this node.
- for (AttributeModel attributeModel : attributeModels) {
- if (attributeModel.getDefaultValue() != null) {
- Optional<XmlAttribute> myAttribute = getAttribute(attributeModel.getName());
- if (myAttribute.isPresent()) {
- myAttribute.get().mergeWithLowerPriorityDefaultValue(
- mergingReport, lowerPriorityNode);
- }
- }
- }
- }
- // are we supposed to merge children ?
- if (mNodeOperationType != NodeOperationType.MERGE_ONLY_ATTRIBUTES) {
- mergeChildren(lowerPriorityNode, mergingReport);
- } else {
- // record rejection of the lower priority node's children .
- for (XmlElement lowerPriorityChild : lowerPriorityNode.getMergeableElements()) {
- mergingReport.getActionRecorder().recordNodeAction(this,
- Actions.ActionType.REJECTED,
- lowerPriorityChild);
- }
- }
- }
-
- public ImmutableList<XmlElement> getMergeableElements() {
- return mMergeableChildren;
- }
-
- /**
- * Returns a child of a particular type and a particular key.
- * @param type the requested child type.
- * @param keyValue the requested child key.
- * @return the child of {@link com.google.common.base.Optional#absent()} if no child of this
- * type and key exist.
- */
- public Optional<XmlElement> getNodeByTypeAndKey(
- ManifestModel.NodeTypes type,
- @Nullable String keyValue) {
-
- for (XmlElement xmlElement : mMergeableChildren) {
- if (xmlElement.isA(type) &&
- (keyValue == null || keyValue.equals(xmlElement.getKey()))) {
- return Optional.of(xmlElement);
- }
- }
- return Optional.absent();
- }
-
- /**
- * Returns all immediate children of this node for a particular type, irrespective of their
- * key.
- * @param type the type of children element requested.
- * @return the list (potentially empty) of children.
- */
- public ImmutableList<XmlElement> getAllNodesByType(ManifestModel.NodeTypes type) {
- ImmutableList.Builder<XmlElement> listBuilder = ImmutableList.builder();
- for (XmlElement mergeableChild : initMergeableChildren()) {
- if (mergeableChild.isA(type)) {
- listBuilder.add(mergeableChild);
- }
- }
- return listBuilder.build();
- }
-
- // merge this higher priority node with a lower priority node.
- public void mergeChildren(XmlElement lowerPriorityNode,
- MergingReport.Builder mergingReport) {
-
- // read all lower priority mergeable nodes.
- // if the same node is not defined in this document merge it in.
- // if the same is defined, so far, give an error message.
- for (XmlElement lowerPriorityChild : lowerPriorityNode.getMergeableElements()) {
-
- if (shouldIgnore(lowerPriorityChild, mergingReport)) {
- continue;
- }
- mergeChild(lowerPriorityChild, mergingReport);
- }
- }
-
- /**
- * Returns true if this element supports having a tools:selector decoration, false otherwise.
- */
- public boolean supportsSelector() {
- return getOperationType().isSelectable();
- }
-
- // merge a child of a lower priority node into this higher priority node.
- private void mergeChild(XmlElement lowerPriorityChild, MergingReport.Builder mergingReport) {
-
- ILogger logger = mergingReport.getLogger();
-
- // If this a custom element, we just blindly merge it in.
- if (lowerPriorityChild.getType() == ManifestModel.NodeTypes.CUSTOM) {
- handleCustomElement(lowerPriorityChild, mergingReport);
- return;
- }
-
- Optional<XmlElement> thisChildOptional =
- getNodeByTypeAndKey(lowerPriorityChild.getType(),lowerPriorityChild.getKey());
-
- // only in the lower priority document ?
- if (!thisChildOptional.isPresent()) {
- addElement(lowerPriorityChild, mergingReport);
- return;
- }
- // it's defined in both files.
- logger.verbose(lowerPriorityChild.getId() + " defined in both files...");
-
- XmlElement thisChild = thisChildOptional.get();
- switch (thisChild.getType().getMergeType()) {
- case CONFLICT:
- addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format(
- "Node %1$s cannot be present in more than one input file and it's "
- + "present at %2$s and %3$s",
- thisChild.getType(),
- thisChild.printPosition(),
- lowerPriorityChild.printPosition()
- ));
- break;
- case ALWAYS:
-
- // no merging, we consume the lower priority node unmodified.
- // if the two elements are equal, just skip it.
-
- // but check first that we are not supposed to replace or remove it.
- NodeOperationType operationType =
- calculateNodeOperationType(thisChild, lowerPriorityChild);
- if (operationType == NodeOperationType.REMOVE ||
- operationType == NodeOperationType.REPLACE) {
- mergingReport.getActionRecorder().recordNodeAction(thisChild,
- Actions.ActionType.REJECTED, lowerPriorityChild);
- break;
- }
-
- if (thisChild.getType().areMultipleDeclarationAllowed()) {
- mergeChildrenWithMultipleDeclarations(lowerPriorityChild, mergingReport);
- } else {
- if (!thisChild.isEquals(lowerPriorityChild)) {
- addElement(lowerPriorityChild, mergingReport);
- }
- }
- break;
- default:
- // 2 nodes exist, some merging need to happen
- handleTwoElementsExistence(thisChild, lowerPriorityChild, mergingReport);
- break;
- }
- }
-
- /**
- * Handles presence of custom elements (elements not part of the android or tools
- * namespaces). Such elements are merged unchanged into the resulting document, and
- * optionally, the namespace definition is added to the merged document root element.
- * @param customElement the custom element present in the lower priority document.
- * @param mergingReport the merging report to log errors and actions.
- */
- private void handleCustomElement(XmlElement customElement,
- MergingReport.Builder mergingReport) {
- addElement(customElement, mergingReport);
-
- // add the custom namespace to the document generation.
- String nodeName = customElement.getXml().getNodeName();
- if (!nodeName.contains(":")) {
- return;
- }
- String prefix = nodeName.substring(0, nodeName.indexOf(':'));
- String namespace = customElement.getDocument().getRootNode()
- .getXml().getAttribute(SdkConstants.XMLNS_PREFIX + prefix);
-
- if (namespace != null) {
- getDocument().getRootNode().getXml().setAttributeNS(
- SdkConstants.XMLNS_URI, SdkConstants.XMLNS_PREFIX + prefix, namespace);
- }
- }
-
- /**
- * Merges two children when this children's type allow multiple elements declaration with the
- * same key value. In that case, we only merge the lower priority child if there is not already
- * an element with the same key value that is equal to the lower priority child. Two children
- * are equals if they have the same attributes and children declared irrespective of the
- * declaration order.
- *
- * @param lowerPriorityChild the lower priority element's child.
- * @param mergingReport the merging report to log errors and actions.
- */
- private void mergeChildrenWithMultipleDeclarations(
- XmlElement lowerPriorityChild,
- MergingReport.Builder mergingReport) {
-
- Preconditions.checkArgument(lowerPriorityChild.getType().areMultipleDeclarationAllowed());
- if (lowerPriorityChild.getType().areMultipleDeclarationAllowed()) {
- for (XmlElement sameTypeChild : getAllNodesByType(lowerPriorityChild.getType())) {
- if (sameTypeChild.getId().equals(lowerPriorityChild.getId()) &&
- sameTypeChild.isEquals(lowerPriorityChild)) {
- return;
- }
- }
- }
- // if we end up here, we never found a child of this element with the same key and strictly
- // equals to the lowerPriorityChild so we should merge it in.
- addElement(lowerPriorityChild, mergingReport);
- }
-
- /**
- * Determine if we should completely ignore a child from any merging activity.
- * There are 2 situations where we should ignore a lower priority child :
- * <p>
- * <ul>
- * <li>The associate {@link com.android.manifmerger.ManifestModel.NodeTypes} is
- * annotated with {@link com.android.manifmerger.MergeType#IGNORE}</li>
- * <li>This element has a child of the same type with no key that has a '
- * tools:node="removeAll' attribute.</li>
- * </ul>
- * @param lowerPriorityChild the lower priority child we should determine eligibility for
- * merging.
- * @return true if the element should be ignored, false otherwise.
- */
- private boolean shouldIgnore(
- XmlElement lowerPriorityChild,
- MergingReport.Builder mergingReport) {
-
- if (lowerPriorityChild.getType().getMergeType() == MergeType.IGNORE) {
- return true;
- }
-
- // do we have an element of the same type of that child with no key ?
- Optional<XmlElement> thisChildElementOptional =
- getNodeByTypeAndKey(lowerPriorityChild.getType(), null /* keyValue */);
- if (!thisChildElementOptional.isPresent()) {
- return false;
- }
- XmlElement thisChild = thisChildElementOptional.get();
-
- // are we supposed to delete all occurrences and if yes, is there a selector defined to
- // filter which elements should be deleted.
- boolean shouldDelete = thisChild.mNodeOperationType == NodeOperationType.REMOVE_ALL
- && (thisChild.mSelector == null
- || thisChild.mSelector.appliesTo(lowerPriorityChild));
- // if we should discard this child element, record the action.
- if (shouldDelete) {
- mergingReport.getActionRecorder().recordNodeAction(thisChildElementOptional.get(),
- Actions.ActionType.REJECTED,
- lowerPriorityChild);
- }
- return shouldDelete;
- }
-
- /**
- * Handle 2 elements (of same identity) merging.
- * higher priority one has a tools:node="remove", remove the low priority one
- * higher priority one has a tools:node="replace", replace the low priority one
- * higher priority one has a tools:node="strict", flag the error if not equals.
- * default or tools:node="merge", merge the two elements.
- * @param higherPriority the higher priority node.
- * @param lowerPriority the lower priority element.
- * @param mergingReport the merging report to log errors and actions.
- */
- private void handleTwoElementsExistence(
- XmlElement higherPriority,
- XmlElement lowerPriority,
- MergingReport.Builder mergingReport) {
-
- NodeOperationType operationType = calculateNodeOperationType(higherPriority, lowerPriority);
- // 2 nodes exist, 3 possibilities :
- // higher priority one has a tools:node="remove", remove the low priority one
- // higher priority one has a tools:node="replace", replace the low priority one
- // higher priority one has a tools:node="strict", flag the error if not equals.
- switch(operationType) {
- case MERGE:
- case MERGE_ONLY_ATTRIBUTES:
- // record the action
- mergingReport.getActionRecorder().recordNodeAction(higherPriority,
- Actions.ActionType.MERGED, lowerPriority);
- // and perform the merge
- higherPriority.mergeWithLowerPriorityNode(lowerPriority, mergingReport);
- break;
- case REMOVE:
- case REPLACE:
- // so far remove and replace and similar, the post validation will take
- // care of removing this node in the case of REMOVE.
-
- // just don't import the lower priority node and record the action.
- mergingReport.getActionRecorder().recordNodeAction(higherPriority,
- Actions.ActionType.REJECTED, lowerPriority);
- break;
- case STRICT:
- Optional<String> compareMessage = higherPriority.compareTo(lowerPriority);
- if (compareMessage.isPresent()) {
- // flag error.
- addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format(
- "Node %1$s at %2$s is tagged with tools:node=\"strict\", yet "
- + "%3$s at %4$s is different : %5$s",
- higherPriority.getId(),
- higherPriority.printPosition(),
- lowerPriority.getId(),
- lowerPriority.printPosition(),
- compareMessage.get()
- ));
- }
- break;
- default:
- mergingReport.getLogger().error(null /* throwable */,
- "Unhandled node operation type %s", higherPriority.getOperationType());
- break;
- }
- }
-
- /**
- * Calculate the effective node operation type for a higher priority node when a lower priority
- * node is queried for merge.
- * @param higherPriority the higher priority node which may have a {@link NodeOperationType}
- * declaration and may also have a {@link Selector} declaration.
- * @param lowerPriority the lower priority node that is elected for merging with the higher
- * priority node.
- * @return the effective {@link NodeOperationType} that should be used to affect higher and
- * lower priority nodes merging.
- */
- private static NodeOperationType calculateNodeOperationType(
- @NonNull XmlElement higherPriority,
- @NonNull XmlElement lowerPriority) {
-
- NodeOperationType operationType = higherPriority.getOperationType();
- // if the operation's selector exists and the lower priority node is not selected,
- // we revert to default operation type which is merge.
- if (higherPriority.supportsSelector()
- && higherPriority.mSelector != null
- && !higherPriority.mSelector.appliesTo(lowerPriority)) {
- operationType = NodeOperationType.MERGE;
- }
- return operationType;
- }
-
- /**
- * Add an element and its leading comments as the last sub-element of the current element.
- * @param elementToBeAdded xml element to be added to the current element.
- * @param mergingReport the merging report to log errors and actions.
- */
- private void addElement(XmlElement elementToBeAdded, MergingReport.Builder mergingReport) {
-
- List<Node> comments = getLeadingComments(elementToBeAdded.getXml());
- // record all the actions before the node is moved from the library document to the main
- // merged document.
- mergingReport.getActionRecorder().recordDefaultNodeAction(elementToBeAdded);
-
- // only in the new file, just import it.
- Node node = getXml().getOwnerDocument().adoptNode(elementToBeAdded.getXml());
- getXml().appendChild(node);
-
- // also adopt the child's comments if any.
- for (Node comment : comments) {
- Node newComment = getXml().getOwnerDocument().adoptNode(comment);
- getXml().insertBefore(newComment, node);
- }
-
- mergingReport.getLogger().verbose("Adopted " + node);
- }
-
- public boolean isEquals(XmlElement otherNode) {
- return !compareTo(otherNode).isPresent();
- }
-
- /**
- * Returns a potentially null (if not present) selector decoration on this element.
- */
- @Nullable
- public Selector getSelector() {
- return mSelector;
- }
-
- /**
- * Compares this element with another {@link XmlElement} ignoring all attributes belonging to
- * the {@link com.android.SdkConstants#TOOLS_URI} namespace.
- *
- * @param other the other element to compare against.
- * @return a {@link String} describing the differences between the two XML elements or
- * {@link Optional#absent()} if they are equals.
- */
- public Optional<String> compareTo(Object other) {
-
- if (!(other instanceof XmlElement)) {
- return Optional.of("Wrong type");
- }
- XmlElement otherNode = (XmlElement) other;
-
- // compare element names
- if (getXml().getNamespaceURI() != null) {
- if (!getXml().getLocalName().equals(otherNode.getXml().getLocalName())) {
- return Optional.of(
- String.format("Element names do not match: %1$s versus %2$s",
- getXml().getLocalName(),
- otherNode.getXml().getLocalName()));
- }
- // compare element ns
- String thisNS = getXml().getNamespaceURI();
- String otherNS = otherNode.getXml().getNamespaceURI();
- if ((thisNS == null && otherNS != null)
- || (thisNS != null && !thisNS.equals(otherNS))) {
- return Optional.of(
- String.format("Element namespaces names do not match: %1$s versus %2$s",
- thisNS, otherNS));
- }
- } else {
- if (!getXml().getNodeName().equals(otherNode.getXml().getNodeName())) {
- return Optional.of(String.format("Element names do not match: %1$s versus %2$s",
- getXml().getNodeName(),
- otherNode.getXml().getNodeName()));
- }
- }
-
- // compare attributes, we do it twice to identify added/missing elements in both lists.
- Optional<String> message = checkAttributes(this, otherNode);
- if (message.isPresent()) {
- return message;
- }
- message = checkAttributes(otherNode, this);
- if (message.isPresent()) {
- return message;
- }
-
- // compare children
- List<Node> expectedChildren = filterUninterestingNodes(getXml().getChildNodes());
- List<Node> actualChildren = filterUninterestingNodes(otherNode.getXml().getChildNodes());
- if (expectedChildren.size() != actualChildren.size()) {
-
- if (expectedChildren.size() > actualChildren.size()) {
- // missing some.
- List<String> missingChildrenNames =
- Lists.transform(expectedChildren, NODE_TO_NAME);
- missingChildrenNames.removeAll(Lists.transform(actualChildren, NODE_TO_NAME));
- return Optional.of(String.format(
- "%1$s: Number of children do not match up: "
- + "expected %2$d versus %3$d at %4$s, missing %5$s",
- getId(),
- expectedChildren.size(),
- actualChildren.size(),
- otherNode.printPosition(),
- Joiner.on(",").join(missingChildrenNames)));
- } else {
- // extra ones.
- List<String> extraChildrenNames = Lists.transform(actualChildren, NODE_TO_NAME);
- extraChildrenNames.removeAll(Lists.transform(expectedChildren, NODE_TO_NAME));
- return Optional.of(String.format(
- "%1$s: Number of children do not match up: "
- + "expected %2$d versus %3$d at %4$s, extra elements found : %5$s",
- getId(),
- expectedChildren.size(),
- actualChildren.size(),
- otherNode.printPosition(),
- Joiner.on(",").join(expectedChildren)));
- }
- }
- for (Node expectedChild : expectedChildren) {
- if (expectedChild.getNodeType() == Node.ELEMENT_NODE) {
- XmlElement expectedChildNode = new XmlElement((Element) expectedChild, mDocument);
- message = findAndCompareNode(otherNode, actualChildren, expectedChildNode);
- if (message.isPresent()) {
- return message;
- }
- }
- }
- return Optional.absent();
- }
-
- private Optional<String> findAndCompareNode(
- XmlElement otherElement,
- List<Node> otherElementChildren,
- XmlElement childNode) {
-
- Optional<String> message = Optional.absent();
- for (Node potentialNode : otherElementChildren) {
- if (potentialNode.getNodeType() == Node.ELEMENT_NODE) {
- XmlElement otherChildNode = new XmlElement((Element) potentialNode, mDocument);
- if (childNode.getType() == otherChildNode.getType()) {
- // check if this element uses a key.
- if (childNode.getType().getNodeKeyResolver().getKeyAttributesNames()
- .isEmpty()) {
- // no key... try all the other elements, if we find one equal, we are done.
- message = childNode.compareTo(otherChildNode);
- if (!message.isPresent()) {
- return Optional.absent();
- }
- } else {
- // key...
- if (childNode.getKey() == null) {
- // other key MUST also be null.
- if (otherChildNode.getKey() == null) {
- return childNode.compareTo(otherChildNode);
- }
- } else {
- if (childNode.getKey().equals(otherChildNode.getKey())) {
- return childNode.compareTo(otherChildNode);
- }
- }
- }
- }
- }
- }
- return message.isPresent()
- ? message
- : Optional.of(String.format("Child %1$s not found in document %2$s",
- childNode.getId(),
- otherElement.printPosition()));
- }
-
- private static List<Node> filterUninterestingNodes(NodeList nodeList) {
- List<Node> interestingNodes = new ArrayList<Node>();
- for (int i = 0; i < nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- if (node.getNodeType() == Node.TEXT_NODE) {
- Text t = (Text) node;
- if (!t.getData().trim().isEmpty()) {
- interestingNodes.add(node);
- }
- } else if (node.getNodeType() != Node.COMMENT_NODE) {
- interestingNodes.add(node);
- }
-
- }
- return interestingNodes;
- }
-
- private static Optional<String> checkAttributes(
- XmlElement expected,
- XmlElement actual) {
-
- for (XmlAttribute expectedAttr : expected.getAttributes()) {
- XmlAttribute.NodeName attributeName = expectedAttr.getName();
- if (attributeName.isInNamespace(SdkConstants.TOOLS_URI)) {
- continue;
- }
- Optional<XmlAttribute> actualAttr = actual.getAttribute(attributeName);
- if (actualAttr.isPresent()) {
- if (!expectedAttr.getValue().equals(actualAttr.get().getValue())) {
- return Optional.of(
- String.format("Attribute %1$s do not match: %2$s versus %3$s at %4$s",
- expectedAttr.getId(),
- expectedAttr.getValue(),
- actualAttr.get().getValue(),
- actual.printPosition()));
- }
- } else {
- return Optional.of(String.format("Attribute %1$s not found at %2$s",
- expectedAttr.getId(), actual.printPosition()));
- }
- }
- return Optional.absent();
- }
-
- private ImmutableList<XmlElement> initMergeableChildren() {
- ImmutableList.Builder<XmlElement> mergeableNodes = new ImmutableList.Builder<XmlElement>();
- NodeList nodeList = getXml().getChildNodes();
- for (int i = 0; i < nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- if (node instanceof Element) {
- XmlElement xmlElement = new XmlElement((Element) node, mDocument);
- mergeableNodes.add(xmlElement);
- }
- }
- return mergeableNodes.build();
- }
-
- /**
- * Returns all leading comments in the source xml before the node to be adopted.
- * @param nodeToBeAdopted node that will be added as a child to this node.
- */
- static List<Node> getLeadingComments(Node nodeToBeAdopted) {
- ImmutableList.Builder<Node> nodesToAdopt = new ImmutableList.Builder<Node>();
- Node previousSibling = nodeToBeAdopted.getPreviousSibling();
- while (previousSibling != null
- && (previousSibling.getNodeType() == Node.COMMENT_NODE
- || previousSibling.getNodeType() == Node.TEXT_NODE)) {
- // we really only care about comments.
- if (previousSibling.getNodeType() == Node.COMMENT_NODE) {
- nodesToAdopt.add(previousSibling);
- }
- previousSibling = previousSibling.getPreviousSibling();
- }
- return nodesToAdopt.build().reverse();
- }
-
- void addMessage(MergingReport.Builder mergingReport,
- MergingReport.Record.Severity severity,
- String message) {
- mergingReport.addMessage(getSourceFilePosition(),
- severity,
- message);
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java
deleted file mode 100644
index 66b08d4..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.ManifestMerger2.SystemProperty;
-import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
-
-import com.android.ide.common.blame.SourceFile;
-import com.android.utils.PositionXmlParser;
-import com.google.common.base.Optional;
-
-import org.w3c.dom.Document;
-import org.xml.sax.SAXException;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Responsible for loading XML files.
- */
-public final class XmlLoader {
-
- private XmlLoader() {}
-
- /**
- * Loads an xml file without doing xml validation and return a {@link XmlDocument}
- *
- * @param displayName the xml file display name.
- * @param xmlFile the xml file.
- * @return the initialized {@link com.android.manifmerger.XmlDocument}
- */
- public static XmlDocument load(
- KeyResolver<String> selectors,
- KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
- String displayName,
- File xmlFile,
- XmlDocument.Type type,
- Optional<String> mainManifestPackageName)
- throws IOException, SAXException, ParserConfigurationException {
- InputStream inputStream = new BufferedInputStream(new FileInputStream(xmlFile));
-
- Document domDocument = PositionXmlParser.parse(inputStream);
- return domDocument != null ? new XmlDocument(
- new SourceFile(xmlFile, displayName),
- selectors,
- systemPropertyResolver,
- domDocument.getDocumentElement(),
- type,
- mainManifestPackageName)
- : null;
- }
-
-
- /**
- * Loads a xml document from its {@link String} representation without doing xml validation and
- * return a {@link com.android.manifmerger.XmlDocument}
- * @param sourceFile the source location to use for logging and record collection.
- * @param xml the persisted xml.
- * @return the initialized {@link com.android.manifmerger.XmlDocument}
- * @throws IOException this should never be thrown.
- * @throws SAXException if the xml is incorrect
- * @throws ParserConfigurationException if the xml engine cannot be configured.
- */
- public static XmlDocument load(
- KeyResolver<String> selectors,
- KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
- SourceFile sourceFile,
- String xml,
- XmlDocument.Type type,
- Optional<String> mainManifestPackageName)
- throws IOException, SAXException, ParserConfigurationException {
- Document domDocument = PositionXmlParser.parse(xml);
- return domDocument != null
- ? new XmlDocument(
- sourceFile,
- selectors,
- systemPropertyResolver,
- domDocument.getDocumentElement(),
- type,
- mainManifestPackageName)
- : null;
- }
-}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java b/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java
deleted file mode 100644
index f1e3feb..0000000
--- a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.concurrency.Immutable;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.google.common.base.Function;
-import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-/**
- * Common behavior of any xml declaration.
- */
-public abstract class XmlNode {
-
- protected static final Function<Node, String> NODE_TO_NAME =
- new Function<Node, String>() {
- @Override
- public String apply(Node input) {
- return input.getNodeName();
- }
- };
-
- private NodeKey mOriginalId = null;
-
- /**
- * Returns a constant Nodekey that can be used throughout the lifecycle of the xml element.
- * The {@link #getId} can return different values over time as the key of the element can be
- * for instance, changed through placeholder replacement.
- */
- public synchronized NodeKey getOriginalId() {
- if (mOriginalId == null) {
- mOriginalId = getId();
- }
- return mOriginalId;
- }
-
- /**
- * Returns an unique id within the manifest file for the element.
- */
- public abstract NodeKey getId();
-
- /**
- * Returns the element's position
- */
- @NonNull
- public abstract SourcePosition getPosition();
-
- /**
- * Returns the element's document xml source file location.
- */
- @NonNull
- public abstract SourceFile getSourceFile();
-
- /**
- * Returns the element's document xml source file location.
- */
- public SourceFilePosition getSourceFilePosition() {
- return new SourceFilePosition(getSourceFile(), getPosition());
- }
-
- /**
- * Returns the element's xml
- */
- @NonNull
- public abstract Node getXml();
-
- /**
- * Returns the name of this xml element or attribute.
- */
- public abstract NodeName getName();
-
- /**
- * Abstraction to an xml name to isolate whether the name has a namespace or not.
- */
- public interface NodeName {
-
- /**
- * Returns true if this attribute name has a namespace declaration and that namespapce is
- * the same as provided, false otherwise.
- */
- boolean isInNamespace(String namespaceURI);
-
- /**
- * Adds a new attribute of this name to a xml element with a value.
- * @param to the xml element to add the attribute to.
- * @param withValue the new attribute's value.
- */
- void addToNode(Element to, String withValue);
-
- /**
- * The local name.
- */
- String getLocalName();
- }
-
- /**
- * Factory method to create an instance of {@link com.android.manifmerger.XmlNode.NodeName}
- * for an existing xml node.
- * @param node the xml definition.
- * @return an instance of {@link com.android.manifmerger.XmlNode.NodeName} providing
- * namespace handling.
- */
- public static NodeName unwrapName(Node node) {
- return node.getNamespaceURI() == null
- ? new Name(node.getNodeName())
- : new NamespaceAwareName(node);
- }
-
- public static NodeName fromXmlName(String name) {
- return (name.contains(":"))
- ? new NamespaceAwareName(SdkConstants.ANDROID_URI,
- name.substring(0, name.indexOf(':')),
- name.substring(name.indexOf(':') + 1))
- : new Name(name);
- }
-
- public static NodeName fromNSName(String namespaceUri, String prefix, String localName) {
- return new NamespaceAwareName(namespaceUri, prefix, localName);
- }
-
- /**
- * Returns the position of this attribute in the original xml file. This may return an invalid
- * location as this xml fragment does not exist in any xml file but is the temporary result
- * of the merging process.
- * @return a human readable position.
- */
- public String printPosition() {
- return getSourceFilePosition().print(true /*shortFormat*/);
- }
-
- /**
- * Implementation of {@link com.android.manifmerger.XmlNode.NodeName} for an
- * node's declaration not using a namespace.
- */
- public static final class Name implements NodeName {
- private final String mName;
-
- private Name(@NonNull String name) {
- this.mName = Preconditions.checkNotNull(name);
- }
-
- @Override
- public boolean isInNamespace(String namespaceURI) {
- return false;
- }
-
- @Override
- public void addToNode(Element to, String withValue) {
- to.setAttribute(mName, withValue);
- }
-
- @Override
- public boolean equals(Object o) {
- return (o != null && o instanceof Name && ((Name) o).mName.equals(this.mName));
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mName);
- }
-
- @Override
- public String toString() {
- return mName;
- }
-
- @Override
- public String getLocalName() {
- return mName;
- }
- }
-
- /**
- * Implementation of the {@link com.android.manifmerger.XmlNode.NodeName} for a namespace aware attribute.
- */
- public static final class NamespaceAwareName implements NodeName {
-
- private final String mNamespaceURI;
-
- // ignore for comparison and hashcoding since different documents can use different
- // prefixes for the same namespace URI.
- private final String mPrefix;
- private final String mLocalName;
-
- private NamespaceAwareName(@NonNull Node node) {
- this.mNamespaceURI = Preconditions.checkNotNull(node.getNamespaceURI());
- this.mPrefix = Preconditions.checkNotNull(node.getPrefix());
- this.mLocalName = Preconditions.checkNotNull(node.getLocalName());
- }
-
- private NamespaceAwareName(@NonNull String namespaceURI,
- @NonNull String prefix,
- @NonNull String localName) {
- mNamespaceURI = Preconditions.checkNotNull(namespaceURI);
- mPrefix = Preconditions.checkNotNull(prefix);
- mLocalName = Preconditions.checkNotNull(localName);
- }
-
- @Override
- public boolean isInNamespace(String namespaceURI) {
- return mNamespaceURI.equals(namespaceURI);
- }
-
- @Override
- public void addToNode(Element to, String withValue) {
- // TODO: consider standardizing everything on "android:"
- to.setAttributeNS(mNamespaceURI, mPrefix + ":" + mLocalName, withValue);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mNamespaceURI, mLocalName);
- }
-
- @Override
- public boolean equals(Object o) {
- return (o != null && o instanceof NamespaceAwareName
- && ((NamespaceAwareName) o).mLocalName.equals(this.mLocalName)
- && ((NamespaceAwareName) o).mNamespaceURI.equals(this.mNamespaceURI));
- }
-
- @Override
- public String toString() {
- return mPrefix + ":" + mLocalName;
- }
-
- @Override
- public String getLocalName() {
- return mLocalName;
- }
- }
-
- /**
- * A xml element or attribute key.
- */
- @Immutable
- public static class NodeKey {
-
- @NonNull
- private final String mKey;
-
- NodeKey(@NonNull String key) {
- mKey = key;
- }
-
- public static NodeKey fromXml(Element element) {
- return new OrphanXmlElement(element).getId();
- }
-
- @Override
- public String toString() {
- return mKey;
- }
-
- @Override
- public boolean equals(Object o) {
- return (o != null && o instanceof NodeKey && ((NodeKey) o).mKey.equals(this.mKey));
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mKey);
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java b/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java
deleted file mode 100644
index 8e0432d..0000000
--- a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.SdkConstants;
-import com.android.sdklib.mock.MockLog;
-import com.google.common.base.Optional;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.w3c.dom.Element;
-import org.xml.sax.SAXException;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Map;
-import java.util.logging.Logger;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Tests for the {@link com.android.manifmerger.ManifestMergerTest} class
- */
-public class ManifestMerger2SmallTest extends TestCase {
-
- @Mock
- ActionRecorder mActionRecorder;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- }
-
- public void testValidationFailure()
- throws ParserConfigurationException, SAXException, IOException,
- ManifestMerger2.MergeFailureException {
-
- MockLog mockLog = new MockLog();
- String input = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
- + " package=\"com.example.lib3\">\n"
- + "\n"
- + " <application android:label=\"@string/lib_name\" />\n"
- + "\n"
- + " <activity android:name=\"activityOne\" "
- + " tools:replace=\"exported\"/>\n"
- + "\n"
- + "</manifest>";
-
- File tmpFile = inputAsFile("ManifestMerger2Test_testValidationFailure", input);
- assertTrue(tmpFile.exists());
-
- try {
- MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
- ManifestMerger2.MergeType.APPLICATION).merge();
- assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
- // check the log complains about the incorrect "tools:replace"
- assertStringPresenceInLogRecords(mergingReport, "tools:replace");
- assertFalse(mergingReport.getMergedDocument().isPresent());
- } finally {
- assertTrue(tmpFile.delete());
- }
- }
-
- public void testToolsAnnotationRemoval()
- throws ParserConfigurationException, SAXException, IOException,
- ManifestMerger2.MergeFailureException {
-
- MockLog mockLog = new MockLog();
- String input = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
- + " package=\"com.example.lib3\">\n"
- + "\n"
- + " <application android:label=\"@string/lib_name\" "
- + " tools:replace=\"label\"/>\n"
- + "\n"
- + "</manifest>";
-
- File tmpFile = inputAsFile("testToolsAnnotationRemoval", input);
- assertTrue(tmpFile.exists());
-
- try {
- MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
- ManifestMerger2.MergeType.APPLICATION)
- .withFeatures(ManifestMerger2.Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)
- .merge();
- assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
- // ensure tools annotation removal.
- XmlDocument mergedDocument = mergingReport.getMergedDocument().get();
- Optional<XmlElement> applicationNode = mergedDocument
- .getByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
- assertTrue(applicationNode.isPresent());
- String replaceAttribute = applicationNode.get().getXml().getAttributeNS(
- SdkConstants.TOOLS_URI, "replace");
- assertTrue(Strings.isNullOrEmpty(replaceAttribute));
- System.out.println(mergedDocument.prettyPrint());
- mergedDocument.prettyPrint();
- } finally {
- assertTrue(tmpFile.delete());
- }
- }
-
- public void testToolsAnnotationPresence()
- throws ParserConfigurationException, SAXException, IOException,
- ManifestMerger2.MergeFailureException {
-
- MockLog mockLog = new MockLog();
- String input = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
- + " package=\"com.example.lib3\">\n"
- + "\n"
- + " <application android:label=\"@string/lib_name\" "
- + " tools:replace=\"label\"/>\n"
- + "\n"
- + "</manifest>";
-
- File tmpFile = inputAsFile("testToolsAnnotationRemoval", input);
- assertTrue(tmpFile.exists());
-
- try {
- MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
- ManifestMerger2.MergeType.LIBRARY)
- .merge();
- assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
- // ensure tools annotation removal.
- XmlDocument mergedDocument = mergingReport.getMergedDocument().get();
- Optional<XmlElement> applicationNode = mergedDocument
- .getByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
- assertTrue(applicationNode.isPresent());
- String replaceAttribute = applicationNode.get().getXml().getAttributeNS(
- SdkConstants.TOOLS_URI, "replace");
- assertEquals("label", replaceAttribute);
- System.out.println(mergedDocument.prettyPrint());
- } finally {
- assertTrue(tmpFile.delete());
- }
- }
-
-
- public void testPackageOverride()
- throws ParserConfigurationException, SAXException, IOException {
- String xml = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\""
- + " package=\"com.foo.old\" >\n"
- + " <activity android:name=\"activityOne\"/>\n"
- + "</manifest>";
-
- XmlDocument refDocument = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(), "testPackageOverride#xml"), xml);
-
- ManifestMerger2.SystemProperty.PACKAGE.addTo(mActionRecorder, refDocument, "com.bar.new");
- // verify the package value was overriden.
- assertEquals("com.bar.new", refDocument.getRootNode().getXml().getAttribute("package"));
- }
-
- public void testMissingPackageOverride()
- throws ParserConfigurationException, SAXException, IOException {
- String xml = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"activityOne\"/>\n"
- + "</manifest>";
-
- XmlDocument refDocument = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(), "testMissingPackageOverride#xml"), xml);
-
- ManifestMerger2.SystemProperty.PACKAGE.addTo(mActionRecorder, refDocument, "com.bar.new");
- // verify the package value was added.
- assertEquals("com.bar.new", refDocument.getRootNode().getXml().getAttribute("package"));
- }
-
- public void testAddingSystemProperties()
- throws ParserConfigurationException, SAXException, IOException {
- String xml = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"activityOne\"/>\n"
- + "</manifest>";
-
- XmlDocument document = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(),
- "testAddingSystemProperties#xml"), xml);
-
- ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
- assertEquals("101",
- document.getXml().getDocumentElement().getAttribute("android:versionCode"));
-
- ManifestMerger2.SystemProperty.VERSION_NAME.addTo(mActionRecorder, document, "1.0.1");
- assertEquals("1.0.1",
- document.getXml().getDocumentElement().getAttribute("android:versionName"));
-
- ManifestMerger2.SystemProperty.MIN_SDK_VERSION.addTo(mActionRecorder, document, "10");
- Element usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
- assertNotNull(usesSdk);
- assertEquals("10", usesSdk.getAttribute("android:minSdkVersion"));
-
- ManifestMerger2.SystemProperty.TARGET_SDK_VERSION.addTo(mActionRecorder, document, "14");
- usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
- assertNotNull(usesSdk);
- assertEquals("14", usesSdk.getAttribute("android:targetSdkVersion"));
-
- ManifestMerger2.SystemProperty.MAX_SDK_VERSION.addTo(mActionRecorder, document, "16");
- usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
- assertNotNull(usesSdk);
- assertEquals("16", usesSdk.getAttribute("android:maxSdkVersion"));
- }
-
- public void testAddingSystemProperties_withDifferentPrefix()
- throws ParserConfigurationException, SAXException, IOException {
- String xml = ""
- + "<manifest\n"
- + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity t:name=\"activityOne\"/>\n"
- + "</manifest>";
-
- XmlDocument document = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(),
- "testAddingSystemProperties#xml"), xml
- );
-
- ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
- // using the non namespace aware API to make sure the prefix is the expected one.
- assertEquals("101",
- document.getXml().getDocumentElement().getAttribute("t:versionCode"));
- }
-
- public void testOverridingSystemProperties()
- throws ParserConfigurationException, SAXException, IOException {
- String xml = ""
- + "<manifest versionCode=\"34\" versionName=\"3.4\"\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <uses-sdk minSdkVersion=\"9\" targetSdkVersion=\".9\"/>\n"
- + " <activity android:name=\"activityOne\"/>\n"
- + "</manifest>";
-
- XmlDocument document = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(),
- "testAddingSystemProperties#xml"), xml);
- // check initial state.
- assertEquals("34", document.getXml().getDocumentElement().getAttribute("versionCode"));
- assertEquals("3.4", document.getXml().getDocumentElement().getAttribute("versionName"));
- Element usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
- assertNotNull(usesSdk);
- assertEquals("9", usesSdk.getAttribute("minSdkVersion"));
- assertEquals(".9", usesSdk.getAttribute("targetSdkVersion"));
-
-
- ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
- assertEquals("101",
- document.getXml().getDocumentElement().getAttribute("android:versionCode"));
-
- ManifestMerger2.SystemProperty.VERSION_NAME.addTo(mActionRecorder, document, "1.0.1");
- assertEquals("1.0.1",
- document.getXml().getDocumentElement().getAttribute("android:versionName"));
-
- ManifestMerger2.SystemProperty.MIN_SDK_VERSION.addTo(mActionRecorder, document, "10");
- usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
- assertNotNull(usesSdk);
- assertEquals("10", usesSdk.getAttribute("android:minSdkVersion"));
-
- ManifestMerger2.SystemProperty.TARGET_SDK_VERSION.addTo(mActionRecorder, document, "14");
- usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
- assertNotNull(usesSdk);
- assertEquals("14", usesSdk.getAttribute("android:targetSdkVersion"));
- }
-
- public void testPlaceholderSubstitution()
- throws ParserConfigurationException, SAXException, IOException,
- ManifestMerger2.MergeFailureException {
- String xml = ""
- + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\".activityOne\" android:label=\"${labelName}\"/>\n"
- + "</manifest>";
-
- Map<String, String> placeholders = ImmutableMap.of("labelName", "injectedLabelName");
- MockLog mockLog = new MockLog();
- File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
- try {
- MergingReport mergingReport = ManifestMerger2
- .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
- .setPlaceHolderValues(placeholders)
- .merge();
-
- assertTrue(mergingReport.getResult().isSuccess());
- assertTrue(mergingReport.getMergedDocument().isPresent());
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- Optional<XmlElement> activityOne = xmlDocument
- .getByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "foo.activityOne");
- assertTrue(activityOne.isPresent());
- Optional<XmlAttribute> attribute = activityOne.get()
- .getAttribute(XmlNode.fromXmlName("android:label"));
- assertTrue(attribute.isPresent());
- assertEquals("injectedLabelName", attribute.get().getValue());
- } finally {
- inputFile.delete();
- }
- }
-
- public void testApplicationIdSubstitution()
- throws ManifestMerger2.MergeFailureException, IOException {
- String xml = ""
- + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"${applicationId}.activityOne\"/>\n"
- + "</manifest>";
-
- MockLog mockLog = new MockLog();
- File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
- try {
- MergingReport mergingReport = ManifestMerger2
- .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
- .setOverride(ManifestMerger2.SystemProperty.PACKAGE, "bar")
- .merge();
-
- assertTrue(mergingReport.getResult().isSuccess());
- assertTrue(mergingReport.getMergedDocument().isPresent());
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- assertEquals("bar", xmlDocument.getPackageName());
- Optional<XmlElement> activityOne = xmlDocument
- .getByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "bar.activityOne");
- assertTrue(activityOne.isPresent());
- } finally {
- inputFile.delete();
- }
- }
-
- public void testNoApplicationIdValueProvided()
- throws IOException, ManifestMerger2.MergeFailureException {
- String xml = ""
- + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"${applicationId}.activityOne\"/>\n"
- + "</manifest>";
-
- MockLog mockLog = new MockLog();
- File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
- try {
- MergingReport mergingReport = ManifestMerger2
- .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
- .merge();
-
- assertTrue(mergingReport.getResult().isSuccess());
- assertTrue(mergingReport.getMergedDocument().isPresent());
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- assertEquals("foo", xmlDocument.getPackageName());
- Optional<XmlElement> activityOne = xmlDocument
- .getByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "foo.activityOne");
- assertTrue(activityOne.isPresent());
- } finally {
- inputFile.delete();
- }
- }
-
- public void testNoFqcnsExtraction()
- throws ParserConfigurationException, SAXException, IOException,
- ManifestMerger2.MergeFailureException {
- String xml = ""
- + "<manifest\n"
- + " package=\"com.foo.example\""
- + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity t:name=\"activityOne\"/>\n"
- + " <activity t:name=\"com.foo.bar.example.activityTwo\"/>\n"
- + " <activity t:name=\"com.foo.example.activityThree\"/>\n"
- + " <application t:name=\".applicationOne\" "
- + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
- + "</manifest>";
-
- File inputFile = inputAsFile("testFcqnsExtraction", xml);
-
- MockLog mockLog = new MockLog();
- MergingReport mergingReport = ManifestMerger2
- .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
- .merge();
-
- assertTrue(mergingReport.getResult().isSuccess());
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- assertEquals("com.foo.example.activityOne",
- xmlDocument.getXml().getElementsByTagName("activity").item(0).getAttributes()
- .item(0).getNodeValue());
- assertEquals("com.foo.bar.example.activityTwo",
- xmlDocument.getXml().getElementsByTagName("activity").item(1).getAttributes()
- .item(0).getNodeValue());
- assertEquals("com.foo.example.activityThree",
- xmlDocument.getXml().getElementsByTagName("activity").item(2).getAttributes()
- .item(0).getNodeValue());
- assertEquals("com.foo.example.applicationOne",
- xmlDocument.getXml().getElementsByTagName("application").item(0).getAttributes()
- .getNamedItemNS("http://schemas.android.com/apk/res/android", "name")
- .getNodeValue());
- assertEquals("com.foo.example.myBackupAgent",
- xmlDocument.getXml().getElementsByTagName("application").item(0).getAttributes()
- .getNamedItemNS("http://schemas.android.com/apk/res/android", "backupAgent")
- .getNodeValue());
- }
-
- public void testFqcnsExtraction()
- throws ParserConfigurationException, SAXException, IOException,
- ManifestMerger2.MergeFailureException {
- String xml = ""
- + "<manifest\n"
- + " package=\"com.foo.example\""
- + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity t:name=\"activityOne\"/>\n"
- + " <activity t:name=\"com.foo.bar.example.activityTwo\"/>\n"
- + " <activity t:name=\"com.foo.example.activityThree\"/>\n"
- + " <application t:name=\".applicationOne\" "
- + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
- + "</manifest>";
-
- File inputFile = inputAsFile("testFcqnsExtraction", xml);
-
- MockLog mockLog = new MockLog();
- MergingReport mergingReport = ManifestMerger2
- .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
- .withFeatures(ManifestMerger2.Invoker.Feature.EXTRACT_FQCNS)
- .merge();
-
- assertTrue(mergingReport.getResult().isSuccess());
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- assertEquals(".activityOne",
- xmlDocument.getXml().getElementsByTagName("activity").item(0).getAttributes()
- .item(0).getNodeValue());
- assertEquals("com.foo.bar.example.activityTwo",
- xmlDocument.getXml().getElementsByTagName("activity").item(1).getAttributes()
- .item(0).getNodeValue());
- assertEquals(".activityThree",
- xmlDocument.getXml().getElementsByTagName("activity").item(2).getAttributes()
- .item(0).getNodeValue());
- assertEquals(".applicationOne",
- xmlDocument.getXml().getElementsByTagName("application").item(0).getAttributes()
- .getNamedItemNS("http://schemas.android.com/apk/res/android", "name")
- .getNodeValue());
- assertEquals(".myBackupAgent",
- xmlDocument.getXml().getElementsByTagName("application").item(0).getAttributes()
- .getNamedItemNS("http://schemas.android.com/apk/res/android", "backupAgent")
- .getNodeValue());
- }
-
- public void testNoPlaceholderReplacement()
- throws IOException, ManifestMerger2.MergeFailureException {
- String xml = ""
- + "<manifest\n"
- + " package=\"${applicationId}\""
- + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity t:name=\"activityOne\"/>\n"
- + " <application t:name=\".applicationOne\" "
- + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
- + "</manifest>";
-
- File inputFile = inputAsFile("testNoPlaceHolderReplacement", xml);
-
- MockLog mockLog = new MockLog();
- MergingReport mergingReport = ManifestMerger2
- .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
- .withFeatures(ManifestMerger2.Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)
- .merge();
-
- assertTrue(mergingReport.getResult().isSuccess());
- XmlDocument xmlDocument = mergingReport.getMergedDocument().get();
- assertEquals("${applicationId}",
- xmlDocument.getXml().getElementsByTagName("manifest")
- .item(0).getAttributes().getNamedItem("package").getNodeValue());
- }
-
- /**
- * Utility method to save a {@link String} XML into a file.
- */
- private static File inputAsFile(String testName, String input) throws IOException {
- File tmpFile = File.createTempFile(testName, ".xml");
- FileWriter fw = null;
- try {
- fw = new FileWriter(tmpFile);
- fw.append(input);
- } finally {
- if (fw != null) fw.close();
- }
- return tmpFile;
- }
-
- private static void assertStringPresenceInLogRecords(MergingReport mergingReport, String s) {
- for (MergingReport.Record record : mergingReport.getLoggingRecords()) {
- if (record.toString().contains(s)) {
- return;
- }
- }
- // failed, dump the records
- for (MergingReport.Record record : mergingReport.getLoggingRecords()) {
- Logger.getAnonymousLogger().info(record.toString());
- }
- fail("could not find " + s + " in logging records");
- }
-}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java b/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java
deleted file mode 100644
index 13152a7..0000000
--- a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.ManifestMerger2.SystemProperty;
-import static com.android.manifmerger.MergingReport.Record;
-
-import com.android.annotations.Nullable;
-import com.android.utils.StdLogger;
-import com.google.common.base.Optional;
-import com.google.common.base.Strings;
-
-import junit.framework.Test;
-import junit.framework.TestSuite;
-
-import java.io.BufferedReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Tests for the {@link com.android.manifmerger.ManifestMerger2} class
- */
-public class ManifestMerger2Test extends ManifestMergerTest {
-
- // so far, I only support 3 original tests.
- private static String[] sDataFiles = new String[]{
- "00_noop",
- "03_inject_attributes.xml",
- "05_inject_package.xml",
- "05_inject_package_placeholder.xml",
- "05_inject_package_with_overlays.xml",
- "06_inject_attributes_with_specific_prefix.xml",
- "07_no_package_provided.xml",
- "08_no_library_package_provided.xml",
- "09_overlay_package_provided.xml",
- "08b_library_injection.xml",
- "09b_overlay_package_different.xml",
- "09c_overlay_package_not_provided.xml",
- "10_activity_merge",
- "11_activity_dup",
- "12_alias_dup",
- "13_service_dup",
- "14_receiver_dup",
- "15_provider_dup",
- "16_fqcn_merge",
- "17_fqcn_conflict",
- "18_fqcn_success",
- "20_uses_lib_merge",
- "21_uses_main_errors",
- "22_uses_lib_errors",
- "25_permission_merge",
- "26_permission_dup",
- "28_uses_perm_merge",
- "29_uses_perm_selector",
- "29b_uses_perm_invalidSelector",
- "30_uses_sdk_ok",
- "32_uses_sdk_minsdk_ok",
- "33_uses_sdk_minsdk_conflict",
- "33b_uses_sdk_minsdk_override.xml",
- "33c_uses_sdk_minsdk_override_and_conflict.xml",
- "34_inject_uses_sdk_no_dup.xml",
- "36_uses_sdk_targetsdk_warning",
- "40_uses_feat_merge",
- "41_uses_feat_errors",
- "45_uses_feat_gles_once",
- "47_uses_feat_gles_conflict",
- "50_uses_conf_warning",
- "52_support_screens_warning",
- "54_compat_screens_warning",
- "56_support_gltext_warning",
- "60_merge_order",
- "65_override_app",
- "66_remove_app",
- "67_override_activities",
- "68_override_uses",
- "69_remove_uses",
- "70_expand_fqcns",
- "71_extract_package_prefix",
- "75_app_metadata_merge",
- "76_app_metadata_ignore",
- "77_app_metadata_conflict",
- "78_removeAll",
- "79_custom_node.xml",
- };
-
- @Override
- protected String getTestDataDirectory() {
- return "data2";
- }
-
- /**
- * This overrides the default test suite created by junit. The test suite is a bland TestSuite
- * with a dedicated name. We inject as many instances of {@link ManifestMergerTest} in the suite
- * as we have declared data files above.
- *
- * @return A new {@link junit.framework.TestSuite}.
- */
- public static Test suite() {
- TestSuite suite = new TestSuite();
- // Give a non-generic name to our test suite, for better unit reports.
- suite.setName("ManifestMergerTestSuite");
-
- for (String fileName : sDataFiles) {
- suite.addTest(TestSuite.createTest(ManifestMerger2Test.class, fileName));
- }
-
- return suite;
- }
-
- public ManifestMerger2Test(String testName) {
- super(testName);
- }
-
- /**
- * Processes the data from the given
- * {@link com.android.manifmerger.ManifestMergerTest.TestFiles} by invoking {@link
- * ManifestMerger#process(java.io.File, java.io.File, java.io.File[], java.util.Map, String)}:
- * the given library files are applied consecutively to the main XML document and the output is
- * generated. <p/> Then the expected and actual outputs are loaded into a DOM, dumped again to a
- * String using an XML transform and compared. This makes sure only the structure is checked and
- * that any formatting is ignored in the comparison.
- *
- * @param testFiles The test files to process. Must not be null.
- * @throws Exception when this go wrong.
- */
- @Override
- void processTestFiles(TestFiles testFiles) throws Exception {
-
- StdLogger stdLogger = new StdLogger(StdLogger.Level.VERBOSE);
- ManifestMerger2.Invoker invoker = ManifestMerger2.newMerger(testFiles.getMain(),
- stdLogger, ManifestMerger2.MergeType.APPLICATION)
- .addLibraryManifests(testFiles.getLibs())
- .addFlavorAndBuildTypeManifests(testFiles.getOverlayFiles())
- .withFeatures(ManifestMerger2.Invoker.Feature.KEEP_INTERMEDIARY_STAGES,
- ManifestMerger2.Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
-
- if (!Strings.isNullOrEmpty(testFiles.getPackageOverride())) {
- invoker.setOverride(SystemProperty.PACKAGE, testFiles.getPackageOverride());
- }
-
- for (Map.Entry<String, String> injectable : testFiles.getInjectAttributes().entrySet()) {
- SystemProperty systemProperty = getSystemProperty(injectable.getKey());
- if (systemProperty != null) {
- invoker.setOverride(systemProperty, injectable.getValue());
- } else {
- invoker.setPlaceHolderValue(injectable.getKey(), injectable.getValue());
- }
- }
-
- MergingReport mergeReport = invoker.merge();
-
-
- // this is obviously quite hacky, refine once merge output is better defined.
- boolean notExpectingError = !isExpectingError(testFiles.getExpectedErrors());
- mergeReport.log(stdLogger);
- if (mergeReport.getMergedDocument().isPresent()) {
-
- XmlDocument actualResult = mergeReport.getMergedDocument().get();
- String prettyResult = actualResult.prettyPrint();
- stdLogger.info(prettyResult);
-
- if (testFiles.getActualResult() != null) {
- FileWriter writer = new FileWriter(testFiles.getActualResult());
- try {
- writer.append(prettyResult);
- } finally {
- writer.close();
- }
- }
-
- if (!notExpectingError) {
- fail("Did not get expected error : " + testFiles.getExpectedErrors());
- }
-
- XmlDocument expectedResult = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(), testFiles.getMain().getName()),
- testFiles.getExpectedResult());
- Optional<String> comparingMessage =
- expectedResult.compareTo(actualResult);
-
- if (comparingMessage.isPresent()) {
- Logger.getAnonymousLogger().severe(comparingMessage.get());
- fail(comparingMessage.get());
- }
- // process any warnings.
- if (mergeReport.getResult() == MergingReport.Result.WARNING) {
- compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
- }
- } else {
- for (Record record : mergeReport.getLoggingRecords()) {
- Logger.getAnonymousLogger().info("Returned log: " + record);
- }
- compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
- assertFalse(notExpectingError);
- }
- }
-
- private boolean isExpectingError(String expectedOutput) throws IOException {
- StringReader stringReader = new StringReader(expectedOutput);
- BufferedReader reader = new BufferedReader(stringReader);
- String line;
- while ((line = reader.readLine()) != null) {
- if (line.startsWith("ERROR")) return true;
- }
- return false;
- }
-
- private void compareExpectedAndActualErrors(
- MergingReport mergeReport,
- String expectedOutput) throws IOException {
-
- StringReader stringReader = new StringReader(expectedOutput);
- BufferedReader reader = new BufferedReader(stringReader);
- String line = reader.readLine();
- List<Record> records = new ArrayList<Record>(mergeReport.getLoggingRecords());
- while (line != null) {
- if (line.startsWith("WARNING") || line.startsWith("ERROR")) {
- String message = line;
- do {
- line = reader.readLine();
- if (line != null && line.startsWith(" ")) {
- message = message + "\n" + line;
- }
- } while (line != null && line.startsWith(" "));
-
- // next might generate an exception which will make the test fail when we
- // get unexpected error message.
- if (!findLineInRecords(message, records)) {
-
- StringBuilder errorMessage = new StringBuilder();
- dumpRecords(records, errorMessage);
- errorMessage.append("Cannot find expected error : \n").append(message);
- fail(errorMessage.toString());
- }
- }
- }
- // check that we do not have any unexpected error messages.
- if (!records.isEmpty()) {
- StringBuilder message = new StringBuilder();
- dumpRecords(records, message);
- message.append("Unexpected error message(s)");
- fail(message.toString());
- }
- }
-
- private boolean findLineInRecords(String errorLine, List<Record> records) {
- String severity = errorLine.substring(0, errorLine.indexOf(':'));
- String message = errorLine.substring(errorLine.indexOf(':') + 1);
- for (Record record : records) {
- int indexOfSuggestions = record.getMessage().indexOf("\n\tSuggestion:");
- String messageRecord = indexOfSuggestions != -1
- ? record.getMessage().substring(0, indexOfSuggestions)
- : record.getMessage();
- Pattern pattern = Pattern.compile(message);
- Matcher matcher = pattern.matcher(messageRecord.replaceAll("\t", " "));
- if (matcher.matches() && record.getSeverity() == Record.Severity.valueOf(severity)) {
- records.remove(record);
- return true;
- }
- }
- return false;
- }
-
- @Nullable
- private SystemProperty getSystemProperty(String name) {
- for (SystemProperty systemProperty : SystemProperty.values()) {
- if (systemProperty.toCamelCase().equals(name)) {
- return systemProperty;
- }
- }
- return null;
- }
-
- private void dumpRecords(List<Record> records, StringBuilder stringBuilder) {
- stringBuilder.append("\n------------ Records : \n");
- for (Record record : records) {
- stringBuilder.append(record.toString());
- stringBuilder.append("\n");
- }
- stringBuilder.append("------------ End of records.\n");
- }
-}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java b/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java
deleted file mode 100755
index 337abb7..0000000
--- a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.manifmerger;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.manifmerger.IMergerLog.FileAndLine;
-import com.android.sdklib.mock.MockLog;
-
-import junit.framework.TestCase;
-
-import org.w3c.dom.Document;
-
-import java.io.File;
-
-public class ManifestMergerSourceLinkTest extends TestCase {
- public void testSourceLinks() throws Exception {
- MockLog log = new MockLog();
- IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
- ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
- @Override
- public int queryCodenameApiLevel(@NonNull String codename) {
- if ("ApiCodename1".equals(codename)) {
- return 1;
- } else if ("ApiCodename10".equals(codename)) {
- return 10;
- }
- return ICallback.UNKNOWN_CODENAME;
- }
- });
- merger.setInsertSourceMarkers(true);
-
- Document mainDoc = MergerXmlUtils.parseDocument(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
- + " package=\"com.example.app1\"\n"
- + " android:versionCode=\"100\"\n"
- + " android:versionName=\"1.0.0\">\n"
- + "\n"
- + " <uses-sdk android:minSdkVersion=\"3\" android:targetSdkVersion=\"11\"/>\n"
- + "\n"
- + " <application\n"
- + " android:name=\"TheApp\"\n"
- + " android:backupAgent=\".MyBackupAgent\" >\n"
- + " <activity android:name=\".MainActivity\" />\n"
- + " <receiver android:name=\"AppReceiver\" />\n"
- + " <activity android:name=\"com.example.lib2.LibActivity\" />\n"
- + "\n"
- + " <!-- This key is defined in the main application. -->\n"
- + " <meta-data\n"
- + " android:name=\"name.for.yet.another.api.key\"\n"
- + " android:value=\"your_yet_another_api_key\"/>\n"
- + "\n"
- + " <!-- Merged elements will be appended here at the end. -->\n"
- + " </application>\n"
- + "\n"
- + "</manifest>",
- mergerLog, new FileAndLine("main", 1));
- assertNotNull(mainDoc);
- Document library1 = MergerXmlUtils.parseDocument(""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.app1\">\n"
- + "\n"
- + " <application android:name=\"TheApp\" >\n"
- + " <activity android:name=\".Library1\" />\n"
- + "\n"
- + " <!-- The library maps API key gets merged in the main application. -->\n"
- + " <meta-data\n"
- + " android:name=\"name.for.maps.api.key\"\n"
- + " android:value=\"your_maps_api_key\"/>\n"
- + "\n"
- + " <!-- The library backup key gets merged in the main application. -->\n"
- + " <meta-data\n"
- + " android:name=\"name.for.backup.api.key\"\n"
- + " android:value=\"your_backup_api_key\" />\n"
- + " </application>\n"
- + "\n"
- + "</manifest>",
- mergerLog, new FileAndLine("library1", 1));
- assertNotNull(library1);
- Document library2 = MergerXmlUtils.parseDocument(""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.lib3\">\n"
- + "\n"
- + " <!-- This comment is ignored. -->\n"
- + "\n"
- + " <application android:label=\"@string/lib_name\" >\n"
- + "\n"
- + " <!-- The first comment just before the element\n"
- + " is carried over as-is.\n"
- + " -->\n"
- + " <!-- Formatting is preserved. -->\n"
- + " <!-- All consecutive comments are taken together. -->\n"
- + "\n"
- + " <activity-alias\n"
- + " android:name=\"com.example.alias.MyActivity\"\n"
- + " android:targetActivity=\"com.example.MainActivity\"\n"
- + " android:label=\"@string/alias_name\"\n"
- + " android:icon=\"@drawable/alias_icon\"\n"
- + " >\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\" />\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
- + " </intent-filter>\n"
- + " </activity-alias>\n"
- + "\n"
- + " <!-- This is a dup of the 2nd activity in lib2 -->\n"
- + " <activity\n"
- + " android:name=\"com.example.LibActivity2\"\n"
- + " android:label=\"@string/lib_activity_name\"\n"
- + " android:icon=\"@drawable/lib_activity_icon\"\n"
- + " android:theme=\"@style/Lib.Theme\">\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\" />\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + "\n"
- + " </application>\n"
- + "\n"
- + "</manifest>",
- mergerLog, new FileAndLine("library2", 1));
- assertNotNull(library2);
-
- Document library3 = MergerXmlUtils.parseDocument(""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.lib3\">\n"
- + "\n"
- + " <application android:label=\"@string/lib_name\" >\n"
- + " <activity\n"
- + " android:name=\"com.example.LibActivity3\"\n"
- + " android:label=\"@string/lib_activity_name3\"\n"
- + " android:icon=\"@drawable/lib_activity_icon3\"\n"
- + " android:theme=\"@style/Lib.Theme\">\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\" />\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + "\n"
- + " </application>\n"
- + "\n"
- + "</manifest>",
- mergerLog, new FileAndLine("library3", 1));
- assertNotNull(library3);
-
- MergerXmlUtils.setSource(mainDoc, new File("/path/to/main/doc"));
- MergerXmlUtils.setSource(library1, new File("/path/to/library1"));
- MergerXmlUtils.setSource(library2, new File("/path/to/library2"));
- MergerXmlUtils.setSource(library3, new File("/path/to/library3"));
-
- boolean ok = merger.process(mainDoc, library1, library2, library3);
- assertTrue(ok);
- String actual = MergerXmlUtils.printXmlString(mainDoc, mergerLog);
- assertEquals("Encountered unexpected errors/warnings", "", log.toString());
- String expected = ""
- + "<!-- From: file:/path/to/main/doc -->\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" android:versionCode=\"100\" android:versionName=\"1.0.0\" package=\"com.example.app1\">\n"
- + "\n"
- + " <uses-sdk android:minSdkVersion=\"3\" android:targetSdkVersion=\"11\"/>\n"
- + "\n"
- + " <application android:backupAgent=\"com.example.app1.MyBackupAgent\" android:name=\"com.example.app1.TheApp\">\n"
- + " <activity android:name=\"com.example.app1.MainActivity\"/>\n"
- + " <receiver android:name=\"com.example.app1.AppReceiver\"/>\n"
- + " <activity android:name=\"com.example.lib2.LibActivity\"/>\n"
- + "\n"
- + " <!-- This key is defined in the main application. -->\n"
- + " <meta-data android:name=\"name.for.yet.another.api.key\" android:value=\"your_yet_another_api_key\"/>\n"
- + "\n"
- + " <!-- Merged elements will be appended here at the end. -->\n"
- + " <!-- From: file:/path/to/library1 -->\n"
- + " <activity android:name=\"com.example.app1.Library1\"/>\n"
- + "\n"
- + " <!-- The library maps API key gets merged in the main application. -->\n"
- + " <meta-data android:name=\"name.for.maps.api.key\" android:value=\"your_maps_api_key\"/>\n"
- + "\n"
- + " <!-- The library backup key gets merged in the main application. -->\n"
- + " <meta-data android:name=\"name.for.backup.api.key\" android:value=\"your_backup_api_key\"/>\n"
- + "\n"
- + " <!-- From: file:/path/to/library2 -->\n"
- + " <!-- This is a dup of the 2nd activity in lib2 -->\n"
- + " <activity android:icon=\"@drawable/lib_activity_icon\" android:label=\"@string/lib_activity_name\" android:name=\"com.example.LibActivity2\" android:theme=\"@style/Lib.Theme\">\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\"/>\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + "\n"
- + " <!-- The first comment just before the element\n"
- + " is carried over as-is.\n"
- + " -->\n"
- + " <!-- Formatting is preserved. -->\n"
- + " <!-- All consecutive comments are taken together. -->\n"
- + "\n"
- + " <activity-alias android:icon=\"@drawable/alias_icon\" android:label=\"@string/alias_name\" android:name=\"com.example.alias.MyActivity\" android:targetActivity=\"com.example.MainActivity\">\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\"/>\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
- + " </intent-filter>\n"
- + " </activity-alias>\n"
- + " <!-- From: file:/path/to/library3 -->\n"
- + " <activity android:icon=\"@drawable/lib_activity_icon3\" android:label=\"@string/lib_activity_name3\" android:name=\"com.example.LibActivity3\" android:theme=\"@style/Lib.Theme\">\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\"/>\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " <!-- From: file:/path/to/main/doc -->\n"
- + " \n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n";
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- // Adjust mock paths & EOLs for windows
- actual = actual.replace("\r\n", "\n");
- expected = expected.replace("file:/path/to/", "file:/C:/path/to/");
- }
-
- try {
- assertEquals(expected, actual);
-
- } catch (Exception originalFailure) {
- // DOM implementations vary slightly whether they'll insert a newline for comment
- // inserted outside document
- // JDK 7 doesn't, JDK 6 does
- int index = expected.indexOf('\n');
- assertTrue(index != -1);
- expected = expected.substring(0, index) + expected.substring(index + 1);
- try {
- assertEquals(expected, actual);
- } catch (Throwable ignore) {
- // If the second test fails too, throw the *original* exception,
- // before we tried to tweak the EOL.
- throw originalFailure;
- }
- }
- }
-}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java b/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
deleted file mode 100755
index def79fd..0000000
--- a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
+++ /dev/null
@@ -1,661 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.manifmerger.IMergerLog.FileAndLine;
-import com.android.sdklib.mock.MockLog;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-import org.w3c.dom.Document;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Some utilities to reduce repetitions in the {@link ManifestMergerTest}s.
- * <p/>
- * See {@link #loadTestData(String)} for an explanation of the data file format.
- */
-public class ManifestMergerTest extends TestCase {
-
- /**
- * Delimiter that indicates the test must fail.
- * An XML output and errors are still generated and checked.
- */
- private static final String DELIM_FAILS = "fails";
- /**
- * Delimiter that starts a library XML content.
- * The delimiter name must be in the form {@code @libSomeName} and it will be
- * used as the base for the test file name. Using separate lib names is encouraged
- * since it makes the error output easier to read.
- */
- private static final String DELIM_LIB = "lib";
- /**
- * Delimiter that starts the main manifest XML content.
- */
- private static final String DELIM_MAIN = "main";
- /**
- * Delimiter that starts an overlay XML content.
- * The delimiter must follow the same rules as {@link #DELIM_LIB}
- */
- private static final String DELIM_OVERLAY = "overlay";
- /**
- * Delimiter that starts the resulting XML content, whatever is generated by the merge.
- */
- private static final String DELIM_RESULT = "result";
- /**
- * Delimiter that starts the SdkLog output.
- * The logger prints each entry on its lines, prefixed with E for errors,
- * W for warnings and P for regular printfs.
- */
- private static final String DELIM_ERRORS = "errors";
- /**
- * Delimiter for starts a section that declares how to inject an attribute.
- * The section is composed of one or more lines with the
- * syntax: "/node/node|attr-URI attrName=attrValue".
- * This is essentially a pseudo XPath-like expression that is described in
- * {@link ManifestMerger#process(Document, File[], Map, String)}.
- */
- private static final String DELIM_INJECT_ATTR = "inject";
- /**
- * Delimiter for a section that declares how to toggle a ManifMerger option.
- * The section is composed of one or more lines with the
- * syntax: "functionName=false|true".
- */
- private static final String DELIM_FEATURES = "features";
-
- /**
- * Delimiter for a section that declares how to override the package.
- * The section is composed of one line containing the new package name.
- */
- private static final String DELIM_PACKAGE = "package";
-
-
- /*
- * Wait, I hear you, where are the tests?
- *
- * processTestFiles() uses loadTestData(), which uses one of the data filename
- * indicated below.
- *
- * We could simplify this even further by dynamically finding the data
- * files to use; however there's some value in having tests break when out
- * of sync with the known data file set.
- */
- private static String[] sDataFiles = new String[] {
- "00_noop",
- "01_ignore_app_attr",
- "02_ignore_instrumentation",
- "03_inject_attributes",
- "04_inject_attributes",
- "05_inject_package",
- "10_activity_merge",
- "11_activity_dup",
- "12_alias_dup",
- "13_service_dup",
- "14_receiver_dup",
- "15_provider_dup",
- "16_fqcn_merge",
- "17_fqcn_conflict",
- "20_uses_lib_merge",
- "21_uses_lib_errors",
- "25_permission_merge",
- "26_permission_dup",
- "28_uses_perm_merge",
- "30_uses_sdk_ok",
- "32_uses_sdk_minsdk_ok",
- "33_uses_sdk_minsdk_conflict",
- "36_uses_sdk_targetsdk_warning",
- "40_uses_feat_merge",
- "41_uses_feat_errors",
- "45_uses_feat_gles_once",
- "47_uses_feat_gles_conflict",
- "50_uses_conf_warning",
- "52_support_screens_warning",
- "54_compat_screens_warning",
- "56_support_gltext_warning",
- "60_merge_order",
- "65_override_app",
- "66_remove_app",
- "67_override_activities",
- "68_override_uses",
- "69_remove_uses",
- "70_expand_fqcns",
- "71_extract_package_prefix",
- "75_app_metadata_merge",
- "76_app_metadata_ignore",
- "77_app_metadata_conflict",
- };
-
- /**
- * This overrides the default test suite created by junit.
- * The test suite is a bland TestSuite with a dedicated name.
- * We inject as many instances of {@link ManifestMergerTest} in the suite
- * as we have declared data files above.
- *
- * @return A new {@link TestSuite}.
- */
- public static Test suite() {
- TestSuite suite = new TestSuite();
- // Give a non-generic name to our test suite, for better unit reports.
- suite.setName("ManifestMergerTestSuite");
-
- for (String fileName : sDataFiles) {
- suite.addTest(TestSuite.createTest(ManifestMergerTest.class, fileName));
- }
-
- return suite;
- }
-
-
- /**
- * Default constructor invoked by {@link TestSuite#createTest(Class, String)}.
- *
- * @param testName The test name provided to {@code TestSuite.createTest()}.
- * This is later accessible via {@link #getName()}.
- */
- public ManifestMergerTest(String testName) {
- super(testName);
- }
-
- /**
- * Invoked by the test framework to run the specific test which name
- * has been passed to the constructor.
- * Note that we create one instance of this class per test to run in
- * the associated {@link TestSuite}.
- */
- @Override
- protected void runTest() throws Throwable {
- String testName = getName();
- assertNotNull(testName);
- processTestFiles(loadTestData(testName));
- }
-
-
- static class TestFiles {
- private final File[] mOverlayFiles;
- private final File mMain;
- private final File[] mLibs;
- private final Map<String, String> mInjectAttributes;
- private final String mPackageOverride;
- private final File mActualResult;
- private final String mExpectedResult;
- private final String mExpectedErrors;
- private final boolean mShouldFail;
- private final Map<String, Boolean> mFeatures;
-
- /** Files used by a given test case. */
- public TestFiles(
- boolean shouldFail,
- @NonNull File[] overlayFiles,
- @NonNull File main,
- @NonNull File[] libs,
- @NonNull Map<String, Boolean> features,
- @NonNull Map<String, String> injectAttributes,
- @Nullable String packageOverride,
- @Nullable File actualResult,
- @NonNull String expectedResult,
- @NonNull String expectedErrors) {
- mShouldFail = shouldFail;
- mMain = main;
- mLibs = libs;
- mFeatures = features;
- mPackageOverride = packageOverride;
- mInjectAttributes = injectAttributes;
- mActualResult = actualResult;
- mExpectedResult = expectedResult;
- mExpectedErrors = expectedErrors;
- mOverlayFiles = overlayFiles;
- }
-
- public boolean getShouldFail() {
- return mShouldFail;
- }
-
- @NonNull
- public File[] getOverlayFiles() {
- return mOverlayFiles;
- }
-
- @NonNull
- public File getMain() {
- return mMain;
- }
-
- @NonNull
- public File[] getLibs() {
- return mLibs;
- }
-
- @NonNull
- public Map<String, Boolean> getFeatures() {
- return mFeatures;
- }
-
- @NonNull
- public Map<String, String> getInjectAttributes() {
- return mInjectAttributes;
- }
-
- @Nullable
- public String getPackageOverride() {
- return mPackageOverride;
- }
-
- @Nullable
- public File getActualResult() {
- return mActualResult;
- }
-
- @NonNull
- public String getExpectedResult() {
- return mExpectedResult;
- }
-
- public String getExpectedErrors() {
- return mExpectedErrors;
- }
-
- // Try to delete any temp file potentially created.
- public void cleanup() {
- if (mMain != null && mMain.isFile()) {
- mMain.delete();
- }
-
- if (mActualResult != null && mActualResult.isFile()) {
- mActualResult.delete();
- }
-
- for (File f : mLibs) {
- if (f != null && f.isFile()) {
- f.delete();
- }
- }
- }
- }
-
- /**
- * Calls {@link #loadTestData(String)} by
- * inferring the data filename from the caller's method name.
- * <p/>
- * The caller method name must be composed of "test" + the leaf filename.
- * Extensions ".xml" or ".txt" are implied.
- * <p/>
- * E.g. to use the data file "12_foo.xml", simply call this from a method
- * named "test12_foo".
- *
- * @return A new {@link TestFiles} instance. Never null.
- * @throws Exception when things go wrong.
- * @see #loadTestData(String)
- */
- @NonNull
- TestFiles loadTestData() throws Exception {
- StackTraceElement[] stack = Thread.currentThread().getStackTrace();
- for (int i = 0, n = stack.length; i < n; i++) {
- StackTraceElement caller = stack[i];
- String name = caller.getMethodName();
- if (name.startsWith("test")) {
- return loadTestData(name.substring(4));
- }
- }
-
- throw new IllegalArgumentException("No caller method found which name started with 'test'");
- }
-
- /**
- * Returns the relative path the test data directory
- */
- protected String getTestDataDirectory() {
- return "data";
- }
-
- /**
- * Loads test data for a given test case.
- * The input (main + libs) are stored in temp files.
- * A new destination temp file is created to store the actual result output.
- * The expected result is actually kept in a string.
- * <p/>
- * Data File Syntax:
- * <ul>
- * <li> Lines starting with # are ignored (anywhere, as long as # is the first char).
- * <li> Lines before the first {@code @delimiter} are ignored.
- * <li> Empty lines just after the {@code @delimiter}
- * and before the first < XML line are ignored.
- * <li> Valid delimiters are {@code @main} for the XML of the main app manifest.
- * <li> Following delimiters are {@code @libXYZ}, read in the order of definition.
- * The name can be anything as long as it starts with "{@code @lib}".
- * </ul>
- *
- * @param filename The test data filename. If no extension is provided, this will
- * try with .xml or .txt. Must not be null.
- * @return A new {@link TestFiles} instance. Must not be null.
- * @throws Exception when things fail to load properly.
- */
- @NonNull
- TestFiles loadTestData(@NonNull String filename) throws Exception {
-
- String resName = getTestDataDirectory() + File.separator + filename;
- InputStream is = null;
- BufferedReader reader = null;
- BufferedWriter writer = null;
-
- try {
- is = this.getClass().getResourceAsStream(resName);
- if (is == null && !filename.endsWith(".xml")) {
- String resName2 = resName + ".xml";
- is = this.getClass().getResourceAsStream(resName2);
- if (is != null) {
- filename = resName2;
- }
- }
- if (is == null && !filename.endsWith(".txt")) {
- String resName3 = resName + ".txt";
- is = this.getClass().getResourceAsStream(resName3);
- if (is != null) {
- filename = resName3;
- }
- }
- assertNotNull("Test data file not found for " + filename, is);
-
- reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
-
- // Get the temporary directory to use. Just create a temp file, extracts its
- // directory and remove the file.
- File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".tmp");
- File tempDir = tempFile.getParentFile();
- if (!tempFile.delete()) {
- tempFile.deleteOnExit();
- }
-
- String line = null;
- String delimiter = null;
- boolean skipEmpty = true;
-
- boolean shouldFail = false;
- Map<String, Boolean> features = new HashMap<String, Boolean>();
- String packageOverride = null;
- Map<String, String> injectAttributes = new HashMap<String, String>();
- StringBuilder expectedResult = new StringBuilder();
- StringBuilder expectedErrors = new StringBuilder();
- File mainFile = null;
- File actualResultFile = null;
- List<File> libFiles = new ArrayList<File>();
- List<File> overlayFiles = new ArrayList<File>();
- int tempIndex = 0;
-
- while ((line = reader.readLine()) != null) {
- if (skipEmpty && line.trim().isEmpty()) {
- continue;
- }
- if (!line.isEmpty() && line.charAt(0) == '#') {
- continue;
- }
- if (!line.isEmpty() && line.charAt(0) == '@') {
- delimiter = line.substring(1);
- assertTrue(
- "Unknown delimiter @" + delimiter + " in " + filename,
- delimiter.startsWith(DELIM_OVERLAY) ||
- delimiter.startsWith(DELIM_LIB) ||
- delimiter.equals(DELIM_MAIN) ||
- delimiter.equals(DELIM_RESULT) ||
- delimiter.equals(DELIM_ERRORS) ||
- delimiter.equals(DELIM_FAILS) ||
- delimiter.equals(DELIM_FEATURES) ||
- delimiter.equals(DELIM_INJECT_ATTR) ||
- delimiter.equals(DELIM_PACKAGE));
-
- skipEmpty = true;
-
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException ignore) {}
- writer = null;
- }
-
- if (delimiter.equals(DELIM_FAILS)) {
- shouldFail = true;
-
- } else if (!delimiter.equals(DELIM_ERRORS) &&
- !delimiter.equals(DELIM_FEATURES) &&
- !delimiter.equals(DELIM_INJECT_ATTR) &&
- !delimiter.equals(DELIM_PACKAGE)) {
- tempFile = new File(tempDir, String.format("%1$s%2$d_%3$s.xml",
- this.getClass().getSimpleName(),
- tempIndex++,
- delimiter.replaceAll("[^a-zA-Z0-9_-]", "")
- ));
- tempFile.deleteOnExit();
-
- if (delimiter.startsWith(DELIM_OVERLAY)) {
- overlayFiles.add(tempFile);
- } else if (delimiter.startsWith(DELIM_LIB)) {
- libFiles.add(tempFile);
-
- } else if (delimiter.equals(DELIM_MAIN)) {
- mainFile = tempFile;
-
- } else if (delimiter.equals(DELIM_RESULT)) {
- actualResultFile = tempFile;
-
- } else {
- fail("Unexpected data file delimiter @" + delimiter +
- " in " + filename);
- }
-
- if (!delimiter.equals(DELIM_RESULT)) {
- writer = new BufferedWriter(new FileWriter(tempFile));
- }
- }
-
- continue;
- }
- if (delimiter != null &&
- skipEmpty &&
- !line.isEmpty() &&
- line.charAt(0) != '#' &&
- line.charAt(0) != '@') {
- skipEmpty = false;
- }
- if (writer != null) {
- writer.write(line);
- writer.write('\n');
- } else if (DELIM_RESULT.equals(delimiter)) {
- expectedResult.append(line).append('\n');
- } else if (DELIM_ERRORS.equals(delimiter)) {
- expectedErrors.append(line).append('\n');
- } else if (DELIM_INJECT_ATTR.equals(delimiter)) {
- String[] in = line.split("=");
- if (in != null && in.length == 2) {
- injectAttributes.put(in[0], "null".equals(in[1]) ? null : in[1]);
- }
- } else if (DELIM_FEATURES.equals(delimiter)) {
- String[] in = line.split("=");
- if (in != null && in.length == 2) {
- features.put(in[0], Boolean.parseBoolean(in[1]));
- }
- } else if (DELIM_PACKAGE.equals(delimiter)) {
- if (packageOverride == null) {
- packageOverride = line;
- }
- }
- }
-
- assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile);
-
- assert mainFile != null;
-
- Collections.sort(libFiles);
-
- return new TestFiles(
- shouldFail,
- overlayFiles.toArray(new File[overlayFiles.size()]),
- mainFile,
- libFiles.toArray(new File[libFiles.size()]),
- features,
- injectAttributes,
- packageOverride,
- actualResultFile,
- expectedResult.toString(),
- expectedErrors.toString());
-
- } catch (UnsupportedEncodingException e) {
- // BufferedReader failed to decode UTF-8, O'RLY?
- throw e;
-
- } finally {
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException ignore) {}
- }
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException ignore) {}
- }
- if (is != null) {
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- }
- }
-
-// /**
-// * Loads the data test files using {@link #loadTestData()} and then
-// * invokes {@link #processTestFiles(TestFiles)} to test them.
-// *
-// * @see #loadTestData()
-// * @see #processTestFiles(TestFiles)
-// */
-// void processTestFiles() throws Exception {
-// processTestFiles(loadTestData());
-// }
-
- /**
- * Processes the data from the given {@link TestFiles} by
- * invoking {@link ManifestMerger#process(File, File, File[], Map, String)}:
- * the given library files are applied consecutively to the main XML
- * document and the output is generated.
- * <p/>
- * Then the expected and actual outputs are loaded into a DOM,
- * dumped again to a String using an XML transform and compared.
- * This makes sure only the structure is checked and that any
- * formatting is ignored in the comparison.
- *
- * @param testFiles The test files to process. Must not be null.
- * @throws Exception when this go wrong.
- */
- void processTestFiles(TestFiles testFiles) throws Exception {
- MockLog log = new MockLog();
- IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
- ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
- @Override
- public int queryCodenameApiLevel(@NonNull String codename) {
- if ("ApiCodename1".equals(codename)) {
- return 1;
- } else if ("ApiCodename10".equals(codename)) {
- return 10;
- }
- return ICallback.UNKNOWN_CODENAME;
- }
- });
-
- for (Entry<String, Boolean> feature : testFiles.getFeatures().entrySet()) {
- Method m = merger.getClass().getMethod(
- feature.getKey(),
- new Class<?>[] { boolean.class } );
- m.invoke(merger, new Object[] { feature.getValue() } );
- }
-
- boolean processOK = merger.process(testFiles.getActualResult(),
- testFiles.getMain(),
- testFiles.getLibs(),
- testFiles.getInjectAttributes(),
- testFiles.getPackageOverride());
-
- // Convert relative pathnames to absolute.
- String expectedErrors = testFiles.getExpectedErrors().trim();
- expectedErrors = expectedErrors.replaceAll(
- Pattern.quote(testFiles.getMain().getName()),
- Matcher.quoteReplacement(testFiles.getMain().getAbsolutePath()));
- for (File file : testFiles.getLibs()) {
- expectedErrors = expectedErrors.replaceAll(
- Pattern.quote(file.getName()),
- Matcher.quoteReplacement(file.getAbsolutePath()));
- }
-
- StringBuilder actualErrors = new StringBuilder();
- for (String s : log.getMessages()) {
- actualErrors.append(s);
- if (!s.endsWith("\n")) {
- actualErrors.append('\n');
- }
- }
- assertEquals("Error generated during merging",
- expectedErrors, actualErrors.toString().trim());
-
- if (testFiles.getShouldFail()) {
- assertFalse("Merge process() returned true, expected false", processOK);
- } else {
- assertTrue("Merge process() returned false, expected true", processOK);
- }
-
- // Test result XML. There should always be one created
- // since the process action does not stop on errors.
- log.clear();
- Document document = MergerXmlUtils.parseDocument(testFiles.getActualResult(), mergerLog,
- merger);
- assertNotNull(document);
- assert document != null; // for Eclipse null analysis
- String actual = MergerXmlUtils.printXmlString(document, mergerLog);
- assertEquals("Error parsing actual result XML", "", log.toString());
- log.clear();
- document = MergerXmlUtils.parseDocument(
- testFiles.getExpectedResult(),
- mergerLog,
- new FileAndLine("<expected-result>", 0));
- assertNotNull("Failed to parse result document: " + testFiles.getExpectedResult(),document);
- assert document != null;
- String expected = MergerXmlUtils.printXmlString(document, mergerLog);
- assertEquals("Error parsing expected result XML", "", log.toString());
- assertEquals("Error comparing expected to actual result", expected, actual);
-
- testFiles.cleanup();
- }
-
-}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergerTest.java b/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergerTest.java
deleted file mode 100644
index da88a37..0000000
--- a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergerTest.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import com.android.sdklib.mock.MockLog;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.CharBuffer;
-
-/**
- * Tests for {@link Merger} class
- */
-public class MergerTest extends TestCase {
-
- @Mock
- ManifestMerger2.Invoker mInvoker;
-
- @Mock
- MergingReport mMergingReport;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- }
-
- private class MergerWithMock extends Merger {
-
- @Override
- protected ManifestMerger2.Invoker createInvoker(File mainManifestFile, ILogger logger) {
- try {
- when(mMergingReport.getResult()).thenReturn(MergingReport.Result.ERROR);
- when(mMergingReport.getLoggingRecords()).thenReturn(
- ImmutableList.<MergingReport.Record>of());
- when(mInvoker.merge()).thenReturn(mMergingReport);
- } catch (ManifestMerger2.MergeFailureException e) {
- fail(e.getMessage());
- }
- return mInvoker;
- }
-
- @Override
- protected File checkPath(String path) throws FileNotFoundException {
- return new File(path); // always exists...
- }
- }
-
- public void testMainParameter() throws FileNotFoundException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml" };
- new MergerWithMock() {
- @Override
- protected ManifestMerger2.Invoker createInvoker(File mainManifestFile, ILogger logger) {
- assertEquals(args[1], mainManifestFile.getPath());
- return super.createInvoker(mainManifestFile, logger);
- }
- }.process(args);
- }
-
- public void testDefaultLoggerParameter() throws FileNotFoundException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml" };
- new MergerWithMock() {
- @Override
- protected ILogger createLogger(StdLogger.Level level) {
- assertEquals(StdLogger.Level.INFO, level);
- return super.createLogger(level);
- }
- }.process(args);
- }
-
- public void testLoggerParameter() throws FileNotFoundException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--log", "VERBOSE" };
- new MergerWithMock() {
- @Override
- protected ILogger createLogger(StdLogger.Level level) {
- assertEquals(StdLogger.Level.VERBOSE, level);
- return super.createLogger(level);
- }
- }.process(args);
- }
-
- public void testLibParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--libs", "src/lib/AndroidManifest.xml" };
- new MergerWithMock().process(args);
- verify(mInvoker).addLibraryManifest(new File("src/lib/AndroidManifest.xml"));
- verify(mInvoker).merge();
- verifyNoMoreInteractions(mInvoker);
- }
-
- public void testLibsParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--libs", "src/lib1/AndroidManifest.xml" + File.pathSeparator
- + "src/lib2/AndroidManifest.xml" + File.pathSeparator
- + "src/lib3/AndroidManifest.xml" };
- new MergerWithMock().process(args);
- verify(mInvoker).addLibraryManifest(new File("src/lib1/AndroidManifest.xml"));
- verify(mInvoker).addLibraryManifest(new File("src/lib2/AndroidManifest.xml"));
- verify(mInvoker).addLibraryManifest(new File("src/lib3/AndroidManifest.xml"));
- verify(mInvoker).merge();
- verifyNoMoreInteractions(mInvoker);
- }
-
- public void testOverlayParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--overlays", "src/flavor1/AndroidManifest.xml" };
- new MergerWithMock().process(args);
- verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor1/AndroidManifest.xml"));
- verify(mInvoker).merge();
- verifyNoMoreInteractions(mInvoker);
- }
-
- public void testOverlaysParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--overlays", "src/flavor1/AndroidManifest.xml" + File.pathSeparator
- + "src/flavor2/AndroidManifest.xml" + File.pathSeparator
- + "src/flavor3/AndroidManifest.xml" };
- new MergerWithMock().process(args);
- verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor1/AndroidManifest.xml"));
- verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor2/AndroidManifest.xml"));
- verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor3/AndroidManifest.xml"));
- verify(mInvoker).merge();
- verifyNoMoreInteractions(mInvoker);
- }
-
- public void testPropertyParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--property", "min_sdk_version=19" };
- new MergerWithMock().process(args);
- verify(mInvoker).setOverride(ManifestMerger2.SystemProperty.MIN_SDK_VERSION, "19");
- verify(mInvoker).merge();
- verifyNoMoreInteractions(mInvoker);
- }
-
- public void testInvalidPropertyParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--property", "Foo=19" };
- final MockLog iLogger = new MockLog();
- Merger merger = new MergerWithMock() {
- @Override
- protected ILogger createLogger(StdLogger.Level level) {
- return iLogger;
- }
- };
- // check that return value marked the failure.
- assertEquals(1, merger.process(args));
- assertEquals(2, iLogger.getMessages().size());
- assertTrue(iLogger.getMessages().get(1).startsWith(
- "E Invalid property name Foo, allowed properties are :"));
- }
-
- public void testInvalidFormatPropertyParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--property", "Foo:19" };
- final MockLog iLogger = new MockLog();
- Merger merger = new MergerWithMock() {
- @Override
- protected ILogger createLogger(StdLogger.Level level) {
- return iLogger;
- }
- };
- // check that return value marked the failure.
- assertEquals(1, merger.process(args));
- assertEquals(1, iLogger.getMessages().size());
- assertEquals("E Invalid property setting, should be NAME=VALUE format",
- iLogger.getMessages().get(0));
- }
-
- public void testPlaceholderParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--placeholder", "foo=bar" };
- new MergerWithMock().process(args);
- verify(mInvoker).setPlaceHolderValue("foo", "bar");
- verify(mInvoker).merge();
- verifyNoMoreInteractions(mInvoker);
- }
-
- public void testInvalidFormatPlaceholderParameter()
- throws FileNotFoundException, ManifestMerger2.MergeFailureException {
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--placeholder", "Foo:19" };
- final MockLog iLogger = new MockLog();
- Merger merger = new MergerWithMock() {
- @Override
- protected ILogger createLogger(StdLogger.Level level) {
- return iLogger;
- }
- };
- // check that return value marked the failure.
- assertEquals(1, merger.process(args));
- assertEquals(1, iLogger.getMessages().size());
- assertEquals("E Invalid placeholder setting, should be NAME=VALUE format",
- iLogger.getMessages().get(0));
- }
-
- public void testCombinedParameters()
- throws IOException, ManifestMerger2.MergeFailureException {
-
- File outFile = File.createTempFile("test", "merger");
-
- final String[] args = { "--main", "src/main/AndroidManifest.xml",
- "--libs", "src/lib1/AndroidManifest.xml" + File.pathSeparator
- + "src/lib2/AndroidManifest.xml" + File.pathSeparator
- + "src/lib3/AndroidManifest.xml",
- "--overlays", "src/flavor1/AndroidManifest.xml" + File.pathSeparator
- + "src/flavor2/AndroidManifest.xml" + File.pathSeparator
- + "src/flavor3/AndroidManifest.xml",
- "--placeholder", "Foo=bar",
- "--property", "max_sdk_version=21",
- "--out", outFile.getAbsolutePath()};
- Merger merger = new MergerWithMock() {
- @Override
- protected ManifestMerger2.Invoker createInvoker(File mainManifestFile, ILogger logger) {
- try {
- XmlDocument xmlDocument = Mockito.mock(XmlDocument.class);
- when(mMergingReport.getResult()).thenReturn(MergingReport.Result.SUCCESS);
- when(mMergingReport.getMergedDocument()).thenReturn(Optional.of(xmlDocument));
- when(xmlDocument.prettyPrint()).thenReturn("Pretty combined");
- when(mInvoker.merge()).thenReturn(mMergingReport);
- } catch (ManifestMerger2.MergeFailureException e) {
- fail(e.getMessage());
- }
- return mInvoker;
- }
- };
- merger.process(args);
- verify(mInvoker).addLibraryManifest(new File("src/lib1/AndroidManifest.xml"));
- verify(mInvoker).addLibraryManifest(new File("src/lib2/AndroidManifest.xml"));
- verify(mInvoker).addLibraryManifest(new File("src/lib3/AndroidManifest.xml"));
- verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor1/AndroidManifest.xml"));
- verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor2/AndroidManifest.xml"));
- verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor3/AndroidManifest.xml"));
- verify(mInvoker).setOverride(ManifestMerger2.SystemProperty.MAX_SDK_VERSION, "21");
- verify(mInvoker).setPlaceHolderValue("Foo", "bar");
- verify(mInvoker).merge();
- verifyNoMoreInteractions(mInvoker);
-
- // check the resulting file content.
- FileReader fileReader = null;
- try {
- fileReader = new FileReader(outFile);
- CharBuffer buffer = CharBuffer.allocate(256);
- fileReader.read(buffer);
- int endOfRead = buffer.position();
- assertEquals("Pretty combined", buffer.rewind().toString().substring(0, endOfRead));
- } finally {
- if (fileReader != null) {
- fileReader.close();
- }
- }
- assertTrue(outFile.delete());
- }
-}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java b/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java
deleted file mode 100644
index 2545f90..0000000
--- a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.MergingReport.Record.Severity;
-import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
-
-import com.android.ide.common.blame.SourceFile;
-import com.android.utils.ILogger;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.w3c.dom.Element;
-
-/**
- * Tests for the {@link com.android.manifmerger.MergingReport} class
- */
-public class MergingReportTest extends TestCase {
-
- @Mock ILogger mLoggerMock;
- @Mock Element mElement;
- SourceFile mSourceLocation = new SourceFile("location");
- @Mock KeyResolver<String> mKeyResolver;
- @Mock KeyBasedValueResolver<ManifestMerger2.SystemProperty> mPropertyResolver;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- }
-
- public void testJustError() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,0, 0, Severity.ERROR,"Something bad happened")
- .build();
-
- assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
- }
-
- public void testJustWarning() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,0, 0, Severity.WARNING, "Something weird happened")
- .build();
-
- assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
- }
-
- public void testJustInfo() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
- .build();
-
- assertEquals(MergingReport.Result.SUCCESS, mergingReport.getResult());
- }
-
-
- public void testJustInfoAndWarning() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
- .addMessage(mSourceLocation,0, 0, Severity.WARNING, "Something weird happened")
- .build();
-
- assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
- }
-
- public void testJustInfoAndError() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
- .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
- .build();
-
- assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
- }
-
- public void testJustWarningAndError() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,0, 0, Severity.WARNING, "something weird happened")
- .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
- .build();
-
- assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
- }
- public void testAllTypes() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
- .addMessage(mSourceLocation,0, 0, Severity.WARNING, "something weird happened")
- .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
- .build();
-
- assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
- }
-
- public void testLogging() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMessage(mSourceLocation,1, 1, Severity.INFO, "merging info")
- .addMessage(mSourceLocation,1, 1, Severity.WARNING, "something weird happened")
- .addMessage(mSourceLocation,1, 1, Severity.ERROR, "something bad happened")
- .build();
-
- mergingReport.log(mLoggerMock);
- Mockito.verify(mLoggerMock).verbose("location:1:1 Info:\n\tmerging info");
- Mockito.verify(mLoggerMock).warning("location:1:1 Warning:\n\tsomething weird happened");
- Mockito.verify(mLoggerMock).error(null /* throwable */,
- "location:1:1 Error:\n\tsomething bad happened");
- Mockito.verify(mLoggerMock).verbose(Actions.HEADER);
- Mockito.verify(mLoggerMock).warning("\nSee http://g.co/androidstudio/manifest-merger "
- + "for more information about the manifest merger.\n");
- Mockito.verifyNoMoreInteractions(mLoggerMock);
- }
-
- public void testItermediaryMerges() {
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .addMergingStage("<first/>")
- .addMergingStage("<second/>")
- .addMergingStage("<third/>")
- .build();
-
- ImmutableList<String> intermediaryStages = mergingReport.getIntermediaryStages();
- assertEquals(3, intermediaryStages.size());
- assertEquals("<first/>", intermediaryStages.get(0));
- assertEquals("<second/>", intermediaryStages.get(1));
- assertEquals("<third/>", intermediaryStages.get(2));
- }
-
- public void testGetMergedDocument() {
- XmlDocument xmlDocument =
- new XmlDocument(
- mSourceLocation,
- mKeyResolver,
- mPropertyResolver,
- mElement,
- XmlDocument.Type.MAIN,
- Optional.<String>absent() /* mainManifestPackageName */);
-
- MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
- .setMergedDocument(xmlDocument)
- .build();
-
- assertTrue(mergingReport.getMergedDocument().isPresent());
- assertEquals(xmlDocument, mergingReport.getMergedDocument().get());
- }
-}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java b/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java
deleted file mode 100644
index ab3869d..0000000
--- a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.sdklib.mock.MockLog;
-import com.google.common.base.Optional;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.xml.sax.SAXException;
-
-import java.io.IOException;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Tests for the {@link com.android.manifmerger.PlaceholderHandler}
- */
-public class PlaceholderHandlerTest extends TestCase {
-
- @Mock
- ActionRecorder mActionRecorder;
-
- @Mock
- MergingReport.Builder mBuilder;
-
- MockLog mMockLog = new MockLog();
-
- KeyBasedValueResolver<String> nullResolver = new KeyBasedValueResolver<String>() {
- @Override
- public String getValue(@NonNull String key) {
- // not provided a placeholder value should generate an error.
- return null;
- }
- };
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- when(mBuilder.getLogger()).thenReturn(mMockLog);
- when(mBuilder.getActionRecorder()).thenReturn(mActionRecorder);
- }
-
- public void testPlaceholders() throws ParserConfigurationException, SAXException, IOException {
-
- String xml = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"activityOne\"\n"
- + " android:attr1=\"${landscapePH}\"\n"
- + " android:attr2=\"prefix.${landscapePH}\"\n"
- + " android:attr3=\"${landscapePH}.suffix\"\n"
- + " android:attr4=\"prefix${landscapePH}suffix\">\n"
- + " </activity>\n"
- + "</manifest>";
-
- XmlDocument refDocument = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
-
- PlaceholderHandler handler = new PlaceholderHandler();
- handler.visit(
- ManifestMerger2.MergeType.APPLICATION,
- refDocument, new KeyBasedValueResolver<String>() {
- @Override
- public String getValue(@NonNull String key) {
- return "newValue";
- }
- }, mBuilder);
-
- Optional<XmlElement> activityOne = refDocument.getRootNode()
- .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, ".activityOne");
- assertTrue(activityOne.isPresent());
- assertEquals(5, activityOne.get().getAttributes().size());
- // check substitution.
- assertEquals("newValue",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr1")).get().getValue());
- assertEquals("prefix.newValue",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr2")).get().getValue());
- assertEquals("newValue.suffix",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr3")).get().getValue());
- assertEquals("prefixnewValuesuffix",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr4")).get().getValue());
-
- for (XmlAttribute xmlAttribute : activityOne.get().getAttributes()) {
- // any attribute other than android:name should have been injected.
- if (!xmlAttribute.getName().toString().contains("name")) {
- verify(mActionRecorder).recordAttributeAction(
- xmlAttribute,
- SourcePosition.UNKNOWN,
- Actions.ActionType.INJECTED,
- null);
- }
- }
- }
-
- public void testSeveralPlaceholders() throws ParserConfigurationException, SAXException, IOException {
-
- String xml = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"activityOne\"\n"
- + " android:attr1=\"prefix${first}${second}\"\n"
- + " android:attr2=\"${first}${second}suffix\"\n"
- + " android:attr3=\"prefix${first}.${second}suffix\"\n"
- + " android:attr4=\"${first}.${second}\"/>\n"
- + "</manifest>";
-
- XmlDocument refDocument = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
-
- PlaceholderHandler handler = new PlaceholderHandler();
- handler.visit(
- ManifestMerger2.MergeType.APPLICATION,
- refDocument, new KeyBasedValueResolver<String>() {
- @Override
- public String getValue(@NonNull String key) {
- if (key.equals("first")) {
- return "firstValue";
- } else {
- return "secondValue";
- }
- }
- }, mBuilder);
-
- Optional<XmlElement> activityOne = refDocument.getRootNode()
- .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, ".activityOne");
- assertTrue(activityOne.isPresent());
- assertEquals(5, activityOne.get().getAttributes().size());
- // check substitution.
-
- assertEquals("prefixfirstValuesecondValue",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr1")).get().getValue());
-
- assertEquals("firstValuesecondValuesuffix",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr2")).get().getValue());
-
- assertEquals("prefixfirstValue.secondValuesuffix",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr3")).get().getValue());
-
- assertEquals("firstValue.secondValue",
- activityOne.get().getAttribute(
- XmlNode.fromXmlName("android:attr4")).get().getValue());
-
- for (XmlAttribute xmlAttribute : activityOne.get().getAttributes()) {
- // any attribute other than android:name should have been injected.
- if (!xmlAttribute.getName().toString().contains("name")) {
- verify(mActionRecorder, times(2)).recordAttributeAction(
- xmlAttribute,
- SourcePosition.UNKNOWN,
- Actions.ActionType.INJECTED,
- null);
-
- }
- }
- }
-
- public void testPlaceHolder_notProvided()
- throws ParserConfigurationException, SAXException, IOException {
- String xml = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"activityOne\"\n"
- + " android:attr1=\"${landscapePH}\"/>\n"
- + "</manifest>";
-
- XmlDocument refDocument = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
-
- PlaceholderHandler handler = new PlaceholderHandler();
- handler.visit(ManifestMerger2.MergeType.APPLICATION, refDocument, nullResolver, mBuilder);
- // verify the error was recorded.
- verify(mBuilder).addMessage(
- any(SourceFilePosition.class),
- eq(MergingReport.Record.Severity.ERROR), anyString());
- }
-
- public void testPlaceHolder_notProvided_inLibrary()
- throws ParserConfigurationException, SAXException, IOException {
- String xml = ""
- + "<manifest\n"
- + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
- + " <activity android:name=\"activityOne\"\n"
- + " android:attr1=\"${landscapePH}\"/>\n"
- + "</manifest>";
-
- XmlDocument refDocument = TestUtils.xmlDocumentFromString(
- TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
-
- PlaceholderHandler handler = new PlaceholderHandler();
- handler.visit(ManifestMerger2.MergeType.LIBRARY, refDocument, nullResolver, mBuilder);
- // verify the error was recorded.
- verify(mBuilder).addMessage(
- any(SourceFilePosition.class),
- eq(MergingReport.Record.Severity.INFO), anyString());
- }
-}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/profile/ExecutionType.java b/base/build-system/profile/src/main/java/com/android/builder/profile/ExecutionType.java
deleted file mode 100644
index 23330e1..0000000
--- a/base/build-system/profile/src/main/java/com/android/builder/profile/ExecutionType.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.profile;
-
-/**
- * Defines a type of processing.
- *
- * Use range for similar categories events :
- * 0-1000 initial setup
- * 1000-2000 application related task creation
- * 2000-3000 library related task creation
- * 3000-4000 Tasks related events.
- */
-public enum ExecutionType {
-
- SOME_RANDOM_PROCESSING(1),
- BASE_PLUGIN_PROJECT_CONFIGURE(2),
- BASE_PLUGIN_PROJECT_BASE_EXTENSTION_CREATION(3),
- BASE_PLUGIN_PROJECT_TASKS_CREATION(4),
- BASE_PLUGIN_BUILD_FINISHED(5),
- TASK_MANAGER_CREATE_TASKS(6),
- BASE_PLUGIN_CREATE_ANDROID_TASKS(7),
- VARIANT_MANAGER_CREATE_ANDROID_TASKS(8),
- VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT(9),
- VARIANT_MANAGER_CREATE_LINT_TASKS(10),
- VARIANT_MANAGER_CREATE_TESTS_TASKS(11),
- VARIANT_MANAGER_CREATE_VARIANTS(12),
- RESOLVE_DEPENDENCIES(12),
- INITIAL_METADATA(100),
- FINAL_METADATA(101),
- GENERAL_CONFIG(102),
- VARIANT_CONFIG(103),
-
- // ApplicationTaskManager per variant tasks.
- APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK(1000),
- APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK(1001),
- APP_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK(1002),
- APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK(1003),
- APP_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK(1004),
- APP_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK(1005),
- APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK(1006),
- APP_TASK_MANAGER_CREATE_AIDL_TASK(1007),
- APP_TASK_MANAGER_CREATE_COMPILE_TASK(1008),
- APP_TASK_MANAGER_CREATE_NDK_TASK(1009),
- APP_TASK_MANAGER_CREATE_SPLIT_TASK(1010),
- APP_TASK_MANAGER_CREATE_PACKAGING_TASK(1011),
- @Deprecated APP_TASK_MANAGER_CREATE_PREPROCESS_RESOURCES_TASK(1012),
- @Deprecated APP_TASK_MANAGER_CREATE_BACKPORT_RESOURCES_TASK(1013),
- APP_TASK_MANAGER_CREATE_LINT_TASK(1014),
-
- // LibraryTaskManager per variant tasks.
- LIB_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK(2000),
- LIB_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK(2001),
- LIB_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK(2002),
- LIB_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK(2003),
- LIB_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK(2004),
- LIB_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK(2005),
- LIB_TASK_MANAGER_CREATE_PROCESS_RES_TASK(2006),
- LIB_TASK_MANAGER_CREATE_AIDL_TASK(2007),
- LIB_TASK_MANAGER_CREATE_COMPILE_TASK(2008),
- LIB_TASK_MANAGER_CREATE_NDK_TASK(2009),
- LIB_TASK_MANAGER_CREATE_SPLIT_TASK(2010),
- LIB_TASK_MANAGER_CREATE_PACKAGING_TASK(2011),
- LIB_TASK_MANAGER_CREATE_MERGE_PROGUARD_FILE_TASK(2012),
- LIB_TASK_MANAGER_CREATE_POST_COMPILATION_TASK(2013),
- LIB_TASK_MANAGER_CREATE_PROGUARD_TASK(2014),
- LIB_TASK_MANAGER_CREATE_PACKAGE_LOCAL_JAR(2015),
- @Deprecated LIB_TASK_MANAGER_CREATE_BACKPORT_RESOURCES_TASK(2016),
- LIB_TASK_MANAGER_CREATE_LINT_TASK(2017),
-
- // TASK_EXECUTION
- GENERIC_TASK_EXECUTION(3000),
- TASK_AIDL_COMPILE(3001),
- TASK_DELETE(3002),
- TASK_CHECK_MANIFEST(3003),
- TASK_PREPARE_DEPENDENCIES_TASK(3004),
- TASK_RENDERSCRIPT_COMPILE(3005),
- TASK_GENERATE_BUILD_CONFIG(3006),
- TASK_MERGE_ASSETS(3007),
- TASK_GENERATE_RES_VALUES(3008),
- TASK_MERGE_RESOURCES(3009),
- TASK_MERGE_MANIFESTS(3010),
- TASK_PROCESS_ANDROID_RESOURCES(3011),
- TASK_JAVA_COMPILE(3012),
- TASK_NDK_COMPILE(3013),
- TASK_PRE_DEX(3014),
- TASK_DEX(3015),
- TASK_PACKAGE_SPLIT_RES(3016),
- TASK_PROCESS_RESOURCES(3017),
- TASK_VALIDATE_SIGNING_TASK(3018),
- TASK_PACKAGE_APPLICATION(3019),
- TASK_SPLIT_ZIP_ALIGN(3020),
- TASK_ZIP_ALIGN(3021),
- TASK_COPY(3022),
- TASK_LINT(3023);
-
- int getId() {
- return id;
- }
-
- private final int id;
-
- ExecutionType(int id) {
- this.id = id;
- }
-}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/profile/ThreadRecorder.java b/base/build-system/profile/src/main/java/com/android/builder/profile/ThreadRecorder.java
deleted file mode 100644
index 60b8fee..0000000
--- a/base/build-system/profile/src/main/java/com/android/builder/profile/ThreadRecorder.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.profile;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.collect.ImmutableList;
-
-import java.util.ArrayDeque;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Facility to record block execution time on a single thread. Threads should not be spawned during
- * the block execution as its processing will not be recorded as of the parent's execution time.
- *
- * // TODO : provide facilities to create a new ThreadRecorder using a parent so the slave threads
- * can be connected to the parent's task.
- */
-public class ThreadRecorder implements Recorder {
-
- private static final Logger logger = Logger.getLogger(ThreadRecorder.class.getName());
-
- // Dummy implementation that records nothing but comply to the overall recording contracts.
- private static final Recorder dummyRecorder = new Recorder() {
- @Nullable
- @Override
- public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
- Property... properties) {
- return record(executionType, block, Collections.<Property>emptyList());
- }
-
- @Nullable
- @Override
- public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
- @NonNull List<Property> properties) {
- try {
- return block.call();
- } catch (Exception e) {
- block.handleException(e);
- }
- return null;
- }
-
- @Override
- public long allocationRecordId() {
- return 0;
- }
-
- @Override
- public void closeRecord(ExecutionRecord record) {
- }
- };
-
- private static final Recorder recorder = new ThreadRecorder();
-
-
- public static Recorder get() {
- return ProcessRecorderFactory.getFactory().isInitialized() ? recorder : dummyRecorder;
- }
-
- private static class PartialRecord {
- final ExecutionType executionType;
- final long recordId;
- final long parentRecordId;
- final long startTimeInMs;
-
- final List<Recorder.Property> extraArgs;
-
- PartialRecord(ExecutionType executionType,
- long recordId,
- long parentId,
- long startTimeInMs,
- List<Recorder.Property> extraArgs) {
- this.executionType = executionType;
- this.recordId = recordId;
- this.parentRecordId = parentId;
- this.startTimeInMs = startTimeInMs;
- this.extraArgs = extraArgs;
- }
- }
-
- /**
- * Do not put anything else than JDK classes in the ThreadLocal as it prevents that class
- * and therefore the plugin classloader to be gc'ed leading to OOM or PermGen issues.
- */
- private static final ThreadLocal<Deque<Long>> recordStacks =
- new ThreadLocal<Deque<Long>>() {
- @Override
- protected Deque<Long> initialValue() {
- return new ArrayDeque<Long>();
- }
- };
-
-
- @Override
- public long allocationRecordId() {
- long recordId = ProcessRecorder.allocateRecordId();
- recordStacks.get().push(recordId);
- return recordId;
- }
-
- @Override
- public void closeRecord(ExecutionRecord executionRecord) {
- if (recordStacks.get().pop() != executionRecord.id) {
- logger.severe("Internal Error : mixed records in profiling stack");
- }
- ProcessRecorder.get().writeRecord(executionRecord);
- }
-
- @Nullable
- @Override
- public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
- Property... properties) {
-
- List<Recorder.Property> propertyList = properties == null
- ? ImmutableList.<Recorder.Property>of()
- : ImmutableList.copyOf(properties);
-
- return record(executionType, block, propertyList);
- }
-
- @Nullable
- @Override
- public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
- @NonNull List<Property> properties) {
-
- long thisRecordId = ProcessRecorder.allocateRecordId();
-
- // am I a child ?
- Long parentId = recordStacks.get().peek();
-
- long startTimeInMs = System.currentTimeMillis();
-
- final PartialRecord currentRecord = new PartialRecord(executionType,
- thisRecordId, parentId == null ? 0 : parentId,
- startTimeInMs, properties);
-
- recordStacks.get().push(thisRecordId);
- try {
- return block.call();
- } catch (Exception e) {
- block.handleException(e);
- } finally {
- // pop this record from the stack.
- if (recordStacks.get().pop() != currentRecord.recordId) {
- logger.log(Level.SEVERE, "Profiler stack corrupted");
- }
- ProcessRecorder.get().writeRecord(
- new ExecutionRecord(currentRecord.recordId,
- currentRecord.parentRecordId,
- currentRecord.startTimeInMs,
- System.currentTimeMillis() - currentRecord.startTimeInMs,
- currentRecord.executionType,
- currentRecord.extraArgs));
- }
- // we always return null when an exception occurred and was not rethrown.
- return null;
- }
-}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/tasks/Job.java b/base/build-system/profile/src/main/java/com/android/builder/tasks/Job.java
deleted file mode 100644
index 61c8897..0000000
--- a/base/build-system/profile/src/main/java/com/android/builder/tasks/Job.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.tasks;
-
-import com.android.annotations.NonNull;
-import com.google.common.base.Objects;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Definition of a queued job. A job has a title, a task to execute, a latch to signal its
- * completion and a boolean result for success or failure.
- */
-public class Job<T> {
-
- private final String mJobTitle;
- private final Task<T> mTask;
- private final BooleanLatch mBooleanLatch;
- private final AtomicBoolean mResult = new AtomicBoolean(false);
-
- public Job(String jobTile, Task<T> task) {
- mJobTitle = jobTile;
- mTask = task;
- mBooleanLatch = new BooleanLatch();
- }
-
- public String getJobTitle() {
- return mJobTitle;
- }
-
- public void runTask(@NonNull JobContext<T> jobContext) throws IOException {
- mTask.run(this, jobContext);
- }
-
- public void finished() {
- mResult.set(true);
- mBooleanLatch.signal();
- }
-
- public void error() {
- mResult.set(false);
- mBooleanLatch.signal();
- }
-
- public boolean await() throws InterruptedException {
-
- mBooleanLatch.await();
- return mResult.get();
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this)
- .add("title", mJobTitle)
- .add("task", mTask)
- .add("latch", mBooleanLatch)
- .add("result", mResult.get())
- .toString();
- }
-}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/tasks/WorkQueue.java b/base/build-system/profile/src/main/java/com/android/builder/tasks/WorkQueue.java
deleted file mode 100644
index 2a09c13..0000000
--- a/base/build-system/profile/src/main/java/com/android/builder/tasks/WorkQueue.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.builder.tasks;
-
-import com.android.annotations.NonNull;
-import com.android.utils.ILogger;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * A work queue that accepts jobs and treat them in order.
- *
- * @author jedo at google.com (Jerome Dochez)
- */
-public class WorkQueue<T> implements Runnable {
-
- private static final boolean VERBOSE = System.getenv("GRADLE_WORK_QUEUE_VERBOSE") != null;
-
- private final ILogger mLogger;
-
- // queue name as human would understand.
- private final String mName;
-
- // the user throttling has already happened before so I am using a potentially
- // infinite linked list of request.
- private final LinkedBlockingQueue<QueueTask<T>> mPendingJobs =
- new LinkedBlockingQueue<QueueTask<T>>();
-
- // List of working threads pumping from this queue.
- private final List<Thread> mWorkThreads = new ArrayList<Thread>();
-
- private final float mGrowthTriggerRation;
- private final int mMWorkforceIncrement;
- private final AtomicInteger mThreadId = new AtomicInteger(0);
- private final QueueThreadContext<T> mQueueThreadContext;
-
- // we could base this on the number of processors this machine has, etc...
- private static final int MAX_WORKFORCE_SIZE = 20;
-
-
- /**
- * Private queue structure to store queue items.
- */
- private static class QueueTask<T> {
-
- enum ActionType { Death, Normal }
- final ActionType actionType;
- final Job<T> job;
-
- private QueueTask(ActionType actionType, Job<T> job) {
- this.actionType = actionType;
- this.job = job;
- }
- }
-
- /**
- * Creates a non expanding queue, with a number of dedicated threads to process
- * the queue's jobs.
- *
- * @param logger to log messages
- * @param queueName a meaningful descriptive name.
- * @param workforce the number of dedicated threads for this queue.
- */
- public WorkQueue(
- @NonNull ILogger logger,
- @NonNull QueueThreadContext<T> queueThreadContext,
- @NonNull String queueName,
- int workforce) {
- this(logger, queueThreadContext, queueName, workforce, Float.MAX_VALUE);
- }
-
- /**
- * Creates a new queue, with a number of dedicated threads to process
- * the queue's jobs.
- *
- * @param logger to log messages
- * @param queueName a meaningful descriptive name.
- * @param workforce the number of dedicated threads for this queue.
- * @param growthTriggerRatio the ratio between outstanding requests and worker threads that
- * should trigger a growth in worker threads.
- */
- public WorkQueue(
- @NonNull ILogger logger,
- @NonNull QueueThreadContext<T> queueThreadContext,
- @NonNull String queueName,
- int workforce,
- float growthTriggerRatio) {
-
- this.mLogger = logger;
- this.mName = queueName;
- this.mGrowthTriggerRation = growthTriggerRatio;
- this.mMWorkforceIncrement = workforce;
- this.mQueueThreadContext = queueThreadContext;
- }
-
- public void push(Job<T> job) throws InterruptedException {
- _push(new QueueTask<T>(QueueTask.ActionType.Normal, job));
- checkWorkforce();
- }
-
- private void _push(QueueTask<T> task) throws InterruptedException {
- // at this point, I am not trying to limit the number of pending jobs.
- // eventually we would want to put some limit to the size of the pending jobs
- // queue so it does not grow out of control.
- mPendingJobs.put(task);
- }
-
- private synchronized void checkWorkforce() {
- if (mWorkThreads.isEmpty()
- || (mPendingJobs.size() / mWorkThreads.size() > mGrowthTriggerRation)) {
- verbose("Request to incrementing workforce from %1$d", mWorkThreads.size());
- if (mWorkThreads.size() >= MAX_WORKFORCE_SIZE) {
- verbose("Already at max workforce %1$d, denied.", MAX_WORKFORCE_SIZE);
- return;
- }
- for (int i = 0; i < mMWorkforceIncrement; i++) {
- Thread t = new Thread(this, mName + "_" + mThreadId.incrementAndGet());
- t.setDaemon(true);
- mWorkThreads.add(t);
- t.start();
- }
- verbose("thread-pool size=%1$d", mWorkThreads.size());
- }
- }
-
- private synchronized void reduceWorkforce() throws InterruptedException {
- verbose("Decrementing workforce from " + mWorkThreads.size());
- // push a the right number of kiss of death tasks to shutdown threads.
- for (int i = 0; i < mMWorkforceIncrement; i++) {
- _push(new QueueTask<T>(QueueTask.ActionType.Death, null));
- }
- }
-
- /**
- * Shutdowns the working queue and wait until all pending requests have
- * been processed. This needs to be reviewed as jobs can still be added
- * to the queue once the shutdown process has started....
- * @throws InterruptedException if the shutdown sequence is interrupted
- */
- public synchronized void shutdown() throws InterruptedException {
-
- // push as many death pills as necessary
- for (Thread t : mWorkThreads) {
- _push(new QueueTask<T>(QueueTask.ActionType.Death, null));
- }
- // we could use a latch.
- for (Thread t : mWorkThreads) {
- t.join();
- }
- mWorkThreads.clear();
- mQueueThreadContext.shutdown();
- }
-
- /**
- * Return a human readable queue name, mainly used for identification
- * purposes.
- *
- * @return a unique meaningful descriptive name
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns the number of jobs waiting to be scheduled.
- *
- * @return the size of the queue.
- */
- public int size() {
- return mPendingJobs.size();
- }
-
-
- /**
- * each thread in the mWorkThreads will run this single infinite processing loop until a
- * death action is received.
- */
- @Override
- public void run() {
- final String threadName = Thread.currentThread().getName();
- // this
- try {
- try {
- verbose("Creating a new working thread %1$s", threadName);
- mQueueThreadContext.creation(Thread.currentThread());
- } catch (IOException e) {
- e.printStackTrace();
- }
- while(true) {
- final QueueTask<T> queueTask = mPendingJobs.take();
- if (queueTask.actionType== QueueTask.ActionType.Death) {
- verbose("Thread(%1$s): Death requested", threadName);
- // we are done.
- return;
- }
- final Job<T> job = queueTask.job;
- if (job == null) {
- // this clearly should not happen.
- Logger.getAnonymousLogger().severe(
- "I got a null pending job out of the priority queue");
- return;
- }
- verbose("Thread(%1$s): scheduling %2$s", threadName, job.getJobTitle());
-
- try {
- mQueueThreadContext.runTask(job);
- } catch (Exception e) {
- Logger.getAnonymousLogger().log(Level.WARNING, "Exception while processing task ", e);
- job.error();
- return;
- }
- // wait for the job completion.
- job.await();
- verbose("Thread(%1$s): job %2$s finished", threadName, job.getJobTitle());
- // we could potentially reduce the workforce at this point if we have little
- // queuing comparatively to the number of worker threads but at this point, the
- // overall process (gradle activity) is fairly short lived so skipping at this
- // point.
- verbose("Thread(%1$s): queue size %2$d", threadName, mPendingJobs.size());
- }
- } catch (InterruptedException e) {
- mLogger.error(e, "Thread(%1$s): Interrupted", threadName);
- } finally {
- try {
- verbose("Thread(%1$s): destruction", threadName);
- mQueueThreadContext.destruction(Thread.currentThread());
- } catch (IOException e) {
- mLogger.error(e, "Thread(%1$s): %2$s", threadName, e.getMessage());
- } catch (InterruptedException e) {
- mLogger.error(e, "Thread(%1$s): %2$s", threadName, e.getMessage());
- }
- }
- }
-
- private void verbose(String format, Object...args) {
- if (VERBOSE) {
- mLogger.verbose(format, args);
- }
- }
-}
diff --git a/base/build-system/project-test-lib/src/main/java/com/android/build/tests/AndroidProjectConnector.java b/base/build-system/project-test-lib/src/main/java/com/android/build/tests/AndroidProjectConnector.java
deleted file mode 100644
index 5277745..0000000
--- a/base/build-system/project-test-lib/src/main/java/com/android/build/tests/AndroidProjectConnector.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.build.tests;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.io.StreamException;
-import com.google.common.collect.Lists;
-
-import org.gradle.tooling.BuildLauncher;
-import org.gradle.tooling.GradleConnector;
-import org.gradle.tooling.ProjectConnection;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- */
-public class AndroidProjectConnector {
-
- @NonNull
- private final File mSdkDir;
-
- @Nullable
- private final File mNdkDir;
-
- public AndroidProjectConnector(@NonNull File sdkDir, @Nullable File ndkDir) {
- mSdkDir = sdkDir;
- mNdkDir = ndkDir;
- }
-
- public void runGradleTasks(
- @NonNull File project,
- @NonNull String gradleVersion,
- @NonNull List<String> arguments,
- @NonNull Map<String, String> jvmDefines,
- @NonNull String... tasks) throws IOException, StreamException {
- File localProp = createLocalProp(project);
-
- try {
- GradleConnector connector = GradleConnector.newConnector();
-
- ProjectConnection connection = connector
- .useGradleVersion(gradleVersion)
- .forProjectDirectory(project)
- .connect();
- try {
- List<String> args = Lists.newArrayListWithCapacity(2 + arguments.size());
- args.add("-i");
- args.add("-u");
- args.addAll(arguments);
-
- BuildLauncher build = connection.newBuild().forTasks(tasks).withArguments(
- args.toArray(new String[args.size()]));
-
- if (!jvmDefines.isEmpty()) {
- String[] jvmArgs = new String[jvmDefines.size()];
- int index = 0;
- for (Map.Entry<String, String> entry : jvmDefines.entrySet()) {
- jvmArgs[index++] = "-D" + entry.getKey() + "=" + entry.getValue();
- }
-
- build.setJvmArguments(jvmArgs);
- }
-
- build.run();
- } finally {
- connection.close();
- }
- } finally {
- localProp.delete();
- }
- }
-
- public AndroidProject getModel(@NonNull File project) {
- // Configure the connector and create the connection
- GradleConnector connector = GradleConnector.newConnector();
-
- connector.forProjectDirectory(project);
-
- ProjectConnection connection = connector.connect();
- try {
- // Load the custom model for the project
- return connection.getModel(AndroidProject.class);
- } finally {
- connection.close();
- }
- }
-
- private File createLocalProp(@NonNull File project) throws IOException, StreamException {
- Properties p = new Properties();
- p.put("sdk.dir", mSdkDir.getAbsolutePath());
- if (mNdkDir != null) {
- p.put("ndk.dir", mNdkDir.getAbsolutePath());
- }
-
- File file = new File(project, "local.properties");
- p.store(new FileOutputStream(file), "automatically generated local.prop. Should be removed after test.");
-
- return file;
- }
-}
diff --git a/base/build.gradle b/base/build.gradle
deleted file mode 100644
index b07f6c9..0000000
--- a/base/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-subprojects { Project project ->
- // only configure leaf projects.
- if (!project.getSubprojects().isEmpty()) return
-
- apply from: "$rootDir/buildSrc/base/baseJava.gradle"
-}
-
diff --git a/base/chartlib/src/main/java/com/android/tools/chartlib/TimelineComponent.java b/base/chartlib/src/main/java/com/android/tools/chartlib/TimelineComponent.java
deleted file mode 100644
index d90b1b8..0000000
--- a/base/chartlib/src/main/java/com/android/tools/chartlib/TimelineComponent.java
+++ /dev/null
@@ -1,693 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.chartlib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.FontMetrics;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.Stroke;
-import java.awt.event.ActionListener;
-import java.awt.event.HierarchyListener;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Arc2D;
-import java.awt.geom.Path2D;
-import java.awt.geom.Point2D;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.swing.Icon;
-
-import gnu.trove.TIntObjectHashMap;
-
-/**
- * A component to display a TimelineData object. It locks the timeline object to prevent
- * modifications to it while it's begin rendered, but objects of this class should not be accessed
- * from different threads.
- */
-public final class TimelineComponent extends AnimatedComponent
- implements ActionListener, HierarchyListener {
-
- private static final Color TEXT_COLOR = new Color(128, 128, 128);
-
- private static final int LEFT_MARGIN = 120;
-
- private static final int RIGHT_MARGIN = 200;
-
- private static final int TOP_MARGIN = 10;
-
- private static final int BOTTOM_MARGIN = 30;
-
- private static final int FPS = 40;
-
- /**
- * The number of pixels a second in the timeline takes on the screen.
- */
- private static final float X_SCALE = 20;
-
- private final float mBufferTime;
-
- @NonNull
- private final TimelineData mData;
-
- @NonNull
- private final EventData mEvents;
-
- private final float mInitialMax;
-
- private final float mAbsoluteMax;
-
- private final float mInitialMarkerSeparation;
-
- private String[] mStreamNames;
-
- private Color[] mStreamColors;
-
- private Map<Integer, Style> mStyles;
-
- private boolean mFirstFrame;
-
- /**
- * The current maximum range in y-axis units.
- */
- private float mCurrentMax;
-
- /**
- * Marker separation in y-axis units.
- */
- private float mMarkerSeparation;
-
- /**
- * The current alpha of markers at even positions. When there are not enough/too many markers,
- * the markers at even positions are faded in/out respectively. This tracks the animated alpha
- * of such markers.
- */
- private float mEvenMarkersAlpha;
-
- /**
- * The current value in pixels where the x-axis is drawn.
- */
- private int mBottom;
-
- /**
- * The current value in pixels where the right hand side y-axis is drawn.
- */
- private int mRight;
-
- /**
- * The current scale from y-axis values to pixels.
- */
- private float mYScale;
-
- /**
- * The current time value at the right edge of the timeline in seconds.
- */
- private float mEndTime;
-
- /**
- * The current time value at the left edge of the timeline in seconds.
- */
- private float mBeginTime;
-
- /**
- * How to render each event type.
- */
- private TIntObjectHashMap<EventInfo> mEventsInfo;
-
- /**
- * The units of the y-axis values.
- */
- private String mUnits;
-
- /**
- * The number of available local samples.
- */
- private int mSize;
-
- /**
- * The times at which the samples occurred.
- */
- private float[] mTimes;
-
- /**
- * The times at which the samples occurred.
- */
- private int[] mTypes;
-
- /**
- * The render values of the samples depending on the layout mode, as in mValues[stream][sample]
- */
- private final float[][] mValues;
-
- /**
- * The last values of the samples for each stream
- */
- private final float[] mCurrent;
-
- /**
- * The number of events to render.
- */
- private int mEventsSize;
-
- /**
- * The start time of each event.
- */
- private float[] mEventStart;
-
- /**
- * The end time of each event, if NaN then the event did not end.
- */
- private float[] mEventEnd;
-
- /**
- * The type of each event.
- */
- private int[] mEventTypes;
-
- /**
- * The animated angle of an event in progress.
- */
- private float mEventProgressStart;
-
- /**
- * The direction of the event animation.
- */
- private float mEventProgressDir = 1.0f;
-
- /**
- * The current state for all in-progress events.
- */
- private float mEventProgress;
-
- /**
- * Creates a timeline component that renders the given timeline data. It will animate the
- * timeline data by showing the value at the current time on the right y-axis of the graph.
- *
- * @param data the data to be displayed.
- * @param bufferTime the time, in seconds, to lag behind the given {@code data}.
- * @param initialMax the initial maximum value for the y-axis.
- * @param absoluteMax the absolute maximum value for the y-axis.
- * @param initialMarkerSeparation the initial separations for the markers on the y-axis.
- */
- public TimelineComponent(
- @NonNull TimelineData data,
- @NonNull EventData events,
- float bufferTime,
- float initialMax,
- float absoluteMax,
- float initialMarkerSeparation) {
- super(FPS);
- mData = data;
- mEvents = events;
- mBufferTime = bufferTime;
- mInitialMax = initialMax;
- mAbsoluteMax = absoluteMax;
- mInitialMarkerSeparation = initialMarkerSeparation;
- int streams = mData.getStreamCount();
- addHierarchyListener(this);
- mStreamNames = new String[streams];
- mStreamColors = new Color[streams];
- mValues = new float[streams][];
- mCurrent = new float[streams];
- mSize = 0;
- for (int i = 0; i < streams; i++) {
- mStreamNames[i] = "Stream " + i;
- mStreamColors[i] = Color.BLACK;
- }
- mStyles = new HashMap<Integer, Style>();
- mUnits = "";
- mEventsInfo = new TIntObjectHashMap<EventInfo>();
- setOpaque(true);
- reset();
- }
-
- public void configureStream(int stream, String name, Color color) {
- mStreamNames[stream] = name;
- mStreamColors[stream] = color;
- }
-
- public void configureEvent(int type, int stream, Icon icon, Color color,
- Color progress, boolean range) {
- mEventsInfo.put(type, new EventInfo(type, stream, icon, color, progress, range));
- }
-
- public void configureType(int type, Style style) {
- mStyles.put(type, style);
- }
-
- public void configureUnits(String units) {
- mUnits = units;
- }
-
- public void reset() {
- mCurrentMax = mInitialMax;
- mMarkerSeparation = mInitialMarkerSeparation;
- mEvenMarkersAlpha = 1.0f;
- mFirstFrame = true;
- }
-
- @Override
- protected void draw(Graphics2D g2d) {
-
- Dimension dim = getSize();
-
- mBottom = dim.height - BOTTOM_MARGIN;
- mRight = dim.width - RIGHT_MARGIN;
-
- g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g2d.setFont(DEFAULT_FONT);
- g2d.setClip(0, 0, dim.width, dim.height);
- g2d.setColor(getBackground());
- g2d.fillRect(0, 0, dim.width, dim.height);
- g2d.setClip(LEFT_MARGIN, TOP_MARGIN, mRight - LEFT_MARGIN, mBottom - TOP_MARGIN);
- drawTimelineData(g2d);
- drawEvents(g2d);
- g2d.setClip(0, 0, dim.width, dim.height);
- drawLabels(g2d);
- drawTimeMarkers(g2d);
- drawMarkers(g2d);
- drawGuides(g2d);
-
- mFirstFrame = false;
- }
-
- @Override
- protected void debugDraw(Graphics2D g2d) {
- int drawn = 0;
- g2d.setFont(DEFAULT_FONT.deriveFont(5.0f));
- for (int i = 0; i < mSize; ++i) {
- if (mTimes[i] > mBeginTime && mTimes[i] < mEndTime) {
- for (int j = 0; j < mValues.length; ++j) {
- int x = (int) timeToX(mTimes[i]);
- int y = (int) valueToY(mValues[j][i]);
- g2d.setColor(new Color((17 * mTypes[i]) % 255, (121 * mTypes[i]) % 255,
- (71 * mTypes[i]) % 255));
- g2d.drawLine(x, y - 2, x, y + 2);
- g2d.drawLine(x - 2, y, x + 2, y);
- g2d.setColor(TEXT_COLOR);
- }
- drawn++;
- }
- }
-
- addDebugInfo("Drawn samples: %d", drawn);
- }
-
- private void drawTimelineData(Graphics2D g2d) {
- mYScale = (mBottom - TOP_MARGIN) / mCurrentMax;
- if (mSize > 1) {
- int from = 0;
- // Optimize to not render too many samples since they get clipped.
- while (from < mSize - 1 && mTimes[from + 1] < mBeginTime) {
- from++;
- }
- int to = from;
- while (to + 1 < mSize && mTimes[to] <= mEndTime) {
- to++;
- }
- if (from == to) {
- return;
- }
- int drawnSegments = 0;
- for (int j = mValues.length - 1; j >= 0; j--) {
- Path2D.Float path = new Path2D.Float();
- path.moveTo(timeToX(mTimes[from]), valueToY(0.0f));
- for (int i = from; i <= to; i++) {
- float val = mValues[j][i];
- path.lineTo(timeToX(mTimes[i]), valueToY(Math.min(val, mAbsoluteMax)));
- }
- path.lineTo(timeToX(mTimes[to]), valueToY(0.0f));
- g2d.setColor(mStreamColors[j]);
- g2d.fill(path);
-
- if (!mStyles.isEmpty()) {
- path = new Path2D.Float();
- Stroke current = g2d.getStroke();
- float step = 3.0f;
- float x0 = timeToX(mTimes[from]);
- float y0 = valueToY(mValues[j][from]);
- g2d.setColor(mStreamColors[j].darker());
- Stroke stroke = null;
- float strokeScale = Float.NaN;
- for (int i = from + 1; i <= to; i++) {
- float x1 = timeToX(mTimes[i]);
- float y1 = valueToY(mValues[j][i]);
- Style style = mStyles.get(mTypes[i]);
- if (style != null && style != Style.NONE) {
- BasicStroke str = new BasicStroke(1.0f);
- float scale = 0;
- if (style == Style.DASHED) {
- float distance = (float) Point2D.distance(x0, y0, x1, y1);
- float delta = mTimes[i] * X_SCALE;
- scale = distance / (x1 - x0);
- str = new BasicStroke(1.0f, BasicStroke.CAP_ROUND,
- BasicStroke.JOIN_ROUND, 0.0f, new float[]{step * scale},
- (delta * scale) % (step * scale * 2));
- }
- if (scale != strokeScale) {
- if (stroke != null) {
- g2d.setStroke(stroke);
- g2d.draw(path);
- path.reset();
- drawnSegments++;
- }
- strokeScale = scale;
- stroke = str;
- path.moveTo(x0, y0);
- }
- path.lineTo(x1, y1);
- }
- x0 = x1;
- y0 = y1;
- }
- if (stroke != null) {
- g2d.setStroke(stroke);
- g2d.draw(path);
- drawnSegments++;
- }
- g2d.setStroke(current);
- }
- }
- addDebugInfo("Drawn segments: %d", drawnSegments);
- }
- addDebugInfo("Total samples: %d", mSize);
- }
-
- private float interpolate(int stream, int sample, float time) {
- int prev = sample > 0 ? sample - 1 : 0;
- int next = sample < mSize ? sample : mSize - 1;
- float a = mValues[stream][prev];
- float b = mValues[stream][next];
- float delta = mTimes[next] - mTimes[prev];
- float ratio = delta != 0 ? (time - mTimes[prev]) / delta : 1.0f;
- return (b - a) * ratio + a;
- }
-
- private void drawEvents(Graphics2D g2d) {
-
- if (mSize > 0) {
- int drawnEvents = 0;
- AffineTransform tx = g2d.getTransform();
- Stroke stroke = g2d.getStroke();
- int s = 0;
- int e = 0;
- while (e < mEventsSize) {
- if (s < mSize && mTimes[s] < mEventStart[e]) {
- s++;
- } else if (Float.isNaN(mEventEnd[e])
- || mEventEnd[e] > mBeginTime && mEventEnd[e] > mTimes[0]) {
- drawnEvents++;
- EventInfo info = mEventsInfo.get(mEventTypes[e]);
- float x = timeToX(mEventStart[e]);
- float y = valueToY(interpolate(info.stream, s, mEventStart[e]));
- AffineTransform dt = new AffineTransform(tx);
- dt.translate(x, y);
- g2d.setTransform(dt);
- info.icon.paintIcon(this, g2d, -info.icon.getIconWidth() / 2,
- -info.icon.getIconHeight() - 5);
- g2d.setTransform(tx);
-
- g2d.setStroke(
- new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
- Path2D.Float p = new Path2D.Float();
- boolean closed = !Float.isNaN(mEventEnd[e]);
- if (info.range) {
- p.moveTo(x, mBottom);
- p.lineTo(x, y);
- float endTime = Float.isNaN(mEventEnd[e]) ? mEndTime : mEventEnd[e];
- int i = s;
- for (; i < mSize && mTimes[i] < endTime; i++) {
- float val = mValues[info.stream][i];
- p.lineTo(timeToX(mTimes[i]), valueToY(val));
- }
- p.lineTo(timeToX(endTime), valueToY(interpolate(info.stream, i, endTime)));
- p.lineTo(timeToX(closed ? mEventEnd[e] : endTime), valueToY(0));
- if (info.color != null) {
- g2d.setColor(info.color);
- g2d.fill(p);
- }
- g2d.setColor(info.progress);
- g2d.draw(p);
- } else {
- p.moveTo(x, y - 2.0f);
- p.lineTo(x, y + 2.0f);
- g2d.setColor(info.progress);
- g2d.draw(p);
- }
- if (!closed) {
- g2d.setColor(info.progress);
- // Draw in progress marker
- float end = 360 * mEventProgress;
- float start = mEventProgressStart;
- if (mEventProgressDir < 0.0f) {
- start += end;
- end = 360 - end;
- }
- g2d.draw(new Arc2D.Float(
- x + info.icon.getIconWidth() / 2 + 3,
- y - info.icon.getIconHeight() - 3,
- 6, 6,
- start, end, Arc2D.OPEN));
-
- }
- e++;
- } else {
- e++;
- }
- }
- g2d.setStroke(stroke);
- addDebugInfo("Drawn events: %d", drawnEvents);
- }
- }
-
- private float valueToY(float val) {
- return mBottom - val * mYScale;
- }
-
- private float timeToX(float time) {
- return LEFT_MARGIN + (time - mBeginTime) * X_SCALE;
- }
-
- private void drawLabels(Graphics2D g2d) {
- g2d.setFont(DEFAULT_FONT);
- FontMetrics metrics = g2d.getFontMetrics();
- for (int i = 0; i < mStreamNames.length && mSize > 0; i++) {
- g2d.setColor(mStreamColors[i]);
- int y = TOP_MARGIN + 15 + (mStreamNames.length - i - 1) * 20;
- g2d.fillRect(mRight + 20, y, 15, 15);
- g2d.setColor(TEXT_COLOR);
- g2d.drawString(
- String.format("%s [%.2f %s]", mStreamNames[i], mCurrent[i], mUnits),
- mRight + 40,
- y + 7 + metrics.getAscent() * .5f);
- }
- }
-
- private void drawTimeMarkers(Graphics2D g2d) {
- g2d.setFont(DEFAULT_FONT);
- g2d.setColor(TEXT_COLOR);
- FontMetrics metrics = g2d.getFontMetrics();
- float offset = metrics.stringWidth("000") * 0.5f;
- Path2D.Float lines = new Path2D.Float();
- for (int sec = Math.max((int) Math.ceil(mBeginTime), 0); sec < mEndTime; sec++) {
- float x = timeToX(sec);
- boolean big = sec % 5 == 0;
- if (big) {
- String text = formatTime(sec);
- g2d.drawString(text, x - metrics.stringWidth(text) + offset,
- mBottom + metrics.getAscent() + 5);
- }
- lines.moveTo(x, mBottom);
- lines.lineTo(x, mBottom + (big ? 5 : 2));
- }
- g2d.draw(lines);
- }
-
- @VisibleForTesting
- static String formatTime(int seconds) {
- int[] factors = {60, seconds};
- String[] suffix = {"m", "h"};
- String ret = seconds % 60 + "s";
- int t = seconds / 60;
- for (int i = 0; i < suffix.length && t > 0; i++) {
- ret = t % factors[i] + suffix[i] + " " + ret;
- t /= factors[i];
- }
- return ret;
- }
-
- private void drawMarkers(Graphics2D g2d) {
- if (mYScale <= 0) {
- return;
- }
-
- int markers = (int) (mCurrentMax / mMarkerSeparation);
- float markerPosition = LEFT_MARGIN - 10;
- for (int i = 0; i < markers + 1; i++) {
- float markerValue = (i + 1) * mMarkerSeparation;
- int y = (int) valueToY(markerValue);
- // Too close to the top
- if (mCurrentMax - markerValue < mMarkerSeparation * 0.5f) {
- markerValue = mCurrentMax;
- //noinspection AssignmentToForLoopParameter
- i = markers;
- y = TOP_MARGIN;
- }
- if (i < markers && i % 2 == 0 && mEvenMarkersAlpha < 1.0f) {
- g2d.setColor(
- new Color(TEXT_COLOR.getColorSpace(), TEXT_COLOR.getColorComponents(null),
- mEvenMarkersAlpha));
- } else {
- g2d.setColor(TEXT_COLOR);
- }
- g2d.drawLine(LEFT_MARGIN - 2, y, LEFT_MARGIN, y);
-
- FontMetrics metrics = getFontMetrics(DEFAULT_FONT);
- String marker = String.format("%.2f %s", markerValue, mUnits);
- g2d.drawString(marker, markerPosition - metrics.stringWidth(marker),
- y + metrics.getAscent() * 0.5f);
- }
- }
-
- private void drawGuides(Graphics2D g2d) {
- g2d.setColor(TEXT_COLOR);
- g2d.drawLine(LEFT_MARGIN - 10, mBottom, mRight + 10, mBottom);
- if (mYScale > 0) {
- g2d.drawLine(LEFT_MARGIN, mBottom, LEFT_MARGIN, TOP_MARGIN);
- g2d.drawLine(mRight, mBottom, mRight, TOP_MARGIN);
- }
- }
-
- @Override
- protected void updateData() {
- long start;
- synchronized (mData) {
- start = mData.getStartTime();
- mSize = mData.size();
- assert mData.getStreamCount() == mValues.length;
- if (mTimes == null || mTimes.length < mSize) {
- int alloc = Math.max(mSize, mTimes == null ? 64 : mTimes.length * 2);
- mTimes = new float[alloc];
- mTypes = new int[alloc];
- for (int j = 0; j < mData.getStreamCount(); ++j) {
- mValues[j] = new float[alloc];
- }
- }
- for (int i = 0; i < mSize; ++i) {
- TimelineData.Sample sample = mData.get(i);
- mTimes[i] = sample.time;
- mTypes[i] = sample.type;
- float value = 0.0f;
- for (int j = 0; j < mData.getStreamCount(); ++j) {
- value += sample.values[j];
- mValues[j][i] = value;
- }
- }
- for (int j = 0; j < mData.getStreamCount(); ++j) {
- mCurrent[j] = mSize > 0 ? mData.get(mSize - 1).values[j] : 0.0f;
- }
-
- // Calculate begin and end times in seconds.
- mEndTime = mData.getEndTime() - mBufferTime;
- mBeginTime = mEndTime - (mRight - LEFT_MARGIN) / X_SCALE;
- // Animate the current maximum towards the real one.
- float cappedMax = Math.min(mData.getMaxTotal(), mAbsoluteMax);
- if (cappedMax > mCurrentMax) {
- mCurrentMax = lerp(mCurrentMax, cappedMax, mFirstFrame ? 1.f : .95f);
- }
-
- // Animate the fade in/out of markers.
- FontMetrics metrics = getFontMetrics(DEFAULT_FONT);
- int ascent = metrics.getAscent();
- float distance = mMarkerSeparation * mYScale;
- float evenMarkersTarget = 1.0f;
- if (distance < ascent * 2) { // Too many markers
- if (mEvenMarkersAlpha < 0.1f) {
- mMarkerSeparation *= 2;
- mEvenMarkersAlpha = 1.0f;
- } else {
- evenMarkersTarget = 0.0f;
- }
- } else if (distance > ascent * 5) { // Not enough
- if (mEvenMarkersAlpha > 0.9f) {
- mMarkerSeparation /= 2;
- mEvenMarkersAlpha = 0.0f;
- }
- }
- mEvenMarkersAlpha = lerp(mEvenMarkersAlpha, evenMarkersTarget, 0.999f);
- }
- synchronized (mEvents) {
- mEventsSize = mEvents.size();
- if (mEventStart == null || mEventStart.length < mEventsSize) {
- int alloc = Math.max(mEventsSize, mEventStart == null ? 64 : mEventStart.length * 2);
- mEventStart = new float[alloc];
- mEventEnd = new float[alloc];
- mEventTypes = new int[alloc];
-
- }
- for (int i = 0; i < mEventsSize; i++) {
- EventData.Event event = mEvents.get(i);
- mEventStart[i] = (event.from - start) / 1000.0f;
- mEventEnd[i] = event.to == -1 ? Float.NaN : (event.to - start) / 1000.0f;
- mEventTypes[i] = event.type;
- }
-
- // Animate events in progress
- if (mEventProgress > 0.95f) {
- mEventProgressDir = -mEventProgressDir;
- mEventProgress = 0.0f;
- }
- mEventProgressStart = (mEventProgressStart + mFrameLength * 200.0f) % 360.0f;
- mEventProgress = lerp(mEventProgress, 1.0f, .99f);
- }
- }
-
- public enum Style {
- NONE,
- SOLID,
- DASHED
- }
-
- private static class EventInfo {
-
- public final int type;
-
- public final int stream;
-
- public final Icon icon;
-
- public final Color color;
-
- public final Color progress;
-
- public final boolean range;
-
- private EventInfo(int type, int stream, Icon icon, Color color,
- Color progress, boolean range) {
- this.type = type;
- this.stream = stream;
- this.icon = icon;
- this.color = color;
- this.progress = progress;
- this.range = range;
- }
- }
-}
diff --git a/base/chartlib/src/main/java/com/android/tools/chartlib/TimelineData.java b/base/chartlib/src/main/java/com/android/tools/chartlib/TimelineData.java
deleted file mode 100644
index fbcc676..0000000
--- a/base/chartlib/src/main/java/com/android/tools/chartlib/TimelineData.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.chartlib;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.concurrency.GuardedBy;
-
-import java.util.List;
-
-/**
- * A group of streams of data sampled over time. This object is thread safe as it can be
- * read/modified from any thread. It uses itself as the mutex object so it is possible to
- * synchronize on it if modifications from other threads want to be prevented.
- */
-public class TimelineData {
-
- private final int myStreams;
-
- @GuardedBy("this")
- private final List<Sample> mSamples;
-
- @GuardedBy("this")
- private long mStart;
-
- @GuardedBy("this")
- private float mMaxTotal;
-
- public TimelineData(int streams, int capacity) {
- myStreams = streams;
- mSamples = new CircularArrayList<Sample>(capacity);
- clear();
- }
-
- @VisibleForTesting
- public synchronized long getStartTime() {
- return mStart;
- }
-
- public int getStreamCount() {
- return myStreams;
- }
-
- public synchronized float getMaxTotal() {
- return mMaxTotal;
- }
-
- public synchronized void add(long time, int type, float... values) {
- assert values.length == myStreams;
- float total = 0.0f;
- for (float value : values) {
- total += value;
- }
- mMaxTotal = Math.max(mMaxTotal, total);
- mSamples.add(new Sample((time - mStart) / 1000.0f, type, values));
- }
-
- public synchronized void clear() {
- mSamples.clear();
- mMaxTotal = 0.0f;
- mStart = System.currentTimeMillis();
- }
-
- public int size() {
- return mSamples.size();
- }
-
- public Sample get(int index) {
- return mSamples.get(index);
- }
-
- public boolean isEmpty() {
- return size() == 0;
- }
-
- public synchronized float getEndTime() {
- return (mSamples.isEmpty() ? 0.0f : (System.currentTimeMillis() - mStart)) / 1000.f;
- }
-
- /**
- * A sample of all the streams at a given moment in time.
- */
- public static class Sample {
-
- /**
- * The time of the sample. In seconds since the start of the sampling.
- */
- public final float time;
-
- public final float[] values;
-
- public final int type;
-
- public Sample(float time, int type, float[] values) {
- this.time = time;
- this.values = values;
- this.type = type;
- }
- }
-}
diff --git a/base/chartlib/src/test/java/AnimatedComponentVisualTests.java b/base/chartlib/src/test/java/AnimatedComponentVisualTests.java
deleted file mode 100644
index c16089d..0000000
--- a/base/chartlib/src/test/java/AnimatedComponentVisualTests.java
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import com.android.tools.chartlib.AnimatedComponent;
-import com.android.tools.chartlib.EventData;
-import com.android.tools.chartlib.SunburstComponent;
-import com.android.tools.chartlib.TimelineComponent;
-import com.android.tools.chartlib.TimelineData;
-import com.android.tools.chartlib.ValuedTreeNode;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.LayoutManager;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JSlider;
-import javax.swing.JTabbedPane;
-import javax.swing.UIManager;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-import javax.swing.tree.DefaultMutableTreeNode;
-
-public class AnimatedComponentVisualTests extends JDialog {
-
- private List<AnimatedComponent> mComponents = new LinkedList<AnimatedComponent>();
-
- public AnimatedComponentVisualTests() {
- JPanel contentPane = new JPanel(new BorderLayout());
- JButton close = new JButton("Close");
- JTabbedPane tabs = new JTabbedPane();
- tabs.addTab("PieChart", getPieChartExample());
- tabs.addTab("Timeline", getTimelineExample());
-
- contentPane.setPreferredSize(new Dimension(1280, 1024));
- contentPane.add(tabs, BorderLayout.CENTER);
-
- JPanel bottom = new JPanel(new BorderLayout());
- bottom.add(close, BorderLayout.EAST);
- contentPane.add(bottom, BorderLayout.SOUTH);
-
- JPanel controls = new JPanel();
- controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
- controls.add(Box.createRigidArea(new Dimension(100, 20)));
- final JCheckBox debug = new JCheckBox("Debug");
- debug.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- for (AnimatedComponent component : mComponents) {
- component.setDrawDebugInfo(debug.isSelected());
- }
- }
- });
- controls.add(debug);
-
- final JButton step = new JButton("Step");
- step.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- for (AnimatedComponent component : mComponents) {
- component.step();
- }
- }
- });
- final JCheckBox update = new JCheckBox("Update");
- update.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- for (AnimatedComponent component : mComponents) {
- component.setUpdateData(update.isSelected());
- }
- step.setEnabled(!update.isSelected());
- }
- });
- update.setSelected(true);
- step.setEnabled(false);
- final JCheckBox dark = new JCheckBox("Dark");
- dark.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- setDarkMode(dark.isSelected());
- }
- });
- controls.add(dark);
- controls.add(update);
- controls.add(step);
- contentPane.add(controls, BorderLayout.WEST);
-
- setDarkMode(false);
- setContentPane(contentPane);
- setModal(true);
- getRootPane().setDefaultButton(close);
-
- close.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- dispose();
- }
- });
- }
-
- private void setDarkMode(boolean dark) {
- for (AnimatedComponent c : mComponents) {
- c.setBackground(dark ? new Color(60, 63, 65) : new Color(244, 244, 244));
- }
- }
-
- interface Value {
- void set(int v);
- int get();
- }
-
- private static JPanel createVaribleSlider(String name, final int a, final int b,
- final Value value) {
- JPanel panel = new JPanel(new BorderLayout());
- final JLabel text = new JLabel();
- final JSlider slider = new JSlider(a, b);
- ChangeListener listener = new ChangeListener() {
- @Override
- public void stateChanged(ChangeEvent changeEvent) {
- value.set(slider.getValue());
- text.setText(String.format("%d [%d,%d]", slider.getValue(), a, b));
- }
- };
- slider.setValue(value.get());
- listener.stateChanged(null);
- slider.addChangeListener(listener);
- panel.add(slider, BorderLayout.CENTER);
- panel.add(new JLabel(name + ": "), BorderLayout.WEST);
- panel.add(text, BorderLayout.EAST);
- panel.setAlignmentX(Component.LEFT_ALIGNMENT);
- return panel;
- }
-
- private JPanel createControlledPane(JPanel panel, AnimatedComponent animated) {
- panel.setLayout(new BorderLayout());
- mComponents.add(animated);
- panel.add(animated, BorderLayout.CENTER);
-
- JPanel controls = new JPanel();
- LayoutManager manager = new BoxLayout(controls, BoxLayout.Y_AXIS);
- controls.setLayout(manager);
- controls.setPreferredSize(new Dimension(300, 800));
- panel.add(controls, BorderLayout.WEST);
- return controls;
- }
-
- static class DataNode extends DefaultMutableTreeNode implements ValuedTreeNode {
-
- private int mCount;
- private int mValue;
-
- public DataNode() {
- this(0, 0);
- }
-
- public DataNode(int count, int value) {
- mCount = count;
- mValue = value;
- }
-
- @Override
- public int getCount() {
- return mCount;
- }
-
- @Override
- public int getValue() {
- return mValue;
- }
-
- public void add(int count, int value) {
- mCount += count;
- mValue += value;
- if (parent instanceof DataNode) {
- ((DataNode)parent).add(count, value);
- }
- }
-
- public void addDataNode(DataNode dataNode) {
- super.add(dataNode);
- add(dataNode.getCount(), dataNode.getValue());
- }
- }
-
- private JPanel getPieChartExample() {
-
- final DataNode data = new DataNode();
- data.addDataNode(new DataNode(1, 10));
-
- final SunburstComponent layout = new SunburstComponent(data);
-
- JPanel panel = new JPanel();
- JPanel controls = createControlledPane(panel, layout);
- final JLabel info = new JLabel("<No information yet>");
- panel.add(info, BorderLayout.SOUTH);
-
- controls.add(createVaribleSlider("Gap", 0, 200, new Value() {
- @Override
- public void set(int v) {
- layout.setGap(v);
- }
-
- @Override
- public int get() {
- return (int) layout.getGap();
- }
- }));
- controls.add(createVaribleSlider("Size", 0, 200, new Value() {
- @Override
- public void set(int v) {
- layout.setSliceWidth(v);
- }
-
- @Override
- public int get() {
- return (int) layout.getSliceWidth();
- }
- }));
- controls.add(createVaribleSlider("Angle", 0, 360, new Value() {
- @Override
- public void set(int v) {
- layout.setAngle(v);
- }
-
- @Override
- public int get() {
- return (int) layout.getAngle();
- }
- }));
- controls.add(createVaribleSlider("Start", 0, 360, new Value() {
- @Override
- public void set(int v) {
- layout.setStart(v);
- }
-
- @Override
- public int get() {
- return (int) layout.getStart();
- }
- }));
- controls.add(createVaribleSlider("Fixed", 1, 100, new Value() {
- @Override
- public void set(int v) {
- layout.setFixed(v);
- }
-
- @Override
- public int get() {
- return (int) layout.getFixed();
- }
- }));
- controls.add(createVaribleSlider("Separator", 0, 100, new Value() {
- @Override
- public void set(int v) {
- layout.setSeparator(v);
- }
-
- @Override
- public int get() {
- return (int) layout.getSeparator();
- }
- }));
- controls.add(createButton("Generate", new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- generateLayoutData((DataNode) layout.getData(), 5);
- }
- }));
- controls.add(createButton("Tree A", new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- DataNode g = new DataNode();
- g.addDataNode(createTree(1));
- g.addDataNode(createValue());
- g.addDataNode(createTree(1));
- g.addDataNode(createValue());
- g.addDataNode(createTree(0));
- layout.setData(g);
- }
- }));
- controls.add(createButton("Tree B", new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- DataNode g = new DataNode();
- g.addDataNode(createValue());
- g.addDataNode(createValue());
- g.addDataNode(createTree(0));
- layout.setData(g);
- }
- }));
- controls.add(createButton("Value", new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- DataNode g = new DataNode();
- g.addDataNode(new DataNode(1, (int) (Math.random() * 50)));
- layout.setData(g);
- }
- }));
- controls.add(createCheckbox("Auto size", new ItemListener() {
- @Override
- public void itemStateChanged(ItemEvent itemEvent) {
- layout.setAutoSize(itemEvent.getStateChange() == ItemEvent.SELECTED);
- }
- }));
- controls.add(
- new Box.Filler(new Dimension(0, 0), new Dimension(300, Integer.MAX_VALUE),
- new Dimension(300, Integer.MAX_VALUE)));
-
- layout.addSelectionListener(new SunburstComponent.SliceSelectionListener() {
- @Override
- public void valueChanged(SunburstComponent.SliceSelectionEvent e) {
- ValuedTreeNode node = e.getNode();
- info.setText(node == null ? "<No selection>" : String.format("Value %d Count %d",
- node.getValue(), node.getCount()));
- }
- });
- return panel;
- }
-
- private static DataNode createValue() {
- return new DataNode(1, (int)(Math.random() * 50));
- }
-
- private static DataNode createTree(int depth) {
- DataNode b = depth == 0 ? createValue() : createTree(depth - 1);
- DataNode c = depth == 0 ? createValue() : createTree(depth - 1);
- DataNode a = new DataNode();
- a.addDataNode(b);
- a.addDataNode(c);
- return a;
- }
-
- private static Component createButton(String label, ActionListener action) {
- JButton button = new JButton(label);
- button.addActionListener(action);
- button.setMaximumSize(new Dimension(Integer.MAX_VALUE, button.getMaximumSize().height));
- return button;
- }
-
- private static Component createCheckbox(String label, ItemListener action) {
- JCheckBox button = new JCheckBox(label);
- button.addItemListener(action);
- button.setMaximumSize(new Dimension(Integer.MAX_VALUE, button.getMaximumSize().height));
- return button;
- }
-
- private static void generateLayoutData(DataNode data, int maxDepth) {
- Random random = new Random();
- int branch = random.nextInt(9) + 1;
- for (int i = 0; i < branch; i++) {
- int value = random.nextInt(1024);
- if (maxDepth > 0 && random.nextInt(4) == 0) {
- DataNode group = new DataNode();
- group.add(new DataNode(1, value));
- generateLayoutData(group, maxDepth - 1);
- data.addDataNode(group);
- } else {
- data.addDataNode(new DataNode(1, value));
- }
- }
- }
-
- private JPanel getTimelineExample() {
- final TimelineData data = new TimelineData(2, 2000);
- final EventData events = new EventData();
- final int streams = 2;
- final AtomicInteger variance = new AtomicInteger(10);
- final AtomicInteger delay = new AtomicInteger(100);
- final AtomicInteger type = new AtomicInteger(0);
- new Thread() {
- @Override
- public void run() {
- super.run();
- try {
- float[] values = new float[streams];
- while (true) {
- int v = variance.get();
- for (int i = 0; i < streams; i++) {
- float delta = (float) Math.random() * variance.get() - v * 0.5f;
- values[i] = Math.max(0, delta + values[i]);
- }
- synchronized (data) {
- data.add(System.currentTimeMillis(), type.get() + (v == 0 ? 1 : 0), Arrays.copyOf(values,
- streams));
- }
- Thread.sleep(delay.get());
- }
- } catch (InterruptedException e) {
- }
- }
- }.start();
- final TimelineComponent timeline = new TimelineComponent(data, events, 1.0f, 10.0f, 1000.0f,
- 10.0f);
- timeline.configureStream(0, "Data 0", new Color(0x78abd9));
- timeline.configureStream(1, "Data 1", new Color(0xbaccdc));
-
- timeline.configureUnits("@");
- timeline.configureEvent(1, 0, UIManager.getIcon("Tree.leafIcon"),
- new Color(0x92ADC6),
- new Color(0x2B4E8C), false);
- timeline.configureEvent(2, 1, UIManager.getIcon("Tree.leafIcon"),
- new Color(255, 191, 176),
- new Color(76, 14, 29), true);
- timeline.configureType(1, TimelineComponent.Style.SOLID);
- timeline.configureType(2, TimelineComponent.Style.DASHED);
-
- final JPanel panel = new JPanel();
- final JPanel controls = createControlledPane(panel, timeline);
- controls.add(createVaribleSlider("Delay", 10, 5000, new Value() {
- @Override
- public void set(int v) {
- delay.set(v);
- }
-
- @Override
- public int get() {
- return delay.get();
- }
- }));
- controls.add(createVaribleSlider("Variance", 0, 50, new Value() {
- @Override
- public void set(int v) {
- variance.set(v);
- }
-
- @Override
- public int get() {
- return variance.get();
- }
- }));
- controls.add(createVaribleSlider("Type", 0, 2, new Value() {
- @Override
- public void set(int v) {
- type.set(v);
- }
-
- @Override
- public int get() {
- return type.get();
- }
- }));
- controls.add(createEventButton(1, events, variance));
- controls.add(createEventButton(1, events, null));
- controls.add(createEventButton(2, events, variance));
-
- controls.add(new Box.Filler(new Dimension(0, 0), new Dimension(300, Integer.MAX_VALUE), new Dimension(300, Integer.MAX_VALUE)));
- panel.add(timeline, BorderLayout.CENTER);
- return panel;
- }
-
- private Component createEventButton(final int type, final EventData events,
- final AtomicInteger variance) {
- final String start = "Start " + (variance != null ? "blocking " : "") + "event type " + type;
- final String stop = "Stop event type " + type;
- return createButton(start, new ActionListener() {
- EventData.Event event = null;
- int var = 0;
-
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- JButton button = (JButton) actionEvent.getSource();
- if (event != null) {
- event.stop(System.currentTimeMillis());
- event = null;
- if (variance != null) {
- variance.set(var);
- }
- button.setText(start);
- } else {
- event = events.start(System.currentTimeMillis(), type);
- if (variance != null) {
- var = variance.get();
- variance.set(0);
- }
- button.setText(stop);
- }
- }
- });
- }
-
- public static void main(String[] args) {
- AnimatedComponentVisualTests dialog = new AnimatedComponentVisualTests();
-
- dialog.pack();
- dialog.setVisible(true);
- System.exit(0);
- }
-}
diff --git a/base/chartlib/src/test/java/com/android/tools/chartlib/TimelineDataTest.java b/base/chartlib/src/test/java/com/android/tools/chartlib/TimelineDataTest.java
deleted file mode 100644
index df61bf1..0000000
--- a/base/chartlib/src/test/java/com/android/tools/chartlib/TimelineDataTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.chartlib;
-
-import junit.framework.TestCase;
-
-public class TimelineDataTest extends TestCase {
-
- private TimelineData mData;
-
- private long mCreationTime;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- mCreationTime = System.currentTimeMillis();
- mData = new TimelineData(2, 2);
- }
-
- public void testStreamGetters() throws Exception {
- assertEquals(2, mData.getStreamCount());
- }
-
- public void testGetStartTime() throws Exception {
- assertTrue(mCreationTime <= mData.getStartTime());
- assertTrue(mData.getStartTime() <= System.currentTimeMillis());
- Thread.sleep(10);
- long now = System.currentTimeMillis();
- mData.clear();
- assertTrue(now <= mData.getStartTime());
- }
-
- public void testGetMaxTotal() throws Exception {
- assertEquals(0.0f, mData.getMaxTotal());
- long now = System.currentTimeMillis();
- mData.add(now + 1, 0, 1.0f, 2.0f);
- assertEquals(3.0f, mData.getMaxTotal());
- mData.add(now + 2, 0, 1.0f, 1.0f);
- assertEquals(3.0f, mData.getMaxTotal());
- mData.add(now + 3, 0, 2.0f, 2.0f);
- assertEquals(4.0f, mData.getMaxTotal());
- }
-
- public void testAdd() throws Exception {
- assertEquals(0, mData.size());
- long start = mData.getStartTime();
-
- mData.add(start, 0, 1.0f, 2.0f);
- assertEquals(1, mData.size());
- assertEquals(0.0f, mData.get(0).time, 0.0001f);
- assertEquals(1.0f, mData.get(0).values[0]);
- assertEquals(2.0f, mData.get(0).values[1]);
-
- mData.add(start + 1000, 0, 3.0f, 4.0f);
- assertEquals(2, mData.size());
- assertEquals(1.0f, mData.get(1).time, 0.0001f);
- assertEquals(3.0f, mData.get(1).values[0]);
- assertEquals(4.0f, mData.get(1).values[1]);
-
- mData.add(start + 2000, 0, 5.0f, 6.0f);
- assertEquals(2, mData.size());
- assertEquals(2.0f, mData.get(1).time, 0.0001);
- assertEquals(5.0f, mData.get(1).values[0]);
- assertEquals(6.0f, mData.get(1).values[1]);
-
- mData.clear();
- assertEquals(0, mData.size());
- }
-}
\ No newline at end of file
diff --git a/base/common/build.gradle b/base/common/build.gradle
deleted file mode 100644
index 904c8f9..0000000
--- a/base/common/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'jacoco'
-apply plugin: 'sdk-java-lib'
-
-dependencies {
- compile project(':base:annotations')
- compile 'com.google.guava:guava:17.0'
-
- testCompile 'junit:junit:4.12'
-}
-
-group = 'com.android.tools'
-archivesBaseName = 'common'
-version = rootProject.ext.baseVersion
-
-project.ext.pomName = 'Android Tools common library'
-project.ext.pomDesc = 'common library used by other Android tools libraries.'
-
-apply from: "$rootDir/buildSrc/base/publish.gradle"
-apply from: "$rootDir/buildSrc/base/bintray.gradle"
-apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/base/common/common.iml b/base/common/common.iml
deleted file mode 100644
index 1fa0440..0000000
--- a/base/common/common.iml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="library" exported="" name="guava-tools" level="project" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="module" module-name="android-annotations" exported="" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/common/src/main/java/com/android/SdkConstants.java b/base/common/src/main/java/com/android/SdkConstants.java
deleted file mode 100644
index 933f652..0000000
--- a/base/common/src/main/java/com/android/SdkConstants.java
+++ /dev/null
@@ -1,1446 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-import java.io.File;
-
-/**
- * Constant definition class.<br>
- * <br>
- * Most constants have a prefix defining the content.
- * <ul>
- * <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
- * <li><code>FN_</code> File name constant.</li>
- * <li><code>FD_</code> Folder name constant.</li>
- * <li><code>TAG_</code> XML element tag name</li>
- * <li><code>ATTR_</code> XML attribute name</li>
- * <li><code>VALUE_</code> XML attribute value</li>
- * <li><code>CLASS_</code> Class name</li>
- * <li><code>DOT_</code> File name extension, including the dot </li>
- * <li><code>EXT_</code> File name extension, without the dot </li>
- * </ul>
- */
- at SuppressWarnings({"javadoc", "unused"}) // Not documenting all the fields here
-public final class SdkConstants {
- public static final int PLATFORM_UNKNOWN = 0;
- public static final int PLATFORM_LINUX = 1;
- public static final int PLATFORM_WINDOWS = 2;
- public static final int PLATFORM_DARWIN = 3;
-
- /**
- * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
- * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
- */
- public static final int CURRENT_PLATFORM = currentPlatform();
-
- /** Environment variable that specifies the path of an Android SDK. */
- public static final String ANDROID_HOME_ENV = "ANDROID_HOME";
-
- /** Property in local.properties file that specifies the path of the Android SDK. */
- public static final String SDK_DIR_PROPERTY = "sdk.dir";
-
- /** Property in local.properties file that specifies the path of the Android NDK. */
- public static final String NDK_DIR_PROPERTY = "ndk.dir";
-
- /** Property in gradle-wrapper.properties file that specifies the URL to the correct Gradle distribution. */
- public static final String GRADLE_DISTRIBUTION_URL_PROPERTY = "distributionUrl"; //$NON-NLS-1$
-
- /**
- * The encoding we strive to use for all files we write.
- * <p>
- * When possible, use the APIs which take a {@link java.nio.charset.Charset} and pass in
- * {@link com.google.common.base.Charsets#UTF_8} instead of using the String encoding
- * method.
- */
- public static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
-
- /**
- * Charset for the ini file handled by the SDK.
- */
- public static final String INI_CHARSET = UTF_8;
-
- /** Path separator used by Gradle */
- public static final String GRADLE_PATH_SEPARATOR = ":"; //$NON-NLS-1$
-
- /** An SDK Project's AndroidManifest.xml file */
- public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml"; //$NON-NLS-1$
- /** pre-dex jar filename. i.e. "classes.jar" */
- public static final String FN_CLASSES_JAR = "classes.jar"; //$NON-NLS-1$
- /** Dex filename inside the APK. i.e. "classes.dex" */
- public static final String FN_APK_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
- /** Dex filename inside the APK. i.e. "classes.dex" */
- public static final String FN_APK_CLASSES_N_DEX = "classes%d.dex"; //$NON-NLS-1$
-
- /** An SDK Project's build.xml file */
- public static final String FN_BUILD_XML = "build.xml"; //$NON-NLS-1$
- /** An SDK Project's build.gradle file */
- public static final String FN_BUILD_GRADLE = "build.gradle"; //$NON-NLS-1$
- /** An SDK Project's settings.gradle file */
- public static final String FN_SETTINGS_GRADLE = "settings.gradle"; //$NON-NLS-1$
- /** An SDK Project's gradle.properties file */
- public static final String FN_GRADLE_PROPERTIES = "gradle.properties"; //$NON-NLS-1$
- /** An SDK Project's gradle daemon executable */
- public static final String FN_GRADLE_UNIX = "gradle"; //$NON-NLS-1$
- /** An SDK Project's gradle.bat daemon executable (gradle for windows) */
- public static final String FN_GRADLE_WIN = FN_GRADLE_UNIX + ".bat"; //$NON-NLS-1$
- /** An SDK Project's gradlew file */
- public static final String FN_GRADLE_WRAPPER_UNIX = "gradlew"; //$NON-NLS-1$
- /** An SDK Project's gradlew.bat file (gradlew for windows) */
- public static final String FN_GRADLE_WRAPPER_WIN = FN_GRADLE_WRAPPER_UNIX + ".bat"; //$NON-NLS-1$
- /** An SDK Project's gradle wrapper library */
- public static final String FN_GRADLE_WRAPPER_JAR = "gradle-wrapper.jar"; //$NON-NLS-1$
- /** Name of the framework library, i.e. "android.jar" */
- public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; //$NON-NLS-1$
- /** Name of the framework library, i.e. "uiautomator.jar" */
- public static final String FN_UI_AUTOMATOR_LIBRARY = "uiautomator.jar"; //$NON-NLS-1$
- /** Name of the layout attributes, i.e. "attrs.xml" */
- public static final String FN_ATTRS_XML = "attrs.xml"; //$NON-NLS-1$
- /** Name of the layout attributes, i.e. "attrs_manifest.xml" */
- public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml"; //$NON-NLS-1$
- /** framework aidl import file */
- public static final String FN_FRAMEWORK_AIDL = "framework.aidl"; //$NON-NLS-1$
- /** framework renderscript folder */
- public static final String FN_FRAMEWORK_RENDERSCRIPT = "renderscript"; //$NON-NLS-1$
- /** framework include folder */
- public static final String FN_FRAMEWORK_INCLUDE = "include"; //$NON-NLS-1$
- /** framework include (clang) folder */
- public static final String FN_FRAMEWORK_INCLUDE_CLANG = "clang-include"; //$NON-NLS-1$
- /** layoutlib.jar file */
- public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar"; //$NON-NLS-1$
- /** widget list file */
- public static final String FN_WIDGETS = "widgets.txt"; //$NON-NLS-1$
- /** Intent activity actions list file */
- public static final String FN_INTENT_ACTIONS_ACTIVITY = "activity_actions.txt"; //$NON-NLS-1$
- /** Intent broadcast actions list file */
- public static final String FN_INTENT_ACTIONS_BROADCAST = "broadcast_actions.txt"; //$NON-NLS-1$
- /** Intent service actions list file */
- public static final String FN_INTENT_ACTIONS_SERVICE = "service_actions.txt"; //$NON-NLS-1$
- /** Intent category list file */
- public static final String FN_INTENT_CATEGORIES = "categories.txt"; //$NON-NLS-1$
-
- /** annotations support jar */
- public static final String FN_ANNOTATIONS_JAR = "annotations.jar"; //$NON-NLS-1$
-
- /** platform build property file */
- public static final String FN_BUILD_PROP = "build.prop"; //$NON-NLS-1$
- /** plugin properties file */
- public static final String FN_PLUGIN_PROP = "plugin.prop"; //$NON-NLS-1$
- /** add-on manifest file */
- public static final String FN_MANIFEST_INI = "manifest.ini"; //$NON-NLS-1$
- /** add-on layout device XML file. */
- public static final String FN_DEVICES_XML = "devices.xml"; //$NON-NLS-1$
- /** hardware properties definition file */
- public static final String FN_HARDWARE_INI = "hardware-properties.ini"; //$NON-NLS-1$
-
- /** project property file */
- public static final String FN_PROJECT_PROPERTIES = "project.properties"; //$NON-NLS-1$
-
- /** project local property file */
- public static final String FN_LOCAL_PROPERTIES = "local.properties"; //$NON-NLS-1$
-
- /** project ant property file */
- public static final String FN_ANT_PROPERTIES = "ant.properties"; //$NON-NLS-1$
-
- /** project local property file */
- public static final String FN_GRADLE_WRAPPER_PROPERTIES = "gradle-wrapper.properties"; //$NON-NLS-1$
-
- /** Skin layout file */
- public static final String FN_SKIN_LAYOUT = "layout"; //$NON-NLS-1$
-
- /** dx.jar file */
- public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
-
- /** dx executable (with extension for the current OS) */
- public static final String FN_DX =
- "dx" + ext(".bat", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** aapt executable (with extension for the current OS) */
- public static final String FN_AAPT =
- "aapt" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** aidl executable (with extension for the current OS) */
- public static final String FN_AIDL =
- "aidl" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** renderscript executable (with extension for the current OS) */
- public static final String FN_RENDERSCRIPT =
- "llvm-rs-cc" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** renderscript support exe (with extension for the current OS) */
- public static final String FN_BCC_COMPAT =
- "bcc_compat" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** renderscript support linker for ARM (with extension for the current OS) */
- public static final String FN_LD_ARM =
- "arm-linux-androideabi-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** renderscript support linker for X86 (with extension for the current OS) */
- public static final String FN_LD_X86 =
- "i686-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** renderscript support linker for MIPS (with extension for the current OS) */
- public static final String FN_LD_MIPS =
- "mipsel-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** adb executable (with extension for the current OS) */
- public static final String FN_ADB =
- "adb" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** emulator executable for the current OS */
- public static final String FN_EMULATOR =
- "emulator" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** zipalign executable (with extension for the current OS) */
- public static final String FN_ZIPALIGN =
- "zipalign" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** dexdump executable (with extension for the current OS) */
- public static final String FN_DEXDUMP =
- "dexdump" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** proguard executable (with extension for the current OS) */
- public static final String FN_PROGUARD =
- "proguard" + ext(".bat", ".sh"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** find_lock for Windows (with extension for the current OS) */
- public static final String FN_FIND_LOCK =
- "find_lock" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** hprof-conv executable (with extension for the current OS) */
- public static final String FN_HPROF_CONV =
- "hprof-conv" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** jack.jar */
- public static final String FN_JACK = "jack.jar"; //$NON-NLS-1$
- /** jill.jar */
- public static final String FN_JILL = "jill.jar"; //$NON-NLS-1$
-
- /** split-select */
- public static final String FN_SPLIT_SELECT = "split-select" + ext(".exe", "");
-
-
- /** properties file for SDK Updater packages */
- public static final String FN_SOURCE_PROP = "source.properties"; //$NON-NLS-1$
- /** properties file for content hash of installed packages */
- public static final String FN_CONTENT_HASH_PROP = "content_hash.properties"; //$NON-NLS-1$
- /** properties file for the SDK */
- public static final String FN_SDK_PROP = "sdk.properties"; //$NON-NLS-1$
-
-
- public static final String FN_RENDERSCRIPT_V8_JAR = "renderscript-v8.jar"; //$NON-NLS-1$
-
- /**
- * filename for gdbserver.
- */
- public static final String FN_GDBSERVER = "gdbserver"; //$NON-NLS-1$
- public static final String FN_GDB_SETUP = "gdb.setup"; //$NON-NLS-1$
-
- /** global Android proguard config file */
- public static final String FN_ANDROID_PROGUARD_FILE = "proguard-android.txt"; //$NON-NLS-1$
- /** global Android proguard config file with optimization enabled */
- public static final String FN_ANDROID_OPT_PROGUARD_FILE = "proguard-android-optimize.txt"; //$NON-NLS-1$
- /** default proguard config file with new file extension (for project specific stuff) */
- public static final String FN_PROJECT_PROGUARD_FILE = "proguard-project.txt"; //$NON-NLS-1$
-
- /* Folder Names for Android Projects . */
-
- /** Resources folder name, i.e. "res". */
- public static final String FD_RESOURCES = "res"; //$NON-NLS-1$
- /** Assets folder name, i.e. "assets" */
- public static final String FD_ASSETS = "assets"; //$NON-NLS-1$
- /** Default source folder name in an SDK project, i.e. "src".
- * <p/>
- * Note: this is not the same as {@link #FD_PKG_SOURCES}
- * which is an SDK sources folder for packages. */
- public static final String FD_SOURCES = "src"; //$NON-NLS-1$
- /** Default main source set folder name, i.e. "main" */
- public static final String FD_MAIN = "main"; //$NON-NLS-1$
- /** Default test source set folder name, i.e. "androidTest" */
- public static final String FD_TEST = "androidTest"; //$NON-NLS-1$
- /** Default java code folder name, i.e. "java" */
- public static final String FD_JAVA = "java"; //$NON-NLS-1$
- /** Default native code folder name, i.e. "jni" */
- public static final String FD_JNI = "jni"; //$NON-NLS-1$
- /** Default gradle folder name, i.e. "gradle" */
- public static final String FD_GRADLE = "gradle"; //$NON-NLS-1$
- /** Default gradle wrapper folder name, i.e. "gradle/wrapper" */
- public static final String FD_GRADLE_WRAPPER = FD_GRADLE + File.separator + "wrapper"; //$NON-NLS-1$
- /** Default generated source folder name, i.e. "gen" */
- public static final String FD_GEN_SOURCES = "gen"; //$NON-NLS-1$
- /** Default native library folder name inside the project, i.e. "libs"
- * While the folder inside the .apk is "lib", we call that one libs because
- * that's what we use in ant for both .jar and .so and we need to make the 2 development ways
- * compatible. */
- public static final String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$
- /** Native lib folder inside the APK: "lib" */
- public static final String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
- /** Default output folder name, i.e. "bin" */
- public static final String FD_OUTPUT = "bin"; //$NON-NLS-1$
- /** Classes output folder name, i.e. "classes" */
- public static final String FD_CLASSES_OUTPUT = "classes"; //$NON-NLS-1$
- /** proguard output folder for mapping, etc.. files */
- public static final String FD_PROGUARD = "proguard"; //$NON-NLS-1$
- /** aidl output folder for copied aidl files */
- public static final String FD_AIDL = "aidl"; //$NON-NLS-1$
-
- /** rs Libs output folder for support mode */
- public static final String FD_RS_LIBS = "rsLibs"; //$NON-NLS-1$
- /** rs Libs output folder for support mode */
- public static final String FD_RS_OBJ = "rsObj"; //$NON-NLS-1$
-
- /** jars folder */
- public static final String FD_JARS = "jars"; //$NON-NLS-1$
-
- /* Folder Names for the Android SDK */
-
- /** Name of the SDK platforms folder. */
- public static final String FD_PLATFORMS = "platforms"; //$NON-NLS-1$
- /** Name of the SDK addons folder. */
- public static final String FD_ADDONS = "add-ons"; //$NON-NLS-1$
- /** Name of the SDK system-images folder. */
- public static final String FD_SYSTEM_IMAGES = "system-images"; //$NON-NLS-1$
- /** Name of the SDK sources folder where source packages are installed.
- * <p/>
- * Note this is not the same as {@link #FD_SOURCES} which is the folder name where sources
- * are installed inside a project. */
- public static final String FD_PKG_SOURCES = "sources"; //$NON-NLS-1$
- /** Name of the SDK tools folder. */
- public static final String FD_TOOLS = "tools"; //$NON-NLS-1$
- /** Name of the SDK tools/support folder. */
- public static final String FD_SUPPORT = "support"; //$NON-NLS-1$
- /** Name of the SDK platform tools folder. */
- public static final String FD_PLATFORM_TOOLS = "platform-tools"; //$NON-NLS-1$
- /** Name of the SDK build tools folder. */
- public static final String FD_BUILD_TOOLS = "build-tools"; //$NON-NLS-1$
- /** Name of the SDK tools/lib folder. */
- public static final String FD_LIB = "lib"; //$NON-NLS-1$
- /** Name of the SDK docs folder. */
- public static final String FD_DOCS = "docs"; //$NON-NLS-1$
- /** Name of the doc folder containing API reference doc (javadoc) */
- public static final String FD_DOCS_REFERENCE = "reference"; //$NON-NLS-1$
- /** Name of the SDK images folder. */
- public static final String FD_IMAGES = "images"; //$NON-NLS-1$
- /** Name of the ABI to support. */
- public static final String ABI_ARMEABI = "armeabi"; //$NON-NLS-1$
- public static final String ABI_ARMEABI_V7A = "armeabi-v7a"; //$NON-NLS-1$
- public static final String ABI_ARM64_V8A = "arm64-v8a"; //$NON-NLS-1$
- public static final String ABI_INTEL_ATOM = "x86"; //$NON-NLS-1$
- public static final String ABI_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$
- public static final String ABI_MIPS = "mips"; //$NON-NLS-1$
- public static final String ABI_MIPS64 = "mips64"; //$NON-NLS-1$
- /** Name of the CPU arch to support. */
- public static final String CPU_ARCH_ARM = "arm"; //$NON-NLS-1$
- public static final String CPU_ARCH_ARM64 = "arm64"; //$NON-NLS-1$
- public static final String CPU_ARCH_INTEL_ATOM = "x86"; //$NON-NLS-1$
- public static final String CPU_ARCH_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$
- public static final String CPU_ARCH_MIPS = "mips"; //$NON-NLS-1$
- /** TODO double-check this is appropriate value for mips64 */
- public static final String CPU_ARCH_MIPS64 = "mips64"; //$NON-NLS-1$
- /** Name of the CPU model to support. */
- public static final String CPU_MODEL_CORTEX_A8 = "cortex-a8"; //$NON-NLS-1$
-
- /** Name of the SDK skins folder. */
- public static final String FD_SKINS = "skins"; //$NON-NLS-1$
- /** Name of the SDK samples folder. */
- public static final String FD_SAMPLES = "samples"; //$NON-NLS-1$
- /** Name of the SDK extras folder. */
- public static final String FD_EXTRAS = "extras"; //$NON-NLS-1$
- public static final String FD_M2_REPOSITORY = "m2repository"; //$NON-NLS-1$
- public static final String FD_NDK = "ndk-bundle"; //$NON-NLS-1$
- /**
- * Name of an extra's sample folder.
- * Ideally extras should have one {@link #FD_SAMPLES} folder containing
- * one or more sub-folders (one per sample). However some older extras
- * might contain a single "sample" folder with directly the samples files
- * in it. When possible we should encourage extras' owners to move to the
- * multi-samples format.
- */
- public static final String FD_SAMPLE = "sample"; //$NON-NLS-1$
- /** Name of the SDK templates folder, i.e. "templates" */
- public static final String FD_TEMPLATES = "templates"; //$NON-NLS-1$
- /** Name of the SDK Ant folder, i.e. "ant" */
- public static final String FD_ANT = "ant"; //$NON-NLS-1$
- /** Name of the SDK data folder, i.e. "data" */
- public static final String FD_DATA = "data"; //$NON-NLS-1$
- /** Name of the SDK renderscript folder, i.e. "rs" */
- public static final String FD_RENDERSCRIPT = "rs"; //$NON-NLS-1$
- /** Name of the Java resources folder, i.e. "resources" */
- public static final String FD_JAVA_RES = "resources"; //$NON-NLS-1$
- /** Name of the SDK resources folder, i.e. "res" */
- public static final String FD_RES = "res"; //$NON-NLS-1$
- /** Name of the SDK font folder, i.e. "fonts" */
- public static final String FD_FONTS = "fonts"; //$NON-NLS-1$
- /** Name of the android sources directory and the root of the SDK sources package folder. */
- public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
- /** Name of the addon libs folder. */
- public static final String FD_ADDON_LIBS = "libs"; //$NON-NLS-1$
-
- /** Name of the cache folder in the $HOME/.android. */
- public static final String FD_CACHE = "cache"; //$NON-NLS-1$
-
- /** API codename of a release (non preview) system image or platform. **/
- public static final String CODENAME_RELEASE = "REL"; //$NON-NLS-1$
-
- /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
- public static final String NS_RESOURCES =
- "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
-
- /**
- * Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s"
- * <p/>
- * This string contains a %s. It must be combined with the desired Java package, e.g.:
- * <pre>
- * String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "android");
- * String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "com.test.mycustomapp");
- * </pre>
- *
- * Note: if you need an URI specifically for the "android" namespace, consider using
- * {@link SdkConstants#NS_RESOURCES} instead.
- */
- public static final String NS_CUSTOM_RESOURCES_S = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$
-
-
- /** The name of the uses-library that provides "android.test.runner" */
- public static final String ANDROID_TEST_RUNNER_LIB =
- "android.test.runner"; //$NON-NLS-1$
-
- /* Folder path relative to the SDK root */
- /** Path of the documentation directory relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator;
-
- /** Path of the tools directory relative to the sdk folder, or to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator;
-
- /** Path of the lib directory relative to the sdk folder, or to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_TOOLS_LIB_FOLDER =
- OS_SDK_TOOLS_FOLDER + FD_LIB + File.separator;
-
- /**
- * Path of the lib directory relative to the sdk folder, or to a platform
- * folder. This is an OS path, ending with a separator.
- */
- public static final String OS_SDK_TOOLS_LIB_EMULATOR_FOLDER = OS_SDK_TOOLS_LIB_FOLDER
- + "emulator" + File.separator; //$NON-NLS-1$
-
- /** Path of the platform tools directory relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_PLATFORM_TOOLS_FOLDER = FD_PLATFORM_TOOLS + File.separator;
-
- /** Path of the build tools directory relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_BUILD_TOOLS_FOLDER = FD_BUILD_TOOLS + File.separator;
-
- /** Path of the Platform tools Lib directory relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_PLATFORM_TOOLS_LIB_FOLDER =
- OS_SDK_PLATFORM_TOOLS_FOLDER + FD_LIB + File.separator;
-
- /** Path of the bin folder of proguard folder relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_TOOLS_PROGUARD_BIN_FOLDER =
- OS_SDK_TOOLS_FOLDER +
- "proguard" + File.separator + //$NON-NLS-1$
- "bin" + File.separator; //$NON-NLS-1$
-
- /** Path of the template gradle wrapper folder relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SDK_TOOLS_TEMPLATES_GRADLE_WRAPPER_FOLDER =
- OS_SDK_TOOLS_FOLDER + FD_TEMPLATES + File.separator + FD_GRADLE_WRAPPER + File.separator;
-
- /* Folder paths relative to a platform or add-on folder */
-
- /** Path of the images directory relative to a platform or addon folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_IMAGES_FOLDER = FD_IMAGES + File.separator;
-
- /** Path of the skin directory relative to a platform or addon folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_SKINS_FOLDER = FD_SKINS + File.separator;
-
- /* Folder paths relative to a Platform folder */
-
- /** Path of the data directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_DATA_FOLDER = FD_DATA + File.separator;
-
- /** Path of the renderscript directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_RENDERSCRIPT_FOLDER = FD_RENDERSCRIPT + File.separator;
-
-
- /** Path of the samples directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_SAMPLES_FOLDER = FD_SAMPLES + File.separator;
-
- /** Path of the resources directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_RESOURCES_FOLDER =
- OS_PLATFORM_DATA_FOLDER + FD_RES + File.separator;
-
- /** Path of the fonts directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_FONTS_FOLDER =
- OS_PLATFORM_DATA_FOLDER + FD_FONTS + File.separator;
-
- /** Path of the android source directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_SOURCES_FOLDER = FD_ANDROID_SOURCES + File.separator;
-
- /** Path of the android templates directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_TEMPLATES_FOLDER = FD_TEMPLATES + File.separator;
-
- /** Path of the Ant build rules directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_PLATFORM_ANT_FOLDER = FD_ANT + File.separator;
-
- /** Path of the attrs.xml file relative to a platform folder. */
- public static final String OS_PLATFORM_ATTRS_XML =
- OS_PLATFORM_RESOURCES_FOLDER + SdkConstants.FD_RES_VALUES + File.separator +
- FN_ATTRS_XML;
-
- /** Path of the attrs_manifest.xml file relative to a platform folder. */
- public static final String OS_PLATFORM_ATTRS_MANIFEST_XML =
- OS_PLATFORM_RESOURCES_FOLDER + SdkConstants.FD_RES_VALUES + File.separator +
- FN_ATTRS_MANIFEST_XML;
-
- /** Path of the layoutlib.jar file relative to a platform folder. */
- public static final String OS_PLATFORM_LAYOUTLIB_JAR =
- OS_PLATFORM_DATA_FOLDER + FN_LAYOUTLIB_JAR;
-
- /** Path of the renderscript include folder relative to a platform folder. */
- public static final String OS_FRAMEWORK_RS =
- FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE;
- /** Path of the renderscript (clang) include folder relative to a platform folder. */
- public static final String OS_FRAMEWORK_RS_CLANG =
- FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE_CLANG;
-
- /* Folder paths relative to a addon folder */
- /** Path of the images directory relative to a folder folder.
- * This is an OS path, ending with a separator. */
- public static final String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator;
-
- /** Skin default **/
- public static final String SKIN_DEFAULT = "default"; //$NON-NLS-1$
-
- /** SDK property: ant templates revision */
- public static final String PROP_SDK_ANT_TEMPLATES_REVISION =
- "sdk.ant.templates.revision"; //$NON-NLS-1$
-
- /** SDK property: default skin */
- public static final String PROP_SDK_DEFAULT_SKIN = "sdk.skin.default"; //$NON-NLS-1$
-
- /* Android Class Constants */
- public static final String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
- public static final String CLASS_APPLICATION = "android.app.Application"; //$NON-NLS-1$
- public static final String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$
- public static final String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
- public static final String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
- public static final String CLASS_ATTRIBUTE_SET = "android.util.AttributeSet"; //$NON-NLS-1$
- public static final String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
- public static final String CLASS_INSTRUMENTATION_RUNNER =
- "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
- public static final String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
- public static final String CLASS_R = "android.R"; //$NON-NLS-1$
- public static final String CLASS_R_PREFIX = CLASS_R + "."; //$NON-NLS-1$
- public static final String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
- public static final String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
- public static final String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
- public static final String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
- public static final String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
- public static final String CLASS_NAME_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
- public static final String CLASS_VIEWGROUP_LAYOUTPARAMS =
- CLASS_VIEWGROUP + "$" + CLASS_NAME_LAYOUTPARAMS; //$NON-NLS-1$
- public static final String CLASS_NAME_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$
- public static final String CLASS_FRAMELAYOUT =
- "android.widget." + CLASS_NAME_FRAMELAYOUT; //$NON-NLS-1$
- public static final String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$
- public static final String CLASS_NAME_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
- public static final String CLASS_PREFERENCES =
- "android.preference." + CLASS_NAME_PREFERENCE_SCREEN; //$NON-NLS-1$
- public static final String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$
- public static final String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$
- public static final String CLASS_PARCEL = "android.os.Parcel"; //$NON-NLS-1$
- public static final String CLASS_FRAGMENT = "android.app.Fragment"; //$NON-NLS-1$
- public static final String CLASS_V4_FRAGMENT = "android.support.v4.app.Fragment"; //$NON-NLS-1$
- public static final String CLASS_ACTION_PROVIDER = "android.view.ActionProvider"; //$NON-NLS-1$
- public static final String CLASS_BACKUP_AGENT = "android.app.backup.BackupAgent"; //$NON-NLS-1$
- /** MockView is part of the layoutlib bridge and used to display classes that have
- * no rendering in the graphical layout editor. */
- public static final String CLASS_MOCK_VIEW = "com.android.layoutlib.bridge.MockView"; //$NON-NLS-1$
- public static final String CLASS_LAYOUT_INFLATER = "android.view.LayoutInflater"; //$NON-NLS-1$
-
- /* Android Design Support Class Constants */
- public static final String CLASS_COORDINATOR_LAYOUT = "android.support.design.widget.CoordinatorLayout"; //$NON-NLS-1$
- public static final String CLASS_APP_BAR_LAYOUT = "android.support.design.widget.AppBarLayout"; //$NON-NLS-1$
- public static final String CLASS_FLOATING_ACTION_BUTTON = "android.support.design.widget.FloatingActionButton"; //$NON-NLS-1$
- public static final String CLASS_COLLAPSING_TOOLBAR_LAYOUT = "android.support.design.widget.CollapsingToolbarLayout"; //$NON-NLS-1$
- public static final String CLASS_NAVIGATION_VIEW = "android.support.design.widget.NavigationView"; //$NON-NLS-1$
- public static final String CLASS_SNACKBAR = "android.support.design.widget.Snackbar"; //$NON-NLS-1$
- public static final String CLASS_TAB_LAYOUT = "android.support.design.widget.TabLayout"; //$NON-NLS-1$
- public static final String CLASS_TEXT_INPUT_LAYOUT = "android.support.design.widget.TextInputLayout"; //$NON-NLS-1$
- public static final String CLASS_NESTED_SCROLL_VIEW = "android.support.v4.widget.NestedScrollView"; //$NON-NLS-1$
-
-
- /** Returns the appropriate name for the 'android' command, which is 'android.exe' for
- * Windows and 'android' for all other platforms. */
- public static String androidCmdName() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- String cmd = "android"; //$NON-NLS-1$
- if (os.startsWith("Windows")) { //$NON-NLS-1$
- cmd += ".bat"; //$NON-NLS-1$
- }
- return cmd;
- }
-
- /** Returns the appropriate name for the 'mksdcard' command, which is 'mksdcard.exe' for
- * Windows and 'mkdsdcard' for all other platforms. */
- public static String mkSdCardCmdName() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- String cmd = "mksdcard"; //$NON-NLS-1$
- if (os.startsWith("Windows")) { //$NON-NLS-1$
- cmd += ".exe"; //$NON-NLS-1$
- }
- return cmd;
- }
-
- /**
- * Returns current platform
- *
- * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
- * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
- */
- public static int currentPlatform() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- if (os.startsWith("Mac OS")) { //$NON-NLS-1$
- return PLATFORM_DARWIN;
- } else if (os.startsWith("Windows")) { //$NON-NLS-1$
- return PLATFORM_WINDOWS;
- } else if (os.startsWith("Linux")) { //$NON-NLS-1$
- return PLATFORM_LINUX;
- }
-
- return PLATFORM_UNKNOWN;
- }
-
- /**
- * Returns current platform's UI name
- *
- * @return one of "Windows", "Mac OS X", "Linux" or "other".
- */
- public static String currentPlatformName() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- if (os.startsWith("Mac OS")) { //$NON-NLS-1$
- return "Mac OS X"; //$NON-NLS-1$
- } else if (os.startsWith("Windows")) { //$NON-NLS-1$
- return "Windows"; //$NON-NLS-1$
- } else if (os.startsWith("Linux")) { //$NON-NLS-1$
- return "Linux"; //$NON-NLS-1$
- }
-
- return "Other";
- }
-
- private static String ext(String windowsExtension, String nonWindowsExtension) {
- if (CURRENT_PLATFORM == PLATFORM_WINDOWS) {
- return windowsExtension;
- } else {
- return nonWindowsExtension;
- }
- }
-
- /** Default anim resource folder name, i.e. "anim" */
- public static final String FD_RES_ANIM = "anim"; //$NON-NLS-1$
- /** Default animator resource folder name, i.e. "animator" */
- public static final String FD_RES_ANIMATOR = "animator"; //$NON-NLS-1$
- /** Default color resource folder name, i.e. "color" */
- public static final String FD_RES_COLOR = "color"; //$NON-NLS-1$
- /** Default drawable resource folder name, i.e. "drawable" */
- public static final String FD_RES_DRAWABLE = "drawable"; //$NON-NLS-1$
- /** Default interpolator resource folder name, i.e. "interpolator" */
- public static final String FD_RES_INTERPOLATOR = "interpolator"; //$NON-NLS-1$
- /** Default layout resource folder name, i.e. "layout" */
- public static final String FD_RES_LAYOUT = "layout"; //$NON-NLS-1$
- /** Default menu resource folder name, i.e. "menu" */
- public static final String FD_RES_MENU = "menu"; //$NON-NLS-1$
- /** Default menu resource folder name, i.e. "mipmap" */
- public static final String FD_RES_MIPMAP = "mipmap"; //$NON-NLS-1$
- /** Default values resource folder name, i.e. "values" */
- public static final String FD_RES_VALUES = "values"; //$NON-NLS-1$
- /** Default xml resource folder name, i.e. "xml" */
- public static final String FD_RES_XML = "xml"; //$NON-NLS-1$
- /** Default raw resource folder name, i.e. "raw" */
- public static final String FD_RES_RAW = "raw"; //$NON-NLS-1$
- /** Separator between the resource folder qualifier. */
- public static final String RES_QUALIFIER_SEP = "-"; //$NON-NLS-1$
- /** Namespace used in XML files for Android attributes */
-
- // ---- XML ----
-
- /** URI of the reserved "xmlns" prefix */
- public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; //$NON-NLS-1$
- /** The "xmlns" attribute name */
- public static final String XMLNS = "xmlns"; //$NON-NLS-1$
- /** The default prefix used for the {@link #XMLNS_URI} */
- public static final String XMLNS_PREFIX = "xmlns:"; //$NON-NLS-1$
- /** Qualified name of the xmlns android declaration element */
- public static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$
- /** The default prefix used for the {@link #ANDROID_URI} name space */
- public static final String ANDROID_NS_NAME = "android"; //$NON-NLS-1$
- /** The default prefix used for the {@link #ANDROID_URI} name space including the colon */
- public static final String ANDROID_NS_NAME_PREFIX = "android:"; //$NON-NLS-1$
- public static final int ANDROID_NS_NAME_PREFIX_LEN = ANDROID_NS_NAME_PREFIX.length();
-
- /** The default prefix used for the app */
- public static final String APP_PREFIX = "app"; //$NON-NLS-1$
- /** The entity for the ampersand character */
- public static final String AMP_ENTITY = "&"; //$NON-NLS-1$
- /** The entity for the quote character */
- public static final String QUOT_ENTITY = """; //$NON-NLS-1$
- /** The entity for the apostrophe character */
- public static final String APOS_ENTITY = "'"; //$NON-NLS-1$
- /** The entity for the less than character */
- public static final String LT_ENTITY = "<"; //$NON-NLS-1$
- /** The entity for the greater than character */
- public static final String GT_ENTITY = ">"; //$NON-NLS-1$
-
- // ---- Elements and Attributes ----
-
- /** Namespace prefix used for all resources */
- public static final String URI_PREFIX =
- "http://schemas.android.com/apk/res/"; //$NON-NLS-1$
- /** Namespace used in XML files for Android attributes */
- public static final String ANDROID_URI =
- "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
- /** Namespace used in XML files for Android Tooling attributes */
- public static final String TOOLS_URI =
- "http://schemas.android.com/tools"; //$NON-NLS-1$
- /** Namespace used for auto-adjusting namespaces */
- public static final String AUTO_URI =
- "http://schemas.android.com/apk/res-auto"; //$NON-NLS-1$
- /** Default prefix used for tools attributes */
- public static final String TOOLS_PREFIX = "tools"; //$NON-NLS-1$
- public static final String R_CLASS = "R"; //$NON-NLS-1$
- public static final String ANDROID_PKG = "android"; //$NON-NLS-1$
-
- // Tags: Manifest
- public static final String TAG_SERVICE = "service"; //$NON-NLS-1$
- public static final String TAG_PERMISSION = "permission"; //$NON-NLS-1$
- public static final String TAG_USES_FEATURE = "uses-feature"; //$NON-NLS-1$
- public static final String TAG_USES_PERMISSION = "uses-permission";//$NON-NLS-1$
- public static final String TAG_USES_LIBRARY = "uses-library"; //$NON-NLS-1$
- public static final String TAG_APPLICATION = "application"; //$NON-NLS-1$
- public static final String TAG_INTENT_FILTER = "intent-filter"; //$NON-NLS-1$
- public static final String TAG_USES_SDK = "uses-sdk"; //$NON-NLS-1$
- public static final String TAG_ACTIVITY = "activity"; //$NON-NLS-1$
- public static final String TAG_RECEIVER = "receiver"; //$NON-NLS-1$
- public static final String TAG_PROVIDER = "provider"; //$NON-NLS-1$
- public static final String TAG_GRANT_PERMISSION = "grant-uri-permission"; //$NON-NLS-1$
- public static final String TAG_PATH_PERMISSION = "path-permission"; //$NON-NLS-1$
-
- // Tags: Resources
- public static final String TAG_RESOURCES = "resources"; //$NON-NLS-1$
- public static final String TAG_STRING = "string"; //$NON-NLS-1$
- public static final String TAG_ARRAY = "array"; //$NON-NLS-1$
- public static final String TAG_STYLE = "style"; //$NON-NLS-1$
- public static final String TAG_ITEM = "item"; //$NON-NLS-1$
- public static final String TAG_GROUP = "group"; //$NON-NLS-1$
- public static final String TAG_STRING_ARRAY = "string-array"; //$NON-NLS-1$
- public static final String TAG_PLURALS = "plurals"; //$NON-NLS-1$
- public static final String TAG_INTEGER_ARRAY = "integer-array"; //$NON-NLS-1$
- public static final String TAG_COLOR = "color"; //$NON-NLS-1$
- public static final String TAG_DIMEN = "dimen"; //$NON-NLS-1$
- public static final String TAG_DRAWABLE = "drawable"; //$NON-NLS-1$
- public static final String TAG_MENU = "menu"; //$NON-NLS-1$
- public static final String TAG_ENUM = "enum"; //$NON-NLS-1$
- public static final String TAG_FLAG = "flag"; //$NON-NLS-1$
- public static final String TAG_ATTR = "attr"; //$NON-NLS-1$
- public static final String TAG_DECLARE_STYLEABLE = "declare-styleable"; //$NON-NLS-1$
- public static final String TAG_EAT_COMMENT = "eat-comment"; //$NON-NLS-1$
- public static final String TAG_SKIP = "skip"; //$NON-NLS-1$
- public static final String TAG_SELECTOR = "selector"; //$NON-NLS-1$
-
- // Tags: XML
- public static final String TAG_HEADER = "header"; //$NON-NLS-1$
- public static final String TAG_APPWIDGET_PROVIDER = "appwidget-provider"; //$NON-NLS-1$
- public static final String TAG_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
-
- // Tags: Layouts
- public static final String VIEW_TAG = "view"; //$NON-NLS-1$
- public static final String VIEW_INCLUDE = "include"; //$NON-NLS-1$
- public static final String VIEW_MERGE = "merge"; //$NON-NLS-1$
- public static final String VIEW_FRAGMENT = "fragment"; //$NON-NLS-1$
- public static final String REQUEST_FOCUS = "requestFocus"; //$NON-NLS-1$
- public static final String TAG = "tag"; //$NON-NLS-1$
-
- public static final String VIEW = "View"; //$NON-NLS-1$
- public static final String VIEW_GROUP = "ViewGroup"; //$NON-NLS-1$
- public static final String FRAME_LAYOUT = "FrameLayout"; //$NON-NLS-1$
- public static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$
- public static final String RELATIVE_LAYOUT = "RelativeLayout"; //$NON-NLS-1$
- public static final String GRID_LAYOUT = "GridLayout"; //$NON-NLS-1$
- public static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$
- public static final String BUTTON = "Button"; //$NON-NLS-1$
- public static final String COMPOUND_BUTTON = "CompoundButton"; //$NON-NLS-1$
- public static final String ADAPTER_VIEW = "AdapterView"; //$NON-NLS-1$
- public static final String GALLERY = "Gallery"; //$NON-NLS-1$
- public static final String GRID_VIEW = "GridView"; //$NON-NLS-1$
- public static final String TAB_HOST = "TabHost"; //$NON-NLS-1$
- public static final String RADIO_GROUP = "RadioGroup"; //$NON-NLS-1$
- public static final String RADIO_BUTTON = "RadioButton"; //$NON-NLS-1$
- public static final String SWITCH = "Switch"; //$NON-NLS-1$
- public static final String EDIT_TEXT = "EditText"; //$NON-NLS-1$
- public static final String LIST_VIEW = "ListView"; //$NON-NLS-1$
- public static final String TEXT_VIEW = "TextView"; //$NON-NLS-1$
- public static final String CHECKED_TEXT_VIEW = "CheckedTextView"; //$NON-NLS-1$
- public static final String IMAGE_VIEW = "ImageView"; //$NON-NLS-1$
- public static final String SURFACE_VIEW = "SurfaceView"; //$NON-NLS-1$
- public static final String ABSOLUTE_LAYOUT = "AbsoluteLayout"; //$NON-NLS-1$
- public static final String TABLE_LAYOUT = "TableLayout"; //$NON-NLS-1$
- public static final String TABLE_ROW = "TableRow"; //$NON-NLS-1$
- public static final String TAB_WIDGET = "TabWidget"; //$NON-NLS-1$
- public static final String IMAGE_BUTTON = "ImageButton"; //$NON-NLS-1$
- public static final String SEEK_BAR = "SeekBar"; //$NON-NLS-1$
- public static final String VIEW_STUB = "ViewStub"; //$NON-NLS-1$
- public static final String SPINNER = "Spinner"; //$NON-NLS-1$
- public static final String WEB_VIEW = "WebView"; //$NON-NLS-1$
- public static final String TOGGLE_BUTTON = "ToggleButton"; //$NON-NLS-1$
- public static final String CHECK_BOX = "CheckBox"; //$NON-NLS-1$
- public static final String ABS_LIST_VIEW = "AbsListView"; //$NON-NLS-1$
- public static final String PROGRESS_BAR = "ProgressBar"; //$NON-NLS-1$
- public static final String ABS_SPINNER = "AbsSpinner"; //$NON-NLS-1$
- public static final String ABS_SEEK_BAR = "AbsSeekBar"; //$NON-NLS-1$
- public static final String VIEW_ANIMATOR = "ViewAnimator"; //$NON-NLS-1$
- public static final String VIEW_SWITCHER = "ViewSwitcher"; //$NON-NLS-1$
- public static final String EXPANDABLE_LIST_VIEW = "ExpandableListView"; //$NON-NLS-1$
- public static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$
- public static final String MULTI_AUTO_COMPLETE_TEXT_VIEW = "MultiAutoCompleteTextView"; //$NON-NLS-1$
- public static final String AUTO_COMPLETE_TEXT_VIEW = "AutoCompleteTextView"; //$NON-NLS-1$
- public static final String CHECKABLE = "Checkable"; //$NON-NLS-1$
- public static final String TEXTURE_VIEW = "TextureView"; //$NON-NLS-1$
-
- /* Android Design Support Tag Constants */
- public static final String COORDINATOR_LAYOUT = CLASS_COORDINATOR_LAYOUT;
- public static final String APP_BAR_LAYOUT = CLASS_APP_BAR_LAYOUT;
- public static final String FLOATING_ACTION_BUTTON = CLASS_FLOATING_ACTION_BUTTON;
- public static final String COLLAPSING_TOOLBAR_LAYOUT = CLASS_COLLAPSING_TOOLBAR_LAYOUT;
- public static final String NAVIGATION_VIEW = CLASS_NAVIGATION_VIEW;
- public static final String SNACKBAR = CLASS_SNACKBAR;
- public static final String TAB_LAYOUT = CLASS_TAB_LAYOUT;
- public static final String TEXT_INPUT_LAYOUT = CLASS_TEXT_INPUT_LAYOUT;
-
- // Tags: Drawables
- public static final String TAG_BITMAP = "bitmap"; //$NON-NLS-1$
-
- // Tags: Data-Binding
- public static final String TAG_LAYOUT = "layout"; //$NON-NLS-1$
- public static final String TAG_DATA = "data"; //$NON-NLS-1$
- public static final String TAG_VARIABLE = "variable"; //$NON-NLS-1$
- public static final String TAG_IMPORT = "import"; //$NON-NLS-1$
-
- // Attributes: Manifest
- public static final String ATTR_EXPORTED = "exported"; //$NON-NLS-1$
- public static final String ATTR_PERMISSION = "permission"; //$NON-NLS-1$
- public static final String ATTR_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-1$
- public static final String ATTR_TARGET_SDK_VERSION = "targetSdkVersion"; //$NON-NLS-1$
- public static final String ATTR_ICON = "icon"; //$NON-NLS-1$
- public static final String ATTR_PACKAGE = "package"; //$NON-NLS-1$
- public static final String ATTR_CORE_APP = "coreApp"; //$NON-NLS-1$
- public static final String ATTR_THEME = "theme"; //$NON-NLS-1$
- public static final String ATTR_SCHEME = "scheme"; //$NON_NLS-1$
- public static final String ATTR_HOST = "host"; //$NON_NLS-1$
- public static final String ATTR_PATH = "path"; //$NON-NLS-1$
- public static final String ATTR_PATH_PREFIX = "pathPrefix"; //$NON-NLS-1$
- public static final String ATTR_PATH_PATTERN = "pathPattern"; //$NON-NLS-1$
- public static final String ATTR_ALLOW_BACKUP = "allowBackup"; //$NON_NLS-1$
- public static final String ATTR_DEBUGGABLE = "debuggable"; //$NON-NLS-1$
- public static final String ATTR_READ_PERMISSION = "readPermission"; //$NON_NLS-1$
- public static final String ATTR_WRITE_PERMISSION = "writePermission"; //$NON_NLS-1$
- public static final String ATTR_VERSION_CODE = "versionCode"; //$NON_NLS-1$
- public static final String ATTR_VERSION_NAME = "versionName"; //$NON_NLS-1$
-
- // Attributes: Resources
- public static final String ATTR_NAME = "name"; //$NON-NLS-1$
- public static final String ATTR_FRAGMENT = "fragment"; //$NON-NLS-1$
- public static final String ATTR_TYPE = "type"; //$NON-NLS-1$
- public static final String ATTR_PARENT = "parent"; //$NON-NLS-1$
- public static final String ATTR_TRANSLATABLE = "translatable"; //$NON-NLS-1$
- public static final String ATTR_COLOR = "color"; //$NON-NLS-1$
- public static final String ATTR_DRAWABLE = "drawable"; //$NON-NLS-1$
- public static final String ATTR_VALUE = "value"; //$NON-NLS-1$
- public static final String ATTR_QUANTITY = "quantity"; //$NON-NLS-1$
- public static final String ATTR_FORMAT = "format"; //$NON-NLS-1$
- public static final String ATTR_PREPROCESSING = "preprocessing"; //$NON-NLS-1$
-
- // Attributes: Data-Binding
- public static final String ATTR_ALIAS = "alias"; //$NON-NLS-1$
-
- // Attributes: Layout
- public static final String ATTR_LAYOUT_RESOURCE_PREFIX = "layout_";//$NON-NLS-1$
- public static final String ATTR_CLASS = "class"; //$NON-NLS-1$
- public static final String ATTR_STYLE = "style"; //$NON-NLS-1$
- public static final String ATTR_CONTEXT = "context"; //$NON-NLS-1$
- public static final String ATTR_ID = "id"; //$NON-NLS-1$
- public static final String ATTR_TEXT = "text"; //$NON-NLS-1$
- public static final String ATTR_TEXT_SIZE = "textSize"; //$NON-NLS-1$
- public static final String ATTR_LABEL = "label"; //$NON-NLS-1$
- public static final String ATTR_HINT = "hint"; //$NON-NLS-1$
- public static final String ATTR_PROMPT = "prompt"; //$NON-NLS-1$
- public static final String ATTR_ON_CLICK = "onClick"; //$NON-NLS-1$
- public static final String ATTR_INPUT_TYPE = "inputType"; //$NON-NLS-1$
- public static final String ATTR_INPUT_METHOD = "inputMethod"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_WIDTH = "layout_width"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_WEIGHT = "layout_weight"; //$NON-NLS-1$
- public static final String ATTR_PADDING = "padding"; //$NON-NLS-1$
- public static final String ATTR_PADDING_BOTTOM = "paddingBottom"; //$NON-NLS-1$
- public static final String ATTR_PADDING_TOP = "paddingTop"; //$NON-NLS-1$
- public static final String ATTR_PADDING_RIGHT = "paddingRight"; //$NON-NLS-1$
- public static final String ATTR_PADDING_LEFT = "paddingLeft"; //$NON-NLS-1$
- public static final String ATTR_PADDING_START = "paddingStart"; //$NON-NLS-1$
- public static final String ATTR_PADDING_END = "paddingEnd"; //$NON-NLS-1$
- public static final String ATTR_FOREGROUND = "foreground"; //$NON-NLS-1$
- public static final String ATTR_BACKGROUND = "background"; //$NON-NLS-1$
- public static final String ATTR_ORIENTATION = "orientation"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$
- public static final String ATTR_ROW_COUNT = "rowCount"; //$NON-NLS-1$
- public static final String ATTR_COLUMN_COUNT = "columnCount"; //$NON-NLS-1$
- public static final String ATTR_LABEL_FOR = "labelFor"; //$NON-NLS-1$
- public static final String ATTR_BASELINE_ALIGNED = "baselineAligned"; //$NON-NLS-1$
- public static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; //$NON-NLS-1$
- public static final String ATTR_IME_ACTION_LABEL = "imeActionLabel"; //$NON-NLS-1$
- public static final String ATTR_PRIVATE_IME_OPTIONS = "privateImeOptions"; //$NON-NLS-1$
- public static final String VALUE_NONE = "none"; //$NON-NLS-1$
- public static final String VALUE_NO = "no"; //$NON-NLS-1$
- public static final String ATTR_NUMERIC = "numeric"; //$NON-NLS-1$
- public static final String ATTR_IME_ACTION_ID = "imeActionId"; //$NON-NLS-1$
- public static final String ATTR_IME_OPTIONS = "imeOptions"; //$NON-NLS-1$
- public static final String ATTR_FREEZES_TEXT = "freezesText"; //$NON-NLS-1$
- public static final String ATTR_EDITOR_EXTRAS = "editorExtras"; //$NON-NLS-1$
- public static final String ATTR_EDITABLE = "editable"; //$NON-NLS-1$
- public static final String ATTR_DIGITS = "digits"; //$NON-NLS-1$
- public static final String ATTR_CURSOR_VISIBLE = "cursorVisible"; //$NON-NLS-1$
- public static final String ATTR_CAPITALIZE = "capitalize"; //$NON-NLS-1$
- public static final String ATTR_PHONE_NUMBER = "phoneNumber"; //$NON-NLS-1$
- public static final String ATTR_PASSWORD = "password"; //$NON-NLS-1$
- public static final String ATTR_BUFFER_TYPE = "bufferType"; //$NON-NLS-1$
- public static final String ATTR_AUTO_TEXT = "autoText"; //$NON-NLS-1$
- public static final String ATTR_ENABLED = "enabled"; //$NON-NLS-1$
- public static final String ATTR_SINGLE_LINE = "singleLine"; //$NON-NLS-1$
- public static final String ATTR_SCALE_TYPE = "scaleType"; //$NON-NLS-1$
- public static final String ATTR_VISIBILITY = "visibility"; //$NON-NLS-1$
- public static final String ATTR_TEXT_IS_SELECTABLE =
- "textIsSelectable"; //$NON-NLS-1$
- public static final String ATTR_IMPORTANT_FOR_ACCESSIBILITY =
- "importantForAccessibility"; //$NON-NLS-1$
- public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT =
- "listPreferredItemPaddingLeft"; //$NON-NLS-1$
- public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT =
- "listPreferredItemPaddingRight"; //$NON-NLS-1$
- public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_START =
- "listPreferredItemPaddingStart"; //$NON-NLS-1$
- public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_END =
- "listPreferredItemPaddingEnd"; //$NON-NLS-1$
- public static final String ATTR_INDEX = "index"; //$NON-NLS-1$
-
- // AbsoluteLayout layout params
- public static final String ATTR_LAYOUT_Y = "layout_y"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_X = "layout_x"; //$NON-NLS-1$
-
- // GridLayout layout params
- public static final String ATTR_LAYOUT_ROW = "layout_row"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ROW_SPAN = "layout_rowSpan";//$NON-NLS-1$
- public static final String ATTR_LAYOUT_COLUMN = "layout_column"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_COLUMN_SPAN = "layout_columnSpan"; //$NON-NLS-1$
-
- // TableRow
- public static final String ATTR_LAYOUT_SPAN = "layout_span"; //$NON-NLS-1$
-
- // RelativeLayout layout params:
- public static final String ATTR_LAYOUT_ALIGN_LEFT = "layout_alignLeft"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_RIGHT = "layout_alignRight"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_START = "layout_alignStart"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_END = "layout_alignEnd"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_TOP = "layout_alignTop"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_BOTTOM = "layout_alignBottom"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_PARENT_LEFT = "layout_alignParentLeft"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_PARENT_RIGHT = "layout_alignParentRight"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_PARENT_START = "layout_alignParentStart"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_PARENT_END = "layout_alignParentEnd"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_PARENT_TOP = "layout_alignParentTop"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_PARENT_BOTTOM = "layout_alignParentBottom"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING = "layout_alignWithParentIfMissing"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ALIGN_BASELINE = "layout_alignBaseline"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_CENTER_IN_PARENT = "layout_centerInParent"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_CENTER_VERTICAL = "layout_centerVertical"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_CENTER_HORIZONTAL = "layout_centerHorizontal"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_TO_RIGHT_OF = "layout_toRightOf"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_TO_LEFT_OF = "layout_toLeftOf"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_TO_START_OF = "layout_toStartOf"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_TO_END_OF = "layout_toEndOf"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_BELOW = "layout_below"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ABOVE = "layout_above"; //$NON-NLS-1$
-
- // Margins
- public static final String ATTR_LAYOUT_MARGIN = "layout_margin"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_MARGIN_LEFT = "layout_marginLeft"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_MARGIN_RIGHT = "layout_marginRight"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_MARGIN_START = "layout_marginStart"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_MARGIN_END = "layout_marginEnd"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_MARGIN_TOP = "layout_marginTop"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_MARGIN_BOTTOM = "layout_marginBottom"; //$NON-NLS-1$
-
- // Attributes: Drawables
- public static final String ATTR_TILE_MODE = "tileMode"; //$NON-NLS-1$
-
- // Attributes: CoordinatorLayout
- public static final String ATTR_LAYOUT_ANCHOR = "layout_anchor"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_ANCHOR_GRAVITY = "layout_anchorGravity"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_BEHAVIOR = "layout_behavior"; //$NON-NLS-1$
- public static final String ATTR_LAYOUT_KEYLINE = "layout_keyline"; //$NON-NLS-1$
-
- // Values: Manifest
- public static final String VALUE_SPLIT_ACTION_BAR_WHEN_NARROW = "splitActionBarWhenNarrow"; // NON-NLS-$1
-
- // Values: Layouts
- public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$
- public static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$
- public static final String VALUE_VERTICAL = "vertical"; //$NON-NLS-1$
- public static final String VALUE_TRUE = "true"; //$NON-NLS-1$
- public static final String VALUE_EDITABLE = "editable"; //$NON-NLS-1$
- public static final String VALUE_AUTO_FIT = "auto_fit"; //$NON-NLS-1$
- public static final String VALUE_SELECTABLE_ITEM_BACKGROUND =
- "?android:attr/selectableItemBackground"; //$NON-NLS-1$
-
- // Values: Resources
- public static final String VALUE_ID = "id"; //$NON-NLS-1$
-
- // Values: Drawables
- public static final String VALUE_DISABLED = "disabled"; //$NON-NLS-1$
- public static final String VALUE_CLAMP = "clamp"; //$NON-NLS-1$
-
- // Menus
- public static final String ATTR_SHOW_AS_ACTION = "showAsAction"; //$NON-NLS-1$
- public static final String ATTR_TITLE = "title"; //$NON-NLS-1$
- public static final String ATTR_VISIBLE = "visible"; //$NON-NLS-1$
- public static final String VALUE_IF_ROOM = "ifRoom"; //$NON-NLS-1$
- public static final String VALUE_ALWAYS = "always"; //$NON-NLS-1$
-
- // Units
- public static final String UNIT_DP = "dp"; //$NON-NLS-1$
- public static final String UNIT_DIP = "dip"; //$NON-NLS-1$
- public static final String UNIT_SP = "sp"; //$NON-NLS-1$
- public static final String UNIT_PX = "px"; //$NON-NLS-1$
- public static final String UNIT_IN = "in"; //$NON-NLS-1$
- public static final String UNIT_MM = "mm"; //$NON-NLS-1$
- public static final String UNIT_PT = "pt"; //$NON-NLS-1$
-
- // Filenames and folder names
- public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$
- public static final String OLD_PROGUARD_FILE = "proguard.cfg"; //$NON-NLS-1$
- public static final String CLASS_FOLDER =
- "bin" + File.separator + "classes"; //$NON-NLS-1$ //$NON-NLS-2$
- public static final String GEN_FOLDER = "gen"; //$NON-NLS-1$
- public static final String SRC_FOLDER = "src"; //$NON-NLS-1$
- public static final String LIBS_FOLDER = "libs"; //$NON-NLS-1$
- public static final String BIN_FOLDER = "bin"; //$NON-NLS-1$
-
- public static final String RES_FOLDER = "res"; //$NON-NLS-1$
- public static final String DOT_XML = ".xml"; //$NON-NLS-1$
- public static final String DOT_XSD = ".xsd"; //$NON-NLS-1$
- public static final String DOT_GIF = ".gif"; //$NON-NLS-1$
- public static final String DOT_JPG = ".jpg"; //$NON-NLS-1$
- public static final String DOT_JPEG = ".jpeg"; //$NON-NLS-1$
- public static final String DOT_WEBP = ".webp"; //$NON-NLS-1$
- public static final String DOT_PNG = ".png"; //$NON-NLS-1$
- public static final String DOT_9PNG = ".9.png"; //$NON-NLS-1$
- public static final String DOT_JAVA = ".java"; //$NON-NLS-1$
- public static final String DOT_CLASS = ".class"; //$NON-NLS-1$
- public static final String DOT_JAR = ".jar"; //$NON-NLS-1$
- public static final String DOT_GRADLE = ".gradle"; //$NON-NLS-1$
- public static final String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$
-
- /** Extension of the Application package Files, i.e. "apk". */
- public static final String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$
- /** Extension of java files, i.e. "java" */
- public static final String EXT_JAVA = "java"; //$NON-NLS-1$
- /** Extension of compiled java files, i.e. "class" */
- public static final String EXT_CLASS = "class"; //$NON-NLS-1$
- /** Extension of xml files, i.e. "xml" */
- public static final String EXT_XML = "xml"; //$NON-NLS-1$
- /** Extension of gradle files, i.e. "gradle" */
- public static final String EXT_GRADLE = "gradle"; //$NON-NLS-1$
- /** Extension of jar files, i.e. "jar" */
- public static final String EXT_JAR = "jar"; //$NON-NLS-1$
- /** Extension of ZIP files, i.e. "zip" */
- public static final String EXT_ZIP = "zip"; //$NON-NLS-1$
- /** Extension of aidl files, i.e. "aidl" */
- public static final String EXT_AIDL = "aidl"; //$NON-NLS-1$
- /** Extension of Renderscript files, i.e. "rs" */
- public static final String EXT_RS = "rs"; //$NON-NLS-1$
- /** Extension of Renderscript files, i.e. "rsh" */
- public static final String EXT_RSH = "rsh"; //$NON-NLS-1$
- /** Extension of FilterScript files, i.e. "fs" */
- public static final String EXT_FS = "fs"; //$NON-NLS-1$
- /** Extension of Renderscript bitcode files, i.e. "bc" */
- public static final String EXT_BC = "bc"; //$NON-NLS-1$
- /** Extension of dependency files, i.e. "d" */
- public static final String EXT_DEP = "d"; //$NON-NLS-1$
- /** Extension of native libraries, i.e. "so" */
- public static final String EXT_NATIVE_LIB = "so"; //$NON-NLS-1$
- /** Extension of dex files, i.e. "dex" */
- public static final String EXT_DEX = "dex"; //$NON-NLS-1$
- /** Extension for temporary resource files, ie "ap_ */
- public static final String EXT_RES = "ap_"; //$NON-NLS-1$
- /** Extension for pre-processable images. Right now pngs */
- public static final String EXT_PNG = "png"; //$NON-NLS-1$
- /** Extension for Android archive files */
- public static final String EXT_AAR = "aar"; //$NON-NLS-1$
- /** Extension for Java heap dumps. */
- public static final String EXT_HPROF = "hprof"; //$NON-NLS-1$
-
- private static final String DOT = "."; //$NON-NLS-1$
-
- /** Dot-Extension of the Application package Files, i.e. ".apk". */
- public static final String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE;
- /** Dot-Extension of aidl files, i.e. ".aidl" */
- public static final String DOT_AIDL = DOT + EXT_AIDL;
- /** Dot-Extension of renderscript files, i.e. ".rs" */
- public static final String DOT_RS = DOT + EXT_RS;
- /** Dot-Extension of renderscript header files, i.e. ".rsh" */
- public static final String DOT_RSH = DOT + EXT_RSH;
- /** Dot-Extension of FilterScript files, i.e. ".fs" */
- public static final String DOT_FS = DOT + EXT_FS;
- /** Dot-Extension of renderscript bitcode files, i.e. ".bc" */
- public static final String DOT_BC = DOT + EXT_BC;
- /** Dot-Extension of dependency files, i.e. ".d" */
- public static final String DOT_DEP = DOT + EXT_DEP;
- /** Dot-Extension of dex files, i.e. ".dex" */
- public static final String DOT_DEX = DOT + EXT_DEX;
- /** Dot-Extension for temporary resource files, ie "ap_ */
- public static final String DOT_RES = DOT + EXT_RES;
- /** Dot-Extension for BMP files, i.e. ".bmp" */
- public static final String DOT_BMP = ".bmp"; //$NON-NLS-1$
- /** Dot-Extension for SVG files, i.e. ".svg" */
- public static final String DOT_SVG = ".svg"; //$NON-NLS-1$
- /** Dot-Extension for template files */
- public static final String DOT_FTL = ".ftl"; //$NON-NLS-1$
- /** Dot-Extension of text files, i.e. ".txt" */
- public static final String DOT_TXT = ".txt"; //$NON-NLS-1$
- /** Dot-Extension for Android archive files */
- public static final String DOT_AAR = DOT + EXT_AAR; //$NON-NLS-1$
- /** Dot-Extension for Java heap dumps. */
- public static final String DOT_HPROF = DOT + EXT_HPROF; //$NON-NLS-1$
-
- /** Resource base name for java files and classes */
- public static final String FN_RESOURCE_BASE = "R"; //$NON-NLS-1$
- /** Resource java class filename, i.e. "R.java" */
- public static final String FN_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_JAVA;
- /** Resource class file filename, i.e. "R.class" */
- public static final String FN_COMPILED_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_CLASS;
- /** Resource text filename, i.e. "R.txt" */
- public static final String FN_RESOURCE_TEXT = FN_RESOURCE_BASE + DOT_TXT;
- /** Filename for public resources in AAR archives */
- public static final String FN_PUBLIC_TXT = "public.txt";
- /** Generated manifest class name */
- public static final String FN_MANIFEST_BASE = "Manifest"; //$NON-NLS-1$
- /** Generated BuildConfig class name */
- public static final String FN_BUILD_CONFIG_BASE = "BuildConfig"; //$NON-NLS-1$
- /** Manifest java class filename, i.e. "Manifest.java" */
- public static final String FN_MANIFEST_CLASS = FN_MANIFEST_BASE + DOT_JAVA;
- /** BuildConfig java class filename, i.e. "BuildConfig.java" */
- public static final String FN_BUILD_CONFIG = FN_BUILD_CONFIG_BASE + DOT_JAVA;
-
- public static final String DRAWABLE_FOLDER = "drawable"; //$NON-NLS-1$
- public static final String DRAWABLE_XHDPI = "drawable-xhdpi"; //$NON-NLS-1$
- public static final String DRAWABLE_XXHDPI = "drawable-xxhdpi"; //$NON-NLS-1$
- public static final String DRAWABLE_XXXHDPI = "drawable-xxxhdpi"; //$NON-NLS-1$
- public static final String DRAWABLE_HDPI = "drawable-hdpi"; //$NON-NLS-1$
- public static final String DRAWABLE_MDPI = "drawable-mdpi"; //$NON-NLS-1$
- public static final String DRAWABLE_LDPI = "drawable-ldpi"; //$NON-NLS-1$
-
- // Resources
- public static final String PREFIX_RESOURCE_REF = "@"; //$NON-NLS-1$
- public static final String PREFIX_THEME_REF = "?"; //$NON-NLS-1$
- public static final String PREFIX_BINDING_EXPR = "@{"; //$NON-NLS-1$
- public static final String ANDROID_PREFIX = "@android:"; //$NON-NLS-1$
- public static final String ANDROID_THEME_PREFIX = "?android:"; //$NON-NLS-1$
- public static final String LAYOUT_RESOURCE_PREFIX = "@layout/"; //$NON-NLS-1$
- public static final String STYLE_RESOURCE_PREFIX = "@style/"; //$NON-NLS-1$
- public static final String COLOR_RESOURCE_PREFIX = "@color/"; //$NON-NLS-1$
- public static final String NEW_ID_PREFIX = "@+id/"; //$NON-NLS-1$
- public static final String ID_PREFIX = "@id/"; //$NON-NLS-1$
- public static final String DRAWABLE_PREFIX = "@drawable/"; //$NON-NLS-1$
- public static final String STRING_PREFIX = "@string/"; //$NON-NLS-1$
- public static final String DIMEN_PREFIX = "@dimen/"; //$NON-NLS-1$
- public static final String MIPMAP_PREFIX = "@mipmap/"; //$NON-NLS-1$
-
- public static final String ANDROID_LAYOUT_RESOURCE_PREFIX = "@android:layout/"; //$NON-NLS-1$
- public static final String ANDROID_STYLE_RESOURCE_PREFIX = "@android:style/"; //$NON-NLS-1$
- public static final String ANDROID_COLOR_RESOURCE_PREFIX = "@android:color/"; //$NON-NLS-1$
- public static final String ANDROID_NEW_ID_PREFIX = "@android:+id/"; //$NON-NLS-1$
- public static final String ANDROID_ID_PREFIX = "@android:id/"; //$NON-NLS-1$
- public static final String ANDROID_DRAWABLE_PREFIX = "@android:drawable/"; //$NON-NLS-1$
- public static final String ANDROID_STRING_PREFIX = "@android:string/"; //$NON-NLS-1$
-
- public static final String RESOURCE_CLZ_ID = "id"; //$NON-NLS-1$
- public static final String RESOURCE_CLZ_COLOR = "color"; //$NON-NLS-1$
- public static final String RESOURCE_CLZ_ARRAY = "array"; //$NON-NLS-1$
- public static final String RESOURCE_CLZ_ATTR = "attr"; //$NON-NLS-1$
- public static final String RESOURCE_CLR_STYLEABLE = "styleable"; //$NON-NLS-1$
- public static final String NULL_RESOURCE = "@null"; //$NON-NLS-1$
- public static final String TRANSPARENT_COLOR = "@android:color/transparent"; //$NON-NLS-1$
- public static final String REFERENCE_STYLE = "style/"; //$NON-NLS-1$
- public static final String PREFIX_ANDROID = "android:"; //$NON-NLS-1$
-
- // Resource Types
- public static final String DRAWABLE_TYPE = "drawable"; //$NON-NLS-1$
- public static final String MENU_TYPE = "menu"; //$NON-NLS-1$
-
- // Packages
- public static final String ANDROID_PKG_PREFIX = "android."; //$NON-NLS-1$
- public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$
- public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$
-
- // Project properties
- public static final String ANDROID_LIBRARY = "android.library"; //$NON-NLS-1$
- public static final String PROGUARD_CONFIG = "proguard.config"; //$NON-NLS-1$
- public static final String ANDROID_LIBRARY_REFERENCE_FORMAT = "android.library.reference.%1$d";//$NON-NLS-1$
- public static final String PROJECT_PROPERTIES = "project.properties";//$NON-NLS-1$
-
- // Java References
- public static final String ATTR_REF_PREFIX = "?attr/"; //$NON-NLS-1$
- public static final String R_PREFIX = "R."; //$NON-NLS-1$
- public static final String R_ID_PREFIX = "R.id."; //$NON-NLS-1$
- public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$
- public static final String R_DRAWABLE_PREFIX = "R.drawable."; //$NON-NLS-1$
- public static final String R_STYLEABLE_PREFIX = "R.styleable."; //$NON-NLS-1$
- public static final String R_ATTR_PREFIX = "R.attr."; //$NON-NLS-1$
-
- // Attributes related to tools
- public static final String ATTR_IGNORE = "ignore"; //$NON-NLS-1$
- public static final String ATTR_LOCALE = "locale"; //$NON-NLS-1$
-
- // SuppressLint
- public static final String SUPPRESS_ALL = "all"; //$NON-NLS-1$
- public static final String SUPPRESS_LINT = "SuppressLint"; //$NON-NLS-1$
- public static final String TARGET_API = "TargetApi"; //$NON-NLS-1$
- public static final String ATTR_TARGET_API = "targetApi"; //$NON-NLS-1$
- public static final String FQCN_SUPPRESS_LINT = "android.annotation." + SUPPRESS_LINT; //$NON-NLS-1$
- public static final String FQCN_TARGET_API = "android.annotation." + TARGET_API; //$NON-NLS-1$
-
- // Class Names
- public static final String CONSTRUCTOR_NAME = "<init>"; //$NON-NLS-1$
- public static final String CLASS_CONSTRUCTOR = "<clinit>"; //$NON-NLS-1$
- public static final String FRAGMENT = "android/app/Fragment"; //$NON-NLS-1$
- public static final String FRAGMENT_V4 = "android/support/v4/app/Fragment"; //$NON-NLS-1$
- public static final String ANDROID_APP_ACTIVITY = "android/app/Activity"; //$NON-NLS-1$
- public static final String ANDROID_APP_SERVICE = "android/app/Service"; //$NON-NLS-1$
- public static final String ANDROID_CONTENT_CONTENT_PROVIDER =
- "android/content/ContentProvider"; //$NON-NLS-1$
- public static final String ANDROID_CONTENT_BROADCAST_RECEIVER =
- "android/content/BroadcastReceiver"; //$NON-NLS-1$
- public static final String ANDROID_VIEW_VIEW = "android/view/View"; //$NON-NLS-1$
-
- // Method Names
- public static final String FORMAT_METHOD = "format"; //$NON-NLS-1$
- public static final String GET_STRING_METHOD = "getString"; //$NON-NLS-1$
-
-
-
-
- public static final String ATTR_TAG = "tag"; //$NON-NLS-1$
- public static final String ATTR_NUM_COLUMNS = "numColumns"; //$NON-NLS-1$
-
- // Some common layout element names
- public static final String CALENDAR_VIEW = "CalendarView"; //$NON-NLS-1$
- public static final String SPACE = "Space"; //$NON-NLS-1$
- public static final String GESTURE_OVERLAY_VIEW = "GestureOverlayView";//$NON-NLS-1$
-
- public static final String ATTR_HANDLE = "handle"; //$NON-NLS-1$
- public static final String ATTR_CONTENT = "content"; //$NON-NLS-1$
- public static final String ATTR_CHECKED = "checked"; //$NON-NLS-1$
-
- // TextView
- public static final String ATTR_DRAWABLE_RIGHT = "drawableRight"; //$NON-NLS-1$
- public static final String ATTR_DRAWABLE_LEFT = "drawableLeft"; //$NON-NLS-1$
- public static final String ATTR_DRAWABLE_START = "drawableStart"; //$NON-NLS-1$
- public static final String ATTR_DRAWABLE_END = "drawableEnd"; //$NON-NLS-1$
- public static final String ATTR_DRAWABLE_BOTTOM = "drawableBottom"; //$NON-NLS-1$
- public static final String ATTR_DRAWABLE_TOP = "drawableTop"; //$NON-NLS-1$
- public static final String ATTR_DRAWABLE_PADDING = "drawablePadding"; //$NON-NLS-1$
-
- public static final String ATTR_USE_DEFAULT_MARGINS = "useDefaultMargins"; //$NON-NLS-1$
- public static final String ATTR_MARGINS_INCLUDED_IN_ALIGNMENT = "marginsIncludedInAlignment"; //$NON-NLS-1$
-
- public static final String VALUE_WRAP_CONTENT = "wrap_content"; //$NON-NLS-1$
- public static final String VALUE_FALSE= "false"; //$NON-NLS-1$
- public static final String VALUE_N_DP = "%ddp"; //$NON-NLS-1$
- public static final String VALUE_ZERO_DP = "0dp"; //$NON-NLS-1$
- public static final String VALUE_ONE_DP = "1dp"; //$NON-NLS-1$
- public static final String VALUE_TOP = "top"; //$NON-NLS-1$
- public static final String VALUE_BOTTOM = "bottom"; //$NON-NLS-1$
- public static final String VALUE_CENTER_VERTICAL = "center_vertical"; //$NON-NLS-1$
- public static final String VALUE_CENTER_HORIZONTAL = "center_horizontal"; //$NON-NLS-1$
- public static final String VALUE_FILL_HORIZONTAL = "fill_horizontal"; //$NON-NLS-1$
- public static final String VALUE_FILL_VERTICAL = "fill_vertical"; //$NON-NLS-1$
- public static final String VALUE_0 = "0"; //$NON-NLS-1$
- public static final String VALUE_1 = "1"; //$NON-NLS-1$
-
- // Gravity values. These have the GRAVITY_ prefix in front of value because we already
- // have VALUE_CENTER_HORIZONTAL defined for layouts, and its definition conflicts
- // (centerHorizontal versus center_horizontal)
- public static final String GRAVITY_VALUE_ = "center"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_CENTER = "center"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_LEFT = "left"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_RIGHT = "right"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_START = "start"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_END = "end"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_BOTTOM = "bottom"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_TOP = "top"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_FILL_HORIZONTAL = "fill_horizontal"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_FILL_VERTICAL = "fill_vertical"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_CENTER_HORIZONTAL = "center_horizontal"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_CENTER_VERTICAL = "center_vertical"; //$NON-NLS-1$
- public static final String GRAVITY_VALUE_FILL = "fill"; //$NON-NLS-1$
-
- /**
- * The top level android package as a prefix, "android.".
- */
- public static final String ANDROID_SUPPORT_PKG_PREFIX = ANDROID_PKG_PREFIX + "support."; //$NON-NLS-1$
-
- /** The android.view. package prefix */
- public static final String ANDROID_VIEW_PKG = ANDROID_PKG_PREFIX + "view."; //$NON-NLS-1$
-
- /** The android.widget. package prefix */
- public static final String ANDROID_WIDGET_PREFIX = ANDROID_PKG_PREFIX + "widget."; //$NON-NLS-1$
-
- /** The android.webkit. package prefix */
- public static final String ANDROID_WEBKIT_PKG = ANDROID_PKG_PREFIX + "webkit."; //$NON-NLS-1$
-
- /** The LayoutParams inner-class name suffix, .LayoutParams */
- public static final String DOT_LAYOUT_PARAMS = ".LayoutParams"; //$NON-NLS-1$
-
- /** The fully qualified class name of an EditText view */
- public static final String FQCN_EDIT_TEXT = "android.widget.EditText"; //$NON-NLS-1$
-
- /** The fully qualified class name of a LinearLayout view */
- public static final String FQCN_LINEAR_LAYOUT = "android.widget.LinearLayout"; //$NON-NLS-1$
-
- /** The fully qualified class name of a RelativeLayout view */
- public static final String FQCN_RELATIVE_LAYOUT = "android.widget.RelativeLayout"; //$NON-NLS-1$
-
- /** The fully qualified class name of a RelativeLayout view */
- public static final String FQCN_GRID_LAYOUT = "android.widget.GridLayout"; //$NON-NLS-1$
- public static final String FQCN_GRID_LAYOUT_V7 = "android.support.v7.widget.GridLayout"; //$NON-NLS-1$
-
- /** The fully qualified class name of a FrameLayout view */
- public static final String FQCN_FRAME_LAYOUT = "android.widget.FrameLayout"; //$NON-NLS-1$
-
- /** The fully qualified class name of a TableRow view */
- public static final String FQCN_TABLE_ROW = "android.widget.TableRow"; //$NON-NLS-1$
-
- /** The fully qualified class name of a TableLayout view */
- public static final String FQCN_TABLE_LAYOUT = "android.widget.TableLayout"; //$NON-NLS-1$
-
- /** The fully qualified class name of a GridView view */
- public static final String FQCN_GRID_VIEW = "android.widget.GridView"; //$NON-NLS-1$
-
- /** The fully qualified class name of a TabWidget view */
- public static final String FQCN_TAB_WIDGET = "android.widget.TabWidget"; //$NON-NLS-1$
-
- /** The fully qualified class name of a Button view */
- public static final String FQCN_BUTTON = "android.widget.Button"; //$NON-NLS-1$
-
- /** The fully qualified class name of a RadioButton view */
- public static final String FQCN_RADIO_BUTTON = "android.widget.RadioButton"; //$NON-NLS-1$
-
- /** The fully qualified class name of a ToggleButton view */
- public static final String FQCN_TOGGLE_BUTTON = "android.widget.ToggleButton"; //$NON-NLS-1$
-
- /** The fully qualified class name of a Spinner view */
- public static final String FQCN_SPINNER = "android.widget.Spinner"; //$NON-NLS-1$
-
- /** The fully qualified class name of an AdapterView */
- public static final String FQCN_ADAPTER_VIEW = "android.widget.AdapterView"; //$NON-NLS-1$
-
- /** The fully qualified class name of a ListView */
- public static final String FQCN_LIST_VIEW = "android.widget.ListView"; //$NON-NLS-1$
-
- /** The fully qualified class name of an ExpandableListView */
- public static final String FQCN_EXPANDABLE_LIST_VIEW = "android.widget.ExpandableListView"; //$NON-NLS-1$
-
- /** The fully qualified class name of a GestureOverlayView */
- public static final String FQCN_GESTURE_OVERLAY_VIEW = "android.gesture.GestureOverlayView"; //$NON-NLS-1$
-
- /** The fully qualified class name of a DatePicker */
- public static final String FQCN_DATE_PICKER = "android.widget.DatePicker"; //$NON-NLS-1$
-
- /** The fully qualified class name of a TimePicker */
- public static final String FQCN_TIME_PICKER = "android.widget.TimePicker"; //$NON-NLS-1$
-
- /** The fully qualified class name of a RadioGroup */
- public static final String FQCN_RADIO_GROUP = "android.widgets.RadioGroup"; //$NON-NLS-1$
-
- /** The fully qualified class name of a Space */
- public static final String FQCN_SPACE = "android.widget.Space"; //$NON-NLS-1$
- public static final String FQCN_SPACE_V7 = "android.support.v7.widget.Space"; //$NON-NLS-1$
-
- /** The fully qualified class name of a TextView view */
- public static final String FQCN_TEXT_VIEW = "android.widget.TextView"; //$NON-NLS-1$
-
- /** The fully qualified class name of an ImageView view */
- public static final String FQCN_IMAGE_VIEW = "android.widget.ImageView"; //$NON-NLS-1$
-
- public static final String ATTR_SRC = "src"; //$NON-NLS-1$
-
- public static final String ATTR_GRAVITY = "gravity"; //$NON-NLS-1$
-
- public static final String ATTR_WEIGHT_SUM = "weightSum"; //$NON-NLS-1$
- public static final String ATTR_EMS = "ems"; //$NON-NLS-1$
-
- public static final String VALUE_HORIZONTAL = "horizontal"; //$NON-NLS-1$
-
- public static final String GRADLE_PLUGIN_NAME = "com.android.tools.build:gradle:";
- public static final String GRADLE_MINIMUM_VERSION = "2.2.1";
- public static final String GRADLE_LATEST_VERSION = "2.4";
- public static final String GRADLE_PLUGIN_MINIMUM_VERSION = "1.0.0";
- public static final String GRADLE_PLUGIN_RECOMMENDED_VERSION = "1.3.0";
- public static final String GRADLE_PLUGIN_LATEST_VERSION = GRADLE_PLUGIN_RECOMMENDED_VERSION;
- public static final String MIN_BUILD_TOOLS_VERSION = "19.1.0";
- public static final String SUPPORT_LIB_ARTIFACT = "com.android.support:support-v4";
- public static final String APPCOMPAT_LIB_ARTIFACT = "com.android.support:appcompat-v7";
-
- // Annotations
- public static final String SUPPORT_ANNOTATIONS_PREFIX = "android.support.annotation.";
- public static final String INT_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntDef";
- public static final String STRING_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "StringDef";
- public static final String TYPE_DEF_VALUE_ATTRIBUTE = "value";
- public static final String TYPE_DEF_FLAG_ATTRIBUTE = "flag";
- public static final String FN_ANNOTATIONS_ZIP = "annotations.zip";
-
- // Data Binding MISC
- public static final String DATA_BINDING_LIB_ARTIFACT = "com.android.databinding:library";
- public static final String[] TAGS_DATA_BINDING = new String[]{TAG_VARIABLE,
- TAG_IMPORT, TAG_LAYOUT, TAG_DATA};
- public static final String[] ATTRS_DATA_BINDING = new String[]{ATTR_NAME,
- ATTR_TYPE, ATTR_CLASS, ATTR_ALIAS};
-}
diff --git a/base/common/src/main/java/com/android/ide/common/blame/Message.java b/base/common/src/main/java/com/android/ide/common/blame/Message.java
deleted file mode 100644
index 08f109e..0000000
--- a/base/common/src/main/java/com/android/ide/common/blame/Message.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.Immutable;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.util.List;
-
- at Immutable
-public final class Message {
-
- @NonNull
- private final Kind mKind;
-
- @NonNull
- private final String mText;
-
- @NonNull
- private final List<SourceFilePosition> mSourceFilePositions;
-
- @NonNull
- private final String mRawMessage;
-
- /**
- * Create a new message, which has a {@link Kind}, a String which will be shown to the user and
- * at least one {@link SourceFilePosition}.
- *
- * @param kind the message type.
- * @param text the text of the message.
- * @param sourceFilePosition the first source file position the message .
- * @param sourceFilePositions any additional source file positions, may be empty.
- */
- public Message(@NonNull Kind kind,
- @NonNull String text,
- @NonNull SourceFilePosition sourceFilePosition,
- @NonNull SourceFilePosition... sourceFilePositions) {
- mKind = kind;
- mText = text;
- mRawMessage = text;
- mSourceFilePositions = ImmutableList.<SourceFilePosition>builder()
- .add(sourceFilePosition).add(sourceFilePositions).build();
- }
-
- /**
- * Create a new message, which has a {@link Kind}, a String which will be shown to the user and
- * at least one {@link SourceFilePosition}.
- *
- * It also has a rawMessage, to store the original string for cases when the message is
- * constructed by parsing the output from another tool.
- *
- * @param kind the message kind.
- * @param text a human-readable string explaining the issue.
- * @param rawMessage the original text of the message, usually from an external tool.
- * @param sourceFilePosition the first source file position.
- * @param sourceFilePositions any additional source file positions, may be empty.
- */
- public Message(@NonNull Kind kind,
- @NonNull String text,
- @NonNull String rawMessage,
- @NonNull SourceFilePosition sourceFilePosition,
- @NonNull SourceFilePosition... sourceFilePositions) {
- mKind = kind;
- mText = text;
- mRawMessage = rawMessage;
- mSourceFilePositions = ImmutableList.<SourceFilePosition>builder()
- .add(sourceFilePosition).add(sourceFilePositions).build();
- }
-
- public Message(@NonNull Kind kind,
- @NonNull String text,
- @NonNull String rawMessage,
- @NonNull ImmutableList<SourceFilePosition> positions) {
- mKind = kind;
- mText = text;
- mRawMessage = rawMessage;
-
- if (positions.isEmpty()) {
- mSourceFilePositions = ImmutableList.of(SourceFilePosition.UNKNOWN);
- } else {
- mSourceFilePositions = positions;
- }
- }
-
- @NonNull
- public Kind getKind() {
- return mKind;
- }
-
- @NonNull
- public String getText() {
- return mText;
- }
-
- /**
- * Returns a list of source positions. Will always contain at least one item.
- */
- @NonNull
- public List<SourceFilePosition> getSourceFilePositions() {
- return mSourceFilePositions;
- }
-
- @NonNull
- public String getRawMessage() {
- return mRawMessage;
- }
-
- @Nullable
- public String getSourcePath() {
- File file = mSourceFilePositions.get(0).getFile().getSourceFile();
- if (file == null) {
- return null;
- }
- return file.getAbsolutePath();
- }
-
- /**
- * Returns a legacy 1-based line number.
- */
- @Deprecated
- public int getLineNumber() {
- return mSourceFilePositions.get(0).getPosition().getStartLine() + 1;
- }
-
- /**
- * @return a legacy 1-based column number.
- */
- @Deprecated
- public int getColumn() {
- return mSourceFilePositions.get(0).getPosition().getStartColumn() + 1;
- }
-
- public enum Kind {
- ERROR, WARNING, INFO, STATISTICS, UNKNOWN, SIMPLE;
-
- public static Kind findIgnoringCase(String s, Kind defaultKind) {
- for (Kind kind : values()) {
- if (kind.toString().equalsIgnoreCase(s)) {
- return kind;
- }
- }
- return defaultKind;
- }
-
- @Nullable
- public static Kind findIgnoringCase(String s) {
- return findIgnoringCase(s, null);
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof Message)) {
- return false;
- }
- Message that = (Message) o;
- return Objects.equal(mKind, that.mKind) &&
- Objects.equal(mText, that.mText) &&
- Objects.equal(mSourceFilePositions, that.mSourceFilePositions);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mKind, mText, mSourceFilePositions);
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this).add("kind", mKind).add("text", mText).add("sources",
- mSourceFilePositions).toString();
- }
-}
diff --git a/base/common/src/main/java/com/android/ide/common/blame/SourceFile.java b/base/common/src/main/java/com/android/ide/common/blame/SourceFile.java
deleted file mode 100644
index be1264b..0000000
--- a/base/common/src/main/java/com/android/ide/common/blame/SourceFile.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.Immutable;
-import com.google.common.base.Objects;
-
-import java.io.File;
-
-/**
- * Represents a source file.
- */
- at Immutable
-public final class SourceFile {
-
- public static final SourceFile UNKNOWN = new SourceFile();
-
- @Nullable
- private final File mSourceFile;
-
- /**
- * A human readable description
- *
- * Usually the file name is OK for the short output, but for the manifest merger,
- * where all of the files will be named AndroidManifest.xml the variant name is more useful.
- */
- @Nullable
- private final String mDescription;
-
- @SuppressWarnings("NullableProblems")
- public SourceFile(
- @NonNull File sourceFile,
- @NonNull String description) {
- mSourceFile = sourceFile;
- mDescription = description;
- }
-
- public SourceFile(
- @SuppressWarnings("NullableProblems") @NonNull File sourceFile) {
- mSourceFile = sourceFile;
- mDescription = null;
- }
-
- public SourceFile(
- @SuppressWarnings("NullableProblems") @NonNull String description) {
- mSourceFile = null;
- mDescription = description;
- }
-
- private SourceFile() {
- mSourceFile = null;
- mDescription = null;
- }
-
- @Nullable
- public File getSourceFile() {
- return mSourceFile;
- }
-
- @Nullable
- public String getDescription() {
- return mDescription;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof SourceFile)) {
- return false;
- }
- SourceFile other = (SourceFile) obj;
-
- return Objects.equal(mDescription, other.mDescription) &&
- Objects.equal(mSourceFile, other.mSourceFile);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mSourceFile, mDescription);
- }
-
- @Override
- public String toString() {
- return print(false /* shortFormat */);
- }
-
- public String print(boolean shortFormat) {
- if (mSourceFile == null) {
- if (mDescription == null) {
- return "Unknown source file";
- }
- return mDescription;
- }
- String fileName = mSourceFile.getName();
- String fileDisplayName = shortFormat ? fileName : mSourceFile.getAbsolutePath();
- if (mDescription == null || mDescription.equals(fileName)) {
- return fileDisplayName;
- } else {
- return String.format("[%1$s] %2$s", mDescription, fileDisplayName);
- }
- }
-
-}
diff --git a/base/common/src/main/java/com/android/ide/common/blame/SourceFilePosition.java b/base/common/src/main/java/com/android/ide/common/blame/SourceFilePosition.java
deleted file mode 100644
index e5ce048..0000000
--- a/base/common/src/main/java/com/android/ide/common/blame/SourceFilePosition.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.concurrency.Immutable;
-import com.google.common.base.Objects;
-
-import java.io.File;
-
- at Immutable
-public final class SourceFilePosition {
-
- public static final com.android.ide.common.blame.SourceFilePosition UNKNOWN =
- new SourceFilePosition(SourceFile.UNKNOWN, SourcePosition.UNKNOWN);
-
- @NonNull
- private final SourceFile mSourceFile;
-
- @NonNull
- private final SourcePosition mSourcePosition;
-
- public SourceFilePosition(@NonNull SourceFile sourceFile,
- @NonNull SourcePosition sourcePosition) {
- mSourceFile = sourceFile;
- mSourcePosition = sourcePosition;
- }
-
- public SourceFilePosition(@NonNull File file,
- @NonNull SourcePosition sourcePosition) {
- this(new SourceFile(file), sourcePosition);
- }
-
- @NonNull
- public SourcePosition getPosition() {
- return mSourcePosition;
- }
-
- @NonNull
- public SourceFile getFile() {
- return mSourceFile;
- }
-
- @Override
- public String toString() {
- return print(false);
- }
-
- public String print(boolean shortFormat) {
- if (mSourcePosition.equals(SourcePosition.UNKNOWN)) {
- return mSourceFile.print(shortFormat);
- } else {
- return mSourceFile.print(shortFormat) + ':' + mSourcePosition.toString();
- }
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mSourceFile, mSourcePosition);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof SourceFilePosition)) {
- return false;
- }
- SourceFilePosition other = (SourceFilePosition) obj;
- return Objects.equal(mSourceFile, other.mSourceFile) &&
- Objects.equal(mSourcePosition, other.mSourcePosition);
- }
-}
diff --git a/base/common/src/main/java/com/android/ide/common/blame/SourcePosition.java b/base/common/src/main/java/com/android/ide/common/blame/SourcePosition.java
deleted file mode 100644
index de62180..0000000
--- a/base/common/src/main/java/com/android/ide/common/blame/SourcePosition.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.Immutable;
-import com.google.common.base.Objects;
-
-/**
- * An immutable position in a text file, used in errors to point the user to an issue.
- *
- * Positions that are unknown are represented by -1.
- */
- at Immutable
-public final class SourcePosition {
-
- public static final SourcePosition UNKNOWN = new SourcePosition();
-
- private final int mStartLine, mStartColumn, mStartOffset, mEndLine, mEndColumn, mEndOffset;
-
- public SourcePosition(int startLine, int startColumn, int startOffset,
- int endLine, int endColumn, int endOffset) {
- mStartLine = startLine;
- mStartColumn = startColumn;
- mStartOffset = startOffset;
- mEndLine = endLine;
- mEndColumn = endColumn;
- mEndOffset = endOffset;
- }
-
- public SourcePosition(int lineNumber, int column, int offset) {
- mStartLine = mEndLine = lineNumber;
- mStartColumn = mEndColumn = column;
- mStartOffset = mEndOffset = offset;
- }
-
- private SourcePosition() {
- mStartLine = mStartColumn = mStartOffset = mEndLine = mEndColumn = mEndOffset = -1;
- }
-
- protected SourcePosition(SourcePosition copy) {
- mStartLine = copy.getStartLine();
- mStartColumn = copy.getStartColumn();
- mStartOffset = copy.getStartOffset();
- mEndLine = copy.getEndLine();
- mEndColumn = copy.getEndColumn();
- mEndOffset = copy.getEndOffset();
- }
-
- /**
- * Outputs positions as human-readable formatted strings.
- *
- * e.g.
- * <pre>84
- * 84-86
- * 84:5
- * 84:5-28
- * 85:5-86:47</pre>
- *
- * @return a human readable position.
- */
- @Override
- public String toString() {
- if (mStartLine == -1) {
- return "?";
- }
- StringBuilder sB = new StringBuilder(15);
- sB.append(mStartLine + 1); // Humans think that the first line is line 1.
- if (mStartColumn != -1) {
- sB.append(':');
- sB.append(mStartColumn + 1);
- }
- if (mEndLine != -1) {
-
- if (mEndLine == mStartLine) {
- if (mEndColumn != -1 && mEndColumn != mStartColumn) {
- sB.append('-');
- sB.append(mEndColumn + 1);
- }
- } else {
- sB.append('-');
- sB.append(mEndLine + 1);
- if (mEndColumn != -1) {
- sB.append(':');
- sB.append(mEndColumn + 1);
- }
- }
- }
- return sB.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof SourcePosition)) {
- return false;
- }
- SourcePosition other = (SourcePosition) obj;
-
- return other.mStartLine == mStartLine &&
- other.mStartColumn == mStartColumn &&
- other.mStartOffset == mStartOffset &&
- other.mEndLine == mEndLine &&
- other.mEndColumn == mEndColumn &&
- other.mEndOffset == mEndOffset;
- }
-
- @Override
- public int hashCode() {
- return Objects
- .hashCode(mStartLine, mStartColumn, mStartOffset, mEndLine, mEndColumn, mEndOffset);
- }
-
- public int getStartLine() {
- return mStartLine;
- }
-
- public int getStartColumn() {
- return mStartColumn;
- }
-
- public int getStartOffset() {
- return mStartOffset;
- }
-
-
- public int getEndLine() {
- return mEndLine;
- }
-
- public int getEndColumn() {
- return mEndColumn;
- }
-
- public int getEndOffset() {
- return mEndOffset;
- }
-
-}
diff --git a/base/common/src/main/java/com/android/prefs/AndroidLocation.java b/base/common/src/main/java/com/android/prefs/AndroidLocation.java
deleted file mode 100644
index 16c17a9..0000000
--- a/base/common/src/main/java/com/android/prefs/AndroidLocation.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2008 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.prefs;
-
-import com.android.annotations.NonNull;
-
-import java.io.File;
-
-/**
- * Manages the location of the android files (including emulator files, ddms config, debug keystore)
- */
-public final class AndroidLocation {
-
- /**
- * The name of the .android folder returned by {@link #getFolder()}.
- */
- public static final String FOLDER_DOT_ANDROID = ".android";
-
- /**
- * Virtual Device folder inside the path returned by {@link #getFolder()}
- */
- public static final String FOLDER_AVD = "avd";
-
- /**
- * Throw when the location of the android folder couldn't be found.
- */
- public static final class AndroidLocationException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public AndroidLocationException(String string) {
- super(string);
- }
- }
-
- private static String sPrefsLocation = null;
-
- /**
- * Enum describing which variables to check and whether they should
- * be checked via {@link System#getProperty(String)} or {@link System#getenv()} or both.
- */
- public enum EnvVar {
- ANDROID_SDK_HOME("ANDROID_SDK_HOME", true, true), // both sys prop and env var
- USER_HOME ("user.home", true, false), // sys prop only
- HOME ("HOME", false, true); // env var only
-
- final String mName;
- final boolean mIsSysProp;
- final boolean mIsEnvVar;
-
- EnvVar(String name, boolean isSysProp, boolean isEnvVar) {
- mName = name;
- mIsSysProp = isSysProp;
- mIsEnvVar = isEnvVar;
- }
-
- public String getName() {
- return mName;
- }
- }
-
- /**
- * Returns the folder used to store android related files.
- * @return an OS specific path, terminated by a separator.
- * @throws AndroidLocationException
- */
- @NonNull
- public static final String getFolder() throws AndroidLocationException {
- if (sPrefsLocation == null) {
- String home = findValidPath(new EnvVar[] { EnvVar.ANDROID_SDK_HOME,
- EnvVar.USER_HOME,
- EnvVar.HOME });
-
- // if the above failed, we throw an exception.
- if (home == null) {
- throw new AndroidLocationException(
- "Unable to get the Android SDK home directory.\n" +
- "Make sure the environment variable ANDROID_SDK_HOME is set up.");
- } else {
- sPrefsLocation = home;
- if (!sPrefsLocation.endsWith(File.separator)) {
- sPrefsLocation += File.separator;
- }
- sPrefsLocation += FOLDER_DOT_ANDROID + File.separator;
- }
- }
-
- // make sure the folder exists!
- File f = new File(sPrefsLocation);
- if (f.exists() == false) {
- try {
- f.mkdir();
- } catch (SecurityException e) {
- AndroidLocationException e2 = new AndroidLocationException(String.format(
- "Unable to create folder '%1$s'. " +
- "This is the path of preference folder expected by the Android tools.",
- sPrefsLocation));
- e2.initCause(e);
- throw e2;
- }
- } else if (f.isFile()) {
- throw new AndroidLocationException(sPrefsLocation +
- " is not a directory! " +
- "This is the path of preference folder expected by the Android tools.");
- }
-
- return sPrefsLocation;
- }
-
- /**
- * Resets the folder used to store android related files. For testing.
- */
- public static final void resetFolder() {
- sPrefsLocation = null;
- }
-
- /**
- * Checks a list of system properties and/or system environment variables for validity, and
- * existing director, and returns the first one.
- * @param vars The variables to check. Order does matter.
- * @return the content of the first property/variable that is a valid directory.
- */
- private static String findValidPath(EnvVar... vars) {
- for (EnvVar var : vars) {
- String path;
- if (var.mIsSysProp) {
- path = checkPath(System.getProperty(var.mName));
- if (path != null) {
- return path;
- }
- }
-
- if (var.mIsEnvVar) {
- path = checkPath(System.getenv(var.mName));
- if (path != null) {
- return path;
- }
- }
- }
-
- return null;
- }
-
- private static String checkPath(String path) {
- if (path != null) {
- File f = new File(path);
- if (f.isDirectory()) {
- return path;
- }
- }
- return null;
- }
-}
diff --git a/base/common/src/main/java/com/android/utils/FileUtils.java b/base/common/src/main/java/com/android/utils/FileUtils.java
deleted file mode 100644
index 897300a..0000000
--- a/base/common/src/main/java/com/android/utils/FileUtils.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.utils;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-import com.android.annotations.NonNull;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Iterables;
-import com.google.common.hash.Hashing;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-
-public class FileUtils {
- public static void deleteFolder(final File folder) throws IOException {
- if (!folder.exists()) {
- return;
- }
- File[] files = folder.listFiles();
- if (files != null) { // i.e. is a directory.
- for (final File file : files) {
- deleteFolder(file);
- }
- }
- if (!folder.delete()) {
- throw new IOException(String.format("Could not delete folder %s", folder));
- }
- }
-
- public static void emptyFolder(final File folder) throws IOException {
- deleteFolder(folder);
- if (!folder.mkdirs()) {
- throw new IOException(String.format("Could not create empty folder %s", folder));
- }
- }
-
- public static void copyFile(File from, File to) throws IOException {
- to = new File(to, from.getName());
- if (from.isDirectory()) {
- if (!to.exists()) {
- if (!to.mkdirs()) {
- throw new IOException(String.format("Could not create directory %s", to));
- }
- }
-
- File[] children = from.listFiles();
- if (children != null) {
- for (File child : children) {
- copyFile(child, to);
- }
- }
- } else if (from.isFile()) {
- Files.copy(from, to);
- }
- }
-
- public static File join(File dir, String... paths) {
- return new File(dir, Joiner.on(File.separatorChar).join(paths));
- }
-
- public static String relativePath(@NonNull File file, @NonNull File dir) {
- checkArgument(file.isFile(), "%s is not a file.", file.getPath());
- checkArgument(dir.isDirectory(), "%s is not a directory.", dir.getPath());
-
- return dir.toURI().relativize(file.toURI()).getPath();
- }
-
- public static String sha1(@NonNull File file) throws IOException {
- return Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
- }
-
- public static String getNamesAsCommaSeparatedList(Iterable<File> files) {
- return Joiner.on(", ").join(Iterables.transform(files, GET_NAME));
- }
-
- private static final Function<File, String> GET_NAME = new Function<File, String>() {
- @Override
- public String apply(File file) {
- return file.getName();
- }
- };
-}
diff --git a/base/common/src/main/java/com/android/utils/HtmlBuilder.java b/base/common/src/main/java/com/android/utils/HtmlBuilder.java
deleted file mode 100644
index 085d7e3..0000000
--- a/base/common/src/main/java/com/android/utils/HtmlBuilder.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.utils;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.net.URL;
-
-public class HtmlBuilder {
- @NonNull private final StringBuilder mStringBuilder;
- private String mTableDataExtra;
-
- public HtmlBuilder(@NonNull StringBuilder stringBuilder) {
- mStringBuilder = stringBuilder;
- }
-
- public HtmlBuilder() {
- mStringBuilder = new StringBuilder(100);
- }
-
- public HtmlBuilder openHtmlBody() {
- addHtml("<html><body>");
-
- return this;
- }
-
- public HtmlBuilder closeHtmlBody() {
- addHtml("</body></html>");
-
- return this;
- }
-
- public HtmlBuilder addHtml(@NonNull String html) {
- mStringBuilder.append(html);
-
- return this;
- }
-
- public HtmlBuilder addNbsp() {
- mStringBuilder.append(" ");
-
- return this;
- }
-
- public HtmlBuilder addNbsps(int count) {
- for (int i = 0; i < count; i++) {
- addNbsp();
- }
-
- return this;
- }
-
- public HtmlBuilder newline() {
- mStringBuilder.append("<BR/>");
-
- return this;
- }
-
- public HtmlBuilder newlineIfNecessary() {
- if (!SdkUtils.endsWith(mStringBuilder, "<BR/>")) {
- mStringBuilder.append("<BR/>");
- }
-
- return this;
- }
-
- public HtmlBuilder addLink(@Nullable String textBefore,
- @NonNull String linkText,
- @Nullable String textAfter,
- @NonNull String url) {
- if (textBefore != null) {
- add(textBefore);
- }
-
- addLink(linkText, url);
-
- if (textAfter != null) {
- add(textAfter);
- }
-
- return this;
- }
-
- public HtmlBuilder addLink(@NonNull String text, @NonNull String url) {
- int begin = 0;
- int length = text.length();
- for (; begin < length; begin++) {
- char c = text.charAt(begin);
- if (Character.isWhitespace(c)) {
- mStringBuilder.append(c);
- } else {
- break;
- }
- }
- mStringBuilder.append("<A HREF=\"");
- mStringBuilder.append(url);
- mStringBuilder.append("\">");
-
- XmlUtils.appendXmlTextValue(mStringBuilder, text.trim());
- mStringBuilder.append("</A>");
-
- int end = length - 1;
- for (; end > begin; end--) {
- char c = text.charAt(begin);
- if (Character.isWhitespace(c)) {
- mStringBuilder.append(c);
- }
- }
-
- return this;
- }
-
- public HtmlBuilder add(@NonNull String text) {
- XmlUtils.appendXmlTextValue(mStringBuilder, text);
-
- return this;
- }
-
- @NonNull
- public String getHtml() {
- return mStringBuilder.toString();
- }
-
- public HtmlBuilder beginBold() {
- mStringBuilder.append("<B>");
-
- return this;
- }
-
- public HtmlBuilder endBold() {
- mStringBuilder.append("</B>");
-
- return this;
- }
-
- public HtmlBuilder addBold(String text) {
- beginBold();
- add(text);
- endBold();
-
- return this;
- }
-
- public HtmlBuilder beginItalic() {
- mStringBuilder.append("<I>");
-
- return this;
- }
-
- public HtmlBuilder endItalic() {
- mStringBuilder.append("</I>");
-
- return this;
- }
-
- public HtmlBuilder addItalic(String text) {
- beginItalic();
- add(text);
- endItalic();
-
- return this;
- }
-
- public HtmlBuilder beginDiv() {
- return beginDiv(null);
- }
-
- public HtmlBuilder beginDiv(@Nullable String cssStyle) {
- mStringBuilder.append("<div");
- if (cssStyle != null) {
- mStringBuilder.append(" style=\"");
- mStringBuilder.append(cssStyle);
- mStringBuilder.append("\"");
- }
- mStringBuilder.append('>');
- return this;
- }
-
- public HtmlBuilder endDiv() {
- mStringBuilder.append("</div>");
- return this;
- }
-
- public HtmlBuilder addHeading(@NonNull String text, @NonNull String fontColor) {
- mStringBuilder.append("<font style=\"font-weight:bold; color:").append(fontColor)
- .append(";\">");
- add(text);
- mStringBuilder.append("</font>");
-
- return this;
- }
-
- /**
- * The JEditorPane HTML renderer creates really ugly bulleted lists; the
- * size is hardcoded to use a giant heavy bullet. So, use a definition
- * list instead.
- */
- private static final boolean USE_DD_LISTS = true;
-
- public HtmlBuilder beginList() {
- if (USE_DD_LISTS) {
- mStringBuilder.append("<DL>");
- } else {
- mStringBuilder.append("<UL>");
- }
-
- return this;
- }
-
- public HtmlBuilder endList() {
- if (USE_DD_LISTS) {
- mStringBuilder.append("</DL>");
- } else {
- mStringBuilder.append("</UL>");
- }
-
- return this;
- }
-
- public HtmlBuilder listItem() {
- if (USE_DD_LISTS) {
- mStringBuilder.append("<DD>");
- mStringBuilder.append("-&NBSP;");
- } else {
- mStringBuilder.append("<LI>");
- }
-
- return this;
- }
-
- public HtmlBuilder addImage(URL url, @Nullable String altText) {
- String link = "";
- try {
- link = url.toURI().toURL().toExternalForm();
- }
- catch (Throwable t) {
- // pass
- }
- mStringBuilder.append("<img src='");
- mStringBuilder.append(link);
- mStringBuilder.append("'");
-
- if (altText != null) {
- mStringBuilder.append(" alt=\"");
- mStringBuilder.append(altText);
- mStringBuilder.append("\"");
- }
- mStringBuilder.append(" />");
-
- return this;
- }
-
- public HtmlBuilder addIcon(@Nullable String src) {
- if (src != null) {
- mStringBuilder.append("<img src='");
- mStringBuilder.append(src);
- mStringBuilder.append("' width=16 height=16 border=0 />");
- }
-
- return this;
- }
-
- public HtmlBuilder beginTable(@Nullable String tdExtra) {
- mStringBuilder.append("<table>");
- mTableDataExtra = tdExtra;
- return this;
- }
-
- public HtmlBuilder beginTable() {
- return beginTable(null);
- }
-
- public HtmlBuilder endTable() {
- mStringBuilder.append("</table>");
- return this;
- }
-
- public HtmlBuilder beginTableRow() {
- mStringBuilder.append("<tr>");
- return this;
- }
-
- public HtmlBuilder endTableRow() {
- mStringBuilder.append("</tr>");
- return this;
- }
-
- public HtmlBuilder addTableRow(boolean isHeader, String... columns) {
- if (columns == null || columns.length == 0) {
- return this;
- }
-
- String tag = "t" + (isHeader ? 'h' : 'd');
-
- beginTableRow();
- for (String c : columns) {
- mStringBuilder.append('<');
- mStringBuilder.append(tag);
- if (mTableDataExtra != null) {
- mStringBuilder.append(' ');
- mStringBuilder.append(mTableDataExtra);
- }
- mStringBuilder.append('>');
-
- mStringBuilder.append(c);
-
- mStringBuilder.append("</");
- mStringBuilder.append(tag);
- mStringBuilder.append('>');
- }
- endTableRow();
-
- return this;
- }
-
- public HtmlBuilder addTableRow(String... columns) {
- return addTableRow(false, columns);
- }
-
- @NonNull
- public StringBuilder getStringBuilder() {
- return mStringBuilder;
- }
-}
diff --git a/base/common/src/main/java/com/android/utils/PositionXmlParser.java b/base/common/src/main/java/com/android/utils/PositionXmlParser.java
deleted file mode 100644
index 86ce844..0000000
--- a/base/common/src/main/java/com/android/utils/PositionXmlParser.java
+++ /dev/null
@@ -1,791 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.utils;
-
-import static com.android.SdkConstants.UTF_8;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourcePosition;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Comment;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.Text;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.ext.DefaultHandler2;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * A simple DOM XML parser which can retrieve exact beginning and end offsets
- * (and line and column numbers) for element nodes as well as attribute nodes.
- */
-public class PositionXmlParser {
- private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$
- private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$
- private static final String CONTENT_KEY = "contents"; //$NON-NLS-1$
- private static final String POS_KEY = "offsets"; //$NON-NLS-1$
- private static final String NAMESPACE_PREFIX_FEATURE =
- "http://xml.org/sax/features/namespace-prefixes"; //$NON-NLS-1$
- private static final String NAMESPACE_FEATURE =
- "http://xml.org/sax/features/namespaces"; //$NON-NLS-1$
- private static final String PROVIDE_XMLNS_URIS =
- "http://xml.org/sax/features/xmlns-uris"; //$NON-NLS-1$
- /** See http://www.w3.org/TR/REC-xml/#NT-EncodingDecl */
- private static final Pattern ENCODING_PATTERN =
- Pattern.compile("encoding=['\"](\\S*)['\"]"); //$NON-NLS-1$
- private static final String LOAD_EXTERNAL_DTD =
- "http://apache.org/xml/features/nonvalidating/load-external-dtd";; //$NON-NLS-1$
-
- /**
- * Parses the XML content from the given input stream.
- *
- * @param input the input stream containing the XML to be parsed
- * @param checkDtd whether or not download the DTD and validate it
- * @return the corresponding document
- * @throws ParserConfigurationException if a SAX parser is not available
- * @throws SAXException if the document contains a parsing error
- * @throws IOException if something is seriously wrong. This should not
- * happen since the input source is known to be constructed from
- * a string.
- */
- @NonNull
- public static Document parse(@NonNull InputStream input, boolean checkDtd)
- throws ParserConfigurationException, SAXException, IOException {
- // Read in all the data
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] buf = new byte[1024];
- while (true) {
- int r = input.read(buf);
- if (r == -1) {
- break;
- }
- out.write(buf, 0, r);
- }
- input.close();
- return parse(out.toByteArray(), checkDtd);
- }
-
- /**
- * @see PositionXmlParser#parse(InputStream, boolean)
- */
- @NonNull
- public static Document parse(@NonNull InputStream input)
- throws IOException, SAXException, ParserConfigurationException {
- return parse(input, true);
- }
-
- /**
- * @see PositionXmlParser#parse(byte[], boolean)
- */
- @NonNull
- public static Document parse(@NonNull byte[] data)
- throws ParserConfigurationException, SAXException, IOException {
- return parse(data, true);
- }
-
- /**
- * Parses the XML content from the given byte array
- *
- * @param data the raw XML data (with unknown encoding)
- * @param checkDtd whether or not download the DTD and validate it
- * @return the corresponding document
- * @throws ParserConfigurationException if a SAX parser is not available
- * @throws SAXException if the document contains a parsing error
- * @throws IOException if something is seriously wrong. This should not
- * happen since the input source is known to be constructed from
- * a string.
- */
- @NonNull
- public static Document parse(@NonNull byte[] data, boolean checkDtd)
- throws ParserConfigurationException, SAXException, IOException {
- String xml = getXmlString(data);
- xml = XmlUtils.stripBom(xml);
- return parse(xml, new InputSource(new StringReader(xml)), true, checkDtd);
- }
-
- /**
- * Parses the given XML content.
- *
- * @param xml the XML string to be parsed. This must be in the correct
- * encoding already.
- * @return the corresponding document
- * @throws ParserConfigurationException if a SAX parser is not available
- * @throws SAXException if the document contains a parsing error
- * @throws IOException if something is seriously wrong. This should not
- * happen since the input source is known to be constructed from
- * a string.
- */
- @NonNull
- public static Document parse(@NonNull String xml)
- throws ParserConfigurationException, SAXException, IOException {
- xml = XmlUtils.stripBom(xml);
- return parse(xml, new InputSource(new StringReader(xml)), true, true);
- }
-
- @NonNull
- private static Document parse(@NonNull String xml, @NonNull InputSource input, boolean checkBom,
- boolean checkDtd)
- throws ParserConfigurationException, SAXException, IOException {
- try {
- SAXParserFactory factory = SAXParserFactory.newInstance();
- if (checkDtd) {
- factory.setFeature(NAMESPACE_FEATURE, true);
- factory.setFeature(NAMESPACE_PREFIX_FEATURE, true);
- factory.setFeature(PROVIDE_XMLNS_URIS, true);
- } else {
- factory.setFeature(LOAD_EXTERNAL_DTD, false);
- }
- SAXParser parser = factory.newSAXParser();
- DomBuilder handler = new DomBuilder(xml);
- XMLReader xmlReader = parser.getXMLReader();
- xmlReader.setProperty(
- "http://xml.org/sax/properties/lexical-handler",
- handler
- );
- parser.parse(input, handler);
- return handler.getDocument();
- } catch (SAXException e) {
- if (checkBom && e.getMessage().contains("Content is not allowed in prolog")) {
- // Byte order mark in the string? Skip it. There are many markers
- // (see http://en.wikipedia.org/wiki/Byte_order_mark) so here we'll
- // just skip those up to the XML prolog beginning character, <
- xml = xml.replaceFirst("^([\\W]+)<","<"); //$NON-NLS-1$ //$NON-NLS-2$
- return parse(xml, new InputSource(new StringReader(xml)), false, checkDtd);
- }
- throw e;
- }
- }
-
- /**
- * Returns the String corresponding to the given byte array of XML data
- * (with unknown encoding). This method attempts to guess the encoding based
- * on the XML prologue.
- * @param data the XML data to be decoded into a string
- * @return a string corresponding to the XML data
- */
- @NonNull
- public static String getXmlString(@NonNull byte[] data) {
- return getXmlString(data, UTF_8);
- }
-
- /**
- * Returns the String corresponding to the given byte array of XML data
- * (with unknown encoding). This method attempts to guess the encoding based
- * on the XML prologue.
- * @param data the XML data to be decoded into a string
- * @param defaultCharset the default charset to use if not specified by an encoding prologue
- * attribute or a byte order mark
- * @return a string corresponding to the XML data
- */
- @NonNull
- public static String getXmlString(@NonNull byte[] data, @NonNull String defaultCharset) {
- int offset = 0;
-
- String charset = null;
- // Look for the byte order mark, to see if we need to remove bytes from
- // the input stream (and to determine whether files are big endian or little endian) etc
- // for files which do not specify the encoding.
- // See http://unicode.org/faq/utf_bom.html#BOM for more.
- if (data.length > 4) {
- if (data[0] == (byte)0xef && data[1] == (byte)0xbb && data[2] == (byte)0xbf) {
- // UTF-8
- defaultCharset = charset = UTF_8;
- offset += 3;
- } else if (data[0] == (byte)0xfe && data[1] == (byte)0xff) {
- // UTF-16, big-endian
- defaultCharset = charset = UTF_16;
- offset += 2;
- } else if (data[0] == (byte)0x0 && data[1] == (byte)0x0
- && data[2] == (byte)0xfe && data[3] == (byte)0xff) {
- // UTF-32, big-endian
- defaultCharset = charset = "UTF_32"; //$NON-NLS-1$
- offset += 4;
- } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe
- && data[2] == (byte)0x0 && data[3] == (byte)0x0) {
- // UTF-32, little-endian. We must check for this *before* looking for
- // UTF_16LE since UTF_32LE has the same prefix!
- defaultCharset = charset = "UTF_32LE"; //$NON-NLS-1$
- offset += 4;
- } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe) {
- // UTF-16, little-endian
- defaultCharset = charset = UTF_16LE;
- offset += 2;
- }
- }
- int length = data.length - offset;
-
- // Guess encoding by searching for an encoding= entry in the first line.
- // The prologue, and the encoding names, will always be in ASCII - which means
- // we don't need to worry about strange character encodings for the prologue characters.
- // However, one wrinkle is that the whole file may be encoded in something like UTF-16
- // where there are two bytes per character, so we can't just look for
- // ['e','n','c','o','d','i','n','g'] etc in the byte array since there could be
- // multiple bytes for each character. However, since again the prologue is in ASCII,
- // we can just drop the zeroes.
- boolean seenOddZero = false;
- boolean seenEvenZero = false;
- int prologueStart = -1;
- for (int lineEnd = offset; lineEnd < data.length; lineEnd++) {
- if (data[lineEnd] == 0) {
- if ((lineEnd - offset) % 2 == 0) {
- seenEvenZero = true;
- } else {
- seenOddZero = true;
- }
- } else if (data[lineEnd] == '\n' || data[lineEnd] == '\r') {
- break;
- } else if (data[lineEnd] == '<') {
- prologueStart = lineEnd;
- } else if (data[lineEnd] == '>') {
- // End of prologue. Quick check to see if this is a utf-8 file since that's
- // common
- for (int i = lineEnd - 4; i >= 0; i--) {
- if ((data[i] == 'u' || data[i] == 'U')
- && (data[i + 1] == 't' || data[i + 1] == 'T')
- && (data[i + 2] == 'f' || data[i + 2] == 'F')
- && (data[i + 3] == '-' || data[i + 3] == '_')
- && (data[i + 4] == '8')
- ) {
- charset = UTF_8;
- break;
- }
- }
-
- if (charset == null) {
- StringBuilder sb = new StringBuilder();
- for (int i = prologueStart; i <= lineEnd; i++) {
- if (data[i] != 0) {
- sb.append((char) data[i]);
- }
- }
- String prologue = sb.toString();
- int encodingIndex = prologue.indexOf("encoding"); //$NON-NLS-1$
- if (encodingIndex != -1) {
- Matcher matcher = ENCODING_PATTERN.matcher(prologue);
- if (matcher.find(encodingIndex)) {
- charset = matcher.group(1);
- }
- }
- }
-
- break;
- }
- }
-
- // No prologue on the first line, and no byte order mark: Assume UTF-8/16
- if (charset == null) {
- charset = seenOddZero ? UTF_16LE : seenEvenZero ? UTF_16 : defaultCharset;
- }
-
- String xml = null;
- try {
- xml = new String(data, offset, length, charset);
- } catch (UnsupportedEncodingException e) {
- try {
- if (charset != defaultCharset) {
- xml = new String(data, offset, length, defaultCharset);
- }
- } catch (UnsupportedEncodingException u) {
- // Just use the default encoding below
- }
- }
- if (xml == null) {
- xml = new String(data, offset, length);
- }
- return xml;
- }
-
- /**
- * Returns the position for the given node. This is the start position. The
- * end position can be obtained via {@link Position#getEnd()}.
- *
- * @param node the node to look up position for
- * @return the position, or null if the node type is not supported for
- * position info
- */
- @NonNull
- public static SourcePosition getPosition(@NonNull Node node) {
- return getPosition(node, -1, -1);
- }
-
- /**
- * Returns the position for the given node. This is the start position. The
- * end position can be obtained via {@link Position#getEnd()}. A specific
- * range within the node can be specified with the {@code start} and
- * {@code end} parameters.
- *
- * @param node the node to look up position for
- * @param start the relative offset within the node range to use as the
- * starting position, inclusive, or -1 to not limit the range
- * @param end the relative offset within the node range to use as the ending
- * position, or -1 to not limit the range
- * @return the position, or null if the node type is not supported for
- * position info
- */
-
- @NonNull
- public static SourcePosition getPosition(@NonNull Node node, int start, int end) {
- Position p = getPositionHelper(node, start, end);
- return p == null ? SourcePosition.UNKNOWN : p.toSourcePosition();
- }
-
- @Nullable
- private static Position getPositionHelper(@NonNull Node node, int start, int end) {
- // Look up the position information stored while parsing for the given node.
- // Note however that we only store position information for elements (because
- // there is no SAX callback for individual attributes).
- // Therefore, this method special cases this:
- // -- First, it looks at the owner element and uses its position
- // information as a first approximation.
- // -- Second, it uses that, as well as the original XML text, to search
- // within the node range for an exact text match on the attribute name
- // and if found uses that as the exact node offsets instead.
- if (node instanceof Attr) {
- Attr attr = (Attr) node;
- Position pos = (Position) attr.getOwnerElement().getUserData(POS_KEY);
- if (pos != null) {
- int startOffset = pos.getOffset();
- int endOffset = pos.getEnd().getOffset();
- if (start != -1) {
- startOffset += start;
- if (end != -1) {
- endOffset = startOffset + (end - start);
- }
- }
-
- // Find attribute in the text
- String contents = (String) node.getOwnerDocument().getUserData(CONTENT_KEY);
- if (contents == null) {
- return null;
- }
-
- // Locate the name=value attribute in the source text
- // Fast string check first for the common occurrence
- String name = attr.getName();
- Pattern pattern = Pattern.compile(attr.getPrefix() != null
- ? String.format("(%1$s\\s*=\\s*[\"'].*?[\"'])", name) //$NON-NLS-1$
- : String.format("[^:](%1$s\\s*=\\s*[\"'].*?[\"'])", name));//$NON-NLS-1$
- Matcher matcher = pattern.matcher(contents);
- if (matcher.find(startOffset) && matcher.start(1) <= endOffset) {
- int index = matcher.start(1);
- // Adjust the line and column to this new offset
- int line = pos.getLine();
- int column = pos.getColumn();
- for (int offset = pos.getOffset(); offset < index; offset++) {
- char t = contents.charAt(offset);
- if (t == '\n') {
- line++;
- column = 0;
- } else {
- column++;
- }
- }
-
- Position attributePosition = new Position(line, column, index);
- // Also set end range for retrieval in getLocation
- attributePosition.setEnd(
- new Position(line, column + matcher.end(1) - index, matcher.end(1)));
- return attributePosition;
- } else {
- // No regexp match either: just fall back to element position
- return pos;
- }
- }
- } else if (node instanceof Text) {
- // Position of parent element, if any
- Position pos = null;
- if (node.getPreviousSibling() != null) {
- pos = (Position) node.getPreviousSibling().getUserData(POS_KEY);
- }
- if (pos == null) {
- pos = (Position) node.getParentNode().getUserData(POS_KEY);
- }
- if (pos != null) {
- // Attempt to point forward to the actual text node
- int startOffset = pos.getOffset();
- int endOffset = pos.getEnd().getOffset();
- int line = pos.getLine();
- int column = pos.getColumn();
-
- // Find attribute in the text
- String contents = (String) node.getOwnerDocument().getUserData(CONTENT_KEY);
- if (contents == null || contents.length() < endOffset) {
- return null;
- }
-
- boolean inAttribute = false;
- for (int offset = startOffset; offset <= endOffset; offset++) {
- char c = contents.charAt(offset);
- if (c == '>' && !inAttribute) {
- // Found the end of the element open tag: this is where the
- // text begins.
-
- // Skip >
- offset++;
- column++;
-
- String text = node.getNodeValue();
- int textIndex = 0;
- int textLength = text.length();
- int newLine = line;
- int newColumn = column;
- if (start != -1) {
- textLength = Math.min(textLength, start);
- for (; textIndex < textLength; textIndex++) {
- char t = text.charAt(textIndex);
- if (t == '\n') {
- newLine++;
- newColumn = 0;
- } else {
- newColumn++;
- }
- }
- } else {
- // Skip text whitespace prefix, if the text node contains
- // non-whitespace characters
- for (; textIndex < textLength; textIndex++) {
- char t = text.charAt(textIndex);
- if (t == '\n') {
- newLine++;
- newColumn = 0;
- } else if (!Character.isWhitespace(t)) {
- break;
- } else {
- newColumn++;
- }
- }
- }
- if (textIndex == text.length()) {
- textIndex = 0; // Whitespace node
- } else {
- line = newLine;
- column = newColumn;
- }
-
- Position attributePosition = new Position(line, column, offset + textIndex);
- // Also set end range for retrieval in getLocation
- if (end != -1) {
- attributePosition.setEnd(new Position(line, column, offset + end));
- } else {
- attributePosition.setEnd(
- new Position(line, column, offset + textLength));
- }
- return attributePosition;
- } else if (c == '"') {
- inAttribute = !inAttribute;
- } else if (c == '\n') {
- line++;
- column = -1; // pre-subtract column added below
- }
- column++;
- }
-
- return pos;
- }
- }
-
- return (Position) node.getUserData(POS_KEY);
- }
-
- /**
- * SAX parser handler which incrementally builds up a DOM document as we go
- * along, and updates position information along the way. Position
- * information is attached to the DOM nodes by setting user data with the
- * {@link #POS_KEY} key.
- */
- private static final class DomBuilder extends DefaultHandler2 {
- private final String mXml;
- private final Document mDocument;
- private Locator mLocator;
- private int mCurrentLine = 0;
- private int mCurrentOffset;
- private int mCurrentColumn;
- private final List<Element> mStack = new ArrayList<Element>();
- private final StringBuilder mPendingText = new StringBuilder();
-
- private DomBuilder(String xml) throws ParserConfigurationException {
- mXml = xml;
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- DocumentBuilder docBuilder = factory.newDocumentBuilder();
- mDocument = docBuilder.newDocument();
- mDocument.setUserData(CONTENT_KEY, xml, null);
- }
-
- /** Returns the document parsed by the handler */
- Document getDocument() {
- return mDocument;
- }
-
- @Override
- public void setDocumentLocator(Locator locator) {
- this.mLocator = locator;
- }
-
- @Override
- public void startElement(String uri, String localName, String qName,
- Attributes attributes) throws SAXException {
- try {
- flushText();
- Element element = mDocument.createElementNS(uri, qName);
- for (int i = 0; i < attributes.getLength(); i++) {
- if (attributes.getURI(i) != null && !attributes.getURI(i).isEmpty()) {
- Attr attr = mDocument.createAttributeNS(attributes.getURI(i),
- attributes.getQName(i));
- attr.setValue(attributes.getValue(i));
- element.setAttributeNodeNS(attr);
- assert attr.getOwnerElement() == element;
- } else {
- Attr attr = mDocument.createAttribute(attributes.getQName(i));
- attr.setValue(attributes.getValue(i));
- element.setAttributeNode(attr);
- assert attr.getOwnerElement() == element;
- }
- }
-
- Position pos = getCurrentPosition();
-
- // The starting position reported to us by SAX is really the END of the
- // open tag in an element, when all the attributes have been processed.
- // We have to scan backwards to find the real beginning. We'll do that
- // by scanning backwards.
- // -1: Make sure that when we have <foo></foo> we don't consider </foo>
- // the beginning since pos.offset will typically point to the first character
- // AFTER the element open tag, which could be a closing tag or a child open
- // tag
- element.setUserData(POS_KEY, findOpeningTag(pos), null);
- mStack.add(element);
- } catch (Exception t) {
- throw new SAXException(t);
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName) {
- flushText();
- Element element = mStack.remove(mStack.size() - 1);
-
- Position pos = (Position) element.getUserData(POS_KEY);
- assert pos != null;
- pos.setEnd(getCurrentPosition());
-
- addNodeToParent(element);
- }
-
- @Override
- public void comment(char[] chars, int start, int length) throws SAXException {
-
- flushText();
- String comment = new String(chars, start, length);
- Comment domComment = mDocument.createComment(comment);
-
- // current position is the closing comment tag.
- Position currentPosition = getCurrentPosition();
- Position startPosition = findOpeningTag(currentPosition);
- startPosition.setEnd(currentPosition);
-
- domComment.setUserData(POS_KEY, startPosition, null);
- addNodeToParent(domComment);
- }
-
- /**
- * Adds a node to the current parent element being visited, or to the document if there is
- * no parent in context.
- * @param nodeToAdd xml node to add.
- */
- private void addNodeToParent(Node nodeToAdd) {
- if (mStack.isEmpty()){
- mDocument.appendChild(nodeToAdd);
- } else {
- Element parent = mStack.get(mStack.size() - 1);
- parent.appendChild(nodeToAdd);
- }
- }
-
- /**
- * Find opening tags from the current position.
- * < cannot appear in attribute values or anywhere else within
- * an element open tag, so we know the first occurrence is the real
- * element start
- * For comments, it is not legal to put < in a comment, however we are not
- * validating so we will return an invalid column in that case.
- * @param startingPosition the position to walk backwards until < is reached.
- * @return the opening tag position or startPosition if cannot be found.
- */
- private Position findOpeningTag(Position startingPosition) {
- for (int offset = startingPosition.getOffset() - 1; offset >= 0; offset--) {
- char c = mXml.charAt(offset);
-
- if (c == '<') {
- // Adjust line position
- int line = startingPosition.getLine();
- for (int i = offset, n = startingPosition.getOffset(); i < n; i++) {
- if (mXml.charAt(i) == '\n') {
- line--;
- }
- }
-
- // Compute new column position
- int column = 0;
- for (int i = offset - 1; i >= 0; i--, column++) {
- if (mXml.charAt(i) == '\n') {
- break;
- }
- }
-
- return new Position(line, column, offset);
- }
- }
- // we did not find it, approximate.
- return startingPosition;
- }
-
- /**
- * Returns a position holder for the current position. The most
- * important part of this function is to incrementally compute the
- * offset as well, by counting forwards until it reaches the new line
- * number and column position of the XML parser, counting characters as
- * it goes along.
- */
- private Position getCurrentPosition() {
- int line = mLocator.getLineNumber() - 1;
- int column = mLocator.getColumnNumber() - 1;
-
- // Compute offset incrementally now that we have the new line and column
- // numbers
- int xmlLength = mXml.length();
- while (mCurrentLine < line && mCurrentOffset < xmlLength) {
- char c = mXml.charAt(mCurrentOffset);
- if (c == '\r' && mCurrentOffset < xmlLength - 1) {
- if (mXml.charAt(mCurrentOffset + 1) != '\n') {
- mCurrentLine++;
- mCurrentColumn = 0;
- }
- } else if (c == '\n') {
- mCurrentLine++;
- mCurrentColumn = 0;
- } else {
- mCurrentColumn++;
- }
- mCurrentOffset++;
- }
-
- mCurrentOffset += column - mCurrentColumn;
- if (mCurrentOffset >= xmlLength) {
- // The parser sometimes passes wrong column numbers at the
- // end of the file: Ensure that the offset remains valid.
- mCurrentOffset = xmlLength;
- }
- mCurrentColumn = column;
-
- return new Position(mCurrentLine, mCurrentColumn, mCurrentOffset);
- }
-
- @Override
- public void characters(char c[], int start, int length) throws SAXException {
- mPendingText.append(c, start, length);
- }
-
- private void flushText() {
- if (mPendingText.length() > 0 && !mStack.isEmpty()) {
- Element element = mStack.get(mStack.size() - 1);
- Node textNode = mDocument.createTextNode(mPendingText.toString());
- element.appendChild(textNode);
- mPendingText.setLength(0);
- }
- }
- }
-
- private static class Position {
- /** The line number (0-based where the first line is line 0) */
- private final int mLine;
- private final int mColumn;
- private final int mOffset;
- private Position mEnd;
-
- /**
- * Creates a new {@link Position}
- *
- * @param line the 0-based line number, or -1 if unknown
- * @param column the 0-based column number, or -1 if unknown
- * @param offset the offset, or -1 if unknown
- */
- public Position(int line, int column, int offset) {
- this.mLine = line;
- this.mColumn = column;
- this.mOffset = offset;
- }
-
- public int getLine() {
- return mLine;
- }
-
- public int getOffset() {
- return mOffset;
- }
-
- public int getColumn() {
- return mColumn;
- }
-
- public Position getEnd() {
- return mEnd;
- }
-
- public void setEnd(@NonNull Position end) {
- mEnd = end;
- }
-
- public SourcePosition toSourcePosition() {
- int endLine = mLine, endColumn = mColumn, endOffset = mOffset;
-
- if (mEnd != null) {
- endLine = mEnd.getLine();
- endColumn = mEnd.getColumn();
- endOffset = mEnd.getOffset();
- }
-
- return new SourcePosition(mLine, mColumn, mOffset, endLine, endColumn, endOffset);
- }
- }
-
- private PositionXmlParser() { }
-}
diff --git a/base/common/src/main/java/com/android/utils/SdkUtils.java b/base/common/src/main/java/com/android/utils/SdkUtils.java
deleted file mode 100644
index 68e7354..0000000
--- a/base/common/src/main/java/com/android/utils/SdkUtils.java
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.utils;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.util.List;
-
-import static com.android.SdkConstants.DOT_WEBP;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.DOT_GIF;
-import static com.android.SdkConstants.DOT_9PNG;
-import static com.android.SdkConstants.DOT_JPEG;
-import static com.android.SdkConstants.DOT_JPG;
-import static com.android.SdkConstants.DOT_BMP;
-
-/** Miscellaneous utilities used by the Android SDK tools */
-public class SdkUtils {
- /**
- * Returns true if the given string ends with the given suffix, using a
- * case-insensitive comparison.
- *
- * @param string the full string to be checked
- * @param suffix the suffix to be checked for
- * @return true if the string case-insensitively ends with the given suffix
- */
- public static boolean endsWithIgnoreCase(@NonNull String string, @NonNull String suffix) {
- return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
- suffix, 0, suffix.length());
- }
-
- /**
- * Returns true if the given sequence ends with the given suffix (case
- * sensitive).
- *
- * @param sequence the character sequence to be checked
- * @param suffix the suffix to look for
- * @return true if the given sequence ends with the given suffix
- */
- public static boolean endsWith(@NonNull CharSequence sequence, @NonNull CharSequence suffix) {
- return endsWith(sequence, sequence.length(), suffix);
- }
-
- /**
- * Returns true if the given sequence ends at the given offset with the given suffix (case
- * sensitive)
- *
- * @param sequence the character sequence to be checked
- * @param endOffset the offset at which the sequence is considered to end
- * @param suffix the suffix to look for
- * @return true if the given sequence ends with the given suffix
- */
- public static boolean endsWith(@NonNull CharSequence sequence, int endOffset,
- @NonNull CharSequence suffix) {
- if (endOffset < suffix.length()) {
- return false;
- }
-
- for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) {
- if (sequence.charAt(i) != suffix.charAt(j)) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Returns true if the given string starts with the given prefix, using a
- * case-insensitive comparison.
- *
- * @param string the full string to be checked
- * @param prefix the prefix to be checked for
- * @return true if the string case-insensitively starts with the given prefix
- */
- public static boolean startsWithIgnoreCase(@NonNull String string, @NonNull String prefix) {
- return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
- }
-
- /**
- * Returns true if the given string starts at the given offset with the
- * given prefix, case insensitively.
- *
- * @param string the full string to be checked
- * @param offset the offset in the string to start looking
- * @param prefix the prefix to be checked for
- * @return true if the string case-insensitively starts at the given offset
- * with the given prefix
- */
- public static boolean startsWith(@NonNull String string, int offset, @NonNull String prefix) {
- return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
- }
-
- /**
- * Strips the whitespace from the given string
- *
- * @param string the string to be cleaned up
- * @return the string, without whitespace
- */
- public static String stripWhitespace(@NonNull String string) {
- StringBuilder sb = new StringBuilder(string.length());
- for (int i = 0, n = string.length(); i < n; i++) {
- char c = string.charAt(i);
- if (!Character.isWhitespace(c)) {
- sb.append(c);
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Returns true if the given string has an upper case character.
- *
- * @param s the string to check
- * @return true if it contains uppercase characters
- */
- public static boolean hasUpperCaseCharacter(@NonNull String s) {
- for (int i = 0; i < s.length(); i++) {
- if (Character.isUpperCase(s.charAt(i))) {
- return true;
- }
- }
-
- return false;
- }
-
- /** For use by {@link #getLineSeparator()} */
- private static String sLineSeparator;
-
- /**
- * Returns the default line separator to use.
- * <p>
- * NOTE: If you have an associated IDocument (Eclipse), it is better to call
- * TextUtilities#getDefaultLineDelimiter(IDocument) since that will
- * allow (for example) editing a \r\n-delimited document on a \n-delimited
- * platform and keep a consistent usage of delimiters in the file.
- *
- * @return the delimiter string to use
- */
- @NonNull
- public static String getLineSeparator() {
- if (sLineSeparator == null) {
- // This is guaranteed to exist:
- sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
- }
-
- return sLineSeparator;
- }
-
- /**
- * Wraps the given text at the given line width, with an optional hanging
- * indent.
- *
- * @param text the text to be wrapped
- * @param lineWidth the number of characters to wrap the text to
- * @param hangingIndent the hanging indent (to be used for the second and
- * subsequent lines in each paragraph, or null if not known
- * @return the string, wrapped
- */
- @NonNull
- public static String wrap(
- @NonNull String text,
- int lineWidth,
- @Nullable String hangingIndent) {
- if (hangingIndent == null) {
- hangingIndent = "";
- }
- int explanationLength = text.length();
- StringBuilder sb = new StringBuilder(explanationLength * 2);
- int index = 0;
-
- while (index < explanationLength) {
- int lineEnd = text.indexOf('\n', index);
- int next;
-
- if (lineEnd != -1 && (lineEnd - index) < lineWidth) {
- next = lineEnd + 1;
- } else {
- // Line is longer than available width; grab as much as we can
- lineEnd = Math.min(index + lineWidth, explanationLength);
- if (lineEnd - index < lineWidth) {
- next = explanationLength;
- } else {
- // then back up to the last space
- int lastSpace = text.lastIndexOf(' ', lineEnd);
- if (lastSpace > index) {
- lineEnd = lastSpace;
- next = lastSpace + 1;
- } else {
- // No space anywhere on the line: it contains something wider than
- // can fit (like a long URL) so just hard break it
- next = lineEnd + 1;
- }
- }
- }
-
- if (sb.length() > 0) {
- sb.append(hangingIndent);
- } else {
- lineWidth -= hangingIndent.length();
- }
-
- sb.append(text.substring(index, lineEnd));
- sb.append('\n');
- index = next;
- }
-
- return sb.toString();
- }
-
- /**
- * Returns the given localized string as an int. For example, in the
- * US locale, "1,000", will return 1000. In the French locale, "1.000" will return
- * 1000. It will return 0 for empty strings.
- * <p>
- * To parse a string without catching parser exceptions, call
- * {@link #parseLocalizedInt(String, int)} instead, passing the
- * default value to be returned if the format is invalid.
- *
- * @param string the string to be parsed
- * @return the integer value
- * @throws ParseException if the format is not correct
- */
- public static int parseLocalizedInt(@NonNull String string) throws ParseException {
- if (string.isEmpty()) {
- return 0;
- }
- return NumberFormat.getIntegerInstance().parse(string).intValue();
- }
-
- /**
- * Returns the given localized string as an int. For example, in the
- * US locale, "1,000", will return 1000. In the French locale, "1.000" will return
- * 1000. If the format is invalid, returns the supplied default value instead.
- *
- * @param string the string to be parsed
- * @param defaultValue the value to be returned if there is a parsing error
- * @return the integer value
- */
- public static int parseLocalizedInt(@NonNull String string, int defaultValue) {
- try {
- return parseLocalizedInt(string);
- } catch (ParseException e) {
- return defaultValue;
- }
- }
-
- /**
- * Returns the given localized string as a double. For example, in the
- * US locale, "3.14", will return 3.14. In the French locale, "3,14" will return
- * 3.14. It will return 0 for empty strings.
- * <p>
- * To parse a string without catching parser exceptions, call
- * {@link #parseLocalizedDouble(String, double)} instead, passing the
- * default value to be returned if the format is invalid.
- *
- * @param string the string to be parsed
- * @return the double value
- * @throws ParseException if the format is not correct
- */
- public static double parseLocalizedDouble(@NonNull String string) throws ParseException {
- if (string.isEmpty()) {
- return 0.0;
- }
- return NumberFormat.getNumberInstance().parse(string).doubleValue();
- }
-
- /**
- * Returns the given localized string as a double. For example, in the
- * US locale, "3.14", will return 3.14. In the French locale, "3,14" will return
- * 3.14. If the format is invalid, returns the supplied default value instead.
- *
- * @param string the string to be parsed
- * @param defaultValue the value to be returned if there is a parsing error
- * @return the double value
- */
- public static double parseLocalizedDouble(@NonNull String string, double defaultValue) {
- try {
- return parseLocalizedDouble(string);
- } catch (ParseException e) {
- return defaultValue;
- }
- }
-
- /**
- * Returns the corresponding {@link File} for the given file:// url
- *
- * @param url the URL string, e.g. file://foo/bar
- * @return the corresponding {@link File} (which may or may not exist)
- * @throws MalformedURLException if the URL string is malformed or is not a file: URL
- */
- @NonNull
- public static File urlToFile(@NonNull String url) throws MalformedURLException {
- return urlToFile(new URL(url));
- }
-
- @NonNull
- public static File urlToFile(@NonNull URL url) throws MalformedURLException {
- try {
- return new File(url.toURI());
- }
- catch (IllegalArgumentException e) {
- MalformedURLException ex = new MalformedURLException(e.getLocalizedMessage());
- ex.initCause(e);
- throw ex;
- }
- catch (URISyntaxException e) {
- return new File(url.getPath());
- }
- }
-
- /**
- * Returns the corresponding URL string for the given {@link File}
- *
- * @param file the file to look up the URL for
- * @return the corresponding URL
- * @throws MalformedURLException in very unexpected cases
- */
- public static String fileToUrlString(@NonNull File file) throws MalformedURLException {
- return fileToUrl(file).toExternalForm();
- }
-
- /**
- * Returns the corresponding URL for the given {@link File}
- *
- * @param file the file to look up the URL for
- * @return the corresponding URL
- * @throws MalformedURLException in very unexpected cases
- */
- public static URL fileToUrl(@NonNull File file) throws MalformedURLException {
- return file.toURI().toURL();
- }
-
- /** Prefix in comments which mark the source locations for merge results */
- public static final String FILENAME_PREFIX = "From: ";
-
- /**
- * Creates the path comment XML string. Note that it does not escape characters
- * such as & and <; those are expected to be escaped by the caller (for
- * example, handled by a call to {@link org.w3c.dom.Document#createComment(String)})
- *
- *
- * @param file the file to create a path comment for
- * @param includePadding whether to include padding. The final comment recognized by
- * error recognizers expect padding between the {@code <!--} and
- * the start marker (From:); you can disable padding if the caller
- * already is in a context where the padding has been added.
- * @return the corresponding XML contents of the string
- */
- public static String createPathComment(@NonNull File file, boolean includePadding)
- throws MalformedURLException {
- String url = fileToUrlString(file);
- int dashes = url.indexOf("--");
- if (dashes != -1) { // Not allowed inside XML comments - for SGML compatibility. Sigh.
- url = url.replace("--", "%2D%2D");
- }
-
- if (includePadding) {
- return ' ' + FILENAME_PREFIX + url + ' ';
- } else {
- return FILENAME_PREFIX + url;
- }
- }
-
- /**
- * Copies the given XML file to the given new path. It also inserts a comment at
- * the end of the file which points to the original source location. This is intended
- * for use with error parsers which can rewrite for example AAPT error messages in
- * say layout or manifest files, which occur in the merged (copied) output, and present
- * it as an error pointing to one of the user's original source files.
- */
- public static void copyXmlWithSourceReference(@NonNull File from, @NonNull File to)
- throws IOException {
- copyXmlWithComment(from, to, createPathComment(from, true));
- }
-
- /** Copies a given XML file, and appends a given comment to the end */
- private static void copyXmlWithComment(@NonNull File from, @NonNull File to,
- @Nullable String comment) throws IOException {
- assert endsWithIgnoreCase(from.getPath(), DOT_XML) : from;
-
- int successfulOps = 0;
- InputStream in = new FileInputStream(from);
- try {
- FileOutputStream out = new FileOutputStream(to, false);
- try {
- ByteStreams.copy(in, out);
- successfulOps++;
- if (comment != null) {
- String commentText = "<!--" + XmlUtils.toXmlTextValue(comment) + "-->";
- byte[] suffix = commentText.getBytes(Charsets.UTF_8);
- out.write(suffix);
- }
- } finally {
- Closeables.close(out, successfulOps < 1);
- successfulOps++;
- }
- } finally {
- Closeables.close(in, successfulOps < 2);
- }
- }
-
- /**
- * Translates an XML name (e.g. xml-name) into a Java / C++ constant name (e.g. XML_NAME)
- * @param xmlName the hyphen separated lower case xml name.
- * @return the equivalent constant name.
- */
- public static String xmlNameToConstantName(String xmlName) {
- return CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, xmlName);
- }
-
- /**
- * Translates a camel case name (e.g. xmlName) into a Java / C++ constant name (e.g. XML_NAME)
- * @param camelCaseName the camel case name.
- * @return the equivalent constant name.
- */
- public static String camelCaseToConstantName(String camelCaseName) {
- return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, camelCaseName);
- }
-
- /**
- * Translates a Java / C++ constant name (e.g. XML_NAME) into camel case name (e.g. xmlName)
- * @param constantName the constant name.
- * @return the equivalent camel case name.
- */
- public static String constantNameToCamelCase(String constantName) {
- return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, constantName);
- }
-
- /**
- * Translates a Java / C++ constant name (e.g. XML_NAME) into a XML case name (e.g. xml-name)
- * @param constantName the constant name.
- * @return the equivalent XML name.
- */
- public static String constantNameToXmlName(String constantName) {
- return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, constantName);
- }
-
-
- /**
- * Get the R field name from a resource name, since
- * AAPT will flatten the namespace, turning dots, dashes and colons into _
- *
- * @param resourceName the name to convert
- * @return the corresponding R field name
- */
- @NonNull
- public static String getResourceFieldName(@NonNull String resourceName) {
- // AAPT will flatten the namespace, turning dots, dashes and colons into _
- for (int i = 0, n = resourceName.length(); i < n; i++) {
- char c = resourceName.charAt(i);
- if (c == '.' || c == ':' || c == '-') {
- return resourceName.replace('.', '_').replace('-', '_').replace(':', '_');
- }
- }
-
- return resourceName;
- }
-
- public static final List<String> IMAGE_EXTENSIONS = ImmutableList.of(
- DOT_PNG, DOT_9PNG, DOT_GIF, DOT_JPEG, DOT_JPG, DOT_BMP, DOT_WEBP);
-
- /**
- * Returns true if the given file path points to an image file recognized by
- * Android. See http://developer.android.com/guide/appendix/media-formats.html
- * for details.
- *
- * @param path the filename to be tested
- * @return true if the file represents an image file
- */
- public static boolean hasImageExtension(String path) {
- for (String ext: IMAGE_EXTENSIONS) {
- if (endsWithIgnoreCase(path, ext)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/base/common/src/main/java/com/android/utils/StringHelper.java b/base/common/src/main/java/com/android/utils/StringHelper.java
deleted file mode 100644
index 7b84fd7..0000000
--- a/base/common/src/main/java/com/android/utils/StringHelper.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.utils;
-
-import com.android.annotations.NonNull;
-
-import java.util.Locale;
-
-/**
- */
-public class StringHelper {
-
- @NonNull
- public static String capitalize(@NonNull String string) {
- StringBuilder sb = new StringBuilder();
- sb.append(string.substring(0, 1).toUpperCase(Locale.US)).append(string.substring(1));
-
- return sb.toString();
- }
-}
diff --git a/base/common/src/main/java/com/android/utils/XmlUtils.java b/base/common/src/main/java/com/android/utils/XmlUtils.java
deleted file mode 100644
index 1159c89..0000000
--- a/base/common/src/main/java/com/android/utils/XmlUtils.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.utils;
-
-import static com.android.SdkConstants.AMP_ENTITY;
-import static com.android.SdkConstants.ANDROID_NS_NAME;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.APOS_ENTITY;
-import static com.android.SdkConstants.APP_PREFIX;
-import static com.android.SdkConstants.GT_ENTITY;
-import static com.android.SdkConstants.LT_ENTITY;
-import static com.android.SdkConstants.QUOT_ENTITY;
-import static com.android.SdkConstants.XMLNS;
-import static com.android.SdkConstants.XMLNS_PREFIX;
-import static com.android.SdkConstants.XMLNS_URI;
-import static com.google.common.base.Charsets.UTF_16BE;
-import static com.google.common.base.Charsets.UTF_16LE;
-import static com.google.common.base.Charsets.UTF_8;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.HashSet;
-import java.util.Locale;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-/** XML Utilities */
-public class XmlUtils {
- public static final String XML_COMMENT_BEGIN = "<!--"; //$NON-NLS-1$
- public static final String XML_COMMENT_END = "-->"; //$NON-NLS-1$
- public static final String XML_PROLOG =
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$
-
- /**
- * Separator for xml namespace and localname
- */
- public static final char NS_SEPARATOR = ':'; //$NON-NLS-1$
-
- /**
- * Returns the namespace prefix matching the requested namespace URI.
- * If no such declaration is found, returns the default "android" prefix for
- * the Android URI, and "app" for other URI's. By default the app namespace
- * will be created. If this is not desirable, call
- * {@link #lookupNamespacePrefix(Node, String, boolean)} instead.
- *
- * @param node The current node. Must not be null.
- * @param nsUri The namespace URI of which the prefix is to be found,
- * e.g. {@link SdkConstants#ANDROID_URI}
- * @return The first prefix declared or the default "android" prefix
- * (or "app" for non-Android URIs)
- */
- @NonNull
- public static String lookupNamespacePrefix(@NonNull Node node, @NonNull String nsUri) {
- String defaultPrefix = ANDROID_URI.equals(nsUri) ? ANDROID_NS_NAME : APP_PREFIX;
- return lookupNamespacePrefix(node, nsUri, defaultPrefix, true /*create*/);
- }
-
- /**
- * Returns the namespace prefix matching the requested namespace URI. If no
- * such declaration is found, returns the default "android" prefix for the
- * Android URI, and "app" for other URI's.
- *
- * @param node The current node. Must not be null.
- * @param nsUri The namespace URI of which the prefix is to be found, e.g.
- * {@link SdkConstants#ANDROID_URI}
- * @param create whether the namespace declaration should be created, if
- * necessary
- * @return The first prefix declared or the default "android" prefix (or
- * "app" for non-Android URIs)
- */
- @NonNull
- public static String lookupNamespacePrefix(@NonNull Node node, @NonNull String nsUri,
- boolean create) {
- String defaultPrefix = ANDROID_URI.equals(nsUri) ? ANDROID_NS_NAME : APP_PREFIX;
- return lookupNamespacePrefix(node, nsUri, defaultPrefix, create);
- }
-
- /**
- * Returns the namespace prefix matching the requested namespace URI. If no
- * such declaration is found, returns the default "android" prefix.
- *
- * @param node The current node. Must not be null.
- * @param nsUri The namespace URI of which the prefix is to be found, e.g.
- * {@link SdkConstants#ANDROID_URI}
- * @param defaultPrefix The default prefix (root) to use if the namespace is
- * not found. If null, do not create a new namespace if this URI
- * is not defined for the document.
- * @param create whether the namespace declaration should be created, if
- * necessary
- * @return The first prefix declared or the provided prefix (possibly with a
- * number appended to avoid conflicts with existing prefixes.
- */
- public static String lookupNamespacePrefix(
- @Nullable Node node, @Nullable String nsUri, @Nullable String defaultPrefix,
- boolean create) {
- // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
- // The following code emulates this simple call:
- // String prefix = node.lookupPrefix(NS_RESOURCES);
-
- // if the requested URI is null, it denotes an attribute with no namespace.
- if (nsUri == null) {
- return null;
- }
-
- // per XML specification, the "xmlns" URI is reserved
- if (XMLNS_URI.equals(nsUri)) {
- return XMLNS;
- }
-
- HashSet<String> visited = new HashSet<String>();
- Document doc = node == null ? null : node.getOwnerDocument();
-
- // Ask the document about it. This method may not be implemented by the Document.
- String nsPrefix = null;
- try {
- nsPrefix = doc != null ? doc.lookupPrefix(nsUri) : null;
- if (nsPrefix != null) {
- return nsPrefix;
- }
- } catch (Throwable t) {
- // ignore
- }
-
- // If that failed, try to look it up manually.
- // This also gathers prefixed in use in the case we want to generate a new one below.
- for (; node != null && node.getNodeType() == Node.ELEMENT_NODE;
- node = node.getParentNode()) {
- NamedNodeMap attrs = node.getAttributes();
- for (int n = attrs.getLength() - 1; n >= 0; --n) {
- Node attr = attrs.item(n);
- if (XMLNS.equals(attr.getPrefix())) {
- String uri = attr.getNodeValue();
- nsPrefix = attr.getLocalName();
- // Is this the URI we are looking for? If yes, we found its prefix.
- if (nsUri.equals(uri)) {
- return nsPrefix;
- }
- visited.add(nsPrefix);
- }
- }
- }
-
- // Failed the find a prefix. Generate a new sensible default prefix, unless
- // defaultPrefix was null in which case the caller does not want the document
- // modified.
- if (defaultPrefix == null) {
- return null;
- }
-
- //
- // We need to make sure the prefix is not one that was declared in the scope
- // visited above. Pick a unique prefix from the provided default prefix.
- String prefix = defaultPrefix;
- String base = prefix;
- for (int i = 1; visited.contains(prefix); i++) {
- prefix = base + Integer.toString(i);
- }
- // Also create & define this prefix/URI in the XML document as an attribute in the
- // first element of the document.
- if (doc != null) {
- node = doc.getFirstChild();
- while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
- node = node.getNextSibling();
- }
- if (node != null && create) {
- // This doesn't work:
- //Attr attr = doc.createAttributeNS(XMLNS_URI, prefix);
- //attr.setPrefix(XMLNS);
- //
- // Xerces throws
- //org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or
- // change an object in a way which is incorrect with regard to namespaces.
- //
- // Instead pass in the concatenated prefix. (This is covered by
- // the UiElementNodeTest#testCreateNameSpace() test.)
- Attr attr = doc.createAttributeNS(XMLNS_URI, XMLNS_PREFIX + prefix);
- attr.setValue(nsUri);
- node.getAttributes().setNamedItemNS(attr);
- }
- }
-
- return prefix;
- }
-
- /**
- * Converts the given attribute value to an XML-attribute-safe value, meaning that
- * single and double quotes are replaced with their corresponding XML entities.
- *
- * @param attrValue the value to be escaped
- * @return the escaped value
- */
- @NonNull
- public static String toXmlAttributeValue(@NonNull String attrValue) {
- for (int i = 0, n = attrValue.length(); i < n; i++) {
- char c = attrValue.charAt(i);
- if (c == '"' || c == '\'' || c == '<' || c == '&') {
- StringBuilder sb = new StringBuilder(2 * attrValue.length());
- appendXmlAttributeValue(sb, attrValue);
- return sb.toString();
- }
- }
-
- return attrValue;
- }
-
- /**
- * Converts the given XML-attribute-safe value to a java string
- *
- * @param escapedAttrValue the escaped value
- * @return the unescaped value
- */
- @NonNull
- public static String fromXmlAttributeValue(@NonNull String escapedAttrValue) {
- String workingString = escapedAttrValue.replace(QUOT_ENTITY, "\"");
- workingString = workingString.replace(LT_ENTITY, "<");
- workingString = workingString.replace(APOS_ENTITY, "'");
- workingString = workingString.replace(AMP_ENTITY, "&");
- workingString = workingString.replace(GT_ENTITY, ">");
-
- return workingString;
- }
-
- /**
- * Converts the given attribute value to an XML-text-safe value, meaning that
- * less than and ampersand characters are escaped.
- *
- * @param textValue the text value to be escaped
- * @return the escaped value
- */
- @NonNull
- public static String toXmlTextValue(@NonNull String textValue) {
- for (int i = 0, n = textValue.length(); i < n; i++) {
- char c = textValue.charAt(i);
- if (c == '<' || c == '&') {
- StringBuilder sb = new StringBuilder(2 * textValue.length());
- appendXmlTextValue(sb, textValue);
- return sb.toString();
- }
- }
-
- return textValue;
- }
-
- /**
- * Appends text to the given {@link StringBuilder} and escapes it as required for a
- * DOM attribute node.
- *
- * @param sb the string builder
- * @param attrValue the attribute value to be appended and escaped
- */
- public static void appendXmlAttributeValue(@NonNull StringBuilder sb,
- @NonNull String attrValue) {
- int n = attrValue.length();
- // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
- // (' legal in a " string and " is legal in a ' string but here we'll stay on the safe
- // side)
- for (int i = 0; i < n; i++) {
- char c = attrValue.charAt(i);
- if (c == '"') {
- sb.append(QUOT_ENTITY);
- } else if (c == '<') {
- sb.append(LT_ENTITY);
- } else if (c == '\'') {
- sb.append(APOS_ENTITY);
- } else if (c == '&') {
- sb.append(AMP_ENTITY);
- } else {
- sb.append(c);
- }
- }
- }
-
- /**
- * Appends text to the given {@link StringBuilder} and escapes it as required for a
- * DOM text node.
- *
- * @param sb the string builder
- * @param textValue the text value to be appended and escaped
- */
- public static void appendXmlTextValue(@NonNull StringBuilder sb, @NonNull String textValue) {
- for (int i = 0, n = textValue.length(); i < n; i++) {
- char c = textValue.charAt(i);
- if (c == '<') {
- sb.append(LT_ENTITY);
- } else if (c == '&') {
- sb.append(AMP_ENTITY);
- } else {
- sb.append(c);
- }
- }
- }
-
- /**
- * Returns true if the given node has one or more element children
- *
- * @param node the node to test for element children
- * @return true if the node has one or more element children
- */
- public static boolean hasElementChildren(@NonNull Node node) {
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns a character reader for the given file, which must be a UTF encoded file.
- * <p>
- * The reader does not need to be closed by the caller (because the file is read in
- * full in one shot and the resulting array is then wrapped in a byte array input stream,
- * which does not need to be closed.)
- */
- public static Reader getUtfReader(@NonNull File file) throws IOException {
- byte[] bytes = Files.toByteArray(file);
- int length = bytes.length;
- if (length == 0) {
- return new StringReader("");
- }
-
- switch (bytes[0]) {
- case (byte)0xEF: {
- if (length >= 3
- && bytes[1] == (byte)0xBB
- && bytes[2] == (byte)0xBF) {
- // UTF-8 BOM: EF BB BF: Skip it
- return new InputStreamReader(new ByteArrayInputStream(bytes, 3, length - 3),
- UTF_8);
- }
- break;
- }
- case (byte)0xFE: {
- if (length >= 2
- && bytes[1] == (byte)0xFF) {
- // UTF-16 Big Endian BOM: FE FF
- return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2),
- UTF_16BE);
- }
- break;
- }
- case (byte)0xFF: {
- if (length >= 2
- && bytes[1] == (byte)0xFE) {
- if (length >= 4
- && bytes[2] == (byte)0x00
- && bytes[3] == (byte)0x00) {
- // UTF-32 Little Endian BOM: FF FE 00 00
- return new InputStreamReader(new ByteArrayInputStream(bytes, 4,
- length - 4), "UTF-32LE");
- }
-
- // UTF-16 Little Endian BOM: FF FE
- return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2),
- UTF_16LE);
- }
- break;
- }
- case (byte)0x00: {
- if (length >= 4
- && bytes[0] == (byte)0x00
- && bytes[1] == (byte)0x00
- && bytes[2] == (byte)0xFE
- && bytes[3] == (byte)0xFF) {
- // UTF-32 Big Endian BOM: 00 00 FE FF
- return new InputStreamReader(new ByteArrayInputStream(bytes, 4, length - 4),
- "UTF-32BE");
- }
- break;
- }
- }
-
- // No byte order mark: Assume UTF-8 (where the BOM is optional).
- return new InputStreamReader(new ByteArrayInputStream(bytes), UTF_8);
- }
-
- /**
- * Parses the given XML string as a DOM document, using the JDK parser. The parser does not
- * validate, and is optionally namespace aware.
- *
- * @param xml the XML content to be parsed (must be well formed)
- * @param namespaceAware whether the parser is namespace aware
- * @return the DOM document
- */
- @NonNull
- public static Document parseDocument(@NonNull String xml, boolean namespaceAware)
- throws ParserConfigurationException, IOException, SAXException {
- xml = stripBom(xml);
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- InputSource is = new InputSource(new StringReader(xml));
- factory.setNamespaceAware(namespaceAware);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- return builder.parse(is);
- }
-
- /**
- * Parses the given UTF file as a DOM document, using the JDK parser. The parser does not
- * validate, and is optionally namespace aware.
- *
- * @param file the UTF encoded file to parse
- * @param namespaceAware whether the parser is namespace aware
- * @return the DOM document
- */
- @NonNull
- public static Document parseUtfXmlFile(@NonNull File file, boolean namespaceAware)
- throws ParserConfigurationException, IOException, SAXException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- Reader reader = getUtfReader(file);
- try {
- InputSource is = new InputSource(reader);
- factory.setNamespaceAware(namespaceAware);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- return builder.parse(is);
- } finally {
- reader.close();
- }
- }
-
- /** Strips out a leading UTF byte order mark, if present */
- @NonNull
- public static String stripBom(@NonNull String xml) {
- if (!xml.isEmpty() && xml.charAt(0) == '\uFEFF') {
- return xml.substring(1);
- }
- return xml;
- }
-
- /**
- * Parses the given XML string as a DOM document, using the JDK parser. The parser does not
- * validate, and is optionally namespace aware. Any parsing errors are silently ignored.
- *
- * @param xml the XML content to be parsed (must be well formed)
- * @param namespaceAware whether the parser is namespace aware
- * @return the DOM document, or null
- */
- @Nullable
- public static Document parseDocumentSilently(@NonNull String xml, boolean namespaceAware) {
- try {
- return parseDocument(xml, namespaceAware);
- } catch (Exception e) {
- // pass
- // This method is deliberately silent; will return null
- }
-
- return null;
- }
-
- /**
- * Dump an XML tree to string. This does not perform any pretty printing.
- * To perform pretty printing, use {@code XmlPrettyPrinter.prettyPrint(node)} in
- * {@code sdk-common}.
- */
- public static String toXml(Node node) {
- StringBuilder sb = new StringBuilder(1000);
- append(sb, node, 0);
- return sb.toString();
- }
-
- /** Dump node to string without indentation adjustments */
- private static void append(
- @NonNull StringBuilder sb,
- @NonNull Node node,
- int indent) {
- short nodeType = node.getNodeType();
- switch (nodeType) {
- case Node.DOCUMENT_NODE:
- case Node.DOCUMENT_FRAGMENT_NODE: {
- sb.append(XML_PROLOG);
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- append(sb, children.item(i), indent);
- }
- break;
- }
- case Node.COMMENT_NODE:
- sb.append(XML_COMMENT_BEGIN);
- sb.append(node.getNodeValue());
- sb.append(XML_COMMENT_END);
- break;
- case Node.TEXT_NODE: {
- sb.append(toXmlTextValue(node.getNodeValue()));
- break;
- }
- case Node.CDATA_SECTION_NODE: {
- sb.append("<![CDATA["); //$NON-NLS-1$
- sb.append(node.getNodeValue());
- sb.append("]]>"); //$NON-NLS-1$
- break;
- }
- case Node.ELEMENT_NODE: {
- sb.append('<');
- Element element = (Element) node;
- sb.append(element.getTagName());
-
- NamedNodeMap attributes = element.getAttributes();
- NodeList children = element.getChildNodes();
- int childCount = children.getLength();
- int attributeCount = attributes.getLength();
-
- if (attributeCount > 0) {
- for (int i = 0; i < attributeCount; i++) {
- Node attribute = attributes.item(i);
- sb.append(' ');
- sb.append(attribute.getNodeName());
- sb.append('=').append('"');
- sb.append(toXmlAttributeValue(attribute.getNodeValue()));
- sb.append('"');
- }
- }
-
- if (childCount == 0) {
- sb.append('/');
- }
- sb.append('>');
- if (childCount > 0) {
- for (int i = 0; i < childCount; i++) {
- Node child = children.item(i);
- append(sb, child, indent + 1);
- }
- sb.append('<').append('/');
- sb.append(element.getTagName());
- sb.append('>');
- }
- break;
- }
-
- default:
- throw new UnsupportedOperationException(
- "Unsupported node type " + nodeType + ": not yet implemented");
- }
- }
-
- /**
- * Format the given floating value into an XML string, omitting decimals if
- * 0
- *
- * @param value the value to be formatted
- * @return the corresponding XML string for the value
- */
- public static String formatFloatAttribute(double value) {
- if (value != (int) value) {
- // Run String.format without a locale, because we don't want locale-specific
- // conversions here like separating the decimal part with a comma instead of a dot!
- return String.format((Locale) null, "%.2f", value); //$NON-NLS-1$
- } else {
- return Integer.toString((int) value);
- }
- }
-}
diff --git a/base/common/src/test/java/com/android/utils/HtmlBuilderTest.java b/base/common/src/test/java/com/android/utils/HtmlBuilderTest.java
deleted file mode 100644
index 82701df..0000000
--- a/base/common/src/test/java/com/android/utils/HtmlBuilderTest.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.utils;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.io.IOException;
-
-public class HtmlBuilderTest extends TestCase {
-
- public void testAddLink() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.add("Plain.");
- builder.addLink(" (link) ", "runnable:0");
- builder.add("Plain.");
- // Check that the spaces surrounding the link text are not included in the link range
- assertEquals("Plain. <A HREF=\"runnable:0\">(link)</A>Plain.", builder.getHtml());
- }
-
- public void testAddBold() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.addBold("This is bold");
- assertEquals("<B>This is bold</B>", builder.getHtml());
- }
-
- public void testAddItalic() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.addItalic("This is italic");
- assertEquals("<I>This is italic</I>", builder.getHtml());
- }
-
- public void testNestLinkInBold() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.add("Plain. ");
- builder.beginBold();
- builder.add("Bold. ");
- builder.addLink("mylink", "foo://bar:123");
- builder.endBold();
- assertEquals("Plain. <B>Bold. <A HREF=\"foo://bar:123\">mylink</A></B>",
- builder.getHtml());
- }
-
- public void testAddList() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.add("Plain").newline();
- builder.beginList().listItem().add("item 1").listItem().add("item 2").endList();
-
- assertEquals("Plain<BR/>" +
- "<DL>" +
- "<DD>-&NBSP;item 1" +
- "<DD>-&NBSP;item 2" +
- "</DL>", builder.getHtml());
- }
-
- public void testAddLinkWithBeforeAndAfterText() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.addLink("This is the ", "linked text", "!", "foo://bar");
- assertEquals("This is the <A HREF=\"foo://bar\">linked text</A>!", builder.getHtml());
- }
-
- public void testAddTable() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.beginTable().addTableRow(true, "Header1", "Header2")
- .addTableRow("Data1", "Data2")
- .endTable();
- assertEquals(
- "<table><tr><th>Header1</th><th>Header2</th></tr><tr><td>Data1</td><td>Data2</td></tr></table>",
- builder.getHtml());
- }
-
- public void testAddTableWithAlignment() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.beginTable("valign=\"top\"").addTableRow("Data1", "Data2").endTable();
- assertEquals(
- "<table><tr><td valign=\"top\">Data1</td><td valign=\"top\">Data2</td></tr></table>",
- builder.getHtml());
- }
-
- public void testAddDiv() {
- HtmlBuilder builder = new HtmlBuilder();
- assertEquals("<div>Hello</div>", builder.beginDiv().add("Hello").endDiv().getHtml());
- }
-
- public void testAddDivWithPadding() {
- HtmlBuilder builder = new HtmlBuilder();
- assertEquals("<div style=\"padding: 10px; text-color: gray\">Hello</div>",
- builder.beginDiv("padding: 10px; text-color: gray").add("Hello").endDiv()
- .getHtml());
- }
-
- public void testAddImage() throws IOException {
- File f = File.createTempFile("img", "png");
- f.deleteOnExit();
-
- String actual = new HtmlBuilder().addImage(SdkUtils.fileToUrl(f), "preview").getHtml();
- String path = f.getAbsolutePath();
-
- if (!path.startsWith("/")) {
- path = '/' + path;
- }
- String expected = String.format("<img src='file:%1$s' alt=\"preview\" />", path);
- if (File.separatorChar != '/') {
- // SdkUtil.fileToUrl always returns / as a separator so adjust
- // Windows path accordingly.
- expected = expected.replace(File.separatorChar, '/');
- }
- assertEquals(expected, actual);
- }
-
- public void testNewlineIfNecessary() {
- HtmlBuilder builder = new HtmlBuilder();
- builder.newlineIfNecessary();
- assertEquals("<BR/>", builder.getHtml());
- builder.newlineIfNecessary();
- assertEquals("<BR/>", builder.getHtml());
- builder.add("a");
- builder.newlineIfNecessary();
- assertEquals("<BR/>a<BR/>", builder.getHtml());
- builder.newline();
- builder.newlineIfNecessary();
- builder.newlineIfNecessary();
- builder.newlineIfNecessary();
- assertEquals("<BR/>a<BR/><BR/>", builder.getHtml());
- }
-}
diff --git a/base/common/src/test/java/com/android/utils/SdkUtilsTest.java b/base/common/src/test/java/com/android/utils/SdkUtilsTest.java
deleted file mode 100755
index 53e01e2..0000000
--- a/base/common/src/test/java/com/android/utils/SdkUtilsTest.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.utils;
-
-import static com.android.utils.SdkUtils.FILENAME_PREFIX;
-import static com.android.utils.SdkUtils.createPathComment;
-import static com.android.utils.SdkUtils.fileToUrlString;
-import static com.android.utils.SdkUtils.urlToFile;
-
-import com.android.SdkConstants;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import junit.framework.TestCase;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.text.ParseException;
-import java.util.Locale;
-
- at SuppressWarnings("javadoc")
-public class SdkUtilsTest extends TestCase {
-
- @Override
- public void setUp() throws Exception {
- // TODO: Use Files.createTempDir() to avoid this.
- if (new File("/tmp/foo").isDirectory()) {
- fail("This test will fail if /tmp/foo exists and is a directory. Please remove it.");
- }
- }
-
- public void testEndsWithIgnoreCase() {
- assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo"));
- assertTrue(SdkUtils.endsWithIgnoreCase("foo", "Foo"));
- assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo"));
- assertTrue(SdkUtils.endsWithIgnoreCase("Barfoo", "foo"));
- assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foo"));
- assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foO"));
-
- assertFalse(SdkUtils.endsWithIgnoreCase("foob", "foo"));
- assertFalse(SdkUtils.endsWithIgnoreCase("foo", "fo"));
- }
-
- public void testStartsWithIgnoreCase() {
- assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo"));
- assertTrue(SdkUtils.startsWithIgnoreCase("foo", "Foo"));
- assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo"));
- assertTrue(SdkUtils.startsWithIgnoreCase("barfoo", "bar"));
- assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bar"));
- assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bAr"));
-
- assertFalse(SdkUtils.startsWithIgnoreCase("bfoo", "foo"));
- assertFalse(SdkUtils.startsWithIgnoreCase("fo", "foo"));
- }
-
- public void testStartsWith() {
- assertTrue(SdkUtils.startsWith("foo", 0, "foo"));
- assertTrue(SdkUtils.startsWith("foo", 0, "Foo"));
- assertTrue(SdkUtils.startsWith("Foo", 0, "foo"));
- assertTrue(SdkUtils.startsWith("aFoo", 1, "foo"));
-
- assertFalse(SdkUtils.startsWith("aFoo", 0, "foo"));
- assertFalse(SdkUtils.startsWith("aFoo", 2, "foo"));
- }
-
- public void testEndsWith() {
- assertTrue(SdkUtils.endsWith("foo", "foo"));
- assertTrue(SdkUtils.endsWith("foobar", "obar"));
- assertTrue(SdkUtils.endsWith("foobar", "bar"));
- assertTrue(SdkUtils.endsWith("foobar", "ar"));
- assertTrue(SdkUtils.endsWith("foobar", "r"));
- assertTrue(SdkUtils.endsWith("foobar", ""));
-
- assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), "bar"));
- assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), new StringBuffer("obar")));
- assertTrue(SdkUtils.endsWith("foobar", new StringBuffer("obar")));
-
- assertFalse(SdkUtils.endsWith("foo", "fo"));
- assertFalse(SdkUtils.endsWith("foobar", "Bar"));
- assertFalse(SdkUtils.endsWith("foobar", "longfoobar"));
- }
-
- public void testEndsWith2() {
- assertTrue(SdkUtils.endsWith("foo", "foo".length(), "foo"));
- assertTrue(SdkUtils.endsWith("foo", "fo".length(), "fo"));
- assertTrue(SdkUtils.endsWith("foo", "f".length(), "f"));
- }
-
- public void testStripWhitespace() {
- assertEquals("foo", SdkUtils.stripWhitespace("foo"));
- assertEquals("foobar", SdkUtils.stripWhitespace("foo bar"));
- assertEquals("foobar", SdkUtils.stripWhitespace(" foo bar \n\t"));
- }
-
- public void testWrap() {
- String s =
- "Hardcoding text attributes directly in layout files is bad for several reasons:\n" +
- "\n" +
- "* When creating configuration variations (for example for landscape or portrait)" +
- "you have to repeat the actual text (and keep it up to date when making changes)\n" +
- "\n" +
- "* The application cannot be translated to other languages by just adding new " +
- "translations for existing string resources.";
- String wrapped = SdkUtils.wrap(s, 70, "");
- assertEquals(
- "Hardcoding text attributes directly in layout files is bad for several\n" +
- "reasons:\n" +
- "\n" +
- "* When creating configuration variations (for example for landscape or\n" +
- "portrait)you have to repeat the actual text (and keep it up to date\n" +
- "when making changes)\n" +
- "\n" +
- "* The application cannot be translated to other languages by just\n" +
- "adding new translations for existing string resources.\n",
- wrapped);
- }
-
- public void testWrapPrefix() {
- String s =
- "Hardcoding text attributes directly in layout files is bad for several reasons:\n" +
- "\n" +
- "* When creating configuration variations (for example for landscape or portrait)" +
- "you have to repeat the actual text (and keep it up to date when making changes)\n" +
- "\n" +
- "* The application cannot be translated to other languages by just adding new " +
- "translations for existing string resources.";
- String wrapped = SdkUtils.wrap(s, 70, " ");
- assertEquals(
- "Hardcoding text attributes directly in layout files is bad for several\n" +
- " reasons:\n" +
- " \n" +
- " * When creating configuration variations (for example for\n" +
- " landscape or portrait)you have to repeat the actual text (and keep\n" +
- " it up to date when making changes)\n" +
- " \n" +
- " * The application cannot be translated to other languages by just\n" +
- " adding new translations for existing string resources.\n",
- wrapped);
- }
-
- public void testParseInt() throws Exception {
- Locale.setDefault(Locale.US);
- assertEquals(1000, SdkUtils.parseLocalizedInt("1000"));
- assertEquals(0, SdkUtils.parseLocalizedInt("0"));
- assertEquals(0, SdkUtils.parseLocalizedInt(""));
- assertEquals(1, SdkUtils.parseLocalizedInt("1"));
- assertEquals(-1, SdkUtils.parseLocalizedInt("-1"));
- assertEquals(1000, SdkUtils.parseLocalizedInt("1,000"));
- assertEquals(1000000, SdkUtils.parseLocalizedInt("1,000,000"));
-
- Locale.setDefault(Locale.ITALIAN);
- assertSame(Locale.ITALIAN, Locale.getDefault());
- assertEquals(1000, SdkUtils.parseLocalizedInt("1000"));
- assertEquals(0, SdkUtils.parseLocalizedInt("0"));
- assertEquals(1, SdkUtils.parseLocalizedInt("1"));
- assertEquals(-1, SdkUtils.parseLocalizedInt("-1"));
- assertEquals(1000, SdkUtils.parseLocalizedInt("1.000"));
- assertEquals(1000000, SdkUtils.parseLocalizedInt("1.000.000"));
-
- // Make sure it throws exceptions
- try {
- SdkUtils.parseLocalizedInt("X");
- fail("Should have thrown exception");
- } catch (ParseException e) {
- // Expected
- }
- }
-
- public void testParseIntWithDefault() throws Exception {
- Locale.setDefault(Locale.US);
- assertEquals(1000, SdkUtils.parseLocalizedInt("1000", 0)); // Valid
- assertEquals(2, SdkUtils.parseLocalizedInt("2.X", 2)); // Parses prefix
- assertEquals(5, SdkUtils.parseLocalizedInt("X", 5)); // Parses prefix
-
- Locale.setDefault(Locale.ITALIAN);
- assertEquals(1000, SdkUtils.parseLocalizedInt("1000", -1)); // Valid
- assertEquals(7, SdkUtils.parseLocalizedInt("X", 7));
- }
-
- public void testParseDouble() throws Exception {
- Locale.setDefault(Locale.US);
- assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000"));
- assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000.0"));
- assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1000.5"));
- assertEquals(0.0, SdkUtils.parseLocalizedDouble("0"));
- assertEquals(0.0, SdkUtils.parseLocalizedDouble(""));
- assertEquals(1.0, SdkUtils.parseLocalizedDouble("1"));
- assertEquals(-1.0, SdkUtils.parseLocalizedDouble("-1"));
- assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1,000"));
- assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1,000.5"));
- assertEquals(1000000.0, SdkUtils.parseLocalizedDouble("1,000,000"));
- assertEquals(1000000.5, SdkUtils.parseLocalizedDouble("1,000,000.5"));
-
- Locale.setDefault(Locale.ITALIAN);
- assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000"));
- assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1000,5"));
- assertEquals(0.0, SdkUtils.parseLocalizedDouble("0"));
- assertEquals(1.0, SdkUtils.parseLocalizedDouble("1"));
- assertEquals(-1.0, SdkUtils.parseLocalizedDouble("-1"));
- assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1.000"));
- assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1.000,5"));
- assertEquals(1000000.0, SdkUtils.parseLocalizedDouble("1.000.000"));
- assertEquals(1000000.5, SdkUtils.parseLocalizedDouble("1.000.000,5"));
-
- // Make sure it throws exceptions
- try {
- SdkUtils.parseLocalizedDouble("X");
- fail("Should have thrown exception");
- } catch (ParseException e) {
- // Expected
- }
- }
-
- public void testParseDoubleWithDefault() throws Exception {
- Locale.setDefault(Locale.US);
- assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000", 0)); // Valid
- assertEquals(2.0, SdkUtils.parseLocalizedDouble("2x", 3)); // Uses prefix
- assertEquals(0.0, SdkUtils.parseLocalizedDouble("", 4));
- assertEquals(5.0, SdkUtils.parseLocalizedDouble("test", 5)); // Invalid
-
- Locale.setDefault(Locale.FRANCE);
- assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000", -1)); // Valid
- assertEquals(0.0, SdkUtils.parseLocalizedDouble("", 8));
- }
-
- public void testFileToUrl() throws Exception {
- // path -- drive "C:" used as prefix in paths, empty for mac/linux.
- String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
- // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
- String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
-
- assertEquals(
- "file:" + uDrive + "/tmp/foo/bar",
- fileToUrlString(new File(pDrive + "/tmp/foo/bar")));
- assertEquals(
- "file:" + uDrive + "/tmp/$&+,:;=%3F@/foo%20bar%25",
- fileToUrlString(new File(pDrive + "/tmp/$&+,:;=?@/foo bar%")));
- }
-
- public void testUrlToFile() throws Exception {
- // path -- drive "C:" used as prefix in paths, empty for mac/linux.
- String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
- // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
- String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
-
- assertEquals(
- new File(pDrive + "/tmp/foo/bar"),
- urlToFile("file:" + uDrive + "/tmp/foo/bar"));
- assertEquals(
- new File(pDrive + "/tmp/$&+,:;=?@/foo bar%"),
- urlToFile("file:" + uDrive + "/tmp/$&+,:;=%3F@/foo%20bar%25"));
-
- assertEquals(
- new File(pDrive + "/tmp/foo/bar"),
- urlToFile(new URL("file:" + uDrive + "/tmp/foo/bar")));
- assertEquals(
- new File(pDrive + "/tmp/$&+,:;=?@/foo bar%"),
- urlToFile(new URL("file:" + uDrive + "/tmp/$&+,:;=%3F@/foo%20bar%25")));
- }
-
- public void testCreatePathComment() throws Exception {
- // path -- drive "C:" used as prefix in paths, empty for mac/linux.
- String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
- // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
- String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
-
- assertEquals(
- "From: file:" + uDrive + "/tmp/foo",
- createPathComment(new File(pDrive + "/tmp/foo"), false));
- assertEquals(
- " From: file:" + uDrive + "/tmp/foo ",
- createPathComment(new File(pDrive + "/tmp/foo"), true));
- assertEquals(
- "From: file:" + uDrive + "/tmp-/%2D%2D/a%2D%2Da/foo",
- createPathComment(new File(pDrive + "/tmp-/--/a--a/foo"), false));
-
- String path = "/tmp/foo";
- String urlString =
- createPathComment(new File(pDrive + path), false).substring(5); // 5: "From:".length()
- assertEquals(
- (pDrive + path).replace('/', File.separatorChar),
- urlToFile(new URL(urlString)).getPath());
-
- path = "/tmp-/--/a--a/foo";
- urlString = createPathComment(new File(pDrive + path), false).substring(5);
- assertEquals(
- (pDrive + path).replace('/', File.separatorChar),
- urlToFile(new URL(urlString)).getPath());
-
- // Make sure we handle file://path too, not just file:path
- urlString = "file:///tmp-/%2D%2D/a%2D%2Da/foo";
- assertEquals(
- path.replace('/', File.separatorChar),
- urlToFile(new URL(urlString)).getPath());
- }
-
- public void testFormattedComment() throws Exception {
- // path -- drive "C:" used as prefix in paths, empty for mac/linux.
- String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
- // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
- String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
-
- Document document = XmlUtils.parseDocumentSilently("<root/>", true);
- assertNotNull(document);
-
- // Many invalid characters in XML, such as -- and <, and characters invalid in URLs, such
- // as spaces
- String path = pDrive + "/My Program Files/--/Q&A/X<Y/foo";
- String comment = createPathComment(new File(path), true);
- Element root = document.getDocumentElement();
- assertNotNull(root);
- root.appendChild(document.createComment(comment));
- String xml = XmlUtils.toXml(document);
- assertEquals(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<root>"
- + "<!-- From: file:" + uDrive + "/My%20Program%20Files/%2D%2D/Q&A/X%3CY/foo -->"
- + "</root>",
- xml);
- int index = xml.indexOf(FILENAME_PREFIX);
- assertTrue(index != -1);
- String urlString = xml.substring(index + FILENAME_PREFIX.length(),
- xml.indexOf("-->")).trim();
- assertEquals(
- path.replace('/', File.separatorChar),
- urlToFile(new URL(urlString)).getPath());
- }
-
- public void testCopyXmlWithSourceReference() throws IOException {
- File source = File.createTempFile("source", SdkConstants.DOT_XML);
- File dest = File.createTempFile("dest", SdkConstants.DOT_XML);
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"description_search\">Search</string>\n"
- + " <string name=\"description_map\">Map</string>\n"
- + " <string name=\"description_refresh\">Refresh</string>\n"
- + " <string name=\"description_share\">Share</string>\n"
- + "</resources>",
- source, Charsets.UTF_8);
- SdkUtils.copyXmlWithSourceReference(source, dest);
-
- String sourceUrl = SdkUtils.fileToUrlString(source);
- assertEquals(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"description_search\">Search</string>\n"
- + " <string name=\"description_map\">Map</string>\n"
- + " <string name=\"description_refresh\">Refresh</string>\n"
- + " <string name=\"description_share\">Share</string>\n"
- + "</resources><!-- From: " + sourceUrl + " -->",
- Files.toString(dest, Charsets.UTF_8));
- boolean deleted = source.delete();
- assertTrue(deleted);
- deleted = dest.delete();
- assertTrue(deleted);
- }
-
- public void testNameConversionRoutines() {
- assertEquals("xml-name", SdkUtils.constantNameToXmlName("XML_NAME"));
- assertEquals("XML_NAME", SdkUtils.xmlNameToConstantName("xml-name"));
- assertEquals("xmlName", SdkUtils.constantNameToCamelCase("XML_NAME"));
- assertEquals("XML_NAME", SdkUtils.camelCaseToConstantName("xmlName"));
- }
-
- public void testGetResourceFieldName() {
- assertEquals("", SdkUtils.getResourceFieldName(""));
- assertEquals("foo", SdkUtils.getResourceFieldName("foo"));
- assertEquals("Theme_Light", SdkUtils.getResourceFieldName("Theme.Light"));
- assertEquals("Theme_Light", SdkUtils.getResourceFieldName("Theme.Light"));
- assertEquals("abc____", SdkUtils.getResourceFieldName("abc:-._"));
- }
-}
diff --git a/base/common/src/test/java/com/android/utils/XmlUtilsTest.java b/base/common/src/test/java/com/android/utils/XmlUtilsTest.java
deleted file mode 100644
index 5d3073c..0000000
--- a/base/common/src/test/java/com/android/utils/XmlUtilsTest.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.utils;
-
-import static com.android.SdkConstants.XMLNS;
-
-import com.android.SdkConstants;
-import com.android.annotations.Nullable;
-import com.google.common.base.Charsets;
-
-import junit.framework.TestCase;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.InputSource;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.Locale;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
- at SuppressWarnings("javadoc")
-public class XmlUtilsTest extends TestCase {
- public void testlookupNamespacePrefix() throws Exception {
- // Setup
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.newDocument();
- Element rootElement = document.createElement("root");
- Attr attr = document.createAttributeNS(SdkConstants.XMLNS_URI,
- "xmlns:customPrefix");
- attr.setValue(SdkConstants.ANDROID_URI);
- rootElement.getAttributes().setNamedItemNS(attr);
- document.appendChild(rootElement);
- Element root = document.getDocumentElement();
- root.appendChild(document.createTextNode(" "));
- Element foo = document.createElement("foo");
- root.appendChild(foo);
- root.appendChild(document.createTextNode(" "));
- Element bar = document.createElement("bar");
- root.appendChild(bar);
- Element baz = document.createElement("baz");
- root.appendChild(baz);
-
- String prefix = XmlUtils.lookupNamespacePrefix(baz, SdkConstants.ANDROID_URI);
- assertEquals("customPrefix", prefix);
-
- prefix = XmlUtils.lookupNamespacePrefix(baz,
- "http://schemas.android.com/tools", "tools", false);
- assertEquals("tools", prefix);
-
- prefix = XmlUtils.lookupNamespacePrefix(baz,
- "http://schemas.android.com/apk/res/my/pkg", "app", false);
- assertEquals("app", prefix);
- assertFalse(declaresNamespace(document, "http://schemas.android.com/apk/res/my/pkg"));
-
- prefix = XmlUtils.lookupNamespacePrefix(baz,
- "http://schemas.android.com/apk/res/my/pkg", "app", true /*create*/);
- assertEquals("app", prefix);
- assertTrue(declaresNamespace(document, "http://schemas.android.com/apk/res/my/pkg"));
- }
-
- private static boolean declaresNamespace(Document document, String uri) {
- NamedNodeMap attributes = document.getDocumentElement().getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- String name = attribute.getName();
- if (name.startsWith(XMLNS) && uri.equals(attribute.getValue())) {
- return true;
- }
- }
-
- return false;
- }
-
- public void testToXmlAttributeValue() throws Exception {
- assertEquals("", XmlUtils.toXmlAttributeValue(""));
- assertEquals("foo", XmlUtils.toXmlAttributeValue("foo"));
- assertEquals("foo<bar", XmlUtils.toXmlAttributeValue("foo<bar"));
- assertEquals("foo>bar", XmlUtils.toXmlAttributeValue("foo>bar"));
-
- assertEquals(""", XmlUtils.toXmlAttributeValue("\""));
- assertEquals("'", XmlUtils.toXmlAttributeValue("'"));
- assertEquals("foo"b''ar",
- XmlUtils.toXmlAttributeValue("foo\"b''ar"));
- assertEquals("<"'>&", XmlUtils.toXmlAttributeValue("<\"'>&"));
- }
-
- public void testFromXmlAttributeValue() throws Exception {
- assertEquals("", XmlUtils.fromXmlAttributeValue(""));
- assertEquals("foo", XmlUtils.fromXmlAttributeValue("foo"));
- assertEquals("foo<bar", XmlUtils.fromXmlAttributeValue("foo<bar"));
- assertEquals("foo<bar<bar>foo", XmlUtils.fromXmlAttributeValue("foo<bar<bar>foo"));
- assertEquals("foo>bar", XmlUtils.fromXmlAttributeValue("foo>bar"));
-
- assertEquals("\"", XmlUtils.fromXmlAttributeValue("""));
- assertEquals("'", XmlUtils.fromXmlAttributeValue("'"));
- assertEquals("foo\"b''ar", XmlUtils.fromXmlAttributeValue("foo"b''ar"));
- assertEquals("<\"'>&", XmlUtils.fromXmlAttributeValue("<"'>&"));
- }
-
- public void testAppendXmlAttributeValue() throws Exception {
- StringBuilder sb = new StringBuilder();
- XmlUtils.appendXmlAttributeValue(sb, "<\"'>&");
- assertEquals("<"'>&", sb.toString());
- }
-
- public void testToXmlTextValue() throws Exception {
- assertEquals("<\"'>&", XmlUtils.toXmlTextValue("<\"'>&"));
- }
-
- public void testAppendXmlTextValue() throws Exception {
- StringBuilder sb = new StringBuilder();
- XmlUtils.appendXmlTextValue(sb, "<\"'>&");
- assertEquals("<\"'>&", sb.toString());
- }
-
- public void testHasChildren() throws Exception {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.newDocument();
- assertFalse(XmlUtils.hasElementChildren(document));
- document.appendChild(document.createElement("A"));
- Element a = document.getDocumentElement();
- assertFalse(XmlUtils.hasElementChildren(a));
- a.appendChild(document.createTextNode("foo"));
- assertFalse(XmlUtils.hasElementChildren(a));
- Element b = document.createElement("B");
- a.appendChild(b);
- assertTrue(XmlUtils.hasElementChildren(a));
- assertFalse(XmlUtils.hasElementChildren(b));
- }
-
- public void testToXml() throws Exception {
- Document doc = createEmptyPlainDocument();
- assertNotNull(doc);
- Element root = doc.createElement("myroot");
- doc.appendChild(root);
- root.setAttribute("foo", "bar");
- root.setAttribute("baz", "baz");
- Element child = doc.createElement("mychild");
- root.appendChild(child);
- Element child2 = doc.createElement("hasComment");
- root.appendChild(child2);
- Node comment = doc.createComment("This is my comment");
- child2.appendChild(comment);
- Element child3 = doc.createElement("hasText");
- root.appendChild(child3);
- Node text = doc.createTextNode(" This is my text ");
- child3.appendChild(text);
-
- String xml = XmlUtils.toXml(doc);
- assertEquals(
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<myroot baz=\"baz\" foo=\"bar\"><mychild/><hasComment><!--This is my comment--></hasComment><hasText> This is my text </hasText></myroot>",
- xml);
- }
-
- public void testToXml2() throws Exception {
- String xml = ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string \n"
- + " name=\"description_search\">Search</string>\n"
- + " <string \n"
- + " name=\"description_map\">Map</string>\n"
- + " <string\n"
- + " name=\"description_refresh\">Refresh</string>\n"
- + " <string \n"
- + " name=\"description_share\">Share</string>\n"
- + "</resources>";
-
- Document doc = parse(xml);
-
- String formatted = XmlUtils.toXml(doc);
- assertEquals(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"description_search\">Search</string>\n"
- + " <string name=\"description_map\">Map</string>\n"
- + " <string name=\"description_refresh\">Refresh</string>\n"
- + " <string name=\"description_share\">Share</string>\n"
- + "</resources>",
- formatted);
- }
-
- public void testToXml3() throws Exception {
- String xml = ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<root>\n"
- + " <!-- ============== -->\n"
- + " <!-- Generic styles -->\n"
- + " <!-- ============== -->\n"
- + "</root>";
- Document doc = parse(xml);
-
- String formatted = XmlUtils.toXml(doc);
- assertEquals(xml, formatted);
- }
-
- public void testToXml3b() throws Exception {
- String xml = ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <!-- ============== -->\n"
- + " <!-- Generic styles -->\n"
- + " <!-- ============== -->\n"
- + " <string name=\"test\">test</string>\n"
- + "</resources>";
- Document doc = parse(xml);
-
- String formatted = XmlUtils.toXml(doc);
- assertEquals(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <!-- ============== -->\n"
- + " <!-- Generic styles -->\n"
- + " <!-- ============== -->\n"
- + " <string name=\"test\">test</string>\n"
- + "</resources>",
- formatted);
- }
-
-
- public void testToXml4() throws Exception {
- String xml = ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<!-- ============== -->\n"
- + "<!-- Generic styles -->\n"
- + "<!-- ============== -->\n"
- + "<root/>";
- Document doc = parse(xml);
-
- xml = XmlUtils.toXml(doc);
- assertEquals(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<!-- ============== --><!-- Generic styles --><!-- ============== --><root/>",
- xml);
- }
-
- public void testToXml5() throws Exception {
- String xml = ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<root>\n"
- + " <!-- <&'>\" -->\n"
- + "</root>";
- Document doc = parse(xml);
-
- String formatted = XmlUtils.toXml(doc);
- assertEquals(xml, formatted);
- }
-
- public void testToXml6() throws Exception {
- // Check CDATA
- String xml = ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string \n"
- + " name=\"description_search\">Search</string>\n"
- + " <string name=\"map_at\">At %1$s:<![CDATA[<br><b>%2$s</b>]]></string>\n"
- + " <string name=\"map_now_playing\">Now playing:\n"
- + "<![CDATA[\n"
- + "<br><b>%1$s</b>\n"
- + "]]></string>\n"
- + "</resources>";
-
- Document doc = parse(xml);
-
- String formatted = XmlUtils.toXml(doc);
- assertEquals(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"description_search\">Search</string>\n"
- + " <string name=\"map_at\">At %1$s:<![CDATA[<br><b>%2$s</b>]]></string>\n"
- + " <string name=\"map_now_playing\">Now playing:\n"
- + "<![CDATA[\n"
- + "<br><b>%1$s</b>\n"
- + "]]></string>\n"
- + "</resources>",
- formatted);
- }
-
-
-
- @Nullable
- private static Document createEmptyPlainDocument() throws Exception {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- factory.setIgnoringComments(true);
- DocumentBuilder builder;
- builder = factory.newDocumentBuilder();
- return builder.newDocument();
- }
-
- @Nullable
- private static Document parse(String xml) throws Exception {
- if (true) {
- return XmlUtils.parseDocumentSilently(xml, true);
- }
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- factory.setExpandEntityReferences(false);
- factory.setXIncludeAware(false);
- factory.setIgnoringComments(false);
- factory.setCoalescing(false);
- DocumentBuilder builder;
- builder = factory.newDocumentBuilder();
- return builder.parse(new InputSource(new StringReader(xml)));
- }
-
- public void testFormatFloatValue() throws Exception {
- assertEquals("1", XmlUtils.formatFloatAttribute(1.0f));
- assertEquals("2", XmlUtils.formatFloatAttribute(2.0f));
- assertEquals("1.50", XmlUtils.formatFloatAttribute(1.5f));
- assertEquals("1.50", XmlUtils.formatFloatAttribute(1.50f));
- assertEquals("1.51", XmlUtils.formatFloatAttribute(1.51f));
- assertEquals("1.51", XmlUtils.formatFloatAttribute(1.514542f));
- assertEquals("1.52", XmlUtils.formatFloatAttribute(1.516542f));
- assertEquals("-1.51", XmlUtils.formatFloatAttribute(-1.51f));
- assertEquals("-1", XmlUtils.formatFloatAttribute(-1f));
- }
-
- public void testFormatFloatValueLocale() throws Exception {
- // Ensure that the layout float values aren't affected by
- // locale settings, like using commas instead of of periods
- Locale originalDefaultLocale = Locale.getDefault();
-
- try {
- Locale.setDefault(Locale.FRENCH);
-
- // Ensure that this is a locale which uses a comma instead of a period:
- assertEquals("5,24", String.format("%.2f", 5.236f));
-
- // Ensure that the formatFloatAttribute is immune
- assertEquals("1.50", XmlUtils.formatFloatAttribute(1.5f));
- } finally {
- Locale.setDefault(originalDefaultLocale);
- }
- }
-
- public void testGetUtfReader() throws IOException {
- File file = File.createTempFile(getName(), SdkConstants.DOT_XML);
-
- BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
- OutputStreamWriter writer = new OutputStreamWriter(stream, Charsets.UTF_8);
- try {
- stream.write(0xef);
- stream.write(0xbb);
- stream.write(0xbf);
- writer.write("OK");
- } finally {
- writer.close();
- }
-
- Reader reader = XmlUtils.getUtfReader(file);
- assertEquals('O', reader.read());
- assertEquals('K', reader.read());
- assertEquals(-1, reader.read());
-
- //noinspection ResultOfMethodCallIgnored
- file.delete();
- }
-
- public void testStripBom() {
- assertEquals("", XmlUtils.stripBom(""));
- assertEquals("Hello", XmlUtils.stripBom("Hello"));
- assertEquals("Hello", XmlUtils.stripBom("\uFEFFHello"));
- }
-
- public void testParseDocument() throws Exception {
- String xml = "" +
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " android:layout_width=\"match_parent\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:orientation=\"vertical\" >\n" +
- "\n" +
- " <Button\n" +
- " android:id=\"@+id/button1\"\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:text=\"Button\" />\n" +
- " some text\n" +
- "\n" +
- "</LinearLayout>\n";
-
- Document document = XmlUtils.parseDocument(xml, true);
- assertNotNull(document);
- assertNotNull(document.getDocumentElement());
- assertEquals("LinearLayout", document.getDocumentElement().getTagName());
-
- // Add BOM
- xml = '\uFEFF' + xml;
- document = XmlUtils.parseDocument(xml, true);
- assertNotNull(document);
- assertNotNull(document.getDocumentElement());
- assertEquals("LinearLayout", document.getDocumentElement().getTagName());
- }
-
- public void testParseUtfXmlFile() throws Exception {
- File file = File.createTempFile(getName(), SdkConstants.DOT_XML);
- String xml = "" +
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " android:layout_width=\"match_parent\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:orientation=\"vertical\" >\n" +
- "\n" +
- " <Button\n" +
- " android:id=\"@+id/button1\"\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:text=\"Button\" />\n" +
- " some text\n" +
- "\n" +
- "</LinearLayout>\n";
-
- BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
- OutputStreamWriter writer = new OutputStreamWriter(stream, Charsets.UTF_8);
- try {
- stream.write(0xef);
- stream.write(0xbb);
- stream.write(0xbf);
- writer.write(xml);
- } finally {
- writer.close();
- }
-
- Document document = XmlUtils.parseUtfXmlFile(file, true);
- assertNotNull(document);
- assertNotNull(document.getDocumentElement());
- assertEquals("LinearLayout", document.getDocumentElement().getTagName());
-
- //noinspection ResultOfMethodCallIgnored
- file.delete();
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java b/base/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java
deleted file mode 100644
index b390f93..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java
+++ /dev/null
@@ -1,894 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.Nullable;
-import com.android.ddmlib.log.LogReceiver;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class to handle requests and connections to adb.
- * <p/>{@link AndroidDebugBridge} is the public API to connection to adb, while {@link AdbHelper}
- * does the low level stuff.
- * <p/>This currently uses spin-wait non-blocking I/O. A Selector would be more efficient,
- * but seems like overkill for what we're doing here.
- */
-final class AdbHelper {
-
- // public static final long kOkay = 0x59414b4fL;
- // public static final long kFail = 0x4c494146L;
-
- static final int WAIT_TIME = 5; // spin-wait sleep, in ms
-
- static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
-
- /** do not instantiate */
- private AdbHelper() {
- }
-
- /**
- * Response from ADB.
- */
- static class AdbResponse {
- public AdbResponse() {
- message = "";
- }
-
- public boolean okay; // first 4 bytes in response were "OKAY"?
-
- public String message; // diagnostic string if #okay is false
- }
-
- /**
- * Create and connect a new pass-through socket, from the host to a port on
- * the device.
- *
- * @param adbSockAddr
- * @param device the device to connect to. Can be null in which case the connection will be
- * to the first available device.
- * @param devicePort the port we're opening
- * @throws TimeoutException in case of timeout on the connection.
- * @throws IOException in case of I/O error on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- */
- public static SocketChannel open(InetSocketAddress adbSockAddr,
- Device device, int devicePort)
- throws IOException, TimeoutException, AdbCommandRejectedException {
-
- SocketChannel adbChan = SocketChannel.open(adbSockAddr);
- try {
- adbChan.socket().setTcpNoDelay(true);
- adbChan.configureBlocking(false);
-
- // if the device is not -1, then we first tell adb we're looking to
- // talk to a specific device
- setDevice(adbChan, device);
-
- byte[] req = createAdbForwardRequest(null, devicePort);
- // Log.hexDump(req);
-
- write(adbChan, req);
-
- AdbResponse resp = readAdbResponse(adbChan, false);
- if (!resp.okay) {
- throw new AdbCommandRejectedException(resp.message);
- }
-
- adbChan.configureBlocking(true);
- } catch (TimeoutException e) {
- adbChan.close();
- throw e;
- } catch (IOException e) {
- adbChan.close();
- throw e;
- } catch (AdbCommandRejectedException e) {
- adbChan.close();
- throw e;
- }
-
- return adbChan;
- }
-
- /**
- * Creates and connects a new pass-through socket, from the host to a port on
- * the device.
- *
- * @param adbSockAddr
- * @param device the device to connect to. Can be null in which case the connection will be
- * to the first available device.
- * @param pid the process pid to connect to.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- public static SocketChannel createPassThroughConnection(InetSocketAddress adbSockAddr,
- Device device, int pid)
- throws TimeoutException, AdbCommandRejectedException, IOException {
-
- SocketChannel adbChan = SocketChannel.open(adbSockAddr);
- try {
- adbChan.socket().setTcpNoDelay(true);
- adbChan.configureBlocking(false);
-
- // if the device is not -1, then we first tell adb we're looking to
- // talk to a specific device
- setDevice(adbChan, device);
-
- byte[] req = createJdwpForwardRequest(pid);
- // Log.hexDump(req);
-
- write(adbChan, req);
-
- AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
- if (!resp.okay) {
- throw new AdbCommandRejectedException(resp.message);
- }
-
- adbChan.configureBlocking(true);
- } catch (TimeoutException e) {
- adbChan.close();
- throw e;
- } catch (IOException e) {
- adbChan.close();
- throw e;
- } catch (AdbCommandRejectedException e) {
- adbChan.close();
- throw e;
- }
-
- return adbChan;
- }
-
- /**
- * Creates a port forwarding request for adb. This returns an array
- * containing "####tcp:{port}:{addStr}".
- * @param addrStr the host. Can be null.
- * @param port the port on the device. This does not need to be numeric.
- */
- private static byte[] createAdbForwardRequest(String addrStr, int port) {
- String reqStr;
-
- if (addrStr == null)
- reqStr = "tcp:" + port;
- else
- reqStr = "tcp:" + port + ":" + addrStr;
- return formAdbRequest(reqStr);
- }
-
- /**
- * Creates a port forwarding request to a jdwp process. This returns an array
- * containing "####jwdp:{pid}".
- * @param pid the jdwp process pid on the device.
- */
- private static byte[] createJdwpForwardRequest(int pid) {
- String reqStr = String.format("jdwp:%1$d", pid); //$NON-NLS-1$
- return formAdbRequest(reqStr);
- }
-
- /**
- * Create an ASCII string preceded by four hex digits. The opening "####"
- * is the length of the rest of the string, encoded as ASCII hex (case
- * doesn't matter).
- */
- public static byte[] formAdbRequest(String req) {
- String resultStr = String.format("%04X%s", req.length(), req); //$NON-NLS-1$
- byte[] result;
- try {
- result = resultStr.getBytes(DEFAULT_ENCODING);
- } catch (UnsupportedEncodingException uee) {
- uee.printStackTrace(); // not expected
- return null;
- }
- assert result.length == req.length() + 4;
- return result;
- }
-
- /**
- * Reads the response from ADB after a command.
- * @param chan The socket channel that is connected to adb.
- * @param readDiagString If true, we're expecting an OKAY response to be
- * followed by a diagnostic string. Otherwise, we only expect the
- * diagnostic string to follow a FAIL.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws IOException in case of I/O error on the connection.
- */
- static AdbResponse readAdbResponse(SocketChannel chan, boolean readDiagString)
- throws TimeoutException, IOException {
-
- AdbResponse resp = new AdbResponse();
-
- byte[] reply = new byte[4];
- read(chan, reply);
-
- if (isOkay(reply)) {
- resp.okay = true;
- } else {
- readDiagString = true; // look for a reason after the FAIL
- resp.okay = false;
- }
-
- // not a loop -- use "while" so we can use "break"
- try {
- while (readDiagString) {
- // length string is in next 4 bytes
- byte[] lenBuf = new byte[4];
- read(chan, lenBuf);
-
- String lenStr = replyToString(lenBuf);
-
- int len;
- try {
- len = Integer.parseInt(lenStr, 16);
- } catch (NumberFormatException nfe) {
- Log.w("ddms", "Expected digits, got '" + lenStr + "': "
- + lenBuf[0] + " " + lenBuf[1] + " " + lenBuf[2] + " "
- + lenBuf[3]);
- Log.w("ddms", "reply was " + replyToString(reply));
- break;
- }
-
- byte[] msg = new byte[len];
- read(chan, msg);
-
- resp.message = replyToString(msg);
- Log.v("ddms", "Got reply '" + replyToString(reply) + "', diag='"
- + resp.message + "'");
-
- break;
- }
- } catch (Exception e) {
- // ignore those, since it's just reading the diagnose string, the response will
- // contain okay==false anyway.
- }
-
- return resp;
- }
-
- /**
- * Retrieve the frame buffer from the device with the given timeout. A timeout of 0 indicates
- * that it will wait forever.
- *
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device, long timeout,
- TimeUnit unit)
- throws TimeoutException, AdbCommandRejectedException, IOException {
-
- RawImage imageParams = new RawImage();
- byte[] request = formAdbRequest("framebuffer:"); //$NON-NLS-1$
- byte[] nudge = {
- 0
- };
- byte[] reply;
-
- SocketChannel adbChan = null;
- try {
- adbChan = SocketChannel.open(adbSockAddr);
- adbChan.configureBlocking(false);
-
- // if the device is not -1, then we first tell adb we're looking to talk
- // to a specific device
- setDevice(adbChan, device);
-
- write(adbChan, request);
-
- AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
- if (!resp.okay) {
- throw new AdbCommandRejectedException(resp.message);
- }
-
- // first the protocol version.
- reply = new byte[4];
- read(adbChan, reply);
-
- ByteBuffer buf = ByteBuffer.wrap(reply);
- buf.order(ByteOrder.LITTLE_ENDIAN);
-
- int version = buf.getInt();
-
- // get the header size (this is a count of int)
- int headerSize = RawImage.getHeaderSize(version);
-
- // read the header
- reply = new byte[headerSize * 4];
- read(adbChan, reply);
-
- buf = ByteBuffer.wrap(reply);
- buf.order(ByteOrder.LITTLE_ENDIAN);
-
- // fill the RawImage with the header
- if (!imageParams.readHeader(version, buf)) {
- Log.e("Screenshot", "Unsupported protocol: " + version);
- return null;
- }
-
- Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size="
- + imageParams.size + ", width=" + imageParams.width
- + ", height=" + imageParams.height);
-
- write(adbChan, nudge);
-
- reply = new byte[imageParams.size];
- read(adbChan, reply, imageParams.size, unit.toMillis(timeout));
-
- imageParams.data = reply;
- } finally {
- if (adbChan != null) {
- adbChan.close();
- }
- }
-
- return imageParams;
- }
-
- /**
- * @deprecated Use {@link #executeRemoteCommand(java.net.InetSocketAddress, String, IDevice, IShellOutputReceiver, long, java.util.concurrent.TimeUnit)}.
- */
- @Deprecated
- static void executeRemoteCommand(InetSocketAddress adbSockAddr,
- String command, IDevice device, IShellOutputReceiver rcvr, int maxTimeToOutputResponse)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
- executeRemoteCommand(adbSockAddr, command, device, rcvr, maxTimeToOutputResponse,
- TimeUnit.MILLISECONDS);
- }
-
- /**
- * Executes a shell command on the device and retrieve the output. The output is
- * handed to <var>rcvr</var> as it arrives.
- *
- * @param adbSockAddr the {@link InetSocketAddress} to adb.
- * @param command the shell command to execute
- * @param device the {@link IDevice} on which to execute the command.
- * @param rcvr the {@link IShellOutputReceiver} that will receives the output of the shell
- * command
- * @param maxTimeToOutputResponse max time between command output. If more time passes
- * between command output, the method will throw
- * {@link ShellCommandUnresponsiveException}. A value of 0 means the method will
- * wait forever for command output and never throw.
- * @param maxTimeUnits Units for non-zero {@code maxTimeToOutputResponse} values.
- * @throws TimeoutException in case of timeout on the connection when sending the command.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
- * for a period longer than <var>maxTimeToOutputResponse</var>.
- * @throws IOException in case of I/O error on the connection.
- *
- * @see DdmPreferences#getTimeOut()
- */
- static void executeRemoteCommand(InetSocketAddress adbSockAddr,
- String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse,
- TimeUnit maxTimeUnits) throws TimeoutException, AdbCommandRejectedException,
- ShellCommandUnresponsiveException, IOException {
-
- executeRemoteCommand(adbSockAddr, AdbService.SHELL, command, device, rcvr, maxTimeToOutputResponse,
- maxTimeUnits, null /* inputStream */);
- }
-
- /**
- * Identify which adb service the command should target.
- */
- public enum AdbService {
- /**
- * the shell service
- */
- SHELL,
-
- /**
- * The exec service.
- */
- EXEC
- }
-
- /**
- * Executes a remote command on the device and retrieve the output. The output is
- * handed to <var>rcvr</var> as it arrives. The command is execute by the remote service
- * identified by the adbService parameter.
- *
- * @param adbSockAddr the {@link InetSocketAddress} to adb.
- * @param adbService the {@link com.android.ddmlib.AdbHelper.AdbService} to use to run the
- * command.
- * @param command the shell command to execute
- * @param device the {@link IDevice} on which to execute the command.
- * @param rcvr the {@link IShellOutputReceiver} that will receives the output of the shell
- * command
- * @param maxTimeToOutputResponse max time between command output. If more time passes
- * between command output, the method will throw
- * {@link ShellCommandUnresponsiveException}. A value of 0 means the method will
- * wait forever for command output and never throw.
- * @param maxTimeUnits Units for non-zero {@code maxTimeToOutputResponse} values.
- * @param is a optional {@link InputStream} to be streamed up after invoking the command
- * and before retrieving the response.
- * @throws TimeoutException in case of timeout on the connection when sending the command.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
- * for a period longer than <var>maxTimeToOutputResponse</var>.
- * @throws IOException in case of I/O error on the connection.
- *
- * @see DdmPreferences#getTimeOut()
- */
- static void executeRemoteCommand(InetSocketAddress adbSockAddr, AdbService adbService,
- String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse,
- TimeUnit maxTimeUnits,
- @Nullable InputStream is) throws TimeoutException, AdbCommandRejectedException,
- ShellCommandUnresponsiveException, IOException {
-
- long maxTimeToOutputMs = 0;
- if (maxTimeToOutputResponse > 0) {
- if (maxTimeUnits == null) {
- throw new NullPointerException("Time unit must not be null for non-zero max.");
- }
- maxTimeToOutputMs = maxTimeUnits.toMillis(maxTimeToOutputResponse);
- }
-
- Log.v("ddms", "execute: running " + command);
-
- SocketChannel adbChan = null;
- try {
- adbChan = SocketChannel.open(adbSockAddr);
- adbChan.configureBlocking(false);
-
- // if the device is not -1, then we first tell adb we're looking to
- // talk
- // to a specific device
- setDevice(adbChan, device);
-
- byte[] request = formAdbRequest(adbService.name().toLowerCase() + ":" + command); //$NON-NLS-1$
- write(adbChan, request);
-
- AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
- if (!resp.okay) {
- Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message);
- throw new AdbCommandRejectedException(resp.message);
- }
-
- byte[] data = new byte[16384];
-
- // stream the input file if present.
- if (is != null) {
- int read;
- while ((read = is.read(data)) != -1) {
- ByteBuffer buf = ByteBuffer.wrap(data, 0, read);
- int written = 0;
- while (buf.hasRemaining()) {
- written += adbChan.write(buf);
- }
- if (written != read) {
- Log.e("ddms",
- "ADB write inconsistency, wrote " + written + "expected " + read);
- throw new AdbCommandRejectedException("write failed");
- }
- }
- }
-
- ByteBuffer buf = ByteBuffer.wrap(data);
- buf.clear();
- long timeToResponseCount = 0;
- while (true) {
- int count;
-
- if (rcvr != null && rcvr.isCancelled()) {
- Log.v("ddms", "execute: cancelled");
- break;
- }
-
- count = adbChan.read(buf);
- if (count < 0) {
- // we're at the end, we flush the output
- rcvr.flush();
- Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: "
- + count);
- break;
- } else if (count == 0) {
- try {
- int wait = WAIT_TIME * 5;
- timeToResponseCount += wait;
- if (maxTimeToOutputMs > 0 && timeToResponseCount > maxTimeToOutputMs) {
- throw new ShellCommandUnresponsiveException();
- }
- Thread.sleep(wait);
- } catch (InterruptedException ie) {
- }
- } else {
- // reset timeout
- timeToResponseCount = 0;
-
- // send data to receiver if present
- if (rcvr != null) {
- rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
- }
- buf.rewind();
- }
- }
- } finally {
- if (adbChan != null) {
- adbChan.close();
- }
- Log.v("ddms", "execute: returning");
- }
- }
-
- /**
- * Runs the Event log service on the {@link Device}, and provides its output to the
- * {@link LogReceiver}.
- * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
- * @param adbSockAddr the socket address to connect to adb
- * @param device the Device on which to run the service
- * @param rcvr the {@link LogReceiver} to receive the log output
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- public static void runEventLogService(InetSocketAddress adbSockAddr, Device device,
- LogReceiver rcvr) throws TimeoutException, AdbCommandRejectedException, IOException {
- runLogService(adbSockAddr, device, "events", rcvr); //$NON-NLS-1$
- }
-
- /**
- * Runs a log service on the {@link Device}, and provides its output to the {@link LogReceiver}.
- * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
- * @param adbSockAddr the socket address to connect to adb
- * @param device the Device on which to run the service
- * @param logName the name of the log file to output
- * @param rcvr the {@link LogReceiver} to receive the log output
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- public static void runLogService(InetSocketAddress adbSockAddr, Device device, String logName,
- LogReceiver rcvr) throws TimeoutException, AdbCommandRejectedException, IOException {
- SocketChannel adbChan = null;
-
- try {
- adbChan = SocketChannel.open(adbSockAddr);
- adbChan.configureBlocking(false);
-
- // if the device is not -1, then we first tell adb we're looking to talk
- // to a specific device
- setDevice(adbChan, device);
-
- byte[] request = formAdbRequest("log:" + logName);
- write(adbChan, request);
-
- AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
- if (!resp.okay) {
- throw new AdbCommandRejectedException(resp.message);
- }
-
- byte[] data = new byte[16384];
- ByteBuffer buf = ByteBuffer.wrap(data);
- while (true) {
- int count;
-
- if (rcvr != null && rcvr.isCancelled()) {
- break;
- }
-
- count = adbChan.read(buf);
- if (count < 0) {
- break;
- } else if (count == 0) {
- try {
- Thread.sleep(WAIT_TIME * 5);
- } catch (InterruptedException ie) {
- }
- } else {
- if (rcvr != null) {
- rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position());
- }
- buf.rewind();
- }
- }
- } finally {
- if (adbChan != null) {
- adbChan.close();
- }
- }
- }
-
- /**
- * Creates a port forwarding between a local and a remote port.
- * @param adbSockAddr the socket address to connect to adb
- * @param device the device on which to do the port forwarding
- * @param localPortSpec specification of the local port to forward, should be of format
- * tcp:<port number>
- * @param remotePortSpec specification of the remote port to forward to, one of:
- * tcp:<port>
- * localabstract:<unix domain socket name>
- * localreserved:<unix domain socket name>
- * localfilesystem:<unix domain socket name>
- * dev:<character device name>
- * jdwp:<process pid> (remote only)
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- public static void createForward(InetSocketAddress adbSockAddr, Device device,
- String localPortSpec, String remotePortSpec)
- throws TimeoutException, AdbCommandRejectedException, IOException {
-
- SocketChannel adbChan = null;
- try {
- adbChan = SocketChannel.open(adbSockAddr);
- adbChan.configureBlocking(false);
-
- byte[] request = formAdbRequest(String.format(
- "host-serial:%1$s:forward:%2$s;%3$s", //$NON-NLS-1$
- device.getSerialNumber(), localPortSpec, remotePortSpec));
-
- write(adbChan, request);
-
- AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
- if (!resp.okay) {
- Log.w("create-forward", "Error creating forward: " + resp.message);
- throw new AdbCommandRejectedException(resp.message);
- }
- } finally {
- if (adbChan != null) {
- adbChan.close();
- }
- }
- }
-
- /**
- * Remove a port forwarding between a local and a remote port.
- * @param adbSockAddr the socket address to connect to adb
- * @param device the device on which to remove the port forwarding
- * @param localPortSpec specification of the local port that was forwarded, should be of format
- * tcp:<port number>
- * @param remotePortSpec specification of the remote port forwarded to, one of:
- * tcp:<port>
- * localabstract:<unix domain socket name>
- * localreserved:<unix domain socket name>
- * localfilesystem:<unix domain socket name>
- * dev:<character device name>
- * jdwp:<process pid> (remote only)
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- public static void removeForward(InetSocketAddress adbSockAddr, Device device,
- String localPortSpec, String remotePortSpec)
- throws TimeoutException, AdbCommandRejectedException, IOException {
-
- SocketChannel adbChan = null;
- try {
- adbChan = SocketChannel.open(adbSockAddr);
- adbChan.configureBlocking(false);
-
- byte[] request = formAdbRequest(String.format(
- "host-serial:%1$s:killforward:%2$s", //$NON-NLS-1$
- device.getSerialNumber(), localPortSpec));
-
- write(adbChan, request);
-
- AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
- if (!resp.okay) {
- Log.w("remove-forward", "Error creating forward: " + resp.message);
- throw new AdbCommandRejectedException(resp.message);
- }
- } finally {
- if (adbChan != null) {
- adbChan.close();
- }
- }
- }
-
- /**
- * Checks to see if the first four bytes in "reply" are OKAY.
- */
- static boolean isOkay(byte[] reply) {
- return reply[0] == (byte)'O' && reply[1] == (byte)'K'
- && reply[2] == (byte)'A' && reply[3] == (byte)'Y';
- }
-
- /**
- * Converts an ADB reply to a string.
- */
- static String replyToString(byte[] reply) {
- String result;
- try {
- result = new String(reply, DEFAULT_ENCODING);
- } catch (UnsupportedEncodingException uee) {
- uee.printStackTrace(); // not expected
- result = "";
- }
- return result;
- }
-
- /**
- * Reads from the socket until the array is filled, or no more data is coming (because
- * the socket closed or the timeout expired).
- * <p/>This uses the default time out value.
- *
- * @param chan the opened socket to read from. It must be in non-blocking
- * mode for timeouts to work
- * @param data the buffer to store the read data into.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws IOException in case of I/O error on the connection.
- */
- static void read(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
- read(chan, data, -1, DdmPreferences.getTimeOut());
- }
-
- /**
- * Reads from the socket until the array is filled, the optional length
- * is reached, or no more data is coming (because the socket closed or the
- * timeout expired). After "timeout" milliseconds since the
- * previous successful read, this will return whether or not new data has
- * been found.
- *
- * @param chan the opened socket to read from. It must be in non-blocking
- * mode for timeouts to work
- * @param data the buffer to store the read data into.
- * @param length the length to read or -1 to fill the data buffer completely
- * @param timeout The timeout value in ms. A timeout of zero means "wait forever".
- */
- static void read(SocketChannel chan, byte[] data, int length, long timeout)
- throws TimeoutException, IOException {
- ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
- int numWaits = 0;
-
- while (buf.position() != buf.limit()) {
- int count;
-
- count = chan.read(buf);
- if (count < 0) {
- Log.d("ddms", "read: channel EOF");
- throw new IOException("EOF");
- } else if (count == 0) {
- // TODO: need more accurate timeout?
- if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
- Log.d("ddms", "read: timeout");
- throw new TimeoutException();
- }
- // non-blocking spin
- try {
- Thread.sleep(WAIT_TIME);
- } catch (InterruptedException ie) {
- }
- numWaits++;
- } else {
- numWaits = 0;
- }
- }
- }
-
- /**
- * Write until all data in "data" is written or the connection fails or times out.
- * <p/>This uses the default time out value.
- * @param chan the opened socket to write to.
- * @param data the buffer to send.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws IOException in case of I/O error on the connection.
- */
- static void write(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
- write(chan, data, -1, DdmPreferences.getTimeOut());
- }
-
- /**
- * Write until all data in "data" is written, the optional length is reached,
- * the timeout expires, or the connection fails. Returns "true" if all
- * data was written.
- * @param chan the opened socket to write to.
- * @param data the buffer to send.
- * @param length the length to write or -1 to send the whole buffer.
- * @param timeout The timeout value. A timeout of zero means "wait forever".
- * @throws TimeoutException in case of timeout on the connection.
- * @throws IOException in case of I/O error on the connection.
- */
- static void write(SocketChannel chan, byte[] data, int length, int timeout)
- throws TimeoutException, IOException {
- ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
- int numWaits = 0;
-
- while (buf.position() != buf.limit()) {
- int count;
-
- count = chan.write(buf);
- if (count < 0) {
- Log.d("ddms", "write: channel EOF");
- throw new IOException("channel EOF");
- } else if (count == 0) {
- // TODO: need more accurate timeout?
- if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
- Log.d("ddms", "write: timeout");
- throw new TimeoutException();
- }
- // non-blocking spin
- try {
- Thread.sleep(WAIT_TIME);
- } catch (InterruptedException ie) {
- }
- numWaits++;
- } else {
- numWaits = 0;
- }
- }
- }
-
- /**
- * tells adb to talk to a specific device
- *
- * @param adbChan the socket connection to adb
- * @param device The device to talk to.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- static void setDevice(SocketChannel adbChan, IDevice device)
- throws TimeoutException, AdbCommandRejectedException, IOException {
- // if the device is not -1, then we first tell adb we're looking to talk
- // to a specific device
- if (device != null) {
- String msg = "host:transport:" + device.getSerialNumber(); //$NON-NLS-1$
- byte[] device_query = formAdbRequest(msg);
-
- write(adbChan, device_query);
-
- AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
- if (!resp.okay) {
- throw new AdbCommandRejectedException(resp.message,
- true/*errorDuringDeviceSelection*/);
- }
- }
- }
-
- /**
- * Reboot the device.
- *
- * @param into what to reboot into (recovery, bootloader). Or null to just reboot.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- public static void reboot(String into, InetSocketAddress adbSockAddr,
- Device device) throws TimeoutException, AdbCommandRejectedException, IOException {
- byte[] request;
- if (into == null) {
- request = formAdbRequest("reboot:"); //$NON-NLS-1$
- } else {
- request = formAdbRequest("reboot:" + into); //$NON-NLS-1$
- }
-
- SocketChannel adbChan = null;
- try {
- adbChan = SocketChannel.open(adbSockAddr);
- adbChan.configureBlocking(false);
-
- // if the device is not -1, then we first tell adb we're looking to talk
- // to a specific device
- setDevice(adbChan, device);
-
- write(adbChan, request);
- } finally {
- if (adbChan != null) {
- adbChan.close();
- }
- }
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java b/base/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
deleted file mode 100644
index 13dec70..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
+++ /dev/null
@@ -1,1183 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.ddmlib.Log.LogLevel;
-import com.google.common.base.Joiner;
-import com.google.common.base.Throwables;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.SettableFuture;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.Thread.State;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
-import java.security.InvalidParameterException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A connection to the host-side android debug bridge (adb)
- * <p/>This is the central point to communicate with any devices, emulators, or the applications
- * running on them.
- * <p/><b>{@link #init(boolean)} must be called before anything is done.</b>
- */
-public final class AndroidDebugBridge {
-
- /*
- * Minimum and maximum version of adb supported. This correspond to
- * ADB_SERVER_VERSION found in //device/tools/adb/adb.h
- */
- private static final AdbVersion MIN_ADB_VERSION = AdbVersion.parseFrom("1.0.20");
-
- private static final String ADB = "adb"; //$NON-NLS-1$
- private static final String DDMS = "ddms"; //$NON-NLS-1$
- private static final String SERVER_PORT_ENV_VAR = "ANDROID_ADB_SERVER_PORT"; //$NON-NLS-1$
-
- // Where to find the ADB bridge.
- static final String DEFAULT_ADB_HOST = "127.0.0.1"; //$NON-NLS-1$
- static final int DEFAULT_ADB_PORT = 5037;
-
- /** Port where adb server will be started **/
- private static int sAdbServerPort = 0;
-
- private static InetAddress sHostAddr;
- private static InetSocketAddress sSocketAddr;
-
- private static AndroidDebugBridge sThis;
- private static boolean sInitialized = false;
- private static boolean sClientSupport;
-
- /** Full path to adb. */
- private String mAdbOsLocation = null;
-
- private boolean mVersionCheck;
-
- private boolean mStarted = false;
-
- private DeviceMonitor mDeviceMonitor;
-
- private static final ArrayList<IDebugBridgeChangeListener> sBridgeListeners =
- new ArrayList<IDebugBridgeChangeListener>();
- private static final ArrayList<IDeviceChangeListener> sDeviceListeners =
- new ArrayList<IDeviceChangeListener>();
- private static final ArrayList<IClientChangeListener> sClientListeners =
- new ArrayList<IClientChangeListener>();
-
- // lock object for synchronization
- private static final Object sLock = sBridgeListeners;
-
- /**
- * Classes which implement this interface provide a method that deals
- * with {@link AndroidDebugBridge} changes.
- */
- public interface IDebugBridgeChangeListener {
- /**
- * Sent when a new {@link AndroidDebugBridge} is connected.
- * <p/>
- * This is sent from a non UI thread.
- * @param bridge the new {@link AndroidDebugBridge} object.
- */
- void bridgeChanged(AndroidDebugBridge bridge);
- }
-
- /**
- * Classes which implement this interface provide methods that deal
- * with {@link IDevice} addition, deletion, and changes.
- */
- public interface IDeviceChangeListener {
- /**
- * Sent when the a device is connected to the {@link AndroidDebugBridge}.
- * <p/>
- * This is sent from a non UI thread.
- * @param device the new device.
- */
- void deviceConnected(IDevice device);
-
- /**
- * Sent when the a device is connected to the {@link AndroidDebugBridge}.
- * <p/>
- * This is sent from a non UI thread.
- * @param device the new device.
- */
- void deviceDisconnected(IDevice device);
-
- /**
- * Sent when a device data changed, or when clients are started/terminated on the device.
- * <p/>
- * This is sent from a non UI thread.
- * @param device the device that was updated.
- * @param changeMask the mask describing what changed. It can contain any of the following
- * values: {@link IDevice#CHANGE_BUILD_INFO}, {@link IDevice#CHANGE_STATE},
- * {@link IDevice#CHANGE_CLIENT_LIST}
- */
- void deviceChanged(IDevice device, int changeMask);
- }
-
- /**
- * Classes which implement this interface provide methods that deal
- * with {@link Client} changes.
- */
- public interface IClientChangeListener {
- /**
- * Sent when an existing client information changed.
- * <p/>
- * This is sent from a non UI thread.
- * @param client the updated client.
- * @param changeMask the bit mask describing the changed properties. It can contain
- * any of the following values: {@link Client#CHANGE_INFO},
- * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
- * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
- * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
- */
- void clientChanged(Client client, int changeMask);
- }
-
- /**
- * Initialized the library only if needed.
- *
- * @param clientSupport Indicates whether the library should enable the monitoring and
- * interaction with applications running on the devices.
- *
- * @see #init(boolean)
- */
- public static synchronized void initIfNeeded(boolean clientSupport) {
- if (sInitialized) {
- return;
- }
-
- init(clientSupport);
- }
-
- /**
- * Initializes the <code>ddm</code> library.
- * <p/>This must be called once <b>before</b> any call to
- * {@link #createBridge(String, boolean)}.
- * <p>The library can be initialized in 2 ways:
- * <ul>
- * <li>Mode 1: <var>clientSupport</var> == <code>true</code>.<br>The library monitors the
- * devices and the applications running on them. It will connect to each application, as a
- * debugger of sort, to be able to interact with them through JDWP packets.</li>
- * <li>Mode 2: <var>clientSupport</var> == <code>false</code>.<br>The library only monitors
- * devices. The applications are left untouched, letting other tools built on
- * <code>ddmlib</code> to connect a debugger to them.</li>
- * </ul>
- * <p/><b>Only one tool can run in mode 1 at the same time.</b>
- * <p/>Note that mode 1 does not prevent debugging of applications running on devices. Mode 1
- * lets debuggers connect to <code>ddmlib</code> which acts as a proxy between the debuggers and
- * the applications to debug. See {@link Client#getDebuggerListenPort()}.
- * <p/>The preferences of <code>ddmlib</code> should also be initialized with whatever default
- * values were changed from the default values.
- * <p/>When the application quits, {@link #terminate()} should be called.
- * @param clientSupport Indicates whether the library should enable the monitoring and
- * interaction with applications running on the devices.
- * @see AndroidDebugBridge#createBridge(String, boolean)
- * @see DdmPreferences
- */
- public static synchronized void init(boolean clientSupport) {
- if (sInitialized) {
- throw new IllegalStateException("AndroidDebugBridge.init() has already been called.");
- }
- sInitialized = true;
- sClientSupport = clientSupport;
-
- // Determine port and instantiate socket address.
- initAdbSocketAddr();
-
- MonitorThread monitorThread = MonitorThread.createInstance();
- monitorThread.start();
-
- HandleHello.register(monitorThread);
- HandleAppName.register(monitorThread);
- HandleTest.register(monitorThread);
- HandleThread.register(monitorThread);
- HandleHeap.register(monitorThread);
- HandleWait.register(monitorThread);
- HandleProfiling.register(monitorThread);
- HandleNativeHeap.register(monitorThread);
- HandleViewDebug.register(monitorThread);
- }
-
- /**
- * Terminates the ddm library. This must be called upon application termination.
- */
- public static synchronized void terminate() {
- // kill the monitoring services
- if (sThis != null && sThis.mDeviceMonitor != null) {
- sThis.mDeviceMonitor.stop();
- sThis.mDeviceMonitor = null;
- }
-
- MonitorThread monitorThread = MonitorThread.getInstance();
- if (monitorThread != null) {
- monitorThread.quit();
- }
-
- sInitialized = false;
- }
-
- /**
- * Returns whether the ddmlib is setup to support monitoring and interacting with
- * {@link Client}s running on the {@link IDevice}s.
- */
- static boolean getClientSupport() {
- return sClientSupport;
- }
-
- /**
- * Returns the socket address of the ADB server on the host.
- */
- public static InetSocketAddress getSocketAddress() {
- return sSocketAddr;
- }
-
- /**
- * Creates a {@link AndroidDebugBridge} that is not linked to any particular executable.
- * <p/>This bridge will expect adb to be running. It will not be able to start/stop/restart
- * adb.
- * <p/>If a bridge has already been started, it is directly returned with no changes (similar
- * to calling {@link #getBridge()}).
- * @return a connected bridge.
- */
- public static AndroidDebugBridge createBridge() {
- synchronized (sLock) {
- if (sThis != null) {
- return sThis;
- }
-
- try {
- sThis = new AndroidDebugBridge();
- sThis.start();
- } catch (InvalidParameterException e) {
- sThis = null;
- }
-
- // because the listeners could remove themselves from the list while processing
- // their event callback, we make a copy of the list and iterate on it instead of
- // the main list.
- // This mostly happens when the application quits.
- IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
- new IDebugBridgeChangeListener[sBridgeListeners.size()]);
-
- // notify the listeners of the change
- for (IDebugBridgeChangeListener listener : listenersCopy) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.bridgeChanged(sThis);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
-
- return sThis;
- }
- }
-
-
- /**
- * Creates a new debug bridge from the location of the command line tool.
- * <p/>
- * Any existing server will be disconnected, unless the location is the same and
- * <code>forceNewBridge</code> is set to false.
- * @param osLocation the location of the command line tool 'adb'
- * @param forceNewBridge force creation of a new bridge even if one with the same location
- * already exists.
- * @return a connected bridge.
- */
- public static AndroidDebugBridge createBridge(String osLocation, boolean forceNewBridge) {
- synchronized (sLock) {
- if (sThis != null) {
- if (sThis.mAdbOsLocation != null && sThis.mAdbOsLocation.equals(osLocation) &&
- !forceNewBridge) {
- return sThis;
- } else {
- // stop the current server
- sThis.stop();
- }
- }
-
- try {
- sThis = new AndroidDebugBridge(osLocation);
- sThis.start();
- } catch (InvalidParameterException e) {
- sThis = null;
- }
-
- // because the listeners could remove themselves from the list while processing
- // their event callback, we make a copy of the list and iterate on it instead of
- // the main list.
- // This mostly happens when the application quits.
- IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
- new IDebugBridgeChangeListener[sBridgeListeners.size()]);
-
- // notify the listeners of the change
- for (IDebugBridgeChangeListener listener : listenersCopy) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.bridgeChanged(sThis);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
-
- return sThis;
- }
- }
-
- /**
- * Returns the current debug bridge. Can be <code>null</code> if none were created.
- */
- public static AndroidDebugBridge getBridge() {
- return sThis;
- }
-
- /**
- * Disconnects the current debug bridge, and destroy the object.
- * <p/>This also stops the current adb host server.
- * <p/>
- * A new object will have to be created with {@link #createBridge(String, boolean)}.
- */
- public static void disconnectBridge() {
- synchronized (sLock) {
- if (sThis != null) {
- sThis.stop();
- sThis = null;
-
- // because the listeners could remove themselves from the list while processing
- // their event callback, we make a copy of the list and iterate on it instead of
- // the main list.
- // This mostly happens when the application quits.
- IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
- new IDebugBridgeChangeListener[sBridgeListeners.size()]);
-
- // notify the listeners.
- for (IDebugBridgeChangeListener listener : listenersCopy) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.bridgeChanged(sThis);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
- }
- }
- }
-
- /**
- * Adds the listener to the collection of listeners who will be notified when a new
- * {@link AndroidDebugBridge} is connected, by sending it one of the messages defined
- * in the {@link IDebugBridgeChangeListener} interface.
- * @param listener The listener which should be notified.
- */
- public static void addDebugBridgeChangeListener(IDebugBridgeChangeListener listener) {
- synchronized (sLock) {
- if (!sBridgeListeners.contains(listener)) {
- sBridgeListeners.add(listener);
- if (sThis != null) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.bridgeChanged(sThis);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
- }
- }
- }
-
- /**
- * Removes the listener from the collection of listeners who will be notified when a new
- * {@link AndroidDebugBridge} is started.
- * @param listener The listener which should no longer be notified.
- */
- public static void removeDebugBridgeChangeListener(IDebugBridgeChangeListener listener) {
- synchronized (sLock) {
- sBridgeListeners.remove(listener);
- }
- }
-
- /**
- * Adds the listener to the collection of listeners who will be notified when a {@link IDevice}
- * is connected, disconnected, or when its properties or its {@link Client} list changed,
- * by sending it one of the messages defined in the {@link IDeviceChangeListener} interface.
- * @param listener The listener which should be notified.
- */
- public static void addDeviceChangeListener(IDeviceChangeListener listener) {
- synchronized (sLock) {
- if (!sDeviceListeners.contains(listener)) {
- sDeviceListeners.add(listener);
- }
- }
- }
-
- /**
- * Removes the listener from the collection of listeners who will be notified when a
- * {@link IDevice} is connected, disconnected, or when its properties or its {@link Client}
- * list changed.
- * @param listener The listener which should no longer be notified.
- */
- public static void removeDeviceChangeListener(IDeviceChangeListener listener) {
- synchronized (sLock) {
- sDeviceListeners.remove(listener);
- }
- }
-
- /**
- * Adds the listener to the collection of listeners who will be notified when a {@link Client}
- * property changed, by sending it one of the messages defined in the
- * {@link IClientChangeListener} interface.
- * @param listener The listener which should be notified.
- */
- public static void addClientChangeListener(IClientChangeListener listener) {
- synchronized (sLock) {
- if (!sClientListeners.contains(listener)) {
- sClientListeners.add(listener);
- }
- }
- }
-
- /**
- * Removes the listener from the collection of listeners who will be notified when a
- * {@link Client} property changed.
- * @param listener The listener which should no longer be notified.
- */
- public static void removeClientChangeListener(IClientChangeListener listener) {
- synchronized (sLock) {
- sClientListeners.remove(listener);
- }
- }
-
-
- /**
- * Returns the devices.
- * @see #hasInitialDeviceList()
- */
- @NonNull
- public IDevice[] getDevices() {
- synchronized (sLock) {
- if (mDeviceMonitor != null) {
- return mDeviceMonitor.getDevices();
- }
- }
-
- return new IDevice[0];
- }
-
- /**
- * Returns whether the bridge has acquired the initial list from adb after being created.
- * <p/>Calling {@link #getDevices()} right after {@link #createBridge(String, boolean)} will
- * generally result in an empty list. This is due to the internal asynchronous communication
- * mechanism with <code>adb</code> that does not guarantee that the {@link IDevice} list has been
- * built before the call to {@link #getDevices()}.
- * <p/>The recommended way to get the list of {@link IDevice} objects is to create a
- * {@link IDeviceChangeListener} object.
- */
- public boolean hasInitialDeviceList() {
- if (mDeviceMonitor != null) {
- return mDeviceMonitor.hasInitialDeviceList();
- }
-
- return false;
- }
-
- /**
- * Sets the client to accept debugger connection on the custom "Selected debug port".
- * @param selectedClient the client. Can be null.
- */
- public void setSelectedClient(Client selectedClient) {
- MonitorThread monitorThread = MonitorThread.getInstance();
- if (monitorThread != null) {
- monitorThread.setSelectedClient(selectedClient);
- }
- }
-
- /**
- * Returns whether the {@link AndroidDebugBridge} object is still connected to the adb daemon.
- */
- public boolean isConnected() {
- MonitorThread monitorThread = MonitorThread.getInstance();
- if (mDeviceMonitor != null && monitorThread != null) {
- return mDeviceMonitor.isMonitoring() && monitorThread.getState() != State.TERMINATED;
- }
- return false;
- }
-
- /**
- * Returns the number of times the {@link AndroidDebugBridge} object attempted to connect
- * to the adb daemon.
- */
- public int getConnectionAttemptCount() {
- if (mDeviceMonitor != null) {
- return mDeviceMonitor.getConnectionAttemptCount();
- }
- return -1;
- }
-
- /**
- * Returns the number of times the {@link AndroidDebugBridge} object attempted to restart
- * the adb daemon.
- */
- public int getRestartAttemptCount() {
- if (mDeviceMonitor != null) {
- return mDeviceMonitor.getRestartAttemptCount();
- }
- return -1;
- }
-
- /**
- * Creates a new bridge.
- * @param osLocation the location of the command line tool
- * @throws InvalidParameterException
- */
- private AndroidDebugBridge(String osLocation) throws InvalidParameterException {
- if (osLocation == null || osLocation.isEmpty()) {
- throw new InvalidParameterException();
- }
- mAdbOsLocation = osLocation;
-
- try {
- checkAdbVersion();
- } catch (IOException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- /**
- * Creates a new bridge not linked to any particular adb executable.
- */
- private AndroidDebugBridge() {
- }
-
- /**
- * Queries adb for its version number and checks that it is atleast {@link #MIN_ADB_VERSION}.
- */
- private void checkAdbVersion() throws IOException {
- // default is bad check
- mVersionCheck = false;
-
- if (mAdbOsLocation == null) {
- return;
- }
-
- File adb = new File(mAdbOsLocation);
- ListenableFuture<AdbVersion> future = getAdbVersion(adb);
- AdbVersion version;
- try {
- version = future.get(5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- return;
- } catch (java.util.concurrent.TimeoutException e) {
- String msg = "Unable to obtain result of 'adb version'";
- Log.logAndDisplay(LogLevel.ERROR, ADB, msg);
- return;
- } catch (ExecutionException e) {
- Log.logAndDisplay(LogLevel.ERROR, ADB, e.getCause().getMessage());
- Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
- return;
- }
-
- if (version.compareTo(MIN_ADB_VERSION) > 0) {
- mVersionCheck = true;
- } else {
- String message = String.format(
- "Required minimum version of adb: %1$s."
- + "Current version is %2$s", MIN_ADB_VERSION, version);
- Log.logAndDisplay(LogLevel.ERROR, ADB, message);
- }
- }
-
- public static ListenableFuture<AdbVersion> getAdbVersion(@NonNull final File adb) {
- final SettableFuture<AdbVersion> future = SettableFuture.create();
- new Thread(new Runnable() {
- @Override
- public void run() {
- ProcessBuilder pb = new ProcessBuilder(adb.getPath(), "version");
- pb.redirectErrorStream(true);
-
- Process p = null;
- try {
- p = pb.start();
- } catch (IOException e) {
- future.setException(e);
- return;
- }
-
- StringBuilder sb = new StringBuilder();
- BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
- try {
- String line;
- while ((line = br.readLine()) != null) {
- AdbVersion version = AdbVersion.parseFrom(line);
- if (version != AdbVersion.UNKNOWN) {
- future.set(version);
- return;
- }
- sb.append(line);
- sb.append('\n');
- }
- } catch (IOException e) {
- future.setException(e);
- return;
- } finally {
- try {
- br.close();
- } catch (IOException e) {
- future.setException(e);
- }
- }
-
- future.setException(new RuntimeException(
- "Unable to detect adb version, adb output: " + sb.toString()));
- }
- }, "Obtaining adb version").start();
- return future;
- }
-
- /**
- * Starts the debug bridge.
- *
- * @return true if success.
- */
- boolean start() {
- if (mAdbOsLocation != null && sAdbServerPort != 0 && (!mVersionCheck || !startAdb())) {
- return false;
- }
-
- mStarted = true;
-
- // now that the bridge is connected, we start the underlying services.
- mDeviceMonitor = new DeviceMonitor(this);
- mDeviceMonitor.start();
-
- return true;
- }
-
- /**
- * Kills the debug bridge, and the adb host server.
- * @return true if success
- */
- boolean stop() {
- // if we haven't started we return false;
- if (!mStarted) {
- return false;
- }
-
- // kill the monitoring services
- if (mDeviceMonitor != null) {
- mDeviceMonitor.stop();
- mDeviceMonitor = null;
- }
-
- if (!stopAdb()) {
- return false;
- }
-
- mStarted = false;
- return true;
- }
-
- /**
- * Restarts adb, but not the services around it.
- * @return true if success.
- */
- public boolean restart() {
- if (mAdbOsLocation == null) {
- Log.e(ADB,
- "Cannot restart adb when AndroidDebugBridge is created without the location of adb."); //$NON-NLS-1$
- return false;
- }
-
- if (sAdbServerPort == 0) {
- Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set."); //$NON-NLS-1$
- return false;
- }
-
- if (!mVersionCheck) {
- Log.logAndDisplay(LogLevel.ERROR, ADB,
- "Attempting to restart adb, but version check failed!"); //$NON-NLS-1$
- return false;
- }
- synchronized (this) {
- stopAdb();
-
- boolean restart = startAdb();
-
- if (restart && mDeviceMonitor == null) {
- mDeviceMonitor = new DeviceMonitor(this);
- mDeviceMonitor.start();
- }
-
- return restart;
- }
- }
-
- /**
- * Notify the listener of a new {@link IDevice}.
- * <p/>
- * The notification of the listeners is done in a synchronized block. It is important to
- * expect the listeners to potentially access various methods of {@link IDevice} as well as
- * {@link #getDevices()} which use internal locks.
- * <p/>
- * For this reason, any call to this method from a method of {@link DeviceMonitor},
- * {@link IDevice} which is also inside a synchronized block, should first synchronize on
- * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
- * @param device the new <code>IDevice</code>.
- * @see #getLock()
- */
- void deviceConnected(IDevice device) {
- // because the listeners could remove themselves from the list while processing
- // their event callback, we make a copy of the list and iterate on it instead of
- // the main list.
- // This mostly happens when the application quits.
- IDeviceChangeListener[] listenersCopy = null;
- synchronized (sLock) {
- listenersCopy = sDeviceListeners.toArray(
- new IDeviceChangeListener[sDeviceListeners.size()]);
- }
-
- // Notify the listeners
- for (IDeviceChangeListener listener : listenersCopy) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.deviceConnected(device);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
- }
-
- /**
- * Notify the listener of a disconnected {@link IDevice}.
- * <p/>
- * The notification of the listeners is done in a synchronized block. It is important to
- * expect the listeners to potentially access various methods of {@link IDevice} as well as
- * {@link #getDevices()} which use internal locks.
- * <p/>
- * For this reason, any call to this method from a method of {@link DeviceMonitor},
- * {@link IDevice} which is also inside a synchronized block, should first synchronize on
- * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
- * @param device the disconnected <code>IDevice</code>.
- * @see #getLock()
- */
- void deviceDisconnected(IDevice device) {
- // because the listeners could remove themselves from the list while processing
- // their event callback, we make a copy of the list and iterate on it instead of
- // the main list.
- // This mostly happens when the application quits.
- IDeviceChangeListener[] listenersCopy = null;
- synchronized (sLock) {
- listenersCopy = sDeviceListeners.toArray(
- new IDeviceChangeListener[sDeviceListeners.size()]);
- }
-
- // Notify the listeners
- for (IDeviceChangeListener listener : listenersCopy) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.deviceDisconnected(device);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
- }
-
- /**
- * Notify the listener of a modified {@link IDevice}.
- * <p/>
- * The notification of the listeners is done in a synchronized block. It is important to
- * expect the listeners to potentially access various methods of {@link IDevice} as well as
- * {@link #getDevices()} which use internal locks.
- * <p/>
- * For this reason, any call to this method from a method of {@link DeviceMonitor},
- * {@link IDevice} which is also inside a synchronized block, should first synchronize on
- * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
- * @param device the modified <code>IDevice</code>.
- * @see #getLock()
- */
- void deviceChanged(IDevice device, int changeMask) {
- // because the listeners could remove themselves from the list while processing
- // their event callback, we make a copy of the list and iterate on it instead of
- // the main list.
- // This mostly happens when the application quits.
- IDeviceChangeListener[] listenersCopy = null;
- synchronized (sLock) {
- listenersCopy = sDeviceListeners.toArray(
- new IDeviceChangeListener[sDeviceListeners.size()]);
- }
-
- // Notify the listeners
- for (IDeviceChangeListener listener : listenersCopy) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.deviceChanged(device, changeMask);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
- }
-
- /**
- * Notify the listener of a modified {@link Client}.
- * <p/>
- * The notification of the listeners is done in a synchronized block. It is important to
- * expect the listeners to potentially access various methods of {@link IDevice} as well as
- * {@link #getDevices()} which use internal locks.
- * <p/>
- * For this reason, any call to this method from a method of {@link DeviceMonitor},
- * {@link IDevice} which is also inside a synchronized block, should first synchronize on
- * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
- * @param client the modified <code>Client</code>.
- * @param changeMask the mask indicating what changed in the <code>Client</code>
- * @see #getLock()
- */
- void clientChanged(Client client, int changeMask) {
- // because the listeners could remove themselves from the list while processing
- // their event callback, we make a copy of the list and iterate on it instead of
- // the main list.
- // This mostly happens when the application quits.
- IClientChangeListener[] listenersCopy = null;
- synchronized (sLock) {
- listenersCopy = sClientListeners.toArray(
- new IClientChangeListener[sClientListeners.size()]);
-
- }
-
- // Notify the listeners
- for (IClientChangeListener listener : listenersCopy) {
- // we attempt to catch any exception so that a bad listener doesn't kill our
- // thread
- try {
- listener.clientChanged(client, changeMask);
- } catch (Exception e) {
- Log.e(DDMS, e);
- }
- }
- }
-
- /**
- * Returns the {@link DeviceMonitor} object.
- */
- DeviceMonitor getDeviceMonitor() {
- return mDeviceMonitor;
- }
-
- /**
- * Starts the adb host side server.
- * @return true if success
- */
- synchronized boolean startAdb() {
- if (mAdbOsLocation == null) {
- Log.e(ADB,
- "Cannot start adb when AndroidDebugBridge is created without the location of adb."); //$NON-NLS-1$
- return false;
- }
-
- if (sAdbServerPort == 0) {
- Log.w(ADB, "ADB server port for starting AndroidDebugBridge is not set."); //$NON-NLS-1$
- return false;
- }
-
- Process proc;
- int status = -1;
-
- String[] command = getAdbLaunchCommand("start-server");
- String commandString = Joiner.on(',').join(command);
- try {
- Log.d(DDMS, String.format("Launching '%1$s' to ensure ADB is running.", commandString));
- ProcessBuilder processBuilder = new ProcessBuilder(command);
- if (DdmPreferences.getUseAdbHost()) {
- String adbHostValue = DdmPreferences.getAdbHostValue();
- if (adbHostValue != null && !adbHostValue.isEmpty()) {
- //TODO : check that the String is a valid IP address
- Map<String, String> env = processBuilder.environment();
- env.put("ADBHOST", adbHostValue);
- }
- }
- proc = processBuilder.start();
-
- ArrayList<String> errorOutput = new ArrayList<String>();
- ArrayList<String> stdOutput = new ArrayList<String>();
- status = grabProcessOutput(proc, errorOutput, stdOutput, false /* waitForReaders */);
- } catch (IOException ioe) {
- Log.e(DDMS, "Unable to run 'adb': " + ioe.getMessage()); //$NON-NLS-1$
- // we'll return false;
- } catch (InterruptedException ie) {
- Log.e(DDMS, "Unable to run 'adb': " + ie.getMessage()); //$NON-NLS-1$
- // we'll return false;
- }
-
- if (status != 0) {
- Log.e(DDMS,
- String.format("'%1$s' failed -- run manually if necessary", commandString)); //$NON-NLS-1$
- return false;
- } else {
- Log.d(DDMS, String.format("'%1$s' succeeded", commandString)); //$NON-NLS-1$
- return true;
- }
- }
-
- private String[] getAdbLaunchCommand(String option) {
- List<String> command = new ArrayList<String>(4);
- command.add(mAdbOsLocation);
- if (sAdbServerPort != DEFAULT_ADB_PORT) {
- command.add("-P"); //$NON-NLS-1$
- command.add(Integer.toString(sAdbServerPort));
- }
- command.add(option);
- return command.toArray(new String[command.size()]);
- }
-
- /**
- * Stops the adb host side server.
- *
- * @return true if success
- */
- private synchronized boolean stopAdb() {
- if (mAdbOsLocation == null) {
- Log.e(ADB,
- "Cannot stop adb when AndroidDebugBridge is created without the location of adb.");
- return false;
- }
-
- if (sAdbServerPort == 0) {
- Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set");
- return false;
- }
-
- Process proc;
- int status = -1;
-
- String[] command = getAdbLaunchCommand("kill-server"); //$NON-NLS-1$
- try {
- proc = Runtime.getRuntime().exec(command);
- status = proc.waitFor();
- }
- catch (IOException ioe) {
- // we'll return false;
- }
- catch (InterruptedException ie) {
- // we'll return false;
- }
-
- String commandString = Joiner.on(',').join(command);
- if (status != 0) {
- Log.w(DDMS, String.format("'%1$s' failed -- run manually if necessary", commandString));
- return false;
- } else {
- Log.d(DDMS, String.format("'%1$s' succeeded", commandString));
- return true;
- }
- }
-
- /**
- * Get the stderr/stdout outputs of a process and return when the process is done.
- * Both <b>must</b> be read or the process will block on windows.
- * @param process The process to get the output from
- * @param errorOutput The array to store the stderr output. cannot be null.
- * @param stdOutput The array to store the stdout output. cannot be null.
- * @param waitForReaders if true, this will wait for the reader threads.
- * @return the process return code.
- * @throws InterruptedException
- */
- private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
- final ArrayList<String> stdOutput, boolean waitForReaders)
- throws InterruptedException {
- assert errorOutput != null;
- assert stdOutput != null;
- // read the lines as they come. if null is returned, it's
- // because the process finished
- Thread t1 = new Thread("") { //$NON-NLS-1$
- @Override
- public void run() {
- // create a buffer to read the stderr output
- InputStreamReader is = new InputStreamReader(process.getErrorStream());
- BufferedReader errReader = new BufferedReader(is);
-
- try {
- while (true) {
- String line = errReader.readLine();
- if (line != null) {
- Log.e(ADB, line);
- errorOutput.add(line);
- } else {
- break;
- }
- }
- } catch (IOException e) {
- // do nothing.
- }
- }
- };
-
- Thread t2 = new Thread("") { //$NON-NLS-1$
- @Override
- public void run() {
- InputStreamReader is = new InputStreamReader(process.getInputStream());
- BufferedReader outReader = new BufferedReader(is);
-
- try {
- while (true) {
- String line = outReader.readLine();
- if (line != null) {
- Log.d(ADB, line);
- stdOutput.add(line);
- } else {
- break;
- }
- }
- } catch (IOException e) {
- // do nothing.
- }
- }
- };
-
- t1.start();
- t2.start();
-
- // it looks like on windows process#waitFor() can return
- // before the thread have filled the arrays, so we wait for both threads and the
- // process itself.
- if (waitForReaders) {
- try {
- t1.join();
- } catch (InterruptedException e) {
- }
- try {
- t2.join();
- } catch (InterruptedException e) {
- }
- }
-
- // get the return code from the process
- return process.waitFor();
- }
-
- /**
- * Returns the singleton lock used by this class to protect any access to the listener.
- * <p/>
- * This includes adding/removing listeners, but also notifying listeners of new bridges,
- * devices, and clients.
- */
- private static Object getLock() {
- return sLock;
- }
-
- /**
- * Instantiates sSocketAddr with the address of the host's adb process.
- */
- private static void initAdbSocketAddr() {
- try {
- sAdbServerPort = getAdbServerPort();
- sHostAddr = InetAddress.getByName(DEFAULT_ADB_HOST);
- sSocketAddr = new InetSocketAddress(sHostAddr, sAdbServerPort);
- } catch (UnknownHostException e) {
- // localhost should always be known.
- }
- }
-
- /**
- * Returns the port where adb server should be launched. This looks at:
- * <ol>
- * <li>The system property ANDROID_ADB_SERVER_PORT</li>
- * <li>The environment variable ANDROID_ADB_SERVER_PORT</li>
- * <li>Defaults to {@link #DEFAULT_ADB_PORT} if neither the system property nor the env var
- * are set.</li>
- * </ol>
- *
- * @return The port number where the host's adb should be expected or started.
- */
- private static int getAdbServerPort() {
- // check system property
- Integer prop = Integer.getInteger(SERVER_PORT_ENV_VAR);
- if (prop != null) {
- try {
- return validateAdbServerPort(prop.toString());
- } catch (IllegalArgumentException e) {
- String msg = String.format(
- "Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT system property.",
- prop);
- Log.w(DDMS, msg);
- }
- }
-
- // when system property is not set or is invalid, parse environment property
- try {
- String env = System.getenv(SERVER_PORT_ENV_VAR);
- if (env != null) {
- return validateAdbServerPort(env);
- }
- } catch (SecurityException ex) {
- // A security manager has been installed that doesn't allow access to env vars.
- // So an environment variable might have been set, but we can't tell.
- // Let's log a warning and continue with ADB's default port.
- // The issue is that adb would be started (by the forked process having access
- // to the env vars) on the desired port, but within this process, we can't figure out
- // what that port is. However, a security manager not granting access to env vars
- // but allowing to fork is a rare and interesting configuration, so the right
- // thing seems to be to continue using the default port, as forking is likely to
- // fail later on in the scenario of the security manager.
- Log.w(DDMS,
- "No access to env variables allowed by current security manager. "
- + "If you've set ANDROID_ADB_SERVER_PORT: it's being ignored.");
- } catch (IllegalArgumentException e) {
- String msg = String.format(
- "Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT environment variable (%2$s).",
- prop, e.getMessage());
- Log.w(DDMS, msg);
- }
-
- // use default port if neither are set
- return DEFAULT_ADB_PORT;
- }
-
- /**
- * Returns the integer port value if it is a valid value for adb server port
- * @param adbServerPort adb server port to validate
- * @return {@code adbServerPort} as a parsed integer
- * @throws IllegalArgumentException when {@code adbServerPort} is not bigger than 0 or it is
- * not a number at all
- */
- private static int validateAdbServerPort(@NonNull String adbServerPort)
- throws IllegalArgumentException {
- try {
- // C tools (adb, emulator) accept hex and octal port numbers, so need to accept them too
- int port = Integer.decode(adbServerPort);
- if (port <= 0 || port >= 65535) {
- throw new IllegalArgumentException("Should be > 0 and < 65535");
- }
- return port;
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("Not a valid port number");
- }
- }
-
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java b/base/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java
deleted file mode 100644
index 36e24a3..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * Subclass this with a class that handles one or more chunk types.
- */
-abstract class ChunkHandler {
-
- public static final int CHUNK_HEADER_LEN = 8; // 4-byte type, 4-byte len
- public static final ByteOrder CHUNK_ORDER = ByteOrder.BIG_ENDIAN;
-
- public static final int CHUNK_FAIL = type("FAIL");
-
- ChunkHandler() {}
-
- /**
- * Client is ready. The monitor thread calls this method on all
- * handlers when the client is determined to be DDM-aware (usually
- * after receiving a HELO response.)
- *
- * The handler can use this opportunity to initialize client-side
- * activity. Because there's a fair chance we'll want to send a
- * message to the client, this method can throw an IOException.
- */
- abstract void clientReady(Client client) throws IOException;
-
- /**
- * Client has gone away. Can be used to clean up any resources
- * associated with this client connection.
- */
- abstract void clientDisconnected(Client client);
-
- /**
- * Handle an incoming chunk. The data, of chunk type "type", begins
- * at the start of "data" and continues to data.limit().
- *
- * If "isReply" is set, then "msgId" will be the ID of the request
- * we sent to the client. Otherwise, it's the ID generated by the
- * client for this event. Note that it's possible to receive chunks
- * in reply packets for which we are not registered.
- *
- * The handler may not modify the contents of "data".
- */
- abstract void handleChunk(Client client, int type,
- ByteBuffer data, boolean isReply, int msgId);
-
- /**
- * Handle chunks not recognized by handlers. The handleChunk() method
- * in sub-classes should call this if the chunk type isn't recognized.
- */
- protected void handleUnknownChunk(Client client, int type,
- ByteBuffer data, boolean isReply, int msgId) {
- if (type == CHUNK_FAIL) {
- int errorCode, msgLen;
- String msg;
-
- errorCode = data.getInt();
- msgLen = data.getInt();
- msg = ByteBufferUtil.getString(data, msgLen);
- Log.w("ddms", "WARNING: failure code=" + errorCode + " msg=" + msg);
- } else {
- Log.w("ddms", "WARNING: received unknown chunk " + name(type)
- + ": len=" + data.limit() + ", reply=" + isReply
- + ", msgId=0x" + Integer.toHexString(msgId));
- }
- Log.w("ddms", " client " + client + ", handler " + this);
- }
-
- /**
- * Utility function to copy a String out of a ByteBuffer.
- */
- public static String getString(ByteBuffer buf, int len) {
- return ByteBufferUtil.getString(buf, len);
- }
-
- /**
- * Convert a 4-character string to a 32-bit type.
- */
- static int type(String typeName) {
- int val = 0;
-
- if (typeName.length() != 4) {
- Log.e("ddms", "Type name must be 4 letter long");
- throw new RuntimeException("Type name must be 4 letter long");
- }
-
- for (int i = 0; i < 4; i++) {
- val <<= 8;
- val |= (byte) typeName.charAt(i);
- }
-
- return val;
- }
-
- /**
- * Convert an integer type to a 4-character string.
- */
- static String name(int type) {
- char[] ascii = new char[4];
-
- ascii[0] = (char) ((type >> 24) & 0xff);
- ascii[1] = (char) ((type >> 16) & 0xff);
- ascii[2] = (char) ((type >> 8) & 0xff);
- ascii[3] = (char) (type & 0xff);
-
- return new String(ascii);
- }
-
- /**
- * Allocate a ByteBuffer with enough space to hold the JDWP packet
- * header and one chunk header in addition to the demands of the
- * chunk being created.
- *
- * "maxChunkLen" indicates the size of the chunk contents only.
- */
- static ByteBuffer allocBuffer(int maxChunkLen) {
- ByteBuffer buf =
- ByteBuffer.allocate(JdwpPacket.JDWP_HEADER_LEN + 8 +maxChunkLen);
- buf.order(CHUNK_ORDER);
- return buf;
- }
-
- /**
- * Return the slice of the JDWP packet buffer that holds just the
- * chunk data.
- */
- static ByteBuffer getChunkDataBuf(ByteBuffer jdwpBuf) {
- ByteBuffer slice;
-
- assert jdwpBuf.position() == 0;
-
- jdwpBuf.position(JdwpPacket.JDWP_HEADER_LEN + CHUNK_HEADER_LEN);
- slice = jdwpBuf.slice();
- slice.order(CHUNK_ORDER);
- jdwpBuf.position(0);
-
- return slice;
- }
-
- /**
- * Write the chunk header at the start of the chunk.
- *
- * Pass in the byte buffer returned by JdwpPacket.getPayload().
- */
- static void finishChunkPacket(JdwpPacket packet, int type, int chunkLen) {
- ByteBuffer buf = packet.getPayload();
-
- buf.putInt(0x00, type);
- buf.putInt(0x04, chunkLen);
-
- packet.finishPacket(CHUNK_HEADER_LEN + chunkLen);
- }
-
- /**
- * Check that the client is opened with the proper debugger port for the
- * specified application name, and if not, reopen it.
- * @param client
- * @param uiThread
- * @param appName
- * @return
- */
- protected static Client checkDebuggerPortForAppName(Client client, String appName) {
- IDebugPortProvider provider = DebugPortManager.getProvider();
- if (provider != null) {
- Device device = client.getDeviceImpl();
- int newPort = provider.getPort(device, appName);
-
- if (newPort != IDebugPortProvider.NO_STATIC_PORT &&
- newPort != client.getDebuggerListenPort()) {
-
- AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
- if (bridge != null) {
- DeviceMonitor deviceMonitor = bridge.getDeviceMonitor();
- if (deviceMonitor != null) {
- deviceMonitor.addClientToDropAndReopen(client, newPort);
- client = null;
- }
- }
- }
- }
-
- return client;
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/Client.java b/base/ddmlib/src/main/java/com/android/ddmlib/Client.java
deleted file mode 100644
index d2b1488..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/Client.java
+++ /dev/null
@@ -1,948 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
-import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
-
-import java.io.IOException;
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-import java.util.HashMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This represents a single client, usually a Dalvik VM process.
- * <p/>This class gives access to basic client information, as well as methods to perform actions
- * on the client.
- * <p/>More detailed information, usually updated in real time, can be access through the
- * {@link ClientData} class. Each <code>Client</code> object has its own <code>ClientData</code>
- * accessed through {@link #getClientData()}.
- */
-public class Client {
-
- private static final int SERVER_PROTOCOL_VERSION = 1;
-
- /** Client change bit mask: application name change */
- public static final int CHANGE_NAME = 0x0001;
- /** Client change bit mask: debugger status change */
- public static final int CHANGE_DEBUGGER_STATUS = 0x0002;
- /** Client change bit mask: debugger port change */
- public static final int CHANGE_PORT = 0x0004;
- /** Client change bit mask: thread update flag change */
- public static final int CHANGE_THREAD_MODE = 0x0008;
- /** Client change bit mask: thread data updated */
- public static final int CHANGE_THREAD_DATA = 0x0010;
- /** Client change bit mask: heap update flag change */
- public static final int CHANGE_HEAP_MODE = 0x0020;
- /** Client change bit mask: head data updated */
- public static final int CHANGE_HEAP_DATA = 0x0040;
- /** Client change bit mask: native heap data updated */
- public static final int CHANGE_NATIVE_HEAP_DATA = 0x0080;
- /** Client change bit mask: thread stack trace updated */
- public static final int CHANGE_THREAD_STACKTRACE = 0x0100;
- /** Client change bit mask: allocation information updated */
- public static final int CHANGE_HEAP_ALLOCATIONS = 0x0200;
- /** Client change bit mask: allocation information updated */
- public static final int CHANGE_HEAP_ALLOCATION_STATUS = 0x0400;
- /** Client change bit mask: allocation information updated */
- public static final int CHANGE_METHOD_PROFILING_STATUS = 0x0800;
- /** Client change bit mask: hprof data updated */
- public static final int CHANGE_HPROF = 0x1000;
-
- /** Client change bit mask: combination of {@link Client#CHANGE_NAME},
- * {@link Client#CHANGE_DEBUGGER_STATUS}, and {@link Client#CHANGE_PORT}.
- */
- public static final int CHANGE_INFO = CHANGE_NAME | CHANGE_DEBUGGER_STATUS | CHANGE_PORT;
-
- private SocketChannel mChan;
-
- // debugger we're associated with, if any
- private Debugger mDebugger;
- private int mDebuggerListenPort;
-
- // list of IDs for requests we have sent to the client
- private HashMap<Integer,ChunkHandler> mOutstandingReqs;
-
- // chunk handlers stash state data in here
- private ClientData mClientData;
-
- // User interface state. Changing the value causes a message to be
- // sent to the client.
- private boolean mThreadUpdateEnabled;
- private boolean mHeapInfoUpdateEnabled;
- private boolean mHeapSegmentUpdateEnabled;
-
- /*
- * Read/write buffers. We can get large quantities of data from the
- * client, e.g. the response to a "give me the list of all known classes"
- * request from the debugger. Requests from the debugger, and from us,
- * are much smaller.
- *
- * Pass-through debugger traffic is sent without copying. "mWriteBuffer"
- * is only used for data generated within Client.
- */
- private static final int INITIAL_BUF_SIZE = 2*1024;
- private static final int MAX_BUF_SIZE = 800*1024*1024;
- private ByteBuffer mReadBuffer;
-
- private static final int WRITE_BUF_SIZE = 256;
- private ByteBuffer mWriteBuffer;
-
- private Device mDevice;
-
- private int mConnState;
-
- private static final int ST_INIT = 1;
- private static final int ST_NOT_JDWP = 2;
- private static final int ST_AWAIT_SHAKE = 10;
- private static final int ST_NEED_DDM_PKT = 11;
- private static final int ST_NOT_DDM = 12;
- private static final int ST_READY = 13;
- private static final int ST_ERROR = 20;
- private static final int ST_DISCONNECTED = 21;
-
-
- /**
- * Create an object for a new client connection.
- *
- * @param device the device this client belongs to
- * @param chan the connected {@link SocketChannel}.
- * @param pid the client pid.
- */
- Client(Device device, SocketChannel chan, int pid) {
- mDevice = device;
- mChan = chan;
-
- mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
- mWriteBuffer = ByteBuffer.allocate(WRITE_BUF_SIZE);
-
- mOutstandingReqs = new HashMap<Integer,ChunkHandler>();
-
- mConnState = ST_INIT;
-
- mClientData = new ClientData(pid);
-
- mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate();
- mHeapInfoUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
- mHeapSegmentUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
- }
-
- /**
- * Returns a string representation of the {@link Client} object.
- */
- @Override
- public String toString() {
- return "[Client pid: " + mClientData.getPid() + "]";
- }
-
- /**
- * Returns the {@link IDevice} on which this Client is running.
- */
- public IDevice getDevice() {
- return mDevice;
- }
-
- /** Returns the {@link Device} on which this Client is running.
- */
- Device getDeviceImpl() {
- return mDevice;
- }
-
- /**
- * Returns the debugger port for this client.
- */
- public int getDebuggerListenPort() {
- return mDebuggerListenPort;
- }
-
- /**
- * Returns <code>true</code> if the client VM is DDM-aware.
- *
- * Calling here is only allowed after the connection has been
- * established.
- */
- public boolean isDdmAware() {
- switch (mConnState) {
- case ST_INIT:
- case ST_NOT_JDWP:
- case ST_AWAIT_SHAKE:
- case ST_NEED_DDM_PKT:
- case ST_NOT_DDM:
- case ST_ERROR:
- case ST_DISCONNECTED:
- return false;
- case ST_READY:
- return true;
- default:
- assert false;
- return false;
- }
- }
-
- /**
- * Returns <code>true</code> if a debugger is currently attached to the client.
- */
- public boolean isDebuggerAttached() {
- return mDebugger.isDebuggerAttached();
- }
-
- /**
- * Return the Debugger object associated with this client.
- */
- Debugger getDebugger() {
- return mDebugger;
- }
-
- /**
- * Returns the {@link ClientData} object containing this client information.
- */
- @NonNull
- public ClientData getClientData() {
- return mClientData;
- }
-
- /**
- * Forces the client to execute its garbage collector.
- */
- public void executeGarbageCollector() {
- try {
- HandleHeap.sendHPGC(this);
- } catch (IOException ioe) {
- Log.w("ddms", "Send of HPGC message failed");
- // ignore
- }
- }
-
- /**
- * Makes the VM dump an HPROF file
- */
- public void dumpHprof() {
- boolean canStream = mClientData.hasFeature(ClientData.FEATURE_HPROF_STREAMING);
- try {
- if (canStream) {
- HandleHeap.sendHPDS(this);
- } else {
- String file = "/sdcard/" + mClientData.getClientDescription().replaceAll(
- "\\:.*", "") + ".hprof";
- HandleHeap.sendHPDU(this, file);
- }
- } catch (IOException e) {
- Log.w("ddms", "Send of HPDU message failed");
- // ignore
- }
- }
-
- /**
- * Toggles method profiling state.
- * @deprecated Use {@link #startMethodTracer()}, {@link #stopMethodTracer()},
- * {@link #startSamplingProfiler(int, java.util.concurrent.TimeUnit)} or
- * {@link #stopSamplingProfiler()} instead.
- */
- public void toggleMethodProfiling() {
- try {
- switch (mClientData.getMethodProfilingStatus()) {
- case TRACER_ON:
- stopMethodTracer();
- break;
- case SAMPLER_ON:
- stopSamplingProfiler();
- break;
- case OFF:
- startMethodTracer();
- break;
- }
- } catch (IOException e) {
- Log.w("ddms", "Toggle method profiling failed");
- // ignore
- }
- }
-
- private int getProfileBufferSize() {
- return DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024;
- }
-
- public void startMethodTracer() throws IOException {
- boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
- int bufferSize = getProfileBufferSize();
- if (canStream) {
- HandleProfiling.sendMPSS(this, bufferSize, 0 /*flags*/);
- } else {
- String file = "/sdcard/" +
- mClientData.getClientDescription().replaceAll("\\:.*", "") +
- DdmConstants.DOT_TRACE;
- HandleProfiling.sendMPRS(this, file, bufferSize, 0 /*flags*/);
- }
- }
-
- public void stopMethodTracer() throws IOException {
- boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
-
- if (canStream) {
- HandleProfiling.sendMPSE(this);
- } else {
- HandleProfiling.sendMPRE(this);
- }
- }
-
- public void startSamplingProfiler(int samplingInterval, TimeUnit timeUnit) throws IOException {
- int bufferSize = getProfileBufferSize();
- HandleProfiling.sendSPSS(this, bufferSize, samplingInterval, timeUnit);
- }
-
- public void stopSamplingProfiler() throws IOException {
- HandleProfiling.sendSPSE(this);
- }
-
- public boolean startOpenGlTracing() {
- boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING);
- if (!canTraceOpenGl) {
- return false;
- }
-
- try {
- HandleViewDebug.sendStartGlTracing(this);
- return true;
- } catch (IOException e) {
- Log.w("ddms", "Start OpenGL Tracing failed");
- return false;
- }
- }
-
- public boolean stopOpenGlTracing() {
- boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING);
- if (!canTraceOpenGl) {
- return false;
- }
-
- try {
- HandleViewDebug.sendStopGlTracing(this);
- return true;
- } catch (IOException e) {
- Log.w("ddms", "Stop OpenGL Tracing failed");
- return false;
- }
- }
-
- /**
- * Sends a request to the VM to send the enable status of the method profiling.
- * This is asynchronous.
- * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
- * The notification that the new status is available will be received through
- * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
- * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
- */
- public void requestMethodProfilingStatus() {
- try {
- HandleHeap.sendREAQ(this);
- } catch (IOException e) {
- Log.e("ddmlib", e);
- }
- }
-
-
- /**
- * Enables or disables the thread update.
- * <p/>If <code>true</code> the VM will be able to send thread information. Thread information
- * must be requested with {@link #requestThreadUpdate()}.
- * @param enabled the enable flag.
- */
- public void setThreadUpdateEnabled(boolean enabled) {
- mThreadUpdateEnabled = enabled;
- if (!enabled) {
- mClientData.clearThreads();
- }
-
- try {
- HandleThread.sendTHEN(this, enabled);
- } catch (IOException ioe) {
- // ignore it here; client will clean up shortly
- ioe.printStackTrace();
- }
-
- update(CHANGE_THREAD_MODE);
- }
-
- /**
- * Returns whether the thread update is enabled.
- */
- public boolean isThreadUpdateEnabled() {
- return mThreadUpdateEnabled;
- }
-
- /**
- * Sends a thread update request. This is asynchronous.
- * <p/>The thread info can be accessed by {@link ClientData#getThreads()}. The notification
- * that the new data is available will be received through
- * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
- * containing the mask {@link #CHANGE_THREAD_DATA}.
- */
- public void requestThreadUpdate() {
- HandleThread.requestThreadUpdate(this);
- }
-
- /**
- * Sends a thread stack trace update request. This is asynchronous.
- * <p/>The thread info can be accessed by {@link ClientData#getThreads()} and
- * {@link ThreadInfo#getStackTrace()}.
- * <p/>The notification that the new data is available
- * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
- * with a <code>changeMask</code> containing the mask {@link #CHANGE_THREAD_STACKTRACE}.
- */
- public void requestThreadStackTrace(int threadId) {
- HandleThread.requestThreadStackCallRefresh(this, threadId);
- }
-
- /**
- * Enables or disables the heap update.
- * <p/>If <code>true</code>, any GC will cause the client to send its heap information.
- * <p/>The heap information can be accessed by {@link ClientData#getVmHeapData()}.
- * <p/>The notification that the new data is available
- * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
- * with a <code>changeMask</code> containing the value {@link #CHANGE_HEAP_DATA}.
- * @param enabled the enable flag
- */
- public void setHeapUpdateEnabled(boolean enabled) {
- setHeapInfoUpdateEnabled(enabled);
- setHeapSegmentUpdateEnabled(enabled);
- }
-
- public void setHeapInfoUpdateEnabled(boolean enabled) {
- mHeapInfoUpdateEnabled = enabled;
-
- try {
- HandleHeap.sendHPIF(this,
- enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER);
-
- } catch (IOException ioe) {
- // ignore it here; client will clean up shortly
- }
-
- update(CHANGE_HEAP_MODE);
- }
-
- public void setHeapSegmentUpdateEnabled(boolean enabled) {
- mHeapSegmentUpdateEnabled = enabled;
-
- try {
- HandleHeap.sendHPSG(this,
- enabled ? HandleHeap.WHEN_GC : HandleHeap.WHEN_DISABLE,
- HandleHeap.WHAT_MERGE);
- } catch (IOException ioe) {
- // ignore it here; client will clean up shortly
- }
-
- update(CHANGE_HEAP_MODE);
- }
-
- void initializeHeapUpdateStatus() throws IOException {
- setHeapInfoUpdateEnabled(mHeapInfoUpdateEnabled);
- }
-
- /**
- * Fires a single heap update.
- */
- public void updateHeapInfo() {
- try {
- HandleHeap.sendHPIF(this, HandleHeap.HPIF_WHEN_NOW);
- } catch (IOException ioe) {
- // ignore it here; client will clean up shortly
- }
- }
-
- /**
- * Returns whether any heap update is enabled.
- * @see #setHeapUpdateEnabled(boolean)
- */
- public boolean isHeapUpdateEnabled() {
- return mHeapInfoUpdateEnabled || mHeapSegmentUpdateEnabled;
- }
-
- /**
- * Sends a native heap update request. this is asynchronous.
- * <p/>The native heap info can be accessed by {@link ClientData#getNativeAllocationList()}.
- * The notification that the new data is available will be received through
- * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
- * containing the mask {@link #CHANGE_NATIVE_HEAP_DATA}.
- */
- public boolean requestNativeHeapInformation() {
- try {
- HandleNativeHeap.sendNHGT(this);
- return true;
- } catch (IOException e) {
- Log.e("ddmlib", e);
- }
-
- return false;
- }
-
- /**
- * Enables or disables the Allocation tracker for this client.
- * <p/>If enabled, the VM will start tracking allocation information. A call to
- * {@link #requestAllocationDetails()} will make the VM sends the information about all the
- * allocations that happened between the enabling and the request.
- * @param enable
- * @see #requestAllocationDetails()
- */
- public void enableAllocationTracker(boolean enable) {
- try {
- HandleHeap.sendREAE(this, enable);
- } catch (IOException e) {
- Log.e("ddmlib", e);
- }
- }
-
- /**
- * Sends a request to the VM to send the enable status of the allocation tracking.
- * This is asynchronous.
- * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
- * The notification that the new status is available will be received through
- * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
- * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
- */
- public void requestAllocationStatus() {
- try {
- HandleHeap.sendREAQ(this);
- } catch (IOException e) {
- Log.e("ddmlib", e);
- }
- }
-
- /**
- * Sends a request to the VM to send the information about all the allocations that have
- * happened since the call to {@link #enableAllocationTracker(boolean)} with <var>enable</var>
- * set to <code>null</code>. This is asynchronous.
- * <p/>The allocation information can be accessed by {@link ClientData#getAllocations()}.
- * The notification that the new data is available will be received through
- * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
- * containing the mask {@link #CHANGE_HEAP_ALLOCATIONS}.
- */
- public void requestAllocationDetails() {
- try {
- HandleHeap.sendREAL(this);
- } catch (IOException e) {
- Log.e("ddmlib", e);
- }
- }
-
- /**
- * Sends a kill message to the VM.
- */
- public void kill() {
- try {
- HandleExit.sendEXIT(this, 1);
- } catch (IOException ioe) {
- Log.w("ddms", "Send of EXIT message failed");
- // ignore
- }
- }
-
- /**
- * Registers the client with a Selector.
- */
- void register(Selector sel) throws IOException {
- if (mChan != null) {
- mChan.register(sel, SelectionKey.OP_READ, this);
- }
- }
-
- /**
- * Sets the client to accept debugger connection on the "selected debugger port".
- *
- * @see AndroidDebugBridge#setSelectedClient(Client)
- * @see DdmPreferences#setSelectedDebugPort(int)
- */
- public void setAsSelectedClient() {
- MonitorThread monitorThread = MonitorThread.getInstance();
- if (monitorThread != null) {
- monitorThread.setSelectedClient(this);
- }
- }
-
- /**
- * Returns whether this client is the current selected client, accepting debugger connection
- * on the "selected debugger port".
- *
- * @see #setAsSelectedClient()
- * @see AndroidDebugBridge#setSelectedClient(Client)
- * @see DdmPreferences#setSelectedDebugPort(int)
- */
- public boolean isSelectedClient() {
- MonitorThread monitorThread = MonitorThread.getInstance();
- if (monitorThread != null) {
- return monitorThread.getSelectedClient() == this;
- }
-
- return false;
- }
-
- /**
- * Tell the client to open a server socket channel and listen for
- * connections on the specified port.
- */
- void listenForDebugger(int listenPort) throws IOException {
- mDebuggerListenPort = listenPort;
- mDebugger = new Debugger(this, listenPort);
- }
-
- /**
- * Initiate the JDWP handshake.
- *
- * On failure, closes the socket and returns false.
- */
- boolean sendHandshake() {
- assert mWriteBuffer.position() == 0;
-
- try {
- // assume write buffer can hold 14 bytes
- JdwpPacket.putHandshake(mWriteBuffer);
- int expectedLen = mWriteBuffer.position();
- mWriteBuffer.flip();
- if (mChan.write(mWriteBuffer) != expectedLen)
- throw new IOException("partial handshake write");
- }
- catch (IOException ioe) {
- Log.e("ddms-client", "IO error during handshake: " + ioe.getMessage());
- mConnState = ST_ERROR;
- close(true /* notify */);
- return false;
- }
- finally {
- mWriteBuffer.clear();
- }
-
- mConnState = ST_AWAIT_SHAKE;
-
- return true;
- }
-
-
- /**
- * Send a non-DDM packet to the client.
- *
- * Equivalent to sendAndConsume(packet, null).
- */
- void sendAndConsume(JdwpPacket packet) throws IOException {
- sendAndConsume(packet, null);
- }
-
- /**
- * Send a DDM packet to the client.
- *
- * Ideally, we can do this with a single channel write. If that doesn't
- * happen, we have to prevent anybody else from writing to the channel
- * until this packet completes, so we synchronize on the channel.
- *
- * Another goal is to avoid unnecessary buffer copies, so we write
- * directly out of the JdwpPacket's ByteBuffer.
- */
- void sendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)
- throws IOException {
-
- // Fix to avoid a race condition on mChan. This should be better synchronized
- // but just capturing the channel here, avoids a NPE.
- SocketChannel chan = mChan;
- if (chan == null) {
- // can happen for e.g. THST packets
- Log.v("ddms", "Not sending packet -- client is closed");
- return;
- }
-
- if (replyHandler != null) {
- /*
- * Add the ID to the list of outstanding requests. We have to do
- * this before sending the packet, in case the response comes back
- * before our thread returns from the packet-send function.
- */
- addRequestId(packet.getId(), replyHandler);
- }
-
- // Synchronizing on this variable is still useful as we do not want to threads
- // reading at the same time from the same channel, and the only change that
- // can happen to this channel is to be closed and mChan become null.
- //noinspection SynchronizationOnLocalVariableOrMethodParameter
- synchronized (chan) {
- try {
- packet.writeAndConsume(chan);
- }
- catch (IOException ioe) {
- removeRequestId(packet.getId());
- throw ioe;
- }
- }
- }
-
- /**
- * Forward the packet to the debugger (if still connected to one).
- *
- * Consumes the packet.
- */
- void forwardPacketToDebugger(JdwpPacket packet)
- throws IOException {
-
- Debugger dbg = mDebugger;
-
- if (dbg == null) {
- Log.d("ddms", "Discarding packet");
- packet.consume();
- } else {
- dbg.sendAndConsume(packet);
- }
- }
-
- /**
- * Read data from our channel.
- *
- * This is called when data is known to be available, and we don't yet
- * have a full packet in the buffer. If the buffer is at capacity,
- * expand it.
- */
- void read()
- throws IOException, BufferOverflowException {
-
- int count;
-
- if (mReadBuffer.position() == mReadBuffer.capacity()) {
- if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
- Log.e("ddms", "Exceeded MAX_BUF_SIZE!");
- throw new BufferOverflowException();
- }
- Log.d("ddms", "Expanding read buffer to "
- + mReadBuffer.capacity() * 2);
-
- ByteBuffer newBuffer = ByteBuffer.allocate(mReadBuffer.capacity() * 2);
-
- // copy entire buffer to new buffer
- mReadBuffer.position(0);
- newBuffer.put(mReadBuffer); // leaves "position" at end of copied
-
- mReadBuffer = newBuffer;
- }
-
- count = mChan.read(mReadBuffer);
- if (count < 0)
- throw new IOException("read failed");
-
- if (Log.Config.LOGV) Log.v("ddms", "Read " + count + " bytes from " + this);
- //Log.hexDump("ddms", Log.DEBUG, mReadBuffer.array(),
- // mReadBuffer.arrayOffset(), mReadBuffer.position());
- }
-
- /**
- * Return information for the first full JDWP packet in the buffer.
- *
- * If we don't yet have a full packet, return null.
- *
- * If we haven't yet received the JDWP handshake, we watch for it here
- * and consume it without admitting to have done so. Upon receipt
- * we send out the "HELO" message, which is why this can throw an
- * IOException.
- */
- JdwpPacket getJdwpPacket() throws IOException {
-
- /*
- * On entry, the data starts at offset 0 and ends at "position".
- * "limit" is set to the buffer capacity.
- */
- if (mConnState == ST_AWAIT_SHAKE) {
- /*
- * The first thing we get from the client is a response to our
- * handshake. It doesn't look like a packet, so we have to
- * handle it specially.
- */
- int result;
-
- result = JdwpPacket.findHandshake(mReadBuffer);
- //Log.v("ddms", "findHand: " + result);
- switch (result) {
- case JdwpPacket.HANDSHAKE_GOOD:
- Log.d("ddms",
- "Good handshake from client, sending HELO to " + mClientData.getPid());
- JdwpPacket.consumeHandshake(mReadBuffer);
- mConnState = ST_NEED_DDM_PKT;
- HandleHello.sendHelloCommands(this, SERVER_PROTOCOL_VERSION);
- // see if we have another packet in the buffer
- return getJdwpPacket();
- case JdwpPacket.HANDSHAKE_BAD:
- Log.d("ddms", "Bad handshake from client");
- if (MonitorThread.getInstance().getRetryOnBadHandshake()) {
- // we should drop the client, but also attempt to reopen it.
- // This is done by the DeviceMonitor.
- mDevice.getMonitor().addClientToDropAndReopen(this,
- IDebugPortProvider.NO_STATIC_PORT);
- } else {
- // mark it as bad, close the socket, and don't retry
- mConnState = ST_NOT_JDWP;
- close(true /* notify */);
- }
- break;
- case JdwpPacket.HANDSHAKE_NOTYET:
- Log.d("ddms", "No handshake from client yet.");
- break;
- default:
- Log.e("ddms", "Unknown packet while waiting for client handshake");
- }
- return null;
- } else if (mConnState == ST_NEED_DDM_PKT ||
- mConnState == ST_NOT_DDM ||
- mConnState == ST_READY) {
- /*
- * Normal packet traffic.
- */
- if (mReadBuffer.position() != 0) {
- if (Log.Config.LOGV) Log.v("ddms",
- "Checking " + mReadBuffer.position() + " bytes");
- }
- return JdwpPacket.findPacket(mReadBuffer);
- } else {
- /*
- * Not expecting data when in this state.
- */
- Log.e("ddms", "Receiving data in state = " + mConnState);
- }
-
- return null;
- }
-
- /*
- * Add the specified ID to the list of request IDs for which we await
- * a response.
- */
- private void addRequestId(int id, ChunkHandler handler) {
- synchronized (mOutstandingReqs) {
- if (Log.Config.LOGV) Log.v("ddms",
- "Adding req 0x" + Integer.toHexString(id) +" to set");
- mOutstandingReqs.put(id, handler);
- }
- }
-
- /*
- * Remove the specified ID from the list, if present.
- */
- void removeRequestId(int id) {
- synchronized (mOutstandingReqs) {
- if (Log.Config.LOGV) Log.v("ddms",
- "Removing req 0x" + Integer.toHexString(id) + " from set");
- mOutstandingReqs.remove(id);
- }
-
- //Log.w("ddms", "Request " + Integer.toHexString(id)
- // + " could not be removed from " + this);
- }
-
- /**
- * Determine whether this is a response to a request we sent earlier.
- * If so, return the ChunkHandler responsible.
- */
- ChunkHandler isResponseToUs(int id) {
-
- synchronized (mOutstandingReqs) {
- ChunkHandler handler = mOutstandingReqs.get(id);
- if (handler != null) {
- if (Log.Config.LOGV) Log.v("ddms",
- "Found 0x" + Integer.toHexString(id)
- + " in request set - " + handler);
- return handler;
- }
- }
-
- return null;
- }
-
- /**
- * An earlier request resulted in a failure. This is the expected
- * response to a HELO message when talking to a non-DDM client.
- */
- void packetFailed(JdwpPacket reply) {
- if (mConnState == ST_NEED_DDM_PKT) {
- Log.d("ddms", "Marking " + this + " as non-DDM client");
- mConnState = ST_NOT_DDM;
- } else if (mConnState != ST_NOT_DDM) {
- Log.w("ddms", "WEIRD: got JDWP failure packet on DDM req");
- }
- }
-
- /**
- * The MonitorThread calls this when it sees a DDM request or reply.
- * If we haven't seen a DDM packet before, we advance the state to
- * ST_READY and return "false". Otherwise, just return true.
- *
- * The idea is to let the MonitorThread know when we first see a DDM
- * packet, so we can send a broadcast to the handlers when a client
- * connection is made. This method is synchronized so that we only
- * send the broadcast once.
- */
- synchronized boolean ddmSeen() {
- if (mConnState == ST_NEED_DDM_PKT) {
- mConnState = ST_READY;
- return false;
- } else if (mConnState != ST_READY) {
- Log.w("ddms", "WEIRD: in ddmSeen with state=" + mConnState);
- }
- return true;
- }
-
- /**
- * Close the client socket channel. If there is a debugger associated
- * with us, close that too.
- *
- * Closing a channel automatically unregisters it from the selector.
- * However, we have to iterate through the selector loop before it
- * actually lets them go and allows the file descriptors to close.
- * The caller is expected to manage that.
- * @param notify Whether or not to notify the listeners of a change.
- */
- void close(boolean notify) {
- Log.d("ddms", "Closing " + this.toString());
-
- mOutstandingReqs.clear();
-
- try {
- if (mChan != null) {
- mChan.close();
- mChan = null;
- }
-
- if (mDebugger != null) {
- mDebugger.close();
- mDebugger = null;
- }
- }
- catch (IOException ioe) {
- Log.w("ddms", "failed to close " + this);
- // swallow it -- not much else to do
- }
-
- mDevice.removeClient(this, notify);
- }
-
- /**
- * Returns whether this {@link Client} has a valid connection to the application VM.
- */
- public boolean isValid() {
- return mChan != null;
- }
-
- void update(int changeMask) {
- mDevice.update(this, changeMask);
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/ClientData.java b/base/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
deleted file mode 100644
index 2362a44..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
+++ /dev/null
@@ -1,843 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ddmlib.HeapSegment.HeapSegmentElement;
-
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-
-/**
- * Contains the data of a {@link Client}.
- */
-public class ClientData {
- /* This is a place to stash data associated with a Client, such as thread
- * states or heap data. ClientData maps 1:1 to Client, but it's a little
- * cleaner if we separate the data out.
- *
- * Message handlers are welcome to stash arbitrary data here.
- *
- * IMPORTANT: The data here is written by HandleFoo methods and read by
- * FooPanel methods, which run in different threads. All non-trivial
- * access should be synchronized against the ClientData object.
- */
-
-
- /** Temporary name of VM to be ignored. */
- private static final String PRE_INITIALIZED = "<pre-initialized>"; //$NON-NLS-1$
-
- public enum DebuggerStatus {
- /** Debugger connection status: not waiting on one, not connected to one, but accepting
- * new connections. This is the default value. */
- DEFAULT,
- /**
- * Debugger connection status: the application's VM is paused, waiting for a debugger to
- * connect to it before resuming. */
- WAITING,
- /** Debugger connection status : Debugger is connected */
- ATTACHED,
- /** Debugger connection status: The listening port for debugger connection failed to listen.
- * No debugger will be able to connect. */
- ERROR
- }
-
- public enum AllocationTrackingStatus {
- /**
- * Allocation tracking status: unknown.
- * <p/>This happens right after a {@link Client} is discovered
- * by the {@link AndroidDebugBridge}, and before the {@link Client} answered the query
- * regarding its allocation tracking status.
- * @see Client#requestAllocationStatus()
- */
- UNKNOWN,
- /** Allocation tracking status: the {@link Client} is not tracking allocations. */
- OFF,
- /** Allocation tracking status: the {@link Client} is tracking allocations. */
- ON
- }
-
- public enum MethodProfilingStatus {
- /**
- * Method profiling status: unknown.
- * <p/>This happens right after a {@link Client} is discovered
- * by the {@link AndroidDebugBridge}, and before the {@link Client} answered the query
- * regarding its method profiling status.
- * @see Client#requestMethodProfilingStatus()
- */
- UNKNOWN,
- /** Method profiling status: the {@link Client} is not profiling method calls. */
- OFF,
- /** Method profiling status: the {@link Client} is tracing method calls. */
- TRACER_ON,
- /** Method profiling status: the {@link Client} is being profiled via sampling. */
- SAMPLER_ON
- }
-
- /**
- * String for feature enabling starting/stopping method profiling
- * @see #hasFeature(String)
- */
- public static final String FEATURE_PROFILING = "method-trace-profiling"; //$NON-NLS-1$
-
- /**
- * String for feature enabling direct streaming of method profiling data
- * @see #hasFeature(String)
- */
- public static final String FEATURE_PROFILING_STREAMING = "method-trace-profiling-streaming"; //$NON-NLS-1$
-
- /**
- * String for feature enabling sampling profiler.
- * @see #hasFeature(String)
- */
- public static final String FEATURE_SAMPLING_PROFILER = "method-sample-profiling"; //$NON-NLS-1$
-
- /**
- * String for feature indicating support for tracing OpenGL calls.
- * @see #hasFeature(String)
- */
- public static final String FEATURE_OPENGL_TRACING = "opengl-tracing"; //$NON-NLS-1$
-
- /**
- * String for feature indicating support for providing view hierarchy.
- * @see #hasFeature(String)
- */
- public static final String FEATURE_VIEW_HIERARCHY = "view-hierarchy"; //$NON-NLS-1$
-
- /**
- * String for feature allowing to dump hprof files
- * @see #hasFeature(String)
- */
- public static final String FEATURE_HPROF = "hprof-heap-dump"; //$NON-NLS-1$
-
- /**
- * String for feature allowing direct streaming of hprof dumps
- * @see #hasFeature(String)
- */
- public static final String FEATURE_HPROF_STREAMING = "hprof-heap-dump-streaming"; //$NON-NLS-1$
-
- @Deprecated
- private static IHprofDumpHandler sHprofDumpHandler;
- private static IMethodProfilingHandler sMethodProfilingHandler;
- private static IAllocationTrackingHandler sAllocationTrackingHandler;
-
- // is this a DDM-aware client?
- private boolean mIsDdmAware;
-
- // the client's process ID
- private final int mPid;
-
- // Java VM identification string
- private String mVmIdentifier;
-
- // client's self-description
- private String mClientDescription;
-
- // client's user id (on device in a multi user environment)
- private int mUserId;
-
- // client's user id is valid
- private boolean mValidUserId;
-
- // client's ABI
- private String mAbi;
-
- // jvm flag: currently only indicates whether checkJni is enabled
- private String mJvmFlags;
-
- // how interested are we in a debugger?
- private DebuggerStatus mDebuggerInterest;
-
- // List of supported features by the client.
- private final HashSet<String> mFeatures = new HashSet<String>();
-
- // Thread tracking (THCR, THDE).
- private TreeMap<Integer,ThreadInfo> mThreadMap;
-
- /** VM Heap data */
- private final HeapData mHeapData = new HeapData();
- /** Native Heap data */
- private final HeapData mNativeHeapData = new HeapData();
-
- /** Hprof data */
- private HprofData mHprofData = null;
-
- private HashMap<Integer, HeapInfo> mHeapInfoMap = new HashMap<Integer, HeapInfo>();
-
- /** library map info. Stored here since the backtrace data
- * is computed on a need to display basis.
- */
- private ArrayList<NativeLibraryMapInfo> mNativeLibMapInfo =
- new ArrayList<NativeLibraryMapInfo>();
-
- /** Native Alloc info list */
- private ArrayList<NativeAllocationInfo> mNativeAllocationList =
- new ArrayList<NativeAllocationInfo>();
- private int mNativeTotalMemory;
-
- private AllocationInfo[] mAllocations;
- private AllocationTrackingStatus mAllocationStatus = AllocationTrackingStatus.UNKNOWN;
-
- @Deprecated
- private String mPendingHprofDump;
-
- private MethodProfilingStatus mProfilingStatus = MethodProfilingStatus.UNKNOWN;
- private String mPendingMethodProfiling;
-
- /**
- * Heap Information.
- * <p/>The heap is composed of several {@link HeapSegment} objects.
- * <p/>A call to {@link #isHeapDataComplete()} will indicate if the segments (available through
- * {@link #getHeapSegments()}) represent the full heap.
- */
- public static class HeapData {
- private TreeSet<HeapSegment> mHeapSegments = new TreeSet<HeapSegment>();
- private boolean mHeapDataComplete = false;
- private byte[] mProcessedHeapData;
- private Map<Integer, ArrayList<HeapSegmentElement>> mProcessedHeapMap;
-
- /**
- * Abandon the current list of heap segments.
- */
- public synchronized void clearHeapData() {
- /* Abandon the old segments instead of just calling .clear().
- * This lets the user hold onto the old set if it wants to.
- */
- mHeapSegments = new TreeSet<HeapSegment>();
- mHeapDataComplete = false;
- }
-
- /**
- * Add raw HPSG chunk data to the list of heap segments.
- *
- * @param data The raw data from an HPSG chunk.
- */
- synchronized void addHeapData(ByteBuffer data) {
- HeapSegment hs;
-
- if (mHeapDataComplete) {
- clearHeapData();
- }
-
- try {
- hs = new HeapSegment(data);
- } catch (BufferUnderflowException e) {
- System.err.println("Discarding short HPSG data (length " + data.limit() + ")");
- return;
- }
-
- mHeapSegments.add(hs);
- }
-
- /**
- * Called when all heap data has arrived.
- */
- synchronized void sealHeapData() {
- mHeapDataComplete = true;
- }
-
- /**
- * Returns whether the heap data has been sealed.
- */
- public boolean isHeapDataComplete() {
- return mHeapDataComplete;
- }
-
- /**
- * Get the collected heap data, if sealed.
- *
- * @return The list of heap segments if the heap data has been sealed, or null if it hasn't.
- */
- public Collection<HeapSegment> getHeapSegments() {
- if (isHeapDataComplete()) {
- return mHeapSegments;
- }
- return null;
- }
-
- /**
- * Sets the processed heap data.
- *
- * @param heapData The new heap data (can be null)
- */
- public void setProcessedHeapData(byte[] heapData) {
- mProcessedHeapData = heapData;
- }
-
- /**
- * Get the processed heap data, if present.
- *
- * @return the processed heap data, or null.
- */
- public byte[] getProcessedHeapData() {
- return mProcessedHeapData;
- }
-
- public void setProcessedHeapMap(Map<Integer, ArrayList<HeapSegmentElement>> heapMap) {
- mProcessedHeapMap = heapMap;
- }
-
- public Map<Integer, ArrayList<HeapSegmentElement>> getProcessedHeapMap() {
- return mProcessedHeapMap;
- }
- }
-
- public static class HeapInfo {
- public long maxSizeInBytes;
- public long sizeInBytes;
- public long bytesAllocated;
- public long objectsAllocated;
- public long timeStamp;
- public byte reason;
-
- public HeapInfo(long maxSizeInBytes,
- long sizeInBytes,
- long bytesAllocated,
- long objectsAllocated,
- long timeStamp,
- byte reason) {
- this.maxSizeInBytes = maxSizeInBytes;
- this.sizeInBytes = sizeInBytes;
- this.bytesAllocated = bytesAllocated;
- this.objectsAllocated = objectsAllocated;
- this.timeStamp = timeStamp;
- this.reason = reason;
- }
- }
-
- public static class HprofData {
- public enum Type {
- FILE,
- DATA
- }
-
- public final Type type;
- public final String filename;
- public final byte[] data;
-
- public HprofData(@NonNull String filename) {
- type = Type.FILE;
- this.filename = filename;
- this.data = null;
- }
-
- public HprofData(@NonNull byte[] data) {
- type = Type.DATA;
- this.data = data;
- this.filename = null;
- }
- }
-
- /**
- * Handlers able to act on HPROF dumps.
- */
- @Deprecated
- public interface IHprofDumpHandler {
- /**
- * Called when a HPROF dump succeeded.
- * @param remoteFilePath the device-side path of the HPROF file.
- * @param client the client for which the HPROF file was.
- */
- void onSuccess(String remoteFilePath, Client client);
-
- /**
- * Called when a HPROF dump was successful.
- * @param data the data containing the HPROF file, streamed from the VM
- * @param client the client that was profiled.
- */
- void onSuccess(byte[] data, Client client);
-
- /**
- * Called when a hprof dump failed to end on the VM side
- * @param client the client that was profiled.
- * @param message an optional (<code>null<code> ok) error message to be displayed.
- */
- void onEndFailure(Client client, String message);
- }
-
- /**
- * Handlers able to act on Method profiling info
- */
- public interface IMethodProfilingHandler {
- /**
- * Called when a method tracing was successful.
- * @param remoteFilePath the device-side path of the trace file.
- * @param client the client that was profiled.
- */
- void onSuccess(String remoteFilePath, Client client);
-
- /**
- * Called when a method tracing was successful.
- * @param data the data containing the trace file, streamed from the VM
- * @param client the client that was profiled.
- */
- void onSuccess(byte[] data, Client client);
-
- /**
- * Called when method tracing failed to start
- * @param client the client that was profiled.
- * @param message an optional (<code>null<code> ok) error message to be displayed.
- */
- void onStartFailure(Client client, String message);
-
- /**
- * Called when method tracing failed to end on the VM side
- * @param client the client that was profiled.
- * @param message an optional (<code>null<code> ok) error message to be displayed.
- */
- void onEndFailure(Client client, String message);
- }
-
- /*
- * Handlers able to act on allocation tracking info
- */
- public interface IAllocationTrackingHandler {
- /**
- * Called when an allocation tracking was successful.
- * @param data the data containing the encoded allocations.
- * See {@link AllocationsParser#parse(java.nio.ByteBuffer)} for parsing this data.
- * @param client the client for which allocations were tracked.
- */
- void onSuccess(@NonNull byte[] data, @NonNull Client client);
- }
-
- public void setHprofData(byte[] data) {
- mHprofData = new HprofData(data);
- }
-
- public void setHprofData(String filename) {
- mHprofData = new HprofData(filename);
- }
-
- public void clearHprofData() {
- mHprofData = null;
- }
-
- public HprofData getHprofData() {
- return mHprofData;
- }
-
- /**
- * Sets the handler to receive notifications when an HPROF dump succeeded or failed.
- * This method is deprecated, please register a client listener and listen for CHANGE_HPROF.
- */
- @Deprecated
- public static void setHprofDumpHandler(IHprofDumpHandler handler) {
- sHprofDumpHandler = handler;
- }
-
- @Deprecated
- static IHprofDumpHandler getHprofDumpHandler() {
- return sHprofDumpHandler;
- }
-
- /**
- * Sets the handler to receive notifications when an HPROF dump succeeded or failed.
- * This method is deprecated, please register a client listener and listen for CHANGE_HPROF.
- */
- public static void setMethodProfilingHandler(IMethodProfilingHandler handler) {
- sMethodProfilingHandler = handler;
- }
-
- static IMethodProfilingHandler getMethodProfilingHandler() {
- return sMethodProfilingHandler;
- }
-
- public static void setAllocationTrackingHandler(@NonNull IAllocationTrackingHandler handler) {
- sAllocationTrackingHandler = handler;
- }
-
- @Nullable
- static IAllocationTrackingHandler getAllocationTrackingHandler() {
- return sAllocationTrackingHandler;
- }
-
- /**
- * Generic constructor.
- */
- ClientData(int pid) {
- mPid = pid;
-
- mDebuggerInterest = DebuggerStatus.DEFAULT;
- mThreadMap = new TreeMap<Integer,ThreadInfo>();
- }
-
- /**
- * Returns whether the process is DDM-aware.
- */
- public boolean isDdmAware() {
- return mIsDdmAware;
- }
-
- /**
- * Sets DDM-aware status.
- */
- void isDdmAware(boolean aware) {
- mIsDdmAware = aware;
- }
-
- /**
- * Returns the process ID.
- */
- public int getPid() {
- return mPid;
- }
-
- /**
- * Returns the Client's VM identifier.
- */
- public String getVmIdentifier() {
- return mVmIdentifier;
- }
-
- /**
- * Sets VM identifier.
- */
- void setVmIdentifier(String ident) {
- mVmIdentifier = ident;
- }
-
- /**
- * Returns the client description.
- * <p/>This is generally the name of the package defined in the
- * <code>AndroidManifest.xml</code>.
- *
- * @return the client description or <code>null</code> if not the description was not yet
- * sent by the client.
- */
- public String getClientDescription() {
- return mClientDescription;
- }
-
- /**
- * Returns the client's user id.
- * @return user id if set, -1 otherwise
- */
- public int getUserId() {
- return mUserId;
- }
-
- /**
- * Returns true if the user id of this client was set. Only devices that support multiple
- * users will actually return the user id to ddms. For other/older devices, this will not
- * be set.
- */
- public boolean isValidUserId() {
- return mValidUserId;
- }
-
- /** Returns the abi flavor (32-bit or 64-bit) of the application, null if unknown or not set. */
- @Nullable
- public String getAbi() {
- return mAbi;
- }
-
- /** Returns the VM flags in use, or null if unknown. */
- public String getJvmFlags() {
- return mJvmFlags;
- }
-
- /**
- * Sets client description.
- *
- * There may be a race between HELO and APNM. Rather than try
- * to enforce ordering on the device, we just don't allow an empty
- * name to replace a specified one.
- */
- void setClientDescription(String description) {
- if (mClientDescription == null && !description.isEmpty()) {
- /*
- * The application VM is first named <pre-initialized> before being assigned
- * its real name.
- * Depending on the timing, we can get an APNM chunk setting this name before
- * another one setting the final actual name. So if we get a SetClientDescription
- * with this value we ignore it.
- */
- if (!PRE_INITIALIZED.equals(description)) {
- mClientDescription = description;
- }
- }
- }
-
- void setUserId(int id) {
- mUserId = id;
- mValidUserId = true;
- }
-
- void setAbi(String abi) {
- mAbi = abi;
- }
-
- void setJvmFlags(String jvmFlags) {
- mJvmFlags = jvmFlags;
- }
-
- /**
- * Returns the debugger connection status.
- */
- public DebuggerStatus getDebuggerConnectionStatus() {
- return mDebuggerInterest;
- }
-
- /**
- * Sets debugger connection status.
- */
- void setDebuggerConnectionStatus(DebuggerStatus status) {
- mDebuggerInterest = status;
- }
-
- /**
- * Sets the current heap info values for the specified heap.
- * @param heapId The heap whose info to update
- * @param sizeInBytes The size of the heap, in bytes
- * @param bytesAllocated The number of bytes currently allocated in the heap
- * @param objectsAllocated The number of objects currently allocated in
- * @param timeStamp
- * @param reason
- */
- synchronized void setHeapInfo(int heapId,
- long maxSizeInBytes,
- long sizeInBytes,
- long bytesAllocated,
- long objectsAllocated,
- long timeStamp,
- byte reason) {
- mHeapInfoMap.put(heapId, new HeapInfo(maxSizeInBytes, sizeInBytes, bytesAllocated,
- objectsAllocated, timeStamp, reason));
- }
-
- /**
- * Returns the {@link HeapData} object for the VM.
- */
- public HeapData getVmHeapData() {
- return mHeapData;
- }
-
- /**
- * Returns the {@link HeapData} object for the native code.
- */
- HeapData getNativeHeapData() {
- return mNativeHeapData;
- }
-
- /**
- * Returns an iterator over the list of known VM heap ids.
- * <p/>
- * The caller must synchronize on the {@link ClientData} object while iterating.
- *
- * @return an iterator over the list of heap ids
- */
- public synchronized Iterator<Integer> getVmHeapIds() {
- return mHeapInfoMap.keySet().iterator();
- }
-
- /**
- * Returns the most-recent info values for the specified VM heap.
- *
- * @param heapId The heap whose info should be returned
- * @return a map containing the info values for the specified heap.
- * Returns <code>null</code> if the heap ID is unknown.
- */
- public synchronized HeapInfo getVmHeapInfo(int heapId) {
- return mHeapInfoMap.get(heapId);
- }
-
- /**
- * Adds a new thread to the list.
- */
- synchronized void addThread(int threadId, String threadName) {
- ThreadInfo attr = new ThreadInfo(threadId, threadName);
- mThreadMap.put(threadId, attr);
- }
-
- /**
- * Removes a thread from the list.
- */
- synchronized void removeThread(int threadId) {
- mThreadMap.remove(threadId);
- }
-
- /**
- * Returns the list of threads as {@link ThreadInfo} objects.
- * <p/>The list is empty until a thread update was requested with
- * {@link Client#requestThreadUpdate()}.
- */
- public synchronized ThreadInfo[] getThreads() {
- Collection<ThreadInfo> threads = mThreadMap.values();
- return threads.toArray(new ThreadInfo[threads.size()]);
- }
-
- /**
- * Returns the {@link ThreadInfo} by thread id.
- */
- synchronized ThreadInfo getThread(int threadId) {
- return mThreadMap.get(threadId);
- }
-
- synchronized void clearThreads() {
- mThreadMap.clear();
- }
-
- /**
- * Returns the list of {@link NativeAllocationInfo}.
- * @see Client#requestNativeHeapInformation()
- */
- public synchronized List<NativeAllocationInfo> getNativeAllocationList() {
- return Collections.unmodifiableList(mNativeAllocationList);
- }
-
- /**
- * adds a new {@link NativeAllocationInfo} to the {@link Client}
- * @param allocInfo The {@link NativeAllocationInfo} to add.
- */
- synchronized void addNativeAllocation(NativeAllocationInfo allocInfo) {
- mNativeAllocationList.add(allocInfo);
- }
-
- /**
- * Clear the current malloc info.
- */
- synchronized void clearNativeAllocationInfo() {
- mNativeAllocationList.clear();
- }
-
- /**
- * Returns the total native memory.
- * @see Client#requestNativeHeapInformation()
- */
- public synchronized int getTotalNativeMemory() {
- return mNativeTotalMemory;
- }
-
- synchronized void setTotalNativeMemory(int totalMemory) {
- mNativeTotalMemory = totalMemory;
- }
-
- synchronized void addNativeLibraryMapInfo(long startAddr, long endAddr, String library) {
- mNativeLibMapInfo.add(new NativeLibraryMapInfo(startAddr, endAddr, library));
- }
-
- /**
- * Returns the list of native libraries mapped in memory for this client.
- */
- public synchronized List<NativeLibraryMapInfo> getMappedNativeLibraries() {
- return Collections.unmodifiableList(mNativeLibMapInfo);
- }
-
- synchronized void setAllocationStatus(AllocationTrackingStatus status) {
- mAllocationStatus = status;
- }
-
- /**
- * Returns the allocation tracking status.
- * @see Client#requestAllocationStatus()
- */
- public synchronized AllocationTrackingStatus getAllocationStatus() {
- return mAllocationStatus;
- }
-
- synchronized void setAllocations(AllocationInfo[] allocs) {
- mAllocations = allocs;
- }
-
- /**
- * Returns the list of tracked allocations.
- * @see Client#requestAllocationDetails()
- */
- @Nullable
- public synchronized AllocationInfo[] getAllocations() {
- return mAllocations;
- }
-
- void addFeature(String feature) {
- mFeatures.add(feature);
- }
-
- /**
- * Returns true if the {@link Client} supports the given <var>feature</var>
- * @param feature The feature to test.
- * @return true if the feature is supported
- *
- * @see ClientData#FEATURE_PROFILING
- * @see ClientData#FEATURE_HPROF
- */
- public boolean hasFeature(String feature) {
- return mFeatures.contains(feature);
- }
-
- /**
- * Sets the device-side path to the hprof file being written
- * @param pendingHprofDump the file to the hprof file
- */
- @Deprecated
- void setPendingHprofDump(String pendingHprofDump) {
- mPendingHprofDump = pendingHprofDump;
- }
-
- /**
- * Returns the path to the device-side hprof file being written.
- */
- @Deprecated
- String getPendingHprofDump() {
- return mPendingHprofDump;
- }
-
- @Deprecated
- public boolean hasPendingHprofDump() {
- return mPendingHprofDump != null;
- }
-
- synchronized void setMethodProfilingStatus(MethodProfilingStatus status) {
- mProfilingStatus = status;
- }
-
- /**
- * Returns the method profiling status.
- * @see Client#requestMethodProfilingStatus()
- */
- public synchronized MethodProfilingStatus getMethodProfilingStatus() {
- return mProfilingStatus;
- }
-
- /**
- * Sets the device-side path to the method profile file being written
- * @param pendingMethodProfiling the file being written
- */
- void setPendingMethodProfiling(String pendingMethodProfiling) {
- mPendingMethodProfiling = pendingMethodProfiling;
- }
-
- /**
- * Returns the path to the device-side method profiling file being written.
- */
- String getPendingMethodProfiling() {
- return mPendingMethodProfiling;
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/Debugger.java b/base/ddmlib/src/main/java/com/android/ddmlib/Debugger.java
deleted file mode 100644
index 9356c13..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/Debugger.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.ddmlib.ClientData.DebuggerStatus;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-
-/**
- * This represents a pending or established connection with a JDWP debugger.
- */
-class Debugger {
-
- /*
- * Messages from the debugger should be pretty small; may not even
- * need an expanding-buffer implementation for this.
- */
- private static final int INITIAL_BUF_SIZE = 1 * 1024;
- private static final int MAX_BUF_SIZE = 32 * 1024;
- private ByteBuffer mReadBuffer;
-
- private static final int PRE_DATA_BUF_SIZE = 256;
- private ByteBuffer mPreDataBuffer;
-
- /* connection state */
- private int mConnState;
- private static final int ST_NOT_CONNECTED = 1;
- private static final int ST_AWAIT_SHAKE = 2;
- private static final int ST_READY = 3;
-
- /* peer */
- private Client mClient; // client we're forwarding to/from
- private int mListenPort; // listen to me
- private ServerSocketChannel mListenChannel;
-
- /* this goes up and down; synchronize methods that access the field */
- private SocketChannel mChannel;
-
- /**
- * Create a new Debugger object, configured to listen for connections
- * on a specific port.
- */
- Debugger(Client client, int listenPort) throws IOException {
-
- mClient = client;
- mListenPort = listenPort;
-
- mListenChannel = ServerSocketChannel.open();
- mListenChannel.configureBlocking(false); // required for Selector
-
- InetSocketAddress addr = new InetSocketAddress(
- InetAddress.getByName("localhost"), //$NON-NLS-1$
- listenPort);
- mListenChannel.socket().setReuseAddress(true); // enable SO_REUSEADDR
- mListenChannel.socket().bind(addr);
-
- mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
- mPreDataBuffer = ByteBuffer.allocate(PRE_DATA_BUF_SIZE);
- mConnState = ST_NOT_CONNECTED;
-
- Log.d("ddms", "Created: " + this.toString());
- }
-
- /**
- * Returns "true" if a debugger is currently attached to us.
- */
- boolean isDebuggerAttached() {
- return mChannel != null;
- }
-
- /**
- * Represent the Debugger as a string.
- */
- @Override
- public String toString() {
- // mChannel != null means we have connection, ST_READY means it's going
- return "[Debugger " + mListenPort + "-->" + mClient.getClientData().getPid()
- + ((mConnState != ST_READY) ? " inactive]" : " active]");
- }
-
- /**
- * Register the debugger's listen socket with the Selector.
- */
- void registerListener(Selector sel) throws IOException {
- mListenChannel.register(sel, SelectionKey.OP_ACCEPT, this);
- }
-
- /**
- * Return the Client being debugged.
- */
- Client getClient() {
- return mClient;
- }
-
- /**
- * Accept a new connection, but only if we don't already have one.
- *
- * Must be synchronized with other uses of mChannel and mPreBuffer.
- *
- * Returns "null" if we're already talking to somebody.
- */
- synchronized SocketChannel accept() throws IOException {
- return accept(mListenChannel);
- }
-
- /**
- * Accept a new connection from the specified listen channel. This
- * is so we can listen on a dedicated port for the "current" client,
- * where "current" is constantly in flux.
- *
- * Must be synchronized with other uses of mChannel and mPreBuffer.
- *
- * Returns "null" if we're already talking to somebody.
- */
- synchronized SocketChannel accept(ServerSocketChannel listenChan)
- throws IOException {
-
- if (listenChan != null) {
- SocketChannel newChan;
-
- newChan = listenChan.accept();
- if (mChannel != null) {
- Log.w("ddms", "debugger already talking to " + mClient
- + " on " + mListenPort);
- newChan.close();
- return null;
- }
- mChannel = newChan;
- mChannel.configureBlocking(false); // required for Selector
- mConnState = ST_AWAIT_SHAKE;
- return mChannel;
- }
-
- return null;
- }
-
- /**
- * Close the data connection only.
- */
- synchronized void closeData() {
- try {
- if (mChannel != null) {
- mChannel.close();
- mChannel = null;
- mConnState = ST_NOT_CONNECTED;
-
- ClientData cd = mClient.getClientData();
- cd.setDebuggerConnectionStatus(DebuggerStatus.DEFAULT);
- mClient.update(Client.CHANGE_DEBUGGER_STATUS);
- }
- } catch (IOException ioe) {
- Log.w("ddms", "Failed to close data " + this);
- }
- }
-
- /**
- * Close the socket that's listening for new connections and (if
- * we're connected) the debugger data socket.
- */
- synchronized void close() {
- try {
- if (mListenChannel != null) {
- mListenChannel.close();
- }
- mListenChannel = null;
- closeData();
- } catch (IOException ioe) {
- Log.w("ddms", "Failed to close listener " + this);
- }
- }
-
- // TODO: ?? add a finalizer that verifies the channel was closed
-
- /**
- * Read data from our channel.
- *
- * This is called when data is known to be available, and we don't yet
- * have a full packet in the buffer. If the buffer is at capacity,
- * expand it.
- */
- void read() throws IOException {
- int count;
-
- if (mReadBuffer.position() == mReadBuffer.capacity()) {
- if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
- throw new BufferOverflowException();
- }
- Log.d("ddms", "Expanding read buffer to "
- + mReadBuffer.capacity() * 2);
-
- ByteBuffer newBuffer =
- ByteBuffer.allocate(mReadBuffer.capacity() * 2);
- mReadBuffer.position(0);
- newBuffer.put(mReadBuffer); // leaves "position" at end
-
- mReadBuffer = newBuffer;
- }
-
- count = mChannel.read(mReadBuffer);
- Log.v("ddms", "Read " + count + " bytes from " + this);
- if (count < 0) throw new IOException("read failed");
- }
-
- /**
- * Return information for the first full JDWP packet in the buffer.
- *
- * If we don't yet have a full packet, return null.
- *
- * If we haven't yet received the JDWP handshake, we watch for it here
- * and consume it without admitting to have done so. We also send
- * the handshake response to the debugger, along with any pending
- * pre-connection data, which is why this can throw an IOException.
- */
- JdwpPacket getJdwpPacket() throws IOException {
- /*
- * On entry, the data starts at offset 0 and ends at "position".
- * "limit" is set to the buffer capacity.
- */
- if (mConnState == ST_AWAIT_SHAKE) {
- int result;
-
- result = JdwpPacket.findHandshake(mReadBuffer);
- //Log.v("ddms", "findHand: " + result);
- switch (result) {
- case JdwpPacket.HANDSHAKE_GOOD:
- Log.d("ddms", "Good handshake from debugger");
- JdwpPacket.consumeHandshake(mReadBuffer);
- sendHandshake();
- mConnState = ST_READY;
-
- ClientData cd = mClient.getClientData();
- cd.setDebuggerConnectionStatus(DebuggerStatus.ATTACHED);
- mClient.update(Client.CHANGE_DEBUGGER_STATUS);
-
- // see if we have another packet in the buffer
- return getJdwpPacket();
- case JdwpPacket.HANDSHAKE_BAD:
- // not a debugger, throw an exception so we drop the line
- Log.d("ddms", "Bad handshake from debugger");
- throw new IOException("bad handshake");
- case JdwpPacket.HANDSHAKE_NOTYET:
- break;
- default:
- Log.e("ddms", "Unknown packet while waiting for client handshake");
- }
- return null;
- } else if (mConnState == ST_READY) {
- if (mReadBuffer.position() != 0) {
- Log.v("ddms", "Checking " + mReadBuffer.position() + " bytes");
- }
- return JdwpPacket.findPacket(mReadBuffer);
- } else {
- Log.e("ddms", "Receiving data in state = " + mConnState);
- }
-
- return null;
- }
-
- /**
- * Forward a packet to the client.
- *
- * "mClient" will never be null, though it's possible that the channel
- * in the client has closed and our send attempt will fail.
- *
- * Consumes the packet.
- */
- void forwardPacketToClient(JdwpPacket packet) throws IOException {
- mClient.sendAndConsume(packet);
- }
-
- /**
- * Send the handshake to the debugger. We also send along any packets
- * we already received from the client (usually just a VM_START event,
- * if anything at all).
- */
- private synchronized void sendHandshake() throws IOException {
- ByteBuffer tempBuffer = ByteBuffer.allocate(JdwpPacket.HANDSHAKE_LEN);
- JdwpPacket.putHandshake(tempBuffer);
- int expectedLength = tempBuffer.position();
- tempBuffer.flip();
- if (mChannel.write(tempBuffer) != expectedLength) {
- throw new IOException("partial handshake write");
- }
-
- expectedLength = mPreDataBuffer.position();
- if (expectedLength > 0) {
- Log.d("ddms", "Sending " + mPreDataBuffer.position()
- + " bytes of saved data");
- mPreDataBuffer.flip();
- if (mChannel.write(mPreDataBuffer) != expectedLength) {
- throw new IOException("partial pre-data write");
- }
- mPreDataBuffer.clear();
- }
- }
-
- /**
- * Send a packet to the debugger.
- *
- * Ideally, we can do this with a single channel write. If that doesn't
- * happen, we have to prevent anybody else from writing to the channel
- * until this packet completes, so we synchronize on the channel.
- *
- * Another goal is to avoid unnecessary buffer copies, so we write
- * directly out of the JdwpPacket's ByteBuffer.
- *
- * We must synchronize on "mChannel" before writing to it. We want to
- * coordinate the buffered data with mChannel creation, so this whole
- * method is synchronized.
- */
- synchronized void sendAndConsume(JdwpPacket packet)
- throws IOException {
-
- if (mChannel == null) {
- /*
- * Buffer this up so we can send it to the debugger when it
- * finally does connect. This is essential because the VM_START
- * message might be telling the debugger that the VM is
- * suspended. The alternative approach would be for us to
- * capture and interpret VM_START and send it later if we
- * didn't choose to un-suspend the VM for our own purposes.
- */
- Log.d("ddms", "Saving packet 0x"
- + Integer.toHexString(packet.getId()));
- packet.movePacket(mPreDataBuffer);
- } else {
- packet.writeAndConsume(mChannel);
- }
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/Device.java b/base/ddmlib/src/main/java/com/android/ddmlib/Device.java
deleted file mode 100644
index 1cb1a4c..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/Device.java
+++ /dev/null
@@ -1,1295 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.concurrency.GuardedBy;
-import com.android.ddmlib.log.LogReceiver;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.Atomics;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-
-/**
- * A Device. It can be a physical device or an emulator.
- */
-final class Device implements IDevice {
- /** Emulator Serial Number regexp. */
- static final String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
-
- /** Serial number of the device */
- private final String mSerialNumber;
-
- /** Name of the AVD */
- private String mAvdName = null;
-
- /** State of the device. */
- private DeviceState mState = null;
-
- /** Device properties. */
- private final PropertyFetcher mPropFetcher = new PropertyFetcher(this);
- private final Map<String, String> mMountPoints = new HashMap<String, String>();
-
- private final BatteryFetcher mBatteryFetcher = new BatteryFetcher(this);
-
- @GuardedBy("mClients")
- private final List<Client> mClients = new ArrayList<Client>();
-
- /** Maps pid's of clients in {@link #mClients} to their package name. */
- private final Map<Integer, String> mClientInfo = new ConcurrentHashMap<Integer, String>();
-
- private DeviceMonitor mMonitor;
-
- private static final String LOG_TAG = "Device";
- private static final char SEPARATOR = '-';
- private static final String UNKNOWN_PACKAGE = ""; //$NON-NLS-1$
-
- private static final long GET_PROP_TIMEOUT_MS = 100;
- private static final long INSTALL_TIMEOUT_MINUTES;
-
- static {
- String installTimeout = System.getenv("ADB_INSTALL_TIMEOUT");
- long time = 4;
- if (installTimeout != null) {
- try {
- time = Long.parseLong(installTimeout);
- } catch (NumberFormatException e) {
- // use default value
- }
- }
- INSTALL_TIMEOUT_MINUTES = time;
- }
-
- /**
- * Socket for the connection monitoring client connection/disconnection.
- */
- private SocketChannel mSocketChannel;
-
- private Integer mLastBatteryLevel = null;
- private long mLastBatteryCheckTime = 0;
-
- /** Path to the screen recorder binary on the device. */
- private static final String SCREEN_RECORDER_DEVICE_PATH = "/system/bin/screenrecord";
- private static final long LS_TIMEOUT_SEC = 2;
-
- /** Flag indicating whether the device has the screen recorder binary. */
- private Boolean mHasScreenRecorder;
-
- /** Cached list of hardware characteristics */
- private Set<String> mHardwareCharacteristics;
-
- private int mApiLevel;
- private String mName;
-
- /**
- * Output receiver for "pm install package.apk" command line.
- */
- private static final class InstallReceiver extends MultiLineReceiver {
-
- private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
- private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
-
- private String mErrorMessage = null;
-
- public InstallReceiver() {
- }
-
- @Override
- public void processNewLines(String[] lines) {
- for (String line : lines) {
- if (!line.isEmpty()) {
- if (line.startsWith(SUCCESS_OUTPUT)) {
- mErrorMessage = null;
- } else {
- Matcher m = FAILURE_PATTERN.matcher(line);
- if (m.matches()) {
- mErrorMessage = m.group(1);
- } else {
- mErrorMessage = "Unknown failure";
- }
- }
- }
- }
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- public String getErrorMessage() {
- return mErrorMessage;
- }
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#getSerialNumber()
- */
- @NonNull
- @Override
- public String getSerialNumber() {
- return mSerialNumber;
- }
-
- @Override
- public String getAvdName() {
- return mAvdName;
- }
-
- /**
- * Sets the name of the AVD
- */
- void setAvdName(String avdName) {
- if (!isEmulator()) {
- throw new IllegalArgumentException(
- "Cannot set the AVD name of the device is not an emulator");
- }
-
- mAvdName = avdName;
- }
-
- @Override
- public String getName() {
- if (mName != null) {
- return mName;
- }
-
- if (isOnline()) {
- // cache name only if device is online
- mName = constructName();
- return mName;
- } else {
- return constructName();
- }
- }
-
- private String constructName() {
- if (isEmulator()) {
- String avdName = getAvdName();
- if (avdName != null) {
- return String.format("%s [%s]", avdName, getSerialNumber());
- } else {
- return getSerialNumber();
- }
- } else {
- String manufacturer = null;
- String model = null;
-
- try {
- manufacturer = cleanupStringForDisplay(getProperty(PROP_DEVICE_MANUFACTURER));
- model = cleanupStringForDisplay(getProperty(PROP_DEVICE_MODEL));
- } catch (Exception e) {
- // If there are exceptions thrown while attempting to get these properties,
- // we can just use the serial number, so ignore these exceptions.
- }
-
- StringBuilder sb = new StringBuilder(20);
-
- if (manufacturer != null) {
- sb.append(manufacturer);
- sb.append(SEPARATOR);
- }
-
- if (model != null) {
- sb.append(model);
- sb.append(SEPARATOR);
- }
-
- sb.append(getSerialNumber());
- return sb.toString();
- }
- }
-
- private String cleanupStringForDisplay(String s) {
- if (s == null) {
- return null;
- }
-
- StringBuilder sb = new StringBuilder(s.length());
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
-
- if (Character.isLetterOrDigit(c)) {
- sb.append(Character.toLowerCase(c));
- } else {
- sb.append('_');
- }
- }
-
- return sb.toString();
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#getState()
- */
- @Override
- public DeviceState getState() {
- return mState;
- }
-
- /**
- * Changes the state of the device.
- */
- void setState(DeviceState state) {
- mState = state;
- }
-
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#getProperties()
- */
- @Override
- public Map<String, String> getProperties() {
- return Collections.unmodifiableMap(mPropFetcher.getProperties());
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#getPropertyCount()
- */
- @Override
- public int getPropertyCount() {
- return mPropFetcher.getProperties().size();
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#getProperty(java.lang.String)
- */
- @Override
- public String getProperty(String name) {
- Future<String> future = mPropFetcher.getProperty(name);
- try {
- return future.get(GET_PROP_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // ignore
- } catch (ExecutionException e) {
- // ignore
- } catch (java.util.concurrent.TimeoutException e) {
- // ignore
- }
- return null;
- }
-
- @Override
- public boolean arePropertiesSet() {
- return mPropFetcher.arePropertiesSet();
- }
-
- @Override
- public String getPropertyCacheOrSync(String name) throws TimeoutException,
- AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
- Future<String> future = mPropFetcher.getProperty(name);
- try {
- return future.get();
- } catch (InterruptedException e) {
- // ignore
- } catch (ExecutionException e) {
- // ignore
- }
- return null;
- }
-
- @Override
- public String getPropertySync(String name) throws TimeoutException,
- AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
- Future<String> future = mPropFetcher.getProperty(name);
- try {
- return future.get();
- } catch (InterruptedException e) {
- // ignore
- } catch (ExecutionException e) {
- // ignore
- }
- return null;
- }
-
- @NonNull
- @Override
- public Future<String> getSystemProperty(@NonNull String name) {
- return mPropFetcher.getProperty(name);
- }
-
- @Override
- public boolean supportsFeature(@NonNull Feature feature) {
- switch (feature) {
- case SCREEN_RECORD:
- if (getApiLevel() < 19) {
- return false;
- }
- if (mHasScreenRecorder == null) {
- mHasScreenRecorder = hasBinary(SCREEN_RECORDER_DEVICE_PATH);
- }
- return mHasScreenRecorder;
- case PROCSTATS:
- return getApiLevel() >= 19;
- default:
- return false;
- }
- }
-
- // The full list of features can be obtained from /etc/permissions/features*
- // However, the smaller set of features we are interested in can be obtained by
- // reading the build characteristics property.
- @Override
- public boolean supportsFeature(@NonNull HardwareFeature feature) {
- if (mHardwareCharacteristics == null) {
- try {
- String characteristics = getProperty(PROP_BUILD_CHARACTERISTICS);
- if (characteristics == null) {
- return false;
- }
-
- mHardwareCharacteristics = Sets.newHashSet(Splitter.on(',').split(characteristics));
- } catch (Exception e) {
- mHardwareCharacteristics = Collections.emptySet();
- }
- }
-
- return mHardwareCharacteristics.contains(feature.getCharacteristic());
- }
-
- private int getApiLevel() {
- if (mApiLevel > 0) {
- return mApiLevel;
- }
-
- try {
- String buildApi = getProperty(PROP_BUILD_API_LEVEL);
- mApiLevel = buildApi == null ? -1 : Integer.parseInt(buildApi);
- return mApiLevel;
- } catch (Exception e) {
- return -1;
- }
- }
-
- private boolean hasBinary(String path) {
- CountDownLatch latch = new CountDownLatch(1);
- CollectingOutputReceiver receiver = new CollectingOutputReceiver(latch);
- try {
- executeShellCommand("ls " + path, receiver);
- } catch (Exception e) {
- return false;
- }
-
- try {
- latch.await(LS_TIMEOUT_SEC, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- return false;
- }
-
- String value = receiver.getOutput().trim();
- return !value.endsWith("No such file or directory");
- }
-
- @Nullable
- @Override
- public String getMountPoint(@NonNull String name) {
- String mount = mMountPoints.get(name);
- if (mount == null) {
- try {
- mount = queryMountPoint(name);
- mMountPoints.put(name, mount);
- } catch (TimeoutException ignored) {
- } catch (AdbCommandRejectedException ignored) {
- } catch (ShellCommandUnresponsiveException ignored) {
- } catch (IOException ignored) {
- }
- }
- return mount;
- }
-
- @Nullable
- private String queryMountPoint(@NonNull final String name)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
-
- final AtomicReference<String> ref = Atomics.newReference();
- executeShellCommand("echo $" + name, new MultiLineReceiver() { //$NON-NLS-1$
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- @Override
- public void processNewLines(String[] lines) {
- for (String line : lines) {
- if (!line.isEmpty()) {
- // this should be the only one.
- ref.set(line);
- }
- }
- }
- });
- return ref.get();
- }
-
- @Override
- public String toString() {
- return mSerialNumber;
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#isOnline()
- */
- @Override
- public boolean isOnline() {
- return mState == DeviceState.ONLINE;
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#isEmulator()
- */
- @Override
- public boolean isEmulator() {
- return mSerialNumber.matches(RE_EMULATOR_SN);
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#isOffline()
- */
- @Override
- public boolean isOffline() {
- return mState == DeviceState.OFFLINE;
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#isBootLoader()
- */
- @Override
- public boolean isBootLoader() {
- return mState == DeviceState.BOOTLOADER;
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#getSyncService()
- */
- @Override
- public SyncService getSyncService()
- throws TimeoutException, AdbCommandRejectedException, IOException {
- SyncService syncService = new SyncService(AndroidDebugBridge.getSocketAddress(), this);
- if (syncService.openSync()) {
- return syncService;
- }
-
- return null;
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#getFileListingService()
- */
- @Override
- public FileListingService getFileListingService() {
- return new FileListingService(this);
- }
-
- @Override
- public RawImage getScreenshot()
- throws TimeoutException, AdbCommandRejectedException, IOException {
- return getScreenshot(0, TimeUnit.MILLISECONDS);
- }
-
- @Override
- public RawImage getScreenshot(long timeout, TimeUnit unit)
- throws TimeoutException, AdbCommandRejectedException, IOException {
- return AdbHelper.getFrameBuffer(AndroidDebugBridge.getSocketAddress(), this, timeout, unit);
- }
-
- @Override
- public void startScreenRecorder(String remoteFilePath, ScreenRecorderOptions options,
- IShellOutputReceiver receiver) throws TimeoutException, AdbCommandRejectedException,
- IOException, ShellCommandUnresponsiveException {
- executeShellCommand(getScreenRecorderCommand(remoteFilePath, options), receiver, 0, null);
- }
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- static String getScreenRecorderCommand(@NonNull String remoteFilePath,
- @NonNull ScreenRecorderOptions options) {
- StringBuilder sb = new StringBuilder();
-
- sb.append("screenrecord");
- sb.append(' ');
-
- if (options.width > 0 && options.height > 0) {
- sb.append("--size ");
- sb.append(options.width);
- sb.append('x');
- sb.append(options.height);
- sb.append(' ');
- }
-
- if (options.bitrateMbps > 0) {
- sb.append("--bit-rate ");
- sb.append(options.bitrateMbps * 1000000);
- sb.append(' ');
- }
-
- if (options.timeLimit > 0) {
- sb.append("--time-limit ");
- long seconds = TimeUnit.SECONDS.convert(options.timeLimit, options.timeLimitUnits);
- if (seconds > 180) {
- seconds = 180;
- }
- sb.append(seconds);
- sb.append(' ');
- }
-
- sb.append(remoteFilePath);
-
- return sb.toString();
- }
-
- @Override
- public void executeShellCommand(String command, IShellOutputReceiver receiver)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
- AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
- receiver, DdmPreferences.getTimeOut());
- }
-
- @Override
- public void executeShellCommand(String command, IShellOutputReceiver receiver,
- int maxTimeToOutputResponse)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
- AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
- receiver, maxTimeToOutputResponse);
- }
-
- @Override
- public void executeShellCommand(String command, IShellOutputReceiver receiver,
- long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
- AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
- receiver, maxTimeToOutputResponse, maxTimeUnits);
- }
-
- @Override
- public void runEventLogService(LogReceiver receiver)
- throws TimeoutException, AdbCommandRejectedException, IOException {
- AdbHelper.runEventLogService(AndroidDebugBridge.getSocketAddress(), this, receiver);
- }
-
- @Override
- public void runLogService(String logname, LogReceiver receiver)
- throws TimeoutException, AdbCommandRejectedException, IOException {
- AdbHelper.runLogService(AndroidDebugBridge.getSocketAddress(), this, logname, receiver);
- }
-
- @Override
- public void createForward(int localPort, int remotePort)
- throws TimeoutException, AdbCommandRejectedException, IOException {
- AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this,
- String.format("tcp:%d", localPort), //$NON-NLS-1$
- String.format("tcp:%d", remotePort)); //$NON-NLS-1$
- }
-
- @Override
- public void createForward(int localPort, String remoteSocketName,
- DeviceUnixSocketNamespace namespace) throws TimeoutException,
- AdbCommandRejectedException, IOException {
- AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this,
- String.format("tcp:%d", localPort), //$NON-NLS-1$
- String.format("%s:%s", namespace.getType(), remoteSocketName)); //$NON-NLS-1$
- }
-
- @Override
- public void removeForward(int localPort, int remotePort)
- throws TimeoutException, AdbCommandRejectedException, IOException {
- AdbHelper.removeForward(AndroidDebugBridge.getSocketAddress(), this,
- String.format("tcp:%d", localPort), //$NON-NLS-1$
- String.format("tcp:%d", remotePort)); //$NON-NLS-1$
- }
-
- @Override
- public void removeForward(int localPort, String remoteSocketName,
- DeviceUnixSocketNamespace namespace) throws TimeoutException,
- AdbCommandRejectedException, IOException {
- AdbHelper.removeForward(AndroidDebugBridge.getSocketAddress(), this,
- String.format("tcp:%d", localPort), //$NON-NLS-1$
- String.format("%s:%s", namespace.getType(), remoteSocketName)); //$NON-NLS-1$
- }
-
- Device(DeviceMonitor monitor, String serialNumber, DeviceState deviceState) {
- mMonitor = monitor;
- mSerialNumber = serialNumber;
- mState = deviceState;
- }
-
- DeviceMonitor getMonitor() {
- return mMonitor;
- }
-
- @Override
- public boolean hasClients() {
- synchronized (mClients) {
- return !mClients.isEmpty();
- }
- }
-
- @Override
- public Client[] getClients() {
- synchronized (mClients) {
- return mClients.toArray(new Client[mClients.size()]);
- }
- }
-
- @Override
- public Client getClient(String applicationName) {
- synchronized (mClients) {
- for (Client c : mClients) {
- if (applicationName.equals(c.getClientData().getClientDescription())) {
- return c;
- }
- }
- }
-
- return null;
- }
-
- void addClient(Client client) {
- synchronized (mClients) {
- mClients.add(client);
- }
-
- addClientInfo(client);
- }
-
- List<Client> getClientList() {
- return mClients;
- }
-
- void clearClientList() {
- synchronized (mClients) {
- mClients.clear();
- }
-
- clearClientInfo();
- }
-
- /**
- * Removes a {@link Client} from the list.
- * @param client the client to remove.
- * @param notify Whether or not to notify the listeners of a change.
- */
- void removeClient(Client client, boolean notify) {
- mMonitor.addPortToAvailableList(client.getDebuggerListenPort());
- synchronized (mClients) {
- mClients.remove(client);
- }
- if (notify) {
- mMonitor.getServer().deviceChanged(this, CHANGE_CLIENT_LIST);
- }
-
- removeClientInfo(client);
- }
-
- /** Sets the socket channel on which a track-jdwp command for this device has been sent. */
- void setClientMonitoringSocket(@NonNull SocketChannel socketChannel) {
- mSocketChannel = socketChannel;
- }
-
- /**
- * Returns the channel on which responses to the track-jdwp command will be available if it
- * has been set, null otherwise. The channel is set via {@link #setClientMonitoringSocket(SocketChannel)},
- * which is usually invoked when the device goes online.
- */
- @Nullable
- SocketChannel getClientMonitoringSocket() {
- return mSocketChannel;
- }
-
- void update(int changeMask) {
- mMonitor.getServer().deviceChanged(this, changeMask);
- }
-
- void update(Client client, int changeMask) {
- mMonitor.getServer().clientChanged(client, changeMask);
- updateClientInfo(client, changeMask);
- }
-
- void setMountingPoint(String name, String value) {
- mMountPoints.put(name, value);
- }
-
- private void addClientInfo(Client client) {
- ClientData cd = client.getClientData();
- setClientInfo(cd.getPid(), cd.getClientDescription());
- }
-
- private void updateClientInfo(Client client, int changeMask) {
- if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) {
- addClientInfo(client);
- }
- }
-
- private void removeClientInfo(Client client) {
- int pid = client.getClientData().getPid();
- mClientInfo.remove(pid);
- }
-
- private void clearClientInfo() {
- mClientInfo.clear();
- }
-
- private void setClientInfo(int pid, String pkgName) {
- if (pkgName == null) {
- pkgName = UNKNOWN_PACKAGE;
- }
-
- mClientInfo.put(pid, pkgName);
- }
-
- @Override
- public String getClientName(int pid) {
- String pkgName = mClientInfo.get(pid);
- return pkgName == null ? UNKNOWN_PACKAGE : pkgName;
- }
-
- @Override
- public void pushFile(String local, String remote)
- throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
- SyncService sync = null;
- try {
- String targetFileName = getFileName(local);
-
- Log.d(targetFileName, String.format("Uploading %1$s onto device '%2$s'",
- targetFileName, getSerialNumber()));
-
- sync = getSyncService();
- if (sync != null) {
- String message = String.format("Uploading file onto device '%1$s'",
- getSerialNumber());
- Log.d(LOG_TAG, message);
- sync.pushFile(local, remote, SyncService.getNullProgressMonitor());
- } else {
- throw new IOException("Unable to open sync connection!");
- }
- } catch (TimeoutException e) {
- Log.e(LOG_TAG, "Error during Sync: timeout.");
- throw e;
-
- } catch (SyncException e) {
- Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
- throw e;
-
- } catch (IOException e) {
- Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
- throw e;
-
- } finally {
- if (sync != null) {
- sync.close();
- }
- }
- }
-
- @Override
- public void pullFile(String remote, String local)
- throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
- SyncService sync = null;
- try {
- String targetFileName = getFileName(remote);
-
- Log.d(targetFileName, String.format("Downloading %1$s from device '%2$s'",
- targetFileName, getSerialNumber()));
-
- sync = getSyncService();
- if (sync != null) {
- String message = String.format("Downloading file from device '%1$s'",
- getSerialNumber());
- Log.d(LOG_TAG, message);
- sync.pullFile(remote, local, SyncService.getNullProgressMonitor());
- } else {
- throw new IOException("Unable to open sync connection!");
- }
- } catch (TimeoutException e) {
- Log.e(LOG_TAG, "Error during Sync: timeout.");
- throw e;
-
- } catch (SyncException e) {
- Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
- throw e;
-
- } catch (IOException e) {
- Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
- throw e;
-
- } finally {
- if (sync != null) {
- sync.close();
- }
- }
- }
-
- @Override
- public String installPackage(String packageFilePath, boolean reinstall,
- String... extraArgs)
- throws InstallException {
- try {
- String remoteFilePath = syncPackageToDevice(packageFilePath);
- String result = installRemotePackage(remoteFilePath, reinstall, extraArgs);
- removeRemotePackage(remoteFilePath);
- return result;
- } catch (IOException e) {
- throw new InstallException(e);
- } catch (AdbCommandRejectedException e) {
- throw new InstallException(e);
- } catch (TimeoutException e) {
- throw new InstallException(e);
- } catch (SyncException e) {
- throw new InstallException(e);
- }
- }
-
- @Override
- public void installPackages(List<String> apkFilePaths, int timeOutInMs, boolean reinstall,
- String... extraArgs) throws InstallException {
-
- assert(!apkFilePaths.isEmpty());
- if (getApiLevel() < 21) {
- Log.w("Internal error : installPackages invoked with device < 21 for %s",
- Joiner.on(",").join(apkFilePaths));
-
- if (apkFilePaths.size() == 1) {
- installPackage(apkFilePaths.get(0), reinstall, extraArgs);
- return;
- }
- Log.e("Internal error : installPackages invoked with device < 21 for multiple APK : %s",
- Joiner.on(",").join(apkFilePaths));
- throw new InstallException(
- "Internal error : installPackages invoked with device < 21 for multiple APK : "
- + Joiner.on(",").join(apkFilePaths));
- }
- String mainPackageFilePath = apkFilePaths.get(0);
- Log.d(mainPackageFilePath,
- String.format("Uploading main %1$s and %2$s split APKs onto device '%3$s'",
- mainPackageFilePath, Joiner.on(',').join(apkFilePaths),
- getSerialNumber()));
-
- try {
- // create a installation session.
-
- List<String> extraArgsList = extraArgs != null
- ? ImmutableList.copyOf(extraArgs)
- : ImmutableList.<String>of();
-
- String sessionId = createMultiInstallSession(apkFilePaths, extraArgsList, reinstall);
- if (sessionId == null) {
- Log.d(mainPackageFilePath, "Failed to establish session, quit installation");
- throw new InstallException("Failed to establish session");
- }
- Log.d(mainPackageFilePath, String.format("Established session id=%1$s", sessionId));
-
- // now upload each APK in turn.
- int index = 0;
- boolean allUploadSucceeded = true;
- while (allUploadSucceeded && index < apkFilePaths.size()) {
- allUploadSucceeded = uploadAPK(sessionId, apkFilePaths.get(index), index++);
- }
-
- // if all files were upload successfully, commit otherwise abandon the installation.
- String command = allUploadSucceeded
- ? "pm install-commit " + sessionId
- : "pm install-abandon " + sessionId;
- InstallReceiver receiver = new InstallReceiver();
- executeShellCommand(command, receiver, timeOutInMs, TimeUnit.MILLISECONDS);
- String errorMessage = receiver.getErrorMessage();
- if (errorMessage != null) {
- String message = String.format("Failed to finalize session : %1$s", errorMessage);
- Log.e(mainPackageFilePath, message);
- throw new InstallException(message);
- }
- // in case not all files were upload and we abandoned the install, make sure to
- // notifier callers.
- if (!allUploadSucceeded) {
- throw new InstallException("Unable to upload some APKs");
- }
- } catch (TimeoutException e) {
- Log.e(LOG_TAG, "Error during Sync: timeout.");
- throw new InstallException(e);
-
- } catch (IOException e) {
- Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
- throw new InstallException(e);
-
- } catch (AdbCommandRejectedException e) {
- throw new InstallException(e);
- } catch (ShellCommandUnresponsiveException e) {
- Log.e(LOG_TAG, String.format("Error during shell execution: %1$s", e.getMessage()));
- throw new InstallException(e);
- }
- }
-
- /**
- * Implementation of {@link com.android.ddmlib.MultiLineReceiver} that can receive a
- * Success message from ADB followed by a session ID.
- */
- private static class MultiInstallReceiver extends MultiLineReceiver {
-
- private static final Pattern successPattern = Pattern.compile("Success: .*\\[(\\d*)\\]");
-
- @Nullable String sessionId = null;
-
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- @Override
- public void processNewLines(String[] lines) {
- for (String line : lines) {
- Matcher matcher = successPattern.matcher(line);
- if (matcher.matches()) {
- sessionId = matcher.group(1);
- }
- }
-
- }
-
- @Nullable
- public String getSessionId() {
- return sessionId;
- }
- }
-
- @Nullable
- private String createMultiInstallSession(List<String> apkFileNames,
- @NonNull Collection<String> extraArgs, boolean reinstall)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
-
- List<File> apkFiles = Lists.transform(apkFileNames, new Function<String, File>() {
- @Override
- public File apply(String input) {
- return new File(input);
- }
- });
-
- long totalFileSize = 0L;
- for (File apkFile : apkFiles) {
- if (apkFile.exists() && apkFile.isFile()) {
- totalFileSize += apkFile.length();
- } else {
- throw new IllegalArgumentException(apkFile.getAbsolutePath() + " is not a file");
- }
- }
- StringBuilder parameters = new StringBuilder();
- if (reinstall) {
- parameters.append(("-r "));
- }
- parameters.append(Joiner.on(' ').join(extraArgs));
- MultiInstallReceiver receiver = new MultiInstallReceiver();
- String cmd = String.format("pm install-create %1$s -S %2$d",
- parameters.toString(),
- totalFileSize);
- executeShellCommand(cmd, receiver, DdmPreferences.getTimeOut());
- return receiver.getSessionId();
- }
-
- private static final CharMatcher UNSAFE_PM_INSTALL_SESSION_SPLIT_NAME_CHARS =
- CharMatcher.inRange('a','z').or(CharMatcher.inRange('A','Z'))
- .or(CharMatcher.anyOf("_-")).negate();
-
- private boolean uploadAPK(final String sessionId, String apkFilePath, int uniqueId) {
- Log.d(sessionId, String.format("Uploading APK %1$s ", apkFilePath));
- File fileToUpload = new File(apkFilePath);
- if (!fileToUpload.exists()) {
- Log.e(sessionId, String.format("File not found: %1$s", apkFilePath));
- return false;
- }
- if (fileToUpload.isDirectory()) {
- Log.e(sessionId, String.format("Directory upload not supported: %1$s", apkFilePath));
- return false;
- }
- String baseName = fileToUpload.getName().lastIndexOf('.') != -1
- ? fileToUpload.getName().substring(0, fileToUpload.getName().lastIndexOf('.'))
- : fileToUpload.getName();
-
- baseName = UNSAFE_PM_INSTALL_SESSION_SPLIT_NAME_CHARS.replaceFrom(baseName, '_');
-
- String command = String.format("pm install-write -S %d %s %d_%s -",
- fileToUpload.length(), sessionId, uniqueId, baseName);
-
- Log.d(sessionId, String.format("Executing : %1$s", command));
- InputStream inputStream = null;
- try {
- inputStream = new BufferedInputStream(new FileInputStream(fileToUpload));
- InstallReceiver receiver = new InstallReceiver();
- AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(),
- AdbHelper.AdbService.EXEC, command, this,
- receiver, DdmPreferences.getTimeOut(), TimeUnit.MILLISECONDS, inputStream);
- if (receiver.getErrorMessage() != null) {
- Log.e(sessionId, String.format("Error while uploading %1$s : %2$s", fileToUpload.getName(),
- receiver.getErrorMessage()));
- } else {
- Log.d(sessionId, String.format("Successfully uploaded %1$s", fileToUpload.getName()));
- }
- return receiver.getErrorMessage() == null;
- } catch (Exception e) {
- Log.e(sessionId, e);
- return false;
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(sessionId, e);
- }
- }
-
- }
- }
-
- @Override
- public String syncPackageToDevice(String localFilePath)
- throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
- SyncService sync = null;
- try {
- String packageFileName = getFileName(localFilePath);
- String remoteFilePath = String.format("/data/local/tmp/%1$s", packageFileName); //$NON-NLS-1$
-
- Log.d(packageFileName, String.format("Uploading %1$s onto device '%2$s'",
- packageFileName, getSerialNumber()));
-
- sync = getSyncService();
- if (sync != null) {
- String message = String.format("Uploading file onto device '%1$s'",
- getSerialNumber());
- Log.d(LOG_TAG, message);
- sync.pushFile(localFilePath, remoteFilePath, SyncService.getNullProgressMonitor());
- } else {
- throw new IOException("Unable to open sync connection!");
- }
- return remoteFilePath;
- } catch (TimeoutException e) {
- Log.e(LOG_TAG, "Error during Sync: timeout.");
- throw e;
-
- } catch (SyncException e) {
- Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
- throw e;
-
- } catch (IOException e) {
- Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
- throw e;
-
- } finally {
- if (sync != null) {
- sync.close();
- }
- }
- }
-
- /**
- * Helper method to retrieve the file name given a local file path
- * @param filePath full directory path to file
- * @return {@link String} file name
- */
- private static String getFileName(String filePath) {
- return new File(filePath).getName();
- }
-
- @Override
- public String installRemotePackage(String remoteFilePath, boolean reinstall,
- String... extraArgs) throws InstallException {
- try {
- InstallReceiver receiver = new InstallReceiver();
- StringBuilder optionString = new StringBuilder();
- if (reinstall) {
- optionString.append("-r ");
- }
- if (extraArgs != null) {
- optionString.append(Joiner.on(' ').join(extraArgs));
- }
- String cmd = String.format("pm install %1$s \"%2$s\"", optionString.toString(),
- remoteFilePath);
- executeShellCommand(cmd, receiver, INSTALL_TIMEOUT_MINUTES, TimeUnit.MINUTES);
- return receiver.getErrorMessage();
- } catch (TimeoutException e) {
- throw new InstallException(e);
- } catch (AdbCommandRejectedException e) {
- throw new InstallException(e);
- } catch (ShellCommandUnresponsiveException e) {
- throw new InstallException(e);
- } catch (IOException e) {
- throw new InstallException(e);
- }
- }
-
- @Override
- public void removeRemotePackage(String remoteFilePath) throws InstallException {
- try {
- executeShellCommand(String.format("rm \"%1$s\"", remoteFilePath),
- new NullOutputReceiver(), INSTALL_TIMEOUT_MINUTES, TimeUnit.MINUTES);
- } catch (IOException e) {
- throw new InstallException(e);
- } catch (TimeoutException e) {
- throw new InstallException(e);
- } catch (AdbCommandRejectedException e) {
- throw new InstallException(e);
- } catch (ShellCommandUnresponsiveException e) {
- throw new InstallException(e);
- }
- }
-
- @Override
- public String uninstallPackage(String packageName) throws InstallException {
- try {
- InstallReceiver receiver = new InstallReceiver();
- executeShellCommand("pm uninstall " + packageName, receiver, INSTALL_TIMEOUT_MINUTES,
- TimeUnit.MINUTES);
- return receiver.getErrorMessage();
- } catch (TimeoutException e) {
- throw new InstallException(e);
- } catch (AdbCommandRejectedException e) {
- throw new InstallException(e);
- } catch (ShellCommandUnresponsiveException e) {
- throw new InstallException(e);
- } catch (IOException e) {
- throw new InstallException(e);
- }
- }
-
- /*
- * (non-Javadoc)
- * @see com.android.ddmlib.IDevice#reboot()
- */
- @Override
- public void reboot(String into)
- throws TimeoutException, AdbCommandRejectedException, IOException {
- AdbHelper.reboot(into, AndroidDebugBridge.getSocketAddress(), this);
- }
-
- @Override
- public Integer getBatteryLevel() throws TimeoutException, AdbCommandRejectedException,
- IOException, ShellCommandUnresponsiveException {
- // use default of 5 minutes
- return getBatteryLevel(5 * 60 * 1000);
- }
-
- @Override
- public Integer getBatteryLevel(long freshnessMs) throws TimeoutException,
- AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException {
- Future<Integer> futureBattery = getBattery(freshnessMs, TimeUnit.MILLISECONDS);
- try {
- return futureBattery.get();
- } catch (InterruptedException e) {
- return null;
- } catch (ExecutionException e) {
- return null;
- }
- }
-
- @NonNull
- @Override
- public Future<Integer> getBattery() {
- return getBattery(5, TimeUnit.MINUTES);
- }
-
- @NonNull
- @Override
- public Future<Integer> getBattery(long freshnessTime, @NonNull TimeUnit timeUnit) {
- return mBatteryFetcher.getBattery(freshnessTime, timeUnit);
- }
-
- @NonNull
- @Override
- public List<String> getAbis() {
- /* Try abiList (implemented in L onwards) otherwise fall back to abi and abi2. */
- String abiList = getProperty(IDevice.PROP_DEVICE_CPU_ABI_LIST);
- if(abiList != null) {
- return Lists.newArrayList(abiList.split(","));
- } else {
- List<String> abis = Lists.newArrayListWithExpectedSize(2);
- String abi = getProperty(IDevice.PROP_DEVICE_CPU_ABI);
- if (abi != null) {
- abis.add(abi);
- }
-
- abi = getProperty(IDevice.PROP_DEVICE_CPU_ABI2);
- if (abi != null) {
- abis.add(abi);
- }
-
- return abis;
- }
- }
-
- @Override
- public int getDensity() {
- String densityValue = getProperty(IDevice.PROP_DEVICE_DENSITY);
- if (densityValue != null) {
- try {
- return Integer.parseInt(densityValue);
- } catch (NumberFormatException e) {
- return -1;
- }
- }
-
- return -1;
- }
-
- @Override
- public String getLanguage() {
- return getProperties().get(IDevice.PROP_DEVICE_LANGUAGE);
- }
-
- @Override
- public String getRegion() {
- return getProperty(IDevice.PROP_DEVICE_REGION);
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java b/base/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java
deleted file mode 100644
index 4aa0daa..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java
+++ /dev/null
@@ -1,884 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.ddmlib.AdbHelper.AdbResponse;
-import com.android.ddmlib.ClientData.DebuggerStatus;
-import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
-import com.android.ddmlib.IDevice.DeviceState;
-import com.android.ddmlib.utils.DebuggerPorts;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Queues;
-import com.google.common.util.concurrent.Uninterruptibles;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.channels.AsynchronousCloseException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The {@link DeviceMonitor} monitors devices attached to adb.
- *
- * On one thread, it runs the {@link com.android.ddmlib.DeviceMonitor.DeviceListMonitorTask}.
- * This establishes a socket connection to the adb host, and issues a
- * {@link #ADB_TRACK_DEVICES_COMMAND}. It then monitors that socket for all changes about device
- * connection and device state.
- *
- * For each device that is detected to be online, it then opens a new socket connection to adb,
- * and issues a "track-jdwp" command to that device. On this connection, it monitors active
- * clients on the device. Note: a single thread monitors jdwp connections from all devices.
- * The different socket connections to adb (one per device) are multiplexed over a single selector.
- */
-final class DeviceMonitor {
- private static final String ADB_TRACK_DEVICES_COMMAND = "host:track-devices";
- private static final String ADB_TRACK_JDWP_COMMAND = "track-jdwp";
-
- private final byte[] mLengthBuffer2 = new byte[4];
-
- private volatile boolean mQuit = false;
-
- private final AndroidDebugBridge mServer;
- private DeviceListMonitorTask mDeviceListMonitorTask;
-
- private Selector mSelector;
-
- private final List<Device> mDevices = Lists.newCopyOnWriteArrayList();
- private final DebuggerPorts mDebuggerPorts =
- new DebuggerPorts(DdmPreferences.getDebugPortBase());
- private final Map<Client, Integer> mClientsToReopen = new HashMap<Client, Integer>();
- private final BlockingQueue<Pair<SocketChannel,Device>> mChannelsToRegister =
- Queues.newLinkedBlockingQueue();
-
- /**
- * Creates a new {@link DeviceMonitor} object and links it to the running
- * {@link AndroidDebugBridge} object.
- * @param server the running {@link AndroidDebugBridge}.
- */
- DeviceMonitor(@NonNull AndroidDebugBridge server) {
- mServer = server;
- }
-
- /**
- * Starts the monitoring.
- */
- void start() {
- mDeviceListMonitorTask = new DeviceListMonitorTask(mServer, new DeviceListUpdateListener());
- new Thread(mDeviceListMonitorTask, "Device List Monitor").start(); //$NON-NLS-1$
- }
-
- /**
- * Stops the monitoring.
- */
- void stop() {
- mQuit = true;
-
- if (mDeviceListMonitorTask != null) {
- mDeviceListMonitorTask.stop();
- }
-
- // wake up the secondary loop by closing the selector.
- if (mSelector != null) {
- mSelector.wakeup();
- }
- }
-
- /**
- * Returns whether the monitor is currently connected to the debug bridge server.
- */
- boolean isMonitoring() {
- return mDeviceListMonitorTask != null && mDeviceListMonitorTask.isMonitoring();
- }
-
- int getConnectionAttemptCount() {
- return mDeviceListMonitorTask == null ? 0
- : mDeviceListMonitorTask.getConnectionAttemptCount();
- }
-
- int getRestartAttemptCount() {
- return mDeviceListMonitorTask == null ? 0 : mDeviceListMonitorTask.getRestartAttemptCount();
- }
-
- boolean hasInitialDeviceList() {
- return mDeviceListMonitorTask != null && mDeviceListMonitorTask.hasInitialDeviceList();
- }
-
- /**
- * Returns the devices.
- */
- @NonNull Device[] getDevices() {
- // Since this is a copy of write array list, we don't want to do a compound operation
- // (toArray with an appropriate size) without locking, so we just let the container provide
- // an appropriately sized array
- //noinspection ToArrayCallWithZeroLengthArrayArgument
- return mDevices.toArray(new Device[0]);
- }
-
- @NonNull
- AndroidDebugBridge getServer() {
- return mServer;
- }
-
- void addClientToDropAndReopen(Client client, int port) {
- synchronized (mClientsToReopen) {
- Log.d("DeviceMonitor",
- "Adding " + client + " to list of client to reopen (" + port + ").");
- if (mClientsToReopen.get(client) == null) {
- mClientsToReopen.put(client, port);
- }
- }
- mSelector.wakeup();
- }
-
- /**
- * Attempts to connect to the debug bridge server.
- * @return a connect socket if success, null otherwise
- */
- @Nullable
- private static SocketChannel openAdbConnection() {
- try {
- SocketChannel adbChannel = SocketChannel.open(AndroidDebugBridge.getSocketAddress());
- adbChannel.socket().setTcpNoDelay(true);
- return adbChannel;
- } catch (IOException e) {
- return null;
- }
- }
-
- /**
- * Updates the device list with the new items received from the monitoring service.
- */
- private void updateDevices(@NonNull List<Device> newList) {
- DeviceListComparisonResult result = DeviceListComparisonResult.compare(mDevices, newList);
- for (IDevice device : result.removed) {
- removeDevice((Device) device);
- mServer.deviceDisconnected(device);
- }
-
- List<Device> newlyOnline = Lists.newArrayListWithExpectedSize(mDevices.size());
-
- for (Map.Entry<IDevice, DeviceState> entry : result.updated.entrySet()) {
- Device device = (Device) entry.getKey();
- device.setState(entry.getValue());
- device.update(Device.CHANGE_STATE);
-
- if (device.isOnline()) {
- newlyOnline.add(device);
- }
- }
-
- for (IDevice device : result.added) {
- mDevices.add((Device) device);
- mServer.deviceConnected(device);
- if (device.isOnline()) {
- newlyOnline.add((Device) device);
- }
- }
-
- if (AndroidDebugBridge.getClientSupport()) {
- for (Device device : newlyOnline) {
- if (!startMonitoringDevice(device)) {
- Log.e("DeviceMonitor", "Failed to start monitoring "
- + device.getSerialNumber());
- }
- }
- }
-
- for (Device device : newlyOnline) {
- queryAvdName(device);
- }
- }
-
- private void removeDevice(@NonNull Device device) {
- device.clearClientList();
- mDevices.remove(device);
-
- SocketChannel channel = device.getClientMonitoringSocket();
- if (channel != null) {
- try {
- channel.close();
- } catch (IOException e) {
- // doesn't really matter if the close fails.
- }
- }
- }
-
- private static void queryAvdName(@NonNull Device device) {
- if (!device.isEmulator()) {
- return;
- }
-
- EmulatorConsole console = EmulatorConsole.getConsole(device);
- if (console != null) {
- device.setAvdName(console.getAvdName());
- console.close();
- }
- }
-
- /**
- * Starts a monitoring service for a device.
- * @param device the device to monitor.
- * @return true if success.
- */
- private boolean startMonitoringDevice(@NonNull Device device) {
- SocketChannel socketChannel = openAdbConnection();
-
- if (socketChannel != null) {
- try {
- boolean result = sendDeviceMonitoringRequest(socketChannel, device);
- if (result) {
-
- if (mSelector == null) {
- startDeviceMonitorThread();
- }
-
- device.setClientMonitoringSocket(socketChannel);
-
- socketChannel.configureBlocking(false);
-
- try {
- mChannelsToRegister.put(Pair.of(socketChannel, device));
- } catch (InterruptedException e) {
- // the queue is unbounded, and isn't going to block
- }
- mSelector.wakeup();
-
- return true;
- }
- } catch (TimeoutException e) {
- try {
- // attempt to close the socket if needed.
- socketChannel.close();
- } catch (IOException e1) {
- // we can ignore that one. It may already have been closed.
- }
- Log.d("DeviceMonitor",
- "Connection Failure when starting to monitor device '"
- + device + "' : timeout");
- } catch (AdbCommandRejectedException e) {
- try {
- // attempt to close the socket if needed.
- socketChannel.close();
- } catch (IOException e1) {
- // we can ignore that one. It may already have been closed.
- }
- Log.d("DeviceMonitor",
- "Adb refused to start monitoring device '"
- + device + "' : " + e.getMessage());
- } catch (IOException e) {
- try {
- // attempt to close the socket if needed.
- socketChannel.close();
- } catch (IOException e1) {
- // we can ignore that one. It may already have been closed.
- }
- Log.d("DeviceMonitor",
- "Connection Failure when starting to monitor device '"
- + device + "' : " + e.getMessage());
- }
- }
-
- return false;
- }
-
- private void startDeviceMonitorThread() throws IOException {
- mSelector = Selector.open();
- new Thread("Device Client Monitor") { //$NON-NLS-1$
- @Override
- public void run() {
- deviceClientMonitorLoop();
- }
- }.start();
- }
-
- private void deviceClientMonitorLoop() {
- do {
- try {
- int count = mSelector.select();
-
- if (mQuit) {
- return;
- }
-
- synchronized (mClientsToReopen) {
- if (!mClientsToReopen.isEmpty()) {
- Set<Client> clients = mClientsToReopen.keySet();
- MonitorThread monitorThread = MonitorThread.getInstance();
-
- for (Client client : clients) {
- Device device = client.getDeviceImpl();
- int pid = client.getClientData().getPid();
-
- monitorThread.dropClient(client, false /* notify */);
-
- // This is kinda bad, but if we don't wait a bit, the client
- // will never answer the second handshake!
- Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
-
- int port = mClientsToReopen.get(client);
-
- if (port == IDebugPortProvider.NO_STATIC_PORT) {
- port = getNextDebuggerPort();
- }
- Log.d("DeviceMonitor", "Reopening " + client);
- openClient(device, pid, port, monitorThread);
- device.update(Device.CHANGE_CLIENT_LIST);
- }
-
- mClientsToReopen.clear();
- }
- }
-
- // register any new channels
- while (!mChannelsToRegister.isEmpty()) {
- try {
- Pair<SocketChannel, Device> data = mChannelsToRegister.take();
- data.getFirst().register(mSelector, SelectionKey.OP_READ, data.getSecond());
- } catch (InterruptedException e) {
- // doesn't block: this thread is the only reader and it reads only when
- // there is data
- }
- }
-
- if (count == 0) {
- continue;
- }
-
- Set<SelectionKey> keys = mSelector.selectedKeys();
- Iterator<SelectionKey> iter = keys.iterator();
-
- while (iter.hasNext()) {
- SelectionKey key = iter.next();
- iter.remove();
-
- if (key.isValid() && key.isReadable()) {
- Object attachment = key.attachment();
-
- if (attachment instanceof Device) {
- Device device = (Device)attachment;
-
- SocketChannel socket = device.getClientMonitoringSocket();
-
- if (socket != null) {
- try {
- int length = readLength(socket, mLengthBuffer2);
-
- processIncomingJdwpData(device, socket, length);
- } catch (IOException ioe) {
- Log.d("DeviceMonitor",
- "Error reading jdwp list: " + ioe.getMessage());
- socket.close();
-
- // restart the monitoring of that device
- if (mDevices.contains(device)) {
- Log.d("DeviceMonitor",
- "Restarting monitoring service for " + device);
- startMonitoringDevice(device);
- }
- }
- }
- }
- }
- }
- } catch (IOException e) {
- Log.e("DeviceMonitor", "Connection error while monitoring clients.");
- }
-
- } while (!mQuit);
- }
-
- private static boolean sendDeviceMonitoringRequest(@NonNull SocketChannel socket,
- @NonNull Device device)
- throws TimeoutException, AdbCommandRejectedException, IOException {
-
- try {
- AdbHelper.setDevice(socket, device);
- AdbHelper.write(socket, AdbHelper.formAdbRequest(ADB_TRACK_JDWP_COMMAND));
- AdbResponse resp = AdbHelper.readAdbResponse(socket, false);
-
- if (!resp.okay) {
- // request was refused by adb!
- Log.e("DeviceMonitor", "adb refused request: " + resp.message);
- }
-
- return resp.okay;
- } catch (TimeoutException e) {
- Log.e("DeviceMonitor", "Sending jdwp tracking request timed out!");
- throw e;
- } catch (IOException e) {
- Log.e("DeviceMonitor", "Sending jdwp tracking request failed!");
- throw e;
- }
- }
-
- private void processIncomingJdwpData(@NonNull Device device,
- @NonNull SocketChannel monitorSocket, int length) throws IOException {
-
- // This methods reads @length bytes from the @monitorSocket channel.
- // These bytes correspond to the pids of the current set of processes on the device.
- // It takes this set of pids and compares them with the existing set of clients
- // for the device. Clients that correspond to pids that are not alive anymore are
- // dropped, and new clients are created for pids that don't have a corresponding Client.
-
- if (length >= 0) {
- // array for the current pids.
- Set<Integer> newPids = new HashSet<Integer>();
-
- // get the string data if there are any
- if (length > 0) {
- byte[] buffer = new byte[length];
- String result = read(monitorSocket, buffer);
-
- // split each line in its own list and create an array of integer pid
- String[] pids = result == null ? new String[0] : result.split("\n"); //$NON-NLS-1$
-
- for (String pid : pids) {
- try {
- newPids.add(Integer.valueOf(pid));
- } catch (NumberFormatException nfe) {
- // looks like this pid is not really a number. Lets ignore it.
- continue;
- }
- }
- }
-
- MonitorThread monitorThread = MonitorThread.getInstance();
-
- List<Client> clients = device.getClientList();
- Map<Integer, Client> existingClients = new HashMap<Integer, Client>();
-
- synchronized (clients) {
- for (Client c : clients) {
- existingClients.put(c.getClientData().getPid(), c);
- }
- }
-
- Set<Client> clientsToRemove = new HashSet<Client>();
- for (Integer pid : existingClients.keySet()) {
- if (!newPids.contains(pid)) {
- clientsToRemove.add(existingClients.get(pid));
- }
- }
-
- Set<Integer> pidsToAdd = new HashSet<Integer>(newPids);
- pidsToAdd.removeAll(existingClients.keySet());
-
- monitorThread.dropClients(clientsToRemove, false);
-
- // at this point whatever pid is left in the list needs to be converted into Clients.
- for (int newPid : pidsToAdd) {
- openClient(device, newPid, getNextDebuggerPort(), monitorThread);
- }
-
- if (!pidsToAdd.isEmpty() || !clientsToRemove.isEmpty()) {
- mServer.deviceChanged(device, Device.CHANGE_CLIENT_LIST);
- }
- }
- }
-
- /** Opens and creates a new client. */
- private static void openClient(@NonNull Device device, int pid, int port,
- @NonNull MonitorThread monitorThread) {
-
- SocketChannel clientSocket;
- try {
- clientSocket = AdbHelper.createPassThroughConnection(
- AndroidDebugBridge.getSocketAddress(), device, pid);
-
- // required for Selector
- clientSocket.configureBlocking(false);
- } catch (UnknownHostException uhe) {
- Log.d("DeviceMonitor", "Unknown Jdwp pid: " + pid);
- return;
- } catch (TimeoutException e) {
- Log.w("DeviceMonitor",
- "Failed to connect to client '" + pid + "': timeout");
- return;
- } catch (AdbCommandRejectedException e) {
- Log.w("DeviceMonitor",
- "Adb rejected connection to client '" + pid + "': " + e.getMessage());
- return;
-
- } catch (IOException ioe) {
- Log.w("DeviceMonitor",
- "Failed to connect to client '" + pid + "': " + ioe.getMessage());
- return ;
- }
-
- createClient(device, pid, clientSocket, port, monitorThread);
- }
-
- /** Creates a client and register it to the monitor thread */
- private static void createClient(@NonNull Device device, int pid, @NonNull SocketChannel socket,
- int debuggerPort, @NonNull MonitorThread monitorThread) {
-
- /*
- * Successfully connected to something. Create a Client object, add
- * it to the list, and initiate the JDWP handshake.
- */
-
- Client client = new Client(device, socket, pid);
-
- if (client.sendHandshake()) {
- try {
- if (AndroidDebugBridge.getClientSupport()) {
- client.listenForDebugger(debuggerPort);
- }
- } catch (IOException ioe) {
- client.getClientData().setDebuggerConnectionStatus(DebuggerStatus.ERROR);
- Log.e("ddms", "Can't bind to local " + debuggerPort + " for debugger");
- // oh well
- }
-
- client.requestAllocationStatus();
- } else {
- Log.e("ddms", "Handshake with " + client + " failed!");
- /*
- * The handshake send failed. We could remove it now, but if the
- * failure is "permanent" we'll just keep banging on it and
- * getting the same result. Keep it in the list with its "error"
- * state so we don't try to reopen it.
- */
- }
-
- if (client.isValid()) {
- device.addClient(client);
- monitorThread.addClient(client);
- }
- }
-
- private int getNextDebuggerPort() {
- return mDebuggerPorts.next();
- }
-
- void addPortToAvailableList(int port) {
- mDebuggerPorts.free(port);
- }
-
- /**
- * Reads the length of the next message from a socket.
- * @param socket The {@link SocketChannel} to read from.
- * @return the length, or 0 (zero) if no data is available from the socket.
- * @throws IOException if the connection failed.
- */
- private static int readLength(@NonNull SocketChannel socket, @NonNull byte[] buffer)
- throws IOException {
- String msg = read(socket, buffer);
-
- if (msg != null) {
- try {
- return Integer.parseInt(msg, 16);
- } catch (NumberFormatException nfe) {
- // we'll throw an exception below.
- }
- }
-
- // we receive something we can't read. It's better to reset the connection at this point.
- throw new IOException("Unable to read length");
- }
-
- /**
- * Fills a buffer by reading data from a socket.
- * @return the content of the buffer as a string, or null if it failed to convert the buffer.
- * @throws IOException if there was not enough data to fill the buffer
- */
- @Nullable
- private static String read(@NonNull SocketChannel socket, @NonNull byte[] buffer)
- throws IOException {
- ByteBuffer buf = ByteBuffer.wrap(buffer, 0, buffer.length);
-
- while (buf.position() != buf.limit()) {
- int count;
-
- count = socket.read(buf);
- if (count < 0) {
- throw new IOException("EOF");
- }
- }
-
- try {
- return new String(buffer, 0, buf.position(), AdbHelper.DEFAULT_ENCODING);
- } catch (UnsupportedEncodingException e) {
- return null;
- }
- }
-
- private class DeviceListUpdateListener implements DeviceListMonitorTask.UpdateListener {
- @Override
- public void connectionError(@NonNull Exception e) {
- for (Device device : mDevices) {
- removeDevice(device);
- mServer.deviceDisconnected(device);
- }
- }
-
- @Override
- public void deviceListUpdate(@NonNull Map<String, DeviceState> devices) {
- List<Device> l = Lists.newArrayListWithExpectedSize(devices.size());
- for (Map.Entry<String, DeviceState> entry : devices.entrySet()) {
- l.add(new Device(DeviceMonitor.this, entry.getKey(), entry.getValue()));
- }
- // now merge the new devices with the old ones.
- updateDevices(l);
- }
- }
-
- @VisibleForTesting
- static class DeviceListComparisonResult {
- @NonNull public final Map<IDevice,DeviceState> updated;
- @NonNull public final List<IDevice> added;
- @NonNull public final List<IDevice> removed;
-
- private DeviceListComparisonResult(@NonNull Map<IDevice,DeviceState> updated,
- @NonNull List<IDevice> added,
- @NonNull List<IDevice> removed) {
- this.updated = updated;
- this.added = added;
- this.removed = removed;
- }
-
- @NonNull
- public static DeviceListComparisonResult compare(@NonNull List<? extends IDevice> previous,
- @NonNull List<? extends IDevice> current) {
- current = Lists.newArrayList(current);
-
- final Map<IDevice,DeviceState> updated = Maps.newHashMapWithExpectedSize(current.size());
- final List<IDevice> added = Lists.newArrayListWithExpectedSize(1);
- final List<IDevice> removed = Lists.newArrayListWithExpectedSize(1);
-
- for (IDevice device : previous) {
- IDevice currentDevice = find(current, device);
- if (currentDevice != null) {
- if (currentDevice.getState() != device.getState()) {
- updated.put(device, currentDevice.getState());
- }
- current.remove(currentDevice);
- } else {
- removed.add(device);
- }
- }
-
- added.addAll(current);
-
- return new DeviceListComparisonResult(updated, added, removed);
- }
-
- @Nullable
- private static IDevice find(@NonNull List<? extends IDevice> devices,
- @NonNull IDevice device) {
- for (IDevice d : devices) {
- if (d.getSerialNumber().equals(device.getSerialNumber())) {
- return d;
- }
- }
-
- return null;
- }
- }
-
- @VisibleForTesting
- static class DeviceListMonitorTask implements Runnable {
- private final byte[] mLengthBuffer = new byte[4];
-
- private final AndroidDebugBridge mBridge;
- private final UpdateListener mListener;
-
- private SocketChannel mAdbConnection = null;
- private boolean mMonitoring = false;
- private int mConnectionAttempt = 0;
- private int mRestartAttemptCount = 0;
- private boolean mInitialDeviceListDone = false;
-
- private volatile boolean mQuit;
-
- private interface UpdateListener {
- void connectionError(@NonNull Exception e);
- void deviceListUpdate(@NonNull Map<String,DeviceState> devices);
- }
-
- public DeviceListMonitorTask(@NonNull AndroidDebugBridge bridge,
- @NonNull UpdateListener listener) {
- mBridge = bridge;
- mListener = listener;
- }
-
- @Override
- public void run() {
- do {
- if (mAdbConnection == null) {
- Log.d("DeviceMonitor", "Opening adb connection");
- mAdbConnection = openAdbConnection();
- if (mAdbConnection == null) {
- mConnectionAttempt++;
- Log.e("DeviceMonitor", "Connection attempts: " + mConnectionAttempt);
- if (mConnectionAttempt > 10) {
- if (!mBridge.startAdb()) {
- mRestartAttemptCount++;
- Log.e("DeviceMonitor",
- "adb restart attempts: " + mRestartAttemptCount);
- } else {
- Log.i("DeviceMonitor", "adb restarted");
- mRestartAttemptCount = 0;
- }
- }
- Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
- } else {
- Log.d("DeviceMonitor", "Connected to adb for device monitoring");
- mConnectionAttempt = 0;
- }
- }
-
- try {
- if (mAdbConnection != null && !mMonitoring) {
- mMonitoring = sendDeviceListMonitoringRequest();
- }
-
- if (mMonitoring) {
- int length = readLength(mAdbConnection, mLengthBuffer);
-
- if (length >= 0) {
- // read the incoming message
- processIncomingDeviceData(length);
-
- // flag the fact that we have build the list at least once.
- mInitialDeviceListDone = true;
- }
- }
- } catch (AsynchronousCloseException ace) {
- // this happens because of a call to Quit. We do nothing, and the loop will break.
- } catch (TimeoutException ioe) {
- handleExceptionInMonitorLoop(ioe);
- } catch (IOException ioe) {
- handleExceptionInMonitorLoop(ioe);
- }
- } while (!mQuit);
- }
-
- private boolean sendDeviceListMonitoringRequest() throws TimeoutException, IOException {
- byte[] request = AdbHelper.formAdbRequest(ADB_TRACK_DEVICES_COMMAND);
-
- try {
- AdbHelper.write(mAdbConnection, request);
- AdbResponse resp = AdbHelper.readAdbResponse(mAdbConnection, false);
- if (!resp.okay) {
- // request was refused by adb!
- Log.e("DeviceMonitor", "adb refused request: " + resp.message);
- }
-
- return resp.okay;
- } catch (IOException e) {
- Log.e("DeviceMonitor", "Sending Tracking request failed!");
- mAdbConnection.close();
- throw e;
- }
- }
-
- private void handleExceptionInMonitorLoop(@NonNull Exception e) {
- if (!mQuit) {
- if (e instanceof TimeoutException) {
- Log.e("DeviceMonitor", "Adb connection Error: timeout");
- } else {
- Log.e("DeviceMonitor", "Adb connection Error:" + e.getMessage());
- }
- mMonitoring = false;
- if (mAdbConnection != null) {
- try {
- mAdbConnection.close();
- } catch (IOException ioe) {
- // we can safely ignore that one.
- }
- mAdbConnection = null;
-
- mListener.connectionError(e);
- }
- }
- }
-
- /** Processes an incoming device message from the socket */
- private void processIncomingDeviceData(int length) throws IOException {
- Map<String, DeviceState> result;
- if (length <= 0) {
- result = Collections.emptyMap();
- } else {
- String response = read(mAdbConnection, new byte[length]);
- result = parseDeviceListResponse(response);
- }
-
- mListener.deviceListUpdate(result);
- }
-
- @VisibleForTesting
- static Map<String, DeviceState> parseDeviceListResponse(@Nullable String result) {
- Map<String, DeviceState> deviceStateMap = Maps.newHashMap();
- String[] devices = result == null ? new String[0] : result.split("\n"); //$NON-NLS-1$
-
- for (String d : devices) {
- String[] param = d.split("\t"); //$NON-NLS-1$
- if (param.length == 2) {
- // new adb uses only serial numbers to identify devices
- deviceStateMap.put(param[0], DeviceState.getState(param[1]));
- }
- }
- return deviceStateMap;
- }
-
- boolean isMonitoring() {
- return mMonitoring;
- }
-
- boolean hasInitialDeviceList() {
- return mInitialDeviceListDone;
- }
-
- int getConnectionAttemptCount() {
- return mConnectionAttempt;
- }
-
- int getRestartAttemptCount() {
- return mRestartAttemptCount;
- }
-
- public void stop() {
- mQuit = true;
-
- // wakeup the main loop thread by closing the main connection to adb.
- if (mAdbConnection != null) {
- try {
- mAdbConnection.close();
- } catch (IOException ignored) {
- }
- }
- }
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java b/base/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java
deleted file mode 100644
index d7d4650..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java
+++ /dev/null
@@ -1,783 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.concurrency.GuardedBy;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.security.InvalidParameterException;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Provides control over emulated hardware of the Android emulator.
- * <p/>This is basically a wrapper around the command line console normally used with telnet.
- *<p/>
- * Regarding line termination handling:<br>
- * One of the issues is that the telnet protocol <b>requires</b> usage of <code>\r\n</code>. Most
- * implementations don't enforce it (the dos one does). In this particular case, this is mostly
- * irrelevant since we don't use telnet in Java, but that means we want to make
- * sure we use the same line termination than what the console expects. The console
- * code removes <code>\r</code> and waits for <code>\n</code>.
- * <p/>However this means you <i>may</i> receive <code>\r\n</code> when reading from the console.
- * <p/>
- * <b>This API will change in the near future.</b>
- */
-public final class EmulatorConsole {
-
- private static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
-
- private static final int WAIT_TIME = 5; // spin-wait sleep, in ms
-
- private static final int STD_TIMEOUT = 5000; // standard delay, in ms
-
- private static final String HOST = "127.0.0.1"; //$NON-NLS-1$
-
- private static final String COMMAND_PING = "help\r\n"; //$NON-NLS-1$
- private static final String COMMAND_AVD_NAME = "avd name\r\n"; //$NON-NLS-1$
- private static final String COMMAND_KILL = "kill\r\n"; //$NON-NLS-1$
- private static final String COMMAND_GSM_STATUS = "gsm status\r\n"; //$NON-NLS-1$
- private static final String COMMAND_GSM_CALL = "gsm call %1$s\r\n"; //$NON-NLS-1$
- private static final String COMMAND_GSM_CANCEL_CALL = "gsm cancel %1$s\r\n"; //$NON-NLS-1$
- private static final String COMMAND_GSM_DATA = "gsm data %1$s\r\n"; //$NON-NLS-1$
- private static final String COMMAND_GSM_VOICE = "gsm voice %1$s\r\n"; //$NON-NLS-1$
- private static final String COMMAND_SMS_SEND = "sms send %1$s %2$s\r\n"; //$NON-NLS-1$
- private static final String COMMAND_NETWORK_STATUS = "network status\r\n"; //$NON-NLS-1$
- private static final String COMMAND_NETWORK_SPEED = "network speed %1$s\r\n"; //$NON-NLS-1$
- private static final String COMMAND_NETWORK_LATENCY = "network delay %1$s\r\n"; //$NON-NLS-1$
- private static final String COMMAND_GPS = "geo fix %1$f %2$f %3$f\r\n"; //$NON-NLS-1$
-
- private static final Pattern RE_KO = Pattern.compile("KO:\\s+(.*)"); //$NON-NLS-1$
-
- /**
- * Array of delay values: no delay, gprs, edge/egprs, umts/3d
- */
- public static final int[] MIN_LATENCIES = new int[] {
- 0, // No delay
- 150, // gprs
- 80, // edge/egprs
- 35 // umts/3g
- };
-
- /**
- * Array of download speeds: full speed, gsm, hscsd, gprs, edge/egprs, umts/3g, hsdpa.
- */
- public static final int[] DOWNLOAD_SPEEDS = new int[] {
- 0, // full speed
- 14400, // gsm
- 43200, // hscsd
- 80000, // gprs
- 236800, // edge/egprs
- 1920000, // umts/3g
- 14400000 // hsdpa
- };
-
- /** Arrays of valid network speeds */
- public static final String[] NETWORK_SPEEDS = new String[] {
- "full", //$NON-NLS-1$
- "gsm", //$NON-NLS-1$
- "hscsd", //$NON-NLS-1$
- "gprs", //$NON-NLS-1$
- "edge", //$NON-NLS-1$
- "umts", //$NON-NLS-1$
- "hsdpa", //$NON-NLS-1$
- };
-
- /** Arrays of valid network latencies */
- public static final String[] NETWORK_LATENCIES = new String[] {
- "none", //$NON-NLS-1$
- "gprs", //$NON-NLS-1$
- "edge", //$NON-NLS-1$
- "umts", //$NON-NLS-1$
- };
-
- /** Gsm Mode enum. */
- public enum GsmMode {
- UNKNOWN((String)null),
- UNREGISTERED(new String[] { "unregistered", "off" }),
- HOME(new String[] { "home", "on" }),
- ROAMING("roaming"),
- SEARCHING("searching"),
- DENIED("denied");
-
- private final String[] tags;
-
- GsmMode(String tag) {
- if (tag != null) {
- this.tags = new String[] { tag };
- } else {
- this.tags = new String[0];
- }
- }
-
- GsmMode(String[] tags) {
- this.tags = tags;
- }
-
- public static GsmMode getEnum(String tag) {
- for (GsmMode mode : values()) {
- for (String t : mode.tags) {
- if (t.equals(tag)) {
- return mode;
- }
- }
- }
- return UNKNOWN;
- }
-
- /**
- * Returns the first tag of the enum.
- */
- public String getTag() {
- if (tags.length > 0) {
- return tags[0];
- }
- return null;
- }
- }
-
- public static final String RESULT_OK = null;
-
- private static final Pattern sEmulatorRegexp = Pattern.compile(Device.RE_EMULATOR_SN);
- private static final Pattern sVoiceStatusRegexp = Pattern.compile(
- "gsm\\s+voice\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
- private static final Pattern sDataStatusRegexp = Pattern.compile(
- "gsm\\s+data\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
- private static final Pattern sDownloadSpeedRegexp = Pattern.compile(
- "\\s+download\\s+speed:\\s+(\\d+)\\s+bits.*", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
- private static final Pattern sMinLatencyRegexp = Pattern.compile(
- "\\s+minimum\\s+latency:\\s+(\\d+)\\s+ms", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
-
- @GuardedBy(value = "sEmulators")
- private static final HashMap<Integer, EmulatorConsole> sEmulators =
- new HashMap<Integer, EmulatorConsole>();
-
- private static final String LOG_TAG = "EmulatorConsole";
-
- /** Gsm Status class */
- public static class GsmStatus {
- /** Voice status. */
- public GsmMode voice = GsmMode.UNKNOWN;
- /** Data status. */
- public GsmMode data = GsmMode.UNKNOWN;
- }
-
- /** Network Status class */
- public static class NetworkStatus {
- /** network speed status. This is an index in the {@link #DOWNLOAD_SPEEDS} array. */
- public int speed = -1;
- /** network latency status. This is an index in the {@link #MIN_LATENCIES} array. */
- public int latency = -1;
- }
-
- private int mPort = -1;
-
- private SocketChannel mSocketChannel;
-
- private byte[] mBuffer = new byte[1024];
-
- /**
- * Returns an {@link EmulatorConsole} object for the given {@link Device}. This can
- * be an already existing console, or a new one if it hadn't been created yet.
- * Note: emulator consoles don't automatically close when an emulator exists. It is the
- * responsibility of higher level code to explicitly call {@link #close()} when the emulator
- * corresponding to a open console is killed.
- * @param d The device that the console links to.
- * @return an <code>EmulatorConsole</code> object or <code>null</code> if the connection failed.
- */
- @Nullable
- public static EmulatorConsole getConsole(IDevice d) {
- // we need to make sure that the device is an emulator
- // get the port number. This is the console port.
- Integer port = getEmulatorPort(d.getSerialNumber());
- if (port == null) {
- Log.w(LOG_TAG, "Failed to find emulator port from serial: " + d.getSerialNumber());
- return null;
- }
-
- EmulatorConsole console = retrieveConsole(port);
-
- if (!console.checkConnection()) {
- removeConsole(console.mPort);
- console = null;
- }
-
- return console;
- }
-
- /**
- * Return port of emulator given its serial number.
- *
- * @param serialNumber the emulator's serial number
- * @return the integer port or <code>null</code> if it could not be determined
- */
- public static Integer getEmulatorPort(String serialNumber) {
- Matcher m = sEmulatorRegexp.matcher(serialNumber);
- if (m.matches()) {
- // get the port number. This is the console port.
- int port;
- try {
- port = Integer.parseInt(m.group(1));
- if (port > 0) {
- return port;
- }
- } catch (NumberFormatException e) {
- // looks like we failed to get the port number. This is a bit strange since
- // it's coming from a regexp that only accept digit, but we handle the case
- // and return null.
- }
- }
- return null;
- }
-
- /**
- * Retrieve a console object for this port, creating if necessary.
- */
- @NonNull
- private static EmulatorConsole retrieveConsole(int port) {
- synchronized (sEmulators) {
- EmulatorConsole console = sEmulators.get(port);
- if (console == null) {
- Log.v(LOG_TAG, "Creating emulator console for " + Integer.toString(port));
- console = new EmulatorConsole(port);
- sEmulators.put(port, console);
- }
- return console;
- }
- }
-
- /**
- * Removes the console object associated with a port from the map.
- * @param port The port of the console to remove.
- */
- private static void removeConsole(int port) {
- synchronized (sEmulators) {
- Log.v(LOG_TAG, "Removing emulator console for " + Integer.toString(port));
- sEmulators.remove(port);
- }
- }
-
- private EmulatorConsole(int port) {
- mPort = port;
- }
-
- /**
- * Determine if connection to emulator console is functioning. Starts the connection if
- * necessary
- * @return true if success.
- */
- private synchronized boolean checkConnection() {
- if (mSocketChannel == null) {
- // connection not established, try to connect
- InetSocketAddress socketAddr;
- try {
- InetAddress hostAddr = InetAddress.getByName(HOST);
- socketAddr = new InetSocketAddress(hostAddr, mPort);
- mSocketChannel = SocketChannel.open(socketAddr);
- mSocketChannel.configureBlocking(false);
- // read initial output from console
- readLines();
- } catch (IOException e) {
- Log.w(LOG_TAG, "Failed to start Emulator console for " + Integer.toString(mPort));
- return false;
- }
- }
-
- return ping();
- }
-
- /**
- * Ping the emulator to check if the connection is still alive.
- * @return true if the connection is alive.
- */
- private synchronized boolean ping() {
- // it looks like we can send stuff, even when the emulator quit, but we can't read
- // from the socket. So we check the return of readLines()
- if (sendCommand(COMMAND_PING)) {
- return readLines() != null;
- }
-
- return false;
- }
-
- /**
- * Sends a KILL command to the emulator.
- */
- public synchronized void kill() {
- if (sendCommand(COMMAND_KILL)) {
- close();
- }
- }
-
- /**
- * Closes this instance of the emulator console.
- */
- public synchronized void close() {
- if (mPort == -1) {
- return;
- }
-
- removeConsole(mPort);
- try {
- if (mSocketChannel != null) {
- mSocketChannel.close();
- }
- mSocketChannel = null;
- mPort = -1;
- } catch (IOException e) {
- Log.w(LOG_TAG, "Failed to close EmulatorConsole channel");
- }
- }
-
- public synchronized String getAvdName() {
- if (sendCommand(COMMAND_AVD_NAME)) {
- String[] result = readLines();
- if (result != null && result.length == 2) { // this should be the name on first line,
- // and ok on 2nd line
- return result[0];
- } else {
- // try to see if there's a message after KO
- Matcher m = RE_KO.matcher(result[result.length-1]);
- if (m.matches()) {
- return m.group(1);
- }
- Log.w(LOG_TAG, "avd name result did not match expected");
- for (int i=0; i < result.length; i++) {
- Log.d(LOG_TAG, result[i]);
- }
- }
- }
-
- return null;
- }
-
- /**
- * Get the network status of the emulator.
- * @return a {@link NetworkStatus} object containing the {@link GsmStatus}, or
- * <code>null</code> if the query failed.
- */
- public synchronized NetworkStatus getNetworkStatus() {
- if (sendCommand(COMMAND_NETWORK_STATUS)) {
- /* Result is in the format
- Current network status:
- download speed: 14400 bits/s (1.8 KB/s)
- upload speed: 14400 bits/s (1.8 KB/s)
- minimum latency: 0 ms
- maximum latency: 0 ms
- */
- String[] result = readLines();
-
- if (isValid(result)) {
- // we only compare against the min latency and the download speed
- // let's not rely on the order of the output, and simply loop through
- // the line testing the regexp.
- NetworkStatus status = new NetworkStatus();
- for (String line : result) {
- Matcher m = sDownloadSpeedRegexp.matcher(line);
- if (m.matches()) {
- // get the string value
- String value = m.group(1);
-
- // get the index from the list
- status.speed = getSpeedIndex(value);
-
- // move on to next line.
- continue;
- }
-
- m = sMinLatencyRegexp.matcher(line);
- if (m.matches()) {
- // get the string value
- String value = m.group(1);
-
- // get the index from the list
- status.latency = getLatencyIndex(value);
-
- // move on to next line.
- continue;
- }
- }
-
- return status;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the current gsm status of the emulator
- * @return a {@link GsmStatus} object containing the gms status, or <code>null</code>
- * if the query failed.
- */
- public synchronized GsmStatus getGsmStatus() {
- if (sendCommand(COMMAND_GSM_STATUS)) {
- /*
- * result is in the format:
- * gsm status
- * gsm voice state: home
- * gsm data state: home
- */
-
- String[] result = readLines();
- if (isValid(result)) {
-
- GsmStatus status = new GsmStatus();
-
- // let's not rely on the order of the output, and simply loop through
- // the line testing the regexp.
- for (String line : result) {
- Matcher m = sVoiceStatusRegexp.matcher(line);
- if (m.matches()) {
- // get the string value
- String value = m.group(1);
-
- // get the index from the list
- status.voice = GsmMode.getEnum(value.toLowerCase(Locale.US));
-
- // move on to next line.
- continue;
- }
-
- m = sDataStatusRegexp.matcher(line);
- if (m.matches()) {
- // get the string value
- String value = m.group(1);
-
- // get the index from the list
- status.data = GsmMode.getEnum(value.toLowerCase(Locale.US));
-
- // move on to next line.
- continue;
- }
- }
-
- return status;
- }
- }
-
- return null;
- }
-
- /**
- * Sets the GSM voice mode.
- * @param mode the {@link GsmMode} value.
- * @return RESULT_OK if success, an error String otherwise.
- * @throws InvalidParameterException if mode is an invalid value.
- */
- public synchronized String setGsmVoiceMode(GsmMode mode) throws InvalidParameterException {
- if (mode == GsmMode.UNKNOWN) {
- throw new InvalidParameterException();
- }
-
- String command = String.format(COMMAND_GSM_VOICE, mode.getTag());
- return processCommand(command);
- }
-
- /**
- * Sets the GSM data mode.
- * @param mode the {@link GsmMode} value
- * @return {@link #RESULT_OK} if success, an error String otherwise.
- * @throws InvalidParameterException if mode is an invalid value.
- */
- public synchronized String setGsmDataMode(GsmMode mode) throws InvalidParameterException {
- if (mode == GsmMode.UNKNOWN) {
- throw new InvalidParameterException();
- }
-
- String command = String.format(COMMAND_GSM_DATA, mode.getTag());
- return processCommand(command);
- }
-
- /**
- * Initiate an incoming call on the emulator.
- * @param number a string representing the calling number.
- * @return {@link #RESULT_OK} if success, an error String otherwise.
- */
- public synchronized String call(String number) {
- String command = String.format(COMMAND_GSM_CALL, number);
- return processCommand(command);
- }
-
- /**
- * Cancels a current call.
- * @param number the number of the call to cancel
- * @return {@link #RESULT_OK} if success, an error String otherwise.
- */
- public synchronized String cancelCall(String number) {
- String command = String.format(COMMAND_GSM_CANCEL_CALL, number);
- return processCommand(command);
- }
-
- /**
- * Sends an SMS to the emulator
- * @param number The sender phone number
- * @param message The SMS message. \ characters must be escaped. The carriage return is
- * the 2 character sequence {'\', 'n' }
- *
- * @return {@link #RESULT_OK} if success, an error String otherwise.
- */
- public synchronized String sendSms(String number, String message) {
- String command = String.format(COMMAND_SMS_SEND, number, message);
- return processCommand(command);
- }
-
- /**
- * Sets the network speed.
- * @param selectionIndex The index in the {@link #NETWORK_SPEEDS} table.
- * @return {@link #RESULT_OK} if success, an error String otherwise.
- */
- public synchronized String setNetworkSpeed(int selectionIndex) {
- String command = String.format(COMMAND_NETWORK_SPEED, NETWORK_SPEEDS[selectionIndex]);
- return processCommand(command);
- }
-
- /**
- * Sets the network latency.
- * @param selectionIndex The index in the {@link #NETWORK_LATENCIES} table.
- * @return {@link #RESULT_OK} if success, an error String otherwise.
- */
- public synchronized String setNetworkLatency(int selectionIndex) {
- String command = String.format(COMMAND_NETWORK_LATENCY, NETWORK_LATENCIES[selectionIndex]);
- return processCommand(command);
- }
-
- public synchronized String sendLocation(double longitude, double latitude, double elevation) {
-
- // need to make sure the string format uses dot and not comma
- Formatter formatter = new Formatter(Locale.US);
- try {
- formatter.format(COMMAND_GPS, longitude, latitude, elevation);
-
- return processCommand(formatter.toString());
- } finally {
- formatter.close();
- }
- }
-
- /**
- * Sends a command to the emulator console.
- * @param command The command string. <b>MUST BE TERMINATED BY \n</b>.
- * @return true if success
- */
- private boolean sendCommand(String command) {
- boolean result = false;
- try {
- byte[] bCommand;
- try {
- bCommand = command.getBytes(DEFAULT_ENCODING);
- } catch (UnsupportedEncodingException e) {
- Log.w(LOG_TAG, "wrong encoding when sending " + command + " to " +
- Integer.toString(mPort));
- // wrong encoding...
- return result;
- }
-
- // write the command
- AdbHelper.write(mSocketChannel, bCommand, bCommand.length, DdmPreferences.getTimeOut());
-
- result = true;
- } catch (Exception e) {
- Log.d(LOG_TAG, "Exception sending command " + command + " to " +
- Integer.toString(mPort));
- return false;
- } finally {
- if (!result) {
- // FIXME connection failed somehow, we need to disconnect the console.
- removeConsole(mPort);
- }
- }
-
- return result;
- }
-
- /**
- * Sends a command to the emulator and parses its answer.
- * @param command the command to send.
- * @return {@link #RESULT_OK} if success, an error message otherwise.
- */
- private String processCommand(String command) {
- if (sendCommand(command)) {
- String[] result = readLines();
-
- if (result != null && result.length > 0) {
- Matcher m = RE_KO.matcher(result[result.length-1]);
- if (m.matches()) {
- return m.group(1);
- }
- return RESULT_OK;
- }
-
- return "Unable to communicate with the emulator";
- }
-
- return "Unable to send command to the emulator";
- }
-
- /**
- * Reads line from the console socket. This call is blocking until we read the lines:
- * <ul>
- * <li>OK\r\n</li>
- * <li>KO<msg>\r\n</li>
- * </ul>
- * @return the array of strings read from the emulator.
- */
- private String[] readLines() {
- try {
- ByteBuffer buf = ByteBuffer.wrap(mBuffer, 0, mBuffer.length);
- int numWaits = 0;
- boolean stop = false;
-
- while (buf.position() != buf.limit() && !stop) {
- int count;
-
- count = mSocketChannel.read(buf);
- if (count < 0) {
- return null;
- } else if (count == 0) {
- if (numWaits * WAIT_TIME > STD_TIMEOUT) {
- return null;
- }
- // non-blocking spin
- try {
- Thread.sleep(WAIT_TIME);
- } catch (InterruptedException ie) {
- }
- numWaits++;
- } else {
- numWaits = 0;
- }
-
- // check the last few char aren't OK. For a valid message to test
- // we need at least 4 bytes (OK/KO + \r\n)
- if (buf.position() >= 4) {
- int pos = buf.position();
- if (endsWithOK(pos) || lastLineIsKO(pos)) {
- stop = true;
- }
- }
- }
-
- String msg = new String(mBuffer, 0, buf.position(), DEFAULT_ENCODING);
- return msg.split("\r\n"); //$NON-NLS-1$
- } catch (IOException e) {
- Log.d(LOG_TAG, "Exception reading lines for " + Integer.toString(mPort));
- return null;
- }
- }
-
- /**
- * Returns true if the 4 characters *before* the current position are "OK\r\n"
- * @param currentPosition The current position
- */
- private boolean endsWithOK(int currentPosition) {
- return mBuffer[currentPosition - 1] == '\n' &&
- mBuffer[currentPosition - 2] == '\r' &&
- mBuffer[currentPosition - 3] == 'K' &&
- mBuffer[currentPosition - 4] == 'O';
-
- }
-
- /**
- * Returns true if the last line starts with KO and is also terminated by \r\n
- * @param currentPosition the current position
- */
- private boolean lastLineIsKO(int currentPosition) {
- // first check that the last 2 characters are CRLF
- if (mBuffer[currentPosition-1] != '\n' ||
- mBuffer[currentPosition-2] != '\r') {
- return false;
- }
-
- // now loop backward looking for the previous CRLF, or the beginning of the buffer
- int i = 0;
- for (i = currentPosition-3 ; i >= 0; i--) {
- if (mBuffer[i] == '\n') {
- // found \n!
- if (i > 0 && mBuffer[i-1] == '\r') {
- // found \r!
- break;
- }
- }
- }
-
- // here it is either -1 if we reached the start of the buffer without finding
- // a CRLF, or the position of \n. So in both case we look at the characters at i+1 and i+2
- if (mBuffer[i+1] == 'K' && mBuffer[i+2] == 'O') {
- // found error!
- return true;
- }
-
- return false;
- }
-
- /**
- * Returns true if the last line of the result does not start with KO
- */
- private boolean isValid(String[] result) {
- if (result != null && result.length > 0) {
- return !(RE_KO.matcher(result[result.length-1]).matches());
- }
- return false;
- }
-
- private int getLatencyIndex(String value) {
- try {
- // get the int value
- int latency = Integer.parseInt(value);
-
- // check for the speed from the index
- for (int i = 0 ; i < MIN_LATENCIES.length; i++) {
- if (MIN_LATENCIES[i] == latency) {
- return i;
- }
- }
- } catch (NumberFormatException e) {
- // Do nothing, we'll just return -1.
- }
-
- return -1;
- }
-
- private int getSpeedIndex(String value) {
- try {
- // get the int value
- int speed = Integer.parseInt(value);
-
- // check for the speed from the index
- for (int i = 0 ; i < DOWNLOAD_SPEEDS.length; i++) {
- if (DOWNLOAD_SPEEDS[i] == speed) {
- return i;
- }
- }
- } catch (NumberFormatException e) {
- // Do nothing, we'll just return -1.
- }
-
- return -1;
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/FileListingService.java b/base/ddmlib/src/main/java/com/android/ddmlib/FileListingService.java
deleted file mode 100644
index 52446ca..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/FileListingService.java
+++ /dev/null
@@ -1,852 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Provides {@link Device} side file listing service.
- * <p/>To get an instance for a known {@link Device}, call {@link Device#getFileListingService()}.
- */
-public final class FileListingService {
-
- /** Pattern to find filenames that match "*.apk" */
- private static final Pattern sApkPattern =
- Pattern.compile(".*\\.apk", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
-
- private static final String PM_FULL_LISTING = "pm list packages -f"; //$NON-NLS-1$
-
- /** Pattern to parse the output of the 'pm -lf' command.<br>
- * The output format looks like:<br>
- * /data/app/myapp.apk=com.mypackage.myapp */
- private static final Pattern sPmPattern = Pattern.compile("^package:(.+?)=(.+)$"); //$NON-NLS-1$
-
- /** Top level data folder. */
- public static final String DIRECTORY_DATA = "data"; //$NON-NLS-1$
- /** Top level sdcard folder. */
- public static final String DIRECTORY_SDCARD = "sdcard"; //$NON-NLS-1$
- /** Top level mount folder. */
- public static final String DIRECTORY_MNT = "mnt"; //$NON-NLS-1$
- /** Top level system folder. */
- public static final String DIRECTORY_SYSTEM = "system"; //$NON-NLS-1$
- /** Top level temp folder. */
- public static final String DIRECTORY_TEMP = "tmp"; //$NON-NLS-1$
- /** Application folder. */
- public static final String DIRECTORY_APP = "app"; //$NON-NLS-1$
-
- public static final long REFRESH_RATE = 5000L;
- /**
- * Refresh test has to be slightly lower for precision issue.
- */
- static final long REFRESH_TEST = (long)(REFRESH_RATE * .8);
-
- /** Entry type: File */
- public static final int TYPE_FILE = 0;
- /** Entry type: Directory */
- public static final int TYPE_DIRECTORY = 1;
- /** Entry type: Directory Link */
- public static final int TYPE_DIRECTORY_LINK = 2;
- /** Entry type: Block */
- public static final int TYPE_BLOCK = 3;
- /** Entry type: Character */
- public static final int TYPE_CHARACTER = 4;
- /** Entry type: Link */
- public static final int TYPE_LINK = 5;
- /** Entry type: Socket */
- public static final int TYPE_SOCKET = 6;
- /** Entry type: FIFO */
- public static final int TYPE_FIFO = 7;
- /** Entry type: Other */
- public static final int TYPE_OTHER = 8;
-
- /** Device side file separator. */
- public static final String FILE_SEPARATOR = "/"; //$NON-NLS-1$
-
- private static final String FILE_ROOT = "/"; //$NON-NLS-1$
-
-
- /**
- * Regexp pattern to parse the result from ls.
- */
- private static final Pattern LS_L_PATTERN = Pattern.compile(
- "^([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])\\s+(\\S+)\\s+(\\S+)\\s+" +
- "([\\d\\s,]*)\\s+(\\d{4}-\\d\\d-\\d\\d)\\s+(\\d\\d:\\d\\d)\\s+(.*)$"); //$NON-NLS-1$
-
- private static final Pattern LS_LD_PATTERN = Pattern.compile(
- "d[rwx-]{9}\\s+\\S+\\s+\\S+\\s+[0-9-]{10}\\s+\\d{2}:\\d{2}$"); //$NON-NLS-1$
-
-
- private Device mDevice;
- private FileEntry mRoot;
-
- private ArrayList<Thread> mThreadList = new ArrayList<Thread>();
-
- /**
- * Represents an entry in a directory. This can be a file or a directory.
- */
- public static final class FileEntry {
- /** Pattern to escape filenames for shell command consumption.
- * This pattern identifies any special characters that need to be escaped with a
- * backslash. */
- private static final Pattern sEscapePattern = Pattern.compile(
- "([\\\\()*+?\"'&#/\\s])"); //$NON-NLS-1$
-
- /**
- * Comparator object for FileEntry
- */
- private static Comparator<FileEntry> sEntryComparator = new Comparator<FileEntry>() {
- @Override
- public int compare(FileEntry o1, FileEntry o2) {
- if (o1 instanceof FileEntry && o2 instanceof FileEntry) {
- FileEntry fe1 = o1;
- FileEntry fe2 = o2;
- return fe1.name.compareTo(fe2.name);
- }
- return 0;
- }
- };
-
- FileEntry parent;
- String name;
- String info;
- String permissions;
- String size;
- String date;
- String time;
- String owner;
- String group;
- int type;
- boolean isAppPackage;
-
- boolean isRoot;
-
- /**
- * Indicates whether the entry content has been fetched yet, or not.
- */
- long fetchTime = 0;
-
- final ArrayList<FileEntry> mChildren = new ArrayList<FileEntry>();
-
- /**
- * Creates a new file entry.
- * @param parent parent entry or null if entry is root
- * @param name name of the entry.
- * @param type entry type. Can be one of the following: {@link FileListingService#TYPE_FILE},
- * {@link FileListingService#TYPE_DIRECTORY}, {@link FileListingService#TYPE_OTHER}.
- */
- private FileEntry(FileEntry parent, String name, int type, boolean isRoot) {
- this.parent = parent;
- this.name = name;
- this.type = type;
- this.isRoot = isRoot;
-
- checkAppPackageStatus();
- }
-
- /**
- * Returns the name of the entry
- */
- public String getName() {
- return name;
- }
-
- /**
- * Returns the size string of the entry, as returned by <code>ls</code>.
- */
- public String getSize() {
- return size;
- }
-
- /**
- * Returns the size of the entry.
- */
- public int getSizeValue() {
- return Integer.parseInt(size);
- }
-
- /**
- * Returns the date string of the entry, as returned by <code>ls</code>.
- */
- public String getDate() {
- return date;
- }
-
- /**
- * Returns the time string of the entry, as returned by <code>ls</code>.
- */
- public String getTime() {
- return time;
- }
-
- /**
- * Returns the permission string of the entry, as returned by <code>ls</code>.
- */
- public String getPermissions() {
- return permissions;
- }
-
- /**
- * Returns the owner string of the entry, as returned by <code>ls</code>.
- */
- public String getOwner() {
- return owner;
- }
-
- /**
- * Returns the group owner of the entry, as returned by <code>ls</code>.
- */
- public String getGroup() {
- return group;
- }
-
- /**
- * Returns the extra info for the entry.
- * <p/>For a link, it will be a description of the link.
- * <p/>For an application apk file it will be the application package as returned
- * by the Package Manager.
- */
- public String getInfo() {
- return info;
- }
-
- /**
- * Return the full path of the entry.
- * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator.
- */
- public String getFullPath() {
- if (isRoot) {
- return FILE_ROOT;
- }
- StringBuilder pathBuilder = new StringBuilder();
- fillPathBuilder(pathBuilder, false);
-
- return pathBuilder.toString();
- }
-
- /**
- * Return the fully escaped path of the entry. This path is safe to use in a
- * shell command line.
- * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator
- */
- public String getFullEscapedPath() {
- StringBuilder pathBuilder = new StringBuilder();
- fillPathBuilder(pathBuilder, true);
-
- return pathBuilder.toString();
- }
-
- /**
- * Returns the path as a list of segments.
- */
- public String[] getPathSegments() {
- ArrayList<String> list = new ArrayList<String>();
- fillPathSegments(list);
-
- return list.toArray(new String[list.size()]);
- }
-
- /**
- * Returns the Entry type as an int, which will match one of the TYPE_(...) constants
- */
- public int getType() {
- return type;
- }
-
- /**
- * Sets a new type.
- */
- public void setType(int type) {
- this.type = type;
- }
-
- /**
- * Returns if the entry is a folder or a link to a folder.
- */
- public boolean isDirectory() {
- return type == TYPE_DIRECTORY || type == TYPE_DIRECTORY_LINK;
- }
-
- /**
- * Returns the parent entry.
- */
- public FileEntry getParent() {
- return parent;
- }
-
- /**
- * Returns the cached children of the entry. This returns the cache created from calling
- * <code>FileListingService.getChildren()</code>.
- */
- public FileEntry[] getCachedChildren() {
- return mChildren.toArray(new FileEntry[mChildren.size()]);
- }
-
- /**
- * Returns the child {@link FileEntry} matching the name.
- * This uses the cached children list.
- * @param name the name of the child to return.
- * @return the FileEntry matching the name or null.
- */
- public FileEntry findChild(String name) {
- for (FileEntry entry : mChildren) {
- if (entry.name.equals(name)) {
- return entry;
- }
- }
- return null;
- }
-
- /**
- * Returns whether the entry is the root.
- */
- public boolean isRoot() {
- return isRoot;
- }
-
- void addChild(FileEntry child) {
- mChildren.add(child);
- }
-
- void setChildren(ArrayList<FileEntry> newChildren) {
- mChildren.clear();
- mChildren.addAll(newChildren);
- }
-
- boolean needFetch() {
- if (fetchTime == 0) {
- return true;
- }
- long current = System.currentTimeMillis();
- return current - fetchTime > REFRESH_TEST;
-
- }
-
- /**
- * Returns if the entry is a valid application package.
- */
- public boolean isApplicationPackage() {
- return isAppPackage;
- }
-
- /**
- * Returns if the file name is an application package name.
- */
- public boolean isAppFileName() {
- Matcher m = sApkPattern.matcher(name);
- return m.matches();
- }
-
- /**
- * Recursively fills the pathBuilder with the full path
- * @param pathBuilder a StringBuilder used to create the path.
- * @param escapePath Whether the path need to be escaped for consumption by
- * a shell command line.
- */
- protected void fillPathBuilder(StringBuilder pathBuilder, boolean escapePath) {
- if (isRoot) {
- return;
- }
-
- if (parent != null) {
- parent.fillPathBuilder(pathBuilder, escapePath);
- }
- pathBuilder.append(FILE_SEPARATOR);
- pathBuilder.append(escapePath ? escape(name) : name);
- }
-
- /**
- * Recursively fills the segment list with the full path.
- * @param list The list of segments to fill.
- */
- protected void fillPathSegments(ArrayList<String> list) {
- if (isRoot) {
- return;
- }
-
- if (parent != null) {
- parent.fillPathSegments(list);
- }
-
- list.add(name);
- }
-
- /**
- * Sets the internal app package status flag. This checks whether the entry is in an app
- * directory like /data/app or /system/app
- */
- private void checkAppPackageStatus() {
- isAppPackage = false;
-
- String[] segments = getPathSegments();
- if (type == TYPE_FILE && segments.length == 3 && isAppFileName()) {
- isAppPackage = DIRECTORY_APP.equals(segments[1]) &&
- (DIRECTORY_SYSTEM.equals(segments[0]) || DIRECTORY_DATA.equals(segments[0]));
- }
- }
-
- /**
- * Returns an escaped version of the entry name.
- * @param entryName
- */
- public static String escape(String entryName) {
- return sEscapePattern.matcher(entryName).replaceAll("\\\\$1"); //$NON-NLS-1$
- }
- }
-
- private static class LsReceiver extends MultiLineReceiver {
-
- private ArrayList<FileEntry> mEntryList;
- private ArrayList<String> mLinkList;
- private FileEntry[] mCurrentChildren;
- private FileEntry mParentEntry;
-
- /**
- * Create an ls receiver/parser.
- * @param currentChildren The list of current children. To prevent
- * collapse during update, reusing the same FileEntry objects for
- * files that were already there is paramount.
- * @param entryList the list of new children to be filled by the
- * receiver.
- * @param linkList the list of link path to compute post ls, to figure
- * out if the link pointed to a file or to a directory.
- */
- public LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList,
- ArrayList<String> linkList) {
- mParentEntry = parentEntry;
- mCurrentChildren = parentEntry.getCachedChildren();
- mEntryList = entryList;
- mLinkList = linkList;
- }
-
- @Override
- public void processNewLines(String[] lines) {
- for (String line : lines) {
- // no need to handle empty lines.
- if (line.isEmpty()) {
- continue;
- }
-
- // run the line through the regexp
- Matcher m = LS_L_PATTERN.matcher(line);
- if (!m.matches()) {
- continue;
- }
-
- // get the name
- String name = m.group(7);
-
- // get the rest of the groups
- String permissions = m.group(1);
- String owner = m.group(2);
- String group = m.group(3);
- String size = m.group(4);
- String date = m.group(5);
- String time = m.group(6);
- String info = null;
-
- // and the type
- int objectType = TYPE_OTHER;
- switch (permissions.charAt(0)) {
- case '-' :
- objectType = TYPE_FILE;
- break;
- case 'b' :
- objectType = TYPE_BLOCK;
- break;
- case 'c' :
- objectType = TYPE_CHARACTER;
- break;
- case 'd' :
- objectType = TYPE_DIRECTORY;
- break;
- case 'l' :
- objectType = TYPE_LINK;
- break;
- case 's' :
- objectType = TYPE_SOCKET;
- break;
- case 'p' :
- objectType = TYPE_FIFO;
- break;
- }
-
-
- // now check what we may be linking to
- if (objectType == TYPE_LINK) {
- String[] segments = name.split("\\s->\\s"); //$NON-NLS-1$
-
- // we should have 2 segments
- if (segments.length == 2) {
- // update the entry name to not contain the link
- name = segments[0];
-
- // and the link name
- info = segments[1];
-
- // now get the path to the link
- String[] pathSegments = info.split(FILE_SEPARATOR);
- if (pathSegments.length == 1) {
- // the link is to something in the same directory,
- // unless the link is ..
- if ("..".equals(pathSegments[0])) { //$NON-NLS-1$
- // set the type and we're done.
- objectType = TYPE_DIRECTORY_LINK;
- } else {
- // either we found the object already
- // or we'll find it later.
- }
- }
- }
-
- // add an arrow in front to specify it's a link.
- info = "-> " + info; //$NON-NLS-1$;
- }
-
- // get the entry, either from an existing one, or a new one
- FileEntry entry = getExistingEntry(name);
- if (entry == null) {
- entry = new FileEntry(mParentEntry, name, objectType, false /* isRoot */);
- }
-
- // add some misc info
- entry.permissions = permissions;
- entry.size = size;
- entry.date = date;
- entry.time = time;
- entry.owner = owner;
- entry.group = group;
- if (objectType == TYPE_LINK) {
- entry.info = info;
- }
-
- mEntryList.add(entry);
- }
- }
-
- /**
- * Queries for an already existing Entry per name
- * @param name the name of the entry
- * @return the existing FileEntry or null if no entry with a matching
- * name exists.
- */
- private FileEntry getExistingEntry(String name) {
- for (int i = 0 ; i < mCurrentChildren.length; i++) {
- FileEntry e = mCurrentChildren[i];
-
- // since we're going to "erase" the one we use, we need to
- // check that the item is not null.
- if (e != null) {
- // compare per name, case-sensitive.
- if (name.equals(e.name)) {
- // erase from the list
- mCurrentChildren[i] = null;
-
- // and return the object
- return e;
- }
- }
- }
-
- // couldn't find any matching object, return null
- return null;
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- /**
- * Determine if any symlinks in the <code entries> list are links-to-directories, and if so
- * mark them as such. This allows us to traverse them properly later on.
- */
- public void finishLinks(IDevice device, ArrayList<FileEntry> entries)
- throws TimeoutException, AdbCommandRejectedException,
- ShellCommandUnresponsiveException, IOException {
- final int[] nLines = {0};
- MultiLineReceiver receiver = new MultiLineReceiver() {
- @Override
- public void processNewLines(String[] lines) {
- for (String line : lines) {
- Matcher m = LS_LD_PATTERN.matcher(line);
- if (m.matches()) {
- nLines[0]++;
- }
- }
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
- };
-
- for (FileEntry entry : entries) {
- if (entry.getType() != TYPE_LINK) continue;
-
- // We simply need to determine whether the referent is a directory or not.
- // We do this by running `ls -ld ${link}/`. If the referent exists and is a
- // directory, we'll see the normal directory listing. Otherwise, we'll see an
- // error of some sort.
- nLines[0] = 0;
-
- final String command = String.format("ls -l -d %s%s", entry.getFullEscapedPath(),
- FILE_SEPARATOR);
-
- device.executeShellCommand(command, receiver);
-
- if (nLines[0] > 0) {
- // We saw lines matching the directory pattern, so it's a directory!
- entry.setType(TYPE_DIRECTORY_LINK);
- }
- }
- }
- }
-
- /**
- * Classes which implement this interface provide a method that deals with asynchronous
- * result from <code>ls</code> command on the device.
- *
- * @see FileListingService#getChildren(com.android.ddmlib.FileListingService.FileEntry, boolean, com.android.ddmlib.FileListingService.IListingReceiver)
- */
- public interface IListingReceiver {
- void setChildren(FileEntry entry, FileEntry[] children);
-
- void refreshEntry(FileEntry entry);
- }
-
- /**
- * Creates a File Listing Service for a specified {@link Device}.
- * @param device The Device the service is connected to.
- */
- FileListingService(Device device) {
- mDevice = device;
- }
-
- /**
- * Returns the root element.
- * @return the {@link FileEntry} object representing the root element or
- * <code>null</code> if the device is invalid.
- */
- public FileEntry getRoot() {
- if (mDevice != null) {
- if (mRoot == null) {
- mRoot = new FileEntry(null /* parent */, "" /* name */, TYPE_DIRECTORY,
- true /* isRoot */);
- }
-
- return mRoot;
- }
-
- return null;
- }
-
- /**
- * Returns the children of a {@link FileEntry}.
- * <p/>
- * This method supports a cache mechanism and synchronous and asynchronous modes.
- * <p/>
- * If <var>receiver</var> is <code>null</code>, the device side <code>ls</code>
- * command is done synchronously, and the method will return upon completion of the command.<br>
- * If <var>receiver</var> is non <code>null</code>, the command is launched is a separate
- * thread and upon completion, the receiver will be notified of the result.
- * <p/>
- * The result for each <code>ls</code> command is cached in the parent
- * <code>FileEntry</code>. <var>useCache</var> allows usage of this cache, but only if the
- * cache is valid. The cache is valid only for {@link FileListingService#REFRESH_RATE} ms.
- * After that a new <code>ls</code> command is always executed.
- * <p/>
- * If the cache is valid and <code>useCache == true</code>, the method will always simply
- * return the value of the cache, whether a {@link IListingReceiver} has been provided or not.
- *
- * @param entry The parent entry.
- * @param useCache A flag to use the cache or to force a new ls command.
- * @param receiver A receiver for asynchronous calls.
- * @return The list of children or <code>null</code> for asynchronous calls.
- *
- * @see FileEntry#getCachedChildren()
- */
- public FileEntry[] getChildren(final FileEntry entry, boolean useCache,
- final IListingReceiver receiver) {
- // first thing we do is check the cache, and if we already have a recent
- // enough children list, we just return that.
- if (useCache && !entry.needFetch()) {
- return entry.getCachedChildren();
- }
-
- // if there's no receiver, then this is a synchronous call, and we
- // return the result of ls
- if (receiver == null) {
- doLs(entry);
- return entry.getCachedChildren();
- }
-
- // this is a asynchronous call.
- // we launch a thread that will do ls and give the listing
- // to the receiver
- Thread t = new Thread("ls " + entry.getFullPath()) { //$NON-NLS-1$
- @Override
- public void run() {
- doLs(entry);
-
- receiver.setChildren(entry, entry.getCachedChildren());
-
- final FileEntry[] children = entry.getCachedChildren();
- if (children.length > 0 && children[0].isApplicationPackage()) {
- final HashMap<String, FileEntry> map = new HashMap<String, FileEntry>();
-
- for (FileEntry child : children) {
- String path = child.getFullPath();
- map.put(path, child);
- }
-
- // call pm.
- String command = PM_FULL_LISTING;
- try {
- mDevice.executeShellCommand(command, new MultiLineReceiver() {
- @Override
- public void processNewLines(String[] lines) {
- for (String line : lines) {
- if (!line.isEmpty()) {
- // get the filepath and package from the line
- Matcher m = sPmPattern.matcher(line);
- if (m.matches()) {
- // get the children with that path
- FileEntry entry = map.get(m.group(1));
- if (entry != null) {
- entry.info = m.group(2);
- receiver.refreshEntry(entry);
- }
- }
- }
- }
- }
- @Override
- public boolean isCancelled() {
- return false;
- }
- });
- } catch (Exception e) {
- // adb failed somehow, we do nothing.
- }
- }
-
-
- // if another thread is pending, launch it
- synchronized (mThreadList) {
- // first remove ourselves from the list
- mThreadList.remove(this);
-
- // then launch the next one if applicable.
- if (!mThreadList.isEmpty()) {
- Thread t = mThreadList.get(0);
- t.start();
- }
- }
- }
- };
-
- // we don't want to run multiple ls on the device at the same time, so we
- // store the thread in a list and launch it only if there's no other thread running.
- // the thread will launch the next one once it's done.
- synchronized (mThreadList) {
- // add to the list
- mThreadList.add(t);
-
- // if it's the only one, launch it.
- if (mThreadList.size() == 1) {
- t.start();
- }
- }
-
- // and we return null.
- return null;
- }
-
- /**
- * Returns the children of a {@link FileEntry}.
- * <p/>
- * This method is the explicit synchronous version of
- * {@link #getChildren(FileEntry, boolean, IListingReceiver)}. It is roughly equivalent to
- * calling
- * getChildren(FileEntry, false, null)
- *
- * @param entry The parent entry.
- * @return The list of children
- * @throws TimeoutException in case of timeout on the connection when sending the command.
- * @throws AdbCommandRejectedException if adb rejects the command.
- * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
- * for a period longer than <var>maxTimeToOutputResponse</var>.
- * @throws IOException in case of I/O error on the connection.
- */
- public FileEntry[] getChildrenSync(final FileEntry entry) throws TimeoutException,
- AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
- doLsAndThrow(entry);
- return entry.getCachedChildren();
- }
-
- private void doLs(FileEntry entry) {
- try {
- doLsAndThrow(entry);
- } catch (Exception e) {
- // do nothing
- }
- }
-
- private void doLsAndThrow(FileEntry entry) throws TimeoutException,
- AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
- // create a list that will receive the list of the entries
- ArrayList<FileEntry> entryList = new ArrayList<FileEntry>();
-
- // create a list that will receive the link to compute post ls;
- ArrayList<String> linkList = new ArrayList<String>();
-
- try {
- // create the command
- String command = "ls -l " + entry.getFullEscapedPath(); //$NON-NLS-1$
- if (entry.isDirectory()) {
- // If we expect a file to behave like a directory, we should stick a "/" at the end.
- // This is a good habit, and is mandatory for symlinks-to-directories, which will
- // otherwise behave like symlinks.
- command += FILE_SEPARATOR;
- }
-
- // create the receiver object that will parse the result from ls
- LsReceiver receiver = new LsReceiver(entry, entryList, linkList);
-
- // call ls.
- mDevice.executeShellCommand(command, receiver);
-
- // finish the process of the receiver to handle links
- receiver.finishLinks(mDevice, entryList);
- } finally {
- // at this point we need to refresh the viewer
- entry.fetchTime = System.currentTimeMillis();
-
- // sort the children and set them as the new children
- Collections.sort(entryList, FileEntry.sEntryComparator);
- entry.setChildren(entryList);
- }
- }
-
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleExit.java b/base/ddmlib/src/main/java/com/android/ddmlib/HandleExit.java
deleted file mode 100644
index adeedbb..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/HandleExit.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Submit an exit request.
- */
-final class HandleExit extends ChunkHandler {
-
- public static final int CHUNK_EXIT = type("EXIT");
-
- private static final HandleExit mInst = new HandleExit();
-
-
- private HandleExit() {}
-
- /**
- * Register for the packets we expect to get from the client.
- */
- public static void register(MonitorThread mt) {}
-
- /**
- * Client is ready.
- */
- @Override
- public void clientReady(Client client) throws IOException {}
-
- /**
- * Client went away.
- */
- @Override
- public void clientDisconnected(Client client) {}
-
- /**
- * Chunk handler entry point.
- */
- @Override
- public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
- handleUnknownChunk(client, type, data, isReply, msgId);
- }
-
- /**
- * Send an EXIT request to the client.
- */
- public static void sendEXIT(Client client, int status)
- throws IOException
- {
- ByteBuffer rawBuf = allocBuffer(4);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.putInt(status);
-
- finishChunkPacket(packet, CHUNK_EXIT, buf.position());
- Log.d("ddm-exit", "Sending " + name(CHUNK_EXIT) + ": " + status);
- client.sendAndConsume(packet, mInst);
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java b/base/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java
deleted file mode 100644
index 97dd867..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.ddmlib.ClientData.AllocationTrackingStatus;
-import com.android.ddmlib.ClientData.IHprofDumpHandler;
-
-import java.io.IOException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-
-/**
- * Handle heap status updates.
- */
-final class HandleHeap extends ChunkHandler {
-
- public static final int CHUNK_HPIF = type("HPIF");
- public static final int CHUNK_HPST = type("HPST");
- public static final int CHUNK_HPEN = type("HPEN");
- public static final int CHUNK_HPSG = type("HPSG");
- public static final int CHUNK_HPGC = type("HPGC");
- public static final int CHUNK_HPDU = type("HPDU");
- public static final int CHUNK_HPDS = type("HPDS");
- public static final int CHUNK_REAE = type("REAE");
- public static final int CHUNK_REAQ = type("REAQ");
- public static final int CHUNK_REAL = type("REAL");
-
- // args to sendHPSG
- public static final int WHEN_DISABLE = 0;
- public static final int WHEN_GC = 1;
- public static final int WHAT_MERGE = 0; // merge adjacent objects
- public static final int WHAT_OBJ = 1; // keep objects distinct
-
- // args to sendHPIF
- public static final int HPIF_WHEN_NEVER = 0;
- public static final int HPIF_WHEN_NOW = 1;
- public static final int HPIF_WHEN_NEXT_GC = 2;
- public static final int HPIF_WHEN_EVERY_GC = 3;
-
- private static final HandleHeap mInst = new HandleHeap();
-
- private HandleHeap() {}
-
- /**
- * Register for the packets we expect to get from the client.
- */
- public static void register(MonitorThread mt) {
- mt.registerChunkHandler(CHUNK_HPIF, mInst);
- mt.registerChunkHandler(CHUNK_HPST, mInst);
- mt.registerChunkHandler(CHUNK_HPEN, mInst);
- mt.registerChunkHandler(CHUNK_HPSG, mInst);
- mt.registerChunkHandler(CHUNK_HPDS, mInst);
- mt.registerChunkHandler(CHUNK_REAQ, mInst);
- mt.registerChunkHandler(CHUNK_REAL, mInst);
- }
-
- /**
- * Client is ready.
- */
- @Override
- public void clientReady(Client client) throws IOException {
- client.initializeHeapUpdateStatus();
- }
-
- /**
- * Client went away.
- */
- @Override
- public void clientDisconnected(Client client) {}
-
- /**
- * Chunk handler entry point.
- */
- @Override
- public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
- Log.d("ddm-heap", "handling " + ChunkHandler.name(type));
-
- if (type == CHUNK_HPIF) {
- handleHPIF(client, data);
- } else if (type == CHUNK_HPST) {
- handleHPST(client, data);
- } else if (type == CHUNK_HPEN) {
- handleHPEN(client, data);
- } else if (type == CHUNK_HPSG) {
- handleHPSG(client, data);
- } else if (type == CHUNK_HPDU) {
- handleHPDU(client, data);
- } else if (type == CHUNK_HPDS) {
- handleHPDS(client, data);
- } else if (type == CHUNK_REAQ) {
- handleREAQ(client, data);
- } else if (type == CHUNK_REAL) {
- handleREAL(client, data);
- } else {
- handleUnknownChunk(client, type, data, isReply, msgId);
- }
- }
-
- /*
- * Handle a heap info message.
- */
- private void handleHPIF(Client client, ByteBuffer data) {
- Log.d("ddm-heap", "HPIF!");
- try {
- int numHeaps = data.getInt();
-
- for (int i = 0; i < numHeaps; i++) {
- int heapId = data.getInt();
- long timeStamp = data.getLong();
- byte reason = data.get();
- long maxHeapSize = (long)data.getInt() & 0x00ffffffff;
- long heapSize = (long)data.getInt() & 0x00ffffffff;
- long bytesAllocated = (long)data.getInt() & 0x00ffffffff;
- long objectsAllocated = (long)data.getInt() & 0x00ffffffff;
-
- client.getClientData().setHeapInfo(heapId, maxHeapSize,
- heapSize, bytesAllocated, objectsAllocated, timeStamp, reason);
- client.update(Client.CHANGE_HEAP_DATA);
- }
- } catch (BufferUnderflowException ex) {
- Log.w("ddm-heap", "malformed HPIF chunk from client");
- }
- }
-
- /**
- * Send an HPIF (HeaP InFo) request to the client.
- */
- public static void sendHPIF(Client client, int when) throws IOException {
- ByteBuffer rawBuf = allocBuffer(1);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.put((byte)when);
-
- finishChunkPacket(packet, CHUNK_HPIF, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_HPIF) + ": when=" + when);
- client.sendAndConsume(packet, mInst);
- }
-
- /*
- * Handle a heap segment series start message.
- */
- private void handleHPST(Client client, ByteBuffer data) {
- /* Clear out any data that's sitting around to
- * get ready for the chunks that are about to come.
- */
-//xxx todo: only clear data that belongs to the heap mentioned in <data>.
- client.getClientData().getVmHeapData().clearHeapData();
- }
-
- /*
- * Handle a heap segment series end message.
- */
- private void handleHPEN(Client client, ByteBuffer data) {
- /* Let the UI know that we've received all of the
- * data for this heap.
- */
-//xxx todo: only seal data that belongs to the heap mentioned in <data>.
- client.getClientData().getVmHeapData().sealHeapData();
- client.update(Client.CHANGE_HEAP_DATA);
- }
-
- /*
- * Handle a heap segment message.
- */
- private void handleHPSG(Client client, ByteBuffer data) {
- byte dataCopy[] = new byte[data.limit()];
- data.rewind();
- data.get(dataCopy);
- data = ByteBuffer.wrap(dataCopy);
- client.getClientData().getVmHeapData().addHeapData(data);
-//xxx todo: add to the heap mentioned in <data>
- }
-
- /**
- * Sends an HPSG (HeaP SeGment) request to the client.
- */
- public static void sendHPSG(Client client, int when, int what)
- throws IOException {
-
- ByteBuffer rawBuf = allocBuffer(2);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.put((byte)when);
- buf.put((byte)what);
-
- finishChunkPacket(packet, CHUNK_HPSG, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_HPSG) + ": when="
- + when + ", what=" + what);
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Sends an HPGC request to the client.
- */
- public static void sendHPGC(Client client)
- throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_HPGC, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_HPGC));
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Sends an HPDU request to the client.
- *
- * We will get an HPDU response when the heap dump has completed. On
- * failure we get a generic failure response.
- *
- * @param fileName name of output file (on device)
- */
- public static void sendHPDU(Client client, String fileName)
- throws IOException {
- ByteBuffer rawBuf = allocBuffer(4 + fileName.length() * 2);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.putInt(fileName.length());
- ByteBufferUtil.putString(buf, fileName);
-
- finishChunkPacket(packet, CHUNK_HPDU, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_HPDU) + " '" + fileName +"'");
- client.sendAndConsume(packet, mInst);
- client.getClientData().setPendingHprofDump(fileName);
- }
-
- /**
- * Sends an HPDS request to the client.
- *
- * We will get an HPDS response when the heap dump has completed. On
- * failure we get a generic failure response.
- *
- * This is more expensive for the device than HPDU, because the entire
- * heap dump is held in RAM instead of spooled out to a temp file. On
- * the other hand, permission to write to /sdcard is not required.
- *
- * @param fileName name of output file (on device)
- */
- public static void sendHPDS(Client client)
- throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- finishChunkPacket(packet, CHUNK_HPDS, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_HPDS));
- client.sendAndConsume(packet, mInst);
- }
-
- /*
- * Handle notification of completion of a HeaP DUmp.
- */
- private void handleHPDU(Client client, ByteBuffer data) {
- byte result;
-
- // get the filename and make the client not have pending HPROF dump anymore.
- String filename = client.getClientData().getPendingHprofDump();
- client.getClientData().setPendingHprofDump(null);
-
- // get the dump result
- result = data.get();
-
- // get the app-level handler for HPROF dump
- IHprofDumpHandler handler = ClientData.getHprofDumpHandler();
- if (result == 0) {
- if (handler != null) {
- handler.onSuccess(filename, client);
- }
- client.getClientData().setHprofData(filename);
- Log.d("ddm-heap", "Heap dump request has finished");
- } else {
- if (handler != null) {
- handler.onEndFailure(client, null);
- }
- client.getClientData().clearHprofData();
- Log.w("ddm-heap", "Heap dump request failed (check device log)");
- }
- client.update(Client.CHANGE_HPROF);
- client.getClientData().clearHprofData();
- }
-
- /*
- * Handle HeaP Dump Streaming response. "data" contains the full
- * hprof dump.
- */
- private void handleHPDS(Client client, ByteBuffer data) {
- byte[] stuff = new byte[data.capacity()];
- data.get(stuff, 0, stuff.length);
-
- Log.d("ddm-hprof", "got hprof file, size: " + data.capacity() + " bytes");
- client.getClientData().setHprofData(stuff);
- IHprofDumpHandler handler = ClientData.getHprofDumpHandler();
- if (handler != null) {
- handler.onSuccess(stuff, client);
- }
- client.update(Client.CHANGE_HPROF);
- client.getClientData().clearHprofData();
- }
-
- /**
- * Sends a REAE (REcent Allocation Enable) request to the client.
- */
- public static void sendREAE(Client client, boolean enable)
- throws IOException {
- ByteBuffer rawBuf = allocBuffer(1);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.put((byte) (enable ? 1 : 0));
-
- finishChunkPacket(packet, CHUNK_REAE, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_REAE) + ": " + enable);
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Sends a REAQ (REcent Allocation Query) request to the client.
- */
- public static void sendREAQ(Client client)
- throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_REAQ, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_REAQ));
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Sends a REAL (REcent ALlocation) request to the client.
- */
- public static void sendREAL(Client client)
- throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_REAL, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_REAL));
- client.sendAndConsume(packet, mInst);
- }
-
- /*
- * Handle the response from our REcent Allocation Query message.
- */
- private void handleREAQ(Client client, ByteBuffer data) {
- boolean enabled;
-
- enabled = (data.get() != 0);
- Log.d("ddm-heap", "REAQ says: enabled=" + enabled);
-
- client.getClientData().setAllocationStatus(enabled ? AllocationTrackingStatus.ON : AllocationTrackingStatus.OFF);
- client.update(Client.CHANGE_HEAP_ALLOCATION_STATUS);
- }
-
- /*
- * Handle a REcent ALlocation response.
- */
- private void handleREAL(Client client, ByteBuffer data) {
- Log.e("ddm-heap", "*** Received " + name(CHUNK_REAL));
- ClientData.IAllocationTrackingHandler handler = ClientData.getAllocationTrackingHandler();
-
- if (handler != null) {
- byte[] stuff = new byte[data.capacity()];
- data.get(stuff, 0, stuff.length);
-
- Log.d("ddm-prof", "got allocations file, size: " + stuff.length + " bytes");
- handler.onSuccess(stuff, client);
- } else {
- // Allocation tracking did not start from Android Studio's device panel
- client.getClientData().setAllocations(AllocationsParser.parse(data));
- client.update(Client.CHANGE_HEAP_ALLOCATIONS);
- }
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java b/base/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java
deleted file mode 100644
index 6bf9150..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import java.io.IOException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-
-/**
- * Handle the "hello" chunk (HELO) and feature discovery.
- */
-final class HandleHello extends ChunkHandler {
-
- public static final int CHUNK_HELO = ChunkHandler.type("HELO");
- public static final int CHUNK_FEAT = ChunkHandler.type("FEAT");
-
- private static final HandleHello mInst = new HandleHello();
-
- private HandleHello() {}
-
- /**
- * Register for the packets we expect to get from the client.
- */
- public static void register(MonitorThread mt) {
- mt.registerChunkHandler(CHUNK_HELO, mInst);
- }
-
- /**
- * Client is ready.
- */
- @Override
- public void clientReady(Client client) throws IOException {
- Log.d("ddm-hello", "Now ready: " + client);
- }
-
- /**
- * Client went away.
- */
- @Override
- public void clientDisconnected(Client client) {
- Log.d("ddm-hello", "Now disconnected: " + client);
- }
-
- /**
- * Sends HELLO-type commands to the VM after a good handshake.
- * @param client
- * @param serverProtocolVersion
- * @throws IOException
- */
- public static void sendHelloCommands(Client client, int serverProtocolVersion)
- throws IOException {
- sendHELO(client, serverProtocolVersion);
- sendFEAT(client);
- HandleProfiling.sendMPRQ(client);
- }
-
- /**
- * Chunk handler entry point.
- */
- @Override
- public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
-
- Log.d("ddm-hello", "handling " + ChunkHandler.name(type));
-
- if (type == CHUNK_HELO) {
- assert isReply;
- handleHELO(client, data);
- } else if (type == CHUNK_FEAT) {
- handleFEAT(client, data);
- } else {
- handleUnknownChunk(client, type, data, isReply, msgId);
- }
- }
-
- /*
- * Handle a reply to our HELO message.
- */
- private static void handleHELO(Client client, ByteBuffer data) {
- int version, pid, vmIdentLen, appNameLen;
- String vmIdent, appName;
-
- version = data.getInt();
- pid = data.getInt();
- vmIdentLen = data.getInt();
- appNameLen = data.getInt();
-
- vmIdent = ByteBufferUtil.getString(data, vmIdentLen);
- appName = ByteBufferUtil.getString(data, appNameLen);
-
- // Newer devices send user id in the APNM packet.
- int userId = -1;
- boolean validUserId = false;
- if (data.hasRemaining()) {
- try {
- userId = data.getInt();
- validUserId = true;
- } catch (BufferUnderflowException e) {
- // five integers + two utf-16 strings
- int expectedPacketLength = 20 + appNameLen * 2 + vmIdentLen * 2;
-
- Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve user id.");
- Log.e("ddm-hello", "Actual chunk length: " + data.capacity());
- Log.e("ddm-hello", "Expected chunk length: " + expectedPacketLength);
- }
- }
-
- // check if the VM has reported information about the ABI
- boolean validAbi = false;
- String abi = null;
- if (data.hasRemaining()) {
- try {
- int abiLength = data.getInt();
- abi = ByteBufferUtil.getString(data, abiLength);
- validAbi = true;
- } catch (BufferUnderflowException e) {
- Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve ABI.");
- }
- }
-
- boolean hasJvmFlags = false;
- String jvmFlags = null;
- if (data.hasRemaining()) {
- try {
- int jvmFlagsLength = data.getInt();
- jvmFlags = ByteBufferUtil.getString(data, jvmFlagsLength);
- hasJvmFlags = true;
- } catch (BufferUnderflowException e) {
- Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve JVM flags");
- }
- }
-
- Log.d("ddm-hello", "HELO: v=" + version + ", pid=" + pid
- + ", vm='" + vmIdent + "', app='" + appName + "'");
-
- ClientData cd = client.getClientData();
-
- if (cd.getPid() == pid) {
- cd.setVmIdentifier(vmIdent);
- cd.setClientDescription(appName);
- cd.isDdmAware(true);
-
- if (validUserId) {
- cd.setUserId(userId);
- }
-
- if (validAbi) {
- cd.setAbi(abi);
- }
-
- if (hasJvmFlags) {
- cd.setJvmFlags(jvmFlags);
- }
- } else {
- Log.e("ddm-hello", "Received pid (" + pid + ") does not match client pid ("
- + cd.getPid() + ")");
- }
-
- client = checkDebuggerPortForAppName(client, appName);
-
- if (client != null) {
- client.update(Client.CHANGE_NAME);
- }
- }
-
-
- /**
- * Send a HELO request to the client.
- */
- public static void sendHELO(Client client, int serverProtocolVersion)
- throws IOException
- {
- ByteBuffer rawBuf = allocBuffer(4);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.putInt(serverProtocolVersion);
-
- finishChunkPacket(packet, CHUNK_HELO, buf.position());
- Log.d("ddm-hello", "Sending " + name(CHUNK_HELO)
- + " ID=0x" + Integer.toHexString(packet.getId()));
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Handle a reply to our FEAT request.
- */
- private static void handleFEAT(Client client, ByteBuffer data) {
- int featureCount;
- int i;
-
- featureCount = data.getInt();
- for (i = 0; i < featureCount; i++) {
- int len = data.getInt();
- String feature = ByteBufferUtil.getString(data, len);
- client.getClientData().addFeature(feature);
-
- Log.d("ddm-hello", "Feature: " + feature);
- }
- }
-
- /**
- * Send a FEAT request to the client.
- */
- public static void sendFEAT(Client client) throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_FEAT, buf.position());
- Log.d("ddm-heap", "Sending " + name(CHUNK_FEAT));
- client.sendAndConsume(packet, mInst);
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleNativeHeap.java b/base/ddmlib/src/main/java/com/android/ddmlib/HandleNativeHeap.java
deleted file mode 100644
index baf6db1..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/HandleNativeHeap.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * Handle thread status updates.
- */
-final class HandleNativeHeap extends ChunkHandler {
-
- public static final int CHUNK_NHGT = type("NHGT"); //$NON-NLS-1$
- public static final int CHUNK_NHSG = type("NHSG"); //$NON-NLS-1$
- public static final int CHUNK_NHST = type("NHST"); //$NON-NLS-1$
- public static final int CHUNK_NHEN = type("NHEN"); //$NON-NLS-1$
-
- private static final HandleNativeHeap mInst = new HandleNativeHeap();
-
- /**
- * Handle getting different sized size_t and pointer reads.
- */
- abstract class NativeBuffer {
- public NativeBuffer(ByteBuffer buffer) {
- mBuffer = buffer;
- }
-
- public abstract int getSizeT();
- public abstract long getPtr();
-
- protected ByteBuffer mBuffer;
- }
-
- /**
- * This class treats size_t and pointer values as 32 bit.
- */
- final class NativeBuffer32 extends NativeBuffer {
- public NativeBuffer32(ByteBuffer buffer) {
- super(buffer);
- }
-
- @Override
- public int getSizeT() {
- return mBuffer.getInt();
- }
- @Override
- public long getPtr() {
- return (long)mBuffer.getInt() & 0x00000000ffffffffL;
- }
- }
-
- /**
- * This class treats size_t and pointer values as 64 bit.
- */
- final class NativeBuffer64 extends NativeBuffer {
- public NativeBuffer64(ByteBuffer buffer) {
- super(buffer);
- }
-
- @Override
- public int getSizeT() {
- return (int)mBuffer.getLong();
- }
- @Override
- public long getPtr() {
- return mBuffer.getLong();
- }
- }
-
- private HandleNativeHeap() {
- }
-
-
- /**
- * Register for the packets we expect to get from the client.
- */
- public static void register(MonitorThread mt) {
- mt.registerChunkHandler(CHUNK_NHGT, mInst);
- mt.registerChunkHandler(CHUNK_NHSG, mInst);
- mt.registerChunkHandler(CHUNK_NHST, mInst);
- mt.registerChunkHandler(CHUNK_NHEN, mInst);
- }
-
- /**
- * Client is ready.
- */
- @Override
- public void clientReady(Client client) throws IOException {}
-
- /**
- * Client went away.
- */
- @Override
- public void clientDisconnected(Client client) {}
-
- /**
- * Chunk handler entry point.
- */
- @Override
- public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
-
- Log.d("ddm-nativeheap", "handling " + ChunkHandler.name(type));
-
- if (type == CHUNK_NHGT) {
- handleNHGT(client, data);
- } else if (type == CHUNK_NHST) {
- // start chunk before any NHSG chunk(s)
- client.getClientData().getNativeHeapData().clearHeapData();
- } else if (type == CHUNK_NHEN) {
- // end chunk after NHSG chunk(s)
- client.getClientData().getNativeHeapData().sealHeapData();
- } else if (type == CHUNK_NHSG) {
- handleNHSG(client, data);
- } else {
- handleUnknownChunk(client, type, data, isReply, msgId);
- }
-
- client.update(Client.CHANGE_NATIVE_HEAP_DATA);
- }
-
- /**
- * Send an NHGT (Native Thread GeT) request to the client.
- */
- public static void sendNHGT(Client client) throws IOException {
-
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data in request message
-
- finishChunkPacket(packet, CHUNK_NHGT, buf.position());
- Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHGT));
- client.sendAndConsume(packet, mInst);
-
- rawBuf = allocBuffer(2);
- packet = new JdwpPacket(rawBuf);
- buf = getChunkDataBuf(rawBuf);
-
- buf.put((byte)HandleHeap.WHEN_DISABLE);
- buf.put((byte)HandleHeap.WHAT_OBJ);
-
- finishChunkPacket(packet, CHUNK_NHSG, buf.position());
- Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHSG));
- client.sendAndConsume(packet, mInst);
- }
-
- /*
- * Handle our native heap data.
- */
- private void handleNHGT(Client client, ByteBuffer data) {
- ClientData clientData = client.getClientData();
-
- Log.d("ddm-nativeheap", "NHGT: " + data.limit() + " bytes");
-
- data.order(ByteOrder.LITTLE_ENDIAN);
-
- // There are two supported header formats.
- //
- // The original version of the header for 32 bit processes:
- //
- // uint32_t mapSize;
- // uint32_t mapSize;
- // uint32_t allocSize;
- // uint32_t allocInfoSize;
- // uint32_t totalMemory;
- // uint32_t backtrace_size;
- //
- // The new header which includes a signature and pointer size:
- //
- // uint32_t signature; (Which is always 0x812345dd)
- // uint16_t version; (Only version 2 of the new format supported)
- // uint16_t pointerSize; (Size in bytes of size_t/pointer values)
- // size_t mapSize;
- // size_t allocSize;
- // size_t allocInfoSize;
- // size_t totalMemory;
- // size_t backtrace_size;
- //
- // If the signature doesn't match, then the code uses the original
- // header format. If the signature matches, then use the new
- // header format with variable sizes of size_t and pointers.
- int signature = data.getInt(0);
- short pointerSize = 4;
- if (signature == 0x812345dd) {
- // Consume signature value.
- int ignore = data.getInt();
- short version = data.getShort();
- if (version != 2) {
- Log.e("ddms", "Unknown header version: " + version);
- return;
- }
- pointerSize = data.getShort();
- }
- NativeBuffer buffer;
- if (pointerSize == 4) {
- buffer = new NativeBuffer32(data);
- } else if (pointerSize == 8) {
- buffer = new NativeBuffer64(data);
- } else {
- Log.e("ddms", "Unknown pointer size: " + pointerSize);
- return;
- }
-
- // clear the previous run
- clientData.clearNativeAllocationInfo();
-
- int mapSize = buffer.getSizeT();
- int allocSize = buffer.getSizeT();
- int allocInfoSize = buffer.getSizeT();
- int totalMemory = buffer.getSizeT();
- int backtraceSize = buffer.getSizeT();
-
- Log.d("ddms", "mapSize: " + mapSize);
- Log.d("ddms", "allocSize: " + allocSize);
- Log.d("ddms", "allocInfoSize: " + allocInfoSize);
- Log.d("ddms", "totalMemory: " + totalMemory);
-
- clientData.setTotalNativeMemory(totalMemory);
-
- // this means that updates aren't turned on.
- if (allocInfoSize == 0) {
- return;
- }
-
- if (mapSize > 0) {
- byte[] maps = new byte[mapSize];
- data.get(maps, 0, mapSize);
- parseMaps(clientData, maps);
- }
-
- int iterations = allocSize / allocInfoSize;
- for (int i = 0 ; i < iterations ; i++) {
- NativeAllocationInfo info = new NativeAllocationInfo(
- buffer.getSizeT() /* size */,
- buffer.getSizeT() /* allocations */);
-
- for (int j = 0 ; j < backtraceSize ; j++) {
- long addr = buffer.getPtr();
- if (addr == 0x0) {
- // skip past null addresses
- continue;
- }
-
- info.addStackCallAddress(addr);
- }
- clientData.addNativeAllocation(info);
- }
- }
-
- private void handleNHSG(Client client, ByteBuffer data) {
- byte dataCopy[] = new byte[data.limit()];
- data.rewind();
- data.get(dataCopy);
- data = ByteBuffer.wrap(dataCopy);
- client.getClientData().getNativeHeapData().addHeapData(data);
-
- if (true) {
- return;
- }
-
- byte[] copy = new byte[data.limit()];
- data.get(copy);
-
- ByteBuffer buffer = ByteBuffer.wrap(copy);
- buffer.order(ByteOrder.BIG_ENDIAN);
-
- int id = buffer.getInt();
- int unitsize = buffer.get();
- long startAddress = buffer.getInt() & 0x00000000ffffffffL;
- int offset = buffer.getInt();
- int allocationUnitCount = buffer.getInt();
-
- // read the usage
- while (buffer.position() < buffer.limit()) {
- int eState = buffer.get() & 0x000000ff;
- int eLen = (buffer.get() & 0x000000ff) + 1;
- }
- }
-
- private void parseMaps(ClientData clientData, byte[] maps) {
- InputStreamReader input = new InputStreamReader(new ByteArrayInputStream(maps));
- BufferedReader reader = new BufferedReader(input);
-
- String line;
-
- try {
- while ((line = reader.readLine()) != null) {
- Log.d("ddms", "line: " + line);
- // Expected format:
- // 7fe51f2000-7fe5213000 rw-p 00000000 00:00 0 [stack]
-
- int library_start = line.lastIndexOf(' ');
- if (library_start == -1) {
- continue;
- }
-
- // Assume that any string that starts with a / is a
- // shared library or executable that we will try to symbolize.
- String library = line.substring(library_start+1);
- if (!library.startsWith("/")) {
- continue;
- }
-
- // Parse the start and end address range.
- int dashIndex = line.indexOf('-');
- int spaceIndex = line.indexOf(' ', dashIndex);
- if (dashIndex == -1 || spaceIndex == -1) {
- continue;
- }
-
- long startAddr = 0;
- long endAddr = 0;
- try {
- startAddr = Long.parseLong(line.substring(0, dashIndex), 16);
- endAddr = Long.parseLong(line.substring(dashIndex+1, spaceIndex), 16);
- } catch (NumberFormatException e) {
- e.printStackTrace();
- continue;
- }
-
- clientData.addNativeLibraryMapInfo(startAddr, endAddr, library);
- Log.d("ddms", library + "(" + Long.toHexString(startAddr) +
- " - " + Long.toHexString(endAddr) + ")");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
-
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java b/base/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
deleted file mode 100644
index ade7f27..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2009 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.ddmlib;
-
-import com.android.ddmlib.ClientData.IMethodProfilingHandler;
-import com.android.ddmlib.ClientData.MethodProfilingStatus;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Handle heap status updates.
- */
-final class HandleProfiling extends ChunkHandler {
-
- public static final int CHUNK_MPRS = type("MPRS");
- public static final int CHUNK_MPRE = type("MPRE");
- public static final int CHUNK_MPSS = type("MPSS");
- public static final int CHUNK_MPSE = type("MPSE");
- public static final int CHUNK_SPSS = type("SPSS");
- public static final int CHUNK_SPSE = type("SPSE");
- public static final int CHUNK_MPRQ = type("MPRQ");
- public static final int CHUNK_FAIL = type("FAIL");
-
- private static final HandleProfiling mInst = new HandleProfiling();
-
- private HandleProfiling() {}
-
- /**
- * Register for the packets we expect to get from the client.
- */
- public static void register(MonitorThread mt) {
- mt.registerChunkHandler(CHUNK_MPRE, mInst);
- mt.registerChunkHandler(CHUNK_MPSE, mInst);
- mt.registerChunkHandler(CHUNK_MPRQ, mInst);
- }
-
- /**
- * Client is ready.
- */
- @Override
- public void clientReady(Client client) throws IOException {}
-
- /**
- * Client went away.
- */
- @Override
- public void clientDisconnected(Client client) {}
-
- /**
- * Chunk handler entry point.
- */
- @Override
- public void handleChunk(Client client, int type, ByteBuffer data,
- boolean isReply, int msgId) {
-
- Log.d("ddm-prof", "handling " + ChunkHandler.name(type));
-
- if (type == CHUNK_MPRE) {
- handleMPRE(client, data);
- } else if (type == CHUNK_MPSE) {
- handleMPSE(client, data);
- } else if (type == CHUNK_MPRQ) {
- handleMPRQ(client, data);
- } else if (type == CHUNK_FAIL) {
- handleFAIL(client, data);
- } else {
- handleUnknownChunk(client, type, data, isReply, msgId);
- }
- }
-
- /**
- * Send a MPRS (Method PRofiling Start) request to the client.
- *
- * The arguments to this method will eventually be passed to
- * android.os.Debug.startMethodTracing() on the device.
- *
- * @param fileName is the name of the file to which profiling data
- * will be written (on the device); it will have {@link DdmConstants#DOT_TRACE}
- * appended if necessary
- * @param bufferSize is the desired buffer size in bytes (8MB is good)
- * @param flags see startMethodTracing() docs; use 0 for default behavior
- */
- public static void sendMPRS(Client client, String fileName, int bufferSize,
- int flags) throws IOException {
-
- ByteBuffer rawBuf = allocBuffer(3*4 + fileName.length() * 2);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.putInt(bufferSize);
- buf.putInt(flags);
- buf.putInt(fileName.length());
- ByteBufferUtil.putString(buf, fileName);
-
- finishChunkPacket(packet, CHUNK_MPRS, buf.position());
- Log.d("ddm-prof", "Sending " + name(CHUNK_MPRS) + " '" + fileName
- + "', size=" + bufferSize + ", flags=" + flags);
- client.sendAndConsume(packet, mInst);
-
- // record the filename we asked for.
- client.getClientData().setPendingMethodProfiling(fileName);
-
- // send a status query. this ensure that the status is properly updated if for some
- // reason starting the tracing failed.
- sendMPRQ(client);
- }
-
- /**
- * Send a MPRE (Method PRofiling End) request to the client.
- */
- public static void sendMPRE(Client client) throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_MPRE, buf.position());
- Log.d("ddm-prof", "Sending " + name(CHUNK_MPRE));
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Handle notification that method profiling has finished writing
- * data to disk.
- */
- private void handleMPRE(Client client, ByteBuffer data) {
- byte result;
-
- // get the filename and make the client not have pending HPROF dump anymore.
- String filename = client.getClientData().getPendingMethodProfiling();
- client.getClientData().setPendingMethodProfiling(null);
-
- result = data.get();
-
- // get the app-level handler for method tracing dump
- IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
- if (handler != null) {
- if (result == 0) {
- handler.onSuccess(filename, client);
-
- Log.d("ddm-prof", "Method profiling has finished");
- } else {
- handler.onEndFailure(client, null /*message*/);
-
- Log.w("ddm-prof", "Method profiling has failed (check device log)");
- }
- }
-
- client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
- client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
- }
-
- /**
- * Send a MPSS (Method Profiling Streaming Start) request to the client.
- *
- * The arguments to this method will eventually be passed to
- * android.os.Debug.startMethodTracing() on the device.
- *
- * @param bufferSize is the desired buffer size in bytes (8MB is good)
- * @param flags see startMethodTracing() docs; use 0 for default behavior
- */
- public static void sendMPSS(Client client, int bufferSize,
- int flags) throws IOException {
-
- ByteBuffer rawBuf = allocBuffer(2*4);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.putInt(bufferSize);
- buf.putInt(flags);
-
- finishChunkPacket(packet, CHUNK_MPSS, buf.position());
- Log.d("ddm-prof", "Sending " + name(CHUNK_MPSS)
- + "', size=" + bufferSize + ", flags=" + flags);
- client.sendAndConsume(packet, mInst);
-
- // send a status query. this ensure that the status is properly updated if for some
- // reason starting the tracing failed.
- sendMPRQ(client);
- }
-
- /**
- * Send a SPSS (Sampling Profiling Streaming Start) request to the client.
- *
- * @param bufferSize is the desired buffer size in bytes (8MB is good)
- * @param samplingInterval sampling interval
- * @param samplingIntervalTimeUnits units for sampling interval
- */
- public static void sendSPSS(Client client, int bufferSize, int samplingInterval,
- TimeUnit samplingIntervalTimeUnits) throws IOException {
- int interval = (int) samplingIntervalTimeUnits.toMicros(samplingInterval);
-
- ByteBuffer rawBuf = allocBuffer(3*4);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.putInt(bufferSize);
- buf.putInt(0); // flags
- buf.putInt(interval);
-
- finishChunkPacket(packet, CHUNK_SPSS, buf.position());
- Log.d("ddm-prof", "Sending " + name(CHUNK_SPSS)
- + "', size=" + bufferSize + ", flags=0, samplingInterval=" + interval);
- client.sendAndConsume(packet, mInst);
-
- // send a status query. this ensure that the status is properly updated if for some
- // reason starting the tracing failed.
- sendMPRQ(client);
- }
-
- /**
- * Send a MPSE (Method Profiling Streaming End) request to the client.
- */
- public static void sendMPSE(Client client) throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_MPSE, buf.position());
- Log.d("ddm-prof", "Sending " + name(CHUNK_MPSE));
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Send a SPSE (Sampling Profiling Streaming End) request to the client.
- */
- public static void sendSPSE(Client client) throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_SPSE, buf.position());
- Log.d("ddm-prof", "Sending " + name(CHUNK_SPSE));
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Handle incoming profiling data. The MPSE packet includes the
- * complete .trace file.
- */
- private void handleMPSE(Client client, ByteBuffer data) {
- IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
- if (handler != null) {
- byte[] stuff = new byte[data.capacity()];
- data.get(stuff, 0, stuff.length);
-
- Log.d("ddm-prof", "got trace file, size: " + stuff.length + " bytes");
-
- handler.onSuccess(stuff, client);
- }
-
- client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
- client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
- }
-
- /**
- * Send a MPRQ (Method PRofiling Query) request to the client.
- */
- public static void sendMPRQ(Client client) throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // no data
-
- finishChunkPacket(packet, CHUNK_MPRQ, buf.position());
- Log.d("ddm-prof", "Sending " + name(CHUNK_MPRQ));
- client.sendAndConsume(packet, mInst);
- }
-
- /**
- * Receive response to query.
- */
- private void handleMPRQ(Client client, ByteBuffer data) {
- byte result;
-
- result = data.get();
-
- if (result == 0) {
- client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
- Log.d("ddm-prof", "Method profiling is not running");
- } else if (result == 1) {
- client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.TRACER_ON);
- Log.d("ddm-prof", "Method tracing is active");
- } else if (result == 2) {
- client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.SAMPLER_ON);
- Log.d("ddm-prof", "Sampler based profiling is active");
- }
- client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
- }
-
- private void handleFAIL(Client client, ByteBuffer data) {
- /*int errorCode =*/ data.getInt();
- int length = data.getInt() * 2;
- String message = null;
- if (length > 0) {
- byte[] messageBuffer = new byte[length];
- data.get(messageBuffer, 0, length);
- message = new String(messageBuffer);
- }
-
- // this can be sent if
- // - MPRS failed (like wrong permission)
- // - MPSE failed for whatever reason
-
- String filename = client.getClientData().getPendingMethodProfiling();
- if (filename != null) {
- // reset the pending file.
- client.getClientData().setPendingMethodProfiling(null);
-
- // and notify of failure
- IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
- if (handler != null) {
- handler.onStartFailure(client, message);
- }
- } else {
- // this is MPRE
- // notify of failure
- IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
- if (handler != null) {
- handler.onEndFailure(client, message);
- }
- }
-
- // send a query to know the current status
- try {
- sendMPRQ(client);
- } catch (IOException e) {
- Log.e("HandleProfiling", e);
- }
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java b/base/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java
deleted file mode 100644
index f3874ce..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Handle thread status updates.
- */
-final class HandleThread extends ChunkHandler {
-
- public static final int CHUNK_THEN = type("THEN");
- public static final int CHUNK_THCR = type("THCR");
- public static final int CHUNK_THDE = type("THDE");
- public static final int CHUNK_THST = type("THST");
- public static final int CHUNK_THNM = type("THNM");
- public static final int CHUNK_STKL = type("STKL");
-
- private static final HandleThread mInst = new HandleThread();
-
- // only read/written by requestThreadUpdates()
- private static volatile boolean sThreadStatusReqRunning = false;
- private static volatile boolean sThreadStackTraceReqRunning = false;
-
- private HandleThread() {}
-
-
- /**
- * Register for the packets we expect to get from the client.
- */
- public static void register(MonitorThread mt) {
- mt.registerChunkHandler(CHUNK_THCR, mInst);
- mt.registerChunkHandler(CHUNK_THDE, mInst);
- mt.registerChunkHandler(CHUNK_THST, mInst);
- mt.registerChunkHandler(CHUNK_THNM, mInst);
- mt.registerChunkHandler(CHUNK_STKL, mInst);
- }
-
- /**
- * Client is ready.
- */
- @Override
- public void clientReady(Client client) throws IOException {
- Log.d("ddm-thread", "Now ready: " + client);
- if (client.isThreadUpdateEnabled())
- sendTHEN(client, true);
- }
-
- /**
- * Client went away.
- */
- @Override
- public void clientDisconnected(Client client) {}
-
- /**
- * Chunk handler entry point.
- */
- @Override
- public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
-
- Log.d("ddm-thread", "handling " + ChunkHandler.name(type));
-
- if (type == CHUNK_THCR) {
- handleTHCR(client, data);
- } else if (type == CHUNK_THDE) {
- handleTHDE(client, data);
- } else if (type == CHUNK_THST) {
- handleTHST(client, data);
- } else if (type == CHUNK_THNM) {
- handleTHNM(client, data);
- } else if (type == CHUNK_STKL) {
- handleSTKL(client, data);
- } else {
- handleUnknownChunk(client, type, data, isReply, msgId);
- }
- }
-
- /*
- * Handle a thread creation message.
- *
- * We should be tolerant of receiving a duplicate create message. (It
- * shouldn't happen with the current implementation.)
- */
- private void handleTHCR(Client client, ByteBuffer data) {
- int threadId, nameLen;
- String name;
-
- threadId = data.getInt();
- nameLen = data.getInt();
- name = ByteBufferUtil.getString(data, nameLen);
-
- Log.v("ddm-thread", "THCR: " + threadId + " '" + name + "'");
-
- client.getClientData().addThread(threadId, name);
- client.update(Client.CHANGE_THREAD_DATA);
- }
-
- /*
- * Handle a thread death message.
- */
- private void handleTHDE(Client client, ByteBuffer data) {
- int threadId;
-
- threadId = data.getInt();
- Log.v("ddm-thread", "THDE: " + threadId);
-
- client.getClientData().removeThread(threadId);
- client.update(Client.CHANGE_THREAD_DATA);
- }
-
- /*
- * Handle a thread status update message.
- *
- * Response has:
- * (1b) header len
- * (1b) bytes per entry
- * (2b) thread count
- * Then, for each thread:
- * (4b) threadId (matches value from THCR)
- * (1b) thread status
- * (4b) tid
- * (4b) utime
- * (4b) stime
- */
- private void handleTHST(Client client, ByteBuffer data) {
- int headerLen, bytesPerEntry, extraPerEntry;
- int threadCount;
-
- headerLen = (data.get() & 0xff);
- bytesPerEntry = (data.get() & 0xff);
- threadCount = data.getShort();
-
- headerLen -= 4; // we've read 4 bytes
- while (headerLen-- > 0)
- data.get();
-
- extraPerEntry = bytesPerEntry - 18; // we want 18 bytes
-
- Log.v("ddm-thread", "THST: threadCount=" + threadCount);
-
- /*
- * For each thread, extract the data, find the appropriate
- * client, and add it to the ClientData.
- */
- for (int i = 0; i < threadCount; i++) {
- int threadId, status, tid, utime, stime;
- boolean isDaemon = false;
-
- threadId = data.getInt();
- status = data.get();
- tid = data.getInt();
- utime = data.getInt();
- stime = data.getInt();
- if (bytesPerEntry >= 18)
- isDaemon = (data.get() != 0);
-
- Log.v("ddm-thread", " id=" + threadId
- + ", status=" + status + ", tid=" + tid
- + ", utime=" + utime + ", stime=" + stime);
-
- ClientData cd = client.getClientData();
- ThreadInfo threadInfo = cd.getThread(threadId);
- if (threadInfo != null)
- threadInfo.updateThread(status, tid, utime, stime, isDaemon);
- else
- Log.d("ddms", "Thread with id=" + threadId + " not found");
-
- // slurp up any extra
- for (int slurp = extraPerEntry; slurp > 0; slurp--)
- data.get();
- }
-
- client.update(Client.CHANGE_THREAD_DATA);
- }
-
- /*
- * Handle a THNM (THread NaMe) message. We get one of these after
- * somebody calls Thread.setName() on a running thread.
- */
- private void handleTHNM(Client client, ByteBuffer data) {
- int threadId, nameLen;
- String name;
-
- threadId = data.getInt();
- nameLen = data.getInt();
- name = ByteBufferUtil.getString(data, nameLen);
-
- Log.v("ddm-thread", "THNM: " + threadId + " '" + name + "'");
-
- ThreadInfo threadInfo = client.getClientData().getThread(threadId);
- if (threadInfo != null) {
- threadInfo.setThreadName(name);
- client.update(Client.CHANGE_THREAD_DATA);
- } else {
- Log.d("ddms", "Thread with id=" + threadId + " not found");
- }
- }
-
-
- /**
- * Parse an incoming STKL.
- */
- private void handleSTKL(Client client, ByteBuffer data) {
- StackTraceElement[] trace;
- int i, threadId, stackDepth;
- @SuppressWarnings("unused")
- int future;
-
- future = data.getInt();
- threadId = data.getInt();
-
- Log.v("ddms", "STKL: " + threadId);
-
- /* un-serialize the StackTraceElement[] */
- stackDepth = data.getInt();
- trace = new StackTraceElement[stackDepth];
- for (i = 0; i < stackDepth; i++) {
- String className, methodName, fileName;
- int len, lineNumber;
-
- len = data.getInt();
- className = ByteBufferUtil.getString(data, len);
- len = data.getInt();
- methodName = ByteBufferUtil.getString(data, len);
- len = data.getInt();
- if (len == 0) {
- fileName = null;
- } else {
- fileName = ByteBufferUtil.getString(data, len);
- }
- lineNumber = data.getInt();
-
- trace[i] = new StackTraceElement(className, methodName, fileName,
- lineNumber);
- }
-
- ThreadInfo threadInfo = client.getClientData().getThread(threadId);
- if (threadInfo != null) {
- threadInfo.setStackCall(trace);
- client.update(Client.CHANGE_THREAD_STACKTRACE);
- } else {
- Log.d("STKL", String.format(
- "Got stackcall for thread %1$d, which does not exists (anymore?).", //$NON-NLS-1$
- threadId));
- }
- }
-
-
- /**
- * Send a THEN (THread notification ENable) request to the client.
- */
- public static void sendTHEN(Client client, boolean enable)
- throws IOException {
-
- ByteBuffer rawBuf = allocBuffer(1);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- if (enable)
- buf.put((byte)1);
- else
- buf.put((byte)0);
-
- finishChunkPacket(packet, CHUNK_THEN, buf.position());
- Log.d("ddm-thread", "Sending " + name(CHUNK_THEN) + ": " + enable);
- client.sendAndConsume(packet, mInst);
- }
-
-
- /**
- * Send a STKL (STacK List) request to the client. The VM will suspend
- * the target thread, obtain its stack, and return it. If the thread
- * is no longer running, a failure result will be returned.
- */
- public static void sendSTKL(Client client, int threadId)
- throws IOException {
-
- if (false) {
- Log.d("ddm-thread", "would send STKL " + threadId);
- return;
- }
-
- ByteBuffer rawBuf = allocBuffer(4);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- buf.putInt(threadId);
-
- finishChunkPacket(packet, CHUNK_STKL, buf.position());
- Log.d("ddm-thread", "Sending " + name(CHUNK_STKL) + ": " + threadId);
- client.sendAndConsume(packet, mInst);
- }
-
-
- /**
- * This is called periodically from the UI thread. To avoid locking
- * the UI while we request the updates, we create a new thread.
- *
- */
- static void requestThreadUpdate(final Client client) {
- if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
- if (sThreadStatusReqRunning) {
- Log.w("ddms", "Waiting for previous thread update req to finish");
- return;
- }
-
- new Thread("Thread Status Req") {
- @Override
- public void run() {
- sThreadStatusReqRunning = true;
- try {
- sendTHST(client);
- } catch (IOException ioe) {
- Log.d("ddms", "Unable to request thread updates from "
- + client + ": " + ioe.getMessage());
- } finally {
- sThreadStatusReqRunning = false;
- }
- }
- }.start();
- }
- }
-
- static void requestThreadStackCallRefresh(final Client client, final int threadId) {
- if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
- if (sThreadStackTraceReqRunning) {
- Log.w("ddms", "Waiting for previous thread stack call req to finish");
- return;
- }
-
- new Thread("Thread Status Req") {
- @Override
- public void run() {
- sThreadStackTraceReqRunning = true;
- try {
- sendSTKL(client, threadId);
- } catch (IOException ioe) {
- Log.d("ddms", "Unable to request thread stack call updates from "
- + client + ": " + ioe.getMessage());
- } finally {
- sThreadStackTraceReqRunning = false;
- }
- }
- }.start();
- }
-
- }
-
- /*
- * Send a THST request to the specified client.
- */
- private static void sendTHST(Client client) throws IOException {
- ByteBuffer rawBuf = allocBuffer(0);
- JdwpPacket packet = new JdwpPacket(rawBuf);
- ByteBuffer buf = getChunkDataBuf(rawBuf);
-
- // nothing much to say
-
- finishChunkPacket(packet, CHUNK_THST, buf.position());
- Log.d("ddm-thread", "Sending " + name(CHUNK_THST));
- client.sendAndConsume(packet, mInst);
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java b/base/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java
deleted file mode 100644
index 4378350..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public final class HandleViewDebug extends ChunkHandler {
- /** Enable/Disable tracing of OpenGL calls. */
- public static final int CHUNK_VUGL = type("VUGL");
-
- /** List {@link ViewRootImpl}'s of this process. */
- public static final int CHUNK_VULW = type("VULW");
-
- /** Operation on view root, first parameter in packet should be one of VURT_* constants */
- public static final int CHUNK_VURT = type("VURT");
-
- /** Dump view hierarchy. */
- private static final int VURT_DUMP_HIERARCHY = 1;
-
- /** Capture View Layers. */
- private static final int VURT_CAPTURE_LAYERS = 2;
-
- /** Dump View Theme. */
- private static final int VURT_DUMP_THEME = 3;
-
- /**
- * Generic View Operation, first parameter in the packet should be one of the
- * VUOP_* constants below.
- */
- public static final int CHUNK_VUOP = type("VUOP");
-
- /** Capture View. */
- private static final int VUOP_CAPTURE_VIEW = 1;
-
- /** Obtain the Display List corresponding to the view. */
- private static final int VUOP_DUMP_DISPLAYLIST = 2;
-
- /** Profile a view. */
- private static final int VUOP_PROFILE_VIEW = 3;
-
- /** Invoke a method on the view. */
- private static final int VUOP_INVOKE_VIEW_METHOD = 4;
-
- /** Set layout parameter. */
- private static final int VUOP_SET_LAYOUT_PARAMETER = 5;
-
- private static final String TAG = "ddmlib"; //$NON-NLS-1$
-
- private static final HandleViewDebug sInstance = new HandleViewDebug();
-
- private static final ViewDumpHandler sViewOpNullChunkHandler =
- new NullChunkHandler(CHUNK_VUOP);
-
- private HandleViewDebug() {}
-
- public static void register(MonitorThread mt) {
- // TODO: add chunk type for auto window updates
- // and register here
- mt.registerChunkHandler(CHUNK_VUGL, sInstance);
- mt.registerChunkHandler(CHUNK_VULW, sInstance);
- mt.registerChunkHandler(CHUNK_VUOP, sInstance);
- mt.registerChunkHandler(CHUNK_VURT, sInstance);
- }
-
- @Override
- public void clientReady(Client client) throws IOException {}
-
- @Override
- public void clientDisconnected(Client client) {}
-
- public abstract static class ViewDumpHandler extends ChunkHandler {
- private final CountDownLatch mLatch = new CountDownLatch(1);
- private final int mChunkType;
-
- public ViewDumpHandler(int chunkType) {
- mChunkType = chunkType;
- }
-
- @Override
- void clientReady(Client client) throws IOException {
- }
-
- @Override
- void clientDisconnected(Client client) {
- }
-
- @Override
- void handleChunk(Client client, int type, ByteBuffer data,
- boolean isReply, int msgId) {
- if (type != mChunkType) {
- handleUnknownChunk(client, type, data, isReply, msgId);
- return;
- }
-
- handleViewDebugResult(data);
- mLatch.countDown();
- }
-
- protected abstract void handleViewDebugResult(ByteBuffer data);
-
- protected void waitForResult(long timeout, TimeUnit unit) {
- try {
- mLatch.await(timeout, unit);
- } catch (InterruptedException e) {
- // pass
- }
- }
- }
-
- public static void listViewRoots(Client client, ViewDumpHandler replyHandler)
- throws IOException {
- ByteBuffer buf = allocBuffer(8);
- JdwpPacket packet = new JdwpPacket(buf);
- ByteBuffer chunkBuf = getChunkDataBuf(buf);
- chunkBuf.putInt(1);
- finishChunkPacket(packet, CHUNK_VULW, chunkBuf.position());
- client.sendAndConsume(packet, replyHandler);
- }
-
- public static void dumpViewHierarchy(@NonNull Client client, @NonNull String viewRoot,
- boolean skipChildren, boolean includeProperties, @NonNull ViewDumpHandler handler)
- throws IOException {
- ByteBuffer buf = allocBuffer(4 // opcode
- + 4 // view root length
- + viewRoot.length() * 2 // view root
- + 4 // skip children
- + 4); // include view properties
- JdwpPacket packet = new JdwpPacket(buf);
- ByteBuffer chunkBuf = getChunkDataBuf(buf);
-
- chunkBuf.putInt(VURT_DUMP_HIERARCHY);
- chunkBuf.putInt(viewRoot.length());
- ByteBufferUtil.putString(chunkBuf, viewRoot);
- chunkBuf.putInt(skipChildren ? 1 : 0);
- chunkBuf.putInt(includeProperties ? 1 : 0);
-
- finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
- client.sendAndConsume(packet, handler);
- }
-
- public static void captureLayers(@NonNull Client client, @NonNull String viewRoot,
- @NonNull ViewDumpHandler handler) throws IOException {
- int bufLen = 8 + viewRoot.length() * 2;
-
- ByteBuffer buf = allocBuffer(bufLen);
- JdwpPacket packet = new JdwpPacket(buf);
- ByteBuffer chunkBuf = getChunkDataBuf(buf);
-
- chunkBuf.putInt(VURT_CAPTURE_LAYERS);
- chunkBuf.putInt(viewRoot.length());
- ByteBufferUtil.putString(chunkBuf, viewRoot);
-
- finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
- client.sendAndConsume(packet, handler);
- }
-
- private static void sendViewOpPacket(@NonNull Client client, int op, @NonNull String viewRoot,
- @NonNull String view, @Nullable byte[] extra, @Nullable ViewDumpHandler handler)
- throws IOException {
- int bufLen = 4 + // opcode
- 4 + viewRoot.length() * 2 + // view root strlen + view root
- 4 + view.length() * 2; // view strlen + view
-
- if (extra != null) {
- bufLen += extra.length;
- }
-
- ByteBuffer buf = allocBuffer(bufLen);
- JdwpPacket packet = new JdwpPacket(buf);
- ByteBuffer chunkBuf = getChunkDataBuf(buf);
-
- chunkBuf.putInt(op);
- chunkBuf.putInt(viewRoot.length());
- ByteBufferUtil.putString(chunkBuf, viewRoot);
-
- chunkBuf.putInt(view.length());
- ByteBufferUtil.putString(chunkBuf, view);
-
- if (extra != null) {
- chunkBuf.put(extra);
- }
-
- finishChunkPacket(packet, CHUNK_VUOP, chunkBuf.position());
- if (handler != null) {
- client.sendAndConsume(packet, handler);
- } else {
- client.sendAndConsume(packet);
- }
- }
-
- public static void profileView(@NonNull Client client, @NonNull String viewRoot,
- @NonNull String view, @NonNull ViewDumpHandler handler) throws IOException {
- sendViewOpPacket(client, VUOP_PROFILE_VIEW, viewRoot, view, null, handler);
- }
-
- public static void captureView(@NonNull Client client, @NonNull String viewRoot,
- @NonNull String view, @NonNull ViewDumpHandler handler) throws IOException {
- sendViewOpPacket(client, VUOP_CAPTURE_VIEW, viewRoot, view, null, handler);
- }
-
- public static void invalidateView(@NonNull Client client, @NonNull String viewRoot,
- @NonNull String view) throws IOException {
- invokeMethod(client, viewRoot, view, "invalidate");
- }
-
- public static void requestLayout(@NonNull Client client, @NonNull String viewRoot,
- @NonNull String view) throws IOException {
- invokeMethod(client, viewRoot, view, "requestLayout");
- }
-
- public static void dumpDisplayList(@NonNull Client client, @NonNull String viewRoot,
- @NonNull String view) throws IOException {
- sendViewOpPacket(client, VUOP_DUMP_DISPLAYLIST, viewRoot, view, null,
- sViewOpNullChunkHandler);
- }
-
- public static void dumpTheme(@NonNull Client client, @NonNull String viewRoot,
- @NonNull ViewDumpHandler handler)
- throws IOException {
- ByteBuffer buf = allocBuffer(4 // opcode
- + 4 // view root length
- + viewRoot.length() * 2); // view root
- JdwpPacket packet = new JdwpPacket(buf);
- ByteBuffer chunkBuf = getChunkDataBuf(buf);
-
- chunkBuf.putInt(VURT_DUMP_THEME);
- chunkBuf.putInt(viewRoot.length());
- ByteBufferUtil.putString(chunkBuf, viewRoot);
-
- finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
- client.sendAndConsume(packet, handler);
- }
-
- /** A {@link ViewDumpHandler} to use when no response is expected. */
- private static class NullChunkHandler extends ViewDumpHandler {
- public NullChunkHandler(int chunkType) {
- super(chunkType);
- }
-
- @Override
- protected void handleViewDebugResult(ByteBuffer data) {
- }
- }
-
- public static void invokeMethod(@NonNull Client client, @NonNull String viewRoot,
- @NonNull String view, @NonNull String method, Object... args) throws IOException {
- int len = 4 + method.length() * 2;
- if (args != null) {
- // # of args
- len += 4;
-
- // for each argument, we send a char type specifier (2 bytes) and
- // the arg value (max primitive size = sizeof(double) = 8
- len += 10 * args.length;
- }
-
- byte[] extra = new byte[len];
- ByteBuffer b = ByteBuffer.wrap(extra);
-
- b.putInt(method.length());
- ByteBufferUtil.putString(b, method);
-
- if (args != null) {
- b.putInt(args.length);
-
- for (int i = 0; i < args.length; i++) {
- Object arg = args[i];
- if (arg instanceof Boolean) {
- b.putChar('Z');
- b.put((byte) ((Boolean) arg ? 1 : 0));
- } else if (arg instanceof Byte) {
- b.putChar('B');
- b.put((Byte) arg);
- } else if (arg instanceof Character) {
- b.putChar('C');
- b.putChar((Character) arg);
- } else if (arg instanceof Short) {
- b.putChar('S');
- b.putShort((Short) arg);
- } else if (arg instanceof Integer) {
- b.putChar('I');
- b.putInt((Integer) arg);
- } else if (arg instanceof Long) {
- b.putChar('J');
- b.putLong((Long) arg);
- } else if (arg instanceof Float) {
- b.putChar('F');
- b.putFloat((Float) arg);
- } else if (arg instanceof Double) {
- b.putChar('D');
- b.putDouble((Double) arg);
- } else {
- Log.e(TAG, "View method invocation only supports primitive arguments, supplied: " + arg);
- return;
- }
- }
- }
-
- sendViewOpPacket(client, VUOP_INVOKE_VIEW_METHOD, viewRoot, view, extra,
- sViewOpNullChunkHandler );
- }
-
- public static void setLayoutParameter(@NonNull Client client, @NonNull String viewRoot,
- @NonNull String view, @NonNull String parameter, int value) throws IOException {
- int len = 4 + parameter.length() * 2 + 4;
- byte[] extra = new byte[len];
- ByteBuffer b = ByteBuffer.wrap(extra);
-
- b.putInt(parameter.length());
- ByteBufferUtil.putString(b, parameter);
- b.putInt(value);
- sendViewOpPacket(client, VUOP_SET_LAYOUT_PARAMETER, viewRoot, view, extra,
- sViewOpNullChunkHandler);
- }
-
- @Override
- public void handleChunk(Client client, int type, ByteBuffer data,
- boolean isReply, int msgId) {
- }
-
- public static void sendStartGlTracing(Client client) throws IOException {
- ByteBuffer buf = allocBuffer(4);
- JdwpPacket packet = new JdwpPacket(buf);
-
- ByteBuffer chunkBuf = getChunkDataBuf(buf);
- chunkBuf.putInt(1);
- finishChunkPacket(packet, CHUNK_VUGL, chunkBuf.position());
-
- client.sendAndConsume(packet);
- }
-
- public static void sendStopGlTracing(Client client) throws IOException {
- ByteBuffer buf = allocBuffer(4);
- JdwpPacket packet = new JdwpPacket(buf);
-
- ByteBuffer chunkBuf = getChunkDataBuf(buf);
- chunkBuf.putInt(0);
- finishChunkPacket(packet, CHUNK_VUGL, chunkBuf.position());
-
- client.sendAndConsume(packet);
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/IDevice.java b/base/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
deleted file mode 100644
index be45449..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
+++ /dev/null
@@ -1,639 +0,0 @@
-/*
- * Copyright (C) 2008 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.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ddmlib.log.LogReceiver;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Device. It can be a physical device or an emulator.
- */
-public interface IDevice extends IShellEnabledDevice {
-
- String PROP_BUILD_VERSION = "ro.build.version.release";
- String PROP_BUILD_API_LEVEL = "ro.build.version.sdk";
- String PROP_BUILD_CODENAME = "ro.build.version.codename";
- String PROP_BUILD_TAGS = "ro.build.tags";
- String PROP_BUILD_TYPE = "ro.build.type";
- String PROP_DEVICE_MODEL = "ro.product.model";
- String PROP_DEVICE_MANUFACTURER = "ro.product.manufacturer";
- String PROP_DEVICE_CPU_ABI_LIST = "ro.product.cpu.abilist";
- String PROP_DEVICE_CPU_ABI = "ro.product.cpu.abi";
- String PROP_DEVICE_CPU_ABI2 = "ro.product.cpu.abi2";
- String PROP_BUILD_CHARACTERISTICS = "ro.build.characteristics";
- String PROP_DEVICE_DENSITY = "ro.sf.lcd_density";
- String PROP_DEVICE_LANGUAGE = "persist.sys.language";
- String PROP_DEVICE_REGION = "persist.sys.country";
-
- String PROP_DEBUGGABLE = "ro.debuggable";
-
- /** Serial number of the first connected emulator. */
- String FIRST_EMULATOR_SN = "emulator-5554"; //$NON-NLS-1$
- /** Device change bit mask: {@link DeviceState} change. */
- int CHANGE_STATE = 0x0001;
- /** Device change bit mask: {@link Client} list change. */
- int CHANGE_CLIENT_LIST = 0x0002;
- /** Device change bit mask: build info change. */
- int CHANGE_BUILD_INFO = 0x0004;
-
- /** Device level software features. */
- enum Feature {
- SCREEN_RECORD, // screen recorder available?
- PROCSTATS, // procstats service (dumpsys procstats) available
- }
-
- /** Device level hardware features. */
- enum HardwareFeature {
- WATCH("watch"),
- TV("tv");
-
- private final String mCharacteristic;
-
- HardwareFeature(String characteristic) {
- mCharacteristic = characteristic;
- }
-
- public String getCharacteristic() {
- return mCharacteristic;
- }
- }
-
- /** @deprecated Use {@link #PROP_BUILD_API_LEVEL}. */
- @Deprecated
- String PROP_BUILD_VERSION_NUMBER = PROP_BUILD_API_LEVEL;
-
- String MNT_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"; //$NON-NLS-1$
- String MNT_ROOT = "ANDROID_ROOT"; //$NON-NLS-1$
- String MNT_DATA = "ANDROID_DATA"; //$NON-NLS-1$
-
- /**
- * The state of a device.
- */
- enum DeviceState {
- BOOTLOADER("bootloader"), //$NON-NLS-1$
- OFFLINE("offline"), //$NON-NLS-1$
- ONLINE("device"), //$NON-NLS-1$
- RECOVERY("recovery"), //$NON-NLS-1$
- UNAUTHORIZED("unauthorized"); //$NON-NLS-1$
-
- private String mState;
-
- DeviceState(String state) {
- mState = state;
- }
-
- /**
- * Returns a {@link DeviceState} from the string returned by <code>adb devices</code>.
- *
- * @param state the device state.
- * @return a {@link DeviceState} object or <code>null</code> if the state is unknown.
- */
- @Nullable
- public static DeviceState getState(String state) {
- for (DeviceState deviceState : values()) {
- if (deviceState.mState.equals(state)) {
- return deviceState;
- }
- }
- return null;
- }
- }
-
- /**
- * Namespace of a Unix Domain Socket created on the device.
- */
- enum DeviceUnixSocketNamespace {
- ABSTRACT("localabstract"), //$NON-NLS-1$
- FILESYSTEM("localfilesystem"), //$NON-NLS-1$
- RESERVED("localreserved"); //$NON-NLS-1$
-
- private String mType;
-
- DeviceUnixSocketNamespace(String type) {
- mType = type;
- }
-
- String getType() {
- return mType;
- }
- }
-
- /** Returns the serial number of the device. */
- @NonNull
- String getSerialNumber();
-
- /**
- * Returns the name of the AVD the emulator is running.
- * <p/>This is only valid if {@link #isEmulator()} returns true.
- * <p/>If the emulator is not running any AVD (for instance it's running from an Android source
- * tree build), this method will return "<code><build></code>".
- *
- * @return the name of the AVD or <code>null</code> if there isn't any.
- */
- @Nullable
- String getAvdName();
-
- /**
- * Returns the state of the device.
- */
- DeviceState getState();
-
- /**
- * Returns the cached device properties. It contains the whole output of 'getprop'
- *
- * @deprecated use {@link #getSystemProperty(String)} instead
- */
- @Deprecated
- Map<String, String> getProperties();
-
- /**
- * Returns the number of property for this device.
- *
- * @deprecated implementation detail
- */
- @Deprecated
- int getPropertyCount();
-
- /**
- * Convenience method that attempts to retrieve a property via
- * {@link #getSystemProperty(String)} with minimal wait time, and swallows exceptions.
- *
- * @param name the name of the value to return.
- * @return the value or <code>null</code> if the property value was not immediately available
- */
- @Nullable
- String getProperty(@NonNull String name);
-
- /**
- * Returns <code>true></code> if properties have been cached
- */
- boolean arePropertiesSet();
-
- /**
- * A variant of {@link #getProperty(String)} that will attempt to retrieve the given
- * property from device directly, without using cache.
- * This method should (only) be used for any volatile properties.
- *
- * @param name the name of the value to return.
- * @return the value or <code>null</code> if the property does not exist
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws ShellCommandUnresponsiveException in case the shell command doesn't send output for a
- * given time.
- * @throws IOException in case of I/O error on the connection.
- * @deprecated use {@link #getSystemProperty(String)}
- */
- @Deprecated
- String getPropertySync(String name) throws TimeoutException,
- AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
-
- /**
- * A combination of {@link #getProperty(String)} and {@link #getPropertySync(String)} that
- * will attempt to retrieve the property from cache. If not found, will synchronously
- * attempt to query device directly and repopulate the cache if successful.
- *
- * @param name the name of the value to return.
- * @return the value or <code>null</code> if the property does not exist
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws ShellCommandUnresponsiveException in case the shell command doesn't send output for a
- * given time.
- * @throws IOException in case of I/O error on the connection.
- * @deprecated use {@link #getSystemProperty(String)} instead
- */
- @Deprecated
- String getPropertyCacheOrSync(String name) throws TimeoutException,
- AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
-
- /** Returns whether this device supports the given software feature. */
- boolean supportsFeature(@NonNull Feature feature);
-
- /** Returns whether this device supports the given hardware feature. */
- boolean supportsFeature(@NonNull HardwareFeature feature);
-
- /**
- * Returns a mount point.
- *
- * @param name the name of the mount point to return
- *
- * @see #MNT_EXTERNAL_STORAGE
- * @see #MNT_ROOT
- * @see #MNT_DATA
- */
- @Nullable
- String getMountPoint(@NonNull String name);
-
- /**
- * Returns if the device is ready.
- *
- * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#ONLINE}.
- */
- boolean isOnline();
-
- /**
- * Returns <code>true</code> if the device is an emulator.
- */
- boolean isEmulator();
-
- /**
- * Returns if the device is offline.
- *
- * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#OFFLINE}.
- */
- boolean isOffline();
-
- /**
- * Returns if the device is in bootloader mode.
- *
- * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#BOOTLOADER}.
- */
- boolean isBootLoader();
-
- /**
- * Returns whether the {@link Device} has {@link Client}s.
- */
- boolean hasClients();
-
- /**
- * Returns the array of clients.
- */
- Client[] getClients();
-
- /**
- * Returns a {@link Client} by its application name.
- *
- * @param applicationName the name of the application
- * @return the <code>Client</code> object or <code>null</code> if no match was found.
- */
- Client getClient(String applicationName);
-
- /**
- * Returns a {@link SyncService} object to push / pull files to and from the device.
- *
- * @return <code>null</code> if the SyncService couldn't be created. This can happen if adb
- * refuse to open the connection because the {@link IDevice} is invalid
- * (or got disconnected).
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException if the connection with adb failed.
- */
- SyncService getSyncService()
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Returns a {@link FileListingService} for this device.
- */
- FileListingService getFileListingService();
-
- /**
- * Takes a screen shot of the device and returns it as a {@link RawImage}.
- *
- * @return the screenshot as a <code>RawImage</code> or <code>null</code> if something
- * went wrong.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- RawImage getScreenshot() throws TimeoutException, AdbCommandRejectedException, IOException;
-
- RawImage getScreenshot(long timeout, TimeUnit unit)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Initiates screen recording on the device if the device supports {@link Feature#SCREEN_RECORD}.
- */
- void startScreenRecorder(@NonNull String remoteFilePath,
- @NonNull ScreenRecorderOptions options, @NonNull IShellOutputReceiver receiver) throws
- TimeoutException, AdbCommandRejectedException, IOException,
- ShellCommandUnresponsiveException;
-
- /**
- * @deprecated Use {@link #executeShellCommand(String, IShellOutputReceiver, long, java.util.concurrent.TimeUnit)}.
- */
- @Deprecated
- void executeShellCommand(String command, IShellOutputReceiver receiver,
- int maxTimeToOutputResponse)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException;
-
- /**
- * Executes a shell command on the device, and sends the result to a <var>receiver</var>
- * <p/>This is similar to calling
- * <code>executeShellCommand(command, receiver, DdmPreferences.getTimeOut())</code>.
- *
- * @param command the shell command to execute
- * @param receiver the {@link IShellOutputReceiver} that will receives the output of the shell
- * command
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws ShellCommandUnresponsiveException in case the shell command doesn't send output
- * for a given time.
- * @throws IOException in case of I/O error on the connection.
- *
- * @see #executeShellCommand(String, IShellOutputReceiver, int)
- * @see DdmPreferences#getTimeOut()
- */
- void executeShellCommand(String command, IShellOutputReceiver receiver)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException;
-
- /**
- * Runs the event log service and outputs the event log to the {@link LogReceiver}.
- * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
- * @param receiver the receiver to receive the event log entries.
- * @throws TimeoutException in case of timeout on the connection. This can only be thrown if the
- * timeout happens during setup. Once logs start being received, no timeout will occur as it's
- * not possible to detect a difference between no log and timeout.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- void runEventLogService(LogReceiver receiver)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Runs the log service for the given log and outputs the log to the {@link LogReceiver}.
- * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
- *
- * @param logname the logname of the log to read from.
- * @param receiver the receiver to receive the event log entries.
- * @throws TimeoutException in case of timeout on the connection. This can only be thrown if the
- * timeout happens during setup. Once logs start being received, no timeout will
- * occur as it's not possible to detect a difference between no log and timeout.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- void runLogService(String logname, LogReceiver receiver)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Creates a port forwarding between a local and a remote port.
- *
- * @param localPort the local port to forward
- * @param remotePort the remote port.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- void createForward(int localPort, int remotePort)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Creates a port forwarding between a local TCP port and a remote Unix Domain Socket.
- *
- * @param localPort the local port to forward
- * @param remoteSocketName name of the unix domain socket created on the device
- * @param namespace namespace in which the unix domain socket was created
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- void createForward(int localPort, String remoteSocketName,
- DeviceUnixSocketNamespace namespace)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Removes a port forwarding between a local and a remote port.
- *
- * @param localPort the local port to forward
- * @param remotePort the remote port.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- void removeForward(int localPort, int remotePort)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Removes an existing port forwarding between a local and a remote port.
- *
- * @param localPort the local port to forward
- * @param remoteSocketName the remote unix domain socket name.
- * @param namespace namespace in which the unix domain socket was created
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- */
- void removeForward(int localPort, String remoteSocketName,
- DeviceUnixSocketNamespace namespace)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Returns the name of the client by pid or <code>null</code> if pid is unknown
- * @param pid the pid of the client.
- */
- String getClientName(int pid);
-
- /**
- * Push a single file.
- * @param local the local filepath.
- * @param remote The remote filepath.
- *
- * @throws IOException in case of I/O error on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws TimeoutException in case of a timeout reading responses from the device.
- * @throws SyncException if file could not be pushed
- */
- void pushFile(String local, String remote)
- throws IOException, AdbCommandRejectedException, TimeoutException, SyncException;
-
- /**
- * Pulls a single file.
- *
- * @param remote the full path to the remote file
- * @param local The local destination.
- *
- * @throws IOException in case of an IO exception.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws TimeoutException in case of a timeout reading responses from the device.
- * @throws SyncException in case of a sync exception.
- */
- void pullFile(String remote, String local)
- throws IOException, AdbCommandRejectedException, TimeoutException, SyncException;
-
- /**
- * Installs an Android application on device. This is a helper method that combines the
- * syncPackageToDevice, installRemotePackage, and removePackage steps
- *
- * @param packageFilePath the absolute file system path to file on local host to install
- * @param reinstall set to <code>true</code> if re-install of app should be performed
- * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
- * available options.
- * @return a {@link String} with an error code, or <code>null</code> if success.
- * @throws InstallException if the installation fails.
- */
- String installPackage(String packageFilePath, boolean reinstall, String... extraArgs)
- throws InstallException;
-
- /**
- * Installs an Android application made of serveral APK files (one main and 0..n split packages)
- *
- * @param apkFilePaths list of absolute file system path to files on local host to install
- * @param timeOutInMs
- * @param reinstall set to <code>true</code> if re-install of app should be performed
- * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
- * available options.
- * @throws InstallException if the installation fails.
- */
-
- void installPackages(List<String> apkFilePaths, int timeOutInMs,
- boolean reinstall, String... extraArgs) throws InstallException;
- /**
- * Pushes a file to device
- *
- * @param localFilePath the absolute path to file on local host
- * @return {@link String} destination path on device for file
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException in case of I/O error on the connection.
- * @throws SyncException if an error happens during the push of the package on the device.
- */
- String syncPackageToDevice(String localFilePath)
- throws TimeoutException, AdbCommandRejectedException, IOException, SyncException;
-
- /**
- * Installs the application package that was pushed to a temporary location on the device.
- *
- * @param remoteFilePath absolute file path to package file on device
- * @param reinstall set to <code>true</code> if re-install of app should be performed
- * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
- * available options.
- * @throws InstallException if the installation fails.
- */
- String installRemotePackage(String remoteFilePath, boolean reinstall,
- String... extraArgs) throws InstallException;
-
- /**
- * Removes a file from device.
- *
- * @param remoteFilePath path on device of file to remove
- * @throws InstallException if the installation fails.
- */
- void removeRemotePackage(String remoteFilePath) throws InstallException;
-
- /**
- * Uninstalls an package from the device.
- *
- * @param packageName the Android application package name to uninstall
- * @return a {@link String} with an error code, or <code>null</code> if success.
- * @throws InstallException if the uninstallation fails.
- */
- String uninstallPackage(String packageName) throws InstallException;
-
- /**
- * Reboot the device.
- *
- * @param into the bootloader name to reboot into, or null to just reboot the device.
- * @throws TimeoutException in case of timeout on the connection.
- * @throws AdbCommandRejectedException if adb rejects the command
- * @throws IOException
- */
- void reboot(String into)
- throws TimeoutException, AdbCommandRejectedException, IOException;
-
- /**
- * Return the device's battery level, from 0 to 100 percent.
- * <p/>
- * The battery level may be cached. Only queries the device for its
- * battery level if 5 minutes have expired since the last successful query.
- *
- * @return the battery level or <code>null</code> if it could not be retrieved
- * @deprecated use {@link #getBattery()}
- */
- @Deprecated
- Integer getBatteryLevel() throws TimeoutException,
- AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException;
-
- /**
- * Return the device's battery level, from 0 to 100 percent.
- * <p/>
- * The battery level may be cached. Only queries the device for its
- * battery level if <code>freshnessMs</code> ms have expired since the last successful query.
- *
- * @param freshnessMs
- * @return the battery level or <code>null</code> if it could not be retrieved
- * @throws ShellCommandUnresponsiveException
- * @deprecated use {@link #getBattery(long, TimeUnit))}
- */
- @Deprecated
- Integer getBatteryLevel(long freshnessMs) throws TimeoutException,
- AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException;
-
- /**
- * Return the device's battery level, from 0 to 100 percent.
- * <p/>
- * The battery level may be cached. Only queries the device for its
- * battery level if 5 minutes have expired since the last successful query.
- *
- * @return a {@link Future} that can be used to query the battery level. The Future will return
- * a {@link ExecutionException} if battery level could not be retrieved.
- */
- @NonNull
- Future<Integer> getBattery();
-
- /**
- * Return the device's battery level, from 0 to 100 percent.
- * <p/>
- * The battery level may be cached. Only queries the device for its
- * battery level if <code>freshnessTime</code> has expired since the last successful query.
- *
- * @param freshnessTime the desired recency of battery level
- * @param timeUnit the {@link TimeUnit} of freshnessTime
- * @return a {@link Future} that can be used to query the battery level. The Future will return
- * a {@link ExecutionException} if battery level could not be retrieved.
- */
- @NonNull
- Future<Integer> getBattery(long freshnessTime, @NonNull TimeUnit timeUnit);
-
-
- /**
- * Returns the ABIs supported by this device. The ABIs are sorted in preferred order, with the
- * first ABI being the most preferred.
- * @return the list of ABIs.
- */
- @NonNull
- List<String> getAbis();
-
- /**
- * Returns the density bucket of the device screen by reading the value for system property
- * {@link #PROP_DEVICE_DENSITY}.
- *
- * @return the density, or -1 if it cannot be determined.
- */
- int getDensity();
-
- /**
- * Returns the user's language.
- *
- * @return the user's language, or null if it's unknown
- */
- String getLanguage();
-
- /**
- * Returns the user's region.
- *
- * @return the user's region, or null if it's unknown
- */
- String getRegion();
-}
\ No newline at end of file
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/JdwpPacket.java b/base/ddmlib/src/main/java/com/android/ddmlib/JdwpPacket.java
deleted file mode 100644
index 23b0249..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/JdwpPacket.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/* //device/tools/ddms/libs/ddmlib/src/com/android/ddmlib/JdwpPacket.java
-**
-** Copyright 2007, 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.ddmlib;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.SocketChannel;
-
-/**
- * A JDWP packet, sitting at the start of a ByteBuffer somewhere.
- *
- * This allows us to wrap a "pointer" to the data with the results of
- * decoding the packet.
- *
- * None of the operations here are synchronized. If multiple threads will
- * be accessing the same ByteBuffers, external sync will be required.
- *
- * Use the constructor to create an empty packet, or "findPacket()" to
- * wrap a JdwpPacket around existing data.
- */
-final class JdwpPacket {
- // header len
- public static final int JDWP_HEADER_LEN = 11;
-
- // results from findHandshake
- public static final int HANDSHAKE_GOOD = 1;
- public static final int HANDSHAKE_NOTYET = 2;
- public static final int HANDSHAKE_BAD = 3;
-
- // our cmdSet/cmd
- private static final int DDMS_CMD_SET = 0xc7; // 'G' + 128
- private static final int DDMS_CMD = 0x01;
-
- // "flags" field
- private static final int REPLY_PACKET = 0x80;
-
- // this is sent and expected at the start of a JDWP connection
- private static final byte[] mHandshake = {
- 'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'
- };
-
- public static final int HANDSHAKE_LEN = mHandshake.length;
-
- private ByteBuffer mBuffer;
- private int mLength, mId, mFlags, mCmdSet, mCmd, mErrCode;
- private boolean mIsNew;
-
- private static int sSerialId = 0x40000000;
-
-
- /**
- * Create a new, empty packet, in "buf".
- */
- JdwpPacket(ByteBuffer buf) {
- mBuffer = buf;
- mIsNew = true;
- }
-
- /**
- * Finish a packet created with newPacket().
- *
- * This always creates a command packet, with the next serial number
- * in sequence.
- *
- * We have to take "payloadLength" as an argument because we can't
- * see the position in the "slice" returned by getPayload(). We could
- * fish it out of the chunk header, but it's legal for there to be
- * more than one chunk in a JDWP packet.
- *
- * On exit, "position" points to the end of the data.
- */
- void finishPacket(int payloadLength) {
- assert mIsNew;
-
- ByteOrder oldOrder = mBuffer.order();
- mBuffer.order(ChunkHandler.CHUNK_ORDER);
-
- mLength = JDWP_HEADER_LEN + payloadLength;
- mId = getNextSerial();
- mFlags = 0;
- mCmdSet = DDMS_CMD_SET;
- mCmd = DDMS_CMD;
-
- mBuffer.putInt(0x00, mLength);
- mBuffer.putInt(0x04, mId);
- mBuffer.put(0x08, (byte) mFlags);
- mBuffer.put(0x09, (byte) mCmdSet);
- mBuffer.put(0x0a, (byte) mCmd);
-
- mBuffer.order(oldOrder);
- mBuffer.position(mLength);
- }
-
- /**
- * Get the next serial number. This creates a unique serial number
- * across all connections, not just for the current connection. This
- * is a useful property when debugging, but isn't necessary.
- *
- * We can't synchronize on an int, so we use a sync method.
- */
- private static synchronized int getNextSerial() {
- return sSerialId++;
- }
-
- /**
- * Return a slice of the byte buffer, positioned past the JDWP header
- * to the start of the chunk header. The buffer's limit will be set
- * to the size of the payload if the size is known; if this is a
- * packet under construction the limit will be set to the end of the
- * buffer.
- *
- * Doesn't examine the packet at all -- works on empty buffers.
- */
- ByteBuffer getPayload() {
- ByteBuffer buf;
- int oldPosn = mBuffer.position();
-
- mBuffer.position(JDWP_HEADER_LEN);
- buf = mBuffer.slice(); // goes from position to limit
- mBuffer.position(oldPosn);
-
- if (mLength > 0)
- buf.limit(mLength - JDWP_HEADER_LEN);
- else
- assert mIsNew;
- buf.order(ChunkHandler.CHUNK_ORDER);
- return buf;
- }
-
- /**
- * Returns "true" if this JDWP packet has a JDWP command type.
- *
- * This never returns "true" for reply packets.
- */
- boolean isDdmPacket() {
- return (mFlags & REPLY_PACKET) == 0 &&
- mCmdSet == DDMS_CMD_SET &&
- mCmd == DDMS_CMD;
- }
-
- /**
- * Returns "true" if this JDWP packet is tagged as a reply.
- */
- boolean isReply() {
- return (mFlags & REPLY_PACKET) != 0;
- }
-
- /**
- * Returns "true" if this JDWP packet is a reply with a nonzero
- * error code.
- */
- boolean isError() {
- return isReply() && mErrCode != 0;
- }
-
- /**
- * Returns "true" if this JDWP packet has no data.
- */
- boolean isEmpty() {
- return (mLength == JDWP_HEADER_LEN);
- }
-
- /**
- * Return the packet's ID. For a reply packet, this allows us to
- * match the reply with the original request.
- */
- int getId() {
- return mId;
- }
-
- /**
- * Return the length of a packet. This includes the header, so an
- * empty packet is 11 bytes long.
- */
- int getLength() {
- return mLength;
- }
-
- /**
- * Write our packet to "chan". Consumes the packet as part of the
- * write.
- *
- * The JDWP packet starts at offset 0 and ends at mBuffer.position().
- */
- void writeAndConsume(SocketChannel chan) throws IOException {
- int oldLimit;
-
- //Log.i("ddms", "writeAndConsume: pos=" + mBuffer.position()
- // + ", limit=" + mBuffer.limit());
-
- assert mLength > 0;
-
- mBuffer.flip(); // limit<-posn, posn<-0
- oldLimit = mBuffer.limit();
- mBuffer.limit(mLength);
- while (mBuffer.position() != mBuffer.limit()) {
- chan.write(mBuffer);
- }
- // position should now be at end of packet
- assert mBuffer.position() == mLength;
-
- mBuffer.limit(oldLimit);
- mBuffer.compact(); // shift posn...limit, posn<-pending data
-
- //Log.i("ddms", " : pos=" + mBuffer.position()
- // + ", limit=" + mBuffer.limit());
- }
-
- /**
- * "Move" the packet data out of the buffer we're sitting on and into
- * buf at the current position.
- */
- void movePacket(ByteBuffer buf) {
- Log.v("ddms", "moving " + mLength + " bytes");
- int oldPosn = mBuffer.position();
-
- mBuffer.position(0);
- mBuffer.limit(mLength);
- buf.put(mBuffer);
- mBuffer.position(mLength);
- mBuffer.limit(oldPosn);
- mBuffer.compact(); // shift posn...limit, posn<-pending data
- }
-
- /**
- * Consume the JDWP packet.
- *
- * On entry and exit, "position" is the #of bytes in the buffer.
- */
- void consume()
- {
- //Log.d("ddms", "consuming " + mLength + " bytes");
- //Log.d("ddms", " posn=" + mBuffer.position()
- // + ", limit=" + mBuffer.limit());
-
- /*
- * The "flip" call sets "limit" equal to the position (usually the
- * end of data) and "position" equal to zero.
- *
- * compact() copies everything from "position" and "limit" to the
- * start of the buffer, sets "position" to the end of data, and
- * sets "limit" to the capacity.
- *
- * On entry, "position" is set to the amount of data in the buffer
- * and "limit" is set to the capacity. We want to call flip()
- * so that position..limit spans our data, advance "position" past
- * the current packet, then compact.
- */
- mBuffer.flip(); // limit<-posn, posn<-0
- mBuffer.position(mLength);
- mBuffer.compact(); // shift posn...limit, posn<-pending data
- mLength = 0;
- //Log.d("ddms", " after compact, posn=" + mBuffer.position()
- // + ", limit=" + mBuffer.limit());
- }
-
- /**
- * Find the JDWP packet at the start of "buf". The start is known,
- * but the length has to be parsed out.
- *
- * On entry, the packet data in "buf" must start at offset 0 and end
- * at "position". "limit" should be set to the buffer capacity. This
- * method does not alter "buf"s attributes.
- *
- * Returns a new JdwpPacket if a full one is found in the buffer. If
- * not, returns null. Throws an exception if the data doesn't look like
- * a valid JDWP packet.
- */
- static JdwpPacket findPacket(ByteBuffer buf) {
- int count = buf.position();
- int length, id, flags, cmdSet, cmd;
-
- if (count < JDWP_HEADER_LEN)
- return null;
-
- ByteOrder oldOrder = buf.order();
- buf.order(ChunkHandler.CHUNK_ORDER);
-
- length = buf.getInt(0x00);
- id = buf.getInt(0x04);
- flags = buf.get(0x08) & 0xff;
- cmdSet = buf.get(0x09) & 0xff;
- cmd = buf.get(0x0a) & 0xff;
-
- buf.order(oldOrder);
-
- if (length < JDWP_HEADER_LEN)
- throw new BadPacketException();
- if (count < length)
- return null;
-
- JdwpPacket pkt = new JdwpPacket(buf);
- //pkt.mBuffer = buf;
- pkt.mLength = length;
- pkt.mId = id;
- pkt.mFlags = flags;
-
- if ((flags & REPLY_PACKET) == 0) {
- pkt.mCmdSet = cmdSet;
- pkt.mCmd = cmd;
- pkt.mErrCode = -1;
- } else {
- pkt.mCmdSet = -1;
- pkt.mCmd = -1;
- pkt.mErrCode = cmdSet | (cmd << 8);
- }
-
- return pkt;
- }
-
- /**
- * Like findPacket(), but when we're expecting the JDWP handshake.
- *
- * Returns one of:
- * HANDSHAKE_GOOD - found handshake, looks good
- * HANDSHAKE_BAD - found enough data, but it's wrong
- * HANDSHAKE_NOTYET - not enough data has been read yet
- */
- static int findHandshake(ByteBuffer buf) {
- int count = buf.position();
- int i;
-
- if (count < mHandshake.length)
- return HANDSHAKE_NOTYET;
-
- for (i = mHandshake.length -1; i >= 0; --i) {
- if (buf.get(i) != mHandshake[i])
- return HANDSHAKE_BAD;
- }
-
- return HANDSHAKE_GOOD;
- }
-
- /**
- * Remove the handshake string from the buffer.
- *
- * On entry and exit, "position" is the #of bytes in the buffer.
- */
- static void consumeHandshake(ByteBuffer buf) {
- // in theory, nothing else can have arrived, so this is overkill
- buf.flip(); // limit<-posn, posn<-0
- buf.position(mHandshake.length);
- buf.compact(); // shift posn...limit, posn<-pending data
- }
-
- /**
- * Copy the handshake string into the output buffer.
- *
- * On exit, "buf"s position will be advanced.
- */
- static void putHandshake(ByteBuffer buf) {
- buf.put(mHandshake);
- }
-}
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/Log.java b/base/ddmlib/src/main/java/com/android/ddmlib/Log.java
deleted file mode 100644
index cc7328f..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/Log.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Log class that mirrors the API in main Android sources.
- * <p/>Default behavior outputs the log to {@link System#out}. Use
- * {@link #setLogOutput(com.android.ddmlib.Log.ILogOutput)} to redirect the log somewhere else.
- */
-public final class Log {
-
- /**
- * Log Level enum.
- */
- public enum LogLevel {
- VERBOSE(2, "verbose", 'V'), //$NON-NLS-1$
- DEBUG(3, "debug", 'D'), //$NON-NLS-1$
- INFO(4, "info", 'I'), //$NON-NLS-1$
- WARN(5, "warn", 'W'), //$NON-NLS-1$
- ERROR(6, "error", 'E'), //$NON-NLS-1$
- ASSERT(7, "assert", 'A'); //$NON-NLS-1$
-
- private int mPriorityLevel;
- private String mStringValue;
- private char mPriorityLetter;
-
- LogLevel(int intPriority, String stringValue, char priorityChar) {
- mPriorityLevel = intPriority;
- mStringValue = stringValue;
- mPriorityLetter = priorityChar;
- }
-
- public static LogLevel getByString(String value) {
- for (LogLevel mode : values()) {
- if (mode.mStringValue.equals(value)) {
- return mode;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the {@link LogLevel} enum matching the specified letter.
- * @param letter the letter matching a <code>LogLevel</code> enum
- * @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
- */
- public static LogLevel getByLetter(char letter) {
- for (LogLevel mode : values()) {
- if (mode.mPriorityLetter == letter) {
- return mode;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the {@link LogLevel} enum matching the specified letter.
- * <p/>
- * The letter is passed as a {@link String} argument, but only the first character
- * is used.
- * @param letter the letter matching a <code>LogLevel</code> enum
- * @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
- */
- public static LogLevel getByLetterString(String letter) {
- if (!letter.isEmpty()) {
- return getByLetter(letter.charAt(0));
- }
-
- return null;
- }
-
- /**
- * Returns the letter identifying the priority of the {@link LogLevel}.
- */
- public char getPriorityLetter() {
- return mPriorityLetter;
- }
-
- /**
- * Returns the numerical value of the priority.
- */
- public int getPriority() {
- return mPriorityLevel;
- }
-
- /**
- * Returns a non translated string representing the LogLevel.
- */
- public String getStringValue() {
- return mStringValue;
- }
- }
-
- /**
- * Classes which implement this interface provides methods that deal with outputting log
- * messages.
- */
- public interface ILogOutput {
- /**
- * Sent when a log message needs to be printed.
- * @param logLevel The {@link LogLevel} enum representing the priority of the message.
- * @param tag The tag associated with the message.
- * @param message The message to display.
- */
- void printLog(LogLevel logLevel, String tag, String message);
-
- /**
- * Sent when a log message needs to be printed, and, if possible, displayed to the user
- * in a dialog box.
- * @param logLevel The {@link LogLevel} enum representing the priority of the message.
- * @param tag The tag associated with the message.
- * @param message The message to display.
- */
- void printAndPromptLog(LogLevel logLevel, String tag, String message);
- }
-
- private static LogLevel sLevel = DdmPreferences.getLogLevel();
-
- private static ILogOutput sLogOutput;
-
- private static final char[] mSpaceLine = new char[72];
- private static final char[] mHexDigit = new char[]
- { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
- static {
- /* prep for hex dump */
- int i = mSpaceLine.length-1;
- while (i >= 0)
- mSpaceLine[i--] = ' ';
- mSpaceLine[0] = mSpaceLine[1] = mSpaceLine[2] = mSpaceLine[3] = '0';
- mSpaceLine[4] = '-';
- }
-
- static final class Config {
- static final boolean LOGV = true;
- static final boolean LOGD = true;
- }
-
- private Log() {}
-
- /**
- * Outputs a {@link LogLevel#VERBOSE} level message.
- * @param tag The tag associated with the message.
- * @param message The message to output.
- */
- public static void v(String tag, String message) {
- println(LogLevel.VERBOSE, tag, message);
- }
-
- /**
- * Outputs a {@link LogLevel#DEBUG} level message.
- * @param tag The tag associated with the message.
- * @param message The message to output.
- */
- public static void d(String tag, String message) {
- println(LogLevel.DEBUG, tag, message);
- }
-
- /**
- * Outputs a {@link LogLevel#INFO} level message.
- * @param tag The tag associated with the message.
- * @param message The message to output.
- */
- public static void i(String tag, String message) {
- println(LogLevel.INFO, tag, message);
- }
-
- /**
- * Outputs a {@link LogLevel#WARN} level message.
- * @param tag The tag associated with the message.
- * @param message The message to output.
- */
- public static void w(String tag, String message) {
- println(LogLevel.WARN, tag, message);
- }
-
- /**
- * Outputs a {@link LogLevel#ERROR} level message.
- * @param tag The tag associated with the message.
- * @param message The message to output.
- */
- public static void e(String tag, String message) {
- println(LogLevel.ERROR, tag, message);
- }
-
- /**
- * Outputs a log message and attempts to display it in a dialog.
- * @param tag The tag associated with the message.
- * @param message The message to output.
- */
- public static void logAndDisplay(LogLevel logLevel, String tag, String message) {
- if (sLogOutput != null) {
- sLogOutput.printAndPromptLog(logLevel, tag, message);
- } else {
- println(logLevel, tag, message);
- }
- }
-
- /**
- * Outputs a {@link LogLevel#ERROR} level {@link Throwable} information.
- * @param tag The tag associated with the message.
- * @param throwable The {@link Throwable} to output.
- */
- public static void e(String tag, Throwable throwable) {
- if (throwable != null) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
-
- throwable.printStackTrace(pw);
- println(LogLevel.ERROR, tag, throwable.getMessage() + '\n' + sw.toString());
- }
- }
-
- static void setLevel(LogLevel logLevel) {
- sLevel = logLevel;
- }
-
- /**
- * Sets the {@link ILogOutput} to use to print the logs. If not set, {@link System#out}
- * will be used.
- * @param logOutput The {@link ILogOutput} to use to print the log.
- */
- public static void setLogOutput(ILogOutput logOutput) {
- sLogOutput = logOutput;
- }
-
- /**
- * Show hex dump.
- * <p/>
- * Local addition. Output looks like:
- * 1230- 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef
- * <p/>
- * Uses no string concatenation; creates one String object per line.
- */
- static void hexDump(String tag, LogLevel level, byte[] data, int offset, int length) {
-
- int kHexOffset = 6;
- int kAscOffset = 55;
- char[] line = new char[mSpaceLine.length];
- int addr, baseAddr, count;
- int i, ch;
- boolean needErase = true;
-
- //Log.w(tag, "HEX DUMP: off=" + offset + ", length=" + length);
-
- baseAddr = 0;
- while (length != 0) {
- if (length > 16) {
- // full line
- count = 16;
- } else {
- // partial line; re-copy blanks to clear end
- count = length;
- needErase = true;
- }
-
- if (needErase) {
- System.arraycopy(mSpaceLine, 0, line, 0, mSpaceLine.length);
- needErase = false;
- }
-
- // output the address (currently limited to 4 hex digits)
- addr = baseAddr;
- addr &= 0xffff;
- ch = 3;
- while (addr != 0) {
- line[ch] = mHexDigit[addr & 0x0f];
- ch--;
- addr >>>= 4;
- }
-
- // output hex digits and ASCII chars
- ch = kHexOffset;
- for (i = 0; i < count; i++) {
- byte val = data[offset + i];
-
- line[ch++] = mHexDigit[(val >>> 4) & 0x0f];
- line[ch++] = mHexDigit[val & 0x0f];
- ch++;
-
- if (val >= 0x20 && val < 0x7f)
- line[kAscOffset + i] = (char) val;
- else
- line[kAscOffset + i] = '.';
- }
-
- println(level, tag, new String(line));
-
- // advance to next chunk of data
- length -= count;
- offset += count;
- baseAddr += count;
- }
-
- }
-
- /**
- * Dump the entire contents of a byte array with DEBUG priority.
- */
- static void hexDump(byte[] data) {
- hexDump("ddms", LogLevel.DEBUG, data, 0, data.length);
- }
-
- /* currently prints to stdout; could write to a log window */
- private static void println(LogLevel logLevel, String tag, String message) {
- if (logLevel.getPriority() >= sLevel.getPriority()) {
- if (sLogOutput != null) {
- sLogOutput.printLog(logLevel, tag, message);
- } else {
- printLog(logLevel, tag, message);
- }
- }
- }
-
- /**
- * Prints a log message.
- * @param logLevel
- * @param tag
- * @param message
- */
- public static void printLog(LogLevel logLevel, String tag, String message) {
- System.out.print(getLogFormatString(logLevel, tag, message));
- }
-
- /**
- * Formats a log message.
- * @param logLevel
- * @param tag
- * @param message
- */
- public static String getLogFormatString(LogLevel logLevel, String tag, String message) {
- SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss", Locale.getDefault());
- return String.format("%s %c/%s: %s\n", formatter.format(new Date()),
- logLevel.getPriorityLetter(), tag, message);
- }
-}
-
-
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/MonitorThread.java b/base/ddmlib/src/main/java/com/android/ddmlib/MonitorThread.java
deleted file mode 100644
index a4ff115..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/MonitorThread.java
+++ /dev/null
@@ -1,790 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-
-import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
-import com.android.ddmlib.Log.LogLevel;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.NotYetBoundException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-/**
- * Monitor open connections.
- */
-final class MonitorThread extends Thread {
-
- // For broadcasts to message handlers
- //private static final int CLIENT_CONNECTED = 1;
-
- private static final int CLIENT_READY = 2;
-
- private static final int CLIENT_DISCONNECTED = 3;
-
- private volatile boolean mQuit = false;
-
- // List of clients we're paying attention to
- private ArrayList<Client> mClientList;
-
- // The almighty mux
- private Selector mSelector;
-
- // Map chunk types to handlers
- private HashMap<Integer, ChunkHandler> mHandlerMap;
-
- // port for "debug selected"
- private ServerSocketChannel mDebugSelectedChan;
-
- private int mNewDebugSelectedPort;
-
- private int mDebugSelectedPort = -1;
-
- /**
- * "Selected" client setup to answer debugging connection to the mNewDebugSelectedPort port.
- */
- private Client mSelectedClient = null;
-
- // singleton
- private static MonitorThread sInstance;
-
- /**
- * Generic constructor.
- */
- private MonitorThread() {
- super("Monitor");
- mClientList = new ArrayList<Client>();
- mHandlerMap = new HashMap<Integer, ChunkHandler>();
-
- mNewDebugSelectedPort = DdmPreferences.getSelectedDebugPort();
- }
-
- /**
- * Creates and return the singleton instance of the client monitor thread.
- */
- static MonitorThread createInstance() {
- return sInstance = new MonitorThread();
- }
-
- /**
- * Get singleton instance of the client monitor thread.
- */
- static MonitorThread getInstance() {
- return sInstance;
- }
-
-
- /**
- * Sets or changes the port number for "debug selected".
- */
- synchronized void setDebugSelectedPort(int port) throws IllegalStateException {
- if (sInstance == null) {
- return;
- }
-
- if (!AndroidDebugBridge.getClientSupport()) {
- return;
- }
-
- if (mDebugSelectedChan != null) {
- Log.d("ddms", "Changing debug-selected port to " + port);
- mNewDebugSelectedPort = port;
- wakeup();
- } else {
- // we set mNewDebugSelectedPort instead of mDebugSelectedPort so that it's automatically
- // opened on the first run loop.
- mNewDebugSelectedPort = port;
- }
- }
-
- /**
- * Sets the client to accept debugger connection on the custom "Selected debug port".
- * @param selectedClient the client. Can be null.
- */
- synchronized void setSelectedClient(Client selectedClient) {
- if (sInstance == null) {
- return;
- }
-
- if (mSelectedClient != selectedClient) {
- Client oldClient = mSelectedClient;
- mSelectedClient = selectedClient;
-
- if (oldClient != null) {
- oldClient.update(Client.CHANGE_PORT);
- }
-
- if (mSelectedClient != null) {
- mSelectedClient.update(Client.CHANGE_PORT);
- }
- }
- }
-
- /**
- * Returns the client accepting debugger connection on the custom "Selected debug port".
- */
- Client getSelectedClient() {
- return mSelectedClient;
- }
-
-
- /**
- * Returns "true" if we want to retry connections to clients if we get a bad
- * JDWP handshake back, "false" if we want to just mark them as bad and
- * leave them alone.
- */
- boolean getRetryOnBadHandshake() {
- return true; // TODO? make configurable
- }
-
- /**
- * Get an array of known clients.
- */
- Client[] getClients() {
- synchronized (mClientList) {
- return mClientList.toArray(new Client[mClientList.size()]);
- }
- }
-
- /**
- * Register "handler" as the handler for type "type".
- */
- synchronized void registerChunkHandler(int type, ChunkHandler handler) {
- if (sInstance == null) {
- return;
- }
-
- synchronized (mHandlerMap) {
- if (mHandlerMap.get(type) == null) {
- mHandlerMap.put(type, handler);
- }
- }
- }
-
- /**
- * Watch for activity from clients and debuggers.
- */
- @Override
- public void run() {
- Log.d("ddms", "Monitor is up");
-
- // create a selector
- try {
- mSelector = Selector.open();
- } catch (IOException ioe) {
- Log.logAndDisplay(LogLevel.ERROR, "ddms",
- "Failed to initialize Monitor Thread: " + ioe.getMessage());
- return;
- }
-
- while (!mQuit) {
-
- try {
- /*
- * sync with new registrations: we wait until addClient is done before going through
- * and doing mSelector.select() again.
- * @see {@link #addClient(Client)}
- */
- synchronized (mClientList) {
- }
-
- // (re-)open the "debug selected" port, if it's not opened yet or
- // if the port changed.
- try {
- if (AndroidDebugBridge.getClientSupport()) {
- if ((mDebugSelectedChan == null ||
- mNewDebugSelectedPort != mDebugSelectedPort) &&
- mNewDebugSelectedPort != -1) {
- if (reopenDebugSelectedPort()) {
- mDebugSelectedPort = mNewDebugSelectedPort;
- }
- }
- }
- } catch (IOException ioe) {
- Log.e("ddms",
- "Failed to reopen debug port for Selected Client to: " + mNewDebugSelectedPort);
- Log.e("ddms", ioe);
- mNewDebugSelectedPort = mDebugSelectedPort; // no retry
- }
-
- int count;
- try {
- count = mSelector.select();
- } catch (IOException ioe) {
- ioe.printStackTrace();
- continue;
- } catch (CancelledKeyException cke) {
- continue;
- }
-
- if (count == 0) {
- // somebody called wakeup() ?
- // Log.i("ddms", "selector looping");
- continue;
- }
-
- Set<SelectionKey> keys = mSelector.selectedKeys();
- Iterator<SelectionKey> iter = keys.iterator();
-
- while (iter.hasNext()) {
- SelectionKey key = iter.next();
- iter.remove();
-
- try {
- if (key.attachment() instanceof Client) {
- processClientActivity(key);
- }
- else if (key.attachment() instanceof Debugger) {
- processDebuggerActivity(key);
- }
- else if (key.attachment() instanceof MonitorThread) {
- processDebugSelectedActivity(key);
- }
- else {
- Log.e("ddms", "unknown activity key");
- }
- } catch (Exception e) {
- // we don't want to have our thread be killed because of any uncaught
- // exception, so we intercept all here.
- Log.e("ddms", "Exception during activity from Selector.");
- Log.e("ddms", e);
- }
- }
- } catch (Exception e) {
- // we don't want to have our thread be killed because of any uncaught
- // exception, so we intercept all here.
- Log.e("ddms", "Exception MonitorThread.run()");
- Log.e("ddms", e);
- }
- }
- }
-
-
- /**
- * Returns the port on which the selected client listen for debugger
- */
- int getDebugSelectedPort() {
- return mDebugSelectedPort;
- }
-
- /*
- * Something happened. Figure out what.
- */
- private void processClientActivity(SelectionKey key) {
- Client client = (Client)key.attachment();
-
- try {
- if (!key.isReadable() || !key.isValid()) {
- Log.d("ddms", "Invalid key from " + client + ". Dropping client.");
- dropClient(client, true /* notify */);
- return;
- }
-
- client.read();
-
- /*
- * See if we have a full packet in the buffer. It's possible we have
- * more than one packet, so we have to loop.
- */
- JdwpPacket packet = client.getJdwpPacket();
- while (packet != null) {
- if (packet.isDdmPacket()) {
- // unsolicited DDM request - hand it off
- assert !packet.isReply();
- callHandler(client, packet, null);
- packet.consume();
- } else if (packet.isReply()
- && client.isResponseToUs(packet.getId()) != null) {
- // reply to earlier DDM request
- ChunkHandler handler = client
- .isResponseToUs(packet.getId());
- if (packet.isError())
- client.packetFailed(packet);
- else if (packet.isEmpty())
- Log.d("ddms", "Got empty reply for 0x"
- + Integer.toHexString(packet.getId())
- + " from " + client);
- else
- callHandler(client, packet, handler);
- packet.consume();
- client.removeRequestId(packet.getId());
- } else {
- Log.v("ddms", "Forwarding client "
- + (packet.isReply() ? "reply" : "event") + " 0x"
- + Integer.toHexString(packet.getId()) + " to "
- + client.getDebugger());
- client.forwardPacketToDebugger(packet);
- }
-
- // find next
- packet = client.getJdwpPacket();
- }
- } catch (CancelledKeyException e) {
- // key was canceled probably due to a disconnected client before we could
- // read stuff coming from the client, so we drop it.
- dropClient(client, true /* notify */);
- } catch (IOException ex) {
- // something closed down, no need to print anything. The client is simply dropped.
- dropClient(client, true /* notify */);
- } catch (Exception ex) {
- Log.e("ddms", ex);
-
- /* close the client; automatically un-registers from selector */
- dropClient(client, true /* notify */);
-
- if (ex instanceof BufferOverflowException) {
- Log.w("ddms",
- "Client data packet exceeded maximum buffer size "
- + client);
- } else {
- // don't know what this is, display it
- Log.e("ddms", ex);
- }
- }
- }
-
- /*
- * Process an incoming DDM packet. If this is a reply to an earlier request,
- * "handler" will be set to the handler responsible for the original
- * request. The spec allows a JDWP message to include multiple DDM chunks.
- */
- private void callHandler(Client client, JdwpPacket packet,
- ChunkHandler handler) {
-
- // on first DDM packet received, broadcast a "ready" message
- if (!client.ddmSeen())
- broadcast(CLIENT_READY, client);
-
- ByteBuffer buf = packet.getPayload();
- int type, length;
- boolean reply = true;
-
- type = buf.getInt();
- length = buf.getInt();
-
- if (handler == null) {
- // not a reply, figure out who wants it
- synchronized (mHandlerMap) {
- handler = mHandlerMap.get(type);
- reply = false;
- }
- }
-
- if (handler == null) {
- Log.w("ddms", "Received unsupported chunk type "
- + ChunkHandler.name(type) + " (len=" + length + ")");
- } else {
- Log.d("ddms", "Calling handler for " + ChunkHandler.name(type)
- + " [" + handler + "] (len=" + length + ")");
- ByteBuffer ibuf = buf.slice();
- ByteBuffer roBuf = ibuf.asReadOnlyBuffer(); // enforce R/O
- roBuf.order(ChunkHandler.CHUNK_ORDER);
- // do the handling of the chunk synchronized on the client list
- // to be sure there's no concurrency issue when we look for HOME
- // in hasApp()
- synchronized (mClientList) {
- handler.handleChunk(client, type, roBuf, reply, packet.getId());
- }
- }
- }
-
- /**
- * Drops a client from the monitor.
- * <p/>This will lock the {@link Client} list of the {@link Device} running <var>client</var>.
- * @param client
- * @param notify
- */
- synchronized void dropClient(Client client, boolean notify) {
- if (sInstance == null) {
- return;
- }
-
- synchronized (mClientList) {
- if (!mClientList.remove(client)) {
- return;
- }
- }
- client.close(notify);
- broadcast(CLIENT_DISCONNECTED, client);
-
- /*
- * http://forum.java.sun.com/thread.jspa?threadID=726715&start=0
- * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5073504
- */
- wakeup();
- }
-
- /**
- * Drops the provided list of clients from the monitor. This will lock the {@link Client}
- * list of the {@link Device} running each of the clients.
- */
- synchronized void dropClients(Collection<? extends Client> clients, boolean notify) {
- for (Client c : clients) {
- dropClient(c, notify);
- }
- }
-
- /*
- * Process activity from one of the debugger sockets. This could be a new
- * connection or a data packet.
- */
- private void processDebuggerActivity(SelectionKey key) {
- Debugger dbg = (Debugger)key.attachment();
-
- try {
- if (key.isAcceptable()) {
- try {
- acceptNewDebugger(dbg, null);
- } catch (IOException ioe) {
- Log.w("ddms", "debugger accept() failed");
- ioe.printStackTrace();
- }
- } else if (key.isReadable()) {
- processDebuggerData(key);
- } else {
- Log.d("ddm-debugger", "key in unknown state");
- }
- } catch (CancelledKeyException cke) {
- // key has been cancelled we can ignore that.
- }
- }
-
- /*
- * Accept a new connection from a debugger. If successful, register it with
- * the Selector.
- */
- private void acceptNewDebugger(Debugger dbg, ServerSocketChannel acceptChan)
- throws IOException {
-
- synchronized (mClientList) {
- SocketChannel chan;
-
- if (acceptChan == null)
- chan = dbg.accept();
- else
- chan = dbg.accept(acceptChan);
-
- if (chan != null) {
- chan.socket().setTcpNoDelay(true);
-
- wakeup();
-
- try {
- chan.register(mSelector, SelectionKey.OP_READ, dbg);
- } catch (IOException ioe) {
- // failed, drop the connection
- dbg.closeData();
- throw ioe;
- } catch (RuntimeException re) {
- // failed, drop the connection
- dbg.closeData();
- throw re;
- }
- } else {
- Log.w("ddms", "ignoring duplicate debugger");
- // new connection already closed
- }
- }
- }
-
- /*
- * We have incoming data from the debugger. Forward it to the client.
- */
- private void processDebuggerData(SelectionKey key) {
- Debugger dbg = (Debugger)key.attachment();
-
- try {
- /*
- * Read pending data.
- */
- dbg.read();
-
- /*
- * See if we have a full packet in the buffer. It's possible we have
- * more than one packet, so we have to loop.
- */
- JdwpPacket packet = dbg.getJdwpPacket();
- while (packet != null) {
- Log.v("ddms", "Forwarding dbg req 0x"
- + Integer.toHexString(packet.getId()) + " to "
- + dbg.getClient());
-
- dbg.forwardPacketToClient(packet);
-
- packet = dbg.getJdwpPacket();
- }
- } catch (IOException ioe) {
- /*
- * Close data connection; automatically un-registers dbg from
- * selector. The failure could be caused by the debugger going away,
- * or by the client going away and failing to accept our data.
- * Either way, the debugger connection does not need to exist any
- * longer. We also need to recycle the connection to the client, so
- * that the VM sees the debugger disconnect. For a DDM-aware client
- * this won't be necessary, and we can just send a "debugger
- * disconnected" message.
- */
- Log.d("ddms", "Closing connection to debugger " + dbg);
- dbg.closeData();
- Client client = dbg.getClient();
- if (client.isDdmAware()) {
- // TODO: soft-disconnect DDM-aware clients
- Log.d("ddms", " (recycling client connection as well)");
-
- // we should drop the client, but also attempt to reopen it.
- // This is done by the DeviceMonitor.
- client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client,
- IDebugPortProvider.NO_STATIC_PORT);
- } else {
- Log.d("ddms", " (recycling client connection as well)");
- // we should drop the client, but also attempt to reopen it.
- // This is done by the DeviceMonitor.
- client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client,
- IDebugPortProvider.NO_STATIC_PORT);
- }
- }
- }
-
- /*
- * Tell the thread that something has changed.
- */
- private void wakeup() {
- mSelector.wakeup();
- }
-
- /**
- * Tell the thread to stop. Called from UI thread.
- */
- synchronized void quit() {
- mQuit = true;
- wakeup();
- Log.d("ddms", "Waiting for Monitor thread");
- try {
- this.join();
- // since we're quitting, lets drop all the client and disconnect
- // the DebugSelectedPort
- synchronized (mClientList) {
- for (Client c : mClientList) {
- c.close(false /* notify */);
- broadcast(CLIENT_DISCONNECTED, c);
- }
- mClientList.clear();
- }
-
- if (mDebugSelectedChan != null) {
- mDebugSelectedChan.close();
- mDebugSelectedChan.socket().close();
- mDebugSelectedChan = null;
- }
- mSelector.close();
- } catch (InterruptedException ie) {
- ie.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
-
- sInstance = null;
- }
-
- /**
- * Add a new Client to the list of things we monitor. Also adds the client's
- * channel and the client's debugger listener to the selection list. This
- * should only be called from one thread (the VMWatcherThread) to avoid a
- * race between "alreadyOpen" and Client creation.
- */
- synchronized void addClient(Client client) {
- if (sInstance == null) {
- return;
- }
-
- Log.d("ddms", "Adding new client " + client);
-
- synchronized (mClientList) {
- mClientList.add(client);
-
- /*
- * Register the Client's socket channel with the selector. We attach
- * the Client to the SelectionKey. If you try to register a new
- * channel with the Selector while it is waiting for I/O, you will
- * block. The solution is to call wakeup() and then hold a lock to
- * ensure that the registration happens before the Selector goes
- * back to sleep.
- */
- try {
- wakeup();
-
- client.register(mSelector);
-
- Debugger dbg = client.getDebugger();
- if (dbg != null) {
- dbg.registerListener(mSelector);
- }
- } catch (IOException ioe) {
- // not really expecting this to happen
- ioe.printStackTrace();
- }
- }
- }
-
- /*
- * Broadcast an event to all message handlers.
- */
- private void broadcast(int event, Client client) {
- Log.d("ddms", "broadcast " + event + ": " + client);
-
- /*
- * The handler objects appear once in mHandlerMap for each message they
- * handle. We want to notify them once each, so we convert the HashMap
- * to a HashSet before we iterate.
- */
- HashSet<ChunkHandler> set;
- synchronized (mHandlerMap) {
- Collection<ChunkHandler> values = mHandlerMap.values();
- set = new HashSet<ChunkHandler>(values);
- }
-
- Iterator<ChunkHandler> iter = set.iterator();
- while (iter.hasNext()) {
- ChunkHandler handler = iter.next();
- switch (event) {
- case CLIENT_READY:
- try {
- handler.clientReady(client);
- } catch (IOException ioe) {
- // Something failed with the client. It should
- // fall out of the list the next time we try to
- // do something with it, so we discard the
- // exception here and assume cleanup will happen
- // later. May need to propagate farther. The
- // trouble is that not all values for "event" may
- // actually throw an exception.
- Log.w("ddms",
- "Got exception while broadcasting 'ready'");
- return;
- }
- break;
- case CLIENT_DISCONNECTED:
- handler.clientDisconnected(client);
- break;
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- }
-
- /**
- * Opens (or reopens) the "debug selected" port and listen for connections.
- * @return true if the port was opened successfully.
- * @throws IOException
- */
- private boolean reopenDebugSelectedPort() throws IOException {
-
- Log.d("ddms", "reopen debug-selected port: " + mNewDebugSelectedPort);
- if (mDebugSelectedChan != null) {
- mDebugSelectedChan.close();
- }
-
- mDebugSelectedChan = ServerSocketChannel.open();
- mDebugSelectedChan.configureBlocking(false); // required for Selector
-
- InetSocketAddress addr = new InetSocketAddress(
- InetAddress.getByName("localhost"), //$NON-NLS-1$
- mNewDebugSelectedPort);
- mDebugSelectedChan.socket().setReuseAddress(true); // enable SO_REUSEADDR
-
- try {
- mDebugSelectedChan.socket().bind(addr);
- if (mSelectedClient != null) {
- mSelectedClient.update(Client.CHANGE_PORT);
- }
-
- mDebugSelectedChan.register(mSelector, SelectionKey.OP_ACCEPT, this);
-
- return true;
- } catch (java.net.BindException e) {
- displayDebugSelectedBindError(mNewDebugSelectedPort);
-
- // do not attempt to reopen it.
- mDebugSelectedChan = null;
- mNewDebugSelectedPort = -1;
-
- return false;
- }
- }
-
- /*
- * We have some activity on the "debug selected" port. Handle it.
- */
- private void processDebugSelectedActivity(SelectionKey key) {
- assert key.isAcceptable();
-
- ServerSocketChannel acceptChan = (ServerSocketChannel)key.channel();
-
- /*
- * Find the debugger associated with the currently-selected client.
- */
- if (mSelectedClient != null) {
- Debugger dbg = mSelectedClient.getDebugger();
-
- if (dbg != null) {
- Log.d("ddms", "Accepting connection on 'debug selected' port");
- try {
- acceptNewDebugger(dbg, acceptChan);
- } catch (IOException ioe) {
- // client should be gone, keep going
- }
-
- return;
- }
- }
-
- Log.w("ddms",
- "Connection on 'debug selected' port, but none selected");
- try {
- SocketChannel chan = acceptChan.accept();
- chan.close();
- } catch (IOException ioe) {
- // not expected; client should be gone, keep going
- } catch (NotYetBoundException e) {
- displayDebugSelectedBindError(mDebugSelectedPort);
- }
- }
-
- private void displayDebugSelectedBindError(int port) {
- String message = String.format(
- "Could not open Selected VM debug port (%1$d). Make sure you do not have another instance of DDMS or of the eclipse plugin running. If it's being used by something else, choose a new port number in the preferences.",
- port);
-
- Log.logAndDisplay(LogLevel.ERROR, "ddms", message);
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java b/base/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java
deleted file mode 100644
index 72d7c77..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2007 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.ddmlib;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Represents a stack call. This is used to return all of the call
- * information as one object.
- */
-public final class NativeStackCallInfo {
- private static final Pattern SOURCE_NAME_PATTERN = Pattern.compile("^(.+):(\\d+)(\\s+\\(discriminator\\s+\\d+\\))?$");
-
- /** address of this stack frame */
- private long mAddress;
-
- /** name of the library */
- private String mLibrary;
-
- /** name of the method */
- private String mMethod;
-
- /**
- * name of the source file + line number in the format<br>
- * <sourcefile>:<linenumber>
- */
- private String mSourceFile;
-
- private int mLineNumber = -1;
-
- /**
- * Basic constructor with library, method, and sourcefile information
- *
- * @param address address of this stack frame
- * @param lib The name of the library
- * @param method the name of the method
- * @param sourceFile the name of the source file and the line number
- * as "[sourcefile]:[fileNumber]"
- */
- public NativeStackCallInfo(long address, String lib, String method, String sourceFile) {
- mAddress = address;
- mLibrary = lib;
- mMethod = method;
-
- Matcher m = SOURCE_NAME_PATTERN.matcher(sourceFile);
- if (m.matches()) {
- mSourceFile = m.group(1);
- try {
- mLineNumber = Integer.parseInt(m.group(2));
- } catch (NumberFormatException e) {
- // do nothing, the line number will stay at -1
- }
- if (m.groupCount() == 3) {
- // A discriminator was found, add that in the source file name.
- mSourceFile += m.group(3);
- }
- } else {
- mSourceFile = sourceFile;
- }
- }
-
- /**
- * Returns the address of this stack frame.
- */
- public long getAddress() {
- return mAddress;
- }
-
- /**
- * Returns the name of the library name.
- */
- public String getLibraryName() {
- return mLibrary;
- }
-
- /**
- * Returns the name of the method.
- */
- public String getMethodName() {
- return mMethod;
- }
-
- /**
- * Returns the name of the source file.
- */
- public String getSourceFile() {
- return mSourceFile;
- }
-
- /**
- * Returns the line number, or -1 if unknown.
- */
- public int getLineNumber() {
- return mLineNumber;
- }
-
- @Override
- public String toString() {
- return String.format("\t%1$08x\t%2$s --- %3$s --- %4$s:%5$d",
- getAddress(), getLibraryName(), getMethodName(), getSourceFile(), getLineNumber());
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/PropertyFetcher.java b/base/ddmlib/src/main/java/com/android/ddmlib/PropertyFetcher.java
deleted file mode 100644
index dc83115..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/PropertyFetcher.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ddmlib;
-
-import com.android.annotations.NonNull;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.SettableFuture;
-
-import java.util.Map;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Fetches and caches 'getprop' values from device.
- */
-class PropertyFetcher {
- /** the amount of time to wait between unsuccessful prop fetch attempts */
- private static final String GETPROP_COMMAND = "getprop"; //$NON-NLS-1$
- private static final Pattern GETPROP_PATTERN = Pattern.compile("^\\[([^]]+)\\]\\:\\s*\\[(.*)\\]$"); //$NON-NLS-1$
- private static final int GETPROP_TIMEOUT_SEC = 2;
- private static final int EXPECTED_PROP_COUNT = 150;
-
- private enum CacheState {
- UNPOPULATED, FETCHING, POPULATED
- }
-
- /**
- * Shell output parser for a getprop command
- */
- @VisibleForTesting
- static class GetPropReceiver extends MultiLineReceiver {
-
- private final Map<String, String> mCollectedProperties =
- Maps.newHashMapWithExpectedSize(EXPECTED_PROP_COUNT);
-
- @Override
- public void processNewLines(String[] lines) {
- // We receive an array of lines. We're expecting
- // to have the build info in the first line, and the build
- // date in the 2nd line. There seems to be an empty line
- // after all that.
-
- for (String line : lines) {
- if (line.isEmpty() || line.startsWith("#")) {
- continue;
- }
-
- Matcher m = GETPROP_PATTERN.matcher(line);
- if (m.matches()) {
- String label = m.group(1);
- String value = m.group(2);
-
- if (!label.isEmpty()) {
- mCollectedProperties.put(label, value);
- }
- }
- }
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- Map<String, String> getCollectedProperties() {
- return mCollectedProperties;
- }
- }
-
- private final Map<String, String> mProperties = Maps.newHashMapWithExpectedSize(
- EXPECTED_PROP_COUNT);
- private final IDevice mDevice;
- private CacheState mCacheState = CacheState.UNPOPULATED;
- private final Map<String, SettableFuture<String>> mPendingRequests =
- Maps.newHashMapWithExpectedSize(4);
-
- public PropertyFetcher(IDevice device) {
- mDevice = device;
- }
-
- /**
- * Returns the full list of cached properties.
- */
- public synchronized Map<String, String> getProperties() {
- return mProperties;
- }
-
- /**
- * Make a possibly asynchronous request for a system property value.
- *
- * @param name the property name to retrieve
- * @return a {@link Future} that can be used to retrieve the prop value
- */
- @NonNull
- public synchronized Future<String> getProperty(@NonNull String name) {
- SettableFuture<String> result;
- if (mCacheState.equals(CacheState.FETCHING)) {
- result = addPendingRequest(name);
- } else if (mDevice.isOnline() && mCacheState.equals(CacheState.UNPOPULATED) || !isRoProp(name)) {
- // cache is empty, or this is a volatile prop that requires a query
- result = addPendingRequest(name);
- mCacheState = CacheState.FETCHING;
- initiatePropertiesQuery();
- } else {
- result = SettableFuture.create();
- // cache is populated and this is a ro prop
- result.set(mProperties.get(name));
- }
- return result;
- }
-
- private SettableFuture<String> addPendingRequest(String name) {
- SettableFuture<String> future = mPendingRequests.get(name);
- if (future == null) {
- future = SettableFuture.create();
- mPendingRequests.put(name, future);
- }
- return future;
- }
-
- private void initiatePropertiesQuery() {
- String threadName = String.format("query-prop-%s", mDevice.getSerialNumber());
- Thread propThread = new Thread(threadName) {
- @Override
- public void run() {
- try {
- GetPropReceiver propReceiver = new GetPropReceiver();
- mDevice.executeShellCommand(GETPROP_COMMAND, propReceiver, GETPROP_TIMEOUT_SEC,
- TimeUnit.SECONDS);
- populateCache(propReceiver.getCollectedProperties());
- } catch (Exception e) {
- handleException(e);
- }
- }
- };
- propThread.setDaemon(true);
- propThread.start();
- }
-
- private synchronized void populateCache(@NonNull Map<String, String> props) {
- mCacheState = props.isEmpty() ? CacheState.UNPOPULATED : CacheState.POPULATED;
- if (!props.isEmpty()) {
- mProperties.putAll(props);
- }
- for (Map.Entry<String, SettableFuture<String>> entry : mPendingRequests.entrySet()) {
- entry.getValue().set(mProperties.get(entry.getKey()));
- }
- mPendingRequests.clear();
- }
-
- private synchronized void handleException(Exception e) {
- mCacheState = CacheState.UNPOPULATED;
- Log.w("PropertyFetcher",
- String.format("%s getting properties for device %s: %s",
- e.getClass().getSimpleName(), mDevice.getSerialNumber(),
- e.getMessage()));
- for (Map.Entry<String, SettableFuture<String>> entry : mPendingRequests.entrySet()) {
- entry.getValue().setException(e);
- }
- mPendingRequests.clear();
- }
-
- /**
- * Return true if cache is populated.
- *
- * @deprecated implementation detail
- */
- @Deprecated
- public synchronized boolean arePropertiesSet() {
- return CacheState.POPULATED.equals(mCacheState);
- }
-
- private static boolean isRoProp(@NonNull String propName) {
- return propName.startsWith("ro.");
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatFilter.java b/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatFilter.java
deleted file mode 100644
index 34fdc38..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatFilter.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ddmlib.logcat;
-
-import com.android.annotations.NonNull;
-import com.android.ddmlib.Log.LogLevel;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * A Filter for logcat messages. A filter can be constructed to match
- * different fields of a logcat message. It can then be queried to see if
- * a message matches the filter's settings.
- */
-public final class LogCatFilter {
- private static final String PID_KEYWORD = "pid:"; //$NON-NLS-1$
- private static final String APP_KEYWORD = "app:"; //$NON-NLS-1$
- private static final String TAG_KEYWORD = "tag:"; //$NON-NLS-1$
- private static final String TEXT_KEYWORD = "text:"; //$NON-NLS-1$
-
- private final String mName;
- private final String mTag;
- private final String mText;
- private final String mPid;
- private final String mAppName;
- private final LogLevel mLogLevel;
-
- private boolean mCheckPid;
- private boolean mCheckAppName;
- private boolean mCheckTag;
- private boolean mCheckText;
-
- private Pattern mAppNamePattern;
- private Pattern mTagPattern;
- private Pattern mTextPattern;
-
- /**
- * Construct a filter with the provided restrictions for the logcat message. All the text
- * fields accept Java regexes as input, but ignore invalid regexes.
- * @param name name for the filter
- * @param tag value for the logcat message's tag field.
- * @param text value for the logcat message's text field.
- * @param pid value for the logcat message's pid field.
- * @param appName value for the logcat message's app name field.
- * @param logLevel value for the logcat message's log level. Only messages of
- * higher priority will be accepted by the filter.
- */
- public LogCatFilter(@NonNull String name, @NonNull String tag, @NonNull String text,
- @NonNull String pid, @NonNull String appName, @NonNull LogLevel logLevel) {
- mName = name.trim();
- mTag = tag.trim();
- mText = text.trim();
- mPid = pid.trim();
- mAppName = appName.trim();
- mLogLevel = logLevel;
-
- mCheckPid = !mPid.isEmpty();
-
- if (!mAppName.isEmpty()) {
- try {
- mAppNamePattern = Pattern.compile(mAppName, getPatternCompileFlags(mAppName));
- mCheckAppName = true;
- } catch (PatternSyntaxException e) {
- mCheckAppName = false;
- }
- }
-
- if (!mTag.isEmpty()) {
- try {
- mTagPattern = Pattern.compile(mTag, getPatternCompileFlags(mTag));
- mCheckTag = true;
- } catch (PatternSyntaxException e) {
- mCheckTag = false;
- }
- }
-
- if (!mText.isEmpty()) {
- try {
- mTextPattern = Pattern.compile(mText, getPatternCompileFlags(mText));
- mCheckText = true;
- } catch (PatternSyntaxException e) {
- mCheckText = false;
- }
- }
- }
-
- /**
- * Obtain the flags to pass to {@link Pattern#compile(String, int)}. This method
- * tries to figure out whether case sensitive matching should be used. It is based on
- * the following heuristic: if the regex has an upper case character, then the match
- * will be case sensitive. Otherwise it will be case insensitive.
- */
- private int getPatternCompileFlags(String regex) {
- for (char c : regex.toCharArray()) {
- if (Character.isUpperCase(c)) {
- return 0;
- }
- }
-
- return Pattern.CASE_INSENSITIVE;
- }
-
- /**
- * Construct a list of {@link LogCatFilter} objects by decoding the query.
- * @param query encoded search string. The query is simply a list of words (can be regexes)
- * a user would type in a search bar. These words are searched for in the text field of
- * each collected logcat message. To search in a different field, the word could be prefixed
- * with a keyword corresponding to the field name. Currently, the following keywords are
- * supported: "pid:", "tag:" and "text:". Invalid regexes are ignored.
- * @param minLevel minimum log level to match
- * @return list of filter settings that fully match the given query
- */
- public static List<LogCatFilter> fromString(String query, LogLevel minLevel) {
- List<LogCatFilter> filterSettings = new ArrayList<LogCatFilter>();
-
- for (String s : query.trim().split(" ")) {
- String tag = "";
- String text = "";
- String pid = "";
- String app = "";
-
- if (s.startsWith(PID_KEYWORD)) {
- pid = s.substring(PID_KEYWORD.length());
- } else if (s.startsWith(APP_KEYWORD)) {
- app = s.substring(APP_KEYWORD.length());
- } else if (s.startsWith(TAG_KEYWORD)) {
- tag = s.substring(TAG_KEYWORD.length());
- } else {
- if (s.startsWith(TEXT_KEYWORD)) {
- text = s.substring(TEXT_KEYWORD.length());
- } else {
- text = s;
- }
- }
- filterSettings.add(new LogCatFilter("livefilter-" + s,
- tag, text, pid, app, minLevel));
- }
-
- return filterSettings;
- }
-
- @NonNull
- public String getName() {
- return mName;
- }
-
- @NonNull
- public String getTag() {
- return mTag;
- }
-
- @NonNull
- public String getText() {
- return mText;
- }
-
- @NonNull
- public String getPid() {
- return mPid;
- }
-
- @NonNull
- public String getAppName() {
- return mAppName;
- }
-
- @NonNull
- public LogLevel getLogLevel() {
- return mLogLevel;
- }
-
- /**
- * Check whether a given message will make it through this filter.
- * @param m message to check
- * @return true if the message matches the filter's conditions.
- */
- public boolean matches(LogCatMessage m) {
- /* filter out messages of a lower priority */
- if (m.getLogLevel().getPriority() < mLogLevel.getPriority()) {
- return false;
- }
-
- /* if pid filter is enabled, filter out messages whose pid does not match
- * the filter's pid */
- if (mCheckPid && !m.getPid().equals(mPid)) {
- return false;
- }
-
- /* if app name filter is enabled, filter out messages not matching the app name */
- if (mCheckAppName) {
- Matcher matcher = mAppNamePattern.matcher(m.getAppName());
- if (!matcher.find()) {
- return false;
- }
- }
-
- /* if tag filter is enabled, filter out messages not matching the tag */
- if (mCheckTag) {
- Matcher matcher = mTagPattern.matcher(m.getTag());
- if (!matcher.find()) {
- return false;
- }
- }
-
- if (mCheckText) {
- Matcher matcher = mTextPattern.matcher(m.getMessage());
- if (!matcher.find()) {
- return false;
- }
- }
-
- return true;
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessage.java b/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessage.java
deleted file mode 100644
index bca1df0..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessage.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ddmlib.logcat;
-
-import com.android.annotations.NonNull;
-import com.android.ddmlib.Log.LogLevel;
-
-/**
- * Model a single log message output from {@code logcat -v long}.
- * A logcat message has a {@link LogLevel}, the pid (process id) of the process
- * generating the message, the time at which the message was generated, and
- * the tag and message itself.
- */
-public final class LogCatMessage {
- private final LogLevel mLogLevel;
- private final String mPid;
- private final String mTid;
- private final String mAppName;
- private final String mTag;
- private final String mTime;
- private final String mMessage;
-
- /**
- * Construct an immutable log message object.
- */
- public LogCatMessage(@NonNull LogLevel logLevel, @NonNull String pid, @NonNull String tid,
- @NonNull String appName, @NonNull String tag,
- @NonNull String time, @NonNull String msg) {
- mLogLevel = logLevel;
- mPid = pid;
- mAppName = appName;
- mTag = tag;
- mTime = time;
- mMessage = msg;
-
- long tidValue;
- try {
- // Thread id's may be in hex on some platforms.
- // Decode and store them in radix 10.
- tidValue = Long.decode(tid.trim());
- } catch (NumberFormatException e) {
- tidValue = -1;
- }
-
- mTid = Long.toString(tidValue);
- }
-
- @NonNull
- public LogLevel getLogLevel() {
- return mLogLevel;
- }
-
- @NonNull
- public String getPid() {
- return mPid;
- }
-
- @NonNull
- public String getTid() {
- return mTid;
- }
-
- @NonNull
- public String getAppName() {
- return mAppName;
- }
-
- @NonNull
- public String getTag() {
- return mTag;
- }
-
- @NonNull
- public String getTime() {
- return mTime;
- }
-
- @NonNull
- public String getMessage() {
- return mMessage;
- }
-
- @Override
- public String toString() {
- return mTime + ": "
- + mLogLevel.getPriorityLetter() + "/"
- + mTag + "("
- + mPid + "): "
- + mMessage;
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessageParser.java b/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessageParser.java
deleted file mode 100644
index 0e8b03c..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessageParser.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ddmlib.logcat;
-
-import com.android.annotations.NonNull;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.Log.LogLevel;
-import com.google.common.primitives.Ints;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Class to parse raw output of {@code adb logcat -v long} to {@link LogCatMessage} objects.
- */
-public final class LogCatMessageParser {
- private LogLevel mCurLogLevel = LogLevel.WARN;
- private String mCurPid = "?";
- private String mCurTid = "?";
- private String mCurTag = "?";
- private String mCurTime = "?:??";
-
- /**
- * This pattern is meant to parse the first line of a log message with the option
- * 'logcat -v long'. The first line represents the date, tag, severity, etc.. while the
- * following lines are the message (can be several lines).<br>
- * This first line looks something like:<br>
- * {@code "[ 00-00 00:00:00.000 <pid>:0x<???> <severity>/<tag>]"}
- * <br>
- * Note: severity is one of V, D, I, W, E, A? or F. However, there doesn't seem to be
- * a way to actually generate an A (assert) message. Log.wtf is supposed to generate
- * a message with severity A, however it generates the undocumented F level. In
- * such a case, the parser will change the level from F to A.<br>
- * Note: the fraction of second value can have any number of digit.<br>
- * Note: the tag should be trimmed as it may have spaces at the end.
- */
- private static final Pattern sLogHeaderPattern = Pattern.compile(
- "^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)"
- + "\\s+(\\d*):\\s*(\\S+)\\s([VDIWEAF])/(.*)\\]$");
-
- /**
- * Parse a list of strings into {@link LogCatMessage} objects. This method
- * maintains state from previous calls regarding the last seen header of
- * logcat messages.
- * @param lines list of raw strings obtained from logcat -v long
- * @param device device from which these log messages have been received
- * @return list of LogMessage objects parsed from the input
- */
- @NonNull
- public List<LogCatMessage> processLogLines(String[] lines, IDevice device) {
- List<LogCatMessage> messages = new ArrayList<LogCatMessage>(lines.length);
-
- for (String line : lines) {
- if (line.isEmpty()) {
- continue;
- }
-
- Matcher matcher = sLogHeaderPattern.matcher(line);
- if (matcher.matches()) {
- mCurTime = matcher.group(1);
- mCurPid = matcher.group(2);
- mCurTid = matcher.group(3);
- mCurLogLevel = LogLevel.getByLetterString(matcher.group(4));
- mCurTag = matcher.group(5).trim();
-
- /* LogLevel doesn't support messages with severity "F". Log.wtf() is supposed
- * to generate "A", but generates "F". */
- if (mCurLogLevel == null && matcher.group(4).equals("F")) {
- mCurLogLevel = LogLevel.ASSERT;
- }
- } else {
- String pkgName = ""; //$NON-NLS-1$
- Integer pid = Ints.tryParse(mCurPid);
- if (pid != null && device != null) {
- pkgName = device.getClientName(pid);
- }
- LogCatMessage m = new LogCatMessage(mCurLogLevel, mCurPid, mCurTid,
- pkgName, mCurTag, mCurTime, line);
- messages.add(m);
- }
- }
-
- return messages;
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatReceiverTask.java b/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatReceiverTask.java
deleted file mode 100644
index b5fd36e..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatReceiverTask.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ddmlib.logcat;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.concurrency.GuardedBy;
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.IShellOutputReceiver;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.MultiLineReceiver;
-import com.android.ddmlib.ShellCommandUnresponsiveException;
-import com.android.ddmlib.TimeoutException;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class LogCatReceiverTask implements Runnable {
- private static final String LOGCAT_COMMAND = "logcat -v long"; //$NON-NLS-1$
- private static final int DEVICE_POLL_INTERVAL_MSEC = 1000;
-
- private static final LogCatMessage sDeviceDisconnectedMsg =
- errorMessage("Device disconnected: 1");
- private static final LogCatMessage sConnectionTimeoutMsg =
- errorMessage("LogCat Connection timed out");
- private static final LogCatMessage sConnectionErrorMsg =
- errorMessage("LogCat Connection error");
-
- private final IDevice mDevice;
- private final LogCatOutputReceiver mReceiver;
- private final LogCatMessageParser mParser;
- private final AtomicBoolean mCancelled;
-
- @GuardedBy("this")
- private final Set<LogCatListener> mListeners = new HashSet<LogCatListener>();
-
- public LogCatReceiverTask(@NonNull IDevice device) {
- mDevice = device;
-
- mReceiver = new LogCatOutputReceiver();
- mParser = new LogCatMessageParser();
- mCancelled = new AtomicBoolean();
- }
-
- @Override
- public void run() {
- // wait while device comes online
- while (!mDevice.isOnline()) {
- try {
- Thread.sleep(DEVICE_POLL_INTERVAL_MSEC);
- } catch (InterruptedException e) {
- return;
- }
- }
-
- try {
- mDevice.executeShellCommand(LOGCAT_COMMAND, mReceiver, 0);
- } catch (TimeoutException e) {
- notifyListeners(Collections.singletonList(sConnectionTimeoutMsg));
- } catch (AdbCommandRejectedException ignored) {
- // will not be thrown as long as the shell supports logcat
- } catch (ShellCommandUnresponsiveException ignored) {
- // this will not be thrown since the last argument is 0
- } catch (IOException e) {
- notifyListeners(Collections.singletonList(sConnectionErrorMsg));
- }
-
- notifyListeners(Collections.singletonList(sDeviceDisconnectedMsg));
- }
-
- public void stop() {
- mCancelled.set(true);
- }
-
- private class LogCatOutputReceiver extends MultiLineReceiver {
- public LogCatOutputReceiver() {
- setTrimLine(false);
- }
-
- /** Implements {@link IShellOutputReceiver#isCancelled() }. */
- @Override
- public boolean isCancelled() {
- return mCancelled.get();
- }
-
- @Override
- public void processNewLines(String[] lines) {
- if (!mCancelled.get()) {
- processLogLines(lines);
- }
- }
-
- private void processLogLines(String[] lines) {
- List<LogCatMessage> newMessages = mParser.processLogLines(lines, mDevice);
- if (!newMessages.isEmpty()) {
- notifyListeners(newMessages);
- }
- }
- }
-
- public synchronized void addLogCatListener(LogCatListener l) {
- mListeners.add(l);
- }
-
- public synchronized void removeLogCatListener(LogCatListener l) {
- mListeners.remove(l);
- }
-
- private synchronized void notifyListeners(List<LogCatMessage> messages) {
- for (LogCatListener l: mListeners) {
- l.log(messages);
- }
- }
-
- private static LogCatMessage errorMessage(String msg) {
- return new LogCatMessage(LogLevel.ERROR, "", "", "", "", "", msg);
- }
-}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java b/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
deleted file mode 100644
index a8f1491..0000000
--- a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2008 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.ddmlib.testrunner;
-
-import com.android.annotations.NonNull;
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.IShellEnabledDevice;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.ShellCommandUnresponsiveException;
-import com.android.ddmlib.TimeoutException;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Hashtable;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Runs a Android test command remotely and reports results.
- */
-public class RemoteAndroidTestRunner implements IRemoteAndroidTestRunner {
-
- private final String mPackageName;
- private final String mRunnerName;
- private IShellEnabledDevice mRemoteDevice;
- // default to no timeout
- private long mMaxTimeToOutputResponse = 0;
- private TimeUnit mMaxTimeUnits = TimeUnit.MILLISECONDS;
- private String mRunName = null;
-
- /** map of name-value instrumentation argument pairs */
- private Map<String, String> mArgMap;
- private InstrumentationResultParser mParser;
-
- private static final String LOG_TAG = "RemoteAndroidTest";
- private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner";
-
- private static final char CLASS_SEPARATOR = ',';
- private static final char METHOD_SEPARATOR = '#';
- private static final char RUNNER_SEPARATOR = '/';
-
- // defined instrumentation argument names
- private static final String CLASS_ARG_NAME = "class";
- private static final String LOG_ARG_NAME = "log";
- private static final String DEBUG_ARG_NAME = "debug";
- private static final String COVERAGE_ARG_NAME = "coverage";
- private static final String PACKAGE_ARG_NAME = "package";
- private static final String SIZE_ARG_NAME = "size";
- private static final String DELAY_MSEC_ARG_NAME = "delay_msec";
- private String mRunOptions = "";
-
- private static final int TEST_COLLECTION_TIMEOUT = 2 * 60 * 1000; //2 min
-
- /**
- * Creates a remote Android test runner.
- *
- * @param packageName the Android application package that contains the tests to run
- * @param runnerName the instrumentation test runner to execute. If null, will use default
- * runner
- * @param remoteDevice the Android device to execute tests on
- */
- public RemoteAndroidTestRunner(String packageName,
- String runnerName,
- IShellEnabledDevice remoteDevice) {
-
- mPackageName = packageName;
- mRunnerName = runnerName;
- mRemoteDevice = remoteDevice;
- mArgMap = new Hashtable<String, String>();
- }
-
- /**
- * Alternate constructor. Uses default instrumentation runner.
- *
- * @param packageName the Android application package that contains the tests to run
- * @param remoteDevice the Android device to execute tests on
- */
- public RemoteAndroidTestRunner(String packageName,
- IShellEnabledDevice remoteDevice) {
- this(packageName, null, remoteDevice);
- }
-
- @Override
- public String getPackageName() {
- return mPackageName;
- }
-
- @Override
- public String getRunnerName() {
- if (mRunnerName == null) {
- return DEFAULT_RUNNER_NAME;
- }
- return mRunnerName;
- }
-
- /**
- * Returns the complete instrumentation component path.
- */
- private String getRunnerPath() {
- return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
- }
-
- @Override
- public void setClassName(String className) {
- addInstrumentationArg(CLASS_ARG_NAME, className);
- }
-
- @Override
- public void setClassNames(String[] classNames) {
- StringBuilder classArgBuilder = new StringBuilder();
-
- for (int i = 0; i < classNames.length; i++) {
- if (i != 0) {
- classArgBuilder.append(CLASS_SEPARATOR);
- }
- classArgBuilder.append(classNames[i]);
- }
- setClassName(classArgBuilder.toString());
- }
-
- @Override
- public void setMethodName(String className, String testName) {
- setClassName(className + METHOD_SEPARATOR + testName);
- }
-
- @Override
- public void setTestPackageName(String packageName) {
- addInstrumentationArg(PACKAGE_ARG_NAME, packageName);
- }
-
- @Override
- public void addInstrumentationArg(String name, String value) {
- if (name == null || value == null) {
- throw new IllegalArgumentException("name or value arguments cannot be null");
- }
- mArgMap.put(name, value);
- }
-
- @Override
- public void removeInstrumentationArg(String name) {
- if (name == null) {
- throw new IllegalArgumentException("name argument cannot be null");
- }
- mArgMap.remove(name);
- }
-
- @Override
- public void addBooleanArg(String name, boolean value) {
- addInstrumentationArg(name, Boolean.toString(value));
- }
-
- @Override
- public void setLogOnly(boolean logOnly) {
- addBooleanArg(LOG_ARG_NAME, logOnly);
- }
-
- @Override
- public void setDebug(boolean debug) {
- addBooleanArg(DEBUG_ARG_NAME, debug);
- }
-
- @Override
- public void setCoverage(boolean coverage) {
- addBooleanArg(COVERAGE_ARG_NAME, coverage);
- }
-
- @Override
- public void setTestSize(TestSize size) {
- addInstrumentationArg(SIZE_ARG_NAME, size.getRunnerValue());
- }
-
- @Override
- public void setTestCollection(boolean collect) {
- if (collect) {
- // skip test execution
- setLogOnly(true);
- // force a timeout for test collection
- setMaxTimeToOutputResponse(TEST_COLLECTION_TIMEOUT, TimeUnit.MILLISECONDS);
- if (getApiLevel() < 16 ) {
- // On older platforms, collecting tests can fail for large volume of tests.
- // Insert a small delay between each test to prevent this
- addInstrumentationArg(DELAY_MSEC_ARG_NAME, "15" /* ms */);
- }
- } else {
- setLogOnly(false);
- // restore timeout to its original set value
- setMaxTimeToOutputResponse(mMaxTimeToOutputResponse, mMaxTimeUnits);
- if (getApiLevel() < 16 ) {
- // remove delay
- removeInstrumentationArg(DELAY_MSEC_ARG_NAME);
- }
- }
- }
-
- /**
- * Attempts to retrieve the Api level of the Android device
- * @return the api level or -1 if the communication with the device wasn't successful
- */
- private int getApiLevel() {
- try {
- return Integer.parseInt(mRemoteDevice.getSystemProperty(
- IDevice.PROP_BUILD_API_LEVEL).get());
- } catch (Exception e) {
- return -1;
- }
- }
-
- @Override
- public void setMaxtimeToOutputResponse(int maxTimeToOutputResponse) {
- setMaxTimeToOutputResponse(maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
- }
-
- @Override
- public void setMaxTimeToOutputResponse(long maxTimeToOutputResponse, TimeUnit maxTimeUnits) {
- mMaxTimeToOutputResponse = maxTimeToOutputResponse;
- mMaxTimeUnits = maxTimeUnits;
- }
-
- @Override
- public void setRunName(String runName) {
- mRunName = runName;
- }
-
- @Override
- public void run(ITestRunListener... listeners)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
- run(Arrays.asList(listeners));
- }
-
- @Override
- public void run(Collection<ITestRunListener> listeners)
- throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
- IOException {
- final String runCaseCommandStr = String.format("am instrument -w -r %1$s %2$s %3$s",
- getRunOptions(), getArgsCommand(), getRunnerPath());
- Log.i(LOG_TAG, String.format("Running %1$s on %2$s", runCaseCommandStr,
- mRemoteDevice.getName()));
- String runName = mRunName == null ? mPackageName : mRunName;
- mParser = new InstrumentationResultParser(runName, listeners);
-
- try {
- mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser, mMaxTimeToOutputResponse,
- mMaxTimeUnits);
- } catch (IOException e) {
- Log.w(LOG_TAG, String.format("IOException %1$s when running tests %2$s on %3$s",
- e.toString(), getPackageName(), mRemoteDevice.getName()));
- // rely on parser to communicate results to listeners
- mParser.handleTestRunFailed(e.toString());
- throw e;
- } catch (ShellCommandUnresponsiveException e) {
- Log.w(LOG_TAG, String.format(
- "ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s",
- e.toString(), getPackageName(), mRemoteDevice.getName()));
- mParser.handleTestRunFailed(String.format(
- "Failed to receive adb shell test output within %1$d ms. " +
- "Test may have timed out, or adb connection to device became unresponsive",
- mMaxTimeToOutputResponse));
- throw e;
- } catch (TimeoutException e) {
- Log.w(LOG_TAG, String.format(
- "TimeoutException when running tests %1$s on %2$s", getPackageName(),
- mRemoteDevice.getName()));
- mParser.handleTestRunFailed(e.toString());
- throw e;
- } catch (AdbCommandRejectedException e) {
- Log.w(LOG_TAG, String.format(
- "AdbCommandRejectedException %1$s when running tests %2$s on %3$s",
- e.toString(), getPackageName(), mRemoteDevice.getName()));
- mParser.handleTestRunFailed(e.toString());
- throw e;
- }
- }
-
- /**
- * Returns options for the am instrument command.
- */
- @NonNull public String getRunOptions() {
- return mRunOptions;
- }
-
- /**
- * Sets options for the am instrument command.
- * See com/android/commands/am/Am.java for full list of options.
- */
- public void setRunOptions(@NonNull String options) {
- mRunOptions = options;
- }
-
- @Override
- public void cancel() {
- if (mParser != null) {
- mParser.cancel();
- }
- }
-
- /**
- * Returns the full instrumentation command line syntax for the provided instrumentation
- * arguments.
- * Returns an empty string if no arguments were specified.
- */
- private String getArgsCommand() {
- StringBuilder commandBuilder = new StringBuilder();
- for (Entry<String, String> argPair : mArgMap.entrySet()) {
- final String argCmd = String.format(" -e %1$s %2$s", argPair.getKey(),
- argPair.getValue());
- commandBuilder.append(argCmd);
- }
- return commandBuilder.toString();
- }
-}
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatFilterTest.java b/base/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatFilterTest.java
deleted file mode 100644
index 9940a11..0000000
--- a/base/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatFilterTest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ddmlib.logcat;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.logcat.LogCatFilter;
-import com.android.ddmlib.logcat.LogCatMessage;
-
-import java.util.List;
-
-import junit.framework.TestCase;
-
-public class LogCatFilterTest extends TestCase {
- public void testFilterByLogLevel() {
- LogCatFilter filter = new LogCatFilter("",
- "", "", "", "", LogLevel.DEBUG);
-
- /* filter message below filter's log level */
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "", "", "");
- assertEquals(false, filter.matches(msg));
-
- /* do not filter message above filter's log level */
- msg = new LogCatMessage(LogLevel.ERROR,
- "", "", "", "", "", "");
- assertEquals(true, filter.matches(msg));
- }
-
- public void testFilterByPid() {
- LogCatFilter filter = new LogCatFilter("",
- "", "", "123", "", LogLevel.VERBOSE);
-
- /* show message with pid matching filter */
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "123", "", "", "", "", "");
- assertEquals(true, filter.matches(msg));
-
- /* don't show message with pid not matching filter */
- msg = new LogCatMessage(LogLevel.VERBOSE,
- "12", "", "", "", "", "");
- assertEquals(false, filter.matches(msg));
- }
-
- public void testFilterByAppNameRegex() {
- LogCatFilter filter = new LogCatFilter("",
- "", "", "", "dalvik.*", LogLevel.VERBOSE);
-
- /* show message with pid matching filter */
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "dalvikvm1", "", "", "");
- assertEquals(true, filter.matches(msg));
-
- /* don't show message with pid not matching filter */
- msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "system", "", "", "");
- assertEquals(false, filter.matches(msg));
- }
-
- public void testFilterByTagRegex() {
- LogCatFilter filter = new LogCatFilter("",
- "tag.*", "", "", "", LogLevel.VERBOSE);
-
- /* show message with tag matching filter */
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "tag123", "", "");
- assertEquals(true, filter.matches(msg));
-
- msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "ta123", "", "");
- assertEquals(false, filter.matches(msg));
- }
-
- public void testFilterByTextRegex() {
- LogCatFilter filter = new LogCatFilter("",
- "", "text.*", "", "", LogLevel.VERBOSE);
-
- /* show message with text matching filter */
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "", "", "text123");
- assertEquals(true, filter.matches(msg));
-
- msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "", "", "te123");
- assertEquals(false, filter.matches(msg));
- }
-
- public void testMatchingText() {
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- "message with word1 and word2"); //$NON-NLS-1$
- assertEquals(true, search("word1 with", msg)); //$NON-NLS-1$
- assertEquals(true, search("text:w.* ", msg)); //$NON-NLS-1$
- assertEquals(false, search("absent", msg)); //$NON-NLS-1$
- }
-
- public void testTagKeyword() {
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "tag", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- "sample message"); //$NON-NLS-1$
- assertEquals(false, search("t.*", msg)); //$NON-NLS-1$
- assertEquals(true, search("tag:t.*", msg)); //$NON-NLS-1$
- }
-
- public void testPidKeyword() {
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "123", "", "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- "sample message"); //$NON-NLS-1$
- assertEquals(false, search("123", msg)); //$NON-NLS-1$
- assertEquals(true, search("pid:123", msg)); //$NON-NLS-1$
- }
-
- public void testAppNameKeyword() {
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "dalvik", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- "sample message"); //$NON-NLS-1$
- assertEquals(false, search("dalv.*", msg)); //$NON-NLS-1$
- assertEquals(true, search("app:dal.*k", msg)); //$NON-NLS-1$
- }
-
- public void testCaseSensitivity() {
- LogCatMessage msg = new LogCatMessage(LogLevel.VERBOSE,
- "", "", "", "", "",
- "Sample message");
-
- // if regex has an upper case character, it should be
- // treated as a case sensitive search
- assertEquals(false, search("Message", msg));
-
- // if regex is all lower case, then it should be a
- // case insensitive search
- assertEquals(true, search("sample", msg));
- }
-
- /**
- * Helper method: search if the query string matches the message.
- * @param query words to search for
- * @param message text to search in
- * @return true if the encoded query is present in message
- */
- private boolean search(String query, LogCatMessage message) {
- List<LogCatFilter> filters = LogCatFilter.fromString(query,
- LogLevel.VERBOSE);
-
- /* all filters have to match for the query to match */
- for (LogCatFilter f : filters) {
- if (!f.matches(message)) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatMessageParserTest.java b/base/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatMessageParserTest.java
deleted file mode 100644
index cdd55c5..0000000
--- a/base/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatMessageParserTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ddmlib.logcat;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.logcat.LogCatMessage;
-import com.android.ddmlib.logcat.LogCatMessageParser;
-
-import java.util.List;
-
-import junit.framework.TestCase;
-
-/**
- * Unit tests for {@link LogCatMessageParser}.
- */
-public final class LogCatMessageParserTest extends TestCase {
- private List<LogCatMessage> mParsedMessages;
-
- /** A list of messages generated with the following code:
- * <pre>
- * {@code
- * Log.d("dtag", "debug message");
- * Log.e("etag", "error message");
- * Log.i("itag", "info message");
- * Log.v("vtag", "verbose message");
- * Log.w("wtag", "warning message");
- * Log.wtf("wtftag", "wtf message");
- * Log.d("dtag", "debug message");
- * }
- * </pre>
- * Note: On Android 2.3, Log.wtf doesn't really generate the message.
- * It only produces the message header, but swallows the message tag.
- * This string has been modified to include the message.
- */
- private static final String[] MESSAGES = new String[] {
- "[ 08-11 19:11:07.132 495:0x1ef D/dtag ]", //$NON-NLS-1$
- "debug message", //$NON-NLS-1$
- "[ 08-11 19:11:07.132 495: 234 E/etag ]", //$NON-NLS-1$
- "error message", //$NON-NLS-1$
- "[ 08-11 19:11:07.132 495:0x1ef I/itag ]", //$NON-NLS-1$
- "info message", //$NON-NLS-1$
- "[ 08-11 19:11:07.132 495:0x1ef V/vtag ]", //$NON-NLS-1$
- "verbose message", //$NON-NLS-1$
- "[ 08-11 19:11:07.132 495:0x1ef W/wtag ]", //$NON-NLS-1$
- "warning message", //$NON-NLS-1$
- "[ 08-11 19:11:07.132 495:0x1ef F/wtftag ]", //$NON-NLS-1$
- "wtf message", //$NON-NLS-1$
- "[ 08-11 21:15:35.7524 540:0x21c D/dtag ]", //$NON-NLS-1$
- "debug message", //$NON-NLS-1$
- };
-
- @Override
- protected void setUp() throws Exception {
- LogCatMessageParser parser = new LogCatMessageParser();
- mParsedMessages = parser.processLogLines(MESSAGES, null);
- }
-
- /** Check that the correct number of messages are received. */
- public void testMessageCount() {
- assertEquals(7, mParsedMessages.size());
- }
-
- /** Check the log level in a few of the parsed messages. */
- public void testLogLevel() {
- assertEquals(mParsedMessages.get(0).getLogLevel(), LogLevel.DEBUG);
- assertEquals(mParsedMessages.get(5).getLogLevel(), LogLevel.ASSERT);
- }
-
- /** Check the parsed tag. */
- public void testTag() {
- assertEquals(mParsedMessages.get(1).getTag(), "etag"); //$NON-NLS-1$
- }
-
- /** Check the time field. */
- public void testTime() {
- assertEquals(mParsedMessages.get(6).getTime(), "08-11 21:15:35.7524"); //$NON-NLS-1$
- }
-
- /** Check the message field. */
- public void testMessage() {
- assertEquals(mParsedMessages.get(2).getMessage(), MESSAGES[5]);
- }
-
- public void testTid() {
- assertEquals(mParsedMessages.get(0).getTid(), Integer.toString(0x1ef));
- assertEquals(mParsedMessages.get(1).getTid(), "234");
- }
-}
diff --git a/base/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd b/base/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd
deleted file mode 100644
index 2017a6a..0000000
--- a/base/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd
+++ /dev/null
@@ -1,954 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.
--->
-
-<xsd:schema
- targetNamespace="http://schemas.android.com/sdk/devices/2"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:c="http://schemas.android.com/sdk/devices/2"
- elementFormDefault="qualified"
- attributeFormDefault="unqualified"
- version="1">
-
- <!-- The devices element contains a collection of device definitions.
-
- History:
- - v1 is used by the dvlib in Tools r20-22..
-
- - v2 is used by the dvlib in Tools r23.
- - It adds support for new ABIs arm64-v8a, x86_64 and mips64.
- -->
- <xsd:element name="devices" type="c:devicesType" />
-
- <xsd:complexType name="devicesType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The "devices" element is the root element of this schema.
-
- It must contain one or more "device" elements that each define the configurations
- and states available for a given device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="device" minOccurs="1" maxOccurs="unbounded">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- A device element contains one hardware profile for a device, along with
- 1 or more software profiles and 1 or more states. Each software profile
- defines the supported software for a given API release, and each state
- profile defines a different possible state of the device (screen in
- portrait orientation, screen in landscape orientation with the keyboard
- out, etc.)
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="name" type= "xsd:token" />
- <xsd:element name="id" type= "xsd:token" minOccurs="0" />
- <xsd:element name="manufacturer" type= "xsd:token" />
- <xsd:element name="meta" type= "c:metaType" minOccurs="0" />
- <xsd:element name="hardware" type= "c:hardwareType" />
- <xsd:element name="software" type= "c:softwareType"
- maxOccurs="unbounded" />
- <xsd:element name="state" type= "c:stateType"
- maxOccurs="unbounded" />
- <xsd:element name="tag-id" type= "c:idType" minOccurs="0" />
- <xsd:element name="boot-props" type= "c:bootPropsType" minOccurs="0" />
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:simpleType name="idType">
- <xsd:annotation>
- <xsd:documentation>
- A tag string for a system image can only be a simple alphanumeric string.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:pattern value="[a-zA-Z0-9_-]+"/>
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:complexType name="bootPropsType" >
- <xsd:annotation>
- <xsd:documentation>
- List of boot properties.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence minOccurs="0" maxOccurs="unbounded">
- <xsd:element name="boot-prop">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="prop-name">
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:pattern value="[^\n\r\t =]+"/>
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="prop-value" type= "xsd:string" />
- </xsd:all>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="hardwareType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The hardwareType contains all of the hardware information for
- a given device. This includes things like the GPU type, screen
- size, mic presence, etc.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="screen" type= "c:screenType" />
- <xsd:element name="networking" type= "c:networkingType" />
- <xsd:element name="sensors" type= "c:sensorsType" />
- <xsd:element name="mic" type= "c:micType" />
- <xsd:element name="camera" type= "c:cameraType"
- minOccurs="0" maxOccurs="unbounded" />
- <xsd:element name="keyboard" type= "c:keyboardType" />
- <xsd:element name="nav" type= "c:navType" />
- <xsd:element name="ram" type= "c:ramType" />
- <xsd:element name="buttons" type= "c:buttonsType" />
- <xsd:element name="internal-storage" type= "c:internalStorageType" />
- <xsd:element name="removable-storage" type= "c:removableStorageType" />
- <xsd:element name="cpu" type= "c:cpuType" />
- <xsd:element name="gpu" type= "c:gpuType" />
- <xsd:element name="abi" type= "c:abiType" />
- <xsd:element name="dock" type= "c:dockType" />
- <xsd:element name="power-type" type= "c:powerType" />
- <xsd:element name="skin" minOccurs="0">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Path to a custom skin directory.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="softwareType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The softwareType contains all of the device's software
- information for a given API version. This includes things like
- live wallpaper support, OpenGL version, etc.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="api-level">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies which API version(s) this this element is
- defining. This can in the form of a single number
- or a range of low to high, separated with a dash and
- with either limit missing. The default lower limit is
- one, and the default upper limit is unbounded.
- The following are valid:
- 10
- 7-10
- -10
- 7-
- -
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:pattern value="[\d]*-[\d]*|[\d]+" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="live-wallpaper-support" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device supports live wallpapers.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
-
- <xsd:element name="bluetooth-profiles">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies all of the available Bluetooth profiles.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:NMTOKEN">
- <xsd:enumeration value="A2DP" />
- <xsd:enumeration value="ATT" />
- <xsd:enumeration value="AVRCP" />
- <xsd:enumeration value="AVDTP" />
- <xsd:enumeration value="BIP" />
- <xsd:enumeration value="BPP" />
- <xsd:enumeration value="CIP" />
- <xsd:enumeration value="CTP" />
- <xsd:enumeration value="DIP" />
- <xsd:enumeration value="DUN" />
- <xsd:enumeration value="FAX" />
- <xsd:enumeration value="FTP" />
- <xsd:enumeration value="GAVDP" />
- <xsd:enumeration value="GAP" />
- <xsd:enumeration value="GATT" />
- <xsd:enumeration value="GOEP" />
- <xsd:enumeration value="HCRP" />
- <xsd:enumeration value="HDP" />
- <xsd:enumeration value="HFP" />
- <xsd:enumeration value="HID" />
- <xsd:enumeration value="HSP" />
- <xsd:enumeration value="ICP" />
- <xsd:enumeration value="LAP" />
- <xsd:enumeration value="MAP" />
- <xsd:enumeration value="OPP" />
- <xsd:enumeration value="PAN" />
- <xsd:enumeration value="PBA" />
- <xsd:enumeration value="PBAP" />
- <xsd:enumeration value="SPP" />
- <xsd:enumeration value="SDAP" />
- <xsd:enumeration value="SAP" />
- <xsd:enumeration value="SIM" />
- <xsd:enumeration value="rSAP" />
- <xsd:enumeration value="SYNCH" />
- <xsd:enumeration value="VDP" />
- <xsd:enumeration value="WAPB" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="gl-version">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the OpenGL version supported for this release.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <xsd:pattern value="[0-9]\.[0-9]" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="gl-extensions">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies all of the supported OpenGL extensions for
- this release.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:list itemType="xsd:NMTOKEN" />
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="status-bar" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device has a status bar in this
- software configuration.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="stateType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The stateType contains the information for a given state of
- of the device. States include things like portrait mode,
- landscape with the keyboard exposed, etc. States can also
- modify the hardware attributes of a device. For instance, if
- sliding out the keyboard increased the available screen
- real estate, you can define a new screenType to override the
- default one defined in the device's hardwareType.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="description" type="xsd:token">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- A description of the defined state.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
-
- <xsd:element name="screen-orientation">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines the orientation of the screen. Use square if
- the device's screen has equal height and width,
- otherwise use landscape or portrait.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="port" />
- <xsd:enumeration value="land" />
- <xsd:enumeration value="square" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="keyboard-state">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines the state of the keyboard. If the device has no
- keyboard use keysoft, otherwise use keysexposed or keyshidden.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="keyssoft" />
- <xsd:enumeration value="keyshidden" />
- <xsd:enumeration value="keysexposed" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="nav-state">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines the state of the primary non-touchscreen
- navigation hardware on the devices. If the device
- doesn't have non-touchscreen navigation hardware use
- nonav, otherwise use navexposed or navhidden.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="nonav" />
- <xsd:enumeration value="navhidden" />
- <xsd:enumeration value="navexposed" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="screen" type="c:screenType" minOccurs="0" />
- <xsd:element name="networking" type="c:networkingType"
- minOccurs="0" />
- <xsd:element name="sensors" type="c:sensorsType" minOccurs="0" />
- <xsd:element name="mic" type="c:micType" minOccurs="0" />
- <xsd:element name="camera" type="c:cameraType"
- minOccurs="0" maxOccurs="unbounded" />
- <xsd:element name="keyboard" type="c:keyboardType" minOccurs="0" />
- <xsd:element name="nav" type="c:navType" minOccurs="0" />
- <xsd:element name="ram" type="c:ramType" minOccurs="0" />
- <xsd:element name="buttons" type="c:buttonsType" minOccurs="0" />
- <xsd:element name="internal-storage" type="c:internalStorageType"
- minOccurs="0" />
- <xsd:element name="removable-storage" type="c:removableStorageType"
- minOccurs="0" />
- <xsd:element name="cpu" type="c:cpuType" minOccurs="0" />
- <xsd:element name="gpu" type="c:gpuType" minOccurs="0" />
- <xsd:element name="abi" type="c:abiType" minOccurs="0" />
- <xsd:element name="dock" type="c:dockType" minOccurs="0" />
- <xsd:element name="power-type" type="c:powerType"
- minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:token" />
- <xsd:attribute name="default" use="optional" type="xsd:boolean" />
- </xsd:complexType>
-
- <xsd:complexType name="metaType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Details where more device information can be found, such as
- icons and frame images.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="icons" minOccurs="0">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Contains the relative paths to the icon files for this
- device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="sixty-four" type="xsd:normalizedString">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Relative path for the 64x64 icon.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="sixteen" type="xsd:normalizedString"
- minOccurs="0">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Relative path for the 16x16 icon.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="frame" minOccurs="0">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Contains information about the frame for the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="path"
- type="xsd:normalizedString">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The relative path to the emulator frame for
- the device.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="portrait-x-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the x direction,
- in portrait mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="portrait-y-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the y direction,
- in portrait mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="landscape-x-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the x direction,
- in landscape mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- <xsd:element name="landscape-y-offset"
- type="xsd:nonNegativeInteger">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- The offset for the frame in the y direction,
- in landscape mode.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:complexType name="screenType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Contains the specifications for the device's screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="screen-size">
- <xsd:simpleType>
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the class of the screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="small" />
- <xsd:enumeration value="normal" />
- <xsd:enumeration value="large" />
- <xsd:enumeration value="xlarge" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="diagonal-length">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the diagonal length of the screen in inches.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <!-- Negative lengths are not valid -->
- <xsd:minInclusive value="0" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="pixel-density">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the screen density of the device. The
- medium density of traditional HVGA screens (mdpi)
- is defined to be approximately 160dpi; low density
- (ldpi) is 120, and high density (hdpi) is 240. There
- is thus a 4:3 scaling factor between each density,
- so a 9x9 bitmap in ldpi would be 12x12 in mdpi and
- 16x16 in hdpi.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="ldpi" />
- <xsd:enumeration value="mdpi" />
- <xsd:enumeration value="tvdpi" />
- <xsd:enumeration value="hdpi" />
- <xsd:enumeration value="280dpi" />
- <xsd:enumeration value="xhdpi" />
- <xsd:enumeration value="360dpi" />
- <xsd:enumeration value="400dpi" />
- <xsd:enumeration value="xxhdpi" />
- <xsd:enumeration value="560dpi" />
- <xsd:enumeration value="xxxhdpi" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="screen-ratio">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the configuration is for a taller or
- wider than traditional screen. This is based purely on
- the aspect ratio of the screen: QVGA, HVGA, and VGA are
- notlong; WQVGA, WVGA, FWVGA are long. Note that long may
- mean either wide or tall, depending on the current
- orientation.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="notlong" />
- <xsd:enumeration value="long" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="dimensions">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the device screen resolution in pixels.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="x-dimension">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the x-dimension's resolution in
- pixels.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:positiveInteger" />
- </xsd:simpleType>
- </xsd:element>
- <xsd:element name="y-dimension">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the y-dimension's resolution in
- pixels.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:positiveInteger" />
- </xsd:simpleType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
-
- <xsd:element name="xdpi">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the actual density in X of the device screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <!-- Negative DPIs are not valid -->
- <xsd:minInclusive value="0" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="ydpi">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the actual density in Y of the device screen.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:decimal">
- <!-- Negative DPIs are not valid -->
- <xsd:minInclusive value="0" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="touch">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the touch properties of the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="multitouch">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the multitouch capabilities of the
- device. This can be none if multitouch is
- not supported, basic if the device can track
- only basic two finger gestures, distinct if
- the device can track two or more fingers
- simultaneously, or jazz-hands if the device
- can track 5 or more fingers simultaneously.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="none" />
- <xsd:enumeration value="basic" />
- <xsd:enumeration value="distinct" />
- <xsd:enumeration value="jazz-hands" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="mechanism">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the mechanism the device was
- created for.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="notouch" />
- <xsd:enumeration value="stylus" />
- <xsd:enumeration value="finger" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="screen-type">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the type of touch screen on the
- device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="notouch" />
- <xsd:enumeration value="capacitive" />
- <xsd:enumeration value="resistive" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
-
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:simpleType name="networkingType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the available networking hardware.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="NFC" />
- <xsd:enumeration value="Bluetooth" />
- <xsd:enumeration value="Wifi" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="sensorsType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the available sensors.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="Accelerometer" />
- <xsd:enumeration value="Barometer" />
- <xsd:enumeration value="Compass" />
- <xsd:enumeration value="GPS" />
- <xsd:enumeration value="Gyroscope" />
- <xsd:enumeration value="LightSensor" />
- <xsd:enumeration value="ProximitySensor" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="micType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device has a mic or not.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:boolean" />
- </xsd:simpleType>
-
- <xsd:complexType name="cameraType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the attributes of the camera.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:sequence>
- <xsd:element name="location">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the location of the camera.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="front" />
- <xsd:enumeration value="back" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:element>
-
- <xsd:element name="autofocus" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the camera can autofocus
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
-
- <xsd:element name="flash" type="xsd:boolean">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the camera has flash.
- </xsd:documentation>
- </xsd:annotation>
- </xsd:element>
- </xsd:sequence>
- </xsd:complexType>
-
- <xsd:simpleType name="keyboardType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the type of keyboard on the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="qwerty" />
- <xsd:enumeration value="12key" />
- <xsd:enumeration value="nokeys" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="navType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the primary non-touchscreen navigation
- hardware on the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="dpad" />
- <xsd:enumeration value="trackball" />
- <xsd:enumeration value="wheel" />
- <xsd:enumeration value="nonav" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:complexType name="ramType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the amount of RAM on the device in the unit provided.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="xsd:positiveInteger">
- <xsd:attribute name="unit" type="c:storageUnitType" use="required" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
- <xsd:simpleType name="buttonsType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device has physical (hard) buttons
- (Home, Search, etc.), or uses soft buttons.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="hard" />
- <xsd:enumeration value="soft" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:complexType name="internalStorageType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- A list specifying the sizes of internal storage in
- the device, in the storage size unit provided.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="c:storageListType">
- <xsd:attribute name="unit" type="c:storageUnitType"
- use="required" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
- <xsd:complexType name="removableStorageType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the range of available removable storage sizes
- in the unit provided. A positive value indicates the device is
- available with that storage size included while a zero value
- indicates an empty storage slot.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:simpleContent>
- <xsd:extension base="c:storageListType">
- <xsd:attribute name="unit" type="c:storageUnitType"
- use="required" />
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-
- <xsd:simpleType name="storageListType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Defines a list for storage configurations such as internal or
- removable storage. A positive value indicates the the device
- has a storage unit of that size, while a zero value indicates
- there is an empty location for a storage unit (such as an empty
- SD card slot).
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:nonNegativeInteger" />
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
- <xsd:simpleType name="gpuType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the device's GPU.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:minLength value="1" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="cpuType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the device's CPU.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:minLength value="1" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="abiType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies which ABIs the device conforms to.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="armeabi" />
- <xsd:enumeration value="armeabi-v7a" />
- <xsd:enumeration value="arm64-v8a" />
- <xsd:enumeration value="x86" />
- <xsd:enumeration value="x86_64" />
- <xsd:enumeration value="mips" />
- <!-- TODO double-check this is appropriate value for mips64 -->
- <xsd:enumeration value="mips64" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="dockType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the official docks available for the device.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:list>
- <xsd:simpleType>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="desk" />
- <xsd:enumeration value="television" />
- <xsd:enumeration value="car" />
- </xsd:restriction>
- </xsd:simpleType>
- </xsd:list>
- </xsd:simpleType>
-
- <xsd:simpleType name="powerType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies whether the device is plugged in.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="plugged-in" />
- <xsd:enumeration value="battery" />
- </xsd:restriction>
- </xsd:simpleType>
-
- <xsd:simpleType name="storageUnitType">
- <xsd:annotation>
- <xsd:documentation xml:lang="en">
- Specifies the unit of storage. This can be MiB, GiB, etc.
- </xsd:documentation>
- </xsd:annotation>
- <xsd:restriction base="xsd:token">
- <xsd:enumeration value="B" />
- <xsd:enumeration value="KiB" />
- <xsd:enumeration value="MiB" />
- <xsd:enumeration value="GiB" />
- <xsd:enumeration value="TiB" />
- </xsd:restriction>
- </xsd:simpleType>
-
-</xsd:schema>
diff --git a/base/gradle/wrapper/gradle-wrapper.properties b/base/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 826eedc..0000000
--- a/base/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Wed Jan 20 15:55:01 CET 2016
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip
diff --git a/base/jobb/src/main/java/Twofish/Twofish_Algorithm.java b/base/jobb/src/main/java/Twofish/Twofish_Algorithm.java
deleted file mode 100644
index 330d68e..0000000
--- a/base/jobb/src/main/java/Twofish/Twofish_Algorithm.java
+++ /dev/null
@@ -1,835 +0,0 @@
-// $Id: $
-//
-// $Log: $
-// Revision 1.0 1998/03/24 raif
-// + start of history.
-//
-// $Endlog$
-/*
- * Copyright (c) 1997, 1998 Systemics Ltd on behalf of
- * the Cryptix Development Team. All rights reserved.
- */
-package Twofish;
-
-import java.io.PrintWriter;
-import java.security.InvalidKeyException;
-
-//...........................................................................
-/**
- * Twofish is an AES candidate algorithm. It is a balanced 128-bit Feistel
- * cipher, consisting of 16 rounds. In each round, a 64-bit S-box value is
- * computed from 64 bits of the block, and this value is xored into the other
- * half of the block. The two half-blocks are then exchanged, and the next
- * round begins. Before the first round, all input bits are xored with key-
- * dependent "whitening" subkeys, and after the final round the output bits
- * are xored with other key-dependent whitening subkeys; these subkeys are
- * not used anywhere else in the algorithm.<p>
- *
- * Twofish was submitted by Bruce Schneier, Doug Whiting, John Kelsey, Chris
- * Hall and David Wagner.<p>
- *
- * Reference:<ol>
- * <li>TWOFISH2.C -- Optimized C API calls for TWOFISH AES submission,
- * Version 1.00, April 1998, by Doug Whiting.</ol><p>
- *
- * <b>Copyright</b> © 1998
- * <a href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the
- * <a href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>.
- * <br>All rights reserved.<p>
- *
- * <b>$Revision: $</b>
- * @author Raif S. Naffah
- */
-public final class Twofish_Algorithm // implicit no-argument constructor
-{
-// Debugging methods and variables
-//...........................................................................
-
- static final String NAME = "Twofish_Algorithm";
- static final boolean IN = true, OUT = false;
-
- static final boolean DEBUG = Twofish_Properties.GLOBAL_DEBUG;
- static final int debuglevel = DEBUG ? Twofish_Properties.getLevel(NAME) : 0;
- static final PrintWriter err = DEBUG ? Twofish_Properties.getOutput() : null;
-
- static final boolean TRACE = Twofish_Properties.isTraceable(NAME);
-
- static void debug (String s) { err.println(">>> "+NAME+": "+s); }
- static void trace (boolean in, String s) {
- if (TRACE) err.println((in?"==> ":"<== ")+NAME+"."+s);
- }
- static void trace (String s) { if (TRACE) err.println("<=> "+NAME+"."+s); }
-
-
-// Constants and variables
-//...........................................................................
-
- static final int BLOCK_SIZE = 16; // bytes in a data-block
- private static final int ROUNDS = 16;
- private static final int MAX_ROUNDS = 16; // max # rounds (for allocating subkeys)
-
- /* Subkey array indices */
- private static final int INPUT_WHITEN = 0;
- private static final int OUTPUT_WHITEN = INPUT_WHITEN + BLOCK_SIZE/4;
- private static final int ROUND_SUBKEYS = OUTPUT_WHITEN + BLOCK_SIZE/4; // 2*(# rounds)
-
- private static final int TOTAL_SUBKEYS = ROUND_SUBKEYS + 2*MAX_ROUNDS;
-
- private static final int SK_STEP = 0x02020202;
- private static final int SK_BUMP = 0x01010101;
- private static final int SK_ROTL = 9;
-
- /** Fixed 8x8 permutation S-boxes */
- private static final byte[][] P = new byte[][] {
- { // p0
- (byte) 0xA9, (byte) 0x67, (byte) 0xB3, (byte) 0xE8,
- (byte) 0x04, (byte) 0xFD, (byte) 0xA3, (byte) 0x76,
- (byte) 0x9A, (byte) 0x92, (byte) 0x80, (byte) 0x78,
- (byte) 0xE4, (byte) 0xDD, (byte) 0xD1, (byte) 0x38,
- (byte) 0x0D, (byte) 0xC6, (byte) 0x35, (byte) 0x98,
- (byte) 0x18, (byte) 0xF7, (byte) 0xEC, (byte) 0x6C,
- (byte) 0x43, (byte) 0x75, (byte) 0x37, (byte) 0x26,
- (byte) 0xFA, (byte) 0x13, (byte) 0x94, (byte) 0x48,
- (byte) 0xF2, (byte) 0xD0, (byte) 0x8B, (byte) 0x30,
- (byte) 0x84, (byte) 0x54, (byte) 0xDF, (byte) 0x23,
- (byte) 0x19, (byte) 0x5B, (byte) 0x3D, (byte) 0x59,
- (byte) 0xF3, (byte) 0xAE, (byte) 0xA2, (byte) 0x82,
- (byte) 0x63, (byte) 0x01, (byte) 0x83, (byte) 0x2E,
- (byte) 0xD9, (byte) 0x51, (byte) 0x9B, (byte) 0x7C,
- (byte) 0xA6, (byte) 0xEB, (byte) 0xA5, (byte) 0xBE,
- (byte) 0x16, (byte) 0x0C, (byte) 0xE3, (byte) 0x61,
- (byte) 0xC0, (byte) 0x8C, (byte) 0x3A, (byte) 0xF5,
- (byte) 0x73, (byte) 0x2C, (byte) 0x25, (byte) 0x0B,
- (byte) 0xBB, (byte) 0x4E, (byte) 0x89, (byte) 0x6B,
- (byte) 0x53, (byte) 0x6A, (byte) 0xB4, (byte) 0xF1,
- (byte) 0xE1, (byte) 0xE6, (byte) 0xBD, (byte) 0x45,
- (byte) 0xE2, (byte) 0xF4, (byte) 0xB6, (byte) 0x66,
- (byte) 0xCC, (byte) 0x95, (byte) 0x03, (byte) 0x56,
- (byte) 0xD4, (byte) 0x1C, (byte) 0x1E, (byte) 0xD7,
- (byte) 0xFB, (byte) 0xC3, (byte) 0x8E, (byte) 0xB5,
- (byte) 0xE9, (byte) 0xCF, (byte) 0xBF, (byte) 0xBA,
- (byte) 0xEA, (byte) 0x77, (byte) 0x39, (byte) 0xAF,
- (byte) 0x33, (byte) 0xC9, (byte) 0x62, (byte) 0x71,
- (byte) 0x81, (byte) 0x79, (byte) 0x09, (byte) 0xAD,
- (byte) 0x24, (byte) 0xCD, (byte) 0xF9, (byte) 0xD8,
- (byte) 0xE5, (byte) 0xC5, (byte) 0xB9, (byte) 0x4D,
- (byte) 0x44, (byte) 0x08, (byte) 0x86, (byte) 0xE7,
- (byte) 0xA1, (byte) 0x1D, (byte) 0xAA, (byte) 0xED,
- (byte) 0x06, (byte) 0x70, (byte) 0xB2, (byte) 0xD2,
- (byte) 0x41, (byte) 0x7B, (byte) 0xA0, (byte) 0x11,
- (byte) 0x31, (byte) 0xC2, (byte) 0x27, (byte) 0x90,
- (byte) 0x20, (byte) 0xF6, (byte) 0x60, (byte) 0xFF,
- (byte) 0x96, (byte) 0x5C, (byte) 0xB1, (byte) 0xAB,
- (byte) 0x9E, (byte) 0x9C, (byte) 0x52, (byte) 0x1B,
- (byte) 0x5F, (byte) 0x93, (byte) 0x0A, (byte) 0xEF,
- (byte) 0x91, (byte) 0x85, (byte) 0x49, (byte) 0xEE,
- (byte) 0x2D, (byte) 0x4F, (byte) 0x8F, (byte) 0x3B,
- (byte) 0x47, (byte) 0x87, (byte) 0x6D, (byte) 0x46,
- (byte) 0xD6, (byte) 0x3E, (byte) 0x69, (byte) 0x64,
- (byte) 0x2A, (byte) 0xCE, (byte) 0xCB, (byte) 0x2F,
- (byte) 0xFC, (byte) 0x97, (byte) 0x05, (byte) 0x7A,
- (byte) 0xAC, (byte) 0x7F, (byte) 0xD5, (byte) 0x1A,
- (byte) 0x4B, (byte) 0x0E, (byte) 0xA7, (byte) 0x5A,
- (byte) 0x28, (byte) 0x14, (byte) 0x3F, (byte) 0x29,
- (byte) 0x88, (byte) 0x3C, (byte) 0x4C, (byte) 0x02,
- (byte) 0xB8, (byte) 0xDA, (byte) 0xB0, (byte) 0x17,
- (byte) 0x55, (byte) 0x1F, (byte) 0x8A, (byte) 0x7D,
- (byte) 0x57, (byte) 0xC7, (byte) 0x8D, (byte) 0x74,
- (byte) 0xB7, (byte) 0xC4, (byte) 0x9F, (byte) 0x72,
- (byte) 0x7E, (byte) 0x15, (byte) 0x22, (byte) 0x12,
- (byte) 0x58, (byte) 0x07, (byte) 0x99, (byte) 0x34,
- (byte) 0x6E, (byte) 0x50, (byte) 0xDE, (byte) 0x68,
- (byte) 0x65, (byte) 0xBC, (byte) 0xDB, (byte) 0xF8,
- (byte) 0xC8, (byte) 0xA8, (byte) 0x2B, (byte) 0x40,
- (byte) 0xDC, (byte) 0xFE, (byte) 0x32, (byte) 0xA4,
- (byte) 0xCA, (byte) 0x10, (byte) 0x21, (byte) 0xF0,
- (byte) 0xD3, (byte) 0x5D, (byte) 0x0F, (byte) 0x00,
- (byte) 0x6F, (byte) 0x9D, (byte) 0x36, (byte) 0x42,
- (byte) 0x4A, (byte) 0x5E, (byte) 0xC1, (byte) 0xE0
- },
- { // p1
- (byte) 0x75, (byte) 0xF3, (byte) 0xC6, (byte) 0xF4,
- (byte) 0xDB, (byte) 0x7B, (byte) 0xFB, (byte) 0xC8,
- (byte) 0x4A, (byte) 0xD3, (byte) 0xE6, (byte) 0x6B,
- (byte) 0x45, (byte) 0x7D, (byte) 0xE8, (byte) 0x4B,
- (byte) 0xD6, (byte) 0x32, (byte) 0xD8, (byte) 0xFD,
- (byte) 0x37, (byte) 0x71, (byte) 0xF1, (byte) 0xE1,
- (byte) 0x30, (byte) 0x0F, (byte) 0xF8, (byte) 0x1B,
- (byte) 0x87, (byte) 0xFA, (byte) 0x06, (byte) 0x3F,
- (byte) 0x5E, (byte) 0xBA, (byte) 0xAE, (byte) 0x5B,
- (byte) 0x8A, (byte) 0x00, (byte) 0xBC, (byte) 0x9D,
- (byte) 0x6D, (byte) 0xC1, (byte) 0xB1, (byte) 0x0E,
- (byte) 0x80, (byte) 0x5D, (byte) 0xD2, (byte) 0xD5,
- (byte) 0xA0, (byte) 0x84, (byte) 0x07, (byte) 0x14,
- (byte) 0xB5, (byte) 0x90, (byte) 0x2C, (byte) 0xA3,
- (byte) 0xB2, (byte) 0x73, (byte) 0x4C, (byte) 0x54,
- (byte) 0x92, (byte) 0x74, (byte) 0x36, (byte) 0x51,
- (byte) 0x38, (byte) 0xB0, (byte) 0xBD, (byte) 0x5A,
- (byte) 0xFC, (byte) 0x60, (byte) 0x62, (byte) 0x96,
- (byte) 0x6C, (byte) 0x42, (byte) 0xF7, (byte) 0x10,
- (byte) 0x7C, (byte) 0x28, (byte) 0x27, (byte) 0x8C,
- (byte) 0x13, (byte) 0x95, (byte) 0x9C, (byte) 0xC7,
- (byte) 0x24, (byte) 0x46, (byte) 0x3B, (byte) 0x70,
- (byte) 0xCA, (byte) 0xE3, (byte) 0x85, (byte) 0xCB,
- (byte) 0x11, (byte) 0xD0, (byte) 0x93, (byte) 0xB8,
- (byte) 0xA6, (byte) 0x83, (byte) 0x20, (byte) 0xFF,
- (byte) 0x9F, (byte) 0x77, (byte) 0xC3, (byte) 0xCC,
- (byte) 0x03, (byte) 0x6F, (byte) 0x08, (byte) 0xBF,
- (byte) 0x40, (byte) 0xE7, (byte) 0x2B, (byte) 0xE2,
- (byte) 0x79, (byte) 0x0C, (byte) 0xAA, (byte) 0x82,
- (byte) 0x41, (byte) 0x3A, (byte) 0xEA, (byte) 0xB9,
- (byte) 0xE4, (byte) 0x9A, (byte) 0xA4, (byte) 0x97,
- (byte) 0x7E, (byte) 0xDA, (byte) 0x7A, (byte) 0x17,
- (byte) 0x66, (byte) 0x94, (byte) 0xA1, (byte) 0x1D,
- (byte) 0x3D, (byte) 0xF0, (byte) 0xDE, (byte) 0xB3,
- (byte) 0x0B, (byte) 0x72, (byte) 0xA7, (byte) 0x1C,
- (byte) 0xEF, (byte) 0xD1, (byte) 0x53, (byte) 0x3E,
- (byte) 0x8F, (byte) 0x33, (byte) 0x26, (byte) 0x5F,
- (byte) 0xEC, (byte) 0x76, (byte) 0x2A, (byte) 0x49,
- (byte) 0x81, (byte) 0x88, (byte) 0xEE, (byte) 0x21,
- (byte) 0xC4, (byte) 0x1A, (byte) 0xEB, (byte) 0xD9,
- (byte) 0xC5, (byte) 0x39, (byte) 0x99, (byte) 0xCD,
- (byte) 0xAD, (byte) 0x31, (byte) 0x8B, (byte) 0x01,
- (byte) 0x18, (byte) 0x23, (byte) 0xDD, (byte) 0x1F,
- (byte) 0x4E, (byte) 0x2D, (byte) 0xF9, (byte) 0x48,
- (byte) 0x4F, (byte) 0xF2, (byte) 0x65, (byte) 0x8E,
- (byte) 0x78, (byte) 0x5C, (byte) 0x58, (byte) 0x19,
- (byte) 0x8D, (byte) 0xE5, (byte) 0x98, (byte) 0x57,
- (byte) 0x67, (byte) 0x7F, (byte) 0x05, (byte) 0x64,
- (byte) 0xAF, (byte) 0x63, (byte) 0xB6, (byte) 0xFE,
- (byte) 0xF5, (byte) 0xB7, (byte) 0x3C, (byte) 0xA5,
- (byte) 0xCE, (byte) 0xE9, (byte) 0x68, (byte) 0x44,
- (byte) 0xE0, (byte) 0x4D, (byte) 0x43, (byte) 0x69,
- (byte) 0x29, (byte) 0x2E, (byte) 0xAC, (byte) 0x15,
- (byte) 0x59, (byte) 0xA8, (byte) 0x0A, (byte) 0x9E,
- (byte) 0x6E, (byte) 0x47, (byte) 0xDF, (byte) 0x34,
- (byte) 0x35, (byte) 0x6A, (byte) 0xCF, (byte) 0xDC,
- (byte) 0x22, (byte) 0xC9, (byte) 0xC0, (byte) 0x9B,
- (byte) 0x89, (byte) 0xD4, (byte) 0xED, (byte) 0xAB,
- (byte) 0x12, (byte) 0xA2, (byte) 0x0D, (byte) 0x52,
- (byte) 0xBB, (byte) 0x02, (byte) 0x2F, (byte) 0xA9,
- (byte) 0xD7, (byte) 0x61, (byte) 0x1E, (byte) 0xB4,
- (byte) 0x50, (byte) 0x04, (byte) 0xF6, (byte) 0xC2,
- (byte) 0x16, (byte) 0x25, (byte) 0x86, (byte) 0x56,
- (byte) 0x55, (byte) 0x09, (byte) 0xBE, (byte) 0x91
- }
- };
-
- /**
- * Define the fixed p0/p1 permutations used in keyed S-box lookup.
- * By changing the following constant definitions, the S-boxes will
- * automatically get changed in the Twofish engine.
- */
- private static final int P_00 = 1;
- private static final int P_01 = 0;
- private static final int P_02 = 0;
- private static final int P_03 = P_01 ^ 1;
- private static final int P_04 = 1;
-
- private static final int P_10 = 0;
- private static final int P_11 = 0;
- private static final int P_12 = 1;
- private static final int P_13 = P_11 ^ 1;
- private static final int P_14 = 0;
-
- private static final int P_20 = 1;
- private static final int P_21 = 1;
- private static final int P_22 = 0;
- private static final int P_23 = P_21 ^ 1;
- private static final int P_24 = 0;
-
- private static final int P_30 = 0;
- private static final int P_31 = 1;
- private static final int P_32 = 1;
- private static final int P_33 = P_31 ^ 1;
- private static final int P_34 = 1;
-
- /** Primitive polynomial for GF(256) */
- private static final int GF256_FDBK = 0x169;
- private static final int GF256_FDBK_2 = 0x169 / 2;
- private static final int GF256_FDBK_4 = 0x169 / 4;
-
- /** MDS matrix */
- private static final int[][] MDS = new int[4][256]; // blank final
-
- private static final int RS_GF_FDBK = 0x14D; // field generator
-
- /** data for hexadecimal visualisation. */
- private static final char[] HEX_DIGITS = {
- '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
- };
-
-
-// Static code - to intialise the MDS matrix
-//...........................................................................
-
- static {
- long time = System.currentTimeMillis();
-
-if (DEBUG && debuglevel > 6) {
-System.out.println("Algorithm Name: "+Twofish_Properties.FULL_NAME);
-System.out.println("Electronic Codebook (ECB) Mode");
-System.out.println();
-}
- //
- // precompute the MDS matrix
- //
- int[] m1 = new int[2];
- int[] mX = new int[2];
- int[] mY = new int[2];
- int i, j;
- for (i = 0; i < 256; i++) {
- j = P[0][i] & 0xFF; // compute all the matrix elements
- m1[0] = j;
- mX[0] = Mx_X( j ) & 0xFF;
- mY[0] = Mx_Y( j ) & 0xFF;
-
- j = P[1][i] & 0xFF;
- m1[1] = j;
- mX[1] = Mx_X( j ) & 0xFF;
- mY[1] = Mx_Y( j ) & 0xFF;
-
- MDS[0][i] = m1[P_00] << 0 | // fill matrix w/ above elements
- mX[P_00] << 8 |
- mY[P_00] << 16 |
- mY[P_00] << 24;
- MDS[1][i] = mY[P_10] << 0 |
- mY[P_10] << 8 |
- mX[P_10] << 16 |
- m1[P_10] << 24;
- MDS[2][i] = mX[P_20] << 0 |
- mY[P_20] << 8 |
- m1[P_20] << 16 |
- mY[P_20] << 24;
- MDS[3][i] = mX[P_30] << 0 |
- m1[P_30] << 8 |
- mY[P_30] << 16 |
- mX[P_30] << 24;
- }
-
- time = System.currentTimeMillis() - time;
-
-if (DEBUG && debuglevel > 8) {
-System.out.println("==========");
-System.out.println();
-System.out.println("Static Data");
-System.out.println();
-System.out.println("MDS[0][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[0][i*4+j])+", "); System.out.println();}
-System.out.println();
-System.out.println("MDS[1][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[1][i*4+j])+", "); System.out.println();}
-System.out.println();
-System.out.println("MDS[2][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[2][i*4+j])+", "); System.out.println();}
-System.out.println();
-System.out.println("MDS[3][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[3][i*4+j])+", "); System.out.println();}
-System.out.println();
-System.out.println("Total initialization time: "+time+" ms.");
-System.out.println();
-}
- }
-
- private static final int LFSR1( int x ) {
- return (x >> 1) ^
- ((x & 0x01) != 0 ? GF256_FDBK_2 : 0);
- }
-
- private static final int LFSR2( int x ) {
- return (x >> 2) ^
- ((x & 0x02) != 0 ? GF256_FDBK_2 : 0) ^
- ((x & 0x01) != 0 ? GF256_FDBK_4 : 0);
- }
-
- private static final int Mx_1( int x ) { return x; }
- private static final int Mx_X( int x ) { return x ^ LFSR2(x); } // 5B
- private static final int Mx_Y( int x ) { return x ^ LFSR1(x) ^ LFSR2(x); } // EF
-
-
-// Basic API methods
-//...........................................................................
-
- /**
- * Expand a user-supplied key material into a session key.
- *
- * @param key The 64/128/192/256-bit user-key to use.
- * @return This cipher's round keys.
- * @exception InvalidKeyException If the key is invalid.
- */
- public static synchronized Object makeKey (byte[] k)
- throws InvalidKeyException {
-if (DEBUG) trace(IN, "makeKey("+k+")");
- if (k == null)
- throw new InvalidKeyException("Empty key");
- int length = k.length;
- if (!(length == 8 || length == 16 || length == 24 || length == 32))
- throw new InvalidKeyException("Incorrect key length");
-
-if (DEBUG && debuglevel > 7) {
-System.out.println("Intermediate Session Key Values");
-System.out.println();
-System.out.println("Raw="+toString(k));
-System.out.println();
-}
- int k64Cnt = length / 8;
- int subkeyCnt = ROUND_SUBKEYS + 2*ROUNDS;
- int[] k32e = new int[4]; // even 32-bit entities
- int[] k32o = new int[4]; // odd 32-bit entities
- int[] sBoxKey = new int[4];
- //
- // split user key material into even and odd 32-bit entities and
- // compute S-box keys using (12, 8) Reed-Solomon code over GF(256)
- //
- int i, j, offset = 0;
- for (i = 0, j = k64Cnt-1; i < 4 && offset < length; i++, j--) {
- k32e[i] = (k[offset++] & 0xFF) |
- (k[offset++] & 0xFF) << 8 |
- (k[offset++] & 0xFF) << 16 |
- (k[offset++] & 0xFF) << 24;
- k32o[i] = (k[offset++] & 0xFF) |
- (k[offset++] & 0xFF) << 8 |
- (k[offset++] & 0xFF) << 16 |
- (k[offset++] & 0xFF) << 24;
- sBoxKey[j] = RS_MDS_Encode( k32e[i], k32o[i] ); // reverse order
- }
- // compute the round decryption subkeys for PHT. these same subkeys
- // will be used in encryption but will be applied in reverse order.
- int q, A, B;
- int[] subKeys = new int[subkeyCnt];
- for (i = q = 0; i < subkeyCnt/2; i++, q += SK_STEP) {
- A = F32( k64Cnt, q , k32e ); // A uses even key entities
- B = F32( k64Cnt, q+SK_BUMP, k32o ); // B uses odd key entities
- B = B << 8 | B >>> 24;
- A += B;
- subKeys[2*i ] = A; // combine with a PHT
- A += B;
- subKeys[2*i + 1] = A << SK_ROTL | A >>> (32-SK_ROTL);
- }
- //
- // fully expand the table for speed
- //
- int k0 = sBoxKey[0];
- int k1 = sBoxKey[1];
- int k2 = sBoxKey[2];
- int k3 = sBoxKey[3];
- int b0, b1, b2, b3;
- int[] sBox = new int[4 * 256];
- for (i = 0; i < 256; i++) {
- b0 = b1 = b2 = b3 = i;
- switch (k64Cnt & 3) {
- case 1:
- sBox[ 2*i ] = MDS[0][(P[P_01][b0] & 0xFF) ^ b0(k0)];
- sBox[ 2*i+1] = MDS[1][(P[P_11][b1] & 0xFF) ^ b1(k0)];
- sBox[0x200+2*i ] = MDS[2][(P[P_21][b2] & 0xFF) ^ b2(k0)];
- sBox[0x200+2*i+1] = MDS[3][(P[P_31][b3] & 0xFF) ^ b3(k0)];
- break;
- case 0: // same as 4
- b0 = (P[P_04][b0] & 0xFF) ^ b0(k3);
- b1 = (P[P_14][b1] & 0xFF) ^ b1(k3);
- b2 = (P[P_24][b2] & 0xFF) ^ b2(k3);
- b3 = (P[P_34][b3] & 0xFF) ^ b3(k3);
- case 3:
- b0 = (P[P_03][b0] & 0xFF) ^ b0(k2);
- b1 = (P[P_13][b1] & 0xFF) ^ b1(k2);
- b2 = (P[P_23][b2] & 0xFF) ^ b2(k2);
- b3 = (P[P_33][b3] & 0xFF) ^ b3(k2);
- case 2: // 128-bit keys
- sBox[ 2*i ] = MDS[0][(P[P_01][(P[P_02][b0] & 0xFF) ^ b0(k1)] & 0xFF) ^ b0(k0)];
- sBox[ 2*i+1] = MDS[1][(P[P_11][(P[P_12][b1] & 0xFF) ^ b1(k1)] & 0xFF) ^ b1(k0)];
- sBox[0x200+2*i ] = MDS[2][(P[P_21][(P[P_22][b2] & 0xFF) ^ b2(k1)] & 0xFF) ^ b2(k0)];
- sBox[0x200+2*i+1] = MDS[3][(P[P_31][(P[P_32][b3] & 0xFF) ^ b3(k1)] & 0xFF) ^ b3(k0)];
- }
- }
-
- Object sessionKey = new Object[] { sBox, subKeys };
-
-if (DEBUG && debuglevel > 7) {
-System.out.println("S-box[]:");
-for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[i*4+j])+", "); System.out.println();}
-System.out.println();
-for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[256+i*4+j])+", "); System.out.println();}
-System.out.println();
-for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[512+i*4+j])+", "); System.out.println();}
-System.out.println();
-for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[768+i*4+j])+", "); System.out.println();}
-System.out.println();
-System.out.println("User (odd, even) keys --> S-Box keys:");
-for(i=0;i<k64Cnt;i++) { System.out.println("0x"+intToString(k32o[i])+" 0x"+intToString(k32e[i])+" --> 0x"+intToString(sBoxKey[k64Cnt-1-i])); }
-System.out.println();
-System.out.println("Round keys:");
-for(i=0;i<ROUND_SUBKEYS + 2*ROUNDS;i+=2) { System.out.println("0x"+intToString(subKeys[i])+" 0x"+intToString(subKeys[i+1])); }
-System.out.println();
-
-}
-if (DEBUG) trace(OUT, "makeKey()");
- return sessionKey;
- }
-
- /**
- * Encrypt exactly one block of plaintext.
- *
- * @param in The plaintext.
- * @param inOffset Index of in from which to start considering data.
- * @param sessionKey The session key to use for encryption.
- * @return The ciphertext generated from a plaintext using the session key.
- */
- public static byte[]
- blockEncrypt (byte[] in, int inOffset, Object sessionKey) {
-if (DEBUG) trace(IN, "blockEncrypt("+in+", "+inOffset+", "+sessionKey+")");
- Object[] sk = (Object[]) sessionKey; // extract S-box and session key
- int[] sBox = (int[]) sk[0];
- int[] sKey = (int[]) sk[1];
-
-if (DEBUG && debuglevel > 6) System.out.println("PT="+toString(in, inOffset, BLOCK_SIZE));
-
- int x0 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
- int x1 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
- int x2 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
- int x3 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
-
- x0 ^= sKey[INPUT_WHITEN ];
- x1 ^= sKey[INPUT_WHITEN + 1];
- x2 ^= sKey[INPUT_WHITEN + 2];
- x3 ^= sKey[INPUT_WHITEN + 3];
-if (DEBUG && debuglevel > 6) System.out.println("PTw="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
-
- int t0, t1;
- int k = ROUND_SUBKEYS;
- for (int R = 0; R < ROUNDS; R += 2) {
- t0 = Fe32( sBox, x0, 0 );
- t1 = Fe32( sBox, x1, 3 );
- x2 ^= t0 + t1 + sKey[k++];
- x2 = x2 >>> 1 | x2 << 31;
- x3 = x3 << 1 | x3 >>> 31;
- x3 ^= t0 + 2*t1 + sKey[k++];
-if (DEBUG && debuglevel > 6) System.out.println("CT"+(R)+"="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
-
- t0 = Fe32( sBox, x2, 0 );
- t1 = Fe32( sBox, x3, 3 );
- x0 ^= t0 + t1 + sKey[k++];
- x0 = x0 >>> 1 | x0 << 31;
- x1 = x1 << 1 | x1 >>> 31;
- x1 ^= t0 + 2*t1 + sKey[k++];
-if (DEBUG && debuglevel > 6) System.out.println("CT"+(R+1)+"="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
- }
- x2 ^= sKey[OUTPUT_WHITEN ];
- x3 ^= sKey[OUTPUT_WHITEN + 1];
- x0 ^= sKey[OUTPUT_WHITEN + 2];
- x1 ^= sKey[OUTPUT_WHITEN + 3];
-if (DEBUG && debuglevel > 6) System.out.println("CTw="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
-
- byte[] result = new byte[] {
- (byte) x2, (byte)(x2 >>> 8), (byte)(x2 >>> 16), (byte)(x2 >>> 24),
- (byte) x3, (byte)(x3 >>> 8), (byte)(x3 >>> 16), (byte)(x3 >>> 24),
- (byte) x0, (byte)(x0 >>> 8), (byte)(x0 >>> 16), (byte)(x0 >>> 24),
- (byte) x1, (byte)(x1 >>> 8), (byte)(x1 >>> 16), (byte)(x1 >>> 24),
- };
-
-if (DEBUG && debuglevel > 6) {
-System.out.println("CT="+toString(result));
-System.out.println();
-}
-if (DEBUG) trace(OUT, "blockEncrypt()");
- return result;
- }
-
- /**
- * Decrypt exactly one block of ciphertext.
- *
- * @param in The ciphertext.
- * @param inOffset Index of in from which to start considering data.
- * @param sessionKey The session key to use for decryption.
- * @return The plaintext generated from a ciphertext using the session key.
- */
- public static byte[]
- blockDecrypt (byte[] in, int inOffset, Object sessionKey) {
-if (DEBUG) trace(IN, "blockDecrypt("+in+", "+inOffset+", "+sessionKey+")");
- Object[] sk = (Object[]) sessionKey; // extract S-box and session key
- int[] sBox = (int[]) sk[0];
- int[] sKey = (int[]) sk[1];
-
-if (DEBUG && debuglevel > 6) System.out.println("CT="+toString(in, inOffset, BLOCK_SIZE));
-
- int x2 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
- int x3 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
- int x0 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
- int x1 = (in[inOffset++] & 0xFF) |
- (in[inOffset++] & 0xFF) << 8 |
- (in[inOffset++] & 0xFF) << 16 |
- (in[inOffset++] & 0xFF) << 24;
-
- x2 ^= sKey[OUTPUT_WHITEN ];
- x3 ^= sKey[OUTPUT_WHITEN + 1];
- x0 ^= sKey[OUTPUT_WHITEN + 2];
- x1 ^= sKey[OUTPUT_WHITEN + 3];
-if (DEBUG && debuglevel > 6) System.out.println("CTw="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
-
- int k = ROUND_SUBKEYS + 2*ROUNDS - 1;
- int t0, t1;
- for (int R = 0; R < ROUNDS; R += 2) {
- t0 = Fe32( sBox, x2, 0 );
- t1 = Fe32( sBox, x3, 3 );
- x1 ^= t0 + 2*t1 + sKey[k--];
- x1 = x1 >>> 1 | x1 << 31;
- x0 = x0 << 1 | x0 >>> 31;
- x0 ^= t0 + t1 + sKey[k--];
-if (DEBUG && debuglevel > 6) System.out.println("PT"+(ROUNDS-R)+"="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
-
- t0 = Fe32( sBox, x0, 0 );
- t1 = Fe32( sBox, x1, 3 );
- x3 ^= t0 + 2*t1 + sKey[k--];
- x3 = x3 >>> 1 | x3 << 31;
- x2 = x2 << 1 | x2 >>> 31;
- x2 ^= t0 + t1 + sKey[k--];
-if (DEBUG && debuglevel > 6) System.out.println("PT"+(ROUNDS-R-1)+"="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
- }
- x0 ^= sKey[INPUT_WHITEN ];
- x1 ^= sKey[INPUT_WHITEN + 1];
- x2 ^= sKey[INPUT_WHITEN + 2];
- x3 ^= sKey[INPUT_WHITEN + 3];
-if (DEBUG && debuglevel > 6) System.out.println("PTw="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
-
- byte[] result = new byte[] {
- (byte) x0, (byte)(x0 >>> 8), (byte)(x0 >>> 16), (byte)(x0 >>> 24),
- (byte) x1, (byte)(x1 >>> 8), (byte)(x1 >>> 16), (byte)(x1 >>> 24),
- (byte) x2, (byte)(x2 >>> 8), (byte)(x2 >>> 16), (byte)(x2 >>> 24),
- (byte) x3, (byte)(x3 >>> 8), (byte)(x3 >>> 16), (byte)(x3 >>> 24),
- };
-
-if (DEBUG && debuglevel > 6) {
-System.out.println("PT="+toString(result));
-System.out.println();
-}
-if (DEBUG) trace(OUT, "blockDecrypt()");
- return result;
- }
-
- /** A basic symmetric encryption/decryption test. */
- public static boolean self_test() { return self_test(BLOCK_SIZE); }
-
-
-// own methods
-//...........................................................................
-
- private static final int b0( int x ) { return x & 0xFF; }
- private static final int b1( int x ) { return (x >>> 8) & 0xFF; }
- private static final int b2( int x ) { return (x >>> 16) & 0xFF; }
- private static final int b3( int x ) { return (x >>> 24) & 0xFF; }
-
- /**
- * Use (12, 8) Reed-Solomon code over GF(256) to produce a key S-box
- * 32-bit entity from two key material 32-bit entities.
- *
- * @param k0 1st 32-bit entity.
- * @param k1 2nd 32-bit entity.
- * @return Remainder polynomial generated using RS code
- */
- private static final int RS_MDS_Encode( int k0, int k1) {
- int r = k1;
- for (int i = 0; i < 4; i++) // shift 1 byte at a time
- r = RS_rem( r );
- r ^= k0;
- for (int i = 0; i < 4; i++)
- r = RS_rem( r );
- return r;
- }
-
- /*
- * Reed-Solomon code parameters: (12, 8) reversible code:<p>
- * <pre>
- * g(x) = x**4 + (a + 1/a) x**3 + a x**2 + (a + 1/a) x + 1
- * </pre>
- * where a = primitive root of field generator 0x14D
- */
- private static final int RS_rem( int x ) {
- int b = (x >>> 24) & 0xFF;
- int g2 = ((b << 1) ^ ( (b & 0x80) != 0 ? RS_GF_FDBK : 0 )) & 0xFF;
- int g3 = (b >>> 1) ^ ( (b & 0x01) != 0 ? (RS_GF_FDBK >>> 1) : 0 ) ^ g2 ;
- int result = (x << 8) ^ (g3 << 24) ^ (g2 << 16) ^ (g3 << 8) ^ b;
- return result;
- }
-
- private static final int F32( int k64Cnt, int x, int[] k32 ) {
- int b0 = b0(x);
- int b1 = b1(x);
- int b2 = b2(x);
- int b3 = b3(x);
- int k0 = k32[0];
- int k1 = k32[1];
- int k2 = k32[2];
- int k3 = k32[3];
-
- int result = 0;
- switch (k64Cnt & 3) {
- case 1:
- result =
- MDS[0][(P[P_01][b0] & 0xFF) ^ b0(k0)] ^
- MDS[1][(P[P_11][b1] & 0xFF) ^ b1(k0)] ^
- MDS[2][(P[P_21][b2] & 0xFF) ^ b2(k0)] ^
- MDS[3][(P[P_31][b3] & 0xFF) ^ b3(k0)];
- break;
- case 0: // same as 4
- b0 = (P[P_04][b0] & 0xFF) ^ b0(k3);
- b1 = (P[P_14][b1] & 0xFF) ^ b1(k3);
- b2 = (P[P_24][b2] & 0xFF) ^ b2(k3);
- b3 = (P[P_34][b3] & 0xFF) ^ b3(k3);
- case 3:
- b0 = (P[P_03][b0] & 0xFF) ^ b0(k2);
- b1 = (P[P_13][b1] & 0xFF) ^ b1(k2);
- b2 = (P[P_23][b2] & 0xFF) ^ b2(k2);
- b3 = (P[P_33][b3] & 0xFF) ^ b3(k2);
- case 2: // 128-bit keys (optimize for this case)
- result =
- MDS[0][(P[P_01][(P[P_02][b0] & 0xFF) ^ b0(k1)] & 0xFF) ^ b0(k0)] ^
- MDS[1][(P[P_11][(P[P_12][b1] & 0xFF) ^ b1(k1)] & 0xFF) ^ b1(k0)] ^
- MDS[2][(P[P_21][(P[P_22][b2] & 0xFF) ^ b2(k1)] & 0xFF) ^ b2(k0)] ^
- MDS[3][(P[P_31][(P[P_32][b3] & 0xFF) ^ b3(k1)] & 0xFF) ^ b3(k0)];
- break;
- }
- return result;
- }
-
- private static final int Fe32( int[] sBox, int x, int R ) {
- return sBox[ 2*_b(x, R ) ] ^
- sBox[ 2*_b(x, R+1) + 1] ^
- sBox[0x200 + 2*_b(x, R+2) ] ^
- sBox[0x200 + 2*_b(x, R+3) + 1];
- }
-
- private static final int _b( int x, int N) {
- int result = 0;
- switch (N%4) {
- case 0: result = b0(x); break;
- case 1: result = b1(x); break;
- case 2: result = b2(x); break;
- case 3: result = b3(x); break;
- }
- return result;
- }
-
- /** @return The length in bytes of the Algorithm input block. */
- public static int blockSize() { return BLOCK_SIZE; }
-
- /** A basic symmetric encryption/decryption test for a given key size. */
- private static boolean self_test (int keysize) {
-if (DEBUG) trace(IN, "self_test("+keysize+")");
- boolean ok = false;
- try {
- byte[] kb = new byte[keysize];
- byte[] pt = new byte[BLOCK_SIZE];
- int i;
-
- for (i = 0; i < keysize; i++)
- kb[i] = (byte) i;
- for (i = 0; i < BLOCK_SIZE; i++)
- pt[i] = (byte) i;
-
-if (DEBUG && debuglevel > 6) {
-System.out.println("==========");
-System.out.println();
-System.out.println("KEYSIZE="+(8*keysize));
-System.out.println("KEY="+toString(kb));
-System.out.println();
-}
- Object key = makeKey(kb);
-
-if (DEBUG && debuglevel > 6) {
-System.out.println("Intermediate Ciphertext Values (Encryption)");
-System.out.println();
-}
- byte[] ct = blockEncrypt(pt, 0, key);
-
-if (DEBUG && debuglevel > 6) {
-System.out.println("Intermediate Plaintext Values (Decryption)");
-System.out.println();
-}
- byte[] cpt = blockDecrypt(ct, 0, key);
-
- ok = areEqual(pt, cpt);
- if (!ok)
- throw new RuntimeException("Symmetric operation failed");
- } catch (Exception x) {
-if (DEBUG && debuglevel > 0) {
- debug("Exception encountered during self-test: " + x.getMessage());
- x.printStackTrace();
-}
- }
-if (DEBUG && debuglevel > 0) debug("Self-test OK? " + ok);
-if (DEBUG) trace(OUT, "self_test()");
- return ok;
- }
-
-
-// utility static methods (from cryptix.util.core ArrayUtil and Hex classes)
-//...........................................................................
-
- /** @return True iff the arrays have identical contents. */
- private static boolean areEqual (byte[] a, byte[] b) {
- int aLength = a.length;
- if (aLength != b.length)
- return false;
- for (int i = 0; i < aLength; i++)
- if (a[i] != b[i])
- return false;
- return true;
- }
-
- /**
- * Returns a string of 8 hexadecimal digits (most significant
- * digit first) corresponding to the integer <i>n</i>, which is
- * treated as unsigned.
- */
- private static String intToString (int n) {
- char[] buf = new char[8];
- for (int i = 7; i >= 0; i--) {
- buf[i] = HEX_DIGITS[n & 0x0F];
- n >>>= 4;
- }
- return new String(buf);
- }
-
- /**
- * Returns a string of hexadecimal digits from a byte array. Each
- * byte is converted to 2 hex symbols.
- */
- private static String toString (byte[] ba) {
- return toString(ba, 0, ba.length);
- }
- private static String toString (byte[] ba, int offset, int length) {
- char[] buf = new char[length * 2];
- for (int i = offset, j = 0, k; i < offset+length; ) {
- k = ba[i++];
- buf[j++] = HEX_DIGITS[(k >>> 4) & 0x0F];
- buf[j++] = HEX_DIGITS[ k & 0x0F];
- }
- return new String(buf);
- }
-
-
-// main(): use to generate the Intermediate Values KAT
-//...........................................................................
-
- public static void main (String[] args) {
- self_test(16);
- self_test(24);
- self_test(32);
- }
-}
\ No newline at end of file
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java b/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java
deleted file mode 100644
index e356ea6..0000000
--- a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2010 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.ide.common.rendering.api;
-
-
-import static com.android.ide.common.rendering.api.Result.Status.NOT_IMPLEMENTED;
-
-import com.android.ide.common.rendering.api.Result.Status;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.util.EnumSet;
-import java.util.Map;
-
-/**
- * Entry point of the Layout Library. Extensions of this class provide a method to compute
- * and render a layout.
- */
- at SuppressWarnings({"MethodMayBeStatic", "UnusedDeclaration"})
-public abstract class Bridge {
-
- public static final int API_CURRENT = 15;
-
- /**
- * Returns the API level of the layout library.
- * <p/>
- * While no methods will ever be removed, some may become deprecated, and some new ones
- * will appear.
- * <p/>All Layout libraries based on {@link Bridge} return at minimum an API level of 5.
- */
- public abstract int getApiLevel();
-
- /**
- * Returns the revision of the library inside a given (layoutlib) API level.
- * The true revision number of the library is {@link #getApiLevel()}.{@link #getRevision()}
- */
- @SuppressWarnings("JavaDoc") // javadoc pointing to itself.
- public int getRevision() {
- return 0;
- }
-
- /**
- * Returns an {@link EnumSet} of the supported {@link Capability}.
- *
- * @return an {@link EnumSet} with the supported capabilities.
- *
- * @deprecated use {@link #supports(int)}
- */
- @Deprecated
- public EnumSet<Capability> getCapabilities() {
- return EnumSet.noneOf(Capability.class);
- }
-
- /**
- * Returns true if the layout library supports the given feature.
- *
- * @see com.android.ide.common.rendering.api.Features
- */
- public boolean supports(int feature) {
- return false;
- }
-
- /**
- * Initializes the Bridge object.
- *
- * @param platformProperties The build properties for the platform.
- * @param fontLocation the location of the fonts.
- * @param enumValueMap map attrName => { map enumFlagName => Integer value }. This is typically
- * read from attrs.xml in the SDK target.
- * @param log a {@link LayoutLog} object. Can be null.
- * @return true if success.
- */
- public boolean init(Map<String, String> platformProperties,
- File fontLocation,
- Map<String, Map<String, Integer>> enumValueMap,
- LayoutLog log) {
- return false;
- }
-
- /**
- * Prepares the layoutlib to unloaded.
- */
- public boolean dispose() {
- return false;
- }
-
- /**
- * Starts a layout session by inflating and rendering it. The method returns a
- * {@link RenderSession} on which further actions can be taken.
- *
- * @return a new {@link RenderSession} object that contains the result of the scene creation and
- * first rendering.
- */
- public RenderSession createSession(SessionParams params) {
- return null;
- }
-
- /**
- * Renders a Drawable. If the rendering is successful, the result image is accessible through
- * {@link Result#getData()}. It is of type {@link BufferedImage}
- * @param params the rendering parameters.
- * @return the result of the action.
- */
- public Result renderDrawable(DrawableParams params) {
- return Status.NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Clears the resource cache for a specific project.
- * <p/>This cache contains bitmaps and nine patches that are loaded from the disk and reused
- * until this method is called.
- * <p/>The cache is not configuration dependent and should only be cleared when a
- * resource changes (at this time only bitmaps and 9 patches go into the cache).
- * <p/>
- * The project key provided must be similar to the one passed in {@link RenderParams}.
- *
- * @param projectKey the key for the project.
- */
- public void clearCaches(Object projectKey) {
-
- }
-
- /**
- * Utility method returning the parent of a given view object.
- *
- * @param viewObject the object for which to return the parent.
- *
- * @return a {@link Result} indicating the status of the action, and if success, the parent
- * object in {@link Result#getData()}
- */
- public Result getViewParent(Object viewObject) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Utility method returning the index of a given view in its parent.
- * @param viewObject the object for which to return the index.
- *
- * @return a {@link Result} indicating the status of the action, and if success, the index in
- * the parent in {@link Result#getData()}
- */
- public Result getViewIndex(Object viewObject) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Returns true if the character orientation of the locale is right to left.
- * @param locale The locale formatted as language-region
- * @return true if the locale is right to left.
- */
- public boolean isRtl(String locale) {
- return false;
- }
-
- /**
- * Utility method returning the baseline value for a given view object. This basically returns
- * View.getBaseline().
- *
- * @param viewObject the object for which to return the index.
- *
- * @return the baseline value or -1 if not applicable to the view object or if this layout
- * library does not implement this method.
- *
- * @deprecated use the extended ViewInfo.
- */
- @Deprecated
- public Result getViewBaseline(Object viewObject) {
- return NOT_IMPLEMENTED.createResult();
- }
-}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Features.java b/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Features.java
deleted file mode 100644
index 78419a4..0000000
--- a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Features.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.rendering.api;
-
-/**
- * List of features describing the LayoutLib capabilities.
- */
-public class Features {
- /** Ability to render at full size, as required by the layout, and unbound by the screen */
- public static final int UNBOUND_RENDERING = 0;
- /** Ability to override the background of the rendering with transparency using
- * {@link SessionParams#setOverrideBgColor(int)} */
- public static final int CUSTOM_BACKGROUND_COLOR = 1;
- /** Ability to call {@link RenderSession#render()} and {@link RenderSession#render(long)}. */
- public static final int RENDER = 2;
- /** Ability to ask for a layout only with no rendering through
- * {@link SessionParams#setLayoutOnly()}
- */
- public static final int LAYOUT_ONLY = 3;
- /**
- * Ability to control embedded layout parsers through {@link ILayoutPullParser#getParser(String)}
- */
- public static final int EMBEDDED_LAYOUT = 4;
- /** Ability to call<br>
- * {@link RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)}<br>
- * {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}<br>
- * {@link RenderSession#setProperty(Object, String, String)}<br>
- * The method that receives an animation listener can only use it if the
- * ANIMATED_VIEW_MANIPULATION, or FULL_ANIMATED_VIEW_MANIPULATION is also supported.
- */
- public static final int VIEW_MANIPULATION = 5;
- /** Ability to play animations with<br>
- * {@link RenderSession#animate(Object, String, boolean, IAnimationListener)}
- */
- public static final int PLAY_ANIMATION = 6;
- /**
- * Ability to manipulate views with animation, as long as the view does not change parent.
- * {@link RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)}<br>
- * {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}<br>
- * {@link RenderSession#removeChild(Object, IAnimationListener)}<br>
- */
- public static final int ANIMATED_VIEW_MANIPULATION = 7;
- /**
- * Ability to move views (even into a different ViewGroup) with animation.
- * see {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}
- */
- public static final int FULL_ANIMATED_VIEW_MANIPULATION = 7;
- public static final int ADAPTER_BINDING = 8;
- public static final int EXTENDED_VIEWINFO = 9;
- /**
- * Ability to properly resize nine-patch assets.
- */
- public static final int FIXED_SCALABLE_NINE_PATCH = 10;
- /**
- * Ability to render RTL layouts.
- */
- public static final int RTL = 11;
- /**
- * Ability to render ActionBar.
- */
- public static final int ACTION_BAR = 12;
- /**
- * Ability to simulate older Platform Versions.
- * <p/>
- * This is the last feature supported by API 12.
- */
- public static final int SIMULATE_PLATFORM = 13;
- /**
- * All features before this map to the ones in {@link Capability}. Any feature greater than this
- * is guaranteed to be not supported by a LayoutLib using the older api.
- */
- public static final int LAST_CAPABILITY = SIMULATE_PLATFORM;
- /**
- * Ability to render preferences.
- */
- public static final int PREFERENCES_RENDERING = 14;
- /**
- * Ability to render all states of a StateListDrawable and return all in a
- * single call.
- */
- public static final int RENDER_ALL_DRAWABLE_STATES = 15;
- /**
- * Ability to provide a fake Adapter for RecyclerView. This is an IDE feature.
- */
- public static final int RECYCLER_VIEW_ADAPTER = 16;
- /**
- * Last known feature.
- * <p/>
- * This should be avoided on the LayoutLib since, since using this makes updating the API used
- * by the LayoutLib without implementing any newly added features.
- */
- public static final int LAST_FEATURE = RECYCLER_VIEW_ADAPTER;
-}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutlibCallback.java b/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutlibCallback.java
deleted file mode 100644
index 3829d82..0000000
--- a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutlibCallback.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.rendering.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceType;
-import com.android.util.Pair;
-
-import org.intellij.lang.annotations.MagicConstant;
-
-/**
- * Intermediary class implementing parts of both the old and new ProjectCallback from the
- * LayoutLib API.
- * <p/>
- * Even newer LayoutLibs use this directly instead of the the interface. This allows the flexibility
- * to add newer methods without having to update {@link Bridge#API_CURRENT LayoutLib API version}.
- * <p/>
- * Clients should use this instead of {@link IProjectCallback} to target both old and new
- * Layout Libraries.
- */
- at SuppressWarnings({"deprecation", "MethodMayBeStatic", "unused"})
-public abstract class LayoutlibCallback implements IProjectCallback,
- com.android.layoutlib.api.IProjectCallback {
-
- /**
- * Like {@link #loadView(String, Class[], Object[])}, but intended for loading classes that may
- * not be custom views.
- *
- * @param name className in binary format (see {@link ClassLoader})
- * @return an new instance created by calling the given constructor.
- * @throws ClassNotFoundException any exceptions thrown when creating the instance is wrapped in
- * ClassNotFoundException.
- * @since API 15
- */
- public Object loadClass(@NonNull String name, @Nullable Class[] constructorSignature,
- @Nullable Object[] constructorArgs) throws ClassNotFoundException {
- try {
- return loadView(name, constructorSignature, constructorArgs);
- }
- catch (ClassNotFoundException e) {
- throw e;
- }
- catch (Exception e) {
- throw new ClassNotFoundException(name + " not found.", e);
- }
- }
-
- /**
- * Returns if the IDE supports the requested feature.
- * @see Features
- * @since API 15
- */
- public abstract boolean supports(
- @MagicConstant(valuesFromClass = Features.class) int ideFeature);
-
- /**
- * A callback to query arbitrary data. This is similar to {@link RenderParams#setFlag(SessionParams.Key,
- * Object)}. The main difference is that when using this, the IDE doesn't have to compute the
- * value in advance and thus may save on some computation.
- * @since API 15
- */
- @Nullable
- public <T> T getFlag(@NonNull SessionParams.Key<T> key) {
- return null;
- }
-
- /**
- * Get a ParserFactory which can be used to create XmlPullParsers.
- * @since API 15
- */
- @NonNull
- public ParserFactory getParserFactory() {
- throw new UnsupportedOperationException("getParserFactory not supported.");
- }
-
- /**
- * Find a custom class in the project.
- * <p/>
- * Like {@link #loadClass(String, Class[], Object[])}, but doesn't instantiate
- * an object and just returns the class found.
- * @param name className in binary format. (see {@link ClassLoader}.
- * @since API 15
- */
- @NonNull
- public Class<?> findClass(@NonNull String name) throws ClassNotFoundException {
- throw new ClassNotFoundException(name + " not found.");
- }
-
- // ------ implementation of the old interface using the new interface.
-
- @Override
- public final Integer getResourceValue(String type, String name) {
- return getResourceId(ResourceType.getEnum(type), name);
- }
-
- @Override
- public final String[] resolveResourceValue(int id) {
- Pair<ResourceType, String> info = resolveResourceId(id);
- if (info != null) {
- return new String[] { info.getSecond(), info.getFirst().getName() };
- }
-
- return null;
- }
-
- @Override
- public final String resolveResourceValue(int[] id) {
- return resolveResourceId(id);
- }
-}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java b/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java
deleted file mode 100644
index e36c6ba..0000000
--- a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2010 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.ide.common.rendering.api;
-
-import static com.android.ide.common.rendering.api.Result.Status.NOT_IMPLEMENTED;
-
-import com.android.ide.common.rendering.api.Result.Status;
-
-import java.awt.image.BufferedImage;
-import java.util.List;
-import java.util.Map;
-
-/**
- * An object allowing interaction with an Android layout.
- *
- * This is returned by {@link Bridge#createSession(SessionParams)}.
- * and can then be used for subsequent actions on the layout.
- *
- * @since 5
- *
- */
-public class RenderSession {
-
- /**
- * Returns the last operation result.
- */
- public Result getResult() {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Returns the {@link ViewInfo} objects for the top level views.
- * <p/>
- * It contains {@code ViewInfo} for only the views in the layout. For {@code ViewInfo} of the
- * System UI surrounding the layout use {@link #getSystemRootViews()}. In most cases the list
- * will only contain one item. If the top level node is a {@code merge} though then it will
- * contain all the items under the {@code merge} tag.
- * <p/>
- * This is reset to a new instance every time {@link #render()} is called and can be
- * <code>null</code> if the call failed (and the method returned a {@link Result} with
- * {@link Status#ERROR_UNKNOWN} or {@link Status#NOT_IMPLEMENTED}.
- * <p/>
- * This can be safely modified by the caller, but {@code #getSystemRootViews} and
- * {@code #getRootViews} share some view infos, so modifying one result can affect the other.
- *
- * @return the list of {@link ViewInfo} or null if there aren't any.
- *
- * @see #getSystemRootViews()
- */
- public List<ViewInfo> getRootViews() {
- return null;
- }
-
- /**
- * Returns the {@link ViewInfo} objects for the system decor views, like the ActionBar.
- * <p/>
- * This is reset to a new instance every time {@link #render()} is called and can be
- * <code>null</code> if the call failed, or there was no system decor.
- * <p/>
- * This can be safely modified by the caller, but {@code #getSystemRootViews} and
- * {@code #getRootViews} share some view infos, so modifying one result can affect the other.
- *
- * @return the list of {@link ViewInfo} or null if there aren't any.
- */
- public List<ViewInfo> getSystemRootViews() {
- return null;
- }
-
- /**
- * Returns the rendering of the full layout.
- * <p>
- * This is reset to a new instance every time {@link #render()} is called and can be
- * <code>null</code> if the call failed (and the method returned a {@link Result} with
- * {@link Status#ERROR_UNKNOWN} or {@link Status#NOT_IMPLEMENTED}.
- * <p/>
- * This can be safely modified by the caller.
- */
- public BufferedImage getImage() {
- return null;
- }
-
- /**
- * Returns true if the current image alpha channel is relevant.
- *
- * @return whether the image alpha channel is relevant.
- */
- public boolean isAlphaChannelImage() {
- return true;
- }
-
- /**
- * Returns a map of (XML attribute name, attribute value) containing only default attribute
- * values, for the given view Object.
- * @param viewObject the view object.
- * @return a map of the default property values or null.
- */
- public Map<String, String> getDefaultProperties(Object viewObject) {
- return null;
- }
-
- /**
- * Re-renders the layout as-is.
- * In case of success, this should be followed by calls to {@link #getRootViews()} and
- * {@link #getImage()} to access the result of the rendering.
- *
- * This is equivalent to calling <code>render(SceneParams.DEFAULT_TIMEOUT)</code>
- *
- * @return a {@link Result} indicating the status of the action.
- */
- public Result render() {
- return render(RenderParams.DEFAULT_TIMEOUT);
- }
-
- /**
- * Re-renders the layout as-is, with a given timeout in case other renderings are being done.
- * In case of success, this should be followed by calls to {@link #getRootViews()} and
- * {@link #getImage()} to access the result of the rendering.
- *
- * The {@link Bridge} is only able to inflate or render one layout at a time. There
- * is an internal lock object whenever such an action occurs. The timeout parameter is used
- * when attempting to acquire the lock. If the timeout expires, the method will return
- * {@link Status#ERROR_TIMEOUT}.
- *
- * @param timeout timeout for the rendering, in milliseconds.
- *
- * @return a {@link Result} indicating the status of the action.
- */
- public Result render(long timeout) {
- return render(timeout, false);
- }
-
- /**
- * Re-renders the layout as-is, with a given timeout in case other renderings are being done.
- * In case of success, this should be followed by calls to {@link #getRootViews()} and
- * {@link #getImage()} to access the result of the rendering.
- * This call also allows triggering a forced measure.
- *
- * The {@link Bridge} is only able to inflate or render one layout at a time. There
- * is an internal lock object whenever such an action occurs. The timeout parameter is used
- * when attempting to acquire the lock. If the timeout expires, the method will return
- * {@link Status#ERROR_TIMEOUT}.
- *
- * @param timeout timeout for the rendering, in milliseconds.
- * @param forceMeasure force running measure for the layout.
- *
- * @return a {@link Result} indicating the status of the action.
- */
- public Result render(long timeout, boolean forceMeasure) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Sets the value of a given property on a given object.
- * <p/>
- * This does nothing more than change the property. To render the scene in its new state, a
- * call to {@link #render()} is required.
- * <p/>
- * Any amount of actions can be taken on the scene before {@link #render()} is called.
- *
- * @param objectView
- * @param propertyName
- * @param propertyValue
- *
- * @return a {@link Result} indicating the status of the action.
- *
- * @throws IllegalArgumentException if the view object is not an android.view.View
- */
- public Result setProperty(Object objectView, String propertyName, String propertyValue) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * returns the value of a given property on a given object.
- * <p/>
- * This returns a {@link Result} object. If the operation of querying the object for its
- * property was successful (check {@link Result#isSuccess()}), then the property value
- * is set in the result and can be accessed through {@link Result#getData()}.
- *
- * @param objectView
- * @param propertyName
- *
- * @return a {@link Result} indicating the status of the action.
- *
- * @throws IllegalArgumentException if the view object is not an android.view.View
- */
- public Result getProperty(Object objectView, String propertyName) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Inserts a new child in a ViewGroup object, and renders the result.
- * <p/>
- * The child is first inflated and then added to its new parent, at the given <var>index<var>
- * position. If the <var>index</var> is -1 then the child is added at the end of the parent.
- * <p/>
- * If an animation listener is passed then the rendering is done asynchronously and the
- * result is sent to the listener.
- * If the listener is null, then the rendering is done synchronously.
- * <p/>
- * The child stays in the view hierarchy after the rendering is done. To remove it call
- * {@link #removeChild(Object, IAnimationListener)}
- * <p/>
- * The returned {@link Result} object will contain the android.view.View object for
- * the newly inflated child. It is accessible through {@link Result#getData()}.
- *
- * @param parentView the parent View object to receive the new child.
- * @param childXml an {@link ILayoutPullParser} containing the content of the new child,
- * including ViewGroup.LayoutParams attributes.
- * @param index the index at which position to add the new child into the parent. -1 means at
- * the end.
- * @param listener an optional {@link IAnimationListener}.
- *
- * @return a {@link Result} indicating the status of the action.
- */
- public Result insertChild(Object parentView, ILayoutPullParser childXml, int index,
- IAnimationListener listener) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Move a new child to a different ViewGroup object.
- * <p/>
- * The child is first removed from its current parent, and then added to its new parent, at the
- * given <var>index<var> position. In case the <var>parentView</var> is the current parent of
- * <var>childView</var> then the index must be the value with the <var>childView</var> removed
- * from its parent. If the <var>index</var> is -1 then the child is added at the end of
- * the parent.
- * <p/>
- * If an animation listener is passed then the rendering is done asynchronously and the
- * result is sent to the listener.
- * If the listener is null, then the rendering is done synchronously.
- * <p/>
- * The child stays in the view hierarchy after the rendering is done. To remove it call
- * {@link #removeChild(Object, IAnimationListener)}
- * <p/>
- * The returned {@link Result} object will contain the android.view.ViewGroup.LayoutParams
- * object created from the <var>layoutParams</var> map if it was non <code>null</code>.
- *
- * @param parentView the parent View object to receive the child. Can be the current parent
- * already.
- * @param childView the view to move.
- * @param index the index at which position to add the new child into the parent. -1 means at
- * the end.
- * @param layoutParams an optional map of new ViewGroup.LayoutParams attribute. If non null,
- * then the current layout params of the view will be removed and a new one will
- * be inflated and set with the content of the map.
- * @param listener an optional {@link IAnimationListener}.
- *
- * @return a {@link Result} indicating the status of the action.
- */
- public Result moveChild(Object parentView, Object childView, int index,
- Map<String, String> layoutParams, IAnimationListener listener) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Removes a child from a ViewGroup object.
- * <p/>
- * This does nothing more than change the layout. To render the scene in its new state, a
- * call to {@link #render()} is required.
- * <p/>
- * Any amount of actions can be taken on the scene before {@link #render()} is called.
- *
- * @param childView the view object to remove from its parent
- * @param listener an optional {@link IAnimationListener}.
- *
- * @return a {@link Result} indicating the status of the action.
- */
- public Result removeChild(Object childView, IAnimationListener listener) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Starts playing an given animation on a given object.
- * <p/>
- * The animation playback is asynchronous and the rendered frame is sent vi the
- * <var>listener</var>.
- *
- * @param targetObject the view object to animate
- * @param animationName the name of the animation (res/anim) to play.
- * @param listener the listener callback.
- *
- * @return a {@link Result} indicating the status of the action.
- */
- public Result animate(Object targetObject, String animationName,
- boolean isFrameworkAnimation, IAnimationListener listener) {
- return NOT_IMPLEMENTED.createResult();
- }
-
- /**
- * Discards the layout. No more actions can be called on this object.
- */
- public void dispose() {
- }
-}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java b/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java
deleted file mode 100644
index ec7da14..0000000
--- a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2008 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.ide.common.rendering.api;
-
-import com.android.layoutlib.api.IResourceValue;
-import com.android.resources.ResourceType;
-
-/**
- * Represents an android resource with a name and a string value.
- */
- at SuppressWarnings("deprecation")
-public class ResourceValue extends ResourceReference implements IResourceValue {
- private final ResourceType mType;
- protected String mValue = null;
-
- public ResourceValue(ResourceType type, String name, boolean isFramework) {
- super(name, isFramework);
- mType = type;
- }
-
- public ResourceValue(ResourceType type, String name, String value, boolean isFramework) {
- super(name, isFramework);
- mType = type;
- mValue = value;
- }
-
- public ResourceType getResourceType() {
- return mType;
- }
-
- /**
- * Returns the type of the resource. For instance "drawable", "color", etc...
- * @deprecated use {@link #getResourceType()} instead.
- */
- @Override
- @Deprecated
- public String getType() {
- return mType.getName();
- }
-
- /**
- * Returns the value of the resource, as defined in the XML. This can be <code>null</code>
- */
- @Override
- public String getValue() {
- return mValue;
- }
-
- /**
- * Similar to {@link #getValue()}, but returns the raw XML value. This is <b>usually</b>
- * the same as getValue, but with a few exceptions. For example, for markup strings,
- * you can have * {@code <string name="markup">This is <b>bold</b></string>}.
- * Here, {@link #getValue()} will return "{@code This is bold}" -- e.g. just
- * the plain text flattened. However, this method will return "{@code This is <b>bold</b>}",
- * which preserves the XML markup elements.
- */
- public String getRawXmlValue() {
- return getValue();
- }
-
- /**
- * Sets the value of the resource.
- * @param value the new value
- */
- public void setValue(String value) {
- mValue = value;
- }
-
- /**
- * Sets the value from another resource.
- * @param value the resource value
- */
- public void replaceWith(ResourceValue value) {
- mValue = value.mValue;
- }
-
- @Override
- public String toString() {
- return "ResourceValue [" + mType + "/" + getName() + " = " + mValue //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- + " (framework:" + isFramework() + ")]"; //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mType == null) ? 0 : mType.hashCode());
- result = prime * result + ((mValue == null) ? 0 : mValue.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (!super.equals(obj))
- return false;
- if (getClass() != obj.getClass())
- return false;
- ResourceValue other = (ResourceValue) obj;
- if (mType == null) {
- //noinspection VariableNotUsedInsideIf
- if (other.mType != null)
- return false;
- } else if (!mType.equals(other.mType))
- return false;
- if (mValue == null) {
- //noinspection VariableNotUsedInsideIf
- if (other.mValue != null)
- return false;
- } else if (!mValue.equals(other.mValue))
- return false;
- return true;
- }
-}
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/Density.java b/base/layoutlib-api/src/main/java/com/android/resources/Density.java
deleted file mode 100644
index 15c81f5..0000000
--- a/base/layoutlib-api/src/main/java/com/android/resources/Density.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2010 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.resources;
-
-
-/**
- * Density enum.
- * <p/>This is used in the manifest in the uses-configuration node and in the resource folder names
- * as well as other places needing to know the density values.
- */
-public enum Density implements ResourceEnum {
- XXXHIGH("xxxhdpi", "XXX-High Density", 640, 18), //$NON-NLS-1$
- DPI_560("560dpi", "560 DPI Density", 560, 1), //$NON-NLS-1$
- XXHIGH( "xxhdpi", "XX-High Density", 480, 16), //$NON-NLS-1$
- DPI_400("400dpi", "400 DPI Density", 400, 1), //$NON-NLS-1$
- DPI_360("360dpi", "360 DPI Density", 360, 23), //$NON-NLS-1$
- XHIGH( "xhdpi", "X-High Density", 320, 8), //$NON-NLS-1$
- DPI_280("280dpi", "280 DPI Density", 280, 22), //$NON-NLS-1$
- HIGH( "hdpi", "High Density", 240, 4), //$NON-NLS-1$
- TV( "tvdpi", "TV Density", 213, 13), //$NON-NLS-1$
- MEDIUM( "mdpi", "Medium Density", 160, 4), //$NON-NLS-1$
- LOW( "ldpi", "Low Density", 120, 4), //$NON-NLS-1$
- ANYDPI( "anydpi", "Any Density", 0, 21), //$NON-NLS-1$
- NODPI( "nodpi", "No Density", 0, 4); //$NON-NLS-1$
-
- public static final int DEFAULT_DENSITY = 160;
-
- private final String mValue;
- private final String mDisplayValue;
- private final int mDensity;
- private final int mSince;
-
- Density(String value, String displayValue, int density, int since) {
- mValue = value;
- mDisplayValue = displayValue;
- mDensity = density;
- mSince = since;
- }
-
- /**
- * Returns the enum matching the provided qualifier value.
- * @param value The qualifier value.
- * @return the enum for the qualifier value or null if no match was found.
- */
- public static Density getEnum(String value) {
- for (Density orient : values()) {
- if (orient.mValue.equals(value)) {
- return orient;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the enum matching the given density value
- * @param value The density value.
- * @return the enum for the density value or null if no match was found.
- */
- public static Density getEnum(int value) {
- for (Density d : values()) {
- if (d.mDensity == value) {
- return d;
- }
- }
-
- return null;
- }
-
- @Override
- public String getResourceValue() {
- return mValue;
- }
-
- public int getDpiValue() {
- return mDensity;
- }
-
- public int since() {
- return mSince;
- }
-
- public String getLegacyValue() {
- if (this != NODPI && this != ANYDPI) {
- return String.format("%1$ddpi", getDpiValue());
- }
-
- return "";
- }
-
- @Override
- public String getShortDisplayValue() {
- return mDisplayValue;
- }
-
- @Override
- public String getLongDisplayValue() {
- return mDisplayValue;
- }
-
- public static int getIndex(Density value) {
- int i = 0;
- for (Density input : values()) {
- if (value == input) {
- return i;
- }
-
- i++;
- }
-
- return -1;
- }
-
- public static Density getByIndex(int index) {
- Density[] values = values();
- if (index >=0 && index < values.length) {
- return values[index];
- }
- return null;
- }
-
- /**
- * Returns true if this density is relevant for app developers (e.g.
- * a density you should consider providing resources for)
- */
- public boolean isRecommended() {
- switch (this) {
- case TV:
- case DPI_280:
- case DPI_360:
- case DPI_400:
- case DPI_560:
- return false;
- default:
- return true;
- }
- }
-
- @Override
- public boolean isFakeValue() {
- return false;
- }
-
- @Override
- public boolean isValidValueForDevice() {
- return this != NODPI && this != ANYDPI; // nodpi/anydpi is not a valid config for devices.
- }
-}
diff --git a/base/legacy/ant-tasks/.classpath b/base/legacy/ant-tasks/.classpath
deleted file mode 100644
index a837fee..0000000
--- a/base/legacy/ant-tasks/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src/main/java"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry combineaccessrules="false" kind="src" path="/sdklib"/>
- <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/org/apache/ant/ant/1.8.0/ant-1.8.0.jar"/>
- <classpathentry combineaccessrules="false" kind="src" path="/common"/>
- <classpathentry combineaccessrules="false" kind="src" path="/manifest-merger"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/base/legacy/ant-tasks/.gitignore b/base/legacy/ant-tasks/.gitignore
deleted file mode 100644
index fe99505..0000000
--- a/base/legacy/ant-tasks/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-bin
-
diff --git a/base/legacy/ant-tasks/.project b/base/legacy/ant-tasks/.project
deleted file mode 100644
index aed1b61..0000000
--- a/base/legacy/ant-tasks/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>ant-tasks</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/base/legacy/ant-tasks/.settings/README.txt b/base/legacy/ant-tasks/.settings/README.txt
deleted file mode 100644
index 9120b20..0000000
--- a/base/legacy/ant-tasks/.settings/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Copy this in eclipse project as a .settings folder at the root.
-This ensure proper compilation compliance and warning/error levels.
\ No newline at end of file
diff --git a/base/legacy/ant-tasks/ant-tasks.iml b/base/legacy/ant-tasks/ant-tasks.iml
deleted file mode 100644
index 3b96e76..0000000
--- a/base/legacy/ant-tasks/ant-tasks.iml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="ant" level="project" />
- <orderEntry type="module" module-name="manifest-merger-base" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/legacy/ant-tasks/build.gradle b/base/legacy/ant-tasks/build.gradle
deleted file mode 100644
index 65288b6..0000000
--- a/base/legacy/ant-tasks/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'sdk-java-lib'
-
-group = 'com.android.tools.build'
-archivesBaseName = 'ant-tasks'
-version = rootProject.ext.baseVersion
-
-dependencies {
- compile project(':base:manifest-merger')
-
- testCompile 'junit:junit:4.12'
-}
-
-// configuration for dependencies provided by the runtime,
-// in this case Ant.
-configurations{
- provided
-}
-
-dependencies{
- provided "org.apache.ant:ant:1.8.0"
-}
-
-//Include provided for compilation
-sourceSets.main.compileClasspath += configurations.provided
-
-javadoc.classpath += configurations.provided
diff --git a/base/legacy/ant-tasks/src/main/ant-tasks.iml b/base/legacy/ant-tasks/src/main/ant-tasks.iml
deleted file mode 100644
index ebf58d4..0000000
--- a/base/legacy/ant-tasks/src/main/ant-tasks.iml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$/../..">
- <sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module" module-name="manifest-merger" exported="" />
- <orderEntry type="library" name="ant-1.8.0" level="project" />
- </component>
-</module>
-
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/AaptExecTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/AaptExecTask.java
deleted file mode 100644
index ea14dac..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/AaptExecTask.java
+++ /dev/null
@@ -1,797 +0,0 @@
-/*
- * Copyright (C) 2009 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.ant;
-
-import com.android.SdkConstants;
-import com.android.sdklib.internal.build.SymbolLoader;
-import com.android.sdklib.internal.build.SymbolWriter;
-import com.android.xml.AndroidXPathFactory;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.Path;
-import org.xml.sax.InputSource;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpressionException;
-
-/**
- * Task to execute aapt.
- *
- * <p>It does not follow the exec task format, instead it has its own parameters, which maps
- * directly to aapt.</p>
- * <p>It is able to run aapt several times if library setup requires generating several
- * R.java files.
- * <p>The following map shows how to use the task for each supported aapt command line
- * parameter.</p>
- *
- * <table border="1">
- * <tr><td><b>Aapt Option</b></td><td><b>Ant Name</b></td><td><b>Type</b></td></tr>
- * <tr><td>path to aapt</td><td>executable</td><td>attribute (Path)</td>
- * <tr><td>command</td><td>command</td><td>attribute (String)</td>
- * <tr><td>-v</td><td>verbose</td><td>attribute (boolean)</td></tr>
- * <tr><td>-f</td><td>force</td><td>attribute (boolean)</td></tr>
- * <tr><td>-M AndroidManifest.xml</td><td>manifest</td><td>attribute (Path)</td></tr>
- * <tr><td>-I base-package</td><td>androidjar</td><td>attribute (Path)</td></tr>
- * <tr><td>-A asset-source-dir</td><td>assets</td><td>attribute (Path</td></tr>
- * <tr><td>-S resource-sources</td><td><res path=""></td><td>nested element(s)<br>with attribute (Path)</td></tr>
- * <tr><td>-0 extension</td><td><nocompress extension=""><br><nocompress></td><td>nested element(s)<br>with attribute (String)</td></tr>
- * <tr><td>-F apk-file</td><td>apkfolder<br>outfolder<br>apkbasename<br>basename</td><td>attribute (Path)<br>attribute (Path) deprecated<br>attribute (String)<br>attribute (String) deprecated</td></tr>
- * <tr><td>-J R-file-dir</td><td>rfolder</td><td>attribute (Path)<br>-m always enabled</td></tr>
- * <tr><td>--rename-manifest-package package-name</td><td>manifestpackage</td><td>attribute (String)</td></tr>
- * <tr><td></td><td></td><td></td></tr>
- * </table>
- */
-public final class AaptExecTask extends SingleDependencyTask {
-
- /**
- * Class representing a <nocompress> node in the main task XML.
- * This let the developers prevent compression of some files in assets/ and res/raw/
- * by extension.
- * If the extension is null, this will disable compression for all files in assets/ and
- * res/raw/
- */
- public static final class NoCompress {
- String mExtension;
-
- /**
- * Sets the value of the "extension" attribute.
- * @param extention the extension.
- */
- public void setExtension(String extention) {
- mExtension = extention;
- }
- }
-
- private String mExecutable;
- private String mCommand;
- private boolean mForce = true; // true due to legacy reasons
- private boolean mDebug = false;
- private boolean mVerbose = false;
- private boolean mUseCrunchCache = false;
- private int mVersionCode = 0;
- private String mVersionName;
- private String mManifestFile;
- private String mManifestPackage;
- private String mOriginalManifestPackage;
- private ArrayList<Path> mResources;
- private String mAssets;
- private String mAndroidJar;
- private String mApkFolder;
- private String mApkName;
- private String mResourceFilter;
- private String mRFolder;
- private final ArrayList<NoCompress> mNoCompressList = new ArrayList<NoCompress>();
- private String mLibraryResFolderPathRefid;
- private String mLibraryPackagesRefid;
- private String mLibraryRFileRefid;
- private boolean mNonConstantId;
- private String mIgnoreAssets;
- private String mBinFolder;
- private String mProguardFile;
-
- /**
- * Input path that ignores the same folders/files that aapt does.
- */
- private static class ResFolderInputPath extends InputPath {
- public ResFolderInputPath(File file, Set<String> extensionsToCheck) {
- super(file, extensionsToCheck);
- }
-
- @Override
- public boolean ignores(File file) {
- String name = file.getName();
- char firstChar = name.charAt(0);
-
- if (firstChar == '.' || (firstChar == '_' && file.isDirectory()) ||
- name.charAt(name.length()-1) == '~') {
- return true;
- }
-
- if ("CVS".equals(name) ||
- "thumbs.db".equalsIgnoreCase(name) ||
- "picasa.ini".equalsIgnoreCase(name)) {
- return true;
- }
-
- String ext = getExtension(name);
- if ("scc".equalsIgnoreCase(ext)) {
- return true;
- }
-
- return false;
- }
- }
-
- private static final InputPathFactory sPathFactory = new InputPathFactory() {
-
- @Override
- public InputPath createPath(File file, Set<String> extensionsToCheck) {
- return new ResFolderInputPath(file, extensionsToCheck);
- }
- };
-
- /**
- * Sets the value of the "executable" attribute.
- * @param executable the value.
- */
- public void setExecutable(Path executable) {
- mExecutable = TaskHelper.checkSinglePath("executable", executable);
- }
-
- /**
- * Sets the value of the "command" attribute.
- * @param command the value.
- */
- public void setCommand(String command) {
- mCommand = command;
- }
-
- /**
- * Sets the value of the "force" attribute.
- * @param force the value.
- */
- public void setForce(boolean force) {
- mForce = force;
- }
-
- /**
- * Sets the value of the "verbose" attribute.
- * @param verbose the value.
- */
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- /**
- * Sets the value of the "usecrunchcache" attribute
- * @param usecrunch whether to use the crunch cache.
- */
- public void setNoCrunch(boolean usecrunch) {
- mUseCrunchCache = usecrunch;
- }
-
- public void setNonConstantId(boolean nonConstantId) {
- mNonConstantId = nonConstantId;
- }
-
- public void setIgnoreAssets(String ignoreAssets) {
- mIgnoreAssets = ignoreAssets;
- }
-
- public void setVersioncode(String versionCode) {
- if (!versionCode.isEmpty()) {
- try {
- mVersionCode = Integer.decode(versionCode);
- } catch (NumberFormatException e) {
- System.out.println(String.format(
- "WARNING: Ignoring invalid version code value '%s'.", versionCode));
- }
- }
- }
-
- /**
- * Sets the value of the "versionName" attribute
- * @param versionName the value
- */
- public void setVersionname(String versionName) {
- mVersionName = versionName;
- }
-
- public void setDebug(boolean value) {
- mDebug = value;
- }
-
- /**
- * Sets the value of the "manifest" attribute.
- * @param manifest the value.
- */
- public void setManifest(Path manifest) {
- mManifestFile = TaskHelper.checkSinglePath("manifest", manifest);
- }
-
- /**
- * Sets a custom manifest package ID to be used during packaging.<p>
- * The manifest will be rewritten so that its package ID becomes the value given here.
- * Relative class names in the manifest (e.g. ".Foo") will be rewritten to absolute names based
- * on the existing package name, meaning that no code changes need to be made.
- *
- * @param packageName The package ID the APK should have.
- */
- public void setManifestpackage(String packageName) {
- if (packageName != null && !packageName.isEmpty()) {
- mManifestPackage = packageName;
- }
- }
-
- /**
- * Sets the original package name found in the manifest. This is the package name where
- * the R class is created.
- *
- * This is merely a shortcut in case the package is known when calling the aapt task. If not
- * provided (and needed) this task will recompute it.
- * @param packageName the package name declared in the manifest.
- */
- public void setOriginalManifestPackage(String packageName) {
- mOriginalManifestPackage = packageName;
- }
-
- /**
- * Sets the value of the "resources" attribute.
- * @param resources the value.
- *
- * @deprecated Use nested element(s) <res path="value" />
- */
- @Deprecated
- public void setResources(Path resources) {
- System.out.println("WARNNG: Using deprecated 'resources' attribute in AaptExecLoopTask." +
- "Use nested element(s) <res path=\"value\" /> instead.");
- if (mResources == null) {
- mResources = new ArrayList<Path>();
- }
-
- mResources.add(new Path(getProject(), resources.toString()));
- }
-
- /**
- * Sets the value of the "assets" attribute.
- * @param assets the value.
- */
- public void setAssets(Path assets) {
- mAssets = TaskHelper.checkSinglePath("assets", assets);
- }
-
- /**
- * Sets the value of the "androidjar" attribute.
- * @param androidJar the value.
- */
- public void setAndroidjar(Path androidJar) {
- mAndroidJar = TaskHelper.checkSinglePath("androidjar", androidJar);
- }
-
- /**
- * Sets the value of the "outfolder" attribute.
- * @param outFolder the value.
- * @deprecated use {@link #setApkfolder(Path)}
- */
- @Deprecated
- public void setOutfolder(Path outFolder) {
- System.out.println("WARNNG: Using deprecated 'outfolder' attribute in AaptExecLoopTask." +
- "Use 'apkfolder' (path) instead.");
- mApkFolder = TaskHelper.checkSinglePath("outfolder", outFolder);
- }
-
- /**
- * Sets the value of the "apkfolder" attribute.
- * @param apkFolder the value.
- */
- public void setApkfolder(Path apkFolder) {
- mApkFolder = TaskHelper.checkSinglePath("apkfolder", apkFolder);
- }
-
- /**
- * Sets the value of the resourcefilename attribute
- * @param apkName the value
- */
- public void setResourcefilename(String apkName) {
- mApkName = apkName;
- }
-
- /**
- * Sets the value of the "rfolder" attribute.
- * @param rFolder the value.
- */
- public void setRfolder(Path rFolder) {
- mRFolder = TaskHelper.checkSinglePath("rfolder", rFolder);
- }
-
- public void setresourcefilter(String filter) {
- if (filter != null && !filter.isEmpty()) {
- mResourceFilter = filter;
- }
- }
-
- /**
- * Set the property name of the property that contains the list of res folder for
- * Library Projects. This sets the name and not the value itself to handle the case where
- * it doesn't exist.
- * @param libraryResFolderPathRefid
- */
- public void setLibraryResFolderPathRefid(String libraryResFolderPathRefid) {
- mLibraryResFolderPathRefid = libraryResFolderPathRefid;
- }
-
- public void setLibraryPackagesRefid(String libraryPackagesRefid) {
- mLibraryPackagesRefid = libraryPackagesRefid;
- }
-
- public void setLibraryRFileRefid(String libraryRFileRefid) {
- mLibraryRFileRefid = libraryRFileRefid;
- }
-
- public void setBinFolder(Path binFolder) {
- mBinFolder = TaskHelper.checkSinglePath("binFolder", binFolder);
- }
-
- public void setProguardFile(Path proguardFile) {
- mProguardFile = TaskHelper.checkSinglePath("proguardFile", proguardFile);
- }
-
- /**
- * Returns an object representing a nested <var>nocompress</var> element.
- */
- public Object createNocompress() {
- NoCompress nc = new NoCompress();
- mNoCompressList.add(nc);
- return nc;
- }
-
- /**
- * Returns an object representing a nested <var>res</var> element.
- */
- public Object createRes() {
- if (mResources == null) {
- mResources = new ArrayList<Path>();
- }
-
- Path path = new Path(getProject());
- mResources.add(path);
-
- return path;
- }
-
- @Override
- protected String getExecTaskName() {
- return "aapt";
- }
-
- /*
- * (non-Javadoc)
- *
- * Executes the loop. Based on the values inside project.properties, this will
- * create alternate temporary ap_ files.
- *
- * @see org.apache.tools.ant.Task#execute()
- */
- @SuppressWarnings("deprecation")
- @Override
- public void execute() throws BuildException {
- if (mLibraryResFolderPathRefid == null) {
- throw new BuildException("Missing attribute libraryResFolderPathRefid");
- }
- if (mLibraryPackagesRefid == null) {
- throw new BuildException("Missing attribute libraryPackagesRefid");
- }
- if (mLibraryRFileRefid == null) {
- throw new BuildException("Missing attribute libraryRFileRefid");
- }
-
- Project taskProject = getProject();
-
- String libPkgProp = null;
- Path libRFileProp = null;
-
- // if the parameters indicate generation of the R class, check if
- // more R classes need to be created for libraries, only if this project itself
- // is not a library
- if (mNonConstantId == false && mRFolder != null && new File(mRFolder).isDirectory()) {
- libPkgProp = taskProject.getProperty(mLibraryPackagesRefid);
- Object rFilePath = taskProject.getReference(mLibraryRFileRefid);
-
- // if one is null, both should be
- if ((libPkgProp == null || rFilePath == null) &&
- rFilePath != libPkgProp) {
- throw new BuildException(String.format(
- "Both %1$s and %2$s should resolve to valid values.",
- mLibraryPackagesRefid, mLibraryRFileRefid));
- }
-
- if (rFilePath instanceof Path) {
- libRFileProp = (Path) rFilePath;
- } else if (rFilePath != null) {
- throw new BuildException("attribute libraryRFileRefid must reference a Path object.");
- }
- }
-
- final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory();
-
- // Get whether we have libraries
- Object libResRef = taskProject.getReference(mLibraryResFolderPathRefid);
-
- // Set up our input paths that matter for dependency checks
- ArrayList<File> paths = new ArrayList<File>();
-
- // the project res folder is an input path of course
- for (Path pathList : mResources) {
- for (String path : pathList.list()) {
- paths.add(new File(path));
- }
- }
-
- // and if libraries exist, their res folders folders too.
- if (libResRef instanceof Path) {
- for (String path : ((Path)libResRef).list()) {
- paths.add(new File(path));
- }
- }
-
- // Now we figure out what we need to do
- if (generateRClass) {
- // in this case we only want to run aapt if an XML file was touched, or if any
- // file is added/removed
- List<InputPath> inputPaths = getInputPaths(paths, Collections.singleton("xml"),
- sPathFactory);
-
- // let's not forget the manifest as an input path (with no extension restrictions).
- if (mManifestFile != null) {
- inputPaths.add(new InputPath(new File(mManifestFile)));
- }
-
- // Check to see if our dependencies have changed. If not, then skip
- if (initDependencies(mRFolder + File.separator + "R.java.d", inputPaths)
- && dependenciesHaveChanged() == false) {
- System.out.println("No changed resources. R.java and Manifest.java untouched.");
- return;
- } else {
- System.out.println("Generating resource IDs...");
- }
- } else {
- // in this case we want to run aapt if any file was updated/removed/added in any of the
- // input paths
- List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/,
- sPathFactory);
-
- // let's not forget the manifest as an input path.
- if (mManifestFile != null) {
- inputPaths.add(new InputPath(new File(mManifestFile)));
- }
-
- // If we're here to generate a .ap_ file we need to use assets as an input path as well.
- if (mAssets != null) {
- File assetsDir = new File(mAssets);
- if (assetsDir.isDirectory()) {
- inputPaths.add(new InputPath(assetsDir));
- }
- }
-
- // Find our dependency file. It should have the same name as our target .ap_ but
- // with a .d extension
- String dependencyFilePath = mApkFolder + File.separator + mApkName;
- dependencyFilePath += ".d";
-
- // Check to see if our dependencies have changed
- if (initDependencies(dependencyFilePath, inputPaths)
- && dependenciesHaveChanged() == false) {
- System.out.println("No changed resources or assets. " + mApkName
- + " remains untouched");
- return;
- }
- if (mResourceFilter == null) {
- System.out.println("Creating full resource package...");
- } else {
- System.out.println(String.format(
- "Creating resource package with filter: (%1$s)...",
- mResourceFilter));
- }
- }
-
- // create a task for the default apk.
- ExecTask task = new ExecTask();
- task.setExecutable(mExecutable);
- task.setFailonerror(true);
-
- task.setTaskName(getExecTaskName());
-
- // aapt command. Only "package" is supported at this time really.
- task.createArg().setValue(mCommand);
-
- // No crunch flag
- if (mUseCrunchCache) {
- task.createArg().setValue("--no-crunch");
- }
-
- if (mNonConstantId) {
- task.createArg().setValue("--non-constant-id");
- }
-
- // force flag
- if (mForce) {
- task.createArg().setValue("-f");
- }
-
- // verbose flag
- if (mVerbose) {
- task.createArg().setValue("-v");
- }
-
- if (mDebug) {
- task.createArg().setValue("--debug-mode");
- }
-
- if (generateRClass) {
- task.createArg().setValue("-m");
- }
-
- // filters if needed
- if (mResourceFilter != null && !mResourceFilter.isEmpty()) {
- task.createArg().setValue("-c");
- task.createArg().setValue(mResourceFilter);
- }
-
- // no compress flag
- // first look to see if there's a NoCompress object with no specified extension
- boolean compressNothing = false;
- for (NoCompress nc : mNoCompressList) {
- if (nc.mExtension == null) {
- task.createArg().setValue("-0");
- task.createArg().setValue("");
- compressNothing = true;
- break;
- }
- }
-
- // never compress apks.
- task.createArg().setValue("-0");
- task.createArg().setValue("apk");
-
- if (compressNothing == false) {
- for (NoCompress nc : mNoCompressList) {
- task.createArg().setValue("-0");
- task.createArg().setValue(nc.mExtension);
- }
- }
-
- // if this is a library or there are library dependencies
- if (mNonConstantId || (libPkgProp != null && !libPkgProp.isEmpty())) {
- if (mBinFolder == null) {
- throw new BuildException(
- "Missing attribute binFolder when compiling libraries or projects with libraries.");
- }
- task.createArg().setValue("--output-text-symbols");
- task.createArg().setValue(mBinFolder);
- }
-
- // if the project contains libraries, force auto-add-overlay
- if (libResRef != null) {
- task.createArg().setValue("--auto-add-overlay");
- }
-
- if (mVersionCode != 0) {
- task.createArg().setValue("--version-code");
- task.createArg().setValue(Integer.toString(mVersionCode));
- }
-
- if (mVersionName != null && !mVersionName.isEmpty()) {
- task.createArg().setValue("--version-name");
- task.createArg().setValue(mVersionName);
- }
-
- // manifest location
- if (mManifestFile != null && !mManifestFile.isEmpty()) {
- task.createArg().setValue("-M");
- task.createArg().setValue(mManifestFile);
- }
-
- // Rename manifest package
- if (mManifestPackage != null) {
- task.createArg().setValue("--rename-manifest-package");
- task.createArg().setValue(mManifestPackage);
- }
-
- // resources locations.
- if (!mResources.isEmpty()) {
- for (Path pathList : mResources) {
- for (String path : pathList.list()) {
- // This may not exists, and aapt doesn't like it, so we check first.
- File res = new File(path);
- if (res.isDirectory()) {
- task.createArg().setValue("-S");
- task.createArg().setValue(path);
- }
- }
- }
- }
-
- // add other resources coming from library project
- if (libResRef instanceof Path) {
- for (String path : ((Path)libResRef).list()) {
- // This may not exists, and aapt doesn't like it, so we check first.
- File res = new File(path);
- if (res.isDirectory()) {
- task.createArg().setValue("-S");
- task.createArg().setValue(path);
- }
- }
- }
-
- // assets location. This may not exists, and aapt doesn't like it, so we check first.
- if (mAssets != null && new File(mAssets).isDirectory()) {
- task.createArg().setValue("-A");
- task.createArg().setValue(mAssets);
- }
-
- // android.jar
- if (mAndroidJar != null) {
- task.createArg().setValue("-I");
- task.createArg().setValue(mAndroidJar);
- }
-
- // apk file. This is based on the apkFolder, apkBaseName, and the configName (if applicable)
- String filename = null;
- if (mApkName != null) {
- filename = mApkName;
- }
-
- if (filename != null) {
- File file = new File(mApkFolder, filename);
- task.createArg().setValue("-F");
- task.createArg().setValue(file.getAbsolutePath());
- }
-
- // R class generation
- if (generateRClass) {
- task.createArg().setValue("-J");
- task.createArg().setValue(mRFolder);
- }
-
- // ignore assets flag
- if (mIgnoreAssets != null && !mIgnoreAssets.isEmpty()) {
- task.createArg().setValue("--ignore-assets");
- task.createArg().setValue(mIgnoreAssets);
- }
-
- // Use dependency generation
- task.createArg().setValue("--generate-dependencies");
-
- // use the proguard file
- if (mProguardFile != null && !mProguardFile.isEmpty()) {
- task.createArg().setValue("-G");
- task.createArg().setValue(mProguardFile);
- }
-
- // final setup of the task
- task.setProject(taskProject);
- task.setOwningTarget(getOwningTarget());
-
- // execute it.
- task.execute();
-
- // now if the project has libraries, R needs to be created for each libraries
- // but only if the project is not a library.
- try {
- if (!mNonConstantId && libPkgProp != null && !libPkgProp.isEmpty()) {
- File rFile = new File(mBinFolder, SdkConstants.FN_RESOURCE_TEXT);
- if (rFile.isFile()) {
- // Load the full symbols from the full R.txt file.
- SymbolLoader fullSymbolValues = new SymbolLoader(rFile);
- fullSymbolValues.load();
-
- // we have two props which contains list of items. Both items represent
- // 2 data of a single property.
- // Don't want to use guava's splitter because it doesn't provide a list of the
- // result. but we know the list starts with a ; so strip it.
- if (libPkgProp.startsWith(";")) {
- libPkgProp = libPkgProp.substring(1).trim();
- }
- String[] packages = libPkgProp.split(";");
- String[] rFiles = libRFileProp.list();
-
- if (packages.length != rFiles.length) {
- throw new BuildException(String.format(
- "%1$s and %2$s must contain the same number of items.",
- mLibraryPackagesRefid, mLibraryRFileRefid));
- }
-
- if (mOriginalManifestPackage == null) {
- mOriginalManifestPackage = getPackageName(mManifestFile);
- }
-
- Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
-
- // First pass processing the libraries, collecting them by packageName,
- // and ignoring the ones that have the same package name as the application
- // (since that R class was already created).
- for (int i = 0 ; i < packages.length ; i++) {
- String libPackage = packages[i];
-
- // skip libraries that have the same package name as the application.
- if (mOriginalManifestPackage.equals(libPackage)) {
- continue;
- }
-
- File rText = new File(rFiles[i]);
- if (rText.isFile()) {
- // load the lib symbols
- SymbolLoader libSymbols = new SymbolLoader(rText);
- libSymbols.load();
-
- // store these symbols by associating them with the package name.
- libMap.put(libPackage, libSymbols);
- }
- }
-
- // now loop on all the package names, merge all the symbols to write,
- // and write them
- for (String packageName : libMap.keySet()) {
- Collection<SymbolLoader> symbols = libMap.get(packageName);
-
- SymbolWriter writer = new SymbolWriter(mRFolder, packageName,
- fullSymbolValues);
- for (SymbolLoader symbolLoader : symbols) {
- writer.addSymbolsToWrite(symbolLoader);
- }
- writer.write();
- }
- }
- }
- } catch (Exception e) {
- // HACK alert.
- // in order for this step to happen again when this part fails, we delete
- // the dependency file.
- File f = new File(mRFolder, "R.java.d");
- f.delete();
-
- throw (e instanceof BuildException) ? (BuildException)e : new BuildException(e);
- }
- }
-
- private String getPackageName(String manifest) {
- XPath xpath = AndroidXPathFactory.newXPath();
-
- try {
- String s = xpath.evaluate("/manifest/@package",
- new InputSource(new FileInputStream(manifest)));
- return s;
- } catch (XPathExpressionException e) {
- throw new BuildException(e);
- } catch (FileNotFoundException e) {
- throw new BuildException(e);
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/AidlExecTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/AidlExecTask.java
deleted file mode 100644
index 2058232..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/AidlExecTask.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2010 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.ant;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.io.FileOp;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.Path;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Task to execute aidl.
- * <p>
- * It expects 5 attributes:<br>
- * 'executable' ({@link Path} with a single path) for the location of the aidl executable<br>
- * 'framework' ({@link Path} with a single path) for the "preprocessed" file containing all the
- * parcelables exported by the framework<br>
- * 'genFolder' ({@link Path} with a single path) for the location of the gen folder.
- * 'aidlOutFolder' ({@link Path} with a single path) for the location of the bin/aidl folder to
- * copy the aidl files.
- * 'libraryBinAidlFolderPathRefid' the name of the reference to a path object that contains
- * libraries aidl output folder.
- *
- * It also expects one or more inner elements called "source" which are identical to {@link Path}
- * elements.
- */
-public class AidlExecTask extends MultiFilesTask {
-
- private String mExecutable;
- private String mFramework;
- private Path mLibraryBinAidlFolderPath;
- private String mGenFolder;
- private final ArrayList<Path> mPaths = new ArrayList<Path>();
- private String mAidlOutFolder;
-
- private class AidlProcessor implements SourceProcessor {
-
- @Override
- @NonNull
- public Set<String> getSourceFileExtensions() {
- return Collections.singleton(SdkConstants.EXT_AIDL);
- }
-
- @Override
- public void process(
- @NonNull String filePath,
- @NonNull String sourceFolder,
- @NonNull List<String> sourceFolders,
- @NonNull Project taskProject) {
- ExecTask task = new ExecTask();
- task.setProject(taskProject);
- task.setOwningTarget(getOwningTarget());
- task.setExecutable(mExecutable);
- task.setTaskName("aidl");
- task.setFailonerror(true);
-
- task.createArg().setValue("-p" + mFramework);
- task.createArg().setValue("-o" + mGenFolder);
- // add all the source folders as import in case an aidl file in a source folder
- // imports a parcelable from another source folder.
- for (String importFolder : sourceFolders) {
- task.createArg().setValue("-I" + importFolder);
- }
-
- // add all the library aidl folders to access parcelables that are in libraries
- if (mLibraryBinAidlFolderPath != null) {
- for (String importFolder : mLibraryBinAidlFolderPath.list()) {
- task.createArg().setValue("-I" + importFolder);
- }
- }
-
- // set auto dependency file creation
- task.createArg().setValue("-a");
-
- task.createArg().setValue(filePath);
-
- // execute it.
- task.execute();
-
- // if we reach here, it was successful (execute throws an exception otherwise).
- // Copy the file into the bin/aidl directory.
- String relative = filePath.substring(sourceFolder.length());
- if (relative.charAt(0) == '/' || relative.charAt(0) == File.separatorChar) {
- relative = relative.substring(1);
- }
-
- try {
- File dest = new File(mAidlOutFolder, relative);
- File parent = dest.getParentFile();
- parent.mkdirs();
-
- FileOp op = new FileOp();
- op.copyFile(new File(filePath), dest);
- } catch (IOException e) {
- throw new BuildException(e);
- }
- }
-
- @Override
- public void displayMessage(@NonNull DisplayType type, int count) {
- switch (type) {
- case FOUND:
- System.out.println(String.format("Found %1$d AIDL files.", count));
- break;
- case COMPILING:
- if (count > 0) {
- System.out.println(String.format("Compiling %1$d AIDL files.",
- count));
- } else {
- System.out.println("No AIDL files to compile.");
- }
- break;
- case REMOVE_OUTPUT:
- System.out.println(String.format("Found %1$d obsolete output files to remove.",
- count));
- break;
- case REMOVE_DEP:
- System.out.println(
- String.format("Found %1$d obsolete dependency files to remove.",
- count));
- break;
- }
- }
-
- @Override
- public void removedOutput(@NonNull File file) {
- // nothing to do.
- }
- }
-
- /**
- * Sets the value of the "executable" attribute.
- * @param executable the value.
- */
- public void setExecutable(Path executable) {
- mExecutable = TaskHelper.checkSinglePath("executable", executable);
- }
-
- public void setFramework(Path value) {
- mFramework = TaskHelper.checkSinglePath("framework", value);
- }
-
- public void setLibraryBinAidlFolderPathRefid(String libraryBinAidlFolderPathRefid) {
- Object libBinAidlRef = getProject().getReference(libraryBinAidlFolderPathRefid);
- if (libBinAidlRef instanceof Path) {
- mLibraryBinAidlFolderPath = (Path) libBinAidlRef;
- }
- }
-
- public void setGenFolder(Path value) {
- mGenFolder = TaskHelper.checkSinglePath("genFolder", value);
- }
-
- public void setAidlOutFolder(Path value) {
- mAidlOutFolder = TaskHelper.checkSinglePath("aidlOutFolder", value);
- }
-
- public Path createSource() {
- Path p = new Path(getProject());
- mPaths.add(p);
- return p;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mExecutable == null) {
- throw new BuildException("AidlExecTask's 'executable' is required.");
- }
- if (mFramework == null) {
- throw new BuildException("AidlExecTask's 'framework' is required.");
- }
- if (mGenFolder == null) {
- throw new BuildException("AidlExecTask's 'genFolder' is required.");
- }
- if (mAidlOutFolder == null) {
- throw new BuildException("AidlExecTask's 'aidlOutFolder' is required.");
- }
-
- processFiles(new AidlProcessor(), mPaths, mGenFolder);
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/ApkBuilderTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/ApkBuilderTask.java
deleted file mode 100644
index f5d8e6b..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/ApkBuilderTask.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (C) 2009 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.ant;
-
-import com.android.sdklib.build.ApkBuilder;
-import com.android.sdklib.build.ApkBuilder.FileEntry;
-import com.android.sdklib.build.ApkCreationException;
-import com.android.sdklib.build.DuplicateFileException;
-import com.android.sdklib.build.SealedApkException;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.types.Path;
-
-import java.io.File;
-import java.io.FilenameFilter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-public class ApkBuilderTask extends SingleDependencyTask {
-
- private static final Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
- Pattern.CASE_INSENSITIVE);
-
- private String mOutFolder;
- private String mApkFilepath;
- private String mResourceFile;
- private boolean mVerbose = false;
- private boolean mDebugPackaging = false;
- private boolean mDebugSigning = false;
- private boolean mHasCode = true;
-
- private Path mDexPath;
-
- private final ArrayList<Path> mZipList = new ArrayList<Path>();
- private final ArrayList<Path> mSourceList = new ArrayList<Path>();
- private final ArrayList<Path> mJarfolderList = new ArrayList<Path>();
- private final ArrayList<Path> mJarfileList = new ArrayList<Path>();
- private final ArrayList<Path> mNativeList = new ArrayList<Path>();
-
- private static class SourceFolderInputPath extends InputPath {
- public SourceFolderInputPath(File file) {
- super(file);
- }
-
- @Override
- public boolean ignores(File file) {
- if (file.isDirectory()) {
- return !ApkBuilder.checkFolderForPackaging(file.getName());
- } else {
- return !ApkBuilder.checkFileForPackaging(file.getName());
- }
- }
- }
-
- /**
- * Sets the value of the "outfolder" attribute.
- * @param outFolder the value.
- */
- public void setOutfolder(Path outFolder) {
- mOutFolder = TaskHelper.checkSinglePath("outfolder", outFolder);
- }
-
- /**
- * Sets the full filepath to the apk to generate.
- * @param filepath
- */
- public void setApkfilepath(String filepath) {
- mApkFilepath = filepath;
- }
-
- /**
- * Sets the resourcefile attribute
- * @param resourceFile
- */
- public void setResourcefile(String resourceFile) {
- mResourceFile = resourceFile;
- }
-
- /**
- * Sets the value of the "verbose" attribute.
- * @param verbose the value.
- */
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- /**
- * Sets the value of the "debug" attribute.
- * @param debug the debug mode value.
- */
- public void setDebug(boolean debug) {
- System.out.println("WARNNG: Using deprecated 'debug' attribute in ApkBuilderTask." +
- "Use 'debugpackaging' and 'debugsigning' instead.");
- mDebugPackaging = debug;
- mDebugSigning = debug;
- }
-
- /**
- * Sets the value of the "debugpackaging" attribute.
- * @param debug the debug mode value.
- */
- public void setDebugpackaging(boolean debug) {
- mDebugPackaging = debug;
- }
-
- /**
- * Sets the value of the "debugsigning" attribute.
- * @param debug the debug mode value.
- */
- public void setDebugsigning(boolean debug) {
- mDebugSigning = debug;
- }
-
- /**
- * Sets the hascode attribute. Default is true.
- * If set to false, then <dex> and <sourcefolder> nodes are ignored and not processed.
- * @param hasCode the value of the attribute.
- */
- public void setHascode(boolean hasCode) {
- mHasCode = hasCode;
- }
-
- /**
- * Returns an object representing a nested <var>zip</var> element.
- */
- public Object createZip() {
- Path path = new Path(getProject());
- mZipList.add(path);
- return path;
- }
-
- /**
- * Returns an object representing a nested <var>dex</var> element.
- * This is similar to a nested <var>file</var> element, except when {@link #mHasCode}
- * is <code>false</code> in which case it's ignored.
- */
- public Object createDex() {
- if (mDexPath == null) {
- return mDexPath = new Path(getProject());
- } else {
- throw new BuildException("Only one <dex> inner element can be provided");
- }
- }
-
- /**
- * Returns an object representing a nested <var>sourcefolder</var> element.
- */
- public Object createSourcefolder() {
- Path path = new Path(getProject());
- mSourceList.add(path);
- return path;
- }
-
- /**
- * Returns an object representing a nested <var>jarfolder</var> element.
- */
- public Object createJarfolder() {
- Path path = new Path(getProject());
- mJarfolderList.add(path);
- return path;
- }
-
- /**
- * Returns an object representing a nested <var>jarfile</var> element.
- */
- public Object createJarfile() {
- Path path = new Path(getProject());
- mJarfileList.add(path);
- return path;
- }
-
- /**
- * Returns an object representing a nested <var>nativefolder</var> element.
- */
- public Object createNativefolder() {
- Path path = new Path(getProject());
- mNativeList.add(path);
- return path;
- }
-
- @Override
- public void execute() throws BuildException {
-
- File outputFile;
- if (mApkFilepath != null) {
- outputFile = new File(mApkFilepath);
- } else {
- throw new BuildException("missing attribute 'apkFilepath'");
- }
-
- if (mResourceFile == null) {
- throw new BuildException("missing attribute 'resourcefile'");
- }
-
- if (mOutFolder == null) {
- throw new BuildException("missing attribute 'outfolder'");
- }
-
- // check dexPath is only one file.
- File dexFile = null;
- if (mHasCode) {
- String[] dexFiles = mDexPath.list();
- if (dexFiles.length != 1) {
- throw new BuildException(String.format(
- "Expected one dex file but path value resolve to %d files.",
- dexFiles.length));
- }
- dexFile = new File(dexFiles[0]);
- }
-
- try {
- // build list of input files/folders to compute dependencies
- // add the content of the zip files.
- List<InputPath> inputPaths = new ArrayList<InputPath>();
-
- // resource file
- InputPath resourceInputPath = new InputPath(new File(mOutFolder, mResourceFile));
- inputPaths.add(resourceInputPath);
-
- // dex file
- if (dexFile != null) {
- inputPaths.add(new InputPath(dexFile));
- }
-
- // zip input files
- List<File> zipFiles = new ArrayList<File>();
- for (Path pathList : mZipList) {
- for (String path : pathList.list()) {
- File f = new File(path);
- zipFiles.add(f);
- inputPaths.add(new InputPath(f));
- }
- }
-
- // now go through the list of source folders used to add non java files.
- List<File> sourceFolderList = new ArrayList<File>();
- if (mHasCode) {
- for (Path pathList : mSourceList) {
- for (String path : pathList.list()) {
- File f = new File(path);
- sourceFolderList.add(f);
- // because this is a source folder but we only care about non
- // java files.
- inputPaths.add(new SourceFolderInputPath(f));
- }
- }
- }
-
- // now go through the list of jar folders.
- List<File> jarFileList = new ArrayList<File>();
- for (Path pathList : mJarfolderList) {
- for (String path : pathList.list()) {
- // it's ok if top level folders are missing
- File folder = new File(path);
- if (folder.isDirectory()) {
- String[] filenames = folder.list(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return PATTERN_JAR_EXT.matcher(name).matches();
- }
- });
-
- for (String filename : filenames) {
- File f = new File(folder, filename);
- jarFileList.add(f);
- inputPaths.add(new InputPath(f));
- }
- }
- }
- }
-
- // now go through the list of jar files.
- for (Path pathList : mJarfileList) {
- for (String path : pathList.list()) {
- File f = new File(path);
- jarFileList.add(f);
- inputPaths.add(new InputPath(f));
- }
- }
-
- // now the native lib folder.
- List<FileEntry> nativeFileList = new ArrayList<FileEntry>();
- for (Path pathList : mNativeList) {
- for (String path : pathList.list()) {
- // it's ok if top level folders are missing
- File folder = new File(path);
- if (folder.isDirectory()) {
- List<FileEntry> entries = ApkBuilder.getNativeFiles(folder,
- mDebugPackaging);
- // add the list to the list of native files and then create an input
- // path for each file
- nativeFileList.addAll(entries);
-
- for (FileEntry entry : entries) {
- inputPaths.add(new InputPath(entry.mFile));
- }
- }
- }
- }
-
- // Finally figure out the path to the dependency file.
- String depFile = outputFile.getAbsolutePath() + ".d";
-
- // check dependencies
- if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) {
- System.out.println(
- "No changes. No need to create apk.");
- return;
- }
-
- if (mDebugSigning) {
- System.out.println(String.format(
- "Creating %s and signing it with a debug key...", outputFile.getName()));
- } else {
- System.out.println(String.format(
- "Creating %s for release...", outputFile.getName()));
- }
-
- ApkBuilder apkBuilder = new ApkBuilder(
- outputFile,
- resourceInputPath.getFile(),
- dexFile,
- mDebugSigning ? ApkBuilder.getDebugKeystore() : null,
- mVerbose ? System.out : null);
- apkBuilder.setDebugMode(mDebugPackaging);
-
-
- // add the content of the zip files.
- for (File f : zipFiles) {
- if (mVerbose) {
- System.out.println("Zip Input: " + f.getAbsolutePath());
- }
- apkBuilder.addZipFile(f);
- }
-
- // now go through the list of file to directly add the to the list.
- for (File f : sourceFolderList) {
- if (mVerbose) {
- System.out.println("Source Folder Input: " + f.getAbsolutePath());
- }
- apkBuilder.addSourceFolder(f);
- }
-
- // now go through the list of jar files.
- for (File f : jarFileList) {
- if (mVerbose) {
- System.out.println("Jar Input: " + f.getAbsolutePath());
- }
- apkBuilder.addResourcesFromJar(f);
- }
-
- // and finally the native files
- apkBuilder.addNativeLibraries(nativeFileList);
-
- // close the archive
- apkBuilder.sealApk();
-
- // and generate the dependency file
- generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath());
- } catch (DuplicateFileException e) {
- System.err.println(String.format(
- "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
- e.getArchivePath(), e.getFile1(), e.getFile2()));
- throw new BuildException(e);
- } catch (ApkCreationException e) {
- throw new BuildException(e);
- } catch (SealedApkException e) {
- throw new BuildException(e);
- } catch (IllegalArgumentException e) {
- throw new BuildException(e);
- }
- }
-
- @Override
- protected String getExecTaskName() {
- return "apkbuilder";
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/BuildConfigTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/BuildConfigTask.java
deleted file mode 100644
index e7154ff..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/BuildConfigTask.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.sdklib.internal.build.BuildConfigGenerator;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.types.Path;
-
-import java.io.File;
-import java.io.IOException;
-
-public class BuildConfigTask extends BuildTypedTask {
-
- private String mGenFolder;
- private String mAppPackage;
-
- public void setGenFolder(Path path) {
- mGenFolder = TaskHelper.checkSinglePath("genFolder", path);
- }
-
- public void setPackage(String appPackage) {
- mAppPackage = appPackage;
- }
-
-
- @Override
- public void execute() throws BuildException {
- if (mGenFolder == null) {
- throw new BuildException("Missing attribute genFolder");
- }
- if (mAppPackage == null) {
- throw new BuildException("Missing attribute package");
- }
-
- BuildConfigGenerator generator = new BuildConfigGenerator(
- mGenFolder, mAppPackage,
- Boolean.parseBoolean(getBuildType()));
-
- // first check if the file is missing.
- File buildConfigFile = generator.getBuildConfigFile();
- boolean missingFile = !buildConfigFile.exists();
-
- if (missingFile || hasBuildTypeChanged()) {
- if (isNewBuild()) {
- System.out.println("Generating BuildConfig class.");
- } else if (missingFile) {
- System.out.println("BuildConfig class missing: Generating new BuildConfig class.");
- } else {
- System.out.println("Build type changed: Generating new BuildConfig class.");
- }
-
- try {
- generator.generate();
- } catch (IOException e) {
- throw new BuildException("Failed to create BuildConfig class", e);
- }
- } else {
- System.out.println("No need to generate new BuildConfig.");
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/BuildTypedTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/BuildTypedTask.java
deleted file mode 100644
index 1656486..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/BuildTypedTask.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import org.apache.tools.ant.Task;
-
-/**
- * Base class for tasks that should exec when the build type change.
- */
-public abstract class BuildTypedTask extends Task {
-
- private String mPreviousBuildType;
- private String mBuildType;
-
- /** Sets the current build type */
- public void setBuildType(String buildType) {
- mBuildType = buildType;
- }
-
- /** Sets the previous build type */
- public void setPreviousBuildType(String previousBuildType) {
- mPreviousBuildType = previousBuildType;
- }
-
- protected String getBuildType() {
- return mBuildType;
- }
-
- /**
- * Returns if it is a new build. If the build type is not input
- * from the XML, this always returns true.
- * A build type is defined by having an empty previousBuildType.
- */
- protected boolean isNewBuild() {
- return mBuildType == null || mPreviousBuildType.isEmpty();
- }
-
- /**
- * Returns true if the build type changed.
- */
- protected boolean hasBuildTypeChanged() {
- // no build type? return false as the feature is simply not used
- if (mBuildType == null && mPreviousBuildType == null) {
- return false;
- }
-
- return !(mBuildType != null && mBuildType.equals(mPreviousBuildType));
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/CheckEnvTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/CheckEnvTask.java
deleted file mode 100644
index 3d40af1..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/CheckEnvTask.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.SdkConstants;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.util.DeweyDecimal;
-
-import java.io.File;
-
-/**
- * Checks the Ant environment to make sure Android builds
- * can run.
- *
- * No parameters are neeed.
- *
- */
-public class CheckEnvTask extends Task {
-
- private static final String ANT_MIN_VERSION = "1.8.0";
-
- @Override
- public void execute() {
-
- Project antProject = getProject();
-
- // check the Ant version
- DeweyDecimal version = getAntVersion(antProject);
- DeweyDecimal atLeast = new DeweyDecimal(ANT_MIN_VERSION);
- if (atLeast.isGreaterThan(version)) {
- throw new BuildException(
- "The Android Ant-based build system requires Ant " +
- ANT_MIN_VERSION +
- " or later. Current version is " +
- version);
- }
-
- // get the SDK location
- File sdkDir = TaskHelper.getSdkLocation(antProject);
-
- // detect that the platform tools is there.
- File platformTools = new File(sdkDir, SdkConstants.FD_PLATFORM_TOOLS);
- if (platformTools.isDirectory() == false) {
- throw new BuildException(String.format(
- "SDK Platform Tools component is missing. " +
- "Please install it with the SDK Manager (%1$s%2$c%3$s)",
- SdkConstants.FD_TOOLS,
- File.separatorChar,
- SdkConstants.androidCmdName()));
- }
-
- // display SDK Tools revision
- DeweyDecimal toolsRevison = TaskHelper.getToolsRevision(sdkDir);
- if (toolsRevison != null) {
- System.out.println("Android SDK Tools Revision " + toolsRevison);
- System.out.println("Installed at " + sdkDir.getAbsolutePath());
- }
- }
-
- /**
- * Returns the Ant version as a {@link DeweyDecimal} object.
- *
- * This is based on the implementation of
- * org.apache.tools.ant.taskdefs.condition.AntVersion.getVersion()
- *
- * @param antProject the current ant project.
- * @return the ant version.
- */
- private DeweyDecimal getAntVersion(Project antProject) {
- char[] versionString = antProject.getProperty("ant.version").toCharArray();
- StringBuilder sb = new StringBuilder();
- boolean foundFirstDigit = false;
- for (int i = 0; i < versionString.length; i++) {
- if (Character.isDigit(versionString[i])) {
- sb.append(versionString[i]);
- foundFirstDigit = true;
- }
- if (versionString[i] == '.' && foundFirstDigit) {
- sb.append(versionString[i]);
- }
- if (Character.isLetter(versionString[i]) && foundFirstDigit) {
- break;
- }
- }
- return new DeweyDecimal(sb.toString());
- }
-
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/ComputeDependencyTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/ComputeDependencyTask.java
deleted file mode 100644
index 7b974d4..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/ComputeDependencyTask.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.SdkConstants;
-import com.android.ant.DependencyHelper.JarProcessor;
-import com.android.io.FileWrapper;
-import com.android.sdklib.build.RenderScriptProcessor;
-import com.android.sdklib.internal.project.IPropertySource;
-import com.android.xml.AndroidManifest;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.Path.PathElement;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Computes the dependency of the current project.
- *
- * Out params:
- * <code>libraryResFolderPathOut</code>: the Path object containing the res folder for all the
- * library projects in the order needed by aapt.
- *
- * <code>libraryPackagesOut</code>: a simple property containing ;-separated package name from
- * the library projects.
- *
- * <code>jarLibraryPathOut</code>: the Path object containing all the 3rd party jar files.
- *
- * <code>libraryNativeFolderPathOut</code>: the Path with all the native folder for the library
- * projects.
- *
- *
- * In params:
- * <code>targetApi</code>: the compilation target api.
- * <code>verbose</code>: whether the build is verbose.
- *
- */
-public class ComputeDependencyTask extends GetLibraryPathTask {
-
- private String mLibraryManifestFilePathOut;
- private String mLibraryResFolderPathOut;
- private String mLibraryPackagesOut;
- private String mJarLibraryPathOut;
- private String mLibraryNativeFolderPathOut;
- private String mLibraryBinAidlFolderPathOut;
- private String mLibraryRFilePathOut;
- private int mTargetApi = -1;
- private boolean mVerbose = false;
- private boolean mRenderscriptSupportMode;
- private String mBuildToolsFolder;
- private String mRenderscriptSupportLibsOut;
-
- public void setLibraryManifestFilePathOut(String libraryManifestFilePathOut) {
- mLibraryManifestFilePathOut = libraryManifestFilePathOut;
- }
-
- public void setLibraryResFolderPathOut(String libraryResFolderPathOut) {
- mLibraryResFolderPathOut = libraryResFolderPathOut;
- }
-
- public void setLibraryPackagesOut(String libraryPackagesOut) {
- mLibraryPackagesOut = libraryPackagesOut;
- }
-
- public void setJarLibraryPathOut(String jarLibraryPathOut) {
- mJarLibraryPathOut = jarLibraryPathOut;
- }
-
- public void setLibraryBinAidlFolderPathOut(String libraryBinAidlFolderPathOut) {
- mLibraryBinAidlFolderPathOut = libraryBinAidlFolderPathOut;
- }
-
- public void setLibraryRFilePathOut(String libraryRFilePathOut) {
- mLibraryRFilePathOut = libraryRFilePathOut;
- }
-
- public void setLibraryNativeFolderPathOut(String libraryNativeFolderPathOut) {
- mLibraryNativeFolderPathOut = libraryNativeFolderPathOut;
- }
-
- public void setTargetApi(int targetApi) {
- mTargetApi = targetApi;
- }
-
- public void setRenderscriptSupportMode(boolean renderscriptSupportMode) {
- mRenderscriptSupportMode = renderscriptSupportMode;
- }
-
- public void setBuildToolsFolder(String folder) {
- mBuildToolsFolder = folder;
- }
-
- public void setRenderscriptSupportLibsOut(String renderscriptSupportLibsOut) {
- mRenderscriptSupportLibsOut = renderscriptSupportLibsOut;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mLibraryManifestFilePathOut == null) {
- throw new BuildException("Missing attribute libraryManifestFilePathOut");
- }
- if (mLibraryResFolderPathOut == null) {
- throw new BuildException("Missing attribute libraryResFolderPathOut");
- }
- if (mLibraryPackagesOut == null) {
- throw new BuildException("Missing attribute libraryPackagesOut");
- }
- if (mJarLibraryPathOut == null) {
- throw new BuildException("Missing attribute jarLibraryPathOut");
- }
- if (mLibraryNativeFolderPathOut == null) {
- throw new BuildException("Missing attribute libraryNativeFolderPathOut");
- }
- if (mLibraryBinAidlFolderPathOut == null) {
- throw new BuildException("Missing attribute libraryBinFolderPathOut");
- }
- if (mLibraryRFilePathOut == null) {
- throw new BuildException("Missing attribute libraryRFilePathOut");
- }
- if (mTargetApi == -1) {
- throw new BuildException("Missing attribute targetApi");
- }
- if (mBuildToolsFolder == null) {
- throw new BuildException("Missing attribute buildToolsFolder");
- }
- if (mRenderscriptSupportLibsOut == null) {
- throw new BuildException("Missing attribute renderscriptSupportLibsOut");
- }
-
-
- final Project antProject = getProject();
-
- // get the SDK location
- File sdkDir = TaskHelper.getSdkLocation(antProject);
-
- // prepare several paths for future tasks
- final Path manifestFilePath = new Path(antProject);
- final Path resFolderPath = new Path(antProject);
- final Path nativeFolderPath = new Path(antProject);
- final Path binAidlFolderPath = new Path(antProject);
- final Path rFilePath = new Path(antProject);
- final StringBuilder packageStrBuilder = new StringBuilder();
-
- // custom jar processor doing a bit more than just collecting the jar files
- JarProcessor processor = new JarProcessor() {
- @Override
- public void processLibrary(String libRootPath, IPropertySource properties) {
- // let the super class handle the jar files
- super.processLibrary(libRootPath, properties);
-
- // get the AndroidManifest.xml path.
- // FIXME: support renamed location.
- PathElement element = manifestFilePath.createPathElement();
- element.setPath(libRootPath + '/' + SdkConstants.FN_ANDROID_MANIFEST_XML);
-
- // get the res path. $PROJECT/res as well as the crunch cache.
- // FIXME: support renamed folders.
- element = resFolderPath.createPathElement();
- element.setPath(libRootPath + '/' + SdkConstants.FD_OUTPUT +
- '/' + SdkConstants.FD_RES);
- element = resFolderPath.createPathElement();
- element.setPath(libRootPath + '/' + SdkConstants.FD_RESOURCES);
-
- // get the folder for the native libraries. Always $PROJECT/libs
- // FIXME: support renamed folder and/or move libs to bin/libs/
- element = nativeFolderPath.createPathElement();
- element.setPath(libRootPath + '/' + SdkConstants.FD_NATIVE_LIBS);
-
- // get the bin/aidl folder. $PROJECT/bin/aidl for now
- // FIXME: support renamed folder.
- element = binAidlFolderPath.createPathElement();
- element.setPath(libRootPath + '/' + SdkConstants.FD_OUTPUT +
- '/' + SdkConstants.FD_AIDL);
-
- // get the package from the manifest.
- FileWrapper manifest = new FileWrapper(libRootPath,
- SdkConstants.FN_ANDROID_MANIFEST_XML);
-
- try {
- String value = AndroidManifest.getPackage(manifest);
- if (value != null) { // aapt will complain if it's missing.
- packageStrBuilder.append(';');
- packageStrBuilder.append(value);
-
- // get the text R file. $PROJECT/bin/R.txt for now
- // This must be in sync with the package list.
- // FIXME: support renamed folder.
- element = rFilePath.createPathElement();
- element.setPath(libRootPath + "/" + SdkConstants.FD_OUTPUT +
- "/" + "R.txt");
-
- }
- } catch (Exception e) {
- throw new BuildException(e);
- }
- }
- };
-
- // list of all the jars that are on the classpath. This will receive the
- // project's libs/*.jar files, the Library Projects output and their own libs/*.jar
- List<File> jars = processor.getJars();
-
-
- // in case clean has been called before a build type target, the list of
- // libraries has already been computed so we don't need to compute it again.
- Path libraryFolderPath = (Path) antProject.getReference(getLibraryFolderPathOut());
- if (libraryFolderPath == null) {
- execute(processor);
- } else {
- // this contains the list of library folder in reverse order (compilation order).
- // We need to process it in the normal order (res order).
- System.out.println("Ordered libraries:");
-
- String[] libraries = libraryFolderPath.list();
- for (int i = libraries.length - 1 ; i >= 0 ; i--) {
- String libRootPath = libraries[i];
- System.out.println(libRootPath);
-
- processor.processLibrary(libRootPath);
- }
- }
-
- boolean hasLibraries = !jars.isEmpty();
-
- System.out.println("\n------------------");
-
- if (mTargetApi <= 15) {
- System.out.println("API<=15: Adding annotations.jar to the classpath.");
-
- jars.add(new File(sdkDir, SdkConstants.FD_TOOLS +
- '/' + SdkConstants.FD_SUPPORT +
- '/' + SdkConstants.FN_ANNOTATIONS_JAR));
-
- }
-
- Path rsSupportPath = new Path(antProject);
-
- if (mRenderscriptSupportMode) {
- File renderScriptSupportJar = RenderScriptProcessor.getSupportJar(mBuildToolsFolder);
- System.out.println(
- "Renderscript support mode: Adding " +
- renderScriptSupportJar.getName() + " to the classpath.");
- jars.add(renderScriptSupportJar);
-
- PathElement element = rsSupportPath.createPathElement();
- element.setPath(RenderScriptProcessor.getSupportNativeLibFolder(mBuildToolsFolder)
- .getAbsolutePath());
- }
- antProject.addReference(mRenderscriptSupportLibsOut, rsSupportPath);
-
- // even with no libraries, always setup these so that various tasks in Ant don't complain
- // (the task themselves can handle a ref to an empty Path)
- antProject.addReference(mLibraryNativeFolderPathOut, nativeFolderPath);
- antProject.addReference(mLibraryManifestFilePathOut, manifestFilePath);
- antProject.addReference(mLibraryBinAidlFolderPathOut, binAidlFolderPath);
-
- // the rest is done only if there's a library.
- if (hasLibraries) {
- antProject.addReference(mLibraryResFolderPathOut, resFolderPath);
- antProject.setProperty(mLibraryPackagesOut, packageStrBuilder.toString());
- antProject.addReference(mLibraryRFilePathOut, rFilePath);
- }
-
- File projectFolder = antProject.getBaseDir();
-
- // add the project's own content of libs/*.jar
- File libsFolder = new File(projectFolder, SdkConstants.FD_NATIVE_LIBS);
- File[] jarFiles = libsFolder.listFiles(processor.getFilter());
- if (jarFiles != null) {
- Collections.addAll(jars, jarFiles);
- }
-
- // now sanitize the path to remove dups
- jars = DependencyHelper.sanitizePaths(projectFolder, new IPropertySource() {
- @Override
- public String getProperty(String name) {
- return antProject.getProperty(name);
- }
-
- @Override
- public void debugPrint() {
- }
- }, jars);
-
- // and create a Path object for them
- Path jarsPath = new Path(antProject);
- if (mVerbose) {
- System.out.println("\n------------------\nSanitized jar list:");
- }
- for (File f : jars) {
- if (mVerbose) {
- System.out.println("- " + f.getAbsolutePath());
- }
- PathElement element = jarsPath.createPathElement();
- element.setPath(f.getAbsolutePath());
- }
- antProject.addReference(mJarLibraryPathOut, jarsPath);
-
- if (mVerbose) {
- System.out.println();
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/ComputeProjectClasspathTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/ComputeProjectClasspathTask.java
deleted file mode 100644
index ac793f3..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/ComputeProjectClasspathTask.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.SdkConstants;
-import com.android.ant.DependencyHelper.JarProcessor;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.Path.PathElement;
-
-import java.io.File;
-import java.util.List;
-
-public class ComputeProjectClasspathTask extends Task {
-
- private String mProjectLocation;
- private String mProjectClassPathOut;
-
- public void setProjectLocation(String projectLocation) {
- mProjectLocation = projectLocation;
- }
-
- public void setProjectClassPathOut(String projectClassPathOut) {
- mProjectClassPathOut = projectClassPathOut;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mProjectLocation == null) {
- throw new BuildException("Missing attribute projectLocation");
- }
- if (mProjectClassPathOut == null) {
- throw new BuildException("Missing attribute projectClassPathOut");
- }
-
- DependencyHelper helper = new DependencyHelper(new File(mProjectLocation),
- false /*verbose*/);
-
- JarProcessor processor = new JarProcessor();
-
- helper.processLibraries(processor);
- List<File> jars = processor.getJars();
-
- // add the project's own content of libs/*.jar
- File libsFolder = new File(mProjectLocation, SdkConstants.FD_NATIVE_LIBS);
- File[] jarFiles = libsFolder.listFiles(processor.getFilter());
- if (jarFiles != null) {
- for (File jarFile : jarFiles) {
- jars.add(jarFile);
- }
- }
-
- jars = helper.sanitizePaths(jars);
-
- Project antProject = getProject();
-
- System.out.println("Resolved classpath:");
-
- // create a path with all the jars and the project's output as well.
- Path path = new Path(antProject);
- for (File jar : jars) {
- PathElement element = path.createPathElement();
- String p = jar.getAbsolutePath();
- element.setPath(p);
- System.out.println(p);
- }
-
- File bin = new File(mProjectLocation,
- helper.getOutDir() + File.separator + "classes");
- PathElement element = path.createPathElement();
- String p = bin.getAbsolutePath();
- element.setPath(p);
- System.out.println(p);
-
- antProject.addReference(mProjectClassPathOut, path);
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/DependencyGraph.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/DependencyGraph.java
deleted file mode 100644
index 36f4b22..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/DependencyGraph.java
+++ /dev/null
@@ -1,441 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import org.apache.tools.ant.BuildException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class takes care of dependency tracking for all targets and prerequisites listed in
- * a single dependency file. A dependency graph always has a dependency file associated with it
- * for the duration of its lifetime
- */
-public class DependencyGraph {
-
- private static final boolean DEBUG = false;
-
- private enum DependencyStatus {
- NONE, NEW_FILE, UPDATED_FILE, MISSING_FILE, ERROR
- }
-
- // Files that we know about from the dependency file
- private Set<File> mTargets = Collections.emptySet();
- private Set<File> mPrereqs = mTargets;
- private File mFirstPrereq = null;
- private boolean mMissingDepFile = false;
- private long mDepFileLastModified;
- private final List<InputPath> mNewInputs;
-
- public DependencyGraph(String dependencyFilePath, List<InputPath> newInputPaths) {
- mNewInputs = newInputPaths;
- parseDependencyFile(dependencyFilePath);
- }
-
- /**
- * Check all the dependencies to see if anything has changed.
- *
- * @param printStatus will print to {@link System#out} the dependencies status.
- * @return true if new prerequisites have appeared, target files are missing or if
- * prerequisite files have been modified since the last target generation.
- */
- public boolean dependenciesHaveChanged(boolean printStatus) {
- // If no dependency file has been set up, then we'll just return true
- // if we have a dependency file, we'll check to see what's been changed
- if (mMissingDepFile) {
- System.out.println("No Dependency File Found");
- return true;
- }
-
- // check for missing output first
- if (missingTargetFile()) {
- if (printStatus) {
- System.out.println("Found Deleted Target File");
- }
- return true;
- }
-
- // get the time stamp of the oldest target.
- long oldestTarget = getOutputLastModified();
-
- // first look through the input folders and look for new files or modified files.
- DependencyStatus status = checkInputs(oldestTarget);
-
- // this can't find missing files. This is done later.
- switch (status) {
- case ERROR:
- throw new BuildException();
- case NEW_FILE:
- if (printStatus) {
- System.out.println("Found new input file");
- }
- return true;
- case UPDATED_FILE:
- if (printStatus) {
- System.out.println("Found modified input file");
- }
- return true;
- }
-
- // now do a full check on the remaining files.
- status = checkPrereqFiles(oldestTarget);
- // this can't find new input files. This is done above.
- switch (status) {
- case ERROR:
- throw new BuildException();
- case MISSING_FILE:
- if (printStatus) {
- System.out.println("Found deleted input file");
- }
- return true;
- case UPDATED_FILE:
- if (printStatus) {
- System.out.println("Found modified input file");
- }
- return true;
- }
-
- return false;
- }
-
- public Set<File> getTargets() {
- return Collections.unmodifiableSet(mTargets);
- }
-
- public File getFirstPrereq() {
- return mFirstPrereq;
- }
-
- /**
- * Parses the given dependency file and stores the file paths
- *
- * @param dependencyFilePath the dependency file
- */
- private void parseDependencyFile(String dependencyFilePath) {
- // first check if the dependency file is here.
- File depFile = new File(dependencyFilePath);
- if (!depFile.isFile()) {
- mMissingDepFile = true;
- return;
- }
-
- // get the modification time of the dep file as we may need it later
- mDepFileLastModified = depFile.lastModified();
-
- // Read in our dependency file
- List<String> content = readFile(depFile);
- if (content == null) {
- System.err.println("ERROR: Couldn't read " + dependencyFilePath);
- return;
- }
-
- // The format is something like:
- // output1 output2 [...]: dep1 dep2 [...]
- // expect it's likely split on several lines. So let's move it back on a single line
- // first
- StringBuilder sb = new StringBuilder();
- for (String line : content) {
- line = line.trim();
- if (line.endsWith("\\")) {
- line = line.substring(0, line.length() - 1);
- }
- sb.append(line);
- }
-
- // split the left and right part
- String[] files = sb.toString().split(":");
-
- // get the target files:
- String[] targets = files[0].trim().split(" ");
-
- String[] prereqs = {};
- // Check to make sure our dependency file is okay
- if (files.length < 1) {
- System.err.println(
- "Warning! Dependency file does not list any prerequisites after ':' ");
- } else {
- // and the prerequisite files:
- prereqs = files[1].trim().split(" ");
- }
-
- mTargets = new HashSet<File>(targets.length);
- for (String path : targets) {
- if (!path.isEmpty()) {
- mTargets.add(new File(path));
- }
- }
-
- mPrereqs = new HashSet<File>(prereqs.length);
- for (String path : prereqs) {
- if (!path.isEmpty()) {
- if (DEBUG) {
- System.out.println("PREREQ: " + path);
- }
- File f = new File(path);
- if (mFirstPrereq == null) {
- mFirstPrereq = f;
- }
- mPrereqs.add(f);
- }
- }
- }
-
- /**
- * Check all the input files and folders to see if there have been new
- * files added to them or if any of the existing files have been modified.
- *
- * This looks at the input paths, not at the list of known prereq. Therefore this
- * will not find missing files. It will however remove processed files from the
- * prereq file list so that we can process those in a 2nd step.
- *
- * This should be followed by a call to {@link #checkPrereqFiles(long)} which
- * will process the remaining files in the prereq list.
- *
- * If a change is found, this will return immediately with either
- * {@link DependencyStatus#NEW_FILE} or {@link DependencyStatus#UPDATED_FILE}.
- *
- * @param oldestTarget the timestamp of the oldest output file to compare against.
- *
- * @return the status of the file in the watched folders.
- *
- */
- private DependencyStatus checkInputs(long oldestTarget) {
- if (mNewInputs != null) {
- for (InputPath input : mNewInputs) {
- File file = input.getFile();
- if (file.isDirectory()) {
- DependencyStatus status = checkInputFolder(file, input, oldestTarget);
- if (status != DependencyStatus.NONE) {
- return status;
- }
- } else if (file.isFile()) {
- DependencyStatus status = checkInputFile(file, input, oldestTarget);
- if (status != DependencyStatus.NONE) {
- return status;
- }
- }
- }
- }
-
- // If we make it all the way through our directories we're good.
- return DependencyStatus.NONE;
- }
-
- /**
- * Check all the files in the tree under root and check to see if the files are
- * listed under the dependencies, or if they have been modified. Recurses into subdirs.
- *
- * @param folder the folder to search through.
- * @param inputFolder the root level inputFolder
- * @param oldestTarget the time stamp of the oldest output file to compare against.
- *
- * @return the status of the file in the folder.
- */
- private DependencyStatus checkInputFolder(File folder, InputPath inputFolder,
- long oldestTarget) {
- if (inputFolder.ignores(folder)) {
- return DependencyStatus.NONE;
- }
-
- File[] files = folder.listFiles();
- if (files == null) {
- System.err.println("ERROR " + folder.toString() + " is not a dir or can't be read");
- return DependencyStatus.ERROR;
- }
- // Loop through files in this folder
- for (File file : files) {
- // If this is a directory, recurse into it
- if (file.isDirectory()) {
- DependencyStatus status = checkInputFolder(file, inputFolder, oldestTarget);
- if (status != DependencyStatus.NONE) {
- return status;
- }
- } else if (file.isFile()) {
- DependencyStatus status = checkInputFile(file, inputFolder, oldestTarget);
- if (status != DependencyStatus.NONE) {
- return status;
- }
- }
- }
- // If we got to here then we didn't find anything interesting
- return DependencyStatus.NONE;
- }
-
- private DependencyStatus checkInputFile(File file, InputPath inputFolder,
- long oldestTarget) {
- if (inputFolder.ignores(file)) {
- return DependencyStatus.NONE;
- }
-
- // if it's a file, remove it from the list of prereqs.
- // This way if files in this folder don't trigger a build we'll have less
- // files to go through manually
- if (!mPrereqs.remove(file)) {
- // turns out this is a new file!
-
- if (DEBUG) {
- System.out.println("NEW FILE: " + file.getAbsolutePath());
- }
- return DependencyStatus.NEW_FILE;
- } else {
- // check the time stamp on this file if it's a file we care about based what the
- // input folder decides.
- if (inputFolder.checksForModification(file)) {
- if (file.lastModified() > oldestTarget) {
- if (DEBUG) {
- System.out.println("UPDATED FILE: " + file.getAbsolutePath());
- }
- return DependencyStatus.UPDATED_FILE;
- }
- }
- }
-
- return DependencyStatus.NONE;
- }
-
- /**
- * Check all the prereq files we know about to make sure they're still there, or that they
- * haven't been modified since the last build.
- *
- * @param oldestTarget the time stamp of the oldest output file to compare against.
- *
- * @return the status of the files
- */
- private DependencyStatus checkPrereqFiles(long oldestTarget) {
- // TODO: Optimize for the case of a specific file as inputPath.
- // We should have a map of filepath to inputpath to quickly search through them?
-
- // Loop through our prereq files and make sure they still exist
- for (File prereq : mPrereqs) {
- if (!prereq.exists()) {
- if (DEBUG) {
- System.out.println("MISSING FILE: " + prereq.getAbsolutePath());
- }
- return DependencyStatus.MISSING_FILE;
- }
-
- // check the time stamp on this file if it's a file we care about.
- // To know if we care about the file we have to find the matching input.
- if (mNewInputs != null) {
- String filePath = prereq.getAbsolutePath();
- for (InputPath input : mNewInputs) {
- File inputFile = input.getFile();
- // if the input path is a directory, check if the prereq file is in it,
- // otherwise check if the prereq file match exactly the input path.
- if (inputFile.isDirectory()) {
- if (filePath.startsWith(inputFile.getAbsolutePath())) {
- // ok file is inside a directory type input folder.
- // check if we need to check this type of file, and if yes, check it.
- if (input.checksForModification(prereq)) {
- if (prereq.lastModified() > oldestTarget) {
- if (DEBUG) {
- System.out.println(
- "UPDATED FILE: " + prereq.getAbsolutePath());
- }
- return DependencyStatus.UPDATED_FILE;
- }
- }
- }
- } else {
- // this is a file input path, we must check if the match is exact.
- if (prereq.equals(inputFile)) {
- if (input.checksForModification(prereq)) {
- if (prereq.lastModified() > oldestTarget) {
- if (DEBUG) {
- System.out.println(
- "UPDATED FILE: " + prereq.getAbsolutePath());
- }
- return DependencyStatus.UPDATED_FILE;
- }
- }
- }
- }
- }
- } else {
- // no input? we consider all files.
- if (prereq.lastModified() > oldestTarget) {
- if (DEBUG) {
- System.out.println("UPDATED FILE: " + prereq.getAbsolutePath());
- }
- return DependencyStatus.UPDATED_FILE;
- }
- }
- }
-
- // If we get this far, then all our prereq are okay
- return DependencyStatus.NONE;
- }
-
- /**
- * Check all the target files we know about to make sure they're still there
- * @return true if any of the target files are missing.
- */
- private boolean missingTargetFile() {
- // Loop through our target files and make sure they still exist
- for (File target : mTargets) {
- if (!target.exists()) {
- return true;
- }
- }
- // If we get this far, then all our targets are okay
- return false;
- }
-
- /**
- * Returns the earliest modification time stamp from all the output targets. If there
- * are no known target, the dependency file time stamp is returned.
- */
- private long getOutputLastModified() {
- // Find the oldest target
- long oldestTarget = Long.MAX_VALUE;
- // if there's no output, then compare to the time of the dependency file.
- if (mTargets.isEmpty()) {
- oldestTarget = mDepFileLastModified;
- } else {
- for (File target : mTargets) {
- if (target.lastModified() < oldestTarget) {
- oldestTarget = target.lastModified();
- }
- }
- }
-
- return oldestTarget;
- }
-
- /**
- * Reads and returns the content of a text file.
- * @param file the file to read
- * @return null if the file could not be read
- */
- private static List<String> readFile(File file) {
- try {
- return Files.readLines(file, Charsets.UTF_8);
- } catch (IOException e) {
- // return null below
- }
-
- return null;
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/DependencyHelper.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/DependencyHelper.java
deleted file mode 100644
index 51a9cf7..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/DependencyHelper.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.SdkConstants;
-import com.android.annotations.Nullable;
-import com.android.io.FolderWrapper;
-import com.android.sdklib.build.JarListSanitizer;
-import com.android.sdklib.build.JarListSanitizer.DifferentLibException;
-import com.android.sdklib.build.JarListSanitizer.Sha1Exception;
-import com.android.sdklib.internal.project.IPropertySource;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
-
-import org.apache.tools.ant.BuildException;
-
-import java.io.File;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Helper class to manage dependency for projects.
- *
- */
-public class DependencyHelper {
-
- private final boolean mVerbose;
- private final File mProjectFolder;
- private final IPropertySource mProperties;
- private final List<File> mLibraries = new ArrayList<File>();
-
- /**
- * A Library Processor. Used in {@link DependencyHelper#processLibraries(LibraryProcessor)}
- *
- */
- protected interface LibraryProcessor {
- void processLibrary(String libRootPath);
- }
-
- /**
- * Advanced version of the {@link LibraryProcessor} that provides the library properties
- * to the processor.
- */
- public abstract static class AdvancedLibraryProcessor implements LibraryProcessor {
-
- public abstract void processLibrary(String libRootPath, IPropertySource properties);
-
- @Override
- public final void processLibrary(String libRootPath) {
- ProjectProperties properties = TaskHelper.getProperties(libRootPath);
-
- processLibrary(libRootPath, properties);
- }
- }
-
- /**
- * Implementation of {@link AdvancedLibraryProcessor} that builds a list of sanitized list
- * of 3rd party jar files from all the Library Projects.
- */
- public static class JarProcessor extends AdvancedLibraryProcessor {
-
- private final List<File> mJars = new ArrayList<File>();
-
- private final FilenameFilter mFilter = new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.toLowerCase(Locale.US).endsWith(".jar");
- }
- };
-
- public List<File> getJars() {
- return mJars;
- }
-
- public FilenameFilter getFilter() {
- return mFilter;
- }
-
- @Override
- public void processLibrary(String libRootPath, IPropertySource properties) {
- // get the library output
- // FIXME: support renamed folder.
- mJars.add(new File(libRootPath + "/" + SdkConstants.FD_OUTPUT +
- "/" + SdkConstants.FN_CLASSES_JAR));
-
- // Get the 3rd party jar files.
- // FIXME: support renamed folder.
- File libsFolder = new File(libRootPath, SdkConstants.FD_NATIVE_LIBS);
- File[] jarFiles = libsFolder.listFiles(mFilter);
- if (jarFiles != null) {
- for (File jarFile : jarFiles) {
- mJars.add(jarFile);
- }
- }
- }
- }
-
-
- public static List<File> sanitizePaths(File projectFolder, IPropertySource properties,
- List<File> paths) {
- // first get the non-files.
- List<File> results = new ArrayList<File>();
- for (int i = 0 ; i < paths.size() ;) {
- File f = paths.get(i);
- // TEMP WORKAROUND: ignore classes.jar as all the output of libraries are
- // called the same (in Ant) but are not actually the same jar file.
- // TODO: Be aware of library output vs. regular jar dependency.
- if (f.isFile() && f.getName().equals(SdkConstants.FN_CLASSES_JAR) == false) {
- i++;
- } else {
- results.add(f);
- paths.remove(i);
- }
- }
-
-
- File outputFile = new File(projectFolder, getOutDir(properties));
- JarListSanitizer sanitizer = new JarListSanitizer(outputFile);
-
- try {
- results.addAll(sanitizer.sanitize(paths));
- } catch (DifferentLibException e) {
- String[] details = e.getDetails();
- for (String s : details) {
- System.err.println(s);
- }
- throw new BuildException(e.getMessage(), e);
- } catch (Sha1Exception e) {
- throw new BuildException(
- "Failed to compute sha1 for " + e.getJarFile().getAbsolutePath(), e);
- }
-
- return results;
- }
-
- /**
- *
- * @param projectFolder the project root folder.
- */
- public DependencyHelper(File projectFolder, boolean verbose) {
- mProjectFolder = projectFolder;
- mVerbose = verbose;
-
- mProperties = TaskHelper.getProperties(projectFolder.getAbsolutePath());
-
- init(projectFolder);
- }
-
- /**
- *
- * @param projectFolder the project root folder.
- * @param properties an {@link IPropertySource} that can provide the project properties values.
- * @param verbose whether the output is verbose
- */
- public DependencyHelper(File projectFolder, IPropertySource properties, boolean verbose) {
- mProjectFolder = projectFolder;
- mProperties = properties;
- mVerbose = verbose;
-
- init(projectFolder);
- }
-
- private void init(File projectFolder) {
- // get the top level list of library dependencies.
- List<File> topLevelLibraries = getDirectDependencies(projectFolder, mProperties);
-
- // process the libraries in case they depend on other libraries.
- resolveFullLibraryDependencies(topLevelLibraries, mLibraries);
- }
-
- public List<File> getLibraries() {
- return mLibraries;
- }
-
- public int getLibraryCount() {
- return mLibraries.size();
- }
-
- public String getProperty(String name) {
- return mProperties.getProperty(name);
- }
-
- public void processLibraries(@Nullable LibraryProcessor processor) {
- // use that same order to process the libraries.
- for (File library : mLibraries) {
- // get the root path.
- String libRootPath = library.getAbsolutePath();
- if (mVerbose) {
- System.out.println(libRootPath);
- }
-
- if (processor != null) {
- processor.processLibrary(libRootPath);
- }
- }
- }
-
- public List<File> sanitizePaths(List<File> paths) {
- return sanitizePaths(mProjectFolder, mProperties, paths);
- }
-
- public String getOutDir() {
- return getOutDir(mProperties);
- }
-
-
- /**
- * Returns the top level library dependencies of a given <var>source</var> representing a
- * project properties.
- * @param baseFolder the base folder of the project (to resolve relative paths)
- * @param properties a source of project properties.
- */
- private List<File> getDirectDependencies(File baseFolder, IPropertySource properties) {
- ArrayList<File> libraries = new ArrayList<File>();
-
- // first build the list. they are ordered highest priority first.
- int index = 1;
- while (true) {
- String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
- String rootPath = properties.getProperty(propName);
-
- if (rootPath == null) {
- break;
- }
-
- try {
- File library = new File(baseFolder, rootPath).getCanonicalFile();
-
- // check for validity
- File projectProp = new File(library, PropertyType.PROJECT.getFilename());
- if (projectProp.isFile() == false) {
- // error!
- throw new BuildException(String.format(
- "%1$s resolve to a path with no %2$s file for project %3$s", rootPath,
- PropertyType.PROJECT.getFilename(), baseFolder.getAbsolutePath()));
- }
-
- if (libraries.contains(library) == false) {
- if (mVerbose) {
- System.out.println(String.format("%1$s: %2$s => %3$s",
- baseFolder.getAbsolutePath(), rootPath, library.getAbsolutePath()));
- }
-
- libraries.add(library);
- }
- } catch (IOException e) {
- throw new BuildException("Failed to resolve library path: " + rootPath, e);
- }
- }
-
- return libraries;
- }
-
- /**
- * Resolves a given list of libraries, finds out if they depend on other libraries, and
- * returns a full list of all the direct and indirect dependencies in the proper order (first
- * is higher priority when calling aapt).
- * @param inLibraries the libraries to resolve
- * @param outLibraries where to store all the libraries.
- */
- private void resolveFullLibraryDependencies(List<File> inLibraries, List<File> outLibraries) {
- // loop in the inverse order to resolve dependencies on the libraries, so that if a library
- // is required by two higher level libraries it can be inserted in the correct place
- for (int i = inLibraries.size() - 1 ; i >= 0 ; i--) {
- File library = inLibraries.get(i);
-
- // get the default.property file for it
- final ProjectProperties projectProp = ProjectProperties.load(
- new FolderWrapper(library), PropertyType.PROJECT);
-
- // get its libraries
- List<File> dependencies = getDirectDependencies(library, projectProp);
-
- // resolve the dependencies for those libraries
- resolveFullLibraryDependencies(dependencies, outLibraries);
-
- // and add the current one (if needed) in front (higher priority)
- if (outLibraries.contains(library) == false) {
- outLibraries.add(0, library);
- }
- }
- }
-
- private static String getOutDir(IPropertySource properties) {
- String bin = properties.getProperty("out.dir");
- if (bin == null) {
- return SdkConstants.FD_OUTPUT;
- }
-
- return bin;
- }
-
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/DexExecTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/DexExecTask.java
deleted file mode 100644
index ca0087f..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/DexExecTask.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.google.common.base.Charsets;
-import com.google.common.hash.HashCode;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hashing;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.FileSet;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.resources.FileResource;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Custom task to execute dx while handling dependencies.
- */
-public class DexExecTask extends SingleDependencyTask {
-
- private String mExecutable;
- private String mOutput;
- private String mDexedLibs;
- private boolean mVerbose = false;
- private boolean mNoLocals = false;
- private boolean mForceJumbo = false;
- private boolean mDisableDexMerger = false;
- private List<Path> mPathInputs;
- private List<FileSet> mFileSetInputs;
-
-
- /**
- * Sets the value of the "executable" attribute.
- * @param executable the value.
- */
- public void setExecutable(Path executable) {
- mExecutable = TaskHelper.checkSinglePath("executable", executable);
- }
-
- /**
- * Sets the value of the "verbose" attribute.
- * @param verbose the value.
- */
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- /**
- * Sets the value of the "output" attribute.
- * @param output the value.
- */
- public void setOutput(Path output) {
- mOutput = TaskHelper.checkSinglePath("output", output);
- }
-
- public void setDexedLibs(Path dexedLibs) {
- mDexedLibs = TaskHelper.checkSinglePath("dexedLibs", dexedLibs);
- }
-
- /**
- * Sets the value of the "nolocals" attribute.
- * @param nolocals the value.
- */
- public void setNoLocals(boolean nolocals) {
- mNoLocals = nolocals;
- }
-
- public void setForceJumbo(boolean forceJumbo) {
- mForceJumbo = forceJumbo;
- }
-
- public void setDisableDexMerger(boolean disableMerger) {
- mDisableDexMerger = disableMerger;
- }
-
- /**
- * Returns an object representing a nested <var>path</var> element.
- */
- public Object createPath() {
- if (mPathInputs == null) {
- mPathInputs = new ArrayList<Path>();
- }
-
- Path path = new Path(getProject());
- mPathInputs.add(path);
-
- return path;
- }
-
- /**
- * Returns an object representing a nested <var>path</var> element.
- */
- public Object createFileSet() {
- if (mFileSetInputs == null) {
- mFileSetInputs = new ArrayList<FileSet>();
- }
-
- FileSet fs = new FileSet();
- fs.setProject(getProject());
- mFileSetInputs.add(fs);
-
- return fs;
- }
-
-
- private void preDexLibraries(List<File> inputs) {
- if (mDisableDexMerger || inputs.size() == 1) {
- // only one input, no need to put a pre-dexed version, even if this path is
- // just a jar file (case for proguard'ed builds)
- return;
- }
-
- final int count = inputs.size();
- for (int i = 0 ; i < count; i++) {
- File input = inputs.get(i);
- if (input.isFile()) {
- // check if this libs needs to be pre-dexed
- String fileName = getDexFileName(input);
- File dexedLib = new File(mDexedLibs, fileName);
- String dexedLibPath = dexedLib.getAbsolutePath();
-
- if (dexedLib.isFile() == false ||
- dexedLib.lastModified() < input.lastModified()) {
-
- System.out.println(
- String.format("Pre-Dexing %1$s -> %2$s",
- input.getAbsolutePath(), fileName));
-
- if (dexedLib.isFile()) {
- dexedLib.delete();
- }
-
- runDx(input, dexedLibPath, false /*showInput*/);
- } else {
- System.out.println(
- String.format("Using Pre-Dexed %1$s <- %2$s",
- fileName, input.getAbsolutePath()));
- }
-
- // replace the input with the pre-dex libs.
- inputs.set(i, dexedLib);
- }
- }
- }
-
- private String getDexFileName(File inputFile) {
- // get the filename
- String name = inputFile.getName();
- // remove the extension
- int pos = name.lastIndexOf('.');
- if (pos != -1) {
- name = name.substring(0, pos);
- }
-
- // add a hash of the original file path
- HashFunction hashFunction = Hashing.md5();
- HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath(), Charsets.UTF_16LE);
-
- return name + "-" + hashCode.toString() + ".jar";
- }
-
-
- @Override
- public void execute() throws BuildException {
-
- // get all input paths
- List<File> paths = new ArrayList<File>();
- if (mPathInputs != null) {
- for (Path pathList : mPathInputs) {
- for (String path : pathList.list()) {
- System.out.println("input: " + path);
- paths.add(new File(path));
- }
- }
- }
-
- if (mFileSetInputs != null) {
- for (FileSet fs : mFileSetInputs) {
- Iterator<?> iter = fs.iterator();
- while (iter.hasNext()) {
- FileResource fr = (FileResource) iter.next();
- System.out.println("input: " + fr.getFile().toString());
- paths.add(fr.getFile());
- }
- }
- }
-
- // pre dex libraries if needed
- preDexLibraries(paths);
-
- // figure out the path to the dependency file.
- String depFile = mOutput + ".d";
-
- // get InputPath with no extension restrictions
- List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/,
- null /*factory*/);
-
- if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) {
- System.out.println(
- "No new compiled code. No need to convert bytecode to dalvik format.");
- return;
- }
-
- System.out.println(String.format(
- "Converting compiled files and external libraries into %1$s...", mOutput));
-
- runDx(paths, mOutput, mVerbose /*showInputs*/);
-
- // generate the dependency file.
- generateDependencyFile(depFile, inputPaths, mOutput);
- }
-
- private void runDx(File input, String output, boolean showInputs) {
- runDx(Collections.singleton(input), output, showInputs);
- }
-
- private void runDx(Collection<File> inputs, String output, boolean showInputs) {
- ExecTask task = new ExecTask();
- task.setProject(getProject());
- task.setOwningTarget(getOwningTarget());
- task.setExecutable(mExecutable);
- task.setTaskName(getExecTaskName());
- task.setFailonerror(true);
-
- task.createArg().setValue("--dex");
-
- if (mNoLocals) {
- task.createArg().setValue("--no-locals");
- }
-
- if (mVerbose) {
- task.createArg().setValue("--verbose");
- }
-
- if (mForceJumbo) {
- task.createArg().setValue("--force-jumbo");
- }
-
- task.createArg().setValue("--output");
- task.createArg().setValue(output);
-
- for (File input : inputs) {
- String absPath = input.getAbsolutePath();
- if (showInputs) {
- System.out.println("Input: " + absPath);
- }
- task.createArg().setValue(absPath);
- }
-
- // execute it.
- task.execute();
- }
-
- @Override
- protected String getExecTaskName() {
- return "dx";
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetBuildToolsTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/GetBuildToolsTask.java
deleted file mode 100644
index 2d2ccfe..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetBuildToolsTask.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.repository.FullRevision;
-import com.android.utils.StdLogger;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-
-public class GetBuildToolsTask extends Task {
-
- private static final FullRevision MIN_BUILD_TOOLS_REV = new FullRevision(19, 1, 0);
-
- private String mName;
- private boolean mVerbose = false;
-
- public void setName(String name) {
- mName = name;
- }
-
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- @Override
- public void execute() throws BuildException {
- Project antProject = getProject();
-
- SdkManager sdkManager = SdkManager.createManager(
- antProject.getProperty(ProjectProperties.PROPERTY_SDK),
- new StdLogger(mVerbose ? StdLogger.Level.VERBOSE : StdLogger.Level.ERROR));
-
- if (sdkManager == null) {
- throw new BuildException("Unable to parse the SDK!");
- }
-
- BuildToolInfo buildToolInfo = null;
-
- String buildToolsVersion = antProject.getProperty(ProjectProperties.PROPERTY_BUILD_TOOLS);
- if (buildToolsVersion != null) {
- buildToolInfo = sdkManager.getBuildTool(FullRevision.parseRevision(buildToolsVersion));
-
- if (buildToolInfo == null) {
- throw new BuildException(
- "Could not find Build Tools revision " + buildToolsVersion);
- }
- }
-
- if (buildToolInfo == null) {
- // get the latest one instead
- buildToolInfo = sdkManager.getLatestBuildTool();
-
- if (buildToolInfo == null) {
- throw new BuildException("SDK does not have any Build Tools installed.");
- }
-
- System.out.println("Using latest Build Tools: " + buildToolInfo.getRevision());
- }
-
- if (buildToolInfo.getRevision().compareTo(MIN_BUILD_TOOLS_REV) < 0) {
- throw new BuildException(String.format(
- "The SDK Build Tools revision (%1$s) is too low for project '%2$s'. Minimum required is %3$s",
- buildToolInfo.getRevision(), getProject().getName(), MIN_BUILD_TOOLS_REV));
- }
-
- antProject.setProperty(mName, buildToolInfo.getLocation().getAbsolutePath());
- antProject.setProperty("aidl", buildToolInfo.getPath(BuildToolInfo.PathId.AIDL));
- antProject.setProperty("aapt", buildToolInfo.getPath(BuildToolInfo.PathId.AAPT));
- antProject.setProperty("zipalign", buildToolInfo.getPath(BuildToolInfo.PathId.ZIP_ALIGN));
- antProject.setProperty("dx", buildToolInfo.getPath(BuildToolInfo.PathId.DX));
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetEmmaFilterTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/GetEmmaFilterTask.java
deleted file mode 100644
index 10c6d9c..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetEmmaFilterTask.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Task;
-
-/**
- * Task building an emma filter to remove all build-only classes.
- *
- * Currently ignore:
- * app.package.R
- * app.package.R$*
- * app.package.Manifest
- * app.package.BuildConfig
- *
- */
-public class GetEmmaFilterTask extends Task {
-
- private static final String[] FILTER_CLASSES = new String[] {
- "R", "R$*", "Manifest", "BuildConfig"
- };
-
- private String mAppPackage;
- private String mLibraryPackagesRefId;
- private String mFilterOut;
-
-
- public void setAppPackage(String appPackage) {
- mAppPackage = appPackage;
- }
-
- public void setLibraryPackagesRefId(String libraryPackagesRefId) {
- mLibraryPackagesRefId = libraryPackagesRefId;
- }
-
- public void setFilterOut(String filterOut) {
- mFilterOut = filterOut;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mAppPackage == null) {
- throw new BuildException("Missing attribute appPackage");
- }
- if (mLibraryPackagesRefId == null) {
- throw new BuildException("Missing attribute libraryPackagesRefId");
- }
- if (mFilterOut == null) {
- throw new BuildException("Missing attribute filterOut");
- }
-
- StringBuilder sb = new StringBuilder();
-
- String libraryPackagesValue = getProject().getProperty(mLibraryPackagesRefId);
-
- if (libraryPackagesValue != null && !libraryPackagesValue.isEmpty()) {
- // split the app packages.
- String[] libPackages = libraryPackagesValue.split(";");
-
- for (String libPackage : libPackages) {
- if (!libPackage.isEmpty()) {
- for (String filterClass : FILTER_CLASSES) {
- sb.append(libPackage).append('.').append(filterClass).append(',');
- }
- }
- }
- }
-
- // add the app package:
- final int count = FILTER_CLASSES.length;
- for (int i = 0 ; i < count ; i++) {
- sb.append(mAppPackage).append('.').append(FILTER_CLASSES[i]);
- if (i < count - 1) {
- sb.append(',');
- }
- }
-
- getProject().setProperty(mFilterOut, sb.toString());
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetLibraryPathTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/GetLibraryPathTask.java
deleted file mode 100644
index d80e381..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetLibraryPathTask.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ant.DependencyHelper.AdvancedLibraryProcessor;
-import com.android.ant.DependencyHelper.LibraryProcessor;
-import com.android.sdklib.internal.project.IPropertySource;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.Path.PathElement;
-
-import java.io.File;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Task to get the list of Library Project paths for either the current project or any given
- * project.
- *
- */
-public class GetLibraryPathTask extends Task {
-
- private String mProjectPath;
- private String mLibraryFolderPathOut;
- private String mLeaf;
- private boolean mVerbose = false;
-
- private static class LeafProcessor extends AdvancedLibraryProcessor {
- private static final Pattern PH = Pattern.compile("^\\@\\{(.*)\\}$");
-
- private Path mPath;
- private final String[] mLeafSegments;
-
- LeafProcessor(Project antProject, String leaf) {
- mPath = new Path(antProject);
- mLeafSegments = leaf.split("/");
- }
-
- @Override
- public void processLibrary(String libRootPath, IPropertySource properties) {
- StringBuilder sb = new StringBuilder(libRootPath);
- for (String segment : mLeafSegments) {
- sb.append('/');
-
- Matcher m = PH.matcher(segment);
- if (m.matches()) {
- String value = properties.getProperty(m.group(1));
- if (value == null) {
- value = TaskHelper.getDefault(m.group(1));
- }
- if (value == null) {
- throw new BuildException(
- "Failed to resolve '" + m.group(1) + "' for project "
- + libRootPath);
- }
- sb.append(value);
- } else {
- sb.append(segment);
- }
- }
-
- PathElement element = mPath.createPathElement();
- element.setPath(sb.toString());
- }
-
- @NonNull public Path getPath() {
- return mPath;
- }
- }
-
- public void setProjectPath(String projectPath) {
- mProjectPath = projectPath;
- }
-
- public void setLibraryFolderPathOut(String libraryFolderPathOut) {
- mLibraryFolderPathOut = libraryFolderPathOut;
- }
-
- protected String getLibraryFolderPathOut() {
- return mLibraryFolderPathOut;
- }
-
- public void setLeaf(String leaf) {
- mLeaf = leaf;
- }
-
- /**
- * Sets the value of the "verbose" attribute.
- * @param verbose the value.
- */
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- protected boolean getVerbose() {
- return mVerbose;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mLibraryFolderPathOut == null) {
- throw new BuildException("Missing attribute libraryFolderPathOut");
- }
-
- LibraryProcessor processor = null;
-
- if (mLeaf != null) {
- // we need a custom processor
- processor = new LeafProcessor(getProject(), mLeaf);
- }
-
- if (mProjectPath == null) {
- execute(processor);
- } else {
- DependencyHelper helper = new DependencyHelper(new File(mProjectPath), mVerbose);
-
- execute(helper, processor);
- }
- }
-
- /**
- * Executes the processor on the current project.
- * @param processor
- * @throws BuildException
- */
- protected void execute(@Nullable LibraryProcessor processor) throws BuildException {
- final Project antProject = getProject();
-
- DependencyHelper helper = new DependencyHelper(antProject.getBaseDir(),
- new IPropertySource() {
- @Override
- public String getProperty(String name) {
- return antProject.getProperty(name);
- }
-
- @Override
- public void debugPrint() {
- }
- },
- mVerbose);
-
- execute(helper, processor);
- }
-
- /**
- * Executes the processor using a given DependencyHelper.
- * @param helper
- * @param processor
- * @throws BuildException
- */
- private void execute(@NonNull DependencyHelper helper, @Nullable LibraryProcessor processor)
- throws BuildException {
-
- final Project antProject = getProject();
-
- System.out.println("Library dependencies:");
-
- Path path = new Path(antProject);
-
- if (helper.getLibraryCount() > 0) {
- System.out.println("\n------------------\nOrdered libraries:");
-
- helper.processLibraries(processor);
-
- if (mLibraryFolderPathOut != null) {
- if (mLeaf == null) {
- // Fill a Path object with all the libraries in reverse order.
- // This is important so that compilation of libraries happens
- // in the reverse order.
- List<File> libraries = helper.getLibraries();
-
- for (int i = libraries.size() - 1 ; i >= 0; i--) {
- File library = libraries.get(i);
- PathElement element = path.createPathElement();
- element.setPath(library.getAbsolutePath());
- }
-
- } else {
- path = ((LeafProcessor) processor).getPath();
- }
- }
- } else {
- System.out.println("No Libraries");
- }
-
- antProject.addReference(mLibraryFolderPathOut, path);
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetProjectPathsTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/GetProjectPathsTask.java
deleted file mode 100644
index 42494ab..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetProjectPathsTask.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.sdklib.internal.project.ProjectProperties;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Task;
-
-import java.io.File;
-
-public class GetProjectPathsTask extends Task {
-
- private String mProjectPath;
- private String mBinName;
- private String mSrcName;
-
- public void setProjectPath(String projectPath) {
- mProjectPath = projectPath;
- }
-
- public void setBinOut(String binName) {
- mBinName = binName;
- }
-
- public void setSrcOut(String srcName) {
- mSrcName = srcName;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mProjectPath == null) {
- throw new BuildException("Missing attribute projectPath");
- }
-
- ProjectProperties props = TaskHelper.getProperties(mProjectPath);
-
- if (mBinName != null) {
- handleProp(props, "out.dir", mBinName);
- }
-
- if (mSrcName != null) {
- handleProp(props, "source.dir", mSrcName);
- }
-
- }
-
- private void handleProp(ProjectProperties props, String inName, String outName) {
- String value = props.getProperty(inName);
- if (value == null) {
- value = TaskHelper.getDefault(inName);
- }
- getProject().setProperty(outName, new File(mProjectPath, value).getAbsolutePath());
-
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetTargetTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/GetTargetTask.java
deleted file mode 100644
index 3a6df9a..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetTargetTask.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.IAndroidTarget.OptionalLibrary;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.utils.ILogger;
-import com.android.xml.AndroidManifest;
-import com.android.xml.AndroidXPathFactory;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.Path.PathElement;
-import org.xml.sax.InputSource;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpressionException;
-
-/**
- * Task to resolve the target of the current Android project.
- *
- * Out params:
- * <code>bootClassPathOut</code>: The boot class path of the project.
- *
- * <code>androidJarFileOut</code>: the android.jar used by the project.
- *
- * <code>androidAidlFileOut</code>: the framework.aidl used by the project.
- *
- * <code>targetApiOut</code>: the build API level.
- *
- * <code>minSdkVersionOut</code>: the app's minSdkVersion.
- *
- */
-public class GetTargetTask extends Task {
-
- private String mBootClassPathOut;
- private String mAndroidJarFileOut;
- private String mAndroidAidlFileOut;
- private String mTargetApiOut;
- private String mMinSdkVersionOut;
-
- public void setBootClassPathOut(String bootClassPathOut) {
- mBootClassPathOut = bootClassPathOut;
- }
-
- public void setAndroidJarFileOut(String androidJarFileOut) {
- mAndroidJarFileOut = androidJarFileOut;
- }
-
- public void setAndroidAidlFileOut(String androidAidlFileOut) {
- mAndroidAidlFileOut = androidAidlFileOut;
- }
-
- public void setTargetApiOut(String targetApiOut) {
- mTargetApiOut = targetApiOut;
- }
-
- public void setMinSdkVersionOut(String minSdkVersionOut) {
- mMinSdkVersionOut = minSdkVersionOut;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mBootClassPathOut == null) {
- throw new BuildException("Missing attribute bootClassPathOut");
- }
- if (mAndroidJarFileOut == null) {
- throw new BuildException("Missing attribute androidJarFileOut");
- }
- if (mAndroidAidlFileOut == null) {
- throw new BuildException("Missing attribute androidAidlFileOut");
- }
- if (mTargetApiOut == null) {
- throw new BuildException("Missing attribute targetApiOut");
- }
- if (mMinSdkVersionOut == null) {
- throw new BuildException("Missing attribute mMinSdkVersionOut");
- }
-
- Project antProject = getProject();
-
- // get the SDK location
- File sdkDir = TaskHelper.getSdkLocation(antProject);
-
- // get the target property value
- String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET);
-
- if (targetHashString == null) {
- throw new BuildException("Android Target is not set.");
- }
-
- // load up the sdk targets.
- final ArrayList<String> messages = new ArrayList<String>();
- SdkManager manager = SdkManager.createManager(sdkDir.getPath(), new ILogger() {
- @Override
- public void error(@Nullable Throwable t, @Nullable String errorFormat,
- Object... args) {
- if (errorFormat != null) {
- messages.add(String.format("Error: " + errorFormat, args));
- }
- if (t != null) {
- messages.add("Error: " + t.getMessage());
- }
- }
-
- @Override
- public void info(@NonNull String msgFormat, Object... args) {
- messages.add(String.format(msgFormat, args));
- }
-
- @Override
- public void verbose(@NonNull String msgFormat, Object... args) {
- info(msgFormat, args);
- }
-
- @Override
- public void warning(@NonNull String warningFormat, Object... args) {
- messages.add(String.format("Warning: " + warningFormat, args));
- }
- });
-
- if (manager == null) {
- // since we failed to parse the SDK, lets display the parsing output.
- for (String msg : messages) {
- System.out.println(msg);
- }
- throw new BuildException("Failed to parse SDK content.");
- }
-
- // resolve it
- IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString);
-
- if (androidTarget == null) {
- throw new BuildException(String.format(
- "Unable to resolve project target '%s'", targetHashString));
- }
-
- // display the project info
- System.out.println( "Project Target: " + androidTarget.getName());
- if (androidTarget.isPlatform() == false) {
- System.out.println("Vendor: " + androidTarget.getVendor());
- System.out.println("Platform Version: " + androidTarget.getVersionName());
- }
- System.out.println( "API level: " + androidTarget.getVersion().getApiString());
-
- antProject.setProperty(mTargetApiOut,
- Integer.toString(androidTarget.getVersion().getApiLevel()));
-
- // always check the manifest minSdkVersion.
- checkManifest(antProject, androidTarget.getVersion());
-
- // sets up the properties to find android.jar/framework.aidl/target tools
- String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR);
- antProject.setProperty(mAndroidJarFileOut, androidJar);
-
- String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL);
- antProject.setProperty(mAndroidAidlFileOut, androidAidl);
-
- // sets up the boot classpath
-
- // create the Path object
- Path bootclasspath = new Path(antProject);
-
- // create a PathElement for the framework jar
- PathElement element = bootclasspath.createPathElement();
- element.setPath(androidJar);
-
- // create PathElement for each optional library.
- List<OptionalLibrary> libraries = androidTarget.getAdditionalLibraries();
- HashSet<File> visitedJars = new HashSet<File>();
- for (OptionalLibrary library : libraries) {
- File jarFile = library.getJar();
- if (!visitedJars.contains(jarFile)) {
- visitedJars.add(jarFile);
-
- element = bootclasspath.createPathElement();
- element.setPath(jarFile.getAbsolutePath());
- }
- }
-
- // sets the path in the project with a reference
- antProject.addReference(mBootClassPathOut, bootclasspath);
- }
-
- /**
- * Checks the manifest <code>minSdkVersion</code> attribute.
- * @param antProject the ant project
- * @param androidVersion the version of the platform the project is compiling against.
- */
- private void checkManifest(Project antProject, AndroidVersion androidVersion) {
- try {
- File manifest = new File(antProject.getBaseDir(), SdkConstants.FN_ANDROID_MANIFEST_XML);
-
- XPath xPath = AndroidXPathFactory.newXPath();
-
- // check the package name.
- String value = xPath.evaluate(
- "/" + AndroidManifest.NODE_MANIFEST +
- "/@" + AndroidManifest.ATTRIBUTE_PACKAGE,
- new InputSource(new FileInputStream(manifest)));
- if (value != null) { // aapt will complain if it's missing.
- // only need to check that the package has 2 segments
- if (value.indexOf('.') == -1) {
- throw new BuildException(String.format(
- "Application package '%1$s' must have a minimum of 2 segments.",
- value));
- }
- }
-
- // check the minSdkVersion value
- value = xPath.evaluate(
- "/" + AndroidManifest.NODE_MANIFEST +
- "/" + AndroidManifest.NODE_USES_SDK +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" +
- AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
- new InputSource(new FileInputStream(manifest)));
-
- if (androidVersion.isPreview()) {
- // in preview mode, the content of the minSdkVersion must match exactly the
- // platform codename.
- String codeName = androidVersion.getCodename();
- if (codeName.equals(value) == false) {
- throw new BuildException(String.format(
- "For '%1$s' SDK Preview, attribute minSdkVersion in AndroidManifest.xml must be '%1$s' (current: %2$s)",
- codeName, value));
- }
-
- // set the minSdkVersion to the previous API level (which is actually the value in
- // androidVersion.)
- antProject.setProperty(mMinSdkVersionOut,
- Integer.toString(androidVersion.getApiLevel()));
-
- } else if (!value.isEmpty()) {
- // for normal platform, we'll only display warnings if the value is lower or higher
- // than the target api level.
- // First convert to an int.
- int minSdkValue = -1;
- try {
- minSdkValue = Integer.parseInt(value);
- } catch (NumberFormatException e) {
- // looks like it's not a number: error!
- throw new BuildException(String.format(
- "Attribute %1$s in AndroidManifest.xml must be an Integer!",
- AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION));
- }
-
- // set the minSdkVersion to the value
- antProject.setProperty(mMinSdkVersionOut, value);
-
- int projectApiLevel = androidVersion.getApiLevel();
- if (minSdkValue > androidVersion.getApiLevel()) {
- System.out.println(String.format(
- "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is higher than the project target API level (%3$d)",
- AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
- minSdkValue, projectApiLevel));
- }
- } else {
- // no minSdkVersion? display a warning
- System.out.println(
- "WARNING: No minSdkVersion value set. Application will install on all Android versions.");
-
- // set the target api to 1
- antProject.setProperty(mMinSdkVersionOut, "1");
- }
-
- } catch (XPathExpressionException e) {
- throw new BuildException(e);
- } catch (FileNotFoundException e) {
- throw new BuildException(e);
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetTypeTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/GetTypeTask.java
deleted file mode 100644
index e7b6f53..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetTypeTask.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.SdkConstants;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.xml.AndroidManifest;
-import com.android.xml.AndroidXPathFactory;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-import org.xml.sax.InputSource;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpressionException;
-
-/**
- * Task to query the type of the current project.
- *
- * Out params:
- *
- * <code>projectTypeOut</code>: String value containing the type of the project. Possible values
- * are 'app', 'library', 'test', 'test-app'
- *
- */
-public class GetTypeTask extends Task {
-
- private String mProjectTypeOut;
-
- public void setProjectTypeOut(String projectTypeOut) {
- mProjectTypeOut = projectTypeOut;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mProjectTypeOut == null) {
- throw new BuildException("Missing attribute projectTypeOut");
- }
-
- Project antProject = getProject();
-
- String libraryProp = antProject.getProperty(ProjectProperties.PROPERTY_LIBRARY);
- if (libraryProp != null) {
- if (Boolean.valueOf(libraryProp).booleanValue()) {
- System.out.println("Project Type: Android Library");
-
- antProject.setProperty(mProjectTypeOut, "library");
- return;
- }
- }
-
- if (antProject.getProperty(ProjectProperties.PROPERTY_TESTED_PROJECT) != null) {
- System.out.println("Project Type: Test Application");
-
- antProject.setProperty(mProjectTypeOut, "test");
- return;
- }
-
- // we also need to check if the Manifest doesn't have some instrumentation which
- // means the app is a self-contained test project.
- try {
- File manifest = new File(antProject.getBaseDir(), SdkConstants.FN_ANDROID_MANIFEST_XML);
- XPath xPath = AndroidXPathFactory.newXPath();
-
- // check the present of /manifest/instrumentation/
- String value = xPath.evaluate(
- "/" + AndroidManifest.NODE_MANIFEST +
- "/" + AndroidManifest.NODE_INSTRUMENTATION +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + AndroidManifest.ATTRIBUTE_TARGET_PACKAGE,
- new InputSource(new FileInputStream(manifest)));
-
- if (value != null && !value.isEmpty()) {
- System.out.println("Project Type: Self-Tested Application");
-
- antProject.setProperty(mProjectTypeOut, "test-app");
- return;
- }
- } catch (XPathExpressionException e) {
- throw new BuildException(e);
- } catch (FileNotFoundException e) {
- throw new BuildException(e);
- }
-
- // default case
- System.out.println("Project Type: Application");
-
- antProject.setProperty(mProjectTypeOut, "app");
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetUiTargetTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/GetUiTargetTask.java
deleted file mode 100644
index 762c827..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/GetUiTargetTask.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.IAndroidTarget.OptionalLibrary;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.utils.ILogger;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.Path.PathElement;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * Task to resolve the target of the current Android uiautomator project.
- *
- * Out params:
- * <code>compileClassPathOut</code>: The compile class path for the project.
- */
-public class GetUiTargetTask extends Task {
-
- private String mCompileClassPathOut;
-
- public void setCompileClassPathOut(String compileClassPathOut) {
- mCompileClassPathOut = compileClassPathOut;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mCompileClassPathOut == null) {
- throw new BuildException("Missing attribute compileClassPathOut");
- }
-
- Project antProject = getProject();
-
- // get the SDK location
- File sdkDir = TaskHelper.getSdkLocation(antProject);
-
- // get the target property value
- String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET);
-
- if (targetHashString == null) {
- throw new BuildException("Android Target is not set.");
- }
-
- // load up the sdk targets.
- final ArrayList<String> messages = new ArrayList<String>();
- SdkManager manager = SdkManager.createManager(sdkDir.getPath(), new ILogger() {
- @Override
- public void error(@Nullable Throwable t, @Nullable String errorFormat,
- Object... args) {
- if (errorFormat != null) {
- messages.add(String.format("Error: " + errorFormat, args));
- }
- if (t != null) {
- messages.add("Error: " + t.getMessage());
- }
- }
-
- @Override
- public void info(@NonNull String msgFormat, Object... args) {
- messages.add(String.format(msgFormat, args));
- }
-
- @Override
- public void verbose(@NonNull String msgFormat, Object... args) {
- info(msgFormat, args);
- }
-
- @Override
- public void warning(@NonNull String warningFormat, Object... args) {
- messages.add(String.format("Warning: " + warningFormat, args));
- }
- });
-
- if (manager == null) {
- // since we failed to parse the SDK, lets display the parsing output.
- for (String msg : messages) {
- System.out.println(msg);
- }
- throw new BuildException("Failed to parse SDK content.");
- }
-
- // resolve it
- IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString);
-
- if (androidTarget == null) {
- throw new BuildException(String.format(
- "Unable to resolve project target '%s'", targetHashString));
- }
-
- // display the project info
- System.out.println( "Project Target: " + androidTarget.getName());
- if (!androidTarget.isPlatform()) {
- System.out.println("Vendor: " + androidTarget.getVendor());
- System.out.println("Platform Version: " + androidTarget.getVersionName());
- }
- System.out.println( "API level: " + androidTarget.getVersion().getApiString());
-
- if (androidTarget.getVersion().getApiLevel() < 16) {
- throw new BuildException("UI Automator requires API 16");
- }
-
- // sets up the properties to find android.jar/framework.aidl/target tools
- String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR);
- String uiAutomatorJar = androidTarget.getPath(IAndroidTarget.UI_AUTOMATOR_JAR);
-
- // sets up the boot classpath
-
- // create the Path object
- Path compileclasspath = new Path(antProject);
-
- // create a PathElement for the framework jars
- PathElement element = compileclasspath.createPathElement();
- element.setPath(androidJar);
-
- element = compileclasspath.createPathElement();
- element.setPath(uiAutomatorJar);
-
- // create PathElement for each optional library.
- List<OptionalLibrary> libraries = androidTarget.getAdditionalLibraries();
- HashSet<File> visitedJars = new HashSet<File>();
- for (OptionalLibrary library : libraries) {
- File jarPath = library.getJar();
- if (!visitedJars.contains(jarPath)) {
- visitedJars.add(jarPath);
-
- element = compileclasspath.createPathElement();
- element.setPath(jarPath.getAbsolutePath());
- }
- }
-
- // sets the path in the project with a reference
- antProject.addReference(mCompileClassPathOut, compileclasspath);
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/IfElseTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/IfElseTask.java
deleted file mode 100644
index f34e486..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/IfElseTask.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2010 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.ant;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.taskdefs.Sequential;
-import org.apache.tools.ant.taskdefs.condition.And;
-
-/**
- * If (condition) then: {@link Sequential} else: {@link Sequential}.
- *
- * In XML:
- * <if condition="${prop with a boolean value}">
- * <then>
- * </then>
- * <else>
- * </else>
- * </if>
- *
- * or
- *
- * <if>
- * <condition>
- * ...
- * </condition>
- * <then>
- * </then>
- * <else>
- * </else>
- * </if>
- *
- * both <then> and <else> behave like <sequential>.
- * <condition> behaves like an <and> condition.
- *
- * The presence of both <then> and <else> is not required, but one of them must be present.
- * <if condition="${some.condition}">
- * <else>
- * </else>
- * </if>
- * is perfectly valid.
- *
- */
-public class IfElseTask extends Task {
-
- private boolean mCondition;
- private boolean mConditionIsSet = false;
- private And mAnd;
- private Sequential mThen;
- private Sequential mElse;
-
- /**
- * Sets the condition value
- */
- public void setCondition(boolean condition) {
- mCondition = condition;
- mConditionIsSet = true;
- }
-
- /**
- * Creates and returns the <condition> node which is basically a <and>.
- */
- public Object createCondition() {
- if (mConditionIsSet) {
- throw new BuildException("Cannot use both condition attribute and <condition> element");
- }
-
- mAnd = new And();
- mAnd.setProject(getProject());
- return mAnd;
- }
-
- /**
- * Creates and returns the <then> {@link Sequential}
- */
- public Object createThen() {
- mThen = new Sequential();
- return mThen;
- }
-
- /**
- * Creates and returns the <else> {@link Sequential}
- */
- public Object createElse() {
- mElse = new Sequential();
- return mElse;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mConditionIsSet == false && mAnd == null) {
- throw new BuildException("condition attribute or element must be set.");
- }
-
- if (mAnd != null) {
- mCondition = mAnd.eval();
- }
-
- // need at least one.
- if (mThen == null && mElse == null) {
- throw new BuildException("Need at least <then> or <else>");
- }
-
- if (mCondition) {
- if (mThen != null) {
- mThen.execute();
- }
- } else {
- if (mElse != null) {
- mElse.execute();
- }
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/InputPath.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/InputPath.java
deleted file mode 100644
index 2299f07..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/InputPath.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import java.io.File;
-import java.util.Set;
-
-public class InputPath {
-
- private final File mFile;
- /**
- * A set of extensions. Only files with an extension in this set will
- * be considered for a modification check. All deleted/created files will still be
- * checked.
- */
- private final Set<String> mTouchedExtensions;
-
- public InputPath(File file) {
- this(file, null);
- }
-
- public InputPath(File file, Set<String> extensionsToCheck) {
- if (file == null) {
- throw new RuntimeException("File in InputPath(File) can't be null");
- }
- mFile = file;
- mTouchedExtensions = extensionsToCheck;
- }
-
- public File getFile() {
- return mFile;
- }
-
- /**
- * Returns whether this input path (likely actually a folder) must check this files for
- * modification (all files are checked for add/delete).
- *
- * This is configured by constructing the {@link InputPath} with additional restriction
- * parameters such as specific extensions.
- * @param file the file to check
- * @return true if the file must be checked for modification.
- */
- public boolean checksForModification(File file) {
- if (ignores(file)) {
- return false;
- }
-
- if (mTouchedExtensions != null &&
- mTouchedExtensions.contains(getExtension(file)) == false) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns whether the InputPath ignores a given file or folder. If it is ignored then
- * the file (or folder) is not checked for any event (modification/add/delete).
- * If it's a folder, then it and its content are completely ignored.
- * @param file the file or folder to check
- * @return true if the file or folder are ignored.
- */
- public boolean ignores(File file) {
- // always ignore hidden files/folders.
- return file.getName().startsWith(".");
- }
-
- /**
- * Gets the extension (if present) on a file by looking at the filename
- * @param file the file to get the extension from
- * @return the extension if present, or the empty string if the filename doesn't have
- * and extension.
- */
- protected static String getExtension(File file) {
- return getExtension(file.getName());
- }
-
- /**
- * Gets the extension (if present) on a file by looking at the filename
- * @param fileName the filename to get the extension from
- * @return the extension if present, or the empty string if the filename doesn't have
- * and extension.
- */
- protected static String getExtension(String fileName) {
- int index = fileName.lastIndexOf('.');
- if (index == -1) {
- return "";
- }
- // Don't include the leading '.' in the extension
- return fileName.substring(index + 1);
- }
-
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/LintExecTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/LintExecTask.java
deleted file mode 100644
index 3d687cb..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/LintExecTask.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.Path;
-
-/**
- * Custom task to execute lint
- */
-public class LintExecTask extends ExecTask {
-
- private String mExecutable;
- private String mHtml;
- private String mXml;
- private Path mSourcePath;
- private Path mClassPath;
-
- /**
- * Sets the value of the "executable" attribute.
- * @param executable the value.
- */
- public void setExecutable(Path executable) {
- mExecutable = TaskHelper.checkSinglePath("executable", executable);
- }
-
- /** Sets the path where Java source code should be found */
- public void setSrc(Path path) {
- mSourcePath = path;
- }
-
- /** Sets the path where class files should be found */
- public void setClasspath(Path path) {
- mClassPath = path;
- }
-
- /**
- * Sets the value of the "html" attribute: a path to a file or directory name
- * where the HTML report should be written.
- *
- * @param html path to the html report
- */
- public void setHtml(Path html) {
- mHtml = TaskHelper.checkSinglePath("html", html);
- }
-
- /**
- * Sets the value of the "xml" attribute: a path to a file or directory name
- * where the XML report should be written.
- *
- * @param xml path to the xml report
- */
- public void setXml(Path xml) {
- mXml = TaskHelper.checkSinglePath("xml", xml);
- }
-
- @Override
- public void execute() throws BuildException {
-
- ExecTask task = new ExecTask();
- task.setProject(getProject());
- task.setOwningTarget(getOwningTarget());
- task.setExecutable(mExecutable);
- task.setTaskName("lint");
- task.setFailonerror(true);
-
- task.createArg().setValue("--text");
- task.createArg().setValue("stdout");
-
- if (mHtml != null) {
- task.createArg().setValue("--html");
- task.createArg().setValue(mHtml);
- }
-
- if (mXml != null) {
- task.createArg().setValue("--xml");
- task.createArg().setValue(mXml);
- }
-
- if (mSourcePath != null) {
- task.createArg().setValue("--sources");
- task.createArg().setValue(mSourcePath.toString());
- }
-
- if (mClassPath != null) {
- task.createArg().setValue("--classpath");
- task.createArg().setValue(mClassPath.toString());
- }
-
- task.createArg().setValue(getProject().getBaseDir().getAbsolutePath());
- task.execute();
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/ManifestMergerTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/ManifestMergerTask.java
deleted file mode 100644
index 80cc459..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/ManifestMergerTask.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.annotations.NonNull;
-import com.android.manifmerger.ICallback;
-import com.android.manifmerger.ManifestMerger;
-import com.android.manifmerger.MergerLog;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.io.FileOp;
-import com.android.utils.StdLogger;
-import com.google.common.collect.Lists;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.types.Path;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class ManifestMergerTask extends SingleDependencyTask {
-
- private String mAppManifest;
- private String mOutManifest;
-
- private ArrayList<Path> mLibraryPaths;
- private boolean mEnabled = false;
-
- public void setAppManifest(Path appManifest) {
- mAppManifest = TaskHelper.checkSinglePath("appManifest", appManifest);
- }
-
- public void setOutManifest(Path outManifest) {
- mOutManifest = TaskHelper.checkSinglePath("outManifest", outManifest);
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- /**
- * Returns an object representing a nested <var>library</var> element.
- */
- public Object createLibrary() {
- if (mLibraryPaths == null) {
- mLibraryPaths = new ArrayList<Path>();
- }
-
- Path path = new Path(getProject());
- mLibraryPaths.add(path);
-
- return path;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mAppManifest == null) {
- throw new BuildException("Missing attribute appManifest");
- }
- if (mOutManifest == null) {
- throw new BuildException("Missing attribute outManifest");
- }
-
- // if we merge, then get the rest of the input paths.
- List<File> libraries = Lists.newArrayList();
- if (mLibraryPaths != null) {
- for (Path pathList : mLibraryPaths) {
- for (String path : pathList.list()) {
- libraries.add(new File(path));
- }
- }
- }
-
- // prepare input files
- ArrayList<File> allInputs = Lists.newArrayListWithCapacity(libraries.size() + 1);
-
- // always: the input manifest.
- File appManifestFile = new File(mAppManifest);
- allInputs.add(appManifestFile);
-
- // if enabled: add the libraries
- if (mEnabled) {
- allInputs.addAll(libraries);
- }
-
- // figure out the path to the dependency file.
- String depFile = mOutManifest + ".d";
-
- // get InputPath with no extension restrictions
- List<InputPath> inputPaths = getInputPaths(allInputs, null /*extensionsToCheck*/,
- null /*factory*/);
-
- if (initDependencies(depFile, inputPaths) && !dependenciesHaveChanged()) {
- System.out.println(
- "No changes in the AndroidManifest files.");
- return;
- }
-
- System.out.println("Merging AndroidManifest files into one.");
-
- if (!mEnabled || libraries.isEmpty()) {
- if (!mEnabled) {
- System.out.println("Manifest merger disabled. Using project manifest only.");
- } else {
- System.out.println("No libraries. Using project manifest only.");
- }
- // no merge (disabled or nothing to merge)? do a simple copy.
- try {
- new FileOp().copyFile(appManifestFile, new File(mOutManifest));
- } catch (IOException e) {
- throw new BuildException(e);
- }
- } else {
- System.out.println(String.format("Merging manifests from project and %d libraries.",
- libraries.size()));
- ManifestMerger merger = new ManifestMerger(
- MergerLog.wrapSdkLog(new StdLogger(StdLogger.Level.VERBOSE)),
- new ICallback() {
- SdkManager mManager;
- @Override
- public int queryCodenameApiLevel(@NonNull String codename) {
- if (mManager == null) {
- File sdkDir = TaskHelper.getSdkLocation(getProject());
- mManager = SdkManager.createManager(sdkDir.getPath(),
- new StdLogger(StdLogger.Level.VERBOSE));
- }
- if (mManager != null) {
- try {
- AndroidVersion version = new AndroidVersion(codename);
- IAndroidTarget t = mManager.getTargetFromHashString(
- AndroidTargetHash.getPlatformHashString(version));
- if (t != null) {
- return t.getVersion().getApiLevel();
- }
- } catch (AndroidVersionException ignored) {
- // Not a valid API or codename.
- }
- }
- return ICallback.UNKNOWN_CODENAME;
- }
- });
- if (!merger.process(
- new File(mOutManifest),
- appManifestFile,
- libraries.toArray(new File[libraries.size()]),
- null /*injectAttributes*/,
- null /*packageOverride*/)) {
- throw new BuildException();
- }
- }
-
- // generate the dependency file.
- generateDependencyFile(depFile, inputPaths, mOutManifest);
- }
-
- @Override
- protected String getExecTaskName() {
- return "ManifestMerger";
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/MultiFilesTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/MultiFilesTask.java
deleted file mode 100644
index eaba43f..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/MultiFilesTask.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.annotations.NonNull;
-
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.types.FileSet;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.PatternSet.NameEntry;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-class MultiFilesTask extends BuildTypedTask {
-
- enum DisplayType {
- FOUND, COMPILING, REMOVE_OUTPUT, REMOVE_DEP
- }
-
- interface SourceProcessor {
- @NonNull Set<String> getSourceFileExtensions();
- void process(@NonNull String filePath, @NonNull String sourceFolder,
- @NonNull List<String> sourceFolders, @NonNull Project taskProject);
- void displayMessage(@NonNull DisplayType type, int count);
- void removedOutput(@NonNull File file);
- }
-
- protected void processFiles(SourceProcessor processor, List<Path> paths, String genFolder) {
-
- Project taskProject = getProject();
-
- Set<String> extensions = processor.getSourceFileExtensions();
-
- // build a list of all the source folders
- ArrayList<String> sourceFolders = new ArrayList<String>();
- for (Path p : paths) {
- String[] values = p.list();
- if (values != null) {
- sourceFolders.addAll(Arrays.asList(values));
- }
- }
-
- ArrayList<String> includePatterns = new ArrayList<String>(extensions.size());
- for (String extension : extensions) {
- includePatterns.add("**/*." + extension);
- }
-
- // gather all the source files from all the source folders.
- Map<String, String> sourceFiles = getFilesByNameEntryFilter(sourceFolders,
- includePatterns.toArray(new String[includePatterns.size()]));
- if (!sourceFiles.isEmpty()) {
- processor.displayMessage(DisplayType.FOUND, sourceFiles.size());
- }
-
- // go look for all dependency files in the gen folder. This will have all dependency
- // files but we can filter them based on the first pre-req file.
- Iterator<?> depFiles = getFilesByNameEntryFilter(genFolder, "**/*.d");
-
- // parse all the dep files and keep the ones that are of the proper type and check if
- // they require compilation again.
- Map<String, String> toCompile = new HashMap<String, String>();
- ArrayList<File> toRemove = new ArrayList<File>();
- ArrayList<String> depsToRemove = new ArrayList<String>();
- while (depFiles.hasNext()) {
- String depFile = depFiles.next().toString();
- DependencyGraph graph = new DependencyGraph(depFile, null /*watchPaths*/);
-
- // get the source file. it's the first item in the pre-reqs
- File sourceFile = graph.getFirstPrereq();
- String sourceFilePath = sourceFile.getAbsolutePath();
-
- // The gen folder may contain other dependency files not generated by this particular
- // processor.
- // We only care if the first pre-rep is of the right extension.
- String fileExtension = sourceFilePath.substring(sourceFilePath.lastIndexOf('.') + 1);
- if (extensions.contains(fileExtension.toLowerCase(Locale.US))) {
- // remove from the list of sourceFiles to mark as "processed" (but not compiled
- // yet, that'll be done by adding it to toCompile)
- String sourceFolder = sourceFiles.get(sourceFilePath);
- if (sourceFolder == null) {
- // looks like the source file does not exist anymore!
- // we'll have to remove the output!
- Set<File> outputFiles = graph.getTargets();
- toRemove.addAll(outputFiles);
-
- // also need to remove the dep file.
- depsToRemove.add(depFile);
- } else {
- // Source file is present. remove it from the list as being processed.
- sourceFiles.remove(sourceFilePath);
-
- // check if it needs to be recompiled.
- if (hasBuildTypeChanged() ||
- graph.dependenciesHaveChanged(false /*printStatus*/)) {
- toCompile.put(sourceFilePath, sourceFolder);
- }
- }
- }
- }
-
- // add to the list of files to compile, whatever is left in sourceFiles. Those are
- // new files that have never been compiled.
- toCompile.putAll(sourceFiles);
-
- processor.displayMessage(DisplayType.COMPILING, toCompile.size());
- if (!toCompile.isEmpty()) {
- for (Entry<String, String> toCompilePath : toCompile.entrySet()) {
- processor.process(toCompilePath.getKey(), toCompilePath.getValue(),
- sourceFolders, taskProject);
- }
- }
-
- if (!toRemove.isEmpty()) {
- processor.displayMessage(DisplayType.REMOVE_OUTPUT, toRemove.size());
-
- for (File toRemoveFile : toRemove) {
- processor.removedOutput(toRemoveFile);
- if (!toRemoveFile.delete()) {
- System.err.println("Failed to remove " + toRemoveFile.getAbsolutePath());
- }
- }
- }
-
- // remove the dependency files that are obsolete
- if (!depsToRemove.isEmpty()) {
- processor.displayMessage(DisplayType.REMOVE_DEP, toRemove.size());
-
- for (String path : depsToRemove) {
- if (!new File(path).delete()) {
- System.err.println("Failed to remove " + path);
- }
- }
- }
- }
-
- /**
- * Returns a list of files found in given folders, all matching a given filter.
- * The result is a map of (file, folder).
- * @param folders the folders to search
- * @param filters the filters for the files. Typically a glob.
- * @return a map of (file, folder)
- */
- private Map<String, String> getFilesByNameEntryFilter(List<String> folders, String[] filters) {
- Map<String, String> sourceFiles = new HashMap<String, String>();
-
- for (String folder : folders) {
- Iterator<?> iterator = getFilesByNameEntryFilter(folder, filters);
-
- while (iterator.hasNext()) {
- sourceFiles.put(iterator.next().toString(), folder);
- }
- }
-
- return sourceFiles;
- }
-
- /**
- * Returns a list of files found in a given folder, matching a given filter.
- * @param folder the folder to search
- * @param filters the filter for the files. Typically a glob.
- * @return an iterator.
- */
- private Iterator<?> getFilesByNameEntryFilter(String folder, String... filters) {
- Project taskProject = getProject();
-
- // create a fileset to find all the files in the folder
- FileSet fs = new FileSet();
- fs.setProject(taskProject);
- fs.setDir(new File(folder));
- for (String filter : filters) {
- NameEntry include = fs.createInclude();
- include.setName(filter);
- }
-
- // loop through the results of the file set
- return fs.iterator();
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/PropertyByReplaceTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/PropertyByReplaceTask.java
deleted file mode 100644
index a0707de..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/PropertyByReplaceTask.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.taskdefs.Property;
-
-/**
- * Task to do simple char to char replacement on strings.
- */
-public class PropertyByReplaceTask extends Property {
-
- private String mInput;
- private char mInputChar;
- private char mWithChar;
-
- public void setInput(String input) {
- mInput = input;
- }
-
- public void setReplace(char inputChar) {
- mInputChar = inputChar;
- }
-
- public void setWith(char withChar) {
- mWithChar = withChar;
- }
-
- @Override
- public void execute() throws BuildException {
- setValue(mInput.replace(mInputChar, mWithChar));
- super.execute();
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/RenderScriptTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/RenderScriptTask.java
deleted file mode 100644
index 60b2e50..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/RenderScriptTask.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2010, 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.build.ManualRenderScriptChecker;
-import com.android.sdklib.build.RenderScriptProcessor;
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.Environment;
-import org.apache.tools.ant.types.Path;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Task to execute renderscript.
- * <p>
- * It expects 7 attributes:<br>
- * 'buildToolsRoot' ({@link Path} with a single path) for the location of the build tools<br>
- * 'framework' ({@link Path} with 1 or more paths) for the include paths.<br>
- * 'genFolder' ({@link Path} with a single path) for the location of the gen folder.<br>
- * 'resFolder' ({@link Path} with a single path) for the location of the res folder.<br>
- * 'targetApi' for the -target-api value.<br>
- * 'optLevel' for the -O optimization level.<br>
- * 'debug' for -g renderscript debugging.<br>
- * <p>
- * It also expects one or more inner elements called "source" which are identical to {@link Path}
- * elements for where to find .rs files.
- */
-public class RenderScriptTask extends BuildTypedTask {
-
- private static final Set<String> EXTENSIONS = Sets.newHashSetWithExpectedSize(2);
- static {
- EXTENSIONS.add(SdkConstants.EXT_RS);
- EXTENSIONS.add(SdkConstants.EXT_FS);
- }
-
-
- private String mBuildToolsRoot;
- private String mGenFolder;
- private String mResFolder;
- private String mRsObjFolder;
- private String mLibsFolder;
- private String mBinFolder;
- private final List<Path> mPaths = new ArrayList<Path>();
- private int mTargetApi = 0;
- private boolean mSupportMode = false;
-
- public enum OptLevel { O0, O1, O2, O3 }
-
- private OptLevel mOptLevel;
- private boolean mDebug = false;
-
- /**
- * Sets the value of the "buildToolsRoot" attribute.
- * @param buildToolsRoot the value.
- */
- public void setBuildToolsRoot(Path buildToolsRoot) {
- mBuildToolsRoot = TaskHelper.checkSinglePath("buildToolsRoot", buildToolsRoot);
- }
-
- public void setGenFolder(Path value) {
- mGenFolder = TaskHelper.checkSinglePath("genFolder", value);
- }
-
- public void setResFolder(Path value) {
- mResFolder = TaskHelper.checkSinglePath("resFolder", value);
- }
-
- public void setRsObjFolder(Path value) {
- mRsObjFolder = TaskHelper.checkSinglePath("rsObjFolder", value);
- }
-
- public void setLibsFolder(Path value) {
- mLibsFolder = TaskHelper.checkSinglePath("libsFolder", value);
- }
-
- public void setTargetApi(String targetApi) {
- try {
- mTargetApi = Integer.parseInt(targetApi);
- if (mTargetApi <= 0) {
- throw new BuildException("targetApi attribute value must be >= 1");
- }
- } catch (NumberFormatException e) {
- throw new BuildException("targetApi attribute value must be an integer", e);
- }
- }
-
- public void setOptLevel(OptLevel optLevel) {
- mOptLevel = optLevel;
- }
-
- public void setSupportMode(boolean supportMode) {
- mSupportMode = supportMode;
- }
-
- public void setBinFolder(Path binFolder) {
- mBinFolder = TaskHelper.checkSinglePath("binFolder", binFolder);
- }
-
- /** Sets the current build type. value is a boolean, true for debug build, false for release */
- @Override
- public void setBuildType(String buildType) {
- super.setBuildType(buildType);
- mDebug = Boolean.valueOf(buildType);
- }
-
- public Path createSource() {
- Path p = new Path(getProject());
- mPaths.add(p);
- return p;
- }
-
- @Override
- public void execute() throws BuildException {
- if (mBuildToolsRoot == null) {
- throw new BuildException("RenderScriptTask's 'buildToolsRoot' is required.");
- }
- if (mGenFolder == null) {
- throw new BuildException("RenderScriptTask's 'genFolder' is required.");
- }
- if (mResFolder == null) {
- throw new BuildException("RenderScriptTask's 'resFolder' is required.");
- }
- if (mRsObjFolder == null) {
- throw new BuildException("RenderScriptTask's 'rsObjFolder' is required.");
- }
- if (mLibsFolder == null) {
- throw new BuildException("RenderScriptTask's 'libsFolder' is required.");
- }
- if (mTargetApi == 0) {
- throw new BuildException("RenderScriptTask's 'targetApi' is required.");
- }
- if (mBinFolder == null) {
- throw new BuildException("RenderScriptTask's 'binFolder' is required.");
- }
-
- // convert the Path to List<File>
- List<File> sourceFolders = Lists.newArrayList();
- for (Path path : mPaths) {
- String[] values = path.list();
- if (values != null) {
- for (String p : values) {
- sourceFolders.add(new File(p));
- }
- }
- }
-
- try {
- File binFile = new File(mBinFolder);
-
- ManualRenderScriptChecker checker = new ManualRenderScriptChecker(
- sourceFolders, binFile);
-
- if (checker.mustCompile() || isNewBuild() || hasBuildTypeChanged()) {
-
- checker.cleanDependencies();
-
- List<File> emptyFileList = Collections.emptyList();
-
- RenderScriptProcessor processor = new RenderScriptProcessor(
- checker.getInputFiles(),
- emptyFileList,
- binFile,
- new File(mGenFolder),
- new File(mResFolder),
- new File(mRsObjFolder),
- new File(mLibsFolder),
- new BuildToolInfo(new FullRevision(0), new File(mBuildToolsRoot)),
- mTargetApi,
- mDebug,
- mOptLevel.ordinal(),
- mSupportMode);
-
- // clean old files first
- processor.cleanOldOutput(checker.getOldOutputs());
-
- // do the compilation(s).
- processor.build(new RenderScriptProcessor.CommandLineLauncher() {
- @Override
- public void launch(
- @NonNull File executable,
- @NonNull List<String> arguments,
- @NonNull Map<String, String> envVariableMap)
- throws IOException, InterruptedException {
-
- ExecTask task = new ExecTask();
- task.setTaskName(executable.getName());
- task.setProject(getProject());
- task.setOwningTarget(getOwningTarget());
- task.setExecutable(executable.getAbsolutePath());
- task.setFailonerror(true);
-
- // create the env var for the dynamic libraries
- for (Map.Entry<String, String> entry : envVariableMap.entrySet()) {
- Environment.Variable var = new Environment.Variable();
- var.setKey(entry.getKey());
- var.setValue(entry.getValue());
- task.addEnv(var);
- }
-
- for (String arg : arguments) {
- task.createArg().setValue(arg);
- }
-
- System.out.println(String.format(
- "COMMAND: %s %s",
- executable.getAbsolutePath(), Joiner.on(' ').join(arguments)));
-
- task.execute();
- }
- });
- }
- } catch (IOException e) {
- throw new BuildException(e);
- } catch (InterruptedException e) {
- throw new BuildException(e);
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/SignApkTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/SignApkTask.java
deleted file mode 100644
index 60cdff0..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/SignApkTask.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import com.android.sdklib.internal.build.SignedJarBuilder;
-import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.types.Path;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.KeyStore.PrivateKeyEntry;
-import java.security.cert.X509Certificate;
-
-/**
- * Simple Task to sign an apk.
- *
- */
-public class SignApkTask extends SingleInputOutputTask {
-
- private String mKeystore;
- private String mStorepass;
- private String mAlias;
- private String mKeypass;
-
- public void setKeystore(Path keystore) {
- mKeystore = TaskHelper.checkSinglePath("keystore", keystore);
- }
-
- public void setStorepass(String storepass) {
- mStorepass = storepass;
- }
-
- public void setAlias(String alias) {
- mAlias = alias;
- }
-
- public void setKeypass(String keypass) {
- mKeypass = keypass;
- }
-
- @Override
- protected void createOutput() throws BuildException {
- PrivateKeyEntry key = loadKeyEntry(
- mKeystore, null, mStorepass.toCharArray(),
- mAlias, mKeypass.toCharArray());
-
- if (key == null) {
- throw new BuildException(String.format("Signing key %s not found", mAlias));
- }
-
- SignedJarBuilder mBuilder = null;
- try {
- mBuilder = new SignedJarBuilder(
- new FileOutputStream(getOutput(), false /* append */),
- key.getPrivateKey(), (X509Certificate) key.getCertificate());
-
- mBuilder.writeZip(new FileInputStream(getInput()), new NullZipFilter());
-
- mBuilder.close();
- } catch (FileNotFoundException e) {
- throw new BuildException(String.format("Keystore '%s' is not found!", mKeystore));
- } catch (Exception e) {
- throw new BuildException(e.getMessage());
- } finally {
- if (mBuilder != null) {
- mBuilder.cleanUp();
- }
- }
- }
-
- /**
- * Loads the debug key from the keystore.
- * @param osKeyStorePath the OS path to the keystore.
- * @param storeType an optional keystore type, or <code>null</code> if the default is to
- * be used.
- * @return <code>true</code> if success, <code>false</code> if the keystore does not exist.
- */
- private PrivateKeyEntry loadKeyEntry(String osKeyStorePath, String storeType,
- char[] storePassword, String alias, char[] aliasPassword) {
- FileInputStream fis = null;
- try {
- KeyStore keyStore = KeyStore.getInstance(
- storeType != null ? storeType : KeyStore.getDefaultType());
- fis = new FileInputStream(osKeyStorePath);
- keyStore.load(fis, storePassword);
- return (KeyStore.PrivateKeyEntry)keyStore.getEntry(
- alias, new KeyStore.PasswordProtection(aliasPassword));
- } catch (Exception e) {
- String msg = e.getMessage();
- String causeMsg = null;
-
- Throwable cause = e.getCause();
- if (cause != null) {
- causeMsg = cause.getMessage();
- }
-
- if (msg != null) {
- if (causeMsg == null) {
- throw new BuildException(msg);
- } else {
- throw new BuildException(msg + ": " + causeMsg);
- }
- } else {
- if (causeMsg == null) {
- throw new BuildException(e);
- } else {
- throw new BuildException(causeMsg);
- }
- }
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- // pass
- }
- }
- }
- }
-
- private static final class NullZipFilter implements IZipEntryFilter {
-
- @Override
- public boolean checkEntry(String archivePath) throws ZipAbortException {
- return true;
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/SingleDependencyTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/SingleDependencyTask.java
deleted file mode 100644
index 08f86bf..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/SingleDependencyTask.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import org.apache.tools.ant.BuildException;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A base class for ant tasks that use a single dependency file to control (re)execution.
- */
-public abstract class SingleDependencyTask extends BuildTypedTask {
-
- private DependencyGraph mDependencies;
-
- protected abstract String getExecTaskName();
-
- protected interface InputPathFactory {
- InputPath createPath(File file, Set<String> extensionsToCheck);
- }
-
- private static final InputPathFactory sDefaultFactory = new InputPathFactory() {
- @Override
- public InputPath createPath(File file, Set<String> extensionsToCheck) {
- return new InputPath(file, extensionsToCheck);
- }
- };
-
- /**
- * Creates a list of {@link InputPath} from a list of {@link File} and an optional list of
- * extensions. All the {@link InputPath} will share the same extension restrictions.
- * @param paths the list of path
- * @param extensionsToCheck A set of extensions. Only files with an extension in this set will
- * be considered for a modification check. All deleted/created files will still be
- * checked. If this is null, all files will be checked for modification date
- * @return a list of {@link InputPath}
- */
- protected static List<InputPath> getInputPaths(List<File> paths,
- Set<String> extensionsToCheck, InputPathFactory factory) {
- List<InputPath> result = new ArrayList<InputPath>(paths.size());
-
- if (factory == null ) {
- factory = sDefaultFactory;
- }
-
- for (File f : paths) {
- result.add(factory.createPath(f, extensionsToCheck));
- }
-
- return result;
- }
-
- /**
- * Set up the dependency graph by passing it the location of the ".d" file, and the new input
- * paths.
- * @param dependencyFile path to the dependency file to use
- * @param inputPaths the new input paths for this new compilation.
- * @return true if the dependency graph was successfully initialized
- */
- protected boolean initDependencies(String dependencyFile, List<InputPath> inputPaths) {
- if (hasBuildTypeChanged()) {
- // we don't care about deps, we need to execute the task no matter what.
- return true;
- }
-
- File depFile = new File(dependencyFile);
- if (depFile.exists()) {
- mDependencies = new DependencyGraph(dependencyFile, inputPaths);
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Wrapper check to see if we need to execute this task at all
- * @return true if the DependencyGraph reports that our prereqs or targets
- * have changed since the last run
- */
- protected boolean dependenciesHaveChanged() {
- if (hasBuildTypeChanged()) {
- // if this is not a new build, display that build type change is forcing running
- // the task.
- if (isNewBuild() == false) {
- String execName = getExecTaskName();
- if (execName == null) {
- System.out.println(
- "Current build type is different than previous build: forced task run.");
- } else {
- System.out.println(
- "Current build type is different than previous build: forced " +
- execName + " run.");
- }
- }
- return true;
- }
-
- assert mDependencies != null : "Dependencies have not been initialized";
- return mDependencies.dependenciesHaveChanged(true /*printStatus*/);
- }
-
- protected void generateDependencyFile(String depFilePath,
- List<InputPath> inputs, String outputFile) {
- File depFile = new File(depFilePath);
-
- try {
- PrintStream ps = new PrintStream(depFile);
-
- // write the output file.
- ps.print(outputFile);
- ps.println(" : \\");
-
- //write the input files
- int count = inputs.size();
- for (int i = 0 ; i < count ; i++) {
- InputPath input = inputs.get(i);
- File file = input.getFile();
- if (file.isDirectory()) {
- writeContent(ps, file, input);
- } else {
- ps.print(file.getAbsolutePath());
- ps.println(" \\");
- }
- }
-
- ps.close();
- } catch (FileNotFoundException e) {
- throw new BuildException(e);
- }
- }
-
- private void writeContent(PrintStream ps, File file, InputPath input) {
- if (input.ignores(file)) {
- return;
- }
-
- File[] files = file.listFiles();
- if (files != null) {
- for (File f : files) {
- if (f.isDirectory()) {
- writeContent(ps, f, input);
- } else if (input.ignores(f) == false) {
- ps.print(f.getAbsolutePath());
- ps.println(" \\");
- }
- }
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/SingleInputOutputTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/SingleInputOutputTask.java
deleted file mode 100644
index a559673..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/SingleInputOutputTask.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.types.Path;
-
-import java.io.File;
-
-/**
- * Single input single output class. Execution is controlled
- * by modification timestamp of the input and output files.
- *
- * Implementation classes must implement {@link #createOutput()}
- *
- */
-public abstract class SingleInputOutputTask extends Task {
-
- private String mInput;
- private String mOutput;
-
- public void setInput(Path inputPath) {
- mInput = TaskHelper.checkSinglePath("input", inputPath);
- }
-
- public void setOutput(Path outputPath) {
- mOutput = TaskHelper.checkSinglePath("output", outputPath);
- }
-
- @Override
- public final void execute() throws BuildException {
- if (mInput == null) {
- throw new BuildException("Missing attribute input");
- }
- if (mOutput == null) {
- throw new BuildException("Missing attribute output");
- }
-
- // check if there's a need for the task to run.
- File outputFile = new File(mOutput);
- if (outputFile.isFile()) {
- File inputFile = new File(mInput);
- if (outputFile.lastModified() >= inputFile.lastModified()) {
- System.out.println(String.format(
- "Run cancelled: no changes to input file %1$s",
- inputFile.getAbsolutePath()));
- return;
- }
- }
-
- createOutput();
- }
-
- protected abstract void createOutput() throws BuildException;
-
- protected String getInput() {
- return mInput;
- }
-
- protected String getOutput() {
- return mOutput;
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/TaskHelper.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/TaskHelper.java
deleted file mode 100644
index 426ec25..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/TaskHelper.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2010 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.ant;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
-import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.util.DeweyDecimal;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-final class TaskHelper {
-
- private static Map<String, String> DEFAULT_ATTR_VALUES = new HashMap<String, String>();
- static {
- DEFAULT_ATTR_VALUES.put("source.dir", SdkConstants.FD_SOURCES);
- DEFAULT_ATTR_VALUES.put("out.dir", SdkConstants.FD_OUTPUT);
- }
-
- static String getDefault(String name) {
- return DEFAULT_ATTR_VALUES.get(name);
- }
-
- static File getSdkLocation(Project antProject) {
- // get the SDK location
- String sdkOsPath = antProject.getProperty(ProjectProperties.PROPERTY_SDK);
-
- // check if it's valid and exists
- if (sdkOsPath == null || sdkOsPath.isEmpty()) {
- throw new BuildException("SDK Location is not set.");
- }
-
- File sdk = new File(sdkOsPath);
- if (sdk.isDirectory() == false) {
- throw new BuildException(String.format("SDK Location '%s' is not valid.", sdkOsPath));
- }
-
- return sdk;
- }
-
- /**
- * Returns the revision of the tools for a given SDK.
- * @param sdkFile the {@link File} for the root folder of the SDK
- * @return the tools revision or -1 if not found.
- */
- @Nullable
- static DeweyDecimal getToolsRevision(File sdkFile) {
- Properties p = new Properties();
- try{
- // tools folder must exist, or this custom task wouldn't run!
- File toolsFolder= new File(sdkFile, SdkConstants.FD_TOOLS);
- File sourceProp = new File(toolsFolder, SdkConstants.FN_SOURCE_PROP);
-
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(sourceProp);
- p.load(fis);
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException ignore) {
- }
- }
- }
-
- String value = p.getProperty(PkgProps.PKG_REVISION);
- if (value != null) {
- FullRevision rev = FullRevision.parseRevision(value);
- return new DeweyDecimal(rev.toIntArray(false /*includePreview*/));
- }
- } catch (NumberFormatException e) {
- // couldn't parse the version number.
- } catch (FileNotFoundException e) {
- // couldn't find the file.
- } catch (IOException e) {
- // couldn't find the file.
- }
-
- return null;
- }
-
- static String checkSinglePath(String attribute, Path path) {
- String[] paths = path.list();
- if (paths.length != 1) {
- throw new BuildException(String.format(
- "Value for '%1$s' is not valid. It must resolve to a single path", attribute));
- }
-
- return paths[0];
- }
-
- /**
- * Returns the ProjectProperties for a given project path.
- * This loads and merges all the .properties files in the same way that Ant does it.
- *
- * Note that this does not return all the Ant properties but only the one customized by the
- * project's own build.xml file.
- *
- * If the project has no .properties files, this returns an empty {@link ProjectProperties}
- * with type {@link PropertyType#PROJECT}.
- *
- * @param projectPath the path to the project root folder.
- * @return a ProjectProperties.
- */
- @NonNull
- static ProjectProperties getProperties(@NonNull String projectPath) {
- // the import order is local, ant, project so we need to respect this.
- PropertyType[] types = PropertyType.getOrderedTypes();
-
- // make a working copy of the first non null props and then merge the rest into it.
- ProjectProperties properties = null;
- for (int i = 0 ; i < types.length ; i++) {
- properties = ProjectProperties.load(projectPath, types[i]);
-
- if (properties != null) {
- ProjectPropertiesWorkingCopy workingCopy = properties.makeWorkingCopy();
- for (int k = i + 1 ; k < types.length ; k++) {
- workingCopy.merge(types[k]);
- }
-
- // revert back to a read-only version
- properties = workingCopy.makeReadOnlyCopy();
-
- return properties;
- }
- }
-
- // return an empty object with type PropertyType.PROJECT (doesn't actually matter).
- return ProjectProperties.createEmpty(projectPath, PropertyType.PROJECT);
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/XPathTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/XPathTask.java
deleted file mode 100644
index c3f3242..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/XPathTask.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2009 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.ant;
-
-import com.android.xml.AndroidXPathFactory;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.types.Path;
-import org.xml.sax.InputSource;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpressionException;
-
-/**
- * Android specific XPath task.
- * The goal is to get the result of an XPath expression on Android XML files. The android namespace
- * (http://schemas.android.com/apk/res/android) must be associated to the "android" prefix.
- */
-public class XPathTask extends Task {
-
- private Path mManifestFile;
- private String mProperty;
- private String mExpression;
- private String mDefault;
-
- public void setInput(Path manifestFile) {
- mManifestFile = manifestFile;
- }
-
- public void setOutput(String property) {
- mProperty = property;
- }
-
- public void setExpression(String expression) {
- mExpression = expression;
- }
-
- public void setDefault(String defaultValue) {
- mDefault = defaultValue;
- }
-
- @Override
- public void execute() throws BuildException {
- try {
- if (mManifestFile == null || mManifestFile.list().length == 0) {
- throw new BuildException("input attribute is missing!");
- }
-
- if (mProperty == null) {
- throw new BuildException("output attribute is missing!");
- }
-
- if (mExpression == null) {
- throw new BuildException("expression attribute is missing!");
- }
-
- XPath xpath = AndroidXPathFactory.newXPath();
-
- String file = mManifestFile.list()[0];
- String result = xpath.evaluate(mExpression, new InputSource(new FileInputStream(file)));
- if (result.isEmpty() && mDefault != null) {
- result = mDefault;
- }
-
- getProject().setProperty(mProperty, result);
- } catch (XPathExpressionException e) {
- throw new BuildException(e);
- } catch (FileNotFoundException e) {
- throw new BuildException(e);
- }
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/java/com/android/ant/ZipAlignTask.java b/base/legacy/ant-tasks/src/main/java/com/android/ant/ZipAlignTask.java
deleted file mode 100644
index 840bee1..0000000
--- a/base/legacy/ant-tasks/src/main/java/com/android/ant/ZipAlignTask.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ant;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.Path;
-
-public class ZipAlignTask extends SingleInputOutputTask {
-
- private String mExecutable;
- private int mAlign = 4;
- private boolean mVerbose = false;
-
- /**
- * Sets the value of the "executable" attribute.
- * @param executable the value.
- */
- public void setExecutable(Path executable) {
- mExecutable = TaskHelper.checkSinglePath("executable", executable);
- }
-
- public void setAlign(int align) {
- mAlign = align;
- }
-
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- @Override
- public void createOutput() throws BuildException {
- if (mExecutable == null) {
- throw new BuildException("Missing attribute executable");
- }
-
- System.out.println("Running zip align on final apk...");
- doZipAlign();
- }
-
- private void doZipAlign() {
- ExecTask task = new ExecTask();
- task.setExecutable(mExecutable);
- task.setFailonerror(true);
- task.setProject(getProject());
- task.setOwningTarget(getOwningTarget());
-
- task.setTaskName("zip-align");
-
- // force overwrite of existing output file
- task.createArg().setValue("-f");
-
- // verbose flag
- if (mVerbose) {
- task.createArg().setValue("-v");
- }
-
- // align value
- task.createArg().setValue(Integer.toString(mAlign));
-
- // input
- task.createArg().setValue(getInput());
-
- // output
- task.createArg().setValue(getOutput());
-
- // execute
- task.execute();
- }
-}
diff --git a/base/legacy/ant-tasks/src/main/resources/anttasks.properties b/base/legacy/ant-tasks/src/main/resources/anttasks.properties
deleted file mode 100644
index f043894..0000000
--- a/base/legacy/ant-tasks/src/main/resources/anttasks.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-checkenv: com.android.ant.CheckEnvTask
-gettype: com.android.ant.GetTypeTask
-gettarget: com.android.ant.GetTargetTask
-getuitarget: com.android.ant.GetUiTargetTask
-getprojectpaths: com.android.ant.GetProjectPathsTask
-getlibpath: com.android.ant.GetLibraryPathTask
-dependency: com.android.ant.ComputeDependencyTask
-testedprojectclasspath: com.android.ant.ComputeProjectClasspathTask
-getemmafilter: com.android.ant.GetEmmaFilterTask
-mergemanifest: com.android.ant.ManifestMergerTask
-aapt: com.android.ant.AaptExecTask
-aidl: com.android.ant.AidlExecTask
-renderscript: com.android.ant.RenderScriptTask
-buildconfig: com.android.ant.BuildConfigTask
-dex: com.android.ant.DexExecTask
-apkbuilder: com.android.ant.ApkBuilderTask
-signapk: com.android.ant.SignApkTask
-zipalign: com.android.ant.ZipAlignTask
-xpath: com.android.ant.XPathTask
-if: com.android.ant.IfElseTask
-propertybyreplace: com.android.ant.PropertyByReplaceTask
-lint: com.android.ant.LintExecTask
-getbuildtools: com.android.ant.GetBuildToolsTask
\ No newline at end of file
diff --git a/base/lint/cli/lint-cli.iml b/base/lint/cli/lint-cli.iml
deleted file mode 100644
index 978b7a8..0000000
--- a/base/lint/cli/lint-cli.iml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- <excludeFolder url="file://$MODULE_DIR$/src/test/.settings" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="module" module-name="testutils" exported="" scope="TEST" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="module" module-name="builder-model" exported="" />
- <orderEntry type="library" name="ecj" level="project" />
- <orderEntry type="library" scope="TEST" name="groovy" level="project" />
- <orderEntry type="module" module-name="sdk-common-base" />
- <orderEntry type="module" module-name="lint-checks-base" />
- <orderEntry type="module" module-name="lint-api-base" />
- <orderEntry type="library" scope="TEST" name="intellij-annotations" level="project" />
- <orderEntry type="module" module-name="lint-tests" scope="TEST" />
- <orderEntry type="library" scope="TEST" name="mockito" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java b/base/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
deleted file mode 100644
index 090e3f1..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
+++ /dev/null
@@ -1,2124 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.SdkConstants.INT_DEF_ANNOTATION;
-import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
-import static com.android.SdkConstants.UTF_8;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.sdklib.IAndroidTarget;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.eclipse.jdt.core.compiler.CategorizedProblem;
-import org.eclipse.jdt.core.compiler.IProblem;
-import org.eclipse.jdt.internal.compiler.CompilationResult;
-import org.eclipse.jdt.internal.compiler.Compiler;
-import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
-import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
-import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
-import org.eclipse.jdt.internal.compiler.IProblemFactory;
-import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
-import org.eclipse.jdt.internal.compiler.ast.Annotation;
-import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
-import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
-import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
-import org.eclipse.jdt.internal.compiler.ast.Expression;
-import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
-import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
-import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
-import org.eclipse.jdt.internal.compiler.ast.Literal;
-import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
-import org.eclipse.jdt.internal.compiler.ast.MagicLiteral;
-import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
-import org.eclipse.jdt.internal.compiler.ast.MessageSend;
-import org.eclipse.jdt.internal.compiler.ast.NameReference;
-import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
-import org.eclipse.jdt.internal.compiler.ast.NumberLiteral;
-import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
-import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
-import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.TypeReference;
-import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
-import org.eclipse.jdt.internal.compiler.batch.FileSystem;
-import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
-import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
-import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
-import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
-import org.eclipse.jdt.internal.compiler.impl.ByteConstant;
-import org.eclipse.jdt.internal.compiler.impl.CharConstant;
-import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
-import org.eclipse.jdt.internal.compiler.impl.Constant;
-import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
-import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
-import org.eclipse.jdt.internal.compiler.impl.IntConstant;
-import org.eclipse.jdt.internal.compiler.impl.LongConstant;
-import org.eclipse.jdt.internal.compiler.impl.ShortConstant;
-import org.eclipse.jdt.internal.compiler.impl.StringConstant;
-import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
-import org.eclipse.jdt.internal.compiler.lookup.Binding;
-import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
-import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
-import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
-import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
-import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
-import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
-import org.eclipse.jdt.internal.compiler.lookup.ProblemBinding;
-import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
-import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
-import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
-import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
-import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
-import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
-import org.eclipse.jdt.internal.compiler.parser.Parser;
-import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
-import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
-import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.Node;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.ecj.EcjTreeConverter;
-
-/**
- * Java parser which uses ECJ for parsing and type attribution
- */
-public class EcjParser extends JavaParser {
- private static final boolean DEBUG_DUMP_PARSE_ERRORS = false;
-
- private final LintClient mClient;
- private final Project mProject;
- private Map<File, ICompilationUnit> mSourceUnits;
- private Map<ICompilationUnit, CompilationUnitDeclaration> mCompiled;
- private Map<String, TypeDeclaration> mTypeUnits;
- private Parser mParser;
- private INameEnvironment mEnvironment;
-
- public EcjParser(@NonNull LintCliClient client, @Nullable Project project) {
- mClient = client;
- mProject = project;
- mParser = getParser();
- }
-
- /**
- * Create the default compiler options
- */
- public static CompilerOptions createCompilerOptions() {
- CompilerOptions options = new CompilerOptions();
-
- // Always using JDK 7 rather than basing it on project metadata since we
- // don't do compilation error validation in lint (we leave that to the IDE's
- // error parser or the command line build's compilation step); we want an
- // AST that is as tolerant as possible.
- long languageLevel = ClassFileConstants.JDK1_7;
- options.complianceLevel = languageLevel;
- options.sourceLevel = languageLevel;
- options.targetJDK = languageLevel;
- options.originalComplianceLevel = languageLevel;
- options.originalSourceLevel = languageLevel;
- options.inlineJsrBytecode = true; // >1.5
-
- options.parseLiteralExpressionsAsConstants = true;
- options.analyseResourceLeaks = false;
- options.docCommentSupport = false;
- options.defaultEncoding = UTF_8;
- options.suppressOptionalErrors = true;
- options.generateClassFiles = false;
- options.isAnnotationBasedNullAnalysisEnabled = false;
- options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false;
- options.reportUnusedDeclaredThrownExceptionIncludeDocCommentReference = false;
- options.reportUnusedDeclaredThrownExceptionWhenOverriding = false;
- options.reportUnusedParameterIncludeDocCommentReference = false;
- options.reportUnusedParameterWhenImplementingAbstract = false;
- options.reportUnusedParameterWhenOverridingConcrete = false;
- options.suppressWarnings = true;
- options.processAnnotations = true;
- options.storeAnnotations = true;
- options.verbose = false;
- return options;
- }
-
- public static long getLanguageLevel(int major, int minor) {
- assert major == 1;
- switch (minor) {
- case 5: return ClassFileConstants.JDK1_5;
- case 6: return ClassFileConstants.JDK1_6;
- case 7:
- default:
- return ClassFileConstants.JDK1_7;
- }
- }
-
- private Parser getParser() {
- if (mParser == null) {
- CompilerOptions options = createCompilerOptions();
- ProblemReporter problemReporter = new ProblemReporter(
- DefaultErrorHandlingPolicies.exitOnFirstError(),
- options,
- new DefaultProblemFactory());
- mParser = new Parser(problemReporter,
- options.parseLiteralExpressionsAsConstants);
- mParser.javadocParser.checkDocComment = false;
- }
- return mParser;
- }
-
- @Override
- public void prepareJavaParse(@NonNull final List<JavaContext> contexts) {
- if (mProject == null || contexts.isEmpty()) {
- return;
- }
-
- List<ICompilationUnit> sources = Lists.newArrayListWithExpectedSize(contexts.size());
- mSourceUnits = Maps.newHashMapWithExpectedSize(sources.size());
- for (JavaContext context : contexts) {
- String contents = context.getContents();
- if (contents == null) {
- continue;
- }
- File file = context.file;
- CompilationUnit unit = new CompilationUnit(contents.toCharArray(), file.getPath(),
- UTF_8);
- sources.add(unit);
- mSourceUnits.put(file, unit);
- }
- List<String> classPath = computeClassPath(contexts);
- mCompiled = Maps.newHashMapWithExpectedSize(mSourceUnits.size());
- try {
- mEnvironment = parse(createCompilerOptions(), sources, classPath, mCompiled, mClient);
- } catch (Throwable t) {
- mClient.log(t, "ECJ compiler crashed");
- }
-
- if (DEBUG_DUMP_PARSE_ERRORS) {
- for (CompilationUnitDeclaration unit : mCompiled.values()) {
- // so maybe I don't need my map!!
- CategorizedProblem[] problems = unit.compilationResult()
- .getAllProblems();
- if (problems != null) {
- for (IProblem problem : problems) {
- if (problem == null || !problem.isError()) {
- continue;
- }
- System.out.println(
- new String(problem.getOriginatingFileName()) + ":"
- + (problem.isError() ? "Error" : "Warning") + ": "
- + problem.getSourceLineNumber() + ": " + problem.getMessage());
- }
- }
- }
- }
- }
-
- /** Parse the given source units and class path and store it into the given output map */
- public static INameEnvironment parse(
- CompilerOptions options,
- @NonNull List<ICompilationUnit> sourceUnits,
- @NonNull List<String> classPath,
- @NonNull Map<ICompilationUnit, CompilationUnitDeclaration> outputMap,
- @Nullable LintClient client) {
- INameEnvironment environment = new FileSystem(
- classPath.toArray(new String[classPath.size()]), new String[0],
- options.defaultEncoding);
- IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.proceedWithAllProblems();
- IProblemFactory problemFactory = new DefaultProblemFactory(Locale.getDefault());
- ICompilerRequestor requestor = new ICompilerRequestor() {
- @Override
- public void acceptResult(CompilationResult result) {
- // Not used; we need the corresponding CompilationUnitDeclaration for the source
- // units (the AST parsed from source) which we don't get access to here, so we
- // instead subclass AST to get our hands on them.
- }
- };
-
- NonGeneratingCompiler compiler = new NonGeneratingCompiler(environment, policy, options,
- requestor, problemFactory, outputMap);
- try {
- compiler.compile(sourceUnits.toArray(new ICompilationUnit[sourceUnits.size()]));
- } catch (OutOfMemoryError e) {
- environment.cleanup();
-
- // Since we're running out of memory, if it's all still held we could potentially
- // fail attempting to log the failure. Actively get rid of the large ECJ data
- // structure references first so minimize the chance of that
- //noinspection UnusedAssignment
- compiler = null;
- //noinspection UnusedAssignment
- environment = null;
- //noinspection UnusedAssignment
- requestor = null;
- //noinspection UnusedAssignment
- problemFactory = null;
- //noinspection UnusedAssignment
- policy = null;
-
- String msg = "Ran out of memory analyzing .java sources with ECJ: Some lint checks "
- + "may not be accurate (missing type information from the compiler)";
- if (client != null) {
- // Don't log exception too; this isn't a compiler error per se where we
- // need to pin point the exact unlucky code that asked for memory when it
- // had already run out
- client.log(null, msg);
- } else {
- System.out.println(msg);
- }
- } catch (Throwable t) {
- if (client != null) {
- CompilationUnitDeclaration currentUnit = compiler.getCurrentUnit();
- if (currentUnit == null || currentUnit.getFileName() == null) {
- client.log(t, "ECJ compiler crashed");
- } else {
- client.log(t, "ECJ compiler crashed processing %1$s",
- new String(currentUnit.getFileName()));
- }
- } else {
- t.printStackTrace();
- }
-
- environment.cleanup();
- environment = null;
- }
-
- return environment;
- }
-
- @NonNull
- private List<String> computeClassPath(@NonNull List<JavaContext> contexts) {
- assert mProject != null;
- List<String> classPath = Lists.newArrayList();
-
- IAndroidTarget compileTarget = mProject.getBuildTarget();
- if (compileTarget != null) {
- String androidJar = compileTarget.getPath(IAndroidTarget.ANDROID_JAR);
- if (androidJar != null && new File(androidJar).exists()) {
- classPath.add(androidJar);
- }
- }
-
- Set<File> libraries = Sets.newHashSet();
- Set<String> names = Sets.newHashSet();
- for (File library : mProject.getJavaLibraries()) {
- libraries.add(library);
- names.add(getLibraryName(library));
- }
- for (Project project : mProject.getAllLibraries()) {
- for (File library : project.getJavaLibraries()) {
- String name = getLibraryName(library);
- // Avoid pulling in android-support-v4.jar from libraries etc
- // since we're pointing to the local copies rather than the real
- // maven/gradle source copies
- if (!names.contains(name)) {
- libraries.add(library);
- names.add(name);
- }
- }
- }
-
- for (File file : libraries) {
- if (file.exists()) {
- classPath.add(file.getPath());
- }
- }
-
- // In incremental mode we may need to point to other sources in the project
- // for type resolution
- EnumSet<Scope> scope = contexts.get(0).getScope();
- if (!scope.contains(Scope.ALL_JAVA_FILES)) {
- // May need other compiled classes too
- for (File dir : mProject.getJavaClassFolders()) {
- if (dir.exists()) {
- classPath.add(dir.getPath());
- }
- }
- }
-
- return classPath;
- }
-
- @NonNull
- private static String getLibraryName(@NonNull File library) {
- String name = library.getName();
- if (name.equals(SdkConstants.FN_CLASSES_JAR)) {
- // For AAR artifacts they'll all clash with "classes.jar"; include more unique
- // context
- String path = library.getPath();
- int index = path.indexOf("exploded-aar");
- if (index != -1) {
- return path.substring(index);
- } else {
- index = path.indexOf("exploded-bundles");
- if (index != -1) {
- return path.substring(index);
- }
- }
- File parent = library.getParentFile();
- if (parent != null) {
- return parent.getName() + File.separatorChar + name;
- }
- }
- return name;
- }
-
- @Override
- public Node parseJava(@NonNull JavaContext context) {
- String code = context.getContents();
- if (code == null) {
- return null;
- }
-
- CompilationUnitDeclaration unit = getParsedUnit(context, code);
- try {
- EcjTreeConverter converter = new EcjTreeConverter();
- converter.visit(code, unit);
- List<? extends Node> nodes = converter.getAll();
-
- if (nodes != null) {
- // There could be more than one node when there are errors; pick out the
- // compilation unit node
- for (Node node : nodes) {
- if (node instanceof lombok.ast.CompilationUnit) {
- return node;
- }
- }
- }
-
- return null;
- } catch (Throwable t) {
- mClient.log(t, "Failed converting ECJ parse tree to Lombok for file %1$s",
- context.file.getPath());
- return null;
- }
- }
-
- @Nullable
- private CompilationUnitDeclaration getParsedUnit(
- @NonNull JavaContext context,
- @NonNull String code) {
- ICompilationUnit sourceUnit = null;
- if (mSourceUnits != null && mCompiled != null) {
- sourceUnit = mSourceUnits.get(context.file);
- if (sourceUnit != null) {
- CompilationUnitDeclaration unit = mCompiled.get(sourceUnit);
- if (unit != null) {
- return unit;
- }
- }
- }
-
- if (sourceUnit == null) {
- sourceUnit = new CompilationUnit(code.toCharArray(), context.file.getName(), UTF_8);
- }
- try {
- CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0);
- return getParser().parse(sourceUnit, compilationResult);
- } catch (AbortCompilation e) {
- // No need to report Java parsing errors while running in Eclipse.
- // Eclipse itself will already provide problem markers for these files,
- // so all this achieves is creating "multiple annotations on this line"
- // tooltips instead.
- return null;
- }
- }
-
- @NonNull
- @Override
- public Location getLocation(@NonNull JavaContext context, @NonNull Node node) {
- lombok.ast.Position position = node.getPosition();
- return Location.create(context.file, context.getContents(),
- position.getStart(), position.getEnd());
- }
-
- @NonNull
- @Override
- public
- Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) {
- return new LocationHandle(context.file, node);
- }
-
- @Override
- public void dispose(@NonNull JavaContext context,
- @NonNull Node compilationUnit) {
- if (mSourceUnits != null && mCompiled != null) {
- ICompilationUnit sourceUnit = mSourceUnits.get(context.file);
- if (sourceUnit != null) {
- mSourceUnits.remove(context.file);
- mCompiled.remove(sourceUnit);
- }
- }
- }
-
- @Override
- public void dispose() {
- if (mEnvironment != null) {
- mEnvironment.cleanup();
- mEnvironment = null;
- }
- }
-
- @Nullable
- private static Object getNativeNode(@NonNull Node node) {
- Object nativeNode = node.getNativeNode();
- if (nativeNode != null) {
- return nativeNode;
- }
-
- Node parent = node.getParent();
- // The ECJ native nodes are sometimes spotty; for example, for a
- // MethodInvocation node we can have a null native node, but its
- // parent expression statement will point to the real MessageSend node
- if (parent != null) {
- nativeNode = parent.getNativeNode();
- if (nativeNode != null) {
- return nativeNode;
- }
- }
-
- if (node instanceof VariableDefinitionEntry) {
- node = node.getParent().getParent();
- }
- if (node instanceof VariableDeclaration) {
- VariableDeclaration declaration = (VariableDeclaration) node;
- VariableDefinition definition = declaration.astDefinition();
- if (definition != null) {
- lombok.ast.TypeReference typeReference = definition.astTypeReference();
- if (typeReference != null) {
- return typeReference.getNativeNode();
- }
- }
- }
-
- return null;
- }
-
- @Override
- @Nullable
- public ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node) {
- Object nativeNode = getNativeNode(node);
- if (nativeNode == null) {
- return null;
- }
-
- if (nativeNode instanceof NameReference) {
- return resolve(((NameReference) nativeNode).binding);
- } else if (nativeNode instanceof TypeReference) {
- return resolve(((TypeReference) nativeNode).resolvedType);
- } else if (nativeNode instanceof MessageSend) {
- return resolve(((MessageSend) nativeNode).binding);
- } else if (nativeNode instanceof AllocationExpression) {
- return resolve(((AllocationExpression) nativeNode).binding);
- } else if (nativeNode instanceof TypeDeclaration) {
- return resolve(((TypeDeclaration) nativeNode).binding);
- } else if (nativeNode instanceof ExplicitConstructorCall) {
- return resolve(((ExplicitConstructorCall) nativeNode).binding);
- } else if (nativeNode instanceof Annotation) {
- AnnotationBinding compilerAnnotation =
- ((Annotation) nativeNode).getCompilerAnnotation();
- if (compilerAnnotation != null) {
- return new EcjResolvedAnnotation(compilerAnnotation);
- }
- return resolve(((Annotation) nativeNode).resolvedType);
- } else if (nativeNode instanceof AbstractMethodDeclaration) {
- return resolve(((AbstractMethodDeclaration) nativeNode).binding);
- } else if (nativeNode instanceof AbstractVariableDeclaration) {
- if (nativeNode instanceof LocalDeclaration) {
- return resolve(((LocalDeclaration) nativeNode).binding);
- } else if (nativeNode instanceof FieldDeclaration) {
- FieldDeclaration fieldDeclaration = (FieldDeclaration) nativeNode;
- if (fieldDeclaration.initialization instanceof AllocationExpression) {
- AllocationExpression allocation =
- (AllocationExpression)fieldDeclaration.initialization;
- if (allocation.binding != null) {
- // Field constructor call: this is an enum constant.
- return new EcjResolvedMethod(allocation.binding);
- }
- }
- return resolve(fieldDeclaration.binding);
- }
- }
-
- // TODO: Handle org.eclipse.jdt.internal.compiler.ast.SuperReference. It
- // doesn't contain an actual method binding; the parent node call should contain
- // it, but is missing a native node reference; investigate the ECJ bridge's super
- // handling.
-
- return null;
- }
-
- private ResolvedNode resolve(@Nullable Binding binding) {
- if (binding == null || binding instanceof ProblemBinding) {
- return null;
- }
-
- if (binding instanceof TypeBinding) {
- TypeBinding tb = (TypeBinding) binding;
- return new EcjResolvedClass(tb);
- } else if (binding instanceof MethodBinding) {
- MethodBinding mb = (MethodBinding) binding;
- if (mb instanceof ProblemMethodBinding) {
- return null;
- }
- //noinspection VariableNotUsedInsideIf
- if (mb.declaringClass != null) {
- return new EcjResolvedMethod(mb);
- }
- } else if (binding instanceof LocalVariableBinding) {
- LocalVariableBinding lvb = (LocalVariableBinding) binding;
- //noinspection VariableNotUsedInsideIf
- if (lvb.type != null) {
- return new EcjResolvedVariable(lvb);
- }
- } else if (binding instanceof FieldBinding) {
- FieldBinding fb = (FieldBinding) binding;
- if (fb instanceof ProblemFieldBinding) {
- return null;
- }
- if (fb.type != null && fb.declaringClass != null) {
- return new EcjResolvedField(fb);
- }
- }
-
- return null;
- }
-
- private TypeDeclaration findTypeDeclaration(@NonNull String signature) {
- if (mTypeUnits == null) {
- mTypeUnits = Maps.newHashMapWithExpectedSize(mCompiled.size());
- for (CompilationUnitDeclaration unit : mCompiled.values()) {
- if (unit.types != null) {
- for (TypeDeclaration typeDeclaration : unit.types) {
- addTypeDeclaration(typeDeclaration);
- }
- }
- }
- }
-
- return mTypeUnits.get(signature);
- }
-
- private void addTypeDeclaration(TypeDeclaration typeDeclaration) {
- String type = new String(typeDeclaration.binding.readableName());
- mTypeUnits.put(type, typeDeclaration);
- // Recurse on member types
- if (typeDeclaration.memberTypes != null) {
- for (TypeDeclaration member : typeDeclaration.memberTypes) {
- addTypeDeclaration(member);
- }
- }
- }
-
- @Override
- @Nullable
- public TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node) {
- Object nativeNode = getNativeNode(node);
- if (nativeNode == null) {
- return null;
- }
-
- if (nativeNode instanceof MessageSend) {
- nativeNode = ((MessageSend)nativeNode).binding;
- } else if (nativeNode instanceof AllocationExpression) {
- nativeNode = ((AllocationExpression)nativeNode).resolvedType;
- } else if (nativeNode instanceof NameReference) {
- nativeNode = ((NameReference)nativeNode).resolvedType;
- } else if (nativeNode instanceof Expression) {
- if (nativeNode instanceof Literal) {
- if (nativeNode instanceof StringLiteral) {
- return getTypeDescriptor(TYPE_STRING);
- } else if (nativeNode instanceof NumberLiteral) {
- if (nativeNode instanceof IntLiteral) {
- return getTypeDescriptor(TYPE_INT);
- } else if (nativeNode instanceof LongLiteral) {
- return getTypeDescriptor(TYPE_LONG);
- } else if (nativeNode instanceof CharLiteral) {
- return getTypeDescriptor(TYPE_CHAR);
- } else if (nativeNode instanceof FloatLiteral) {
- return getTypeDescriptor(TYPE_FLOAT);
- } else if (nativeNode instanceof DoubleLiteral) {
- return getTypeDescriptor(TYPE_DOUBLE);
- }
- } else if (nativeNode instanceof MagicLiteral) {
- if (nativeNode instanceof TrueLiteral || nativeNode instanceof FalseLiteral) {
- return getTypeDescriptor(TYPE_BOOLEAN);
- } else if (nativeNode instanceof NullLiteral) {
- return getTypeDescriptor(TYPE_NULL);
- }
- }
- }
- nativeNode = ((Expression)nativeNode).resolvedType;
- } else if (nativeNode instanceof TypeDeclaration) {
- nativeNode = ((TypeDeclaration) nativeNode).binding;
- } else if (nativeNode instanceof AbstractMethodDeclaration) {
- nativeNode = ((AbstractMethodDeclaration) nativeNode).binding;
- }
-
- if (nativeNode instanceof Binding) {
- Binding binding = (Binding) nativeNode;
- if (binding instanceof TypeBinding) {
- TypeBinding tb = (TypeBinding) binding;
- return getTypeDescriptor(tb);
- } else if (binding instanceof LocalVariableBinding) {
- LocalVariableBinding lvb = (LocalVariableBinding) binding;
- if (lvb.type != null) {
- return getTypeDescriptor(lvb.type);
- }
- } else if (binding instanceof FieldBinding) {
- FieldBinding fb = (FieldBinding) binding;
- if (fb.type != null) {
- return getTypeDescriptor(fb.type);
- }
- } else if (binding instanceof MethodBinding) {
- return getTypeDescriptor(((MethodBinding) binding).returnType);
- } else if (binding instanceof ProblemBinding) {
- // Unresolved type. We just don't know.
- return null;
- }
- }
- return null;
- }
-
- @Nullable
- @Override
- public ResolvedClass findClass(@NonNull JavaContext context,
- @NonNull String fullyQualifiedName) {
- Node compilationUnit = context.getCompilationUnit();
- if (compilationUnit == null) {
- return null;
- }
- Object nativeObj = getNativeNode(compilationUnit);
- if (!(nativeObj instanceof CompilationUnitDeclaration)) {
- return null;
- }
- CompilationUnitDeclaration ecjUnit = (CompilationUnitDeclaration) nativeObj;
-
- // Convert "foo.bar.Baz" into char[][] 'foo','bar','Baz' as required for
- // ECJ name lookup
- List<char[]> arrays = Lists.newArrayList();
- for (String segment : Splitter.on('.').split(fullyQualifiedName)) {
- arrays.add(segment.toCharArray());
- }
- char[][] compoundName = new char[arrays.size()][];
- for (int i = 0, n = arrays.size(); i < n; i++) {
- compoundName[i] = arrays.get(i);
- }
-
- Binding typeOrPackage = ecjUnit.scope.getTypeOrPackage(compoundName);
- if (typeOrPackage instanceof TypeBinding && !(typeOrPackage instanceof ProblemReferenceBinding)) {
- return new EcjResolvedClass((TypeBinding)typeOrPackage);
- }
-
- return null;
- }
-
- @Nullable
- private TypeDescriptor getTypeDescriptor(@Nullable TypeBinding resolvedType) {
- if (resolvedType == null) {
- return null;
- }
- return new EcjTypeDescriptor(resolvedType);
- }
-
- private static TypeDescriptor getTypeDescriptor(String fqn) {
- return new DefaultTypeDescriptor(fqn);
- }
-
- /** Computes the super method, if any, given a method binding */
- private static MethodBinding findSuperMethodBinding(@NonNull MethodBinding binding) {
- try {
- ReferenceBinding superclass = binding.declaringClass.superclass();
- while (superclass != null) {
- MethodBinding[] methods = superclass.getMethods(binding.selector,
- binding.parameters.length);
- for (MethodBinding method : methods) {
- if (method.areParameterErasuresEqual(binding)) {
- return method;
- }
- }
-
- superclass = superclass.superclass();
- }
- } catch (Exception ignore) {
- // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268
- }
-
- return null;
- }
-
- @NonNull
- private static Collection<ResolvedAnnotation> merge(
- @Nullable Collection<ResolvedAnnotation> first,
- @Nullable Collection<ResolvedAnnotation> second) {
- if (first == null || first.isEmpty()) {
- if (second == null) {
- return Collections.emptyList();
- } else {
- return second;
- }
- } else if (second == null || second.isEmpty()) {
- return first;
- } else {
- int size = first.size() + second.size();
- List<ResolvedAnnotation> merged = Lists.newArrayListWithExpectedSize(size);
- merged.addAll(first);
- merged.addAll(second);
- return merged;
- }
- }
-
- /* Handle for creating positions cheaply and returning full fledged locations later */
- private static class LocationHandle implements Location.Handle {
- private File mFile;
- private Node mNode;
- private Object mClientData;
-
- public LocationHandle(File file, Node node) {
- mFile = file;
- mNode = node;
- }
-
- @NonNull
- @Override
- public Location resolve() {
- lombok.ast.Position pos = mNode.getPosition();
- return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
- }
-
- @Override
- public void setClientData(@Nullable Object clientData) {
- mClientData = clientData;
- }
-
- @Override
- @Nullable
- public Object getClientData() {
- return mClientData;
- }
- }
-
- // Custom version of the compiler which skips code generation and records source units
- private static class NonGeneratingCompiler extends Compiler {
- private Map<ICompilationUnit, CompilationUnitDeclaration> mUnits;
- private CompilationUnitDeclaration mCurrentUnit;
-
- public NonGeneratingCompiler(INameEnvironment environment, IErrorHandlingPolicy policy,
- CompilerOptions options, ICompilerRequestor requestor,
- IProblemFactory problemFactory,
- Map<ICompilationUnit, CompilationUnitDeclaration> units) {
- super(environment, policy, options, requestor, problemFactory, null, null);
- mUnits = units;
- }
-
- @Nullable
- CompilationUnitDeclaration getCurrentUnit() {
- // Can't use lookupEnvironment.unitBeingCompleted directly; it gets nulled out
- // as part of the exception catch handling in the compiler before this method
- // is called from lint -- therefore we stash a copy in our own mCurrentUnit field
- return mCurrentUnit;
- }
-
- @Override
- protected synchronized void addCompilationUnit(ICompilationUnit sourceUnit,
- CompilationUnitDeclaration parsedUnit) {
- super.addCompilationUnit(sourceUnit, parsedUnit);
- mUnits.put(sourceUnit, parsedUnit);
- }
-
- @Override
- public void process(CompilationUnitDeclaration unit, int unitNumber) {
- mCurrentUnit = lookupEnvironment.unitBeingCompleted = unit;
-
- parser.getMethodBodies(unit);
- if (unit.scope != null) {
- unit.scope.faultInTypes();
- unit.scope.verifyMethods(lookupEnvironment.methodVerifier());
- }
- unit.resolve();
- unit.analyseCode();
-
- // This is where we differ from super: DON'T call generateCode().
- // Sadly we can't just set ignoreMethodBodies=true to have the same effect,
- // since that would also skip the analyseCode call, which we DO, want:
- // unit.generateCode();
-
- if (options.produceReferenceInfo && unit.scope != null) {
- unit.scope.storeDependencyInfo();
- }
- unit.finalizeProblems();
- unit.compilationResult.totalUnitsKnown = totalUnits;
- lookupEnvironment.unitBeingCompleted = null;
- }
- }
-
- private class EcjTypeDescriptor extends TypeDescriptor {
- private final TypeBinding mBinding;
-
- private EcjTypeDescriptor(@NonNull TypeBinding binding) {
- mBinding = binding;
- }
-
- @NonNull
- @Override
- public String getName() {
- return new String(mBinding.readableName());
- }
-
- @Override
- public boolean matchesName(@NonNull String name) {
- return sameChars(name, mBinding.readableName());
- }
-
- @Override
- public boolean matchesSignature(@NonNull String signature) {
- return sameChars(signature, mBinding.readableName());
- }
-
- @NonNull
- @Override
- public String getSignature() {
- return getName();
- }
-
- @Override
- @Nullable
- public ResolvedClass getTypeClass() {
- if (!mBinding.isPrimitiveType()) {
- return new EcjResolvedClass(mBinding);
- }
- return null;
- }
-
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- EcjTypeDescriptor that = (EcjTypeDescriptor) o;
-
- if (!mBinding.equals(that.mBinding)) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return mBinding.hashCode();
- }
- }
-
- private class EcjResolvedMethod extends ResolvedMethod {
- private MethodBinding mBinding;
-
- private EcjResolvedMethod(MethodBinding binding) {
- mBinding = binding;
- assert mBinding.declaringClass != null;
- }
-
- @NonNull
- @Override
- public String getName() {
- char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
- return new String(c);
- }
-
- @Override
- public boolean matches(@NonNull String name) {
- char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
- return sameChars(name, c);
- }
-
- @NonNull
- @Override
- public ResolvedClass getContainingClass() {
- return new EcjResolvedClass(mBinding.declaringClass);
- }
-
- @Override
- public int getArgumentCount() {
- return mBinding.parameters != null ? mBinding.parameters.length : 0;
- }
-
- @NonNull
- @Override
- public TypeDescriptor getArgumentType(int index) {
- TypeBinding parameterType = mBinding.parameters[index];
- TypeDescriptor typeDescriptor = getTypeDescriptor(parameterType);
- assert typeDescriptor != null; // because parameter is not null
- return typeDescriptor;
- }
-
- @Override
- public boolean argumentMatchesType(int index, @NonNull String signature) {
- return sameChars(signature, mBinding.parameters[index].readableName());
- }
-
- @Nullable
- @Override
- public TypeDescriptor getReturnType() {
- return isConstructor() ? null : getTypeDescriptor(mBinding.returnType);
- }
-
- @Override
- public boolean isConstructor() {
- return mBinding.isConstructor();
- }
-
- @Override
- @Nullable
- public ResolvedMethod getSuperMethod() {
- MethodBinding superBinding = findSuperMethodBinding(mBinding);
- if (superBinding != null) {
- return new EcjResolvedMethod(superBinding);
- }
-
- return null;
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(4);
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
-
- MethodBinding binding = this.mBinding;
- while (binding != null) {
- AnnotationBinding[] annotations = binding.getAnnotations();
- int count = annotations.length;
- if (count > 0) {
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- all.add(new EcjResolvedAnnotation(annotation));
- }
- }
- }
-
- // Look for external annotations
- Collection<ResolvedAnnotation> external = manager.getAnnotations(
- new EcjResolvedMethod(binding));
- if (external != null) {
- all.addAll(external);
- }
-
- binding = findSuperMethodBinding(binding);
- }
-
- return all;
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getParameterAnnotations(int index) {
- List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(4);
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
-
- MethodBinding binding = this.mBinding;
- while (binding != null) {
- AnnotationBinding[][] parameterAnnotations = binding.getParameterAnnotations();
- if (parameterAnnotations != null &&
- index >= 0 && index < parameterAnnotations.length) {
- AnnotationBinding[] annotations = parameterAnnotations[index];
- int count = annotations.length;
- if (count > 0) {
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- all.add(new EcjResolvedAnnotation(annotation));
- }
- }
- }
- }
-
- // Look for external annotations
- Collection<ResolvedAnnotation> external = manager.getAnnotations(
- new EcjResolvedMethod(binding), index);
- if (external != null) {
- all.addAll(external);
- }
-
- binding = findSuperMethodBinding(binding);
- }
-
- return all;
- }
-
- @Override
- public int getModifiers() {
- return mBinding.getAccessFlags();
- }
-
- @Override
- public String getSignature() {
- return mBinding.toString();
- }
-
- @Override
- public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
- PackageBinding pkg = mBinding.declaringClass.getPackage();
- if (pkg != null) {
- return includeSubPackages ?
- startsWithCompound(pkgName, pkg.compoundName) :
- equalsCompound(pkgName, pkg.compoundName);
- }
- return false;
- }
-
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- EcjResolvedMethod that = (EcjResolvedMethod) o;
-
- if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return mBinding != null ? mBinding.hashCode() : 0;
- }
- }
-
- private class EcjResolvedClass extends ResolvedClass {
- protected final TypeBinding mBinding;
-
- private EcjResolvedClass(TypeBinding binding) {
- mBinding = binding;
- }
-
- @NonNull
- @Override
- public String getName() {
- String name = new String(mBinding.readableName());
- if (name.indexOf('.') == -1 && mBinding.enclosingType() != null) {
- return new String(mBinding.enclosingType().readableName()) + '.' +
- name;
- }
-
- return name;
- }
-
- @NonNull
- @Override
- public String getSimpleName() {
- return new String(mBinding.shortReadableName());
- }
-
- @Override
- public boolean matches(@NonNull String name) {
- return sameChars(name, mBinding.readableName());
- }
-
- @Nullable
- @Override
- public ResolvedClass getSuperClass() {
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding refBinding = (ReferenceBinding) mBinding;
- ReferenceBinding superClass = refBinding.superclass();
- if (superClass != null) {
- return new EcjResolvedClass(superClass);
- }
- }
-
- return null;
- }
-
- @Nullable
- @Override
- public ResolvedClass getContainingClass() {
- if (mBinding instanceof NestedTypeBinding) {
- NestedTypeBinding ntb = (NestedTypeBinding) mBinding;
- if (ntb.enclosingType != null) {
- return new EcjResolvedClass(ntb.enclosingType);
- }
- }
-
- return null;
- }
-
- @Override
- public boolean isSubclassOf(@NonNull String name, boolean strict) {
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding cls = (ReferenceBinding) mBinding;
- if (strict) {
- cls = cls.superclass();
- }
- for (; cls != null; cls = cls.superclass()) {
- if (sameChars(name, cls.readableName())) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Override
- @NonNull
- public Iterable<ResolvedMethod> getConstructors() {
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding cls = (ReferenceBinding) mBinding;
- MethodBinding[] methods = cls.getMethods(TypeConstants.INIT);
- if (methods != null) {
- int count = methods.length;
- List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
- for (MethodBinding method : methods) {
- if (method.isConstructor()) {
- result.add(new EcjResolvedMethod(method));
- }
- }
- return result;
- }
- }
-
- return Collections.emptyList();
- }
-
- @Override
- @NonNull
- public Iterable<ResolvedMethod> getMethods(@NonNull String name,
- boolean includeInherited) {
- return findMethods(name, includeInherited);
- }
-
- @Override
- @NonNull
- public Iterable<ResolvedMethod> getMethods(boolean includeInherited) {
- return findMethods(null, includeInherited);
- }
-
- @NonNull
- private Iterable<ResolvedMethod> findMethods(@Nullable String name,
- boolean includeInherited) {
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding cls = (ReferenceBinding) mBinding;
- if (includeInherited) {
- List<ResolvedMethod> result = null;
- while (cls != null) {
- MethodBinding[] methods =
- name != null ? cls.getMethods(name.toCharArray()) : cls.methods();
- if (methods != null) {
- int count = methods.length;
- if (count > 0) {
- if (result == null) {
- result = Lists.newArrayListWithExpectedSize(count);
- }
- for (MethodBinding method : methods) {
- if (!method.isConstructor()) {
- // See if this method looks like it's masked
- boolean masked = false;
- for (ResolvedMethod m : result) {
- MethodBinding mb = ((EcjResolvedMethod) m).mBinding;
- if (mb.areParameterErasuresEqual(method)) {
- masked = true;
- break;
- }
- }
- if (masked) {
- continue;
- }
-
- result.add(new EcjResolvedMethod(method));
- }
- }
- }
- }
- cls = cls.superclass();
- }
-
- return result != null ? result : Collections.<ResolvedMethod>emptyList();
- } else {
- MethodBinding[] methods =
- name != null ? cls.getMethods(name.toCharArray()) : cls.methods();
- if (methods != null) {
- int count = methods.length;
- List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
- for (MethodBinding method : methods) {
- if (!method.isConstructor()) {
- result.add(new EcjResolvedMethod(method));
- }
- }
- return result;
- }
- }
- }
-
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(2);
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
-
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding cls = (ReferenceBinding) mBinding;
- while (cls != null) {
- AnnotationBinding[] annotations = cls.getAnnotations();
- int count = annotations.length;
- if (count > 0) {
- all = Lists.newArrayListWithExpectedSize(count);
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- all.add(new EcjResolvedAnnotation(annotation));
- }
- }
- }
-
- // Look for external annotations
- Collection<ResolvedAnnotation> external = manager.getAnnotations(
- new EcjResolvedClass(cls));
- if (external != null) {
- all.addAll(external);
- }
-
- cls = cls.superclass();
- }
- } else {
- Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
- if (external != null) {
- all.addAll(external);
- }
- }
-
- return all;
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedField> getFields(boolean includeInherited) {
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding cls = (ReferenceBinding) mBinding;
- if (includeInherited) {
- List<ResolvedField> result = null;
- while (cls != null) {
- FieldBinding[] fields = cls.fields();
- if (fields != null) {
- int count = fields.length;
- if (count > 0) {
- if (result == null) {
- result = Lists.newArrayListWithExpectedSize(count);
- }
- for (FieldBinding field : fields) {
- // See if this field looks like it's masked
- boolean masked = false;
- for (ResolvedField f : result) {
- FieldBinding mb = ((EcjResolvedField) f).mBinding;
- if (Arrays.equals(mb.readableName(),
- field.readableName())) {
- masked = true;
- break;
- }
- }
- if (masked) {
- continue;
- }
-
- result.add(new EcjResolvedField(field));
- }
- }
- }
- cls = cls.superclass();
- }
-
- return result != null ? result : Collections.<ResolvedField>emptyList();
- } else {
- FieldBinding[] fields = cls.fields();
- if (fields != null) {
- int count = fields.length;
- List<ResolvedField> result = Lists.newArrayListWithExpectedSize(count);
- for (FieldBinding field : fields) {
- result.add(new EcjResolvedField(field));
- }
- return result;
- }
- }
- }
-
- return Collections.emptyList();
- }
-
- @Override
- @Nullable
- public ResolvedField getField(@NonNull String name, boolean includeInherited) {
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding cls = (ReferenceBinding) mBinding;
- while (cls != null) {
- FieldBinding[] fields = cls.fields();
- if (fields != null) {
- for (FieldBinding field : fields) {
- if (sameChars(name, field.name)) {
- return new EcjResolvedField(field);
- }
- }
- }
- if (includeInherited) {
- cls = cls.superclass();
- } else {
- break;
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- @Override
- public ResolvedPackage getPackage() {
- return new EcjResolvedPackage(mBinding.getPackage());
- }
-
- @Override
- public int getModifiers() {
- if (mBinding instanceof ReferenceBinding) {
- ReferenceBinding cls = (ReferenceBinding) mBinding;
- // These constants from ClassFileConstants luckily agree with the Modifier
- // constants in the low bits we care about (public, abstract, static, etc)
- return cls.getAccessFlags();
- }
- return 0;
- }
-
- @Override
- public String getSignature() {
- return getName();
- }
-
- @Override
- public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
- PackageBinding pkg = mBinding.getPackage();
- if (pkg != null) {
- return includeSubPackages ?
- startsWithCompound(pkgName, pkg.compoundName) :
- equalsCompound(pkgName, pkg.compoundName);
- }
- return false;
- }
-
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- EcjResolvedClass that = (EcjResolvedClass) o;
-
- if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return mBinding != null ? mBinding.hashCode() : 0;
- }
- }
-
- // "package-info" as a char
- private static final char[] PACKAGE_INFO_CHARS = new char[] {
- 'p', 'a', 'c', 'k', 'a', 'g', 'e', '-', 'i', 'n', 'f', 'o'
- };
-
- private class EcjResolvedPackage extends ResolvedPackage {
- private final PackageBinding mBinding;
-
- public EcjResolvedPackage(PackageBinding binding) {
- mBinding = binding;
- }
-
- @NonNull
- @Override
- public String getName() {
- return new String(mBinding.readableName());
- }
-
- @Override
- public String getSignature() {
- return getName();
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(2);
-
- AnnotationBinding[] annotations = mBinding.getAnnotations();
- int count = annotations.length;
- if (count == 0) {
- Binding pkgInfo = mBinding.getTypeOrPackage(PACKAGE_INFO_CHARS);
- if (pkgInfo != null) {
- annotations = pkgInfo.getAnnotations();
- }
- count = annotations.length;
- }
- if (count > 0) {
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- all.add(new EcjResolvedAnnotation(annotation));
- }
- }
- }
-
- // Merge external annotations
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
- Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
- if (external != null) {
- all.addAll(external);
- }
-
- return all;
- }
-
- @Override
- public int getModifiers() {
- return 0;
- }
- }
-
- private class EcjResolvedField extends ResolvedField {
- private FieldBinding mBinding;
-
- private EcjResolvedField(FieldBinding binding) {
- mBinding = binding;
- }
-
- @NonNull
- @Override
- public String getName() {
- return new String(mBinding.readableName());
- }
-
- @Override
- public boolean matches(@NonNull String name) {
- return sameChars(name, mBinding.readableName());
- }
-
- @NonNull
- @Override
- public TypeDescriptor getType() {
- TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
- assert typeDescriptor != null; // because mBinding.type is known not to be null
- return typeDescriptor;
- }
-
- @NonNull
- @Override
- public ResolvedClass getContainingClass() {
- return new EcjResolvedClass(mBinding.declaringClass);
- }
-
- @Nullable
- @Override
- public Object getValue() {
- return getConstantValue(mBinding.constant());
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- List<ResolvedAnnotation> compiled = null;
- AnnotationBinding[] annotations = mBinding.getAnnotations();
- int count = annotations.length;
- if (count > 0) {
- compiled = Lists.newArrayListWithExpectedSize(count);
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- compiled.add(new EcjResolvedAnnotation(annotation));
- }
- }
- }
-
- // Look for external annotations
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
- Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
-
- return merge(compiled, external);
- }
-
- @Override
- public int getModifiers() {
- return mBinding.getAccessFlags();
- }
-
- @Override
- public String getSignature() {
- return mBinding.toString();
- }
-
- @Override
- public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
- PackageBinding pkg = mBinding.declaringClass.getPackage();
- if (pkg != null) {
- return includeSubPackages ?
- startsWithCompound(pkgName, pkg.compoundName) :
- equalsCompound(pkgName, pkg.compoundName);
- }
- return false;
- }
-
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- EcjResolvedField that = (EcjResolvedField) o;
-
- if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return mBinding != null ? mBinding.hashCode() : 0;
- }
- }
-
- private class EcjResolvedVariable extends ResolvedVariable {
- private LocalVariableBinding mBinding;
-
- private EcjResolvedVariable(LocalVariableBinding binding) {
- mBinding = binding;
- }
-
- @NonNull
- @Override
- public String getName() {
- return new String(mBinding.readableName());
- }
-
- @Override
- public boolean matches(@NonNull String name) {
- return sameChars(name, mBinding.readableName());
- }
-
- @NonNull
- @Override
- public TypeDescriptor getType() {
- TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
- assert typeDescriptor != null; // because mBinding.type is known not to be null
- return typeDescriptor;
- }
-
- @Override
- public int getModifiers() {
- return mBinding.modifiers;
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- AnnotationBinding[] annotations = mBinding.getAnnotations();
- int count = annotations.length;
- if (count > 0) {
- List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(count);
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- result.add(new EcjResolvedAnnotation(annotation));
- }
- }
- return result;
- }
-
- // No external annotations for variables
-
- return Collections.emptyList();
- }
-
- @Override
- public String getSignature() {
- return mBinding.toString();
- }
-
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- EcjResolvedVariable that = (EcjResolvedVariable) o;
-
- if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return mBinding != null ? mBinding.hashCode() : 0;
- }
- }
-
- private class EcjResolvedAnnotation extends ResolvedAnnotation {
- private AnnotationBinding mBinding;
-
- private EcjResolvedAnnotation(@NonNull final AnnotationBinding binding) {
- mBinding = binding;
- }
-
- @NonNull
- @Override
- public String getName() {
- return new String(mBinding.getAnnotationType().readableName());
- }
-
- @Override
- public boolean matches(@NonNull String name) {
- return sameChars(name, mBinding.getAnnotationType().readableName());
- }
-
- @NonNull
- @Override
- public TypeDescriptor getType() {
- TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.getAnnotationType());
- assert typeDescriptor != null; // because mBinding.type is known not to be null
- return typeDescriptor;
- }
-
- @Override
- public ResolvedClass getClassType() {
- ReferenceBinding annotationType = mBinding.getAnnotationType();
- return new EcjResolvedClass(annotationType) {
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- AnnotationBinding[] annotations = mBinding.getAnnotations();
- int count = annotations.length;
- if (count > 0) {
- List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(count);
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- // Special case: If you look up the annotations *on* annotations,
- // you're probably working with the typedef annotations, @IntDef
- // and @StringDef. For these, we can't use the normal annotation
- // handling, because the compiler only keeps the values of the
- // constants, not the references to the constants which is what we
- // care about for those annotations. So in this case, construct
- // a special subclass of ResolvedAnnotation: EcjAstAnnotation, where
- // we keep the AST node for the annotation definition such that
- // we can look up the constant references themselves when queries
- // via the annotation's getValue() lookup methods.
- char[] readableName = annotation.getAnnotationType().readableName();
- if (sameChars(INT_DEF_ANNOTATION, readableName)
- || sameChars(STRING_DEF_ANNOTATION, readableName)) {
- TypeDeclaration typeDeclaration = findTypeDeclaration(getName());
- if (typeDeclaration != null && typeDeclaration.annotations != null) {
- Annotation astAnnotation = null;
- for (Annotation a : typeDeclaration.annotations) {
- if (a.resolvedType != null
- && (sameChars(INT_DEF_ANNOTATION, a.resolvedType.readableName()) ||
- sameChars(STRING_DEF_ANNOTATION, a.resolvedType.readableName()))) {
- astAnnotation = a;
- break;
- }
- }
-
- if (astAnnotation != null) {
- result.add(new EcjAstAnnotation(annotation, astAnnotation));
- continue;
- }
- }
- }
-
- result.add(new EcjResolvedAnnotation(annotation));
- }
- }
- return result;
- }
-
- return Collections.emptyList();
- }
-
- };
- }
-
- @NonNull
- @Override
- public List<Value> getValues() {
- ElementValuePair[] pairs = mBinding.getElementValuePairs();
- if (pairs != null && pairs.length > 0) {
- List<Value> values = Lists.newArrayListWithExpectedSize(pairs.length);
- for (ElementValuePair pair : pairs) {
- values.add(new Value(new String(pair.getName()), getPairValue(pair)));
- }
- }
-
- return Collections.emptyList();
- }
-
- @Nullable
- @Override
- public Object getValue(@NonNull String name) {
- ElementValuePair[] pairs = mBinding.getElementValuePairs();
- if (pairs != null) {
- for (ElementValuePair pair : pairs) {
- if (sameChars(name, pair.getName())) {
- return getPairValue(pair);
- }
- }
- }
-
- return null;
- }
-
- private Object getPairValue(ElementValuePair pair) {
- return getConstantValue(pair.getValue());
- }
-
- @Override
- public String getSignature() {
- return new String(mBinding.getAnnotationType().readableName());
- }
-
- @Override
- public int getModifiers() {
- // Not applicable; move from ResolvedNode into ones that matter?
- return 0;
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- List<ResolvedAnnotation> compiled = null;
- AnnotationBinding[] annotations = mBinding.getAnnotationType().getAnnotations();
- int count = annotations.length;
- if (count > 0) {
- compiled = Lists.newArrayListWithExpectedSize(count);
- for (AnnotationBinding annotation : annotations) {
- if (annotation != null) {
- compiled.add(new EcjResolvedAnnotation(annotation));
- }
- }
- }
-
- // Look for external annotations
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
- Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
-
- return merge(compiled, external);
- }
-
- private class EcjAstAnnotation extends EcjResolvedAnnotation {
-
- private final Annotation mAstAnnotation;
- private List<Value> mValues;
-
- public EcjAstAnnotation(
- @NonNull AnnotationBinding binding, @NonNull Annotation astAnnotation) {
- super(binding);
- mAstAnnotation = astAnnotation;
- }
-
- @NonNull
- @Override
- public List<Value> getValues() {
- if (mValues == null) {
- MemberValuePair[] memberValuePairs = mAstAnnotation.memberValuePairs();
- List<Value> result = Lists
- .newArrayListWithExpectedSize(memberValuePairs.length);
-
- for (MemberValuePair pair : memberValuePairs) {
- // String n = new String(pair.name);
- Expression expression = pair.value;
- Object value = null;
- if (expression instanceof ArrayInitializer) {
- ArrayInitializer initializer = (ArrayInitializer) expression;
- Expression[] expressions = initializer.expressions;
- List<Object> values = Lists.newArrayList();
- for (Expression e : expressions) {
- if (e instanceof NameReference) {
- ResolvedNode resolved = resolve(((NameReference) e).binding);
- if (resolved != null) {
- values.add(resolved);
- }
- } else if (e instanceof IntLiteral) {
- values.add(((IntLiteral) e).value);
- } else if (e instanceof StringLiteral) {
- values.add(String.valueOf(((StringLiteral) e).source()));
- } else {
- values.add(e.toString());
- }
- }
- value = values.toArray();
- } else if (expression instanceof IntLiteral) {
- IntLiteral intLiteral = (IntLiteral) expression;
- value = intLiteral.value;
- } else if (expression instanceof TrueLiteral) {
- value = true;
- } else if (expression instanceof FalseLiteral) {
- value = false;
- } else if (expression instanceof StringLiteral) {
- value = String.valueOf(((StringLiteral) expression).source());
- }
- // Unfortunately, FloatLiteral, LongLiteral etc do not
- // expose the value field as public. Luckily, we don't need that
- // for our current annotations.
-
- result.add(new Value(new String(pair.name), value));
- }
- mValues = result;
- }
-
- return mValues;
- }
-
- @Nullable
- @Override
- public Object getValue(@NonNull String name) {
- for (Value value : getValues()) {
- if (name.equals(value.name)) {
- return value.value;
- }
- }
- return null;
- }
- }
-
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- EcjResolvedAnnotation that = (EcjResolvedAnnotation) o;
-
- if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return mBinding != null ? mBinding.hashCode() : 0;
- }
- }
-
- @Nullable
- private Object getConstantValue(@Nullable Object value) {
- if (value instanceof Constant) {
- if (value == Constant.NotAConstant) {
- return null;
- }
- if (value instanceof StringConstant) {
- return ((StringConstant) value).stringValue();
- } else if (value instanceof IntConstant) {
- return ((IntConstant) value).intValue();
- } else if (value instanceof BooleanConstant) {
- return ((BooleanConstant) value).booleanValue();
- } else if (value instanceof FloatConstant) {
- return ((FloatConstant) value).floatValue();
- } else if (value instanceof LongConstant) {
- return ((LongConstant) value).longValue();
- } else if (value instanceof DoubleConstant) {
- return ((DoubleConstant) value).doubleValue();
- } else if (value instanceof ShortConstant) {
- return ((ShortConstant) value).shortValue();
- } else if (value instanceof CharConstant) {
- return ((CharConstant) value).charValue();
- } else if (value instanceof ByteConstant) {
- return ((ByteConstant) value).byteValue();
- }
- } else if (value instanceof Object[]) {
- Object[] array = (Object[]) value;
- if (array.length > 0) {
- List<Object> list = Lists.newArrayListWithExpectedSize(array.length);
- for (Object element : array) {
- list.add(getConstantValue(element));
- }
- // Pick type of array. Annotations are limited to Strings, Classes
- // and Annotations
- if (!list.isEmpty()) {
- Object first = list.get(0);
- if (first instanceof String) {
- //noinspection SuspiciousToArrayCall
- return list.toArray(new String[list.size()]);
- } else if (first instanceof java.lang.annotation.Annotation) {
- //noinspection SuspiciousToArrayCall
- return list.toArray(new Annotation[list.size()]);
- } else if (first instanceof Class) {
- //noinspection SuspiciousToArrayCall
- return list.toArray(new Class[list.size()]);
- }
- }
-
- return list.toArray();
- }
- } else if (value instanceof AnnotationBinding) {
- return new EcjResolvedAnnotation((AnnotationBinding) value);
- }
-
- return value;
- }
-
- private static boolean sameChars(String str, char[] chars) {
- int length = str.length();
- if (chars.length != length) {
- return false;
- }
-
- for (int i = 0; i < length; i++) {
- if (chars[i] != str.charAt(i)) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Does the given compound name match the given string?
- * <p>
- * TODO: Check if ECJ already has this as a utility somewhere
- */
- @VisibleForTesting
- static boolean startsWithCompound(@NonNull String name, @NonNull char[][] compoundName) {
- int length = name.length();
- if (length == 0) {
- return false;
- }
- int index = 0;
- for (int i = 0, n = compoundName.length; i < n; i++) {
- char[] o = compoundName[i];
- for (int j = 0, m = o.length; j < m; j++) {
- if (index == length) {
- return false; // Don't allow prefix in a compound name
- }
- if (name.charAt(index) != o[j]) {
- return false;
- }
- index++;
- }
- if (i < n - 1) {
- if (index == length) {
- return true;
- }
- if (name.charAt(index) != '.') {
- return false;
- }
- index++;
- if (index == length) {
- return true;
- }
- }
- }
-
- return index == length;
- }
-
- @VisibleForTesting
- static boolean equalsCompound(@NonNull String name, @NonNull char[][] compoundName) {
- int length = name.length();
- if (length == 0) {
- return false;
- }
- int index = 0;
- for (int i = 0, n = compoundName.length; i < n; i++) {
- char[] o = compoundName[i];
- for (int j = 0, m = o.length; j < m; j++) {
- if (index == length) {
- return false; // Don't allow prefix in a compound name
- }
- if (name.charAt(index) != o[j]) {
- return false;
- }
- index++;
- }
- if (i < n - 1) {
- if (index == length) {
- return false;
- }
- if (name.charAt(index) != '.') {
- return false;
- }
- index++;
- if (index == length) {
- return false;
- }
- }
- }
-
- return index == length;
- }
-}
\ No newline at end of file
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java b/base/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java
deleted file mode 100644
index 0cf2eff..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java
+++ /dev/null
@@ -1,1170 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.DOT_JAR;
-import static com.android.SdkConstants.FN_ANNOTATIONS_ZIP;
-import static com.android.SdkConstants.VALUE_FALSE;
-import static com.android.SdkConstants.VALUE_TRUE;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.client.api.JavaParser.DefaultTypeDescriptor;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation.Value;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.JavaParser.ResolvedField;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.ResolvedPackage;
-import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Project;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Charsets;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.jar.JarInputStream;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-
-/**
- * Handler for IntelliJ database files for external annotations.
- * It can be pointed to an annotations .jar file, which it then reads,
- * and can return {@link ResolvedAnnotation} instances when queried
- * for annotations on a {@link ResolvedClass} or a {@link ResolvedMethod},
- * including its parameters.
- */
-public class ExternalAnnotationRepository {
- public static final String SDK_ANNOTATIONS_PATH = "platform-tools/api/annotations.zip"; //$NON-NLS-1$
- public static final String FN_ANNOTATIONS_XML = "annotations.xml"; //$NON-NLS-1$
-
- private static final boolean DEBUG = false;
-
- private static ExternalAnnotationRepository sSingleton;
-
- private final List<AnnotationsDatabase> mDatabases;
-
- private ExternalAnnotationRepository(@NonNull List<AnnotationsDatabase> databases) {
- mDatabases = databases;
- }
-
- @NonNull
- public static synchronized ExternalAnnotationRepository get(@NonNull LintClient client) {
- if (sSingleton == null) {
- HashSet<AndroidLibrary> seen = Sets.newHashSet();
- Collection<Project> projects = client.getKnownProjects();
- List<File> files = Lists.newArrayListWithExpectedSize(2);
- for (Project project : projects) {
- if (project.isGradleProject()) {
- Variant variant = project.getCurrentVariant();
- AndroidProject model = project.getGradleProjectModel();
- if (model != null && variant != null) {
- Dependencies dependencies = variant.getMainArtifact().getDependencies();
- for (AndroidLibrary library : dependencies.getLibraries()) {
- addLibraries(files, library, seen);
- }
- }
- }
- }
-
- File sdkAnnotations = client.findResource(SDK_ANNOTATIONS_PATH);
- if (sdkAnnotations == null) {
- // Until the SDK annotations are bundled in platform tools, provide
- // a fallback for Gradle builds to point to a locally installed version
- String path = System.getenv("SDK_ANNOTATIONS");
- if (path != null) {
- sdkAnnotations = new File(path);
- if (!sdkAnnotations.exists()) {
- sdkAnnotations = null;
- }
- }
- }
- if (sdkAnnotations != null) {
- files.add(sdkAnnotations);
- }
-
- sSingleton = create(client, files);
- }
-
- return sSingleton;
- }
-
- @VisibleForTesting
- @NonNull
- static synchronized ExternalAnnotationRepository create(
- @Nullable LintClient client,
- @NonNull List<File> files) {
- long begin;
- if (DEBUG) {
- begin = System.currentTimeMillis();
- }
-
- List<AnnotationsDatabase> databases = Lists.newArrayListWithExpectedSize(files.size());
- for (File file : files) {
- try {
- AnnotationsDatabase database = getDatabase(file);
- if (database != null) {
- databases.add(database);
- }
- } catch (IOException ioe) {
- if (client != null) {
- client.log(ioe, "Could not read %1$s", file.getPath());
- } else {
- ioe.printStackTrace();
- }
- }
- }
-
- ExternalAnnotationRepository manager = new ExternalAnnotationRepository(databases);
-
- if (DEBUG) {
- long end = System.currentTimeMillis();
- System.out.println("Initialization of annotations took " + (end - begin) + " ms");
- }
-
- return manager;
- }
-
- private static void addLibraries(
- @NonNull List<File> result,
- @NonNull AndroidLibrary library,
- Set<AndroidLibrary> seen) {
- if (seen.contains(library)) {
- return;
- }
- seen.add(library);
-
- // As of 1.2 this is available in the model:
- // https://android-review.googlesource.com/#/c/137750/
- // Switch over to this when it's in more common usage
- // (until it is, we'll pay for failed proxying errors)
- File zip = new File(library.getResFolder().getParent(), FN_ANNOTATIONS_ZIP);
- if (zip.exists()) {
- result.add(zip);
- }
-
- for (AndroidLibrary dependency : library.getLibraryDependencies()) {
- addLibraries(result, dependency, seen);
- }
- }
-
- @Nullable
- private static AnnotationsDatabase getDatabase(
- @NonNull LintClient client,
- @NonNull File file) {
- try {
- return file.isFile() ? new AnnotationsDatabase(file) : null;
- } catch (IOException ioe) {
- client.log(ioe, "Could not read %1$s", file.getPath());
- return null;
- }
- }
-
- @VisibleForTesting
- @Nullable
- static AnnotationsDatabase getDatabase(@NonNull File file) throws IOException {
- return file.exists() ? new AnnotationsDatabase(file) : null;
- }
-
- @Nullable
- private static AnnotationsDatabase getDatabase(
- @NonNull LintClient client,
- @NonNull AndroidLibrary library) {
- // As of 1.2 this is available in the model:
- // https://android-review.googlesource.com/#/c/137750/
- // Switch over to this when it's in more common usage
- // (until it is, we'll pay for failed proxying errors)
- File zip = new File(library.getResFolder().getParent(), FN_ANNOTATIONS_ZIP);
- return getDatabase(client, zip);
- }
-
- // ---- Query methods ----
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method, @NonNull String type) {
- for (AnnotationsDatabase database : mDatabases) {
- ResolvedAnnotation annotation = database.getAnnotation(method, type);
- if (annotation != null) {
- return annotation;
- }
- }
-
- return null;
- }
-
- @Nullable
- public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedMethod method) {
- for (AnnotationsDatabase database : mDatabases) {
- Collection<ResolvedAnnotation> annotations = database.getAnnotations(method);
- if (annotations != null) {
- return annotations;
- }
- }
-
- return null;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method,
- int parameterIndex, @NonNull String type) {
- for (AnnotationsDatabase database : mDatabases) {
- ResolvedAnnotation annotation = database.getAnnotation(method, parameterIndex, type);
- if (annotation != null) {
- return annotation;
- }
- }
-
- return null;
- }
-
- @Nullable
- public Collection<ResolvedAnnotation> getAnnotations(
- @NonNull ResolvedMethod method,
- int parameterIndex) {
- for (AnnotationsDatabase database : mDatabases) {
- Collection<ResolvedAnnotation> annotations = database.getAnnotations(method,
- parameterIndex);
- if (annotations != null) {
- return annotations;
- }
- }
-
- return null;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedClass cls, @NonNull String type) {
- for (AnnotationsDatabase database : mDatabases) {
- ResolvedAnnotation annotation = database.getAnnotation(cls, type);
- if (annotation != null) {
- return annotation;
- }
- }
-
- return null;
- }
-
- @Nullable
- public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedClass cls) {
- for (AnnotationsDatabase database : mDatabases) {
- Collection<ResolvedAnnotation> annotations = database.getAnnotations(cls);
- if (annotations != null) {
- return annotations;
- }
- }
-
- return null;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedField field, @NonNull String type) {
- for (AnnotationsDatabase database : mDatabases) {
- ResolvedAnnotation annotation = database.getAnnotation(field, type);
- if (annotation != null) {
- return annotation;
- }
- }
-
- return null;
- }
-
- @Nullable
- public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedField field) {
- for (AnnotationsDatabase database : mDatabases) {
- Collection<ResolvedAnnotation> annotations = database.getAnnotations(field);
- if (annotations != null) {
- return annotations;
- }
- }
-
- return null;
- }
-
- @Nullable
- public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedAnnotation cls) {
- for (AnnotationsDatabase database : mDatabases) {
- Collection<ResolvedAnnotation> annotations = database.getAnnotations(cls);
- if (annotations != null) {
- return annotations;
- }
- }
-
- return null;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedPackage pkg, @NonNull String type) {
- for (AnnotationsDatabase database : mDatabases) {
- ResolvedAnnotation annotation = database.getAnnotation(pkg, type);
- if (annotation != null) {
- return annotation;
- }
- }
-
- return null;
- }
- @Nullable
- public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedPackage pkg) {
- for (AnnotationsDatabase database : mDatabases) {
- Collection<ResolvedAnnotation> annotations = database.getAnnotations(pkg);
- if (annotations != null) {
- return annotations;
- }
- }
-
- return null;
- }
-
- // ---- Reading from storage ----
-
- private static final Pattern XML_SIGNATURE = Pattern.compile(
- // Class (FieldName | Type? Name(ArgList) Argnum?)
- "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)");
-
- /** Map from class fully qualified name to the class annotations info */
- // Query database
- private static class ClassInfo {
- public List<ResolvedAnnotation> annotations;
- public Multimap<String,MethodInfo> methods;
- public Map<String,FieldInfo> fields;
- }
-
- private static class MethodInfo {
- public String parameters;
- public boolean constructor;
- public List<ResolvedAnnotation> annotations;
- public Multimap<Integer,ResolvedAnnotation> parameterAnnotations;
- }
-
- private static class FieldInfo {
- public List<ResolvedAnnotation> annotations;
- }
-
- /** An {@linkplain AnnotationsDatabase} corresponds to a single external annotations .zip
- * file (or if in the dev tree, a corresponding directory tree.
- * <p>
- * The SDK has an annotations database, and AAR libraries can also supply individual databases.
- * The {@linkplain ExternalAnnotationRepository} class manages all of these and performs lookup
- * into the various databases through a single entrypoint.
- * */
- static class AnnotationsDatabase {
- AnnotationsDatabase(@NonNull File file) throws IOException {
- String path = file.getPath();
- if (path.endsWith(DOT_JAR) || path.endsWith(FN_ANNOTATIONS_ZIP)) {
- initializeFromJar(file);
- } else {
- assert file.isDirectory() : file;
- initializeFromDirectory(file);
- }
- }
-
- // ---- Query methods ----
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method,
- @NonNull String type) {
- MethodInfo m = findMethod(method);
- if (m == null) {
- return null;
- }
-
- if (m.annotations != null) {
- for (ResolvedAnnotation annotation : m.annotations) {
- if (type.equals(annotation.getSignature())) {
- return annotation;
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedMethod method) {
- MethodInfo m = findMethod(method);
- if (m == null) {
- return null;
- }
- return m.annotations;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method,
- int parameterIndex, @NonNull String type) {
- MethodInfo m = findMethod(method);
- if (m == null) {
- return null;
- }
-
- if (m.parameterAnnotations != null) {
- Collection<ResolvedAnnotation> annotations = m.parameterAnnotations.get(parameterIndex);
- if (annotations != null) {
- for (ResolvedAnnotation annotation : annotations) {
- if (type.equals(annotation.getSignature())) {
- return annotation;
- }
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- public Collection<ResolvedAnnotation> getAnnotations(
- @NonNull ResolvedMethod method,
- int parameterIndex) {
- MethodInfo m = findMethod(method);
- if (m == null) {
- return null;
- }
-
- if (m.parameterAnnotations != null) {
- return m.parameterAnnotations.get(parameterIndex);
- }
-
- return m.annotations;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedClass cls, @NonNull String type) {
- ClassInfo c = findClass(cls);
- if (c == null) {
- return null;
- }
-
- if (c.annotations != null) {
- for (ResolvedAnnotation annotation : c.annotations) {
- if (type.equals(annotation.getSignature())) {
- return annotation;
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedClass cls) {
- ClassInfo c = findClass(cls);
- if (c == null) {
- return null;
- }
-
- return c.annotations;
- }
-
- @Nullable
- public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedAnnotation cls) {
- ClassInfo c = findClass(cls);
- if (c == null) {
- return null;
- }
-
- return c.annotations;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedPackage pkg, @NonNull String type) {
- ClassInfo c = findPackage(pkg);
-
- if (c == null) {
- return null;
- }
-
- if (c.annotations != null) {
- for (ResolvedAnnotation annotation : c.annotations) {
- if (type.equals(annotation.getSignature())) {
- return annotation;
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedPackage pkg) {
- ClassInfo c = findPackage(pkg);
- if (c == null) {
- return null;
- }
-
- return c.annotations;
- }
-
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull ResolvedField field, @NonNull String type) {
- FieldInfo f = findField(field);
-
- if (f == null) {
- return null;
- }
- if (f.annotations != null) {
- for (ResolvedAnnotation annotation : f.annotations) {
- if (type.equals(annotation.getSignature())) {
- return annotation;
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedField field) {
- FieldInfo f = findField(field);
-
- if (f == null) {
- return null;
- }
- return f.annotations;
- }
-
- // ---- Initialization ----
-
- private void initializeFromDirectory(File file) throws IOException {
- if (file.isDirectory()) {
- File[] files = file.listFiles();
- if (files != null) {
- for (File f : files) {
- initializeFromDirectory(f);
- }
- }
- } else if (file.getPath().endsWith(FN_ANNOTATIONS_XML)) {
- String xml = Files.toString(file, Charsets.UTF_8);
- initializePackage(xml, file.getPath());
- }
- }
-
- private void initializeFromJar(File file) throws IOException {
- // Reads in an existing annotations jar and merges in entries found there
- // with the annotations analyzed from source.
- JarInputStream zis = null;
- try {
- @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
- FileInputStream fis = new FileInputStream(file);
- zis = new JarInputStream(fis);
- ZipEntry entry = zis.getNextEntry();
- while (entry != null) {
- if (entry.getName().endsWith(".xml")) {
- byte[] bytes = ByteStreams.toByteArray(zis);
- String xml = new String(bytes, Charsets.UTF_8);
- initializePackage(xml, entry.getName());
- }
- entry = zis.getNextEntry();
- }
- } finally {
- try {
- Closeables.close(zis, true);
- } catch (IOException e) {
- // pass
- }
- }
- }
-
- /**
- * Takes the XML contents of an annotations.xml file, parses it and initialize
- * the necessary data structures
- */
- private void initializePackage(@NonNull String xml, @NonNull String path)
- throws IOException {
- try {
- Document document = XmlUtils.parseDocument(xml, false);
-
- Element root = document.getDocumentElement();
- String rootTag = root.getTagName();
- assert rootTag.equals("root") : rootTag;
-
- for (Element item : LintUtils.getChildren(root)) {
- String signature = item.getAttribute(ATTR_NAME);
- if (signature == null || signature.equals("null")) {
- continue; // malformed item
- }
-
- signature = XmlUtils.fromXmlAttributeValue(signature);
- Matcher matcher = XML_SIGNATURE.matcher(signature);
- if (matcher.matches()) {
- String containingClass = matcher.group(1);
- if (containingClass == null) {
- throw new IOException("Could not find class for " + signature);
- }
- String methodName = matcher.group(5);
- if (methodName != null) {
- String type = matcher.group(4);
- boolean isConstructor = type == null;
- String parameters = matcher.group(6);
- mergeMethodOrParameter(item, matcher, containingClass, methodName,
- isConstructor, parameters);
- } else {
- String fieldName = matcher.group(2);
- mergeField(item, containingClass, fieldName);
- }
- } else if (signature.indexOf(' ') == -1 && signature.indexOf('.') != -1) {
- mergeClass(item, signature);
- } else {
- throw new IOException("No merge match for signature " + signature);
- }
- }
- } catch (Exception e) {
- throw new IOException("Could not parse XML from " + path);
- }
- }
-
- // SDK annotations
- private Map<String,ClassInfo> mClassMap = Maps.newHashMapWithExpectedSize(800);
-
- @Nullable
- private ClassInfo findClass(@NonNull ResolvedClass cls) {
- return mClassMap.get(cls.getName());
- }
-
- @Nullable
- private ClassInfo findClass(@NonNull ResolvedAnnotation cls) {
- return mClassMap.get(cls.getName());
- }
-
- private ClassInfo findPackage(@NonNull ResolvedPackage pkg) {
- return mClassMap.get(pkg.getName() +".package-info");
- }
-
- @Nullable
- private MethodInfo findMethod(@NonNull ResolvedMethod method) {
- ClassInfo c = findClass(method.getContainingClass());
- if (c == null) {
- return null;
- }
- if (c.methods == null) {
- return null;
- }
- Collection<MethodInfo> methods = c.methods.get(method.getName());
- if (methods == null) {
- return null;
- }
- boolean constructor = method.isConstructor();
- for (MethodInfo m : methods) {
- if (constructor != m.constructor) {
- continue;
- }
- // Check parameter types
- // TODO: Perform faster parameter check! This is inefficient
- // Stash parameter count such that I can quickly compare the two
- String signature = m.parameters;
- int index = 0;
- boolean matches = true;
- for (int i = 0, n = method.getArgumentCount(); i < n; i++) {
- String parameterType = method.getArgumentType(i).getSignature();
- int length = parameterType.indexOf('<');
- if (length == -1) {
- length = parameterType.length();
- }
- if (!signature.regionMatches(false, index, parameterType, 0, length)) {
- // Check if we have a varargs match: x... vs x[]
- if (length <= 3 || index <= 3 || ((parameterType.charAt(length - 1) != '.')
- && (signature.length() < index + length
- || signature.charAt(index + length - 1) != '.'))
- || !isVarArgsMatch(signature, index, parameterType, length)) {
- matches = false;
- break;
- }
- }
- index += length;
- if (i < n - 1) {
- if (index == signature.length()) {
- matches = false;
- break;
- } else if (signature.charAt(index) == '<') {
- // Skip raw types
- int balance = 1;
- for (int j = index + 1, max = signature.length(); j < max; j++) {
- char ch = signature.charAt(j);
- if (ch == '<') {
- balance++;
- } else if (ch == '>') {
- balance--;
- if (balance == 0) {
- index = j + 1;
- break;
- }
- }
- }
- if (balance > 0) {
- matches = false;
- break;
- }
- } else if (signature.charAt(index) != ',') {
- matches = false;
- break;
- }
- }
- index++; // skip comma
- }
-
- if (matches) {
- return m;
- }
- }
-
- return null;
- }
-
- /**
- * Checks whether the string at parameterType(0,length) and signature(index,index+length)
- * are the same, except with one possibly ending with [] and the other with ... - if
- * so these should be taken to match
- */
- private static boolean isVarArgsMatch(String signature, int index, String parameterType,
- int length) {
- return parameterType.regionMatches(false, length - 3, "...", 0, 3) &&
- signature.regionMatches(false, index + length - 3, "[]", 0, 2) &&
- parameterType.regionMatches(false, 0, signature, index, length - 3)
- || parameterType.regionMatches(false, length - 2, "[]", 0, 2) &&
- signature.regionMatches(false, index + length - 2, "...", 0, 3) &&
- parameterType.regionMatches(false, 0, signature, index, length - 2);
- }
-
- @Nullable
- private FieldInfo findField(@NonNull ResolvedField field) {
- ClassInfo c = findClass(field.getContainingClass());
- if (c == null) {
- return null;
- }
- if (c.fields == null) {
- return null;
- }
- return c.fields.get(field.getName());
- }
-
- @NonNull
- private MethodInfo createMethod(@NonNull String containingClass, @NonNull String methodName,
- boolean constructor, @NonNull String parameters) {
- ClassInfo cls = createClass(containingClass);
- if (cls.methods != null) {
- Collection<MethodInfo> methods = cls.methods.get(methodName);
- if (methods != null) {
- for (MethodInfo method : methods) {
- if (parameters.equals(method.parameters)
- && constructor == method.constructor) {
- return method;
- }
- }
- }
- }
-
- MethodInfo method = new MethodInfo();
- method.parameters = parameters;
- method.constructor = constructor;
-
- if (cls.methods == null) {
- cls.methods = ArrayListMultimap.create(); // TODO: Size me
- }
- cls.methods.put(methodName, method);
- return method;
- }
-
- @NonNull
- private ClassInfo createClass(@NonNull String containingClass) {
- ClassInfo cls = mClassMap.get(containingClass);
- if (cls == null) {
- cls = new ClassInfo();
- mClassMap.put(containingClass, cls);
- }
- return cls;
- }
-
- @NonNull
- private FieldInfo createField(@NonNull String containingClass, @NonNull String fieldName) {
- ClassInfo cls = createClass(containingClass);
- if (cls.fields != null) {
- FieldInfo field = cls.fields.get(fieldName);
- if (field != null) {
- return field;
- }
- }
-
- FieldInfo field = new FieldInfo();
- if (cls.fields == null) {
- cls.fields = Maps.newHashMap(); // TODO: Size me
- }
- cls.fields.put(fieldName, field);
- return field;
- }
-
- private void mergeMethodOrParameter(Element item, Matcher matcher, String containingClass,
- String methodName, boolean constructor, String parameters) {
- parameters = fixParameterString(parameters);
-
- MethodInfo method = createMethod(containingClass, methodName, constructor, parameters);
- List<ResolvedAnnotation> annotations = createAnnotations(item);
-
- String argNum = matcher.group(7);
- if (argNum != null) {
- argNum = argNum.trim();
- int parameter = Integer.parseInt(argNum);
-
- if (method.parameterAnnotations == null) {
- // Do I know the parameter count here?
- int parameterCount = 4;
- method.parameterAnnotations = ArrayListMultimap
- .create(parameterCount, annotations.size());
- }
- for (ResolvedAnnotation annotation : annotations) {
- method.parameterAnnotations.put(parameter, annotation);
- }
- } else {
- if (method.annotations == null) {
- method.annotations = Lists.newArrayListWithExpectedSize(annotations.size());
- }
- method.annotations.addAll(annotations);
- }
- }
-
- private void mergeField(Element item, String containingClass, String fieldName) {
- FieldInfo field = createField(containingClass, fieldName);
- List<ResolvedAnnotation> annotations = createAnnotations(item);
- if (field.annotations == null) {
- field.annotations = Lists.newArrayListWithExpectedSize(annotations.size());
- }
- field.annotations.addAll(annotations);
- }
-
- private void mergeClass(Element item, String containingClass) {
- ClassInfo cls = createClass(containingClass);
- List<ResolvedAnnotation> annotations = createAnnotations(item);
- if (cls.annotations == null) {
- cls.annotations = Lists.newArrayListWithExpectedSize(annotations.size());
- }
- cls.annotations.addAll(annotations);
- }
-
- private List<ResolvedAnnotation> createAnnotations(Element itemElement) {
- List<Element> children = getChildren(itemElement);
- List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(children.size());
- for (Element annotationElement : children) {
- ResolvedAnnotation annotation = createAnnotation(annotationElement);
- result.add(annotation);
- }
-
- return result;
- }
-
- private static class ResolvedExternalAnnotation extends ResolvedAnnotation {
-
- @NonNull
- private String mSignature;
-
- @Nullable
- private List<Value> mValues;
-
- public ResolvedExternalAnnotation(@NonNull String signature) {
- mSignature = signature;
- }
-
- void addValue(@NonNull Value value) {
- if (mValues == null) {
- mValues = Lists.newArrayList();
- }
- mValues.add(value);
- }
-
- @NonNull
- @Override
- public String getName() {
- return mSignature;
- }
-
- @NonNull
- @Override
- public String getSignature() {
- return mSignature;
- }
-
- @Override
- public int getModifiers() {
- return Modifier.PUBLIC;
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- return Collections.emptyList();
- }
-
- @Override
- public boolean matches(@NonNull String name) {
- return mSignature.equals(name);
- }
-
- @NonNull
- @Override
- public TypeDescriptor getType() {
- return new DefaultTypeDescriptor(mSignature);
- }
-
- @Nullable
- @Override
- public ResolvedClass getClassType() {
- // No nested annotations in the database
- return null;
- }
-
- @NonNull
- @Override
- public List<Value> getValues() {
- return mValues == null ? Collections.<Value>emptyList() : mValues;
- }
- }
-
- private Map<String, ResolvedExternalAnnotation> mMarkerAnnotations = Maps.newHashMapWithExpectedSize(30);
-
- private ResolvedAnnotation createAnnotation(Element annotationElement) {
- String tagName = annotationElement.getTagName();
- assert tagName.equals("annotation") : tagName;
- String name = annotationElement.getAttribute(ATTR_NAME);
- assert name != null && !name.isEmpty();
-
- ResolvedExternalAnnotation annotation = mMarkerAnnotations.get(name);
- if (annotation != null) {
- return annotation;
- }
-
- annotation = new ResolvedExternalAnnotation(name);
-
- List<Element> valueElements = getChildren(annotationElement);
- if (valueElements.isEmpty()
- // Permission annotations are sometimes used as marker annotations (on
- // parameters) but that shouldn't let us conclude that any future
- // permission annotations are
- && !name.startsWith(PERMISSION_ANNOTATION)) {
- mMarkerAnnotations.put(name, annotation);
- return annotation;
- }
-
- for (Element valueElement : valueElements) {
- if (valueElement.getTagName().equals("val")) {
- String valueName = valueElement.getAttribute(ATTR_NAME);
- String valueString = valueElement.getAttribute("val");
- if (!valueName.isEmpty() && !valueString.isEmpty()) {
- // Guess type
- Object value;
- if (valueString.equals(VALUE_TRUE)) {
- value = true;
- } else if (valueString.equals(VALUE_FALSE)) {
- value = false;
- } else if (valueString.startsWith("\"") && valueString.endsWith("\"") &&
- valueString.length() >= 2) {
- value = valueString.substring(1, valueString.length() - 1);
- } else if (valueString.startsWith("{") && valueString.endsWith("}")) {
- // Array of values
- String listString = valueString.substring(1, valueString.length() - 1);
- // We don't know the types, but we'll assume that they're either
- // all strings (the most common array type in our annotations), or
- // field references. We can't know the types of the fields; it's
- // not part of the annotation metadata. We'll place them in an Object[]
- // for now.
- boolean allStrings = true;
- Splitter splitter = Splitter.on(',').omitEmptyStrings().trimResults();
- List<Object> result = Lists.newArrayList();
- for (String reference : splitter.split(listString)) {
- if (reference.startsWith("\"")) {
- result.add(reference.substring(1, reference.length() - 1));
- } else {
- result.add(new ResolvedExternalField(reference));
- allStrings = false;
- }
- }
- if (allStrings) {
- value = result.toArray(new String[result.size()]);
- } else {
- value = result.toArray();
- }
-
- // We don't know the actual type of these fields; we'll assume they're
- // a special form of
- } else if (Character.isDigit(valueString.charAt(0))) {
- try {
- if (valueString.contains(".")) {
- value = Double.parseDouble(valueString);
- } else {
- value = Long.parseLong(valueString);
- }
- } catch (NumberFormatException nufe) {
- value = valueString;
- }
- } else {
- value = valueString; // unknown type
- }
- annotation.addValue(new Value(valueName, value));
- }
- }
- }
-
- return annotation;
- }
- }
-
- /** Special implementation of a {@link ResolvedField} which can
- * do equality comparisons with {@link EcjParser.EcjResolvedField} */
- private static class ResolvedExternalField extends ResolvedField {
- private final String mSignature;
-
- public ResolvedExternalField(String signature) {
- mSignature = signature;
- assert mSignature.indexOf(' ') == -1 : '"' + mSignature + '"';
- }
-
- @NonNull
- @Override
- public String getName() {
- return mSignature.substring(mSignature.lastIndexOf('.') + 1);
- }
-
- @Override
- public String getSignature() {
- return mSignature;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof ResolvedExternalField) {
- return mSignature.equals(((ResolvedExternalField)obj).mSignature);
- } else if (obj instanceof ResolvedField) {
- ResolvedField field = (ResolvedField)obj;
- if (mSignature.endsWith(field.getName())) {
- String signature = field.getContainingClass().getSignature() +
- "." + field.getName();
- return mSignature.equals(signature);
- }
- return false;
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- return mSignature.hashCode();
- }
-
- @Override
- public int getModifiers() {
- return 0;
- }
-
- @Override
- public boolean matches(@NonNull String name) {
- return mSignature.equals(name);
- }
-
- @NonNull
- @Override
- public TypeDescriptor getType() {
- return new DefaultTypeDescriptor(mSignature);
- }
-
- @NonNull
- @Override
- public ResolvedClass getContainingClass() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getContainingClassName() {
- return mSignature.substring(0, mSignature.lastIndexOf('.'));
- }
-
- @Nullable
- @Override
- public Object getValue() {
- return null;
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- return Collections.emptyList();
- }
- }
-
- @NonNull
- private static List<Element> getChildren(@NonNull Element element) {
- NodeList itemList = element.getChildNodes();
- int length = itemList.getLength();
- if (length == 0) {
- return Collections.emptyList();
- }
- List<Element> result = new ArrayList<Element>(Math.max(5, length / 2 + 1));
- for (int i = 0; i < length; i++) {
- Node node = itemList.item(i);
- if (node.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
-
- result.add((Element) node);
- }
-
- return result;
- }
-
- // The parameter declaration used in XML files should not have duplicated spaces,
- // and there should be no space after commas (we can't however strip out all spaces,
- // since for example the spaces around the "extends" keyword needs to be there in
- // types like Map<String,? extends Number>
- private static String fixParameterString(String parameters) {
- return parameters.replace(" ", " ").replace(", ", ",");
- }
-
- /** For test usage only */
- @VisibleForTesting
- static synchronized void set(ExternalAnnotationRepository singleton) {
- assert singleton == null || sSingleton == null;
- sSingleton = singleton;
- }
-}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java b/base/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
deleted file mode 100644
index c71b5a0..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
+++ /dev/null
@@ -1,827 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.SdkConstants.DOT_JPG;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-import static com.android.tools.lint.detector.api.TextFormat.HTML;
-import static com.android.tools.lint.detector.api.TextFormat.RAW;
-
-import com.android.tools.lint.checks.BuiltinIssueRegistry;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Position;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.utils.SdkUtils;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Maps;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A reporter which emits lint results into an HTML report.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class HtmlReporter extends Reporter {
- private static final boolean USE_HOLO_STYLE = true;
- @SuppressWarnings("ConstantConditions")
- private static final String CSS = USE_HOLO_STYLE
- ? "hololike.css" : "default.css"; //$NON-NLS-1$ //$NON-NLS-2$
-
- /**
- * Maximum number of warnings allowed for a single issue type before we
- * split up and hide all but the first {@link #SHOWN_COUNT} items.
- */
- private static final int SPLIT_LIMIT = 8;
- /**
- * When a warning has at least {@link #SPLIT_LIMIT} items, then we show the
- * following number of items before the "Show more" button/link.
- */
- private static final int SHOWN_COUNT = SPLIT_LIMIT - 3;
-
- protected final Writer mWriter;
- private String mStripPrefix;
- private String mFixUrl;
-
- /**
- * Creates a new {@link HtmlReporter}
- *
- * @param client the associated client
- * @param output the output file
- * @throws IOException if an error occurs
- */
- public HtmlReporter(LintCliClient client, File output) throws IOException {
- super(client, output);
- mWriter = new BufferedWriter(Files.newWriter(output, Charsets.UTF_8));
- }
-
- @Override
- public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException {
- Map<Issue, String> missing = computeMissingIssues(issues);
-
- mWriter.write(
- "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + //$NON-NLS-1$
- "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + //$NON-NLS-1$
- "<head>\n" + //$NON-NLS-1$
- "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" + //$NON-NLS-1$
- "<title>" + mTitle + "</title>\n"); //$NON-NLS-1$//$NON-NLS-2$
-
- writeStyleSheet();
-
- if (!mSimpleFormat) {
- // JavaScript for collapsing/expanding long lists
- mWriter.write(
- "<script language=\"javascript\" type=\"text/javascript\"> \n" + //$NON-NLS-1$
- "<!--\n" + //$NON-NLS-1$
- "function reveal(id) {\n" + //$NON-NLS-1$
- "if (document.getElementById) {\n" + //$NON-NLS-1$
- "document.getElementById(id).style.display = 'block';\n" + //$NON-NLS-1$
- "document.getElementById(id+'Link').style.display = 'none';\n" + //$NON-NLS-1$
- "}\n" + //$NON-NLS-1$
- "}\n" + //$NON-NLS-1$
- "//--> \n" + //$NON-NLS-1$
- "</script>\n"); //$NON-NLS-1$
- }
-
- mWriter.write(
- "</head>\n" + //$NON-NLS-1$
- "<body>\n" + //$NON-NLS-1$
- "<h1>" + //$NON-NLS-1$
- mTitle +
- "</h1>\n" + //$NON-NLS-1$
- "<div class=\"titleSeparator\"></div>\n"); //$NON-NLS-1$
-
- mWriter.write(String.format("Check performed at %1$s.",
- new Date().toString()));
- mWriter.write("<br/>\n"); //$NON-NLS-1$
- mWriter.write(String.format("%1$d errors and %2$d warnings found:",
- errorCount, warningCount));
- mWriter.write("<br/><br/>\n"); //$NON-NLS-1$
-
- Issue previousIssue = null;
- if (!issues.isEmpty()) {
- List<List<Warning>> related = new ArrayList<List<Warning>>();
- List<Warning> currentList = null;
- for (Warning warning : issues) {
- if (warning.issue != previousIssue) {
- previousIssue = warning.issue;
- currentList = new ArrayList<Warning>();
- related.add(currentList);
- }
- assert currentList != null;
- currentList.add(warning);
- }
-
- writeOverview(related, missing.size());
-
- Category previousCategory = null;
- for (List<Warning> warnings : related) {
- Warning first = warnings.get(0);
- Issue issue = first.issue;
-
- if (issue.getCategory() != previousCategory) {
- previousCategory = issue.getCategory();
- mWriter.write("\n<a name=\""); //$NON-NLS-1$
- mWriter.write(issue.getCategory().getFullName());
- mWriter.write("\"></a>\n"); //$NON-NLS-1$
- mWriter.write("<div class=\"category\"><a href=\"#\" title=\"Return to top\">"); //$NON-NLS-1$
- mWriter.write(issue.getCategory().getFullName());
- mWriter.write("</a><div class=\"categorySeparator\"></div>\n");//$NON-NLS-1$
- mWriter.write("</div>\n"); //$NON-NLS-1$
- }
-
- mWriter.write("<a name=\"" + issue.getId() + "\"></a>\n"); //$NON-NLS-1$ //$NON-NLS-2$
- mWriter.write("<div class=\"issue\">\n"); //$NON-NLS-1$
-
- // Explain this issue
- mWriter.write("<div class=\"id\"><a href=\"#\" title=\"Return to top\">"); //$NON-NLS-1$
- mWriter.write(issue.getId());
- mWriter.write(": "); //$NON-NLS-1$
- mWriter.write(issue.getBriefDescription(HTML));
- mWriter.write("</a><div class=\"issueSeparator\"></div>\n"); //$NON-NLS-1$
- mWriter.write("</div>\n"); //$NON-NLS-1$
-
- mWriter.write("<div class=\"warningslist\">\n"); //$NON-NLS-1$
- boolean partialHide = !mSimpleFormat && warnings.size() > SPLIT_LIMIT;
-
- int count = 0;
- for (Warning warning : warnings) {
- if (partialHide && count == SHOWN_COUNT) {
- String id = warning.issue.getId() + "Div"; //$NON-NLS-1$
- mWriter.write("<button id=\""); //$NON-NLS-1$
- mWriter.write(id);
- mWriter.write("Link\" onclick=\"reveal('"); //$NON-NLS-1$
- mWriter.write(id);
- mWriter.write("');\" />"); //$NON-NLS-1$
- mWriter.write(String.format("+ %1$d More Occurrences...",
- warnings.size() - SHOWN_COUNT));
- mWriter.write("</button>\n"); //$NON-NLS-1$
- mWriter.write("<div id=\""); //$NON-NLS-1$
- mWriter.write(id);
- mWriter.write("\" style=\"display: none\">\n"); //$NON-NLS-1$
- }
- count++;
- String url = null;
- if (warning.path != null) {
- url = writeLocation(warning.file, warning.path, warning.line);
- mWriter.write(':');
- mWriter.write(' ');
- }
-
- // Is the URL for a single image? If so, place it here near the top
- // of the error floating on the right. If there are multiple images,
- // they will instead be placed in a horizontal box below the error
- boolean addedImage = false;
- if (url != null && warning.location != null
- && warning.location.getSecondary() == null) {
- addedImage = addImage(url, warning.location);
- }
- mWriter.write("<span class=\"message\">"); //$NON-NLS-1$
- mWriter.append(RAW.convertTo(warning.message, HTML));
- mWriter.write("</span>"); //$NON-NLS-1$
- if (addedImage) {
- mWriter.write("<br clear=\"right\"/>"); //$NON-NLS-1$
- } else {
- mWriter.write("<br />"); //$NON-NLS-1$
- }
-
- // Insert surrounding code block window
- if (warning.line >= 0 && warning.fileContents != null) {
- mWriter.write("<pre class=\"errorlines\">\n"); //$NON-NLS-1$
- appendCodeBlock(warning.fileContents, warning.line, warning.offset);
- mWriter.write("\n</pre>"); //$NON-NLS-1$
- }
- mWriter.write('\n');
- if (warning.location != null && warning.location.getSecondary() != null) {
- mWriter.write("<ul>");
- Location l = warning.location.getSecondary();
- int otherLocations = 0;
- while (l != null) {
- String message = l.getMessage();
- if (message != null && !message.isEmpty()) {
- Position start = l.getStart();
- int line = start != null ? start.getLine() : -1;
- String path = mClient.getDisplayPath(warning.project, l.getFile());
- writeLocation(l.getFile(), path, line);
- mWriter.write(':');
- mWriter.write(' ');
- mWriter.write("<span class=\"message\">"); //$NON-NLS-1$
- mWriter.append(RAW.convertTo(message, HTML));
- mWriter.write("</span>"); //$NON-NLS-1$
- mWriter.write("<br />"); //$NON-NLS-1$
-
- String name = l.getFile().getName();
- if (!(endsWith(name, DOT_PNG) || endsWith(name, DOT_JPG))) {
- String s = mClient.readFile(l.getFile());
- if (s != null && !s.isEmpty()) {
- mWriter.write("<pre class=\"errorlines\">\n"); //$NON-NLS-1$
- int offset = start != null ? start.getOffset() : -1;
- appendCodeBlock(s, line, offset);
- mWriter.write("\n</pre>"); //$NON-NLS-1$
- }
- }
- } else {
- otherLocations++;
- }
-
- l = l.getSecondary();
- }
- mWriter.write("</ul>");
- if (otherLocations > 0) {
- String id = "Location" + count + "Div"; //$NON-NLS-1$
- mWriter.write("<button id=\""); //$NON-NLS-1$
- mWriter.write(id);
- mWriter.write("Link\" onclick=\"reveal('"); //$NON-NLS-1$
- mWriter.write(id);
- mWriter.write("');\" />"); //$NON-NLS-1$
- mWriter.write(String.format("+ %1$d Additional Locations...",
- otherLocations));
- mWriter.write("</button>\n"); //$NON-NLS-1$
- mWriter.write("<div id=\""); //$NON-NLS-1$
- mWriter.write(id);
- mWriter.write("\" style=\"display: none\">\n"); //$NON-NLS-1$
-
- mWriter.write("Additional locations: ");
- mWriter.write("<ul>\n"); //$NON-NLS-1$
- l = warning.location.getSecondary();
- while (l != null) {
- Position start = l.getStart();
- int line = start != null ? start.getLine() : -1;
- String path = mClient.getDisplayPath(warning.project, l.getFile());
- mWriter.write("<li> "); //$NON-NLS-1$
- writeLocation(l.getFile(), path, line);
- mWriter.write("\n"); //$NON-NLS-1$
- l = l.getSecondary();
- }
- mWriter.write("</ul>\n"); //$NON-NLS-1$
-
- mWriter.write("</div><br/><br/>\n"); //$NON-NLS-1$
- }
- }
-
- // Place a block of images?
- if (!addedImage && url != null && warning.location != null
- && warning.location.getSecondary() != null) {
- addImage(url, warning.location);
- }
-
- if (warning.isVariantSpecific()) {
- mWriter.write("\n");
- mWriter.write("Applies to variants: ");
- mWriter.write(Joiner.on(", ").join(warning.getIncludedVariantNames()));
- mWriter.write("<br/>\n");
- mWriter.write("Does <b>not</b> apply to variants: ");
- mWriter.write(Joiner.on(", ").join(warning.getExcludedVariantNames()));
- mWriter.write("<br/>\n");
- }
- }
- if (partialHide) { // Close up the extra div
- mWriter.write("</div>\n"); //$NON-NLS-1$
- }
-
- mWriter.write("</div>\n"); //$NON-NLS-1$
- writeIssueMetadata(issue, first.severity, null);
-
- mWriter.write("</div>\n"); //$NON-NLS-1$
- }
-
- if (!mClient.isCheckingSpecificIssues()) {
- writeMissingIssues(missing);
- }
-
- writeSuppressInfo();
- } else {
- mWriter.write("Congratulations!");
- }
- mWriter.write("\n</body>\n</html>"); //$NON-NLS-1$
- mWriter.close();
-
- if (!mClient.getFlags().isQuiet()
- && (mDisplayEmpty || errorCount > 0 || warningCount > 0)) {
- String url = SdkUtils.fileToUrlString(mOutput.getAbsoluteFile());
- System.out.println(String.format("Wrote HTML report to %1$s", url));
- }
- }
-
- private void writeIssueMetadata(Issue issue, Severity severity, String disabledBy)
- throws IOException {
- mWriter.write("<div class=\"metadata\">"); //$NON-NLS-1$
-
- if (mClient.getRegistry() instanceof BuiltinIssueRegistry) {
- boolean adtHasFix = QuickfixHandler.ADT.hasAutoFix(issue);
- boolean studioHasFix = QuickfixHandler.STUDIO.hasAutoFix(issue);
- if (adtHasFix || studioHasFix) {
- String adt = "Eclipse/ADT";
- String studio = "Android Studio/IntelliJ";
- String tools = adtHasFix && studioHasFix
- ? (adt + " & " + studio) : studioHasFix ? studio : adt;
- mWriter.write("Note: This issue has an associated quickfix operation in " + tools);
- if (mFixUrl != null) {
- mWriter.write(" <img alt=\"Fix\" border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
- mWriter.write(mFixUrl);
- mWriter.write("\" />\n"); //$NON-NLS-1$
- }
-
- mWriter.write("<br>\n");
- }
- }
-
- if (disabledBy != null) {
- mWriter.write(String.format("Disabled By: %1$s<br/>\n", disabledBy));
- }
-
- mWriter.write("Priority: ");
- mWriter.write(String.format("%1$d / 10", issue.getPriority()));
- mWriter.write("<br/>\n"); //$NON-NLS-1$
- mWriter.write("Category: ");
- mWriter.write(issue.getCategory().getFullName());
- mWriter.write("</div>\n"); //$NON-NLS-1$
-
- mWriter.write("Severity: ");
- if (severity == Severity.ERROR || severity == Severity.FATAL) {
- mWriter.write("<span class=\"error\">"); //$NON-NLS-1$
- } else if (severity == Severity.WARNING) {
- mWriter.write("<span class=\"warning\">"); //$NON-NLS-1$
- } else {
- mWriter.write("<span>"); //$NON-NLS-1$
- }
- appendEscapedText(severity.getDescription());
- mWriter.write("</span>"); //$NON-NLS-1$
-
- mWriter.write("<div class=\"summary\">\n"); //$NON-NLS-1$
- mWriter.write("Explanation: ");
- String description = issue.getBriefDescription(HTML);
- mWriter.write(description);
- if (!description.isEmpty()
- && Character.isLetter(description.charAt(description.length() - 1))) {
- mWriter.write('.');
- }
- mWriter.write("</div>\n"); //$NON-NLS-1$
- mWriter.write("<div class=\"explanation\">\n"); //$NON-NLS-1$
- String explanationHtml = issue.getExplanation(HTML);
- mWriter.write(explanationHtml);
- mWriter.write("\n</div>\n"); //$NON-NLS-1$;
- List<String> moreInfo = issue.getMoreInfo();
- mWriter.write("<br/>"); //$NON-NLS-1$
- mWriter.write("<div class=\"moreinfo\">"); //$NON-NLS-1$
- mWriter.write("More info: ");
- int count = moreInfo.size();
- if (count > 1) {
- mWriter.write("<ul>"); //$NON-NLS-1$
- }
- for (String uri : moreInfo) {
- if (count > 1) {
- mWriter.write("<li>"); //$NON-NLS-1$
- }
- mWriter.write("<a href=\""); //$NON-NLS-1$
- mWriter.write(uri);
- mWriter.write("\">" ); //$NON-NLS-1$
- mWriter.write(uri);
- mWriter.write("</a>\n"); //$NON-NLS-1$
- }
- if (count > 1) {
- mWriter.write("</ul>"); //$NON-NLS-1$
- }
- mWriter.write("</div>"); //$NON-NLS-1$
-
- mWriter.write("<br/>"); //$NON-NLS-1$
- mWriter.write(String.format(
- "To suppress this error, use the issue id \"%1$s\" as explained in the " +
- "%2$sSuppressing Warnings and Errors%3$s section.",
- issue.getId(),
- "<a href=\"#SuppressInfo\">", "</a>")); //$NON-NLS-1$ //$NON-NLS-2$
- mWriter.write("<br/>\n");
- }
-
- private void writeSuppressInfo() throws IOException {
- //getSuppressHelp
- mWriter.write("\n<a name=\"SuppressInfo\"></a>\n"); //$NON-NLS-1$
- mWriter.write("<div class=\"category\">"); //$NON-NLS-1$
- mWriter.write("Suppressing Warnings and Errors");
- mWriter.write("<div class=\"categorySeparator\"></div>\n");//$NON-NLS-1$
- mWriter.write("</div>\n"); //$NON-NLS-1$
- mWriter.write(TextFormat.RAW.convertTo(Main.getSuppressHelp(), TextFormat.HTML));
- mWriter.write('\n');
- }
-
- protected Map<Issue, String> computeMissingIssues(List<Warning> warnings) {
- Set<Project> projects = new HashSet<Project>();
- Set<Issue> seen = new HashSet<Issue>();
- for (Warning warning : warnings) {
- projects.add(warning.project);
- seen.add(warning.issue);
- }
- Configuration cliConfiguration = mClient.getConfiguration();
- Map<Issue, String> map = Maps.newHashMap();
- for (Issue issue : mClient.getRegistry().getIssues()) {
- if (!seen.contains(issue)) {
- if (mClient.isSuppressed(issue)) {
- map.put(issue, "Command line flag");
- continue;
- }
-
- if (!issue.isEnabledByDefault() && !mClient.isAllEnabled()) {
- map.put(issue, "Default");
- continue;
- }
-
- if (cliConfiguration != null && !cliConfiguration.isEnabled(issue)) {
- map.put(issue, "Command line supplied --config lint.xml file");
- continue;
- }
-
- // See if any projects disable this warning
- for (Project project : projects) {
- if (!project.getConfiguration(null).isEnabled(issue)) {
- map.put(issue, "Project lint.xml file");
- break;
- }
- }
- }
- }
-
- return map;
- }
-
- private void writeMissingIssues(Map<Issue, String> missing) throws IOException {
- mWriter.write("\n<a name=\"MissingIssues\"></a>\n"); //$NON-NLS-1$
- mWriter.write("<div class=\"category\">"); //$NON-NLS-1$
- mWriter.write("Disabled Checks");
- mWriter.write("<div class=\"categorySeparator\"></div>\n"); //$NON-NLS-1$
- mWriter.write("</div>\n"); //$NON-NLS-1$
-
- mWriter.write(
- "The following issues were not run by lint, either " +
- "because the check is not enabled by default, or because " +
- "it was disabled with a command line flag or via one or " +
- "more lint.xml configuration files in the project directories.");
- mWriter.write("\n<br/><br/>\n"); //$NON-NLS-1$
-
- List<Issue> list = new ArrayList<Issue>(missing.keySet());
- Collections.sort(list);
-
-
- for (Issue issue : list) {
- mWriter.write("<a name=\"" + issue.getId() + "\"></a>\n"); //$NON-NLS-1$ //$NON-NLS-2$
- mWriter.write("<div class=\"issue\">\n"); //$NON-NLS-1$
-
- // Explain this issue
- mWriter.write("<div class=\"id\">"); //$NON-NLS-1$
- mWriter.write(issue.getId());
- mWriter.write("<div class=\"issueSeparator\"></div>\n"); //$NON-NLS-1$
- mWriter.write("</div>\n"); //$NON-NLS-1$
- String disabledBy = missing.get(issue);
- writeIssueMetadata(issue, issue.getDefaultSeverity(), disabledBy);
- mWriter.write("</div>\n"); //$NON-NLS-1$
- }
- }
-
- protected void writeStyleSheet() throws IOException {
- if (USE_HOLO_STYLE) {
- mWriter.write(
- "<link rel=\"stylesheet\" type=\"text/css\" " + //$NON-NLS-1$
- "href=\"http://fonts.googleapis.com/css?family=Roboto\" />\n" );//$NON-NLS-1$
- }
-
- URL cssUrl = HtmlReporter.class.getResource(CSS);
- if (mSimpleFormat) {
- // Inline the CSS
- mWriter.write("<style>\n"); //$NON-NLS-1$
- InputStream input = cssUrl.openStream();
- byte[] bytes = ByteStreams.toByteArray(input);
- try {
- Closeables.close(input, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- String css = new String(bytes, Charsets.UTF_8);
- mWriter.write(css);
- mWriter.write("</style>\n"); //$NON-NLS-1$
- } else {
- String ref = addLocalResources(cssUrl);
- if (ref != null) {
- mWriter.write(
- "<link rel=\"stylesheet\" type=\"text/css\" href=\"" //$NON-NLS-1$
- + ref + "\" />\n"); //$NON-NLS-1$
- }
- }
- }
-
- private void writeOverview(List<List<Warning>> related, int missingCount)
- throws IOException {
- // Write issue id summary
- mWriter.write("<table class=\"overview\">\n"); //$NON-NLS-1$
-
- String errorUrl = null;
- String warningUrl = null;
- if (!mSimpleFormat) {
- errorUrl = addLocalResources(getErrorIconUrl());
- warningUrl = addLocalResources(getWarningIconUrl());
- mFixUrl = addLocalResources(HtmlReporter.class.getResource("lint-run.png")); //$NON-NLS-1$)
- }
-
- Category previousCategory = null;
- for (List<Warning> warnings : related) {
- Issue issue = warnings.get(0).issue;
-
- boolean isError = false;
- for (Warning warning : warnings) {
- if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) {
- isError = true;
- break;
- }
- }
-
- if (issue.getCategory() != previousCategory) {
- mWriter.write("<tr><td></td><td class=\"categoryColumn\">");
- previousCategory = issue.getCategory();
- String categoryName = issue.getCategory().getFullName();
- mWriter.write("<a href=\"#"); //$NON-NLS-1$
- mWriter.write(categoryName);
- mWriter.write("\">"); //$NON-NLS-1$
- mWriter.write(categoryName);
- mWriter.write("</a>\n"); //$NON-NLS-1$
- mWriter.write("</td></tr>"); //$NON-NLS-1$
- mWriter.write("\n"); //$NON-NLS-1$
- }
- mWriter.write("<tr>\n"); //$NON-NLS-1$
-
- // Count column
- mWriter.write("<td class=\"countColumn\">"); //$NON-NLS-1$
- mWriter.write(Integer.toString(warnings.size()));
- mWriter.write("</td>"); //$NON-NLS-1$
-
- mWriter.write("<td class=\"issueColumn\">"); //$NON-NLS-1$
-
- String imageUrl = isError ? errorUrl : warningUrl;
- if (imageUrl != null) {
- mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
- mWriter.write(imageUrl);
- mWriter.write("\" alt=\"");
- mWriter.write(isError ? "Error" : "Warning");
- mWriter.write("\" />\n"); //$NON-NLS-1$
- }
-
- mWriter.write("<a href=\"#"); //$NON-NLS-1$
- mWriter.write(issue.getId());
- mWriter.write("\">"); //$NON-NLS-1$
- mWriter.write(issue.getId());
- mWriter.write(": "); //$NON-NLS-1$
- mWriter.write(issue.getBriefDescription(HTML));
- mWriter.write("</a>\n"); //$NON-NLS-1$
-
- mWriter.write("</td></tr>\n");
- }
-
- if (missingCount > 0 && !mClient.isCheckingSpecificIssues()) {
- mWriter.write("<tr><td></td>"); //$NON-NLS-1$
- mWriter.write("<td class=\"categoryColumn\">"); //$NON-NLS-1$
- mWriter.write("<a href=\"#MissingIssues\">"); //$NON-NLS-1$
- mWriter.write(String.format("Disabled Checks (%1$d)",
- missingCount));
-
- mWriter.write("</a>\n"); //$NON-NLS-1$
- mWriter.write("</td></tr>"); //$NON-NLS-1$
- }
-
- mWriter.write("</table>\n"); //$NON-NLS-1$
- mWriter.write("<br/>"); //$NON-NLS-1$
- }
-
- private String writeLocation(File file, String path, int line) throws IOException {
- String url;
- mWriter.write("<span class=\"location\">"); //$NON-NLS-1$
-
- url = getUrl(file);
- if (url != null) {
- mWriter.write("<a href=\""); //$NON-NLS-1$
- mWriter.write(url);
- mWriter.write("\">"); //$NON-NLS-1$
- }
-
- String displayPath = stripPath(path);
- if (url != null && url.startsWith("../") && new File(displayPath).isAbsolute()) {
- displayPath = url;
- }
- mWriter.write(displayPath);
- //noinspection VariableNotUsedInsideIf
- if (url != null) {
- mWriter.write("</a>"); //$NON-NLS-1$
- }
- if (line >= 0) {
- // 0-based line numbers, but display 1-based
- mWriter.write(':');
- mWriter.write(Integer.toString(line + 1));
- }
- mWriter.write("</span>"); //$NON-NLS-1$
- return url;
- }
-
- private boolean addImage(String url, Location location) throws IOException {
- if (url != null && endsWith(url, DOT_PNG)) {
- if (location.getSecondary() != null) {
- // Emit many images
- // Add in linked images as well
- List<String> urls = new ArrayList<String>();
- while (location != null && location.getFile() != null) {
- String imageUrl = getUrl(location.getFile());
- if (imageUrl != null
- && endsWith(imageUrl, DOT_PNG)) {
- urls.add(imageUrl);
- }
- location = location.getSecondary();
- }
- if (!urls.isEmpty()) {
- // Sort in order
- Collections.sort(urls, new Comparator<String>() {
- @Override
- public int compare(String s1, String s2) {
- return getDpiRank(s1) - getDpiRank(s2);
- }
- });
- mWriter.write("<table>"); //$NON-NLS-1$
- mWriter.write("<tr>"); //$NON-NLS-1$
- for (String linkedUrl : urls) {
- // Image series: align top
- mWriter.write("<td>"); //$NON-NLS-1$
- mWriter.write("<a href=\""); //$NON-NLS-1$
- mWriter.write(linkedUrl);
- mWriter.write("\">"); //$NON-NLS-1$
- mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
- mWriter.write(linkedUrl);
- mWriter.write("\" /></a>\n"); //$NON-NLS-1$
- mWriter.write("</td>"); //$NON-NLS-1$
- }
- mWriter.write("</tr>"); //$NON-NLS-1$
-
- mWriter.write("<tr>"); //$NON-NLS-1$
- for (String linkedUrl : urls) {
- mWriter.write("<th>"); //$NON-NLS-1$
- int index = linkedUrl.lastIndexOf("drawable-"); //$NON-NLS-1$
- if (index != -1) {
- index += "drawable-".length(); //$NON-NLS-1$
- int end = linkedUrl.indexOf('/', index);
- if (end != -1) {
- mWriter.write(linkedUrl.substring(index, end));
- }
- }
- mWriter.write("</th>"); //$NON-NLS-1$
- }
- mWriter.write("</tr>\n"); //$NON-NLS-1$
-
- mWriter.write("</table>\n"); //$NON-NLS-1$
- }
- } else {
- // Just this image: float to the right
- mWriter.write("<img class=\"embedimage\" align=\"right\" src=\""); //$NON-NLS-1$
- mWriter.write(url);
- mWriter.write("\" />"); //$NON-NLS-1$
- }
-
- return true;
- }
-
- return false;
- }
-
- /** Provide a sorting rank for a url */
- private static int getDpiRank(String url) {
- if (url.contains("-xhdpi")) { //$NON-NLS-1$
- return 0;
- } else if (url.contains("-hdpi")) { //$NON-NLS-1$
- return 1;
- } else if (url.contains("-mdpi")) { //$NON-NLS-1$
- return 2;
- } else if (url.contains("-ldpi")) { //$NON-NLS-1$
- return 3;
- } else {
- return 4;
- }
- }
-
- private void appendCodeBlock(String contents, int lineno, int offset)
- throws IOException {
- int max = lineno + 3;
- int min = lineno - 3;
- for (int l = min; l < max; l++) {
- if (l >= 0) {
- int lineOffset = LintCliClient.getLineOffset(contents, l);
- if (lineOffset == -1) {
- break;
- }
-
- mWriter.write(String.format("<span class=\"lineno\">%1$4d</span> ", (l + 1))); //$NON-NLS-1$
-
- String line = LintCliClient.getLineOfOffset(contents, lineOffset);
- if (offset != -1 && lineOffset <= offset && lineOffset+line.length() >= offset) {
- // Text nodes do not always have correct lines/offsets
- //assert l == lineno;
-
- // This line contains the beginning of the offset
- // First print everything before
- int delta = offset - lineOffset;
- appendEscapedText(line.substring(0, delta));
- mWriter.write("<span class=\"errorspan\">"); //$NON-NLS-1$
- appendEscapedText(line.substring(delta));
- mWriter.write("</span>"); //$NON-NLS-1$
- } else if (offset == -1 && l == lineno) {
- mWriter.write("<span class=\"errorline\">"); //$NON-NLS-1$
- appendEscapedText(line);
- mWriter.write("</span>"); //$NON-NLS-1$
- } else {
- appendEscapedText(line);
- }
- if (l < max - 1) {
- mWriter.write("\n"); //$NON-NLS-1$
- }
- }
- }
- }
-
- protected void appendEscapedText(String textValue) throws IOException {
- for (int i = 0, n = textValue.length(); i < n; i++) {
- char c = textValue.charAt(i);
- if (c == '<') {
- mWriter.write("<"); //$NON-NLS-1$
- } else if (c == '&') {
- mWriter.write("&"); //$NON-NLS-1$
- } else if (c == '\n') {
- mWriter.write("<br/>\n");
- } else {
- if (c > 255) {
- mWriter.write("&#"); //$NON-NLS-1$
- mWriter.write(Integer.toString(c));
- mWriter.write(';');
- } else {
- mWriter.write(c);
- }
- }
- }
- }
-
- private String stripPath(String path) {
- if (mStripPrefix != null && path.startsWith(mStripPrefix)
- && path.length() > mStripPrefix.length()) {
- int index = mStripPrefix.length();
- if (path.charAt(index) == File.separatorChar) {
- index++;
- }
- return path.substring(index);
- }
-
- return path;
- }
-
- /** Sets path prefix to strip from displayed file names */
- void setStripPrefix(String prefix) {
- mStripPrefix = prefix;
- }
-
- static URL getWarningIconUrl() {
- return HtmlReporter.class.getResource("lint-warning.png"); //$NON-NLS-1$
- }
-
- static URL getErrorIconUrl() {
- return HtmlReporter.class.getResource("lint-error.png"); //$NON-NLS-1$
- }
-}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java b/base/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
deleted file mode 100644
index 8bcbedf..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
+++ /dev/null
@@ -1,761 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.tools.lint.LintCliFlags.ERRNO_ERRORS;
-import static com.android.tools.lint.LintCliFlags.ERRNO_SUCCESS;
-import static com.android.tools.lint.client.api.IssueRegistry.LINT_ERROR;
-import static com.android.tools.lint.client.api.IssueRegistry.PARSER_ERROR;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.checks.HardcodedValuesDetector;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.client.api.DefaultConfiguration;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.LintListener;
-import com.android.tools.lint.client.api.LintRequest;
-import com.android.tools.lint.client.api.XmlParser;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Position;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.Closeables;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
-/**
- * Lint client for command line usage. Supports the flags in {@link LintCliFlags},
- * and offers text, HTML and XML reporting, etc.
- * <p>
- * Minimal example:
- * <pre>
- * // files is a list of java.io.Files, typically a directory containing
- * // lint projects or direct references to project root directories
- * IssueRegistry registry = new BuiltinIssueRegistry();
- * LintCliFlags flags = new LintCliFlags();
- * LintCliClient client = new LintCliClient(flags);
- * int exitCode = client.run(registry, files);
- * </pre>
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class LintCliClient extends LintClient {
- protected final List<Warning> mWarnings = new ArrayList<Warning>();
- protected boolean mHasErrors;
- protected int mErrorCount;
- protected int mWarningCount;
- protected IssueRegistry mRegistry;
- protected LintDriver mDriver;
- protected final LintCliFlags mFlags;
- private Configuration mConfiguration;
- private boolean mValidatedIds;
-
- /** Creates a CLI driver */
- public LintCliClient() {
- mFlags = new LintCliFlags();
- TextReporter reporter = new TextReporter(this, mFlags, new PrintWriter(System.out, true),
- false);
- mFlags.getReporters().add(reporter);
- }
-
- public LintCliClient(LintCliFlags flags) {
- mFlags = flags;
- }
-
- /**
- * Runs the static analysis command line driver. You need to add at least one error reporter
- * to the command line flags.
- */
- public int run(@NonNull IssueRegistry registry, @NonNull List<File> files) throws IOException {
- assert !mFlags.getReporters().isEmpty();
- mRegistry = registry;
- mDriver = new LintDriver(registry, this);
-
- mDriver.setAbbreviating(!mFlags.isShowEverything());
- addProgressPrinter();
- mDriver.addLintListener(new LintListener() {
- @Override
- public void update(@NonNull LintDriver driver, @NonNull EventType type,
- @Nullable Context context) {
- if (type == EventType.SCANNING_PROJECT && !mValidatedIds) {
- // Make sure all the id's are valid once the driver is all set up and
- // ready to run (such that custom rules are available in the registry etc)
- validateIssueIds(context != null ? context.getProject() : null);
- }
- }
- });
-
- mDriver.analyze(createLintRequest(files));
-
- Collections.sort(mWarnings);
-
- boolean hasConsoleOutput = false;
- for (Reporter reporter : mFlags.getReporters()) {
- reporter.write(mErrorCount, mWarningCount, mWarnings);
- if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
- hasConsoleOutput = true;
- }
- }
-
- if (!mFlags.isQuiet() && !hasConsoleOutput) {
- System.out.println(String.format(
- "Lint found %1$d errors and %2$d warnings", mErrorCount, mWarningCount));
- }
-
- return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
- }
-
- protected void addProgressPrinter() {
- if (!mFlags.isQuiet()) {
- mDriver.addLintListener(new ProgressPrinter());
- }
- }
-
- /** Creates a lint request */
- @NonNull
- protected LintRequest createLintRequest(@NonNull List<File> files) {
- return new LintRequest(this, files);
- }
-
- @Override
- public void log(
- @NonNull Severity severity,
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args) {
- System.out.flush();
- if (!mFlags.isQuiet()) {
- // Place the error message on a line of its own since we're printing '.' etc
- // with newlines during analysis
- System.err.println();
- }
- if (format != null) {
- System.err.println(String.format(format, args));
- }
- if (exception != null) {
- exception.printStackTrace();
- }
- }
-
- @Override
- public XmlParser getXmlParser() {
- return new LintCliXmlParser();
- }
-
- @NonNull
- @Override
- public Configuration getConfiguration(@NonNull Project project, @Nullable LintDriver driver) {
- return new CliConfiguration(getConfiguration(), project, mFlags.isFatalOnly());
- }
-
- /** File content cache */
- private final Map<File, String> mFileContents = new HashMap<File, String>(100);
-
- /** Read the contents of the given file, possibly cached */
- private String getContents(File file) {
- String s = mFileContents.get(file);
- if (s == null) {
- s = readFile(file);
- mFileContents.put(file, s);
- }
-
- return s;
- }
-
- @Override
- public JavaParser getJavaParser(@Nullable Project project) {
- return new EcjParser(this, project);
- }
-
- @Override
- public void report(
- @NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message,
- @NonNull TextFormat format) {
- assert context.isEnabled(issue) || issue == LINT_ERROR;
-
- if (severity == Severity.IGNORE) {
- return;
- }
-
- if (severity == Severity.ERROR || severity == Severity.FATAL) {
- mHasErrors = true;
- mErrorCount++;
- } else {
- mWarningCount++;
- }
-
- // Store the message in the raw format internally such that we can
- // convert it to text for the text reporter, HTML for the HTML reporter
- // and so on.
- message = format.convertTo(message, TextFormat.RAW);
- Warning warning = new Warning(issue, message, severity, context.getProject());
- mWarnings.add(warning);
-
- if (location != null) {
- warning.location = location;
- File file = location.getFile();
- if (file != null) {
- warning.file = file;
- warning.path = getDisplayPath(context.getProject(), file);
- }
-
- Position startPosition = location.getStart();
- if (startPosition != null) {
- int line = startPosition.getLine();
- warning.line = line;
- warning.offset = startPosition.getOffset();
- if (line >= 0) {
- if (context.file == location.getFile()) {
- warning.fileContents = context.getContents();
- }
- if (warning.fileContents == null) {
- warning.fileContents = getContents(location.getFile());
- }
-
- if (mFlags.isShowSourceLines()) {
- // Compute error line contents
- warning.errorLine = getLine(warning.fileContents, line);
- if (warning.errorLine != null) {
- // Replace tabs with spaces such that the column
- // marker (^) lines up properly:
- warning.errorLine = warning.errorLine.replace('\t', ' ');
- int column = startPosition.getColumn();
- if (column < 0) {
- column = 0;
- for (int i = 0; i < warning.errorLine.length(); i++, column++) {
- if (!Character.isWhitespace(warning.errorLine.charAt(i))) {
- break;
- }
- }
- }
- StringBuilder sb = new StringBuilder(100);
- sb.append(warning.errorLine);
- sb.append('\n');
- for (int i = 0; i < column; i++) {
- sb.append(' ');
- }
-
- boolean displayCaret = true;
- Position endPosition = location.getEnd();
- if (endPosition != null) {
- int endLine = endPosition.getLine();
- int endColumn = endPosition.getColumn();
- if (endLine == line && endColumn > column) {
- for (int i = column; i < endColumn; i++) {
- sb.append('~');
- }
- displayCaret = false;
- }
- }
-
- if (displayCaret) {
- sb.append('^');
- }
- sb.append('\n');
- warning.errorLine = sb.toString();
- }
- }
- }
- }
- }
- }
-
- /** Look up the contents of the given line */
- static String getLine(String contents, int line) {
- int index = getLineOffset(contents, line);
- if (index != -1) {
- return getLineOfOffset(contents, index);
- } else {
- return null;
- }
- }
-
- static String getLineOfOffset(String contents, int offset) {
- int end = contents.indexOf('\n', offset);
- if (end == -1) {
- end = contents.indexOf('\r', offset);
- }
- return contents.substring(offset, end != -1 ? end : contents.length());
- }
-
-
- /** Look up the contents of the given line */
- static int getLineOffset(String contents, int line) {
- int index = 0;
- for (int i = 0; i < line; i++) {
- index = contents.indexOf('\n', index);
- if (index == -1) {
- return -1;
- }
- index++;
- }
-
- return index;
- }
-
- @NonNull
- @Override
- public String readFile(@NonNull File file) {
- try {
- return LintUtils.getEncodedString(this, file);
- } catch (IOException e) {
- return ""; //$NON-NLS-1$
- }
- }
-
- boolean isCheckingSpecificIssues() {
- return mFlags.getExactCheckedIds() != null;
- }
-
- private Map<Project, ClassPathInfo> mProjectInfo;
-
- @Override
- @NonNull
- protected ClassPathInfo getClassPath(@NonNull Project project) {
- ClassPathInfo classPath = super.getClassPath(project);
-
- List<File> sources = mFlags.getSourcesOverride();
- List<File> classes = mFlags.getClassesOverride();
- List<File> libraries = mFlags.getLibrariesOverride();
- if (classes == null && sources == null && libraries == null) {
- return classPath;
- }
-
- ClassPathInfo info;
- if (mProjectInfo == null) {
- mProjectInfo = Maps.newHashMap();
- info = null;
- } else {
- info = mProjectInfo.get(project);
- }
-
- if (info == null) {
- if (sources == null) {
- sources = classPath.getSourceFolders();
- }
- if (classes == null) {
- classes = classPath.getClassFolders();
- }
- if (libraries == null) {
- libraries = classPath.getLibraries();
- }
-
- info = new ClassPathInfo(sources, classes, libraries,
- classPath.getTestSourceFolders());
- mProjectInfo.put(project, info);
- }
-
- return info;
- }
-
- @NonNull
- @Override
- public List<File> getResourceFolders(@NonNull Project project) {
- List<File> resources = mFlags.getResourcesOverride();
- if (resources == null) {
- return super.getResourceFolders(project);
- }
-
- return resources;
- }
-
- /**
- * Consult the lint.xml file, but override with the --enable and --disable
- * flags supplied on the command line
- */
- class CliConfiguration extends DefaultConfiguration {
- private boolean mFatalOnly;
-
- CliConfiguration(@NonNull Configuration parent, @NonNull Project project,
- boolean fatalOnly) {
- super(LintCliClient.this, project, parent);
- mFatalOnly = fatalOnly;
- }
-
- CliConfiguration(File lintFile, boolean fatalOnly) {
- super(LintCliClient.this, null /*project*/, null /*parent*/, lintFile);
- mFatalOnly = fatalOnly;
- }
-
- @NonNull
- @Override
- public Severity getSeverity(@NonNull Issue issue) {
- Severity severity = computeSeverity(issue);
-
- if (mFatalOnly && severity != Severity.FATAL) {
- return Severity.IGNORE;
- }
-
- if (mFlags.isWarningsAsErrors() && severity == Severity.WARNING) {
- severity = Severity.ERROR;
- }
-
- if (mFlags.isIgnoreWarnings() && severity == Severity.WARNING) {
- severity = Severity.IGNORE;
- }
-
- return severity;
- }
-
- @NonNull
- @Override
- protected Severity getDefaultSeverity(@NonNull Issue issue) {
- if (mFlags.isCheckAllWarnings()) {
- return issue.getDefaultSeverity();
- }
-
- return super.getDefaultSeverity(issue);
- }
-
- private Severity computeSeverity(@NonNull Issue issue) {
- Severity severity = super.getSeverity(issue);
-
- String id = issue.getId();
- Set<String> suppress = mFlags.getSuppressedIds();
- if (suppress.contains(id)) {
- return Severity.IGNORE;
- }
-
- Severity manual = mFlags.getSeverityOverrides().get(id);
- if (manual != null) {
- return manual;
- }
-
- Set<String> enabled = mFlags.getEnabledIds();
- Set<String> check = mFlags.getExactCheckedIds();
- if (enabled.contains(id) || (check != null && check.contains(id))) {
- // Overriding default
- // Detectors shouldn't be returning ignore as a default severity,
- // but in case they do, force it up to warning here to ensure that
- // it's run
- if (severity == Severity.IGNORE) {
- severity = issue.getDefaultSeverity();
- if (severity == Severity.IGNORE) {
- severity = Severity.WARNING;
- }
- }
-
- return severity;
- }
-
- if (check != null && issue != LINT_ERROR && issue != PARSER_ERROR) {
- return Severity.IGNORE;
- }
-
- return severity;
- }
- }
-
- /**
- * Checks that any id's specified by id refer to valid, known, issues. This
- * typically can't be done right away (in for example the Gradle code which
- * handles DSL references to strings, or in the command line parser for the
- * lint command) because the full set of valid id's is not known until lint
- * actually starts running and for example gathers custom rules from all
- * AAR dependencies reachable from libraries, etc.
- */
- private void validateIssueIds(@Nullable Project project) {
- if (mDriver != null) {
- IssueRegistry registry = mDriver.getRegistry();
- if (!registry.isIssueId(HardcodedValuesDetector.ISSUE.getId())) {
- // This should not be necessary, but there have been some strange
- // reports where lint has reported some well known builtin issues
- // to not exist:
- //
- // Error: Unknown issue id "DuplicateDefinition" [LintError]
- // Error: Unknown issue id "GradleIdeError" [LintError]
- // Error: Unknown issue id "InvalidPackage" [LintError]
- // Error: Unknown issue id "JavascriptInterface" [LintError]
- // ...
- //
- // It's not clear how this can happen, though it's probably related
- // to using 3rd party lint rules (where lint will create new composite
- // issue registries to wrap the various additional issues) - but
- // we definitely don't want to validate issue id's if we can't find
- // well known issues.
- return;
- }
- mValidatedIds = true;
- validateIssueIds(project, registry, mFlags.getExactCheckedIds());
- validateIssueIds(project, registry, mFlags.getEnabledIds());
- validateIssueIds(project, registry, mFlags.getSuppressedIds());
- validateIssueIds(project, registry, mFlags.getSeverityOverrides().keySet());
- }
- }
-
- private void validateIssueIds(@Nullable Project project, @NonNull IssueRegistry registry,
- @Nullable Collection<String> ids) {
- if (ids != null) {
- for (String id : ids) {
- if (registry.getIssue(id) == null) {
- reportNonExistingIssueId(project, id);
- }
- }
- }
- }
-
- protected void reportNonExistingIssueId(@Nullable Project project, @NonNull String id) {
- String message = String.format("Unknown issue id \"%1$s\"", id);
-
- if (mDriver != null && project != null) {
- Location location = Location.create(project.getDir());
- if (!isSuppressed(IssueRegistry.LINT_ERROR)) {
- report(new Context(mDriver, project, project, project.getDir()),
- IssueRegistry.LINT_ERROR,
- project.getConfiguration(mDriver).getSeverity(IssueRegistry.LINT_ERROR),
- location, message, TextFormat.RAW);
- }
- } else {
- log(Severity.ERROR, null, "Lint: %1$s", message);
- }
- }
-
- private static class ProgressPrinter implements LintListener {
- @Override
- public void update(
- @NonNull LintDriver lint,
- @NonNull EventType type,
- @Nullable Context context) {
- switch (type) {
- case SCANNING_PROJECT: {
- String name = context != null ? context.getProject().getName() : "?";
- if (lint.getPhase() > 1) {
- System.out.print(String.format(
- "\nScanning %1$s (Phase %2$d): ",
- name,
- lint.getPhase()));
- } else {
- System.out.print(String.format(
- "\nScanning %1$s: ",
- name));
- }
- break;
- }
- case SCANNING_LIBRARY_PROJECT: {
- String name = context != null ? context.getProject().getName() : "?";
- System.out.print(String.format(
- "\n - %1$s: ",
- name));
- break;
- }
- case SCANNING_FILE:
- System.out.print('.');
- break;
- case NEW_PHASE:
- // Ignored for now: printing status as part of next project's status
- break;
- case CANCELED:
- case COMPLETED:
- System.out.println();
- break;
- case STARTING:
- // Ignored for now
- break;
- }
- }
- }
-
- /**
- * Given a file, it produces a cleaned up path from the file.
- * This will clean up the path such that
- * <ul>
- * <li> {@code foo/./bar} becomes {@code foo/bar}
- * <li> {@code foo/bar/../baz} becomes {@code foo/baz}
- * </ul>
- *
- * Unlike {@link java.io.File#getCanonicalPath()} however, it will <b>not</b> attempt
- * to make the file canonical, such as expanding symlinks and network mounts.
- *
- * @param file the file to compute a clean path for
- * @return the cleaned up path
- */
- @VisibleForTesting
- @NonNull
- static String getCleanPath(@NonNull File file) {
- String path = file.getPath();
- StringBuilder sb = new StringBuilder(path.length());
-
- if (path.startsWith(File.separator)) {
- sb.append(File.separator);
- }
- elementLoop:
- for (String element : Splitter.on(File.separatorChar).omitEmptyStrings().split(path)) {
- if (element.equals(".")) { //$NON-NLS-1$
- continue;
- } else if (element.equals("..")) { //$NON-NLS-1$
- if (sb.length() > 0) {
- for (int i = sb.length() - 1; i >= 0; i--) {
- char c = sb.charAt(i);
- if (c == File.separatorChar) {
- sb.setLength(i == 0 ? 1 : i);
- continue elementLoop;
- }
- }
- sb.setLength(0);
- continue;
- }
- }
-
- if (sb.length() > 1) {
- sb.append(File.separatorChar);
- } else if (sb.length() > 0 && sb.charAt(0) != File.separatorChar) {
- sb.append(File.separatorChar);
- }
- sb.append(element);
- }
- if (path.endsWith(File.separator) && sb.length() > 0
- && sb.charAt(sb.length() - 1) != File.separatorChar) {
- sb.append(File.separator);
- }
-
- return sb.toString();
- }
-
- String getDisplayPath(Project project, File file) {
- String path = file.getPath();
- if (!mFlags.isFullPath() && path.startsWith(project.getReferenceDir().getPath())) {
- int chop = project.getReferenceDir().getPath().length();
- if (path.length() > chop && path.charAt(chop) == File.separatorChar) {
- chop++;
- }
- path = path.substring(chop);
- if (path.isEmpty()) {
- path = file.getName();
- }
- } else if (mFlags.isFullPath()) {
- path = getCleanPath(file.getAbsoluteFile());
- }
-
- return path;
- }
-
- /** Returns whether all warnings are enabled, including those disabled by default */
- boolean isAllEnabled() {
- return mFlags.isCheckAllWarnings();
- }
-
- /** Returns the issue registry used by this client */
- IssueRegistry getRegistry() {
- return mRegistry;
- }
-
- /** Returns the driver running the lint checks */
- LintDriver getDriver() {
- return mDriver;
- }
-
- private static Set<File> sAlreadyWarned;
-
- /** Returns the configuration used by this client */
- Configuration getConfiguration() {
- if (mConfiguration == null) {
- File configFile = mFlags.getDefaultConfiguration();
- if (configFile != null) {
- if (!configFile.exists()) {
- if (sAlreadyWarned == null || !sAlreadyWarned.contains(configFile)) {
- log(Severity.ERROR, null,
- "Warning: Configuration file %1$s does not exist", configFile);
- }
- if (sAlreadyWarned == null) {
- sAlreadyWarned = Sets.newHashSet();
- }
- sAlreadyWarned.add(configFile);
- }
- mConfiguration = createConfigurationFromFile(configFile);
- }
- }
-
- return mConfiguration;
- }
-
- /** Returns true if the given issue has been explicitly disabled */
- boolean isSuppressed(Issue issue) {
- return mFlags.getSuppressedIds().contains(issue.getId());
- }
-
- public Configuration createConfigurationFromFile(File file) {
- return new CliConfiguration(file, mFlags.isFatalOnly());
- }
-
- @Nullable
- String getRevision() {
- File file = findResource("tools" + File.separator + //$NON-NLS-1$
- "source.properties"); //$NON-NLS-1$
- if (file != null && file.exists()) {
- FileInputStream input = null;
- try {
- input = new FileInputStream(file);
- Properties properties = new Properties();
- properties.load(input);
-
- String revision = properties.getProperty("Pkg.Revision"); //$NON-NLS-1$
- if (revision != null && !revision.isEmpty()) {
- return revision;
- }
- } catch (IOException e) {
- // Couldn't find or read the version info: just print out unknown below
- } finally {
- try {
- Closeables.close(input, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- }
- }
-
- return null;
- }
-
- @NonNull
- public LintCliFlags getFlags() {
- return mFlags;
- }
-
- public boolean haveErrors() {
- return mErrorCount > 0;
- }
-}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/Main.java b/base/lint/cli/src/main/java/com/android/tools/lint/Main.java
deleted file mode 100644
index 126d02f..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/Main.java
+++ /dev/null
@@ -1,1067 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.VALUE_NONE;
-import static com.android.tools.lint.LintCliFlags.ERRNO_ERRORS;
-import static com.android.tools.lint.LintCliFlags.ERRNO_EXISTS;
-import static com.android.tools.lint.LintCliFlags.ERRNO_HELP;
-import static com.android.tools.lint.LintCliFlags.ERRNO_INVALID_ARGS;
-import static com.android.tools.lint.LintCliFlags.ERRNO_SUCCESS;
-import static com.android.tools.lint.LintCliFlags.ERRNO_USAGE;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-import static com.android.tools.lint.detector.api.TextFormat.TEXT;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.checks.BuiltinIssueRegistry;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.utils.SdkUtils;
-import com.google.common.annotations.Beta;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Command line driver for the lint framework
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class Main {
- static final int MAX_LINE_WIDTH = 78;
- private static final String ARG_ENABLE = "--enable"; //$NON-NLS-1$
- private static final String ARG_DISABLE = "--disable"; //$NON-NLS-1$
- private static final String ARG_CHECK = "--check"; //$NON-NLS-1$
- private static final String ARG_IGNORE = "--ignore"; //$NON-NLS-1$
- private static final String ARG_LIST_IDS = "--list"; //$NON-NLS-1$
- private static final String ARG_SHOW = "--show"; //$NON-NLS-1$
- private static final String ARG_QUIET = "--quiet"; //$NON-NLS-1$
- private static final String ARG_FULL_PATH = "--fullpath"; //$NON-NLS-1$
- private static final String ARG_SHOW_ALL = "--showall"; //$NON-NLS-1$
- private static final String ARG_HELP = "--help"; //$NON-NLS-1$
- private static final String ARG_NO_LINES = "--nolines"; //$NON-NLS-1$
- private static final String ARG_HTML = "--html"; //$NON-NLS-1$
- private static final String ARG_SIMPLE_HTML= "--simplehtml"; //$NON-NLS-1$
- private static final String ARG_XML = "--xml"; //$NON-NLS-1$
- private static final String ARG_TEXT = "--text"; //$NON-NLS-1$
- private static final String ARG_CONFIG = "--config"; //$NON-NLS-1$
- private static final String ARG_URL = "--url"; //$NON-NLS-1$
- private static final String ARG_VERSION = "--version"; //$NON-NLS-1$
- private static final String ARG_EXIT_CODE = "--exitcode"; //$NON-NLS-1$
- private static final String ARG_CLASSES = "--classpath"; //$NON-NLS-1$
- private static final String ARG_SOURCES = "--sources"; //$NON-NLS-1$
- private static final String ARG_RESOURCES = "--resources"; //$NON-NLS-1$
- private static final String ARG_LIBRARIES = "--libraries"; //$NON-NLS-1$
-
- private static final String ARG_NO_WARN_2 = "--nowarn"; //$NON-NLS-1$
- // GCC style flag names for options
- private static final String ARG_NO_WARN_1 = "-w"; //$NON-NLS-1$
- private static final String ARG_WARN_ALL = "-Wall"; //$NON-NLS-1$
- private static final String ARG_ALL_ERROR = "-Werror"; //$NON-NLS-1$
-
- private static final String PROP_WORK_DIR = "com.android.tools.lint.workdir"; //$NON-NLS-1$
-
- private LintCliFlags mFlags = new LintCliFlags();
- private IssueRegistry mGlobalRegistry;
-
- /** Creates a CLI driver */
- public Main() {
- }
-
- /**
- * Runs the static analysis command line driver
- *
- * @param args program arguments
- */
- public static void main(String[] args) {
- new Main().run(args);
- }
-
- /**
- * Runs the static analysis command line driver
- *
- * @param args program arguments
- */
- @SuppressWarnings("UnnecessaryLocalVariable")
- public void run(String[] args) {
- if (args.length < 1) {
- printUsage(System.err);
- System.exit(ERRNO_USAGE);
- }
-
-
- // When running lint from the command line, warn if the project is a Gradle project
- // since those projects may have custom project configuration that the command line
- // runner won't know about.
- LintCliClient client = new LintCliClient(mFlags) {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- Project project = super.createProject(dir, referenceDir);
- if (project.isGradleProject()) {
- @SuppressWarnings("SpellCheckingInspection")
- String message = String.format("\"`%1$s`\" is a Gradle project. To correctly "
- + "analyze Gradle projects, you should run \"`gradlew :lint`\" instead.",
- project.getName());
- Location location = Location.create(project.getDir());
- Context context = new Context(mDriver, project, project, project.getDir());
- if (context.isEnabled(IssueRegistry.LINT_ERROR) &&
- !getConfiguration(project, null).isIgnored(context,
- IssueRegistry.LINT_ERROR, location, message)) {
- report(context,
- IssueRegistry.LINT_ERROR,
- project.getConfiguration(null).getSeverity(
- IssueRegistry.LINT_ERROR), location, message,
- TextFormat.RAW);
- }
- }
- return project;
- }
-
- @NonNull
- @Override
- public Configuration getConfiguration(@NonNull final Project project,
- @Nullable LintDriver driver) {
- if (project.isGradleProject()) {
- // Don't report any issues when analyzing a Gradle project from the
- // non-Gradle runner; they are likely to be false, and will hide the real
- // problem reported above
- return new CliConfiguration(getConfiguration(), project, true) {
- @NonNull
- @Override
- public Severity getSeverity(@NonNull Issue issue) {
- return issue == IssueRegistry.LINT_ERROR
- ? Severity.FATAL : Severity.IGNORE;
- }
-
- @Override
- public boolean isIgnored(@NonNull Context context, @NonNull Issue issue,
- @Nullable Location location, @NonNull String message) {
- // If you've deliberately ignored IssueRegistry.LINT_ERROR
- // don't flag that one either
- if (issue == IssueRegistry.LINT_ERROR && new LintCliClient(mFlags).isSuppressed(
- IssueRegistry.LINT_ERROR)) {
- return true;
- }
-
- return issue != IssueRegistry.LINT_ERROR;
- }
- };
- }
- return super.getConfiguration(project, driver);
- }
- };
-
- // Mapping from file path prefix to URL. Applies only to HTML reports
- String urlMap = null;
-
- List<File> files = new ArrayList<File>();
- for (int index = 0; index < args.length; index++) {
- String arg = args[index];
-
- if (arg.equals(ARG_HELP)
- || arg.equals("-h") || arg.equals("-?")) { //$NON-NLS-1$ //$NON-NLS-2$
- if (index < args.length - 1) {
- String topic = args[index + 1];
- if (topic.equals("suppress") || topic.equals("ignore")) {
- printHelpTopicSuppress();
- System.exit(ERRNO_HELP);
- } else {
- System.err.println(String.format("Unknown help topic \"%1$s\"", topic));
- System.exit(ERRNO_INVALID_ARGS);
- }
- }
- printUsage(System.out);
- System.exit(ERRNO_HELP);
- } else if (arg.equals(ARG_LIST_IDS)) {
- IssueRegistry registry = getGlobalRegistry(client);
- // Did the user provide a category list?
- if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$
- String[] ids = args[++index].split(",");
- for (String id : ids) {
- if (registry.isCategoryName(id)) {
- // List all issues with the given category
- String category = id;
- for (Issue issue : registry.getIssues()) {
- // Check prefix such that filtering on the "Usability" category
- // will match issue category "Usability:Icons" etc.
- if (issue.getCategory().getName().startsWith(category) ||
- issue.getCategory().getFullName().startsWith(category)) {
- listIssue(System.out, issue);
- }
- }
- } else {
- System.err.println("Invalid category \"" + id + "\".\n");
- displayValidIds(registry, System.err);
- System.exit(ERRNO_INVALID_ARGS);
- }
- }
- } else {
- displayValidIds(registry, System.out);
- }
- System.exit(ERRNO_SUCCESS);
- } else if (arg.equals(ARG_SHOW)) {
- IssueRegistry registry = getGlobalRegistry(client);
- // Show specific issues?
- if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$
- String[] ids = args[++index].split(",");
- for (String id : ids) {
- if (registry.isCategoryName(id)) {
- // Show all issues in the given category
- String category = id;
- for (Issue issue : registry.getIssues()) {
- // Check prefix such that filtering on the "Usability" category
- // will match issue category "Usability:Icons" etc.
- if (issue.getCategory().getName().startsWith(category) ||
- issue.getCategory().getFullName().startsWith(category)) {
- describeIssue(issue);
- System.out.println();
- }
- }
- } else if (registry.isIssueId(id)) {
- describeIssue(registry.getIssue(id));
- System.out.println();
- } else {
- System.err.println("Invalid id or category \"" + id + "\".\n");
- displayValidIds(registry, System.err);
- System.exit(ERRNO_INVALID_ARGS);
- }
- }
- } else {
- showIssues(registry);
- }
- System.exit(ERRNO_SUCCESS);
- } else if (arg.equals(ARG_FULL_PATH)
- || arg.equals(ARG_FULL_PATH + "s")) { // allow "--fullpaths" too
- mFlags.setFullPath(true);
- } else if (arg.equals(ARG_SHOW_ALL)) {
- mFlags.setShowEverything(true);
- } else if (arg.equals(ARG_QUIET) || arg.equals("-q")) {
- mFlags.setQuiet(true);
- } else if (arg.equals(ARG_NO_LINES)) {
- mFlags.setShowSourceLines(false);
- } else if (arg.equals(ARG_EXIT_CODE)) {
- mFlags.setSetExitCode(true);
- } else if (arg.equals(ARG_VERSION)) {
- printVersion(client);
- System.exit(ERRNO_SUCCESS);
- } else if (arg.equals(ARG_URL)) {
- if (index == args.length - 1) {
- System.err.println("Missing URL mapping string");
- System.exit(ERRNO_INVALID_ARGS);
- }
- String map = args[++index];
- // Allow repeated usage of the argument instead of just comma list
- if (urlMap != null) {
- urlMap = urlMap + ',' + map;
- } else {
- urlMap = map;
- }
- } else if (arg.equals(ARG_CONFIG)) {
- if (index == args.length - 1 || !endsWith(args[index + 1], DOT_XML)) {
- System.err.println("Missing XML configuration file argument");
- System.exit(ERRNO_INVALID_ARGS);
- }
- File file = getInArgumentPath(args[++index]);
- if (!file.exists()) {
- System.err.println(file.getAbsolutePath() + " does not exist");
- System.exit(ERRNO_INVALID_ARGS);
- }
- mFlags.setDefaultConfiguration(file);
- } else if (arg.equals(ARG_HTML) || arg.equals(ARG_SIMPLE_HTML)) {
- if (index == args.length - 1) {
- System.err.println("Missing HTML output file name");
- System.exit(ERRNO_INVALID_ARGS);
- }
- File output = getOutArgumentPath(args[++index]);
- // Get an absolute path such that we can ask its parent directory for
- // write permission etc.
- output = output.getAbsoluteFile();
- if (output.isDirectory() ||
- (!output.exists() && output.getName().indexOf('.') == -1)) {
- if (!output.exists()) {
- boolean mkdirs = output.mkdirs();
- if (!mkdirs) {
- log(null, "Could not create output directory %1$s", output);
- System.exit(ERRNO_EXISTS);
- }
- }
- try {
- MultiProjectHtmlReporter reporter =
- new MultiProjectHtmlReporter(client, output);
- if (arg.equals(ARG_SIMPLE_HTML)) {
- reporter.setSimpleFormat(true);
- }
- mFlags.getReporters().add(reporter);
- } catch (IOException e) {
- log(e, null);
- System.exit(ERRNO_INVALID_ARGS);
- }
- continue;
- }
- if (output.exists()) {
- boolean delete = output.delete();
- if (!delete) {
- System.err.println("Could not delete old " + output);
- System.exit(ERRNO_EXISTS);
- }
- }
- if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
- System.err.println("Cannot write HTML output file " + output);
- System.exit(ERRNO_EXISTS);
- }
- try {
- HtmlReporter htmlReporter = new HtmlReporter(client, output);
- if (arg.equals(ARG_SIMPLE_HTML)) {
- htmlReporter.setSimpleFormat(true);
- }
- mFlags.getReporters().add(htmlReporter);
- } catch (IOException e) {
- log(e, null);
- System.exit(ERRNO_INVALID_ARGS);
- }
- } else if (arg.equals(ARG_XML)) {
- if (index == args.length - 1) {
- System.err.println("Missing XML output file name");
- System.exit(ERRNO_INVALID_ARGS);
- }
- File output = getOutArgumentPath(args[++index]);
- // Get an absolute path such that we can ask its parent directory for
- // write permission etc.
- output = output.getAbsoluteFile();
-
- if (output.exists()) {
- boolean delete = output.delete();
- if (!delete) {
- System.err.println("Could not delete old " + output);
- System.exit(ERRNO_EXISTS);
- }
- }
- if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
- System.err.println("Cannot write XML output file " + output);
- System.exit(ERRNO_EXISTS);
- }
- try {
- mFlags.getReporters().add(new XmlReporter(client, output));
- } catch (IOException e) {
- log(e, null);
- System.exit(ERRNO_INVALID_ARGS);
- }
- } else if (arg.equals(ARG_TEXT)) {
- if (index == args.length - 1) {
- System.err.println("Missing text output file name");
- System.exit(ERRNO_INVALID_ARGS);
- }
-
- Writer writer = null;
- boolean closeWriter;
- String outputName = args[++index];
- if (outputName.equals("stdout")) { //$NON-NLS-1$
- //noinspection IOResourceOpenedButNotSafelyClosed
- writer = new PrintWriter(System.out, true);
- closeWriter = false;
- } else {
- File output = getOutArgumentPath(outputName);
-
- // Get an absolute path such that we can ask its parent directory for
- // write permission etc.
- output = output.getAbsoluteFile();
-
- if (output.exists()) {
- boolean delete = output.delete();
- if (!delete) {
- System.err.println("Could not delete old " + output);
- System.exit(ERRNO_EXISTS);
- }
- }
- if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
- System.err.println("Cannot write text output file " + output);
- System.exit(ERRNO_EXISTS);
- }
- try {
- //noinspection IOResourceOpenedButNotSafelyClosed
- writer = new BufferedWriter(new FileWriter(output));
- } catch (IOException e) {
- log(e, null);
- System.exit(ERRNO_INVALID_ARGS);
- }
- closeWriter = true;
- }
- mFlags.getReporters().add(new TextReporter(client, mFlags, writer, closeWriter));
- } else if (arg.equals(ARG_DISABLE) || arg.equals(ARG_IGNORE)) {
- if (index == args.length - 1) {
- System.err.println("Missing categories or id's to disable");
- System.exit(ERRNO_INVALID_ARGS);
- }
- IssueRegistry registry = getGlobalRegistry(client);
- String[] ids = args[++index].split(",");
- for (String id : ids) {
- if (registry.isCategoryName(id)) {
- // Suppress all issues with the given category
- String category = id;
- for (Issue issue : registry.getIssues()) {
- // Check prefix such that filtering on the "Usability" category
- // will match issue category "Usability:Icons" etc.
- if (issue.getCategory().getName().startsWith(category) ||
- issue.getCategory().getFullName().startsWith(category)) {
- mFlags.getSuppressedIds().add(issue.getId());
- }
- }
- } else if (!registry.isIssueId(id)) {
- System.err.println("Invalid id or category \"" + id + "\".\n");
- displayValidIds(registry, System.err);
- System.exit(ERRNO_INVALID_ARGS);
- } else {
- mFlags.getSuppressedIds().add(id);
- }
- }
- } else if (arg.equals(ARG_ENABLE)) {
- if (index == args.length - 1) {
- System.err.println("Missing categories or id's to enable");
- System.exit(ERRNO_INVALID_ARGS);
- }
- IssueRegistry registry = getGlobalRegistry(client);
- String[] ids = args[++index].split(",");
- for (String id : ids) {
- if (registry.isCategoryName(id)) {
- // Enable all issues with the given category
- String category = id;
- for (Issue issue : registry.getIssues()) {
- if (issue.getCategory().getName().startsWith(category) ||
- issue.getCategory().getFullName().startsWith(category)) {
- mFlags.getEnabledIds().add(issue.getId());
- }
- }
- } else if (!registry.isIssueId(id)) {
- System.err.println("Invalid id or category \"" + id + "\".\n");
- displayValidIds(registry, System.err);
- System.exit(ERRNO_INVALID_ARGS);
- } else {
- mFlags.getEnabledIds().add(id);
- }
- }
- } else if (arg.equals(ARG_CHECK)) {
- if (index == args.length - 1) {
- System.err.println("Missing categories or id's to check");
- System.exit(ERRNO_INVALID_ARGS);
- }
- Set<String> checkedIds = mFlags.getExactCheckedIds();
- if (checkedIds == null) {
- checkedIds = new HashSet<String>();
- mFlags.setExactCheckedIds(checkedIds);
- }
- IssueRegistry registry = getGlobalRegistry(client);
- String[] ids = args[++index].split(",");
- for (String id : ids) {
- if (registry.isCategoryName(id)) {
- // Check all issues with the given category
- String category = id;
- for (Issue issue : registry.getIssues()) {
- // Check prefix such that filtering on the "Usability" category
- // will match issue category "Usability:Icons" etc.
- if (issue.getCategory().getName().startsWith(category) ||
- issue.getCategory().getFullName().startsWith(category)) {
- checkedIds.add(issue.getId());
- }
- }
- } else if (!registry.isIssueId(id)) {
- System.err.println("Invalid id or category \"" + id + "\".\n");
- displayValidIds(registry, System.err);
- System.exit(ERRNO_INVALID_ARGS);
- } else {
- checkedIds.add(id);
- }
- }
- } else if (arg.equals(ARG_NO_WARN_1) || arg.equals(ARG_NO_WARN_2)) {
- mFlags.setIgnoreWarnings(true);
- } else if (arg.equals(ARG_WARN_ALL)) {
- mFlags.setCheckAllWarnings(true);
- } else if (arg.equals(ARG_ALL_ERROR)) {
- mFlags.setWarningsAsErrors(true);
- } else if (arg.equals(ARG_CLASSES)) {
- if (index == args.length - 1) {
- System.err.println("Missing class folder name");
- System.exit(ERRNO_INVALID_ARGS);
- }
- String paths = args[++index];
- for (String path : LintUtils.splitPath(paths)) {
- File input = getInArgumentPath(path);
- if (!input.exists()) {
- System.err.println("Class path entry " + input + " does not exist.");
- System.exit(ERRNO_INVALID_ARGS);
- }
- List<File> classes = mFlags.getClassesOverride();
- if (classes == null) {
- classes = new ArrayList<File>();
- mFlags.setClassesOverride(classes);
- }
- classes.add(input);
- }
- } else if (arg.equals(ARG_SOURCES)) {
- if (index == args.length - 1) {
- System.err.println("Missing source folder name");
- System.exit(ERRNO_INVALID_ARGS);
- }
- String paths = args[++index];
- for (String path : LintUtils.splitPath(paths)) {
- File input = getInArgumentPath(path);
- if (!input.exists()) {
- System.err.println("Source folder " + input + " does not exist.");
- System.exit(ERRNO_INVALID_ARGS);
- }
- List<File> sources = mFlags.getSourcesOverride();
- if (sources == null) {
- sources = new ArrayList<File>();
- mFlags.setSourcesOverride(sources);
- }
- sources.add(input);
- }
- } else if (arg.equals(ARG_RESOURCES)) {
- if (index == args.length - 1) {
- System.err.println("Missing resource folder name");
- System.exit(ERRNO_INVALID_ARGS);
- }
- String paths = args[++index];
- for (String path : LintUtils.splitPath(paths)) {
- File input = getInArgumentPath(path);
- if (!input.exists()) {
- System.err.println("Resource folder " + input + " does not exist.");
- System.exit(ERRNO_INVALID_ARGS);
- }
- List<File> resources = mFlags.getResourcesOverride();
- if (resources == null) {
- resources = new ArrayList<File>();
- mFlags.setResourcesOverride(resources);
- }
- resources.add(input);
- }
- } else if (arg.equals(ARG_LIBRARIES)) {
- if (index == args.length - 1) {
- System.err.println("Missing library folder name");
- System.exit(ERRNO_INVALID_ARGS);
- }
- String paths = args[++index];
- for (String path : LintUtils.splitPath(paths)) {
- File input = getInArgumentPath(path);
- if (!input.exists()) {
- System.err.println("Library " + input + " does not exist.");
- System.exit(ERRNO_INVALID_ARGS);
- }
- List<File> libraries = mFlags.getLibrariesOverride();
- if (libraries == null) {
- libraries = new ArrayList<File>();
- mFlags.setLibrariesOverride(libraries);
- }
- libraries.add(input);
- }
- } else if (arg.startsWith("--")) {
- System.err.println("Invalid argument " + arg + "\n");
- printUsage(System.err);
- System.exit(ERRNO_INVALID_ARGS);
- } else {
- String filename = arg;
- File file = getInArgumentPath(filename);
-
- if (!file.exists()) {
- System.err.println(String.format("%1$s does not exist.", filename));
- System.exit(ERRNO_EXISTS);
- }
- files.add(file);
- }
- }
-
- if (files.isEmpty()) {
- System.err.println("No files to analyze.");
- System.exit(ERRNO_INVALID_ARGS);
- } else if (files.size() > 1
- && (mFlags.getClassesOverride() != null
- || mFlags.getSourcesOverride() != null
- || mFlags.getLibrariesOverride() != null
- || mFlags.getResourcesOverride() != null)) {
- System.err.println(String.format(
- "The %1$s, %2$s, %3$s and %4$s arguments can only be used with a single project",
- ARG_SOURCES, ARG_CLASSES, ARG_LIBRARIES, ARG_RESOURCES));
- System.exit(ERRNO_INVALID_ARGS);
- }
-
- List<Reporter> reporters = mFlags.getReporters();
- if (reporters.isEmpty()) {
- //noinspection VariableNotUsedInsideIf
- if (urlMap != null) {
- System.err.println(String.format(
- "Warning: The %1$s option only applies to HTML reports (%2$s)",
- ARG_URL, ARG_HTML));
- }
-
- reporters.add(new TextReporter(client, mFlags,
- new PrintWriter(System.out, true), false));
- } else {
- //noinspection VariableNotUsedInsideIf
- if (urlMap != null) {
- for (Reporter reporter : reporters) {
- if (!reporter.isSimpleFormat()) {
- reporter.setBundleResources(true);
- }
- }
-
- if (!urlMap.equals(VALUE_NONE)) {
- Map<String, String> map = new HashMap<String, String>();
- String[] replace = urlMap.split(","); //$NON-NLS-1$
- for (String s : replace) {
- // Allow ='s in the suffix part
- int index = s.indexOf('=');
- if (index == -1) {
- System.err.println(
- "The URL map argument must be of the form 'path_prefix=url_prefix'");
- System.exit(ERRNO_INVALID_ARGS);
- }
- String key = s.substring(0, index);
- String value = s.substring(index + 1);
- map.put(key, value);
- }
- for (Reporter reporter : reporters) {
- reporter.setUrlMap(map);
- }
- }
- }
- }
-
- try {
- // Not using mGlobalRegistry; LintClient will do its own registry merging
- // also including project rules.
- int exitCode = client.run(new BuiltinIssueRegistry(), files);
- System.exit(exitCode);
- } catch (IOException e) {
- log(e, null);
- System.exit(ERRNO_INVALID_ARGS);
- }
- }
-
- private IssueRegistry getGlobalRegistry(LintCliClient client) {
- if (mGlobalRegistry == null) {
- mGlobalRegistry = client.addCustomLintRules(new BuiltinIssueRegistry());
- }
-
- return mGlobalRegistry;
- }
-
- /**
- * Converts a relative or absolute command-line argument into an input file.
- *
- * @param filename The filename given as a command-line argument.
- * @return A File matching filename, either absolute or relative to lint.workdir if defined.
- */
- private static File getInArgumentPath(String filename) {
- File file = new File(filename);
-
- if (!file.isAbsolute()) {
- File workDir = getLintWorkDir();
- if (workDir != null) {
- File file2 = new File(workDir, filename);
- if (file2.exists()) {
- try {
- file = file2.getCanonicalFile();
- } catch (IOException e) {
- file = file2;
- }
- }
- }
- }
- return file;
- }
-
- /**
- * Converts a relative or absolute command-line argument into an output file.
- * <p/>
- * The difference with {@code getInArgumentPath} is that we can't check whether the
- * a relative path turned into an absolute compared to lint.workdir actually exists.
- *
- * @param filename The filename given as a command-line argument.
- * @return A File matching filename, either absolute or relative to lint.workdir if defined.
- */
- private static File getOutArgumentPath(String filename) {
- File file = new File(filename);
-
- if (!file.isAbsolute()) {
- File workDir = getLintWorkDir();
- if (workDir != null) {
- File file2 = new File(workDir, filename);
- try {
- file = file2.getCanonicalFile();
- } catch (IOException e) {
- file = file2;
- }
- }
- }
- return file;
- }
-
- /**
- * Returns the File corresponding to the system property or the environment variable
- * for {@link #PROP_WORK_DIR}.
- * This property is typically set by the SDK/tools/lint[.bat] wrapper.
- * It denotes the path where the command-line client was originally invoked from
- * and can be used to convert relative input/output paths.
- *
- * @return A new File corresponding to {@link #PROP_WORK_DIR} or null.
- */
- @Nullable
- private static File getLintWorkDir() {
- // First check the Java properties (e.g. set using "java -jar ... -Dname=value")
- String path = System.getProperty(PROP_WORK_DIR);
- if (path == null || path.isEmpty()) {
- // If not found, check environment variables.
- path = System.getenv(PROP_WORK_DIR);
- }
- if (path != null && !path.isEmpty()) {
- return new File(path);
- }
- return null;
- }
-
- private static void printHelpTopicSuppress() {
- System.out.println(wrap(TextFormat.RAW.convertTo(getSuppressHelp(), TextFormat.TEXT)));
- }
-
- static String getSuppressHelp() {
- // \\u00a0 is a non-breaking space
- final String NBSP = "\u00a0\u00a0\u00a0\u00a0";
-
- return
- "Lint errors can be suppressed in a variety of ways:\n" +
- "\n" +
- "1. With a `@SuppressLint` annotation in the Java code\n" +
- "2. With a `tools:ignore` attribute in the XML file\n" +
- "3. With ignore flags specified in the `build.gradle` file, " +
- "as explained below\n" +
- "4. With a `lint.xml` configuration file in the project\n" +
- "5. With a `lint.xml` configuration file passed to lint " +
- "via the " + ARG_CONFIG + " flag\n" +
- "6. With the " + ARG_IGNORE + " flag passed to lint.\n" +
- "\n" +
- "To suppress a lint warning with an annotation, add " +
- "a `@SuppressLint(\"id\")` annotation on the class, method " +
- "or variable declaration closest to the warning instance " +
- "you want to disable. The id can be one or more issue " +
- "id's, such as `\"UnusedResources\"` or `{\"UnusedResources\"," +
- "\"UnusedIds\"}`, or it can be `\"all\"` to suppress all lint " +
- "warnings in the given scope.\n" +
- "\n" +
- "To suppress a lint warning in an XML file, add a " +
- "`tools:ignore=\"id\"` attribute on the element containing " +
- "the error, or one of its surrounding elements. You also " +
- "need to define the namespace for the tools prefix on the " +
- "root element in your document, next to the `xmlns:android` " +
- "declaration:\n" +
- "`xmlns:tools=\"http://schemas.android.com/tools\"`\n" +
- "\n" +
- "To suppress a lint warning in a `build.gradle` file, add a " +
- "section like this:\n" +
- "\n" +
- "android {\n" +
- NBSP + "lintOptions {\n" +
- NBSP + NBSP + "disable 'TypographyFractions','TypographyQuotes'\n" +
- NBSP + "}\n" +
- "}\n" +
- "\n" +
- "Here we specify a comma separated list of issue id's after the " +
- "disable command. You can also use `warning` or `error` instead " +
- "of `disable` to change the severity of issues.\n" +
- "\n" +
- "To suppress lint warnings with a configuration XML file, " +
- "create a file named `lint.xml` and place it at the root " +
- "directory of the project in which it applies.\n" +
- "\n" +
- "The format of the `lint.xml` file is something like the " +
- "following:\n" +
- "\n" +
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
- "<lint>\n" +
- NBSP + "<!-- Disable this given check in this project -->\n" +
- NBSP + "<issue id=\"IconMissingDensityFolder\" severity=\"ignore\" />\n" +
- "\n" +
- NBSP + "<!-- Ignore the ObsoleteLayoutParam issue in the given files -->\n" +
- NBSP + "<issue id=\"ObsoleteLayoutParam\">\n" +
- NBSP + NBSP + "<ignore path=\"res/layout/activation.xml\" />\n" +
- NBSP + NBSP + "<ignore path=\"res/layout-xlarge/activation.xml\" />\n" +
- NBSP + "</issue>\n" +
- "\n" +
- NBSP + "<!-- Ignore the UselessLeaf issue in the given file -->\n" +
- NBSP + "<issue id=\"UselessLeaf\">\n" +
- NBSP + NBSP + "<ignore path=\"res/layout/main.xml\" />\n" +
- NBSP + "</issue>\n" +
- "\n" +
- NBSP + "<!-- Change the severity of hardcoded strings to \"error\" -->\n" +
- NBSP + "<issue id=\"HardcodedText\" severity=\"error\" />\n" +
- "</lint>\n" +
- "\n" +
- "To suppress lint checks from the command line, pass the " + ARG_IGNORE + " " +
- "flag with a comma separated list of ids to be suppressed, such as:\n" +
- "`$ lint --ignore UnusedResources,UselessLeaf /my/project/path`\n" +
- "\n" +
- "For more information, see " +
- "http://g.co/androidstudio/suppressing-lint-warnings\n";
- }
-
- private static void printVersion(LintCliClient client) {
- String revision = client.getRevision();
- if (revision != null) {
- System.out.println(String.format("lint: version %1$s", revision));
- } else {
- System.out.println("lint: unknown version");
- }
- }
-
- private static void displayValidIds(IssueRegistry registry, PrintStream out) {
- List<Category> categories = registry.getCategories();
- out.println("Valid issue categories:");
- for (Category category : categories) {
- out.println(" " + category.getFullName());
- }
- out.println();
- List<Issue> issues = registry.getIssues();
- out.println("Valid issue id's:");
- for (Issue issue : issues) {
- listIssue(out, issue);
- }
- }
-
- private static void listIssue(PrintStream out, Issue issue) {
- out.print(wrapArg("\"" + issue.getId() + "\": " + issue.getBriefDescription(TEXT)));
- }
-
- private static void showIssues(IssueRegistry registry) {
- List<Issue> issues = registry.getIssues();
- List<Issue> sorted = new ArrayList<Issue>(issues);
- Collections.sort(sorted, new Comparator<Issue>() {
- @Override
- public int compare(Issue issue1, Issue issue2) {
- int d = issue1.getCategory().compareTo(issue2.getCategory());
- if (d != 0) {
- return d;
- }
- d = issue2.getPriority() - issue1.getPriority();
- if (d != 0) {
- return d;
- }
-
- return issue1.getId().compareTo(issue2.getId());
- }
- });
-
- System.out.println("Available issues:\n");
- Category previousCategory = null;
- for (Issue issue : sorted) {
- Category category = issue.getCategory();
- if (!category.equals(previousCategory)) {
- String name = category.getFullName();
- System.out.println(name);
- for (int i = 0, n = name.length(); i < n; i++) {
- System.out.print('=');
- }
- System.out.println('\n');
- previousCategory = category;
- }
-
- describeIssue(issue);
- System.out.println();
- }
- }
-
- private static void describeIssue(Issue issue) {
- System.out.println(issue.getId());
- for (int i = 0; i < issue.getId().length(); i++) {
- System.out.print('-');
- }
- System.out.println();
- System.out.println(wrap("Summary: " + issue.getBriefDescription(TEXT)));
- System.out.println("Priority: " + issue.getPriority() + " / 10");
- System.out.println("Severity: " + issue.getDefaultSeverity().getDescription());
- System.out.println("Category: " + issue.getCategory().getFullName());
-
- if (!issue.isEnabledByDefault()) {
- System.out.println("NOTE: This issue is disabled by default!");
- System.out.println(String.format("You can enable it by adding %1$s %2$s", ARG_ENABLE,
- issue.getId()));
- }
-
- System.out.println();
- System.out.println(wrap(issue.getExplanation(TEXT)));
- List<String> moreInfo = issue.getMoreInfo();
- if (!moreInfo.isEmpty()) {
- System.out.println("More information: ");
- for (String uri : moreInfo) {
- System.out.println(uri);
- }
- }
- }
-
- static String wrapArg(String explanation) {
- // Wrap arguments such that the wrapped lines are not showing up in the left column
- return wrap(explanation, MAX_LINE_WIDTH, " ");
- }
-
- static String wrap(String explanation) {
- return wrap(explanation, MAX_LINE_WIDTH, "");
- }
-
- static String wrap(String explanation, int lineWidth, String hangingIndent) {
- return SdkUtils.wrap(explanation, lineWidth, hangingIndent);
- }
-
- private static void printUsage(PrintStream out) {
- // TODO: Look up launcher script name!
- String command = "lint"; //$NON-NLS-1$
-
- out.println("Usage: " + command + " [flags] <project directories>\n");
- out.println("Flags:\n");
-
- printUsage(out, new String[] {
- ARG_HELP, "This message.",
- ARG_HELP + " <topic>", "Help on the given topic, such as \"suppress\".",
- ARG_LIST_IDS, "List the available issue id's and exit.",
- ARG_VERSION, "Output version information and exit.",
- ARG_EXIT_CODE, "Set the exit code to " + ERRNO_ERRORS + " if errors are found.",
- ARG_SHOW, "List available issues along with full explanations.",
- ARG_SHOW + " <ids>", "Show full explanations for the given list of issue id's.",
-
- "", "\nEnabled Checks:",
- ARG_DISABLE + " <list>", "Disable the list of categories or " +
- "specific issue id's. The list should be a comma-separated list of issue " +
- "id's or categories.",
- ARG_ENABLE + " <list>", "Enable the specific list of issues. " +
- "This checks all the default issues plus the specifically enabled issues. The " +
- "list should be a comma-separated list of issue id's or categories.",
- ARG_CHECK + " <list>", "Only check the specific list of issues. " +
- "This will disable everything and re-enable the given list of issues. " +
- "The list should be a comma-separated list of issue id's or categories.",
- ARG_NO_WARN_1 + ", " + ARG_NO_WARN_2, "Only check for errors (ignore warnings)",
- ARG_WARN_ALL, "Check all warnings, including those off by default",
- ARG_ALL_ERROR, "Treat all warnings as errors",
- ARG_CONFIG + " <filename>", "Use the given configuration file to " +
- "determine whether issues are enabled or disabled. If a project contains " +
- "a lint.xml file, then this config file will be used as a fallback.",
-
-
- "", "\nOutput Options:",
- ARG_QUIET, "Don't show progress.",
- ARG_FULL_PATH, "Use full paths in the error output.",
- ARG_SHOW_ALL, "Do not truncate long messages, lists of alternate locations, etc.",
- ARG_NO_LINES, "Do not include the source file lines with errors " +
- "in the output. By default, the error output includes snippets of source code " +
- "on the line containing the error, but this flag turns it off.",
- ARG_HTML + " <filename>", "Create an HTML report instead. If the filename is a " +
- "directory (or a new filename without an extension), lint will create a " +
- "separate report for each scanned project.",
- ARG_URL + " filepath=url", "Add links to HTML report, replacing local " +
- "path prefixes with url prefix. The mapping can be a comma-separated list of " +
- "path prefixes to corresponding URL prefixes, such as " +
- "C:\\temp\\Proj1=http://buildserver/sources/temp/Proj1. To turn off linking " +
- "to files, use " + ARG_URL + " " + VALUE_NONE,
- ARG_SIMPLE_HTML + " <filename>", "Create a simple HTML report",
- ARG_XML + " <filename>", "Create an XML report instead.",
-
- "", "\nProject Options:",
- ARG_RESOURCES + " <dir>", "Add the given folder (or path) as a resource directory " +
- "for the project. Only valid when running lint on a single project.",
- ARG_SOURCES + " <dir>", "Add the given folder (or path) as a source directory for " +
- "the project. Only valid when running lint on a single project.",
- ARG_CLASSES + " <dir>", "Add the given folder (or jar file, or path) as a class " +
- "directory for the project. Only valid when running lint on a single project.",
- ARG_LIBRARIES + " <dir>", "Add the given folder (or jar file, or path) as a class " +
- "library for the project. Only valid when running lint on a single project.",
-
- "", "\nExit Status:",
- "0", "Success.",
- Integer.toString(ERRNO_ERRORS), "Lint errors detected.",
- Integer.toString(ERRNO_USAGE), "Lint usage.",
- Integer.toString(ERRNO_EXISTS), "Cannot clobber existing file.",
- Integer.toString(ERRNO_HELP), "Lint help.",
- Integer.toString(ERRNO_INVALID_ARGS), "Invalid command-line argument.",
- });
- }
-
- private static void printUsage(PrintStream out, String[] args) {
- int argWidth = 0;
- for (int i = 0; i < args.length; i += 2) {
- String arg = args[i];
- argWidth = Math.max(argWidth, arg.length());
- }
- argWidth += 2;
- StringBuilder sb = new StringBuilder(20);
- for (int i = 0; i < argWidth; i++) {
- sb.append(' ');
- }
- String indent = sb.toString();
- String formatString = "%1$-" + argWidth + "s%2$s"; //$NON-NLS-1$
-
- for (int i = 0; i < args.length; i += 2) {
- String arg = args[i];
- String description = args[i + 1];
- if (arg.isEmpty()) {
- out.println(description);
- } else {
- out.print(wrap(String.format(formatString, arg, description),
- MAX_LINE_WIDTH, indent));
- }
- }
- }
-
- public void log(
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args) {
- System.out.flush();
- if (!mFlags.isQuiet()) {
- // Place the error message on a line of its own since we're printing '.' etc
- // with newlines during analysis
- System.err.println();
- }
- if (format != null) {
- System.err.println(String.format(format, args));
- }
- if (exception != null) {
- exception.printStackTrace();
- }
- }
-}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/Reporter.java b/base/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
deleted file mode 100644
index 8a0b92b..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.SdkConstants.CURRENT_PLATFORM;
-import static com.android.SdkConstants.DOT_9PNG;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.PLATFORM_LINUX;
-import static com.android.SdkConstants.UTF_8;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-import static java.io.File.separatorChar;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.checks.AccessibilityDetector;
-import com.android.tools.lint.checks.AlwaysShowActionDetector;
-import com.android.tools.lint.checks.ApiDetector;
-import com.android.tools.lint.checks.AppCompatCallDetector;
-import com.android.tools.lint.checks.ByteOrderMarkDetector;
-import com.android.tools.lint.checks.CommentDetector;
-import com.android.tools.lint.checks.DetectMissingPrefix;
-import com.android.tools.lint.checks.DosLineEndingDetector;
-import com.android.tools.lint.checks.DuplicateResourceDetector;
-import com.android.tools.lint.checks.GradleDetector;
-import com.android.tools.lint.checks.GridLayoutDetector;
-import com.android.tools.lint.checks.HardcodedValuesDetector;
-import com.android.tools.lint.checks.IncludeDetector;
-import com.android.tools.lint.checks.InefficientWeightDetector;
-import com.android.tools.lint.checks.JavaPerformanceDetector;
-import com.android.tools.lint.checks.ManifestDetector;
-import com.android.tools.lint.checks.MissingClassDetector;
-import com.android.tools.lint.checks.MissingIdDetector;
-import com.android.tools.lint.checks.NamespaceDetector;
-import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector;
-import com.android.tools.lint.checks.PropertyFileDetector;
-import com.android.tools.lint.checks.PxUsageDetector;
-import com.android.tools.lint.checks.ScrollViewChildDetector;
-import com.android.tools.lint.checks.SecurityDetector;
-import com.android.tools.lint.checks.SharedPrefsDetector;
-import com.android.tools.lint.checks.SignatureOrSystemDetector;
-import com.android.tools.lint.checks.SupportAnnotationDetector;
-import com.android.tools.lint.checks.TextFieldDetector;
-import com.android.tools.lint.checks.TextViewDetector;
-import com.android.tools.lint.checks.TitleDetector;
-import com.android.tools.lint.checks.TranslationDetector;
-import com.android.tools.lint.checks.TypoDetector;
-import com.android.tools.lint.checks.TypographyDetector;
-import com.android.tools.lint.checks.UseCompoundDrawableDetector;
-import com.android.tools.lint.checks.UselessViewDetector;
-import com.android.tools.lint.checks.Utf8Detector;
-import com.android.tools.lint.checks.WrongCallDetector;
-import com.android.tools.lint.checks.WrongCaseDetector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.utils.SdkUtils;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closer;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-/** A reporter is an output generator for lint warnings
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public abstract class Reporter {
- protected final LintCliClient mClient;
- protected final File mOutput;
- protected String mTitle = "Lint Report";
- protected boolean mSimpleFormat;
- protected boolean mBundleResources;
- protected Map<String, String> mUrlMap;
- protected File mResources;
- protected final Map<File, String> mResourceUrl = new HashMap<File, String>();
- protected final Map<String, File> mNameToFile = new HashMap<String, File>();
- protected boolean mDisplayEmpty = true;
-
- /**
- * Write the given warnings into the report
- *
- * @param errorCount the number of errors
- * @param warningCount the number of warnings
- * @param issues the issues to be reported
- * @throws IOException if an error occurs
- */
- public abstract void write(int errorCount, int warningCount, List<Warning> issues)
- throws IOException;
-
- protected Reporter(LintCliClient client, File output) {
- mClient = client;
- mOutput = output;
- }
-
- /**
- * Sets the report title
- *
- * @param title the title of the report
- */
- public void setTitle(String title) {
- mTitle = title;
- }
-
- /** @return the title of the report */
- public String getTitle() {
- return mTitle;
- }
-
- /**
- * Sets whether the report should bundle up resources along with the HTML report.
- * This implies a non-simple format (see {@link #setSimpleFormat(boolean)}).
- *
- * @param bundleResources if true, copy images into a directory relative to
- * the report
- */
- public void setBundleResources(boolean bundleResources) {
- mBundleResources = bundleResources;
- mSimpleFormat = false;
- }
-
- /**
- * Sets whether the report should use simple formatting (meaning no JavaScript,
- * embedded images, etc).
- *
- * @param simpleFormat whether the formatting should be simple
- */
- public void setSimpleFormat(boolean simpleFormat) {
- mSimpleFormat = simpleFormat;
- }
-
- /**
- * Returns whether the report should use simple formatting (meaning no JavaScript,
- * embedded images, etc).
- *
- * @return whether the report should use simple formatting
- */
- public boolean isSimpleFormat() {
- return mSimpleFormat;
- }
-
-
- String getUrl(File file) {
- if (mBundleResources && !mSimpleFormat) {
- String url = getRelativeResourceUrl(file);
- if (url != null) {
- return url;
- }
- }
-
- if (mUrlMap != null) {
- String path = file.getAbsolutePath();
- // Perform the comparison using URLs such that we properly escape spaces etc.
- String pathUrl = encodeUrl(path);
- for (Map.Entry<String, String> entry : mUrlMap.entrySet()) {
- String prefix = entry.getKey();
- String prefixUrl = encodeUrl(prefix);
- if (pathUrl.startsWith(prefixUrl)) {
- String relative = pathUrl.substring(prefixUrl.length());
- return entry.getValue() + relative;
- }
- }
- }
-
- if (file.isAbsolute()) {
- String relativePath = getRelativePath(mOutput.getParentFile(), file);
- if (relativePath != null) {
- relativePath = relativePath.replace(separatorChar, '/');
- return encodeUrl(relativePath);
- }
- }
-
- try {
- return SdkUtils.fileToUrlString(file);
- } catch (MalformedURLException e) {
- return null;
- }
- }
-
- /** Encodes the given String as a safe URL substring, escaping spaces etc */
- static String encodeUrl(String url) {
- try {
- url = url.replace('\\', '/');
- return URLEncoder.encode(url, UTF_8).replace("%2F", "/"); //$NON-NLS-1$
- } catch (UnsupportedEncodingException e) {
- // This shouldn't happen for UTF-8
- System.err.println("Invalid string " + e.getLocalizedMessage());
- return url;
- }
- }
-
- /** Set mapping of path prefixes to corresponding URLs in the HTML report */
- public void setUrlMap(@Nullable Map<String, String> urlMap) {
- mUrlMap = urlMap;
- }
-
- /** Gets a pointer to the local resource directory, if any */
- File getResourceDir() {
- if (mResources == null && mBundleResources) {
- mResources = computeResourceDir();
- if (mResources == null) {
- mBundleResources = false;
- }
- }
-
- return mResources;
- }
-
- /** Finds/creates the local resource directory, if possible */
- File computeResourceDir() {
- String fileName = mOutput.getName();
- int dot = fileName.indexOf('.');
- if (dot != -1) {
- fileName = fileName.substring(0, dot);
- }
-
- File resources = new File(mOutput.getParentFile(), fileName + "_files"); //$NON-NLS-1$
- if (!resources.exists() && !resources.mkdir()) {
- resources = null;
- }
-
- return resources;
- }
-
- /** Returns a URL to a local copy of the given file, or null */
- protected String getRelativeResourceUrl(File file) {
- String resource = mResourceUrl.get(file);
- if (resource != null) {
- return resource;
- }
-
- String name = file.getName();
- if (!endsWith(name, DOT_PNG) || endsWith(name, DOT_9PNG)) {
- return null;
- }
-
- // Attempt to make local copy
- File resourceDir = getResourceDir();
- if (resourceDir != null) {
- String base = file.getName();
-
- File path = mNameToFile.get(base);
- if (path != null && !path.equals(file)) {
- // That filename already exists and is associated with a different path:
- // make a new unique version
- for (int i = 0; i < 100; i++) {
- base = '_' + base;
- path = mNameToFile.get(base);
- if (path == null || path.equals(file)) {
- break;
- }
- }
- }
-
- File target = new File(resourceDir, base);
- try {
- Files.copy(file, target);
- } catch (IOException e) {
- return null;
- }
- return resourceDir.getName() + '/' + encodeUrl(base);
- }
- return null;
- }
-
- /** Returns a URL to a local copy of the given resource, or null. There is
- * no filename conflict resolution. */
- protected String addLocalResources(URL url) throws IOException {
- // Attempt to make local copy
- File resourceDir = computeResourceDir();
- if (resourceDir != null) {
- String base = url.getFile();
- base = base.substring(base.lastIndexOf('/') + 1);
- mNameToFile.put(base, new File(url.toExternalForm()));
-
- File target = new File(resourceDir, base);
- Closer closer = Closer.create();
- try {
- FileOutputStream output = closer.register(new FileOutputStream(target));
- InputStream input = closer.register(url.openStream());
- ByteStreams.copy(input, output);
- } catch (Throwable e) {
- closer.rethrow(e);
- } finally {
- closer.close();
- }
- return resourceDir.getName() + '/' + encodeUrl(base);
- }
- return null;
- }
-
- // Based on similar code in com.intellij.openapi.util.io.FileUtilRt
- @Nullable
- static String getRelativePath(File base, File file) {
- if (base == null || file == null) {
- return null;
- }
- if (!base.isDirectory()) {
- base = base.getParentFile();
- if (base == null) {
- return null;
- }
- }
- if (base.equals(file)) {
- return ".";
- }
-
- final String filePath = file.getAbsolutePath();
- String basePath = base.getAbsolutePath();
-
- // TODO: Make this return null if we go all the way to the root!
-
- basePath = !basePath.isEmpty() && basePath.charAt(basePath.length() - 1) == separatorChar
- ? basePath : basePath + separatorChar;
-
- // Whether filesystem is case sensitive. Technically on OSX you could create a
- // sensitive one, but it's not the default.
- boolean caseSensitive = CURRENT_PLATFORM == PLATFORM_LINUX;
- Locale l = Locale.getDefault();
- String basePathToCompare = caseSensitive ? basePath : basePath.toLowerCase(l);
- String filePathToCompare = caseSensitive ? filePath : filePath.toLowerCase(l);
- if (basePathToCompare.equals(!filePathToCompare.isEmpty()
- && filePathToCompare.charAt(filePathToCompare.length() - 1) == separatorChar
- ? filePathToCompare : filePathToCompare + separatorChar)) {
- return ".";
- }
- int len = 0;
- int lastSeparatorIndex = 0;
- // bug in inspection; see http://youtrack.jetbrains.com/issue/IDEA-118971
- //noinspection ConstantConditions
- while (len < filePath.length() && len < basePath.length()
- && filePathToCompare.charAt(len) == basePathToCompare.charAt(len)) {
- if (basePath.charAt(len) == separatorChar) {
- lastSeparatorIndex = len;
- }
- len++;
- }
- if (len == 0) {
- return null;
- }
-
- StringBuilder relativePath = new StringBuilder();
- for (int i = len; i < basePath.length(); i++) {
- if (basePath.charAt(i) == separatorChar) {
- relativePath.append("..");
- relativePath.append(separatorChar);
- }
- }
- relativePath.append(filePath.substring(lastSeparatorIndex + 1));
- return relativePath.toString();
- }
-
- /**
- * Returns whether this report should display info (such as a path to the report) if
- * no issues were found
- */
- public boolean isDisplayEmpty() {
- return mDisplayEmpty;
- }
-
- /**
- * Sets whether this report should display info (such as a path to the report) if
- * no issues were found
- */
- public void setDisplayEmpty(boolean displayEmpty) {
- mDisplayEmpty = displayEmpty;
- }
-
- private static Set<Issue> sAdtFixes;
- private static Set<Issue> sStudioFixes;
-
- /** Tools known to have quickfixes for lint */
- enum QuickfixHandler {
- /** Android Studio or IntelliJ */
- STUDIO,
- /** Eclipse */
- ADT;
-
- public boolean hasAutoFix(Issue issue) {
- return Reporter.hasAutoFix(this, issue);
- }
- }
-
- /**
- * Returns true if the given issue has an automatic IDE fix.
- *
- * @param tool the name of the tool to be checked
- * @param issue the issue to be checked
- * @return true if the given tool is known to have an automatic fix for the
- * given issue
- */
- public static boolean hasAutoFix(@NonNull QuickfixHandler tool, Issue issue) {
- if (tool == QuickfixHandler.ADT) {
- if (sAdtFixes == null) {
- sAdtFixes = Sets.newHashSet(
- InefficientWeightDetector.INEFFICIENT_WEIGHT,
- AccessibilityDetector.ISSUE,
- InefficientWeightDetector.BASELINE_WEIGHTS,
- HardcodedValuesDetector.ISSUE,
- UselessViewDetector.USELESS_LEAF,
- UselessViewDetector.USELESS_PARENT,
- PxUsageDetector.PX_ISSUE,
- TextFieldDetector.ISSUE,
- SecurityDetector.EXPORTED_SERVICE,
- DetectMissingPrefix.MISSING_NAMESPACE,
- ScrollViewChildDetector.ISSUE,
- ObsoleteLayoutParamsDetector.ISSUE,
- TypographyDetector.DASHES,
- TypographyDetector.ELLIPSIS,
- TypographyDetector.FRACTIONS,
- TypographyDetector.OTHER,
- TypographyDetector.QUOTES,
- UseCompoundDrawableDetector.ISSUE,
- ApiDetector.UNSUPPORTED,
- ApiDetector.INLINED,
- TypoDetector.ISSUE,
- ManifestDetector.ALLOW_BACKUP,
- MissingIdDetector.ISSUE,
- TranslationDetector.MISSING,
- DosLineEndingDetector.ISSUE
- );
- }
- return sAdtFixes.contains(issue);
- } else if (tool == QuickfixHandler.STUDIO) {
- // List generated by AndroidLintInspectionToolProviderTest in tools/adt/idea;
- // set LIST_ISSUES_WITH_QUICK_FIXES to true
- if (sStudioFixes == null) {
- sStudioFixes = Sets.newHashSet(
- AccessibilityDetector.ISSUE,
- AlwaysShowActionDetector.ISSUE,
- ApiDetector.INLINED,
- ApiDetector.UNSUPPORTED,
- AppCompatCallDetector.ISSUE,
- ByteOrderMarkDetector.BOM,
- CommentDetector.STOP_SHIP,
- DetectMissingPrefix.MISSING_NAMESPACE,
- DuplicateResourceDetector.TYPE_MISMATCH,
- GradleDetector.COMPATIBILITY,
- GradleDetector.DEPENDENCY,
- GradleDetector.DEPRECATED,
- GradleDetector.PLUS,
- GradleDetector.REMOTE_VERSION,
- GradleDetector.STRING_INTEGER,
- GridLayoutDetector.ISSUE,
- IncludeDetector.ISSUE,
- InefficientWeightDetector.BASELINE_WEIGHTS,
- InefficientWeightDetector.INEFFICIENT_WEIGHT,
- InefficientWeightDetector.ORIENTATION,
- JavaPerformanceDetector.USE_VALUE_OF,
- ManifestDetector.ALLOW_BACKUP,
- ManifestDetector.APPLICATION_ICON,
- ManifestDetector.MIPMAP,
- ManifestDetector.MOCK_LOCATION,
- ManifestDetector.TARGET_NEWER,
- MissingClassDetector.INNERCLASS,
- MissingIdDetector.ISSUE,
- NamespaceDetector.RES_AUTO,
- ObsoleteLayoutParamsDetector.ISSUE,
- PropertyFileDetector.ESCAPE,
- PropertyFileDetector.HTTP,
- PxUsageDetector.DP_ISSUE,
- PxUsageDetector.PX_ISSUE,
- ScrollViewChildDetector.ISSUE,
- SecurityDetector.EXPORTED_SERVICE,
- SharedPrefsDetector.ISSUE,
- SignatureOrSystemDetector.ISSUE,
- SupportAnnotationDetector.CHECK_PERMISSION,
- SupportAnnotationDetector.CHECK_RESULT,
- TextFieldDetector.ISSUE,
- TextViewDetector.SELECTABLE,
- TitleDetector.ISSUE,
- TypoDetector.ISSUE,
- TypographyDetector.DASHES,
- TypographyDetector.ELLIPSIS,
- TypographyDetector.FRACTIONS,
- TypographyDetector.OTHER,
- TypographyDetector.QUOTES,
- UselessViewDetector.USELESS_LEAF,
- Utf8Detector.ISSUE,
- WrongCallDetector.ISSUE,
- WrongCaseDetector.WRONG_CASE
- );
- }
- return sStudioFixes.contains(issue);
- }
-
- return false;
- }
-}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/Warning.java b/base/lint/cli/src/main/java/com/android/tools/lint/Warning.java
deleted file mode 100644
index 0a37058..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/Warning.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import com.android.annotations.NonNull;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A {@link Warning} represents a specific warning that a {@link LintClient}
- * has been told about. The context stores these as they are reported into a
- * list of warnings such that it can sort them all before presenting them all at
- * the end.
- */
-public class Warning implements Comparable<Warning> {
- public final Issue issue;
- public final String message;
- public final Severity severity;
- public final Project project;
- public AndroidProject gradleProject;
- public Location location;
- public File file;
- public String path;
- public int line = -1;
- public int offset = -1;
- public String errorLine;
- public String fileContents;
- public Set<Variant> variants;
-
- public Warning(Issue issue, String message, Severity severity, Project project) {
- this.issue = issue;
- this.message = message;
- this.severity = severity;
- this.project = project;
- }
-
- // ---- Implements Comparable<Warning> ----
- @SuppressWarnings({"VariableNotUsedInsideIf", "ConstantConditions"})
- @Override
- public int compareTo(@NonNull Warning other) {
- // Sort by category, then by priority, then by id,
- // then by file, then by line
- int categoryDelta = issue.getCategory().compareTo(other.issue.getCategory());
- if (categoryDelta != 0) {
- return categoryDelta;
- }
- // DECREASING priority order
- int priorityDelta = other.issue.getPriority() - issue.getPriority();
- if (priorityDelta != 0) {
- return priorityDelta;
- }
- String id1 = issue.getId();
- String id2 = other.issue.getId();
- assert id1 != null;
- assert id2 != null;
- int idDelta = id1.compareTo(id2);
- if (idDelta != 0) {
- return idDelta;
- }
- if (file != null) {
- if (other.file != null) {
- int fileDelta = file.getName().compareTo(
- other.file.getName());
- if (fileDelta != 0) {
- return fileDelta;
- }
- } else {
- return -1;
- }
- } else if (other.file != null) {
- return 1;
- }
- if (line != other.line) {
- return line - other.line;
- }
-
- int delta = message.compareTo(other.message);
- if (delta != 0) {
- return delta;
- }
-
- if (file != null) {
- if (other.file != null) {
- delta = file.compareTo(other.file);
- if (delta != 0) {
- return delta;
- }
- } else {
- return -1;
- }
- } else if (other.file != null) {
- return 1;
- }
-
- Location secondary1 = location != null ? location.getSecondary() : null;
- File secondaryFile1 = secondary1 != null ? secondary1.getFile() : null;
- Location secondary2 = other.location != null ? other.location.getSecondary() : null;
- File secondaryFile2 = secondary2 != null ? secondary2.getFile() : null;
- if (secondaryFile1 != null) {
- if (secondaryFile2 != null) {
- return secondaryFile1.compareTo(secondaryFile2);
- } else {
- return -1;
- }
- } else if (secondaryFile2 != null) {
- return 1;
- }
-
- // This handles the case where you have a huge XML document without hewlines,
- // such that all the errors end up on the same line.
- if (location != null && other.location != null &&
- location.getStart() != null && other.location.getStart() != null) {
- delta = location.getStart().getColumn() - other.location.getStart().getColumn();
- if (delta != 0) {
- return delta;
- }
- }
-
- return 0;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- Warning warning = (Warning) o;
-
- if (line != warning.line) {
- return false;
- }
- if (file != null ? !file.equals(warning.file) : warning.file != null) {
- return false;
- }
- if (!issue.getCategory().equals(warning.issue.getCategory())) {
- return false;
- }
- if (issue.getPriority() != warning.issue.getPriority()) {
- return false;
- }
- if (!issue.getId().equals(warning.issue.getId())) {
- return false;
- }
- //noinspection RedundantIfStatement
- if (!message.equals(warning.message)) {
- return false;
- }
-
- Location secondary1 = location != null ? location.getSecondary() : null;
- Location secondary2 = warning.location != null ? warning.location.getSecondary() : null;
- if (secondary1 != null) {
- if (secondary2 != null) {
- if (!Objects.equal(secondary1.getFile(), secondary2.getFile())) {
- return false;
- }
- } else {
- return false;
- }
- } else //noinspection VariableNotUsedInsideIf
- if (secondary2 != null) {
- return false;
- }
-
- // This handles the case where you have a huge XML document without hewlines,
- // such that all the errors end up on the same line.
- //noinspection RedundantIfStatement
- if (location != null && warning.location != null &&
- location.getStart() != null && warning.location.getStart() != null &&
- location.getStart().getColumn() != warning.location.getStart().getColumn()) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = message.hashCode();
- result = 31 * result + (file != null ? file.hashCode() : 0);
- return result;
- }
-
- public boolean isVariantSpecific() {
- return variants != null && variants.size() < gradleProject.getVariants().size();
- }
-
- public boolean includesMoreThanExcludes() {
- assert isVariantSpecific();
- int variantCount = variants.size();
- int allVariantCount = gradleProject.getVariants().size();
- return variantCount <= allVariantCount - variantCount;
- }
-
- public List<String> getIncludedVariantNames() {
- assert isVariantSpecific();
- List<String> names = new ArrayList<String>();
- if (variants != null) {
- for (Variant variant : variants) {
- names.add(variant.getName());
- }
- }
- Collections.sort(names);
- return names;
- }
-
- public List<String> getExcludedVariantNames() {
- assert isVariantSpecific();
- Collection<Variant> variants = gradleProject.getVariants();
- Set<String> allVariants = new HashSet<String>(variants.size());
- for (Variant variant : variants) {
- allVariants.add(variant.getName());
- }
- Set<String> included = new HashSet<String>(getIncludedVariantNames());
- Set<String> excluded = Sets.difference(allVariants, included);
- List<String> sorted = Lists.newArrayList(excluded);
- Collections.sort(sorted);
- return sorted;
- }
-
- @Override
- public String toString() {
- return "Warning{" +
- "issue=" + issue +
- ", message='" + message + '\'' +
- ", file=" + file +
- ", line=" + line +
- '}';
- }
-}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java b/base/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
deleted file mode 100644
index 1428d63..0000000
--- a/base/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.tools.lint.detector.api.TextFormat.RAW;
-
-import com.android.tools.lint.checks.BuiltinIssueRegistry;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Position;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.io.Files;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.List;
-
-/**
- * A reporter which emits lint results into an XML report.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class XmlReporter extends Reporter {
- private final Writer mWriter;
-
- /**
- * Constructs a new {@link XmlReporter}
- *
- * @param client the client
- * @param output the output file
- * @throws IOException if an error occurs
- */
- public XmlReporter(LintCliClient client, File output) throws IOException {
- super(client, output);
- mWriter = new BufferedWriter(Files.newWriter(output, Charsets.UTF_8));
- }
-
- @Override
- public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException {
- mWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); //$NON-NLS-1$
- // Format 4: added urls= attribute with all more info links, comma separated
- mWriter.write("<issues format=\"4\""); //$NON-NLS-1$
- String revision = mClient.getRevision();
- if (revision != null) {
- mWriter.write(String.format(" by=\"lint %1$s\"", revision)); //$NON-NLS-1$
- }
- mWriter.write(">\n"); //$NON-NLS-1$
-
- if (!issues.isEmpty()) {
- for (Warning warning : issues) {
- mWriter.write('\n');
- indent(mWriter, 1);
- mWriter.write("<issue"); //$NON-NLS-1$
- Issue issue = warning.issue;
- writeAttribute(mWriter, 2, "id", issue.getId()); //$NON-NLS-1$
- writeAttribute(mWriter, 2, "severity",
- warning.severity.getDescription());
- writeAttribute(mWriter, 2, "message", warning.message); //$NON-NLS-1$
-
- writeAttribute(mWriter, 2, "category", //$NON-NLS-1$
- issue.getCategory().getFullName());
- writeAttribute(mWriter, 2, "priority", //$NON-NLS-1$
- Integer.toString(issue.getPriority()));
- writeAttribute(mWriter, 2, "summary", issue.getBriefDescription(RAW));//$NON-NLS-1$
- writeAttribute(mWriter, 2, "explanation", issue.getExplanation(RAW)); //$NON-NLS-1$
- List<String> moreInfo = issue.getMoreInfo();
- if (!moreInfo.isEmpty()) {
- // Compatibility with old format: list first URL
- writeAttribute(mWriter, 2, "url", moreInfo.get(0)); //$NON-NLS-1$
- writeAttribute(mWriter, 2, "urls", //$NON-NLS-1$
- Joiner.on(',').join(issue.getMoreInfo()));
- }
- if (warning.errorLine != null && !warning.errorLine.isEmpty()) {
- String line = warning.errorLine;
- int index1 = line.indexOf('\n');
- if (index1 != -1) {
- int index2 = line.indexOf('\n', index1 + 1);
- if (index2 != -1) {
- String line1 = line.substring(0, index1);
- String line2 = line.substring(index1 + 1, index2);
- writeAttribute(mWriter, 2, "errorLine1", line1); //$NON-NLS-1$
- writeAttribute(mWriter, 2, "errorLine2", line2); //$NON-NLS-1$
- }
- }
- }
-
- if (warning.isVariantSpecific()) {
- writeAttribute(mWriter, 2, "includedVariants", Joiner.on(',').join(warning.getIncludedVariantNames()));
- writeAttribute(mWriter, 2, "excludedVariants", Joiner.on(',').join(warning.getExcludedVariantNames()));
- }
-
- if (mClient.getRegistry() instanceof BuiltinIssueRegistry) {
- boolean adt = QuickfixHandler.ADT.hasAutoFix(issue);
- boolean studio = QuickfixHandler.STUDIO.hasAutoFix(issue);
- if (adt || studio) { //$NON-NLS-1$
- String value = adt && studio ? "studio,adt" : studio ? "studio" : "adt";
- writeAttribute(mWriter, 2, "quickfix", value); //$NON-NLS-1$ //$NON-NLS-2$
- }
- }
-
- assert (warning.file != null) == (warning.location != null);
-
- if (warning.file != null) {
- assert warning.location.getFile() == warning.file;
- }
-
- Location location = warning.location;
- if (location != null) {
- mWriter.write(">\n"); //$NON-NLS-1$
- while (location != null) {
- indent(mWriter, 2);
- mWriter.write("<location"); //$NON-NLS-1$
- String path = mClient.getDisplayPath(warning.project, location.getFile());
- writeAttribute(mWriter, 3, "file", path); //$NON-NLS-1$
- Position start = location.getStart();
- if (start != null) {
- int line = start.getLine();
- int column = start.getColumn();
- if (line >= 0) {
- // +1: Line numbers internally are 0-based, report should be
- // 1-based.
- writeAttribute(mWriter, 3, "line", //$NON-NLS-1$
- Integer.toString(line + 1));
- if (column >= 0) {
- writeAttribute(mWriter, 3, "column", //$NON-NLS-1$
- Integer.toString(column + 1));
- }
- }
- }
-
- mWriter.write("/>\n"); //$NON-NLS-1$
- location = location.getSecondary();
- }
- indent(mWriter, 1);
- mWriter.write("</issue>\n"); //$NON-NLS-1$
- } else {
- mWriter.write('\n');
- indent(mWriter, 1);
- mWriter.write("/>\n"); //$NON-NLS-1$
- }
- }
- }
-
- mWriter.write("\n</issues>\n"); //$NON-NLS-1$
- mWriter.close();
-
- if (!mClient.getFlags().isQuiet()
- && (mDisplayEmpty || errorCount > 0 || warningCount > 0)) {
- String path = mOutput.getAbsolutePath();
- System.out.println(String.format("Wrote XML report to %1$s", path));
- }
- }
-
- private static void writeAttribute(Writer writer, int indent, String name, String value)
- throws IOException {
- writer.write('\n');
- indent(writer, indent);
- writer.write(name);
- writer.write('=');
- writer.write('"');
- for (int i = 0, n = value.length(); i < n; i++) {
- char c = value.charAt(i);
- switch (c) {
- case '"':
- writer.write("""); //$NON-NLS-1$
- break;
- case '\'':
- writer.write("'"); //$NON-NLS-1$
- break;
- case '&':
- writer.write("&"); //$NON-NLS-1$
- break;
- case '<':
- writer.write("<"); //$NON-NLS-1$
- break;
- default:
- writer.write(c);
- break;
- }
- }
- writer.write('"');
- }
-
- private static void indent(Writer writer, int indent) throws IOException {
- for (int level = 0; level < indent; level++) {
- writer.write(" "); //$NON-NLS-1$
- }
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-api/lint-api-base.iml b/base/lint/libs/lint-api/lint-api-base.iml
deleted file mode 100644
index 1be0d2e..0000000
--- a/base/lint/libs/lint-api/lint-api-base.iml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="library" exported="" name="asm-tools" level="project" />
- <orderEntry type="library" exported="" name="lombok-ast" level="project" />
- <orderEntry type="library" exported="" name="builder-model" level="project" />
- <orderEntry type="module" module-name="android-annotations" exported="" />
- <orderEntry type="library" exported="" name="guava-tools" level="project" />
- <orderEntry type="module" module-name="common" />
- <orderEntry type="module" module-name="layoutlib-api-base" />
- <orderEntry type="module" module-name="sdk-common-base" />
- <orderEntry type="module" module-name="sdklib-base" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
deleted file mode 100644
index 09e70dc..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Maps;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Registry which provides a list of checks to be performed on an Android project
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public abstract class IssueRegistry {
- private static List<Category> sCategories;
- private static Map<String, Issue> sIdToIssue;
- private static Map<EnumSet<Scope>, List<Issue>> sScopeIssues = Maps.newHashMap();
-
- /**
- * Creates a new {@linkplain IssueRegistry}
- */
- protected IssueRegistry() {
- }
-
- private static final Implementation DUMMY_IMPLEMENTATION = new Implementation(Detector.class,
- EnumSet.noneOf(Scope.class));
- /**
- * Issue reported by lint (not a specific detector) when it cannot even
- * parse an XML file prior to analysis
- */
- @NonNull
- public static final Issue PARSER_ERROR = Issue.create(
- "ParserError", //$NON-NLS-1$
- "Parser Errors",
- "Lint will ignore any files that contain fatal parsing errors. These may contain " +
- "other errors, or contain code which affects issues in other files.",
- Category.CORRECTNESS,
- 10,
- Severity.ERROR,
- DUMMY_IMPLEMENTATION);
-
- /**
- * Issue reported by lint for various other issues which prevents lint from
- * running normally when it's not necessarily an error in the user's code base.
- */
- @NonNull
- public static final Issue LINT_ERROR = Issue.create(
- "LintError", //$NON-NLS-1$
- "Lint Failure",
- "This issue type represents a problem running lint itself. Examples include " +
- "failure to find bytecode for source files (which means certain detectors " +
- "could not be run), parsing errors in lint configuration files, etc." +
- "\n" +
- "These errors are not errors in your own code, but they are shown to make " +
- "it clear that some checks were not completed.",
-
- Category.LINT,
- 10,
- Severity.ERROR,
- DUMMY_IMPLEMENTATION);
-
- /**
- * Issue reported when lint is canceled
- */
- @NonNull
- public static final Issue CANCELLED = Issue.create(
- "LintCanceled", //$NON-NLS-1$
- "Lint Canceled",
- "Lint canceled by user; the issue report may not be complete.",
-
- Category.LINT,
- 0,
- Severity.INFORMATIONAL,
- DUMMY_IMPLEMENTATION);
-
- /**
- * Returns the list of issues that can be found by all known detectors.
- *
- * @return the list of issues to be checked (including those that may be
- * disabled!)
- */
- @NonNull
- public abstract List<Issue> getIssues();
-
- /**
- * Get an approximate issue count for a given scope. This is just an optimization,
- * so the number does not have to be accurate.
- *
- * @param scope the scope set
- * @return an approximate ceiling of the number of issues expected for a given scope set
- */
- protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) {
- return 20;
- }
-
- /**
- * Returns all available issues of a given scope (regardless of whether
- * they are actually enabled for a given configuration etc)
- *
- * @param scope the applicable scope set
- * @return a list of issues
- */
- @NonNull
- protected List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
- List<Issue> list = sScopeIssues.get(scope);
- if (list == null) {
- List<Issue> issues = getIssues();
- if (scope.equals(Scope.ALL)) {
- list = issues;
- } else {
- list = new ArrayList<Issue>(getIssueCapacity(scope));
- for (Issue issue : issues) {
- // Determine if the scope matches
- if (issue.getImplementation().isAdequate(scope)) {
- list.add(issue);
- }
- }
- }
- sScopeIssues.put(scope, list);
- }
-
- return list;
- }
-
- /**
- * Creates a list of detectors applicable to the given scope, and with the
- * given configuration.
- *
- * @param client the client to report errors to
- * @param configuration the configuration to look up which issues are
- * enabled etc from
- * @param scope the scope for the analysis, to filter out detectors that
- * require wider analysis than is currently being performed
- * @param scopeToDetectors an optional map which (if not null) will be
- * filled by this method to contain mappings from each scope to
- * the applicable detectors for that scope
- * @return a list of new detector instances
- */
- @NonNull
- final List<? extends Detector> createDetectors(
- @NonNull LintClient client,
- @NonNull Configuration configuration,
- @NonNull EnumSet<Scope> scope,
- @Nullable Map<Scope, List<Detector>> scopeToDetectors) {
-
- List<Issue> issues = getIssuesForScope(scope);
- if (issues.isEmpty()) {
- return Collections.emptyList();
- }
-
- Set<Class<? extends Detector>> detectorClasses = new HashSet<Class<? extends Detector>>();
- Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope =
- new HashMap<Class<? extends Detector>, EnumSet<Scope>>();
-
- for (Issue issue : issues) {
- Implementation implementation = issue.getImplementation();
- Class<? extends Detector> detectorClass = implementation.getDetectorClass();
- EnumSet<Scope> issueScope = implementation.getScope();
- if (!detectorClasses.contains(detectorClass)) {
- // Determine if the issue is enabled
- if (!configuration.isEnabled(issue)) {
- continue;
- }
-
- assert implementation.isAdequate(scope); // Ensured by getIssuesForScope above
-
- detectorClass = client.replaceDetector(detectorClass);
-
- assert detectorClass != null : issue.getId();
- detectorClasses.add(detectorClass);
- }
-
- if (scopeToDetectors != null) {
- EnumSet<Scope> s = detectorToScope.get(detectorClass);
- if (s == null) {
- detectorToScope.put(detectorClass, issueScope);
- } else if (!s.containsAll(issueScope)) {
- EnumSet<Scope> union = EnumSet.copyOf(s);
- union.addAll(issueScope);
- detectorToScope.put(detectorClass, union);
- }
- }
- }
-
- List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size());
- for (Class<? extends Detector> clz : detectorClasses) {
- try {
- Detector detector = clz.newInstance();
- detectors.add(detector);
-
- if (scopeToDetectors != null) {
- EnumSet<Scope> union = detectorToScope.get(clz);
- for (Scope s : union) {
- List<Detector> list = scopeToDetectors.get(s);
- if (list == null) {
- list = new ArrayList<Detector>();
- scopeToDetectors.put(s, list);
- }
- list.add(detector);
- }
-
- }
- } catch (Throwable t) {
- client.log(t, "Can't initialize detector %1$s", clz.getName()); //$NON-NLS-1$
- }
- }
-
- return detectors;
- }
-
- /**
- * Returns true if the given id represents a valid issue id
- *
- * @param id the id to be checked
- * @return true if the given id is valid
- */
- public final boolean isIssueId(@NonNull String id) {
- return getIssue(id) != null;
- }
-
- /**
- * Returns true if the given category is a valid category
- *
- * @param name the category name to be checked
- * @return true if the given string is a valid category
- */
- public final boolean isCategoryName(@NonNull String name) {
- for (Category category : getCategories()) {
- if (category.getName().equals(name) || category.getFullName().equals(name)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns the available categories
- *
- * @return an iterator for all the categories, never null
- */
- @NonNull
- public List<Category> getCategories() {
- if (sCategories == null) {
- final Set<Category> categories = new HashSet<Category>();
- for (Issue issue : getIssues()) {
- categories.add(issue.getCategory());
- }
- List<Category> sorted = new ArrayList<Category>(categories);
- Collections.sort(sorted);
- sCategories = Collections.unmodifiableList(sorted);
- }
-
- return sCategories;
- }
-
- /**
- * Returns the issue for the given id, or null if it's not a valid id
- *
- * @param id the id to be checked
- * @return the corresponding issue, or null
- */
- @Nullable
- public final Issue getIssue(@NonNull String id) {
- if (sIdToIssue == null) {
- List<Issue> issues = getIssues();
- sIdToIssue = new HashMap<String, Issue>(issues.size());
- for (Issue issue : issues) {
- sIdToIssue.put(issue.getId(), issue);
- }
-
- sIdToIssue.put(PARSER_ERROR.getId(), PARSER_ERROR);
- sIdToIssue.put(LINT_ERROR.getId(), LINT_ERROR);
- }
- return sIdToIssue.get(id);
- }
-
- /**
- * Reset the registry such that it recomputes its available issues.
- * <p>
- * NOTE: This is only intended for testing purposes.
- */
- @VisibleForTesting
- protected static void reset() {
- sIdToIssue = null;
- sCategories = null;
- sScopeIssues = Maps.newHashMap();
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
deleted file mode 100644
index 0262691..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.utils.SdkUtils;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.SoftReference;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.jar.Attributes;
-import java.util.jar.JarFile;
-import java.util.jar.Manifest;
-
-/**
- * <p> An {@link IssueRegistry} for a custom lint rule jar file. The rule jar should provide a
- * manifest entry with the key {@code Lint-Registry} and the value of the fully qualified name of an
- * implementation of {@link IssueRegistry} (with a default constructor). </p>
- *
- * <p> NOTE: The custom issue registry should not extend this file; it should be a plain
- * IssueRegistry! This file is used internally to wrap the given issue registry.</p>
- */
-class JarFileIssueRegistry extends IssueRegistry {
- /**
- * Manifest constant for declaring an issue provider. Example: Lint-Registry:
- * foo.bar.CustomIssueRegistry
- */
- private static final String MF_LINT_REGISTRY = "Lint-Registry"; //$NON-NLS-1$
-
- private static Map<File, SoftReference<JarFileIssueRegistry>> sCache;
-
- private final List<Issue> myIssues;
-
- @NonNull
- static IssueRegistry get(@NonNull LintClient client, @NonNull File jarFile) throws IOException,
- ClassNotFoundException, IllegalAccessException, InstantiationException {
- if (sCache == null) {
- sCache = new HashMap<File, SoftReference<JarFileIssueRegistry>>();
- } else {
- SoftReference<JarFileIssueRegistry> reference = sCache.get(jarFile);
- if (reference != null) {
- JarFileIssueRegistry registry = reference.get();
- if (registry != null) {
- return registry;
- }
- }
- }
-
- JarFileIssueRegistry registry = new JarFileIssueRegistry(client, jarFile);
- sCache.put(jarFile, new SoftReference<JarFileIssueRegistry>(registry));
- return registry;
- }
-
- private JarFileIssueRegistry(@NonNull LintClient client, @NonNull File file)
- throws IOException, ClassNotFoundException, IllegalAccessException,
- InstantiationException {
- myIssues = Lists.newArrayList();
- JarFile jarFile = null;
- try {
- jarFile = new JarFile(file);
- Manifest manifest = jarFile.getManifest();
- Attributes attrs = manifest.getMainAttributes();
- Object object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY));
- if (object instanceof String) {
- String className = (String) object;
- // Make a class loader for this jar
- URL url = SdkUtils.fileToUrl(file);
- URLClassLoader loader = new URLClassLoader(new URL[]{url},
- JarFileIssueRegistry.class.getClassLoader());
- Class<?> registryClass = Class.forName(className, true, loader);
- IssueRegistry registry = (IssueRegistry) registryClass.newInstance();
- myIssues.addAll(registry.getIssues());
- } else {
- client.log(Severity.ERROR, null,
- "Custom lint rule jar %1$s does not contain a valid registry manifest key " +
- "(%2$s).\n" +
- "Either the custom jar is invalid, or it uses an outdated API not supported " +
- "this lint client", file.getPath(), MF_LINT_REGISTRY);
- }
- } finally {
- if (jarFile != null) {
- jarFile.close();
- }
- }
- }
-
- @NonNull
- @Override
- public List<Issue> getIssues() {
- return myIssues;
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
deleted file mode 100644
index f150eb2..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.ATTR_VALUE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
-
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.Identifier;
-import lombok.ast.Node;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.TypeReference;
-import lombok.ast.TypeReferencePart;
-
-/**
- * A wrapper for a Java parser. This allows tools integrating lint to map directly
- * to builtin services, such as already-parsed data structures in Java editors.
- * <p/>
- * <b>NOTE: This is not public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public abstract class JavaParser {
- public static final String TYPE_OBJECT = "java.lang.Object"; //$NON-NLS-1$
- public static final String TYPE_STRING = "java.lang.String"; //$NON-NLS-1$
- public static final String TYPE_INT = "int"; //$NON-NLS-1$
- public static final String TYPE_LONG = "long"; //$NON-NLS-1$
- public static final String TYPE_CHAR = "char"; //$NON-NLS-1$
- public static final String TYPE_FLOAT = "float"; //$NON-NLS-1$
- public static final String TYPE_DOUBLE = "double"; //$NON-NLS-1$
- public static final String TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$
- public static final String TYPE_SHORT = "short"; //$NON-NLS-1$
- public static final String TYPE_BYTE = "byte"; //$NON-NLS-1$
- public static final String TYPE_NULL = "null"; //$NON-NLS-1$
-
- /**
- * Prepare to parse the given contexts. This method will be called before
- * a series of {@link #parseJava(JavaContext)} calls, which allows some
- * parsers to do up front global computation in case they want to more
- * efficiently process multiple files at the same time. This allows a single
- * type-attribution pass for example, which is a lot more efficient than
- * performing global type analysis over and over again for each individual
- * file
- *
- * @param contexts a list of contexts to be parsed
- */
- public abstract void prepareJavaParse(@NonNull List<JavaContext> contexts);
-
- /**
- * Parse the file pointed to by the given context.
- *
- * @param context the context pointing to the file to be parsed, typically
- * via {@link Context#getContents()} but the file handle (
- * {@link Context#file} can also be used to map to an existing
- * editor buffer in the surrounding tool, etc)
- * @return the compilation unit node for the file
- */
- @Nullable
- public abstract Node parseJava(@NonNull JavaContext context);
-
- /**
- * Returns a {@link Location} for the given node
- *
- * @param context information about the file being parsed
- * @param node the node to create a location for
- * @return a location for the given node
- */
- @NonNull
- public abstract Location getLocation(@NonNull JavaContext context, @NonNull Node node);
-
- /**
- * Creates a light-weight handle to a location for the given node. It can be
- * turned into a full fledged location by
- * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
- *
- * @param context the context providing the node
- * @param node the node (element or attribute) to create a location handle
- * for
- * @return a location handle
- */
- @NonNull
- public abstract Location.Handle createLocationHandle(@NonNull JavaContext context,
- @NonNull Node node);
-
- /**
- * Dispose any data structures held for the given context.
- * @param context information about the file previously parsed
- * @param compilationUnit the compilation unit being disposed
- */
- public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) {
- }
-
- /**
- * Dispose any remaining data structures held for all contexts.
- * Typically frees up any resources allocated by
- * {@link #prepareJavaParse(List)}
- */
- public void dispose() {
- }
-
- /**
- * Resolves the given expression node: computes the declaration for the given symbol
- *
- * @param context information about the file being parsed
- * @param node the node to resolve
- * @return a node representing the resolved fully type: class/interface/annotation,
- * field, method or variable
- */
- @Nullable
- public abstract ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node);
-
- /**
- * Finds the given type, if possible (which should be reachable from the compilation
- * patch of the given node.
- *
- * @param context information about the file being parsed
- * @param fullyQualifiedName the fully qualified name of the class to look up
- * @return the class, or null if not found
- */
- @Nullable
- public ResolvedClass findClass(
- @NonNull JavaContext context,
- @NonNull String fullyQualifiedName) {
- return null;
- }
-
- /**
- * Gets the type of the given node
- *
- * @param context information about the file being parsed
- * @param node the node to look up the type for
- * @return the type of the node, if known
- */
- @Nullable
- public abstract TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node);
-
- /** A description of a type, such as a primitive int or the android.app.Activity class */
- public abstract static class TypeDescriptor {
- /**
- * Returns the fully qualified name of the type, such as "int" or "android.app.Activity"
- * */
- @NonNull public abstract String getName();
-
- /**
- * Returns the full signature of the type, which is normally the same as {@link #getName()}
- * but for arrays can include []'s, for generic methods can include generics parameters
- * etc
- */
- @NonNull public abstract String getSignature();
-
- public abstract boolean matchesName(@NonNull String name);
-
- public abstract boolean matchesSignature(@NonNull String signature);
-
- @NonNull
- public TypeReference getNode() {
- TypeReference typeReference = new TypeReference();
- StrictListAccessor<TypeReferencePart, TypeReference> parts = typeReference.astParts();
- for (String part : Splitter.on('.').split(getName())) {
- Identifier identifier = Identifier.of(part);
- parts.addToEnd(new TypeReferencePart().astIdentifier(identifier));
- }
-
- return typeReference;
- }
-
- /** If the type is not primitive, returns the class of the type if known */
- @Nullable
- public abstract ResolvedClass getTypeClass();
-
- @Override
- public abstract boolean equals(Object o);
-
- }
-
- /** Convenience implementation of {@link TypeDescriptor} */
- public static class DefaultTypeDescriptor extends TypeDescriptor {
-
- private String mName;
-
- public DefaultTypeDescriptor(String name) {
- mName = name;
- }
-
- @NonNull
- @Override
- public String getName() {
- return mName;
- }
-
- @NonNull
- @Override
- public String getSignature() {
- return getName();
- }
-
- @Override
- public boolean matchesName(@NonNull String name) {
- return mName.equals(name);
- }
-
- @Override
- public boolean matchesSignature(@NonNull String signature) {
- return matchesName(signature);
- }
-
- @Override
- public String toString() {
- return getSignature();
- }
-
- @Override
- @Nullable
- public ResolvedClass getTypeClass() {
- return null;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- DefaultTypeDescriptor that = (DefaultTypeDescriptor) o;
-
- return !(mName != null ? !mName.equals(that.mName) : that.mName != null);
-
- }
-
- @Override
- public int hashCode() {
- return mName != null ? mName.hashCode() : 0;
- }
- }
-
- /** A resolved declaration from an AST Node reference */
- public abstract static class ResolvedNode {
- @NonNull
- public abstract String getName();
-
- /** Returns the signature of the resolved node */
- public abstract String getSignature();
-
- public abstract int getModifiers();
-
- @Override
- public String toString() {
- return getSignature();
- }
-
- /** Returns any annotations defined on this node */
- @NonNull
- public abstract Iterable<ResolvedAnnotation> getAnnotations();
-
- /**
- * Searches for the annotation of the given type on this node
- *
- * @param type the fully qualified name of the annotation to check
- * @return the annotation, or null if not found
- */
- @Nullable
- public ResolvedAnnotation getAnnotation(@NonNull String type) {
- for (ResolvedAnnotation annotation : getAnnotations()) {
- if (annotation.getType().matchesSignature(type)) {
- return annotation;
- }
- }
-
- return null;
- }
-
- /**
- * Returns true if this element is in the given package (or optionally, in one of its sub
- * packages)
- *
- * @param pkg the package name
- * @param includeSubPackages whether to include subpackages
- * @return true if the element is in the given package
- */
- public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
- return getSignature().startsWith(pkg);
- }
- }
-
- /** A resolved class declaration (class, interface, enumeration or annotation) */
- public abstract static class ResolvedClass extends ResolvedNode {
- /** Returns the fully qualified name of this class */
- @Override
- @NonNull
- public abstract String getName();
-
- /** Returns the simple of this class */
- @NonNull
- public abstract String getSimpleName();
-
- /** Returns the package name of this class */
- @NonNull
- public String getPackageName() {
- String name = getName();
- String simpleName = getSimpleName();
- if (name.length() > simpleName.length() + 1) {
- return name.substring(0, name.length() - simpleName.length() - 1);
- }
- return name;
- }
-
- /** Returns whether this class' fully qualified name matches the given name */
- public abstract boolean matches(@NonNull String name);
-
- @Nullable
- public abstract ResolvedClass getSuperClass();
-
- @Nullable
- public abstract ResolvedClass getContainingClass();
-
- public TypeDescriptor getType() {
- return new DefaultTypeDescriptor(getName());
- }
-
- /**
- * Determines whether this class extends the given name. If strict is true,
- * it will not consider C extends C true.
- *
- * @param name the fully qualified class name
- * @param strict if true, do not consider a class to be extending itself
- * @return true if this class extends the given class
- */
- public abstract boolean isSubclassOf(@NonNull String name, boolean strict);
-
- @NonNull
- public abstract Iterable<ResolvedMethod> getConstructors();
-
- /** Returns the methods defined in this class, and optionally any methods inherited from any superclasses as well */
- @NonNull
- public abstract Iterable<ResolvedMethod> getMethods(boolean includeInherited);
-
- /** Returns the methods of a given name defined in this class, and optionally any methods inherited from any superclasses as well */
- @NonNull
- public abstract Iterable<ResolvedMethod> getMethods(@NonNull String name, boolean includeInherited);
-
- /** Returns the fields defined in this class, and optionally any fields declared in any superclasses as well */
- @NonNull
- public abstract Iterable<ResolvedField> getFields(boolean includeInherited);
-
- /** Returns the named field defined in this class, or optionally inherited from a superclass */
- @Nullable
- public abstract ResolvedField getField(@NonNull String name, boolean includeInherited);
-
- /** Returns the package containing this class */
- @Nullable
- public abstract ResolvedPackage getPackage();
-
- @Override
- public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
- String packageName = getPackageName();
-
- //noinspection SimplifiableIfStatement
- if (pkg.equals(packageName)) {
- return true;
- }
-
- return includeSubPackages && packageName.length() > pkg.length() &&
- packageName.charAt(pkg.length()) == '.' &&
- packageName.startsWith(pkg);
- }
- }
-
- /** A method or constructor declaration */
- public abstract static class ResolvedMethod extends ResolvedNode {
- @Override
- @NonNull
- public abstract String getName();
-
- /** Returns whether this method name matches the given name */
- public abstract boolean matches(@NonNull String name);
-
- @NonNull
- public abstract ResolvedClass getContainingClass();
-
- public abstract int getArgumentCount();
-
- @NonNull
- public abstract TypeDescriptor getArgumentType(int index);
-
- /** Returns true if the parameter at the given index matches the given type signature */
- public boolean argumentMatchesType(int index, @NonNull String signature) {
- return getArgumentType(index).matchesSignature(signature);
- }
-
- @Nullable
- public abstract TypeDescriptor getReturnType();
-
- public boolean isConstructor() {
- return getReturnType() == null;
- }
-
- /** Returns any annotations defined on the given parameter of this method */
- @NonNull
- public abstract Iterable<ResolvedAnnotation> getParameterAnnotations(int index);
-
- /**
- * Searches for the annotation of the given type on the method
- *
- * @param type the fully qualified name of the annotation to check
- * @param parameterIndex the index of the parameter to look up
- * @return the annotation, or null if not found
- */
- @Nullable
- public ResolvedAnnotation getParameterAnnotation(@NonNull String type,
- int parameterIndex) {
- for (ResolvedAnnotation annotation : getParameterAnnotations(parameterIndex)) {
- if (annotation.getType().matchesSignature(type)) {
- return annotation;
- }
- }
-
- return null;
- }
-
- /** Returns the super implementation of the given method, if any */
- @Nullable
- public ResolvedMethod getSuperMethod() {
- ResolvedClass cls = getContainingClass().getSuperClass();
- if (cls != null) {
- String methodName = getName();
- int argCount = getArgumentCount();
- for (ResolvedMethod method : cls.getMethods(methodName, true)) {
- if (argCount != method.getArgumentCount()) {
- continue;
- }
- boolean sameTypes = true;
- for (int arg = 0; arg < argCount; arg++) {
- if (!method.getArgumentType(arg).equals(getArgumentType(arg))) {
- sameTypes = false;
- break;
- }
- }
- if (sameTypes) {
- return method;
- }
- }
- }
-
- return null;
- }
-
- @Override
- public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
- String packageName = getContainingClass().getPackageName();
-
- //noinspection SimplifiableIfStatement
- if (pkg.equals(packageName)) {
- return true;
- }
-
- return includeSubPackages && packageName.length() > pkg.length() &&
- packageName.charAt(pkg.length()) == '.' &&
- packageName.startsWith(pkg);
- }
- }
-
- /** A field declaration */
- public abstract static class ResolvedField extends ResolvedNode {
- @Override
- @NonNull
- public abstract String getName();
-
- /** Returns whether this field name matches the given name */
- public abstract boolean matches(@NonNull String name);
-
- @NonNull
- public abstract TypeDescriptor getType();
-
- @NonNull
- public abstract ResolvedClass getContainingClass();
-
- @Nullable
- public abstract Object getValue();
-
- public String getContainingClassName() {
- return getContainingClass().getName();
- }
-
- @Override
- public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
- String packageName = getContainingClass().getPackageName();
-
- //noinspection SimplifiableIfStatement
- if (pkg.equals(packageName)) {
- return true;
- }
-
- return includeSubPackages && packageName.length() > pkg.length() &&
- packageName.charAt(pkg.length()) == '.' &&
- packageName.startsWith(pkg);
- }
- }
-
- /**
- * An annotation <b>reference</b>. Note that this refers to a usage of an annotation,
- * not a declaraton of an annotation. You can call {@link #getClassType()} to
- * find the declaration for the annotation.
- */
- public abstract static class ResolvedAnnotation extends ResolvedNode {
- @Override
- @NonNull
- public abstract String getName();
-
- /** Returns whether this field name matches the given name */
- public abstract boolean matches(@NonNull String name);
-
- @NonNull
- public abstract TypeDescriptor getType();
-
- /** Returns the {@link ResolvedClass} which defines the annotation */
- @Nullable
- public abstract ResolvedClass getClassType();
-
- public static class Value {
- @NonNull public final String name;
- @Nullable public final Object value;
-
- public Value(@NonNull String name, @Nullable Object value) {
- this.name = name;
- this.value = value;
- }
- }
-
- @NonNull
- public abstract List<Value> getValues();
-
- @Nullable
- public Object getValue(@NonNull String name) {
- for (Value value : getValues()) {
- if (name.equals(value.name)) {
- return value.value;
- }
- }
- return null;
- }
-
- @Nullable
- public Object getValue() {
- return getValue(ATTR_VALUE);
- }
-
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- return Collections.emptyList();
- }
- }
-
- /** A package declaration */
- public abstract static class ResolvedPackage extends ResolvedNode {
- @NonNull
- @Override
- public Iterable<ResolvedAnnotation> getAnnotations() {
- return Collections.emptyList();
- }
- }
-
- /** A local variable or parameter declaration */
- public abstract static class ResolvedVariable extends ResolvedNode {
- @Override
- @NonNull
- public abstract String getName();
-
- /** Returns whether this variable name matches the given name */
- public abstract boolean matches(@NonNull String name);
-
- @NonNull
- public abstract TypeDescriptor getType();
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
deleted file mode 100644
index fbce577..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
+++ /dev/null
@@ -1,1392 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.ANDROID_PKG;
-import static com.android.SdkConstants.R_CLASS;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Detector.XmlScanner;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AlternateConstructorInvocation;
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationDeclaration;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationMethodDeclaration;
-import lombok.ast.AnnotationValueArray;
-import lombok.ast.ArrayAccess;
-import lombok.ast.ArrayCreation;
-import lombok.ast.ArrayDimension;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.Assert;
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.Block;
-import lombok.ast.BooleanLiteral;
-import lombok.ast.Break;
-import lombok.ast.Case;
-import lombok.ast.Cast;
-import lombok.ast.Catch;
-import lombok.ast.CharLiteral;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ClassLiteral;
-import lombok.ast.Comment;
-import lombok.ast.CompilationUnit;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Continue;
-import lombok.ast.Default;
-import lombok.ast.DoWhile;
-import lombok.ast.EmptyDeclaration;
-import lombok.ast.EmptyStatement;
-import lombok.ast.EnumConstant;
-import lombok.ast.EnumDeclaration;
-import lombok.ast.EnumTypeBody;
-import lombok.ast.Expression;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.FloatingPointLiteral;
-import lombok.ast.For;
-import lombok.ast.ForEach;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Identifier;
-import lombok.ast.If;
-import lombok.ast.ImportDeclaration;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.InstanceInitializer;
-import lombok.ast.InstanceOf;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.InterfaceDeclaration;
-import lombok.ast.KeywordModifier;
-import lombok.ast.LabelledStatement;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.NullLiteral;
-import lombok.ast.PackageDeclaration;
-import lombok.ast.Return;
-import lombok.ast.Select;
-import lombok.ast.StaticInitializer;
-import lombok.ast.StringLiteral;
-import lombok.ast.Super;
-import lombok.ast.SuperConstructorInvocation;
-import lombok.ast.Switch;
-import lombok.ast.Synchronized;
-import lombok.ast.This;
-import lombok.ast.Throw;
-import lombok.ast.Try;
-import lombok.ast.TypeReference;
-import lombok.ast.TypeReferencePart;
-import lombok.ast.TypeVariable;
-import lombok.ast.UnaryExpression;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-import lombok.ast.While;
-
-/**
- * Specialized visitor for running detectors on a Java AST.
- * It operates in three phases:
- * <ol>
- * <li> First, it computes a set of maps where it generates a map from each
- * significant AST attribute (such as method call names) to a list
- * of detectors to consult whenever that attribute is encountered.
- * Examples of "attributes" are method names, Android resource identifiers,
- * and general AST node types such as "cast" nodes etc. These are
- * defined on the {@link JavaScanner} interface.
- * <li> Second, it iterates over the document a single time, delegating to
- * the detectors found at each relevant AST attribute.
- * <li> Finally, it calls the remaining visitors (those that need to process a
- * whole document on their own).
- * </ol>
- * It also notifies all the detectors before and after the document is processed
- * such that they can do pre- and post-processing.
- */
-public class JavaVisitor {
- /** Default size of lists holding detectors of the same type for a given node type */
- private static final int SAME_TYPE_COUNT = 8;
-
- private final Map<String, List<VisitingDetector>> mMethodDetectors =
- Maps.newHashMapWithExpectedSize(40);
- private final Map<String, List<VisitingDetector>> mConstructorDetectors =
- Maps.newHashMapWithExpectedSize(12);
- private Set<String> mConstructorSimpleNames;
- private final List<VisitingDetector> mResourceFieldDetectors =
- new ArrayList<VisitingDetector>();
- private final List<VisitingDetector> mAllDetectors;
- private final List<VisitingDetector> mFullTreeDetectors;
- private final Map<Class<? extends Node>, List<VisitingDetector>> mNodeTypeDetectors =
- new HashMap<Class<? extends Node>, List<VisitingDetector>>(16);
- private final JavaParser mParser;
- private final Map<String, List<VisitingDetector>> mSuperClassDetectors =
- new HashMap<String, List<VisitingDetector>>();
-
- /**
- * Number of fatal exceptions (internal errors, usually from ECJ) we've
- * encountered; we don't log each and every one to avoid massive log spam
- * in code which triggers this condition
- */
- private static int sExceptionCount;
- /** Max number of logs to include */
- private static final int MAX_REPORTED_CRASHES = 20;
-
- JavaVisitor(@NonNull JavaParser parser, @NonNull List<Detector> detectors) {
- mParser = parser;
- mAllDetectors = new ArrayList<VisitingDetector>(detectors.size());
- mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size());
-
- for (Detector detector : detectors) {
- VisitingDetector v = new VisitingDetector(detector, (JavaScanner) detector);
- mAllDetectors.add(v);
-
- List<String> applicableSuperClasses = detector.applicableSuperClasses();
- if (applicableSuperClasses != null) {
- for (String fqn : applicableSuperClasses) {
- List<VisitingDetector> list = mSuperClassDetectors.get(fqn);
- if (list == null) {
- list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
- mSuperClassDetectors.put(fqn, list);
- }
- list.add(v);
- }
- continue;
- }
-
- List<Class<? extends Node>> nodeTypes = detector.getApplicableNodeTypes();
- if (nodeTypes != null) {
- for (Class<? extends Node> type : nodeTypes) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(type);
- if (list == null) {
- list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
- mNodeTypeDetectors.put(type, list);
- }
- list.add(v);
- }
- }
-
- List<String> names = detector.getApplicableMethodNames();
- if (names != null) {
- // not supported in Java visitors; adding a method invocation node is trivial
- // for that case.
- assert names != XmlScanner.ALL;
-
- for (String name : names) {
- List<VisitingDetector> list = mMethodDetectors.get(name);
- if (list == null) {
- list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
- mMethodDetectors.put(name, list);
- }
- list.add(v);
- }
- }
-
- List<String> types = detector.getApplicableConstructorTypes();
- if (types != null) {
- // not supported in Java visitors; adding a method invocation node is trivial
- // for that case.
- assert types != XmlScanner.ALL;
- if (mConstructorSimpleNames == null) {
- mConstructorSimpleNames = Sets.newHashSet();
- }
- for (String type : types) {
- List<VisitingDetector> list = mConstructorDetectors.get(type);
- if (list == null) {
- list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
- mConstructorDetectors.put(type, list);
- mConstructorSimpleNames.add(type.substring(type.lastIndexOf('.')+1));
- }
- list.add(v);
- }
- }
-
- if (detector.appliesToResourceRefs()) {
- mResourceFieldDetectors.add(v);
- } else if ((names == null || names.isEmpty())
- && (nodeTypes == null || nodeTypes.isEmpty())
- && (types == null || types.isEmpty())) {
- mFullTreeDetectors.add(v);
- }
- }
- }
-
- void visitFile(@NonNull JavaContext context) {
- Node compilationUnit = null;
- try {
- compilationUnit = mParser.parseJava(context);
- if (compilationUnit == null) {
- // No need to log this; the parser should be reporting
- // a full warning (such as IssueRegistry#PARSER_ERROR)
- // with details, location, etc.
- return;
- }
- context.setCompilationUnit(compilationUnit);
-
- for (VisitingDetector v : mAllDetectors) {
- v.setContext(context);
- v.getDetector().beforeCheckFile(context);
- }
-
- if (!mSuperClassDetectors.isEmpty()) {
- SuperclassVisitor visitor = new SuperclassVisitor(context);
- compilationUnit.accept(visitor);
- }
-
- for (VisitingDetector v : mFullTreeDetectors) {
- AstVisitor visitor = v.getVisitor();
- compilationUnit.accept(visitor);
- }
-
- if (!mMethodDetectors.isEmpty() || !mResourceFieldDetectors.isEmpty() ||
- !mConstructorDetectors.isEmpty()) {
- AstVisitor visitor = new DelegatingJavaVisitor(context);
- compilationUnit.accept(visitor);
- } else if (!mNodeTypeDetectors.isEmpty()) {
- AstVisitor visitor = new DispatchVisitor();
- compilationUnit.accept(visitor);
- }
-
- for (VisitingDetector v : mAllDetectors) {
- v.getDetector().afterCheckFile(context);
- }
- } catch (RuntimeException e) {
- if (sExceptionCount++ > MAX_REPORTED_CRASHES) {
- // No need to keep spamming the user that a lot of the files
- // are tripping up ECJ, they get the picture.
- return;
- }
-
- // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268
- // Don't allow lint bugs to take down the whole build. TRY to log this as a
- // lint error instead!
- StringBuilder sb = new StringBuilder(100);
- sb.append("Unexpected failure during lint analysis of ");
- sb.append(context.file.getName());
- sb.append(" (this is a bug in lint or one of the libraries it depends on)\n");
-
- StackTraceElement[] stackTrace = e.getStackTrace();
- int count = 0;
- for (StackTraceElement frame : stackTrace) {
- if (count > 0) {
- sb.append("->");
- }
-
- String className = frame.getClassName();
- sb.append(className.substring(className.lastIndexOf('.') + 1));
- sb.append('.').append(frame.getMethodName());
- sb.append('(');
- sb.append(frame.getFileName()).append(':').append(frame.getLineNumber());
- sb.append(')');
- count++;
- // Only print the top 3-4 frames such that we can identify the bug
- if (count == 4) {
- break;
- }
- }
- Throwable throwable = null; // NOT e: this makes for very noisy logs
- //noinspection ConstantConditions
- context.log(throwable, sb.toString());
- } finally {
- if (compilationUnit != null) {
- mParser.dispose(context, compilationUnit);
- }
- }
- }
-
- public void prepare(@NonNull List<JavaContext> contexts) {
- mParser.prepareJavaParse(contexts);
- }
-
- public void dispose() {
- mParser.dispose();
- }
-
- private static class VisitingDetector {
- private AstVisitor mVisitor; // construct lazily, and clear out on context switch!
- private JavaContext mContext;
- public final Detector mDetector;
- public final JavaScanner mJavaScanner;
-
- public VisitingDetector(@NonNull Detector detector, @NonNull JavaScanner javaScanner) {
- mDetector = detector;
- mJavaScanner = javaScanner;
- }
-
- @NonNull
- public Detector getDetector() {
- return mDetector;
- }
-
- @NonNull
- public JavaScanner getJavaScanner() {
- return mJavaScanner;
- }
-
- public void setContext(@NonNull JavaContext context) {
- mContext = context;
-
- // The visitors are one-per-context, so clear them out here and construct
- // lazily only if needed
- mVisitor = null;
- }
-
- @NonNull
- AstVisitor getVisitor() {
- if (mVisitor == null) {
- mVisitor = mDetector.createJavaVisitor(mContext);
- if (mVisitor == null) {
- mVisitor = new ForwardingAstVisitor() {
- };
- }
- }
- return mVisitor;
- }
- }
-
- private class SuperclassVisitor extends ForwardingAstVisitor {
- private JavaContext mContext;
-
- public SuperclassVisitor(@NonNull JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- ResolvedNode resolved = mContext.resolve(node);
- if (!(resolved instanceof ResolvedClass)) {
- return true;
- }
-
- ResolvedClass resolvedClass = (ResolvedClass) resolved;
- ResolvedClass cls = resolvedClass;
- while (cls != null) {
- List<VisitingDetector> list = mSuperClassDetectors.get(cls.getName());
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getJavaScanner().checkClass(mContext, node, node, resolvedClass);
- }
- }
-
- cls = cls.getSuperClass();
- }
-
- return false;
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- NormalTypeBody anonymous = node.astAnonymousClassBody();
- if (anonymous != null) {
- ResolvedNode resolved = mContext.resolve(anonymous);
- if (resolved instanceof ResolvedMethod) {
- resolved = ((ResolvedMethod) resolved).getContainingClass();
- }
- if (!(resolved instanceof ResolvedClass)) {
- return true;
- }
-
- ResolvedClass resolvedClass = (ResolvedClass) resolved;
- ResolvedClass cls = resolvedClass;
- while (cls != null) {
- List<VisitingDetector> list = mSuperClassDetectors.get(cls.getName());
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getJavaScanner().checkClass(mContext, null, anonymous,
- resolvedClass);
- }
- }
-
- cls = cls.getSuperClass();
- }
- }
-
- return true;
- }
-
- @Override
- public boolean visitImportDeclaration(ImportDeclaration node) {
- return true;
- }
- }
-
- /**
- * Generic dispatcher which visits all nodes (once) and dispatches to
- * specific visitors for each node. Each visitor typically only wants to
- * look at a small part of a tree, such as a method call or a class
- * declaration, so this means we avoid visiting all "uninteresting" nodes in
- * the tree repeatedly.
- */
- private class DispatchVisitor extends ForwardingAstVisitor {
- @Override
- public void endVisit(Node node) {
- for (VisitingDetector v : mAllDetectors) {
- v.getVisitor().endVisit(node);
- }
- }
-
- @Override
- public boolean visitAlternateConstructorInvocation(AlternateConstructorInvocation node) {
- List<VisitingDetector> list =
- mNodeTypeDetectors.get(AlternateConstructorInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAlternateConstructorInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotation(Annotation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Annotation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationDeclaration(AnnotationDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationElement(AnnotationElement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationElement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationElement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationMethodDeclaration(AnnotationMethodDeclaration node) {
- List<VisitingDetector> list =
- mNodeTypeDetectors.get(AnnotationMethodDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationMethodDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationValueArray(AnnotationValueArray node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationValueArray.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationValueArray(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayAccess(ArrayAccess node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayAccess.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayAccess(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayCreation(ArrayCreation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayCreation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayCreation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayDimension(ArrayDimension node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayDimension.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayDimension(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayInitializer(ArrayInitializer node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayInitializer.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayInitializer(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAssert(Assert node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Assert.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAssert(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBinaryExpression(BinaryExpression node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(BinaryExpression.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBinaryExpression(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBlock(Block node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Block.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBlock(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBooleanLiteral(BooleanLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(BooleanLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBooleanLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBreak(Break node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Break.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBreak(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCase(Case node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Case.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCase(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCast(Cast node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Cast.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCast(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCatch(Catch node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Catch.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCatch(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCharLiteral(CharLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(CharLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCharLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ClassDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitClassDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitClassLiteral(ClassLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ClassLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitClassLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitComment(Comment node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Comment.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitComment(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCompilationUnit(CompilationUnit node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(CompilationUnit.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCompilationUnit(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitConstructorDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitConstructorInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitContinue(Continue node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Continue.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitContinue(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitDefault(Default node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Default.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitDefault(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitDoWhile(DoWhile node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(DoWhile.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitDoWhile(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEmptyDeclaration(EmptyDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEmptyDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEmptyStatement(EmptyStatement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyStatement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEmptyStatement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEnumConstant(EnumConstant node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EnumConstant.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEnumConstant(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEnumDeclaration(EnumDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EnumDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEnumDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEnumTypeBody(EnumTypeBody node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EnumTypeBody.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEnumTypeBody(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitExpressionStatement(ExpressionStatement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ExpressionStatement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitExpressionStatement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitFloatingPointLiteral(FloatingPointLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(FloatingPointLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitFloatingPointLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitFor(For node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(For.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitFor(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitForEach(ForEach node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ForEach.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitForEach(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitIdentifier(Identifier node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Identifier.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitIdentifier(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitIf(If node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(If.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitIf(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitImportDeclaration(ImportDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ImportDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitImportDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInlineIfExpression(InlineIfExpression node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InlineIfExpression.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInlineIfExpression(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInstanceInitializer(InstanceInitializer node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceInitializer.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInstanceInitializer(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInstanceOf(InstanceOf node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceOf.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInstanceOf(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitIntegralLiteral(IntegralLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(IntegralLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitIntegralLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInterfaceDeclaration(InterfaceDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InterfaceDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInterfaceDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitKeywordModifier(KeywordModifier node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(KeywordModifier.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitKeywordModifier(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitLabelledStatement(LabelledStatement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(LabelledStatement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitLabelledStatement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(MethodDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitMethodDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(MethodInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitMethodInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitModifiers(Modifiers node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Modifiers.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitModifiers(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitNormalTypeBody(NormalTypeBody node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(NormalTypeBody.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitNormalTypeBody(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitNullLiteral(NullLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(NullLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitNullLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitPackageDeclaration(PackageDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(PackageDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitPackageDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitParseArtefact(Node node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Node.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitParseArtefact(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitReturn(Return node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Return.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitReturn(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSelect(Select node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Select.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSelect(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitStaticInitializer(StaticInitializer node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(StaticInitializer.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitStaticInitializer(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitStringLiteral(StringLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(StringLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitStringLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSuper(Super node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Super.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSuper(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSuperConstructorInvocation(SuperConstructorInvocation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(SuperConstructorInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSuperConstructorInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSwitch(Switch node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Switch.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSwitch(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSynchronized(Synchronized node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Synchronized.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSynchronized(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitThis(This node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(This.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitThis(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitThrow(Throw node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Throw.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitThrow(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTry(Try node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Try.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTry(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTypeReference(TypeReference node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReference.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTypeReference(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTypeReferencePart(TypeReferencePart node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReferencePart.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTypeReferencePart(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTypeVariable(TypeVariable node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(TypeVariable.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTypeVariable(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitUnaryExpression(UnaryExpression node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(UnaryExpression.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitUnaryExpression(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableDeclaration(VariableDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableDefinition(VariableDefinition node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinition.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableDefinition(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinitionEntry.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableDefinitionEntry(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableReference(VariableReference node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableReference.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableReference(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitWhile(While node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(While.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitWhile(node);
- }
- }
- return false;
- }
- }
-
- /** Performs common AST searches for method calls and R-type-field references.
- * Note that this is a specialized form of the {@link DispatchVisitor}. */
- private class DelegatingJavaVisitor extends DispatchVisitor {
- private final JavaContext mContext;
- private final boolean mVisitResources;
- private final boolean mVisitMethods;
- private final boolean mVisitConstructors;
-
- public DelegatingJavaVisitor(JavaContext context) {
- mContext = context;
-
- mVisitMethods = !mMethodDetectors.isEmpty();
- mVisitConstructors = !mConstructorDetectors.isEmpty();
- mVisitResources = !mResourceFieldDetectors.isEmpty();
- }
-
- @Override
- public boolean visitSelect(Select node) {
- if (mVisitResources) {
- // R.type.name
- if (node.astOperand() instanceof Select) {
- Select select = (Select) node.astOperand();
- if (select.astOperand() instanceof VariableReference) {
- VariableReference reference = (VariableReference) select.astOperand();
- if (reference.astIdentifier().astValue().equals(R_CLASS)) {
- String type = select.astIdentifier().astValue();
- String name = node.astIdentifier().astValue();
-
- // R -could- be referenced locally and really have been
- // imported as "import android.R;" in the import statements,
- // but this is not recommended (and in fact there's a specific
- // lint rule warning against it)
- boolean isFramework = false;
-
- for (VisitingDetector v : mResourceFieldDetectors) {
- JavaScanner detector = v.getJavaScanner();
- //noinspection ConstantConditions
- detector.visitResourceReference(mContext, v.getVisitor(),
- node, type, name, isFramework);
- }
-
- return super.visitSelect(node);
- }
- }
- }
-
- // Arbitrary packages -- android.R.type.name, foo.bar.R.type.name
- if (node.astIdentifier().astValue().equals(R_CLASS)) {
- Node parent = node.getParent();
- if (parent instanceof Select) {
- Node grandParent = parent.getParent();
- if (grandParent instanceof Select) {
- Select select = (Select) grandParent;
- String name = select.astIdentifier().astValue();
- Expression typeOperand = select.astOperand();
- if (typeOperand instanceof Select) {
- Select typeSelect = (Select) typeOperand;
- String type = typeSelect.astIdentifier().astValue();
- boolean isFramework = node.astOperand().toString().equals(
- ANDROID_PKG);
- for (VisitingDetector v : mResourceFieldDetectors) {
- JavaScanner detector = v.getJavaScanner();
- detector.visitResourceReference(mContext, v.getVisitor(),
- node, type, name, isFramework);
- }
- }
- }
- }
- }
- }
-
- return super.visitSelect(node);
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (mVisitMethods) {
- String methodName = node.astName().astValue();
- List<VisitingDetector> list = mMethodDetectors.get(methodName);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getJavaScanner().visitMethod(mContext, v.getVisitor(), node);
- }
- }
- }
-
- return super.visitMethodInvocation(node);
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- if (mVisitConstructors) {
- TypeReference typeReference = node.astTypeReference();
- if (typeReference != null) {
- TypeReferencePart last = typeReference.astParts().last();
- if (last != null) {
- String name = last.astIdentifier().astValue();
- if (mConstructorSimpleNames.contains(name)) {
- ResolvedNode resolved = mContext.resolve(node);
- if (resolved instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- String type = method.getContainingClass().getName();
- List<VisitingDetector> list = mConstructorDetectors.get(type);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getJavaScanner().visitConstructor(mContext,
- v.getVisitor(), node, method);
- }
- }
-
- }
- }
- }
- }
- }
-
- return super.visitConstructorInvocation(node);
- }
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
deleted file mode 100755
index 473c95e..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
+++ /dev/null
@@ -1,1088 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.CLASS_FOLDER;
-import static com.android.SdkConstants.DOT_AAR;
-import static com.android.SdkConstants.DOT_JAR;
-import static com.android.SdkConstants.GEN_FOLDER;
-import static com.android.SdkConstants.LIBS_FOLDER;
-import static com.android.SdkConstants.RES_FOLDER;
-import static com.android.SdkConstants.SRC_FOLDER;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.Variant;
-import com.android.ide.common.repository.ResourceVisibilityLookup;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.prefs.AndroidLocation;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.sdklib.repository.local.LocalSdk;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.utils.XmlUtils;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Information about the tool embedding the lint analyzer. IDEs and other tools
- * implementing lint support will extend this to integrate logging, displaying errors,
- * etc.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public abstract class LintClient {
- private static final String PROP_BIN_DIR = "com.android.tools.lint.bindir"; //$NON-NLS-1$
-
- /**
- * Returns a configuration for use by the given project. The configuration
- * provides information about which issues are enabled, any customizations
- * to the severity of an issue, etc.
- * <p>
- * By default this method returns a {@link DefaultConfiguration}.
- *
- * @param project the project to obtain a configuration for
- * @param driver the current driver, if any
- * @return a configuration, never null.
- */
- @NonNull
- public Configuration getConfiguration(@NonNull Project project, @Nullable LintDriver driver) {
- return DefaultConfiguration.create(this, project, null);
- }
-
- /**
- * Report the given issue. This method will only be called if the configuration
- * provided by {@link #getConfiguration(Project,LintDriver)} has reported the corresponding
- * issue as enabled and has not filtered out the issue with its
- * {@link Configuration#ignore(Context,Issue,Location,String)} method.
- * <p>
- * @param context the context used by the detector when the issue was found
- * @param issue the issue that was found
- * @param severity the severity of the issue
- * @param location the location of the issue
- * @param message the associated user message
- * @param format the format of the description and location descriptions
- */
- public abstract void report(
- @NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message,
- @NonNull TextFormat format);
-
- /**
- * Send an exception or error message (with warning severity) to the log
- *
- * @param exception the exception, possibly null
- * @param format the error message using {@link String#format} syntax, possibly null
- * (though in that case the exception should not be null)
- * @param args any arguments for the format string
- */
- public void log(
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args) {
- log(Severity.WARNING, exception, format, args);
- }
-
- /**
- * Send an exception or error message to the log
- *
- * @param severity the severity of the warning
- * @param exception the exception, possibly null
- * @param format the error message using {@link String#format} syntax, possibly null
- * (though in that case the exception should not be null)
- * @param args any arguments for the format string
- */
- public abstract void log(
- @NonNull Severity severity,
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args);
-
- /**
- * Returns a {@link XmlParser} to use to parse XML
- *
- * @return a new {@link XmlParser}, or null if this client does not support
- * XML analysis
- */
- @Nullable
- public abstract XmlParser getXmlParser();
-
- /**
- * Returns a {@link JavaParser} to use to parse Java
- *
- * @param project the project to parse, if known (this can be used to look up
- * the class path for type attribution etc, and it can also be used
- * to more efficiently process a set of files, for example to
- * perform type attribution for multiple units in a single pass)
- * @return a new {@link JavaParser}, or null if this client does not
- * support Java analysis
- */
- @Nullable
- public abstract JavaParser getJavaParser(@Nullable Project project);
-
- /**
- * Returns an optimal detector, if applicable. By default, just returns the
- * original detector, but tools can replace detectors using this hook with a version
- * that takes advantage of native capabilities of the tool.
- *
- * @param detectorClass the class of the detector to be replaced
- * @return the new detector class, or just the original detector (not null)
- */
- @NonNull
- public Class<? extends Detector> replaceDetector(
- @NonNull Class<? extends Detector> detectorClass) {
- return detectorClass;
- }
-
- /**
- * Reads the given text file and returns the content as a string
- *
- * @param file the file to read
- * @return the string to return, never null (will be empty if there is an
- * I/O error)
- */
- @NonNull
- public abstract String readFile(@NonNull File file);
-
- /**
- * Reads the given binary file and returns the content as a byte array.
- * By default this method will read the bytes from the file directly,
- * but this can be customized by a client if for example I/O could be
- * held in memory and not flushed to disk yet.
- *
- * @param file the file to read
- * @return the bytes in the file, never null
- * @throws IOException if the file does not exist, or if the file cannot be
- * read for some reason
- */
- @NonNull
- public byte[] readBytes(@NonNull File file) throws IOException {
- return Files.toByteArray(file);
- }
-
- /**
- * Returns the list of source folders for Java source files
- *
- * @param project the project to look up Java source file locations for
- * @return a list of source folders to search for .java files
- */
- @NonNull
- public List<File> getJavaSourceFolders(@NonNull Project project) {
- return getClassPath(project).getSourceFolders();
- }
-
- /**
- * Returns the list of output folders for class files
- *
- * @param project the project to look up class file locations for
- * @return a list of output folders to search for .class files
- */
- @NonNull
- public List<File> getJavaClassFolders(@NonNull Project project) {
- return getClassPath(project).getClassFolders();
-
- }
-
- /**
- * Returns the list of Java libraries
- *
- * @param project the project to look up jar dependencies for
- * @return a list of jar dependencies containing .class files
- */
- @NonNull
- public List<File> getJavaLibraries(@NonNull Project project) {
- return getClassPath(project).getLibraries();
- }
-
- /**
- * Returns the list of source folders for test source files
- *
- * @param project the project to look up test source file locations for
- * @return a list of source folders to search for .java files
- */
- @NonNull
- public List<File> getTestSourceFolders(@NonNull Project project) {
- return getClassPath(project).getTestSourceFolders();
- }
-
- /**
- * Returns the resource folders.
- *
- * @param project the project to look up the resource folder for
- * @return a list of files pointing to the resource folders, possibly empty
- */
- @NonNull
- public List<File> getResourceFolders(@NonNull Project project) {
- File res = new File(project.getDir(), RES_FOLDER);
- if (res.exists()) {
- return Collections.singletonList(res);
- }
-
- return Collections.emptyList();
- }
-
- /**
- * Returns the {@link SdkInfo} to use for the given project.
- *
- * @param project the project to look up an {@link SdkInfo} for
- * @return an {@link SdkInfo} for the project
- */
- @NonNull
- public SdkInfo getSdkInfo(@NonNull Project project) {
- // By default no per-platform SDK info
- return new DefaultSdkInfo();
- }
-
- /**
- * Returns a suitable location for storing cache files. Note that the
- * directory may not exist.
- *
- * @param create if true, attempt to create the cache dir if it does not
- * exist
- * @return a suitable location for storing cache files, which may be null if
- * the create flag was false, or if for some reason the directory
- * could not be created
- */
- @Nullable
- public File getCacheDir(boolean create) {
- String home = System.getProperty("user.home");
- String relative = ".android" + File.separator + "cache"; //$NON-NLS-1$ //$NON-NLS-2$
- File dir = new File(home, relative);
- if (create && !dir.exists()) {
- if (!dir.mkdirs()) {
- return null;
- }
- }
- return dir;
- }
-
- /**
- * Returns the File corresponding to the system property or the environment variable
- * for {@link #PROP_BIN_DIR}.
- * This property is typically set by the SDK/tools/lint[.bat] wrapper.
- * It denotes the path of the wrapper on disk.
- *
- * @return A new File corresponding to {@link LintClient#PROP_BIN_DIR} or null.
- */
- @Nullable
- private static File getLintBinDir() {
- // First check the Java properties (e.g. set using "java -jar ... -Dname=value")
- String path = System.getProperty(PROP_BIN_DIR);
- if (path == null || path.isEmpty()) {
- // If not found, check environment variables.
- path = System.getenv(PROP_BIN_DIR);
- }
- if (path != null && !path.isEmpty()) {
- File file = new File(path);
- if (file.exists()) {
- return file;
- }
- }
- return null;
- }
-
- /**
- * Returns the File pointing to the user's SDK install area. This is generally
- * the root directory containing the lint tool (but also platforms/ etc).
- *
- * @return a file pointing to the user's install area
- */
- @Nullable
- public File getSdkHome() {
- File binDir = getLintBinDir();
- if (binDir != null) {
- assert binDir.getName().equals("tools");
-
- File root = binDir.getParentFile();
- if (root != null && root.isDirectory()) {
- return root;
- }
- }
-
- String home = System.getenv("ANDROID_HOME"); //$NON-NLS-1$
- if (home != null) {
- return new File(home);
- }
-
- return null;
- }
-
- /**
- * Locates an SDK resource (relative to the SDK root directory).
- * <p>
- * TODO: Consider switching to a {@link URL} return type instead.
- *
- * @param relativePath A relative path (using {@link File#separator} to
- * separate path components) to the given resource
- * @return a {@link File} pointing to the resource, or null if it does not
- * exist
- */
- @Nullable
- public File findResource(@NonNull String relativePath) {
- File top = getSdkHome();
- if (top == null) {
- throw new IllegalArgumentException("Lint must be invoked with the System property "
- + PROP_BIN_DIR + " pointing to the ANDROID_SDK tools directory");
- }
-
- File file = new File(top, relativePath);
- if (file.exists()) {
- return file;
- } else {
- return null;
- }
- }
-
- private Map<Project, ClassPathInfo> mProjectInfo;
-
- /**
- * Returns true if this project is a Gradle-based Android project
- *
- * @param project the project to check
- * @return true if this is a Gradle-based project
- */
- public boolean isGradleProject(Project project) {
- // This is not an accurate test; specific LintClient implementations (e.g.
- // IDEs or a gradle-integration of lint) have more context and can perform a more accurate
- // check
- if (new File(project.getDir(), SdkConstants.FN_BUILD_GRADLE).exists()) {
- return true;
- }
-
- File parent = project.getDir().getParentFile();
- if (parent != null && parent.getName().equals(SdkConstants.FD_SOURCES)) {
- File root = parent.getParentFile();
- if (root != null && new File(root, SdkConstants.FN_BUILD_GRADLE).exists()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Information about class paths (sources, class files and libraries)
- * usually associated with a project.
- */
- protected static class ClassPathInfo {
- private final List<File> mClassFolders;
- private final List<File> mSourceFolders;
- private final List<File> mLibraries;
- private final List<File> mTestFolders;
-
- public ClassPathInfo(
- @NonNull List<File> sourceFolders,
- @NonNull List<File> classFolders,
- @NonNull List<File> libraries,
- @NonNull List<File> testFolders) {
- mSourceFolders = sourceFolders;
- mClassFolders = classFolders;
- mLibraries = libraries;
- mTestFolders = testFolders;
- }
-
- @NonNull
- public List<File> getSourceFolders() {
- return mSourceFolders;
- }
-
- @NonNull
- public List<File> getClassFolders() {
- return mClassFolders;
- }
-
- @NonNull
- public List<File> getLibraries() {
- return mLibraries;
- }
-
- public List<File> getTestSourceFolders() {
- return mTestFolders;
- }
- }
-
- /**
- * Considers the given project as an Eclipse project and returns class path
- * information for the project - the source folder(s), the output folder and
- * any libraries.
- * <p>
- * Callers will not cache calls to this method, so if it's expensive to compute
- * the classpath info, this method should perform its own caching.
- *
- * @param project the project to look up class path info for
- * @return a class path info object, never null
- */
- @NonNull
- protected ClassPathInfo getClassPath(@NonNull Project project) {
- ClassPathInfo info;
- if (mProjectInfo == null) {
- mProjectInfo = Maps.newHashMap();
- info = null;
- } else {
- info = mProjectInfo.get(project);
- }
-
- if (info == null) {
- List<File> sources = new ArrayList<File>(2);
- List<File> classes = new ArrayList<File>(1);
- List<File> libraries = new ArrayList<File>();
- // No test folders in Eclipse:
- // https://bugs.eclipse.org/bugs/show_bug.cgi?id=224708
- List<File> tests = Collections.emptyList();
-
- File projectDir = project.getDir();
- File classpathFile = new File(projectDir, ".classpath"); //$NON-NLS-1$
- if (classpathFile.exists()) {
- String classpathXml = readFile(classpathFile);
- try {
- Document document = XmlUtils.parseDocument(classpathXml, false);
- NodeList tags = document.getElementsByTagName("classpathentry"); //$NON-NLS-1$
- for (int i = 0, n = tags.getLength(); i < n; i++) {
- Element element = (Element) tags.item(i);
- String kind = element.getAttribute("kind"); //$NON-NLS-1$
- List<File> addTo = null;
- if (kind.equals("src")) { //$NON-NLS-1$
- addTo = sources;
- } else if (kind.equals("output")) { //$NON-NLS-1$
- addTo = classes;
- } else if (kind.equals("lib")) { //$NON-NLS-1$
- addTo = libraries;
- }
- if (addTo != null) {
- String path = element.getAttribute("path"); //$NON-NLS-1$
- File folder = new File(projectDir, path);
- if (folder.exists()) {
- addTo.add(folder);
- }
- }
- }
- } catch (Exception e) {
- log(null, null);
- }
- }
-
- // Add in libraries that aren't specified in the .classpath file
- File libs = new File(project.getDir(), LIBS_FOLDER);
- if (libs.isDirectory()) {
- File[] jars = libs.listFiles();
- if (jars != null) {
- for (File jar : jars) {
- if (LintUtils.endsWith(jar.getPath(), DOT_JAR)
- && !libraries.contains(jar)) {
- libraries.add(jar);
- }
- }
- }
- }
-
- if (classes.isEmpty()) {
- File folder = new File(projectDir, CLASS_FOLDER);
- if (folder.exists()) {
- classes.add(folder);
- } else {
- // Maven checks
- folder = new File(projectDir,
- "target" + File.separator + "classes"); //$NON-NLS-1$ //$NON-NLS-2$
- if (folder.exists()) {
- classes.add(folder);
-
- // If it's maven, also correct the source path, "src" works but
- // it's in a more specific subfolder
- if (sources.isEmpty()) {
- File src = new File(projectDir,
- "src" + File.separator //$NON-NLS-1$
- + "main" + File.separator //$NON-NLS-1$
- + "java"); //$NON-NLS-1$
- if (src.exists()) {
- sources.add(src);
- } else {
- src = new File(projectDir, SRC_FOLDER);
- if (src.exists()) {
- sources.add(src);
- }
- }
-
- File gen = new File(projectDir,
- "target" + File.separator //$NON-NLS-1$
- + "generated-sources" + File.separator //$NON-NLS-1$
- + "r"); //$NON-NLS-1$
- if (gen.exists()) {
- sources.add(gen);
- }
- }
- }
- }
- }
-
- // Fallback, in case there is no Eclipse project metadata here
- if (sources.isEmpty()) {
- File src = new File(projectDir, SRC_FOLDER);
- if (src.exists()) {
- sources.add(src);
- }
- File gen = new File(projectDir, GEN_FOLDER);
- if (gen.exists()) {
- sources.add(gen);
- }
- }
-
- info = new ClassPathInfo(sources, classes, libraries, tests);
- mProjectInfo.put(project, info);
- }
-
- return info;
- }
-
- /**
- * A map from directory to existing projects, or null. Used to ensure that
- * projects are unique for a directory (in case we process a library project
- * before its including project for example)
- */
- private Map<File, Project> mDirToProject;
-
- /**
- * Returns a project for the given directory. This should return the same
- * project for the same directory if called repeatedly.
- *
- * @param dir the directory containing the project
- * @param referenceDir See {@link Project#getReferenceDir()}.
- * @return a project, never null
- */
- @NonNull
- public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
- if (mDirToProject == null) {
- mDirToProject = new HashMap<File, Project>();
- }
-
- File canonicalDir = dir;
- try {
- // Attempt to use the canonical handle for the file, in case there
- // are symlinks etc present (since when handling library projects,
- // we also call getCanonicalFile to compute the result of appending
- // relative paths, which can then resolve symlinks and end up with
- // a different prefix)
- canonicalDir = dir.getCanonicalFile();
- } catch (IOException ioe) {
- // pass
- }
-
- Project project = mDirToProject.get(canonicalDir);
- if (project != null) {
- return project;
- }
-
- project = createProject(dir, referenceDir);
- mDirToProject.put(canonicalDir, project);
- return project;
- }
-
- /**
- * Returns the list of known projects (projects registered via
- * {@link #getProject(File, File)}
- *
- * @return a collection of projects in any order
- */
- public Collection<Project> getKnownProjects() {
- return mDirToProject != null ? mDirToProject.values() : Collections.<Project>emptyList();
- }
-
- /**
- * Registers the given project for the given directory. This can
- * be used when projects are initialized outside of the client itself.
- *
- * @param dir the directory of the project, which must be unique
- * @param project the project
- */
- public void registerProject(@NonNull File dir, @NonNull Project project) {
- File canonicalDir = dir;
- try {
- // Attempt to use the canonical handle for the file, in case there
- // are symlinks etc present (since when handling library projects,
- // we also call getCanonicalFile to compute the result of appending
- // relative paths, which can then resolve symlinks and end up with
- // a different prefix)
- canonicalDir = dir.getCanonicalFile();
- } catch (IOException ioe) {
- // pass
- }
-
-
- if (mDirToProject == null) {
- mDirToProject = new HashMap<File, Project>();
- } else {
- assert !mDirToProject.containsKey(dir) : dir;
- }
- mDirToProject.put(canonicalDir, project);
- }
-
- private Set<File> mProjectDirs = Sets.newHashSet();
-
- /**
- * Create a project for the given directory
- * @param dir the root directory of the project
- * @param referenceDir See {@link Project#getReferenceDir()}.
- * @return a new project
- */
- @NonNull
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- if (mProjectDirs.contains(dir)) {
- throw new CircularDependencyException(
- "Circular library dependencies; check your project.properties files carefully");
- }
- mProjectDirs.add(dir);
- return Project.create(this, dir, referenceDir);
- }
-
- /**
- * Returns the name of the given project
- *
- * @param project the project to look up
- * @return the name of the project
- */
- @NonNull
- public String getProjectName(@NonNull Project project) {
- return project.getDir().getName();
- }
-
- protected IAndroidTarget[] mTargets;
-
- /**
- * Returns all the {@link IAndroidTarget} versions installed in the user's SDK install
- * area.
- *
- * @return all the installed targets
- */
- @NonNull
- public IAndroidTarget[] getTargets() {
- if (mTargets == null) {
- LocalSdk localSdk = getSdk();
- if (localSdk != null) {
- mTargets = localSdk.getTargets();
- } else {
- mTargets = new IAndroidTarget[0];
- }
- }
-
- return mTargets;
- }
-
- protected LocalSdk mSdk;
-
- /**
- * Returns the SDK installation (used to look up platforms etc)
- *
- * @return the SDK if known
- */
- @Nullable
- public LocalSdk getSdk() {
- if (mSdk == null) {
- File sdkHome = getSdkHome();
- if (sdkHome != null) {
- mSdk = new LocalSdk(sdkHome);
- }
- }
-
- return mSdk;
- }
-
- /**
- * Returns the compile target to use for the given project
- *
- * @param project the project in question
- *
- * @return the compile target to use to build the given project
- */
- @Nullable
- public IAndroidTarget getCompileTarget(@NonNull Project project) {
- int buildSdk = project.getBuildSdk();
- IAndroidTarget[] targets = getTargets();
- for (int i = targets.length - 1; i >= 0; i--) {
- IAndroidTarget target = targets[i];
- if (target.isPlatform() && target.getVersion().getApiLevel() == buildSdk) {
- return target;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the highest known API level.
- *
- * @return the highest known API level
- */
- public int getHighestKnownApiLevel() {
- int max = SdkVersionInfo.HIGHEST_KNOWN_STABLE_API;
-
- for (IAndroidTarget target : getTargets()) {
- if (target.isPlatform()) {
- int api = target.getVersion().getApiLevel();
- if (api > max && !target.getVersion().isPreview()) {
- max = api;
- }
- }
- }
-
- return max;
- }
-
- /**
- * Returns the super class for the given class name, which should be in VM
- * format (e.g. java/lang/Integer, not java.lang.Integer, and using $ rather
- * than . for inner classes). If the super class is not known, returns null.
- * <p>
- * This is typically not necessary, since lint analyzes all the available
- * classes. However, if this lint client is invoking lint in an incremental
- * context (for example, an IDE offering incremental analysis of a single
- * source file), then lint may not see all the classes, and the client can
- * provide its own super class lookup.
- *
- * @param project the project containing the class
- * @param name the fully qualified class name
- * @return the corresponding super class name (in VM format), or null if not
- * known
- */
- @Nullable
- public String getSuperClass(@NonNull Project project, @NonNull String name) {
- assert name.indexOf('.') == -1 : "Use VM signatures, e.g. java/lang/Integer";
-
- if ("java/lang/Object".equals(name)) { //$NON-NLS-1$
- return null;
- }
-
- String superClass = project.getSuperClassMap().get(name);
- if (superClass != null) {
- return superClass;
- }
-
- for (Project library : project.getAllLibraries()) {
- superClass = library.getSuperClassMap().get(name);
- if (superClass != null) {
- return superClass;
- }
- }
-
- return null;
- }
-
- /**
- * Creates a super class map for the given project. The map maps from
- * internal class name (e.g. java/lang/Integer, not java.lang.Integer) to its
- * corresponding super class name. The root class, java/lang/Object, is not in the map.
- *
- * @param project the project to initialize the super class with; this will include
- * local classes as well as any local .jar libraries; not transitive
- * dependencies
- * @return a map from class to its corresponding super class; never null
- */
- @NonNull
- public Map<String, String> createSuperClassMap(@NonNull Project project) {
- List<File> libraries = project.getJavaLibraries();
- List<File> classFolders = project.getJavaClassFolders();
- List<ClassEntry> classEntries = ClassEntry.fromClassPath(this, classFolders, true);
- if (libraries.isEmpty()) {
- return ClassEntry.createSuperClassMap(this, classEntries);
- }
- List<ClassEntry> libraryEntries = ClassEntry.fromClassPath(this, libraries, true);
- return ClassEntry.createSuperClassMap(this, libraryEntries, classEntries);
- }
-
- /**
- * Checks whether the given name is a subclass of the given super class. If
- * the method does not know, it should return null, and otherwise return
- * {@link Boolean#TRUE} or {@link Boolean#FALSE}.
- * <p>
- * Note that the class names are in internal VM format (java/lang/Integer,
- * not java.lang.Integer, and using $ rather than . for inner classes).
- *
- * @param project the project context to look up the class in
- * @param name the name of the class to be checked
- * @param superClassName the name of the super class to compare to
- * @return true if the class of the given name extends the given super class
- */
- @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
- @Nullable
- public Boolean isSubclassOf(
- @NonNull Project project,
- @NonNull String name,
- @NonNull String superClassName) {
- return null;
- }
-
- /**
- * Finds any custom lint rule jars that should be included for analysis,
- * regardless of project.
- * <p>
- * The default implementation locates custom lint jars in ~/.android/lint/ and
- * in $ANDROID_LINT_JARS
- *
- * @return a list of rule jars (possibly empty).
- */
- @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
- @NonNull
- public List<File> findGlobalRuleJars() {
- // Look for additional detectors registered by the user, via
- // (1) an environment variable (useful for build servers etc), and
- // (2) via jar files in the .android/lint directory
- List<File> files = null;
- try {
- String androidHome = AndroidLocation.getFolder();
- File lint = new File(androidHome + File.separator + "lint"); //$NON-NLS-1$
- if (lint.exists()) {
- File[] list = lint.listFiles();
- if (list != null) {
- for (File jarFile : list) {
- if (endsWith(jarFile.getName(), DOT_JAR)) {
- if (files == null) {
- files = new ArrayList<File>();
- }
- files.add(jarFile);
- }
- }
- }
- }
- } catch (AndroidLocation.AndroidLocationException e) {
- // Ignore -- no android dir, so no rules to load.
- }
-
- String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$
- if (lintClassPath != null && !lintClassPath.isEmpty()) {
- String[] paths = lintClassPath.split(File.pathSeparator);
- for (String path : paths) {
- File jarFile = new File(path);
- if (jarFile.exists()) {
- if (files == null) {
- files = new ArrayList<File>();
- } else if (files.contains(jarFile)) {
- continue;
- }
- files.add(jarFile);
- }
- }
- }
-
- return files != null ? files : Collections.<File>emptyList();
- }
-
- /**
- * Finds any custom lint rule jars that should be included for analysis
- * in the given project
- *
- * @param project the project to look up rule jars from
- * @return a list of rule jars (possibly empty).
- */
- @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
- @NonNull
- public List<File> findRuleJars(@NonNull Project project) {
- if (project.isGradleProject()) {
- if (project.isLibrary()) {
- AndroidLibrary model = project.getGradleLibraryModel();
- if (model != null) {
- File lintJar = model.getLintJar();
- if (lintJar.exists()) {
- return Collections.singletonList(lintJar);
- }
- }
- } else if (project.getSubset() != null) {
- // Probably just analyzing a single file: we still want to look for custom
- // rules applicable to the file
- List<File> rules = null;
- final Variant variant = project.getCurrentVariant();
- if (variant != null) {
- Collection<AndroidLibrary> libraries = variant.getMainArtifact()
- .getDependencies().getLibraries();
- for (AndroidLibrary library : libraries) {
- File lintJar = library.getLintJar();
- if (lintJar.exists()) {
- if (rules == null) {
- rules = Lists.newArrayListWithExpectedSize(4);
- }
- rules.add(lintJar);
- }
- }
- if (rules != null) {
- return rules;
- }
- }
- } else if (project.getDir().getPath().endsWith(DOT_AAR)) {
- File lintJar = new File(project.getDir(), "lint.jar"); //$NON-NLS-1$
- if (lintJar.exists()) {
- return Collections.singletonList(lintJar);
- }
- }
- }
-
- return Collections.emptyList();
- }
-
- /**
- * Opens a URL connection.
- *
- * Clients such as IDEs can override this to for example consider the user's IDE proxy
- * settings.
- *
- * @param url the URL to read
- * @return a {@link java.net.URLConnection} or null
- * @throws IOException if any kind of IO exception occurs
- */
- @Nullable
- public URLConnection openConnection(@NonNull URL url) throws IOException {
- return url.openConnection();
- }
-
- /** Closes a connection previously returned by {@link #openConnection(java.net.URL)} */
- public void closeConnection(@NonNull URLConnection connection) throws IOException {
- if (connection instanceof HttpURLConnection) {
- ((HttpURLConnection)connection).disconnect();
- }
- }
-
- /**
- * Returns true if the given directory is a lint project directory.
- * By default, a project directory is the directory containing a manifest file,
- * but in Gradle projects for example it's the root gradle directory.
- *
- * @param dir the directory to check
- * @return true if the directory represents a lint project
- */
- @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
- public boolean isProjectDirectory(@NonNull File dir) {
- return LintUtils.isManifestFolder(dir) || Project.isAospFrameworksProject(dir);
- }
-
- /**
- * Returns whether lint should look for suppress comments. Tools that already do
- * this on their own can return false here to avoid doing unnecessary work.
- */
- public boolean checkForSuppressComments() {
- return true;
- }
-
- /**
- * Adds in any custom lint rules and returns the result as a new issue registry,
- * or the same one if no custom rules were found
- *
- * @param registry the main registry to add rules to
- * @return a new registry containing the passed in rules plus any custom rules,
- * or the original registry if no custom rules were found
- */
- public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) {
- List<File> jarFiles = findGlobalRuleJars();
-
- if (!jarFiles.isEmpty()) {
- List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
- registries.add(registry);
- for (File jarFile : jarFiles) {
- try {
- registries.add(JarFileIssueRegistry.get(this, jarFile));
- } catch (Throwable e) {
- log(e, "Could not load custom rule jar file %1$s", jarFile);
- }
- }
- if (registries.size() > 1) { // the first item is the passed in registry itself
- return new CompositeIssueRegistry(registries);
- }
- }
-
- return registry;
- }
-
- /**
- * Returns true if this client supports project resource repository lookup via
- * {@link #getProjectResources(Project,boolean)}
- *
- * @return true if the client can provide project resources
- */
- public boolean supportsProjectResources() {
- return false;
- }
-
- /**
- * Returns the project resources, if available
- *
- * @param includeDependencies if true, include merged view of all dependencies
- * @return the project resources, or null if not available
- */
- @Nullable
- public AbstractResourceRepository getProjectResources(Project project,
- boolean includeDependencies) {
- return null;
- }
-
- /**
- * For a lint client which supports resource items (via {@link #supportsProjectResources()})
- * return a handle for a resource item
- *
- * @param item the resource item to look up a location handle for
- * @return a corresponding handle
- */
- @NonNull
- public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
- return new Location.ResourceItemHandle(item);
- }
-
- private ResourceVisibilityLookup.Provider mResourceVisibility;
-
- /**
- * Returns a shared {@link ResourceVisibilityLookup.Provider}
- *
- * @return a shared provider for looking up resource visibility
- */
- @NonNull
- public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() {
- if (mResourceVisibility == null) {
- mResourceVisibility = new ResourceVisibilityLookup.Provider();
- }
- return mResourceVisibility;
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
deleted file mode 100644
index b30cdb8..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
+++ /dev/null
@@ -1,2557 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.ATTR_IGNORE;
-import static com.android.SdkConstants.CLASS_CONSTRUCTOR;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.DOT_CLASS;
-import static com.android.SdkConstants.DOT_JAR;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.FD_GRADLE_WRAPPER;
-import static com.android.SdkConstants.FN_GRADLE_WRAPPER_PROPERTIES;
-import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
-import static com.android.SdkConstants.RES_FOLDER;
-import static com.android.SdkConstants.SUPPRESS_ALL;
-import static com.android.SdkConstants.SUPPRESS_LINT;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.ide.common.resources.configuration.FolderConfiguration.QUALIFIER_SPLITTER;
-import static com.android.tools.lint.detector.api.LintUtils.isAnonymousClass;
-import static java.io.File.separator;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.resources.ResourceFolderType;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.repository.local.LocalSdk;
-import com.android.tools.lint.client.api.LintListener.EventType;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceContext;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Objects;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.AnnotationNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationValue;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.Expression;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.TypeDeclaration;
-import lombok.ast.TypeReference;
-import lombok.ast.VariableDefinition;
-
-/**
- * Analyzes Android projects and files
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class LintDriver {
- /**
- * Max number of passes to run through the lint runner if requested by
- * {@link #requestRepeat}
- */
- private static final int MAX_PHASES = 3;
- private static final String SUPPRESS_LINT_VMSIG = '/' + SUPPRESS_LINT + ';';
- /** Prefix used by the comment suppress mechanism in Studio/IntelliJ */
- private static final String STUDIO_ID_PREFIX = "AndroidLint";
-
- private final LintClient mClient;
- private LintRequest mRequest;
- private IssueRegistry mRegistry;
- private volatile boolean mCanceled;
- private EnumSet<Scope> mScope;
- private List<? extends Detector> mApplicableDetectors;
- private Map<Scope, List<Detector>> mScopeDetectors;
- private List<LintListener> mListeners;
- private int mPhase;
- private List<Detector> mRepeatingDetectors;
- private EnumSet<Scope> mRepeatScope;
- private Project[] mCurrentProjects;
- private Project mCurrentProject;
- private boolean mAbbreviating = true;
- private boolean mParserErrors;
- private Map<Object,Object> mProperties;
-
- /**
- * Creates a new {@link LintDriver}
- *
- * @param registry The registry containing issues to be checked
- * @param client the tool wrapping the analyzer, such as an IDE or a CLI
- */
- public LintDriver(@NonNull IssueRegistry registry, @NonNull LintClient client) {
- mRegistry = registry;
- mClient = new LintClientWrapper(client);
- }
-
- /** Cancels the current lint run as soon as possible */
- public void cancel() {
- mCanceled = true;
- }
-
- /**
- * Returns the scope for the lint job
- *
- * @return the scope, never null
- */
- @NonNull
- public EnumSet<Scope> getScope() {
- return mScope;
- }
-
- /**
- * Sets the scope for the lint job
- *
- * @param scope the scope to use
- */
- public void setScope(@NonNull EnumSet<Scope> scope) {
- mScope = scope;
- }
-
- /**
- * Returns the lint client requesting the lint check. This may not be the same
- * instance as the one passed in to this driver; lint uses a wrapper which performs
- * additional validation to ensure that for example badly behaved detectors which report
- * issues that have been disabled will get muted without the real lint client getting
- * notified. Thus, this {@link LintClient} is suitable for use by detectors to look
- * up a client to for example get location handles from, but tool handling code should
- * never try to cast this client back to their original lint client. For the original
- * lint client, use {@link LintRequest} instead.
- *
- * @return the client, never null
- */
- @NonNull
- public LintClient getClient() {
- return mClient;
- }
-
- /**
- * Returns the current request, which points to the original files to be checked,
- * the original scope, the original {@link LintClient}, as well as the release mode.
- *
- * @return the request
- */
- @NonNull
- public LintRequest getRequest() {
- return mRequest;
- }
-
- /**
- * Records a property for later retrieval by {@link #getProperty(Object)}
- *
- * @param key the key to associate the value with
- * @param value the value, or null to remove a previous binding
- */
- public void putProperty(@NonNull Object key, @Nullable Object value) {
- if (mProperties == null) {
- mProperties = Maps.newHashMap();
- }
- if (value == null) {
- mProperties.remove(key);
- } else {
- mProperties.put(key, value);
- }
- }
-
- /**
- * Returns the property previously stored with the given key, or null
- *
- * @param key the key
- * @return the value or null if not found
- */
- @Nullable
- public Object getProperty(@NonNull Object key) {
- if (mProperties != null) {
- return mProperties.get(key);
- }
-
- return null;
- }
-
- /**
- * Returns the current phase number. The first pass is numbered 1. Only one pass
- * will be performed, unless a {@link Detector} calls {@link #requestRepeat}.
- *
- * @return the current phase, usually 1
- */
- public int getPhase() {
- return mPhase;
- }
-
- /**
- * Returns the current {@link IssueRegistry}.
- *
- * @return the current {@link IssueRegistry}
- */
- @NonNull
- public IssueRegistry getRegistry() {
- return mRegistry;
- }
-
- /**
- * Returns the project containing a given file, or null if not found. This searches
- * only among the currently checked project and its library projects, not among all
- * possible projects being scanned sequentially.
- *
- * @param file the file to be checked
- * @return the corresponding project, or null if not found
- */
- @Nullable
- public Project findProjectFor(@NonNull File file) {
- if (mCurrentProjects != null) {
- if (mCurrentProjects.length == 1) {
- return mCurrentProjects[0];
- }
- String path = file.getPath();
- for (Project project : mCurrentProjects) {
- if (path.startsWith(project.getDir().getPath())) {
- return project;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Sets whether lint should abbreviate output when appropriate.
- *
- * @param abbreviating true to abbreviate output, false to include everything
- */
- public void setAbbreviating(boolean abbreviating) {
- mAbbreviating = abbreviating;
- }
-
- /**
- * Returns whether lint should abbreviate output when appropriate.
- *
- * @return true if lint should abbreviate output, false when including everything
- */
- public boolean isAbbreviating() {
- return mAbbreviating;
- }
-
- /**
- * Returns whether lint has encountered any files with fatal parser errors
- * (e.g. broken source code, or even broken parsers)
- * <p>
- * This is useful for checks that need to make sure they've seen all data in
- * order to be conclusive (such as an unused resource check).
- *
- * @return true if any files were not properly processed because they
- * contained parser errors
- */
- public boolean hasParserErrors() {
- return mParserErrors;
- }
-
- /**
- * Sets whether lint has encountered files with fatal parser errors.
- *
- * @see #hasParserErrors()
- * @param hasErrors whether parser errors have been encountered
- */
- public void setHasParserErrors(boolean hasErrors) {
- mParserErrors = hasErrors;
- }
-
- /**
- * Returns the projects being analyzed
- *
- * @return the projects being analyzed
- */
- @NonNull
- public List<Project> getProjects() {
- if (mCurrentProjects != null) {
- return Arrays.asList(mCurrentProjects);
- }
- return Collections.emptyList();
- }
-
- /**
- * Analyze the given file (which can point to an Android project). Issues found
- * are reported to the associated {@link LintClient}.
- *
- * @param files the files and directories to be analyzed
- * @param scope the scope of the analysis; detectors with a wider scope will
- * not be run. If null, the scope will be inferred from the files.
- * @deprecated use {@link #analyze(LintRequest) instead}
- */
- @Deprecated
- public void analyze(@NonNull List<File> files, @Nullable EnumSet<Scope> scope) {
- analyze(new LintRequest(mClient, files).setScope(scope));
- }
-
- /**
- * Analyze the given files (which can point to Android projects or directories
- * containing Android projects). Issues found are reported to the associated
- * {@link LintClient}.
- * <p>
- * Note that the {@link LintDriver} is not multi thread safe or re-entrant;
- * if you want to run potentially overlapping lint jobs, create a separate driver
- * for each job.
- *
- * @param request the files and directories to be analyzed
- */
- public void analyze(@NonNull LintRequest request) {
- try {
- mRequest = request;
- analyze();
- } finally {
- mRequest = null;
- }
- }
-
- /** Runs the driver to analyze the requested files */
- private void analyze() {
- mCanceled = false;
- mScope = mRequest.getScope();
- assert mScope == null || !mScope.contains(Scope.ALL_RESOURCE_FILES) ||
- mScope.contains(Scope.RESOURCE_FILE);
-
- Collection<Project> projects;
- try {
- projects = mRequest.getProjects();
- if (projects == null) {
- projects = computeProjects(mRequest.getFiles());
- }
- } catch (CircularDependencyException e) {
- mCurrentProject = e.getProject();
- if (mCurrentProject != null) {
- Location location = e.getLocation();
- File file = location != null ? location.getFile() : mCurrentProject.getDir();
- Context context = new Context(this, mCurrentProject, null, file);
- context.report(IssueRegistry.LINT_ERROR, e.getLocation(), e.getMessage());
- mCurrentProject = null;
- }
- return;
- }
- if (projects.isEmpty()) {
- mClient.log(null, "No projects found for %1$s", mRequest.getFiles().toString());
- return;
- }
- if (mCanceled) {
- return;
- }
-
- registerCustomDetectors(projects);
-
- if (mScope == null) {
- mScope = Scope.infer(projects);
- }
-
- fireEvent(EventType.STARTING, null);
-
- for (Project project : projects) {
- mPhase = 1;
-
- Project main = mRequest.getMainProject(project);
-
- // The set of available detectors varies between projects
- computeDetectors(project);
-
- if (mApplicableDetectors.isEmpty()) {
- // No detectors enabled in this project: skip it
- continue;
- }
-
- checkProject(project, main);
- if (mCanceled) {
- break;
- }
-
- runExtraPhases(project, main);
- }
-
- fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null);
- }
-
- @Nullable
- private Set<Issue> myCustomIssues;
-
- /**
- * Returns true if the given issue is an issue that was loaded as a custom rule
- * (e.g. a 3rd-party library provided the detector, it's not built in)
- *
- * @param issue the issue to be looked up
- * @return true if this is a custom (non-builtin) check
- */
- public boolean isCustomIssue(@NonNull Issue issue) {
- return myCustomIssues != null && myCustomIssues.contains(issue);
- }
-
- private void registerCustomDetectors(Collection<Project> projects) {
- // Look at the various projects, and if any of them provide a custom
- // lint jar, "add" them (this will replace the issue registry with
- // a CompositeIssueRegistry containing the original issue registry
- // plus JarFileIssueRegistry instances for each lint jar
- Set<File> jarFiles = Sets.newHashSet();
- for (Project project : projects) {
- jarFiles.addAll(mClient.findRuleJars(project));
- for (Project library : project.getAllLibraries()) {
- jarFiles.addAll(mClient.findRuleJars(library));
- }
- }
-
- jarFiles.addAll(mClient.findGlobalRuleJars());
-
- if (!jarFiles.isEmpty()) {
- List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
- registries.add(mRegistry);
- for (File jarFile : jarFiles) {
- try {
- IssueRegistry registry = JarFileIssueRegistry.get(mClient, jarFile);
- if (myCustomIssues == null) {
- myCustomIssues = Sets.newHashSet();
- }
- myCustomIssues.addAll(registry.getIssues());
- registries.add(registry);
- } catch (Throwable e) {
- mClient.log(e, "Could not load custom rule jar file %1$s", jarFile);
- }
- }
- if (registries.size() > 1) { // the first item is mRegistry itself
- mRegistry = new CompositeIssueRegistry(registries);
- }
- }
- }
-
- private void runExtraPhases(@NonNull Project project, @NonNull Project main) {
- // Did any detectors request another phase?
- if (mRepeatingDetectors != null) {
- // Yes. Iterate up to MAX_PHASES times.
-
- // During the extra phases, we might be narrowing the scope, and setting it in the
- // scope field such that detectors asking about the available scope will get the
- // correct result. However, we need to restore it to the original scope when this
- // is done in case there are other projects that will be checked after this, since
- // the repeated phases is done *per project*, not after all projects have been
- // processed.
- EnumSet<Scope> oldScope = mScope;
-
- do {
- mPhase++;
- fireEvent(EventType.NEW_PHASE,
- new Context(this, project, null, project.getDir()));
-
- // Narrow the scope down to the set of scopes requested by
- // the rules.
- if (mRepeatScope == null) {
- mRepeatScope = Scope.ALL;
- }
- mScope = Scope.intersect(mScope, mRepeatScope);
- if (mScope.isEmpty()) {
- break;
- }
-
- // Compute the detectors to use for this pass.
- // Unlike the normal computeDetectors(project) call,
- // this is going to use the existing instances, and include
- // those that apply for the configuration.
- computeRepeatingDetectors(mRepeatingDetectors, project);
-
- if (mApplicableDetectors.isEmpty()) {
- // No detectors enabled in this project: skip it
- continue;
- }
-
- checkProject(project, main);
- if (mCanceled) {
- break;
- }
- } while (mPhase < MAX_PHASES && mRepeatingDetectors != null);
-
- mScope = oldScope;
- }
- }
-
- private void computeRepeatingDetectors(List<Detector> detectors, Project project) {
- // Ensure that the current visitor is recomputed
- mCurrentFolderType = null;
- mCurrentVisitor = null;
- mCurrentXmlDetectors = null;
- mCurrentBinaryDetectors = null;
-
- // Create map from detector class to issue such that we can
- // compute applicable issues for each detector in the list of detectors
- // to be repeated
- List<Issue> issues = mRegistry.getIssues();
- Multimap<Class<? extends Detector>, Issue> issueMap =
- ArrayListMultimap.create(issues.size(), 3);
- for (Issue issue : issues) {
- issueMap.put(issue.getImplementation().getDetectorClass(), issue);
- }
-
- Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope =
- new HashMap<Class<? extends Detector>, EnumSet<Scope>>();
- Map<Scope, List<Detector>> scopeToDetectors =
- new EnumMap<Scope, List<Detector>>(Scope.class);
-
- List<Detector> detectorList = new ArrayList<Detector>();
- // Compute the list of detectors (narrowed down from mRepeatingDetectors),
- // and simultaneously build up the detectorToScope map which tracks
- // the scopes each detector is affected by (this is used to populate
- // the mScopeDetectors map which is used during iteration).
- Configuration configuration = project.getConfiguration(this);
- for (Detector detector : detectors) {
- Class<? extends Detector> detectorClass = detector.getClass();
- Collection<Issue> detectorIssues = issueMap.get(detectorClass);
- if (detectorIssues != null) {
- boolean add = false;
- for (Issue issue : detectorIssues) {
- // The reason we have to check whether the detector is enabled
- // is that this is a per-project property, so when running lint in multiple
- // projects, a detector enabled only in a different project could have
- // requested another phase, and we end up in this project checking whether
- // the detector is enabled here.
- if (!configuration.isEnabled(issue)) {
- continue;
- }
-
- add = true; // Include detector if any of its issues are enabled
-
- EnumSet<Scope> s = detectorToScope.get(detectorClass);
- EnumSet<Scope> issueScope = issue.getImplementation().getScope();
- if (s == null) {
- detectorToScope.put(detectorClass, issueScope);
- } else if (!s.containsAll(issueScope)) {
- EnumSet<Scope> union = EnumSet.copyOf(s);
- union.addAll(issueScope);
- detectorToScope.put(detectorClass, union);
- }
- }
-
- if (add) {
- detectorList.add(detector);
- EnumSet<Scope> union = detectorToScope.get(detector.getClass());
- for (Scope s : union) {
- List<Detector> list = scopeToDetectors.get(s);
- if (list == null) {
- list = new ArrayList<Detector>();
- scopeToDetectors.put(s, list);
- }
- list.add(detector);
- }
- }
- }
- }
-
- mApplicableDetectors = detectorList;
- mScopeDetectors = scopeToDetectors;
- mRepeatingDetectors = null;
- mRepeatScope = null;
-
- validateScopeList();
- }
-
- private void computeDetectors(@NonNull Project project) {
- // Ensure that the current visitor is recomputed
- mCurrentFolderType = null;
- mCurrentVisitor = null;
-
- Configuration configuration = project.getConfiguration(this);
- mScopeDetectors = new EnumMap<Scope, List<Detector>>(Scope.class);
- mApplicableDetectors = mRegistry.createDetectors(mClient, configuration,
- mScope, mScopeDetectors);
-
- validateScopeList();
- }
-
- /** Development diagnostics only, run with assertions on */
- @SuppressWarnings("all") // Turn off warnings for the intentional assertion side effect below
- private void validateScopeList() {
- boolean assertionsEnabled = false;
- assert assertionsEnabled = true; // Intentional side-effect
- if (assertionsEnabled) {
- List<Detector> resourceFileDetectors = mScopeDetectors.get(Scope.RESOURCE_FILE);
- if (resourceFileDetectors != null) {
- for (Detector detector : resourceFileDetectors) {
- assert detector instanceof ResourceXmlDetector : detector;
- }
- }
-
- List<Detector> manifestDetectors = mScopeDetectors.get(Scope.MANIFEST);
- if (manifestDetectors != null) {
- for (Detector detector : manifestDetectors) {
- assert detector instanceof Detector.XmlScanner : detector;
- }
- }
- List<Detector> javaCodeDetectors = mScopeDetectors.get(Scope.ALL_JAVA_FILES);
- if (javaCodeDetectors != null) {
- for (Detector detector : javaCodeDetectors) {
- assert detector instanceof Detector.JavaScanner : detector;
- }
- }
- List<Detector> javaFileDetectors = mScopeDetectors.get(Scope.JAVA_FILE);
- if (javaFileDetectors != null) {
- for (Detector detector : javaFileDetectors) {
- assert detector instanceof Detector.JavaScanner : detector;
- }
- }
-
- List<Detector> classDetectors = mScopeDetectors.get(Scope.CLASS_FILE);
- if (classDetectors != null) {
- for (Detector detector : classDetectors) {
- assert detector instanceof Detector.ClassScanner : detector;
- }
- }
-
- List<Detector> classCodeDetectors = mScopeDetectors.get(Scope.ALL_CLASS_FILES);
- if (classCodeDetectors != null) {
- for (Detector detector : classCodeDetectors) {
- assert detector instanceof Detector.ClassScanner : detector;
- }
- }
-
- List<Detector> gradleDetectors = mScopeDetectors.get(Scope.GRADLE_FILE);
- if (gradleDetectors != null) {
- for (Detector detector : gradleDetectors) {
- assert detector instanceof Detector.GradleScanner : detector;
- }
- }
-
- List<Detector> otherDetectors = mScopeDetectors.get(Scope.OTHER);
- if (otherDetectors != null) {
- for (Detector detector : otherDetectors) {
- assert detector instanceof Detector.OtherFileScanner : detector;
- }
- }
-
- List<Detector> dirDetectors = mScopeDetectors.get(Scope.RESOURCE_FOLDER);
- if (dirDetectors != null) {
- for (Detector detector : dirDetectors) {
- assert detector instanceof Detector.ResourceFolderScanner : detector;
- }
- }
-
- List<Detector> binaryDetectors = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE);
- if (binaryDetectors != null) {
- for (Detector detector : binaryDetectors) {
- assert detector instanceof Detector.BinaryResourceScanner : detector;
- }
- }
- }
- }
-
- private void registerProjectFile(
- @NonNull Map<File, Project> fileToProject,
- @NonNull File file,
- @NonNull File projectDir,
- @NonNull File rootDir) {
- fileToProject.put(file, mClient.getProject(projectDir, rootDir));
- }
-
- private Collection<Project> computeProjects(@NonNull List<File> files) {
- // Compute list of projects
- Map<File, Project> fileToProject = new LinkedHashMap<File, Project>();
-
- File sharedRoot = null;
-
- // Ensure that we have absolute paths such that if you lint
- // "foo bar" in "baz" we can show baz/ as the root
- if (files.size() > 1) {
- List<File> absolute = new ArrayList<File>(files.size());
- for (File file : files) {
- absolute.add(file.getAbsoluteFile());
- }
- files = absolute;
-
- sharedRoot = LintUtils.getCommonParent(files);
- if (sharedRoot != null && sharedRoot.getParentFile() == null) { // "/" ?
- sharedRoot = null;
- }
- }
-
- for (File file : files) {
- if (file.isDirectory()) {
- File rootDir = sharedRoot;
- if (rootDir == null) {
- rootDir = file;
- if (files.size() > 1) {
- rootDir = file.getParentFile();
- if (rootDir == null) {
- rootDir = file;
- }
- }
- }
-
- // Figure out what to do with a directory. Note that the meaning of the
- // directory can be ambiguous:
- // If you pass a directory which is unknown, we don't know if we should
- // search upwards (in case you're pointing at a deep java package folder
- // within the project), or if you're pointing at some top level directory
- // containing lots of projects you want to scan. We attempt to do the
- // right thing, which is to see if you're pointing right at a project or
- // right within it (say at the src/ or res/) folder, and if not, you're
- // hopefully pointing at a project tree that you want to scan recursively.
- if (mClient.isProjectDirectory(file)) {
- registerProjectFile(fileToProject, file, file, rootDir);
- continue;
- } else {
- File parent = file.getParentFile();
- if (parent != null) {
- if (mClient.isProjectDirectory(parent)) {
- registerProjectFile(fileToProject, file, parent, parent);
- continue;
- } else {
- parent = parent.getParentFile();
- if (parent != null && mClient.isProjectDirectory(parent)) {
- registerProjectFile(fileToProject, file, parent, parent);
- continue;
- }
- }
- }
-
- // Search downwards for nested projects
- addProjects(file, fileToProject, rootDir);
- }
- } else {
- // Pointed at a file: Search upwards for the containing project
- File parent = file.getParentFile();
- while (parent != null) {
- if (mClient.isProjectDirectory(parent)) {
- registerProjectFile(fileToProject, file, parent, parent);
- break;
- }
- parent = parent.getParentFile();
- }
- }
-
- if (mCanceled) {
- return Collections.emptySet();
- }
- }
-
- for (Map.Entry<File, Project> entry : fileToProject.entrySet()) {
- File file = entry.getKey();
- Project project = entry.getValue();
- if (!file.equals(project.getDir())) {
- if (file.isDirectory()) {
- try {
- File dir = file.getCanonicalFile();
- if (dir.equals(project.getDir())) {
- continue;
- }
- } catch (IOException ioe) {
- // pass
- }
- }
-
- project.addFile(file);
- }
- }
-
- // Partition the projects up such that we only return projects that aren't
- // included by other projects (e.g. because they are library projects)
-
- Collection<Project> allProjects = fileToProject.values();
- Set<Project> roots = new HashSet<Project>(allProjects);
- for (Project project : allProjects) {
- roots.removeAll(project.getAllLibraries());
- }
-
- // Report issues for all projects that are explicitly referenced. We need to
- // do this here, since the project initialization will mark all library
- // projects as no-report projects by default.
- for (Project project : allProjects) {
- // Report issues for all projects explicitly listed or found via a directory
- // traversal -- including library projects.
- project.setReportIssues(true);
- }
-
- if (LintUtils.assertionsEnabled()) {
- // Make sure that all the project directories are unique. This ensures
- // that we didn't accidentally end up with different project instances
- // for a library project discovered as a directory as well as one
- // initialized from the library project dependency list
- IdentityHashMap<Project, Project> projects =
- new IdentityHashMap<Project, Project>();
- for (Project project : roots) {
- projects.put(project, project);
- for (Project library : project.getAllLibraries()) {
- projects.put(library, library);
- }
- }
- Set<File> dirs = new HashSet<File>();
- for (Project project : projects.keySet()) {
- assert !dirs.contains(project.getDir());
- dirs.add(project.getDir());
- }
- }
-
- return roots;
- }
-
- private void addProjects(
- @NonNull File dir,
- @NonNull Map<File, Project> fileToProject,
- @NonNull File rootDir) {
- if (mCanceled) {
- return;
- }
-
- if (mClient.isProjectDirectory(dir)) {
- registerProjectFile(fileToProject, dir, dir, rootDir);
- } else {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- addProjects(file, fileToProject, rootDir);
- }
- }
- }
- }
- }
-
- private void checkProject(@NonNull Project project, @NonNull Project main) {
- File projectDir = project.getDir();
-
- Context projectContext = new Context(this, project, null, projectDir);
- fireEvent(EventType.SCANNING_PROJECT, projectContext);
-
- List<Project> allLibraries = project.getAllLibraries();
- Set<Project> allProjects = new HashSet<Project>(allLibraries.size() + 1);
- allProjects.add(project);
- allProjects.addAll(allLibraries);
- mCurrentProjects = allProjects.toArray(new Project[allProjects.size()]);
-
- mCurrentProject = project;
-
- for (Detector check : mApplicableDetectors) {
- check.beforeCheckProject(projectContext);
- if (mCanceled) {
- return;
- }
- }
-
- assert mCurrentProject == project;
- runFileDetectors(project, main);
-
- if (!Scope.checkSingleFile(mScope)) {
- List<Project> libraries = project.getAllLibraries();
- for (Project library : libraries) {
- Context libraryContext = new Context(this, library, project, projectDir);
- fireEvent(EventType.SCANNING_LIBRARY_PROJECT, libraryContext);
- mCurrentProject = library;
-
- for (Detector check : mApplicableDetectors) {
- check.beforeCheckLibraryProject(libraryContext);
- if (mCanceled) {
- return;
- }
- }
- assert mCurrentProject == library;
-
- runFileDetectors(library, main);
- if (mCanceled) {
- return;
- }
-
- assert mCurrentProject == library;
-
- for (Detector check : mApplicableDetectors) {
- check.afterCheckLibraryProject(libraryContext);
- if (mCanceled) {
- return;
- }
- }
- }
- }
-
- mCurrentProject = project;
-
- for (Detector check : mApplicableDetectors) {
- check.afterCheckProject(projectContext);
- if (mCanceled) {
- return;
- }
- }
-
- if (mCanceled) {
- mClient.report(
- projectContext,
- // Must provide an issue since API guarantees that the issue parameter
- IssueRegistry.CANCELLED,
- Severity.INFORMATIONAL,
- null /*range*/,
- "Lint canceled by user", TextFormat.RAW);
- }
-
- mCurrentProjects = null;
- }
-
- private void runFileDetectors(@NonNull Project project, @Nullable Project main) {
- // Look up manifest information (but not for library projects)
- if (project.isAndroidProject()) {
- for (File manifestFile : project.getManifestFiles()) {
- XmlParser parser = mClient.getXmlParser();
- if (parser != null) {
- XmlContext context = new XmlContext(this, project, main, manifestFile, null,
- parser);
- context.document = parser.parseXml(context);
- if (context.document != null) {
- try {
- project.readManifest(context.document);
-
- if ((!project.isLibrary() || (main != null
- && main.isMergingManifests()))
- && mScope.contains(Scope.MANIFEST)) {
- List<Detector> detectors = mScopeDetectors.get(Scope.MANIFEST);
- if (detectors != null) {
- ResourceVisitor v = new ResourceVisitor(parser, detectors,
- null);
- fireEvent(EventType.SCANNING_FILE, context);
- v.visitFile(context, manifestFile);
- }
- }
- } finally {
- if (context.document != null) { // else: freed by XmlVisitor above
- parser.dispose(context, context.document);
- }
- }
- }
- }
- }
-
- // Process both Scope.RESOURCE_FILE and Scope.ALL_RESOURCE_FILES detectors together
- // in a single pass through the resource directories.
- if (mScope.contains(Scope.ALL_RESOURCE_FILES)
- || mScope.contains(Scope.RESOURCE_FILE)
- || mScope.contains(Scope.RESOURCE_FOLDER)
- || mScope.contains(Scope.BINARY_RESOURCE_FILE)) {
- List<Detector> dirChecks = mScopeDetectors.get(Scope.RESOURCE_FOLDER);
- List<Detector> binaryChecks = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE);
- List<Detector> checks = union(mScopeDetectors.get(Scope.RESOURCE_FILE),
- mScopeDetectors.get(Scope.ALL_RESOURCE_FILES));
- boolean haveXmlChecks = checks != null && !checks.isEmpty();
- List<ResourceXmlDetector> xmlDetectors;
- if (haveXmlChecks) {
- xmlDetectors = new ArrayList<ResourceXmlDetector>(checks.size());
- for (Detector detector : checks) {
- if (detector instanceof ResourceXmlDetector) {
- xmlDetectors.add((ResourceXmlDetector) detector);
- }
- }
- haveXmlChecks = !xmlDetectors.isEmpty();
- } else {
- xmlDetectors = Collections.emptyList();
- }
- if (haveXmlChecks
- || dirChecks != null && !dirChecks.isEmpty()
- || binaryChecks != null && !binaryChecks.isEmpty()) {
- List<File> files = project.getSubset();
- if (files != null) {
- checkIndividualResources(project, main, xmlDetectors, dirChecks,
- binaryChecks, files);
- } else {
- List<File> resourceFolders = project.getResourceFolders();
- if (!resourceFolders.isEmpty()) {
- for (File res : resourceFolders) {
- checkResFolder(project, main, res, xmlDetectors, dirChecks,
- binaryChecks);
- }
- }
- }
- }
- }
-
- if (mCanceled) {
- return;
- }
- }
-
- if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) {
- List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE),
- mScopeDetectors.get(Scope.ALL_JAVA_FILES));
- if (checks != null && !checks.isEmpty()) {
- List<File> files = project.getSubset();
- if (files != null) {
- checkIndividualJavaFiles(project, main, checks, files);
- } else {
- List<File> sourceFolders = project.getJavaSourceFolders();
- if (mScope.contains(Scope.TEST_SOURCES)) {
- List<File> testFolders = project.getTestSourceFolders();
- if (!testFolders.isEmpty()) {
- List<File> combined = Lists.newArrayListWithExpectedSize(
- sourceFolders.size() + testFolders.size());
- combined.addAll(sourceFolders);
- combined.addAll(testFolders);
- sourceFolders = combined;
- }
- }
-
- checkJava(project, main, sourceFolders, checks);
-
- }
- }
- }
-
- if (mCanceled) {
- return;
- }
-
- if (mScope.contains(Scope.CLASS_FILE)
- || mScope.contains(Scope.ALL_CLASS_FILES)
- || mScope.contains(Scope.JAVA_LIBRARIES)) {
- checkClasses(project, main);
- }
-
- if (mCanceled) {
- return;
- }
-
- if (mScope.contains(Scope.GRADLE_FILE)) {
- checkBuildScripts(project, main);
- }
-
- if (mCanceled) {
- return;
- }
-
- if (mScope.contains(Scope.OTHER)) {
- List<Detector> checks = mScopeDetectors.get(Scope.OTHER);
- if (checks != null) {
- OtherFileVisitor visitor = new OtherFileVisitor(checks);
- visitor.scan(this, project, main);
- }
- }
-
- if (mCanceled) {
- return;
- }
-
- if (project == main && mScope.contains(Scope.PROGUARD_FILE) &&
- project.isAndroidProject()) {
- checkProGuard(project, main);
- }
-
- if (project == main && mScope.contains(Scope.PROPERTY_FILE)) {
- checkProperties(project, main);
- }
- }
-
- private void checkBuildScripts(Project project, Project main) {
- List<Detector> detectors = mScopeDetectors.get(Scope.GRADLE_FILE);
- if (detectors != null) {
- List<File> files = project.getSubset();
- if (files == null) {
- files = project.getGradleBuildScripts();
- }
- for (File file : files) {
- Context context = new Context(this, project, main, file);
- fireEvent(EventType.SCANNING_FILE, context);
- for (Detector detector : detectors) {
- if (detector.appliesTo(context, file)) {
- detector.beforeCheckFile(context);
- detector.visitBuildScript(context, Maps.<String, Object>newHashMap());
- detector.afterCheckFile(context);
- }
- }
- }
- }
- }
-
- private void checkProGuard(Project project, Project main) {
- List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE);
- if (detectors != null) {
- List<File> files = project.getProguardFiles();
- for (File file : files) {
- Context context = new Context(this, project, main, file);
- fireEvent(EventType.SCANNING_FILE, context);
- for (Detector detector : detectors) {
- if (detector.appliesTo(context, file)) {
- detector.beforeCheckFile(context);
- detector.run(context);
- detector.afterCheckFile(context);
- }
- }
- }
- }
- }
-
- private void checkProperties(Project project, Project main) {
- List<Detector> detectors = mScopeDetectors.get(Scope.PROPERTY_FILE);
- if (detectors != null) {
- checkPropertyFile(project, main, detectors, FN_LOCAL_PROPERTIES);
- checkPropertyFile(project, main, detectors, FD_GRADLE_WRAPPER + separator +
- FN_GRADLE_WRAPPER_PROPERTIES);
- }
- }
-
- private void checkPropertyFile(Project project, Project main, List<Detector> detectors,
- String relativePath) {
- File file = new File(project.getDir(), relativePath);
- if (file.exists()) {
- Context context = new Context(this, project, main, file);
- fireEvent(EventType.SCANNING_FILE, context);
- for (Detector detector : detectors) {
- if (detector.appliesTo(context, file)) {
- detector.beforeCheckFile(context);
- detector.run(context);
- detector.afterCheckFile(context);
- }
- }
- }
- }
-
- /** True if execution has been canceled */
- boolean isCanceled() {
- return mCanceled;
- }
-
- /**
- * Returns the super class for the given class name,
- * which should be in VM format (e.g. java/lang/Integer, not java.lang.Integer).
- * If the super class is not known, returns null. This can happen if
- * the given class is not a known class according to the project or its
- * libraries, for example because it refers to one of the core libraries which
- * are not analyzed by lint.
- *
- * @param name the fully qualified class name
- * @return the corresponding super class name (in VM format), or null if not known
- */
- @Nullable
- public String getSuperClass(@NonNull String name) {
- return mClient.getSuperClass(mCurrentProject, name);
- }
-
- /**
- * Returns true if the given class is a subclass of the given super class.
- *
- * @param classNode the class to check whether it is a subclass of the given
- * super class name
- * @param superClassName the fully qualified super class name (in VM format,
- * e.g. java/lang/Integer, not java.lang.Integer.
- * @return true if the given class is a subclass of the given super class
- */
- public boolean isSubclassOf(@NonNull ClassNode classNode, @NonNull String superClassName) {
- if (superClassName.equals(classNode.superName)) {
- return true;
- }
-
- if (mCurrentProject != null) {
- Boolean isSub = mClient.isSubclassOf(mCurrentProject, classNode.name, superClassName);
- if (isSub != null) {
- return isSub;
- }
- }
-
- String className = classNode.name;
- while (className != null) {
- if (className.equals(superClassName)) {
- return true;
- }
- className = getSuperClass(className);
- }
-
- return false;
- }
- @Nullable
- private static List<Detector> union(
- @Nullable List<Detector> list1,
- @Nullable List<Detector> list2) {
- if (list1 == null) {
- return list2;
- } else if (list2 == null) {
- return list1;
- } else {
- // Use set to pick out unique detectors, since it's possible for there to be overlap,
- // e.g. the DuplicateIdDetector registers both a cross-resource issue and a
- // single-file issue, so it shows up on both scope lists:
- Set<Detector> set = new HashSet<Detector>(list1.size() + list2.size());
- set.addAll(list1);
- set.addAll(list2);
-
- return new ArrayList<Detector>(set);
- }
- }
-
- /** Check the classes in this project (and if applicable, in any library projects */
- private void checkClasses(Project project, Project main) {
- List<File> files = project.getSubset();
- if (files != null) {
- checkIndividualClassFiles(project, main, files);
- return;
- }
-
- // We need to read in all the classes up front such that we can initialize
- // the parent chains (such that for example for a virtual dispatch, we can
- // also check the super classes).
-
- List<File> libraries = project.getJavaLibraries();
- List<ClassEntry> libraryEntries = ClassEntry.fromClassPath(mClient, libraries, true);
-
- List<File> classFolders = project.getJavaClassFolders();
- List<ClassEntry> classEntries;
- if (classFolders.isEmpty()) {
- String message = String.format("No `.class` files were found in project \"%1$s\", "
- + "so none of the classfile based checks could be run. "
- + "Does the project need to be built first?", project.getName());
- Location location = Location.create(project.getDir());
- mClient.report(new Context(this, project, main, project.getDir()),
- IssueRegistry.LINT_ERROR,
- project.getConfiguration(this).getSeverity(IssueRegistry.LINT_ERROR),
- location, message, TextFormat.RAW);
- classEntries = Collections.emptyList();
- } else {
- classEntries = ClassEntry.fromClassPath(mClient, classFolders, true);
- }
-
- // Actually run the detectors. Libraries should be called before the
- // main classes.
- runClassDetectors(Scope.JAVA_LIBRARIES, libraryEntries, project, main);
-
- if (mCanceled) {
- return;
- }
-
- runClassDetectors(Scope.CLASS_FILE, classEntries, project, main);
- runClassDetectors(Scope.ALL_CLASS_FILES, classEntries, project, main);
- }
-
- private void checkIndividualClassFiles(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<File> files) {
- List<File> classFiles = Lists.newArrayListWithExpectedSize(files.size());
- List<File> classFolders = project.getJavaClassFolders();
- if (!classFolders.isEmpty()) {
- for (File file : files) {
- String path = file.getPath();
- if (file.isFile() && path.endsWith(DOT_CLASS)) {
- classFiles.add(file);
- }
- }
- }
-
- List<ClassEntry> entries = ClassEntry.fromClassFiles(mClient, classFiles, classFolders,
- true);
- if (!entries.isEmpty()) {
- Collections.sort(entries);
- runClassDetectors(Scope.CLASS_FILE, entries, project, main);
- }
- }
-
- /**
- * Stack of {@link ClassNode} nodes for outer classes of the currently
- * processed class, including that class itself. Populated by
- * {@link #runClassDetectors(Scope, List, Project, Project)} and used by
- * {@link #getOuterClassNode(ClassNode)}
- */
- private Deque<ClassNode> mOuterClasses;
-
- private void runClassDetectors(Scope scope, List<ClassEntry> entries,
- Project project, Project main) {
- if (mScope.contains(scope)) {
- List<Detector> classDetectors = mScopeDetectors.get(scope);
- if (classDetectors != null && !classDetectors.isEmpty() && !entries.isEmpty()) {
- AsmVisitor visitor = new AsmVisitor(mClient, classDetectors);
-
- String sourceContents = null;
- String sourceName = "";
- mOuterClasses = new ArrayDeque<ClassNode>();
- ClassEntry prev = null;
- for (ClassEntry entry : entries) {
- if (prev != null && prev.compareTo(entry) == 0) {
- // Duplicate entries for some reason: ignore
- continue;
- }
- prev = entry;
-
- ClassReader reader;
- ClassNode classNode;
- try {
- reader = new ClassReader(entry.bytes);
- classNode = new ClassNode();
- reader.accept(classNode, 0 /* flags */);
- } catch (Throwable t) {
- mClient.log(null, "Error processing %1$s: broken class file?",
- entry.path());
- continue;
- }
-
- ClassNode peek;
- while ((peek = mOuterClasses.peek()) != null) {
- if (classNode.name.startsWith(peek.name)) {
- break;
- } else {
- mOuterClasses.pop();
- }
- }
- mOuterClasses.push(classNode);
-
- if (isSuppressed(null, classNode)) {
- // Class was annotated with suppress all -- no need to look any further
- continue;
- }
-
- if (sourceContents != null) {
- // Attempt to reuse the source buffer if initialized
- // This means making sure that the source files
- // foo/bar/MyClass and foo/bar/MyClass$Bar
- // and foo/bar/MyClass$3 and foo/bar/MyClass$3$1 have the same prefix.
- String newName = classNode.name;
- int newRootLength = newName.indexOf('$');
- if (newRootLength == -1) {
- newRootLength = newName.length();
- }
- int oldRootLength = sourceName.indexOf('$');
- if (oldRootLength == -1) {
- oldRootLength = sourceName.length();
- }
- if (newRootLength != oldRootLength ||
- !sourceName.regionMatches(0, newName, 0, newRootLength)) {
- sourceContents = null;
- }
- }
-
- ClassContext context = new ClassContext(this, project, main,
- entry.file, entry.jarFile, entry.binDir, entry.bytes,
- classNode, scope == Scope.JAVA_LIBRARIES /*fromLibrary*/,
- sourceContents);
-
- try {
- visitor.runClassDetectors(context);
- } catch (Exception e) {
- mClient.log(e, null);
- }
-
- if (mCanceled) {
- return;
- }
-
- sourceContents = context.getSourceContents(false/*read*/);
- sourceName = classNode.name;
- }
-
- mOuterClasses = null;
- }
- }
- }
-
- /** Returns the outer class node of the given class node
- * @param classNode the inner class node
- * @return the outer class node */
- public ClassNode getOuterClassNode(@NonNull ClassNode classNode) {
- String outerName = classNode.outerClass;
-
- Iterator<ClassNode> iterator = mOuterClasses.iterator();
- while (iterator.hasNext()) {
- ClassNode node = iterator.next();
- if (outerName != null) {
- if (node.name.equals(outerName)) {
- return node;
- }
- } else if (node == classNode) {
- return iterator.hasNext() ? iterator.next() : null;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the {@link ClassNode} corresponding to the given type, if possible, or null
- *
- * @param type the fully qualified type, using JVM signatures (/ and $, not . as path
- * separators)
- * @param flags the ASM flags to pass to the {@link ClassReader}, normally 0 but can
- * for example be {@link ClassReader#SKIP_CODE} and/oor
- * {@link ClassReader#SKIP_DEBUG}
- * @return the class node for the type, or null
- */
- @Nullable
- public ClassNode findClass(@NonNull ClassContext context, @NonNull String type, int flags) {
- String relative = type.replace('/', File.separatorChar) + DOT_CLASS;
- File classFile = findClassFile(context.getProject(), relative);
- if (classFile != null) {
- if (classFile.getPath().endsWith(DOT_JAR)) {
- // TODO: Handle .jar files
- return null;
- }
-
- try {
- byte[] bytes = mClient.readBytes(classFile);
- ClassReader reader = new ClassReader(bytes);
- ClassNode classNode = new ClassNode();
- reader.accept(classNode, flags);
-
- return classNode;
- } catch (Throwable t) {
- mClient.log(null, "Error processing %1$s: broken class file?",
- classFile.getPath());
- }
- }
-
- return null;
- }
-
- @Nullable
- private File findClassFile(@NonNull Project project, String relativePath) {
- for (File root : mClient.getJavaClassFolders(project)) {
- File path = new File(root, relativePath);
- if (path.exists()) {
- return path;
- }
- }
- // Search in the libraries
- for (File root : mClient.getJavaLibraries(project)) {
- // TODO: Handle .jar files!
- //if (root.getPath().endsWith(DOT_JAR)) {
- //}
-
- File path = new File(root, relativePath);
- if (path.exists()) {
- return path;
- }
- }
-
- // Search dependent projects
- for (Project library : project.getDirectLibraries()) {
- File path = findClassFile(library, relativePath);
- if (path != null) {
- return path;
- }
- }
-
- return null;
- }
-
- private void checkJava(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<File> sourceFolders,
- @NonNull List<Detector> checks) {
- JavaParser javaParser = mClient.getJavaParser(project);
- if (javaParser == null) {
- mClient.log(null, "No java parser provided to lint: not running Java checks");
- return;
- }
-
- assert !checks.isEmpty();
-
- // Gather all Java source files in a single pass; more efficient.
- List<File> sources = new ArrayList<File>(100);
- for (File folder : sourceFolders) {
- gatherJavaFiles(folder, sources);
- }
- if (!sources.isEmpty()) {
- JavaVisitor visitor = new JavaVisitor(javaParser, checks);
- List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(sources.size());
- for (File file : sources) {
- JavaContext context = new JavaContext(this, project, main, file, javaParser);
- contexts.add(context);
- }
-
- visitor.prepare(contexts);
- for (JavaContext context : contexts) {
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context);
- if (mCanceled) {
- return;
- }
- }
- visitor.dispose();
- }
- }
-
- private void checkIndividualJavaFiles(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<Detector> checks,
- @NonNull List<File> files) {
-
- JavaParser javaParser = mClient.getJavaParser(project);
- if (javaParser == null) {
- mClient.log(null, "No java parser provided to lint: not running Java checks");
- return;
- }
-
- JavaVisitor visitor = new JavaVisitor(javaParser, checks);
-
- List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(files.size());
- for (File file : files) {
- if (file.isFile() && file.getPath().endsWith(DOT_JAVA)) {
- contexts.add(new JavaContext(this, project, main, file, javaParser));
- }
- }
-
- if (contexts.isEmpty()) {
- return;
- }
-
- visitor.prepare(contexts);
-
- if (mCanceled) {
- return;
- }
-
- for (JavaContext context : contexts) {
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context);
- if (mCanceled) {
- return;
- }
- }
-
- visitor.dispose();
- }
-
- private static void gatherJavaFiles(@NonNull File dir, @NonNull List<File> result) {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isFile() && file.getName().endsWith(".java")) { //$NON-NLS-1$
- result.add(file);
- } else if (file.isDirectory()) {
- gatherJavaFiles(file, result);
- }
- }
- }
- }
-
- private ResourceFolderType mCurrentFolderType;
- private List<ResourceXmlDetector> mCurrentXmlDetectors;
- private List<Detector> mCurrentBinaryDetectors;
- private ResourceVisitor mCurrentVisitor;
-
- @Nullable
- private ResourceVisitor getVisitor(
- @NonNull ResourceFolderType type,
- @NonNull List<ResourceXmlDetector> checks,
- @Nullable List<Detector> binaryChecks) {
- if (type != mCurrentFolderType) {
- mCurrentFolderType = type;
-
- // Determine which XML resource detectors apply to the given folder type
- List<ResourceXmlDetector> applicableXmlChecks =
- new ArrayList<ResourceXmlDetector>(checks.size());
- for (ResourceXmlDetector check : checks) {
- if (check.appliesTo(type)) {
- applicableXmlChecks.add(check);
- }
- }
- List<Detector> applicableBinaryChecks = null;
- if (binaryChecks != null) {
- applicableBinaryChecks = new ArrayList<Detector>(binaryChecks.size());
- for (Detector check : binaryChecks) {
- if (check.appliesTo(type)) {
- applicableBinaryChecks.add(check);
- }
- }
- }
-
- // If the list of detectors hasn't changed, then just use the current visitor!
- if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableXmlChecks)
- && Objects.equal(mCurrentBinaryDetectors, applicableBinaryChecks)) {
- return mCurrentVisitor;
- }
-
- mCurrentXmlDetectors = applicableXmlChecks;
- mCurrentBinaryDetectors = applicableBinaryChecks;
-
- if (applicableXmlChecks.isEmpty()
- && (applicableBinaryChecks == null || applicableBinaryChecks.isEmpty())) {
- mCurrentVisitor = null;
- return null;
- }
-
- XmlParser parser = mClient.getXmlParser();
- if (parser != null) {
- mCurrentVisitor = new ResourceVisitor(parser, applicableXmlChecks,
- applicableBinaryChecks);
- } else {
- mCurrentVisitor = null;
- }
- }
-
- return mCurrentVisitor;
- }
-
- private void checkResFolder(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File res,
- @NonNull List<ResourceXmlDetector> xmlChecks,
- @Nullable List<Detector> dirChecks,
- @Nullable List<Detector> binaryChecks) {
- File[] resourceDirs = res.listFiles();
- if (resourceDirs == null) {
- return;
- }
-
- // Sort alphabetically such that we can process related folder types at the
- // same time, and to have a defined behavior such that detectors can rely on
- // predictable ordering, e.g. layouts are seen before menus are seen before
- // values, etc (l < m < v).
-
- Arrays.sort(resourceDirs);
- for (File dir : resourceDirs) {
- ResourceFolderType type = ResourceFolderType.getFolderType(dir.getName());
- if (type != null) {
- checkResourceFolder(project, main, dir, type, xmlChecks, dirChecks, binaryChecks);
- }
-
- if (mCanceled) {
- return;
- }
- }
- }
-
- private void checkResourceFolder(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File dir,
- @NonNull ResourceFolderType type,
- @NonNull List<ResourceXmlDetector> xmlChecks,
- @Nullable List<Detector> dirChecks,
- @Nullable List<Detector> binaryChecks) {
-
- // Process the resource folder
-
- if (dirChecks != null && !dirChecks.isEmpty()) {
- ResourceContext context = new ResourceContext(this, project, main, dir, type);
- String folderName = dir.getName();
- fireEvent(EventType.SCANNING_FILE, context);
- for (Detector check : dirChecks) {
- if (check.appliesTo(type)) {
- check.beforeCheckFile(context);
- check.checkFolder(context, folderName);
- check.afterCheckFile(context);
- }
- }
- if (binaryChecks == null && xmlChecks.isEmpty()) {
- return;
- }
- }
-
- File[] files = dir.listFiles();
- if (files == null || files.length <= 0) {
- return;
- }
-
- ResourceVisitor visitor = getVisitor(type, xmlChecks, binaryChecks);
- if (visitor != null) { // if not, there are no applicable rules in this folder
- // Process files in alphabetical order, to ensure stable output
- // (for example for the duplicate resource detector)
- Arrays.sort(files);
- for (File file : files) {
- if (LintUtils.isXmlFile(file)) {
- XmlContext context = new XmlContext(this, project, main, file, type,
- visitor.getParser());
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- } else if (binaryChecks != null && LintUtils.isBitmapFile(file)) {
- ResourceContext context = new ResourceContext(this, project, main, file, type);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitBinaryResource(context);
- }
- if (mCanceled) {
- return;
- }
- }
- }
- }
-
- /** Checks individual resources */
- private void checkIndividualResources(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<ResourceXmlDetector> xmlDetectors,
- @Nullable List<Detector> dirChecks,
- @Nullable List<Detector> binaryChecks,
- @NonNull List<File> files) {
- for (File file : files) {
- if (file.isDirectory()) {
- // Is it a resource folder?
- ResourceFolderType type = ResourceFolderType.getFolderType(file.getName());
- if (type != null && new File(file.getParentFile(), RES_FOLDER).exists()) {
- // Yes.
- checkResourceFolder(project, main, file, type, xmlDetectors, dirChecks,
- binaryChecks);
- } else if (file.getName().equals(RES_FOLDER)) { // Is it the res folder?
- // Yes
- checkResFolder(project, main, file, xmlDetectors, dirChecks, binaryChecks);
- } else {
- mClient.log(null, "Unexpected folder %1$s; should be project, " +
- "\"res\" folder or resource folder", file.getPath());
- }
- } else if (file.isFile() && LintUtils.isXmlFile(file)) {
- // Yes, find out its resource type
- String folderName = file.getParentFile().getName();
- ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
- if (type != null) {
- ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks);
- if (visitor != null) {
- XmlContext context = new XmlContext(this, project, main, file, type,
- visitor.getParser());
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- }
- }
- } else if (binaryChecks != null && file.isFile() && LintUtils.isBitmapFile(file)) {
- // Yes, find out its resource type
- String folderName = file.getParentFile().getName();
- ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
- if (type != null) {
- ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks);
- if (visitor != null) {
- ResourceContext context = new ResourceContext(this, project, main, file,
- type);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitBinaryResource(context);
- if (mCanceled) {
- return;
- }
- }
- }
- }
- }
- }
-
- /**
- * Adds a listener to be notified of lint progress
- *
- * @param listener the listener to be added
- */
- public void addLintListener(@NonNull LintListener listener) {
- if (mListeners == null) {
- mListeners = new ArrayList<LintListener>(1);
- }
- mListeners.add(listener);
- }
-
- /**
- * Removes a listener such that it is no longer notified of progress
- *
- * @param listener the listener to be removed
- */
- public void removeLintListener(@NonNull LintListener listener) {
- mListeners.remove(listener);
- if (mListeners.isEmpty()) {
- mListeners = null;
- }
- }
-
- /** Notifies listeners, if any, that the given event has occurred */
- private void fireEvent(@NonNull LintListener.EventType type, @Nullable Context context) {
- if (mListeners != null) {
- for (LintListener listener : mListeners) {
- listener.update(this, type, context);
- }
- }
- }
-
- /**
- * Wrapper around the lint client. This sits in the middle between a
- * detector calling for example {@link LintClient#report} and
- * the actual embedding tool, and performs filtering etc such that detectors
- * and lint clients don't have to make sure they check for ignored issues or
- * filtered out warnings.
- */
- private class LintClientWrapper extends LintClient {
- @NonNull
- private final LintClient mDelegate;
-
- public LintClientWrapper(@NonNull LintClient delegate) {
- mDelegate = delegate;
- }
-
- @Override
- public void report(
- @NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message,
- @NonNull TextFormat format) {
- assert mCurrentProject != null;
- if (!mCurrentProject.getReportIssues()) {
- return;
- }
-
- Configuration configuration = context.getConfiguration();
- if (!configuration.isEnabled(issue)) {
- if (issue != IssueRegistry.PARSER_ERROR && issue != IssueRegistry.LINT_ERROR) {
- mDelegate.log(null, "Incorrect detector reported disabled issue %1$s",
- issue.toString());
- }
- return;
- }
-
- if (configuration.isIgnored(context, issue, location, message)) {
- return;
- }
-
- if (severity == Severity.IGNORE) {
- return;
- }
-
- mDelegate.report(context, issue, severity, location, message, format);
- }
-
- // Everything else just delegates to the embedding lint client
-
- @Override
- @NonNull
- public Configuration getConfiguration(@NonNull Project project,
- @Nullable LintDriver driver) {
- return mDelegate.getConfiguration(project, driver);
- }
-
- @Override
- public void log(@NonNull Severity severity, @Nullable Throwable exception,
- @Nullable String format, @Nullable Object... args) {
- mDelegate.log(exception, format, args);
- }
-
- @Override
- @NonNull
- public String readFile(@NonNull File file) {
- return mDelegate.readFile(file);
- }
-
- @Override
- @NonNull
- public byte[] readBytes(@NonNull File file) throws IOException {
- return mDelegate.readBytes(file);
- }
-
- @Override
- @NonNull
- public List<File> getJavaSourceFolders(@NonNull Project project) {
- return mDelegate.getJavaSourceFolders(project);
- }
-
- @Override
- @NonNull
- public List<File> getJavaClassFolders(@NonNull Project project) {
- return mDelegate.getJavaClassFolders(project);
- }
-
- @NonNull
- @Override
- public List<File> getJavaLibraries(@NonNull Project project) {
- return mDelegate.getJavaLibraries(project);
- }
-
- @Override
- @NonNull
- public List<File> getResourceFolders(@NonNull Project project) {
- return mDelegate.getResourceFolders(project);
- }
-
- @Override
- @Nullable
- public XmlParser getXmlParser() {
- return mDelegate.getXmlParser();
- }
-
- @Override
- @NonNull
- public Class<? extends Detector> replaceDetector(
- @NonNull Class<? extends Detector> detectorClass) {
- return mDelegate.replaceDetector(detectorClass);
- }
-
- @Override
- @NonNull
- public SdkInfo getSdkInfo(@NonNull Project project) {
- return mDelegate.getSdkInfo(project);
- }
-
- @Override
- @NonNull
- public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
- return mDelegate.getProject(dir, referenceDir);
- }
-
- @Nullable
- @Override
- public JavaParser getJavaParser(@Nullable Project project) {
- return mDelegate.getJavaParser(project);
- }
-
- @Override
- public File findResource(@NonNull String relativePath) {
- return mDelegate.findResource(relativePath);
- }
-
- @Override
- @Nullable
- public File getCacheDir(boolean create) {
- return mDelegate.getCacheDir(create);
- }
-
- @Override
- @NonNull
- protected ClassPathInfo getClassPath(@NonNull Project project) {
- return mDelegate.getClassPath(project);
- }
-
- @Override
- public void log(@Nullable Throwable exception, @Nullable String format,
- @Nullable Object... args) {
- mDelegate.log(exception, format, args);
- }
-
- @Override
- @Nullable
- public File getSdkHome() {
- return mDelegate.getSdkHome();
- }
-
- @Override
- @NonNull
- public IAndroidTarget[] getTargets() {
- return mDelegate.getTargets();
- }
-
- @Nullable
- @Override
- public LocalSdk getSdk() {
- return mDelegate.getSdk();
- }
-
- @Nullable
- @Override
- public IAndroidTarget getCompileTarget(@NonNull Project project) {
- return mDelegate.getCompileTarget(project);
- }
-
- @Override
- public int getHighestKnownApiLevel() {
- return mDelegate.getHighestKnownApiLevel();
- }
-
- @Override
- @Nullable
- public String getSuperClass(@NonNull Project project, @NonNull String name) {
- return mDelegate.getSuperClass(project, name);
- }
-
- @Override
- @Nullable
- public Boolean isSubclassOf(@NonNull Project project, @NonNull String name,
- @NonNull String superClassName) {
- return mDelegate.isSubclassOf(project, name, superClassName);
- }
-
- @Override
- @NonNull
- public String getProjectName(@NonNull Project project) {
- return mDelegate.getProjectName(project);
- }
-
- @Override
- public boolean isGradleProject(Project project) {
- return mDelegate.isGradleProject(project);
- }
-
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return mDelegate.createProject(dir, referenceDir);
- }
-
- @NonNull
- @Override
- public List<File> findGlobalRuleJars() {
- return mDelegate.findGlobalRuleJars();
- }
-
- @NonNull
- @Override
- public List<File> findRuleJars(@NonNull Project project) {
- return mDelegate.findRuleJars(project);
- }
-
- @Override
- public boolean isProjectDirectory(@NonNull File dir) {
- return mDelegate.isProjectDirectory(dir);
- }
-
- @Override
- public void registerProject(@NonNull File dir, @NonNull Project project) {
- log(Severity.WARNING, null, "Too late to register projects");
- mDelegate.registerProject(dir, project);
- }
-
- @Override
- public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) {
- return mDelegate.addCustomLintRules(registry);
- }
-
- @Override
- public boolean checkForSuppressComments() {
- return mDelegate.checkForSuppressComments();
- }
-
- @Override
- public boolean supportsProjectResources() {
- return mDelegate.supportsProjectResources();
- }
-
- @Nullable
- @Override
- public AbstractResourceRepository getProjectResources(Project project,
- boolean includeDependencies) {
- return mDelegate.getProjectResources(project, includeDependencies);
- }
-
- @NonNull
- @Override
- public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
- return mDelegate.createResourceItemHandle(item);
- }
-
- @Nullable
- @Override
- public URLConnection openConnection(@NonNull URL url) throws IOException {
- return mDelegate.openConnection(url);
- }
-
- @Override
- public void closeConnection(@NonNull URLConnection connection) throws IOException {
- mDelegate.closeConnection(connection);
- }
- }
-
- /**
- * Requests another pass through the data for the given detector. This is
- * typically done when a detector needs to do more expensive computation,
- * but it only wants to do this once it <b>knows</b> that an error is
- * present, or once it knows more specifically what to check for.
- *
- * @param detector the detector that should be included in the next pass.
- * Note that the lint runner may refuse to run more than a couple
- * of runs.
- * @param scope the scope to be revisited. This must be a subset of the
- * current scope ({@link #getScope()}, and it is just a performance hint;
- * in particular, the detector should be prepared to be called on other
- * scopes as well (since they may have been requested by other detectors).
- * You can pall null to indicate "all".
- */
- public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) {
- if (mRepeatingDetectors == null) {
- mRepeatingDetectors = new ArrayList<Detector>();
- }
- mRepeatingDetectors.add(detector);
-
- if (scope != null) {
- if (mRepeatScope == null) {
- mRepeatScope = scope;
- } else {
- mRepeatScope = EnumSet.copyOf(mRepeatScope);
- mRepeatScope.addAll(scope);
- }
- } else {
- mRepeatScope = Scope.ALL;
- }
- }
-
- // Unfortunately, ASMs nodes do not extend a common DOM node type with parent
- // pointers, so we have to have multiple methods which pass in each type
- // of node (class, method, field) to be checked.
-
- /**
- * Returns whether the given issue is suppressed in the given method.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param classNode the class containing the issue
- * @param method the method containing the issue
- * @param instruction the instruction within the method, if any
- * @return true if there is a suppress annotation covering the specific
- * issue on this method
- */
- public boolean isSuppressed(
- @Nullable Issue issue,
- @NonNull ClassNode classNode,
- @NonNull MethodNode method,
- @Nullable AbstractInsnNode instruction) {
- if (method.invisibleAnnotations != null) {
- @SuppressWarnings("unchecked")
- List<AnnotationNode> annotations = method.invisibleAnnotations;
- return isSuppressed(issue, annotations);
- }
-
- // Initializations of fields end up placed in generated methods (<init>
- // for members and <clinit> for static fields).
- if (instruction != null && method.name.charAt(0) == '<') {
- AbstractInsnNode next = LintUtils.getNextInstruction(instruction);
- if (next != null && next.getType() == AbstractInsnNode.FIELD_INSN) {
- FieldInsnNode fieldRef = (FieldInsnNode) next;
- FieldNode field = findField(classNode, fieldRef.owner, fieldRef.name);
- if (field != null && isSuppressed(issue, field)) {
- return true;
- }
- } else if (classNode.outerClass != null && classNode.outerMethod == null
- && isAnonymousClass(classNode)) {
- if (isSuppressed(issue, classNode)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Nullable
- private static MethodInsnNode findConstructorInvocation(
- @NonNull MethodNode method,
- @NonNull String className) {
- InsnList nodes = method.instructions;
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- if (instruction.getOpcode() == Opcodes.INVOKESPECIAL) {
- MethodInsnNode call = (MethodInsnNode) instruction;
- if (className.equals(call.owner)) {
- return call;
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- private FieldNode findField(
- @NonNull ClassNode classNode,
- @NonNull String owner,
- @NonNull String name) {
- ClassNode current = classNode;
- while (current != null) {
- if (owner.equals(current.name)) {
- @SuppressWarnings("rawtypes") // ASM API
- List fieldList = current.fields;
- for (Object f : fieldList) {
- FieldNode field = (FieldNode) f;
- if (field.name.equals(name)) {
- return field;
- }
- }
- return null;
- }
- current = getOuterClassNode(current);
- }
- return null;
- }
-
- @Nullable
- private MethodNode findMethod(
- @NonNull ClassNode classNode,
- @NonNull String name,
- boolean includeInherited) {
- ClassNode current = classNode;
- while (current != null) {
- @SuppressWarnings("rawtypes") // ASM API
- List methodList = current.methods;
- for (Object f : methodList) {
- MethodNode method = (MethodNode) f;
- if (method.name.equals(name)) {
- return method;
- }
- }
-
- if (includeInherited) {
- current = getOuterClassNode(current);
- } else {
- break;
- }
- }
- return null;
- }
-
- /**
- * Returns whether the given issue is suppressed for the given field.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param field the field potentially annotated with a suppress annotation
- * @return true if there is a suppress annotation covering the specific
- * issue on this field
- */
- @SuppressWarnings("MethodMayBeStatic") // API; reserve need to require driver state later
- public boolean isSuppressed(@Nullable Issue issue, @NonNull FieldNode field) {
- if (field.invisibleAnnotations != null) {
- @SuppressWarnings("unchecked")
- List<AnnotationNode> annotations = field.invisibleAnnotations;
- return isSuppressed(issue, annotations);
- }
-
- return false;
- }
-
- /**
- * Returns whether the given issue is suppressed in the given class.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param classNode the class containing the issue
- * @return true if there is a suppress annotation covering the specific
- * issue in this class
- */
- public boolean isSuppressed(@Nullable Issue issue, @NonNull ClassNode classNode) {
- if (classNode.invisibleAnnotations != null) {
- @SuppressWarnings("unchecked")
- List<AnnotationNode> annotations = classNode.invisibleAnnotations;
- return isSuppressed(issue, annotations);
- }
-
- if (classNode.outerClass != null && classNode.outerMethod == null
- && isAnonymousClass(classNode)) {
- ClassNode outer = getOuterClassNode(classNode);
- if (outer != null) {
- MethodNode m = findMethod(outer, CONSTRUCTOR_NAME, false);
- if (m != null) {
- MethodInsnNode call = findConstructorInvocation(m, classNode.name);
- if (call != null) {
- if (isSuppressed(issue, outer, m, call)) {
- return true;
- }
- }
- }
- m = findMethod(outer, CLASS_CONSTRUCTOR, false);
- if (m != null) {
- MethodInsnNode call = findConstructorInvocation(m, classNode.name);
- if (call != null) {
- if (isSuppressed(issue, outer, m, call)) {
- return true;
- }
- }
- }
- }
- }
-
- return false;
- }
-
- private static boolean isSuppressed(@Nullable Issue issue, List<AnnotationNode> annotations) {
- for (AnnotationNode annotation : annotations) {
- String desc = annotation.desc;
-
- // We could obey @SuppressWarnings("all") too, but no need to look for it
- // because that annotation only has source retention.
-
- if (desc.endsWith(SUPPRESS_LINT_VMSIG)) {
- if (annotation.values != null) {
- for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
- String key = (String) annotation.values.get(i);
- if (key.equals("value")) { //$NON-NLS-1$
- Object value = annotation.values.get(i + 1);
- if (value instanceof String) {
- String id = (String) value;
- if (matches(issue, id)) {
- return true;
- }
- } else if (value instanceof List) {
- @SuppressWarnings("rawtypes")
- List list = (List) value;
- for (Object v : list) {
- if (v instanceof String) {
- String id = (String) v;
- if (matches(issue, id)) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- return false;
- }
-
- private static boolean matches(@Nullable Issue issue, @NonNull String id) {
- if (id.equalsIgnoreCase(SUPPRESS_ALL)) {
- return true;
- }
-
- if (issue != null) {
- String issueId = issue.getId();
- if (id.equalsIgnoreCase(issueId)) {
- return true;
- }
- if (id.startsWith(STUDIO_ID_PREFIX)
- && id.regionMatches(true, STUDIO_ID_PREFIX.length(), issueId, 0, issueId.length())
- && id.substring(STUDIO_ID_PREFIX.length()).equalsIgnoreCase(issueId)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns whether the given issue is suppressed in the given parse tree node.
- *
- * @param context the context for the source being scanned
- * @param issue the issue to be checked, or null to just check for "all"
- * @param scope the AST node containing the issue
- * @return true if there is a suppress annotation covering the specific
- * issue in this class
- */
- public boolean isSuppressed(@Nullable JavaContext context, @NonNull Issue issue,
- @Nullable Node scope) {
- boolean checkComments = mClient.checkForSuppressComments() &&
- context != null && context.containsCommentSuppress();
- while (scope != null) {
- Class<? extends Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == VariableDefinition.class) {
- // Variable
- VariableDefinition declaration = (VariableDefinition) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- } else if (type == MethodDeclaration.class) {
- // Method
- // Look for annotations on the method
- MethodDeclaration declaration = (MethodDeclaration) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- } else if (type == ConstructorDeclaration.class) {
- // Constructor
- // Look for annotations on the method
- ConstructorDeclaration declaration = (ConstructorDeclaration) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- } else if (TypeDeclaration.class.isAssignableFrom(type)) {
- // Class, annotation, enum, interface
- TypeDeclaration declaration = (TypeDeclaration) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- }
-
- if (checkComments && context.isSuppressedWithComment(scope, issue)) {
- return true;
- }
-
- scope = scope.getParent();
- }
-
- return false;
- }
-
- /**
- * Returns true if the given AST modifier has a suppress annotation for the
- * given issue (which can be null to check for the "all" annotation)
- *
- * @param issue the issue to be checked
- * @param modifiers the modifier to check
- * @return true if the issue or all issues should be suppressed for this
- * modifier
- */
- private static boolean isSuppressed(@Nullable Issue issue, @Nullable Modifiers modifiers) {
- if (modifiers == null) {
- return false;
- }
- StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations();
- if (annotations == null) {
- return false;
- }
-
- for (Annotation annotation : annotations) {
- TypeReference t = annotation.astAnnotationTypeReference();
- String typeName = t.getTypeName();
- if (typeName.endsWith(SUPPRESS_LINT)
- || typeName.endsWith("SuppressWarnings")) { //$NON-NLS-1$
- StrictListAccessor<AnnotationElement, Annotation> values =
- annotation.astElements();
- if (values != null) {
- for (AnnotationElement element : values) {
- AnnotationValue valueNode = element.astValue();
- if (valueNode == null) {
- continue;
- }
- if (valueNode instanceof StringLiteral) {
- StringLiteral literal = (StringLiteral) valueNode;
- String value = literal.astValue();
- if (matches(issue, value)) {
- return true;
- }
- } else if (valueNode instanceof ArrayInitializer) {
- ArrayInitializer array = (ArrayInitializer) valueNode;
- StrictListAccessor<Expression, ArrayInitializer> expressions =
- array.astExpressions();
- if (expressions == null) {
- continue;
- }
- for (Expression arrayElement : expressions) {
- if (arrayElement instanceof StringLiteral) {
- String value = ((StringLiteral) arrayElement).astValue();
- if (matches(issue, value)) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns whether the given issue is suppressed in the given XML DOM node.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param node the DOM node containing the issue
- * @return true if there is a suppress annotation covering the specific
- * issue in this class
- */
- public boolean isSuppressed(@Nullable XmlContext context, @NonNull Issue issue,
- @Nullable org.w3c.dom.Node node) {
- if (node instanceof Attr) {
- node = ((Attr) node).getOwnerElement();
- }
- boolean checkComments = mClient.checkForSuppressComments()
- && context != null && context.containsCommentSuppress();
- while (node != null) {
- if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
- Element element = (Element) node;
- if (element.hasAttributeNS(TOOLS_URI, ATTR_IGNORE)) {
- String ignore = element.getAttributeNS(TOOLS_URI, ATTR_IGNORE);
- if (ignore.indexOf(',') == -1) {
- if (matches(issue, ignore)) {
- return true;
- }
- } else {
- for (String id : ignore.split(",")) { //$NON-NLS-1$
- if (matches(issue, id)) {
- return true;
- }
- }
- }
- } else if (checkComments && context.isSuppressedWithComment(node, issue)) {
- return true;
- }
- }
-
- node = node.getParentNode();
- }
-
- return false;
- }
-
- private File mCachedFolder = null;
- private int mCachedFolderVersion = -1;
- /** Pattern for version qualifiers */
- private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$"); //$NON-NLS-1$
-
- /**
- * Returns the folder version of the given file. For example, for the file values-v14/foo.xml,
- * it returns 14.
- *
- * @param resourceFile the file to be checked
- * @return the folder version, or -1 if no specific version was specified
- */
- public int getResourceFolderVersion(@NonNull File resourceFile) {
- File parent = resourceFile.getParentFile();
- if (parent == null) {
- return -1;
- }
- if (parent.equals(mCachedFolder)) {
- return mCachedFolderVersion;
- }
-
- mCachedFolder = parent;
- mCachedFolderVersion = -1;
-
- for (String qualifier : QUALIFIER_SPLITTER.split(parent.getName())) {
- Matcher matcher = VERSION_PATTERN.matcher(qualifier);
- if (matcher.matches()) {
- String group = matcher.group(1);
- assert group != null;
- mCachedFolderVersion = Integer.parseInt(group);
- break;
- }
- }
-
- return mCachedFolderVersion;
- }
-
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java
deleted file mode 100644
index a950634..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.annotations.Beta;
-
-/**
- * A category is a container for related issues.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public final class Category implements Comparable<Category> {
- private final String mName;
- private final int mPriority;
- private final Category mParent;
-
- /**
- * Creates a new {@link Category}.
- *
- * @param parent the name of a parent category, or null
- * @param name the name of the category
- * @param priority a sorting priority, with higher being more important
- */
- private Category(
- @Nullable Category parent,
- @NonNull String name,
- int priority) {
- mParent = parent;
- mName = name;
- mPriority = priority;
- }
-
- /**
- * Creates a new top level {@link Category} with the given sorting priority.
- *
- * @param name the name of the category
- * @param priority a sorting priority, with higher being more important
- * @return a new category
- */
- @NonNull
- public static Category create(@NonNull String name, int priority) {
- return new Category(null, name, priority);
- }
-
- /**
- * Creates a new top level {@link Category} with the given sorting priority.
- *
- * @param parent the name of a parent category, or null
- * @param name the name of the category
- * @param priority a sorting priority, with higher being more important
- * @return a new category
- */
- @NonNull
- public static Category create(@Nullable Category parent, @NonNull String name, int priority) {
- return new Category(parent, name, priority);
- }
-
- /**
- * Returns the parent category, or null if this is a top level category
- *
- * @return the parent category, or null if this is a top level category
- */
- public Category getParent() {
- return mParent;
- }
-
- /**
- * Returns the name of this category
- *
- * @return the name of this category
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns a full name for this category. For a top level category, this is just
- * the {@link #getName()} value, but for nested categories it will include the parent
- * names as well.
- *
- * @return a full name for this category
- */
- public String getFullName() {
- if (mParent != null) {
- return mParent.getFullName() + ':' + mName;
- } else {
- return mName;
- }
- }
-
- @Override
- public int compareTo(Category other) {
- if (other.mPriority == mPriority) {
- if (mParent == other) {
- return 1;
- } else if (other.mParent == this) {
- return -1;
- }
- }
- return other.mPriority - mPriority;
- }
-
- /** Issues related to running lint itself */
- public static final Category LINT = create("Lint", 110);
-
- /** Issues related to correctness */
- public static final Category CORRECTNESS = create("Correctness", 100);
-
- /** Issues related to security */
- public static final Category SECURITY = create("Security", 90);
-
- /** Issues related to performance */
- public static final Category PERFORMANCE = create("Performance", 80);
-
- /** Issues related to usability */
- public static final Category USABILITY = create("Usability", 70);
-
- /** Issues related to accessibility */
- public static final Category A11Y = create("Accessibility", 60);
-
- /** Issues related to internationalization */
- public static final Category I18N = create("Internationalization", 50);
-
- /** Issues related to right to left and bi-directional text support */
- public static final Category RTL = create("Bi-directional Text", 40);
-
- // Sub categories
-
- /** Issues related to icons */
- public static final Category ICONS = create(USABILITY, "Icons", 73);
-
- /** Issues related to typography */
- public static final Category TYPOGRAPHY = create(USABILITY, "Typography", 76);
-
- /** Issues related to messages/strings */
- public static final Category MESSAGES = create(CORRECTNESS, "Messages", 95);
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
deleted file mode 100644
index 2d08ed9..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
+++ /dev/null
@@ -1,720 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.DOT_CLASS;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.EOL_BACKWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Location.SearchDirection;
-import com.android.tools.lint.detector.api.Location.SearchHints;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
-
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.LineNumberNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * A {@link Context} used when checking .class files.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class ClassContext extends Context {
- private final File mBinDir;
- /** The class file DOM root node */
- private final ClassNode mClassNode;
- /** The class file byte data */
- private final byte[] mBytes;
- /** The source file, if known/found */
- private File mSourceFile;
- /** The contents of the source file, if source file is known/found */
- private String mSourceContents;
- /** Whether we've searched for the source file (used to avoid repeated failed searches) */
- private boolean mSearchedForSource;
- /** If the file is a relative path within a jar file, this is the jar file, otherwise null */
- private final File mJarFile;
- /** Whether this class is part of a library (rather than corresponding to one of the
- * source files in this project */
- private final boolean mFromLibrary;
-
- /**
- * Construct a new {@link ClassContext}
- *
- * @param driver the driver running through the checks
- * @param project the project containing the file being checked
- * @param main the main project if this project is a library project, or
- * null if this is not a library project. The main project is the
- * root project of all library projects, not necessarily the
- * directly including project.
- * @param file the file being checked
- * @param jarFile If the file is a relative path within a jar file, this is
- * the jar file, otherwise null
- * @param binDir the root binary directory containing this .class file.
- * @param bytes the bytecode raw data
- * @param classNode the bytecode object model
- * @param fromLibrary whether this class is from a library rather than part
- * of this project
- * @param sourceContents initial contents of the Java source, if known, or
- * null
- */
- public ClassContext(
- @NonNull LintDriver driver,
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File file,
- @Nullable File jarFile,
- @NonNull File binDir,
- @NonNull byte[] bytes,
- @NonNull ClassNode classNode,
- boolean fromLibrary,
- @Nullable String sourceContents) {
- super(driver, project, main, file);
- mJarFile = jarFile;
- mBinDir = binDir;
- mBytes = bytes;
- mClassNode = classNode;
- mFromLibrary = fromLibrary;
- mSourceContents = sourceContents;
- }
-
- /**
- * Returns the raw bytecode data for this class file
- *
- * @return the byte array containing the bytecode data
- */
- @NonNull
- public byte[] getBytecode() {
- return mBytes;
- }
-
- /**
- * Returns the bytecode object model
- *
- * @return the bytecode object model, never null
- */
- @NonNull
- public ClassNode getClassNode() {
- return mClassNode;
- }
-
- /**
- * Returns the jar file, if any. If this is null, the .class file is a real file
- * on disk, otherwise it represents a relative path within the jar file.
- *
- * @return the jar file, or null
- */
- @Nullable
- public File getJarFile() {
- return mJarFile;
- }
-
- /**
- * Returns whether this class is part of a library (not this project).
- *
- * @return true if this class is part of a library
- */
- public boolean isFromClassLibrary() {
- return mFromLibrary;
- }
-
- /**
- * Returns the source file for this class file, if possible.
- *
- * @return the source file, or null
- */
- @Nullable
- public File getSourceFile() {
- if (mSourceFile == null && !mSearchedForSource) {
- mSearchedForSource = true;
-
- String source = mClassNode.sourceFile;
- if (source == null) {
- source = file.getName();
- if (source.endsWith(DOT_CLASS)) {
- source = source.substring(0, source.length() - DOT_CLASS.length()) + DOT_JAVA;
- }
- int index = source.indexOf('$');
- if (index != -1) {
- source = source.substring(0, index) + DOT_JAVA;
- }
- }
- if (source != null) {
- if (mJarFile != null) {
- String relative = file.getParent() + File.separator + source;
- List<File> sources = getProject().getJavaSourceFolders();
- for (File dir : sources) {
- File sourceFile = new File(dir, relative);
- if (sourceFile.exists()) {
- mSourceFile = sourceFile;
- break;
- }
- }
- } else {
- // Determine package
- String topPath = mBinDir.getPath();
- String parentPath = file.getParentFile().getPath();
- if (parentPath.startsWith(topPath)) {
- int start = topPath.length() + 1;
- String relative = start > parentPath.length() ? // default package?
- "" : parentPath.substring(start);
- List<File> sources = getProject().getJavaSourceFolders();
- for (File dir : sources) {
- File sourceFile = new File(dir, relative + File.separator + source);
- if (sourceFile.exists()) {
- mSourceFile = sourceFile;
- break;
- }
- }
- }
- }
- }
- }
-
- return mSourceFile;
- }
-
- /**
- * Returns the contents of the source file for this class file, if found.
- *
- * @return the source contents, or ""
- */
- @NonNull
- public String getSourceContents() {
- if (mSourceContents == null) {
- File sourceFile = getSourceFile();
- if (sourceFile != null) {
- mSourceContents = getClient().readFile(mSourceFile);
- }
-
- if (mSourceContents == null) {
- mSourceContents = "";
- }
- }
-
- return mSourceContents;
- }
-
- /**
- * Returns the contents of the source file for this class file, if found. If
- * {@code read} is false, do not read the source contents if it has not
- * already been read. (This is primarily intended for the lint
- * infrastructure; most client code would call {@link #getSourceContents()}
- * .)
- *
- * @param read whether to read the source contents if it has not already
- * been initialized
- * @return the source contents, which will never be null if {@code read} is
- * true, or null if {@code read} is false and the source contents
- * hasn't already been read.
- */
- @Nullable
- public String getSourceContents(boolean read) {
- if (read) {
- return getSourceContents();
- } else {
- return mSourceContents;
- }
- }
-
- /**
- * Returns a location for the given source line number in this class file's
- * source file, if available.
- *
- * @param line the line number (1-based, which is what ASM uses)
- * @param patternStart optional pattern to search for in the source for
- * range start
- * @param patternEnd optional pattern to search for in the source for range
- * end
- * @param hints additional hints about the pattern search (provided
- * {@code patternStart} is non null)
- * @return a location, never null
- */
- @NonNull
- public Location getLocationForLine(int line, @Nullable String patternStart,
- @Nullable String patternEnd, @Nullable SearchHints hints) {
- File sourceFile = getSourceFile();
- if (sourceFile != null) {
- // ASM line numbers are 1-based, and lint line numbers are 0-based
- if (line != -1) {
- return Location.create(sourceFile, getSourceContents(), line - 1,
- patternStart, patternEnd, hints);
- } else {
- return Location.create(sourceFile);
- }
- }
-
- return Location.create(file);
- }
-
- /**
- * Reports an issue.
- * <p>
- * Detectors should only call this method if an error applies to the whole class
- * scope and there is no specific method or field that applies to the error.
- * If so, use
- * {@link #report(Issue, org.objectweb.asm.tree.MethodNode, org.objectweb.asm.tree.AbstractInsnNode, Location, String)} or
- * {@link #report(Issue, org.objectweb.asm.tree.FieldNode, Location, String)}, such that
- * suppress annotations are checked.
- *
- * @param issue the issue to report
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- */
- @Override
- public void report(
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message) {
- if (mDriver.isSuppressed(issue, mClassNode)) {
- return;
- }
- ClassNode curr = mClassNode;
- while (curr != null) {
- ClassNode prev = curr;
- curr = mDriver.getOuterClassNode(curr);
- if (curr != null) {
- if (prev.outerMethod != null) {
- @SuppressWarnings("rawtypes") // ASM API
- List methods = curr.methods;
- for (Object m : methods) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(prev.outerMethod)
- && method.desc.equals(prev.outerMethodDesc)) {
- // Found the outer method for this anonymous class; continue
- // reporting on it (which will also work its way up the parent
- // class hierarchy)
- if (method != null && mDriver.isSuppressed(issue, mClassNode, method,
- null)) {
- return;
- }
- break;
- }
- }
- }
- if (mDriver.isSuppressed(issue, curr)) {
- return;
- }
- }
- }
-
- super.report(issue, location, message);
- }
-
- // Unfortunately, ASMs nodes do not extend a common DOM node type with parent
- // pointers, so we have to have multiple methods which pass in each type
- // of node (class, method, field) to be checked.
-
- /**
- * Reports an issue applicable to a given method node.
- *
- * @param issue the issue to report
- * @param method the method scope the error applies to. The lint
- * infrastructure will check whether there are suppress
- * annotations on this method (or its enclosing class) and if so
- * suppress the warning without involving the client.
- * @param instruction the instruction within the method the error applies
- * to. You cannot place annotations on individual method
- * instructions (for example, annotations on local variables are
- * allowed, but are not kept in the .class file). However, this
- * instruction is needed to handle suppressing errors on field
- * initializations; in that case, the errors may be reported in
- * the {@code <clinit>} method, but the annotation is found not
- * on that method but for the {@link FieldNode}'s.
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- */
- public void report(
- @NonNull Issue issue,
- @Nullable MethodNode method,
- @Nullable AbstractInsnNode instruction,
- @Nullable Location location,
- @NonNull String message) {
- if (method != null && mDriver.isSuppressed(issue, mClassNode, method, instruction)) {
- return;
- }
- report(issue, location, message); // also checks the class node
- }
-
- /**
- * Reports an issue applicable to a given method node.
- *
- * @param issue the issue to report
- * @param field the scope the error applies to. The lint infrastructure
- * will check whether there are suppress annotations on this field (or its enclosing
- * class) and if so suppress the warning without involving the client.
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- */
- public void report(
- @NonNull Issue issue,
- @Nullable FieldNode field,
- @Nullable Location location,
- @NonNull String message) {
- if (field != null && mDriver.isSuppressed(issue, field)) {
- return;
- }
- report(issue, location, message); // also checks the class node
- }
-
- /**
- * Report an error.
- * Like {@link #report(Issue, MethodNode, AbstractInsnNode, Location, String)} but with
- * a now-unused data parameter at the end.
- *
- * @deprecated Use {@link #report(Issue, FieldNode, Location, String)} instead;
- * this method is here for custom rule compatibility
- */
- @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules
- @Deprecated
- public void report(
- @NonNull Issue issue,
- @Nullable MethodNode method,
- @Nullable AbstractInsnNode instruction,
- @Nullable Location location,
- @NonNull String message,
- @SuppressWarnings("UnusedParameters") @Nullable Object data) {
- report(issue, method, instruction, location, message);
- }
-
- /**
- * Report an error.
- * Like {@link #report(Issue, FieldNode, Location, String)} but with
- * a now-unused data parameter at the end.
- *
- * @deprecated Use {@link #report(Issue, FieldNode, Location, String)} instead;
- * this method is here for custom rule compatibility
- */
- @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules
- @Deprecated
- public void report(
- @NonNull Issue issue,
- @Nullable FieldNode field,
- @Nullable Location location,
- @NonNull String message,
- @SuppressWarnings("UnusedParameters") @Nullable Object data) {
- report(issue, field, location, message);
- }
-
- /**
- * Finds the line number closest to the given node
- *
- * @param node the instruction node to get a line number for
- * @return the closest line number, or -1 if not known
- */
- public static int findLineNumber(@NonNull AbstractInsnNode node) {
- AbstractInsnNode curr = node;
-
- // First search backwards
- while (curr != null) {
- if (curr.getType() == AbstractInsnNode.LINE) {
- return ((LineNumberNode) curr).line;
- }
- curr = curr.getPrevious();
- }
-
- // Then search forwards
- curr = node;
- while (curr != null) {
- if (curr.getType() == AbstractInsnNode.LINE) {
- return ((LineNumberNode) curr).line;
- }
- curr = curr.getNext();
- }
-
- return -1;
- }
-
- /**
- * Finds the line number closest to the given method declaration
- *
- * @param node the method node to get a line number for
- * @return the closest line number, or -1 if not known
- */
- public static int findLineNumber(@NonNull MethodNode node) {
- if (node.instructions != null && node.instructions.size() > 0) {
- return findLineNumber(node.instructions.get(0));
- }
-
- return -1;
- }
-
- /**
- * Finds the line number closest to the given class declaration
- *
- * @param node the method node to get a line number for
- * @return the closest line number, or -1 if not known
- */
- public static int findLineNumber(@NonNull ClassNode node) {
- if (node.methods != null && !node.methods.isEmpty()) {
- MethodNode firstMethod = getFirstRealMethod(node);
- if (firstMethod != null) {
- return findLineNumber(firstMethod);
- }
- }
-
- return -1;
- }
-
- /**
- * Returns a location for the given {@link ClassNode}, where class node is
- * either the top level class, or an inner class, in the current context.
- *
- * @param classNode the class in the current context
- * @return a location pointing to the class declaration, or as close to it
- * as possible
- */
- @NonNull
- public Location getLocation(@NonNull ClassNode classNode) {
- // Attempt to find a proper location for this class. This is tricky
- // since classes do not have line number entries in the class file; we need
- // to find a method, look up the corresponding line number then search
- // around it for a suitable tag, such as the class name.
- String pattern;
- if (isAnonymousClass(classNode.name)) {
- pattern = classNode.superName;
- } else {
- pattern = classNode.name;
- }
- int index = pattern.lastIndexOf('$');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
- index = pattern.lastIndexOf('/');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
-
- return getLocationForLine(findLineNumber(classNode), pattern, null,
- SearchHints.create(BACKWARD).matchJavaSymbol());
- }
-
- @Nullable
- private static MethodNode getFirstRealMethod(@NonNull ClassNode classNode) {
- // Return the first method in the class for line number purposes. Skip <init>,
- // since it's typically not located near the real source of the method.
- if (classNode.methods != null) {
- @SuppressWarnings("rawtypes") // ASM API
- List methods = classNode.methods;
- for (Object m : methods) {
- MethodNode method = (MethodNode) m;
- if (method.name.charAt(0) != '<') {
- return method;
- }
- }
-
- if (!classNode.methods.isEmpty()) {
- return (MethodNode) classNode.methods.get(0);
- }
- }
-
- return null;
- }
-
- /**
- * Returns a location for the given {@link MethodNode}.
- *
- * @param methodNode the class in the current context
- * @param classNode the class containing the method
- * @return a location pointing to the class declaration, or as close to it
- * as possible
- */
- @NonNull
- public Location getLocation(@NonNull MethodNode methodNode,
- @NonNull ClassNode classNode) {
- // Attempt to find a proper location for this class. This is tricky
- // since classes do not have line number entries in the class file; we need
- // to find a method, look up the corresponding line number then search
- // around it for a suitable tag, such as the class name.
- String pattern;
- SearchDirection searchMode;
- if (methodNode.name.equals(CONSTRUCTOR_NAME)) {
- searchMode = EOL_BACKWARD;
- if (isAnonymousClass(classNode.name)) {
- pattern = classNode.superName.substring(classNode.superName.lastIndexOf('/') + 1);
- } else {
- pattern = classNode.name.substring(classNode.name.lastIndexOf('$') + 1);
- }
- } else {
- searchMode = BACKWARD;
- pattern = methodNode.name;
- }
-
- return getLocationForLine(findLineNumber(methodNode), pattern, null,
- SearchHints.create(searchMode).matchJavaSymbol());
- }
-
- /**
- * Returns a location for the given {@link AbstractInsnNode}.
- *
- * @param instruction the instruction to look up the location for
- * @return a location pointing to the instruction, or as close to it
- * as possible
- */
- @NonNull
- public Location getLocation(@NonNull AbstractInsnNode instruction) {
- SearchHints hints = SearchHints.create(FORWARD).matchJavaSymbol();
- String pattern = null;
- if (instruction instanceof MethodInsnNode) {
- MethodInsnNode call = (MethodInsnNode) instruction;
- if (call.name.equals(CONSTRUCTOR_NAME)) {
- pattern = call.owner;
- hints = hints.matchConstructor();
- } else {
- pattern = call.name;
- }
- int index = pattern.lastIndexOf('$');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
- index = pattern.lastIndexOf('/');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
- }
-
- int line = findLineNumber(instruction);
- return getLocationForLine(line, pattern, null, hints);
- }
-
- private static boolean isAnonymousClass(@NonNull String fqcn) {
- int lastIndex = fqcn.lastIndexOf('$');
- if (lastIndex != -1 && lastIndex < fqcn.length() - 1) {
- if (Character.isDigit(fqcn.charAt(lastIndex + 1))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Converts from a VM owner name (such as foo/bar/Foo$Baz) to a
- * fully qualified class name (such as foo.bar.Foo.Baz).
- *
- * @param owner the owner name to convert
- * @return the corresponding fully qualified class name
- */
- @NonNull
- public static String getFqcn(@NonNull String owner) {
- return owner.replace('/', '.').replace('$','.');
- }
-
- /**
- * Computes a user-readable type signature from the given class owner, name
- * and description. For example, for owner="foo/bar/Foo$Baz", name="foo",
- * description="(I)V", it returns "void foo.bar.Foo.Bar#foo(int)".
- *
- * @param owner the class name
- * @param name the method name
- * @param desc the method description
- * @return a user-readable string
- */
- public static String createSignature(String owner, String name, String desc) {
- StringBuilder sb = new StringBuilder(100);
-
- if (desc != null) {
- Type returnType = Type.getReturnType(desc);
- sb.append(getTypeString(returnType));
- sb.append(' ');
- }
-
- if (owner != null) {
- sb.append(getFqcn(owner));
- }
- if (name != null) {
- sb.append('#');
- sb.append(name);
- if (desc != null) {
- Type[] argumentTypes = Type.getArgumentTypes(desc);
- if (argumentTypes != null && argumentTypes.length > 0) {
- sb.append('(');
- boolean first = true;
- for (Type type : argumentTypes) {
- if (first) {
- first = false;
- } else {
- sb.append(", ");
- }
- sb.append(getTypeString(type));
- }
- sb.append(')');
- }
- }
- }
-
- return sb.toString();
- }
-
- private static String getTypeString(Type type) {
- String s = type.getClassName();
- if (s.startsWith("java.lang.")) { //$NON-NLS-1$
- s = s.substring("java.lang.".length()); //$NON-NLS-1$
- }
-
- return s;
- }
-
- /**
- * Computes the internal class name of the given fully qualified class name.
- * For example, it converts foo.bar.Foo.Bar into foo/bar/Foo$Bar
- *
- * @param fqcn the fully qualified class name
- * @return the internal class name
- */
- @NonNull
- public static String getInternalName(@NonNull String fqcn) {
- if (fqcn.indexOf('.') == -1) {
- return fqcn;
- }
-
- // If class name contains $, it's not an ambiguous inner class name.
- if (fqcn.indexOf('$') != -1) {
- return fqcn.replace('.', '/');
- }
- // Let's assume that components that start with Caps are class names.
- StringBuilder sb = new StringBuilder(fqcn.length());
- String prev = null;
- for (String part : Splitter.on('.').split(fqcn)) {
- if (prev != null && !prev.isEmpty()) {
- if (Character.isUpperCase(prev.charAt(0))) {
- sb.append('$');
- } else {
- sb.append('/');
- }
- }
- sb.append(part);
- prev = part;
- }
-
- return sb.toString();
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java
deleted file mode 100644
index 4550cc5..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.detector.api;
-
-import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser.ResolvedField;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-
-import java.util.ListIterator;
-
-import lombok.ast.BinaryExpression;
-import lombok.ast.BinaryOperator;
-import lombok.ast.BooleanLiteral;
-import lombok.ast.Cast;
-import lombok.ast.Expression;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.FloatingPointLiteral;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.Node;
-import lombok.ast.NullLiteral;
-import lombok.ast.Select;
-import lombok.ast.Statement;
-import lombok.ast.StringLiteral;
-import lombok.ast.UnaryExpression;
-import lombok.ast.UnaryOperator;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/** Evaluates constant expressions */
-public class ConstantEvaluator {
- private final JavaContext mContext;
- private boolean mAllowUnknown;
-
- /**
- * Creates a new constant evaluator
- *
- * @param context the context to use to resolve field references, if any
- */
- public ConstantEvaluator(@Nullable JavaContext context) {
- mContext = context;
- }
-
- /**
- * Whether we allow computing values where some terms are unknown. For example, the expression
- * {@code "foo" + x + "bar"} would return {@code null} without and {@code "foobar"} with.
- *
- * @return this for constructor chaining
- */
- public ConstantEvaluator allowUnknowns() {
- mAllowUnknown = true;
- return this;
- }
-
- /**
- * Evaluates the given node and returns the constant value it resolves to, if any
- *
- * @param node the node to compute the constant value for
- * @return the corresponding constant value - a String, an Integer, a Float, and so on
- */
- @Nullable
- public Object evaluate(@NonNull Node node) {
- if (node instanceof NullLiteral) {
- return null;
- } else if (node instanceof BooleanLiteral) {
- return ((BooleanLiteral)node).astValue();
- } else if (node instanceof StringLiteral) {
- StringLiteral string = (StringLiteral) node;
- return string.astValue();
- } else if (node instanceof IntegralLiteral) {
- IntegralLiteral literal = (IntegralLiteral) node;
- // Don't combine to ?: since that will promote astIntValue to a long
- if (literal.astMarkedAsLong()) {
- return literal.astLongValue();
- } else {
- return literal.astIntValue();
- }
- } else if (node instanceof FloatingPointLiteral) {
- FloatingPointLiteral literal = (FloatingPointLiteral) node;
- // Don't combine to ?: since that will promote astFloatValue to a double
- if (literal.astMarkedAsFloat()) {
- return literal.astFloatValue();
- } else {
- return literal.astDoubleValue();
- }
- } else if (node instanceof UnaryExpression) {
- UnaryOperator operator = ((UnaryExpression) node).astOperator();
- Object operand = evaluate(((UnaryExpression) node).astOperand());
- if (operand == null) {
- return null;
- }
- switch (operator) {
- case LOGICAL_NOT:
- if (operand instanceof Boolean) {
- return !(Boolean) operand;
- }
- break;
- case UNARY_PLUS:
- return operand;
- case BINARY_NOT:
- if (operand instanceof Integer) {
- return ~(Integer) operand;
- } else if (operand instanceof Long) {
- return ~(Long) operand;
- } else if (operand instanceof Short) {
- return ~(Short) operand;
- } else if (operand instanceof Character) {
- return ~(Character) operand;
- } else if (operand instanceof Byte) {
- return ~(Byte) operand;
- }
- break;
- case UNARY_MINUS:
- if (operand instanceof Integer) {
- return -(Integer) operand;
- } else if (operand instanceof Long) {
- return -(Long) operand;
- } else if (operand instanceof Double) {
- return -(Double) operand;
- } else if (operand instanceof Float) {
- return -(Float) operand;
- } else if (operand instanceof Short) {
- return -(Short) operand;
- } else if (operand instanceof Character) {
- return -(Character) operand;
- } else if (operand instanceof Byte) {
- return -(Byte) operand;
- }
- break;
- }
- } else if (node instanceof InlineIfExpression) {
- InlineIfExpression expression = (InlineIfExpression) node;
- Object known = evaluate(expression.astCondition());
- if (known == Boolean.TRUE && expression.astIfTrue() != null) {
- return evaluate(expression.astIfTrue());
- } else if (known == Boolean.FALSE && expression.astIfFalse() != null) {
- return evaluate(expression.astIfFalse());
- }
- } else if (node instanceof BinaryExpression) {
- BinaryOperator operator = ((BinaryExpression) node).astOperator();
- Object operandLeft = evaluate(((BinaryExpression) node).astLeft());
- Object operandRight = evaluate(((BinaryExpression) node).astRight());
- if (operandLeft == null || operandRight == null) {
- if (mAllowUnknown) {
- if (operandLeft == null) {
- return operandRight;
- } else {
- return operandLeft;
- }
- }
- return null;
- }
- if (operandLeft instanceof String && operandRight instanceof String) {
- if (operator == BinaryOperator.PLUS) {
- return operandLeft.toString() + operandRight.toString();
- }
- return null;
- } else if (operandLeft instanceof Boolean && operandRight instanceof Boolean) {
- boolean left = (Boolean) operandLeft;
- boolean right = (Boolean) operandRight;
- switch (operator) {
- case LOGICAL_OR:
- return left || right;
- case LOGICAL_AND:
- return left && right;
- case BITWISE_OR:
- return left | right;
- case BITWISE_XOR:
- return left ^ right;
- case BITWISE_AND:
- return left & right;
- case EQUALS:
- return left == right;
- case NOT_EQUALS:
- return left != right;
- }
- } else if (operandLeft instanceof Number && operandRight instanceof Number) {
- Number left = (Number) operandLeft;
- Number right = (Number) operandRight;
- boolean isInteger =
- !(left instanceof Float || left instanceof Double
- || right instanceof Float || right instanceof Double);
- boolean isWide =
- isInteger ? (left instanceof Long || right instanceof Long)
- : (left instanceof Double || right instanceof Double);
-
- switch (operator) {
- case BITWISE_OR:
- if (isWide) {
- return left.longValue() | right.longValue();
- } else {
- return left.intValue() | right.intValue();
- }
- case BITWISE_XOR:
- if (isWide) {
- return left.longValue() ^ right.longValue();
- } else {
- return left.intValue() ^ right.intValue();
- }
- case BITWISE_AND:
- if (isWide) {
- return left.longValue() & right.longValue();
- } else {
- return left.intValue() & right.intValue();
- }
- case EQUALS:
- if (isInteger) {
- return left.longValue() == right.longValue();
- } else {
- return left.doubleValue() == right.doubleValue();
- }
- case NOT_EQUALS:
- if (isInteger) {
- return left.longValue() != right.longValue();
- } else {
- return left.doubleValue() != right.doubleValue();
- }
- case GREATER:
- if (isInteger) {
- return left.longValue() > right.longValue();
- } else {
- return left.doubleValue() > right.doubleValue();
- }
- case GREATER_OR_EQUAL:
- if (isInteger) {
- return left.longValue() >= right.longValue();
- } else {
- return left.doubleValue() >= right.doubleValue();
- }
- case LESS:
- if (isInteger) {
- return left.longValue() < right.longValue();
- } else {
- return left.doubleValue() < right.doubleValue();
- }
- case LESS_OR_EQUAL:
- if (isInteger) {
- return left.longValue() <= right.longValue();
- } else {
- return left.doubleValue() <= right.doubleValue();
- }
- case SHIFT_LEFT:
- if (isWide) {
- return left.longValue() << right.intValue();
- } else {
- return left.intValue() << right.intValue();
- }
- case SHIFT_RIGHT:
- if (isWide) {
- return left.longValue() >> right.intValue();
- } else {
- return left.intValue() >> right.intValue();
- }
- case BITWISE_SHIFT_RIGHT:
- if (isWide) {
- return left.longValue() >>> right.intValue();
- } else {
- return left.intValue() >>> right.intValue();
- }
- case PLUS:
- if (isInteger) {
- if (isWide) {
- return left.longValue() + right.longValue();
- } else {
- return left.intValue() + right.intValue();
- }
- } else {
- if (isWide) {
- return left.doubleValue() + right.doubleValue();
- } else {
- return left.floatValue() + right.floatValue();
- }
- }
- case MINUS:
- if (isInteger) {
- if (isWide) {
- return left.longValue() - right.longValue();
- } else {
- return left.intValue() - right.intValue();
- }
- } else {
- if (isWide) {
- return left.doubleValue() - right.doubleValue();
- } else {
- return left.floatValue() - right.floatValue();
- }
- }
- case MULTIPLY:
- if (isInteger) {
- if (isWide) {
- return left.longValue() * right.longValue();
- } else {
- return left.intValue() * right.intValue();
- }
- } else {
- if (isWide) {
- return left.doubleValue() * right.doubleValue();
- } else {
- return left.floatValue() * right.floatValue();
- }
- }
- case DIVIDE:
- if (isInteger) {
- if (isWide) {
- return left.longValue() / right.longValue();
- } else {
- return left.intValue() / right.intValue();
- }
- } else {
- if (isWide) {
- return left.doubleValue() / right.doubleValue();
- } else {
- return left.floatValue() / right.floatValue();
- }
- }
- case REMAINDER:
- if (isInteger) {
- if (isWide) {
- return left.longValue() % right.longValue();
- } else {
- return left.intValue() % right.intValue();
- }
- } else {
- if (isWide) {
- return left.doubleValue() % right.doubleValue();
- } else {
- return left.floatValue() % right.floatValue();
- }
- }
- default:
- return null;
- }
- }
- } else if (node instanceof Cast) {
- Cast cast = (Cast)node;
- Object operandValue = evaluate(cast.astOperand());
- if (operandValue instanceof Number) {
- Number number = (Number)operandValue;
- String typeName = cast.astTypeReference().getTypeName();
- if (typeName.equals("float")) {
- return number.floatValue();
- } else if (typeName.equals("double")) {
- return number.doubleValue();
- } else if (typeName.equals("int")) {
- return number.intValue();
- } else if (typeName.equals("long")) {
- return number.longValue();
- } else if (typeName.equals("short")) {
- return number.shortValue();
- } else if (typeName.equals("byte")) {
- return number.byteValue();
- }
- }
- return operandValue;
- } else if (mContext != null && (node instanceof VariableReference ||
- node instanceof Select)) {
- ResolvedNode resolved = mContext.resolve(node);
- if (resolved instanceof ResolvedField) {
- ResolvedField field = (ResolvedField) resolved;
- return field.getValue();
- } else if (node instanceof VariableReference) {
- Statement statement = getParentOfType(node, Statement.class, false);
- if (statement != null) {
- ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
- while (iterator.hasNext()) {
- if (iterator.next() == statement) {
- if (iterator.hasPrevious()) { // should always be true
- iterator.previous();
- }
- break;
- }
- }
-
- String targetName = ((VariableReference)node).astIdentifier().astValue();
- while (iterator.hasPrevious()) {
- Node previous = iterator.previous();
- if (previous instanceof VariableDeclaration) {
- VariableDeclaration declaration = (VariableDeclaration) previous;
- VariableDefinition definition = declaration.astDefinition();
- for (VariableDefinitionEntry entry : definition
- .astVariables()) {
- if (entry.astInitializer() != null
- && entry.astName().astValue().equals(targetName)) {
- return evaluate(entry.astInitializer());
- }
- }
- } else if (previous instanceof ExpressionStatement) {
- ExpressionStatement expressionStatement = (ExpressionStatement) previous;
- Expression expression = expressionStatement.astExpression();
- if (expression instanceof BinaryExpression &&
- ((BinaryExpression) expression).astOperator()
- == BinaryOperator.ASSIGN) {
- BinaryExpression binaryExpression = (BinaryExpression) expression;
- if (targetName.equals(binaryExpression.astLeft().toString())) {
- return evaluate(binaryExpression.astRight());
- }
- }
- }
- }
- }
- }
- }
-
- // TODO: Check for MethodInvocation and perform some common operations -
- // Math.* methods, String utility methods like notNullize, etc
-
- return null;
- }
-
- /**
- * Evaluates the given node and returns the constant value it resolves to, if any. Convenience
- * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns
- * the result.
- *
- * @param context the context to use to resolve field references, if any
- * @param node the node to compute the constant value for
- * @return the corresponding constant value - a String, an Integer, a Float, and so on
- */
- @Nullable
- public static Object evaluate(@NonNull JavaContext context, @NonNull Node node) {
- return new ConstantEvaluator(context).evaluate(node);
- }
-
- /**
- * Evaluates the given node and returns the constant string it resolves to, if any. Convenience
- * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns
- * the result if the result is a string.
- *
- * @param context the context to use to resolve field references, if any
- * @param node the node to compute the constant value for
- * @param allowUnknown whether we should construct the string even if some parts of it are
- * unknown
- * @return the corresponding string, if any
- */
- @Nullable
- public static String evaluateString(@NonNull JavaContext context, @NonNull Node node,
- boolean allowUnknown) {
- ConstantEvaluator evaluator = new ConstantEvaluator(context);
- if (allowUnknown) {
- evaluator.allowUnknowns();
- }
- Object value = evaluator.evaluate(node);
- return value instanceof String ? (String) value : null;
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
deleted file mode 100644
index 04e1937..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
+++ /dev/null
@@ -1,778 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.LintDriver;
-import com.google.common.annotations.Beta;
-
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-
-/**
- * A detector is able to find a particular problem (or a set of related problems).
- * Each problem type is uniquely identified as an {@link Issue}.
- * <p>
- * Detectors will be called in a predefined order:
- * <ol>
- * <li> Manifest file
- * <li> Resource files, in alphabetical order by resource type
- * (therefore, "layout" is checked before "values", "values-de" is checked before
- * "values-en" but after "values", and so on.
- * <li> Java sources
- * <li> Java classes
- * <li> Gradle files
- * <li> Generic files
- * <li> Proguard files
- * <li> Property files
- * </ol>
- * If a detector needs information when processing a file type that comes from a type of
- * file later in the order above, they can request a second phase; see
- * {@link LintDriver#requestRepeat}.
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public abstract class Detector {
- /** Specialized interface for detectors that scan Java source file parse trees */
- public interface JavaScanner {
- /**
- * Create a parse tree visitor to process the parse tree. All
- * {@link JavaScanner} detectors must provide a visitor, unless they
- * either return true from {@link #appliesToResourceRefs()} or return
- * non null from {@link #getApplicableMethodNames()}.
- * <p>
- * If you return specific AST node types from
- * {@link #getApplicableNodeTypes()}, then the visitor will <b>only</b>
- * be called for the specific requested node types. This is more
- * efficient, since it allows many detectors that apply to only a small
- * part of the AST (such as method call nodes) to share iteration of the
- * majority of the parse tree.
- * <p>
- * If you return null from {@link #getApplicableNodeTypes()}, then your
- * visitor will be called from the top and all node types visited.
- * <p>
- * Note that a new visitor is created for each separate compilation
- * unit, so you can store per file state in the visitor.
- *
- * @param context the {@link Context} for the file being analyzed
- * @return a visitor, or null.
- */
- @Nullable
- AstVisitor createJavaVisitor(@NonNull JavaContext context);
-
- /**
- * Return the types of AST nodes that the visitor returned from
- * {@link #createJavaVisitor(JavaContext)} should visit. See the
- * documentation for {@link #createJavaVisitor(JavaContext)} for details
- * on how the shared visitor is used.
- * <p>
- * If you return null from this method, then the visitor will process
- * the full tree instead.
- * <p>
- * Note that for the shared visitor, the return codes from the visit
- * methods are ignored: returning true will <b>not</b> prune iteration
- * of the subtree, since there may be other node types interested in the
- * children. If you need to ensure that your visitor only processes a
- * part of the tree, use a full visitor instead. See the
- * OverdrawDetector implementation for an example of this.
- *
- * @return the list of applicable node types (AST node classes), or null
- */
- @Nullable
- List<Class<? extends Node>> getApplicableNodeTypes();
-
- /**
- * Return the list of method names this detector is interested in, or
- * null. If this method returns non-null, then any AST nodes that match
- * a method call in the list will be passed to the
- * {@link #visitMethod(JavaContext, AstVisitor, MethodInvocation)}
- * method for processing. The visitor created by
- * {@link #createJavaVisitor(JavaContext)} is also passed to that
- * method, although it can be null.
- * <p>
- * This makes it easy to write detectors that focus on some fixed calls.
- * For example, the StringFormatDetector uses this mechanism to look for
- * "format" calls, and when found it looks around (using the AST's
- * {@link Node#getParent()} method) to see if it's called on
- * a String class instance, and if so do its normal processing. Note
- * that since it doesn't need to do any other AST processing, that
- * detector does not actually supply a visitor.
- *
- * @return a set of applicable method names, or null.
- */
- @Nullable
- List<String> getApplicableMethodNames();
-
- /**
- * Method invoked for any method calls found that matches any names
- * returned by {@link #getApplicableMethodNames()}. This also passes
- * back the visitor that was created by
- * {@link #createJavaVisitor(JavaContext)}, but a visitor is not
- * required. It is intended for detectors that need to do additional AST
- * processing, but also want the convenience of not having to look for
- * method names on their own.
- *
- * @param context the context of the lint request
- * @param visitor the visitor created from
- * {@link #createJavaVisitor(JavaContext)}, or null
- * @param node the {@link MethodInvocation} node for the invoked method
- */
- void visitMethod(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node);
-
- /**
- * Return the list of constructor types this detector is interested in, or
- * null. If this method returns non-null, then any AST nodes that match
- * a constructor call in the list will be passed to the
- * {@link #visitConstructor(JavaContext, AstVisitor, ConstructorInvocation, ResolvedMethod)}
- * method for processing. The visitor created by
- * {@link #createJavaVisitor(JavaContext)} is also passed to that
- * method, although it can be null.
- * <p>
- * This makes it easy to write detectors that focus on some fixed constructors.
- *
- * @return a set of applicable fully qualified types, or null.
- */
- @Nullable
- List<String> getApplicableConstructorTypes();
-
- /**
- * Method invoked for any constructor calls found that matches any names
- * returned by {@link #getApplicableConstructorTypes()}. This also passes
- * back the visitor that was created by
- * {@link #createJavaVisitor(JavaContext)}, but a visitor is not
- * required. It is intended for detectors that need to do additional AST
- * processing, but also want the convenience of not having to look for
- * method names on their own.
- *
- * @param context the context of the lint request
- * @param visitor the visitor created from
- * {@link #createJavaVisitor(JavaContext)}, or null
- * @param node the {@link ConstructorInvocation} node for the invoked method
- * @param constructor the resolved constructor method with type information
- */
- void visitConstructor(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull ConstructorInvocation node,
- @NonNull ResolvedMethod constructor);
-
- /**
- * Returns whether this detector cares about Android resource references
- * (such as {@code R.layout.main} or {@code R.string.app_name}). If it
- * does, then the visitor will look for these patterns, and if found, it
- * will invoke {@link #visitResourceReference} passing the resource type
- * and resource name. It also passes the visitor, if any, that was
- * created by {@link #createJavaVisitor(JavaContext)}, such that a
- * detector can do more than just look for resources.
- *
- * @return true if this detector wants to be notified of R resource
- * identifiers found in the code.
- */
- boolean appliesToResourceRefs();
-
- /**
- * Called for any resource references (such as {@code R.layout.main}
- * found in Java code, provided this detector returned {@code true} from
- * {@link #appliesToResourceRefs()}.
- *
- * @param context the lint scanning context
- * @param visitor the visitor created from
- * {@link #createJavaVisitor(JavaContext)}, or null
- * @param node the variable reference for the resource
- * @param type the resource type, such as "layout" or "string"
- * @param name the resource name, such as "main" from
- * {@code R.layout.main}
- * @param isFramework whether the resource is a framework resource
- * (android.R) or a local project resource (R)
- */
- void visitResourceReference(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull Node node,
- @NonNull String type,
- @NonNull String name,
- boolean isFramework);
-
- /**
- * Returns a list of fully qualified names for super classes that this
- * detector cares about. If not null, this detector will *only* be called
- * if the current class is a subclass of one of the specified superclasses.
- *
- * @return a list of fully qualified names
- */
- @Nullable
- List<String> applicableSuperClasses();
-
- /**
- * Called for each class that extends one of the super classes specified with
- * {@link #applicableSuperClasses()}
- *
- * @param context the lint scanning context
- * @param declaration the class declaration node, or null for anonymous classes
- * @param node the class declaration node or the anonymous class construction node
- * @param resolvedClass the resolved class
- */
- void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
- @NonNull Node node, @NonNull ResolvedClass resolvedClass);
- }
-
- /** Specialized interface for detectors that scan Java class files */
- public interface ClassScanner {
- /**
- * Checks the given class' bytecode for issues.
- *
- * @param context the context of the lint check, pointing to for example
- * the file
- * @param classNode the root class node
- */
- void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode);
-
- /**
- * Returns the list of node types (corresponding to the constants in the
- * {@link AbstractInsnNode} class) that this scanner applies to. The
- * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)}
- * method will be called for each match.
- *
- * @return an array containing all the node types this detector should be
- * called for, or null if none.
- */
- @Nullable
- int[] getApplicableAsmNodeTypes();
-
- /**
- * Process a given instruction node, and register lint issues if
- * applicable.
- *
- * @param context the context of the lint check, pointing to for example
- * the file
- * @param classNode the root class node
- * @param method the method node containing the call
- * @param instruction the actual instruction
- */
- void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull AbstractInsnNode instruction);
-
- /**
- * Return the list of method call names (in VM format, e.g. "<init>" for
- * constructors, etc) for method calls this detector is interested in,
- * or null. T his will be used to dispatch calls to
- * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
- * for only the method calls in owners that the detector is interested
- * in.
- * <p>
- * <b>NOTE</b>: If you return non null from this method, then <b>only</b>
- * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
- * will be called if a suitable method is found;
- * {@link #checkClass(ClassContext, ClassNode)} will not be called under
- * any circumstances.
- * <p>
- * This makes it easy to write detectors that focus on some fixed calls,
- * and allows lint to make a single pass over the bytecode over a class,
- * and efficiently dispatch method calls to any detectors that are
- * interested in it. Without this, each new lint check interested in a
- * single method, would be doing a complete pass through all the
- * bytecode instructions of the class via the
- * {@link #checkClass(ClassContext, ClassNode)} method, which would make
- * each newly added lint check make lint slower. Now a single dispatch
- * map is used instead, and for each encountered call in the single
- * dispatch, it looks up in the map which if any detectors are
- * interested in the given call name, and dispatches to each one in
- * turn.
- *
- * @return a list of applicable method names, or null.
- */
- @Nullable
- List<String> getApplicableCallNames();
-
- /**
- * Just like {@link Detector#getApplicableCallNames()}, but for the owner
- * field instead. The
- * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
- * method will be called for all {@link MethodInsnNode} instances where the
- * owner field matches any of the members returned in this node.
- * <p>
- * Note that if your detector provides both a name and an owner, the
- * method will be called for any nodes matching either the name <b>or</b>
- * the owner, not only where they match <b>both</b>. Note also that it will
- * be called twice - once for the name match, and (at least once) for the owner
- * match.
- *
- * @return a list of applicable owner names, or null.
- */
- @Nullable
- List<String> getApplicableCallOwners();
-
- /**
- * Process a given method call node, and register lint issues if
- * applicable. This is similar to the
- * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)}
- * method, but has the additional advantage that it is only called for known
- * method names or method owners, according to
- * {@link #getApplicableCallNames()} and {@link #getApplicableCallOwners()}.
- *
- * @param context the context of the lint check, pointing to for example
- * the file
- * @param classNode the root class node
- * @param method the method node containing the call
- * @param call the actual method call node
- */
- void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call);
- }
-
- /** Specialized interface for detectors that scan binary resource files */
- public interface BinaryResourceScanner {
- /**
- * Called for each resource folder
- *
- * @param context the context for the resource file
- */
- void checkBinaryResource(@NonNull ResourceContext context);
-
- /**
- * Returns whether this detector applies to the given folder type. This
- * allows the detectors to be pruned from iteration, so for example when we
- * are analyzing a string value file we don't need to look up detectors
- * related to layout.
- *
- * @param folderType the folder type to be visited
- * @return true if this detector can apply to resources in folders of the
- * given type
- */
- boolean appliesTo(@NonNull ResourceFolderType folderType);
- }
-
- /** Specialized interface for detectors that scan resource folders (the folder directory
- * itself, not the individual files within it */
- public interface ResourceFolderScanner {
- /**
- * Called for each resource folder
- *
- * @param context the context for the resource folder
- * @param folderName the resource folder name
- */
- void checkFolder(@NonNull ResourceContext context, @NonNull String folderName);
-
- /**
- * Returns whether this detector applies to the given folder type. This
- * allows the detectors to be pruned from iteration, so for example when we
- * are analyzing a string value file we don't need to look up detectors
- * related to layout.
- *
- * @param folderType the folder type to be visited
- * @return true if this detector can apply to resources in folders of the
- * given type
- */
- boolean appliesTo(@NonNull ResourceFolderType folderType);
- }
-
- /** Specialized interface for detectors that scan XML files */
- public interface XmlScanner {
- /**
- * Visit the given document. The detector is responsible for its own iteration
- * through the document.
- * @param context information about the document being analyzed
- * @param document the document to examine
- */
- void visitDocument(@NonNull XmlContext context, @NonNull Document document);
-
- /**
- * Visit the given element.
- * @param context information about the document being analyzed
- * @param element the element to examine
- */
- void visitElement(@NonNull XmlContext context, @NonNull Element element);
-
- /**
- * Visit the given element after its children have been analyzed.
- * @param context information about the document being analyzed
- * @param element the element to examine
- */
- void visitElementAfter(@NonNull XmlContext context, @NonNull Element element);
-
- /**
- * Visit the given attribute.
- * @param context information about the document being analyzed
- * @param attribute the attribute node to examine
- */
- void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute);
-
- /**
- * Returns the list of elements that this detector wants to analyze. If non
- * null, this detector will be called (specifically, the
- * {@link #visitElement} method) for each matching element in the document.
- * <p>
- * If this method returns null, and {@link #getApplicableAttributes()} also returns
- * null, then the {@link #visitDocument} method will be called instead.
- *
- * @return a collection of elements, or null, or the special
- * {@link XmlScanner#ALL} marker to indicate that every single
- * element should be analyzed.
- */
- @Nullable
- Collection<String> getApplicableElements();
-
- /**
- * Returns the list of attributes that this detector wants to analyze. If non
- * null, this detector will be called (specifically, the
- * {@link #visitAttribute} method) for each matching attribute in the document.
- * <p>
- * If this method returns null, and {@link #getApplicableElements()} also returns
- * null, then the {@link #visitDocument} method will be called instead.
- *
- * @return a collection of attributes, or null, or the special
- * {@link XmlScanner#ALL} marker to indicate that every single
- * attribute should be analyzed.
- */
- @Nullable
- Collection<String> getApplicableAttributes();
-
- /**
- * Special marker collection returned by {@link #getApplicableElements()} or
- * {@link #getApplicableAttributes()} to indicate that the check should be
- * invoked on all elements or all attributes
- */
- @NonNull
- List<String> ALL = new ArrayList<String>(0); // NOT Collections.EMPTY!
- // We want to distinguish this from just an *empty* list returned by the caller!
- }
-
- /** Specialized interface for detectors that scan Gradle files */
- public interface GradleScanner {
- void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData);
- }
-
- /** Specialized interface for detectors that scan other files */
- public interface OtherFileScanner {
- /**
- * Returns the set of files this scanner wants to consider. If this includes
- * {@link Scope#OTHER} then all source files will be checked. Note that the
- * set of files will not just include files of the indicated type, but all files
- * within the relevant source folder. For example, returning {@link Scope#JAVA_FILE}
- * will not just return {@code .java} files, but also other resource files such as
- * {@code .html} and other files found within the Java source folders.
- * <p>
- * Lint will call the {@link #run(Context)}} method when the file should be checked.
- *
- * @return set of scopes that define the types of source files the
- * detector wants to consider
- */
- @NonNull
- EnumSet<Scope> getApplicableFiles();
- }
-
- /**
- * Runs the detector. This method will not be called for certain specialized
- * detectors, such as {@link XmlScanner} and {@link JavaScanner}, where
- * there are specialized analysis methods instead such as
- * {@link XmlScanner#visitElement(XmlContext, Element)}.
- *
- * @param context the context describing the work to be done
- */
- public void run(@NonNull Context context) {
- }
-
- /**
- * Returns true if this detector applies to the given file
- *
- * @param context the context to check
- * @param file the file in the context to check
- * @return true if this detector applies to the given context and file
- */
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return false;
- }
-
- /**
- * Analysis is about to begin, perform any setup steps.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void beforeCheckProject(@NonNull Context context) {
- }
-
- /**
- * Analysis has just been finished for the whole project, perform any
- * cleanup or report issues that require project-wide analysis.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void afterCheckProject(@NonNull Context context) {
- }
-
- /**
- * Analysis is about to begin for the given library project, perform any setup steps.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void beforeCheckLibraryProject(@NonNull Context context) {
- }
-
- /**
- * Analysis has just been finished for the given library project, perform any
- * cleanup or report issues that require library-project-wide analysis.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void afterCheckLibraryProject(@NonNull Context context) {
- }
-
- /**
- * Analysis is about to be performed on a specific file, perform any setup
- * steps.
- * <p>
- * Note: When this method is called at the beginning of checking an XML
- * file, the context is guaranteed to be an instance of {@link XmlContext},
- * and similarly for a Java source file, the context will be a
- * {@link JavaContext} and so on.
- *
- * @param context the context for the check referencing the file to be
- * checked, the project, etc.
- */
- public void beforeCheckFile(@NonNull Context context) {
- }
-
- /**
- * Analysis has just been finished for a specific file, perform any cleanup
- * or report issues found
- * <p>
- * Note: When this method is called at the end of checking an XML
- * file, the context is guaranteed to be an instance of {@link XmlContext},
- * and similarly for a Java source file, the context will be a
- * {@link JavaContext} and so on.
- *
- * @param context the context for the check referencing the file to be
- * checked, the project, etc.
- */
- public void afterCheckFile(@NonNull Context context) {
- }
-
- /**
- * Returns the expected speed of this detector
- *
- * @return the expected speed of this detector
- */
- @NonNull
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- /**
- * Returns the expected speed of this detector.
- * The issue parameter is made available for subclasses which analyze multiple issues
- * and which need to distinguish implementation cost by issue. If the detector does
- * not analyze multiple issues or does not vary in speed by issue type, just override
- * {@link #getSpeed()} instead.
- *
- * @param issue the issue to look up the analysis speed for
- * @return the expected speed of this detector
- */
- @NonNull
- public Speed getSpeed(@SuppressWarnings("UnusedParameters") @NonNull Issue issue) {
- // If not overridden, this detector does not distinguish speed by issue type
- return getSpeed();
- }
-
- // ---- Dummy implementations to make implementing XmlScanner easier: ----
-
- @SuppressWarnings("javadoc")
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- // This method must be overridden if your detector does
- // not return something from getApplicableElements or
- // getApplicableAttributes
- assert false;
- }
-
- @SuppressWarnings("javadoc")
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- // This method must be overridden if your detector returns
- // tag names from getApplicableElements
- assert false;
- }
-
- @SuppressWarnings("javadoc")
- public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) {
- }
-
- @SuppressWarnings("javadoc")
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- // This method must be overridden if your detector returns
- // attribute names from getApplicableAttributes
- assert false;
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public Collection<String> getApplicableElements() {
- return null;
- }
-
- @Nullable
- @SuppressWarnings("javadoc")
- public Collection<String> getApplicableAttributes() {
- return null;
- }
-
- // ---- Dummy implementations to make implementing JavaScanner easier: ----
-
- @Nullable @SuppressWarnings("javadoc")
- public List<String> getApplicableMethodNames() {
- return null;
- }
-
- @Nullable @SuppressWarnings("javadoc")
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return null;
- }
-
- @Nullable @SuppressWarnings("javadoc")
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- }
-
- @SuppressWarnings("javadoc")
- public boolean appliesToResourceRefs() {
- return false;
- }
-
- @SuppressWarnings("javadoc")
- public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull Node node, @NonNull String type, @NonNull String name,
- boolean isFramework) {
- }
-
- @Nullable
- public List<String> applicableSuperClasses() {
- return null;
- }
-
- public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
- @NonNull Node node, @NonNull ResolvedClass resolvedClass) {
- }
-
- @Nullable @SuppressWarnings("javadoc")
- public List<String> getApplicableConstructorTypes() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- public void visitConstructor(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull ConstructorInvocation node,
- @NonNull ResolvedMethod constructor) {
- }
-
- // ---- Dummy implementations to make implementing a ClassScanner easier: ----
-
- @SuppressWarnings("javadoc")
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public List<String> getApplicableCallNames() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public List<String> getApplicableCallOwners() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public int[] getApplicableAsmNodeTypes() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- public void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull AbstractInsnNode instruction) {
- }
-
- // ---- Dummy implementations to make implementing an OtherFileScanner easier: ----
-
- public boolean appliesToFolder(@NonNull Scope scope, @Nullable ResourceFolderType folderType) {
- return false;
- }
-
- @NonNull
- public EnumSet<Scope> getApplicableFiles() {
- return Scope.OTHER_SCOPE;
- }
-
- // ---- Dummy implementations to make implementing an GradleScanner easier: ----
-
- public void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData) {
- }
-
- // ---- Dummy implementations to make implementing a resource folder scanner easier: ----
-
- public void checkFolder(@NonNull ResourceContext context, @NonNull String folderName) {
- }
-
- // ---- Dummy implementations to make implementing a binary resource scanner easier: ----
-
- public void checkBinaryResource(@NonNull ResourceContext context) {
- }
-
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return true;
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
deleted file mode 100644
index 58f66be..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.CLASS_CONTEXT;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.LintDriver;
-import com.google.common.collect.Iterators;
-
-import java.io.File;
-import java.util.Iterator;
-
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.EnumConstant;
-import lombok.ast.Expression;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Position;
-
-/**
- * A {@link Context} used when checking Java files.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-public class JavaContext extends Context {
- static final String SUPPRESS_COMMENT_PREFIX = "//noinspection "; //$NON-NLS-1$
-
- /** The parse tree */
- private Node mCompilationUnit;
-
- /** The parser which produced the parse tree */
- private final JavaParser mParser;
-
- /**
- * Constructs a {@link JavaContext} for running lint on the given file, with
- * the given scope, in the given project reporting errors to the given
- * client.
- *
- * @param driver the driver running through the checks
- * @param project the project to run lint on which contains the given file
- * @param main the main project if this project is a library project, or
- * null if this is not a library project. The main project is
- * the root project of all library projects, not necessarily the
- * directly including project.
- * @param file the file to be analyzed
- * @param parser the parser to use
- */
- public JavaContext(
- @NonNull LintDriver driver,
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File file,
- @NonNull JavaParser parser) {
- super(driver, project, main, file);
- mParser = parser;
- }
-
- /**
- * Returns a location for the given node
- *
- * @param node the AST node to get a location for
- * @return a location for the given node
- */
- @NonNull
- public Location getLocation(@NonNull Node node) {
- return mParser.getLocation(this, node);
- }
-
- @NonNull
- public JavaParser getParser() {
- return mParser;
- }
-
- @Nullable
- public Node getCompilationUnit() {
- return mCompilationUnit;
- }
-
- /**
- * Sets the compilation result. Not intended for client usage; the lint infrastructure
- * will set this when a context has been processed
- *
- * @param compilationUnit the parse tree
- */
- public void setCompilationUnit(@Nullable Node compilationUnit) {
- mCompilationUnit = compilationUnit;
- }
-
- @Override
- public void report(@NonNull Issue issue, @Nullable Location location,
- @NonNull String message) {
- if (mDriver.isSuppressed(this, issue, mCompilationUnit)) {
- return;
- }
- super.report(issue, location, message);
- }
-
- /**
- * Reports an issue applicable to a given AST node. The AST node is used as the
- * scope to check for suppress lint annotations.
- *
- * @param issue the issue to report
- * @param scope the AST node scope the error applies to. The lint infrastructure
- * will check whether there are suppress annotations on this node (or its enclosing
- * nodes) and if so suppress the warning without involving the client.
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- */
- public void report(
- @NonNull Issue issue,
- @Nullable Node scope,
- @Nullable Location location,
- @NonNull String message) {
- if (scope != null && mDriver.isSuppressed(this, issue, scope)) {
- return;
- }
- super.report(issue, location, message);
- }
-
- /**
- * Report an error.
- * Like {@link #report(Issue, Node, Location, String)} but with
- * a now-unused data parameter at the end.
- *
- * @deprecated Use {@link #report(Issue, Node, Location, String)} instead;
- * this method is here for custom rule compatibility
- */
- @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules
- @Deprecated
- public void report(
- @NonNull Issue issue,
- @Nullable Node scope,
- @Nullable Location location,
- @NonNull String message,
- @SuppressWarnings("UnusedParameters") @Nullable Object data) {
- report(issue, scope, location, message);
- }
-
- @Nullable
- public static Node findSurroundingMethod(Node scope) {
- while (scope != null) {
- Class<? extends Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) {
- return scope;
- }
-
- scope = scope.getParent();
- }
-
- return null;
- }
-
- @Nullable
- public static ClassDeclaration findSurroundingClass(@Nullable Node scope) {
- while (scope != null) {
- Class<? extends Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == ClassDeclaration.class) {
- return (ClassDeclaration) scope;
- }
-
- scope = scope.getParent();
- }
-
- return null;
- }
-
- @Override
- @Nullable
- protected String getSuppressCommentPrefix() {
- return SUPPRESS_COMMENT_PREFIX;
- }
-
- public boolean isSuppressedWithComment(@NonNull Node scope, @NonNull Issue issue) {
- // Check whether there is a comment marker
- String contents = getContents();
- assert contents != null; // otherwise we wouldn't be here
- Position position = scope.getPosition();
- if (position == null) {
- return false;
- }
-
- int start = position.getStart();
- return isSuppressedWithComment(start, issue);
- }
-
- @NonNull
- public Location.Handle createLocationHandle(@NonNull Node node) {
- return mParser.createLocationHandle(this, node);
- }
-
- @Nullable
- public ResolvedNode resolve(@NonNull Node node) {
- return mParser.resolve(this, node);
- }
-
- @Nullable
- public ResolvedClass findClass(@NonNull String fullyQualifiedName) {
- return mParser.findClass(this, fullyQualifiedName);
- }
-
- @Nullable
- public TypeDescriptor getType(@NonNull Node node) {
- return mParser.getType(this, node);
- }
-
- @Nullable
- public static String getMethodName(@NonNull Node call) {
- if (call instanceof MethodInvocation) {
- return ((MethodInvocation)call).astName().astValue();
- } else if (call instanceof ConstructorInvocation) {
- return ((ConstructorInvocation)call).astTypeReference().getTypeName();
- } else if (call instanceof EnumConstant) {
- return ((EnumConstant)call).astName().astValue();
- } else {
- return null;
- }
- }
-
- @NonNull
- public static Iterator<Expression> getParameters(@NonNull Node call) {
- if (call instanceof MethodInvocation) {
- return ((MethodInvocation) call).astArguments().iterator();
- } else if (call instanceof ConstructorInvocation) {
- return ((ConstructorInvocation) call).astArguments().iterator();
- } else if (call instanceof EnumConstant) {
- return ((EnumConstant) call).astArguments().iterator();
- } else {
- return Iterators.emptyIterator();
- }
- }
-
- @Nullable
- public static Node getParameter(@NonNull Node call, int parameter) {
- Iterator<Expression> iterator = getParameters(call);
-
- for (int i = 0; i < parameter - 1; i++) {
- if (!iterator.hasNext()) {
- return null;
- }
- iterator.next();
- }
- return iterator.hasNext() ? iterator.next() : null;
- }
-
- /**
- * Returns true if the given method invocation node corresponds to a call on a
- * {@code android.content.Context}
- *
- * @param node the method call node
- * @return true iff the method call is on a class extending context
- */
- public boolean isContextMethod(@NonNull MethodInvocation node) {
- // Method name used in many other contexts where it doesn't have the
- // same semantics; only use this one if we can resolve types
- // and we're certain this is the Context method
- ResolvedNode resolved = resolve(node);
- if (resolved instanceof JavaParser.ResolvedMethod) {
- JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
- ResolvedClass containingClass = method.getContainingClass();
- if (containingClass.isSubclassOf(CLASS_CONTEXT, false)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the first ancestor node of the given type
- *
- * @param element the element to search from
- * @param clz the target node type
- * @param <T> the target node type
- * @return the nearest ancestor node in the parent chain, or null if not found
- */
- @Nullable
- public static <T extends Node> T getParentOfType(
- @Nullable Node element,
- @NonNull Class<T> clz) {
- return getParentOfType(element, clz, true);
- }
-
- /**
- * Returns the first ancestor node of the given type
- *
- * @param element the element to search from
- * @param clz the target node type
- * @param strict if true, do not consider the element itself, only its parents
- * @param <T> the target node type
- * @return the nearest ancestor node in the parent chain, or null if not found
- */
- @Nullable
- public static <T extends Node> T getParentOfType(
- @Nullable Node element,
- @NonNull Class<T> clz,
- boolean strict) {
- if (element == null) {
- return null;
- }
-
- if (strict) {
- element = element.getParent();
- }
-
- while (element != null) {
- if (clz.isInstance(element)) {
- //noinspection unchecked
- return (T) element;
- }
- element = element.getParent();
- }
-
- return null;
- }
-
- /**
- * Returns the first ancestor node of the given type, stopping at the given type
- *
- * @param element the element to search from
- * @param clz the target node type
- * @param strict if true, do not consider the element itself, only its parents
- * @param terminators optional node types to terminate the search at
- * @param <T> the target node type
- * @return the nearest ancestor node in the parent chain, or null if not found
- */
- @Nullable
- public static <T extends Node> T getParentOfType(@Nullable Node element,
- @NonNull Class<T> clz,
- boolean strict,
- @NonNull Class<? extends Node>... terminators) {
- if (element == null) {
- return null;
- }
- if (strict) {
- element = element.getParent();
- }
-
- while (element != null && !clz.isInstance(element)) {
- for (Class<?> terminator : terminators) {
- if (terminator.isInstance(element)) {
- return null;
- }
- }
- element = element.getParent();
- }
-
- //noinspection unchecked
- return (T) element;
- }
-
- /**
- * Returns the first sibling of the given node that is of the given class
- *
- * @param sibling the sibling to search from
- * @param clz the type to look for
- * @param <T> the type
- * @return the first sibling of the given type, or null
- */
- @Nullable
- public static <T extends Node> T getNextSiblingOfType(@Nullable Node sibling,
- @NonNull Class<T> clz) {
- if (sibling == null) {
- return null;
- }
- Node parent = sibling.getParent();
- if (parent == null) {
- return null;
- }
-
- Iterator<Node> iterator = parent.getChildren().iterator();
- while (iterator.hasNext()) {
- if (iterator.next() == sibling) {
- break;
- }
- }
-
- while (iterator.hasNext()) {
- Node child = iterator.next();
- if (clz.isInstance(child)) {
- //noinspection unchecked
- return (T) child;
- }
-
- }
-
- return null;
- }
-
-
- /**
- * Returns the given argument of the given call
- *
- * @param call the call containing arguments
- * @param index the index of the target argument
- * @return the argument at the given index
- * @throws IllegalArgumentException if index is outside the valid range
- */
- @NonNull
- public static Node getArgumentNode(@NonNull MethodInvocation call, int index) {
- int i = 0;
- for (Expression parameter : call.astArguments()) {
- if (i == index) {
- return parameter;
- }
- i++;
- }
- throw new IllegalArgumentException(Integer.toString(index));
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
deleted file mode 100755
index b233546..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
+++ /dev/null
@@ -1,1265 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_LOCALE;
-import static com.android.SdkConstants.BIN_FOLDER;
-import static com.android.SdkConstants.DOT_GIF;
-import static com.android.SdkConstants.DOT_JPEG;
-import static com.android.SdkConstants.DOT_JPG;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.DOT_WEBP;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.SdkConstants.UTF_8;
-import static com.android.ide.common.resources.configuration.FolderConfiguration.QUALIFIER_SPLITTER;
-import static com.android.ide.common.resources.configuration.LocaleQualifier.BCP_47_PREFIX;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ApiVersion;
-import com.android.ide.common.rendering.api.ItemResourceValue;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.LocaleQualifier;
-import com.android.resources.FolderTypeRelationship;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.utils.PositionXmlParser;
-import com.android.utils.SdkUtils;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-import java.util.Queue;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-import lombok.ast.ImportDeclaration;
-
-
-/**
- * Useful utility methods related to lint.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class LintUtils {
- // Utility class, do not instantiate
- private LintUtils() {
- }
-
- /**
- * Format a list of strings, and cut of the list at {@code maxItems} if the
- * number of items are greater.
- *
- * @param strings the list of strings to print out as a comma separated list
- * @param maxItems the maximum number of items to print
- * @return a comma separated list
- */
- @NonNull
- public static String formatList(@NonNull List<String> strings, int maxItems) {
- StringBuilder sb = new StringBuilder(20 * strings.size());
-
- for (int i = 0, n = strings.size(); i < n; i++) {
- if (sb.length() > 0) {
- sb.append(", "); //$NON-NLS-1$
- }
- sb.append(strings.get(i));
-
- if (maxItems > 0 && i == maxItems - 1 && n > maxItems) {
- sb.append(String.format("... (%1$d more)", n - i - 1));
- break;
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Determine if the given type corresponds to a resource that has a unique
- * file
- *
- * @param type the resource type to check
- * @return true if the given type corresponds to a file-type resource
- */
- public static boolean isFileBasedResourceType(@NonNull ResourceType type) {
- List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);
- for (ResourceFolderType folderType : folderTypes) {
- if (folderType != ResourceFolderType.VALUES) {
- return type != ResourceType.ID;
- }
- }
- return false;
- }
-
- /**
- * Returns true if the given file represents an XML file
- *
- * @param file the file to be checked
- * @return true if the given file is an xml file
- */
- public static boolean isXmlFile(@NonNull File file) {
- return SdkUtils.endsWithIgnoreCase(file.getPath(), DOT_XML);
- }
-
- /**
- * Returns true if the given file represents a bitmap drawable file
- *
- * @param file the file to be checked
- * @return true if the given file is an xml file
- */
- public static boolean isBitmapFile(@NonNull File file) {
- String path = file.getPath();
- // endsWith(name, DOT_PNG) is also true for endsWith(name, DOT_9PNG)
- return endsWith(path, DOT_PNG)
- || endsWith(path, DOT_JPG)
- || endsWith(path, DOT_GIF)
- || endsWith(path, DOT_JPEG)
- || endsWith(path, DOT_WEBP);
- }
-
- /**
- * Case insensitive ends with
- *
- * @param string the string to be tested whether it ends with the given
- * suffix
- * @param suffix the suffix to check
- * @return true if {@code string} ends with {@code suffix},
- * case-insensitively.
- */
- public static boolean endsWith(@NonNull String string, @NonNull String suffix) {
- return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
- suffix, 0, suffix.length());
- }
-
- /**
- * Case insensitive starts with
- *
- * @param string the string to be tested whether it starts with the given prefix
- * @param prefix the prefix to check
- * @param offset the offset to start checking with
- * @return true if {@code string} starts with {@code prefix},
- * case-insensitively.
- */
- public static boolean startsWith(@NonNull String string, @NonNull String prefix, int offset) {
- return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
- }
-
- /**
- * Returns the basename of the given filename, unless it's a dot-file such as ".svn".
- *
- * @param fileName the file name to extract the basename from
- * @return the basename (the filename without the file extension)
- */
- public static String getBaseName(@NonNull String fileName) {
- int extension = fileName.indexOf('.');
- if (extension > 0) {
- return fileName.substring(0, extension);
- } else {
- return fileName;
- }
- }
-
- /**
- * Returns the children elements of the given node
- *
- * @param node the parent node
- * @return a list of element children, never null
- */
- @NonNull
- public static List<Element> getChildren(@NonNull Node node) {
- NodeList childNodes = node.getChildNodes();
- List<Element> children = new ArrayList<Element>(childNodes.getLength());
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- children.add((Element) child);
- }
- }
-
- return children;
- }
-
- /**
- * Returns the <b>number</b> of children of the given node
- *
- * @param node the parent node
- * @return the count of element children
- */
- public static int getChildCount(@NonNull Node node) {
- NodeList childNodes = node.getChildNodes();
- int childCount = 0;
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- childCount++;
- }
- }
-
- return childCount;
- }
-
- /**
- * Returns true if the given element is the root element of its document
- *
- * @param element the element to test
- * @return true if the element is the root element
- */
- public static boolean isRootElement(Element element) {
- return element == element.getOwnerDocument().getDocumentElement();
- }
-
- /**
- * Returns the given id without an {@code @id/} or {@code @+id} prefix
- *
- * @param id the id to strip
- * @return the stripped id, never null
- */
- @NonNull
- public static String stripIdPrefix(@Nullable String id) {
- if (id == null) {
- return "";
- } else if (id.startsWith(NEW_ID_PREFIX)) {
- return id.substring(NEW_ID_PREFIX.length());
- } else if (id.startsWith(ID_PREFIX)) {
- return id.substring(ID_PREFIX.length());
- }
-
- return id;
- }
-
- /**
- * Returns true if the given two id references match. This is similar to
- * String equality, but it also considers "{@code @+id/foo == @id/foo}.
- *
- * @param id1 the first id to compare
- * @param id2 the second id to compare
- * @return true if the two id references refer to the same id
- */
- public static boolean idReferencesMatch(@Nullable String id1, @Nullable String id2) {
- if (id1 == null || id2 == null || id1.isEmpty() || id2.isEmpty()) {
- return false;
- }
- if (id1.startsWith(NEW_ID_PREFIX)) {
- if (id2.startsWith(NEW_ID_PREFIX)) {
- return id1.equals(id2);
- } else {
- assert id2.startsWith(ID_PREFIX) : id2;
- return ((id1.length() - id2.length())
- == (NEW_ID_PREFIX.length() - ID_PREFIX.length()))
- && id1.regionMatches(NEW_ID_PREFIX.length(), id2,
- ID_PREFIX.length(),
- id2.length() - ID_PREFIX.length());
- }
- } else {
- assert id1.startsWith(ID_PREFIX) : id1;
- if (id2.startsWith(ID_PREFIX)) {
- return id1.equals(id2);
- } else {
- assert id2.startsWith(NEW_ID_PREFIX);
- return (id2.length() - id1.length()
- == (NEW_ID_PREFIX.length() - ID_PREFIX.length()))
- && id2.regionMatches(NEW_ID_PREFIX.length(), id1,
- ID_PREFIX.length(),
- id1.length() - ID_PREFIX.length());
- }
- }
- }
-
- /**
- * Computes the edit distance (number of insertions, deletions or substitutions
- * to edit one string into the other) between two strings. In particular,
- * this will compute the Levenshtein distance.
- * <p>
- * See http://en.wikipedia.org/wiki/Levenshtein_distance for details.
- *
- * @param s the first string to compare
- * @param t the second string to compare
- * @return the edit distance between the two strings
- */
- public static int editDistance(@NonNull String s, @NonNull String t) {
- int m = s.length();
- int n = t.length();
- int[][] d = new int[m + 1][n + 1];
- for (int i = 0; i <= m; i++) {
- d[i][0] = i;
- }
- for (int j = 0; j <= n; j++) {
- d[0][j] = j;
- }
- for (int j = 1; j <= n; j++) {
- for (int i = 1; i <= m; i++) {
- if (s.charAt(i - 1) == t.charAt(j - 1)) {
- d[i][j] = d[i - 1][j - 1];
- } else {
- int deletion = d[i - 1][j] + 1;
- int insertion = d[i][j - 1] + 1;
- int substitution = d[i - 1][j - 1] + 1;
- d[i][j] = Math.min(deletion, Math.min(insertion, substitution));
- }
- }
- }
-
- return d[m][n];
- }
-
- /**
- * Returns true if assertions are enabled
- *
- * @return true if assertions are enabled
- */
- @SuppressWarnings("all")
- public static boolean assertionsEnabled() {
- boolean assertionsEnabled = false;
- assert assertionsEnabled = true; // Intentional side-effect
- return assertionsEnabled;
- }
-
- /**
- * Returns the layout resource name for the given layout file
- *
- * @param layoutFile the file pointing to the layout
- * @return the layout resource name, not including the {@code @layout}
- * prefix
- */
- public static String getLayoutName(File layoutFile) {
- String name = layoutFile.getName();
- int dotIndex = name.indexOf('.');
- if (dotIndex != -1) {
- name = name.substring(0, dotIndex);
- }
- return name;
- }
-
- /**
- * Splits the given path into its individual parts, attempting to be
- * tolerant about path separators (: or ;). It can handle possibly ambiguous
- * paths, such as {@code c:\foo\bar:\other}, though of course these are to
- * be avoided if possible.
- *
- * @param path the path variable to split, which can use both : and ; as
- * path separators.
- * @return the individual path components as an Iterable of strings
- */
- public static Iterable<String> splitPath(@NonNull String path) {
- if (path.indexOf(';') != -1) {
- return Splitter.on(';').omitEmptyStrings().trimResults().split(path);
- }
-
- List<String> combined = new ArrayList<String>();
- Iterables.addAll(combined, Splitter.on(':').omitEmptyStrings().trimResults().split(path));
- for (int i = 0, n = combined.size(); i < n; i++) {
- String p = combined.get(i);
- if (p.length() == 1 && i < n - 1 && Character.isLetter(p.charAt(0))
- // Technically, Windows paths do not have to have a \ after the :,
- // which means it would be using the current directory on that drive,
- // but that's unlikely to be the case in a path since it would have
- // unpredictable results
- && !combined.get(i+1).isEmpty() && combined.get(i+1).charAt(0) == '\\') {
- combined.set(i, p + ':' + combined.get(i+1));
- combined.remove(i+1);
- n--;
- continue;
- }
- }
-
- return combined;
- }
-
- /**
- * Computes the shared parent among a set of files (which may be null).
- *
- * @param files the set of files to be checked
- * @return the closest common ancestor file, or null if none was found
- */
- @Nullable
- public static File getCommonParent(@NonNull List<File> files) {
- int fileCount = files.size();
- if (fileCount == 0) {
- return null;
- } else if (fileCount == 1) {
- return files.get(0);
- } else if (fileCount == 2) {
- return getCommonParent(files.get(0), files.get(1));
- } else {
- File common = files.get(0);
- for (int i = 1; i < fileCount; i++) {
- common = getCommonParent(common, files.get(i));
- if (common == null) {
- return null;
- }
- }
-
- return common;
- }
- }
-
- /**
- * Computes the closest common parent path between two files.
- *
- * @param file1 the first file to be compared
- * @param file2 the second file to be compared
- * @return the closest common ancestor file, or null if the two files have
- * no common parent
- */
- @Nullable
- public static File getCommonParent(@NonNull File file1, @NonNull File file2) {
- if (file1.equals(file2)) {
- return file1;
- } else if (file1.getPath().startsWith(file2.getPath())) {
- return file2;
- } else if (file2.getPath().startsWith(file1.getPath())) {
- return file1;
- } else {
- // Dumb and simple implementation
- File first = file1.getParentFile();
- while (first != null) {
- File second = file2.getParentFile();
- while (second != null) {
- if (first.equals(second)) {
- return first;
- }
- second = second.getParentFile();
- }
-
- first = first.getParentFile();
- }
- }
- return null;
- }
-
- private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$
- private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$
-
- /**
- * Returns the encoded String for the given file. This is usually the
- * same as {@code Files.toString(file, Charsets.UTF8}, but if there's a UTF byte order mark
- * (for UTF8, UTF_16 or UTF_16LE), use that instead.
- *
- * @param client the client to use for I/O operations
- * @param file the file to read from
- * @return the string
- * @throws IOException if the file cannot be read properly
- */
- @NonNull
- public static String getEncodedString(
- @NonNull LintClient client,
- @NonNull File file) throws IOException {
- byte[] bytes = client.readBytes(file);
- if (endsWith(file.getName(), DOT_XML)) {
- return PositionXmlParser.getXmlString(bytes);
- }
-
- return getEncodedString(bytes);
- }
-
- /**
- * Returns the String corresponding to the given data. This is usually the
- * same as {@code new String(data)}, but if there's a UTF byte order mark
- * (for UTF8, UTF_16 or UTF_16LE), use that instead.
- * <p>
- * NOTE: For XML files, there is the additional complication that there
- * could be a {@code encoding=} attribute in the prologue. For those files,
- * use {@link PositionXmlParser#getXmlString(byte[])} instead.
- *
- * @param data the byte array to construct the string from
- * @return the string
- */
- @NonNull
- public static String getEncodedString(@Nullable byte[] data) {
- if (data == null) {
- return "";
- }
-
- int offset = 0;
- String defaultCharset = UTF_8;
- String charset = null;
- // Look for the byte order mark, to see if we need to remove bytes from
- // the input stream (and to determine whether files are big endian or little endian) etc
- // for files which do not specify the encoding.
- // See http://unicode.org/faq/utf_bom.html#BOM for more.
- if (data.length > 4) {
- if (data[0] == (byte)0xef && data[1] == (byte)0xbb && data[2] == (byte)0xbf) {
- // UTF-8
- defaultCharset = charset = UTF_8;
- offset += 3;
- } else if (data[0] == (byte)0xfe && data[1] == (byte)0xff) {
- // UTF-16, big-endian
- defaultCharset = charset = UTF_16;
- offset += 2;
- } else if (data[0] == (byte)0x0 && data[1] == (byte)0x0
- && data[2] == (byte)0xfe && data[3] == (byte)0xff) {
- // UTF-32, big-endian
- defaultCharset = charset = "UTF_32"; //$NON-NLS-1$
- offset += 4;
- } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe
- && data[2] == (byte)0x0 && data[3] == (byte)0x0) {
- // UTF-32, little-endian. We must check for this *before* looking for
- // UTF_16LE since UTF_32LE has the same prefix!
- defaultCharset = charset = "UTF_32LE"; //$NON-NLS-1$
- offset += 4;
- } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe) {
- // UTF-16, little-endian
- defaultCharset = charset = UTF_16LE;
- offset += 2;
- }
- }
- int length = data.length - offset;
-
- // Guess encoding by searching for an encoding= entry in the first line.
- boolean seenOddZero = false;
- boolean seenEvenZero = false;
- for (int lineEnd = offset; lineEnd < data.length; lineEnd++) {
- if (data[lineEnd] == 0) {
- if ((lineEnd - offset) % 2 == 0) {
- seenEvenZero = true;
- } else {
- seenOddZero = true;
- }
- } else if (data[lineEnd] == '\n' || data[lineEnd] == '\r') {
- break;
- }
- }
-
- if (charset == null) {
- charset = seenOddZero ? UTF_16LE : seenEvenZero ? UTF_16 : UTF_8;
- }
-
- String text = null;
- try {
- text = new String(data, offset, length, charset);
- } catch (UnsupportedEncodingException e) {
- try {
- if (charset != defaultCharset) {
- text = new String(data, offset, length, defaultCharset);
- }
- } catch (UnsupportedEncodingException u) {
- // Just use the default encoding below
- }
- }
- if (text == null) {
- text = new String(data, offset, length);
- }
- return text;
- }
-
- /**
- * Returns true if the given class node represents a static inner class.
- *
- * @param classNode the inner class to be checked
- * @return true if the class node represents an inner class that is static
- */
- public static boolean isStaticInnerClass(@NonNull ClassNode classNode) {
- // Note: We can't just filter out static inner classes like this:
- // (classNode.access & Opcodes.ACC_STATIC) != 0
- // because the static flag only appears on methods and fields in the class
- // file. Instead, look for the synthetic this pointer.
-
- @SuppressWarnings("rawtypes") // ASM API
- List fieldList = classNode.fields;
- for (Object f : fieldList) {
- FieldNode field = (FieldNode) f;
- if (field.name.startsWith("this$") && (field.access & Opcodes.ACC_SYNTHETIC) != 0) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Returns true if the given class node represents an anonymous inner class
- *
- * @param classNode the class to be checked
- * @return true if the class appears to be an anonymous class
- */
- public static boolean isAnonymousClass(@NonNull ClassNode classNode) {
- if (classNode.outerClass == null) {
- return false;
- }
-
- String name = classNode.name;
- int index = name.lastIndexOf('$');
- if (index == -1 || index == name.length() - 1) {
- return false;
- }
-
- return Character.isDigit(name.charAt(index + 1));
- }
-
- /**
- * Returns the previous opcode prior to the given node, ignoring label and
- * line number nodes
- *
- * @param node the node to look up the previous opcode for
- * @return the previous opcode, or {@link Opcodes#NOP} if no previous node
- * was found
- */
- public static int getPrevOpcode(@NonNull AbstractInsnNode node) {
- AbstractInsnNode prev = getPrevInstruction(node);
- if (prev != null) {
- return prev.getOpcode();
- } else {
- return Opcodes.NOP;
- }
- }
-
- /**
- * Returns the previous instruction prior to the given node, ignoring label
- * and line number nodes.
- *
- * @param node the node to look up the previous instruction for
- * @return the previous instruction, or null if no previous node was found
- */
- @Nullable
- public static AbstractInsnNode getPrevInstruction(@NonNull AbstractInsnNode node) {
- AbstractInsnNode prev = node;
- while (true) {
- prev = prev.getPrevious();
- if (prev == null) {
- return null;
- } else {
- int type = prev.getType();
- if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL
- && type != AbstractInsnNode.FRAME) {
- return prev;
- }
- }
- }
- }
-
- /**
- * Returns the next opcode after to the given node, ignoring label and line
- * number nodes
- *
- * @param node the node to look up the next opcode for
- * @return the next opcode, or {@link Opcodes#NOP} if no next node was found
- */
- public static int getNextOpcode(@NonNull AbstractInsnNode node) {
- AbstractInsnNode next = getNextInstruction(node);
- if (next != null) {
- return next.getOpcode();
- } else {
- return Opcodes.NOP;
- }
- }
-
- /**
- * Returns the next instruction after to the given node, ignoring label and
- * line number nodes.
- *
- * @param node the node to look up the next node for
- * @return the next instruction, or null if no next node was found
- */
- @Nullable
- public static AbstractInsnNode getNextInstruction(@NonNull AbstractInsnNode node) {
- AbstractInsnNode next = node;
- while (true) {
- next = next.getNext();
- if (next == null) {
- return null;
- } else {
- int type = next.getType();
- if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL
- && type != AbstractInsnNode.FRAME) {
- return next;
- }
- }
- }
- }
-
- /**
- * Returns true if the given directory is a lint manifest file directory.
- *
- * @param dir the directory to check
- * @return true if the directory contains a manifest file
- */
- public static boolean isManifestFolder(File dir) {
- boolean hasManifest = new File(dir, ANDROID_MANIFEST_XML).exists();
- if (hasManifest) {
- // Special case: the bin/ folder can also contain a copy of the
- // manifest file, but this is *not* a project directory
- if (dir.getName().equals(BIN_FOLDER)) {
- // ...unless of course it just *happens* to be a project named bin, in
- // which case we peek at its parent to see if this is the case
- dir = dir.getParentFile();
- //noinspection ConstantConditions
- if (dir != null && isManifestFolder(dir)) {
- // Yes, it's a bin/ directory inside a real project: ignore this dir
- return false;
- }
- }
- }
-
- return hasManifest;
- }
-
- /**
- * Look up the locale and region from the given parent folder name and
- * return it as a combined string, such as "en", "en-rUS", b+eng-US, etc, or null if
- * no language is specified.
- *
- * @param folderName the folder name
- * @return the locale+region string or null
- */
- @Nullable
- public static String getLocaleAndRegion(@NonNull String folderName) {
- if (folderName.indexOf('-') == -1) {
- return null;
- }
-
- String locale = null;
-
- for (String qualifier : QUALIFIER_SPLITTER.split(folderName)) {
- int qualifierLength = qualifier.length();
- if (qualifierLength == 2) {
- char first = qualifier.charAt(0);
- char second = qualifier.charAt(1);
- if (first >= 'a' && first <= 'z' && second >= 'a' && second <= 'z') {
- locale = qualifier;
- }
- } else if (qualifierLength == 3 && qualifier.charAt(0) == 'r' && locale != null) {
- char first = qualifier.charAt(1);
- char second = qualifier.charAt(2);
- if (first >= 'A' && first <= 'Z' && second >= 'A' && second <= 'Z') {
- return locale + '-' + qualifier;
- }
- break;
- } else if (qualifier.startsWith(BCP_47_PREFIX)) {
- return qualifier;
- }
- }
-
- return locale;
- }
-
- /**
- * Returns true if the given class (specified by a fully qualified class
- * name) name is imported in the given compilation unit either through a fully qualified
- * import or by a wildcard import.
- *
- * @param compilationUnit the compilation unit
- * @param fullyQualifiedName the fully qualified class name
- * @return true if the given imported name refers to the given fully
- * qualified name
- */
- public static boolean isImported(
- @Nullable lombok.ast.Node compilationUnit,
- @NonNull String fullyQualifiedName) {
- if (compilationUnit == null) {
- return false;
- }
- int dotIndex = fullyQualifiedName.lastIndexOf('.');
- int dotLength = fullyQualifiedName.length() - dotIndex;
-
- boolean imported = false;
- for (lombok.ast.Node rootNode : compilationUnit.getChildren()) {
- if (rootNode instanceof ImportDeclaration) {
- ImportDeclaration importDeclaration = (ImportDeclaration) rootNode;
- String fqn = importDeclaration.asFullyQualifiedName();
- if (fqn.equals(fullyQualifiedName)) {
- return true;
- } else if (fullyQualifiedName.regionMatches(dotIndex, fqn,
- fqn.length() - dotLength, dotLength)) {
- // This import is importing the class name using some other prefix, so there
- // fully qualified class name cannot be imported under that name
- return false;
- } else if (importDeclaration.astStarImport()
- && fqn.regionMatches(0, fqn, 0, dotIndex + 1)) {
- imported = true;
- // but don't break -- keep searching in case there's a non-wildcard
- // import of the specific class name, e.g. if we're looking for
- // android.content.SharedPreferences.Editor, don't match on the following:
- // import android.content.SharedPreferences.*;
- // import foo.bar.Editor;
- }
- }
- }
-
- return imported;
- }
-
- /**
- * Looks up the resource values for the given attribute given a style. Note that
- * this only looks project-level style values, it does not resume into the framework
- * styles.
- */
- @Nullable
- public static List<ResourceValue> getStyleAttributes(
- @NonNull Project project, @NonNull LintClient client,
- @NonNull String styleUrl, @NonNull String namespace, @NonNull String attribute) {
- if (!client.supportsProjectResources()) {
- return null;
- }
-
- AbstractResourceRepository resources = client.getProjectResources(project, true);
- if (resources == null) {
- return null;
- }
-
- ResourceUrl style = ResourceUrl.parse(styleUrl);
- if (style == null || style.framework) {
- return null;
- }
-
- List<ResourceValue> result = null;
-
- Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>();
- queue.add(new ResourceValue(style.type, style.name, false));
- Set<String> seen = Sets.newHashSet();
- int count = 0;
- boolean isFrameworkAttribute = ANDROID_URI.equals(namespace);
- while (count < 30 && !queue.isEmpty()) {
- ResourceValue front = queue.remove();
- String name = front.getName();
- seen.add(name);
- List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name);
- if (items != null) {
- for (ResourceItem item : items) {
- ResourceValue rv = item.getResourceValue(false);
- if (rv instanceof StyleResourceValue) {
- StyleResourceValue srv = (StyleResourceValue) rv;
- ItemResourceValue value = srv.getItem(attribute, isFrameworkAttribute);
- if (value != null) {
- if (result == null) {
- result = Lists.newArrayList();
- }
- if (!result.contains(value)) {
- result.add(value);
- }
- }
-
- String parent = srv.getParentStyle();
- if (parent != null && !parent.startsWith(ANDROID_PREFIX)) {
- ResourceUrl p = ResourceUrl.parse(parent);
- if (p != null && !p.framework && !seen.contains(p.name)) {
- seen.add(p.name);
- queue.add(new ResourceValue(ResourceType.STYLE, p.name,
- false));
- }
- }
-
- int index = name.lastIndexOf('.');
- if (index > 0) {
- String parentName = name.substring(0, index);
- if (!seen.contains(parentName)) {
- seen.add(parentName);
- queue.add(new ResourceValue(ResourceType.STYLE, parentName,
- false));
- }
- }
- }
- }
- }
-
- count++;
- }
-
- return result;
- }
-
- @Nullable
- public static List<StyleResourceValue> getInheritedStyles(
- @NonNull Project project, @NonNull LintClient client,
- @NonNull String styleUrl) {
- if (!client.supportsProjectResources()) {
- return null;
- }
-
- AbstractResourceRepository resources = client.getProjectResources(project, true);
- if (resources == null) {
- return null;
- }
-
- ResourceUrl style = ResourceUrl.parse(styleUrl);
- if (style == null || style.framework) {
- return null;
- }
-
- List<StyleResourceValue> result = null;
-
- Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>();
- queue.add(new ResourceValue(style.type, style.name, false));
- Set<String> seen = Sets.newHashSet();
- int count = 0;
- while (count < 30 && !queue.isEmpty()) {
- ResourceValue front = queue.remove();
- String name = front.getName();
- seen.add(name);
- List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name);
- if (items != null) {
- for (ResourceItem item : items) {
- ResourceValue rv = item.getResourceValue(false);
- if (rv instanceof StyleResourceValue) {
- StyleResourceValue srv = (StyleResourceValue) rv;
- if (result == null) {
- result = Lists.newArrayList();
- }
- result.add(srv);
-
- String parent = srv.getParentStyle();
- if (parent != null && !parent.startsWith(ANDROID_PREFIX)) {
- ResourceUrl p = ResourceUrl.parse(parent);
- if (p != null && !p.framework && !seen.contains(p.name)) {
- seen.add(p.name);
- queue.add(new ResourceValue(ResourceType.STYLE, p.name,
- false));
- }
- }
-
- int index = name.lastIndexOf('.');
- if (index > 0) {
- String parentName = name.substring(0, index);
- if (!seen.contains(parentName)) {
- seen.add(parentName);
- queue.add(new ResourceValue(ResourceType.STYLE, parentName,
- false));
- }
- }
- }
- }
- }
-
- count++;
- }
-
- return result;
- }
-
- /** Returns true if the given two paths point to the same logical resource file within
- * a source set. This means that it only checks the parent folder name and individual
- * file name, not the path outside the parent folder.
- *
- * @param file1 the first file to compare
- * @param file2 the second file to compare
- * @return true if the two files have the same parent and file names
- */
- public static boolean isSameResourceFile(@Nullable File file1, @Nullable File file2) {
- if (file1 != null && file2 != null
- && file1.getName().equals(file2.getName())) {
- File parent1 = file1.getParentFile();
- File parent2 = file2.getParentFile();
- if (parent1 != null && parent2 != null &&
- parent1.getName().equals(parent2.getName())) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Whether we should attempt to look up the prefix from the model. Set to false
- * if we encounter a model which is too old.
- * <p>
- * This is public such that code which for example syncs to a new gradle model
- * can reset it.
- */
- public static boolean sTryPrefixLookup = true;
-
- /** Looks up the resource prefix for the given Gradle project, if possible */
- @Nullable
- public static String computeResourcePrefix(@Nullable AndroidProject project) {
- try {
- if (sTryPrefixLookup && project != null) {
- return project.getResourcePrefix();
- }
- } catch (Exception e) {
- // This happens if we're talking to an older model than 0.10
- // Ignore; fall through to normal handling and never try again.
- //noinspection AssignmentToStaticFieldFromInstanceMethod
- sTryPrefixLookup = false;
- }
-
- return null;
- }
-
- /** Computes a suggested name given a resource prefix and resource name */
- public static String computeResourceName(@NonNull String prefix, @NonNull String name) {
- if (prefix.isEmpty()) {
- return name;
- } else if (name.isEmpty()) {
- return prefix;
- } else if (prefix.endsWith("_")) {
- return prefix + name;
- } else {
- return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
- }
- }
-
-
- /**
- * Convert an {@link com.android.builder.model.ApiVersion} to a {@link
- * com.android.sdklib.AndroidVersion}. The chief problem here is that the {@link
- * com.android.builder.model.ApiVersion}, when using a codename, will not encode the
- * corresponding API level (it just reflects the string entered by the user in the gradle file)
- * so we perform a search here (since lint really wants to know the actual numeric API level)
- *
- * @param api the api version to convert
- * @param targets if known, the installed targets (used to resolve platform codenames, only
- * needed to resolve platforms newer than the tools since {@link
- * com.android.sdklib.SdkVersionInfo} knows the rest)
- * @return the corresponding version
- */
- @NonNull
- public static AndroidVersion convertVersion(
- @NonNull ApiVersion api,
- @Nullable IAndroidTarget[] targets) {
- String codename = api.getCodename();
- if (codename != null) {
- AndroidVersion version = SdkVersionInfo.getVersion(codename, targets);
- if (version != null) {
- return version;
- }
- return new AndroidVersion(api.getApiLevel(), codename);
- }
- return new AndroidVersion(api.getApiLevel(), null);
- }
-
- /**
- * Returns true if the given Gradle model is older than the given version number
- */
- public static boolean isModelOlderThan(@Nullable AndroidProject project,
- int major, int minor, int micro) {
- if (project != null) {
- String modelVersion = project.getModelVersion();
- try {
- FullRevision version = FullRevision.parseRevision(modelVersion);
- if (version.getMajor() != major) {
- return version.getMajor() < major;
- }
- if (version.getMinor() != minor) {
- return version.getMinor() < minor;
- }
- return version.getMicro() < micro;
- } catch (NumberFormatException e) {
- // ignore
- }
- }
-
- return false;
- }
-
- /**
- * Looks for a certain string within a larger string, which should immediately follow
- * the given prefix and immediately precede the given suffix.
- *
- * @param string the full string to search
- * @param prefix the optional prefix to follow
- * @param suffix the optional suffix to precede
- * @return the corresponding substring, if present
- */
- @Nullable
- public static String findSubstring(@NonNull String string, @Nullable String prefix,
- @Nullable String suffix) {
- int start = 0;
- if (prefix != null) {
- start = string.indexOf(prefix);
- if (start == -1) {
- return null;
- }
- start += prefix.length();
- }
-
- if (suffix != null) {
- int end = string.indexOf(suffix, start);
- if (end == -1) {
- return null;
- }
- return string.substring(start, end);
- }
-
- return string.substring(start);
- }
-
- /**
- * Splits up the given message coming from a given string format (where the string
- * format follows the very specific convention of having only strings formatted exactly
- * with the format %n$s where n is between 1 and 9 inclusive, and each formatting parameter
- * appears exactly once, and in increasing order.
- *
- * @param format the format string responsible for creating the error message
- * @param errorMessage an error message formatted with the format string
- * @return the specific values inserted into the format
- */
- @NonNull
- public static List<String> getFormattedParameters(
- @NonNull String format,
- @NonNull String errorMessage) {
- StringBuilder pattern = new StringBuilder(format.length());
- int parameter = 1;
- for (int i = 0, n = format.length(); i < n; i++) {
- char c = format.charAt(i);
- if (c == '%') {
- // Only support formats of the form %n$s where n is 1 <= n <=9
- assert i < format.length() - 4 : format;
- assert format.charAt(i + 1) == ('0' + parameter) : format;
- assert Character.isDigit(format.charAt(i + 1)) : format;
- assert format.charAt(i + 2) == '$' : format;
- assert format.charAt(i + 3) == 's' : format;
- parameter++;
- i += 3;
- pattern.append("(.*)");
- } else {
- pattern.append(c);
- }
- }
- try {
- Pattern compile = Pattern.compile(pattern.toString());
- Matcher matcher = compile.matcher(errorMessage);
- if (matcher.find()) {
- int groupCount = matcher.groupCount();
- List<String> parameters = Lists.newArrayListWithExpectedSize(groupCount);
- for (int i = 1; i <= groupCount; i++) {
- parameters.add(matcher.group(i));
- }
-
- return parameters;
- }
-
- } catch (PatternSyntaxException pse) {
- // Internal error: string format is not valid. Should be caught by unit tests
- // as a failure to return the formatted parameters.
- }
- return Collections.emptyList();
- }
-
- /**
- * Escapes the given property file value (right hand side of property assignment)
- * as required by the property file format (e.g. escapes colons and backslashes)
- *
- * @param value the value to be escaped
- * @return the escaped value
- */
- @NonNull
- public static String escapePropertyValue(@NonNull String value) {
- // Slow, stupid implementation, but is 100% compatible with Java's property file
- // implementation
- Properties properties = new Properties();
- properties.setProperty("k", value); // key doesn't matter
- StringWriter writer = new StringWriter();
- try {
- properties.store(writer, null);
- String s = writer.toString();
- int end = s.length();
-
- // Writer inserts trailing newline
- String lineSeparator = SdkUtils.getLineSeparator();
- if (s.endsWith(lineSeparator)) {
- end -= lineSeparator.length();
- }
-
- int start = s.indexOf('=');
- assert start != -1 : s;
- return s.substring(start + 1, end);
- }
- catch (IOException e) {
- return value; // shouldn't happen; we're not going to disk
- }
- }
-
- /**
- * Returns the locale for the given parent folder.
- *
- * @param parent the name of the parent folder
- * @return null if the locale is not known, or a locale qualifier providing the language
- * and possibly region
- */
- @Nullable
- public static LocaleQualifier getLocale(@NonNull String parent) {
- if (parent.indexOf('-') != -1) {
- FolderConfiguration config = FolderConfiguration.getConfigForFolder(parent);
- if (config != null) {
- return config.getLocaleQualifier();
- }
- }
- return null;
- }
-
- /**
- * Returns the locale for the given context.
- *
- * @param context the context to look up the locale for
- * @return null if the locale is not known, or a locale qualifier providing the language
- * and possibly region
- */
- @Nullable
- public static LocaleQualifier getLocale(@NonNull XmlContext context) {
- Element root = context.document.getDocumentElement();
- if (root != null) {
- String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
- if (locale != null && !locale.isEmpty()) {
- return getLocale(locale);
- }
- }
-
- return getLocale(context.file.getParentFile().getName());
- }
-
- /**
- * Check whether the given resource file is in an English locale
- * @param context the XML context for the resource file
- * @param assumeForBase whether the base folder (e.g. no locale specified) should be
- * treated as English
- */
- public static boolean isEnglishResource(@NonNull XmlContext context, boolean assumeForBase) {
- LocaleQualifier locale = LintUtils.getLocale(context);
- if (locale == null) {
- return assumeForBase;
- } else {
- return "en".equals(locale.getLanguage()); //$NON-NLS-1$
- }
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
deleted file mode 100644
index 284e41b..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
+++ /dev/null
@@ -1,777 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.ide.common.res2.ResourceFile;
-import com.android.ide.common.res2.ResourceItem;
-import com.google.common.annotations.Beta;
-
-import java.io.File;
-
-/**
- * Location information for a warning
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class Location {
- private static final String SUPER_KEYWORD = "super"; //$NON-NLS-1$
-
- private final File mFile;
- private final Position mStart;
- private final Position mEnd;
- private String mMessage;
- private Location mSecondary;
- private Object mClientData;
-
- /**
- * (Private constructor, use one of the factory methods
- * {@link Location#create(File)},
- * {@link Location#create(File, Position, Position)}, or
- * {@link Location#create(File, String, int, int)}.
- * <p>
- * Constructs a new location range for the given file, from start to end. If
- * the length of the range is not known, end may be null.
- *
- * @param file the associated file (but see the documentation for
- * {@link #getFile()} for more information on what the file
- * represents)
- * @param start the starting position, or null
- * @param end the ending position, or null
- */
- protected Location(@NonNull File file, @Nullable Position start, @Nullable Position end) {
- super();
- mFile = file;
- mStart = start;
- mEnd = end;
- }
-
- /**
- * Returns the file containing the warning. Note that the file *itself* may
- * not yet contain the error. When editing a file in the IDE for example,
- * the tool could generate warnings in the background even before the
- * document is saved. However, the file is used as a identifying token for
- * the document being edited, and the IDE integration can map this back to
- * error locations in the editor source code.
- *
- * @return the file handle for the location
- */
- @NonNull
- public File getFile() {
- return mFile;
- }
-
- /**
- * The start position of the range
- *
- * @return the start position of the range, or null
- */
- @Nullable
- public Position getStart() {
- return mStart;
- }
-
- /**
- * The end position of the range
- *
- * @return the start position of the range, may be null for an empty range
- */
- @Nullable
- public Position getEnd() {
- return mEnd;
- }
-
- /**
- * Returns a secondary location associated with this location (if
- * applicable), or null.
- *
- * @return a secondary location or null
- */
- @Nullable
- public Location getSecondary() {
- return mSecondary;
- }
-
- /**
- * Sets a secondary location for this location.
- *
- * @param secondary a secondary location associated with this location
- */
- public void setSecondary(@Nullable Location secondary) {
- mSecondary = secondary;
- }
-
- /**
- * Sets a custom message for this location. This is typically used for
- * secondary locations, to describe the significance of this alternate
- * location. For example, for a duplicate id warning, the primary location
- * might say "This is a duplicate id", pointing to the second occurrence of
- * id declaration, and then the secondary location could point to the
- * original declaration with the custom message "Originally defined here".
- *
- * @param message the message to apply to this location
- */
- public void setMessage(@NonNull String message) {
- mMessage = message;
- }
-
- /**
- * Returns the custom message for this location, if any. This is typically
- * used for secondary locations, to describe the significance of this
- * alternate location. For example, for a duplicate id warning, the primary
- * location might say "This is a duplicate id", pointing to the second
- * occurrence of id declaration, and then the secondary location could point
- * to the original declaration with the custom message
- * "Originally defined here".
- *
- * @return the custom message for this location, or null
- */
- @Nullable
- public String getMessage() {
- return mMessage;
- }
-
- /**
- * Sets the client data associated with this location. This is an optional
- * field which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @param clientData the data to store with this location
- */
- public void setClientData(@Nullable Object clientData) {
- mClientData = clientData;
- }
-
- /**
- * Returns the client data associated with this location - an optional field
- * which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @return the data associated with this location
- */
- @Nullable
- public Object getClientData() {
- return mClientData;
- }
-
- @Override
- public String toString() {
- return "Location [file=" + mFile + ", start=" + mStart + ", end=" + mEnd + ", message="
- + mMessage + ']';
- }
-
- /**
- * Creates a new location for the given file
- *
- * @param file the file to create a location for
- * @return a new location
- */
- @NonNull
- public static Location create(@NonNull File file) {
- return new Location(file, null /*start*/, null /*end*/);
- }
-
- /**
- * Creates a new location for the given file and SourcePosition.
- *
- * @param file the file containing the positions
- * @param position the source position
- * @return a new location
- */
- @NonNull
- public static Location create(
- @NonNull File file,
- @NonNull SourcePosition position) {
- if (position.equals(SourcePosition.UNKNOWN)) {
- return new Location(file, null /*start*/, null /*end*/);
- }
- return new Location(file,
- new DefaultPosition(
- position.getStartLine(),
- position.getStartColumn(),
- position.getStartOffset()),
- new DefaultPosition(
- position.getEndLine(),
- position.getEndColumn(),
- position.getEndOffset()));
- }
-
- /**
- * Creates a new location for the given file and starting and ending
- * positions.
- *
- * @param file the file containing the positions
- * @param start the starting position
- * @param end the ending position
- * @return a new location
- */
- @NonNull
- public static Location create(
- @NonNull File file,
- @NonNull Position start,
- @Nullable Position end) {
- return new Location(file, start, end);
- }
-
- /**
- * Creates a new location for the given file, with the given contents, for
- * the given offset range.
- *
- * @param file the file containing the location
- * @param contents the current contents of the file
- * @param startOffset the starting offset
- * @param endOffset the ending offset
- * @return a new location
- */
- @NonNull
- public static Location create(
- @NonNull File file,
- @Nullable String contents,
- int startOffset,
- int endOffset) {
- if (startOffset < 0 || endOffset < startOffset) {
- throw new IllegalArgumentException("Invalid offsets");
- }
-
- if (contents == null) {
- return new Location(file,
- new DefaultPosition(-1, -1, startOffset),
- new DefaultPosition(-1, -1, endOffset));
- }
-
- int size = contents.length();
- endOffset = Math.min(endOffset, size);
- startOffset = Math.min(startOffset, endOffset);
- Position start = null;
- int line = 0;
- int lineOffset = 0;
- char prev = 0;
- for (int offset = 0; offset <= size; offset++) {
- if (offset == startOffset) {
- start = new DefaultPosition(line, offset - lineOffset, offset);
- }
- if (offset == endOffset) {
- Position end = new DefaultPosition(line, offset - lineOffset, offset);
- return new Location(file, start, end);
- }
- char c = contents.charAt(offset);
- if (c == '\n') {
- lineOffset = offset + 1;
- if (prev != '\r') {
- line++;
- }
- } else if (c == '\r') {
- line++;
- lineOffset = offset + 1;
- }
- prev = c;
- }
- return create(file);
- }
-
- /**
- * Creates a new location for the given file, with the given contents, for
- * the given line number.
- *
- * @param file the file containing the location
- * @param contents the current contents of the file
- * @param line the line number (0-based) for the position
- * @return a new location
- */
- @NonNull
- public static Location create(@NonNull File file, @NonNull String contents, int line) {
- return create(file, contents, line, null, null, null);
- }
-
- /**
- * Creates a new location for the given file, with the given contents, for
- * the given line number.
- *
- * @param file the file containing the location
- * @param contents the current contents of the file
- * @param line the line number (0-based) for the position
- * @param patternStart an optional pattern to search for from the line
- * match; if found, adjust the column and offsets to begin at the
- * pattern start
- * @param patternEnd an optional pattern to search for behind the start
- * pattern; if found, adjust the end offset to match the end of
- * the pattern
- * @param hints optional additional information regarding the pattern search
- * @return a new location
- */
- @NonNull
- public static Location create(@NonNull File file, @NonNull String contents, int line,
- @Nullable String patternStart, @Nullable String patternEnd,
- @Nullable SearchHints hints) {
- int currentLine = 0;
- int offset = 0;
- while (currentLine < line) {
- offset = contents.indexOf('\n', offset);
- if (offset == -1) {
- return create(file);
- }
- currentLine++;
- offset++;
- }
-
- if (line == currentLine) {
- if (patternStart != null) {
- SearchDirection direction = SearchDirection.NEAREST;
- if (hints != null) {
- direction = hints.mDirection;
- }
-
- int index;
- if (direction == SearchDirection.BACKWARD) {
- index = findPreviousMatch(contents, offset, patternStart, hints);
- line = adjustLine(contents, line, offset, index);
- } else if (direction == SearchDirection.EOL_BACKWARD) {
- int lineEnd = contents.indexOf('\n', offset);
- if (lineEnd == -1) {
- lineEnd = contents.length();
- }
-
- index = findPreviousMatch(contents, lineEnd, patternStart, hints);
- line = adjustLine(contents, line, offset, index);
- } else if (direction == SearchDirection.FORWARD) {
- index = findNextMatch(contents, offset, patternStart, hints);
- line = adjustLine(contents, line, offset, index);
- } else {
- assert direction == SearchDirection.NEAREST;
-
- int before = findPreviousMatch(contents, offset, patternStart, hints);
- int after = findNextMatch(contents, offset, patternStart, hints);
-
- if (before == -1) {
- index = after;
- line = adjustLine(contents, line, offset, index);
- } else if (after == -1) {
- index = before;
- line = adjustLine(contents, line, offset, index);
- } else if (offset - before < after - offset) {
- index = before;
- line = adjustLine(contents, line, offset, index);
- } else {
- index = after;
- line = adjustLine(contents, line, offset, index);
- }
- }
-
- if (index != -1) {
- int lineStart = contents.lastIndexOf('\n', index);
- if (lineStart == -1) {
- lineStart = 0;
- } else {
- lineStart++; // was pointing to the previous line's CR, not line start
- }
- int column = index - lineStart;
- if (patternEnd != null) {
- int end = contents.indexOf(patternEnd, offset + patternStart.length());
- if (end != -1) {
- return new Location(file, new DefaultPosition(line, column, index),
- new DefaultPosition(line, -1, end + patternEnd.length()));
- }
- } else if (hints != null && (hints.isJavaSymbol() || hints.isWholeWord())) {
- if (hints.isConstructor() && contents.startsWith(SUPER_KEYWORD, index)) {
- patternStart = SUPER_KEYWORD;
- }
- return new Location(file, new DefaultPosition(line, column, index),
- new DefaultPosition(line, column + patternStart.length(),
- index + patternStart.length()));
- }
- return new Location(file, new DefaultPosition(line, column, index),
- new DefaultPosition(line, column, index + patternStart.length()));
- }
- }
-
- Position position = new DefaultPosition(line, -1, offset);
- return new Location(file, position, position);
- }
-
- return create(file);
- }
-
- private static int findPreviousMatch(@NonNull String contents, int offset, String pattern,
- @Nullable SearchHints hints) {
- while (true) {
- int index = contents.lastIndexOf(pattern, offset);
- if (index == -1) {
- return -1;
- } else {
- if (isMatch(contents, index, pattern, hints)) {
- return index;
- } else {
- offset = index - pattern.length();
- }
- }
- }
- }
-
- private static int findNextMatch(@NonNull String contents, int offset, String pattern,
- @Nullable SearchHints hints) {
- int constructorIndex = -1;
- if (hints != null && hints.isConstructor()) {
- // Special condition: See if the call is referenced as "super" instead.
- assert hints.isWholeWord();
- int index = contents.indexOf(SUPER_KEYWORD, offset);
- if (index != -1 && isMatch(contents, index, SUPER_KEYWORD, hints)) {
- constructorIndex = index;
- }
- }
-
- while (true) {
- int index = contents.indexOf(pattern, offset);
- if (index == -1) {
- return constructorIndex;
- } else {
- if (isMatch(contents, index, pattern, hints)) {
- if (constructorIndex != -1) {
- return Math.min(constructorIndex, index);
- }
- return index;
- } else {
- offset = index + pattern.length();
- }
- }
- }
- }
-
- private static boolean isMatch(@NonNull String contents, int offset, String pattern,
- @Nullable SearchHints hints) {
- if (!contents.startsWith(pattern, offset)) {
- return false;
- }
-
- if (hints != null) {
- char prevChar = offset > 0 ? contents.charAt(offset - 1) : 0;
- int lastIndex = offset + pattern.length() - 1;
- char nextChar = lastIndex < contents.length() - 1 ? contents.charAt(lastIndex + 1) : 0;
-
- if (hints.isWholeWord() && (Character.isLetter(prevChar)
- || Character.isLetter(nextChar))) {
- return false;
-
- }
-
- if (hints.isJavaSymbol()) {
- if (Character.isJavaIdentifierPart(prevChar)
- || Character.isJavaIdentifierPart(nextChar)) {
- return false;
- }
-
- if (prevChar == '"') {
- return false;
- }
-
- // TODO: Additional validation to see if we're in a comment, string, etc.
- // This will require lexing from the beginning of the buffer.
- }
-
- if (hints.isConstructor() && SUPER_KEYWORD.equals(pattern)) {
- // Only looking for super(), not super.x, so assert that the next
- // non-space character is (
- int index = lastIndex + 1;
- while (index < contents.length() - 1) {
- char c = contents.charAt(index);
- if (c == '(') {
- break;
- } else if (!Character.isWhitespace(c)) {
- return false;
- }
- index++;
- }
- }
- }
-
- return true;
- }
-
- private static int adjustLine(String doc, int line, int offset, int newOffset) {
- if (newOffset == -1) {
- return line;
- }
-
- if (newOffset < offset) {
- return line - countLines(doc, newOffset, offset);
- } else {
- return line + countLines(doc, offset, newOffset);
- }
- }
-
- private static int countLines(String doc, int start, int end) {
- int lines = 0;
- for (int offset = start; offset < end; offset++) {
- char c = doc.charAt(offset);
- if (c == '\n') {
- lines++;
- }
- }
-
- return lines;
- }
-
- /**
- * Reverses the secondary location list initiated by the given location
- *
- * @param location the first location in the list
- * @return the first location in the reversed list
- */
- public static Location reverse(@NonNull Location location) {
- Location next = location.getSecondary();
- location.setSecondary(null);
- while (next != null) {
- Location nextNext = next.getSecondary();
- next.setSecondary(location);
- location = next;
- next = nextNext;
- }
-
- return location;
- }
-
- /**
- * A {@link Handle} is a reference to a location. The point of a location
- * handle is to be able to create them cheaply, and then resolve them into
- * actual locations later (if needed). This makes it possible to for example
- * delay looking up line numbers, for locations that are offset based.
- */
- public interface Handle {
- /**
- * Compute a full location for the given handle
- *
- * @return create a location for this handle
- */
- @NonNull
- Location resolve();
-
- /**
- * Sets the client data associated with this location. This is an optional
- * field which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @param clientData the data to store with this location
- */
- void setClientData(@Nullable Object clientData);
-
- /**
- * Returns the client data associated with this location - an optional field
- * which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @return the data associated with this location
- */
- @Nullable
- Object getClientData();
- }
-
- /** A default {@link Handle} implementation for simple file offsets */
- public static class DefaultLocationHandle implements Handle {
- private final File mFile;
- private final String mContents;
- private final int mStartOffset;
- private final int mEndOffset;
- private Object mClientData;
-
- /**
- * Constructs a new {@link DefaultLocationHandle}
- *
- * @param context the context pointing to the file and its contents
- * @param startOffset the start offset within the file
- * @param endOffset the end offset within the file
- */
- public DefaultLocationHandle(@NonNull Context context, int startOffset, int endOffset) {
- mFile = context.file;
- mContents = context.getContents();
- mStartOffset = startOffset;
- mEndOffset = endOffset;
- }
-
- @Override
- @NonNull
- public Location resolve() {
- return create(mFile, mContents, mStartOffset, mEndOffset);
- }
-
- @Override
- public void setClientData(@Nullable Object clientData) {
- mClientData = clientData;
- }
-
- @Override
- @Nullable
- public Object getClientData() {
- return mClientData;
- }
- }
-
- public static class ResourceItemHandle implements Handle {
- private final ResourceItem mItem;
-
- public ResourceItemHandle(@NonNull ResourceItem item) {
- mItem = item;
- }
- @NonNull
- @Override
- public Location resolve() {
- // TODO: Look up the exact item location more
- // closely
- ResourceFile source = mItem.getSource();
- assert source != null : mItem;
- return create(source.getFile());
- }
-
- @Override
- public void setClientData(@Nullable Object clientData) {
- }
-
- @Nullable
- @Override
- public Object getClientData() {
- return null;
- }
- }
-
- /**
- * Whether to look forwards, or backwards, or in both directions, when
- * searching for a pattern in the source code to determine the right
- * position range for a given symbol.
- * <p>
- * When dealing with bytecode for example, there are only line number entries
- * within method bodies, so when searching for the method declaration, we should only
- * search backwards from the first line entry in the method.
- */
- public enum SearchDirection {
- /** Only search forwards */
- FORWARD,
-
- /** Only search backwards */
- BACKWARD,
-
- /** Search backwards from the current end of line (normally it's the beginning of
- * the current line) */
- EOL_BACKWARD,
-
- /**
- * Search both forwards and backwards from the given line, and prefer
- * the match that is closest
- */
- NEAREST,
- }
-
- /**
- * Extra information pertaining to finding a symbol in a source buffer,
- * used by {@link Location#create(File, String, int, String, String, SearchHints)}
- */
- public static class SearchHints {
- /**
- * the direction to search for the nearest match in (provided
- * {@code patternStart} is non null)
- */
- @NonNull
- private final SearchDirection mDirection;
-
- /** Whether the matched pattern should be a whole word */
- private boolean mWholeWord;
-
- /**
- * Whether the matched pattern should be a Java symbol (so for example,
- * a match inside a comment or string literal should not be used)
- */
- private boolean mJavaSymbol;
-
- /**
- * Whether the matched pattern corresponds to a constructor; if so, look for
- * some other possible source aliases too, such as "super".
- */
- private boolean mConstructor;
-
- private SearchHints(@NonNull SearchDirection direction) {
- super();
- mDirection = direction;
- }
-
- /**
- * Constructs a new {@link SearchHints} object
- *
- * @param direction the direction to search in for the pattern
- * @return a new @link SearchHints} object
- */
- @NonNull
- public static SearchHints create(@NonNull SearchDirection direction) {
- return new SearchHints(direction);
- }
-
- /**
- * Indicates that pattern matches should apply to whole words only
-
- * @return this, for constructor chaining
- */
- @NonNull
- public SearchHints matchWholeWord() {
- mWholeWord = true;
-
- return this;
- }
-
- /** @return true if the pattern match should be for whole words only */
- public boolean isWholeWord() {
- return mWholeWord;
- }
-
- /**
- * Indicates that pattern matches should apply to Java symbols only
- *
- * @return this, for constructor chaining
- */
- @NonNull
- public SearchHints matchJavaSymbol() {
- mJavaSymbol = true;
- mWholeWord = true;
-
- return this;
- }
-
- /** @return true if the pattern match should be for Java symbols only */
- public boolean isJavaSymbol() {
- return mJavaSymbol;
- }
-
- /**
- * Indicates that pattern matches should apply to constructors. If so, look for
- * some other possible source aliases too, such as "super".
- *
- * @return this, for constructor chaining
- */
- @NonNull
- public SearchHints matchConstructor() {
- mConstructor = true;
- mWholeWord = true;
- mJavaSymbol = true;
-
- return this;
- }
-
- /** @return true if the pattern match should be for a constructor */
- public boolean isConstructor() {
- return mConstructor;
- }
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
deleted file mode 100755
index 3831284..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
+++ /dev/null
@@ -1,1378 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.ANDROID_LIBRARY;
-import static com.android.SdkConstants.ANDROID_LIBRARY_REFERENCE_FORMAT;
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT;
-import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
-import static com.android.SdkConstants.ATTR_PACKAGE;
-import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
-import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
-import static com.android.SdkConstants.OLD_PROGUARD_FILE;
-import static com.android.SdkConstants.PROGUARD_CONFIG;
-import static com.android.SdkConstants.PROJECT_PROPERTIES;
-import static com.android.SdkConstants.RES_FOLDER;
-import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
-import static com.android.SdkConstants.TAG_USES_SDK;
-import static com.android.SdkConstants.VALUE_TRUE;
-import static com.android.sdklib.SdkVersionInfo.HIGHEST_KNOWN_API;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidArtifactOutput;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.Variant;
-import com.android.ide.common.repository.ResourceVisibilityLookup;
-import com.android.resources.Density;
-import com.android.resources.ResourceFolderType;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.tools.lint.client.api.CircularDependencyException;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.SdkInfo;
-import com.google.common.annotations.Beta;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Charsets;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A project contains information about an Android project being scanned for
- * Lint errors.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public class Project {
- protected final LintClient mClient;
- protected final File mDir;
- protected final File mReferenceDir;
- protected Configuration mConfiguration;
- protected String mPackage;
- protected int mBuildSdk = -1;
- protected IAndroidTarget mTarget;
-
- protected AndroidVersion mManifestMinSdk = AndroidVersion.DEFAULT;
- protected AndroidVersion mManifestTargetSdk = AndroidVersion.DEFAULT;
-
- protected boolean mLibrary;
- protected String mName;
- protected String mProguardPath;
- protected boolean mMergeManifests;
-
- /** The SDK info, if any */
- protected SdkInfo mSdkInfo;
-
- /**
- * If non null, specifies a non-empty list of specific files under this
- * project which should be checked.
- */
- protected List<File> mFiles;
- protected List<File> mProguardFiles;
- protected List<File> mGradleFiles;
- protected List<File> mManifestFiles;
- protected List<File> mJavaSourceFolders;
- protected List<File> mJavaClassFolders;
- protected List<File> mJavaLibraries;
- protected List<File> mTestSourceFolders;
- protected List<File> mResourceFolders;
- protected List<Project> mDirectLibraries;
- protected List<Project> mAllLibraries;
- protected boolean mReportIssues = true;
- protected Boolean mGradleProject;
- protected Boolean mSupportLib;
- protected Boolean mAppCompat;
- private Map<String, String> mSuperClassMap;
- private ResourceVisibilityLookup mResourceVisibility;
-
- /**
- * Creates a new {@link Project} for the given directory.
- *
- * @param client the tool running the lint check
- * @param dir the root directory of the project
- * @param referenceDir See {@link #getReferenceDir()}.
- * @return a new {@link Project}
- */
- @NonNull
- public static Project create(
- @NonNull LintClient client,
- @NonNull File dir,
- @NonNull File referenceDir) {
- return new Project(client, dir, referenceDir);
- }
-
- /**
- * Returns true if this project is a Gradle-based Android project
- *
- * @return true if this is a Gradle-based project
- */
- public boolean isGradleProject() {
- if (mGradleProject == null) {
- mGradleProject = mClient.isGradleProject(this);
- }
-
- return mGradleProject;
- }
-
- /**
- * Returns true if this project is an Android project.
- *
- * @return true if this project is an Android project.
- */
- @SuppressWarnings("MethodMayBeStatic")
- public boolean isAndroidProject() {
- return true;
- }
-
- /**
- * Returns the project model for this project if it corresponds to
- * a Gradle project. This is the case if {@link #isGradleProject()}
- * is true and {@link #isLibrary()} is false.
- *
- * @return the project model, or null
- */
- @Nullable
- public AndroidProject getGradleProjectModel() {
- return null;
- }
-
- /**
- * Returns the project model for this project if it corresponds to
- * a Gradle library. This is the case if both
- * {@link #isGradleProject()} and {@link #isLibrary()} return true.
- *
- * @return the project model, or null
- */
- @SuppressWarnings("UnusedDeclaration")
- @Nullable
- public AndroidLibrary getGradleLibraryModel() {
- return null;
- }
-
- /**
- * Returns the current selected variant, if any (and if the current project is a Gradle
- * project). This can be used by incremental lint rules to warn about problems in the current
- * context. Lint rules should however strive to perform cross variant analysis and warn about
- * problems in any configuration.
- *
- * @return the select variant, or null
- */
- @Nullable
- public Variant getCurrentVariant() {
- return null;
- }
-
- /** Creates a new Project. Use one of the factory methods to create. */
- protected Project(
- @NonNull LintClient client,
- @NonNull File dir,
- @NonNull File referenceDir) {
- mClient = client;
- mDir = dir;
- mReferenceDir = referenceDir;
- initialize();
- }
-
- protected void initialize() {
- // Default initialization: Use ADT/ant style project.properties file
- try {
- // Read properties file and initialize library state
- Properties properties = new Properties();
- File propFile = new File(mDir, PROJECT_PROPERTIES);
- if (propFile.exists()) {
- BufferedInputStream is = new BufferedInputStream(new FileInputStream(propFile));
- try {
- properties.load(is);
- String value = properties.getProperty(ANDROID_LIBRARY);
- mLibrary = VALUE_TRUE.equals(value);
- String proguardPath = properties.getProperty(PROGUARD_CONFIG);
- if (proguardPath != null) {
- mProguardPath = proguardPath;
- }
- mMergeManifests = VALUE_TRUE.equals(properties.getProperty(
- "manifestmerger.enabled")); //$NON-NLS-1$
- String target = properties.getProperty("target"); //$NON-NLS-1$
- if (target != null) {
- int index = target.lastIndexOf('-');
- if (index == -1) {
- index = target.lastIndexOf(':');
- }
- if (index != -1) {
- String versionString = target.substring(index + 1);
- try {
- mBuildSdk = Integer.parseInt(versionString);
- } catch (NumberFormatException nufe) {
- mClient.log(Severity.WARNING, null,
- "Unexpected build target format: %1$s", target);
- }
- }
- }
-
- for (int i = 1; i < 1000; i++) {
- String key = String.format(ANDROID_LIBRARY_REFERENCE_FORMAT, i);
- String library = properties.getProperty(key);
- if (library == null || library.isEmpty()) {
- // No holes in the numbering sequence is allowed
- break;
- }
-
- File libraryDir = new File(mDir, library).getCanonicalFile();
-
- if (mDirectLibraries == null) {
- mDirectLibraries = new ArrayList<Project>();
- }
-
- // Adjust the reference dir to be a proper prefix path of the
- // library dir
- File libraryReferenceDir = mReferenceDir;
- if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) {
- // Symlinks etc might have been resolved, so do those to
- // the reference dir as well
- libraryReferenceDir = libraryReferenceDir.getCanonicalFile();
- if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) {
- File file = libraryReferenceDir;
- while (file != null && !file.getPath().isEmpty()) {
- if (libraryDir.getPath().startsWith(file.getPath())) {
- libraryReferenceDir = file;
- break;
- }
- file = file.getParentFile();
- }
- }
- }
-
- try {
- Project libraryPrj = mClient.getProject(libraryDir,
- libraryReferenceDir);
- mDirectLibraries.add(libraryPrj);
- // By default, we don't report issues in inferred library projects.
- // The driver will set report = true for those library explicitly
- // requested.
- libraryPrj.setReportIssues(false);
- } catch (CircularDependencyException e) {
- e.setProject(this);
- e.setLocation(Location.create(propFile));
- throw e;
- }
- }
- } finally {
- try {
- Closeables.close(is, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- }
- }
- } catch (IOException ioe) {
- mClient.log(ioe, "Initializing project state");
- }
-
- if (mDirectLibraries != null) {
- mDirectLibraries = Collections.unmodifiableList(mDirectLibraries);
- } else {
- mDirectLibraries = Collections.emptyList();
- }
- }
-
- @Override
- public String toString() {
- return "Project [dir=" + mDir + ']';
- }
-
- @Override
- public int hashCode() {
- return mDir.hashCode();
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Project other = (Project) obj;
- return mDir.equals(other.mDir);
- }
-
- /**
- * Adds the given file to the list of files which should be checked in this
- * project. If no files are added, the whole project will be checked.
- *
- * @param file the file to be checked
- */
- public void addFile(@NonNull File file) {
- if (mFiles == null) {
- mFiles = new ArrayList<File>();
- }
- mFiles.add(file);
- }
-
- /**
- * The list of files to be checked in this project. If null, the whole
- * project should be checked.
- *
- * @return the subset of files to be checked, or null for the whole project
- */
- @Nullable
- public List<File> getSubset() {
- return mFiles;
- }
-
- /**
- * Returns the list of source folders for Java source files
- *
- * @return a list of source folders to search for .java files
- */
- @NonNull
- public List<File> getJavaSourceFolders() {
- if (mJavaSourceFolders == null) {
- if (isAospFrameworksProject(mDir)) {
- return Collections.singletonList(new File(mDir, "java")); //$NON-NLS-1$
- }
- if (isAospBuildEnvironment()) {
- String top = getAospTop();
- if (mDir.getAbsolutePath().startsWith(top)) {
- mJavaSourceFolders = getAospJavaSourcePath();
- return mJavaSourceFolders;
- }
- }
-
- mJavaSourceFolders = mClient.getJavaSourceFolders(this);
- }
-
- return mJavaSourceFolders;
- }
-
- /**
- * Returns the list of output folders for class files
- * @return a list of output folders to search for .class files
- */
- @NonNull
- public List<File> getJavaClassFolders() {
- if (mJavaClassFolders == null) {
- if (isAospFrameworksProject(mDir)) {
- File top = mDir.getParentFile().getParentFile().getParentFile();
- if (top != null) {
- File out = new File(top, "out");
- if (out.exists()) {
- String relative =
- "target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar";
- File jar = new File(out, relative.replace('/', File.separatorChar));
- if (jar.exists()) {
- mJavaClassFolders = Collections.singletonList(jar);
- return mJavaClassFolders;
- }
- }
- }
- }
- if (isAospBuildEnvironment()) {
- String top = getAospTop();
- if (mDir.getAbsolutePath().startsWith(top)) {
- mJavaClassFolders = getAospJavaClassPath();
- return mJavaClassFolders;
- }
- }
-
- mJavaClassFolders = mClient.getJavaClassFolders(this);
- }
- return mJavaClassFolders;
- }
-
- /**
- * Returns the list of Java libraries (typically .jar files) that this
- * project depends on. Note that this refers to jar libraries, not Android
- * library projects which are processed in a separate pass with their own
- * source and class folders.
- *
- * @return a list of .jar files (or class folders) that this project depends
- * on.
- */
- @NonNull
- public List<File> getJavaLibraries() {
- if (mJavaLibraries == null) {
- // AOSP builds already merge libraries and class folders into
- // the single classes.jar file, so these have already been processed
- // in getJavaClassFolders.
-
- mJavaLibraries = mClient.getJavaLibraries(this);
- }
-
- return mJavaLibraries;
- }
-
- /**
- * Returns the list of source folders for Java test source files
- *
- * @return a list of source folders to search for .java files
- */
- @NonNull
- public List<File> getTestSourceFolders() {
- if (mTestSourceFolders == null) {
- mTestSourceFolders = mClient.getTestSourceFolders(this);
- }
-
- return mTestSourceFolders;
- }
-
- /**
- * Returns the resource folder.
- *
- * @return a file pointing to the resource folder, or null if the project
- * does not contain any resources
- */
- @NonNull
- public List<File> getResourceFolders() {
- if (mResourceFolders == null) {
- List<File> folders = mClient.getResourceFolders(this);
-
- if (folders.size() == 1 && isAospFrameworksProject(mDir)) {
- // No manifest file for this project: just init the manifest values here
- mManifestMinSdk = mManifestTargetSdk = new AndroidVersion(HIGHEST_KNOWN_API, null);
- File folder = new File(folders.get(0), RES_FOLDER);
- if (!folder.exists()) {
- folders = Collections.emptyList();
- }
- }
-
- mResourceFolders = folders;
- }
-
- return mResourceFolders;
-
- }
-
- /**
- * Returns the relative path of a given file relative to the user specified
- * directory (which is often the project directory but sometimes a higher up
- * directory when a directory tree is being scanned
- *
- * @param file the file under this project to check
- * @return the path relative to the reference directory (often the project directory)
- */
- @NonNull
- public String getDisplayPath(@NonNull File file) {
- String path = file.getPath();
- String referencePath = mReferenceDir.getPath();
- if (path.startsWith(referencePath)) {
- int length = referencePath.length();
- if (path.length() > length && path.charAt(length) == File.separatorChar) {
- length++;
- }
-
- return path.substring(length);
- }
-
- return path;
- }
-
- /**
- * Returns the relative path of a given file within the current project.
- *
- * @param file the file under this project to check
- * @return the path relative to the project
- */
- @NonNull
- public String getRelativePath(@NonNull File file) {
- String path = file.getPath();
- String referencePath = mDir.getPath();
- if (path.startsWith(referencePath)) {
- int length = referencePath.length();
- if (path.length() > length && path.charAt(length) == File.separatorChar) {
- length++;
- }
-
- return path.substring(length);
- }
-
- return path;
- }
-
- /**
- * Returns the project root directory
- *
- * @return the dir
- */
- @NonNull
- public File getDir() {
- return mDir;
- }
-
- /**
- * Returns the original user supplied directory where the lint search
- * started. For example, if you run lint against {@code /tmp/foo}, and it
- * finds a project to lint in {@code /tmp/foo/dev/src/project1}, then the
- * {@code dir} is {@code /tmp/foo/dev/src/project1} and the
- * {@code referenceDir} is {@code /tmp/foo/}.
- *
- * @return the reference directory, never null
- */
- @NonNull
- public File getReferenceDir() {
- return mReferenceDir;
- }
-
- /**
- * Gets the configuration associated with this project
- *
- * @param driver the current driver, if any
- * @return the configuration associated with this project
- */
- @NonNull
- public Configuration getConfiguration(@Nullable LintDriver driver) {
- if (mConfiguration == null) {
- mConfiguration = mClient.getConfiguration(this, driver);
- }
- return mConfiguration;
- }
-
- /**
- * Returns the application package specified by the manifest
- *
- * @return the application package, or null if unknown
- */
- @Nullable
- public String getPackage() {
- //assert !mLibrary; // Should call getPackage on the master project, not the library
- // Assertion disabled because you might be running lint on a standalone library project.
-
- return mPackage;
- }
-
- /**
- * Returns the minimum API level for the project
- *
- * @return the minimum API level or {@link AndroidVersion#DEFAULT} if unknown
- */
- @NonNull
- public AndroidVersion getMinSdkVersion() {
- return mManifestMinSdk == null ? AndroidVersion.DEFAULT : mManifestMinSdk;
- }
-
- /**
- * Returns the minimum API <b>level</b> requested by the manifest, or -1 if not
- * specified. Use {@link #getMinSdkVersion()} to get a full version if you need
- * to check if the platform is a preview platform etc.
- *
- * @return the minimum API level or -1 if unknown
- */
- public int getMinSdk() {
- AndroidVersion version = getMinSdkVersion();
- return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel();
- }
-
- /**
- * Returns the target API level for the project
- *
- * @return the target API level or {@link AndroidVersion#DEFAULT} if unknown
- */
- @NonNull
- public AndroidVersion getTargetSdkVersion() {
- return mManifestTargetSdk == AndroidVersion.DEFAULT
- ? getMinSdkVersion() : mManifestTargetSdk;
- }
-
- /**
- * Returns the target API <b>level</b> specified by the manifest, or -1 if not
- * specified. Use {@link #getTargetSdkVersion()} to get a full version if you need
- * to check if the platform is a preview platform etc.
- *
- * @return the target API level or -1 if unknown
- */
- public int getTargetSdk() {
- AndroidVersion version = getTargetSdkVersion();
- return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel();
- }
-
- /**
- * Returns the target API used to build the project, or -1 if not known
- *
- * @return the build target API or -1 if unknown
- */
- public int getBuildSdk() {
- return mBuildSdk;
- }
-
- /**
- * Returns the target used to build the project, or null if not known
- *
- * @return the build target, or null
- */
- @Nullable
- public IAndroidTarget getBuildTarget() {
- if (mTarget == null) {
- mTarget = mClient.getCompileTarget(this);
- }
-
- return mTarget;
- }
-
- /**
- * Initialized the manifest state from the given manifest model
- *
- * @param document the DOM document for the manifest XML document
- */
- public void readManifest(@NonNull Document document) {
- Element root = document.getDocumentElement();
- if (root == null) {
- return;
- }
-
- mPackage = root.getAttribute(ATTR_PACKAGE);
-
- // Treat support libraries as non-reportable (in Eclipse where we don't
- // have binary libraries, the support libraries have to be source-copied into
- // the workspace which then triggers warnings in these libraries that users
- // shouldn't have to investigate)
- if (mPackage != null && mPackage.startsWith("android.support.")) {
- mReportIssues = false;
- }
-
- // Initialize minSdk and targetSdk
- mManifestMinSdk = AndroidVersion.DEFAULT;
- mManifestTargetSdk = AndroidVersion.DEFAULT;
- NodeList usesSdks = root.getElementsByTagName(TAG_USES_SDK);
- if (usesSdks.getLength() > 0) {
- Element element = (Element) usesSdks.item(0);
-
- String minSdk = null;
- if (element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
- minSdk = element.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
- }
- if (minSdk != null) {
- IAndroidTarget[] targets = mClient.getTargets();
- mManifestMinSdk = SdkVersionInfo.getVersion(minSdk, targets);
- }
-
- if (element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
- String targetSdk = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
- if (targetSdk != null) {
- IAndroidTarget[] targets = mClient.getTargets();
- mManifestTargetSdk = SdkVersionInfo.getVersion(targetSdk, targets);
- }
- } else {
- mManifestTargetSdk = mManifestMinSdk;
- }
- } else if (isAospBuildEnvironment()) {
- extractAospMinSdkVersion();
- mManifestTargetSdk = mManifestMinSdk;
- }
- }
-
- /**
- * Returns true if this project is an Android library project
- *
- * @return true if this project is an Android library project
- */
- public boolean isLibrary() {
- return mLibrary;
- }
-
- /**
- * Returns the list of library projects referenced by this project
- *
- * @return the list of library projects referenced by this project, never
- * null
- */
- @NonNull
- public List<Project> getDirectLibraries() {
- return mDirectLibraries != null ? mDirectLibraries : Collections.<Project>emptyList();
- }
-
- /**
- * Returns the transitive closure of the library projects for this project
- *
- * @return the transitive closure of the library projects for this project
- */
- @NonNull
- public List<Project> getAllLibraries() {
- if (mAllLibraries == null) {
- if (mDirectLibraries.isEmpty()) {
- return mDirectLibraries;
- }
-
- List<Project> all = new ArrayList<Project>();
- Set<Project> seen = Sets.newHashSet();
- Set<Project> path = Sets.newHashSet();
- seen.add(this);
- path.add(this);
- addLibraryProjects(all, seen, path);
- mAllLibraries = all;
- }
-
- return mAllLibraries;
- }
-
- /**
- * Adds this project's library project and their library projects
- * recursively into the given collection of projects
- *
- * @param collection the collection to add the projects into
- * @param seen full set of projects we've processed
- * @param path the current path of library dependencies followed
- */
- private void addLibraryProjects(@NonNull Collection<Project> collection,
- @NonNull Set<Project> seen, @NonNull Set<Project> path) {
- for (Project library : mDirectLibraries) {
- if (seen.contains(library)) {
- if (path.contains(library)) {
- mClient.log(Severity.WARNING, null,
- "Internal lint error: cyclic library dependency for %1$s", library);
- }
- continue;
- }
- collection.add(library);
- seen.add(library);
- path.add(library);
- // Recurse
- library.addLibraryProjects(collection, seen, path);
- path.remove(library);
- }
- }
-
- /**
- * Gets the SDK info for the current project.
- *
- * @return the SDK info for the current project, never null
- */
- @NonNull
- public SdkInfo getSdkInfo() {
- if (mSdkInfo == null) {
- mSdkInfo = mClient.getSdkInfo(this);
- }
-
- return mSdkInfo;
- }
-
- /**
- * Gets the paths to the manifest files in this project, if any exists. The manifests
- * should be provided such that the main manifest comes first, then any flavor versions,
- * then any build types.
- *
- * @return the path to the manifest file, or null if it does not exist
- */
- @NonNull
- public List<File> getManifestFiles() {
- if (mManifestFiles == null) {
- File manifestFile = new File(mDir, ANDROID_MANIFEST_XML);
- if (manifestFile.exists()) {
- mManifestFiles = Collections.singletonList(manifestFile);
- } else {
- mManifestFiles = Collections.emptyList();
- }
- }
-
- return mManifestFiles;
- }
-
- /**
- * Returns the proguard files configured for this project, if any
- *
- * @return the proguard files, if any
- */
- @NonNull
- public List<File> getProguardFiles() {
- if (mProguardFiles == null) {
- List<File> files = new ArrayList<File>();
- if (mProguardPath != null) {
- Splitter splitter = Splitter.on(CharMatcher.anyOf(":;")); //$NON-NLS-1$
- for (String path : splitter.split(mProguardPath)) {
- if (path.contains("${")) { //$NON-NLS-1$
- // Don't analyze the global/user proguard files
- continue;
- }
- File file = new File(path);
- if (!file.isAbsolute()) {
- file = new File(getDir(), path);
- }
- if (file.exists()) {
- files.add(file);
- }
- }
- }
- if (files.isEmpty()) {
- File file = new File(getDir(), OLD_PROGUARD_FILE);
- if (file.exists()) {
- files.add(file);
- }
- file = new File(getDir(), FN_PROJECT_PROGUARD_FILE);
- if (file.exists()) {
- files.add(file);
- }
- }
- mProguardFiles = files;
- }
- return mProguardFiles;
- }
-
- /**
- * Returns the Gradle build script files configured for this project, if any
- *
- * @return the Gradle files, if any
- */
- @NonNull
- public List<File> getGradleBuildScripts() {
- if (mGradleFiles == null) {
- if (isGradleProject()) {
- mGradleFiles = Lists.newArrayListWithExpectedSize(2);
- File build = new File(mDir, SdkConstants.FN_BUILD_GRADLE);
- if (build.exists()) {
- mGradleFiles.add(build);
- }
- File settings = new File(mDir, SdkConstants.FN_SETTINGS_GRADLE);
- if (settings.exists()) {
- mGradleFiles.add(settings);
- }
- } else {
- mGradleFiles = Collections.emptyList();
- }
- }
-
- return mGradleFiles;
- }
-
- /**
- * Returns the name of the project
- *
- * @return the name of the project, never null
- */
- @NonNull
- public String getName() {
- if (mName == null) {
- mName = mClient.getProjectName(this);
- }
-
- return mName;
- }
-
- /**
- * Sets the name of the project
- *
- * @param name the name of the project, never null
- */
- public void setName(@NonNull String name) {
- assert !name.isEmpty();
- mName = name;
- }
-
- /**
- * Sets whether lint should report issues in this project. See
- * {@link #getReportIssues()} for a full description of what that means.
- *
- * @param reportIssues whether lint should report issues in this project
- */
- public void setReportIssues(boolean reportIssues) {
- mReportIssues = reportIssues;
- }
-
- /**
- * Returns whether lint should report issues in this project.
- * <p>
- * If a user specifies a project and its library projects for analysis, then
- * those library projects are all "included", and all errors found in all
- * the projects are reported. But if the user is only running lint on the
- * main project, we shouldn't report errors in any of the library projects.
- * We still need to <b>consider</b> them for certain types of checks, such
- * as determining whether resources found in the main project are unused, so
- * the detectors must still get a chance to look at these projects. The
- * {@code #getReportIssues()} attribute is used for this purpose.
- *
- * @return whether lint should report issues in this project
- */
- public boolean getReportIssues() {
- return mReportIssues;
- }
-
- /**
- * Returns whether manifest merging is in effect
- *
- * @return true if manifests in library projects should be merged into main projects
- */
- public boolean isMergingManifests() {
- return mMergeManifests;
- }
-
-
- // ---------------------------------------------------------------------------
- // Support for running lint on the AOSP source tree itself
-
- private static Boolean sAospBuild;
-
- /** Is lint running in an AOSP build environment */
- private static boolean isAospBuildEnvironment() {
- if (sAospBuild == null) {
- sAospBuild = getAospTop() != null;
- }
-
- return sAospBuild;
- }
-
- /**
- * Is this the frameworks AOSP project? Needs some hardcoded support since
- * it doesn't have a manifest file, etc.
- *
- * @param dir the project directory to check
- * @return true if this looks like the frameworks/base/core project
- */
- public static boolean isAospFrameworksProject(@NonNull File dir) {
- if (!dir.getPath().endsWith("core")) { //$NON-NLS-1$
- return false;
- }
-
- File parent = dir.getParentFile();
- if (parent == null || !parent.getName().equals("base")) { //$NON-NLS-1$
- return false;
- }
-
- parent = parent.getParentFile();
- //noinspection RedundantIfStatement
- if (parent == null || !parent.getName().equals("frameworks")) { //$NON-NLS-1$
- return false;
- }
-
- return true;
- }
-
- /** Get the root AOSP dir, if any */
- private static String getAospTop() {
- return System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
- }
-
- /** Get the host out directory in AOSP, if any */
- private static String getAospHostOut() {
- return System.getenv("ANDROID_HOST_OUT"); //$NON-NLS-1$
- }
-
- /** Get the product out directory in AOSP, if any */
- private static String getAospProductOut() {
- return System.getenv("ANDROID_PRODUCT_OUT"); //$NON-NLS-1$
- }
-
- private List<File> getAospJavaSourcePath() {
- List<File> sources = new ArrayList<File>(2);
- // Normal sources
- File src = new File(mDir, "src"); //$NON-NLS-1$
- if (src.exists()) {
- sources.add(src);
- }
-
- // Generates sources
- for (File dir : getIntermediateDirs()) {
- File classes = new File(dir, "src"); //$NON-NLS-1$
- if (classes.exists()) {
- sources.add(classes);
- }
- }
-
- if (sources.isEmpty()) {
- mClient.log(null,
- "Warning: Could not find sources or generated sources for project %1$s",
- getName());
- }
-
- return sources;
- }
-
- private List<File> getAospJavaClassPath() {
- List<File> classDirs = new ArrayList<File>(1);
-
- for (File dir : getIntermediateDirs()) {
- File classes = new File(dir, "classes"); //$NON-NLS-1$
- if (classes.exists()) {
- classDirs.add(classes);
- } else {
- classes = new File(dir, "classes.jar"); //$NON-NLS-1$
- if (classes.exists()) {
- classDirs.add(classes);
- }
- }
- }
-
- if (classDirs.isEmpty()) {
- mClient.log(null,
- "No bytecode found: Has the project been built? (%1$s)", getName());
- }
-
- return classDirs;
- }
-
- /** Find the _intermediates directories for a given module name */
- private List<File> getIntermediateDirs() {
- // See build/core/definitions.mk and in particular the "intermediates-dir-for" definition
- List<File> intermediates = new ArrayList<File>();
-
- // TODO: Look up the module name, e.g. LOCAL_MODULE. However,
- // some Android.mk files do some complicated things with it - and most
- // projects use the same module name as the directory name.
- String moduleName = mDir.getName();
-
- String top = getAospTop();
- final String[] outFolders = new String[] {
- top + "/out/host/common/obj", //$NON-NLS-1$
- top + "/out/target/common/obj", //$NON-NLS-1$
- getAospHostOut() + "/obj", //$NON-NLS-1$
- getAospProductOut() + "/obj" //$NON-NLS-1$
- };
- final String[] moduleClasses = new String[] {
- "APPS", //$NON-NLS-1$
- "JAVA_LIBRARIES", //$NON-NLS-1$
- };
-
- for (String out : outFolders) {
- assert new File(out.replace('/', File.separatorChar)).exists() : out;
- for (String moduleClass : moduleClasses) {
- String path = out + '/' + moduleClass + '/' + moduleName
- + "_intermediates"; //$NON-NLS-1$
- File file = new File(path.replace('/', File.separatorChar));
- if (file.exists()) {
- intermediates.add(file);
- }
- }
- }
-
- return intermediates;
- }
-
- private void extractAospMinSdkVersion() {
- // Is the SDK level specified by a Makefile?
- boolean found = false;
- File makefile = new File(mDir, "Android.mk"); //$NON-NLS-1$
- if (makefile.exists()) {
- try {
- List<String> lines = Files.readLines(makefile, Charsets.UTF_8);
- Pattern p = Pattern.compile("LOCAL_SDK_VERSION\\s*:=\\s*(.*)"); //$NON-NLS-1$
- for (String line : lines) {
- line = line.trim();
- Matcher matcher = p.matcher(line);
- if (matcher.matches()) {
- found = true;
- String version = matcher.group(1);
- if (version.equals("current")) { //$NON-NLS-1$
- mManifestMinSdk = findCurrentAospVersion();
- } else {
- mManifestMinSdk = SdkVersionInfo.getVersion(version,
- mClient.getTargets());
- }
- break;
- }
- }
- } catch (IOException ioe) {
- mClient.log(ioe, null);
- }
- }
-
- if (!found) {
- mManifestMinSdk = findCurrentAospVersion();
- }
- }
-
- /** Cache for {@link #findCurrentAospVersion()} */
- private static AndroidVersion sCurrentVersion;
-
- /** In an AOSP build environment, identify the currently built image version, if available */
- private static AndroidVersion findCurrentAospVersion() {
- if (sCurrentVersion == null) {
- File apiDir = new File(getAospTop(), "frameworks/base/api" //$NON-NLS-1$
- .replace('/', File.separatorChar));
- File[] apiFiles = apiDir.listFiles();
- if (apiFiles == null) {
- sCurrentVersion = AndroidVersion.DEFAULT;
- return sCurrentVersion;
- }
- int max = 1;
- for (File apiFile : apiFiles) {
- String name = apiFile.getName();
- int index = name.indexOf('.');
- if (index > 0) {
- String base = name.substring(0, index);
- if (Character.isDigit(base.charAt(0))) {
- try {
- int version = Integer.parseInt(base);
- if (version > max) {
- max = version;
- }
- } catch (NumberFormatException nufe) {
- // pass
- }
- }
- }
- }
- sCurrentVersion = new AndroidVersion(max, null);
- }
-
- return sCurrentVersion;
- }
-
- /**
- * Returns true if this project depends on the given artifact. Note that
- * the project doesn't have to be a Gradle project; the artifact is just
- * an identifier for name a specific library, such as com.android.support:support-v4
- * to identify the support library
- *
- * @param artifact the Gradle/Maven name of a library
- * @return true if the library is installed, false if it is not, and null if
- * we're not sure
- */
- @Nullable
- public Boolean dependsOn(@NonNull String artifact) {
- if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
- if (mSupportLib == null) {
- for (File file : getJavaLibraries()) {
- String name = file.getName();
- if (name.equals("android-support-v4.jar") //$NON-NLS-1$
- || name.startsWith("support-v4-")) { //$NON-NLS-1$
- mSupportLib = true;
- break;
- }
- }
- if (mSupportLib == null) {
- for (Project dependency : getDirectLibraries()) {
- Boolean b = dependency.dependsOn(artifact);
- if (b != null && b) {
- mSupportLib = true;
- break;
- }
- }
- }
- if (mSupportLib == null) {
- mSupportLib = false;
- }
- }
-
- return mSupportLib;
- } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
- if (mAppCompat == null) {
- for (File file : getJavaLibraries()) {
- String name = file.getName();
- if (name.startsWith("appcompat-v7-")) { //$NON-NLS-1$
- mAppCompat = true;
- break;
- }
- }
- if (mAppCompat == null) {
- for (Project dependency : getDirectLibraries()) {
- Boolean b = dependency.dependsOn(artifact);
- if (b != null && b) {
- mAppCompat = true;
- break;
- }
- }
- }
- if (mAppCompat == null) {
- mAppCompat = false;
- }
- }
-
- return mAppCompat;
- }
-
- return null;
- }
-
- private List<String> mCachedApplicableDensities;
-
- /**
- * Returns the set of applicable densities for this project. If null, there are no density
- * restrictions and all densities apply.
- *
- * @return the list of specific densities that apply in this project, or null if all densities
- * apply
- */
- @Nullable
- public List<String> getApplicableDensities() {
- if (mCachedApplicableDensities == null) {
- // Use the gradle API to set up relevant densities. For example, if the
- // build.gradle file contains this:
- // android {
- // defaultConfig {
- // resConfigs "nodpi", "hdpi"
- // }
- // }
- // ...then we should only enforce hdpi densities, not all these others!
- if (isGradleProject() && getGradleProjectModel() != null &&
- getCurrentVariant() != null) {
- Set<String> relevantDensities = Sets.newHashSet();
- Variant variant = getCurrentVariant();
- List<String> variantFlavors = variant.getProductFlavors();
- AndroidProject gradleProjectModel = getGradleProjectModel();
-
- addResConfigsFromFlavor(relevantDensities, null,
- getGradleProjectModel().getDefaultConfig());
- for (ProductFlavorContainer container : gradleProjectModel.getProductFlavors()) {
- addResConfigsFromFlavor(relevantDensities, variantFlavors, container);
- }
-
- // Are there any splits that specify densities?
- if (relevantDensities.isEmpty()) {
- AndroidArtifact mainArtifact = variant.getMainArtifact();
- Collection<AndroidArtifactOutput> outputs = mainArtifact.getOutputs();
- for (AndroidArtifactOutput output : outputs) {
- for (OutputFile file : output.getOutputs()) {
- final String DENSITY_NAME = OutputFile.FilterType.DENSITY.name();
- if (file.getFilterTypes().contains(DENSITY_NAME)) {
- for (FilterData data : file.getFilters()) {
- if (DENSITY_NAME.equals(data.getFilterType())) {
- relevantDensities.add(data.getIdentifier());
- }
- }
- }
- }
- }
- }
-
- if (!relevantDensities.isEmpty()) {
- mCachedApplicableDensities = Lists.newArrayListWithExpectedSize(10);
- for (String density : relevantDensities) {
- String folder = ResourceFolderType.DRAWABLE.getName() + '-' + density;
- mCachedApplicableDensities.add(folder);
- }
- Collections.sort(mCachedApplicableDensities);
- } else {
- mCachedApplicableDensities = Collections.emptyList();
- }
- } else {
- mCachedApplicableDensities = Collections.emptyList();
- }
- }
-
- return mCachedApplicableDensities.isEmpty() ? null : mCachedApplicableDensities;
- }
-
- /**
- * Returns a super class map for this project. The keys and values are internal
- * class names (e.g. java/lang/Integer, not java.lang.Integer).
- * @return a map, possibly empty but never null
- */
- @NonNull
- public Map<String, String> getSuperClassMap() {
- if (mSuperClassMap == null) {
- mSuperClassMap = mClient.createSuperClassMap(this);
- }
-
- return mSuperClassMap;
- }
-
- /**
- * Adds in the resConfig values specified by the given flavor container, assuming
- * it's in one of the relevant variantFlavors, into the given set
- */
- private static void addResConfigsFromFlavor(@NonNull Set<String> relevantDensities,
- @Nullable List<String> variantFlavors,
- @NonNull ProductFlavorContainer container) {
- ProductFlavor flavor = container.getProductFlavor();
- if (variantFlavors == null || variantFlavors.contains(flavor.getName())) {
- if (!flavor.getResourceConfigurations().isEmpty()) {
- for (String densityName : flavor.getResourceConfigurations()) {
- Density density = Density.getEnum(densityName);
- if (density != null && density.isRecommended()
- && density != Density.NODPI && density != Density.ANYDPI) {
- relevantDensities.add(densityName);
- }
- }
- }
- }
- }
-
- /**
- * Returns a shared {@link ResourceVisibilityLookup}
- *
- * @return a shared provider for looking up resource visibility
- */
- @NonNull
- public ResourceVisibilityLookup getResourceVisibility() {
- if (mResourceVisibility == null) {
- if (isGradleProject()) {
- AndroidProject project = getGradleProjectModel();
- Variant variant = getCurrentVariant();
- if (project != null && variant != null) {
- mResourceVisibility = mClient.getResourceVisibilityProvider().get(project,
- variant);
-
- } else if (getGradleLibraryModel() != null) {
- try {
- mResourceVisibility = mClient.getResourceVisibilityProvider()
- .get(getGradleLibraryModel());
- } catch (Exception ignore) {
- // Handle talking to older Gradle plugins (where we don't
- // have access to the model version to check up front
- }
- }
- }
- if (mResourceVisibility == null) {
- mResourceVisibility = ResourceVisibilityLookup.NONE;
- }
- }
-
- return mResourceVisibility;
- }
-
- /**
- * Returns the associated client
- *
- * @return the client
- */
- @NonNull
- public LintClient getClient() {
- return mClient;
- }
-
- /**
- * Returns the compile target to use for this project
- *
- * @return the compile target to use to build this project
- */
- @Nullable
- public IAndroidTarget getCompileTarget() {
- return mClient.getCompileTarget(this);
- }
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
deleted file mode 100644
index 55dd778..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.DOT_CLASS;
-import static com.android.SdkConstants.DOT_GRADLE;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.DOT_PROPERTIES;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
-import static com.android.SdkConstants.OLD_PROGUARD_FILE;
-import static com.android.SdkConstants.RES_FOLDER;
-
-import com.android.annotations.NonNull;
-import com.google.common.annotations.Beta;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-
-/**
- * The scope of a detector is the set of files a detector must consider when
- * performing its analysis. This can be used to determine when issues are
- * potentially obsolete, whether a detector should re-run on a file save, etc.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
-public enum Scope {
- /**
- * The analysis only considers a single XML resource file at a time.
- * <p>
- * Issues which are only affected by a single resource file can be checked
- * for incrementally when a file is edited.
- */
- RESOURCE_FILE,
-
- /**
- * The analysis only considers a single binary (typically a bitmap) resource file at a time.
- * <p>
- * Issues which are only affected by a single resource file can be checked
- * for incrementally when a file is edited.
- */
- BINARY_RESOURCE_FILE,
-
- /**
- * The analysis considers the resource folders
- */
- RESOURCE_FOLDER,
-
- /**
- * The analysis considers <b>all</b> the resource file. This scope must not
- * be used in conjunction with {@link #RESOURCE_FILE}; an issue scope is
- * either considering just a single resource file or all the resources, not
- * both.
- */
- ALL_RESOURCE_FILES,
-
- /**
- * The analysis only considers a single Java source file at a time.
- * <p>
- * Issues which are only affected by a single Java source file can be
- * checked for incrementally when a Java source file is edited.
- */
- JAVA_FILE,
-
- /**
- * The analysis considers <b>all</b> the Java source files together.
- * <p>
- * This flag is mutually exclusive with {@link #JAVA_FILE}.
- */
- ALL_JAVA_FILES,
-
- /**
- * The analysis only considers a single Java class file at a time.
- * <p>
- * Issues which are only affected by a single Java class file can be checked
- * for incrementally when a Java source file is edited and then recompiled.
- */
- CLASS_FILE,
-
- /**
- * The analysis considers <b>all</b> the Java class files together.
- * <p>
- * This flag is mutually exclusive with {@link #CLASS_FILE}.
- */
- ALL_CLASS_FILES,
-
- /** The analysis considers the manifest file */
- MANIFEST,
-
- /** The analysis considers the Proguard configuration file */
- PROGUARD_FILE,
-
- /**
- * The analysis considers classes in the libraries for this project. These
- * will be analyzed before the classes themselves.
- */
- JAVA_LIBRARIES,
-
- /** The analysis considers a Gradle build file */
- GRADLE_FILE,
-
- /** The analysis considers Java property files */
- PROPERTY_FILE,
-
- /** The analysis considers test sources as well */
- TEST_SOURCES,
-
- /**
- * Scope for other files. Issues that specify a custom scope will be called unconditionally.
- * This will call {@link Detector#run(Context)}} on the detectors unconditionally.
- */
- OTHER;
-
- /**
- * Returns true if the given scope set corresponds to scanning a single file
- * rather than a whole project
- *
- * @param scopes the scope set to check
- * @return true if the scope set references a single file
- */
- public static boolean checkSingleFile(@NonNull EnumSet<Scope> scopes) {
- int size = scopes.size();
- if (size == 2) {
- // When single checking a Java source file, we check both its Java source
- // and the associated class files
- return scopes.contains(JAVA_FILE) && scopes.contains(CLASS_FILE);
- } else {
- return size == 1 &&
- (scopes.contains(JAVA_FILE)
- || scopes.contains(CLASS_FILE)
- || scopes.contains(RESOURCE_FILE)
- || scopes.contains(PROGUARD_FILE)
- || scopes.contains(PROPERTY_FILE)
- || scopes.contains(GRADLE_FILE)
- || scopes.contains(MANIFEST));
- }
- }
-
- /**
- * Returns the intersection of two scope sets
- *
- * @param scope1 the first set to intersect
- * @param scope2 the second set to intersect
- * @return the intersection of the two sets
- */
- @NonNull
- public static EnumSet<Scope> intersect(
- @NonNull EnumSet<Scope> scope1,
- @NonNull EnumSet<Scope> scope2) {
- EnumSet<Scope> scope = EnumSet.copyOf(scope1);
- scope.retainAll(scope2);
-
- return scope;
- }
-
- /**
- * Infers a suitable scope to use from the given projects to be analyzed
- * @param projects the projects to find a suitable scope for
- * @return the scope to use
- */
- @NonNull
- public static EnumSet<Scope> infer(@NonNull Collection<Project> projects) {
- // Infer the scope
- EnumSet<Scope> scope = EnumSet.noneOf(Scope.class);
- for (Project project : projects) {
- List<File> subset = project.getSubset();
- if (subset != null) {
- for (File file : subset) {
- String name = file.getName();
- if (name.equals(ANDROID_MANIFEST_XML)) {
- scope.add(MANIFEST);
- } else if (name.endsWith(DOT_XML)) {
- scope.add(RESOURCE_FILE);
- } else if (name.endsWith(DOT_JAVA)) {
- scope.add(JAVA_FILE);
- } else if (name.endsWith(DOT_CLASS)) {
- scope.add(CLASS_FILE);
- } else if (name.endsWith(DOT_GRADLE)) {
- scope.add(GRADLE_FILE);
- } else if (name.equals(OLD_PROGUARD_FILE)
- || name.equals(FN_PROJECT_PROGUARD_FILE)) {
- scope.add(PROGUARD_FILE);
- } else if (name.endsWith(DOT_PROPERTIES)) {
- scope.add(PROPERTY_FILE);
- } else if (name.endsWith(DOT_PNG)) {
- scope.add(BINARY_RESOURCE_FILE);
- } else if (name.equals(RES_FOLDER)
- || file.getParent().equals(RES_FOLDER)) {
- scope.add(ALL_RESOURCE_FILES);
- scope.add(RESOURCE_FILE);
- scope.add(BINARY_RESOURCE_FILE);
- scope.add(RESOURCE_FOLDER);
- }
- }
- } else {
- // Specified a full project: just use the full project scope
- scope = Scope.ALL;
- break;
- }
- }
-
- return scope;
- }
-
- /** All scopes: running lint on a project will check these scopes */
- public static final EnumSet<Scope> ALL = EnumSet.allOf(Scope.class);
- /** Scope-set used for detectors which are affected by a single resource file */
- public static final EnumSet<Scope> RESOURCE_FILE_SCOPE = EnumSet.of(RESOURCE_FILE);
- /** Scope-set used for detectors which are affected by a single resource folder */
- public static final EnumSet<Scope> RESOURCE_FOLDER_SCOPE = EnumSet.of(RESOURCE_FOLDER);
- /** Scope-set used for detectors which scan all resources */
- public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
- /** Scope-set used for detectors which are affected by a single Java source file */
- public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE);
- /** Scope-set used for detectors which are affected by a single Java class file */
- public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE);
- /** Scope-set used for detectors which are affected by a single Gradle build file */
- public static final EnumSet<Scope> GRADLE_SCOPE = EnumSet.of(GRADLE_FILE);
- /** Scope-set used for detectors which are affected by the manifest only */
- public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST);
- /** Scope-set used for detectors which correspond to some other context */
- public static final EnumSet<Scope> OTHER_SCOPE = EnumSet.of(OTHER);
- /** Scope-set used for detectors which are affected by a single ProGuard class file */
- public static final EnumSet<Scope> PROGUARD_SCOPE = EnumSet.of(PROGUARD_FILE);
- /** Scope-set used for detectors which correspond to property files */
- public static final EnumSet<Scope> PROPERTY_SCOPE = EnumSet.of(PROPERTY_FILE);
- /** Resource XML files and manifest files */
- public static final EnumSet<Scope> MANIFEST_AND_RESOURCE_SCOPE =
- EnumSet.of(Scope.MANIFEST, Scope.RESOURCE_FILE);
- /** Scope-set used for detectors which are affected by single XML and Java source files */
- public static final EnumSet<Scope> JAVA_AND_RESOURCE_FILES =
- EnumSet.of(RESOURCE_FILE, JAVA_FILE);
- /** Scope-set used for analyzing individual class files and all resource files */
- public static final EnumSet<Scope> CLASS_AND_ALL_RESOURCE_FILES =
- EnumSet.of(ALL_RESOURCE_FILES, CLASS_FILE);
- /** Scope-set used for analyzing all class files, including those in libraries */
- public static final EnumSet<Scope> ALL_CLASSES_AND_LIBRARIES =
- EnumSet.of(Scope.ALL_CLASS_FILES, Scope.JAVA_LIBRARIES);
- /** Scope-set used for detectors which are affected by Java libraries */
- public static final EnumSet<Scope> JAVA_LIBRARY_SCOPE = EnumSet.of(JAVA_LIBRARIES);
- /** Scope-set used for detectors which are affected by a single binary resource file */
- public static final EnumSet<Scope> BINARY_RESOURCE_FILE_SCOPE =
- EnumSet.of(BINARY_RESOURCE_FILE);
-}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java b/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java
deleted file mode 100644
index a66b326..0000000
--- a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.utils.SdkUtils;
-import com.android.utils.XmlUtils;
-
-/**
- * Lint error message, issue explanations and location descriptions
- * are described in a {@link #RAW} format which looks similar to text
- * but which can contain bold, symbols and links. These issues can
- * also be converted to plain text and to HTML markup, using the
- * {@link #convertTo(String, TextFormat)} method.
- *
- * @see Issue#getDescription(TextFormat)
- * @see Issue#getExplanation(TextFormat)
- * @see Issue#getBriefDescription(TextFormat)
- */
-public enum TextFormat {
- /**
- * Raw output format which is similar to text but allows some markup:
- * <ul>
- * <li>HTTP urls (http://...)
- * <li>Sentences immediately surrounded by * will be shown as bold.
- * <li>Sentences immediately surrounded by ` will be shown using monospace
- * fonts
- * </ul>
- * Furthermore, newlines are converted to br's when converting newlines.
- * Note: It does not insert {@code <html>} tags around the fragment for HTML output.
- * <p>
- * TODO: Consider switching to the restructured text format -
- * http://docutils.sourceforge.net/docs/user/rst/quickstart.html
- */
- RAW,
-
- /**
- * Plain text output
- */
- TEXT,
-
- /**
- * HTML formatted output (note: does not include surrounding {@code <html></html>} tags)
- */
- HTML;
-
- /**
- * Converts the given text to HTML
- *
- * @param text the text to format
- * @return the corresponding text formatted as HTML
- */
- @NonNull
- public String toHtml(@NonNull String text) {
- return convertTo(text, HTML);
- }
-
- /**
- * Converts the given text to plain text
- *
- * @param text the tetx to format
- * @return the corresponding text formatted as HTML
- */
- @NonNull
- public String toText(@NonNull String text) {
- return convertTo(text, TEXT);
- }
-
- /**
- * Converts the given message to the given format. Note that some
- * conversions are lossy; e.g. once converting away from the raw format
- * (which contains all the markup) you can't convert back to it.
- * Note that you can convert to the format it's already in; that just
- * returns the same string.
- *
- * @param message the message to convert
- * @param to the format to convert to
- * @return a converted message
- */
- public String convertTo(@NonNull String message, @NonNull TextFormat to) {
- if (this == to) {
- return message;
- }
- switch (this) {
- case RAW: {
- switch (to) {
- case RAW:
- return message;
- case TEXT:
- case HTML:
- return to.fromRaw(message);
- }
- }
- case TEXT: {
- switch (to) {
- case TEXT:
- case RAW:
- return message;
- case HTML:
- return XmlUtils.toXmlTextValue(message);
- }
- }
- case HTML: {
- switch (to) {
- case HTML:
- return message;
- case RAW:
- case TEXT: {
- return to.fromHtml(message);
-
- }
- }
- }
- }
- return message;
- }
-
- /** Converts to this output format from the given HTML-format text */
- @NonNull
- private String fromHtml(@NonNull String html) {
- assert this == RAW || this == TEXT : this;
-
- // Drop all tags; replace all entities, insert newlines
- // (this won't do wrapping)
- StringBuilder sb = new StringBuilder(html.length());
- for (int i = 0, n = html.length(); i < n; i++) {
- char c = html.charAt(i);
- if (c == '<') {
- // Scan forward to the end
- if (html.startsWith("<br>", i) ||
- html.startsWith("<br />", i) ||
- html.startsWith("<BR>", i) ||
- html.startsWith("<BR />", i)) {
- sb.append('\n');
- } else if (html.startsWith("<!--")) {
- i = Math.max(i, html.indexOf("-->", i));
- }
- i = html.indexOf('>', i);
- } else if (c == '&') {
- int end = html.indexOf(';', i);
- if (end > i) {
- String entity = html.substring(i, end + 1);
- sb.append(XmlUtils.fromXmlAttributeValue(entity));
- i = end;
- } else {
- sb.append(c);
- }
- } else if (c == '\n') {
- sb.append(' ');
- } else {
- sb.append(c);
- }
- }
-
- // Collapse repeated spaces
- String s = sb.toString();
- sb.setLength(0);
- boolean wasSpace = false;
- for (int i = 0, n = s.length(); i < n; i++) {
- char c = s.charAt(i);
- if (c == '\t') { // we keep newlines; came from <br>'s
- c = ' ';
- }
- boolean isSpace = c == ' ';
- if (!isSpace || !wasSpace) {
- wasSpace = isSpace;
- sb.append(c);
- }
- }
- s = sb.toString();
-
- // Line-wrap
- s = SdkUtils.wrap(s, 60, null);
-
- return s;
- }
-
- private static final String HTTP_PREFIX = "http://"; //$NON-NLS-1$
-
- /** Converts to this output format from the given raw-format text */
- @NonNull
- private String fromRaw(@NonNull String text) {
- assert this == HTML || this == TEXT : this;
- StringBuilder sb = new StringBuilder(3 * text.length() / 2);
- boolean html = this == HTML;
-
- char prev = 0;
- int flushIndex = 0;
- int n = text.length();
- for (int i = 0; i < n; i++) {
- char c = text.charAt(i);
- if ((c == '*' || c == '`') && i < n - 1) {
- // Scout ahead for range end
- if (!Character.isLetterOrDigit(prev)
- && !Character.isWhitespace(text.charAt(i + 1))) {
- // Found * or ` immediately before a letter, and not in the middle of a word
- // Find end
- int end = text.indexOf(c, i + 1);
- if (end != -1 && (end == n - 1 || !Character.isLetter(text.charAt(end + 1)))) {
- if (i > flushIndex) {
- appendEscapedText(sb, text, html, flushIndex, i);
- }
- if (html) {
- String tag = c == '*' ? "b" : "code"; //$NON-NLS-1$ //$NON-NLS-2$
- sb.append('<').append(tag).append('>');
- appendEscapedText(sb, text, html, i + 1, end);
- sb.append('<').append('/').append(tag).append('>');
- } else {
- appendEscapedText(sb, text, html, i + 1, end);
- }
- flushIndex = end + 1;
- i = flushIndex - 1; // -1: account for the i++ in the loop
- }
- }
- } else if (html && c == 'h' && i < n - 1 && text.charAt(i + 1) == 't'
- && text.startsWith(HTTP_PREFIX, i) && !Character.isLetterOrDigit(prev)) {
- // Find url end
- int end = i + HTTP_PREFIX.length();
- while (end < n) {
- char d = text.charAt(end);
- if (Character.isWhitespace(d)) {
- break;
- }
- end++;
- }
- char last = text.charAt(end - 1);
- if (last == '.' || last == ')' || last == '!') {
- end--;
- }
- if (end > i + HTTP_PREFIX.length()) {
- if (i > flushIndex) {
- appendEscapedText(sb, text, html, flushIndex, i);
- }
-
- String url = text.substring(i, end);
- sb.append("<a href=\""); //$NON-NLS-1$
- sb.append(url);
- sb.append('"').append('>');
- sb.append(url);
- sb.append("</a>"); //$NON-NLS-1$
-
- flushIndex = end;
- i = flushIndex - 1; // -1: account for the i++ in the loop
- }
- }
- prev = c;
- }
-
- if (flushIndex < n) {
- appendEscapedText(sb, text, html, flushIndex, n);
- }
-
- return sb.toString();
- }
-
- private static void appendEscapedText(@NonNull StringBuilder sb, @NonNull String text,
- boolean html, int start, int end) {
- if (html) {
- for (int i = start; i < end; i++) {
- char c = text.charAt(i);
- if (c == '<') {
- sb.append("<"); //$NON-NLS-1$
- } else if (c == '&') {
- sb.append("&"); //$NON-NLS-1$
- } else if (c == '\n') {
- sb.append("<br/>\n");
- } else {
- if (c > 255) {
- sb.append("&#"); //$NON-NLS-1$
- sb.append(Integer.toString(c));
- sb.append(';');
- } else if (c == '\u00a0') {
- sb.append(" "); //$NON-NLS-1$
- } else {
- sb.append(c);
- }
- }
- }
- } else {
- for (int i = start; i < end; i++) {
- char c = text.charAt(i);
- sb.append(c);
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
deleted file mode 100644
index 9cd147f..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_VALUE;
-import static com.android.SdkConstants.FQCN_SUPPRESS_LINT;
-import static com.android.SdkConstants.INT_DEF_ANNOTATION;
-import static com.android.SdkConstants.SUPPRESS_LINT;
-import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
-import static com.android.tools.lint.detector.api.JavaContext.findSurroundingClass;
-import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationDeclaration;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationValue;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.AstVisitor;
-import lombok.ast.Block;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.TypeBody;
-import lombok.ast.TypeMember;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Checks annotations to make sure they are valid
- */
-public class AnnotationDetector extends Detector implements Detector.JavaScanner {
-
- public static final Implementation IMPLEMENTATION = new Implementation(
- AnnotationDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Placing SuppressLint on a local variable doesn't work for class-file based checks */
- public static final Issue INSIDE_METHOD = Issue.create(
- "LocalSuppress", //$NON-NLS-1$
- "@SuppressLint on invalid element",
-
- "The `@SuppressAnnotation` is used to suppress Lint warnings in Java files. However, " +
- "while many lint checks analyzes the Java source code, where they can find " +
- "annotations on (for example) local variables, some checks are analyzing the " +
- "`.class` files. And in class files, annotations only appear on classes, fields " +
- "and methods. Annotations placed on local variables disappear. If you attempt " +
- "to suppress a lint error for a class-file based lint check, the suppress " +
- "annotation not work. You must move the annotation out to the surrounding method.",
-
- Category.CORRECTNESS,
- 3,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** IntDef annotations should be unique */
- public static final Issue UNIQUE = Issue.create(
- "UniqueConstants", //$NON-NLS-1$
- "Overlapping Enumeration Constants",
-
- "The `@IntDef` annotation allows you to " +
- "create a light-weight \"enum\" or type definition. However, it's possible to " +
- "accidentally specify the same value for two or more of the values, which can " +
- "lead to hard-to-detect bugs. This check looks for this scenario and flags any " +
- "repeated constants.\n" +
- "\n" +
- "In some cases, the repeated constant is intentional (for example, renaming a " +
- "constant to a more intuitive name, and leaving the old name in place for " +
- "compatibility purposes.) In that case, simply suppress this check by adding a " +
- "`@SuppressLint(\"UniqueConstants\")` annotation.",
-
- Category.CORRECTNESS,
- 3,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Flags should typically be specified as bit shifts */
- public static final Issue FLAG_STYLE = Issue.create(
- "ShiftFlags", //$NON-NLS-1$
- "Dangerous Flag Constant Declaration",
-
- "When defining multiple constants for use in flags, the recommended style is " +
- "to use the form `1 << 2`, `1 << 3`, `1 << 4` and so on to ensure that the " +
- "constants are unique and non-overlapping.",
-
- Category.CORRECTNESS,
- 3,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Constructs a new {@link AnnotationDetector} check */
- public AnnotationDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>>singletonList(Annotation.class);
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new AnnotationChecker(context);
- }
-
- private static class AnnotationChecker extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public AnnotationChecker(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitAnnotation(Annotation node) {
- String type = node.astAnnotationTypeReference().getTypeName();
- if (SUPPRESS_LINT.equals(type) || FQCN_SUPPRESS_LINT.equals(type)) {
- Node parent = node.getParent();
- if (parent instanceof Modifiers) {
- parent = parent.getParent();
- if (parent instanceof VariableDefinition) {
- for (AnnotationElement element : node.astElements()) {
- AnnotationValue valueNode = element.astValue();
- if (valueNode == null) {
- continue;
- }
- if (valueNode instanceof StringLiteral) {
- StringLiteral literal = (StringLiteral) valueNode;
- String id = literal.astValue();
- if (!checkId(node, id)) {
- return super.visitAnnotation(node);
- }
- } else if (valueNode instanceof ArrayInitializer) {
- ArrayInitializer array = (ArrayInitializer) valueNode;
- StrictListAccessor<Expression, ArrayInitializer> expressions =
- array.astExpressions();
- if (expressions == null) {
- continue;
- }
- for (Expression arrayElement : expressions) {
- if (arrayElement instanceof StringLiteral) {
- String id = ((StringLiteral) arrayElement).astValue();
- if (!checkId(node, id)) {
- return super.visitAnnotation(node);
- }
- }
- }
- }
- }
- }
- }
- } else if (INT_DEF_ANNOTATION.equals(type) || "IntDef".equals(type)) {
- // Make sure that all the constants are unique
- ResolvedNode resolved = mContext.resolve(node);
- if (resolved instanceof ResolvedAnnotation) {
- ensureUniqueValues(((ResolvedAnnotation)resolved), node);
- }
- }
-
- return super.visitAnnotation(node);
- }
-
- private void ensureUniqueValues(@NonNull ResolvedAnnotation annotation,
- @NonNull Annotation node) {
- Object allowed = annotation.getValue();
- if (allowed instanceof Object[]) {
- Object[] allowedValues = (Object[]) allowed;
- Map<Number,Integer> valueToIndex =
- Maps.newHashMapWithExpectedSize(allowedValues.length);
-
- List<Node> constants = null;
- for (AnnotationElement element : node.astElements()) {
- if (element.astName() == null
- || ATTR_VALUE.equals(element.astName().astValue())) {
- AnnotationValue value = element.astValue();
- if (value instanceof ArrayInitializer) {
- ArrayInitializer initializer = (ArrayInitializer)value;
- constants = Lists.newArrayListWithExpectedSize(allowedValues.length);
- for (Expression expression : initializer.astExpressions()) {
- constants.add(expression);
- }
- }
- break;
- }
- }
- if (constants != null) {
- if (constants.size() != allowedValues.length) {
- constants = null;
- } else {
- boolean flag = annotation.getValue(TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE;
- if (flag) {
- ensureUsingFlagStyle(constants);
- }
- }
- }
-
- for (int index = 0; index < allowedValues.length; index++) {
- Object o = allowedValues[index];
- if (o instanceof Number) {
- Number number = (Number)o;
- if (valueToIndex.containsKey(number)) {
- @SuppressWarnings("UnnecessaryLocalVariable")
- Number repeatedValue = number;
-
- Location location;
- String message;
- if (constants != null) {
- Node constant = constants.get(index);
- int prevIndex = valueToIndex.get(number);
- Node prevConstant = constants.get(prevIndex);
- message = String.format(
- "Constants `%1$s` and `%2$s` specify the same exact "
- + "value (%3$s); this is usually a cut & paste or "
- + "merge error",
- constant.toString(), prevConstant.toString(),
- repeatedValue.toString());
- location = mContext.getLocation(constant);
- Location secondary = mContext.getLocation(prevConstant);
- secondary.setMessage("Previous same value");
- location.setSecondary(secondary);
- } else {
- message = String.format(
- "More than one constant specifies the same exact "
- + "value (%1$s); this is usually a cut & paste or"
- + "merge error",
- repeatedValue.toString());
- location = mContext.getLocation(node);
- }
- Node scope = getAnnotationScope(node);
- mContext.report(UNIQUE, scope, location, message);
- break;
- }
- valueToIndex.put(number, index);
- }
- }
- }
- }
-
- @NonNull
- private static List<VariableDefinitionEntry> findDeclarations(
- @Nullable ClassDeclaration cls,
- @NonNull List<VariableReference> references) {
- if (cls == null) {
- return Collections.emptyList();
- }
- Map<String, VariableReference> referenceMap = Maps.newHashMap();
- for (VariableReference reference : references) {
- String name = reference.astIdentifier().astValue();
- referenceMap.put(name, reference);
- }
- List<VariableDefinitionEntry> declarations = Lists.newArrayList();
- for (TypeMember member : cls.astBody().astMembers()) {
- if (member instanceof VariableDeclaration) {
- VariableDeclaration declaration = (VariableDeclaration)member;
- VariableDefinitionEntry field = declaration.astDefinition().astVariables()
- .first();
- String name = field.astName().astValue();
- if (referenceMap.containsKey(name)) {
- // TODO: When the Lombok ECJ bridge properly handles resolving variable
- // definitions into ECJ bindings this code should check that
- // mContext.resolve(field) == mContext.resolve(referenceMap.get(name)) !
- declarations.add(field);
- }
- }
- }
-
- return declarations;
- }
-
- private void ensureUsingFlagStyle(@NonNull List<Node> constants) {
- if (constants.size() < 3) {
- return;
- }
-
- List<VariableReference> references =
- Lists.newArrayListWithExpectedSize(constants.size());
- for (Node constant : constants) {
- if (constant instanceof VariableReference) {
- references.add((VariableReference) constant);
- }
- }
- List<VariableDefinitionEntry> entries = findDeclarations(
- findSurroundingClass(constants.get(0)), references);
- for (VariableDefinitionEntry entry : entries) {
- Expression declaration = entry.astInitializer();
- if (declaration == null) {
- continue;
- }
- if (declaration instanceof IntegralLiteral) {
- IntegralLiteral literal = (IntegralLiteral) declaration;
- // Allow -1, 0 and 1. You can write 1 as "1 << 0" but IntelliJ for
- // example warns that that's a redundant shift.
- long value = literal.astLongValue();
- if (Math.abs(value) <= 1) {
- continue;
- }
- // Only warn if we're setting a specific bit
- if (Long.bitCount(value) != 1) {
- continue;
- }
- int shift = Long.numberOfTrailingZeros(value);
- String message = String.format(
- "Consider declaring this constant using 1 << %1$d instead",
- shift);
- mContext.report(FLAG_STYLE, declaration, mContext.getLocation(declaration),
- message);
- }
- }
- }
-
- private boolean checkId(Annotation node, String id) {
- IssueRegistry registry = mContext.getDriver().getRegistry();
- Issue issue = registry.getIssue(id);
- // Special-case the ApiDetector issue, since it does both source file analysis
- // only on field references, and class file analysis on the rest, so we allow
- // annotations outside of methods only on fields
- if (issue != null && !issue.getImplementation().getScope().contains(Scope.JAVA_FILE)
- || issue == ApiDetector.UNSUPPORTED) {
- // Ensure that this isn't a field
- Node parent = node.getParent();
- while (parent != null) {
- if (parent instanceof MethodDeclaration
- || parent instanceof ConstructorDeclaration
- || parent instanceof Block) {
- break;
- } else if (parent instanceof TypeBody) { // It's a field
- return true;
- } else if (issue == ApiDetector.UNSUPPORTED
- && parent instanceof VariableDefinition) {
- VariableDefinition definition = (VariableDefinition) parent;
- for (VariableDefinitionEntry entry : definition.astVariables()) {
- Expression initializer = entry.astInitializer();
- if (initializer instanceof Select) {
- return true;
- }
- }
- }
- parent = parent.getParent();
- if (parent == null) {
- return true;
- }
- }
-
- // This issue doesn't have AST access: annotations are not
- // available for local variables or parameters
- Node scope = getAnnotationScope(node);
- mContext.report(INSIDE_METHOD, scope, mContext.getLocation(node), String.format(
- "The `@SuppressLint` annotation cannot be used on a local " +
- "variable with the lint check '%1$s': move out to the " +
- "surrounding method", id));
- return false;
- }
-
- return true;
- }
- }
-
- /**
- * Returns the node to use as the scope for the given annotation node.
- * You can't annotate an annotation itself (with {@code @SuppressLint}), but
- * you should be able to place an annotation next to it, as a sibling, to only
- * suppress the error on this annotated element, not the whole surrounding class.
- */
- @NonNull
- private static Node getAnnotationScope(@NonNull Annotation node) {
- //
- Node scope = getParentOfType(node,
- AnnotationDeclaration.class, true);
- if (scope == null) {
- scope = node;
- }
- return scope;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java
deleted file mode 100644
index ca84b27..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-
-import org.xml.sax.SAXException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Main entry point for API description.
- *
- * To create the {@link Api}, use {@link #parseApi(File)}
- *
- */
-public class Api {
-
- /**
- * Parses simplified API file.
- * @param apiFile the file to read
- * @return a new ApiInfo
- */
- public static Api parseApi(File apiFile) {
- FileInputStream fileInputStream = null;
- try {
- fileInputStream = new FileInputStream(apiFile);
- SAXParserFactory parserFactory = SAXParserFactory.newInstance();
- SAXParser parser = parserFactory.newSAXParser();
- ApiParser apiParser = new ApiParser();
- parser.parse(fileInputStream, apiParser);
- return new Api(apiParser.getClasses());
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fileInputStream != null) {
- try {
- fileInputStream.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- return null;
- }
-
- private final Map<String, ApiClass> mClasses;
-
- private Api(Map<String, ApiClass> classes) {
- mClasses = new HashMap<String, ApiClass>(classes);
- }
-
- ApiClass getClass(String fqcn) {
- return mClasses.get(fqcn);
- }
-
- Map<String, ApiClass> getClasses() {
- return Collections.unmodifiableMap(mClasses);
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
deleted file mode 100644
index c5946b9..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-
-import com.android.annotations.Nullable;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Represents a class and its methods/fields.
- *
- * {@link #getSince()} gives the API level it was introduced.
- *
- * {@link #getMethod} returns when the method was introduced.
- * {@link #getField} returns when the field was introduced.
- */
-public class ApiClass {
-
- private final String mName;
- private final int mSince;
-
- private final List<Pair<String, Integer>> mSuperClasses = Lists.newArrayList();
- private final List<Pair<String, Integer>> mInterfaces = Lists.newArrayList();
-
- private final Map<String, Integer> mFields = new HashMap<String, Integer>();
- private final Map<String, Integer> mMethods = new HashMap<String, Integer>();
-
- ApiClass(String name, int since) {
- mName = name;
- mSince = since;
- }
-
- /**
- * Returns the name of the class.
- * @return the name of the class
- */
- String getName() {
- return mName;
- }
-
- /**
- * Returns when the class was introduced.
- * @return the api level the class was introduced.
- */
- int getSince() {
- return mSince;
- }
-
- /**
- * Returns when a field was added, or null if it doesn't exist.
- * @param name the name of the field.
- * @param info the corresponding info
- */
- Integer getField(String name, Api info) {
- // The field can come from this class or from a super class or an interface
- // The value can never be lower than this introduction of this class.
- // When looking at super classes and interfaces, it can never be lower than when the
- // super class or interface was added as a super class or interface to this class.
- // Look at all the values and take the lowest.
- // For instance:
- // This class A is introduced in 5 with super class B.
- // In 10, the interface C was added.
- // Looking for SOME_FIELD we get the following:
- // Present in A in API 15
- // Present in B in API 11
- // Present in C in API 7.
- // The answer is 10, which is when C became an interface
- int min = Integer.MAX_VALUE;
- Integer i = mFields.get(name);
- if (i != null) {
- min = i;
- }
-
- // now look at the super classes
- for (Pair<String, Integer> superClassPair : mSuperClasses) {
- ApiClass superClass = info.getClass(superClassPair.getFirst());
- if (superClass != null) {
- i = superClass.getField(name, info);
- if (i != null) {
- int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- // now look at the interfaces
- for (Pair<String, Integer> superClassPair : mInterfaces) {
- ApiClass superClass = info.getClass(superClassPair.getFirst());
- if (superClass != null) {
- i = superClass.getField(name, info);
- if (i != null) {
- int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- return min;
- }
-
- /**
- * Returns when a method was added, or null if it doesn't exist. This goes through the super
- * class to find method only present there.
- * @param methodSignature the method signature
- */
- int getMethod(String methodSignature, Api info) {
- // The method can come from this class or from a super class.
- // The value can never be lower than this introduction of this class.
- // When looking at super classes, it can never be lower than when the super class became
- // a super class of this class.
- // Look at all the values and take the lowest.
- // For instance:
- // This class A is introduced in 5 with super class B.
- // In 10, the super class changes to C.
- // Looking for foo() we get the following:
- // Present in A in API 15
- // Present in B in API 11
- // Present in C in API 7.
- // The answer is 10, which is when C became the super class.
- int min = Integer.MAX_VALUE;
- Integer i = mMethods.get(methodSignature);
- if (i != null) {
- min = i;
-
- // Constructors aren't inherited
- if (methodSignature.startsWith(CONSTRUCTOR_NAME)) {
- return i;
- }
- }
-
- // now look at the super classes
- for (Pair<String, Integer> superClassPair : mSuperClasses) {
- ApiClass superClass = info.getClass(superClassPair.getFirst());
- if (superClass != null) {
- i = superClass.getMethod(methodSignature, info);
- if (i != null) {
- int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- // now look at the interfaces classes
- for (Pair<String, Integer> interfacePair : mInterfaces) {
- ApiClass superClass = info.getClass(interfacePair.getFirst());
- if (superClass != null) {
- i = superClass.getMethod(methodSignature, info);
- if (i != null) {
- int tmp = interfacePair.getSecond() > i ? interfacePair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- return min;
- }
-
- void addField(String name, int since) {
- Integer i = mFields.get(name);
- if (i == null || i.intValue() > since) {
- mFields.put(name, Integer.valueOf(since));
- }
- }
-
- void addMethod(String name, int since) {
- // Strip off the method type at the end to ensure that the code which
- // produces inherited methods doesn't get confused and end up multiple entries.
- // For example, java/nio/Buffer has the method "array()Ljava/lang/Object;",
- // and the subclass java/nio/ByteBuffer has the method "array()[B". We want
- // the lookup on mMethods to associate the ByteBuffer array method to be
- // considered overriding the Buffer method.
- int index = name.indexOf(')');
- if (index != -1) {
- name = name.substring(0, index + 1);
- }
-
- Integer i = mMethods.get(name);
- if (i == null || i.intValue() > since) {
- mMethods.put(name, Integer.valueOf(since));
- }
- }
-
- void addSuperClass(String superClass, int since) {
- addToArray(mSuperClasses, superClass, since);
- }
-
- void addInterface(String interfaceClass, int since) {
- addToArray(mInterfaces, interfaceClass, since);
- }
-
- static void addToArray(List<Pair<String, Integer>> list, String name, int value) {
- // check if we already have that name (at a lower level)
- for (Pair<String, Integer> pair : list) {
- if (name.equals(pair.getFirst())) {
- return;
- }
- }
-
- list.add(Pair.of(name, Integer.valueOf(value)));
-
- }
-
- @Nullable
- public String getPackage() {
- int index = mName.lastIndexOf('/');
- if (index != -1) {
- return mName.substring(0, index);
- }
-
- return null;
- }
-
- @Override
- public String toString() {
- return mName;
- }
-
- /**
- * Returns the set of all methods, including inherited
- * ones.
- *
- * @param info the api to look up super classes from
- * @return a set containing all the members fields
- */
- Set<String> getAllMethods(Api info) {
- Set<String> members = new HashSet<String>(100);
- addAllMethods(info, members, true /*includeConstructors*/);
-
- return members;
- }
-
- private void addAllMethods(Api info, Set<String> set, boolean includeConstructors) {
- if (!includeConstructors) {
- for (String method : mMethods.keySet()) {
- if (!method.startsWith(CONSTRUCTOR_NAME)) {
- set.add(method);
- }
- }
- } else {
- for (String method : mMethods.keySet()) {
- set.add(method);
- }
- }
-
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllMethods(info, set, false);
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllMethods(info, set, false);
- }
- }
- }
-
- /**
- * Returns the set of all fields, including inherited
- * ones.
- *
- * @param info the api to look up super classes from
- * @return a set containing all the fields
- */
- Set<String> getAllFields(Api info) {
- Set<String> members = new HashSet<String>(100);
- addAllFields(info, members);
-
- return members;
- }
-
- private void addAllFields(Api info, Set<String> set) {
- for (String field : mFields.keySet()) {
- set.add(field);
- }
-
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllFields(info, set);
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllFields(info, set);
- }
- }
- }
-
- /* This code can be used to scan through all the fields and look for fields
- that have moved to a higher class:
- Field android/view/MotionEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
- Field android/provider/ContactsContract$CommonDataKinds$Organization#PHONETIC_NAME has api=5 but parent android/provider/ContactsContract$ContactNameColumns provides it as 11
- Field android/widget/ListView#CHOICE_MODE_MULTIPLE has api=1 but parent android/widget/AbsListView provides it as 11
- Field android/widget/ListView#CHOICE_MODE_NONE has api=1 but parent android/widget/AbsListView provides it as 11
- Field android/widget/ListView#CHOICE_MODE_SINGLE has api=1 but parent android/widget/AbsListView provides it as 11
- Field android/view/KeyEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
- This is used for example in the ApiDetector to filter out warnings which result
- when people follow Eclipse's advice to replace
- ListView.CHOICE_MODE_MULTIPLE
- references with
- AbsListView.CHOICE_MODE_MULTIPLE
- since the latter has API=11 and the former has API=1; since the constant is unchanged
- between the two, and the literal is copied into the class, using the AbsListView
- reference works.
- public void checkFields(Api info) {
- fieldLoop:
- for (String field : mFields.keySet()) {
- Integer since = getField(field, info);
- if (since == null || since == Integer.MAX_VALUE) {
- continue;
- }
-
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- Integer superSince = clz.getField(field, info);
- if (superSince == Integer.MAX_VALUE) {
- continue;
- }
-
- if (superSince != null && superSince > since) {
- String declaredIn = clz.findFieldDeclaration(info, field);
- System.out.println("Field " + getName() + "#" + field + " has api="
- + since + " but parent " + declaredIn + " provides it as "
- + superSince);
- continue fieldLoop;
- }
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- Integer superSince = clz.getField(field, info);
- if (superSince == Integer.MAX_VALUE) {
- continue;
- }
- if (superSince != null && superSince > since) {
- String declaredIn = clz.findFieldDeclaration(info, field);
- System.out.println("Field " + getName() + "#" + field + " has api="
- + since + " but parent " + declaredIn + " provides it as "
- + superSince);
- continue fieldLoop;
- }
- }
- }
- }
- }
-
- private String findFieldDeclaration(Api info, String name) {
- if (mFields.containsKey(name)) {
- return getName();
- }
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- String declaredIn = clz.findFieldDeclaration(info, name);
- if (declaredIn != null) {
- return declaredIn;
- }
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- String declaredIn = clz.findFieldDeclaration(info, name);
- if (declaredIn != null) {
- return declaredIn;
- }
- }
- }
-
- return null;
- }
- */
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
deleted file mode 100755
index 8219e9b..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
+++ /dev/null
@@ -1,2181 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PREFIX;
-import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LABEL_FOR;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PADDING_START;
-import static com.android.SdkConstants.ATTR_PARENT;
-import static com.android.SdkConstants.ATTR_TARGET_API;
-import static com.android.SdkConstants.ATTR_TEXT_IS_SELECTABLE;
-import static com.android.SdkConstants.BUTTON;
-import static com.android.SdkConstants.CHECK_BOX;
-import static com.android.SdkConstants.CLASS_CONSTRUCTOR;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.PREFIX_ANDROID;
-import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.SWITCH;
-import static com.android.SdkConstants.TAG;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.TARGET_API;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.SdkConstants.VIEW_TAG;
-import static com.android.tools.lint.detector.api.ClassContext.getFqcn;
-import static com.android.tools.lint.detector.api.ClassContext.getInternalName;
-import static com.android.tools.lint.detector.api.LintUtils.getNextInstruction;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.NEAREST;
-import static com.android.utils.SdkUtils.getResourceFieldName;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.DefaultPosition;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.SearchHints;
-import com.android.tools.lint.detector.api.Position;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.AnnotationNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.IntInsnNode;
-import org.objectweb.asm.tree.JumpInsnNode;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.LocalVariableNode;
-import org.objectweb.asm.tree.LookupSwitchInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationValue;
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.Case;
-import lombok.ast.Catch;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.If;
-import lombok.ast.ImportDeclaration;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Modifiers;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.SuperConstructorInvocation;
-import lombok.ast.Switch;
-import lombok.ast.Try;
-import lombok.ast.TypeReference;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Looks for usages of APIs that are not supported in all the versions targeted
- * by this application (according to its minimum API requirement in the manifest).
- */
-public class ApiDetector extends ResourceXmlDetector
- implements Detector.ClassScanner, Detector.JavaScanner {
-
- /**
- * Whether we flag variable, field, parameter and return type declarations of a type
- * not yet available. It appears Dalvik is very forgiving and doesn't try to preload
- * classes until actually needed, so there is no need to flag these, and in fact,
- * patterns used for supporting new and old versions sometimes declares these methods
- * and only conditionally end up actually accessing methods and fields, so only check
- * method and field accesses.
- */
- private static final boolean CHECK_DECLARATIONS = false;
-
- private static final boolean AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null; //$NON-NLS-1$
-
- /** Accessing an unsupported API */
- @SuppressWarnings("unchecked")
- public static final Issue UNSUPPORTED = Issue.create(
- "NewApi", //$NON-NLS-1$
- "Calling new methods on older versions",
-
- "This check scans through all the Android API calls in the application and " +
- "warns about any calls that are not available on *all* versions targeted " +
- "by this application (according to its minimum SDK attribute in the manifest).\n" +
- "\n" +
- "If you really want to use this API and don't need to support older devices just " +
- "set the `minSdkVersion` in your `build.gradle` or `AndroidManifest.xml` files.\n" +
- "\n" +
- "If your code is *deliberately* accessing newer APIs, and you have ensured " +
- "(e.g. with conditional execution) that this code will only ever be called on a " +
- "supported platform, then you can annotate your class or method with the " +
- "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
- "`@TargetApi(11)`, such that this check considers 11 rather than your manifest " +
- "file's minimum SDK as the required API level.\n" +
- "\n" +
- "If you are deliberately setting `android:` attributes in style definitions, " +
- "make sure you place this in a `values-vNN` folder in order to avoid running " +
- "into runtime conflicts on certain devices where manufacturers have added " +
- "custom attributes whose ids conflict with the new ones on later platforms.\n" +
- "\n" +
- "Similarly, you can use tools:targetApi=\"11\" in an XML file to indicate that " +
- "the element will only be inflated in an adequate context.",
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- new Implementation(
- ApiDetector.class,
- EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST),
- Scope.RESOURCE_FILE_SCOPE,
- Scope.CLASS_FILE_SCOPE,
- Scope.MANIFEST_SCOPE));
-
- /** Accessing an inlined API on older platforms */
- public static final Issue INLINED = Issue.create(
- "InlinedApi", //$NON-NLS-1$
- "Using inlined constants on older versions",
-
- "This check scans through all the Android API field references in the application " +
- "and flags certain constants, such as static final integers and Strings, " +
- "which were introduced in later versions. These will actually be copied " +
- "into the class files rather than being referenced, which means that " +
- "the value is available even when running on older devices. In some " +
- "cases that's fine, and in other cases it can result in a runtime " +
- "crash or incorrect behavior. It depends on the context, so consider " +
- "the code carefully and device whether it's safe and can be suppressed " +
- "or whether the code needs tbe guarded.\n" +
- "\n" +
- "If you really want to use this API and don't need to support older devices just " +
- "set the `minSdkVersion` in your `build.gradle` or `AndroidManifest.xml` files." +
- "\n" +
- "If your code is *deliberately* accessing newer APIs, and you have ensured " +
- "(e.g. with conditional execution) that this code will only ever be called on a " +
- "supported platform, then you can annotate your class or method with the " +
- "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
- "`@TargetApi(11)`, such that this check considers 11 rather than your manifest " +
- "file's minimum SDK as the required API level.\n",
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- new Implementation(
- ApiDetector.class,
- Scope.JAVA_FILE_SCOPE));
-
- /** Accessing an unsupported API */
- public static final Issue OVERRIDE = Issue.create(
- "Override", //$NON-NLS-1$
- "Method conflicts with new inherited method",
-
- "Suppose you are building against Android API 8, and you've subclassed Activity. " +
- "In your subclass you add a new method called `isDestroyed`(). At some later point, " +
- "a method of the same name and signature is added to Android. Your method will " +
- "now override the Android method, and possibly break its contract. Your method " +
- "is not calling `super.isDestroyed()`, since your compilation target doesn't " +
- "know about the method.\n" +
- "\n" +
- "The above scenario is what this lint detector looks for. The above example is " +
- "real, since `isDestroyed()` was added in API 17, but it will be true for *any* " +
- "method you have added to a subclass of an Android class where your build target " +
- "is lower than the version the method was introduced in.\n" +
- "\n" +
- "To fix this, either rename your method, or if you are really trying to augment " +
- "the builtin method if available, switch to a higher build target where you can " +
- "deliberately add `@Override` on your overriding method, and call `super` if " +
- "appropriate etc.\n",
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- new Implementation(
- ApiDetector.class,
- Scope.CLASS_FILE_SCOPE));
-
- /** Accessing an inlined API on older platforms */
- public static final Issue UNUSED = Issue.create(
- "UnusedAttribute", //$NON-NLS-1$
- "Attribute unused on older versions",
-
- "This check finds attributes set in XML files that were introduced in a version " +
- "newer than the oldest version targeted by your application (with the " +
- "`minSdkVersion` attribute).\n" +
- "\n" +
- "This is not an error; the application will simply ignore the attribute. However, " +
- "if the attribute is important to the appearance of functionality of your " +
- "application, you should consider finding an alternative way to achieve the " +
- "same result with only available attributes, and then you can optionally create " +
- "a copy of the layout in a layout-vNN folder which will be used on API NN or " +
- "higher where you can take advantage of the newer attribute.\n" +
- "\n" +
- "Note: This check does not only apply to attributes. For example, some tags can be " +
- "unused too, such as the new `<tag>` element in layouts introduced in API 21.",
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- new Implementation(
- ApiDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
- private static final String TARGET_API_VMSIG = '/' + TARGET_API + ';';
- private static final String SWITCH_TABLE_PREFIX = "$SWITCH_TABLE$"; //$NON-NLS-1$
- private static final String ORDINAL_METHOD = "ordinal"; //$NON-NLS-1$
- public static final String ENUM_SWITCH_PREFIX = "$SwitchMap$"; //$NON-NLS-1$
-
- private static final String TAG_RIPPLE = "ripple";
- private static final String TAG_VECTOR = "vector";
- private static final String TAG_ANIMATED_VECTOR = "animated-vector";
- private static final String TAG_ANIMATED_SELECTOR = "animated-selector";
-
- private static final String SDK_INT = "SDK_INT";
- private static final String ANDROID_OS_BUILD_VERSION = "android/os/Build$VERSION";
-
- protected ApiLookup mApiDatabase;
- private boolean mWarnedMissingDb;
- private int mMinApi = -1;
- private Map<String, List<Pair<String, Location>>> mPendingFields;
-
- /** Constructs a new API check */
- public ApiDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- mApiDatabase = ApiLookup.get(context.getClient());
- // We can't look up the minimum API required by the project here:
- // The manifest file hasn't been processed yet in the -before- project hook.
- // For now it's initialized lazily in getMinSdk(Context), but the
- // lint infrastructure should be fixed to parse manifest file up front.
-
- if (mApiDatabase == null && !mWarnedMissingDb) {
- mWarnedMissingDb = true;
- context.report(IssueRegistry.LINT_ERROR, Location.create(context.file),
- "Can't find API database; API check not performed");
- }
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return true;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return ALL;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (mApiDatabase == null) {
- return;
- }
-
- int attributeApiLevel = -1;
- if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
- String name = attribute.getLocalName();
- if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) &&
- !(name.equals(ATTR_ID)))) {
- String owner = "android/R$attr"; //$NON-NLS-1$
- attributeApiLevel = mApiDatabase.getFieldVersion(owner, name);
- int minSdk = getMinSdk(context);
- if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion()
- && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement())
- && !isBenignUnusedAttribute(name)
- && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) {
- if (RtlDetector.isRtlAttributeName(name)) {
- // No need to warn for example that
- // "layout_alignParentEnd will only be used in API level 17 and higher"
- // since we have a dedicated RTL lint rule dealing with those attributes
-
- // However, paddingStart in particular is known to cause crashes
- // when used on TextViews (and subclasses of TextViews), on some
- // devices, because vendor specific attributes conflict with the
- // later-added framework resources, and these are apparently read
- // by the text views:
- if (name.equals(ATTR_PADDING_START) &&
- viewMayExtendTextView(attribute.getOwnerElement())) {
- Location location = context.getLocation(attribute);
- String message = String.format(
- "Attribute `%1$s` referenced here can result in a crash on "
- + "some specific devices older than API %2$d "
- + "(current min is %3$d)",
- attribute.getLocalName(), attributeApiLevel, minSdk);
- context.report(UNSUPPORTED, attribute, location, message);
- }
- } else {
- Location location = context.getLocation(attribute);
- String message = String.format(
- "Attribute `%1$s` is only used in API level %2$d and higher "
- + "(current min is %3$d)",
- attribute.getLocalName(), attributeApiLevel, minSdk);
- context.report(UNUSED, attribute, location, message);
- }
- }
- }
-
- // Special case:
- // the dividers attribute is present in API 1, but it won't be read on older
- // versions, so don't flag the common pattern
- // android:divider="?android:attr/dividerHorizontal"
- // since this will work just fine. See issue 67440 for more.
- if (name.equals("divider")) {
- return;
- }
- }
-
- String value = attribute.getValue();
- String owner = null;
- String name = null;
- String prefix;
- if (value.startsWith(ANDROID_PREFIX)) {
- prefix = ANDROID_PREFIX;
- } else if (value.startsWith(ANDROID_THEME_PREFIX)) {
- prefix = ANDROID_THEME_PREFIX;
- } else if (value.startsWith(PREFIX_ANDROID) && ATTR_NAME.equals(attribute.getName())
- && TAG_ITEM.equals(attribute.getOwnerElement().getTagName())
- && attribute.getOwnerElement().getParentNode() != null
- && TAG_STYLE.equals(attribute.getOwnerElement().getParentNode().getNodeName())) {
- owner = "android/R$attr"; //$NON-NLS-1$
- name = value.substring(PREFIX_ANDROID.length());
- prefix = null;
- } else if (value.startsWith(PREFIX_ANDROID) && ATTR_PARENT.equals(attribute.getName())
- && TAG_STYLE.equals(attribute.getOwnerElement().getTagName())) {
- owner = "android/R$style"; //$NON-NLS-1$
- name = getResourceFieldName(value.substring(PREFIX_ANDROID.length()));
- prefix = null;
- } else {
- return;
- }
-
- if (owner == null) {
- // Convert @android:type/foo into android/R$type and "foo"
- int index = value.indexOf('/', prefix.length());
- if (index != -1) {
- owner = "android/R$" //$NON-NLS-1$
- + value.substring(prefix.length(), index);
- name = getResourceFieldName(value.substring(index + 1));
- } else if (value.startsWith(ANDROID_THEME_PREFIX)) {
- owner = "android/R$attr"; //$NON-NLS-1$
- name = value.substring(ANDROID_THEME_PREFIX.length());
- } else {
- return;
- }
- }
- int api = mApiDatabase.getFieldVersion(owner, name);
- int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(attribute.getOwnerElement())) {
- // Don't complain about resource references in the tools namespace,
- // such as for example "tools:layout="@android:layout/list_content",
- // used only for designtime previews
- if (TOOLS_URI.equals(attribute.getNamespaceURI())) {
- return;
- }
-
- //noinspection StatementWithEmptyBody
- if (attributeApiLevel >= api) {
- // The attribute will only be *read* on platforms >= attributeApiLevel.
- // If this isn't lower than the attribute reference's API level, it
- // won't be a problem
- } else if (attributeApiLevel > minSdk) {
- String attributeName = attribute.getLocalName();
- Location location = context.getLocation(attribute);
- String message = String.format(
- "`%1$s` requires API level %2$d (current min is %3$d), but note "
- + "that attribute `%4$s` is only used in API level %5$d "
- + "and higher",
- name, api, minSdk, attributeName, attributeApiLevel);
- context.report(UNSUPPORTED, attribute, location, message);
- } else {
- Location location = context.getLocation(attribute);
- String message = String.format(
- "`%1$s` requires API level %2$d (current min is %3$d)",
- value, api, minSdk);
- context.report(UNSUPPORTED, attribute, location, message);
- }
- }
- }
-
- /**
- * Returns true if the view tag is possibly a text view. It may not be certain,
- * but will err on the side of caution (for example, any custom view is considered
- * to be a potential text view.)
- */
- private static boolean viewMayExtendTextView(@NonNull Element element) {
- String tag = element.getTagName();
- if (tag.equals(SdkConstants.VIEW_TAG)) {
- tag = element.getAttribute(ATTR_CLASS);
- if (tag == null || tag.isEmpty()) {
- return false;
- }
- }
-
- //noinspection SimplifiableIfStatement
- if (tag.indexOf('.') != -1) {
- // Custom views: not sure. Err on the side of caution.
- return true;
-
- }
-
- return tag.contains("Text") // TextView, EditText, etc
- || tag.contains(BUTTON) // Button, ToggleButton, etc
- || tag.equals("DigitalClock")
- || tag.equals("Chronometer")
- || tag.equals(CHECK_BOX)
- || tag.equals(SWITCH);
- }
-
- /**
- * Returns true if this attribute is in a drawable document with one of the
- * root tags that require API 21
- */
- private static boolean isAlreadyWarnedDrawableFile(@NonNull XmlContext context,
- @NonNull Attr attribute, int attributeApiLevel) {
- // Don't complain if it's in a drawable file where we've already
- // flagged the root drawable type as being unsupported
- if (context.getResourceFolderType() == ResourceFolderType.DRAWABLE
- && attributeApiLevel == 21) {
- String root = attribute.getOwnerDocument().getDocumentElement().getTagName();
- if (TAG_RIPPLE.equals(root)
- || TAG_VECTOR.equals(root)
- || TAG_ANIMATED_VECTOR.equals(root)
- || TAG_ANIMATED_SELECTOR.equals(root)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Is the given attribute a "benign" unused attribute, one we probably don't need to
- * flag to the user as not applicable on all versions? These are typically attributes
- * which add some nice platform behavior when available, but that are not critical
- * and developers would not typically need to be aware of to try to implement workarounds
- * on older platforms.
- */
- public static boolean isBenignUnusedAttribute(@NonNull String name) {
- return ATTR_LABEL_FOR.equals(name) || ATTR_TEXT_IS_SELECTABLE.equals(name);
-
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (mApiDatabase == null) {
- return;
- }
-
- String tag = element.getTagName();
-
- ResourceFolderType folderType = context.getResourceFolderType();
- if (folderType != ResourceFolderType.LAYOUT) {
- if (folderType == ResourceFolderType.DRAWABLE) {
- checkElement(context, element, TAG_RIPPLE, 21, UNSUPPORTED);
- checkElement(context, element, TAG_VECTOR, 21, UNSUPPORTED);
- checkElement(context, element, TAG_ANIMATED_SELECTOR, 21, UNSUPPORTED);
- checkElement(context, element, TAG_ANIMATED_VECTOR, 21, UNSUPPORTED);
- }
- if (element.getParentNode().getNodeType() != Node.ELEMENT_NODE) {
- // Root node
- return;
- }
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node textNode = childNodes.item(i);
- if (textNode.getNodeType() == Node.TEXT_NODE) {
- String text = textNode.getNodeValue();
- if (text.contains(ANDROID_PREFIX)) {
- text = text.trim();
- // Convert @android:type/foo into android/R$type and "foo"
- int index = text.indexOf('/', ANDROID_PREFIX.length());
- if (index != -1) {
- String owner = "android/R$" //$NON-NLS-1$
- + text.substring(ANDROID_PREFIX.length(), index);
- String name = getResourceFieldName(text.substring(index + 1));
- int api = mApiDatabase.getFieldVersion(owner, name);
- int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
- Location location = context.getLocation(textNode);
- String message = String.format(
- "`%1$s` requires API level %2$d (current min is %3$d)",
- text, api, minSdk);
- context.report(UNSUPPORTED, element, location, message);
- }
- }
- }
- }
- }
- } else {
- if (VIEW_TAG.equals(tag)) {
- tag = element.getAttribute(ATTR_CLASS);
- if (tag == null || tag.isEmpty()) {
- return;
- }
- } else {
- // TODO: Complain if <tag> is used at the root level!
- checkElement(context, element, TAG, 21, UNUSED);
- }
-
- // Check widgets to make sure they're available in this version of the SDK.
- if (tag.indexOf('.') != -1) {
- // Custom views aren't in the index
- return;
- }
- String fqn = "android/widget/" + tag; //$NON-NLS-1$
- if (tag.equals("TextureView")) { //$NON-NLS-1$
- fqn = "android/view/TextureView"; //$NON-NLS-1$
- }
- // TODO: Consider other widgets outside of android.widget.*
- int api = mApiDatabase.getClassVersion(fqn);
- int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
- Location location = context.getLocation(element);
- String message = String.format(
- "View requires API level %1$d (current min is %2$d): `<%3$s>`",
- api, minSdk, tag);
- context.report(UNSUPPORTED, element, location, message);
- }
- }
- }
-
- /** Checks whether the given element is the given tag, and if so, whether it satisfied
- * the minimum version that the given tag is supported in */
- private void checkElement(@NonNull XmlContext context, @NonNull Element element,
- @NonNull String tag, int api, @NonNull Issue issue) {
- if (tag.equals(element.getTagName())) {
- int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
- Location location = context.getLocation(element);
- String message;
- if (issue == UNSUPPORTED) {
- message = String.format(
- "`<%1$s>` requires API level %2$d (current min is %3$d)", tag, api,
- minSdk);
- } else {
- assert issue == UNUSED : issue;
- message = String.format(
- "`<%1$s>` is only used in API level %2$d and higher "
- + "(current min is %3$d)", tag, api, minSdk);
- }
- context.report(issue, element, location, message);
- }
- }
- }
-
- protected int getMinSdk(Context context) {
- if (mMinApi == -1) {
- AndroidVersion minSdkVersion = context.getMainProject().getMinSdkVersion();
- mMinApi = minSdkVersion.getFeatureLevel();
- }
-
- return mMinApi;
- }
-
- // ---- Implements ClassScanner ----
-
- @SuppressWarnings("rawtypes") // ASM API
- @Override
- public void checkClass(@NonNull final ClassContext context, @NonNull ClassNode classNode) {
- if (mApiDatabase == null) {
- return;
- }
-
- if (AOSP_BUILD && classNode.name.startsWith("android/support/")) { //$NON-NLS-1$
- return;
- }
-
- // Requires util package (add prebuilts/tools/common/asm-tools/asm-debug-all-4.0.jar)
- //classNode.accept(new TraceClassVisitor(new PrintWriter(System.out)));
-
- int classMinSdk = getClassMinSdk(context, classNode);
- if (classMinSdk == -1) {
- classMinSdk = getMinSdk(context);
- }
-
- List methodList = classNode.methods;
- if (methodList.isEmpty()) {
- return;
- }
-
- boolean checkCalls = context.isEnabled(UNSUPPORTED)
- || context.isEnabled(INLINED);
- boolean checkMethods = context.isEnabled(OVERRIDE)
- && context.getMainProject().getBuildSdk() >= 1;
- String frameworkParent = null;
- if (checkMethods) {
- LintDriver driver = context.getDriver();
- String owner = classNode.superName;
- while (owner != null) {
- // For virtual dispatch, walk up the inheritance chain checking
- // each inherited method
- if ((owner.startsWith("android/") //$NON-NLS-1$
- && !owner.startsWith("android/support/")) //$NON-NLS-1$
- || owner.startsWith("java/") //$NON-NLS-1$
- || owner.startsWith("javax/")) { //$NON-NLS-1$
- frameworkParent = owner;
- break;
- }
- owner = driver.getSuperClass(owner);
- }
- if (frameworkParent == null) {
- checkMethods = false;
- }
- }
-
- if (checkCalls) { // Check implements/extends
- if (classNode.superName != null) {
- String signature = classNode.superName;
- checkExtendsClass(context, classNode, classMinSdk, signature);
- }
- if (classNode.interfaces != null) {
- @SuppressWarnings("unchecked") // ASM API
- List<String> interfaceList = classNode.interfaces;
- for (String signature : interfaceList) {
- checkExtendsClass(context, classNode, classMinSdk, signature);
- }
- }
- }
-
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
-
- int minSdk = getLocalMinSdk(method.invisibleAnnotations);
- if (minSdk == -1) {
- minSdk = classMinSdk;
- }
-
- InsnList nodes = method.instructions;
-
- if (checkMethods && Character.isJavaIdentifierStart(method.name.charAt(0))) {
- int buildSdk = context.getMainProject().getBuildSdk();
- String name = method.name;
- assert frameworkParent != null;
- int api = mApiDatabase.getCallVersion(frameworkParent, name, method.desc);
- if (api > buildSdk && buildSdk != -1) {
- // TODO: Don't complain if it's annotated with @Override; that means
- // somehow the build target isn't correct.
- String fqcn;
- String owner = classNode.name;
- if (CONSTRUCTOR_NAME.equals(name)) {
- fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$
- } else {
- fqcn = ClassContext.getFqcn(owner) + '#' + name;
- }
- String message = String.format(
- "This method is not overriding anything with the current build " +
- "target, but will in API level %1$d (current target is %2$d): `%3$s`",
- api, buildSdk, fqcn);
-
- Location location = context.getLocation(method, classNode);
- context.report(OVERRIDE, method, null, location, message);
- }
- }
-
- if (!checkCalls) {
- continue;
- }
-
- if (CHECK_DECLARATIONS) {
- // Check types in parameter list and types of local variables
- List localVariables = method.localVariables;
- if (localVariables != null) {
- for (Object v : localVariables) {
- LocalVariableNode var = (LocalVariableNode) v;
- String desc = var.desc;
- if (desc.charAt(0) == 'L') {
- // "Lpackage/Class;" => "package/Bar"
- String className = desc.substring(1, desc.length() - 1);
- int api = mApiDatabase.getClassVersion(className);
- if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(className);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
- report(context, message, var.start, method,
- className.substring(className.lastIndexOf('/') + 1), null,
- SearchHints.create(NEAREST).matchJavaSymbol());
- }
- }
- }
- }
-
- // Check return type
- // The parameter types are already handled as local variables so we can skip
- // right to the return type.
- // Check types in parameter list
- String signature = method.desc;
- if (signature != null) {
- int args = signature.indexOf(')');
- if (args != -1 && signature.charAt(args + 1) == 'L') {
- String type = signature.substring(args + 2, signature.length() - 1);
- int api = mApiDatabase.getClassVersion(type);
- if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(type);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
- AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
- report(context, message, first, method, method.name, null,
- SearchHints.create(BACKWARD).matchJavaSymbol());
- }
- }
- }
- }
-
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- int type = instruction.getType();
- if (type == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode node = (MethodInsnNode) instruction;
- String name = node.name;
- String owner = node.owner;
- String desc = node.desc;
-
- // No need to check methods in this local class; we know they
- // won't be an API match
- if (node.getOpcode() == Opcodes.INVOKEVIRTUAL
- && owner.equals(classNode.name)) {
- owner = classNode.superName;
- }
-
- boolean checkingSuperClass = false;
- while (owner != null) {
- int api = mApiDatabase.getCallVersion(owner, name, desc);
- if (api > minSdk) {
- if (method.name.startsWith(SWITCH_TABLE_PREFIX)) {
- // We're in a compiler-generated method to generate an
- // array indexed by enum ordinal values to enum values. The enum
- // itself must be requiring a higher API number than is
- // currently used, but the call site for the switch statement
- // will also be referencing it, so no need to report these
- // calls.
- break;
- }
-
- if (!checkingSuperClass
- && node.getOpcode() == Opcodes.INVOKEVIRTUAL
- && methodDefinedLocally(classNode, name, desc)) {
- break;
- }
-
- String fqcn;
- if (CONSTRUCTOR_NAME.equals(name)) {
- fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$
- } else {
- fqcn = ClassContext.getFqcn(owner) + '#' + name;
- }
- String message = String.format(
- "Call requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
-
- if (name.equals(ORDINAL_METHOD)
- && instruction.getNext() != null
- && instruction.getNext().getNext() != null
- && instruction.getNext().getOpcode() == Opcodes.IALOAD
- && instruction.getNext().getNext().getOpcode()
- == Opcodes.TABLESWITCH) {
- message = String.format(
- "Enum for switch requires API level %1$d " +
- "(current min is %2$d): `%3$s`",
- api, minSdk, ClassContext.getFqcn(owner));
- }
-
- // If you're simply calling super.X from method X, even if method X
- // is in a higher API level than the minSdk, we're generally safe;
- // that method should only be called by the framework on the right
- // API levels. (There is a danger of somebody calling that method
- // locally in other contexts, but this is hopefully unlikely.)
- if (instruction.getOpcode() == Opcodes.INVOKESPECIAL &&
- name.equals(method.name) && desc.equals(method.desc) &&
- // We specifically exclude constructors from this check,
- // because we do want to flag constructors requiring the
- // new API level; it's highly likely that the constructor
- // is called by local code so you should specifically
- // investigate this as a developer
- !name.equals(CONSTRUCTOR_NAME)) {
- break;
- }
-
- if (isWithinSdkConditional(context, classNode, method, instruction,
- api)) {
- break;
- }
-
- report(context, message, node, method, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
- break;
- }
-
- // For virtual dispatch, walk up the inheritance chain checking
- // each inherited method
- if (owner.startsWith("android/") //$NON-NLS-1$
- || owner.startsWith("javax/")) { //$NON-NLS-1$
- // The API map has already inlined all inherited methods
- // so no need to keep checking up the chain
- // -- unless it's the support library which is also in
- // the android/ namespace:
- if (owner.startsWith("android/support/")) { //$NON-NLS-1$
- owner = context.getDriver().getSuperClass(owner);
- } else {
- owner = null;
- }
- } else if (owner.startsWith("java/")) { //$NON-NLS-1$
- if (owner.equals(LocaleDetector.DATE_FORMAT_OWNER)) {
- checkSimpleDateFormat(context, method, node, minSdk);
- }
- // Already inlined; see comment above
- owner = null;
- } else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
- owner = context.getDriver().getSuperClass(owner);
- } else if (node.getOpcode() == Opcodes.INVOKESTATIC && api == -1) {
- // Inherit through static classes as well
- owner = context.getDriver().getSuperClass(owner);
- } else {
- owner = null;
- }
-
- checkingSuperClass = true;
- }
- } else if (type == AbstractInsnNode.FIELD_INSN) {
- FieldInsnNode node = (FieldInsnNode) instruction;
- String name = node.name;
- String owner = node.owner;
- int api = mApiDatabase.getFieldVersion(owner, name);
- if (api > minSdk) {
- if (method.name.startsWith(SWITCH_TABLE_PREFIX)) {
- checkSwitchBlock(context, classNode, node, method, name, owner,
- api, minSdk);
- continue;
- }
-
- if (isSkippedEnumSwitch(context, classNode, method, node, owner, api)) {
- continue;
- }
-
- if (isWithinSdkConditional(context, classNode, method, instruction, api)) {
- continue;
- }
-
- String fqcn = ClassContext.getFqcn(owner) + '#' + name;
- if (mPendingFields != null) {
- mPendingFields.remove(fqcn);
- }
- String message = String.format(
- "Field requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
- report(context, message, node, method, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
- }
- } else if (type == AbstractInsnNode.LDC_INSN) {
- LdcInsnNode node = (LdcInsnNode) instruction;
- if (node.cst instanceof Type) {
- Type t = (Type) node.cst;
- String className = t.getInternalName();
-
- int api = mApiDatabase.getClassVersion(className);
- if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(className);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
- report(context, message, node, method,
- className.substring(className.lastIndexOf('/') + 1), null,
- SearchHints.create(FORWARD).matchJavaSymbol());
- }
- }
- }
- }
- }
- }
-
- private void checkExtendsClass(ClassContext context, ClassNode classNode, int classMinSdk,
- String signature) {
- int api = mApiDatabase.getClassVersion(signature);
- if (api > classMinSdk) {
- String fqcn = ClassContext.getFqcn(signature);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): `%3$s`",
- api, classMinSdk, fqcn);
-
- String name = signature.substring(signature.lastIndexOf('/') + 1);
- name = name.substring(name.lastIndexOf('$') + 1);
- SearchHints hints = SearchHints.create(BACKWARD).matchJavaSymbol();
- int lineNumber = ClassContext.findLineNumber(classNode);
- Location location = context.getLocationForLine(lineNumber, name, null,
- hints);
- context.report(UNSUPPORTED, location, message);
- }
- }
-
- private static void checkSimpleDateFormat(ClassContext context, MethodNode method,
- MethodInsnNode node, int minSdk) {
- if (minSdk >= 9) {
- // Already OK
- return;
- }
- if (node.name.equals(CONSTRUCTOR_NAME) && !node.desc.equals("()V")) { //$NON-NLS-1$
- // Check first argument
- AbstractInsnNode prev = LintUtils.getPrevInstruction(node);
- if (prev != null && !node.desc.equals("(Ljava/lang/String;)V")) { //$NON-NLS-1$
- prev = LintUtils.getPrevInstruction(prev);
- }
- if (prev != null && prev.getOpcode() == Opcodes.LDC) {
- LdcInsnNode ldc = (LdcInsnNode) prev;
- Object cst = ldc.cst;
- if (cst instanceof String) {
- String pattern = (String) cst;
- boolean isEscaped = false;
- for (int i = 0; i < pattern.length(); i++) {
- char c = pattern.charAt(i);
- if (c == '\'') {
- isEscaped = !isEscaped;
- } else if (!isEscaped && (c == 'L' || c == 'c')) {
- String message = String.format(
- "The pattern character '%1$c' requires API level 9 (current " +
- "min is %2$d) : \"`%3$s`\"", c, minSdk, pattern);
- report(context, message, node, method, pattern, null,
- SearchHints.create(FORWARD));
- return;
- }
- }
- }
- }
- }
- }
-
- @SuppressWarnings("rawtypes") // ASM API
- private static boolean methodDefinedLocally(ClassNode classNode, String name, String desc) {
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- if (name.equals(method.name) && desc.equals(method.desc)) {
- return true;
- }
- }
-
- return false;
- }
-
- @SuppressWarnings("rawtypes") // ASM API
- private static void checkSwitchBlock(ClassContext context, ClassNode classNode,
- FieldInsnNode field, MethodNode method, String name, String owner, int api,
- int minSdk) {
- // Switch statements on enums are tricky. The compiler will generate a method
- // which returns an array of the enum constants, indexed by their ordinal() values.
- // However, we only want to complain if the code is actually referencing one of
- // the non-available enum fields.
- //
- // For the android.graphics.PorterDuff.Mode enum for example, the first few items
- // in the array are populated like this:
- //
- // L0
- // ALOAD 0
- // GETSTATIC android/graphics/PorterDuff$Mode.ADD : Landroid/graphics/PorterDuff$Mode;
- // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
- // ICONST_1
- // IASTORE
- // L1
- // GOTO L3
- // L2
- // FRAME FULL [[I] [java/lang/NoSuchFieldError]
- // POP
- // L3
- // FRAME SAME
- // ALOAD 0
- // GETSTATIC android/graphics/PorterDuff$Mode.CLEAR : Landroid/graphics/PorterDuff$Mode;
- // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
- // ICONST_2
- // IASTORE
- // ...
- // So if we for example find that the "ADD" field isn't accessible, since it requires
- // API 11, we need to
- // (1) First find out what its ordinal number is. We can look at the following
- // instructions to discover this; it's the "ICONST_1" and "IASTORE" instructions.
- // (After ICONST_5 it moves on to BIPUSH 6, BIPUSH 7, etc.)
- // (2) Find the corresponding *usage* of this switch method. For the above enum,
- // the switch ordinal lookup method will be called
- // "$SWITCH_TABLE$android$graphics$PorterDuff$Mode" with desc "()[I".
- // This means we will be looking for an invocation in some other method which looks
- // like this:
- // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I
- // (obviously, it can be invoked more than once)
- // Note that it can be used more than once in this class and all sites should be
- // checked!
- // (3) Look up the corresponding table switch, which should look something like this:
- // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I
- // ALOAD 0
- // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
- // IALOAD
- // LOOKUPSWITCH
- // 2: L1
- // 11: L2
- // default: L3
- // Here we need to see if the LOOKUPSWITCH instruction is referencing our target
- // case. Above we were looking for the "ADD" case which had ordinal 1. Since this
- // isn't explicitly referenced, we can ignore this field reference.
- AbstractInsnNode next = field.getNext();
- if (next == null || next.getOpcode() != Opcodes.INVOKEVIRTUAL) {
- return;
- }
- next = next.getNext();
- if (next == null) {
- return;
- }
- int ordinal;
- switch (next.getOpcode()) {
- case Opcodes.ICONST_0: ordinal = 0; break;
- case Opcodes.ICONST_1: ordinal = 1; break;
- case Opcodes.ICONST_2: ordinal = 2; break;
- case Opcodes.ICONST_3: ordinal = 3; break;
- case Opcodes.ICONST_4: ordinal = 4; break;
- case Opcodes.ICONST_5: ordinal = 5; break;
- case Opcodes.BIPUSH: {
- IntInsnNode iin = (IntInsnNode) next;
- ordinal = iin.operand;
- break;
- }
- default:
- return;
- }
-
- // Find usages of this call site
- List methodList = classNode.methods;
- for (Object m : methodList) {
- InsnList nodes = ((MethodNode) m).instructions;
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- if (instruction.getOpcode() != Opcodes.INVOKESTATIC){
- continue;
- }
- MethodInsnNode node = (MethodInsnNode) instruction;
- if (node.name.equals(method.name)
- && node.desc.equals(method.desc)
- && node.owner.equals(classNode.name)) {
- // Find lookup switch
- AbstractInsnNode target = getNextInstruction(node);
- while (target != null) {
- if (target.getOpcode() == Opcodes.LOOKUPSWITCH) {
- LookupSwitchInsnNode lookup = (LookupSwitchInsnNode) target;
- @SuppressWarnings("unchecked") // ASM API
- List<Integer> keys = lookup.keys;
- if (keys != null && keys.contains(ordinal)) {
- String fqcn = ClassContext.getFqcn(owner) + '#' + name;
- String message = String.format(
- "Enum value requires API level %1$d " +
- "(current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
- report(context, message, lookup, (MethodNode) m, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
-
- // Break out of the inner target search only; the switch
- // statement could be used in other places in this class as
- // well and we want to report all problematic usages.
- break;
- }
- }
- target = getNextInstruction(target);
- }
- }
- }
- }
- }
-
- private static boolean isEnumSwitchInitializer(ClassNode classNode) {
- @SuppressWarnings("rawtypes") // ASM API
- List fieldList = classNode.fields;
- for (Object f : fieldList) {
- FieldNode field = (FieldNode) f;
- if (field.name.startsWith(ENUM_SWITCH_PREFIX)) {
- return true;
- }
- }
- return false;
- }
-
- private static MethodNode findEnumSwitchUsage(ClassNode classNode, String owner) {
- String target = ENUM_SWITCH_PREFIX + owner.replace('/', '$');
- @SuppressWarnings("rawtypes") // ASM API
- List methodList = classNode.methods;
- for (Object f : methodList) {
- MethodNode method = (MethodNode) f;
- InsnList nodes = method.instructions;
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- if (instruction.getOpcode() == Opcodes.GETSTATIC) {
- FieldInsnNode field = (FieldInsnNode) instruction;
- if (field.name.equals(target)) {
- return method;
- }
- }
- }
- }
- return null;
- }
-
- private static boolean isSkippedEnumSwitch(ClassContext context, ClassNode classNode,
- MethodNode method, FieldInsnNode node, String owner, int api) {
- // Enum-style switches are handled in a different way: it generates
- // an innerclass where the class initializer creates a mapping from
- // the ordinals to the corresponding values.
- // Here we need to check to see if the call site which *used* the
- // table switch had a suppress node on it (or up that node's parent
- // chain
- AbstractInsnNode next = getNextInstruction(node);
- if (next != null && next.getOpcode() == Opcodes.INVOKEVIRTUAL
- && CLASS_CONSTRUCTOR.equals(method.name)
- && ORDINAL_METHOD.equals(((MethodInsnNode) next).name)
- && classNode.outerClass != null
- && isEnumSwitchInitializer(classNode)) {
- LintDriver driver = context.getDriver();
- ClassNode outer = driver.getOuterClassNode(classNode);
- if (outer != null) {
- MethodNode switchUser = findEnumSwitchUsage(outer, owner);
- if (switchUser != null) {
- // Is the API check suppressed at the call site?
- if (driver.isSuppressed(UNSUPPORTED, outer, switchUser,
- null)) {
- return true;
- }
- // Is there a @TargetAPI annotation on the method or
- // class referencing this switch map class?
- if (getLocalMinSdk(switchUser.invisibleAnnotations) >= api
- || getLocalMinSdk(outer.invisibleAnnotations) >= api) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- /**
- * Return the {@code @TargetApi} level to use for the given {@code classNode};
- * this will be the {@code @TargetApi} annotation on the class, or any outer
- * methods (for anonymous inner classes) or outer classes (for inner classes)
- * of the given class.
- */
- private static int getClassMinSdk(ClassContext context, ClassNode classNode) {
- int classMinSdk = getLocalMinSdk(classNode.invisibleAnnotations);
- if (classMinSdk != -1) {
- return classMinSdk;
- }
-
- LintDriver driver = context.getDriver();
- while (classNode != null) {
- ClassNode prev = classNode;
- classNode = driver.getOuterClassNode(classNode);
- if (classNode != null) {
- // TODO: Should this be "curr" instead?
- if (prev.outerMethod != null) {
- @SuppressWarnings("rawtypes") // ASM API
- List methods = classNode.methods;
- for (Object m : methods) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(prev.outerMethod)
- && method.desc.equals(prev.outerMethodDesc)) {
- // Found the outer method for this anonymous class; check method
- // annotations on it, then continue up the class hierarchy
- int methodMinSdk = getLocalMinSdk(method.invisibleAnnotations);
- if (methodMinSdk != -1) {
- return methodMinSdk;
- }
-
- break;
- }
- }
- }
-
- classMinSdk = getLocalMinSdk(classNode.invisibleAnnotations);
- if (classMinSdk != -1) {
- return classMinSdk;
- }
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the minimum SDK to use according to the given annotation list, or
- * -1 if no annotation was found.
- *
- * @param annotations a list of annotation nodes from ASM
- * @return the API level to use for this node, or -1
- */
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static int getLocalMinSdk(List annotations) {
- if (annotations != null) {
- for (AnnotationNode annotation : (List<AnnotationNode>)annotations) {
- String desc = annotation.desc;
- if (desc.endsWith(TARGET_API_VMSIG)) {
- if (annotation.values != null) {
- for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
- String key = (String) annotation.values.get(i);
- if (key.equals("value")) { //$NON-NLS-1$
- Object value = annotation.values.get(i + 1);
- if (value instanceof Integer) {
- return (Integer) value;
- }
- }
- }
- }
- }
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the minimum SDK to use in the given element context, or -1 if no
- * {@code tools:targetApi} attribute was found.
- *
- * @param element the element to look at, including parents
- * @return the API level to use for this element, or -1
- */
- private static int getLocalMinSdk(@NonNull Element element) {
- while (element != null) {
- String targetApi = element.getAttributeNS(TOOLS_URI, ATTR_TARGET_API);
- if (targetApi != null && !targetApi.isEmpty()) {
- if (Character.isDigit(targetApi.charAt(0))) {
- try {
- return Integer.parseInt(targetApi);
- } catch (NumberFormatException nufe) {
- break;
- }
- } else {
- return SdkVersionInfo.getApiByBuildCode(targetApi, true);
- }
- }
-
- Node parent = element.getParentNode();
- if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
- element = (Element) parent;
- } else {
- break;
- }
- }
-
- return -1;
- }
-
- private static void report(final ClassContext context, String message, AbstractInsnNode node,
- MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
- int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
-
- // If looking for a constructor, the string we'll see in the source is not the
- // method name (<init>) but the class name
- if (patternStart != null && patternStart.equals(CONSTRUCTOR_NAME)
- && node instanceof MethodInsnNode) {
- if (hints != null) {
- hints = hints.matchConstructor();
- }
- patternStart = ((MethodInsnNode) node).owner;
- }
-
- if (patternStart != null) {
- int index = patternStart.lastIndexOf('$');
- if (index != -1) {
- patternStart = patternStart.substring(index + 1);
- }
- index = patternStart.lastIndexOf('/');
- if (index != -1) {
- patternStart = patternStart.substring(index + 1);
- }
- }
-
- Location location = context.getLocationForLine(lineNumber, patternStart, patternEnd,
- hints);
- context.report(UNSUPPORTED, method, node, location, message);
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mPendingFields != null) {
- for (List<Pair<String, Location>> list : mPendingFields.values()) {
- for (Pair<String, Location> pair : list) {
- String message = pair.getFirst();
- Location location = pair.getSecond();
- context.report(INLINED, location, message);
- }
- }
- }
-
- super.afterCheckProject(context);
- }
-
-// ---- Implements JavaScanner ----
-
- @Nullable
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- if (mApiDatabase == null) {
- return new ForwardingAstVisitor() {
- };
- }
- return new ApiVisitor(context);
- }
-
- @Nullable
- @Override
- public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
- List<Class<? extends lombok.ast.Node>> types =
- new ArrayList<Class<? extends lombok.ast.Node>>(2);
- types.add(ImportDeclaration.class);
- types.add(Select.class);
- types.add(MethodDeclaration.class);
- types.add(ConstructorDeclaration.class);
- types.add(VariableDefinitionEntry.class);
- types.add(VariableReference.class);
- types.add(Try.class);
- return types;
- }
-
- /**
- * Checks whether the given instruction is a benign usage of a constant defined in
- * a later version of Android than the application's {@code minSdkVersion}.
- *
- * @param node the instruction to check
- * @param name the name of the constant
- * @param owner the field owner
- * @return true if the given usage is safe on older versions than the introduction
- * level of the constant
- */
- public static boolean isBenignConstantUsage(
- @Nullable lombok.ast.Node node,
- @NonNull String name,
- @NonNull String owner) {
- if (owner.equals("android/os/Build$VERSION_CODES")) { //$NON-NLS-1$
- // These constants are required for compilation, not execution
- // and valid code checks it even on older platforms
- return true;
- }
- if (owner.equals("android/view/ViewGroup$LayoutParams") //$NON-NLS-1$
- && name.equals("MATCH_PARENT")) { //$NON-NLS-1$
- return true;
- }
- if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$
- && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$
- || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$
- || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$
- // android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1,
- // but in API 11 it was moved up to the parent class AbsListView.
- // Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11,
- // but the constant is the same as the older version, so accept this without
- // warning.
- return true;
- }
-
- // Gravity#START and Gravity#END are okay; these were specifically written to
- // be backwards compatible (by using the same lower bits for START as LEFT and
- // for END as RIGHT)
- if ("android/view/Gravity".equals(owner) //$NON-NLS-1$
- && ("START".equals(name) || "END".equals(name))) { //$NON-NLS-1$ //$NON-NLS-2$
- return true;
- }
-
- if (node == null) {
- return false;
- }
-
- // It's okay to reference the constant as a case constant (since that
- // code path won't be taken) or in a condition of an if statement
- lombok.ast.Node curr = node.getParent();
- while (curr != null) {
- Class<? extends lombok.ast.Node> nodeType = curr.getClass();
- if (nodeType == Case.class) {
- Case caseStatement = (Case) curr;
- Expression condition = caseStatement.astCondition();
- return condition != null && isAncestor(condition, node);
- } else if (nodeType == If.class) {
- If ifStatement = (If) curr;
- Expression condition = ifStatement.astCondition();
- return condition != null && isAncestor(condition, node);
- } else if (nodeType == InlineIfExpression.class) {
- InlineIfExpression ifStatement = (InlineIfExpression) curr;
- Expression condition = ifStatement.astCondition();
- return condition != null && isAncestor(condition, node);
- }
- curr = curr.getParent();
- }
-
- return false;
- }
-
- private static boolean isAncestor(
- @NonNull lombok.ast.Node ancestor,
- @Nullable lombok.ast.Node node) {
- while (node != null) {
- if (node == ancestor) {
- return true;
- }
- node = node.getParent();
- }
-
- return false;
- }
-
- private final class ApiVisitor extends ForwardingAstVisitor {
- private JavaContext mContext;
- private Map<String, String> mClassToImport = Maps.newHashMap();
- private List<String> mStarImports;
- private Set<String> mLocalVars;
- private lombok.ast.Node mCurrentMethod;
- private Set<String> mFields;
- private List<String> mStaticStarImports;
-
- private ApiVisitor(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitImportDeclaration(ImportDeclaration node) {
- if (node.astStarImport()) {
- // Similarly, if you're inheriting from a constants class, figure out
- // how that works... :=(
- String fqcn = node.asFullyQualifiedName();
- int strip = fqcn.lastIndexOf('*');
- if (strip != -1) {
- strip = fqcn.lastIndexOf('.', strip);
- if (strip != -1) {
- String pkgName = getInternalName(fqcn.substring(0, strip));
- if (ApiLookup.isRelevantOwner(pkgName)) {
- if (node.astStaticImport()) {
- if (mStaticStarImports == null) {
- mStaticStarImports = Lists.newArrayList();
- }
- mStaticStarImports.add(pkgName);
- } else {
- if (mStarImports == null) {
- mStarImports = Lists.newArrayList();
- }
- mStarImports.add(pkgName);
- }
- }
- }
- }
- } else if (node.astStaticImport()) {
- String fqcn = node.asFullyQualifiedName();
- String fieldName = getInternalName(fqcn);
- int index = fieldName.lastIndexOf('$');
- if (index != -1) {
- String owner = fieldName.substring(0, index);
- String name = fieldName.substring(index + 1);
- checkField(node, name, owner);
- }
- } else {
- // Store in map -- if it's "one of ours"
- // Use override detector's map for that purpose
- String fqcn = node.asFullyQualifiedName();
-
- int last = fqcn.lastIndexOf('.');
- if (last != -1) {
- String className = fqcn.substring(last + 1);
- mClassToImport.put(className, fqcn);
- }
- }
-
- return super.visitImportDeclaration(node);
- }
-
- @Override
- public boolean visitSelect(Select node) {
- boolean result = super.visitSelect(node);
-
- if (node.getParent() instanceof Select) {
- // We only want to look at the leaf expressions; e.g. if you have
- // "foo.bar.baz" we only care about the select foo.bar.baz, not foo.bar
- return result;
- }
-
- // See if this corresponds to a field reference. We assume it's a field if
- // it's a select (x.y) and either the identifier y is capitalized (e.g.
- // foo.VIEW_MASK) or if it's a member of an R class (R.id.foo).
- String name = node.astIdentifier().astValue();
- boolean isField = Character.isUpperCase(name.charAt(0));
- if (!isField) {
- // See if there's an R class
- Select current = node;
- while (current != null) {
- Expression operand = current.astOperand();
- if (operand instanceof Select) {
- current = (Select) operand;
- if (R_CLASS.equals(current.astIdentifier().astValue())) {
- isField = true;
- break;
- }
- } else if (operand instanceof VariableReference) {
- VariableReference reference = (VariableReference) operand;
- if (R_CLASS.equals(reference.astIdentifier().astValue())) {
- isField = true;
- }
- break;
- } else {
- break;
- }
- }
- }
-
- if (isField) {
- Expression operand = node.astOperand();
- if (operand.getClass() == Select.class) {
- // Possibly a fully qualified name in place
- String cls = operand.toString();
-
- // See if it's an imported class with an inner class
- // (e.g. Manifest.permission.FIELD)
- if (Character.isUpperCase(cls.charAt(0))) {
- int firstDot = cls.indexOf('.');
- if (firstDot != -1) {
- String base = cls.substring(0, firstDot);
- String fqcn = mClassToImport.get(base);
- if (fqcn != null) {
- // Yes imported
- String owner = getInternalName(fqcn + cls.substring(firstDot));
- checkField(node, name, owner);
- return result;
- }
-
- // Might be a star import: have to iterate and check here
- if (mStarImports != null) {
- for (String packagePrefix : mStarImports) {
- String owner = getInternalName(packagePrefix + '/' + cls);
- if (checkField(node, name, owner)) {
- mClassToImport.put(name, owner);
- return result;
- }
- }
- }
- }
- }
-
- // See if it's a fully qualified reference in place
- String owner = getInternalName(cls);
- checkField(node, name, owner);
- return result;
- } else if (operand.getClass() == VariableReference.class) {
- String className = ((VariableReference) operand).astIdentifier().astValue();
- // Not a FQCN that we care about: look in imports
- String fqcn = mClassToImport.get(className);
- if (fqcn != null) {
- // Yes imported
- String owner = getInternalName(fqcn);
- checkField(node, name, owner);
- return result;
- }
-
- if (Character.isUpperCase(className.charAt(0))) {
- // Might be a star import: have to iterate and check here
- if (mStarImports != null) {
- for (String packagePrefix : mStarImports) {
- String owner = getInternalName(packagePrefix) + '/' + className;
- if (checkField(node, name, owner)) {
- mClassToImport.put(name, owner);
- return result;
- }
- }
- }
- }
- }
- }
- return result;
- }
-
- @Override
- public boolean visitVariableReference(VariableReference node) {
- boolean result = super.visitVariableReference(node);
-
- if (node.getParent() != null) {
- lombok.ast.Node parent = node.getParent();
- Class<? extends lombok.ast.Node> parentClass = parent.getClass();
- if (parentClass == Select.class
- || parentClass == Switch.class // look up on the switch expression type
- || parentClass == Case.class
- || parentClass == ConstructorInvocation.class
- || parentClass == SuperConstructorInvocation.class
- || parentClass == AnnotationElement.class) {
- return result;
- }
-
- if (parent instanceof MethodInvocation &&
- ((MethodInvocation) parent).astOperand() == node) {
- return result;
- } else if (parent instanceof BinaryExpression) {
- BinaryExpression expression = (BinaryExpression) parent;
- if (expression.astLeft() == node) {
- return result;
- }
- }
- }
-
- String name = node.astIdentifier().astValue();
- if (Character.isUpperCase(name.charAt(0))
- && (mLocalVars == null || !mLocalVars.contains(name))
- && (mFields == null || !mFields.contains(name))) {
- // Potential field reference: check it
- if (mStaticStarImports != null) {
- for (String owner : mStaticStarImports) {
- if (checkField(node, name, owner)) {
- break;
- }
- }
- }
- }
-
- return result;
- }
-
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- if (mCurrentMethod != null) {
- if (mLocalVars == null) {
- mLocalVars = Sets.newHashSet();
- }
- mLocalVars.add(node.astName().astValue());
- } else {
- if (mFields == null) {
- mFields = Sets.newHashSet();
- }
- mFields.add(node.astName().astValue());
- }
- return super.visitVariableDefinitionEntry(node);
- }
-
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- mLocalVars = null;
- mCurrentMethod = node;
- return super.visitMethodDeclaration(node);
- }
-
- @Override
- public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
- mLocalVars = null;
- mCurrentMethod = node;
- return super.visitConstructorDeclaration(node);
- }
-
- @Override
- public boolean visitTry(Try node) {
- Object nativeNode = node.getNativeNode();
- if (nativeNode != null && nativeNode.getClass().getName().equals(
- "org.eclipse.jdt.internal.compiler.ast.TryStatement")) {
- boolean isTryWithResources = false;
- try {
- Field field = nativeNode.getClass().getDeclaredField("resources");
- Object value = field.get(nativeNode);
- if (value instanceof Object[]) {
- Object[] resources = (Object[]) value;
- isTryWithResources = resources.length > 0;
- }
- } catch (NoSuchFieldException e) {
- // Unexpected: ECJ parser internals have changed; can't detect try block
- } catch (IllegalAccessException e) {
- // Unexpected: ECJ parser internals have changed; can't detect try block
- }
- if (isTryWithResources) {
- int minSdk = getMinSdk(mContext);
- int api = 19; // minSdk for try with resources
- if (api > minSdk && api > getLocalMinSdk(node)) {
- Location location = mContext.getLocation(node);
- String message = String.format("Try-with-resources requires "
- + "API level %1$d (current min is %2$d)", api, minSdk);
- LintDriver driver = mContext.getDriver();
- if (!driver.isSuppressed(mContext, UNSUPPORTED, node)) {
- mContext.report(UNSUPPORTED, node, location, message);
- }
- }
- } else {
- // Special case: check types of catch block variables; these apparently
- // need to be available at runtime even if there are no explicit calls
- for (Catch c : node.astCatches()) {
- VariableDefinition variableDefinition = c.astExceptionDeclaration();
- TypeReference typeReference = variableDefinition.astTypeReference();
- String fqcn = null;
- JavaParser.ResolvedNode resolved = mContext.resolve(typeReference);
- if (resolved != null) {
- fqcn = resolved.getSignature();
- } else if (typeReference.getTypeName().equals(
- "ReflectiveOperationException")) {
- fqcn = "java.lang.ReflectiveOperationException";
- }
- if (fqcn != null) {
- String owner = getInternalName(fqcn);
- int api = mApiDatabase.getClassVersion(owner);
- int minSdk = getMinSdk(mContext);
- if (api > minSdk && api > getLocalMinSdk(typeReference)) {
- Location location = mContext.getLocation(typeReference);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
- LintDriver driver = mContext.getDriver();
- if (!driver.isSuppressed(mContext, UNSUPPORTED, typeReference)) {
- mContext.report(UNSUPPORTED, typeReference, location, message);
- }
- }
- }
- }
- }
- }
-
- return super.visitTry(node);
- }
-
- @Override
- public void endVisit(lombok.ast.Node node) {
- if (node == mCurrentMethod) {
- mCurrentMethod = null;
- }
- super.endVisit(node);
- }
-
- /**
- * Checks a Java source field reference. Returns true if the field is known
- * regardless of whether it's an invalid field or not
- */
- private boolean checkField(
- @NonNull lombok.ast.Node node,
- @NonNull String name,
- @NonNull String owner) {
- int api = mApiDatabase.getFieldVersion(owner, name);
- if (api != -1) {
- int minSdk = getMinSdk(mContext);
- if (api > minSdk
- && api > getLocalMinSdk(node)) {
- if (isBenignConstantUsage(node, name, owner)) {
- return true;
- }
-
- Location location = mContext.getLocation(node);
- String fqcn = getFqcn(owner) + '#' + name;
-
- if (node instanceof ImportDeclaration) {
- // Replace import statement location range with just
- // the identifier part
- ImportDeclaration d = (ImportDeclaration) node;
- int startOffset = d.astParts().first().getPosition().getStart();
- Position start = location.getStart();
- int startColumn = start.getColumn();
- int startLine = start.getLine();
- start = new DefaultPosition(startLine,
- startColumn + startOffset - start.getOffset(), startOffset);
- int fqcnLength = fqcn.length();
- Position end = new DefaultPosition(startLine,
- start.getColumn() + fqcnLength,
- start.getOffset() + fqcnLength);
- location = Location.create(location.getFile(), start, end);
- }
-
- String message = String.format(
- "Field requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
-
- LintDriver driver = mContext.getDriver();
- if (driver.isSuppressed(mContext, INLINED, node)) {
- return true;
- }
-
- // Also allow to suppress these issues with NewApi, since some
- // fields used to get identified that way
- if (driver.isSuppressed(mContext, UNSUPPORTED, node)) {
- return true;
- }
-
- // We can't report the issue right away; we don't yet know if
- // this is an actual inlined (static primitive or String) yet.
- // So just make a note of it, and report these after the project
- // checking has finished; any fields that aren't inlined will be
- // cleared when they're noticed by the class check.
- if (mPendingFields == null) {
- mPendingFields = Maps.newHashMapWithExpectedSize(20);
- }
- List<Pair<String, Location>> list = mPendingFields.get(fqcn);
- if (list == null) {
- list = new ArrayList<Pair<String, Location>>();
- mPendingFields.put(fqcn, list);
- } else {
- // See if this location already exists. This can happen if
- // we have multiple references to an inlined field on the same
- // line. Since the class file only gives us line information, we
- // can't distinguish between these in the client as separate usages,
- // so they end up being identical errors.
- for (Pair<String, Location> pair : list) {
- Location existingLocation = pair.getSecond();
- if (location.getFile().equals(existingLocation.getFile())) {
- Position start = location.getStart();
- Position existingStart = existingLocation.getStart();
- if (start != null && existingStart != null
- && start.getLine() == existingStart.getLine()) {
- return true;
- }
- }
- }
- }
- list.add(Pair.of(message, location));
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Returns the minimum SDK to use according to the given AST node, or null
- * if no {@code TargetApi} annotations were found
- *
- * @return the API level to use for this node, or -1
- */
- public int getLocalMinSdk(@Nullable lombok.ast.Node scope) {
- while (scope != null) {
- Class<? extends lombok.ast.Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == VariableDefinition.class) {
- // Variable
- VariableDefinition declaration = (VariableDefinition) scope;
- int targetApi = getTargetApi(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- } else if (type == MethodDeclaration.class) {
- // Method
- // Look for annotations on the method
- MethodDeclaration declaration = (MethodDeclaration) scope;
- int targetApi = getTargetApi(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- } else if (type == ConstructorDeclaration.class) {
- // Constructor
- // Look for annotations on the method
- ConstructorDeclaration declaration = (ConstructorDeclaration) scope;
- int targetApi = getTargetApi(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- } else if (type == ClassDeclaration.class) {
- // Class
- ClassDeclaration declaration = (ClassDeclaration) scope;
- int targetApi = getTargetApi(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- }
-
- scope = scope.getParent();
- }
-
- return -1;
- }
- }
-
- /**
- * Returns the API level for the given AST node if specified with
- * an {@code @TargetApi} annotation.
- *
- * @param modifiers the modifier to check
- * @return the target API level, or -1 if not specified
- */
- public static int getTargetApi(@Nullable Modifiers modifiers) {
- if (modifiers == null) {
- return -1;
- }
- StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations();
- if (annotations == null) {
- return -1;
- }
-
- for (Annotation annotation : annotations) {
- TypeReference t = annotation.astAnnotationTypeReference();
- String typeName = t.getTypeName();
- if (typeName.endsWith(TARGET_API)) {
- StrictListAccessor<AnnotationElement, Annotation> values =
- annotation.astElements();
- if (values != null) {
- for (AnnotationElement element : values) {
- AnnotationValue valueNode = element.astValue();
- if (valueNode == null) {
- continue;
- }
- if (valueNode instanceof IntegralLiteral) {
- IntegralLiteral literal = (IntegralLiteral) valueNode;
- return literal.astIntValue();
- } else if (valueNode instanceof StringLiteral) {
- String value = ((StringLiteral) valueNode).astValue();
- return SdkVersionInfo.getApiByBuildCode(value, true);
- } else if (valueNode instanceof Select) {
- Select select = (Select) valueNode;
- String codename = select.astIdentifier().astValue();
- return SdkVersionInfo.getApiByBuildCode(codename, true);
- } else if (valueNode instanceof VariableReference) {
- VariableReference reference = (VariableReference) valueNode;
- String codename = reference.astIdentifier().astValue();
- return SdkVersionInfo.getApiByBuildCode(codename, true);
- }
- }
- }
- }
- }
-
- return -1;
- }
-
- public static int getRequiredVersion(@NonNull Issue issue, @NonNull String errorMessage,
- @NonNull TextFormat format) {
- errorMessage = format.toText(errorMessage);
-
- if (issue == UNSUPPORTED || issue == INLINED) {
- Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); //$NON-NLS-1$
- Matcher matcher = pattern.matcher(errorMessage);
- if (matcher.find()) {
- return Integer.parseInt(matcher.group(1));
- }
- }
-
- return -1;
- }
-
- private static boolean isWithinSdkConditional(
- @NonNull ClassContext context,
- @NonNull ClassNode classNode,
- @NonNull MethodNode method,
- @NonNull AbstractInsnNode call,
- int requiredApi) {
- assert requiredApi != -1;
-
- if (!containsSimpleSdkCheck(method)) {
- return false;
- }
-
- try {
- // Search in the control graph, from beginning, up to the target call
- // node, to see if it's reachable. The call graph is constructed in a
- // special way: we include all control flow edges, *except* those that
- // are satisfied by a SDK_INT version check (where the operand is a version
- // that is at least as high as the one needed for the given call).
- //
- // If we can reach the call, that means that there is a way this call
- // can be reached on some versions, and lint should flag the call/field lookup.
- //
- //
- // Let's say you have code like this:
- // if (SDK_INT >= LOLLIPOP) {
- // // Call
- // return property.hasAdjacentMapping();
- // }
- // ...
- //
- // The compiler will turn this into the following byte code:
- //
- // 0: getstatic #3; //Field android/os/Build$VERSION.SDK_INT:I
- // 3: bipush 21
- // 5: if_icmple 17
- // 8: aload_1
- // 9: invokeinterface #4, 1; //InterfaceMethod
- // android/view/ViewDebug$ExportedProperty.hasAdjacentMapping:()Z
- // 14: ifeq 17
- // 17: ... code after if loop
- //
- // When the call graph is constructed, for an if branch we're called twice; once
- // where the target is the next instruction (the one taken if byte code check is false)
- // and one to the jump label (the one taken if the byte code condition is true).
- //
- // Notice how at the byte code level, the logic is reversed: the >= instruction
- // is turned into "<" and we jump to the code *after* the if clause; otherwise
- // it will just fall through. Therefore, if we take a byte code branch, that means
- // that the SDK check was *not* satisfied, and conversely, the target call is reachable
- // if we don't take the branch.
- //
- // Therefore, when we build the call graph, we will add call graph nodes for an
- // if check if :
- // (1) it is some other comparison than <, <= or !=.
- // (2) if the byte code comparison check is *not* satisfied, this means that the the
- // SDK check was successful and that the call graph should only include
- // the jump edge
- // (3) all other edges are added
- //
- // With a flow control graph like that, we can determine whether a target call
- // is guarded by a given SDK check: that will be the case if we cannot reach
- // the target call in the call graph
-
- ApiCheckGraph graph = new ApiCheckGraph(requiredApi);
- ControlFlowGraph.create(graph, classNode, method);
-
- // Note: To debug unit tests, you may want to for example do
- // ControlFlowGraph.Node callNode = graph.getNode(call);
- // Set<ControlFlowGraph.Node> highlight = Sets.newHashSet(callNode);
- // Files.write(graph.toDot(highlight), new File("/tmp/graph.gv"), Charsets.UTF_8);
- // This will generate a graphviz file you can visualize with the "dot" utility
- AbstractInsnNode first = method.instructions.get(0);
- return !graph.isConnected(first, call);
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
-
- return false;
- }
-
- private static boolean containsSimpleSdkCheck(@NonNull MethodNode method) {
- // Look for a compiled version of "if (Build.VERSION.SDK_INT op N) {"
- InsnList nodes = method.instructions;
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- if (isSdkVersionLookup(instruction)) {
- AbstractInsnNode bipush = getNextInstruction(instruction);
- if (bipush != null && bipush.getOpcode() == Opcodes.BIPUSH) {
- AbstractInsnNode ifNode = getNextInstruction(bipush);
- if (ifNode != null && ifNode.getType() == AbstractInsnNode.JUMP_INSN) {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- private static boolean isSdkVersionLookup(@NonNull AbstractInsnNode instruction) {
- if (instruction.getOpcode() == Opcodes.GETSTATIC) {
- FieldInsnNode fieldNode = (FieldInsnNode) instruction;
- return (SDK_INT.equals(fieldNode.name)
- && ANDROID_OS_BUILD_VERSION.equals(fieldNode.owner));
- }
- return false;
- }
-
- /**
- * Control flow graph which skips control flow edges that check
- * a given SDK_VERSION requirement that is not met by a given call
- */
- private static class ApiCheckGraph extends ControlFlowGraph {
- private final int mRequiredApi;
-
- public ApiCheckGraph(int requiredApi) {
- mRequiredApi = requiredApi;
- }
-
- @Override
- protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- if (from.getType() == AbstractInsnNode.JUMP_INSN &&
- from.getPrevious() != null &&
- from.getPrevious().getType() == AbstractInsnNode.INT_INSN) {
- IntInsnNode intNode = (IntInsnNode) from.getPrevious();
- if (intNode.getPrevious() != null && isSdkVersionLookup(intNode.getPrevious())) {
- JumpInsnNode jumpNode = (JumpInsnNode) from;
- int api = intNode.operand;
- boolean isJumpEdge = to == jumpNode.label;
- boolean includeEdge;
- switch (from.getOpcode()) {
- case Opcodes.IF_ICMPNE:
- includeEdge = api < mRequiredApi || isJumpEdge;
- break;
- case Opcodes.IF_ICMPLE:
- includeEdge = api < mRequiredApi - 1 || isJumpEdge;
- break;
- case Opcodes.IF_ICMPLT:
- includeEdge = api < mRequiredApi || isJumpEdge;
- break;
-
- case Opcodes.IF_ICMPGE:
- includeEdge = api < mRequiredApi || !isJumpEdge;
- break;
- case Opcodes.IF_ICMPGT:
- includeEdge = api < mRequiredApi - 1 || !isJumpEdge;
- break;
- default:
- // unexpected comparison for int API level
- includeEdge = true;
- }
- if (!includeEdge) {
- return;
- }
- }
- }
-
- super.add(from, to);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
deleted file mode 100644
index 4097a3f..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
+++ /dev/null
@@ -1,996 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PKG;
-import static com.android.SdkConstants.DOT_XML;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.PkgType;
-import com.android.sdklib.repository.local.LocalPkgInfo;
-import com.android.sdklib.repository.local.LocalSdk;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-import com.google.common.primitives.UnsignedBytes;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel.MapMode;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Database for API checking: Allows quick lookup of a given class, method or field
- * to see which API level it was introduced in.
- * <p>
- * This class is optimized for quick bytecode lookup used in conjunction with the
- * ASM library: It has lookup methods that take internal JVM signatures, and for a method
- * call for example it processes the owner, name and description parameters separately
- * the way they are provided from ASM.
- * <p>
- * The {@link Api} class provides access to the full Android API along with version
- * information, initialized from an XML file. This lookup class adds a binary cache around
- * the API to make initialization faster and to require fewer objects. It creates
- * a binary cache data structure, which fits in a single byte array, which means that
- * to open the database you can just read in the byte array and go. On one particular
- * machine, this takes about 30-50 ms versus 600-800ms for the full parse. It also
- * helps memory by placing everything in a compact byte array instead of needing separate
- * strings (2 bytes per character in a char[] for the 25k method entries, 11k field entries
- * and 6k class entries) - and it also avoids the same number of Map.Entry objects.
- * When creating the memory data structure it performs a few other steps to help memory:
- * <ul>
- * <li> It stores the strings as single bytes, since all the JVM signatures are in ASCII
- * <li> It strips out the method return types (which takes the binary size down from
- * about 4.7M to 4.0M)
- * <li> It strips out all APIs that have since=1, since the lookup only needs to find
- * classes, methods and fields that have an API level *higher* than 1. This drops
- * the memory use down from 4.0M to 1.7M.
- * </ul>
- */
-public class ApiLookup {
- /** Relative path to the api-versions.xml database file within the Lint installation */
- private static final String XML_FILE_PATH = "platform-tools/api/api-versions.xml"; //$NON-NLS-1$
- private static final String FILE_HEADER = "API database used by Android lint\000";
- private static final int BINARY_FORMAT_VERSION = 6;
- private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
- private static final boolean DEBUG_SEARCH = false;
- private static final boolean WRITE_STATS = false;
- /** Default size to reserve for each API entry when creating byte buffer to build up data */
- private static final int BYTES_PER_ENTRY = 36;
-
- private final Api mInfo;
- private byte[] mData;
- private int[] mIndices;
- private int mClassCount;
- private String[] mJavaPackages;
-
- private static WeakReference<ApiLookup> sInstance =
- new WeakReference<ApiLookup>(null);
-
- /**
- * Returns an instance of the API database
- *
- * @param client the client to associate with this database - used only for
- * logging. The database object may be shared among repeated invocations,
- * and in that case client used will be the one originally passed in.
- * In other words, this parameter may be ignored if the client created
- * is not new.
- * @return a (possibly shared) instance of the API database, or null
- * if its data can't be found
- */
- @Nullable
- public static ApiLookup get(@NonNull LintClient client) {
- synchronized (ApiLookup.class) {
- ApiLookup db = sInstance.get();
- if (db == null) {
- File file = client.findResource(XML_FILE_PATH);
- if (file == null) {
- // AOSP build environment?
- String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
- if (build != null) {
- file = new File(build, "development/sdk/api-versions.xml" //$NON-NLS-1$
- .replace('/', File.separatorChar));
- }
- }
-
- if (file == null || !file.exists()) {
- return null;
- } else {
- db = get(client, file);
- }
- sInstance = new WeakReference<ApiLookup>(db);
- }
-
- return db;
- }
- }
-
- @VisibleForTesting
- @Nullable
- static String getPlatformVersion(@NonNull LintClient client) {
- LocalSdk sdk = client.getSdk();
- if (sdk != null) {
- LocalPkgInfo pkgInfo = sdk.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
- if (pkgInfo != null) {
- FullRevision version = pkgInfo.getDesc().getFullRevision();
- if (version != null) {
- return version.toShortString();
- }
- }
- }
-
- return null;
- }
-
- @VisibleForTesting
- @NonNull
- static String getCacheFileName(@NonNull String xmlFileName, @Nullable String platformVersion) {
- if (LintUtils.endsWith(xmlFileName, DOT_XML)) {
- xmlFileName = xmlFileName.substring(0, xmlFileName.length() - DOT_XML.length());
- }
-
- StringBuilder sb = new StringBuilder(100);
- sb.append(xmlFileName);
-
- // Incorporate version number in the filename to avoid upgrade filename
- // conflicts on Windows (such as issue #26663)
- sb.append('-').append(BINARY_FORMAT_VERSION);
-
- if (platformVersion != null) {
- sb.append('-').append(platformVersion);
- }
-
- sb.append(".bin"); //$NON-NLS-1$
- return sb.toString();
- }
-
- /**
- * Returns an instance of the API database
- *
- * @param client the client to associate with this database - used only for
- * logging
- * @param xmlFile the XML file containing configuration data to use for this
- * database
- * @return a (possibly shared) instance of the API database, or null
- * if its data can't be found
- */
- private static ApiLookup get(LintClient client, File xmlFile) {
- if (!xmlFile.exists()) {
- client.log(null, "The API database file %1$s does not exist", xmlFile);
- return null;
- }
-
- File cacheDir = client.getCacheDir(true/*create*/);
- if (cacheDir == null) {
- cacheDir = xmlFile.getParentFile();
- }
-
- String platformVersion = getPlatformVersion(client);
- File binaryData = new File(cacheDir, getCacheFileName(xmlFile.getName(), platformVersion));
-
- if (DEBUG_FORCE_REGENERATE_BINARY) {
- System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom "
- + xmlFile + "\nto " + binaryData);
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified()
- || binaryData.length() == 0) {
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- }
-
- if (!binaryData.exists()) {
- client.log(null, "The API database file %1$s does not exist", binaryData);
- return null;
- }
-
- return new ApiLookup(client, xmlFile, binaryData, null);
- }
-
- private static boolean createCache(LintClient client, File xmlFile, File binaryData) {
- long begin = 0;
- if (WRITE_STATS) {
- begin = System.currentTimeMillis();
- }
-
- Api info = Api.parseApi(xmlFile);
-
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("Reading XML data structures took " + (end - begin) + " ms)");
- }
-
- if (info != null) {
- try {
- writeDatabase(binaryData, info);
- return true;
- } catch (IOException ioe) {
- client.log(ioe, "Can't write API cache file");
- }
- }
-
- return false;
- }
-
- /** Use one of the {@link #get} factory methods instead */
- private ApiLookup(
- @NonNull LintClient client,
- @NonNull File xmlFile,
- @Nullable File binaryFile,
- @Nullable Api info) {
- mInfo = info;
-
- if (binaryFile != null) {
- readData(client, xmlFile, binaryFile);
- }
- }
-
- /**
- * Database format:
- * <pre>
- * 1. A file header, which is the exact contents of {@link #FILE_HEADER} encoded
- * as ASCII characters. The purpose of the header is to identify what the file
- * is for, for anyone attempting to open the file.
- * 2. A file version number. If the binary file does not match the reader's expected
- * version, it can ignore it (and regenerate the cache from XML).
- * 3. The number of classes [1 int]
- * 4. The number of members (across all classes) [1 int].
- * 5. The number of java/javax packages [1 int]
- * 6. The java/javax package name table. Each item consists of a byte count for
- * the package string (as 1 byte) followed by the UTF-8 encoded bytes for each package.
- * These are in sorted order.
- * 7. Class offset table (one integer per class, pointing to the byte offset in the
- * file (relative to the beginning of the file) where each class begins.
- * The classes are always sorted alphabetically by fully qualified name.
- * 8. Member offset table (one integer per member, pointing to the byte offset in the
- * file (relative to the beginning of the file) where each member entry begins.
- * The members are always sorted alphabetically.
- * 9. Class entry table. Each class entry consists of the fully qualified class name,
- * in JVM format (using / instead of . in package names and $ for inner classes),
- * followed by the byte 0 as a terminator, followed by the API version as a byte.
- * 10. Member entry table. Each member entry consists of the class number (as a short),
- * followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
- * signature terminator, followed by the API level as a byte.
- * <p>
- * TODO: Pack the offsets: They increase by a small amount for each entry, so no need
- * to spend 4 bytes on each. These will need to be processed when read back in anyway,
- * so consider storing the offset -deltas- as single bytes and adding them up cumulatively
- * in readData().
- * </pre>
- */
- private void readData(@NonNull LintClient client, @NonNull File xmlFile,
- @NonNull File binaryFile) {
- if (!binaryFile.exists()) {
- client.log(null, "%1$s does not exist", binaryFile);
- return;
- }
- long start = System.currentTimeMillis();
- try {
- MappedByteBuffer buffer = Files.map(binaryFile, MapMode.READ_ONLY);
- assert buffer.order() == ByteOrder.BIG_ENDIAN;
-
- // First skip the header
- byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII);
- buffer.rewind();
- for (int offset = 0; offset < expectedHeader.length; offset++) {
- if (expectedHeader[offset] != buffer.get()) {
- client.log(null, "Incorrect file header: not an API database cache " +
- "file, or a corrupt cache file");
- return;
- }
- }
-
- // Read in the format number
- if (buffer.get() != BINARY_FORMAT_VERSION) {
- // Force regeneration of new binary data with up to date format
- if (createCache(client, xmlFile, binaryFile)) {
- readData(client, xmlFile, binaryFile); // Recurse
- }
-
- return;
- }
-
- mClassCount = buffer.getInt();
- int methodCount = buffer.getInt();
-
- int javaPackageCount = buffer.getInt();
- // Read in the Java packages
- mJavaPackages = new String[javaPackageCount];
- for (int i = 0; i < javaPackageCount; i++) {
- int count = UnsignedBytes.toInt(buffer.get());
- byte[] bytes = new byte[count];
- buffer.get(bytes, 0, count);
- mJavaPackages[i] = new String(bytes, Charsets.UTF_8);
- }
-
- // Read in the class table indices;
- int count = mClassCount + methodCount;
- int[] offsets = new int[count];
-
- // Another idea: I can just store the DELTAS in the file (and add them up
- // when reading back in) such that it takes just ONE byte instead of four!
-
- for (int i = 0; i < count; i++) {
- offsets[i] = buffer.getInt();
- }
-
- // No need to read in the rest -- we'll just keep the whole byte array in memory
- // TODO: Make this code smarter/more efficient.
- int size = buffer.limit();
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- mData = b;
- mIndices = offsets;
-
- // TODO: We only need to keep the data portion here since we've initialized
- // the offset array separately.
- // TODO: Investigate (profile) accessing the byte buffer directly instead of
- // accessing a byte array.
- } catch (Throwable e) {
- client.log(null, "Failure reading binary cache file %1$s", binaryFile.getPath());
- client.log(null, "Please delete the file and restart the IDE/lint: %1$s",
- binaryFile.getPath());
- client.log(e, null);
- }
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("\nRead API database in " + (end - start)
- + " milliseconds.");
- System.out.println("Size of data table: " + mData.length + " bytes ("
- + Integer.toString(mData.length / 1024) + "k)\n");
- }
- }
-
- /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */
- private static void writeDatabase(File file, Api info) throws IOException {
- /*
- * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- * as ASCII characters. The purpose of the header is to identify what the file
- * is for, for anyone attempting to open the file.
- * 2. A file version number. If the binary file does not match the reader's expected
- * version, it can ignore it (and regenerate the cache from XML).
- */
- Map<String, ApiClass> classMap = info.getClasses();
- // Write the class table
-
- List<String> classes = new ArrayList<String>(classMap.size());
- Map<ApiClass, List<String>> memberMap =
- Maps.newHashMapWithExpectedSize(classMap.size());
- int memberCount = 0;
- Set<String> javaPackageSet = Sets.newHashSetWithExpectedSize(70);
- for (Map.Entry<String, ApiClass> entry : classMap.entrySet()) {
- String className = entry.getKey();
- ApiClass apiClass = entry.getValue();
-
- if (className.startsWith("java/") //$NON-NLS-1$
- || className.startsWith("javax/")) { //$NON-NLS-1$
- String pkg = apiClass.getPackage();
- javaPackageSet.add(pkg);
- }
-
- if (!isRelevantOwner(className)) {
- System.out.println("Warning: The isRelevantOwner method does not pass "
- + className);
- }
-
- Set<String> allMethods = apiClass.getAllMethods(info);
- Set<String> allFields = apiClass.getAllFields(info);
-
- // Strip out all members that have been supported since version 1.
- // This makes the database *much* leaner (down from about 4M to about
- // 1.7M), and this just fills the table with entries that ultimately
- // don't help the API checker since it just needs to know if something
- // requires a version *higher* than the minimum. If in the future the
- // database needs to answer queries about whether a method is public
- // or not, then we'd need to put this data back in.
- List<String> members = new ArrayList<String>(allMethods.size() + allFields.size());
- for (String member : allMethods) {
-
- Integer since = apiClass.getMethod(member, info);
- if (since == null) {
- assert false : className + ':' + member;
- since = 1;
- }
- if (since != 1) {
- members.add(member);
- }
- }
-
- // Strip out all members that have been supported since version 1.
- // This makes the database *much* leaner (down from about 4M to about
- // 1.7M), and this just fills the table with entries that ultimately
- // don't help the API checker since it just needs to know if something
- // requires a version *higher* than the minimum. If in the future the
- // database needs to answer queries about whether a method is public
- // or not, then we'd need to put this data back in.
- for (String member : allFields) {
- Integer since = apiClass.getField(member, info);
- if (since == null) {
- assert false : className + ':' + member;
- since = 1;
- }
- if (since != 1) {
- members.add(member);
- }
- }
-
- // Only include classes that have one or more members requiring version 2 or higher:
- if (!members.isEmpty()) {
- classes.add(className);
- memberMap.put(apiClass, members);
- memberCount += members.size();
- }
- }
- Collections.sort(classes);
-
- List<String> javaPackages = Lists.newArrayList(javaPackageSet);
- Collections.sort(javaPackages);
- int javaPackageCount = javaPackages.size();
-
- int entryCount = classMap.size() + memberCount;
- int capacity = entryCount * BYTES_PER_ENTRY;
- ByteBuffer buffer = ByteBuffer.allocate(capacity);
- buffer.order(ByteOrder.BIG_ENDIAN);
- // 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- // as ASCII characters. The purpose of the header is to identify what the file
- // is for, for anyone attempting to open the file.
-
- buffer.put(FILE_HEADER.getBytes(Charsets.US_ASCII));
-
- // 2. A file version number. If the binary file does not match the reader's expected
- // version, it can ignore it (and regenerate the cache from XML).
- buffer.put((byte) BINARY_FORMAT_VERSION);
-
- // 3. The number of classes [1 int]
- buffer.putInt(classes.size());
-
- // 4. The number of members (across all classes) [1 int].
- buffer.putInt(memberCount);
-
- // 5. The number of Java packages [1 int].
- buffer.putInt(javaPackageCount);
-
- // 6. The Java package table. There are javaPackage.size() entries, where each entry
- // consists of a string length, as a byte, followed by the bytes in the package.
- // There is no terminating 0.
- for (String pkg : javaPackages) {
- byte[] bytes = pkg.getBytes(Charsets.UTF_8);
- assert bytes.length < 255 : pkg;
- buffer.put((byte) bytes.length);
- buffer.put(bytes);
- }
-
- // 7. Class offset table (one integer per class, pointing to the byte offset in the
- // file (relative to the beginning of the file) where each class begins.
- // The classes are always sorted alphabetically by fully qualified name.
- int classOffsetTable = buffer.position();
-
- // Reserve enough room for the offset table here: we will backfill it with pointers
- // as we're writing out the data structures below
- for (int i = 0, n = classes.size(); i < n; i++) {
- buffer.putInt(0);
- }
-
- // 8. Member offset table (one integer per member, pointing to the byte offset in the
- // file (relative to the beginning of the file) where each member entry begins.
- // The members are always sorted alphabetically.
- int methodOffsetTable = buffer.position();
- for (int i = 0, n = memberCount; i < n; i++) {
- buffer.putInt(0);
- }
-
- int nextEntry = buffer.position();
- int nextOffset = classOffsetTable;
-
- // 9. Class entry table. Each class entry consists of the fully qualified class name,
- // in JVM format (using / instead of . in package names and $ for inner classes),
- // followed by the byte 0 as a terminator, followed by the API version as a byte.
- for (String clz : classes) {
- buffer.position(nextOffset);
- buffer.putInt(nextEntry);
- nextOffset = buffer.position();
- buffer.position(nextEntry);
- buffer.put(clz.getBytes(Charsets.UTF_8));
- buffer.put((byte) 0);
-
- ApiClass apiClass = classMap.get(clz);
- assert apiClass != null : clz;
- int since = apiClass.getSince();
- assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits
- buffer.put((byte) since);
-
- nextEntry = buffer.position();
- }
-
- // 10. Member entry table. Each member entry consists of the class number (as a short),
- // followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
- // signature terminator, followed by the API level as a byte.
- assert nextOffset == methodOffsetTable;
-
- for (int classNumber = 0, n = classes.size(); classNumber < n; classNumber++) {
- String clz = classes.get(classNumber);
- ApiClass apiClass = classMap.get(clz);
- assert apiClass != null : clz;
- List<String> members = memberMap.get(apiClass);
- Collections.sort(members);
-
- for (String member : members) {
- buffer.position(nextOffset);
- buffer.putInt(nextEntry);
- nextOffset = buffer.position();
- buffer.position(nextEntry);
-
- Integer since;
- if (member.indexOf('(') != -1) {
- since = apiClass.getMethod(member, info);
- } else {
- since = apiClass.getField(member, info);
- }
- if (since == null) {
- assert false : clz + ':' + member;
- since = 1;
- }
-
- assert classNumber == (short) classNumber;
- buffer.putShort((short) classNumber);
- byte[] signature = member.getBytes(Charsets.UTF_8);
- for (int i = 0; i < signature.length; i++) {
- // Make sure all signatures are really just simple ASCII
- byte b = signature[i];
- assert b == (b & 0x7f) : member;
- buffer.put(b);
- // Skip types on methods
- if (b == (byte) ')') {
- break;
- }
- }
- buffer.put((byte) 0);
- int api = since;
- assert api == UnsignedBytes.toInt((byte) api);
- //assert api >= 1 && api < 0xFF; // max that fits in a byte
- buffer.put((byte) api);
- nextEntry = buffer.position();
- }
- }
-
- int size = buffer.position();
- assert size <= buffer.limit();
- buffer.mark();
-
- if (WRITE_STATS) {
- System.out.println("Wrote " + classes.size() + " classes and "
- + memberCount + " member entries");
- System.out.print("Actual binary size: " + size + " bytes");
- System.out.println(String.format(" (%.1fM)", size/(1024*1024.f)));
-
- System.out.println("Allocated size: " + (entryCount * BYTES_PER_ENTRY) + " bytes");
- System.out.println("Required bytes per entry: " + (size/ entryCount) + " bytes");
- }
-
- // Now dump this out as a file
- // There's probably an API to do this more efficiently; TODO: Look into this.
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- if (file.exists()) {
- file.delete();
- }
- FileOutputStream output = Files.newOutputStreamSupplier(file).getOutput();
- output.write(b);
- output.close();
- }
-
- // For debugging only
- private String dumpEntry(int offset) {
- if (DEBUG_SEARCH) {
- StringBuilder sb = new StringBuilder(200);
- for (int i = offset; i < mData.length; i++) {
- if (mData[i] == 0) {
- break;
- }
- char c = (char) UnsignedBytes.toInt(mData[i]);
- sb.append(c);
- }
-
- return sb.toString();
- } else {
- return "<disabled>"; //$NON-NLS-1$
- }
- }
-
- private static int compare(byte[] data, int offset, byte terminator, String s, int max) {
- int i = offset;
- int j = 0;
- for (; j < max; i++, j++) {
- byte b = data[i];
- char c = s.charAt(j);
- // TODO: Check somewhere that the strings are purely in the ASCII range; if not
- // they're not a match in the database
- byte cb = (byte) c;
- int delta = b - cb;
- if (delta != 0) {
- return delta;
- }
- }
-
- return data[i] - terminator;
- }
-
- /**
- * Quick determination whether a given class name is possibly interesting; this
- * is a quick package prefix check to determine whether we need to consider
- * the class at all. This let's us do less actual searching for the vast majority
- * of APIs (in libraries, application code etc) that have nothing to do with the
- * APIs in our packages.
- * @param name the class name in VM format (e.g. using / instead of .)
- * @return true if the owner is <b>possibly</b> relevant
- */
- public static boolean isRelevantClass(String name) {
- // TODO: Add quick switching here. This is tied to the database file so if
- // we end up with unexpected prefixes there, this could break. For that reason,
- // for now we consider everything relevant.
- return true;
- }
-
- /**
- * Returns the API version required by the given class reference,
- * or -1 if this is not a known API class. Note that it may return -1
- * for classes introduced in version 1; internally the database only
- * stores version data for version 2 and up.
- *
- * @param className the internal name of the class, e.g. its
- * fully qualified name (as returned by Class.getName(), but with
- * '.' replaced by '/'.
- * @return the minimum API version the method is supported for, or -1 if
- * it's unknown <b>or version 1</b>.
- */
- public int getClassVersion(@NonNull String className) {
- if (!isRelevantClass(className)) {
- return -1;
- }
-
- if (mData != null) {
- int classNumber = findClass(className);
- if (classNumber != -1) {
- int offset = mIndices[classNumber];
- while (mData[offset] != 0) {
- offset++;
- }
- offset++;
- return UnsignedBytes.toInt(mData[offset]);
- }
- } else {
- ApiClass clz = mInfo.getClass(className);
- if (clz != null) {
- int since = clz.getSince();
- if (since == Integer.MAX_VALUE) {
- since = -1;
- }
- return since;
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the API version required by the given method call. The method is
- * referred to by its {@code owner}, {@code name} and {@code desc} fields.
- * If the method is unknown it returns -1. Note that it may return -1 for
- * classes introduced in version 1; internally the database only stores
- * version data for version 2 and up.
- *
- * @param owner the internal name of the method's owner class, e.g. its
- * fully qualified name (as returned by Class.getName(), but with
- * '.' replaced by '/'.
- * @param name the method's name
- * @param desc the method's descriptor - see {@link org.objectweb.asm.Type}
- * @return the minimum API version the method is supported for, or -1 if
- * it's unknown <b>or version 1</b>.
- */
- public int getCallVersion(
- @NonNull String owner,
- @NonNull String name,
- @NonNull String desc) {
- if (!isRelevantClass(owner)) {
- return -1;
- }
-
- if (mData != null) {
- int classNumber = findClass(owner);
- if (classNumber != -1) {
- return findMember(classNumber, name, desc);
- }
- } else {
- ApiClass clz = mInfo.getClass(owner);
- if (clz != null) {
- String signature = name + desc;
- int since = clz.getMethod(signature, mInfo);
- if (since == Integer.MAX_VALUE) {
- since = -1;
- }
- return since;
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the API version required to access the given field, or -1 if this
- * is not a known API method. Note that it may return -1 for classes
- * introduced in version 1; internally the database only stores version data
- * for version 2 and up.
- *
- * @param owner the internal name of the method's owner class, e.g. its
- * fully qualified name (as returned by Class.getName(), but with
- * '.' replaced by '/'.
- * @param name the method's name
- * @return the minimum API version the method is supported for, or -1 if
- * it's unknown <b>or version 1</b>
- */
- public int getFieldVersion(
- @NonNull String owner,
- @NonNull String name) {
- if (!isRelevantClass(owner)) {
- return -1;
- }
-
- if (mData != null) {
- int classNumber = findClass(owner);
- if (classNumber != -1) {
- return findMember(classNumber, name, null);
- }
- } else {
- ApiClass clz = mInfo.getClass(owner);
- if (clz != null) {
- int since = clz.getField(name, mInfo);
- if (since == Integer.MAX_VALUE) {
- since = -1;
- }
- return since;
- }
- }
-
- return -1;
- }
-
- /**
- * Returns true if the given owner (in VM format) is relevant to the database.
- * This allows quick filtering out of owners that won't return any data
- * for the various {@code #getFieldVersion} etc methods.
- *
- * @param owner the owner to look up
- * @return true if the owner might be relevant to the API database
- */
- public static boolean isRelevantOwner(@NonNull String owner) {
- if (owner.startsWith("java")) { //$NON-NLS-1$ // includes javax/
- return true;
- }
- if (owner.startsWith(ANDROID_PKG)) {
- return !owner.startsWith("/support/", 7);
- } else if (owner.startsWith("org/")) { //$NON-NLS-1$
- if (owner.startsWith("xml", 4) //$NON-NLS-1$
- || owner.startsWith("w3c/", 4) //$NON-NLS-1$
- || owner.startsWith("json/", 4) //$NON-NLS-1$
- || owner.startsWith("apache/", 4)) { //$NON-NLS-1$
- return true;
- }
- } else if (owner.startsWith("com/")) { //$NON-NLS-1$
- if (owner.startsWith("google/", 4) //$NON-NLS-1$
- || owner.startsWith("android/", 4)) { //$NON-NLS-1$
- return true;
- }
- } else if (owner.startsWith("junit") //$NON-NLS-1$
- || owner.startsWith("dalvik")) { //$NON-NLS-1$
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Returns true if the given owner (in VM format) is a valid Java package supported
- * in any version of Android.
- *
- * @param owner the package, in VM format
- * @return true if the package is included in one or more versions of Android
- */
- public boolean isValidJavaPackage(@NonNull String owner) {
- int packageLength = owner.lastIndexOf('/');
- if (packageLength == -1) {
- return false;
- }
-
- // The index array contains class indexes from 0 to classCount and
- // member indices from classCount to mIndices.length.
- int low = 0;
- int high = mJavaPackages.length - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = middle;
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + owner + " with entry at " + offset
- + ": " + mJavaPackages[offset]);
- }
-
- // Compare the api info at the given index.
- int compare = comparePackage(mJavaPackages[offset], owner, packageLength);
- if (compare == 0) {
- return true;
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return false;
- }
- }
-
- return false;
- }
-
- private static int comparePackage(String s1, String s2, int max) {
- for (int i = 0; i < max; i++) {
- if (i == s1.length()) {
- return -1;
- }
- char c1 = s1.charAt(i);
- char c2 = s2.charAt(i);
- if (c1 != c2) {
- return c1 - c2;
- }
- }
-
- if (s1.length() > max) {
- return 1;
- }
-
- return 0;
- }
-
- /** Returns the class number of the given class, or -1 if it is unknown */
- private int findClass(@NonNull String owner) {
- assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner;
-
- // The index array contains class indexes from 0 to classCount and
- // member indices from classCount to mIndices.length.
- int low = 0;
- int high = mClassCount - 1;
- // Compare the api info at the given index.
- int classNameLength = owner.length();
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + owner + " with entry at " + offset
- + ": " + dumpEntry(offset));
- }
-
- int compare = compare(mData, offset, (byte) 0, owner, classNameLength);
- if (compare == 0) {
- return middle;
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return -1;
- }
- }
-
- return -1;
- }
-
- private int findMember(int classNumber, @NonNull String name, @Nullable String desc) {
- // The index array contains class indexes from 0 to classCount and
- // member indices from classCount to mIndices.length.
- int low = mClassCount;
- int high = mIndices.length - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + (name + ';' + desc) +
- " with entry at " + offset + ": " + dumpEntry(offset));
- }
-
- // Check class number: read short. The byte data is always big endian.
- int entryClass = (mData[offset++] & 0xFF) << 8 | (mData[offset++] & 0xFF);
- int compare = entryClass - classNumber;
- if (compare == 0) {
- if (desc != null) {
- // Method
- int nameLength = name.length();
- compare = compare(mData, offset, (byte) '(', name, nameLength);
- if (compare == 0) {
- offset += nameLength;
- int argsEnd = desc.indexOf(')');
- // Only compare up to the ) -- after that we have a return value in the
- // input description, which isn't there in the database
- compare = compare(mData, offset, (byte) ')', desc, argsEnd);
- if (compare == 0) {
- offset += argsEnd + 1;
-
- if (mData[offset++] == 0) {
- // Yes, terminated argument list: get the API level
- return UnsignedBytes.toInt(mData[offset]);
- }
- }
- }
- } else {
- // Field
- int nameLength = name.length();
- compare = compare(mData, offset, (byte) 0, name, nameLength);
- if (compare == 0) {
- offset += nameLength;
- if (mData[offset++] == 0) {
- // Yes, terminated argument list: get the API level
- return UnsignedBytes.toInt(mData[offset]);
- }
- }
- }
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return -1;
- }
- }
-
- return -1;
- }
-
- /** Clears out any existing lookup instances */
- @VisibleForTesting
- static void dispose() {
- sInstance.clear();
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
deleted file mode 100644
index b3c2f2a..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Parser for the simplified XML API format version 1.
- */
-public class ApiParser extends DefaultHandler {
-
- private static final String NODE_API = "api";
- private static final String NODE_CLASS = "class";
- private static final String NODE_FIELD = "field";
- private static final String NODE_METHOD = "method";
- private static final String NODE_EXTENDS = "extends";
- private static final String NODE_IMPLEMENTS = "implements";
-
- private static final String ATTR_NAME = "name";
- private static final String ATTR_SINCE = "since";
-
- private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>();
-
- private ApiClass mCurrentClass;
-
- ApiParser() {
- }
-
- Map<String, ApiClass> getClasses() {
- return mClasses;
- }
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
-
- if (localName == null || localName.isEmpty()) {
- localName = qName;
- }
-
- try {
- if (NODE_API.equals(localName)) {
- // do nothing.
-
- } else if (NODE_CLASS.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = Integer.parseInt(attributes.getValue(ATTR_SINCE));
-
- mCurrentClass = addClass(name, since);
-
- } else if (NODE_EXTENDS.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addSuperClass(name, since);
-
- } else if (NODE_IMPLEMENTS.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addInterface(name, since);
-
- } else if (NODE_METHOD.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addMethod(name, since);
-
- } else if (NODE_FIELD.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addField(name, since);
-
- }
-
- } finally {
- super.startElement(uri, localName, qName, attributes);
- }
- }
-
- private ApiClass addClass(String name, int apiLevel) {
- ApiClass theClass = mClasses.get(name);
- if (theClass == null) {
- theClass = new ApiClass(name, apiLevel);
- mClasses.put(name, theClass);
- }
-
- return theClass;
- }
-
- private int getSince(Attributes attributes) {
- int since = mCurrentClass.getSince();
- String sinceAttr = attributes.getValue(ATTR_SINCE);
-
- if (sinceAttr != null) {
- since = Integer.parseInt(sinceAttr);
- }
-
- return since;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
deleted file mode 100644
index 11222ac..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_SHOW_AS_ACTION;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check that the right namespace is used for app compat menu items
- *
- * Using app:showAsAction instead of android:showAsAction leads to problems, but
- * isn't caught by the API Detector since it's not in the Android namespace.
- */
-public class AppCompatResourceDetector extends ResourceXmlDetector implements JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "AppCompatResource", //$NON-NLS-1$
- "Menu namespace",
-
- "When using the appcompat library, menu resources should refer to the " +
- "`showAsAction` in the `app:` namespace, not the `android:` namespace.\n" +
- "\n" +
- "Similarly, when *not* using the appcompat library, you should be using " +
- "the `android:showAsAction` attribute.",
-
- Category.USABILITY,
- 5,
- Severity.ERROR,
- new Implementation(
- AppCompatResourceDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
- /** Constructs a new {@link com.android.tools.lint.checks.AppCompatResourceDetector} */
- public AppCompatResourceDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.MENU;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_SHOW_AS_ACTION);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- Project mainProject = context.getMainProject();
- if (mainProject.isGradleProject()) {
- Boolean appCompat = mainProject.dependsOn("com.android.support:appcompat-v7");
- if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
- if (context.getFolderVersion() >= 14) {
- return;
- }
- if (appCompat == Boolean.TRUE) {
- context.report(ISSUE, attribute,
- context.getLocation(attribute),
- "Should use `app:showAsAction` with the appcompat library with "
- + "`xmlns:app=\"http://schemas.android.com/apk/res-auto\"`");
- }
- } else {
- if (appCompat == Boolean.FALSE) {
- context.report(ISSUE, attribute,
- context.getLocation(attribute),
- "Should use `android:showAsAction` when not using the appcompat library");
- }
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java
deleted file mode 100644
index 1a8dd67..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_HOST;
-import static com.android.SdkConstants.ATTR_PATH;
-import static com.android.SdkConstants.ATTR_PATH_PATTERN;
-import static com.android.SdkConstants.ATTR_PATH_PREFIX;
-import static com.android.SdkConstants.ATTR_SCHEME;
-
-import static com.android.xml.AndroidManifest.ATTRIBUTE_MIME_TYPE;
-import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
-import static com.android.xml.AndroidManifest.ATTRIBUTE_PORT;
-import static com.android.xml.AndroidManifest.NODE_ACTION;
-import static com.android.xml.AndroidManifest.NODE_CATEGORY;
-import static com.android.xml.AndroidManifest.NODE_DATA;
-import static com.android.xml.AndroidManifest.NODE_INTENT;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * Check if the usage of App Indexing is correct.
- */
-public class AppIndexingApiDetector extends Detector implements Detector.XmlScanner {
-
- private static final Implementation IMPLEMENTATION = new Implementation(
- AppIndexingApiDetector.class, Scope.MANIFEST_SCOPE);
-
- public static final Issue ISSUE_ERROR = Issue.create("AppIndexingError", //$NON-NLS-1$
- "Wrong Usage of App Indexing",
- "Ensures the app can correctly handle deep links and integrate with " +
- "App Indexing for Google search.",
- Category.USABILITY, 5, Severity.ERROR, IMPLEMENTATION)
- .addMoreInfo("https://g.co/AppIndexing");
-
- public static final Issue ISSUE_WARNING = Issue.create("AppIndexingWarning", //$NON-NLS-1$
- "Missing App Indexing Support",
- "Ensures the app can correctly handle deep links and integrate with " +
- "App Indexing for Google search.",
- Category.USABILITY, 5, Severity.WARNING, IMPLEMENTATION)
- .addMoreInfo("https://g.co/AppIndexing");
-
- private static final String[] PATH_ATTR_LIST = new String[]{ATTR_PATH_PREFIX, ATTR_PATH,
- ATTR_PATH_PATTERN};
-
- @Override
- @Nullable
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(NODE_INTENT);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element intent) {
- boolean actionView = hasActionView(intent);
- boolean browsable = isBrowsable(intent);
- boolean isHttp = false;
- boolean hasScheme = false;
- boolean hasHost = false;
- boolean hasPort = false;
- boolean hasPath = false;
- boolean hasMimeType = false;
- Element firstData = null;
- NodeList children = intent.getChildNodes();
- for (int i = 0; i < children.getLength(); i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(NODE_DATA)) {
- Element data = (Element) child;
- if (firstData == null) {
- firstData = data;
- }
- if (isHttpSchema(data)) {
- isHttp = true;
- }
- checkSingleData(context, data);
-
- for (String name : PATH_ATTR_LIST) {
- if (data.hasAttributeNS(ANDROID_URI, name)) {
- hasPath = true;
- }
- }
-
- if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) {
- hasScheme = true;
- }
-
- if (data.hasAttributeNS(ANDROID_URI, ATTR_HOST)) {
- hasHost = true;
- }
-
- if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) {
- hasPort = true;
- }
-
- if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_MIME_TYPE)) {
- hasMimeType = true;
- }
- }
- }
-
- // In data field, a URL is consisted by
- // <scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]
- // Each part of the URL should not have illegal character.
- if ((hasPath || hasHost || hasPort) && !hasScheme) {
- context.report(ISSUE_ERROR, firstData, context.getLocation(firstData),
- "android:scheme missing");
- }
-
- if ((hasPath || hasPort) && !hasHost) {
- context.report(ISSUE_ERROR, firstData, context.getLocation(firstData),
- "android:host missing");
- }
-
- if (actionView && browsable) {
- if (firstData == null) {
- // If this activity is an ACTION_VIEW action with category BROWSABLE, but doesn't
- // have data node, it may be a mistake and we will report error.
- context.report(ISSUE_ERROR, intent, context.getLocation(intent),
- "Missing data node?");
- } else if (!hasScheme && !hasMimeType) {
- // If this activity is an action view, is browsable, but has neither a
- // URL nor mimeType, it may be a mistake and we will report error.
- context.report(ISSUE_ERROR, firstData, context.getLocation(firstData),
- "Missing URL for the intent filter?");
- }
- }
-
- // If this activity is an ACTION_VIEW action, has a http URL but doesn't have
- // BROWSABLE, it may be a mistake and and we will report warning.
- if (actionView && isHttp && !browsable) {
- context.report(ISSUE_WARNING, intent, context.getLocation(intent),
- "Activity supporting ACTION_VIEW is not set as BROWSABLE");
- }
- }
-
- private static boolean hasActionView(Element intent) {
- NodeList children = intent.getChildNodes();
- for (int i = 0; i < children.getLength(); i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- child.getNodeName().equals(NODE_ACTION)) {
- Element action = (Element) child;
- if (action.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
- Attr attr = action.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
- if (attr.getValue().equals("android.intent.action.VIEW")) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- private static boolean isBrowsable(Element intent) {
- NodeList children = intent.getChildNodes();
- for (int i = 0; i < children.getLength(); i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- child.getNodeName().equals(NODE_CATEGORY)) {
- Element e = (Element) child;
- if (e.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
- Attr attr = e.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
- if (attr.getNodeValue().equals("android.intent.category.BROWSABLE")) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- private static boolean isHttpSchema(Element data) {
- if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) {
- String value = data.getAttributeNodeNS(ANDROID_URI, ATTR_SCHEME).getValue();
- if (value.equalsIgnoreCase("http") || value.equalsIgnoreCase("https")) {
- return true;
- }
- }
- return false;
- }
-
- private static void checkSingleData(XmlContext context, Element data) {
- // path, pathPrefix and pathPattern should starts with /.
- for (String name : PATH_ATTR_LIST) {
- if (data.hasAttributeNS(ANDROID_URI, name)) {
- Attr attr = data.getAttributeNodeNS(ANDROID_URI, name);
- String path = replaceUrlWithValue(context, attr.getValue());
- if (!path.startsWith("/") && !path.startsWith(SdkConstants.PREFIX_RESOURCE_REF)) {
- context.report(ISSUE_ERROR, attr, context.getLocation(attr),
- "android:" + name + " attribute should start with '/', but it is : "
- + path);
- }
- }
- }
-
- // port should be a legal number.
- if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) {
- Attr attr = data.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_PORT);
- try {
- String port = replaceUrlWithValue(context, attr.getValue());
- Integer.parseInt(port);
- } catch (NumberFormatException e) {
- context.report(ISSUE_ERROR, attr, context.getLocation(attr),
- "android:port is not a legal number");
- }
- }
-
- // Each field should be non empty.
- NamedNodeMap attrs = data.getAttributes();
- for (int i = 0; i < attrs.getLength(); i++) {
- Node item = attrs.item(i);
- if (item.getNodeType() == Node.ATTRIBUTE_NODE) {
- Attr attr = (Attr) attrs.item(i);
- if (attr.getValue().isEmpty()) {
- context.report(ISSUE_ERROR, attr, context.getLocation(attr),
- attr.getName() + " cannot be empty");
- }
- }
- }
- }
-
- private static String replaceUrlWithValue(@NonNull XmlContext context,
- @NonNull String str) {
- Project project = context.getProject();
- LintClient client = context.getClient();
- if (!client.supportsProjectResources()) {
- return str;
- }
- ResourceUrl style = ResourceUrl.parse(str);
- if (style == null || style.type != ResourceType.STRING || style.framework) {
- return str;
- }
- AbstractResourceRepository resources = client.getProjectResources(project, true);
- if (resources == null) {
- return str;
- }
- List<ResourceItem> items = resources.getResourceItem(ResourceType.STRING, style.name);
- if (items == null || items.isEmpty()) {
- return str;
- }
- ResourceValue resourceValue = items.get(0).getResourceValue(false);
- if (resourceValue == null) {
- return str;
- }
- return resourceValue.getValue() == null ? str : resourceValue.getValue();
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
deleted file mode 100644
index 4e50397..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Scope;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-
-/** Registry which provides a list of checks to be performed on an Android project */
-public class BuiltinIssueRegistry extends IssueRegistry {
- private static final List<Issue> sIssues;
- static final int INITIAL_CAPACITY = 222;
-
- static {
- List<Issue> issues = new ArrayList<Issue>(INITIAL_CAPACITY);
-
- issues.add(AccessibilityDetector.ISSUE);
- issues.add(AddJavascriptInterfaceDetector.ISSUE);
- issues.add(AlarmDetector.ISSUE);
- issues.add(AlwaysShowActionDetector.ISSUE);
- issues.add(AnnotationDetector.FLAG_STYLE);
- issues.add(AnnotationDetector.INSIDE_METHOD);
- issues.add(AnnotationDetector.UNIQUE);
- issues.add(ApiDetector.INLINED);
- issues.add(ApiDetector.OVERRIDE);
- issues.add(ApiDetector.UNSUPPORTED);
- issues.add(ApiDetector.UNUSED);
- issues.add(AppCompatCallDetector.ISSUE);
- issues.add(AppCompatResourceDetector.ISSUE);
- issues.add(AppIndexingApiDetector.ISSUE_ERROR);
- issues.add(AppIndexingApiDetector.ISSUE_WARNING);
- issues.add(ArraySizeDetector.INCONSISTENT);
- issues.add(AssertDetector.ISSUE);
- issues.add(ButtonDetector.BACK_BUTTON);
- issues.add(ButtonDetector.CASE);
- issues.add(ButtonDetector.ORDER);
- issues.add(ButtonDetector.STYLE);
- issues.add(ByteOrderMarkDetector.BOM);
- issues.add(CallSuperDetector.ISSUE);
- issues.add(ChildCountDetector.ADAPTER_VIEW_ISSUE);
- issues.add(ChildCountDetector.SCROLLVIEW_ISSUE);
- issues.add(CipherGetInstanceDetector.ISSUE);
- issues.add(CleanupDetector.COMMIT_FRAGMENT);
- issues.add(CleanupDetector.RECYCLE_RESOURCE);
- issues.add(ClickableViewAccessibilityDetector.ISSUE);
- issues.add(CommentDetector.EASTER_EGG);
- issues.add(CommentDetector.STOP_SHIP);
- issues.add(CustomViewDetector.ISSUE);
- issues.add(CutPasteDetector.ISSUE);
- issues.add(DateFormatDetector.DATE_FORMAT);
- issues.add(DeprecationDetector.ISSUE);
- issues.add(DetectMissingPrefix.MISSING_NAMESPACE);
- issues.add(DosLineEndingDetector.ISSUE);
- issues.add(DuplicateIdDetector.CROSS_LAYOUT);
- issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
- issues.add(DuplicateResourceDetector.ISSUE);
- issues.add(DuplicateResourceDetector.TYPE_MISMATCH);
- issues.add(ExtraTextDetector.ISSUE);
- issues.add(FieldGetterDetector.ISSUE);
- issues.add(FullBackupContentDetector.ISSUE);
- issues.add(FragmentDetector.ISSUE);
- issues.add(GetSignaturesDetector.ISSUE);
- issues.add(GradleDetector.COMPATIBILITY);
- issues.add(GradleDetector.GRADLE_PLUGIN_COMPATIBILITY);
- issues.add(GradleDetector.DEPENDENCY);
- issues.add(GradleDetector.DEPRECATED);
- issues.add(GradleDetector.GRADLE_GETTER);
- issues.add(GradleDetector.IDE_SUPPORT);
- issues.add(GradleDetector.PATH);
- issues.add(GradleDetector.PLUS);
- issues.add(GradleDetector.STRING_INTEGER);
- issues.add(GradleDetector.REMOTE_VERSION);
- issues.add(GradleDetector.ACCIDENTAL_OCTAL);
- issues.add(GridLayoutDetector.ISSUE);
- issues.add(HandlerDetector.ISSUE);
- issues.add(HardcodedDebugModeDetector.ISSUE);
- issues.add(HardcodedValuesDetector.ISSUE);
- issues.add(IconDetector.DUPLICATES_CONFIGURATIONS);
- issues.add(IconDetector.DUPLICATES_NAMES);
- issues.add(IconDetector.GIF_USAGE);
- issues.add(IconDetector.ICON_COLORS);
- issues.add(IconDetector.ICON_DENSITIES);
- issues.add(IconDetector.ICON_DIP_SIZE);
- issues.add(IconDetector.ICON_EXPECTED_SIZE);
- issues.add(IconDetector.ICON_EXTENSION);
- issues.add(IconDetector.ICON_LAUNCHER_SHAPE);
- issues.add(IconDetector.ICON_LOCATION);
- issues.add(IconDetector.ICON_MISSING_FOLDER);
- issues.add(IconDetector.ICON_MIX_9PNG);
- issues.add(IconDetector.ICON_NODPI);
- issues.add(IconDetector.ICON_XML_AND_PNG);
- issues.add(IncludeDetector.ISSUE);
- issues.add(InefficientWeightDetector.BASELINE_WEIGHTS);
- issues.add(InefficientWeightDetector.INEFFICIENT_WEIGHT);
- issues.add(InefficientWeightDetector.NESTED_WEIGHTS);
- issues.add(InefficientWeightDetector.ORIENTATION);
- issues.add(InefficientWeightDetector.WRONG_0DP);
- issues.add(InvalidPackageDetector.ISSUE);
- issues.add(JavaPerformanceDetector.PAINT_ALLOC);
- issues.add(JavaPerformanceDetector.USE_SPARSE_ARRAY);
- issues.add(JavaPerformanceDetector.USE_VALUE_OF);
- issues.add(JavaScriptInterfaceDetector.ISSUE);
- issues.add(LabelForDetector.ISSUE);
- issues.add(LayoutConsistencyDetector.INCONSISTENT_IDS);
- issues.add(LayoutInflationDetector.ISSUE);
- issues.add(LocaleDetector.STRING_LOCALE);
- issues.add(LocaleFolderDetector.DEPRECATED_CODE);
- issues.add(LocaleFolderDetector.INVALID_FOLDER);
- issues.add(LocaleFolderDetector.WRONG_REGION);
- issues.add(LocaleFolderDetector.USE_ALPHA_2);
- issues.add(LogDetector.CONDITIONAL);
- issues.add(LogDetector.LONG_TAG);
- issues.add(LogDetector.WRONG_TAG);
- issues.add(ManifestDetector.ALLOW_BACKUP);
- issues.add(ManifestDetector.APPLICATION_ICON);
- issues.add(ManifestDetector.DEVICE_ADMIN);
- issues.add(ManifestDetector.DUPLICATE_ACTIVITY);
- issues.add(ManifestDetector.DUPLICATE_USES_FEATURE);
- issues.add(ManifestDetector.GRADLE_OVERRIDES);
- issues.add(ManifestDetector.ILLEGAL_REFERENCE);
- issues.add(ManifestDetector.MIPMAP);
- issues.add(ManifestDetector.MOCK_LOCATION);
- issues.add(ManifestDetector.MULTIPLE_USES_SDK);
- issues.add(ManifestDetector.ORDER);
- issues.add(ManifestDetector.SET_VERSION);
- issues.add(ManifestDetector.TARGET_NEWER);
- issues.add(ManifestDetector.UNIQUE_PERMISSION);
- issues.add(ManifestDetector.USES_SDK);
- issues.add(ManifestDetector.WRONG_PARENT);
- issues.add(ManifestTypoDetector.ISSUE);
- issues.add(MathDetector.ISSUE);
- issues.add(MergeRootFrameLayoutDetector.ISSUE);
- issues.add(MissingClassDetector.INNERCLASS);
- issues.add(MissingClassDetector.INSTANTIATABLE);
- issues.add(MissingClassDetector.MISSING);
- issues.add(MissingIdDetector.ISSUE);
- issues.add(NamespaceDetector.CUSTOM_VIEW);
- issues.add(NamespaceDetector.RES_AUTO);
- issues.add(NamespaceDetector.TYPO);
- issues.add(NamespaceDetector.UNUSED);
- issues.add(NegativeMarginDetector.ISSUE);
- issues.add(NestedScrollingWidgetDetector.ISSUE);
- issues.add(NfcTechListDetector.ISSUE);
- issues.add(NonInternationalizedSmsDetector.ISSUE);
- issues.add(ObsoleteLayoutParamsDetector.ISSUE);
- issues.add(OnClickDetector.ISSUE);
- issues.add(OverdrawDetector.ISSUE);
- issues.add(OverrideDetector.ISSUE);
- issues.add(OverrideConcreteDetector.ISSUE);
- issues.add(ParcelDetector.ISSUE);
- issues.add(PluralsDetector.EXTRA);
- issues.add(PluralsDetector.MISSING);
- issues.add(PluralsDetector.IMPLIED_QUANTITY);
- issues.add(PreferenceActivityDetector.ISSUE);
- issues.add(PrivateKeyDetector.ISSUE);
- issues.add(PrivateResourceDetector.ISSUE);
- issues.add(ProguardDetector.SPLIT_CONFIG);
- issues.add(ProguardDetector.WRONG_KEEP);
- issues.add(PropertyFileDetector.ESCAPE);
- issues.add(PropertyFileDetector.HTTP);
- issues.add(PxUsageDetector.DP_ISSUE);
- issues.add(PxUsageDetector.IN_MM_ISSUE);
- issues.add(PxUsageDetector.PX_ISSUE);
- issues.add(PxUsageDetector.SMALL_SP_ISSUE);
- issues.add(RegistrationDetector.ISSUE);
- issues.add(RelativeOverlapDetector.ISSUE);
- issues.add(RequiredAttributeDetector.ISSUE);
- issues.add(ResourceCycleDetector.CRASH);
- issues.add(ResourceCycleDetector.CYCLE);
- issues.add(ResourcePrefixDetector.ISSUE);
- issues.add(RtlDetector.COMPAT);
- issues.add(RtlDetector.ENABLED);
- issues.add(RtlDetector.SYMMETRY);
- issues.add(RtlDetector.USE_START);
- issues.add(ScrollViewChildDetector.ISSUE);
- issues.add(SdCardDetector.ISSUE);
- issues.add(SecureRandomDetector.ISSUE);
- issues.add(SecureRandomGeneratorDetector.ISSUE);
- issues.add(SecurityDetector.EXPORTED_PROVIDER);
- issues.add(SecurityDetector.EXPORTED_RECEIVER);
- issues.add(SecurityDetector.EXPORTED_SERVICE);
- issues.add(SecurityDetector.OPEN_PROVIDER);
- issues.add(SecurityDetector.WORLD_READABLE);
- issues.add(SecurityDetector.WORLD_WRITEABLE);
- issues.add(ServiceCastDetector.ISSUE);
- issues.add(SetJavaScriptEnabledDetector.ISSUE);
- issues.add(SharedPrefsDetector.ISSUE);
- issues.add(SignatureOrSystemDetector.ISSUE);
- issues.add(SQLiteDetector.ISSUE);
- issues.add(StateListDetector.ISSUE);
- issues.add(StringFormatDetector.ARG_COUNT);
- issues.add(StringFormatDetector.ARG_TYPES);
- issues.add(StringFormatDetector.INVALID);
- issues.add(StringFormatDetector.POTENTIAL_PLURAL);
- issues.add(SupportAnnotationDetector.CHECK_PERMISSION);
- issues.add(SupportAnnotationDetector.CHECK_RESULT);
- issues.add(SupportAnnotationDetector.COLOR_USAGE);
- issues.add(SupportAnnotationDetector.MISSING_PERMISSION);
- issues.add(SupportAnnotationDetector.RANGE);
- issues.add(SupportAnnotationDetector.RESOURCE_TYPE);
- issues.add(SupportAnnotationDetector.THREAD);
- issues.add(SupportAnnotationDetector.TYPE_DEF);
- issues.add(SystemPermissionsDetector.ISSUE);
- issues.add(TextFieldDetector.ISSUE);
- issues.add(TextViewDetector.ISSUE);
- issues.add(TextViewDetector.SELECTABLE);
- issues.add(TitleDetector.ISSUE);
- issues.add(ToastDetector.ISSUE);
- issues.add(TooManyViewsDetector.TOO_DEEP);
- issues.add(TooManyViewsDetector.TOO_MANY);
- issues.add(TranslationDetector.EXTRA);
- issues.add(TranslationDetector.MISSING);
- issues.add(TypoDetector.ISSUE);
- issues.add(TypographyDetector.DASHES);
- issues.add(TypographyDetector.ELLIPSIS);
- issues.add(TypographyDetector.FRACTIONS);
- issues.add(TypographyDetector.OTHER);
- issues.add(TypographyDetector.QUOTES);
- issues.add(UnusedResourceDetector.ISSUE);
- issues.add(UnusedResourceDetector.ISSUE_IDS);
- issues.add(UseCompoundDrawableDetector.ISSUE);
- issues.add(UselessViewDetector.USELESS_LEAF);
- issues.add(UselessViewDetector.USELESS_PARENT);
- issues.add(Utf8Detector.ISSUE);
- issues.add(ViewConstructorDetector.ISSUE);
- issues.add(ViewHolderDetector.ISSUE);
- issues.add(ViewTagDetector.ISSUE);
- issues.add(ViewTypeDetector.ISSUE);
- issues.add(WakelockDetector.ISSUE);
- issues.add(WebViewDetector.ISSUE);
- issues.add(WrongCallDetector.ISSUE);
- issues.add(WrongCaseDetector.WRONG_CASE);
- issues.add(WrongIdDetector.INVALID);
- issues.add(WrongIdDetector.NOT_SIBLING);
- issues.add(WrongIdDetector.UNKNOWN_ID);
- issues.add(WrongIdDetector.UNKNOWN_ID_LAYOUT);
- issues.add(WrongImportDetector.ISSUE);
- issues.add(WrongLocationDetector.ISSUE);
-
- sIssues = Collections.unmodifiableList(issues);
- }
-
- /**
- * Constructs a new {@link BuiltinIssueRegistry}
- */
- public BuiltinIssueRegistry() {
- }
-
- @NonNull
- @Override
- public List<Issue> getIssues() {
- return sIssues;
- }
-
- @Override
- protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) {
- if (scope.equals(Scope.ALL)) {
- return getIssues().size();
- } else {
- int initialSize = 12;
- if (scope.contains(Scope.RESOURCE_FILE)) {
- initialSize += 75;
- } else if (scope.contains(Scope.ALL_RESOURCE_FILES)) {
- initialSize += 10;
- }
-
- if (scope.contains(Scope.JAVA_FILE)) {
- initialSize += 55;
- } else if (scope.contains(Scope.CLASS_FILE)) {
- initialSize += 15;
- } else if (scope.contains(Scope.MANIFEST)) {
- initialSize += 30;
- } else if (scope.contains(Scope.GRADLE_FILE)) {
- initialSize += 5;
- }
- return initialSize;
- }
- }
-
- /**
- * Reset the registry such that it recomputes its available issues.
- * <p>
- * NOTE: This is only intended for testing purposes.
- */
- @VisibleForTesting
- public static void reset() {
- IssueRegistry.reset();
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
deleted file mode 100644
index 568293d..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
+++ /dev/null
@@ -1,784 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BACKGROUND;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_ORIENTATION;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.ATTR_TEXT;
-import static com.android.SdkConstants.BUTTON;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.STRING_PREFIX;
-import static com.android.SdkConstants.TABLE_ROW;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.VALUE_SELECTABLE_ITEM_BACKGROUND;
-import static com.android.SdkConstants.VALUE_TRUE;
-import static com.android.SdkConstants.VALUE_VERTICAL;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Check which looks at the order of buttons in dialogs and makes sure that
- * "the dismissive action of a dialog is always on the left whereas the affirmative actions
- * are on the right."
- * <p>
- * This only looks for the affirmative and dismissive actions named "OK" and "Cancel";
- * "Cancel" usually works, but the affirmative action often has many other names -- "Done",
- * "Send", "Go", etc.
- * <p>
- * TODO: Perhaps we should look for Yes/No dialogs and suggested they be rephrased as
- * Cancel/OK dialogs? Similarly, consider "Abort" a synonym for "Cancel" ?
- */
-public class ButtonDetector extends ResourceXmlDetector {
- /** Name of cancel value ("Cancel") */
- private static final String CANCEL_LABEL = "Cancel";
- /** Name of OK value ("Cancel") */
- private static final String OK_LABEL = "OK";
- /** Name of Back value ("Back") */
- private static final String BACK_LABEL = "Back";
- /** Yes */
- private static final String YES_LABEL = "Yes";
- /** No */
- private static final String NO_LABEL = "No";
-
- /** Layout text attribute reference to {@code @android:string/ok} */
- private static final String ANDROID_OK_RESOURCE =
- ANDROID_STRING_PREFIX + "ok"; //$NON-NLS-1$
- /** Layout text attribute reference to {@code @android:string/cancel} */
- private static final String ANDROID_CANCEL_RESOURCE =
- ANDROID_STRING_PREFIX + "cancel"; //$NON-NLS-1$
- /** Layout text attribute reference to {@code @android:string/yes} */
- private static final String ANDROID_YES_RESOURCE =
- ANDROID_STRING_PREFIX + "yes"; //$NON-NLS-1$
- /** Layout text attribute reference to {@code @android:string/no} */
- private static final String ANDROID_NO_RESOURCE =
- ANDROID_STRING_PREFIX + "no"; //$NON-NLS-1$
-
- private static final Implementation IMPLEMENTATION = new Implementation(
- ButtonDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** The main issue discovered by this detector */
- public static final Issue ORDER = Issue.create(
- "ButtonOrder", //$NON-NLS-1$
- "Button order",
-
- "According to the Android Design Guide,\n" +
- "\n" +
- "\"Action buttons are typically Cancel and/or OK, with OK indicating the preferred " +
- "or most likely action. However, if the options consist of specific actions such " +
- "as Close or Wait rather than a confirmation or cancellation of the action " +
- "described in the content, then all the buttons should be active verbs. As a rule, " +
- "the dismissive action of a dialog is always on the left whereas the affirmative " +
- "actions are on the right.\"\n" +
- "\n" +
- "This check looks for button bars and buttons which look like cancel buttons, " +
- "and makes sure that these are on the left.",
-
- Category.USABILITY,
- 8,
- Severity.WARNING,
- IMPLEMENTATION)
- .addMoreInfo(
- "http://developer.android.com/design/building-blocks/dialogs.html"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue STYLE = Issue.create(
- "ButtonStyle", //$NON-NLS-1$
- "Button should be borderless",
-
- "Button bars typically use a borderless style for the buttons. Set the " +
- "`style=\"?android:attr/buttonBarButtonStyle\"` attribute " +
- "on each of the buttons, and set `style=\"?android:attr/buttonBarStyle\"` on " +
- "the parent layout",
-
- Category.USABILITY,
- 5,
- Severity.WARNING,
- IMPLEMENTATION)
- .addMoreInfo(
- "http://developer.android.com/design/building-blocks/buttons.html"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue BACK_BUTTON = Issue.create(
- "BackButton", //$NON-NLS-1$
- "Back button",
- // TODO: Look for ">" as label suffixes as well
-
- "According to the Android Design Guide,\n" +
- "\n" +
- "\"Other platforms use an explicit back button with label to allow the user " +
- "to navigate up the application's hierarchy. Instead, Android uses the main " +
- "action bar's app icon for hierarchical navigation and the navigation bar's " +
- "back button for temporal navigation.\"" +
- "\n" +
- "This check is not very sophisticated (it just looks for buttons with the " +
- "label \"Back\"), so it is disabled by default to not trigger on common " +
- "scenarios like pairs of Back/Next buttons to paginate through screens.",
-
- Category.USABILITY,
- 6,
- Severity.WARNING,
- IMPLEMENTATION)
- .setEnabledByDefault(false)
- .addMoreInfo(
- "http://developer.android.com/design/patterns/pure-android.html"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue CASE = Issue.create(
- "ButtonCase", //$NON-NLS-1$
- "Cancel/OK dialog button capitalization",
-
- "The standard capitalization for OK/Cancel dialogs is \"OK\" and \"Cancel\". " +
- "To ensure that your dialogs use the standard strings, you can use " +
- "the resource strings @android:string/ok and @android:string/cancel.",
-
- Category.USABILITY,
- 2,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Set of resource names whose value was either OK or Cancel */
- private Set<String> mApplicableResources;
-
- /**
- * Map of resource names we'd like resolved into strings in phase 2. The
- * values should be filled in with the actual string contents.
- */
- private Map<String, String> mKeyToLabel;
-
- /**
- * Set of elements we've already warned about. If we've already complained
- * about a cancel button, don't also report the OK button (since it's listed
- * for the warnings on OK buttons).
- */
- private Set<Element> mIgnore;
-
- /** Constructs a new {@link ButtonDetector} */
- public ButtonDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(BUTTON, TAG_STRING);
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- int phase = context.getPhase();
- if (phase == 1 && mApplicableResources != null) {
- // We found resources for the string "Cancel"; perform a second pass
- // where we check layout text attributes against these strings.
- context.getDriver().requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
- }
- }
-
- private static String stripLabel(String text) {
- text = text.trim();
- if (text.length() > 2
- && (text.charAt(0) == '"' || text.charAt(0) == '\'')
- && (text.charAt(0) == text.charAt(text.length() - 1))) {
- text = text.substring(1, text.length() - 1);
- }
-
- return text;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- // This detector works in two passes.
- // In pass 1, it looks in layout files for hardcoded strings of "Cancel", or
- // references to @string/cancel or @android:string/cancel.
- // It also looks in values/ files for strings whose value is "Cancel",
- // and if found, stores the corresponding keys in a map. (This is necessary
- // since value files are processed after layout files).
- // Then, if at the end of phase 1 any "Cancel" string resources were
- // found in the value files, then it requests a *second* phase,
- // where it looks only for <Button>'s whose text matches one of the
- // cancel string resources.
- int phase = context.getPhase();
- String tagName = element.getTagName();
- if (phase == 1 && tagName.equals(TAG_STRING)) {
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = child.getNodeValue();
- for (int j = 0, len = text.length(); j < len; j++) {
- char c = text.charAt(j);
- if (!Character.isWhitespace(c)) {
- if (c == '"' || c == '\'') {
- continue;
- }
- if (LintUtils.startsWith(text, CANCEL_LABEL, j)) {
- String label = stripLabel(text);
- if (label.equalsIgnoreCase(CANCEL_LABEL)) {
- String name = element.getAttribute(ATTR_NAME);
- foundResource(context, name, element);
-
- if (!label.equals(CANCEL_LABEL)
- && LintUtils.isEnglishResource(context, true)
- && context.isEnabled(CASE)) {
- assert label.trim().equalsIgnoreCase(CANCEL_LABEL) : label;
- context.report(CASE, child, context.getLocation(child),
- String.format(
- "The standard Android way to capitalize %1$s " +
- "is \"Cancel\" (tip: use `@android:string/cancel` instead)",
- label));
- }
- }
- } else if (LintUtils.startsWith(text, OK_LABEL, j)) {
- String label = stripLabel(text);
- if (label.equalsIgnoreCase(OK_LABEL)) {
- String name = element.getAttribute(ATTR_NAME);
- foundResource(context, name, element);
-
- if (!label.equals(OK_LABEL)
- && LintUtils.isEnglishResource(context, true)
- && context.isEnabled(CASE)) {
- assert label.trim().equalsIgnoreCase(OK_LABEL) : label;
- context.report(CASE, child, context.getLocation(child),
- String.format(
- "The standard Android way to capitalize %1$s " +
- "is \"OK\" (tip: use `@android:string/ok` instead)",
- label));
- }
- }
- } else if (LintUtils.startsWith(text, BACK_LABEL, j) &&
- stripLabel(text).equalsIgnoreCase(BACK_LABEL)) {
- String name = element.getAttribute(ATTR_NAME);
- foundResource(context, name, element);
- }
- break;
- }
- }
- }
- }
- } else if (tagName.equals(BUTTON)) {
- if (phase == 1) {
- if (isInButtonBar(element)
- && !element.hasAttribute(ATTR_STYLE)
- && !VALUE_SELECTABLE_ITEM_BACKGROUND.equals(
- element.getAttributeNS(ANDROID_URI, ATTR_BACKGROUND))
- && (context.getProject().getMinSdk() >= 11
- || context.getFolderVersion() >= 11)
- && context.isEnabled(STYLE)
- && !parentDefinesSelectableItem(element)) {
- context.report(STYLE, element, context.getLocation(element),
- "Buttons in button bars should be borderless; use " +
- "`style=\"?android:attr/buttonBarButtonStyle\"` (and " +
- "`?android:attr/buttonBarStyle` on the parent)");
- }
- }
-
- String text = element.getAttributeNS(ANDROID_URI, ATTR_TEXT);
- if (phase == 2) {
- if (mApplicableResources.contains(text)) {
- String key = text;
- if (key.startsWith(STRING_PREFIX)) {
- key = key.substring(STRING_PREFIX.length());
- }
- String label = mKeyToLabel.get(key);
- boolean isCancel = CANCEL_LABEL.equalsIgnoreCase(label);
- if (isCancel) {
- if (isWrongCancelPosition(element)) {
- reportCancelPosition(context, element);
- }
- } else if (OK_LABEL.equalsIgnoreCase(label)) {
- if (isWrongOkPosition(element)) {
- reportOkPosition(context, element);
- }
- } else {
- assert BACK_LABEL.equalsIgnoreCase(label) : label + ':' + context.file;
- Location location = context.getLocation(element);
- if (context.isEnabled(BACK_BUTTON)) {
- context.report(BACK_BUTTON, element, location,
- "Back buttons are not standard on Android; see design guide's " +
- "navigation section");
- }
- }
- }
- } else if (text.equals(CANCEL_LABEL) || text.equals(ANDROID_CANCEL_RESOURCE)) {
- if (isWrongCancelPosition(element)) {
- reportCancelPosition(context, element);
- }
- } else if (text.equals(OK_LABEL) || text.equals(ANDROID_OK_RESOURCE)) {
- if (isWrongOkPosition(element)) {
- reportOkPosition(context, element);
- }
- } else {
- boolean isYes = text.equals(ANDROID_YES_RESOURCE);
- if (isYes || text.equals(ANDROID_NO_RESOURCE)) {
- Attr attribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_TEXT);
- Location location = context.getLocation(attribute);
- String message = String.format("%1$s actually returns \"%2$s\", not \"%3$s\"; "
- + "use %4$s instead or create a local string resource for %5$s",
- text,
- isYes ? OK_LABEL : CANCEL_LABEL,
- isYes ? YES_LABEL : NO_LABEL,
- isYes ? ANDROID_OK_RESOURCE : ANDROID_CANCEL_RESOURCE,
- isYes ? YES_LABEL : NO_LABEL);
- context.report(CASE, element, location, message);
- }
- }
- }
- }
-
- private static boolean parentDefinesSelectableItem(Element element) {
- String background = element.getAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
- if (VALUE_SELECTABLE_ITEM_BACKGROUND.equals(background)) {
- return true;
- }
-
- Node parent = element.getParentNode();
- if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
- return parentDefinesSelectableItem((Element) parent);
- }
-
- return false;
- }
-
- /** Report the given OK button as being in the wrong position */
- private void reportOkPosition(XmlContext context, Element element) {
- report(context, element, false /*isCancel*/);
- }
-
- /** Report the given Cancel button as being in the wrong position */
- private void reportCancelPosition(XmlContext context, Element element) {
- report(context, element, true /*isCancel*/);
- }
-
- /**
- * We've found a resource reference to some label we're interested in ("OK",
- * "Cancel", "Back", ...). Record the corresponding name such that in the
- * next pass through the layouts we can check the context (for OK/Cancel the
- * button order etc).
- */
- private void foundResource(XmlContext context, String name, Element element) {
- if (!LintUtils.isEnglishResource(context, true)) {
- return;
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- if (mApplicableResources == null) {
- mApplicableResources = new HashSet<String>();
- }
-
- mApplicableResources.add(STRING_PREFIX + name);
-
- // ALSO record all the other string resources in this file to pick up other
- // labels. If you define "OK" in one resource file and "Cancel" in another
- // this won't work, but that's probably not common and has lower overhead.
- Node parentNode = element.getParentNode();
-
- List<Element> items = LintUtils.getChildren(parentNode);
- if (mKeyToLabel == null) {
- mKeyToLabel = new HashMap<String, String>(items.size());
- }
- for (Element item : items) {
- String itemName = item.getAttribute(ATTR_NAME);
- NodeList childNodes = item.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = stripLabel(child.getNodeValue());
- if (!text.isEmpty()) {
- mKeyToLabel.put(itemName, text);
- break;
- }
- }
- }
- }
- }
-
- /** Report the given OK/Cancel button as being in the wrong position */
- private void report(XmlContext context, Element element, boolean isCancel) {
- if (!context.isEnabled(ORDER)) {
- return;
- }
-
- if (mIgnore != null && mIgnore.contains(element)) {
- return;
- }
-
- int target = context.getProject().getTargetSdk();
- if (target < 14) {
- // If you're only targeting pre-ICS UI's, this is not an issue
- return;
- }
-
- boolean mustCreateIcsLayout = false;
- if (context.getProject().getMinSdk() < 14) {
- // If you're *also* targeting pre-ICS UIs, then this reverse button
- // order is correct for layouts intended for pre-ICS and incorrect for
- // ICS layouts.
- //
- // Therefore, we need to know if this layout is an ICS layout or
- // a pre-ICS layout.
- boolean isIcsLayout = context.getFolderVersion() >= 14;
- if (!isIcsLayout) {
- // This layout is not an ICS layout. However, there *must* also be
- // an ICS layout here, or this button order will be wrong:
- File res = context.file.getParentFile().getParentFile();
- File[] resFolders = res.listFiles();
- String fileName = context.file.getName();
- if (resFolders != null) {
- for (File folder : resFolders) {
- String folderName = folder.getName();
- if (folderName.startsWith(SdkConstants.FD_RES_LAYOUT)
- && folderName.contains("-v14")) { //$NON-NLS-1$
- File layout = new File(folder, fileName);
- if (layout.exists()) {
- // Yes, a v14 specific layout is available so this pre-ICS
- // layout order is not a problem
- return;
- }
- }
- }
- }
- mustCreateIcsLayout = true;
- }
- }
-
- List<Element> buttons = LintUtils.getChildren(element.getParentNode());
-
- if (mIgnore == null) {
- mIgnore = new HashSet<Element>();
- }
- for (Element button : buttons) {
- // Mark all the siblings in the ignore list to ensure that we don't
- // report *both* the Cancel and the OK button in "OK | Cancel"
- mIgnore.add(button);
- }
-
- String message;
- if (isCancel) {
- message = "Cancel button should be on the left";
- } else {
- message = "OK button should be on the right";
- }
-
- if (mustCreateIcsLayout) {
- message = String.format(
- "Layout uses the wrong button order for API >= 14: Create a " +
- "`layout-v14/%1$s` file with opposite order: %2$s",
- context.file.getName(), message);
- }
-
- // Show existing button order? We can only do that for LinearLayouts
- // since in for example a RelativeLayout the order of the elements may
- // not be the same as the visual order
- String layout = element.getParentNode().getNodeName();
- if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
- List<String> labelList = getLabelList(buttons);
- String wrong = describeButtons(labelList);
- sortButtons(labelList);
- String right = describeButtons(labelList);
- message += String.format(" (was \"%1$s\", should be \"%2$s\")", wrong, right);
- }
-
- Location location = context.getLocation(element);
- context.report(ORDER, element, location, message);
- }
-
- /**
- * Sort a list of label buttons into the expected order (Cancel on the left,
- * OK on the right
- */
- private static void sortButtons(List<String> labelList) {
- for (int i = 0, n = labelList.size(); i < n; i++) {
- String label = labelList.get(i);
- if (label.equalsIgnoreCase(CANCEL_LABEL) && i > 0) {
- swap(labelList, 0, i);
- } else if (label.equalsIgnoreCase(OK_LABEL) && i < n - 1) {
- swap(labelList, n - 1, i);
- }
- }
- }
-
- /** Swaps the strings at positions i and j */
- private static void swap(List<String> strings, int i, int j) {
- if (i != j) {
- String temp = strings.get(i);
- strings.set(i, strings.get(j));
- strings.set(j, temp);
- }
- }
-
- /** Creates a display string for a list of button labels, such as "Cancel | OK" */
- private static String describeButtons(List<String> labelList) {
- StringBuilder sb = new StringBuilder(80);
- for (String label : labelList) {
- if (sb.length() > 0) {
- sb.append(" | "); //$NON-NLS-1$
- }
- sb.append(label);
- }
-
- return sb.toString();
- }
-
- /** Returns the ordered list of button labels */
- private List<String> getLabelList(List<Element> views) {
- List<String> labels = new ArrayList<String>();
-
- if (mIgnore == null) {
- mIgnore = new HashSet<Element>();
- }
-
- for (Element view : views) {
- if (view.getTagName().equals(BUTTON)) {
- String text = view.getAttributeNS(ANDROID_URI, ATTR_TEXT);
- String label = getLabel(text);
- labels.add(label);
-
- // Mark all the siblings in the ignore list to ensure that we don't
- // report *both* the Cancel and the OK button in "OK | Cancel"
- mIgnore.add(view);
- }
- }
-
- return labels;
- }
-
- private String getLabel(String key) {
- String label = null;
- if (key.startsWith(ANDROID_STRING_PREFIX)) {
- if (key.equals(ANDROID_OK_RESOURCE)) {
- label = OK_LABEL;
- } else if (key.equals(ANDROID_CANCEL_RESOURCE)) {
- label = CANCEL_LABEL;
- }
- } else if (mKeyToLabel != null) {
- if (key.startsWith(STRING_PREFIX)) {
- label = mKeyToLabel.get(key.substring(STRING_PREFIX.length()));
- }
- }
-
- if (label == null) {
- label = key;
- }
-
- if (label.indexOf(' ') != -1 && label.indexOf('"') == -1) {
- label = '"' + label + '"';
- }
-
- return label;
- }
-
- /** Is the cancel button in the wrong position? It has to be on the left. */
- private static boolean isWrongCancelPosition(Element element) {
- return isWrongPosition(element, true /*isCancel*/);
- }
-
- /** Is the OK button in the wrong position? It has to be on the right. */
- private static boolean isWrongOkPosition(Element element) {
- return isWrongPosition(element, false /*isCancel*/);
- }
-
- private static boolean isInButtonBar(Element element) {
- assert element.getTagName().equals(BUTTON) : element.getTagName();
- Node parentNode = element.getParentNode();
- if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
- return false;
- }
- Element parent = (Element) parentNode;
-
- String style = parent.getAttribute(ATTR_STYLE);
- if (style != null && style.contains("buttonBarStyle")) { //$NON-NLS-1$
- return true;
- }
-
- // Don't warn about single Cancel / OK buttons
- if (LintUtils.getChildCount(parent) < 2) {
- return false;
- }
-
- String layout = parent.getTagName();
- if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
- String orientation = parent.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
- if (VALUE_VERTICAL.equals(orientation)) {
- return false;
- }
- } else {
- return false;
- }
-
- // Ensure that all the children are buttons
- Node n = parent.getFirstChild();
- while (n != null) {
- if (n.getNodeType() == Node.ELEMENT_NODE) {
- if (!BUTTON.equals(n.getNodeName())) {
- return false;
- }
- }
- n = n.getNextSibling();
- }
-
- return true;
- }
-
- /** Is the given button in the wrong position? */
- private static boolean isWrongPosition(Element element, boolean isCancel) {
- Node parentNode = element.getParentNode();
- if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
- return false;
- }
- Element parent = (Element) parentNode;
-
- // Don't warn about single Cancel / OK buttons
- if (LintUtils.getChildCount(parent) < 2) {
- return false;
- }
-
- String layout = parent.getTagName();
- if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
- String orientation = parent.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
- if (VALUE_VERTICAL.equals(orientation)) {
- return false;
- }
-
- if (isCancel) {
- Node n = element.getPreviousSibling();
- while (n != null) {
- if (n.getNodeType() == Node.ELEMENT_NODE) {
- return true;
- }
- n = n.getPreviousSibling();
- }
- } else {
- Node n = element.getNextSibling();
- while (n != null) {
- if (n.getNodeType() == Node.ELEMENT_NODE) {
- return true;
- }
- n = n.getNextSibling();
- }
- }
-
- return false;
- } else if (layout.equals(RELATIVE_LAYOUT)) {
- // In RelativeLayouts, look for attachments which look like a clear sign
- // that the OK or Cancel buttons are out of order:
- // -- a left attachment on a Cancel button (where the left attachment
- // is a button; we don't want to complain if it's pointing to a spacer
- // or image or progress indicator etc)
- // -- a right-side parent attachment on a Cancel button (unless it's also
- // attached on the left, e.g. a cancel button stretching across the
- // layout)
- // etc.
- if (isCancel) {
- if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_TO_RIGHT_OF)
- && isButtonId(parent, element.getAttributeNS(ANDROID_URI,
- ATTR_LAYOUT_TO_RIGHT_OF))) {
- return true;
- }
- if (isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_RIGHT) &&
- !isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_LEFT)) {
- return true;
- }
- } else {
- if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_TO_LEFT_OF)
- && isButtonId(parent, element.getAttributeNS(ANDROID_URI,
- ATTR_LAYOUT_TO_RIGHT_OF))) {
- return true;
- }
- if (isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_LEFT) &&
- !isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) {
- return true;
- }
- }
-
- return false;
- } else {
- // TODO: Consider other button layouts - GridLayouts, custom views extending
- // LinearLayout etc?
- return false;
- }
- }
-
- /**
- * Returns true if the given attribute (in the Android namespace) is set to
- * true on the given element
- */
- private static boolean isTrue(Element element, String attribute) {
- return VALUE_TRUE.equals(element.getAttributeNS(ANDROID_URI, attribute));
- }
-
- /** Is the given target id the id of a {@code <Button>} within this RelativeLayout? */
- private static boolean isButtonId(Element parent, String targetId) {
- for (Element child : LintUtils.getChildren(parent)) {
- String id = child.getAttributeNS(ANDROID_URI, ATTR_ID);
- if (LintUtils.idReferencesMatch(id, targetId)) {
- return child.getTagName().equals(BUTTON);
- }
- }
- return false;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
deleted file mode 100644
index 6dc6e67..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CLASS_VIEW;
-import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.filterRelevantAnnotations;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Super;
-
-/**
- * Makes sure that methods call super when overriding methods.
- */
-public class CallSuperDetector extends Detector implements Detector.JavaScanner {
- private static final String CALL_SUPER_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "CallSuper"; //$NON-NLS-1$
- private static final String ON_DETACHED_FROM_WINDOW = "onDetachedFromWindow"; //$NON-NLS-1$
- private static final String ON_VISIBILITY_CHANGED = "onVisibilityChanged"; //$NON-NLS-1$
-
- private static final Implementation IMPLEMENTATION = new Implementation(
- CallSuperDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Missing call to super */
- public static final Issue ISSUE = Issue.create(
- "MissingSuperCall", //$NON-NLS-1$
- "Missing Super Call",
-
- "Some methods, such as `View#onDetachedFromWindow`, require that you also " +
- "call the super implementation as part of your method.",
-
- Category.CORRECTNESS,
- 9,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Constructs a new {@link CallSuperDetector} check */
- public CallSuperDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>>singletonList(MethodDeclaration.class);
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
- return new ForwardingAstVisitor() {
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- ResolvedNode resolved = context.resolve(node);
- if (resolved instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- checkCallSuper(context, node, method);
- }
-
- return false;
- }
- };
- }
-
- private static void checkCallSuper(@NonNull JavaContext context,
- @NonNull MethodDeclaration declaration,
- @NonNull ResolvedMethod method) {
-
- ResolvedMethod superMethod = getRequiredSuperMethod(method);
- if (superMethod != null) {
- if (!SuperCallVisitor.callsSuper(context, declaration, superMethod)) {
- String methodName = method.getName();
- String message = "Overriding method should call `super."
- + methodName + "`";
- Location location = context.getLocation(declaration.astMethodName());
- context.report(ISSUE, declaration, location, message);
- }
- }
- }
-
- /**
- * Checks whether the given method overrides a method which requires the super method
- * to be invoked, and if so, returns it (otherwise returns null)
- */
- @Nullable
- private static ResolvedMethod getRequiredSuperMethod(
- @NonNull ResolvedMethod method) {
-
- String name = method.getName();
- if (ON_DETACHED_FROM_WINDOW.equals(name)) {
- // No longer annotated on the framework method since it's
- // now handled via onDetachedFromWindowInternal, but overriding
- // is still dangerous if supporting older versions so flag
- // this for now (should make annotation carry metadata like
- // compileSdkVersion >= N).
- if (!method.getContainingClass().isSubclassOf(CLASS_VIEW, false)) {
- return null;
- }
- return method.getSuperMethod();
- } else if (ON_VISIBILITY_CHANGED.equals(name)) {
- // From Android Wear API; doesn't yet have an annotation
- // but we want to enforce this right away until the AAR
- // is updated to supply it once @CallSuper is available in
- // the support library
- if (!method.getContainingClass().isSubclassOf(
- "android.support.wearable.watchface.WatchFaceService.Engine", false)) {
- return null;
- }
- return method.getSuperMethod();
- }
-
- // Look up annotations metadata
- ResolvedMethod directSuper = method.getSuperMethod();
- ResolvedMethod superMethod = directSuper;
- while (superMethod != null) {
- Iterable<ResolvedAnnotation> annotations = superMethod.getAnnotations();
- annotations = filterRelevantAnnotations(annotations);
- for (ResolvedAnnotation annotation : annotations) {
- String signature = annotation.getSignature();
- if (CALL_SUPER_ANNOTATION.equals(signature)) {
- return directSuper;
- } else if (signature.endsWith(".OverrideMustInvoke")) {
- // Handle findbugs annotation on the fly too
- return directSuper;
- }
- }
- superMethod = superMethod.getSuperMethod();
- }
-
- return null;
- }
-
- /** Visits a method and determines whether the method calls its super method */
- private static class SuperCallVisitor extends ForwardingAstVisitor {
- private final JavaContext mContext;
- private final ResolvedMethod mMethod;
- private boolean mCallsSuper;
-
- public static boolean callsSuper(
- @NonNull JavaContext context,
- @NonNull MethodDeclaration methodDeclaration,
- @NonNull ResolvedMethod method) {
- SuperCallVisitor visitor = new SuperCallVisitor(context, method);
- methodDeclaration.accept(visitor);
- return visitor.mCallsSuper;
- }
-
- private SuperCallVisitor(@NonNull JavaContext context, @NonNull ResolvedMethod method) {
- mContext = context;
- mMethod = method;
- }
-
- @Override
- public boolean visitSuper(Super node) {
- ResolvedNode resolved = null;
- if (node.getParent() instanceof MethodInvocation) {
- resolved = mContext.resolve(node.getParent());
- }
- if (resolved == null) {
- resolved = mContext.resolve(node);
- }
- if (mMethod.equals(resolved)) {
- mCallsSuper = true;
- return true;
- }
- return false;
- }
-
- @Override
- public boolean visitNode(Node node) {
- return mCallsSuper || super.visitNode(node);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
deleted file mode 100644
index 3c53650..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.google.common.collect.Sets;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-
-/**
- * Ensures that Cipher.getInstance is not called with AES as the parameter.
- */
-public class CipherGetInstanceDetector extends Detector implements Detector.JavaScanner {
- public static final Issue ISSUE = Issue.create(
- "GetInstance", //$NON-NLS-1$
- "Cipher.getInstance with ECB",
- "`Cipher#getInstance` should not be called with ECB as the cipher mode or without "
- + "setting the cipher mode because the default mode on android is ECB, which "
- + "is insecure.",
- Category.SECURITY,
- 9,
- Severity.WARNING,
- new Implementation(
- CipherGetInstanceDetector.class,
- Scope.JAVA_FILE_SCOPE));
-
- private static final String CIPHER = "javax.crypto.Cipher"; //$NON-NLS-1$
- private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
- private static final Set<String> ALGORITHM_ONLY =
- Sets.newHashSet("AES", "DES", "DESede"); //$NON-NLS-1$
- private static final String ECB = "ECB"; //$NON-NLS-1$
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Nullable
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList(GET_INSTANCE);
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- // Ignore if the method doesn't fit our description.
- JavaParser.ResolvedNode resolved = context.resolve(node);
- if (!(resolved instanceof JavaParser.ResolvedMethod)) {
- return;
- }
- JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
- if (!method.getContainingClass().isSubclassOf(CIPHER, false)) {
- return;
- }
- StrictListAccessor<Expression, MethodInvocation> argumentList = node.astArguments();
- if (argumentList != null && argumentList.size() == 1) {
- Expression expression = argumentList.first();
- if (expression instanceof StringLiteral) {
- StringLiteral argument = (StringLiteral)expression;
- String parameter = argument.astValue();
- checkParameter(context, node, argument, parameter, false);
- } else {
- JavaParser.ResolvedNode resolve = context.resolve(expression);
- if (resolve instanceof JavaParser.ResolvedField) {
- JavaParser.ResolvedField field = (JavaParser.ResolvedField) resolve;
- Object value = field.getValue();
- if (value instanceof String) {
- checkParameter(context, node, expression, (String)value, true);
- }
- }
- }
- }
- }
-
- private static void checkParameter(@NonNull JavaContext context,
- @NonNull MethodInvocation call, @NonNull Node node, @NonNull String value,
- boolean includeValue) {
- if (ALGORITHM_ONLY.contains(value)) {
- String message = "`Cipher.getInstance` should not be called without setting the"
- + " encryption mode and padding";
- context.report(ISSUE, call, context.getLocation(node), message);
- } else if (value.contains(ECB)) {
- String message = "ECB encryption mode should not be used";
- if (includeValue) {
- message += " (was \"" + value + "\")";
- }
- context.report(ISSUE, call, context.getLocation(node), message);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
deleted file mode 100644
index b28df29..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CLASS_CONTEXT;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.JavaParser.ResolvedField;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
-import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.collect.Lists;
-
-import java.util.Arrays;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.BinaryOperator;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Return;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Checks for missing {@code recycle} calls on resources that encourage it, and
- * for missing {@code commit} calls on FragmentTransactions, etc.
- */
-public class CleanupDetector extends Detector implements JavaScanner {
-
- private static final Implementation IMPLEMENTATION = new Implementation(
- CleanupDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Problems with missing recycle calls */
- public static final Issue RECYCLE_RESOURCE = Issue.create(
- "Recycle", //$NON-NLS-1$
- "Missing `recycle()` calls",
-
- "Many resources, such as TypedArrays, VelocityTrackers, etc., " +
- "should be recycled (with a `recycle()` call) after use. This lint check looks " +
- "for missing `recycle()` calls.",
-
- Category.PERFORMANCE,
- 7,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Problems with missing commit calls. */
- public static final Issue COMMIT_FRAGMENT = Issue.create(
- "CommitTransaction", //$NON-NLS-1$
- "Missing `commit()` calls",
-
- "After creating a `FragmentTransaction`, you typically need to commit it as well",
-
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- IMPLEMENTATION);
-
- // Target method names
- private static final String RECYCLE = "recycle"; //$NON-NLS-1$
- private static final String RELEASE = "release"; //$NON-NLS-1$
- private static final String OBTAIN = "obtain"; //$NON-NLS-1$
- private static final String SHOW = "show"; //$NON-NLS-1$
- private static final String ACQUIRE_CPC = "acquireContentProviderClient"; //$NON-NLS-1$
- private static final String OBTAIN_NO_HISTORY = "obtainNoHistory"; //$NON-NLS-1$
- private static final String OBTAIN_ATTRIBUTES = "obtainAttributes"; //$NON-NLS-1$
- private static final String OBTAIN_TYPED_ARRAY = "obtainTypedArray"; //$NON-NLS-1$
- private static final String OBTAIN_STYLED_ATTRIBUTES = "obtainStyledAttributes"; //$NON-NLS-1$
- private static final String BEGIN_TRANSACTION = "beginTransaction"; //$NON-NLS-1$
- private static final String COMMIT = "commit"; //$NON-NLS-1$
- private static final String COMMIT_ALLOWING_LOSS = "commitAllowingStateLoss"; //$NON-NLS-1$
- private static final String QUERY = "query"; //$NON-NLS-1$
- private static final String RAW_QUERY = "rawQuery"; //$NON-NLS-1$
- private static final String QUERY_WITH_FACTORY = "queryWithFactory"; //$NON-NLS-1$
- private static final String RAW_QUERY_WITH_FACTORY = "rawQueryWithFactory"; //$NON-NLS-1$
- private static final String CLOSE = "close"; //$NON-NLS-1$
-
- private static final String MOTION_EVENT_CLS = "android.view.MotionEvent"; //$NON-NLS-1$
- private static final String RESOURCES_CLS = "android.content.res.Resources"; //$NON-NLS-1$
- private static final String PARCEL_CLS = "android.os.Parcel"; //$NON-NLS-1$
- private static final String TYPED_ARRAY_CLS = "android.content.res.TypedArray"; //$NON-NLS-1$
- private static final String VELOCITY_TRACKER_CLS = "android.view.VelocityTracker";//$NON-NLS-1$
- private static final String DIALOG_FRAGMENT = "android.app.DialogFragment"; //$NON-NLS-1$
- private static final String DIALOG_V4_FRAGMENT =
- "android.support.v4.app.DialogFragment"; //$NON-NLS-1$
- private static final String FRAGMENT_MANAGER_CLS = "android.app.FragmentManager"; //$NON-NLS-1$
- private static final String FRAGMENT_MANAGER_V4_CLS =
- "android.support.v4.app.FragmentManager"; //$NON-NLS-1$
- private static final String FRAGMENT_TRANSACTION_CLS =
- "android.app.FragmentTransaction"; //$NON-NLS-1$
- private static final String FRAGMENT_TRANSACTION_V4_CLS =
- "android.support.v4.app.FragmentTransaction"; //$NON-NLS-1$
-
- public static final String SURFACE_CLS = "android.view.Surface";
- public static final String SURFACE_TEXTURE_CLS = "android.graphics.SurfaceTexture";
-
- public static final String CONTENT_PROVIDER_CLIENT_CLS
- = "android.content.ContentProviderClient";
-
- public static final String CONTENT_RESOLVER_CLS = "android.content.ContentResolver";
- public static final String CONTENT_PROVIDER_CLS = "android.content.ContentProvider";
- @SuppressWarnings("SpellCheckingInspection")
- public static final String SQLITE_DATABASE_CLS = "android.database.sqlite.SQLiteDatabase";
- public static final String CURSOR_CLS = "android.database.Cursor";
-
- /** Constructs a new {@link CleanupDetector} */
- public CleanupDetector() {
- }
-
- // ---- Implements JavaScanner ----
-
- @Nullable
- @Override
- public List<String> getApplicableMethodNames() {
- return Arrays.asList(
- // FragmentManager commit check
- BEGIN_TRANSACTION,
-
- // Recycle check
- OBTAIN, OBTAIN_NO_HISTORY,
- OBTAIN_STYLED_ATTRIBUTES,
- OBTAIN_ATTRIBUTES,
- OBTAIN_TYPED_ARRAY,
-
- // Release check
- ACQUIRE_CPC,
-
- // Cursor close check
- QUERY, RAW_QUERY, QUERY_WITH_FACTORY, RAW_QUERY_WITH_FACTORY
- );
- }
-
- @Nullable
- @Override
- public List<String> getApplicableConstructorTypes() {
- return Arrays.asList(SURFACE_TEXTURE_CLS, SURFACE_CLS);
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
-
- String name = node.astName().astValue();
- if (BEGIN_TRANSACTION.equals(name)) {
- checkTransactionCommits(context, node);
- } else {
- checkResourceRecycled(context, node, name);
- }
- }
-
- @Override
- public void visitConstructor(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull ConstructorInvocation node, @NonNull ResolvedMethod constructor) {
- checkRecycled(context, node, constructor.getContainingClass().getSignature(), RELEASE);
- }
-
- private static void checkResourceRecycled(@NonNull JavaContext context,
- @NonNull MethodInvocation node, @NonNull String name) {
- // Recycle detector
- ResolvedNode resolved = context.resolve(node);
- if (!(resolved instanceof ResolvedMethod)) {
- return;
- }
- ResolvedMethod method = (ResolvedMethod) resolved;
- ResolvedClass containingClass = method.getContainingClass();
- if ((OBTAIN.equals(name) || OBTAIN_NO_HISTORY.equals(name)) &&
- containingClass.isSubclassOf(MOTION_EVENT_CLS, false)) {
- checkRecycled(context, node, MOTION_EVENT_CLS, RECYCLE);
- } else if (OBTAIN.equals(name) && containingClass.isSubclassOf(PARCEL_CLS, false)) {
- checkRecycled(context, node, PARCEL_CLS, RECYCLE);
- } else if (OBTAIN.equals(name) &&
- containingClass.isSubclassOf(VELOCITY_TRACKER_CLS, false)) {
- checkRecycled(context, node, VELOCITY_TRACKER_CLS, RECYCLE);
- } else if ((OBTAIN_STYLED_ATTRIBUTES.equals(name)
- || OBTAIN_ATTRIBUTES.equals(name)
- || OBTAIN_TYPED_ARRAY.equals(name)) &&
- (containingClass.isSubclassOf(CLASS_CONTEXT, false) ||
- containingClass.isSubclassOf(RESOURCES_CLS, false))) {
- TypeDescriptor returnType = method.getReturnType();
- if (returnType != null && returnType.matchesSignature(TYPED_ARRAY_CLS)) {
- checkRecycled(context, node, TYPED_ARRAY_CLS, RECYCLE);
- }
- } else if (ACQUIRE_CPC.equals(name) && containingClass.isSubclassOf(
- CONTENT_RESOLVER_CLS, false)) {
- checkRecycled(context, node, CONTENT_PROVIDER_CLIENT_CLS, RELEASE);
- } else if ((QUERY.equals(name)
- || RAW_QUERY.equals(name)
- || QUERY_WITH_FACTORY.equals(name)
- || RAW_QUERY_WITH_FACTORY.equals(name))
- && (containingClass.isSubclassOf(SQLITE_DATABASE_CLS, false) ||
- containingClass.isSubclassOf(CONTENT_RESOLVER_CLS, false) ||
- containingClass.isSubclassOf(CONTENT_PROVIDER_CLS, false) ||
- containingClass.isSubclassOf(CONTENT_PROVIDER_CLIENT_CLS, false))) {
- // Other potential cursors-returning methods that should be tracked:
- // android.app.DownloadManager#query
- // android.content.ContentProviderClient#query
- // android.content.ContentResolver#query
- // android.database.sqlite.SQLiteQueryBuilder#query
- // android.provider.Browser#getAllBookmarks
- // android.provider.Browser#getAllVisitedUrls
- // android.provider.DocumentsProvider#queryChildDocuments
- // android.provider.DocumentsProvider#qqueryDocument
- // android.provider.DocumentsProvider#queryRecentDocuments
- // android.provider.DocumentsProvider#queryRoots
- // android.provider.DocumentsProvider#querySearchDocuments
- // android.provider.MediaStore$Images$Media#query
- // android.widget.FilterQueryProvider#runQuery
- checkRecycled(context, node, CURSOR_CLS, CLOSE);
- }
- }
-
- private static void checkRecycled(@NonNull final JavaContext context, @NonNull Node node,
- @NonNull final String recycleType, @NonNull final String recycleName) {
- ResolvedVariable boundVariable = getVariable(context, node);
- if (boundVariable == null) {
- return;
- }
-
- Node method = JavaContext.findSurroundingMethod(node);
- if (method == null) {
- return;
- }
-
- FinishVisitor visitor = new FinishVisitor(context, boundVariable) {
- @Override
- protected boolean isCleanupCall(@NonNull MethodInvocation call) {
- String methodName = call.astName().astValue();
- if (!recycleName.equals(methodName)) {
- return false;
- }
- ResolvedNode resolved = mContext.resolve(call);
- if (resolved instanceof ResolvedMethod) {
- ResolvedClass containingClass = ((ResolvedMethod) resolved).getContainingClass();
- if (containingClass.isSubclassOf(recycleType, false)) {
- // Yes, called the right recycle() method; now make sure
- // we're calling it on the right variable
- Expression operand = call.astOperand();
- if (operand != null) {
- resolved = mContext.resolve(operand);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- return true;
- }
- }
- }
- }
- return false;
- }
- };
-
- method.accept(visitor);
- if (visitor.isCleanedUp() || visitor.variableEscapes()) {
- return;
- }
-
- String className = recycleType.substring(recycleType.lastIndexOf('.') + 1);
- String message;
- if (RECYCLE.equals(recycleName)) {
- message = String.format(
- "This `%1$s` should be recycled after use with `#recycle()`", className);
- } else {
- message = String.format(
- "This `%1$s` should be freed up after use with `#%2$s()`", className,
- recycleName);
- }
- Node locationNode = node instanceof MethodInvocation ?
- ((MethodInvocation) node).astName() : node;
- Location location = context.getLocation(locationNode);
- context.report(RECYCLE_RESOURCE, node, location, message);
- }
-
- private static boolean checkTransactionCommits(@NonNull JavaContext context,
- @NonNull MethodInvocation node) {
- if (isBeginTransaction(context, node)) {
- ResolvedVariable boundVariable = getVariable(context, node);
- if (boundVariable == null && isCommittedInChainedCalls(context, node)) {
- return true;
- }
-
- if (boundVariable != null) {
- Node method = JavaContext.findSurroundingMethod(node);
- if (method == null) {
- return true;
- }
-
- FinishVisitor commitVisitor = new FinishVisitor(context, boundVariable) {
- @Override
- protected boolean isCleanupCall(@NonNull MethodInvocation call) {
- if (isTransactionCommitMethodCall(mContext, call)) {
- Expression operand = call.astOperand();
- if (operand != null) {
- ResolvedNode resolved = mContext.resolve(operand);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- return true;
- } else if (resolved instanceof ResolvedMethod
- && operand instanceof MethodInvocation
- && isCommittedInChainedCalls(mContext,(MethodInvocation) operand)) {
- // Check that the target of the committed chains is the
- // right variable!
- while (operand instanceof MethodInvocation) {
- operand = ((MethodInvocation)operand).astOperand();
- }
- if (operand instanceof VariableReference) {
- resolved = mContext.resolve(operand);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- return true;
- }
- }
- }
- }
- } else if (isShowFragmentMethodCall(mContext, call)) {
- StrictListAccessor<Expression, MethodInvocation> arguments =
- call.astArguments();
- if (arguments.size() == 2) {
- Expression first = arguments.first();
- ResolvedNode resolved = mContext.resolve(first);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- return true;
- }
- }
- }
- return false;
- }
- };
-
- method.accept(commitVisitor);
- if (commitVisitor.isCleanedUp() || commitVisitor.variableEscapes()) {
- return true;
- }
- }
-
- String message = "This transaction should be completed with a `commit()` call";
- context.report(COMMIT_FRAGMENT, node, context.getLocation(node.astName()),
- message);
- }
- return false;
- }
-
- private static boolean isCommittedInChainedCalls(@NonNull JavaContext context,
- @NonNull MethodInvocation node) {
- // Look for chained calls since the FragmentManager methods all return "this"
- // to allow constructor chaining, e.g.
- // getFragmentManager().beginTransaction().addToBackStack("test")
- // .disallowAddToBackStack().hide(mFragment2).setBreadCrumbShortTitle("test")
- // .show(mFragment2).setCustomAnimations(0, 0).commit();
- Node parent = node.getParent();
- while (parent instanceof MethodInvocation) {
- MethodInvocation methodInvocation = (MethodInvocation) parent;
- if (isTransactionCommitMethodCall(context, methodInvocation)
- || isShowFragmentMethodCall(context, methodInvocation)) {
- return true;
- }
-
- parent = parent.getParent();
- }
-
- return false;
- }
-
- private static boolean isTransactionCommitMethodCall(@NonNull JavaContext context,
- @NonNull MethodInvocation call) {
-
- String methodName = call.astName().astValue();
- return (COMMIT.equals(methodName) || COMMIT_ALLOWING_LOSS.equals(methodName)) &&
- isMethodOnFragmentClass(context, call,
- FRAGMENT_TRANSACTION_CLS,
- FRAGMENT_TRANSACTION_V4_CLS);
- }
-
- private static boolean isShowFragmentMethodCall(@NonNull JavaContext context,
- @NonNull MethodInvocation call) {
- String methodName = call.astName().astValue();
- return SHOW.equals(methodName)
- && isMethodOnFragmentClass(context, call,
- DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT);
- }
-
- private static boolean isMethodOnFragmentClass(
- @NonNull JavaContext context,
- @NonNull MethodInvocation call,
- @NonNull String fragmentClass,
- @NonNull String v4FragmentClass) {
- ResolvedNode resolved = context.resolve(call);
- if (resolved instanceof ResolvedMethod) {
- ResolvedClass containingClass = ((ResolvedMethod) resolved).getContainingClass();
- return containingClass.isSubclassOf(fragmentClass, false) ||
- containingClass.isSubclassOf(v4FragmentClass, false);
- }
-
- return false;
- }
-
- @Nullable
- public static ResolvedVariable getVariable(@NonNull JavaContext context,
- @NonNull Node expression) {
- Node parent = expression.getParent();
- if (parent instanceof BinaryExpression) {
- BinaryExpression binaryExpression = (BinaryExpression) parent;
- if (binaryExpression.astOperator() == BinaryOperator.ASSIGN) {
- Expression lhs = binaryExpression.astLeft();
- ResolvedNode resolved = context.resolve(lhs);
- if (resolved instanceof ResolvedVariable) {
- return (ResolvedVariable) resolved;
- }
- }
- } else if (parent instanceof VariableDefinitionEntry) {
- ResolvedNode resolved = context.resolve(parent);
- if (resolved instanceof ResolvedVariable) {
- return (ResolvedVariable) resolved;
- }
- }
-
- return null;
- }
-
- private static boolean isBeginTransaction(@NonNull JavaContext context,
- @NonNull MethodInvocation node) {
- String methodName = node.astName().astValue();
- assert methodName.equals(BEGIN_TRANSACTION) : methodName;
- if (BEGIN_TRANSACTION.equals(methodName)) {
- ResolvedNode resolved = context.resolve(node);
- if (resolved instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- ResolvedClass containingClass = method.getContainingClass();
- if (containingClass.isSubclassOf(FRAGMENT_MANAGER_CLS, false)
- || containingClass.isSubclassOf(FRAGMENT_MANAGER_V4_CLS,
- false)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Visitor which checks whether an operation is "finished"; in the case
- * of a FragmentTransaction we're looking for a "commit" call; in the
- * case of a TypedArray we're looking for a "recycle", call, in the
- * case of a database cursor we're looking for a "close" call, etc.
- */
- private abstract static class FinishVisitor extends ForwardingAstVisitor {
- protected final JavaContext mContext;
- protected final List<ResolvedVariable> mVariables;
- private boolean mContainsCleanup;
- private boolean mEscapes;
-
- public FinishVisitor(JavaContext context, @NonNull ResolvedVariable variable) {
- mContext = context;
- mVariables = Lists.newArrayList(variable);
- }
-
- public boolean isCleanedUp() {
- return mContainsCleanup;
- }
-
- public boolean variableEscapes() {
- return mEscapes;
- }
-
- @Override
- public boolean visitNode(Node node) {
- return mContainsCleanup || super.visitNode(node);
- }
-
- protected abstract boolean isCleanupCall(@NonNull MethodInvocation call);
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation call) {
- if (mContainsCleanup) {
- return true;
- }
-
- super.visitMethodInvocation(call);
-
- // Look for escapes
- if (!mEscapes) {
- for (Expression expression : call.astArguments()) {
- if (expression instanceof VariableReference) {
- ResolvedNode resolved = mContext.resolve(expression);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- mEscapes = true;
-
- // Special case: MotionEvent.obtain(MotionEvent): passing in an
- // event here does not recycle the event, and we also know it
- // doesn't escape
- if (OBTAIN.equals(call.astName().astValue())) {
- ResolvedNode r = mContext.resolve(call);
- if (r instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) r;
- ResolvedClass cls = method.getContainingClass();
- if (cls.matches(MOTION_EVENT_CLS)) {
- mEscapes = false;
- }
- }
- }
- }
- }
- }
- }
-
- if (isCleanupCall(call)) {
- mContainsCleanup = true;
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- Expression initializer = node.astInitializer();
- if (initializer instanceof VariableReference) {
- ResolvedNode resolved = mContext.resolve(initializer);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- ResolvedNode resolvedVariable = mContext.resolve(node);
- if (resolvedVariable instanceof ResolvedVariable) {
- ResolvedVariable variable = (ResolvedVariable) resolvedVariable;
- mVariables.add(variable);
- } else if (resolvedVariable instanceof ResolvedField) {
- mEscapes = true;
- }
- }
- }
- return super.visitVariableDefinitionEntry(node);
- }
-
- @Override
- public boolean visitBinaryExpression(BinaryExpression node) {
- if (node.astOperator() == BinaryOperator.ASSIGN) {
- Expression rhs = node.astRight();
- if (rhs instanceof VariableReference) {
- ResolvedNode resolved = mContext.resolve(rhs);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- ResolvedNode resolvedLhs = mContext.resolve(node.astLeft());
- if (resolvedLhs instanceof ResolvedVariable) {
- ResolvedVariable variable = (ResolvedVariable) resolvedLhs;
- mVariables.add(variable);
- } else if (resolvedLhs instanceof ResolvedField) {
- mEscapes = true;
- }
- }
- }
- }
- return super.visitBinaryExpression(node);
- }
-
- @Override
- public boolean visitReturn(Return node) {
- Expression value = node.astValue();
- if (value instanceof VariableReference) {
- ResolvedNode resolved = mContext.resolve(value);
- //noinspection SuspiciousMethodCalls
- if (resolved != null && mVariables.contains(resolved)) {
- mEscapes = true;
- }
- }
-
- return super.visitReturn(node);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
deleted file mode 100644
index 6c11ec1..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
+++ /dev/null
@@ -1,533 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
-import org.objectweb.asm.tree.FrameNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.IntInsnNode;
-import org.objectweb.asm.tree.JumpInsnNode;
-import org.objectweb.asm.tree.LabelNode;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.LineNumberNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.TryCatchBlockNode;
-import org.objectweb.asm.tree.TypeInsnNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-//import org.objectweb.asm.util.Printer;
-
-/**
- * A {@linkplain ControlFlowGraph} is a graph containing a node for each
- * instruction in a method, and an edge for each possible control flow; usually
- * just "next" for the instruction following the current instruction, but in the
- * case of a branch such as an "if", multiple edges to each successive location,
- * or with a "goto", a single edge to the jumped-to instruction.
- * <p>
- * It also adds edges for abnormal control flow, such as the possibility of a
- * method call throwing a runtime exception.
- */
-public class ControlFlowGraph {
- /** Map from instructions to nodes */
- private Map<AbstractInsnNode, Node> mNodeMap;
- private MethodNode mMethod;
-
- /**
- * Creates a new {@link ControlFlowGraph} and populates it with the flow
- * control for the given method. If the optional {@code initial} parameter is
- * provided with an existing graph, then the graph is simply populated, not
- * created. This allows subclassing of the graph instance, if necessary.
- *
- * @param initial usually null, but can point to an existing instance of a
- * {@link ControlFlowGraph} in which that graph is reused (but
- * populated with new edges)
- * @param classNode the class containing the method to be analyzed
- * @param method the method to be analyzed
- * @return a {@link ControlFlowGraph} with nodes for the control flow in the
- * given method
- * @throws AnalyzerException if the underlying bytecode library is unable to
- * analyze the method bytecode
- */
- @NonNull
- public static ControlFlowGraph create(
- @Nullable ControlFlowGraph initial,
- @NonNull ClassNode classNode,
- @NonNull MethodNode method) throws AnalyzerException {
- final ControlFlowGraph graph = initial != null ? initial : new ControlFlowGraph();
- final InsnList instructions = method.instructions;
- graph.mNodeMap = Maps.newHashMapWithExpectedSize(instructions.size());
- graph.mMethod = method;
-
- // Create a flow control graph using ASM5's analyzer. According to the ASM 4 guide
- // (download.forge.objectweb.org/asm/asm4-guide.pdf) there are faster ways to construct
- // it, but those require a lot more code.
- Analyzer analyzer = new Analyzer(new BasicInterpreter()) {
- @Override
- protected void newControlFlowEdge(int insn, int successor) {
- // Update the information as of whether the this object has been
- // initialized at the given instruction.
- AbstractInsnNode from = instructions.get(insn);
- AbstractInsnNode to = instructions.get(successor);
- graph.add(from, to);
- }
-
- @Override
- protected boolean newControlFlowExceptionEdge(int insn, TryCatchBlockNode tcb) {
- AbstractInsnNode from = instructions.get(insn);
- graph.exception(from, tcb);
- return super.newControlFlowExceptionEdge(insn, tcb);
- }
-
- @Override
- protected boolean newControlFlowExceptionEdge(int insn, int successor) {
- AbstractInsnNode from = instructions.get(insn);
- AbstractInsnNode to = instructions.get(successor);
- graph.exception(from, to);
- return super.newControlFlowExceptionEdge(insn, successor);
- }
- };
-
- analyzer.analyze(classNode.name, method);
- return graph;
- }
-
- /**
- * Checks whether there is a path from the given source node to the given
- * destination node
- */
- @SuppressWarnings("MethodMayBeStatic")
- private boolean isConnected(@NonNull Node from,
- @NonNull Node to, @NonNull Set<Node> seen) {
- if (from == to) {
- return true;
- } else if (seen.contains(from)) {
- return false;
- }
- seen.add(from);
-
- List<Node> successors = from.successors;
- List<Node> exceptions = from.exceptions;
- if (exceptions != null) {
- for (Node successor : exceptions) {
- if (isConnected(successor, to, seen)) {
- return true;
- }
- }
- }
-
- if (successors != null) {
- for (Node successor : successors) {
- if (isConnected(successor, to, seen)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Checks whether there is a path from the given source node to the given
- * destination node
- */
- public boolean isConnected(@NonNull Node from, @NonNull Node to) {
- return isConnected(from, to, Sets.<Node>newIdentityHashSet());
- }
-
- /**
- * Checks whether there is a path from the given instruction to the given
- * instruction node
- */
- public boolean isConnected(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- return isConnected(getNode(from), getNode(to));
- }
-
- /** A {@link Node} is a node in the control flow graph for a method, pointing to
- * the instruction and its possible successors */
- public static class Node {
- /** The instruction */
- public final AbstractInsnNode instruction;
- /** Any normal successors (e.g. following instruction, or goto or conditional flow) */
- public final List<Node> successors = new ArrayList<Node>(2);
- /** Any abnormal successors (e.g. the handler to go to following an exception) */
- public final List<Node> exceptions = new ArrayList<Node>(1);
-
- /** A tag for use during depth-first-search iteration of the graph etc */
- public int visit;
-
- /**
- * Constructs a new control graph node
- *
- * @param instruction the instruction to associate with this node
- */
- public Node(@NonNull AbstractInsnNode instruction) {
- this.instruction = instruction;
- }
-
- void addSuccessor(@NonNull Node node) {
- if (!successors.contains(node)) {
- successors.add(node);
- }
- }
-
- void addExceptionPath(@NonNull Node node) {
- if (!exceptions.contains(node)) {
- exceptions.add(node);
- }
- }
-
- /**
- * Represents this instruction as a string, for debugging purposes
- *
- * @param includeAdjacent whether it should include a display of
- * adjacent nodes as well
- * @return a string representation
- */
- @NonNull
- public String toString(boolean includeAdjacent) {
- StringBuilder sb = new StringBuilder(100);
-
- sb.append(getId(instruction));
- sb.append(':');
-
- if (instruction instanceof LabelNode) {
- //LabelNode l = (LabelNode) instruction;
- //sb.append('L' + l.getLabel().getOffset() + ":");
- //sb.append('L' + l.getLabel().info + ":");
- sb.append("LABEL");
- } else if (instruction instanceof LineNumberNode) {
- sb.append("LINENUMBER ").append(((LineNumberNode)instruction).line);
- } else if (instruction instanceof FrameNode) {
- sb.append("FRAME");
- } else {
- int opcode = instruction.getOpcode();
- String opcodeName = getOpcodeName(opcode);
- sb.append(opcodeName);
- if (instruction.getType() == AbstractInsnNode.METHOD_INSN) {
- sb.append('(').append(((MethodInsnNode)instruction).name).append(')');
- }
- }
-
- if (includeAdjacent) {
- if (successors != null && !successors.isEmpty()) {
- sb.append(" Next:");
- for (Node successor : successors) {
- sb.append(' ');
- sb.append(successor.toString(false));
- }
- }
-
- if (exceptions != null && !exceptions.isEmpty()) {
- sb.append(" Exceptions:");
- for (Node exception : exceptions) {
- sb.append(' ');
- sb.append(exception.toString(false));
- }
- }
- sb.append('\n');
- }
-
- return sb.toString();
- }
- }
-
- /** Adds an exception flow to this graph */
- protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- getNode(from).addSuccessor(getNode(to));
- }
-
- /** Adds an exception flow to this graph */
- protected void exception(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- // For now, these edges appear useless; we also get more specific
- // information via the TryCatchBlockNode which we use instead.
- //getNode(from).addExceptionPath(getNode(to));
- }
-
- /** Adds an exception try block node to this graph */
- protected void exception(@NonNull AbstractInsnNode from, @NonNull TryCatchBlockNode tcb) {
- // Add tcb's to all instructions in the range
- LabelNode start = tcb.start;
- LabelNode end = tcb.end; // exclusive
-
- // Add exception edges for all method calls in the range
- AbstractInsnNode curr = start;
- Node handlerNode = getNode(tcb.handler);
- while (curr != end && curr != null) {
- if (curr.getType() == AbstractInsnNode.METHOD_INSN) {
- // Method call; add exception edge to handler
- if (tcb.type == null) {
- // finally block: not an exception path
- getNode(curr).addSuccessor(handlerNode);
- }
- getNode(curr).addExceptionPath(handlerNode);
- }
- curr = curr.getNext();
- }
- }
-
- /**
- * Looks up (and if necessary) creates a graph node for the given instruction
- *
- * @param instruction the instruction
- * @return the control flow graph node corresponding to the given
- * instruction
- */
- @NonNull
- public Node getNode(@NonNull AbstractInsnNode instruction) {
- Node node = mNodeMap.get(instruction);
- if (node == null) {
- node = new Node(instruction);
- mNodeMap.put(instruction, node);
- }
-
- return node;
- }
-
- /**
- * Creates a human readable version of the graph
- *
- * @param start the starting instruction, or null if not known or to use the
- * first instruction
- * @return a string version of the graph
- */
- @NonNull
- public String toString(@Nullable Node start) {
- StringBuilder sb = new StringBuilder(400);
-
- AbstractInsnNode curr;
- if (start != null) {
- curr = start.instruction;
- } else {
- if (mNodeMap.isEmpty()) {
- return "<empty>";
- } else {
- curr = mNodeMap.keySet().iterator().next();
- while (curr.getPrevious() != null) {
- curr = curr.getPrevious();
- }
- }
- }
-
- while (curr != null) {
- Node node = mNodeMap.get(curr);
- if (node != null) {
- sb.append(node.toString(true));
- }
- curr = curr.getNext();
- }
-
- return sb.toString();
- }
-
- @Override
- public String toString() {
- return toString(null);
- }
-
- // ---- For debugging only ----
-
- private static Map<Object, String> sIds = null;
- private static int sNextId = 1;
- private static String getId(Object object) {
- if (sIds == null) {
- sIds = Maps.newHashMap();
- }
- String id = sIds.get(object);
- if (id == null) {
- id = Integer.toString(sNextId++);
- sIds.put(object, id);
- }
- return id;
- }
-
- /**
- * Generates dot output of the graph. This can be used with
- * graphwiz to visualize the graph. For example, if you
- * save the output as graph1.gv you can run
- * <pre>
- * $ dot -Tps graph1.gv -o graph1.ps
- * </pre>
- * to generate a postscript file, which you can then view
- * with "gv graph1.ps".
- *
- * (There are also some online web sites where you can
- * paste in dot graphs and see the visualization right
- * there in the browser.)
- *
- * @return a dot description of this control flow graph,
- * useful for debugging
- */
- public String toDot(@Nullable Set<Node> highlight) {
- StringBuilder sb = new StringBuilder();
- sb.append("digraph G {\n");
-
-
- AbstractInsnNode instruction = mMethod.instructions.getFirst();
-
- // Special start node
- sb.append(" start -> ").append(getId(mNodeMap.get(instruction))).append(";\n");
- sb.append(" start [shape=plaintext];\n");
-
- while (instruction != null) {
- Node node = mNodeMap.get(instruction);
- if (node != null) {
- if (node.successors != null) {
- for (Node to : node.successors) {
- sb.append(" ").append(getId(node)).append(" -> ").append(getId(to));
- if (node.instruction instanceof JumpInsnNode) {
- sb.append(" [label=\"");
- if (((JumpInsnNode)node.instruction).label == to.instruction) {
- sb.append("yes");
- } else {
- sb.append("no");
- }
- sb.append("\"]");
- }
- sb.append(";\n");
- }
- }
- if (node.exceptions != null) {
- for (Node to : node.exceptions) {
- sb.append(getId(node)).append(" -> ").append(getId(to));
- sb.append(" [label=\"exception\"];\n");
- }
- }
- }
-
- instruction = instruction.getNext();
- }
-
-
- // Labels
- sb.append("\n");
- for (Node node : mNodeMap.values()) {
- instruction = node.instruction;
- sb.append(" ").append(getId(node)).append(" ");
- sb.append("[label=\"").append(dotDescribe(node)).append("\"");
- if (highlight != null && highlight.contains(node)) {
- sb.append(",shape=box,style=filled");
- } else if (instruction instanceof LineNumberNode ||
- instruction instanceof LabelNode ||
- instruction instanceof FrameNode) {
- sb.append(",shape=oval,style=dotted");
- } else {
- sb.append(",shape=box");
- }
- sb.append("];\n");
- }
-
- sb.append("}");
- return sb.toString();
- }
-
- private static String dotDescribe(Node node) {
- AbstractInsnNode instruction = node.instruction;
- if (instruction instanceof LabelNode) {
- return "Label";
- } else if (instruction instanceof LineNumberNode) {
- LineNumberNode lineNode = (LineNumberNode)instruction;
- return "Line " + lineNode.line;
- } else if (instruction instanceof FrameNode) {
- return "Stack Frame";
- } else if (instruction instanceof MethodInsnNode) {
- MethodInsnNode method = (MethodInsnNode)instruction;
- String cls = method.owner.substring(method.owner.lastIndexOf('/') + 1);
- cls = cls.replace('$','.');
- return "Call " + cls + "#" + method.name;
- } else if (instruction instanceof FieldInsnNode) {
- FieldInsnNode field = (FieldInsnNode) instruction;
- String cls = field.owner.substring(field.owner.lastIndexOf('/') + 1);
- cls = cls.replace('$','.');
- return "Field " + cls + "#" + field.name;
- } else if (instruction instanceof TypeInsnNode && instruction.getOpcode() == Opcodes.NEW) {
- return "New " + ((TypeInsnNode)instruction).desc;
- }
- StringBuilder sb = new StringBuilder();
- String opcodeName = getOpcodeName(instruction.getOpcode());
- sb.append(opcodeName);
-
- if (instruction instanceof IntInsnNode) {
- IntInsnNode in = (IntInsnNode) instruction;
- sb.append(" ").append(Integer.toString(in.operand));
- } else if (instruction instanceof LdcInsnNode) {
- LdcInsnNode ldc = (LdcInsnNode) instruction;
- sb.append(" ");
- if (ldc.cst instanceof String) {
- sb.append("\\\"");
- }
- sb.append(ldc.cst);
- if (ldc.cst instanceof String) {
- sb.append("\\\"");
- }
- }
- return sb.toString();
- }
-
- private static String getOpcodeName(int opcode) {
- if (sOpcodeNames == null) {
- sOpcodeNames = new String[255];
- try {
- Field[] fields = Opcodes.class.getDeclaredFields();
- for (Field field : fields) {
- if (field.getType() == int.class) {
- String name = field.getName();
- if (name.startsWith("ASM") || name.startsWith("V1_") ||
- name.startsWith("ACC_") || name.startsWith("T_") ||
- name.startsWith("H_") || name.startsWith("F_")) {
- continue;
- }
- int val = field.getInt(null);
- if (val >= 0 && val < sOpcodeNames.length) {
- sOpcodeNames[val] = field.getName();
- }
-
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- if (opcode >= 0 && opcode < sOpcodeNames.length) {
- String name = sOpcodeNames[opcode];
- if (name != null) {
- return name;
- }
- }
-
- return Integer.toString(opcode);
- }
-
- private static String[] sOpcodeNames;
-}
-
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
deleted file mode 100644
index fec87c8..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ABSOLUTE_LAYOUT;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_AUTO_TEXT;
-import static com.android.SdkConstants.ATTR_CAPITALIZE;
-import static com.android.SdkConstants.ATTR_EDITABLE;
-import static com.android.SdkConstants.ATTR_ENABLED;
-import static com.android.SdkConstants.ATTR_INPUT_METHOD;
-import static com.android.SdkConstants.ATTR_NUMERIC;
-import static com.android.SdkConstants.ATTR_PASSWORD;
-import static com.android.SdkConstants.ATTR_PHONE_NUMBER;
-import static com.android.SdkConstants.ATTR_SINGLE_LINE;
-import static com.android.SdkConstants.EDIT_TEXT;
-import static com.android.SdkConstants.VALUE_TRUE;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check which looks for usage of deprecated tags, attributes, etc.
- */
-public class DeprecationDetector extends LayoutDetector {
- /** Usage of deprecated views or attributes */
- public static final Issue ISSUE = Issue.create(
- "Deprecated", //$NON-NLS-1$
- "Using deprecated resources",
- "Deprecated views, attributes and so on are deprecated because there " +
- "is a better way to do something. Do it that new way. You've been warned.",
- Category.CORRECTNESS,
- 2,
- Severity.WARNING,
- new Implementation(
- DeprecationDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
- /** Constructs a new {@link DeprecationDetector} */
- public DeprecationDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(
- ABSOLUTE_LAYOUT
- );
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Arrays.asList(
- // TODO: fill_parent is deprecated as of API 8.
- // We could warn about it, but it will probably be very noisy
- // and make people disable the deprecation check; let's focus on
- // some older flags for now
- //"fill_parent",
-
- ATTR_EDITABLE,
- ATTR_INPUT_METHOD,
- ATTR_AUTO_TEXT,
- ATTR_CAPITALIZE,
-
- // This flag is still used a lot and is still properly handled by TextView
- // so in the interest of not being too noisy and make people ignore all the
- // output, keep quiet about this one -for now-.
- //ATTR_SINGLE_LINE,
-
- // This attribute is marked deprecated in android.R.attr but apparently
- // using the suggested replacement of state_enabled doesn't work, see issue 27613
- //ATTR_ENABLED,
-
- ATTR_NUMERIC,
- ATTR_PHONE_NUMBER,
- ATTR_PASSWORD
-
- // These attributes are also deprecated; not yet enabled until we
- // know the API level to apply the deprecation for:
-
- // "ignored as of ICS (but deprecated earlier)"
- //"fadingEdge",
-
- // "This attribute is not used by the Android operating system."
- //"restoreNeedsApplication",
-
- // "This will create a non-standard UI appearance, because the search bar UI is
- // changing to use only icons for its buttons."
- //"searchButtonText",
-
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- context.report(ISSUE, element, context.getLocation(element),
- String.format("`%1$s` is deprecated", element.getTagName()));
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
- return;
- }
-
- String name = attribute.getLocalName();
- String fix;
- int minSdk = 1;
- if (name.equals(ATTR_EDITABLE)) {
- if (!EDIT_TEXT.equals(attribute.getOwnerElement().getTagName())) {
- fix = "Use an `<EditText>` to make it editable";
- } else {
- if (VALUE_TRUE.equals(attribute.getValue())) {
- fix = "`<EditText>` is already editable";
- } else {
- fix = "Use `inputType` instead";
- }
- }
- } else if (name.equals(ATTR_ENABLED)) {
- fix = "Use `state_enabled` instead";
- } else if (name.equals(ATTR_SINGLE_LINE)) {
- fix = "Use `maxLines=\"1\"` instead";
- } else {
- assert name.equals(ATTR_INPUT_METHOD)
- || name.equals(ATTR_CAPITALIZE)
- || name.equals(ATTR_NUMERIC)
- || name.equals(ATTR_PHONE_NUMBER)
- || name.equals(ATTR_PASSWORD)
- || name.equals(ATTR_AUTO_TEXT);
- fix = "Use `inputType` instead";
- // The inputType attribute was introduced in API 3 so don't warn about
- // deprecation if targeting older platforms
- minSdk = 3;
- }
-
- if (context.getProject().getMinSdk() < minSdk) {
- return;
- }
-
- context.report(ISSUE, attribute, context.getLocation(attribute),
- String.format("`%1$s` is deprecated: %2$s",
- attribute.getName(), fix));
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
deleted file mode 100644
index 7fbc3f3..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Document;
-
-/**
- * Checks that the line endings in DOS files are consistent
- */
-public class DosLineEndingDetector extends LayoutDetector {
- /** Detects mangled DOS line ending documents */
- public static final Issue ISSUE = Issue.create(
- "MangledCRLF", //$NON-NLS-1$
- "Mangled file line endings",
-
- "On Windows, line endings are typically recorded as carriage return plus " +
- "newline: \\r\\n.\n" +
- "\n" +
- "This detector looks for invalid line endings with repeated carriage return " +
- "characters (without newlines). Previous versions of the ADT plugin could " +
- "accidentally introduce these into the file, and when editing the file, the " +
- "editor could produce confusing visual artifacts.",
-
- Category.CORRECTNESS,
- 2,
- Severity.ERROR,
- new Implementation(
- DosLineEndingDetector.class,
- Scope.RESOURCE_FILE_SCOPE))
- .addMoreInfo("https://bugs.eclipse.org/bugs/show_bug.cgi?id=375421"); //$NON-NLS-1$
-
- /** Constructs a new {@link DosLineEndingDetector} */
- public DosLineEndingDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- String contents = context.getContents();
- if (contents == null) {
- return;
- }
-
- // We could look for *consistency* and complain if you mix \n and \r\n too,
- // but that isn't really a problem (most editors handle it) so let's
- // not complain needlessly.
-
- char prev = 0;
- for (int i = 0, n = contents.length(); i < n; i++) {
- char c = contents.charAt(i);
- if (c == '\r' && prev == '\r') {
- String message = "Incorrect line ending: found carriage return (`\\r`) without " +
- "corresponding newline (`\\n`)";
-
- // Mark the whole line as the error range, since pointing just to the
- // line ending makes the error invisible in IDEs and error reports etc
- // Find the most recent non-blank line
- boolean blankLine = true;
- for (int index = i - 2; index < i; index++) {
- char d = contents.charAt(index);
- if (!Character.isWhitespace(d)) {
- blankLine = false;
- }
- }
-
- int lineBegin = i;
- for (int index = i - 2; index >= 0; index--) {
- char d = contents.charAt(index);
- if (d == '\n') {
- lineBegin = index + 1;
- if (!blankLine) {
- break;
- }
- } else if (!Character.isWhitespace(d)) {
- blankLine = false;
- }
- }
-
- int lineEnd = Math.min(contents.length(), i + 1);
- Location location = Location.create(context.file, contents, lineBegin, lineEnd);
- context.report(ISSUE, document.getDocumentElement(), location, message);
- return;
- }
- prev = c;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
deleted file mode 100644
index ee3ca8b..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.utils.SdkUtils.getResourceFieldName;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This detector identifies cases where a resource is defined multiple times in the
- * same resource folder
- */
-public class DuplicateResourceDetector extends ResourceXmlDetector {
-
- /** The main issue discovered by this detector */
- @SuppressWarnings("unchecked")
- public static final Issue ISSUE = Issue.create(
- "DuplicateDefinition", //$NON-NLS-1$
- "Duplicate definitions of resources",
-
- "You can define a resource multiple times in different resource folders; that's how " +
- "string translations are done, for example. However, defining the same resource " +
- "more than once in the same resource folder is likely an error, for example " +
- "attempting to add a new resource without realizing that the name is already used, " +
- "and so on.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- new Implementation(
- DuplicateResourceDetector.class,
- // We should be able to do this incrementally!
- Scope.ALL_RESOURCES_SCOPE,
- Scope.RESOURCE_FILE_SCOPE));
-
- /** Wrong resource value type */
- public static final Issue TYPE_MISMATCH = Issue.create(
- "ReferenceType", //$NON-NLS-1$
- "Incorrect reference types",
- "When you generate a resource alias, the resource you are pointing to must be " +
- "of the same type as the alias",
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- new Implementation(
- DuplicateResourceDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
-
- private static final String PRODUCT = "product"; //$NON-NLS-1$
- private Map<ResourceType, Set<String>> mTypeMap;
- private Map<ResourceType, List<Pair<String, Location.Handle>>> mLocations;
- private File mParent;
-
- /** Constructs a new {@link DuplicateResourceDetector} */
- public DuplicateResourceDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- @Nullable
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_NAME);
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- File parent = context.file.getParentFile();
- if (!parent.equals(mParent)) {
- mParent = parent;
- mTypeMap = Maps.newEnumMap(ResourceType.class);
- mLocations = Maps.newEnumMap(ResourceType.class);
- }
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- Element element = attribute.getOwnerElement();
-
- if (element.hasAttribute(PRODUCT)) {
- return;
- }
-
- String tag = element.getTagName();
- String typeString = tag;
- if (tag.equals(TAG_ITEM)) {
- typeString = element.getAttribute(ATTR_TYPE);
- if (typeString == null || typeString.isEmpty()) {
- if (element.getParentNode().getNodeName().equals(
- ResourceType.STYLE.getName()) && isFirstElementChild(element)) {
- checkUniqueNames(context, (Element) element.getParentNode());
- }
- return;
- }
- }
- ResourceType type = ResourceType.getEnum(typeString);
- if (type == null) {
- return;
- }
-
- if (type == ResourceType.ATTR
- && element.getParentNode().getNodeName().equals(
- ResourceType.DECLARE_STYLEABLE.getName())) {
- if (isFirstElementChild(element)) {
- checkUniqueNames(context, (Element) element.getParentNode());
- }
- return;
- }
-
- NodeList children = element.getChildNodes();
- int childCount = children.getLength();
- for (int i = 0; i < childCount; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = child.getNodeValue();
- for (int j = 0, length = text.length(); j < length; j++) {
- char c = text.charAt(j);
- if (c == '@') {
- if (!text.regionMatches(false, j + 1, typeString, 0,
- typeString.length()) && context.isEnabled(TYPE_MISMATCH)) {
- ResourceUrl url = ResourceUrl.parse(text.trim());
- if (url != null && url.type != type &&
- // colors and mipmaps can apparently be used as drawables
- !(type == ResourceType.DRAWABLE
- && (url.type == ResourceType.COLOR
- || url.type == ResourceType.MIPMAP))) {
- String message = "Unexpected resource reference type; "
- + "expected value of type `@" + type + "/`";
- context.report(TYPE_MISMATCH, element,
- context.getLocation(child),
- message);
- }
- }
- break;
- } else if (!Character.isWhitespace(c)) {
- break;
- }
- }
- break;
- }
- }
-
- Set<String> names = mTypeMap.get(type);
- if (names == null) {
- names = Sets.newHashSetWithExpectedSize(40);
- mTypeMap.put(type, names);
- }
-
- String name = attribute.getValue();
- String originalName = name;
- // AAPT will flatten the namespace, turning dots, dashes and colons into _
- name = getResourceFieldName(name);
-
- if (names.contains(name)) {
- String message = String.format("`%1$s` has already been defined in this folder", name);
- if (!name.equals(originalName)) {
- message += " (`" + name + "` is equivalent to `" + originalName + "`)";
- }
- Location location = context.getLocation(attribute);
- List<Pair<String, Handle>> list = mLocations.get(type);
- for (Pair<String, Handle> pair : list) {
- if (name.equals(pair.getFirst())) {
- Location secondary = pair.getSecond().resolve();
- secondary.setMessage("Previously defined here");
- location.setSecondary(secondary);
- }
- }
- context.report(ISSUE, attribute, location, message);
- } else {
- names.add(name);
- List<Pair<String, Handle>> list = mLocations.get(type);
- if (list == null) {
- list = Lists.newArrayList();
- mLocations.put(type, list);
- }
- Location.Handle handle = context.createLocationHandle(attribute);
- list.add(Pair.of(name, handle));
- }
- }
-
- private static void checkUniqueNames(XmlContext context, Element parent) {
- List<Element> items = LintUtils.getChildren(parent);
- if (items.size() > 1) {
- Set<String> names = Sets.newHashSet();
- for (Element item : items) {
- Attr nameNode = item.getAttributeNode(ATTR_NAME);
- if (nameNode != null) {
- String name = nameNode.getValue();
- if (names.contains(name) && context.isEnabled(ISSUE)) {
- Location location = context.getLocation(nameNode);
- for (Element prevItem : items) {
- Attr attribute = item.getAttributeNode(ATTR_NAME);
- if (attribute != null && name.equals(attribute.getValue())) {
- assert prevItem != item;
- Location prev = context.getLocation(prevItem);
- prev.setMessage("Previously defined here");
- location.setSecondary(prev);
- break;
- }
- }
- String message = String.format(
- "`%1$s` has already been defined in this `<%2$s>`",
- name, parent.getTagName());
- context.report(ISSUE, nameNode, location, message);
- }
- names.add(name);
- }
- }
- }
- }
-
- private static boolean isFirstElementChild(Node node) {
- node = node.getPreviousSibling();
- while (node != null) {
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- return false;
- }
- node = node.getPreviousSibling();
- }
-
- return true;
- }
-
- /**
- * Returns the resource type expected for a {@link #TYPE_MISMATCH} error reported by
- * this lint detector. Intended for IDE quickfix implementations.
- *
- * @param message the error message created by this lint detector
- * @param format the format of the error message
- */
- public static String getExpectedType(@NonNull String message, @NonNull TextFormat format) {
- return LintUtils.findSubstring(format.toText(message), "value of type @", "/");
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
deleted file mode 100644
index 40e0a73..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CLASS_FRAGMENT;
-import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.List;
-
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.TypeMember;
-
-/**
- * Checks that Fragment subclasses can be instantiated via
- * {link {@link Class#newInstance()}}: the class is public, static, and has
- * a public null constructor.
- * <p>
- * This helps track down issues like
- * http://stackoverflow.com/questions/8058809/fragment-activity-crashes-on-screen-rotate
- * (and countless duplicates)
- */
-public class FragmentDetector extends Detector implements JavaScanner {
- /** Are fragment subclasses instantiatable? */
- public static final Issue ISSUE = Issue.create(
- "ValidFragment", //$NON-NLS-1$
- "Fragment not instantiatable",
-
- "From the Fragment documentation:\n" +
- "*Every* fragment must have an empty constructor, so it can be instantiated when " +
- "restoring its activity's state. It is strongly recommended that subclasses do not " +
- "have other constructors with parameters, since these constructors will not be " +
- "called when the fragment is re-instantiated; instead, arguments can be supplied " +
- "by the caller with `setArguments(Bundle)` and later retrieved by the Fragment " +
- "with `getArguments()`.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- new Implementation(
- FragmentDetector.class,
- Scope.JAVA_FILE_SCOPE)
- ).addMoreInfo(
- "http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$
-
-
- /** Constructs a new {@link FragmentDetector} */
- public FragmentDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Nullable
- @Override
- public List<String> applicableSuperClasses() {
- return Arrays.asList(CLASS_FRAGMENT, CLASS_V4_FRAGMENT);
- }
-
- @Override
- public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
- @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
- if (node == null) {
- return;
- }
-
- int flags = node.astModifiers().getEffectiveModifierFlags();
- if ((flags & Modifier.ABSTRACT) != 0) {
- return;
- }
-
- if ((flags & Modifier.PUBLIC) == 0) {
- String message = String.format("This fragment class should be public (%1$s)",
- cls.getName());
- context.report(ISSUE, node, context.getLocation(node.astName()), message);
- return;
- }
-
- if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) {
- String message = String.format(
- "This fragment inner class should be static (%1$s)", cls.getName());
- context.report(ISSUE, node, context.getLocation(node.astName()), message);
- return;
- }
-
- boolean hasDefaultConstructor = false;
- boolean hasConstructor = false;
- NormalTypeBody body = node.astBody();
- if (body != null) {
- for (TypeMember member : body.astMembers()) {
- if (member instanceof ConstructorDeclaration) {
- hasConstructor = true;
- ConstructorDeclaration constructor = (ConstructorDeclaration) member;
- if (constructor.astParameters().isEmpty()) {
- // The constructor must be public
- if (constructor.astModifiers().isPublic()) {
- hasDefaultConstructor = true;
- } else {
- Location location = context.getLocation(
- constructor.astTypeName());
- context.report(ISSUE, constructor, location,
- "The default constructor must be public");
- // Also mark that we have a constructor so we don't complain again
- // below since we've already emitted a more specific error related
- // to the default constructor
- hasDefaultConstructor = true;
- }
- } else {
- Location location = context.getLocation(constructor.astTypeName());
- // TODO: Use separate issue for this which isn't an error
- String message = "Avoid non-default constructors in fragments: "
- + "use a default constructor plus "
- + "`Fragment#setArguments(Bundle)` instead";
- context.report(ISSUE, constructor, location, message);
- }
- }
- }
- }
-
- if (!hasDefaultConstructor && hasConstructor) {
- String message = String.format(
- "This fragment should provide a default constructor (a public " +
- "constructor with no arguments) (`%1$s`)",
- cls.getName());
- context.report(ISSUE, node, context.getLocation(node.astName()), message);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java
deleted file mode 100644
index 3b05589..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Check which makes sure that a full-backup-content descriptor file is valid and logical
- */
-public class FullBackupContentDetector extends ResourceXmlDetector implements JavaScanner {
- /**
- * Validation of {@code <full-backup-content>} XML elements
- */
- public static final Issue ISSUE = Issue.create(
- "FullBackupContent", //$NON-NLS-1$
- "Valid Full Backup Content File",
-
- "Ensures that a `<full-backup-content>` file, which is pointed to by a " +
- "`android:fullBackupContent attribute` in the manifest file, is valid",
-
- Category.CORRECTNESS,
- 5,
- Severity.FATAL,
- new Implementation(
- FullBackupContentDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
- @SuppressWarnings("SpellCheckingInspection")
- private static final String DOMAIN_SHARED_PREF = "sharedpref";
- private static final String DOMAIN_ROOT = "root";
- private static final String DOMAIN_FILE = "file";
- private static final String DOMAIN_DATABASE = "database";
- private static final String DOMAIN_EXTERNAL = "external";
- private static final String TAG_EXCLUDE = "exclude";
- private static final String TAG_INCLUDE = "include";
- private static final String TAG_FULL_BACKUP_CONTENT = "full-backup-content";
- private static final String ATTR_PATH = "path";
- private static final String ATTR_DOMAIN = "domain";
-
- /**
- * Constructs a new {@link FullBackupContentDetector}
- */
- public FullBackupContentDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.XML;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- Element root = document.getDocumentElement();
- if (root == null) {
- return;
- }
- if (!TAG_FULL_BACKUP_CONTENT.equals(root.getTagName())) {
- return;
- }
-
- List<Element> includes = Lists.newArrayList();
- List<Element> excludes = Lists.newArrayList();
- NodeList children = root.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- Element element = (Element) child;
- String tag = element.getTagName();
- if (TAG_INCLUDE.equals(tag)) {
- includes.add(element);
- } else if (TAG_EXCLUDE.equals(tag)) {
- excludes.add(element);
- } else {
- // See FullBackup#validateInnerTagContents
- context.report(ISSUE, element, context.getNameLocation(element),
- String.format("Unexpected element `<%1$s>`", tag));
- }
- }
- }
-
- Multimap<String, String> includePaths = ArrayListMultimap.create(includes.size(), 4);
- for (Element include : includes) {
- String domain = validateDomain(context, include);
- String path = validatePath(context, include);
- if (domain == null) {
- continue;
- }
- includePaths.put(domain, path);
- }
-
- for (Element exclude : excludes) {
- String excludePath = validatePath(context, exclude);
- if (excludePath.isEmpty()) {
- continue;
- }
- String domain = validateDomain(context, exclude);
- if (domain == null) {
- continue;
- }
- if (includePaths.isEmpty()) {
- // There is no <include> anywhere: that means that everything
- // is considered included and there's no potential prefix mismatch
- continue;
- }
-
- boolean hasPrefix = false;
- Collection<String> included = includePaths.get(domain);
- if (included == null) {
- continue;
- }
- for (String includePath : included) {
- if (excludePath.startsWith(includePath)) {
- if (excludePath.equals(includePath)) {
- Attr pathNode = exclude.getAttributeNode(ATTR_PATH);
- assert pathNode != null;
- Location location = context.getValueLocation(pathNode);
- // Find corresponding include path so we can link to it in the
- // chained location list
- for (Element include : includes) {
- Attr includePathNode = include.getAttributeNode(ATTR_PATH);
- String includeDomain = include.getAttribute(ATTR_DOMAIN);
- if (includePathNode != null
- && excludePath.equals(includePathNode.getValue())
- && domain.equals(includeDomain)) {
- Location earlier = context.getLocation(includePathNode);
- earlier.setMessage("Unnecessary/conflicting <include>");
- location.setSecondary(earlier);
- }
- }
- context.report(ISSUE, exclude, location,
- String.format("Include `%1$s` is also excluded", excludePath));
- }
- hasPrefix = true;
- break;
- }
- }
- if (!hasPrefix) {
- Attr pathNode = exclude.getAttributeNode(ATTR_PATH);
- assert pathNode != null;
- context.report(ISSUE, exclude, context.getValueLocation(pathNode),
- String.format("`%1$s` is not in an included path", excludePath));
- }
- }
- }
-
- @NonNull
- private static String validatePath(@NonNull XmlContext context, @NonNull Element element) {
- Attr pathNode = element.getAttributeNode(ATTR_PATH);
- if (pathNode == null) {
- return "";
- }
- String value = pathNode.getValue();
- if (value.contains("//")) {
- context.report(ISSUE, element, context.getValueLocation(pathNode),
- "Paths are not allowed to contain `//`");
- } else if (value.contains("..")) {
- context.report(ISSUE, element, context.getValueLocation(pathNode),
- "Paths are not allowed to contain `..`");
- } else if (value.contains("/")) {
- String domain = element.getAttribute(ATTR_DOMAIN);
- if (DOMAIN_SHARED_PREF.equals(domain) || DOMAIN_DATABASE.equals(domain)) {
- context.report(ISSUE, element, context.getValueLocation(pathNode),
- String.format("Subdirectories are not allowed for domain `%1$s`",
- domain));
- }
- }
- return value;
- }
-
- @Nullable
- private static String validateDomain(@NonNull XmlContext context, @NonNull Element element) {
- Attr domainNode = element.getAttributeNode(ATTR_DOMAIN);
- if (domainNode == null) {
- context.report(ISSUE, element, context.getLocation(element),
- String.format("Missing domain attribute, expected one of %1$s",
- Joiner.on(", ").join(VALID_DOMAINS)));
- return null;
- }
- String domain = domainNode.getValue();
- for (String availableDomain : VALID_DOMAINS) {
- if (availableDomain.equals(domain)) {
- return domain;
- }
- }
- context.report(ISSUE, element, context.getValueLocation(domainNode),
- String.format("Unexpected domain `%1$s`, expected one of %2$s", domain,
- Joiner.on(", ").join(VALID_DOMAINS)));
-
- return domain;
- }
-
- /** Valid domains; see FullBackup#getTokenForXmlDomain for authoritative list */
- private static final String[] VALID_DOMAINS = new String[] {
- DOMAIN_ROOT, DOMAIN_FILE, DOMAIN_DATABASE, DOMAIN_SHARED_PREF, DOMAIN_EXTERNAL
- };
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
deleted file mode 100644
index 33e49e6..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
+++ /dev/null
@@ -1,1175 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.FD_BUILD_TOOLS;
-import static com.android.SdkConstants.GRADLE_PLUGIN_MINIMUM_VERSION;
-import static com.android.SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION;
-import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
-import static com.android.tools.lint.checks.ManifestDetector.TARGET_NEWER;
-import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
-import static com.google.common.base.Charsets.UTF_8;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.MavenCoordinates;
-import com.android.builder.model.Variant;
-import com.android.ide.common.repository.GradleCoordinate;
-import com.android.ide.common.repository.GradleCoordinate.RevisionComponent;
-import com.android.ide.common.repository.SdkMavenRepository;
-import com.android.sdklib.repository.PreciseRevision;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLEncoder;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Checks Gradle files for potential errors
- */
-public class GradleDetector extends Detector implements Detector.GradleScanner {
-
- private static final Implementation IMPLEMENTATION = new Implementation(
- GradleDetector.class,
- Scope.GRADLE_SCOPE);
-
- /** Obsolete dependencies */
- public static final Issue DEPENDENCY = Issue.create(
- "GradleDependency", //$NON-NLS-1$
- "Obsolete Gradle Dependency",
- "This detector looks for usages of libraries where the version you are using " +
- "is not the current stable release. Using older versions is fine, and there are " +
- "cases where you deliberately want to stick with an older version. However, " +
- "you may simply not be aware that a more recent version is available, and that is " +
- "what this lint check helps find.",
- Category.CORRECTNESS,
- 4,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Deprecated Gradle constructs */
- public static final Issue DEPRECATED = Issue.create(
- "GradleDeprecated", //$NON-NLS-1$
- "Deprecated Gradle Construct",
- "This detector looks for deprecated Gradle constructs which currently work but " +
- "will likely stop working in a future update.",
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Incompatible Android Gradle plugin */
- public static final Issue GRADLE_PLUGIN_COMPATIBILITY = Issue.create(
- "AndroidGradlePluginVersion", //$NON-NLS-1$
- "Incompatible Android Gradle Plugin",
- "Not all versions of the Android Gradle plugin are compatible with all versions " +
- "of the SDK. If you update your tools, or if you are trying to open a project that " +
- "was built with an old version of the tools, you may need to update your plugin " +
- "version number.",
- Category.CORRECTNESS,
- 8,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Invalid or dangerous paths */
- public static final Issue PATH = Issue.create(
- "GradlePath", //$NON-NLS-1$
- "Gradle Path Issues",
- "Gradle build scripts are meant to be cross platform, so file paths use " +
- "Unix-style path separators (a forward slash) rather than Windows path separators " +
- "(a backslash). Similarly, to keep projects portable and repeatable, avoid " +
- "using absolute paths on the system; keep files within the project instead. To " +
- "share code between projects, consider creating an android-library and an AAR " +
- "dependency",
- Category.CORRECTNESS,
- 4,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Constructs the IDE support struggles with */
- public static final Issue IDE_SUPPORT = Issue.create(
- "GradleIdeError", //$NON-NLS-1$
- "Gradle IDE Support Issues",
- "Gradle is highly flexible, and there are things you can do in Gradle files which " +
- "can make it hard or impossible for IDEs to properly handle the project. This lint " +
- "check looks for constructs that potentially break IDE support.",
- Category.CORRECTNESS,
- 4,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Using + in versions */
- public static final Issue PLUS = Issue.create(
- "GradleDynamicVersion", //$NON-NLS-1$
- "Gradle Dynamic Version",
- "Using `+` in dependencies lets you automatically pick up the latest available " +
- "version rather than a specific, named version. However, this is not recommended; " +
- "your builds are not repeatable; you may have tested with a slightly different " +
- "version than what the build server used. (Using a dynamic version as the major " +
- "version number is more problematic than using it in the minor version position.)",
- Category.CORRECTNESS,
- 4,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Accidentally calling a getter instead of your own methods */
- public static final Issue GRADLE_GETTER = Issue.create(
- "GradleGetter", //$NON-NLS-1$
- "Gradle Implicit Getter Call",
- "Gradle will let you replace specific constants in your build scripts with method " +
- "calls, so you can for example dynamically compute a version string based on your " +
- "current version control revision number, rather than hardcoding a number.\n" +
- "\n" +
- "When computing a version name, it's tempting to for example call the method to do " +
- "that `getVersionName`. However, when you put that method call inside the " +
- "`defaultConfig` block, you will actually be calling the Groovy getter for the " +
- "`versionName` property instead. Therefore, you need to name your method something " +
- "which does not conflict with the existing implicit getters. Consider using " +
- "`compute` as a prefix instead of `get`.",
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Using incompatible versions */
- public static final Issue COMPATIBILITY = Issue.create(
- "GradleCompatible", //$NON-NLS-1$
- "Incompatible Gradle Versions",
-
- "There are some combinations of libraries, or tools and libraries, that are " +
- "incompatible, or can lead to bugs. One such incompatibility is compiling with " +
- "a version of the Android support libraries that is not the latest version (or in " +
- "particular, a version lower than your `targetSdkVersion`.)",
-
- Category.CORRECTNESS,
- 8,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Using a string where an integer is expected */
- public static final Issue STRING_INTEGER = Issue.create(
- "StringShouldBeInt", //$NON-NLS-1$
- "String should be int",
-
- "The properties `compileSdkVersion`, `minSdkVersion` and `targetSdkVersion` are " +
- "usually numbers, but can be strings when you are using an add-on (in the case " +
- "of `compileSdkVersion`) or a preview platform (for the other two properties).\n" +
- "\n" +
- "However, you can not use a number as a string (e.g. \"19\" instead of 19); that " +
- "will result in a platform not found error message at build/sync time.",
-
- Category.CORRECTNESS,
- 8,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** A newer version is available on a remote server */
- public static final Issue REMOTE_VERSION = Issue.create(
- "NewerVersionAvailable", //$NON-NLS-1$
- "Newer Library Versions Available",
- "This detector checks with a central repository to see if there are newer versions " +
- "available for the dependencies used by this project. " +
- "This is similar to the `GradleDependency` check, which checks for newer versions " +
- "available in the Android SDK tools and libraries, but this works with any " +
- "MavenCentral dependency, and connects to the library every time, which makes " +
- "it more flexible but also *much* slower.",
- Category.CORRECTNESS,
- 4,
- Severity.WARNING,
- IMPLEMENTATION).setEnabledByDefault(false);
-
- /** Accidentally using octal numbers */
- public static final Issue ACCIDENTAL_OCTAL = Issue.create(
- "AccidentalOctal", //$NON-NLS-1$
- "Accidental Octal",
-
- "In Groovy, an integer literal that starts with a leading 0 will be interpreted " +
- "as an octal number. That is usually (always?) an accident and can lead to " +
- "subtle bugs, for example when used in the `versionCode` of an app.",
-
- Category.CORRECTNESS,
- 2,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** The Gradle plugin ID for Android applications */
- public static final String APP_PLUGIN_ID = "com.android.application";
- /** The Gradle plugin ID for Android libraries */
- public static final String LIB_PLUGIN_ID = "com.android.library";
-
- /** Previous plugin id for applications */
- public static final String OLD_APP_PLUGIN_ID = "android";
- /** Previous plugin id for libraries */
- public static final String OLD_LIB_PLUGIN_ID = "android-library";
-
- private int mMinSdkVersion;
- private int mCompileSdkVersion;
- private int mTargetSdkVersion;
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @Override
- @NonNull
- public Speed getSpeed(@SuppressWarnings("UnusedParameters") @NonNull Issue issue) {
- return issue == REMOTE_VERSION ? Speed.REALLY_SLOW : Speed.NORMAL;
- }
-
- // ---- Implements Detector.GradleScanner ----
-
- @Override
- public void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData) {
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected static boolean isInterestingBlock(
- @NonNull String parent,
- @Nullable String parentParent) {
- return parent.equals("defaultConfig")
- || parent.equals("android")
- || parent.equals("dependencies")
- || parent.equals("repositories")
- || parentParent != null && parentParent.equals("buildTypes");
- }
-
- protected static boolean isInterestingStatement(
- @NonNull String statement,
- @Nullable String parent) {
- return parent == null && statement.equals("apply");
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected static boolean isInterestingProperty(
- @NonNull String property,
- @SuppressWarnings("UnusedParameters")
- @NonNull String parent,
- @Nullable String parentParent) {
- return property.equals("targetSdkVersion")
- || property.equals("buildToolsVersion")
- || property.equals("versionName")
- || property.equals("versionCode")
- || property.equals("compileSdkVersion")
- || property.equals("minSdkVersion")
- || property.equals("applicationIdSuffix")
- || property.equals("packageName")
- || property.equals("packageNameSuffix")
- || parent.equals("dependencies");
- }
-
- protected void checkOctal(
- @NonNull Context context,
- @NonNull String value,
- @NonNull Object cookie) {
- if (value.length() >= 2
- && value.charAt(0) == '0'
- && (value.length() > 2 || value.charAt(1) >= '8'
- && isInteger(value))
- && context.isEnabled(ACCIDENTAL_OCTAL)) {
- String message = "The leading 0 turns this number into octal which is probably "
- + "not what was intended";
- try {
- long numericValue = Long.decode(value);
- message += " (interpreted as " + numericValue + ")";
- } catch (NumberFormatException nufe) {
- message += " (and it is not a valid octal number)";
- }
- report(context, cookie, ACCIDENTAL_OCTAL, message);
- }
- }
-
- /** Called with for example "android", "defaultConfig", "minSdkVersion", "7" */
- @SuppressWarnings("UnusedDeclaration")
- protected void checkDslPropertyAssignment(
- @NonNull Context context,
- @NonNull String property,
- @NonNull String value,
- @NonNull String parent,
- @Nullable String parentParent,
- @NonNull Object valueCookie,
- @NonNull Object statementCookie) {
- if (parent.equals("defaultConfig")) {
- if (property.equals("targetSdkVersion")) {
- int version = getIntLiteralValue(value, -1);
- if (version > 0 && version < context.getClient().getHighestKnownApiLevel()) {
- String message =
- "Not targeting the latest versions of Android; compatibility " +
- "modes apply. Consider testing and updating this version. " +
- "Consult the android.os.Build.VERSION_CODES javadoc for details.";
- report(context, valueCookie, TARGET_NEWER, message);
- }
- if (version > 0) {
- mTargetSdkVersion = version;
- checkTargetCompatibility(context, valueCookie);
- } else {
- checkIntegerAsString(context, value, valueCookie);
- }
- } else if (property.equals("minSdkVersion")) {
- int version = getIntLiteralValue(value, -1);
- if (version > 0) {
- mMinSdkVersion = version;
- } else {
- checkIntegerAsString(context, value, valueCookie);
- }
- }
-
- if (value.startsWith("0")) {
- checkOctal(context, value, valueCookie);
- }
-
- if (property.equals("versionName") || property.equals("versionCode") &&
- !isInteger(value) || !isStringLiteral(value)) {
- // Method call -- make sure it does not match one of the getters in the
- // configuration!
- if ((value.equals("getVersionCode") ||
- value.equals("getVersionName"))) {
- String message = "Bad method name: pick a unique method name which does not "
- + "conflict with the implicit getters for the defaultConfig "
- + "properties. For example, try using the prefix compute- "
- + "instead of get-.";
- report(context, valueCookie, GRADLE_GETTER, message);
- }
- } else if (property.equals("packageName")) {
- if (isModelOlderThan011(context)) {
- return;
- }
- String message = "Deprecated: Replace 'packageName' with 'applicationId'";
- report(context, getPropertyKeyCookie(valueCookie), DEPRECATED, message);
- }
- } else if (property.equals("compileSdkVersion") && parent.equals("android")) {
- int version = getIntLiteralValue(value, -1);
- if (version > 0) {
- mCompileSdkVersion = version;
- checkTargetCompatibility(context, valueCookie);
- } else {
- checkIntegerAsString(context, value, valueCookie);
- }
- } else if (property.equals("buildToolsVersion") && parent.equals("android")) {
- String versionString = getStringLiteralValue(value);
- if (versionString != null) {
- PreciseRevision version = parseRevisionSilently(versionString);
- if (version != null) {
- PreciseRevision recommended = getLatestBuildTools(context.getClient(),
- version.getMajor());
- if (recommended != null && version.compareTo(recommended) < 0) {
- // Keep in sync with {@link #getOldValue} and {@link #getNewValue}
- String message = "Old buildToolsVersion " + version +
- "; recommended version is " + recommended + " or later";
- report(context, valueCookie, DEPENDENCY, message);
- }
- }
- }
- } else if (parent.equals("dependencies")) {
- if (value.startsWith("files('") && value.endsWith("')")) {
- String path = value.substring("files('".length(), value.length() - 2);
- if (path.contains("\\\\")) {
- String message = "Do not use Windows file separators in .gradle files; "
- + "use / instead";
- report(context, valueCookie, PATH, message);
-
- } else if (new File(path.replace('/', File.separatorChar)).isAbsolute()) {
- String message = "Avoid using absolute paths in .gradle files";
- report(context, valueCookie, PATH, message);
- }
- } else {
- String dependency = getStringLiteralValue(value);
- if (dependency == null) {
- dependency = getNamedDependency(value);
- }
- // If the dependency is a GString (i.e. it uses Groovy variable substitution,
- // with a $variable_name syntax) then don't try to parse it.
- if (dependency != null) {
- GradleCoordinate gc = GradleCoordinate.parseCoordinateString(dependency);
- if (gc != null && dependency.contains("$")) {
- gc = resolveCoordinate(context, gc);
- }
- if (gc != null) {
- if (gc.acceptsGreaterRevisions()) {
- String message = "Avoid using + in version numbers; can lead "
- + "to unpredictable and unrepeatable builds (" + dependency + ")";
- report(context, valueCookie, PLUS, message);
- }
- if (!dependency.startsWith(SdkConstants.GRADLE_PLUGIN_NAME) ||
- !checkGradlePluginDependency(context, gc, valueCookie)) {
- checkDependency(context, gc, valueCookie);
- }
- }
- }
- }
- } else if (property.equals("packageNameSuffix")) {
- if (isModelOlderThan011(context)) {
- return;
- }
- String message = "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'";
- report(context, getPropertyKeyCookie(valueCookie), DEPRECATED, message);
- } else if (property.equals("applicationIdSuffix")) {
- String suffix = getStringLiteralValue(value);
- if (suffix != null && !suffix.startsWith(".")) {
- String message = "Package suffix should probably start with a \".\"";
- report(context, valueCookie, PATH, message);
- }
- }
- }
-
- @Nullable
- private static GradleCoordinate resolveCoordinate(@NonNull Context context,
- @NonNull GradleCoordinate gc) {
- assert gc.getFullRevision().contains("$") : gc.getFullRevision();
- Variant variant = context.getProject().getCurrentVariant();
- if (variant != null) {
- Dependencies dependencies = variant.getMainArtifact().getDependencies();
- for (AndroidLibrary library : dependencies.getLibraries()) {
- MavenCoordinates mc = library.getResolvedCoordinates();
- if (mc != null
- && mc.getGroupId().equals(gc.getGroupId())
- && mc.getArtifactId().equals(gc.getArtifactId())) {
- List<RevisionComponent> revisions =
- GradleCoordinate.parseRevisionNumber(mc.getVersion());
- if (!revisions.isEmpty()) {
- return new GradleCoordinate(mc.getGroupId(), mc.getArtifactId(),
- revisions, null);
- }
- break;
- }
- }
- }
-
- return null;
- }
-
- // Convert a long-hand dependency, like
- // group: 'com.android.support', name: 'support-v4', version: '21.0.+'
- // into an equivalent short-hand dependency, like
- // com.android.support:support-v4:21.0.+
- @VisibleForTesting
- @Nullable
- static String getNamedDependency(@NonNull String expression) {
- //if (value.startsWith("group: 'com.android.support', name: 'support-v4', version: '21.0.+'"))
- if (expression.indexOf(',') != -1 && expression.contains("version:")) {
- String artifact = null;
- String group = null;
- String version = null;
- Splitter splitter = Splitter.on(',').omitEmptyStrings().trimResults();
- for (String property : splitter.split(expression)) {
- int colon = property.indexOf(':');
- if (colon == -1) {
- return null;
- }
- char quote = '\'';
- int valueStart = property.indexOf(quote, colon + 1);
- if (valueStart == -1) {
- quote = '"';
- valueStart = property.indexOf(quote, colon + 1);
- }
- if (valueStart == -1) {
- // For example, "transitive: false"
- continue;
- }
- valueStart++;
- int valueEnd = property.indexOf(quote, valueStart);
- if (valueEnd == -1) {
- return null;
- }
- String value = property.substring(valueStart, valueEnd);
- if (property.startsWith("group:")) {
- group = value;
- } else if (property.startsWith("name:")) {
- artifact = value;
- } else if (property.startsWith("version:")) {
- version = value;
- }
- }
-
- if (artifact != null && group != null && version != null) {
- return group + ':' + artifact + ':' + version;
- }
- }
-
- return null;
- }
-
- private void checkIntegerAsString(Context context, String value, Object valueCookie) {
- // When done developing with a preview platform you might be tempted to switch from
- // compileSdkVersion 'android-G'
- // to
- // compileSdkVersion '19'
- // but that won't work; it needs to be
- // compileSdkVersion 19
- String string = getStringLiteralValue(value);
- if (isNumberString(string)) {
- String quote = Character.toString(value.charAt(0));
- String message = String.format("Use an integer rather than a string here "
- + "(replace %1$s%2$s%1$s with just %2$s)", quote, string);
- report(context, valueCookie, STRING_INTEGER, message);
- }
- }
-
- /**
- * Given an error message produced by this lint detector for the given issue type,
- * returns the old value to be replaced in the source code.
- * <p>
- * Intended for IDE quickfix implementations.
- *
- * @param issue the corresponding issue
- * @param errorMessage the error message associated with the error
- * @param format the format of the error message
- * @return the corresponding old value, or null if not recognized
- */
- @Nullable
- public static String getOldValue(@NonNull Issue issue, @NonNull String errorMessage,
- @NonNull TextFormat format) {
- errorMessage = format.toText(errorMessage);
-
- // Consider extracting all the error strings as constants and handling this
- // using the LintUtils#getFormattedParameters() method to pull back out the information
- if (issue == DEPENDENCY) {
- // "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0"
- if (errorMessage.startsWith("A newer ")) {
- return findSubstring(errorMessage, " than ", " ");
- }
- if (errorMessage.startsWith("Old buildToolsVersion ")) {
- return findSubstring(errorMessage, "Old buildToolsVersion ", ";");
- }
- // "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)"
- return findSubstring(errorMessage, "targetSdkVersion (", ")");
- } else if (issue == STRING_INTEGER) {
- return findSubstring(errorMessage, "replace ", " with ");
- } else if (issue == DEPRECATED) {
- if (errorMessage.contains(GradleDetector.APP_PLUGIN_ID) &&
- errorMessage.contains(GradleDetector.OLD_APP_PLUGIN_ID)) {
- return GradleDetector.OLD_APP_PLUGIN_ID;
- } else if (errorMessage.contains(GradleDetector.LIB_PLUGIN_ID) &&
- errorMessage.contains(GradleDetector.OLD_LIB_PLUGIN_ID)) {
- return GradleDetector.OLD_LIB_PLUGIN_ID;
- }
- // "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'"
- return findSubstring(errorMessage, "Replace '", "'");
- } else if (issue == PLUS) {
- return findSubstring(errorMessage, "(", ")");
- } else if (issue == COMPATIBILITY) {
- if (errorMessage.startsWith("Version 5.2.08")) {
- return "5.2.08";
- }
- }
-
- return null;
- }
-
- /**
- * Given an error message produced by this lint detector for the given issue type,
- * returns the new value to be put into the source code.
- * <p>
- * Intended for IDE quickfix implementations.
- *
- * @param issue the corresponding issue
- * @param errorMessage the error message associated with the error
- * @param format the format of the error message
- * @return the corresponding new value, or null if not recognized
- */
- @Nullable
- public static String getNewValue(@NonNull Issue issue, @NonNull String errorMessage,
- @NonNull TextFormat format) {
- errorMessage = format.toText(errorMessage);
-
- if (issue == DEPENDENCY) {
- // "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0"
- if (errorMessage.startsWith("A newer ")) {
- return findSubstring(errorMessage, " is available: ", null);
- }
- if (errorMessage.startsWith("Old buildToolsVersion ")) {
- return findSubstring(errorMessage, " version is ", " ");
- }
- // "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)"
- return findSubstring(errorMessage, "compileSdkVersion (", ")");
- } else if (issue == STRING_INTEGER) {
- return findSubstring(errorMessage, " just ", ")");
- } else if (issue == DEPRECATED) {
- if (errorMessage.contains(GradleDetector.APP_PLUGIN_ID) &&
- errorMessage.contains(GradleDetector.OLD_APP_PLUGIN_ID)) {
- return GradleDetector.APP_PLUGIN_ID;
- } else if (errorMessage.contains(GradleDetector.LIB_PLUGIN_ID) &&
- errorMessage.contains(GradleDetector.OLD_LIB_PLUGIN_ID)) {
- return GradleDetector.LIB_PLUGIN_ID;
- }
- // "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'"
- return findSubstring(errorMessage, " with '", "'");
- } else if (issue == COMPATIBILITY) {
- if (errorMessage.startsWith("Version 5.2.08")) {
- return findSubstring(errorMessage, "Use version ", " ");
- }
- }
-
- return null;
- }
-
- private static boolean isNumberString(@Nullable String s) {
- if (s == null || s.isEmpty()) {
- return false;
- }
- for (int i = 0, n = s.length(); i < n; i++) {
- if (!Character.isDigit(s.charAt(i))) {
- return false;
- }
- }
-
- return true;
- }
-
- protected void checkMethodCall(
- @NonNull Context context,
- @NonNull String statement,
- @Nullable String parent,
- @NonNull Map<String, String> namedArguments,
- @SuppressWarnings("UnusedParameters")
- @NonNull List<String> unnamedArguments,
- @NonNull Object cookie) {
- String plugin = namedArguments.get("plugin");
- if (statement.equals("apply") && parent == null) {
- boolean isOldAppPlugin = OLD_APP_PLUGIN_ID.equals(plugin);
- if (isOldAppPlugin || OLD_LIB_PLUGIN_ID.equals(plugin)) {
- String replaceWith = isOldAppPlugin ? APP_PLUGIN_ID : LIB_PLUGIN_ID;
- String message = String.format("'%1$s' is deprecated; use '%2$s' instead", plugin,
- replaceWith);
- report(context, cookie, DEPRECATED, message);
- }
- }
- }
-
- @Nullable
- private static PreciseRevision parseRevisionSilently(String versionString) {
- try {
- return PreciseRevision.parseRevision(versionString);
- } catch (Throwable t) {
- return null;
- }
- }
-
- private static boolean isModelOlderThan011(@NonNull Context context) {
- return LintUtils.isModelOlderThan(context.getProject().getGradleProjectModel(), 0, 11, 0);
- }
-
- private static int sMajorBuildTools;
- private static PreciseRevision sLatestBuildTools;
-
- /** Returns the latest build tools installed for the given major version.
- * We just cache this once; we don't need to be accurate in the sense that if the
- * user opens the SDK manager and installs a more recent version, we capture this in
- * the same IDE session.
- *
- * @param client the associated client
- * @param major the major version of build tools to look up (e.g. typically 18, 19, ...)
- * @return the corresponding highest known revision
- */
- @Nullable
- private static PreciseRevision getLatestBuildTools(@NonNull LintClient client, int major) {
- if (major != sMajorBuildTools) {
- sMajorBuildTools = major;
-
- List<PreciseRevision> revisions = Lists.newArrayList();
- if (major == 21) {
- revisions.add(new PreciseRevision(21, 1, 2));
- } else if (major == 20) {
- revisions.add(new PreciseRevision(20));
- } else if (major == 19) {
- revisions.add(new PreciseRevision(19, 1));
- } else if (major == 18) {
- revisions.add(new PreciseRevision(18, 1, 1));
- }
- // The above versions can go stale.
- // Check if a more recent one is installed. (The above are still useful for
- // people who haven't updated with the SDK manager recently.)
- File sdkHome = client.getSdkHome();
- if (sdkHome != null) {
- File[] dirs = new File(sdkHome, FD_BUILD_TOOLS).listFiles();
- if (dirs != null) {
- for (File dir : dirs) {
- String name = dir.getName();
- if (!dir.isDirectory() || !Character.isDigit(name.charAt(0))) {
- continue;
- }
- PreciseRevision v = parseRevisionSilently(name);
- if (v != null && v.getMajor() == major) {
- revisions.add(v);
- }
- }
- }
- }
-
- if (!revisions.isEmpty()) {
- sLatestBuildTools = Collections.max(revisions);
- }
- }
-
- return sLatestBuildTools;
- }
-
- private void checkTargetCompatibility(Context context, Object cookie) {
- if (mCompileSdkVersion > 0 && mTargetSdkVersion > 0
- && mTargetSdkVersion > mCompileSdkVersion) {
- // NOTE: Keep this in sync with {@link #getOldValue} and {@link #getNewValue}
- String message = "The targetSdkVersion (" + mTargetSdkVersion
- + ") should not be higher than the compileSdkVersion ("
- + mCompileSdkVersion + ")";
- report(context, cookie, DEPENDENCY, message);
- }
- }
-
- @Nullable
- private static String getStringLiteralValue(@NonNull String value) {
- if (value.length() > 2 && (value.startsWith("'") && value.endsWith("'") ||
- value.startsWith("\"") && value.endsWith("\""))) {
- return value.substring(1, value.length() - 1);
- }
-
- return null;
- }
-
- private static int getIntLiteralValue(@NonNull String value, int defaultValue) {
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- private static boolean isInteger(String token) {
- return token.matches("\\d+");
- }
-
- private static boolean isStringLiteral(String token) {
- return token.startsWith("\"") && token.endsWith("\"") ||
- token.startsWith("'") && token.endsWith("'");
- }
-
- private void checkDependency(
- @NonNull Context context,
- @NonNull GradleCoordinate dependency,
- @NonNull Object cookie) {
- if ("com.android.support".equals(dependency.getGroupId())) {
- checkSupportLibraries(context, dependency, cookie);
- if (mMinSdkVersion >= 14 && "appcompat-v7".equals(dependency.getArtifactId())
- && mCompileSdkVersion >= 1 && mCompileSdkVersion < 21) {
- report(context, cookie, DEPENDENCY,
- "Using the appcompat library when minSdkVersion >= 14 and "
- + "compileSdkVersion < 21 is not necessary");
- }
- return;
- } else if ("com.google.android.gms".equals(dependency.getGroupId())
- && dependency.getArtifactId() != null) {
-
- // 5.2.08 is not supported; special case and warn about this
- if ("5.2.08".equals(dependency.getFullRevision()) && context.isEnabled(COMPATIBILITY)) {
- // This specific version is actually a preview version which should
- // not be used (https://code.google.com/p/android/issues/detail?id=75292)
- String version = "6.1.11";
- // Try to find a more recent available version, if one is available
- File sdkHome = context.getClient().getSdkHome();
- File repository = SdkMavenRepository.GOOGLE.getRepositoryLocation(sdkHome, true);
- if (repository != null) {
- GradleCoordinate max = SdkMavenRepository.getHighestInstalledVersion(
- dependency.getGroupId(), dependency.getArtifactId(), repository,
- null, false);
- if (max != null) {
- if (COMPARE_PLUS_HIGHER.compare(dependency, max) < 0) {
- version = max.getFullRevision();
- }
- }
- }
- String message = String.format("Version `5.2.08` should not be used; the app "
- + "can not be published with this version. Use version `%1$s` "
- + "instead.", version);
- report(context, cookie, COMPATIBILITY, message);
- }
-
- checkPlayServices(context, dependency, cookie);
- return;
- }
-
- PreciseRevision version = null;
- Issue issue = DEPENDENCY;
- if ("com.android.tools.build".equals(dependency.getGroupId()) &&
- "gradle".equals(dependency.getArtifactId())) {
- try {
- PreciseRevision v =
- PreciseRevision.parseRevision(GRADLE_PLUGIN_RECOMMENDED_VERSION);
- if (!v.isPreview()) {
- version = getNewerRevision(dependency, v);
- }
- } catch (NumberFormatException e) {
- context.log(e, null);
- }
- } else if ("com.google.guava".equals(dependency.getGroupId()) &&
- "guava".equals(dependency.getArtifactId())) {
- version = getNewerRevision(dependency, new PreciseRevision(18, 0));
- } else if ("com.google.code.gson".equals(dependency.getGroupId()) &&
- "gson".equals(dependency.getArtifactId())) {
- version = getNewerRevision(dependency, new PreciseRevision(2, 3));
- } else if ("org.apache.httpcomponents".equals(dependency.getGroupId()) &&
- "httpclient".equals(dependency.getArtifactId())) {
- version = getNewerRevision(dependency, new PreciseRevision(4, 3, 5));
- }
-
- // Network check for really up to date libraries? Only done in batch mode
- if (context.getScope().size() > 1 && context.isEnabled(REMOTE_VERSION)) {
- PreciseRevision latest = getLatestVersionFromRemoteRepo(context.getClient(), dependency,
- dependency.isPreview());
- if (latest != null && isOlderThan(dependency, latest.getMajor(), latest.getMinor(),
- latest.getMicro())) {
- version = latest;
- issue = REMOTE_VERSION;
- }
- }
-
- if (version != null) {
- String message = getNewerVersionAvailableMessage(dependency, version);
- report(context, cookie, issue, message);
- }
- }
-
- private static String getNewerVersionAvailableMessage(GradleCoordinate dependency,
- PreciseRevision version) {
- return getNewerVersionAvailableMessage(dependency, version.toString());
- }
-
- private static String getNewerVersionAvailableMessage(GradleCoordinate dependency,
- String version) {
- // NOTE: Keep this in sync with {@link #getOldValue} and {@link #getNewValue}
- return "A newer version of " + dependency.getGroupId() + ":" +
- dependency.getArtifactId() + " than " + dependency.getFullRevision() +
- " is available: " + version;
- }
-
- /** TODO: Cache these results somewhere! */
- @Nullable
- public static PreciseRevision getLatestVersionFromRemoteRepo(@NonNull LintClient client,
- @NonNull GradleCoordinate dependency, boolean allowPreview) {
- return getLatestVersionFromRemoteRepo(client, dependency, true, allowPreview);
- }
-
- @Nullable
- private static PreciseRevision getLatestVersionFromRemoteRepo(@NonNull LintClient client,
- @NonNull GradleCoordinate dependency, boolean firstRowOnly, boolean allowPreview) {
- StringBuilder query = new StringBuilder();
- String encoding = UTF_8.name();
- try {
- query.append("http://search.maven.org/solrsearch/select?q=g:%22");
- query.append(URLEncoder.encode(dependency.getGroupId(), encoding));
- query.append("%22+AND+a:%22");
- query.append(URLEncoder.encode(dependency.getArtifactId(), encoding));
- } catch (UnsupportedEncodingException ee) {
- return null;
- }
- query.append("%22&core=gav");
- if (firstRowOnly) {
- query.append("&rows=1");
- }
- query.append("&wt=json");
-
- String response = readUrlData(client, dependency, query.toString());
- if (response == null) {
- return null;
- }
-
- // Sample response:
- // {
- // "responseHeader": {
- // "status": 0,
- // "QTime": 0,
- // "params": {
- // "fl": "id,g,a,v,p,ec,timestamp,tags",
- // "sort": "score desc,timestamp desc,g asc,a asc,v desc",
- // "indent": "off",
- // "q": "g:\"com.google.guava\" AND a:\"guava\"",
- // "core": "gav",
- // "wt": "json",
- // "rows": "1",
- // "version": "2.2"
- // }
- // },
- // "response": {
- // "numFound": 37,
- // "start": 0,
- // "docs": [{
- // "id": "com.google.guava:guava:17.0",
- // "g": "com.google.guava",
- // "a": "guava",
- // "v": "17.0",
- // "p": "bundle",
- // "timestamp": 1398199666000,
- // "tags": ["spec", "libraries", "classes", "google", "code"],
- // "ec": ["-javadoc.jar", "-sources.jar", ".jar", "-site.jar", ".pom"]
- // }]
- // }
- // }
-
- // Look for version info: This is just a cheap skim of the above JSON results
- boolean foundPreview = false;
- int index = response.indexOf("\"response\""); //$NON-NLS-1$
- while (index != -1) {
- index = response.indexOf("\"v\":", index); //$NON-NLS-1$
- if (index != -1) {
- index += 4;
- int start = response.indexOf('"', index) + 1;
- int end = response.indexOf('"', start + 1);
- if (end > start && start >= 0) {
- PreciseRevision revision = parseRevisionSilently(response.substring(start, end));
- if (revision != null) {
- foundPreview = revision.isPreview();
- if (allowPreview || !foundPreview) {
- return revision;
- }
- }
- }
- }
- }
-
- if (!allowPreview && foundPreview && firstRowOnly) {
- // Recurse: search more than the first row this time to see if we can find a
- // non-preview version
- return getLatestVersionFromRemoteRepo(client, dependency, false, false);
- }
-
- return null;
- }
-
- /** Normally null; used for testing */
- @Nullable
- @VisibleForTesting
- static Map<String,String> sMockData;
-
- @Nullable
- private static String readUrlData(
- @NonNull LintClient client,
- @NonNull GradleCoordinate dependency,
- @NonNull String query) {
- // For unit testing: avoid network as well as unexpected new versions
- if (sMockData != null) {
- String value = sMockData.get(query);
- assert value != null : query;
- return value;
- }
-
- try {
- URL url = new URL(query);
-
- URLConnection connection = client.openConnection(url);
- if (connection == null) {
- return null;
- }
- try {
- InputStream is = connection.getInputStream();
- if (is == null) {
- return null;
- }
- BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8));
- try {
- StringBuilder sb = new StringBuilder(500);
- String line;
- while ((line = reader.readLine()) != null) {
- sb.append(line);
- sb.append('\n');
- }
-
- return sb.toString();
- } finally {
- reader.close();
- }
- } finally {
- client.closeConnection(connection);
- }
- } catch (IOException ioe) {
- client.log(ioe, "Could not connect to maven central to look up the " +
- "latest available version for %1$s", dependency);
- return null;
- }
- }
-
- private boolean checkGradlePluginDependency(Context context, GradleCoordinate dependency,
- Object cookie) {
- GradleCoordinate latestPlugin = GradleCoordinate.parseCoordinateString(
- SdkConstants.GRADLE_PLUGIN_NAME +
- GRADLE_PLUGIN_MINIMUM_VERSION);
- if (GradleCoordinate.COMPARE_PLUS_HIGHER.compare(dependency, latestPlugin) < 0) {
- String message = "You must use a newer version of the Android Gradle plugin. The "
- + "minimum supported version is " + GRADLE_PLUGIN_MINIMUM_VERSION +
- " and the recommended version is " + GRADLE_PLUGIN_RECOMMENDED_VERSION;
- report(context, cookie, GRADLE_PLUGIN_COMPATIBILITY, message);
- return true;
- }
- return false;
- }
-
- private void checkSupportLibraries(Context context, GradleCoordinate dependency,
- Object cookie) {
- String groupId = dependency.getGroupId();
- String artifactId = dependency.getArtifactId();
- assert groupId != null && artifactId != null;
-
- // See if the support library version is lower than the targetSdkVersion
- if (mTargetSdkVersion > 0 && dependency.getMajorVersion() < mTargetSdkVersion &&
- dependency.getMajorVersion() != GradleCoordinate.PLUS_REV_VALUE &&
- // The multidex library doesn't follow normal supportlib numbering scheme
- !dependency.getArtifactId().startsWith("multidex") &&
- context.isEnabled(COMPATIBILITY)) {
- String message = "This support library should not use a lower version ("
- + dependency.getMajorVersion() + ") than the `targetSdkVersion` ("
- + mTargetSdkVersion + ")";
- report(context, cookie, COMPATIBILITY, message);
- }
-
- // Check to make sure you have the Android support repository installed
- File sdkHome = context.getClient().getSdkHome();
- File repository = SdkMavenRepository.ANDROID.getRepositoryLocation(sdkHome, true);
- if (repository == null) {
- report(context, cookie, DEPENDENCY,
- "Dependency on a support library, but the SDK installation does not "
- + "have the \"Extras > Android Support Repository\" installed. "
- + "Open the SDK manager and install it.");
- } else {
- checkLocalMavenVersions(context, dependency, cookie, groupId, artifactId,
- repository);
- }
- }
-
- private void checkPlayServices(Context context, GradleCoordinate dependency, Object cookie) {
- String groupId = dependency.getGroupId();
- String artifactId = dependency.getArtifactId();
- assert groupId != null && artifactId != null;
-
- File sdkHome = context.getClient().getSdkHome();
- File repository = SdkMavenRepository.GOOGLE.getRepositoryLocation(sdkHome, true);
- if (repository == null) {
- report(context, cookie, DEPENDENCY,
- "Dependency on Play Services, but the SDK installation does not "
- + "have the \"Extras > Google Repository\" installed. "
- + "Open the SDK manager and install it.");
- } else {
- checkLocalMavenVersions(context, dependency, cookie, groupId, artifactId,
- repository);
- }
- }
-
- private void checkLocalMavenVersions(Context context, GradleCoordinate dependency,
- Object cookie, String groupId, String artifactId, File repository) {
- GradleCoordinate max = SdkMavenRepository.getHighestInstalledVersion(groupId, artifactId,
- repository, null, false);
- if (max != null) {
- if (COMPARE_PLUS_HIGHER.compare(dependency, max) < 0
- && context.isEnabled(DEPENDENCY)) {
- String message = getNewerVersionAvailableMessage(dependency, max.getFullRevision());
- report(context, cookie, DEPENDENCY, message);
- }
- }
- }
-
- private static PreciseRevision getNewerRevision(@NonNull GradleCoordinate dependency,
- @NonNull PreciseRevision revision) {
- assert dependency.getGroupId() != null;
- assert dependency.getArtifactId() != null;
- GradleCoordinate coordinate;
- if (revision.isPreview()) {
- String coordinateString = dependency.getGroupId()
- + ":" + dependency.getArtifactId()
- + ":" + revision.toString();
- coordinate = GradleCoordinate.parseCoordinateString(coordinateString);
- } else {
- coordinate = new GradleCoordinate(dependency.getGroupId(), dependency.getArtifactId(),
- revision.getMajor(), revision.getMinor(), revision.getMicro());
- }
- if (COMPARE_PLUS_HIGHER.compare(dependency, coordinate) < 0) {
- return revision;
- } else {
- return null;
- }
- }
-
- private static boolean isOlderThan(@NonNull GradleCoordinate dependency, int major, int minor,
- int micro) {
- assert dependency.getGroupId() != null;
- assert dependency.getArtifactId() != null;
- return COMPARE_PLUS_HIGHER.compare(dependency,
- new GradleCoordinate(dependency.getGroupId(),
- dependency.getArtifactId(), major, minor, micro)) < 0;
- }
-
- private void report(@NonNull Context context, @NonNull Object cookie, @NonNull Issue issue,
- @NonNull String message) {
- if (context.isEnabled(issue)) {
- // Suppressed?
- // Temporarily unconditionally checking for suppress comments in Gradle files
- // since Studio insists on an AndroidLint id prefix
- boolean checkComments = /*context.getClient().checkForSuppressComments()
- &&*/ context.containsCommentSuppress();
- if (checkComments) {
- int startOffset = getStartOffset(context, cookie);
- if (startOffset >= 0 && context.isSuppressedWithComment(startOffset, issue)) {
- return;
- }
- }
-
- context.report(issue, createLocation(context, cookie), message);
- }
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- @NonNull
- protected Object getPropertyKeyCookie(@NonNull Object cookie) {
- return cookie;
- }
-
- @SuppressWarnings({"MethodMayBeStatic", "UnusedDeclaration"})
- @NonNull
- protected Object getPropertyPairCookie(@NonNull Object cookie) {
- return cookie;
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- protected int getStartOffset(@NonNull Context context, @NonNull Object cookie) {
- return -1;
- }
-
- @SuppressWarnings({"MethodMayBeStatic", "UnusedParameters"})
- protected Location createLocation(@NonNull Context context, @NonNull Object cookie) {
- return null;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
deleted file mode 100644
index f436283..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-
-import java.lang.reflect.Modifier;
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.ClassDeclaration;
-import lombok.ast.Node;
-
-/**
- * Checks that Handler implementations are top level classes or static.
- * See the corresponding check in the android.os.Handler source code.
- */
-public class HandlerDetector extends Detector implements Detector.JavaScanner {
-
- /** Potentially leaking handlers */
- public static final Issue ISSUE = Issue.create(
- "HandlerLeak", //$NON-NLS-1$
- "Handler reference leaks",
-
- "Since this Handler is declared as an inner class, it may prevent the outer " +
- "class from being garbage collected. If the Handler is using a Looper or " +
- "MessageQueue for a thread other than the main thread, then there is no issue. " +
- "If the Handler is using the Looper or MessageQueue of the main thread, you " +
- "need to fix your Handler declaration, as follows: Declare the Handler as a " +
- "static class; In the outer class, instantiate a WeakReference to the outer " +
- "class and pass this object to your Handler when you instantiate the Handler; " +
- "Make all references to members of the outer class using the WeakReference object.",
-
- Category.PERFORMANCE,
- 4,
- Severity.WARNING,
- new Implementation(
- HandlerDetector.class,
- Scope.JAVA_FILE_SCOPE));
-
- private static final String LOOPER_CLS = "android.os.Looper";
- private static final String HANDLER_CLS = "android.os.Handler";
-
- /** Constructs a new {@link HandlerDetector} */
- public HandlerDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Nullable
- @Override
- public List<String> applicableSuperClasses() {
- return Collections.singletonList(HANDLER_CLS);
- }
-
- @Override
- public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
- @NonNull Node node, @NonNull ResolvedClass cls) {
- if (!isInnerClass(declaration)) {
- return;
- }
-
- if (isStaticClass(declaration)) {
- return;
- }
-
- // Only flag handlers using the default looper
- if (hasLooperConstructorParameter(cls)) {
- return;
- }
-
- Node locationNode = node instanceof ClassDeclaration
- ? ((ClassDeclaration) node).astName() : node;
- Location location = context.getLocation(locationNode);
- context.report(ISSUE, locationNode, location, String.format(
- "This Handler class should be static or leaks might occur (%1$s)",
- cls.getName()));
- }
-
- private static boolean isInnerClass(@Nullable ClassDeclaration node) {
- return node == null || // null class declarations means anonymous inner class
- JavaContext.getParentOfType(node, ClassDeclaration.class, true) != null;
- }
-
- private static boolean isStaticClass(@Nullable ClassDeclaration node) {
- if (node == null) {
- // A null class declaration means anonymous inner class, and these can't be static
- return false;
- }
-
- int flags = node.astModifiers().getEffectiveModifierFlags();
- return (flags & Modifier.STATIC) != 0;
- }
-
- private static boolean hasLooperConstructorParameter(@NonNull ResolvedClass cls) {
- for (ResolvedMethod constructor : cls.getConstructors()) {
- for (int i = 0, n = constructor.getArgumentCount(); i < n; i++) {
- TypeDescriptor type = constructor.getArgumentType(i);
- if (type.matchesSignature(LOOPER_CLS)) {
- return true;
- }
- }
- }
- return false;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
deleted file mode 100644
index 288b3b1..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CONTENT_DESCRIPTION;
-import static com.android.SdkConstants.ATTR_HINT;
-import static com.android.SdkConstants.ATTR_LABEL;
-import static com.android.SdkConstants.ATTR_PROMPT;
-import static com.android.SdkConstants.ATTR_TEXT;
-import static com.android.SdkConstants.ATTR_TITLE;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Check which looks at the children of ScrollViews and ensures that they fill/match
- * the parent width instead of setting wrap_content.
- */
-public class HardcodedValuesDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "HardcodedText", //$NON-NLS-1$
- "Hardcoded text",
-
- "Hardcoding text attributes directly in layout files is bad for several reasons:\n" +
- "\n" +
- "* When creating configuration variations (for example for landscape or portrait)" +
- "you have to repeat the actual text (and keep it up to date when making changes)\n" +
- "\n" +
- "* The application cannot be translated to other languages by just adding new " +
- "translations for existing string resources.\n" +
- "\n" +
- "In Android Studio and Eclipse there are quickfixes to automatically extract this " +
- "hardcoded string into a resource lookup.",
-
- Category.I18N,
- 5,
- Severity.WARNING,
- new Implementation(
- HardcodedValuesDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
- // TODO: Add additional issues here, such as hardcoded colors, hardcoded sizes, etc
-
- /** Constructs a new {@link HardcodedValuesDetector} */
- public HardcodedValuesDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Arrays.asList(
- // Layouts
- ATTR_TEXT,
- ATTR_CONTENT_DESCRIPTION,
- ATTR_HINT,
- ATTR_LABEL,
- ATTR_PROMPT,
-
- // Menus
- ATTR_TITLE
- );
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.MENU;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String value = attribute.getValue();
- if (!value.isEmpty() && (value.charAt(0) != '@' && value.charAt(0) != '?')) {
- // Make sure this is really one of the android: attributes
- if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
- return;
- }
-
- context.report(ISSUE, attribute, context.getLocation(attribute),
- String.format("[I18N] Hardcoded string \"%1$s\", should use `@string` resource",
- value));
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
deleted file mode 100644
index 59998ea..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.FORMAT_METHOD;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.Frame;
-import org.objectweb.asm.tree.analysis.SourceInterpreter;
-import org.objectweb.asm.tree.analysis.SourceValue;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Checks for errors related to locale handling
- */
-public class LocaleDetector extends Detector implements ClassScanner {
- private static final Implementation IMPLEMENTATION = new Implementation(
- LocaleDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- /** Calling risky convenience methods */
- public static final Issue STRING_LOCALE = Issue.create(
- "DefaultLocale", //$NON-NLS-1$
- "Implied default locale in case conversion",
-
- "Calling `String#toLowerCase()` or `#toUpperCase()` *without specifying an " +
- "explicit locale* is a common source of bugs. The reason for that is that those " +
- "methods will use the current locale on the user's device, and even though the " +
- "code appears to work correctly when you are developing the app, it will fail " +
- "in some locales. For example, in the Turkish locale, the uppercase replacement " +
- "for `i` is *not* `I`.\n" +
- "\n" +
- "If you want the methods to just perform ASCII replacement, for example to convert " +
- "an enum name, call `String#toUpperCase(Locale.US)` instead. If you really want to " +
- "use the current locale, call `String#toUpperCase(Locale.getDefault())` instead.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- IMPLEMENTATION)
- .addMoreInfo(
- "http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$
-
- static final String DATE_FORMAT_OWNER = "java/text/SimpleDateFormat"; //$NON-NLS-1$
- private static final String STRING_OWNER = "java/lang/String"; //$NON-NLS-1$
-
- /** Constructs a new {@link LocaleDetector} */
- public LocaleDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(
- "toLowerCase", //$NON-NLS-1$
- "toUpperCase", //$NON-NLS-1$
- FORMAT_METHOD
- );
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String owner = call.owner;
- if (!owner.equals(STRING_OWNER)) {
- return;
- }
-
- String desc = call.desc;
- String name = call.name;
-
- if (name.equals(FORMAT_METHOD)) {
- // Only check the non-locale version of String.format
- if (!desc.equals("(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;")) { //$NON-NLS-1$
- return;
- }
- // Find the formatting string
- Analyzer analyzer = new Analyzer(new SourceInterpreter() {
- @Override
- public SourceValue newOperation(AbstractInsnNode insn) {
- if (insn.getOpcode() == Opcodes.LDC) {
- Object cst = ((LdcInsnNode) insn).cst;
- if (cst instanceof String) {
- return new StringValue(1, (String) cst);
- }
- }
- return super.newOperation(insn);
- }
- });
- try {
- Frame[] frames = analyzer.analyze(classNode.name, method);
- InsnList instructions = method.instructions;
- Frame frame = frames[instructions.indexOf(call)];
- if (frame.getStackSize() == 0) {
- return;
- }
- SourceValue stackValue = (SourceValue) frame.getStack(0);
- if (stackValue instanceof StringValue) {
- String format = ((StringValue) stackValue).getString();
- if (format != null && StringFormatDetector.isLocaleSpecific(format)) {
- Location location = context.getLocation(call);
- String message =
- "Implicitly using the default locale is a common source of bugs: " +
- "Use `String.format(Locale, ...)` instead";
- context.report(STRING_LOCALE, method, call, location, message);
- }
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- } else {
- if (desc.equals("()Ljava/lang/String;")) { //$NON-NLS-1$
- Location location = context.getLocation(call);
- String message = String.format(
- "Implicitly using the default locale is a common source of bugs: " +
- "Use `%1$s(Locale)` instead", name);
- context.report(STRING_LOCALE, method, call, location, message);
- }
- }
- }
-
- private static class StringValue extends SourceValue {
- private final String mString;
-
- StringValue(int size, String string) {
- super(size);
- mString = string;
- }
-
- String getString() {
- return mString;
- }
-
- @Override
- public int getSize() {
- return 1;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java
deleted file mode 100644
index e8fd0e9..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ConstantEvaluator;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.Expression;
-import lombok.ast.If;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.StringLiteral;
-import lombok.ast.VariableReference;
-
-/**
- * Detector for finding inefficiencies and errors in logging calls.
- */
-public class LogDetector extends Detector implements Detector.JavaScanner {
- private static final Implementation IMPLEMENTATION = new Implementation(
- LogDetector.class, Scope.JAVA_FILE_SCOPE);
-
-
- /** Log call missing surrounding if */
- public static final Issue CONDITIONAL = Issue.create(
- "LogConditional", //$NON-NLS-1$
- "Unconditional Logging Calls",
- "The BuildConfig class (available in Tools 17) provides a constant, \"DEBUG\", " +
- "which indicates whether the code is being built in release mode or in debug " +
- "mode. In release mode, you typically want to strip out all the logging calls. " +
- "Since the compiler will automatically remove all code which is inside a " +
- "\"if (false)\" check, surrounding your logging calls with a check for " +
- "BuildConfig.DEBUG is a good idea.\n" +
- "\n" +
- "If you *really* intend for the logging to be present in release mode, you can " +
- "suppress this warning with a @SuppressLint annotation for the intentional " +
- "logging calls.",
-
- Category.PERFORMANCE,
- 5,
- Severity.WARNING,
- IMPLEMENTATION).setEnabledByDefault(false);
-
- /** Mismatched tags between isLogging and log calls within it */
- public static final Issue WRONG_TAG = Issue.create(
- "LogTagMismatch", //$NON-NLS-1$
- "Mismatched Log Tags",
- "When guarding a `Log.v(tag, ...)` call with `Log.isLoggable(tag)`, the " +
- "tag passed to both calls should be the same. Similarly, the level passed " +
- "in to `Log.isLoggable` should typically match the type of `Log` call, e.g. " +
- "if checking level `Log.DEBUG`, the corresponding `Log` call should be `Log.d`, " +
- "not `Log.i`.",
-
- Category.CORRECTNESS,
- 5,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Log tag is too long */
- public static final Issue LONG_TAG = Issue.create(
- "LongLogTag", //$NON-NLS-1$
- "Too Long Log Tags",
- "Log tags are only allowed to be at most 23 tag characters long.",
-
- Category.CORRECTNESS,
- 5,
- Severity.ERROR,
- IMPLEMENTATION);
-
- @SuppressWarnings("SpellCheckingInspection")
- private static final String IS_LOGGABLE = "isLoggable"; //$NON-NLS-1$
- private static final String LOG_CLS = "android.util.Log"; //$NON-NLS-1$
- private static final String PRINTLN = "println"; //$NON-NLS-1$
-
- // ---- Implements Detector.JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Arrays.asList(
- "d", //$NON-NLS-1$
- "e", //$NON-NLS-1$
- "i", //$NON-NLS-1$
- "v", //$NON-NLS-1$
- "w", //$NON-NLS-1$
- PRINTLN,
- IS_LOGGABLE);
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, @NonNull MethodInvocation node) {
- ResolvedNode resolved = context.resolve(node);
- if (!(resolved instanceof ResolvedMethod)) {
- return;
- }
-
- ResolvedMethod method = (ResolvedMethod) resolved;
- if (!method.getContainingClass().matches(LOG_CLS)) {
- return;
- }
-
- String name = node.astName().astValue();
- boolean withinConditional = IS_LOGGABLE.equals(name) ||
- checkWithinConditional(context, node.getParent(), node);
-
- // See if it's surrounded by an if statement (and it's one of the non-error, spammy
- // log methods (info, verbose, etc))
- if (("i".equals(name) || "d".equals(name) || "v".equals(name) || PRINTLN.equals(name))
- && !withinConditional
- && performsWork(context, node)
- && context.isEnabled(CONDITIONAL)) {
- String message = String.format("The log call Log.%1$s(...) should be " +
- "conditional: surround with `if (Log.isLoggable(...))` or " +
- "`if (BuildConfig.DEBUG) { ... }`",
- node.astName().toString());
- context.report(CONDITIONAL, node, context.getLocation(node), message);
- }
-
- // Check tag length
- if (context.isEnabled(LONG_TAG)) {
- int tagArgumentIndex = PRINTLN.equals(name) ? 1 : 0;
- if (method.getArgumentCount() > tagArgumentIndex
- && method.getArgumentType(tagArgumentIndex).matchesSignature(TYPE_STRING)
- && node.astArguments().size() == method.getArgumentCount()) {
- Iterator<Expression> iterator = node.astArguments().iterator();
- if (tagArgumentIndex == 1) {
- iterator.next();
- }
- Node argument = iterator.next();
- String tag = ConstantEvaluator.evaluateString(context, argument, true);
- if (tag != null && tag.length() > 23) {
- String message = String.format(
- "The logging tag can be at most 23 characters, was %1$d (%2$s)",
- tag.length(), tag);
- context.report(LONG_TAG, node, context.getLocation(node), message);
- }
- }
- }
- }
-
- /** Returns true if the given logging call performs "work" to compute the message */
- private static boolean performsWork(
- @NonNull JavaContext context,
- @NonNull MethodInvocation node) {
- int messageArgumentIndex = PRINTLN.equals(node.astName().astValue()) ? 2 : 1;
- if (node.astArguments().size() >= messageArgumentIndex) {
- Iterator<Expression> iterator = node.astArguments().iterator();
- Node argument = null;
- for (int i = 0; i <= messageArgumentIndex; i++) {
- argument = iterator.next();
- }
- if (argument == null) {
- return false;
- }
- if (argument instanceof StringLiteral || argument instanceof VariableReference) {
- return false;
- }
- if (argument instanceof BinaryExpression) {
- String string = ConstantEvaluator.evaluateString(context, argument, false);
- //noinspection VariableNotUsedInsideIf
- if (string != null) { // does it resolve to a constant?
- return false;
- }
- } else if (argument instanceof Select) {
- String string = ConstantEvaluator.evaluateString(context, argument, false);
- //noinspection VariableNotUsedInsideIf
- if (string != null) {
- return false;
- }
- }
-
- // Method invocations etc
- return true;
- }
-
- return false;
- }
-
- private static boolean checkWithinConditional(
- @NonNull JavaContext context,
- @Nullable Node curr,
- @NonNull MethodInvocation logCall) {
- while (curr != null) {
- if (curr instanceof If) {
- If ifNode = (If) curr;
- if (ifNode.astCondition() instanceof MethodInvocation) {
- MethodInvocation call = (MethodInvocation) ifNode.astCondition();
- if (IS_LOGGABLE.equals(call.astName().astValue())) {
- checkTagConsistent(context, logCall, call);
- }
- }
-
- return true;
- } else if (curr instanceof MethodInvocation
- || curr instanceof ClassDeclaration) { // static block
- break;
- }
- curr = curr.getParent();
- }
- return false;
- }
-
- /** Checks that the tag passed to Log.s and Log.isLoggable match */
- private static void checkTagConsistent(JavaContext context, MethodInvocation logCall,
- MethodInvocation call) {
- Iterator<Expression> isLogIterator = call.astArguments().iterator();
- Iterator<Expression> logIterator = logCall.astArguments().iterator();
- if (!isLogIterator.hasNext() || !logIterator.hasNext()) {
- return;
- }
- Expression isLoggableTag = isLogIterator.next();
- Expression logTag = logIterator.next();
-
- //String callName = logCall.astName().astValue();
- String logCallName = logCall.astName().astValue();
- boolean isPrintln = PRINTLN.equals(logCallName);
- if (isPrintln) {
- if (!logIterator.hasNext()) {
- return;
- }
- logTag = logIterator.next();
- }
-
- if (logTag != null) {
- if (!isLoggableTag.toString().equals(logTag.toString())) {
- ResolvedNode resolved1 = context.resolve(isLoggableTag);
- ResolvedNode resolved2 = context.resolve(logTag);
- if ((resolved1 == null || resolved2 == null || !resolved1.equals(resolved2))
- && context.isEnabled(WRONG_TAG)) {
- Location location = context.getLocation(logTag);
- Location alternate = context.getLocation(isLoggableTag);
- alternate.setMessage("Conflicting tag");
- location.setSecondary(alternate);
- String isLoggableDescription = resolved1 != null ? resolved1
- .getName()
- : isLoggableTag.toString();
- String logCallDescription = resolved2 != null ? resolved2.getName()
- : logTag.toString();
- String message = String.format(
- "Mismatched tags: the `%1$s()` and `isLoggable()` calls typically " +
- "should pass the same tag: `%2$s` versus `%3$s`",
- logCallName,
- isLoggableDescription,
- logCallDescription);
- context.report(WRONG_TAG, call, location, message);
- }
- }
- }
-
- // Check log level versus the actual log call type (e.g. flag
- // if (Log.isLoggable(TAG, Log.DEBUG) Log.info(TAG, "something")
-
- if (logCallName.length() != 1 || !isLogIterator.hasNext()) { // e.g. println
- return;
- }
- Expression isLoggableLevel = isLogIterator.next();
- if (isLoggableLevel == null) {
- return;
- }
- String levelString = isLoggableLevel.toString();
- if (isLoggableLevel instanceof Select) {
- levelString = ((Select)isLoggableLevel).astIdentifier().astValue();
- }
- if (levelString.isEmpty()) {
- return;
- }
- char levelChar = Character.toLowerCase(levelString.charAt(0));
- if (logCallName.charAt(0) == levelChar || !context.isEnabled(WRONG_TAG)) {
- return;
- }
- switch (levelChar) {
- case 'd':
- case 'e':
- case 'i':
- case 'v':
- case 'w':
- break;
- default:
- // Some other char; e.g. user passed in a literal value or some
- // local constant or variable alias
- return;
- }
- String expectedCall = String.valueOf(levelChar);
- String message = String.format(
- "Mismatched logging levels: when checking `isLoggable` level `%1$s`, the " +
- "corresponding log call should be `Log.%2$s`, not `Log.%3$s`",
- levelString, expectedCall, logCallName);
- Location location = context.getLocation(logCall.astName());
- Location alternate = context.getLocation(isLoggableLevel);
- alternate.setMessage("Conflicting tag");
- location.setSecondary(alternate);
- context.report(WRONG_TAG, call, location, message);
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
deleted file mode 100644
index 66537f5..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
+++ /dev/null
@@ -1,1041 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ALLOW_BACKUP;
-import static com.android.SdkConstants.ATTR_ICON;
-import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PACKAGE;
-import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
-import static com.android.SdkConstants.ATTR_VERSION_CODE;
-import static com.android.SdkConstants.ATTR_VERSION_NAME;
-import static com.android.SdkConstants.DRAWABLE_PREFIX;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_INTENT_FILTER;
-import static com.android.SdkConstants.TAG_PERMISSION;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-import static com.android.SdkConstants.TAG_USES_FEATURE;
-import static com.android.SdkConstants.TAG_USES_LIBRARY;
-import static com.android.SdkConstants.TAG_USES_PERMISSION;
-import static com.android.SdkConstants.TAG_USES_SDK;
-import static com.android.xml.AndroidManifest.NODE_ACTION;
-import static com.android.xml.AndroidManifest.NODE_DATA;
-import static com.android.xml.AndroidManifest.NODE_METADATA;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.BuildTypeContainer;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.SourceProviderContainer;
-import com.android.builder.model.Variant;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Maps;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks for issues in AndroidManifest files such as declaring elements in the
- * wrong order.
- */
-public class ManifestDetector extends Detector implements Detector.XmlScanner {
- private static final Implementation IMPLEMENTATION = new Implementation(
- ManifestDetector.class,
- Scope.MANIFEST_SCOPE
- );
-
- /** Wrong order of elements in the manifest */
- public static final Issue ORDER = Issue.create(
- "ManifestOrder", //$NON-NLS-1$
- "Incorrect order of elements in manifest",
- "The <application> tag should appear after the elements which declare " +
- "which version you need, which features you need, which libraries you " +
- "need, and so on. In the past there have been subtle bugs (such as " +
- "themes not getting applied correctly) when the `<application>` tag appears " +
- "before some of these other elements, so it's best to order your " +
- "manifest in the logical dependency order.",
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Missing a {@code <uses-sdk>} element */
- public static final Issue USES_SDK = Issue.create(
- "UsesMinSdkAttributes", //$NON-NLS-1$
- "Minimum SDK and target SDK attributes not defined",
-
- "The manifest should contain a `<uses-sdk>` element which defines the " +
- "minimum API Level required for the application to run, " +
- "as well as the target version (the highest API level you have tested " +
- "the version for.)",
-
- Category.CORRECTNESS,
- 9,
- Severity.WARNING,
- IMPLEMENTATION).addMoreInfo(
- "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
-
- /** Using a targetSdkVersion that isn't recent */
- public static final Issue TARGET_NEWER = Issue.create(
- "OldTargetApi", //$NON-NLS-1$
- "Target SDK attribute is not targeting latest version",
-
- "When your application runs on a version of Android that is more recent than your " +
- "`targetSdkVersion` specifies that it has been tested with, various compatibility " +
- "modes kick in. This ensures that your application continues to work, but it may " +
- "look out of place. For example, if the `targetSdkVersion` is less than 14, your " +
- "app may get an option button in the UI.\n" +
- "\n" +
- "To fix this issue, set the `targetSdkVersion` to the highest available value. Then " +
- "test your app to make sure everything works correctly. You may want to consult " +
- "the compatibility notes to see what changes apply to each version you are adding " +
- "support for: " +
- "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- IMPLEMENTATION).addMoreInfo(
- "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html"); //$NON-NLS-1$
-
- /** Using multiple {@code <uses-sdk>} elements */
- public static final Issue MULTIPLE_USES_SDK = Issue.create(
- "MultipleUsesSdk", //$NON-NLS-1$
- "Multiple `<uses-sdk>` elements in the manifest",
-
- "The `<uses-sdk>` element should appear just once; the tools will *not* merge the " +
- "contents of all the elements so if you split up the attributes across multiple " +
- "elements, only one of them will take effect. To fix this, just merge all the " +
- "attributes from the various elements into a single <uses-sdk> element.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- IMPLEMENTATION).addMoreInfo(
- "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
-
- /** Missing a {@code <uses-sdk>} element */
- public static final Issue WRONG_PARENT = Issue.create(
- "WrongManifestParent", //$NON-NLS-1$
- "Wrong manifest parent",
-
- "The `<uses-library>` element should be defined as a direct child of the " +
- "`<application>` tag, not the `<manifest>` tag or an `<activity>` tag. Similarly, " +
- "a `<uses-sdk>` tag much be declared at the root level, and so on. This check " +
- "looks for incorrect declaration locations in the manifest, and complains " +
- "if an element is found in the wrong place.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- IMPLEMENTATION).addMoreInfo(
- "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
-
- /** Missing a {@code <uses-sdk>} element */
- public static final Issue DUPLICATE_ACTIVITY = Issue.create(
- "DuplicateActivity", //$NON-NLS-1$
- "Activity registered more than once",
-
- "An activity should only be registered once in the manifest. If it is " +
- "accidentally registered more than once, then subtle errors can occur, " +
- "since attribute declarations from the two elements are not merged, so " +
- "you may accidentally remove previous declarations.",
-
- Category.CORRECTNESS,
- 5,
- Severity.FATAL,
- IMPLEMENTATION);
-
- /** Not explicitly defining allowBackup */
- public static final Issue ALLOW_BACKUP = Issue.create(
- "AllowBackup", //$NON-NLS-1$
- "Missing `allowBackup` attribute",
-
- "The allowBackup attribute determines if an application's data can be backed up " +
- "and restored. It is documented at " +
- "http://developer.android.com/reference/android/R.attr.html#allowBackup\n" +
- "\n" +
- "By default, this flag is set to `true`. When this flag is set to `true`, " +
- "application data can be backed up and restored by the user using `adb backup` " +
- "and `adb restore`.\n" +
- "\n" +
- "This may have security consequences for an application. `adb backup` allows " +
- "users who have enabled USB debugging to copy application data off of the " +
- "device. Once backed up, all application data can be read by the user. " +
- "`adb restore` allows creation of application data from a source specified " +
- "by the user. Following a restore, applications should not assume that the " +
- "data, file permissions, and directory permissions were created by the " +
- "application itself.\n" +
- "\n" +
- "Setting `allowBackup=\"false\"` opts an application out of both backup and " +
- "restore.\n" +
- "\n" +
- "To fix this warning, decide whether your application should support backup, " +
- "and explicitly set `android:allowBackup=(true|false)\"`",
-
- Category.SECURITY,
- 3,
- Severity.WARNING,
- IMPLEMENTATION).addMoreInfo(
- "http://developer.android.com/reference/android/R.attr.html#allowBackup");
-
- /** Conflicting permission names */
- public static final Issue UNIQUE_PERMISSION = Issue.create(
- "UniquePermission", //$NON-NLS-1$
- "Permission names are not unique",
-
- "The unqualified names or your permissions must be unique. The reason for this " +
- "is that at build time, the `aapt` tool will generate a class named `Manifest` " +
- "which contains a field for each of your permissions. These fields are named " +
- "using your permission unqualified names (i.e. the name portion after the last " +
- "dot).\n" +
- "\n" +
- "If more than one permission maps to the same field name, that field will " +
- "arbitrarily name just one of them.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- IMPLEMENTATION);
-
- /** Using a resource for attributes that do not allow it */
- public static final Issue SET_VERSION = Issue.create(
- "MissingVersion", //$NON-NLS-1$
- "Missing application name/version",
-
- "You should define the version information for your application.\n" +
- "`android:versionCode`: An integer value that represents the version of the " +
- "application code, relative to other versions.\n" +
- "\n" +
- "`android:versionName`: A string value that represents the release version of " +
- "the application code, as it should be shown to users.",
-
- Category.CORRECTNESS,
- 2,
- Severity.WARNING,
- IMPLEMENTATION).addMoreInfo(
- "http://developer.android.com/tools/publishing/versioning.html#appversioning");
-
- /** Using a resource for attributes that do not allow it */
- public static final Issue ILLEGAL_REFERENCE = Issue.create(
- "IllegalResourceRef", //$NON-NLS-1$
- "Name and version must be integer or string, not resource",
-
- "For the `versionCode` attribute, you have to specify an actual integer " +
- "literal; you cannot use an indirection with a `@dimen/name` resource. " +
- "Similarly, the `versionName` attribute should be an actual string, not " +
- "a string resource url.",
-
- Category.CORRECTNESS,
- 8,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Declaring a uses-feature multiple time */
- public static final Issue DUPLICATE_USES_FEATURE = Issue.create(
- "DuplicateUsesFeature", //$NON-NLS-1$
- "Feature declared more than once",
-
- "A given feature should only be declared once in the manifest.",
-
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Not explicitly defining application icon */
- public static final Issue APPLICATION_ICON = Issue.create(
- "MissingApplicationIcon", //$NON-NLS-1$
- "Missing application icon",
-
- "You should set an icon for the application as whole because there is no " +
- "default. This attribute must be set as a reference to a drawable resource " +
- "containing the image (for example `@drawable/icon`).",
-
- Category.ICONS,
- 5,
- Severity.WARNING,
- IMPLEMENTATION).addMoreInfo(
- "http://developer.android.com/tools/publishing/preparing.html#publishing-configure"); //$NON-NLS-1$
-
- /** Malformed Device Admin */
- public static final Issue DEVICE_ADMIN = Issue.create(
- "DeviceAdmin", //$NON-NLS-1$
- "Malformed Device Admin",
- "If you register a broadcast receiver which acts as a device admin, you must also " +
- "register an `<intent-filter>` for the action " +
- "`android.app.action.DEVICE_ADMIN_ENABLED`, without any `<data>`, such that the " +
- "device admin can be activated/deactivated.\n" +
- "\n" +
- "To do this, add\n" +
- "`<intent-filter>`\n" +
- " `<action android:name=\"android.app.action.DEVICE_ADMIN_ENABLED\" />`\n" +
- "`</intent-filter>`\n" +
- "to your `<receiver>`.",
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Using a mock location in a non-debug-specific manifest file */
- public static final Issue MOCK_LOCATION = Issue.create(
- "MockLocation", //$NON-NLS-1$
- "Using mock location provider in production",
-
- "Using a mock location provider (by requiring the permission " +
- "`android.permission.ACCESS_MOCK_LOCATION`) should *only* be done " +
- "in debug builds (or from tests). In Gradle projects, that means you should only " +
- "request this permission in a test or debug source set specific manifest file.\n" +
- "\n" +
- "To fix this, create a new manifest file in the debug folder and move " +
- "the `<uses-permission>` element there. A typical path to a debug manifest " +
- "override file in a Gradle project is src/debug/AndroidManifest.xml.",
-
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- IMPLEMENTATION);
-
- /** Defining a value that is overridden by Gradle */
- public static final Issue GRADLE_OVERRIDES = Issue.create(
- "GradleOverrides", //$NON-NLS-1$
- "Value overridden by Gradle build script",
-
- "The value of (for example) `minSdkVersion` is only used if it is not specified in " +
- "the `build.gradle` build scripts. When specified in the Gradle build scripts, " +
- "the manifest value is ignored and can be misleading, so should be removed to " +
- "avoid ambiguity.",
-
- Category.CORRECTNESS,
- 4,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Using drawable rather than mipmap launcher icons */
- public static final Issue MIPMAP = Issue.create(
- "MipmapIcons", //$NON-NLS-1$
- "Use Mipmap Launcher Icons",
-
- "Launcher icons should be provided in the `mipmap` resource directory. " +
- "This is the same as the `drawable` resource directory, except resources in " +
- "the `mipmap` directory will not get stripped out when creating density-specific " +
- "APKs.\n" +
- "\n" +
- "In certain cases, the Launcher app may use a higher resolution asset (than " +
- "would normally be computed for the device) to display large app shortcuts. " +
- "If drawables for densities other than the device's resolution have been " +
- "stripped out, then the app shortcut could appear blurry.\n" +
- "\n" +
- "To fix this, move your launcher icons from `drawable-`dpi to `mipmap-`dpi " +
- "and change references from @drawable/ and R.drawable to @mipmap/ and R.mipmap.\n" +
- "In Android Studio this lint warning has a quickfix to perform this automatically.",
- Category.ICONS,
- 5,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Permission name of mock location permission */
- public static final String MOCK_LOCATION_PERMISSION =
- "android.permission.ACCESS_MOCK_LOCATION"; //$NON-NLS-1$
-
- /** Constructs a new {@link ManifestDetector} check */
- public ManifestDetector() {
- }
-
- private boolean mSeenApplication;
-
- /** Number of times we've seen the <uses-sdk> element */
- private int mSeenUsesSdk;
-
- /** Activities we've encountered */
- private Set<String> mActivities;
-
- /** Features we've encountered */
- private Set<String> mUsesFeatures;
-
- /** Permission basenames */
- private Map<String, String> mPermissionNames;
-
- /** Handle to the {@code <application>} tag */
- private Location.Handle mApplicationTagHandle;
-
- /** Whether we've seen an application icon definition in any of the manifest files (or
- * if a manifest tag warning for this has been explicitly disabled) */
- private boolean mSeenAppIcon;
-
- /** Whether we've seen an allow backup definition in any of the manifest files (or
- * if a manifest tag warning for this has been explicitly disabled) */
- private boolean mSeenAllowBackup;
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- mSeenApplication = false;
- mSeenUsesSdk = 0;
- mActivities = new HashSet<String>();
- mUsesFeatures = new HashSet<String>();
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- XmlContext xmlContext = (XmlContext) context;
- Element element = xmlContext.document.getDocumentElement();
- if (element != null) {
- checkDocumentElement(xmlContext, element);
- }
-
- if (mSeenUsesSdk == 0 && context.isEnabled(USES_SDK)
- // Not required in Gradle projects; typically defined in build.gradle instead
- // and inserted at build time
- && !context.getMainProject().isGradleProject()) {
- context.report(USES_SDK, Location.create(context.file),
- "Manifest should specify a minimum API level with " +
- "`<uses-sdk android:minSdkVersion=\"?\" />`; if it really supports " +
- "all versions of Android set it to 1.");
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (!mSeenAllowBackup && context.isEnabled(ALLOW_BACKUP)
- && !context.getProject().isLibrary()
- && context.getMainProject().getMinSdk() >= 4) {
- Location location = getMainApplicationTagLocation(context);
- context.report(ALLOW_BACKUP, location,
- "Should explicitly set `android:allowBackup` to `true` or " +
- "`false` (it's `true` by default, and that can have some security " +
- "implications for the application's data)");
- }
-
- if (!context.getMainProject().isLibrary()
- && !mSeenAppIcon && context.isEnabled(APPLICATION_ICON)) {
- Location location = getMainApplicationTagLocation(context);
- context.report(APPLICATION_ICON, location,
- "Should explicitly set `android:icon`, there is no default");
- }
- }
-
- @Nullable
- private Location getMainApplicationTagLocation(@NonNull Context context) {
- if (mApplicationTagHandle != null) {
- return mApplicationTagHandle.resolve();
- }
-
- List<File> manifestFiles = context.getMainProject().getManifestFiles();
- if (!manifestFiles.isEmpty()) {
- return Location.create(manifestFiles.get(0));
- }
-
- return null;
- }
-
- private static void checkDocumentElement(XmlContext context, Element element) {
- Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_VERSION_CODE);
- if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- context.report(ILLEGAL_REFERENCE, element, context.getLocation(codeNode),
- "The `android:versionCode` cannot be a resource url, it must be "
- + "a literal integer");
- } else if (codeNode == null && context.isEnabled(SET_VERSION)
- // Not required in Gradle projects; typically defined in build.gradle instead
- // and inserted at build time
- && !context.getMainProject().isGradleProject()) {
- context.report(SET_VERSION, element, context.getLocation(element),
- "Should set `android:versionCode` to specify the application version");
- }
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_VERSION_NAME);
- if (nameNode == null && context.isEnabled(SET_VERSION)
- // Not required in Gradle projects; typically defined in build.gradle instead
- // and inserted at build time
- && !context.getMainProject().isGradleProject()) {
- context.report(SET_VERSION, element, context.getLocation(element),
- "Should set `android:versionName` to specify the application version");
- }
-
- checkOverride(context, element, ATTR_VERSION_CODE);
- checkOverride(context, element, ATTR_VERSION_NAME);
-
- Attr pkgNode = element.getAttributeNode(ATTR_PACKAGE);
- if (pkgNode != null) {
- String pkg = pkgNode.getValue();
- if (pkg.contains("${") && context.getMainProject().isGradleProject()) {
- context.report(GRADLE_OVERRIDES, pkgNode, context.getLocation(pkgNode),
- "Cannot use placeholder for the package in the manifest; "
- + "set `applicationId` in `build.gradle` instead");
- }
- }
- }
-
- private static void checkOverride(XmlContext context, Element element, String attributeName) {
- Project project = context.getProject();
- Attr attribute = element.getAttributeNodeNS(ANDROID_URI, attributeName);
- if (project.isGradleProject() && attribute != null && context.isEnabled(GRADLE_OVERRIDES)) {
- Variant variant = project.getCurrentVariant();
- if (variant != null) {
- ProductFlavor flavor = variant.getMergedFlavor();
- String gradleValue = null;
- if (ATTR_MIN_SDK_VERSION.equals(attributeName)) {
- try {
- ApiVersion minSdkVersion = flavor.getMinSdkVersion();
- gradleValue = minSdkVersion != null ? minSdkVersion.getApiString() : null;
- } catch (Throwable e) {
- // TODO: REMOVE ME
- // This method was added in the 0.11 model. We'll need to drop support
- // for 0.10 shortly but until 0.11 is available this is a stopgap measure
- }
- } else if (ATTR_TARGET_SDK_VERSION.equals(attributeName)) {
- try {
- ApiVersion targetSdkVersion = flavor.getTargetSdkVersion();
- gradleValue = targetSdkVersion != null ? targetSdkVersion.getApiString() : null;
- } catch (Throwable e) {
- // TODO: REMOVE ME
- // This method was added in the 0.11 model. We'll need to drop support
- // for 0.10 shortly but until 0.11 is available this is a stopgap measure
- }
- } else if (ATTR_VERSION_CODE.equals(attributeName)) {
- Integer versionCode = flavor.getVersionCode();
- if (versionCode != null) {
- gradleValue = versionCode.toString();
- }
- } else if (ATTR_VERSION_NAME.equals(attributeName)) {
- gradleValue = flavor.getVersionName();
- } else {
- assert false : attributeName;
- return;
- }
-
- if (gradleValue != null) {
- String manifestValue = attribute.getValue();
-
- String message = String.format("This `%1$s` value (`%2$s`) is not used; it is "
- + "always overridden by the value specified in the Gradle build "
- + "script (`%3$s`)", attributeName, manifestValue, gradleValue);
- context.report(GRADLE_OVERRIDES, attribute, context.getLocation(attribute),
- message);
- }
- }
- }
- }
-
- // ---- Implements Detector.XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_APPLICATION,
- TAG_USES_PERMISSION,
- TAG_PERMISSION,
- "permission-tree", //$NON-NLS-1$
- "permission-group", //$NON-NLS-1$
- TAG_USES_SDK,
- "uses-configuration", //$NON-NLS-1$
- TAG_USES_FEATURE,
- "supports-screens", //$NON-NLS-1$
- "compatible-screens", //$NON-NLS-1$
- "supports-gl-texture", //$NON-NLS-1$
- TAG_USES_LIBRARY,
- TAG_ACTIVITY,
- TAG_SERVICE,
- TAG_PROVIDER,
- TAG_RECEIVER
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
- Node parentNode = element.getParentNode();
-
- boolean isReceiver = tag.equals(TAG_RECEIVER);
- if (isReceiver) {
- checkDeviceAdmin(context, element);
- }
-
- if (tag.equals(TAG_USES_LIBRARY) || tag.equals(TAG_ACTIVITY) || tag.equals(TAG_SERVICE)
- || tag.equals(TAG_PROVIDER) || isReceiver) {
- if (!TAG_APPLICATION.equals(parentNode.getNodeName())
- && context.isEnabled(WRONG_PARENT)) {
- context.report(WRONG_PARENT, element, context.getLocation(element),
- String.format(
- "The `<%1$s>` element must be a direct child of the <application> element",
- tag));
- }
-
- if (tag.equals(TAG_ACTIVITY)) {
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (nameNode != null) {
- String name = nameNode.getValue();
- if (!name.isEmpty()) {
- String pkg = context.getMainProject().getPackage();
- if (name.charAt(0) == '.') {
- name = pkg + name;
- } else if (name.indexOf('.') == -1) {
- name = pkg + '.' + name;
- }
- if (mActivities.contains(name)) {
- String message = String.format(
- "Duplicate registration for activity `%1$s`", name);
- context.report(DUPLICATE_ACTIVITY, element,
- context.getLocation(nameNode), message);
- } else {
- mActivities.add(name);
- }
- }
- }
-
- checkMipmapIcon(context, element);
- }
-
- return;
- }
-
- if (parentNode != element.getOwnerDocument().getDocumentElement()
- && context.isEnabled(WRONG_PARENT)) {
- context.report(WRONG_PARENT, element, context.getLocation(element),
- String.format(
- "The `<%1$s>` element must be a direct child of the " +
- "`<manifest>` root element", tag));
- }
-
- if (tag.equals(TAG_USES_SDK)) {
- mSeenUsesSdk++;
-
- if (mSeenUsesSdk == 2) { // Only warn when we encounter the first one
- Location location = context.getLocation(element);
-
- // Link up *all* encountered locations in the document
- NodeList elements = element.getOwnerDocument().getElementsByTagName(TAG_USES_SDK);
- Location secondary = null;
- for (int i = elements.getLength() - 1; i >= 0; i--) {
- Element e = (Element) elements.item(i);
- if (e != element) {
- Location l = context.getLocation(e);
- l.setSecondary(secondary);
- l.setMessage("Also appears here");
- secondary = l;
- }
- }
- location.setSecondary(secondary);
-
- if (context.isEnabled(MULTIPLE_USES_SDK)) {
- context.report(MULTIPLE_USES_SDK, element, location,
- "There should only be a single `<uses-sdk>` element in the manifest:" +
- " merge these together");
- }
- return;
- }
-
- if (!element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
- if (context.isEnabled(USES_SDK) && !context.getMainProject().isGradleProject()) {
- context.report(USES_SDK, element, context.getLocation(element),
- "`<uses-sdk>` tag should specify a minimum API level with " +
- "`android:minSdkVersion=\"?\"`");
- }
- } else {
- Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
- if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- context.report(ILLEGAL_REFERENCE, element, context.getLocation(codeNode),
- "The `android:minSdkVersion` cannot be a resource url, it must be "
- + "a literal integer (or string if a preview codename)");
- }
-
- checkOverride(context, element, ATTR_MIN_SDK_VERSION);
- }
-
- if (!element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
- // Warn if not setting target SDK -- but only if the min SDK is somewhat
- // old so there's some compatibility stuff kicking in (such as the menu
- // button etc)
- if (context.isEnabled(USES_SDK) && !context.getMainProject().isGradleProject()) {
- context.report(USES_SDK, element, context.getLocation(element),
- "`<uses-sdk>` tag should specify a target API level (the " +
- "highest verified version; when running on later versions, " +
- "compatibility behaviors may be enabled) with " +
- "`android:targetSdkVersion=\"?\"`");
- }
- } else {
- checkOverride(context, element, ATTR_TARGET_SDK_VERSION);
-
- if (context.isEnabled(TARGET_NEWER)) {
- Attr targetSdkVersionNode = element.getAttributeNodeNS(ANDROID_URI,
- ATTR_TARGET_SDK_VERSION);
- if (targetSdkVersionNode != null) {
- String target = targetSdkVersionNode.getValue();
- try {
- int api = Integer.parseInt(target);
- if (api < context.getClient().getHighestKnownApiLevel()) {
- context.report(TARGET_NEWER, element,
- context.getLocation(targetSdkVersionNode),
- "Not targeting the latest versions of Android; compatibility " +
- "modes apply. Consider testing and updating this version. " +
- "Consult the `android.os.Build.VERSION_CODES` javadoc for details.");
- }
- } catch (NumberFormatException nufe) {
- // Ignore: AAPT will enforce this.
- }
- }
- }
- }
-
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
- if (nameNode != null && nameNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- context.report(ILLEGAL_REFERENCE, element, context.getLocation(nameNode),
- "The `android:targetSdkVersion` cannot be a resource url, it must be "
- + "a literal integer (or string if a preview codename)");
- }
- }
- if (tag.equals(TAG_PERMISSION)) {
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (nameNode != null) {
- String name = nameNode.getValue();
- String base = name.substring(name.lastIndexOf('.') + 1);
- if (mPermissionNames == null) {
- mPermissionNames = Maps.newHashMap();
- } else if (mPermissionNames.containsKey(base)) {
- String prevName = mPermissionNames.get(base);
- Location location = context.getLocation(nameNode);
- NodeList siblings = element.getParentNode().getChildNodes();
- for (int i = 0, n = siblings.getLength(); i < n; i++) {
- Node node = siblings.item(i);
- if (node == element) {
- break;
- } else if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element sibling = (Element) node;
- String suffix = '.' + base;
- if (sibling.getTagName().equals(TAG_PERMISSION)) {
- String b = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (b.endsWith(suffix)) {
- Location prevLocation = context.getLocation(node);
- prevLocation.setMessage("Previous permission here");
- location.setSecondary(prevLocation);
- break;
- }
-
- }
- }
- }
-
- String message = String.format("Permission name `%1$s` is not unique " +
- "(appears in both `%2$s` and `%3$s`)", base, prevName, name);
- context.report(UNIQUE_PERMISSION, element, location, message);
- }
-
- mPermissionNames.put(base, name);
- }
- }
-
- if (tag.equals(TAG_USES_PERMISSION)) {
- Attr name = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (name != null && name.getValue().equals(MOCK_LOCATION_PERMISSION)
- && context.getMainProject().isGradleProject()
- && !isDebugOrTestManifest(context, context.file)
- && context.isEnabled(MOCK_LOCATION)) {
- String message = "Mock locations should only be requested in a test or " +
- "debug-specific manifest file (typically `src/debug/AndroidManifest.xml`)";
- Location location = context.getLocation(name);
- context.report(MOCK_LOCATION, element, location, message);
- }
- }
-
- if (tag.equals(TAG_APPLICATION)) {
- mSeenApplication = true;
- boolean recordLocation = false;
- if (element.hasAttributeNS(ANDROID_URI, ATTR_ALLOW_BACKUP)
- || context.getDriver().isSuppressed(context, ALLOW_BACKUP, element)) {
- mSeenAllowBackup = true;
- } else {
- recordLocation = true;
- }
- if (element.hasAttributeNS(ANDROID_URI, ATTR_ICON)
- || context.getDriver().isSuppressed(context, APPLICATION_ICON, element)) {
- checkMipmapIcon(context, element);
- mSeenAppIcon = true;
- } else {
- recordLocation = true;
- }
- if (recordLocation && !context.getProject().isLibrary() &&
- (mApplicationTagHandle == null || isMainManifest(context, context.file))) {
- mApplicationTagHandle = context.createLocationHandle(element);
- }
- Attr fullBackupNode = element.getAttributeNodeNS(ANDROID_URI, "fullBackupContent");
- if (fullBackupNode != null &&
- fullBackupNode.getValue().startsWith(PREFIX_RESOURCE_REF) &&
- context.getClient().supportsProjectResources()) {
- AbstractResourceRepository resources = context.getClient()
- .getProjectResources(context.getProject(), true);
- ResourceUrl url = ResourceUrl.parse(fullBackupNode.getValue());
- if (url != null && !url.framework
- && resources != null
- && !resources.hasResourceItem(url.type, url.name)) {
- Location location = context.getValueLocation(fullBackupNode);
- context.report(ALLOW_BACKUP, fullBackupNode, location,
- "Missing `<full-backup-content>` resource");
- }
- } else if (fullBackupNode == null && context.getMainProject().getTargetSdk() >= 23) {
- Location location = context.getLocation(element);
- context.report(ALLOW_BACKUP, element, location,
- "Should explicitly set `android:fullBackupContent` to `true` or `false` "
- + "to opt-in to or out of full app data back-up and restore, or "
- + "alternatively to an `@xml` resource which specifies which "
- + "files to backup");
- } else if (fullBackupNode == null && hasGcmReceiver(element)) {
- Location location = context.getLocation(element);
- context.report(ALLOW_BACKUP, element, location,
- "Should explicitly set `android:fullBackupContent` to avoid backing up "
- + "the GCM device specific regId.");
- }
- } else if (mSeenApplication) {
- if (context.isEnabled(ORDER)) {
- context.report(ORDER, element, context.getLocation(element),
- String.format("`<%1$s>` tag appears after `<application>` tag", tag));
- }
-
- // Don't complain for *every* element following the <application> tag
- mSeenApplication = false;
- }
-
- if (tag.equals(TAG_USES_FEATURE)) {
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (nameNode != null) {
- String name = nameNode.getValue();
- if (!name.isEmpty()) {
- if (mUsesFeatures.contains(name)) {
- String message = String.format(
- "Duplicate declaration of uses-feature `%1$s`", name);
- context.report(DUPLICATE_USES_FEATURE, element,
- context.getLocation(nameNode), message);
- } else {
- mUsesFeatures.add(name);
- }
- }
- }
- }
- }
-
- /**
- * Returns true if the given application element has a receiver with an intent filter
- * action for GCM receive
- */
- private static boolean hasGcmReceiver(@NonNull Element application) {
- NodeList applicationChildren = application.getChildNodes();
- for (int i1 = 0, n1 = applicationChildren.getLength(); i1 < n1; i1++) {
- Node applicationChild = applicationChildren.item(i1);
- if (applicationChild.getNodeType() == Node.ELEMENT_NODE
- && TAG_RECEIVER.equals(applicationChild.getNodeName())) {
- NodeList receiverChildren = applicationChild.getChildNodes();
- for (int i2 = 0, n2 = receiverChildren.getLength(); i2 < n2; i2++) {
- Node receiverChild = receiverChildren.item(i2);
- if (receiverChild.getNodeType() == Node.ELEMENT_NODE
- && TAG_INTENT_FILTER.equals(receiverChild.getNodeName())) {
- NodeList filterChildren = receiverChild.getChildNodes();
- for (int i3 = 0, n3 = filterChildren.getLength(); i3 < n3; i3++) {
- Node filterChild = filterChildren.item(i3);
- if (filterChild.getNodeType() == Node.ELEMENT_NODE
- && NODE_ACTION.equals(filterChild.getNodeName())) {
- Element action = (Element) filterChild;
- String name = action.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if ("com.google.android.c2dm.intent.RECEIVE".equals(name)) {
- return true;
- }
- }
- }
- }
- }
- }
- }
-
- return false;
- }
-
- private static void checkMipmapIcon(@NonNull XmlContext context, @NonNull Element element) {
- Attr attribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_ICON);
- if (attribute == null) {
- return;
- }
- String icon = attribute.getValue();
- if (icon.startsWith(DRAWABLE_PREFIX)) {
- if (TAG_ACTIVITY.equals(element.getTagName()) && !isLaunchableActivity(element)) {
- return;
- }
-
- if (context.isEnabled(MIPMAP)
- // Only complain if this app is skipping some densities
- && context.getProject().getApplicableDensities() != null) {
- context.report(MIPMAP, element, context.getLocation(attribute),
- "Should use `@mipmap` instead of `@drawable` for launcher icons");
- }
- }
- }
-
- @SuppressWarnings("SpellCheckingInspection")
- private static boolean isLaunchableActivity(@NonNull Element element) {
- if (!TAG_ACTIVITY.equals(element.getTagName())) {
- return false;
- }
-
- for (Element child : LintUtils.getChildren(element)) {
- if (child.getTagName().equals(TAG_INTENT_FILTER)) {
- for (Element innerChild : LintUtils.getChildren(child)) {
- if (innerChild.getTagName().equals("category")) { //$NON-NLS-1$
- String categoryString = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
- return "android.intent.category.LAUNCHER".equals(categoryString);
- }
- }
- }
- }
-
- return false;
- }
-
- /** Returns true iff the given manifest file is the main manifest file */
- private static boolean isMainManifest(XmlContext context, File manifestFile) {
- if (!context.getProject().isGradleProject()) {
- // In non-gradle projects, just one manifest per project
- return true;
- }
-
- AndroidProject model = context.getProject().getGradleProjectModel();
- return model == null || manifestFile
- .equals(model.getDefaultConfig().getSourceProvider().getManifestFile());
- }
-
- /**
- * Returns true iff the given manifest file is in a debug-specific source set,
- * or a test source set
- */
- private static boolean isDebugOrTestManifest(
- @NonNull XmlContext context,
- @NonNull File manifestFile) {
- AndroidProject model = context.getProject().getGradleProjectModel();
- if (model != null) {
- // Quickly check if it's the main manifest first; that's the most likely scenario
- if (manifestFile.equals(model.getDefaultConfig().getSourceProvider().getManifestFile())) {
- return false;
- }
-
- // Debug build type?
- for (BuildTypeContainer container : model.getBuildTypes()) {
- if (container.getBuildType().isDebuggable()) {
- if (manifestFile.equals(container.getSourceProvider().getManifestFile())) {
- return true;
- }
- }
- }
-
- // Test source set?
- for (ProductFlavorContainer container : model.getProductFlavors()) {
- for (SourceProviderContainer extra : container.getExtraSourceProviders()) {
- String artifactName = extra.getArtifactName();
- if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)
- && manifestFile.equals(extra.getSourceProvider().getManifestFile())) {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- private static void checkDeviceAdmin(XmlContext context, Element element) {
- List<Element> children = LintUtils.getChildren(element);
- boolean requiredIntentFilterFound = false;
- boolean deviceAdmin = false;
- Attr locationNode = null;
- for (Element child : children) {
- String tagName = child.getTagName();
- if (tagName.equals(TAG_INTENT_FILTER) && !requiredIntentFilterFound) {
- boolean dataFound = false;
- boolean actionFound = false;
- for (Element filterChild : LintUtils.getChildren(child)) {
- String filterTag = filterChild.getTagName();
- if (filterTag.equals(NODE_ACTION)) {
- String name = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if ("android.app.action.DEVICE_ADMIN_ENABLED".equals(name)) { //$NON-NLS-1$
- actionFound = true;
- }
- } else if (filterTag.equals(NODE_DATA)) {
- dataFound = true;
- }
- }
- if (actionFound && !dataFound) {
- requiredIntentFilterFound = true;
- }
- } else if (tagName.equals(NODE_METADATA)) {
- Attr valueNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (valueNode != null) {
- String name = valueNode.getValue();
- if ("android.app.device_admin".equals(name)) { //$NON-NLS-1$
- deviceAdmin = true;
- locationNode = valueNode;
- }
- }
- }
- }
-
- if (deviceAdmin && !requiredIntentFilterFound && context.isEnabled(DEVICE_ADMIN)) {
- context.report(DEVICE_ADMIN, locationNode, context.getLocation(locationNode),
- "You must have an intent filter for action "
- + "`android.app.action.DEVICE_ADMIN_ENABLED`");
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
deleted file mode 100644
index e9bb3a5..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Looks for usages of {@link java.lang.Math} methods which can be replaced with
- * {@code android.util.FloatMath} methods to avoid casting.
- */
-public class MathDetector extends Detector implements Detector.ClassScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "FloatMath", //$NON-NLS-1$
- "Using `FloatMath` instead of `Math`",
-
- "In older versions of Android, using `android.util.FloatMath` was recommended " +
- "for performance reasons when operating on floats. However, on modern hardware " +
- "doubles are just as fast as float (though they take more memory), and in " +
- "recent versions of Android, `FloatMath` is actually slower than using `java.lang.Math` " +
- "due to the way the JIT optimizes `java.lang.Math`. Therefore, you should use " +
- "`Math` instead of `FloatMath` if you are only targeting Froyo and above.",
-
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- new Implementation(
- MathDetector.class,
- Scope.CLASS_FILE_SCOPE))
- .addMoreInfo(
- "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
-
- /** Constructs a new {@link MathDetector} check */
- public MathDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(
- "sin", //$NON-NLS-1$
- "cos", //$NON-NLS-1$
- "ceil", //$NON-NLS-1$
- "sqrt", //$NON-NLS-1$
- "floor" //$NON-NLS-1$
- );
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String owner = call.owner;
-
- if (owner.equals("android/util/FloatMath") //$NON-NLS-1$
- && context.getProject().getMinSdk() >= 8) {
- String message = String.format(
- "Use `java.lang.Math#%1$s` instead of `android.util.FloatMath#%1$s()` " +
- "since it is faster as of API 8", call.name);
- context.report(ISSUE, method, call, context.getLocation(call), message /*data*/);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
deleted file mode 100644
index 0ad7a7e..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ATTR_FRAGMENT;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_HEADER;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.VIEW_FRAGMENT;
-import static com.android.SdkConstants.VIEW_TAG;
-import static com.android.resources.ResourceFolderType.LAYOUT;
-import static com.android.resources.ResourceFolderType.VALUES;
-import static com.android.resources.ResourceFolderType.XML;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.SdkUtils;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks to ensure that classes referenced in the manifest actually exist and are included
- *
- */
-public class MissingClassDetector extends LayoutDetector implements ClassScanner {
- /** Manifest-referenced classes missing from the project or libraries */
- public static final Issue MISSING = Issue.create(
- "MissingRegistered", //$NON-NLS-1$
- "Missing registered class",
-
- "If a class is referenced in the manifest, it must also exist in the project (or in one " +
- "of the libraries included by the project. This check helps uncover typos in " +
- "registration names, or attempts to rename or move classes without updating the " +
- "manifest file properly.",
-
- Category.CORRECTNESS,
- 8,
- Severity.ERROR,
- new Implementation(
- MissingClassDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE,
- Scope.JAVA_LIBRARIES, Scope.RESOURCE_FILE)))
- .addMoreInfo("http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
-
- /** Are activity, service, receiver etc subclasses instantiatable? */
- public static final Issue INSTANTIATABLE = Issue.create(
- "Instantiatable", //$NON-NLS-1$
- "Registered class is not instantiatable",
-
- "Activities, services, broadcast receivers etc. registered in the manifest file " +
- "must be \"instantiatable\" by the system, which means that the class must be " +
- "public, it must have an empty public constructor, and if it's an inner class, " +
- "it must be a static inner class.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- new Implementation(
- MissingClassDetector.class,
- Scope.CLASS_FILE_SCOPE));
-
- /** Is the right character used for inner class separators? */
- public static final Issue INNERCLASS = Issue.create(
- "InnerclassSeparator", //$NON-NLS-1$
- "Inner classes should use `$` rather than `.`",
-
- "When you reference an inner class in a manifest file, you must use '$' instead of '.' " +
- "as the separator character, i.e. Outer$Inner instead of Outer.Inner.\n" +
- "\n" +
- "(If you get this warning for a class which is not actually an inner class, it's " +
- "because you are using uppercase characters in your package name, which is not " +
- "conventional.)",
-
- Category.CORRECTNESS,
- 3,
- Severity.WARNING,
- new Implementation(
- MissingClassDetector.class,
- Scope.MANIFEST_SCOPE));
-
- private Map<String, Location.Handle> mReferencedClasses;
- private Set<String> mCustomViews;
- private boolean mHaveClasses;
-
- /** Constructs a new {@link MissingClassDetector} */
- public MissingClassDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return ALL;
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == VALUES || folderType == LAYOUT || folderType == XML;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String pkg = null;
- Node classNameNode;
- String className;
- String tag = element.getTagName();
- ResourceFolderType folderType = context.getResourceFolderType();
- if (folderType == VALUES) {
- if (!tag.equals(TAG_STRING)) {
- return;
- }
- Attr attr = element.getAttributeNode(ATTR_NAME);
- if (attr == null) {
- return;
- }
- className = attr.getValue();
- classNameNode = attr;
- } else if (folderType == LAYOUT) {
- if (tag.indexOf('.') > 0) {
- className = tag;
- classNameNode = element;
- } else if (tag.equals(VIEW_FRAGMENT) || tag.equals(VIEW_TAG)) {
- Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (attr == null) {
- attr = element.getAttributeNode(ATTR_CLASS);
- }
- if (attr == null) {
- return;
- }
- className = attr.getValue();
- classNameNode = attr;
- } else {
- return;
- }
- } else if (folderType == XML) {
- if (!tag.equals(TAG_HEADER)) {
- return;
- }
- Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_FRAGMENT);
- if (attr == null) {
- return;
- }
- className = attr.getValue();
- classNameNode = attr;
- } else {
- // Manifest file
- if (TAG_APPLICATION.equals(tag)
- || TAG_ACTIVITY.equals(tag)
- || TAG_SERVICE.equals(tag)
- || TAG_RECEIVER.equals(tag)
- || TAG_PROVIDER.equals(tag)) {
- Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (attr == null) {
- return;
- }
- className = attr.getValue();
- classNameNode = attr;
- pkg = context.getMainProject().getPackage();
- } else {
- return;
- }
- }
- if (className.isEmpty()) {
- return;
- }
-
- String fqcn;
- int dotIndex = className.indexOf('.');
- if (dotIndex <= 0) {
- if (pkg == null) {
- return; // value file
- }
- if (dotIndex == 0) {
- fqcn = pkg + className;
- } else {
- // According to the <activity> manifest element documentation, this is not
- // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
- // but it appears in manifest files and appears to be supported by the runtime
- // so handle this in code as well:
- fqcn = pkg + '.' + className;
- }
- } else { // else: the class name is already a fully qualified class name
- fqcn = className;
- // Only look for fully qualified tracker names in analytics files
- if (folderType == VALUES
- && !SdkUtils.endsWith(context.file.getPath(), "analytics.xml")) { //$NON-NLS-1$
- return;
- }
- }
-
- String signature = ClassContext.getInternalName(fqcn);
- if (signature.isEmpty() || signature.startsWith(ANDROID_PKG_PREFIX)) {
- return;
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- Handle handle = null;
- if (!context.getDriver().isSuppressed(context, MISSING, element)) {
- if (mReferencedClasses == null) {
- mReferencedClasses = Maps.newHashMapWithExpectedSize(16);
- mCustomViews = Sets.newHashSetWithExpectedSize(8);
- }
-
- handle = context.createLocationHandle(element);
- mReferencedClasses.put(signature, handle);
- if (folderType == LAYOUT && !tag.equals(VIEW_FRAGMENT)) {
- mCustomViews.add(ClassContext.getInternalName(className));
- }
- }
-
- if (signature.indexOf('$') != -1) {
- checkInnerClass(context, element, pkg, classNameNode, className);
-
- // The internal name contains a $ which means it's an inner class.
- // The conversion from fqcn to internal name is a bit ambiguous:
- // "a.b.C.D" usually means "inner class D in class C in package a.b".
- // However, it can (see issue 31592) also mean class D in package "a.b.C".
- // To make sure we don't falsely complain that foo/Bar$Baz doesn't exist,
- // in case the user has actually created a package named foo/Bar and a proper
- // class named Baz, we register *both* into the reference map.
- // When generating errors we'll look for these an rip them back out if
- // it looks like one of the two variations have been seen.
- if (handle != null) {
- // Assume that each successive $ is really a capitalized package name
- // instead. In other words, for A$B$C$D (assumed to be class A with
- // inner classes A.B, A.B.C and A.B.C.D) generate the following possible
- // referenced classes A/B$C$D (class B in package A with inner classes C and C.D),
- // A/B/C$D and A/B/C/D
- while (true) {
- int index = signature.indexOf('$');
- if (index == -1) {
- break;
- }
- signature = signature.substring(0, index) + '/'
- + signature.substring(index + 1);
- mReferencedClasses.put(signature, handle);
- if (folderType == LAYOUT && !tag.equals(VIEW_FRAGMENT)) {
- mCustomViews.add(signature);
- }
- }
- }
- }
- }
-
- private static void checkInnerClass(XmlContext context, Element element, String pkg,
- Node classNameNode, String className) {
- if (pkg != null && className.indexOf('$') == -1 && className.indexOf('.', 1) > 0) {
- boolean haveUpperCase = false;
- for (int i = 0, n = pkg.length(); i < n; i++) {
- if (Character.isUpperCase(pkg.charAt(i))) {
- haveUpperCase = true;
- break;
- }
- }
- if (!haveUpperCase) {
- String fixed = className.charAt(0) + className.substring(1).replace('.','$');
- String message = "Use '$' instead of '.' for inner classes " +
- "(or use only lowercase letters in package names); replace \"" +
- className + "\" with \"" + fixed + "\"";
- Location location = context.getLocation(classNameNode);
- context.report(INNERCLASS, element, location, message);
- }
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (!context.getProject().isLibrary() && mHaveClasses
- && mReferencedClasses != null && !mReferencedClasses.isEmpty()
- && context.getDriver().getScope().contains(Scope.CLASS_FILE)) {
- List<String> classes = new ArrayList<String>(mReferencedClasses.keySet());
- Collections.sort(classes);
- for (String owner : classes) {
- Location.Handle handle = mReferencedClasses.get(owner);
- String fqcn = ClassContext.getFqcn(owner);
-
- String signature = ClassContext.getInternalName(fqcn);
- if (!signature.equals(owner)) {
- if (!mReferencedClasses.containsKey(signature)) {
- continue;
- }
- } else if (signature.indexOf('$') != -1) {
- signature = signature.replace('$', '/');
- if (!mReferencedClasses.containsKey(signature)) {
- continue;
- }
- }
- mReferencedClasses.remove(owner);
-
- // Ignore usages of platform libraries
- if (owner.startsWith("android/")) { //$NON-NLS-1$
- continue;
- }
-
- String message = String.format(
- "Class referenced in the manifest, `%1$s`, was not found in the " +
- "project or the libraries", fqcn);
- Location location = handle.resolve();
- File parentFile = location.getFile().getParentFile();
- if (parentFile != null) {
- String parent = parentFile.getName();
- ResourceFolderType type = ResourceFolderType.getFolderType(parent);
- if (type == LAYOUT) {
- message = String.format(
- "Class referenced in the layout file, `%1$s`, was not found in "
- + "the project or the libraries", fqcn);
- } else if (type == XML) {
- message = String.format(
- "Class referenced in the preference header file, `%1$s`, was not "
- + "found in the project or the libraries", fqcn);
-
- } else if (type == VALUES) {
- message = String.format(
- "Class referenced in the analytics file, `%1$s`, was not "
- + "found in the project or the libraries", fqcn);
- }
- }
-
- context.report(MISSING, location, message);
- }
- }
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if (!mHaveClasses && !context.isFromClassLibrary()
- && context.getProject() == context.getMainProject()) {
- mHaveClasses = true;
- }
- String curr = classNode.name;
- if (mReferencedClasses != null && mReferencedClasses.containsKey(curr)) {
- boolean isCustomView = mCustomViews.contains(curr);
- removeReferences(curr);
-
- // Ensure that the class is public, non static and has a null constructor!
-
- if ((classNode.access & Opcodes.ACC_PUBLIC) == 0) {
- context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
- "This class should be public (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)));
- return;
- }
-
- if (classNode.name.indexOf('$') != -1 && !LintUtils.isStaticInnerClass(classNode)) {
- context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
- "This inner class should be static (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)));
- return;
- }
-
- boolean hasDefaultConstructor = false;
- @SuppressWarnings("rawtypes") // ASM API
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(CONSTRUCTOR_NAME)) {
- if (method.desc.equals("()V")) { //$NON-NLS-1$
- // The constructor must be public
- if ((method.access & Opcodes.ACC_PUBLIC) != 0) {
- hasDefaultConstructor = true;
- } else {
- context.report(INSTANTIATABLE, context.getLocation(method, classNode),
- "The default constructor must be public");
- // Also mark that we have a constructor so we don't complain again
- // below since we've already emitted a more specific error related
- // to the default constructor
- hasDefaultConstructor = true;
- }
- }
- }
- }
-
- if (!hasDefaultConstructor && !isCustomView && !context.isFromClassLibrary()
- && context.getProject().getReportIssues()) {
- context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
- "This class should provide a default constructor (a public " +
- "constructor with no arguments) (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)));
- }
- }
- }
-
- private void removeReferences(String curr) {
- mReferencedClasses.remove(curr);
-
- // Since "A.B.C" is ambiguous whether it's referencing a class in package A.B or
- // an inner class C in package A, we insert multiple possible references when we
- // encounter the A.B.C reference; now that we've seen the actual class we need to
- // remove all the possible permutations we've added such that the permutations
- // don't count as unreferenced classes.
- int index = curr.lastIndexOf('/');
- if (index == -1) {
- return;
- }
- boolean hasCapitalizedPackageName = false;
- for (int i = index - 1; i >= 0; i--) {
- char c = curr.charAt(i);
- if (Character.isUpperCase(c)) {
- hasCapitalizedPackageName = true;
- break;
- }
- }
- if (!hasCapitalizedPackageName) {
- // No path ambiguity
- return;
- }
-
- while (true) {
- index = curr.lastIndexOf('/');
- if (index == -1) {
- break;
- }
- curr = curr.substring(0, index) + '$' + curr.substring(index + 1);
- mReferencedClasses.remove(curr);
- }
- }
-
- /**
- * Given an error message produced by this lint detector for the given issue type,
- * returns the old value to be replaced in the source code.
- * <p>
- * Intended for IDE quickfix implementations.
- *
- * @param issue the corresponding issue
- * @param errorMessage the error message associated with the error
- * @param format the format of the error message
- * @return the corresponding old value, or null if not recognized
- */
- @Nullable
- public static String getOldValue(@NonNull Issue issue, @NonNull String errorMessage,
- @NonNull TextFormat format) {
- if (issue == INNERCLASS) {
- errorMessage = format.toText(errorMessage);
- return LintUtils.findSubstring(errorMessage, " replace \"", "\"");
- }
-
- return null;
- }
-
- /**
- * Given an error message produced by this lint detector for the given issue type,
- * returns the new value to be put into the source code.
- * <p>
- * Intended for IDE quickfix implementations.
- *
- * @param issue the corresponding issue
- * @param errorMessage the error message associated with the error
- * @param format the format of the error message
- * @return the corresponding new value, or null if not recognized
- */
- @Nullable
- public static String getNewValue(@NonNull Issue issue, @NonNull String errorMessage,
- @NonNull TextFormat format) {
- if (issue == INNERCLASS) {
- errorMessage = format.toText(errorMessage);
- return LintUtils.findSubstring(errorMessage, " with \"", "\"");
- }
- return null;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
deleted file mode 100644
index 1fe3afb..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedField;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-
-import org.objectweb.asm.Opcodes;
-
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Node;
-import lombok.ast.TypeReference;
-
-/**
- * Looks for Parcelable classes that are missing a CREATOR field
- */
-public class ParcelDetector extends Detector implements Detector.JavaScanner {
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ParcelCreator", //$NON-NLS-1$
- "Missing Parcelable `CREATOR` field",
-
- "According to the `Parcelable` interface documentation, " +
- "\"Classes implementing the Parcelable interface must also have a " +
- "static field called `CREATOR`, which is an object implementing the " +
- "`Parcelable.Creator` interface.",
-
- Category.USABILITY,
- 3,
- Severity.ERROR,
- new Implementation(
- ParcelDetector.class,
- Scope.JAVA_FILE_SCOPE))
- .addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html");
-
- /** Constructs a new {@link com.android.tools.lint.checks.ParcelDetector} check */
- public ParcelDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Nullable
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>>singletonList(ClassDeclaration.class);
- }
-
- @Nullable
- @Override
- public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
- return new ParcelVisitor(context);
- }
-
- private static class ParcelVisitor extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public ParcelVisitor(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- // Only applies to concrete classes
- int flags = node.astModifiers().getExplicitModifierFlags();
- if ((flags & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0) {
- return true;
- }
-
- if (node.astImplementing() != null)
- for (TypeReference reference : node.astImplementing()) {
- String name = reference.astParts().last().astIdentifier().astValue();
- if (name.equals("Parcelable")) {
- JavaParser.ResolvedNode resolved = mContext.resolve(node);
- if (resolved instanceof ResolvedClass) {
- ResolvedClass cls = (ResolvedClass) resolved;
- ResolvedField field = cls.getField("CREATOR", false);
- if (field == null) {
- // Make doubly sure that we're really implementing
- // android.os.Parcelable
- JavaParser.ResolvedNode r = mContext.resolve(reference);
- if (r instanceof ResolvedClass) {
- ResolvedClass parcelable = (ResolvedClass) r;
- if (!parcelable.isSubclassOf("android.os.Parcelable", false)) {
- return true;
- }
- }
- Location location = mContext.getLocation(node.astName());
- mContext.report(ISSUE, node, location,
- "This class implements `Parcelable` but does not "
- + "provide a `CREATOR` field");
- }
- }
- break;
- }
- }
-
- return true;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java
deleted file mode 100644
index 6494e78..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CLASS_INTENT;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_READ;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_WRITE;
-import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.client.api.JavaParser.ResolvedField;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.detector.api.JavaContext;
-
-import java.util.ListIterator;
-
-import lombok.ast.BinaryExpression;
-import lombok.ast.BinaryOperator;
-import lombok.ast.Cast;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.Node;
-import lombok.ast.NullLiteral;
-import lombok.ast.Select;
-import lombok.ast.Statement;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Utility for locating permissions required by an intent or content resolver
- */
-public class PermissionFinder {
- /**
- * Operation that has a permission requirement -- such as a method call,
- * a content resolver read or write operation, an intent, etc.
- */
- public enum Operation {
- CALL, ACTION, READ, WRITE;
-
- /** Prefix to use when describing a name with a permission requirement */
- public String prefix() {
- switch (this) {
- case ACTION:
- return "by intent";
- case READ:
- return "to read";
- case WRITE:
- return "to write";
- case CALL:
- default:
- return "by";
- }
- }
- }
-
- /** A permission requirement given a name and operation */
- public static class Result {
- @NonNull public final PermissionRequirement requirement;
- @NonNull public final String name;
- @NonNull public final Operation operation;
-
- public Result(
- @NonNull Operation operation,
- @NonNull PermissionRequirement requirement,
- @NonNull String name) {
- this.operation = operation;
- this.requirement = requirement;
- this.name = name;
- }
- }
-
- /**
- * Searches for a permission requirement for the given parameter in the given call
- *
- * @param operation the operation to look up
- * @param context the context to use for lookup
- * @param parameter the parameter which contains the value which implies the permission
- * @return the result with the permission requirement, or null if nothing is found
- */
- @Nullable
- public static Result findRequiredPermissions(
- @NonNull Operation operation,
- @NonNull JavaContext context,
- @NonNull Node parameter) {
-
- // To find the permission required by an intent, we proceed in 3 steps:
- // (1) Locate the parameter in the start call that corresponds to
- // the Intent
- //
- // (2) Find the place where the intent is initialized, and figure
- // out the action name being passed to it.
- //
- // (3) Find the place where the action is defined, and look for permission
- // annotations on that action declaration!
-
- return new PermissionFinder(context, operation).search(parameter);
- }
-
- private PermissionFinder(@NonNull JavaContext context, @NonNull Operation operation) {
- mContext = context;
- mOperation = operation;
- }
-
- @NonNull private final JavaContext mContext;
- @NonNull private final Operation mOperation;
-
- @Nullable
- public Result search(@NonNull Node node) {
- if (node instanceof NullLiteral) {
- return null;
- } else if (node instanceof InlineIfExpression) {
- InlineIfExpression expression = (InlineIfExpression) node;
- if (expression.astIfTrue() != null) {
- Result result = search(expression.astIfTrue());
- if (result != null) {
- return result;
- }
- }
- if (expression.astIfFalse() != null) {
- Result result = search(expression.astIfFalse());
- if (result != null) {
- return result;
- }
- }
- } else if (node instanceof Cast) {
- Cast cast = (Cast) node;
- return search(cast.astOperand());
- } else if (node instanceof ConstructorInvocation && mOperation == Operation.ACTION) {
- // Identifies "new Intent(argument)" calls and, if found, continues
- // resolving the argument instead looking for the action definition
- ConstructorInvocation call = (ConstructorInvocation) node;
- String type = call.astTypeReference().getTypeName();
- if (type.equals("Intent") || type.equals(CLASS_INTENT)) {
- Expression action = call.astArguments().first();
- if (action != null) {
- return search(action);
- }
- }
- return null;
- } else if ((node instanceof VariableReference || node instanceof Select)) {
- ResolvedNode resolved = mContext.resolve(node);
- if (resolved instanceof ResolvedField) {
- ResolvedField field = (ResolvedField) resolved;
- if (mOperation == Operation.ACTION) {
- ResolvedAnnotation annotation = field.getAnnotation(PERMISSION_ANNOTATION);
- if (annotation != null) {
- return getPermissionRequirement(field, annotation);
- }
- } else if (mOperation == Operation.READ || mOperation == Operation.WRITE) {
- String fqn = mOperation == Operation.READ
- ? PERMISSION_ANNOTATION_READ : PERMISSION_ANNOTATION_WRITE;
- ResolvedAnnotation annotation = field.getAnnotation(fqn);
- if (annotation != null) {
- Object o = annotation.getValue();
- if (o instanceof ResolvedAnnotation) {
- annotation = (ResolvedAnnotation) o;
- if (annotation.matches(PERMISSION_ANNOTATION)) {
- return getPermissionRequirement(field, annotation);
- }
- } else {
- // The complex annotations used for read/write cannot be
- // expressed in the external annotations format, so they're inlined.
- // (See Extractor.AnnotationData#write).
- //
- // Instead we've inlined the fields of the annotation on the
- // outer one:
- return getPermissionRequirement(field, annotation);
- }
- }
- } else {
- assert false : mOperation;
- }
- } else if (node instanceof VariableReference) {
- Statement statement = getParentOfType(node, Statement.class, false);
- if (statement != null) {
- ListIterator<Node> iterator =
- statement.getParent().getChildren().listIterator();
- while (iterator.hasNext()) {
- if (iterator.next() == statement) {
- if (iterator.hasPrevious()) { // should always be true
- iterator.previous();
- }
- break;
- }
- }
-
- String targetName = ((VariableReference)node).astIdentifier().astValue();
- while (iterator.hasPrevious()) {
- Node previous = iterator.previous();
- if (previous instanceof VariableDeclaration) {
- VariableDeclaration declaration = (VariableDeclaration) previous;
- VariableDefinition definition = declaration.astDefinition();
- for (VariableDefinitionEntry entry : definition
- .astVariables()) {
- if (entry.astInitializer() != null
- && entry.astName().astValue().equals(targetName)) {
- return search(entry.astInitializer());
- }
- }
- } else if (previous instanceof ExpressionStatement) {
- ExpressionStatement expressionStatement =
- (ExpressionStatement) previous;
- Expression expression = expressionStatement.astExpression();
- if (expression instanceof BinaryExpression &&
- ((BinaryExpression) expression).astOperator()
- == BinaryOperator.ASSIGN) {
- BinaryExpression binaryExpression = (BinaryExpression) expression;
- if (targetName.equals(binaryExpression.astLeft().toString())) {
- return search(binaryExpression.astRight());
- }
- }
- }
- }
- }
- }
- }
-
- return null;
- }
-
- @NonNull
- private Result getPermissionRequirement(
- @NonNull ResolvedField field,
- @NonNull ResolvedAnnotation annotation) {
- PermissionRequirement requirement = PermissionRequirement.create(mContext, annotation);
- String name = field.getContainingClass().getSimpleName() + "." + field.getName();
- return new Result(mOperation, requirement, name);
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java
deleted file mode 100644
index 8923fcb..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java
+++ /dev/null
@@ -1,763 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-
-import static com.android.SdkConstants.ATTR_VALUE;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.ATTR_ALL_OF;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.ATTR_ANY_OF;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.ATTR_CONDITIONAL;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.sdklib.AndroidVersion;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
-
-import lombok.ast.BinaryExpression;
-import lombok.ast.BinaryOperator;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.VariableDefinitionEntry;
-
-/**
- * A permission requirement is a boolean expression of permission names that a
- * caller must satisfy for a given Android API.
- */
-public abstract class PermissionRequirement {
- public static final String ATTR_PROTECTION_LEVEL = "protectionLevel"; //$NON-NLS-1$
- public static final String VALUE_DANGEROUS = "dangerous"; //$NON-NLS-1$
-
- protected final ResolvedAnnotation annotation;
- private int firstApi;
- private int lastApi;
-
- @SuppressWarnings("ConstantConditions")
- public static final PermissionRequirement NONE = new PermissionRequirement(null) {
- @Override
- public boolean isSatisfied(@NonNull PermissionHolder available) {
- return true;
- }
-
- @Override
- public boolean appliesTo(@NonNull PermissionHolder available) {
- return false;
- }
-
- @Override
- public boolean isConditional() {
- return false;
- }
-
- @Override
- public boolean isRevocable(@NonNull PermissionHolder revocable) {
- return false;
- }
-
- @Override
- public String toString() {
- return "None";
- }
-
- @Override
- protected void addMissingPermissions(@NonNull PermissionHolder available,
- @NonNull Set<String> result) {
- }
-
- @Override
- protected void addRevocablePermissions(@NonNull Set<String> result,
- @NonNull PermissionHolder revocable) {
- }
-
- @Nullable
- @Override
- public BinaryOperator getOperator() {
- return null;
- }
-
- @NonNull
- @Override
- public Iterable<PermissionRequirement> getChildren() {
- return Collections.emptyList();
- }
- };
-
- private PermissionRequirement(@NonNull ResolvedAnnotation annotation) {
- this.annotation = annotation;
- }
-
- @NonNull
- public static PermissionRequirement create(
- @Nullable Context context,
- @NonNull ResolvedAnnotation annotation) {
- String value = (String)annotation.getValue(ATTR_VALUE);
- if (value != null && !value.isEmpty()) {
- for (int i = 0, n = value.length(); i < n; i++) {
- char c = value.charAt(i);
- // See if it's a complex expression and if so build it up
- if (c == '&' || c == '|' || c == '^') {
- return Complex.parse(annotation, context, value);
- }
- }
-
- return new Single(annotation, value);
- }
-
- Object v = annotation.getValue(ATTR_ANY_OF);
- if (v != null) {
- if (v instanceof String[]) {
- String[] anyOf = (String[])v;
- if (anyOf.length > 0) {
- return new Many(annotation, BinaryOperator.LOGICAL_OR, anyOf);
- }
- } else if (v instanceof String) {
- String[] anyOf = new String[] { (String)v };
- return new Many(annotation, BinaryOperator.LOGICAL_OR, anyOf);
- }
- }
-
- v = annotation.getValue(ATTR_ALL_OF);
- if (v != null) {
- if (v instanceof String[]) {
- String[] allOf = (String[])v;
- if (allOf.length > 0) {
- return new Many(annotation, BinaryOperator.LOGICAL_AND, allOf);
- }
- } else if (v instanceof String) {
- String[] allOf = new String[] { (String)v };
- return new Many(annotation, BinaryOperator.LOGICAL_AND, allOf);
- }
- }
-
- return NONE;
- }
-
- /**
- * Returns false if this permission does not apply given the specified minimum and
- * target sdk versions
- *
- * @param minSdkVersion the minimum SDK version
- * @param targetSdkVersion the target SDK version
- * @return true if this permission requirement applies for the given versions
- */
- /**
- * Returns false if this permission does not apply given the specified minimum and target
- * sdk versions
- *
- * @param available the permission holder which also knows the min and target versions
- * @return true if this permission requirement applies for the given versions
- */
- protected boolean appliesTo(@NonNull PermissionHolder available) {
- if (firstApi == 0) { // initialized?
- firstApi = -1; // initialized, not specified
-
- // Not initialized
- Object o = annotation.getValue("apis");
- if (o instanceof String) {
- String range = (String)o;
- // Currently only support the syntax "a..b" where a and b are inclusive end points
- // and where "a" and "b" are optional
- int index = range.indexOf("..");
- if (index != -1) {
- try {
- if (index > 0) {
- firstApi = Integer.parseInt(range.substring(0, index));
- } else {
- firstApi = 1;
- }
- if (index + 2 < range.length()) {
- lastApi = Integer.parseInt(range.substring(index + 2));
- } else {
- lastApi = Integer.MAX_VALUE;
- }
- } catch (NumberFormatException ignore) {
- }
- }
- }
- }
-
- if (firstApi != -1) {
- AndroidVersion minSdkVersion = available.getMinSdkVersion();
- if (minSdkVersion.getFeatureLevel() > lastApi) {
- return false;
- }
-
- AndroidVersion targetSdkVersion = available.getTargetSdkVersion();
- if (targetSdkVersion.getFeatureLevel() < firstApi) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns whether this requirement is conditional, meaning that there are
- * some circumstances in which the requirement is not necessary. For
- * example, consider
- * {@code android.app.backup.BackupManager.dataChanged(java.lang.String)} .
- * Here the {@code android.permission.BACKUP} is required but only if the
- * argument is not your own package.
- * <p>
- * This is used to handle permissions differently between the "missing" and
- * "unused" checks. When checking for missing permissions, we err on the
- * side of caution: if you are missing a permission, but the permission is
- * conditional, you may not need it so we may not want to complain. However,
- * when looking for unused permissions, we don't want to flag the
- * conditional permissions as unused since they may be required.
- *
- * @return true if this requirement is conditional
- */
- public boolean isConditional() {
- Object o = annotation.getValue(ATTR_CONDITIONAL);
- if (o instanceof Boolean) {
- return (Boolean)o;
- }
- return false;
- }
-
- /**
- * Returns whether this requirement is for a single permission (rather than
- * a boolean expression such as one permission or another.)
- *
- * @return true if this requirement is just a simple permission name
- */
- public boolean isSingle() {
- return true;
- }
-
- /**
- * Whether the permission requirement is satisfied given the set of granted permissions
- *
- * @param available the available permissions
- * @return true if all permissions specified by this requirement are available
- */
- public abstract boolean isSatisfied(@NonNull PermissionHolder available);
-
- /** Describes the missing permissions (e.g. "P1, P2 and P3") */
- public String describeMissingPermissions(@NonNull PermissionHolder available) {
- return "";
- }
-
- /** Returns the missing permissions (e.g. {"P1", "P2", "P3"} */
- public Set<String> getMissingPermissions(@NonNull PermissionHolder available) {
- Set<String> result = Sets.newHashSet();
- addMissingPermissions(available, result);
- return result;
- }
-
- protected abstract void addMissingPermissions(@NonNull PermissionHolder available,
- @NonNull Set<String> result);
-
- /** Returns the permissions in the requirement that are revocable */
- public Set<String> getRevocablePermissions(@NonNull PermissionHolder revocable) {
- Set<String> result = Sets.newHashSet();
- addRevocablePermissions(result, revocable);
- return result;
- }
-
- protected abstract void addRevocablePermissions(@NonNull Set<String> result,
- @NonNull PermissionHolder revocable);
-
- /**
- * Returns whether this permission is revocable
- *
- * @param revocable the set of revocable permissions
- * @return true if a user can revoke the permission
- */
- public abstract boolean isRevocable(@NonNull PermissionHolder revocable);
-
- /**
- * For permission requirements that combine children, the operator to combine them with; null
- * for leaf nodes
- */
- @Nullable
- public abstract BinaryOperator getOperator();
-
- /**
- * Returns nested requirements, combined via {@link #getOperator()}
- */
- @NonNull
- public abstract Iterable<PermissionRequirement> getChildren();
-
- /** Require a single permission */
- private static class Single extends PermissionRequirement {
- public final String name;
-
- public Single(@NonNull ResolvedAnnotation annotation, @NonNull String name) {
- super(annotation);
- this.name = name;
- }
-
- @Override
- public boolean isRevocable(@NonNull PermissionHolder revocable) {
- return revocable.isRevocable(name) || isRevocableSystemPermission(name);
- }
-
- @Nullable
- @Override
- public BinaryOperator getOperator() {
- return null;
- }
-
- @NonNull
- @Override
- public Iterable<PermissionRequirement> getChildren() {
- return Collections.emptyList();
- }
-
- @Override
- public boolean isSingle() {
- return true;
- }
-
- @Override
- public String toString() {
- return name;
- }
-
- @Override
- public boolean isSatisfied(@NonNull PermissionHolder available) {
- return available.hasPermission(name) || !appliesTo(available);
- }
-
- @Override
- public String describeMissingPermissions(@NonNull PermissionHolder available) {
- return isSatisfied(available) ? "" : name;
- }
-
- @Override
- protected void addMissingPermissions(@NonNull PermissionHolder available,
- @NonNull Set<String> missing) {
- if (!isSatisfied(available)) {
- missing.add(name);
- }
- }
-
- @Override
- protected void addRevocablePermissions(@NonNull Set<String> result,
- @NonNull PermissionHolder revocable) {
- if (isRevocable(revocable)) {
- result.add(name);
- }
- }
- }
-
- protected static void appendOperator(StringBuilder sb, BinaryOperator operator) {
- sb.append(' ');
- if (operator == BinaryOperator.LOGICAL_AND) {
- sb.append("and");
- } else if (operator == BinaryOperator.LOGICAL_OR) {
- sb.append("or");
- } else {
- assert operator == BinaryOperator.BITWISE_XOR : operator;
- sb.append("xor");
- }
- sb.append(' ');
- }
-
- /**
- * Require a series of permissions, all with the same operator.
- */
- private static class Many extends PermissionRequirement {
- public final BinaryOperator operator;
- public final List<PermissionRequirement> permissions;
-
- public Many(
- @NonNull ResolvedAnnotation annotation,
- BinaryOperator operator,
- String[] names) {
- super(annotation);
- assert operator == BinaryOperator.LOGICAL_OR
- || operator == BinaryOperator.LOGICAL_AND : operator;
- assert names.length >= 2;
- this.operator = operator;
- this.permissions = Lists.newArrayListWithExpectedSize(names.length);
- for (String name : names) {
- permissions.add(new Single(annotation, name));
- }
- }
-
- @Override
- public boolean isSingle() {
- return false;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
-
- sb.append(permissions.get(0));
-
- for (int i = 1; i < permissions.size(); i++) {
- appendOperator(sb, operator);
- sb.append(permissions.get(i));
- }
-
- return sb.toString();
- }
-
- @Override
- public boolean isSatisfied(@NonNull PermissionHolder available) {
- if (operator == BinaryOperator.LOGICAL_AND) {
- for (PermissionRequirement requirement : permissions) {
- if (!requirement.isSatisfied(available) && requirement.appliesTo(available)) {
- return false;
- }
- }
- return true;
- } else {
- assert operator == BinaryOperator.LOGICAL_OR : operator;
- for (PermissionRequirement requirement : permissions) {
- if (requirement.isSatisfied(available) || !requirement.appliesTo(available)) {
- return true;
- }
- }
- return false;
- }
- }
-
- @Override
- public String describeMissingPermissions(@NonNull PermissionHolder available) {
- StringBuilder sb = new StringBuilder();
- boolean first = true;
- for (PermissionRequirement requirement : permissions) {
- if (!requirement.isSatisfied(available)) {
- if (first) {
- first = false;
- } else {
- appendOperator(sb, operator);
- }
- sb.append(requirement.describeMissingPermissions(available));
- }
- }
- return sb.toString();
- }
-
- @Override
- protected void addMissingPermissions(@NonNull PermissionHolder available,
- @NonNull Set<String> missing) {
- for (PermissionRequirement requirement : permissions) {
- if (!requirement.isSatisfied(available)) {
- requirement.addMissingPermissions(available, missing);
- }
- }
- }
-
- @Override
- protected void addRevocablePermissions(@NonNull Set<String> result,
- @NonNull PermissionHolder revocable) {
- for (PermissionRequirement requirement : permissions) {
- requirement.addRevocablePermissions(result, revocable);
- }
- }
-
- @Override
- public boolean isRevocable(@NonNull PermissionHolder revocable) {
- // TODO: Pass in the available set of permissions here, and if
- // the operator is BinaryOperator.LOGICAL_OR, only return revocable=true
- // if an unsatisfied permission is also revocable. In other words,
- // if multiple permissions are allowed, and some of them are satisfied and
- // not revocable the overall permission requirement is not revocable.
- for (PermissionRequirement requirement : permissions) {
- if (requirement.isRevocable(revocable)) {
- return true;
- }
- }
- return false;
- }
-
- @Nullable
- @Override
- public BinaryOperator getOperator() {
- return operator;
- }
-
- @NonNull
- @Override
- public Iterable<PermissionRequirement> getChildren() {
- return permissions;
- }
- }
-
- /**
- * Require multiple permissions. This is a group of permissions with some
- * associated boolean logic, such as "B or (C and (D or E))".
- */
- private static class Complex extends PermissionRequirement {
- public final BinaryOperator operator;
- public final PermissionRequirement left;
- public final PermissionRequirement right;
-
- public Complex(
- @NonNull ResolvedAnnotation annotation,
- BinaryOperator operator,
- PermissionRequirement left,
- PermissionRequirement right) {
- super(annotation);
- this.operator = operator;
- this.left = left;
- this.right = right;
- }
-
- @Override
- public boolean isSingle() {
- return false;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
-
- boolean needsParentheses = left instanceof Complex &&
- ((Complex) left).operator != BinaryOperator.LOGICAL_AND;
- if (needsParentheses) {
- sb.append('(');
- }
- sb.append(left.toString());
- if (needsParentheses) {
- sb.append(')');
- }
-
- appendOperator(sb, operator);
-
- needsParentheses = right instanceof Complex &&
- ((Complex) right).operator != BinaryOperator.LOGICAL_AND;
- if (needsParentheses) {
- sb.append('(');
- }
- sb.append(right.toString());
- if (needsParentheses) {
- sb.append(')');
- }
-
- return sb.toString();
- }
-
- @Override
- public boolean isSatisfied(@NonNull PermissionHolder available) {
- boolean satisfiedLeft = left.isSatisfied(available) || !left.appliesTo(available);
- boolean satisfiedRight = right.isSatisfied(available) || !right.appliesTo(available);
- if (operator == BinaryOperator.LOGICAL_AND) {
- return satisfiedLeft && satisfiedRight;
- } else if (operator == BinaryOperator.LOGICAL_OR) {
- return satisfiedLeft || satisfiedRight;
- } else {
- assert operator == BinaryOperator.BITWISE_XOR : operator;
- return satisfiedLeft ^ satisfiedRight;
- }
- }
-
- @Override
- public String describeMissingPermissions(@NonNull PermissionHolder available) {
- boolean satisfiedLeft = left.isSatisfied(available);
- boolean satisfiedRight = right.isSatisfied(available);
- if (operator == BinaryOperator.LOGICAL_AND || operator == BinaryOperator.LOGICAL_OR) {
- if (satisfiedLeft) {
- if (satisfiedRight) {
- return "";
- }
- return right.describeMissingPermissions(available);
- } else if (satisfiedRight) {
- return left.describeMissingPermissions(available);
- } else {
- StringBuilder sb = new StringBuilder();
- sb.append(left.describeMissingPermissions(available));
- appendOperator(sb, operator);
- sb.append(right.describeMissingPermissions(available));
- return sb.toString();
- }
- } else {
- assert operator == BinaryOperator.BITWISE_XOR : operator;
- return toString();
- }
- }
-
- @Override
- protected void addMissingPermissions(@NonNull PermissionHolder available,
- @NonNull Set<String> missing) {
- boolean satisfiedLeft = left.isSatisfied(available);
- boolean satisfiedRight = right.isSatisfied(available);
- if (operator == BinaryOperator.LOGICAL_AND || operator == BinaryOperator.LOGICAL_OR) {
- if (satisfiedLeft) {
- if (satisfiedRight) {
- return;
- }
- right.addMissingPermissions(available, missing);
- } else if (satisfiedRight) {
- left.addMissingPermissions(available, missing);
- } else {
- left.addMissingPermissions(available, missing);
- right.addMissingPermissions(available, missing);
- }
- } else {
- assert operator == BinaryOperator.BITWISE_XOR : operator;
- left.addMissingPermissions(available, missing);
- right.addMissingPermissions(available, missing);
- }
- }
-
- @Override
- protected void addRevocablePermissions(@NonNull Set<String> result,
- @NonNull PermissionHolder revocable) {
- left.addRevocablePermissions(result, revocable);
- right.addRevocablePermissions(result, revocable);
- }
-
- @Override
- public boolean isRevocable(@NonNull PermissionHolder revocable) {
- // TODO: If operator == BinaryOperator.LOGICAL_OR only return
- // revocable the there isn't a non-revocable term which is also satisfied.
- return left.isRevocable(revocable) || right.isRevocable(revocable);
- }
-
- @NonNull
- public static PermissionRequirement parse(@NonNull ResolvedAnnotation annotation,
- @Nullable Context context, @NonNull final String value) {
- // Parse an expression of the form (A op1 B op2 C) op3 (D op4 E) etc.
- // We'll just use the Java parser to handle this to ensure that operator
- // precedence etc is correct.
- if (context == null) {
- return NONE;
- }
- JavaParser javaParser = context.getClient().getJavaParser(null);
- if (javaParser == null) {
- return NONE;
- }
- try {
- JavaContext javaContext = new JavaContext(context.getDriver(),
- context.getProject(), context.getMainProject(), context.file,
- javaParser) {
- @Nullable
- @Override
- public String getContents() {
- return ""
- + "class Test { void test() {\n"
- + "boolean result=" + value
- + ";\n}\n}";
- }
- };
- Node node = javaParser.parseJava(javaContext);
- if (node != null) {
- final AtomicReference<Expression> reference = new AtomicReference<Expression>();
- node.accept(new ForwardingAstVisitor() {
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- reference.set(node.astInitializer());
- return true;
- }
- });
- Expression expression = reference.get();
- if (expression != null) {
- return parse(annotation, expression);
- }
- }
-
- return NONE;
- } finally {
- javaParser.dispose();
- }
- }
-
- private static PermissionRequirement parse(
- @NonNull ResolvedAnnotation annotation,
- @NonNull Expression expression) {
- if (expression instanceof Select) {
- return new Single(annotation, expression.toString());
- } else if (expression instanceof BinaryExpression) {
- BinaryExpression binaryExpression = (BinaryExpression) expression;
- BinaryOperator operator = binaryExpression.astOperator();
- if (operator == BinaryOperator.LOGICAL_AND
- || operator == BinaryOperator.LOGICAL_OR
- || operator == BinaryOperator.BITWISE_XOR) {
- PermissionRequirement left = parse(annotation, binaryExpression.astLeft());
- PermissionRequirement right = parse(annotation, binaryExpression.astRight());
- return new Complex(annotation, operator, left, right);
- }
- }
- return NONE;
- }
-
- @Nullable
- @Override
- public BinaryOperator getOperator() {
- return operator;
- }
-
- @NonNull
- @Override
- public Iterable<PermissionRequirement> getChildren() {
- return Arrays.asList(left, right);
- }
- }
-
- /**
- * Returns true if the given permission name is a revocable permission for
- * targetSdkVersion >= 23
- *
- * @param name permission name
- * @return true if this is a revocable permission
- */
- public static boolean isRevocableSystemPermission(@NonNull String name) {
- return Arrays.binarySearch(REVOCABLE_PERMISSION_NAMES, name) >= 0;
- }
-
- @VisibleForTesting
- static final String[] REVOCABLE_PERMISSION_NAMES = new String[] {
- "android.permission.ACCESS_COARSE_LOCATION",
- "android.permission.ACCESS_FINE_LOCATION",
- "android.permission.BODY_SENSORS",
- "android.permission.CALL_PHONE",
- "android.permission.CAMERA",
- "android.permission.PROCESS_OUTGOING_CALLS",
- "android.permission.READ_CALENDAR",
- "android.permission.READ_CALL_LOG",
- "android.permission.READ_CELL_BROADCASTS",
- "android.permission.READ_CONTACTS",
- "android.permission.READ_EXTERNAL_STORAGE",
- "android.permission.READ_PHONE_STATE",
- "android.permission.READ_PROFILE",
- "android.permission.READ_SMS",
- "android.permission.READ_SOCIAL_STREAM",
- "android.permission.RECEIVE_MMS",
- "android.permission.RECEIVE_SMS",
- "android.permission.RECEIVE_WAP_PUSH",
- "android.permission.RECORD_AUDIO",
- "android.permission.SEND_SMS",
- "android.permission.USE_FINGERPRINT",
- "android.permission.USE_SIP",
- "android.permission.WRITE_CALENDAR",
- "android.permission.WRITE_CALL_LOG",
- "android.permission.WRITE_CONTACTS",
- "android.permission.WRITE_EXTERNAL_STORAGE",
- "android.permission.WRITE_SETTINGS",
- "android.permission.WRITE_PROFILE",
- "android.permission.WRITE_SOCIAL_STREAM",
- "com.android.voicemail.permission.ADD_VOICEMAIL",
- };
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
deleted file mode 100644
index f9d1fc4..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.few;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.many;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.one;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.two;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.zero;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.google.common.collect.Maps;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Database used by the {@link com.android.tools.lint.checks.PluralsDetector} to get information
- * about plural forms for a given language
- */
-public class PluralsDatabase {
- private static final EnumSet<Quantity> NONE = EnumSet.noneOf(Quantity.class);
-
- private static final PluralsDatabase sInstance = new PluralsDatabase();
- private Map<String, EnumSet<Quantity>> mPlurals = Maps.newHashMap();
-
- /** Bit set if this language uses quantity zero */
- @SuppressWarnings("PointlessBitwiseExpression")
- static final int FLAG_ZERO = 1 << 0;
- /** Bit set if this language uses quantity one */
- static final int FLAG_ONE = 1 << 1;
- /** Bit set if this language uses quantity two */
- static final int FLAG_TWO = 1 << 2;
- /** Bit set if this language uses quantity few */
- static final int FLAG_FEW = 1 << 3;
- /** Bit set if this language uses quantity many */
- static final int FLAG_MANY = 1 << 4;
- /** Bit set if this language has multiple values that match quantity zero */
- static final int FLAG_MULTIPLE_ZERO = 1 << 5;
- /** Bit set if this language has multiple values that match quantity one */
- static final int FLAG_MULTIPLE_ONE = 1 << 6;
- /** Bit set if this language has multiple values that match quantity two */
- static final int FLAG_MULTIPLE_TWO = 1 << 7;
-
- @NonNull
- public static PluralsDatabase get() {
- return sInstance;
- }
-
- private static int getFlags(@NonNull String language) {
- int index = getLanguageIndex(language);
- if (index != -1) {
- return FLAGS[index];
- }
- return 0;
- }
-
- private static int getLanguageIndex(@NonNull String language) {
- int index = Arrays.binarySearch(LANGUAGE_CODES, language);
- if (index >= 0) {
- assert LANGUAGE_CODES[index].equals(language);
- return index;
- } else {
- return -1;
- }
- }
-
- @Nullable
- public EnumSet<Quantity> getRelevant(@NonNull String language) {
- EnumSet<Quantity> set = mPlurals.get(language);
- if (set == null) {
- int index = getLanguageIndex(language);
- if (index == -1) {
- mPlurals.put(language, NONE);
- return null;
- }
-
- // Process each item and look for relevance
- int flag = FLAGS[index];
-
- set = EnumSet.noneOf(Quantity.class);
- if ((flag & FLAG_ZERO) != 0) {
- set.add(zero);
- }
- if ((flag & FLAG_ONE) != 0) {
- set.add(one);
- }
- if ((flag & FLAG_TWO) != 0) {
- set.add(two);
- }
- if ((flag & FLAG_FEW) != 0) {
- set.add(few);
- }
- if ((flag & FLAG_MANY) != 0) {
- set.add(many);
- }
-
- mPlurals.put(language, set);
- }
- return set == NONE ? null : set;
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- public boolean hasMultipleValuesForQuantity(
- @NonNull String language,
- @NonNull Quantity quantity) {
- if (quantity == one) {
- return (getFlags(language) & FLAG_MULTIPLE_ONE) != 0;
- } else if (quantity == two) {
- return (getFlags(language) & FLAG_MULTIPLE_TWO) != 0;
- } else {
- return quantity == zero && (getFlags(language) & FLAG_MULTIPLE_ZERO) != 0;
- }
- }
-
- @SuppressWarnings("MethodMayBeStatic")
- @Nullable
- public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) {
- if (quantity == one) {
- return getExampleForQuantityOne(language);
- } else if (quantity == two) {
- return getExampleForQuantityTwo(language);
- } else if (quantity == zero) {
- return getExampleForQuantityZero(language);
- } else {
- return null;
- }
- }
-
- public enum Quantity {
- // deliberately lower case to match attribute names
- few, many, one, two, zero, other;
-
- @Nullable
- public static Quantity get(@NonNull String name) {
- for (Quantity quantity : values()) {
- if (name.equals(quantity.name())) {
- return quantity;
- }
- }
-
- return null;
- }
-
- public static String formatSet(@NonNull EnumSet<Quantity> set) {
- List<String> list = new ArrayList<String>(set.size());
- for (Quantity quantity : set) {
- list.add('`' + quantity.name() + '`');
- }
- return LintUtils.formatList(list, Integer.MAX_VALUE);
- }
- }
-
- // GENERATED DATA.
- // This data is generated by the #testDatabaseAccurate method in PluralsDatabaseTest
- // which will generate the following if it can find an ICU plurals database file
- // in the unit test data folder.
-
- /** Set of language codes relevant to plurals data */
- private static final String[] LANGUAGE_CODES = new String[] {
- "af", "ak", "am", "ar", "az", "be", "bg", "bh", "bm", "bn",
- "bo", "br", "bs", "ca", "cs", "cy", "da", "de", "dv", "dz",
- "ee", "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi",
- "fo", "fr", "fy", "ga", "gd", "gl", "gu", "gv", "ha", "he",
- "hi", "hr", "hu", "hy", "id", "ig", "ii", "in", "is", "it",
- "iu", "iw", "ja", "ji", "jv", "ka", "kk", "kl", "km", "kn",
- "ko", "ks", "ku", "kw", "ky", "lb", "lg", "ln", "lo", "lt",
- "lv", "mg", "mk", "ml", "mn", "mr", "ms", "mt", "my", "nb",
- "nd", "ne", "nl", "nn", "no", "nr", "ny", "om", "or", "os",
- "pa", "pl", "ps", "pt", "rm", "ro", "ru", "se", "sg", "si",
- "sk", "sl", "sn", "so", "sq", "sr", "ss", "st", "sv", "sw",
- "ta", "te", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts",
- "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh",
- "yi", "yo", "zh", "zu"
- };
-
- /**
- * Relevant flags for each language (corresponding to each language listed
- * in the same position in {@link #LANGUAGE_CODES}).
- */
- private static final int[] FLAGS = new int[] {
- 0x0002, 0x0042, 0x0042, 0x001f, 0x0002, 0x005a, 0x0002, 0x0042,
- 0x0000, 0x0042, 0x0000, 0x00de, 0x004a, 0x0002, 0x000a, 0x001f,
- 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0002, 0x0002, 0x0002,
- 0x0002, 0x0002, 0x0002, 0x0042, 0x0042, 0x0002, 0x0002, 0x0042,
- 0x0002, 0x001e, 0x00ce, 0x0002, 0x0042, 0x00ce, 0x0002, 0x0016,
- 0x0042, 0x004a, 0x0002, 0x0042, 0x0000, 0x0000, 0x0000, 0x0000,
- 0x0042, 0x0002, 0x0006, 0x0016, 0x0000, 0x0002, 0x0000, 0x0002,
- 0x0002, 0x0002, 0x0000, 0x0042, 0x0000, 0x0002, 0x0002, 0x0006,
- 0x0002, 0x0002, 0x0002, 0x0042, 0x0000, 0x004a, 0x0063, 0x0042,
- 0x0042, 0x0002, 0x0002, 0x0042, 0x0000, 0x001a, 0x0000, 0x0002,
- 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
- 0x0002, 0x0002, 0x0042, 0x001a, 0x0002, 0x0002, 0x0002, 0x000a,
- 0x005a, 0x0006, 0x0000, 0x0042, 0x000a, 0x00ce, 0x0002, 0x0002,
- 0x0002, 0x004a, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
- 0x0000, 0x0042, 0x0002, 0x0042, 0x0002, 0x0000, 0x0002, 0x0002,
- 0x0002, 0x005a, 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0042,
- 0x0000, 0x0002, 0x0002, 0x0000, 0x0000, 0x0042
- };
-
- @Nullable
- private static String getExampleForQuantityZero(@NonNull String language) {
- int index = getLanguageIndex(language);
- switch (index) {
- // set14
- case 70: // lv
- return "0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, \u2026";
- case -1:
- default:
- return null;
- }
- }
-
- @Nullable
- private static String getExampleForQuantityOne(@NonNull String language) {
- int index = getLanguageIndex(language);
- switch (index) {
- // set1
- case 2: // am
- case 9: // bn
- case 27: // fa
- case 36: // gu
- case 40: // hi
- case 59: // kn
- case 75: // mr
- case 133: // zu
- return "0, 1";
- // set11
- case 48: // is
- return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
- // set12
- case 72: // mk
- return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
- // set13
- case 115: // tl
- return "0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, \u2026";
- // set14
- case 70: // lv
- return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
- // set2
- case 28: // ff
- case 31: // fr
- case 43: // hy
- return "0, 1";
- // set20
- case 12: // bs
- case 41: // hr
- case 105: // sr
- return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
- // set21
- case 34: // gd
- return "1, 11";
- // set22
- case 101: // sl
- return "1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026";
- // set26
- case 5: // be
- return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
- // set27
- case 69: // lt
- return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
- // set29
- case 96: // ru
- case 121: // uk
- return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
- // set30
- case 11: // br
- return "1, 21, 31, 41, 51, 61, 81, 101, 1001, \u2026";
- // set32
- case 37: // gv
- return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
- // set5
- case 99: // si
- return "0, 1";
- // set6
- case 1: // ak
- case 7: // bh
- case 67: // ln
- case 71: // mg
- case 90: // pa
- case 113: // ti
- case 127: // wa
- return "0, 1";
- case -1:
- default:
- return null;
- }
- }
-
- @Nullable
- private static String getExampleForQuantityTwo(@NonNull String language) {
- int index = getLanguageIndex(language);
- switch (index) {
- // set21
- case 34: // gd
- return "2, 12";
- // set22
- case 101: // sl
- return "2, 102, 202, 302, 402, 502, 602, 702, 1002, \u2026";
- // set30
- case 11: // br
- return "2, 22, 32, 42, 52, 62, 82, 102, 1002, \u2026";
- // set32
- case 37: // gv
- return "2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, \u2026";
- case -1:
- default:
- return null;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
deleted file mode 100644
index a3e0400..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_REF_PREFIX;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.FD_RES_VALUES;
-import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE;
-import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY;
-import static com.android.SdkConstants.RESOURCE_CLZ_ID;
-import static com.android.SdkConstants.TAG_ARRAY;
-import static com.android.SdkConstants.TAG_INTEGER_ARRAY;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_PLURALS;
-import static com.android.SdkConstants.TAG_RESOURCES;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.SdkConstants.VALUE_TRUE;
-import static com.android.tools.lint.detector.api.LintUtils.getBaseName;
-import static com.android.utils.SdkUtils.getResourceFieldName;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.MavenCoordinates;
-import com.android.ide.common.repository.ResourceVisibilityLookup;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.resources.FolderTypeRelationship;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Node;
-
-/**
- * Check which looks for access of private resources.
- */
-public class PrivateResourceDetector extends ResourceXmlDetector implements JavaScanner {
- /** Attribute for overriding a resource */
- private static final String ATTR_OVERRIDE = "override";
-
- @SuppressWarnings("unchecked")
- private static final Implementation IMPLEMENTATION = new Implementation(
- PrivateResourceDetector.class,
- Scope.JAVA_AND_RESOURCE_FILES,
- Scope.JAVA_FILE_SCOPE,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "PrivateResource", //$NON-NLS-1$
- "Using private resources",
-
- "Private resources should not be referenced; the may not be present everywhere, and " +
- "even where they are they may disappear without notice.\n" +
- "\n" +
- "To fix this, copy the resource into your own project instead.",
-
- Category.CORRECTNESS,
- 3,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Constructs a new detector */
- public PrivateResourceDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public boolean appliesToResourceRefs() {
- return true;
- }
-
- @Override
- public void visitResourceReference(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull Node node,
- @NonNull String type,
- @NonNull String name,
- boolean isFramework) {
- if (context.getProject().isGradleProject() && !isFramework) {
- Project project = context.getProject();
- if (project.getGradleProjectModel() != null && project.getCurrentVariant() != null) {
- ResourceType resourceType = ResourceType.getEnum(type);
- if (resourceType != null && isPrivate(context, resourceType, name)) {
- String message = createUsageErrorMessage(context, resourceType, name);
- context.report(ISSUE, node, context.getLocation(node), message);
- }
- }
- }
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- /** Check resource references: accessing a private resource from an upstream library? */
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String value = attribute.getNodeValue();
- if (context.getProject().isGradleProject()) {
- ResourceUrl url = ResourceUrl.parse(value);
- if (isPrivate(context, url)) {
- String message = createUsageErrorMessage(context, url.type, url.name);
- context.report(ISSUE, attribute, context.getValueLocation(attribute), message);
- }
- }
- }
-
- /** Check resource definitions: overriding a private resource from an upstream library? */
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STYLE,
- TAG_RESOURCES,
- TAG_ARRAY,
- TAG_STRING_ARRAY,
- TAG_INTEGER_ARRAY,
- TAG_PLURALS
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (TAG_RESOURCES.equals(element.getTagName())) {
- for (Element item : LintUtils.getChildren(element)) {
- Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
- if (nameAttribute != null) {
- String name = getResourceFieldName(nameAttribute.getValue());
- String type = item.getTagName();
- if (type.equals(TAG_ITEM)) {
- type = item.getAttribute(ATTR_TYPE);
- if (type == null || type.isEmpty()) {
- type = RESOURCE_CLZ_ID;
- }
- } else if (type.equals("declare-styleable")) { //$NON-NLS-1$
- type = RESOURCE_CLR_STYLEABLE;
- } else if (type.contains("array")) { //$NON-NLS-1$
- // <string-array> etc
- type = RESOURCE_CLZ_ARRAY;
- }
- ResourceType t = ResourceType.getEnum(type);
- if (t != null && isPrivate(context, t, name) &&
- !VALUE_TRUE.equals(item.getAttributeNS(TOOLS_URI, ATTR_OVERRIDE))) {
- String message = createOverrideErrorMessage(context, t, name);
- Location location = context.getValueLocation(nameAttribute);
- context.report(ISSUE, nameAttribute, location, message);
- }
- }
- }
- } else {
- assert TAG_STYLE.equals(element.getTagName())
- || TAG_ARRAY.equals(element.getTagName())
- || TAG_PLURALS.equals(element.getTagName())
- || TAG_INTEGER_ARRAY.equals(element.getTagName())
- || TAG_STRING_ARRAY.equals(element.getTagName());
- for (Element item : LintUtils.getChildren(element)) {
- checkChildRefs(context, item);
- }
- }
- }
-
- private static boolean isPrivate(Context context, ResourceType type, String name) {
- if (context.getProject().isGradleProject()) {
- ResourceVisibilityLookup lookup = context.getProject().getResourceVisibility();
- return lookup.isPrivate(type, name);
- }
-
- return false;
- }
-
- private static boolean isPrivate(@NonNull Context context, @Nullable ResourceUrl url) {
- return url != null && !url.framework && isPrivate(context, url.type, url.name);
- }
-
- private static void checkChildRefs(@NonNull XmlContext context, Element item) {
- // Look for ?attr/ and @dimen/foo etc references in the item children
- NodeList childNodes = item.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- org.w3c.dom.Node child = childNodes.item(i);
- if (child.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
- String text = child.getNodeValue();
-
- int index = text.indexOf(ATTR_REF_PREFIX);
- if (index != -1) {
- String name = text.substring(index + ATTR_REF_PREFIX.length()).trim();
- if (isPrivate(context, ResourceType.ATTR, name)) {
- String message = createUsageErrorMessage(context, ResourceType.ATTR, name);
- context.report(ISSUE, item, context.getLocation(child), message);
- }
- } else {
- for (int j = 0, m = text.length(); j < m; j++) {
- char c = text.charAt(j);
- if (c == '@') {
- ResourceUrl url = ResourceUrl.parse(text.trim());
- if (isPrivate(context, url)) {
- String message = createUsageErrorMessage(context, url.type,
- url.name);
- context.report(ISSUE, item, context.getLocation(child), message);
- }
- break;
- } else if (!Character.isWhitespace(c)) {
- break;
- }
- }
- }
- }
- }
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- File file = context.file;
- boolean isXmlFile = LintUtils.isXmlFile(file);
- if (!isXmlFile && !LintUtils.isBitmapFile(file)) {
- return;
- }
- String parentName = file.getParentFile().getName();
- int dash = parentName.indexOf('-');
- if (dash != -1 || FD_RES_VALUES.equals(parentName)) {
- return;
- }
- ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName);
- if (folderType == null) {
- return;
- }
- List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType);
- if (types.isEmpty()) {
- return;
- }
- ResourceType type = types.get(0);
- String resourceName = getResourceFieldName(getBaseName(file.getName()));
- if (isPrivate(context, type, resourceName)) {
- String message = createOverrideErrorMessage(context, type, resourceName);
- Location location = Location.create(file);
- context.report(ISSUE, location, message);
- }
- }
-
- private static String createOverrideErrorMessage(@NonNull Context context,
- @NonNull ResourceType type, @NonNull String name) {
- String libraryName = getLibraryName(context, type, name);
- return String.format("Overriding `@%1$s/%2$s` which is marked as private in %3$s. If "
- + "deliberate, use tools:override=\"true\", otherwise pick a "
- + "different name.", type, name, libraryName);
- }
-
- private static String createUsageErrorMessage(@NonNull Context context,
- @NonNull ResourceType type, @NonNull String name) {
- String libraryName = getLibraryName(context, type, name);
- return String.format("The resource `@%1$s/%2$s` is marked as private in %3$s", type,
- name, libraryName);
- }
-
- /** Pick a suitable name to describe the library defining the private resource */
- @Nullable
- private static String getLibraryName(@NonNull Context context, @NonNull ResourceType type,
- @NonNull String name) {
- ResourceVisibilityLookup lookup = context.getProject().getResourceVisibility();
- AndroidLibrary library = lookup.getPrivateIn(type, name);
- if (library != null) {
- String libraryName = library.getProject();
- if (libraryName != null) {
- return libraryName;
- }
- MavenCoordinates coordinates = library.getResolvedCoordinates();
- if (coordinates != null) {
- return coordinates.getGroupId() + ':' + coordinates.getArtifactId();
- }
- }
- return "the library";
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
deleted file mode 100644
index 744e5a3..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.DOT_PROPERTIES;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.google.common.base.Splitter;
-
-import java.io.File;
-import java.util.Iterator;
-
-/**
- * Check for errors in .property files
- * <p>
- * TODO: Warn about bad paths like sdk properties with ' in the path, or suffix of " " etc
- */
-public class PropertyFileDetector extends Detector {
- /** Property file not escaped */
- public static final Issue ESCAPE = Issue.create(
- "PropertyEscape", //$NON-NLS-1$
- "Incorrect property escapes",
- "All backslashes and colons in .property files must be escaped with " +
- "a backslash (\\). This means that when writing a Windows path, you " +
- "must escape the file separators, so the path \\My\\Files should be " +
- "written as `key=\\\\My\\\\Files.`",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- new Implementation(
- PropertyFileDetector.class,
- Scope.PROPERTY_SCOPE));
-
- /** Using HTTP instead of HTTPS for the wrapper */
- public static final Issue HTTP = Issue.create(
- "UsingHttp", //$NON-NLS-1$
- "Using HTTP instead of HTTPS",
- "The Gradle Wrapper is available both via HTTP and HTTPS. HTTPS is more " +
- "secure since it protects against man-in-the-middle attacks etc. Older " +
- "projects created in Android Studio used HTTP but we now default to HTTPS " +
- "and recommend upgrading existing projects.",
-
- Category.SECURITY,
- 6,
- Severity.WARNING,
- new Implementation(
- PropertyFileDetector.class,
- Scope.PROPERTY_SCOPE));
-
- /** Constructs a new {@link PropertyFileDetector} */
- public PropertyFileDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getPath().endsWith(DOT_PROPERTIES);
- }
-
- @Override
- public void run(@NonNull Context context) {
- String contents = context.getContents();
- if (contents == null) {
- return;
- }
- int offset = 0;
- Iterator<String> iterator = Splitter.on('\n').split(contents).iterator();
- String line;
- for (; iterator.hasNext(); offset += line.length() + 1) {
- line = iterator.next();
- if (line.startsWith("#") || line.startsWith(" ")) {
- continue;
- }
- if (line.indexOf('\\') == -1 && line.indexOf(':') == -1) {
- continue;
- }
- int valueStart = line.indexOf('=') + 1;
- if (valueStart == 0) {
- continue;
- }
- checkLine(context, contents, line, offset, valueStart);
- }
- }
-
- private static void checkLine(@NonNull Context context, @NonNull String contents,
- @NonNull String line, int offset, int valueStart) {
- String prefix = "distributionUrl=http\\";
- if (line.startsWith(prefix)) {
- String https = "https" + line.substring(prefix.length() - 1);
- String message = String.format("Replace HTTP with HTTPS for better security; use %1$s",
- https);
- int startOffset = offset + valueStart;
- int endOffset = startOffset + 4; // 4: "http".length()
- Location location = Location.create(context.file, contents, startOffset, endOffset);
- context.report(HTTP, location, message);
- }
-
- boolean escaped = false;
- boolean hadNonPathEscape = false;
- int errorStart = -1;
- int errorEnd = -1;
- StringBuilder path = new StringBuilder();
- for (int i = valueStart; i < line.length(); i++) {
- char c = line.charAt(i);
- if (c == '\\') {
- escaped = !escaped;
- if (escaped) {
- path.append(c);
- }
- } else if (c == ':') {
- if (!escaped) {
- hadNonPathEscape = true;
- if (errorStart < 0) {
- errorStart = i;
- }
- errorEnd = i;
- } else {
- escaped = false;
- }
- path.append(c);
- } else {
- if (escaped) {
- hadNonPathEscape = true;
- if (errorStart < 0) {
- errorStart = i;
- }
- errorEnd = i;
- }
- escaped = false;
- path.append(c);
- }
- }
- String pathString = path.toString();
- String key = line.substring(0, valueStart);
- if (hadNonPathEscape && key.endsWith(".dir=") || new File(pathString).exists()) {
- String escapedPath = suggestEscapes(line.substring(valueStart, line.length()));
-
- // NOTE: Keep in sync with {@link #getSuggestedEscape} below
- String message = "Windows file separators (`\\`) and drive letter "
- + "separators (':') must be escaped (`\\\\`) in property files; use "
- + escapedPath;
- int startOffset = offset + errorStart;
- int endOffset = offset + errorEnd + 1;
- Location location = Location.create(context.file, contents, startOffset,
- endOffset);
- context.report(ESCAPE, location, message);
- }
- }
-
- @NonNull
- static String suggestEscapes(@NonNull String value) {
- value = value.replace("\\:", ":").replace("\\\\", "\\");
- return LintUtils.escapePropertyValue(value);
- }
-
- /**
- * Returns the escaped string value suggested by the error message which should have
- * been computed by this lint detector.
- *
- * @param message the error message created by this lint detector
- * @param format the format of the error message
- * @return the suggested escaped value
- */
- @Nullable
- public static String getSuggestedEscape(@NonNull String message, @NonNull TextFormat format) {
- return LintUtils.findSubstring(format.toText(message), "; use ", null);
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
deleted file mode 100644
index de262a0..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
-import static com.android.SdkConstants.ANDROID_APP_SERVICE;
-import static com.android.SdkConstants.ANDROID_CONTENT_BROADCAST_RECEIVER;
-import static com.android.SdkConstants.ANDROID_CONTENT_CONTENT_PROVIDER;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.w3c.dom.Element;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.Map.Entry;
-
-/**
- * Checks for missing manifest registrations for activities, services etc
- * and also makes sure that they are registered with the correct tag
- * <p>
- * TODO: Rewrite as Java visitor!
- */
-public class RegistrationDetector extends LayoutDetector implements ClassScanner {
- /** Unregistered activities and services */
- public static final Issue ISSUE = Issue.create(
- "Registered", //$NON-NLS-1$
- "Class is not registered in the manifest",
-
- "Activities, services and content providers should be registered in the " +
- "`AndroidManifest.xml` file using `<activity>`, `<service>` and `<provider>` tags.\n" +
- "\n" +
- "If your activity is simply a parent class intended to be subclassed by other " +
- "\"real\" activities, make it an abstract class.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- new Implementation(
- RegistrationDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE)))
- .addMoreInfo(
- "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
-
- protected Multimap<String, String> mManifestRegistrations;
-
- /** Constructs a new {@link RegistrationDetector} */
- public RegistrationDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(sTags);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String fqcn = getFqcn(context, element);
- String tag = element.getTagName();
- String frameworkClass = tagToClass(tag);
- if (frameworkClass != null) {
- String signature = ClassContext.getInternalName(fqcn);
- if (mManifestRegistrations == null) {
- mManifestRegistrations = ArrayListMultimap.create(4, 8);
- }
- mManifestRegistrations.put(frameworkClass, signature);
- if (signature.indexOf('$') != -1) {
- // The internal name contains a $ which means it's an inner class.
- // The conversion from fqcn to internal name is a bit ambiguous:
- // "a.b.C.D" usually means "inner class D in class C in package a.b".
- // However, it can (see issue 31592) also mean class D in package "a.b.C".
- // Place *both* of these possibilities in the registered map, since this
- // is only used to check that an activity is registered, not the other way
- // (so it's okay to have entries there that do not correspond to real classes).
- signature = signature.replace('$', '/');
- mManifestRegistrations.put(frameworkClass, signature);
- }
- }
- }
-
- /**
- * Returns the fully qualified class name for a manifest entry element that
- * specifies a name attribute
- *
- * @param context the query context providing the project
- * @param element the element
- * @return the fully qualified class name
- */
- @NonNull
- private static String getFqcn(@NonNull XmlContext context, @NonNull Element element) {
- String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (className.startsWith(".")) { //$NON-NLS-1$
- return context.getMainProject().getPackage() + className;
- } else if (className.indexOf('.') == -1) {
- // According to the <activity> manifest element documentation, this is not
- // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
- // but it appears in manifest files and appears to be supported by the runtime
- // so handle this in code as well:
- return context.getMainProject().getPackage() + '.' + className;
- } // else: the class name is already a fully qualified class name
-
- return className;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- // Abstract classes do not need to be registered
- if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
- return;
- }
- String curr = classNode.name;
-
- int lastIndex = curr.lastIndexOf('$');
- if (lastIndex != -1 && lastIndex < curr.length() - 1) {
- if (Character.isDigit(curr.charAt(lastIndex+1))) {
- // Anonymous inner class, doesn't need to be registered
- return;
- }
- }
-
- while (curr != null) {
- for (String s : sClasses) {
- if (curr.equals(s)) {
- Collection<String> registered = mManifestRegistrations != null ?
- mManifestRegistrations.get(curr) : null;
- if (registered == null || !registered.contains(classNode.name)) {
- report(context, classNode, curr);
- }
-
- }
- }
-
- curr = context.getDriver().getSuperClass(curr);
- }
- }
-
- private void report(ClassContext context, ClassNode classNode, String curr) {
- String tag = classToTag(curr);
- String className = ClassContext.createSignature(classNode.name, null, null);
-
- String wrongClass = null; // The framework class this class actually extends
- if (mManifestRegistrations != null) {
- Collection<Entry<String,String>> entries =
- mManifestRegistrations.entries();
- for (Entry<String,String> entry : entries) {
- if (entry.getValue().equals(classNode.name)) {
- wrongClass = entry.getKey();
- break;
- }
- }
- }
- if (wrongClass != null) {
- Location location = context.getLocation(classNode);
- context.report(
- ISSUE,
- location,
- String.format(
- "`%1$s` is a `<%2$s>` but is registered in the manifest as a `<%3$s>`",
- className, tag, classToTag(wrongClass)));
- } else if (!TAG_RECEIVER.equals(tag)) { // don't need to be registered
- if (context.getMainProject().isGradleProject()) {
- // Disabled for now; we need to formalize the difference between
- // the *manifest* package and the variant package, since in some contexts
- // (such as manifest registrations) we should be using the manifest package,
- // not the gradle package
- return;
- }
- Location location = context.getLocation(classNode);
- context.report(
- ISSUE,
- location,
- String.format(
- "The `<%1$s> %2$s` is not registered in the manifest",
- tag, className));
- }
- }
-
- /** The manifest tags we care about */
- private static final String[] sTags = new String[] {
- TAG_ACTIVITY,
- TAG_SERVICE,
- TAG_RECEIVER,
- TAG_PROVIDER,
- // Keep synchronized with {@link #sClasses}
- };
-
- /** The corresponding framework classes that the tags in {@link #sTags} should extend */
- private static final String[] sClasses = new String[] {
- ANDROID_APP_ACTIVITY,
- ANDROID_APP_SERVICE,
- ANDROID_CONTENT_BROADCAST_RECEIVER,
- ANDROID_CONTENT_CONTENT_PROVIDER,
- // Keep synchronized with {@link #sTags}
- };
-
- /** Looks up the corresponding framework class a given manifest tag's class should extend */
- private static String tagToClass(String tag) {
- for (int i = 0, n = sTags.length; i < n; i++) {
- if (sTags[i].equals(tag)) {
- return sClasses[i];
- }
- }
-
- return null;
- }
-
- /** Looks up the tag a given framework class should be registered with */
- protected static String classToTag(String className) {
- for (int i = 0, n = sClasses.length; i < n; i++) {
- if (sClasses[i].equals(className)) {
- return sTags[i];
- }
- }
-
- return null;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java
deleted file mode 100644
index 1fae9e6..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_END;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_START;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_END_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_START_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_TEXT;
-import static com.android.SdkConstants.ATTR_VISIBILITY;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.PREFIX_THEME_REF;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.VALUE_TRUE;
-import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
-import static com.android.SdkConstants.VIEW;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Maps;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Check for potential item overlaps in a RelativeLayout when left- and
- * right-aligned text items are used.
- */
-public class RelativeOverlapDetector extends LayoutDetector {
- public static final Issue ISSUE = Issue.create(
- "RelativeOverlap",
- "Overlapping items in RelativeLayout",
- "If relative layout has text or button items aligned to left and right " +
- "sides they can overlap each other due to localized text expansion " +
- "unless they have mutual constraints like `toEndOf`/`toStartOf`.",
- Category.I18N, 3, Severity.WARNING,
- new Implementation(RelativeOverlapDetector.class, Scope.RESOURCE_FILE_SCOPE));
-
- private static class LayoutNode {
- private enum Bucket {
- TOP, BOTTOM, SKIP
- }
-
- private int mIndex;
- private boolean mProcessed;
- private Element mNode;
- private Bucket mBucket;
- private LayoutNode mToLeft;
- private LayoutNode mToRight;
- private boolean mLastLeft;
- private boolean mLastRight;
-
- public LayoutNode(@NonNull Element node, int index) {
- mNode = node;
- mIndex = index;
- mProcessed = false;
- mLastLeft = true;
- mLastRight = true;
- }
-
- @NonNull
- public String getNodeId() {
- String nodeid = mNode.getAttributeNS(ANDROID_URI, ATTR_ID);
- if (nodeid.isEmpty()) {
- return String.format("%1$s-%2$d", mNode.getTagName(), mIndex);
- } else {
- return uniformId(nodeid);
- }
- }
-
- @NonNull
- public String getNodeTextId() {
- String text = mNode.getAttributeNS(ANDROID_URI, ATTR_TEXT);
- if (text.isEmpty()) {
- return getNodeId();
- } else {
- return uniformId(text);
- }
- }
-
- @NonNull
- @Override
- public String toString() {
- return getNodeTextId();
- }
-
- public boolean isInvisible() {
- String visibility = mNode.getAttributeNS(ANDROID_URI,
- ATTR_VISIBILITY);
- return visibility.equals("gone") || visibility.equals("invisible");
- }
-
- /**
- * Determine if not can grow due to localization or not.
- */
- public boolean fixedWidth() {
- String width = mNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
- if (width.equals(VALUE_WRAP_CONTENT)) {
- // First check child nodes. If at least one of them is not
- // fixed-width,
- // treat whole layout as non-fixed-width
- NodeList childNodes = mNode.getChildNodes();
- for (int i = 0; i < childNodes.getLength(); i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- LayoutNode childLayout = new LayoutNode((Element) child,
- i);
- if (!childLayout.fixedWidth()) {
- return false;
- }
- }
- }
- // If node contains text attribute, consider it fixed-width if
- // text is hard-coded, otherwise it is not fixed-width.
- String text = mNode.getAttributeNS(ANDROID_URI, ATTR_TEXT);
- if (!text.isEmpty()) {
- return !text.startsWith(PREFIX_RESOURCE_REF)
- && !text.startsWith(PREFIX_THEME_REF);
- }
-
- String nodeName = mNode.getTagName();
- if (nodeName.contains("Image") || nodeName.contains("Progress")
- || nodeName.contains("Radio")) {
- return true;
- } else if (nodeName.contains("Button")
- || nodeName.contains("Text")) {
- return false;
- }
- }
- return true;
- }
-
- @NonNull
- public Element getNode() {
- return mNode;
- }
-
- /**
- * Process a node of a layout. Put it into one of three processing
- * units and determine its right and left neighbours.
- */
- public void processNode(@NonNull Map<String, LayoutNode> nodes) {
- if (mProcessed) {
- return;
- }
- mProcessed = true;
-
- if (isInvisible() ||
- hasAttr(ATTR_LAYOUT_ALIGN_RIGHT) ||
- hasAttr(ATTR_LAYOUT_ALIGN_END) ||
- hasAttr(ATTR_LAYOUT_ALIGN_LEFT) ||
- hasAttr(ATTR_LAYOUT_ALIGN_START)) {
- mBucket = Bucket.SKIP;
- } else if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_TOP)) {
- mBucket = Bucket.TOP;
- } else if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM)) {
- mBucket = Bucket.BOTTOM;
- } else {
- if (hasAttr(ATTR_LAYOUT_ABOVE) || hasAttr(ATTR_LAYOUT_BELOW)) {
- mBucket = Bucket.SKIP;
- } else {
- String[] checkAlignment = { ATTR_LAYOUT_ALIGN_TOP,
- ATTR_LAYOUT_ALIGN_BOTTOM,
- ATTR_LAYOUT_ALIGN_BASELINE };
- for (String alignment : checkAlignment) {
- String value = mNode.getAttributeNS(ANDROID_URI,
- alignment);
- if (!value.isEmpty()) {
- LayoutNode otherNode = nodes.get(uniformId(value));
- if (otherNode != null) {
- otherNode.processNode(nodes);
- mBucket = otherNode.mBucket;
- }
- }
- }
- }
- }
- if (mBucket == null) {
- mBucket = Bucket.TOP;
- }
-
- // Check relative placement
- boolean positioned = false;
- mToLeft = findNodeByAttr(nodes, ATTR_LAYOUT_TO_START_OF);
- if (mToLeft == null) {
- mToLeft = findNodeByAttr(nodes, ATTR_LAYOUT_TO_LEFT_OF);
- }
- // Avoid circular dependency
- for (LayoutNode n = mToLeft; n != null; n = n.mToLeft) {
- if (n.equals(this)) {
- mToLeft = null;
- mBucket = Bucket.SKIP;
- break;
- }
- }
- if (mToLeft != null) {
- mToLeft.mLastLeft = false;
- mLastRight = false;
- positioned = true;
- }
- mToRight = findNodeByAttr(nodes, ATTR_LAYOUT_TO_END_OF);
- if (mToRight == null) {
- mToRight = findNodeByAttr(nodes, ATTR_LAYOUT_TO_RIGHT_OF);
- }
- // Avoid circular dependency
- for (LayoutNode n = mToRight; n != null; n = n.mToRight) {
- if (n.equals(this)) {
- mToRight = null;
- mBucket = Bucket.SKIP;
- break;
- }
- }
- if (mToRight != null) {
- mToRight.mLastRight = false;
- mLastLeft = false;
- positioned = true;
- }
-
- if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_END)
- || hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) {
- mLastRight = false;
- positioned = true;
- }
- if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_START)
- || hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_LEFT)) {
- mLastLeft = false;
- positioned = true;
- }
- // Treat any node that does not have explicit relative placement
- // same as if it has layout_alignParentStart = true;
- if (!positioned) {
- mLastLeft = false;
- }
- }
-
- @NonNull
- public Set<LayoutNode> canGrowLeft() {
- Set<LayoutNode> nodes;
- if (mToRight != null) {
- nodes = mToRight.canGrowLeft();
- } else {
- nodes = new LinkedHashSet<LayoutNode>();
- }
- if (!fixedWidth()) {
- nodes.add(this);
- }
- return nodes;
- }
-
- @NonNull
- public Set<LayoutNode> canGrowRight() {
- Set<LayoutNode> nodes;
- if (mToLeft != null) {
- nodes = mToLeft.canGrowRight();
- } else {
- nodes = new LinkedHashSet<LayoutNode>();
- }
- if (!fixedWidth()) {
- nodes.add(this);
- }
- return nodes;
- }
-
- /**
- * Determines if not should be skipped from checking.
- */
- public boolean skip() {
- if (mBucket == Bucket.SKIP) {
- return true;
- }
-
- // Skip all includes and Views
- return mNode.getTagName().equals(VIEW_INCLUDE)
- || mNode.getTagName().equals(VIEW);
- }
-
- public boolean sameBucket(@NonNull LayoutNode node) {
- return mBucket == node.mBucket;
- }
-
- @Nullable
- private LayoutNode findNodeByAttr(
- @NonNull Map<String, LayoutNode> nodes,
- @NonNull String attrName) {
- String value = mNode.getAttributeNS(ANDROID_URI, attrName);
- if (!value.isEmpty()) {
- return nodes.get(uniformId(value));
- } else {
- return null;
- }
- }
-
- private boolean hasAttr(@NonNull String key) {
- return mNode.hasAttributeNS(ANDROID_URI, key);
- }
-
- private boolean hasTrueAttr(@NonNull String key) {
- return mNode.getAttributeNS(ANDROID_URI, key).equals(VALUE_TRUE);
- }
-
- @NonNull
- private static String uniformId(@NonNull String value) {
- return value.replaceFirst("@\\+", "@");
- }
- }
-
- public RelativeOverlapDetector() {
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(RELATIVE_LAYOUT);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- // Traverse all child elements
- NodeList childNodes = element.getChildNodes();
- int count = childNodes.getLength();
- Map<String, LayoutNode> nodes = Maps.newHashMap();
- for (int i = 0; i < count; i++) {
- Node node = childNodes.item(i);
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- LayoutNode ln = new LayoutNode((Element) node, i);
- nodes.put(ln.getNodeId(), ln);
- }
- }
-
- // Node map is populated, recalculate nodes sizes
- for (LayoutNode ln : nodes.values()) {
- ln.processNode(nodes);
- }
- for (LayoutNode right : nodes.values()) {
- if (!right.mLastLeft || right.skip()) {
- continue;
- }
- Set<LayoutNode> canGrowLeft = right.canGrowLeft();
- for (LayoutNode left : nodes.values()) {
- if (left == right || !left.mLastRight || left.skip()
- || !left.sameBucket(right)) {
- continue;
- }
- Set<LayoutNode> canGrowRight = left.canGrowRight();
- if (!canGrowLeft.isEmpty() || !canGrowRight.isEmpty()) {
- canGrowRight.addAll(canGrowLeft);
- LayoutNode nodeToBlame = right;
- LayoutNode otherNode = left;
- if (!canGrowRight.contains(right)
- && canGrowRight.contains(left)) {
- nodeToBlame = left;
- otherNode = right;
- }
- context.report(ISSUE, nodeToBlame.getNode(),
- context.getLocation(nodeToBlame.getNode()),
- String.format(
- "`%1$s` can overlap `%2$s` if %3$s %4$s due to localized text expansion",
- nodeToBlame.getNodeId(), otherNode.getNodeId(),
- Joiner.on(", ").join(canGrowRight),
- canGrowRight.size() > 1 ? "grow" : "grows"));
- }
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
deleted file mode 100644
index a78ca03..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
+++ /dev/null
@@ -1,622 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
-import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PARENT;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
-import static com.android.SdkConstants.FN_RESOURCE_BASE;
-import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7;
-import static com.android.SdkConstants.GRID_LAYOUT;
-import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.REQUEST_FOCUS;
-import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.TABLE_LAYOUT;
-import static com.android.SdkConstants.TABLE_ROW;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-import static com.android.SdkConstants.VIEW_MERGE;
-import static com.android.resources.ResourceFolderType.LAYOUT;
-import static com.android.resources.ResourceFolderType.VALUES;
-import static com.android.tools.lint.detector.api.LintUtils.getLayoutName;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.NullLiteral;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.VariableReference;
-
-/**
- * Ensures that layout width and height attributes are specified
- */
-public class RequiredAttributeDetector extends LayoutDetector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "RequiredSize", //$NON-NLS-1$
- "Missing `layout_width` or `layout_height` attributes",
-
- "All views must specify an explicit `layout_width` and `layout_height` attribute. " +
- "There is a runtime check for this, so if you fail to specify a size, an exception " +
- "is thrown at runtime.\n" +
- "\n" +
- "It's possible to specify these widths via styles as well. GridLayout, as a special " +
- "case, does not require you to specify a size.",
- Category.CORRECTNESS,
- 4,
- Severity.ERROR,
- new Implementation(
- RequiredAttributeDetector.class,
- EnumSet.of(Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES)));
-
- /** Map from each style name to parent style */
- @Nullable private Map<String, String> mStyleParents;
-
- /** Set of style names where the style sets the layout width */
- @Nullable private Set<String> mWidthStyles;
-
- /** Set of style names where the style sets the layout height */
- @Nullable private Set<String> mHeightStyles;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the width is set on the include */
- @Nullable private Set<String> mIncludedWidths;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the height is set on the include */
- @Nullable private Set<String> mIncludedHeights;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the width is <b>not</b> set on the include */
- @Nullable private Set<String> mNotIncludedWidths;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the height is <b>not</b> set on the include */
- @Nullable private Set<String> mNotIncludedHeights;
-
- /** Whether the width was set in a theme definition */
- private boolean mSetWidthInTheme;
-
- /** Whether the height was set in a theme definition */
- private boolean mSetHeightInTheme;
-
- /** Constructs a new {@link RequiredAttributeDetector} */
- public RequiredAttributeDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == LAYOUT || folderType == VALUES;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- // Process checks in two phases:
- // Phase 1: Gather styles and includes (styles are encountered after the layouts
- // so we can't do it in a single phase, and includes can be affected by includes from
- // layouts we haven't seen yet)
- // Phase 2: Process layouts, using gathered style and include data, and mark layouts
- // not known.
- //
- if (context.getPhase() == 1) {
- checkSizeSetInTheme();
-
- context.requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
- }
- }
-
- private boolean isWidthStyle(String style) {
- return isSizeStyle(style, mWidthStyles);
- }
-
- private boolean isHeightStyle(String style) {
- return isSizeStyle(style, mHeightStyles);
- }
-
- private boolean isSizeStyle(String style, Set<String> sizeStyles) {
- if (isFrameworkSizeStyle(style)) {
- return true;
- }
- if (sizeStyles == null) {
- return false;
- }
- return isSizeStyle(stripStylePrefix(style), sizeStyles, 0);
- }
-
- private static boolean isFrameworkSizeStyle(String style) {
- // The styles Widget.TextView.ListSeparator (and several theme variations, such as
- // Widget.Holo.TextView.ListSeparator, Widget.Holo.Light.TextView.ListSeparator, etc)
- // define layout_width and layout_height.
- // These are exposed through the listSeparatorTextViewStyle style.
- if (style.equals("?android:attr/listSeparatorTextViewStyle") //$NON-NLS-1$
- || style.equals("?android/listSeparatorTextViewStyle")) { //$NON-NLS-1$
- return true;
- }
-
- // It's also set on Widget.QuickContactBadge and Widget.QuickContactBadgeSmall
- // These are exposed via a handful of attributes with a common prefix
- if (style.startsWith("?android:attr/quickContactBadgeStyle")) { //$NON-NLS-1$
- return true;
- }
-
- // Finally, the styles are set on MediaButton and Widget.Holo.Tab (and
- // Widget.Holo.Light.Tab) but these are not exposed via attributes.
-
- return false;
- }
-
- private boolean isSizeStyle(
- @NonNull String style,
- @NonNull Set<String> sizeStyles, int depth) {
- if (depth == 30) {
- // Cycle between local and framework attribute style missed
- // by the fact that we're stripping the distinction between framework
- // and local styles here
- return false;
- }
-
- assert !style.startsWith(STYLE_RESOURCE_PREFIX)
- && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
-
- if (sizeStyles.contains(style)) {
- return true;
- }
-
- if (mStyleParents != null) {
- String parentStyle = mStyleParents.get(style);
- if (parentStyle != null) {
- parentStyle = stripStylePrefix(parentStyle);
- if (isSizeStyle(parentStyle, sizeStyles, depth + 1)) {
- return true;
- }
- }
- }
-
- int index = style.lastIndexOf('.');
- if (index > 0) {
- return isSizeStyle(style.substring(0, index), sizeStyles, depth + 1);
- }
-
- return false;
- }
-
- private void checkSizeSetInTheme() {
- // Look through the styles and determine whether each style is a theme
- if (mStyleParents == null) {
- return;
- }
-
- Map<String, Boolean> isTheme = Maps.newHashMap();
- for (String style : mStyleParents.keySet()) {
- if (isTheme(stripStylePrefix(style), isTheme, 0)) {
- mSetWidthInTheme = true;
- mSetHeightInTheme = true;
- break;
- }
- }
- }
-
- private boolean isTheme(String style, Map<String, Boolean> isTheme, int depth) {
- if (depth == 30) {
- // Cycle between local and framework attribute style missed
- // by the fact that we're stripping the distinction between framework
- // and local styles here
- return false;
- }
-
- assert !style.startsWith(STYLE_RESOURCE_PREFIX)
- && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
-
- Boolean known = isTheme.get(style);
- if (known != null) {
- return known;
- }
-
- if (style.contains("Theme")) { //$NON-NLS-1$
- isTheme.put(style, true);
- return true;
- }
-
- if (mStyleParents != null) {
- String parentStyle = mStyleParents.get(style);
- if (parentStyle != null) {
- parentStyle = stripStylePrefix(parentStyle);
- if (isTheme(parentStyle, isTheme, depth + 1)) {
- isTheme.put(style, true);
- return true;
- }
- }
- }
-
- int index = style.lastIndexOf('.');
- if (index > 0) {
- String parentStyle = style.substring(0, index);
- boolean result = isTheme(parentStyle, isTheme, depth + 1);
- isTheme.put(style, result);
- return result;
- }
-
- return false;
- }
-
- @VisibleForTesting
- static boolean hasLayoutVariations(File file) {
- File parent = file.getParentFile();
- if (parent == null) {
- return false;
- }
- File res = parent.getParentFile();
- if (res == null) {
- return false;
- }
- String name = file.getName();
- File[] folders = res.listFiles();
- if (folders == null) {
- return false;
- }
- for (File folder : folders) {
- if (!folder.getName().startsWith(FD_RES_LAYOUT)) {
- continue;
- }
- if (folder.equals(parent)) {
- continue;
- }
- File other = new File(folder, name);
- if (other.exists()) {
- return true;
- }
- }
-
- return false;
- }
-
- private static String stripStylePrefix(@NonNull String style) {
- if (style.startsWith(STYLE_RESOURCE_PREFIX)) {
- style = style.substring(STYLE_RESOURCE_PREFIX.length());
- } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
- style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length());
- }
-
- return style;
- }
-
- private static boolean isRootElement(@NonNull Node node) {
- return node == node.getOwnerDocument().getDocumentElement();
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return ALL;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- ResourceFolderType folderType = context.getResourceFolderType();
- int phase = context.getPhase();
- if (phase == 1 && folderType == VALUES) {
- String tag = element.getTagName();
- if (TAG_STYLE.equals(tag)) {
- String parent = element.getAttribute(ATTR_PARENT);
- if (parent != null && !parent.isEmpty()) {
- String name = element.getAttribute(ATTR_NAME);
- if (name != null && !name.isEmpty()) {
- if (mStyleParents == null) {
- mStyleParents = Maps.newHashMap();
- }
- mStyleParents.put(name, parent);
- }
- }
- } else if (TAG_ITEM.equals(tag)
- && TAG_STYLE.equals(element.getParentNode().getNodeName())) {
- String name = element.getAttribute(ATTR_NAME);
- if (name.endsWith(ATTR_LAYOUT_WIDTH) &&
- name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_WIDTH)) {
- if (mWidthStyles == null) {
- mWidthStyles = Sets.newHashSet();
- }
- String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
- mWidthStyles.add(styleName);
- }
- if (name.endsWith(ATTR_LAYOUT_HEIGHT) &&
- name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_HEIGHT)) {
- if (mHeightStyles == null) {
- mHeightStyles = Sets.newHashSet();
- }
- String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
- mHeightStyles.add(styleName);
- }
- }
- } else if (folderType == LAYOUT) {
- if (phase == 1) {
- // Gather includes
- if (element.getTagName().equals(VIEW_INCLUDE)) {
- String layout = element.getAttribute(ATTR_LAYOUT);
- if (layout != null && !layout.isEmpty()) {
- recordIncludeWidth(layout,
- element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH));
- recordIncludeHeight(layout,
- element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT));
- }
- }
- } else {
- assert phase == 2; // Check everything using style data and include data
- boolean hasWidth = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
- boolean hasHeight = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
-
- if (mSetWidthInTheme) {
- hasWidth = true;
- }
-
- if (mSetHeightInTheme) {
- hasHeight = true;
- }
-
- if (hasWidth && hasHeight) {
- return;
- }
-
- String tag = element.getTagName();
- if (VIEW_MERGE.equals(tag)
- || VIEW_INCLUDE.equals(tag)
- || REQUEST_FOCUS.equals(tag)) {
- return;
- }
-
- String parentTag = element.getParentNode() != null
- ? element.getParentNode().getNodeName() : "";
- if (TABLE_LAYOUT.equals(parentTag)
- || TABLE_ROW.equals(parentTag)
- || GRID_LAYOUT.equals(parentTag)
- || FQCN_GRID_LAYOUT_V7.equals(parentTag)) {
- return;
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- boolean certain = true;
- boolean isRoot = isRootElement(element);
- if (isRoot || isRootElement(element.getParentNode())
- && VIEW_MERGE.equals(parentTag)) {
- String name = LAYOUT_RESOURCE_PREFIX + getLayoutName(context.file);
- if (!hasWidth && mIncludedWidths != null) {
- hasWidth = mIncludedWidths.contains(name);
- // If the layout is *also* included in a context where the width
- // was not set, we're not certain; it's possible that
- if (mNotIncludedWidths != null && mNotIncludedWidths.contains(name)) {
- hasWidth = false;
- // If we only have a single layout we know that this layout isn't
- // always included with layout_width or layout_height set, but
- // if there are multiple layouts, it's possible that at runtime
- // we only load the size-less layout by the tag which includes
- // the size
- certain = !hasLayoutVariations(context.file);
- }
- }
- if (!hasHeight && mIncludedHeights != null) {
- hasHeight = mIncludedHeights.contains(name);
- if (mNotIncludedHeights != null && mNotIncludedHeights.contains(name)) {
- hasHeight = false;
- certain = !hasLayoutVariations(context.file);
- }
- }
- if (hasWidth && hasHeight) {
- return;
- }
- }
-
- if (!hasWidth || !hasHeight) {
- String style = element.getAttribute(ATTR_STYLE);
- if (style != null && !style.isEmpty()) {
- if (!hasWidth) {
- hasWidth = isWidthStyle(style);
- }
- if (!hasHeight) {
- hasHeight = isHeightStyle(style);
- }
- }
- if (hasWidth && hasHeight) {
- return;
- }
- }
-
- String message;
- if (!(hasWidth || hasHeight)) {
- if (certain) {
- message = "The required `layout_width` and `layout_height` attributes " +
- "are missing";
- } else {
- message = "The required `layout_width` and `layout_height` attributes " +
- "*may* be missing";
- }
- } else {
- String attribute = hasWidth ? ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH;
- if (certain) {
- message = String.format("The required `%1$s` attribute is missing",
- attribute);
- } else {
- message = String.format("The required `%1$s` attribute *may* be missing",
- attribute);
- }
- }
- context.report(ISSUE, element, context.getLocation(element),
- message);
- }
- }
- }
-
- private void recordIncludeWidth(String layout, boolean providesWidth) {
- if (providesWidth) {
- if (mIncludedWidths == null) {
- mIncludedWidths = Sets.newHashSet();
- }
- mIncludedWidths.add(layout);
- } else {
- if (mNotIncludedWidths == null) {
- mNotIncludedWidths = Sets.newHashSet();
- }
- mNotIncludedWidths.add(layout);
- }
- }
-
- private void recordIncludeHeight(String layout, boolean providesHeight) {
- if (providesHeight) {
- if (mIncludedHeights == null) {
- mIncludedHeights = Sets.newHashSet();
- }
- mIncludedHeights.add(layout);
- } else {
- if (mNotIncludedHeights == null) {
- mNotIncludedHeights = Sets.newHashSet();
- }
- mNotIncludedHeights.add(layout);
- }
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("inflate"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull MethodInvocation call) {
- // Handle
- // View#inflate(Context context, int resource, ViewGroup root)
- // LayoutInflater#inflate(int resource, ViewGroup root)
- // LayoutInflater#inflate(int resource, ViewGroup root, boolean attachToRoot)
- StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
-
- String layout = null;
- int index = 0;
- for (Iterator<Expression> iterator = args.iterator(); iterator.hasNext(); index++) {
- Expression expression = iterator.next();
- if (expression instanceof Select) {
- Select outer = (Select) expression;
- Expression operand = outer.astOperand();
- if (operand instanceof Select) {
- Select inner = (Select) operand;
- if (inner.astOperand() instanceof VariableReference) {
- VariableReference reference = (VariableReference) inner.astOperand();
- if (FN_RESOURCE_BASE.equals(reference.astIdentifier().astValue())
- // TODO: constant
- && "layout".equals(inner.astIdentifier().astValue())) {
- layout = LAYOUT_RESOURCE_PREFIX + outer.astIdentifier().astValue();
- break;
- }
- }
- }
- }
- }
-
- if (layout == null) {
- lombok.ast.Node method = StringFormatDetector.getParentMethod(call);
- if (method != null) {
- // Must track local types
- index = 0;
- String name = StringFormatDetector.getResourceArg(method, call, index);
- if (name == null) {
- index = 1;
- name = StringFormatDetector.getResourceArg(method, call, index);
- }
- if (name != null) {
- layout = LAYOUT_RESOURCE_PREFIX + name;
- }
- }
- if (layout == null) {
- // Flow analysis didn't succeed
- return;
- }
- }
-
- // In all the applicable signatures, the view root argument is immediately after
- // the layout resource id.
- int viewRootPos = index + 1;
- if (viewRootPos < args.size()) {
- int i = 0;
- Iterator<Expression> iterator = args.iterator();
- while (iterator.hasNext() && i < viewRootPos) {
- iterator.next();
- i++;
- }
- if (iterator.hasNext()) {
- Expression viewRoot = iterator.next();
- if (viewRoot instanceof NullLiteral) {
- // Yep, this one inflates the given view with a null parent:
- // Tag it as such. For now just use the include data structure since
- // it has the same net effect
- recordIncludeWidth(layout, true);
- recordIncludeHeight(layout, true);
- }
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
deleted file mode 100644
index 7250d78..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.TAG_DECLARE_STYLEABLE;
-import static com.android.SdkConstants.TAG_RESOURCES;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceContext;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.EnumSet;
-
-/**
- * Ensure that resources in Gradle projects which specify a resource prefix
- * conform to the given name
- *
- * TODO: What about id's?
- */
-public class ResourcePrefixDetector extends ResourceXmlDetector implements
- Detector.BinaryResourceScanner {
- /** The main issue discovered by this detector */
- @SuppressWarnings("unchecked")
- public static final Issue ISSUE = Issue.create(
- "ResourceName", //$NON-NLS-1$
- "Resource with Wrong Prefix",
- "In Gradle projects you can specify a resource prefix that all resources " +
- "in the project must conform to. This makes it easier to ensure that you don't " +
- "accidentally combine resources from different libraries, since they all end " +
- "up in the same shared app namespace.",
-
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- new Implementation(
- ResourcePrefixDetector.class,
- EnumSet.of(Scope.RESOURCE_FILE, Scope.BINARY_RESOURCE_FILE),
- Scope.RESOURCE_FILE_SCOPE,
- Scope.BINARY_RESOURCE_FILE_SCOPE));
-
- /** Constructs a new {@link com.android.tools.lint.checks.ResourcePrefixDetector} */
- public ResourcePrefixDetector() {
- }
-
- private String mPrefix;
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(TAG_RESOURCES, TAG_DECLARE_STYLEABLE);
- }
-
- @Nullable
- private static String computeResourcePrefix(@NonNull Project project) {
- if (project.isGradleProject()) {
- return LintUtils.computeResourcePrefix(project.getGradleProjectModel());
- }
-
- return null;
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- mPrefix = computeResourcePrefix(context.getProject());
- }
-
- @Override
- public void beforeCheckLibraryProject(@NonNull Context context) {
- // TODO: Make sure this doesn't wipe out the prefix for the remaining projects
- mPrefix = computeResourcePrefix(context.getProject());
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- mPrefix = null;
- }
-
- @Override
- public void afterCheckLibraryProject(@NonNull Context context) {
- mPrefix = null;
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- if (mPrefix != null && context instanceof XmlContext) {
- XmlContext xmlContext = (XmlContext) context;
- ResourceFolderType folderType = xmlContext.getResourceFolderType();
- if (folderType != null && folderType != ResourceFolderType.VALUES) {
- String name = LintUtils.getBaseName(context.file.getName());
- if (!name.startsWith(mPrefix)) {
- // Attempt to report the error on the root tag of the associated
- // document to make suppressing the error with a tools:suppress
- // attribute etc possible
- if (xmlContext.document != null) {
- Element root = xmlContext.document.getDocumentElement();
- if (root != null) {
- xmlContext.report(ISSUE, root, xmlContext.getLocation(root),
- getErrorMessage(name));
- return;
- }
- }
- context.report(ISSUE, Location.create(context.file),
- getErrorMessage(name));
- }
- }
- }
- }
-
- private String getErrorMessage(String name) {
- assert mPrefix != null && !name.startsWith(mPrefix);
- return String.format("Resource named '`%1$s`' does not start "
- + "with the project's resource prefix '`%2$s`'; rename to '`%3$s`' ?",
- name, mPrefix, LintUtils.computeResourceName(mPrefix, name));
- }
-
- // --- Implements XmlScanner ----
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (mPrefix == null || context.getResourceFolderType() != ResourceFolderType.VALUES) {
- return;
- }
-
- for (Element item : LintUtils.getChildren(element)) {
- Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
- if (nameAttribute != null) {
- String name = nameAttribute.getValue();
- if (!name.startsWith(mPrefix)) {
- String message = getErrorMessage(name);
- context.report(ISSUE, nameAttribute, context.getLocation(nameAttribute),
- message);
- }
- }
- }
- }
-
- // ---- Implements BinaryResourceScanner ---
-
- @Override
- public void checkBinaryResource(@NonNull ResourceContext context) {
- if (mPrefix != null) {
- ResourceFolderType folderType = context.getResourceFolderType();
- if (folderType != null && folderType != ResourceFolderType.VALUES) {
- String name = LintUtils.getBaseName(context.file.getName());
- if (!name.startsWith(mPrefix)) {
- Location location = Location.create(context.file);
- context.report(ISSUE, location, getErrorMessage(name));
- }
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
deleted file mode 100644
index bc98129..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
+++ /dev/null
@@ -1,645 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_DRAWABLE_END;
-import static com.android.SdkConstants.ATTR_DRAWABLE_LEFT;
-import static com.android.SdkConstants.ATTR_DRAWABLE_RIGHT;
-import static com.android.SdkConstants.ATTR_DRAWABLE_START;
-import static com.android.SdkConstants.ATTR_GRAVITY;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_END;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_START;
-import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_START;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_END_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_START_OF;
-import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_END;
-import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT;
-import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT;
-import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_START;
-import static com.android.SdkConstants.ATTR_PADDING;
-import static com.android.SdkConstants.ATTR_PADDING_END;
-import static com.android.SdkConstants.ATTR_PADDING_LEFT;
-import static com.android.SdkConstants.ATTR_PADDING_RIGHT;
-import static com.android.SdkConstants.ATTR_PADDING_START;
-import static com.android.SdkConstants.GRAVITY_VALUE_END;
-import static com.android.SdkConstants.GRAVITY_VALUE_LEFT;
-import static com.android.SdkConstants.GRAVITY_VALUE_RIGHT;
-import static com.android.SdkConstants.GRAVITY_VALUE_START;
-import static com.android.SdkConstants.TAG_APPLICATION;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.EnumConstant;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Identifier;
-import lombok.ast.ImportDeclaration;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Check which looks for RTL issues (right-to-left support) in layouts
- */
-public class RtlDetector extends LayoutDetector implements Detector.JavaScanner {
-
- @SuppressWarnings("unchecked")
- private static final Implementation IMPLEMENTATION = new Implementation(
- RtlDetector.class,
- EnumSet.of(Scope.RESOURCE_FILE, Scope.JAVA_FILE, Scope.MANIFEST),
- Scope.RESOURCE_FILE_SCOPE,
- Scope.JAVA_FILE_SCOPE,
- Scope.MANIFEST_SCOPE
- );
-
- public static final Issue USE_START = Issue.create(
- "RtlHardcoded", //$NON-NLS-1$
- "Using left/right instead of start/end attributes",
-
- "Using `Gravity#LEFT` and `Gravity#RIGHT` can lead to problems when a layout is " +
- "rendered in locales where text flows from right to left. Use `Gravity#START` " +
- "and `Gravity#END` instead. Similarly, in XML `gravity` and `layout_gravity` " +
- "attributes, use `start` rather than `left`.\n" +
- "\n" +
- "For XML attributes such as paddingLeft and `layout_marginLeft`, use `paddingStart` " +
- "and `layout_marginStart`. *NOTE*: If your `minSdkVersion` is less than 17, you should " +
- "add *both* the older left/right attributes *as well as* the new start/right " +
- "attributes. On older platforms, where RTL is not supported and the start/right " +
- "attributes are unknown and therefore ignored, you need the older left/right " +
- "attributes. There is a separate lint check which catches that type of error.\n" +
- "\n" +
- "(Note: For `Gravity#LEFT` and `Gravity#START`, you can use these constants even " +
- "when targeting older platforms, because the `start` bitmask is a superset of the " +
- "`left` bitmask. Therefore, you can use `gravity=\"start\"` rather than " +
- "`gravity=\"left|start\"`.)",
-
- Category.RTL, 5, Severity.WARNING, IMPLEMENTATION);
-
- public static final Issue COMPAT = Issue.create(
- "RtlCompat", //$NON-NLS-1$
- "Right-to-left text compatibility issues",
-
- "API 17 adds a `textAlignment` attribute to specify text alignment. However, " +
- "if you are supporting older versions than API 17, you must *also* specify a " +
- "gravity or layout_gravity attribute, since older platforms will ignore the " +
- "`textAlignment` attribute.",
-
- Category.RTL, 6, Severity.ERROR, IMPLEMENTATION);
-
- public static final Issue SYMMETRY = Issue.create(
- "RtlSymmetry", //$NON-NLS-1$
- "Padding and margin symmetry",
-
- "If you specify padding or margin on the left side of a layout, you should " +
- "probably also specify padding on the right side (and vice versa) for " +
- "right-to-left layout symmetry.",
-
- Category.RTL, 6, Severity.WARNING, IMPLEMENTATION);
-
-
- public static final Issue ENABLED = Issue.create(
- "RtlEnabled", //$NON-NLS-1$
- "Using RTL attributes without enabling RTL support",
-
- "To enable right-to-left support, when running on API 17 and higher, you must " +
- "set the `android:supportsRtl` attribute in the manifest `<application>` element.\n" +
- "\n" +
- "If you have started adding RTL attributes, but have not yet finished the " +
- "migration, you can set the attribute to false to satisfy this lint check.",
-
- Category.RTL, 3, Severity.WARNING, IMPLEMENTATION);
-
- /* TODO:
- public static final Issue FIELD = Issue.create(
- "RtlFieldAccess", //$NON-NLS-1$
- "Accessing margin and padding fields directly",
-
- "Modifying the padding and margin constants in view objects directly is " +
- "problematic when using RTL support, since it can lead to inconsistent states. You " +
- "*must* use the corresponding setter methods instead (`View#setPadding` etc).",
-
- Category.RTL, 3, Severity.WARNING, IMPLEMENTATION).setEnabledByDefault(false);
-
- public static final Issue AWARE = Issue.create(
- "RtlAware", //$NON-NLS-1$
- "View code not aware of RTL APIs",
-
- "When manipulating views, and especially when implementing custom layouts, " +
- "the code may need to be aware of RTL APIs. This lint check looks for usages of " +
- "APIs that frequently require adjustments for right-to-left text, and warns if it " +
- "does not also see text direction look-ups indicating that the code has already " +
- "been updated to handle RTL layouts.",
-
- Category.RTL, 3, Severity.WARNING, IMPLEMENTATION).setEnabledByDefault(false);
- */
-
- private static final String RIGHT_FIELD = "RIGHT"; //$NON-NLS-1$
- private static final String LEFT_FIELD = "LEFT"; //$NON-NLS-1$
- private static final String GRAVITY_CLASS = "Gravity"; //$NON-NLS-1$
- private static final String FQCN_GRAVITY = "android.view.Gravity"; //$NON-NLS-1$
- private static final String FQCN_GRAVITY_PREFIX = "android.view.Gravity."; //$NON-NLS-1$
- private static final String ATTR_SUPPORTS_RTL = "supportsRtl"; //$NON-NLS-1$
- private static final String ATTR_TEXT_ALIGNMENT = "textAlignment"; //$NON-NLS-1$
-
- /** API version in which RTL support was added */
- private static final int RTL_API = 17;
-
- private static final String LEFT = "Left";
- private static final String START = "Start";
- private static final String RIGHT = "Right";
- private static final String END = "End";
-
- private Boolean mEnabledRtlSupport;
- private boolean mUsesRtlAttributes;
-
- /** Constructs a new {@link RtlDetector} */
- public RtlDetector() {
- }
-
- @Override
- @NonNull
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- private boolean rtlApplies(@NonNull Context context) {
- Project project = context.getMainProject();
- if (project.getTargetSdk() < RTL_API) {
- return false;
- }
-
- int buildTarget = project.getBuildSdk();
- if (buildTarget != -1 && buildTarget < RTL_API) {
- return false;
- }
-
- //noinspection RedundantIfStatement
- if (mEnabledRtlSupport != null && !mEnabledRtlSupport) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mUsesRtlAttributes && mEnabledRtlSupport == null && rtlApplies(context)) {
- List<File> manifestFile = context.getMainProject().getManifestFiles();
- if (!manifestFile.isEmpty()) {
- Location location = Location.create(manifestFile.get(0));
- context.report(ENABLED, location,
- "The project references RTL attributes, but does not explicitly enable " +
- "or disable RTL support with `android:supportsRtl` in the manifest");
- }
- }
- }
-
- // ---- Implements XmlDetector ----
-
- @VisibleForTesting
- static final String[] ATTRIBUTES = new String[] {
- // Pairs, from left/right constants to corresponding start/end constants
- ATTR_LAYOUT_ALIGN_PARENT_LEFT, ATTR_LAYOUT_ALIGN_PARENT_START,
- ATTR_LAYOUT_ALIGN_PARENT_RIGHT, ATTR_LAYOUT_ALIGN_PARENT_END,
- ATTR_LAYOUT_MARGIN_LEFT, ATTR_LAYOUT_MARGIN_START,
- ATTR_LAYOUT_MARGIN_RIGHT, ATTR_LAYOUT_MARGIN_END,
- ATTR_PADDING_LEFT, ATTR_PADDING_START,
- ATTR_PADDING_RIGHT, ATTR_PADDING_END,
- ATTR_DRAWABLE_LEFT, ATTR_DRAWABLE_START,
- ATTR_DRAWABLE_RIGHT, ATTR_DRAWABLE_END,
- ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT, ATTR_LIST_PREFERRED_ITEM_PADDING_START,
- ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT, ATTR_LIST_PREFERRED_ITEM_PADDING_END,
-
- // RelativeLayout
- ATTR_LAYOUT_TO_LEFT_OF, ATTR_LAYOUT_TO_START_OF,
- ATTR_LAYOUT_TO_RIGHT_OF, ATTR_LAYOUT_TO_END_OF,
- ATTR_LAYOUT_ALIGN_LEFT, ATTR_LAYOUT_ALIGN_START,
- ATTR_LAYOUT_ALIGN_RIGHT, ATTR_LAYOUT_ALIGN_END,
- };
- static {
- if (LintUtils.assertionsEnabled()) {
- for (int i = 0; i < ATTRIBUTES.length; i += 2) {
- String replace = ATTRIBUTES[i];
- String with = ATTRIBUTES[i + 1];
- assert with.equals(convertOldToNew(replace));
- assert replace.equals(convertNewToOld(with));
- }
- }
- }
-
- public static boolean isRtlAttributeName(@NonNull String attribute) {
- for (int i = 1; i < ATTRIBUTES.length; i += 2) {
- if (attribute.equals(ATTRIBUTES[i])) {
- return true;
- }
- }
- return false;
- }
-
- @VisibleForTesting
- static String convertOldToNew(String attribute) {
- if (attribute.contains(LEFT)) {
- return attribute.replace(LEFT, START);
- } else {
- return attribute.replace(RIGHT, END);
- }
- }
-
- @VisibleForTesting
- static String convertNewToOld(String attribute) {
- if (attribute.contains(START)) {
- return attribute.replace(START, LEFT);
- } else {
- return attribute.replace(END, RIGHT);
- }
- }
-
- @VisibleForTesting
- static String convertToOppositeDirection(String attribute) {
- if (attribute.contains(LEFT)) {
- return attribute.replace(LEFT, RIGHT);
- } else if (attribute.contains(RIGHT)) {
- return attribute.replace(RIGHT, LEFT);
- } else if (attribute.contains(START)) {
- return attribute.replace(START, END);
- } else {
- return attribute.replace(END, START);
- }
- }
-
- @Nullable
- static String getTextAlignmentToGravity(String attribute) {
- if (attribute.endsWith(START)) { // textStart, viewStart, ...
- return GRAVITY_VALUE_START;
- } else if (attribute.endsWith(END)) { // textEnd, viewEnd, ...
- return GRAVITY_VALUE_END;
- } else {
- return null; // inherit, others
- }
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- int size = ATTRIBUTES.length + 4;
- List<String> attributes = new ArrayList<String>(size);
-
- // For detecting whether RTL support is enabled
- attributes.add(ATTR_SUPPORTS_RTL);
-
- // For detecting left/right attributes which should probably be
- // migrated to start/end
- attributes.add(ATTR_GRAVITY);
- attributes.add(ATTR_LAYOUT_GRAVITY);
-
- // For detecting existing attributes which indicate an attempt to
- // use RTL
- attributes.add(ATTR_TEXT_ALIGNMENT);
-
- // Add conversion attributes: left/right attributes to nominate
- // attributes that should be added as start/end, and start/end
- // attributes to use to look up elements that should have compatibility
- // left/right ones as well
- Collections.addAll(attributes, ATTRIBUTES);
-
- assert attributes.size() == size : attributes.size();
-
- return attributes;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- Project project = context.getMainProject();
- String value = attribute.getValue();
-
- if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
- // Layout attribute not in the Android namespace (or a custom namespace).
- // This is likely an application error (which should get caught by
- // the MissingPrefixDetector)
- return;
- }
-
- String name = attribute.getLocalName();
- assert name != null : attribute.getName();
-
- if (name.equals(ATTR_SUPPORTS_RTL)) {
- mEnabledRtlSupport = Boolean.valueOf(value);
- if (!attribute.getOwnerElement().getTagName().equals(TAG_APPLICATION)) {
- context.report(ENABLED, attribute, context.getLocation(attribute), String.format(
- "Wrong declaration: `%1$s` should be defined on the `<application>` element",
- attribute.getName()));
- }
- int targetSdk = project.getTargetSdk();
- if (mEnabledRtlSupport && targetSdk < RTL_API) {
- String message = String.format(
- "You must set `android:targetSdkVersion` to at least %1$d when "
- + "enabling RTL support (is %2$d)",
- RTL_API, project.getTargetSdk());
- context.report(ENABLED, attribute, context.getValueLocation(attribute), message);
- }
- return;
- }
-
- if (!rtlApplies(context)) {
- return;
- }
-
- if (name.equals(ATTR_TEXT_ALIGNMENT)) {
- if (context.getProject().getReportIssues()) {
- mUsesRtlAttributes = true;
- }
-
- Element element = attribute.getOwnerElement();
- final String gravity;
- final Attr gravityNode;
- if (element.hasAttributeNS(ANDROID_URI, ATTR_GRAVITY)) {
- gravityNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_GRAVITY);
- gravity = gravityNode.getValue();
- } else if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY)) {
- gravityNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY);
- gravity = gravityNode.getValue();
- } else if (project.getMinSdk() < RTL_API) {
- int folderVersion = context.getFolderVersion();
- if (folderVersion < RTL_API && context.isEnabled(COMPAT)) {
- String expectedGravity = getTextAlignmentToGravity(value);
- if (expectedGravity != null) {
- String message = String.format(
- "To support older versions than API 17 (project specifies %1$d) "
- + "you must *also* specify `gravity` or `layout_gravity=\"%2$s\"`",
- project.getMinSdk(), expectedGravity);
- context.report(COMPAT, attribute,
- context.getNameLocation(attribute), message);
- }
- }
- return;
- } else {
- return;
- }
-
- String expectedGravity = getTextAlignmentToGravity(value);
- if (expectedGravity != null && !gravity.contains(expectedGravity)
- && context.isEnabled(COMPAT)) {
- String message = String.format("Inconsistent alignment specification between "
- + "`textAlignment` and `gravity` attributes: was `%1$s`, expected `%2$s`",
- gravity, expectedGravity);
- Location location = context.getValueLocation(attribute);
- context.report(COMPAT, attribute, location, message);
- Location secondary = context.getValueLocation(gravityNode);
- secondary.setMessage("Incompatible direction here");
- location.setSecondary(secondary);
- }
- return;
- }
-
- if (name.equals(ATTR_GRAVITY) || name.equals(ATTR_LAYOUT_GRAVITY)) {
- boolean isLeft = value.contains(GRAVITY_VALUE_LEFT);
- boolean isRight = value.contains(GRAVITY_VALUE_RIGHT);
- if (!isLeft && !isRight) {
- if ((value.contains(GRAVITY_VALUE_START) || value.contains(GRAVITY_VALUE_END))
- && context.getProject().getReportIssues()) {
- mUsesRtlAttributes = true;
- }
- return;
- }
- String message = String.format(
- "Use \"`%1$s`\" instead of \"`%2$s`\" to ensure correct behavior in "
- + "right-to-left locales",
- isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END,
- isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT);
- if (context.isEnabled(USE_START)) {
- context.report(USE_START, attribute, context.getValueLocation(attribute), message);
- }
-
- return;
- }
-
- // Some other left/right/start/end attribute
- int targetSdk = project.getTargetSdk();
-
- // TODO: If attribute is drawableLeft or drawableRight, add note that you might
- // want to consider adding a specialized image in the -ldrtl folder as well
-
- Element element = attribute.getOwnerElement();
- boolean isPaddingAttribute = isPaddingAttribute(name);
- if (isPaddingAttribute || isMarginAttribute(name)) {
- String opposite = convertToOppositeDirection(name);
- if (element.hasAttributeNS(ANDROID_URI, opposite)) {
- String oldValue = element.getAttributeNS(ANDROID_URI, opposite);
- if (value.equals(oldValue)) {
- return;
- }
- } else if (isPaddingAttribute
- && !element.hasAttributeNS(ANDROID_URI,
- isOldAttribute(opposite) ? convertOldToNew(opposite)
- : convertNewToOld(opposite)) && context.isEnabled(SYMMETRY)) {
- String message = String.format(
- "When you define `%1$s` you should probably also define `%2$s` for "
- + "right-to-left symmetry", name, opposite);
- context.report(SYMMETRY, attribute, context.getNameLocation(attribute), message);
- }
- }
-
- boolean isOld = isOldAttribute(name);
- if (isOld) {
- if (!context.isEnabled(USE_START)) {
- return;
- }
- String rtl = convertOldToNew(name);
- if (element.hasAttributeNS(ANDROID_URI, rtl)) {
- if (project.getMinSdk() >= RTL_API || context.getFolderVersion() >= RTL_API) {
- // Warn that left/right isn't needed
- String message = String.format(
- "Redundant attribute `%1$s`; already defining `%2$s` with "
- + "`targetSdkVersion` %3$s",
- name, rtl, targetSdk);
- context.report(USE_START, attribute,
- context.getNameLocation(attribute), message);
- }
- } else {
- String message;
- if (project.getMinSdk() >= RTL_API || context.getFolderVersion() >= RTL_API) {
- message = String.format(
- "Consider replacing `%1$s` with `%2$s:%3$s=\"%4$s\"` to better support "
- + "right-to-left layouts",
- attribute.getName(), attribute.getPrefix(), rtl, value);
- } else {
- message = String.format(
- "Consider adding `%1$s:%2$s=\"%3$s\"` to better support "
- + "right-to-left layouts",
- attribute.getPrefix(), rtl, value);
- }
- context.report(USE_START, attribute,
- context.getNameLocation(attribute), message);
- }
- } else {
- if (project.getMinSdk() >= RTL_API || !context.isEnabled(COMPAT)) {
- // Only supporting 17+: no need to define older attributes
- return;
- }
- int folderVersion = context.getFolderVersion();
- if (folderVersion >= RTL_API) {
- // In a -v17 folder or higher: no need to define older attributes
- return;
- }
- String old = convertNewToOld(name);
- if (element.hasAttributeNS(ANDROID_URI, old)) {
- return;
- }
- String message = String.format(
- "To support older versions than API 17 (project specifies %1$d) "
- + "you should *also* add `%2$s:%3$s=\"%4$s\"`",
- project.getMinSdk(), attribute.getPrefix(), old,
- convertNewToOld(value));
- context.report(COMPAT, attribute, context.getNameLocation(attribute), message);
- }
- }
-
- private static boolean isOldAttribute(String name) {
- return name.contains(LEFT) || name.contains(RIGHT);
- }
-
- private static boolean isMarginAttribute(@NonNull String name) {
- return name.startsWith(ATTR_LAYOUT_MARGIN);
- }
-
- private static boolean isPaddingAttribute(@NonNull String name) {
- return name.startsWith(ATTR_PADDING);
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>>singletonList(Identifier.class);
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- if (rtlApplies(context)) {
- return new IdentifierChecker(context);
- }
-
- return new ForwardingAstVisitor() { };
- }
-
- private static class IdentifierChecker extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public IdentifierChecker(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitIdentifier(Identifier node) {
- String identifier = node.astValue();
- boolean isLeft = LEFT_FIELD.equals(identifier);
- boolean isRight = RIGHT_FIELD.equals(identifier);
- if (!isLeft && !isRight) {
- return false;
- }
-
- Node parent = node.getParent();
- if (parent instanceof ImportDeclaration || parent instanceof EnumConstant
- || parent instanceof VariableDefinitionEntry) {
- return false;
- }
-
- JavaParser.ResolvedNode resolved = mContext.resolve(node);
- if (resolved != null) {
- if (!(resolved instanceof JavaParser.ResolvedField)) {
- return false;
- } else {
- JavaParser.ResolvedField field = (JavaParser.ResolvedField) resolved;
- if (!field.getContainingClass().matches(FQCN_GRAVITY)) {
- return false;
- }
- }
- } else {
- // Can't resolve types (for example while editing code with errors):
- // rely on heuristics like import statements and class qualifiers
- if (parent instanceof Select &&
- !(GRAVITY_CLASS.equals(((Select) parent).astOperand().toString()))) {
- return false;
- }
- if (parent instanceof VariableReference) {
- // No operand: make sure it's statically imported
- if (!LintUtils.isImported(mContext.getCompilationUnit(),
- FQCN_GRAVITY_PREFIX + identifier)) {
- return false;
- }
- }
- }
-
- String message = String.format(
- "Use \"`Gravity.%1$s`\" instead of \"`Gravity.%2$s`\" to ensure correct "
- + "behavior in right-to-left locales",
- (isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END).toUpperCase(Locale.US),
- (isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT).toUpperCase(Locale.US));
- Location location = mContext.getLocation(node);
- mContext.report(USE_START, node, location, message);
-
- return true;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
deleted file mode 100644
index 48b1a35..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-import org.objectweb.asm.tree.analysis.BasicValue;
-import org.objectweb.asm.tree.analysis.Frame;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Checks for hardcoded seeds with random numbers.
- */
-public class SecureRandomDetector extends Detector implements ClassScanner {
- /** Unregistered activities and services */
- public static final Issue ISSUE = Issue.create(
- "SecureRandom", //$NON-NLS-1$
- "Using a fixed seed with `SecureRandom`",
-
- "Specifying a fixed seed will cause the instance to return a predictable sequence " +
- "of numbers. This may be useful for testing but it is not appropriate for secure use.",
-
- Category.SECURITY,
- 9,
- Severity.WARNING,
- new Implementation(
- SecureRandomDetector.class,
- Scope.CLASS_FILE_SCOPE))
- .addMoreInfo("http://developer.android.com/reference/java/security/SecureRandom.html");
-
- private static final String SET_SEED = "setSeed"; //$NON-NLS-1$
- static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; //$NON-NLS-1$
- private static final String OWNER_RANDOM = "java/util/Random"; //$NON-NLS-1$
- private static final String VM_SECURE_RANDOM = 'L' + OWNER_SECURE_RANDOM + ';';
- /** Method description for a method that takes a long argument (no return type specified */
- private static final String LONG_ARG = "(J)"; //$NON-NLS-1$
-
- /** Constructs a new {@link SecureRandomDetector} */
- public SecureRandomDetector() {
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Collections.singletonList(SET_SEED);
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String owner = call.owner;
- String desc = call.desc;
- if (owner.equals(OWNER_SECURE_RANDOM)) {
- if (desc.startsWith(LONG_ARG)) {
- checkValidSetSeed(context, call);
- } else if (desc.startsWith("([B)")) { //$NON-NLS-1$
- // setSeed(byte[]) ...
- // We could do some flow analysis here to see whether the byte array getting
- // passed in appears to be fixed.
- // However, people calling this constructor rather than the simpler one
- // with a fixed integer are probably less likely to make that mistake... right?
- }
- } else if (owner.equals(OWNER_RANDOM) && desc.startsWith(LONG_ARG)) {
- // Called setSeed(long) on an instanceof a Random object. Flag this if the instance
- // is likely a SecureRandom.
-
- // Track allocations such that we know whether the type of the call
- // is on a SecureRandom rather than a Random
- Analyzer analyzer = new Analyzer(new BasicInterpreter() {
- @Override
- public BasicValue newValue(Type type) {
- if (type != null && type.getDescriptor().equals(VM_SECURE_RANDOM)) {
- return new BasicValue(type);
- }
- return super.newValue(type);
- }
- });
- try {
- Frame[] frames = analyzer.analyze(classNode.name, method);
- InsnList instructions = method.instructions;
- Frame frame = frames[instructions.indexOf(call)];
- int stackSlot = frame.getStackSize();
- for (Type type : Type.getArgumentTypes(desc)) {
- stackSlot -= type.getSize();
- }
- BasicValue stackValue = (BasicValue) frame.getStack(stackSlot);
- Type type = stackValue.getType();
- if (type != null && type.getDescriptor().equals(VM_SECURE_RANDOM)) {
- checkValidSetSeed(context, call);
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- } else if (owner.equals(OWNER_RANDOM) && desc.startsWith(LONG_ARG)) {
- // Called setSeed(long) on an instanceof a Random object. Flag this if the instance
- // is likely a SecureRandom.
- // TODO
- }
- }
-
- private static void checkValidSetSeed(ClassContext context, MethodInsnNode call) {
- assert call.name.equals(SET_SEED);
-
- // Make sure the argument passed is not a literal
- AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
- if (prev == null) {
- return;
- }
- int opcode = prev.getOpcode();
- if (opcode == Opcodes.LCONST_0 || opcode == Opcodes.LCONST_1 || opcode == Opcodes.LDC) {
- context.report(ISSUE, context.getLocation(call),
- "Do not call `setSeed()` on a `SecureRandom` with a fixed seed: " +
- "it is not secure. Use `getSeed()`.");
- } else if (opcode == Opcodes.INVOKESTATIC) {
- String methodName = ((MethodInsnNode) prev).name;
- if (methodName.equals("currentTimeMillis") || methodName.equals("nanoTime")) {
- context.report(ISSUE, context.getLocation(call),
- "It is dangerous to seed `SecureRandom` with the current time because " +
- "that value is more predictable to an attacker than the default seed.");
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
deleted file mode 100644
index fc3bd2f..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.tools.lint.checks.SecureRandomDetector.OWNER_SECURE_RANDOM;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Checks for pseudo random number generator initialization issues
- */
-public class SecureRandomGeneratorDetector extends Detector implements ClassScanner {
-
- @SuppressWarnings("SpellCheckingInspection")
- private static final String BLOG_URL
- = "https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html";
-
- /** Whether the random number generator is initialized correctly */
- public static final Issue ISSUE = Issue.create(
- "TrulyRandom", //$NON-NLS-1$
- "Weak RNG",
- "Key generation, signing, encryption, and random number generation may not " +
- "receive cryptographically strong values due to improper initialization of " +
- "the underlying PRNG on Android 4.3 and below.\n" +
- "\n" +
- "If your application relies on cryptographically secure random number generation " +
- "you should apply the workaround described in " + BLOG_URL + " .\n" +
- "\n" +
- "This lint rule is mostly informational; it does not accurately detect whether " +
- "cryptographically secure RNG is required, or whether the workaround has already " +
- "been applied. After reading the blog entry and updating your code if necessary, " +
- "you can disable this lint issue.",
-
- Category.SECURITY,
- 9,
- Severity.WARNING,
- new Implementation(
- SecureRandomGeneratorDetector.class,
- Scope.CLASS_FILE_SCOPE))
- .addMoreInfo(BLOG_URL);
-
- private static final String WRAP = "wrap"; //$NON-NLS-1$
- private static final String UNWRAP = "unwrap"; //$NON-NLS-1$
- private static final String INIT = "init"; //$NON-NLS-1$
- private static final String INIT_SIGN = "initSign"; //$NON-NLS-1$
- private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
- private static final String FOR_NAME = "forName"; //$NON-NLS-1$
- private static final String JAVA_LANG_CLASS = "java/lang/Class"; //$NON-NLS-1$
- private static final String JAVAX_CRYPTO_KEY_GENERATOR = "javax/crypto/KeyGenerator";
- private static final String JAVAX_CRYPTO_KEY_AGREEMENT = "javax/crypto/KeyAgreement";
- private static final String JAVA_SECURITY_KEY_PAIR_GENERATOR =
- "java/security/KeyPairGenerator";
- private static final String JAVAX_CRYPTO_SIGNATURE = "javax/crypto/Signature";
- private static final String JAVAX_CRYPTO_CIPHER = "javax/crypto/Cipher";
- private static final String JAVAX_NET_SSL_SSLENGINE = "javax/net/ssl/SSLEngine";
-
- /** Constructs a new {@link SecureRandomGeneratorDetector} */
- public SecureRandomGeneratorDetector() {
- }
-
- // ---- Implements ClassScanner ----
-
- @Nullable
- @Override
- public List<String> getApplicableCallOwners() {
- return Arrays.asList(
- JAVAX_CRYPTO_KEY_GENERATOR,
- JAVA_SECURITY_KEY_PAIR_GENERATOR,
- JAVAX_CRYPTO_KEY_AGREEMENT,
- OWNER_SECURE_RANDOM,
- JAVAX_NET_SSL_SSLENGINE,
- JAVAX_CRYPTO_SIGNATURE,
- JAVAX_CRYPTO_CIPHER
- );
- }
-
- @Nullable
- @Override
- public List<String> getApplicableCallNames() {
- return Collections.singletonList(FOR_NAME);
- }
-
- /** Location of first call to key generator (etc), if any */
- private Location mLocation;
-
- /** Whether the issue should be ignored (because we have a workaround, or because
- * we're only targeting correct implementations, etc */
- private boolean mIgnore;
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- if (mIgnore) {
- return;
- }
-
- String owner = call.owner;
- String name = call.name;
-
- // Look for the workaround code: if we see a Class.forName on the harmony NativeCrypto,
- // we'll consider that a sign.
-
- if (name.equals(FOR_NAME)) {
- if (call.getOpcode() != Opcodes.INVOKESTATIC ||
- !owner.equals(JAVA_LANG_CLASS)) {
- return;
- }
- AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
- if (prev instanceof LdcInsnNode) {
- Object cst = ((LdcInsnNode)prev).cst;
- //noinspection SpellCheckingInspection
- if (cst instanceof String &&
- "org.apache.harmony.xnet.provider.jsse.NativeCrypto".equals(cst)) {
- mIgnore = true;
- }
- }
- return;
- }
-
- // Look for calls that probably require a properly initialized random number generator.
- assert owner.equals(JAVAX_CRYPTO_KEY_GENERATOR)
- || owner.equals(JAVA_SECURITY_KEY_PAIR_GENERATOR)
- || owner.equals(JAVAX_CRYPTO_KEY_AGREEMENT)
- || owner.equals(OWNER_SECURE_RANDOM)
- || owner.equals(JAVAX_CRYPTO_CIPHER)
- || owner.equals(JAVAX_CRYPTO_SIGNATURE)
- || owner.equals(JAVAX_NET_SSL_SSLENGINE) : owner;
- boolean warn = false;
-
- if (owner.equals(JAVAX_CRYPTO_SIGNATURE)) {
- warn = name.equals(INIT_SIGN);
- } else if (owner.equals(JAVAX_CRYPTO_CIPHER)) {
- if (name.equals(INIT)) {
- int arity = getDescArity(call.desc);
- AbstractInsnNode node = call;
- for (int i = 0; i < arity; i++) {
- node = LintUtils.getPrevInstruction(node);
- if (node == null) {
- break;
- }
- }
- if (node != null) {
- int opcode = node.getOpcode();
- if (opcode == Opcodes.ICONST_3 || // Cipher.WRAP_MODE
- opcode == Opcodes.ICONST_1) { // Cipher.ENCRYPT_MODE
- warn = true;
- }
- }
- }
- } else if (name.equals(GET_INSTANCE) || name.equals(CONSTRUCTOR_NAME)
- || name.equals(WRAP) || name.equals(UNWRAP)) { // For SSLEngine
- warn = true;
- }
-
- if (warn) {
- if (mLocation != null) {
- return;
- }
- if (context.getMainProject().getMinSdk() > 18) {
- // Fix no longer needed
- mIgnore = true;
- return;
- }
-
- if (context.getDriver().isSuppressed(ISSUE, classNode, method, call)) {
- mIgnore = true;
- } else {
- mLocation = context.getLocation(call);
- }
- }
- }
-
- @VisibleForTesting
- static int getDescArity(String desc) {
- int arity = 0;
- // For example, (ILjava/security/Key;)V => 2
- for (int i = 1, max = desc.length(); i < max; i++) {
- char c = desc.charAt(i);
- if (c == ')') {
- break;
- } else if (c == 'L') {
- arity++;
- i = desc.indexOf(';', i);
- assert i != -1 : desc;
- } else {
- arity++;
- }
- }
-
- return arity;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mLocation != null && !mIgnore) {
- String message = "Potentially insecure random numbers on Android 4.3 and older. "
- + "Read " + BLOG_URL + " for more info.";
- context.report(ISSUE, mLocation, message);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
deleted file mode 100644
index 9071724..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_EXPORTED;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PATH;
-import static com.android.SdkConstants.ATTR_PATH_PATTERN;
-import static com.android.SdkConstants.ATTR_PATH_PREFIX;
-import static com.android.SdkConstants.ATTR_PERMISSION;
-import static com.android.SdkConstants.ATTR_READ_PERMISSION;
-import static com.android.SdkConstants.ATTR_WRITE_PERMISSION;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_GRANT_PERMISSION;
-import static com.android.SdkConstants.TAG_INTENT_FILTER;
-import static com.android.SdkConstants.TAG_PATH_PERMISSION;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-import static com.android.xml.AndroidManifest.NODE_ACTION;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Identifier;
-import lombok.ast.MethodInvocation;
-import lombok.ast.StrictListAccessor;
-
-/**
- * Checks that exported services request a permission.
- */
-public class SecurityDetector extends Detector implements Detector.XmlScanner,
- Detector.JavaScanner {
-
- private static final Implementation IMPLEMENTATION_MANIFEST = new Implementation(
- SecurityDetector.class,
- Scope.MANIFEST_SCOPE);
-
- private static final Implementation IMPLEMENTATION_JAVA = new Implementation(
- SecurityDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Exported services */
- public static final Issue EXPORTED_SERVICE = Issue.create(
- "ExportedService", //$NON-NLS-1$
- "Exported service does not require permission",
- "Exported services (services which either set `exported=true` or contain " +
- "an intent-filter and do not specify `exported=false`) should define a " +
- "permission that an entity must have in order to launch the service " +
- "or bind to it. Without this, any application can use this service.",
- Category.SECURITY,
- 5,
- Severity.WARNING,
- IMPLEMENTATION_MANIFEST);
-
- /** Exported content providers */
- public static final Issue EXPORTED_PROVIDER = Issue.create(
- "ExportedContentProvider", //$NON-NLS-1$
- "Content provider does not require permission",
- "Content providers are exported by default and any application on the " +
- "system can potentially use them to read and write data. If the content " +
- "provider provides access to sensitive data, it should be protected by " +
- "specifying `export=false` in the manifest or by protecting it with a " +
- "permission that can be granted to other applications.",
- Category.SECURITY,
- 5,
- Severity.WARNING,
- IMPLEMENTATION_MANIFEST);
-
- /** Exported receivers */
- public static final Issue EXPORTED_RECEIVER = Issue.create(
- "ExportedReceiver", //$NON-NLS-1$
- "Receiver does not require permission",
- "Exported receivers (receivers which either set `exported=true` or contain " +
- "an intent-filter and do not specify `exported=false`) should define a " +
- "permission that an entity must have in order to launch the receiver " +
- "or bind to it. Without this, any application can use this receiver.",
- Category.SECURITY,
- 5,
- Severity.WARNING,
- IMPLEMENTATION_MANIFEST);
-
- /** Content provides which grant all URIs access */
- public static final Issue OPEN_PROVIDER = Issue.create(
- "GrantAllUris", //$NON-NLS-1$
- "Content provider shares everything",
- "The `<grant-uri-permission>` element allows specific paths to be shared. " +
- "This detector checks for a path URL of just '/' (everything), which is " +
- "probably not what you want; you should limit access to a subset.",
- Category.SECURITY,
- 7,
- Severity.WARNING,
- IMPLEMENTATION_MANIFEST);
-
- /** Using the world-writable flag */
- public static final Issue WORLD_WRITEABLE = Issue.create(
- "WorldWriteableFiles", //$NON-NLS-1$
- "`openFileOutput()` call passing `MODE_WORLD_WRITEABLE`",
- "There are cases where it is appropriate for an application to write " +
- "world writeable files, but these should be reviewed carefully to " +
- "ensure that they contain no private data, and that if the file is " +
- "modified by a malicious application it does not trick or compromise " +
- "your application.",
- Category.SECURITY,
- 4,
- Severity.WARNING,
- IMPLEMENTATION_JAVA);
-
-
- /** Using the world-readable flag */
- public static final Issue WORLD_READABLE = Issue.create(
- "WorldReadableFiles", //$NON-NLS-1$
- "`openFileOutput()` call passing `MODE_WORLD_READABLE`",
- "There are cases where it is appropriate for an application to write " +
- "world readable files, but these should be reviewed carefully to " +
- "ensure that they contain no private data that is leaked to other " +
- "applications.",
- Category.SECURITY,
- 4,
- Severity.WARNING,
- IMPLEMENTATION_JAVA);
-
- /** Constructs a new {@link SecurityDetector} check */
- public SecurityDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- // ---- Implements Detector.XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_SERVICE,
- TAG_GRANT_PERMISSION,
- TAG_PROVIDER,
- TAG_ACTIVITY,
- TAG_RECEIVER
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
- if (tag.equals(TAG_SERVICE)) {
- checkService(context, element);
- } else if (tag.equals(TAG_GRANT_PERMISSION)) {
- checkGrantPermission(context, element);
- } else if (tag.equals(TAG_PROVIDER)) {
- checkProvider(context, element);
- } else if (tag.equals(TAG_RECEIVER)) {
- checkReceiver(context, element);
- }
- }
-
- public static boolean getExported(Element element) {
- // Used to check whether an activity, service or broadcast receiver is exported.
- String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
- if (exportValue != null && !exportValue.isEmpty()) {
- return Boolean.valueOf(exportValue);
- } else {
- for (Element child : LintUtils.getChildren(element)) {
- if (child.getTagName().equals(TAG_INTENT_FILTER)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static boolean isUnprotectedByPermission(Element element) {
- // Used to check whether an activity, service or broadcast receiver are
- // protected by a permission.
- String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- Node parent = element.getParentNode();
- if (parent.getNodeType() == Node.ELEMENT_NODE
- && parent.getNodeName().equals(TAG_APPLICATION)) {
- Element application = (Element) parent;
- permission = application.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
- return permission == null || permission.isEmpty();
- }
- }
-
- return false;
- }
-
- private static boolean isWearableBindListener(@NonNull Element element) {
- // Checks whether a service has an Android Wear bind listener
- for (Element child : LintUtils.getChildren(element)) {
- if (child.getTagName().equals(TAG_INTENT_FILTER)) {
- for (Element innerChild : LintUtils.getChildren(child)) {
- if (innerChild.getTagName().equals(NODE_ACTION)) {
- String name = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if ("com.google.android.gms.wearable.BIND_LISTENER".equals(name)) {
- return true;
- }
- }
- }
- }
- }
-
- return false;
- }
-
- private static boolean isStandardReceiver(Element element) {
- // Play Services also the following receiver which we'll consider standard
- // in the sense that it doesn't require a separate permission
- String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if ("com.google.android.gms.tagmanager.InstallReferrerReceiver".equals(name)) {
- return true;
- }
-
- // Checks whether a broadcast receiver receives a standard Android action
- for (Element child : LintUtils.getChildren(element)) {
- if (child.getTagName().equals(TAG_INTENT_FILTER)) {
- for (Element innerChild : LintUtils.getChildren(child)) {
- if (innerChild.getTagName().equals(NODE_ACTION)) {
- String categoryString = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
- return categoryString.startsWith("android."); //$NON-NLS-1$
- }
- }
- }
- }
-
- return false;
- }
-
- private static void checkReceiver(XmlContext context, Element element) {
- if (getExported(element) && isUnprotectedByPermission(element) &&
- !isStandardReceiver(element)) {
- // No declared permission for this exported receiver: complain
- context.report(EXPORTED_RECEIVER, element, context.getLocation(element),
- "Exported receiver does not require permission");
- }
- }
-
- private static void checkService(XmlContext context, Element element) {
- if (getExported(element) && isUnprotectedByPermission(element)
- && !isWearableBindListener(element)) {
- // No declared permission for this exported service: complain
- context.report(EXPORTED_SERVICE, element, context.getLocation(element),
- "Exported service does not require permission");
- }
- }
-
- private static void checkGrantPermission(XmlContext context, Element element) {
- Attr path = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH);
- Attr prefix = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PREFIX);
- Attr pattern = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PATTERN);
-
- String msg = "Content provider shares everything; this is potentially dangerous.";
- if (path != null && path.getValue().equals("/")) { //$NON-NLS-1$
- context.report(OPEN_PROVIDER, path, context.getLocation(path), msg);
- }
- if (prefix != null && prefix.getValue().equals("/")) { //$NON-NLS-1$
- context.report(OPEN_PROVIDER, prefix, context.getLocation(prefix), msg);
- }
- if (pattern != null && (pattern.getValue().equals("/") //$NON-NLS-1$
- /* || pattern.getValue().equals(".*")*/)) {
- context.report(OPEN_PROVIDER, pattern, context.getLocation(pattern), msg);
- }
- }
-
- private static void checkProvider(XmlContext context, Element element) {
- String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
- // Content providers are exported by default
- boolean exported = true;
- if (exportValue != null && !exportValue.isEmpty()) {
- exported = Boolean.valueOf(exportValue);
- }
-
- if (exported) {
- // Just check for some use of permissions. Other Lint checks can check the saneness
- // of the permissions. We'll accept the permission, readPermission, or writePermission
- // attributes on the provider element, or a path-permission element.
- String permission = element.getAttributeNS(ANDROID_URI, ATTR_READ_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- permission = element.getAttributeNS(ANDROID_URI, ATTR_WRITE_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- // No permission attributes? Check for path-permission.
-
- // TODO: Add a Lint check to ensure the path-permission is good, similar to
- // the grant-uri-permission check.
- boolean hasPermission = false;
- for (Element child : LintUtils.getChildren(element)) {
- String tag = child.getTagName();
- if (tag.equals(TAG_PATH_PERMISSION)) {
- hasPermission = true;
- break;
- }
- }
-
- if (!hasPermission) {
- context.report(EXPORTED_PROVIDER, element,
- context.getLocation(element),
- "Exported content providers can provide access to " +
- "potentially sensitive data");
- }
- }
- }
- }
- }
- }
-
- // ---- Implements Detector.JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- // These are the API calls that can accept a MODE_WORLD_READABLE/MODE_WORLD_WRITABLE
- // argument.
- List<String> values = new ArrayList<String>(2);
- values.add("openFileOutput"); //$NON-NLS-1$
- values.add("getSharedPreferences"); //$NON-NLS-1$
- return values;
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- StrictListAccessor<Expression,MethodInvocation> args = node.astArguments();
- for (Expression arg : args) {
- arg.accept(visitor);
- }
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new IdentifierVisitor(context);
- }
-
- private static class IdentifierVisitor extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public IdentifierVisitor(JavaContext context) {
- super();
- mContext = context;
- }
-
- @Override
- public boolean visitIdentifier(Identifier node) {
- if ("MODE_WORLD_WRITEABLE".equals(node.astValue())) { //$NON-NLS-1$
- Location location = mContext.getLocation(node);
- mContext.report(WORLD_WRITEABLE, node, location,
- "Using `MODE_WORLD_WRITEABLE` when creating files can be " +
- "risky, review carefully");
- } else if ("MODE_WORLD_READABLE".equals(node.astValue())) { //$NON-NLS-1$
- Location location = mContext.getLocation(node);
- mContext.report(WORLD_READABLE, node, location,
- "Using `MODE_WORLD_READABLE` when creating files can be " +
- "risky, review carefully");
- }
-
- return false;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
deleted file mode 100644
index f11eaf4..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.collect.Maps;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Cast;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.StrictListAccessor;
-
-/**
- * Detector looking for casts on th result of context.getSystemService which are suspect
- */
-public class ServiceCastDetector extends Detector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ServiceCast", //$NON-NLS-1$
- "Wrong system service casts",
-
- "When you call `Context#getSystemService()`, the result is typically cast to " +
- "a specific interface. This lint check ensures that the cast is compatible with " +
- "the expected type of the return value.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- new Implementation(
- ServiceCastDetector.class,
- Scope.JAVA_FILE_SCOPE));
-
- /** Constructs a new {@link ServiceCastDetector} check */
- public ServiceCastDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("getSystemService"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- if (node.getParent() instanceof Cast) {
- Cast cast = (Cast) node.getParent();
- StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
- if (args.size() == 1) {
- String name = stripPackage(args.first().toString());
- String expectedClass = getExpectedType(name);
- if (expectedClass != null) {
- String castType = cast.astTypeReference().getTypeName();
- if (castType.indexOf('.') == -1) {
- expectedClass = stripPackage(expectedClass);
- }
- if (!castType.equals(expectedClass)) {
- // It's okay to mix and match
- // android.content.ClipboardManager and android.text.ClipboardManager
- if (isClipboard(castType) && isClipboard(expectedClass)) {
- return;
- }
-
- String message = String.format(
- "Suspicious cast to `%1$s` for a `%2$s`: expected `%3$s`",
- stripPackage(castType), name, stripPackage(expectedClass));
- context.report(ISSUE, node, context.getLocation(cast), message);
- }
- }
-
- }
- }
- }
-
- private static boolean isClipboard(String cls) {
- return cls.equals("android.content.ClipboardManager") //$NON-NLS-1$
- || cls.equals("android.text.ClipboardManager"); //$NON-NLS-1$
- }
-
- private static String stripPackage(String fqcn) {
- int index = fqcn.lastIndexOf('.');
- if (index != -1) {
- fqcn = fqcn.substring(index + 1);
- }
-
- return fqcn;
- }
-
- @Nullable
- private static String getExpectedType(@NonNull String value) {
- return getServiceMap().get(value);
- }
-
- @NonNull
- private static Map<String, String> getServiceMap() {
- if (sServiceMap == null) {
- final int EXPECTED_SIZE = 49;
- sServiceMap = Maps.newHashMapWithExpectedSize(EXPECTED_SIZE);
-
- sServiceMap.put("ACCESSIBILITY_SERVICE", "android.view.accessibility.AccessibilityManager");
- sServiceMap.put("ACCOUNT_SERVICE", "android.accounts.AccountManager");
- sServiceMap.put("ACTIVITY_SERVICE", "android.app.ActivityManager");
- sServiceMap.put("ALARM_SERVICE", "android.app.AlarmManager");
- sServiceMap.put("APPWIDGET_SERVICE", "android.appwidget.AppWidgetManager");
- sServiceMap.put("APP_OPS_SERVICE", "android.app.AppOpsManager");
- sServiceMap.put("AUDIO_SERVICE", "android.media.AudioManager");
- sServiceMap.put("BATTERY_SERVICE", "android.os.BatteryManager");
- sServiceMap.put("BLUETOOTH_SERVICE", "android.bluetooth.BluetoothManager");
- sServiceMap.put("CAMERA_SERVICE", "android.hardware.camera2.CameraManager");
- sServiceMap.put("CAPTIONING_SERVICE", "android.view.accessibility.CaptioningManager");
- sServiceMap.put("CLIPBOARD_SERVICE", "android.text.ClipboardManager");
- sServiceMap.put("CONNECTIVITY_SERVICE", "android.net.ConnectivityManager");
- sServiceMap.put("CONSUMER_IR_SERVICE", "android.hardware.ConsumerIrManager");
- sServiceMap.put("DEVICE_POLICY_SERVICE", "android.app.admin.DevicePolicyManager");
- sServiceMap.put("DISPLAY_SERVICE", "android.hardware.display.DisplayManager");
- sServiceMap.put("DOWNLOAD_SERVICE", "android.app.DownloadManager");
- sServiceMap.put("DROPBOX_SERVICE", "android.os.DropBoxManager");
- sServiceMap.put("INPUT_METHOD_SERVICE", "android.view.inputmethod.InputMethodManager");
- sServiceMap.put("INPUT_SERVICE", "android.hardware.input.InputManager");
- sServiceMap.put("JOB_SCHEDULER_SERVICE", "android.app.job.JobScheduler");
- sServiceMap.put("KEYGUARD_SERVICE", "android.app.KeyguardManager");
- sServiceMap.put("LAUNCHER_APPS_SERVICE", "android.content.pm.LauncherApps");
- sServiceMap.put("LAYOUT_INFLATER_SERVICE", "android.view.LayoutInflater");
- sServiceMap.put("LOCATION_SERVICE", "android.location.LocationManager");
- sServiceMap.put("MEDIA_PROJECTION_SERVICE", "android.media.projection.MediaProjectionManager");
- sServiceMap.put("MEDIA_ROUTER_SERVICE", "android.media.MediaRouter");
- sServiceMap.put("MEDIA_SESSION_SERVICE", "android.media.session.MediaSessionManager");
- sServiceMap.put("NFC_SERVICE", "android.nfc.NfcManager");
- sServiceMap.put("NOTIFICATION_SERVICE", "android.app.NotificationManager");
- sServiceMap.put("NSD_SERVICE", "android.net.nsd.NsdManager");
- sServiceMap.put("POWER_SERVICE", "android.os.PowerManager");
- sServiceMap.put("PRINT_SERVICE", "android.print.PrintManager");
- sServiceMap.put("RESTRICTIONS_SERVICE", "android.content.RestrictionsManager");
- sServiceMap.put("SEARCH_SERVICE", "android.app.SearchManager");
- sServiceMap.put("SENSOR_SERVICE", "android.hardware.SensorManager");
- sServiceMap.put("STORAGE_SERVICE", "android.os.storage.StorageManager");
- sServiceMap.put("TELECOM_SERVICE", "android.telecom.TelecomManager");
- sServiceMap.put("TELEPHONY_SERVICE", "android.telephony.TelephonyManager");
- sServiceMap.put("TEXT_SERVICES_MANAGER_SERVICE", "android.view.textservice.TextServicesManager");
- sServiceMap.put("TV_INPUT_SERVICE", "android.media.tv.TvInputManager");
- sServiceMap.put("UI_MODE_SERVICE", "android.app.UiModeManager");
- sServiceMap.put("USB_SERVICE", "android.hardware.usb.UsbManager");
- sServiceMap.put("USER_SERVICE", "android.os.UserManager");
- sServiceMap.put("VIBRATOR_SERVICE", "android.os.Vibrator");
- sServiceMap.put("WALLPAPER_SERVICE", "com.android.server.WallpaperService");
- sServiceMap.put("WIFI_P2P_SERVICE", "android.net.wifi.p2p.WifiP2pManager");
- sServiceMap.put("WIFI_SERVICE", "android.net.wifi.WifiManager");
- sServiceMap.put("WINDOW_SERVICE", "android.view.WindowManager");
-
- assert sServiceMap.size() == EXPECTED_SIZE : sServiceMap.size();
- }
-
- return sServiceMap;
- }
-
- private static Map<String, String> sServiceMap;
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
deleted file mode 100644
index 2310d23..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.Assert;
-import lombok.ast.AstVisitor;
-import lombok.ast.Block;
-import lombok.ast.Case;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.DoWhile;
-import lombok.ast.Expression;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.For;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.If;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.Return;
-import lombok.ast.Statement;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableReference;
-import lombok.ast.While;
-
-/**
- * Detector looking for SharedPreferences.edit() calls without a corresponding
- * commit() or apply() call
- */
-public class SharedPrefsDetector extends Detector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "CommitPrefEdits", //$NON-NLS-1$
- "Missing `commit()` on `SharedPreference` editor",
-
- "After calling `edit()` on a `SharedPreference`, you must call `commit()` " +
- "or `apply()` on the editor to save the results.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- new Implementation(
- SharedPrefsDetector.class,
- Scope.JAVA_FILE_SCOPE));
-
- public static final String ANDROID_CONTENT_SHARED_PREFERENCES =
- "android.content.SharedPreferences"; //$NON-NLS-1$
- private static final String ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR =
- "android.content.SharedPreferences.Editor"; //$NON-NLS-1$
-
- /** Constructs a new {@link SharedPrefsDetector} check */
- public SharedPrefsDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("edit"); //$NON-NLS-1$
- }
-
- @Nullable
- private static NormalTypeBody findSurroundingTypeBody(Node scope) {
- while (scope != null) {
- Class<? extends Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == NormalTypeBody.class) {
- return (NormalTypeBody) scope;
- }
-
- scope = scope.getParent();
- }
-
- return null;
- }
-
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- assert node.astName().astValue().equals("edit");
-
- boolean verifiedType = false;
- ResolvedNode resolve = context.resolve(node);
- if (resolve instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolve;
- TypeDescriptor returnType = method.getReturnType();
- if (returnType == null ||
- !returnType.matchesName(ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR)) {
- return;
- }
- verifiedType = true;
- }
-
- Expression operand = node.astOperand();
- if (operand == null) {
- return;
- }
-
- // Looking for the specific pattern where you assign the edit() result
- // to a local variable; this means we won't recognize some other usages
- // of the API (e.g. assigning it to a previously declared variable) but
- // is needed until we have type attribution in the AST itself.
- Node parent = node.getParent();
-
- VariableDefinition definition = getLhs(parent);
- boolean allowCommitBeforeTarget;
- if (definition == null) {
- if (operand instanceof VariableReference) {
- if (!verifiedType) {
- NormalTypeBody body = findSurroundingTypeBody(parent);
- if (body == null) {
- return;
- }
- String variableName = ((VariableReference) operand).astIdentifier().astValue();
- String type = getFieldType(body, variableName);
- if (type == null || !type.equals("SharedPreferences")) { //$NON-NLS-1$
- return;
- }
- }
- allowCommitBeforeTarget = true;
- } else {
- return;
- }
- } else {
- if (!verifiedType) {
- String type = definition.astTypeReference().toString();
- if (!type.endsWith("SharedPreferences.Editor")) { //$NON-NLS-1$
- if (!type.equals("Editor") || //$NON-NLS-1$
- !LintUtils.isImported(context.getCompilationUnit(),
- ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR)) {
- return;
- }
- }
- }
- allowCommitBeforeTarget = false;
- }
-
- Node method = JavaContext.findSurroundingMethod(parent);
- if (method == null) {
- return;
- }
-
- CommitFinder finder = new CommitFinder(context, node, allowCommitBeforeTarget);
- method.accept(finder);
- if (!finder.isCommitCalled()) {
- context.report(ISSUE, method, context.getLocation(node),
- "`SharedPreferences.edit()` without a corresponding `commit()` or `apply()` call");
- }
- }
-
- @Nullable
- private static String getFieldType(@NonNull NormalTypeBody cls, @NonNull String name) {
- List<Node> children = cls.getChildren();
- for (Node child : children) {
- if (child.getClass() == VariableDeclaration.class) {
- VariableDeclaration declaration = (VariableDeclaration) child;
- VariableDefinition definition = declaration.astDefinition();
- return definition.astTypeReference().toString();
- }
- }
-
- return null;
- }
-
- @Nullable
- private static VariableDefinition getLhs(@NonNull Node node) {
- while (node != null) {
- Class<? extends Node> type = node.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) {
- return null;
- }
- if (type == VariableDefinition.class) {
- return (VariableDefinition) node;
- }
-
- node = node.getParent();
- }
-
- return null;
- }
-
- private static class CommitFinder extends ForwardingAstVisitor {
- /** The target edit call */
- private final MethodInvocation mTarget;
- /** whether it allows the commit call to be seen before the target node */
- private final boolean mAllowCommitBeforeTarget;
-
- private final JavaContext mContext;
-
- /** Whether we've found one of the commit/cancel methods */
- private boolean mFound;
- /** Whether we've seen the target edit node yet */
- private boolean mSeenTarget;
-
- private CommitFinder(JavaContext context, MethodInvocation target,
- boolean allowCommitBeforeTarget) {
- mContext = context;
- mTarget = target;
- mAllowCommitBeforeTarget = allowCommitBeforeTarget;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node == mTarget) {
- mSeenTarget = true;
- } else if (mAllowCommitBeforeTarget || mSeenTarget || node.astOperand() == mTarget) {
- String name = node.astName().astValue();
- boolean isCommit = "commit".equals(name);
- if (isCommit || "apply".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
- // TODO: Do more flow analysis to see whether we're really calling commit/apply
- // on the right type of object?
- mFound = true;
-
- ResolvedNode resolved = mContext.resolve(node);
- if (resolved instanceof JavaParser.ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- JavaParser.ResolvedClass clz = method.getContainingClass();
- if (clz.isSubclassOf("android.content.SharedPreferences.Editor", false)
- && mContext.getProject().getMinSdkVersion().getApiLevel() >= 9) {
- // See if the return value is read: can only replace commit with
- // apply if the return value is not considered
- Node parent = node.getParent();
- boolean returnValueIgnored = false;
- if (parent instanceof MethodDeclaration ||
- parent instanceof ConstructorDeclaration ||
- parent instanceof ClassDeclaration ||
- parent instanceof Block ||
- parent instanceof ExpressionStatement) {
- returnValueIgnored = true;
- } else if (parent instanceof Statement) {
- if (parent instanceof If) {
- returnValueIgnored = ((If) parent).astCondition() != node;
- } else if (parent instanceof Return) {
- returnValueIgnored = false;
- } else if (parent instanceof VariableDeclaration) {
- returnValueIgnored = false;
- } else if (parent instanceof For) {
- returnValueIgnored = ((For) parent).astCondition() != node;
- } else if (parent instanceof While) {
- returnValueIgnored = ((While) parent).astCondition() != node;
- } else if (parent instanceof DoWhile) {
- returnValueIgnored = ((DoWhile) parent).astCondition() != node;
- } else if (parent instanceof Case) {
- returnValueIgnored = ((Case) parent).astCondition() != node;
- } else if (parent instanceof Assert) {
- returnValueIgnored = ((Assert) parent).astAssertion() != node;
- } else {
- returnValueIgnored = true;
- }
- }
- if (returnValueIgnored && isCommit) {
- String message = "Consider using `apply()` instead; `commit` writes "
- + "its data to persistent storage immediately, whereas "
- + "`apply` will handle it in the background";
- mContext.report(ISSUE, node, mContext.getLocation(node), message);
- }
- }
- }
- }
- }
-
- return super.visitMethodInvocation(node);
- }
-
- @Override
- public boolean visitReturn(Return node) {
- if (node.astValue() == mTarget) {
- // If you just do "return editor.commit() don't warn
- mFound = true;
- }
- return super.visitReturn(node);
- }
-
- boolean isCommitCalled() {
- return mFound;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
deleted file mode 100644
index 6b902a5..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
+++ /dev/null
@@ -1,1682 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.FORMAT_METHOD;
-import static com.android.SdkConstants.GET_STRING_METHOD;
-import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.R_PREFIX;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.tools.lint.checks.SharedPrefsDetector.ANDROID_CONTENT_SHARED_PREFERENCES;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_BYTE;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_DOUBLE;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_FLOAT;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_NULL;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_OBJECT;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_SHORT;
-import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
-import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-import com.android.tools.lint.detector.api.Position;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.BooleanLiteral;
-import lombok.ast.CharLiteral;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.FloatingPointLiteral;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.NullLiteral;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Check which looks for problems with formatting strings such as inconsistencies between
- * translations or between string declaration and string usage in Java.
- * <p>
- * TODO: Verify booleans!
- * TODO: Handle Resources.getQuantityString as well
- */
-public class StringFormatDetector extends ResourceXmlDetector implements Detector.JavaScanner {
- private static final Implementation IMPLEMENTATION_XML = new Implementation(
- StringFormatDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- @SuppressWarnings("unchecked")
- private static final Implementation IMPLEMENTATION_XML_AND_JAVA = new Implementation(
- StringFormatDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE),
- Scope.JAVA_FILE_SCOPE);
-
-
- /** Whether formatting strings are invalid */
- public static final Issue INVALID = Issue.create(
- "StringFormatInvalid", //$NON-NLS-1$
- "Invalid format string",
-
- "If a string contains a '%' character, then the string may be a formatting string " +
- "which will be passed to `String.format` from Java code to replace each '%' " +
- "occurrence with specific values.\n" +
- "\n" +
- "This lint warning checks for two related problems:\n" +
- "(1) Formatting strings that are invalid, meaning that `String.format` will throw " +
- "exceptions at runtime when attempting to use the format string.\n" +
- "(2) Strings containing '%' that are not formatting strings getting passed to " +
- "a `String.format` call. In this case the '%' will need to be escaped as '%%'.\n" +
- "\n" +
- "NOTE: Not all Strings which look like formatting strings are intended for " +
- "use by `String.format`; for example, they may contain date formats intended " +
- "for `android.text.format.Time#format()`. Lint cannot always figure out that " +
- "a String is a date format, so you may get false warnings in those scenarios. " +
- "See the suppress help topic for information on how to suppress errors in " +
- "that case.",
-
- Category.MESSAGES,
- 9,
- Severity.ERROR,
- IMPLEMENTATION_XML);
-
- /** Whether formatting argument types are consistent across translations */
- public static final Issue ARG_COUNT = Issue.create(
- "StringFormatCount", //$NON-NLS-1$
- "Formatting argument types incomplete or inconsistent",
-
- "When a formatted string takes arguments, it usually needs to reference the " +
- "same arguments in all translations (or all arguments if there are no " +
- "translations.\n" +
- "\n" +
- "There are cases where this is not the case, so this issue is a warning rather " +
- "than an error by default. However, this usually happens when a language is not " +
- "translated or updated correctly.",
- Category.MESSAGES,
- 5,
- Severity.WARNING,
- IMPLEMENTATION_XML);
-
- /** Whether the string format supplied in a call to String.format matches the format string */
- public static final Issue ARG_TYPES = Issue.create(
- "StringFormatMatches", //$NON-NLS-1$
- "`String.format` string doesn't match the XML format string",
-
- "This lint check ensures the following:\n" +
- "(1) If there are multiple translations of the format string, then all translations " +
- "use the same type for the same numbered arguments\n" +
- "(2) The usage of the format string in Java is consistent with the format string, " +
- "meaning that the parameter types passed to String.format matches those in the " +
- "format string.",
- Category.MESSAGES,
- 9,
- Severity.ERROR,
- IMPLEMENTATION_XML_AND_JAVA);
-
- /** This plural does not use the quantity value */
- public static final Issue POTENTIAL_PLURAL = Issue.create(
- "PluralsCandidate", //$NON-NLS-1$
- "Potential Plurals",
-
- "This lint check looks for potential errors in internationalization where you have " +
- "translated a message which involves a quantity and it looks like other parts of " +
- "the string may need grammatical changes.\n" +
- "\n" +
- "For example, rather than something like this:\n" +
- " <string name=\"try_again\">Try again in %d seconds.</string>\n" +
- "you should be using a plural:\n" +
- " <plurals name=\"try_again\">\n" +
- " <item quantity=\"one\">Try again in %d second</item>\n" +
- " <item quantity=\"other\">Try again in %d seconds</item>\n" +
- " </plurals>\n" +
- "This will ensure that in other languages the right set of translations are " +
- "provided for the different quantity classes.\n" +
- "\n" +
- "(This check depends on some heuristics, so it may not accurately determine whether " +
- "a string really should be a quantity. You can use tools:ignore to filter out false " +
- "positives.",
-
- Category.MESSAGES,
- 5,
- Severity.WARNING,
- IMPLEMENTATION_XML).addMoreInfo(
- "http://developer.android.com/guide/topics/resources/string-resource.html#Plurals");
-
- /**
- * Map from a format string name to a list of declaration file and actual
- * formatting string content. We're using a list since a format string can be
- * defined multiple times, usually for different translations.
- */
- private Map<String, List<Pair<Handle, String>>> mFormatStrings;
-
- /**
- * Map of strings that contain percents that aren't formatting strings; these
- * should not be passed to String.format.
- */
- private final Map<String, Handle> mNotFormatStrings = new HashMap<String, Handle>();
-
- /**
- * Set of strings that have an unknown format such as date formatting; we should not
- * flag these as invalid when used from a String#format call
- */
- private Set<String> mIgnoreStrings;
-
- /** Constructs a new {@link StringFormatDetector} check */
- public StringFormatDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- if (LintUtils.endsWith(file.getName(), DOT_JAVA)) {
- return mFormatStrings != null;
- }
-
- return super.appliesTo(context, file);
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(TAG_STRING);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- NodeList childNodes = element.getChildNodes();
- if (childNodes.getLength() > 0) {
- if (childNodes.getLength() == 1) {
- Node child = childNodes.item(0);
- if (child.getNodeType() == Node.TEXT_NODE) {
- checkTextNode(context, element, strip(child.getNodeValue()));
- }
- } else {
- // Concatenate children and build up a plain string.
- // This is needed to handle xliff localization documents,
- // but this needs more work so ignore compound XML documents as
- // string values for now:
- StringBuilder sb = new StringBuilder();
- addText(sb, element);
- if (sb.length() > 0) {
- checkTextNode(context, element, sb.toString());
- }
- }
- }
- }
-
- private static void addText(StringBuilder sb, Node node) {
- if (node.getNodeType() == Node.TEXT_NODE) {
- sb.append(strip(node.getNodeValue().trim()));
- } else {
- NodeList childNodes = node.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- addText(sb, childNodes.item(i));
- }
- }
- }
-
- private static String strip(String s) {
- if (s.length() < 2) {
- return s;
- }
- char first = s.charAt(0);
- char last = s.charAt(s.length() - 1);
- if (first == last && (first == '\'' || first == '"')) {
- return s.substring(1, s.length() - 1);
- }
-
- return s;
- }
-
- private void checkTextNode(XmlContext context, Element element, String text) {
- String name = null;
- boolean found = false;
- boolean foundPlural = false;
-
- // Look at the String and see if it's a format string (contains
- // positional %'s)
- for (int j = 0, m = text.length(); j < m; j++) {
- char c = text.charAt(j);
- if (c == '\\') {
- j++;
- }
- if (c == '%') {
- if (name == null) {
- name = element.getAttribute(ATTR_NAME);
- }
-
- // Also make sure this String isn't an unformatted String
- String formatted = element.getAttribute("formatted"); //$NON-NLS-1$
- if (!formatted.isEmpty() && !Boolean.parseBoolean(formatted)) {
- if (!mNotFormatStrings.containsKey(name)) {
- Handle handle = context.createLocationHandle(element);
- handle.setClientData(element);
- mNotFormatStrings.put(name, handle);
- }
- return;
- }
-
- // See if it's not a format string, e.g. "Battery charge is 100%!".
- // If so we want to record this name in a special list such that we can
- // make sure you don't attempt to reference this string from a String.format
- // call.
- Matcher matcher = FORMAT.matcher(text);
- if (!matcher.find(j)) {
- if (!mNotFormatStrings.containsKey(name)) {
- Handle handle = context.createLocationHandle(element);
- handle.setClientData(element);
- mNotFormatStrings.put(name, handle);
- }
- return;
- }
-
- String conversion = matcher.group(6);
- int conversionClass = getConversionClass(conversion.charAt(0));
- if (conversionClass == CONVERSION_CLASS_UNKNOWN || matcher.group(5) != null) {
- if (mIgnoreStrings == null) {
- mIgnoreStrings = new HashSet<String>();
- }
- mIgnoreStrings.add(name);
-
- // Don't process any other strings here; some of them could
- // accidentally look like a string, e.g. "%H" is a hash code conversion
- // in String.format (and hour in Time formatting).
- return;
- }
-
- if (conversionClass == CONVERSION_CLASS_INTEGER && !foundPlural) {
- // See if there appears to be further text content here.
- // Look for whitespace followed by a letter, with no punctuation in between
- for (int k = matcher.end(); k < m; k++) {
- char nc = text.charAt(k);
- if (!Character.isWhitespace(nc)) {
- if (Character.isLetter(nc)) {
- foundPlural = checkPotentialPlural(context, element, text, k);
- }
- break;
- }
- }
- }
-
- found = true;
- j++; // Ensure that when we process a "%%" we don't separately check the second %
- }
- }
-
- if (found && name != null) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- // Record it for analysis when seen in Java code
- if (mFormatStrings == null) {
- mFormatStrings = new HashMap<String, List<Pair<Handle,String>>>();
- }
-
- List<Pair<Handle, String>> list = mFormatStrings.get(name);
- if (list == null) {
- list = new ArrayList<Pair<Handle, String>>();
- mFormatStrings.put(name, list);
- }
- Handle handle = context.createLocationHandle(element);
- handle.setClientData(element);
- list.add(Pair.of(handle, text));
- }
- }
-
- /**
- * Checks whether the text begins with a non-unit word, pointing to a string
- * that should probably be a plural instead. This
- */
- private static boolean checkPotentialPlural(XmlContext context, Element element, String text,
- int wordBegin) {
- // This method should only be called if the text is known to start with a word
- assert Character.isLetter(text.charAt(wordBegin));
-
- int wordEnd = wordBegin;
- while (wordEnd < text.length()) {
- if (!Character.isLetter(text.charAt(wordEnd))) {
- break;
- }
- wordEnd++;
- }
-
- // Eliminate units, since those are not sentences you need to use plurals for, e.g.
- // "Elevation gain: %1$d m (%2$d ft)"
- // We'll determine whether something is a unit by looking for
- // (1) Multiple uppercase characters (e.g. KB, or MiB), or better yet, uppercase characters
- // anywhere but as the first letter
- // (2) No vowels (e.g. ft)
- // (3) Adjacent consonants (e.g. ft); this one can eliminate some legitimate
- // English words as well (e.g. "the") so we should really limit this to
- // letter pairs that are not common in English. This is probably overkill
- // so not handled yet. Instead we use a simpler heuristic:
- // (4) Very short "words" (1-2 letters)
- if (wordEnd - wordBegin <= 2) {
- // Very short word (1-2 chars): possible unit, e.g. "m", "ft", "kb", etc
- return false;
- }
- boolean hasVowel = false;
- for (int i = wordBegin; i < wordEnd; i++) {
- // Uppercase character anywhere but first character: probably a unit (e.g. KB)
- char c = text.charAt(i);
- if (i > wordBegin && Character.isUpperCase(c)) {
- return false;
- }
- if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'y') {
- hasVowel = true;
- }
- }
- if (!hasVowel) {
- // No vowels: likely unit
- return false;
- }
-
- String word = text.substring(wordBegin, wordEnd);
-
- // Some other known abbreviations that we don't want to count:
- if (word.equals("min")) {
- return false;
- }
-
- // This heuristic only works in English!
- if (LintUtils.isEnglishResource(context, true)) {
- String message = String.format("Formatting %%d followed by words (\"%1$s\"): "
- + "This should probably be a plural rather than a string", word);
- context.report(POTENTIAL_PLURAL, element,
- context.getLocation(element),
- message);
- // Avoid reporting multiple errors on the same string
- // (if it contains more than one %d)
- return true;
- }
-
- return false;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mFormatStrings != null) {
- boolean checkCount = context.isEnabled(ARG_COUNT);
- boolean checkValid = context.isEnabled(INVALID);
- boolean checkTypes = context.isEnabled(ARG_TYPES);
-
- // Ensure that all the format strings are consistent with respect to each other;
- // e.g. they all have the same number of arguments, they all use all the
- // arguments, and they all use the same types for all the numbered arguments
- for (Map.Entry<String, List<Pair<Handle, String>>> entry : mFormatStrings.entrySet()) {
- String name = entry.getKey();
- List<Pair<Handle, String>> list = entry.getValue();
-
- // Check argument counts
- if (checkCount) {
- checkArity(context, name, list);
- }
-
- // Check argument types (and also make sure that the formatting strings are valid)
- if (checkValid || checkTypes) {
- checkTypes(context, checkValid, checkTypes, name, list);
- }
- }
- }
- }
-
- private static void checkTypes(Context context, boolean checkValid,
- boolean checkTypes, String name, List<Pair<Handle, String>> list) {
- Map<Integer, String> types = new HashMap<Integer, String>();
- Map<Integer, Handle> typeDefinition = new HashMap<Integer, Handle>();
- for (Pair<Handle, String> pair : list) {
- Handle handle = pair.getFirst();
- String formatString = pair.getSecond();
-
- //boolean warned = false;
- Matcher matcher = FORMAT.matcher(formatString);
- int index = 0;
- int prevIndex = 0;
- int nextNumber = 1;
- while (true) {
- if (matcher.find(index)) {
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = formatString.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- index = matcher.end(); // Ensure loop proceeds
- String str = formatString.substring(matchStart, matcher.end());
- if (str.equals("%%") || str.equals("%n")) { //$NON-NLS-1$ //$NON-NLS-2$
- // Just an escaped %
- continue;
- }
-
- if (checkValid) {
- // Make sure it's a valid format string
- if (str.length() > 2 && str.charAt(str.length() - 2) == ' ') {
- char last = str.charAt(str.length() - 1);
- // If you forget to include the conversion character, e.g.
- // "Weight=%1$ g" instead of "Weight=%1$d g", then
- // you're going to end up with a format string interpreted as
- // "%1$ g". This means that the space character is interpreted
- // as a flag character, but it can only be a flag character
- // when used in conjunction with the numeric conversion
- // formats (d, o, x, X). If that's not the case, make a
- // dedicated error message
- if (last != 'd' && last != 'o' && last != 'x' && last != 'X') {
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(null, INVALID,
- (Node) clientData)) {
- return;
- }
- }
-
- Location location = handle.resolve();
- String message = String.format(
- "Incorrect formatting string `%1$s`; missing conversion " +
- "character in '`%2$s`' ?", name, str);
- context.report(INVALID, location, message);
- //warned = true;
- continue;
- }
- }
- }
-
- if (!checkTypes) {
- continue;
- }
-
- // Shouldn't throw a number format exception since we've already
- // matched the pattern in the regexp
- int number;
- String numberString = matcher.group(1);
- if (numberString != null) {
- // Strip off trailing $
- numberString = numberString.substring(0, numberString.length() - 1);
- number = Integer.parseInt(numberString);
- nextNumber = number + 1;
- } else {
- number = nextNumber++;
- }
- String format = matcher.group(6);
- String currentFormat = types.get(number);
- if (currentFormat == null) {
- types.put(number, format);
- typeDefinition.put(number, handle);
- } else if (!currentFormat.equals(format)
- && isIncompatible(currentFormat.charAt(0), format.charAt(0))) {
-
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(null, ARG_TYPES,
- (Node) clientData)) {
- return;
- }
- }
-
- Location location = handle.resolve();
- // Attempt to limit the location range to just the formatting
- // string in question
- location = refineLocation(context, location, formatString,
- matcher.start(), matcher.end());
- Location otherLocation = typeDefinition.get(number).resolve();
- otherLocation.setMessage("Conflicting argument type here");
- location.setSecondary(otherLocation);
- File f = otherLocation.getFile();
- String message = String.format(
- "Inconsistent formatting types for argument #%1$d in " +
- "format string `%2$s` ('%3$s'): Found both '`%4$s`' and '`%5$s`' " +
- "(in %6$s)",
- number, name,
- str,
- currentFormat, format,
- f.getParentFile().getName() + File.separator + f.getName());
- //warned = true;
- context.report(ARG_TYPES, location, message);
- break;
- }
- } else {
- break;
- }
- }
-
- // Check that the format string is valid by actually attempting to instantiate
- // it. We only do this if we haven't already complained about this string
- // for other reasons.
- /* Check disabled for now: it had many false reports due to conversion
- * errors (which is expected since we just pass in strings), but once those
- * are eliminated there aren't really any other valid error messages returned
- * (for example, calling the formatter with bogus formatting flags always just
- * returns a "conversion" error. It looks like we'd need to actually pass compatible
- * arguments to trigger other types of formatting errors such as precision errors.
- if (!warned && checkValid) {
- try {
- formatter.format(formatString, "", "", "", "", "", "", "",
- "", "", "", "", "", "", "");
-
- } catch (IllegalFormatException t) { // TODO: UnknownFormatConversionException
- if (!t.getLocalizedMessage().contains(" != ")
- && !t.getLocalizedMessage().contains("Conversion")) {
- Location location = handle.resolve();
- context.report(INVALID, location,
- String.format("Wrong format for %1$s: %2$s",
- name, t.getLocalizedMessage()), null);
- }
- }
- }
- */
- }
- }
-
- /**
- * Returns true if two String.format conversions are "incompatible" (meaning
- * that using these two for the same argument across different translations
- * is more likely an error than intentional. Some conversions are
- * incompatible, e.g. "d" and "s" where one is a number and string, whereas
- * others may work (e.g. float versus integer) but are probably not
- * intentional.
- */
- private static boolean isIncompatible(char conversion1, char conversion2) {
- int class1 = getConversionClass(conversion1);
- int class2 = getConversionClass(conversion2);
- return class1 != class2
- && class1 != CONVERSION_CLASS_UNKNOWN
- && class2 != CONVERSION_CLASS_UNKNOWN;
- }
-
- private static final int CONVERSION_CLASS_UNKNOWN = 0;
- private static final int CONVERSION_CLASS_STRING = 1;
- private static final int CONVERSION_CLASS_CHARACTER = 2;
- private static final int CONVERSION_CLASS_INTEGER = 3;
- private static final int CONVERSION_CLASS_FLOAT = 4;
- private static final int CONVERSION_CLASS_BOOLEAN = 5;
- private static final int CONVERSION_CLASS_HASHCODE = 6;
- private static final int CONVERSION_CLASS_PERCENT = 7;
- private static final int CONVERSION_CLASS_NEWLINE = 8;
- private static final int CONVERSION_CLASS_DATETIME = 9;
-
- private static int getConversionClass(char conversion) {
- // See http://developer.android.com/reference/java/util/Formatter.html
- switch (conversion) {
- case 't': // Time/date conversion
- case 'T':
- return CONVERSION_CLASS_DATETIME;
- case 's': // string
- case 'S': // Uppercase string
- return CONVERSION_CLASS_STRING;
- case 'c': // character
- case 'C': // Uppercase character
- return CONVERSION_CLASS_CHARACTER;
- case 'd': // decimal
- case 'o': // octal
- case 'x': // hex
- case 'X':
- return CONVERSION_CLASS_INTEGER;
- case 'f': // decimal float
- case 'e': // exponential float
- case 'E':
- case 'g': // decimal or exponential depending on size
- case 'G':
- case 'a': // hex float
- case 'A':
- return CONVERSION_CLASS_FLOAT;
- case 'b': // boolean
- case 'B':
- return CONVERSION_CLASS_BOOLEAN;
- case 'h': // boolean
- case 'H':
- return CONVERSION_CLASS_HASHCODE;
- case '%': // literal
- return CONVERSION_CLASS_PERCENT;
- case 'n': // literal
- return CONVERSION_CLASS_NEWLINE;
- }
-
- return CONVERSION_CLASS_UNKNOWN;
- }
-
- private static Location refineLocation(Context context, Location location,
- String formatString, int substringStart, int substringEnd) {
- Position startLocation = location.getStart();
- Position endLocation = location.getEnd();
- if (startLocation != null && endLocation != null) {
- int startOffset = startLocation.getOffset();
- int endOffset = endLocation.getOffset();
- if (startOffset >= 0) {
- String contents = context.getClient().readFile(location.getFile());
- if (endOffset <= contents.length() && startOffset < endOffset) {
- int formatOffset = contents.indexOf(formatString, startOffset);
- if (formatOffset != -1 && formatOffset <= endOffset) {
- return Location.create(location.getFile(), contents,
- formatOffset + substringStart, formatOffset + substringEnd);
- }
- }
- }
- }
-
- return location;
- }
-
- /**
- * Check that the number of arguments in the format string is consistent
- * across translations, and that all arguments are used
- */
- private static void checkArity(Context context, String name, List<Pair<Handle, String>> list) {
- // Check to make sure that the argument counts and types are consistent
- int prevCount = -1;
- for (Pair<Handle, String> pair : list) {
- Set<Integer> indices = new HashSet<Integer>();
- int count = getFormatArgumentCount(pair.getSecond(), indices);
- Handle handle = pair.getFirst();
- if (prevCount != -1 && prevCount != count) {
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(null, ARG_COUNT, (Node) clientData)) {
- return;
- }
- }
- Location location = handle.resolve();
- Location secondary = list.get(0).getFirst().resolve();
- secondary.setMessage("Conflicting number of arguments here");
- location.setSecondary(secondary);
- String message = String.format(
- "Inconsistent number of arguments in formatting string `%1$s`; " +
- "found both %2$d and %3$d", name, prevCount, count);
- context.report(ARG_COUNT, location, message);
- break;
- }
-
- for (int i = 1; i <= count; i++) {
- if (!indices.contains(i)) {
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(null, ARG_COUNT,
- (Node) clientData)) {
- return;
- }
- }
-
- Set<Integer> all = new HashSet<Integer>();
- for (int j = 1; j < count; j++) {
- all.add(j);
- }
- all.removeAll(indices);
- List<Integer> sorted = new ArrayList<Integer>(all);
- Collections.sort(sorted);
- Location location = handle.resolve();
- String message = String.format(
- "Formatting string '`%1$s`' is not referencing numbered arguments %2$s",
- name, sorted);
- context.report(ARG_COUNT, location, message);
- break;
- }
- }
-
- prevCount = count;
- }
- }
-
- // See java.util.Formatter docs
- public static final Pattern FORMAT = Pattern.compile(
- // Generic format:
- // %[argument_index$][flags][width][.precision]conversion
- //
- "%" + //$NON-NLS-1$
- // Argument Index
- "(\\d+\\$)?" + //$NON-NLS-1$
- // Flags
- "([-+#, 0(\\<]*)?" + //$NON-NLS-1$
- // Width
- "(\\d+)?" + //$NON-NLS-1$
- // Precision
- "(\\.\\d+)?" + //$NON-NLS-1$
- // Conversion. These are all a single character, except date/time conversions
- // which take a prefix of t/T:
- "([tT])?" + //$NON-NLS-1$
- // The current set of conversion characters are
- // b,h,s,c,d,o,x,e,f,g,a,t (as well as all those as upper-case characters), plus
- // n for newlines and % as a literal %. And then there are all the time/date
- // characters: HIKLm etc. Just match on all characters here since there should
- // be at least one.
- "([a-zA-Z%])"); //$NON-NLS-1$
-
- /** Given a format string returns the format type of the given argument */
- @VisibleForTesting
- @Nullable
- static String getFormatArgumentType(String s, int argument) {
- Matcher matcher = FORMAT.matcher(s);
- int index = 0;
- int prevIndex = 0;
- int nextNumber = 1;
- while (true) {
- if (matcher.find(index)) {
- String value = matcher.group(6);
- if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$
- index = matcher.end();
- continue;
- }
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = s.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- // Shouldn't throw a number format exception since we've already
- // matched the pattern in the regexp
- int number;
- String numberString = matcher.group(1);
- if (numberString != null) {
- // Strip off trailing $
- numberString = numberString.substring(0, numberString.length() - 1);
- number = Integer.parseInt(numberString);
- nextNumber = number + 1;
- } else {
- number = nextNumber++;
- }
-
- if (number == argument) {
- return matcher.group(6);
- }
- index = matcher.end();
- } else {
- break;
- }
- }
-
- return null;
- }
-
- /**
- * Given a format string returns the number of required arguments. If the
- * {@code seenArguments} parameter is not null, put the indices of any
- * observed arguments into it.
- */
- @VisibleForTesting
- static int getFormatArgumentCount(@NonNull String s, @Nullable Set<Integer> seenArguments) {
- Matcher matcher = FORMAT.matcher(s);
- int index = 0;
- int prevIndex = 0;
- int nextNumber = 1;
- int max = 0;
- while (true) {
- if (matcher.find(index)) {
- String value = matcher.group(6);
- if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$
- index = matcher.end();
- continue;
- }
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = s.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- // Shouldn't throw a number format exception since we've already
- // matched the pattern in the regexp
- int number;
- String numberString = matcher.group(1);
- if (numberString != null) {
- // Strip off trailing $
- numberString = numberString.substring(0, numberString.length() - 1);
- number = Integer.parseInt(numberString);
- nextNumber = number + 1;
- } else {
- number = nextNumber++;
- }
-
- if (number > max) {
- max = number;
- }
- if (seenArguments != null) {
- seenArguments.add(number);
- }
-
- index = matcher.end();
- } else {
- break;
- }
- }
-
- return max;
- }
-
- /**
- * Determines whether the given {@link String#format(String, Object...)}
- * formatting string is "locale dependent", meaning that its output depends
- * on the locale. This is the case if it for example references decimal
- * numbers of dates and times.
- *
- * @param format the format string
- * @return true if the format is locale sensitive, false otherwise
- */
- public static boolean isLocaleSpecific(@NonNull String format) {
- if (format.indexOf('%') == -1) {
- return false;
- }
-
- Matcher matcher = FORMAT.matcher(format);
- int index = 0;
- int prevIndex = 0;
- while (true) {
- if (matcher.find(index)) {
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = format.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- String type = matcher.group(6);
- if (!type.isEmpty()) {
- char t = type.charAt(0);
-
- // The following formatting characters are locale sensitive:
- switch (t) {
- case 'd': // decimal integer
- case 'e': // scientific
- case 'E':
- case 'f': // decimal float
- case 'g': // general
- case 'G':
- case 't': // date/time
- case 'T':
- return true;
- }
- }
- index = matcher.end();
- } else {
- break;
- }
- }
-
- return false;
- }
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Arrays.asList(FORMAT_METHOD, GET_STRING_METHOD);
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- if (mFormatStrings == null && !context.getClient().supportsProjectResources()) {
- return;
- }
-
- String methodName = node.astName().astValue();
- if (methodName.equals(FORMAT_METHOD)) {
- // String.format(getResources().getString(R.string.foo), arg1, arg2, ...)
- // Check that the arguments in R.string.foo match arg1, arg2, ...
- if (node.astOperand() instanceof VariableReference) {
- VariableReference ref = (VariableReference) node.astOperand();
- if ("String".equals(ref.astIdentifier().astValue())) { //$NON-NLS-1$
- // Found a String.format call
- // Look inside to see if we can find an R string
- // Find surrounding method
- checkFormatCall(context, node);
- }
- }
- } else {
- // getResources().getString(R.string.foo, arg1, arg2, ...)
- // Check that the arguments in R.string.foo match arg1, arg2, ...
- if (node.astArguments().size() > 1 && node.astOperand() != null ) {
- checkFormatCall(context, node);
- }
- }
- }
-
- private void checkFormatCall(JavaContext context, MethodInvocation node) {
- lombok.ast.Node current = getParentMethod(node);
- if (current != null) {
- checkStringFormatCall(context, current, node);
- }
- }
-
- /**
- * Check the given String.format call (with the given arguments) to see if
- * the string format is being used correctly
- *
- * @param context the context to report errors to
- * @param method the method containing the {@link String#format} call
- * @param call the AST node for the {@link String#format}
- */
- private void checkStringFormatCall(
- JavaContext context,
- lombok.ast.Node method,
- MethodInvocation call) {
-
- StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
- if (args.isEmpty()) {
- return;
- }
-
- StringTracker tracker = new StringTracker(context, method, call, 0);
- method.accept(tracker);
- String name = tracker.getFormatStringName();
- if (name == null) {
- return;
- }
-
- if (mIgnoreStrings != null && mIgnoreStrings.contains(name)) {
- return;
- }
-
- if (mNotFormatStrings.containsKey(name)) {
- Handle handle = mNotFormatStrings.get(name);
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(null, INVALID, (Node) clientData)) {
- return;
- }
- }
- Location location = handle.resolve();
- String message = String.format(
- "Format string '`%1$s`' is not a valid format string so it should not be " +
- "passed to `String.format`",
- name);
- context.report(INVALID, call, location, message);
- return;
- }
-
- Iterator<Expression> argIterator = args.iterator();
- Expression first = argIterator.next();
- Expression second = argIterator.hasNext() ? argIterator.next() : null;
-
- boolean specifiesLocale;
- TypeDescriptor parameterType = context.getType(first);
- if (parameterType != null) {
- specifiesLocale = isLocaleReference(parameterType.getName());
- } else if (!call.astName().astValue().equals(FORMAT_METHOD)) {
- specifiesLocale = false;
- } else {
- // No type information with this AST; use string patterns instead to make
- // an educated guess
- String firstName = first.toString();
- specifiesLocale = firstName.startsWith("Locale.") //$NON-NLS-1$
- || firstName.contains("locale") //$NON-NLS-1$
- || firstName.equals("null") //$NON-NLS-1$
- || (second != null && second.toString().contains("getString") //$NON-NLS-1$
- && !firstName.contains("getString") //$NON-NLS-1$
- && !firstName.contains(R_PREFIX)
- && !(first instanceof StringLiteral));
- }
-
- List<Pair<Handle, String>> list = mFormatStrings != null ? mFormatStrings.get(name) : null;
- if (list == null) {
- LintClient client = context.getClient();
- if (client.supportsProjectResources() &&
- !context.getScope().contains(Scope.RESOURCE_FILE)) {
- AbstractResourceRepository resources = client
- .getProjectResources(context.getMainProject(), true);
- List<ResourceItem> items = resources
- .getResourceItem(ResourceType.STRING, name);
- if (items != null) {
- for (final ResourceItem item : items) {
- ResourceValue v = item.getResourceValue(false);
- if (v != null) {
- String value = v.getRawXmlValue();
- if (value != null) {
- // Make sure it's really a formatting string,
- // not for example "Battery remaining: 90%"
- boolean isFormattingString = value.indexOf('%') != -1;
- for (int j = 0, m = value.length();
- j < m && isFormattingString;
- j++) {
- char c = value.charAt(j);
- if (c == '\\') {
- j++;
- } else if (c == '%') {
- Matcher matcher = FORMAT.matcher(value);
- if (!matcher.find(j)) {
- isFormattingString = false;
- } else {
- String conversion = matcher.group(6);
- int conversionClass = getConversionClass(
- conversion.charAt(0));
- if (conversionClass == CONVERSION_CLASS_UNKNOWN
- || matcher.group(5) != null) {
- // Some date format etc - don't process
- return;
- }
- }
- j++; // Don't process second % in a %%
- }
- // If the user marked the string with
- }
-
- if (isFormattingString) {
- if (list == null) {
- list = Lists.newArrayList();
- if (mFormatStrings == null) {
- mFormatStrings = Maps.newHashMap();
- }
- mFormatStrings.put(name, list);
- }
- Handle handle = client.createResourceItemHandle(item);
- list.add(Pair.of(handle, value));
- }
- }
- }
- }
- }
- } else {
- return;
- }
- }
-
- if (list != null) {
- Set<String> reported = null;
- for (Pair<Handle, String> pair : list) {
- String s = pair.getSecond();
- if (reported != null && reported.contains(s)) {
- continue;
- }
- int count = getFormatArgumentCount(s, null);
- Handle handle = pair.getFirst();
- if (count != args.size() - 1 - (specifiesLocale ? 1 : 0)) {
- if (isSharedPreferenceGetString(context, call)) {
- continue;
- }
-
- Location location = context.getLocation(call);
- Location secondary = handle.resolve();
- secondary.setMessage(String.format("This definition requires %1$d arguments",
- count));
- location.setSecondary(secondary);
- String message = String.format(
- "Wrong argument count, format string `%1$s` requires `%2$d` but format " +
- "call supplies `%3$d`",
- name, count, args.size() - 1 - (specifiesLocale ? 1 : 0));
- context.report(ARG_TYPES, method, location, message);
- if (reported == null) {
- reported = Sets.newHashSet();
- }
- reported.add(s);
- } else {
- for (int i = 1; i <= count; i++) {
- int argumentIndex = i + (specifiesLocale ? 1 : 0);
- Class<?> type = tracker.getArgumentType(argumentIndex);
- if (type != null) {
- boolean valid = true;
- String formatType = getFormatArgumentType(s, i);
- if (formatType == null) {
- continue;
- }
- char last = formatType.charAt(formatType.length() - 1);
- if (formatType.length() >= 2 &&
- Character.toLowerCase(
- formatType.charAt(formatType.length() - 2)) == 't') {
- // Date time conversion.
- // TODO
- continue;
- }
- switch (last) {
- // Booleans. It's okay to pass objects to these;
- // it will print "true" if non-null, but it's
- // unusual and probably not intended.
- case 'b':
- case 'B':
- valid = type == Boolean.TYPE;
- break;
-
- // Numeric: integer and floats in various formats
- case 'x':
- case 'X':
- case 'd':
- case 'o':
- case 'e':
- case 'E':
- case 'f':
- case 'g':
- case 'G':
- case 'a':
- case 'A':
- valid = type == Integer.TYPE
- || type == Float.TYPE
- || type == Double.TYPE
- || type == Long.TYPE
- || type == Byte.TYPE
- || type == Short.TYPE;
- break;
- case 'c':
- case 'C':
- // Unicode character
- valid = type == Character.TYPE;
- break;
- case 'h':
- case 'H': // Hex print of hash code of objects
- case 's':
- case 'S':
- // String. Can pass anything, but warn about
- // numbers since you may have meant more
- // specific formatting. Use special issue
- // explanation for this?
- valid = type != Boolean.TYPE &&
- !Number.class.isAssignableFrom(type);
- break;
- }
-
- if (!valid) {
- if (isSharedPreferenceGetString(context, call)) {
- continue;
- }
-
- Expression argument = tracker.getArgument(argumentIndex);
- Location location = context.getLocation(argument);
- Location secondary = handle.resolve();
- secondary.setMessage("Conflicting argument declaration here");
- location.setSecondary(secondary);
-
- String message = String.format(
- "Wrong argument type for formatting argument '#%1$d' " +
- "in `%2$s`: conversion is '`%3$s`', received `%4$s` " +
- "(argument #%5$d in method call)",
- i, name, formatType, type.getSimpleName(),
- argumentIndex + 1);
- context.report(ARG_TYPES, method, location, message);
- if (reported == null) {
- reported = Sets.newHashSet();
- }
- reported.add(s);
- }
- }
- }
- }
- }
- }
- }
-
- private static boolean isSharedPreferenceGetString(@NonNull JavaContext context,
- @NonNull MethodInvocation call) {
- if (!GET_STRING_METHOD.equals(call.astName().astValue())) {
- return false;
- }
-
- JavaParser.ResolvedNode resolved = context.resolve(call);
- if (resolved instanceof JavaParser.ResolvedMethod) {
- JavaParser.ResolvedMethod resolvedMethod = (JavaParser.ResolvedMethod) resolved;
- JavaParser.ResolvedClass containingClass = resolvedMethod.getContainingClass();
- return containingClass.isSubclassOf(ANDROID_CONTENT_SHARED_PREFERENCES, false);
- }
-
- return false; // not certain
- }
-
- private static boolean isLocaleReference(@Nullable TypeDescriptor reference) {
- return reference != null && isLocaleReference(reference.getName());
- }
-
- private static boolean isLocaleReference(@Nullable String typeName) {
- return typeName != null && (typeName.equals("Locale") //$NON-NLS-1$
- || typeName.equals("java.util.Locale")); //$NON-NLS-1$
- }
-
- /** Returns the parent method of the given AST node */
- @Nullable
- public static lombok.ast.Node getParentMethod(@NonNull lombok.ast.Node node) {
- lombok.ast.Node current = node.getParent();
- while (current != null
- && !(current instanceof MethodDeclaration)
- && !(current instanceof ConstructorDeclaration)) {
- current = current.getParent();
- }
-
- return current;
- }
-
- /** Returns the resource name corresponding to the first argument in the given call */
- @Nullable
- public static String getResourceForFirstArg(
- @NonNull lombok.ast.Node method,
- @NonNull lombok.ast.Node call) {
- assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
- StringTracker tracker = new StringTracker(null, method, call, 0);
- method.accept(tracker);
-
- return tracker.getFormatStringName();
- }
-
- /** Returns the resource name corresponding to the given argument in the given call */
- @Nullable
- public static String getResourceArg(
- @NonNull lombok.ast.Node method,
- @NonNull lombok.ast.Node call,
- int argIndex) {
- assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
- StringTracker tracker = new StringTracker(null, method, call, argIndex);
- method.accept(tracker);
-
- return tracker.getFormatStringName();
- }
-
- /**
- * Given a variable reference, finds the original R.string value corresponding to it.
- * For example:
- * <pre>
- * {@code
- * String target = "World";
- * String hello = getResources().getString(R.string.hello);
- * String output = String.format(hello, target);
- * }
- * </pre>
- *
- * Given the {@code String.format} call, we want to find out what R.string resource
- * corresponds to the first argument, in this case {@code R.string.hello}.
- * To do this, we look for R.string references, and track those through assignments
- * until we reach the target node.
- * <p>
- * In addition, it also does some primitive type tracking such that it (in some cases)
- * can answer questions about the types of variables. This allows it to check whether
- * certain argument types are valid. Note however that it does not do full-blown
- * type analysis by checking method call signatures and so on.
- */
- private static class StringTracker extends ForwardingAstVisitor {
- /** Method we're searching within */
- private final lombok.ast.Node mTop;
- /** The argument index in the method we're targeting */
- private final int mArgIndex;
- /** Map from variable name to corresponding string resource name */
- private final Map<String, String> mMap = new HashMap<String, String>();
- /** Map from variable name to corresponding type */
- private final Map<String, Class<?>> mTypes = new HashMap<String, Class<?>>();
- /** The AST node for the String.format we're interested in */
- private final lombok.ast.Node mTargetNode;
- private boolean mDone;
- @Nullable
- private JavaContext mContext;
-
- /**
- * Result: the name of the string resource being passed to the
- * String.format, if any
- */
- private String mName;
-
- public StringTracker(@Nullable JavaContext context, lombok.ast.Node top, lombok.ast.Node targetNode, int argIndex) {
- mContext = context;
- mTop = top;
- mArgIndex = argIndex;
- mTargetNode = targetNode;
- }
-
- public String getFormatStringName() {
- return mName;
- }
-
- /** Returns the argument type of the given formatting argument of the
- * target node. Note: This is in the formatting string, which is one higher
- * than the String.format parameter number, since the first argument is the
- * formatting string itself.
- *
- * @param argument the argument number
- * @return the class (such as {@link Integer#TYPE} etc) or null if not known
- */
- public Class<?> getArgumentType(int argument) {
- Expression arg = getArgument(argument);
- if (arg != null) {
- // Look up type based on the source code literals
- Class<?> type = getType(arg);
- if (type != null) {
- return type;
- }
-
- // If the AST supports type resolution, use that for other types
- // of expressions
- if (mContext != null) {
- return getTypeClass(mContext.getType(arg));
- }
- }
-
- return null;
- }
-
- private static Class<?> getTypeClass(@Nullable TypeDescriptor type) {
- if (type != null) {
- return getTypeClass(type.getName());
- }
- return null;
- }
-
- private static Class<?> getTypeClass(@Nullable String fqcn) {
- if (fqcn == null) {
- return null;
- } else if (fqcn.equals(TYPE_STRING) || fqcn.equals("String")) { //$NON-NLS-1$
- return String.class;
- } else if (fqcn.equals(TYPE_INT)) {
- return Integer.TYPE;
- } else if (fqcn.equals(TYPE_BOOLEAN)) {
- return Boolean.TYPE;
- } else if (fqcn.equals(TYPE_NULL)) {
- return Object.class;
- } else if (fqcn.equals(TYPE_LONG)) {
- return Long.TYPE;
- } else if (fqcn.equals(TYPE_FLOAT)) {
- return Float.TYPE;
- } else if (fqcn.equals(TYPE_DOUBLE)) {
- return Double.TYPE;
- } else if (fqcn.equals(TYPE_CHAR)) {
- return Character.TYPE;
- } else if (fqcn.equals("BigDecimal") //$NON-NLS-1$
- || fqcn.equals("java.math.BigDecimal")) { //$NON-NLS-1$
- return Float.TYPE;
- } else if (fqcn.equals("BigInteger") //$NON-NLS-1$
- || fqcn.equals("java.math.BigInteger")) { //$NON-NLS-1$
- return Integer.TYPE;
- } else if (fqcn.equals(TYPE_OBJECT)) {
- return null;
- } else if (fqcn.startsWith("java.lang.")) {
- if (fqcn.equals("java.lang.Integer")
- || fqcn.equals("java.lang.Short")
- || fqcn.equals("java.lang.Byte")
- || fqcn.equals("java.lang.Long")) {
- return Integer.TYPE;
- } else if (fqcn.equals("java.lang.Float")
- || fqcn.equals("java.lang.Double")) {
- return Float.TYPE;
- } else {
- return null;
- }
- } else if (fqcn.equals(TYPE_BYTE)) {
- return Byte.TYPE;
- } else if (fqcn.equals(TYPE_SHORT)) {
- return Short.TYPE;
- } else {
- return null;
- }
- }
-
- public Expression getArgument(int argument) {
- if (!(mTargetNode instanceof MethodInvocation)) {
- return null;
- }
- MethodInvocation call = (MethodInvocation) mTargetNode;
- StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
- if (argument >= args.size()) {
- return null;
- }
-
- Iterator<Expression> iterator = args.iterator();
- int index = 0;
- while (iterator.hasNext()) {
- Expression arg = iterator.next();
- if (index++ == argument) {
- return arg;
- }
- }
-
- return null;
- }
-
- @Override
- public boolean visitNode(lombok.ast.Node node) {
- return mDone || super.visitNode(node);
-
- }
-
- @Override
- public boolean visitVariableReference(VariableReference node) {
- if (node.astIdentifier().astValue().equals(R_CLASS) && //$NON-NLS-1$
- node.getParent() instanceof Select &&
- node.getParent().getParent() instanceof Select) {
-
- // See if we're on the right hand side of an assignment
- lombok.ast.Node current = node.getParent().getParent();
- String reference = ((Select) current).astIdentifier().astValue();
-
- while (current != mTop && !(current instanceof VariableDefinitionEntry)) {
- if (current == mTargetNode) {
- mName = reference;
- mDone = true;
- return false;
- }
- current = current.getParent();
- }
- if (current instanceof VariableDefinitionEntry) {
- VariableDefinitionEntry entry = (VariableDefinitionEntry) current;
- String variable = entry.astName().astValue();
- mMap.put(variable, reference);
- }
- }
-
- return false;
- }
-
- @Nullable
- private Expression getTargetArgument() {
- Iterator<Expression> iterator;
- if (mTargetNode instanceof MethodInvocation) {
- iterator = ((MethodInvocation) mTargetNode).astArguments().iterator();
- } else if (mTargetNode instanceof ConstructorInvocation) {
- iterator = ((ConstructorInvocation) mTargetNode).astArguments().iterator();
- } else {
- return null;
- }
- int i = 0;
- while (i < mArgIndex && iterator.hasNext()) {
- iterator.next();
- i++;
- }
- if (iterator.hasNext()) {
- Expression next = iterator.next();
- if (next != null && mContext != null && iterator.hasNext()) {
- TypeDescriptor type = mContext.getType(next);
- if (isLocaleReference(type)) {
- next = iterator.next();
- } else if (type == null
- && next.toString().startsWith("Locale.")) { //$NON-NLS-1$
- next = iterator.next();
- }
- }
- return next;
- }
-
- return null;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node == mTargetNode) {
- Expression arg = getTargetArgument();
- if (arg instanceof VariableReference) {
- VariableReference reference = (VariableReference) arg;
- String variable = reference.astIdentifier().astValue();
- mName = mMap.get(variable);
- mDone = true;
- return true;
- }
- }
-
- // Is this a getString() call? On a resource object? If so,
- // promote the resource argument up to the left hand side
- return super.visitMethodInvocation(node);
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- if (node == mTargetNode) {
- Expression arg = getTargetArgument();
- if (arg instanceof VariableReference) {
- VariableReference reference = (VariableReference) arg;
- String variable = reference.astIdentifier().astValue();
- mName = mMap.get(variable);
- mDone = true;
- return true;
- }
- }
-
- // Is this a getString() call? On a resource object? If so,
- // promote the resource argument up to the left hand side
- return super.visitConstructorInvocation(node);
- }
-
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- String name = node.astName().astValue();
- Expression rhs = node.astInitializer();
- Class<?> type = getType(rhs);
- if (type != null) {
- mTypes.put(name, type);
- } else {
- // Make sure we're not visiting the String.format node itself. If you have
- // msg = String.format("%1$s", msg)
- // then we'd be wiping out the type of "msg" before visiting the
- // String.format call!
- if (rhs != mTargetNode) {
- mTypes.remove(name);
- }
- }
-
- return super.visitVariableDefinitionEntry(node);
- }
-
- private Class<?> getType(Expression expression) {
- if (expression == null) {
- return null;
- }
-
- if (expression instanceof VariableReference) {
- VariableReference reference = (VariableReference) expression;
- String variable = reference.astIdentifier().astValue();
- Class<?> type = mTypes.get(variable);
- if (type != null) {
- return type;
- }
- } else if (expression instanceof MethodInvocation) {
- MethodInvocation method = (MethodInvocation) expression;
- String methodName = method.astName().astValue();
- if (methodName.equals(GET_STRING_METHOD)) {
- return String.class;
- }
- } else if (expression instanceof StringLiteral) {
- return String.class;
- } else if (expression instanceof IntegralLiteral) {
- return Integer.TYPE;
- } else if (expression instanceof FloatingPointLiteral) {
- return Float.TYPE;
- } else if (expression instanceof CharLiteral) {
- return Character.TYPE;
- } else if (expression instanceof BooleanLiteral) {
- return Boolean.TYPE;
- } else if (expression instanceof NullLiteral) {
- return Object.class;
- }
-
- if (mContext != null) {
- TypeDescriptor type = mContext.getType(expression);
- if (type != null) {
- Class<?> typeClass = getTypeClass(type);
- if (typeClass != null) {
- return typeClass;
- } else {
- return Object.class;
- }
- }
- }
-
- return null;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
deleted file mode 100644
index d34759a..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
+++ /dev/null
@@ -1,1605 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_VALUE;
-import static com.android.SdkConstants.CLASS_INTENT;
-import static com.android.SdkConstants.INT_DEF_ANNOTATION;
-import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
-import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
-import static com.android.SdkConstants.TAG_PERMISSION;
-import static com.android.SdkConstants.TAG_USES_PERMISSION;
-import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
-import static com.android.resources.ResourceType.COLOR;
-import static com.android.resources.ResourceType.DRAWABLE;
-import static com.android.resources.ResourceType.MIPMAP;
-import static com.android.tools.lint.checks.PermissionFinder.Operation.ACTION;
-import static com.android.tools.lint.checks.PermissionFinder.Operation.READ;
-import static com.android.tools.lint.checks.PermissionFinder.Operation.WRITE;
-import static com.android.tools.lint.checks.PermissionRequirement.ATTR_PROTECTION_LEVEL;
-import static com.android.tools.lint.checks.PermissionRequirement.VALUE_DANGEROUS;
-import static com.android.tools.lint.detector.api.JavaContext.findSurroundingMethod;
-import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceType;
-import com.android.sdklib.AndroidVersion;
-import com.android.tools.lint.checks.PermissionFinder.Operation;
-import com.android.tools.lint.checks.PermissionFinder.Result;
-import com.android.tools.lint.checks.PermissionHolder.SetPermissionLookup;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.JavaParser.ResolvedField;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ConstantEvaluator;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.utils.XmlUtils;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Locale;
-import java.util.Set;
-
-import lombok.ast.ArrayCreation;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.BinaryOperator;
-import lombok.ast.Catch;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.EnumConstant;
-import lombok.ast.Expression;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.FloatingPointLiteral;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.NullLiteral;
-import lombok.ast.Select;
-import lombok.ast.Statement;
-import lombok.ast.StringLiteral;
-import lombok.ast.Try;
-import lombok.ast.TypeReference;
-import lombok.ast.UnaryExpression;
-import lombok.ast.UnaryOperator;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Looks up annotations on method calls and enforces the various things they
- * express, e.g. for {@code @CheckReturn} it makes sure the return value is used,
- * for {@code ColorInt} it ensures that a proper color integer is passed in, etc.
- *
- * TODO: Throw in some annotation usage checks here too; e.g. specifying @Size without parameters,
- * specifying toInclusive without setting to, combining @ColorInt with any @ResourceTypeRes,
- * using @CheckResult on a void method, etc.
- */
-public class SupportAnnotationDetector extends Detector implements Detector.JavaScanner {
-
- public static final Implementation IMPLEMENTATION
- = new Implementation(SupportAnnotationDetector.class, Scope.JAVA_FILE_SCOPE);
-
- /** Method result should be used */
- public static final Issue RANGE = Issue.create(
- "Range", //$NON-NLS-1$
- "Outside Range",
-
- "Some parameters are required to in a particular numerical range; this check " +
- "makes sure that arguments passed fall within the range. For arrays, Strings " +
- "and collections this refers to the size or length.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /**
- * Attempting to set a resource id as a color
- */
- public static final Issue RESOURCE_TYPE = Issue.create(
- "ResourceType", //$NON-NLS-1$
- "Wrong Resource Type",
-
- "Ensures that resource id's passed to APIs are of the right type; for example, " +
- "calling `Resources.getColor(R.string.name)` is wrong.",
-
- Category.CORRECTNESS,
- 7,
- Severity.FATAL,
- IMPLEMENTATION);
-
- /** Attempting to set a resource id as a color */
- public static final Issue COLOR_USAGE = Issue.create(
- "ResourceAsColor", //$NON-NLS-1$
- "Should pass resolved color instead of resource id",
-
- "Methods that take a color in the form of an integer should be passed " +
- "an RGB triple, not the actual color resource id. You must call " +
- "`getResources().getColor(resource)` to resolve the actual color value first.",
-
- Category.CORRECTNESS,
- 7,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Passing the wrong constant to an int or String method */
- public static final Issue TYPE_DEF = Issue.create(
- "WrongConstant", //$NON-NLS-1$
- "Incorrect constant",
-
- "Ensures that when parameter in a method only allows a specific set " +
- "of constants, calls obey those rules.",
-
- Category.SECURITY,
- 6,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Method result should be used */
- public static final Issue CHECK_RESULT = Issue.create(
- "CheckResult", //$NON-NLS-1$
- "Ignoring results",
-
- "Some methods have no side effects, an calling them without doing something " +
- "without the result is suspicious. ",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Failing to enforce security by just calling check permission */
- public static final Issue CHECK_PERMISSION = Issue.create(
- "UseCheckPermission", //$NON-NLS-1$
- "Using the result of check permission calls",
-
- "You normally want to use the result of checking a permission; these methods " +
- "return whether the permission is held; they do not throw an error if the permission " +
- "is not granted. Code which does not do anything with the return value probably " +
- "meant to be calling the enforce methods instead, e.g. rather than " +
- "`Context#checkCallingPermission` it should call `Context#enforceCallingPermission`.",
-
- Category.SECURITY,
- 6,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Method result should be used */
- public static final Issue MISSING_PERMISSION = Issue.create(
- "MissingPermission", //$NON-NLS-1$
- "Missing Permissions",
-
- "This check scans through your code and libraries and looks at the APIs being used, " +
- "and checks this against the set of permissions required to access those APIs. If " +
- "the code using those APIs is called at runtime, then the program will crash.\n" +
- "\n" +
- "Furthermore, for permissions that are revocable (with targetSdkVersion 23), client " +
- "code must also be prepared to handle the calls throwing an exception if the user " +
- "rejects the request for permission at runtime.",
-
- Category.CORRECTNESS,
- 9,
- Severity.ERROR,
- IMPLEMENTATION);
-
- /** Passing the wrong constant to an int or String method */
- public static final Issue THREAD = Issue.create(
- "WrongThread", //$NON-NLS-1$
- "Wrong Thread",
-
- "Ensures that a method which expects to be called on a specific thread, is actually " +
- "called from that thread. For example, calls on methods in widgets should always " +
- "be made on the UI thread.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- IMPLEMENTATION)
- .addMoreInfo(
- "http://developer.android.com/guide/components/processes-and-threads.html#Threads");
-
- public static final String CHECK_RESULT_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "CheckResult"; //$NON-NLS-1$
- public static final String COLOR_INT_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "ColorInt"; //$NON-NLS-1$
- public static final String INT_RANGE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntRange"; //$NON-NLS-1$
- public static final String FLOAT_RANGE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "FloatRange"; //$NON-NLS-1$
- public static final String SIZE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "Size"; //$NON-NLS-1$
- public static final String PERMISSION_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "RequiresPermission"; //$NON-NLS-1$
- public static final String UI_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "UiThread"; //$NON-NLS-1$
- public static final String MAIN_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "MainThread"; //$NON-NLS-1$
- public static final String WORKER_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "WorkerThread"; //$NON-NLS-1$
- public static final String BINDER_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "BinderThread"; //$NON-NLS-1$
- public static final String PERMISSION_ANNOTATION_READ = PERMISSION_ANNOTATION + ".Read"; //$NON-NLS-1$
- public static final String PERMISSION_ANNOTATION_WRITE = PERMISSION_ANNOTATION + ".Write"; //$NON-NLS-1$
-
- public static final String RES_SUFFIX = "Res";
- public static final String THREAD_SUFFIX = "Thread";
- public static final String ATTR_SUGGEST = "suggest";
- public static final String ATTR_TO = "to";
- public static final String ATTR_FROM = "from";
- public static final String ATTR_FROM_INCLUSIVE = "fromInclusive";
- public static final String ATTR_TO_INCLUSIVE = "toInclusive";
- public static final String ATTR_MULTIPLE = "multiple";
- public static final String ATTR_MIN = "min";
- public static final String ATTR_MAX = "max";
- public static final String ATTR_ALL_OF = "allOf";
- public static final String ATTR_ANY_OF = "anyOf";
- public static final String ATTR_CONDITIONAL = "conditional";
-
- /**
- * Marker ResourceType used to signify that an expression is of type {@code @ColorInt},
- * which isn't actually a ResourceType but one we want to specifically compare with.
- * We're using {@link ResourceType#PUBLIC} because that one won't appear in the R
- * class (and ResourceType is an enum we can't just create new constants for.)
- */
- public static final ResourceType COLOR_INT_MARKER_TYPE = ResourceType.PUBLIC;
-
- /**
- * Constructs a new {@link SupportAnnotationDetector} check
- */
- public SupportAnnotationDetector() {
- }
-
- private void checkMethodAnnotation(
- @NonNull JavaContext context,
- @NonNull ResolvedMethod method,
- @NonNull Node node,
- @NonNull ResolvedAnnotation annotation) {
- String signature = annotation.getSignature();
- if (CHECK_RESULT_ANNOTATION.equals(signature)
- || signature.endsWith(".CheckReturnValue")) { // support findbugs annotation too
- checkResult(context, node, annotation);
- } else if (signature.equals(PERMISSION_ANNOTATION)) {
- PermissionRequirement requirement = PermissionRequirement.create(context, annotation);
- checkPermission(context, node, method, null, requirement);
- } else if (signature.endsWith(THREAD_SUFFIX)
- && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
- checkThreading(context, node, method, signature);
- }
- }
-
- private void checkParameterAnnotation(
- @NonNull JavaContext context,
- @NonNull Node argument,
- @NonNull Node call,
- @NonNull ResolvedMethod method,
- @NonNull ResolvedAnnotation annotation,
- @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
- String signature = annotation.getSignature();
-
- if (COLOR_INT_ANNOTATION.equals(signature)) {
- checkColor(context, argument);
- } else if (signature.equals(INT_RANGE_ANNOTATION)) {
- checkIntRange(context, annotation, argument, allAnnotations);
- } else if (signature.equals(FLOAT_RANGE_ANNOTATION)) {
- checkFloatRange(context, annotation, argument);
- } else if (signature.equals(SIZE_ANNOTATION)) {
- checkSize(context, annotation, argument);
- } else if (signature.startsWith(PERMISSION_ANNOTATION)) {
- // PERMISSION_ANNOTATION, PERMISSION_ANNOTATION_READ, PERMISSION_ANNOTATION_WRITE
- // When specified on a parameter, that indicates that we're dealing with
- // a permission requirement on this *method* which depends on the value
- // supplied by this parameter
- checkParameterPermission(context, signature, call, method, argument);
- } else {
- // We only run @IntDef, @StringDef and @<Type>Res checks if we're not
- // running inside Android Studio / IntelliJ where there are already inspections
- // covering the same warnings (using IntelliJ's own data flow analysis); we
- // don't want to (a) create redundant warnings or (b) work harder than we
- // have to
- if (signature.equals(INT_DEF_ANNOTATION)) {
- boolean flag = annotation.getValue(TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE;
- checkTypeDefConstant(context, annotation, argument, null, flag, allAnnotations);
- } else if (signature.equals(STRING_DEF_ANNOTATION)) {
- checkTypeDefConstant(context, annotation, argument, null, false, allAnnotations);
- } else if (signature.endsWith(RES_SUFFIX)) {
- String typeString = signature.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
- signature.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
- ResourceType type = ResourceType.getEnum(typeString);
- if (type != null) {
- checkResourceType(context, argument, type);
- } else if (typeString.equals("any")) { // @AnyRes
- checkResourceType(context, argument, null);
- }
- }
- }
- }
-
- private void checkParameterPermission(
- @NonNull JavaContext context,
- @NonNull String signature,
- @NonNull Node call,
- @NonNull ResolvedMethod method,
- @NonNull Node argument) {
- Operation operation = null;
- if (signature.equals(PERMISSION_ANNOTATION_READ)) {
- operation = READ;
- } else if (signature.equals(PERMISSION_ANNOTATION_WRITE)) {
- operation = WRITE;
- } else {
- TypeDescriptor type = context.getType(argument);
- if (type == null) {
- return;
- }
- if (type.matchesSignature(CLASS_INTENT)) {
- operation = ACTION;
- }
- }
- if (operation == null) {
- return;
- }
- Result result = PermissionFinder.findRequiredPermissions(operation, context, argument);
- if (result != null) {
- checkPermission(context, call, method, result, result.requirement);
- }
- }
-
- private static void checkColor(@NonNull JavaContext context, @NonNull Node argument) {
- if (argument instanceof InlineIfExpression) {
- InlineIfExpression expression = (InlineIfExpression) argument;
- checkColor(context, expression.astIfTrue());
- checkColor(context, expression.astIfFalse());
- return;
- }
-
- List<ResourceType> types = getResourceTypes(context, argument);
-
- if (types != null && types.contains(ResourceType.COLOR)) {
- String message = String.format(
- "Should pass resolved color instead of resource id here: " +
- "`getResources().getColor(%1$s)`", argument.toString());
- context.report(COLOR_USAGE, argument, context.getLocation(argument), message);
- }
- }
-
- private void checkPermission(
- @NonNull JavaContext context,
- @NonNull Node node,
- @Nullable ResolvedMethod method,
- @Nullable Result result,
- @NonNull PermissionRequirement requirement) {
- if (requirement.isConditional()) {
- return;
- }
- PermissionHolder permissions = getPermissions(context);
- if (!requirement.isSatisfied(permissions)) {
- // See if it looks like we're holding the permission implicitly by @RequirePermission
- // annotations in the surrounding context
- permissions = addLocalPermissions(context, permissions, node);
- if (!requirement.isSatisfied(permissions)) {
- Operation operation;
- String name;
- if (result != null) {
- name = result.name;
- operation = result.operation;
- } else {
- assert method != null;
- name = method.getContainingClass().getSimpleName() + "." + method.getName();
- operation = Operation.CALL;
- }
- String message = getMissingPermissionMessage(requirement, name, permissions,
- operation);
- context.report(MISSING_PERMISSION, node, context.getLocation(node), message);
- }
- } else if (requirement.isRevocable(permissions) &&
- context.getMainProject().getTargetSdkVersion().getFeatureLevel() >= 23) {
- // Ensure that the caller is handling a security exception
- // First check to see if we're inside a try/catch which catches a SecurityException
- // (or some wider exception than that). Check for nested try/catches too.
- boolean handlesMissingPermission = false;
- Node parent = node;
- while (true) {
- Try tryCatch = getParentOfType(parent, Try.class);
- if (tryCatch == null) {
- break;
- } else {
- for (Catch aCatch : tryCatch.astCatches()) {
- TypeReference catchType = aCatch.astExceptionDeclaration().
- astTypeReference();
- if (isSecurityException(context,
- catchType)) {
- handlesMissingPermission = true;
- break;
- }
- }
- parent = tryCatch;
- }
- }
-
- // If not, check to see if the method itself declares that it throws a
- // SecurityException or something wider.
- if (!handlesMissingPermission) {
- MethodDeclaration declaration = getParentOfType(parent, MethodDeclaration.class);
- if (declaration != null) {
- for (TypeReference typeReference : declaration.astThrownTypeReferences()) {
- if (isSecurityException(context, typeReference)) {
- handlesMissingPermission = true;
- break;
- }
- }
- }
- }
-
- // If not, check to see if the code is deliberately checking to see if the
- // given permission is available.
- if (!handlesMissingPermission) {
- Node methodNode = JavaContext.findSurroundingMethod(node);
- if (methodNode != null) {
- CheckPermissionVisitor visitor = new CheckPermissionVisitor(node);
- methodNode.accept(visitor);
- handlesMissingPermission = visitor.checksPermission();
- }
- }
-
- if (!handlesMissingPermission) {
- String message = getUnhandledPermissionMessage();
- context.report(MISSING_PERMISSION, node, context.getLocation(node), message);
- }
- }
- }
-
- @NonNull
- private static PermissionHolder addLocalPermissions(
- @NonNull JavaContext context,
- @NonNull PermissionHolder permissions,
- @NonNull Node node) {
- // Accumulate @RequirePermissions available in the local context
- Node methodNode = JavaContext.findSurroundingMethod(node);
- if (methodNode == null) {
- return permissions;
- }
- ResolvedNode resolved = context.resolve(methodNode);
- if (!(resolved instanceof ResolvedMethod)) {
- return permissions;
- }
- ResolvedMethod method = (ResolvedMethod) resolved;
- ResolvedAnnotation annotation = method.getAnnotation(PERMISSION_ANNOTATION);
- permissions = mergeAnnotationPermissions(context, permissions, annotation);
- annotation = method.getContainingClass().getAnnotation(PERMISSION_ANNOTATION);
- permissions = mergeAnnotationPermissions(context, permissions, annotation);
- return permissions;
- }
-
- @NonNull
- private static PermissionHolder mergeAnnotationPermissions(
- @NonNull JavaContext context,
- @NonNull PermissionHolder permissions,
- @Nullable ResolvedAnnotation annotation) {
- if (annotation != null) {
- PermissionRequirement requirement = PermissionRequirement.create(context, annotation);
- permissions = SetPermissionLookup.join(permissions, requirement);
- }
-
- return permissions;
- }
-
- /** Returns the error message shown when a given call is missing one or more permissions */
- public static String getMissingPermissionMessage(@NonNull PermissionRequirement requirement,
- @NonNull String callName, @NonNull PermissionHolder permissions,
- @NonNull Operation operation) {
- return String.format("Missing permissions required %1$s %2$s: %3$s", operation.prefix(),
- callName, requirement.describeMissingPermissions(permissions));
- }
-
- /** Returns the error message shown when a revocable permission call is not properly handled */
- public static String getUnhandledPermissionMessage() {
- return "Call requires permission which may be rejected by user: code should explicitly "
- + "check to see if permission is available (with `checkPermission`) or handle "
- + "a potential `SecurityException`";
- }
-
- /**
- * Visitor which looks through a method, up to a given call (the one requiring a
- * permission) and checks whether it's preceeded by a call to checkPermission or
- * checkCallingPermission or enforcePermission etc.
- * <p>
- * Currently it only looks for the presence of this check; it does not perform
- * flow analysis to determine whether the check actually affects program flow
- * up to the permission call, or whether the check permission is checking for
- * permissions sufficient to satisfy the permission requirement of the target call,
- * or whether the check return value (== PERMISSION_GRANTED vs != PERMISSION_GRANTED)
- * is handled correctly, etc.
- */
- private static class CheckPermissionVisitor extends ForwardingAstVisitor {
- private boolean mChecksPermission;
- private boolean mDone;
- private final Node mTarget;
-
- public CheckPermissionVisitor(@NonNull Node target) {
- mTarget = target;
- }
-
- @Override
- public boolean visitNode(Node node) {
- return mDone;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node == mTarget) {
- mDone = true;
- }
-
- String name = node.astName().astValue();
- if ((name.startsWith("check") || name.startsWith("enforce"))
- && name.endsWith("Permission")) {
- mChecksPermission = true;
- mDone = true;
- }
- return super.visitMethodInvocation(node);
- }
-
- public boolean checksPermission() {
- return mChecksPermission;
- }
- }
-
- private static boolean isSecurityException(
- @NonNull JavaContext context,
- @NonNull TypeReference typeReference) {
- TypeDescriptor type = context.getType(typeReference);
- return type != null && (type.matchesSignature("java.lang.SecurityException") ||
- type.matchesSignature("java.lang.RuntimeException") ||
- type.matchesSignature("java.lang.Exception") ||
- type.matchesSignature("java.lang.Throwable"));
- }
-
- private PermissionHolder mPermissions;
-
- private PermissionHolder getPermissions(
- @NonNull JavaContext context) {
- if (mPermissions == null) {
- Set<String> permissions = Sets.newHashSetWithExpectedSize(30);
- Set<String> revocable = Sets.newHashSetWithExpectedSize(4);
- LintClient client = context.getClient();
- // Gather permissions from all projects that contribute to the
- // main project.
- Project mainProject = context.getMainProject();
- for (File manifest : mainProject.getManifestFiles()) {
- addPermissions(client, permissions, revocable, manifest);
- }
- for (Project library : mainProject.getAllLibraries()) {
- for (File manifest : library.getManifestFiles()) {
- addPermissions(client, permissions, revocable, manifest);
- }
- }
-
- AndroidVersion minSdkVersion = mainProject.getMinSdkVersion();
- AndroidVersion targetSdkVersion = mainProject.getTargetSdkVersion();
- mPermissions = new SetPermissionLookup(permissions, revocable, minSdkVersion,
- targetSdkVersion);
- }
-
- return mPermissions;
- }
-
- private static void addPermissions(@NonNull LintClient client,
- @NonNull Set<String> permissions,
- @NonNull Set<String> revocable,
- @NonNull File manifest) {
- Document document = XmlUtils.parseDocumentSilently(client.readFile(manifest), true);
- if (document == null) {
- return;
- }
- Element root = document.getDocumentElement();
- if (root == null) {
- return;
- }
- NodeList children = root.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- org.w3c.dom.Node item = children.item(i);
- if (item.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE) {
- continue;
- }
- String nodeName = item.getNodeName();
- if (nodeName.equals(TAG_USES_PERMISSION)) {
- Element element = (Element)item;
- String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (!name.isEmpty()) {
- permissions.add(name);
- }
- } else if (nodeName.equals(TAG_PERMISSION)) {
- Element element = (Element)item;
- String protectionLevel = element.getAttributeNS(ANDROID_URI,
- ATTR_PROTECTION_LEVEL);
- if (VALUE_DANGEROUS.equals(protectionLevel)) {
- String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (!name.isEmpty()) {
- revocable.add(name);
- }
- }
- }
- }
- }
-
- private static void checkResult(@NonNull JavaContext context, @NonNull Node node,
- @NonNull ResolvedAnnotation annotation) {
- if (node.getParent() instanceof ExpressionStatement) {
- String methodName = JavaContext.getMethodName(node);
- Object suggested = annotation.getValue(ATTR_SUGGEST);
-
- // Failing to check permissions is a potential security issue (and had an existing
- // dedicated issue id before which people may already have configured with a
- // custom severity in their LintOptions etc) so continue to use that issue
- // (which also has category Security rather than Correctness) for these:
- Issue issue = CHECK_RESULT;
- if (methodName != null && methodName.startsWith("check")
- && methodName.contains("Permission")) {
- issue = CHECK_PERMISSION;
- }
-
- String message = String.format("The result of `%1$s` is not used",
- methodName);
- if (suggested != null) {
- // TODO: Resolve suggest attribute (e.g. prefix annotation class if it starts
- // with "#" etc?
- message = String.format(
- "The result of `%1$s` is not used; did you mean to call `%2$s`?",
- methodName, suggested.toString());
- }
- context.report(issue, node, context.getLocation(node), message);
- }
- }
-
- private static void checkThreading(
- @NonNull JavaContext context,
- @NonNull Node node,
- @NonNull ResolvedMethod method,
- @NonNull String annotation) {
- String threadContext = getThreadContext(context, node);
- if (threadContext != null && !isCompatibleThread(threadContext, annotation)) {
- String message = String.format("Method %1$s must be called from the `%2$s` thread, currently inferred thread is `%3$s` thread",
- method.getName(), describeThread(annotation), describeThread(threadContext));
- context.report(THREAD, node, context.getLocation(node), message);
- }
- }
-
- @NonNull
- public static String describeThread(@NonNull String annotation) {
- if (UI_THREAD_ANNOTATION.equals(annotation)) {
- return "UI";
- }
- else if (MAIN_THREAD_ANNOTATION.equals(annotation)) {
- return "main";
- }
- else if (BINDER_THREAD_ANNOTATION.equals(annotation)) {
- return "binder";
- }
- else if (WORKER_THREAD_ANNOTATION.equals(annotation)) {
- return "worker";
- } else {
- return "other";
- }
- }
-
- /** returns true if the two threads are compatible */
- public static boolean isCompatibleThread(@NonNull String thread1, @NonNull String thread2) {
- if (thread1.equals(thread2)) {
- return true;
- }
-
- // Allow @UiThread and @MainThread to be combined
- if (thread1.equals(UI_THREAD_ANNOTATION)) {
- if (thread2.equals(MAIN_THREAD_ANNOTATION)) {
- return true;
- }
- } else if (thread1.equals(MAIN_THREAD_ANNOTATION)) {
- if (thread2.equals(UI_THREAD_ANNOTATION)) {
- return true;
- }
- }
-
- return false;
- }
-
- /** Attempts to infer the current thread context at the site of the given method call */
- @Nullable
- private static String getThreadContext(@NonNull JavaContext context,
- @NonNull Node methodCall) {
- Node node = findSurroundingMethod(methodCall);
- if (node != null) {
- ResolvedNode resolved = context.resolve(node);
- if (resolved instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- ResolvedClass cls = method.getContainingClass();
-
- while (method != null) {
- for (ResolvedAnnotation annotation : method.getAnnotations()) {
- String name = annotation.getSignature();
- if (name.startsWith(SUPPORT_ANNOTATIONS_PREFIX)
- && name.endsWith(THREAD_SUFFIX)) {
- return name;
- }
- }
- method = method.getSuperMethod();
- }
-
- // See if we're extending a class with a known threading context
- while (cls != null) {
- for (ResolvedAnnotation annotation : cls.getAnnotations()) {
- String name = annotation.getSignature();
- if (name.startsWith(SUPPORT_ANNOTATIONS_PREFIX)
- && name.endsWith(THREAD_SUFFIX)) {
- return name;
- }
- }
- cls = cls.getSuperClass();
- }
- }
- }
-
- // In the future, we could also try to infer the threading context using
- // other heuristics. For example, if we're in a method with unknown threading
- // context, but we see that the method is called by another method with a known
- // threading context, we can infer that that threading context is the context for
- // this thread too (assuming the call is direct).
-
- return null;
- }
-
- private static boolean isNumber(@NonNull Node argument) {
- return argument instanceof IntegralLiteral || argument instanceof UnaryExpression
- && ((UnaryExpression) argument).astOperator() == UnaryOperator.UNARY_MINUS
- && ((UnaryExpression) argument).astOperand() instanceof IntegralLiteral;
- }
-
- private static boolean isZero(@NonNull Node argument) {
- return argument instanceof IntegralLiteral
- && ((IntegralLiteral) argument).astIntValue() == 0;
- }
-
- private static boolean isMinusOne(@NonNull Node argument) {
- return argument instanceof UnaryExpression
- && ((UnaryExpression) argument).astOperator() == UnaryOperator.UNARY_MINUS
- && ((UnaryExpression) argument).astOperand() instanceof IntegralLiteral
- && ((IntegralLiteral) ((UnaryExpression) argument).astOperand()).astIntValue()
- == 1;
- }
-
- private static void checkResourceType(
- @NonNull JavaContext context,
- @NonNull Node argument,
- @Nullable ResourceType expectedType) {
- List<ResourceType> actual = getResourceTypes(context, argument);
- if (actual == null && (!isNumber(argument) || isZero(argument) || isMinusOne(argument)) ) {
- return;
- } else if (actual != null && (expectedType == null
- || actual.contains(expectedType)
- || expectedType == DRAWABLE && (actual.contains(COLOR) || actual.contains(MIPMAP)))) {
- return;
- }
-
- String message;
- if (actual != null && actual.size() == 1 && actual.get(0) == COLOR_INT_MARKER_TYPE) {
- message = "Expected a color resource id (`R.color.`) but received an RGB integer";
- } else if (expectedType == COLOR_INT_MARKER_TYPE) {
- message = String.format("Should pass resolved color instead of resource id here: " +
- "`getResources().getColor(%1$s)`", argument.toString());
- } else if (expectedType != null) {
- message = String.format(
- "Expected resource of type %1$s", expectedType.getName());
- } else {
- message = "Expected resource identifier (`R`.type.`name`)";
- }
- context.report(RESOURCE_TYPE, argument, context.getLocation(argument), message);
- }
-
- @Nullable
- private static List<ResourceType> getResourceTypes(@NonNull JavaContext context,
- @NonNull Node argument) {
- if (argument instanceof Select) {
- Select node = (Select) argument;
- if (node.astOperand() instanceof Select) {
- Select select = (Select) node.astOperand();
- if (select.astOperand() instanceof Select) { // android.R....
- Select innerSelect = (Select) select.astOperand();
- if (innerSelect.astIdentifier().astValue().equals(R_CLASS)) {
- String typeName = select.astIdentifier().astValue();
- ResourceType type = ResourceType.getEnum(typeName);
- return type != null ? Collections.singletonList(type) : null;
- }
- }
- if (select.astOperand() instanceof VariableReference) {
- VariableReference reference = (VariableReference) select.astOperand();
- if (reference.astIdentifier().astValue().equals(R_CLASS)) {
- String typeName = select.astIdentifier().astValue();
- ResourceType type = ResourceType.getEnum(typeName);
- return type != null ? Collections.singletonList(type) : null;
- }
- }
- }
-
- // Arbitrary packages -- android.R.type.name, foo.bar.R.type.name
- if (node.astIdentifier().astValue().equals(R_CLASS)) {
- Node parent = node.getParent();
- if (parent instanceof Select) {
- Node grandParent = parent.getParent();
- if (grandParent instanceof Select) {
- Select select = (Select) grandParent;
- Expression typeOperand = select.astOperand();
- if (typeOperand instanceof Select) {
- Select typeSelect = (Select) typeOperand;
- String typeName = typeSelect.astIdentifier().astValue();
- ResourceType type = ResourceType.getEnum(typeName);
- return type != null ? Collections.singletonList(type) : null;
- }
- }
- }
- }
- } else if (argument instanceof VariableReference) {
- Statement statement = getParentOfType(argument, Statement.class, false);
- if (statement != null) {
- ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
- while (iterator.hasNext()) {
- if (iterator.next() == statement) {
- if (iterator.hasPrevious()) { // should always be true
- iterator.previous();
- }
- break;
- }
- }
-
- String targetName = ((VariableReference)argument).astIdentifier().astValue();
- while (iterator.hasPrevious()) {
- Node previous = iterator.previous();
- if (previous instanceof VariableDeclaration) {
- VariableDeclaration declaration = (VariableDeclaration) previous;
- VariableDefinition definition = declaration.astDefinition();
- for (VariableDefinitionEntry entry : definition
- .astVariables()) {
- if (entry.astInitializer() != null
- && entry.astName().astValue().equals(targetName)) {
- return getResourceTypes(context, entry.astInitializer());
- }
- }
- } else if (previous instanceof ExpressionStatement) {
- ExpressionStatement expressionStatement = (ExpressionStatement) previous;
- Expression expression = expressionStatement.astExpression();
- if (expression instanceof BinaryExpression &&
- ((BinaryExpression) expression).astOperator()
- == BinaryOperator.ASSIGN) {
- BinaryExpression binaryExpression = (BinaryExpression) expression;
- if (targetName.equals(binaryExpression.astLeft().toString())) {
- return getResourceTypes(context, binaryExpression.astRight());
- }
- }
- }
- }
- }
- } else if (argument instanceof MethodInvocation) {
- ResolvedNode resolved = context.resolve(argument);
- if (resolved != null) {
- for (ResolvedAnnotation annotation : resolved.getAnnotations()) {
- String signature = annotation.getSignature();
- if (signature.equals(COLOR_INT_ANNOTATION)) {
- return Collections.singletonList(COLOR_INT_MARKER_TYPE);
- }
- if (signature.endsWith(RES_SUFFIX)
- && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
- String typeString = signature.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
- signature.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
- ResourceType type = ResourceType.getEnum(typeString);
- if (type != null) {
- return Collections.singletonList(type);
- } else if (typeString.equals("any")) { // @AnyRes
- ResourceType[] types = ResourceType.values();
- List<ResourceType> result = Lists.newArrayListWithExpectedSize(
- types.length);
- for (ResourceType t : types) {
- if (t != COLOR_INT_MARKER_TYPE) {
- result.add(t);
- }
- }
-
- return result;
- }
- }
- }
- }
- }
-
- return null;
- }
-
- private static void checkIntRange(
- @NonNull JavaContext context,
- @NonNull ResolvedAnnotation annotation,
- @NonNull Node argument,
- @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
- String message = getIntRangeError(context, annotation, argument);
- if (message != null) {
- if (findIntDef(allAnnotations) != null) {
- // Don't flag int range errors if there is an int def annotation there too;
- // there could be a valid @IntDef constant. (The @IntDef check will
- // perform range validation by calling getIntRange.)
- return;
- }
-
- context.report(RANGE, argument, context.getLocation(argument), message);
- }
- }
-
- @Nullable
- private static String getIntRangeError(
- @NonNull JavaContext context,
- @NonNull ResolvedAnnotation annotation,
- @NonNull Node argument) {
- Object object = ConstantEvaluator.evaluate(context, argument);
- if (!(object instanceof Number)) {
- return null;
- }
- long value = ((Number)object).longValue();
- long from = getLongAttribute(annotation, ATTR_FROM, Long.MIN_VALUE);
- long to = getLongAttribute(annotation, ATTR_TO, Long.MAX_VALUE);
-
- return getIntRangeError(value, from, to);
- }
-
- /**
- * Checks whether a given integer value is in the allowed range, and if so returns
- * null; otherwise returns a suitable error message.
- */
- private static String getIntRangeError(long value, long from, long to) {
- String message = null;
- if (value < from || value > to) {
- StringBuilder sb = new StringBuilder(20);
- if (value < from) {
- sb.append("Value must be \u2265 ");
- sb.append(Long.toString(from));
- } else {
- assert value > to;
- sb.append("Value must be \u2264 ");
- sb.append(Long.toString(to));
- }
- sb.append(" (was ").append(value).append(')');
- message = sb.toString();
- }
- return message;
- }
-
- private static void checkFloatRange(
- @NonNull JavaContext context,
- @NonNull ResolvedAnnotation annotation,
- @NonNull Node argument) {
- Object object = ConstantEvaluator.evaluate(context, argument);
- if (!(object instanceof Number)) {
- return;
- }
- double value = ((Number)object).doubleValue();
- double from = getDoubleAttribute(annotation, ATTR_FROM, Double.NEGATIVE_INFINITY);
- double to = getDoubleAttribute(annotation, ATTR_TO, Double.POSITIVE_INFINITY);
- boolean fromInclusive = getBoolean(annotation, ATTR_FROM_INCLUSIVE, true);
- boolean toInclusive = getBoolean(annotation, ATTR_TO_INCLUSIVE, true);
-
- String message = getFloatRangeError(value, from, to, fromInclusive, toInclusive, argument);
- if (message != null) {
- context.report(RANGE, argument, context.getLocation(argument), message);
- }
- }
-
- /**
- * Checks whether a given floating point value is in the allowed range, and if so returns
- * null; otherwise returns a suitable error message.
- */
- @Nullable
- private static String getFloatRangeError(double value, double from, double to,
- boolean fromInclusive, boolean toInclusive, @NonNull Node node) {
- if (!((fromInclusive && value >= from || !fromInclusive && value > from) &&
- (toInclusive && value <= to || !toInclusive && value < to))) {
- StringBuilder sb = new StringBuilder(20);
- if (from != Double.NEGATIVE_INFINITY) {
- if (to != Double.POSITIVE_INFINITY) {
- if (fromInclusive && value < from || !fromInclusive && value <= from) {
- sb.append("Value must be ");
- if (fromInclusive) {
- sb.append('\u2265'); // >= sign
- } else {
- sb.append('>');
- }
- sb.append(' ');
- sb.append(Double.toString(from));
- } else {
- assert toInclusive && value > to || !toInclusive && value >= to;
- sb.append("Value must be ");
- if (toInclusive) {
- sb.append('\u2264'); // <= sign
- } else {
- sb.append('<');
- }
- sb.append(' ');
- sb.append(Double.toString(to));
- }
- } else {
- sb.append("Value must be ");
- if (fromInclusive) {
- sb.append('\u2265'); // >= sign
- } else {
- sb.append('>');
- }
- sb.append(' ');
- sb.append(Double.toString(from));
- }
- } else if (to != Double.POSITIVE_INFINITY) {
- sb.append("Value must be ");
- if (toInclusive) {
- sb.append('\u2264'); // <= sign
- } else {
- sb.append('<');
- }
- sb.append(' ');
- sb.append(Double.toString(to));
- }
- sb.append(" (was ");
- if (node instanceof FloatingPointLiteral || node instanceof IntegralLiteral) {
- // Use source text instead to avoid rounding errors involved in conversion, e.g
- // Error: Value must be > 2.5 (was 2.490000009536743) [Range]
- // printAtLeastExclusive(2.49f); // ERROR
- // ~~~~~
- String str = node.toString();
- if (str.endsWith("f") || str.endsWith("F")) {
- str = str.substring(0, str.length() - 1);
- }
- sb.append(str);
- } else {
- sb.append(value);
- }
- sb.append(')');
- return sb.toString();
- }
- return null;
- }
-
- private static void checkSize(
- @NonNull JavaContext context,
- @NonNull ResolvedAnnotation annotation,
- @NonNull Node argument) {
- int actual;
- if (argument instanceof StringLiteral) {
- // Check string length
- StringLiteral literal = (StringLiteral) argument;
- String s = literal.astValue();
- actual = s.length();
- } else if (argument instanceof ArrayCreation) {
- ArrayCreation literal = (ArrayCreation) argument;
- ArrayInitializer initializer = literal.astInitializer();
- if (initializer == null) {
- return;
- }
- actual = initializer.astExpressions().size();
- } else {
- // TODO: Collections syntax, e.g. Arrays.asList => param count, emptyList=0, singleton=1, etc
- // TODO: Flow analysis
- // No flow analysis for this check yet, only checking literals passed in as parameters
- return;
- }
- long exact = getLongAttribute(annotation, ATTR_VALUE, -1);
- long min = getLongAttribute(annotation, ATTR_MIN, Long.MIN_VALUE);
- long max = getLongAttribute(annotation, ATTR_MAX, Long.MAX_VALUE);
- long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1);
-
- String unit;
- boolean isString = argument instanceof StringLiteral;
- if (isString) {
- unit = "length";
- } else {
- unit = "size";
- }
- String message = getSizeError(actual, exact, min, max, multiple, unit);
- if (message != null) {
- context.report(RANGE, argument, context.getLocation(argument), message);
- }
- }
-
- /**
- * Checks whether a given size follows the given constraints, and if so returns
- * null; otherwise returns a suitable error message.
- */
- private static String getSizeError(long actual, long exact, long min, long max, long multiple,
- @NonNull String unit) {
- String message = null;
- if (exact != -1) {
- if (exact != actual) {
- message = String.format("Expected %1$s %2$d (was %3$d)",
- unit, exact, actual);
- }
- } else if (actual < min || actual > max) {
- StringBuilder sb = new StringBuilder(20);
- if (actual < min) {
- sb.append("Expected ").append(unit).append(" \u2265 ");
- sb.append(Long.toString(min));
- } else {
- assert actual > max;
- sb.append("Expected ").append(unit).append(" \u2264 ");
- sb.append(Long.toString(max));
- }
- sb.append(" (was ").append(actual).append(')');
- message = sb.toString();
- } else if (actual % multiple != 0) {
- message = String.format("Expected %1$s to be a multiple of %2$d (was %3$d "
- + "and should be either %4$d or %5$d)",
- unit, multiple, actual, (actual / multiple) * multiple,
- (actual / multiple + 1) * multiple);
- }
- return message;
- }
-
- @Nullable
- private static ResolvedAnnotation findIntRange(
- @NonNull Iterable<ResolvedAnnotation> annotations) {
- for (ResolvedAnnotation annotation : annotations) {
- if (INT_RANGE_ANNOTATION.equals(annotation.getName())) {
- return annotation;
- }
- }
-
- return null;
- }
-
- @Nullable
- private static ResolvedAnnotation findIntDef(
- @NonNull Iterable<ResolvedAnnotation> annotations) {
- for (ResolvedAnnotation annotation : annotations) {
- if (INT_DEF_ANNOTATION.equals(annotation.getName())) {
- return annotation;
- }
- }
-
- return null;
- }
-
- private static void checkTypeDefConstant(
- @NonNull JavaContext context,
- @NonNull ResolvedAnnotation annotation,
- @NonNull Node argument,
- @Nullable Node errorNode,
- boolean flag,
- @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
- if (argument instanceof NullLiteral) {
- // Accepted for @StringDef
- return;
- }
-
- if (argument instanceof StringLiteral) {
- StringLiteral string = (StringLiteral) argument;
- checkTypeDefConstant(context, annotation, argument, errorNode, false, string.astValue(),
- allAnnotations);
- } else if (argument instanceof IntegralLiteral) {
- IntegralLiteral literal = (IntegralLiteral) argument;
- int value = literal.astIntValue();
- if (flag && value == 0) {
- // Accepted for a flag @IntDef
- return;
- }
-
- ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
- if (rangeAnnotation != null) {
- // Allow @IntRange on this number
- if (getIntRangeError(context, rangeAnnotation, literal) == null) {
- return;
- }
- }
-
- checkTypeDefConstant(context, annotation, argument, errorNode, flag, value,
- allAnnotations);
- } else if (isMinusOne(argument)) {
- // -1 is accepted unconditionally for flags
- if (!flag) {
- ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
- if (rangeAnnotation != null) {
- // Allow @IntRange on this number
- if (getIntRangeError(context, rangeAnnotation, argument) == null) {
- return;
- }
- }
-
- reportTypeDef(context, annotation, argument, errorNode, allAnnotations);
- }
- } else if (argument instanceof InlineIfExpression) {
- InlineIfExpression expression = (InlineIfExpression) argument;
- if (expression.astIfTrue() != null) {
- checkTypeDefConstant(context, annotation, expression.astIfTrue(), errorNode, flag,
- allAnnotations);
- }
- if (expression.astIfFalse() != null) {
- checkTypeDefConstant(context, annotation, expression.astIfFalse(), errorNode, flag,
- allAnnotations);
- }
- } else if (argument instanceof UnaryExpression) {
- UnaryExpression expression = (UnaryExpression) argument;
- UnaryOperator operator = expression.astOperator();
- if (flag) {
- checkTypeDefConstant(context, annotation, expression.astOperand(), errorNode, true,
- allAnnotations);
- } else if (operator == UnaryOperator.BINARY_NOT) {
- context.report(TYPE_DEF, expression, context.getLocation(expression),
- "Flag not allowed here");
- } else if (operator == UnaryOperator.UNARY_MINUS) {
- ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
- if (rangeAnnotation != null) {
- // Allow @IntRange on this number
- if (getIntRangeError(context, rangeAnnotation, argument) == null) {
- return;
- }
- }
-
- reportTypeDef(context, annotation, argument, errorNode, allAnnotations);
- }
- } else if (argument instanceof BinaryExpression) {
- // If it's ?: then check both the if and else clauses
- BinaryExpression expression = (BinaryExpression) argument;
- if (flag) {
- checkTypeDefConstant(context, annotation, expression.astLeft(), errorNode, true,
- allAnnotations);
- checkTypeDefConstant(context, annotation, expression.astRight(), errorNode, true,
- allAnnotations);
- } else {
- BinaryOperator operator = expression.astOperator();
- if (operator == BinaryOperator.BITWISE_AND
- || operator == BinaryOperator.BITWISE_OR
- || operator == BinaryOperator.BITWISE_XOR) {
- context.report(TYPE_DEF, expression, context.getLocation(expression),
- "Flag not allowed here");
- }
- }
- } else {
- ResolvedNode resolved = context.resolve(argument);
- if (resolved instanceof ResolvedField) {
- checkTypeDefConstant(context, annotation, argument, errorNode, flag, resolved,
- allAnnotations);
- } else if (argument instanceof VariableReference) {
- Statement statement = getParentOfType(argument, Statement.class, false);
- if (statement != null) {
- ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
- while (iterator.hasNext()) {
- if (iterator.next() == statement) {
- if (iterator.hasPrevious()) { // should always be true
- iterator.previous();
- }
- break;
- }
- }
-
- String targetName = ((VariableReference)argument).astIdentifier().astValue();
- while (iterator.hasPrevious()) {
- Node previous = iterator.previous();
- if (previous instanceof VariableDeclaration) {
- VariableDeclaration declaration = (VariableDeclaration) previous;
- VariableDefinition definition = declaration.astDefinition();
- for (VariableDefinitionEntry entry : definition
- .astVariables()) {
- if (entry.astInitializer() != null
- && entry.astName().astValue().equals(targetName)) {
- checkTypeDefConstant(context, annotation,
- entry.astInitializer(),
- errorNode != null ? errorNode : argument, flag,
- allAnnotations);
- return;
- }
- }
- } else if (previous instanceof ExpressionStatement) {
- ExpressionStatement expressionStatement = (ExpressionStatement) previous;
- Expression expression = expressionStatement.astExpression();
- if (expression instanceof BinaryExpression &&
- ((BinaryExpression) expression).astOperator()
- == BinaryOperator.ASSIGN) {
- BinaryExpression binaryExpression = (BinaryExpression) expression;
- if (targetName.equals(binaryExpression.astLeft().toString())) {
- checkTypeDefConstant(context, annotation,
- binaryExpression.astRight(),
- errorNode != null ? errorNode : argument, flag,
- allAnnotations);
- return;
- }
- }
- }
- }
- }
- }
- }
- }
-
- private static void checkTypeDefConstant(@NonNull JavaContext context,
- @NonNull ResolvedAnnotation annotation, @NonNull Node argument,
- @Nullable Node errorNode, boolean flag, Object value,
- @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
- Object allowed = annotation.getValue();
- if (allowed instanceof Object[]) {
- Object[] allowedValues = (Object[]) allowed;
- for (Object o : allowedValues) {
- if (o.equals(value)) {
- return;
- }
- }
- reportTypeDef(context, argument, errorNode, flag, allowedValues, allAnnotations);
- }
- }
-
- private static void reportTypeDef(@NonNull JavaContext context,
- @NonNull ResolvedAnnotation annotation, @NonNull Node argument,
- @Nullable Node errorNode, @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
- Object allowed = annotation.getValue();
- if (allowed instanceof Object[]) {
- Object[] allowedValues = (Object[]) allowed;
- reportTypeDef(context, argument, errorNode, false, allowedValues, allAnnotations);
- }
- }
-
- private static void reportTypeDef(@NonNull JavaContext context, @NonNull Node node,
- @Nullable Node errorNode, boolean flag, @NonNull Object[] allowedValues,
- @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
- String values = listAllowedValues(allowedValues);
- String message;
- if (flag) {
- message = "Must be one or more of: " + values;
- } else {
- message = "Must be one of: " + values;
- }
-
- ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
- if (rangeAnnotation != null) {
- // Allow @IntRange on this number
- String rangeError = getIntRangeError(context, rangeAnnotation, node);
- if (rangeError != null && !rangeError.isEmpty()) {
- message += " or " + Character.toLowerCase(rangeError.charAt(0))
- + rangeError.substring(1);
- }
- }
-
- if (errorNode == null) {
- errorNode = node;
- }
- context.report(TYPE_DEF, errorNode, context.getLocation(errorNode), message);
- }
-
- private static String listAllowedValues(@NonNull Object[] allowedValues) {
- StringBuilder sb = new StringBuilder();
- for (Object allowedValue : allowedValues) {
- String s;
- if (allowedValue instanceof Integer) {
- s = allowedValue.toString();
- } else if (allowedValue instanceof ResolvedNode) {
- ResolvedNode node = (ResolvedNode) allowedValue;
- if (node instanceof ResolvedField) {
- ResolvedField field = (ResolvedField) node;
- String containingClassName = field.getContainingClassName();
- containingClassName = containingClassName.substring(containingClassName.lastIndexOf('.') + 1);
- s = containingClassName + "." + field.getName();
- } else {
- s = node.getSignature();
- }
- } else {
- continue;
- }
- if (sb.length() > 0) {
- sb.append(", ");
- }
- sb.append(s);
- }
- return sb.toString();
- }
-
- private static double getDoubleAttribute(@NonNull ResolvedAnnotation annotation,
- @NonNull String name, double defaultValue) {
- Object value = annotation.getValue(name);
- if (value instanceof Number) {
- return ((Number) value).doubleValue();
- }
-
- return defaultValue;
- }
-
- private static long getLongAttribute(@NonNull ResolvedAnnotation annotation,
- @NonNull String name, long defaultValue) {
- Object value = annotation.getValue(name);
- if (value instanceof Number) {
- return ((Number) value).longValue();
- }
-
- return defaultValue;
- }
-
- private static boolean getBoolean(@NonNull ResolvedAnnotation annotation,
- @NonNull String name, boolean defaultValue) {
- Object value = annotation.getValue(name);
- if (value instanceof Boolean) {
- return ((Boolean) value);
- }
-
- return defaultValue;
- }
-
- @NonNull
- static Iterable<ResolvedAnnotation> filterRelevantAnnotations(
- @NonNull Iterable<ResolvedAnnotation> annotations) {
- List<ResolvedAnnotation> result = null;
- Iterator<ResolvedAnnotation> iterator = annotations.iterator();
- int index = 0;
- while (iterator.hasNext()) {
- ResolvedAnnotation annotation = iterator.next();
- index++;
-
- String signature = annotation.getSignature();
- if (signature.startsWith("java.")) {
- // @Override, @SuppressWarnings etc. Ignore
- continue;
- }
-
- if (signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
- // Bail on the nullness annotations early since they're the most commonly
- // defined ones. They're not analyzed in lint yet.
- if (signature.endsWith(".Nullable") || signature.endsWith(".NonNull")) {
- continue;
- }
-
- // Common case: there's just one annotation; no need to create a list copy
- if (!iterator.hasNext() && index == 1) {
- return annotations;
- }
- if (result == null) {
- result = new ArrayList<ResolvedAnnotation>(2);
- }
- result.add(annotation);
- }
-
- // Special case @IntDef and @StringDef: These are used on annotations
- // themselves. For example, you create a new annotation named @foo.bar.Baz,
- // annotate it with @IntDef, and then use @foo.bar.Baz in your signatures.
- // Here we want to map from @foo.bar.Baz to the corresponding int def.
- // Don't need to compute this if performing @IntDef or @StringDef lookup
- ResolvedClass type = annotation.getClassType();
- if (type != null) {
- Iterator<ResolvedAnnotation> iterator2 = type.getAnnotations().iterator();
- while (iterator2.hasNext()) {
- ResolvedAnnotation inner = iterator2.next();
- if (inner.matches(INT_DEF_ANNOTATION)
- || inner.matches(PERMISSION_ANNOTATION)
- || inner.matches(INT_RANGE_ANNOTATION)
- || inner.matches(STRING_DEF_ANNOTATION)) {
- if (!iterator.hasNext() && !iterator2.hasNext() && index == 1) {
- return annotations;
- }
- if (result == null) {
- result = new ArrayList<ResolvedAnnotation>(2);
- }
- result.add(inner);
- }
- }
- }
- }
-
- return result != null ? result : Collections.<ResolvedAnnotation>emptyList();
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public
- List<Class<? extends Node>> getApplicableNodeTypes() {
- //noinspection unchecked
- return Arrays.<Class<? extends Node>>asList(
- MethodInvocation.class,
- ConstructorInvocation.class,
- EnumConstant.class);
- }
-
- @Nullable
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new CallVisitor(context);
- }
-
- private class CallVisitor extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public CallVisitor(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitMethodInvocation(@NonNull MethodInvocation call) {
- ResolvedNode resolved = mContext.resolve(call);
- if (resolved instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- checkCall(call, method);
- }
-
- return false;
- }
-
- @Override
- public boolean visitConstructorInvocation(@NonNull ConstructorInvocation call) {
- ResolvedNode resolved = mContext.resolve(call);
- if (resolved instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- checkCall(call, method);
- }
-
- return false;
- }
-
- @Override
- public boolean visitEnumConstant(EnumConstant node) {
- ResolvedNode resolved = mContext.resolve(node);
- if (resolved instanceof ResolvedMethod) {
- ResolvedMethod method = (ResolvedMethod) resolved;
- checkCall(node, method);
- }
-
- return false;
- }
-
- private void checkCall(@NonNull Node call, ResolvedMethod method) {
- Iterable<ResolvedAnnotation> annotations = method.getAnnotations();
- annotations = filterRelevantAnnotations(annotations);
- for (ResolvedAnnotation annotation : annotations) {
- checkMethodAnnotation(mContext, method, call, annotation);
- }
-
- // Look for annotations on the class as well: these trickle
- // down to all the methods in the class
- ResolvedClass containingClass = method.getContainingClass();
- annotations = containingClass.getAnnotations();
- annotations = filterRelevantAnnotations(annotations);
- for (ResolvedAnnotation annotation : annotations) {
- checkMethodAnnotation(mContext, method, call, annotation);
- }
-
- Iterator<Expression> arguments = JavaContext.getParameters(call);
- for (int i = 0, n = method.getArgumentCount();
- i < n && arguments.hasNext();
- i++) {
- Expression argument = arguments.next();
-
- annotations = method.getParameterAnnotations(i);
- annotations = filterRelevantAnnotations(annotations);
- for (ResolvedAnnotation annotation : annotations) {
- checkParameterAnnotation(mContext, argument, call, method, annotation,
- annotations);
- }
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
deleted file mode 100644
index 2eaab73..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
+++ /dev/null
@@ -1,722 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PREFIX;
-import static com.android.SdkConstants.ATTR_LOCALE;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TRANSLATABLE;
-import static com.android.SdkConstants.FD_RES_VALUES;
-import static com.android.SdkConstants.STRING_PREFIX;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-import static com.android.SdkConstants.TOOLS_URI;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.Variant;
-import com.android.ide.common.resources.LocaleManager;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.LocaleQualifier;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks for incomplete translations - e.g. keys that are only present in some
- * locales but not all.
- */
-public class TranslationDetector extends ResourceXmlDetector {
- @VisibleForTesting
- static boolean sCompleteRegions =
- System.getenv("ANDROID_LINT_COMPLETE_REGIONS") != null; //$NON-NLS-1$
-
- private static final Implementation IMPLEMENTATION = new Implementation(
- TranslationDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Are all translations complete? */
- public static final Issue MISSING = Issue.create(
- "MissingTranslation", //$NON-NLS-1$
- "Incomplete translation",
- "If an application has more than one locale, then all the strings declared in " +
- "one language should also be translated in all other languages.\n" +
- "\n" +
- "If the string should *not* be translated, you can add the attribute " +
- "`translatable=\"false\"` on the `<string>` element, or you can define all " +
- "your non-translatable strings in a resource file called `donottranslate.xml`. " +
- "Or, you can ignore the issue with a `tools:ignore=\"MissingTranslation\"` " +
- "attribute.\n" +
- "\n" +
- "By default this detector allows regions of a language to just provide a " +
- "subset of the strings and fall back to the standard language strings. " +
- "You can require all regions to provide a full translation by setting the " +
- "environment variable `ANDROID_LINT_COMPLETE_REGIONS`.\n" +
- "\n" +
- "You can tell lint (and other tools) which language is the default language " +
- "in your `res/values/` folder by specifying `tools:locale=\"languageCode\"` for " +
- "the root `<resources>` element in your resource file. (The `tools` prefix refers " +
- "to the namespace declaration `http://schemas.android.com/tools`.)",
- Category.MESSAGES,
- 8,
- Severity.FATAL,
- IMPLEMENTATION);
-
- /** Are there extra translations that are "unused" (appear only in specific languages) ? */
- public static final Issue EXTRA = Issue.create(
- "ExtraTranslation", //$NON-NLS-1$
- "Extra translation",
- "If a string appears in a specific language translation file, but there is " +
- "no corresponding string in the default locale, then this string is probably " +
- "unused. (It's technically possible that your application is only intended to " +
- "run in a specific locale, but it's still a good idea to provide a fallback.).\n" +
- "\n" +
- "Note that these strings can lead to crashes if the string is looked up on any " +
- "locale not providing a translation, so it's important to clean them up.",
- Category.MESSAGES,
- 6,
- Severity.FATAL,
- IMPLEMENTATION);
-
- private Set<String> mNames;
- private Set<String> mTranslatedArrays;
- private Set<String> mNonTranslatable;
- private boolean mIgnoreFile;
- private Map<File, Set<String>> mFileToNames;
- private Map<File, String> mFileToLocale;
-
- /** Locations for each untranslated string name. Populated during phase 2, if necessary */
- private Map<String, Location> mMissingLocations;
-
- /** Locations for each extra translated string name. Populated during phase 2, if necessary */
- private Map<String, Location> mExtraLocations;
-
- /** Error messages for each untranslated string name. Populated during phase 2, if necessary */
- private Map<String, String> mDescriptions;
-
- /** Constructs a new {@link TranslationDetector} */
- public TranslationDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STRING,
- TAG_STRING_ARRAY
- );
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- if (context.getDriver().getPhase() == 1) {
- mFileToNames = new HashMap<File, Set<String>>();
- }
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mNames = new HashSet<String>();
- }
-
- // Convention seen in various projects
- mIgnoreFile = context.file.getName().startsWith("donottranslate") //$NON-NLS-1$
- || UnusedResourceDetector.isAnalyticsFile(context);
-
- if (!context.getProject().getReportIssues()) {
- mIgnoreFile = true;
- }
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- if (context.getPhase() == 1) {
- // Store this layout's set of ids for full project analysis in afterCheckProject
- if (context.getProject().getReportIssues() && mNames != null && !mNames.isEmpty()) {
- mFileToNames.put(context.file, mNames);
-
- Element root = ((XmlContext) context).document.getDocumentElement();
- if (root != null) {
- String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
- if (locale != null && !locale.isEmpty()) {
- if (mFileToLocale == null) {
- mFileToLocale = Maps.newHashMap();
- }
- mFileToLocale.put(context.file, locale);
- }
- // Add in English here if not specified? Worry about false positives listing "en" explicitly
- }
- }
-
- mNames = null;
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- // NOTE - this will look for the presence of translation strings.
- // If you create a resource folder but don't actually place a file in it
- // we won't detect that, but it seems like a smaller problem.
-
- checkTranslations(context);
-
- mFileToNames = null;
-
- if (mMissingLocations != null || mExtraLocations != null) {
- context.getDriver().requestRepeat(this, Scope.ALL_RESOURCES_SCOPE);
- }
- } else {
- assert context.getPhase() == 2;
-
- reportMap(context, MISSING, mMissingLocations);
- reportMap(context, EXTRA, mExtraLocations);
- mMissingLocations = null;
- mExtraLocations = null;
- mDescriptions = null;
- }
- }
-
- private void reportMap(Context context, Issue issue, Map<String, Location> map) {
- if (map != null) {
- for (Map.Entry<String, Location> entry : map.entrySet()) {
- Location location = entry.getValue();
- String name = entry.getKey();
- String message = mDescriptions.get(name);
-
- if (location == null) {
- location = Location.create(context.getProject().getDir());
- }
-
- // We were prepending locations, but we want to prefer the base folders
- location = Location.reverse(location);
-
- context.report(issue, location, message);
- }
- }
- }
-
- private void checkTranslations(Context context) {
- // Only one file defining strings? If so, no problems.
- Set<File> files = mFileToNames.keySet();
- Set<File> parentFolders = new HashSet<File>();
- for (File file : files) {
- parentFolders.add(file.getParentFile());
- }
- if (parentFolders.size() == 1
- && FD_RES_VALUES.equals(parentFolders.iterator().next().getName())) {
- // Only one language - no problems.
- return;
- }
-
- boolean reportMissing = context.isEnabled(MISSING);
- boolean reportExtra = context.isEnabled(EXTRA);
-
- // res/strings.xml etc
- String defaultLanguage = "Default";
-
- Map<File, String> parentFolderToLanguage = new HashMap<File, String>();
- for (File parent : parentFolders) {
- String name = parent.getName();
-
- // Look up the language for this folder.
- String language = getLanguageTag(name);
- if (language == null) {
- language = defaultLanguage;
- }
-
- parentFolderToLanguage.put(parent, language);
- }
-
- int languageCount = parentFolderToLanguage.values().size();
- if (languageCount == 0 || languageCount == 1 && defaultLanguage.equals(
- parentFolderToLanguage.values().iterator().next())) {
- // At most one language -- no problems.
- return;
- }
-
- // Merge together the various files building up the translations for each language
- Map<String, Set<String>> languageToStrings =
- new HashMap<String, Set<String>>(languageCount);
- Set<String> allStrings = new HashSet<String>(200);
- for (File file : files) {
- String language = null;
- if (mFileToLocale != null) {
- String locale = mFileToLocale.get(file);
- if (locale != null) {
- int index = locale.indexOf('-');
- if (index != -1) {
- locale = locale.substring(0, index);
- }
- language = locale;
- }
- }
- if (language == null) {
- language = parentFolderToLanguage.get(file.getParentFile());
- }
- assert language != null : file.getParent();
- Set<String> fileStrings = mFileToNames.get(file);
-
- Set<String> languageStrings = languageToStrings.get(language);
- if (languageStrings == null) {
- // We don't need a copy; we're done with the string tables now so we
- // can modify them
- languageToStrings.put(language, fileStrings);
- } else {
- languageStrings.addAll(fileStrings);
- }
- allStrings.addAll(fileStrings);
- }
-
- Set<String> defaultStrings = languageToStrings.get(defaultLanguage);
- if (defaultStrings == null) {
- defaultStrings = new HashSet<String>();
- }
-
- // See if it looks like the user has named a specific locale as the base language
- // (this impacts whether we report strings as "extra" or "missing")
- if (mFileToLocale != null) {
- Set<String> specifiedLocales = Sets.newHashSet();
- for (Map.Entry<File, String> entry : mFileToLocale.entrySet()) {
- String locale = entry.getValue();
- int index = locale.indexOf('-');
- if (index != -1) {
- locale = locale.substring(0, index);
- }
- specifiedLocales.add(locale);
- }
- if (specifiedLocales.size() == 1) {
- String first = specifiedLocales.iterator().next();
- Set<String> languageStrings = languageToStrings.get(first);
- assert languageStrings != null;
- defaultStrings.addAll(languageStrings);
- }
- }
-
- int stringCount = allStrings.size();
-
- // Treat English is the default language if not explicitly specified
- if (!sCompleteRegions && !languageToStrings.containsKey("en")
- && mFileToLocale == null) { //$NON-NLS-1$
- // But only if we have an actual region
- for (String l : languageToStrings.keySet()) {
- if (l.startsWith("en-")) { //$NON-NLS-1$
- languageToStrings.put("en", defaultStrings); //$NON-NLS-1$
- break;
- }
- }
- }
-
- List<String> resConfigLanguages = getResConfigLanguages(context.getMainProject());
- if (resConfigLanguages != null) {
- List<String> keys = Lists.newArrayList(languageToStrings.keySet());
- for (String locale : keys) {
- if (defaultLanguage.equals(locale)) {
- continue;
- }
- String language = locale;
- int index = language.indexOf('-');
- if (index != -1) {
- // Strip off region
- language = language.substring(0, index);
- }
- if (!resConfigLanguages.contains(language)) {
- languageToStrings.remove(locale);
- }
- }
- }
-
- // Do we need to resolve fallback strings for regions that only define a subset
- // of the strings in the language and fall back on the main language for the rest?
- if (!sCompleteRegions) {
- for (String l : languageToStrings.keySet()) {
- if (l.indexOf('-') != -1) {
- // Yes, we have regions. Merge all base language string names into each region.
- for (Map.Entry<String, Set<String>> entry : languageToStrings.entrySet()) {
- Set<String> strings = entry.getValue();
- if (stringCount != strings.size()) {
- String languageRegion = entry.getKey();
- int regionIndex = languageRegion.indexOf('-');
- if (regionIndex != -1) {
- String language = languageRegion.substring(0, regionIndex);
- Set<String> fallback = languageToStrings.get(language);
- if (fallback != null) {
- strings.addAll(fallback);
- }
- }
- }
- }
- // We only need to do this once; when we see the first region we know
- // we need to do it; once merged we can bail
- break;
- }
- }
- }
-
- // Fast check to see if there's no problem: if the default locale set is the
- // same as the all set (meaning there are no extra strings in the other languages)
- // then we can quickly determine if everything is okay by just making sure that
- // each language defines everything. If that's the case they will all have the same
- // string count.
- if (stringCount == defaultStrings.size()) {
- boolean haveError = false;
- for (Map.Entry<String, Set<String>> entry : languageToStrings.entrySet()) {
- Set<String> strings = entry.getValue();
- if (stringCount != strings.size()) {
- haveError = true;
- break;
- }
- }
- if (!haveError) {
- return;
- }
- }
-
- List<String> languages = new ArrayList<String>(languageToStrings.keySet());
- Collections.sort(languages);
- for (String language : languages) {
- Set<String> strings = languageToStrings.get(language);
- if (defaultLanguage.equals(language)) {
- continue;
- }
-
- // if strings.size() == stringCount, then this language is defining everything,
- // both all the default language strings and the union of all extra strings
- // defined in other languages, so there's no problem.
- if (stringCount != strings.size()) {
- if (reportMissing) {
- Set<String> difference = Sets.difference(defaultStrings, strings);
- if (!difference.isEmpty()) {
- if (mMissingLocations == null) {
- mMissingLocations = new HashMap<String, Location>();
- }
- if (mDescriptions == null) {
- mDescriptions = new HashMap<String, String>();
- }
-
- for (String s : difference) {
- mMissingLocations.put(s, null);
- String message = mDescriptions.get(s);
- if (message == null) {
- message = String.format("\"`%1$s`\" is not translated in %2$s",
- s, getLanguageDescription(language));
- } else {
- message = message + ", " + getLanguageDescription(language);
- }
- mDescriptions.put(s, message);
- }
- }
- }
- }
-
- if (stringCount != defaultStrings.size()) {
- if (reportExtra) {
- Set<String> difference = Sets.difference(strings, defaultStrings);
- if (!difference.isEmpty()) {
- if (mExtraLocations == null) {
- mExtraLocations = new HashMap<String, Location>();
- }
- if (mDescriptions == null) {
- mDescriptions = new HashMap<String, String>();
- }
-
- for (String s : difference) {
- if (mTranslatedArrays != null && mTranslatedArrays.contains(s)) {
- continue;
- }
- if (mNonTranslatable != null && mNonTranslatable.contains(s)) {
- continue;
- }
-
- mExtraLocations.put(s, null);
- String message = String.format(
- "\"`%1$s`\" is translated here but not found in default locale", s);
- mDescriptions.put(s, message);
- }
- }
- }
- }
- }
- }
-
- public static String getLanguageDescription(@NonNull String locale) {
- int index = locale.indexOf('-');
- String regionCode = null;
- String languageCode = locale;
- if (index != -1) {
- regionCode = locale.substring(index + 1).toUpperCase(Locale.US);
- languageCode = locale.substring(0, index).toLowerCase(Locale.US);
- }
-
- String languageName = LocaleManager.getLanguageName(languageCode);
- if (languageName != null) {
- if (regionCode != null) {
- String regionName = LocaleManager.getRegionName(regionCode);
- if (regionName != null) {
- languageName = languageName + ": " + regionName;
- }
- }
-
- return String.format("\"%1$s\" (%2$s)", locale, languageName);
- } else {
- return '"' + locale + '"';
- }
- }
-
-
- /** Look up the language for the given folder name */
- private static String getLanguageTag(String name) {
- if (FD_RES_VALUES.equals(name)) {
- return null;
- }
-
- FolderConfiguration configuration = FolderConfiguration.getConfigForFolder(name);
- if (configuration != null) {
- LocaleQualifier locale = configuration.getLocaleQualifier();
- if (locale != null && !locale.hasFakeValue()) {
- return locale.getTag();
- }
- }
-
- return null;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (mIgnoreFile) {
- return;
- }
-
- Attr attribute = element.getAttributeNode(ATTR_NAME);
-
- if (context.getPhase() == 2) {
- // Just locating names requested in the {@link #mLocations} map
- if (attribute == null) {
- return;
- }
- String name = attribute.getValue();
- if (mMissingLocations != null && mMissingLocations.containsKey(name)) {
- String language = getLanguageTag(context.file.getParentFile().getName());
- if (language == null) {
- if (context.getDriver().isSuppressed(context, MISSING, element)) {
- mMissingLocations.remove(name);
- return;
- }
-
- Location location = context.getLocation(attribute);
- location.setClientData(element);
- location.setSecondary(mMissingLocations.get(name));
- mMissingLocations.put(name, location);
- }
- }
- if (mExtraLocations != null && mExtraLocations.containsKey(name)) {
- String language = getLanguageTag(context.file.getParentFile().getName());
- if (language != null) {
- if (context.getDriver().isSuppressed(context, EXTRA, element)) {
- mExtraLocations.remove(name);
- return;
- }
- Location location = context.getLocation(attribute);
- location.setClientData(element);
- location.setMessage("Also translated here");
- location.setSecondary(mExtraLocations.get(name));
- mExtraLocations.put(name, location);
- }
- }
- return;
- }
-
- assert context.getPhase() == 1;
- if (attribute == null || attribute.getValue().isEmpty()) {
- context.report(MISSING, element, context.getLocation(element),
- "Missing `name` attribute in `<string>` declaration");
- } else {
- String name = attribute.getValue();
-
- Attr translatable = element.getAttributeNode(ATTR_TRANSLATABLE);
- if (translatable != null && !Boolean.valueOf(translatable.getValue())) {
- String l = LintUtils.getLocaleAndRegion(context.file.getParentFile().getName());
- //noinspection VariableNotUsedInsideIf
- if (l != null) {
- context.report(EXTRA, translatable, context.getLocation(translatable),
- "Non-translatable resources should only be defined in the base " +
- "`values/` folder");
- } else {
- if (mNonTranslatable == null) {
- mNonTranslatable = new HashSet<String>();
- }
- mNonTranslatable.add(name);
- }
- return;
- } else if (name.equals("google_maps_key") //$NON-NLS-1$
- || name.equals("google_maps_key_instructions")) { //$NON-NLS-1$
- // Older versions of the templates shipped with these not marked as
- // non-translatable; don't flag them
- if (mNonTranslatable == null) {
- mNonTranslatable = new HashSet<String>();
- }
- mNonTranslatable.add(name);
- return;
- }
-
- if (element.getTagName().equals(TAG_STRING_ARRAY) &&
- allItemsAreReferences(element)) {
- // No need to provide translations for string arrays where all
- // the children items are defined as translated string resources,
- // e.g.
- // <string-array name="foo">
- // <item>@string/item1</item>
- // <item>@string/item2</item>
- // </string-array>
- // However, we need to remember these names such that we don't consider
- // these arrays "extra" if one of the *translated* versions of the array
- // perform an inline translation of an array item
- if (mTranslatedArrays == null) {
- mTranslatedArrays = new HashSet<String>();
- }
- mTranslatedArrays.add(name);
- return;
- }
-
- // Check for duplicate name definitions? No, because there can be
- // additional customizations like product=
- //if (mNames.contains(name)) {
- // context.mClient.report(ISSUE, context.getLocation(attribute),
- // String.format("Duplicate name %1$s, already defined earlier in this file",
- // name));
- //}
-
- mNames.add(name);
-
- if (mNonTranslatable != null && mNonTranslatable.contains(name)) {
- String message = String.format("The resource string \"`%1$s`\" has been marked as " +
- "`translatable=\"false\"`", name);
- context.report(EXTRA, attribute, context.getLocation(attribute), message);
- }
-
- // TBD: Also make sure that the strings are not empty or placeholders?
- }
- }
-
- private static boolean allItemsAreReferences(Element element) {
- assert element.getTagName().equals(TAG_STRING_ARRAY);
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node item = childNodes.item(i);
- if (item.getNodeType() == Node.ELEMENT_NODE &&
- TAG_ITEM.equals(item.getNodeName())) {
- NodeList itemChildren = item.getChildNodes();
- for (int j = 0, m = itemChildren.getLength(); j < m; j++) {
- Node valueNode = itemChildren.item(j);
- if (valueNode.getNodeType() == Node.TEXT_NODE) {
- String value = valueNode.getNodeValue().trim();
- if (!value.startsWith(ANDROID_PREFIX)
- && !value.startsWith(STRING_PREFIX)) {
- return false;
- }
- }
- }
- }
- }
-
- return true;
- }
-
- @Nullable
- private static List<String> getResConfigLanguages(@NonNull Project project) {
- if (project.isGradleProject() && project.getGradleProjectModel() != null &&
- project.getCurrentVariant() != null) {
- Set<String> relevantDensities = Sets.newHashSet();
- Variant variant = project.getCurrentVariant();
- List<String> variantFlavors = variant.getProductFlavors();
- AndroidProject gradleProjectModel = project.getGradleProjectModel();
-
- addResConfigsFromFlavor(relevantDensities, null,
- project.getGradleProjectModel().getDefaultConfig());
- for (ProductFlavorContainer container : gradleProjectModel.getProductFlavors()) {
- addResConfigsFromFlavor(relevantDensities, variantFlavors, container);
- }
- if (!relevantDensities.isEmpty()) {
- ArrayList<String> strings = Lists.newArrayList(relevantDensities);
- Collections.sort(strings);
- return strings;
- }
- }
-
- return null;
- }
-
- /**
- * Adds in the resConfig values specified by the given flavor container, assuming
- * it's in one of the relevant variantFlavors, into the given set
- */
- private static void addResConfigsFromFlavor(@NonNull Set<String> relevantLanguages,
- @Nullable List<String> variantFlavors,
- @NonNull ProductFlavorContainer container) {
- ProductFlavor flavor = container.getProductFlavor();
- if (variantFlavors == null || variantFlavors.contains(flavor.getName())) {
- if (!flavor.getResourceConfigurations().isEmpty()) {
- for (String resConfig : flavor.getResourceConfigurations()) {
- // Look for languages; these are of length 2. (ResConfigs
- // can also refer to densities, etc.)
- if (resConfig.length() == 2) {
- relevantLanguages.add(resConfig);
- }
- }
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
deleted file mode 100644
index 5d76e84..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_LOCALE;
-import static com.android.SdkConstants.ATTR_TRANSLATABLE;
-import static com.android.SdkConstants.FD_RES_VALUES;
-import static com.android.SdkConstants.TAG_PLURALS;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.tools.lint.checks.TypoLookup.isLetter;
-import static com.google.common.base.Objects.equal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.resources.configuration.LocaleQualifier;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.base.Charsets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Check which looks for likely typos in Strings.
- * <p>
- * TODO:
- * <ul>
- * <li> Add check of Java String literals too!
- * <li> Add support for <b>additional</b> languages. The typo detector is now
- * multilingual and looks for typos-*locale*.txt files to use. However,
- * we need to seed it with additional typo databases. I did some searching
- * and came up with some alternatives. Here's the strategy I used:
- * Used Google Translate to translate "Wikipedia Common Misspellings", and
- * then I went to google.no, google.fr etc searching with that translation, and
- * came up with what looks like wikipedia language local lists of typos.
- * This is how I found the Norwegian one for example:
- * <br>
- * http://no.wikipedia.org/wiki/Wikipedia:Liste_over_alminnelige_stavefeil/Maskinform
- * <br>
- * Here are some additional possibilities not yet processed:
- * <ul>
- * <li> French: http://fr.wikipedia.org/wiki/Wikip%C3%A9dia:Liste_de_fautes_d'orthographe_courantes
- * (couldn't find a machine-readable version there?)
- * <li> Swedish:
- * http://sv.wikipedia.org/wiki/Wikipedia:Lista_%C3%B6ver_vanliga_spr%C3%A5kfel
- * (couldn't find a machine-readable version there?)
- * <li> German
- * http://de.wikipedia.org/wiki/Wikipedia:Liste_von_Tippfehlern/F%C3%BCr_Maschinen
- * </ul>
- * <li> Consider also digesting files like
- * http://sv.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/Typos
- * See http://en.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/User_manual.
- * </ul>
- */
-public class TypoDetector extends ResourceXmlDetector {
- @Nullable private TypoLookup mLookup;
- @Nullable private String mLastLanguage;
- @Nullable private String mLastRegion;
- @Nullable private String mLanguage;
- @Nullable private String mRegion;
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "Typos", //$NON-NLS-1$
- "Spelling error",
-
- "This check looks through the string definitions, and if it finds any words " +
- "that look like likely misspellings, they are flagged.",
- Category.MESSAGES,
- 7,
- Severity.WARNING,
- new Implementation(
- TypoDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
- /** Constructs a new detector */
- public TypoDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- /** Look up the locale and region from the given parent folder name and store it
- * in {@link #mLanguage} and {@link #mRegion} */
- private void initLocale(@NonNull String parent) {
- mLanguage = null;
- mRegion = null;
-
- if (parent.equals(FD_RES_VALUES)) {
- return;
- }
-
- LocaleQualifier locale = LintUtils.getLocale(parent);
- if (locale != null) {
- mLanguage = locale.getLanguage();
- mRegion = locale.hasRegion() ? locale.getRegion() : null;
- }
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- initLocale(context.file.getParentFile().getName());
- if (mLanguage == null) {
- // Check to see if the user has specified the language for this folder
- // using a tools:locale attribute
- if (context instanceof XmlContext) {
- Element root = ((XmlContext) context).document.getDocumentElement();
- if (root != null) {
- String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
- if (locale != null && !locale.isEmpty()) {
- initLocale(FD_RES_VALUES + '-' + locale);
- }
- }
- }
-
- if (mLanguage == null) {
- mLanguage = "en"; //$NON-NLS-1$
- }
- }
-
- if (!equal(mLastLanguage, mLanguage) || !equal(mLastRegion, mRegion)) {
- mLookup = TypoLookup.get(context.getClient(), mLanguage, mRegion);
- mLastLanguage = mLanguage;
- mLastRegion = mRegion;
- }
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STRING,
- TAG_STRING_ARRAY,
- TAG_PLURALS
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (mLookup == null) {
- return;
- }
-
- visit(context, element, element);
- }
-
- private void visit(XmlContext context, Element parent, Node node) {
- if (node.getNodeType() == Node.TEXT_NODE) {
- // TODO: Figure out how to deal with entities
- check(context, parent, node, node.getNodeValue());
- } else {
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- visit(context, parent, children.item(i));
- }
- }
- }
-
- private void check(XmlContext context, Element element, Node node, String text) {
- int max = text.length();
- int index = 0;
- int lastWordBegin = -1;
- int lastWordEnd = -1;
- boolean checkedTypos = false;
-
- for (; index < max; index++) {
- char c = text.charAt(index);
- if (!Character.isWhitespace(c)) {
- if (c == '@' || (c == '?')) {
- // Don't look for typos in resource references; they are not
- // user visible anyway
- return;
- }
- break;
- }
- }
-
- while (index < max) {
- for (; index < max; index++) {
- char c = text.charAt(index);
- if (c == '\\') {
- index++;
- } else if (Character.isLetter(c)) {
- break;
- }
- }
- if (index >= max) {
- return;
- }
- int begin = index;
- for (; index < max; index++) {
- char c = text.charAt(index);
- if (c == '\\') {
- index++;
- break;
- } else if (!Character.isLetter(c)) {
- break;
- } else if (text.charAt(index) >= 0x80) {
- // Switch to UTF-8 handling for this string
- if (checkedTypos) {
- // If we've already checked words we may have reported typos
- // so create a substring from the current word and on.
- byte[] utf8Text = text.substring(begin).getBytes(Charsets.UTF_8);
- check(context, element, node, utf8Text, 0, utf8Text.length, text, begin);
- } else {
- // If all we've done so far is skip whitespace (common scenario)
- // then no need to substring the text, just re-search with the
- // UTF-8 routines
- byte[] utf8Text = text.getBytes(Charsets.UTF_8);
- check(context, element, node, utf8Text, 0, utf8Text.length, text, 0);
- }
- return;
- }
- }
-
- int end = index;
- checkedTypos = true;
- assert mLookup != null;
- List<String> replacements = mLookup.getTypos(text, begin, end);
- if (replacements != null && isTranslatable(element)) {
- reportTypo(context, node, text, begin, replacements);
- }
-
- checkRepeatedWords(context, element, node, text, lastWordBegin, lastWordEnd, begin,
- end);
-
- lastWordBegin = begin;
- lastWordEnd = end;
- index = end + 1;
- }
- }
-
- private static void checkRepeatedWords(XmlContext context, Element element, Node node,
- String text, int lastWordBegin, int lastWordEnd, int begin, int end) {
- if (lastWordBegin != -1 && end - begin == lastWordEnd - lastWordBegin
- && end - begin > 1) {
- // See whether we have a repeated word
- boolean different = false;
- for (int i = lastWordBegin, j = begin; i < lastWordEnd; i++, j++) {
- if (text.charAt(i) != text.charAt(j)) {
- different = true;
- break;
- }
- }
- if (!different && onlySpace(text, lastWordEnd, begin) && isTranslatable(element)) {
- reportRepeatedWord(context, node, text, lastWordBegin, begin, end);
- }
- }
- }
-
- private static boolean onlySpace(String text, int fromInclusive, int toExclusive) {
- for (int i = fromInclusive; i < toExclusive; i++) {
- if (!Character.isWhitespace(text.charAt(i))) {
- return false;
- }
- }
-
- return true;
- }
-
- private void check(XmlContext context, Element element, Node node, byte[] utf8Text,
- int byteStart, int byteEnd, String text, int charStart) {
- int lastWordBegin = -1;
- int lastWordEnd = -1;
- int index = byteStart;
- while (index < byteEnd) {
- // Find beginning of word
- while (index < byteEnd) {
- byte b = utf8Text[index];
- if (b == '\\') {
- index++;
- charStart++;
- if (index < byteEnd) {
- b = utf8Text[index];
- }
- } else if (isLetter(b)) {
- break;
- }
- index++;
- if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
- // First characters in UTF-8 are always ASCII (0 high bit) or 11XXXXXX
- charStart++;
- }
- }
-
- if (index >= byteEnd) {
- return;
- }
- int charEnd = charStart;
- int begin = index;
-
- // Find end of word. Unicode has the nice property that even 2nd, 3rd and 4th
- // bytes won't match these ASCII characters (because the high bit must be set there)
- while (index < byteEnd) {
- byte b = utf8Text[index];
- if (b == '\\') {
- index++;
- charEnd++;
- if (index < byteEnd) {
- b = utf8Text[index++];
- if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
- charEnd++;
- }
- }
- break;
- } else if (!isLetter(b)) {
- break;
- }
- index++;
- if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
- // First characters in UTF-8 are always ASCII (0 high bit) or 11XXXXXX
- charEnd++;
- }
- }
-
- int end = index;
- List<String> replacements = mLookup.getTypos(utf8Text, begin, end);
- if (replacements != null && isTranslatable(element)) {
- reportTypo(context, node, text, charStart, replacements);
- }
-
- checkRepeatedWords(context, element, node, text, lastWordBegin, lastWordEnd, charStart,
- charEnd);
-
- lastWordBegin = charStart;
- lastWordEnd = charEnd;
- charStart = charEnd;
- }
- }
-
- private static boolean isTranslatable(Element element) {
- Attr translatable = element.getAttributeNode(ATTR_TRANSLATABLE);
- return translatable == null || Boolean.valueOf(translatable.getValue());
- }
-
- /** Report the typo found at the given offset and suggest the given replacements */
- private static void reportTypo(XmlContext context, Node node, String text, int begin,
- List<String> replacements) {
- if (replacements.size() < 2) {
- return;
- }
-
- String typo = replacements.get(0);
- String word = text.substring(begin, begin + typo.length());
-
- String first = null;
- String message;
-
- boolean isCapitalized = Character.isUpperCase(word.charAt(0));
- StringBuilder sb = new StringBuilder(40);
- for (int i = 1, n = replacements.size(); i < n; i++) {
- String replacement = replacements.get(i);
- if (first == null) {
- first = replacement;
- }
- if (sb.length() > 0) {
- sb.append(" or ");
- }
- sb.append('"');
- if (isCapitalized) {
- sb.append(Character.toUpperCase(replacement.charAt(0)));
- sb.append(replacement.substring(1));
- } else {
- sb.append(replacement);
- }
- sb.append('"');
- }
-
- if (first != null && first.equalsIgnoreCase(word)) {
- if (first.equals(word)) {
- return;
- }
- message = String.format(
- "\"%1$s\" is usually capitalized as \"%2$s\"",
- word, first);
- } else {
- message = String.format(
- "\"%1$s\" is a common misspelling; did you mean %2$s ?",
- word, sb.toString());
- }
-
- int end = begin + word.length();
- context.report(ISSUE, node, context.getLocation(node, begin, end), message);
- }
-
- /** Reports a repeated word */
- private static void reportRepeatedWord(XmlContext context, Node node, String text,
- int lastWordBegin,
- int begin, int end) {
- String message = String.format(
- "Repeated word \"%1$s\" in message: possible typo",
- text.substring(begin, end));
- Location location = context.getLocation(node, lastWordBegin, end);
- context.report(ISSUE, node, location, message);
- }
-
- /** Returns the suggested replacements, if any, for the given typo. The error
- * message <b>must</b> be one supplied by lint.
- *
- * @param errorMessage the error message
- * @param format the format of the error message
- * @return a list of replacement words suggested by the error message
- */
- @Nullable
- public static List<String> getSuggestions(@NonNull String errorMessage,
- @NonNull TextFormat format) {
- errorMessage = format.toText(errorMessage);
-
- // The words are all in quotes; the first word is the misspelling,
- // the other words are the suggested replacements
- List<String> words = new ArrayList<String>();
- // Skip the typo
- int index = errorMessage.indexOf('"');
- index = errorMessage.indexOf('"', index + 1);
- index++;
-
- while (true) {
- index = errorMessage.indexOf('"', index);
- if (index == -1) {
- break;
- }
- index++;
- int start = index;
- index = errorMessage.indexOf('"', index);
- if (index == -1) {
- index = errorMessage.length();
- }
- words.add(errorMessage.substring(start, index));
- index++;
- }
-
- return words;
- }
-
- /**
- * Returns the typo word in the error message from this detector
- *
- * @param errorMessage the error message produced earlier by this detector
- * @param format the format of the error message
- * @return the typo
- */
- @Nullable
- public static String getTypo(@NonNull String errorMessage, @NonNull TextFormat format) {
- errorMessage = format.toText(errorMessage);
- // The words are all in quotes
- int index = errorMessage.indexOf('"');
- int start = index + 1;
- index = errorMessage.indexOf('"', start);
- if (index != -1) {
- return errorMessage.substring(start, index);
- }
-
- return null;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
deleted file mode 100644
index 56ede7e..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
+++ /dev/null
@@ -1,778 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.google.common.base.Charsets;
-import com.google.common.base.Splitter;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel.MapMode;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.WeakHashMap;
-
-/**
- * Database of common typos / misspellings.
- */
-public class TypoLookup {
- private static final TypoLookup NONE = new TypoLookup();
-
- /** String separating misspellings and suggested replacements in the text file */
- private static final String WORD_SEPARATOR = "->"; //$NON-NLS-1$
-
- /** Relative path to the typos database file within the Lint installation */
- private static final String XML_FILE_PATH = "tools/support/typos-%1$s.txt"; //$NON-NLS-1$
- private static final String FILE_HEADER = "Typo database used by Android lint\000";
- private static final int BINARY_FORMAT_VERSION = 2;
- private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
- private static final boolean DEBUG_SEARCH = false;
- private static final boolean WRITE_STATS = false;
- /** Default size to reserve for each API entry when creating byte buffer to build up data */
- private static final int BYTES_PER_ENTRY = 28;
-
- private byte[] mData;
- private int[] mIndices;
- private int mWordCount;
-
- private static final WeakHashMap<String, TypoLookup> sInstanceMap =
- new WeakHashMap<String, TypoLookup>();
-
- /**
- * Returns an instance of the Typo database for the given locale
- *
- * @param client the client to associate with this database - used only for
- * logging. The database object may be shared among repeated
- * invocations, and in that case client used will be the one
- * originally passed in. In other words, this parameter may be
- * ignored if the client created is not new.
- * @param locale the locale to look up a typo database for (should be a
- * language code (ISO 639-1, two lowercase character names)
- * @param region the region to look up a typo database for (should be a two
- * letter ISO 3166-1 alpha-2 country code in upper case) language
- * code
- * @return a (possibly shared) instance of the typo database, or null if its
- * data can't be found
- */
- @Nullable
- public static TypoLookup get(@NonNull LintClient client, @NonNull String locale,
- @Nullable String region) {
- synchronized (TypoLookup.class) {
- String key = locale;
-
- if (region != null && region.length() == 2) { // skip BCP-47 regions
- // Allow for region-specific dictionaries. See for example
- // http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences
- assert region.length() == 2
- && Character.isUpperCase(region.charAt(0))
- && Character.isUpperCase(region.charAt(1)) : region;
- // Look for typos-en-rUS.txt etc
- key = locale + 'r' + region;
- }
-
- TypoLookup db = sInstanceMap.get(key);
- if (db == null) {
- String path = String.format(XML_FILE_PATH, key);
- File file = client.findResource(path);
- if (file == null) {
- // AOSP build environment?
- String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
- if (build != null) {
- file = new File(build, ("sdk/files/" //$NON-NLS-1$
- + path.substring(path.lastIndexOf('/') + 1))
- .replace('/', File.separatorChar));
- }
- }
-
- if (file == null || !file.exists()) {
- //noinspection VariableNotUsedInsideIf
- if (region != null) {
- // Fall back to the generic locale (non-region-specific) database
- return get(client, locale, null);
- }
- db = NONE;
- } else {
- db = get(client, file);
- assert db != null : file;
- }
- sInstanceMap.put(key, db);
- }
-
- if (db == NONE) {
- return null;
- } else {
- return db;
- }
- }
- }
-
- /**
- * Returns an instance of the typo database
- *
- * @param client the client to associate with this database - used only for
- * logging
- * @param xmlFile the XML file containing configuration data to use for this
- * database
- * @return a (possibly shared) instance of the typo database, or null
- * if its data can't be found
- */
- @Nullable
- private static TypoLookup get(LintClient client, File xmlFile) {
- if (!xmlFile.exists()) {
- client.log(null, "The typo database file %1$s does not exist", xmlFile);
- return null;
- }
-
- String name = xmlFile.getName();
- if (LintUtils.endsWith(name, DOT_XML)) {
- name = name.substring(0, name.length() - DOT_XML.length());
- }
- File cacheDir = client.getCacheDir(true/*create*/);
- if (cacheDir == null) {
- cacheDir = xmlFile.getParentFile();
- }
-
- File binaryData = new File(cacheDir, name
- // Incorporate version number in the filename to avoid upgrade filename
- // conflicts on Windows (such as issue #26663)
- + '-' + BINARY_FORMAT_VERSION + ".bin"); //$NON-NLS-1$
-
- if (DEBUG_FORCE_REGENERATE_BINARY) {
- System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom "
- + xmlFile + "\nto " + binaryData);
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified()) {
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- }
-
- if (!binaryData.exists()) {
- client.log(null, "The typo database file %1$s does not exist", binaryData);
- return null;
- }
-
- return new TypoLookup(client, xmlFile, binaryData);
- }
-
- private static boolean createCache(LintClient client, File xmlFile, File binaryData) {
- long begin = 0;
- if (WRITE_STATS) {
- begin = System.currentTimeMillis();
- }
-
- // Read in data
- List<String> lines;
- try {
- lines = Files.readLines(xmlFile, Charsets.UTF_8);
- } catch (IOException e) {
- client.log(e, "Can't read typo database file");
- return false;
- }
-
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("Reading data structures took " + (end - begin) + " ms)");
- }
-
- try {
- writeDatabase(binaryData, lines);
- return true;
- } catch (IOException ioe) {
- client.log(ioe, "Can't write typo cache file");
- }
-
- return false;
- }
-
- /** Use one of the {@link #get} factory methods instead */
- private TypoLookup(
- @NonNull LintClient client,
- @NonNull File xmlFile,
- @Nullable File binaryFile) {
- if (binaryFile != null) {
- readData(client, xmlFile, binaryFile);
- }
- }
-
- private TypoLookup() {
- }
-
- private void readData(@NonNull LintClient client, @NonNull File xmlFile,
- @NonNull File binaryFile) {
- if (!binaryFile.exists()) {
- client.log(null, "%1$s does not exist", binaryFile);
- return;
- }
- long start = System.currentTimeMillis();
- try {
- MappedByteBuffer buffer = Files.map(binaryFile, MapMode.READ_ONLY);
- assert buffer.order() == ByteOrder.BIG_ENDIAN;
-
- // First skip the header
- byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII);
- buffer.rewind();
- for (int offset = 0; offset < expectedHeader.length; offset++) {
- if (expectedHeader[offset] != buffer.get()) {
- client.log(null, "Incorrect file header: not an typo database cache " +
- "file, or a corrupt cache file");
- return;
- }
- }
-
- // Read in the format number
- if (buffer.get() != BINARY_FORMAT_VERSION) {
- // Force regeneration of new binary data with up to date format
- if (createCache(client, xmlFile, binaryFile)) {
- readData(client, xmlFile, binaryFile); // Recurse
- }
-
- return;
- }
-
- mWordCount = buffer.getInt();
-
- // Read in the word table indices;
- int count = mWordCount;
- int[] offsets = new int[count];
-
- // Another idea: I can just store the DELTAS in the file (and add them up
- // when reading back in) such that it takes just ONE byte instead of four!
-
- for (int i = 0; i < count; i++) {
- offsets[i] = buffer.getInt();
- }
-
- // No need to read in the rest -- we'll just keep the whole byte array in memory
- // TODO: Make this code smarter/more efficient.
- int size = buffer.limit();
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- mData = b;
- mIndices = offsets;
-
- // TODO: We only need to keep the data portion here since we've initialized
- // the offset array separately.
- // TODO: Investigate (profile) accessing the byte buffer directly instead of
- // accessing a byte array.
- } catch (IOException e) {
- client.log(e, null);
- }
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("\nRead typo database in " + (end - start)
- + " milliseconds.");
- System.out.println("Size of data table: " + mData.length + " bytes ("
- + Integer.toString(mData.length/1024) + "k)\n");
- }
- }
-
- /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */
- private static void writeDatabase(File file, List<String> lines) throws IOException {
- /*
- * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- * as ASCII characters. The purpose of the header is to identify what the file
- * is for, for anyone attempting to open the file.
- * 2. A file version number. If the binary file does not match the reader's expected
- * version, it can ignore it (and regenerate the cache from XML).
- */
-
- // Drop comments etc
- List<String> words = new ArrayList<String>(lines.size());
- for (String line : lines) {
- if (!line.isEmpty() && Character.isLetter(line.charAt(0))) {
- int end = line.indexOf(WORD_SEPARATOR);
- if (end == -1) {
- end = line.trim().length();
- }
- String typo = line.substring(0, end).trim();
- String replacements = line.substring(end + WORD_SEPARATOR.length()).trim();
- if (replacements.isEmpty()) {
- // We don't support empty replacements
- continue;
- }
- String combined = typo + (char) 0 + replacements;
-
- words.add(combined);
- }
- }
-
- byte[][] wordArrays = new byte[words.size()][];
- for (int i = 0, n = words.size(); i < n; i++) {
- String word = words.get(i);
- wordArrays[i] = word.getBytes(Charsets.UTF_8);
- }
- // Sort words, using our own comparator to ensure that it matches the
- // binary search in getTypos()
- Comparator<byte[]> comparator = new Comparator<byte[]>() {
- @Override
- public int compare(byte[] o1, byte[] o2) {
- return TypoLookup.compare(o1, 0, (byte) 0, o2, 0, o2.length);
- }
- };
- Arrays.sort(wordArrays, comparator);
-
- byte[] headerBytes = FILE_HEADER.getBytes(Charsets.US_ASCII);
- int entryCount = wordArrays.length;
- int capacity = entryCount * BYTES_PER_ENTRY + headerBytes.length + 5;
- ByteBuffer buffer = ByteBuffer.allocate(capacity);
- buffer.order(ByteOrder.BIG_ENDIAN);
- // 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- // as ASCII characters. The purpose of the header is to identify what the file
- // is for, for anyone attempting to open the file.
- buffer.put(headerBytes);
-
- // 2. A file version number. If the binary file does not match the reader's expected
- // version, it can ignore it (and regenerate the cache from XML).
- buffer.put((byte) BINARY_FORMAT_VERSION);
-
- // 3. The number of words [1 int]
- buffer.putInt(entryCount);
-
- // 4. Word offset table (one integer per word, pointing to the byte offset in the
- // file (relative to the beginning of the file) where each word begins.
- // The words are always sorted alphabetically.
- int wordOffsetTable = buffer.position();
-
- // Reserve enough room for the offset table here: we will backfill it with pointers
- // as we're writing out the data structures below
- for (int i = 0, n = entryCount; i < n; i++) {
- buffer.putInt(0);
- }
-
- int nextEntry = buffer.position();
- int nextOffset = wordOffsetTable;
-
- // 7. Word entry table. Each word entry consists of the word, followed by the byte 0
- // as a terminator, followed by a comma separated list of suggestions (which
- // may be empty), or a final 0.
- for (int i = 0; i < entryCount; i++) {
- byte[] word = wordArrays[i];
- buffer.position(nextOffset);
- buffer.putInt(nextEntry);
- nextOffset = buffer.position();
- buffer.position(nextEntry);
-
- buffer.put(word); // already embeds 0 to separate typo from words
- buffer.put((byte) 0);
-
- nextEntry = buffer.position();
- }
-
- int size = buffer.position();
- assert size <= buffer.limit();
- buffer.mark();
-
- if (WRITE_STATS) {
- System.out.println("Wrote " + words.size() + " word entries");
- System.out.print("Actual binary size: " + size + " bytes");
- System.out.println(String.format(" (%.1fM)", size/(1024*1024.f)));
-
- System.out.println("Allocated size: " + (entryCount * BYTES_PER_ENTRY) + " bytes");
- System.out.println("Required bytes per entry: " + (size/ entryCount) + " bytes");
- }
-
- // Now dump this out as a file
- // There's probably an API to do this more efficiently; TODO: Look into this.
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- FileOutputStream output = Files.newOutputStreamSupplier(file).getOutput();
- output.write(b);
- output.close();
- }
-
- // For debugging only
- private String dumpEntry(int offset) {
- if (DEBUG_SEARCH) {
- int end = offset;
- while (mData[end] != 0) {
- end++;
- }
- return new String(mData, offset, end - offset, Charsets.UTF_8);
- } else {
- return "<disabled>"; //$NON-NLS-1$
- }
- }
-
- /** Comparison function: *only* used for ASCII strings */
- @VisibleForTesting
- static int compare(byte[] data, int offset, byte terminator, CharSequence s,
- int begin, int end) {
- int i = offset;
- int j = begin;
- for (; ; i++, j++) {
- byte b = data[i];
- if (b == ' ') {
- // We've matched up to the space in a split-word typo, such as
- // in German all zu=>allzu; here we've matched just past "all".
- // Rather than terminating, attempt to continue in the buffer.
- if (j == end) {
- int max = s.length();
- if (end < max && s.charAt(end) == ' ') {
- // Find next word
- for (; end < max; end++) {
- char c = s.charAt(end);
- if (!Character.isLetter(c)) {
- if (c == ' ' && end == j) {
- continue;
- }
- break;
- }
- }
- }
- }
- }
-
- if (j == end) {
- break;
- }
-
- if (b == '*') {
- // Glob match (only supported at the end)
- return 0;
- }
- char c = s.charAt(j);
- byte cb = (byte) c;
- int delta = b - cb;
- if (delta != 0) {
- cb = (byte) Character.toLowerCase(c);
- if (b != cb) {
- // Ensure that it has the right sign
- b = (byte) Character.toLowerCase(b);
- delta = b - cb;
- if (delta != 0) {
- return delta;
- }
- }
- }
- }
-
- return data[i] - terminator;
- }
-
- /** Comparison function used for general UTF-8 encoded strings */
- @VisibleForTesting
- static int compare(byte[] data, int offset, byte terminator, byte[] s,
- int begin, int end) {
- int i = offset;
- int j = begin;
- for (; ; i++, j++) {
- byte b = data[i];
- if (b == ' ') {
- // We've matched up to the space in a split-word typo, such as
- // in German all zu=>allzu; here we've matched just past "all".
- // Rather than terminating, attempt to continue in the buffer.
- // We've matched up to the space in a split-word typo, such as
- // in German all zu=>allzu; here we've matched just past "all".
- // Rather than terminating, attempt to continue in the buffer.
- if (j == end) {
- int max = s.length;
- if (end < max && s[end] == ' ') {
- // Find next word
- for (; end < max; end++) {
- byte cb = s[end];
- if (!isLetter(cb)) {
- if (cb == ' ' && end == j) {
- continue;
- }
- break;
- }
- }
- }
- }
- }
-
- if (j == end) {
- break;
- }
- if (b == '*') {
- // Glob match (only supported at the end)
- return 0;
- }
- byte cb = s[j];
- int delta = b - cb;
- if (delta != 0) {
- cb = toLowerCase(cb);
- b = toLowerCase(b);
- delta = b - cb;
- if (delta != 0) {
- return delta;
- }
- }
-
- if (b == terminator || cb == terminator) {
- return delta;
- }
- }
-
- return data[i] - terminator;
- }
-
- /**
- * Look up whether this word is a typo, and if so, return the typo itself
- * and one or more likely meanings
- *
- * @param text the string containing the word
- * @param begin the index of the first character in the word
- * @param end the index of the first character after the word. Note that the
- * search may extend <b>beyond</b> this index, if for example the
- * word matches a multi-word typo in the dictionary
- * @return a list of the typo itself followed by the replacement strings if
- * the word represents a typo, and null otherwise
- */
- @Nullable
- public List<String> getTypos(@NonNull CharSequence text, int begin, int end) {
- assert end <= text.length();
-
- if (assertionsEnabled()) {
- for (int i = begin; i < end; i++) {
- char c = text.charAt(i);
- if (c >= 128) {
- assert false : "Call the UTF-8 version of this method instead";
- return null;
- }
- }
- }
-
- int low = 0;
- int high = mWordCount - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + text +" with entry at " + offset
- + ": " + dumpEntry(offset));
- }
-
- // Compare the word at the given index.
- int compare = compare(mData, offset, (byte) 0, text, begin, end);
-
- if (compare == 0) {
- offset = mIndices[middle];
-
- // Don't allow matching uncapitalized words, such as "enlish", when
- // the dictionary word is capitalized, "Enlish".
- if (mData[offset] != text.charAt(begin)
- && Character.isLowerCase(text.charAt(begin))) {
- return null;
- }
-
- // Make sure there is a case match; we only want to allow
- // matching capitalized words to capitalized typos or uncapitalized typos
- // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized
- // typos (e.g. "enlish" to "Enlish").
- String glob = null;
- for (int i = begin; ; i++) {
- byte b = mData[offset++];
- if (b == 0) {
- offset--;
- break;
- } else if (b == '*') {
- int globEnd = i;
- while (globEnd < text.length()
- && Character.isLetter(text.charAt(globEnd))) {
- globEnd++;
- }
- glob = text.subSequence(i, globEnd).toString();
- break;
- }
- char c = text.charAt(i);
- byte cb = (byte) c;
- if (b != cb && i > begin) {
- return null;
- }
- }
-
- return computeSuggestions(mIndices[middle], offset, glob);
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return null;
- }
- }
-
- return null;
- }
-
- /**
- * Look up whether this word is a typo, and if so, return the typo itself
- * and one or more likely meanings
- *
- * @param utf8Text the string containing the word, encoded as UTF-8
- * @param begin the index of the first character in the word
- * @param end the index of the first character after the word. Note that the
- * search may extend <b>beyond</b> this index, if for example the
- * word matches a multi-word typo in the dictionary
- * @return a list of the typo itself followed by the replacement strings if
- * the word represents a typo, and null otherwise
- */
- @Nullable
- public List<String> getTypos(@NonNull byte[] utf8Text, int begin, int end) {
- assert end <= utf8Text.length;
-
- int low = 0;
- int high = mWordCount - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- String s = new String(Arrays.copyOfRange(utf8Text, begin, end), Charsets.UTF_8);
- System.out.println("Comparing string " + s +" with entry at " + offset
- + ": " + dumpEntry(offset));
- System.out.println(" middle=" + middle + ", low=" + low + ", high=" + high);
- }
-
- // Compare the word at the given index.
- int compare = compare(mData, offset, (byte) 0, utf8Text, begin, end);
-
- if (DEBUG_SEARCH) {
- System.out.println(" signum=" + (int)Math.signum(compare) + ", delta=" + compare);
- }
-
- if (compare == 0) {
- offset = mIndices[middle];
-
- // Don't allow matching uncapitalized words, such as "enlish", when
- // the dictionary word is capitalized, "Enlish".
- if (mData[offset] != utf8Text[begin] && isUpperCase(mData[offset])) {
- return null;
- }
-
- // Make sure there is a case match; we only want to allow
- // matching capitalized words to capitalized typos or uncapitalized typos
- // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized
- // typos (e.g. "enlish" to "Enlish").
- String glob = null;
- for (int i = begin; ; i++) {
- byte b = mData[offset++];
- if (b == 0) {
- offset--;
- break;
- } else if (b == '*') {
- int globEnd = i;
- while (globEnd < utf8Text.length && isLetter(utf8Text[globEnd])) {
- globEnd++;
- }
- glob = new String(utf8Text, i, globEnd - i, Charsets.UTF_8);
- break;
- }
- byte cb = utf8Text[i];
- if (b != cb && i > begin) {
- return null;
- }
- }
-
- return computeSuggestions(mIndices[middle], offset, glob);
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return null;
- }
- }
-
- return null;
- }
-
- private List<String> computeSuggestions(int begin, int offset, String glob) {
- String typo = new String(mData, begin, offset - begin, Charsets.UTF_8);
-
- if (glob != null) {
- typo = typo.replaceAll("\\*", glob); //$NON-NLS-1$
- }
-
- assert mData[offset] == 0;
- offset++;
- int replacementEnd = offset;
- while (mData[replacementEnd] != 0) {
- replacementEnd++;
- }
- String replacements = new String(mData, offset, replacementEnd - offset, Charsets.UTF_8);
- List<String> words = new ArrayList<String>();
- words.add(typo);
-
- // The first entry should be the typo itself. We need to pass this back since due
- // to multi-match words and globbing it could extend beyond the initial word range
-
- for (String s : Splitter.on(',').omitEmptyStrings().trimResults().split(replacements)) {
- if (glob != null) {
- // Need to append the glob string to each result
- words.add(s.replaceAll("\\*", glob)); //$NON-NLS-1$
- } else {
- words.add(s);
- }
- }
-
- return words;
- }
-
- // "Character" handling for bytes. This assumes that the bytes correspond to Unicode
- // characters in the ISO 8859-1 range, which is are encoded the same way in UTF-8.
- // This obviously won't work to for example uppercase to lowercase conversions for
- // multi byte characters, which means we simply won't catch typos if the dictionaries
- // contain these. None of the currently included dictionaries do. However, it does
- // help us properly deal with punctuation and spacing characters.
-
- static boolean isUpperCase(byte b) {
- return Character.isUpperCase((char) b);
- }
-
- static byte toLowerCase(byte b) {
- return (byte) Character.toLowerCase((char) b);
- }
-
- static boolean isSpace(byte b) {
- return Character.isWhitespace((char) b);
- }
-
- static boolean isLetter(byte b) {
- // Assume that multi byte characters represent letters in other languages.
- // Obviously, it could be unusual punctuation etc but letters are more likely
- // in this context.
- return Character.isLetter((char) b) || (b & 0x80) != 0;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
deleted file mode 100644
index 4117067..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
+++ /dev/null
@@ -1,602 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_REF_PREFIX;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE;
-import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY;
-import static com.android.SdkConstants.RESOURCE_CLZ_ID;
-import static com.android.SdkConstants.R_ATTR_PREFIX;
-import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.R_ID_PREFIX;
-import static com.android.SdkConstants.R_PREFIX;
-import static com.android.SdkConstants.TAG_ARRAY;
-import static com.android.SdkConstants.TAG_INTEGER_ARRAY;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_PLURALS;
-import static com.android.SdkConstants.TAG_RESOURCES;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.utils.SdkUtils.getResourceFieldName;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Lists;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-
-/**
- * Finds unused resources.
- * <p>
- * Note: This detector currently performs *string* analysis to check Java files.
- * The Lint API needs an official Java AST API (or map to an existing one like
- * BCEL for bytecode analysis etc) and once it does this should be updated to
- * use it.
- */
-public class UnusedResourceDetector extends ResourceXmlDetector implements Detector.JavaScanner {
-
- private static final Implementation IMPLEMENTATION = new Implementation(
- UnusedResourceDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES,
- Scope.TEST_SOURCES));
-
- /** Unused resources (other than ids). */
- public static final Issue ISSUE = Issue.create(
- "UnusedResources", //$NON-NLS-1$
- "Unused resources",
- "Unused resources make applications larger and slow down builds.",
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- IMPLEMENTATION);
-
- /** Unused id's */
- public static final Issue ISSUE_IDS = Issue.create(
- "UnusedIds", //$NON-NLS-1$
- "Unused id",
- "This resource id definition appears not to be needed since it is not referenced " +
- "from anywhere. Having id definitions, even if unused, is not necessarily a bad " +
- "idea since they make working on layouts and menus easier, so there is not a " +
- "strong reason to delete these.",
- Category.PERFORMANCE,
- 1,
- Severity.WARNING,
- IMPLEMENTATION)
- .setEnabledByDefault(false);
-
- private Set<String> mDeclarations;
- private Set<String> mReferences;
- private Map<String, Location> mUnused;
-
- /**
- * Constructs a new {@link UnusedResourceDetector}
- */
- public UnusedResourceDetector() {
- }
-
- @Override
- public void run(@NonNull Context context) {
- assert false;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mDeclarations = new HashSet<String>(300);
- mReferences = new HashSet<String>(300);
- }
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- File file = context.file;
-
- boolean isXmlFile = LintUtils.isXmlFile(file);
- if (isXmlFile || LintUtils.isBitmapFile(file)) {
- String fileName = file.getName();
- String parentName = file.getParentFile().getName();
- int dash = parentName.indexOf('-');
- String typeName = parentName.substring(0, dash == -1 ? parentName.length() : dash);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type != null && LintUtils.isFileBasedResourceType(type)) {
- String baseName = fileName.substring(0, fileName.length() - DOT_XML.length());
- String resource = R_PREFIX + typeName + '.' + baseName;
- if (context.getPhase() == 1) {
- mDeclarations.add(resource);
- } else {
- assert context.getPhase() == 2;
- if (mUnused.containsKey(resource)) {
- // Check whether this is an XML document that has a tools:ignore attribute
- // on the document element: if so don't record it as a declaration.
- if (isXmlFile && context instanceof XmlContext) {
- XmlContext xmlContext = (XmlContext) context;
- if (xmlContext.document != null
- && xmlContext.document.getDocumentElement() != null) {
- Element root = xmlContext.document.getDocumentElement();
- if (xmlContext.getDriver().isSuppressed(xmlContext, ISSUE, root)) {
- // Also remove it from consideration such that even the
- // presence of this field in the R file is ignored.
- mUnused.remove(resource);
- return;
- }
- }
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- mUnused.remove(resource);
- return;
- }
-
- recordLocation(resource, Location.create(file));
- }
- }
- }
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mDeclarations.removeAll(mReferences);
- Set<String> unused = mDeclarations;
- mReferences = null;
- mDeclarations = null;
-
- // Remove styles and attributes: they may be used, analysis isn't complete for these
- List<String> styles = new ArrayList<String>();
- for (String resource : unused) {
- // R.style.x, R.styleable.x, R.attr
- if (resource.startsWith("R.style") //$NON-NLS-1$
- || resource.startsWith("R.attr")) { //$NON-NLS-1$
- styles.add(resource);
- }
- }
- unused.removeAll(styles);
-
- // Remove id's if the user has disabled reporting issue ids
- if (!unused.isEmpty() && !context.isEnabled(ISSUE_IDS)) {
- // Remove all R.id references
- List<String> ids = new ArrayList<String>();
- for (String resource : unused) {
- if (resource.startsWith(R_ID_PREFIX)) {
- ids.add(resource);
- }
- }
- unused.removeAll(ids);
- }
-
- if (!unused.isEmpty() && !context.getDriver().hasParserErrors()) {
- mUnused = new HashMap<String, Location>(unused.size());
- for (String resource : unused) {
- mUnused.put(resource, null);
- }
-
- // Request another pass, and in the second pass we'll gather location
- // information for all declaration locations we've found
- context.requestRepeat(this, Scope.ALL_RESOURCES_SCOPE);
- }
- } else {
- assert context.getPhase() == 2;
-
- // Report any resources that we (for some reason) could not find a declaration
- // location for
- if (!mUnused.isEmpty()) {
- // Fill in locations for files that we didn't encounter in other ways
- for (Map.Entry<String, Location> entry : mUnused.entrySet()) {
- String resource = entry.getKey();
- Location location = entry.getValue();
- //noinspection VariableNotUsedInsideIf
- if (location != null) {
- continue;
- }
-
- // Try to figure out the file if it's a file based resource (such as R.layout) --
- // in that case we can figure out the filename since it has a simple mapping
- // from the resource name (though the presence of qualifiers like -land etc
- // makes it a little tricky if there's no base file provided)
- int secondDot = resource.indexOf('.', 2);
- String typeName = resource.substring(2, secondDot); // 2: Skip R.
- ResourceType type = ResourceType.getEnum(typeName);
- if (type != null && LintUtils.isFileBasedResourceType(type)) {
- String name = resource.substring(secondDot + 1);
-
- List<File> folders = Lists.newArrayList();
- List<File> resourceFolders = context.getProject().getResourceFolders();
- for (File res : resourceFolders) {
- File[] f = res.listFiles();
- if (f != null) {
- folders.addAll(Arrays.asList(f));
- }
- }
- if (folders != null) {
- // Process folders in alphabetical order such that we process
- // based folders first: we want the locations in base folder
- // order
- Collections.sort(folders, new Comparator<File>() {
- @Override
- public int compare(File file1, File file2) {
- return file1.getName().compareTo(file2.getName());
- }
- });
- for (File folder : folders) {
- if (folder.getName().startsWith(typeName)) {
- File[] files = folder.listFiles();
- if (files != null) {
- Arrays.sort(files);
- for (File file : files) {
- String fileName = file.getName();
- if (fileName.startsWith(name)
- && fileName.startsWith(".", //$NON-NLS-1$
- name.length())) {
- recordLocation(resource, Location.create(file));
- }
- }
- }
- }
- }
- }
- }
- }
-
- List<String> sorted = new ArrayList<String>(mUnused.keySet());
- Collections.sort(sorted);
-
- Boolean skippedLibraries = null;
-
- for (String resource : sorted) {
- Location location = mUnused.get(resource);
- if (location != null) {
- // We were prepending locations, but we want to prefer the base folders
- location = Location.reverse(location);
- }
-
- if (location == null) {
- if (skippedLibraries == null) {
- skippedLibraries = false;
- for (Project project : context.getDriver().getProjects()) {
- if (!project.getReportIssues()) {
- skippedLibraries = true;
- break;
- }
- }
- }
- if (skippedLibraries) {
- // Skip this resource if we don't have a location, and one or
- // more library projects were skipped; the resource was very
- // probably defined in that library project and only encountered
- // in the main project's java R file
- continue;
- }
- }
-
- String message = String.format("The resource `%1$s` appears to be unused",
- resource);
- Issue issue = getIssue(resource);
- // TODO: Compute applicable node scope
- context.report(issue, location, message);
- }
- }
- }
- }
-
- private static Issue getIssue(String resource) {
- return resource.startsWith(R_ID_PREFIX) ? ISSUE_IDS : ISSUE;
- }
-
- private void recordLocation(String resource, Location location) {
- Location oldLocation = mUnused.get(resource);
- if (oldLocation != null) {
- location.setSecondary(oldLocation);
- }
- mUnused.put(resource, location);
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STYLE,
- TAG_RESOURCES,
- TAG_ARRAY,
- TAG_STRING_ARRAY,
- TAG_INTEGER_ARRAY,
- TAG_PLURALS
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (TAG_RESOURCES.equals(element.getTagName())) {
- for (Element item : LintUtils.getChildren(element)) {
- Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
- if (nameAttribute != null) {
- String name = getResourceFieldName(nameAttribute.getValue());
- String type = item.getTagName();
- if (type.equals(TAG_ITEM)) {
- type = item.getAttribute(ATTR_TYPE);
- if (type == null || type.isEmpty()) {
- type = RESOURCE_CLZ_ID;
- }
- } else if (type.equals("declare-styleable")) { //$NON-NLS-1$
- type = RESOURCE_CLR_STYLEABLE;
- } else if (type.contains("array")) { //$NON-NLS-1$
- // <string-array> etc
- type = RESOURCE_CLZ_ARRAY;
- }
- String resource = R_PREFIX + type + '.' + name;
-
- if (context.getPhase() == 1) {
- mDeclarations.add(resource);
- checkChildRefs(item);
- } else {
- assert context.getPhase() == 2;
- if (mUnused.containsKey(resource)) {
- if (context.getDriver().isSuppressed(context, getIssue(resource),
- item)) {
- mUnused.remove(resource);
- continue;
- }
- if (!context.getProject().getReportIssues()) {
- mUnused.remove(resource);
- continue;
- }
- if (isAnalyticsFile(context)) {
- mUnused.remove(resource);
- continue;
- }
-
- recordLocation(resource, context.getLocation(nameAttribute));
- }
- }
- }
- }
- } else //noinspection VariableNotUsedInsideIf
- if (mReferences != null) {
- assert TAG_STYLE.equals(element.getTagName())
- || TAG_ARRAY.equals(element.getTagName())
- || TAG_PLURALS.equals(element.getTagName())
- || TAG_INTEGER_ARRAY.equals(element.getTagName())
- || TAG_STRING_ARRAY.equals(element.getTagName());
- for (Element item : LintUtils.getChildren(element)) {
- checkChildRefs(item);
- }
- }
- }
-
- private static final String ANALYTICS_FILE = "analytics.xml"; //$NON-NLS-1$
-
- /**
- * Returns true if this XML file corresponds to an Analytics configuration file;
- * these contain some attributes read by the library which won't be flagged as
- * used by the application
- *
- * @param context the context used for scanning
- * @return true if the file represents an analytics file
- */
- public static boolean isAnalyticsFile(Context context) {
- File file = context.file;
- return file.getPath().endsWith(ANALYTICS_FILE) && file.getName().equals(ANALYTICS_FILE);
- }
-
- private void checkChildRefs(Element item) {
- // Look for ?attr/ and @dimen/foo etc references in the item children
- NodeList childNodes = item.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = child.getNodeValue();
-
- int index = text.indexOf(ATTR_REF_PREFIX);
- if (index != -1) {
- String name = text.substring(index + ATTR_REF_PREFIX.length()).trim();
- mReferences.add(R_ATTR_PREFIX + name);
- } else {
- index = text.indexOf('@');
- if (index != -1 && text.indexOf('/', index) != -1
- && !text.startsWith("@android:", index)) { //$NON-NLS-1$
- // Compute R-string, e.g. @string/foo => R.string.foo
- String token = text.substring(index + 1).trim().replace('/', '.');
- String r = R_PREFIX + token;
- mReferences.add(r);
- }
- }
- }
- }
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String value = attribute.getValue();
-
- if (value.startsWith("@+") && !value.startsWith("@+android")) { //$NON-NLS-1$ //$NON-NLS-2$
- String resource = R_PREFIX + value.substring(2).replace('/', '.');
- // We already have the declarations when we scan the R file, but we're tracking
- // these here to get attributes for position info
-
- if (context.getPhase() == 1) {
- mDeclarations.add(resource);
- } else if (mUnused.containsKey(resource)) {
- if (context.getDriver().isSuppressed(context, getIssue(resource), attribute)) {
- mUnused.remove(resource);
- return;
- }
- if (!context.getProject().getReportIssues()) {
- mUnused.remove(resource);
- return;
- }
- recordLocation(resource, context.getLocation(attribute));
- return;
- }
- } else if (mReferences != null) {
- if (value.startsWith("@") //$NON-NLS-1$
- && !value.startsWith("@android:")) { //$NON-NLS-1$
- // Compute R-string, e.g. @string/foo => R.string.foo
- String r = R_PREFIX + value.substring(1).replace('/', '.');
- mReferences.add(r);
- } else if (value.startsWith(ATTR_REF_PREFIX)) {
- mReferences.add(R_ATTR_PREFIX + value.substring(ATTR_REF_PREFIX.length()));
- }
- }
-
- if (attribute.getNamespaceURI() != null
- && !ANDROID_URI.equals(attribute.getNamespaceURI()) && mReferences != null) {
- mReferences.add(R_ATTR_PREFIX + attribute.getLocalName());
- }
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends lombok.ast.Node>>singletonList(ClassDeclaration.class);
- }
-
- @Override
- public boolean appliesToResourceRefs() {
- return true;
- }
-
- @Override
- public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull lombok.ast.Node node, @NonNull String type, @NonNull String name,
- boolean isFramework) {
- if (mReferences != null && !isFramework) {
- String reference = R_PREFIX + type + '.' + name;
- mReferences.add(reference);
- }
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- if (mReferences != null) {
- return new UnusedResourceVisitor();
- } else {
- // Second pass, computing resource declaration locations: No need to look at Java
- return null;
- }
- }
-
- // Look for references and declarations
- private class UnusedResourceVisitor extends ForwardingAstVisitor {
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- // Look for declarations of R class fields and store them in
- // mDeclarations
- String description = node.astName().astValue();
- if (description.equals(R_CLASS)) {
- // This is an R class. We can process this class very deliberately.
- // The R class has a very specific AST format:
- // ClassDeclaration ("R")
- // NormalTypeBody
- // ClassDeclaration (e.g. "drawable")
- // NormalTypeBody
- // VariableDeclaration
- // VariableDefinition (e.g. "ic_launcher")
- for (lombok.ast.Node body : node.getChildren()) {
- if (body instanceof NormalTypeBody) {
- for (lombok.ast.Node subclass : body.getChildren()) {
- if (subclass instanceof ClassDeclaration) {
- String className = ((ClassDeclaration) subclass).astName().astValue();
- for (lombok.ast.Node innerBody : subclass.getChildren()) {
- if (innerBody instanceof NormalTypeBody) {
- for (lombok.ast.Node field : innerBody.getChildren()) {
- if (field instanceof VariableDeclaration) {
- for (lombok.ast.Node child : field.getChildren()) {
- if (child instanceof VariableDefinition) {
- VariableDefinition def =
- (VariableDefinition) child;
- String name = def.astVariables().first()
- .astName().astValue();
- String resource = R_PREFIX + className
- + '.' + name;
- mDeclarations.add(resource);
- } // Else: It could be a comment node
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
deleted file mode 100644
index 77f5fe7..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.SdkInfo;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-import org.objectweb.asm.tree.analysis.BasicValue;
-import org.objectweb.asm.tree.analysis.Frame;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Checks for missing view tag detectors
- */
-public class ViewTagDetector extends Detector implements ClassScanner {
- /** Using setTag and leaking memory */
- public static final Issue ISSUE = Issue.create(
- "ViewTag", //$NON-NLS-1$
- "Tagged object leaks",
-
- "Prior to Android 4.0, the implementation of `View.setTag(int, Object)` would " +
- "store the objects in a static map, where the values were strongly referenced. " +
- "This means that if the object contains any references pointing back to the " +
- "context, the context (which points to pretty much everything else) will leak. " +
- "If you pass a view, the view provides a reference to the context " +
- "that created it. Similarly, view holders typically contain a view, and cursors " +
- "are sometimes also associated with views.",
-
- Category.PERFORMANCE,
- 6,
- Severity.WARNING,
- new Implementation(
- ViewTagDetector.class,
- Scope.CLASS_FILE_SCOPE));
-
- /** Constructs a new {@link ViewTagDetector} */
- public ViewTagDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Collections.singletonList("setTag"); //$NON-NLS-1$
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- // The leak behavior is fixed in ICS:
- // http://code.google.com/p/android/issues/detail?id=18273
- if (context.getMainProject().getMinSdk() >= 14) {
- return;
- }
-
- String owner = call.owner;
- String desc = call.desc;
- if (owner.equals("android/view/View") //$NON-NLS-1$
- && desc.equals("(ILjava/lang/Object;)V")) { //$NON-NLS-1$
- Analyzer analyzer = new Analyzer(new BasicInterpreter() {
- @Override
- public BasicValue newValue(Type type) {
- if (type == null) {
- return BasicValue.UNINITIALIZED_VALUE;
- } else if (type.getSort() == Type.VOID) {
- return null;
- } else {
- return new BasicValue(type);
- }
- }
- });
- try {
- Frame[] frames = analyzer.analyze(classNode.name, method);
- InsnList instructions = method.instructions;
- Frame frame = frames[instructions.indexOf(call)];
- if (frame.getStackSize() < 3) {
- return;
- }
- BasicValue stackValue = (BasicValue) frame.getStack(2);
- Type type = stackValue.getType();
- if (type == null) {
- return;
- }
-
- String internalName = type.getInternalName();
- String className = type.getClassName();
- LintDriver driver = context.getDriver();
-
- SdkInfo sdkInfo = context.getClient().getSdkInfo(context.getMainProject());
- String objectType = null;
- while (className != null) {
- if (className.equals("android.view.View")) { //$NON-NLS-1$
- objectType = "views";
- break;
- } else if (className.endsWith("ViewHolder")) { //$NON-NLS-1$
- objectType = "view holders";
- break;
- } else if (className.endsWith("Cursor") //$NON-NLS-1$
- && className.startsWith("android.")) { //$NON-NLS-1$
- objectType = "cursors";
- break;
- }
-
- // TBD: Bitmaps, drawables? That's tricky, because as explained in
- // http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
- // apparently these are used along with nulling out the callbacks,
- // and that's harder to detect here
-
- String parent = sdkInfo.getParentViewClass(className);
- if (parent == null) {
- if (internalName == null) {
- internalName = className.replace('.', '/');
- }
- assert internalName != null;
- parent = driver.getSuperClass(internalName);
- }
- className = parent;
- internalName = null;
- }
-
- if (objectType != null) {
- Location location = context.getLocation(call);
- String message = String.format("Avoid setting %1$s as values for `setTag`: " +
- "Can lead to memory leaks in versions older than Android 4.0",
- objectType);
- context.report(ISSUE, method, call, location, message);
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
deleted file mode 100644
index ec8edd3..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.SdkConstants.VIEW_TAG;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.ResourceFile;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Cast;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-
-/** Detector for finding inconsistent usage of views and casts
- * <p>
- * TODO: Check findFragmentById
- * <pre>
- * ((ItemListFragment) getSupportFragmentManager()
- * .findFragmentById(R.id.item_list))
- * .setActivateOnItemClick(true);
- * </pre>
- * Here we should check the {@code <fragment>} tag pointed to by the id, and
- * check its name or class attributes to make sure the cast is compatible with
- * the named fragment class!
- */
-public class ViewTypeDetector extends ResourceXmlDetector implements Detector.JavaScanner {
- /** Mismatched view types */
- @SuppressWarnings("unchecked")
- public static final Issue ISSUE = Issue.create(
- "WrongViewCast", //$NON-NLS-1$
- "Mismatched view type",
- "Keeps track of the view types associated with ids and if it finds a usage of " +
- "the id in the Java code it ensures that it is treated as the same type.",
- Category.CORRECTNESS,
- 9,
- Severity.FATAL,
- new Implementation(
- ViewTypeDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES),
- Scope.JAVA_FILE_SCOPE));
-
- /** Flag used to do no work if we're running in incremental mode in a .java file without
- * a client supporting project resources */
- private Boolean mIgnore = null;
-
- private final Map<String, Object> mIdToViewTag = new HashMap<String, Object>(50);
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_ID);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String view = attribute.getOwnerElement().getTagName();
- String value = attribute.getValue();
- String id = null;
- if (value.startsWith(ID_PREFIX)) {
- id = value.substring(ID_PREFIX.length());
- } else if (value.startsWith(NEW_ID_PREFIX)) {
- id = value.substring(NEW_ID_PREFIX.length());
- } // else: could be @android id
-
- if (id != null) {
- if (view.equals(VIEW_TAG)) {
- view = attribute.getOwnerElement().getAttribute(ATTR_CLASS);
- }
-
- Object existing = mIdToViewTag.get(id);
- if (existing == null) {
- mIdToViewTag.put(id, view);
- } else if (existing instanceof String) {
- String existingString = (String) existing;
- if (!existingString.equals(view)) {
- // Convert to list
- List<String> list = new ArrayList<String>(2);
- list.add((String) existing);
- list.add(view);
- mIdToViewTag.put(id, list);
- }
- } else if (existing instanceof List<?>) {
- @SuppressWarnings("unchecked")
- List<String> list = (List<String>) existing;
- if (!list.contains(view)) {
- list.add(view);
- }
- }
- }
- }
-
- // ---- Implements Detector.JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("findViewById"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- LintClient client = context.getClient();
- if (mIgnore == Boolean.TRUE) {
- return;
- } else if (mIgnore == null) {
- mIgnore = !context.getScope().contains(Scope.ALL_RESOURCE_FILES) &&
- !client.supportsProjectResources();
- if (mIgnore) {
- return;
- }
- }
- assert node.astName().astValue().equals("findViewById");
- if (node.getParent() instanceof Cast) {
- Cast cast = (Cast) node.getParent();
- String castType = cast.astTypeReference().getTypeName();
- StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
- if (args.size() == 1) {
- Expression first = args.first();
- // TODO: Do flow analysis as in the StringFormatDetector in order
- // to handle variable references too
- if (first instanceof Select) {
- String resource = first.toString();
- if (resource.startsWith("R.id.")) { //$NON-NLS-1$
- String id = ((Select) first).astIdentifier().astValue();
-
- if (client.supportsProjectResources()) {
- AbstractResourceRepository resources = client
- .getProjectResources(context.getMainProject(), true);
- if (resources == null) {
- return;
- }
-
- List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
- id);
- if (items != null && !items.isEmpty()) {
- Set<String> compatible = Sets.newHashSet();
- for (ResourceItem item : items) {
- Collection<String> tags = getViewTags(context, item);
- if (tags != null) {
- compatible.addAll(tags);
- }
- }
- if (!compatible.isEmpty()) {
- ArrayList<String> layoutTypes = Lists.newArrayList(compatible);
- checkCompatible(context, castType, null, layoutTypes, cast);
- }
- }
- } else {
- Object types = mIdToViewTag.get(id);
- if (types instanceof String) {
- String layoutType = (String) types;
- checkCompatible(context, castType, layoutType, null, cast);
- } else if (types instanceof List<?>) {
- @SuppressWarnings("unchecked")
- List<String> layoutTypes = (List<String>) types;
- checkCompatible(context, castType, null, layoutTypes, cast);
- }
- }
- }
- }
- }
- }
- }
-
- @Nullable
- protected Collection<String> getViewTags(
- @NonNull Context context,
- @NonNull ResourceItem item) {
- // Check view tag in this file. Can I do it cheaply? Try with
- // an XML pull parser. Or DOM if we have multiple resources looked
- // up?
- ResourceFile source = item.getSource();
- if (source != null) {
- File file = source.getFile();
- Multimap<String,String> map = getIdToTagsIn(context, file);
- if (map != null) {
- return map.get(item.getName());
- }
- }
-
- return null;
- }
-
-
- private Map<File, Multimap<String, String>> mFileIdMap;
-
- @Nullable
- private Multimap<String, String> getIdToTagsIn(@NonNull Context context, @NonNull File file) {
- if (!file.getPath().endsWith(DOT_XML)) {
- return null;
- }
- if (mFileIdMap == null) {
- mFileIdMap = Maps.newHashMap();
- }
- Multimap<String, String> map = mFileIdMap.get(file);
- if (map == null) {
- map = ArrayListMultimap.create();
- mFileIdMap.put(file, map);
-
- String xml = context.getClient().readFile(file);
- // TODO: Use pull parser instead for better performance!
- Document document = XmlUtils.parseDocumentSilently(xml, true);
- if (document != null && document.getDocumentElement() != null) {
- addViewTags(map, document.getDocumentElement());
- }
- }
- return map;
- }
-
- private static void addViewTags(Multimap<String, String> map, Element element) {
- String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
- if (id != null && !id.isEmpty()) {
- id = LintUtils.stripIdPrefix(id);
- if (!map.containsEntry(id, element.getTagName())) {
- map.put(id, element.getTagName());
- }
- }
-
- NodeList children = element.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- addViewTags(map, (Element) child);
- }
- }
- }
-
- /** Check if the view and cast type are compatible */
- private static void checkCompatible(JavaContext context, String castType, String layoutType,
- List<String> layoutTypes, Cast node) {
- assert layoutType == null || layoutTypes == null; // Should only specify one or the other
- boolean compatible = true;
- if (layoutType != null) {
- if (!layoutType.equals(castType)
- && !context.getSdkInfo().isSubViewOf(castType, layoutType)) {
- compatible = false;
- }
- } else {
- compatible = false;
- assert layoutTypes != null;
- for (String type : layoutTypes) {
- if (type.equals(castType)
- || context.getSdkInfo().isSubViewOf(castType, type)) {
- compatible = true;
- break;
- }
- }
- }
-
- if (!compatible) {
- if (layoutType == null) {
- layoutType = Joiner.on("|").join(layoutTypes);
- }
- String message = String.format(
- "Unexpected cast to `%1$s`: layout tag was `%2$s`",
- castType, layoutType);
- context.report(ISSUE, node, context.getLocation(node), message);
- }
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
deleted file mode 100644
index 7ec48e1..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.checks.ControlFlowGraph.Node;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.JumpInsnNode;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Checks for problems with wakelocks (such as failing to release them)
- * which can lead to unnecessary battery usage.
- */
-public class WakelockDetector extends Detector implements ClassScanner {
-
- /** Problems using wakelocks */
- public static final Issue ISSUE = Issue.create(
- "Wakelock", //$NON-NLS-1$
- "Incorrect `WakeLock` usage",
-
- "Failing to release a wakelock properly can keep the Android device in " +
- "a high power mode, which reduces battery life. There are several causes " +
- "of this, such as releasing the wake lock in `onDestroy()` instead of in " +
- "`onPause()`, failing to call `release()` in all possible code paths after " +
- "an `acquire()`, and so on.\n" +
- "\n" +
- "NOTE: If you are using the lock just to keep the screen on, you should " +
- "strongly consider using `FLAG_KEEP_SCREEN_ON` instead. This window flag " +
- "will be correctly managed by the platform as the user moves between " +
- "applications and doesn't require a special permission. See " +
- "http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON.",
-
- Category.PERFORMANCE,
- 9,
- Severity.WARNING,
- new Implementation(
- WakelockDetector.class,
- Scope.CLASS_FILE_SCOPE));
-
- private static final String WAKELOCK_OWNER = "android/os/PowerManager$WakeLock"; //$NON-NLS-1$
- private static final String RELEASE_METHOD = "release"; //$NON-NLS-1$
- private static final String ACQUIRE_METHOD = "acquire"; //$NON-NLS-1$
- private static final String IS_HELD_METHOD = "isHeld"; //$NON-NLS-1$
- private static final String POWER_MANAGER = "android/os/PowerManager"; //$NON-NLS-1$
- private static final String NEW_WAKE_LOCK_METHOD = "newWakeLock"; //$NON-NLS-1$
-
- /** Print diagnostics during analysis (display flow control graph etc).
- * Make sure you add the asm-debug or asm-util jars to the runtime classpath
- * as well since the opcode integer to string mapping display routine looks for
- * it via reflection. */
- private static final boolean DEBUG = false;
-
- /** Constructs a new {@link WakelockDetector} */
- public WakelockDetector() {
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mHasAcquire && !mHasRelease && context.getDriver().getPhase() == 1) {
- // Gather positions of the acquire calls
- context.getDriver().requestRepeat(this, Scope.CLASS_FILE_SCOPE);
- }
- }
-
- // ---- Implements ClassScanner ----
-
- /** Whether any {@code acquire()} calls have been encountered */
- private boolean mHasAcquire;
-
- /** Whether any {@code release()} calls have been encountered */
- private boolean mHasRelease;
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(ACQUIRE_METHOD, RELEASE_METHOD, NEW_WAKE_LOCK_METHOD);
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- if (call.owner.equals(WAKELOCK_OWNER)) {
- String name = call.name;
- if (name.equals(ACQUIRE_METHOD)) {
- if (call.desc.equals("(J)V")) { // acquire(long timeout) does not require a corresponding release
- return;
- }
- mHasAcquire = true;
-
- if (context.getDriver().getPhase() == 2) {
- assert !mHasRelease;
- context.report(ISSUE, method, call, context.getLocation(call),
- "Found a wakelock `acquire()` but no `release()` calls anywhere");
- } else {
- assert context.getDriver().getPhase() == 1;
- // Perform flow analysis in this method to see if we're
- // performing an acquire/release block, where there are code paths
- // between the acquire and release which can result in the
- // release call not getting reached.
- checkFlow(context, classNode, method, call);
- }
- } else if (name.equals(RELEASE_METHOD)) {
- mHasRelease = true;
-
- // See if the release is happening in an onDestroy method, in an
- // activity.
- if ("onDestroy".equals(method.name) //$NON-NLS-1$
- && context.getDriver().isSubclassOf(
- classNode, ANDROID_APP_ACTIVITY)) {
- context.report(ISSUE, method, call, context.getLocation(call),
- "Wakelocks should be released in `onPause`, not `onDestroy`");
- }
- }
- } else if (call.owner.equals(POWER_MANAGER)) {
- if (call.name.equals(NEW_WAKE_LOCK_METHOD)) {
- AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
- if (prev == null) {
- return;
- }
- prev = LintUtils.getPrevInstruction(prev);
- if (prev == null || prev.getOpcode() != Opcodes.LDC) {
- return;
- }
- LdcInsnNode ldc = (LdcInsnNode) prev;
- Object constant = ldc.cst;
- if (constant instanceof Integer) {
- int flag = ((Integer) constant).intValue();
- // Constant values are copied into the bytecode so we have to compare
- // values; however, that means the values are part of the API
- final int PARTIAL_WAKE_LOCK = 0x00000001;
- final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
- final int both = PARTIAL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP;
- if ((flag & both) == both) {
- context.report(ISSUE, method, call, context.getLocation(call),
- "Should not set both `PARTIAL_WAKE_LOCK` and `ACQUIRE_CAUSES_WAKEUP`. "
- + "If you do not want the screen to turn on, get rid of "
- + "`ACQUIRE_CAUSES_WAKEUP`");
- }
- }
-
- }
- }
- }
-
- private static void checkFlow(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode acquire) {
- // Track allocations such that we know whether the type of the call
- // is on a SecureRandom rather than a Random
- final InsnList instructions = method.instructions;
- MethodInsnNode release = null;
-
- // Find release call
- for (int i = 0, n = instructions.size(); i < n; i++) {
- AbstractInsnNode instruction = instructions.get(i);
- int type = instruction.getType();
- if (type == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode call = (MethodInsnNode) instruction;
- if (call.name.equals(RELEASE_METHOD) &&
- call.owner.equals(WAKELOCK_OWNER)) {
- release = call;
- break;
- }
- }
- }
-
- if (release == null) {
- // Didn't find both acquire and release in this method; no point in doing
- // local flow analysis
- return;
- }
-
- try {
- MyGraph graph = new MyGraph();
- ControlFlowGraph.create(graph, classNode, method);
-
- if (DEBUG) {
- // Requires util package
- //ClassNode clazz = classNode;
- //clazz.accept(new TraceClassVisitor(new PrintWriter(System.out)));
- System.out.println(graph.toString(graph.getNode(acquire)));
- }
-
- int status = dfs(graph.getNode(acquire));
- if ((status & SEEN_RETURN) != 0) {
- String message;
- if ((status & SEEN_EXCEPTION) != 0) {
- message = "The `release()` call is not always reached (via exceptional flow)";
- } else {
- message = "The `release()` call is not always reached";
- }
-
- context.report(ISSUE, method, acquire,
- context.getLocation(release), message);
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- }
-
- private static final int SEEN_TARGET = 1;
- private static final int SEEN_BRANCH = 2;
- private static final int SEEN_EXCEPTION = 4;
- private static final int SEEN_RETURN = 8;
-
- /** TODO RENAME */
- private static class MyGraph extends ControlFlowGraph {
- @Override
- protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- if (from.getOpcode() == Opcodes.IFNULL) {
- JumpInsnNode jump = (JumpInsnNode) from;
- if (jump.label == to) {
- // Skip jump targets on null if it's surrounding the release call
- //
- // if (lock != null) {
- // lock.release();
- // }
- //
- // The above shouldn't be considered a scenario where release() may not
- // be called.
- AbstractInsnNode next = LintUtils.getNextInstruction(from);
- if (next != null && next.getType() == AbstractInsnNode.VAR_INSN) {
- next = LintUtils.getNextInstruction(next);
- if (next != null && next.getType() == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode method = (MethodInsnNode) next;
- if (method.name.equals(RELEASE_METHOD) &&
- method.owner.equals(WAKELOCK_OWNER)) {
- // This isn't entirely correct; this will also trigger
- // for "if (lock == null) { lock.release(); }" but that's
- // not likely (and caught by other null checking in tools)
- return;
- }
- }
- }
- }
- } else if (from.getOpcode() == Opcodes.IFEQ) {
- JumpInsnNode jump = (JumpInsnNode) from;
- if (jump.label == to) {
- AbstractInsnNode prev = LintUtils.getPrevInstruction(from);
- if (prev != null && prev.getType() == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode method = (MethodInsnNode) prev;
- if (method.name.equals(IS_HELD_METHOD) &&
- method.owner.equals(WAKELOCK_OWNER)) {
- AbstractInsnNode next = LintUtils.getNextInstruction(from);
- if (next != null) {
- super.add(from, next);
- return;
- }
- }
- }
- }
- }
-
- super.add(from, to);
- }
- }
-
- /** Search from the given node towards the target; return false if we reach
- * an exit point such as a return or a call on the way there that is not within
- * a try/catch clause.
- *
- * @param node the current node
- * @return true if the target was reached
- * XXX RETURN VALUES ARE WRONG AS OF RIGHT NOW
- */
- protected static int dfs(ControlFlowGraph.Node node) {
- AbstractInsnNode instruction = node.instruction;
- if (instruction.getType() == AbstractInsnNode.JUMP_INSN) {
- int opcode = instruction.getOpcode();
- if (opcode == Opcodes.RETURN || opcode == Opcodes.ARETURN
- || opcode == Opcodes.LRETURN || opcode == Opcodes.IRETURN
- || opcode == Opcodes.DRETURN || opcode == Opcodes.FRETURN
- || opcode == Opcodes.ATHROW) {
- if (DEBUG) {
- System.out.println("Found exit via explicit return: " //$NON-NLS-1$
- + node.toString(false));
- }
- return SEEN_RETURN;
- }
- }
-
- if (!DEBUG) {
- // There are no cycles, so no *NEED* for this, though it does avoid
- // researching shared labels. However, it makes debugging harder (no re-entry)
- // so this is only done when debugging is off
- if (node.visit != 0) {
- return 0;
- }
- node.visit = 1;
- }
-
- // Look for the target. This is any method call node which is a release on the
- // lock (later also check it's the same instance, though that's harder).
- // This is because finally blocks tend to be inlined so from a single try/catch/finally
- // with a release() in the finally, the bytecode can contain multiple repeated
- // (inlined) release() calls.
- if (instruction.getType() == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode method = (MethodInsnNode) instruction;
- if (method.name.equals(RELEASE_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
- return SEEN_TARGET;
- } else if (method.name.equals(ACQUIRE_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
- // OK
- } else if (method.name.equals(IS_HELD_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
- // OK
- } else {
- // Some non acquire/release method call: if this is not associated with a
- // try-catch block, it would mean the exception would exit the method,
- // which would be an error
- if (node.exceptions == null || node.exceptions.isEmpty()) {
- // Look up the corresponding frame, if any
- AbstractInsnNode curr = method.getPrevious();
- boolean foundFrame = false;
- while (curr != null) {
- if (curr.getType() == AbstractInsnNode.FRAME) {
- foundFrame = true;
- break;
- }
- curr = curr.getPrevious();
- }
-
- if (!foundFrame) {
- if (DEBUG) {
- System.out.println("Found exit via unguarded method call: " //$NON-NLS-1$
- + node.toString(false));
- }
- return SEEN_RETURN;
- }
- }
- }
- }
-
- // if (node.instruction is a call, and the call is not caught by
- // a try/catch block (provided the release is not inside the try/catch block)
- // then return false
- int status = 0;
-
- boolean implicitReturn = true;
- List<Node> successors = node.successors;
- List<Node> exceptions = node.exceptions;
- if (exceptions != null) {
- if (!exceptions.isEmpty()) {
- implicitReturn = false;
- }
- for (Node successor : exceptions) {
- status = dfs(successor) | status;
- if ((status & SEEN_RETURN) != 0) {
- if (DEBUG) {
- System.out.println("Found exit via exception: " //$NON-NLS-1$
- + node.toString(false));
- }
- return status;
- }
- }
-
- if (status != 0) {
- status |= SEEN_EXCEPTION;
- }
- }
-
- if (successors != null) {
- if (!successors.isEmpty()) {
- implicitReturn = false;
- if (successors.size() > 1) {
- status |= SEEN_BRANCH;
- }
- }
- for (Node successor : successors) {
- status = dfs(successor) | status;
- if ((status & SEEN_RETURN) != 0) {
- if (DEBUG) {
- System.out.println("Found exit via branches: " //$NON-NLS-1$
- + node.toString(false));
- }
- return status;
- }
- }
- }
-
- if (implicitReturn) {
- status |= SEEN_RETURN;
- if (DEBUG) {
- System.out.println("Found exit: via implicit return: " //$NON-NLS-1$
- + node.toString(false));
- }
- }
-
- return status;
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
deleted file mode 100644
index 5a7c1d7..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CLASS_VIEW;
-import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_DRAW;
-import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_LAYOUT;
-import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_MEASURE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.TextFormat;
-
-import java.util.Arrays;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Super;
-
-/**
- * Checks for cases where the wrong call is being made
- */
-public class WrongCallDetector extends Detector implements Detector.JavaScanner {
- /** Calling the wrong method */
- public static final Issue ISSUE = Issue.create(
- "WrongCall", //$NON-NLS-1$
- "Using wrong draw/layout method",
-
- "Custom views typically need to call `measure()` on their children, not `onMeasure`. " +
- "Ditto for onDraw, onLayout, etc.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- new Implementation(
- WrongCallDetector.class,
- Scope.JAVA_FILE_SCOPE));
-
- /** Constructs a new {@link WrongCallDetector} */
- public WrongCallDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableMethodNames() {
- return Arrays.asList(
- ON_DRAW,
- ON_MEASURE,
- ON_LAYOUT
- );
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
-
- // Call is only allowed if it is both only called on the super class (invoke special)
- // as well as within the same overriding method (e.g. you can't call super.onLayout
- // from the onMeasure method)
- Expression operand = node.astOperand();
- if (!(operand instanceof Super)) {
- report(context, node);
- return;
- }
-
- Node method = StringFormatDetector.getParentMethod(node);
- if (!(method instanceof MethodDeclaration) ||
- !((MethodDeclaration)method).astMethodName().astValue().equals(
- node.astName().astValue())) {
- report(context, node);
- }
- }
-
- private static void report(JavaContext context, MethodInvocation node) {
- // Make sure the call is on a view
- JavaParser.ResolvedNode resolved = context.resolve(node);
- if (resolved instanceof JavaParser.ResolvedMethod) {
- JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
- JavaParser.ResolvedClass containingClass = method.getContainingClass();
- if (!containingClass.isSubclassOf(CLASS_VIEW, false)) {
- return;
- }
- }
-
- String name = node.astName().astValue();
- String suggestion = Character.toLowerCase(name.charAt(2)) + name.substring(3);
- String message = String.format(
- // Keep in sync with {@link #getOldValue} and {@link #getNewValue} below!
- "Suspicious method call; should probably call \"`%1$s`\" rather than \"`%2$s`\"",
- suggestion, name);
- context.report(ISSUE, node, context.getLocation(node.astName()), message);
- }
-
- /**
- * Given an error message produced by this lint detector for the given issue type,
- * returns the old value to be replaced in the source code.
- * <p>
- * Intended for IDE quickfix implementations.
- *
- * @param errorMessage the error message associated with the error
- * @param format the format of the error message
- * @return the corresponding old value, or null if not recognized
- */
- @Nullable
- public static String getOldValue(@NonNull String errorMessage, @NonNull TextFormat format) {
- errorMessage = format.toText(errorMessage);
- return LintUtils.findSubstring(errorMessage, "than \"", "\"");
- }
-
- /**
- * Given an error message produced by this lint detector for the given issue type,
- * returns the new value to be put into the source code.
- * <p>
- * Intended for IDE quickfix implementations.
- *
- * @param errorMessage the error message associated with the error
- * @param format the format of the error message
- * @return the corresponding new value, or null if not recognized
- */
- @Nullable
- public static String getNewValue(@NonNull String errorMessage, @NonNull TextFormat format) {
- errorMessage = format.toText(errorMessage);
- return LintUtils.findSubstring(errorMessage, "call \"", "\"");
- }
-}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java b/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
deleted file mode 100644
index 6041f63..0000000
--- a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
+++ /dev/null
@@ -1,507 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.FD_RES_VALUES;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.VALUE_ID;
-import static com.android.tools.lint.detector.api.LintUtils.editDistance;
-import static com.android.tools.lint.detector.api.LintUtils.getChildren;
-import static com.android.tools.lint.detector.api.LintUtils.isSameResourceFile;
-import static com.android.tools.lint.detector.api.LintUtils.stripIdPrefix;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.ResourceFile;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.Speed;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Checks for duplicate ids within a layout and within an included layout
- */
-public class WrongIdDetector extends LayoutDetector {
- private static final Implementation IMPLEMENTATION = new Implementation(
- WrongIdDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Ids bound to widgets in any of the layout files */
- private final Set<String> mGlobalIds = new HashSet<String>(100);
-
- /** Ids bound to widgets in the current layout file */
- private Set<String> mFileIds;
-
- /** Ids declared in a value's file, e.g. {@code <item type="id" name="foo"/>} */
- private Set<String> mDeclaredIds;
-
- /**
- * Location handles for the various id references that were not found as
- * defined in the same layout, to be checked after the whole project has
- * been scanned
- */
- private List<Pair<String, Location.Handle>> mHandles;
-
- /** List of RelativeLayout elements in the current layout */
- private List<Element> mRelativeLayouts;
-
- /** Reference to an unknown id */
- @SuppressWarnings("unchecked")
- public static final Issue UNKNOWN_ID = Issue.create(
- "UnknownId", //$NON-NLS-1$
- "Reference to an unknown id",
- "The `@+id/` syntax refers to an existing id, or creates a new one if it has " +
- "not already been defined elsewhere. However, this means that if you have a " +
- "typo in your reference, or if the referred view no longer exists, you do not " +
- "get a warning since the id will be created on demand. This check catches " +
- "errors where you have renamed an id without updating all of the references to " +
- "it.",
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- new Implementation(
- WrongIdDetector.class,
- Scope.ALL_RESOURCES_SCOPE,
- Scope.RESOURCE_FILE_SCOPE));
-
- /** Reference to an id that is not a sibling */
- public static final Issue NOT_SIBLING = Issue.create(
- "NotSibling", //$NON-NLS-1$
- "RelativeLayout Invalid Constraints",
- "Layout constraints in a given `RelativeLayout` should reference other views " +
- "within the same relative layout (but not itself!)",
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- IMPLEMENTATION);
-
- /** An ID declaration which is not valid */
- public static final Issue INVALID = Issue.create(
- "InvalidId", //$NON-NLS-1$
- "Invalid ID declaration",
- "An id definition *must* be of the form `@+id/yourname`. The tools have not " +
- "rejected strings of the form `@+foo/bar` in the past, but that was an error, " +
- "and could lead to tricky errors because of the way the id integers are assigned.\n" +
- "\n" +
- "If you really want to have different \"scopes\" for your id's, use prefixes " +
- "instead, such as `login_button1` and `login_button2`.",
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- IMPLEMENTATION);
-
- /** Reference to an id that is not in the current layout */
- public static final Issue UNKNOWN_ID_LAYOUT = Issue.create(
- "UnknownIdInLayout", //$NON-NLS-1$
- "Reference to an id that is not in the current layout",
-
- "The `@+id/` syntax refers to an existing id, or creates a new one if it has " +
- "not already been defined elsewhere. However, this means that if you have a " +
- "typo in your reference, or if the referred view no longer exists, you do not " +
- "get a warning since the id will be created on demand.\n" +
- "\n" +
- "This is sometimes intentional, for example where you are referring to a view " +
- "which is provided in a different layout via an include. However, it is usually " +
- "an accident where you have a typo or you have renamed a view without updating " +
- "all the references to it.",
-
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- new Implementation(
- WrongIdDetector.class,
- Scope.RESOURCE_FILE_SCOPE));
-
- /** Constructs a duplicate id check */
- public WrongIdDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_ID);
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(RELATIVE_LAYOUT, TAG_ITEM);
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- mFileIds = new HashSet<String>();
- mRelativeLayouts = null;
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- if (mRelativeLayouts != null) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- for (Element layout : mRelativeLayouts) {
- List<Element> children = getChildren(layout);
- Set<String> ids = Sets.newHashSetWithExpectedSize(children.size());
- for (Element child : children) {
- String id = child.getAttributeNS(ANDROID_URI, ATTR_ID);
- if (id != null && !id.isEmpty()) {
- ids.add(id);
- }
- }
-
- for (Element element : children) {
- String selfId = stripIdPrefix(element.getAttributeNS(ANDROID_URI, ATTR_ID));
-
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attr = (Attr) attributes.item(i);
- String value = attr.getValue();
- if ((value.startsWith(NEW_ID_PREFIX) ||
- value.startsWith(ID_PREFIX))
- && ANDROID_URI.equals(attr.getNamespaceURI())
- && attr.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
- if (!idDefined(mFileIds, value)) {
- // Stash a reference to this id and location such that
- // we can check after the *whole* layout has been processed,
- // since it's too early to conclude here that the id does
- // not exist (you are allowed to have forward references)
- XmlContext xmlContext = (XmlContext) context;
- Handle handle = xmlContext.createLocationHandle(attr);
- handle.setClientData(attr);
-
- if (mHandles == null) {
- mHandles = new ArrayList<Pair<String,Handle>>();
- }
- mHandles.add(Pair.of(value, handle));
- } else {
- // Check siblings. TODO: Look for cycles!
- if (ids.contains(value)) {
- // Make sure it's not pointing to self
- if (!ATTR_ID.equals(attr.getLocalName())
- && !selfId.isEmpty()
- && value.endsWith(selfId)
- && stripIdPrefix(value).equals(selfId)) {
- XmlContext xmlContext = (XmlContext) context;
- String message = String.format(
- "Cannot be relative to self: id=%1$s, %2$s=%3$s",
- selfId, attr.getLocalName(), selfId);
- Location location = xmlContext.getLocation(attr);
- xmlContext.report(NOT_SIBLING, attr, location, message);
- }
-
- continue;
- }
- if (value.startsWith(NEW_ID_PREFIX)) {
- if (ids.contains(ID_PREFIX + stripIdPrefix(value))) {
- continue;
- }
- } else {
- assert value.startsWith(ID_PREFIX) : value;
- if (ids.contains(NEW_ID_PREFIX + stripIdPrefix(value))) {
- continue;
- }
- }
- if (context.isEnabled(NOT_SIBLING)) {
- XmlContext xmlContext = (XmlContext) context;
- String message = String.format(
- "`%1$s` is not a sibling in the same `RelativeLayout`",
- value);
- Location location = xmlContext.getLocation(attr);
- xmlContext.report(NOT_SIBLING, attr, location, message);
- }
- }
- }
- }
- }
- }
- }
-
- mFileIds = null;
-
- if (!context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
- checkHandles(context);
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
- checkHandles(context);
- }
- }
-
- private void checkHandles(@NonNull Context context) {
- if (mHandles != null) {
- boolean checkSameLayout = context.isEnabled(UNKNOWN_ID_LAYOUT);
- boolean checkExists = context.isEnabled(UNKNOWN_ID);
- boolean projectScope = context.getScope().contains(Scope.ALL_RESOURCE_FILES);
- for (Pair<String, Handle> pair : mHandles) {
- String id = pair.getFirst();
- boolean isBound = projectScope ? idDefined(mGlobalIds, id) :
- idDefined(context, id, context.file);
- LintClient client = context.getClient();
- if (!isBound && checkExists
- && (projectScope || client.supportsProjectResources())) {
- Handle handle = pair.getSecond();
- boolean isDeclared = idDefined(mDeclaredIds, id);
- id = stripIdPrefix(id);
- String suggestionMessage;
- Set<String> spellingDictionary = mGlobalIds;
- if (!projectScope && client.supportsProjectResources()) {
- AbstractResourceRepository resources =
- client.getProjectResources(context.getProject(), true);
- if (resources != null) {
- spellingDictionary = Sets.newHashSet(
- resources.getItemsOfType(ResourceType.ID));
- spellingDictionary.remove(id);
- }
- }
- List<String> suggestions = getSpellingSuggestions(id, spellingDictionary);
- if (suggestions.size() > 1) {
- suggestionMessage = String.format(" Did you mean one of {%2$s} ?",
- id, Joiner.on(", ").join(suggestions));
- } else if (!suggestions.isEmpty()) {
- suggestionMessage = String.format(" Did you mean %2$s ?",
- id, suggestions.get(0));
- } else {
- suggestionMessage = "";
- }
- String message;
- if (isDeclared) {
- message = String.format(
- "The id \"`%1$s`\" is defined but not assigned to any views.%2$s",
- id, suggestionMessage);
- } else {
- message = String.format(
- "The id \"`%1$s`\" is not defined anywhere.%2$s",
- id, suggestionMessage);
- }
- report(context, UNKNOWN_ID, handle, message);
- } else if (checkSameLayout && (!projectScope || isBound)
- && id.startsWith(NEW_ID_PREFIX)) {
- // The id was defined, but in a different layout. Usually not intentional
- // (might be referring to a random other view that happens to have the same
- // name.)
- Handle handle = pair.getSecond();
- report(context, UNKNOWN_ID_LAYOUT, handle,
- String.format(
- "The id \"`%1$s`\" is not referring to any views in this layout",
- stripIdPrefix(id)));
- }
- }
- }
- }
-
- private static void report(Context context, Issue issue, Handle handle, String message) {
- Location location = handle.resolve();
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(null, issue, (Node) clientData)) {
- return;
- }
- }
-
- context.report(issue, location, message);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (element.getTagName().equals(RELATIVE_LAYOUT)) {
- if (mRelativeLayouts == null) {
- mRelativeLayouts = new ArrayList<Element>();
- }
- mRelativeLayouts.add(element);
- } else {
- assert element.getTagName().equals(TAG_ITEM);
- String type = element.getAttribute(ATTR_TYPE);
- if (VALUE_ID.equals(type)) {
- String name = element.getAttribute(ATTR_NAME);
- if (!name.isEmpty()) {
- if (mDeclaredIds == null) {
- mDeclaredIds = Sets.newHashSet();
- }
- mDeclaredIds.add(ID_PREFIX + name);
- }
- }
- }
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- assert attribute.getName().equals(ATTR_ID) || attribute.getLocalName().equals(ATTR_ID);
- String id = attribute.getValue();
- mFileIds.add(id);
- mGlobalIds.add(id);
-
- if (id.equals(NEW_ID_PREFIX) || id.equals(ID_PREFIX) || "@+id".equals(ID_PREFIX)) {
- String message = "Invalid id: missing value";
- context.report(INVALID, attribute, context.getLocation(attribute), message);
- } else if (id.startsWith("@+") && !id.startsWith(NEW_ID_PREFIX) //$NON-NLS-1$
- && !id.startsWith("@+android:id/") //$NON-NLS-1$
- || id.startsWith(NEW_ID_PREFIX)
- && id.indexOf('/', NEW_ID_PREFIX.length()) != -1) {
- int nameStart = id.startsWith(NEW_ID_PREFIX) ? NEW_ID_PREFIX.length() : 2;
- String suggested = NEW_ID_PREFIX + id.substring(nameStart).replace('/', '_');
- String message = String.format(
- "ID definitions *must* be of the form `@+id/name`; try using `%1$s`", suggested);
- context.report(INVALID, attribute, context.getLocation(attribute), message);
- }
- }
-
- private static boolean idDefined(Set<String> ids, String id) {
- if (ids == null) {
- return false;
- }
- boolean definedLocally = ids.contains(id);
- if (!definedLocally) {
- if (id.startsWith(NEW_ID_PREFIX)) {
- definedLocally = ids.contains(ID_PREFIX +
- id.substring(NEW_ID_PREFIX.length()));
- } else if (id.startsWith(ID_PREFIX)) {
- definedLocally = ids.contains(NEW_ID_PREFIX +
- id.substring(ID_PREFIX.length()));
- }
- }
-
- return definedLocally;
- }
-
- private boolean idDefined(@NonNull Context context, @NonNull String id,
- @Nullable File notIn) {
- AbstractResourceRepository resources =
- context.getClient().getProjectResources(context.getProject(), true);
- if (resources != null) {
- List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
- stripIdPrefix(id));
- if (items == null || items.isEmpty()) {
- return false;
- }
- for (ResourceItem item : items) {
- ResourceFile source = item.getSource();
- if (source != null) {
- File file = source.getFile();
- if (file.getParentFile().getName().startsWith(FD_RES_VALUES)) {
- if (mDeclaredIds == null) {
- mDeclaredIds = Sets.newHashSet();
- }
- mDeclaredIds.add(id);
- continue;
- }
-
- // Ignore definitions in the given file. This is used to ignore
- // matches in the same file as the reference, since the reference
- // is often expressed as a definition
- if (!isSameResourceFile(file, notIn)) {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- private static List<String> getSpellingSuggestions(String id, Collection<String> ids) {
- int maxDistance = id.length() >= 4 ? 2 : 1;
-
- // Look for typos and try to match with custom views and android views
- Multimap<Integer, String> matches = ArrayListMultimap.create(2, 10);
- int count = 0;
- if (!ids.isEmpty()) {
- for (String matchWith : ids) {
- matchWith = stripIdPrefix(matchWith);
- if (Math.abs(id.length() - matchWith.length()) > maxDistance) {
- // The string lengths differ more than the allowed edit distance;
- // no point in even attempting to compute the edit distance (requires
- // O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
- continue;
- }
- int distance = editDistance(id, matchWith);
- if (distance <= maxDistance) {
- matches.put(distance, matchWith);
- }
-
- if (count++ > 100) {
- // Make sure that for huge projects we don't completely grind to a halt
- break;
- }
- }
- }
-
- for (int i = 0; i < maxDistance; i++) {
- Collection<String> strings = matches.get(i);
- if (strings != null && !strings.isEmpty()) {
- List<String> suggestions = new ArrayList<String>(strings);
- Collections.sort(suggestions);
- return suggestions;
- }
- }
-
- return Collections.emptyList();
- }
-}
diff --git a/base/lint/libs/lint-tests/lint-tests.iml b/base/lint/libs/lint-tests/lint-tests.iml
deleted file mode 100644
index 66d4ddf..0000000
--- a/base/lint/libs/lint-tests/lint-tests.iml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- <excludeFolder url="file://$MODULE_DIR$/src/test/.settings" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="module" module-name="common" exported="" />
- <orderEntry type="module" module-name="integration-test" />
- <orderEntry type="module" module-name="lint-checks-base" exported="" />
- <orderEntry type="module" module-name="lint-cli" exported="" />
- <orderEntry type="module" module-name="testutils" exported="" />
- <orderEntry type="module" module-name="lint-api-base" exported="" />
- <orderEntry type="module" module-name="sdk-common-base" exported="" />
- <orderEntry type="library" exported="" name="JUnit4" level="project" />
- <orderEntry type="module" module-name="layoutlib-api-base" exported="" />
- <orderEntry type="module" module-name="sdklib-base" exported="" />
- <orderEntry type="library" exported="" name="intellij-annotations" level="project" />
- <orderEntry type="library" scope="TEST" name="groovy" level="project" />
- <orderEntry type="library" scope="TEST" name="mockito" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java b/base/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java
deleted file mode 100644
index e103a08..0000000
--- a/base/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java
+++ /dev/null
@@ -1,886 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks.infrastructure;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.res2.DuplicateDataException;
-import com.android.ide.common.res2.MergingException;
-import com.android.ide.common.res2.ResourceFile;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.ide.common.res2.ResourceMerger;
-import com.android.ide.common.res2.ResourceRepository;
-import com.android.ide.common.res2.ResourceSet;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.sdklib.IAndroidTarget;
-import com.android.testutils.SdkTestCase;
-import com.android.tools.lint.ExternalAnnotationRepository;
-import com.android.tools.lint.LintCliClient;
-import com.android.tools.lint.LintCliFlags;
-import com.android.tools.lint.Reporter;
-import com.android.tools.lint.TextReporter;
-import com.android.tools.lint.Warning;
-import com.android.tools.lint.checks.BuiltinIssueRegistry;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.client.api.DefaultConfiguration;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.LintRequest;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.TextFormat;
-import com.android.utils.ILogger;
-import com.android.utils.SdkUtils;
-import com.android.utils.StdLogger;
-import com.android.utils.XmlUtils;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.intellij.lang.annotations.Language;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringWriter;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.CodeSource;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test case for lint detectors.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
- at Beta
- at SuppressWarnings("javadoc")
-public abstract class LintDetectorTest extends SdkTestCase {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- BuiltinIssueRegistry.reset();
- }
-
- protected abstract Detector getDetector();
-
- private Detector mDetector;
-
- protected final Detector getDetectorInstance() {
- if (mDetector == null) {
- mDetector = getDetector();
- }
-
- return mDetector;
- }
-
- protected abstract List<Issue> getIssues();
-
- public class CustomIssueRegistry extends IssueRegistry {
- @NonNull
- @Override
- public List<Issue> getIssues() {
- return LintDetectorTest.this.getIssues();
- }
- }
-
- protected String lintFiles(String... relativePaths) throws Exception {
- List<File> files = new ArrayList<File>();
- File targetDir = getTargetDir();
- for (String relativePath : relativePaths) {
- File file = getTestfile(targetDir, relativePath);
- assertNotNull(file);
- files.add(file);
- }
-
- Collections.sort(files, new Comparator<File>() {
- @Override
- public int compare(File file1, File file2) {
- ResourceFolderType folder1 = ResourceFolderType.getFolderType(
- file1.getParentFile().getName());
- ResourceFolderType folder2 = ResourceFolderType.getFolderType(
- file2.getParentFile().getName());
- if (folder1 != null && folder2 != null && folder1 != folder2) {
- return folder1.compareTo(folder2);
- }
- return file1.compareTo(file2);
- }
- });
-
- addManifestFile(targetDir);
-
- return checkLint(files);
- }
-
- protected String checkLint(List<File> files) throws Exception {
- TestLintClient lintClient = createClient();
- return checkLint(lintClient, files);
- }
-
- protected String checkLint(TestLintClient lintClient, List<File> files) throws Exception {
- if (System.getenv("ANDROID_BUILD_TOP") != null) {
- fail("Don't run the lint tests with $ANDROID_BUILD_TOP set; that enables lint's "
- + "special support for detecting AOSP projects (looking for .class "
- + "files in $ANDROID_HOST_OUT etc), and this confuses lint.");
- }
-
- mOutput = new StringBuilder();
- String result = lintClient.analyze(files);
-
- // The output typically contains a few directory/filenames.
- // On Windows we need to change the separators to the unix-style
- // forward slash to make the test as OS-agnostic as possible.
- if (File.separatorChar != '/') {
- result = result.replace(File.separatorChar, '/');
- }
-
- for (File f : files) {
- deleteFile(f);
- }
-
- return result;
- }
-
- protected void checkReportedError(
- @NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message) {
- }
-
- protected TestLintClient createClient() {
- return new TestLintClient();
- }
-
- protected TestConfiguration getConfiguration(LintClient client, Project project) {
- return new TestConfiguration(client, project, null);
- }
-
- protected void configureDriver(LintDriver driver) {
- }
-
- /**
- * Run lint on the given files when constructed as a separate project
- * @return The output of the lint check. On Windows, this transforms all directory
- * separators to the unix-style forward slash.
- */
- protected String lintProject(String... relativePaths) throws Exception {
- File projectDir = getProjectDir(null, relativePaths);
- return checkLint(Collections.singletonList(projectDir));
- }
-
- protected String lintProjectIncrementally(String currentFile, String... relativePaths)
- throws Exception {
- File projectDir = getProjectDir(null, relativePaths);
- File current = new File(projectDir, currentFile.replace('/', File.separatorChar));
- assertTrue(current.exists());
- TestLintClient client = createClient();
- client.setIncremental(current);
- return checkLint(client, Collections.singletonList(projectDir));
- }
-
- protected String lintProjectIncrementally(String currentFile, TestFile... files)
- throws Exception {
- File projectDir = getProjectDir(null, files);
- File current = new File(projectDir, currentFile.replace('/', File.separatorChar));
- assertTrue(current.exists());
- TestLintClient client = createClient();
- client.setIncremental(current);
- return checkLint(client, Collections.singletonList(projectDir));
- }
-
- /**
- * Run lint on the given files when constructed as a separate project
- * @return The output of the lint check. On Windows, this transforms all directory
- * separators to the unix-style forward slash.
- */
- protected String lintProject(TestFile... files) throws Exception {
- File projectDir = getProjectDir(null, files);
- return checkLint(Collections.singletonList(projectDir));
- }
-
- @Override
- protected File getTargetDir() {
- File targetDir = new File(getTempDir(), getClass().getSimpleName() + "_" + getName());
- addCleanupDir(targetDir);
- return targetDir;
- }
-
- @NonNull
- public TestFile file() {
- return new TestFile();
- }
-
- @NonNull
- public TestFile source(@NonNull String to, @NonNull String source) {
- return file().to(to).withSource(source);
- }
-
- @NonNull
- public TestFile java(@NonNull String to, @NonNull @Language("JAVA") String source) {
- return file().to(to).withSource(source);
- }
-
- @NonNull
- public TestFile xml(@NonNull String to, @NonNull @Language("XML") String source) {
- return file().to(to).withSource(source);
- }
-
- @NonNull
- public TestFile copy(@NonNull String from, @NonNull String to) {
- return file().from(from).to(to);
- }
-
- @NonNull
- public TestFile copy(@NonNull String from) {
- return file().from(from).to(from);
- }
-
- /** Creates a project directory structure from the given files */
- protected File getProjectDir(String name, String ...relativePaths) throws Exception {
- assertFalse("getTargetDir must be overridden to make a unique directory",
- getTargetDir().equals(getTempDir()));
-
- List<TestFile> testFiles = Lists.newArrayList();
- for (String relativePath : relativePaths) {
- testFiles.add(file().copy(relativePath));
- }
- return getProjectDir(name, testFiles.toArray(new TestFile[testFiles.size()]));
- }
-
- /** Creates a project directory structure from the given files */
- protected File getProjectDir(String name, TestFile... testFiles) throws Exception {
- assertFalse("getTargetDir must be overridden to make a unique directory",
- getTargetDir().equals(getTempDir()));
-
- File projectDir = getTargetDir();
- if (name != null) {
- projectDir = new File(projectDir, name);
- }
- if (!projectDir.exists()) {
- assertTrue(projectDir.getPath(), projectDir.mkdirs());
- }
-
- for (TestFile fp : testFiles) {
- File file = fp.createFile(projectDir);
- assertNotNull(file);
- }
-
- addManifestFile(projectDir);
- return projectDir;
- }
-
- private static void addManifestFile(File projectDir) throws IOException {
- // Ensure that there is at least a manifest file there to make it a valid project
- // as far as Lint is concerned:
- if (!new File(projectDir, "AndroidManifest.xml").exists()) {
- File manifest = new File(projectDir, "AndroidManifest.xml");
- FileWriter fw = new FileWriter(manifest);
- fw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " package=\"foo.bar2\"\n" +
- " android:versionCode=\"1\"\n" +
- " android:versionName=\"1.0\" >\n" +
- "</manifest>\n");
- fw.close();
- }
- }
-
- private StringBuilder mOutput = null;
-
- @Override
- protected InputStream getTestResource(String relativePath, boolean expectExists) {
- String path = "data" + File.separator + relativePath; //$NON-NLS-1$
- InputStream stream = LintDetectorTest.class.getResourceAsStream(path);
- if (!expectExists && stream == null) {
- return null;
- }
- return stream;
- }
-
- protected boolean isEnabled(Issue issue) {
- Class<? extends Detector> detectorClass = getDetectorInstance().getClass();
- if (issue.getImplementation().getDetectorClass() == detectorClass) {
- return true;
- }
-
- if (issue == IssueRegistry.LINT_ERROR || issue == IssueRegistry.PARSER_ERROR) {
- return !ignoreSystemErrors();
- }
-
- return false;
- }
-
- protected boolean includeParentPath() {
- return false;
- }
-
- protected EnumSet<Scope> getLintScope(List<File> file) {
- return null;
- }
-
- public String getSuperClass(Project project, String name) {
- return null;
- }
-
- protected boolean ignoreSystemErrors() {
- return true;
- }
-
- public class TestLintClient extends LintCliClient {
- private StringWriter mWriter = new StringWriter();
- private File mIncrementalCheck;
-
- public TestLintClient() {
- super(new LintCliFlags());
- mFlags.getReporters().add(new TextReporter(this, mFlags, mWriter, false));
- }
-
- @Override
- public String getSuperClass(@NonNull Project project, @NonNull String name) {
- String superClass = LintDetectorTest.this.getSuperClass(project, name);
- if (superClass != null) {
- return superClass;
- }
-
- return super.getSuperClass(project, name);
- }
-
- public String analyze(List<File> files) throws Exception {
- mDriver = new LintDriver(new CustomIssueRegistry(), this);
- configureDriver(mDriver);
- LintRequest request = new LintRequest(this, files);
- if (mIncrementalCheck != null) {
- assertEquals(1, files.size());
- File projectDir = files.get(0);
- assertTrue(isProjectDirectory(projectDir));
- Project project = createProject(projectDir, projectDir);
- project.addFile(mIncrementalCheck);
- List<Project> projects = Collections.singletonList(project);
- request.setProjects(projects);
- }
-
- mDriver.analyze(request.setScope(getLintScope(files)));
-
- // Check compare contract
- Warning prev = null;
- for (Warning warning : mWarnings) {
- if (prev != null) {
- boolean equals = warning.equals(prev);
- assertEquals(equals, prev.equals(warning));
- int compare = warning.compareTo(prev);
- assertEquals(equals, compare == 0);
- assertEquals(-compare, prev.compareTo(warning));
- }
- prev = warning;
- }
-
- Collections.sort(mWarnings);
-
- // Check compare contract & transitivity
- Warning prev2 = prev;
- prev = null;
- for (Warning warning : mWarnings) {
- if (prev != null && prev2 != null) {
- assertTrue(warning.compareTo(prev) >= 0);
- assertTrue(prev.compareTo(prev2) >= 0);
- assertTrue(warning.compareTo(prev2) >= 0);
-
- assertTrue(prev.compareTo(warning) <= 0);
- assertTrue(prev2.compareTo(prev) <= 0);
- assertTrue(prev2.compareTo(warning) <= 0);
- }
- prev2 = prev;
- prev = warning;
- }
-
- for (Reporter reporter : mFlags.getReporters()) {
- reporter.write(mErrorCount, mWarningCount, mWarnings);
- }
-
- mOutput.append(mWriter.toString());
-
- if (mOutput.length() == 0) {
- mOutput.append("No warnings.");
- }
-
- String result = mOutput.toString();
- if (result.equals("No issues found.\n")) {
- result = "No warnings.";
- }
-
- result = cleanup(result);
-
- return result;
- }
-
- public String getErrors() throws Exception {
- return mWriter.toString();
- }
-
- @Override
- public void report(
- @NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message,
- @NonNull TextFormat format) {
- if (ignoreSystemErrors() && (issue == IssueRegistry.LINT_ERROR)) {
- return;
- }
-
- // Use plain ascii in the test golden files for now. (This also ensures
- // that the markup is wellformed, e.g. if we have a ` without a matching
- // closing `, the ` would show up in the plain text.)
- message = format.convertTo(message, TextFormat.TEXT);
-
- checkReportedError(context, issue, severity, location, message);
-
- if (severity == Severity.FATAL) {
- // Treat fatal errors like errors in the golden files.
- severity = Severity.ERROR;
- }
-
- // For messages into all secondary locations to ensure they get
- // specifically included in the text report
- if (location != null && location.getSecondary() != null) {
- Location l = location.getSecondary();
- if (l == location) {
- fail("Location link cycle");
- }
- while (l != null) {
- if (l.getMessage() == null) {
- l.setMessage("<No location-specific message");
- }
- if (l == l.getSecondary()) {
- fail("Location link cycle");
- }
- l = l.getSecondary();
- }
- }
-
- super.report(context, issue, severity, location, message, format);
-
- // Make sure errors are unique!
- Warning prev = null;
- for (Warning warning : mWarnings) {
- assertNotSame(warning, prev);
- assert prev == null || !warning.equals(prev) : warning;
- prev = warning;
- }
- }
-
- @Override
- public void log(Throwable exception, String format, Object... args) {
- if (exception != null) {
- exception.printStackTrace();
- }
- StringBuilder sb = new StringBuilder();
- if (format != null) {
- sb.append(String.format(format, args));
- }
- if (exception != null) {
- sb.append(exception.toString());
- }
- System.err.println(sb);
-
- if (exception != null) {
- fail(exception.toString());
- }
- }
-
- @NonNull
- @Override
- public Configuration getConfiguration(@NonNull Project project,
- @Nullable LintDriver driver) {
- return LintDetectorTest.this.getConfiguration(this, project);
- }
-
- @Override
- public File findResource(@NonNull String relativePath) {
- if (relativePath.equals("platform-tools/api/api-versions.xml")) {
- // Look in the current Git repository and try to find it there
- File rootDir = getRootDir();
- if (rootDir != null) {
- File file = new File(rootDir, "development" + File.separator + "sdk"
- + File.separator + "api-versions.xml");
- if (file.exists()) {
- return file;
- }
- }
- // Look in an SDK install, if found
- File home = getSdkHome();
- if (home != null) {
- return new File(home, relativePath);
- }
- } else if (relativePath.equals(ExternalAnnotationRepository.SDK_ANNOTATIONS_PATH)) {
- // Look in the current Git repository and try to find it there
- File rootDir = getRootDir();
- if (rootDir != null) {
- File file = new File(rootDir,
- "tools" + File.separator
- + "adt" + File.separator
- + "idea" + File.separator
- + "android" + File.separator
- + "annotations");
- if (file.exists()) {
- return file;
- }
- }
- // Look in an SDK install, if found
- File home = getSdkHome();
- if (home != null) {
- File file = new File(home, relativePath);
- return file.exists() ? file : null;
- }
- } else if (relativePath.startsWith("tools/support/")) {
- // Look in the current Git repository and try to find it there
- String base = relativePath.substring("tools/support/".length());
- File rootDir = getRootDir();
- if (rootDir != null) {
- File file = new File(rootDir, "tools"
- + File.separator + "base"
- + File.separator + "files"
- + File.separator + "typos"
- + File.separator + base);
- if (file.exists()) {
- return file;
- }
- }
- // Look in an SDK install, if found
- File home = getSdkHome();
- if (home != null) {
- return new File(home, relativePath);
- }
- } else {
- fail("Unit tests don't support arbitrary resource lookup yet.");
- }
-
- return super.findResource(relativePath);
- }
-
- @NonNull
- @Override
- public List<File> findGlobalRuleJars() {
- // Don't pick up random custom rules in ~/.android/lint when running unit tests
- return Collections.emptyList();
- }
-
- public void setIncremental(File currentFile) {
- mIncrementalCheck = currentFile;
- }
-
- @Override
- public boolean supportsProjectResources() {
- return mIncrementalCheck != null;
- }
-
- @Nullable
- @Override
- public AbstractResourceRepository getProjectResources(Project project,
- boolean includeDependencies) {
- if (mIncrementalCheck == null) {
- return null;
- }
-
- ResourceRepository repository = new ResourceRepository(false);
- ILogger logger = new StdLogger(StdLogger.Level.INFO);
- ResourceMerger merger = new ResourceMerger();
-
- ResourceSet resourceSet = new ResourceSet(getName()) {
- @Override
- protected void checkItems() throws DuplicateDataException {
- // No checking in ProjectResources; duplicates can happen, but
- // the project resources shouldn't abort initialization
- }
- };
- // Only support 1 resource folder in test setup right now
- int size = project.getResourceFolders().size();
- assertTrue("Found " + size + " test resources folders", size <= 1);
- if (size == 1) {
- resourceSet.addSource(project.getResourceFolders().get(0));
- }
- try {
- resourceSet.loadFromFiles(logger);
- merger.addDataSet(resourceSet);
- merger.mergeData(repository.createMergeConsumer(), true);
-
- // Make tests stable: sort the item lists!
- Map<ResourceType, ListMultimap<String, ResourceItem>> map = repository.getItems();
- for (Map.Entry<ResourceType, ListMultimap<String, ResourceItem>> entry : map.entrySet()) {
- Map<String, List<ResourceItem>> m = Maps.newHashMap();
- ListMultimap<String, ResourceItem> value = entry.getValue();
- List<List<ResourceItem>> lists = Lists.newArrayList();
- for (Map.Entry<String, ResourceItem> e : value.entries()) {
- String key = e.getKey();
- ResourceItem item = e.getValue();
-
- List<ResourceItem> list = m.get(key);
- if (list == null) {
- list = Lists.newArrayList();
- lists.add(list);
- m.put(key, list);
- }
- list.add(item);
- }
-
- for (List<ResourceItem> list : lists) {
- Collections.sort(list, new Comparator<ResourceItem>() {
- @Override
- public int compare(ResourceItem o1, ResourceItem o2) {
- return o1.getKey().compareTo(o2.getKey());
- }
- });
- }
-
- // Store back in list multi map in new sorted order
- value.clear();
- for (Map.Entry<String, List<ResourceItem>> e : m.entrySet()) {
- String key = e.getKey();
- List<ResourceItem> list = e.getValue();
- for (ResourceItem item : list) {
- value.put(key, item);
- }
- }
- }
-
- // Workaround: The repository does not insert ids from layouts! We need
- // to do that here.
- Map<ResourceType,ListMultimap<String,ResourceItem>> items = repository.getItems();
- ListMultimap<String, ResourceItem> layouts = items
- .get(ResourceType.LAYOUT);
- if (layouts != null) {
- for (ResourceItem item : layouts.values()) {
- ResourceFile source = item.getSource();
- if (source == null) {
- continue;
- }
- File file = source.getFile();
- try {
- String xml = Files.toString(file, Charsets.UTF_8);
- Document document = XmlUtils.parseDocumentSilently(xml, true);
- assertNotNull(document);
- Set<String> ids = Sets.newHashSet();
- addIds(ids, document); // TODO: pull parser
- if (!ids.isEmpty()) {
- ListMultimap<String, ResourceItem> idMap =
- items.get(ResourceType.ID);
- if (idMap == null) {
- idMap = ArrayListMultimap.create();
- items.put(ResourceType.ID, idMap);
- }
- for (String id : ids) {
- ResourceItem idItem = new ResourceItem(id, ResourceType.ID,
- null);
- String qualifiers = file.getParentFile().getName();
- if (qualifiers.startsWith("layout-")) {
- qualifiers = qualifiers.substring("layout-".length());
- } else if (qualifiers.equals("layout")) {
- qualifiers = "";
- }
- idItem.setSource(new ResourceFile(file, item, qualifiers));
- idMap.put(id, idItem);
- }
- }
- } catch (IOException e) {
- fail(e.toString());
- }
- }
- }
- }
- catch (MergingException e) {
- fail(e.getMessage());
- }
-
- return repository;
- }
-
- private void addIds(Set<String> ids, Node node) {
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element element = (Element) node;
- String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
- if (id != null && !id.isEmpty()) {
- ids.add(LintUtils.stripIdPrefix(id));
- }
-
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- String value = attribute.getValue();
- if (value.startsWith(NEW_ID_PREFIX)) {
- ids.add(value.substring(NEW_ID_PREFIX.length()));
- }
- }
- }
-
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- addIds(ids, child);
- }
- }
-
- @Nullable
- @Override
- public IAndroidTarget getCompileTarget(@NonNull Project project) {
- IAndroidTarget compileTarget = super.getCompileTarget(project);
- if (compileTarget == null) {
- IAndroidTarget[] targets = getTargets();
- for (int i = targets.length - 1; i >= 0; i--) {
- IAndroidTarget target = targets[i];
- if (target.isPlatform()) {
- return target;
- }
- }
- }
-
- return compileTarget;
- }
-
- @NonNull
- @Override
- public List<File> getTestSourceFolders(@NonNull Project project) {
- List<File> testSourceFolders = super.getTestSourceFolders(project);
-
- //List<File> tests = new ArrayList<File>();
- File tests = new File(project.getDir(), "test");
- if (tests.exists()) {
- List<File> all = Lists.newArrayList(testSourceFolders);
- all.add(tests);
- testSourceFolders = all;
- }
-
- return testSourceFolders;
- }
- }
-
- /**
- * Returns the Android source tree root dir.
- * @return the root dir or null if it couldn't be computed.
- */
- protected File getRootDir() {
- CodeSource source = getClass().getProtectionDomain().getCodeSource();
- if (source != null) {
- URL location = source.getLocation();
- try {
- File dir = SdkUtils.urlToFile(location);
- assertTrue(dir.getPath(), dir.exists());
- while (dir != null) {
- File settingsGradle = new File(dir, "settings.gradle"); //$NON-NLS-1$
- if (settingsGradle.exists()) {
- return dir.getParentFile().getParentFile();
- }
- File lint = new File(dir, "lint"); //$NON-NLS-1$
- if (lint.exists() && new File(lint, "cli").exists()) { //$NON-NLS-1$
- return dir.getParentFile().getParentFile();
- }
- dir = dir.getParentFile();
- }
-
- return null;
- } catch (MalformedURLException e) {
- fail(e.getLocalizedMessage());
- }
- }
-
- return null;
- }
-
- public class TestConfiguration extends DefaultConfiguration {
- protected TestConfiguration(
- @NonNull LintClient client,
- @NonNull Project project,
- @Nullable Configuration parent) {
- super(client, project, parent);
- }
-
- public TestConfiguration(
- @NonNull LintClient client,
- @Nullable Project project,
- @Nullable Configuration parent,
- @NonNull File configFile) {
- super(client, project, parent, configFile);
- }
-
- @Override
- @NonNull
- protected Severity getDefaultSeverity(@NonNull Issue issue) {
- // In unit tests, include issues that are ignored by default
- Severity severity = super.getDefaultSeverity(issue);
- if (severity == Severity.IGNORE) {
- if (issue.getDefaultSeverity() != Severity.IGNORE) {
- return issue.getDefaultSeverity();
- }
- return Severity.WARNING;
- }
- return severity;
- }
-
- @Override
- public boolean isEnabled(@NonNull Issue issue) {
- return LintDetectorTest.this.isEnabled(issue);
- }
-
- @Override
- public void ignore(@NonNull Context context, @NonNull Issue issue,
- @Nullable Location location, @NonNull String message) {
- fail("Not supported in tests.");
- }
-
- @Override
- public void setSeverity(@NonNull Issue issue, @Nullable Severity severity) {
- fail("Not supported in tests.");
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java
deleted file mode 100644
index 457a447..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java
+++ /dev/null
@@ -1,916 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.tools.lint.EcjParser.equalsCompound;
-import static com.android.tools.lint.EcjParser.startsWithCompound;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedField;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import static com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.checks.AbstractCheckTest;
-import com.android.tools.lint.checks.SdCardDetector;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtilsTest;
-import com.android.tools.lint.detector.api.Project;
-import com.google.common.collect.Lists;
-
-import org.intellij.lang.annotations.Language;
-import org.junit.Assert;
-
-import java.io.File;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import lombok.ast.AnnotationElement;
-import lombok.ast.BinaryExpression;
-import lombok.ast.Block;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.DescribedNode;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Identifier;
-import lombok.ast.KeywordModifier;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.Select;
-import lombok.ast.TypeReferencePart;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableReference;
-import lombok.ast.printer.SourceFormatter;
-import lombok.ast.printer.SourcePrinter;
-import lombok.ast.printer.TextFormatter;
-
-public class EcjParserTest extends AbstractCheckTest {
- public void testTryCatchHang() throws Exception {
- // Ensure that we're really using this parser
- JavaParser javaParser = createClient().getJavaParser(null);
- assertNotNull(javaParser);
- assertTrue(javaParser.getClass().getName(), javaParser instanceof EcjParser);
-
- // See https://code.google.com/p/projectlombok/issues/detail?id=573#c6
- // With lombok.ast 0.2.1 and the parboiled-based Java parser this test will hang forever.
- assertEquals(
- "No warnings.",
-
- lintProject("src/test/pkg/TryCatchHang.java.txt=>src/test/pkg/TryCatchHang.java"));
- }
-
- public void testKitKatLanguageFeatures() throws Exception {
- String testClass = "" +
- "package test.pkg;\n" +
- "\n" +
- "import java.io.BufferedReader;\n" +
- "import java.io.FileReader;\n" +
- "import java.io.IOException;\n" +
- "import java.lang.reflect.InvocationTargetException;\n" +
- "import java.util.List;\n" +
- "import java.util.Map;\n" +
- "import java.util.TreeMap;\n" +
- "\n" +
- "public class Java7LanguageFeatureTest {\n" +
- " public void testDiamondOperator() {\n" +
- " Map<String, List<Integer>> map = new TreeMap<>();\n" +
- " }\n" +
- "\n" +
- " public int testStringSwitches(String value) {\n" +
- " final String first = \"first\";\n" +
- " final String second = \"second\";\n" +
- "\n" +
- " switch (value) {\n" +
- " case first:\n" +
- " return 41;\n" +
- " case second:\n" +
- " return 42;\n" +
- " default:\n" +
- " return 0;\n" +
- " }\n" +
- " }\n" +
- "\n" +
- " public String testTryWithResources(String path) throws IOException {\n" +
- " try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n" +
- " return br.readLine();\n" +
- " }\n" +
- " }\n" +
- "\n" +
- " public void testNumericLiterals() {\n" +
- " int thousand = 1_000;\n" +
- " int million = 1_000_000;\n" +
- " int binary = 0B01010101;\n" +
- " }\n" +
- "\n" +
- " public void testMultiCatch() {\n" +
- "\n" +
- " try {\n" +
- " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n" +
- " } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n" +
- " e.printStackTrace();\n" +
- " } catch (ClassNotFoundException e) {\n" +
- " // TODO: Logging here\n" +
- " }\n" +
- " }\n" +
- "}\n";
-
- Node unit = LintUtilsTest.getCompilationUnit(testClass);
- assertNotNull(unit);
-
- // Now print the AST back and make sure that it contains at least the essence of the AST
- TextFormatter formatter = new TextFormatter();
- unit.accept(new SourcePrinter(formatter));
- String actual = formatter.finish();
- assertEquals(""
- + "package test.pkg;\n"
- + "\n"
- + "import java.io.BufferedReader;\n"
- + "import java.io.FileReader;\n"
- + "import java.io.IOException;\n"
- + "import java.lang.reflect.InvocationTargetException;\n"
- + "import java.util.List;\n"
- + "import java.util.Map;\n"
- + "import java.util.TreeMap;\n"
- + "\n"
- + "public class Java7LanguageFeatureTest {\n"
- + " public void testDiamondOperator() {\n"
- + " Map<String, List<Integer>> map = new TreeMap();\n" // missing types on rhs
- + " }\n"
- + " \n"
- + " public int testStringSwitches(String value) {\n"
- + " final String first = \"first\";\n"
- + " final String second = \"second\";\n"
- + " switch (value) {\n"
- + " case first:\n"
- + " return 41;\n"
- + " case second:\n"
- + " return 42;\n"
- + " default:\n"
- + " return 0;\n"
- + " }\n"
- + " }\n"
- + " \n"
- + " public String testTryWithResources(String path) throws IOException {\n"
- + " try {\n" // Note how the initialization clause is gone here
- + " return br.readLine();\n"
- + " }\n"
- + " }\n"
- + " \n"
- + " public void testNumericLiterals() {\n"
- + " int thousand = 1_000;\n"
- + " int million = 1_000_000;\n"
- + " int binary = 0B01010101;\n"
- + " }\n"
- + " \n"
- + " public void testMultiCatch() {\n"
- + " try {\n"
- + " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n"
- + " } catch (IllegalAccessException e) {\n" // Note: missing other union types
- + " e.printStackTrace();\n"
- + " } catch (ClassNotFoundException e) {\n"
- + " }\n"
- + " }\n"
- + "}",
- actual);
- }
-
- @SuppressWarnings("ClassNameDiffersFromFileName")
- public void testGetFields() throws Exception {
- @Language("JAVA")
- String source = ""
- + "public class FieldTest {\n"
- + " public int field1 = 1;\n"
- + " public int field2 = 3;\n"
- + " public int field3 = 5;\n"
- + " \n"
- + " public static class Inner extends FieldTest {\n"
- + " public int field2 = 5;\n"
- + " }\n"
- + "}\n";
-
- final JavaContext context = LintUtilsTest.parse(source,
- new File("src/test/pkg/FieldTest.java"));
- assertNotNull(context);
-
- Node compilationUnit = context.getCompilationUnit();
- assertNotNull(compilationUnit);
- final AtomicBoolean found = new AtomicBoolean();
- compilationUnit.accept(new ForwardingAstVisitor() {
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- if (node.astName().astValue().equals("Inner")) {
- found.set(true);
- ResolvedNode resolved = context.resolve(node);
- assertNotNull(resolved);
- ResolvedClass cls = (ResolvedClass) resolved;
- List<ResolvedField> declaredFields = Lists.newArrayList(cls.getFields(false));
- assertEquals(1, declaredFields.size());
- assertEquals("field2", declaredFields.get(0).getName());
-
- declaredFields = Lists.newArrayList(cls.getFields(true));
- assertEquals(3, declaredFields.size());
- assertEquals("field2", declaredFields.get(0).getName());
- assertEquals("FieldTest.Inner", declaredFields.get(0).getContainingClassName());
- assertEquals("field1", declaredFields.get(1).getName());
- assertEquals("FieldTest", declaredFields.get(1).getContainingClassName());
- assertEquals("field3", declaredFields.get(2).getName());
- assertEquals("FieldTest", declaredFields.get(2).getContainingClassName());
- }
-
- return super.visitClassDeclaration(node);
- }
- });
- assertTrue(found.get());
- }
-
- public void testResolution() throws Exception {
- String source =
- "package test.pkg;\n" +
- "\n" +
- "import java.io.File;\n" +
- "\n" +
- "public class TypeResolutionTest {\n" +
- " public static class Inner extends File {\n" +
- " public float myField = 5f;\n" +
- " public int[] myInts;\n" +
- "\n" +
- " public Inner(File dir, String name) {\n" +
- " super(dir, name);\n" +
- " }\n" +
- "\n" +
- " public void call(int arg1, double arg2) {\n" +
- " boolean x = super.canRead();\n" +
- " System.out.println(x);\n" +
- " }\n" +
- " }\n" +
- "\n" +
- " @SuppressWarnings(\"all\")\n" +
- " public static class Other {\n" +
- " private void client(int z) {\n" +
- " int x = z;\n" +
- " int y = x + 5;\n" +
- " Inner inner = new Inner(null, null);\n" +
- " inner.myField = 6;\n" +
- " System.out.println(inner.myInts);\n" +
- " }\n" +
- " }\n" +
- "}\n";
-
- Node unit = LintUtilsTest.getCompilationUnit(source,
- new File("src/test/pkg/TypeResolutionTest.java"));
-
- // Visit all nodes and assert nativeNode != null unless I expect it!
- unit.accept(new ForwardingAstVisitor() {
- @SuppressWarnings("Contract")
- @Override
- public boolean visitNode(Node node) {
- if (node.getNativeNode() == null && requiresNativeNode(node)) {
- fail("Expected native node on node of type " +
- node.getClass().getSimpleName());
- }
- return super.visitNode(node);
- }
-
- private boolean requiresNativeNode(Node node) {
- if (node instanceof TypeReferencePart &&
- node.getParent().getNativeNode() != null) {
- return false;
- }
-
- if (node instanceof Identifier
- || node instanceof NormalTypeBody
- || node instanceof Block
- || node instanceof VariableDeclaration
- || node instanceof VariableDefinition
- || node instanceof AnnotationElement
- || node instanceof BinaryExpression
- || node instanceof Modifiers
- || node instanceof KeywordModifier) {
- return false;
- }
-
- if (node instanceof VariableReference) {
- VariableReference reference = (VariableReference)node;
- if (reference.getParent() instanceof Select) {
- return false;
- }
- } else if (node instanceof MethodInvocation) {
- Node parent = node.getParent();
- if (parent instanceof ExpressionStatement &&
- parent.getNativeNode() != null) {
- return false;
- }
- }
-
- return true;
- }
- });
-
- JavaParser parser = new EcjParser(new LintCliClient(), null);
- AstPrettyPrinter astPrettyPrinter = new AstPrettyPrinter(parser);
- unit.accept(new SourcePrinter(astPrettyPrinter));
- String actual = astPrettyPrinter.finish();
- assertEquals(
- "[CompilationUnit]\n" +
- " [PackageDeclaration]\n" +
- " [Identifier test]\n" +
- " PROPERTY: name = test\n" +
- " [Identifier pkg]\n" +
- " PROPERTY: name = pkg\n" +
- " [ImportDeclaration]\n" +
- " PROPERTY: static = false\n" +
- " PROPERTY: star = false\n" +
- " [Identifier java]\n" +
- " PROPERTY: name = java\n" +
- " [Identifier io]\n" +
- " PROPERTY: name = io\n" +
- " [Identifier File]\n" +
- " PROPERTY: name = File\n" +
- " [ClassDeclaration TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
- " [Modifiers], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
- " [KeywordModifier public]\n" +
- " PROPERTY: modifier = public\n" +
- " typeName: [Identifier TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
- " PROPERTY: name = TypeResolutionTest\n" +
- " [NormalTypeBody], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
- " [ClassDeclaration Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " [Modifiers], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " [KeywordModifier public]\n" +
- " PROPERTY: modifier = public\n" +
- " [KeywordModifier static]\n" +
- " PROPERTY: modifier = static\n" +
- " typeName: [Identifier Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " PROPERTY: name = Inner\n" +
- " extends: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
- " [Identifier File]\n" +
- " PROPERTY: name = File\n" +
- " [NormalTypeBody], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " [VariableDeclaration], type: float, resolved class: float \n" +
- " [VariableDefinition]\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " [KeywordModifier public]\n" +
- " PROPERTY: modifier = public\n" +
- " type: [TypeReference float], type: float, resolved class: float \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: float, resolved class: float \n" +
- " [Identifier float]\n" +
- " PROPERTY: name = float\n" +
- " [VariableDefinitionEntry], resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier myField], resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: name = myField\n" +
- " [FloatingPointLiteral 5.0], type: float\n" +
- " PROPERTY: value = 5f\n" +
- " [VariableDeclaration], type: int[], resolved class: int[] \n" +
- " [VariableDefinition]\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " [KeywordModifier public]\n" +
- " PROPERTY: modifier = public\n" +
- " type: [TypeReference int[]], type: int[], resolved class: int[] \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 1\n" +
- " [TypeReferencePart], type: int[], resolved class: int[] \n" +
- " [Identifier int]\n" +
- " PROPERTY: name = int\n" +
- " [VariableDefinitionEntry], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier myInts], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: name = myInts\n" +
- " [ConstructorDeclaration], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
- " [Modifiers], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
- " [KeywordModifier public]\n" +
- " PROPERTY: modifier = public\n" +
- " typeName: [Identifier Inner], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: name = Inner\n" +
- " parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
- " [Identifier File]\n" +
- " PROPERTY: name = File\n" +
- " [VariableDefinitionEntry], resolved variable: dir java.io.File\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier dir], resolved variable: dir java.io.File\n" +
- " PROPERTY: name = dir\n" +
- " parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference String], type: java.lang.String, resolved class: java.lang.String \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: java.lang.String, resolved class: java.lang.String \n" +
- " [Identifier String]\n" +
- " PROPERTY: name = String\n" +
- " [VariableDefinitionEntry], resolved variable: name java.lang.String\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier name], resolved variable: name java.lang.String\n" +
- " PROPERTY: name = name\n" +
- " [Block], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
- " [SuperConstructorInvocation], resolved method: java.io.File java.io.File\n" +
- " [VariableReference], type: java.io.File, resolved variable: dir java.io.File\n" +
- " [Identifier dir], type: java.io.File, resolved variable: dir java.io.File\n" +
- " PROPERTY: name = dir\n" +
- " [VariableReference], type: java.lang.String, resolved variable: name java.lang.String\n" +
- " [Identifier name], type: java.lang.String, resolved variable: name java.lang.String\n" +
- " PROPERTY: name = name\n" +
- " [MethodDeclaration call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
- " [Modifiers], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
- " [KeywordModifier public]\n" +
- " PROPERTY: modifier = public\n" +
- " returnType: [TypeReference void], type: void, resolved class: void \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: void, resolved class: void \n" +
- " [Identifier void]\n" +
- " PROPERTY: name = void\n" +
- " methodName: [Identifier call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: name = call\n" +
- " parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference int], type: int, resolved class: int \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: int, resolved class: int \n" +
- " [Identifier int]\n" +
- " PROPERTY: name = int\n" +
- " [VariableDefinitionEntry], resolved variable: arg1 int\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier arg1], resolved variable: arg1 int\n" +
- " PROPERTY: name = arg1\n" +
- " parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference double], type: double, resolved class: double \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: double, resolved class: double \n" +
- " [Identifier double]\n" +
- " PROPERTY: name = double\n" +
- " [VariableDefinitionEntry], resolved variable: arg2 double\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier arg2], resolved variable: arg2 double\n" +
- " PROPERTY: name = arg2\n" +
- " [Block], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
- " [VariableDeclaration], type: boolean, resolved class: boolean \n" +
- " [VariableDefinition]\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference boolean], type: boolean, resolved class: boolean \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: boolean, resolved class: boolean \n" +
- " [Identifier boolean]\n" +
- " PROPERTY: name = boolean\n" +
- " [VariableDefinitionEntry], resolved variable: x boolean\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier x], resolved variable: x boolean\n" +
- " PROPERTY: name = x\n" +
- " [MethodInvocation canRead], type: boolean, resolved method: canRead java.io.File\n" +
- " operand: [Super], type: java.io.File\n" +
- " methodName: [Identifier canRead], type: boolean, resolved method: canRead java.io.File\n" +
- " PROPERTY: name = canRead\n" +
- " [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
- " [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
- " operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
- " operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
- " [Identifier System]\n" +
- " PROPERTY: name = System\n" +
- " selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
- " PROPERTY: name = out\n" +
- " methodName: [Identifier println]\n" +
- " PROPERTY: name = println\n" +
- " [VariableReference], type: boolean, resolved variable: x boolean\n" +
- " [Identifier x], type: boolean, resolved variable: x boolean\n" +
- " PROPERTY: name = x\n" +
- " [ClassDeclaration Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
- " [Modifiers], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
- " [Annotation SuppressWarnings], type: java.lang.SuppressWarnings, resolved annotation: java.lang.SuppressWarnings \n" +
- " [TypeReference SuppressWarnings], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
- " [Identifier SuppressWarnings]\n" +
- " PROPERTY: name = SuppressWarnings\n" +
- " [AnnotationElement null], type: java.lang.SuppressWarnings, resolved annotation: java.lang.SuppressWarnings \n" +
- " [StringLiteral all], type: java.lang.String\n" +
- " PROPERTY: value = \"all\"\n" +
- " [KeywordModifier public]\n" +
- " PROPERTY: modifier = public\n" +
- " [KeywordModifier static]\n" +
- " PROPERTY: modifier = static\n" +
- " typeName: [Identifier Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
- " PROPERTY: name = Other\n" +
- " [NormalTypeBody], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
- " [MethodDeclaration client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
- " [Modifiers], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
- " [KeywordModifier private]\n" +
- " PROPERTY: modifier = private\n" +
- " returnType: [TypeReference void], type: void, resolved class: void \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: void, resolved class: void \n" +
- " [Identifier void]\n" +
- " PROPERTY: name = void\n" +
- " methodName: [Identifier client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
- " PROPERTY: name = client\n" +
- " parameter: [VariableDefinition], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference int], type: int, resolved class: int \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: int, resolved class: int \n" +
- " [Identifier int]\n" +
- " PROPERTY: name = int\n" +
- " [VariableDefinitionEntry], resolved variable: z int\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier z], resolved variable: z int\n" +
- " PROPERTY: name = z\n" +
- " [Block], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
- " [VariableDeclaration], type: int, resolved class: int \n" +
- " [VariableDefinition]\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference int], type: int, resolved class: int \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: int, resolved class: int \n" +
- " [Identifier int]\n" +
- " PROPERTY: name = int\n" +
- " [VariableDefinitionEntry], resolved variable: x int\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier x], resolved variable: x int\n" +
- " PROPERTY: name = x\n" +
- " [VariableReference], type: int, resolved variable: z int\n" +
- " [Identifier z], type: int, resolved variable: z int\n" +
- " PROPERTY: name = z\n" +
- " [VariableDeclaration], type: int, resolved class: int \n" +
- " [VariableDefinition]\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference int], type: int, resolved class: int \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: int, resolved class: int \n" +
- " [Identifier int]\n" +
- " PROPERTY: name = int\n" +
- " [VariableDefinitionEntry], resolved variable: y int\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier y], resolved variable: y int\n" +
- " PROPERTY: name = y\n" +
- " [BinaryExpression +], type: int\n" +
- " PROPERTY: operator = +\n" +
- " left: [VariableReference], type: int, resolved variable: x int\n" +
- " [Identifier x], type: int, resolved variable: x int\n" +
- " PROPERTY: name = x\n" +
- " right: [IntegralLiteral 5], type: int\n" +
- " PROPERTY: value = 5\n" +
- " [VariableDeclaration], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " [VariableDefinition]\n" +
- " PROPERTY: varargs = false\n" +
- " [Modifiers]\n" +
- " type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " [Identifier Inner]\n" +
- " PROPERTY: name = Inner\n" +
- " [VariableDefinitionEntry], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " varName: [Identifier inner], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: name = inner\n" +
- " [ConstructorInvocation Inner], type: test.pkg.TypeResolutionTest.Inner, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
- " type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " PROPERTY: WildcardKind = NONE\n" +
- " PROPERTY: arrayDimensions = 0\n" +
- " [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
- " [Identifier Inner]\n" +
- " PROPERTY: name = Inner\n" +
- " [NullLiteral], type: null\n" +
- " [NullLiteral], type: null\n" +
- " [ExpressionStatement], type: float\n" +
- " [BinaryExpression =], type: float\n" +
- " PROPERTY: operator = =\n" +
- " left: [Select], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " operand: [VariableReference], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " [Identifier inner]\n" +
- " PROPERTY: name = inner\n" +
- " selected: [Identifier myField], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: name = myField\n" +
- " right: [IntegralLiteral 6], type: int\n" +
- " PROPERTY: value = 6\n" +
- " [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
- " [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
- " operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
- " operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
- " [Identifier System]\n" +
- " PROPERTY: name = System\n" +
- " selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
- " PROPERTY: name = out\n" +
- " methodName: [Identifier println]\n" +
- " PROPERTY: name = println\n" +
- " [Select], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " operand: [VariableReference], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " [Identifier inner]\n" +
- " PROPERTY: name = inner\n" +
- " selected: [Identifier myInts], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
- " PROPERTY: name = myInts\n",
- actual);
- }
-
- public void testStartsWithCompound() throws Exception {
- assertTrue(startsWithCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray()
- }));
-
- assertTrue(startsWithCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray(),
- "other".toCharArray(),
- }));
-
- assertFalse(startsWithCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "other".toCharArray()
- }));
-
- // Corner cases
- assertFalse(startsWithCompound("test.pk",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray(),
- }));
- assertFalse(startsWithCompound("test.pkg",
- new char[][]{
- "test".toCharArray()
- }));
- assertTrue(startsWithCompound("test.",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray()
- }));
- assertFalse(startsWithCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "pk".toCharArray()
- }));
- }
-
- public void testEqualsWithCompound() throws Exception {
- assertTrue(equalsCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray()
- }));
-
- assertFalse(equalsCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray(),
- "other".toCharArray(),
- }));
-
- assertFalse(equalsCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "other".toCharArray()
- }));
-
- // Corner cases
- assertFalse(equalsCompound("test.pk",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray(),
- }));
- assertFalse(equalsCompound("test.pkg",
- new char[][]{
- "test".toCharArray()
- }));
- assertFalse(equalsCompound("test.",
- new char[][]{
- "test".toCharArray(),
- "pkg".toCharArray()
- }));
- assertFalse(equalsCompound("test.pkg",
- new char[][]{
- "test".toCharArray(),
- "pk".toCharArray()
- }));
- }
-
- @Override
- protected Detector getDetector() {
- return new SdCardDetector();
- }
-
- @Override
- protected TestLintClient createClient() {
- return new TestLintClient() {
- @NonNull
- @Override
- protected ClassPathInfo getClassPath(@NonNull Project project) {
- ClassPathInfo classPath = super.getClassPath(project);
- // Insert fake classpath entries (non existent directories) to
- // make sure the parser handles that gracefully. See issue 87740.
- classPath.getLibraries().add(new File("nonexistent path"));
- return classPath;
- }
- };
- }
-
- public static class AstPrettyPrinter implements SourceFormatter {
-
- private final StringBuilder mOutput = new StringBuilder(1000);
-
- private final JavaParser mResolver;
-
- private int mIndent;
-
- private String mName;
-
- public AstPrettyPrinter(JavaParser resolver) {
- mResolver = resolver;
- }
-
- private void add(String in, Object... args) {
- for (int i = 0; i < mIndent; i++) {
- mOutput.append(" ");
- }
- if (mName != null) {
- mOutput.append(mName).append(": ");
- mName = null;
- }
- if (args.length == 0) {
- mOutput.append(in);
- } else {
- mOutput.append(String.format(in, args));
- }
- }
-
- @Override
- public void buildInline(Node node) {
- buildNode(node);
- }
-
- @Override
- public void buildBlock(Node node) {
- buildNode(node);
- }
-
- private void buildNode(Node node) {
- if (node == null) {
- mIndent++;
- return;
- }
- String name = node.getClass().getSimpleName();
- String description = "";
- if (node instanceof DescribedNode) {
- description = " " + ((DescribedNode) node).getDescription();
- }
-
- String typeDescription = "";
- String resolutionDescription = "";
- JavaParser.TypeDescriptor t = mResolver.getType(null, node);
- if (t != null) {
- typeDescription = ", type: " + t.getName();
- }
- ResolvedNode resolved = mResolver.resolve(null, node);
- if (resolved != null) {
- String c = "unknown";
- String extra = "";
- if (resolved instanceof ResolvedClass) {
- c = "class";
- } else if (resolved instanceof ResolvedMethod) {
- c = "method";
- ResolvedMethod method = (ResolvedMethod) resolved;
- extra = method.getContainingClass().getName();
- } else if (resolved instanceof ResolvedField) {
- c = "field";
- ResolvedField field = (ResolvedField) resolved;
- extra = field.getContainingClass().getName();
- } else if (resolved instanceof ResolvedVariable) {
- c = "variable";
- ResolvedVariable variable = (ResolvedVariable) resolved;
- extra = variable.getType().getName();
- } else if (resolved instanceof ResolvedAnnotation) {
- c = "annotation";
- }
- resolutionDescription = String.format(", resolved %1$s: %2$s %3$s",
- c, resolved.getName(), extra);
- }
-
- add("[%1$s%2$s]%3$s%4$s\n", name, description, typeDescription, resolutionDescription);
-
- mIndent++;
- }
-
- @Override
- public void fail(String fail) {
- Assert.fail(fail);
- }
-
- @Override
- public void property(String name, Object value) {
- add("PROPERTY: %s = %s\n", name, value);
- }
-
- @Override
- public void keyword(String text) {
- }
-
- @Override
- public void operator(String text) {
- }
-
- @Override
- public void verticalSpace() {
- }
-
- @Override
- public void space() {
- }
-
- @Override
- public void append(String text) {
- }
-
- @Override
- public void startSuppressBlock() {
- }
-
- @Override
- public void endSuppressBlock() {
- }
-
- @Override
- public void startSuppressIndent() {
- }
-
- @Override
- public void endSuppressIndent() {
- }
-
- @Override
- public void closeInline() {
- mIndent--;
- }
-
- @Override
- public void closeBlock() {
- mIndent--;
- }
-
- @Override
- public void addError(int start, int end, String message) {
- fail(message);
- }
-
- @Override
- public String finish() {
- return mOutput.toString();
- }
-
- @Override
- public void setTimeTaken(long taken) {
- }
-
- @Override
- public void nameNextElement(String name) {
- mName = name;
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java
deleted file mode 100644
index 774a4c8..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import static com.android.tools.lint.ExternalAnnotationRepository.FN_ANNOTATIONS_XML;
-import static com.google.common.base.Charsets.UTF_8;
-import static java.io.File.separatorChar;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.testutils.SdkTestCase;
-import com.android.tools.lint.client.api.JavaParser.DefaultTypeDescriptor;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
-import com.android.tools.lint.client.api.JavaParser.ResolvedField;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
-import com.android.tools.lint.client.api.JavaParser.ResolvedPackage;
-import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtilsTest;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-
-public class ExternalAnnotationRepositoryTest extends SdkTestCase {
-
- @Nullable
- private ExternalAnnotationRepository getSdkAnnotations() {
- File annotations = findSrcRelativeDir("tools/adt/idea/android/annotations");
- if (annotations != null) {
- List<File> files = Collections.singletonList(annotations);
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.create(null, files);
- assertNotNull(manager);
- return manager;
- } else {
- // Can't find it when running from Gradle; ignore for now
- //fail("Could not find annotations database");
- }
-
- return null;
- }
-
- @Nullable
- private ExternalAnnotationRepository getExternalAnnotations(@NonNull String pkg,
- @NonNull String contents) throws IOException {
- File dir = Files.createTempDir();
- try {
- File pkgDir = new File(dir, pkg.replace('.', separatorChar));
- boolean mkdirs = pkgDir.mkdirs();
- assertTrue(mkdirs);
- Files.write(contents, new File(pkgDir, FN_ANNOTATIONS_XML), UTF_8);
-
- List<File> files = Collections.singletonList(dir);
- ExternalAnnotationRepository manager = ExternalAnnotationRepository.create(null, files);
- assertNotNull(manager);
- return manager;
-
- } finally {
- deleteFile(dir);
- }
- }
-
- public void testFields() throws Exception {
- ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"android.graphics.Color\">\n"
- + " <annotation name=\"android.support.annotation.Annotation1\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Color BLUE\">\n"
- + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Color TRANSPARENT\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- ResolvedClass cls = createClass("android.graphics.Color");
- assertNotNull(manager.getAnnotation(cls, "android.support.annotation.Annotation1"));
- ResolvedField blueField = createField("android.graphics.Color", "BLUE");
- ResolvedField transparentField = createField("android.graphics.Color", "TRANSPARENT");
- assertNotNull(manager.getAnnotation(blueField, "android.support.annotation.Annotation3"));
- assertNull(manager.getAnnotation(blueField, "android.support.annotation.Annotation4"));
- assertNull(manager.getAnnotation(blueField, "android.support.annotation.Annotation5"));
-
- assertNotNull(manager.getAnnotation(transparentField, "android.support.annotation.Annotation5"));
- assertNull(manager.getAnnotation(transparentField, "android.support.annotation.Annotation3"));
- assertNull(manager.getAnnotation(transparentField, "android.support.annotation.Annotation4"));
- }
-
- public void testMethods1() throws Exception {
- ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"android.graphics.Color int HSVToColor(float[]) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Color int HSVToColor(int, float[]) 1\">\n"
- + " <annotation name=\"android.support.annotation.Annotation7\">\n"
- + " <val name=\"value\" val=\"3\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Color int alpha(int)\">\n"
- + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Color int argb(int, int, int, int)\">\n"
- + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.RadialGradient RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode) 4\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.ArrayAdapter ArrayAdapter(android.content.Context, int, int, java.util.List<T>) 3\">\n"
- + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
- + " </item>"
- + "</root>\n");
- assertNotNull(manager);
- ResolvedMethod method1 = createMethod("android.graphics.Color", "int", "HSVToColor",
- "float[]");
- assertNotNull(manager.getAnnotation(method1, 0, "android.support.annotation.Annotation5"));
-
- // Generic types
- ResolvedMethod method2 = createConstructor("android.graphics.ArrayAdapter", "ArrayAdapter",
- "android.content.Context, int, int, java.util.List<T>");
- assertNotNull(manager.getAnnotation(method2, 3, "android.support.annotation.Annotation4"));
-
- // Raw types
- method2 = createConstructor("android.graphics.ArrayAdapter", "ArrayAdapter",
- "android.content.Context, int, int, java.util.List");
- assertNotNull(manager.getAnnotation(method2, 3, "android.support.annotation.Annotation4"));
- }
-
- public void testMethods2() throws Exception {
- ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"test.pkg.Test java.lang.Object myMethod()\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " <annotation name=\"android.support.annotation.Annotation6\">\n"
- + " <val name=\"suggest\" val=\""#other(String,String)"\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test java.lang.Object myMethod(int) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation7\">\n"
- + " <val name=\"value\" val=\"3\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test java.lang.Object myMethod(int[]) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test java.lang.Object myMethod(int,java.lang.Object) 1\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test java.lang.Object myMethod(android.content.Context, int, int, java.util.List<T>) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test java.lang.Object myMethod(android.content.Context, int, int, java.util.List<T>) 1\">\n"
- + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test java.lang.Object myMethod(java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.String>>,int) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- ResolvedMethod method;
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod", "");
- assertNotNull(manager.getAnnotation(method, "android.support.annotation.Annotation5"));
- assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
- assertNotNull(manager.getAnnotation(method, "android.support.annotation.Annotation6"));
-
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod", "int");
- assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
- assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation7"));
-
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod", "int[]");
- assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
- assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation3"));
- assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
- assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation3"));
-
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
- "int,java.lang.Object");
- assertNotNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation5"));
-
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
- "android.content.Context, int, int, java.util.List<T>");
- assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
- assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation5"));
- assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
- assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation3"));
- assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation5"));
- assertNotNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation3"));
- assertNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation5"));
-
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
- "android.content.Context, int, int, java.util.List");
- assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation5"));
- assertNotNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation3"));
-
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
- Arrays.asList("java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.String>>",
- "int"), false);
- assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
- method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
- "java.util.Map,int");
- assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
- }
-
- // test intdef!
-
-
- public void testAnnotationAttributes() throws Exception {
- ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"android.graphics.Color int HSVToColor(float[]) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation7\">\n"
- + " <val name=\"value\" val=\"3\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Color int HSVToColor(int, float[]) 1\">\n"
- + " <annotation name=\"android.support.annotation.Annotation7\">\n"
- + " <val name=\"value\" val=\"3\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Canvas void drawLines(float[], android.graphics.Paint) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation7\">\n"
- + " <val name=\"min\" val=\"4\" />\n"
- + " <val name=\"multiple\" val=\"2\" />\n"
- + " </annotation>\n"
- + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Canvas int saveLayer(android.graphics.RectF, android.graphics.Paint, int) 2\">\n"
- + " <annotation name=\"android.support.annotation.Annotation8\">\n"
- + " <val name=\"value\" val=\"{android.graphics.Canvas.MATRIX_SAVE_FLAG, android.graphics.Canvas.CLIP_SAVE_FLAG, android.graphics.Canvas.HAS_ALPHA_LAYER_SAVE_FLAG, android.graphics.Canvas.FULL_COLOR_LAYER_SAVE_FLAG, android.graphics.Canvas.CLIP_TO_LAYER_SAVE_FLAG, android.graphics.Canvas.ALL_SAVE_FLAG}\" />\n"
- + " <val name=\"flag\" val=\"true\" />\n"
- + " </annotation>\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- ResolvedMethod method;
- ResolvedAnnotation annotation;
-
- // Size 1
- method = createMethod("android.graphics.Color", "int", "HSVToColor", "int, float[]");
- assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation7"));
- assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation7"));
- annotation = manager.getAnnotation(method, 1, "android.support.annotation.Annotation7");
- assertNotNull(annotation);
- assertEquals(3L, annotation.getValue());
- assertEquals(3L, annotation.getValue("value"));
- //noinspection ConstantConditions
- assertEquals(3, ((Number)annotation.getValue("value")).intValue());
- assertNotNull(annotation);
-
- // Size 2
- method = createMethod("android.graphics.Canvas", "void", "drawLines", "float[], android.graphics.Paint");
- assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
- annotation = manager.getAnnotation(method, 0, "android.support.annotation.Annotation7");
- assertNotNull(annotation);
- assertEquals(4L, annotation.getValue("min"));
- assertEquals(2L, annotation.getValue("multiple"));
- assertNotNull(annotation);
-
- // Intdef
- method = createMethod("android.graphics.Canvas", "int", "saveLayer",
- "android.graphics.RectF, android.graphics.Paint, int");
- annotation = manager.getAnnotation(method, 2, "android.support.annotation.Annotation8");
- assertNotNull(annotation);
- assertEquals(true, annotation.getValue("flag"));
- Object[] values = (Object[]) annotation.getValue("value");
- assertNotNull(values);
- assertEquals(6, values.length);
- assertTrue(values[0] instanceof ResolvedField);
- assertFalse(values[0].equals(createField("android.graphics.Canvas", "WRONG_NAME")));
- assertEquals(values[0], createField("android.graphics.Canvas", "MATRIX_SAVE_FLAG"));
- assertEquals(values[1], createField("android.graphics.Canvas", "CLIP_SAVE_FLAG"));
- assertEquals(values[2], createField("android.graphics.Canvas", "HAS_ALPHA_LAYER_SAVE_FLAG"));
- assertEquals(values[3], createField("android.graphics.Canvas", "FULL_COLOR_LAYER_SAVE_FLAG"));
- assertEquals(values[4], createField("android.graphics.Canvas", "CLIP_TO_LAYER_SAVE_FLAG"));
- assertEquals(values[5], createField("android.graphics.Canvas", "ALL_SAVE_FLAG"));
-
- ResolvedField field = (ResolvedField)values[0];
- assertEquals("android.graphics.Canvas.MATRIX_SAVE_FLAG", field.getSignature());
- assertEquals("android.graphics.Canvas", field.getContainingClassName());
- assertEquals("MATRIX_SAVE_FLAG", field.getName());
- }
-
- public void testConstructors() throws Exception {
- ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"android.graphics.RadialGradient RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode) 4\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- ResolvedMethod method = createConstructor("android.graphics.RadialGradient",
- "RadialGradient",
- "float, float, float, int[], float[], android.graphics.Shader.TileMode");
- assertNull(manager.getAnnotation(method, 4, "android.support.annotation.Annotation4"));
- assertNotNull(manager.getAnnotation(method, 4, "android.support.annotation.Annotation5"));
- }
-
- public void testVarArgs() throws Exception {
- ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"android.graphics.RadialGradient RadialGradient(float, float, float, int...) 3\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + " <item name=\"android.graphics.Bitmap android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]) 2\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- // Match "..." in external annotation with ... in code lookup
- ResolvedMethod method = createConstructor("android.graphics.RadialGradient",
- "RadialGradient",
- "float, float, float, int...");
- assertNotNull(manager.getAnnotation(method, 3, "android.support.annotation.Annotation5"));
- // Match "..." in external annotation with [] in code lookup
- method = createConstructor("android.graphics.RadialGradient",
- "RadialGradient",
- "float, float, float, int[]");
- assertNotNull(manager.getAnnotation(method, 3, "android.support.annotation.Annotation5"));
-
- // Match "[]" in external annotation with [] in code lookup
- method = createMethod("android.graphics.Bitmap",
- "android.graphics.Bitmap",
- "extractAlpha",
- "android.graphics.Paint, int[]");
- assertNotNull(manager.getAnnotation(method, 2, "android.support.annotation.Annotation5"));
-
- // Match "[]" in external annotation with ... in code lookup
- method = createMethod("android.graphics.Bitmap",
- "android.graphics.Bitmap",
- "extractAlpha",
- "android.graphics.Paint, int...");
- assertNotNull(manager.getAnnotation(method, 2, "android.support.annotation.Annotation5"));
- }
-
- public void testPackage() throws Exception {
- ExternalAnnotationRepository manager = getExternalAnnotations("foo.bar.baz", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"foo.bar.baz.package-info\">\n"
- + " <annotation name=\"my.pkg.MyAnnotation\"/>\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- ResolvedClass cls = createClass("foo.bar.baz.AdView");
- ResolvedPackage pkg = cls.getPackage();
- assertNotNull(pkg);
- assertNull(manager.getAnnotation(pkg, "foo.bar.Baz"));
- assertNotNull(manager.getAnnotation(pkg, "my.pkg.MyAnnotation"));
- }
-
- public void testMatchWithEcj() throws Exception {
- try {
- ExternalAnnotationRepository manager = getExternalAnnotations("test.pkg", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"test.pkg.Test\">\n"
- + " <annotation name=\"android.support.annotation.Annotation1\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test.Inner\">\n"
- + " <annotation name=\"android.support.annotation.Annotation2\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test void foo(int, int[], int...)\">\n"
- + " <annotation name=\"android.support.annotation.Annotation6\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test void foo(int, int[], int...) 0\">\n"
- + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test void foo(int, int[], int...) 1\">\n"
- + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
- + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test void foo(int, int[], int...) 2\">\n"
- + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- ExternalAnnotationRepository.set(manager);
-
- String source =
- "package test.pkg;\n" +
- "\n" +
- "public class Test {\n" +
- " public void foo(int a, int[] b, int... c) {\n" +
- " }\n" +
- " public static class Inner {\n" +
- " }\n" +
- "}\n";
-
- final JavaContext context = LintUtilsTest.parse(source,
- new File("src/test/pkg/Test.java"));
- assertNotNull(context);
- Node unit = context.getCompilationUnit();
- assertNotNull(unit);
- unit.accept(new ForwardingAstVisitor() {
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- ResolvedNode resolved = context.resolve(node);
- assertNotNull(resolved);
- assertTrue(resolved.getClass().getName(), resolved instanceof ResolvedClass);
- ResolvedClass cls = (ResolvedClass) resolved;
- if (cls.getName().endsWith(".Inner")) {
- assertNull(cls.getAnnotation("android.support.annotation.Annotation1"));
- assertNotNull(cls.getAnnotation("android.support.annotation.Annotation2"));
- } else {
- assertNotNull(cls.getAnnotation("android.support.annotation.Annotation1"));
- assertNull(cls.getAnnotation("android.support.annotation.Annotation2"));
- }
-
- return super.visitClassDeclaration(node);
- }
-
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- ResolvedNode resolved = context.resolve(node);
- assertNotNull(resolved);
- assertTrue(resolved.getClass().getName(), resolved instanceof ResolvedMethod);
- ResolvedMethod method = (ResolvedMethod) resolved;
- assertNull(method.getAnnotation("android.support.annotation.Annotation5"));
- assertNotNull(method.getAnnotation("android.support.annotation.Annotation6"));
- assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation3", 0));
- assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation4", 1));
- assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation3", 1));
- assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation5", 2));
-
- return super.visitMethodDeclaration(node);
- }
- });
- } finally {
- ExternalAnnotationRepository.set(null);
- }
- }
-
- public void testMergeParameters() throws Exception {
- try {
- ExternalAnnotationRepository manager = getExternalAnnotations("test.pkg", ""
- + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<root>\n"
- + " <item name=\"test.pkg.Test.Parent void testMethod(int)\">\n"
- + " <annotation name=\"android.support.annotation.Annotation1\" />\n"
- + " </item>\n"
- + " <item name=\"test.pkg.Test.Child void testMethod(int)\">\n"
- + " <annotation name=\"android.support.annotation.Annotation2\" />\n"
- + " </item>\n"
- + "</root>\n");
- assertNotNull(manager);
- ExternalAnnotationRepository.set(manager);
-
- String source = ""
- + "package test.pkg;\n"
- + "\n"
- + "public class Test {\n"
- + " public void test(Child child) {\n"
- + " child.testMethod(5);\n"
- + " }\n"
- + "\n"
- + " public static class Parent {\n"
- + " public void testMethod(int parameter) {\n"
- + " }\n"
- + " }\n"
- + "\n"
- + " public static class Child extends Parent {\n"
- + " @Override\n"
- + " public void testMethod(int parameter) {\n"
- + " }\n"
- + " }\n"
- + "}\n";
-
- final JavaContext context = LintUtilsTest.parse(source,
- new File("src/test/pkg/Test.java"));
- assertNotNull(context);
- Node unit = context.getCompilationUnit();
- assertNotNull(unit);
- final AtomicBoolean foundMethod = new AtomicBoolean();
- unit.accept(new ForwardingAstVisitor() {
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- foundMethod.set(true);
- assertEquals("testMethod", node.astName().astValue());
- ResolvedNode resolved = context.resolve(node);
- assertTrue(resolved instanceof ResolvedMethod);
- ResolvedMethod method = (ResolvedMethod)resolved;
- List<ResolvedAnnotation> annotations =
- Lists.newArrayList(method.getAnnotations());
- assertEquals(3, annotations.size());
- Collections.sort(annotations,
- new Comparator<ResolvedAnnotation>() {
- @Override
- public int compare(ResolvedAnnotation a1,
- ResolvedAnnotation a2) {
- return a1.getName().compareTo(a2.getName());
- }
- });
- assertEquals("android.support.annotation.Annotation1", annotations.get(0).getName());
- assertEquals("android.support.annotation.Annotation2", annotations.get(1).getName());
- assertEquals("java.lang.Override", annotations.get(2).getName());
- return super.visitMethodInvocation(node);
- }
- });
- assertTrue(foundMethod.get());
- } finally {
- ExternalAnnotationRepository.set(null);
- }
- }
-
- public void testSdkAnnotations() throws Exception {
- ExternalAnnotationRepository manager = getSdkAnnotations();
- if (manager == null) {
- // Can't find it when running from Gradle; ignore for now
- return;
- }
- ResolvedMethod method = createMethod("android.view.LayoutInflater", "android.view.View",
- "createView", "java.lang.String, java.lang.String, android.util.AttributeSet");
- assertNotNull(manager.getAnnotation(method, 2, "android.support.annotation.NonNull"));
- }
-
- private static ResolvedClass createClass(String name) {
- ResolvedClass mock = mock(ResolvedClass.class);
- when(mock.getName()).thenReturn(name);
- when(mock.getSignature()).thenReturn(name);
- assertTrue(name, name.indexOf('.') != -1);
- ResolvedPackage pkg = createPackage(name.substring(0, name.lastIndexOf('.')));
- when(mock.getPackage()).thenReturn(pkg);
- return mock;
- }
-
- private static ResolvedMethod createConstructor(String containingClass, String name,
- String parameters) {
- return createMethod(containingClass, null, name, parameters, true);
- }
-
- public static ResolvedMethod createMethod(String containingClass, String returnType,
- String name, String parameters) {
- return createMethod(containingClass, returnType, name, parameters, false);
- }
-
- public static ResolvedMethod createMethod(String containingClass, String returnType,
- String name, String parameters, boolean isConstructor) {
- return createMethod(containingClass, returnType, name,
- Splitter.on(',').trimResults().split(parameters), isConstructor);
- }
-
- public static ResolvedMethod createMethod(String containingClass, String returnType,
- String name, Iterable<String> parameters, boolean isConstructor) {
- ResolvedMethod mock = mock(ResolvedMethod.class);
- when(mock.isConstructor()).thenReturn(isConstructor);
- when(mock.getName()).thenReturn(name);
- if (!isConstructor) {
- DefaultTypeDescriptor typeDescriptor = new DefaultTypeDescriptor(returnType);
- when(mock.getReturnType()).thenReturn(typeDescriptor);
- }
- ResolvedClass cls = createClass(containingClass);
- when(mock.getContainingClass()).thenReturn(cls);
- int index = 0;
- for (String argument : parameters) {
- TypeDescriptor typeDescriptor = new DefaultTypeDescriptor(argument);
- when(mock.getArgumentType(index)).thenReturn(typeDescriptor);
- index++;
- }
- when(mock.getArgumentCount()).thenReturn(index);
- return mock;
- }
-
- public static ResolvedField createField(String containingClass, String name) {
- ResolvedField mock = mock(ResolvedField.class);
- when(mock.getName()).thenReturn(name);
- ResolvedClass cls = createClass(containingClass);
- when(mock.getContainingClass()).thenReturn(cls);
- return mock;
- }
-
- public static ResolvedPackage createPackage(String pkgName) {
- ResolvedPackage mock = mock(ResolvedPackage.class);
- when(mock.getName()).thenReturn(pkgName);
- return mock;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
deleted file mode 100644
index d53c03d..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.checks.AbstractCheckTest;
-import com.android.tools.lint.checks.HardcodedValuesDetector;
-import com.android.tools.lint.checks.ManifestDetector;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.detector.api.DefaultPosition;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class MultiProjectHtmlReporterTest extends AbstractCheckTest {
- public void test() throws Exception {
- File dir = new File(getTargetDir(), "report");
- try {
- LintCliClient client = new LintCliClient() {
- @Override
- IssueRegistry getRegistry() {
- if (mRegistry == null) {
- mRegistry = new IssueRegistry() {
- @NonNull
- @Override
- public List<Issue> getIssues() {
- return Arrays.asList(
- ManifestDetector.USES_SDK,
- HardcodedValuesDetector.ISSUE,
- // Not reported, but for the disabled-list
- ManifestDetector.MOCK_LOCATION);
- }
- };
- }
- return mRegistry;
- }
- };
-
- //noinspection ResultOfMethodCallIgnored
- dir.mkdirs();
- MultiProjectHtmlReporter reporter = new MultiProjectHtmlReporter(client, dir);
- Project project = Project.create(client, new File("/foo/bar/Foo"),
- new File("/foo/bar/Foo"));
-
- Warning warning1 = new Warning(ManifestDetector.USES_SDK,
- "<uses-sdk> tag should specify a target API level (the highest verified " +
- "version; when running on later versions, compatibility behaviors may " +
- "be enabled) with android:targetSdkVersion=\"?\"",
- Severity.WARNING, project);
- warning1.line = 6;
- warning1.file = new File("/foo/bar/Foo/AndroidManifest.xml");
- warning1.errorLine = " <uses-sdk android:minSdkVersion=\"8\" />\n ^\n";
- warning1.path = "AndroidManifest.xml";
- warning1.location = Location.create(warning1.file,
- new DefaultPosition(6, 4, 198), new DefaultPosition(6, 42, 236));
-
- Warning warning2 = new Warning(HardcodedValuesDetector.ISSUE,
- "[I18N] Hardcoded string \"Fooo\", should use @string resource",
- Severity.WARNING, project);
- warning2.line = 11;
- warning2.file = new File("/foo/bar/Foo/res/layout/main.xml");
- warning2.errorLine = " (java.lang.String) android:text=\"Fooo\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~\n";
- warning2.path = "res/layout/main.xml";
- warning2.location = Location.create(warning2.file,
- new DefaultPosition(11, 8, 377), new DefaultPosition(11, 27, 396));
-
- List<Warning> warnings = new ArrayList<Warning>();
- warnings.add(warning1);
- warnings.add(warning2);
-
- reporter.write(0, 2, warnings);
-
- String report = Files.toString(new File(dir, "index.html"), Charsets.UTF_8);
-
- // Replace the timestamp to make golden file comparison work
- String timestampPrefix = "Check performed at ";
- int begin = report.indexOf(timestampPrefix);
- assertTrue(begin != -1);
- begin += timestampPrefix.length();
- int end = report.indexOf(".<br/>", begin);
- assertTrue(end != -1);
- report = report.substring(0, begin) + "$DATE" + report.substring(end);
-
- // NOTE: If you change the output, please validate it manually in
- // http://validator.w3.org/#validate_by_input
- // before updating the following
- assertEquals(""
- + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
- + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
- + "<head>\n"
- + "<title>Lint Report</title>\n"
- + "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://fonts.googleapis.com/css?family=Roboto\" />\n"
- + "<link rel=\"stylesheet\" type=\"text/css\" href=\"index_files/hololike.css\" />\n"
- + "</head>\n"
- + "<body>\n"
- + "<h1>Lint Report</h1>\n"
- + "<div class=\"titleSeparator\"></div>\n"
- + "Check performed at $DATE.<br/>\n"
- + "0 errors and 2 warnings found:\n"
- + "<br/><br/>\n"
- + "<table class=\"overview\">\n"
- + "<tr><th>Project</th><th class=\"countColumn\"><img border=\"0\" align=\"top\" src=\"index_files/lint-error.png\" alt=\"Error\" />\n"
- + "Errors</th><th class=\"countColumn\"><img border=\"0\" align=\"top\" src=\"index_files/lint-warning.png\" alt=\"Warning\" />\n"
- + "Warnings</th></tr>\n"
- + "<tr><td><a href=\"Foo.html\">Foo</a></td><td class=\"countColumn\">0</td><td class=\"countColumn\">2</td></tr>\n"
- + "</table>\n"
- + "</body>\n"
- + "</html>\n",
- report);
-
- assertTrue(new File(dir, "index_files" + File.separator + "hololike.css").exists());
- assertTrue(new File(dir, "index_files" + File.separator + "lint-warning.png").exists());
- assertTrue(new File(dir, "index_files" + File.separator + "lint-error.png").exists());
- assertTrue(new File(dir, "Foo.html").exists());
- } finally {
- //noinspection ResultOfMethodCallIgnored
- dir.delete();
- }
- }
-
- @Override
- protected Detector getDetector() {
- fail("Not used in this test");
- return null;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java
deleted file mode 100644
index 46b567d..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint;
-
-import com.android.tools.lint.checks.AbstractCheckTest;
-import com.android.tools.lint.checks.UnusedResourceDetector;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.LintRequest;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class WarningTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new UnusedResourceDetector();
- }
-
- @Override
- protected boolean isEnabled(Issue issue) {
- return true;
- }
-
- public void testComparator() throws Exception {
- File projectDir = getProjectDir(null, // Rename .txt files to .java
- "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
- "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
- "AndroidManifest.xml",
- "res/layout/accessibility.xml");
-
- final AtomicReference<List<Warning>> warningsHolder = new AtomicReference<List<Warning>>();
- TestLintClient lintClient = new TestLintClient() {
- @Override
- public String analyze(List<File> files) throws Exception {
- //String analyze = super.analyze(files);
- mDriver = new LintDriver(new CustomIssueRegistry(), this);
- configureDriver(mDriver);
- mDriver.analyze(new LintRequest(this, files).setScope(getLintScope(files)));
- warningsHolder.set(mWarnings);
- return null;
- }
- };
- List<File> files = Collections.singletonList(projectDir);
- lintClient.analyze(files);
-
- List<Warning> warnings = warningsHolder.get();
- Warning prev = null;
- for (Warning warning : warnings) {
- if (prev != null) {
- boolean equals = warning.equals(prev);
- assertEquals(equals, prev.equals(warning));
- int compare = warning.compareTo(prev);
- assertEquals(equals, compare == 0);
- assertEquals(-compare, prev.compareTo(warning));
- }
- prev = warning;
- }
-
- Collections.sort(warnings);
-
- Warning prev2 = prev;
- prev = null;
- for (Warning warning : warnings) {
- if (prev != null && prev2 != null) {
- assertTrue(warning.compareTo(prev) > 0);
- assertTrue(prev.compareTo(prev2) > 0);
- assertTrue(warning.compareTo(prev2) > 0);
-
- assertTrue(prev.compareTo(warning) < 0);
- assertTrue(prev2.compareTo(prev) < 0);
- assertTrue(prev2.compareTo(warning) < 0);
- }
- prev2 = prev;
- prev = warning;
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java
deleted file mode 100644
index 1831953..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-
-import java.util.List;
-
- at SuppressWarnings("javadoc")
-public class AnnotationDetectorTest extends AbstractCheckTest {
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/WrongAnnotation.java:9: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
- " public static void foobar(View view, @SuppressLint(\"NewApi\") int foo) { // Invalid: class-file check\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/WrongAnnotation.java:10: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
- " @SuppressLint(\"NewApi\") // Invalid\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/WrongAnnotation.java:12: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
- " @SuppressLint({\"SdCardPath\", \"NewApi\"}) // Invalid: class-file based check on local variable\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/WrongAnnotation.java:14: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
- " @android.annotation.SuppressLint({\"SdCardPath\", \"NewApi\"}) // Invalid (FQN)\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/WrongAnnotation.java:28: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
- " @SuppressLint(\"NewApi\")\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "5 errors, 0 warnings\n",
-
- lintProject(
- "src/test/pkg/WrongAnnotation.java.txt=>src/test/pkg/WrongAnnotation.java"
- ));
- }
-
- @SuppressWarnings("ClassNameDiffersFromFileName")
- public void testUniqueValues() throws Exception {
- assertEquals(""
- + "src/test/pkg/IntDefTest.java:9: Error: Constants STYLE_NO_INPUT and STYLE_NO_FRAME specify the same exact value (2); this is usually a cut & paste or merge error [UniqueConstants]\n"
- + " @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})\n"
- + " ~~~~~~~~~~~~~~\n"
- + " src/test/pkg/IntDefTest.java:9: Previous same value\n"
- + "src/test/pkg/IntDefTest.java:28: Error: Constants FLAG3 and FLAG2 specify the same exact value (562949953421312); this is usually a cut & paste or merge error [UniqueConstants]\n"
- + " @IntDef({FLAG2, FLAG3, FLAG1})\n"
- + " ~~~~~\n"
- + " src/test/pkg/IntDefTest.java:28: Previous same value\n"
- + "2 errors, 0 warnings\n",
-
- lintProject(
- java("src/test/pkg/IntDefTest.java", ""
- + "package test.pkg;\n"
- + "import android.support.annotation.IntDef;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.RetentionPolicy;\n"
- + "\n"
- + "@SuppressLint(\"UnusedDeclaration\")\n"
- + "public class IntDefTest {\n"
- + " @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface DialogStyle {}\n"
- + "\n"
- + " public static final int STYLE_NORMAL = 0;\n"
- + " public static final int STYLE_NO_TITLE = 1;\n"
- + " public static final int STYLE_NO_FRAME = 2;\n"
- + " public static final int STYLE_NO_INPUT = 2;\n"
- + "\n"
- + " @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})\n"
- + " @SuppressWarnings(\"UniqueConstants\")\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface SuppressedDialogStyle {}\n"
- + "\n"
- + "\n"
- + " public static final long FLAG1 = 0x100000000000L;\n"
- + " public static final long FLAG2 = 0x0002000000000000L;\n"
- + " public static final long FLAG3 = 0x2000000000000L;\n"
- + "\n"
- + " @IntDef({FLAG2, FLAG3, FLAG1})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface Flags {}\n"
- + "\n"
-
- + ""
- + "}"),
- copy("src/android/support/annotation/IntDef.java.txt",
- "src/android/support/annotation/IntDef.java")));
- }
-
- @SuppressWarnings("ClassNameDiffersFromFileName")
- public void testFlagStyle() throws Exception {
- assertEquals(""
- + "src/test/pkg/IntDefTest.java:13: Warning: Consider declaring this constant using 1 << 44 instead [ShiftFlags]\n"
- + " public static final long FLAG5 = 0x100000000000L;\n"
- + " ~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:14: Warning: Consider declaring this constant using 1 << 49 instead [ShiftFlags]\n"
- + " public static final long FLAG6 = 0x0002000000000000L;\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:15: Warning: Consider declaring this constant using 1 << 3 instead [ShiftFlags]\n"
- + " public static final long FLAG7 = 8L;\n"
- + " ~~\n"
- + "0 errors, 3 warnings\n",
- lintProject(
- java("src/test/pkg/IntDefTest.java", ""
- + "package test.pkg;\n"
- + "import android.support.annotation.IntDef;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.RetentionPolicy;\n"
- + "\n"
- + "@SuppressWarnings(\"unused\")\n"
- + "public class IntDefTest {\n"
- + " public static final long FLAG1 = 1;\n"
- + " public static final long FLAG2 = 2;\n"
- + " public static final long FLAG3 = 1 << 2;\n"
- + " public static final long FLAG4 = 1 << 3;\n"
- + " public static final long FLAG5 = 0x100000000000L;\n"
- + " public static final long FLAG6 = 0x0002000000000000L;\n"
- + " public static final long FLAG7 = 8L;\n"
- + " public static final long FLAG8 = 9L;\n"
- + " public static final long FLAG9 = 0;\n"
- + " public static final long FLAG10 = 1;\n"
- + " public static final long FLAG11 = -1;\n"
- + "\n"
- // Not a flag (missing flag=true)
- + " @IntDef({FLAG1, FLAG2, FLAG3})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface Flags1 {}\n"
- + "\n"
- // OK: Too few values
- + " @IntDef(flag = true, value={FLAG1, FLAG2})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface Flags2 {}\n"
- + "\n"
- // OK: Allow 0, 1, -1
- + " @IntDef(flag = true, value={FLAG9, FLAG10, FLAG11})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface Flags3 {}\n"
- + "\n"
- // OK: Already using shifts
- + " @IntDef(flag = true, value={FLAG1, FLAG3, FLAG4})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface Flags4 {}\n"
- + "\n"
- // Wrong: should be flagged
- + " @IntDef(flag = true, value={FLAG5, FLAG6, FLAG7, FLAG8})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " private @interface Flags5 {}\n"
- + "}"),
- copy("src/android/support/annotation/IntDef.java.txt",
- "src/android/support/annotation/IntDef.java")));
- }
-
- @Override
- protected Detector getDetector() {
- return new AnnotationDetector();
- }
-
- @Override
- protected List<Issue> getIssues() {
- List<Issue> issues = super.getIssues();
-
- // Need these issues on to be found by the registry as well to look up scope
- // in id references (these ids are referenced in the unit test java file below)
- issues.add(ApiDetector.UNSUPPORTED);
- issues.add(SdCardDetector.ISSUE);
-
- return issues;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
deleted file mode 100644
index 1fb5069..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
+++ /dev/null
@@ -1,1774 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.checks.ApiDetector.INLINED;
-import static com.android.tools.lint.checks.ApiDetector.UNSUPPORTED;
-import static com.android.tools.lint.detector.api.TextFormat.TEXT;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-
-import java.io.File;
-
- at SuppressWarnings("javadoc")
-public class ApiDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new ApiDetector();
- }
-
- public void testXmlApi1() throws Exception {
- assertEquals(
- "res/color/colors.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
- " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
- " ^\n" +
- "res/layout/layout.xml:9: Error: View requires API level 5 (current min is 1): <QuickContactBadge> [NewApi]\n" +
- " <QuickContactBadge\n" +
- " ^\n" +
- "res/layout/layout.xml:15: Error: View requires API level 11 (current min is 1): <CalendarView> [NewApi]\n" +
- " <CalendarView\n" +
- " ^\n" +
- "res/layout/layout.xml:21: Error: View requires API level 14 (current min is 1): <GridLayout> [NewApi]\n" +
- " <GridLayout\n" +
- " ^\n" +
- "res/layout/layout.xml:22: Error: @android:attr/actionBarSplitStyle requires API level 14 (current min is 1) [NewApi]\n" +
- " foo=\"@android:attr/actionBarSplitStyle\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout.xml:23: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
- " bar=\"@android:color/holo_red_light\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/themes.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
- " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
- " ^\n" +
- "7 errors, 0 warnings\n" +
- "",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml"
- ));
- }
-
- public void testXmlApi2() throws Exception {
- assertEquals(""
- + "res/layout/textureview.xml:8: Error: View requires API level 14 (current min is 1): <TextureView> [NewApi]\n"
- + " <TextureView\n"
- + " ^\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "res/layout/textureview.xml=>res/layout/textureview.xml"
- ));
- }
-
- public void testTag() throws Exception {
- assertEquals(""
- + "res/layout/tag.xml:12: Warning: <tag> is only used in API level 21 and higher (current min is 1) [UnusedAttribute]\n"
- + " <tag id=\"@+id/test\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "res/layout/tag.xml=>res/layout/tag.xml"
- ));
- }
-
- public void testAttrWithoutSlash() throws Exception {
- assertEquals(""
- + "res/layout/attribute.xml:4: Error: ?android:indicatorStart requires API level 18 (current min is 1) [NewApi]\n"
- + " android:enabled=\"?android:indicatorStart\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/attribute.xml=>res/layout/attribute.xml"
- ));
- }
-
- public void testUnusedAttributes() throws Exception {
- assertEquals(""
- + "res/layout/divider.xml:9: Warning: Attribute showDividers is only used in API level 11 and higher (current min is 4) [UnusedAttribute]\n"
- + " android:showDividers=\"middle\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/layout/labelfor.xml",
- "res/layout/edit_textview.xml",
- "apicheck/divider.xml=>res/layout/divider.xml"
- ));
- }
-
- public void testUnusedOnSomeVersions1() throws Exception {
- assertEquals(""
- + "res/layout/attribute2.xml:4: Error: switchTextAppearance requires API level 14 (current min is 1), but note that attribute editTextColor is only used in API level 11 and higher [NewApi]\n"
- + " android:editTextColor=\"?android:switchTextAppearance\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/attribute2.xml:4: Warning: Attribute editTextColor is only used in API level 11 and higher (current min is 1) [UnusedAttribute]\n"
- + " android:editTextColor=\"?android:switchTextAppearance\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/attribute2.xml=>res/layout/attribute2.xml"
- ));
- }
-
- public void testXmlApi() throws Exception {
- assertEquals(""
- + "res/layout/attribute2.xml:4: Error: ?android:switchTextAppearance requires API level 14 (current min is 11) [NewApi]\n"
- + " android:editTextColor=\"?android:switchTextAppearance\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/minsdk11.xml=>AndroidManifest.xml",
- "apicheck/attribute2.xml=>res/layout/attribute2.xml"
- ));
- }
-
- public void testReportAttributeName() throws Exception {
- assertEquals("res/layout/layout.xml:13: Warning: Attribute layout_row is only used in API level 14 and higher (current min is 4) [UnusedAttribute]\n"
- + " android:layout_row=\"2\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/layoutattr.xml=>res/layout/layout.xml"
- ));
- }
-
- public void testXmlApi14() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml"
- ));
- }
-
- public void testXmlApiIceCreamSandwich() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minics.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml"
- ));
- }
-
- public void testXmlApi1TargetApi() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/layout_targetapi.xml=>res/layout/layout.xml"
- ));
- }
-
- public void testXmlApiFolderVersion11() throws Exception {
- assertEquals(
- "res/color-v11/colors.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
- " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
- " ^\n" +
- "res/layout-v11/layout.xml:21: Error: View requires API level 14 (current min is 1): <GridLayout> [NewApi]\n" +
- " <GridLayout\n" +
- " ^\n" +
- "res/layout-v11/layout.xml:22: Error: @android:attr/actionBarSplitStyle requires API level 14 (current min is 1) [NewApi]\n" +
- " foo=\"@android:attr/actionBarSplitStyle\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout-v11/layout.xml:23: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
- " bar=\"@android:color/holo_red_light\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values-v11/themes.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
- " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
- " ^\n" +
- "5 errors, 0 warnings\n" +
- "",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout-v11/layout.xml",
- "apicheck/themes.xml=>res/values-v11/themes.xml",
- "apicheck/themes.xml=>res/color-v11/colors.xml"
- ));
- }
-
- public void testXmlApiFolderVersion14() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout-v14/layout.xml",
- "apicheck/themes.xml=>res/values-v14/themes.xml",
- "apicheck/themes.xml=>res/color-v14/colors.xml"
- ));
- }
-
- public void testThemeVersion() throws Exception {
- assertEquals(""
- + "res/values/themes3.xml:3: Error: android:Theme.Holo.Light.DarkActionBar requires API level 14 (current min is 4) [NewApi]\n"
- + " <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/values/themes3.xml"
- ));
- }
-
- public void testApi1() throws Exception {
- assertEquals(
- "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
- " getActionBar(); // API 11\n" +
- " ~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
- " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:27: Error: Call requires API level 3 (current min is 1): android.widget.Chronometer#getOnChronometerTickListener [NewApi]\n" +
- " chronometer.getOnChronometerTickListener(); // API 3 \n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 1): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
- " chronometer.setTextIsSelectable(true); // API 11\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
- " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
- // Note: the above error range is wrong; should be pointing to the second
- "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
- " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
- " ~~~~~~~\n" +
- "7 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
- ));
- }
-
- public void testApi2() throws Exception {
- assertEquals(
- "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 2): android.app.Activity#getActionBar [NewApi]\n" +
- " getActionBar(); // API 11\n" +
- " ~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 2): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
- " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:27: Error: Call requires API level 3 (current min is 2): android.widget.Chronometer#getOnChronometerTickListener [NewApi]\n" +
- " chronometer.getOnChronometerTickListener(); // API 3 \n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 2): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
- " chronometer.setTextIsSelectable(true); // API 11\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 2): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
- " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 2): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 2): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
- " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
- " ~~~~~~~\n" +
- "7 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk2.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
- ));
- }
-
- public void testApi4() throws Exception {
- assertEquals(
- "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 4): android.app.Activity#getActionBar [NewApi]\n" +
- " getActionBar(); // API 11\n" +
- " ~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 4): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
- " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 4): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
- " chronometer.setTextIsSelectable(true); // API 11\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 4): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
- " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 4): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
- " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
- " ~~~~~~~\n" +
- "6 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
- ));
- }
-
- public void testApi10() throws Exception {
- assertEquals(
- "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 10): android.app.Activity#getActionBar [NewApi]\n" +
- " getActionBar(); // API 11\n" +
- " ~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 10): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
- " chronometer.setTextIsSelectable(true); // API 11\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 10): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
- " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 10): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 10): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
- " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
- " ~~~~~~~\n" +
- "5 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk10.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
- ));
- }
-
- public void testApi14() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
- ));
- }
-
- public void testInheritStatic() throws Exception {
- assertEquals(
- "src/foo/bar/ApiCallTest5.java:16: Error: Call requires API level 11 (current min is 2): android.view.View#resolveSizeAndState [NewApi]\n" +
- " int measuredWidth = View.resolveSizeAndState(widthMeasureSpec,\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest5.java:18: Error: Call requires API level 11 (current min is 2): android.view.View#resolveSizeAndState [NewApi]\n" +
- " int measuredHeight = resolveSizeAndState(heightMeasureSpec,\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest5.java:20: Error: Call requires API level 11 (current min is 2): android.view.View#combineMeasuredStates [NewApi]\n" +
- " View.combineMeasuredStates(0, 0);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest5.java:21: Error: Call requires API level 11 (current min is 2): android.view.View#combineMeasuredStates [NewApi]\n" +
- " ApiCallTest5.combineMeasuredStates(0, 0);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "4 errors, 0 warnings\n" +
- "",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk2.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest5.java.txt=>src/foo/bar/ApiCallTest5.java",
- "apicheck/ApiCallTest5.class.data=>bin/classes/foo/bar/ApiCallTest5.class"
- ));
- }
-
- public void testInheritLocal() throws Exception {
- // Test virtual dispatch in a local class which extends some other local class (which
- // in turn extends an Android API)
- assertEquals(
- "src/test/pkg/ApiCallTest3.java:10: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
- " getActionBar(); // API 11\n" +
- " ~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n" +
- "",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java",
- "apicheck/ApiCallTest3.java.txt=>src/test/pkg/ApiCallTest3.java",
- "apicheck/ApiCallTest3.class.data=>bin/classes/test/pkg/ApiCallTest3.class",
- "apicheck/Intermediate.class.data=>bin/classes/test/pkg/Intermediate.class"
- ));
- }
-
- public void testViewClassLayoutReference() throws Exception {
- assertEquals(
- "res/layout/view.xml:9: Error: View requires API level 5 (current min is 1): <QuickContactBadge> [NewApi]\n" +
- " <view\n" +
- " ^\n" +
- "res/layout/view.xml:16: Error: View requires API level 11 (current min is 1): <CalendarView> [NewApi]\n" +
- " <view\n" +
- " ^\n" +
- "res/layout/view.xml:24: Error: ?android:attr/dividerHorizontal requires API level 11 (current min is 1) [NewApi]\n" +
- " unknown=\"?android:attr/dividerHorizontal\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/view.xml:25: Error: ?android:attr/textColorLinkInverse requires API level 11 (current min is 1) [NewApi]\n" +
- " android:textColor=\"?android:attr/textColorLinkInverse\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "4 errors, 0 warnings\n" +
- "",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/view.xml=>res/layout/view.xml"
- ));
- }
-
- public void testIOException() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=35190
- assertEquals(
- "src/test/pkg/ApiCallTest6.java:8: Error: Call requires API level 9 (current min is 1): new java.io.IOException [NewApi]\n" +
- " IOException ioException = new IOException(throwable);\n" +
- " ~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java",
- "apicheck/ApiCallTest6.java.txt=>src/test/pkg/ApiCallTest6.java",
- "apicheck/ApiCallTest6.class.data=>bin/classes/test/pkg/ApiCallTest6.class"
- ));
- }
-
- // Test suppressing errors -- on classes, methods etc.
-
- public void testSuppress() throws Exception {
- assertEquals(
- // These errors are correctly -not- suppressed because they
- // appear in method3 (line 74-98) which is annotated with a
- // @SuppressLint annotation specifying only an unrelated issue id
-
- "src/foo/bar/SuppressTest1.java:76: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
- " getActionBar(); // API 11\n" +
- " ~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:80: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
- " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:83: Error: Call requires API level 3 (current min is 1): android.widget.Chronometer#getOnChronometerTickListener [NewApi]\n" +
- " chronometer.getOnChronometerTickListener(); // API 3\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:86: Error: Call requires API level 11 (current min is 1): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
- " chronometer.setTextIsSelectable(true); // API 11\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:89: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
- " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:94: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:97: Error: Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
- " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
- " ~~~~~~~\n" +
-
- // Note: These annotations are within the methods, not ON the methods, so they have
- // no effect (because they don't end up in the bytecode)
-
-
- "src/foo/bar/SuppressTest4.java:19: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = report.batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
- "8 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/SuppressTest1.java.txt=>src/foo/bar/SuppressTest1.java",
- "apicheck/SuppressTest1.class.data=>bin/classes/foo/bar/SuppressTest1.class",
- "apicheck/SuppressTest2.java.txt=>src/foo/bar/SuppressTest2.java",
- "apicheck/SuppressTest2.class.data=>bin/classes/foo/bar/SuppressTest2.class",
- "apicheck/SuppressTest3.java.txt=>src/foo/bar/SuppressTest3.java",
- "apicheck/SuppressTest3.class.data=>bin/classes/foo/bar/SuppressTest3.class",
- "apicheck/SuppressTest4.java.txt=>src/foo/bar/SuppressTest4.java",
- "apicheck/SuppressTest4.class.data=>bin/classes/foo/bar/SuppressTest4.class"
- ));
- }
-
- public void testSuppressInnerClasses() throws Exception {
- assertEquals(
- // These errors are correctly -not- suppressed because they
- // appear outside the middle inner class suppressing its own errors
- // and its child's errors
- "src/test/pkg/ApiCallTest4.java:9: Error: Call requires API level 14 (current min is 1): new android.widget.GridLayout [NewApi]\n" +
- " new GridLayout(null, null, 0);\n" +
- " ~~~~~~~~~~\n" +
- "src/test/pkg/ApiCallTest4.java:38: Error: Call requires API level 14 (current min is 1): new android.widget.GridLayout [NewApi]\n" +
- " new GridLayout(null, null, 0);\n" +
- " ~~~~~~~~~~\n" +
- "2 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest4.java.txt=>src/test/pkg/ApiCallTest4.java",
- "apicheck/ApiCallTest4.class.data=>bin/classes/test/pkg/ApiCallTest4.class",
- "apicheck/ApiCallTest4$1.class.data=>bin/classes/test/pkg/ApiCallTest4$1.class",
- "apicheck/ApiCallTest4$InnerClass1.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass1.class",
- "apicheck/ApiCallTest4$InnerClass2.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass2.class",
- "apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass1$InnerInnerClass1.class"
- ));
- }
-
- public void testApiTargetAnnotation() throws Exception {
- assertEquals(
- "src/foo/bar/ApiTargetTest.java:13: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
- " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiTargetTest.java:25: Error: Class requires API level 8 (current min is 4): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
- " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiTargetTest.java:39: Error: Class requires API level 8 (current min is 7): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
- " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "3 errors, 0 warnings\n" +
- "",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/ApiTargetTest.java.txt=>src/foo/bar/ApiTargetTest.java",
- "apicheck/ApiTargetTest.class.data=>bin/classes/foo/bar/ApiTargetTest.class",
- "apicheck/ApiTargetTest$LocalClass.class.data=>bin/classes/foo/bar/ApiTargetTest$LocalClass.class"
- ));
- }
-
- public void testTargetAnnotationInner() throws Exception {
- assertEquals(
- "src/test/pkg/ApiTargetTest2.java:32: Error: Call requires API level 14 (current min is 3): new android.widget.GridLayout [NewApi]\n" +
- " new GridLayout(null, null, 0);\n" +
- " ~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/ApiTargetTest2.java.txt=>src/test/pkg/ApiTargetTest2.java",
- "apicheck/ApiTargetTest2.class.data=>bin/classes/test/pkg/ApiTargetTest2.class",
- "apicheck/ApiTargetTest2$1.class.data=>bin/classes/test/pkg/ApiTargetTest2$1.class",
- "apicheck/ApiTargetTest2$1$2.class.data=>bin/classes/test/pkg/ApiTargetTest2$1$2.class",
- "apicheck/ApiTargetTest2$1$1.class.data=>bin/classes/test/pkg/ApiTargetTest2$1$1.class"
- ));
- }
-
- public void testSuper() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=36384
- assertEquals(
- "src/test/pkg/ApiCallTest7.java:8: Error: Call requires API level 9 (current min is 4): new java.io.IOException [NewApi]\n" +
- " super(message, cause); // API 9\n" +
- " ~~~~~\n" +
- "src/test/pkg/ApiCallTest7.java:12: Error: Call requires API level 9 (current min is 4): new java.io.IOException [NewApi]\n" +
- " super.toString(); throw new IOException((Throwable) null); // API 9\n" +
- " ~~~~~~~~~~~\n" +
- "2 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest7.java.txt=>src/test/pkg/ApiCallTest7.java",
- "apicheck/ApiCallTest7.class.data=>bin/classes/test/pkg/ApiCallTest7.class"
- ));
- }
-
- public void testEnums() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=36951
- assertEquals(
- "src/test/pkg/TestEnum.java:26: Error: Enum value requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
- " case OVERLAY: {\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/TestEnum.java:37: Error: Enum value requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
- " case OVERLAY: {\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/TestEnum.java:61: Error: Enum for switch requires API level 11 (current min is 4): android.renderscript.Element.DataType [NewApi]\n" +
- " switch (type) {\n" +
- " ^\n" +
- "3 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/TestEnum.java.txt=>src/test/pkg/TestEnum.java",
- "apicheck/TestEnum.class.data=>bin/classes/test/pkg/TestEnum.class"
- ));
- }
-
- @Override
- public String getSuperClass(Project project, String name) {
- // For testInterfaceInheritance
- if (name.equals("android/database/sqlite/SQLiteStatement")) {
- return "android/database/sqlite/SQLiteProgram";
- } else if (name.equals("android/database/sqlite/SQLiteProgram")) {
- return "android/database/sqlite/SQLiteClosable";
- } else if (name.equals("android/database/sqlite/SQLiteClosable")) {
- return "java/lang/Object";
- }
- return null;
- }
-
- public void testInterfaceInheritance() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=38004
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/CloseTest.java.txt=>src/test/pkg/CloseTest.java",
- "apicheck/CloseTest.class.data=>bin/classes/test/pkg/CloseTest.class"
- ));
- }
-
- public void testInnerClassPositions() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=38113
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest8.java.txt=>src/test/pkg/ApiCallTest8.java",
- "apicheck/ApiCallTest8.class.data=>bin/classes/test/pkg/ApiCallTest8.class"
- ));
- }
-
- public void testManifestReferences() throws Exception {
- assertEquals(
- "AndroidManifest.xml:15: Error: @android:style/Theme.Holo requires API level 11 (current min is 4) [NewApi]\n" +
- " android:theme=\"@android:style/Theme.Holo\" >\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/holomanifest.xml=>AndroidManifest.xml"
- ));
- }
-
- public void testSuppressFieldAnnotations() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=38626
- assertEquals(
- "src/test/pkg/ApiCallTest9.java:9: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n" +
- " private GridLayout field1 = new GridLayout(null);\n" +
- " ~~~~~~~~~~\n" +
- "src/test/pkg/ApiCallTest9.java:12: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n" +
- " private static GridLayout field2 = new GridLayout(null);\n" +
- " ~~~~~~~~~~\n" +
- "2 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest9.java.txt=>src/test/pkg/ApiCallTest9.java",
- "apicheck/ApiCallTest9.class.data=>bin/classes/test/pkg/ApiCallTest9.class"
- ));
- }
-
- public void test38195() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=38195
- assertEquals(
- "bin/classes/TestLint.class: Error: Call requires API level 16 (current min is 4): new android.database.SQLException [NewApi]\n" +
- "bin/classes/TestLint.class: Error: Call requires API level 9 (current min is 4): java.lang.String#isEmpty [NewApi]\n" +
- "bin/classes/TestLint.class: Error: Call requires API level 9 (current min is 4): new java.sql.SQLException [NewApi]\n" +
- "3 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- /*
- Compiled from "TestLint.java"
- public class test.pkg.TestLint extends java.lang.Object{
- public test.pkg.TestLint();
- Code:
- 0: aload_0
- 1: invokespecial #8; //Method java/lang/Object."<init>":()V
- 4: return
-
- public void test(java.lang.Exception) throws java.lang.Exception;
- Code:
- 0: ldc #19; //String
- 2: invokevirtual #21; //Method java/lang/String.isEmpty:()Z
- 5: istore_2
- 6: new #27; //class java/sql/SQLException
- 9: dup
- 10: ldc #29; //String error on upgrade:
- 12: aload_1
- 13: invokespecial #31; //Method java/sql/SQLException."<init>":
- (Ljava/lang/String;Ljava/lang/Throwable;)V
- 16: athrow
-
- public void test2(java.lang.Exception) throws java.lang.Exception;
- Code:
- 0: new #39; //class android/database/SQLException
- 3: dup
- 4: ldc #29; //String error on upgrade:
- 6: aload_1
- 7: invokespecial #41; //Method android/database/SQLException.
- "<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
- 10: athrow
- }
- */
- "apicheck/TestLint.class.data=>bin/classes/TestLint.class"
- ));
- }
-
- public void testAllowLocalMethodsImplementingInaccessible() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=39030
- assertEquals(
- "src/test/pkg/ApiCallTest10.java:40: Error: Call requires API level 14 (current min is 4): android.view.View#dispatchHoverEvent [NewApi]\n" +
- " dispatchHoverEvent(null);\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest10.java.txt=>src/test/pkg/ApiCallTest10.java",
- "apicheck/ApiCallTest10.class.data=>bin/classes/test/pkg/ApiCallTest10.class"
- ));
- }
-
- public void testOverrideUnknownTarget() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest11.java.txt=>src/test/pkg/ApiCallTest11.java",
- "apicheck/ApiCallTest11.class.data=>bin/classes/test/pkg/ApiCallTest11.class"
- ));
- }
-
- public void testOverride() throws Exception {
- assertEquals(
- "src/test/pkg/ApiCallTest11.java:13: Error: This method is not overriding anything with the current build target, but will in API level 11 (current target is 3): test.pkg.ApiCallTest11#getActionBar [Override]\n" +
- " public ActionBar getActionBar() {\n" +
- " ~~~~~~~~~~~~\n" +
- "src/test/pkg/ApiCallTest11.java:17: Error: This method is not overriding anything with the current build target, but will in API level 17 (current target is 3): test.pkg.ApiCallTest11#isDestroyed [Override]\n" +
- " public boolean isDestroyed() {\n" +
- " ~~~~~~~~~~~\n" +
- "src/test/pkg/ApiCallTest11.java:39: Error: This method is not overriding anything with the current build target, but will in API level 11 (current target is 3): test.pkg.ApiCallTest11.MyLinear#setDividerDrawable [Override]\n" +
- " public void setDividerDrawable(Drawable dividerDrawable) {\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "3 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "apicheck/ApiCallTest11.java.txt=>src/test/pkg/ApiCallTest11.java",
- "apicheck/ApiCallTest11.class.data=>bin/classes/test/pkg/ApiCallTest11.class",
- "apicheck/ApiCallTest11$MyLinear.class.data=>bin/classes/test/pkg/ApiCallTest11$MyLinear.class",
- "apicheck/ApiCallTest11$MyActivity.class.data=>bin/classes/test/pkg/ApiCallTest11$MyActivity.class"
- ));
- }
-
- public void testDateFormat() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=40876
- assertEquals(
- "src/test/pkg/ApiCallTest12.java:18: Error: Call requires API level 9 (current min is 4): java.text.DateFormatSymbols#getInstance [NewApi]\n" +
- " new SimpleDateFormat(\"yyyy-MM-dd\", DateFormatSymbols.getInstance());\n" +
- " ~~~~~~~~~~~\n" +
- "src/test/pkg/ApiCallTest12.java:23: Error: The pattern character 'L' requires API level 9 (current min is 4) : \"yyyy-MM-dd LL\" [NewApi]\n" +
- " new SimpleDateFormat(\"yyyy-MM-dd LL\", Locale.US);\n" +
- " ^\n" +
- "src/test/pkg/ApiCallTest12.java:25: Error: The pattern character 'c' requires API level 9 (current min is 4) : \"cc yyyy-MM-dd\" [NewApi]\n" +
- " SimpleDateFormat format = new SimpleDateFormat(\"cc yyyy-MM-dd\");\n" +
- " ^\n" +
- "3 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java",
- "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class"
- ));
- }
-
- public void testDateFormatOk() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk10.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java",
- "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class"
- ));
- }
-
- public void testJavaConstants() throws Exception {
- assertEquals(""
- + "src/test/pkg/ApiSourceCheck.java:5: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
- + "import static android.view.View.MEASURED_STATE_MASK;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:30: Warning: Field requires API level 11 (current min is 1): android.widget.ZoomControls#MEASURED_STATE_MASK [InlinedApi]\n"
- + " int x = MEASURED_STATE_MASK;\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:33: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
- + " int y = android.view.View.MEASURED_STATE_MASK;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:36: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
- + " int z = View.MEASURED_STATE_MASK;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:37: Warning: Field requires API level 14 (current min is 1): android.view.View#FIND_VIEWS_WITH_TEXT [InlinedApi]\n"
- + " int find2 = View.FIND_VIEWS_WITH_TEXT; // requires API 14\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:40: Warning: Field requires API level 12 (current min is 1): android.app.ActivityManager#MOVE_TASK_NO_USER_ACTION [InlinedApi]\n"
- + " int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:41: Warning: Field requires API level 14 (current min is 1): android.widget.ZoomButton#FIND_VIEWS_WITH_CONTENT_DESCRIPTION [InlinedApi]\n"
- + " int find1 = ZoomButton.FIND_VIEWS_WITH_CONTENT_DESCRIPTION; // requires\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:44: Warning: Field requires API level 9 (current min is 1): android.widget.ZoomControls#OVER_SCROLL_ALWAYS [InlinedApi]\n"
- + " int overScroll = OVER_SCROLL_ALWAYS; // requires API 9\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:47: Warning: Field requires API level 16 (current min is 1): android.widget.ZoomControls#IMPORTANT_FOR_ACCESSIBILITY_AUTO [InlinedApi]\n"
- + " int auto = IMPORTANT_FOR_ACCESSIBILITY_AUTO; // requires API 16\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:54: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
- + " return (child.getMeasuredWidth() & View.MEASURED_STATE_MASK)\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [InlinedApi]\n"
- + " | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
- + " | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:90: Warning: Field requires API level 8 (current min is 1): android.R.id#custom [InlinedApi]\n"
- + " int custom = android.R.id.custom; // API 8\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:94: Warning: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [InlinedApi]\n"
- + " String setPointerSpeed = permission.SET_POINTER_SPEED;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:95: Warning: Field requires API level 13 (current min is 1): android.Manifest.permission#SET_POINTER_SPEED [InlinedApi]\n"
- + " String setPointerSpeed2 = Manifest.permission.SET_POINTER_SPEED;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:120: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
- + " int y = View.MEASURED_STATE_MASK; // Not OK\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:121: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
- + " testBenignUsages(View.MEASURED_STATE_MASK); // Not OK\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:51: Error: Field requires API level 14 (current min is 1): android.widget.ZoomButton#ROTATION_X [NewApi]\n"
- + " Object rotationX = ZoomButton.ROTATION_X; // Requires API 14\n"
- + " ~~~~~~~~~~\n"
- + "1 errors, 17 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "apicheck/ApiSourceCheck.java.txt=>src/test/pkg/ApiSourceCheck.java",
- "apicheck/ApiSourceCheck.class.data=>bin/classes/test/pkg/ApiSourceCheck.class"
- ));
- }
-
- public void testStyleDeclaration() throws Exception {
- assertEquals(""
- + "res/values/styles2.xml:5: Error: android:actionBarStyle requires API level 11 (current min is 10) [NewApi]\n"
- + " <item name=\"android:actionBarStyle\">...</item>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk10.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "res/values/styles2.xml"
- ));
- }
-
- public void testStyleDeclarationInV9() throws Exception {
- assertEquals(""
- + "res/values-v9/styles2.xml:5: Error: android:actionBarStyle requires API level 11 (current min is 10) [NewApi]\n"
- + " <item name=\"android:actionBarStyle\">...</item>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk10.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "res/values/styles2.xml=>res/values-v9/styles2.xml"
- ));
- }
-
- public void testStyleDeclarationInV11() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk10.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "res/values/styles2.xml=>res/values-v11/styles2.xml"
- ));
- }
-
- public void testStyleDeclarationInV14() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk10.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "res/values/styles2.xml=>res/values-v14/styles2.xml"
- ));
- }
-
- public void testMovedConstants() throws Exception {
- assertEquals(""
- // These two constants were introduced in API 11; the other 3 were available
- // on subclass ListView from API 1
- + "src/test/pkg/ApiSourceCheck2.java:10: Warning: Field requires API level 11 (current min is 1): android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL [InlinedApi]\n"
- + " int mode2 = AbsListView.CHOICE_MODE_MULTIPLE_MODAL;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck2.java:14: Warning: Field requires API level 11 (current min is 1): android.widget.ListView#CHOICE_MODE_MULTIPLE_MODAL [InlinedApi]\n"
- + " int mode6 = ListView.CHOICE_MODE_MULTIPLE_MODAL;\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "apicheck/ApiSourceCheck2.java.txt=>src/test/pkg/ApiSourceCheck2.java",
- "apicheck/ApiSourceCheck2.class.data=>bin/classes/test/pkg/ApiSourceCheck2.class"
- ));
- }
-
- public void testInheritCompatLibrary() throws Exception {
- assertEquals(""
- + "src/test/pkg/MyActivityImpl.java:8: Error: Call requires API level 11 (current min is 1): android.app.Activity#isChangingConfigurations [NewApi]\n"
- + " boolean isChanging = super.isChangingConfigurations();\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/MyActivityImpl.java:12: Error: This method is not overriding anything with the current build target, but will in API level 11 (current target is 3): test.pkg.MyActivityImpl#isChangingConfigurations [Override]\n"
- + " public boolean isChangingConfigurations() {\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "apicheck/MyActivityImpl.java.txt=>src/test/pkg/MyActivityImpl.java",
- "apicheck/MyActivityImpl.class.data=>bin/classes/test/pkg/MyActivityImpl.class",
- "apicheck/android-support-v4.jar.data=>libs/android-support-v4.jar"
- ));
- }
-
- public void testImplements() throws Exception {
- assertEquals(""
- + "src/test/pkg/ApiCallTest13.java:8: Error: Class requires API level 14 (current min is 4): android.widget.GridLayout [NewApi]\n"
- + "public class ApiCallTest13 extends GridLayout implements\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/ApiCallTest13.java:9: Error: Class requires API level 11 (current min is 4): android.view.View.OnLayoutChangeListener [NewApi]\n"
- + " View.OnSystemUiVisibilityChangeListener, OnLayoutChangeListener {\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiCallTest13.java:9: Error: Class requires API level 11 (current min is 4): android.view.View.OnSystemUiVisibilityChangeListener [NewApi]\n"
- + " View.OnSystemUiVisibilityChangeListener, OnLayoutChangeListener {\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiCallTest13.java:12: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " super(context);\n"
- + " ~~~~~\n"
- + "4 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties1=>project.properties",
- "apicheck/ApiCallTest13.java.txt=>src/test/pkg/ApiCallTest13.java",
- "apicheck/ApiCallTest13.class.data=>bin/classes/test/pkg/ApiCallTest13.class"
- ));
- }
-
- public void testFieldSuppress() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=52726
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest14.java.txt=>src/test/pkg/ApiCallTest14.java",
- "apicheck/ApiCallTest14.class.data=>bin/classes/test/pkg/ApiCallTest14.class",
- "apicheck/ApiCallTest14$1.class.data=>bin/classes/test/pkg/ApiCallTest14$1.class",
- "apicheck/ApiCallTest14$2.class.data=>bin/classes/test/pkg/ApiCallTest14$2.class",
- "apicheck/ApiCallTest14$3.class.data=>bin/classes/test/pkg/ApiCallTest14$3.class"
- ));
- }
-
- public void testTryWithResources() throws Exception {
- assertEquals(""
- + "src/test/pkg/TryWithResources.java:13: Error: Try-with-resources requires API level 19 (current min is 1) [NewApi]\n"
- + " try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n"
- + " ^\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "src/test/pkg/TryWithResources.java.txt=>src/test/pkg/TryWithResources.java"
- ));
- }
-
- public void testTryWithResourcesOk() throws Exception {
- assertEquals(""
- + "No warnings.",
- lintProject(
- "apicheck/minsdk19.xml=>AndroidManifest.xml",
- "src/test/pkg/TryWithResources.java.txt=>src/test/pkg/TryWithResources.java"
- ));
- }
-
- public void testReflectiveOperationException() throws Exception {
- assertEquals(""
- + "src/test/pkg/Java7API.java:8: Error: Class requires API level 19 (current min is 1): java.lang.ReflectiveOperationException [NewApi]\n"
- + " } catch (ReflectiveOperationException e) {\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/Java7API.java:9: Error: Call requires API level 19 (current min is 1): java.lang.ReflectiveOperationException#printStackTrace [NewApi]\n"
- + " e.printStackTrace();\n"
- + " ~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "src/test/pkg/Java7API.java.txt=>src/test/pkg/Java7API.java",
- "src/test/pkg/Java7API.class.data=>bin/classes/test/pkg/Java7API.class"
- ));
- }
-
- public void testReflectiveOperationExceptionOk() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- "apicheck/minsdk19.xml=>AndroidManifest.xml",
- "src/test/pkg/Java7API.java.txt=>src/test/pkg/Java7API.java"
- ));
- }
-
- public void testMissingApiDatabase() throws Exception {
- ApiLookup.dispose();
- assertEquals(""
- + "ApiDetectorTest_testMissingApiDatabase: Error: Can't find API database; API check not performed [LintError]\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml",
- "apicheck/classpath=>.classpath",
- "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
- ));
- }
-
- public void testRipple() throws Exception {
- assertEquals(""
- + "res/drawable/ripple.xml:1: Error: <ripple> requires API level 21 (current min is 14) [NewApi]\n"
- + "<ripple\n"
- + "^\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/ripple.xml=>res/drawable/ripple.xml"
- ));
- }
-
- public void testRippleOk1() throws Exception {
- // minSdkVersion satisfied
- assertEquals("No warnings.",
- lintProject(
- "apicheck/minsdk21.xml=>AndroidManifest.xml",
- "apicheck/ripple.xml=>res/drawable/ripple.xml"
- ));
- }
-
- public void testRippleOk2() throws Exception {
- // -vNN location satisfied
- assertEquals("No warnings.",
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/ripple.xml=>res/drawable-v21/ripple.xml"
- ));
- }
-
- public void testVector() throws Exception {
- assertEquals(""
- + "res/drawable/vector.xml:1: Error: <vector> requires API level 21 (current min is 4) [NewApi]\n"
- + "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
- + "^\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/vector.xml=>res/drawable/vector.xml"
- ));
- }
-
- public void testAnimatedSelector() throws Exception {
- assertEquals(""
- + "res/drawable/animated_selector.xml:1: Error: <animated-selector> requires API level 21 (current min is 14) [NewApi]\n"
- + "<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + "^\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/animated_selector.xml=>res/drawable/animated_selector.xml"
- ));
- }
-
- public void testAnimatedVector() throws Exception {
- assertEquals(""
- + "res/drawable/animated_vector.xml:1: Error: <animated-vector> requires API level 21 (current min is 14) [NewApi]\n"
- + "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + "^\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/animated_vector.xml=>res/drawable/animated_vector.xml"
- ));
- }
-
- public void testPaddingStart() throws Exception {
- assertEquals(""
- + "res/layout/padding_start.xml:14: Error: Attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
- + " android:paddingStart=\"20dp\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/padding_start.xml:21: Error: Attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
- + " android:paddingStart=\"20dp\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/padding_start.xml:28: Error: Attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
- + " android:paddingStart=\"20dp\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "3 errors, 0 warnings\n",
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/padding_start.xml=>res/layout/padding_start.xml"
- ));
- }
-
- public void testPaddingStartNotApplicable() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/padding_start.xml=>res/layout-v17/padding_start.xml"
- ));
- }
-
- public void testSwitch() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/TargetApiTest.java.txt=>src/test/pkg/TargetApiTest.java",
- "apicheck/TargetApiTest.class.data=>bin/classes/test/pkg/TargetApiTest.class",
- "apicheck/TargetApiTest$1.class.data=>bin/classes/test/pkg/TargetApiTest$1.class"
- ));
- }
-
- public void testGravity() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/GravityTest.java.txt=>src/test/pkg/GravityTest.java"
- ));
- }
-
- public void testSuperCall() throws Exception {
- assertEquals(""
- + "src/test/pkg/SuperCallTest.java:20: Error: Call requires API level 21 (current min is 19): android.service.wallpaper.WallpaperService.Engine#onApplyWindowInsets [NewApi]\n"
- + " super.onApplyWindowInsets(insets); // Error\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/SuperCallTest.java:27: Error: Call requires API level 21 (current min is 19): android.service.wallpaper.WallpaperService.Engine#onApplyWindowInsets [NewApi]\n"
- + " onApplyWindowInsets(insets); // Error: not overridden\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk19.xml=>AndroidManifest.xml",
- "apicheck/SuperCallTest.java.txt=>src/test/pkg/SuperCallTest.java",
- "apicheck/SuperCallTest.class.data=>bin/classes/test/pkg/SuperCallTest.class",
- "apicheck/SuperCallTest$MyEngine2.class.data=>bin/classes/test/pkg/SuperCallTest$MyEngine2.class",
- "apicheck/SuperCallTest$MyEngine1.class.data=>bin/classes/test/pkg/SuperCallTest$MyEngine1.class"
- ));
- }
-
- public void testSuperClassInLibrary() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=97006
- // 97006: Gradle lint does not recognize Context.getDrawable() as API 21+
- assertEquals(
- "src/test/pkg/MyFragment.java:10: Error: Call requires API level 21 (current min is 14): android.app.Activity#getDrawable [NewApi]\n" +
- " getActivity().getDrawable(R.color.my_color);\n" +
- " ~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java",
- "apicheck/MyFragment.java.txt=>src/test/pkg/MyFragment.java",
- "apicheck/MyFragment$R$color.class.data=>bin/classes/test/pkg/MyFragment$R$color.class",
- "apicheck/MyFragment$R.class.data=>bin/classes/test/pkg/MyFragment$R.class",
- "apicheck/MyFragment.class.data=>bin/classes/test/pkg/MyFragment.class",
-
- // Library project
- "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
- "multiproject/library.properties=>../LibraryProject/project.properties",
- "multiproject/LibraryCode.java.txt=>../LibraryProject/src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>../LibraryProject/res/values/strings.xml",
- "apicheck/fragment_support.jar.data=>../LibraryProject/libs/fragment_support.jar"
- ));
- }
-
- public void testConditionalApi0() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=137195
- assertEquals(""
- + "src/test/pkg/ConditionalApiTest.java:28: Error: Call requires API level 18 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
- + " new RectEvaluator(); // ERROR\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/ConditionalApiTest.java:37: Error: Call requires API level 21 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
- + " new RectEvaluator(rect); // ERROR\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/ConditionalApiTest.java:43: Error: Call requires API level 21 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
- + " new RectEvaluator(rect); // ERROR\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/ConditionalApiTest.java:45: Error: Call requires API level 21 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
- + " new RectEvaluator(rect); // ERROR\n"
- + " ~~~~~~~~~~~~~\n"
- + "4 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/ConditionalApiTest.java.txt=>src/test/pkg/ConditionalApiTest.java",
- "apicheck/ConditionalApiTest.class.data=>bin/classes/test/pkg/ConditionalApiTest.class"
- ));
- }
-
- public void testConditionalApi1() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=137195
- assertEquals(""
- + "src/test/pkg/VersionConditional1.java:18: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:18: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:24: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:24: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:30: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:30: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:36: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:36: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:40: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:40: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:48: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:48: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:54: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:54: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:60: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:60: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:62: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:62: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:65: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:65: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:76: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:84: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:90: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:94: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:94: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:96: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:102: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:108: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:114: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:118: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:126: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1.java:132: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "32 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/VersionConditional1.java.txt=>src/test/pkg/VersionConditional1.java",
- "apicheck/VersionConditional1.class.data=>bin/classes/test/pkg/VersionConditional1.class"
- ));
- }
-
- public void testConditionalApi1b() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=137195
- // This is like testConditionalApi1, but with each logical lookup call extracted into
- // a single method. This makes debugging through the control flow graph a lot easier.
- assertEquals(""
- + "src/test/pkg/VersionConditional1b.java:23: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:31: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:31: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:33: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:33: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:36: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:36: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:44: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:44: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:52: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:52: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:58: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:58: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:68: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:68: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:76: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:76: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:84: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:84: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:92: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:92: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:100: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:106: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:110: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:110: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null).getOrientation(); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:112: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:118: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:124: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:130: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:134: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:142: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional1b.java:148: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
- + " new GridLayout(null); // Flagged\n"
- + " ~~~~~~~~~~\n"
- + "32 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/VersionConditional1b.java.txt=>src/test/pkg/VersionConditional1b.java",
- "apicheck/VersionConditional1b.class.data=>bin/classes/test/pkg/VersionConditional1b.class"
- ));
- }
-
- public void testConditionalApi2() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=137195
- assertEquals(""
- + "src/test/pkg/VersionConditional2.java:20: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:24: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:42: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:46: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:50: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:66: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:72: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:78: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:98: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:104: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:128: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:132: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2.java:136: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "13 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/VersionConditional2.java.txt=>src/test/pkg/VersionConditional2.java",
- "apicheck/VersionConditional2.class.data=>bin/classes/test/pkg/VersionConditional2.class"
- ));
- }
-
- public void testConditionalApi2b() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=137195
- // This is like testConditionalApi2, but with each logical lookup call extracted into
- // a single method. This makes debugging through the control flow graph a lot easier.
- assertEquals(""
- + "src/test/pkg/VersionConditional2b.java:17: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:23: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:47: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:53: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:59: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:79: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:87: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:95: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:119: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:127: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:157: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:163: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional2b.java:169: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
- + " root.setBackground(background); // Flagged\n"
- + " ~~~~~~~~~~~~~\n"
- + "13 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/VersionConditional2b.java.txt=>src/test/pkg/VersionConditional2b.java",
- "apicheck/VersionConditional2b.class.data=>bin/classes/test/pkg/VersionConditional2b.class"
- ));
- }
-
- public void testConditionalApi3() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=137195
- assertEquals(""
- + "src/test/pkg/VersionConditional3.java:13: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:15: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > 19 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:24: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT >= 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:26: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT >= 19 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:28: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT >= 20 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:35: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT == 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:37: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT == 19 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:39: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT == 20 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:46: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT < 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:48: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT < 22 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:50: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT <= 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:52: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT <= 22 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:56: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:58: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > VERSION_CODES.KITKAT && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:66: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > 21 || property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3.java:83: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " property.hasAdjacentMapping() && // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "16 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/VersionConditional3.java.txt=>src/test/pkg/VersionConditional3.java",
- "apicheck/VersionConditional3.class.data=>bin/classes/test/pkg/VersionConditional3.class"
- ));
- }
-
- public void testConditionalApi3b() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=137195
- // This is like testConditionalApi3, but with each logical lookup call extracted into
- // a single method. This makes debugging through the control flow graph a lot easier.
- assertEquals(""
- + "src/test/pkg/VersionConditional3b.java:21: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " property.hasAdjacentMapping() && // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:44: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > 21 || property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:59: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > VERSION_CODES.KITKAT && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:64: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > VERSION_CODES.GINGERBREAD && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:69: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT <= 22 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:74: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT <= 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:79: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT < 22 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:84: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT < 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:99: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT == 20 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:104: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT == 19 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:109: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT == 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:124: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT >= 20 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:129: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT >= 19 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:134: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT >= 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:154: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > 19 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/VersionConditional3b.java:159: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
- + " if (Build.VERSION.SDK_INT > 18 && property.hasAdjacentMapping()) { // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "16 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/VersionConditional3b.java.txt=>src/test/pkg/VersionConditional3b.java",
- "apicheck/VersionConditional3b.class.data=>bin/classes/test/pkg/VersionConditional3b.class"
- ));
- }
-
- @Override
- protected TestLintClient createClient() {
- if (getName().equals("testMissingApiDatabase")) {
- // Simulate an environment where there is no API database
- return new TestLintClient() {
- @Override
- public File findResource(@NonNull String relativePath) {
- return null;
- }
- };
- }
- return super.createClient();
- }
-
- @Override
- protected boolean ignoreSystemErrors() {
- if (getName().equals("testMissingApiDatabase")) {
- return false;
- }
- return super.ignoreSystemErrors();
- }
-
- @Override
- protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
- @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
- if (issue == UNSUPPORTED || issue == INLINED) {
- int requiredVersion = ApiDetector.getRequiredVersion(issue, message, TEXT);
- assertTrue("Could not extract message tokens from " + message,
- requiredVersion >= 1 && requiredVersion <= SdkVersionInfo.HIGHEST_KNOWN_API);
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
deleted file mode 100644
index 7a7fa28..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Severity;
-
-import java.io.File;
-import java.io.PrintWriter;
-import java.io.RandomAccessFile;
-import java.io.StringWriter;
-
- at SuppressWarnings("javadoc")
-public class ApiLookupTest extends AbstractCheckTest {
- private final ApiLookup mDb = ApiLookup.get(new TestLintClient());
-
- public void test1() {
- assertEquals(5, mDb.getFieldVersion("android/Manifest$permission", "AUTHENTICATE_ACCOUNTS"));
- assertTrue(mDb.getFieldVersion("android/R$attr", "absListViewStyle") <= 1);
- assertEquals(11, mDb.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
- assertEquals(5, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
- "<init>", "(Landroid/content/res/Resources;Ljava/lang/String;)V"));
- assertEquals(4, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
- "setTargetDensity", "(Landroid/util/DisplayMetrics;)V"));
- assertEquals(7, mDb.getClassVersion("android/app/WallpaperInfo"));
- assertEquals(11, mDb.getClassVersion("android/widget/StackView"));
- assertTrue(mDb.getClassVersion("ava/text/ChoiceFormat") <= 1);
-
- // Class lookup: Unknown class
- assertEquals(-1, mDb.getClassVersion("foo/Bar"));
- // Field lookup: Unknown class
- assertEquals(-1, mDb.getFieldVersion("foo/Bar", "FOOBAR"));
- // Field lookup: Unknown field
- assertEquals(-1, mDb.getFieldVersion("android/Manifest$permission", "FOOBAR"));
- // Method lookup: Unknown class
- assertEquals(-1, mDb.getCallVersion("foo/Bar",
- "<init>", "(Landroid/content/res/Resources;Ljava/lang/String;)V"));
- // Method lookup: Unknown name
- assertEquals(-1, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
- "foo", "(Landroid/content/res/Resources;Ljava/lang/String;)V"));
- // Method lookup: Unknown argument list
- assertEquals(-1, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
- "<init>", "(I)V"));
- }
-
- public void test2() {
- // Regression test:
- // This used to return 11 because of some wildcard syntax in the signature
- assertTrue(mDb.getCallVersion("java/lang/Object", "getClass", "()") <= 1);
- }
-
- public void testIssue26467() {
- assertTrue(mDb.getCallVersion("java/nio/ByteBuffer", "array", "()") <= 1);
- assertEquals(9, mDb.getCallVersion("java/nio/Buffer", "array", "()"));
- }
-
- public void testNoInheritedConstructors() {
- assertTrue(mDb.getCallVersion("java/util/zip/ZipOutputStream", "<init>", "()") <= 1);
- assertTrue(mDb.getCallVersion("android/app/AliasActivity", "<init>", "(Landroid/content/Context;I)") <= 1);
- }
-
- public void testIssue35190() {
- assertEquals(9, mDb.getCallVersion("java/io/IOException", "<init>",
- "(Ljava/lang/Throwable;)V"));
- }
-
- public void testInheritInterfaces() {
- // The onPreferenceStartFragment is inherited via the
- // android/preference/PreferenceFragment$OnPreferenceStartFragmentCallback
- // interface
- assertEquals(11, mDb.getCallVersion("android/preference/PreferenceActivity",
- "onPreferenceStartFragment",
- "(Landroid/preference/PreferenceFragment;Landroid/preference/Preference;)"));
- }
-
- public void testIsValidPackage() {
- assertTrue(mDb.isValidJavaPackage("java/lang/Integer"));
- assertTrue(mDb.isValidJavaPackage("javax/crypto/Cipher"));
- assertTrue(mDb.isValidJavaPackage("java/awt/font/NumericShaper"));
-
- assertFalse(mDb.isValidJavaPackage("javax/swing/JButton"));
- assertFalse(mDb.isValidJavaPackage("java/rmi/Naming"));
- assertFalse(mDb.isValidJavaPackage("java/lang/instrument/Instrumentation"));
- }
-
- @Override
- protected Detector getDetector() {
- fail("This is not used in the ApiDatabase test");
- return null;
- }
-
- private File mCacheDir;
- @SuppressWarnings("StringBufferField")
- private StringBuilder mLogBuffer = new StringBuilder();
-
- @SuppressWarnings({"ConstantConditions", "IOResourceOpenedButNotSafelyClosed",
- "ResultOfMethodCallIgnored"})
- public void testCorruptedCacheHandling() throws Exception {
- ApiLookup lookup;
-
- // Real cache:
- mCacheDir = new TestLintClient().getCacheDir(true);
- mLogBuffer.setLength(0);
- lookup = ApiLookup.get(new LookupTestClient());
- assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
- assertNotNull(lookup);
- assertEquals("", mLogBuffer.toString()); // No warnings
- ApiLookup.dispose();
-
- // Custom cache dir: should also work
- mCacheDir = new File(getTempDir(), "test-cache");
- mCacheDir.mkdirs();
- mLogBuffer.setLength(0);
- lookup = ApiLookup.get(new LookupTestClient());
- assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
- assertNotNull(lookup);
- assertEquals("", mLogBuffer.toString()); // No warnings
- ApiLookup.dispose();
-
- // Now truncate cache file
- File cacheFile = new File(mCacheDir,
- ApiLookup.getCacheFileName("api-versions.xml",
- ApiLookup.getPlatformVersion(new LookupTestClient()))); //$NON-NLS-1$
- mLogBuffer.setLength(0);
- assertTrue(cacheFile.exists());
- RandomAccessFile raf = new RandomAccessFile(cacheFile, "rw");
- // Truncate file in half
- raf.setLength(100); // Broken header
- raf.close();
- lookup = ApiLookup.get(new LookupTestClient());
- String message = mLogBuffer.toString();
- // NOTE: This test is incompatible with the DEBUG_FORCE_REGENERATE_BINARY and WRITE_STATS
- // flags in the ApiLookup class, so if the test fails during development and those are
- // set, clear them.
- assertTrue(message.contains("Please delete the file and restart the IDE/lint:"));
- assertTrue(message.contains(mCacheDir.getPath()));
- ApiLookup.dispose();
-
- mLogBuffer.setLength(0);
- assertTrue(cacheFile.exists());
- raf = new RandomAccessFile(cacheFile, "rw");
- // Truncate file in half in the data portion
- raf.setLength(raf.length() / 2);
- raf.close();
- lookup = ApiLookup.get(new LookupTestClient());
- // This data is now truncated: lookup returns the wrong size.
- try {
- assertNotNull(lookup);
- lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance");
- fail("Can't look up bogus data");
- } catch (Throwable t) {
- // Expected this: the database is corrupted.
- }
- assertTrue(message.contains("Please delete the file and restart the IDE/lint:"));
- assertTrue(message.contains(mCacheDir.getPath()));
- ApiLookup.dispose();
-
- mLogBuffer.setLength(0);
- assertTrue(cacheFile.exists());
- raf = new RandomAccessFile(cacheFile, "rw");
- // Truncate file to 0 bytes
- raf.setLength(0);
- raf.close();
- lookup = ApiLookup.get(new LookupTestClient());
- assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
- assertNotNull(lookup);
- assertEquals("", mLogBuffer.toString()); // No warnings
- ApiLookup.dispose();
- }
-
- private final class LookupTestClient extends TestLintClient {
- @SuppressWarnings("ResultOfMethodCallIgnored")
- @Override
- public File getCacheDir(boolean create) {
- assertNotNull(mCacheDir);
- if (create && !mCacheDir.exists()) {
- mCacheDir.mkdirs();
- }
- return mCacheDir;
- }
-
- @Override
- public void log(
- @NonNull Severity severity,
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args) {
- if (format != null) {
- mLogBuffer.append(String.format(format, args));
- mLogBuffer.append('\n');
- }
- if (exception != null) {
- StringWriter writer = new StringWriter();
- exception.printStackTrace(new PrintWriter(writer));
- mLogBuffer.append(writer.toString());
- mLogBuffer.append('\n');
- }
- }
-
- @Override
- public void log(Throwable exception, String format, Object... args) {
- log(Severity.WARNING, exception, format, args);
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java
deleted file mode 100644
index 4c0a56a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class AppIndexingApiDetectorTest extends AbstractCheckTest {
-
- @Override
- protected Detector getDetector() {
- return new AppIndexingApiDetector();
- }
-
- public void testOk() throws Exception {
- assertEquals("No warnings.",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:scheme=\"http\"\n"
- + " android:host=\"example.com\"\n"
- + " android:pathPrefix=\"/gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testDataMissing() throws Exception {
- assertEquals(""
- + "AndroidManifest.xml:15: Error: Missing data node? [AppIndexingError]\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " ^\n"
- + "1 errors, 0 warnings\n",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testNoUrl() throws Exception {
- assertEquals(""
- + "AndroidManifest.xml:17: Error: Missing URL for the intent filter? [AppIndexingError]\n"
- + " <data />\n"
- + " ~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testMimeType() throws Exception {
- assertEquals("No warnings.",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:mimeType=\"mimetype\" /> "
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
-
- public void testNoActivity() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testNotBrowsable() throws Exception {
- assertEquals(""
- + "AndroidManifest.xml:25: Warning: Activity supporting ACTION_VIEW is not set as BROWSABLE [AppIndexingWarning]\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " ^\n"
- + "0 errors, 1 warnings\n",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".MainActivity\"\n"
- + " android:label=\"@string/app_name\" >\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\" />\n"
- + "\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + "\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:scheme=\"http\"\n"
- + " android:host=\"example.com\"\n"
- + " android:pathPrefix=\"/gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testWrongPathPrefix() throws Exception {
- assertEquals(""
- + "AndroidManifest.xml:19: Error: android:pathPrefix attribute should start with '/', but it is : gizmos [AppIndexingError]\n"
- + " android:pathPrefix=\"gizmos\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:scheme=\"http\"\n"
- + " android:host=\"example.com\"\n"
- + " android:pathPrefix=\"gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testWrongPort() throws Exception {
- assertEquals(""
- + "AndroidManifest.xml:19: Error: android:port is not a legal number [AppIndexingError]\n"
- + " android:port=\"ABCD\"\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:scheme=\"http\"\n"
- + " android:host=\"example.com\"\n"
- + " android:port=\"ABCD\"\n"
- + " android:pathPrefix=\"/gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testSchemeAndHostMissing() throws Exception {
- assertEquals(""
- + "AndroidManifest.xml:17: Error: Missing URL for the intent filter? [AppIndexingError]\n"
- + " <data android:pathPrefix=\"/gizmos\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:17: Error: android:host missing [AppIndexingError]\n"
- + " <data android:pathPrefix=\"/gizmos\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:17: Error: android:scheme missing [AppIndexingError]\n"
- + " <data android:pathPrefix=\"/gizmos\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "3 errors, 0 warnings\n",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:pathPrefix=\"/gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testMultiData() throws Exception {
- assertEquals("No warnings.",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:scheme=\"http\" />\n"
- + " <data android:host=\"example.com\" />\n"
- + " <data android:pathPrefix=\"/gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testMultiIntent() throws Exception {
- assertEquals("No warnings.",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\" />\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
- + " </intent-filter>"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:scheme=\"http\"\n"
- + " android:host=\"example.com\"\n"
- + " android:pathPrefix=\"/gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testMultiIntentWithError() throws Exception {
- assertEquals(""
- + "AndroidManifest.xml:20: Error: android:host missing [AppIndexingError]\n"
- + " <data android:scheme=\"http\"\n"
- + " ^\n"
- + "1 errors, 0 warnings\n",
- lintProject(xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:icon=\"@mipmap/ic_launcher\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " <activity\n"
- + " android:name=\".FullscreenActivity\"\n"
- + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
- + " android:label=\"@string/title_activity_fullscreen\"\n"
- + " android:theme=\"@style/FullscreenTheme\" >\n"
- + " <intent-filter>\n"
- + " <action android:name=\"android.intent.action.MAIN\" />\n"
- + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
- + " </intent-filter>"
- + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
- + " <action android:name=\"android.intent.action.VIEW\" />\n"
- + " <data android:scheme=\"http\"\n"
- + " android:pathPrefix=\"/gizmos\" />\n"
- + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
- + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
- + " </intent-filter>\n"
- + " </activity>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testOkWithResource() throws Exception {
- assertEquals("No warnings.",
- lintProjectIncrementally(
- "AndroidManifest.xml",
- "appindexing_manifest.xml=>AndroidManifest.xml",
- "res/values/appindexing_strings.xml"));
- }
-
- public void testWrongWithResource() throws Exception {
- assertEquals("" + "AndroidManifest.xml:18: Error: android:pathPrefix attribute should start with '/', but it is : pathprefix [AppIndexingError]\n"
- + " android:pathPrefix=\"@string/path_prefix\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:19: Error: android:port is not a legal number [AppIndexingError]\n"
- + " android:port=\"@string/port\"/>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
- lintProjectIncrementally(
- "AndroidManifest.xml",
- "appindexing_manifest.xml=>AndroidManifest.xml",
- "res/values/appindexing_wrong_strings.xml"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
deleted file mode 100644
index aba38f4..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Scope;
-
-import junit.framework.TestCase;
-
-import java.lang.reflect.Field;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class BuiltinIssueRegistryTest extends TestCase {
- public void testNoListResize() {
- BuiltinIssueRegistry registry = new BuiltinIssueRegistry();
- List<Issue> issues = registry.getIssues();
- int issueCount = issues.size();
- assertTrue(Integer.toString(issueCount),
- BuiltinIssueRegistry.INITIAL_CAPACITY >= issueCount);
- }
-
- @SuppressWarnings("unchecked")
- public void testCapacities() throws IllegalAccessException {
- TestIssueRegistry registry = new TestIssueRegistry();
- for (Scope scope : Scope.values()) {
- EnumSet<Scope> scopeSet = EnumSet.of(scope);
- checkCapacity(registry, scopeSet);
- }
-
- // Also check the commonly used combinations
- for (Field field : Scope.class.getDeclaredFields()) {
- if (field.getType().isAssignableFrom(EnumSet.class)) {
- checkCapacity(registry, (EnumSet<Scope>) field.get(null));
- }
- }
- }
-
- public void testUnique() {
- // Check that ids are unique
- Set<String> ids = new HashSet<String>();
- for (Issue issue : new BuiltinIssueRegistry().getIssues()) {
- String id = issue.getId();
- assertTrue("Duplicate id " + id, !ids.contains(id));
- ids.add(id);
- }
- }
-
- private static void checkCapacity(TestIssueRegistry registry,
- EnumSet<Scope> scopeSet) {
- List<Issue> issuesForScope = registry.getIssuesForScope(scopeSet);
- int requiredSize = issuesForScope.size();
- int capacity = registry.getIssueCapacity(scopeSet);
- if (requiredSize > capacity) {
- fail("For Scope set " + scopeSet + ": capacity " + capacity
- + " < actual " + requiredSize);
- }
- }
-
- private static class TestIssueRegistry extends BuiltinIssueRegistry {
- // Override to make method accessible outside package
- @NonNull
- @Override
- public List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
- return super.getIssuesForScope(scope);
- }
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
deleted file mode 100644
index 1b885f3..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
-public class CallSuperDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new CallSuperDetector();
- }
-
- public void testCallSuper() throws Exception {
- assertEquals(""
- + "src/test/pkg/CallSuperTest.java:11: Error: Overriding method should call super.test1 [MissingSuperCall]\n"
- + " protected void test1() { // ERROR\n"
- + " ~~~~~~~\n"
- + "src/test/pkg/CallSuperTest.java:14: Error: Overriding method should call super.test2 [MissingSuperCall]\n"
- + " protected void test2() { // ERROR\n"
- + " ~~~~~~~\n"
- + "src/test/pkg/CallSuperTest.java:17: Error: Overriding method should call super.test3 [MissingSuperCall]\n"
- + " protected void test3() { // ERROR\n"
- + " ~~~~~~~\n"
- + "src/test/pkg/CallSuperTest.java:20: Error: Overriding method should call super.test4 [MissingSuperCall]\n"
- + " protected void test4(int arg) { // ERROR\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/CallSuperTest.java:26: Error: Overriding method should call super.test5 [MissingSuperCall]\n"
- + " protected void test5(int arg1, boolean arg2, Map<List<String>,?> arg3, // ERROR\n"
- + " ^\n"
- + "src/test/pkg/CallSuperTest.java:30: Error: Overriding method should call super.test5 [MissingSuperCall]\n"
- + " protected void test5() { // ERROR\n"
- + " ~~~~~~~\n"
- + "6 errors, 0 warnings\n",
-
- lintProject("src/test/pkg/CallSuperTest.java.txt=>src/test/pkg/CallSuperTest.java",
- "src/android/support/annotation/CallSuper.java.txt=>src/android/support/annotation/CallSuper.java"));
- }
-
- @SuppressWarnings("ClassNameDiffersFromFileName")
- public void testCallSuperIndirect() throws Exception {
- // Ensure that when the @CallSuper is on an indirect super method,
- // we correctly check that you call the direct super method, not the ancestor.
- //
- // Regression test for
- // https://code.google.com/p/android/issues/detail?id=174964
- assertEquals("No warnings.",
- lintProject(
- java("src/test/pkg/CallSuperTest.java", ""
- + "package test.pkg;\n"
- + "\n"
- + "import android.support.annotation.CallSuper;\n"
- + "\n"
- + "import java.util.List;\n"
- + "import java.util.Map;\n"
- + "\n"
- + "@SuppressWarnings(\"UnusedDeclaration\")\n"
- + "public class CallSuperTest {\n"
- + " private static class Child extends Parent {\n"
- + " @Override\n"
- + " protected void test1() {\n"
- + " super.test1();\n"
- + " }\n"
- + " }\n"
- + "\n"
- + " private static class Parent extends ParentParent {\n"
- + " @Override\n"
- + " protected void test1() {\n"
- + " super.test1();\n"
- + " }\n"
- + " }\n"
- + "\n"
- + " private static class ParentParent extends ParentParentParent {\n"
- + " @CallSuper\n"
- + " protected void test1() {\n"
- + " }\n"
- + " }\n"
- + "\n"
- + " private static class ParentParentParent {\n"
- + "\n"
- + " }\n"
- + "}\n"),
- copy("src/android/support/annotation/CallSuper.java.txt",
- "src/android/support/annotation/CallSuper.java")));
- }
-
- public void testDetachFromWindow() throws Exception {
- assertEquals(""
- + "src/test/pkg/DetachedFromWindow.java:7: Error: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
- + " protected void onDetachedFromWindow() {\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/DetachedFromWindow.java:26: Error: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
- + " protected void onDetachedFromWindow() {\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
-
- lintProject("src/test/pkg/DetachedFromWindow.java.txt=>" +
- "src/test/pkg/DetachedFromWindow.java"));
- }
-
- public void testWatchFaceVisibility() throws Exception {
- assertEquals(""
- + "src/test/pkg/WatchFaceTest.java:9: Error: Overriding method should call super.onVisibilityChanged [MissingSuperCall]\n"
- + " public void onVisibilityChanged(boolean visible) { // ERROR: Missing super call\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "src/test/pkg/WatchFaceTest.java.txt=>src/test/pkg/WatchFaceTest.java",
- "stubs/WatchFaceService.java.txt=>src/android/support/wearable/watchface/WatchFaceService.java",
- "stubs/CanvasWatchFaceService.java.txt=>src/android/support/wearable/watchface/CanvasWatchFaceService.java"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java
deleted file mode 100644
index 1fd8e61..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class CleanupDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new CleanupDetector();
- }
-
- public void testRecycle() throws Exception {
- assertEquals(
- "src/test/pkg/RecycleTest.java:56: Warning: This TypedArray should be recycled after use with #recycle() [Recycle]\n" +
- " final TypedArray a = getContext().obtainStyledAttributes(attrs,\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:63: Warning: This TypedArray should be recycled after use with #recycle() [Recycle]\n" +
- " final TypedArray a = getContext().obtainStyledAttributes(new int[0]);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:79: Warning: This VelocityTracker should be recycled after use with #recycle() [Recycle]\n" +
- " VelocityTracker tracker = VelocityTracker.obtain();\n" +
- " ~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:92: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
- " MotionEvent event1 = MotionEvent.obtain(null);\n" +
- " ~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:93: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
- " MotionEvent event2 = MotionEvent.obtainNoHistory(null);\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:98: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
- " MotionEvent event2 = MotionEvent.obtainNoHistory(null); // Not recycled\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:103: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
- " MotionEvent event1 = MotionEvent.obtain(null); // Not recycled\n" +
- " ~~~~~~\n" +
- /* Not implemented in AST visitor; not a typical user error and easy to diagnose if it's done
- "src/test/pkg/RecycleTest.java:113: Warning: This MotionEvent has already been recycled [Recycle]\n" +
- " int contents2 = event1.describeContents(); // BAD, after recycle\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:117: Warning: This TypedArray has already been recycled [Recycle]\n" +
- " example = a.getString(R.styleable.MyView_exampleString); // BAD, after recycle\n" +
- " ~~~~~~~~~\n" +
- */
- "src/test/pkg/RecycleTest.java:129: Warning: This Parcel should be recycled after use with #recycle() [Recycle]\n" +
- " Parcel myparcel = Parcel.obtain();\n" +
- " ~~~~~~\n" +
- "src/test/pkg/RecycleTest.java:190: Warning: This TypedArray should be recycled after use with #recycle() [Recycle]\n" +
- " final TypedArray a = getContext().obtainStyledAttributes(attrs, // Not recycled\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 9 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "bytecode/RecycleTest.java.txt=>src/test/pkg/RecycleTest.java",
- "bytecode/RecycleTest.class.data=>bin/classes/test/pkg/RecycleTest.class"
- ));
- }
-
- public void testCommit() throws Exception {
- assertEquals("" +
- "src/test/pkg/CommitTest.java:25: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
- " getFragmentManager().beginTransaction(); // Missing commit\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/CommitTest.java:30: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
- " FragmentTransaction transaction2 = getFragmentManager().beginTransaction(); // Missing commit\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/CommitTest.java:39: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
- " getFragmentManager().beginTransaction(); // Missing commit\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/CommitTest.java:65: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n" +
- " getSupportFragmentManager().beginTransaction();\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "0 errors, 4 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "bytecode/CommitTest.java.txt=>src/test/pkg/CommitTest.java",
- "bytecode/CommitTest.class.data=>bin/classes/test/pkg/CommitTest.class",
- // Stubs just to be able to do type resolution without needing the full appcompat jar
- "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
- "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
- "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
- "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
- ));
- }
-
- public void testCommit2() throws Exception {
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "bytecode/DialogFragment.class.data=>bin/classes/test/pkg/DialogFragment.class",
- // Stubs just to be able to do type resolution without needing the full appcompat jar
- "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
- "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
- "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
- "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
- ));
- }
-
- public void testCommit3() throws Exception {
- assertEquals("" +
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "bytecode/CommitTest2.java.txt=>src/test/pkg/CommitTest2.java",
- "bytecode/CommitTest2$MyDialogFragment.class.data=>bin/classes/test/pkg/CommitTest2$MyDialogFragment.class",
- "bytecode/CommitTest2.class.data=>bin/classes/test/pkg/CommitTest2.class",
- // Stubs just to be able to do type resolution without needing the full appcompat jar
- "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
- "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
- "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
- "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
- ));
- }
-
- public void testCommit4() throws Exception {
- assertEquals("" +
- "src/test/pkg/CommitTest3.java:35: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
- + " getCompatFragmentManager().beginTransaction();\n"
- + " ~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "bytecode/CommitTest3.java.txt=>src/test/pkg/CommitTest3.java",
- "bytecode/CommitTest3.class.data=>bin/classes/test/pkg/CommitTest3.class",
- "bytecode/CommitTest3$MyDialogFragment.class.data=>bin/classes/test/pkg/CommitTest3$MyDialogFragment.class",
- "bytecode/CommitTest3$MyCompatDialogFragment.class.data=>bin/classes/test/pkg/CommitTest3$MyCompatDialogFragment.class",
- // Stubs just to be able to do type resolution without needing the full appcompat jar
- "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
- "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
- "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
- "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
- ));
- }
-
- public void testCommitChainedCalls() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=135204
- assertEquals(""
- + "src/test/pkg/TransactionTest.java:8: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
- + " android.app.FragmentTransaction transaction2 = getFragmentManager().beginTransaction();\n"
- + " ~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "src/test/pkg/TransactionTest.java.txt=>src/test/pkg/TransactionTest.java",
- // Stubs just to be able to do type resolution without needing the full appcompat jar
- "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
- "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
- "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
- "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
- ));
- }
-
- public void testSurfaceTexture() throws Exception {
- assertEquals(
- "src/test/pkg/SurfaceTextureTest.java:18: Warning: This SurfaceTexture should be freed up after use with #release() [Recycle]\n" +
- " SurfaceTexture texture = new SurfaceTexture(1); // Warn: texture not released\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SurfaceTextureTest.java:25: Warning: This SurfaceTexture should be freed up after use with #release() [Recycle]\n" +
- " SurfaceTexture texture = new SurfaceTexture(1); // Warn: texture not released\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SurfaceTextureTest.java:32: Warning: This Surface should be freed up after use with #release() [Recycle]\n" +
- " Surface surface = new Surface(texture); // Warn: surface not released\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 3 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "src/test/pkg/SurfaceTextureTest.java.txt=>src/test/pkg/SurfaceTextureTest.java"
- ));
- }
-
- public void testContentProviderClient() throws Exception {
- assertEquals(
- "src/test/pkg/ContentProviderClientTest.java:8: Warning: This ContentProviderClient should be freed up after use with #release() [Recycle]\n" +
- " ContentProviderClient client = resolver.acquireContentProviderClient(\"test\"); // Warn\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "src/test/pkg/ContentProviderClientTest.java.txt=>src/test/pkg/ContentProviderClientTest.java"
- ));
- }
-
- public void testDatabaseCursor() throws Exception {
- assertEquals(
- "src/test/pkg/CursorTest.java:14: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
- " Cursor cursor = db.query(\"TABLE_TRIPS\",\n" +
- " ~~~~~\n" +
- "src/test/pkg/CursorTest.java:23: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
- " Cursor cursor = db.query(\"TABLE_TRIPS\",\n" +
- " ~~~~~\n" +
- "src/test/pkg/CursorTest.java:74: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
- " Cursor query = provider.query(uri, null, null, null, null);\n" +
- " ~~~~~\n" +
- "src/test/pkg/CursorTest.java:75: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
- " Cursor query2 = resolver.query(uri, null, null, null, null);\n" +
- " ~~~~~\n" +
- "src/test/pkg/CursorTest.java:76: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
- " Cursor query3 = client.query(uri, null, null, null, null);\n" +
- " ~~~~~\n" +
- "0 errors, 5 warnings\n",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "project.properties19=>project.properties",
- "src/test/pkg/CursorTest.java.txt=>src/test/pkg/CursorTest.java"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java
deleted file mode 100644
index df02a41..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class CutPasteDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new CutPasteDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/PasteError.java:15: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
- " View view2 = findViewById(R.id.textView1);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " src/test/pkg/PasteError.java:14: First usage here\n" +
- "src/test/pkg/PasteError.java:71: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
- " view2 = findViewById(R.id.textView1);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " src/test/pkg/PasteError.java:68: First usage here\n" +
- "src/test/pkg/PasteError.java:78: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
- " view2 = findViewById(R.id.textView1);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " src/test/pkg/PasteError.java:76: First usage here\n" +
- "src/test/pkg/PasteError.java:86: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
- " view2 = findViewById(R.id.textView1);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " src/test/pkg/PasteError.java:83: First usage here\n" +
- "src/test/pkg/PasteError.java:95: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
- " view2 = findViewById(R.id.textView1);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " src/test/pkg/PasteError.java:91: First usage here\n" +
- "0 errors, 5 warnings\n",
-
- lintProject("src/test/pkg/PasteError.java.txt=>" +
- "src/test/pkg/PasteError.java"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java
deleted file mode 100644
index f399014..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class DeprecationDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new DeprecationDetector();
- }
-
- public void testApi1() throws Exception {
- assertEquals(
- "res/layout/deprecation.xml:2: Warning: AbsoluteLayout is deprecated [Deprecated]\n" +
- "<AbsoluteLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- "^\n" +
- "res/layout/deprecation.xml:18: Warning: android:editable is deprecated: Use an <EditText> to make it editable [Deprecated]\n" +
- " android:editable=\"true\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:26: Warning: android:editable is deprecated: <EditText> is already editable [Deprecated]\n" +
- " <EditText android:editable=\"true\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:27: Warning: android:editable is deprecated: Use inputType instead [Deprecated]\n" +
- " <EditText android:editable=\"false\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 4 warnings\n",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "res/layout/deprecation.xml"));
- }
-
- public void testApi4() throws Exception {
- assertEquals(
- "res/layout/deprecation.xml:2: Warning: AbsoluteLayout is deprecated [Deprecated]\n" +
- "<AbsoluteLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- "^\n" +
- "res/layout/deprecation.xml:16: Warning: android:autoText is deprecated: Use inputType instead [Deprecated]\n" +
- " android:autoText=\"true\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:17: Warning: android:capitalize is deprecated: Use inputType instead [Deprecated]\n" +
- " android:capitalize=\"true\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:18: Warning: android:editable is deprecated: Use an <EditText> to make it editable [Deprecated]\n" +
- " android:editable=\"true\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:20: Warning: android:inputMethod is deprecated: Use inputType instead [Deprecated]\n" +
- " android:inputMethod=\"@+id/foo\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:21: Warning: android:numeric is deprecated: Use inputType instead [Deprecated]\n" +
- " android:numeric=\"true\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:22: Warning: android:password is deprecated: Use inputType instead [Deprecated]\n" +
- " android:password=\"true\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:23: Warning: android:phoneNumber is deprecated: Use inputType instead [Deprecated]\n" +
- " android:phoneNumber=\"true\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:26: Warning: android:editable is deprecated: <EditText> is already editable [Deprecated]\n" +
- " <EditText android:editable=\"true\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/deprecation.xml:27: Warning: android:editable is deprecated: Use inputType instead [Deprecated]\n" +
- " <EditText android:editable=\"false\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 10 warnings\n",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/layout/deprecation.xml"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
deleted file mode 100644
index fc6b042..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.detector.api.TextFormat.RAW;
-import static com.android.tools.lint.detector.api.TextFormat.TEXT;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Severity;
-
- at SuppressWarnings("javadoc")
-public class DuplicateResourceDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new DuplicateResourceDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "res/values/customattr2.xml:2: Error: ContentFrame has already been defined in this folder [DuplicateDefinition]\n" +
- " <declare-styleable name=\"ContentFrame\">\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- " res/values/customattr.xml:2: Previously defined here\n" +
- "res/values/strings2.xml:19: Error: wallpaper_instructions has already been defined in this folder [DuplicateDefinition]\n" +
- " <string name=\"wallpaper_instructions\">Tap image to set landscape wallpaper</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values/strings.xml:29: Previously defined here\n" +
- "2 errors, 0 warnings\n",
-
- lintProject(
- "res/values/strings.xml",
- "res/values-land/strings.xml=>res/values/strings2.xml",
- "res/values-cs/strings.xml",
- "res/values/customattr.xml",
- "res/values/customattr.xml=>res/values/customattr2.xml"));
- }
-
- public void testDotAliases() throws Exception {
- assertEquals(""
- + "res/values/duplicate-strings2.xml:5: Error: app_name has already been defined in this folder (app_name is equivalent to app.name) [DuplicateDefinition]\n"
- + " <string name=\"app.name\">App Name 1</string>\n"
- + " ~~~~~~~~~~~~~~~\n"
- + " res/values/duplicate-strings2.xml:4: Previously defined here\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "res/values/duplicate-strings2.xml"));
- }
-
- public void testSameFile() throws Exception {
- assertEquals(""
- + "res/values/duplicate-strings.xml:6: Error: app_name has already been defined in this folder [DuplicateDefinition]\n"
- + " <string name=\"app_name\">App Name 1</string>\n"
- + " ~~~~~~~~~~~~~~~\n"
- + " res/values/duplicate-strings.xml:4: Previously defined here\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "res/values/duplicate-strings.xml"));
- }
-
- public void testStyleItems() throws Exception {
- assertEquals(""
- + "res/values/duplicate-items.xml:7: Error: android:textColor has already been defined in this <style> [DuplicateDefinition]\n"
- + " <item name=\"android:textColor\">#ff0000</item>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + " res/values/duplicate-items.xml:5: Previously defined here\n"
- + "res/values/duplicate-items.xml:13: Error: contentId has already been defined in this <declare-styleable> [DuplicateDefinition]\n"
- + " <attr name=\"contentId\" format=\"integer\" />\n"
- + " ~~~~~~~~~~~~~~~~\n"
- + " res/values/duplicate-items.xml:11: Previously defined here\n"
- + "2 errors, 0 warnings\n",
-
- lintProject(
- "res/values/duplicate-items.xml"));
- }
-
- public void testOk() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/strings.xml",
- "res/values-cs/strings.xml",
- "res/values-de-rDE/strings.xml",
- "res/values-es/strings.xml",
- "res/values-es-rUS/strings.xml",
- "res/values-land/strings.xml",
- "res/values-cs/arrays.xml",
- "res/values-es/donottranslate.xml",
- "res/values-nl-rNL/strings.xml"));
- }
-
- public void testResourceAliases() throws Exception {
- assertEquals(""
- + "res/values/refs.xml:3: Error: Unexpected resource reference type; expected value of type @string/ [ReferenceType]\n"
- + " <item name=\"invalid1\" type=\"string\">@layout/other</item>\n"
- + " ^\n"
- + "res/values/refs.xml:5: Error: Unexpected resource reference type; expected value of type @drawable/ [ReferenceType]\n"
- + " @layout/other\n"
- + " ^\n"
- + "res/values/refs.xml:10: Error: Unexpected resource reference type; expected value of type @string/ [ReferenceType]\n"
- + " <string name=\"invalid4\">@layout/indirect</string>\n"
- + " ^\n"
- + "res/values/refs.xml:15: Error: Unexpected resource reference type; expected value of type @color/ [ReferenceType]\n"
- + " <item name=\"drawableAsColor\" type=\"color\">@drawable/my_drawable</item>\n"
- + " ^\n"
- + "4 errors, 0 warnings\n",
-
- lintProject("res/values/refs.xml"));
- }
-
- public void testGetExpectedType() {
- assertEquals("string", DuplicateResourceDetector.getExpectedType(
- "Unexpected resource reference type; expected value of type `@string/`", RAW));
- assertEquals("string", DuplicateResourceDetector.getExpectedType(
- "Unexpected resource reference type; expected value of type @string/", TEXT));
- }
-
- public void testMipmapDrawable() throws Exception {
- // https://code.google.com/p/android/issues/detail?id=109892
- assertEquals("No warnings.",
-
- lintProject("res/values/refs2.xml"));
- }
-
- @Override
- protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
- @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
- if (issue == DuplicateResourceDetector.TYPE_MISMATCH) {
- assertNotNull(message, DuplicateResourceDetector.getExpectedType(message, TEXT));
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
deleted file mode 100644
index 150b772..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class FragmentDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new FragmentDetector();
- }
-
- public void test() throws Exception {
- assertEquals(""
- + "src/test/pkg/FragmentTest.java:10: Error: This fragment class should be public (test.pkg.FragmentTest.Fragment1) [ValidFragment]\n"
- + " private static class Fragment1 extends Fragment {\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/FragmentTest.java:15: Error: This fragment inner class should be static (test.pkg.FragmentTest.Fragment2) [ValidFragment]\n"
- + " public class Fragment2 extends Fragment {\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/FragmentTest.java:21: Error: The default constructor must be public [ValidFragment]\n"
- + " private Fragment3() {\n"
- + " ~~~~~~~~~~~\n"
- + "src/test/pkg/FragmentTest.java:26: Error: This fragment should provide a default constructor (a public constructor with no arguments) (test.pkg.FragmentTest.Fragment4) [ValidFragment]\n"
- + " public static class Fragment4 extends Fragment {\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/FragmentTest.java:27: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n"
- + " private Fragment4(int dummy) {\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/FragmentTest.java:36: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n"
- + " public Fragment5(int dummy) {\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "6 errors, 0 warnings\n",
-
- lintProject(
- "bytecode/FragmentTest$Fragment1.class.data=>bin/classes/test/pkg/FragmentTest$Fragment1.class",
- "bytecode/FragmentTest$Fragment2.class.data=>bin/classes/test/pkg/FragmentTest$Fragment2.class",
- "bytecode/FragmentTest$Fragment3.class.data=>bin/classes/test/pkg/FragmentTest$Fragment3.class",
- "bytecode/FragmentTest$Fragment4.class.data=>bin/classes/test/pkg/FragmentTest$Fragment4.class",
- "bytecode/FragmentTest$Fragment5.class.data=>bin/classes/test/pkg/FragmentTest$Fragment5.class",
- "bytecode/FragmentTest$Fragment6.class.data=>bin/classes/test/pkg/FragmentTest$Fragment6.class",
- "bytecode/FragmentTest$NotAFragment.class.data=>bin/classes/test/pkg/FragmentTest$NotAFragment.class",
- "bytecode/FragmentTest.java.txt=>src/test/pkg/FragmentTest.java"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java
deleted file mode 100644
index 12acffc..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
-public class GetSignaturesDetectorTest extends AbstractCheckTest {
-
- @Override
- protected Detector getDetector() {
- return new GetSignaturesDetector();
- }
-
- public void testLintWarningOnSingleGetSignaturesFlag() throws Exception {
- assertEquals(
- "src/test/pkg/GetSignaturesSingleFlagTest.java:9: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
- + " .getPackageInfo(\"some.pkg\", PackageManager.GET_SIGNATURES);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
- lintProject(
- "src/test/pkg/GetSignaturesSingleFlagTest.java.txt" +
- "=>src/test/pkg/GetSignaturesSingleFlagTest.java"
- ));
- }
-
- public void testLintWarningOnGetSignaturesFlagInBitwiseOrExpression() throws Exception {
- assertEquals(
- "src/test/pkg/GetSignaturesBitwiseOrTest.java:11: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
- + " .getPackageInfo(\"some.pkg\", GET_GIDS | GET_SIGNATURES | GET_PROVIDERS);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
- lintProject(
- "src/test/pkg/GetSignaturesBitwiseOrTest.java.txt" +
- "=>src/test/pkg/GetSignaturesBitwiseOrTest.java"
- ));
- }
-
- public void testLintWarningOnGetSignaturesFlagInBitwiseXorExpression() throws Exception {
- assertEquals(
- "src/test/pkg/GetSignaturesBitwiseXorTest.java:8: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
- + " getPackageManager().getPackageInfo(\"some.pkg\", PackageManager.GET_SIGNATURES ^ 0x0);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
- lintProject(
- "src/test/pkg/GetSignaturesBitwiseXorTest.java.txt" +
- "=>src/test/pkg/GetSignaturesBitwiseXorTest.java"
- ));
- }
-
- public void testLintWarningOnGetSignaturesFlagInBitwiseAndExpression() throws Exception {
- assertEquals(
- "src/test/pkg/GetSignaturesBitwiseAndTest.java:9: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
- + " Integer.MAX_VALUE & PackageManager.GET_SIGNATURES);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
- lintProject(
- "src/test/pkg/GetSignaturesBitwiseAndTest.java.txt" +
- "=>src/test/pkg/GetSignaturesBitwiseAndTest.java"
- ));
- }
-
- public void testLintWarningOnFlagsInStaticField() throws Exception {
- assertEquals(
- "src/test/pkg/GetSignaturesStaticFieldTest.java:9: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
- + " getPackageManager().getPackageInfo(\"some.pkg\", FLAGS);\n"
- + " ~~~~~\n"
- + "0 errors, 1 warnings\n",
- lintProject(
- "src/test/pkg/GetSignaturesStaticFieldTest.java.txt" +
- "=>src/test/pkg/GetSignaturesStaticFieldTest.java"
- ));
- }
-
- public void testNoLintWarningOnFlagsInLocalVariable() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject(
- "src/test/pkg/GetSignaturesLocalVariableTest.java.txt" +
- "=>src/test/pkg/GetSignaturesLocalVariableTest.java"
- ));
- }
-
- public void testNoLintWarningOnGetSignaturesWithNoFlag() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject(
- "src/test/pkg/GetSignaturesNoFlagTest.java.txt" +
- "=>src/test/pkg/GetSignaturesNoFlagTest.java"
- ));
- }
-
- public void testNoLintWarningOnGetPackageInfoOnNonPackageManagerClass() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject(
- "src/test/pkg/GetSignaturesNotPackageManagerTest.java.txt" +
- "=>src/test/pkg/GetSignaturesNotPackageManagerTest.java"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java
deleted file mode 100644
index 71e07a2..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java
+++ /dev/null
@@ -1,882 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.GRADLE_PLUGIN_MINIMUM_VERSION;
-import static com.android.SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION;
-import static com.android.tools.lint.checks.GradleDetector.ACCIDENTAL_OCTAL;
-import static com.android.tools.lint.checks.GradleDetector.COMPATIBILITY;
-import static com.android.tools.lint.checks.GradleDetector.DEPENDENCY;
-import static com.android.tools.lint.checks.GradleDetector.DEPRECATED;
-import static com.android.tools.lint.checks.GradleDetector.GRADLE_GETTER;
-import static com.android.tools.lint.checks.GradleDetector.GRADLE_PLUGIN_COMPATIBILITY;
-import static com.android.tools.lint.checks.GradleDetector.PATH;
-import static com.android.tools.lint.checks.GradleDetector.PLUS;
-import static com.android.tools.lint.checks.GradleDetector.REMOTE_VERSION;
-import static com.android.tools.lint.checks.GradleDetector.STRING_INTEGER;
-import static com.android.tools.lint.checks.GradleDetector.getNamedDependency;
-import static com.android.tools.lint.checks.GradleDetector.getNewValue;
-import static com.android.tools.lint.checks.GradleDetector.getOldValue;
-import static com.android.tools.lint.detector.api.TextFormat.TEXT;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.MavenCoordinates;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.DefaultPosition;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Implementation;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.codehaus.groovy.ast.ASTNode;
-import org.codehaus.groovy.ast.CodeVisitorSupport;
-import org.codehaus.groovy.ast.GroovyCodeVisitor;
-import org.codehaus.groovy.ast.builder.AstBuilder;
-import org.codehaus.groovy.ast.expr.ArgumentListExpression;
-import org.codehaus.groovy.ast.expr.ClosureExpression;
-import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.MapEntryExpression;
-import org.codehaus.groovy.ast.expr.MethodCallExpression;
-import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
-import org.codehaus.groovy.ast.expr.TupleExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
-import org.codehaus.groovy.ast.stmt.ExpressionStatement;
-import org.codehaus.groovy.ast.stmt.ReturnStatement;
-import org.codehaus.groovy.ast.stmt.Statement;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * <b>NOTE</b>: Most GradleDetector unit tests are in the Studio plugin, as tests
- * for IntellijGradleDetector
- */
-public class GradleDetectorTest extends AbstractCheckTest {
-
- private File mSdkDir;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- if (mSdkDir != null) {
- deleteFile(mSdkDir);
- mSdkDir = null;
- }
- }
-
- /** Creates a mock SDK installation structure, containing a fixed set of dependencies */
- private File getMockSupportLibraryInstallation() {
- if (mSdkDir == null) {
- // Make fake SDK "installation" such that we can predict the set
- // of Maven repositories discovered by this test
- mSdkDir = Files.createTempDir();
-
- String[] paths = new String[]{
- // Android repository
- "extras/android/m2repository/com/android/support/appcompat-v7/18.0.0/appcompat-v7-18.0.0.aar",
- "extras/android/m2repository/com/android/support/appcompat-v7/19.0.0/appcompat-v7-19.0.0.aar",
- "extras/android/m2repository/com/android/support/appcompat-v7/19.0.1/appcompat-v7-19.0.1.aar",
- "extras/android/m2repository/com/android/support/appcompat-v7/19.1.0/appcompat-v7-19.1.0.aar",
- "extras/android/m2repository/com/android/support/appcompat-v7/20.0.0/appcompat-v7-20.0.0.aar",
- "extras/android/m2repository/com/android/support/appcompat-v7/21.0.0/appcompat-v7-21.0.0.aar",
- "extras/android/m2repository/com/android/support/appcompat-v7/21.0.2/appcompat-v7-21.0.2.aar",
- "extras/android/m2repository/com/android/support/cardview-v7/21.0.0/cardview-v7-21.0.0.aar",
- "extras/android/m2repository/com/android/support/cardview-v7/21.0.2/cardview-v7-21.0.2.aar",
- "extras/android/m2repository/com/android/support/support-v13/20.0.0/support-v13-20.0.0.aar",
- "extras/android/m2repository/com/android/support/support-v13/21.0.0/support-v13-21.0.0.aar",
- "extras/android/m2repository/com/android/support/support-v13/21.0.2/support-v13-21.0.2.aar",
- "extras/android/m2repository/com/android/support/support-v4/20.0.0/support-v4-20.0.0.aar",
- "extras/android/m2repository/com/android/support/support-v4/21.0.0/support-v4-21.0.0.aar",
- "extras/android/m2repository/com/android/support/support-v4/21.0.2/support-v4-21.0.2.aar",
-
- // Google repository
- "extras/google/m2repository/com/google/android/gms/play-services/3.1.36/play-services-3.1.36.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/3.1.59/play-services-3.1.59.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/3.2.25/play-services-3.2.25.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/3.2.65/play-services-3.2.65.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/4.0.30/play-services-4.0.30.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/4.1.32/play-services-4.1.32.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/4.2.42/play-services-4.2.42.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/4.3.23/play-services-4.3.23.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/4.4.52/play-services-4.4.52.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/5.0.89/play-services-5.0.89.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/6.1.11/play-services-6.1.11.aar",
- "extras/google/m2repository/com/google/android/gms/play-services/6.1.71/play-services-6.1.71.aar",
- "extras/google/m2repository/com/google/android/gms/play-services-wearable/5.0.77/play-services-wearable-5.0.77.aar",
- "extras/google/m2repository/com/google/android/gms/play-services-wearable/6.1.11/play-services-wearable-6.1.11.aar",
- "extras/google/m2repository/com/google/android/gms/play-services-wearable/6.1.71/play-services-wearable-6.1.71.aar",
- "extras/google/m2repository/com/google/android/support/wearable/1.0.0/wearable-1.0.0.aar"
- };
-
- for (String path : paths) {
- File file = new File(mSdkDir, path.replace('/', File.separatorChar));
- File parent = file.getParentFile();
- if (!parent.exists()) {
- boolean ok = parent.mkdirs();
- assertTrue(ok);
- }
- try {
- boolean created = file.createNewFile();
- assertTrue(created);
- } catch (IOException e) {
- fail(e.toString());
- }
- }
- }
-
- return mSdkDir;
- }
-
- public void testGetOldValue() {
- assertEquals("11.0.2", getOldValue(DEPENDENCY,
- "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0",
- TEXT));
- assertNull(getOldValue(DEPENDENCY, "Bogus", TEXT));
- assertNull(getOldValue(DEPENDENCY, "bogus", TEXT));
- // targetSdkVersion 20, compileSdkVersion 19: Should replace targetVersion 20 with 19
- assertEquals("20", getOldValue(DEPENDENCY,
- "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)",
- TEXT));
- assertEquals("'19'", getOldValue(STRING_INTEGER,
- "Use an integer rather than a string here (replace '19' with just 19)", TEXT));
- assertEquals("android", getOldValue(DEPRECATED,
- "'android' is deprecated; use 'com.android.application' instead", TEXT));
- assertEquals("android-library", getOldValue(DEPRECATED,
- "'android-library' is deprecated; use 'com.android.library' instead", TEXT));
- assertEquals("packageName", getOldValue(DEPRECATED,
- "Deprecated: Replace 'packageName' with 'applicationId'", TEXT));
- assertEquals("packageNameSuffix", getOldValue(DEPRECATED,
- "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'", TEXT));
- assertEquals("18.0.0", getOldValue(DEPENDENCY,
- "Old buildToolsVersion 18.0.0; recommended version is 19.1 or later", TEXT));
- }
-
- public void testGetNewValue() {
- assertEquals("17.0.0", getNewValue(DEPENDENCY,
- "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0",
- TEXT));
- assertNull(getNewValue(DEPENDENCY,
- "A newer version of com.google.guava:guava than 11.0.2 is available", TEXT));
- assertNull(getNewValue(DEPENDENCY, "bogus", TEXT));
- // targetSdkVersion 20, compileSdkVersion 19: Should replace targetVersion 20 with 19
- assertEquals("19", getNewValue(DEPENDENCY,
- "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)",
- TEXT));
- assertEquals("19", getNewValue(STRING_INTEGER,
- "Use an integer rather than a string here (replace '19' with just 19)", TEXT));
- assertEquals("com.android.application", getNewValue(DEPRECATED,
- "'android' is deprecated; use 'com.android.application' instead", TEXT));
- assertEquals("com.android.library", getNewValue(DEPRECATED,
- "'android-library' is deprecated; use 'com.android.library' instead", TEXT));
- assertEquals("applicationId", getNewValue(DEPRECATED,
- "Deprecated: Replace 'packageName' with 'applicationId'", TEXT));
- assertEquals("applicationIdSuffix", getNewValue(DEPRECATED,
- "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'", TEXT));
- assertEquals("19.1", getNewValue(DEPENDENCY,
- "Old buildToolsVersion 18.0.0; recommended version is 19.1 or later", TEXT));
- }
-
- public void test() throws Exception {
- mEnabled = Sets.newHashSet(COMPATIBILITY, DEPRECATED, DEPENDENCY, PLUS);
- assertEquals(""
- + "build.gradle:25: Error: This support library should not use a lower version (13) than the targetSdkVersion (17) [GradleCompatible]\n"
- + " compile 'com.android.support:appcompat-v7:13.0.0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:1: Warning: 'android' is deprecated; use 'com.android.application' instead [GradleDeprecated]\n"
- + "apply plugin: 'android'\n"
- + "~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:5: Warning: Old buildToolsVersion 19.0.0; recommended version is 19.1 or later [GradleDependency]\n"
- + " buildToolsVersion \"19.0.0\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:24: Warning: A newer version of com.google.guava:guava than 11.0.2 is available: 18.0 [GradleDependency]\n"
- + " freeCompile 'com.google.guava:guava:11.0.2'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:25: Warning: A newer version of com.android.support:appcompat-v7 than 13.0.0 is available: 21.0.2 [GradleDependency]\n"
- + " compile 'com.android.support:appcompat-v7:13.0.0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:23: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:appcompat-v7:+) [GradleDynamicVersion]\n"
- + " compile 'com.android.support:appcompat-v7:+'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 5 warnings\n",
-
- lintProject("gradle/Dependencies.gradle=>build.gradle"));
- }
-
- public void testCompatibility() throws Exception {
- mEnabled = Collections.singleton(COMPATIBILITY);
- assertEquals(""
- + "build.gradle:16: Error: This support library should not use a lower version (18) than the targetSdkVersion (19) [GradleCompatible]\n"
- + " compile 'com.android.support:support-v4:18.0.0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject("gradle/Compatibility.gradle=>build.gradle"));
- }
-
- public void testIncompatiblePlugin() throws Exception {
- mEnabled = Collections.singleton(GRADLE_PLUGIN_COMPATIBILITY);
- assertEquals(""
- + "build.gradle:6: Error: You must use a newer version of the Android Gradle plugin. The minimum supported version is " + GRADLE_PLUGIN_MINIMUM_VERSION + " and the recommended version is " + GRADLE_PLUGIN_RECOMMENDED_VERSION + " [AndroidGradlePluginVersion]\n"
- + " classpath 'com.android.tools.build:gradle:0.1.0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject("gradle/IncompatiblePlugin.gradle=>build.gradle"));
- }
-
- public void testSetter() throws Exception {
- mEnabled = Collections.singleton(GRADLE_GETTER);
- assertEquals(""
- + "build.gradle:18: Error: Bad method name: pick a unique method name which does not conflict with the implicit getters for the defaultConfig properties. For example, try using the prefix compute- instead of get-. [GradleGetter]\n"
- + " versionCode getVersionCode\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:19: Error: Bad method name: pick a unique method name which does not conflict with the implicit getters for the defaultConfig properties. For example, try using the prefix compute- instead of get-. [GradleGetter]\n"
- + " versionName getVersionName\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
-
- lintProject("gradle/Setter.gradle=>build.gradle"));
- }
-
- public void testDependencies() throws Exception {
- mEnabled = Collections.singleton(DEPENDENCY);
- assertEquals(""
- + "build.gradle:5: Warning: Old buildToolsVersion 19.0.0; recommended version is 19.1 or later [GradleDependency]\n"
- + " buildToolsVersion \"19.0.0\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:24: Warning: A newer version of com.google.guava:guava than 11.0.2 is available: 18.0 [GradleDependency]\n"
- + " freeCompile 'com.google.guava:guava:11.0.2'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:25: Warning: A newer version of com.android.support:appcompat-v7 than 13.0.0 is available: 21.0.2 [GradleDependency]\n"
- + " compile 'com.android.support:appcompat-v7:13.0.0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 3 warnings\n",
-
- lintProject("gradle/Dependencies.gradle=>build.gradle"));
- }
-
- public void testLongHandDependencies() throws Exception {
- mEnabled = Collections.singleton(DEPENDENCY);
- assertEquals(""
- + "build.gradle:9: Warning: A newer version of com.android.support:support-v4 than 19.0 is available: 21.0.2 [GradleDependency]\n"
- + " compile group: 'com.android.support', name: 'support-v4', version: '19.0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("gradle/DependenciesProps.gradle=>build.gradle"));
- }
-
- public void testDependenciesMinSdkVersion() throws Exception {
- mEnabled = Collections.singleton(DEPENDENCY);
- assertEquals(""
- + "build.gradle:13: Warning: Using the appcompat library when minSdkVersion >= 14 and compileSdkVersion < 21 is not necessary [GradleDependency]\n"
- + " compile 'com.android.support:appcompat-v7:+'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("gradle/Dependencies14.gradle=>build.gradle"));
- }
-
- public void testDependenciesMinSdkVersionLollipop() throws Exception {
- mEnabled = Collections.singleton(DEPENDENCY);
- assertEquals("No warnings.",
- lintProject("gradle/Dependencies14_21.gradle=>build.gradle"));
- }
-
- public void testDependenciesNoMicroVersion() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=77594
- mEnabled = Collections.singleton(DEPENDENCY);
- assertEquals(""
- + "build.gradle:13: Warning: A newer version of com.google.code.gson:gson than 2.2 is available: 2.3 [GradleDependency]\n"
- + " compile 'com.google.code.gson:gson:2.2'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("gradle/DependenciesGson.gradle=>build.gradle"));
- }
-
- public void testPaths() throws Exception {
- mEnabled = Collections.singleton(PATH);
- assertEquals(""
- + "build.gradle:4: Warning: Do not use Windows file separators in .gradle files; use / instead [GradlePath]\n"
- + " compile files('my\\\\libs\\\\http.jar')\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:5: Warning: Avoid using absolute paths in .gradle files [GradlePath]\n"
- + " compile files('/libs/android-support-v4.jar')\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject("gradle/Paths.gradle=>build.gradle"));
- }
-
- public void testIdSuffix() throws Exception {
- mEnabled = Collections.singleton(PATH);
- assertEquals(""
- + "build.gradle:6: Warning: Package suffix should probably start with a \".\" [GradlePath]\n"
- + " applicationIdSuffix \"debug\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("gradle/IdSuffix.gradle=>build.gradle"));
- }
-
- public void testPackage() throws Exception {
- mEnabled = Collections.singleton(DEPRECATED);
- assertEquals(""
- + "build.gradle:5: Warning: Deprecated: Replace 'packageName' with 'applicationId' [GradleDeprecated]\n"
- + " packageName 'my.pkg'\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:9: Warning: Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix' [GradleDeprecated]\n"
- + " packageNameSuffix \".debug\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject("gradle/Package.gradle=>build.gradle"));
- }
-
- public void testPlus() throws Exception {
- mEnabled = Collections.singleton(PLUS);
- assertEquals(""
- + "build.gradle:9: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:appcompat-v7:+) [GradleDynamicVersion]\n"
- + " compile 'com.android.support:appcompat-v7:+'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:10: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:support-v4:21.0.+) [GradleDynamicVersion]\n"
- + " compile group: 'com.android.support', name: 'support-v4', version: '21.0.+'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject("gradle/Plus.gradle=>build.gradle"));
- }
-
- public void testStringInt() throws Exception {
- mEnabled = Collections.singleton(STRING_INTEGER);
- assertEquals(""
- + "build.gradle:4: Error: Use an integer rather than a string here (replace '19' with just 19) [StringShouldBeInt]\n"
- + " compileSdkVersion '19'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:7: Error: Use an integer rather than a string here (replace '8' with just 8) [StringShouldBeInt]\n"
- + " minSdkVersion '8'\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:8: Error: Use an integer rather than a string here (replace '16' with just 16) [StringShouldBeInt]\n"
- + " targetSdkVersion '16'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "3 errors, 0 warnings\n",
-
- lintProject("gradle/StringInt.gradle=>build.gradle"));
- }
-
- public void testSuppressLine2() throws Exception {
- mEnabled = null;
- assertEquals("No warnings.",
-
- lintProject("gradle/SuppressLine2.gradle=>build.gradle"));
- }
-
- public void testDeprecatedPluginId() throws Exception {
- mEnabled = Sets.newHashSet(DEPRECATED);
- assertEquals(""
- + "build.gradle:4: Warning: 'android' is deprecated; use 'com.android.application' instead [GradleDeprecated]\n"
- + "apply plugin: 'android'\n"
- + "~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:5: Warning: 'android-library' is deprecated; use 'com.android.library' instead [GradleDeprecated]\n"
- + "apply plugin: 'android-library'\n"
- + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject("gradle/DeprecatedPluginId.gradle=>build.gradle"));
- }
-
- public void testIgnoresGStringsInDependencies() throws Exception {
- mEnabled = null;
- assertEquals("No warnings.",
-
- lintProject("gradle/IgnoresGStringsInDependencies.gradle=>build.gradle"));
- }
-
- public void testAccidentalOctal() throws Exception {
- mEnabled = Collections.singleton(ACCIDENTAL_OCTAL);
- assertEquals(""
- + "build.gradle:13: Error: The leading 0 turns this number into octal which is probably not what was intended (interpreted as 8) [AccidentalOctal]\n"
- + " versionCode 010\n"
- + " ~~~~~~~~~~~~~~~\n"
- + "build.gradle:16: Error: The leading 0 turns this number into octal which is probably not what was intended (and it is not a valid octal number) [AccidentalOctal]\n"
- + " versionCode 01 // line suffix comments are not handled correctly\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
-
- lintProject("gradle/AccidentalOctal.gradle=>build.gradle"));
- }
-
- public void testBadPlayServicesVersion() throws Exception {
- mEnabled = Collections.singleton(COMPATIBILITY);
- assertEquals(""
- + "build.gradle:5: Error: Version 5.2.08 should not be used; the app can not be published with this version. Use version 6.1.71 instead. [GradleCompatible]\n"
- + " compile 'com.google.android.gms:play-services:5.2.08'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject("gradle/PlayServices.gradle=>build.gradle"));
- }
-
- public void testRemoteVersions() throws Exception {
- mEnabled = Collections.singleton(REMOTE_VERSION);
- try {
- HashMap<String, String> data = Maps.newHashMap();
- GradleDetector.sMockData = data;
- data.put("http://search.maven.org/solrsearch/select?q=g:%22joda-time%22+AND+a:%22joda-time%22&core=gav&rows=1&wt=json",
- "{\"responseHeader\":{\"status\":0,\"QTime\":1,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"joda-time\\\" AND a:\\\"joda-time\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"rows\":\"1\",\"version\":\"2.2\"}},\"response\":{\"numFound\":17,\"start\":0,\"docs\":[{\"id\":\"joda-time:joda-time:2.3\",\"g\":\"joda-time\",\"a\":\"joda-time\",\"v\":\"2.3\",\"p\":\"jar\",\"timestamp\":13 [...]
- data.put("http://search.maven.org/solrsearch/select?q=g:%22com.squareup.dagger%22+AND+a:%22dagger%22&core=gav&rows=1&wt=json",
- "{\"responseHeader\":{\"status\":0,\"QTime\":1,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"com.squareup.dagger\\\" AND a:\\\"dagger\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"rows\":\"1\",\"version\":\"2.2\"}},\"response\":{\"numFound\":5,\"start\":0,\"docs\":[{\"id\":\"com.squareup.dagger:dagger:1.2.1\",\"g\":\"com.squareup.dagger\",\"a\":\"dagger\",\"v\":\"1.2.1\",\"p\": [...]
-
- assertEquals(""
- + "build.gradle:9: Warning: A newer version of joda-time:joda-time than 2.1 is available: 2.3 [NewerVersionAvailable]\n"
- + " compile 'joda-time:joda-time:2.1'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:10: Warning: A newer version of com.squareup.dagger:dagger than 1.2.0 is available: 1.2.1 [NewerVersionAvailable]\n"
- + " compile 'com.squareup.dagger:dagger:1.2.0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject("gradle/RemoteVersions.gradle=>build.gradle"));
- } finally {
- GradleDetector.sMockData = null;
- }
- }
-
- public void testRemoteVersionsWithPreviews() throws Exception {
- // If the most recent version is a rc version, query for all versions
- mEnabled = Collections.singleton(REMOTE_VERSION);
- try {
- HashMap<String, String> data = Maps.newHashMap();
- GradleDetector.sMockData = data;
- data.put("http://search.maven.org/solrsearch/select?q=g:%22com.google.guava%22+AND+a:%22guava%22&core=gav&rows=1&wt=json",
- "{\"responseHeader\":{\"status\":0,\"QTime\":0,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"com.google.guava\\\" AND a:\\\"guava\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"rows\":\"1\",\"version\":\"2.2\"}},\"response\":{\"numFound\":38,\"start\":0,\"docs\":[{\"id\":\"com.google.guava:guava:18.0-rc1\",\"g\":\"com.google.guava\",\"a\":\"guava\",\"v\":\"18.0-rc1\",\"p\":\"bun [...]
- data.put("http://search.maven.org/solrsearch/select?q=g:%22com.google.guava%22+AND+a:%22guava%22&core=gav&wt=json",
- "{\"responseHeader\":{\"status\":0,\"QTime\":1,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"com.google.guava\\\" AND a:\\\"guava\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"version\":\"2.2\"}},\"response\":{\"numFound\":38,\"start\":0,\"docs\":[{\"id\":\"com.google.guava:guava:18.0-rc1\",\"g\":\"com.google.guava\",\"a\":\"guava\",\"v\":\"18.0-rc1\",\"p\":\"bundle\",\"timesta [...]
-
- assertEquals(""
- + "build.gradle:9: Warning: A newer version of com.google.guava:guava than 11.0.2 is available: 17.0 [NewerVersionAvailable]\n"
- + " compile 'com.google.guava:guava:11.0.2'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "build.gradle:10: Warning: A newer version of com.google.guava:guava than 16.0-rc1 is available: 18.0.0-rc1 [NewerVersionAvailable]\n"
- + " compile 'com.google.guava:guava:16.0-rc1'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject("gradle/RemoteVersions2.gradle=>build.gradle"));
- } finally {
- GradleDetector.sMockData = null;
- }
- }
-
- public void testPreviewVersions() throws Exception {
- mEnabled = Collections.singleton(DEPENDENCY);
- // This test only works when SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION contains
- // a preview string:
- if (!GRADLE_PLUGIN_RECOMMENDED_VERSION.startsWith("1.0.0-rc")) {
- return;
- }
- assertEquals(""
- + "build.gradle:6: Warning: A newer version of com.android.tools.build:gradle than 1.0.0-rc0 is available: " + GRADLE_PLUGIN_RECOMMENDED_VERSION + " [GradleDependency]\n"
- + " classpath 'com.android.tools.build:gradle:1.0.0-rc0'\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("gradle/PreviewDependencies.gradle=>build.gradle"));
- }
-
-
- public void testDependenciesInVariables() throws Exception {
- mEnabled = Collections.singleton(DEPENDENCY);
- assertEquals(""
- + "build.gradle:10: Warning: A newer version of com.google.android.gms:play-services-wearable than 5.0.77 is available: 6.1.71 [GradleDependency]\n"
- + " compile \"com.google.android.gms:play-services-wearable:${GPS_VERSION}\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("gradle/DependenciesVariable.gradle=>build.gradle"));
- }
-
- @Override
- protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
- @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
- if (issue == DEPENDENCY && message.startsWith("Using the appcompat library when ")) {
- // No data embedded in this specific message
- return;
- }
-
- // Issues we're supporting getOldFrom
- if (issue == DEPENDENCY
- || issue == STRING_INTEGER
- || issue == DEPRECATED
- || issue == PLUS) {
- assertNotNull("Could not extract message tokens from " + message,
- GradleDetector.getOldValue(issue, message, TEXT));
- }
-
- if (issue == DEPENDENCY
- || issue == STRING_INTEGER
- || issue == DEPRECATED) {
- assertNotNull("Could not extract message tokens from " + message,
- GradleDetector.getNewValue(issue, message, TEXT));
- }
-
- if (issue == COMPATIBILITY) {
- if (message.startsWith("Version ")) {
- assertNotNull("Could not extract message tokens from " + message,
- GradleDetector.getNewValue(issue, message, TEXT));
- }
- }
- }
-
- public void testGetNamedDependency() {
- assertEquals("com.android.support:support-v4:21.0.+", getNamedDependency(
- "group: 'com.android.support', name: 'support-v4', version: '21.0.+'"
- ));
- assertEquals("com.android.support:support-v4:21.0.+", getNamedDependency(
- "name:'support-v4', group: \"com.android.support\", version: '21.0.+'"
- ));
- assertEquals("junit:junit:4.+", getNamedDependency(
- "group: 'junit', name: 'junit', version: '4.+'"
- ));
- assertEquals("com.android.support:support-v4:19.0.+", getNamedDependency(
- "group: 'com.android.support', name: 'support-v4', version: '19.0.+'"
- ));
- assertEquals("com.google.guava:guava:11.0.1", getNamedDependency(
- "group: 'com.google.guava', name: 'guava', version: '11.0.1', transitive: false"
- ));
- assertEquals("com.google.api-client:google-api-client:1.6.0-beta", getNamedDependency(
- "group: 'com.google.api-client', name: 'google-api-client', version: '1.6.0-beta', transitive: false"
- ));
- assertEquals("org.robolectric:robolectric:2.3-SNAPSHOT", getNamedDependency(
- "group: 'org.robolectric', name: 'robolectric', version: '2.3-SNAPSHOT'"
- ));
- }
-
- // -------------------------------------------------------------------------------------------
- // Test infrastructure below here
- // -------------------------------------------------------------------------------------------
-
- static final Implementation IMPLEMENTATION = new Implementation(
- GroovyGradleDetector.class,
- Scope.GRADLE_SCOPE);
- static {
- for (Issue issue : new BuiltinIssueRegistry().getIssues()) {
- if (issue.getImplementation().getDetectorClass() == GradleDetector.class) {
- issue.setImplementation(IMPLEMENTATION);
- }
- }
- }
-
- @Override
- protected Detector getDetector() {
- return new GroovyGradleDetector();
- }
-
- private Set<Issue> mEnabled;
-
- @Override
- protected TestConfiguration getConfiguration(LintClient client, Project project) {
- return new TestConfiguration(client, project, null) {
- @Override
- public boolean isEnabled(@NonNull Issue issue) {
- return super.isEnabled(issue) && (mEnabled == null || mEnabled.contains(issue));
- }
- };
- }
-
- @Override
- protected TestLintClient createClient() {
- return new TestLintClient() {
- @Nullable
- @Override
- public File getSdkHome() {
- return getMockSupportLibraryInstallation();
- }
-
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- if (!"testDependenciesInVariables".equals(getName())) {
- return super.createProject(dir, referenceDir);
- }
-
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public Variant getCurrentVariant() {
- /*
- Simulate variant which has an AndroidLibrary with
- resolved coordinates
-
- com.google.android.gms:play-services-wearable:5.0.77"
- */
- MavenCoordinates coordinates = mock(MavenCoordinates.class);
- when(coordinates.getGroupId()).thenReturn("com.google.android.gms");
- when(coordinates.getArtifactId()).thenReturn("play-services-wearable");
- when(coordinates.getVersion()).thenReturn("5.0.77");
-
- AndroidLibrary library = mock(AndroidLibrary.class);
- when(library.getResolvedCoordinates()).thenReturn(coordinates);
- List<AndroidLibrary> libraries = Collections.singletonList(library);
-
- Dependencies dependencies = mock(Dependencies.class);
- when(dependencies.getLibraries()).thenReturn(libraries);
-
- AndroidArtifact artifact = mock(AndroidArtifact.class);
- when(artifact.getDependencies()).thenReturn(dependencies);
-
- Variant variant = mock(Variant.class);
- when(variant.getMainArtifact()).thenReturn(artifact);
- return variant;
- }
- };
- }
- };
- }
-
- // Copy of com.android.build.gradle.tasks.GroovyGradleDetector (with "static" added as
- // a modifier, and the unused field IMPLEMENTATION removed, and with fail(t.toString())
- // inserted into visitBuildScript's catch handler.
- //
- // THIS CODE DUPLICATION IS NOT AN IDEAL SITUATION! But, it's preferable to a lack of
- // tests.
- //
- // A more proper fix would be to extract the groovy detector into a library shared by
- // the testing framework and the gradle plugin.
-
- public static class GroovyGradleDetector extends GradleDetector {
- @Override
- public void visitBuildScript(@NonNull final Context context, Map<String, Object> sharedData) {
- try {
- visitQuietly(context, sharedData);
- } catch (Throwable t) {
- // ignore
- // Parsing the build script can involve class loading that we sometimes can't
- // handle. This happens for example when running lint in build-system/tests/api/.
- // This is a lint limitation rather than a user error, so don't complain
- // about these. Consider reporting a Issue#LINT_ERROR.
- fail(t.toString());
- }
- }
-
- private void visitQuietly(@NonNull final Context context,
- @SuppressWarnings("UnusedParameters") Map<String, Object> sharedData) {
- String source = context.getContents();
- if (source == null) {
- return;
- }
-
- List<ASTNode> astNodes = new AstBuilder().buildFromString(source);
- GroovyCodeVisitor visitor = new CodeVisitorSupport() {
- private List<MethodCallExpression> mMethodCallStack = Lists.newArrayList();
- @Override
- public void visitMethodCallExpression(MethodCallExpression expression) {
- mMethodCallStack.add(expression);
- super.visitMethodCallExpression(expression);
- Expression arguments = expression.getArguments();
- String parent = expression.getMethodAsString();
- String parentParent = getParentParent();
- if (arguments instanceof ArgumentListExpression) {
- ArgumentListExpression ale = (ArgumentListExpression)arguments;
- List<Expression> expressions = ale.getExpressions();
- if (expressions.size() == 1 &&
- expressions.get(0) instanceof ClosureExpression) {
- if (isInterestingBlock(parent, parentParent)) {
- ClosureExpression closureExpression =
- (ClosureExpression)expressions.get(0);
- Statement block = closureExpression.getCode();
- if (block instanceof BlockStatement) {
- BlockStatement bs = (BlockStatement)block;
- for (Statement statement : bs.getStatements()) {
- if (statement instanceof ExpressionStatement) {
- ExpressionStatement e = (ExpressionStatement)statement;
- if (e.getExpression() instanceof MethodCallExpression) {
- checkDslProperty(parent,
- (MethodCallExpression)e.getExpression(),
- parentParent);
- }
- } else if (statement instanceof ReturnStatement) {
- // Single item in block
- ReturnStatement e = (ReturnStatement)statement;
- if (e.getExpression() instanceof MethodCallExpression) {
- checkDslProperty(parent,
- (MethodCallExpression)e.getExpression(),
- parentParent);
- }
- }
- }
- }
- }
- }
- } else if (arguments instanceof TupleExpression) {
- if (isInterestingStatement(parent, parentParent)) {
- TupleExpression te = (TupleExpression) arguments;
- Map<String, String> namedArguments = Maps.newHashMap();
- List<String> unnamedArguments = Lists.newArrayList();
- for (Expression subExpr : te.getExpressions()) {
- if (subExpr instanceof NamedArgumentListExpression) {
- NamedArgumentListExpression nale = (NamedArgumentListExpression) subExpr;
- for (MapEntryExpression mae : nale.getMapEntryExpressions()) {
- namedArguments.put(mae.getKeyExpression().getText(),
- mae.getValueExpression().getText());
- }
- }
- }
- checkMethodCall(context, parent, parentParent, namedArguments, unnamedArguments, expression);
- }
- }
- assert !mMethodCallStack.isEmpty();
- assert mMethodCallStack.get(mMethodCallStack.size() - 1) == expression;
- mMethodCallStack.remove(mMethodCallStack.size() - 1);
- }
-
- private String getParentParent() {
- for (int i = mMethodCallStack.size() - 2; i >= 0; i--) {
- MethodCallExpression expression = mMethodCallStack.get(i);
- Expression arguments = expression.getArguments();
- if (arguments instanceof ArgumentListExpression) {
- ArgumentListExpression ale = (ArgumentListExpression)arguments;
- List<Expression> expressions = ale.getExpressions();
- if (expressions.size() == 1 &&
- expressions.get(0) instanceof ClosureExpression) {
- return expression.getMethodAsString();
- }
- }
- }
-
- return null;
- }
-
- private void checkDslProperty(String parent, MethodCallExpression c,
- String parentParent) {
- String property = c.getMethodAsString();
- if (isInterestingProperty(property, parent, getParentParent())) {
- String value = getText(c.getArguments());
- checkDslPropertyAssignment(context, property, value, parent, parentParent, c, c);
- }
- }
-
- private String getText(ASTNode node) {
- String source = context.getContents();
- Pair<Integer, Integer> offsets = getOffsets(node, context);
- return source.substring(offsets.getFirst(), offsets.getSecond());
- }
- };
-
- for (ASTNode node : astNodes) {
- node.visit(visitor);
- }
- }
-
- @NonNull
- private static Pair<Integer, Integer> getOffsets(ASTNode node, Context context) {
- if (node.getLastLineNumber() == -1 && node instanceof TupleExpression) {
- // Workaround: TupleExpressions yield bogus offsets, so use its
- // children instead
- TupleExpression exp = (TupleExpression) node;
- List<Expression> expressions = exp.getExpressions();
- if (!expressions.isEmpty()) {
- return Pair.of(
- getOffsets(expressions.get(0), context).getFirst(),
- getOffsets(expressions.get(expressions.size() - 1), context).getSecond());
- }
- }
- String source = context.getContents();
- assert source != null; // because we successfully parsed
- int start = 0;
- int end = source.length();
- int line = 1;
- int startLine = node.getLineNumber();
- int startColumn = node.getColumnNumber();
- int endLine = node.getLastLineNumber();
- int endColumn = node.getLastColumnNumber();
- int column = 1;
- for (int index = 0, len = end; index < len; index++) {
- if (line == startLine && column == startColumn) {
- start = index;
- }
- if (line == endLine && column == endColumn) {
- end = index;
- break;
- }
-
- char c = source.charAt(index);
- if (c == '\n') {
- line++;
- column = 1;
- } else {
- column++;
- }
- }
-
- return Pair.of(start, end);
- }
-
- @Override
- protected int getStartOffset(@NonNull Context context, @NonNull Object cookie) {
- ASTNode node = (ASTNode) cookie;
- Pair<Integer, Integer> offsets = getOffsets(node, context);
- return offsets.getFirst();
- }
-
- @Override
- protected Location createLocation(@NonNull Context context, @NonNull Object cookie) {
- ASTNode node = (ASTNode) cookie;
- Pair<Integer, Integer> offsets = getOffsets(node, context);
- int fromLine = node.getLineNumber() - 1;
- int fromColumn = node.getColumnNumber() - 1;
- int toLine = node.getLastLineNumber() - 1;
- int toColumn = node.getLastColumnNumber() - 1;
- return Location.create(context.file,
- new DefaultPosition(fromLine, fromColumn, offsets.getFirst()),
- new DefaultPosition(toLine, toColumn, offsets.getSecond()));
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
deleted file mode 100644
index aa52ba2..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
-public class HandlerDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new HandlerDetector();
- }
-
- public void testRegistered() throws Exception {
- assertEquals(
- "src/test/pkg/HandlerTest.java:12: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.Inner) [HandlerLeak]\n" +
- " public class Inner extends Handler { // ERROR\n" +
- " ~~~~~\n" +
- "src/test/pkg/HandlerTest.java:18: Warning: This Handler class should be static or leaks might occur (new android.os.Handler(){}) [HandlerLeak]\n" +
- " Handler anonymous = new Handler() { // ERROR\n" +
- " ^\n" +
- "0 errors, 2 warnings\n",
-
- lintProject(
- "bytecode/HandlerTest.java.txt=>src/test/pkg/HandlerTest.java",
- "bytecode/HandlerTest.class.data=>bin/classes/test/pkg/HandlerTest.class",
- "bytecode/HandlerTest$Inner.class.data=>bin/classes/test/pkg/HandlerTest$Inner.class",
- "bytecode/HandlerTest$StaticInner.class.data=>bin/classes/test/pkg/HandlerTest$StaticInner.class",
- "bytecode/HandlerTest$WithArbitraryLooper.class.data=>bin/classes/test/pkg/HandlerTest$WithArbitraryLooper.class",
- "bytecode/HandlerTest$1.class.data=>bin/classes/test/pkg/HandlerTest$1.class",
- "bytecode/HandlerTest$2.class.data=>bin/classes/test/pkg/HandlerTest$2.class"));
- }
-
- public void testSuppress() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- java("src/test/pkg/CheckActivity.java", ""
- + "package test.pkg;\n"
- + "import android.annotation.SuppressLint;\n"
- + "import android.app.Activity;\n"
- + "import android.os.Handler;\n"
- + "import android.os.Message;\n"
- + "\n"
- + "public class CheckActivity extends Activity {\n"
- + "\n"
- + " @SuppressWarnings(\"unused\")\n"
- + " @SuppressLint(\"HandlerLeak\")\n"
- + " Handler handler = new Handler() {\n"
- + "\n"
- + " public void handleMessage(Message msg) {\n"
- + "\n"
- + " }\n"
- + " };\n"
- + "\n"
- + "}")
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
deleted file mode 100644
index 8b9163a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class HardcodedValuesDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new HardcodedValuesDetector();
- }
-
- public void testStrings() throws Exception {
- assertEquals(
- "res/layout/accessibility.xml:3: Warning: [I18N] Hardcoded string \"Button\", should use @string resource [HardcodedText]\n" +
- " <Button android:text=\"Button\" android:id=\"@+id/button1\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/accessibility.xml:6: Warning: [I18N] Hardcoded string \"Button\", should use @string resource [HardcodedText]\n" +
- " <Button android:text=\"Button\" android:id=\"@+id/button2\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 2 warnings\n",
-
- lintFiles("res/layout/accessibility.xml"));
- }
-
- public void testMenus() throws Exception {
- assertEquals(
- "res/menu/menu.xml:7: Warning: [I18N] Hardcoded string \"My title 1\", should use @string resource [HardcodedText]\n" +
- " android:title=\"My title 1\">\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/menu/menu.xml:13: Warning: [I18N] Hardcoded string \"My title 2\", should use @string resource [HardcodedText]\n" +
- " android:title=\"My title 2\">\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 2 warnings\n",
-
- lintFiles("res/menu/menu.xml"));
- }
-
- public void testMenusOk() throws Exception {
- assertEquals(
- "No warnings.",
- lintFiles("res/menu/titles.xml"));
- }
-
- public void testSuppress() throws Exception {
- // All but one errors in the file contain ignore attributes - direct, inherited
- // and lists
- assertEquals(
- "res/layout/ignores.xml:61: Warning: [I18N] Hardcoded string \"Hardcoded\", should use @string resource [HardcodedText]\n" +
- " android:text=\"Hardcoded\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintFiles("res/layout/ignores.xml"));
- }
-
- public void testSuppressViaComment() throws Exception {
- assertEquals(""
- + "res/layout/ignores2.xml:51: Warning: [I18N] Hardcoded string \"Hardcoded\", should use @string resource [HardcodedText]\n"
- + " android:text=\"Hardcoded\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintFiles("res/layout/ignores2.xml"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
deleted file mode 100644
index 17703af..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
+++ /dev/null
@@ -1,812 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.build.FilterData;
-import com.android.build.OutputFile;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidArtifactOutput;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Project;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import org.mockito.stubbing.OngoingStubbing;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
- at SuppressWarnings("javadoc")
-public class IconDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new IconDetector();
- }
-
- private Set<Issue> mEnabled = new HashSet<Issue>();
- private boolean mAbbreviate;
-
- private static final Set<Issue> ALL = new HashSet<Issue>();
- static {
- ALL.add(IconDetector.DUPLICATES_CONFIGURATIONS);
- ALL.add(IconDetector.DUPLICATES_NAMES);
- ALL.add(IconDetector.GIF_USAGE);
- ALL.add(IconDetector.ICON_DENSITIES);
- ALL.add(IconDetector.ICON_DIP_SIZE);
- ALL.add(IconDetector.ICON_EXTENSION);
- ALL.add(IconDetector.ICON_LOCATION);
- ALL.add(IconDetector.ICON_MISSING_FOLDER);
- ALL.add(IconDetector.ICON_NODPI);
- ALL.add(IconDetector.ICON_COLORS);
- ALL.add(IconDetector.ICON_XML_AND_PNG);
- ALL.add(IconDetector.ICON_LAUNCHER_SHAPE);
- ALL.add(IconDetector.ICON_MIX_9PNG);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mAbbreviate = true;
- }
-
- @Override
- protected void configureDriver(LintDriver driver) {
- driver.setAbbreviating(mAbbreviate);
- }
-
- @Override
- protected TestConfiguration getConfiguration(LintClient client, Project project) {
- return new TestConfiguration(client, project, null) {
- @Override
- public boolean isEnabled(@NonNull Issue issue) {
- return super.isEnabled(issue) && mEnabled.contains(issue);
- }
- };
- }
-
- public void test() throws Exception {
- mEnabled = ALL;
- assertEquals(
- "res/drawable-mdpi/sample_icon.gif: Warning: Using the .gif format for bitmaps is discouraged [GifUsage]\n" +
- "res/drawable/ic_launcher.png: Warning: The ic_launcher.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable [IconDuplicatesConfig]\n" +
- " res/drawable-mdpi/ic_launcher.png: <No location-specific message\n" +
- "res/drawable/ic_launcher.png: Warning: Found bitmap drawable res/drawable/ic_launcher.png in densityless folder [IconLocation]\n" +
- "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: sample_icon.gif (found in drawable-mdpi) [IconDensities]\n" +
- "res: Warning: Missing density variation folders in res: drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
- "0 errors, 5 warnings\n" +
- "",
-
- lintProject(
- // Use minSDK4 to ensure that we get warnings about missing drawables
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/drawable/ic_launcher.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher.png",
- "res/drawable-mdpi/sample_icon.gif",
- // Make a dummy file named .svn to make sure it doesn't get seen as
- // an icon name
- "res/drawable-mdpi/sample_icon.gif=>res/drawable-hdpi/.svn",
- "res/drawable-hdpi/ic_launcher.png"));
- }
-
- public void testMixed() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_XML_AND_PNG);
- assertEquals(
- "res/drawable/background.xml: Warning: The following images appear both as density independent .xml files and as bitmap files: res/drawable-mdpi/background.png, res/drawable/background.xml [IconXmlAndPng]\n" +
- " res/drawable-mdpi/background.png: <No location-specific message\n" +
- "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "apicheck/minsdk4.xml=>res/drawable/background.xml",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/background.png"));
- }
-
- public void testApi1() throws Exception {
- mEnabled = ALL;
- assertEquals(
- "No warnings.",
-
- lintProject(
- // manifest file which specifies uses sdk = 2
- "apicheck/minsdk2.xml=>AndroidManifest.xml",
- "res/drawable/ic_launcher.png"));
- }
-
- public void test2() throws Exception {
- mEnabled = ALL;
- assertEquals(
- "res/drawable-hdpi/other.9.png: Warning: The following unrelated icon files have identical contents: appwidget_bg.9.png, other.9.png [IconDuplicates]\n" +
- " res/drawable-hdpi/appwidget_bg.9.png: <No location-specific message\n" +
- "res/drawable-hdpi/unrelated.png: Warning: The following unrelated icon files have identical contents: ic_launcher.png, unrelated.png [IconDuplicates]\n" +
- " res/drawable-hdpi/ic_launcher.png: <No location-specific message\n" +
- "res: Warning: Missing density variation folders in res: drawable-mdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
- "0 errors, 3 warnings\n",
-
- lintProject(
- "res/drawable-hdpi/unrelated.png",
- "res/drawable-hdpi/appwidget_bg.9.png",
- "res/drawable-hdpi/appwidget_bg_focus.9.png",
- "res/drawable-hdpi/other.9.png",
- "res/drawable-hdpi/ic_launcher.png"
- ));
- }
-
- public void testNoDpi() throws Exception {
- mEnabled = ALL;
- assertEquals(
- "res/drawable-mdpi/frame.png: Warning: The following images appear in both -nodpi and in a density folder: frame.png [IconNoDpi]\n" +
- "res/drawable-xlarge-nodpi-v11/frame.png: Warning: The frame.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable-nodpi, drawable-xlarge-nodpi-v11 [IconDuplicatesConfig]\n" +
- " res/drawable-nodpi/frame.png: <No location-specific message\n" +
- " res/drawable-mdpi/frame.png: <No location-specific message\n" +
- "res: Warning: Missing density variation folders in res: drawable-hdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
- "0 errors, 3 warnings\n" +
- "",
-
- lintProject(
- "res/drawable-mdpi/frame.png",
- "res/drawable-nodpi/frame.png",
- "res/drawable-xlarge-nodpi-v11/frame.png"));
- }
-
- public void testNoDpi2() throws Exception {
- mEnabled = ALL;
- // Having additional icon names in the no-dpi folder should not cause any complaints
- assertEquals(
- "res/drawable-xxxhdpi/frame.png: Warning: The image frame.png varies significantly in its density-independent (dip) size across the various density versions: drawable-ldpi/frame.png: 629x387 dp (472x290 px), drawable-mdpi/frame.png: 472x290 dp (472x290 px), drawable-hdpi/frame.png: 315x193 dp (472x290 px), drawable-xhdpi/frame.png: 236x145 dp (472x290 px), drawable-xxhdpi/frame.png: 157x97 dp (472x290 px), drawable-xxxhdpi/frame.png: 118x73 dp (472x290 px) [IconDipSize]\n" +
- " res/drawable-xxhdpi/frame.png: <No location-specific message\n" +
- " res/drawable-xhdpi/frame.png: <No location-specific message\n" +
- " res/drawable-hdpi/frame.png: <No location-specific message\n" +
- " res/drawable-mdpi/frame.png: <No location-specific message\n" +
- " res/drawable-ldpi/frame.png: <No location-specific message\n" +
- "res/drawable-xxxhdpi/frame.png: Warning: The following unrelated icon files have identical contents: frame.png, frame.png, frame.png, file1.png, file2.png, frame.png, frame.png, frame.png [IconDuplicates]\n" +
- " res/drawable-xxhdpi/frame.png: <No location-specific message\n" +
- " res/drawable-xhdpi/frame.png: <No location-specific message\n" +
- " res/drawable-nodpi/file2.png: <No location-specific message\n" +
- " res/drawable-nodpi/file1.png: <No location-specific message\n" +
- " res/drawable-mdpi/frame.png: <No location-specific message\n" +
- " res/drawable-ldpi/frame.png: <No location-specific message\n" +
- " res/drawable-hdpi/frame.png: <No location-specific message\n" +
- "0 errors, 2 warnings\n" +
- "",
-
- lintProject(
- "res/drawable-mdpi/frame.png=>res/drawable-mdpi/frame.png",
- "res/drawable-mdpi/frame.png=>res/drawable-hdpi/frame.png",
- "res/drawable-mdpi/frame.png=>res/drawable-ldpi/frame.png",
- "res/drawable-mdpi/frame.png=>res/drawable-xhdpi/frame.png",
- "res/drawable-mdpi/frame.png=>res/drawable-xxhdpi/frame.png",
- "res/drawable-mdpi/frame.png=>res/drawable-xxxhdpi/frame.png",
- "res/drawable-mdpi/frame.png=>res/drawable-nodpi/file1.png",
- "res/drawable-mdpi/frame.png=>res/drawable-nodpi/file2.png"));
- }
-
- public void testNoDpiMix() throws Exception {
- mEnabled = ALL;
- assertEquals(
- "res/drawable-mdpi/frame.xml: Warning: The following images appear in both -nodpi and in a density folder: frame.png, frame.xml [IconNoDpi]\n" +
- " res/drawable-mdpi/frame.png: <No location-specific message\n" +
- "res/drawable-nodpi/frame.xml: Warning: The following images appear both as density independent .xml files and as bitmap files: res/drawable-mdpi/frame.png, res/drawable-nodpi/frame.xml [IconXmlAndPng]\n" +
- " res/drawable-mdpi/frame.png: <No location-specific message\n" +
- "res: Warning: Missing density variation folders in res: drawable-hdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
- "0 errors, 3 warnings\n",
-
- lintProject(
- "res/drawable-mdpi/frame.png",
- "res/drawable/states.xml=>res/drawable-nodpi/frame.xml"));
- }
-
-
- public void testMixedFormat() throws Exception {
- mEnabled = ALL;
- // Test having a mixture of .xml and .png resources for the same name
- // Make sure we don't get:
- // drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: f.png (found in drawable-mdpi)
- // drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: f.png (found in drawable-mdpi)
- assertEquals(
- "res/drawable-xxxhdpi/f.xml: Warning: The following images appear both as density independent .xml files and as bitmap files: res/drawable-hdpi/f.xml, res/drawable-mdpi/f.png [IconXmlAndPng]\n" +
- " res/drawable-xxhdpi/f.xml: <No location-specific message\n" +
- " res/drawable-xhdpi/f.xml: <No location-specific message\n" +
- " res/drawable-mdpi/f.png: <No location-specific message\n" +
- " res/drawable-hdpi/f.xml: <No location-specific message\n" +
- "0 errors, 1 warnings\n",
-
- lintProject(
- "res/drawable-mdpi/frame.png=>res/drawable-mdpi/f.png",
- "res/drawable/states.xml=>res/drawable-hdpi/f.xml",
- "res/drawable/states.xml=>res/drawable-xhdpi/f.xml",
- "res/drawable/states.xml=>res/drawable-xxhdpi/f.xml",
- "res/drawable/states.xml=>res/drawable-xxxhdpi/f.xml"));
- }
-
- public void testMisleadingFileName() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_EXTENSION);
- assertEquals(
- "res/drawable-mdpi/frame.gif: Warning: Misleading file extension; named .gif but the file format is png [IconExtension]\n" +
- "res/drawable-mdpi/frame.jpg: Warning: Misleading file extension; named .jpg but the file format is png [IconExtension]\n" +
- "res/drawable-mdpi/myjpg.png: Warning: Misleading file extension; named .png but the file format is JPEG [IconExtension]\n" +
- "res/drawable-mdpi/sample_icon.jpeg: Warning: Misleading file extension; named .jpeg but the file format is gif [IconExtension]\n" +
- "res/drawable-mdpi/sample_icon.jpg: Warning: Misleading file extension; named .jpg but the file format is gif [IconExtension]\n" +
- "res/drawable-mdpi/sample_icon.png: Warning: Misleading file extension; named .png but the file format is gif [IconExtension]\n" +
- "0 errors, 6 warnings\n",
-
- lintProject(
- "res/drawable-mdpi/sample_icon.jpg=>res/drawable-mdpi/myjpg.jpg", // VALID
- "res/drawable-mdpi/sample_icon.jpg=>res/drawable-mdpi/myjpg.jpeg", // VALID
- "res/drawable-mdpi/frame.png=>res/drawable-mdpi/frame.gif",
- "res/drawable-mdpi/frame.png=>res/drawable-mdpi/frame.jpg",
- "res/drawable-mdpi/sample_icon.jpg=>res/drawable-mdpi/myjpg.png",
- "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.jpg",
- "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.jpeg",
- "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.png"));
- }
-
- public void testColors() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
- assertEquals(
- "res/drawable-mdpi/ic_menu_my_action.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
- "res/drawable-mdpi-v11/ic_stat_my_notification.png: Warning: Notification icons must be entirely white [IconColors]\n" +
- "res/drawable-mdpi-v9/ic_stat_my_notification2.png: Warning: Notification icons must be entirely white [IconColors]\n" +
- "0 errors, 3 warnings\n",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_menu_my_action.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi-v11/ic_stat_my_notification.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi-v9/ic_stat_my_notification2.png",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
- }
-
- public void testNotActionBarIcons() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
- assertEquals(
- "No warnings.",
-
- // No Java code designates the menu as an action bar menu
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "res/menu/menu.xml",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png", // Not action bar
- "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
- }
-
- public void testActionBarIcons() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
- assertEquals(
- "res/drawable-mdpi/icon1.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
- "res/drawable-mdpi/icon2.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
- "0 errors, 2 warnings\n",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "res/menu/menu.xml",
- "src/test/pkg/ActionBarTest.java.txt=>src/test/pkg/ActionBarTest.java",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png", // Not action bar
- "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
- }
-
- public void testOkActionBarIcons() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "res/menu/menu.xml",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon1.png",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png"));
- }
-
- public void testNotificationIcons() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
- assertEquals(
- "res/drawable-mdpi/icon1.png: Warning: Notification icons must be entirely white [IconColors]\n" +
- "res/drawable-mdpi/icon2.png: Warning: Notification icons must be entirely white [IconColors]\n" +
- "res/drawable-mdpi/icon3.png: Warning: Notification icons must be entirely white [IconColors]\n" +
- "res/drawable-mdpi/icon4.png: Warning: Notification icons must be entirely white [IconColors]\n" +
- "res/drawable-mdpi/icon5.png: Warning: Notification icons must be entirely white [IconColors]\n" +
- "0 errors, 5 warnings\n",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon4.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon5.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon6.png", // not a notification
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon7.png", // ditto
- "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
- }
-
- public void testOkNotificationIcons() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon1.png",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon3.png",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon4.png",
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon5.png"));
- }
-
- public void testExpectedSize() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_EXPECTED_SIZE);
- assertEquals(
- "res/drawable-mdpi/ic_launcher.png: Warning: Incorrect icon size for drawable-mdpi/ic_launcher.png: expected 48x48, but was 24x24 [IconExpectedSize]\n" +
- "res/drawable-mdpi/icon1.png: Warning: Incorrect icon size for drawable-mdpi/icon1.png: expected 32x32, but was 48x48 [IconExpectedSize]\n" +
- "res/drawable-mdpi/icon3.png: Warning: Incorrect icon size for drawable-mdpi/icon3.png: expected 24x24, but was 48x48 [IconExpectedSize]\n" +
- "0 errors, 3 warnings\n",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
- "res/menu/menu.xml",
- "src/test/pkg/ActionBarTest.java.txt=>src/test/pkg/ActionBarTest.java",
-
- // 3 wrong-sized icons:
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png",
- "res/drawable-mdpi/stat_notify_alarm.png=>res/drawable-mdpi/ic_launcher.png",
-
- // OK sizes
- "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png",
- "res/drawable-mdpi/stat_notify_alarm.png=>res/drawable-mdpi/icon4.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png"
- ));
- }
-
- public void testAbbreviate() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_DENSITIES);
- assertEquals(
- "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: " +
- "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png, " +
- "ic_launcher3.png... (6 more) [IconDensities]\n" +
- "res/drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: " +
- "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png, " +
- "ic_launcher3.png... (6 more) [IconDensities]\n" +
- "0 errors, 2 warnings\n",
-
- lintProject(
- // Use minSDK4 to ensure that we get warnings about missing drawables
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/drawable/ic_launcher.png=>res/drawable-hdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-xhdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher3.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher4.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher5.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher6.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher7.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher8.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher9.webp",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher10.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher11.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher12.png"
- ));
- }
-
- public void testShowAll() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_DENSITIES);
- mAbbreviate = false;
- assertEquals(
- "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: " +
- "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png, " +
- "ic_launcher3.png, ic_launcher4.png, ic_launcher5.png, ic_launcher6.png, " +
- "ic_launcher7.png, ic_launcher8.png, ic_launcher9.png [IconDensities]\n" +
- "res/drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: " +
- "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png," +
- " ic_launcher3.png, ic_launcher4.png, ic_launcher5.png, ic_launcher6.png, " +
- "ic_launcher7.png, ic_launcher8.png, ic_launcher9.png [IconDensities]\n" +
- "0 errors, 2 warnings\n",
-
- lintProject(
- // Use minSDK4 to ensure that we get warnings about missing drawables
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/drawable/ic_launcher.png=>res/drawable-hdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-xhdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher3.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher4.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher5.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher6.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher7.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher8.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher9.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher10.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher11.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher12.png"
- ));
- }
-
- public void testIgnoreMissingFolders() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_DENSITIES);
- assertEquals(
- "No warnings.",
-
- lintProject(
- // Use minSDK4 to ensure that we get warnings about missing drawables
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "ignoremissing.xml=>lint.xml",
- "res/drawable/ic_launcher.png=>res/drawable-hdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher1.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png"
- ));
- }
-
- public void testSquareLauncher() throws Exception {
- mEnabled = Collections.singleton(IconDetector.ICON_LAUNCHER_SHAPE);
- assertEquals(
- "res/drawable-hdpi/ic_launcher_filled.png: Warning: Launcher icons should not fill every pixel of their square region; see the design guide for details [IconLauncherShape]\n" +
- "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/drawable-hdpi/filled.png=>res/drawable-hdpi/ic_launcher_filled.png",
- "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/ic_launcher_2.gif"
- ));
- }
-
- public void testMixNinePatch() throws Exception {
- // https://code.google.com/p/android/issues/detail?id=43075
- mEnabled = Collections.singleton(IconDetector.ICON_MIX_9PNG);
- assertEquals(""
- + "res/drawable-mdpi/ic_launcher_filled.png: Warning: The files ic_launcher_filled.png and ic_launcher_filled.9.png clash; both will map to @drawable/ic_launcher_filled [IconMixedNinePatch]\n"
- + " res/drawable-hdpi/ic_launcher_filled.png: <No location-specific message\n"
- + " res/drawable-hdpi/ic_launcher_filled.9.png: <No location-specific message\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/drawable-hdpi/filled.png=>res/drawable-mdpi/ic_launcher_filled.png",
- "res/drawable-hdpi/filled.png=>res/drawable-hdpi/ic_launcher_filled.png",
- "res/drawable-hdpi/filled.png=>res/drawable-hdpi/ic_launcher_filled.9.png",
- "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/ic_launcher_2.gif"
- ));
- }
-
- public void test67486() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=67486
- mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
- assertEquals("No warnings.",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "res/drawable-xhdpi/ic_stat_notify.png=>res/drawable-xhdpi/ic_stat_notify.png"
- ));
- }
-
- public void testDuplicatesWithDpNames() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=74584
- mEnabled = Collections.singleton(IconDetector.DUPLICATES_NAMES);
- assertEquals("No warnings.",
-
- lintProject(
- "res/drawable-hdpi/unrelated.png=>res/drawable-mdpi/foo_72dp.png",
- "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_36dp.png"
- ));
- }
-
- public void testClaimedSize() throws Exception {
- // Check that icons which declare a dp size actually correspond to that dp size
- mEnabled = Collections.singleton(IconDetector.ICON_DIP_SIZE);
- assertEquals(""
- + "res/drawable-xhdpi/foo_30dp.png: Warning: Suspicious file name foo_30dp.png: The implied 30 dp size does not match the actual dp size (pixel size 72\u00d772 in a drawable-xhdpi folder computes to 36\u00d736 dp) [IconDipSize]\n"
- + "res/drawable-mdpi/foo_80dp.png: Warning: Suspicious file name foo_80dp.png: The implied 80 dp size does not match the actual dp size (pixel size 72\u00d772 in a drawable-mdpi folder computes to 72\u00d772 dp) [IconDipSize]\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "res/drawable-hdpi/unrelated.png=>res/drawable-mdpi/foo_72dp.png", // ok
- "res/drawable-hdpi/unrelated.png=>res/drawable-mdpi/foo_80dp.png", // wrong
- "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_36dp.png", // ok
- "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_35dp.png", // ~ok
- "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_30dp.png" // wrong
- ));
- }
-
- public void testResConfigs1() throws Exception {
- // resConfigs in the Gradle model sets up the specific set of resource configs
- // that are included in the packaging: we use this to limit the set of required
- // densities
- mEnabled = Sets.newHashSet(IconDetector.ICON_DENSITIES, IconDetector.ICON_MISSING_FOLDER);
- assertEquals(""
- + "res: Warning: Missing density variation folders in res: drawable-hdpi [IconMissingDensityFolder]\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "res/drawable-mdpi/frame.png",
- "res/drawable-nodpi/frame.png",
- "res/drawable-xlarge-nodpi-v11/frame.png"));
- }
-
- public void testResConfigs2() throws Exception {
- mEnabled = Sets.newHashSet(IconDetector.ICON_DENSITIES, IconDetector.ICON_MISSING_FOLDER);
- assertEquals(""
- + "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: sample_icon.gif (found in drawable-mdpi) [IconDensities]\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- // Use minSDK4 to ensure that we get warnings about missing drawables
- "apicheck/minsdk4.xml=>AndroidManifest.xml",
- "res/drawable/ic_launcher.png",
- "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher.png",
- "res/drawable/ic_launcher.png=>res/drawable-xhdpi/ic_launcher.png",
- "res/drawable-mdpi/sample_icon.gif",
- "res/drawable-hdpi/ic_launcher.png"));
- }
-
- public void testSplits1() throws Exception {
- // splits in the Gradle model sets up the specific set of resource configs
- // that are included in the packaging: we use this to limit the set of required
- // densities
- mEnabled = Sets.newHashSet(IconDetector.ICON_DENSITIES, IconDetector.ICON_MISSING_FOLDER);
- assertEquals(""
- + "res: Warning: Missing density variation folders in res: drawable-hdpi [IconMissingDensityFolder]\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "res/drawable-mdpi/frame.png",
- "res/drawable-nodpi/frame.png",
- "res/drawable-xlarge-nodpi-v11/frame.png"));
- }
-
- @Override
- protected TestLintClient createClient() {
- String testName = getName();
- if (testName.startsWith("testResConfigs")) {
- return createClientForTestResConfigs();
- } else if (testName.startsWith("testSplits")) {
- return createClientForTestSplits();
- } else {
- return super.createClient();
- }
- }
-
- private TestLintClient createClientForTestResConfigs() {
-
- // Set up a mock project model for the resource configuration test(s)
- // where we provide a subset of densities to be included
-
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public AndroidProject getGradleProjectModel() {
- /*
- Simulate variant freeBetaDebug in this setup:
- defaultConfig {
- ...
- resConfigs "mdpi"
- }
- flavorDimensions "pricing", "releaseType"
- productFlavors {
- beta {
- flavorDimension "releaseType"
- resConfig "en"
- resConfigs "nodpi", "hdpi"
- }
- normal { flavorDimension "releaseType" }
- free { flavorDimension "pricing" }
- paid { flavorDimension "pricing" }
- }
- */
- ProductFlavor flavorFree = mock(ProductFlavor.class);
- when(flavorFree.getName()).thenReturn("free");
- when(flavorFree.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorNormal = mock(ProductFlavor.class);
- when(flavorNormal.getName()).thenReturn("normal");
- when(flavorNormal.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorPaid = mock(ProductFlavor.class);
- when(flavorPaid.getName()).thenReturn("paid");
- when(flavorPaid.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorBeta = mock(ProductFlavor.class);
- when(flavorBeta.getName()).thenReturn("beta");
- List<String> resConfigs = Arrays.asList("hdpi", "en", "nodpi");
- when(flavorBeta.getResourceConfigurations()).thenReturn(resConfigs);
-
- ProductFlavor defaultFlavor = mock(ProductFlavor.class);
- when(defaultFlavor.getName()).thenReturn("main");
- when(defaultFlavor.getResourceConfigurations()).thenReturn(
- Collections.singleton("mdpi"));
-
- ProductFlavorContainer containerBeta =
- mock(ProductFlavorContainer.class);
- when(containerBeta.getProductFlavor()).thenReturn(flavorBeta);
-
- ProductFlavorContainer containerFree =
- mock(ProductFlavorContainer.class);
- when(containerFree.getProductFlavor()).thenReturn(flavorFree);
-
- ProductFlavorContainer containerPaid =
- mock(ProductFlavorContainer.class);
- when(containerPaid.getProductFlavor()).thenReturn(flavorPaid);
-
- ProductFlavorContainer containerNormal =
- mock(ProductFlavorContainer.class);
- when(containerNormal.getProductFlavor()).thenReturn(flavorNormal);
-
- ProductFlavorContainer defaultContainer =
- mock(ProductFlavorContainer.class);
- when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
-
- List<ProductFlavorContainer> containers = Arrays.asList(
- containerPaid, containerFree, containerNormal, containerBeta
- );
-
- AndroidProject project = mock(AndroidProject.class);
- when(project.getProductFlavors()).thenReturn(containers);
- when(project.getDefaultConfig()).thenReturn(defaultContainer);
- return project;
- }
-
- @Nullable
- @Override
- public Variant getCurrentVariant() {
- List<String> productFlavorNames = Arrays.asList("free", "beta");
- Variant mock = mock(Variant.class);
- when(mock.getProductFlavors()).thenReturn(productFlavorNames);
- return mock;
- }
- };
- }
- };
- }
-
- private TestLintClient createClientForTestSplits() {
-
- // Set up a mock project model for the resource configuration test(s)
- // where we provide a subset of densities to be included
-
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public AndroidProject getGradleProjectModel() {
- /*
- Simulate variant debug in this setup:
- splits {
- density {
- enable true
- reset()
- include "mdpi", "hdpi"
- }
- }
- */
-
- ProductFlavor defaultFlavor = mock(ProductFlavor.class);
- when(defaultFlavor.getName()).thenReturn("main");
- when(defaultFlavor.getResourceConfigurations()).thenReturn(
- Collections.<String>emptyList());
-
- ProductFlavorContainer defaultContainer =
- mock(ProductFlavorContainer.class);
- when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
-
- AndroidProject project = mock(AndroidProject.class);
- when(project.getProductFlavors()).thenReturn(
- Collections.<ProductFlavorContainer>emptyList());
- when(project.getDefaultConfig()).thenReturn(defaultContainer);
- return project;
- }
-
- @Nullable
- @Override
- public Variant getCurrentVariant() {
- Collection<AndroidArtifactOutput> outputs = Lists.newArrayList();
-
- outputs.add(createAndroidArtifactOutput("", ""));
- outputs.add(createAndroidArtifactOutput("DENSITY", "mdpi"));
- outputs.add(createAndroidArtifactOutput("DENSITY", "hdpi"));
-
- AndroidArtifact mainArtifact = mock(AndroidArtifact.class);
- when(mainArtifact.getOutputs()).thenReturn(outputs);
-
- List<String> productFlavorNames = Collections.emptyList();
- Variant mock = mock(Variant.class);
- when(mock.getProductFlavors()).thenReturn(productFlavorNames);
- when(mock.getMainArtifact()).thenReturn(mainArtifact);
- return mock;
- }
-
- private AndroidArtifactOutput createAndroidArtifactOutput(
- @NonNull String filterType,
- @NonNull String identifier) {
- AndroidArtifactOutput artifactOutput = mock(
- AndroidArtifactOutput.class);
-
- OutputFile outputFile = mock(OutputFile.class);
- if (filterType.isEmpty()) {
- when(outputFile.getFilterTypes())
- .thenReturn(Collections.<String>emptyList());
- when(outputFile.getFilters())
- .thenReturn(Collections.<FilterData>emptyList());
- } else {
- when(outputFile.getFilterTypes())
- .thenReturn(Collections.singletonList(filterType));
- List<FilterData> filters = Lists.newArrayList();
- FilterData filter = mock(FilterData.class);
- when(filter.getFilterType()).thenReturn(filterType);
- when(filter.getIdentifier()).thenReturn(identifier);
- filters.add(filter);
- when(outputFile.getFilters()).thenReturn(filters);
- }
-
- // Work around wildcard capture
- //when(artifactOutput.getOutputs()).thenReturn(outputFiles);
- List<OutputFile> outputFiles = Collections.singletonList(outputFile);
- OngoingStubbing<? extends Collection<? extends OutputFile>> when = when(
- artifactOutput.getOutputs());
- //noinspection unchecked,RedundantCast
- ((OngoingStubbing<Collection<? extends OutputFile>>) (OngoingStubbing<?>) when)
- .thenReturn(outputFiles);
-
- return artifactOutput;
- }
- };
- }
- };
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
deleted file mode 100644
index 02cddae..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class InvalidPackageDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new InvalidPackageDetector();
- }
-
- public void testUnsupportedJavaLibraryCode() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=39109
- assertEquals(
- "libs/unsupported.jar: Error: Invalid package reference in library; not included in Android: java.awt. Referenced from test.pkg.LibraryClass. [InvalidPackage]\n" +
- "libs/unsupported.jar: Error: Invalid package reference in library; not included in Android: javax.swing. Referenced from test.pkg.LibraryClass. [InvalidPackage]\n" +
- "2 errors, 0 warnings\n",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml",
- "apicheck/unsupported.jar.data=>libs/unsupported.jar"
- ));
- }
-
- public void testOk() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/classpath=>.classpath",
- "apicheck/minsdk2.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
- "bytecode/GetterTest.jar.data=>libs/GetterTest.jar",
- "bytecode/classes.jar=>libs/classes.jar"
- ));
- }
-
- public void testLibraryInJavax() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml",
- "bytecode/javax.jar.data=>libs/javax.jar"
- ));
- }
-
- public void testAnnotationProcessors1() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=64014
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml",
- "bytecode/butterknife-2.0.1.jar.data=>libs/butterknife-2.0.1.jar"
- ));
- }
-
- public void testAnnotationProcessors2() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=64014
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "apicheck/layout.xml=>res/layout/layout.xml",
- "apicheck/themes.xml=>res/values/themes.xml",
- "apicheck/themes.xml=>res/color/colors.xml",
- "bytecode/dagger-compiler-1.2.1-subset.jar.data=>libs/dagger-compiler-1.2.1.jar"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
deleted file mode 100644
index 60ca2a0..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class JavaPerformanceDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new JavaPerformanceDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/JavaPerformanceTest.java:28: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " new String(\"foo\");\n" +
- " ~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:29: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " String s = new String(\"bar\");\n" +
- " ~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:103: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " new String(\"flag me\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:109: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " new String(\"flag me\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:112: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " Bitmap.createBitmap(100, 100, null);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:113: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " android.graphics.Bitmap.createScaledBitmap(null, 100, 100, false);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:114: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " BitmapFactory.decodeFile(null);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:116: Warning: Avoid object allocations during draw operations: Use Canvas.getClipBounds(Rect) instead of Canvas.getClipBounds() which allocates a temporary Rect [DrawAllocation]\n" +
- " canvas.getClipBounds(); // allocates on your behalf\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:140: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
- " new String(\"foo\");\n" +
- " ~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:70: Warning: Use new SparseArray<String>(...) instead for better performance [UseSparseArrays]\n" +
- " Map<Integer, String> myMap = new HashMap<Integer, String>();\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:72: Warning: Use new SparseBooleanArray(...) instead for better performance [UseSparseArrays]\n" +
- " Map<Integer, Boolean> myBoolMap = new HashMap<Integer, Boolean>();\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:74: Warning: Use new SparseIntArray(...) instead for better performance [UseSparseArrays]\n" +
- " Map<Integer, Integer> myIntMap = new java.util.HashMap<Integer, Integer>();\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:190: Warning: Use new SparseIntArray(...) instead for better performance [UseSparseArrays]\n" +
- " new SparseArray<Integer>(); // Use SparseIntArray instead\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:192: Warning: Use new SparseBooleanArray(...) instead for better performance [UseSparseArrays]\n" +
- " new SparseArray<Boolean>(); // Use SparseBooleanArray instead\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:201: Warning: Use new SparseArray<String>(...) instead for better performance [UseSparseArrays]\n" +
- " Map<Byte, String> myByteMap = new HashMap<Byte, String>();\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:33: Warning: Use Integer.valueOf(5) instead [UseValueOf]\n" +
- " Integer i = new Integer(5);\n" +
- " ~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:145: Warning: Use Integer.valueOf(42) instead [UseValueOf]\n" +
- " Integer i1 = new Integer(42);\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:146: Warning: Use Long.valueOf(42L) instead [UseValueOf]\n" +
- " Long l1 = new Long(42L);\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:147: Warning: Use Boolean.valueOf(true) instead [UseValueOf]\n" +
- " Boolean b1 = new Boolean(true);\n" +
- " ~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:148: Warning: Use Character.valueOf('c') instead [UseValueOf]\n" +
- " Character c1 = new Character('c');\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:149: Warning: Use Float.valueOf(1.0f) instead [UseValueOf]\n" +
- " Float f1 = new Float(1.0f);\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/JavaPerformanceTest.java:150: Warning: Use Double.valueOf(1.0) instead [UseValueOf]\n" +
- " Double d1 = new Double(1.0);\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "0 errors, 22 warnings\n",
-
- lintProject("src/test/pkg/JavaPerformanceTest.java.txt=>" +
- "src/test/pkg/JavaPerformanceTest.java"));
- }
-
- public void testLongSparseArray() throws Exception {
- assertEquals(""
- + "src/test/pkg/LongSparseArray.java:10: Warning: Use new LongSparseArray(...) instead for better performance [UseSparseArrays]\n"
- + " Map<Long, String> myStringMap = new HashMap<Long, String>();\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk17.xml=>AndroidManifest.xml",
- "src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java"));
- }
-
- public void testLongSparseSupportLibArray() throws Exception {
- assertEquals(""
- + "src/test/pkg/LongSparseArray.java:10: Warning: Use new android.support.v4.util.LongSparseArray(...) instead for better performance [UseSparseArrays]\n"
- + " Map<Long, String> myStringMap = new HashMap<Long, String>();\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java",
- "bytecode/classes.jar=>libs/android-support-v4.jar"));
- }
-
- public void testNoLongSparseArray() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java"));
- }
-
- public void testSparseLongArray1() throws Exception {
- assertEquals(""
- + "src/test/pkg/SparseLongArray.java:10: Warning: Use new SparseLongArray(...) instead for better performance [UseSparseArrays]\n"
- + " Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk19.xml=>AndroidManifest.xml",
- "src/test/pkg/SparseLongArray.java.txt=>src/test/pkg/SparseLongArray.java"));
- }
-
- public void testSparseLongArray2() throws Exception {
- // Note -- it's offering a SparseArray, not a SparseLongArray!
- assertEquals(""
- + "src/test/pkg/SparseLongArray.java:10: Warning: Use new SparseArray<Long>(...) instead for better performance [UseSparseArrays]\n"
- + " Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "src/test/pkg/SparseLongArray.java.txt=>src/test/pkg/SparseLongArray.java"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java
deleted file mode 100644
index 211af9d..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class LayoutConsistencyDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new LayoutConsistencyDetector();
- }
-
- public void test() throws Exception {
- assertEquals(""
- + "res/layout/layout1.xml:11: Warning: The id \"button1\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout) [InconsistentLayout]\n"
- + " android:id=\"@+id/button1\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/layout1.xml:38: Warning: The id \"button4\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout) [InconsistentLayout]\n"
- + " android:id=\"@+id/button4\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
- "wrongid/layout1.xml=>res/layout/layout1.xml",
- "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
- ));
- }
-
- public void testSuppress() throws Exception {
- // Same as unit test above, but button1 is suppressed with tools:ignore; button4 is not
- assertEquals(""
- + "res/layout/layout1.xml:56: Warning: The id \"button4\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout) [InconsistentLayout]\n"
- + " android:id=\"@+id/button4\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
- "wrongid/layout1_ignore.xml=>res/layout/layout1.xml",
- "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
- ));
- }
-
- public void test2() throws Exception {
- assertEquals(""
- + "res/layout/layout1.xml:11: Warning: The id \"button1\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout, layout-sw600dp, layout-sw600dp-land) [InconsistentLayout]\n"
- + " android:id=\"@+id/button1\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + " res/layout-sw600dp/layout1.xml:11: Occurrence in layout-sw600dp\n"
- + " res/layout-sw600dp-land/layout1.xml:11: Occurrence in layout-sw600dp-land\n"
- + "res/layout/layout1.xml:38: Warning: The id \"button4\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout, layout-sw600dp, layout-sw600dp-land) [InconsistentLayout]\n"
- + " android:id=\"@+id/button4\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + " res/layout-sw600dp/layout1.xml:38: Occurrence in layout-sw600dp\n"
- + " res/layout-sw600dp-land/layout1.xml:38: Occurrence in layout-sw600dp-land\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
- "wrongid/layout1.xml=>res/layout/layout1.xml",
- "wrongid/layout1.xml=>res/layout-sw600dp/layout1.xml",
- "wrongid/layout1.xml=>res/layout-sw600dp-land/layout1.xml",
- "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
- ));
- }
-
- public void test3() throws Exception {
- assertEquals(""
- + "res/layout/layout1.xml:11: Warning: The id \"button1\" in layout \"layout1\" is only present in the following layout configurations: layout (missing from layout-sw600dp, layout-sw600dp-land, layout-xlarge) [InconsistentLayout]\n"
- + " android:id=\"@+id/button1\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/layout1.xml:38: Warning: The id \"button4\" in layout \"layout1\" is only present in the following layout configurations: layout (missing from layout-sw600dp, layout-sw600dp-land, layout-xlarge) [InconsistentLayout]\n"
- + " android:id=\"@+id/button4\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
- "wrongid/layout1.xml=>res/layout/layout1.xml",
- "wrongid/layout2.xml=>res/layout-sw600dp/layout1.xml",
- "wrongid/layout2.xml=>res/layout-sw600dp-land/layout1.xml",
- "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
- ));
- }
-
- public void testNoJavaRefs() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "wrongid/layout1.xml=>res/layout/layout1.xml",
- "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
deleted file mode 100644
index ca1f290..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.StringReader;
-
-public class LayoutInflationDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new LayoutInflationDetector();
- }
-
- public void test() throws Exception {
- assertEquals(""
- + "src/test/pkg/LayoutInflationTest.java:13: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
- + " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
- + " ~~~~\n"
- + "src/test/pkg/LayoutInflationTest.java:14: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
- + " convertView = mInflater.inflate(R.layout.your_layout, null, true);\n"
- + " ~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "src/test/pkg/LayoutInflationTest.java.txt=>src/test/pkg/LayoutInflationTest.java",
- "res/layout/textsize.xml=>res/layout/your_layout.xml",
- "res/layout/listseparator.xml=>res/layout-port/your_layout.xml"));
- }
-
- public void testNoLayoutParams() throws Exception {
- assertEquals("No warnings.",
-
- lintProject(
- "src/test/pkg/LayoutInflationTest.java.txt=>src/test/pkg/LayoutInflationTest.java",
- "res/layout/listseparator.xml=>res/layout/your_layout.xml"));
- }
-
- public void testHasLayoutParams() throws IOException, XmlPullParserException {
- assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader("")));
- assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader("<LinearLayout/>")));
- assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " android:orientation=\"vertical\" >\n"
- + "\n"
- + " <include\n"
- + " android:layout_width=\"wrap_content\"\n"
- + " android:layout_height=\"wrap_content\"\n"
- + " layout=\"@layout/layoutcycle1\" />\n"
- + "\n"
- + "</LinearLayout>")));
-
-
- assertTrue(LayoutInflationDetector.hasLayoutParams(new StringReader(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " android:layout_width=\"match_parent\"\n"
- + " android:layout_height=\"match_parent\"\n"
- + " android:orientation=\"vertical\" >\n"
- + "\n"
- + " <include\n"
- + " android:layout_width=\"wrap_content\"\n"
- + " android:layout_height=\"wrap_content\"\n"
- + " layout=\"@layout/layoutcycle1\" />\n"
- + "\n"
- + "</LinearLayout>")));
- }
-
- public void testSuppressed() throws Exception {
- assertEquals("No warnings.",
-
- lintProject(
- "src/test/pkg/LayoutInflationTest_ignored.java.txt=>src/test/pkg/LayoutInflationTest.java",
- "res/layout/textsize.xml=>res/layout/your_layout.xml",
- "res/layout/listseparator.xml=>res/layout-port/your_layout.xml"));
- }
-}
-
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java
deleted file mode 100644
index d34ce36..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class LocaleDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new LocaleDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/LocaleTest.java:11: Warning: Implicitly using the default locale is a common source of bugs: Use toUpperCase(Locale) instead [DefaultLocale]\n" +
- " System.out.println(\"WRONG\".toUpperCase());\n" +
- " ~~~~~~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:16: Warning: Implicitly using the default locale is a common source of bugs: Use toLowerCase(Locale) instead [DefaultLocale]\n" +
- " System.out.println(\"WRONG\".toLowerCase());\n" +
- " ~~~~~~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:20: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
- " String.format(\"WRONG: %f\", 1.0f); // Implies locale\n" +
- " ~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:21: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
- " String.format(\"WRONG: %1$f\", 1.0f);\n" +
- " ~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:22: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
- " String.format(\"WRONG: %e\", 1.0f);\n" +
- " ~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:23: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
- " String.format(\"WRONG: %d\", 1.0f);\n" +
- " ~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:24: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
- " String.format(\"WRONG: %g\", 1.0f);\n" +
- " ~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:25: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
- " String.format(\"WRONG: %g\", 1.0f);\n" +
- " ~~~~~~\n" +
- "src/test/pkg/LocaleTest.java:26: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n" +
- " String.format(\"WRONG: %1$tm %1$te,%1$tY\",\n" +
- " ~~~~~~\n" +
- "0 errors, 9 warnings\n",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/LocaleTest.java.txt=>src/test/pkg/LocaleTest.java",
- "bytecode/LocaleTest.class.data=>bin/classes/test/pkg/LocaleTest.class"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java
deleted file mode 100644
index 7187b6c..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class LogDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new LogDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/LogTest.java:33: Error: Mismatched tags: the d() and isLoggable() calls typically should pass the same tag: TAG1 versus TAG2 [LogTagMismatch]\n" +
- " Log.d(TAG2, \"message\"); // warn: mismatched tags!\n" +
- " ~~~~\n" +
- " src/test/pkg/LogTest.java:32: Conflicting tag\n" +
- "src/test/pkg/LogTest.java:36: Error: Mismatched tags: the d() and isLoggable() calls typically should pass the same tag: \"my_tag\" versus \"other_tag\" [LogTagMismatch]\n" +
- " Log.d(\"other_tag\", \"message\"); // warn: mismatched tags!\n" +
- " ~~~~~~~~~~~\n" +
- " src/test/pkg/LogTest.java:35: Conflicting tag\n" +
- "src/test/pkg/LogTest.java:80: Error: Mismatched logging levels: when checking isLoggable level DEBUG, the corresponding log call should be Log.d, not Log.v [LogTagMismatch]\n" +
- " Log.v(TAG1, \"message\"); // warn: wrong level\n" +
- " ~\n" +
- " src/test/pkg/LogTest.java:79: Conflicting tag\n" +
- "src/test/pkg/LogTest.java:83: Error: Mismatched logging levels: when checking isLoggable level DEBUG, the corresponding log call should be Log.d, not Log.v [LogTagMismatch]\n" +
- " Log.v(TAG1, \"message\"); // warn: wrong level\n" +
- " ~\n" +
- " src/test/pkg/LogTest.java:82: Conflicting tag\n" +
- "src/test/pkg/LogTest.java:86: Error: Mismatched logging levels: when checking isLoggable level VERBOSE, the corresponding log call should be Log.v, not Log.d [LogTagMismatch]\n" +
- " Log.d(TAG1, \"message\"); // warn? verbose is a lower logging level, which includes debug\n" +
- " ~\n" +
- " src/test/pkg/LogTest.java:85: Conflicting tag\n" +
- "src/test/pkg/LogTest.java:53: Error: The logging tag can be at most 23 characters, was 43 (really_really_really_really_really_long_tag) [LongLogTag]\n" +
- " Log.d(\"really_really_really_really_really_long_tag\", \"message\"); // error: too long\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/LogTest.java:59: Error: The logging tag can be at most 23 characters, was 24 (123456789012345678901234) [LongLogTag]\n" +
- " Log.d(TAG24, \"message\"); // error: too long\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/LogTest.java:60: Error: The logging tag can be at most 23 characters, was 39 (MyReallyReallyReallyReallyReallyLongTag) [LongLogTag]\n" +
- " Log.d(LONG_TAG, \"message\"); // error: way too long\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/LogTest.java:64: Error: The logging tag can be at most 23 characters, was 39 (MyReallyReallyReallyReallyReallyLongTag) [LongLogTag]\n" +
- " Log.d(LOCAL_TAG, \"message\"); // error: too long\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/LogTest.java:67: Error: The logging tag can be at most 23 characters, was 28 (1234567890123456789012MyTag1) [LongLogTag]\n" +
- " Log.d(TAG22 + TAG1, \"message\"); // error: too long\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/LogTest.java:68: Error: The logging tag can be at most 23 characters, was 27 (1234567890123456789012MyTag) [LongLogTag]\n" +
- " Log.d(TAG22 + \"MyTag\", \"message\"); // error: too long\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/LogTest.java:21: Warning: The log call Log.i(...) should be conditional: surround with if (Log.isLoggable(...)) or if (BuildConfig.DEBUG) { ... } [LogConditional]\n" +
- " Log.i(TAG1, \"message\" + m); // error: unconditional w/ computation\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/LogTest.java:22: Warning: The log call Log.i(...) should be conditional: surround with if (Log.isLoggable(...)) or if (BuildConfig.DEBUG) { ... } [LogConditional]\n" +
- " Log.i(TAG1, toString()); // error: unconditional w/ computation\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "11 errors, 2 warnings\n",
-
- lintProject(
- "src/test/pkg/LogTest.java.txt=>src/test/pkg/LogTest.java",
- // stub for type resolution
- "src/test/pkg/Log.java.txt=>src/android/util/Log.java"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
deleted file mode 100644
index 31fedb1..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
+++ /dev/null
@@ -1,945 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ApiVersion;
-import com.android.builder.model.BuildType;
-import com.android.builder.model.BuildTypeContainer;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.SourceProvider;
-import com.android.builder.model.SourceProviderContainer;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Project;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
- at SuppressWarnings("javadoc")
-public class ManifestDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new ManifestDetector();
- }
-
- private Set<Issue> mEnabled = new HashSet<Issue>();
-
- @Override
- protected TestConfiguration getConfiguration(LintClient client, Project project) {
- return new TestConfiguration(client, project, null) {
- @Override
- public boolean isEnabled(@NonNull Issue issue) {
- return super.isEnabled(issue) && mEnabled.contains(issue);
- }
- };
- }
-
- public void testOrderOk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ORDER);
- assertEquals(
- "No warnings.",
- lintProject(
- "AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testBrokenOrder() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ORDER);
- assertEquals(
- "AndroidManifest.xml:16: Warning: <uses-sdk> tag appears after <application> tag [ManifestOrder]\n" +
- " <uses-sdk android:minSdkVersion=\"Froyo\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "broken-manifest.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testMissingUsesSdk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
- assertEquals(
- "AndroidManifest.xml: Warning: Manifest should specify a minimum API level with <uses-sdk android:minSdkVersion=\"?\" />; if it really supports all versions of Android set it to 1. [UsesMinSdkAttributes]\n" +
- "0 errors, 1 warnings\n",
- lintProject(
- "missingusessdk.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testMissingUsesSdkInGradle() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
- assertEquals(""
- + "No warnings.",
- lintProject("missingusessdk.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>build.gradle")); // dummy; only name counts
- }
-
- public void testMissingMinSdk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
- assertEquals(
- "AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a minimum API level with android:minSdkVersion=\"?\" [UsesMinSdkAttributes]\n" +
- " <uses-sdk android:targetSdkVersion=\"10\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "missingmin.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testMissingTargetSdk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
- assertEquals(
- "AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\" [UsesMinSdkAttributes]\n" +
- " <uses-sdk android:minSdkVersion=\"10\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n",
- lintProject(
- "missingtarget.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testOldTargetSdk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.TARGET_NEWER);
- assertEquals(
- "AndroidManifest.xml:7: Warning: Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details. [OldTargetApi]\n" +
- " <uses-sdk android:minSdkVersion=\"10\" android:targetSdkVersion=\"14\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n",
- lintProject(
- "oldtarget.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testMultipleSdk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.MULTIPLE_USES_SDK);
- assertEquals(
- "AndroidManifest.xml:8: Error: There should only be a single <uses-sdk> element in the manifest: merge these together [MultipleUsesSdk]\n" +
- " <uses-sdk android:targetSdkVersion=\"14\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " AndroidManifest.xml:7: Also appears here\n" +
- " AndroidManifest.xml:9: Also appears here\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "multiplesdk.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testWrongLocation() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.WRONG_PARENT);
- assertEquals(
- "AndroidManifest.xml:8: Error: The <uses-sdk> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <uses-sdk android:minSdkVersion=\"Froyo\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:9: Error: The <uses-permission> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <uses-permission />\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:10: Error: The <permission> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <permission />\n" +
- " ~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:11: Error: The <permission-tree> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <permission-tree />\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:12: Error: The <permission-group> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <permission-group />\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:14: Error: The <uses-sdk> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <uses-sdk />\n" +
- " ~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:15: Error: The <uses-configuration> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <uses-configuration />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:16: Error: The <uses-feature> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <uses-feature />\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:17: Error: The <supports-screens> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <supports-screens />\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:18: Error: The <compatible-screens> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <compatible-screens />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:19: Error: The <supports-gl-texture> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
- " <supports-gl-texture />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:24: Error: The <uses-library> element must be a direct child of the <application> element [WrongManifestParent]\n" +
- " <uses-library />\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:25: Error: The <activity> element must be a direct child of the <application> element [WrongManifestParent]\n" +
- " <activity android:name=\".HelloWorld\"\n" +
- " ^\n" +
- "13 errors, 0 warnings\n" +
- "",
-
- lintProject("broken-manifest2.xml=>AndroidManifest.xml"));
- }
-
- public void testDuplicateActivity() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
- assertEquals(
- "AndroidManifest.xml:16: Error: Duplicate registration for activity com.example.helloworld.HelloWorld [DuplicateActivity]\n" +
- " <activity android:name=\"com.example.helloworld.HelloWorld\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n" +
- "",
-
- lintProject(
- "duplicate-manifest.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testDuplicateActivityAcrossSourceSets() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
- File master = getProjectDir("MasterProject",
- // Master project
- "AndroidManifest.xml=>AndroidManifest.xml",
- "multiproject/main-merge.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
- );
- File library = getProjectDir("LibraryProject",
- // Library project
- "AndroidManifest.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>res/values/strings.xml"
- );
- assertEquals("No warnings.",
- checkLint(Arrays.asList(master, library)));
- }
-
- public void testIgnoreDuplicateActivity() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "duplicate-manifest-ignore.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testAllowBackup() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(
- "AndroidManifest.xml:9: Warning: Should explicitly set android:allowBackup to " +
- "true or false (it's true by default, and that can have some security " +
- "implications for the application's data) [AllowBackup]\n" +
- " <application\n" +
- " ^\n" +
- "0 errors, 1 warnings\n",
- lintProject(
- "AndroidManifest.xml",
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testAllowBackupOk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(
- "No warnings.",
- lintProject(
- "allowbackup.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testAllowBackupOk2() throws Exception {
- // Requires build api >= 4
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(
- "No warnings.",
- lintProject(
- "apicheck/minsdk1.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testAllowBackupOk3() throws Exception {
- // Not flagged in library projects
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(
- "No warnings.",
- lintProject(
- "AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "res/values/strings.xml"));
- }
-
- public void testAllowIgnore() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(
- "No warnings.",
- lintProject(
- "allowbackup_ignore.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testDuplicatePermissions() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.UNIQUE_PERMISSION);
- assertEquals(
- "AndroidManifest.xml:12: Error: Permission name SEND_SMS is not unique (appears in both foo.permission.SEND_SMS and bar.permission.SEND_SMS) [UniquePermission]\n" +
- " <permission android:name=\"bar.permission.SEND_SMS\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " AndroidManifest.xml:9: Previous permission here\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "duplicate_permissions1.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testDuplicatePermissionsMultiProject() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.UNIQUE_PERMISSION);
-
- File master = getProjectDir("MasterProject",
- // Master project
- "duplicate_permissions2.xml=>AndroidManifest.xml",
- "multiproject/main-merge.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
- );
- File library = getProjectDir("LibraryProject",
- // Library project
- "duplicate_permissions3.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>res/values/strings.xml"
- );
- assertEquals(
- "LibraryProject/AndroidManifest.xml:9: Error: Permission name SEND_SMS is not unique (appears in both foo.permission.SEND_SMS and bar.permission.SEND_SMS) [UniquePermission]\n" +
- " <permission android:name=\"bar.permission.SEND_SMS\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n",
-
- checkLint(Arrays.asList(master, library)));
- }
-
- public void testMissingVersion() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
- assertEquals(""
- + "AndroidManifest.xml:2: Warning: Should set android:versionCode to specify the application version [MissingVersion]\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + "^\n"
- + "AndroidManifest.xml:2: Warning: Should set android:versionName to specify the application version [MissingVersion]\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + "^\n"
- + "0 errors, 2 warnings\n",
- lintProject("no_version.xml=>AndroidManifest.xml"));
- }
-
- public void testVersionNotMissingInGradleProjects() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
- assertEquals(""
- + "No warnings.",
- lintProject("no_version.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>build.gradle")); // dummy; only name counts
- }
-
- public void testIllegalReference() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ILLEGAL_REFERENCE);
- assertEquals(""
- + "AndroidManifest.xml:4: Warning: The android:versionCode cannot be a resource url, it must be a literal integer [IllegalResourceRef]\n"
- + " android:versionCode=\"@dimen/versionCode\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:7: Warning: The android:minSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
- + " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:7: Warning: The android:targetSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
- + " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 3 warnings\n",
-
- lintProject("illegal_version.xml=>AndroidManifest.xml"));
- }
-
- public void testDuplicateUsesFeature() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_USES_FEATURE);
- assertEquals(
- "AndroidManifest.xml:11: Warning: Duplicate declaration of uses-feature android.hardware.camera [DuplicateUsesFeature]\n" +
- " <uses-feature android:name=\"android.hardware.camera\"/>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n",
- lintProject(
- "duplicate_uses_feature.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testDuplicateUsesFeatureOk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_USES_FEATURE);
- assertEquals(
- "No warnings.",
- lintProject(
- "duplicate_uses_feature_ok.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testMissingApplicationIcon() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
- assertEquals(
- "AndroidManifest.xml:9: Warning: Should explicitly set android:icon, there is no default [MissingApplicationIcon]\n" +
- " <application\n" +
- " ^\n" +
- "0 errors, 1 warnings\n",
- lintProject(
- "missing_application_icon.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testMissingApplicationIconInLibrary() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
- assertEquals(
- "No warnings.",
- lintProject(
- "missing_application_icon.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "res/values/strings.xml"));
- }
-
- public void testMissingApplicationIconOk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
- assertEquals(
- "No warnings.",
- lintProject(
- "AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testDeviceAdmin() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.DEVICE_ADMIN);
- assertEquals(""
- + "AndroidManifest.xml:31: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
- + " <meta-data android:name=\"android.app.device_admin\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:44: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
- + " <meta-data android:name=\"android.app.device_admin\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:56: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
- + " <meta-data android:name=\"android.app.device_admin\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 3 warnings\n",
- lintProject("deviceadmin.xml=>AndroidManifest.xml"));
- }
-
- public void testMockLocations() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
- assertEquals(""
- + "AndroidManifest.xml:9: Error: Mock locations should only be requested in a test or debug-specific manifest file (typically src/debug/AndroidManifest.xml) [MockLocation]\n"
- + " <uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\" /> \n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- "mock_location.xml=>AndroidManifest.xml",
- "mock_location.xml=>debug/AndroidManifest.xml",
- "mock_location.xml=>test/AndroidManifest.xml",
- "multiproject/library.properties=>build.gradle")); // dummy; only name counts
- // TODO: When we have an instantiatable gradle model, test with real model and verify
- // that a manifest file in a debug build type does not get flagged.
- }
-
- public void testMockLocationsOk() throws Exception {
- // Not a Gradle project
- mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
- assertEquals(""
- + "No warnings.",
- lintProject(
- "mock_location.xml=>AndroidManifest.xml"));
- }
-
- public void testGradleOverrides() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
- assertEquals(""
- + "AndroidManifest.xml:4: Warning: This versionCode value (1) is not used; it is always overridden by the value specified in the Gradle build script (2) [GradleOverrides]\n"
- + " android:versionCode=\"1\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:5: Warning: This versionName value (1.0) is not used; it is always overridden by the value specified in the Gradle build script (MyName) [GradleOverrides]\n"
- + " android:versionName=\"1.0\" >\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:7: Warning: This minSdkVersion value (14) is not used; it is always overridden by the value specified in the Gradle build script (5) [GradleOverrides]\n"
- + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:7: Warning: This targetSdkVersion value (17) is not used; it is always overridden by the value specified in the Gradle build script (16) [GradleOverrides]\n"
- + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 4 warnings\n",
- lintProject(
- "gradle_override.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>build.gradle")); // dummy; only name counts
- }
-
- public void testGradleOverridesOk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
- // (See custom logic in #createClient which returns -1/null for the merged flavor
- // from this test, and not from testGradleOverrides)
- assertEquals(""
- + "No warnings.",
- lintProject(
- "gradle_override.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>build.gradle")); // dummy; only name counts
- }
-
- public void testManifestPackagePlaceholder() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
- assertEquals(""
- + "AndroidManifest.xml:3: Warning: Cannot use placeholder for the package in the manifest; set applicationId in build.gradle instead [GradleOverrides]\n"
- + " package=\"${packageName}\" >\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "gradle_override_placeholder.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>build.gradle")); // dummy; only name counts
- }
-
- public void testMipMap() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.MIPMAP);
- assertEquals("No warnings.",
-
- lintProject(
- "mipmap.xml=>AndroidManifest.xml"));
- }
-
- public void testMipMapWithDensityFiltering() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.MIPMAP);
- assertEquals(""
- + "AndroidManifest.xml:9: Warning: Should use @mipmap instead of @drawable for launcher icons [MipmapIcons]\n"
- + " android:icon=\"@drawable/ic_launcher\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "AndroidManifest.xml:14: Warning: Should use @mipmap instead of @drawable for launcher icons [MipmapIcons]\n"
- + " android:icon=\"@drawable/activity1\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "mipmap.xml=>AndroidManifest.xml"));
- }
-
- public void testFullBackupContentBoolean() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals("No warnings.",
-
- lintProjectIncrementally(
- "AndroidManifest.xml",
- xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:fullBackupContent=\"true\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testFullBackupContentMissing() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(""
- + "AndroidManifest.xml:7: Warning: Missing <full-backup-content> resource [AllowBackup]\n"
- + " android:fullBackupContent=\"@xml/backup\"\n"
- + " ~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProjectIncrementally(
- "AndroidManifest.xml",
- xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:fullBackupContent=\"@xml/backup\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testFullBackupContentOk() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals("No warnings.",
-
- lintProjectIncrementally(
- "AndroidManifest.xml",
- xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + "\n"
- + " <application\n"
- + " android:allowBackup=\"true\"\n"
- + " android:fullBackupContent=\"@xml/backup\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n"),
- xml("res/xml/backup.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<full-backup-content>\n"
- + " <include domain=\"file\" path=\"dd\"/>\n"
- + " <exclude domain=\"file\" path=\"dd/fo3o.txt\"/>\n"
- + " <exclude domain=\"file\" path=\"dd/ss/foo.txt\"/>\n"
- + "</full-backup-content>")));
- }
-
- public void testHasBackupSpecifiedInTarget23() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals("No warnings.",
-
- lintProject(
- xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + " <uses-sdk android:targetSdkVersion=\"23\" />"
- + "\n"
- + " <application\n"
- + " android:fullBackupContent=\"no\"\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testMissingBackupInTarget23() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(""
- + "AndroidManifest.xml:5: Warning: Should explicitly set android:fullBackupContent to true or false to opt-in to or out of full app data back-up and restore, or alternatively to an @xml resource which specifies which files to backup [AllowBackup]\n"
- + " <application\n"
- + " ^\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + " <uses-sdk android:targetSdkVersion=\"23\" />"
- + "\n"
- + " <application\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testMissingBackupWithoutGcmPreTarget23() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals("No warnings.",
-
- lintProject(
- xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + " <uses-sdk android:targetSdkVersion=\"21\" />"
- + "\n"
- + " <application\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- public void testMissingBackupWithGcmPreTarget23() throws Exception {
- mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
- assertEquals(""
- + "AndroidManifest.xml:5: Warning: Should explicitly set android:fullBackupContent to avoid backing up the GCM device specific regId. [AllowBackup]\n"
- + " <application\n"
- + " ^\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"com.example.helloworld\" >\n"
- + " <uses-sdk android:targetSdkVersion=\"21\" />"
- + "\n"
- + " <application\n"
- + " android:label=\"@string/app_name\"\n"
- + " android:theme=\"@style/AppTheme\" >"
- + " <receiver\n"
- + " android:name=\".GcmBroadcastReceiver\"\n"
- + " android:permission=\"com.google.android.c2dm.permission.SEND\" >\n"
- + " <intent-filter>\n"
- + " <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n"
- + " <category android:name=\"com.example.gcm\" />\n"
- + " </intent-filter>\n"
- + " </receiver>\n"
- + " </application>\n"
- + "\n"
- + "</manifest>\n")));
- }
-
- // Custom project which locates all manifest files in the project rather than just
- // being hardcoded to the root level
-
- @Override
- protected TestLintClient createClient() {
- if ("testMipMapWithDensityFiltering".equals(getName())) {
- // Set up a mock project model for the resource configuration test(s)
- // where we provide a subset of densities to be included
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public AndroidProject getGradleProjectModel() {
- /*
- Simulate variant freeBetaDebug in this setup:
- defaultConfig {
- ...
- resConfigs "cs"
- }
- flavorDimensions "pricing", "releaseType"
- productFlavors {
- beta {
- flavorDimension "releaseType"
- resConfig "en", "de"
- resConfigs "nodpi", "hdpi"
- }
- normal { flavorDimension "releaseType" }
- free { flavorDimension "pricing" }
- paid { flavorDimension "pricing" }
- }
- */
- ProductFlavor flavorFree = mock(ProductFlavor.class);
- when(flavorFree.getName()).thenReturn("free");
- when(flavorFree.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorNormal = mock(ProductFlavor.class);
- when(flavorNormal.getName()).thenReturn("normal");
- when(flavorNormal.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorPaid = mock(ProductFlavor.class);
- when(flavorPaid.getName()).thenReturn("paid");
- when(flavorPaid.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorBeta = mock(ProductFlavor.class);
- when(flavorBeta.getName()).thenReturn("beta");
- List<String> resConfigs = Arrays.asList("hdpi", "en", "de", "nodpi");
- when(flavorBeta.getResourceConfigurations()).thenReturn(resConfigs);
-
- ProductFlavor defaultFlavor = mock(ProductFlavor.class);
- when(defaultFlavor.getName()).thenReturn("main");
- when(defaultFlavor.getResourceConfigurations()).thenReturn(
- Collections.singleton("cs"));
-
- ProductFlavorContainer containerBeta =
- mock(ProductFlavorContainer.class);
- when(containerBeta.getProductFlavor()).thenReturn(flavorBeta);
-
- ProductFlavorContainer containerFree =
- mock(ProductFlavorContainer.class);
- when(containerFree.getProductFlavor()).thenReturn(flavorFree);
-
- ProductFlavorContainer containerPaid =
- mock(ProductFlavorContainer.class);
- when(containerPaid.getProductFlavor()).thenReturn(flavorPaid);
-
- ProductFlavorContainer containerNormal =
- mock(ProductFlavorContainer.class);
- when(containerNormal.getProductFlavor()).thenReturn(flavorNormal);
-
- ProductFlavorContainer defaultContainer =
- mock(ProductFlavorContainer.class);
- when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
-
- List<ProductFlavorContainer> containers = Arrays.asList(
- containerPaid, containerFree, containerNormal, containerBeta
- );
-
- AndroidProject project = mock(AndroidProject.class);
- when(project.getProductFlavors()).thenReturn(containers);
- when(project.getDefaultConfig()).thenReturn(defaultContainer);
- return project;
- }
-
- @Nullable
- @Override
- public Variant getCurrentVariant() {
- List<String> productFlavorNames = Arrays.asList("free", "beta");
- Variant mock = mock(Variant.class);
- when(mock.getProductFlavors()).thenReturn(productFlavorNames);
- return mock;
- }
- };
- }
- };
- }
- if (mEnabled.contains(ManifestDetector.MOCK_LOCATION)) {
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @NonNull
- @Override
- public List<File> getManifestFiles() {
- if (mManifestFiles == null) {
- mManifestFiles = Lists.newArrayList();
- addManifestFiles(mDir);
- }
-
- return mManifestFiles;
- }
-
- private void addManifestFiles(File dir) {
- if (dir.getName().equals(ANDROID_MANIFEST_XML)) {
- mManifestFiles.add(dir);
- } else if (dir.isDirectory()) {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File file : files) {
- addManifestFiles(file);
- }
- }
- }
- }
-
- @NonNull SourceProvider createSourceProvider(File manifest) {
- SourceProvider provider = mock(SourceProvider.class);
- when(provider.getManifestFile()).thenReturn(manifest);
- return provider;
- }
-
- @Nullable
- @Override
- public AndroidProject getGradleProjectModel() {
- if (!isGradleProject()) {
- return null;
- }
-
- File main = new File(mDir, ANDROID_MANIFEST_XML);
- File debug = new File(mDir, "debug" + File.separator + ANDROID_MANIFEST_XML);
- File test = new File(mDir, "test" + File.separator + ANDROID_MANIFEST_XML);
-
- SourceProvider defaultSourceProvider = createSourceProvider(main);
- SourceProvider debugSourceProvider = createSourceProvider(debug);
- SourceProvider testSourceProvider = createSourceProvider(test);
-
- ProductFlavorContainer defaultConfig = mock(ProductFlavorContainer.class);
- when(defaultConfig.getSourceProvider()).thenReturn(defaultSourceProvider);
-
- BuildType buildType = mock(BuildType.class);
- when(buildType.isDebuggable()).thenReturn(true);
-
- BuildTypeContainer buildTypeContainer = mock(BuildTypeContainer.class);
- when(buildTypeContainer.getBuildType()).thenReturn(buildType);
- when(buildTypeContainer.getSourceProvider()).thenReturn(debugSourceProvider);
- List<BuildTypeContainer> buildTypes = Lists.newArrayList(buildTypeContainer);
-
- SourceProviderContainer extraProvider = mock(SourceProviderContainer.class);
- when(extraProvider.getArtifactName()).thenReturn(AndroidProject.ARTIFACT_ANDROID_TEST);
- when(extraProvider.getSourceProvider()).thenReturn(testSourceProvider);
- List<SourceProviderContainer> extraProviders = Lists.newArrayList(extraProvider);
-
- ProductFlavorContainer productFlavorContainer = mock(ProductFlavorContainer.class);
- when(productFlavorContainer.getExtraSourceProviders()).thenReturn(extraProviders);
- List<ProductFlavorContainer> productFlavors = Lists.newArrayList(productFlavorContainer);
-
- AndroidProject project = mock(AndroidProject.class);
- when(project.getDefaultConfig()).thenReturn(defaultConfig);
- when(project.getBuildTypes()).thenReturn(buildTypes);
- when(project.getProductFlavors()).thenReturn(productFlavors);
- return project;
- }
- };
- }
- };
- } else if (mEnabled.contains(ManifestDetector.GRADLE_OVERRIDES)) {
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public Variant getCurrentVariant() {
- ProductFlavor flavor = mock(ProductFlavor.class);
- if (getName().equals("ManifestDetectorTest_testGradleOverridesOk") ||
- getName().equals(
- "ManifestDetectorTest_testManifestPackagePlaceholder")) {
- when(flavor.getMinSdkVersion()).thenReturn(null);
- when(flavor.getTargetSdkVersion()).thenReturn(null);
- when(flavor.getVersionCode()).thenReturn(null);
- when(flavor.getVersionName()).thenReturn(null);
- } else {
- assertEquals(getName(), "ManifestDetectorTest_testGradleOverrides");
-
- ApiVersion apiMock = mock(ApiVersion.class);
- when(apiMock.getApiLevel()).thenReturn(5);
- when(apiMock.getApiString()).thenReturn("5");
- when(flavor.getMinSdkVersion()).thenReturn(apiMock);
-
- apiMock = mock(ApiVersion.class);
- when(apiMock.getApiLevel()).thenReturn(16);
- when(apiMock.getApiString()).thenReturn("16");
- when(flavor.getTargetSdkVersion()).thenReturn(apiMock);
-
- when(flavor.getVersionCode()).thenReturn(2);
- when(flavor.getVersionName()).thenReturn("MyName");
- }
-
- Variant mock = mock(Variant.class);
- when(mock.getMergedFlavor()).thenReturn(flavor);
- return mock;
- }
- };
- }
- };
-
- }
- return super.createClient();
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java
deleted file mode 100644
index da12bda..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class MathDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new MathDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/bytecode/MathTest.java:11: Warning: Use java.lang.Math#cos instead of android.util.FloatMath#cos() since it is faster as of API 8 [FloatMath]\n" +
- " floatResult = FloatMath.cos(x);\n" +
- " ~~~\n" +
- "src/test/bytecode/MathTest.java:12: Warning: Use java.lang.Math#sin instead of android.util.FloatMath#sin() since it is faster as of API 8 [FloatMath]\n" +
- " floatResult = FloatMath.sin((float) y);\n" +
- " ~~~\n" +
- "src/test/bytecode/MathTest.java:13: Warning: Use java.lang.Math#ceil instead of android.util.FloatMath#ceil() since it is faster as of API 8 [FloatMath]\n" +
- " floatResult = android.util.FloatMath.ceil((float) y);\n" +
- " ~~~~\n" +
- "src/test/bytecode/MathTest.java:14: Warning: Use java.lang.Math#floor instead of android.util.FloatMath#floor() since it is faster as of API 8 [FloatMath]\n" +
- " System.out.println(FloatMath.floor(x));\n" +
- " ~~~~~\n" +
- "src/test/bytecode/MathTest.java:15: Warning: Use java.lang.Math#sqrt instead of android.util.FloatMath#sqrt() since it is faster as of API 8 [FloatMath]\n" +
- " System.out.println(FloatMath.sqrt(z));\n" +
- " ~~~~\n" +
- "0 errors, 5 warnings\n",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "bytecode/MathTest.java.txt=>src/test/bytecode/MathTest.java",
- "bytecode/MathTest.class.data=>bin/classes/test/bytecode/MathTest.class"
- ));
- }
-
- public void testNoWarningsPreFroyo() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "apicheck/minsdk2.xml=>AndroidManifest.xml",
- "bytecode/MathTest.java.txt=>src/test/bytecode/MathTest.java",
- "bytecode/MathTest.class.data=>bin/classes/test/bytecode/MathTest.class"
- ));
- }
-
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
deleted file mode 100644
index 22a001f..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class MergeRootFrameLayoutDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new MergeRootFrameLayoutDetector();
- }
-
-
- public void testMergeRefFromJava() throws Exception {
- assertEquals(
- "res/layout/simple.xml:3: Warning: This <FrameLayout> can be replaced with a <merge> tag [MergeRootFrame]\n" +
- "<FrameLayout\n" +
- "^\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "res/layout/simple.xml",
- "src/test/pkg/ImportFrameActivity.java.txt=>src/test/pkg/ImportFrameActivity.java"
- ));
- }
-
- public void testMergeRefFromInclude() throws Exception {
- assertEquals(
- "res/layout/simple.xml:3: Warning: This <FrameLayout> can be replaced with a <merge> tag [MergeRootFrame]\n" +
- "<FrameLayout\n" +
- "^\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "res/layout/simple.xml",
- "res/layout/simpleinclude.xml"
- ));
- }
-
- public void testMergeRefFromIncludeSuppressed() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject(
- "res/layout/simple_ignore.xml=>res/layout/simple.xml",
- "res/layout/simpleinclude.xml"
- ));
- }
-
- public void testNotIncluded() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject("res/layout/simple.xml"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
deleted file mode 100644
index 86b0494..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.checks.MissingClassDetector.INNERCLASS;
-import static com.android.tools.lint.checks.MissingClassDetector.INSTANTIATABLE;
-import static com.android.tools.lint.checks.MissingClassDetector.MISSING;
-import static com.android.tools.lint.detector.api.TextFormat.TEXT;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.collect.Sets;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
- at SuppressWarnings("javadoc")
-public class MissingClassDetectorTest extends AbstractCheckTest {
- private EnumSet<Scope> mScopes;
- private Set<Issue> mEnabled = new HashSet<Issue>();
-
- @Override
- protected Detector getDetector() {
- return new MissingClassDetector();
- }
-
- @Override
- protected EnumSet<Scope> getLintScope(List<File> file) {
- return mScopes;
- }
-
- @Override
- protected TestConfiguration getConfiguration(LintClient client, Project project) {
- return new TestConfiguration(client, project, null) {
- @Override
- public boolean isEnabled(@NonNull Issue issue) {
- return super.isEnabled(issue) && mEnabled.contains(issue);
- }
- };
- }
-
- public void test() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING);
- assertEquals(
- "AndroidManifest.xml:13: Error: Class referenced in the manifest, test.pkg.TestProvider, was not found in the project or the libraries [MissingRegistered]\n" +
- " <activity android:name=\".TestProvider\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.TestProvider2, was not found in the project or the libraries [MissingRegistered]\n" +
- " <service android:name=\"test.pkg.TestProvider2\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:15: Error: Class referenced in the manifest, test.pkg.TestService, was not found in the project or the libraries [MissingRegistered]\n" +
- " <provider android:name=\".TestService\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:16: Error: Class referenced in the manifest, test.pkg.OnClickActivity, was not found in the project or the libraries [MissingRegistered]\n" +
- " <receiver android:name=\"OnClickActivity\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:17: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n" +
- " <service android:name=\"TestReceiver\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "5 errors, 0 warnings\n",
-
- lintProject(
- "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
- "bytecode/.classpath=>.classpath"
- ));
- }
-
- public void testIncrementalInManifest() throws Exception {
- mScopes = Scope.MANIFEST_SCOPE;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath"
- ));
- }
-
- public void testNoWarningBeforeBuild() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath"
- ));
- }
-
- public void testOkClasses() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
- "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
- "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
- "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
- "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
- "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
- "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
- "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
- "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class"
- ));
- }
-
- public void testOkLibraries() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "bytecode/classes.jar=>libs/classes.jar"
- ));
- }
-
- public void testLibraryProjects() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- File master = getProjectDir("MasterProject",
- // Master project
- "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
- "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
- "bytecode/.classpath=>.classpath"
- );
- File library = getProjectDir("LibraryProject",
- // Library project
- "multiproject/library-manifest.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
- "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
- "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
- "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
- "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class"
- // Missing TestReceiver: Test should complain about just that class
- );
- assertEquals(""
- + "MasterProject/AndroidManifest.xml:32: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n"
- + " <receiver android:name=\"TestReceiver\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- checkLint(Arrays.asList(master, library)));
- }
-
- public void testIndirectLibraryProjects() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- File master = getProjectDir("MasterProject",
- // Master project
- "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
- "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
- "bytecode/.classpath=>.classpath"
- );
- File library2 = getProjectDir("LibraryProject",
- // Library project
- "multiproject/library-manifest2.xml=>AndroidManifest.xml",
- "multiproject/library2.properties=>project.properties"
- );
- File library = getProjectDir("RealLibrary",
- // Library project
- "multiproject/library-manifest.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
- "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
- "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
- "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
- "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class"
- // Missing TestReceiver: Test should complain about just that class
- );
- assertEquals(""
- + "MasterProject/AndroidManifest.xml:32: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n"
- + " <receiver android:name=\"TestReceiver\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- checkLint(Arrays.asList(master, library2, library)));
- }
-
- public void testInnerClassStatic() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "src/test/pkg/Foo.java:8: Error: This inner class should be static (test.pkg.Foo.Baz) [Instantiatable]\n" +
- " public class Baz extends Activity {\n" +
- " ^\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "registration/AndroidManifest.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "registration/Foo.java.txt=>src/test/pkg/Foo.java",
- "registration/Foo.class.data=>bin/classes/test/pkg/Foo.class",
- "registration/Foo$Bar.class.data=>bin/classes/test/pkg/Foo$Bar.class",
- "registration/Foo$Baz.class.data=>bin/classes/test/pkg/Foo$Baz.class"
- ));
- }
-
- public void testInnerClassPublic() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "src/test/pkg/Foo/Bar.java:6: Error: The default constructor must be public [Instantiatable]\n" +
- " private Bar() {\n" +
- " ^\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "registration/AndroidManifestInner.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java",
- "registration/Bar.class.data=>bin/classes/test/pkg/Foo/Bar.class"
- ));
- }
-
- public void testInnerClass() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
- " <activity\n" +
- " ^\n" +
- "AndroidManifest.xml:23: Error: Class referenced in the manifest, test.pkg.Foo.Baz, was not found in the project or the libraries [MissingRegistered]\n" +
- " <activity\n" +
- " ^\n" +
- "2 errors, 0 warnings\n",
-
- lintProject(
- "registration/AndroidManifest.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
- "registration/Foo.java.txt=>src/test/pkg/Foo.java"
- ));
- }
-
- public void testInnerClass2() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
- " <activity\n" +
- " ^\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "registration/AndroidManifestInner.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
- "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
- ));
- }
-
- public void testWrongSeparator1() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
- " <activity\n" +
- " ^\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "registration/AndroidManifestWrong.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
- "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
- ));
- }
-
- public void testWrongSeparator2() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
- " <activity\n" +
- " ^\n" +
- "AndroidManifest.xml:15: Warning: Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \".Foo.Bar\" with \".Foo$Bar\" [InnerclassSeparator]\n" +
- " android:name=\".Foo.Bar\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 1 warnings\n",
-
- lintProject(
- "registration/AndroidManifestWrong2.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
- "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
- ));
- }
-
- public void testNoClassesWithLibraries() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "bytecode/GetterTest.jar.data=>libs/foo.jar"
- ));
- }
-
- public void testFragment() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(""
- + "res/layout/fragment2.xml:7: Error: Class referenced in the layout file, my.app.Fragment, was not found in the project or the libraries [MissingRegistered]\n"
- + " <fragment\n"
- + " ^\n"
- + "res/layout/fragment2.xml:12: Error: Class referenced in the layout file, my.app.MyView, was not found in the project or the libraries [MissingRegistered]\n"
- + " <view\n"
- + " ^\n"
- + "res/layout/fragment2.xml:17: Error: Class referenced in the layout file, my.app.Fragment2, was not found in the project or the libraries [MissingRegistered]\n"
- + " <fragment\n"
- + " ^\n"
- + "src/test/pkg/Foo/Bar.java:6: Error: The default constructor must be public [Instantiatable]\n"
- + " private Bar() {\n"
- + " ^\n"
- + "4 errors, 0 warnings\n",
-
- lintProject(
- "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
- "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
- "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
- "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
- "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
- "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
- "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
- "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
- "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class",
- "registration/Foo.java.txt=>src/test/pkg/Foo.java",
- "registration/Foo.class.data=>bin/classes/test/pkg/Foo.class",
- "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java",
- "registration/Bar.class.data=>bin/classes/test/pkg/Foo/Bar.class",
-
- "res/layout/fragment2.xml"
- ));
- }
-
- public void testAnalytics() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(""
- + "res/values/analytics.xml:13: Error: Class referenced in the analytics file, com.example.app.BaseActivity, was not found in the project or the libraries [MissingRegistered]\n"
- + " <string name=\"com.example.app.BaseActivity\">Home</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values/analytics.xml:14: Error: Class referenced in the analytics file, com.example.app.PrefsActivity, was not found in the project or the libraries [MissingRegistered]\n"
- + " <string name=\"com.example.app.PrefsActivity\">Preferences</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "res/values/analytics.xml",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class"
- ));
- }
-
- public void testCustomView() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(""
- + "res/layout/customview.xml:21: Error: Class referenced in the layout file, foo.bar.Baz, was not found in the project or the libraries [MissingRegistered]\n"
- + " <foo.bar.Baz\n"
- + " ^\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "res/layout/customview.xml",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class"
- ));
- }
-
- public void testCustomViewInCapitalizedPackage() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "res/layout/customview3.xml",
- "bytecode/CustomView3.java.txt=>src/test/bytecode/CustomView3.java",
- "bytecode/CustomView3.class.data=>bin/classes/test/bytecode/CustomView3.class"
- ));
- }
-
- public void testCustomViewNotReferenced() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/CustomView3.java.txt=>src/test/bytecode/CustomView3.java",
- "bytecode/CustomView3.class.data=>bin/classes/test/bytecode/CustomView3.class"
- ));
- }
-
-
- public void testMissingClass() throws Exception {
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/user_prefs_fragment.xml=>res/layout/user_prefs_fragment.xml",
- "bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data=>bin/classes/course/examples/DataManagement/PreferenceActivity/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class"
- ));
- }
-
- public void testFragments() throws Exception {
- mScopes = Scope.MANIFEST_SCOPE;
- mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
-
- // Ensure that we don't do instantiation checks here since they are handled by
- // the FragmentDetector
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/FragmentTest$Fragment1.class.data=>bin/classes/test/pkg/FragmentTest$Fragment1.class",
- "bytecode/FragmentTest$Fragment2.class.data=>bin/classes/test/pkg/FragmentTest$Fragment2.class",
- "bytecode/FragmentTest$Fragment3.class.data=>bin/classes/test/pkg/FragmentTest$Fragment3.class",
- "bytecode/FragmentTest$Fragment4.class.data=>bin/classes/test/pkg/FragmentTest$Fragment4.class",
- "bytecode/FragmentTest$Fragment5.class.data=>bin/classes/test/pkg/FragmentTest$Fragment5.class",
- "bytecode/FragmentTest$Fragment6.class.data=>bin/classes/test/pkg/FragmentTest$Fragment6.class",
- "bytecode/FragmentTest$NotAFragment.class.data=>bin/classes/test/pkg/FragmentTest$NotAFragment.class",
- "bytecode/FragmentTest.java.txt=>src/test/pkg/FragmentTest.java"));
- }
-
- public void testHeaders() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=51851
- mScopes = null;
- mEnabled = Sets.newHashSet(MISSING, INNERCLASS);
- assertEquals(""
- + "res/xml/prefs_headers.xml:3: Error: Class referenced in the preference header file, foo.bar.MyFragment.Missing, was not found in the project or the libraries [MissingRegistered]\n"
- + "<header\n"
- + "^\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "bytecode/FragmentTest$Fragment1.class.data=>bin/classes/test/pkg/FragmentTest$Fragment1.class",
- "bytecode/FragmentTest$Fragment2.class.data=>bin/classes/test/pkg/FragmentTest$Fragment2.class",
- "bytecode/FragmentTest$Fragment3.class.data=>bin/classes/test/pkg/FragmentTest$Fragment3.class",
- "bytecode/FragmentTest$Fragment4.class.data=>bin/classes/test/pkg/FragmentTest$Fragment4.class",
- "bytecode/FragmentTest$Fragment5.class.data=>bin/classes/test/pkg/FragmentTest$Fragment5.class",
- "bytecode/FragmentTest$Fragment6.class.data=>bin/classes/test/pkg/FragmentTest$Fragment6.class",
- "bytecode/FragmentTest$NotAFragment.class.data=>bin/classes/test/pkg/FragmentTest$NotAFragment.class",
- "bytecode/FragmentTest.java.txt=>src/test/pkg/FragmentTest.java",
- "bytecode/.classpath=>.classpath",
- "res/xml/prefs_headers.xml"));
- }
-
-
- public void testGetOldValue() {
- assertEquals(".Foo.Bar", MissingClassDetector.getOldValue(INNERCLASS,
- "Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \".Foo.Bar\" with \".Foo$Bar\" [InnerclassSeparator]",
- TEXT));
- }
-
- public void testGetNewValue() {
- assertEquals(".Foo$Bar", MissingClassDetector.getNewValue(INNERCLASS,
- "Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \".Foo.Bar\" with \".Foo$Bar\" [InnerclassSeparator]",
- TEXT));
- }
-
- @Override
- protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
- @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
- if (issue == INNERCLASS) {
- assertNotNull(message, MissingClassDetector.getOldValue(issue, message, TEXT));
- assertNotNull(message, MissingClassDetector.getNewValue(issue, message, TEXT));
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
deleted file mode 100644
index 49eb702..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class ParcelDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new ParcelDetector();
- }
-
- public void test() throws Exception {
- assertEquals(""
- + "src/test/bytecode/MyParcelable1.java:6: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
- + "public class MyParcelable1 implements Parcelable {\n"
- + " ~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "bytecode/MyParcelable1.java.txt=>src/test/bytecode/MyParcelable1.java",
- "bytecode/MyParcelable2.java.txt=>src/test/bytecode/MyParcelable2.java",
- "bytecode/MyParcelable3.java.txt=>src/test/bytecode/MyParcelable3.java",
- "bytecode/MyParcelable4.java.txt=>src/test/bytecode/MyParcelable4.java",
- "bytecode/MyParcelable5.java.txt=>src/test/bytecode/MyParcelable5.java",
- "bytecode/MyParcelable1.class.data=>bin/classes/test/bytecode/MyParcelable1.class",
- "bytecode/MyParcelable2.class.data=>bin/classes/test/bytecode/MyParcelable2.class",
- "bytecode/MyParcelable2$1.class.data=>bin/classes/test/bytecode/MyParcelable2$1.class",
- "bytecode/MyParcelable3.class.data=>bin/classes/test/bytecode/MyParcelable3.class",
- "bytecode/MyParcelable4.class.data=>bin/classes/test/bytecode/MyParcelable4.class",
- "bytecode/MyParcelable5.class.data=>bin/classes/test/bytecode/MyParcelable5.class"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java
deleted file mode 100644
index 7992903..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.TAG_PERMISSION;
-import static com.android.tools.lint.checks.PermissionRequirement.REVOCABLE_PERMISSION_NAMES;
-import static com.android.tools.lint.checks.PermissionRequirement.isRevocableSystemPermission;
-import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.testutils.SdkTestCase;
-import com.android.tools.lint.checks.PermissionHolder.SetPermissionLookup;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import junit.framework.TestCase;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-import lombok.ast.BinaryOperator;
-
-public class PermissionRequirementTest extends TestCase {
- private static ResolvedAnnotation createAnnotation(
- @NonNull String name,
- @NonNull ResolvedAnnotation.Value... values) {
- ResolvedAnnotation annotation = mock(ResolvedAnnotation.class);
- when(annotation.getName()).thenReturn(name);
- when(annotation.getValues()).thenReturn(Arrays.asList(values));
- for (ResolvedAnnotation.Value value : values) {
- when(annotation.getValue(value.name)).thenReturn(value.value);
- }
- return annotation;
- }
-
- public void testSingle() {
- ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("value",
- "android.permission.ACCESS_FINE_LOCATION");
- Set<String> emptySet = Collections.emptySet();
- Set<String> fineSet = Collections.singleton("android.permission.ACCESS_FINE_LOCATION");
- ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
- PermissionRequirement req = PermissionRequirement.create(null, annotation);
- assertTrue(req.isRevocable(new SetPermissionLookup(emptySet)));
-
- assertFalse(req.isSatisfied(new SetPermissionLookup(emptySet)));
- assertFalse(req.isSatisfied(new SetPermissionLookup(Collections.singleton(""))));
- assertTrue(req.isSatisfied(new SetPermissionLookup(fineSet)));
- assertEquals("android.permission.ACCESS_FINE_LOCATION",
- req.describeMissingPermissions(new SetPermissionLookup(emptySet)));
- assertEquals(fineSet, req.getMissingPermissions(new SetPermissionLookup(emptySet)));
- assertEquals(emptySet, req.getMissingPermissions(new SetPermissionLookup(fineSet)));
- assertEquals(fineSet, req.getRevocablePermissions(new SetPermissionLookup(emptySet)));
- assertNull(req.getOperator());
- assertFalse(req.getChildren().iterator().hasNext());
- }
-
- public void testAny() {
- ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("anyOf",
- new String[]{"android.permission.ACCESS_FINE_LOCATION",
- "android.permission.ACCESS_COARSE_LOCATION"});
- Set<String> emptySet = Collections.emptySet();
- Set<String> fineSet = Collections.singleton("android.permission.ACCESS_FINE_LOCATION");
- Set<String> coarseSet = Collections.singleton("android.permission.ACCESS_COARSE_LOCATION");
- Set<String> bothSet = Sets.newHashSet(
- "android.permission.ACCESS_FINE_LOCATION",
- "android.permission.ACCESS_COARSE_LOCATION");
-
- ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
- PermissionRequirement req = PermissionRequirement.create(null, annotation);
- assertTrue(req.isRevocable(new SetPermissionLookup(emptySet)));
- assertFalse(req.isSatisfied(new SetPermissionLookup(emptySet)));
- assertFalse(req.isSatisfied(new SetPermissionLookup(Collections.singleton(""))));
- assertTrue(req.isSatisfied(new SetPermissionLookup(fineSet)));
- assertTrue(req.isSatisfied(new SetPermissionLookup(coarseSet)));
- assertEquals(
- "android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION",
- req.describeMissingPermissions(new SetPermissionLookup(emptySet)));
- assertEquals(bothSet, req.getMissingPermissions(new SetPermissionLookup(emptySet)));
- assertEquals(bothSet, req.getRevocablePermissions(new SetPermissionLookup(emptySet)));
- assertSame(BinaryOperator.LOGICAL_OR, req.getOperator());
- }
-
- public void testAll() {
- ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("allOf",
- new String[]{"android.permission.ACCESS_FINE_LOCATION",
- "android.permission.ACCESS_COARSE_LOCATION"});
- Set<String> emptySet = Collections.emptySet();
- Set<String> fineSet = Collections.singleton("android.permission.ACCESS_FINE_LOCATION");
- Set<String> coarseSet = Collections.singleton("android.permission.ACCESS_COARSE_LOCATION");
- Set<String> bothSet = Sets.newHashSet(
- "android.permission.ACCESS_FINE_LOCATION",
- "android.permission.ACCESS_COARSE_LOCATION");
-
- ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
- PermissionRequirement req = PermissionRequirement.create(null, annotation);
- assertTrue(req.isRevocable(new SetPermissionLookup(emptySet)));
- assertFalse(req.isSatisfied(new SetPermissionLookup(emptySet)));
- assertFalse(req.isSatisfied(new SetPermissionLookup(Collections.singleton(""))));
- assertFalse(req.isSatisfied(new SetPermissionLookup(fineSet)));
- assertFalse(req.isSatisfied(new SetPermissionLookup(coarseSet)));
- assertTrue(req.isSatisfied(new SetPermissionLookup(bothSet)));
- assertEquals(
- "android.permission.ACCESS_FINE_LOCATION and android.permission.ACCESS_COARSE_LOCATION",
- req.describeMissingPermissions(new SetPermissionLookup(emptySet)));
- assertEquals(bothSet, req.getMissingPermissions(new SetPermissionLookup(emptySet)));
- assertEquals(
- "android.permission.ACCESS_COARSE_LOCATION",
- req.describeMissingPermissions(new SetPermissionLookup(fineSet)));
- assertEquals(coarseSet, req.getMissingPermissions(new SetPermissionLookup(fineSet)));
- assertEquals(
- "android.permission.ACCESS_FINE_LOCATION",
- req.describeMissingPermissions(new SetPermissionLookup(coarseSet)));
- assertEquals(fineSet, req.getMissingPermissions(new SetPermissionLookup(coarseSet)));
- assertEquals(bothSet, req.getRevocablePermissions(new SetPermissionLookup(emptySet)));
- assertSame(BinaryOperator.LOGICAL_AND, req.getOperator());
- }
-
- public void testRevocable() {
- assertTrue(isRevocableSystemPermission("android.permission.ACCESS_FINE_LOCATION"));
- assertTrue(isRevocableSystemPermission("android.permission.ACCESS_COARSE_LOCATION"));
- assertFalse(isRevocableSystemPermission("android.permission.UNKNOWN_PERMISSION_NAME"));
- }
-
- public void testRevocable2() {
- assertTrue(new SetPermissionLookup(Collections.<String>emptySet(),
- Sets.newHashSet("my.permission1", "my.permission2")).isRevocable("my.permission2"));
- }
-
- public void testAppliesTo() {
- ResolvedAnnotation annotation;
- PermissionRequirement req;
-
- // No date range applies to permission
- annotation = createAnnotation(PERMISSION_ANNOTATION,
- new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"));
- req = PermissionRequirement.create(null, annotation);
- assertTrue(req.appliesTo(getHolder(15, 1)));
- assertTrue(req.appliesTo(getHolder(15, 19)));
- assertTrue(req.appliesTo(getHolder(15, 23)));
- assertTrue(req.appliesTo(getHolder(22, 23)));
- assertTrue(req.appliesTo(getHolder(23, 23)));
-
- // Permission discontinued in API 23:
- annotation = createAnnotation(PERMISSION_ANNOTATION,
- new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"),
- new ResolvedAnnotation.Value("apis", "..22"));
- req = PermissionRequirement.create(null, annotation);
- assertTrue(req.appliesTo(getHolder(15, 1)));
- assertTrue(req.appliesTo(getHolder(15, 19)));
- assertTrue(req.appliesTo(getHolder(15, 23)));
- assertTrue(req.appliesTo(getHolder(22, 23)));
- assertFalse(req.appliesTo(getHolder(23, 23)));
-
- // Permission requirement started in API 23
- annotation = createAnnotation(PERMISSION_ANNOTATION,
- new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"),
- new ResolvedAnnotation.Value("apis", "23.."));
- req = PermissionRequirement.create(null, annotation);
- assertFalse(req.appliesTo(getHolder(15, 1)));
- assertFalse(req.appliesTo(getHolder(1, 19)));
- assertFalse(req.appliesTo(getHolder(15, 22)));
- assertTrue(req.appliesTo(getHolder(22, 23)));
- assertTrue(req.appliesTo(getHolder(23, 30)));
-
- // Permission requirement applied from API 14 through API 18
- annotation = createAnnotation(PERMISSION_ANNOTATION,
- new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"),
- new ResolvedAnnotation.Value("apis", "14..18"));
- req = PermissionRequirement.create(null, annotation);
- assertFalse(req.appliesTo(getHolder(1, 5)));
- assertTrue(req.appliesTo(getHolder(15, 19)));
- }
-
- private static PermissionHolder getHolder(int min, int target) {
- return new PermissionHolder.SetPermissionLookup(Collections.<String>emptySet(),
- Collections.<String>emptySet(), new AndroidVersion(min, null),
- new AndroidVersion(target, null));
- }
-
- public static void testDbUpToDate() throws Exception {
- List<String> expected = getDangerousPermissions();
- if (expected == null) {
- return;
- }
- List<String> actual = Arrays.asList(REVOCABLE_PERMISSION_NAMES);
- if (!expected.equals(actual)) {
- System.out.println("Correct list of exceptions:");
- for (String name : expected) {
- System.out.println(" \"" + name + "\",");
- }
- fail("List of revocable permissions has changed:\n" +
- // Make the diff show what it take to bring the actual results into the
- // expected results
- SdkTestCase.getDiff(Joiner.on('\n').join(actual),
- Joiner.on('\n').join(expected)));
- }
- }
-
- @Nullable
- private static List<String> getDangerousPermissions() throws IOException {
- Pattern pattern = Pattern.compile("dangerous");
- String top = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
- if (top == null) {
- top = "/Volumes/android/mnc-dev";
- }
-
- // TODO: We should ship this file with the SDK!
- File file = new File(top, "frameworks/base/core/res/AndroidManifest.xml");
- if (!file.exists()) {
- System.out.println("Set $ANDROID_BUILD_TOP to point to the git repository to check permissions");
- return null;
- }
- boolean passedRuntimeHeader = false;
- boolean passedInstallHeader = false;
- String xml = Files.toString(file, Charsets.UTF_8);
- Document document = XmlUtils.parseDocumentSilently(xml, true);
- Set<String> revocable = Sets.newHashSet();
- if (document != null && document.getDocumentElement() != null) {
- NodeList children = document.getDocumentElement().getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- short nodeType = child.getNodeType();
- if (nodeType == Node.COMMENT_NODE) {
- String comment = child.getNodeValue();
- if (comment.contains("RUNTIME PERMISSIONS")) {
- passedRuntimeHeader = true;
- } else if (comment.contains("INSTALLTIME PERMISSIONS"))
- passedInstallHeader = true;
- } else if (passedRuntimeHeader
- && !passedInstallHeader
- && nodeType == Node.ELEMENT_NODE
- && child.getNodeName().equals(TAG_PERMISSION)) {
- Element element = (Element) child;
- String protectionLevel = element.getAttributeNS(ANDROID_URI, "protectionLevel");
- String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (!name.isEmpty() && pattern.matcher(protectionLevel).find()) {
- revocable.add(name);
- }
- }
- }
- }
-
- List<String> expected = Lists.newArrayList(revocable);
- Collections.sort(expected);
- return expected;
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
deleted file mode 100644
index 1ec507a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
+++ /dev/null
@@ -1,716 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_FEW;
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MANY;
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MULTIPLE_ONE;
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MULTIPLE_TWO;
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MULTIPLE_ZERO;
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_ONE;
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_TWO;
-import static com.android.tools.lint.checks.PluralsDatabase.FLAG_ZERO;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.few;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.many;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.one;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.two;
-import static com.android.tools.lint.checks.PluralsDatabase.Quantity.zero;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.resources.LocaleManager;
-import com.google.common.base.Charsets;
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-
-import junit.framework.TestCase;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-public class PluralsDatabaseTest extends TestCase {
- public void testGetRelevant() {
- PluralsDatabase db = PluralsDatabase.get();
- assertNull(db.getRelevant("unknown"));
- EnumSet<Quantity> relevant = db.getRelevant("en");
- assertNotNull(relevant);
- assertEquals(1, relevant.size());
- assertSame(Quantity.one, relevant.iterator().next());
-
- relevant = db.getRelevant("cs");
- assertNotNull(relevant);
- assertEquals(EnumSet.of(Quantity.few, Quantity.one), relevant);
- }
-
- public void testFindExamples() {
- PluralsDatabase db = PluralsDatabase.get();
-
- //noinspection ConstantConditions
- assertEquals("1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026",
- db.findIntegerExamples("sl", Quantity.one));
-
- assertEquals("1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026",
- db.findIntegerExamples("ru", Quantity.one));
- }
-
- public void testHasMultiValue() {
- PluralsDatabase db = PluralsDatabase.get();
-
- assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.one));
- assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.two));
- assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.few));
- assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.many));
-
- assertTrue(db.hasMultipleValuesForQuantity("br", Quantity.two));
- assertTrue(db.hasMultipleValuesForQuantity("mk", Quantity.one));
- assertTrue(db.hasMultipleValuesForQuantity("lv", Quantity.zero));
- }
-
- /**
- * If the lint unit test data/ folder contains a plurals.txt database file,
- * this test will parse that file and ensure that our current database produces
- * exactly the same results as those inferred from the file. If not, it will
- * dump out updated data structures for the database.
- */
- public void testDatabaseAccurate() {
- List<String> languages = new ArrayList<String>(LocaleManager.getLanguageCodes());
- Collections.sort(languages);
- PluralsTextDatabase db = PluralsTextDatabase.get();
- db.ensureInitialized();
-
- if (db.getSetName("en") == null) {
- // plurals.txt not found
- System.out.println("No plurals.txt database included; not checking consistency");
- return;
- }
-
- // Ensure that the two databases (the plurals.txt backed one and our actual
- // database) fully agree on everything
- PluralsDatabase pdb = PluralsDatabase.get();
- for (String language : languages) {
- if (!Objects.equal(pdb.getRelevant(language), db.getRelevant(language))) {
- dumpDatabaseTables();
- assertEquals(language, pdb.getRelevant(language), db.getRelevant(language));
- }
- if (db.getSetName(language) == null) {
- continue;
- }
- for (Quantity q : Quantity.values()) {
- boolean mv1 = pdb.hasMultipleValuesForQuantity(language, q);
- boolean mv2 = db.hasMultipleValuesForQuantity(language, q);
- if (mv1 != mv2) {
- dumpDatabaseTables();
- assertEquals(language, mv1, mv2);
- }
- if (mv2) {
- String e1 = pdb.findIntegerExamples(language, q);
- String e2 = db.findIntegerExamples(language, q);
- if (!Objects.equal(e1, e2)) {
- dumpDatabaseTables();
- assertEquals(language, e1, e2);
- }
- }
- }
- }
- }
-
- private static void dumpDatabaseTables() {
- List<String> languages = new ArrayList<String>(LocaleManager.getLanguageCodes());
- Collections.sort(languages);
- PluralsTextDatabase db = PluralsTextDatabase.get();
- db.ensureInitialized();
-
- db.getRelevant("en"); // ensure initialized
- Map<String,String> languageMap = Maps.newHashMap();
- Map<String,EnumSet<Quantity>> setMap = Maps.newHashMap();
- for (String language : languages) {
- String set = db.getSetName(language);
- if (set == null) {
- continue;
- }
- EnumSet<Quantity> quantitySet = db.getRelevant(language);
- if (quantitySet == null) {
- // No plurals data for this language. For example, in ICU 52, no
- // plurals data for the "nv" language (Navajo).
- continue;
- }
- assertNotNull(language, quantitySet);
- setMap.put(set, quantitySet);
- languageMap.put(set, language); // Could be multiple
- }
-
- List<String> setNames = Lists.newArrayList(setMap.keySet());
- Collections.sort(setNames);
-
- // Compute uniqueness
- Map<String,String> sameAs = Maps.newHashMap();
- for (int i = 0, n = setNames.size(); i < n; i++) {
- for (int j = i + 1; j < n; j++) {
- String iSetName = setNames.get(i);
- String jSetName = setNames.get(j);
- assertNotNull(iSetName);
- assertNotNull(jSetName);
- EnumSet<Quantity> iSet = setMap.get(iSetName);
- EnumSet<Quantity> jSet = setMap.get(jSetName);
- assertNotNull(iSet);
- assertNotNull(jSet);
- if (iSet.equals(jSet)) {
- String alias = sameAs.get(iSetName);
- if (alias != null) {
- iSetName = alias;
- }
- sameAs.put(jSetName, iSetName);
- break;
- }
- }
- }
-
- final String indent = " ";
- StringBuilder sb = new StringBuilder();
-
- // Multi Value Set names
- Set<String> sets = Sets.newHashSet();
- for (String language : languages) {
- String set = db.getSetName(language);
- sets.add(set);
- languageMap.put(set, language); // Could be multiple
- }
-
- Map<String,Integer> indices = Maps.newTreeMap();
- int index = 0;
- for (String set : setNames) {
- indices.put(set, index++);
- }
-
- // Language indices
- Map<String,Integer> languageIndices = Maps.newTreeMap();
- index = 0;
- for (String language : languages) {
- String set = db.getSetName(language);
- if (set == null) {
- continue;
- }
-
- languageIndices.put(language, index++);
- }
-
- Map<String, String> zero = computeExamples(db, Quantity.zero, sets, languageMap);
- Map<String, String> one = computeExamples(db, Quantity.one, sets, languageMap);
- Map<String, String> two = computeExamples(db, Quantity.two, sets, languageMap);
-
- // Language map
- sb.setLength(0);
- sb.append("/** Set of language codes relevant to plurals data */\n");
- sb.append("private static final String[] LANGUAGE_CODES = new String[] {\n");
- int column = 0;
- index = 0;
- sb.append(indent);
- for (String language : languages) {
- String set = db.getSetName(language);
- if (set == null) {
- continue;
- }
- sb.append('"').append(language).append("\", ");
- column++;
- if (column == 10) {
- sb.append("\n");
- sb.append(indent);
- column = 0;
- }
- assertEquals((int)languageIndices.get(language), index);
- index++;
- }
- sb.append("\n};\n");
- System.out.println(sb);
-
- // Quantity map
- sb.setLength(0);
- sb.append("/**\n"
- + " * Relevant flags for each language (corresponding to each language listed\n"
- + " * in the same position in {@link #LANGUAGE_CODES})\n"
- + " */\n");
- sb.append("private static final int[] FLAGS = new int[] {\n");
- column = 0;
- sb.append(indent);
- index = 0;
- for (String language : languages) {
- String setName = db.getSetName(language);
- if (setName == null) {
- continue;
- }
- assertEquals((int)languageIndices.get(language), index);
-
- // Compute flag
- int flag = 0;
- EnumSet<Quantity> relevant = db.getRelevant(language);
- assertNotNull(relevant);
- if (relevant.contains(Quantity.zero)) {
- flag |= FLAG_ZERO;
- }
- if (relevant.contains(Quantity.one)) {
- flag |= FLAG_ONE;
- }
- if (relevant.contains(Quantity.two)) {
- flag |= FLAG_TWO;
- }
- if (relevant.contains(Quantity.few)) {
- flag |= FLAG_FEW;
- }
- if (relevant.contains(Quantity.many)) {
- flag |= FLAG_MANY;
- }
- if (zero.containsKey(setName)) {
- flag |= FLAG_MULTIPLE_ZERO;
- }
- if (one.containsKey(setName)) {
- flag |= FLAG_MULTIPLE_ONE;
- }
- if (two.containsKey(setName)) {
- flag |= FLAG_MULTIPLE_TWO;
- }
-
- sb.append(String.format(Locale.US, "0x%04x, ", flag));
- column++;
- if (column == 8) {
- sb.append("\n");
- sb.append(indent);
- column = 0;
- }
-
- index++;
- }
- sb.append("\n};\n");
- System.out.println(sb);
-
- // Switch statement methods for examples
- printSwitch(db, Quantity.zero, languages, languageIndices, indices, zero);
- printSwitch(db, Quantity.one, languages, languageIndices, indices, one);
- printSwitch(db, Quantity.two, languages, languageIndices, indices, two);
-
- }
-
- private static Map<String, String> computeExamples(PluralsTextDatabase db, Quantity quantity,
- Set<String> sets, Map<String, String> languageMap) {
-
- Map<String, String> setsWithExamples = Maps.newHashMap();
- for (String set : sets) {
- String language = languageMap.get(set);
- String examples = db.findIntegerExamples(language, quantity);
- if (examples != null && examples.indexOf(',') != -1) {
- setsWithExamples.put(set, examples);
- }
- }
-
- return setsWithExamples;
- }
-
-
- private static void printSwitch(
- PluralsTextDatabase db,
- Quantity quantity,
- List<String> languages,
- Map<String,Integer> languageIndices,
- Map<String, Integer> indices,
- Map<String, String> setsWithExamples) {
-
- List<String> sorted = new ArrayList<String>(setsWithExamples.keySet());
- Collections.sort(sorted);
-
- StringBuilder sb = new StringBuilder();
- String quantityName = quantity.name();
- quantityName = Character.toUpperCase(quantityName.charAt(0)) + quantityName.substring(1);
- sb.append(" @Nullable\n"
- + " private static String getExampleForQuantity").append(quantityName)
- .append("(@NonNull String language) {\n"
- + " int index = getLanguageIndex(language);\n"
- + " switch (index) {\n");
-
- for (Map.Entry<String, Integer> entry : indices.entrySet()) {
- String set = entry.getKey();
- if (!setsWithExamples.containsKey(set)) {
- continue;
- }
- String example = setsWithExamples.get(set);
- example = example.replace("…", "\\u2026");
- sb.append(" // ").append(set).append("\n");
- for (String language : languages) {
- String setName = db.getSetName(language);
- if (set.equals(setName)) {
- int languageIndex = languageIndices.get(language);
- sb.append(" case ");
- sb.append(languageIndex).append(": // ").append(language).append("\n");
- }
- }
-
- sb.append(" return ");
- sb.append("\"").append(example).append("\"");
- sb.append(";\n");
- }
-
- sb.append(" case -1:\n"
- + " default:\n"
- + " return null;\n"
- + " }\n"
- + " }\n");
-
- System.out.println(sb);
- }
-
- /**
- * Plurals database backed by a plurals.txt file from ICU
- */
- private static class PluralsTextDatabase {
- private static final boolean DEBUG = false;
- private static final EnumSet<Quantity> NONE = EnumSet.noneOf(Quantity.class);
-
- private static final PluralsTextDatabase sInstance = new PluralsTextDatabase();
-
- private Map<String, EnumSet<Quantity>> mPlurals;
- private Map<Quantity, Set<String>> mMultiValueSetNames = Maps.newEnumMap(Quantity.class);
- private String mDescriptions;
- private int mRuleSetOffset;
- private Map<String,String> mSetNamePerLanguage;
-
- @NonNull
- public static PluralsTextDatabase get() {
- return sInstance;
- }
-
- @Nullable
- public EnumSet<Quantity> getRelevant(@NonNull String language) {
- ensureInitialized();
- EnumSet<Quantity> set = mPlurals.get(language);
- if (set == null) {
- String s = getLocaleData(language);
- if (s == null) {
- mPlurals.put(language, NONE);
- return null;
- }
- // Process each item and look for relevance
-
- set = EnumSet.noneOf(Quantity.class);
- int length = s.length();
- for (int offset = 0, end; offset < length; offset = end + 1) {
- for (; offset < length; offset++) {
- if (!Character.isWhitespace(s.charAt(offset))) {
- break;
- }
- }
-
- int begin = s.indexOf('{', offset);
- if (begin == -1) {
- break;
- }
- end = findBalancedEnd(s, begin);
- if (end == -1) {
- end = length;
- }
-
- if (s.startsWith("other{", offset)) {
- // Not included
- continue;
- }
-
- // Make sure the rule references applies to integers:
- // Rule definition mentions n or i or @integer
- //
- // n absolute value of the source number (integer and decimals).
- // i integer digits of n.
- // v number of visible fraction digits in n, with trailing zeros.
- // w number of visible fraction digits in n, without trailing zeros.
- // f visible fractional digits in n, with trailing zeros.
- // t visible fractional digits in n, without trailing zeros.
- boolean appliesToIntegers = false;
- boolean inQuotes = false;
- for (int i = begin + 1; i < end - 1; i++) {
- char c = s.charAt(i);
- if (c == '"') {
- inQuotes = !inQuotes;
- } else if (inQuotes) {
- if (c == '@') {
- if (s.startsWith("@integer", i)) {
- appliesToIntegers = true;
- break;
- } else {
- // @decimal always comes after @integer
- break;
- }
- } else if ((c == 'i' || c == 'n') && Character
- .isWhitespace(s.charAt(i + 1))) {
- appliesToIntegers = true;
- break;
- }
- }
- }
-
- if (!appliesToIntegers) {
- if (DEBUG) {
- System.out.println("Skipping quantity " + s.substring(offset, begin)
- + " in set for locale " + language + " (" + getSetName(language)
- + ")");
- }
- continue;
- }
-
- if (s.startsWith("one{", offset)) {
- set.add(one);
- } else if (s.startsWith("few{", offset)) {
- set.add(few);
- } else if (s.startsWith("many{", offset)) {
- set.add(many);
- } else if (s.startsWith("two{", offset)) {
- set.add(two);
- } else if (s.startsWith("zero{", offset)) {
- set.add(zero);
- } else {
- // Unexpected quantity: ignore
- if (DEBUG) {
- assert false : s.substring(offset, Math.min(offset + 10, length));
- }
- }
- }
-
- mPlurals.put(language, set);
- }
- return set == NONE ? null : set;
- }
-
- public boolean hasMultipleValuesForQuantity(
- @NonNull String language,
- @NonNull Quantity quantity) {
- if (quantity == Quantity.one || quantity == Quantity.two || quantity == Quantity.zero) {
- ensureInitialized();
- String setName = getSetName(language);
- if (setName != null) {
- Set<String> names = mMultiValueSetNames.get(quantity);
- assert names != null : quantity;
- return names.contains(setName);
- }
- }
-
- return false;
- }
-
- private void ensureInitialized() {
- if (mPlurals == null) {
- initialize();
- }
- }
-
- @SuppressWarnings({"UnnecessaryLocalVariable", "UnusedDeclaration"})
- private void initialize() {
- // Sets where more than a single integer maps to the quantity. Take for example
- // set 10:
- // set10{
- // one{
- // "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81,"
- // " 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0"
- // ", 101.0, 1001.0, …"
- // }
- // }
- // Here we see that both "1" and "21" will match the "one" category.
- // Note that this only applies to integers (since getQuantityString only takes integer)
- // whereas the plurals data also covers fractions. I was not sure what to do about
- // set17:
- // set17{
- // one{"i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6"}
- // }
- // since it looks to me like this only differs from 1 in the fractional part.
- //
- // This is encoded by looking at the rules; this is done by the unit test
- // testDeriveMultiValueSetNames() (which ensures that the set is correct and if
- // not computes the correct set of set names to use for the current plurals.txt
- // database.
-
- mMultiValueSetNames = Maps.newEnumMap(Quantity.class);
- mMultiValueSetNames.put(Quantity.two, Sets.newHashSet("set21", "set22", "set30", "set32"));
- mMultiValueSetNames.put(Quantity.one, Sets.newHashSet(
- "set1", "set11", "set12", "set13", "set14", "set2", "set20",
- "set21", "set22", "set26", "set27", "set29", "set30", "set32", "set5",
- "set6"));
- mMultiValueSetNames.put(Quantity.zero, Sets.newHashSet("set14"));
-
- mSetNamePerLanguage = Maps.newHashMapWithExpectedSize(20);
- mPlurals = Maps.newHashMapWithExpectedSize(20);
- }
-
- @Nullable
- public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) {
- String data = getQuantityData(language, quantity);
- if (data != null) {
- int index = data.indexOf("@integer");
- if (index == -1) {
- return null;
- }
- int start = index + "@integer".length();
- int end = data.indexOf('@', start);
- if (end == -1) {
- end = data.length();
- }
- return data.substring(start, end).trim();
- }
-
- return null;
- }
-
-
- @NonNull
- private String getPluralsDescriptions() {
- if (mDescriptions == null) {
- InputStream stream = PluralsDatabaseTest.class.getResourceAsStream("data/plurals.txt");
- if (stream != null) {
- try {
- byte[] bytes = ByteStreams.toByteArray(stream);
- mDescriptions = new String(bytes, Charsets.UTF_8);
- mRuleSetOffset = mDescriptions.indexOf("rules{");
- if (mRuleSetOffset == -1) {
- if (DEBUG) {
- assert false;
- }
- mDescriptions = "";
- mRuleSetOffset = 0;
- }
-
- } catch (IOException e) {
- try {
- stream.close();
- } catch (IOException e1) {
- // Stupid API.
- }
- }
- }
- if (mDescriptions == null) {
- mDescriptions = "";
- }
- }
- return mDescriptions;
- }
-
- @Nullable
- public String getQuantityData(@NonNull String language, @NonNull Quantity quantity) {
- String data = getLocaleData(language);
- if (data == null) {
- return null;
- }
- String quantityDeclaration = quantity.name() + "{";
- int quantityStart = data.indexOf(quantityDeclaration);
- if (quantityStart == -1) {
- return null;
- }
- int quantityEnd = findBalancedEnd(data, quantityStart);
- if (quantityEnd == -1) {
- return null;
- }
- //String s = data.substring(quantityStart + quantityDeclaration.length(), quantityEnd);
- StringBuilder sb = new StringBuilder();
- boolean inString = false;
- for (int i = quantityStart + quantityDeclaration.length(); i < quantityEnd; i++) {
- char c = data.charAt(i);
- if (c == '"') {
- inString = !inString;
- } else if (inString) {
- sb.append(c);
- }
- }
- return sb.toString();
- }
-
- @Nullable
- public String getSetName(@NonNull String language) {
- String name = mSetNamePerLanguage.get(language);
- if (name == null) {
- name = findSetName(language);
- if (name == null) {
- name = ""; // Store "" instead of null so we remember search result
- }
- mSetNamePerLanguage.put(language, name);
- }
-
- return name.isEmpty() ? null : name;
- }
-
- @Nullable
- private String findSetName(@NonNull String language) {
- String data = getPluralsDescriptions();
- int index = data.indexOf("locales{");
- if (index == -1) {
- return null;
- }
- int end = data.indexOf("locales_ordinals{", index + 1);
- if (end == -1) {
- return null;
- }
- String languageDeclaration = " " + language + "{\"";
- index = data.indexOf(languageDeclaration);
- if (index == -1 || index >= end) {
- return null;
- }
- int setEnd = data.indexOf('\"', index + languageDeclaration.length());
- if (setEnd == -1) {
- return null;
- }
- return data.substring(index + languageDeclaration.length(), setEnd).trim();
- }
-
- @Nullable
- public String getLocaleData(@NonNull String language) {
- String set = getSetName(language);
- if (set == null) {
- return null;
- }
- String data = getPluralsDescriptions();
- int setStart = data.indexOf(set + "{", mRuleSetOffset);
- if (setStart == -1) {
- return null;
- }
- int setEnd = findBalancedEnd(data, setStart);
- if (setEnd == -1) {
- return null;
- }
- return data.substring(setStart + set.length() + 1, setEnd);
- }
-
- private static int findBalancedEnd(String data, int offset) {
- int balance = 0;
- int length = data.length();
- for (; offset < length; offset++) {
- char c = data.charAt(offset);
- if (c == '{') {
- balance++;
- } else if (c == '}') {
- balance--;
- if (balance == 0) {
- return offset;
- }
- }
- }
-
- return -1;
- }
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java
deleted file mode 100644
index 4df2fd2..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.FN_PUBLIC_TXT;
-import static com.android.SdkConstants.FN_RESOURCE_TEXT;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.Variant;
-import com.android.testutils.TestUtils;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Project;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import org.mockito.stubbing.OngoingStubbing;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
- at SuppressWarnings("javadoc")
-public class PrivateResourceDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new PrivateResourceDetector();
- }
-
- public void testPrivateInXml() throws Exception {
- assertEquals(""
- + "res/layout/private.xml:11: Warning: The resource @string/my_private_string is marked as private in the library [PrivateResource]\n"
- + " android:text=\"@string/my_private_string\" />\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
- lintProject(xml("res/layout/private.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " android:id=\"@+id/newlinear\"\n"
- + " android:orientation=\"vertical\"\n"
- + " android:layout_width=\"match_parent\"\n"
- + " android:layout_height=\"match_parent\">\n"
- + "\n"
- + " <TextView\n"
- + " android:layout_width=\"wrap_content\"\n"
- + " android:layout_height=\"wrap_content\"\n"
- + " android:text=\"@string/my_private_string\" />\n"
- + "\n"
- + " <TextView\n"
- + " android:layout_width=\"wrap_content\"\n"
- + " android:layout_height=\"wrap_content\"\n"
- + " android:text=\"@string/my_public_string\" />\n"
- + "</LinearLayout>\n")));
- }
-
- public void testPrivateInJava() throws Exception {
- assertEquals(""
- + ""
- + "src/test/pkg/Private.java:3: Warning: The resource @string/my_private_string is marked as private in the library [PrivateResource]\n"
- + " int x = R.string.my_private_string; // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
- lintProject(java("src/test/pkg/Private.java", ""
- + "public class PrivateResourceDetectorTest {\n"
- + " void test() {\n"
- + " int x = R.string.my_private_string; // ERROR\n"
- + " int y = R.string.my_public_string; // OK\n"
- + " int y = android.R.string.my_private_string; // OK (not in project namespace)\n"
- + " }\n"
- + "}\n")));
- }
-
- public void testOverride() throws Exception {
- assertEquals(""
- + "res/layout/my_private_layout.xml: Warning: Overriding @layout/my_private_layout which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
- + "res/values/strings.xml:5: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
- + " <string name=\"my_private_string\">String 1</string>\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "res/values/strings.xml:9: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
- + " <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "res/values/strings.xml:12: Warning: Overriding @string/my_private_string which is marked as private in the library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
- + " <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 4 warnings\n",
- lintProject(xml("res/values/strings.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n"
- + "\n"
- + " <string name=\"app_name\">LibraryProject</string>\n"
- + " <string name=\"my_private_string\">String 1</string>\n"
- + " <string name=\"my_public_string\">String 2</string>\n"
- + " <string name=\"string3\"> @my_private_string </string>\n"
- + " <string name=\"string4\"> @my_public_string </string>\n"
- + " <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
- + " <dimen name=\"my_private_string\">String 1</dimen>\n" // unrelated
- + " <string tools:ignore=\"PrivateResource\" name=\"my_private_string\">String 2</string>\n"
- + " <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
- + " <string tools:override=\"true\" name=\"my_private_string\">String 2</string>\n"
- + "\n"
- + "</resources>\n"),
- xml("res/layout/my_private_layout.xml", "<LinearLayout/>"),
- xml("res/layout/my_public_layout.xml", "<LinearLayout/>")));
- }
-
- @Override
- protected TestLintClient createClient() {
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public AndroidProject getGradleProjectModel() {
- // First version which supported private resources; this does not
- // need to track later versions we release
- return createMockProject("1.3.0-alpha2", 3);
- }
-
- @Nullable
- @Override
- public Variant getCurrentVariant() {
- try {
- AndroidLibrary library = createMockLibrary(
- ""
- + "int string my_private_string 0x7f040000\n"
- + "int string my_public_string 0x7f040001\n"
- + "int layout my_private_layout 0x7f040002\n",
- ""
- + ""
- + "string my_public_string\n",
- Collections.<AndroidLibrary>emptyList()
- );
- AndroidArtifact artifact = createMockArtifact(
- Collections.singletonList(library));
-
- Variant variant = mock(Variant.class);
- when(variant.getMainArtifact()).thenReturn(artifact);
- return variant;
- } catch (Exception e) {
- fail(e.toString());
- return null;
- }
- }
- };
- }
- };
- }
-
- public static AndroidProject createMockProject(String modelVersion, int apiVersion) {
- AndroidProject project = mock(AndroidProject.class);
- when(project.getApiVersion()).thenReturn(apiVersion);
- when(project.getModelVersion()).thenReturn(modelVersion);
-
- return project;
- }
-
- public static AndroidArtifact createMockArtifact(List<AndroidLibrary> libraries) {
- Dependencies dependencies = mock(Dependencies.class);
- when(dependencies.getLibraries()).thenReturn(libraries);
-
- AndroidArtifact artifact = mock(AndroidArtifact.class);
- when(artifact.getDependencies()).thenReturn(dependencies);
-
- return artifact;
- }
-
- public static AndroidLibrary createMockLibrary(String allResources, String publicResources,
- List<AndroidLibrary> dependencies)
- throws IOException {
- final File tempDir = TestUtils.createTempDirDeletedOnExit();
-
- Files.write(allResources, new File(tempDir, FN_RESOURCE_TEXT), Charsets.UTF_8);
- File publicTxtFile = new File(tempDir, FN_PUBLIC_TXT);
- if (publicResources != null) {
- Files.write(publicResources, publicTxtFile, Charsets.UTF_8);
- }
- AndroidLibrary library = mock(AndroidLibrary.class);
- when(library.getPublicResources()).thenReturn(publicTxtFile);
-
- // Work around wildcard capture
- //when(mock.getLibraryDependencies()).thenReturn(dependencies);
- List libraryDependencies = library.getLibraryDependencies();
- OngoingStubbing<List> setter = when(libraryDependencies);
- setter.thenReturn(dependencies);
- return library;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java
deleted file mode 100644
index 23a5ac3..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class RegistrationDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new RegistrationDetector();
- }
-
- public void testRegistered() throws Exception {
- assertEquals(
- "src/test/pkg/OnClickActivity.java:8: Warning: The <activity> test.pkg.OnClickActivity is not registered in the manifest [Registered]\n" +
- "public class OnClickActivity extends Activity {\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/TestProvider.java:8: Warning: The <provider> test.pkg.TestProvider is not registered in the manifest [Registered]\n" +
- "public class TestProvider extends ContentProvider {\n" +
- " ~~~~~~~~~~~~\n" +
- "src/test/pkg/TestProvider2.java:3: Warning: The <provider> test.pkg.TestProvider2 is not registered in the manifest [Registered]\n" +
- "public class TestProvider2 extends TestProvider {\n" +
- "^\n" +
- "src/test/pkg/TestService.java:7: Warning: The <service> test.pkg.TestService is not registered in the manifest [Registered]\n" +
- "public class TestService extends Service {\n" +
- " ~~~~~~~~~~~\n" +
- "0 errors, 4 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
- "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
- "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
- "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
- "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
- "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
- "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
- "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
- "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class"
- ));
- }
-
- public void testNoDot() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/AndroidManifestReg.xml=>AndroidManifest.xml",
- "bytecode/.classpath=>.classpath",
- "bytecode/CommentsActivity.java.txt=>src/test/pkg/Foo/CommentsActivity.java",
- "bytecode/CommentsActivity.class.data=>bin/classes/test/pkg/Foo/CommentsActivity.class"
- ));
- }
-
- public void testWrongRegistrations() throws Exception {
- assertEquals(
- "src/test/pkg/OnClickActivity.java:8: Warning: test.pkg.OnClickActivity is a <activity> but is registered in the manifest as a <receiver> [Registered]\n" +
- "public class OnClickActivity extends Activity {\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/TestProvider.java:8: Warning: test.pkg.TestProvider is a <provider> but is registered in the manifest as a <activity> [Registered]\n" +
- "public class TestProvider extends ContentProvider {\n" +
- " ~~~~~~~~~~~~\n" +
- "src/test/pkg/TestProvider2.java:3: Warning: test.pkg.TestProvider2 is a <provider> but is registered in the manifest as a <service> [Registered]\n" +
- "public class TestProvider2 extends TestProvider {\n" +
- "^\n" +
- "src/test/pkg/TestReceiver.java:7: Warning: test.pkg.TestReceiver is a <receiver> but is registered in the manifest as a <service> [Registered]\n" +
- "public class TestReceiver extends BroadcastReceiver {\n" +
- " ~~~~~~~~~~~~\n" +
- "src/test/pkg/TestService.java:7: Warning: test.pkg.TestService is a <service> but is registered in the manifest as a <provider> [Registered]\n" +
- "public class TestService extends Service {\n" +
- " ~~~~~~~~~~~\n" +
- "0 errors, 5 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
- "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
- "bytecode/AbstractActivity.java.txt=>src/test/pkg/AbstractActivity.java",
- "bytecode/AbstractActivity.class.data=>bin/classes/test/pkg/AbstractActivity.class",
- "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
- "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
- "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
- "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
- "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
- "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
- "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
- "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class",
- "bytecode/TestReceiver$1.class.data=>bin/classes/test/pkg/TestReceiver$1.class"
- ));
- }
-
- public void testLibraryProjects() throws Exception {
- // If a library project provides additional activities, it is not an error to
- // not register all of those here
- assertEquals(
- "No warnings.",
-
- lintProject(
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
-
- // Library project
- "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
- "multiproject/library.properties=>../LibraryProject/project.properties",
-
- "bytecode/.classpath=>../LibraryProject/.classpath",
- "bytecode/OnClickActivity.java.txt=>../LibraryProject/src/test/pkg/OnClickActivity.java",
- "bytecode/OnClickActivity.class.data=>../LibraryProject/bin/classes/test/pkg/OnClickActivity.class"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java
deleted file mode 100644
index 0e78d22..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class RelativeOverlapDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new RelativeOverlapDetector();
- }
-
- public void testOneOverlap() throws Exception {
- assertEquals(
- "res/layout/relative_overlap.xml:17: Warning: @id/label2 can overlap @id/label1 if @string/label1_text, @string/label2_text grow due to localized text expansion [RelativeOverlap]\n" +
- " <TextView\n" +
- " ^\n" +
- "0 errors, 1 warnings\n",
- lintFiles("res/layout/relative_overlap.xml"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
deleted file mode 100644
index da36270..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
-import java.io.File;
-
- at SuppressWarnings("javadoc")
-public class RequiredAttributeDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new RequiredAttributeDetector();
- }
-
- public void test() throws Exception {
- // Simple: Only consider missing attributes in the layout xml file
- // (though skip warnings on <merge> tags and under <GridLayout>
- assertEquals(
- "res/layout/size.xml:13: Error: The required layout_height attribute is missing [RequiredSize]\n" +
- " <RadioButton\n" +
- " ^\n" +
- "res/layout/size.xml:18: Error: The required layout_width attribute is missing [RequiredSize]\n" +
- " <EditText\n" +
- " ^\n" +
- "res/layout/size.xml:23: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
- " <EditText\n" +
- " ^\n" +
- "3 errors, 0 warnings\n",
-
- lintProject("res/layout/size.xml"));
- }
-
- public void test2() throws Exception {
- // Consider styles (specifying sizes) and includes (providing sizes for the root tags)
- assertEquals(
- "res/layout/size2.xml:9: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
- " <Button\n" +
- " ^\n" +
- "res/layout/size2.xml:18: Error: The required layout_height attribute is missing [RequiredSize]\n" +
- " <Button\n" +
- " ^\n" +
- "2 errors, 0 warnings\n",
-
- lintProject(
- "res/layout/size2.xml",
- "res/layout/sizeincluded.xml",
- "res/values/sizestyles.xml"
- ));
- }
-
- public void testInflaters() throws Exception {
- // Consider java inflation
- assertEquals(
- "res/layout/size5.xml:2: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
- "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- "^\n" +
- "1 errors, 0 warnings\n",
-
- lintProject(
- "src/test/pkg/InflaterTest.java.txt=>src/test/pkg/InflaterTest.java",
- "res/layout/sizeincluded.xml=>res/layout/size1.xml",
- "res/layout/sizeincluded.xml=>res/layout/size2.xml",
- "res/layout/sizeincluded.xml=>res/layout/size3.xml",
- "res/layout/sizeincluded.xml=>res/layout/size4.xml",
- "res/layout/sizeincluded.xml=>res/layout/size5.xml",
- "res/layout/sizeincluded.xml=>res/layout/size6.xml",
- "res/layout/sizeincluded.xml=>res/layout/size7.xml"
- ));
- }
-
- public void testRequestFocus() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=38700
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/layout/edit_type.xml"
- ));
- }
-
- public void testFrameworkStyles() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=38958
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/layout/listseparator.xml"
- ));
- }
-
- public void testThemeStyles() throws Exception {
- // Check that we don't complain about cases where the size is defined in a theme
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/layout/size.xml",
- "res/values/themes.xml"
- ));
- }
-
- public void testThemeStyles2() throws Exception {
- // Check that we don't complain about cases where the size is defined in a theme
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/layout/size.xml",
- "res/values/themes2.xml"
- ));
- }
-
- public void testHasLayoutVariations() throws Exception {
- File projectDir = getProjectDir(null,
- copy("res/layout/size.xml"),
- copy("res/layout/size.xml", "res/layout-land/size.xml"),
- copy("res/layout/size.xml", "res/layout/size2.xml"));
- assertTrue(RequiredAttributeDetector.hasLayoutVariations(
- new File(projectDir, "res/layout/size.xml".replace('/', File.separatorChar))));
- assertTrue(RequiredAttributeDetector.hasLayoutVariations(
- new File(projectDir, "res/layout-land/size.xml".replace('/', File.separatorChar))));
- assertFalse(RequiredAttributeDetector.hasLayoutVariations(
- new File(projectDir, "res/layout/size2.xml".replace('/', File.separatorChar))));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
deleted file mode 100644
index 7c3aced..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Project;
-
-import java.io.File;
-import java.util.Arrays;
-
-public class ResourcePrefixDetectorTest extends AbstractCheckTest {
-
- @Override
- protected Detector getDetector() {
- return new ResourcePrefixDetector();
- }
-
- public void testResourceFiles() throws Exception {
- assertEquals(""
- + "res/drawable-mdpi/frame.png: Error: Resource named 'frame' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_frame' ? [ResourceName]\n"
- + "res/layout/layout1.xml:2: Error: Resource named 'layout1' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_layout1' ? [ResourceName]\n"
- + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + "^\n"
- + "res/menu/menu.xml:2: Error: Resource named 'menu' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_menu' ? [ResourceName]\n"
- + "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
- + "^\n"
- + "3 errors, 0 warnings\n",
- lintProject(
- "res/layout/layout1.xml",
- "res/menu/menu.xml",
- "res/layout/layout1.xml=>res/layout/unit_test_prefix_ok.xml",
- "res/drawable-mdpi/frame.png",
- "res/drawable-mdpi/frame.png=>res/drawable/unit_test_prefix_ok1.png",
- "res/drawable-mdpi/frame.png=>res/drawable/unit_test_prefix_ok2.9.png"
- ));
- }
-
- public void testValues() throws Exception {
- assertEquals(""
- + "res/values/customattr.xml:2: Error: Resource named 'ContentFrame' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_ContentFrame' ? [ResourceName]\n"
- + " <declare-styleable name=\"ContentFrame\">\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "res/values/customattr.xml:3: Error: Resource named 'content' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_content' ? [ResourceName]\n"
- + " <attr name=\"content\" format=\"reference\" />\n"
- + " ~~~~~~~~~~~~~~\n"
- + "res/values/customattr.xml:4: Error: Resource named 'contentId' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_contentId' ? [ResourceName]\n"
- + " <attr name=\"contentId\" format=\"reference\" />\n"
- + " ~~~~~~~~~~~~~~~~\n"
- + "res/layout/customattrlayout.xml:2: Error: Resource named 'customattrlayout' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_customattrlayout' ? [ResourceName]\n"
- + "<foo.bar.ContentFrame\n"
- + "^\n"
- + "4 errors, 0 warnings\n",
-
- lintProject(
- "res/values/customattr.xml",
- "res/layout/customattrlayout.xml",
- "unusedR.java.txt=>gen/my/pkg/R.java",
- "AndroidManifest.xml"));
- }
-
- public void testMultiProject() throws Exception {
- File master = getProjectDir("MasterProject",
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
- );
- File library = getProjectDir("LibraryProject",
- // Library project
- "multiproject/library-manifest.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>res/values/strings.xml"
- );
- assertEquals(""
- + "LibraryProject/res/values/strings.xml:4: Error: Resource named 'app_name' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_app_name' ? [ResourceName]\n"
- + " <string name=\"app_name\">LibraryProject</string>\n"
- + " ~~~~~~~~~~~~~~~\n"
- + "LibraryProject/res/values/strings.xml:5: Error: Resource named 'string1' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string1' ? [ResourceName]\n"
- + " <string name=\"string1\">String 1</string>\n"
- + " ~~~~~~~~~~~~~~\n"
- + "LibraryProject/res/values/strings.xml:6: Error: Resource named 'string2' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string2' ? [ResourceName]\n"
- + " <string name=\"string2\">String 2</string>\n"
- + " ~~~~~~~~~~~~~~\n"
- + "LibraryProject/res/values/strings.xml:7: Error: Resource named 'string3' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string3' ? [ResourceName]\n"
- + " <string name=\"string3\">String 3</string>\n"
- + " ~~~~~~~~~~~~~~\n"
- + "4 errors, 0 warnings\n",
-
- checkLint(Arrays.asList(master, library)).replace("/TESTROOT/",""));
- }
-
- // TODO: Test suppressing root level tag
-
- @Override
- protected TestLintClient createClient() {
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public AndroidProject getGradleProjectModel() {
- AndroidProject project = mock(AndroidProject.class);
- when(project.getResourcePrefix()).thenReturn("unit_test_prefix_");
- return project;
- }
- };
- }
- };
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
deleted file mode 100644
index 5e3e42e..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_DRAWABLE_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.tools.lint.checks.RtlDetector.ATTRIBUTES;
-import static com.android.tools.lint.checks.RtlDetector.convertNewToOld;
-import static com.android.tools.lint.checks.RtlDetector.convertOldToNew;
-import static com.android.tools.lint.checks.RtlDetector.convertToOppositeDirection;
-import static com.android.tools.lint.checks.RtlDetector.isRtlAttributeName;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Project;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
- at SuppressWarnings("javadoc")
-public class RtlDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new RtlDetector();
- }
-
- private Set<Issue> mEnabled = new HashSet<Issue>();
- private static final Set<Issue> ALL = new HashSet<Issue>();
- static {
- ALL.add(RtlDetector.USE_START);
- ALL.add(RtlDetector.ENABLED);
- ALL.add(RtlDetector.COMPAT);
- ALL.add(RtlDetector.SYMMETRY);
- }
-
- public void testIsRtlAttributeName() {
- assertTrue(isRtlAttributeName(ATTR_LAYOUT_ALIGN_PARENT_START));
- assertTrue(isRtlAttributeName(ATTR_LAYOUT_MARGIN_END));
- assertTrue(isRtlAttributeName(ATTR_LAYOUT_ALIGN_END));
- assertFalse(isRtlAttributeName(ATTR_LAYOUT_ALIGN_PARENT_LEFT));
- assertFalse(isRtlAttributeName(ATTR_DRAWABLE_RIGHT));
- assertFalse(isRtlAttributeName(ATTR_LAYOUT_ALIGN_RIGHT));
- assertFalse(isRtlAttributeName(ATTR_NAME));
- }
-
- @Override
- protected TestConfiguration getConfiguration(LintClient client, Project project) {
- return new TestConfiguration(client, project, null) {
- @Override
- public boolean isEnabled(@NonNull Issue issue) {
- return super.isEnabled(issue) && mEnabled.contains(issue);
- }
- };
- }
-
- public void testTarget14WithRtl() throws Exception {
- mEnabled = ALL;
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "minsdk5targetsdk14.xml=>AndroidManifest.xml",
- "rtl/rtl.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testTarget17WithRtl() throws Exception {
- mEnabled = ALL;
- assertEquals(""
- + "res/layout/rtl.xml:14: Warning: Use \"start\" instead of \"left\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:layout_gravity=\"left\"\n"
- + " ~~~~\n"
- + "res/layout/rtl.xml:22: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:layout_gravity=\"right\"\n"
- + " ~~~~~\n"
- + "res/layout/rtl.xml:30: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:gravity=\"right\"\n"
- + " ~~~~~\n"
- + "AndroidManifest.xml: Warning: The project references RTL attributes, but does not explicitly enable or disable RTL support with android:supportsRtl in the manifest [RtlEnabled]\n"
- + "0 errors, 4 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/rtl.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testTarget14() throws Exception {
- mEnabled = ALL;
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "minsdk5targetsdk14.xml=>AndroidManifest.xml"
- ));
- }
-
- public void testOlderCompilationTarget() throws Exception {
- mEnabled = ALL;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "rtl/project-api14.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/rtl.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testUseStart() throws Exception {
- mEnabled = Collections.singleton(RtlDetector.USE_START);
- assertEquals(""
- + "res/layout/rtl.xml:14: Warning: Use \"start\" instead of \"left\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:layout_gravity=\"left\"\n"
- + " ~~~~\n"
- + "res/layout/rtl.xml:22: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:layout_gravity=\"right\"\n"
- + " ~~~~~\n"
- + "res/layout/rtl.xml:30: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:gravity=\"right\"\n"
- + " ~~~~~\n"
- + "0 errors, 3 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/rtl.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testTarget17Rtl() throws Exception {
- mEnabled = Collections.singleton(RtlDetector.USE_START);
- assertEquals(""
- + "res/layout/rtl.xml:14: Warning: Use \"start\" instead of \"left\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:layout_gravity=\"left\"\n"
- + " ~~~~\n"
- + "res/layout/rtl.xml:22: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:layout_gravity=\"right\"\n"
- + " ~~~~~\n"
- + "res/layout/rtl.xml:30: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " android:gravity=\"right\"\n"
- + " ~~~~~\n"
- + "0 errors, 3 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/min17rtl.xml=>AndroidManifest.xml",
- "rtl/rtl.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testRelativeLayoutInOld() throws Exception {
- mEnabled = Collections.singleton(RtlDetector.USE_START);
- assertEquals(""
- + "res/layout/relative.xml:10: Warning: Consider adding android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignParentLeft=\"true\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:13: Warning: Consider adding android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_marginLeft=\"40dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:24: Warning: Consider adding android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_marginLeft=\"40dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:26: Warning: Consider adding android:layout_toEndOf=\"@id/loading_progress\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_toRightOf=\"@id/loading_progress\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:29: Warning: Consider adding android:paddingEnd=\"120dip\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:paddingRight=\"120dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:37: Warning: Consider adding android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignParentLeft=\"true\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:38: Warning: Consider adding android:layout_alignEnd=\"@id/text\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignRight=\"@id/text\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:47: Warning: Consider adding android:layout_alignStart=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignLeft=\"@id/cancel\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:48: Warning: Consider adding android:layout_alignEnd=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignRight=\"@id/cancel\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 9 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/relative.xml=>res/layout/relative.xml"
- ));
- }
-
- public void testRelativeLayoutInNew() throws Exception {
- mEnabled = Collections.singleton(RtlDetector.USE_START);
- assertEquals(""
- + "res/layout/relative.xml:10: Warning: Consider replacing android:layout_alignParentLeft with android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignParentLeft=\"true\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:13: Warning: Consider replacing android:layout_marginLeft with android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_marginLeft=\"40dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:24: Warning: Consider replacing android:layout_marginLeft with android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_marginLeft=\"40dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:26: Warning: Consider replacing android:layout_toRightOf with android:layout_toEndOf=\"@id/loading_progress\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_toRightOf=\"@id/loading_progress\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:29: Warning: Consider replacing android:paddingRight with android:paddingEnd=\"120dip\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:paddingRight=\"120dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:37: Warning: Consider replacing android:layout_alignParentLeft with android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignParentLeft=\"true\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:38: Warning: Consider replacing android:layout_alignRight with android:layout_alignEnd=\"@id/text\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignRight=\"@id/text\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:47: Warning: Consider replacing android:layout_alignLeft with android:layout_alignStart=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignLeft=\"@id/cancel\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:48: Warning: Consider replacing android:layout_alignRight with android:layout_alignEnd=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
- + " android:layout_alignRight=\"@id/cancel\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 9 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/min17rtl.xml=>AndroidManifest.xml",
- "rtl/relative.xml=>res/layout/relative.xml"
- ));
- }
-
- public void testRelativeLayoutCompat() throws Exception {
- mEnabled = Collections.singleton(RtlDetector.COMPAT);
- assertEquals(""
- + "res/layout/relative.xml:10: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignParentLeft=\"true\" [RtlCompat]\n"
- + " android:layout_alignParentStart=\"true\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:13: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_marginLeft=\"40dip\" [RtlCompat]\n"
- + " android:layout_marginStart=\"40dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:24: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_marginLeft=\"40dip\" [RtlCompat]\n"
- + " android:layout_marginStart=\"40dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:26: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_toRightOf=\"@id/loading_progress\" [RtlCompat]\n"
- + " android:layout_toEndOf=\"@id/loading_progress\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:29: Error: To support older versions than API 17 (project specifies 5) you should also add android:paddingRight=\"120dip\" [RtlCompat]\n"
- + " android:paddingEnd=\"120dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:37: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignParentLeft=\"true\" [RtlCompat]\n"
- + " android:layout_alignParentStart=\"true\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:38: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignRight=\"@id/text\" [RtlCompat]\n"
- + " android:layout_alignEnd=\"@id/text\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:47: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignLeft=\"@id/cancel\" [RtlCompat]\n"
- + " android:layout_alignStart=\"@id/cancel\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/relative.xml:48: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignRight=\"@id/cancel\" [RtlCompat]\n"
- + " android:layout_alignEnd=\"@id/cancel\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "9 errors, 0 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/relativeCompat.xml=>res/layout/relative.xml"
- ));
- }
-
-
- public void testRelativeCompatOk() throws Exception {
- mEnabled = ALL;
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/relativeOk.xml=>res/layout/relative.xml"
- ));
- }
-
- public void testTarget17NoRtl() throws Exception {
- mEnabled = ALL;
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/min17nortl.xml=>AndroidManifest.xml",
- "rtl/rtl.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testJava() throws Exception {
- mEnabled = ALL;
- assertEquals(""
- + "src/test/pkg/GravityTest.java:24: Warning: Use \"Gravity.START\" instead of \"Gravity.LEFT\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " t1.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);\n"
- + " ~~~~\n"
- + "src/test/pkg/GravityTest.java:30: Warning: Use \"Gravity.START\" instead of \"Gravity.LEFT\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " t1.setGravity(LEFT | RIGHT); // static imports\n"
- + " ~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/GravityTest.java.txt=>src/test/pkg/GravityTest.java"
- ));
- }
-
- public void testOk1() throws Exception {
- mEnabled = ALL;
- // targetSdkVersion < 17
- assertEquals(
- "No warnings.",
-
- lintProject("rtl/rtl.xml=>res/layout/rtl.xml"));
- }
-
- public void testOk2() throws Exception {
- mEnabled = ALL;
- // build target < 14
- assertEquals(
- "No warnings.",
-
- lintProject(
- "overdraw/project.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/rtl.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testNullLocalName() throws Exception {
- // Regression test for attribute with null local name
- mEnabled = ALL;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "overdraw/project.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/rtl_noprefix.xml=>res/layout/rtl.xml"
- ));
- }
-
- public void testSymmetry() throws Exception {
- mEnabled = Collections.singleton(RtlDetector.SYMMETRY);
- assertEquals(""
- + "res/layout/relative.xml:29: Warning: When you define paddingRight you should probably also define paddingLeft for right-to-left symmetry [RtlSymmetry]\n"
- + " android:paddingRight=\"120dip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/relative.xml=>res/layout/relative.xml"
- ));
- }
-
- public void testCompatAttributeValueConversion() throws Exception {
- // Ensure that when the RTL value contains a direction, we produce the
- // compatibility version of it for the compatibility attribute, e.g. if the
- // attribute for paddingEnd is ?listPreferredItemPaddingEnd, when we suggest
- // also setting paddingRight we suggest ?listPreferredItemPaddingRight
- mEnabled = Collections.singleton(RtlDetector.COMPAT);
- assertEquals(""
- + "res/layout/symmetry.xml:8: Error: To support older versions than API 17 (project specifies 5) you should also add android:paddingRight=\"?android:listPreferredItemPaddingRight\" [RtlCompat]\n"
- + " android:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/symmetry.xml=>res/layout/symmetry.xml"
- ));
- }
-
- public void testTextAlignment() throws Exception {
- mEnabled = Collections.singleton(RtlDetector.COMPAT);
- assertEquals(""
- + "res/layout/spinner.xml:49: Error: Inconsistent alignment specification between textAlignment and gravity attributes: was end, expected start [RtlCompat]\n"
- + " android:textAlignment=\"textStart\"/> <!-- ERROR -->\n"
- + " ~~~~~~~~~\n"
- + " res/layout/spinner.xml:46: Incompatible direction here\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/spinner.xml=>res/layout/spinner.xml"
- ));
- }
-
- public void testConvertBetweenAttributes() {
- assertEquals("alignParentStart", convertOldToNew("alignParentLeft"));
- assertEquals("alignParentEnd", convertOldToNew("alignParentRight"));
- assertEquals("paddingEnd", convertOldToNew("paddingRight"));
- assertEquals("paddingStart", convertOldToNew("paddingLeft"));
-
- assertEquals("alignParentLeft", convertNewToOld("alignParentStart"));
- assertEquals("alignParentRight", convertNewToOld("alignParentEnd"));
- assertEquals("paddingRight", convertNewToOld("paddingEnd"));
- assertEquals("paddingLeft", convertNewToOld("paddingStart"));
-
- for (int i = 0, n = ATTRIBUTES.length; i < n; i += 2) {
- String oldAttribute = ATTRIBUTES[i];
- String newAttribute = ATTRIBUTES[i + 1];
- assertEquals(newAttribute, convertOldToNew(oldAttribute));
- assertEquals(oldAttribute, convertNewToOld(newAttribute));
- }
- }
-
- public void testConvertToOppositeDirection() {
- assertEquals("alignParentRight", convertToOppositeDirection("alignParentLeft"));
- assertEquals("alignParentLeft", convertToOppositeDirection("alignParentRight"));
- assertEquals("alignParentStart", convertToOppositeDirection("alignParentEnd"));
- assertEquals("alignParentEnd", convertToOppositeDirection("alignParentStart"));
- assertEquals("paddingLeft", convertToOppositeDirection("paddingRight"));
- assertEquals("paddingRight", convertToOppositeDirection("paddingLeft"));
- assertEquals("paddingStart", convertToOppositeDirection("paddingEnd"));
- assertEquals("paddingEnd", convertToOppositeDirection("paddingStart"));
- }
-
- public void testEnumConstants() throws Exception {
- // Regression test for
- // https://code.google.com/p/android/issues/detail?id=75480
- // Also checks that static imports work correctly
- mEnabled = ALL;
- assertEquals(""
- + "src/test/pkg/GravityTest2.java:19: Warning: Use \"Gravity.START\" instead of \"Gravity.LEFT\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
- + " if (gravity == LEFT) { // ERROR\n"
- + " ~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "rtl/project-api17.properties=>project.properties",
- "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
- "rtl/GravityTest2.java.txt=>src/test/pkg/GravityTest2.java"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java
deleted file mode 100644
index d98ed6a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
-public class SQLiteDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new SQLiteDetector();
- }
-
- public void test() throws Exception {
- assertEquals(""
- + "src/test/pkg/SQLiteTest.java:25: Warning: Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example,strings that look like integers can drop leading zeroes. See issue explanation for details.) [SQLiteString]\n"
- + " db.execSQL(\"CREATE TABLE \" + name + \"(\" + Tables.AppKeys.SCHEMA + \");\"); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/SQLiteTest.java:30: Warning: Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example,strings that look like integers can drop leading zeroes. See issue explanation for details.) [SQLiteString]\n"
- + " db.execSQL(TracksColumns.CREATE_TABLE); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 2 warnings\n",
-
- lintProject(
- "src/test/pkg/MyTracksProvider.java.txt=>src/test/pkg/MyTracksProvider.java",
- "src/test/pkg/SQLiteTest.java.txt=>src/test/pkg/SQLiteTest.java",
- // stub for type resolution
- "src/test/pkg/SQLiteDatabase.java.txt=>src/android/database/sqlite/SQLiteDatabase.java"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java
deleted file mode 100644
index 5ad46b9..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class SdCardDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new SdCardDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/SdCardTest.java:13: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " private static final String SDCARD_TEST_HTML = \"/sdcard/test.html\";\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:14: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " public static final String SDCARD_ROOT = \"/sdcard\";\n" +
- " ~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:15: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " public static final String PACKAGES_PATH = \"/sdcard/o/packages/\";\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:16: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " File deviceDir = new File(\"/sdcard/vr\");\n" +
- " ~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:20: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " android.os.Debug.startMethodTracing(\"/sdcard/launcher\");\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:22: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " if (new File(\"/sdcard\").exists()) {\n" +
- " ~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:24: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " String FilePath = \"/sdcard/\" + new File(\"test\");\n" +
- " ~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:29: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " intent.setDataAndType(Uri.parse(\"file://sdcard/foo.json\"), \"application/bar-json\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:30: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " intent.putExtra(\"path-filter\", \"/sdcard(/.+)*\");\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:31: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " intent.putExtra(\"start-dir\", \"/sdcard\");\n" +
- " ~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:32: Warning: Do not hardcode \"/data/\"; use Context.getFilesDir().getPath() instead [SdCardPath]\n" +
- " String mypath = \"/data/data/foo\";\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:33: Warning: Do not hardcode \"/data/\"; use Context.getFilesDir().getPath() instead [SdCardPath]\n" +
- " String base = \"/data/data/foo.bar/test-profiling\";\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SdCardTest.java:34: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " String s = \"file://sdcard/foo\";\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 13 warnings\n",
-
- lintProject("src/test/pkg/SdCardTest.java.txt=>src/test/pkg/SdCardTest.java"));
- }
-
- public void testSuppress() throws Exception {
- assertEquals(
- // The only reference in the file not covered by an annotation
- "src/test/pkg/SuppressTest5.java:40: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " String notAnnotated = \"/sdcard/mypath\";\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- // File with lots of /sdcard references, but with @SuppressLint warnings
- // on fields, methods, variable declarations etc
- lintProject("src/test/pkg/SuppressTest5.java.txt=>src/test/pkg/SuppressTest5.java"));
- }
-
- public void testUtf8Bom() throws Exception {
- assertEquals(
- "src/test/pkg/Utf8BomTest.java:4: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
- " String s = \"/sdcard/mydir\";\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject("src/test/pkg/Utf8BomTest.java.data=>src/test/pkg/Utf8BomTest.java"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java
deleted file mode 100644
index 0ecbe02..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class SecureRandomDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new SecureRandomDetector();
- }
-
- public void test1() throws Exception {
- assertEquals(
- "src/test/pkg/SecureRandomTest.java:12: Warning: It is dangerous to seed SecureRandom with the current time because that value is more predictable to an attacker than the default seed. [SecureRandom]\n" +
- " random1.setSeed(System.currentTimeMillis()); // OK\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/SecureRandomTest.java:14: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
- " random1.setSeed(0); // Wrong\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/SecureRandomTest.java:15: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
- " random1.setSeed(1); // Wrong\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/SecureRandomTest.java:16: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
- " random1.setSeed((int)1023); // Wrong\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/SecureRandomTest.java:17: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
- " random1.setSeed(1023L); // Wrong\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/SecureRandomTest.java:18: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
- " random1.setSeed(FIXED_SEED); // Wrong\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/SecureRandomTest.java:28: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n" +
- " random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object\n" +
- " ~~~~~~~\n" +
- "0 errors, 7 warnings\n" +
- "",
- // Missing error on line 40, using flow analysis to determine that the seed byte
- // array passed into the SecureRandom constructor is static.
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/SecureRandomTest.java.txt=>src/test/pkg/SecureRandomTest.java",
- "bytecode/SecureRandomTest.class.data=>bin/classes/test/pkg/SecureRandomTest.class"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java
deleted file mode 100644
index a2ac0f8..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class SecurityDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new SecurityDetector();
- }
-
- public void testBroken() throws Exception {
- assertEquals(
- "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" +
- " <service\n" +
- " ^\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "exportservice1.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testBroken2() throws Exception {
- assertEquals(
- "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" +
- " <service\n" +
- " ^\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "exportservice2.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testBroken3() throws Exception {
- // Not defining exported, but have intent-filters
- assertEquals(
- "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" +
- " <service\n" +
- " ^\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "exportservice5.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testOk1() throws Exception {
- // Defines a permission on the <service> element
- assertEquals(
- "No warnings.",
- lintProject(
- "exportservice3.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testOk2() throws Exception {
- // Defines a permission on the parent <application> element
- assertEquals(
- "No warnings.",
- lintProject(
- "exportservice4.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testUri() throws Exception {
- assertEquals(
- "AndroidManifest.xml:25: Warning: Content provider shares everything; this is potentially dangerous. [GrantAllUris]\n" +
- " <grant-uri-permission android:path=\"/\"/>\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "AndroidManifest.xml:26: Warning: Content provider shares everything; this is potentially dangerous. [GrantAllUris]\n" +
- " <grant-uri-permission android:pathPrefix=\"/\"/>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 2 warnings\n" +
- "",
-
- lintProject(
- "grantpermission.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- // exportprovider1.xml has two exported content providers with no permissions
- public void testContentProvider1() throws Exception {
- assertEquals(
- "AndroidManifest.xml:14: Warning: Exported content providers can provide access to potentially sensitive data [ExportedContentProvider]\n" +
- " <provider\n" +
- " ^\n" +
- "AndroidManifest.xml:20: Warning: Exported content providers can provide access to potentially sensitive data [ExportedContentProvider]\n" +
- " <provider\n" +
- " ^\n" +
- "0 errors, 2 warnings\n" +
- "",
- lintProject(
- "exportprovider1.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- // exportprovider2.xml has no un-permissioned exported content providers
- public void testContentProvider2() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject(
- "exportprovider2.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testWorldWriteable() throws Exception {
- assertEquals(
- "src/test/pkg/WorldWriteableFile.java:26: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
- " out = openFileOutput(mFile.getName(), MODE_WORLD_READABLE);\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/WorldWriteableFile.java:31: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
- " prefs = getSharedPreferences(mContext, MODE_WORLD_READABLE);\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/WorldWriteableFile.java:25: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
- " out = openFileOutput(mFile.getName(), MODE_WORLD_WRITEABLE);\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/WorldWriteableFile.java:30: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
- " prefs = getSharedPreferences(mContext, MODE_WORLD_WRITEABLE);\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 4 warnings\n" +
- "",
-
- lintProject(
- // Java files must be renamed in source tree
- "src/test/pkg/WorldWriteableFile.java.txt=>src/test/pkg/WorldWriteableFile.java"));
- }
-
- public void testReceiver0() throws Exception {
- // Activities that do not have intent-filters do not need warnings
- assertEquals(
- "No warnings.",
- lintProject(
- "exportreceiver0.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testReceiver1() throws Exception {
- assertEquals(
- "AndroidManifest.xml:12: Warning: Exported receiver does not require permission [ExportedReceiver]\n" +
- " <receiver\n" +
- " ^\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "exportreceiver1.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testReceiver2() throws Exception {
- // Defines a permission on the <activity> element
- assertEquals(
- "No warnings.",
- lintProject(
- "exportreceiver2.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testReceiver3() throws Exception {
- // Defines a permission on the parent <application> element
- assertEquals(
- "No warnings.",
- lintProject(
- "exportreceiver3.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testReceiver4() throws Exception {
- // Not defining exported, but have intent-filters
- assertEquals(
- "AndroidManifest.xml:12: Warning: Exported receiver does not require permission [ExportedReceiver]\n" +
- " <receiver\n" +
- " ^\n" +
- "0 errors, 1 warnings\n" +
- "",
- lintProject(
- "exportreceiver4.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testReceiver5() throws Exception {
- // Intent filter for standard Android action
- assertEquals(
- "No warnings.",
- lintProject(
- "exportreceiver5.xml=>AndroidManifest.xml",
- "res/values/strings.xml"));
- }
-
- public void testStandard() throws Exception {
- // Various regression tests for http://code.google.com/p/android/issues/detail?id=33976
- assertEquals(
- "No warnings.",
- lintProject("exportreceiver6.xml=>AndroidManifest.xml"));
- }
-
- public void testUsingInstallReferrerReceiver() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=73934
- assertEquals(
- "No warnings.",
- lintProject("exportreceiver7.xml=>AndroidManifest.xml"));
- }
-
- public void testGmsWearable() throws Exception {
- // As documented in
- // https://developer.android.com/training/wearables/data-layer/events.html
- // you shouldn't need a permission here.
- assertEquals(
- "No warnings.",
- lintProject("exportreceiver8.xml=>AndroidManifest.xml"));
-
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
deleted file mode 100644
index 4fb95fa..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class SharedPrefsDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new SharedPrefsDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/SharedPrefsTest.java:54: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " SharedPreferences.Editor editor = preferences.edit();\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest.java:62: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " SharedPreferences.Editor editor = preferences.edit();\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 2 warnings\n" +
- "",
-
- lintProject("src/test/pkg/SharedPrefsTest.java.txt=>" +
- "src/test/pkg/SharedPrefsTest.java"));
- }
-
- public void test2() throws Exception {
- // Regression test 1 for http://code.google.com/p/android/issues/detail?id=34322
- assertEquals(
- "src/test/pkg/SharedPrefsTest2.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " SharedPreferences.Editor editor = preferences.edit();\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest2.java:17: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " Editor editor = preferences.edit();\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 2 warnings\n",
-
- lintProject("src/test/pkg/SharedPrefsTest2.java.txt=>" +
- "src/test/pkg/SharedPrefsTest2.java"));
- }
-
- public void test3() throws Exception {
- // Regression test 2 for http://code.google.com/p/android/issues/detail?id=34322
- assertEquals(
- "src/test/pkg/SharedPrefsTest3.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " Editor editor = preferences.edit();\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n",
-
- lintProject("src/test/pkg/SharedPrefsTest3.java.txt=>" +
- "src/test/pkg/SharedPrefsTest3.java"));
- }
-
- public void test4() throws Exception {
- // Regression test 3 for http://code.google.com/p/android/issues/detail?id=34322
- assertEquals(""
- + "src/test/pkg/SharedPrefsTest4.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
- + " Editor editor = preferences.edit();\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("src/test/pkg/SharedPrefsTest4.java.txt=>" +
- "src/test/pkg/SharedPrefsTest4.java"));
- }
-
- public void test5() throws Exception {
- // Check fields too: http://code.google.com/p/android/issues/detail?id=39134
- assertEquals(
- "src/test/pkg/SharedPrefsTest5.java:16: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " mPreferences.edit().putString(PREF_FOO, \"bar\");\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest5.java:17: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " mPreferences.edit().remove(PREF_BAZ).remove(PREF_FOO);\n" +
- " ~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest5.java:26: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " preferences.edit().putString(PREF_FOO, \"bar\");\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest5.java:27: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " preferences.edit().remove(PREF_BAZ).remove(PREF_FOO);\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest5.java:32: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " preferences.edit().putString(PREF_FOO, \"bar\");\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest5.java:33: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " preferences.edit().remove(PREF_BAZ).remove(PREF_FOO);\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/SharedPrefsTest5.java:38: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
- " Editor editor = preferences.edit().putString(PREF_FOO, \"bar\");\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 7 warnings\n",
-
- lintProject("src/test/pkg/SharedPrefsTest5.java.txt=>" +
- "src/test/pkg/SharedPrefsTest5.java"));
- }
-
- public void test6() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=68692
- assertEquals(""
- + "src/test/pkg/SharedPrefsTest7.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
- + " settings.edit().putString(MY_PREF_KEY, myPrefValue);\n"
- + " ~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject("src/test/pkg/SharedPrefsTest7.java.txt=>" +
- "src/test/pkg/SharedPrefsTest7.java"));
- }
-
- public void test7() throws Exception {
- assertEquals("No warnings.", // minSdk < 9: no warnings
-
- lintProject("src/test/pkg/SharedPrefsTest8.java.txt=>" +
- "src/test/pkg/SharedPrefsTest8.java"));
- }
-
- public void test8() throws Exception {
- assertEquals(""
- + "src/test/pkg/SharedPrefsTest8.java:11: Warning: Consider using apply() instead; commit writes its data to persistent storage immediately, whereas apply will handle it in the background [CommitPrefEdits]\n"
- + " editor.commit();\n"
- + " ~~~~~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "apicheck/minsdk11.xml=>AndroidManifest.xml",
- "src/test/pkg/SharedPrefsTest8.java.txt=>src/test/pkg/SharedPrefsTest8.java"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
deleted file mode 100644
index 862d0ec..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.checks.StringFormatDetector.isLocaleSpecific;
-
-import com.android.tools.lint.detector.api.Detector;
-
-import java.util.HashSet;
-import java.util.Set;
-
- at SuppressWarnings("javadoc")
-public class StringFormatDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new StringFormatDetector();
- }
-
- public void testAll() throws Exception {
- assertEquals(
- "src/test/pkg/StringFormatActivity.java:13: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
- " String output1 = String.format(hello, target);\n" +
- " ~~~~~~\n" +
- " res/values-es/formatstrings.xml:3: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormatActivity.java:15: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " String output2 = String.format(hello2, target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:21: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
- " String output4 = String.format(score, true); // wrong\n" +
- " ~~~~\n" +
- " res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
- " String output4 = String.format(score, won); // wrong\n" +
- " ~~~\n" +
- " res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:25: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " getResources().getString(hello2, target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:26: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " getResources().getString(R.string.hello2, target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:33: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
- " String output1 = String.format(hello, target);\n" +
- " ~~~~~~\n" +
- " res/values-es/formatstrings.xml:3: Conflicting argument declaration here\n" +
- "res/values-es/formatstrings.xml:3: Error: Inconsistent formatting types for argument #1 in format string hello ('%1$d'): Found both 's' and 'd' (in values/formatstrings.xml) [StringFormatMatches]\n" +
- " <string name=\"hello\">%1$d</string>\n" +
- " ~~~~\n" +
- " res/values/formatstrings.xml:3: Conflicting argument type here\n" +
- "res/values-es/formatstrings.xml:4: Warning: Inconsistent number of arguments in formatting string hello2; found both 2 and 3 [StringFormatCount]\n" +
- " <string name=\"hello2\">%3$d: %1$s, %2$s?</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values/formatstrings.xml:4: Conflicting number of arguments here\n" +
- "res/values/formatstrings.xml:5: Warning: Formatting string 'missing' is not referencing numbered arguments [1, 2] [StringFormatCount]\n" +
- " <string name=\"missing\">Hello %3$s World</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "9 errors, 2 warnings\n",
-
- lintProject(
- "res/values/formatstrings.xml",
- "res/values-es/formatstrings.xml",
- // Java files must be renamed in source tree
- "src/test/pkg/StringFormatActivity.java.txt=>src/test/pkg/StringFormatActivity.java"
- ));
- }
-
- public void testArgCount() {
- assertEquals(0, StringFormatDetector.getFormatArgumentCount(
- "%n%% ", null));
- assertEquals(1, StringFormatDetector.getFormatArgumentCount(
- "%n%% %s", null));
- assertEquals(3, StringFormatDetector.getFormatArgumentCount(
- "First: %1$s, Second %2$s, Third %3$s", null));
- assertEquals(11, StringFormatDetector.getFormatArgumentCount(
- "Skipping stuff: %11$s", null));
- assertEquals(1, StringFormatDetector.getFormatArgumentCount(
- "First: %1$s, Skip \\%2$s", null));
- assertEquals(1, StringFormatDetector.getFormatArgumentCount(
- "First: %s, Skip \\%s", null));
-
- Set<Integer> indices = new HashSet<Integer>();
- assertEquals(11, StringFormatDetector.getFormatArgumentCount(
- "Skipping stuff: %2$d %11$s", indices));
- assertEquals(2, indices.size());
- assertTrue(indices.contains(2));
- assertTrue(indices.contains(11));
- }
-
- public void testArgType() {
- assertEquals("s", StringFormatDetector.getFormatArgumentType(
- "First: %n%% %1$s, Second %2$s, Third %3$s", 1));
- assertEquals("s", StringFormatDetector.getFormatArgumentType(
- "First: %1$s, Second %2$s, Third %3$s", 1));
- assertEquals("d", StringFormatDetector.getFormatArgumentType(
- "First: %1$s, Second %2$-5d, Third %3$s", 2));
- assertEquals("s", StringFormatDetector.getFormatArgumentType(
- "Skipping stuff: %11$s",11));
- assertEquals("d", StringFormatDetector.getFormatArgumentType(
- "First: %1$s, Skip \\%2$s, Value=%2$d", 2));
- }
-
- public void testWrongSyntax() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/formatstrings2.xml"
- ));
- }
-
- public void testDateStrings() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/formatstrings-version1.xml=>res/values-tl/donottranslate-cldr.xml",
- "res/values/formatstrings-version2.xml=>res/values/donottranslate-cldr.xml"
- ));
- }
-
- public void testUa() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/formatstrings-version1.xml=>res/values-tl/donottranslate-cldr.xml",
- "src/test/pkg/StringFormat2.java.txt=>src/test/pkg/StringFormat2.java"
- ));
- }
-
- public void testSuppressed() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/formatstrings_ignore.xml=>res/values/formatstrings.xml",
- "res/values-es/formatstrings_ignore.xml=>res/values-es/formatstrings.xml",
- "src/test/pkg/StringFormatActivity_ignore.java.txt=>src/test/pkg/StringFormatActivity.java"
- ));
- }
-
- public void testIssue27108() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject("res/values/formatstrings3.xml"));
- }
-
- public void testIssue39758() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/formatstrings4.xml",
- "src/test/pkg/StringFormatActivity2.java.txt=>src/test/pkg/StringFormatActivity2.java"));
- }
-
- public void testIssue42798() throws Exception {
- // http://code.google.com/p/android/issues/detail?id=42798
- // String playsCount = String.format(Locale.FRANCE, this.context.getString(R.string.gridview_views_count), article.playsCount);
- assertEquals(
- "src/test/pkg/StringFormat3.java:12: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
- " context.getString(R.string.gridview_views_count), article.playsCount);\n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormat3.java:16: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
- " context.getString(R.string.gridview_views_count), \"wrong\");\n" +
- " ~~~~~~~\n" +
- " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormat3.java:17: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
- " String s4 = String.format(context.getString(R.string.gridview_views_count), \"wrong\");\n" +
- " ~~~~~~~\n" +
- " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormat3.java:22: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
- " context.getString(R.string.gridview_views_count), \"string\");\n" +
- " ~~~~~~~~\n" +
- " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
- "res/values/formatstrings5.xml:3: Warning: Formatting %d followed by words (\"vues\"): This should probably be a plural rather than a string [PluralsCandidate]\n" +
- " <string name=\"gridview_views_count\">%d vues</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "4 errors, 1 warnings\n",
-
- lintProject(
- "res/values/formatstrings5.xml",
- "src/test/pkg/StringFormat3.java.txt=>src/test/pkg/StringFormat3.java"));
- }
-
- public void testIsLocaleSpecific() throws Exception {
- assertFalse(isLocaleSpecific(""));
- assertFalse(isLocaleSpecific("Hello World!"));
- assertFalse(isLocaleSpecific("%% %n"));
- assertFalse(isLocaleSpecific(" %%f"));
- assertFalse(isLocaleSpecific("%x %A %c %b %B %h %n %%"));
- assertTrue(isLocaleSpecific("%f"));
- assertTrue(isLocaleSpecific(" %1$f "));
- assertTrue(isLocaleSpecific(" %5$e "));
- assertTrue(isLocaleSpecific(" %E "));
- assertTrue(isLocaleSpecific(" %g "));
- assertTrue(isLocaleSpecific(" %1$tm %1$te,%1$tY "));
- }
-
- public void testGetStringAsParameter() throws Exception {
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "res/values/formatstrings6.xml",
- "src/test/pkg/StringFormat4.java.txt=>src/test/pkg/StringFormat3.java"));
- }
-
- public void testNotLocaleMethod() throws Exception {
- // https://code.google.com/p/android/issues/detail?id=53238
- assertEquals(""
- + "No warnings.",
-
- lintProject(
- "res/values/formatstrings7.xml",
- "src/test/pkg/StringFormat5.java.txt=>src/test/pkg/StringFormat5.java"));
- }
-
- public void testNewlineChar() throws Exception {
- // https://code.google.com/p/android/issues/detail?id=65692
- assertEquals(""
- + "src/test/pkg/StringFormat8.java:12: Error: Wrong argument count, format string amount_string requires 1 but format call supplies 0 [StringFormatMatches]\n"
- + " String amount4 = String.format(getResources().getString(R.string.amount_string)); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + " res/values/formatstrings8.xml:2: This definition requires 1 arguments\n"
- + "src/test/pkg/StringFormat8.java:13: Error: Wrong argument count, format string amount_string requires 1 but format call supplies 2 [StringFormatMatches]\n"
- + " String amount5 = getResources().getString(R.string.amount_string, amount, amount); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + " res/values/formatstrings8.xml:2: This definition requires 1 arguments\n"
- + "2 errors, 0 warnings\n",
-
- lintProject(
- "res/values/formatstrings8.xml",
- "src/test/pkg/StringFormat8.java.txt=>src/test/pkg/StringFormat8.java"));
- }
-
- public void testIncremental() throws Exception {
- assertEquals(
- "src/test/pkg/StringFormatActivity.java:13: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
- " String output1 = String.format(hello, target);\n" +
- " ~~~~~~\n" +
- " res/values-es/formatstrings.xml: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormatActivity.java:15: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " String output2 = String.format(hello2, target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:21: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
- " String output4 = String.format(score, true); // wrong\n" +
- " ~~~~\n" +
- " res/values/formatstrings.xml: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
- " String output4 = String.format(score, won); // wrong\n" +
- " ~~~\n" +
- " res/values/formatstrings.xml: Conflicting argument declaration here\n" +
- "src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:25: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " getResources().getString(hello2, target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:26: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
- " getResources().getString(R.string.hello2, target, \"How are you\");\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
- "src/test/pkg/StringFormatActivity.java:33: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
- " String output1 = String.format(hello, target);\n" +
- " ~~~~~~\n" +
- " res/values-es/formatstrings.xml: Conflicting argument declaration here\n" +
- "res/values/formatstrings.xml: Error: Inconsistent formatting types for argument #1 in format string hello ('%1$s'): Found both 'd' and 's' (in values-es/formatstrings.xml) [StringFormatMatches]\n" +
- " res/values-es/formatstrings.xml: Conflicting argument type here\n" +
- "res/values/formatstrings.xml: Warning: Inconsistent number of arguments in formatting string hello2; found both 3 and 2 [StringFormatCount]\n" +
- " res/values-es/formatstrings.xml: Conflicting number of arguments here\n" +
- "9 errors, 1 warnings\n",
-
- lintProjectIncrementally(
- "src/test/pkg/StringFormatActivity.java",
- "res/values/formatstrings.xml",
- "res/values-es/formatstrings.xml",
- // Java files must be renamed in source tree
- "src/test/pkg/StringFormatActivity.java.txt=>src/test/pkg/StringFormatActivity.java"
- ));
- }
-
- public void testNotStringFormat() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=67597
- assertEquals("No warnings.",
-
- lintProject(
- "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
- "res/values/shared_prefs_keys.xml",
- "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsTest6.java"));
- }
-
- public void testNotStringFormatIncrementally() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=67597
- assertEquals("No warnings.",
-
- lintProjectIncrementally(
- "src/test/pkg/SharedPrefsTest6.java",
-
- "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
- "res/values/shared_prefs_keys.xml",
- "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsTest6.java"));
- }
-
- public void testIncrementalNonMatch() throws Exception {
- // Regression test for scenario where the below source files would crash during
- // a string format check with
- // java.lang.IllegalStateException: No match found
- // at java.util.regex.Matcher.group(Matcher.java:468)
- // at com.android.tools.lint.checks.StringFormatDetector.checkStringFormatCall(StringFormatDetector.java:1028)
- // ...
- assertEquals("No warnings.",
-
- lintProjectIncrementally(
- "src/test/pkg/StringFormatActivity3.java",
- "res/values/formatstrings11.xml",
- "res/values/formatstrings11.xml=>res/values-de/formatstrings11de.xml",
- "src/test/pkg/StringFormatActivity3.java.txt=>src/test/pkg/StringFormatActivity3.java"));
- }
-
- public void testXliff() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/formatstrings9.xml",
- "src/test/pkg/StringFormat9.java.txt=>src/test/pkg/StringFormat9.java"
- ));
- }
-
- public void testXliffIncremental() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProjectIncrementally(
- "src/test/pkg/StringFormat9.java",
- "res/values/formatstrings9.xml",
- "src/test/pkg/StringFormat9.java.txt=>src/test/pkg/StringFormat9.java"
- ));
- }
-
- public void testBigDecimal() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=69527
- assertEquals("No warnings.",
-
- lintProject(
- "res/values/formatstrings10.xml",
- "src/test/pkg/StringFormat10.java.txt=>src/test/pkg/StringFormat10.java"
- ));
-
- }
-
- public void testWrapperClasses() throws Exception {
- assertEquals("No warnings.",
-
- lintProject(
- "res/values/formatstrings10.xml",
- "src/test/pkg/StringFormat11.java.txt=>src/test/pkg/StringFormat11.java"
- ));
- }
-
- public void testPluralsCandidates() throws Exception {
- assertEquals(""
- + "res/values/plurals_candidates.xml:4: Warning: Formatting %d followed by words (\"times\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
- + " <string name=\"lockscreen_too_many_failed_attempts_dialog_message1\">\n"
- + " ^\n"
- + "res/values/plurals_candidates.xml:10: Warning: Formatting %d followed by words (\"times\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
- + " <string name=\"lockscreen_too_many_failed_attempts_dialog_message2\">\n"
- + " ^\n"
- + "res/values/plurals_candidates.xml:14: Warning: Formatting %d followed by words (\"moves\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
- + " <string name=\"win_dialog\">You won in %1$s and %2$d moves!</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values/plurals_candidates.xml:15: Warning: Formatting %d followed by words (\"times\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
- + " <string name=\"countdown_complete_sub\">Timer was paused %d times</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values/plurals_candidates.xml:16: Warning: Formatting %d followed by words (\"satellites\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
- + " <string name=\"service_gpsstatus\">Logging: %s (%s with %d satellites)</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values/plurals_candidates.xml:17: Warning: Formatting %d followed by words (\"seconds\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
- + " <string name=\"sync_log_clocks_unsynchronized\">The clock on your device is incorrect by %1$d seconds%2$s;</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values/plurals_candidates.xml:18: Warning: Formatting %d followed by words (\"tasks\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
- + " <string name=\"EPr_manage_purge_deleted_status\">Purged %d tasks!</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 7 warnings\n",
-
- lintProject(
- "res/values/plurals_candidates.xml",
- // Should not flag on anything but English strings
- "res/values/plurals_candidates.xml=>res/values-de/plurals_candidates.xml"
-
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
deleted file mode 100644
index 518566a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
+++ /dev/null
@@ -1,1263 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
-
-import com.android.tools.lint.ExternalAnnotationRepository;
-import com.android.tools.lint.ExternalAnnotationRepositoryTest;
-import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
-import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("ClassNameDiffersFromFileName") // For embedded unit tests
-public class SupportAnnotationDetectorTest extends AbstractCheckTest {
- private static final boolean SDK_ANNOTATIONS_AVAILABLE =
- new SupportAnnotationDetectorTest().createClient().findResource(
- ExternalAnnotationRepository.SDK_ANNOTATIONS_PATH) != null;
-
- @Override
- protected Detector getDetector() {
- return new SupportAnnotationDetector();
- }
-
- public void testRange() throws Exception {
- assertEquals(""
- + "src/test/pkg/RangeTest.java:32: Error: Expected length 5 (was 4) [Range]\n"
- + " printExact(\"1234\"); // ERROR\n"
- + " ~~~~~~\n"
- + "src/test/pkg/RangeTest.java:34: Error: Expected length 5 (was 6) [Range]\n"
- + " printExact(\"123456\"); // ERROR\n"
- + " ~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:36: Error: Expected length ≥ 5 (was 4) [Range]\n"
- + " printMin(\"1234\"); // ERROR\n"
- + " ~~~~~~\n"
- + "src/test/pkg/RangeTest.java:43: Error: Expected length ≤ 8 (was 9) [Range]\n"
- + " printMax(\"123456789\"); // ERROR\n"
- + " ~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:45: Error: Expected length ≥ 4 (was 3) [Range]\n"
- + " printRange(\"123\"); // ERROR\n"
- + " ~~~~~\n"
- + "src/test/pkg/RangeTest.java:49: Error: Expected length ≤ 6 (was 7) [Range]\n"
- + " printRange(\"1234567\"); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:53: Error: Expected size 5 (was 4) [Range]\n"
- + " printExact(new int[]{1, 2, 3, 4}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:55: Error: Expected size 5 (was 6) [Range]\n"
- + " printExact(new int[]{1, 2, 3, 4, 5, 6}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:57: Error: Expected size ≥ 5 (was 4) [Range]\n"
- + " printMin(new int[]{1, 2, 3, 4}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:65: Error: Expected size ≤ 8 (was 9) [Range]\n"
- + " printMax(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:67: Error: Expected size ≥ 4 (was 3) [Range]\n"
- + " printRange(new int[] {1,2,3}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:71: Error: Expected size ≤ 6 (was 7) [Range]\n"
- + " printRange(new int[] {1,2,3,4,5,6,7}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:74: Error: Expected size to be a multiple of 3 (was 4 and should be either 3 or 6) [Range]\n"
- + " printMultiple(new int[] {1,2,3,4}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:75: Error: Expected size to be a multiple of 3 (was 5 and should be either 3 or 6) [Range]\n"
- + " printMultiple(new int[] {1,2,3,4,5}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:77: Error: Expected size to be a multiple of 3 (was 7 and should be either 6 or 9) [Range]\n"
- + " printMultiple(new int[] {1,2,3,4,5,6,7}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:80: Error: Expected size ≥ 4 (was 3) [Range]\n"
- + " printMinMultiple(new int[]{1, 2, 3}); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RangeTest.java:84: Error: Value must be ≥ 4 (was 3) [Range]\n"
- + " printAtLeast(3); // ERROR\n"
- + " ~\n"
- + "src/test/pkg/RangeTest.java:91: Error: Value must be ≤ 7 (was 8) [Range]\n"
- + " printAtMost(8); // ERROR\n"
- + " ~\n"
- + "src/test/pkg/RangeTest.java:93: Error: Value must be ≥ 4 (was 3) [Range]\n"
- + " printBetween(3); // ERROR\n"
- + " ~\n"
- + "src/test/pkg/RangeTest.java:98: Error: Value must be ≤ 7 (was 8) [Range]\n"
- + " printBetween(8); // ERROR\n"
- + " ~\n"
- + "src/test/pkg/RangeTest.java:102: Error: Value must be ≥ 2.5 (was 2.49) [Range]\n"
- + " printAtLeastInclusive(2.49f); // ERROR\n"
- + " ~~~~~\n"
- + "src/test/pkg/RangeTest.java:106: Error: Value must be > 2.5 (was 2.49) [Range]\n"
- + " printAtLeastExclusive(2.49f); // ERROR\n"
- + " ~~~~~\n"
- + "src/test/pkg/RangeTest.java:107: Error: Value must be > 2.5 (was 2.5) [Range]\n"
- + " printAtLeastExclusive(2.5f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:113: Error: Value must be ≤ 7.0 (was 7.1) [Range]\n"
- + " printAtMostInclusive(7.1f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:117: Error: Value must be < 7.0 (was 7.0) [Range]\n"
- + " printAtMostExclusive(7.0f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:118: Error: Value must be < 7.0 (was 7.1) [Range]\n"
- + " printAtMostExclusive(7.1f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:120: Error: Value must be ≥ 2.5 (was 2.4) [Range]\n"
- + " printBetweenFromInclusiveToInclusive(2.4f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:124: Error: Value must be ≤ 5.0 (was 5.1) [Range]\n"
- + " printBetweenFromInclusiveToInclusive(5.1f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:126: Error: Value must be > 2.5 (was 2.4) [Range]\n"
- + " printBetweenFromExclusiveToInclusive(2.4f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:127: Error: Value must be > 2.5 (was 2.5) [Range]\n"
- + " printBetweenFromExclusiveToInclusive(2.5f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:129: Error: Value must be ≤ 5.0 (was 5.1) [Range]\n"
- + " printBetweenFromExclusiveToInclusive(5.1f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:131: Error: Value must be ≥ 2.5 (was 2.4) [Range]\n"
- + " printBetweenFromInclusiveToExclusive(2.4f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:135: Error: Value must be < 5.0 (was 5.0) [Range]\n"
- + " printBetweenFromInclusiveToExclusive(5.0f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:137: Error: Value must be > 2.5 (was 2.4) [Range]\n"
- + " printBetweenFromExclusiveToExclusive(2.4f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:138: Error: Value must be > 2.5 (was 2.5) [Range]\n"
- + " printBetweenFromExclusiveToExclusive(2.5f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:141: Error: Value must be < 5.0 (was 5.0) [Range]\n"
- + " printBetweenFromExclusiveToExclusive(5.0f); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/RangeTest.java:145: Error: Value must be ≥ 4 (was -7) [Range]\n"
- + " printBetween(-7); // ERROR\n"
- + " ~~\n"
- + "src/test/pkg/RangeTest.java:146: Error: Value must be > 2.5 (was -10.0) [Range]\n"
- + " printAtLeastExclusive(-10.0f); // ERROR\n"
- + " ~~~~~~\n"
- + "src/test/pkg/RangeTest.java:156: Error: Value must be ≥ -1 (was -2) [Range]\n"
- + " printIndirect(-2); // ERROR\n"
- + " ~~\n"
- + "src/test/pkg/RangeTest.java:157: Error: Value must be ≤ 42 (was 43) [Range]\n"
- + " printIndirect(43); // ERROR\n"
- + " ~~\n"
- + "src/test/pkg/RangeTest.java:158: Error: Expected length 5 (was 7) [Range]\n"
- + " printIndirectSize(\"1234567\"); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "41 errors, 0 warnings\n",
-
- lintProject("src/test/pkg/RangeTest.java.txt=>src/test/pkg/RangeTest.java",
- "src/android/support/annotation/Size.java.txt=>src/android/support/annotation/Size.java",
- "src/android/support/annotation/IntRange.java.txt=>src/android/support/annotation/IntRange.java",
- "src/android/support/annotation/FloatRange.java.txt=>src/android/support/annotation/FloatRange.java"
- ));
- }
-
- public void testTypeDef() throws Exception {
- assertEquals(""
- + "src/test/pkg/IntDefTest.java:31: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setStyle(0, 0); // ERROR\n"
- + " ~\n"
- + "src/test/pkg/IntDefTest.java:32: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setStyle(-1, 0); // ERROR\n"
- + " ~~\n"
- + "src/test/pkg/IntDefTest.java:33: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setStyle(UNRELATED, 0); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:34: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setStyle(IntDefTest.UNRELATED, 0); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:35: Error: Flag not allowed here [WrongConstant]\n"
- + " setStyle(IntDefTest.STYLE_NORMAL|STYLE_NO_FRAME, 0); // ERROR: Not a flag\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:36: Error: Flag not allowed here [WrongConstant]\n"
- + " setStyle(~STYLE_NO_FRAME, 0); // ERROR: Not a flag\n"
- + " ~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:55: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setFlags(\"\", UNRELATED); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:56: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setFlags(\"\", UNRELATED|STYLE_NO_TITLE); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:57: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setFlags(\"\", STYLE_NORMAL|STYLE_NO_TITLE|UNRELATED); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:58: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setFlags(\"\", 1); // ERROR\n"
- + " ~\n"
- + "src/test/pkg/IntDefTest.java:59: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setFlags(\"\", arg < 0 ? STYLE_NORMAL : UNRELATED); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:60: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setFlags(\"\", arg < 0 ? UNRELATED : STYLE_NORMAL); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:79: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
- + " setTitle(\"\", UNRELATED_TYPE); // ERROR\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:80: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
- + " setTitle(\"\", \"type2\"); // ERROR\n"
- + " ~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:87: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
- + " setTitle(\"\", type); // ERROR\n"
- + " ~~~~\n"
- + "src/test/pkg/IntDefTest.java:92: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
- + " setFlags(\"\", flag); // ERROR\n"
- + " ~~~~\n"
- + (SDK_ANNOTATIONS_AVAILABLE ?
- "src/test/pkg/IntDefTest.java:99: Error: Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE [WrongConstant]\n"
- + " view.setLayoutDirection(View.TEXT_DIRECTION_LTR); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:100: Error: Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE [WrongConstant]\n"
- + " view.setLayoutDirection(0); // ERROR\n"
- + " ~\n"
- + "src/test/pkg/IntDefTest.java:101: Error: Flag not allowed here [WrongConstant]\n"
- + " view.setLayoutDirection(View.LAYOUT_DIRECTION_LTR|View.LAYOUT_DIRECTION_RTL); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/IntDefTest.java:102: Error: Must be one of: Context.POWER_SERVICE, Context.WINDOW_SERVICE, Context.LAYOUT_INFLATER_SERVICE, Context.ACCOUNT_SERVICE, Context.ACTIVITY_SERVICE, Context.ALARM_SERVICE, Context.NOTIFICATION_SERVICE, Context.ACCESSIBILITY_SERVICE, Context.CAPTIONING_SERVICE, Context.KEYGUARD_SERVICE, Context.LOCATION_SERVICE, Context.SEARCH_SERVICE, Context.SENSOR_SERVICE, Context.STORAGE_SERVICE, Context.WALLPAPER_SERVICE, Context.VIBRATOR_SERV [...]
- + " context.getSystemService(TYPE_1); // ERROR\n"
- + " ~~~~~~\n"
- + "20 errors, 0 warnings\n" :
- "16 errors, 0 warnings\n"),
-
- lintProject("src/test/pkg/IntDefTest.java.txt=>src/test/pkg/IntDefTest.java",
- "src/android/support/annotation/IntDef.java.txt=>src/android/support/annotation/IntDef.java",
- "src/android/support/annotation/StringDef.java.txt=>src/android/support/annotation/StringDef.java"
- ));
- }
-
- public void testColorInt() throws Exception {
- // Needs updated annotations!
- assertEquals((SDK_ANNOTATIONS_AVAILABLE ? ""
- + "src/test/pkg/WrongColor.java:9: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
- + " paint2.setColor(R.color.blue);\n"
- + " ~~~~~~~~~~~~\n"
- + "src/test/pkg/WrongColor.java:11: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.red) [ResourceAsColor]\n"
- + " textView.setTextColor(R.color.red);\n"
- + " ~~~~~~~~~~~\n"
- + "src/test/pkg/WrongColor.java:12: Error: Should pass resolved color instead of resource id here: getResources().getColor(android.R.color.black) [ResourceAsColor]\n"
- + " textView.setTextColor(android.R.color.black);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/WrongColor.java:13: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
- + " textView.setTextColor(foo > 0 ? R.color.green : R.color.blue);\n"
- + " ~~~~~~~~~~~~\n"
- + "src/test/pkg/WrongColor.java:13: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.green) [ResourceAsColor]\n"
- + " textView.setTextColor(foo > 0 ? R.color.green : R.color.blue);\n"
- + " ~~~~~~~~~~~~~\n" : "")
- + "src/test/pkg/WrongColor.java:21: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
- + " foo2(R.color.blue);\n"
- + " ~~~~~~~~~~~~\n"
- + "src/test/pkg/WrongColor.java:20: Error: Expected resource of type color [ResourceType]\n"
- + " foo1(0xffff0000);\n"
- + " ~~~~~~~~~~\n"
- + (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "2 errors, 0 warnings\n"),
-
- lintProject(
- copy("src/test/pkg/WrongColor.java.txt", "src/test/pkg/WrongColor.java"),
- copy("src/android/support/annotation/ColorInt.java.txt", "src/android/support/annotation/ColorInt.java"),
- mColorResAnnotation
- ));
- }
-
- public void testColorInt2() throws Exception {
- assertEquals(""
- + "src/test/pkg/ColorTest.java:23: Error: Should pass resolved color instead of resource id here: getResources().getColor(actualColor) [ResourceAsColor]\n"
- + " setColor2(actualColor); // ERROR\n"
- + " ~~~~~~~~~~~\n"
- + "src/test/pkg/ColorTest.java:24: Error: Should pass resolved color instead of resource id here: getResources().getColor(getColor2()) [ResourceAsColor]\n"
- + " setColor2(getColor2()); // ERROR\n"
- + " ~~~~~~~~~~~\n"
- + "src/test/pkg/ColorTest.java:17: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
- + " setColor1(actualColor); // ERROR\n"
- + " ~~~~~~~~~~~\n"
- + "src/test/pkg/ColorTest.java:18: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
- + " setColor1(getColor1()); // ERROR\n"
- + " ~~~~~~~~~~~\n"
- + "4 errors, 0 warnings\n",
-
- lintProject(
- java("src/test/pkg/ColorTest.java", ""
- + "package test.pkg;\n"
- + "import android.content.Context;\n"
- + "import android.content.res.Resources;\n"
- + "import android.support.annotation.ColorInt;\n"
- + "import android.support.annotation.ColorRes;\n"
- + "\n"
- + "public abstract class ColorTest {\n"
- + " @ColorInt\n"
- + " public abstract int getColor1();\n"
- + " public abstract void setColor1(@ColorRes int color);\n"
- + " @ColorRes\n"
- + " public abstract int getColor2();\n"
- + " public abstract void setColor2(@ColorInt int color);\n"
- + "\n"
- + " public void test1(Context context) {\n"
- + " int actualColor = getColor1();\n"
- + " setColor1(actualColor); // ERROR\n"
- + " setColor1(getColor1()); // ERROR\n"
- + " setColor1(getColor2()); // OK\n"
- + " }\n"
- + " public void test2(Context context) {\n"
- + " int actualColor = getColor2();\n"
- + " setColor2(actualColor); // ERROR\n"
- + " setColor2(getColor2()); // ERROR\n"
- + " setColor2(getColor1()); // OK\n"
- + " }\n"
- + "}\n"),
- mColorResAnnotation,
- mColorIntAnnotation
- ));
- }
-
- public void testColorInt3() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=176321
-
- if (!SDK_ANNOTATIONS_AVAILABLE) {
- return;
- }
- assertEquals(""
- + "src/test/pkg/ColorTest.java:11: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
- + " setColor(actualColor);\n"
- + " ~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- java("src/test/pkg/ColorTest.java", ""
- + "package test.pkg;\n"
- + "import android.content.Context;\n"
- + "import android.content.res.Resources;\n"
- + "import android.support.annotation.ColorRes;\n"
- + "\n"
- + "public abstract class ColorTest {\n"
- + " public abstract void setColor(@ColorRes int color);\n"
- + "\n"
- + " public void test(Context context, @ColorRes int id) {\n"
- + " int actualColor = context.getResources().getColor(id, null);\n"
- + " setColor(actualColor);\n"
- + " }\n"
- + "}\n"),
- mColorResAnnotation
- ));
- }
- public void testResourceType() throws Exception {
- assertEquals((SDK_ANNOTATIONS_AVAILABLE ? ""
- + "src/p1/p2/Flow.java:13: Error: Expected resource of type drawable [ResourceType]\n"
- + " resources.getDrawable(10); // ERROR\n"
- + " ~~\n"
- + "src/p1/p2/Flow.java:18: Error: Expected resource of type drawable [ResourceType]\n"
- + " resources.getDrawable(R.string.my_string); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n" : "")
- + "src/p1/p2/Flow.java:22: Error: Expected resource of type drawable [ResourceType]\n"
- + " myMethod(R.string.my_string, null); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/p1/p2/Flow.java:26: Error: Expected resource of type drawable [ResourceType]\n"
- + " resources.getDrawable(R.string.my_string); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "src/p1/p2/Flow.java:32: Error: Expected resource identifier (R.type.name) [ResourceType]\n"
- + " myAnyResMethod(50); // ERROR\n"
- + " ~~\n"
- + (SDK_ANNOTATIONS_AVAILABLE ? "src/p1/p2/Flow.java:60: Error: Expected resource of type drawable [ResourceType]\n"
- + " resources.getDrawable(MimeTypes.getAnnotatedString()); // Error\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" : "")
- + "src/p1/p2/Flow.java:68: Error: Expected resource of type drawable [ResourceType]\n"
- + " myMethod(z, null); // ERROR\n"
- + " ~\n"
- + (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "4 errors, 0 warnings\n"),
-
- lintProject(
- copy("src/p1/p2/Flow.java.txt", "src/p1/p2/Flow.java"),
- copy("src/android/support/annotation/DrawableRes.java.txt", "src/android/support/annotation/DrawableRes.java"),
- mStringResAnnotation,
- mStyleResAnnotation,
- mAnyResAnnotation
- ));
- }
-
- public void testTypes2() throws Exception {
- assertEquals(""
- + "src/test/pkg/ActivityType.java:5: Error: Expected resource of type drawable [ResourceType]\n"
- + " SKI(1),\n"
- + " ~\n"
- + "src/test/pkg/ActivityType.java:6: Error: Expected resource of type drawable [ResourceType]\n"
- + " SNOWBOARD(2);\n"
- + " ~\n"
- + "2 errors, 0 warnings\n",
-
- lintProject(
- java("src/test/pkg/ActivityType.java", ""
- + "import android.support.annotation.DrawableRes;\n"
- + "\n"
- + "enum ActivityType {\n"
- + "\n"
- + " SKI(1),\n"
- + " SNOWBOARD(2);\n"
- + "\n"
- + " private final int mIconResId;\n"
- + "\n"
- + " ActivityType(@DrawableRes int iconResId) {\n"
- + " mIconResId = iconResId;\n"
- + " }\n"
- + "}"),
- copy("src/android/support/annotation/DrawableRes.java.txt",
- "src/android/support/annotation/DrawableRes.java")));
- }
-
- @SuppressWarnings({"MethodMayBeStatic", "ResultOfObjectAllocationIgnored"})
- public void testConstructor() throws Exception {
- assertEquals(""
- + "src/test/pkg/ConstructorTest.java:14: Error: Expected resource of type drawable [ResourceType]\n"
- + " new ConstructorTest(1, 3);\n"
- + " ~\n"
- + "src/test/pkg/ConstructorTest.java:14: Error: Value must be ≥ 5 (was 3) [Range]\n"
- + " new ConstructorTest(1, 3);\n"
- + " ~\n"
- + "src/test/pkg/ConstructorTest.java:19: Error: Method test.pkg.ConstructorTest must be called from the UI thread, currently inferred thread is worker thread [WrongThread]\n"
- + " new ConstructorTest(res, range);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "3 errors, 0 warnings\n",
-
- lintProject(
- java("src/test/pkg/ConstructorTest.java", ""
- + "package test.pkg;\n"
- + "\n"
- + "import android.support.annotation.DrawableRes;\n"
- + "import android.support.annotation.IntRange;\n"
- + "import android.support.annotation.UiThread;\n"
- + "import android.support.annotation.WorkerThread;\n"
- + "\n"
- + "public class ConstructorTest {\n"
- + " @UiThread\n"
- + " ConstructorTest(@DrawableRes int iconResId, @IntRange(from = 5) int start) {\n"
- + " }\n"
- + "\n"
- + " public void testParameters() {\n"
- + " new ConstructorTest(1, 3);\n"
- + " }\n"
- + "\n"
- + " @WorkerThread\n"
- + " public void testMethod(int res, int range) {\n"
- + " new ConstructorTest(res, range);\n"
- + " }\n"
- + "}\n"),
- mWorkerThreadPermission,
- mUiThreadPermission,
- copy("src/android/support/annotation/DrawableRes.java.txt",
- "src/android/support/annotation/DrawableRes.java"),
- copy("src/android/support/annotation/IntRange.java.txt",
- "src/android/support/annotation/IntRange.java")
- ));
- }
-
- public void testColorAsDrawable() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject("src/p1/p2/ColorAsDrawable.java.txt=>src/p1/p2/ColorAsDrawable.java"));
- }
-
- public void testCheckResult() throws Exception {
- if (!SDK_ANNOTATIONS_AVAILABLE) {
- // Currently only tests @CheckResult on SDK annotations
- return;
- }
- assertEquals(""
- + "src/test/pkg/CheckPermissions.java:22: Warning: The result of extractAlpha is not used [CheckResult]\n"
- + " bitmap.extractAlpha(); // WARNING\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/CheckPermissions.java:10: Warning: The result of checkCallingOrSelfPermission is not used; did you mean to call #enforceCallingOrSelfPermission(String,String)? [UseCheckPermission]\n"
- + " context.checkCallingOrSelfPermission(Manifest.permission.INTERNET); // WRONG\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/CheckPermissions.java:11: Warning: The result of checkPermission is not used; did you mean to call #enforcePermission(String,int,int,String)? [UseCheckPermission]\n"
- + " context.checkPermission(Manifest.permission.INTERNET, 1, 1);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "0 errors, 3 warnings\n",
-
- lintProject("src/test/pkg/CheckPermissions.java.txt=>src/test/pkg/CheckPermissions.java"));
- }
-
- private final TestFile mPermissionTest = java("src/test/pkg/PermissionTest.java", ""
- + "package test.pkg;\n"
- + "\n"
- + "import android.location.LocationManager;\n"
- + "\n"
- + "public class PermissionTest {\n"
- + " public static void test(LocationManager locationManager, String provider) {\n"
- + " LocationManager.Location location = locationManager.myMethod(provider);\n"
- + " }\n"
- + "}\n");
-
- private final TestFile mLocationManagerStub = java("src/android/location/LocationManager.java", ""
- + "package android.location;\n"
- + "\n"
- + "import android.support.annotation.RequiresPermission;\n"
- + "\n"
- + "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
- + "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
- + "\n"
- + "@SuppressWarnings(\"UnusedDeclaration\")\n"
- + "public abstract class LocationManager {\n"
- + " @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})\n"
- + " public abstract Location myMethod(String provider);\n"
- + " public static class Location {\n"
- + " }\n"
- + "}\n");
-
- private final TestFile mComplexLocationManagerStub = java("src/android/location/LocationManager.java", ""
- + "package android.location;\n"
- + "\n"
- + "import android.support.annotation.RequiresPermission;\n"
- + "\n"
- + "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
- + "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
- + "import static android.Manifest.permission.BLUETOOTH;\n"
- + "import static android.Manifest.permission.READ_SMS;\n"
- + "\n"
- + "@SuppressWarnings(\"UnusedDeclaration\")\n"
- + "public abstract class LocationManager {\n"
- + " @RequiresPermission(\"(\" + ACCESS_FINE_LOCATION + \"|| \" + ACCESS_COARSE_LOCATION + \") && (\" + BLUETOOTH + \" ^ \" + READ_SMS + \")\")\n"
- + " public abstract Location myMethod(String provider);\n"
- + " public static class Location {\n"
- + " }\n"
- + "}\n");
-
- private final TestFile mRequirePermissionAnnotation = java("src/android/support/annotation/RequiresPermission.java", ""
- + "/*\n"
- + " * Copyright (C) 2015 The Android Open Source Project\n"
- + " *\n"
- + " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
- + " * you may not use this file except in compliance with the License.\n"
- + " * You may obtain a copy of the License at\n"
- + " *\n"
- + " * http://www.apache.org/licenses/LICENSE-2.0\n"
- + " *\n"
- + " * Unless required by applicable law or agreed to in writing, software\n"
- + " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
- + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
- + " * See the License for the specific language governing permissions and\n"
- + " * limitations under the License.\n"
- + " */\n"
- + "package android.support.annotation;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.Target;\n"
- + "\n"
- + "import static java.lang.annotation.ElementType.*;\n"
- + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
- + "@Retention(CLASS)\n"
- + "@Target({METHOD,CONSTRUCTOR,FIELD,PARAMETER,ANNOTATION_TYPE})\n"
- + "public @interface RequiresPermission {\n"
- + " String value() default \"\";\n"
- + " String[] allOf() default {};\n"
- + " String[] anyOf() default {};\n"
- + " boolean conditional() default false;\n"
- + " String notes() default \"\";\n"
- + " @Target({FIELD,METHOD,PARAMETER})\n"
- + " @interface Read {\n"
- + " RequiresPermission value();\n"
- + " }\n"
- + " @Target({FIELD,METHOD,PARAMETER})\n"
- + " @interface Write {\n"
- + " RequiresPermission value();\n"
- + " }\n"
- + "}");
-
- private final TestFile mUiThreadPermission = java("src/android/support/annotation/UiThread.java", ""
- + "package android.support.annotation;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.Target;\n"
- + "\n"
- + "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
- + "import static java.lang.annotation.ElementType.METHOD;\n"
- + "import static java.lang.annotation.ElementType.TYPE;\n"
- + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
- + "\n"
- + "@Retention(CLASS)\n"
- + "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
- + "public @interface UiThread {\n"
- + "}\n");
-
- private final TestFile mMainThreadPermission = java("src/android/support/annotation/MainThread.java", ""
- + "package android.support.annotation;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.Target;\n"
- + "\n"
- + "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
- + "import static java.lang.annotation.ElementType.METHOD;\n"
- + "import static java.lang.annotation.ElementType.TYPE;\n"
- + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
- + "\n"
- + "@Retention(CLASS)\n"
- + "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
- + "public @interface MainThread {\n"
- + "}\n");
-
- private final TestFile mWorkerThreadPermission = java("src/android/support/annotation/WorkerThread.java", ""
- + "package android.support.annotation;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.Target;\n"
- + "\n"
- + "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
- + "import static java.lang.annotation.ElementType.METHOD;\n"
- + "import static java.lang.annotation.ElementType.TYPE;\n"
- + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
- + "\n"
- + "@Retention(CLASS)\n"
- + "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
- + "public @interface WorkerThread {\n"
- + "}\n");
-
- private TestFile createResAnnotation(String prefix) {
- return java("src/android/support/annotation/" + prefix + "Res.java", ""
- + "package android.support.annotation;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.Target;\n"
- + "\n"
- + "import static java.lang.annotation.ElementType.*;\n"
- + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
- + "\n"
- + "@Retention(CLASS)\n"
- + "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
- + "public @interface " + prefix + "Res {\n"
- + "}\n");
- }
-
- private final TestFile mColorResAnnotation = createResAnnotation("Color");
- private final TestFile mStringResAnnotation = createResAnnotation("String");
- private final TestFile mStyleResAnnotation = createResAnnotation("Style");
- private final TestFile mAnyResAnnotation = createResAnnotation("Any");
-
- private final TestFile mColorIntAnnotation = java("src/android/support/annotation/ColorInt.java", ""
- + "package android.support.annotation;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.Target;\n"
- + "\n"
- + "import static java.lang.annotation.ElementType.*;\n"
- + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
- + "\n"
- + "@Retention(CLASS)\n"
- + "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
- + "public @interface ColorInt {\n"
- + "}\n");
-
- private TestFile getManifestWithPermissions(int targetSdk, String... permissions) {
- return getManifestWithPermissions(1, targetSdk, permissions);
- }
-
- private TestFile getManifestWithPermissions(int minSdk, int targetSdk, String... permissions) {
- StringBuilder permissionBlock = new StringBuilder();
- for (String permission : permissions) {
- permissionBlock.append(" <uses-permission android:name=\"").append(permission)
- .append("\" />\n");
- }
- return xml("AndroidManifest.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " package=\"foo.bar2\"\n"
- + " android:versionCode=\"1\"\n"
- + " android:versionName=\"1.0\" >\n"
- + "\n"
- + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\""
- + targetSdk + "\" />\n"
- + "\n"
- + permissionBlock.toString()
- + "\n"
- + " <application\n"
- + " android:icon=\"@drawable/ic_launcher\"\n"
- + " android:label=\"@string/app_name\" >\n"
- + " </application>\n"
- + "\n"
- + "</manifest>");
- }
-
- private TestFile mRevokeTest = java("src/test/pkg/RevokeTest.java", ""
- + "package test.pkg;\n"
- + "\n"
- + "import android.content.Context;\n"
- + "import android.content.pm.PackageManager;\n"
- + "import android.location.LocationManager;\n"
- + "\n"
- + "import java.security.AccessControlException;\n"
- + "\n"
- + "public class RevokeTest {\n"
- + " public static void test1(LocationManager locationManager, String provider) {\n"
- + " try {\n"
- + " // Ok: Security exception caught in one of the branches\n"
- + " locationManager.myMethod(provider); // OK\n"
- + " } catch (IllegalArgumentException ignored) {\n"
- + " } catch (SecurityException ignored) {\n"
- + " }\n"
- + "\n"
- + " try {\n"
- + " // Ok: Security exception super class caught in one of the branches\n"
- + " locationManager.myMethod(provider); // OK\n"
- + " } catch (RuntimeException e) { // includes Security Exception\n"
- + " }\n"
- + "\n"
- + " try {\n"
- + " // Ok: Caught in outer statement\n"
- + " try {\n"
- + " locationManager.myMethod(provider); // OK\n"
- + " } catch (IllegalArgumentException e) {\n"
- + " // inner\n"
- + " }\n"
- + " } catch (SecurityException ignored) {\n"
- + " }\n"
- + "\n"
- + " try {\n"
- + " // Ok: Security exception super class caught in one of the branches\n"
- + " locationManager.myMethod(provider); // OK\n"
- + " } catch (Exception e) { // includes Security Exception\n"
- + " }\n"
- + "\n"
- + " // NOT OK: Catching security exception subclass (except for dedicated ones?)\n"
- + "\n"
- + " try {\n"
- + " // Error: catching security exception, but not all of them\n"
- + " locationManager.myMethod(provider); // ERROR\n"
- + " } catch (AccessControlException e) { // security exception but specific one\n"
- + " }\n"
- + " }\n"
- + "\n"
- + " public static void test2(LocationManager locationManager, String provider) {\n"
- + " locationManager.myMethod(provider); // ERROR: not caught\n"
- + " }\n"
- + "\n"
- + " public static void test3(LocationManager locationManager, String provider)\n"
- + " throws IllegalArgumentException {\n"
- + " locationManager.myMethod(provider); // ERROR: not caught by right type\n"
- + " }\n"
- + "\n"
- + " public static void test4(LocationManager locationManager, String provider)\n"
- + " throws AccessControlException { // Security exception but specific one\n"
- + " locationManager.myMethod(provider); // ERROR\n"
- + " }\n"
- + "\n"
- + " public static void test5(LocationManager locationManager, String provider)\n"
- + " throws SecurityException {\n"
- + " locationManager.myMethod(provider); // OK\n"
- + " }\n"
- + "\n"
- + " public static void test6(LocationManager locationManager, String provider)\n"
- + " throws Exception { // includes Security Exception\n"
- + " locationManager.myMethod(provider); // OK\n"
- + " }\n"
- + "\n"
- + " public static void test7(LocationManager locationManager, String provider, Context context)\n"
- + " throws IllegalArgumentException {\n"
- + " if (context.getPackageManager().checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, context.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n"
- + " return;\n"
- + " }\n"
- + " locationManager.myMethod(provider); // OK: permission checked\n"
- + " }\n"
- + "\n"
- + "}\n");
-
- public void testMissingPermissions() throws Exception {
- assertEquals(""
- + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION [MissingPermission]\n"
- + " LocationManager.Location location = locationManager.myMethod(provider);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- getManifestWithPermissions(14),
- mPermissionTest,
- mLocationManagerStub,
- mRequirePermissionAnnotation));
- }
-
- public void testHasPermission() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- getManifestWithPermissions(14, "android.permission.ACCESS_FINE_LOCATION"),
- mPermissionTest,
- mLocationManagerStub,
- mRequirePermissionAnnotation));
- }
-
- public void testRevokePermissions() throws Exception {
- assertEquals(""
- + "src/test/pkg/RevokeTest.java:44: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
- + " locationManager.myMethod(provider); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RevokeTest.java:50: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
- + " locationManager.myMethod(provider); // ERROR: not caught\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RevokeTest.java:55: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
- + " locationManager.myMethod(provider); // ERROR: not caught by right type\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/RevokeTest.java:60: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or handle a potential SecurityException [MissingPermission]\n"
- + " locationManager.myMethod(provider); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "4 errors, 0 warnings\n",
- lintProject(
- getManifestWithPermissions(23, "android.permission.ACCESS_FINE_LOCATION"),
- mLocationManagerStub,
- mRequirePermissionAnnotation,
- mRevokeTest
- ));
- }
-
- public void testImpliedPermissions() throws Exception {
- // Regression test for
- // https://code.google.com/p/android/issues/detail?id=177381
- assertEquals(""
- + "src/test/pkg/PermissionTest2.java:11: Error: Missing permissions required by PermissionTest2.method1: my.permission.PERM2 [MissingPermission]\n"
- + " method1(); // ERROR\n"
- + " ~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- getManifestWithPermissions(14, 14, "android.permission.ACCESS_FINE_LOCATION"),
- java("src/test/pkg/PermissionTest2.java", ""
- + "package test.pkg;\n"
- + "import android.support.annotation.RequiresPermission;\n"
- + "\n"
- + "public class PermissionTest2 {\n"
- + " @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n"
- + " public void method1() {\n"
- + " }\n"
- + "\n"
- + " @RequiresPermission(\"my.permission.PERM1\")\n"
- + " public void method2() {\n"
- + " method1(); // ERROR\n"
- + " }\n"
- + "\n"
- + " @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n"
- + " public void method3() {\n"
- + " // The above @RequiresPermission implies that we are holding these\n"
- + " // permissions here, so the call to method1() should not be flagged as\n"
- + " // missing a permission!\n"
- + " method1(); // OK\n"
- + " }\n"
- + "}\n"),
- mRequirePermissionAnnotation
- ));
- }
-
- public void testRevokePermissionsPre23() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- getManifestWithPermissions(14, "android.permission.ACCESS_FINE_LOCATION"),
- mLocationManagerStub,
- mRequirePermissionAnnotation,
- mRevokeTest
- ));
- }
-
- public void testComplexPermission1() throws Exception {
- assertEquals(""
- + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
- + " LocationManager.Location location = locationManager.myMethod(provider);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- getManifestWithPermissions(14,
- "android.permission.ACCESS_FINE_LOCATION"),
- mPermissionTest,
- mComplexLocationManagerStub,
- mRequirePermissionAnnotation));
- }
-
- public void testComplexPermission2() throws Exception {
- assertEquals("No warnings.",
- lintProject(
- getManifestWithPermissions(14,
- "android.permission.ACCESS_FINE_LOCATION",
- "android.permission.BLUETOOTH"),
- mPermissionTest,
- mComplexLocationManagerStub,
- mRequirePermissionAnnotation));
- }
-
- public void testComplexPermission3() throws Exception {
- assertEquals(""
- + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
- + " LocationManager.Location location = locationManager.myMethod(provider);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- getManifestWithPermissions(14,
- "android.permission.ACCESS_FINE_LOCATION",
- "android.permission.BLUETOOTH",
- "android.permission.READ_SMS"),
- mPermissionTest,
- mComplexLocationManagerStub,
- mRequirePermissionAnnotation));
- }
-
- public void testPermissionAnnotation() throws Exception {
- assertEquals(""
- + "src/test/pkg/LocationManager.java:24: Error: Missing permissions required by LocationManager.getLastKnownLocation: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION [MissingPermission]\n"
- + " Location location = manager.getLastKnownLocation(\"provider\");\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
- lintProject(
- java("src/test/pkg/LocationManager.java", ""
- + "package test.pkg;\n"
- + "\n"
- + "import android.support.annotation.RequiresPermission;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.RetentionPolicy;\n"
- + "\n"
- + "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
- + "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
- + "\n"
- + "@SuppressWarnings(\"UnusedDeclaration\")\n"
- + "public abstract class LocationManager {\n"
- + " @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " @interface AnyLocationPermission {\n"
- + " }\n"
- + "\n"
- + " @AnyLocationPermission\n"
- + " public abstract Location getLastKnownLocation(String provider);\n"
- + " public static class Location {\n"
- + " }\n"
- + " \n"
- + " public static void test(LocationManager manager) {\n"
- + " Location location = manager.getLastKnownLocation(\"provider\");\n"
- + " }\n"
- + "}\n"),
- mRequirePermissionAnnotation));
- }
-
- public void testThreading() throws Exception {
- assertEquals(""
- + "src/test/pkg/ThreadTest.java:15: Error: Method onPreExecute must be called from the main thread, currently inferred thread is worker thread [WrongThread]\n"
- + " onPreExecute(); // ERROR\n"
- + " ~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ThreadTest.java:16: Error: Method paint must be called from the UI thread, currently inferred thread is worker thread [WrongThread]\n"
- + " view.paint(); // ERROR\n"
- + " ~~~~~~~~~~~~\n"
- + "src/test/pkg/ThreadTest.java:22: Error: Method publishProgress must be called from the worker thread, currently inferred thread is main thread [WrongThread]\n"
- + " publishProgress(); // ERROR\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "3 errors, 0 warnings\n",
-
- lintProject(
- java("src/test/pkg/ThreadTest.java", ""
- + "package test.pkg;\n"
- + "\n"
- + "import android.support.annotation.MainThread;\n"
- + "import android.support.annotation.UiThread;\n"
- + "import android.support.annotation.WorkerThread;\n"
- + "\n"
- + "public class ThreadTest {\n"
- + " public static AsyncTask testTask() {\n"
- + "\n"
- + " return new AsyncTask() {\n"
- + " final CustomView view = new CustomView();\n"
- + "\n"
- + " @Override\n"
- + " protected void doInBackground(Object... params) {\n"
- + " onPreExecute(); // ERROR\n"
- + " view.paint(); // ERROR\n"
- + " publishProgress(); // OK\n"
- + " }\n"
- + "\n"
- + " @Override\n"
- + " protected void onPreExecute() {\n"
- + " publishProgress(); // ERROR\n"
- + " onProgressUpdate(); // OK\n"
- + " }\n"
- + " };\n"
- + " }\n"
- + "\n"
- + " @UiThread\n"
- + " public static class View {\n"
- + " public void paint() {\n"
- + " }\n"
- + " }\n"
- + "\n"
- + " public static class CustomView extends View {\n"
- + " @Override public void paint() {\n"
- + " }\n"
- + " }\n"
- + "\n"
- + " public abstract static class AsyncTask {\n"
- + " @WorkerThread\n"
- + " protected abstract void doInBackground(Object... params);\n"
- + "\n"
- + " @MainThread\n"
- + " protected void onPreExecute() {\n"
- + " }\n"
- + "\n"
- + " @MainThread\n"
- + " protected void onProgressUpdate(Object... values) {\n"
- + " }\n"
- + "\n"
- + " @WorkerThread\n"
- + " protected final void publishProgress(Object... values) {\n"
- + " }\n"
- + " }\n"
- + "}\n"),
- mUiThreadPermission,
- mMainThreadPermission,
- mWorkerThreadPermission));
- }
-
- public void testIntentPermission() throws Exception {
- if (SDK_ANNOTATIONS_AVAILABLE) {
- TestLintClient client = createClient();
- ExternalAnnotationRepository repository = ExternalAnnotationRepository.get(client);
- ResolvedMethod method = ExternalAnnotationRepositoryTest.createMethod(
- "android.content.Context", "void", "startActivity",
- "android.content.Intent");
- ResolvedAnnotation a = repository.getAnnotation(method, 0, PERMISSION_ANNOTATION);
- if (a == null) {
- // Running tests from outside the IDE (where it can't find the
- // bundled up to date annotations in tools/adt/idea/android/annotations)
- // and we have the annotations.zip file available in platform-tools,
- // but its contents are old (it's from Android M Preview 1, not including
- // the new intent-annotation data); skip this test for now.
- return;
- }
- }
-
- assertEquals(!SDK_ANNOTATIONS_AVAILABLE ? "" // Most of the intent/content provider checks are based on framework annotations
- + "src/test/pkg/ActionTest.java:86: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:87: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:88: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " myWriteResolverMethod(BOOKMARKS_URI);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "3 errors, 0 warnings\n" : ""
-
- + "src/test/pkg/ActionTest.java:36: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startActivity(intent);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:42: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startActivity(intent);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:43: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startActivity(intent, null);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:44: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startActivityForResult(intent, 0);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:45: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startActivityFromChild(activity, intent, 0);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:46: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startActivityIfNeeded(intent, 0);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:47: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startActivityFromFragment(null, intent, 0);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:48: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " activity.startNextMatchingActivity(intent);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:54: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " context.sendBroadcast(intent);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:55: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " context.sendBroadcast(intent, \"\");\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:56: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " context.sendBroadcastAsUser(intent, null);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:57: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " context.sendStickyBroadcast(intent);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:62: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " resolver.query(BOOKMARKS_URI, null, null, null, null);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:65: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " resolver.insert(BOOKMARKS_URI, null);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:66: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " resolver.delete(BOOKMARKS_URI, null, null);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:67: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " resolver.update(BOOKMARKS_URI, null, null, null);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:86: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
- + " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:87: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:88: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " myWriteResolverMethod(BOOKMARKS_URI);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "19 errors, 0 warnings\n",
-
- lintProject(
- getManifestWithPermissions(14, 23),
- java("src/test/pkg/ActionTest.java", ""
- + "package test.pkg;\n"
- + "\n"
- + "import android.Manifest;\n"
- + "import android.app.Activity;\n"
- + "import android.content.ContentResolver;\n"
- + "import android.content.Context;\n"
- + "import android.content.Intent;\n"
- + "import android.database.Cursor;\n"
- + "import android.net.Uri;\n"
- + "import android.support.annotation.RequiresPermission;\n"
- + "\n"
- //+ "import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;\n"
- //+ "import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;\n"
- + "\n"
- + "@SuppressWarnings({\"deprecation\", \"unused\"})\n"
- + "public class ActionTest {\n"
- + " public static final String READ_HISTORY_BOOKMARKS=\"com.android.browser.permission.READ_HISTORY_BOOKMARKS\";\n"
- + " public static final String WRITE_HISTORY_BOOKMARKS=\"com.android.browser.permission.WRITE_HISTORY_BOOKMARKS\";\n"
- + " @RequiresPermission(Manifest.permission.CALL_PHONE)\n"
- + " public static final String ACTION_CALL = \"android.intent.action.CALL\";\n"
- + "\n"
- + " @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))\n"
- + " @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))\n"
- + " public static final Uri BOOKMARKS_URI = Uri.parse(\"content://browser/bookmarks\");\n"
- + "\n"
- + " public static final Uri COMBINED_URI = Uri.withAppendedPath(BOOKMARKS_URI, \"bookmarks\");\n"
- + " \n"
- + " public static void activities1(Activity activity) {\n"
- + " Intent intent = new Intent(Intent.ACTION_CALL);\n"
- + " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
- + " // This one will only be flagged if we have framework metadata on Intent.ACTION_CALL\n"
- + " activity.startActivity(intent);\n"
- + " }\n"
- + "\n"
- + " public static void activities2(Activity activity) {\n"
- + " Intent intent = new Intent(ACTION_CALL);\n"
- + " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
- + " activity.startActivity(intent);\n"
- + " }\n"
- + " public static void activities3(Activity activity) {\n"
- + " Intent intent;\n"
- + " intent = new Intent(ACTION_CALL);\n"
- + " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
- + " activity.startActivity(intent);\n"
- + " activity.startActivity(intent, null);\n"
- + " activity.startActivityForResult(intent, 0);\n"
- + " activity.startActivityFromChild(activity, intent, 0);\n"
- + " activity.startActivityIfNeeded(intent, 0);\n"
- + " activity.startActivityFromFragment(null, intent, 0);\n"
- + " activity.startNextMatchingActivity(intent);\n"
- + " }\n"
- + "\n"
- + " public static void broadcasts(Context context) {\n"
- + " Intent intent;\n"
- + " intent = new Intent(ACTION_CALL);\n"
- + " context.sendBroadcast(intent);\n"
- + " context.sendBroadcast(intent, \"\");\n"
- + " context.sendBroadcastAsUser(intent, null);\n"
- + " context.sendStickyBroadcast(intent);\n"
- + " }\n"
- + "\n"
- + " public static void contentResolvers(Context context, ContentResolver resolver) {\n"
- + " // read\n"
- + " resolver.query(BOOKMARKS_URI, null, null, null, null);\n"
- + "\n"
- + " // write\n"
- + " resolver.insert(BOOKMARKS_URI, null);\n"
- + " resolver.delete(BOOKMARKS_URI, null, null);\n"
- + " resolver.update(BOOKMARKS_URI, null, null, null);\n"
- + "\n"
- + " // Framework (external) annotation\n"
- + "//REMOVED resolver.query(android.provider.Browser.BOOKMARKS_URI, null, null, null, null);\n"
- + "\n"
- + " // TODO: Look for more complex URI manipulations\n"
- + " }\n"
- + "\n"
- + " public static void myStartActivity(String s1, String s2, \n"
- + " @RequiresPermission Intent intent) {\n"
- + " }\n"
- + "\n"
- + " public static void myReadResolverMethod(String s1, @RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
- + " }\n"
- + "\n"
- + " public static void myWriteResolverMethod(@RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
- + " }\n"
- + " \n"
- + " public static void testCustomMethods() {\n"
- + " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
- + " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
- + " myWriteResolverMethod(BOOKMARKS_URI);\n"
- + " }\n"
- + "}\n"),
- mRequirePermissionAnnotation
- ));
- }
-
- public void testCombinedIntDefAndIntRange() throws Exception {
- assertEquals(""
- + "src/test/pkg/X.java:27: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG [WrongConstant]\n"
- + " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
- + " ~~~~~~~~~\n"
- + "src/test/pkg/X.java:28: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was -5) [WrongConstant]\n"
- + " setDuration(-5); // ERROR (not right int def or value\n"
- + " ~~\n"
- + "src/test/pkg/X.java:29: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was 8) [WrongConstant]\n"
- + " setDuration(8); // ERROR (not matching number range)\n"
- + " ~\n"
- + "3 errors, 0 warnings\n",
- lintProject(
- getManifestWithPermissions(14, 23),
- java("src/test/pkg/X.java", ""
- + "\n"
- + "package test.pkg;\n"
- + "\n"
- + "import android.support.annotation.IntDef;\n"
- + "import android.support.annotation.IntRange;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.RetentionPolicy;\n"
- + "\n"
- + "@SuppressWarnings({\"UnusedParameters\", \"unused\", \"SpellCheckingInspection\"})\n"
- + "public class X {\n"
- + "\n"
- + " public static final int UNRELATED = 500;\n"
- + "\n"
- + " @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n"
- + " @IntRange(from = 10)\n"
- + " @Retention(RetentionPolicy.SOURCE)\n"
- + " public @interface Duration {}\n"
- + "\n"
- + " public static final int LENGTH_INDEFINITE = -2;\n"
- + " public static final int LENGTH_SHORT = -1;\n"
- + " public static final int LENGTH_LONG = 0;\n"
- + " public void setDuration(@Duration int duration) {\n"
- + " }\n"
- + "\n"
- + " public void test() {\n"
- + " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
- + " setDuration(-5); // ERROR (not right int def or value\n"
- + " setDuration(8); // ERROR (not matching number range)\n"
- + " setDuration(8000); // OK (@IntRange applies)\n"
- + " setDuration(LENGTH_INDEFINITE); // OK (@IntDef)\n"
- + " setDuration(LENGTH_LONG); // OK (@IntDef)\n"
- + " setDuration(LENGTH_SHORT); // OK (@IntDef)\n"
- + " }\n"
- + "}\n"),
- copy("src/android/support/annotation/IntDef.java.txt", "src/android/support/annotation/IntDef.java"),
- copy("src/android/support/annotation/IntRange.java.txt", "src/android/support/annotation/IntRange.java")
- ));
- }
-
-
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java
deleted file mode 100644
index 07941b7..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class ToastDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new ToastDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/ToastTest.java:31: Warning: Toast created but not shown: did you forget to call show() ? [ShowToast]\n" +
- " Toast.makeText(context, \"foo\", Toast.LENGTH_LONG);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/ToastTest.java:32: Warning: Expected duration Toast.LENGTH_SHORT or Toast.LENGTH_LONG, a custom duration value is not supported [ShowToast]\n" +
- " Toast toast = Toast.makeText(context, R.string.app_name, 5000);\n" +
- " ~~~~\n" +
- "src/test/pkg/ToastTest.java:32: Warning: Toast created but not shown: did you forget to call show() ? [ShowToast]\n" +
- " Toast toast = Toast.makeText(context, R.string.app_name, 5000);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/test/pkg/ToastTest.java:38: Warning: Toast created but not shown: did you forget to call show() ? [ShowToast]\n" +
- " Toast.makeText(context, \"foo\", Toast.LENGTH_LONG);\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 4 warnings\n" +
- "",
-
- lintProject("src/test/pkg/ToastTest.java.txt=>src/test/pkg/ToastTest.java"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
deleted file mode 100644
index 4f53c7d..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ProductFlavor;
-import com.android.builder.model.ProductFlavorContainer;
-import com.android.builder.model.Variant;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Project;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
- at SuppressWarnings("javadoc")
-public class TranslationDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new TranslationDetector();
- }
-
- @Override
- protected boolean includeParentPath() {
- return true;
- }
-
- public void testTranslation() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(
- // Sample files from the Home app
- "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
- " <string name=\"show_all_apps\">All</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
- " <string name=\"menu_wallpaper\">Wallpaper</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-DE\" (German: Germany), \"es\" (Spanish), \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
- " <string name=\"menu_settings\">Settings</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values-cs/arrays.xml:3: Error: \"security_questions\" is translated here but not found in default locale [ExtraTranslation]\n" +
- " <string-array name=\"security_questions\">\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-es/strings.xml:12: Also translated here\n" +
- "res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n" +
- " <string name=\"continue_skip_label\">\"Weiter\"</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "5 errors, 0 warnings\n",
-
- lintProject(
- "res/values/strings.xml",
- "res/values-cs/strings.xml",
- "res/values-de-rDE/strings.xml",
- "res/values-es/strings.xml",
- "res/values-es-rUS/strings.xml",
- "res/values-land/strings.xml",
- "res/values-cs/arrays.xml",
- "res/values-es/donottranslate.xml",
- "res/values-nl-rNL/strings.xml"));
- }
-
- public void testTranslationWithCompleteRegions() throws Exception {
- TranslationDetector.sCompleteRegions = true;
- assertEquals(
- // Sample files from the Home app
- "res/values/strings.xml:19: Error: \"home_title\" is not translated in \"es-US\" (Spanish: United States) [MissingTranslation]\n" +
- " <string name=\"home_title\">Home Sample</string>\n" +
- " ~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
- " <string name=\"show_all_apps\">All</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
- " <string name=\"menu_wallpaper\">Wallpaper</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-DE\" (German: Germany), \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
- " <string name=\"menu_settings\">Settings</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "res/values/strings.xml:29: Error: \"wallpaper_instructions\" is not translated in \"es-US\" (Spanish: United States) [MissingTranslation]\n" +
- " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- " res/values-land/strings.xml:19: <No location-specific message\n" +
- "res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n" +
- " <string name=\"continue_skip_label\">\"Weiter\"</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "6 errors, 0 warnings\n",
-
- lintProject(
- "res/values/strings.xml",
- "res/values-cs/strings.xml",
- "res/values-de-rDE/strings.xml",
- "res/values-es-rUS/strings.xml",
- "res/values-land/strings.xml",
- "res/values-nl-rNL/strings.xml"));
- }
-
- public void testBcp47() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(""
- + "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"tlh\" (Klingon; tlhIngan-Hol) [MissingTranslation]\n"
- + " <string name=\"menu_settings\">Settings</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "res/values/strings.xml",
- "res/values-cs/strings.xml=>res/values-b+tlh/strings.xml"));
- }
-
- public void testHandleBom() throws Exception {
- // This isn't really testing translation detection; it's just making sure that the
- // XML parser doesn't bomb on BOM bytes (byte order marker) at the beginning of
- // the XML document
- assertEquals(
- "No warnings.",
- lintProject(
- "res/values-de/strings.xml=>res/values/strings.xml"
- ));
- }
-
- public void testTranslatedArrays() throws Exception {
- TranslationDetector.sCompleteRegions = true;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/translatedarrays.xml",
- "res/values-cs/translatedarrays.xml"));
- }
-
- public void testTranslationSuppresss() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/strings_ignore.xml=>res/values/strings.xml",
- "res/values-es/strings_ignore.xml=>res/values-es/strings.xml",
- "res/values-nl-rNL/strings.xml=>res/values-nl-rNL/strings.xml"));
- }
-
- public void testMixedTranslationArrays() throws Exception {
- // See issue http://code.google.com/p/android/issues/detail?id=29263
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/strings3.xml=>res/values/strings.xml",
- "res/values-fr/strings.xml=>res/values-fr/strings.xml"));
- }
-
- public void testLibraryProjects() throws Exception {
- // If a library project provides additional locales, that should not force
- // the main project to include all those translations
- assertEquals(
- "No warnings.",
-
- lintProject(
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "res/values/strings2.xml",
-
- // Library project
- "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
- "multiproject/library.properties=>../LibraryProject/project.properties",
-
- "res/values/strings.xml=>../LibraryProject/res/values/strings.xml",
- "res/values-cs/strings.xml=>../LibraryProject/res/values-cs/strings.xml",
- "res/values-cs/strings.xml=>../LibraryProject/res/values-de/strings.xml",
- "res/values-cs/strings.xml=>../LibraryProject/res/values-nl/strings.xml"
- ));
- }
-
- public void testNonTranslatable1() throws Exception {
- TranslationDetector.sCompleteRegions = true;
- assertEquals(
- "res/values-nb/nontranslatable.xml:3: Error: The resource string \"dummy\" has been marked as translatable=\"false\" [ExtraTranslation]\n" +
- " <string name=\"dummy\">Ignore Me</string>\n" +
- " ~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n" +
- "",
-
- lintProject("res/values/nontranslatable.xml",
- "res/values/nontranslatable2.xml=>res/values-nb/nontranslatable.xml"));
- }
-
- public void testNonTranslatable2() throws Exception {
- TranslationDetector.sCompleteRegions = true;
- assertEquals(
- "res/values-nb/nontranslatable.xml:3: Error: Non-translatable resources should only be defined in the base values/ folder [ExtraTranslation]\n" +
- " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n" +
- " ~~~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n" +
- "",
-
- lintProject("res/values/nontranslatable.xml=>res/values-nb/nontranslatable.xml"));
- }
-
- public void testNonTranslatable3() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=92861
- // Don't treat "google_maps_key" or "google_maps_key_instructions" as translatable
- TranslationDetector.sCompleteRegions = true;
- assertEquals(
- "No warnings.",
-
- lintProject("res/values/google_maps_api.xml",
- "res/values/strings2.xml",
- "res/values/strings2.xml=>res/values-nb/strings2.xml"));
- }
-
- public void testSpecifiedLanguageOk() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values-es/strings.xml=>res/values/strings.xml",
- "res/values-es/strings.xml=>res/values-es/strings.xml",
- "res/values-es-rUS/strings.xml"));
- }
-
- public void testSpecifiedLanguage() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values-es/strings_locale.xml=>res/values/strings.xml",
- "res/values-es-rUS/strings.xml"));
- }
-
- public void testAnalytics() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=43070
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/analytics.xml",
- "res/values-es/donottranslate.xml" // to make app multilingual
- ));
- }
-
- public void testIssue33845() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=33845
- assertEquals(""
- + "res/values/strings.xml:5: Error: \"dateTimeFormat\" is not translated in \"de\" (German) [MissingTranslation]\n"
- + " <string name=\"dateTimeFormat\">MM/dd/yyyy - HH:mm</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "locale33845/.classpath=>.classpath",
- "locale33845/AndroidManifest.xml=>AndroidManifest.xml",
- "locale33845/project.properties=>project.properties",
- "locale33845/res/values/strings.xml=>res/values/strings.xml",
- "locale33845/res/values-de/strings.xml=>res/values-de/strings.xml",
- "locale33845/res/values-en-rGB/strings.xml=>res/values-en-rGB/strings.xml"
- ));
- }
-
- public void testIssue33845b() throws Exception {
- // Similar to issue 33845, but with some variations to the test data
- // See http://code.google.com/p/android/issues/detail?id=33845
- assertEquals("No warnings.",
-
- lintProject(
- "locale33845/.classpath=>.classpath",
- "locale33845/AndroidManifest.xml=>AndroidManifest.xml",
- "locale33845/project.properties=>project.properties",
- "locale33845/res/values/styles.xml=>res/values/styles.xml",
- "locale33845/res/values/strings2.xml=>res/values/strings.xml",
- "locale33845/res/values-en-rGB/strings2.xml=>res/values-en-rGB/strings.xml"
- ));
- }
-
- public void testEnglishRegionAndValuesAsEnglish1() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- // tools:locale=en in base folder
- // Regression test for https://code.google.com/p/android/issues/detail?id=75879
- assertEquals("No warnings.",
-
- lintProject(
- "locale33845/res/values/strings3.xml=>res/values/strings.xml",
- "locale33845/res/values-en-rGB/strings3.xml=>res/values-en-rGB/strings.xml"
- ));
- }
-
- public void testEnglishRegionAndValuesAsEnglish2() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- // No tools:locale specified in the base folder: *assume* English
- // Regression test for https://code.google.com/p/android/issues/detail?id=75879
- assertEquals(""
- + "res/values/strings.xml:5: Error: \"other\" is not translated in \"de-DE\" (German: Germany) [MissingTranslation]\n"
- + " <string name=\"other\">other</string>\n"
- + " ~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintProject(
- "locale33845/res/values/strings4.xml=>res/values/strings.xml",
- // Flagged because it's not the default locale:
- "locale33845/res/values-en-rGB/strings3.xml=>res/values-de-rDE/strings.xml",
- // Not flagged because it's the implicit default locale
- "locale33845/res/values-en-rGB/strings3.xml=>res/values-en-rGB/strings.xml"
- ));
- }
-
- public void testEnglishRegionAndValuesAsEnglish3() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- // tools:locale=de in base folder
- // Regression test for https://code.google.com/p/android/issues/detail?id=75879
- assertEquals("No warnings.",
-
- lintProject(
- "locale33845/res/values/strings5.xml=>res/values/strings.xml",
- "locale33845/res/values-en-rGB/strings3.xml=>res/values-de-rDE/strings.xml"
- ));
- }
-
- public void testResConfigs() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(""
- + "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-DE\" (German: Germany) [MissingTranslation]\n"
- + " <string name=\"menu_settings\">Settings</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values-cs/arrays.xml:3: Error: \"security_questions\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string-array name=\"security_questions\">\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + " res/values-es/strings.xml:12: Also translated here\n"
- + "res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"continue_skip_label\">\"Weiter\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "3 errors, 0 warnings\n",
-
- lintProject(
- "res/values/strings.xml",
- "res/values-cs/strings.xml",
- "res/values-de-rDE/strings.xml",
- "res/values-es/strings.xml",
- "res/values-es-rUS/strings.xml",
- "res/values-land/strings.xml",
- "res/values-cs/arrays.xml",
- "res/values-es/donottranslate.xml",
- "res/values-nl-rNL/strings.xml"));
- }
-
- public void testMissingBaseCompletely() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(""
- + "res/values-cs/strings.xml:4: Error: \"home_title\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"home_title\">\"Domů\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "res/values-cs/strings.xml:5: Error: \"show_all_apps\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"show_all_apps\">\"Vše\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values-cs/strings.xml:6: Error: \"menu_wallpaper\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"menu_wallpaper\">\"Tapeta\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values-cs/strings.xml:7: Error: \"menu_search\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"menu_search\">\"Hledat\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "res/values-cs/strings.xml:10: Error: \"wallpaper_instructions\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"wallpaper_instructions\">\"Klepnutím na obrázek nastavíte tapetu portrétu\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "5 errors, 0 warnings\n",
-
- lintProject("res/values-cs/strings.xml"));
- }
-
- public void testMissingSomeBaseStrings() throws Exception {
- TranslationDetector.sCompleteRegions = false;
- assertEquals(""
- + "res/values-es/strings.xml:4: Error: \"home_title\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"home_title\">\"Casa\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~\n"
- + "res/values-es/strings.xml:5: Error: \"show_all_apps\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"show_all_apps\">\"Todo\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values-es/strings.xml:6: Error: \"menu_wallpaper\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"menu_wallpaper\">\"Papel tapiz\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values-es/strings.xml:10: Error: \"wallpaper_instructions\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string name=\"wallpaper_instructions\">\"Puntee en la imagen para establecer papel tapiz vertical\"</string>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/values-es/strings.xml:12: Error: \"security_questions\" is translated here but not found in default locale [ExtraTranslation]\n"
- + " <string-array name=\"security_questions\">\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "5 errors, 0 warnings\n",
-
- lintProject(
- "res/values-es-rUS/strings.xml=>res/values/strings.xml",
- "res/values-es/strings.xml=>res/values-es/strings.xml"
-
- ));
- }
-
- @Override
- protected TestLintClient createClient() {
- if (!getName().startsWith("testResConfigs")) {
- return super.createClient();
- }
-
- // Set up a mock project model for the resource configuration test(s)
- // where we provide a subset of densities to be included
-
- return new TestLintClient() {
- @NonNull
- @Override
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- return new Project(this, dir, referenceDir) {
- @Override
- public boolean isGradleProject() {
- return true;
- }
-
- @Nullable
- @Override
- public AndroidProject getGradleProjectModel() {
- /*
- Simulate variant freeBetaDebug in this setup:
- defaultConfig {
- ...
- resConfigs "cs"
- }
- flavorDimensions "pricing", "releaseType"
- productFlavors {
- beta {
- flavorDimension "releaseType"
- resConfig "en", "de"
- resConfigs "nodpi", "hdpi"
- }
- normal { flavorDimension "releaseType" }
- free { flavorDimension "pricing" }
- paid { flavorDimension "pricing" }
- }
- */
- ProductFlavor flavorFree = mock(ProductFlavor.class);
- when(flavorFree.getName()).thenReturn("free");
- when(flavorFree.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorNormal = mock(ProductFlavor.class);
- when(flavorNormal.getName()).thenReturn("normal");
- when(flavorNormal.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorPaid = mock(ProductFlavor.class);
- when(flavorPaid.getName()).thenReturn("paid");
- when(flavorPaid.getResourceConfigurations())
- .thenReturn(Collections.<String>emptyList());
-
- ProductFlavor flavorBeta = mock(ProductFlavor.class);
- when(flavorBeta.getName()).thenReturn("beta");
- List<String> resConfigs = Arrays.asList("hdpi", "en", "de", "nodpi");
- when(flavorBeta.getResourceConfigurations()).thenReturn(resConfigs);
-
- ProductFlavor defaultFlavor = mock(ProductFlavor.class);
- when(defaultFlavor.getName()).thenReturn("main");
- when(defaultFlavor.getResourceConfigurations()).thenReturn(
- Collections.singleton("cs"));
-
- ProductFlavorContainer containerBeta =
- mock(ProductFlavorContainer.class);
- when(containerBeta.getProductFlavor()).thenReturn(flavorBeta);
-
- ProductFlavorContainer containerFree =
- mock(ProductFlavorContainer.class);
- when(containerFree.getProductFlavor()).thenReturn(flavorFree);
-
- ProductFlavorContainer containerPaid =
- mock(ProductFlavorContainer.class);
- when(containerPaid.getProductFlavor()).thenReturn(flavorPaid);
-
- ProductFlavorContainer containerNormal =
- mock(ProductFlavorContainer.class);
- when(containerNormal.getProductFlavor()).thenReturn(flavorNormal);
-
- ProductFlavorContainer defaultContainer =
- mock(ProductFlavorContainer.class);
- when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
-
- List<ProductFlavorContainer> containers = Arrays.asList(
- containerPaid, containerFree, containerNormal, containerBeta
- );
-
- AndroidProject project = mock(AndroidProject.class);
- when(project.getProductFlavors()).thenReturn(containers);
- when(project.getDefaultConfig()).thenReturn(defaultContainer);
- return project;
- }
-
- @Nullable
- @Override
- public Variant getCurrentVariant() {
- List<String> productFlavorNames = Arrays.asList("free", "beta");
- Variant mock = mock(Variant.class);
- when(mock.getProductFlavors()).thenReturn(productFlavorNames);
- return mock;
- }
- };
- }
- };
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
deleted file mode 100644
index 6d07839..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import static com.android.tools.lint.detector.api.TextFormat.TEXT;
-
-import com.android.tools.lint.detector.api.Detector;
-
-import java.util.Arrays;
-
- at SuppressWarnings("javadoc")
-public class TypoDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new TypoDetector();
- }
-
- public void testPlainValues() throws Exception {
- assertEquals(
- "res/values/strings.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
- " <string name=\"s2\">Andriod activites!</string>\n" +
- " ^\n" +
- "res/values/strings.xml:6: Warning: \"activites\" is a common misspelling; did you mean \"activities\" ? [Typos]\n" +
- " <string name=\"s2\">Andriod activites!</string>\n" +
- " ^\n" +
- "res/values/strings.xml:8: Warning: \"Cmoputer\" is a common misspelling; did you mean \"Computer\" ? [Typos]\n" +
- " <string name=\"s3\"> (Cmoputer </string>\n" +
- " ^\n" +
- "res/values/strings.xml:10: Warning: \"throught\" is a common misspelling; did you mean \"thought\" or \"through\" or \"throughout\" ? [Typos]\n" +
- " <string name=\"s4\"><b>throught</b></string>\n" +
- " ^\n" +
- "res/values/strings.xml:12: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n" +
- " <string name=\"s5\">Seach</string>\n" +
- " ^\n" +
- "res/values/strings.xml:16: Warning: \"Tuscon\" is a common misspelling; did you mean \"Tucson\" ? [Typos]\n" +
- " <string name=\"s7\">Tuscon tuscon</string>\n" +
- " ^\n" +
- "res/values/strings.xml:20: Warning: \"Ok\" is usually capitalized as \"OK\" [Typos]\n" +
- " <string name=\"dlg_button_ok\">Ok</string>\n" +
- " ^\n" +
- "0 errors, 7 warnings\n" +
- "",
- lintProject("res/values/typos.xml=>res/values/strings.xml"));
- }
-
- public void testRepeatedWords() throws Exception {
- assertEquals(
- "res/values/strings.xml:5: Warning: Repeated word \"to\" in message: possible typo [Typos]\n" +
- " extra location provider commands. This may allow the app to to interfere\n" +
- " ^\n" +
- "res/values/strings.xml:7: Warning: Repeated word \"zü\" in message: possible typo [Typos]\n" +
- " <string name=\"other\">\"ü test\\n zü zü\"</string>\n" +
- " ^\n" +
- "0 errors, 2 warnings\n",
- lintProject("res/values/repeated_words.xml=>res/values/strings.xml"));
- }
-
- public void testEnLanguage() throws Exception {
- assertEquals(
- "res/values-en-rUS/strings-en.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
- " <string name=\"s2\">Andriod activites!</string>\n" +
- " ^\n" +
- "res/values-en-rUS/strings-en.xml:6: Warning: \"activites\" is a common misspelling; did you mean \"activities\" ? [Typos]\n" +
- " <string name=\"s2\">Andriod activites!</string>\n" +
- " ^\n" +
- "res/values-en-rUS/strings-en.xml:8: Warning: \"Cmoputer\" is a common misspelling; did you mean \"Computer\" ? [Typos]\n" +
- " <string name=\"s3\"> (Cmoputer </string>\n" +
- " ^\n" +
- "res/values-en-rUS/strings-en.xml:10: Warning: \"throught\" is a common misspelling; did you mean \"thought\" or \"through\" or \"throughout\" ? [Typos]\n" +
- " <string name=\"s4\"><b>throught</b></string>\n" +
- " ^\n" +
- "res/values-en-rUS/strings-en.xml:12: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n" +
- " <string name=\"s5\">Seach</string>\n" +
- " ^\n" +
- "res/values-en-rUS/strings-en.xml:16: Warning: \"Tuscon\" is a common misspelling; did you mean \"Tucson\" ? [Typos]\n" +
- " <string name=\"s7\">Tuscon tuscon</string>\n" +
- " ^\n" +
- "res/values-en-rUS/strings-en.xml:20: Warning: \"Ok\" is usually capitalized as \"OK\" [Typos]\n" +
- " <string name=\"dlg_button_ok\">Ok</string>\n" +
- " ^\n" +
- "0 errors, 7 warnings\n" +
- "",
- lintProject("res/values/typos.xml=>res/values-en-rUS/strings-en.xml"));
- }
-
- public void testEnLanguageBcp47() throws Exception {
- // Check BCP-47 locale declaration for English
- assertEquals(
- "res/values-b+en+USA/strings-en.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
- " <string name=\"s2\">Andriod activites!</string>\n" +
- " ^\n" +
- "res/values-b+en+USA/strings-en.xml:6: Warning: \"activites\" is a common misspelling; did you mean \"activities\" ? [Typos]\n" +
- " <string name=\"s2\">Andriod activites!</string>\n" +
- " ^\n" +
- "res/values-b+en+USA/strings-en.xml:8: Warning: \"Cmoputer\" is a common misspelling; did you mean \"Computer\" ? [Typos]\n" +
- " <string name=\"s3\"> (Cmoputer </string>\n" +
- " ^\n" +
- "res/values-b+en+USA/strings-en.xml:10: Warning: \"throught\" is a common misspelling; did you mean \"thought\" or \"through\" or \"throughout\" ? [Typos]\n" +
- " <string name=\"s4\"><b>throught</b></string>\n" +
- " ^\n" +
- "res/values-b+en+USA/strings-en.xml:12: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n" +
- " <string name=\"s5\">Seach</string>\n" +
- " ^\n" +
- "res/values-b+en+USA/strings-en.xml:16: Warning: \"Tuscon\" is a common misspelling; did you mean \"Tucson\" ? [Typos]\n" +
- " <string name=\"s7\">Tuscon tuscon</string>\n" +
- " ^\n" +
- "res/values-b+en+USA/strings-en.xml:20: Warning: \"Ok\" is usually capitalized as \"OK\" [Typos]\n" +
- " <string name=\"dlg_button_ok\">Ok</string>\n" +
- " ^\n" +
- "0 errors, 7 warnings\n",
- lintProject("res/values/typos.xml=>res/values-b+en+USA/strings-en.xml"));
- }
-
- public void testNorwegian() throws Exception {
- // UTF-8 handling
- assertEquals(
- "res/values-nb/typos.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
- " <string name=\"s2\">Mer morro med Andriod</string>\n" +
- " ^\n" +
- "res/values-nb/typos.xml:6: Warning: \"morro\" is a common misspelling; did you mean \"moro\" ? [Typos]\n" +
- " <string name=\"s2\">Mer morro med Andriod</string>\n" +
- " ^\n" +
- "res/values-nb/typos.xml:8: Warning: \"Parallel\" is a common misspelling; did you mean \"Parallell\" ? [Typos]\n" +
- " <string name=\"s3\"> Parallel </string>\n" +
- " ^\n" +
- "res/values-nb/typos.xml:10: Warning: \"altid\" is a common misspelling; did you mean \"alltid\" ? [Typos]\n" +
- " <string name=\"s4\"><b>altid</b></string>\n" +
- " ^\n" +
- "res/values-nb/typos.xml:12: Warning: \"Altid\" is a common misspelling; did you mean \"Alltid\" ? [Typos]\n" +
- " <string name=\"s5\">Altid</string>\n" +
- " ^\n" +
- "res/values-nb/typos.xml:18: Warning: \"karriære\" is a common misspelling; did you mean \"karrière\" ? [Typos]\n" +
- " <string name=\"s7\">Koding er en spennende karriære</string>\n" +
- " ^\n" +
- "0 errors, 6 warnings\n" +
- "",
- lintProject("res/values-nb/typos.xml"));
- }
-
- public void testGerman() throws Exception {
- // Test globbing and multiple word matching
- assertEquals(
- "res/values-de/typos.xml:6: Warning: \"befindet eine\" is a common misspelling; did you mean \"befindet sich eine\" ? [Typos]\n" +
- " wo befindet eine ip\n" +
- " ^\n" +
- "res/values-de/typos.xml:9: Warning: \"Authorisierungscode\" is a common misspelling; did you mean \"Autorisierungscode\" ? [Typos]\n" +
- " <string name=\"s2\">(Authorisierungscode!)</string>\n" +
- " ^\n" +
- "res/values-de/typos.xml:10: Warning: \"zurück gefoobaren\" is a common misspelling; did you mean \"zurückgefoobaren\" ? [Typos]\n" +
- " <string name=\"s3\"> zurück gefoobaren!</string>\n" +
- " ^\n" +
- "0 errors, 3 warnings\n" +
- "",
- lintProject("res/values-de/typos.xml"));
- }
-
- public void testOk() throws Exception {
- assertEquals(
- "No warnings.",
- lintProject("res/values/typos.xml=>res/values-xy/strings.xml"));
- }
-
- public void testGetReplacements() {
- String s = "\"throught\" is a common misspelling; did you mean \"thought\" or " +
- "\"through\" or \"throughout\" ?\n";
- assertEquals("throught", TypoDetector.getTypo(s, TEXT));
- assertEquals(Arrays.asList("thought", "through", "throughout"),
- TypoDetector.getSuggestions(s, TEXT));
- }
-
- public void testNorwegianDefault() throws Exception {
- assertEquals(
- "res/values/typos.xml:5: Warning: \"altid\" is a common misspelling; did you mean \"alltid\" ? [Typos]\n" +
- " <string name=\"s4\"><b>altid</b></string>\n" +
- " ^\n" +
- "res/values/typos.xml:7: Warning: \"Altid\" is a common misspelling; did you mean \"Alltid\" ? [Typos]\n" +
- " <string name=\"s5\">Altid</string>\n" +
- " ^\n" +
- "0 errors, 2 warnings\n",
-
- lintProject("res/values-nb/typos_locale.xml=>res/values/typos.xml"));
- }
-
-
- public void testPluralsAndStringArrays() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=82588
- assertEquals(""
- + "res/values/plurals_typography.xml:8: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n"
- + " <item quantity=\"other\">Andriod</item>\n"
- + " ^\n"
- + "res/values/plurals_typography.xml:13: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n"
- + " <item>Seach</item>\n"
- + " ^\n"
- + "0 errors, 2 warnings\n",
-
- lintProject("res/values/plurals_typography.xml"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
deleted file mode 100644
index 673685e..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-
-import java.io.File;
-import java.util.Arrays;
-
- at SuppressWarnings("javadoc")
-public class UnusedResourceDetectorTest extends AbstractCheckTest {
- private boolean mEnableIds = false;
-
- @Override
- protected Detector getDetector() {
- return new UnusedResourceDetector();
- }
-
- @Override
- protected boolean isEnabled(Issue issue) {
- if (issue == UnusedResourceDetector.ISSUE_IDS) {
- return mEnableIds;
- } else {
- return true;
- }
- }
-
- public void testUnused() throws Exception {
- mEnableIds = false;
- assertEquals(
- "res/layout/accessibility.xml: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n" +
- "res/layout/main.xml: Warning: The resource R.layout.main appears to be unused [UnusedResources]\n" +
- "res/layout/other.xml: Warning: The resource R.layout.other appears to be unused [UnusedResources]\n" +
- "res/values/strings2.xml:3: Warning: The resource R.string.hello appears to be unused [UnusedResources]\n" +
- " <string name=\"hello\">Hello</string>\n" +
- " ~~~~~~~~~~~~\n" +
- "0 errors, 4 warnings\n" +
- "",
-
- lintProject(
- "res/values/strings2.xml",
- "res/layout/layout1.xml=>res/layout/main.xml",
- "res/layout/layout1.xml=>res/layout/other.xml",
-
- // Rename .txt files to .java
- "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
- "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
- "AndroidManifest.xml",
- "res/layout/accessibility.xml"));
- }
-
- public void testUnusedIds() throws Exception {
- mEnableIds = true;
-
- assertEquals(
- "res/layout/accessibility.xml: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n" +
- "Warning: The resource R.layout.main appears to be unused [UnusedResources]\n" +
- "Warning: The resource R.layout.other appears to be unused [UnusedResources]\n" +
- "Warning: The resource R.string.hello appears to be unused [UnusedResources]\n" +
- "res/layout/accessibility.xml:2: Warning: The resource R.id.newlinear appears to be unused [UnusedIds]\n" +
- "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:id=\"@+id/newlinear\" android:orientation=\"vertical\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\">\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/accessibility.xml:3: Warning: The resource R.id.button1 appears to be unused [UnusedIds]\n" +
- " <Button android:text=\"Button\" android:id=\"@+id/button1\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/accessibility.xml:4: Warning: The resource R.id.android_logo appears to be unused [UnusedIds]\n" +
- " <ImageView android:id=\"@+id/android_logo\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:src=\"@drawable/android_button\" android:focusable=\"false\" android:clickable=\"false\" android:layout_weight=\"1.0\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/accessibility.xml:5: Warning: The resource R.id.android_logo2 appears to be unused [UnusedIds]\n" +
- " <ImageButton android:importantForAccessibility=\"yes\" android:id=\"@+id/android_logo2\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:src=\"@drawable/android_button\" android:focusable=\"false\" android:clickable=\"false\" android:layout_weight=\"1.0\" />\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "Warning: The resource R.id.imageView1 appears to be unused [UnusedIds]\n" +
- "Warning: The resource R.id.include1 appears to be unused [UnusedIds]\n" +
- "Warning: The resource R.id.linearLayout2 appears to be unused [UnusedIds]\n" +
- "0 errors, 11 warnings\n",
-
- lintProject(
- // Rename .txt files to .java
- "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
- "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
- "AndroidManifest.xml",
- "res/layout/accessibility.xml"));
- }
-
- public void testArrayReference() throws Exception {
- assertEquals(
- "res/values/arrayusage.xml:3: Warning: The resource R.array.my_array appears to be unused [UnusedResources]\n" +
- "<string-array name=\"my_array\">\n" +
- " ~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "AndroidManifest.xml",
- "res/values/arrayusage.xml"));
- }
-
- public void testAttrs() throws Exception {
- assertEquals(
- "res/layout/customattrlayout.xml: Warning: The resource R.layout.customattrlayout appears to be unused [UnusedResources]\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "res/values/customattr.xml",
- "res/layout/customattrlayout.xml",
- "unusedR.java.txt=>gen/my/pkg/R.java",
- "AndroidManifest.xml"));
- }
-
- public void testMultiProjectIgnoreLibraries() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java",
-
- // Library project
- "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
- "multiproject/library.properties=>../LibraryProject/project.properties",
- "multiproject/LibraryCode.java.txt=>../LibraryProject/src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>../LibraryProject/res/values/strings.xml"
- ));
- }
-
- public void testMultiProject() throws Exception {
- File master = getProjectDir("MasterProject",
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
- );
- File library = getProjectDir("LibraryProject",
- // Library project
- "multiproject/library-manifest.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>res/values/strings.xml"
- );
- assertEquals(
- // string1 is defined and used in the library project
- // string2 is defined in the library project and used in the master project
- // string3 is defined in the library project and not used anywhere
- "LibraryProject/res/values/strings.xml:7: Warning: The resource R.string.string3 appears to be unused [UnusedResources]\n" +
- " <string name=\"string3\">String 3</string>\n" +
- " ~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n",
-
- checkLint(Arrays.asList(master, library)).replace("/TESTROOT/", ""));
- }
-
- public void testFqcnReference() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/layout/layout1.xml=>res/layout/main.xml",
- "src/test/pkg/UnusedReference.java.txt=>src/test/pkg/UnusedReference.java",
- "AndroidManifest.xml"));
- }
-
- public void testIgnoreXmlDrawable() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/drawable/ic_menu_help.xml",
- "gen/my/pkg/R2.java.txt=>gen/my/pkg/R.java"
- ));
- }
-
- public void testPlurals() throws Exception {
- assertEquals(
- "res/values/plurals.xml:3: Warning: The resource R.plurals.my_plural appears to be unused [UnusedResources]\n" +
- " <plurals name=\"my_plural\">\n" +
- " ~~~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "res/values/strings4.xml",
- "res/values/plurals.xml",
- "AndroidManifest.xml"));
- }
-
- public void testNoMerging() throws Exception {
- // http://code.google.com/p/android/issues/detail?id=36952
-
- File master = getProjectDir("MasterProject",
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
- );
- File library = getProjectDir("LibraryProject",
- // Library project
- "multiproject/library-manifest.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>res/values/strings.xml"
- );
- assertEquals(
- // The strings are all referenced in the library project's manifest file
- // which in this project is merged in
- "LibraryProject/res/values/strings.xml:7: Warning: The resource R.string.string3 appears to be unused [UnusedResources]\n" +
- " <string name=\"string3\">String 3</string>\n" +
- " ~~~~~~~~~~~~~~\n" +
- "0 errors, 1 warnings\n",
-
- checkLint(Arrays.asList(master, library)).replace("/TESTROOT/", ""));
- }
-
- public void testLibraryMerging() throws Exception {
- // http://code.google.com/p/android/issues/detail?id=36952
- File master = getProjectDir("MasterProject",
- // Master project
- "multiproject/main-manifest.xml=>AndroidManifest.xml",
- "multiproject/main-merge.properties=>project.properties",
- "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
- );
- File library = getProjectDir("LibraryProject",
- // Library project
- "multiproject/library-manifest.xml=>AndroidManifest.xml",
- "multiproject/library.properties=>project.properties",
- "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
- "multiproject/strings.xml=>res/values/strings.xml"
- );
- assertEquals(
- // The strings are all referenced in the library project's manifest file
- // which in this project is merged in
- "No warnings.",
-
- checkLint(Arrays.asList(master, library)));
- }
-
- public void testCornerCase() throws Exception {
- // See http://code.google.com/p/projectlombok/issues/detail?id=415
- mEnableIds = true;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "src/test/pkg/Foo.java.txt=>src/test/pkg/Foo.java",
- "AndroidManifest.xml"));
- }
-
- public void testAnalytics() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=42565
- mEnableIds = false;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/analytics.xml"
- ));
- }
-
- public void testIntegers() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=53995
- mEnableIds = true;
- assertEquals(
- "No warnings.",
-
- lintProject(
- "res/values/integers.xml",
- "res/anim/slide_in_out.xml"
- ));
- }
-
- public void testIntegerArrays() throws Exception {
- // See http://code.google.com/p/android/issues/detail?id=59761
- mEnableIds = false;
- assertEquals(
- "No warnings.",
- lintProject("res/values/integer_arrays.xml=>res/values/integer_arrays.xml"));
- }
-
- public void testUnitTestReferences() throws Exception {
- // Make sure that we pick up references in unit tests as well
- // Regression test for
- // https://code.google.com/p/android/issues/detail?id=79066
- mEnableIds = false;
- //noinspection ClassNameDiffersFromFileName
- assertEquals("No warnings.",
-
- lintProject(
- copy("res/values/strings2.xml"),
- copy("res/layout/layout1.xml", "res/layout/main.xml"),
- copy("res/layout/layout1.xml", "res/layout/other.xml"),
-
- copy("src/my/pkg/Test.java.txt", "src/my/pkg/Test.java"),
- copy("gen/my/pkg/R.java.txt", "gen/my/pkg/R.java"),
- copy("AndroidManifest.xml"),
- copy("res/layout/accessibility.xml"),
-
- // Add unit test source which references resources which would otherwise
- // be marked as unused
- java("test/my/pkg/MyTest.java", ""
- + "package my.pkg;\n"
- + "class MyTest {\n"
- + " public void test() {\n"
- + " System.out.println(R.layout.accessibility);\n"
- + " System.out.println(R.layout.main);\n"
- + " System.out.println(R.layout.other);\n"
- + " System.out.println(R.string.hello);\n"
- + " }\n"
- + "}\n")
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
deleted file mode 100644
index cf21023..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class ViewTagDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new ViewTagDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "src/test/pkg/ViewTagTest.java:21: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
- " view.setTag(android.R.id.button1, group); // ERROR\n" +
- " ~~~~~~\n" +
- "src/test/pkg/ViewTagTest.java:22: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
- " view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR\n" +
- " ~~~~~~\n" +
- "src/test/pkg/ViewTagTest.java:23: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
- " view.setTag(android.R.id.icon1, cursor1); // ERROR\n" +
- " ~~~~~~\n" +
- "src/test/pkg/ViewTagTest.java:24: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
- " view.setTag(android.R.id.icon2, cursor2); // ERROR\n" +
- " ~~~~~~\n" +
- "src/test/pkg/ViewTagTest.java:25: Warning: Avoid setting view holders as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n" +
- " view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR\n" +
- " ~~~~~~\n" +
- "0 errors, 5 warnings\n",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/ViewTagTest.java.txt=>src/test/pkg/ViewTagTest.java",
- "bytecode/ViewTagTest.class.data=>bin/classes/test/pkg/ViewTagTest.class"
- ));
- }
-
- public void testICS() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "apicheck/minsdk14.xml=>AndroidManifest.xml",
- "bytecode/ViewTagTest.java.txt=>src/test/pkg/ViewTagTest.java",
- "bytecode/ViewTagTest.class.data=>bin/classes/test/pkg/ViewTagTest.class"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
deleted file mode 100644
index 9505b3a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class WakelockDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new WakelockDetector();
- }
-
- public void test1() throws Exception {
- assertEquals(
- "src/test/pkg/WakelockActivity1.java:15: Warning: Found a wakelock acquire() but no release() calls anywhere [Wakelock]\n" +
- " mWakeLock.acquire(); // Never released\n" +
- " ~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/WakelockActivity1.java.txt=>src/test/pkg/WakelockActivity1.java",
- "bytecode/WakelockActivity1.class.data=>bin/classes/test/pkg/WakelockActivity1.class"
- ));
- }
-
- public void test2() throws Exception {
- assertEquals(
- "src/test/pkg/WakelockActivity2.java:13: Warning: Wakelocks should be released in onPause, not onDestroy [Wakelock]\n" +
- " mWakeLock.release(); // Should be done in onPause instead\n" +
- " ~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/WakelockActivity2.java.txt=>src/test/pkg/WakelockActivity2.java",
- "bytecode/WakelockActivity2.class.data=>bin/classes/test/pkg/WakelockActivity2.class"
- ));
- }
-
- public void test3() throws Exception {
- assertEquals(
- "src/test/pkg/WakelockActivity3.java:13: Warning: The release() call is not always reached [Wakelock]\n" +
- " lock.release(); // Should be in finally block\n" +
- " ~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/WakelockActivity3.java.txt=>src/test/pkg/WakelockActivity3.java",
- "bytecode/WakelockActivity3.class.data=>bin/classes/test/pkg/WakelockActivity3.class"
- ));
- }
-
- public void test4() throws Exception {
- assertEquals(
- "src/test/pkg/WakelockActivity4.java:10: Warning: The release() call is not always reached [Wakelock]\n" +
- " getLock().release(); // Should be in finally block\n" +
- " ~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/WakelockActivity4.java.txt=>src/test/pkg/WakelockActivity4.java",
- "bytecode/WakelockActivity4.class.data=>bin/classes/test/pkg/WakelockActivity4.class"
- ));
- }
-
- public void test5() throws Exception {
- assertEquals(
- "src/test/pkg/WakelockActivity5.java:13: Warning: The release() call is not always reached [Wakelock]\n" +
- " lock.release(); // Should be in finally block\n" +
- " ~~~~~~~\n" +
- "0 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/WakelockActivity5.java.txt=>src/test/pkg/WakelockActivity5.java",
- "bytecode/WakelockActivity5.class.data=>bin/classes/test/pkg/WakelockActivity5.class"
- ));
- }
-
- public void test6() throws Exception {
- assertEquals(
- "src/test/pkg/WakelockActivity6.java:19: Warning: The release() call is not always reached [Wakelock]\n" +
- " lock.release(); // Wrong\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/WakelockActivity6.java:28: Warning: The release() call is not always reached [Wakelock]\n" +
- " lock.release(); // Wrong\n" +
- " ~~~~~~~\n" +
- "src/test/pkg/WakelockActivity6.java:65: Warning: The release() call is not always reached [Wakelock]\n" +
- " lock.release(); // Wrong\n" +
- " ~~~~~~~\n" +
- "0 errors, 3 warnings\n" +
- "",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/WakelockActivity6.java.txt=>src/test/pkg/WakelockActivity6.java",
- "bytecode/WakelockActivity6.class.data=>bin/classes/test/pkg/WakelockActivity6.class"
- ));
- }
-
- public void test7() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "res/layout/onclick.xml=>res/layout/onclick.xml",
- "bytecode/WakelockActivity7.java.txt=>src/test/pkg/WakelockActivity7.java",
- "bytecode/WakelockActivity7.class.data=>bin/classes/test/pkg/WakelockActivity7.class"
- ));
- }
-
- public void test8() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "bytecode/WakelockActivity8.java.txt=>src/test/pkg/WakelockActivity8.java",
- "bytecode/WakelockActivity8.class.data=>bin/classes/test/pkg/WakelockActivity8.class"
- ));
- }
-
- public void test9() throws Exception {
- // Regression test for 66040
- assertEquals(
- "No warnings.",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "bytecode/WakelockActivity9.java.txt=>src/test/pkg/WakelockActivity9.java",
- "bytecode/WakelockActivity9.class.data=>bin/classes/test/pkg/WakelockActivity9.class"
- ));
- }
-
- public void testFlags() throws Exception {
- assertEquals(""
- + "src/test/pkg/PowerManagerFlagTest.java:15: Warning: Should not set both PARTIAL_WAKE_LOCK and ACQUIRE_CAUSES_WAKEUP. If you do not want the screen to turn on, get rid of ACQUIRE_CAUSES_WAKEUP [Wakelock]\n"
- + " pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|ACQUIRE_CAUSES_WAKEUP, \"Test\"); // Bad\n"
- + " ~~~~~~~~~~~\n"
- + "0 errors, 1 warnings\n",
-
- lintProject(
- "bytecode/.classpath=>.classpath",
- "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
- "bytecode/PowerManagerFlagTest.java.txt=>src/test/pkg/PowerManagerFlagTest.java",
- "bytecode/PowerManagerFlagTest.class.data=>bin/classes/test/pkg/PowerManagerFlagTest.class"
- ));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
deleted file mode 100644
index fccd0be..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.checks;
-
-import com.android.tools.lint.detector.api.Detector;
-
- at SuppressWarnings("javadoc")
-public class WrongIdDetectorTest extends AbstractCheckTest {
- @Override
- protected Detector getDetector() {
- return new WrongIdDetector();
- }
-
- public void test() throws Exception {
- assertEquals(
- "res/layout/layout1.xml:14: Error: The id \"button5\" is not defined anywhere. Did you mean one of {button1, button2, button3, button4} ? [UnknownId]\n" +
- " android:layout_alignBottom=\"@+id/button5\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:17: Error: The id \"my_id3\" is not defined anywhere. Did you mean my_id2 ? [UnknownId]\n" +
- " android:layout_alignRight=\"@+id/my_id3\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:18: Error: The id \"my_id1\" is defined but not assigned to any views. Did you mean my_id2 ? [UnknownId]\n" +
- " android:layout_alignTop=\"@+id/my_id1\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:15: Warning: The id \"my_id2\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
- " android:layout_alignLeft=\"@+id/my_id2\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "3 errors, 1 warnings\n" +
- "",
-
- lintProject(
- "wrongid/layout1.xml=>res/layout/layout1.xml",
- "wrongid/layout2.xml=>res/layout/layout2.xml",
- "wrongid/ids.xml=>res/values/ids.xml"
- ));
- }
-
- public void testSingleFile() throws Exception {
- assertEquals(
- "res/layout/layout1.xml:14: Warning: The id \"button5\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
- " android:layout_alignBottom=\"@+id/button5\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:15: Warning: The id \"my_id2\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
- " android:layout_alignLeft=\"@+id/my_id2\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:17: Warning: The id \"my_id3\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
- " android:layout_alignRight=\"@+id/my_id3\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:18: Warning: The id \"my_id1\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
- " android:layout_alignTop=\"@+id/my_id1\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 4 warnings\n" +
- "",
-
- lintFiles("wrongid/layout1.xml=>res/layout/layout1.xml"));
- }
-
- public void testSuppressed() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintProject(
- "wrongid/ignorelayout1.xml=>res/layout/layout1.xml",
- "wrongid/layout2.xml=>res/layout/layout2.xml",
- "wrongid/ids.xml=>res/values/ids.xml"
- ));
- }
-
- public void testSuppressedSingleFile() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintFiles("wrongid/ignorelayout1.xml=>res/layout/layout1.xml"));
- }
-
- public void testNewIdPrefix() throws Exception {
- assertEquals(
- "No warnings.",
-
- lintFiles("res/layout/default_item_badges.xml",
- "res/layout/detailed_item.xml"));
- }
-
- public void testSiblings() throws Exception {
- assertEquals(""
- + "res/layout/siblings.xml:55: Error: @id/button5 is not a sibling in the same RelativeLayout [NotSibling]\n"
- + " android:layout_alignTop=\"@id/button5\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/siblings.xml:56: Error: @id/button6 is not a sibling in the same RelativeLayout [NotSibling]\n"
- + " android:layout_toRightOf=\"@id/button6\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/siblings.xml:63: Error: @+id/button5 is not a sibling in the same RelativeLayout [NotSibling]\n"
- + " android:layout_alignTop=\"@+id/button5\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/siblings.xml:64: Error: @+id/button6 is not a sibling in the same RelativeLayout [NotSibling]\n"
- + " android:layout_toRightOf=\"@+id/button6\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "4 errors, 0 warnings\n",
-
- lintFiles("res/layout/siblings.xml"));
- }
-
- public void testInvalidIds1() throws Exception {
- // See https://code.google.com/p/android/issues/detail?id=56029
- assertEquals(""
- + "res/layout/invalid_ids.xml:23: Error: ID definitions must be of the form @+id/name; try using @+id/menu_Reload [InvalidId]\n"
- + " android:id=\"@+menu/Reload\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/invalid_ids.xml:31: Error: ID definitions must be of the form @+id/name; try using @+id/_id_foo [InvalidId]\n"
- + " android:id=\"@+/id_foo\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/invalid_ids.xml:37: Error: ID definitions must be of the form @+id/name; try using @+id/myid_button5 [InvalidId]\n"
- + " android:id=\"@+myid/button5\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/invalid_ids.xml:43: Error: ID definitions must be of the form @+id/name; try using @+id/string_whatevs [InvalidId]\n"
- + " android:id=\"@+string/whatevs\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "4 errors, 0 warnings\n",
-
- lintFiles("res/layout/invalid_ids.xml"));
- }
-
- public void testInvalidIds2() throws Exception {
- // https://code.google.com/p/android/issues/detail?id=65244
- assertEquals(""
- + "res/layout/invalid_ids2.xml:8: Error: ID definitions must be of the form @+id/name; try using @+id/btn_skip [InvalidId]\n"
- + " android:id=\"@+id/btn/skip\"\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "res/layout/invalid_ids2.xml:16: Error: Invalid id: missing value [InvalidId]\n"
- + " android:id=\"@+id/\"\n"
- + " ~~~~~~~~~~~~~~~~~~\n"
- + "2 errors, 0 warnings\n",
-
- lintFiles("res/layout/invalid_ids2.xml"));
- }
-
- public void testIncremental() throws Exception {
- assertEquals(
- "res/layout/layout1.xml:14: Error: The id \"button5\" is not defined anywhere. Did you mean one of {button1, button2, button3, button4} ? [UnknownId]\n" +
- " android:layout_alignBottom=\"@+id/button5\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:17: Error: The id \"my_id3\" is not defined anywhere. Did you mean one of {my_id1, my_id2} ? [UnknownId]\n" +
- " android:layout_alignRight=\"@+id/my_id3\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:18: Error: The id \"my_id1\" is defined but not assigned to any views. Did you mean one of {my_id2, my_id3} ? [UnknownId]\n" +
- " android:layout_alignTop=\"@+id/my_id1\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "res/layout/layout1.xml:15: Warning: The id \"my_id2\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
- " android:layout_alignLeft=\"@+id/my_id2\"\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "3 errors, 1 warnings\n",
-
- lintProjectIncrementally(
- "res/layout/layout1.xml",
-
- "wrongid/layout1.xml=>res/layout/layout1.xml",
- "wrongid/layout2.xml=>res/layout/layout2.xml",
- "wrongid/ids.xml=>res/values/ids.xml"
- ));
- }
-
- public void testSelfReference() throws Exception {
- // Make sure we highlight direct references to self
- // Regression test for https://code.google.com/p/android/issues/detail?id=136103
- assertEquals(""
- + "res/layout/layout3.xml:9: Error: Cannot be relative to self: id=tv_portfolio_title, layout_below=tv_portfolio_title [NotSibling]\n"
- + " android:layout_below=\"@+id/tv_portfolio_title\"/>\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "1 errors, 0 warnings\n",
-
- lintFiles("wrongid/layout3.xml=>res/layout/layout3.xml"));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
deleted file mode 100644
index 429b388..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-package test.pkg;
-
-import android.util.Property;
-import android.view.View;
-import static android.view.View.MEASURED_STATE_MASK;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import android.view.*;
-import android.annotation.*;
-import android.app.*;
-import android.widget.*;
-import static android.widget.ZoomControls.*;
-import android.Manifest.permission;
-import android.Manifest;
-
-/** Various tests for source-level checks */
-final class ApiSourceCheck extends LinearLayout {
- public ApiSourceCheck(android.content.Context context) {
- super(context);
- }
-
- /**
- * Return only the state bits of {@link #getMeasuredWidthAndState()} and
- * {@link #getMeasuredHeightAndState()}, combined into one integer. The
- * width component is in the regular bits {@link #MEASURED_STATE_MASK} and
- * the height component is at the shifted bits
- * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
- */
- public static int m1(View child) {
- // from static import of field
- int x = MEASURED_STATE_MASK;
-
- // fully qualified name field access
- int y = android.view.View.MEASURED_STATE_MASK;
-
- // from explicitly imported class
- int z = View.MEASURED_STATE_MASK;
- int find2 = View.FIND_VIEWS_WITH_TEXT; // requires API 14
-
- // from wildcard import of package
- int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;
- int find1 = ZoomButton.FIND_VIEWS_WITH_CONTENT_DESCRIPTION; // requires
- // API 14
- // from static wildcard import
- int overScroll = OVER_SCROLL_ALWAYS; // requires API 9
-
- // Inherited field from ancestor class (View)
- int auto = IMPORTANT_FOR_ACCESSIBILITY_AUTO; // requires API 16
-
- // object field reference: ensure that we don't get two errors
- // (one from source scan, the other from class scan)
- Object rotationX = ZoomButton.ROTATION_X; // Requires API 14
-
- // different type of expression than variable declaration
- return (child.getMeasuredWidth() & View.MEASURED_STATE_MASK)
- | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));
- }
-
- @SuppressLint("NewApi")
- private void testSuppress1() {
- // Checks suppress on surrounding method
- int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;
- }
-
- private void testSuppress2() {
- // Checks suppress on surrounding declaration statement
- @SuppressLint("NewApi")
- int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
- }
-
- @TargetApi(17)
- private void testTargetApi1() {
- // Checks @TargetApi on surrounding method
- int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
- }
-
- @TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
- private void testTargetApi2() {
- // Checks @TargetApi with codename
- int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
- }
-
- @TargetApi(JELLY_BEAN_MR1)
- private void testTargetApi3() {
- // Checks @TargetApi with codename
- int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
- }
-
- private void checkOtherFields() {
- // Look at fields that aren't capitalized
- int custom = android.R.id.custom; // API 8
- }
-
- private void innerclass() {
- String setPointerSpeed = permission.SET_POINTER_SPEED;
- String setPointerSpeed2 = Manifest.permission.SET_POINTER_SPEED;
- }
-
- private void test() {
- // Make sure that local variable references which look like fields,
- // even imported ones, aren't taken as invalid references
- int OVER_SCROLL_ALWAYS = 1, IMPORTANT_FOR_ACCESSIBILITY_AUTO = 2;
- int x = OVER_SCROLL_ALWAYS;
- int y = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
- findViewById(IMPORTANT_FOR_ACCESSIBILITY_AUTO); // yes, nonsensical
- }
-
- private void testBenignUsages(int x) {
- // Certain types of usages (such as switch/case constants) are okay
- switch (x) {
- case View.MEASURED_STATE_MASK: { // OK
- break;
- }
- }
- if (x == View.MEASURED_STATE_MASK) { // OK
- }
- if (false || x == View.MEASURED_STATE_MASK) { // OK
- }
- if (x >= View.MEASURED_STATE_MASK) { // OK
- }
- int y = View.MEASURED_STATE_MASK; // Not OK
- testBenignUsages(View.MEASURED_STATE_MASK); // Not OK
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt
deleted file mode 100644
index a8775d7..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt
+++ /dev/null
@@ -1,137 +0,0 @@
-package test.pkg;
-
-import org.w3c.dom.DOMError;
-import org.w3c.dom.DOMErrorHandler;
-import org.w3c.dom.DOMLocator;
-
-import android.view.ViewGroup.LayoutParams;
-import android.annotations.tools.SuppressLint;
-import android.app.Activity;
-import android.app.ApplicationErrorReport;
-import android.app.ApplicationErrorReport.BatteryInfo;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuff.Mode;
-import android.widget.Chronometer;
-import android.widget.GridLayout;
-import dalvik.bytecode.OpcodeInfo;
-
-public class SuppressTest1 extends Activity {
- @SuppressLint("all")
- public void method1(Chronometer chronometer, DOMLocator locator) {
- // Virtual call
- getActionBar(); // API 11
-
- // Class references (no call or field access)
- DOMError error = null; // API 8
- Class<?> clz = DOMErrorHandler.class; // API 8
-
- // Method call
- chronometer.getOnChronometerTickListener(); // API 3
-
- // Inherited method call (from TextView
- chronometer.setTextIsSelectable(true); // API 11
-
- // Field access
- int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
- int fillParent = LayoutParams.FILL_PARENT; // API 1
- // This is a final int, which means it gets inlined
- int matchParent = LayoutParams.MATCH_PARENT; // API 8
- // Field access: non final
- BatteryInfo batteryInfo = getReport().batteryInfo;
-
- // Enum access
- Mode mode = PorterDuff.Mode.OVERLAY; // API 11
- }
-
- @SuppressLint("NewApi")
- public void method2(Chronometer chronometer, DOMLocator locator) {
- // Virtual call
- getActionBar(); // API 11
-
- // Class references (no call or field access)
- DOMError error = null; // API 8
- Class<?> clz = DOMErrorHandler.class; // API 8
-
- // Method call
- chronometer.getOnChronometerTickListener(); // API 3
-
- // Inherited method call (from TextView
- chronometer.setTextIsSelectable(true); // API 11
-
- // Field access
- int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
- int fillParent = LayoutParams.FILL_PARENT; // API 1
- // This is a final int, which means it gets inlined
- int matchParent = LayoutParams.MATCH_PARENT; // API 8
- // Field access: non final
- BatteryInfo batteryInfo = getReport().batteryInfo;
-
- // Enum access
- Mode mode = PorterDuff.Mode.OVERLAY; // API 11
- }
-
- @SuppressLint("SomethingElse")
- public void method3(Chronometer chronometer, DOMLocator locator) {
- // Virtual call
- getActionBar(); // API 11
-
- // Class references (no call or field access)
- DOMError error = null; // API 8
- Class<?> clz = DOMErrorHandler.class; // API 8
-
- // Method call
- chronometer.getOnChronometerTickListener(); // API 3
-
- // Inherited method call (from TextView
- chronometer.setTextIsSelectable(true); // API 11
-
- // Field access
- int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
- int fillParent = LayoutParams.FILL_PARENT; // API 1
- // This is a final int, which means it gets inlined
- int matchParent = LayoutParams.MATCH_PARENT; // API 8
- // Field access: non final
- BatteryInfo batteryInfo = getReport().batteryInfo;
-
- // Enum access
- Mode mode = PorterDuff.Mode.OVERLAY; // API 11
- }
-
- @SuppressLint({"SomethingElse", "NewApi"})
- public void method4(Chronometer chronometer, DOMLocator locator) {
- // Virtual call
- getActionBar(); // API 11
-
- // Class references (no call or field access)
- DOMError error = null; // API 8
- Class<?> clz = DOMErrorHandler.class; // API 8
-
- // Method call
- chronometer.getOnChronometerTickListener(); // API 3
-
- // Inherited method call (from TextView
- chronometer.setTextIsSelectable(true); // API 11
-
- // Field access
- int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
- int fillParent = LayoutParams.FILL_PARENT; // API 1
- // This is a final int, which means it gets inlined
- int matchParent = LayoutParams.MATCH_PARENT; // API 8
- // Field access: non final
- BatteryInfo batteryInfo = getReport().batteryInfo;
-
- // Enum access
- Mode mode = PorterDuff.Mode.OVERLAY; // API 11
- }
-
- // Return type
- @SuppressLint("NewApi")
- GridLayout getGridLayout() { // API 14
- return null;
- }
-
- @SuppressLint("all")
- private ApplicationErrorReport getReport() {
- return null;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt
deleted file mode 100644
index e325413..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-package test.pkg;
-
-import org.w3c.dom.DOMLocator;
-
-import android.annotations.tools.SuppressLint;
-import android.app.Activity;
-import android.app.ApplicationErrorReport;
-import android.widget.Chronometer;
-import android.widget.GridLayout;
-
- at SuppressLint("all")
-public class SuppressTest2 extends Activity {
- public void method(Chronometer chronometer, DOMLocator locator) {
- getActionBar(); // API 11
- }
-
- // Return type
- GridLayout getGridLayout() { // API 14
- return null;
- }
-
- private ApplicationErrorReport getReport() {
- return null;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt
deleted file mode 100644
index c430ce4..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-package test.pkg;
-
-import org.w3c.dom.DOMLocator;
-
-import android.annotations.tools.SuppressLint;
-import android.app.Activity;
-import android.app.ApplicationErrorReport;
-import android.widget.Chronometer;
-import android.widget.GridLayout;
-
- at SuppressLint("NewApi")
-public class SuppressTest3 extends Activity {
- public void method(Chronometer chronometer, DOMLocator locator) {
- getActionBar(); // API 11
- }
-
- // Return type
- GridLayout getGridLayout() { // API 14
- return null;
- }
-
- private ApplicationErrorReport getReport() {
- return null;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt
deleted file mode 100644
index 2fa2af8..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-package test.pkg;
-
-import android.annotations.tools.SuppressLint;
-import android.app.Activity;
-import android.app.ApplicationErrorReport;
-import android.app.ApplicationErrorReport.BatteryInfo;
-
-public class SuppressTest4 extends Activity {
- public void method() {
-
- // These annotations within the method do not end up
- // in the bytecode, so they have no effect. We need a
- // lint annotation check to find these.
-
- @SuppressLint("NewApi")
- ApplicationErrorReport report = null;
-
- @SuppressLint("NewApi")
- BatteryInfo batteryInfo = report.batteryInfo;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/android-support-v4.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/android-support-v4.jar.data
deleted file mode 100644
index 6080877..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/android-support-v4.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/fragment_support.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/fragment_support.jar.data
deleted file mode 100644
index 94622f0..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/fragment_support.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/unsupported.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/unsupported.jar.data
deleted file mode 100644
index 7cbedc1..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/unsupported.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt
deleted file mode 100644
index ddb68e4..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-package test.pkg;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-
-public class MyPreferenceActivity extends PreferenceActivity {
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getActionBar(); // Should not generate a warning
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt
deleted file mode 100644
index 2e08a97..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-package android.support.v4.app;
-
-/** Stub to make unit tests able to resolve types without having a real dependency
- * on the appcompat library */
-public abstract class DialogFragment extends Fragment {
- public abstract void show(FragmentManager manager, String tag);
- public abstract int show(FragmentTransaction transaction, String tag);
- public abstract void dismiss();
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt
deleted file mode 100644
index d12bc55..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-package android.support.v4.app;
-
-/** Stub to make unit tests able to resolve types without having a real dependency
- * on the appcompat library */
-public abstract class FragmentTransaction {
- public abstract int commit();
- public abstract int commitAllowingStateLoss();
- public abstract FragmentTransaction show(Fragment fragment);
- public abstract FragmentTransaction hide(Fragment fragment);
- public abstract FragmentTransaction attach(Fragment fragment);
- public abstract FragmentTransaction detach(Fragment fragment);
- public abstract FragmentTransaction add(int containerViewId, Fragment fragment);
- public abstract FragmentTransaction addToBackStack(String name);
- public abstract FragmentTransaction disallowAddToBackStack();
- public abstract FragmentTransaction setBreadCrumbShortTitle(int res);
- public abstract FragmentTransaction setCustomAnimations(int enter, int exit);
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt
deleted file mode 100644
index 8f0b844..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt
+++ /dev/null
@@ -1,119 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-
- at SuppressWarnings("unused")
-public class CommitTest extends Activity {
- public void ok1() {
- getFragmentManager().beginTransaction().commit();
- }
-
- public void ok2() {
- FragmentTransaction transaction = getFragmentManager().beginTransaction();
- transaction.commit();
- }
-
- public void ok3() {
- FragmentTransaction transaction = getFragmentManager().beginTransaction();
- transaction.commitAllowingStateLoss();
- }
-
- public void error1() {
- getFragmentManager().beginTransaction(); // Missing commit
- }
-
- public void error() {
- FragmentTransaction transaction1 = getFragmentManager().beginTransaction();
- FragmentTransaction transaction2 = getFragmentManager().beginTransaction(); // Missing commit
- transaction1.commit();
- }
-
- public void error3_public() {
- error3();
- }
-
- private void error3() {
- getFragmentManager().beginTransaction(); // Missing commit
- }
-
- public void ok4(FragmentManager manager, String tag) {
- FragmentTransaction ft = manager.beginTransaction();
- ft.add(null, tag);
- ft.commit();
- }
-
- // Support library
-
- private android.support.v4.app.FragmentManager getSupportFragmentManager() {
- return null;
- }
-
- public void ok5() {
- getSupportFragmentManager().beginTransaction().commit();
- }
-
- public void ok6(android.support.v4.app.FragmentManager manager, String tag) {
- android.support.v4.app.FragmentTransaction ft = manager.beginTransaction();
- ft.add(null, tag);
- ft.commit();
- }
-
- public void error4() {
- getSupportFragmentManager().beginTransaction();
- }
-
- android.support.v4.app.Fragment mFragment1 = null;
- Fragment mFragment2 = null;
-
- public void ok7() {
- getSupportFragmentManager().beginTransaction().add(android.R.id.content, mFragment1).commit();
- }
-
- public void ok8() {
- getFragmentManager().beginTransaction().add(android.R.id.content, mFragment2).commit();
- }
-
- public void ok10() {
- // Test chaining
- FragmentManager fragmentManager = getFragmentManager();
- fragmentManager.beginTransaction().addToBackStack("test").attach(mFragment2).detach(mFragment2)
- .disallowAddToBackStack().hide(mFragment2).setBreadCrumbShortTitle("test")
- .show(mFragment2).setCustomAnimations(0, 0).commit();
- }
-
- public void ok9() {
- FragmentManager fragmentManager = getFragmentManager();
- fragmentManager.beginTransaction().commit();
- }
-
- public void ok11() {
- FragmentTransaction transaction;
- // Comment in between variable declaration and assignment
- transaction = getFragmentManager().beginTransaction();
- transaction.commit();
- }
-
- public void ok12() {
- FragmentTransaction transaction;
- transaction = (getFragmentManager().beginTransaction());
- transaction.commit();
- }
-
- @SuppressWarnings("UnnecessaryLocalVariable")
- public void ok13() {
- FragmentTransaction transaction = getFragmentManager().beginTransaction();
- FragmentTransaction temp;
- temp = transaction;
- temp.commitAllowingStateLoss();
- }
-
- @SuppressWarnings("UnnecessaryLocalVariable")
- public void ok14() {
- FragmentTransaction transaction = getFragmentManager().beginTransaction();
- FragmentTransaction temp = transaction;
- temp.commitAllowingStateLoss();
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.jar.data
deleted file mode 100644
index f5ef6d1..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data
deleted file mode 100644
index ae65532..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data
deleted file mode 100644
index 52183c2..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data
deleted file mode 100644
index 3975896..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data
deleted file mode 100644
index 690ee89..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data
deleted file mode 100644
index b389dc5..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
deleted file mode 100644
index 8b01ef2..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
deleted file mode 100644
index 06f3298..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-package test.pkg;
-import android.os.Looper;
-import android.os.Handler;
-import android.os.Message;
-
-public class HandlerTest extends Handler { // OK
- public static class StaticInner extends Handler { // OK
- public void dispatchMessage(Message msg) {
- super.dispatchMessage(msg);
- };
- }
- public class Inner extends Handler { // ERROR
- public void dispatchMessage(Message msg) {
- super.dispatchMessage(msg);
- };
- }
- void method() {
- Handler anonymous = new Handler() { // ERROR
- public void dispatchMessage(Message msg) {
- super.dispatchMessage(msg);
- };
- };
-
- Looper looper = null;
- Handler anonymous2 = new Handler(looper) { // OK
- public void dispatchMessage(Message msg) {
- super.dispatchMessage(msg);
- };
- };
- }
-
- public class WithArbitraryLooper extends Handler {
- public WithArbitraryLooper(String unused, Looper looper) { // OK
- super(looper, null);
- }
-
- public void dispatchMessage(Message msg) {
- super.dispatchMessage(msg);
- };
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.class.data
deleted file mode 100644
index 3236187..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.java.txt
deleted file mode 100644
index c05fbd9..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SecureRandomTest.java.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-package test.pkg;
-
-import java.security.SecureRandom;
-import java.util.Random;
-
-public class SecureRandomTest {
- private static final long FIXED_SEED = 1000L;
- protected int getDynamicSeed() { return 1; }
-
- public void testLiterals() {
- SecureRandom random1 = new SecureRandom();
- random1.setSeed(System.currentTimeMillis()); // OK
- random1.setSeed(getDynamicSeed()); // OK
- random1.setSeed(0); // Wrong
- random1.setSeed(1); // Wrong
- random1.setSeed((int)1023); // Wrong
- random1.setSeed(1023L); // Wrong
- random1.setSeed(FIXED_SEED); // Wrong
- }
-
- public void testRandomTypeOk() {
- Random random2 = new Random();
- random2.setSeed(0); // OK
- }
-
- public void testRandomTypeWrong() {
- Random random3 = new SecureRandom();
- random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object
- }
-
- public void testBytesOk() {
- SecureRandom random1 = new SecureRandom();
- byte[] seed = random1.generateSeed(4);
- random1.setSeed(seed); // OK
- }
-
- public void testBytesWrong() {
- SecureRandom random2 = new SecureRandom();
- byte[] seed = new byte[3];
- random2.setSeed(seed); // Wrong
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.class.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.class.data
deleted file mode 100644
index f26032c..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.class.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.java.txt
deleted file mode 100644
index 8e72fd0..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewTagTest.java.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-package test.pkg;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-
- at SuppressWarnings("unused")
-public abstract class ViewTagTest {
- public View newView(Context context, ViewGroup group, Cursor cursor1,
- MatrixCursor cursor2) {
- LayoutInflater inflater = LayoutInflater.from(context);
- View view = inflater.inflate(android.R.layout.activity_list_item, null);
- view.setTag(android.R.id.background, "Some random tag"); // OK
- view.setTag(android.R.id.button1, group); // ERROR
- view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR
- view.setTag(android.R.id.icon1, cursor1); // ERROR
- view.setTag(android.R.id.icon2, cursor2); // ERROR
- view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR
- return view;
- }
-
- @SuppressLint("ViewTag")
- public void checkSuppress(Context context, View view) {
- view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon));
- }
-
- private class MyViewHolder {
- View view;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/butterknife-2.0.1.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/butterknife-2.0.1.jar.data
deleted file mode 100644
index 4d997fb..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/butterknife-2.0.1.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/dagger-compiler-1.2.1-subset.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/dagger-compiler-1.2.1-subset.jar.data
deleted file mode 100644
index 3eca919..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/dagger-compiler-1.2.1-subset.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/javax.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/javax.jar.data
deleted file mode 100644
index 179637c..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/javax.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle
deleted file mode 100644
index 520216c..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion "19.0.0"
-
- defaultConfig {
- minSdkVersion 7
- targetSdkVersion 19
- versionCode 1
- versionName "1.0"
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:18.0.0'
- compile 'com.android.support.test:espresso:0.2'
- compile 'com.android.support:multidex:1.0.1'
- compile 'com.android.support:multidex-instrumentation:1.0.1'
-
- // Suppressed:
- //noinspection GradleCompatible
- compile 'com.android.support:support-v4:18.0.0'
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle
deleted file mode 100644
index 0aa261e..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle
+++ /dev/null
@@ -1,11 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 19
- buildToolsVersion "19.0.1"
-}
-
-dependencies {
- compile 'com.android.support:appcompat-v7:+'
- compile group: 'com.android.support', name: 'support-v4', version: '21.0.+'
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/appcompat.jar.data b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/appcompat.jar.data
deleted file mode 100644
index defc450..0000000
Binary files a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/appcompat.jar.data and /dev/null differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt
deleted file mode 100644
index 353c0a3..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.support.annotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
- at Retention(CLASS)
- at Target({METHOD,CONSTRUCTOR})
-public @interface AnyRes {
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt
deleted file mode 100644
index bbb0407..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-package android.support.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
- at Documented
- at Retention(SOURCE)
- at Target({METHOD, PARAMETER, FIELD})
-public @interface DrawableRes {
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt
deleted file mode 100644
index 5fea6be..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-package test.pkg;
-
-import android.view.MenuItem;
-
-public class ActionTest1 {
- @SuppressLint("AlwaysShowAction")
- public void foo() {
- System.out.println(MenuItem.SHOW_AS_ACTION_ALWAYS);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt
deleted file mode 100644
index 770d00f..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-package test.pkg;
-
-import android.app.AlarmManager;
-
-public class TestAlarm {
- public void test(AlarmManager alarmManager) {
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, 60000, null); // OK
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 6000, 70000, null); // OK
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 50, 10, null); // ERROR
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, // ERROR
- OtherClass.MY_INTERVAL, null); // ERROR
-
- // Check value flow analysis
- int interval = 10;
- long interval2 = 2 * interval;
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, interval2, null); // ERROR
- }
-
- private static class OtherClass {
- public static final long MY_INTERVAL = 1000L;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
deleted file mode 100644
index 63a9ffd..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-package test.pkg;
-
-import android.annotations.tools.SuppressLint;
-
-public class Assert {
- public Assert(int param, Object param2, Object param3) {
- assert false; // ERROR
- assert param > 5 : "My description"; // ERROR
- assert param2 == param3; // ERROR
- assert param2 != null && param3 == param2; // ERROR
- assert true; // OK
- assert param2 == null; // OK
- assert param2 != null && param3 == null; // OK
- assert param2 == null && param3 != null; // OK
- assert param2 != null && param3 != null; // OK
- assert null != param2; // OK
- assert param2 != null; // OK
- assert param2 != null : "My description"; // OK
- assert checkSuppressed(5) != null; // OK
- }
-
- @SuppressLint("Assert")
- public Object checkSuppressed(int param) {
- assert param > 5 : "My description";
- return null;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
deleted file mode 100644
index 394ec43..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
+++ /dev/null
@@ -1,40 +0,0 @@
-package test.pkg;
-
-import android.view.View;
-
-public class DetachedFromWindow {
- private static class Test1 extends View {
- protected void onDetachedFromWindow() {
- // Error
- }
- }
-
- private static class Test2 extends View {
- protected void onDetachedFromWindow(int foo) {
- // OK: not overriding the right method
- }
- }
-
- private static class Test3 extends View {
- protected void onDetachedFromWindow() {
- // OK: Calling super
- super.onDetachedFromWindow();
- }
- }
-
- private static class Test4 extends View {
- protected void onDetachedFromWindow() {
- // Error: missing detach call
- int x = 1;
- x++;
- System.out.println(x);
- }
- }
-
- private static class Test5 extends Object {
- protected void onDetachedFromWindow() {
- // OK - not in a view
- // Regression test for http://b.android.com/73571
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt
deleted file mode 100644
index 4d8f993..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-package test.pkg;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import android.content.Context;
-
-public class LongSparseArray extends Button {
- public void test() { // but only minSdkVersion >= 17
- Map<Long, String> myStringMap = new HashMap<Long, String>();
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt
deleted file mode 100644
index 9a3a438..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-package test.pkg;
-
-import android.preference.PreferenceActivity;
-
-public class PreferenceActivitySubclass extends PreferenceActivity {
- @Override
- protected boolean isValidFragment(String fragmentName) {
- return false;
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt
deleted file mode 100644
index b10bc01..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-package test.pkg;
-
-import android.database.sqlite.SQLiteDatabase;
-
- at SuppressWarnings({"unused", "SpellCheckingInspection"})
-public class SqliteTest {
- public interface Tables {
- interface AppKeys {
- String NAME = "appkeys";
-
- interface Columns {
- String _ID = "_id";
- String PKG_NAME = "packageName";
- String PKG_SIG = "signatureDigest";
- }
-
- String SCHEMA =
- Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Columns.PKG_NAME + " STRING NOT NULL," +
- Columns.PKG_SIG + " STRING NOT NULL";
- }
- }
-
- public void test(SQLiteDatabase db, String name) {
- db.execSQL("CREATE TABLE " + name + "(" + Tables.AppKeys.SCHEMA + ");"); // ERROR
-
- }
-
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(TracksColumns.CREATE_TABLE); // ERROR
- }
-
- private void doCreate(SQLiteDatabase db) {
- // Not yet handled; we need to flow string concatenation across procedure calls
- createTable(db, Tables.AppKeys.NAME, Tables.AppKeys.SCHEMA); // ERROR
- }
-
- private void createTable(SQLiteDatabase db, String tableName, String schema) {
- db.execSQL("CREATE TABLE " + tableName + "(" + schema + ");");
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt
deleted file mode 100644
index ad8383a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.company.something;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.webkit.WebView;
-
-public class HelloWebApp extends Activity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- WebView webView = (WebView)findViewById(R.id.webView);
- webView.getSettings().setJavaScriptEnabled(true); // bad
- webView.getSettings().setJavaScriptEnabled(false); // good
- webView.loadUrl("file:///android_asset/www/index.html");
- }
-
- // Test Suppress
- // Constructor: See issue 35588
- @SuppressLint("SetJavaScriptEnabled")
- public HelloWebApp() {
- WebView webView = (WebView)findViewById(R.id.webView);
- webView.getSettings().setJavaScriptEnabled(true); // bad
- webView.getSettings().setJavaScriptEnabled(false); // good
- webView.loadUrl("file:///android_asset/www/index.html");
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
deleted file mode 100644
index 862a59f..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-package test.pkg;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import android.content.Context;
-
-public class SparseLongArray extends Button {
- public void test() { // but only minSdkVersion >= 18
- Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt
deleted file mode 100644
index a09e44b..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class StringFormat2 extends Activity {
- public static final String buildUserAgent(Context context) {
- StringBuilder arg = new StringBuilder();
- // Snip
- final String base = context.getResources().getText(R.string.web_user_agent).toString();
- String ua = String.format(base, arg);
- return ua;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt
deleted file mode 100644
index 96c8d03..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.content.Context;
-
-import java.util.Locale;
-
-public class StringFormat3 extends Activity {
- public final void test(Context context) {
- Article article = new Article();
- String s1 = String.format(Locale.FRANCE,
- context.getString(R.string.gridview_views_count), article.playsCount);
- String s2 = String.format(Locale.FRANCE,
- context.getString(R.string.gridview_views_count), 5);
- String s3 = String.format(Locale.FRANCE,
- context.getString(R.string.gridview_views_count), "wrong");
- String s4 = String.format(context.getString(R.string.gridview_views_count), "wrong");
- String s5 = String.format(context.getString(R.string.gridview_views_count), 5); // OK
- String s6 = String.format(Locale.getDefault(),
- context.getString(R.string.gridview_views_count), 5);
- String s7 = String.format((Locale) null,
- context.getString(R.string.gridview_views_count), "string");
- }
-
- private static class Article {
- String playsCount;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt
deleted file mode 100644
index 88c3a54..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.content.Context;
-
-public class StringFormat4 extends Activity {
- public final void test(Context context) {
- // lint error:
- getString(R.string.error_and_source, getString(R.string.data_source));
- // no lint error:
- getString(R.string.error_and_source, "data source");
- String.format(getString(R.string.preferences_about_app_title), getString(R.string.app_name), "");
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt
deleted file mode 100644
index 8a75e7a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.content.Context;
-
-public class StringFormat5 extends Activity {
- public final void test(Context context) {
- Resources resources = getResources();
- String string = resources.getString(R.string.VibrationLevelIs, resources.getString(PolarPoint.textResourceForIPS()));
- System.out.println(string);
- }
-
- private static class PolarPoint {
- public static int textResourceForIPS() {
- return R.string.app_name;
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
deleted file mode 100644
index f2d0123..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.content.Context;
-
-public class StringFormat8 extends Activity {
- public final void test(Context context, float amount) {
- Resources resources = getResources();
- String amount1 = resources.getString(R.string.amount_string, amount);
- String amount2 = getResources().getString(R.string.amount_string, amount);
- String amount3 = String.format(getResources().getString(R.string.amount_string), amount);
- String amount4 = String.format(getResources().getString(R.string.amount_string)); // ERROR
- String amount5 = getResources().getString(R.string.amount_string, amount, amount); // ERROR
- String misc = String.format(resource.getString(R.string.percent_newline));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
deleted file mode 100644
index 49bad01..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-package test.pkg;
-
-import android.content.res.Resources;
-
-public class StringFormat9 {
- public String format(Resources resources, int percentUsed) {
- return resources.getString(R.string.toast_percent_copy_quota_used, percentUsed);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt
deleted file mode 100644
index c22828f..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class StringFormatActivity extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- String target = "World";
- String hello = getResources().getString(R.string.hello);
- String output1 = String.format(hello, target);
- String hello2 = getResources().getString(R.string.hello2);
- String output2 = String.format(hello2, target, "How are you");
- setContentView(R.layout.main);
- String score = getResources().getString(R.string.score);
- int points = 50;
- boolean won = true;
- String output3 = String.format(score, points);
- String output4 = String.format(score, true); // wrong
- String output4 = String.format(score, won); // wrong
- String output5 = String.format(score, 75);
- String.format(getResources().getString(R.string.hello2), target, "How are you");
- getResources().getString(hello2, target, "How are you");
- getResources().getString(R.string.hello2, target, "How are you");
- }
-
- // Test constructor handling (issue 35588)
- public StringFormatActivity() {
- String target = "World";
- String hello = getResources().getString(R.string.hello);
- String output1 = String.format(hello, target);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt
deleted file mode 100644
index c039dce..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class StringFormatActivity2 extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- String target = "World";
- getResources().getString(R.string.formattest1, "hello");
- getResources().getString(R.string.formattest2, "hello");
- getResources().getString(R.string.formattest3, 42);
- getResources().getString(R.string.formattest4, 42);
- getResources().getString(R.string.formattest5, "hello");
- getResources().getString(R.string.formattest6, "hello");
- getResources().getString(R.string.formattest7, "hello");
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt
deleted file mode 100644
index b0794c9..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class StringFormatActivity3 extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getResources().getString(R.string.format_90);
- getResources().getString(R.string.format_80);
- String.format(getResources().getString(R.string.format_90));
- String.format(getResources().getString(R.string.format_80));
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt
deleted file mode 100644
index 58717dc..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.annotations.tools.SuppressLint;
-
-public class StringFormatActivity extends Activity {
- /** Called when the activity is first created. */
- @SuppressLint("all")
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- String target = "World";
- String hello = getResources().getString(R.string.hello);
- String output1 = String.format(hello, target);
- String hello2 = getResources().getString(R.string.hello2);
- String output2 = String.format(hello2, target, "How are you");
- setContentView(R.layout.main);
- String score = getResources().getString(R.string.score);
- int points = 50;
- boolean won = true;
- String output3 = String.format(score, points);
- String output4 = String.format(score, true); // wrong
- String output4 = String.format(score, won); // wrong
- String output5 = String.format(score, 75);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
deleted file mode 100644
index d79e421..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
+++ /dev/null
@@ -1,63 +0,0 @@
-package test.pkg;
-
-import android.annotations.tools.SuppressLint;
-
- at SuppressWarnings("unused")
-public class SuppressTest5 {
- private String suppressVariable() {
- @SuppressLint("SdCardPath")
- String string = "/sdcard/mypath1";
- return string;
- }
-
- @SuppressLint("SdCardPath")
- private String suppressMethod() {
- String string = "/sdcard/mypath2";
- return string;
- }
-
- @SuppressLint("SdCardPath")
- private static class SuppressClass {
- private String suppressMethod() {
- String string = "/sdcard/mypath3";
- return string;
- }
- }
-
- private String suppressAll() {
- @SuppressLint("all")
- String string = "/sdcard/mypath4";
- return string;
- }
-
- private String suppressCombination() {
- @SuppressLint({"foo1", "foo2", "SdCardPath"})
- String string = "/sdcard/mypath5";
-
- // This is NOT annotated and *should* generate
- // a warning (here to make sure we don't just
- // suppress everything when we see an annotation
- String notAnnotated = "/sdcard/mypath";
-
- return string;
- }
-
- private String suppressWarnings() {
- @SuppressWarnings("all")
- String string = "/sdcard/mypath6";
-
- @SuppressWarnings("SdCardPath")
- String string2 = "/sdcard/mypath7";
-
- @SuppressWarnings("AndroidLintSdCardPath")
- String string3 = "/sdcard/mypath9";
-
- //noinspection AndroidLintSdCardPath
- String string4 = "/sdcard/mypath9";
-
- return string;
- }
-
- @SuppressLint("SdCardPath")
- private String supressField = "/sdcard/mypath8";
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt
deleted file mode 100644
index 503b4ba..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-package test.pkg;
-import android.content.ClipboardManager;
-import android.app.Activity;
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.service.wallpaper.WallpaperService;
-
-public class SystemServiceTest extends Activity {
-
- public void test1() {
- DisplayManager displayServiceOk = (DisplayManager) getSystemService(DISPLAY_SERVICE);
- DisplayManager displayServiceWrong = (DisplayManager) getSystemService(
- DEVICE_POLICY_SERVICE);
- WallpaperService wallPaperOk = (WallpaperService) getSystemService(WALLPAPER_SERVICE);
- WallpaperManager wallPaperWrong = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
- }
-
- public void test2(Context context) {
- DisplayManager displayServiceOk = (DisplayManager) context
- .getSystemService(DISPLAY_SERVICE);
- DisplayManager displayServiceWrong = (DisplayManager) context
- .getSystemService(DEVICE_POLICY_SERVICE);
- }
-
- public void clipboard() {
- ClipboardManager clipboard = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
- android.content.ClipboardManager clipboard = (android.content.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt
deleted file mode 100644
index 44ccaaa..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt
+++ /dev/null
@@ -1,51 +0,0 @@
-package foo.bar;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.widget.Toast;
-
-public class ToastTest {
- private Toast createToast(Context context) {
- // Don't warn here
- return Toast.makeText(context, "foo", Toast.LENGTH_LONG);
- }
-
- private void showToast(Context context) {
- // Don't warn here
- Toast toast = Toast.makeText(context, "foo", Toast.LENGTH_LONG);
- System.out.println("Other intermediate code here");
- int temp = 5 + 2;
- toast.show();
- }
-
- private void showToast2(Context context) {
- // Don't warn here
- int duration = Toast.LENGTH_LONG;
- Toast.makeText(context, "foo", Toast.LENGTH_LONG).show();
- Toast.makeText(context, R.string.app_name, duration).show();
- }
-
- private void broken(Context context) {
- // Errors
- Toast.makeText(context, "foo", Toast.LENGTH_LONG);
- Toast toast = Toast.makeText(context, R.string.app_name, 5000);
- toast.getDuration();
- }
-
- // Constructor test
- public ToastTest(Context context) {
- Toast.makeText(context, "foo", Toast.LENGTH_LONG);
- }
-
- @android.annotation.SuppressLint("ShowToast")
- private void checkSuppress1(Context context) {
- Toast toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG);
- }
-
- private void checkSuppress2(Context context) {
- @android.annotation.SuppressLint("ShowToast")
- Toast toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG);
- }
-}
-
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
deleted file mode 100644
index f5514bf..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
+++ /dev/null
@@ -1,155 +0,0 @@
-package test.pkg;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-
- at SuppressWarnings({"ConstantConditions", "UnusedDeclaration"})
-public abstract class ViewHolderTest extends BaseAdapter {
- @Override
- public int getCount() {
- return 0;
- }
-
- @Override
- public Object getItem(int position) {
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return 0;
- }
-
- public static class Adapter1 extends ViewHolderTest {
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return null;
- }
- }
-
- public static class Adapter2 extends ViewHolderTest {
- LayoutInflater mInflater;
-
- public View getView(int position, View convertView, ViewGroup parent) {
- // Should use View Holder pattern here
- convertView = mInflater.inflate(R.layout.your_layout, null);
-
- TextView text = (TextView) convertView.findViewById(R.id.text);
- text.setText("Position " + position);
-
- return convertView;
- }
- }
-
- public static class Adapter3 extends ViewHolderTest {
- LayoutInflater mInflater;
-
- public View getView(int position, View convertView, ViewGroup parent) {
- // Already using View Holder pattern
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.your_layout, null);
- }
-
- TextView text = (TextView) convertView.findViewById(R.id.text);
- text.setText("Position " + position);
-
- return convertView;
- }
- }
-
- public static class Adapter4 extends ViewHolderTest {
- LayoutInflater mInflater;
-
- public View getView(int position, View convertView, ViewGroup parent) {
- // Already using View Holder pattern
- //noinspection StatementWithEmptyBody
- if (convertView != null) {
- } else {
- convertView = mInflater.inflate(R.layout.your_layout, null);
- }
-
- TextView text = (TextView) convertView.findViewById(R.id.text);
- text.setText("Position " + position);
-
- return convertView;
- }
- }
-
- public static class Adapter5 extends ViewHolderTest {
- LayoutInflater mInflater;
-
- public View getView(int position, View convertView, ViewGroup parent) {
- // Already using View Holder pattern
- convertView = convertView == null ? mInflater.inflate(R.layout.your_layout, null) : convertView;
-
- TextView text = (TextView) convertView.findViewById(R.id.text);
- text.setText("Position " + position);
-
- return convertView;
- }
- }
-
- public static class Adapter6 extends ViewHolderTest {
- private Context mContext;
- private LayoutInflater mLayoutInflator;
- private ArrayList<Double> mLapTimes;
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (mLayoutInflator == null)
- mLayoutInflator = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- View v = convertView;
- if (v == null) v = mLayoutInflator.inflate(R.layout.your_layout, null);
-
- LinearLayout listItemHolder = (LinearLayout) v.findViewById(R.id.laptimes_list_item_holder);
- listItemHolder.removeAllViews();
-
- for (int i = 0; i < mLapTimes.size(); i++) {
- View lapItemView = mLayoutInflator.inflate(R.layout.laptime_item, null);
- if (i == 0) {
- TextView t = (TextView) lapItemView.findViewById(R.id.laptime_text);
- //t.setText(TimeUtils.createStyledSpannableString(mContext, mLapTimes.get(i), true));
- }
-
- TextView t2 = (TextView) lapItemView.findViewById(R.id.laptime_text2);
- if (i < mLapTimes.size() - 1 && mLapTimes.size() > 1) {
- double laptime = mLapTimes.get(i) - mLapTimes.get(i + 1);
- if (laptime < 0) laptime = mLapTimes.get(i);
- //t2.setText(TimeUtils.createStyledSpannableString(mContext, laptime, true));
- } else {
- //t2.setText(TimeUtils.createStyledSpannableString(mContext, mLapTimes.get(i), true));
- }
-
- listItemHolder.addView(lapItemView);
-
- }
- return v;
- }
- }
-
- public static class Adapter7 extends ViewHolderTest {
- LayoutInflater inflater;
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- View rootView = convertView;
- final int itemViewType = getItemViewType(position);
- switch (itemViewType) {
- case 0:
- if (rootView != null)
- return rootView;
- rootView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
- break;
- }
- return rootView;
- }
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt
deleted file mode 100644
index 1e6285b..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-package test.pkg;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.InputStream;
-import java.io.FileNotFoundException;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.app.Activity;
-import android.os.Bundle;
-
-public class WorldWriteableFile {
- File mFile;
- Context mContext;
-
- public void foo() {
- OutputStream out = null;
- SharedPreferences prefs = null;
-
- boolean success = false;
- try {
- out = openFileOutput(mFile.getName()); // ok
- out = openFileOutput(mFile.getName(), MODE_PRIVATE); // ok
- out = openFileOutput(mFile.getName(), MODE_WORLD_WRITEABLE);
- out = openFileOutput(mFile.getName(), MODE_WORLD_READABLE);
-
- prefs = getSharedPreferences(mContext, 0); // ok
- prefs = getSharedPreferences(mContext, MODE_PRIVATE); // ok
- prefs = getSharedPreferences(mContext, MODE_WORLD_WRITEABLE);
- prefs = getSharedPreferences(mContext, MODE_WORLD_READABLE);
- // Flickr.get().downloadPhoto(params[0], Flickr.PhotoSize.LARGE,
- // out);
- success = true;
- } catch (FileNotFoundException e) {
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
deleted file mode 100644
index 45743ce..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-package test.pkg;
-
-import android.annotation.SuppressLint;
-import android.view.View;
-
-public class WrongAnnotation {
- @Override
- @SuppressLint("NewApi") // Valid: class-file check on method
- public static void foobar(View view, @SuppressLint("NewApi") int foo) { // Invalid: class-file check
- @SuppressLint("NewApi") // Invalid
- boolean a;
- @SuppressLint({"SdCardPath", "NewApi"}) // Invalid: class-file based check on local variable
- boolean b;
- @android.annotation.SuppressLint({"SdCardPath", "NewApi"}) // Invalid (FQN)
- boolean c;
- @SuppressLint("SdCardPath") // Valid: AST-based check
- boolean d;
- }
-
- @SuppressLint("NewApi")
- private int field1;
-
- @SuppressLint("NewApi")
- private int field2 = 5;
-
- static {
- // Local variable outside method: invalid
- @SuppressLint("NewApi")
- int localvar = 5;
- }
-
- private static void test() {
- @SuppressLint("NewApi") // Invalid
- int a = View.MEASURED_STATE_MASK;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt
deleted file mode 100644
index 45e53d0..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-package test.pkg;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.*;
-
-public class WrongCastActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.casts);
- Button button = (Button) findViewById(R.id.button);
- ToggleButton toggleButton = (ToggleButton) findViewById(R.id.button);
- TextView textView = (TextView) findViewById(R.id.edittext);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt
deleted file mode 100644
index 7cd422a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-package test.pkg;
-
-import android.app.*;
-import android.view.*;
-import android.widget.*;
-
-public class WrongCastActivity2 extends Activity {
- private TextView additionalButton;
-
- private void configureAdditionalButton(View bodyView) {
- this.additionalButton = (TextView) bodyView
- .findViewById(R.id.additional);
- Object x = (AdapterView<?>) bodyView.findViewById(R.id.reminder_lead);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt
deleted file mode 100644
index 1701600..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-package test.pkg;
-
-import android.app.*;
-import android.view.*;
-import android.widget.*;
-
-public class WrongCastActivity3 extends Activity {
- private void test() {
- final Checkable check = (Checkable) findViewById(R.id.additional);
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
deleted file mode 100644
index 4a73420..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.checks.AbstractCheckTest;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.StringWriter;
-
- at SuppressWarnings("SpellCheckingInspection")
-public class JarFileIssueRegistryTest extends AbstractCheckTest {
- public void testError() {
- try {
- JarFileIssueRegistry.get(new TestLintClient(), new File("bogus"));
- fail("Expected exception for bogus path");
- } catch (Throwable t) {
- // pass
- }
- }
-
- public void testCached() throws Exception {
- File targetDir = Files.createTempDir();
- File file1 = getTestfile(targetDir, "rules/appcompat.jar.data");
- File file2 = getTestfile(targetDir, "apicheck/unsupported.jar.data");
- assertTrue(file1.getPath(), file1.exists());
- final StringWriter mLoggedWarnings = new StringWriter();
- TestLintClient client = new TestLintClient() {
- @Override
- public void log(@NonNull Severity severity, @Nullable Throwable exception,
- @Nullable String format, @Nullable Object... args) {
- if (format != null) {
- mLoggedWarnings.append(String.format(format, args));
- }
- }
-
- };
- IssueRegistry registry1 = JarFileIssueRegistry.get(client, file1);
- IssueRegistry registry2 = JarFileIssueRegistry.get(client, new File(file1.getPath()));
- assertSame(registry1, registry2);
- IssueRegistry registry3 = JarFileIssueRegistry.get(client, file2);
- assertNotSame(registry1, registry3);
-
- assertEquals(1, registry1.getIssues().size());
- assertEquals("AppCompatMethod", registry1.getIssues().get(0).getId());
-
- assertEquals(
- "Custom lint rule jar " + file2.getPath() + " does not contain a valid "
- + "registry manifest key (Lint-Registry).\n"
- + "Either the custom jar is invalid, or it uses an outdated API not "
- + "supported this lint client", mLoggedWarnings.toString());
- }
-
- @Override
- protected Detector getDetector() {
- fail("Not used in this test");
- return null;
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java
deleted file mode 100644
index c13ef11..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.util.concurrent.atomic.AtomicReference;
-
-import lombok.ast.CompilationUnit;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.VariableDefinitionEntry;
-
-public class ConstantEvaluatorTest extends TestCase {
- private static void check(Object expected, String source, final String targetVariable) {
- JavaContext context = LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
- assertNotNull(context);
- CompilationUnit unit = (CompilationUnit) context.getCompilationUnit();
- assertNotNull(unit);
-
- // Find the expression
- final AtomicReference<Expression> reference = new AtomicReference<Expression>();
- unit.accept(new ForwardingAstVisitor() {
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- if (node.astName().astValue().equals(targetVariable)) {
- reference.set(node.astInitializer());
- }
- return super.visitVariableDefinitionEntry(node);
- }
- });
- Expression expression = reference.get();
- Object actual = ConstantEvaluator.evaluate(context, expression);
- if (expected == null) {
- assertNull(actual);
- } else {
- assertNotNull("Couldn't compute value for " + source + ", expected " + expected,
- actual);
- assertEquals(expected.getClass(), actual.getClass());
- assertEquals(expected.toString(), actual.toString());
- }
- assertEquals(expected, actual);
- if (expected instanceof String) {
- assertEquals(expected, ConstantEvaluator.evaluateString(context, expression,
- false));
- }
- }
-
- private static void checkStatements(Object expected, String statementsSource,
- final String targetVariable) {
- String source = ""
- + "package test.pkg;\n"
- + "public class Test {\n"
- + " public void test() {\n"
- + " " + statementsSource + "\n"
- + " }\n"
- + " public static final int MY_INT_FIELD = 5;\n"
- + " public static final boolean MY_BOOLEAN_FIELD = true;\n"
- + " public static final String MY_STRING_FIELD = \"test\";\n"
- + "}\n";
-
- check(expected, source, targetVariable);
- }
-
- private static void checkExpression(Object expected, String expressionSource) {
- String source = ""
- + "package test.pkg;\n"
- + "public class Test {\n"
- + " public void test() {\n"
- + " Object expression = " + expressionSource + ";\n"
- + " }\n"
- + " public static final int MY_INT_FIELD = 5;\n"
- + " public static final boolean MY_BOOLEAN_FIELD = true;\n"
- + " public static final String MY_STRING_FIELD = \"test\";\n"
- + "}\n";
-
- check(expected, source, "expression");
- }
-
- public void testStrings() throws Exception {
- checkExpression(null, "null");
- checkExpression("hello", "\"hello\"");
- checkExpression("abcd", "\"ab\" + \"cd\"");
- }
-
- public void testBooleans() throws Exception {
- checkExpression(true, "true");
- checkExpression(false, "false");
- checkExpression(false, "false && true");
- checkExpression(true, "false || true");
- checkExpression(true, "!false");
- }
-
- public void testCasts() throws Exception {
- checkExpression(1, "(int)1");
- checkExpression(1L, "(long)1");
- checkExpression(1, "(int)1.1f");
- checkExpression((short)65537, "(short)65537");
- checkExpression((byte)1023, "(byte)1023");
- checkExpression(1.5, "(double)1.5f");
- checkExpression(-5.0, "(double)-5");
- }
-
- public void testArithmetic() throws Exception {
- checkExpression(1, "1");
- checkExpression(1L, "1L");
- checkExpression(4, "1 + 3");
- checkExpression(-2, "1 - 3");
- checkExpression(10, "2 * 5");
- checkExpression(2, "10 / 5");
- checkExpression(1, "11 % 5");
- checkExpression(8, "1 << 3");
- checkExpression(16, "32 >> 1");
- checkExpression(16, "32 >>> 1");
- checkExpression(5, "5 | 1");
- checkExpression(1, "5 & 1");
- checkExpression(~5, "~5");
- checkExpression(~(long)5, "~(long)5");
- checkExpression(~(short)5, "~(short)5");
- checkExpression(~(byte)5, "~(byte)5");
- checkExpression(-(long)5, "-(long)5");
- checkExpression(-(short)5, "-(short)5");
- checkExpression(-(byte)5, "-(byte)5");
- checkExpression(-(double)5, "-(double)5");
- checkExpression(-(float)5, "-(float)5");
- checkExpression(-2, "1 + -3");
-
- checkExpression(false, "11 == 5");
- checkExpression(true, "11 == 11");
- checkExpression(true, "11 != 5");
- checkExpression(false, "11 != 11");
- checkExpression(true, "11 > 5");
- checkExpression(false, "5 > 11");
- checkExpression(false, "11 < 5");
- checkExpression(true, "5 < 11");
- checkExpression(true, "11 >= 5");
- checkExpression(false, "5 >= 11");
- checkExpression(false, "11 <= 5");
- checkExpression(true, "5 <= 11");
-
- checkExpression(3.5f, "1.0f + 2.5f");
- }
-
- public void testFieldReferences() throws Exception {
- checkExpression(5, "MY_INT_FIELD");
- checkExpression("test", "MY_STRING_FIELD");
- checkExpression("prefix-test-postfix", "\"prefix-\" + MY_STRING_FIELD + \"-postfix\"");
- checkExpression(-4, "3 - (MY_INT_FIELD + 2)");
- }
-
- public void testStatements() throws Exception {
- checkStatements(9, ""
- + "int x = +5;\n"
- + "int y = x;\n"
- + "int w;\n"
- + "w = -1;\n"
- + "int z = x + 5 + w;\n",
- "z");
- checkStatements("hello world", ""
- + "String initial = \"hello\";\n"
- + "String other;\n"
- + "other = \" world\";\n"
- + "String finalString = initial + other;\n",
- "finalString");
- }
-
- public void testConditionals() throws Exception {
- checkStatements(-5, ""
- + "boolean condition = false;\n"
- + "condition = !condition;\n"
- + "int z = condition ? -5 : 4;\n",
- "z");
- checkStatements(-4, ""
- + "boolean condition = true && false;\n"
- + "int z = condition ? 5 : -4;\n",
- "z");
- }
-}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
deleted file mode 100644
index 6956445..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import static com.android.tools.lint.detector.api.LintUtils.computeResourceName;
-import static com.android.tools.lint.detector.api.LintUtils.convertVersion;
-import static com.android.tools.lint.detector.api.LintUtils.escapePropertyValue;
-import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
-import static com.android.tools.lint.detector.api.LintUtils.getFormattedParameters;
-import static com.android.tools.lint.detector.api.LintUtils.getLocaleAndRegion;
-import static com.android.tools.lint.detector.api.LintUtils.isImported;
-import static com.android.tools.lint.detector.api.LintUtils.splitPath;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.ApiVersion;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.tools.lint.EcjParser;
-import com.android.tools.lint.LintCliClient;
-import com.android.tools.lint.checks.BuiltinIssueRegistry;
-import com.android.tools.lint.client.api.JavaParser;
-import com.android.tools.lint.client.api.LintDriver;
-import com.google.common.collect.Iterables;
-
-import junit.framework.TestCase;
-
-import org.intellij.lang.annotations.Language;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStreamWriter;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Locale;
-
-import lombok.ast.Node;
-
- at SuppressWarnings("javadoc")
-public class LintUtilsTest extends TestCase {
- public void testPrintList() throws Exception {
- assertEquals("foo, bar, baz",
- LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 3));
- assertEquals("foo, bar, baz",
- LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 5));
-
- assertEquals("foo, bar, baz... (3 more)",
- LintUtils.formatList(
- Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 3));
- assertEquals("foo... (5 more)",
- LintUtils.formatList(
- Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 1));
- assertEquals("foo, bar, baz",
- LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 0));
- }
-
- public void testEndsWith() throws Exception {
- assertTrue(LintUtils.endsWith("Foo", ""));
- assertTrue(LintUtils.endsWith("Foo", "o"));
- assertTrue(LintUtils.endsWith("Foo", "oo"));
- assertTrue(LintUtils.endsWith("Foo", "Foo"));
- assertTrue(LintUtils.endsWith("Foo", "FOO"));
- assertTrue(LintUtils.endsWith("Foo", "fOO"));
-
- assertFalse(LintUtils.endsWith("Foo", "f"));
- }
-
- public void testStartsWith() throws Exception {
- assertTrue(LintUtils.startsWith("FooBar", "Bar", 3));
- assertTrue(LintUtils.startsWith("FooBar", "BAR", 3));
- assertTrue(LintUtils.startsWith("FooBar", "Foo", 0));
- assertFalse(LintUtils.startsWith("FooBar", "Foo", 2));
- }
-
- public void testIsXmlFile() throws Exception {
- assertTrue(LintUtils.isXmlFile(new File("foo.xml")));
- assertTrue(LintUtils.isXmlFile(new File("foo.Xml")));
- assertTrue(LintUtils.isXmlFile(new File("foo.XML")));
-
- assertFalse(LintUtils.isXmlFile(new File("foo.png")));
- assertFalse(LintUtils.isXmlFile(new File("xml")));
- assertFalse(LintUtils.isXmlFile(new File("xml.png")));
- }
-
- public void testGetBasename() throws Exception {
- assertEquals("foo", LintUtils.getBaseName("foo.png"));
- assertEquals("foo", LintUtils.getBaseName("foo.9.png"));
- assertEquals(".foo", LintUtils.getBaseName(".foo"));
- }
-
- public void testEditDistance() {
- assertEquals(0, LintUtils.editDistance("kitten", "kitten"));
-
- // editing kitten to sitting has edit distance 3:
- // replace k with s
- // replace e with i
- // append g
- assertEquals(3, LintUtils.editDistance("kitten", "sitting"));
-
- assertEquals(3, LintUtils.editDistance("saturday", "sunday"));
- assertEquals(1, LintUtils.editDistance("button", "bitton"));
- assertEquals(6, LintUtils.editDistance("radiobutton", "bitton"));
- }
-
- public void testSplitPath() throws Exception {
- assertTrue(Arrays.equals(new String[] { "/foo", "/bar", "/baz" },
- Iterables.toArray(splitPath("/foo:/bar:/baz"), String.class)));
-
- assertTrue(Arrays.equals(new String[] { "/foo", "/bar" },
- Iterables.toArray(splitPath("/foo;/bar"), String.class)));
-
- assertTrue(Arrays.equals(new String[] { "/foo", "/bar:baz" },
- Iterables.toArray(splitPath("/foo;/bar:baz"), String.class)));
-
- assertTrue(Arrays.equals(new String[] { "\\foo\\bar", "\\bar\\foo" },
- Iterables.toArray(splitPath("\\foo\\bar;\\bar\\foo"), String.class)));
-
- assertTrue(Arrays.equals(new String[] { "${sdk.dir}\\foo\\bar", "\\bar\\foo" },
- Iterables.toArray(splitPath("${sdk.dir}\\foo\\bar;\\bar\\foo"),
- String.class)));
-
- assertTrue(Arrays.equals(new String[] { "${sdk.dir}/foo/bar", "/bar/foo" },
- Iterables.toArray(splitPath("${sdk.dir}/foo/bar:/bar/foo"),
- String.class)));
-
- assertTrue(Arrays.equals(new String[] { "C:\\foo", "/bar" },
- Iterables.toArray(splitPath("C:\\foo:/bar"), String.class)));
- }
-
- public void testCommonParen1() {
- assertEquals(new File("/a"), (LintUtils.getCommonParent(
- new File("/a/b/c/d/e"), new File("/a/c"))));
- assertEquals(new File("/a"), (LintUtils.getCommonParent(
- new File("/a/c"), new File("/a/b/c/d/e"))));
-
- assertEquals(new File("/"), LintUtils.getCommonParent(
- new File("/foo/bar"), new File("/bar/baz")));
- assertEquals(new File("/"), LintUtils.getCommonParent(
- new File("/foo/bar"), new File("/")));
- assertNull(LintUtils.getCommonParent(
- new File("C:\\Program Files"), new File("F:\\")));
- assertNull(LintUtils.getCommonParent(
- new File("C:/Program Files"), new File("F:/")));
-
- assertEquals(new File("/foo/bar/baz"), LintUtils.getCommonParent(
- new File("/foo/bar/baz"), new File("/foo/bar/baz")));
- assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
- new File("/foo/bar/baz"), new File("/foo/bar")));
- assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
- new File("/foo/bar/baz"), new File("/foo/bar/foo")));
- assertEquals(new File("/foo"), LintUtils.getCommonParent(
- new File("/foo/bar"), new File("/foo/baz")));
- assertEquals(new File("/foo"), LintUtils.getCommonParent(
- new File("/foo/bar"), new File("/foo/baz")));
- assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
- new File("/foo/bar"), new File("/foo/bar/baz")));
- }
-
- public void testCommonParent2() {
- assertEquals(new File("/"), LintUtils.getCommonParent(
- Arrays.asList(new File("/foo/bar"), new File("/bar/baz"))));
- assertEquals(new File("/"), LintUtils.getCommonParent(
- Arrays.asList(new File("/foo/bar"), new File("/"))));
- assertNull(LintUtils.getCommonParent(
- Arrays.asList(new File("C:\\Program Files"), new File("F:\\"))));
- assertNull(LintUtils.getCommonParent(
- Arrays.asList(new File("C:/Program Files"), new File("F:/"))));
-
- assertEquals(new File("/foo"), LintUtils.getCommonParent(
- Arrays.asList(new File("/foo/bar"), new File("/foo/baz"))));
- assertEquals(new File("/foo"), LintUtils.getCommonParent(
- Arrays.asList(new File("/foo/bar"), new File("/foo/baz"),
- new File("/foo/baz/f"))));
- assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
- Arrays.asList(new File("/foo/bar"), new File("/foo/bar/baz"),
- new File("/foo/bar/foo2/foo3"))));
- }
-
- public void testStripIdPrefix() throws Exception {
- assertEquals("foo", LintUtils.stripIdPrefix("@+id/foo"));
- assertEquals("foo", LintUtils.stripIdPrefix("@id/foo"));
- assertEquals("foo", LintUtils.stripIdPrefix("foo"));
- }
-
- public void testIdReferencesMatch() throws Exception {
- assertTrue(LintUtils.idReferencesMatch("@+id/foo", "@+id/foo"));
- assertTrue(LintUtils.idReferencesMatch("@id/foo", "@id/foo"));
- assertTrue(LintUtils.idReferencesMatch("@id/foo", "@+id/foo"));
- assertTrue(LintUtils.idReferencesMatch("@+id/foo", "@id/foo"));
-
- assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/bar"));
- assertFalse(LintUtils.idReferencesMatch("@id/foo", "@+id/bar"));
- assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@id/bar"));
- assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/bar"));
-
- assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/foo1"));
- assertFalse(LintUtils.idReferencesMatch("@id/foo", "@id/foo1"));
- assertFalse(LintUtils.idReferencesMatch("@id/foo", "@+id/foo1"));
- assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@id/foo1"));
-
- assertFalse(LintUtils.idReferencesMatch("@+id/foo1", "@+id/foo"));
- assertFalse(LintUtils.idReferencesMatch("@id/foo1", "@id/foo"));
- assertFalse(LintUtils.idReferencesMatch("@id/foo1", "@+id/foo"));
- assertFalse(LintUtils.idReferencesMatch("@+id/foo1", "@id/foo"));
- }
-
- private static void checkEncoding(String encoding, boolean writeBom, String lineEnding)
- throws Exception {
- @SuppressWarnings("StringBufferReplaceableByString")
- StringBuilder sb = new StringBuilder();
-
- // Norwegian extra vowel characters such as "latin small letter a with ring above"
- String value = "\u00e6\u00d8\u00e5";
- String expected = "First line." + lineEnding + "Second line." + lineEnding
- + "Third line." + lineEnding + value + lineEnding;
- sb.append(expected);
- File file = File.createTempFile("getEncodingTest" + encoding + writeBom, ".txt");
- file.deleteOnExit();
- BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
- OutputStreamWriter writer = new OutputStreamWriter(stream, encoding);
-
- if (writeBom) {
- String normalized = encoding.toLowerCase(Locale.US).replace("-", "_");
- if (normalized.equals("utf_8")) {
- stream.write(0xef);
- stream.write(0xbb);
- stream.write(0xbf);
- } else if (normalized.equals("utf_16")) {
- stream.write(0xfe);
- stream.write(0xff);
- } else if (normalized.equals("utf_16le")) {
- stream.write(0xff);
- stream.write(0xfe);
- } else if (normalized.equals("utf_32")) {
- stream.write(0x0);
- stream.write(0x0);
- stream.write(0xfe);
- stream.write(0xff);
- } else if (normalized.equals("utf_32le")) {
- stream.write(0xff);
- stream.write(0xfe);
- stream.write(0x0);
- stream.write(0x0);
- } else {
- fail("Can't write BOM for encoding " + encoding);
- }
- }
- writer.write(sb.toString());
- writer.close();
-
- String s = LintUtils.getEncodedString(new LintCliClient(), file);
- assertEquals(expected, s);
- }
-
- public void testGetEncodedString() throws Exception {
- checkEncoding("utf-8", false /*bom*/, "\n");
- checkEncoding("UTF-8", false /*bom*/, "\n");
- checkEncoding("UTF_16", false /*bom*/, "\n");
- checkEncoding("UTF-16", false /*bom*/, "\n");
- checkEncoding("UTF_16LE", false /*bom*/, "\n");
-
- // Try BOM's
- checkEncoding("utf-8", true /*bom*/, "\n");
- checkEncoding("UTF-8", true /*bom*/, "\n");
- checkEncoding("UTF_16", true /*bom*/, "\n");
- checkEncoding("UTF-16", true /*bom*/, "\n");
- checkEncoding("UTF_16LE", true /*bom*/, "\n");
- checkEncoding("UTF_32", true /*bom*/, "\n");
- checkEncoding("UTF_32LE", true /*bom*/, "\n");
-
- // Make sure this works for \r and \r\n as well
- checkEncoding("UTF-16", false /*bom*/, "\r");
- checkEncoding("UTF_16LE", false /*bom*/, "\r");
- checkEncoding("UTF-16", false /*bom*/, "\r\n");
- checkEncoding("UTF_16LE", false /*bom*/, "\r\n");
- checkEncoding("UTF-16", true /*bom*/, "\r");
- checkEncoding("UTF_16LE", true /*bom*/, "\r");
- checkEncoding("UTF_32", true /*bom*/, "\r");
- checkEncoding("UTF_32LE", true /*bom*/, "\r");
- checkEncoding("UTF-16", true /*bom*/, "\r\n");
- checkEncoding("UTF_16LE", true /*bom*/, "\r\n");
- checkEncoding("UTF_32", true /*bom*/, "\r\n");
- checkEncoding("UTF_32LE", true /*bom*/, "\r\n");
- }
-
- public void testGetLocaleAndRegion() throws Exception {
- assertNull(getLocaleAndRegion(""));
- assertNull(getLocaleAndRegion("values"));
- assertNull(getLocaleAndRegion("values-xlarge-port"));
- assertEquals("en", getLocaleAndRegion("values-en"));
- assertEquals("pt-rPT", getLocaleAndRegion("values-pt-rPT-nokeys"));
- assertEquals("b+pt+PT", getLocaleAndRegion("values-b+pt+PT-nokeys"));
- assertEquals("zh-rCN", getLocaleAndRegion("values-zh-rCN-keyshidden"));
- assertEquals("ms", getLocaleAndRegion("values-ms-keyshidden"));
- }
-
- public void testIsImported() throws Exception {
- assertFalse(isImported(getCompilationUnit(
- "package foo.bar;\n" +
- "class Foo {\n" +
- "}\n"),
- "android.app.Activity"));
-
- assertTrue(isImported(getCompilationUnit(
- "package foo.bar;\n" +
- "import foo.bar.*;\n" +
- "import android.app.Activity;\n" +
- "import foo.bar.Baz;\n" +
- "class Foo {\n" +
- "}\n"),
- "android.app.Activity"));
-
- assertTrue(isImported(getCompilationUnit(
- "package foo.bar;\n" +
- "import android.app.Activity;\n" +
- "class Foo {\n" +
- "}\n"),
- "android.app.Activity"));
-
- assertTrue(isImported(getCompilationUnit(
- "package foo.bar;\n" +
- "import android.app.*;\n" +
- "class Foo {\n" +
- "}\n"),
- "android.app.Activity"));
-
- assertFalse(isImported(getCompilationUnit(
- "package foo.bar;\n" +
- "import android.app.*;\n" +
- "import foo.bar.Activity;\n" +
- "class Foo {\n" +
- "}\n"),
- "android.app.Activity"));
- }
-
- public void testComputeResourceName() {
- assertEquals("", computeResourceName("", ""));
- assertEquals("foo", computeResourceName("", "foo"));
- assertEquals("foo", computeResourceName("foo", ""));
- assertEquals("prefix_name", computeResourceName("prefix_", "name"));
- assertEquals("prefixName", computeResourceName("prefix", "name"));
- }
-
- public static Node getCompilationUnit(@Language("JAVA") String javaSource) {
- return getCompilationUnit(javaSource, new File("test"));
- }
-
-
- public static Node getCompilationUnit(@Language("JAVA") String javaSource, File relativePath) {
- JavaContext context = parse(javaSource, relativePath);
- return context.getCompilationUnit();
- }
-
- public static JavaContext parse(@Language("JAVA") final String javaSource,
- final File relativePath) {
- File dir = new File("projectDir");
- final File fullPath = new File(dir, relativePath.getPath());
- LintCliClient client = new LintCliClient() {
- @NonNull
- @Override
- public String readFile(@NonNull File file) {
- if (file.getPath().equals(fullPath.getPath())) {
- return javaSource;
- }
- return super.readFile(file);
- }
-
- @Nullable
- @Override
- public IAndroidTarget getCompileTarget(@NonNull Project project) {
- IAndroidTarget[] targets = getTargets();
- for (int i = targets.length - 1; i >= 0; i--) {
- IAndroidTarget target = targets[i];
- if (target.isPlatform()) {
- return target;
- }
- }
-
- return super.getCompileTarget(project);
- }
- };
- Project project = client.getProject(dir, dir);
-
- LintDriver driver = new LintDriver(new BuiltinIssueRegistry(),
- new LintCliClient());
- driver.setScope(Scope.JAVA_FILE_SCOPE);
- TestContext context = new TestContext(driver, client, project, javaSource, fullPath);
- JavaParser parser = new EcjParser(client, project);
- parser.prepareJavaParse(Collections.<JavaContext>singletonList(context));
- Node compilationUnit = parser.parseJava(context);
- assertNotNull(javaSource, compilationUnit);
- context.setCompilationUnit(compilationUnit);
- return context;
- }
-
- public void testConvertVersion() {
- assertEquals(new AndroidVersion(5, null), convertVersion(new DefaultApiVersion(5, null),
- null));
- assertEquals(new AndroidVersion(19, null), convertVersion(new DefaultApiVersion(19, null),
- null));
- //noinspection SpellCheckingInspection
- assertEquals(new AndroidVersion(18, "KITKAT"), // a preview platform API level is not final
- convertVersion(new DefaultApiVersion(0, "KITKAT"),
- null));
- }
-
- public void testIsModelOlderThan() throws Exception {
- AndroidProject project = mock(AndroidProject.class);
- when(project.getModelVersion()).thenReturn("0.10.4");
-
- assertTrue(LintUtils.isModelOlderThan(project, 0, 10, 5));
- assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 0));
- assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 4));
- assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
-
- project = mock(AndroidProject.class);
- when(project.getModelVersion()).thenReturn("0.11.0");
-
- assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
- assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
- assertFalse(LintUtils.isModelOlderThan(project, 0, 10, 4));
-
- project = mock(AndroidProject.class);
- when(project.getModelVersion()).thenReturn("0.11.5");
-
- assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
- assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
-
- project = mock(AndroidProject.class);
- when(project.getModelVersion()).thenReturn("1.0.0");
-
- assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 1));
- assertFalse(LintUtils.isModelOlderThan(project, 1, 0, 0));
- assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
- }
-
- private static final class DefaultApiVersion implements ApiVersion {
- private final int mApiLevel;
- private final String mCodename;
-
- public DefaultApiVersion(int apiLevel, @Nullable String codename) {
- mApiLevel = apiLevel;
- mCodename = codename;
- }
-
- @Override
- public int getApiLevel() {
- return mApiLevel;
- }
-
- @Nullable
- @Override
- public String getCodename() {
- return mCodename;
- }
-
- @NonNull
- @Override
- public String getApiString() {
- fail("Not needed in this test");
- return "<invalid>";
- }
- }
-
- public void testFindSubstring() {
- assertEquals("foo", findSubstring("foo", null, null));
- assertEquals("foo", findSubstring("foo ", null, " "));
- assertEquals("foo", findSubstring(" foo", " ", null));
- assertEquals("foo", findSubstring("[foo]", "[", "]"));
- }
-
- public void testGetFormattedParameters() {
- assertEquals(Arrays.asList("foo","bar"),
- getFormattedParameters("Prefix %1$s Divider %2$s Suffix",
- "Prefix foo Divider bar Suffix"));
- }
-
- public void testEscapePropertyValue() throws Exception {
- assertEquals("foo", escapePropertyValue("foo"));
- assertEquals("\\ foo ", escapePropertyValue(" foo "));
- assertEquals("c\\:/foo/bar", escapePropertyValue("c:/foo/bar"));
- assertEquals("\\!\\#\\:\\\\a\\\\b\\\\c", escapePropertyValue("!#:\\a\\b\\c"));
- assertEquals(
- "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo\\#foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
- escapePropertyValue("foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo#foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo"));
- }
-
- private static class TestContext extends JavaContext {
- private final String mJavaSource;
- public TestContext(LintDriver driver, LintCliClient client, Project project,
- String javaSource, File file) {
- //noinspection ConstantConditions
- super(driver, project,
- null, file, client.getJavaParser(null));
-
- mJavaSource = javaSource;
- }
-
- @Override
- @Nullable
- public String getContents() {
- return mJavaSource;
- }
- }
-}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java b/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java
deleted file mode 100644
index 1984c0a..0000000
--- a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.AUTO_URI;
-import static com.android.tools.lint.detector.api.TextFormat.HTML;
-import static com.android.tools.lint.detector.api.TextFormat.RAW;
-import static com.android.tools.lint.detector.api.TextFormat.TEXT;
-
-import junit.framework.TestCase;
-
-public class TextFormatTest extends TestCase {
- private static String convertMarkup(String raw, TextFormat to) {
- return RAW.convertTo(raw, to);
- }
-
- public void testConvertMarkup() throws Exception {
- assertEquals("", convertMarkup("", HTML));
-
- // Normal escapes
- assertEquals("foo bar", convertMarkup("foo bar", HTML));
- assertEquals("foo<br/>\nbar", convertMarkup("foo\nbar", HTML));
- assertEquals("foo<br/>\nbar", convertMarkup("foo\nbar", HTML));
- assertEquals("<&>'\"", convertMarkup("<&>'\"", HTML));
-
- // HTML Formatting
- assertEquals("<code>@TargetApi(11)</code>, ", convertMarkup("`@TargetApi(11)`, ",
- HTML));
- assertEquals("with <code>getArguments()</code>.",
- convertMarkup("with `getArguments()`.", HTML));
- assertEquals("(<code>dip</code>)", convertMarkup("(`dip`)", HTML));
- assertEquals(" <code>0dp</code> ", convertMarkup(" `0dp` ", HTML));
- assertEquals(
- "resources under <code>$ANDROID_SK/platforms/android-$VERSION/data/res/.</code>",
- convertMarkup(
- "resources under `$ANDROID_SK/platforms/android-$VERSION/data/res/.`",
- HTML));
- assertEquals("wrong format. Instead of <code>-keepclasseswithmembernames</code> use ",
- convertMarkup("wrong format. Instead of `-keepclasseswithmembernames` use ",
- HTML));
- assertEquals("<code>exported=false</code>)", convertMarkup("`exported=false`)",
- HTML));
- assertEquals("by setting <code>inputType=\"text\"</code>.",
- convertMarkup("by setting `inputType=\"text\"`.", HTML));
- assertEquals("* <code>View(Context context)</code><br/>\n",
- convertMarkup("* `View(Context context)`\n", HTML));
- assertEquals("The <code>@+id/</code> syntax", convertMarkup("The `@+id/` syntax",
- HTML));
- assertEquals("", convertMarkup("", HTML));
- assertEquals("", convertMarkup("", HTML));
- assertEquals("This is <b>bold</b>", convertMarkup("This is *bold*", HTML));
- assertEquals("Visit <a href=\"http://google.com\">http://google.com</a>.",
- convertMarkup("Visit http://google.com.", HTML));
- assertEquals("This is <code>monospace</code>!", convertMarkup("This is `monospace`!",
- HTML));
- assertEquals(
- "See <a href=\"http://developer.android.com/reference/android/view/" +
- "WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON\">http://developer." +
- "android.com/reference/android/view/WindowManager.LayoutParams.html#" +
- "FLAG_KEEP_SCREEN_ON</a>.",
- convertMarkup(
- "See http://developer.android.com/reference/android/view/WindowManager.Layout" +
- "Params.html#FLAG_KEEP_SCREEN_ON.", HTML));
-
- // Text formatting
- assertEquals("@TargetApi(11), ", convertMarkup("`@TargetApi(11)`, ", TEXT));
- assertEquals("with getArguments().", convertMarkup("with `getArguments()`.", TEXT));
- assertEquals("bold", convertMarkup("*bold*", TEXT));
- assertEquals("Visit http://google.com.", convertMarkup("Visit http://google.com.",
- TEXT));
-
- // Corners (match at the beginning and end)
- assertEquals("<b>bold</b>", convertMarkup("*bold*", HTML));
- assertEquals("<code>monospace</code>!", convertMarkup("`monospace`!", HTML));
-
- // Not formatting
- assertEquals("a*b", convertMarkup("a*b", HTML));
- assertEquals("a* b*", convertMarkup("a* b*", HTML));
- assertEquals("*a *b", convertMarkup("*a *b", HTML));
- assertEquals("Prefix is http:// ", convertMarkup("Prefix is http:// ", HTML));
- assertEquals("", convertMarkup("", HTML));
- assertEquals("", convertMarkup("", HTML));
- assertEquals("", convertMarkup("", HTML));
- assertEquals("", convertMarkup("", HTML));
- assertEquals("This is * not * bold", convertMarkup("This is * not * bold", HTML));
- assertEquals("* List item 1<br/>\n* List Item 2",
- convertMarkup("* List item 1\n* List Item 2", HTML));
- assertEquals("myhttp://foo.bar", convertMarkup("myhttp://foo.bar", HTML));
- }
-
- public void testConvertMarkup2() throws Exception {
- // http at the end:
- // Explanation from ManifestDetector#TARGET_NEWER
- String explanation =
- "When your application runs on a version of Android that is more recent than your " +
- "targetSdkVersion specifies that it has been tested with, various compatibility " +
- "modes kick in. This ensures that your application continues to work, but it may " +
- "look out of place. For example, if the targetSdkVersion is less than 14, your " +
- "app may get an option button in the UI.\n" +
- "\n" +
- "To fix this issue, set the targetSdkVersion to the highest available value. Then " +
- "test your app to make sure everything works correctly. You may want to consult " +
- "the compatibility notes to see what changes apply to each version you are adding " +
- "support for: " +
- "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html";
-
- assertEquals(
- "When your application runs on a version of Android that is more recent than your " +
- "targetSdkVersion specifies that it has been tested with, various compatibility " +
- "modes kick in. This ensures that your application continues to work, but it may " +
- "look out of place. For example, if the targetSdkVersion is less than 14, your " +
- "app may get an option button in the UI.<br/>\n" +
- "<br/>\n" +
- "To fix this issue, set the targetSdkVersion to the highest available value. Then " +
- "test your app to make sure everything works correctly. You may want to consult " +
- "the compatibility notes to see what changes apply to each version you are adding " +
- "support for: " +
- "<a href=\"http://developer.android.com/reference/android/os/Build.VERSION_CODES." +
- "html\">http://developer.android.com/reference/android/os/Build.VERSION_CODES.html" +
- "</a>",
- convertMarkup(explanation, HTML));
- }
-
- public void testConvertMarkup3() throws Exception {
- // embedded http markup test
- // Explanation from NamespaceDetector#CUSTOMVIEW
- String explanation =
- "When using a custom view with custom attributes in a library project, the layout " +
- "must use the special namespace " + AUTO_URI + " instead of a URI which includes " +
- "the library project's own package. This will be used to automatically adjust the " +
- "namespace of the attributes when the library resources are merged into the " +
- "application project.";
- assertEquals(
- "When using a custom view with custom attributes in a library project, the layout " +
- "must use the special namespace " +
- "<a href=\"http://schemas.android.com/apk/res-auto\">" +
- "http://schemas.android.com/apk/res-auto</a> " +
- "instead of a URI which includes the library project's own package. " +
- "This will be used to automatically adjust the namespace of the attributes when " +
- "the library resources are merged into the application project.",
- convertMarkup(explanation, HTML));
- }
-
- public void testConvertMarkup4() throws Exception {
- // monospace test
- String explanation =
- "The manifest should contain a `<uses-sdk>` element which defines the " +
- "minimum minimum API Level required for the application to run, " +
- "as well as the target version (the highest API level you have tested " +
- "the version for.)";
-
- assertEquals(
- "The manifest should contain a <code><uses-sdk></code> element which defines the " +
- "minimum minimum API Level required for the application to run, " +
- "as well as the target version (the highest API level you have tested " +
- "the version for.)",
- convertMarkup(explanation, HTML));
- }
-
- public void testConvertMarkup5() throws Exception {
- // monospace and bold test
- // From ManifestDetector#MULTIPLE_USES_SDK
- String explanation =
- "The `<uses-sdk>` element should appear just once; the tools will *not* merge the " +
- "contents of all the elements so if you split up the attributes across multiple " +
- "elements, only one of them will take effect. To fix this, just merge all the " +
- "attributes from the various elements into a single <uses-sdk> element.";
-
- assertEquals(
- "The <code><uses-sdk></code> element should appear just once; the tools " +
- "will <b>not</b> merge the " +
- "contents of all the elements so if you split up the attributes across multiple " +
- "elements, only one of them will take effect. To fix this, just merge all the " +
- "attributes from the various elements into a single <uses-sdk> element.",
- convertMarkup(explanation, HTML));
- }
-
- public void testConvertMarkup6() throws Exception {
- // Embedded code next to attributes
- // From AlwaysShowActionDetector#ISSUE
- String explanation =
- "Using `showAsAction=\"always\"` in menu XML, or `MenuItem.SHOW_AS_ACTION_ALWAYS` in "+
- "Java code is usually a deviation from the user interface style guide." +
- "Use `ifRoom` or the corresponding `MenuItem.SHOW_AS_ACTION_IF_ROOM` instead.\n" +
- "\n" +
- "If `always` is used sparingly there are usually no problems and behavior is " +
- "roughly equivalent to `ifRoom` but with preference over other `ifRoom` " +
- "items. Using it more than twice in the same menu is a bad idea.\n" +
- "\n" +
- "This check looks for menu XML files that contain more than two `always` " +
- "actions, or some `always` actions and no `ifRoom` actions. In Java code, " +
- "it looks for projects that contain references to `MenuItem.SHOW_AS_ACTION_ALWAYS` " +
- "and no references to `MenuItem.SHOW_AS_ACTION_IF_ROOM`.";
-
- assertEquals(
- "Using <code>showAsAction=\"always\"</code> in menu XML, or " +
- "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> in Java code is usually a deviation " +
- "from the user interface style guide.Use <code>ifRoom</code> or the " +
- "corresponding <code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code> instead.<br/>\n" +
- "<br/>\n" +
- "If <code>always</code> is used sparingly there are usually no problems and " +
- "behavior is roughly equivalent to <code>ifRoom</code> but with preference over " +
- "other <code>ifRoom</code> items. Using it more than twice in the same menu " +
- "is a bad idea.<br/>\n" +
- "<br/>\n" +
- "This check looks for menu XML files that contain more than two <code>always</code> " +
- "actions, or some <code>always</code> actions and no <code>ifRoom</code> actions. " +
- "In Java code, it looks for projects that contain references to " +
- "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> and no references to " +
- "<code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code>.",
- convertMarkup(explanation, HTML));
- }
-
- public void testConvertSelf() throws Exception {
- // No changes
- assertEquals("`foo`<b>", RAW.convertTo("`foo`<b>", RAW));
- assertEquals("`foo`<b>", TEXT.convertTo("`foo`<b>", TEXT));
- assertEquals("`foo`<b>", HTML.convertTo("`foo`<b>", HTML));
- }
-
- public void testConvertFromHtml() throws Exception {
- assertEquals(""
- + "Line 1\n"
- + "Line 2 <div>\n",
- HTML.convertTo("<html>Line 1<br>Line 2\n<!-- comment --><div></html>",
- TEXT));
- }
-
- public void testConvertFromHtml2() throws Exception {
- assertEquals(""
- + "Using showAsAction=\"always\" in menu XML, or\n"
- + "MenuItem.SHOW_AS_ACTION_ALWAYS in Java code is usually a\n"
- + "deviation from the user interface style guide.Use ifRoom or\n"
- + "the corresponding MenuItem.SHOW_AS_ACTION_IF_ROOM instead.\n"
- + "If always is used sparingly there are usually no problems\n"
- + "and behavior is roughly equivalent to ifRoom but with\n"
- + "preference over other ifRoom items. Using it more than twice\n"
- + "in the same menu is a bad idea. This check looks for menu\n"
- + "XML files that contain more than two always actions, or some\n"
- + "always actions and no ifRoom actions. In Java code, it looks\n"
- + "for projects that contain references to\n"
- + "MenuItem.SHOW_AS_ACTION_ALWAYS and no references to\n"
- + "MenuItem.SHOW_AS_ACTION_IF_ROOM.\n",
- HTML.convertTo(
- "Using <code>showAsAction=\"always\"</code> in menu XML, or " +
- "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> in Java code is usually a deviation " +
- "from the user interface style guide.Use <code>ifRoom</code> or the " +
- "corresponding <code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code> instead.<br/>\n" +
- "<br/>\n" +
- "If <code>always</code> is used sparingly there are usually no problems and " +
- "behavior is roughly equivalent to <code>ifRoom</code> but with preference over " +
- "other <code>ifRoom</code> items. Using it more than twice in the same menu " +
- "is a bad idea.<br/>\n" +
- "<br/>\n" +
- "This check looks for menu XML files that contain more than two <code>always</code> " +
- "actions, or some <code>always</code> actions and no <code>ifRoom</code> actions. " +
- "In Java code, it looks for projects that contain references to " +
- "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> and no references to " +
- "<code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code>.",
- TEXT));
- }
-
- public void testNbsp() throws Exception {
- assertEquals(" text", RAW.convertTo("\u00a0\u00A0text", HTML));
- }
-
- public void test181820() throws Exception {
- // Regression test for https://code.google.com/p/android/issues/detail?id=181820
- // Make sure we handle formatting characters at the end
- assertEquals("foo bar *", convertMarkup("foo bar *", HTML));
- }
-}
\ No newline at end of file
diff --git a/base/misc/api-generator/build.gradle b/base/misc/api-generator/build.gradle
deleted file mode 100755
index 754744b..0000000
--- a/base/misc/api-generator/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-apply plugin: 'application'
-apply plugin: 'java'
-
-mainClassName = "com.android.apigenerator.Main"
-applicationDefaultJvmArgs = ["-ea", "-Xms1048m", "-Xmx2048m"]
-
-repositories {
- jcenter()
-}
-
-sourceCompatibility = 1.6
-dependencies {
- compile 'com.android.tools:common:24.2.3'
- compile 'net.sf.kxml:kxml2:2.3.0'
- compile 'org.ow2.asm:asm:4.0'
- compile 'org.ow2.asm:asm-tree:4.0'
-}
-
-defaultTasks 'installApp'
diff --git a/base/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java b/base/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java
deleted file mode 100644
index c06e1fa..0000000
--- a/base/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apigenerator;
-
-import com.android.utils.Pair;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}.
- *
- */
-public class AndroidJarReader {
-
- private static final byte[] BUFFER = new byte[65535];
-
- private final int mMinApi;
- private final ArrayList<String> mPatterns;
-
- public AndroidJarReader(ArrayList<String> patterns, int minApi) {
- mPatterns = patterns;
- mMinApi = minApi;
- }
-
- public Map<String, ApiClass> getClasses() {
- HashMap<String, ApiClass> map = new HashMap<String, ApiClass>();
-
- // Get all the android.jar. They are in platforms-#
- int apiLevel = mMinApi - 1;
- while (true) {
- apiLevel++;
- try {
- File jar = getAndroidJarFile(apiLevel);
- if (jar == null || !jar.isFile()) {
- System.out.println("Last API level found: " + (apiLevel-1));
- break;
- }
- System.out.println("Found API " + apiLevel + " at " + jar.getPath());
-
- FileInputStream fis = new FileInputStream(jar);
- ZipInputStream zis = new ZipInputStream(fis);
- ZipEntry entry = zis.getNextEntry();
- while (entry != null) {
- String name = entry.getName();
-
- if (name.endsWith(".class")) {
-
- int index = 0;
- do {
- int size = zis.read(BUFFER, index, BUFFER.length - index);
- if (size >= 0) {
- index += size;
- } else {
- break;
- }
- } while (true);
-
- byte[] b = new byte[index];
- System.arraycopy(BUFFER, 0, b, 0, index);
-
- ClassReader reader = new ClassReader(b);
- ClassNode classNode = new ClassNode();
- reader.accept(classNode, 0 /*flags*/);
-
- ApiClass theClass = addClass(map, classNode.name, apiLevel,
- (classNode.access & Opcodes.ACC_DEPRECATED) != 0);
-
- // super class
- if (classNode.superName != null) {
- theClass.addSuperClass(classNode.superName, apiLevel);
- }
-
- // interfaces
- for (Object interfaceName : classNode.interfaces) {
- theClass.addInterface((String) interfaceName, apiLevel);
- }
-
- // fields
- for (Object field : classNode.fields) {
- FieldNode fieldNode = (FieldNode) field;
- if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) {
- continue;
- }
- if (!fieldNode.name.startsWith("this$") &&
- !fieldNode.name.equals("$VALUES")) {
- boolean deprecated = (fieldNode.access & Opcodes.ACC_DEPRECATED) != 0;
- theClass.addField(fieldNode.name, apiLevel, deprecated);
- }
- }
-
- // methods
- for (Object method : classNode.methods) {
- MethodNode methodNode = (MethodNode) method;
- if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) {
- continue;
- }
- if (!methodNode.name.equals("<clinit>")) {
- boolean deprecated = (methodNode.access & Opcodes.ACC_DEPRECATED) != 0;
- theClass.addMethod(methodNode.name + methodNode.desc, apiLevel, deprecated);
- }
- }
- }
- entry = zis.getNextEntry();
- }
-
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
-
- }
- }
-
- postProcessClasses(map);
-
- return map;
- }
-
- private File getAndroidJarFile(int apiLevel) {
- for (String pattern : mPatterns) {
- File f = new File(pattern.replace("%", Integer.toString(apiLevel)));
- if (f.isFile()) {
- return f;
- }
- }
- return null;
- }
-
- private void postProcessClasses(Map<String, ApiClass> classes) {
- for (ApiClass theClass : classes.values()) {
- Map<String, Integer> methods = theClass.getMethods();
- Map<String, Integer> fixedMethods = new HashMap<String, Integer>();
-
- List<Pair<String, Integer>> superClasses = theClass.getSuperClasses();
- List<Pair<String, Integer>> interfaces = theClass.getInterfaces();
-
- methodLoop: for (Entry<String, Integer> method : methods.entrySet()) {
- String methodName = method.getKey();
- int apiLevel = method.getValue();
-
- if (!methodName.startsWith("<init>(")) {
-
- for (Pair<String, Integer> parent : superClasses) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass parentClass = classes.get(parent.getFirst());
- assert parentClass != null;
- if (parentClass != null &&
- checkClassContains(theClass.getName(),
- methodName, apiLevel,
- classes, parentClass)) {
- continue methodLoop;
- }
- }
- }
-
- for (Pair<String, Integer> parent : interfaces) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass parentClass = classes.get(parent.getFirst());
- assert parentClass != null;
- if (parentClass != null &&
- checkClassContains(theClass.getName(),
- methodName, apiLevel,
- classes, parentClass)) {
- continue methodLoop;
- }
- }
- }
- }
-
- // if we reach here. the method isn't an override
- fixedMethods.put(methodName, method.getValue());
- }
-
- theClass.replaceMethods(fixedMethods);
- }
- }
-
- private boolean checkClassContains(String className, String methodName, int apiLevel,
- Map<String, ApiClass> classMap, ApiClass parentClass) {
-
- Integer parentMethodApiLevel = parentClass.getMethods().get(methodName);
- if (parentMethodApiLevel != null && parentMethodApiLevel <= apiLevel) {
- // the parent class has the method and it was introduced in the parent at the
- // same api level as the method, or before.
- return true;
- }
-
- // check on this class parents.
- List<Pair<String, Integer>> superClasses = parentClass.getSuperClasses();
- List<Pair<String, Integer>> interfaces = parentClass.getInterfaces();
-
- for (Pair<String, Integer> parent : superClasses) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass superParentClass = classMap.get(parent.getFirst());
- assert superParentClass != null;
- if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
- classMap, superParentClass)) {
- return true;
- }
- }
- }
-
- for (Pair<String, Integer> parent : interfaces) {
- // only check the parent if it was a parent class at the introduction
- // of the method.
- if (parent.getSecond() <= apiLevel) {
- ApiClass superParentClass = classMap.get(parent.getFirst());
- assert superParentClass != null;
- if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
- classMap, superParentClass)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private ApiClass addClass(HashMap<String, ApiClass> classes, String name, int apiLevel, boolean deprecated) {
- ApiClass theClass = classes.get(name);
- if (theClass == null) {
- theClass = new ApiClass(name, apiLevel);
- classes.put(name, theClass);
- }
-
- if (deprecated && apiLevel < theClass.deprecatedIn) {
- theClass.deprecatedIn = apiLevel;
- }
-
- return theClass;
- }
-}
diff --git a/base/misc/api-generator/src/main/java/com/android/apigenerator/Main.java b/base/misc/api-generator/src/main/java/com/android/apigenerator/Main.java
deleted file mode 100644
index 2ca0c44..0000000
--- a/base/misc/api-generator/src/main/java/com/android/apigenerator/Main.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apigenerator;
-
-import java.io.File;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Main class for command line command to convert the existing API XML/TXT files into diff-based
- * simple text files.
- */
-public class Main {
-
- public static void main(String[] args) {
-
- boolean error = false;
- int minApi = 1;
- ArrayList<String> patterns = new ArrayList<String>();
- String outPath = null;
-
- for (int i = 0; i < args.length && !error; i++) {
- String arg = args[i];
-
- if (arg.equals("--pattern")) {
- i++;
- if (i < args.length) {
- patterns.add(args[i]);
- } else {
- System.err.println("Missing argument after " + arg);
- error = true;
- }
-
- } else if (arg.equals("--min-api")) {
- i++;
- if (i < args.length) {
- minApi = Integer.parseInt(args[i]);
- } else {
- System.err.println("Missing number >= 1 after " + arg);
- error = true;
- }
- } else if (arg.length() >= 2 && arg.substring(0, 2).equals("--")) {
- System.err.println("Unknown argument: " + arg);
- error = true;
-
- } else if (outPath == null) {
- outPath = arg;
-
- } else if (new File(arg).isDirectory()) {
- String pattern = arg;
- if (!pattern.endsWith(File.separator)) {
- pattern += File.separator;
- }
- pattern += "platforms" + File.separator + "android-%" + File.separator + "android.jar";
- patterns.add(pattern);
-
- } else {
- System.err.println("Unknown argument: " + arg);
- error = true;
- }
- }
-
- if (!error && outPath == null) {
- System.err.println("Missing out file path");
- error = true;
- }
-
- if (!error && patterns.isEmpty()) {
- System.err.println("Missing SdkFolder or --pattern.");
- error = true;
- }
-
- if (error) {
- printUsage();
- System.exit(1);
- }
-
- AndroidJarReader reader = new AndroidJarReader(patterns, minApi);
- Map<String, ApiClass> classes = reader.getClasses();
- if (!createApiFile(new File(outPath), classes)) {
- System.exit(1);
- }
- }
-
- private static void printUsage() {
- System.err.println("\nGenerates a single API file from the content of an SDK.");
- System.err.println("Usage:");
- System.err.println("\tApiCheck [--min-api=1] OutFile [SdkFolder | --pattern sdk/%/android.jar]+");
- System.err.println("Options:");
- System.err.println("--min-api <int> : The first API level to consider (>=1).");
- System.err.println("--pattern : Path pattern to find per-API android.jar files, where\n" +
- " '%' is replacedby the API level.");
- System.err.println("SdkFolder: if given, this adds the pattern\n" +
- " '$SdkFolder/platforms/android-%/android.jar'");
- System.err.println("If multiple --pattern are specified, they are tried in the order given.\n");
- }
-
- /**
- * Creates the simplified diff-based API level.
- * @param outFile the output file
- * @param classes the classes to write
- */
- private static boolean createApiFile(File outFile, Map<String, ApiClass> classes) {
-
- PrintStream ps = null;
- try {
- ps = new PrintStream(outFile, "UTF-8");
- ps.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
- ps.println("<api version=\"2\">");
- TreeMap<String, ApiClass> map = new TreeMap<String, ApiClass>(classes);
- for (ApiClass theClass : map.values()) {
- (theClass).print(ps);
- }
- ps.println("</api>");
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- } finally {
- if (ps != null) {
- ps.close();
- }
- }
-
- return true;
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/ArrayInstance.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/ArrayInstance.java
deleted file mode 100644
index 23774dc..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/ArrayInstance.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.tools.perflib.heap.io.HprofBuffer;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-
-public class ArrayInstance extends Instance {
-
- private final Type mType;
-
- private final int mLength;
-
- private final long mValuesOffset;
-
- public ArrayInstance(long id, @NonNull StackTrace stack, @NonNull Type type, int length,
- long valuesOffset) {
- super(id, stack);
- mType = type;
- mLength = length;
- mValuesOffset = valuesOffset;
- }
-
- @NonNull
- public Object[] getValues() {
- Object[] values = new Object[mLength];
-
- getBuffer().setPosition(mValuesOffset);
- for (int i = 0; i < mLength; i++) {
- values[i] = readValue(mType);
- }
- return values;
- }
-
- @NonNull
- private byte[] asRawByteArray(int start, int elementCount) {
- getBuffer().setPosition(mValuesOffset);
- assert mType != Type.OBJECT;
- assert start + elementCount <= mLength;
- byte[] bytes = new byte[elementCount * mType.getSize()];
- getBuffer().readSubSequence(bytes, start * mType.getSize(), elementCount * mType.getSize());
- return bytes;
- }
-
- @NonNull
- public char[] asCharArray(int offset, int length) {
- assert mType == Type.CHAR;
- // TODO: Make this copy less by supporting offset in asRawByteArray.
- CharBuffer charBuffer = ByteBuffer.wrap(asRawByteArray(offset, length)).order(HprofBuffer.HPROF_BYTE_ORDER).asCharBuffer();
- char[] result = new char[length];
- charBuffer.get(result);
- return result;
- }
-
- @Override
- public final int getSize() {
- // TODO: Take the rest of the fields into account: length, type, etc (~16 bytes).
- return mLength * mHeap.mSnapshot.getTypeSize(mType);
- }
-
- @Override
- public final void accept(@NonNull Visitor visitor) {
- visitor.visitArrayInstance(this);
- if (mType == Type.OBJECT) {
- for (Object value : getValues()) {
- if (value instanceof Instance) {
- if (!mReferencesAdded) {
- ((Instance)value).addReference(null, this);
- }
- visitor.visitLater(this, (Instance)value);
- }
- }
- mReferencesAdded = true;
- }
- }
-
- @Override
- public ClassObj getClassObj() {
- if (mType == Type.OBJECT) {
- return super.getClassObj();
- } else {
- // Primitive arrays don't set their classId, we need to do the lookup manually.
- return mHeap.mSnapshot.findClass(Type.getClassNameOfPrimitiveArray(mType));
- }
- }
-
- public Type getArrayType() {
- return mType;
- }
-
- public final String toString() {
- String className = getClassObj().getClassName();
- if (className.endsWith("[]")) {
- className = className.substring(0, className.length() - 2);
- }
- return String.format("%s[%d]@%d (0x%x)", className, mLength, getUniqueId(), getUniqueId());
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/ClassInstance.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/ClassInstance.java
deleted file mode 100644
index 562ab48..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/ClassInstance.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ClassInstance extends Instance {
-
- private final long mValuesOffset;
-
- public ClassInstance(long id, @NonNull StackTrace stack, long valuesOffset) {
- super(id, stack);
- mValuesOffset = valuesOffset;
- }
-
- @VisibleForTesting
- @NonNull
- List<FieldValue> getFields(String name) {
- ArrayList<FieldValue> result = new ArrayList<FieldValue>();
- for (FieldValue value : getValues()) {
- if (value.getField().getName().equals(name)) {
- result.add(value);
- }
- }
- return result;
- }
-
- @NonNull
- public List<FieldValue> getValues() {
- ArrayList<FieldValue> result = new ArrayList<FieldValue>();
-
- ClassObj clazz = getClassObj();
- getBuffer().setPosition(mValuesOffset);
- while (clazz != null) {
- for (Field field : clazz.getFields()) {
- result.add(new FieldValue(field, readValue(field.getType())));
- }
- clazz = clazz.getSuperClassObj();
- }
- return result;
- }
-
- @Override
- public final void accept(@NonNull Visitor visitor) {
- visitor.visitClassInstance(this);
- for (FieldValue field : getValues()) {
- if (field.getValue() instanceof Instance) {
- if (!mReferencesAdded) {
- ((Instance) field.getValue()).addReference(field.getField(), this);
- }
- visitor.visitLater(this, (Instance) field.getValue());
- }
- }
- mReferencesAdded = true;
- }
-
- @Override
- public boolean getIsSoftReference() {
- return getClassObj().getIsSoftReference();
- }
-
- public final String toString() {
- return String.format("%s@%d (0x%x)", getClassObj().getClassName(), getUniqueId(), getUniqueId());
- }
-
- public static class FieldValue {
- private Field mField;
- private Object mValue;
-
- public FieldValue(@NonNull Field field, @Nullable Object value) {
- this.mField = field;
- this.mValue = value;
- }
-
- public Field getField() {
- return mField;
- }
-
- public Object getValue() {
- return mValue;
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/ClassObj.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/ClassObj.java
deleted file mode 100644
index 121c27c..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/ClassObj.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import gnu.trove.TIntObjectHashMap;
-
-import java.util.*;
-
-public class ClassObj extends Instance implements Comparable<ClassObj> {
- public static class HeapData {
- public int mShallowSize = 0;
-
- public List<Instance> mInstances = new ArrayList<Instance>();
- }
-
- @NonNull
- final String mClassName;
-
- private final long mStaticFieldsOffset;
-
- long mSuperClassId;
-
- long mClassLoaderId;
-
- Field[] mFields;
-
- Field[] mStaticFields;
-
- private int mInstanceSize;
-
- private boolean mIsSoftReference = false;
-
- @NonNull
- TIntObjectHashMap<HeapData> mHeapData = new TIntObjectHashMap<HeapData>();
-
- @NonNull
- Set<ClassObj> mSubclasses = new HashSet<ClassObj>();
-
- public ClassObj(long id, @NonNull StackTrace stack, @NonNull String className,
- long staticFieldsOffset) {
- super(id, stack);
- mClassName = className;
- mStaticFieldsOffset = staticFieldsOffset;
- }
-
- public final void addSubclass(ClassObj subclass) {
- mSubclasses.add(subclass);
- }
-
- @NonNull
- public final Set<ClassObj> getSubclasses() {
- return mSubclasses;
- }
-
- public final void dumpSubclasses() {
- for (ClassObj subclass : mSubclasses) {
- System.out.println(" " + subclass.mClassName);
- }
- }
-
- @NonNull
- public final String toString() {
- return mClassName.replace('/', '.');
- }
-
- public final void addInstance(int heapId, @NonNull Instance instance) {
- if (instance instanceof ClassInstance) {
- instance.setSize(mInstanceSize);
- }
-
- HeapData heapData = mHeapData.get(heapId);
- if (heapData == null) {
- heapData = new HeapData();
- mHeapData.put(heapId, heapData);
- }
- heapData.mInstances.add(instance);
- heapData.mShallowSize += instance.getSize();
- }
-
- public final void setSuperClassId(long superClass) {
- mSuperClassId = superClass;
- }
-
- public final void setClassLoaderId(long classLoader) {
- mClassLoaderId = classLoader;
- }
-
- public int getAllFieldsCount() {
- int result = 0;
- ClassObj clazz = this;
- while (clazz != null) {
- result += clazz.getFields().length;
- clazz = clazz.getSuperClassObj();
- }
- return result;
- }
-
- public Field[] getFields() {
- return mFields;
- }
-
- public void setFields(@NonNull Field[] fields) {
- mFields = fields;
- }
-
- public void setStaticFields(@NonNull Field[] staticFields) {
- mStaticFields = staticFields;
- }
-
- public void setInstanceSize(int size) {
- mInstanceSize = size;
- }
-
- public int getInstanceSize() {
- return mInstanceSize;
- }
-
- public int getShallowSize(int heapId) {
- HeapData heapData = mHeapData.get(heapId);
- return heapData == null ? 0 : mHeapData.get(heapId).mShallowSize;
- }
-
- public void setIsSoftReference() {
- mIsSoftReference = true;
- }
-
- @Override
- public boolean getIsSoftReference() {
- return mIsSoftReference;
- }
-
- @NonNull
- public Map<Field, Object> getStaticFieldValues() {
- Map<Field, Object> result = new HashMap<Field, Object>();
- getBuffer().setPosition(mStaticFieldsOffset);
-
- int numEntries = readUnsignedShort();
- for (int i = 0; i < numEntries; i++) {
- Field f = mStaticFields[i];
-
- readId();
- readUnsignedByte();
-
- Object value = readValue(f.getType());
- result.put(f, value);
- }
- return result;
- }
-
- public final void dump() {
- System.out.println("+---------- ClassObj dump for: " + mClassName);
-
- System.out.println("+----- Static fields");
- Map<Field, Object> staticFields = getStaticFieldValues();
- for (Field field : staticFields.keySet()) {
- System.out.println(field.getName() + ": " + field.getType() + " = "
- + staticFields.get(field));
- }
-
- System.out.println("+----- Instance fields");
- for (Field field : mFields) {
- System.out.println(field.getName() + ": " + field.getType());
- }
- if (getSuperClassObj() != null) {
- getSuperClassObj().dump();
- }
- }
-
- @NonNull
- public final String getClassName() {
- return mClassName;
- }
-
- @Override
- public final void accept(@NonNull Visitor visitor) {
- visitor.visitClassObj(this);
- for (Map.Entry<Field, Object> entry : getStaticFieldValues().entrySet()) {
- Object value = entry.getValue();
- if (value instanceof Instance) {
- if (!mReferencesAdded) {
- ((Instance)value).addReference(entry.getKey(), this);
- }
- visitor.visitLater(this, (Instance)value);
- }
- }
- mReferencesAdded = true;
- }
-
- @Override
- public final int compareTo(@NonNull ClassObj o) {
- if (getId() == o.getId()) {
- return 0;
- }
-
- int nameCompareResult = mClassName.compareTo(o.mClassName);
- if (nameCompareResult != 0) {
- return nameCompareResult;
- }
- else {
- return getId() - o.getId() > 0 ? 1 : -1;
- }
- }
-
- public final boolean equals(Object o) {
- if (!(o instanceof ClassObj)) {
- return false;
- }
-
- return 0 == compareTo((ClassObj) o);
- }
-
- @Override
- public int hashCode() {
- return mClassName.hashCode();
- }
-
- @VisibleForTesting
- Object getStaticField(Type type, String name) {
- return getStaticFieldValues().get(new Field(type, name));
- }
-
- public ClassObj getSuperClassObj() {
- return mHeap.mSnapshot.findClass(mSuperClassId);
- }
-
- @Nullable
- public Instance getClassLoader() {
- return mHeap.mSnapshot.findInstance(mClassLoaderId);
- }
-
- public List<Instance> getInstancesList() {
- int count = getInstanceCount();
- ArrayList<Instance> resultList = new ArrayList<Instance>(count);
- for (int heapId : mHeapData.keys()) {
- resultList.addAll(getHeapInstances(heapId));
- }
- return resultList;
- }
-
- @NonNull
- public List<Instance> getHeapInstances(int heapId) {
- HeapData result = mHeapData.get(heapId);
- return result == null ? new ArrayList<Instance>(0) : result.mInstances;
- }
-
- public int getHeapInstancesCount(int heapId) {
- HeapData result = mHeapData.get(heapId);
- return result == null ? 0 : result.mInstances.size();
- }
-
- public int getInstanceCount() {
- int count = 0;
- for (Object heapStat : mHeapData.getValues()) {
- count += ((HeapData)heapStat).mInstances.size();
- }
- return count;
- }
-
- public int getShallowSize() {
- int size = 0;
- for (Object heapStat : mHeapData.getValues()) {
- size += ((HeapData)heapStat).mShallowSize;
- }
- return size;
- }
-
- @NonNull
- public static String getReferenceClassName() {
- return "java.lang.ref.Reference";
- }
-
- @NonNull
- public List<ClassObj> getDescendantClasses() {
- List<ClassObj> descendants = new ArrayList<ClassObj>();
-
- Stack<ClassObj> searchStack = new Stack<ClassObj>();
- searchStack.push(this);
-
- while (!searchStack.isEmpty()) {
- ClassObj classObj = searchStack.pop();
- descendants.add(classObj);
- for (ClassObj subClass : classObj.getSubclasses()) {
- searchStack.push(subClass);
- }
- }
-
- return descendants;
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Heap.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/Heap.java
deleted file mode 100644
index 3d617e6..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/Heap.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import com.google.common.collect.*;
-import gnu.trove.TIntObjectHashMap;
-import gnu.trove.TLongObjectHashMap;
-import gnu.trove.TObjectProcedure;
-
-public class Heap {
-
- private final int mId;
-
- @NonNull
- private final String mName;
-
- // List of individual stack frames
- @NonNull
- TLongObjectHashMap<StackFrame> mFrames = new TLongObjectHashMap<StackFrame>();
-
- // List stack traces, which are lists of stack frames
- @NonNull
- TIntObjectHashMap<StackTrace> mTraces = new TIntObjectHashMap<StackTrace>();
-
- // Root objects such as interned strings, jni locals, etc
- @NonNull
- ArrayList<RootObj> mRoots = new ArrayList<RootObj>();
-
- // List of threads
- @NonNull
- TIntObjectHashMap<ThreadObj> mThreads = new TIntObjectHashMap<ThreadObj>();
-
- // Class definitions
- @NonNull
- TLongObjectHashMap<ClassObj> mClassesById = new TLongObjectHashMap<ClassObj>();
-
- @NonNull Multimap<String, ClassObj> mClassesByName = ArrayListMultimap.create();
-
- // List of instances of above class definitions
- private final TLongObjectHashMap<Instance> mInstances = new TLongObjectHashMap<Instance>();
-
- // The snapshot that this heap is part of
- Snapshot mSnapshot;
-
- public Heap(int id, @NonNull String name) {
- mId = id;
- mName = name;
- }
-
- public int getId() {
- return mId;
- }
-
- @NonNull
- public String getName() {
- return mName;
- }
-
- public final void addStackFrame(@NonNull StackFrame theFrame) {
- mFrames.put(theFrame.mId, theFrame);
- }
-
- public final StackFrame getStackFrame(long id) {
- return mFrames.get(id);
- }
-
- public final void addStackTrace(@NonNull StackTrace theTrace) {
- mTraces.put(theTrace.mSerialNumber, theTrace);
- }
-
- public final StackTrace getStackTrace(int traceSerialNumber) {
- return mTraces.get(traceSerialNumber);
- }
-
- public final StackTrace getStackTraceAtDepth(int traceSerialNumber,
- int depth) {
- StackTrace trace = mTraces.get(traceSerialNumber);
-
- if (trace != null) {
- trace = trace.fromDepth(depth);
- }
-
- return trace;
- }
-
- public final void addRoot(@NonNull RootObj root) {
- root.mIndex = mRoots.size();
- mRoots.add(root);
- }
-
- public final void addThread(ThreadObj thread, int serialNumber) {
- mThreads.put(serialNumber, thread);
- }
-
- public final ThreadObj getThread(int serialNumber) {
- return mThreads.get(serialNumber);
- }
-
- public final void addInstance(long id, Instance instance) {
- mInstances.put(id, instance);
- }
-
- public final Instance getInstance(long id) {
- return mInstances.get(id);
- }
-
- public final void addClass(long id, @NonNull ClassObj theClass) {
- mClassesById.put(id, theClass);
- mClassesByName.put(theClass.mClassName, theClass);
- }
-
- public final ClassObj getClass(long id) {
- return mClassesById.get(id);
- }
-
- public final ClassObj getClass(String name) {
- Collection<ClassObj> classes = mClassesByName.get(name);
- if (classes.size() == 1) {
- return classes.iterator().next();
- }
- return null;
- }
-
- public final Collection<ClassObj> getClasses(String name) {
- return mClassesByName.get(name);
- }
-
- public final void dumpInstanceCounts() {
- for (Object value : mClassesById.getValues()) {
- ClassObj theClass = (ClassObj) value;
- int count = theClass.getInstanceCount();
-
- if (count > 0) {
- System.out.println(theClass + ": " + count);
- }
- }
- }
-
- public final void dumpSubclasses() {
- for (Object value : mClassesById.getValues()) {
- ClassObj theClass = (ClassObj) value;
- int count = theClass.mSubclasses.size();
-
- if (count > 0) {
- System.out.println(theClass);
- theClass.dumpSubclasses();
- }
- }
- }
-
- public final void dumpSizes() {
- for (Object value : mClassesById.getValues()) {
- ClassObj theClass = (ClassObj) value;
-
- int size = 0;
-
- for (Instance instance : theClass.getHeapInstances(getId())) {
- size += instance.getCompositeSize();
- }
-
- if (size > 0) {
- System.out.println(theClass + ": base " + theClass.getSize()
- + ", composite " + size);
- }
- }
- }
-
- @NonNull
- public Collection<ClassObj> getClasses() {
- return mClassesByName.values();
- }
-
- @NonNull
- public Collection<Instance> getInstances() {
- final ArrayList<Instance> result = new ArrayList<Instance>(mInstances.size());
- mInstances.forEachValue(new TObjectProcedure<Instance>() {
- @Override
- public boolean execute(Instance instance) {
- result.add(instance);
- return true;
- }
- });
- return result;
- }
-
- public int getInstancesCount() {
- return mInstances.size();
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/HprofParser.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/HprofParser.java
deleted file mode 100644
index 80271ab..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/HprofParser.java
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.tools.perflib.heap.io.HprofBuffer;
-import com.google.common.primitives.UnsignedBytes;
-import com.google.common.primitives.UnsignedInts;
-
-import java.io.EOFException;
-import java.io.IOException;
-
-import gnu.trove.TLongObjectHashMap;
-
-public class HprofParser {
-
- private static final int STRING_IN_UTF8 = 0x01;
-
- private static final int LOAD_CLASS = 0x02;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int UNLOAD_CLASS = 0x03;
-
- private static final int STACK_FRAME = 0x04;
-
- private static final int STACK_TRACE = 0x05;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int ALLOC_SITES = 0x06;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int HEAP_SUMMARY = 0x07;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int START_THREAD = 0x0a;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int END_THREAD = 0x0b;
-
- private static final int HEAP_DUMP = 0x0c;
-
- private static final int HEAP_DUMP_SEGMENT = 0x1c;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int HEAP_DUMP_END = 0x2c;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int CPU_SAMPLES = 0x0d;
-
- @SuppressWarnings("UnusedDeclaration")
- private static final int CONTROL_SETTINGS = 0x0e;
-
- private static final int ROOT_UNKNOWN = 0xff;
-
- private static final int ROOT_JNI_GLOBAL = 0x01;
-
- private static final int ROOT_JNI_LOCAL = 0x02;
-
- private static final int ROOT_JAVA_FRAME = 0x03;
-
- private static final int ROOT_NATIVE_STACK = 0x04;
-
- private static final int ROOT_STICKY_CLASS = 0x05;
-
- private static final int ROOT_THREAD_BLOCK = 0x06;
-
- private static final int ROOT_MONITOR_USED = 0x07;
-
- private static final int ROOT_THREAD_OBJECT = 0x08;
-
- private static final int ROOT_CLASS_DUMP = 0x20;
-
- private static final int ROOT_INSTANCE_DUMP = 0x21;
-
- private static final int ROOT_OBJECT_ARRAY_DUMP = 0x22;
-
- private static final int ROOT_PRIMITIVE_ARRAY_DUMP = 0x23;
-
- /**
- * Android format addition
- *
- * Specifies information about which heap certain objects came from. When a sub-tag of this type
- * appears in a HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT record, entries that follow it will
- * be associated with the specified heap. The HEAP_DUMP_INFO data is reset at the end of the
- * HEAP_DUMP[_SEGMENT]. Multiple HEAP_DUMP_INFO entries may appear in a single
- * HEAP_DUMP[_SEGMENT].
- *
- * Format: u1: Tag value (0xFE) u4: heap ID ID: heap name string ID
- */
- private static final int ROOT_HEAP_DUMP_INFO = 0xfe;
-
- private static final int ROOT_INTERNED_STRING = 0x89;
-
- private static final int ROOT_FINALIZING = 0x8a;
-
- private static final int ROOT_DEBUGGER = 0x8b;
-
- private static final int ROOT_REFERENCE_CLEANUP = 0x8c;
-
- private static final int ROOT_VM_INTERNAL = 0x8d;
-
- private static final int ROOT_JNI_MONITOR = 0x8e;
-
- private static final int ROOT_UNREACHABLE = 0x90;
-
- private static final int ROOT_PRIMITIVE_ARRAY_NODATA = 0xc3;
-
- @NonNull
- private final HprofBuffer mInput;
-
- int mIdSize;
-
- Snapshot mSnapshot;
-
- /*
- * These are only needed while parsing so are not kept as part of the
- * heap data.
- */
- @NonNull
- TLongObjectHashMap<String> mStrings = new TLongObjectHashMap<String>();
-
- @NonNull
- TLongObjectHashMap<String> mClassNames = new TLongObjectHashMap<String>();
-
- public HprofParser(@NonNull HprofBuffer buffer) {
- mInput = buffer;
- }
-
- @NonNull
- public final Snapshot parse() {
- Snapshot snapshot = new Snapshot(mInput);
- mSnapshot = snapshot;
-
- try {
- try {
- readNullTerminatedString(); // Version, ignored for now.
-
- mIdSize = mInput.readInt();
- mSnapshot.setIdSize(mIdSize);
-
- mInput.readLong(); // Timestamp, ignored for now.
-
- while (mInput.hasRemaining()) {
- int tag = readUnsignedByte();
- mInput.readInt(); // Ignored: timestamp
- long length = readUnsignedInt();
-
- switch (tag) {
- case STRING_IN_UTF8:
- // String length is limited by Int.MAX_VALUE anyway.
- loadString((int) length - mIdSize);
- break;
-
- case LOAD_CLASS:
- loadClass();
- break;
-
- case STACK_FRAME:
- loadStackFrame();
- break;
-
- case STACK_TRACE:
- loadStackTrace();
- break;
-
- case HEAP_DUMP:
- loadHeapDump(length);
- mSnapshot.setToDefaultHeap();
- break;
-
- case HEAP_DUMP_SEGMENT:
- loadHeapDump(length);
- mSnapshot.setToDefaultHeap();
- break;
-
- default:
- skipFully(length);
- }
-
- }
- } catch (EOFException eof) {
- // this is fine
- }
- mSnapshot.resolveClasses();
- mSnapshot.resolveReferences();
- // TODO: enable this after the dominators computation is also optimized.
- // mSnapshot.computeRetainedSizes();
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- mClassNames.clear();
- mStrings.clear();
- return snapshot;
- }
-
- @NonNull
- private String readNullTerminatedString() throws IOException {
- StringBuilder s = new StringBuilder();
- for (byte c = mInput.readByte(); c != 0; c = mInput.readByte()) {
- s.append((char) c);
- }
- return s.toString();
- }
-
- private long readId() throws IOException {
- // As long as we don't interpret IDs, reading signed values here is fine.
- switch (mIdSize) {
- case 1:
- return mInput.readByte();
- case 2:
- return mInput.readShort();
- case 4:
- return mInput.readInt();
- case 8:
- return mInput.readLong();
- }
-
- throw new IllegalArgumentException("ID Length must be 1, 2, 4, or 8");
- }
-
- @NonNull
- private String readUTF8(int length) throws IOException {
- byte[] b = new byte[length];
-
- mInput.read(b);
-
- return new String(b, "utf-8");
- }
-
- private int readUnsignedByte() throws IOException {
- return UnsignedBytes.toInt(mInput.readByte());
- }
-
- private int readUnsignedShort() throws IOException {
- return mInput.readShort() & 0xffff;
- }
-
- private long readUnsignedInt() throws IOException {
- return UnsignedInts.toLong(mInput.readInt());
- }
-
- private void loadString(int length) throws IOException {
- long id = readId();
- String string = readUTF8(length);
-
- mStrings.put(id, string);
- }
-
- private void loadClass() throws IOException {
- mInput.readInt(); // Ignored: Class serial number.
- long id = readId();
- mInput.readInt(); // Ignored: Stack trace serial number.
- String name = mStrings.get(readId());
-
- mClassNames.put(id, name);
- }
-
- private void loadStackFrame() throws IOException {
- long id = readId();
- String methodName = mStrings.get(readId());
- String methodSignature = mStrings.get(readId());
- String sourceFile = mStrings.get(readId());
- int serial = mInput.readInt();
- int lineNumber = mInput.readInt();
-
- StackFrame frame = new StackFrame(id, methodName, methodSignature,
- sourceFile, serial, lineNumber);
-
- mSnapshot.addStackFrame(frame);
- }
-
- private void loadStackTrace() throws IOException {
- int serialNumber = mInput.readInt();
- int threadSerialNumber = mInput.readInt();
- final int numFrames = mInput.readInt();
- StackFrame[] frames = new StackFrame[numFrames];
-
- for (int i = 0; i < numFrames; i++) {
- frames[i] = mSnapshot.getStackFrame(readId());
- }
-
- StackTrace trace = new StackTrace(serialNumber, threadSerialNumber, frames);
-
- mSnapshot.addStackTrace(trace);
- }
-
- private void loadHeapDump(long length) throws IOException {
- while (length > 0) {
- int tag = readUnsignedByte();
- length--;
-
- switch (tag) {
- case ROOT_UNKNOWN:
- length -= loadBasicObj(RootType.UNKNOWN);
- break;
-
- case ROOT_JNI_GLOBAL:
- length -= loadBasicObj(RootType.NATIVE_STATIC);
- readId(); // ignored
- length -= mIdSize;
- break;
-
- case ROOT_JNI_LOCAL:
- length -= loadJniLocal();
- break;
-
- case ROOT_JAVA_FRAME:
- length -= loadJavaFrame();
- break;
-
- case ROOT_NATIVE_STACK:
- length -= loadNativeStack();
- break;
-
- case ROOT_STICKY_CLASS:
- length -= loadBasicObj(RootType.SYSTEM_CLASS);
- break;
-
- case ROOT_THREAD_BLOCK:
- length -= loadThreadBlock();
- break;
-
- case ROOT_MONITOR_USED:
- length -= loadBasicObj(RootType.BUSY_MONITOR);
- break;
-
- case ROOT_THREAD_OBJECT:
- length -= loadThreadObject();
- break;
-
- case ROOT_CLASS_DUMP:
- length -= loadClassDump();
- break;
-
- case ROOT_INSTANCE_DUMP:
- length -= loadInstanceDump();
- break;
-
- case ROOT_OBJECT_ARRAY_DUMP:
- length -= loadObjectArrayDump();
- break;
-
- case ROOT_PRIMITIVE_ARRAY_DUMP:
- length -= loadPrimitiveArrayDump();
- break;
-
- case ROOT_PRIMITIVE_ARRAY_NODATA:
- System.err.println("+--- PRIMITIVE ARRAY NODATA DUMP");
- length -= loadPrimitiveArrayDump();
-
- throw new IllegalArgumentException(
- "Don't know how to load a nodata array");
-
- case ROOT_HEAP_DUMP_INFO:
- int heapId = mInput.readInt();
- long heapNameId = readId();
- String heapName = mStrings.get(heapNameId);
-
- mSnapshot.setHeapTo(heapId, heapName);
- length -= 4 + mIdSize;
- break;
-
- case ROOT_INTERNED_STRING:
- length -= loadBasicObj(RootType.INTERNED_STRING);
- break;
-
- case ROOT_FINALIZING:
- length -= loadBasicObj(RootType.FINALIZING);
- break;
-
- case ROOT_DEBUGGER:
- length -= loadBasicObj(RootType.DEBUGGER);
- break;
-
- case ROOT_REFERENCE_CLEANUP:
- length -= loadBasicObj(RootType.REFERENCE_CLEANUP);
- break;
-
- case ROOT_VM_INTERNAL:
- length -= loadBasicObj(RootType.VM_INTERNAL);
- break;
-
- case ROOT_JNI_MONITOR:
- length -= loadJniMonitor();
- break;
-
- case ROOT_UNREACHABLE:
- length -= loadBasicObj(RootType.UNREACHABLE);
- break;
-
- default:
- throw new IllegalArgumentException(
- "loadHeapDump loop with unknown tag " + tag
- + " with " + mInput.remaining()
- + " bytes possibly remaining");
- }
- }
- }
-
- private int loadJniLocal() throws IOException {
- long id = readId();
- int threadSerialNumber = mInput.readInt();
- int stackFrameNumber = mInput.readInt();
- ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
- StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber);
- RootObj root = new RootObj(RootType.NATIVE_LOCAL, id, threadSerialNumber, trace);
-
- mSnapshot.addRoot(root);
-
- return mIdSize + 4 + 4;
- }
-
- private int loadJavaFrame() throws IOException {
- long id = readId();
- int threadSerialNumber = mInput.readInt();
- int stackFrameNumber = mInput.readInt();
- ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
- StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber);
- RootObj root = new RootObj(RootType.JAVA_LOCAL, id, threadSerialNumber, trace);
-
- mSnapshot.addRoot(root);
-
- return mIdSize + 4 + 4;
- }
-
- private int loadNativeStack() throws IOException {
- long id = readId();
- int threadSerialNumber = mInput.readInt();
- ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
- StackTrace trace = mSnapshot.getStackTrace(thread.mStackTrace);
- RootObj root = new RootObj(RootType.NATIVE_STACK, id, threadSerialNumber, trace);
-
- mSnapshot.addRoot(root);
-
- return mIdSize + 4;
- }
-
- private int loadBasicObj(RootType type) throws IOException {
- long id = readId();
- RootObj root = new RootObj(type, id);
-
- mSnapshot.addRoot(root);
-
- return mIdSize;
- }
-
- private int loadThreadBlock() throws IOException {
- long id = readId();
- int threadSerialNumber = mInput.readInt();
- ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
- StackTrace stack = mSnapshot.getStackTrace(thread.mStackTrace);
- RootObj root = new RootObj(RootType.THREAD_BLOCK, id, threadSerialNumber, stack);
-
- mSnapshot.addRoot(root);
-
- return mIdSize + 4;
- }
-
- private int loadThreadObject() throws IOException {
- long id = readId();
- int threadSerialNumber = mInput.readInt();
- int stackSerialNumber = mInput.readInt();
- ThreadObj thread = new ThreadObj(id, stackSerialNumber);
-
- mSnapshot.addThread(thread, threadSerialNumber);
-
- return mIdSize + 4 + 4;
- }
-
- private int loadClassDump() throws IOException {
- final long id = readId();
- int stackSerialNumber = mInput.readInt();
- StackTrace stack = mSnapshot.getStackTrace(stackSerialNumber);
- final long superClassId = readId();
- final long classLoaderId = readId();
- readId(); // Ignored: Signeres ID.
- readId(); // Ignored: Protection domain ID.
- readId(); // RESERVED.
- readId(); // RESERVED.
- int instanceSize = mInput.readInt();
-
- int bytesRead = (7 * mIdSize) + 4 + 4;
-
- // Skip over the constant pool
- int numEntries = readUnsignedShort();
- bytesRead += 2;
-
- for (int i = 0; i < numEntries; i++) {
- readUnsignedShort();
- bytesRead += 2 + skipValue();
- }
-
- final ClassObj theClass = new ClassObj(id, stack, mClassNames.get(id), mInput.position());
- theClass.setSuperClassId(superClassId);
- theClass.setClassLoaderId(classLoaderId);
-
- // Skip over static fields
- numEntries = readUnsignedShort();
- bytesRead += 2;
-
- Field[] staticFields = new Field[numEntries];
-
- for (int i = 0; i < numEntries; i++) {
- String name = mStrings.get(readId());
- Type type = Type.getType(mInput.readByte());
-
- staticFields[i] = new Field(type, name);
- skipFully(mSnapshot.getTypeSize(type));
-
- bytesRead += mIdSize + 1 + mSnapshot.getTypeSize(type);
- }
-
- theClass.setStaticFields(staticFields);
-
- // Instance fields
- numEntries = readUnsignedShort();
- bytesRead += 2;
-
- Field[] fields = new Field[numEntries];
-
- for (int i = 0; i < numEntries; i++) {
- String name = mStrings.get(readId());
- Type type = Type.getType(readUnsignedByte());
-
- fields[i] = new Field(type, name);
-
- bytesRead += mIdSize + 1;
- }
-
- theClass.setFields(fields);
- theClass.setInstanceSize(instanceSize);
-
- mSnapshot.addClass(id, theClass);
-
- return bytesRead;
- }
-
- private int loadInstanceDump() throws IOException {
- long id = readId();
- int stackId = mInput.readInt();
- StackTrace stack = mSnapshot.getStackTrace(stackId);
- long classId = readId();
- int remaining = mInput.readInt();
-
- long position = mInput.position();
- ClassInstance instance = new ClassInstance(id, stack, position);
- instance.setClassId(classId);
- mSnapshot.addInstance(id, instance);
-
- skipFully(remaining);
- return mIdSize + 4 + mIdSize + 4 + remaining;
- }
-
- private int loadObjectArrayDump() throws IOException {
- final long id = readId();
- int stackId = mInput.readInt();
- StackTrace stack = mSnapshot.getStackTrace(stackId);
- int numElements = mInput.readInt();
- long classId = readId();
- ArrayInstance array =
- new ArrayInstance(id, stack, Type.OBJECT, numElements, mInput.position());
- array.setClassId(classId);
- mSnapshot.addInstance(id, array);
-
- int remaining = numElements * mIdSize;
- skipFully(remaining);
- return mIdSize + 4 + 4 + mIdSize + remaining;
- }
-
- private int loadPrimitiveArrayDump() throws IOException {
- long id = readId();
- int stackId = mInput.readInt();
- StackTrace stack = mSnapshot.getStackTrace(stackId);
- int numElements = mInput.readInt();
- Type type = Type.getType(readUnsignedByte());
- int size = mSnapshot.getTypeSize(type);
- ArrayInstance array = new ArrayInstance(id, stack, type, numElements, mInput.position());
- mSnapshot.addInstance(id, array);
-
- int remaining = numElements * size;
- skipFully(remaining);
- return mIdSize + 4 + 4 + 1 + remaining;
- }
-
- private int loadJniMonitor() throws IOException {
- long id = readId();
- int threadSerialNumber = mInput.readInt();
- int stackDepth = mInput.readInt();
- ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
- StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackDepth);
- RootObj root = new RootObj(RootType.NATIVE_MONITOR, id, threadSerialNumber, trace);
-
- mSnapshot.addRoot(root);
-
- return mIdSize + 4 + 4;
- }
-
- private int skipValue() throws IOException {
- Type type = Type.getType(readUnsignedByte());
- int size = mSnapshot.getTypeSize(type);
-
- skipFully(size);
-
- return size + 1;
- }
-
- private void skipFully(long numBytes) throws IOException {
- mInput.setPosition(mInput.position() + numBytes);
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Instance.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/Instance.java
deleted file mode 100644
index 4aab914..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/Instance.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.perflib.heap.io.HprofBuffer;
-import com.google.common.collect.ImmutableList;
-import com.google.common.primitives.UnsignedBytes;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public abstract class Instance {
-
- protected final long mId;
-
- // The stack in which this object was allocated
- @NonNull
- protected final StackTrace mStack;
-
- // Id of the ClassObj of which this object is an instance
- long mClassId;
-
- // The heap in which this object was allocated (app, zygote, etc)
- Heap mHeap;
-
- // The size of this object
- int mSize;
-
- // Another identifier for this Instance, that we computed during the analysis phase.
- int mTopologicalOrder;
-
- int mDistanceToGcRoot = Integer.MAX_VALUE;
-
- boolean mReferencesAdded = false;
-
- Instance mNextInstanceToGcRoot = null;
-
- // The immediate dominator of this instance, or null if not reachable from any GC roots.
- @Nullable
- private Instance mImmediateDominator;
-
- // The retained size of this object, indexed by heap (default, image, app, zygote).
- // Intuitively, this represents the amount of memory that could be reclaimed in each heap if
- // the instance were removed.
- // To save space, we only keep a primitive array here following the order in mSnapshot.mHeaps.
- private long[] mRetainedSizes;
-
- // List of all objects that hold a live reference to this object
- private final ArrayList<Instance> mHardReferences = new ArrayList<Instance>();
-
- // List of all objects that hold a soft/weak/phantom reference to this object.
- // Don't create an actual list until we need to.
- private ArrayList<Instance> mSoftReferences = null;
-
- Instance(long id, @NonNull StackTrace stackTrace) {
- mId = id;
- mStack = stackTrace;
- }
-
- public long getId() {
- return mId;
- }
-
- public long getUniqueId() {
- return getId() & mHeap.mSnapshot.getIdSizeMask();
- }
-
- public abstract void accept(Visitor visitor);
-
- public void setClassId(long classId) {
- mClassId = classId;
- }
-
- public ClassObj getClassObj() {
- return mHeap.mSnapshot.findClass(mClassId);
- }
-
- public final int getCompositeSize() {
- CompositeSizeVisitor visitor = new CompositeSizeVisitor();
- visitor.doVisit(ImmutableList.of(this));
- return visitor.getCompositeSize();
- }
-
- // Returns the instrinsic size of a given object
- public int getSize() {
- return mSize;
- }
-
- public void setSize(int size) {
- mSize = size;
- }
-
- public void setHeap(Heap heap) {
- mHeap = heap;
- }
-
- public Heap getHeap() {
- return mHeap;
- }
-
- public int getTopologicalOrder() {
- return mTopologicalOrder;
- }
-
- public void setTopologicalOrder(int topologicalOrder) {
- mTopologicalOrder = topologicalOrder;
- }
-
- @Nullable
- public Instance getImmediateDominator() {
- return mImmediateDominator;
- }
-
- public void setImmediateDominator(@NonNull Instance dominator) {
- mImmediateDominator = dominator;
- }
-
- public int getDistanceToGcRoot() {
- return mDistanceToGcRoot;
- }
-
- public Instance getNextInstanceToGcRoot() {
- return mNextInstanceToGcRoot;
- }
-
- public void setDistanceToGcRoot(int newDistance) {
- assert(newDistance < mDistanceToGcRoot);
- mDistanceToGcRoot = newDistance;
- }
-
- public void setNextInstanceToGcRoot(Instance instance) {
- mNextInstanceToGcRoot = instance;
- }
-
- public void resetRetainedSize() {
- List<Heap> allHeaps = mHeap.mSnapshot.mHeaps;
- if (mRetainedSizes == null) {
- mRetainedSizes = new long[allHeaps.size()];
- } else {
- Arrays.fill(mRetainedSizes, 0);
- }
- mRetainedSizes[allHeaps.indexOf(mHeap)] = getSize();
- }
-
- public void addRetainedSize(int heapIndex, long size) {
- mRetainedSizes[heapIndex] += size;
- }
-
- public long getRetainedSize(int heapIndex) {
- return mRetainedSizes[heapIndex];
- }
-
- public long getTotalRetainedSize() {
- if (mRetainedSizes == null) {
- return 0;
- }
-
- long totalSize = 0;
- for (long mRetainedSize : mRetainedSizes) {
- totalSize += mRetainedSize;
- }
- return totalSize;
- }
-
- /**
- * Add to the list of objects that references this Instance.
- *
- * @param field the named variable in #reference pointing to this instance. If the name of the field is "referent", and #reference is a
- * soft reference type, then reference is counted as a soft reference instead of the usual hard reference.
- * @param reference another instance that references this instance
- */
- public void addReference(@Nullable Field field, @NonNull Instance reference) {
- if (reference.getIsSoftReference() && field != null && field.getName().equals("referent")) {
- if (mSoftReferences == null) {
- mSoftReferences = new ArrayList<Instance>();
- }
- mSoftReferences.add(reference);
- }
- else {
- mHardReferences.add(reference);
- }
- }
-
- @NonNull
- public ArrayList<Instance> getHardReferences() {
- return mHardReferences;
- }
-
- @Nullable
- public ArrayList<Instance> getSoftReferences() {
- return mSoftReferences;
- }
-
- /**
- * There is an underlying assumption that a class that is a soft reference will only have one referent.
- *
- * @return true if the instance is a soft reference type, or false otherwise
- */
- public boolean getIsSoftReference() {
- return false;
- }
-
- @Nullable
- protected Object readValue(@NonNull Type type) {
- switch (type) {
- case OBJECT:
- long id = readId();
- return mHeap.mSnapshot.findInstance(id);
- case BOOLEAN:
- return getBuffer().readByte() != 0;
- case CHAR:
- return getBuffer().readChar();
- case FLOAT:
- return getBuffer().readFloat();
- case DOUBLE:
- return getBuffer().readDouble();
- case BYTE:
- return getBuffer().readByte();
- case SHORT:
- return getBuffer().readShort();
- case INT:
- return getBuffer().readInt();
- case LONG:
- return getBuffer().readLong();
- }
- return null;
- }
-
- protected long readId() {
- // As long as we don't interpret IDs, reading signed values here is fine.
- switch (mHeap.mSnapshot.getTypeSize(Type.OBJECT)) {
- case 1:
- return getBuffer().readByte();
- case 2:
- return getBuffer().readShort();
- case 4:
- return getBuffer().readInt();
- case 8:
- return getBuffer().readLong();
- }
- return 0;
- }
-
- protected int readUnsignedByte(){
- return UnsignedBytes.toInt(getBuffer().readByte());
- }
-
- protected int readUnsignedShort() {
- return getBuffer().readShort() & 0xffff;
- }
-
- protected HprofBuffer getBuffer() {
- return mHeap.mSnapshot.mBuffer;
- }
-
-
- public static class CompositeSizeVisitor extends NonRecursiveVisitor {
-
- int mSize = 0;
-
- @Override
- protected void defaultAction(Instance node) {
- mSize += node.getSize();
- }
-
- public int getCompositeSize() {
- return mSize;
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Main.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/Main.java
deleted file mode 100644
index 6983a44..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/Main.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.tools.perflib.heap.io.HprofBuffer;
-import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Set;
-
-public class Main {
-
- public static void main(String argv[]) {
- try {
- long start = System.nanoTime();
- HprofBuffer buffer = new MemoryMappedFileBuffer(new File(argv[0]));
- Snapshot snapshot = (new HprofParser(buffer)).parse();
-
- testClassesQuery(snapshot);
- testAllClassesQuery(snapshot);
- testFindInstancesOf(snapshot);
- testFindAllInstancesOf(snapshot);
-
- System.out.println("Memory stats: free=" + Runtime.getRuntime().freeMemory()
- + " / total=" + Runtime.getRuntime().totalMemory());
- System.out.println("Time: " + (System.nanoTime() - start) / 1000000 + "ms");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- private static void testClassesQuery(Snapshot snapshot) {
- String[] x = new String[]{
- "char[",
- "javax.",
- "org.xml.sax"
- };
-
- Map<String, Set<ClassObj>> someClasses = Queries.classes(snapshot, x);
-
- for (String thePackage : someClasses.keySet()) {
- System.out.println("------------------- " + thePackage);
-
- Set<ClassObj> classes = someClasses.get(thePackage);
-
- for (ClassObj theClass : classes) {
- System.out.println(" " + theClass.mClassName);
- }
- }
- }
-
- private static void testAllClassesQuery(Snapshot snapshot) {
- Map<String, Set<ClassObj>> allClasses = Queries.allClasses(snapshot);
-
- for (String thePackage : allClasses.keySet()) {
- System.out.println("------------------- " + thePackage);
-
- Set<ClassObj> classes = allClasses.get(thePackage);
-
- for (ClassObj theClass : classes) {
- System.out.println(" " + theClass.mClassName);
- }
- }
- }
-
- private static void testFindInstancesOf(Snapshot snapshot) {
- Instance[] instances = Queries.instancesOf(snapshot, "java.lang.String");
-
- System.out.println("There are " + instances.length + " Strings.");
- }
-
- private static void testFindAllInstancesOf(Snapshot snapshot) {
- Instance[] instances = Queries.allInstancesOf(snapshot,
- "android.graphics.drawable.Drawable");
-
- System.out.println("There are " + instances.length
- + " instances of Drawables and its subclasses.");
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Queries.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/Queries.java
deleted file mode 100644
index 3868308..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/Queries.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.util.*;
-
-public class Queries {
- /*
- * NOTES: Here's a list of the queries that can be done in hat and
- * how you'd perform a similar query here in hit:
- *
- * hat hit
- * ------------------------------------------------------------------------
- * allClasses classes
- * allClassesWithPlatform allClasses
- * class findClass
- * instances instancesOf
- * allInstances allInstancesOf
- * object findObject
- * showRoots getRoots
- * newInstances newInstances
- *
- * reachableFrom make a call to findObject to get the target
- * parent object, this will give you an Instance.
- * Then call visit(Set, Filter) on that to have
- * it build the set of objects in its subgraph.
- *
- * rootsTo make a call to findObject on the leaf node
- * in question, this will give you an Instance.
- * Instances have an ArrayList of all of the
- * parent objects that refer to it. You can
- * follow those parent links until you hit an
- * object whose parent is null or a ThreadObj.
- * You've not successfully traced the paths to
- * the roots.
- */
-
- private static final String DEFAULT_PACKAGE = "<default>";
-
- /*
- * Produce a collection of all classes, broken down by package.
- * The keys of the resultant map iterate in sorted package order.
- * The values of the map are the classes defined in each package.
- */
- @NonNull
- public static Map<String, Set<ClassObj>> allClasses(@NonNull Snapshot snapshot) {
- return classes(snapshot, null);
- }
-
- @NonNull
- public static Map<String, Set<ClassObj>> classes(@NonNull Snapshot snapshot,
- @Nullable String[] excludedPrefixes) {
- TreeMap<String, Set<ClassObj>> result =
- new TreeMap<String, Set<ClassObj>>();
-
- Set<ClassObj> classes = new TreeSet<ClassObj>();
-
- // Build a set of all classes across all heaps
- for (Heap heap : snapshot.mHeaps) {
- classes.addAll(heap.getClasses());
- }
-
- // Filter it if needed
- if (excludedPrefixes != null) {
- final int N = excludedPrefixes.length;
- Iterator<ClassObj> iter = classes.iterator();
-
- while (iter.hasNext()) {
- ClassObj theClass = iter.next();
- String classPath = theClass.toString();
-
- for (int i = 0; i < N; i++) {
- if (classPath.startsWith(excludedPrefixes[i])) {
- iter.remove();
- break;
- }
- }
- }
- }
-
- // Now that we have a final list of classes, group them by package
- for (ClassObj theClass : classes) {
- String packageName = DEFAULT_PACKAGE;
- int lastDot = theClass.mClassName.lastIndexOf('.');
-
- if (lastDot != -1) {
- packageName = theClass.mClassName.substring(0, lastDot);
- }
-
- Set<ClassObj> classSet = result.get(packageName);
-
- if (classSet == null) {
- classSet = new TreeSet<ClassObj>();
- result.put(packageName, classSet);
- }
-
- classSet.add(theClass);
- }
-
- return result;
- }
-
- /**
- * Returns a collection of classes common to both snapshots.
- *
- * <p>The query returns instances from the first snapshot. Note: two classes having the same
- * fully-qualified name are considered equal, even if they differ in static fields, superclass,
- * classloader, etc.
- */
- @NonNull
- public static Collection<ClassObj> commonClasses(@NonNull Snapshot first,
- @NonNull Snapshot second) {
- Collection<ClassObj> classes = new ArrayList<ClassObj>();
- for (Heap heap : first.getHeaps()) {
- for (ClassObj clazz : heap.getClasses()) {
- if (second.findClass(clazz.getClassName()) != null) {
- classes.add(clazz);
- }
- }
- }
- return classes;
- }
-
- /*
- * It's sorta sad that this is a pass-through call, but it seems like
- * having all of the hat-like query methods in one place is a good thing
- * even if there is duplication of effort.
- */
- public static ClassObj findClass(@NonNull Snapshot snapshot, String name) {
- return snapshot.findClass(name);
- }
-
- /*
- * Return an array of instances of the given class. This does not include
- * instances of subclasses.
- */
- @NonNull
- public static Instance[] instancesOf(@NonNull Snapshot snapshot, String baseClassName) {
- ClassObj theClass = snapshot.findClass(baseClassName);
-
- if (theClass == null) {
- throw new IllegalArgumentException("Class not found: " + baseClassName);
- }
-
- List<Instance> instances = theClass.getInstancesList();
- return instances.toArray(new Instance[instances.size()]);
- }
-
- /*
- * Return an array of instances of the given class. This includes
- * instances of subclasses.
- */
- @NonNull
- public static Instance[] allInstancesOf(@NonNull Snapshot snapshot, String baseClassName) {
- ClassObj theClass = snapshot.findClass(baseClassName);
-
- if (theClass == null) {
- throw new IllegalArgumentException("Class not found: " + baseClassName);
- }
-
- ArrayList<ClassObj> classList = new ArrayList<ClassObj>();
-
- classList.add(theClass);
- classList.addAll(traverseSubclasses(theClass));
-
- ArrayList<Instance> instanceList = new ArrayList<Instance>();
-
- for (ClassObj someClass : classList) {
- instanceList.addAll(someClass.getInstancesList());
- }
-
- Instance[] result = new Instance[instanceList.size()];
-
- instanceList.toArray(result);
-
- return result;
- }
-
- @NonNull
- private static ArrayList<ClassObj> traverseSubclasses(@NonNull ClassObj base) {
- ArrayList<ClassObj> result = new ArrayList<ClassObj>();
-
- for (ClassObj subclass : base.mSubclasses) {
- result.add(subclass);
- result.addAll(traverseSubclasses(subclass));
- }
-
- return result;
- }
-
- /*
- * Find a reference to an object based on its id. The id should be
- * in hexadecimal.
- */
- public static Instance findObject(@NonNull Snapshot snapshot, String id) {
- long id2 = Long.parseLong(id, 16);
-
- return snapshot.findInstance(id2);
- }
-
- @NonNull
- public static Collection<RootObj> getRoots(@NonNull Snapshot snapshot) {
- HashSet<RootObj> result = new HashSet<RootObj>();
-
- for (Heap heap : snapshot.mHeaps) {
- result.addAll(heap.mRoots);
- }
-
- return result;
- }
-
- @NonNull
- public static final Instance[] newInstances(@NonNull Snapshot older, @NonNull Snapshot newer) {
- ArrayList<Instance> resultList = new ArrayList<Instance>();
-
- for (Heap newHeap : newer.mHeaps) {
- Heap oldHeap = older.getHeap(newHeap.getName());
-
- if (oldHeap == null) {
- continue;
- }
-
- for (Instance instance : newHeap.getInstances()) {
- Instance oldInstance = oldHeap.getInstance(instance.mId);
-
- /*
- * If this instance wasn't in the old heap, or was there,
- * but that ID was for an obj of a different type, then we have
- * a newly allocated object and we should report it in the
- * results.
- */
- if (oldInstance == null || (instance.getClassObj() != oldInstance.getClassObj())) {
- resultList.add(instance);
- }
- }
- }
-
- Instance[] resultArray = new Instance[resultList.size()];
-
- return resultList.toArray(resultArray);
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/RootObj.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/RootObj.java
deleted file mode 100644
index 3a6fb86..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/RootObj.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-public class RootObj extends Instance {
- public static final String UNDEFINED_CLASS_NAME = "no class defined!!";
-
- RootType mType = RootType.UNKNOWN;
-
- int mIndex;
-
- int mThread;
-
- public RootObj(RootType type) {
- this(type, 0, 0, null);
- }
-
- public RootObj(RootType type, long id) {
- this(type, id, 0, null);
- }
-
- public RootObj(RootType type, long id, int thread, StackTrace stack) {
- super(id, stack);
- mType = type;
- mThread = thread;
- }
-
- public final String getClassName(@NonNull Snapshot snapshot) {
- ClassObj theClass;
-
- if (mType == RootType.SYSTEM_CLASS) {
- theClass = snapshot.findClass(mId);
- } else {
- theClass = snapshot.findInstance(mId).getClassObj();
- }
-
- if (theClass == null) {
- return UNDEFINED_CLASS_NAME;
- }
-
- return theClass.mClassName;
- }
-
- @Override
- public final void accept(Visitor visitor) {
- visitor.visitRootObj(this);
- Instance instance = getReferredInstance();
- if (instance != null) {
- visitor.visitLater(null, instance);
- }
- }
-
- public final String toString() {
- return String.format("%s at 0x%08x", mType.getName(), mId);
- }
-
- @Nullable
- public Instance getReferredInstance() {
- if (mType == RootType.SYSTEM_CLASS) {
- return mHeap.mSnapshot.findClass(mId);
- } else {
- return mHeap.mSnapshot.findInstance(mId);
- }
- }
-
- public RootType getRootType() {
- return mType;
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Snapshot.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/Snapshot.java
deleted file mode 100644
index 0b074bf..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/Snapshot.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.perflib.heap.analysis.Dominators;
-import com.android.tools.perflib.heap.analysis.ShortestDistanceVisitor;
-import com.android.tools.perflib.heap.analysis.TopologicalSort;
-import com.android.tools.perflib.heap.io.HprofBuffer;
-import com.google.common.collect.ImmutableList;
-import gnu.trove.THashSet;
-
-import java.util.*;
-
-/*
- * A snapshot of all of the heaps, and related meta-data, for the runtime at a given instant.
- *
- * There are three possible heaps: default, app and zygote. GC roots are always reported in the
- * default heap, and they are simply references to objects living in the zygote or the app heap.
- * During parsing of the HPROF file HEAP_DUMP_INFO chunks change which heap is being referenced.
- */
-public class Snapshot {
-
- private static final String JAVA_LANG_CLASS = "java.lang.Class";
-
- // Special root object used in dominator computation for objects reachable via multiple roots.
- public static final Instance SENTINEL_ROOT = new RootObj(RootType.UNKNOWN);
-
- private static final int DEFAULT_HEAP_ID = 0;
-
- @NonNull
- final HprofBuffer mBuffer;
-
- @NonNull
- ArrayList<Heap> mHeaps = new ArrayList<Heap>();
-
- @NonNull
- Heap mCurrentHeap;
-
- private ImmutableList<Instance> mTopSort;
-
- private Dominators mDominators;
-
- // The set of all classes that are (sub)class(es) of java.lang.ref.Reference.
- private THashSet<ClassObj> mReferenceClasses = new THashSet<ClassObj>();
-
- private int[] mTypeSizes;
-
- private long mIdSizeMask = 0x00000000ffffffffl;
-
- public Snapshot(@NonNull HprofBuffer buffer) {
- mBuffer = buffer;
- setToDefaultHeap();
- }
-
- @NonNull
- public Heap setToDefaultHeap() {
- return setHeapTo(DEFAULT_HEAP_ID, "default");
- }
-
- @NonNull
- public Heap setHeapTo(int id, @NonNull String name) {
- Heap heap = getHeap(id);
-
- if (heap == null) {
- heap = new Heap(id, name);
- heap.mSnapshot = this;
- mHeaps.add(heap);
- }
-
- mCurrentHeap = heap;
-
- return mCurrentHeap;
- }
-
- public int getHeapIndex(@NonNull Heap heap) {
- return mHeaps.indexOf(heap);
- }
-
- @Nullable
- public Heap getHeap(int id) {
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < mHeaps.size(); i++) {
- if (mHeaps.get(i).getId() == id) {
- return mHeaps.get(i);
- }
- }
- return null;
- }
-
- @Nullable
- public Heap getHeap(@NonNull String name) {
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < mHeaps.size(); i++) {
- if (name.equals(mHeaps.get(i).getName())) {
- return mHeaps.get(i);
- }
- }
- return null;
- }
-
- @NonNull
- public Collection<Heap> getHeaps() {
- return mHeaps;
- }
-
- @NonNull
- public Collection<RootObj> getGCRoots() {
- // Roots are always in the default heap.
- return mHeaps.get(DEFAULT_HEAP_ID).mRoots;
- }
-
- public final void addStackFrame(@NonNull StackFrame theFrame) {
- mCurrentHeap.addStackFrame(theFrame);
- }
-
- public final StackFrame getStackFrame(long id) {
- return mCurrentHeap.getStackFrame(id);
- }
-
- public final void addStackTrace(@NonNull StackTrace theTrace) {
- mCurrentHeap.addStackTrace(theTrace);
- }
-
- public final StackTrace getStackTrace(int traceSerialNumber) {
- return mCurrentHeap.getStackTrace(traceSerialNumber);
- }
-
- public final StackTrace getStackTraceAtDepth(int traceSerialNumber, int depth) {
- return mCurrentHeap.getStackTraceAtDepth(traceSerialNumber, depth);
- }
-
- public final void addRoot(@NonNull RootObj root) {
- mCurrentHeap.addRoot(root);
- root.setHeap(mCurrentHeap);
- }
-
- public final void addThread(ThreadObj thread, int serialNumber) {
- mCurrentHeap.addThread(thread, serialNumber);
- }
-
- public final ThreadObj getThread(int serialNumber) {
- return mCurrentHeap.getThread(serialNumber);
- }
-
- public final void setIdSize(int size) {
- int maxId = -1;
- for (int i = 0; i < Type.values().length; ++i) {
- maxId = Math.max(Type.values()[i].getTypeId(), maxId);
- }
- assert (maxId > 0) && (maxId <= Type.LONG.getTypeId()); // Update this if hprof format ever changes its supported types.
- mTypeSizes = new int[maxId + 1];
- Arrays.fill(mTypeSizes, -1);
-
- for (int i = 0; i < Type.values().length; ++i) {
- mTypeSizes[Type.values()[i].getTypeId()] = Type.values()[i].getSize();
- }
- mTypeSizes[Type.OBJECT.getTypeId()] = size;
- mIdSizeMask = 0xffffffffffffffffl >>> ((8 - size) * 8);
- }
-
- public final int getTypeSize(Type type) {
- return mTypeSizes[type.getTypeId()];
- }
-
- public final long getIdSizeMask() {
- return mIdSizeMask;
- }
-
- public final void addInstance(long id, @NonNull Instance instance) {
- mCurrentHeap.addInstance(id, instance);
- instance.setHeap(mCurrentHeap);
- }
-
- public final void addClass(long id, @NonNull ClassObj theClass) {
- mCurrentHeap.addClass(id, theClass);
- theClass.setHeap(mCurrentHeap);
- }
-
- @Nullable
- public final Instance findInstance(long id) {
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < mHeaps.size(); i++) {
- Instance instance = mHeaps.get(i).getInstance(id);
-
- if (instance != null) {
- return instance;
- }
- }
-
- // Couldn't find an instance of a class, look for a class object
- return findClass(id);
- }
-
- @Nullable
- public final ClassObj findClass(long id) {
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < mHeaps.size(); i++) {
- ClassObj theClass = mHeaps.get(i).getClass(id);
-
- if (theClass != null) {
- return theClass;
- }
- }
-
- return null;
- }
-
- /**
- * Finds the first ClassObj with a class name that matches <code>name</code>.
- *
- * @param name of the class to find
- * @return the found <code>ClassObj</code>, or null if not found
- */
- @Nullable
- public final ClassObj findClass(String name) {
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < mHeaps.size(); i++) {
- ClassObj theClass = mHeaps.get(i).getClass(name);
-
- if (theClass != null) {
- return theClass;
- }
- }
-
- return null;
- }
-
- /**
- * Finds all <code>ClassObj</code>s with class name that match the given <code>name</code>.
- *
- * @param name of the class to find
- * @return a collection of the found <code>ClassObj</code>s, or empty collection if not found
- */
- @NonNull
- public final Collection<ClassObj> findClasses(String name) {
- ArrayList<ClassObj> classObjs = new ArrayList<ClassObj>();
-
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < mHeaps.size(); i++) {
- classObjs.addAll(mHeaps.get(i).getClasses(name));
- }
-
- return classObjs;
- }
-
- public void resolveClasses() {
- ClassObj clazz = findClass(JAVA_LANG_CLASS);
- int javaLangClassSize = clazz != null ? clazz.getInstanceSize() : 0;
-
- for (Heap heap : mHeaps) {
- for (ClassObj classObj : heap.getClasses()) {
- ClassObj superClass = classObj.getSuperClassObj();
- if (superClass != null) {
- superClass.addSubclass(classObj);
- }
- // We under-approximate the size of the class by including the size of Class.class
- // and the size of static fields, and omitting padding, vtable and imtable sizes.
- int classSize = javaLangClassSize;
-
- for (Field f : classObj.mStaticFields) {
- classSize += getTypeSize(f.getType());
- }
- classObj.setSize(classSize);
- }
- for (Instance instance : heap.getInstances()) {
- ClassObj classObj = instance.getClassObj();
- if (classObj != null) {
- classObj.addInstance(heap.getId(), instance);
- }
- }
- }
- }
-
- public void resolveReferences() {
- List<ClassObj> referenceDescendants = findAllDescendantClasses(ClassObj.getReferenceClassName());
- for (ClassObj classObj : referenceDescendants) {
- classObj.setIsSoftReference();
- mReferenceClasses.add(classObj);
- }
- }
-
- @NonNull
- public List<ClassObj> findAllDescendantClasses(@NonNull String className) {
- Collection<ClassObj> ancestorClasses = findClasses(className);
- List<ClassObj> descendants = new ArrayList<ClassObj>();
- for (ClassObj ancestor : ancestorClasses) {
- descendants.addAll(ancestor.getDescendantClasses());
- }
- return descendants;
- }
-
- // TODO: Break dominator computation into fixed chunks, because it can be unbounded/expensive.
- public void computeDominators() {
- if (mDominators == null) {
- mTopSort = TopologicalSort.compute(getGCRoots());
- mDominators = new Dominators(this, mTopSort);
- mDominators.computeRetainedSizes();
-
- ShortestDistanceVisitor shortestDistanceVisitor = new ShortestDistanceVisitor();
- shortestDistanceVisitor.doVisit(getGCRoots());
- }
- }
-
- @NonNull
- public List<Instance> getReachableInstances() {
- List<Instance> result = new ArrayList<Instance>(mTopSort.size());
- for (Instance node : mTopSort) {
- if (node.getImmediateDominator() != null) {
- result.add(node);
- }
- }
- return result;
- }
-
- public ImmutableList<Instance> getTopologicalOrdering() {
- return mTopSort;
- }
-
- public final void dumpInstanceCounts() {
- for (Heap heap : mHeaps) {
- System.out.println(
- "+------------------ instance counts for heap: " + heap.getName());
- heap.dumpInstanceCounts();
- }
- }
-
- public final void dumpSizes() {
- for (Heap heap : mHeaps) {
- System.out.println(
- "+------------------ sizes for heap: " + heap.getName());
- heap.dumpSizes();
- }
- }
-
- public final void dumpSubclasses() {
- for (Heap heap : mHeaps) {
- System.out.println(
- "+------------------ subclasses for heap: " + heap.getName());
- heap.dumpSubclasses();
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/StackFrame.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/StackFrame.java
deleted file mode 100644
index a91bc55..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/StackFrame.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-
-public class StackFrame {
-
- public static final int NO_LINE_NUMBER = 0;
-
- public static final int UNKNOWN_LOCATION = -1;
-
- public static final int COMPILED_METHOD = -2;
-
- public static final int NATIVE_METHOD = -3;
-
- long mId;
-
- String mMethodName;
-
- String mSignature;
-
- String mFilename;
-
- int mSerialNumber;
-
- int mLineNumber;
-
- public StackFrame(long id, String method, String sig, String file,
- int serial, int line) {
- mId = id;
- mMethodName = method;
- mSignature = sig;
- mFilename = file;
- mSerialNumber = serial;
- mLineNumber = line;
- }
-
- @NonNull
- private String lineNumberString() {
- switch (mLineNumber) {
- case NO_LINE_NUMBER:
- return "No line number";
- case UNKNOWN_LOCATION:
- return "Unknown line number";
- case COMPILED_METHOD:
- return "Compiled method";
- case NATIVE_METHOD:
- return "Native method";
-
- default:
- return String.valueOf(mLineNumber);
- }
- }
-
- @NonNull
- public final String toString() {
- return mMethodName
- + mSignature.replace('/', '.')
- + " - "
- + mFilename + ":"
- + lineNumberString();
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/StackTrace.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/StackTrace.java
deleted file mode 100644
index 4d0259e..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/StackTrace.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-public class StackTrace {
-
- int mSerialNumber;
-
- int mThreadSerialNumber;
-
- StackFrame[] mFrames;
-
- /*
- * For subsets of the stack frame we'll reference the parent list of frames
- * but keep track of its offset into the parent's list of stack frame ids.
- * This alleviates the need to constantly be duplicating subsections of the
- * list of stack frame ids.
- */
- @Nullable
- StackTrace mParent = null;
-
- int mOffset = 0;
-
- private StackTrace() {
-
- }
-
- public StackTrace(int serial, int thread, StackFrame[] frames) {
- mSerialNumber = serial;
- mThreadSerialNumber = thread;
- mFrames = frames;
- }
-
- @NonNull
- public final StackTrace fromDepth(int startingDepth) {
- StackTrace result = new StackTrace();
-
- if (mParent != null) {
- result.mParent = mParent;
- } else {
- result.mParent = this;
- }
-
- result.mOffset = startingDepth + mOffset;
-
- return result;
- }
-
- public final void dump() {
- final int N = mFrames.length;
-
- for (int i = 0; i < N; i++) {
- System.out.println(mFrames[i].toString());
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Value.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/Value.java
deleted file mode 100644
index 4e74ba6..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/Value.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-public class Value {
-
- private Object mValue;
-
- /**
- * The instance this value belongs to.
- */
- private final Instance instance;
-
- public Value(Instance instance) {
- this.instance = instance;
- }
-
- public Object getValue() {
- return mValue;
- }
-
- public void setValue(Object value) {
- mValue = value;
-
- if (value instanceof Instance) {
- ((Instance) value).addReference(null, instance);
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/Dominators.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/Dominators.java
deleted file mode 100644
index f7b04e8..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/Dominators.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap.analysis;
-
-import com.android.annotations.NonNull;
-import com.android.tools.perflib.heap.*;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
-/**
- * Initial implementation of dominator computation.
- *
- * Node <i>d</i> is said to dominate node <i>n</i> if every path from any of the roots to node
- * <i>n</i> must go through <i>d</i>. The <b>immediate</b> dominator of a node <i>n</i> is the
- * dominator <i>d</i> that is closest to <i>n</i>. The immediate dominance relation yields a tree
- * called <b>dominator tree</b>, with the important property that the subtree of a node corresponds
- * to the retained object graph of that particular node, i.e. the amount of memory that could be
- * freed if the node were garbage collected.
- *
- * The full algorithm is described in {@see http://www.cs.rice.edu/~keith/EMBED/dom.pdf}. It's a
- * simple iterative algorithm with worst-case complexity of O(N^2).
- */
-public class Dominators {
-
- @NonNull
- private final Snapshot mSnapshot;
-
- @NonNull
- private final ImmutableList<Instance> mTopSort;
-
- public Dominators(@NonNull Snapshot snapshot, @NonNull ImmutableList<Instance> topSort) {
- mSnapshot = snapshot;
- mTopSort = topSort;
-
- // Only instances reachable from the GC roots will participate in dominator computation.
- // We will omit from the analysis any other nodes which could be considered roots, i.e. with
- // no incoming references, if they are not GC roots.
- for (RootObj root : snapshot.getGCRoots()) {
- Instance ref = root.getReferredInstance();
- if (ref != null) {
- ref.setImmediateDominator(Snapshot.SENTINEL_ROOT);
- }
- }
- }
-
- private void computeDominators() {
- // We need to iterate on the dominator computation because the graph may contain cycles.
- // TODO: Check how long it takes to converge, and whether we need to place an upper bound.
- boolean changed = true;
- while (changed) {
- changed = false;
-
- for (int i = 0; i < mTopSort.size(); i++) {
- Instance node = mTopSort.get(i);
- // Root nodes and nodes immediately dominated by the SENTINEL_ROOT are skipped.
- if (node.getImmediateDominator() != Snapshot.SENTINEL_ROOT) {
- Instance dominator = null;
-
- for (int j = 0; j < node.getHardReferences().size(); j++) {
- Instance predecessor = node.getHardReferences().get(j);
- if (predecessor.getImmediateDominator() == null) {
- // If we don't have a dominator/approximation for predecessor, skip it
- continue;
- }
- if (dominator == null) {
- dominator = predecessor;
- } else {
- Instance fingerA = dominator;
- Instance fingerB = predecessor;
- while (fingerA != fingerB) {
- if (fingerA.getTopologicalOrder() < fingerB.getTopologicalOrder()) {
- fingerB = fingerB.getImmediateDominator();
- } else {
- fingerA = fingerA.getImmediateDominator();
- }
- }
- dominator = fingerA;
- }
- }
-
- if (node.getImmediateDominator() != dominator) {
- node.setImmediateDominator(dominator);
- changed = true;
- }
- }
- }
- }
- }
-
- /**
- * Kicks off the computation of dominators and retained sizes.
- */
- public void computeRetainedSizes() {
- // Initialize retained sizes for all classes and objects, including unreachable ones.
- for (Heap heap : mSnapshot.getHeaps()) {
- for (Instance instance : Iterables.concat(heap.getClasses(), heap.getInstances())) {
- instance.resetRetainedSize();
- }
- }
- computeDominators();
- // We only update the retained sizes of objects in the dominator tree (i.e. reachable).
- for (Instance node : mSnapshot.getReachableInstances()) {
- int heapIndex = mSnapshot.getHeapIndex(node.getHeap());
- // Add the size of the current node to the retained size of every dominator up to the
- // root, in the same heap.
- for (Instance dom = node.getImmediateDominator(); dom != Snapshot.SENTINEL_ROOT;
- dom = dom.getImmediateDominator()) {
- dom.addRetainedSize(heapIndex, node.getSize());
- }
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ShortestDistanceVisitor.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ShortestDistanceVisitor.java
deleted file mode 100644
index 5cb1a42..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ShortestDistanceVisitor.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.perflib.heap.analysis;
-
-import com.android.annotations.NonNull;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.NonRecursiveVisitor;
-
-import java.util.Comparator;
-import java.util.PriorityQueue;
-
-public class ShortestDistanceVisitor extends NonRecursiveVisitor {
- private PriorityQueue<Instance> mPriorityQueue = new PriorityQueue<Instance>(1024, new Comparator<Instance>() {
- @Override
- public int compare(Instance o1, Instance o2) {
- return o1.getDistanceToGcRoot() - o2.getDistanceToGcRoot();
- }
- });
- private Instance mPreviousInstance = null;
- private int mVisitDistance = 0;
-
- @Override
- public void visitLater(Instance parent, @NonNull Instance child) {
- if (mVisitDistance < child.getDistanceToGcRoot() &&
- (parent == null ||
- child.getSoftReferences() == null ||
- !child.getSoftReferences().contains(parent) ||
- child.getIsSoftReference())) {
- child.setDistanceToGcRoot(mVisitDistance);
- child.setNextInstanceToGcRoot(mPreviousInstance);
- mPriorityQueue.add(child);
- }
- }
-
- @Override
- public void doVisit(Iterable<? extends Instance> startNodes) {
- // root nodes are instances that share the same id as the node they point to.
- // This means that we cannot mark them as visited here or they would be marking
- // the actual root instance
- // TODO RootObj should not be Instance objects
- for (Instance node : startNodes) {
- node.accept(this);
- }
-
- while (!mPriorityQueue.isEmpty()) {
- Instance node = mPriorityQueue.poll();
- mVisitDistance = node.getDistanceToGcRoot() + 1;
- mPreviousInstance = node;
- node.accept(this);
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/TopologicalSort.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/TopologicalSort.java
deleted file mode 100644
index 3bcc704..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/analysis/TopologicalSort.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap.analysis;
-
-import com.android.annotations.NonNull;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.NonRecursiveVisitor;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.Snapshot;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-import gnu.trove.TLongHashSet;
-
-public class TopologicalSort {
-
- @NonNull
- public static ImmutableList<Instance> compute(@NonNull Iterable<RootObj> roots) {
- TopologicalSortVisitor visitor = new TopologicalSortVisitor();
- visitor.doVisit(roots);
- ImmutableList<Instance> instances = visitor.getOrderedInstances();
-
- // We add the special sentinel node as the single root of the object graph, to ensure the
- // dominator algorithm terminates when having to choose between two GC roots.
- Snapshot.SENTINEL_ROOT.setTopologicalOrder(0);
-
- // Set localIDs in the range 1..keys.size(). This simplifies the algorithm & data structures
- // for dominator computation.
- int currentIndex = 0;
- for (Instance node : instances) {
- node.setTopologicalOrder(++currentIndex);
- }
-
- return instances;
- }
-
-
- /**
- * Topological sort visitor computing a post-order traversal of the graph.
- *
- * We use the classic iterative three-color marking algorithm in order to correctly compute the
- * finishing time for each node. Nodes in decreasing order of their finishing time satisfy the
- * topological order property, i.e. any node appears before its successors.
- */
- private static class TopologicalSortVisitor extends NonRecursiveVisitor {
-
- // Marks nodes that have been fully visited and popped off the stack.
- private final TLongHashSet mVisited = new TLongHashSet();
-
- private final List<Instance> mPostorder = Lists.newArrayList();
-
- @Override
- public void visitLater(Instance parent, @NonNull Instance child) {
- if (!mSeen.contains(child.getId())) {
- mStack.push(child);
- }
- }
-
- @Override
- public void doVisit(Iterable<? extends Instance> startNodes) {
- // root nodes are instances that share the same id as the node they point to.
- // This means that we cannot mark them as visited here or they would be marking
- // the actual root instance
- // TODO RootObj should not be Instance objects
-
- for (Instance node : startNodes) {
- node.accept(this);
- }
- while (!mStack.isEmpty()) {
- Instance node = mStack.peek();
- if (mSeen.add(node.getId())) {
- node.accept(this);
- } else {
- mStack.pop();
- if (mVisited.add(node.getId())) {
- mPostorder.add(node);
- }
- }
- }
- }
-
- ImmutableList<Instance> getOrderedInstances() {
- return ImmutableList.copyOf(Lists.reverse(mPostorder));
- }
- }
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/io/HprofBuffer.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/io/HprofBuffer.java
deleted file mode 100644
index 15d7d6d..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/io/HprofBuffer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.perflib.heap.io;
-
-import java.nio.ByteOrder;
-
-public interface HprofBuffer {
- ByteOrder HPROF_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
-
- byte readByte();
-
- void read(byte[] b);
-
- void readSubSequence(byte[] b, int sourceStart, int sourceEnd);
-
- char readChar();
-
- short readShort();
-
- int readInt();
-
- long readLong();
-
- float readFloat();
-
- double readDouble();
-
- void setPosition(long position);
-
- long position();
-
- boolean hasRemaining();
-
- long remaining();
-}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/io/MemoryMappedFileBuffer.java b/base/perflib/src/main/java/com/android/tools/perflib/heap/io/MemoryMappedFileBuffer.java
deleted file mode 100644
index 7607f0f..0000000
--- a/base/perflib/src/main/java/com/android/tools/perflib/heap/io/MemoryMappedFileBuffer.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap.io;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-
-import sun.nio.ch.DirectBuffer;
-
-public class MemoryMappedFileBuffer implements HprofBuffer {
-
- // Default chunk size is 1 << 30, or 1,073,741,824 bytes.
- private static final int DEFAULT_SIZE = 1 << 30;
-
- // Eliminate wrapped, multi-byte reads across chunks in most cases.
- private static final int DEFAULT_PADDING = 1024;
-
- private final int mBufferSize;
-
- private final int mPadding;
-
- @NonNull
- private final ByteBuffer[] mByteBuffers;
-
- private final long mLength;
-
- private long mCurrentPosition;
-
- @VisibleForTesting
- MemoryMappedFileBuffer(@NonNull File f, int bufferSize, int padding) throws IOException {
- mBufferSize = bufferSize;
- mPadding = padding;
- mLength = f.length();
- int shards = (int) (mLength / mBufferSize) + 1;
- mByteBuffers = new ByteBuffer[shards];
-
- FileInputStream inputStream = new FileInputStream(f);
- try {
- long offset = 0;
- for (int i = 0; i < shards; i++) {
- long size = Math.min(mLength - offset, mBufferSize + mPadding);
- mByteBuffers[i] = inputStream.getChannel()
- .map(FileChannel.MapMode.READ_ONLY, offset, size);
- mByteBuffers[i].order(HPROF_BYTE_ORDER);
- offset += mBufferSize;
- }
- mCurrentPosition = 0;
- } finally {
- inputStream.close();
- }
- }
-
- /**
- * Creates a buffer by memory-mapping file {@param f}.
- *
- * It may be a good idea to dispose() the buffer if no longer needed. A garbage collection isn't
- * guaranteed to free up the resources, and in a long-running 32-bit JVM there's the risk of
- * exhausting the address space this way. On Windows, mmap locks the file, preventing it from
- * being deleted. See {@link http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4715154}.
- */
- public MemoryMappedFileBuffer(@NonNull File f) throws IOException {
- this(f, DEFAULT_SIZE, DEFAULT_PADDING);
- }
-
- /**
- * Attempts to unmap the buffer. It is the caller's responsibility to ensure there are no other
- * accesses to this buffer, otherwise this can result in a crash and kill the JVM.
- */
- public void dispose() {
- try {
- for (int i = 0; i < mByteBuffers.length; i++) {
- ((DirectBuffer) mByteBuffers[i]).cleaner().clean();
- }
- } catch (Exception ex) {
- // ignore, this is a best effort attempt.
- }
- }
-
- @Override
- public byte readByte() {
- byte result = mByteBuffers[getIndex()].get(getOffset());
- mCurrentPosition++;
- return result;
- }
-
- @Override
- public void read(@NonNull byte[] b) {
- int index = getIndex();
- mByteBuffers[index].position(getOffset());
- if (b.length <= mByteBuffers[index].remaining()) {
- mByteBuffers[index].get(b, 0, b.length);
- } else {
- // Wrapped read
- int split = mBufferSize - mByteBuffers[index].position();
- mByteBuffers[index].get(b, 0, split);
- mByteBuffers[index + 1].position(0);
- mByteBuffers[index + 1].get(b, split, b.length - split);
- }
- mCurrentPosition += b.length;
- }
-
- @Override
- public void readSubSequence(@NonNull byte[] b, int sourceStart, int length) {
- assert length < mLength;
-
- mCurrentPosition += sourceStart;
-
- int index = getIndex();
- mByteBuffers[index].position(getOffset());
- if (b.length <= mByteBuffers[index].remaining()) {
- mByteBuffers[index].get(b, 0, b.length);
- } else {
- int split = mBufferSize - mByteBuffers[index].position();
- mByteBuffers[index].get(b, 0, split);
-
- int start = split;
- int remainingMaxLength = Math.min(length - start, b.length - start);
- int remainingShardCount = (remainingMaxLength + mBufferSize - 1) / mBufferSize;
- for (int i = 0; i < remainingShardCount; ++i) {
- int maxToRead = Math.min(remainingMaxLength, mBufferSize);
- mByteBuffers[index + 1 + i].position(0);
- mByteBuffers[index + 1 + i].get(b, start, maxToRead);
- start += maxToRead;
- remainingMaxLength -= maxToRead;
- }
- }
-
- mCurrentPosition += Math.min(b.length, length);
- }
-
- @Override
- public char readChar() {
- char result = mByteBuffers[getIndex()].getChar(getOffset());
- mCurrentPosition += 2;
- return result;
- }
-
- @Override
- public short readShort() {
- short result = mByteBuffers[getIndex()].getShort(getOffset());
- mCurrentPosition += 2;
- return result;
- }
-
- @Override
- public int readInt() {
- int result = mByteBuffers[getIndex()].getInt(getOffset());
- mCurrentPosition += 4;
- return result;
- }
-
- @Override
- public long readLong() {
- long result = mByteBuffers[getIndex()].getLong(getOffset());
- mCurrentPosition += 8;
- return result;
- }
-
- @Override
- public float readFloat() {
- float result = mByteBuffers[getIndex()].getFloat(getOffset());
- mCurrentPosition += 4;
- return result;
- }
-
- @Override
- public double readDouble() {
- double result = mByteBuffers[getIndex()].getDouble(getOffset());
- mCurrentPosition += 8;
- return result;
- }
-
- @Override
- public void setPosition(long position) {
- mCurrentPosition = position;
- }
-
- @Override
- public long position() {
- return mCurrentPosition;
- }
-
- @Override
- public boolean hasRemaining() {
- return mCurrentPosition < mLength;
- }
-
- @Override
- public long remaining() {
- return mLength - mCurrentPosition;
- }
-
- private int getIndex() {
- return (int) (mCurrentPosition / mBufferSize);
- }
-
- private int getOffset() {
- return (int) (mCurrentPosition % mBufferSize);
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/ClassObjTest.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/ClassObjTest.java
deleted file mode 100644
index 9374e33..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/ClassObjTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.perflib.heap;
-
-import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
-import junit.framework.TestCase;
-
-import java.io.File;
-
-public class ClassObjTest extends TestCase {
-
- Snapshot mSnapshot;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- File file = new File(getClass().getResource("/dialer.android-hprof").getFile());
- mSnapshot = (new HprofParser(new MemoryMappedFileBuffer(file))).parse();
- }
-
- public void testGetAllFieldsCount() {
- ClassObj application = mSnapshot.findClass("android.app.Application");
- assertNotNull(application);
- assertEquals(5, application.getAllFieldsCount());
-
- assertNull(application.getClassObj());
-
- ClassObj dialer = mSnapshot.findClass("com.android.dialer.DialerApplication");
- assertNotNull(dialer);
- assertEquals(5, dialer.getAllFieldsCount());
- }
-
- public void testComparison() {
- ClassObj a = new ClassObj(1, null, "TestClassA", 0);
- ClassObj b = new ClassObj(1, null, "TestClassB", 0);
- ClassObj c = new ClassObj(2, null, "TestClassC", 0);
- ClassObj aAlt = new ClassObj(3, null, "TestClassA", 0);
-
- assertEquals(0, a.compareTo(a));
- assertEquals(0, a.compareTo(b)); // This is a weird test case, since IDs are supposed to be unique.
- assertTrue(c.compareTo(a) > 0);
- assertTrue(aAlt.compareTo(a) > 0);
- }
-
- public void testSubClassNameClash() {
- ClassObj superClass = new ClassObj(1, null, "TestClassA", 0);
- ClassObj subClass1 = new ClassObj(2, null, "SubClass", 0);
- ClassObj subClass2 = new ClassObj(3, null, "SubClass", 0);
- superClass.addSubclass(subClass1);
- superClass.addSubclass(subClass2);
-
- assertEquals(2, superClass.getSubclasses().size());
- assertTrue(superClass.getSubclasses().contains(subClass1));
- assertTrue(superClass.getSubclasses().contains(subClass2));
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/HprofParserTest.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/HprofParserTest.java
deleted file mode 100644
index 9b674b2..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/HprofParserTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-public class HprofParserTest extends TestCase {
-
- Snapshot mSnapshot;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- File file = new File(getClass().getResource("/dialer.android-hprof").getFile());
- mSnapshot = (new HprofParser(new MemoryMappedFileBuffer(file))).parse();
- }
-
- public void testHierarchy() {
- ClassObj application = mSnapshot.findClass("android.app.Application");
- assertNotNull(application);
-
- ClassObj contextWrapper = application.getSuperClassObj();
- assertNotNull(contextWrapper);
- assertEquals("android.content.ContextWrapper", contextWrapper.getClassName());
- contextWrapper.getSubclasses().contains(application);
-
- ClassObj context = contextWrapper.getSuperClassObj();
- assertNotNull(context);
- assertEquals("android.content.Context", context.getClassName());
- context.getSubclasses().contains(contextWrapper);
-
- ClassObj object = context.getSuperClassObj();
- assertNotNull(object);
- assertEquals("java.lang.Object", object.getClassName());
- object.getSubclasses().contains(context);
-
- ClassObj none = object.getSuperClassObj();
- assertNull(none);
- }
-
- public void testClassLoaders() {
- ClassObj application = mSnapshot.findClass("android.app.Application");
- assertNull(application.getClassLoader());
-
- ClassObj dialer = mSnapshot.findClass("com.android.dialer.DialerApplication");
- Instance classLoader = dialer.getClassLoader();
- assertNotNull(classLoader);
- assertEquals("dalvik.system.PathClassLoader", classLoader.getClassObj().getClassName());
- }
-
- public void testPrimitiveArrays() {
- ClassObj byteArray = mSnapshot.findClass("byte[]");
- assertEquals(1406, byteArray.getInstancesList().size());
- assertEquals(0, byteArray.getInstanceSize());
- assertEquals(681489, byteArray.getShallowSize());
-
- ArrayInstance byteArrayInstance = (ArrayInstance) mSnapshot.findInstance(0xB0D60401);
- assertEquals(byteArray, byteArrayInstance.getClassObj());
- assertEquals(43224, byteArrayInstance.getSize());
- assertEquals(43224, byteArrayInstance.getCompositeSize());
-
- ClassObj intArrayArray = mSnapshot.findClass("int[][]");
- assertEquals(37, intArrayArray.getInstancesList().size());
-
- ArrayInstance intArrayInstance = (ArrayInstance) mSnapshot.findInstance(0xB0F69F58);
- assertEquals(intArrayArray, intArrayInstance.getClassObj());
- assertEquals(40, intArrayInstance.getSize());
- assertEquals(52, intArrayInstance.getCompositeSize());
-
- ClassObj stringArray = mSnapshot.findClass("java.lang.String[]");
- assertEquals(1396, stringArray.getInstancesList().size());
- }
-
- /**
- * Tests the creation of an Enum class which covers static values, fields of type references,
- * strings and primitive values.
- */
- public void testObjectConstruction() {
- ClassObj clazz = mSnapshot.findClass("java.lang.Thread$State");
- assertNotNull(clazz);
-
- Object object = clazz.getStaticField(Type.OBJECT, "$VALUES");
- assertTrue(object instanceof ArrayInstance);
- ArrayInstance array = (ArrayInstance) object;
- Object[] values = array.getValues();
- assertEquals(6, values.length);
-
- Collection<Instance> instances = clazz.getInstancesList();
- for (Object value : values) {
- assertTrue(value instanceof Instance);
- assertTrue(instances.contains(value));
- }
-
- Object enumValue = clazz.getStaticField(Type.OBJECT, "NEW");
- assertTrue(enumValue instanceof ClassInstance);
- ClassInstance instance = (ClassInstance) enumValue;
- assertSame(clazz, instance.getClassObj());
-
- List<ClassInstance.FieldValue> fields = instance.getFields("name");
- assertEquals(1, fields.size());
- assertEquals(Type.OBJECT, fields.get(0).getField().getType());
- Object name = fields.get(0).getValue();
-
- assertTrue(name instanceof ClassInstance);
- ClassInstance string = (ClassInstance) name;
- assertEquals("java.lang.String", string.getClassObj().getClassName());
- fields = string.getFields("value");
- assertEquals(1, fields.size());
- assertEquals(Type.OBJECT, fields.get(0).getField().getType());
- Object value = fields.get(0).getValue();
- assertTrue(value instanceof ArrayInstance);
- Object[] data = ((ArrayInstance) value).getValues();
- assertEquals(3, data.length);
- assertEquals('N', data[0]);
- assertEquals('E', data[1]);
- assertEquals('W', data[2]);
-
- fields = instance.getFields("ordinal");
- assertEquals(1, fields.size());
- assertEquals(Type.INT, fields.get(0).getField().getType());
- assertEquals(0, fields.get(0).getValue());
- }
-
- /**
- * Tests getValues to make sure it's not adding duplicate entries to the back references.
- */
- public void testDuplicateEntries() {
- mSnapshot = new SnapshotBuilder(2).addReferences(1, 2).addRoot(1).build();
- mSnapshot.computeDominators();
-
- assertEquals(2, mSnapshot.getReachableInstances().size());
- ClassInstance parent = (ClassInstance)mSnapshot.findInstance(1);
- List<ClassInstance.FieldValue> firstGet = parent.getValues();
- List<ClassInstance.FieldValue> secondGet = parent.getValues();
- assertEquals(1, firstGet.size());
- assertEquals(firstGet.size(), secondGet.size());
- Instance child = mSnapshot.findInstance(2);
- assertEquals(1, child.getHardReferences().size());
- }
-
- public void testResolveReferences() {
- mSnapshot = new SnapshotBuilder(1).addRoot(1).build();
- ClassObj subSoftReferenceClass = new ClassObj(98, null, "SubSoftReference", 0);
- subSoftReferenceClass.setSuperClassId(SnapshotBuilder.SOFT_REFERENCE_ID);
- ClassObj subSubSoftReferenceClass = new ClassObj(97, null, "SubSubSoftReference", 0);
- subSubSoftReferenceClass.setSuperClassId(98);
-
- mSnapshot.findClass(SnapshotBuilder.SOFT_REFERENCE_ID).addSubclass(subSoftReferenceClass);
- subSoftReferenceClass.addSubclass(subSubSoftReferenceClass);
-
- mSnapshot.addClass(98, subSoftReferenceClass);
- mSnapshot.addClass(97, subSubSoftReferenceClass);
-
- mSnapshot.resolveReferences();
-
- assertTrue(subSoftReferenceClass.getIsSoftReference());
- assertTrue(subSubSoftReferenceClass.getIsSoftReference());
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/QueriesTest.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/QueriesTest.java
deleted file mode 100644
index 5c68ac1..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/QueriesTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.util.Collection;
-
-public class QueriesTest extends TestCase {
-
- public void testCommonClassesQuery() throws Exception {
- File basic = new File(getClass().getResource("/basic.android-hprof").getFile());
- Snapshot basicSnapshot = (new HprofParser(new MemoryMappedFileBuffer(basic))).parse();
-
- File dialer = new File(getClass().getResource("/dialer.android-hprof").getFile());
- Snapshot dialerSnapshot = (new HprofParser(new MemoryMappedFileBuffer(dialer))).parse();
-
- Collection<ClassObj> classes = Queries.commonClasses(basicSnapshot, dialerSnapshot);
- assertEquals(3521, classes.size());
-
- ClassObj clazz1 = basicSnapshot.findClass("android.app.Application");
- assertNotNull(dialerSnapshot.findClass(clazz1.getClassName()));
- assertTrue(classes.contains(clazz1));
-
- // Application class in basicTest.
- ClassObj clazz2 = basicSnapshot.findClass("com.android.tests.basic.Main");
- assertNull(dialerSnapshot.findClass(clazz2.getClassName()));
- assertFalse(classes.contains(clazz2));
-
- // Application class in Dialer.
- ClassObj clazz3 = dialerSnapshot.findClass("com.android.dialer.DialerApplication");
- assertNull(basicSnapshot.findClass(clazz3.getClassName()));
- assertFalse(classes.contains(clazz2));
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/SnapshotBuilder.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/SnapshotBuilder.java
deleted file mode 100644
index 35f06e9..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/SnapshotBuilder.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap;
-
-import com.android.tools.perflib.heap.io.InMemoryBuffer;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Utility for creating Snapshot objects to be used in tests.
- *
- * As the main concern here is graph connectivity, we only initialize the app heap, creating
- * ClassInstance objects with id in [1..numNodes], each instance pointing to a unique ClassObj.
- * The class ids range in [101..100+numNodes] and their size is set to match the id of their object
- * instance. The default heap holds the roots.
- */
-public class SnapshotBuilder {
- public static final int SOFT_REFERENCE_ID = 99;
-
- public static final int SOFT_AND_HARD_REFERENCE_ID = 98;
-
- private final Snapshot mSnapshot;
-
- private final ClassInstance[] mNodes;
-
- private final int[] mOffsets;
-
- private final ByteBuffer mDirectBuffer;
-
- private final int mMaxTotalNodes;
-
- private short mNextAvailableSoftReferenceNodeId;
-
- private short mNextAvailableSoftAndHardReferenceNodeId;
-
- public SnapshotBuilder(int numNodes) {
- this(numNodes, 0, 0);
- }
-
- public SnapshotBuilder(int numNodes, int numSoftNodes, int numSoftAndHardNodes) {
- mMaxTotalNodes = numNodes + numSoftNodes + numSoftAndHardNodes;
- InMemoryBuffer buffer = new InMemoryBuffer(2 * mMaxTotalNodes * mMaxTotalNodes);
- mDirectBuffer = buffer.getDirectBuffer();
- mOffsets = new int[mMaxTotalNodes + 1];
-
- mSnapshot = new Snapshot(buffer);
- mSnapshot.setHeapTo(13, "testHeap");
- mSnapshot.setIdSize(2);
-
- ClassObj softClazz = new ClassObj(SOFT_REFERENCE_ID, null, ClassObj.getReferenceClassName(), 0);
- softClazz.setClassLoaderId(0);
- softClazz.setFields(new Field[]{new Field(Type.OBJECT, "referent")});
- softClazz.setIsSoftReference();
- mSnapshot.addClass(SOFT_REFERENCE_ID, softClazz);
-
- ClassObj softAndHardClazz = new ClassObj(SOFT_AND_HARD_REFERENCE_ID, null, "SoftAndHardReference", 0);
- softAndHardClazz.setSuperClassId(SOFT_REFERENCE_ID);
- softAndHardClazz.setClassLoaderId(0);
- softAndHardClazz.setFields(new Field[]{new Field(Type.OBJECT, "referent"), new Field(Type.OBJECT, "hardReference")});
- softAndHardClazz.setIsSoftReference();
- mSnapshot.addClass(SOFT_AND_HARD_REFERENCE_ID, softAndHardClazz);
-
- mNodes = new ClassInstance[mMaxTotalNodes + 1];
- for (int i = 1; i <= numNodes; i++) {
- // Use same name classes on different loaders to extend test coverage
- ClassObj clazz = new ClassObj(100 + i, null, "Class" + (i / 2), 0);
- clazz.setClassLoaderId(i % 2);
- clazz.setFields(new Field[0]);
- mSnapshot.addClass(100 + i, clazz);
-
- mOffsets[i] = 2 * (i - 1) * mMaxTotalNodes;
- mNodes[i] = new ClassInstance(i, null, mOffsets[i]);
- mNodes[i].setClassId(100 + i);
- mNodes[i].setSize(i);
- mSnapshot.addInstance(i, mNodes[i]);
- }
-
- mNextAvailableSoftReferenceNodeId = (short)(numNodes + 1);
- for (int i = mNextAvailableSoftReferenceNodeId; i <= mMaxTotalNodes; ++i) {
- mOffsets[i] = 2 * (i - 1) * mMaxTotalNodes;
- mNodes[i] = new ClassInstance(i, null, mOffsets[i]);
- mNodes[i].setClassId(SOFT_REFERENCE_ID);
- mNodes[i].setSize(i);
- mSnapshot.addInstance(i, mNodes[i]);
- }
-
- mNextAvailableSoftAndHardReferenceNodeId = (short)(numNodes + numSoftNodes + 1);
- for (int i = mNextAvailableSoftAndHardReferenceNodeId; i <= mMaxTotalNodes; ++i) {
- mOffsets[i] = 2 * (i - 1) * mMaxTotalNodes;
- mNodes[i] = new ClassInstance(i, null, mOffsets[i]);
- mNodes[i].setClassId(SOFT_AND_HARD_REFERENCE_ID);
- mNodes[i].setSize(i);
- mSnapshot.addInstance(i, mNodes[i]);
- }
- }
-
- public SnapshotBuilder addReferences(int nodeFrom, int... nodesTo) {
- assertEquals(mNodes[nodeFrom].getClassObj().getFields().length, 0);
-
- Field[] fields = new Field[nodesTo.length];
- for (int i = 0; i < nodesTo.length; i++) {
- insertHardReferenceIntoBuffer(nodeFrom, nodesTo[i], i);
- // Fields should support duplicated field names due to inheritance of private fields
- fields[i] = new Field(Type.OBJECT, "duplicated_name");
- }
-
- mNodes[nodeFrom].getClassObj().setFields(fields);
- return this;
- }
-
- /**
- * Inserts a soft reference instance between <code>nodeFrom</code> to <code>nodeTo</code>.
- *
- * @param nodeFrom the parent node
- * @param nodeTo the child node
- * @return this
- */
- public SnapshotBuilder insertSoftReference(int nodeFrom, int nodeToSoftReference) {
- Field[] nodeFromFields = mNodes[nodeFrom].getClassObj().getFields();
- Field[] newFields = Arrays.copyOf(nodeFromFields, nodeFromFields.length + 1);
-
- short softReferenceId = mNextAvailableSoftReferenceNodeId++;
- assert softReferenceId <= mMaxTotalNodes;
- insertSoftReferenceIntoBuffer(nodeFrom, softReferenceId, nodeFromFields.length);
-
- setupSoftReference(softReferenceId, nodeToSoftReference);
- newFields[nodeFromFields.length] = new Field(Type.OBJECT, "soft" + nodeToSoftReference);
- mNodes[nodeFrom].getClassObj().setFields(newFields);
- return this;
- }
-
- public SnapshotBuilder insertSoftAndHardReference(int nodeFrom, int nodeToSoftReference, int nodeToHardReference) {
- Field[] nodeFromFields = mNodes[nodeFrom].getClassObj().getFields();
- Field[] newFields = Arrays.copyOf(nodeFromFields, nodeFromFields.length + 1);
-
- short softReferenceId = mNextAvailableSoftAndHardReferenceNodeId++;
- assert softReferenceId <= mMaxTotalNodes;
- insertSoftReferenceIntoBuffer(nodeFrom, softReferenceId, nodeFromFields.length);
-
- setupSoftAndHardReference(softReferenceId, nodeToSoftReference, nodeToHardReference);
- newFields[nodeFromFields.length] = new Field(Type.OBJECT, "soft" + nodeToSoftReference + "hard" + nodeToHardReference);
- mNodes[nodeFrom].getClassObj().setFields(newFields);
- return this;
- }
-
- public SnapshotBuilder addRoot(int node) {
- RootObj root = new RootObj(RootType.JAVA_LOCAL, node);
- mSnapshot.setToDefaultHeap();
- mSnapshot.addRoot(root);
- return this;
- }
-
- public Snapshot build() {
- return mSnapshot;
- }
-
- private void insertHardReferenceIntoBuffer(int nodeFrom, int nodeTo, int fieldIndex) {
- mDirectBuffer.putShort(mOffsets[nodeFrom] + fieldIndex * 2, (short)nodeTo);
- }
-
- private void insertSoftReferenceIntoBuffer(int nodeFrom, int softReferenceId, int fieldIndex) {
- mDirectBuffer.putShort(mOffsets[nodeFrom] + fieldIndex * 2, (short)softReferenceId);
- }
-
- private void setupSoftReference(int softReferenceId, int referent) {
- mDirectBuffer.putShort(mOffsets[softReferenceId], (short)referent);
- }
-
- private void setupSoftAndHardReference(int softReferenceId, int referent, int hardReference) {
- mDirectBuffer.putShort(mOffsets[softReferenceId], (short)referent);
- mDirectBuffer.putShort(mOffsets[softReferenceId] + 2, (short)hardReference);
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorsTest.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorsTest.java
deleted file mode 100644
index e398a2c..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorsTest.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap.analysis;
-
-import com.android.tools.perflib.heap.*;
-import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-
-public class DominatorsTest extends TestCase {
-
- private Snapshot mSnapshot;
-
- public void testSimpleGraph() {
- mSnapshot = new SnapshotBuilder(6)
- .addReferences(1, 2, 3)
- .addReferences(2, 4, 6)
- .addReferences(3, 4, 5)
- .addReferences(4, 6)
- .addRoot(1)
- .build();
-
- mSnapshot.computeDominators();
-
- assertEquals(6, mSnapshot.getReachableInstances().size());
- assertDominates(1, 2);
- assertDominates(1, 3);
- assertDominates(1, 4);
- assertDominates(1, 6);
- assertDominates(3, 5);
-
- assertParentPathToGc(2, 1);
- assertParentPathToGc(3, 1);
- assertParentPathToGc(4, 2, 3);
- assertParentPathToGc(5, 3);
- assertParentPathToGc(6, 2);
- }
-
- public void testCyclicGraph() {
- mSnapshot = new SnapshotBuilder(4)
- .addReferences(1, 2, 3, 4)
- .addReferences(2, 3)
- .addReferences(3, 4)
- .addReferences(4, 2)
- .addRoot(1)
- .build();
-
- mSnapshot.computeDominators();
-
- assertEquals(4, mSnapshot.getReachableInstances().size());
- assertDominates(1, 2);
- assertDominates(1, 3);
- assertDominates(1, 4);
-
- assertParentPathToGc(2, 1);
- assertParentPathToGc(3, 1);
- assertParentPathToGc(4, 1);
- }
-
- public void testMultipleRoots() {
- mSnapshot = new SnapshotBuilder(6)
- .addReferences(1, 3)
- .addReferences(2, 4)
- .addReferences(3, 5)
- .addReferences(4, 5)
- .addReferences(5, 6)
- .addRoot(1)
- .addRoot(2)
- .build();
-
- mSnapshot.computeDominators();
-
- assertEquals(6, mSnapshot.getReachableInstances().size());
- assertDominates(1, 3);
- assertDominates(2, 4);
- // Node 5 is reachable via both roots, neither of which can be the sole dominator.
- assertEquals(mSnapshot.SENTINEL_ROOT, mSnapshot.findInstance(5).getImmediateDominator());
- assertDominates(5, 6);
-
- assertParentPathToGc(3, 1);
- assertParentPathToGc(4, 2);
- assertParentPathToGc(5, 3, 4);
- assertParentPathToGc(6, 5);
- }
-
- public void testDoublyLinkedList() {
- // Node 1 points to a doubly-linked list 2-3-4-5-6-7-8-9.
- mSnapshot = new SnapshotBuilder(9)
- .addReferences(1, 2)
- .addReferences(2, 3, 9)
- .addReferences(3, 2, 4)
- .addReferences(4, 3, 5)
- .addReferences(5, 4, 6)
- .addReferences(6, 5, 7)
- .addReferences(7, 6, 8)
- .addReferences(8, 7, 9)
- .addReferences(9, 2, 8)
- .addRoot(1)
- .build();
-
- mSnapshot.computeDominators();
-
- assertEquals(45, mSnapshot.findInstance(1).getRetainedSize(1));
- assertEquals(44, mSnapshot.findInstance(2).getRetainedSize(1));
- for (int i = 3; i <= 9; i++) {
- assertEquals(i, mSnapshot.findInstance(i).getRetainedSize(1));
- }
-
- assertParentPathToGc(2, 1);
- assertParentPathToGc(3, 2);
- assertParentPathToGc(9, 2);
- assertParentPathToGc(4, 3);
- assertParentPathToGc(8, 9);
- assertParentPathToGc(5, 4);
- assertParentPathToGc(7, 8);
- assertParentPathToGc(6, 5, 7);
- }
-
- public void testSameClassDifferentLoader() {
- mSnapshot = new SnapshotBuilder(4)
- .addReferences(1, 3, 2)
- .addReferences(3, 2)
- .addRoot(1)
- .build();
-
- assertNotNull(mSnapshot.getHeap(13).getClass(102));
- assertNotNull(mSnapshot.getHeap(13).getClass(103));
-
- mSnapshot.computeDominators();
-
- assertEquals(0, mSnapshot.getHeap(13).getClass(102).getRetainedSize(1));
- assertEquals(0, mSnapshot.getHeap(13).getClass(103).getRetainedSize(1));
- }
-
- public void testTopSort() {
- mSnapshot = new SnapshotBuilder(4)
- .addReferences(1, 3, 2)
- .addReferences(3, 2)
- .addRoot(1)
- .build();
-
- mSnapshot.computeDominators();
-
- assertEquals(6, mSnapshot.findInstance(1).getRetainedSize(1));
- assertEquals(2, mSnapshot.findInstance(2).getRetainedSize(1));
- assertEquals(3, mSnapshot.findInstance(3).getRetainedSize(1));
- }
-
- public void testMultiplePaths() {
- mSnapshot = new SnapshotBuilder(8)
- .addReferences(1, 7, 8)
- .addReferences(7, 2, 3)
- .addReferences(8, 2)
- .addReferences(2, 4)
- .addReferences(3, 5)
- .addReferences(5, 4)
- .addReferences(4, 6)
- .addRoot(1)
- .build();
-
- mSnapshot.computeDominators();
-
- assertEquals(mSnapshot.findInstance(1), mSnapshot.findInstance(4).getImmediateDominator());
- assertEquals(mSnapshot.findInstance(4), mSnapshot.findInstance(6).getImmediateDominator());
- assertEquals(36, mSnapshot.findInstance(1).getRetainedSize(1));
- assertEquals(2, mSnapshot.findInstance(2).getRetainedSize(1));
- assertEquals(8, mSnapshot.findInstance(3).getRetainedSize(1));
- }
-
- public void testReachableInstances() {
- mSnapshot = new SnapshotBuilder(11, 2, 1)
- .addReferences(1, 2, 3)
- .insertSoftReference(1, 11)
- .addReferences(2, 4)
- .addReferences(3, 5, 6)
- .insertSoftReference(4, 9)
- .addReferences(5, 7)
- .addReferences(6, 7)
- .addReferences(7, 8, 10)
- .insertSoftAndHardReference(8, 10, 9)
- .addRoot(1)
- .build();
-
- mSnapshot.computeDominators();
- for (Heap heap : mSnapshot.getHeaps()) {
- ClassObj softClass = heap.getClass(SnapshotBuilder.SOFT_REFERENCE_ID);
- if (softClass != null) {
- assertTrue(softClass.getIsSoftReference());
- }
-
- ClassObj softAndHardClass = heap.getClass(SnapshotBuilder.SOFT_AND_HARD_REFERENCE_ID);
- if (softAndHardClass != null) {
- assertTrue(softAndHardClass.getIsSoftReference());
- }
- }
-
- Instance instance9 = mSnapshot.findInstance(9);
- assertNotNull(instance9);
- assertNotNull(instance9.getSoftReferences());
- assertEquals(1, instance9.getHardReferences().size());
- assertEquals(1, instance9.getSoftReferences().size());
- assertEquals(6, instance9.getDistanceToGcRoot());
-
- Instance instance10 = mSnapshot.findInstance(10);
- assertNotNull(instance10);
- assertNotNull(instance10.getSoftReferences());
- assertEquals(1, instance10.getHardReferences().size());
- assertEquals(1, instance10.getSoftReferences().size());
- assertEquals(4, instance10.getDistanceToGcRoot());
-
- Instance instance11 = mSnapshot.findInstance(11);
- assertNotNull(instance11);
- assertNotNull(instance11.getSoftReferences());
- assertEquals(0, instance11.getHardReferences().size());
- assertEquals(1, instance11.getSoftReferences().size());
- assertEquals(Integer.MAX_VALUE, instance11.getDistanceToGcRoot());
-
- assertEquals(13, mSnapshot.getReachableInstances().size());
- }
-
- public void testSampleHprof() throws Exception {
- File file = new File(ClassLoader.getSystemResource("dialer.android-hprof").getFile());
- mSnapshot = (new HprofParser(new MemoryMappedFileBuffer(file))).parse();
- mSnapshot.computeDominators();
-
- Set<Instance> topologicalSet = new HashSet<Instance>(mSnapshot.getTopologicalOrdering());
- assertEquals(topologicalSet.size(), mSnapshot.getTopologicalOrdering().size());
-
- long totalInstanceCount = 0;
- for (Heap heap : mSnapshot.getHeaps()) {
- totalInstanceCount += heap.getInstances().size();
- totalInstanceCount += heap.getClasses().size();
- }
- assertEquals(43687, totalInstanceCount);
-
- assertEquals(42839, mSnapshot.getReachableInstances().size());
-
- // An object reachable via two GC roots, a JNI global and a Thread.
- Instance instance = mSnapshot.findInstance(0xB0EDFFA0);
- assertEquals(Snapshot.SENTINEL_ROOT, instance.getImmediateDominator());
-
- int appIndex = mSnapshot.getHeapIndex(mSnapshot.getHeap("app"));
- int zygoteIndex = mSnapshot.getHeapIndex(mSnapshot.getHeap("zygote"));
-
- // The largest object in our sample hprof belongs to the zygote
- ClassObj htmlParser = mSnapshot.findClass("android.text.Html$HtmlParser");
- assertEquals(116492, htmlParser.getRetainedSize(zygoteIndex));
- assertEquals(0, htmlParser.getRetainedSize(appIndex));
-
- // One of the bigger objects in the app heap
- ClassObj activityThread = mSnapshot.findClass("android.app.ActivityThread");
- assertEquals(853, activityThread.getRetainedSize(zygoteIndex));
- assertEquals(576, activityThread.getRetainedSize(appIndex));
- }
-
- /**
- * Asserts that nodeA dominates nodeB in mHeap.
- */
- private void assertDominates(int nodeA, int nodeB) {
- assertEquals(mSnapshot.findInstance(nodeA),
- mSnapshot.findInstance(nodeB).getImmediateDominator());
- }
-
- /**
- * Asserts that one of the parents is the direct parent to node in the shortest path to the GC root.
- */
- private void assertParentPathToGc(int node, int... parents) {
- for (int parent : parents) {
- Instance parentInstance = mSnapshot.findInstance(node).getNextInstanceToGcRoot();
- if (parentInstance != null && parentInstance.getId() == parent) {
- return;
- }
- }
- fail();
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/analysis/VisitorsTest.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/analysis/VisitorsTest.java
deleted file mode 100644
index 9b26671..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/analysis/VisitorsTest.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap.analysis;
-
-import com.android.annotations.NonNull;
-import com.android.tools.perflib.heap.*;
-import com.android.tools.perflib.heap.io.InMemoryBuffer;
-import com.google.common.collect.Maps;
-
-import junit.framework.TestCase;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * There are two testing scenarios we want to cover here: basic connectivity between different
- * Instance types, and the visitor's ability to deal with cycles, diamonds, etc. in the graph. For
- * the latter we create heaps that are only concerned with connectivity between nodes.
- */
-public class VisitorsTest extends TestCase {
-
- private final ClassObj mDummyClass = new ClassObj(42, null, "dummy", 0);
-
- private Snapshot mSnapshot;
-
- @Override
- public void setUp() throws Exception {
- mSnapshot = new Snapshot(new InMemoryBuffer(10));
- mSnapshot.setHeapTo(13, "testHeap");
- mDummyClass.setFields(new Field[0]);
- mSnapshot.addClass(42, mDummyClass);
- }
-
- public void testSimpleStaticFieldsGraph() {
- mSnapshot.setIdSize(4);
- final ClassInstance object1 = new ClassInstance(1, null, 0);
- object1.setClassId(42);
- object1.setSize(20);
- mSnapshot.addInstance(1, object1);
-
- final ClassInstance object2 = new ClassInstance(2, null, 0);
- object2.setClassId(42);
- object2.setSize(20);
- mSnapshot.addInstance(2, object2);
-
- ClassObj clazz = new ClassObj(13, null, "FooBar", 0) {
- @NonNull
- @Override
- public Map<Field, Object> getStaticFieldValues() {
- Map<Field, Object> result = Maps.newHashMap();
- result.put(new Field(Type.OBJECT, "foo"), object1);
- result.put(new Field(Type.OBJECT, "bar"), object2);
- return result;
- }
- };
- clazz.setSize(10);
- mSnapshot.addClass(13, clazz);
-
- mSnapshot.setToDefaultHeap();
- RootObj root = new RootObj(RootType.SYSTEM_CLASS, 13);
- mSnapshot.addRoot(root);
-
- // Size of root is 2 x sizeof(mDummyClass) + sizeof(clazz)
- assertEquals(50, root.getCompositeSize());
- }
-
- public void testSimpleArray() {
- mSnapshot.setIdSize(4);
- final ClassInstance object = new ClassInstance(1, null, 0);
- object.setClassId(42);
- object.setSize(20);
- mSnapshot.addInstance(1, object);
-
- ArrayInstance array = new ArrayInstance(2, null, Type.OBJECT, 3, 0) {
- @NonNull
- @Override
- public Object[] getValues() {
- return new Object[] {object, object, object};
- }
- };
- mSnapshot.addInstance(2, array);
-
- mSnapshot.setToDefaultHeap();
- RootObj root = new RootObj(RootType.JAVA_LOCAL, 2);
- mSnapshot.addRoot(root);
-
- // Size of root is sizeof(object) + 3 x sizeof(pointer to object)
- assertEquals(32, root.getCompositeSize());
- }
-
- public void testBasicDiamond() {
- Snapshot snapshot = new SnapshotBuilder(4)
- .addReferences(1, 2, 3)
- .addReferences(2, 4)
- .addReferences(3, 4)
- .addRoot(1)
- .build();
-
- assertEquals(10, snapshot.findInstance(1).getCompositeSize());
- assertEquals(6, snapshot.findInstance(2).getCompositeSize());
- assertEquals(7, snapshot.findInstance(3).getCompositeSize());
- assertEquals(4, snapshot.findInstance(4).getCompositeSize());
- }
-
- public void testBasicCycle() {
- Snapshot snapshot = new SnapshotBuilder(3)
- .addReferences(1, 2)
- .addReferences(2, 3)
- .addReferences(3, 1)
- .addRoot(1)
- .build();
-
- // The composite size is a sum over all nodes participating in the cycle.
- assertEquals(6, snapshot.findInstance(1).getCompositeSize());
- assertEquals(6, snapshot.findInstance(2).getCompositeSize());
- assertEquals(6, snapshot.findInstance(3).getCompositeSize());
- }
-
- public void testTopSortSimpleGraph() {
- Snapshot snapshot = new SnapshotBuilder(6)
- .addReferences(1, 2, 3)
- .addReferences(2, 4, 6)
- .addReferences(3, 4, 5)
- .addReferences(4, 6)
- .addRoot(1)
- .build();
-
- List<Instance> topSort = TopologicalSort.compute(snapshot.getGCRoots());
- assertEquals(6, topSort.size());
- // Make sure finishing times are computed correctly. A visitor simply collecting nodes as
- // they are expanded will not yield the correct order. The correct invariant for a DAG is:
- // for each directed edge (u,v), topsort(u) < topsort(v).
- assertTrue(snapshot.findInstance(1).getTopologicalOrder() <
- snapshot.findInstance(2).getTopologicalOrder());
- assertTrue(snapshot.findInstance(1).getTopologicalOrder() <
- snapshot.findInstance(3).getTopologicalOrder());
- assertTrue(snapshot.findInstance(2).getTopologicalOrder() <
- snapshot.findInstance(4).getTopologicalOrder());
- assertTrue(snapshot.findInstance(2).getTopologicalOrder() <
- snapshot.findInstance(6).getTopologicalOrder());
- assertTrue(snapshot.findInstance(3).getTopologicalOrder() <
- snapshot.findInstance(4).getTopologicalOrder());
- assertTrue(snapshot.findInstance(3).getTopologicalOrder() <
- snapshot.findInstance(5).getTopologicalOrder());
- assertTrue(snapshot.findInstance(4).getTopologicalOrder() <
- snapshot.findInstance(6).getTopologicalOrder());
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/io/HprofBufferTest.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/io/HprofBufferTest.java
deleted file mode 100644
index ddf4210..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/io/HprofBufferTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap.io;
-
-import com.android.tools.perflib.heap.HprofParser;
-import com.android.tools.perflib.heap.Snapshot;
-
-import junit.framework.TestCase;
-import sun.misc.IOUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.RandomAccessFile;
-import java.util.Arrays;
-
-public class HprofBufferTest extends TestCase {
-
- File file = new File(getClass().getResource("/dialer.android-hprof").getFile());
-
- public void testSimpleMapping() throws Exception {
- Snapshot snapshot = (new HprofParser(new MemoryMappedFileBuffer(file))).parse();
- assertSnapshotCorrect(snapshot);
- }
-
- public void testMultiMapping() throws Exception {
- // Split the file into chunks of 4096 bytes each, leave 128 bytes for padding.
- MemoryMappedFileBuffer shardedBuffer = new MemoryMappedFileBuffer(file, 4096, 128);
- Snapshot snapshot = (new HprofParser(shardedBuffer)).parse();
- assertSnapshotCorrect(snapshot);
- }
-
- public void testMultiMappingWrappedRead() throws Exception {
- // Leave just 8 bytes for padding to force wrapped reads.
- MemoryMappedFileBuffer shardedBuffer = new MemoryMappedFileBuffer(file, 9973, 8);
- Snapshot snapshot = (new HprofParser(shardedBuffer)).parse();
- assertSnapshotCorrect(snapshot);
- }
-
- public void testMemoryMappingRemoval() throws Exception {
- File tmpFile = File.createTempFile("test_vm", ".tmp");
- System.err.println("vm temp file: " + tmpFile.getAbsolutePath());
- System.err.println("jvm " + System.getProperty("sun.arch.data.model"));
-
- long n = 500000000L;
- RandomAccessFile raf = new RandomAccessFile(tmpFile, "rw");
- raf.setLength(n);
- raf.write(1);
- raf.seek(n - 1);
- raf.write(2);
- raf.close();
-
- MemoryMappedFileBuffer buffer = new MemoryMappedFileBuffer(tmpFile);
- assertEquals(1, buffer.readByte());
- buffer.setPosition(n - 1);
- assertEquals(2, buffer.readByte());
-
- // On Windows, tmpFile can't be deleted without unmapping it first.
- buffer.dispose();
- tmpFile.delete();
-
- File g = new File(tmpFile.getCanonicalPath());
- assertFalse(g.exists());
- }
-
- public void testSubsequenceReads() throws Exception {
- byte[] fileContents = null;
- FileInputStream fileInputStream = new FileInputStream(file);
- try {
- fileContents = IOUtils.readFully(fileInputStream, -1, true);
- }
- finally {
- fileInputStream.close();
- }
-
- MemoryMappedFileBuffer mappedBuffer = new MemoryMappedFileBuffer(file, 8259, 8);
-
- byte[] buffer = new byte[8190];
- mappedBuffer.readSubSequence(buffer, 0, 8190);
- assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 0, 8190)));
- assertEquals(mappedBuffer.position(), 8190);
-
- buffer = new byte[8190];
- mappedBuffer.setPosition(0);
- mappedBuffer.readSubSequence(buffer, 2000, 8190);
- assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 2000, 2000 + 8190)));
- assertEquals(mappedBuffer.position(), 2000 + 8190);
-
- buffer = new byte[100000];
- mappedBuffer.setPosition(0);
- mappedBuffer.readSubSequence(buffer, 19242, 100000);
- assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 19242, 19242 + 100000)));
- assertEquals(mappedBuffer.position(), 19242 + 100000);
-
- buffer = new byte[8259];
- mappedBuffer.setPosition(0);
- mappedBuffer.readSubSequence(buffer, 0, 8259);
- assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 0, 8259)));
- assertEquals(mappedBuffer.position(), 8259);
-
- buffer = new byte[8259];
- mappedBuffer.setPosition(0);
- mappedBuffer.readSubSequence(buffer, 8259, 8259);
- assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 8259, 8259 + 8259)));
- assertEquals(mappedBuffer.position(), 8259 + 8259);
-
- mappedBuffer.readSubSequence(buffer, 8259, 8259);
- assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 8259 * 3, 8259 * 4)));
- assertEquals(mappedBuffer.position(), 8259 * 4);
- }
-
- private void assertSnapshotCorrect(Snapshot snapshot) {
- assertEquals(11182, snapshot.getGCRoots().size());
- assertEquals(38, snapshot.getHeap(65).getClasses().size());
- assertEquals(1406, snapshot.getHeap(65).getInstances().size());
- assertEquals(3533, snapshot.getHeap(90).getClasses().size());
- assertEquals(38710, snapshot.getHeap(90).getInstances().size());
- }
-}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/heap/io/InMemoryBuffer.java b/base/perflib/src/test/java/com/android/tools/perflib/heap/io/InMemoryBuffer.java
deleted file mode 100644
index 055eb26..0000000
--- a/base/perflib/src/test/java/com/android/tools/perflib/heap/io/InMemoryBuffer.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.perflib.heap.io;
-
-import java.nio.ByteBuffer;
-
-public class InMemoryBuffer implements HprofBuffer {
-
- private final ByteBuffer mBuffer;
-
- public InMemoryBuffer(int capacity) {
- mBuffer = ByteBuffer.allocateDirect(capacity);
- }
-
- public ByteBuffer getDirectBuffer() {
- return mBuffer;
- }
-
- @Override
- public byte readByte() {
- return mBuffer.get();
- }
-
- @Override
- public void read(byte[] b) {
- mBuffer.get(b);
- }
-
- @Override
- public void readSubSequence(byte[] b, int sourceStart, int sourceEnd) {
- ((ByteBuffer)mBuffer.slice().position(sourceStart)).get(b);
- }
-
- @Override
- public char readChar() {
- return mBuffer.getChar();
- }
-
- @Override
- public short readShort() {
- return mBuffer.getShort();
- }
-
- @Override
- public int readInt() {
- return mBuffer.getInt();
- }
-
- @Override
- public long readLong() {
- return mBuffer.getLong();
- }
-
- @Override
- public float readFloat() {
- return mBuffer.getFloat();
- }
-
- @Override
- public double readDouble() {
- return mBuffer.getDouble();
- }
-
- @Override
- public void setPosition(long position) {
- mBuffer.position((int) position);
- }
-
- @Override
- public long position() {
- return mBuffer.position();
- }
-
- @Override
- public boolean hasRemaining() {
- return mBuffer.hasRemaining();
- }
-
- @Override
- public long remaining() {
- return mBuffer.remaining();
- }
-}
diff --git a/base/rpclib/rpclib.iml b/base/rpclib/rpclib.iml
deleted file mode 100644
index 8a3735e..0000000
--- a/base/rpclib/rpclib.iml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module" module-name="annotations" />
- <orderEntry type="module" module-name="util" />
- <orderEntry type="module" module-name="android-annotations" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- </component>
-</module>
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObject.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObject.java
deleted file mode 100644
index f2187af..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObject.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-/**
- * An object that can be encoded and decoded with calls to {@link Encoder#object} and
- * {@link Decoder#object}, respectively.
- */
-public interface BinaryObject extends Decodable, Encodable {
- int NULL_ID = 0;
-
- /**
- * @return the object's unique type identifier.
- */
- ObjectTypeID type();
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObjectCreator.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObjectCreator.java
deleted file mode 100644
index fc6ba34..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObjectCreator.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-/**
- * An object used to construct {@link BinaryObject}s from an RPC encoded type.
- * </p>
- * See: {@link BinaryObject}
- */
-public interface BinaryObjectCreator {
- BinaryObject create();
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Decodable.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Decodable.java
deleted file mode 100644
index 1078664..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Decodable.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.IOException;
-
-/**
- * An object that can be decoded with the {@link Decoder}.
- */
-public interface Decodable {
- void decode(@NotNull Decoder e) throws IOException;
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Decoder.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Decoder.java
deleted file mode 100644
index f9eb68d..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Decoder.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import gnu.trove.TIntObjectHashMap;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-
-/**
- * A decoder of various RPC primitive types.
- * The encoding format is documented at the following link:
- * https://android.googlesource.com/platform/tools/gpu/+/master/binary/doc.go
- */
-public class Decoder {
- @NotNull private final TIntObjectHashMap<BinaryObject> mDecodedMap;
- @NotNull private final InputStream mInputStream;
- @NotNull private final byte[] mBuffer;
-
- public Decoder(@NotNull InputStream in) {
- mDecodedMap = new TIntObjectHashMap<BinaryObject>();
- mInputStream = in;
- mBuffer = new byte[9];
- }
-
- public void read(byte[] buf, int count) throws IOException {
- int off = 0;
- while (off < count) {
- off += mInputStream.read(buf, off, count - off);
- }
- }
-
- private void read(int count) throws IOException {
- read(mBuffer, count);
- }
-
- public boolean bool() throws IOException {
- read(1);
- return mBuffer[0] != 0;
- }
-
- public byte int8() throws IOException {
- read(1);
- return mBuffer[0];
- }
-
- public byte uint8() throws IOException {
- return int8();
- }
-
-
- private long intv() throws IOException {
- long uv = uintv();
- long v = uv >>> 1;
- if ((uv & 1) != 0) {
- v = ~v;
- }
- return v;
- }
-
- private long uintv() throws IOException {
- read(1);
- int count = 0;
- while (((0x80 >> count) & mBuffer[0]) != 0) count++;
- long v = mBuffer[0] & (0xff >> count);
- if (count == 0) {
- return v;
- }
- read(count);
- for (int i = 0; i < count; i++) {
- v = (v << 8) | (mBuffer[i] & 0xffL);
- }
- return v;
- }
-
- public short int16() throws IOException {
- return (short)intv();
- }
-
- public short uint16() throws IOException {
- return (short)uintv();
- }
-
- public int int32() throws IOException {
- return (int)intv();
- }
-
- public int uint32() throws IOException {
- return (int)uintv();
- }
-
- public long int64() throws IOException {
- return intv();
- }
-
- public long uint64() throws IOException {
- return uintv();
- }
-
- public float float32() throws IOException {
- int bits = (int)uintv();
- int shuffled = ((bits & 0x000000ff) << 24) |
- ((bits & 0x0000ff00) << 8) |
- ((bits & 0x00ff0000) >> 8) |
- ((bits & 0xff000000) >>> 24);
- return Float.intBitsToFloat(shuffled);
- }
-
- public double float64() throws IOException {
- long bits = uintv();
- long shuffled = ((bits & 0x00000000000000ffL) << 56) |
- ((bits & 0x000000000000ff00L) << 40) |
- ((bits & 0x0000000000ff0000L) << 24) |
- ((bits & 0x00000000ff000000L) << 8) |
- ((bits & 0x000000ff00000000L) >> 8) |
- ((bits & 0x0000ff0000000000L) >> 24) |
- ((bits & 0x00ff000000000000L) >> 40) |
- ((bits & 0xff00000000000000L) >>> 56);
- return Double.longBitsToDouble(shuffled);
- }
-
- public String string() throws IOException {
- int size = uint32();
- byte[] bytes = new byte[size];
- for (int i = 0; i < size; i++) {
- bytes[i] = int8();
- }
- try {
- return new String(bytes, "UTF-8");
- }
- catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e); // Should never happen
- }
- }
-
- @Nullable
- public BinaryObject object() throws IOException {
- int key = uint32();
-
- if (key == BinaryObject.NULL_ID) {
- return null;
- }
-
- BinaryObject obj = mDecodedMap.get(key);
- if (obj != null) {
- return obj;
- }
-
- ObjectTypeID type = new ObjectTypeID(this);
- BinaryObjectCreator creator = ObjectTypeID.lookup(type);
- if (creator == null) {
- throw new RuntimeException("Unknown type id encountered: " + type);
- }
- obj = creator.create();
- obj.decode(this);
-
- mDecodedMap.put(key, obj);
- return obj;
- }
-
- public InputStream stream() {
- return mInputStream;
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Encodable.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Encodable.java
deleted file mode 100644
index 7b10aeb..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Encodable.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.IOException;
-
-/**
- * An object that can be encoded with the {@link Encoder}.
- */
-public interface Encodable {
- void encode(@NotNull Encoder e) throws IOException;
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Encoder.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Encoder.java
deleted file mode 100644
index d5392b8..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Encoder.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import gnu.trove.TObjectIntHashMap;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-
-/**
- * An encoder of various primitive types.
- * The encoding format is documented at the following link:
- * https://android.googlesource.com/platform/tools/gpu/+/master/binary/doc.go
- */
-public class Encoder {
- @NotNull private final OutputStream mOutputStream;
- @NotNull private final TObjectIntHashMap<BinaryObject> mEncodedMap;
- @NotNull private final byte[] mBuffer;
-
- public Encoder(@NotNull OutputStream out) {
- mEncodedMap = new TObjectIntHashMap<BinaryObject>();
- mOutputStream = out;
- mBuffer = new byte[9];
- }
-
- public void bool(boolean v) throws IOException {
- mBuffer[0] = (byte)(v ? 1 : 0);
- mOutputStream.write(mBuffer, 0, 1);
- }
-
- public void int8(byte v) throws IOException {
- mBuffer[0] = v;
- mOutputStream.write(mBuffer, 0, 1);
- }
-
- public void uint8(short v) throws IOException {
- mBuffer[0] = (byte)(v & 0xff);
- mOutputStream.write(mBuffer, 0, 1);
- }
-
- private void intv(long v) throws IOException {
- long uv = v << 1;
- if (v < 0) uv = ~uv;
- uintv(uv);
- }
-
- private void uintv(long v) throws IOException {
- long space = ~0x7fL;
- int tag = 0;
- for (int o = 8; true; o--) {
- if ((v & space) == 0) {
- mBuffer[o] = (byte)(v | tag);
- mOutputStream.write(mBuffer, o, 9 - o);
- return;
- }
- mBuffer[o] = (byte)(v&0xff);
- v >>>= 8;
- space >>= 1;
- tag =(tag >> 1) | 0x80;
- }
- }
-
- public void int16(short v) throws IOException {
- intv(v);
- }
-
- public void uint16(int v) throws IOException {
- uintv(v);
- }
-
- public void int32(int v) throws IOException {
- intv(v);
- }
-
- public void uint32(long v) throws IOException {
- uintv(v);
- }
-
- public void int64(long v) throws IOException {
- intv(v);
- }
-
- public void uint64(long v) throws IOException {
- uintv(v);
- }
-
- public void float32(float v) throws IOException {
- int bits = Float.floatToIntBits(v);
- int shuffled = ((bits & 0x000000ff) << 24) |
- ((bits & 0x0000ff00) << 8) |
- ((bits & 0x00ff0000) >> 8) |
- ((bits & 0xff000000) >>> 24);
- uintv(shuffled);
- }
-
- public void float64(double v) throws IOException {
- long bits = Double.doubleToLongBits(v);
- long shuffled = ((bits & 0x00000000000000ffL) << 56) |
- ((bits & 0x000000000000ff00L) << 40) |
- ((bits & 0x0000000000ff0000L) << 24) |
- ((bits & 0x00000000ff000000L) << 8) |
- ((bits & 0x000000ff00000000L) >> 8) |
- ((bits & 0x0000ff0000000000L) >> 24) |
- ((bits & 0x00ff000000000000L) >> 40) |
- ((bits & 0xff00000000000000L) >>> 56);
- uintv(shuffled);
- }
-
- public void string(@Nullable String v) throws IOException {
- try {
- if (v == null) {
- uint32(0);
- return;
- }
-
- byte[] bytes = v.getBytes("UTF-8");
- uint32(bytes.length);
- for (byte b : bytes) {
- int8(b);
- }
- }
- catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e); // Should never happen
- }
- }
-
- public void object(@Nullable BinaryObject obj) throws IOException {
- if (obj == null) {
- uint32(BinaryObject.NULL_ID);
- return;
- }
-
- if (mEncodedMap.containsKey(obj)) {
- int key = mEncodedMap.get(obj);
- uint16(key);
- return;
- }
-
- int key = mEncodedMap.size() + 1;
- mEncodedMap.put(obj, key);
- uint32(key);
- obj.type().encode(this);
- obj.encode(this);
- }
-
- public OutputStream stream() {
- return mOutputStream;
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Handle.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Handle.java
deleted file mode 100644
index da5443a..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/Handle.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import org.jetbrains.annotations.NotNull;
-
-import javax.xml.bind.DatatypeConverter;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-/**
- * A binary resource handle. These are used as 20-byte-unique identifiers to an immutable object or
- * immutable data. They are often returned from RPC calls and if the client already has the object
- * or data cached locally, offer a way to avoid an unnecessary transfer of data from the server.
- */
-public class Handle {
- private static final int SIZE = 20;
- @NotNull private final byte[] mValue = new byte[SIZE];
- private final int mHashCode;
-
- public Handle(@NotNull byte[] value) {
- assert value.length == SIZE;
- System.arraycopy(value, 0, mValue, 0, SIZE);
- mHashCode = ByteBuffer.wrap(mValue).getInt();
- }
-
- public Handle(@NotNull Decoder d) throws IOException {
- assert d.stream().read(mValue) == SIZE;
- mHashCode = ByteBuffer.wrap(mValue).getInt();
- }
-
- public void encode(@NotNull Encoder e) throws IOException {
- e.stream().write(mValue);
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof Handle)) {
- return false;
- }
- if (other == this) {
- return true;
- }
- return Arrays.equals(mValue, ((Handle)other).mValue);
- }
-
- @Override
- public String toString() {
- return DatatypeConverter.printHexBinary(mValue).toLowerCase();
- }
-
- @Override
- public int hashCode() {
- return mHashCode;
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/ObjectTypeID.java b/base/rpclib/src/main/java/com/android/tools/rpclib/binary/ObjectTypeID.java
deleted file mode 100644
index c7e30ef..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/binary/ObjectTypeID.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * An object used to hold the registry of type id to {@link BinaryObjectCreator}s.
- * </p>
- * See: {@link BinaryObject}
- */
-public class ObjectTypeID extends Handle {
- private static Map<ObjectTypeID, BinaryObjectCreator> registry = new HashMap<ObjectTypeID, BinaryObjectCreator>();
-
- public ObjectTypeID(byte[] value) {
- super(value);
- }
-
- public ObjectTypeID(Decoder d) throws IOException {
- super(d);
- }
-
- public static void register(ObjectTypeID id, BinaryObjectCreator creator) {
- registry.put(id, creator);
- }
-
- public static BinaryObjectCreator lookup(ObjectTypeID id) {
- return registry.get(id);
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Channel.java b/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Channel.java
deleted file mode 100644
index f469234..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Channel.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.multiplex;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class Channel implements Closeable {
- private final OutputStream mOutputStream;
- private final InputStream mInputStream;
- private final PipeInputStream mPipeInputStream;
- private final long mId;
- private final EventHandler mEventHandler;
- private boolean mIsClosed;
-
- public Channel(long id, @NotNull EventHandler events) throws IOException {
- PipeInputStream in = new PipeInputStream();
-
- mId = id;
- mEventHandler = events;
- mInputStream = in;
- mOutputStream = new Output();
- mPipeInputStream = in;
- }
-
- public InputStream getInputStream() {
- return mInputStream;
- }
-
- public OutputStream getOutputStream() {
- return mOutputStream;
- }
-
- @Override
- public synchronized void close() throws IOException {
- if (!mIsClosed) {
- mIsClosed = true;
- mEventHandler.closeChannel(mId);
- mInputStream.close();
- mOutputStream.close();
- }
- }
-
- void receive(byte[] data) throws IOException {
- mPipeInputStream.getSource().write(data);
- }
-
- synchronized void closeNoEvent() throws IOException {
- if (!mIsClosed) {
- mIsClosed = true;
- mInputStream.close();
- mOutputStream.close();
- }
- }
-
- interface EventHandler {
- void closeChannel(long id) throws IOException;
- void writeChannel(long id, byte b[], int off, int len) throws IOException;
- }
-
- private class Output extends OutputStream {
- @Override
- public void write(int b) throws IOException, UnsupportedOperationException {
- write(new byte[]{(byte)b}, 0, 1);
- // We really, really should not be writing out single bytes. For now, throw an exception.
- throw new UnsupportedOperationException("Use write(byte[], int, int) instead of writing single bytes!");
- }
-
- @Override
- public void write(byte b[], int off, int len) throws IOException {
- mEventHandler.writeChannel(mId, b, off, len);
- }
-
- @Override
- public void close() throws IOException {
- Channel.this.close();
- }
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Multiplexer.java b/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Multiplexer.java
deleted file mode 100644
index 2967c73..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Multiplexer.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.multiplex;
-
-import com.android.annotations.concurrency.GuardedBy;
-import com.android.tools.rpclib.binary.Decoder;
-import com.android.tools.rpclib.binary.Encoder;
-import com.intellij.openapi.diagnostic.Logger;
-import gnu.trove.TLongObjectHashMap;
-import gnu.trove.TLongObjectIterator;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicLong;
-
-public class Multiplexer {
- @NotNull private static final Logger LOG = Logger.getInstance(Multiplexer.class);
- private final Decoder mDecoder;
- private final Encoder mEncoder;
- private final NewChannelListener mNewChannelListener;
- private final Channel.EventHandler mChannelEventHandler;
- private final Sender mSender;
- private final AtomicLong mNextChannelId;
- @GuardedBy("mChannelMap") private final TLongObjectHashMap<Channel> mChannelMap;
-
- public Multiplexer(@NotNull InputStream in, @NotNull OutputStream out, int mtu,
- @NotNull ExecutorService executorService,
- @Nullable NewChannelListener newChannelListener) {
- mDecoder = new Decoder(in);
- mEncoder = new Encoder(out);
- mNewChannelListener = newChannelListener;
- mChannelEventHandler = new ChannelEventHandler();
- mSender = new Sender(mtu, executorService);
- mChannelMap = new TLongObjectHashMap<Channel>();
- mNextChannelId = new AtomicLong(0);
- executorService.execute(new Receiver());
- }
-
- public Channel openChannel() throws IOException {
- final long id = mNextChannelId.getAndIncrement();
- Channel channel = newChannel(id);
- mSender.sendOpenChannel(id);
- return channel;
- }
-
- private Channel newChannel(final long id) throws IOException {
- Channel channel = new Channel(id, mChannelEventHandler);
-
- synchronized (mChannelMap) {
- if (mChannelMap.isEmpty()) {
- mSender.begin(mEncoder);
- }
- mChannelMap.put(id, channel);
- }
-
- return channel;
- }
-
- private void deleteChannel(long id) {
- synchronized (mChannelMap) {
- if (mChannelMap.containsKey(id)) {
- // TODO: Mark channel closed.
- mChannelMap.remove(id);
- if (mChannelMap.isEmpty()) {
- mSender.end();
- }
- }
- else {
- // This can happen when both ends close simultaneously.
- LOG.info("Attempting to close unknown channel " + id);
- }
- }
- }
-
- private Channel getChannel(long id) {
- Channel channel;
- synchronized (mChannelMap) {
- channel = mChannelMap.get(id);
- }
- return channel;
- }
-
- private void closeAllChannels() {
- synchronized (mChannelMap) {
- for (TLongObjectIterator<Channel> it = mChannelMap.iterator(); it.hasNext(); it.advance()) {
- Channel c = it.value();
- try {
- c.close();
- }
- catch (IOException e) {
- }
- it.remove();
- }
- }
- }
-
- private class ChannelEventHandler implements Channel.EventHandler {
- @Override
- public void closeChannel(long id) throws IOException {
- mSender.sendCloseChannel(id);
- deleteChannel(id);
- }
-
- @Override
- public void writeChannel(long id, byte[] b, int off, int len) throws IOException {
- mSender.sendData(id, b, off, len);
- }
- }
-
- private class Receiver extends Thread {
- Receiver() {
- super("rpclib.multiplex Receiver");
- }
-
- @Override
- public void run() {
- try {
- while (true) {
- short msgType = mDecoder.uint8();
- long id = ~(mDecoder.uint32() & 0xffffffff);
- switch (msgType) {
- case Message.OPEN_CHANNEL: {
- Channel channel = newChannel(id);
- if (mNewChannelListener != null) {
- mNewChannelListener.onNewChannel(channel);
- }
- break;
- }
- case Message.CLOSE_CHANNEL: {
- Channel channel = getChannel(id);
- if (channel != null) {
- channel.closeNoEvent();
- deleteChannel(id);
- }
- break;
- }
- case Message.DATA: {
- int count = mDecoder.uint32();
- byte[] buf = new byte[count];
- for (int offset = 0; offset < count;) {
- offset += mDecoder.stream().read(buf, offset, count-offset);
- }
- Channel channel = getChannel(id);
- if (channel != null) {
- channel.receive(buf);
- }
- else {
- // Likely this channel was closed this side, and we're receiving data
- // that should be dropped on the floor.
- LOG.info("Received data on unknown channel " + id);
- }
- break;
- }
- default:
- throw new UnsupportedOperationException("Unknown msgType: " + msgType);
- }
- }
- }
- catch (IOException e) {
- LOG.info(e);
- }
- catch (UnsupportedOperationException e) {
- LOG.error(e);
- }
- finally {
- closeAllChannels();
- }
- }
- }
-
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/PipeInputStream.java b/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/PipeInputStream.java
deleted file mode 100644
index 02b6fde..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/PipeInputStream.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.multiplex;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.LinkedList;
-import java.util.concurrent.Semaphore;
-
-/**
- * An object that provides an {@link java.io.InputStream} interface to read data that has been written to a
- * {@link java.io.OutputStream}.
- * <p/>
- * Buffers passed to {@link java.io.OutputStream#write} on the {@link #source} stream are <b>not</b> internally copied,
- * and are assumed immutable. Mutation of any buffers passed to the {@link java.io.OutputStream#write} after the method
- * has returned will result in undefined behaviour when calling the {@link #read} methods.
- * <p/>
- * Note: This is similar to {@link java.io.PipedInputStream} and {@link java.io.PipedOutputStream}, except this
- * implementation does not use an internal ring buffer, and and does not suffer from 1 second stalls (JDK-4404700).
- */
-public class PipeInputStream extends InputStream {
- private static final Item ITEM_CLOSE = new Item(null, 0, 0);
- private final OutputStream mSource;
- private final LinkedList<Item> mQueue;
- private final Semaphore mSemaphore;
- private final byte[] mByte;
-
- PipeInputStream() {
- mQueue = new LinkedList<Item>();
- mSemaphore = new Semaphore(0);
- mByte = new byte[1];
- mSource = new Writer();
- }
-
- public OutputStream getSource() {
- return mSource;
- }
-
- @Override
- public int read() throws IOException {
- return (read(mByte, 0, 1) > 0) ? mByte[0] : -1;
- }
-
- @Override
- public int read(byte b[], int off, int len) throws IOException {
- int n = 0;
- boolean closed = false;
- while (!closed && len > n) {
- try {
- mSemaphore.acquire();
- synchronized (mQueue) {
- Item item = mQueue.getFirst();
- if (item != ITEM_CLOSE) {
- n += item.read(b, off + n, len - n);
- if (item.remaining() == 0) {
- mQueue.removeFirst();
- }
- else {
- mSemaphore.release();
- }
- }
- else {
- closed = true;
- }
- }
- }
- catch (InterruptedException e) {
- break;
- }
- }
- if (n == 0 && closed) {
- return -1;
- }
- return n;
- }
-
- private static class Item {
- private final byte[] mData;
- private final int mCount;
- private int mOffset;
-
- public Item(byte[] data, int offset, int count) {
- mData = data;
- mCount = count;
- mOffset = offset;
- }
-
- public int read(byte[] out, int offset, int count) {
- int remaining = remaining();
- if (count > remaining) {
- count = remaining;
- }
- System.arraycopy(mData, mOffset, out, offset, count);
- mOffset += count;
- return count;
- }
-
- public int remaining() {
- return mCount - mOffset;
- }
- }
-
- private class Writer extends OutputStream {
- @Override
- public void write(int b) throws IOException {
- write(new byte[]{(byte)b}, 0, 1);
- }
-
- @Override
- public void write(byte b[], int off, int len) throws IOException {
- if (len > 0) {
- synchronized (mQueue) {
- mQueue.addLast(new Item(b, off, len));
- }
- mSemaphore.release();
- }
- }
-
- @Override
- public void close() throws IOException {
- synchronized (mQueue) {
- mQueue.addLast(ITEM_CLOSE);
- mSemaphore.release();
- }
- }
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Sender.java b/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Sender.java
deleted file mode 100644
index 2938cfe..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Sender.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.multiplex;
-
-import com.android.tools.rpclib.binary.Encoder;
-import gnu.trove.TLongObjectHashMap;
-import gnu.trove.TLongObjectIterator;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.IOException;
-import java.util.ArrayDeque;
-import java.util.Queue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-
-class Sender {
- private static final int MAX_PENDING_SEND_COUNT = 1024;
- private static final SendItem NOP_ITEM = new SendNop();
- private final int mMtu;
- @NotNull private ExecutorService mExecutorService;
- @NotNull private final LinkedBlockingQueue<SendItem> mPendingItems;
- private Worker mWorker;
-
- Sender(int mtu, @NotNull ExecutorService executorService) {
- mMtu = mtu;
- mExecutorService = executorService;
- mPendingItems = new LinkedBlockingQueue<SendItem>(MAX_PENDING_SEND_COUNT);
- }
-
- void begin(Encoder out) {
- mWorker = new Worker(out);
- mExecutorService.execute(mWorker);
- }
-
- void end() {
- try {
- synchronized (mWorker) {
- mWorker.setRunning(false);
- mPendingItems.add(NOP_ITEM); // Unblock the sender
- while (!mWorker.isStopped()) {
- mWorker.wait();
- }
- mWorker = null;
- }
- }
- catch (InterruptedException e) {
- }
- }
-
- void sendData(long channel, byte b[], int off, int len) throws IOException {
- send(new SendData(channel, b, off, len));
- }
-
- void sendOpenChannel(long channel) throws IOException {
- send(new OpenChannel(channel));
- }
-
- void sendCloseChannel(long channel) throws IOException {
- send(new CloseChannel(channel));
- }
-
- private void send(SendItem item) throws IOException {
- if (mWorker == null) {
- throw new RuntimeException("Attempting to send item when sender is not running");
- }
- mPendingItems.add(item);
- item.sync();
- }
-
- private static abstract class SendItem {
- final long mChannel;
- private boolean mDone;
- private IOException mException;
-
- SendItem(long channel) {
- mChannel = channel;
- }
-
- /**
- * Encodes the item to the provided {@link Encoder}, unblocking any calls to {@link #sync}.
- *
- * @return true if the item was fully sent, or false if there is more to send.
- */
- final boolean send(Encoder e) {
- try {
- return encode(e);
- }
- catch (IOException exception) {
- synchronized (this) {
- mException = exception;
- }
- return true;
- }
- finally {
- synchronized (this) {
- mDone = true;
- notifyAll();
- }
- }
- }
-
- /**
- * Waits for {@link #send} to be called, re-throwing an {@link java.io.IOException} if there was an exception
- * thrown while sending the item.
- */
- final void sync() throws IOException {
- synchronized (this) {
- while (!mDone) {
- try {
- this.wait();
- }
- catch (InterruptedException e) {
- }
- }
- if (mException != null) {
- throw mException;
- }
- }
- }
-
-
- /** @return true if the item was fully sent, or false if there is more to send. */
- protected abstract boolean encode(Encoder e) throws IOException;
- }
-
- private static class OpenChannel extends SendItem {
- OpenChannel(long channel) {
- super(channel);
- }
-
- @Override
- protected boolean encode(Encoder e) throws IOException {
- e.uint8(Message.OPEN_CHANNEL);
- e.uint32(mChannel);
- return true;
- }
- }
-
- private static class CloseChannel extends SendItem {
- CloseChannel(long channel) {
- super(channel);
- }
-
- @Override
- protected boolean encode(Encoder e) throws IOException {
- e.uint8(Message.CLOSE_CHANNEL);
- e.uint32(mChannel);
- return true;
- }
- }
-
- /** SendNop encodes nothing, and is simply used to unblock the sender in {@link #end}. */
- private static class SendNop extends SendItem {
- SendNop() {
- super(0);
- }
-
- @Override
- protected boolean encode(Encoder e) {
- return true;
- }
- }
-
- private final class Worker extends Thread {
- private final Encoder mEncoder;
- private boolean mIsRunning;
- private boolean mIsStopped;
-
- Worker(Encoder encoder) {
- super("rpclib.multiplex Sender");
- mEncoder = encoder;
- mIsRunning = true;
- }
-
- public boolean isStopped() {
- return mIsStopped;
- }
-
- public void setRunning(boolean running) {
- mIsRunning = running;
- }
-
- @Override
- public void run() {
- SendMap map = new SendMap();
- try {
- while (mIsRunning) {
- SendItem item;
- if (map.size() == 0) {
- // If there's nothing being worked on, block until we have something.
- item = mPendingItems.take();
- }
- else {
- // If we're busy, grab more work only if there's something there.
- item = mPendingItems.poll();
- }
- if (item != null) {
- map.add(item);
- map.flush(mEncoder);
- }
- }
- // Drain map
- while (map.size() > 0) {
- map.flush(mEncoder);
- }
- // Signal that this thread is done
- synchronized (this) {
- mIsStopped = true;
- notifyAll();
- }
- }
- catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- private class SendData extends SendItem {
- final byte[] mData;
- int mOffset;
- int mLength;
-
- SendData(long channel, byte[] data, int off, int len) {
- super(channel);
- mData = data;
- mOffset = off;
- mLength = len;
- }
-
- @Override
- protected boolean encode(Encoder e) throws IOException {
- e.uint8(Message.DATA);
- e.uint32(mChannel);
- int c = Math.min(mLength, mMtu);
- e.uint32(c);
- e.stream().write(mData, mOffset, c);
- mOffset += c;
- mLength -= c;
- return mLength == 0;
- }
- }
-
- private class SendMap {
- @NotNull private final TLongObjectHashMap<Queue<SendItem>> mQueues =
- new TLongObjectHashMap<Queue<SendItem>>();
-
- public int size() {
- return mQueues.size();
- }
-
- public void add(SendItem item) {
- long channel = item.mChannel;
- Queue<SendItem> queue = mQueues.get(channel);
- if (queue == null) {
- queue = new ArrayDeque<SendItem>();
- mQueues.put(channel, queue);
- }
- queue.add(item);
- }
-
- public void flush(Encoder e) {
- TLongObjectIterator<Queue<SendItem>> it = mQueues.iterator();
- for (int i = mQueues.size(); i-- > 0; ) {
- it.advance();
- Queue<SendItem> queue = it.value();
- SendItem item = queue.peek();
- if (item.send(e)) {
- // Item has been fully encoded.
- queue.remove();
- if (queue.poll() == null) {
- it.remove();
- }
- }
- }
- }
- }
-
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Broadcaster.java b/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Broadcaster.java
deleted file mode 100644
index 20e74b7..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Broadcaster.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.rpccore;
-
-import com.android.tools.rpclib.binary.Decoder;
-import com.android.tools.rpclib.binary.Encoder;
-import com.android.tools.rpclib.multiplex.Channel;
-import com.android.tools.rpclib.multiplex.Multiplexer;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.ExecutorService;
-
-public class Broadcaster {
- private final Multiplexer mMultiplexer;
- private final int mMtu;
-
- public Broadcaster(@NotNull InputStream in, @NotNull OutputStream out, int mtu,
- @NotNull ExecutorService executorService) {
- mMultiplexer = new Multiplexer(in, out, mtu, executorService, null);
- mMtu = mtu;
- }
-
- private static void writeHeader(@NotNull Encoder encoder) throws IOException {
- encoder.int8((byte)'r');
- encoder.int8((byte)'p');
- encoder.int8((byte)'c');
- encoder.int8((byte)'0');
- }
-
- public Result Send(@NotNull Call call) throws IOException, RpcException {
- Channel channel = mMultiplexer.openChannel();
-
- try {
- BufferedOutputStream out = new BufferedOutputStream(channel.getOutputStream(), mMtu);
- Encoder e = new Encoder(out);
- Decoder d = new Decoder(channel.getInputStream());
-
- // Write the RPC header
- writeHeader(e);
-
- // Write the call
- e.object(call);
-
- // Flush the buffer
- out.flush();
-
- // Wait for and read the response
- Object res = d.object();
-
- // Check to see if the response was an error
- if (res instanceof RpcError) {
- throw new RpcException((RpcError)res);
- }
-
- return (Result)res;
- }
- finally {
- // Close the channel
- channel.close();
- }
- }
-
- static {
- // Make sure the RpcError type is properly registered.
- assert ObjectFactory.RpcErrorID != null;
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Call.java b/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Call.java
deleted file mode 100644
index 80b6f79..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Call.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.rpccore;
-
-import com.android.tools.rpclib.binary.BinaryObject;
-
-/**
- * A object that represents a single RPC call.
- */
-public interface Call extends BinaryObject {
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ObjectFactory.java b/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ObjectFactory.java
deleted file mode 100755
index b976145..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ObjectFactory.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * THIS WILL BE REMOVED ONCE THE CODE GENERATOR IS INTEGRATED INTO THE BUILD.
- */
-package com.android.tools.rpclib.rpccore;
-
-import com.android.tools.rpclib.binary.BinaryObject;
-import com.android.tools.rpclib.binary.BinaryObjectCreator;
-import com.android.tools.rpclib.binary.Decoder;
-import com.android.tools.rpclib.binary.Encoder;
-import com.android.tools.rpclib.binary.ObjectTypeID;
-import java.io.IOException;
-
-class ObjectFactory {
- public enum Entries implements BinaryObjectCreator {
- ErrorEnum {
- @Override public BinaryObject create() {
- return new RpcError();
- }
- },
- }
-
- public static byte[] RpcErrorIDBytes = { -2, 118, -32, 58, 68, -93, -64, 56, -37, 98, 46, -29, -13, -28, -7, -121, -7, 25, -66, -3, };
-
- public static ObjectTypeID RpcErrorID = new ObjectTypeID(RpcErrorIDBytes);
-
- static {
- ObjectTypeID.register(RpcErrorID, Entries.ErrorEnum);
- }
-
- public static void encode(Encoder e, RpcError o) throws IOException {
- e.string(o.mMessage);
- }
-
- public static void decode(Decoder d, RpcError o) throws IOException {
- o.mMessage = d.string();
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Result.java b/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Result.java
deleted file mode 100644
index ccae323..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Result.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.rpccore;
-
-import com.android.tools.rpclib.binary.BinaryObject;
-
-public interface Result extends BinaryObject {
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcError.java b/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcError.java
deleted file mode 100644
index 437d6b3..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcError.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.rpccore;
-
-import com.android.tools.rpclib.binary.BinaryObject;
-import com.android.tools.rpclib.binary.Decoder;
-import com.android.tools.rpclib.binary.Encoder;
-import com.android.tools.rpclib.binary.ObjectTypeID;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.IOException;
-
-/**
- * The base RPC error object.
- */
-public class RpcError implements BinaryObject {
- String mMessage;
-
- @Override
- public void encode(@NotNull Encoder e) throws IOException {
- ObjectFactory.encode(e, this);
- }
-
- @Override
- public void decode(@NotNull Decoder d) throws IOException {
- ObjectFactory.decode(d, this);
- }
-
- @Override
- public ObjectTypeID type() {
- return ObjectFactory.RpcErrorID;
- }
-}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcException.java b/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcException.java
deleted file mode 100644
index d9e1171..0000000
--- a/base/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.rpccore;
-
-public class RpcException extends Exception {
- public final RpcError mError;
-
- public RpcException(RpcError error) {
- mError = error;
- }
-
- @Override
- public String getMessage() {
- return mError.mMessage;
- }
-}
diff --git a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/DecoderTest.java b/base/rpclib/src/test/java/com/android/tools/rpclib/binary/DecoderTest.java
deleted file mode 100644
index c891a79..0000000
--- a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/DecoderTest.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import junit.framework.TestCase;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Assert;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-public class DecoderTest extends TestCase {
- public void testDecodeBool() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x01, (byte)0x00
- });
- final boolean[] expected = new boolean[]{true, false};
-
- Decoder d = new Decoder(input);
- for (boolean bool : expected) {
- assertEquals(bool, d.bool());
- }
- }
-
- public void testDecodeInt8() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff
- });
- final byte[] expected = new byte[]{0, 127, -128, -1};
-
- Decoder d = new Decoder(input);
- for (short s8 : expected) {
- assertEquals(s8, d.int8());
- }
- }
-
- public void testDecodeUint8() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff
- });
- final short[] expected = new short[]{0x00, 0x7f, 0x80, 0xff};
-
- Decoder d = new Decoder(input);
- for (short u8 : expected) {
- assertEquals(u8, d.uint8() & 0xff);
- }
- }
-
- public void testDecodeInt16() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0xff, (byte)0xfe,
- (byte)0xc0, (byte)0xff, (byte)0xff,
- (byte)0x01,
- });
- final short[] expected = new short[]{0, 32767, -32768, -1};
-
- Decoder d = new Decoder(input);
- for (short s16 : expected) {
- assertEquals(s16, d.int16());
- }
- }
-
- public void testDecodeUint16() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0xbe, (byte)0xef,
- (byte)0xc0, (byte)0xc0, (byte)0xde
- });
- final int[] expected = new int[]{0, 0xbeef, 0xc0de};
-
- Decoder d = new Decoder(input);
- for (int u16 : expected) {
- assertEquals(u16, d.uint16() & 0xffff);
- }
- }
-
- public void testDecodeInt32() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
- (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
- (byte)0x01
- });
- final int[] expected = new int[]{0, 2147483647, -2147483648, -1};
-
- Decoder d = new Decoder(input);
- for (int s32 : expected) {
- assertEquals(s32, d.int32());
- }
- }
-
- public void testDecodeUint32() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xe1, (byte)0x23, (byte)0x45, (byte)0x67,
- (byte)0xf0, (byte)0x10, (byte)0xab, (byte)0xcd, (byte)0xef
- });
- final long[] expected = new long[]{0, 0x01234567, 0x10abcdef};
-
- Decoder d = new Decoder(input);
- for (long u32 : expected) {
- assertEquals(u32, d.uint32() & 0xffffffff);
- }
- }
-
- public void testDecodeInt64() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
- (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,(byte) 0xff,
- (byte)0x01
- });
- final long[] expected = new long[]{0L, 9223372036854775807L, -9223372036854775808L, -1L};
-
- Decoder d = new Decoder(input);
- for (long s64 : expected) {
- assertEquals(s64, d.int64());
- }
- }
-
- public void testDecodeUint64() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xff, (byte)0x01, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef,
- (byte)0xff, (byte)0xfe, (byte)0xdc, (byte)0xba, (byte)0x98, (byte)0x76, (byte)0x54, (byte)0x32, (byte)0x10
- });
- final long[] expected = new long[]{0L, 0x0123456789abcdefL, 0xfedcba9876543210L};
-
- Decoder d = new Decoder(input);
- for (long u64 : expected) {
- assertEquals(u64, d.uint64());
- }
- }
-
- public void testDecodeFloat32() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0x80, (byte)0x3f,
- (byte)0xc0,(byte) 0x81, (byte)0x42,
- });
- final float[] expected = new float[]{0.F, 1.F, 64.5F};
-
- Decoder d = new Decoder(input);
- for (float f32 : expected) {
- assertEquals(f32, d.float32());
- }
- }
-
- public void testDecodeFloat64() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0xf0, (byte)0x3f,
- (byte)0xe0, (byte)0x20, (byte)0x50, (byte)0x40,
- });
- final double[] expected = new double[]{0.D, 1.D, 64.5D};
-
- Decoder d = new Decoder(input);
- for (double f64 : expected) {
- assertEquals(f64, d.float64());
- }
- }
-
- public void testDecodeString() throws IOException {
- final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
- 0x05, 'H', 'e', 'l', 'l', 'o',
- 0x00, // empty string
- 0x05, 'W', 'o', 'r', 'l', 'd',
-
- 0x15,
- (byte)0xe3, (byte)0x81, (byte)0x93, (byte)0xe3, (byte)0x82, (byte)0x93, (byte)0xe3,
- (byte)0x81, (byte)0xab, (byte)0xe3, (byte)0x81, (byte)0xa1, (byte)0xe3, (byte)0x81,
- (byte)0xaf, (byte)0xe4, (byte)0xb8, (byte)0x96, (byte)0xe7, (byte)0x95, (byte)0x8c
- });
- final String[] expected = new String[]{"Hello", "", "World", "こんにちは世界"};
-
- Decoder d = new Decoder(input);
-
- for (String str : expected) {
- assertEquals(str, d.string());
- }
- }
-
- public void testDecodeObject() throws IOException {
- final byte[] stubObjectTypeIDBytes = new byte[]{
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09
- };
- class DummyObject implements BinaryObject {
- final String dummy = "dummy";
- @Override
- public ObjectTypeID type() {
- return new ObjectTypeID(stubObjectTypeIDBytes);
- }
- @Override
- public void decode(@NotNull Decoder d) throws IOException {
- assert d.string().equals(dummy);
- }
- @Override
- public void encode(@NotNull Encoder e) throws IOException {
- e.string(dummy);
- }
- }
- final BinaryObject dummyObject = new DummyObject();
-
- class DummyObjectCreator implements BinaryObjectCreator {
- @Override public BinaryObject create() {
- return dummyObject;
- }
- }
- final BinaryObjectCreator dummyObjectCreator = new DummyObjectCreator();
- ObjectTypeID.register(dummyObject.type(), dummyObjectCreator);
-
- ByteArrayOutputStream inputBytes = new ByteArrayOutputStream();
-
- // null BinaryObject:
- inputBytes.write(new byte[]{(byte)0x00}); // BinaryObject.NULL_ID
-
- // stubObject:
- inputBytes.write(new byte[]{0x01}); // stubObject reference
- inputBytes.write(stubObjectTypeIDBytes); // stubObject.type()
- inputBytes.write(new byte[]{0x05, 'd', 'u', 'm', 'm', 'y'});
-
- // stubObject again, only by reference this time:
- inputBytes.write(new byte[]{0x01}); // stubObject reference
-
- final ByteArrayInputStream input = new ByteArrayInputStream(inputBytes.toByteArray());
- final BinaryObject[] expected = new BinaryObject[]{null, dummyObject, dummyObject};
-
- Decoder d = new Decoder(input);
- for (BinaryObject obj : expected) {
- assertEquals(obj, d.object());
- }
- }
-}
diff --git a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/EncoderTest.java b/base/rpclib/src/test/java/com/android/tools/rpclib/binary/EncoderTest.java
deleted file mode 100644
index 9b41173..0000000
--- a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/EncoderTest.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import junit.framework.TestCase;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Assert;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-public class EncoderTest extends TestCase {
- public void testEncodeBool() throws IOException {
- final boolean[] input = new boolean[]{true, false};
- final byte[] expected = new byte[]{(byte)0x01, (byte)0x00};
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (boolean bool : input) {
- e.bool(bool);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeInt8() throws IOException {
- final byte[] input = new byte[]{0, 127, -128, -1};
- final byte[] expected = new byte[]{(byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff};
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (byte s8 : input) {
- e.int8(s8);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeUint8() throws IOException {
- final short[] input = new short[]{0x00, 0x7f, 0x80, 0xff};
- final byte[] expected = new byte[]{(byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff};
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (short u8 : input) {
- e.uint8(u8);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeInt16() throws IOException {
- final short[] input = new short[]{0, 32767, -32768, -1};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0xff, (byte)0xfe,
- (byte)0xc0, (byte)0xff, (byte)0xff,
- (byte)0x01,
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (short s16 : input) {
- e.int16(s16);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeUint16() throws IOException {
- final int[] input = new int[]{0, 0xbeef, 0xc0de};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0xbe, (byte)0xef,
- (byte)0xc0, (byte)0xc0, (byte)0xde
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (int u16 : input) {
- e.uint16(u16);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeInt32() throws IOException {
- final int[] input = new int[]{0, 2147483647, -2147483648, -1};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
- (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
- (byte)0x01
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (int s32 : input) {
- e.int32(s32);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeUint32() throws IOException {
- final long[] input = new long[]{0, 0x01234567, 0x10abcdef};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xe1, (byte)0x23, (byte)0x45, (byte)0x67,
- (byte)0xf0, (byte)0x10, (byte)0xab, (byte)0xcd, (byte)0xef
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (long u32 : input) {
- e.uint32(u32);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeInt64() throws IOException {
- final long[] input = new long[]{0L, 9223372036854775807L, -9223372036854775808L, -1L};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
- (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,(byte) 0xff,
- (byte)0x01
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (long s64 : input) {
- e.int64(s64);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeUint64() throws IOException {
- final long[] input = new long[]{0L, 0x0123456789abcdefL, 0xfedcba9876543210L};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xff, (byte)0x01, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef,
- (byte)0xff, (byte)0xfe, (byte)0xdc, (byte)0xba, (byte)0x98, (byte)0x76, (byte)0x54, (byte)0x32, (byte)0x10
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (long u64 : input) {
- e.uint64(u64);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeFloat32() throws IOException {
- final float[] input = new float[]{0.F, 1.F, 64.5F};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0x80, (byte)0x3f,
- (byte)0xc0,(byte) 0x81, (byte)0x42,
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (float f32 : input) {
- e.float32(f32);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeFloat64() throws IOException {
- final double[] input = new double[]{0.D, 1.D, 64.5D};
- final byte[] expected = new byte[]{
- (byte)0x00,
- (byte)0xc0, (byte)0xf0, (byte)0x3f,
- (byte)0xe0, (byte)0x20, (byte)0x50, (byte)0x40,
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (double f64 : input) {
- e.float64(f64);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeString() throws IOException {
- final String[] input = new String[]{null, "Hello", "", "World", "こんにちは世界"};
- final byte[] expected = new byte[]{
- 0x00, // null string
- 0x05, 'H', 'e', 'l', 'l', 'o',
- 0x00, // empty string
- 0x05, 'W', 'o', 'r', 'l', 'd',
-
- 0x15,
- (byte)0xe3, (byte)0x81, (byte)0x93, (byte)0xe3, (byte)0x82, (byte)0x93, (byte)0xe3,
- (byte)0x81, (byte)0xab, (byte)0xe3, (byte)0x81, (byte)0xa1, (byte)0xe3, (byte)0x81,
- (byte)0xaf, (byte)0xe4, (byte)0xb8, (byte)0x96, (byte)0xe7, (byte)0x95, (byte)0x8c
- };
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (String str : input) {
- e.string(str);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-
- public void testEncodeObject() throws IOException {
- final byte[] dummyObjectTypeIDBytes = new byte[]{
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09
- };
- class DummyObject implements BinaryObject {
- final String dummy = "dummy";
- @Override
- public ObjectTypeID type() {
- return new ObjectTypeID(dummyObjectTypeIDBytes);
- }
- @Override
- public void decode(@NotNull Decoder d) throws IOException {
- assert d.string().equals(dummy);
- }
- @Override
- public void encode(@NotNull Encoder e) throws IOException {
- e.string(dummy);
- }
- }
-
- final BinaryObject dummyObject = new DummyObject();
- final BinaryObject[] input = new BinaryObject[]{null, dummyObject, dummyObject};
- byte[] expected = null;
-
- ByteArrayOutputStream expectedStream = new ByteArrayOutputStream();
-
- // null BinaryObject:
- expectedStream.write(new byte[]{(byte)0x00}); // BinaryObject.NULL_ID
-
- // dummyObject:
- expectedStream.write(new byte[]{0x01}); // dummyObject reference
- expectedStream.write(dummyObjectTypeIDBytes); // dummyObject.type()
- expectedStream.write(new byte[]{0x05, 'd', 'u', 'm', 'm', 'y'});
-
- // dummyObject again, only by reference this time:
- expectedStream.write(new byte[]{0x01}); // dummyObject reference
- expected = expectedStream.toByteArray();
-
- ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
- Encoder e = new Encoder(output);
-
- for (BinaryObject obj : input) {
- e.object(obj);
- }
- Assert.assertArrayEquals(expected, output.toByteArray());
- }
-}
diff --git a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/HandleTest.java b/base/rpclib/src/test/java/com/android/tools/rpclib/binary/HandleTest.java
deleted file mode 100644
index 6e5e35b..0000000
--- a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/HandleTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-public class HandleTest extends TestCase {
- static final String handleString = "000102030405060708090a0b0c0d0e0f10111213";
- static final byte[] handleBytes = {
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13
- };
-
- public void testHandleEquality() {
- // Check handle identity.
- Handle handle1 = new Handle(handleBytes);
- assertEquals(handle1, handle1);
-
- // Check equality of two handles created with the same bytes.
- Handle handle2 = new Handle(handleBytes);
- assertEquals(handle1, handle2);
- }
-
- public void testHandleNonEquality() {
- Handle handle = new Handle(handleBytes);
- assertFalse(handle.equals(null));
-
- // Check that we're getting a different handle than the zero-bytes handle.
- Handle zeroHandle = new Handle(new byte[handleBytes.length]);
- assertNotSame(zeroHandle, handle);
-
- // Check that we're getting a different handle if only the last byte differs.
- byte[] handleLastDiffBytes = new byte[handleBytes.length];
- System.arraycopy(handleBytes, 0, handleLastDiffBytes, 0, handleBytes.length);
- handleLastDiffBytes[handleLastDiffBytes.length-1]++;
- Handle handleLastDiff = new Handle(handleLastDiffBytes);
- assertNotSame(handleLastDiff, handle);
-
- // Check that we're getting a different handle if only the first byte differs.
- byte[] handleFirstDiffBytes = new byte[handleBytes.length];
- System.arraycopy(handleBytes, 0, handleFirstDiffBytes, 0, handleBytes.length);
- handleLastDiffBytes[0]++;
- Handle handleFirstDiff = new Handle(handleFirstDiffBytes);
- assertNotSame(handleFirstDiff, handle);
- }
-
- public void testHandleToString() {
- Handle handle = new Handle(handleBytes);
- assertEquals(handleString, handle.toString());
- }
-
- public void testHandleAsKey() {
- Set<Handle> set = new HashSet<Handle>();
-
- Handle handle1 = new Handle(handleBytes);
- set.add(handle1);
- assertTrue(set.contains(handle1));
- assertEquals(1, set.size());
-
- // Two handles with the same bytes should be seen as the same set elements.
- Handle sameHandle = new Handle(handleBytes);
- set.add(sameHandle);
- assertTrue(set.contains(sameHandle));
- assertEquals(1, set.size());
-
- // Two handles with different bytes should be seen as separate elements in a set.
- Handle zeroHandle = new Handle(new byte[20]);
- set.add(zeroHandle);
- assertTrue(set.contains(zeroHandle));
- assertEquals(2, set.size());
- }
-
- public void testDecodeHandle() throws IOException {
- ByteArrayInputStream input = new ByteArrayInputStream(handleBytes);
- Decoder d = new Decoder(input);
-
- Handle handle = new Handle(handleBytes);
- Handle handleFromDecoder = new Handle(d);
- assertEquals(handle, handleFromDecoder);
- }
-}
diff --git a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/ObjectTypeIDTest.java b/base/rpclib/src/test/java/com/android/tools/rpclib/binary/ObjectTypeIDTest.java
deleted file mode 100644
index 244f9be..0000000
--- a/base/rpclib/src/test/java/com/android/tools/rpclib/binary/ObjectTypeIDTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.rpclib.binary;
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-
-public class ObjectTypeIDTest extends TestCase {
- static final byte[] idBytes = {
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13
- };
- static final ObjectTypeID id = new ObjectTypeID(idBytes);
-
- public void testDecodeObjectTypeID() throws IOException {
- ByteArrayInputStream input = new ByteArrayInputStream(idBytes);
- Decoder d = new Decoder(input);
-
- ObjectTypeID idFromDecoder = new ObjectTypeID(d);
- assertEquals(id, idFromDecoder);
- }
-
- public void testRegisterAndLookupObjectTypeID() {
- BinaryObjectCreator dummyObjectCreator = new BinaryObjectCreator() {
- @Override public BinaryObject create() { return null; }
- };
- ObjectTypeID.register(id, dummyObjectCreator);
- assertEquals(ObjectTypeID.lookup(id), dummyObjectCreator);
- }
-}
diff --git a/base/sdk-common/build.gradle b/base/sdk-common/build.gradle
deleted file mode 100644
index cc946c4..0000000
--- a/base/sdk-common/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'jacoco'
-apply plugin: 'sdk-java-lib'
-apply plugin: 'application'
-
-group = 'com.android.tools'
-archivesBaseName = 'sdk-common'
-version = rootProject.ext.baseVersion
-mainClassName = 'com.android.ide.common.vectordrawable.VdPreview'
-
-dependencies {
- compile project(':base:sdklib')
- compile project(':base:builder-test-api')
- compile project(':base:builder-model')
-
- testCompile 'junit:junit:4.12'
- testCompile project(':base:sdklib').sourceSets.test.output
- testCompile project(':base:testutils')
- testCompile 'org.easymock:easymock:3.3'
- testCompile 'org.mockito:mockito-all:1.9.5'
-}
-
-project.ext.pomName = 'Android Tools sdk-common library'
-project.ext.pomDesc = 'sdk-common library used by other Android tools libraries.'
-
-apply from: "$rootDir/buildSrc/base/publish.gradle"
-apply from: "$rootDir/buildSrc/base/bintray.gradle"
-apply from: "$rootDir/buildSrc/base/javadoc.gradle"
-
diff --git a/base/sdk-common/sdk-common-base.iml b/base/sdk-common/sdk-common-base.iml
deleted file mode 100644
index 1c7c2dc..0000000
--- a/base/sdk-common/sdk-common-base.iml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="module" module-name="layoutlib-api-base" exported="" />
- <orderEntry type="module" module-name="common" exported="" />
- <orderEntry type="library" exported="" name="kxml2" level="project" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="module" module-name="testutils" scope="TEST" />
- <orderEntry type="module" module-name="builder-model" exported="" />
- <orderEntry type="library" scope="TEST" name="mockito" level="project" />
- <orderEntry type="module" module-name="builder-test-api" exported="" />
- <orderEntry type="module" module-name="sdklib-base" exported="" />
- <orderEntry type="library" scope="TEST" name="easymock-tools" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/sdk-common/sdk-common.iml b/base/sdk-common/sdk-common.iml
deleted file mode 100644
index b6ca29f..0000000
--- a/base/sdk-common/sdk-common.iml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="module" module-name="layoutlib-api" exported="" />
- <orderEntry type="module" module-name="common" exported="" />
- <orderEntry type="module" module-name="sdklib" exported="" />
- <orderEntry type="library" exported="" name="kxml2" level="project" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="module" module-name="testutils" scope="TEST" />
- <orderEntry type="library" exported="" name="builder-model" level="project" />
- <orderEntry type="library" scope="TEST" name="mockito" level="project" />
- <orderEntry type="library" exported="" name="gson" level="project" />
- <orderEntry type="module" module-name="builder-test-api" />
- <orderEntry type="library" scope="TEST" name="easymock-tools" level="project" />
- </component>
-</module>
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/MessageJsonSerializer.java b/base/sdk-common/src/main/java/com/android/ide/common/blame/MessageJsonSerializer.java
deleted file mode 100644
index 2bb4652..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/blame/MessageJsonSerializer.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import com.google.common.collect.BiMap;
-import com.google.common.collect.EnumHashBiMap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
-import com.google.gson.GsonBuilder;
-import com.google.gson.TypeAdapter;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Class to handle json serialization and deserialization of messages.
- *
- * Reads json objects of the form:
- *
- * <pre>
- * {
- * "kind":"ERROR",
- * "text":"errorText",
- * "original":"unparsed error text: Error in some intermediate file",
- * "sources": [{
- * "file":"/path/to/source.java",
- * "position":{
- * "startLine":1,
- * "startColumn":2,
- * "startOffset":3,
- * "endLine":4,
- * "endColumn":5,
- * "endOffset":6
- * }
- * }]
- * }</pre>
- *
- * All fields, other than text, may be omitted. They have the following defaults:
- *
- * <table>
- * <tr><th>Property</th> <th>Default</th> <th>Notes</th></tr>
- * <tr><td>kind (ERROR, WARNING,
- * INFO, UNKNOWN)</td> <td>UNKNOWN</td> <td></td></tr>
- * <tr><td>text</td> <td><i>Empty String</i></td> <td>Should not be omitted.</td></tr>
- * <tr><td>file (Absolute)</td> <td>{} <i>[unknown]</i></td> <td>See {@link SourceFileJsonTypeAdapter}</td></tr>
- * <tr><td>position</td> <td>UNKNOWN</td> <td></td></tr>
- * <tr><td>startLine,
- * startColumn,
- * startOffset</td> <td>-1 <i>[unknown]</i></td> <td rowspan="2">0-based</td></tr>
- * <tr><td>endLine,
- * endColumn,
- * endOffset</td> <td>startLine, startColumn,
- * startOffset</td></tr>
- * </table>
- *
- * <h4>Notes</h4>
- * <ul>
- * <li>Offset need not be included, if needed by the consumer of the message it can
- * be derived from the file, line and column.</li>
- * <li>If line is included and column is not the message will be considered to apply
- * to the whole line.</li>
- * <li>A message can have multiple sources.</li>
- * </ul>
- * It also can read legacy serialized objects of the form:
- *
- * <pre>{
- * "kind":"ERROR",
- * "text":"errorText",
- * "sourcePath": "/path/to/source.java",
- * "position":{
- * "startLine":1,
- * "startColumn":2,
- * "startOffset":3,
- * "endLine":4,
- * "endColumn":5,
- * "endOffset":6
- * }
- * }</pre>
- *
- * These serializers are implemented using the lower-level TypeAdapter gson API which gives much
- * more control and allow changes to be made without breaking backward compatibility.
- */
-public class MessageJsonSerializer extends TypeAdapter<Message> {
-
- private static final String KIND = "kind";
-
- private static final String TEXT = "text";
-
- private static final String SOURCE_FILE_POSITIONS = "sources";
-
- private static final String RAW_MESSAGE = "original";
-
- private static final String LEGACY_SOURCE_PATH = "sourcePath";
-
- private static final String LEGACY_POSITION = "position";
-
- private static final BiMap<Message.Kind, String> KIND_STRING_ENUM_MAP;
-
- static {
- EnumHashBiMap<Message.Kind, String> map = EnumHashBiMap.create(Message.Kind.class);
- map.put(Message.Kind.ERROR, "error");
- map.put(Message.Kind.WARNING, "warning");
- map.put(Message.Kind.INFO, "info");
- map.put(Message.Kind.STATISTICS, "statistics");
- map.put(Message.Kind.UNKNOWN, "unknown");
- map.put(Message.Kind.SIMPLE, "simple");
- KIND_STRING_ENUM_MAP = Maps.unmodifiableBiMap(map);
- }
-
- private final SourceFilePositionJsonSerializer mSourceFilePositionTypeAdapter;
- private final SourcePositionJsonTypeAdapter mSourcePositionTypeAdapter;
-
- public MessageJsonSerializer() {
- mSourceFilePositionTypeAdapter = new SourceFilePositionJsonSerializer();
- mSourcePositionTypeAdapter = mSourceFilePositionTypeAdapter.getSourcePositionTypeAdapter();
- }
-
- @Override
- public void write(JsonWriter out, Message message) throws IOException {
- out.beginObject()
- .name(KIND).value(KIND_STRING_ENUM_MAP.get(message.getKind()))
- .name(TEXT).value(message.getText())
- .name(SOURCE_FILE_POSITIONS).beginArray();
- for (SourceFilePosition position : message.getSourceFilePositions()) {
- mSourceFilePositionTypeAdapter.write(out, position);
- }
- out.endArray();
- if (!message.getRawMessage().equals(message.getText())) {
- out.name(RAW_MESSAGE).value(message.getRawMessage());
- }
- out.endObject();
- }
-
- @Override
- public Message read(JsonReader in) throws IOException {
- in.beginObject();
- Message.Kind kind = Message.Kind.UNKNOWN;
- String text = "";
- String rawMessage = null;
- ImmutableList.Builder<SourceFilePosition> positions =
- new ImmutableList.Builder<SourceFilePosition>();
- SourceFile legacyFile = SourceFile.UNKNOWN;
- SourcePosition legacyPosition = SourcePosition.UNKNOWN;
- while (in.hasNext()) {
- String name = in.nextName();
- if (name.equals(KIND)) {
- //noinspection StringToUpperCaseOrToLowerCaseWithoutLocale
- Message.Kind theKind = KIND_STRING_ENUM_MAP.inverse()
- .get(in.nextString().toLowerCase());
- kind = (theKind != null) ? theKind : Message.Kind.UNKNOWN;
- } else if (name.equals(TEXT)) {
- text = in.nextString();
- } else if (name.equals(RAW_MESSAGE)) {
- rawMessage = in.nextString();
- } else if (name.equals(SOURCE_FILE_POSITIONS)) {
- switch (in.peek()) {
- case BEGIN_ARRAY:
- in.beginArray();
- while(in.hasNext()) {
- positions.add(mSourceFilePositionTypeAdapter.read(in));
- }
- in.endArray();
- break;
- case BEGIN_OBJECT:
- positions.add(mSourceFilePositionTypeAdapter.read(in));
- break;
- default:
- in.skipValue();
- break;
- }
- } else if (name.equals(LEGACY_SOURCE_PATH)) {
- legacyFile = new SourceFile(new File(in.nextString()));
- } else if (name.equals(LEGACY_POSITION)) {
- legacyPosition = mSourcePositionTypeAdapter.read(in);
- } else {
- in.skipValue();
- }
- }
- in.endObject();
-
- if (legacyFile != SourceFile.UNKNOWN || legacyPosition != SourcePosition.UNKNOWN) {
- positions.add(new SourceFilePosition(legacyFile, legacyPosition));
- }
- if (rawMessage == null) {
- rawMessage = text;
- }
- ImmutableList<SourceFilePosition> sourceFilePositions = positions.build();
- if (!sourceFilePositions.isEmpty()) {
- return new Message(kind, text, rawMessage, sourceFilePositions);
- } else {
- return new Message(kind, text, rawMessage, ImmutableList.of(SourceFilePosition.UNKNOWN));
- }
- }
-
- public static void registerTypeAdapters(GsonBuilder builder) {
- builder.registerTypeAdapter(SourceFile.class, new SourceFileJsonTypeAdapter());
- builder.registerTypeAdapter(SourcePosition.class, new SourcePositionJsonTypeAdapter());
- builder.registerTypeAdapter(SourceFilePosition.class,
- new SourceFilePositionJsonSerializer());
- builder.registerTypeAdapter(Message.class, new MessageJsonSerializer());
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/MessageReceiver.java b/base/sdk-common/src/main/java/com/android/ide/common/blame/MessageReceiver.java
deleted file mode 100644
index 1b6ab98..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/blame/MessageReceiver.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import com.android.annotations.NonNull;
-
-public interface MessageReceiver {
- void receiveMessage(@NonNull Message m);
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/LegacyNdkOutputParser.java b/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/LegacyNdkOutputParser.java
deleted file mode 100644
index 36f802a..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/LegacyNdkOutputParser.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.blame.parser;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.ide.common.blame.parser.util.OutputLineReader;
-import com.android.utils.ILogger;
-import com.android.utils.SdkUtils;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Parses output from the legacy NDK support.
- */
-public class LegacyNdkOutputParser implements PatternAwareOutputParser {
-
- private static final String FROM = "from";
- private static final String UNKNOWN_MSG_PREFIX1 = "In file included " + FROM;
- private static final String UNKNOWN_MSG_PREFIX2 = " " + FROM;
-
- private static final char COLON = ':';
-
- @Override
- public boolean parse(@NonNull String line, @NonNull OutputLineReader reader,
- @NonNull List<Message> messages, @NonNull ILogger logger)
- throws ParsingFailedException {
- // Parses unknown message
- if (line.startsWith(UNKNOWN_MSG_PREFIX1) || line.startsWith(UNKNOWN_MSG_PREFIX2)) {
- int fromIndex = line.indexOf(FROM);
- String unknownMsgCause = line.substring(0, fromIndex).trim();
- unknownMsgCause = "(Unknown) " + unknownMsgCause;
- String coordinates = line.substring(fromIndex + FROM.length()).trim();
- if (!coordinates.isEmpty()) {
- int colonIndex1 = line.indexOf(COLON);
- if (colonIndex1 == 1) { // drive letter (Windows)
- coordinates = coordinates.substring(colonIndex1 + 1);
- }
- if (coordinates.endsWith(",") || coordinates.endsWith(":")) {
- coordinates = coordinates.substring(0, coordinates.length() - 1);
- }
-
- List<String> segments = Splitter.on(COLON).splitToList(coordinates);
- if (segments.size() == 3) {
- String pathname = segments.get(0);
- File file = new File(pathname);
- int lineNumber = 0;
- try {
- lineNumber = Integer.parseInt(segments.get(1));
- }
- catch (NumberFormatException ignore) {
- }
- int column = 0;
- try {
- column = Integer.parseInt(segments.get(2));
- }
- catch (NumberFormatException ignore) {
- }
- SourceFilePosition position = new SourceFilePosition(file,
- new SourcePosition(lineNumber - 1, column - 1, -1));
- Message message = new Message(Message.Kind.INFO, unknownMsgCause.trim(), position);
- if (!messages.contains(message)) {
- // There may be a few duplicate "unknown" messages
- addMessage(message, messages);
- }
- }
- }
- return true;
- }
-
- // Parses unresolved include.
- int colonIndex1 = line.indexOf(COLON);
- if (colonIndex1 == 1) { // drive letter (Windows)
- colonIndex1 = line.indexOf(COLON, colonIndex1 + 1);
- }
- if (colonIndex1 >= 0) { // looks like found something like a file path.
- String part1 = line.substring(0, colonIndex1).trim();
-
- int colonIndex2 = line.indexOf(COLON, colonIndex1 + 1);
- if (colonIndex2 >= 0) {
- File file = new File(part1);
- if (!file.isFile()) {
- // the part one is not a file path.
- return false;
- }
- try {
- int lineNumber = Integer.parseInt(
- line.substring(colonIndex1 + 1, colonIndex2).trim()); // 1-based.
-
- int colonIndex3 = line.indexOf(COLON, colonIndex2 + 1);
- if (colonIndex1 >= 0) {
- int column = Integer.parseInt(
- line.substring(colonIndex2 + 1, colonIndex3).trim());
-
- int colonIndex4 = line.indexOf(COLON, colonIndex3 + 1);
- if (colonIndex4 >= 0) {
- Message.Kind kind = Message.Kind.INFO;
-
- String severity =
- line.substring(colonIndex3 + 1,
- colonIndex4).toLowerCase(Locale.getDefault()).trim();
- if (severity.endsWith("error")) {
- kind = Message.Kind.ERROR;
- } else if (severity.endsWith("warning")) {
- kind = Message.Kind.WARNING;
- }
- String text = line.substring(colonIndex4 + 1).trim();
- List<String> messageList = Lists.newArrayList();
- messageList.add(text);
- String prevLine = null;
- do {
- String nextLine = reader.readLine();
- if (nextLine == null) {
- return false;
- }
- if (nextLine.trim().equals("^")) {
- String messageEnd = reader.readLine();
-
- while (isMessageEnd(messageEnd)) {
- messageList.add(messageEnd.trim());
- messageEnd = reader.readLine();
- }
-
- if (messageEnd != null) {
- reader.pushBack(messageEnd);
- }
- break;
- }
- if (prevLine != null) {
- messageList.add(prevLine);
- }
- prevLine = nextLine;
- } while (true);
-
- if (column >= 0) {
- messageList = convertMessages(messageList);
- StringBuilder buf = new StringBuilder();
- for (String m : messageList) {
- if (buf.length() > 0) {
- buf.append(SdkUtils.getLineSeparator());
- }
- buf.append(m);
- }
- Message msg = new Message(kind, buf.toString(),
- new SourceFilePosition(file,
- new SourcePosition(lineNumber - 1, column - 1, -1)));
- if (!messages.contains(msg)) {
- addMessage(msg, messages);
- }
- return true;
- }
- }
-
- }
- } catch (NumberFormatException ignored) {
- }
- }
- }
- return false;
- }
-
- private static void addMessage(@NonNull Message message, @NonNull List<Message> messages) {
- boolean duplicatesPrevious = false;
- int messageCount = messages.size();
- if (messageCount > 0) {
- Message lastMessage = messages.get(messageCount - 1);
- duplicatesPrevious = lastMessage.equals(message);
- }
- if (!duplicatesPrevious) {
- messages.add(message);
- }
- }
-
- private static boolean isMessageEnd(@Nullable String line) {
- return line != null && !line.isEmpty() && Character.isWhitespace(line.charAt(0));
- }
-
- @NonNull
- private static List<String> convertMessages(@NonNull List<String> messages) {
- if (messages.size() <= 1) {
- return messages;
- }
- final String line0 = messages.get(0);
- final String line1 = messages.get(1);
- final int colonIndex = line1.indexOf(':');
- if (colonIndex > 0) {
- String part1 = line1.substring(0, colonIndex).trim();
- // jikes
- if ("symbol".equals(part1)) {
- String symbol = line1.substring(colonIndex + 1).trim();
- messages.remove(1);
- if (messages.size() >= 2) {
- messages.remove(1);
- }
- messages.set(0, line0 + " " + symbol);
- }
- }
- return messages;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/ToolOutputParser.java b/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/ToolOutputParser.java
deleted file mode 100644
index 4cc42d2..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/ToolOutputParser.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame.parser;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.parser.util.OutputLineReader;
-import com.android.utils.ILogger;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import java.util.Collections;
-import java.util.List;
-
-public class ToolOutputParser {
-
- @NonNull
- private final List<PatternAwareOutputParser> mParsers;
-
- @NonNull
- private final ILogger mLogger;
-
- public ToolOutputParser(@NonNull Iterable<PatternAwareOutputParser> parsers, @NonNull ILogger logger) {
- mParsers = ImmutableList.copyOf(parsers);
- mLogger = logger;
- }
-
- public ToolOutputParser(@NonNull PatternAwareOutputParser [] parsers, @NonNull ILogger logger) {
- mParsers = ImmutableList.copyOf(parsers);
- mLogger = logger;
- }
-
- public ToolOutputParser(@NonNull PatternAwareOutputParser parser, @NonNull ILogger logger) {
- mParsers = ImmutableList.of(parser);
- mLogger = logger;
- }
-
- public List<Message> parseToolOutput(@NonNull String output) {
- OutputLineReader outputReader = new OutputLineReader(output);
-
- if (outputReader.getLineCount() == 0) {
- return Collections.emptyList();
- }
-
- List<Message> messages = Lists.newArrayList();
- String line;
- while ((line = outputReader.readLine()) != null) {
- if (line.isEmpty()) {
- continue;
- }
- boolean handled = false;
- for (PatternAwareOutputParser parser : mParsers) {
- try {
- if (parser.parse(line, outputReader, messages, mLogger)) {
- handled = true;
- break;
- }
- }
- catch (ParsingFailedException e) {
- return Collections.emptyList();
- }
- }
- if (handled) {
- int messageCount = messages.size();
- if (messageCount > 0) {
- Message last = messages.get(messageCount - 1);
- if (last.getText().contains("Build cancelled")) {
- // Build was cancelled, just quit. Extra messages are just confusing noise.
- break;
- }
- }
- }
- else {
- // If none of the standard parsers recogni ze the input, include it as info such
- // that users don't miss potentially vital output such as gradle plugin exceptions.
- // If there is predictable useless input we don't want to appear here, add a custom
- // parser to digest it.
- messages.add(new Message(Message.Kind.SIMPLE, line, SourceFilePosition.UNKNOWN));
- }
- }
- return messages;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AbstractAaptOutputParser.java b/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AbstractAaptOutputParser.java
deleted file mode 100644
index e30eae1..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AbstractAaptOutputParser.java
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.blame.parser.aapt;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.TAG_ITEM;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.ide.common.blame.parser.util.OutputLineReader;
-import com.android.ide.common.blame.parser.ParsingFailedException;
-import com.android.ide.common.blame.parser.PatternAwareOutputParser;
-import com.android.resources.ResourceFolderType;
-import com.android.utils.ILogger;
-import com.android.utils.SdkUtils;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
-import java.net.MalformedURLException;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
- at VisibleForTesting
-public abstract class AbstractAaptOutputParser implements PatternAwareOutputParser {
- /**
- * Portion of the error message which states the context in which the error occurred, such as
- * which property was being processed and what the string value was that caused the error.
- * <pre>
- * error: No resource found that matches the given name (at 'text' with value '@string/foo')
- * </pre>
- */
- private static final Pattern PROPERTY_NAME_AND_VALUE = Pattern
- .compile("\\(at '(.+)' with value '(.*)'\\)");
-
- /**
- * Portion of error message which points to the second occurrence of a repeated resource
- * definition. <p/> Example: error: Resource entry repeatedStyle1 already has bag item
- * android:gravity.
- */
- private static final Pattern REPEATED_RESOURCE = Pattern
- .compile("Resource entry (.+) already has bag item (.+)\\.");
-
- /**
- * Suffix of error message which points to the first occurrence of a repeated resource
- * definition. Example: Originally defined here.
- */
- private static final String ORIGINALLY_DEFINED_HERE = "Originally defined here.";
-
- private static final Pattern NO_RESOURCE_FOUND = Pattern
- .compile("No resource found that matches the given name: attr '(.+)'\\.");
-
- /**
- * Portion of error message which points to a missing required attribute in a resource
- * definition. <p/> Example: error: error: A 'name' attribute is required for <style>
- */
- private static final Pattern REQUIRED_ATTRIBUTE = Pattern
- .compile("A '(.+)' attribute is required for <(.+)>");
-
- // Keep in sync with SdkUtils#FILENAME_PREFIX
- private static final String START_MARKER = "<!-- From: ";
-
- private static final String END_MARKER = " -->";
-
- private static final Cache<String, ReadOnlyDocument> ourDocumentsByPathCache = CacheBuilder
- .newBuilder()
- .weakValues().build();
-
- @VisibleForTesting
- public static File ourRootDir;
-
- @NonNull
- private static SourcePosition findMessagePositionInFile(@NonNull File file, @NonNull String msgText, int locationLine, ILogger logger) {
- SourcePosition exactPosition = findExactMessagePositionInFile(file, msgText, locationLine, logger);
- if (exactPosition != null) {
- return exactPosition;
- } else {
- return new SourcePosition(locationLine, -1, -1);
- }
- }
-
- @Nullable
- private static SourcePosition findExactMessagePositionInFile(@NonNull File file, @NonNull String msgText, int locationLine, @NonNull ILogger logger) {
- Matcher matcher = PROPERTY_NAME_AND_VALUE.matcher(msgText);
- if (matcher.find()) {
- String name = matcher.group(1);
- String value = matcher.group(2);
- if (!value.isEmpty()) {
- return findText(file, name, value, locationLine, logger);
- }
- SourcePosition position1 = findText(file, name, "\"\"", locationLine, logger);
- SourcePosition position2 = findText(file, name, "''", locationLine, logger);
- if (position1 == null) {
- if (position2 == null) {
- // position at the property name instead.
- return findText(file, name, null, locationLine, logger);
- }
- return position2;
- } else if (position2 == null) {
- return position1;
- } else if (position1.getStartOffset() < position2.getStartOffset()) {
- return position1;
- } else {
- return position2;
- }
- }
-
- matcher = REPEATED_RESOURCE.matcher(msgText);
- if (matcher.find()) {
- String property = matcher.group(2);
- return findText(file, property, null, locationLine, logger);
- }
-
- matcher = NO_RESOURCE_FOUND.matcher(msgText);
- if (matcher.find()) {
- String property = matcher.group(1);
- return findText(file, property, null, locationLine, logger);
- }
-
- matcher = REQUIRED_ATTRIBUTE.matcher(msgText);
- if (matcher.find()) {
- String elementName = matcher.group(2);
- return findText(file, '<' + elementName, null, locationLine, logger);
- }
-
- if (msgText.endsWith(ORIGINALLY_DEFINED_HERE)) {
- return findLineStart(file, locationLine, logger);
- }
- return null;
- }
-
- @Nullable
- private static SourcePosition findText(@NonNull File file, @NonNull String first,
- @Nullable String second, int locationLine, @NonNull ILogger logger) {
- ReadOnlyDocument document = getDocument(file, logger);
- if (document == null) {
- return null;
- }
- int offset = document.lineOffset(locationLine);
- if (offset == -1L) {
- return null;
- }
- int resultOffset = document.findText(first, offset);
- if (resultOffset == -1L) {
- return null;
- }
-
- if (second != null) {
- resultOffset = document.findText(second, resultOffset + first.length());
- if (resultOffset == -1L) {
- return null;
- }
- }
-
- int startLineNumber = document.lineNumber(resultOffset);
- int startLineOffset = document.lineOffset(startLineNumber);
- int endResultOffset = resultOffset + (second != null ? second.length() : first.length());
- int endLineNumber = document.lineNumber(endResultOffset);
- int endLineOffset = document.lineOffset((endLineNumber));
- return new SourcePosition(startLineNumber, resultOffset - startLineOffset, resultOffset,
- endLineNumber, endResultOffset - endLineOffset, endResultOffset);
- }
-
- @Nullable
- private static SourcePosition findLineStart(@NonNull File file, int locationLine, ILogger logger) {
- ReadOnlyDocument document = getDocument(file, logger);
- if (document == null) {
- return null;
- }
- int lineOffset = document.lineOffset(locationLine);
- if (lineOffset == -1L) {
- return null;
- }
- int nextLineOffset = document.lineOffset(locationLine + 1);
- if (nextLineOffset == -1) {
- nextLineOffset = document.length();
- }
-
- // Ignore whitespace at the beginning of the line
- int resultOffset = -1;
- for (int i = lineOffset; i < nextLineOffset; i++) {
- char c = document.charAt(i);
- if (!Character.isWhitespace(c)) {
- resultOffset = i;
- break;
- }
- }
- if (resultOffset == -1L) {
- return null;
- }
-
- //Ignore whitespace at the end of the line
- int endResultOffset = resultOffset;
- for (int i = nextLineOffset - 1; i >= resultOffset; i--) {
- char c = document.charAt(i);
- if (!Character.isWhitespace(c)) {
- endResultOffset = i;
- break;
- }
- }
- return new SourcePosition(locationLine, resultOffset - lineOffset, resultOffset,
- locationLine, endResultOffset - lineOffset, endResultOffset);
- }
-
- @Nullable
- private static ReadOnlyDocument getDocument(@NonNull File file, ILogger logger) {
- String filePath = file.getAbsolutePath();
- ReadOnlyDocument document = ourDocumentsByPathCache.getIfPresent(filePath);
- if (document == null || document.isStale()) {
- try {
- if (!file.exists()) {
- if (ourRootDir != null && ourRootDir.isAbsolute() && !file.isAbsolute()) {
- file = new File(ourRootDir, file.getPath());
- return getDocument(file, logger);
- }
- return null;
- }
- document = new ReadOnlyDocument(file);
- ourDocumentsByPathCache.put(filePath, document);
- } catch (IOException e) {
- String format = "Unexpected error occurred while reading file '%s' [%s]";
- logger.warning(format, file.getAbsolutePath(), e);
- return null;
- }
- }
- return document;
- }
-
- @NonNull
- private static String urlToPath(@NonNull String url) {
- if (url.startsWith("file:")) {
- String prefix;
- if (url.startsWith("file://")) {
- prefix = "file://";
- } else {
- prefix = "file:";
- }
- return url.substring(prefix.length());
- }
- return url;
- }
-
- /**
- * Locates a resource value definition in a given file for a given key, and returns the
- * corresponding line number, or -1 if not found. For example, given the key
- * "string/group2_string" it will locate an element {@code <string name="group2_string">} or
- * {@code <item type="string" name="group2_string"}
- */
- public static SourcePosition findResourceLine(@NonNull File file, @NonNull String key, @NonNull ILogger logger) {
- int slash = key.indexOf('/');
- if (slash == -1) {
- assert false : slash; // invalid key format
- return SourcePosition.UNKNOWN;
- }
-
- final String type = key.substring(0, slash);
- final String name = key.substring(slash + 1);
-
- return findValueDeclaration(file, type, name, logger);
- }
-
- /**
- * Locates a resource value declaration in a given file and returns the corresponding line
- * number, or -1 if not found.
- */
- public static SourcePosition findValueDeclaration(@NonNull File file, @NonNull final String type,
- @NonNull final String name, @NonNull ILogger logger) {
- if (!file.exists()) {
- return SourcePosition.UNKNOWN;
- }
-
- final ReadOnlyDocument document = getDocument(file, logger);
- if (document == null) {
- return SourcePosition.UNKNOWN;
- }
-
- // First just do something simple: scan for the string. If it only occurs once, it's easy!
- int index = document.findText(name, 0);
- if (index == -1) {
- return SourcePosition.UNKNOWN;
- }
-
- // See if there are any more occurrences; if not, we're done
- if (document.findText(name, index + name.length()) == -1) {
- return document.sourcePosition(index);
- }
-
- // Try looking for name="$name"
- int nameIndex = document.findText("name=\"" + name + "\"", 0);
- if (nameIndex != -1) {
- // TODO: Disambiguate by type, so if values.xml contains both R.string.foo and R.dimen.foo we
- // pick the right one!
- return document.sourcePosition(nameIndex);
- }
-
- SourcePosition lineNumber = findValueDeclarationViaParse(type, name, document);
- if (!SourcePosition.UNKNOWN.equals(lineNumber)) {
- return lineNumber;
- }
-
- // Just fall back to the first occurrence of the string
- //noinspection ConstantConditions
- assert index != -1;
- return document.sourcePosition(index);
- }
-
- private static SourcePosition findValueDeclarationViaParse(final String type, final String name,
- ReadOnlyDocument document) {
- // Finally do a full SAX parse to identify the position
- final int[] certain = new int[]{-1, 0}; // line,column for exact match
- final int[] possible = new int[]{-1,
- 0}; // line,column for possible match, not confirmed by type
-
- final AtomicReference<Integer> line = new AtomicReference<Integer>(-1);
- final DefaultHandler handler = new DefaultHandler() {
- private int myDepth;
-
- private Locator myLocator;
-
- @Override
- public void setDocumentLocator(final Locator locator) {
- myLocator = locator;
- }
-
- @Override
- public void startElement(String uri, String localName, String qName,
- Attributes attributes) throws SAXException {
- myDepth++;
- if (myDepth == 2) {
- if (name.equals(attributes.getValue(ATTR_NAME))) {
- int lineNumber = myLocator.getLineNumber() - 1;
- int column = myLocator.getColumnNumber() - 1;
- if (qName.equals(type) || TAG_ITEM.equals(qName) && type
- .equals(attributes.getValue(ATTR_TYPE))) {
- line.set(lineNumber);
- certain[0] = lineNumber;
- certain[1] = column;
- } else if (line.get() < 0) {
- // Use a negative number to indicate a match where we're not totally confident (type didn't match)
- line.set(-lineNumber);
- possible[0] = lineNumber;
- possible[1] = column;
- }
- }
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- myDepth--;
- }
- };
-
- SAXParserFactory factory = SAXParserFactory.newInstance();
- try {
- // Parse the input
- SAXParser saxParser = factory.newSAXParser();
- saxParser.parse(new InputSource(new StringReader(document.getContents())), handler);
- } catch (Throwable t) {
- // Ignore parser errors; we might have found the error position earlier than the parse error position
- }
-
- int endLineNumber;
- int endColumn;
- if (certain[0] != -1) {
- endLineNumber = certain[0];
- endColumn = certain[1];
- } else {
- endLineNumber = possible[0];
- endColumn = possible[1];
- }
- if (endLineNumber != -1) {
- // SAX' locator will point to the END of the opening declaration, meaning that if it spans multiple lines, we are pointing
- // to the last line:
- // <item
- // type="dimen"
- // name="attribute"
- // > <--- this is where the locator points, so we need to search backwards
- int endOffset = document.lineOffset(endLineNumber) + endColumn;
- int offset = document.findTextBackwards(name, endOffset);
- if (offset != -1) {
- SourcePosition start = document.sourcePosition(offset);
- return new SourcePosition(start.getStartLine(), start.getStartColumn(), start.getStartOffset(),
- endLineNumber, endColumn, endOffset);
- }
- return new SourcePosition(endLineNumber, endColumn, endOffset);
- }
-
- return SourcePosition.UNKNOWN;
- }
-
- @Nullable
- final Matcher getNextLineMatcher(@NonNull OutputLineReader reader, @NonNull Pattern pattern) {
- // unless we can't, because we reached the last line
- String line = reader.readLine();
- if (line == null) {
- // we expected a 2nd line, so we flag as error and we bail
- return null;
- }
- Matcher m = pattern.matcher(line);
- return m.matches() ? m : null;
- }
-
- @NonNull
- Message createMessage(@NonNull Message.Kind kind,
- @NonNull String text,
- @Nullable String sourcePath,
- @Nullable String lineNumberAsText,
- @NonNull String original,
- ILogger logger) throws ParsingFailedException {
- File file = null;
- if (sourcePath != null) {
- file = new File(sourcePath);
- if (!file.isFile()) {
- throw new ParsingFailedException();
- }
- }
-
- SourcePosition errorPosition = parseLineNumber(lineNumberAsText);
- if (sourcePath != null) {
- SourceFilePosition source = findSourcePosition(file, errorPosition.getStartLine(), text, logger);
- if (source != null) {
- file = source.getFile().getSourceFile();
- sourcePath = file.getPath();
- if (source.getPosition().getStartLine() != -1) {
- errorPosition = source.getPosition();
- }
- }
- }
-
- // Attempt to determine the exact range of characters affected by this error.
- // This will look up the actual text of the file, go to the particular error line and findText for the specific string mentioned in the
- // error.
- if (file != null && errorPosition.getStartLine() != -1) {
- errorPosition = findMessagePositionInFile(file, text, errorPosition.getStartLine(), logger);
- }
- return new Message(kind, text, original, new SourceFilePosition(file, errorPosition));
- }
-
- private SourcePosition parseLineNumber(String lineNumberAsText) throws ParsingFailedException {
- int lineNumber = -1;
- if (lineNumberAsText != null) {
- try {
- lineNumber = Integer.parseInt(lineNumberAsText);
- } catch (NumberFormatException e) {
- throw new ParsingFailedException();
- }
- }
-
- return new SourcePosition(lineNumber - 1, -1, -1);
- }
-
- /**
- *
- * @param file
- * @param locationLine
- * @param message
- * @param logger
- * @return null if could not be found, new SourceFilePosition(new SourceFile file,
- */
- @Nullable
- protected static SourceFilePosition findSourcePosition(@NonNull File file, int locationLine,
- String message, ILogger logger) {
- if (!file.getPath().endsWith(DOT_XML)) {
- return null;
- }
-
- ReadOnlyDocument document = getDocument(file, logger);
- if (document == null) {
- return null;
- }
- // All value files get merged together into a single values file; in that case, we need to
- // search for comment markers backwards which indicates the source file for the current file
-
- int searchStart;
- String fileName = file.getName();
- boolean isManifest = fileName.equals(ANDROID_MANIFEST_XML);
- boolean isValueFile = fileName.startsWith(ResourceFolderType.VALUES.getName());
- if (isValueFile || isManifest) {
- searchStart = document.lineOffset(locationLine);
- } else {
- searchStart = document.length();
- }
- if (searchStart == -1L) {
- return null;
- }
-
- int start = document.findTextBackwards(START_MARKER, searchStart);
- assert start < searchStart;
-
- if (start == -1 && isManifest && searchStart < document.length()) {
- // If the manifest file didn't need to merge, it will place the source reference at the end instead
- searchStart = document.length();
- if (searchStart != -1L) {
- start = document.findTextBackwards(START_MARKER, searchStart);
- assert start < searchStart;
- }
- }
-
- if (start == -1) {
- return null;
- }
- start += START_MARKER.length();
- int end = document.findText(END_MARKER, start);
- if (end == -1) {
- return null;
- }
- String sourcePath = document.subsequence(start, end);
- File sourceFile;
- if (sourcePath.startsWith("file:")) {
- String originalPath = sourcePath;
- sourcePath = urlToPath(sourcePath);
- sourceFile = new File(sourcePath);
- if (!sourceFile.exists()) {
- // JpsPathUtil.urlToPath just chops off the prefix; try a little harder
- // for example to decode %2D's which are used by the MergedResourceWriter to
- // encode --'s in the path, since those are invalid in XML comments
- try {
- sourceFile = SdkUtils.urlToFile(originalPath);
- } catch (MalformedURLException e) {
- logger.warning("Invalid file URL: " + originalPath);
- }
- }
- } else {
- sourceFile = new File(sourcePath);
- }
-
- if (isValueFile) {
- // Look up the line number
- SourcePosition position = findMessagePositionInFile(sourceFile, message,
- 1, logger); // Search from the beginning
- return new SourceFilePosition(new SourceFile(sourceFile), position);
- }
-
- return new SourceFilePosition(new SourceFile(sourceFile), SourcePosition.UNKNOWN);
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/OutputLineReader.java b/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/OutputLineReader.java
deleted file mode 100644
index 692a503..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/OutputLineReader.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.blame.parser.util;
-
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.util.regex.Pattern;
-
-/**
- * Reads a compiler's output line-by-line.
- */
-public class OutputLineReader {
- private static final Pattern LINE_BREAK = Pattern.compile("\\r?\\n");
-
- @NonNull private final String[] myLines;
-
- private final int myLineCount;
- private int myPosition;
-
- /**
- * Creates a new {@link OutputLineReader}.
- *
- * @param text the text to read.
- */
- public OutputLineReader(@NonNull String text) {
- myLines = LINE_BREAK.split(text);
- myLineCount = myLines.length;
- }
-
- public int getLineCount() {
- return myLineCount;
- }
-
- /**
- * Reads the next line of text, moving the line pointer to the next one.
- *
- * @return the contents of the next line, or {@code null} if we reached the end of the text.
- */
- @Nullable
- public String readLine() {
- if (myPosition >= 0 && myPosition < myLineCount) {
- return myLines[myPosition++];
- }
- return null;
- }
-
- /**
- * Reads the text of one the line at the given position, without moving the line pointer.
- *
- * @param lineToSkipCount the number of lines to skip from the line pointer.
- * @return the contents of the specified line, or {@code null} if the specified position is greater than the end of the text.
- */
- @Nullable
- public String peek(int lineToSkipCount) {
- int tempPosition = lineToSkipCount + myPosition;
- if (tempPosition >= 0 && tempPosition < myLineCount) {
- return myLines[tempPosition];
- }
- return null;
- }
-
- public boolean hasNextLine() {
- return myPosition < myLineCount - 1;
- }
-
- public void skipNextLine() {
- myPosition++;
- }
-
- public void pushBack(@NonNull String text) {
- myPosition--;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java b/base/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java
deleted file mode 100644
index 9d578e4..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.internal;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CompletionService;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorCompletionService;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
-/**
- * A utility wrapper around a {@link CompletionService} using an ThreadPoolExecutor so that it
- * is possible to wait on all the tasks.
- *
- * Tasks are submitted as {@link Callable} with {@link #execute(java.util.concurrent.Callable)}.
- *
- * After executing all tasks, it is possible to wait on them with
- * {@link #waitForTasksWithQuickFail(boolean)}, or {@link #waitForAllTasks()}.
- *
- * This class is not Thread safe!
- */
-public class WaitableExecutor<T> {
-
- private final ExecutorService mExecutorService;
- private final CompletionService<T> mCompletionService;
- private final Set<Future<T>> mFutureSet = Sets.newHashSet();
-
- /**
- * Creates an executor that will use at most <var>nThreads</var> threads.
- * @param nThreads the number of threads, or zero for default count (which is number of core)
- */
- public WaitableExecutor(int nThreads) {
- if (nThreads < 1) {
- nThreads = Runtime.getRuntime().availableProcessors();
- }
-
- mExecutorService = Executors.newFixedThreadPool(nThreads);
- mCompletionService = new ExecutorCompletionService<T>(mExecutorService);
- }
-
- /**
- * Creates an executor that will use at most 1 thread per core.
- */
- public WaitableExecutor() {
- mExecutorService = null;
- mCompletionService = new ExecutorCompletionService<T>(ExecutorSingleton.getExecutor());
- }
-
- /**
- * Submits a Callable for execution.
- *
- * @param runnable the callable to run.
- */
- public void execute(Callable<T> runnable) {
- mFutureSet.add(mCompletionService.submit(runnable));
- }
-
- /**
- * Waits for all tasks to be executed. If a tasks throws an exception, it will be thrown from
- * this method inside the ExecutionException, preventing access to the result of the other
- * threads.
- *
- * If you want to get the results of all tasks (result and/or exception), use
- * {@link #waitForAllTasks()}
- *
- * @param cancelRemaining if true, and a task fails, cancel all remaining tasks.
- *
- * @return a list of all the return values from the tasks.
- *
- * @throws InterruptedException if this thread was interrupted. Not if the tasks were interrupted.
- */
- public List<T> waitForTasksWithQuickFail(boolean cancelRemaining) throws InterruptedException,
- LoggedErrorException {
- List<T> results = Lists.newArrayListWithCapacity(mFutureSet.size());
- try {
- while (!mFutureSet.isEmpty()) {
- Future<T> future = mCompletionService.take();
-
- assert mFutureSet.contains(future);
- mFutureSet.remove(future);
-
- // Get the result from the task. If the task threw an exception,
- // this will throw it, wrapped in an ExecutionException, caught below.
- results.add(future.get());
- }
- } catch (ExecutionException e) {
- if (cancelRemaining) {
- cancelAllTasks();
- }
-
- // get the original exception adn throw that one.
- Throwable cause = e.getCause();
- if (cause instanceof LoggedErrorException) {
- throw (LoggedErrorException) cause;
- } else {
- throw new RuntimeException(cause);
- }
- } finally {
- if (mExecutorService != null) {
- mExecutorService.shutdownNow();
- }
- }
-
- return results;
- }
-
- public static final class TaskResult<T> {
- public T value;
- public Throwable exception;
-
- static <T> TaskResult<T> withValue(T value) {
- TaskResult<T> result = new TaskResult<T>(null);
- result.value = value;
- return result;
- }
-
- TaskResult(Throwable cause) {
- exception = cause;
- }
- }
-
- /**
- * Waits for all tasks to be executed, and returns a {@link TaskResult} for each, containing
- * either the result or the exception thrown by the task.
- *
- * If a task is cancelled (and it threw InterruptedException) then the result for the task
- * is *not* included.
- *
- * @return a list of all the return values from the tasks.
- *
- * @throws InterruptedException if this thread was interrupted. Not if the tasks were interrupted.
- */
- public List<TaskResult<T>> waitForAllTasks() throws InterruptedException {
- List<TaskResult<T>> results = Lists.newArrayListWithCapacity(mFutureSet.size());
- try {
- while (!mFutureSet.isEmpty()) {
- Future<T> future = mCompletionService.take();
-
- assert mFutureSet.contains(future);
- mFutureSet.remove(future);
-
- // Get the result from the task.
- try {
- results.add(TaskResult.withValue(future.get()));
- } catch (ExecutionException e) {
- // the original exception thrown by the task is the cause of this one.
- Throwable cause = e.getCause();
-
- //noinspection StatementWithEmptyBody
- if (cause instanceof InterruptedException) {
- // if the task was cancelled we probably don't care about its result.
- } else {
- // there was an error.
- results.add(new TaskResult<T>(cause));
- }
- }
- }
- } finally {
- if (mExecutorService != null) {
- mExecutorService.shutdownNow();
- }
- }
-
- return results;
- }
-
- /**
- * Cancel all remaining tasks.
- */
- public void cancelAllTasks() {
- for (Future<T> future : mFutureSet) {
- future.cancel(true /*mayInterruptIfRunning*/);
- }
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java b/base/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java
deleted file mode 100644
index 19e4a19..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.packaging;
-
-import com.google.common.collect.ImmutableList;
-
-/**
- * Utility class for packaging.
- */
-public class PackagingUtils {
-
- /**
- * Checks whether a folder and its content is valid for packaging into the .apk as
- * standard Java resource.
- * @param folderName the name of the folder.
- *
- * @return true if the folder is valid for packaging.
- */
- public static boolean checkFolderForPackaging(String folderName) {
- return !folderName.equalsIgnoreCase("CVS") &&
- !folderName.equalsIgnoreCase(".svn") &&
- !folderName.equalsIgnoreCase("SCCS") &&
- !folderName.startsWith("_");
- }
-
- /**
- * Checks a file to make sure it should be packaged as standard resources.
- * @param fileName the name of the file (including extension)
- * @return true if the file should be packaged as standard java resources.
- */
- public static boolean checkFileForPackaging(String fileName) {
- String[] fileSegments = fileName.split("\\.");
- String fileExt = "";
- if (fileSegments.length > 1) {
- fileExt = fileSegments[fileSegments.length-1];
- }
-
- return checkFileForPackaging(fileName, fileExt);
- }
-
- /**
- * Checks a file to make sure it should be packaged as standard resources.
- * @param fileName the name of the file (including extension)
- * @param extension the extension of the file (excluding '.')
- * @return true if the file should be packaged as standard java resources.
- */
- public static boolean checkFileForPackaging(String fileName, String extension) {
- // ignore hidden files and backup files
- return !(fileName.charAt(0) == '.' || fileName.charAt(fileName.length() - 1) == '~') &&
- !isOfNonResourcesExtensions(extension) &&
- !isNotAResourceFile(fileName);
- }
-
- private static boolean isOfNonResourcesExtensions(String extension) {
- for (String ext : NON_RESOURCES_EXTENSIONS) {
- if (ext.equalsIgnoreCase(extension)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isNotAResourceFile(String fileName) {
- for (String name : NON_RESOURCES_FILENAMES) {
- if (name.equalsIgnoreCase(fileName)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the list of file extensions that represents non resources files.
- */
- public static final ImmutableList<String> NON_RESOURCES_EXTENSIONS =
- ImmutableList.<String>builder()
- .add("aidl") // Aidl files
- .add("rs") // RenderScript files
- .add("fs") // FilterScript files
- .add("rsh") // RenderScript header files
- .add("d") // Dependency files
- .add("java") // Java files
- .add("scala") // Scala files
- .add("class") // Java class files
- .add("so") // native .so libraries
- .add("scc") // VisualSourceSafe
- .add("swp") // vi swap file
- .build();
-
- /**
- * Return file names that are not resource files.
- */
- public static final ImmutableList<String> NON_RESOURCES_FILENAMES =
- ImmutableList.<String>builder()
- .add("thumbs.db") // image index file
- .add("picasa.ini") // image index file
- .add("about.html") // Javadoc
- .add("package.html") // Javadoc
- .add("overview.html") // Javadoc
- .build();
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/BaseProcessOutputHandler.java b/base/sdk-common/src/main/java/com/android/ide/common/process/BaseProcessOutputHandler.java
deleted file mode 100644
index 43b0115..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/process/BaseProcessOutputHandler.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.process;
-
-import com.android.annotations.NonNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-
-/**
- * Partial implementation of ProcessOutputHandler that creates a ProcessOutput that caches the
- * output in a ByteArrayOutputStream.
- *
- * This does not do anything with it, since it does not implement
- * {@link ProcessOutputHandler#handleOutput(ProcessOutput)}
- */
-public abstract class BaseProcessOutputHandler implements ProcessOutputHandler {
-
- public BaseProcessOutputHandler() {
- }
-
- @NonNull
- @Override
- public ProcessOutput createOutput() {
- return new BaseProcessOutput();
- }
-
- public static final class BaseProcessOutput implements ProcessOutput {
- private final ByteArrayOutputStream mStandardOutput = new ByteArrayOutputStream();
- private final ByteArrayOutputStream mErrorOutput = new ByteArrayOutputStream();
-
- @NonNull
- @Override
- public OutputStream getStandardOutput() {
- return mStandardOutput;
- }
-
- @NonNull
- @Override
- public OutputStream getErrorOutput() {
- return mErrorOutput;
- }
-
- @NonNull
- public String getStandardOutputAsString() throws ProcessException {
- return getString(mStandardOutput);
- }
-
- @NonNull
- public String getErrorOutputAsString() throws ProcessException {
- return getString(mErrorOutput);
- }
- }
-
- private static String getString(@NonNull ByteArrayOutputStream stream) throws ProcessException {
- try {
- return stream.toString(Charset.defaultCharset().name());
- } catch (UnsupportedEncodingException e) {
- throw new ProcessException(e);
- }
- }
-
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessResult.java b/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessResult.java
deleted file mode 100644
index 6f06146..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessResult.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.process;
-
-/**
- * The result of executing an external process.
- */
-public interface ProcessResult {
-
- /**
- * Throws an exception if the process exited with a non-zero exit value.
- * @return this
- * @throws ProcessException if the process exited with a non-zero exit value
- */
- ProcessResult assertNormalExitValue() throws ProcessException;
-
- /**
- * Returns the exit value of the process.
- */
- int getExitValue();
-
- /**
- * Re-throws any failure executing this process.
- * @return this
- * @throws ProcessException the execution failure wrapped in a ProcessExecution
- */
- ProcessResult rethrowFailure() throws ProcessException;
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessResultImpl.java b/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessResultImpl.java
deleted file mode 100644
index 33d815d..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessResultImpl.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.process;
-
-/**
- * Internal implementation of ProcessResult used by DefaultProcessExecutor.
- */
-class ProcessResultImpl implements ProcessResult {
-
- private final String mCommand;
- private final int mExitValue;
- private final Exception mFailure;
-
- ProcessResultImpl(String command, int exitValue) {
- this(command, exitValue, null);
- }
-
- ProcessResultImpl(String command, Exception failure) {
- this(command, -1, failure);
- }
-
- ProcessResultImpl(String command, int exitValue, Exception failure) {
- mCommand = command;
- mExitValue = exitValue;
- mFailure = failure;
- }
-
- @Override
- public ProcessResult assertNormalExitValue() throws ProcessException {
- if (mExitValue != 0) {
- throw new ProcessException(
- String.format("Return code %d for process '%s'", mExitValue, mCommand));
- }
-
- return this;
- }
-
- @Override
- public int getExitValue() {
- return mExitValue;
- }
-
- @Override
- public ProcessResult rethrowFailure() throws ProcessException {
- if (mFailure != null) {
- throw new ProcessException("", mFailure);
- }
-
- return this;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java b/base/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java
deleted file mode 100644
index b6a32dd..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.rendering;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.HardwareConfig;
-import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenRound;
-import com.android.sdklib.devices.ButtonType;
-import com.android.sdklib.devices.Device;
-import com.android.sdklib.devices.Screen;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Helper method to create a {@link HardwareConfig} object.
- *
- * The base data comes from a {@link Device} object, with additional data provided on the helper
- * object.
- *
- * Since {@link HardwareConfig} is immutable, this allows creating one in several (optional)
- * steps more easily.
- *
- */
-public class HardwareConfigHelper {
-
- @NonNull
- private final Device mDevice;
- @NonNull
- private ScreenOrientation mScreenOrientation = ScreenOrientation.PORTRAIT;
-
- // optional
- private int mMaxRenderWidth = -1;
- private int mMaxRenderHeight = -1;
- private int mOverrideRenderWidth = -1;
- private int mOverrideRenderHeight = -1;
-
- /**
- * Creates a new helper for a given device.
- * @param device the device to provide the base data.
- */
- public HardwareConfigHelper(@NonNull Device device) {
- mDevice = device;
- }
-
- /**
- * Sets the orientation of the config.
- * @param screenOrientation the orientation.
- * @return this (such that chains of setters can be stringed together)
- */
- @NonNull
- public HardwareConfigHelper setOrientation(@NonNull ScreenOrientation screenOrientation) {
- mScreenOrientation = screenOrientation;
- return this;
- }
-
- /**
- * Overrides the width and height to be used during rendering.
- *
- * A value of -1 will make the rendering use the normal width and height coming from the
- * {@link Device} object.
- *
- * @param overrideRenderWidth the width in pixels of the layout to be rendered
- * @param overrideRenderHeight the height in pixels of the layout to be rendered
- * @return this (such that chains of setters can be stringed together)
- */
- @NonNull
- public HardwareConfigHelper setOverrideRenderSize(int overrideRenderWidth,
- int overrideRenderHeight) {
- mOverrideRenderWidth = overrideRenderWidth;
- mOverrideRenderHeight = overrideRenderHeight;
- return this;
- }
-
- /**
- * Sets the max width and height to be used during rendering.
- *
- * A value of -1 will make the rendering use the normal width and height coming from the
- * {@link Device} object.
- *
- * @param maxRenderWidth the max width in pixels of the layout to be rendered
- * @param maxRenderHeight the max height in pixels of the layout to be rendered
- * @return this (such that chains of setters can be stringed together)
- */
- @NonNull
- public HardwareConfigHelper setMaxRenderSize(int maxRenderWidth, int maxRenderHeight) {
- mMaxRenderWidth = maxRenderWidth;
- mMaxRenderHeight = maxRenderHeight;
- return this;
- }
-
- /**
- * Creates and returns the HardwareConfig object.
- * @return the config
- */
- @SuppressWarnings("SuspiciousNameCombination") // Deliberately swapping orientations
- @NonNull
- public HardwareConfig getConfig() {
- Screen screen = mDevice.getDefaultHardware().getScreen();
-
- // compute width and height to take orientation into account.
- int x = screen.getXDimension();
- int y = screen.getYDimension();
- int width, height;
-
- if (x > y) {
- if (mScreenOrientation == ScreenOrientation.LANDSCAPE) {
- width = x;
- height = y;
- } else {
- width = y;
- height = x;
- }
- } else {
- if (mScreenOrientation == ScreenOrientation.LANDSCAPE) {
- width = y;
- height = x;
- } else {
- width = x;
- height = y;
- }
- }
-
- if (mOverrideRenderHeight != -1) {
- width = mOverrideRenderWidth;
- }
-
- if (mOverrideRenderHeight != -1) {
- height = mOverrideRenderHeight;
- }
-
- if (mMaxRenderWidth != -1) {
- width = mMaxRenderWidth;
- }
-
- if (mMaxRenderHeight != -1) {
- height = mMaxRenderHeight;
- }
-
- return new HardwareConfig(
- width,
- height,
- screen.getPixelDensity(),
- (float) screen.getXdpi(),
- (float) screen.getYdpi(),
- screen.getSize(),
- mScreenOrientation,
- mDevice.getDefaultHardware().getScreen().getScreenRound(),
- mDevice.getDefaultHardware().getButtonType() == ButtonType.SOFT);
- }
-
- // ---- Device Display Helpers ----
-
- /** Manufacturer used by the generic devices in the device list */
- public static final String MANUFACTURER_GENERIC = "Generic"; //$NON-NLS-1$
- private static final String NEXUS = "Nexus"; //$NON-NLS-1$
- private static final Pattern GENERIC_PATTERN =
- Pattern.compile("(\\d+\\.?\\d*)\" (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
- private static final String ID_PREFIX_WEAR = "wear_"; //$NON-NLS-1$
- private static final String ID_PREFIX_WEAR_ROUND = "wear_round"; //$NON-NLS-1$
- private static final String ID_PREFIX_TV = "tv_"; //$NON-NLS-1$
-
- /**
- * Returns a user-displayable description of the given Nexus device
- * @param device the device to check
- * @return the label
- * @see #isNexus(com.android.sdklib.devices.Device)
- */
- @NonNull
- public static String getNexusLabel(@NonNull Device device) {
- String name = device.getDisplayName();
- Screen screen = device.getDefaultHardware().getScreen();
- float length = (float) screen.getDiagonalLength();
- // Round dimensions to the nearest tenth
- length = Math.round(10 * length) / 10.0f;
- return String.format(Locale.US, "%1$s (%3$s\", %2$s)",
- name, getResolutionString(device), Float.toString(length));
- }
-
- /**
- * Returns a user-displayable description of the given generic device
- * @param device the device to check
- * @return the label
- * @see #isGeneric(Device)
- */
- @NonNull
- public static String getGenericLabel(@NonNull Device device) {
- // * Use the same precision for all devices (all but one specify decimals)
- // * Add some leading space such that the dot ends up roughly in the
- // same space
- // * Add in screen resolution and density
- String name = device.getDisplayName();
- Matcher matcher = GENERIC_PATTERN.matcher(name);
- if (matcher.matches()) {
- String size = matcher.group(1);
- String n = matcher.group(2);
- int dot = size.indexOf('.');
- if (dot == -1) {
- size += ".0";
- dot = size.length() - 2;
- }
- for (int i = 0; i < 2 - dot; i++) {
- size = ' ' + size;
- }
- name = size + "\" " + n;
- }
-
- return String.format(Locale.US, "%1$s (%2$s)", name,
- getResolutionString(device));
- }
-
- /**
- * Returns a user displayable screen resolution string for the given device
- * @param device the device to look up the string for
- * @return a user displayable string
- */
- @NonNull
- public static String getResolutionString(@NonNull Device device) {
- Screen screen = device.getDefaultHardware().getScreen();
- return String.format(Locale.US,
- "%1$d \u00D7 %2$d: %3$s", // U+00D7: Unicode multiplication sign
- screen.getXDimension(),
- screen.getYDimension(),
- screen.getPixelDensity().getResourceValue());
- }
-
- /**
- * Returns true if the given device is a generic device
- * @param device the device to check
- * @return true if the device is generic
- */
- public static boolean isGeneric(@NonNull Device device) {
- return device.getManufacturer().equals(MANUFACTURER_GENERIC);
- }
-
- /**
- * Returns true if the given device is a Nexus device
- * @param device the device to check
- * @return true if the device is a Nexus
- */
- public static boolean isNexus(@NonNull Device device) {
- return device.getId().contains(NEXUS);
- }
-
- /**
- * Whether the given device is a wear device
- */
- public static boolean isWear(@Nullable Device device) {
- return device != null && device.getId().startsWith(ID_PREFIX_WEAR);
- }
-
- /**
- * Whether the given device is a TV device
- */
- public static boolean isTv(@Nullable Device device) {
- return device != null && device.getId().startsWith(ID_PREFIX_TV);
- }
-
- /**
- * Returns the rank of the given nexus device. This can be used to order
- * the devices chronologically.
- *
- * @param device the device to look up the rank for
- * @return the rank of the device
- */
- public static int nexusRank(Device device) {
- String id = device.getId();
- if (id.equals("Nexus One")) { //$NON-NLS-1$
- return 1;
- }
- if (id.equals("Nexus S")) { //$NON-NLS-1$
- return 2;
- }
- if (id.equals("Galaxy Nexus")) { //$NON-NLS-1$
- return 3;
- }
- if (id.equals("Nexus 7")) { //$NON-NLS-1$
- return 4; // 2012 version
- }
- if (id.equals("Nexus 10")) { //$NON-NLS-1$
- return 5;
- }
- if (id.equals("Nexus 4")) { //$NON-NLS-1$
- return 6;
- }
- if (id.equals("Nexus 7 2013")) { //$NON-NLS-1$
- return 7;
- }
- if (id.equals("Nexus 5")) { //$NON-NLS-1$
- return 8;
- }
- if (id.equals("Nexus 9")) { //$NON-NLS-1$
- return 9;
- }
- if (id.equals("Nexus 6")) { //$NON-NLS-1$
- return 10;
- }
-
- return 100; // devices released in the future?
- }
-
- /**
- * Sorts the given list of Nexus devices according to rank
- * @param list the list to sort
- */
- public static void sortNexusList(@NonNull List<Device> list) {
- Collections.sort(list, new Comparator<Device>() {
- @Override
- public int compare(Device device1, Device device2) {
- // Descending order of age
- return nexusRank(device2) - nexusRank(device1);
- }
- });
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java b/base/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java
deleted file mode 100644
index 627100a..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java
+++ /dev/null
@@ -1,671 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.repository;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Joiner;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * This class represents a maven coordinate and allows for comparison at any level.
- * <p>
- * This class does not directly implement {@link java.lang.Comparable}; instead,
- * you should use one of the specific {@link java.util.Comparator} constants based
- * on what type of ordering you need.
- */
-public class GradleCoordinate {
- private static final String NONE = "NONE";
-
- /**
- * Maven coordinates take the following form: groupId:artifactId:packaging:classifier:version
- * where
- * groupId is dot-notated alphanumeric
- * artifactId is the name of the project
- * packaging is optional and is jar/war/pom/aar/etc
- * classifier is optional and provides filtering context
- * version uniquely identifies a version.
- *
- * We only care about coordinates of the following form: groupId:artifactId:revision
- * where revision is a series of '.' separated numbers optionally terminated by a '+' character.
- */
-
- /**
- * List taken from <a href="http://maven.apache.org/pom.html#Maven_Coordinates">http://maven.apache.org/pom.html#Maven_Coordinates</a>
- */
- public enum ArtifactType {
- POM("pom"),
- JAR("jar"),
- MAVEN_PLUGIN("maven-plugin"),
- EJB("ejb"),
- WAR("war"),
- EAR("ear"),
- RAR("rar"),
- PAR("par"),
- AAR("aar");
-
- private final String mId;
-
- ArtifactType(String id) {
- mId = id;
- }
-
- @Nullable
- public static ArtifactType getArtifactType(@Nullable String name) {
- if (name != null) {
- for (ArtifactType type : ArtifactType.values()) {
- if (type.mId.equalsIgnoreCase(name)) {
- return type;
- }
- }
- }
- return null;
- }
-
- @Override
- public String toString() {
- return mId;
- }
- }
-
- public static final String PREVIEW_ID = "rc";
-
- /**
- * A single component of a revision number: either a number, a string or a list of
- * components separated by dashes.
- */
- public abstract static class RevisionComponent implements Comparable<RevisionComponent> {
- public abstract int asInteger();
- public abstract boolean isPreview();
- }
-
- public static class NumberComponent extends RevisionComponent {
- private final int mNumber;
-
- public NumberComponent(int number) {
- mNumber = number;
- }
-
- @Override
- public String toString() {
- return Integer.toString(mNumber);
- }
-
- @Override
- public int asInteger() {
- return mNumber;
- }
-
- @Override
- public boolean isPreview() {
- return false;
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof NumberComponent && ((NumberComponent) o).mNumber == mNumber;
- }
-
- @Override
- public int hashCode() {
- return mNumber;
- }
-
- @Override
- public int compareTo(RevisionComponent o) {
- if (o instanceof NumberComponent) {
- return mNumber - ((NumberComponent) o).mNumber;
- }
- if (o instanceof StringComponent) {
- return 1;
- }
- if (o instanceof ListComponent) {
- return 1; // 1.0.x > 1-1
- }
- return 0;
- }
- }
-
- /**
- * Like NumberComponent, but used for numeric strings that have leading zeroes which
- * we must preserve
- */
- public static class PaddedNumberComponent extends NumberComponent {
- private final String mString;
-
- public PaddedNumberComponent(int number, String string) {
- super(number);
- mString = string;
- }
-
- @Override
- public String toString() {
- return mString;
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof PaddedNumberComponent
- && ((PaddedNumberComponent) o).mString.equals(mString);
- }
- }
-
- public static class StringComponent extends RevisionComponent {
- private final String mString;
-
- public StringComponent(String string) {
- this.mString = string;
- }
-
- @Override
- public String toString() {
- return mString;
- }
-
- @Override
- public int asInteger() {
- return 0;
- }
-
- @Override
- public boolean isPreview() {
- return mString.startsWith(PREVIEW_ID);
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof StringComponent && ((StringComponent) o).mString.equals(mString);
- }
-
- @Override
- public int hashCode() {
- return mString.hashCode();
- }
-
- @Override
- public int compareTo(RevisionComponent o) {
- if (o instanceof NumberComponent) {
- return -1;
- }
- if (o instanceof StringComponent) {
- return mString.compareTo(((StringComponent) o).mString);
- }
- if (o instanceof ListComponent) {
- return -1; // 1-sp < 1-1
- }
- return 0;
- }
- }
-
- private static class PlusComponent extends RevisionComponent {
- @Override
- public String toString() {
- return "+";
- }
-
- @Override
- public int asInteger() {
- return PLUS_REV_VALUE;
- }
-
- @Override
- public boolean isPreview() {
- return false;
- }
-
- @Override
- public int compareTo(RevisionComponent o) {
- throw new UnsupportedOperationException(
- "Please use a specific comparator that knows how to handle +");
- }
- }
-
- /**
- * A list of components separated by dashes.
- */
- public static class ListComponent extends RevisionComponent {
- private final List<RevisionComponent> mItems = new ArrayList<RevisionComponent>();
- private boolean mClosed = false;
-
- public static ListComponent of(RevisionComponent... components) {
- ListComponent result = new ListComponent();
- for (RevisionComponent component : components) {
- result.add(component);
- }
- return result;
- }
-
- public void add(RevisionComponent component) {
- mItems.add(component);
- }
-
- @Override
- public int asInteger() {
- return 0;
- }
-
- @Override
- public boolean isPreview() {
- return !mItems.isEmpty() && mItems.get(mItems.size() - 1).isPreview();
- }
-
- @Override
- public int compareTo(RevisionComponent o) {
- if (o instanceof NumberComponent) {
- return -1; // 1-1 < 1.0.x
- }
- if (o instanceof StringComponent) {
- return 1; // 1-1 > 1-sp
- }
- if (o instanceof ListComponent) {
- ListComponent rhs = (ListComponent) o;
- for (int i = 0; i < mItems.size() && i < rhs.mItems.size(); i++) {
- int rc = mItems.get(i).compareTo(rhs.mItems.get(i));
- if (rc != 0) return rc;
- }
- return mItems.size() - rhs.mItems.size();
- }
- return 0;
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof ListComponent && ((ListComponent) o).mItems.equals(mItems);
- }
-
- @Override
- public int hashCode() {
- return mItems.hashCode();
- }
-
- @Override
- public String toString() {
- return Joiner.on("-").join(mItems);
- }
- }
-
- public static final PlusComponent PLUS_REV = new PlusComponent();
- public static final int PLUS_REV_VALUE = -1;
-
- private final String mGroupId;
-
- private final String mArtifactId;
-
- private final ArtifactType mArtifactType;
-
- private final List<RevisionComponent> mRevisions = new ArrayList<RevisionComponent>(3);
-
- private static final Pattern MAVEN_PATTERN =
- Pattern.compile("([\\w\\d\\.-]+):([\\w\\d\\.-]+):([^:@]+)(@[\\w-]+)?");
-
- /**
- * Constructor
- */
- public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
- @NonNull RevisionComponent... revisions) {
- this(groupId, artifactId, Arrays.asList(revisions), null);
- }
-
- /**
- * Constructor
- */
- public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
- @NonNull int... revisions) {
- this(groupId, artifactId, createComponents(revisions), null);
- }
-
- private static List<RevisionComponent> createComponents(int[] revisions) {
- List<RevisionComponent> result = new ArrayList<RevisionComponent>(revisions.length);
- for (int revision : revisions) {
- if (revision == PLUS_REV_VALUE) {
- result.add(PLUS_REV);
- } else {
- result.add(new NumberComponent(revision));
- }
- }
- return result;
- }
-
- /**
- * Constructor
- */
- public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
- @NonNull List<RevisionComponent> revisions, @Nullable ArtifactType type) {
- mGroupId = groupId;
- mArtifactId = artifactId;
- mRevisions.addAll(revisions);
-
- mArtifactType = type;
- }
-
- /**
- * Create a GradleCoordinate from a string of the form groupId:artifactId:MajorRevision.MinorRevision.(MicroRevision|+)
- *
- * @param coordinateString the string to parse
- * @return a coordinate object or null if the given string was malformed.
- */
- @Nullable
- public static GradleCoordinate parseCoordinateString(@NonNull String coordinateString) {
- if (coordinateString == null) {
- return null;
- }
-
- Matcher matcher = MAVEN_PATTERN.matcher(coordinateString);
- if (!matcher.matches()) {
- return null;
- }
-
- String groupId = matcher.group(1);
- String artifactId = matcher.group(2);
- String revision = matcher.group(3);
- String typeString = matcher.group(4);
- ArtifactType type = null;
-
- if (typeString != null) {
- // Strip off the '@' symbol and try to convert
- type = ArtifactType.getArtifactType(typeString.substring(1));
- }
-
- List<RevisionComponent> revisions = parseRevisionNumber(revision);
-
- return new GradleCoordinate(groupId, artifactId, revisions, type);
- }
-
- public static GradleCoordinate parseVersionOnly(@NonNull String revision) {
- return new GradleCoordinate(NONE, NONE, parseRevisionNumber(revision), null);
- }
-
- @NonNull
- public static List<RevisionComponent> parseRevisionNumber(@NonNull String revision) {
- List<RevisionComponent> components = new ArrayList<RevisionComponent>();
- StringBuilder buffer = new StringBuilder();
- for (int i = 0; i < revision.length(); i++) {
- char c = revision.charAt(i);
- if (c == '.') {
- flushBuffer(components, buffer, true);
- } else if (c == '+') {
- if (buffer.length() > 0) {
- flushBuffer(components, buffer, true);
- }
- components.add(PLUS_REV);
- break;
- } else if (c == '-') {
- flushBuffer(components, buffer, false);
- int last = components.size() - 1;
- if (last == -1) {
- components.add(ListComponent.of(new NumberComponent(0)));
- } else if (!(components.get(last) instanceof ListComponent)) {
- components.set(last, ListComponent.of(components.get(last)));
- }
- } else {
- buffer.append(c);
- }
- }
- if (buffer.length() > 0 || components.isEmpty()) {
- flushBuffer(components, buffer, true);
- }
- return components;
- }
-
- private static void flushBuffer(List<RevisionComponent> components, StringBuilder buffer,
- boolean closeList) {
- RevisionComponent newComponent;
- if (buffer.length() == 0) {
- newComponent = new NumberComponent(0);
- } else {
- String string = buffer.toString();
- try {
- int number = Integer.parseInt(string);
- if (string.length() > 1 && string.charAt(0) == '0') {
- newComponent = new PaddedNumberComponent(number, string);
- } else {
- newComponent = new NumberComponent(number);
- }
- } catch (NumberFormatException e) {
- newComponent = new StringComponent(string);
- }
- }
- buffer.setLength(0);
- if (!components.isEmpty() &&
- components.get(components.size() - 1) instanceof ListComponent) {
- ListComponent component = (ListComponent) components.get(components.size() - 1);
- if (!component.mClosed) {
- component.add(newComponent);
- if (closeList) {
- component.mClosed = true;
- }
- return;
- }
- }
- components.add(newComponent);
- }
-
- @Override
- public String toString() {
- String s = String.format(Locale.US, "%s:%s:%s", mGroupId, mArtifactId, getFullRevision());
- if (mArtifactType != null) {
- s += "@" + mArtifactType.toString();
- }
- return s;
- }
-
- @Nullable
- public String getGroupId() {
- return mGroupId;
- }
-
- @Nullable
- public String getArtifactId() {
- return mArtifactId;
- }
-
- @Nullable
- public String getId() {
- if (mGroupId == null || mArtifactId == null) {
- return null;
- }
-
- return String.format("%s:%s", mGroupId, mArtifactId);
- }
-
- @Nullable
- public ArtifactType getType() {
- return mArtifactType;
- }
-
- public boolean acceptsGreaterRevisions() {
- return mRevisions.get(mRevisions.size() - 1) == PLUS_REV;
- }
-
- public String getFullRevision() {
- StringBuilder revision = new StringBuilder();
- for (RevisionComponent component : mRevisions) {
- if (revision.length() > 0) {
- revision.append('.');
- }
- revision.append(component.toString());
- }
-
- return revision.toString();
- }
-
- public boolean isPreview() {
- return !mRevisions.isEmpty() && mRevisions.get(mRevisions.size() - 1).isPreview();
- }
-
- /**
- * Returns the major version (X in X.2.3), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
- * if it is not available
- */
- public int getMajorVersion() {
- return mRevisions.isEmpty() ? Integer.MIN_VALUE : mRevisions.get(0).asInteger();
- }
-
- /**
- * Returns the minor version (X in 1.X.3), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
- * if it is not available
- */
- public int getMinorVersion() {
- return mRevisions.size() < 2 ? Integer.MIN_VALUE : mRevisions.get(1).asInteger();
- }
-
- /**
- * Returns the major version (X in 1.2.X), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
- * if it is not available
- */
- public int getMicroVersion() {
- return mRevisions.size() < 3 ? Integer.MIN_VALUE : mRevisions.get(2).asInteger();
- }
-
- /**
- * Returns true if and only if the given coordinate refers to the same group and artifact.
- *
- * @param o the coordinate to compare with
- * @return true iff the other group and artifact match the group and artifact of this
- * coordinate.
- */
- public boolean isSameArtifact(@NonNull GradleCoordinate o) {
- return o.mGroupId.equals(mGroupId) && o.mArtifactId.equals(mArtifactId);
- }
-
- @Override
- public boolean equals(@NonNull Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- GradleCoordinate that = (GradleCoordinate) o;
-
- if (!mRevisions.equals(that.mRevisions)) {
- return false;
- }
- if (!mArtifactId.equals(that.mArtifactId)) {
- return false;
- }
- if (!mGroupId.equals(that.mGroupId)) {
- return false;
- }
- if ((mArtifactType == null) != (that.mArtifactType == null)) {
- return false;
- }
- if (mArtifactType != null && !mArtifactType.equals(that.mArtifactType)) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = mGroupId.hashCode();
- result = 31 * result + mArtifactId.hashCode();
- for (RevisionComponent component : mRevisions) {
- result = 31 * result + component.hashCode();
- }
- if (mArtifactType != null) {
- result = 31 * result + mArtifactType.hashCode();
- }
- return result;
- }
-
- /**
- * Comparator which compares Gradle versions - and treats a + version as lower
- * than a specific number in the same place. This is typically useful when trying
- * to for example order coordinates by "most specific".
- */
- public static final Comparator<GradleCoordinate> COMPARE_PLUS_LOWER =
- new GradleCoordinateComparator(-1);
-
- /**
- * Comparator which compares Gradle versions - and treats a + version as higher
- * than a specific number. This is typically useful when seeing if a dependency
- * is met, e.g. if you require version 0.7.3, comparing it with 0.7.+ would consider
- * 0.7.+ higher and therefore satisfying the version requirement.
- */
- public static final Comparator<GradleCoordinate> COMPARE_PLUS_HIGHER =
- new GradleCoordinateComparator(1);
-
- private static class GradleCoordinateComparator implements Comparator<GradleCoordinate> {
- private final int mPlusResult;
-
- private GradleCoordinateComparator(int plusResult) {
- mPlusResult = plusResult;
- }
-
- @Override
- public int compare(@NonNull GradleCoordinate a, @NonNull GradleCoordinate b) {
- // Make sure we're comparing apples to apples. If not, compare artifactIds
- if (!a.isSameArtifact(b)) {
- return a.mArtifactId.compareTo(b.mArtifactId);
- }
-
- int sizeA = a.mRevisions.size();
- int sizeB = b.mRevisions.size();
- int common = Math.min(sizeA, sizeB);
- for (int i = 0; i < common; ++i) {
- RevisionComponent revision1 = a.mRevisions.get(i);
- if (revision1 instanceof PlusComponent) return mPlusResult;
- RevisionComponent revision2 = b.mRevisions.get(i);
- if (revision2 instanceof PlusComponent) return -mPlusResult;
- int delta = revision1.compareTo(revision2);
- if (delta != 0) {
- return delta;
- }
- }
- if (sizeA == sizeB) {
- return 0;
- } else {
- // Treat X.0 and X.0.0 as equal
- List<RevisionComponent> revisionList;
- int returnValueIfNonZero;
- int from;
- int to;
- if (sizeA < sizeB) {
- revisionList = b.mRevisions;
- from = sizeA;
- to = sizeB;
- returnValueIfNonZero = -1;
- } else {
- revisionList = a.mRevisions;
- from = sizeB;
- to = sizeA;
- returnValueIfNonZero = 1;
- }
- for (int i = from; i < to; ++i) {
- RevisionComponent revision = revisionList.get(i);
- if (revision instanceof NumberComponent) {
- if (revision.asInteger() != 0) {
- return returnValueIfNonZero;
- }
- } else {
- return returnValueIfNonZero;
- }
- }
- return 0;
- }
- }
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/repository/ResourceVisibilityLookup.java b/base/sdk-common/src/main/java/com/android/ide/common/repository/ResourceVisibilityLookup.java
deleted file mode 100644
index f30e921..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/repository/ResourceVisibilityLookup.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.repository;
-
-import static com.android.SdkConstants.FN_RESOURCE_TEXT;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Variant;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.resources.ResourceType;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Class which provides information about whether Android resources for a given library are
- * public or private.
- */
-public abstract class ResourceVisibilityLookup {
- /**
- * Returns true if the given resource is private
- *
- * @param type the type of the resource
- * @param name the resource field name of the resource (in other words, for
- * style Theme:Variant.Cls the name would be Theme_Variant_Cls; you can use
- * {@link LintUtils#g}
- * @return true if the given resource is private
- */
- public abstract boolean isPrivate(
- @NonNull ResourceType type,
- @NonNull String name);
-
- /**
- * Returns true if the given resource is private in the library
- *
- * @param url the resource URL
- * @return true if the given resource is private
- */
- public boolean isPrivate(@NonNull ResourceUrl url) {
- assert !url.framework; // Framework resources are not part of the library
- return isPrivate(url.type, url.name);
- }
-
- /**
- * For a private resource, return the {@link AndroidLibrary} that the resource was defined as
- * private in
- *
- * @param type the type of the resource
- * @param name the name of the resource
- * @return the library which defines the resource as private
- */
- @Nullable
- public abstract AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name);
-
- /** Returns true if this repository does not declare any resources to be private */
- public abstract boolean isEmpty();
-
- /**
- * Creates a {@link ResourceVisibilityLookup} for a given library.
- * <p>
- * NOTE: The {@link Provider} class can be used to share/cache {@link ResourceVisibilityLookup}
- * instances, e.g. when you have library1 and library2 each referencing libraryBase, the {@link
- * Provider} will ensure that a the libraryBase data is shared.
- *
- * @param library the library
- * @return a corresponding {@link ResourceVisibilityLookup}
- */
- @NonNull
- public static ResourceVisibilityLookup create(@NonNull AndroidLibrary library) {
- return new LibraryResourceVisibility(library);
- }
-
- /**
- * Creates a {@link ResourceVisibilityLookup} for the set of libraries.
- * <p>
- * NOTE: The {@link Provider} class can be used to share/cache {@link ResourceVisibilityLookup}
- * instances, e.g. when you have library1 and library2 each referencing libraryBase, the {@link
- * Provider} will ensure that a the libraryBase data is shared.
- *
- * @param libraries the list of libraries
- * @param provider an optional manager instance for caching of individual libraries, if any
- * @return a corresponding {@link ResourceVisibilityLookup}
- */
- @NonNull
- public static ResourceVisibilityLookup create(@NonNull List<AndroidLibrary> libraries,
- @Nullable Provider provider) {
- List<ResourceVisibilityLookup> list = Lists.newArrayListWithExpectedSize(libraries.size());
- for (AndroidLibrary library : libraries) {
- ResourceVisibilityLookup v = provider != null ? provider.get(library) : create(library);
- if (!v.isEmpty()) {
- list.add(v);
- }
- }
- return new MultipleLibraryResourceVisibility(list);
- }
-
- public static final ResourceVisibilityLookup NONE = new ResourceVisibilityLookup() {
- @Override
- public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
- return false;
- }
-
- @Nullable
- @Override
- public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
- return null;
- }
-
- @Override
- public boolean isEmpty() {
- return true;
- }
- };
-
- /** Searches multiple libraries */
- private static class MultipleLibraryResourceVisibility extends ResourceVisibilityLookup {
-
- private final List<ResourceVisibilityLookup> mRepositories;
-
- public MultipleLibraryResourceVisibility(List<ResourceVisibilityLookup> repositories) {
- mRepositories = repositories;
- }
-
- // It's anticipated that these methods will be called a lot (e.g. in inner loops
- // iterating over all resources matching code completion etc) so since we know
- // that our list has random access, avoid creating iterators here
- @SuppressWarnings("ForLoopReplaceableByForEach")
- @Override
- public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
- for (int i = 0, n = mRepositories.size(); i < n; i++) {
- if (mRepositories.get(i).isPrivate(type, name)) {
- return true;
- }
- }
- return false;
- }
-
- @SuppressWarnings("ForLoopReplaceableByForEach")
- @Override
- public boolean isEmpty() {
- for (int i = 0, n = mRepositories.size(); i < n; i++) {
- if (!mRepositories.get(i).isEmpty()) {
- return false;
- }
- }
- return true;
- }
-
- @SuppressWarnings("ForLoopReplaceableByForEach")
- @Nullable
- @Override
- public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
- for (int i = 0, n = mRepositories.size(); i < n; i++) {
- ResourceVisibilityLookup r = mRepositories.get(i);
- if (r.isPrivate(type, name)) {
- return r.getPrivateIn(type, name);
- }
- }
- return null;
- }
- }
-
- /**
- * Provider which keeps a set of {@link ResourceVisibilityLookup} instances around for
- * repeated queries, including from different libraries that may share dependencies
- */
- public static class Provider {
- /**
- * We store lookup instances for multiple separate types of keys here:
- * {@link AndroidLibrary}, {@link AndroidArtifact}, and {@link Variant}
- */
- private Map<Object, ResourceVisibilityLookup> mInstances = Maps.newHashMap();
-
- /**
- * Looks up a (possibly cached) {@link ResourceVisibilityLookup} for the given {@link
- * AndroidLibrary}
- *
- * @param library the library
- * @return the corresponding {@link ResourceVisibilityLookup}
- */
- @NonNull
- public ResourceVisibilityLookup get(@NonNull AndroidLibrary library) {
- ResourceVisibilityLookup visibility = mInstances.get(library);
- if (visibility == null) {
- visibility = new LibraryResourceVisibility(library);
- if (visibility.isEmpty()) {
- visibility = NONE;
- }
- List<? extends AndroidLibrary> dependsOn = library.getLibraryDependencies();
- if (!dependsOn.isEmpty()) {
- List<ResourceVisibilityLookup> list =
- Lists.newArrayListWithExpectedSize(dependsOn.size() + 1);
- list.add(visibility);
- for (AndroidLibrary d : dependsOn) {
- ResourceVisibilityLookup v = get(d);
- if (!v.isEmpty()) {
- list.add(v);
- }
- }
- if (list.size() > 1) {
- visibility = new MultipleLibraryResourceVisibility(list);
- }
- }
- mInstances.put(library, visibility);
- }
- return visibility;
- }
-
- /**
- * Looks up a (possibly cached) {@link ResourceVisibilityLookup} for the given {@link
- * AndroidArtifact}
- *
- * @param artifact the artifact
- * @return the corresponding {@link ResourceVisibilityLookup}
- */
- @NonNull
- public ResourceVisibilityLookup get(@NonNull AndroidArtifact artifact) {
- ResourceVisibilityLookup visibility = mInstances.get(artifact);
- if (visibility == null) {
- Collection<AndroidLibrary> dependsOn = artifact.getDependencies().getLibraries();
- List<ResourceVisibilityLookup> list =
- Lists.newArrayListWithExpectedSize(dependsOn.size() + 1);
- for (AndroidLibrary d : dependsOn) {
- ResourceVisibilityLookup v = get(d);
- if (!v.isEmpty()) {
- list.add(v);
- }
- }
- int size = list.size();
- visibility = size == 0 ? NONE : size == 1 ? list.get(0) : new MultipleLibraryResourceVisibility(list);
- mInstances.put(artifact, visibility);
- }
- return visibility;
- }
-
- /**
- * Returns true if the given Gradle model is compatible with public resources.
- * (Older models than 1.3 will throw exceptions if we attempt to for example
- * query the public resource file location.
- *
- * @param project the project to check
- * @return true if the model is recent enough to support resource visibility queries
- */
- public static boolean isVisibilityAwareModel(@NonNull AndroidProject project) {
- String modelVersion = project.getModelVersion();
- // getApiVersion doesn't work prior to 1.2, and API level must be at least 3
- return !(modelVersion.startsWith("1.0") || modelVersion.startsWith("1.1"))
- && project.getApiVersion() >= 3;
- }
-
- /**
- * Looks up a (possibly cached) {@link ResourceVisibilityLookup} for the given {@link
- * AndroidArtifact}
- *
- * @param project the project
- * @return the corresponding {@link ResourceVisibilityLookup}
- */
- @NonNull
- public ResourceVisibilityLookup get(
- @NonNull AndroidProject project,
- @NonNull Variant variant) {
- ResourceVisibilityLookup visibility = mInstances.get(variant);
- if (visibility == null) {
- if (isVisibilityAwareModel(project)) {
- AndroidArtifact artifact = variant.getMainArtifact();
- visibility = get(artifact);
- } else {
- visibility = NONE;
- }
- mInstances.put(variant, visibility);
- }
- return visibility;
- }
- }
-
- /** Visibility data for a single library */
- private static class LibraryResourceVisibility extends ResourceVisibilityLookup {
- private final AndroidLibrary mLibrary;
- private final Multimap<String, ResourceType> mAll;
- private final Multimap<String, ResourceType> mPublic;
-
- private LibraryResourceVisibility(@NonNull AndroidLibrary library) {
- mLibrary = library;
-
- mPublic = computeVisibilityMap();
- //noinspection VariableNotUsedInsideIf
- if (mPublic != null) {
- mAll = computeAllMap();
- } else {
- mAll = null;
- }
- }
-
- @Override
- public boolean isEmpty() {
- return mPublic == null;
- }
-
- @Nullable
- @Override
- public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
- if (isPrivate(type, name)) {
- return mLibrary;
- }
-
- return null;
- }
-
- /**
- * Returns a map from name to applicable resource types where the presence of the type+name
- * combination means that the corresponding resource is explicitly public.
- *
- * If the result is null, there is no {@code public.txt} definition for this library, so all
- * resources should be taken to be public.
- *
- * @return a map from name to resource type for public resources in this library
- */
- @Nullable
- private Multimap<String, ResourceType> computeVisibilityMap() {
- File publicResources = mLibrary.getPublicResources();
- if (!publicResources.exists()) {
- return null;
- }
-
- try {
- List<String> lines = Files.readLines(publicResources, Charsets.UTF_8);
- Multimap<String, ResourceType> result = ArrayListMultimap.create(lines.size(), 2);
- for (String line : lines) {
- // These files are written by code in MergedResourceWriter#postWriteAction
- // Format for each line: <type><space><name>\n
- // Therefore, we don't expect/allow variations in the format (we don't
- // worry about extra spaces needing to be trimmed etc)
- int index = line.indexOf(' ');
- if (index == -1 || line.isEmpty()) {
- continue;
- }
-
- String typeString = line.substring(0, index);
- ResourceType type = ResourceType.getEnum(typeString);
- if (type == null) {
- // This could in theory happen if in the future a new ResourceType is
- // introduced, and a newer version of the Gradle build system writes the
- // name of this type into the public.txt file, and an older version of
- // the IDE then attempts to read it. Just skip these symbols.
- continue;
- }
- String name = line.substring(index + 1);
- result.put(name, type);
- }
- return result;
- } catch (IOException ignore) {
- }
- return null;
- }
-
- /**
- * Returns a map from name to resource types for all resources known to this library. This
- * is used to make sure that when the {@link #isPrivate(ResourceType, String)} query method
- * is called, it can tell the difference between a resource implicitly private by not being
- * declared as public and a resource unknown to this library (e.g. defined by a different
- * library or the user's own project resources.)
- *
- * @return a map from name to resource type for all resources in this library
- */
- @Nullable
- private Multimap<String, ResourceType> computeAllMap() {
- // getSymbolFile() is not defined in AndroidLibrary, only in the subclass LibraryBundle
- File symbolFile = new File(mLibrary.getPublicResources().getParentFile(),
- FN_RESOURCE_TEXT);
- if (!symbolFile.exists()) {
- return null;
- }
-
- try {
- List<String> lines = Files.readLines(symbolFile, Charsets.UTF_8);
- Multimap<String, ResourceType> result = ArrayListMultimap.create(lines.size(), 2);
-
- ResourceType previousType = null;
- String previousTypeString = "";
- int lineIndex = 1;
- final int count = lines.size();
- for (; lineIndex <= count; lineIndex++) {
- String line = lines.get(lineIndex - 1);
-
- if (line.startsWith("int ")) { // not int[] definitions for styleables
- // format is "int <type> <class> <name> <value>"
- int typeStart = 4;
- int typeEnd = line.indexOf(' ', typeStart);
-
- // Items are sorted by type, so we can avoid looping over types in
- // ResourceType.getEnum() for each line by sharing type in each section
- String typeString = line.substring(typeStart, typeEnd);
- ResourceType type;
- if (typeString.equals(previousTypeString)) {
- type = previousType;
- } else {
- type = ResourceType.getEnum(typeString);
- previousTypeString = typeString;
- previousType = type;
- }
- if (type == null) { // some newly introduced type
- continue;
- }
-
- int nameStart = typeEnd + 1;
- int nameEnd = line.indexOf(' ', nameStart);
- String name = line.substring(nameStart, nameEnd);
- result.put(name, type);
- }
- }
- return result;
- } catch (IOException ignore) {
- }
- return null;
- }
-
- /**
- * Returns true if the given resource is private in the library
- *
- * @param type the type of the resource
- * @param name the name of the resource
- * @return true if the given resource is private
- */
- @Override
- public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
- //noinspection SimplifiableIfStatement
- if (mPublic == null) {
- // No public definitions: Everything assumed to be public
- return false;
- }
-
- //noinspection SimplifiableIfStatement
- if (!mAll.containsEntry(name, type)) {
- // Don't respond to resource URLs that are not part of this project
- // since we won't have private information on them
- return false;
- }
- return !mPublic.containsEntry(name, type);
- }
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/repository/SdkMavenRepository.java b/base/sdk-common/src/main/java/com/android/ide/common/repository/SdkMavenRepository.java
deleted file mode 100644
index 22c8f1e..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/repository/SdkMavenRepository.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.repository;
-
-import static com.android.SdkConstants.FD_EXTRAS;
-import static com.android.SdkConstants.FD_M2_REPOSITORY;
-import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
-import static java.io.File.separator;
-import static java.io.File.separatorChar;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.android.sdklib.repository.descriptors.PkgType;
-import com.android.sdklib.repository.local.LocalPkgInfo;
-import com.android.sdklib.repository.local.LocalSdk;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A {@linkplain com.android.ide.common.repository.SdkMavenRepository} represents a Maven
- * repository that is shipped with the SDK and located in the {@code extras} folder of the
- * SDK location.
- */
-public enum SdkMavenRepository {
- /** The Android repository; contains support lib, app compat, media router, etc */
- ANDROID("android", "Android Support Repository"),
-
- /** The Google repository; contains Play Services etc */
- GOOGLE("google", "Google Support Repository");
-
- private final String mDir;
- @NonNull private final String myDisplayName;
-
- SdkMavenRepository(@NonNull String dir, @NonNull String displayName) {
- mDir = dir;
- myDisplayName = displayName;
- }
-
- /**
- * Returns the location of the repository within a given SDK home
- * @param sdkHome the SDK home, or null
- * @param requireExists if true, the location will only be returned if it also exists
- * @return the location of the this repository within a given SDK
- */
- @Nullable
- public File getRepositoryLocation(@Nullable File sdkHome, boolean requireExists) {
- if (sdkHome != null) {
- File dir = new File(sdkHome, FD_EXTRAS + separator + mDir
- + separator + FD_M2_REPOSITORY);
- if (!requireExists || dir.isDirectory()) {
- return dir;
- }
- }
-
- return null;
- }
-
- /**
- * Returns true if the given SDK repository is installed
- *
- * @param sdkHome the SDK installation location
- * @return true if the repository is installed
- */
- public boolean isInstalled(@Nullable File sdkHome) {
- return getRepositoryLocation(sdkHome, true) != null;
- }
-
- /**
- * Returns true if the given SDK repository is installed
- *
- * @param sdk the SDK to check
- * @return true if the repository is installed
- */
- public boolean isInstalled(@Nullable LocalSdk sdk) {
- if (sdk != null) {
- LocalPkgInfo[] infos = sdk.getPkgsInfos(PkgType.PKG_EXTRA);
- for (LocalPkgInfo info : infos) {
- IPkgDesc d = info.getDesc();
- //noinspection ConstantConditions,ConstantConditions
- if (d.hasVendor() && mDir.equals(d.getVendor().getId()) &&
- d.hasPath() && FD_M2_REPOSITORY.equals(d.getPath())) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Find the best matching {@link GradleCoordinate}
- *
- * @param sdkHome the SDK installation
- * @param groupId the artifact group id
- * @param artifactId the artifact id
- * @param filter an optional filter which the matched coordinate's version name must start with
- * @param allowPreview whether preview versions are allowed to match
- * @return the best (highest version) matching coordinate, or null if none were found
- */
- @Nullable
- public GradleCoordinate getHighestInstalledVersion(
- @Nullable File sdkHome,
- @NonNull String groupId,
- @NonNull String artifactId,
- @Nullable String filter,
- boolean allowPreview) {
- File repository = getRepositoryLocation(sdkHome, true);
- if (repository != null) {
- return getHighestInstalledVersion(groupId, artifactId, repository, filter,
- allowPreview);
- }
-
- return null;
- }
-
- /**
- * Find the best matching {@link GradleCoordinate}
- *
- * @param groupId the artifact group id
- * @param artifactId the artifact id
- * @param repository the path to the m2repository directory
- * @param filter an optional filter which the matched coordinate's version name must start with
- * @param allowPreview whether preview versions are allowed to match
- * @return the best (highest version) matching coordinate, or null if none were found
- */
- @Nullable
- public static GradleCoordinate getHighestInstalledVersion(
- @NonNull String groupId,
- @NonNull String artifactId,
- @NonNull File repository,
- @Nullable String filter,
- boolean allowPreview) {
- assert FD_M2_REPOSITORY.equals(repository.getName()) : repository;
-
- File versionDir = new File(repository,
- groupId.replace('.', separatorChar) + separator + artifactId);
- File[] versions = versionDir.listFiles();
- if (versions != null) {
- List<GradleCoordinate> versionCoordinates = Lists.newArrayList();
- for (File dir : versions) {
- if (!dir.isDirectory()) {
- continue;
- }
- if (filter != null && !dir.getName().startsWith(filter)) {
- continue;
- }
- GradleCoordinate gc = GradleCoordinate.parseCoordinateString(
- groupId + ":" + artifactId + ":" + dir.getName());
-
- if (gc != null && (allowPreview || !gc.getFullRevision().contains("-rc"))) {
- if (!allowPreview && "5.2.08".equals(gc.getFullRevision()) &&
- "play-services".equals(gc.getArtifactId())) {
- // This specific version is actually a preview version which should
- // not be used (https://code.google.com/p/android/issues/detail?id=75292)
- continue;
- }
- FullRevision.parseRevision(gc.getFullRevision());
- versionCoordinates.add(gc);
- }
- }
- if (!versionCoordinates.isEmpty()) {
- return Collections.max(versionCoordinates, COMPARE_PLUS_HIGHER);
- }
- }
-
- return null;
- }
-
- @Nullable
- public static SdkMavenRepository getByGroupId(@NonNull String groupId) {
- if ("com.android.support".equals(groupId) || "com.android.support.test".equals(groupId)) {
- return ANDROID;
- }
- if (groupId.startsWith("com.google.android.")) {
- // com.google.android.gms, com.google.android.support.wearable,
- // com.google.android.wearable, ... possibly more in the future
- return GOOGLE;
- }
-
- return null;
- }
-
- /** The directory name of the repository inside the extras folder */
- @NonNull
- public String getDirName() {
- return mDir;
- }
-
- /**
- * @return SDK package description for this repository
- */
- public IPkgDesc getPackageDescription() {
- return PkgDesc.Builder.newExtra(new IdDisplay(mDir, ""), FD_M2_REPOSITORY, myDisplayName,
- null, new NoPreviewRevision(1)).create();
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java
deleted file mode 100644
index 4387a2c..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.SdkConstants.ATTR_REF_PREFIX;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.PREFIX_THEME_REF;
-import static com.android.SdkConstants.RESOURCE_CLZ_ATTR;
-import static com.android.ide.common.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.ide.common.resources.configuration.Configurable;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.LocaleQualifier;
-import com.android.resources.ResourceType;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-
-public abstract class AbstractResourceRepository {
-
- private final boolean mFramework;
-
- private class RepositoryMerger implements MergeConsumer<ResourceItem> {
-
- @Override
- public void start(@NonNull DocumentBuilderFactory factory)
- throws ConsumerException {
- }
-
- @Override
- public void end() throws ConsumerException {
- }
-
- @Override
- public void addItem(@NonNull ResourceItem item) throws ConsumerException {
- if (item.isTouched()) {
- AbstractResourceRepository.this.addItem(item);
- }
- }
-
- @Override
- public void removeItem(@NonNull ResourceItem removedItem, @Nullable ResourceItem replacedBy)
- throws ConsumerException {
- AbstractResourceRepository.this.removeItem(removedItem);
- }
-
- @Override
- public boolean ignoreItemInMerge(ResourceItem item) {
- // we never ignore any item.
- return false;
- }
- }
-
- public AbstractResourceRepository(boolean isFramework) {
- mFramework = isFramework;
- }
-
- public boolean isFramework() {
- return mFramework;
- }
-
- @NonNull
- public MergeConsumer<ResourceItem> createMergeConsumer() {
- return new RepositoryMerger();
- }
-
- @NonNull
- protected abstract Map<ResourceType, ListMultimap<String, ResourceItem>> getMap();
-
- @Nullable
- protected abstract ListMultimap<String, ResourceItem> getMap(ResourceType type, boolean create);
-
- @NonNull
- protected ListMultimap<String, ResourceItem> getMap(ResourceType type) {
- //noinspection ConstantConditions
- return getMap(type, true); // Won't return null if create is false
- }
-
- @NonNull
- public Map<ResourceType, ListMultimap<String, ResourceItem>> getItems() {
- return getMap();
- }
-
- /** Lock used to protect map access */
- protected static final Object ITEM_MAP_LOCK = new Object();
-
- // TODO: Rename to getResourceItemList?
- @Nullable
- public List<ResourceItem> getResourceItem(@NonNull ResourceType resourceType,
- @NonNull String resourceName) {
- synchronized (ITEM_MAP_LOCK) {
- ListMultimap<String, ResourceItem> map = getMap(resourceType, false);
-
- if (map != null) {
- return map.get(resourceName);
- }
- }
-
- return null;
- }
-
- @NonNull
- public Collection<String> getItemsOfType(@NonNull ResourceType type) {
- synchronized (ITEM_MAP_LOCK) {
- Multimap<String, ResourceItem> map = getMap(type, false);
- if (map == null) {
- return Collections.emptyList();
- }
- return Collections.unmodifiableCollection(map.keySet());
- }
- }
-
- /**
- * Returns true if this resource repository contains a resource of the given
- * name.
- *
- * @param url the resource URL
- * @return true if the resource is known
- */
- public boolean hasResourceItem(@NonNull String url) {
- // Handle theme references
- if (url.startsWith(PREFIX_THEME_REF)) {
- String remainder = url.substring(PREFIX_THEME_REF.length());
- if (url.startsWith(ATTR_REF_PREFIX)) {
- url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
- return hasResourceItem(url);
- }
- int colon = url.indexOf(':');
- if (colon != -1) {
- // Convert from ?android:progressBarStyleBig to ?android:attr/progressBarStyleBig
- if (remainder.indexOf('/', colon) == -1) {
- remainder = remainder.substring(0, colon) + RESOURCE_CLZ_ATTR + '/'
- + remainder.substring(colon);
- }
- url = PREFIX_RESOURCE_REF + remainder;
- return hasResourceItem(url);
- } else {
- int slash = url.indexOf('/');
- if (slash == -1) {
- url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder;
- return hasResourceItem(url);
- }
- }
- }
-
- if (!url.startsWith(PREFIX_RESOURCE_REF)) {
- return false;
- }
-
- assert url.startsWith("@") || url.startsWith("?") : url;
-
- int typeEnd = url.indexOf('/', 1);
- if (typeEnd != -1) {
- int nameBegin = typeEnd + 1;
-
- // Skip @ and @+
- int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
-
- int colon = url.lastIndexOf(':', typeEnd);
- if (colon != -1) {
- typeBegin = colon + 1;
- }
- String typeName = url.substring(typeBegin, typeEnd);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type != null) {
- String name = url.substring(nameBegin);
- return hasResourceItem(type, name);
- }
- }
-
- return false;
- }
-
- /**
- * Returns true if this resource repository contains a resource of the given
- * name.
- *
- * @param resourceType the type of resource to look up
- * @param resourceName the name of the resource
- * @return true if the resource is known
- */
- public boolean hasResourceItem(@NonNull ResourceType resourceType,
- @NonNull String resourceName) {
- synchronized (ITEM_MAP_LOCK) {
- ListMultimap<String, ResourceItem> map = getMap(resourceType, false);
-
- if (map != null) {
- List<ResourceItem> itemList = map.get(resourceName);
- return itemList != null && !itemList.isEmpty();
- }
- }
-
- return false;
- }
-
- /**
- * Returns whether the repository has resources of a given {@link ResourceType}.
- * @param resourceType the type of resource to check.
- * @return true if the repository contains resources of the given type, false otherwise.
- */
- public boolean hasResourcesOfType(@NonNull ResourceType resourceType) {
- synchronized (ITEM_MAP_LOCK) {
- ListMultimap<String, ResourceItem> map = getMap(resourceType, false);
- return map != null && !map.isEmpty();
- }
- }
-
- @NonNull
- public List<ResourceType> getAvailableResourceTypes() {
- synchronized (ITEM_MAP_LOCK) {
- return Lists.newArrayList(getMap().keySet());
- }
- }
-
- /**
- * Returns the {@link ResourceFile} matching the given name, {@link ResourceType} and
- * configuration.
- * <p/>
- * This only works with files generating one resource named after the file
- * (for instance, layouts, bitmap based drawable, xml, anims).
- *
- * @param name the resource name
- * @param type the folder type search for
- * @param config the folder configuration to match for
- * @return the matching file or <code>null</code> if no match was found.
- */
- @Nullable
- public ResourceFile getMatchingFile(
- @NonNull String name,
- @NonNull ResourceType type,
- @NonNull FolderConfiguration config) {
- List<ResourceFile> matchingFiles = getMatchingFiles(name, type, config);
- return matchingFiles.isEmpty() ? null : matchingFiles.get(0);
- }
-
- /**
- * Returns a list of {@link ResourceFile} matching the given name, {@link ResourceType} and
- * configuration. This ignores the qualifiers which are missing from the configuration.
- * <p/>
- * This only works with files generating one resource named after the file (for instance,
- * layouts, bitmap based drawable, xml, anims).
- *
- * @param name the resource name
- * @param type the folder type search for
- * @param config the folder configuration to match for
- *
- * @see #getMatchingFile(String, ResourceType, FolderConfiguration)
- */
- @NonNull
- public List<ResourceFile> getMatchingFiles(
- @NonNull String name,
- @NonNull ResourceType type,
- @NonNull FolderConfiguration config) {
- return getMatchingFiles(name, type, config, new HashSet<String>(), 0);
- }
-
- @NonNull
- private List<ResourceFile> getMatchingFiles(
- @NonNull String name,
- @NonNull ResourceType type,
- @NonNull FolderConfiguration config,
- @NonNull Set<String> seenNames,
- int depth) {
- assert !seenNames.contains(name);
- if (depth >= MAX_RESOURCE_INDIRECTION) {
- return Collections.emptyList();
- }
- List<ResourceFile> output;
- synchronized (ITEM_MAP_LOCK) {
- ListMultimap<String, ResourceItem> typeItems = getMap(type, false);
- if (typeItems == null) {
- return Collections.emptyList();
- }
- seenNames.add(name);
- output = new ArrayList<ResourceFile>();
- List<ResourceItem> matchingItems = typeItems.get(name);
- List<Configurable> matches = config.findMatchingConfigurables(matchingItems);
- for (Configurable conf : matches) {
- ResourceItem match = (ResourceItem) conf;
- // if match is an alias, check if the name is in seen names.
- ResourceValue resourceValue = match.getResourceValue(isFramework());
- if (resourceValue != null) {
- String value = resourceValue.getValue();
- if (value != null && value.startsWith(PREFIX_RESOURCE_REF)) {
- ResourceUrl url = ResourceUrl.parse(value);
- if (url != null && url.type == type && url.framework == isFramework()) {
- if (!seenNames.contains(url.name)) {
- // This resource alias needs to be resolved again.
- output.addAll(getMatchingFiles(
- url.name, type, config, seenNames, depth + 1));
- }
- continue;
- }
- }
- }
- output.add(match.getSource());
-
- }
- }
-
- return output;
- }
-
- /**
- * Returns the resources values matching a given {@link FolderConfiguration}.
- *
- * @param referenceConfig the configuration that each value must match.
- * @return a map with guaranteed to contain an entry for each {@link ResourceType}
- */
- @NonNull
- public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
- @NonNull FolderConfiguration referenceConfig) {
- Map<ResourceType, Map<String, ResourceValue>> map = Maps.newEnumMap(ResourceType.class);
-
- synchronized (ITEM_MAP_LOCK) {
- Map<ResourceType, ListMultimap<String, ResourceItem>> itemMap = getMap();
- for (ResourceType key : ResourceType.values()) {
- // get the local results and put them in the map
- map.put(key, getConfiguredResources(itemMap, key, referenceConfig));
- }
- }
-
- return map;
- }
-
- /**
- * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
- * <p/>The values returned are taken from the resource files best matching a given
- * {@link FolderConfiguration}.
- * @param type the type of the resources.
- * @param referenceConfig the configuration to best match.
- */
- @NonNull
- public Map<String, ResourceValue> getConfiguredResources(
- @NonNull ResourceType type,
- @NonNull FolderConfiguration referenceConfig) {
- return getConfiguredResources(getMap(), type, referenceConfig);
- }
-
- @NonNull
- public Map<String, ResourceValue> getConfiguredResources(
- @NonNull Map<ResourceType, ListMultimap<String, ResourceItem>> itemMap,
- @NonNull ResourceType type,
- @NonNull FolderConfiguration referenceConfig) {
- // get the resource item for the given type
- ListMultimap<String, ResourceItem> items = itemMap.get(type);
- if (items == null) {
- return Maps.newHashMap();
- }
-
- Set<String> keys = items.keySet();
-
- // create the map
- Map<String, ResourceValue> map = Maps.newHashMapWithExpectedSize(keys.size());
-
- for (String key : keys) {
- List<ResourceItem> keyItems = items.get(key);
-
- // look for the best match for the given configuration
- // the match has to be of type ResourceFile since that's what the input list contains
- ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
- if (match != null) {
- ResourceValue value = match.getResourceValue(mFramework);
- if (value != null) {
- map.put(match.getName(), value);
- }
- }
- }
-
- return map;
- }
-
- @Nullable
- public ResourceValue getConfiguredValue(
- @NonNull ResourceType type,
- @NonNull String name,
- @NonNull FolderConfiguration referenceConfig) {
- // get the resource item for the given type
- ListMultimap<String, ResourceItem> items = getMap(type, false);
- if (items == null) {
- return null;
- }
-
- List<ResourceItem> keyItems = items.get(name);
- if (keyItems == null) {
- return null;
- }
-
- // look for the best match for the given configuration
- // the match has to be of type ResourceFile since that's what the input list contains
- ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
- return match != null ? match.getResourceValue(mFramework) : null;
- }
-
- private void addItem(@NonNull ResourceItem item) {
- synchronized (ITEM_MAP_LOCK) {
- ListMultimap<String, ResourceItem> map = getMap(item.getType());
- if (!map.containsValue(item)) {
- map.put(item.getName(), item);
- }
- }
- }
-
- private void removeItem(@NonNull ResourceItem removedItem) {
- synchronized (ITEM_MAP_LOCK) {
- Multimap<String, ResourceItem> map = getMap(removedItem.getType(), false);
- if (map != null) {
- map.remove(removedItem.getName(), removedItem);
- }
- }
- }
-
- /**
- * Returns the sorted list of languages used in the resources.
- */
- @NonNull
- public SortedSet<String> getLanguages() {
- SortedSet<String> set = new TreeSet<String>();
-
- // As an optimization we could just look for values since that's typically where
- // the languages are defined -- not on layouts, menus, etc -- especially if there
- // are no translations for it
- Set<String> qualifiers = Sets.newHashSet();
-
- synchronized (ITEM_MAP_LOCK) {
- for (ListMultimap<String, ResourceItem> map : getMap().values()) {
- for (ResourceItem item : map.values()) {
- qualifiers.add(item.getQualifiers());
- }
- }
- }
-
- for (String s : qualifiers) {
- FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
- if (configuration != null) {
- LocaleQualifier locale = configuration.getLocaleQualifier();
- if (locale != null) {
- set.add(locale.getLanguage());
- }
- }
- }
-
- return set;
- }
-
- /**
- * Returns the sorted list of languages used in the resources.
- */
- @NonNull
- public SortedSet<LocaleQualifier> getLocales() {
- SortedSet<LocaleQualifier> set = new TreeSet<LocaleQualifier>();
-
- // As an optimization we could just look for values since that's typically where
- // the languages are defined -- not on layouts, menus, etc -- especially if there
- // are no translations for it
- Set<String> qualifiers = Sets.newHashSet();
-
- synchronized (ITEM_MAP_LOCK) {
- for (ListMultimap<String, ResourceItem> map : getMap().values()) {
- for (ResourceItem item : map.values()) {
- qualifiers.add(item.getQualifiers());
- }
- }
- }
-
- for (String s : qualifiers) {
- FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
- if (configuration != null) {
- LocaleQualifier locale = configuration.getLocaleQualifier();
- if (locale != null) {
- set.add(locale);
- }
- }
- }
-
- return set;
- }
-
- /**
- * Returns the sorted list of regions used in the resources with the given language.
- * @param currentLanguage the current language the region must be associated with.
- */
- @NonNull
- public SortedSet<String> getRegions(@NonNull String currentLanguage) {
- SortedSet<String> set = new TreeSet<String>();
-
- // As an optimization we could just look for values since that's typically where
- // the languages are defined -- not on layouts, menus, etc -- especially if there
- // are no translations for it
- Set<String> qualifiers = Sets.newHashSet();
- synchronized (ITEM_MAP_LOCK) {
- for (ListMultimap<String, ResourceItem> map : getMap().values()) {
- for (ResourceItem item : map.values()) {
- qualifiers.add(item.getQualifiers());
- }
- }
- }
-
- for (String s : qualifiers) {
- FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
- if (configuration != null) {
- LocaleQualifier locale = configuration.getLocaleQualifier();
- if (locale != null && locale.getRegion() != null
- && locale.getLanguage().equals(currentLanguage)) {
- set.add(locale.getRegion());
- }
- }
- }
-
- return set;
- }
-
- public void clear() {
- getMap().clear();
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataItem.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/DataItem.java
deleted file mode 100644
index ff0ce90..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataItem.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.io.File;
-
-/**
- * Base item.
- *
- * This includes its name and source file as a {@link DataFile}.
- *
- */
-abstract class DataItem<F extends DataFile> {
-
- private static final int MASK_TOUCHED = 0x01;
- private static final int MASK_REMOVED = 0x02;
- private static final int MASK_WRITTEN = 0x10;
-
- private final String mName;
- private F mSource;
-
- /**
- * The status of the Item. It's a bit mask as opposed to an enum
- * to differentiate removed and removed+written
- */
- private int mStatus = 0;
-
- /**
- * Constructs the object with a name, type and optional value.
- *
- * Note that the object is not fully usable as-is. It must be added to a DataFile first.
- *
- * @param name the name of the item
- */
- DataItem(@NonNull String name) {
- mName = name;
- }
-
- /**
- * Returns the name of the item.
- * @return the name.
- */
- @NonNull
- public String getName() {
- return mName;
- }
-
- /**
- * Returns the DataFile the item is coming from. Can be null.
- * @return the data file.
- */
- @Nullable
- public F getSource() {
- return mSource;
- }
-
- /**
- * Sets the DataFile
- * @param sourceFile the DataFile
- */
- public void setSource(F sourceFile) {
- mSource = sourceFile;
- }
-
- public File getFile() {
- return getSource().getFile();
- }
-
- /**
- * Resets the state of the item be nothing.
- * @return this
- *
- */
- DataItem resetStatus() {
- mStatus = 0;
- return this;
- }
-
- /**
- * Resets the state of the item be WRITTEN. All other states are removed.
- * @return this
- *
- * @see #isWritten()
- */
- DataItem resetStatusToWritten() {
- mStatus = MASK_WRITTEN;
- return this;
- }
-
- /**
- * Resets the state of the item be TOUCHED. All other states are removed.
- * @return this
- *
- * @see #isWritten()
- */
- DataItem resetStatusToTouched() {
- mStatus = MASK_TOUCHED;
- return this;
- }
-
- /**
- * Sets the item status to be WRITTEN. Other states are kept.
- * @return this
- *
- * @see #isWritten()
- */
- DataItem setWritten() {
- mStatus |= MASK_WRITTEN;
- return this;
- }
-
- /**
- * Sets the item status to be REMOVED. Other states are kept.
- * @return this
- *
- * @see #isRemoved()
- */
- DataItem setRemoved() {
- mStatus |= MASK_REMOVED;
- return this;
- }
-
- /**
- * Sets the item status to be TOUCHED. Other states are kept.
- * @return this
- *
- * @see #isTouched()
- */
- DataItem setTouched() {
- mStatus |= MASK_TOUCHED;
- wasTouched();
- return this;
- }
-
- /**
- * Returns whether the item status is REMOVED
- * @return true if removed
- */
- boolean isRemoved() {
- return (mStatus & MASK_REMOVED) != 0;
- }
-
- /**
- * Returns whether the item status is TOUCHED
- * @return true if touched
- */
- boolean isTouched() {
- return (mStatus & MASK_TOUCHED) != 0;
- }
-
- /**
- * Returns whether the item status is WRITTEN
- * @return true if written
- */
- boolean isWritten() {
- return (mStatus & MASK_WRITTEN) != 0;
- }
-
- protected int getStatus() {
- return mStatus;
- }
-
- /**
- * Returns a key for this item. They key uniquely identifies this item. This is the name.
- *
- * @return the key for this item.
- *
- */
- public String getKey() {
- return mName;
- }
-
- void addExtraAttributes(Document document, Node node, String namespaceUri) {
- // nothing
- }
-
- /**
- * Returns a node that describes additional properties of this {@link DataItem}. If not null, it
- * will be persisted in the merger XML blob and can be used used to restore the exact state of
- * this item.
- */
- Node getDetailsXml(Document document) {
- return null;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- DataItem dataItem = (DataItem) o;
-
- if (!mName.equals(dataItem.mName)) {
- return false;
- }
-
- return !(mSource != null ? !mSource.equals(dataItem.mSource) : dataItem.mSource != null);
- }
-
- @Override
- public int hashCode() {
- int result = mName.hashCode();
- result = 31 * result + (mSource != null ? mSource.hashCode() : 0);
- return result;
- }
-
- protected void wasTouched() {
-
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java
deleted file mode 100755
index c25926b..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java
+++ /dev/null
@@ -1,694 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Merges {@link DataSet}s and writes a resulting data folder.
- *
- * This is able to save its post work state and reload this for incremental update.
- */
-abstract class DataMerger<I extends DataItem<F>, F extends DataFile<I>, S extends DataSet<I,F>> implements DataMap<I> {
-
- static final String FN_MERGER_XML = "merger.xml";
- static final String NODE_MERGER = "merger";
- static final String NODE_DATA_SET = "dataSet";
-
- static final String NODE_CONFIGURATION = "configuration";
-
- static final String ATTR_VERSION = "version";
- static final String MERGE_BLOB_VERSION = "3";
-
- @NonNull
- protected final DocumentBuilderFactory mFactory;
-
- /**
- * All the DataSets.
- */
- private final List<S> mDataSets = Lists.newArrayList();
-
- public DataMerger() {
- mFactory = DocumentBuilderFactory.newInstance();
- mFactory.setNamespaceAware(true);
- mFactory.setValidating(false);
- mFactory.setIgnoringComments(true);
- }
-
- protected abstract S createFromXml(Node node) throws MergingException;
-
- protected abstract boolean requiresMerge(@NonNull String dataItemKey);
-
- /**
- * Merge items together, and register the merged items with the given consumer.
- * @param dataItemKey the key for the items
- * @param items the items, from lower priority to higher priority.
- * @param consumer the consumer to receive the merged items.
- * @throws MergingException
- */
- protected abstract void mergeItems(
- @NonNull String dataItemKey,
- @NonNull List<I> items,
- @NonNull MergeConsumer<I> consumer) throws MergingException;
-
- /**
- * adds a new {@link DataSet} and overlays it on top of the existing DataSet.
- *
- * @param resourceSet the ResourceSet to add.
- */
- public void addDataSet(S resourceSet) {
- // TODO figure out if we allow partial overlay through a per-resource flag.
- mDataSets.add(resourceSet);
- }
-
- /**
- * Returns the list of ResourceSet objects.
- * @return the resource sets.
- */
- @NonNull
- public List<S> getDataSets() {
- return mDataSets;
- }
-
- @VisibleForTesting
- void validateDataSets() throws DuplicateDataException {
- for (S resourceSet : mDataSets) {
- resourceSet.checkItems();
- }
- }
-
- /**
- * Returns the number of items.
- * @return the number of items.
- *
- * @see DataMap
- */
- @Override
- public int size() {
- // put all the resource keys in a set.
- Set<String> keys = Sets.newHashSet();
-
- for (S resourceSet : mDataSets) {
- ListMultimap<String, I> map = resourceSet.getDataMap();
- keys.addAll(map.keySet());
- }
-
- return keys.size();
- }
-
- /**
- * Returns a map of the data items.
- * @return a map of items.
- *
- * @see DataMap
- */
- @NonNull
- @Override
- public ListMultimap<String, I> getDataMap() {
- // put all the sets in a multimap. The result is that for each key,
- // there is a sorted list of items from all the layers, including removed ones.
- ListMultimap<String, I> fullItemMultimap = ArrayListMultimap.create();
-
- for (S resourceSet : mDataSets) {
- ListMultimap<String, I> map = resourceSet.getDataMap();
- for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) {
- fullItemMultimap.putAll(entry.getKey(), entry.getValue());
- }
- }
-
- return fullItemMultimap;
- }
-
- /**
- * Merges the data into a given consumer.
- *
- * @param consumer the consumer of the merge.
- * @param doCleanUp clean up the state to be able to do further incremental merges. If this
- * is a one-shot merge, this can be false to improve performance.
-
- * @throws MergingException such as a DuplicateDataException or a
- * MergeConsumer.ConsumerException if something goes wrong
- */
- public void mergeData(@NonNull MergeConsumer<I> consumer, boolean doCleanUp)
- throws MergingException {
-
- consumer.start(mFactory);
-
- try {
- // get all the items keys.
- Set<String> dataItemKeys = Sets.newHashSet();
-
- for (S dataSet : mDataSets) {
- // quick check on duplicates in the resource set.
- dataSet.checkItems();
- ListMultimap<String, I> map = dataSet.getDataMap();
- dataItemKeys.addAll(map.keySet());
- }
-
- // loop on all the data items.
- for (String dataItemKey : dataItemKeys) {
- if (requiresMerge(dataItemKey)) {
- // get all the available items, from the lower priority, to the higher
- // priority
- List<I> items = Lists.newArrayListWithExpectedSize(mDataSets.size());
- for (S dataSet : mDataSets) {
-
- // look for the resource key in the set
- ListMultimap<String, I> itemMap = dataSet.getDataMap();
-
- List<I> setItems = itemMap.get(dataItemKey);
- items.addAll(setItems);
- }
-
- mergeItems(dataItemKey, items, consumer);
- continue;
- }
-
- // for each items, look in the data sets, starting from the end of the list.
-
- I previouslyWritten = null;
- I toWrite = null;
-
- /*
- * We are looking for what to write/delete: the last non deleted item, and the
- * previously written one.
- */
-
- boolean foundIgnoredItem = false;
-
- setLoop: for (int i = mDataSets.size() - 1 ; i >= 0 ; i--) {
- S dataSet = mDataSets.get(i);
-
- // look for the resource key in the set
- ListMultimap<String, I> itemMap = dataSet.getDataMap();
-
- List<I> items = itemMap.get(dataItemKey);
- if (items.isEmpty()) {
- continue;
- }
-
- // The list can contain at max 2 items. One touched and one deleted.
- // More than one deleted means there was more than one which isn't possible
- // More than one touched means there is more than one and this isn't possible.
- for (int ii = items.size() - 1 ; ii >= 0 ; ii--) {
- I item = items.get(ii);
-
- if (consumer.ignoreItemInMerge(item)) {
- foundIgnoredItem = true;
- continue;
- }
-
- if (item.isWritten()) {
- assert previouslyWritten == null;
- previouslyWritten = item;
- }
-
- if (toWrite == null && !item.isRemoved()) {
- toWrite = item;
- }
-
- if (toWrite != null && previouslyWritten != null) {
- break setLoop;
- }
- }
- }
-
- // done searching, we should at least have something, unless we only
- // found items that are not meant to be written (attr inside declare styleable)
- assert foundIgnoredItem || previouslyWritten != null || toWrite != null;
-
- //noinspection ConstantConditions
- if (previouslyWritten == null && toWrite == null) {
- continue;
- }
-
- // now need to handle, the type of each (single res file, multi res file), whether
- // they are the same object or not, whether the previously written object was deleted.
-
- if (toWrite == null) {
- // nothing to write? delete only then.
- assert previouslyWritten.isRemoved();
-
- consumer.removeItem(previouslyWritten, null /*replacedBy*/);
-
- } else if (previouslyWritten == null || previouslyWritten == toWrite) {
- // easy one: new or updated res
- consumer.addItem(toWrite);
- } else {
- // replacement of a resource by another.
-
- // force write the new value
- toWrite.setTouched();
- consumer.addItem(toWrite);
- // and remove the old one
- consumer.removeItem(previouslyWritten, toWrite);
- }
- }
- } finally {
- consumer.end();
- }
-
- if (doCleanUp) {
- // reset all states. We can't just reset the toWrite and previouslyWritten objects
- // since overlayed items might have been touched as well.
- // Should also clean (remove) objects that are removed.
- postMergeCleanUp();
- }
- }
-
- /**
- * Writes a single blob file to store all that the DataMerger knows about.
- *
- * @param blobRootFolder the root folder where blobs are store.
- * @param consumer the merge consumer that was used by the merge.
- *
- * @throws MergingException if something goes wrong
- *
- * @see #loadFromBlob(File, boolean)
- */
- public void writeBlobTo(@NonNull File blobRootFolder, @NonNull MergeConsumer<I> consumer)
- throws MergingException {
- // write "compact" blob
- DocumentBuilder builder;
-
- try {
- builder = mFactory.newDocumentBuilder();
- Document document = builder.newDocument();
-
- Node rootNode = document.createElement(NODE_MERGER);
- // add the version code.
- NodeUtils.addAttribute(document, rootNode, null, ATTR_VERSION, MERGE_BLOB_VERSION);
-
- document.appendChild(rootNode);
-
- for (S dataSet : mDataSets) {
- Node dataSetNode = document.createElement(NODE_DATA_SET);
- rootNode.appendChild(dataSetNode);
-
- dataSet.appendToXml(dataSetNode, document, consumer);
- }
-
- // write merged items
- writeAdditionalData(document, rootNode);
-
- String content = XmlUtils.toXml(document);
-
- try {
- createDir(blobRootFolder);
- } catch (IOException ioe) {
- throw MergingException.wrapException(ioe).withFile(blobRootFolder).build();
- }
- File file = new File(blobRootFolder, FN_MERGER_XML);
- try {
- Files.write(content, file, Charsets.UTF_8);
- } catch (IOException ioe) {
- throw MergingException.wrapException(ioe).withFile(file).build();
- }
- } catch (ParserConfigurationException e) {
- throw MergingException.wrapException(e).build();
- }
- }
-
- /**
- * Loads the merger state from a blob file.
- *
- * This can be loaded into two different ways that differ only by the state on the
- * {@link DataItem} objects.
- *
- * If <var>incrementalState</var> is <code>true</code> then the items that are on disk are
- * marked as written ({@link DataItem#isWritten()} returning <code>true</code>.
- * This is to be used by {@link MergeWriter} to update a merged res folder.
- *
- * If <code>false</code>, the items are marked as touched, and this can be used to feed a new
- * {@link ResourceRepository} object.
- *
- * @param blobRootFolder the folder containing the blob.
- * @param incrementalState whether to load into an incremental state or a new state.
- * @return true if the blob was loaded.
- * @throws MergingException if something goes wrong
- *
- * @see #writeBlobTo(File, MergeConsumer)
- */
- public boolean loadFromBlob(@NonNull File blobRootFolder, boolean incrementalState)
- throws MergingException {
- File file = new File(blobRootFolder, FN_MERGER_XML);
- if (!file.isFile()) {
- return false;
- }
-
- try {
- Document document = XmlUtils.parseUtfXmlFile(file, true /*namespaceAware*/);
-
- // get the root node
- Node rootNode = document.getDocumentElement();
- if (rootNode == null || !NODE_MERGER.equals(rootNode.getLocalName())) {
- return false;
- }
-
- // get the version code.
- String version = null;
- Attr versionAttr = (Attr) rootNode.getAttributes().getNamedItem(ATTR_VERSION);
- if (versionAttr != null) {
- version = versionAttr.getValue();
- }
- if (!MERGE_BLOB_VERSION.equals(version)) {
- return false;
- }
-
- NodeList nodes = rootNode.getChildNodes();
-
- for (int i = 0, n = nodes.getLength(); i < n; i++) {
- Node node = nodes.item(i);
-
- if (node.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
-
- if (NODE_DATA_SET.equals(node.getLocalName())) {
- S dataSet = createFromXml(node);
- if (dataSet != null) {
- addDataSet(dataSet);
- }
- } else if (incrementalState
- && getAdditionalDataTagName().equals(node.getLocalName())) {
- loadAdditionalData(node, incrementalState);
- }
- }
-
- if (incrementalState) {
- setPostBlobLoadStateToWritten();
- } else {
- setPostBlobLoadStateToTouched();
- }
-
- return true;
- } catch (SAXParseException e) {
- throw MergingException.wrapException(e).withFile(file).build();
- } catch (IOException e) {
- throw MergingException.wrapException(e).withFile(file).build();
- } catch (ParserConfigurationException e) {
- throw MergingException.wrapException(e).withFile(file).build();
- } catch (SAXException e) {
- throw MergingException.wrapException(e).withFile(file).build();
- }
- }
-
- @NonNull
- protected String getAdditionalDataTagName() {
- // No tag can have an empty name, so mergers that store additional data, have to provide
- // this.
- return "";
- }
-
- protected void loadAdditionalData(@NonNull Node additionalDataNode, boolean incrementalState)
- throws MergingException {
- // do nothing by default.
- }
-
- protected void writeAdditionalData(Document document, Node rootNode) throws MergingException {
- // do nothing by default.
- }
-
- public void cleanBlob(@NonNull File blobRootFolder) {
- File file = new File(blobRootFolder, FN_MERGER_XML);
- if (file.isFile()) {
- file.delete();
- }
- }
-
- /**
- * Sets the post blob load state to WRITTEN.
- *
- * After a load from the blob file, all items have their state set to nothing.
- * If the load mode is set to incrementalState then we want the items that are in the current
- * merge result to have their state be WRITTEN.
- *
- * This will allow further updates with {@link #mergeData(MergeConsumer, boolean)} to ignore the
- * state at load time and only apply the new changes.
- *
- * @see #loadFromBlob(java.io.File, boolean)
- * @see DataItem#isWritten()
- */
- private void setPostBlobLoadStateToWritten() {
- ListMultimap<String, I> itemMap = ArrayListMultimap.create();
-
- // put all the sets into list per keys. The order is important as the lower sets are
- // overridden by the higher sets.
- for (S dataSet : mDataSets) {
- ListMultimap<String, I> map = dataSet.getDataMap();
- for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) {
- itemMap.putAll(entry.getKey(), entry.getValue());
- }
- }
-
- // the items that represent the current state is the last item in the list for each key.
- for (String key : itemMap.keySet()) {
- List<I> itemList = itemMap.get(key);
- itemList.get(itemList.size() - 1).resetStatusToWritten();
- }
- }
-
- /**
- * Sets the post blob load state to TOUCHED.
- *
- * After a load from the blob file, all items have their state set to nothing.
- * If the load mode is not set to incrementalState then we want the items that are in the
- * current merge result to have their state be TOUCHED.
- *
- * This will allow the first use of {@link #mergeData(MergeConsumer, boolean)} to add these
- * to the consumer as if they were new items.
- *
- * @see #loadFromBlob(java.io.File, boolean)
- * @see DataItem#isTouched()
- */
- private void setPostBlobLoadStateToTouched() {
- ListMultimap<String, I> itemMap = ArrayListMultimap.create();
-
- // put all the sets into list per keys. The order is important as the lower sets are
- // overridden by the higher sets.
- for (S dataSet : mDataSets) {
- ListMultimap<String, I> map = dataSet.getDataMap();
- for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) {
- itemMap.putAll(entry.getKey(), entry.getValue());
- }
- }
-
- // the items that represent the current state is the last item in the list for each key.
- for (String key : itemMap.keySet()) {
- List<I> itemList = itemMap.get(key);
- itemList.get(itemList.size() - 1).resetStatusToTouched();
- }
- }
-
- /**
- * Post merge clean up.
- *
- * - Remove the removed items.
- * - Clear the state of all the items (this allow newly overridden items to lose their
- * WRITTEN state)
- * - Set the items that are part of the new merge to be WRITTEN to allow the next merge to
- * be incremental.
- */
- private void postMergeCleanUp() {
- ListMultimap<String, I> itemMap = ArrayListMultimap.create();
-
- // remove all removed items, and copy the rest in the full map while resetting their state.
- for (S dataSet : mDataSets) {
- ListMultimap<String, I> map = dataSet.getDataMap();
-
- List<String> keys = Lists.newArrayList(map.keySet());
- for (String key : keys) {
- List<I> list = map.get(key);
- for (int i = 0 ; i < list.size() ;) {
- I item = list.get(i);
- if (item.isRemoved()) {
- list.remove(i);
- } else {
- //noinspection unchecked
- itemMap.put(key, (I) item.resetStatus());
- i++;
- }
- }
- }
- }
-
- // for the last items (the one that have been written into the consumer), set their
- // state to WRITTEN
- for (String key : itemMap.keySet()) {
- List<I> itemList = itemMap.get(key);
- itemList.get(itemList.size() - 1).resetStatusToWritten();
- }
- }
-
- /**
- * Checks that a loaded merger can be updated with a given list of DataSet.
- *
- * For now this means the sets haven't changed.
- *
- * @param dataSets the resource sets.
- * @return true if the update can be performed. false if a full merge should be done.
- */
- public boolean checkValidUpdate(List<S> dataSets) {
- if (dataSets.size() != mDataSets.size()) {
- return false;
- }
-
- for (int i = 0, n = dataSets.size(); i < n; i++) {
- S localSet = mDataSets.get(i);
- S newSet = dataSets.get(i);
-
- List<File> localSourceFiles = localSet.getSourceFiles();
- List<File> newSourceFiles = newSet.getSourceFiles();
-
- // compare the config name and source files sizes.
- if (!newSet.getConfigName().equals(localSet.getConfigName()) ||
- localSourceFiles.size() != newSourceFiles.size()) {
- return false;
- }
-
- // compare the source files. The order is not important so it should be normalized
- // before it's compared.
- // make copies to sort.
- localSourceFiles = Lists.newArrayList(localSourceFiles);
- Collections.sort(localSourceFiles);
- newSourceFiles = Lists.newArrayList(newSourceFiles);
- Collections.sort(newSourceFiles);
-
- if (!localSourceFiles.equals(newSourceFiles)) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Finds the {@link DataSet} that contains the given file.
- * This methods will also performs some checks to make sure the given file is a valid file
- * in the data set.
- *
- * All the information is set in a {@link FileValidity} object that is returned.
- *
- * {@link FileValidity} contains information about the changed file including:
- * - is it from an known set, is it an ignored file, or is it unknown?
- * - what data set does it belong to
- * - what source folder in the data set does it belong to.
- *
- * "belong" means that the DataSet has a source file/folder that is the root folder
- * of this file. The folder and/or file doesn't have to exist.
- *
- * @param file the file to check
- *
- * @return a new FileValidity.
- */
- public FileValidity<S> findDataSetContaining(@NonNull File file) {
- return findDataSetContaining(file, null);
- }
-
- /**
- * Finds the {@link DataSet} that contains the given file.
- * This methods will also performs some checks to make sure the given file is a valid file
- * in the data set.
- *
- * All the information is set in a {@link FileValidity} object that is returned. If an instance
- * is passed, then this object is filled instead, and returned.
- *
- * {@link FileValidity} contains information about the changed file including:
- * - is it from an known set, is it an ignored file, or is it unknown?
- * - what data set does it belong to
- * - what source folder in the data set does it belong to.
- *
- * "belong" means that the DataSet has a source file/folder that is the root folder
- * of this file. The folder and/or file doesn't have to exist.
- *
- * @param file the file to check
- * @param fileValidity an optional FileValidity to fill. If null a new one is returned.
- *
- * @return a new FileValidity or the one given as a parameter.
- */
- public FileValidity<S> findDataSetContaining(@NonNull File file,
- @Nullable FileValidity<S> fileValidity) {
- if (fileValidity == null) {
- fileValidity = new FileValidity<S>();
- }
-
- if (mDataSets.isEmpty()) {
- fileValidity.status = FileValidity.FileStatus.UNKNOWN_FILE;
- return fileValidity;
- }
-
- if (DataSet.isIgnored(file)) {
- fileValidity.status = FileValidity.FileStatus.IGNORED_FILE;
- return fileValidity;
- }
-
- for (S dataSet : mDataSets) {
- File sourceFile = dataSet.findMatchingSourceFile(file);
-
- if (sourceFile != null) {
- fileValidity.dataSet = dataSet;
- fileValidity.sourceFile = sourceFile;
- fileValidity.status = dataSet.isValidSourceFile(sourceFile, file) ?
- FileValidity.FileStatus.VALID_FILE : FileValidity.FileStatus.IGNORED_FILE;
- return fileValidity;
- }
- }
-
- fileValidity.status = FileValidity.FileStatus.UNKNOWN_FILE;
- return fileValidity;
- }
-
- protected synchronized void createDir(File folder) throws IOException {
- if (!folder.isDirectory() && !folder.mkdirs()) {
- throw new IOException("Failed to create directory: " + folder);
- }
- }
-
- @Override
- public String toString() {
- return Arrays.toString(mDataSets.toArray());
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java
deleted file mode 100644
index f7316fd..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-
-/**
- * Exception when a {@link DataItem} is declared more than once in a {@link DataSet}
- */
-public class DuplicateDataException extends MergingException {
-
- private static final String DUPLICATE_RESOURCES = "Duplicate resources";
-
- DuplicateDataException(Message[] messages) {
- super(null, messages);
- }
-
- static <I extends DataItem> Message[] createMessages(
- @NonNull Collection<Collection<I>> duplicateDataItemSets) {
- List<Message> messages = Lists.newArrayListWithCapacity(duplicateDataItemSets.size());
- for (Collection<I> duplicateItems : duplicateDataItemSets) {
- ImmutableList.Builder<SourceFilePosition> positions = ImmutableList.builder();
- for (I item : duplicateItems) {
- if (!item.isRemoved()) {
- positions.add(getPosition(item));
- }
- }
- messages.add(new Message(
- Message.Kind.ERROR,
- DUPLICATE_RESOURCES,
- DUPLICATE_RESOURCES,
- positions.build()));
- }
- return Iterables.toArray(messages, Message.class);
- }
-
- private static SourceFilePosition getPosition(DataItem item) {
- DataFile dataFile = item.getSource();
- if (dataFile == null) {
- return new SourceFilePosition(new SourceFile(item.getKey()), SourcePosition.UNKNOWN);
- }
- File f = dataFile.getFile();
- SourcePosition sourcePosition = SourcePosition.UNKNOWN; // TODO: find position in file.
- return new SourceFilePosition(new SourceFile(f, item.getKey()), sourcePosition);
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceSet.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceSet.java
deleted file mode 100644
index 587d347..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceSet.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.annotations.NonNull;
-import com.android.utils.ILogger;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.io.File;
-
-/**
- * A {@link ResourceSet} that contains only generated files (e.g. PNGs generated from a vector
- * drawable XML). It is always a mirror of a normal {@link ResourceSet} which delegates to this
- * set when it encounters a file that needs to be replaced by generated files.
- */
-public class GeneratedResourceSet extends ResourceSet {
-
- public GeneratedResourceSet(ResourceSet originalSet) {
- super(originalSet.getConfigName() + "$Generated", originalSet.getValidateEnabled());
- for (File source : originalSet.getSourceFiles()) {
- addSource(source);
- }
- }
-
- public GeneratedResourceSet(String name) {
- super(name);
- }
-
- @Override
- protected DataSet<ResourceItem, ResourceFile> createSet(String name) {
- return new GeneratedResourceSet(name);
- }
-
- @Override
- void appendToXml(
- @NonNull Node setNode,
- @NonNull Document document,
- @NonNull MergeConsumer<ResourceItem> consumer) {
- NodeUtils.addAttribute(document, setNode, null, "generated", "true");
- super.appendToXml(setNode, document, consumer);
- }
-
- @Override
- public void loadFromFiles(ILogger logger) throws MergingException {
- // Do nothing, the original set will hand us the generated files.
- }
-
- @Override
- public File findMatchingSourceFile(File file) {
- // Do nothing, the original set will hand us the generated files.
- return null;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java
deleted file mode 100644
index 3f4881e..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.google.common.base.Objects;
-import com.google.common.base.Throwables;
-
-import java.io.File;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-
-/**
- * A consumer of merges. Used with {@link DataMerger#mergeData(MergeConsumer, boolean)}.
- */
-public interface MergeConsumer<I extends DataItem> {
-
- /**
- * An exception thrown during by the consumer. It always contains the original exception as its
- * cause.
- */
- class ConsumerException extends MergingException {
-
- public ConsumerException(@NonNull Throwable cause) {
- this(cause, SourceFile.UNKNOWN);
- }
-
- public ConsumerException(@NonNull Throwable cause, @NonNull File file) {
- this(cause, new SourceFile(file));
- }
-
- private ConsumerException(@NonNull Throwable cause, @NonNull SourceFile file) {
- super(cause, new Message(
- Message.Kind.ERROR,
- Objects.firstNonNull(
- cause.getLocalizedMessage(),
- cause.getClass().getCanonicalName()),
- Throwables.getStackTraceAsString(cause),
- new SourceFilePosition(file, SourcePosition.UNKNOWN)));
- }
- }
-
- /**
- * Called before the merge starts.
- */
- void start(@NonNull DocumentBuilderFactory factory) throws ConsumerException;
-
- /**
- * Called after the merge ends.
- */
- void end() throws ConsumerException;
-
- /**
- * Adds an item. The item may already be existing. Calling {@link DataItem#isTouched()} will
- * indicate whether the item actually changed.
- *
- * @param item the new item.
- */
- void addItem(@NonNull I item) throws ConsumerException;
-
- /**
- * Removes an item. Optionally pass the item that will replace this one. This methods does not
- * do the replacement. The replaced item is just there in case the removal can be optimized when
- * it's a replacement vs. a removal.
- *
- * @param removedItem the removed item.
- * @param replacedBy the optional item that replaces the removed item.
- */
- void removeItem(@NonNull I removedItem, @Nullable I replacedBy) throws ConsumerException;
-
- boolean ignoreItemInMerge(I item);
-
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java
deleted file mode 100644
index ef56149..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.DOT_9PNG;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.RES_QUALIFIER_SEP;
-import static com.android.SdkConstants.TAG_EAT_COMMENT;
-import static com.android.SdkConstants.TAG_RESOURCES;
-import static com.android.utils.SdkUtils.createPathComment;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.ide.common.internal.PngException;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.utils.SdkUtils;
-import com.android.utils.XmlUtils;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-/**
- * A {@link MergeWriter} for assets, using {@link ResourceItem}.
- */
-public class MergedResourceWriter extends MergeWriter<ResourceItem> {
-
- @NonNull
- private final PngCruncher mCruncher;
-
- @NonNull
- private final ResourcePreprocessor mPreprocessor;
-
- /**
- * If non-null, points to a File that we should write public.txt to
- */
- private final File mPublicFile;
-
- private DocumentBuilderFactory mFactory;
-
- private boolean mInsertSourceMarkers = true;
-
- private final boolean mCrunchPng;
-
- private final boolean mProcess9Patch;
-
- private final int mCruncherKey;
-
- /**
- * map of XML values files to write after parsing all the files. the key is the qualifier.
- */
- private ListMultimap<String, ResourceItem> mValuesResMap;
-
- /**
- * Set of qualifier that had a previously written resource now gone. This is to keep a list of
- * values files that must be written out even with no touched or updated resources, in case one
- * or more resources were removed.
- */
- private Set<String> mQualifierWithDeletedValues;
-
- public MergedResourceWriter(@NonNull File rootFolder,
- @NonNull PngCruncher pngRunner,
- boolean crunchPng,
- boolean process9Patch,
- @Nullable File publicFile,
- @NonNull ResourcePreprocessor preprocessor) {
- super(rootFolder);
- mCruncher = pngRunner;
- mCruncherKey = mCruncher.start();
- mCrunchPng = crunchPng;
- mProcess9Patch = process9Patch;
- mPublicFile = publicFile;
- mPreprocessor = preprocessor;
- }
-
- /**
- * Sets whether this manifest merger will insert source markers into the merged source
- *
- * @param insertSourceMarkers if true, insert source markers
- */
- public void setInsertSourceMarkers(boolean insertSourceMarkers) {
- mInsertSourceMarkers = insertSourceMarkers;
- }
-
- /**
- * Returns whether this manifest merger will insert source markers into the merged source
- *
- * @return whether this manifest merger will insert source markers into the merged source
- */
- public boolean isInsertSourceMarkers() {
- return mInsertSourceMarkers;
- }
-
- @Override
- public void start(@NonNull DocumentBuilderFactory factory) throws ConsumerException {
- super.start(factory);
- mValuesResMap = ArrayListMultimap.create();
- mQualifierWithDeletedValues = Sets.newHashSet();
- mFactory = factory;
- }
-
- @Override
- public void end() throws ConsumerException {
- // Make sure all PNGs are generated first.
- super.end();
- try {
- // Wait for all PNGs to be crunched.
- mCruncher.end(mCruncherKey);
- } catch (InterruptedException e) {
- throw new ConsumerException(e);
- }
-
- mValuesResMap = null;
- mQualifierWithDeletedValues = null;
- mFactory = null;
- }
-
- @Override
- public boolean ignoreItemInMerge(ResourceItem item) {
- return item.getIgnoredFromDiskMerge();
- }
-
- @Override
- public void addItem(@NonNull final ResourceItem item) throws ConsumerException {
- final ResourceFile.FileType type = item.getSourceType();
-
- if (type == ResourceFile.FileType.XML_VALUES) {
- // this is a resource for the values files
-
- // just add the node to write to the map based on the qualifier.
- // We'll figure out later if the files needs to be written or (not)
- mValuesResMap.put(item.getQualifiers(), item);
- } else {
- checkState(item.getSource() != null);
- // This is a single value file or a set of generated files. Only write it if the state
- // is TOUCHED.
- if (item.isTouched()) {
- getExecutor().execute(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- File file = item.getFile();
-
- String filename = file.getName();
- String folderName = getFolderName(item);
- File typeFolder = new File(getRootFolder(), folderName);
- try {
- createDir(typeFolder);
- } catch (IOException ioe) {
- throw MergingException.wrapException(ioe).withFile(typeFolder).build();
- }
-
- File outFile = new File(typeFolder, filename);
-
- if (type == DataFile.FileType.GENERATED_FILES) {
- mPreprocessor.generateFile(file, item.getSource().getFile());
- }
-
- try {
- if (item.getType() == ResourceType.RAW) {
- // Don't crunch, don't insert source comments, etc - leave alone.
- Files.copy(file, outFile);
- } else if (filename.endsWith(DOT_PNG)) {
- if (mCrunchPng && mProcess9Patch) {
- mCruncher.crunchPng(mCruncherKey, file, outFile);
- } else {
- // we should not crunch the png files, but we should still
- // process the nine patch.
- if (mProcess9Patch && filename.endsWith(DOT_9PNG)) {
- mCruncher.crunchPng(mCruncherKey, file, outFile);
- } else {
- Files.copy(file, outFile);
- }
- }
- } else if (mInsertSourceMarkers && filename.endsWith(DOT_XML)) {
- SdkUtils.copyXmlWithSourceReference(file, outFile);
- } else {
- Files.copy(file, outFile);
- }
- } catch (PngException e) {
- throw MergingException.wrapException(e).withFile(file).build();
- } catch (IOException ioe) {
- throw MergingException.wrapException(ioe).withFile(file).build();
- }
- return null;
- }
- });
- }
- }
- }
-
- @Override
- public void removeItem(@NonNull ResourceItem removedItem, @Nullable ResourceItem replacedBy)
- throws ConsumerException {
- ResourceFile.FileType removedType = removedItem.getSourceType();
- ResourceFile.FileType replacedType = replacedBy != null
- ? replacedBy.getSourceType()
- : null;
-
- switch (removedType) {
- case SINGLE_FILE: // Fall through.
- case GENERATED_FILES:
- if (replacedType == DataFile.FileType.SINGLE_FILE
- || replacedType == DataFile.FileType.GENERATED_FILES) {
- // Save one IO operation and don't delete a file that will be overwritten
- // anyway.
- break;
- }
- removeOutFile(removedItem);
- break;
- case XML_VALUES:
- mQualifierWithDeletedValues.add(removedItem.getQualifiers());
- break;
- default:
- throw new IllegalStateException();
- }
- }
-
- @Override
- protected void postWriteAction() throws ConsumerException {
-
- // now write the values files.
- for (String key : mValuesResMap.keySet()) {
- // the key is the qualifier.
-
- // check if we have to write the file due to deleted values.
- // also remove it from that list anyway (to detect empty qualifiers later).
- boolean mustWriteFile = mQualifierWithDeletedValues.remove(key);
-
- // get the list of items to write
- List<ResourceItem> items = mValuesResMap.get(key);
-
- // now check if we really have to write it
- if (!mustWriteFile) {
- for (ResourceItem item : items) {
- if (item.isTouched()) {
- mustWriteFile = true;
- break;
- }
- }
- }
-
- if (mustWriteFile) {
- String folderName = key.isEmpty() ?
- ResourceFolderType.VALUES.getName() :
- ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key;
-
- File valuesFolder = new File(getRootFolder(), folderName);
- // Name of the file is the same as the folder as AAPT gets confused with name
- // collision when not normalizing folders name.
- File outFile = new File(valuesFolder, folderName + DOT_XML);
- ResourceFile currentFile = null;
- try {
- createDir(valuesFolder);
-
- DocumentBuilder builder = mFactory.newDocumentBuilder();
- Document document = builder.newDocument();
- final String publicTag = ResourceType.PUBLIC.getName();
- List<Node> publicNodes = null;
-
- Node rootNode = document.createElement(TAG_RESOURCES);
- document.appendChild(rootNode);
-
- Collections.sort(items);
-
- for (ResourceItem item : items) {
- Node nodeValue = item.getValue();
- if (nodeValue != null && publicTag.equals(nodeValue.getNodeName())) {
- if (publicNodes == null) {
- publicNodes = Lists.newArrayList();
- }
- publicNodes.add(nodeValue);
- continue;
- }
-
- // add a carriage return so that the nodes are not all on the same line.
- // also add an indent of 4 spaces.
- rootNode.appendChild(document.createTextNode("\n "));
-
- ResourceFile source = item.getSource();
- if (source != currentFile && source != null && mInsertSourceMarkers) {
- currentFile = source;
- File file = source.getFile();
- rootNode.appendChild(document.createComment(
- createPathComment(file, true)));
- rootNode.appendChild(document.createTextNode("\n "));
- // Add an <eat-comment> element to ensure that this comment won't
- // get merged into a potential comment from the next child (or
- // even added as the sole comment in the R class)
- rootNode.appendChild(document.createElement(TAG_EAT_COMMENT));
- rootNode.appendChild(document.createTextNode("\n "));
- }
-
- Node adoptedNode = NodeUtils.adoptNode(document, nodeValue);
- rootNode.appendChild(adoptedNode);
- }
-
- // finish with a carriage return
- rootNode.appendChild(document.createTextNode("\n"));
-
- currentFile = null;
-
- String content = XmlUtils.toXml(document);
- Files.write(content, outFile, Charsets.UTF_8);
-
- if (publicNodes != null && mPublicFile != null) {
- // Generate public.txt:
- int size = publicNodes.size();
- StringBuilder sb = new StringBuilder(size * 80);
- for (Node node : publicNodes) {
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element element = (Element) node;
- String name = element.getAttribute(ATTR_NAME);
- String type = element.getAttribute(ATTR_TYPE);
- if (!name.isEmpty() && !type.isEmpty()) {
- sb.append(type).append(' ').append(name).append('\n');
- }
- }
- }
- File parentFile = mPublicFile.getParentFile();
- if (!parentFile.exists()) {
- boolean mkdirs = parentFile.mkdirs();
- if (!mkdirs) {
- throw new IOException("Could not create " + parentFile);
- }
- }
- String text = sb.toString();
- Files.write(text, mPublicFile, Charsets.UTF_8);
- }
- } catch (Throwable t) {
- ConsumerException exception = new ConsumerException(t,
- currentFile != null ? currentFile.getFile() : outFile);
- throw exception;
- }
- }
- }
-
- // now remove empty values files.
- for (String key : mQualifierWithDeletedValues) {
- String folderName = key != null && !key.isEmpty() ?
- ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key :
- ResourceFolderType.VALUES.getName();
-
- removeOutFile(folderName, folderName + DOT_XML);
- }
- }
-
- /**
- * Removes a file that already exists in the out res folder. This has to be a non value file.
- *
- * @param resourceItem the source item that created the file to remove.
- * @return true if success.
- */
- private boolean removeOutFile(ResourceItem resourceItem) {
- return removeOutFile(getFolderName(resourceItem), resourceItem.getFile().getName());
- }
-
- /**
- * Removes a file from a folder based on a sub folder name and a filename
- *
- * @param folderName the sub folder name
- * @param fileName the file name.
- * @return true if success.
- */
- private boolean removeOutFile(String folderName, String fileName) {
- File valuesFolder = new File(getRootFolder(), folderName);
- File outFile = new File(valuesFolder, fileName);
- return outFile.delete();
- }
-
- private synchronized void createDir(File folder) throws IOException {
- if (!folder.isDirectory() && !folder.mkdirs()) {
- throw new IOException("Failed to create directory: " + folder);
- }
- }
-
- /**
- * Calculates the right folder name give a resource item.
- *
- * @param resourceItem the resource item to calculate the folder name from.
- * @return a relative folder name
- */
- @NonNull
- private static String getFolderName(ResourceItem resourceItem) {
- ResourceType itemType = resourceItem.getType();
- String folderName = itemType.getName();
- String qualifiers = resourceItem.getQualifiers();
- if (!qualifiers.isEmpty()) {
- folderName = folderName + RES_QUALIFIER_SEP + qualifiers;
- }
- return folderName;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java
deleted file mode 100644
index e0c7b68..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.Message.Kind;
-import com.android.ide.common.blame.SourceFile;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
-import org.xml.sax.SAXParseException;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Exception for errors during merging.
- */
-public class MergingException extends Exception {
-
- public static final String MULTIPLE_ERRORS = "Multiple errors:";
-
- @NonNull
- private final List<Message> mMessages;
-
- /**
- * For internal use. Creates a new MergingException
- *
- * @param cause the original exception. May be null.
- * @param messages the messaged. Must contain at least one item.
- */
- protected MergingException(@Nullable Throwable cause, @NonNull Message... messages) {
- super(messages.length == 1 ? messages[0].getText() : MULTIPLE_ERRORS, cause);
- mMessages = ImmutableList.copyOf(messages);
- }
-
- public static class Builder {
-
- @Nullable
- private Throwable mCause = null;
-
- @Nullable
- private String mMessageText = null;
-
- @Nullable
- private String mOriginalMessageText = null;
-
- @NonNull
- private SourceFile mFile = SourceFile.UNKNOWN;
-
- @NonNull
- private SourcePosition mPosition = SourcePosition.UNKNOWN;
-
- private Builder() {
- }
-
- public Builder wrapException(@NonNull Throwable cause) {
- mCause = cause;
- mOriginalMessageText = Throwables.getStackTraceAsString(cause);
- return this;
- }
-
- public Builder withFile(@NonNull File file) {
- mFile = new SourceFile(file);
- return this;
- }
-
- public Builder withFile(@NonNull SourceFile file) {
- mFile = file;
- return this;
- }
-
- public Builder withPosition(@NonNull SourcePosition position) {
- mPosition = position;
- return this;
- }
-
- public Builder withMessage(@NonNull String messageText, Object... args) {
- mMessageText = args.length == 0 ? messageText : String.format(messageText, args);
- return this;
- }
-
- public MergingException build() {
- if (mCause != null) {
- if (mMessageText == null) {
- mMessageText = Objects.firstNonNull(
- mCause.getLocalizedMessage(), mCause.getClass().getCanonicalName());
- }
- if (mPosition == SourcePosition.UNKNOWN && mCause instanceof SAXParseException) {
- SAXParseException exception = (SAXParseException) mCause;
- int lineNumber = exception.getLineNumber();
- if (lineNumber != -1) {
- // Convert positions to be 0-based for SourceFilePosition.
- mPosition = new SourcePosition(lineNumber - 1,
- exception.getColumnNumber() - 1, -1);
- }
- }
- }
-
- if (mMessageText == null) {
- mMessageText = "Unknown error.";
- }
-
- return new MergingException(
- mCause,
- new Message(
- Kind.ERROR,
- mMessageText,
- Objects.firstNonNull(mOriginalMessageText, mMessageText),
- new SourceFilePosition(mFile, mPosition)));
- }
-
- }
-
- public static Builder wrapException(@NonNull Throwable cause) {
- return new Builder().wrapException(cause);
- }
-
- public static Builder withMessage(@NonNull String message, Object... args) {
- return new Builder().withMessage(message, args);
- }
-
-
- public static void throwIfNonEmpty(Collection<Message> messages) throws MergingException {
- if (!messages.isEmpty()) {
- throw new MergingException(null, Iterables.toArray(messages, Message.class));
- }
- }
-
- @NonNull
- public List<Message> getMessages() {
- return mMessages;
- }
-
- /**
- * Computes the error message to display for this error
- */
- @NonNull
- @Override
- public String getMessage() {
- List<String> messages = Lists.newArrayListWithCapacity(mMessages.size());
- for (Message message : mMessages) {
- StringBuilder sb = new StringBuilder();
- List<SourceFilePosition> sourceFilePositions = message.getSourceFilePositions();
- if (sourceFilePositions.size() > 1 || !sourceFilePositions.get(0)
- .equals(SourceFilePosition.UNKNOWN)) {
- sb.append(Joiner.on('\t').join(sourceFilePositions));
- }
-
- String text = message.getText();
- if (sb.length() > 0) {
- sb.append(':').append(' ');
-
- // ALWAYS insert the string "Error:" between the path and the message.
- // This is done to make the error messages more simple to detect
- // (since a generic path: message pattern can match a lot of output, basically
- // any labeled output, and we don't want to do file existence checks on any random
- // string to the left of a colon.)
- if (!text.startsWith("Error: ")) {
- sb.append("Error: ");
- }
- } else if (!text.contains("Error: ")) {
- sb.append("Error: ");
- }
-
- // If the error message already starts with the path, strip it out.
- // This avoids redundant looking error messages you can end up with
- // like for example for permission denied errors where the error message
- // string itself contains the path as a prefix:
- // /my/full/path: /my/full/path (Permission denied)
- if (sourceFilePositions.size() == 1) {
- File file = sourceFilePositions.get(0).getFile().getSourceFile();
- if (file != null) {
- String path = file.getAbsolutePath();
- if (text.startsWith(path)) {
- int stripStart = path.length();
- if (text.length() > stripStart && text.charAt(stripStart) == ':') {
- stripStart++;
- }
- if (text.length() > stripStart && text.charAt(stripStart) == ' ') {
- stripStart++;
- }
- text = text.substring(stripStart);
- }
- }
- }
-
- sb.append(text);
- messages.add(sb.toString());
- }
- return Joiner.on('\n').join(messages);
- }
-
- @Override
- public String toString() {
- return getMessage();
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java
deleted file mode 100644
index f124398..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.List;
-
-
-/**
- * Utility class to handle Nodes.
- *
- * - convert Node from one XML {@link Document} to be used by another Document
- * - compare Nodes and attributes.
- */
-class NodeUtils {
-
- /**
- * Makes a new document adopt a node from a different document, and correctly reassign namespace
- * and prefix
- * @param document the new document
- * @param node the node to adopt.
- * @return the adopted node.
- */
- static Node adoptNode(Document document, Node node) {
- Node newNode = document.adoptNode(node);
-
- updateNamespace(newNode, document);
-
- return newNode;
- }
-
- static Node duplicateNode(Document document, Node node) {
- Node newNode;
- if (node.getNamespaceURI() != null) {
- newNode = document.createElementNS(node.getNamespaceURI(), node.getLocalName());
- } else {
- newNode = document.createElement(node.getNodeName());
- }
-
- // copy the attributes
- NamedNodeMap attributes = node.getAttributes();
- for (int i = 0 ; i < attributes.getLength(); i++) {
- Attr attr = (Attr) attributes.item(i);
-
- Attr newAttr;
- if (attr.getNamespaceURI() != null) {
- newAttr = document.createAttributeNS(attr.getNamespaceURI(), attr.getLocalName());
- newNode.getAttributes().setNamedItemNS(newAttr);
- } else {
- newAttr = document.createAttribute(attr.getName());
- newNode.getAttributes().setNamedItem(newAttr);
- }
-
- newAttr.setValue(attr.getValue());
- }
-
- // then duplicate the sub-nodes.
- NodeList children = node.getChildNodes();
- for (int i = 0 ; i < children.getLength() ; i++) {
- Node child = children.item(i);
- if (child.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
- Node duplicatedChild = duplicateNode(document, child);
- newNode.appendChild(duplicatedChild);
- }
-
- return newNode;
- }
-
- static void addAttribute(Document document, Node node,
- String namespaceUri, String attrName, String attrValue) {
- Attr attr;
- if (namespaceUri != null) {
- attr = document.createAttributeNS(namespaceUri, attrName);
- } else {
- attr = document.createAttribute(attrName);
- }
-
- attr.setValue(attrValue);
-
- if (namespaceUri != null) {
- node.getAttributes().setNamedItemNS(attr);
- } else {
- node.getAttributes().setNamedItem(attr);
- }
- }
-
- /**
- * Updates the namespace of a given node (and its children) to work in a given document
- * @param node the node to update
- * @param document the new document
- */
- private static void updateNamespace(Node node, Document document) {
-
- // first process this node
- processSingleNodeNamespace(node, document);
-
- // then its attributes
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Node attribute = attributes.item(i);
- if (!processSingleNodeNamespace(attribute, document)) {
- String nsUri = attribute.getNamespaceURI();
- if (nsUri != null) {
- attributes.removeNamedItemNS(nsUri, attribute.getLocalName());
- } else {
- attributes.removeNamedItem(attribute.getLocalName());
- }
- }
- }
- }
-
- // then do it for the children nodes.
- NodeList children = node.getChildNodes();
- if (children != null) {
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- if (child != null) {
- updateNamespace(child, document);
- }
- }
- }
- }
-
- /**
- * Update the namespace of a given node to work with a given document.
- *
- * @param node the node to update
- * @param document the new document
- *
- * @return false if the attribute is to be dropped
- */
- private static boolean processSingleNodeNamespace(Node node, Document document) {
- if ("xmlns".equals(node.getLocalName())) {
- return false;
- }
-
- String ns = node.getNamespaceURI();
- if (ns != null) {
- NamedNodeMap docAttributes = document.getAttributes();
-
- String prefix = getPrefixForNs(docAttributes, ns);
- if (prefix == null) {
- prefix = getUniqueNsAttribute(docAttributes);
- Attr nsAttr = document.createAttribute(prefix);
- nsAttr.setValue(ns);
- document.getChildNodes().item(0).getAttributes().setNamedItem(nsAttr);
- }
-
- // set the prefix on the node, by removing the xmlns: start
- prefix = prefix.substring(6);
- node.setPrefix(prefix);
- }
-
- return true;
- }
-
- /**
- * Looks for an existing prefix for a a given namespace.
- * The prefix must start with "xmlns:". The whole prefix is returned.
- * @param attributes the list of attributes to look through
- * @param ns the namespace to find.
- * @return the found prefix or null if none is found.
- */
- private static String getPrefixForNs(NamedNodeMap attributes, String ns) {
- if (attributes != null) {
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- if (ns.equals(attribute.getValue()) && ns.startsWith(SdkConstants.XMLNS_PREFIX)) {
- return attribute.getName();
- }
- }
- }
-
- return null;
- }
-
- private static String getUniqueNsAttribute(NamedNodeMap attributes) {
- if (attributes == null) {
- return "xmlns:ns1";
- }
-
- int i = 2;
- while (true) {
- String name = String.format("xmlns:ns%d", i++);
- if (attributes.getNamedItem(name) == null) {
- return name;
- }
- }
- }
-
- static boolean compareElementNode(@NonNull Node node1, @NonNull Node node2, boolean strict) {
- if (!node1.getNodeName().equals(node2.getNodeName())) {
- return false;
- }
-
- NamedNodeMap attr1 = node1.getAttributes();
- NamedNodeMap attr2 = node2.getAttributes();
-
- if (!compareAttributes(attr1, attr2)) {
- return false;
- }
-
- if (strict) {
- return compareChildren(node1.getChildNodes(), node2.getChildNodes());
- }
-
- return compareContent(node1.getChildNodes(), node2.getChildNodes());
- }
-
- private static boolean compareChildren(
- @NonNull NodeList children1,
- @NonNull NodeList children2) {
- // because this represents a resource values, we're going to be very strict about this
- // comparison.
- if (children1.getLength() != children2.getLength()) {
- return false;
- }
-
- for (int i = 0, n = children1.getLength(); i < n; i++) {
- Node child1 = children1.item(i);
- Node child2 = children2.item(i);
-
- short nodeType = child1.getNodeType();
- if (nodeType != child2.getNodeType()) {
- return false;
- }
-
- switch (nodeType) {
- case Node.ELEMENT_NODE:
- if (!compareElementNode(child1, child2, true)) {
- return false;
- }
- break;
- case Node.CDATA_SECTION_NODE:
- case Node.TEXT_NODE:
- case Node.COMMENT_NODE:
- if (!child1.getNodeValue().equals(child2.getNodeValue())) {
- return false;
- }
- break;
- }
- }
-
- return true;
- }
-
- private static boolean compareContent(
- @NonNull NodeList children1,
- @NonNull NodeList children2) {
- // only compares the content (ie not the text node).
-
- // accumulate both true children list.
- List<Node> childList = getElementChildren(children1);
- List<Node> childList2 = getElementChildren(children2);
-
- if (childList.size() != childList2.size()) {
- return false;
- }
-
- // no attempt to match nodes one to one.
- for (Node child : childList) {
- boolean found = false;
- for (Node child2 : childList2) {
- if (compareElementNode(child, child2, false)) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return false;
- }
- }
-
- return true;
- }
-
- @NonNull
- private static List<Node> getElementChildren(@NonNull NodeList children) {
- List<Node> results = Lists.newArrayListWithExpectedSize(children.getLength());
-
- final int len = children.getLength();
- for (int i = 0; i < len; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- results.add(child);
- }
- }
-
- return results;
- }
-
- @VisibleForTesting
- static boolean compareAttributes(
- @NonNull NamedNodeMap attrMap1,
- @NonNull NamedNodeMap attrMap2) {
- if (attrMap1.getLength() != attrMap2.getLength()) {
- return false;
- }
-
- for (int i = 0, n = attrMap1.getLength(); i < n; i++) {
- Attr attr1 = (Attr) attrMap1.item(i);
-
- String ns1 = attr1.getNamespaceURI();
-
- Attr attr2;
- if (ns1 != null) {
- attr2 = (Attr) attrMap2.getNamedItemNS(ns1, attr1.getLocalName());
- } else {
- attr2 = (Attr) attrMap2.getNamedItem(attr1.getName());
- }
-
- if (attr2 == null || !attr2.getValue().equals(attr1.getValue())) {
- return false;
- }
- }
-
- return true;
- }
-
- @Nullable
- static String getAttribute(@NonNull Node node, @NonNull String attrName) {
- Attr attr = (Attr) node.getAttributes().getNamedItem(attrName);
- if (attr != null) {
- return attr.getValue();
- }
- return null;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java
deleted file mode 100644
index 3274494..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.google.common.base.Objects;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Represents a file in a resource folders.
- *
- * It contains a link to the {@link File}, the qualifier string (which is the name of the folder
- * after the first '-' character), a list of {@link ResourceItem} and a type.
- *
- * The type of the file is based on whether the file is located in a values folder (FileType.MULTI)
- * or in another folder (FileType.SINGLE).
- */
-public class ResourceFile extends DataFile<ResourceItem> {
-
- static final String ATTR_QUALIFIER = "qualifiers";
-
- private String mQualifiers;
-
- /**
- * Creates a resource file with a single resource item.
- *
- * The source file is set on the item with {@link ResourceItem#setSource(DataFile)}
- *
- * The type of the ResourceFile will be {@link FileType#SINGLE_FILE}.
- *
- * @param file the File
- * @param item the resource item
- * @param qualifiers the qualifiers.
- */
- public ResourceFile(@NonNull File file, @NonNull ResourceItem item,
- @NonNull String qualifiers) {
- super(file, FileType.SINGLE_FILE);
- mQualifiers = qualifiers;
- init(item);
- }
-
- /**
- * Creates a resource file with a list of resource items.
- *
- * The source file is set on the items with {@link ResourceItem#setSource(DataFile)}
- *
- * The type of the ResourceFile will be {@link FileType#XML_VALUES}.
- *
- * @param file the File
- * @param items the resource items
- * @param qualifiers the qualifiers.
- */
- public ResourceFile(@NonNull File file, @NonNull List<ResourceItem> items,
- @NonNull String qualifiers) {
- this(file, items, qualifiers, FileType.XML_VALUES);
- }
-
- private ResourceFile(@NonNull File file, @NonNull List<ResourceItem> items,
- @NonNull String qualifiers, @NonNull FileType fileType) {
- super(file, fileType);
- mQualifiers = qualifiers;
- init(items);
- }
-
- public static ResourceFile generatedFiles(
- @NonNull File file,
- @NonNull List<ResourceItem> items,
- @NonNull String qualifiers) {
- // TODO: Replace other constructors with named methods.
- return new ResourceFile(file, items, qualifiers, FileType.GENERATED_FILES);
- }
-
-
- @NonNull
- public String getQualifiers() {
- return mQualifiers;
- }
-
- // Used in Studio
- public void setQualifiers(@NonNull String qualifiers) {
- mQualifiers = qualifiers;
- }
-
- @Override
- void addExtraAttributes(Document document, Node node, String namespaceUri) {
- NodeUtils.addAttribute(document, node, namespaceUri, ATTR_QUALIFIER,
- getQualifiers());
-
- if (getType() == FileType.GENERATED_FILES) {
- NodeUtils.addAttribute(document, node, namespaceUri, SdkConstants.ATTR_PREPROCESSING, "true");
- }
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(getClass())
- .add("mFile", mFile)
- .add("mQualifiers", mQualifiers)
- .toString();
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceMerger.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceMerger.java
deleted file mode 100644
index 2cfba18..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceMerger.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.TAG_DECLARE_STYLEABLE;
-import static com.android.ide.common.res2.DataFile.FileType;
-import static com.android.ide.common.res2.ResourceFile.ATTR_QUALIFIER;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceType;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Implementation of {@link DataMerger} for {@link ResourceSet}, {@link ResourceItem}, and
- * {@link ResourceFile}.
- */
-public class ResourceMerger extends DataMerger<ResourceItem, ResourceFile, ResourceSet> {
- private static final String NODE_MERGED_ITEMS = "mergedItems";
-
- /**
- * Override of the normal ResourceItem to handle merged item cases.
- * This is mostly to deal with items that do not have a matching source file.
- * This override the method returning the qualifier or the source type, to directly
- * return a value instead of relying on a source file (since merged items don't have any).
- */
- private static class MergedResourceItem extends ResourceItem {
-
- @NonNull
- private final String mQualifiers;
-
- /**
- * Constructs the object with a name, type and optional value.
- *
- * Note that the object is not fully usable as-is. It must be added to a ResourceFile first.
- *
- * @param name the name of the resource
- * @param type the type of the resource
- * @param qualifiers the qualifiers of the resource
- * @param value an optional Node that represents the resource value.
- */
- public MergedResourceItem(
- @NonNull String name,
- @NonNull ResourceType type,
- @NonNull String qualifiers,
- @Nullable Node value) {
- super(name, type, value);
- mQualifiers = qualifiers;
- }
-
- @NonNull
- @Override
- public String getQualifiers() {
- return mQualifiers;
- }
-
- @Override
- @NonNull
- public FileType getSourceType() {
- return FileType.XML_VALUES;
- }
- }
-
- /**
- * Map of items that are purely results of merges (ie item that made up of several
- * original items). The first map key is the associated qualifier for the items,
- * the second map key is the item name.
- */
- protected final Map<String, Map<String, ResourceItem>> mMergedItems = Maps.newHashMap();
-
-
- /**
- * Reads the {@link ResourceSet} from the blob XML. {@link ResourceMerger} deals with two kinds
- * of sets - {@link GeneratedResourceSet} and "plain" {@link ResourceSet} . Instances of the
- * former are marked with {@code generated="true"} attribute. Instances of the latter have a
- * {@code generated-set} attribute that references the corresponding generated set by name.
- * For any variant, the generated set has a lower priority, so it comes in the XML first. This
- * means we will find it by name at this stage.
- */
- @Override
- protected ResourceSet createFromXml(Node node) throws MergingException {
- String generated = NodeUtils.getAttribute(node, "generated");
- ResourceSet set;
- if ("true".equals(generated)) {
- set = new GeneratedResourceSet("");
- } else {
- set = new ResourceSet("");
- }
- ResourceSet newResourceSet = (ResourceSet) set.createFromXml(node);
-
- String generatedSetName = NodeUtils.getAttribute(node, "generated-set");
- if (generatedSetName != null) {
- for (ResourceSet resourceSet : getDataSets()) {
- if (resourceSet.getConfigName().equals(generatedSetName)) {
- newResourceSet.setGeneratedSet(resourceSet);
- break;
- }
- }
- }
-
- return newResourceSet;
- }
-
- @Override
- protected boolean requiresMerge(@NonNull String dataItemKey) {
- return dataItemKey.startsWith("declare-styleable/");
- }
-
- @Override
- protected void mergeItems(
- @NonNull String dataItemKey,
- @NonNull List<ResourceItem> items,
- @NonNull MergeConsumer<ResourceItem> consumer) throws MergingException {
- boolean touched = false; // touched becomes true if one is touched.
- boolean removed = true; // removed stays true if all items are removed.
- for (ResourceItem item : items) {
- touched |= item.isTouched();
- removed &= item.isRemoved();
- }
-
- // get the name of the item (the key is the full key not just the same).
- ResourceItem sourceItem = items.get(0);
- String itemName = sourceItem.getName();
- String qualifier = sourceItem.getQualifiers();
- // get the matching mergedItem
- ResourceItem previouslyWrittenItem = getMergedItem(qualifier, itemName);
-
- try {
- if (touched || (previouslyWrittenItem == null && !removed)) {
- DocumentBuilder builder = mFactory.newDocumentBuilder();
- Document document = builder.newDocument();
-
- Node declareStyleableNode = document.createElementNS(null, TAG_DECLARE_STYLEABLE);
-
- Attr nameAttr = document.createAttribute(ATTR_NAME);
- nameAttr.setValue(itemName);
- declareStyleableNode.getAttributes().setNamedItem(nameAttr);
-
- // loop through all the items and gather a unique list of nodes.
- // because we start with the lower priority items, this means that attr with
- // format inside declare-styleable will be processed first, and added first
- // while the redundant attr (with no format) will be ignored.
- Set<String> attrs = Sets.newHashSet();
-
- for (ResourceItem item : items) {
- if (!item.isRemoved()) {
- Node oldDeclareStyleable = item.getValue();
- if (oldDeclareStyleable != null) {
- NodeList children = oldDeclareStyleable.getChildNodes();
- for (int i = 0; i < children.getLength(); i++) {
- Node attrNode = children.item(i);
- if (attrNode.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
-
- if (SdkConstants.TAG_EAT_COMMENT.equals(attrNode.getLocalName())) {
- continue;
- }
-
- // get the name
- NamedNodeMap attributes = attrNode.getAttributes();
- nameAttr = (Attr) attributes.getNamedItemNS(null, ATTR_NAME);
- if (nameAttr == null) {
- continue;
- }
-
- String name = nameAttr.getNodeValue();
- if (attrs.contains(name)) {
- continue;
- }
-
- // duplicate the node.
- attrs.add(name);
- Node newAttrNode = NodeUtils.duplicateNode(document, attrNode);
- declareStyleableNode.appendChild(newAttrNode);
- }
- }
- }
- }
-
- // always write it for now.
- MergedResourceItem newItem = new MergedResourceItem(
- itemName,
- sourceItem.getType(),
- qualifier,
- declareStyleableNode);
-
- // check whether the result of the merge is new or touched compared
- // to the previous state.
- //noinspection ConstantConditions
- if (previouslyWrittenItem == null ||
- !NodeUtils.compareElementNode(newItem.getValue(), previouslyWrittenItem.getValue(), false)) {
- newItem.setTouched();
- }
-
- // then always add it both to the list of merged items in the merge
- // and to the consumer.
- addMergedItem(qualifier, newItem);
- consumer.addItem(newItem);
-
- } else if (previouslyWrittenItem != null) {
- // since we are keeping the previous merge item, no need
- // to add it internally, just send it to the consumer.
- if (removed) {
- consumer.removeItem(previouslyWrittenItem, null);
- } else {
- // don't need to compute but we need to write the item anyway since
- // the item might be written due to the values file requiring (re)writing due
- // to another res change
- consumer.addItem(previouslyWrittenItem);
- }
- }
- } catch (ParserConfigurationException e) {
- throw MergingException.wrapException(e).build();
- }
- }
-
- @Nullable
- private ResourceItem getMergedItem(@NonNull String qualifiers, @NonNull String name) {
- Map<String, ResourceItem> map = mMergedItems.get(qualifiers);
- if (map != null) {
- return map.get(name);
- }
-
- return null;
- }
-
- @NonNull
- @Override
- protected String getAdditionalDataTagName() {
- return NODE_MERGED_ITEMS;
- }
-
- @Override
- protected void loadAdditionalData(@NonNull Node mergedItemsNode, boolean incrementalState) throws MergingException {
- // only load the merged item in incremental state.
- // In non incremental state, they will be recreated by the touched
- // items anyway.
- if (!incrementalState) {
- return;
- }
-
- // loop on the qualifiers.
- NodeList configurationList = mergedItemsNode.getChildNodes();
-
- for (int j = 0, n2 = configurationList.getLength(); j < n2; j++) {
- Node configuration = configurationList.item(j);
-
- if (configuration.getNodeType() != Node.ELEMENT_NODE ||
- !NODE_CONFIGURATION.equals(configuration.getLocalName())) {
- continue;
- }
-
- // get the qualifier value.
- Attr qualifierAttr = (Attr) configuration.getAttributes().getNamedItem(
- ATTR_QUALIFIER);
- if (qualifierAttr == null) {
- continue;
- }
-
- String qualifier = qualifierAttr.getValue();
-
- // get the resource items
- NodeList itemList = configuration.getChildNodes();
-
- for (int k = 0, n3 = itemList.getLength(); k < n3; k++) {
- Node itemNode = itemList.item(k);
-
- if (itemNode.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
-
- ResourceItem item = getMergedResourceItem(itemNode, qualifier);
- if (item != null) {
- addMergedItem(qualifier, item);
- }
- }
- }
- }
-
- @Override
- protected void writeAdditionalData(Document document, Node rootNode) {
- Node mergedItemsNode = document.createElement(getAdditionalDataTagName());
- rootNode.appendChild(mergedItemsNode);
-
- for (String qualifier : mMergedItems.keySet()) {
- Map<String, ResourceItem> itemMap = mMergedItems.get(qualifier);
-
- Node qualifierNode = document.createElement(NODE_CONFIGURATION);
- NodeUtils.addAttribute(document, qualifierNode, null, ATTR_QUALIFIER,
- qualifier);
-
- mergedItemsNode.appendChild(qualifierNode);
-
- for (ResourceItem item : itemMap.values()) {
- Node adoptedNode = item.getDetailsXml(document);
- if (adoptedNode != null) {
- qualifierNode.appendChild(adoptedNode);
- }
- }
- }
- }
-
- private void addMergedItem(@NonNull String qualifier, @NonNull ResourceItem item) {
- Map<String, ResourceItem> map = mMergedItems.get(qualifier);
- if (map == null) {
- map = Maps.newHashMap();
- mMergedItems.put(qualifier, map);
- }
-
- map.put(item.getName(), item);
- }
-
- /**
- * Returns a new ResourceItem object for a given node.
- * @param node the node representing the resource.
- * @return a ResourceItem object or null.
- */
- static MergedResourceItem getMergedResourceItem(@NonNull Node node, @NonNull String qualifiers)
- throws MergingException {
- ResourceType type = ValueResourceParser2.getType(node, null);
- String name = ValueResourceParser2.getName(node);
-
- if (name != null && type != null) {
- return new MergedResourceItem(name, type, qualifiers, node);
- }
-
- return null;
- }
-
- @Override
- public void addDataSet(ResourceSet resourceSet) {
-
- super.addDataSet(resourceSet);
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java
deleted file mode 100644
index 09795bf..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.ide.common.res2.ResourceFile.ATTR_QUALIFIER;
-import static com.google.common.base.Objects.firstNonNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.resources.FolderTypeRelationship;
-import com.android.resources.ResourceConstants;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.utils.ILogger;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implementation of {@link DataSet} for {@link ResourceItem} and {@link ResourceFile}.
- *
- * This is able to detect duplicates from the same source folders (same resource coming from
- * the values folder in same or different files).
- */
-public class ResourceSet extends DataSet<ResourceItem, ResourceFile> {
-
- private boolean mNormalizeResources = false;
- private ResourceSet mGeneratedSet;
- private ResourcePreprocessor mPreprocessor;
-
- public ResourceSet(String name) {
- this(name, true /*validateEnabled*/);
- }
-
- public ResourceSet(String name, boolean validateEnabled) {
- super(name, validateEnabled);
- }
-
- public void setNormalizeResources(boolean normalizeResources) {
- mNormalizeResources = normalizeResources;
- }
-
- public void setGeneratedSet(ResourceSet generatedSet) {
- mGeneratedSet = generatedSet;
- }
-
- public void setPreprocessor(ResourcePreprocessor preprocessor) {
- mPreprocessor = preprocessor;
- }
-
- @Override
- protected DataSet<ResourceItem, ResourceFile> createSet(String name) {
- return new ResourceSet(name);
- }
-
- @Override
- protected ResourceFile createFileAndItems(File sourceFolder, File file, ILogger logger)
- throws MergingException {
- // get the type.
- FolderData folderData = getFolderData(file.getParentFile());
-
- if (folderData == null) {
- return null;
- }
-
- return createResourceFile(file, folderData, logger);
- }
-
- @Override
- protected ResourceFile createFileAndItemsFromXml(@NonNull File file, @NonNull Node fileNode)
- throws MergingException {
- String qualifier = firstNonNull(NodeUtils.getAttribute(fileNode, ATTR_QUALIFIER), "");
- String typeAttr = NodeUtils.getAttribute(fileNode, SdkConstants.ATTR_TYPE);
-
- if (NodeUtils.getAttribute(fileNode, SdkConstants.ATTR_PREPROCESSING) != null) {
- // FileType.GENERATED_FILES
- NodeList childNodes = fileNode.getChildNodes();
- int childCount = childNodes.getLength();
-
- List<ResourceItem> resourceItems =
- Lists.newArrayListWithCapacity(childCount);
-
- for (int i = 0; i < childCount; i++) {
- Node childNode = childNodes.item(i);
-
- String path = NodeUtils.getAttribute(childNode, SdkConstants.ATTR_PATH);
- if (path == null) {
- continue;
- }
-
- File generatedFile = new File(path);
- String resourceType = NodeUtils.getAttribute(childNode, SdkConstants.ATTR_TYPE);
- if (resourceType == null) {
- continue;
- }
- String qualifers = NodeUtils.getAttribute(childNode, ATTR_QUALIFIER);
- if (qualifers == null) {
- continue;
- }
-
- resourceItems.add(
- new GeneratedResourceItem(
- getNameForFile(generatedFile),
- generatedFile,
- FolderTypeRelationship
- .getRelatedResourceTypes(
- ResourceFolderType.getTypeByName(resourceType))
- .get(0),
- qualifers));
- }
-
- return ResourceFile.generatedFiles(file, resourceItems, qualifier);
- }
- else if (typeAttr == null) {
- // FileType.XML_VALUES
- List<ResourceItem> resourceList = Lists.newArrayList();
-
- // loop on each node that represent a resource
- NodeList resNodes = fileNode.getChildNodes();
- for (int iii = 0, nnn = resNodes.getLength(); iii < nnn; iii++) {
- Node resNode = resNodes.item(iii);
-
- if (resNode.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
-
- ResourceItem r = ValueResourceParser2.getResource(resNode, file);
- if (r != null) {
- resourceList.add(r);
- if (r.getType() == ResourceType.DECLARE_STYLEABLE) {
- // Need to also create ATTR items for its children
- try {
- ValueResourceParser2.addStyleableItems(resNode, resourceList, null, file);
- } catch (MergingException ignored) {
- // since we are not passing a dup map, this will never be thrown
- assert false : file + ": " + ignored.getMessage();
- }
- }
- }
- }
-
- return new ResourceFile(file, resourceList, qualifier);
-
- } else {
- // single res file
- ResourceType type = ResourceType.getEnum(typeAttr);
- if (type == null) {
- return null;
- }
-
- String nameAttr = NodeUtils.getAttribute(fileNode, ATTR_NAME);
- if (nameAttr == null) {
- return null;
- }
-
- if (getValidateEnabled()) {
- FileResourceNameValidator.validate(file, type);
- }
- ResourceItem item = new ResourceItem(nameAttr, type, null);
- return new ResourceFile(file, item, qualifier);
- }
- }
-
- @Override
- protected void readSourceFolder(File sourceFolder, ILogger logger)
- throws MergingException {
- List<Message> errors = Lists.newArrayList();
- File[] folders = sourceFolder.listFiles();
- if (folders != null) {
- for (File folder : folders) {
- if (folder.isDirectory() && !isIgnored(folder)) {
- FolderData folderData = getFolderData(folder);
- if (folderData != null) {
- try {
- parseFolder(sourceFolder, folder, folderData, logger);
- } catch (MergingException e) {
- errors.addAll(e.getMessages());
- }
- }
- }
- }
- }
- MergingException.throwIfNonEmpty(errors);
- }
-
- @Override
- protected boolean isValidSourceFile(@NonNull File sourceFolder, @NonNull File file) {
- if (!super.isValidSourceFile(sourceFolder, file)) {
- return false;
- }
-
- File resFolder = file.getParentFile();
- // valid files are right under a resource folder under the source folder
- return resFolder.getParentFile().equals(sourceFolder) &&
- !isIgnored(resFolder) &&
- ResourceFolderType.getFolderType(resFolder.getName()) != null;
- }
-
- @Override
- protected boolean handleNewFile(File sourceFolder, File file, ILogger logger)
- throws MergingException {
- ResourceFile resourceFile = createFileAndItems(sourceFolder, file, logger);
- processNewResourceFile(sourceFolder, resourceFile);
- return true;
- }
-
- @Override
- protected boolean handleRemovedFile(File removedFile) {
- if (mGeneratedSet != null && mGeneratedSet.getDataFile(removedFile) != null) {
- return mGeneratedSet.handleRemovedFile(removedFile);
- } else {
- return super.handleRemovedFile(removedFile);
- }
- }
-
- @Override
- protected boolean handleChangedFile(
- @NonNull File sourceFolder,
- @NonNull File changedFile,
- @NonNull ILogger logger) throws MergingException {
- FolderData folderData = getFolderData(changedFile.getParentFile());
- if (folderData == null) {
- return true;
- }
-
- ResourceFile resourceFile = getDataFile(changedFile);
- if (mGeneratedSet == null) {
- // This is a generated set.
- doHandleChangedFile(changedFile, resourceFile);
- return true;
- }
-
- ResourceFile generatedSetResourceFile = mGeneratedSet.getDataFile(changedFile);
- boolean needsPreprocessing = needsPreprocessing(changedFile);
-
- if (resourceFile != null && generatedSetResourceFile == null && needsPreprocessing) {
- // It didn't use to need preprocessing, but it does now.
- handleRemovedFile(changedFile);
- mGeneratedSet.handleNewFile(sourceFolder, changedFile, logger);
- } else if (resourceFile == null
- && generatedSetResourceFile != null
- && !needsPreprocessing) {
- // It used to need preprocessing, but not anymore.
- mGeneratedSet.handleRemovedFile(changedFile);
- handleNewFile(sourceFolder, changedFile, logger);
- } else if (resourceFile == null
- && generatedSetResourceFile != null
- && needsPreprocessing) {
- // Delegate to the generated set.
- mGeneratedSet.handleChangedFile(sourceFolder, changedFile, logger);
- } else if (resourceFile != null
- && !needsPreprocessing
- && generatedSetResourceFile == null) {
- // The "normal" case, handle it here.
- doHandleChangedFile(changedFile, resourceFile);
- } else {
- // Something strange happened.
- throw MergingException.withMessage("In DataSet '%s', no data file for changedFile. "
- + "This is an internal error in the incremental builds code; "
- + "to work around it, try doing a full clean build.",
- getConfigName()).withFile(changedFile).build();
- }
-
- return true;
- }
-
- private void doHandleChangedFile(@NonNull File changedFile, ResourceFile resourceFile)
- throws MergingException {
- switch (resourceFile.getType()) {
- case SINGLE_FILE:
- // single res file
- resourceFile.getItem().setTouched();
- break;
- case GENERATED_FILES:
- handleChangedItems(resourceFile,
- getResourceItemsForGeneratedFiles(changedFile));
- break;
- case XML_VALUES:
- // multi res. Need to parse the file and compare the items one by one.
- ValueResourceParser2 parser = new ValueResourceParser2(changedFile);
-
- List<ResourceItem> parsedItems = parser.parseFile();
- handleChangedItems(resourceFile, parsedItems);
- break;
- default:
- throw new IllegalStateException();
- }
- }
-
- private void handleChangedItems(
- ResourceFile resourceFile,
- List<ResourceItem> currentItems) throws MergingException {
- Map<String, ResourceItem> oldItems = Maps.newHashMap(resourceFile.getItemMap());
- Map<String, ResourceItem> addedItems = Maps.newHashMap();
-
- // Set the source of newly determined items, so we can call getKey() on them.
- for (ResourceItem currentItem : currentItems) {
- currentItem.setSource(resourceFile);
- }
-
- for (ResourceItem newItem : currentItems) {
- String newKey = newItem.getKey();
- ResourceItem oldItem = oldItems.get(newKey);
-
- if (oldItem == null) {
- // this is a new item
- newItem.setTouched();
- addedItems.put(newKey, newItem);
- } else {
- // remove it from the list of oldItems (this is to detect deletion)
- //noinspection SuspiciousMethodCalls
- oldItems.remove(oldItem.getKey());
-
- if (oldItem.getSource().getType() == DataFile.FileType.XML_VALUES) {
- if (!oldItem.compareValueWith(newItem)) {
- // if the values are different, take the values from the newItem
- // and update the old item status.
-
- oldItem.setValue(newItem);
- }
- } else {
- oldItem.setTouched();
- }
- }
- }
-
- // at this point oldItems is left with the deleted items.
- // just update their status to removed.
- for (ResourceItem deletedItem : oldItems.values()) {
- deletedItem.setRemoved();
- }
-
- // Now we need to add the new items to the resource file and the main map
- resourceFile.addItems(addedItems.values());
- for (Map.Entry<String, ResourceItem> entry : addedItems.entrySet()) {
- addItem(entry.getValue(), entry.getKey());
- }
- }
-
- /**
- * Reads the content of a typed resource folder (sub folder to the root of res folder), and
- * loads the resources from it.
- *
- *
- * @param sourceFolder the main res folder
- * @param folder the folder to read.
- * @param folderData the folder Data
- * @param logger a logger object
- *
- * @throws MergingException if something goes wrong
- */
- private void parseFolder(File sourceFolder, File folder, FolderData folderData, ILogger logger)
- throws MergingException {
- File[] files = folder.listFiles();
- if (files != null && files.length > 0) {
- for (File file : files) {
- if (!file.isFile() || isIgnored(file)) {
- continue;
- }
-
- ResourceFile resourceFile = createResourceFile(file, folderData, logger);
- processNewResourceFile(sourceFolder, resourceFile);
- }
- }
- }
-
- private void processNewResourceFile(File sourceFolder, ResourceFile resourceFile)
- throws MergingException {
- if (resourceFile != null) {
- if (resourceFile.getType() == DataFile.FileType.GENERATED_FILES
- && mGeneratedSet != null) {
- mGeneratedSet.processNewDataFile(sourceFolder, resourceFile, true);
- } else {
- processNewDataFile(sourceFolder, resourceFile, true /*setTouched*/);
- }
- }
- }
-
- private ResourceFile createResourceFile(@NonNull File file,
- @NonNull FolderData folderData, @NonNull ILogger logger) throws MergingException {
- if (folderData.type != null) {
- FileResourceNameValidator.validate(file, folderData.type);
- String name = getNameForFile(file);
-
- if (needsPreprocessing(file)) {
- return ResourceFile.generatedFiles(
- file,
- getResourceItemsForGeneratedFiles(file),
- folderData.qualifiers);
- } else {
- return new ResourceFile(
- file,
- new ResourceItem(name, folderData.type, null),
- folderData.qualifiers);
- }
- } else {
- try {
- ValueResourceParser2 parser = new ValueResourceParser2(file);
- List<ResourceItem> items = parser.parseFile();
-
- return new ResourceFile(file, items, folderData.qualifiers);
- } catch (MergingException e) {
- logger.error(e, "Failed to parse %s", file.getAbsolutePath());
- throw e;
- }
- }
- }
-
- private boolean needsPreprocessing(@NonNull File file) {
- return mPreprocessor != null && mPreprocessor.needsPreprocessing(file);
- }
-
- @NonNull
- private List<ResourceItem> getResourceItemsForGeneratedFiles(
- @NonNull File file)
- throws MergingException {
- List<ResourceItem> resourceItems = new ArrayList<ResourceItem>();
-
- for (File generatedFile : mPreprocessor.getFilesToBeGenerated(file)) {
- FolderData generatedFileFolderData =
- getFolderData(generatedFile.getParentFile());
-
- checkState(
- generatedFileFolderData != null,
- "Can't determine folder type for %s",
- generatedFile.getPath());
-
- resourceItems.add(
- new GeneratedResourceItem(
- getNameForFile(generatedFile),
- generatedFile,
- generatedFileFolderData.type,
- generatedFileFolderData.qualifiers));
- }
- return resourceItems;
- }
-
- @NonNull
- private static String getNameForFile(@NonNull File file) {
- String name = file.getName();
- int pos = name.indexOf('.'); // get the resource name based on the filename
- if (pos >= 0) {
- name = name.substring(0, pos);
- }
- return name;
- }
-
- /**
- * temp structure containing a qualifier string and a {@link com.android.resources.ResourceType}.
- */
- private static class FolderData {
- String qualifiers = "";
- ResourceType type = null;
- ResourceFolderType folderType = null;
- }
-
- /**
- * Returns a FolderData for the given folder.
- *
- * @param folder the folder.
- * @return the FolderData object, or null if we can't determine the {#link ResourceFolderType}
- * of the folder.
- */
- @Nullable
- private FolderData getFolderData(File folder) throws MergingException {
- FolderData fd = new FolderData();
-
- String folderName = folder.getName();
- int pos = folderName.indexOf(ResourceConstants.RES_QUALIFIER_SEP);
- if (pos != -1) {
- fd.folderType = ResourceFolderType.getTypeByName(folderName.substring(0, pos));
- if (fd.folderType == null) {
- return null;
- }
-
- FolderConfiguration folderConfiguration = FolderConfiguration.getConfigForFolder(folderName);
- if (folderConfiguration == null) {
- throw MergingException.withMessage("Invalid resource directory name")
- .withFile(folder).build();
- }
-
- if (mNormalizeResources) {
- // normalize it
- folderConfiguration.normalize();
- }
-
- // get the qualifier portion from the folder config.
- // the returned string starts with "-" so we remove that.
- fd.qualifiers = folderConfiguration.getUniqueKey().substring(1);
-
- } else {
- fd.folderType = ResourceFolderType.getTypeByName(folderName);
- }
-
- if (fd.folderType != null && fd.folderType != ResourceFolderType.VALUES) {
- fd.type = FolderTypeRelationship.getRelatedResourceTypes(fd.folderType).get(0);
- }
-
- return fd;
- }
-
- @Override
- void appendToXml(@NonNull Node setNode, @NonNull Document document,
- @NonNull MergeConsumer<ResourceItem> consumer) {
- if (mGeneratedSet != null) {
- NodeUtils.addAttribute(
- document,
- setNode,
- null,
- "generated-set",
- mGeneratedSet.getConfigName());
- }
- super.appendToXml(setNode, document, consumer);
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceNameValidator.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceNameValidator.java
deleted file mode 100644
index 0639052..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceNameValidator.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceType;
-
-import java.io.File;
-
-import javax.lang.model.SourceVersion;
-
-public final class ValueResourceNameValidator {
-
- private ValueResourceNameValidator() {
- }
-
- /**
- * Validate a value resource name.
- *
- * @param resourceName the resource name to validate.
- * @param resourceType the resource type.
- * @param file the file the resource came from.
- * @throws MergingException is the resource name is not valid.
- */
- public static void validate(@NonNull String resourceName, @NonNull ResourceType resourceType,
- @Nullable File file)
- throws MergingException {
- String error = getErrorText(resourceName, resourceType);
- if (error != null) {
- // TODO find location in file.
- throw MergingException.withMessage( error).withFile(file).build();
- }
- }
-
- /**
- * Validate a value resource name.
- *
- * @param fullResourceName the resource name to validate.
- * @param resourceType the resource type.
- * @return null if no error, otherwise a string describing the error.
- */
- @Nullable
- public static String getErrorText(@NonNull String fullResourceName, ResourceType resourceType) {
-
- if (resourceType == ResourceType.ATTR) {
- if (fullResourceName.startsWith("android:")) {
- fullResourceName = fullResourceName.substring(8);
- }
- }
- final String resourceName = fullResourceName.replace('.', '_');
-
- // Resource names must be valid Java identifiers, since they will
- // be represented as Java identifiers in the R file:
- if (!SourceVersion.isIdentifier(resourceName)) {
- if (!Character.isJavaIdentifierStart(resourceName.charAt(0))) {
- return "The resource name must start with a letter";
- } else {
- for (int i = 1, n = resourceName.length(); i < n; i++) {
- char c = resourceName.charAt(i);
- if (!Character.isJavaIdentifierPart(c)) {
- return String
- .format("'%1$c' is not a valid resource name character", c);
- }
- }
- }
- }
-
- if (SourceVersion.isKeyword(resourceName)) {
- return String.format("%1$s is not a valid resource name (reserved Java keyword)",
- resourceName);
- }
-
- // Success.
- return null;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java b/base/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java
deleted file mode 100644
index 1e3b303..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.SdkConstants.AMP_ENTITY;
-import static com.android.SdkConstants.APOS_ENTITY;
-import static com.android.SdkConstants.GT_ENTITY;
-import static com.android.SdkConstants.LT_ENTITY;
-import static com.android.SdkConstants.QUOT_ENTITY;
-
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-
-/**
- * Helper class to help with XML values resource file.
- */
-public class ValueXmlHelper {
-
- /**
- * Replaces escapes in an XML resource string with the actual characters,
- * performing unicode substitutions (replacing any {@code \\uNNNN} references in the
- * given string with the corresponding unicode characters), etc.
- *
- * @param s the string to unescape
- * @param escapeEntities XML entities
- * @param trim whether surrounding space and quotes should be trimmed
- * @return the string with the escape characters removed and expanded
- */
- @SuppressWarnings("UnnecessaryContinue")
- @Nullable
- public static String unescapeResourceString(
- @Nullable String s,
- boolean escapeEntities, boolean trim) {
- if (s == null) {
- return null;
- }
-
- // Trim space surrounding optional quotes
- int i = 0;
- int n = s.length();
- boolean quoted = false;
- if (trim) {
- while (i < n) {
- char c = s.charAt(i);
- if (!Character.isWhitespace(c)) {
- break;
- }
- i++;
- }
- while (n > i) {
- char c = s.charAt(n - 1);
- if (!Character.isWhitespace(c)) {
- //See if this was a \, and if so, see whether it was escaped
- if (n < s.length() && isEscaped(s, n)) {
- n++;
- }
- break;
- }
- n--;
- }
-
- // Trim surrounding quotes. Note that there can be *any* number of these, and
- // the left side and right side do not have to match; e.g. you can have
- // """"f"" => f
- int quoteEnd = n;
- while (i < n) {
- char c = s.charAt(i);
- if (c != '"') {
- break;
- }
- quoted = true;
- i++;
- }
- // Searching backwards is slightly more complicated; make sure we don't trim
- // quotes that have been escaped.
- if (quoted) {
- while (n > i) {
- char c = s.charAt(n - 1);
- if (c != '"') {
- if (n < s.length() && isEscaped(s, n)) {
- n++;
- }
- break;
- }
- n--;
- }
- }
- if (n == i) {
- return ""; //$NON-NLS-1$
- }
-
- // Only trim leading spaces if we didn't already process a leading quote:
- if (!quoted) {
- while (i < n) {
- char c = s.charAt(i);
- if (!Character.isWhitespace(c)) {
- break;
- }
- i++;
- }
-
- // Only trim trailing spaces if we didn't already process a trailing quote:
- if (n == quoteEnd) {
- while (n > i) {
- char c = s.charAt(n - 1);
- if (!Character.isWhitespace(c)) {
- //See if this was a \, and if so, see whether it was escaped
- if (n < s.length() && isEscaped(s, n)) {
- n++;
- }
- break;
- }
- n--;
- }
- }
- if (n == i) {
- return ""; //$NON-NLS-1$
- }
- }
- }
-
- // Perform a single pass over the string and see if it contains
- // (1) spaces that should be converted (e.g. repeated spaces or a newline which
- // should be converted to a space)
- // (2) escape characters (\ and &) which will require expansions
- // If we find neither of these, we can simply return the string
- boolean rewriteWhitespace = false;
- if (!quoted) {
- // See if we need to fold adjacent spaces
- boolean prevSpace = false;
- boolean hasEscape = false;
- for (int curr = i; curr < n; curr++) {
- char c = s.charAt(curr);
- if (c == '\\' || c == '&') {
- hasEscape = true;
- }
- boolean isSpace = Character.isWhitespace(c);
- if (isSpace && prevSpace) {
- // fold adjacent spaces
- rewriteWhitespace = true;
- } else if (c == '\n') {
- // rewrite newlines as spaces
- rewriteWhitespace = true;
- }
- prevSpace = isSpace;
- }
-
- if (!trim) {
- rewriteWhitespace = false;
- }
-
- // If no surrounding whitespace and no escape characters, no need to do any
- // more work
- if (!rewriteWhitespace && !hasEscape && i == 0 && n == s.length()) {
- return s;
- }
- }
-
- StringBuilder sb = new StringBuilder(n - i);
- boolean prevSpace = false;
- for (; i < n; i++) {
- char c = s.charAt(i);
- if (c == '\\' && i < n - 1) {
- prevSpace = false;
- char next = s.charAt(i + 1);
- // Unicode escapes
- if (next == 'u' && i < n - 5) { // case sensitive
- String hex = s.substring(i + 2, i + 6);
- try {
- int unicodeValue = Integer.parseInt(hex, 16);
- sb.append((char) unicodeValue);
- i += 5;
- continue;
- } catch (NumberFormatException e) {
- // Invalid escape: Just proceed to literally transcribe it
- sb.append(c);
- }
- } else if (next == 'n') {
- sb.append('\n');
- i++;
- continue;
- } else if (next == 't') {
- sb.append('\t');
- i++;
- continue;
- } else {
- sb.append(next);
- i++;
- continue;
- }
- } else {
- if (c == '&' && escapeEntities) {
- prevSpace = false;
- if (s.regionMatches(true, i, LT_ENTITY, 0, LT_ENTITY.length())) {
- sb.append('<');
- i += LT_ENTITY.length() - 1;
- continue;
- } else if (s.regionMatches(true, i, AMP_ENTITY, 0, AMP_ENTITY.length())) {
- sb.append('&');
- i += AMP_ENTITY.length() - 1;
- continue;
- } else if (s.regionMatches(true, i, QUOT_ENTITY, 0, QUOT_ENTITY.length())) {
- sb.append('"');
- i += QUOT_ENTITY.length() - 1;
- continue;
- } else if (s.regionMatches(true, i, APOS_ENTITY, 0, APOS_ENTITY.length())) {
- sb.append('\'');
- i += APOS_ENTITY.length() - 1;
- continue;
- } else if (s.regionMatches(true, i, GT_ENTITY, 0, GT_ENTITY.length())) {
- sb.append('>');
- i += GT_ENTITY.length() - 1;
- continue;
- } else if (i < n - 2 && s.charAt(i + 1) == '#') {
- int end = s.indexOf(';', i + 1);
- if (end != -1) {
- char first = s.charAt(i + 2);
- boolean hex = first == 'x' || first == 'X';
- String number = s.substring(i + (hex ? 3 : 2), end);
- try {
- int unicodeValue = Integer.parseInt(number, hex ? 16 : 10);
- sb.append((char) unicodeValue);
- i = end;
- continue;
- } catch (NumberFormatException e) {
- // Invalid escape: Just proceed to literally transcribe it
- sb.append(c);
- }
- } else {
- // Invalid escape: Just proceed to literally transcribe it
- sb.append(c);
- }
- }
- }
-
- if (rewriteWhitespace) {
- boolean isSpace = Character.isWhitespace(c);
- if (isSpace) {
- if (!prevSpace) {
- sb.append(' '); // replace newlines etc with a plain space
- }
- } else {
- sb.append(c);
- }
- prevSpace = isSpace;
- } else {
- sb.append(c);
- }
- }
- }
- s = sb.toString();
-
- return s;
- }
-
- /**
- * Returns true if the character at the given offset in the string is escaped
- * (the previous character is a \, and that character isn't itself an escaped \)
- *
- * @param s the string
- * @param index the index of the character in the string to check
- * @return true if the character is escaped
- */
- @VisibleForTesting
- static boolean isEscaped(String s, int index) {
- if (index == 0 || index == s.length()) {
- return false;
- }
- int prevPos = index - 1;
- char prev = s.charAt(prevPos);
- if (prev != '\\') {
- return false;
- }
- // The character *may* be escaped; not sure if the \ we ran into is
- // an escape character, or an escaped backslash; we have to search backwards
- // to be certain.
- int j = prevPos - 1;
- while (j >= 0) {
- if (s.charAt(j) != '\\') {
- break;
- }
- j--;
- }
- // If we passed an odd number of \'s, the space is escaped
- return (prevPos - j) % 2 == 1;
- }
-
- /**
- * Escape a string value to be placed in a string resource file such that it complies with
- * the escaping rules described here:
- * http://developer.android.com/guide/topics/resources/string-resource.html
- * More examples of the escaping rules can be found here:
- * http://androidcookbook.com/Recipe.seam?recipeId=2219&recipeFrom=ViewTOC
- * This method assumes that the String is not escaped already.
- *
- * Rules:
- * <ul>
- * <li>Double quotes are needed if string starts or ends with at least one space.
- * <li>{@code @, ?} at beginning of string have to be escaped with a backslash.
- * <li>{@code ', ", \} have to be escaped with a backslash.
- * <li>{@code <, >, &} have to be replaced by their predefined xml entity.
- * <li>{@code \n, \t} have to be replaced by a backslash and the appropriate character.
- * </ul>
- * @param s the string to be escaped
- * @return the escaped string as it would appear in the XML text in a values file
- */
- public static String escapeResourceString(String s) {
- return escapeResourceString(s, true);
- }
-
- /**
- * Escape a string value to be placed in a string resource file such that it complies with
- * the escaping rules described here:
- * http://developer.android.com/guide/topics/resources/string-resource.html
- * More examples of the escaping rules can be found here:
- * http://androidcookbook.com/Recipe.seam?recipeId=2219&recipeFrom=ViewTOC
- * This method assumes that the String is not escaped already.
- *
- * Rules:
- * <ul>
- * <li>Double quotes are needed if string starts or ends with at least one space.
- * <li>{@code @, ?} at beginning of string have to be escaped with a backslash.
- * <li>{@code ', ", \} have to be escaped with a backslash.
- * <li>{@code <, >, &} have to be replaced by their predefined xml entity.
- * <li>{@code \n, \t} have to be replaced by a backslash and the appropriate character.
- * </ul>
- * @param s the string to be escaped
- * @param escapeXml whether XML characters like {@code <} and {@code &} should be
- * escaped; this should normally be true, but is optional since
- * some callers need to pass the string over to code which already escapes
- * XML content, and you wouldn't want the ampersand in the escaped character
- * entity to itself be escaped.
- * @return the escaped string as it would appear in the XML text in a values file
- */
- public static String escapeResourceString(String s, boolean escapeXml) {
- int n = s.length();
- if (n == 0) {
- return "";
- }
-
- StringBuilder sb = new StringBuilder(s.length() * 2);
- boolean hasSpace = s.charAt(0) == ' ' || s.charAt(n - 1) == ' ';
-
- if (hasSpace) {
- sb.append('"');
- } else if (s.charAt(0) == '@' || s.charAt(0) == '?') {
- sb.append('\\');
- }
-
- for (int i = 0; i < n; ++i) {
- char c = s.charAt(i);
- switch (c) {
- case '\'':
- if (!hasSpace) {
- sb.append('\\');
- }
- sb.append(c);
- break;
- case '"':
- case '\\':
- sb.append('\\');
- sb.append(c);
- break;
- case '<':
- if (escapeXml) {
- sb.append(LT_ENTITY);
- } else {
- sb.append(c);
- }
- break;
- case '&':
- if (escapeXml) {
- sb.append(AMP_ENTITY);
- } else {
- sb.append(c);
- }
- break;
- case '\n':
- sb.append("\\n"); //$NON-NLS-1$
- break;
- case '\t':
- sb.append("\\t"); //$NON-NLS-1$
- break;
- default:
- sb.append(c);
- break;
- }
- }
-
- if (hasSpace) {
- sb.append('"');
- }
-
- return sb.toString();
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java b/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java
deleted file mode 100644
index f218abe..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.resources;
-
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.PREFIX_THEME_REF;
-import static com.android.ide.common.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.ide.common.res2.AbstractResourceRepository;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.resources.ResourceType;
-
-import java.util.List;
-
-/**
- * Like {@link ResourceResolver} but for a single item, so it does not need the full resource maps
- * to be resolved up front. Typically used for cases where we may not have fully configured
- * resource maps and we need to look up a specific value, such as in Android Studio where
- * a color reference is found in an XML style file, and we want to resolve it in order to
- * display the final resolved color in the editor margin.
- */
-public class ResourceItemResolver extends RenderResources {
- private final FolderConfiguration mConfiguration;
- private final LayoutLog mLogger;
- private final ResourceProvider mResourceProvider;
- private ResourceRepository mFrameworkResources;
- private ResourceResolver mResolver;
- private AbstractResourceRepository myAppResources;
- @Nullable private List<ResourceValue> mLookupChain;
-
- public ResourceItemResolver(
- @NonNull FolderConfiguration configuration,
- @NonNull ResourceProvider resourceProvider,
- @Nullable LayoutLog logger) {
- mConfiguration = configuration;
- mResourceProvider = resourceProvider;
- mLogger = logger;
- mResolver = resourceProvider.getResolver(false);
- }
-
- public ResourceItemResolver(
- @NonNull FolderConfiguration configuration,
- @NonNull ResourceRepository frameworkResources,
- @NonNull AbstractResourceRepository appResources,
- @Nullable LayoutLog logger) {
- mConfiguration = configuration;
- mResourceProvider = null;
- mLogger = logger;
- mFrameworkResources = frameworkResources;
- myAppResources = appResources;
- }
-
- @Override
- @Nullable
- public ResourceValue resolveResValue(@Nullable ResourceValue resValue) {
- if (mResolver != null) {
- return mResolver.resolveResValue(resValue);
- }
- if (mLookupChain != null) {
- mLookupChain.add(resValue);
- }
- return resolveResValue(resValue, 0);
- }
-
- @Nullable
- private ResourceValue resolveResValue(@Nullable ResourceValue resValue, int depth) {
- if (resValue == null) {
- return null;
- }
-
- // if the resource value is null, we simply return it.
- String value = resValue.getValue();
- if (value == null) {
- return resValue;
- }
-
- // else attempt to find another ResourceValue referenced by this one.
- ResourceValue resolvedResValue = findResValue(value, resValue.isFramework());
-
- // if the value did not reference anything, then we simply return the input value
- if (resolvedResValue == null) {
- return resValue;
- }
-
- // detect potential loop due to mishandled namespace in attributes
- if (resValue == resolvedResValue || depth >= MAX_RESOURCE_INDIRECTION) {
- if (mLogger != null) {
- mLogger.error(LayoutLog.TAG_BROKEN,
- String.format(
- "Potential stack overflow trying to resolve '%s': cyclic resource definitions? Render may not be accurate.",
- value),
- null);
- }
- return resValue;
- }
-
- // otherwise, we attempt to resolve this new value as well
- return resolveResValue(resolvedResValue, depth + 1);
- }
-
- @Override
- @Nullable
- public ResourceValue findResValue(@Nullable String reference, boolean inFramework) {
- if (mResolver != null) {
- return mResolver.findResValue(reference, inFramework);
- }
-
- if (reference == null) {
- return null;
- }
-
- if (mLookupChain != null && !mLookupChain.isEmpty() &&
- reference.startsWith(PREFIX_RESOURCE_REF)) {
- ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
- if (!reference.equals(prev.getValue())) {
- ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),
- prev.isFramework());
- next.setValue(reference);
- mLookupChain.add(next);
- }
- }
-
- ResourceUrl resource = ResourceUrl.parse(reference);
- if (resource != null && resource.hasValidName()) {
- if (resource.theme) {
- // Do theme lookup? We can't do that here; requires full global analysis, so just use
- // a real resource resolver
- ResourceResolver resolver = getFullResolver();
- if (resolver != null) {
- return resolver.findResValue(reference, inFramework);
- } else {
- return null;
- }
- } else if (reference.startsWith(PREFIX_RESOURCE_REF)) {
- return findResValue(resource.type, resource.name, inFramework || resource.framework);
- }
- }
-
- // Looks like the value didn't reference anything. Return null.
- return null;
- }
-
- private ResourceValue findResValue(ResourceType resType, String resName, boolean framework) {
- // map of ResourceValue for the given type
- // if allowed, search in the project resources first.
- if (!framework) {
- if (myAppResources == null) {
- assert mResourceProvider != null;
- myAppResources = mResourceProvider.getAppResources();
- if (myAppResources == null) {
- return null;
- }
- }
- ResourceValue item = null;
- item = myAppResources.getConfiguredValue(resType, resName, mConfiguration);
- if (item != null) {
- if (mLookupChain != null) {
- mLookupChain.add(item);
- }
- return item;
- }
- } else {
- if (mFrameworkResources == null) {
- assert mResourceProvider != null;
- mFrameworkResources = mResourceProvider.getFrameworkResources();
- if (mFrameworkResources == null) {
- return null;
- }
- }
- // now search in the framework resources.
- if (mFrameworkResources.hasResourceItem(resType, resName)) {
- ResourceItem item = mFrameworkResources.getResourceItem(resType, resName);
- ResourceValue value = item.getResourceValue(resType, mConfiguration, true);
- if (value != null && mLookupChain != null) {
- mLookupChain.add(value);
- }
- return value;
- }
- }
-
- // didn't find the resource anywhere.
- if (mLogger != null) {
- mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE,
- "Couldn't resolve resource @" +
- (framework ? "android:" : "") + resType + "/" + resName,
- new ResourceValue(resType, resName, framework));
- }
- return null;
- }
-
- @Override
- public StyleResourceValue getCurrentTheme() {
- ResourceResolver resolver = getFullResolver();
- if (resolver != null) {
- return resolver.getCurrentTheme();
- }
-
- return null;
- }
-
- @Override
- public ResourceValue resolveValue(ResourceType type, String name, String value,
- boolean isFrameworkValue) {
- if (value == null) {
- return null;
- }
-
- // get the ResourceValue referenced by this value
- ResourceValue resValue = findResValue(value, isFrameworkValue);
-
- // if resValue is null, but value is not null, this means it was not a reference.
- // we return the name/value wrapper in a ResourceValue. the isFramework flag doesn't
- // matter.
- if (resValue == null) {
- return new ResourceValue(type, name, value, isFrameworkValue);
- }
-
- // we resolved a first reference, but we need to make sure this isn't a reference also.
- return resolveResValue(resValue);
- }
-
- // For theme lookup, we need to delegate to a full resource resolver
-
- @Override
- public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
- assert false; // This method shouldn't be called on this resolver
- return super.getTheme(name, frameworkTheme);
- }
-
- @Override
- public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
- assert false; // This method shouldn't be called on this resolver
- return super.themeIsParentOf(parentTheme, childTheme);
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public ResourceValue findItemInTheme(String itemName) {
- ResourceResolver resolver = getFullResolver();
- return resolver != null ? resolver.findItemInTheme(itemName) : null;
- }
-
- @Override
- public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
- ResourceResolver resolver = getFullResolver();
- return resolver != null ? resolver.findItemInTheme(attrName, isFrameworkAttr) : null;
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
- ResourceResolver resolver = getFullResolver();
- return resolver != null ? resolver.findItemInStyle(style, attrName) : null;
- }
-
- @Override
- public ResourceValue findItemInStyle(StyleResourceValue style, String attrName,
- boolean isFrameworkAttr) {
- ResourceResolver resolver = getFullResolver();
- return resolver != null ? resolver.findItemInStyle(style, attrName, isFrameworkAttr) : null;
- }
-
- @Override
- public StyleResourceValue getParent(StyleResourceValue style) {
- ResourceResolver resolver = getFullResolver();
- return resolver != null ? resolver.getParent(style) : null;
- }
-
- private ResourceResolver getFullResolver() {
- if (mResolver == null) {
- if (mResourceProvider == null) {
- return null;
- }
- mResolver = mResourceProvider.getResolver(true);
- if (mResolver != null) {
- if (mLookupChain != null) {
- mResolver = mResolver.createRecorder(mLookupChain);
- }
- }
-
- }
- return mResolver;
- }
-
- /**
- * Optional method to set a list the resolver should record all value resolutions
- * into. Useful if you want to find out the resolution chain for a resource,
- * e.g. {@code @color/buttonForeground => @color/foreground => @android:color/black }.
- * <p>
- * There is no getter. Clients setting this list should look it up themselves.
- * Note also that if this resolver has to delegate to a full resource resolver,
- * e.g. to follow theme attributes, those resolutions will not be recorded.
- *
- * @param lookupChain the list to set, or null
- */
- public void setLookupChainList(@Nullable List<ResourceValue> lookupChain) {
- mLookupChain = lookupChain;
- }
-
- /** Returns the lookup chain being used by this resolver */
- @Nullable
- public List<ResourceValue> getLookupChain() {
- return mLookupChain;
- }
-
- /**
- * Returns a display string for a resource lookup
- *
- * @param type the resource type
- * @param name the resource name
- * @param isFramework whether the item is in the framework
- * @param lookupChain the list of resolved items to display
- * @return the display string
- */
- public static String getDisplayString(
- @NonNull ResourceType type,
- @NonNull String name,
- boolean isFramework,
- @NonNull List<ResourceValue> lookupChain) {
- String url = ResourceUrl.create(type, name, isFramework, false).toString();
- return getDisplayString(url, lookupChain);
- }
-
- /**
- * Returns a display string for a resource lookup
- * @param url the resource url, such as {@code @string/foo}
- * @param lookupChain the list of resolved items to display
- * @return the display string
- */
- @NonNull
- public static String getDisplayString(
- @NonNull String url,
- @NonNull List<ResourceValue> lookupChain) {
- StringBuilder sb = new StringBuilder();
- sb.append(url);
- String prev = url;
- for (ResourceValue element : lookupChain) {
- if (element == null) {
- continue;
- }
- String value = element.getValue();
- if (value == null) {
- continue;
- }
- String text = value;
- if (text.equals(prev)) {
- continue;
- }
-
- sb.append(" => ");
-
- // Strip paths
- if (!(text.startsWith(PREFIX_THEME_REF) || text.startsWith(PREFIX_RESOURCE_REF))) {
- int end = Math.max(text.lastIndexOf('/'), text.lastIndexOf('\\'));
- if (end != -1) {
- text = text.substring(end + 1);
- }
- }
- sb.append(text);
-
- prev = value;
- }
-
- return sb.toString();
- }
-
- /**
- * Interface implemented by clients of the {@link ResourceItemResolver} which allows
- * it to lazily look up the project resources, the framework resources and optionally
- * to provide a fully configured resource resolver, if any
- */
- public interface ResourceProvider {
- @Nullable ResourceResolver getResolver(boolean createIfNecessary);
- @Nullable ResourceRepository getFrameworkResources();
- @Nullable AbstractResourceRepository getAppResources();
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java b/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
deleted file mode 100644
index 23f471b..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
+++ /dev/null
@@ -1,904 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.resources;
-
-import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.PREFIX_ANDROID;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.REFERENCE_STYLE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.resources.ResourceType;
-import com.google.common.collect.Maps;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class ResourceResolver extends RenderResources {
- public static final String THEME_NAME = "Theme";
- public static final String THEME_NAME_DOT = "Theme.";
- public static final String XLIFF_NAMESPACE_PREFIX = "urn:oasis:names:tc:xliff:document:";
- public static final String XLIFF_G_TAG = "g";
- public static final String ATTR_EXAMPLE = "example";
-
- /**
- * Number of indirections we'll follow for resource resolution before assuming there
- * is a cyclic dependency error in the input
- */
- public static final int MAX_RESOURCE_INDIRECTION = 50;
-
- private final Map<ResourceType, Map<String, ResourceValue>> mProjectResources;
- private final Map<ResourceType, Map<String, ResourceValue>> mFrameworkResources;
- private final Map<StyleResourceValue, StyleResourceValue> mStyleInheritanceMap =
- new HashMap<StyleResourceValue, StyleResourceValue>();
- private StyleResourceValue mDefaultTheme;
- // The resources should be searched in all the themes in the list in order.
- private final List<StyleResourceValue> mThemes;
- private FrameworkResourceIdProvider mFrameworkProvider;
- private LayoutLog mLogger;
- private String mThemeName;
- private boolean mIsProjectTheme;
- // AAPT flattens the names by converting '.', '-' and ':' to '_'. These maps undo the
- // flattening. We prefer lazy initialization of these maps since they are not used in many
- // applications.
- @Nullable
- private Map<String, String> mReverseFrameworkStyles;
- @Nullable
- private Map<String, String> mReverseProjectStyles;
-
- private ResourceResolver(
- Map<ResourceType, Map<String, ResourceValue>> projectResources,
- Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
- String themeName, boolean isProjectTheme) {
- mProjectResources = projectResources;
- mFrameworkResources = frameworkResources;
- mThemeName = themeName;
- mIsProjectTheme = isProjectTheme;
- mThemes = new LinkedList<StyleResourceValue>();
- }
-
- /**
- * Creates a new {@link ResourceResolver} object.
- *
- * @param projectResources the project resources.
- * @param frameworkResources the framework resources.
- * @param themeName the name of the current theme.
- * @param isProjectTheme Is this a project theme?
- * @return a new {@link ResourceResolver}
- */
- public static ResourceResolver create(
- Map<ResourceType, Map<String, ResourceValue>> projectResources,
- Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
- String themeName, boolean isProjectTheme) {
-
- ResourceResolver resolver = new ResourceResolver(projectResources, frameworkResources,
- themeName, isProjectTheme);
- resolver.computeStyleMaps();
-
- return resolver;
- }
-
- /**
- * Sets up the light and dark default styles with the given concrete styles. This is used if we
- * want to override the defaults configured in the framework for this particular platform.
- */
- public void setDeviceDefaults(@Nullable String lightStyle, @Nullable String darkStyle) {
- if (darkStyle != null) {
- replace("Theme.DeviceDefault", darkStyle);
- }
- if (lightStyle != null && replace("Theme.DeviceDefault.Light", lightStyle)) {
- replace("Theme.DeviceDefault.Light.DarkActionBar", lightStyle + ".DarkActionBar");
- }
- }
-
- private boolean replace(String fromStyleName, String toStyleName) {
- Map<String, ResourceValue> map = mFrameworkResources.get(ResourceType.STYLE);
- if (map != null) {
- ResourceValue from = map.get(fromStyleName);
- if (from instanceof StyleResourceValue) {
- ResourceValue to = map.get(toStyleName);
- if (to instanceof StyleResourceValue) {
- mStyleInheritanceMap.put((StyleResourceValue)from, (StyleResourceValue)to);
- return true;
- }
- }
- }
- return false;
- }
-
- // ---- Methods to help dealing with older LayoutLibs.
-
- public String getThemeName() {
- return mThemeName;
- }
-
- public boolean isProjectTheme() {
- return mIsProjectTheme;
- }
-
- public Map<ResourceType, Map<String, ResourceValue>> getProjectResources() {
- return mProjectResources;
- }
-
- public Map<ResourceType, Map<String, ResourceValue>> getFrameworkResources() {
- return mFrameworkResources;
- }
-
- // ---- RenderResources Methods
-
- @Override
- public void setFrameworkResourceIdProvider(FrameworkResourceIdProvider provider) {
- mFrameworkProvider = provider;
- }
-
- @Override
- public void setLogger(LayoutLog logger) {
- mLogger = logger;
- }
-
- @Override
- public StyleResourceValue getDefaultTheme() {
- return mDefaultTheme;
- }
-
- @Override
- public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
- if (theme == null) {
- return;
- }
- if (useAsPrimary) {
- mThemes.add(0, theme);
- } else {
- mThemes.add(theme);
- }
- }
-
- @Override
- public void clearStyles() {
- mThemes.clear();
- mThemes.add(mDefaultTheme);
- }
-
- @Override
- public List<StyleResourceValue> getAllThemes() {
- return mThemes;
- }
-
- @Override
- public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
- ResourceValue theme;
-
- if (frameworkTheme) {
- Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(
- ResourceType.STYLE);
- theme = frameworkStyleMap.get(name);
- } else {
- Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE);
- theme = projectStyleMap.get(name);
- }
-
- if (theme instanceof StyleResourceValue) {
- return (StyleResourceValue) theme;
- }
-
- return null;
- }
-
- @Override
- public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
- do {
- childTheme = mStyleInheritanceMap.get(childTheme);
- if (childTheme == null) {
- return false;
- } else if (childTheme == parentTheme) {
- return true;
- }
- } while (true);
- }
-
- @Override
- public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
- return getResource(resourceType, resourceName, mFrameworkResources);
- }
-
- @Override
- public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
- return getResource(resourceType, resourceName, mProjectResources);
- }
-
- @SuppressWarnings("deprecation") // Required to support older layoutlib clients
- @Override
- @Deprecated
- public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
- // this method is deprecated because it doesn't know about the namespace of the
- // attribute so we search for the project namespace first and then in the
- // android namespace if needed.
- ResourceValue item = findItemInStyle(style, attrName, false);
- if (item == null) {
- item = findItemInStyle(style, attrName, true);
- }
-
- return item;
- }
-
- @Override
- public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
- boolean isFrameworkAttr) {
- return findItemInStyle(style, itemName, isFrameworkAttr, 0);
- }
-
- private ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
- boolean isFrameworkAttr, int depth) {
- ResourceValue item = style.getItem(itemName, isFrameworkAttr);
-
- // if we didn't find it, we look in the parent style (if applicable)
- //noinspection VariableNotUsedInsideIf
- if (item == null) {
- StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
- if (parentStyle != null) {
- if (depth >= MAX_RESOURCE_INDIRECTION) {
- if (mLogger != null) {
- mLogger.error(LayoutLog.TAG_BROKEN,
- String.format("Cyclic style parent definitions: %1$s",
- computeCyclicStyleChain(style)),
- null);
- }
-
- return null;
- }
-
- return findItemInStyle(parentStyle, itemName, isFrameworkAttr, depth + 1);
- }
- }
-
- return item;
- }
-
- private String computeCyclicStyleChain(StyleResourceValue style) {
- StringBuilder sb = new StringBuilder(100);
- appendStyleParents(style, new HashSet<StyleResourceValue>(), 0, sb);
- return sb.toString();
- }
-
- private void appendStyleParents(StyleResourceValue style, Set<StyleResourceValue> seen,
- int depth, StringBuilder sb) {
- if (depth >= MAX_RESOURCE_INDIRECTION) {
- sb.append("...");
- return;
- }
-
- boolean haveSeen = seen.contains(style);
- seen.add(style);
-
- sb.append('"');
- if (style.isFramework()) {
- sb.append(PREFIX_ANDROID);
- }
- sb.append(style.getName());
- sb.append('"');
-
- if (haveSeen) {
- return;
- }
-
- StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
- if (parentStyle != null) {
- if (style.getParentStyle() != null) {
- sb.append(" specifies parent ");
- } else {
- sb.append(" implies parent ");
- }
-
- appendStyleParents(parentStyle, seen, depth + 1, sb);
- }
- }
-
- @Override
- public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
- if (reference == null) {
- return null;
- }
-
- ResourceUrl resource = ResourceUrl.parse(reference);
- if (resource != null && resource.hasValidName()) {
- if (resource.theme) {
- // no theme? no need to go further!
- if (mDefaultTheme == null) {
- return null;
- }
-
- if (resource.type != ResourceType.ATTR) {
- // At this time, no support for ?type/name where type is not "attr"
- return null;
- }
-
- // Now look for the item in the theme, starting with the current one.
- return findItemInTheme(resource.name, forceFrameworkOnly || resource.framework);
- } else {
- return findResValue(resource, forceFrameworkOnly);
- }
- }
-
- // Looks like the value didn't reference anything. Return null.
- return null;
- }
-
- @Override
- public ResourceValue resolveValue(ResourceType type, String name, String value,
- boolean isFrameworkValue) {
- if (value == null) {
- return null;
- }
-
- // get the ResourceValue referenced by this value
- ResourceValue resValue = findResValue(value, isFrameworkValue);
-
- // if resValue is null, but value is not null, this means it was not a reference.
- // we return the name/value wrapper in a ResourceValue. the isFramework flag doesn't
- // matter.
- if (resValue == null) {
- return new ResourceValue(type, name, value, isFrameworkValue);
- }
-
- // we resolved a first reference, but we need to make sure this isn't a reference also.
- return resolveResValue(resValue);
- }
-
- @Override
- public ResourceValue resolveResValue(ResourceValue resValue) {
- return resolveResValue(resValue, 0);
- }
-
- private ResourceValue resolveResValue(ResourceValue resValue, int depth) {
- if (resValue == null) {
- return null;
- }
-
- // if the resource value is null, we simply return it.
- String value = resValue.getValue();
- if (value == null) {
- return resValue;
- }
-
- // else attempt to find another ResourceValue referenced by this one.
- ResourceValue resolvedResValue = findResValue(value, resValue.isFramework());
-
- // if the value did not reference anything, then we simply return the input value
- if (resolvedResValue == null) {
- return resValue;
- }
-
- // detect potential loop due to mishandled namespace in attributes
- if (resValue == resolvedResValue || depth >= MAX_RESOURCE_INDIRECTION) {
- if (mLogger != null) {
- mLogger.error(LayoutLog.TAG_BROKEN,
- String.format("Potential stack overflow trying to resolve '%s': cyclic resource definitions? Render may not be accurate.", value),
- null);
- }
- return resValue;
- }
-
- // otherwise, we attempt to resolve this new value as well
- return resolveResValue(resolvedResValue, depth + 1);
- }
-
- // ---- Private helper methods.
-
- /**
- * Searches for, and returns a {@link ResourceValue} by its parsed reference.
- * @param resource the parsed resource
- * @param forceFramework if <code>true</code>, the method does not search in the
- * project resources
- */
- private ResourceValue findResValue(ResourceUrl resource, boolean forceFramework) {
- // map of ResourceValue for the given type
- Map<String, ResourceValue> typeMap;
- ResourceType resType = resource.type;
- String resName = resource.name;
- boolean isFramework = forceFramework || resource.framework;
-
- if (!isFramework) {
- typeMap = mProjectResources.get(resType);
- ResourceValue item = typeMap.get(resName);
- if (item != null) {
- return item;
- }
- } else {
- typeMap = mFrameworkResources.get(resType);
- if (typeMap != null) {
- ResourceValue item = typeMap.get(resName);
- if (item != null) {
- return item;
- }
- }
-
- // if it was not found and the type is an id, it is possible that the ID was
- // generated dynamically when compiling the framework resources.
- // Look for it in the R map.
- if (mFrameworkProvider != null && resType == ResourceType.ID) {
- if (mFrameworkProvider.getId(resType, resName) != null) {
- return new ResourceValue(resType, resName, true);
- }
- }
- }
-
- // didn't find the resource anywhere.
- if (!resource.create && mLogger != null) {
- mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE,
- "Couldn't resolve resource @" +
- (isFramework ? "android:" : "") + resType + "/" + resName,
- new ResourceValue(resType, resName, isFramework));
- }
- return null;
- }
-
- private ResourceValue getResource(ResourceType resourceType, String resourceName,
- Map<ResourceType, Map<String, ResourceValue>> resourceRepository) {
- Map<String, ResourceValue> typeMap = resourceRepository.get(resourceType);
- if (typeMap != null) {
- ResourceValue item = typeMap.get(resourceName);
- if (item != null) {
- item = resolveResValue(item);
- return item;
- }
- }
-
- // didn't find the resource anywhere.
- return null;
- }
-
- /**
- * Compute style information from the given list of style for the project and framework.
- */
- private void computeStyleMaps() {
- Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE);
- Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(ResourceType.STYLE);
-
- // first, get the theme
- ResourceValue theme = null;
-
- // project theme names have been prepended with a *
- if (mIsProjectTheme) {
- if (projectStyleMap != null) {
- theme = projectStyleMap.get(mThemeName);
- }
- } else {
- if (frameworkStyleMap != null) {
- theme = frameworkStyleMap.get(mThemeName);
- }
- }
-
- if (theme instanceof StyleResourceValue) {
- // compute the inheritance map for both the project and framework styles
- computeStyleInheritance(projectStyleMap.values(), projectStyleMap,
- frameworkStyleMap);
-
- // Compute the style inheritance for the framework styles/themes.
- // Since, for those, the style parent values do not contain 'android:'
- // we want to force looking in the framework style only to avoid using
- // similarly named styles from the project.
- // To do this, we pass null in lieu of the project style map.
- if (frameworkStyleMap != null) {
- computeStyleInheritance(frameworkStyleMap.values(), null /*inProjectStyleMap */,
- frameworkStyleMap);
- }
-
- mDefaultTheme = (StyleResourceValue) theme;
- mThemes.clear();
- mThemes.add(mDefaultTheme);
- }
- }
-
- /**
- * Compute the parent style for all the styles in a given list.
- * @param styles the styles for which we compute the parent.
- * @param inProjectStyleMap the map of project styles.
- * @param inFrameworkStyleMap the map of framework styles.
- */
- private void computeStyleInheritance(Collection<ResourceValue> styles,
- Map<String, ResourceValue> inProjectStyleMap,
- Map<String, ResourceValue> inFrameworkStyleMap) {
- for (ResourceValue value : styles) {
- if (value instanceof StyleResourceValue) {
- StyleResourceValue style = (StyleResourceValue)value;
-
- // first look for a specified parent.
- String parentName = style.getParentStyle();
-
- // no specified parent? try to infer it from the name of the style.
- if (parentName == null) {
- parentName = getParentName(value.getName());
- }
-
- if (parentName != null) {
- StyleResourceValue parentStyle = getStyle(parentName, inProjectStyleMap,
- inFrameworkStyleMap);
-
- if (parentStyle != null) {
- mStyleInheritanceMap.put(style, parentStyle);
- }
- }
- }
- }
- }
-
- /**
- * Computes the name of the parent style, or <code>null</code> if the style is a root style.
- */
- private static String getParentName(String styleName) {
- int index = styleName.lastIndexOf('.');
- if (index != -1) {
- return styleName.substring(0, index);
- }
-
- return null;
- }
-
- @Override
- @Nullable
- public StyleResourceValue getParent(@NonNull StyleResourceValue style) {
- return mStyleInheritanceMap.get(style);
- }
-
- @Override
- @Nullable
- public StyleResourceValue getStyle(@NonNull String styleName, boolean isFramework) {
- ResourceValue res;
- Map<String, ResourceValue> styleMap;
-
- // First check if we can find the style directly.
- if (isFramework) {
- styleMap = mFrameworkResources.get(ResourceType.STYLE);
- } else {
- styleMap = mProjectResources.get(ResourceType.STYLE);
- }
- res = getStyleFromMap(styleMap, styleName);
- if (res != null) {
- // If the obtained resource is not StyleResourceValue, return null.
- return res instanceof StyleResourceValue ? (StyleResourceValue) res : null;
- }
-
- // We cannot find the style directly. The style name may have been flattened by AAPT for use
- // in the R class. Try and obtain the original name.
- String xmlStyleName = getReverseStyleMap(isFramework)
- .get(getNormalizedStyleName(styleName));
- if (!styleName.equals(xmlStyleName)) {
- res = getStyleFromMap(styleMap, xmlStyleName);
- }
- return res instanceof StyleResourceValue ? (StyleResourceValue) res : null;
- }
-
- @Override
- @Nullable
- public String getXmlName(@NonNull ResourceType type, @NonNull String name,
- boolean isFramework) {
- if (type != ResourceType.STYLE) {
- // The method is currently implemented for styles only.
- return null;
- }
- Map<String, String> reverseStyles;
- reverseStyles = getReverseStyleMap(isFramework);
- return reverseStyles.get(name);
- }
-
- /**
- * Returns the reverse style map using the appropriate resources. It also initializes the map if
- * it hasn't been initialized yet.
- */
- private Map<String, String> getReverseStyleMap(boolean isFramework) {
- if (isFramework) {
- // The reverse style map may need to be initialized.
- if (mReverseFrameworkStyles == null) {
- Map<String, ResourceValue> styleMap = mFrameworkResources.get(ResourceType.STYLE);
- mReverseFrameworkStyles = createReverseStyleMap(styleMap.keySet());
- }
- return mReverseFrameworkStyles;
- } else {
- if (mReverseProjectStyles == null) {
- Map<String, ResourceValue> styleMap = mProjectResources.get(ResourceType.STYLE);
- mReverseProjectStyles = createReverseStyleMap(styleMap.keySet());
- }
- return mReverseProjectStyles;
- }
- }
-
- /**
- * Create a map from the normalized form of the style names in {@code styles} to the original
- * style name.
- *
- * @see #getNormalizedStyleName(String)
- */
- private static Map<String, String> createReverseStyleMap(@NonNull Set<String> styles) {
- Map<String, String> reverseStyles = Maps.newHashMapWithExpectedSize(styles.size());
- for (String style : styles) {
- reverseStyles.put(getNormalizedStyleName(style), style);
- }
- return reverseStyles;
- }
-
- /**
- * Flatten the styleName like AAPT by replacing dots, dashes and colons with underscores.
- */
- @NonNull
- private static String getNormalizedStyleName(@NonNull String styleName) {
- return styleName.replace('.', '_').replace('-', '_').replace(':', '_');
- }
-
- /**
- * Search for the style in the given map and log an error if the obtained resource is not
- * {@link StyleResourceValue}.
- *
- * @return The {@link ResourceValue} found in the map.
- */
- @Nullable
- private ResourceValue getStyleFromMap(@NonNull Map<String, ResourceValue> styleMap,
- @NonNull String styleName) {
- ResourceValue res;
- res = styleMap.get(styleName);
- if (res != null) {
- if (!(res instanceof StyleResourceValue) && mLogger != null) {
- mLogger.error(null, String.format(
- "Style %1$s is not of type STYLE (instead %2$s)",
- styleName, res.getResourceType().toString()),
- null);
- }
- }
- return res;
- }
-
- /**
- * Searches for and returns the {@link StyleResourceValue} from a given name.
- * <p/>The format of the name can be:
- * <ul>
- * <li>[android:]<name></li>
- * <li>[android:]style/<name></li>
- * <li>@[android:]style/<name></li>
- * </ul>
- * @param parentName the name of the style.
- * @param inProjectStyleMap the project style map. Can be <code>null</code>
- * @param inFrameworkStyleMap the framework style map.
- * @return The matching {@link StyleResourceValue} object or <code>null</code> if not found.
- */
- private StyleResourceValue getStyle(String parentName,
- Map<String, ResourceValue> inProjectStyleMap,
- Map<String, ResourceValue> inFrameworkStyleMap) {
- boolean frameworkOnly = false;
-
- String name = parentName;
-
- // remove the useless @ if it's there
- if (name.startsWith(PREFIX_RESOURCE_REF)) {
- name = name.substring(PREFIX_RESOURCE_REF.length());
- }
-
- // check for framework identifier.
- if (name.startsWith(PREFIX_ANDROID)) {
- frameworkOnly = true;
- name = name.substring(PREFIX_ANDROID.length());
- }
-
- // at this point we could have the format <type>/<name>. we want only the name as long as
- // the type is style.
- if (name.startsWith(REFERENCE_STYLE)) {
- name = name.substring(REFERENCE_STYLE.length());
- } else if (name.indexOf('/') != -1) {
- return null;
- }
-
- ResourceValue parent = null;
-
- // if allowed, search in the project resources.
- if (!frameworkOnly && inProjectStyleMap != null) {
- parent = inProjectStyleMap.get(name);
- }
-
- // if not found, then look in the framework resources.
- if (parent == null) {
- if (inFrameworkStyleMap == null) {
- return null;
- }
- parent = inFrameworkStyleMap.get(name);
- }
-
- // make sure the result is the proper class type and return it.
- if (parent instanceof StyleResourceValue) {
- return (StyleResourceValue)parent;
- }
-
- if (mLogger != null) {
- mLogger.error(LayoutLog.TAG_RESOURCES_RESOLVE,
- String.format("Unable to resolve parent style name: %s", parentName),
- null /*data*/);
- }
-
- return null;
- }
-
- /** Returns true if the given {@link ResourceValue} represents a theme */
- public boolean isTheme(
- @NonNull ResourceValue value,
- @Nullable Map<ResourceValue, Boolean> cache) {
- return isTheme(value, cache, 0);
- }
-
- private boolean isTheme(
- @NonNull ResourceValue value,
- @Nullable Map<ResourceValue, Boolean> cache,
- int depth) {
- if (cache != null) {
- Boolean known = cache.get(value);
- if (known != null) {
- return known;
- }
- }
- if (value instanceof StyleResourceValue) {
- StyleResourceValue srv = (StyleResourceValue) value;
- String name = srv.getName();
- if (srv.isFramework() && (name.equals(THEME_NAME) || name.startsWith(THEME_NAME_DOT))) {
- if (cache != null) {
- cache.put(value, true);
- }
- return true;
- }
-
- StyleResourceValue parentStyle = mStyleInheritanceMap.get(srv);
- if (parentStyle != null) {
- if (depth >= MAX_RESOURCE_INDIRECTION) {
- if (mLogger != null) {
- mLogger.error(LayoutLog.TAG_BROKEN,
- String.format("Cyclic style parent definitions: %1$s",
- computeCyclicStyleChain(srv)),
- null);
- }
-
- return false;
- }
-
- boolean result = isTheme(parentStyle, cache, depth + 1);
- if (cache != null) {
- cache.put(value, result);
- }
- return result;
- }
- }
-
- return false;
- }
-
- /**
- * Returns true if the given {@code themeStyle} extends the theme given by
- * {@code parentStyle}
- */
- public boolean themeExtends(@NonNull String parentStyle, @NonNull String themeStyle) {
- ResourceValue parentValue = findResValue(parentStyle,
- parentStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
- if (parentValue instanceof StyleResourceValue) {
- ResourceValue themeValue = findResValue(themeStyle,
- themeStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
- if (themeValue == parentValue) {
- return true;
- }
- if (themeValue instanceof StyleResourceValue) {
- return themeIsParentOf((StyleResourceValue) parentValue,
- (StyleResourceValue) themeValue);
- }
- }
-
- return false;
- }
-
- /**
- * Creates a new {@link ResourceResolver} which records all resource resolution
- * lookups into the given list. Note that it is the responsibility of the caller
- * to clear/reset the list between subsequent lookup operations.
- *
- * @param lookupChain the list to write resource lookups into
- * @return a new {@link ResourceResolver}
- */
- public ResourceResolver createRecorder(List<ResourceValue> lookupChain) {
- ResourceResolver resolver = new RecordingResourceResolver(
- lookupChain, mProjectResources, mFrameworkResources, mThemeName, mIsProjectTheme);
- resolver.mFrameworkProvider = mFrameworkProvider;
- resolver.mLogger = mLogger;
- resolver.mDefaultTheme = mDefaultTheme;
- resolver.mStyleInheritanceMap.putAll(mStyleInheritanceMap);
- resolver.mThemes.addAll(mThemes);
- return resolver;
- }
-
- private static class RecordingResourceResolver extends ResourceResolver {
- @NonNull private List<ResourceValue> mLookupChain;
-
- private RecordingResourceResolver(
- @NonNull List<ResourceValue> lookupChain,
- @NonNull Map<ResourceType, Map<String, ResourceValue>> projectResources,
- @NonNull Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
- @NonNull String themeName, boolean isProjectTheme) {
- super(projectResources, frameworkResources, themeName, isProjectTheme);
- mLookupChain = lookupChain;
- }
-
- @Override
- public ResourceValue resolveResValue(ResourceValue resValue) {
- if (resValue != null) {
- mLookupChain.add(resValue);
- }
-
- return super.resolveResValue(resValue);
- }
-
- @Override
- public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
- if (!mLookupChain.isEmpty() && reference.startsWith(PREFIX_RESOURCE_REF)) {
- ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
- if (!reference.equals(prev.getValue())) {
- ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),
- prev.isFramework());
- next.setValue(reference);
- mLookupChain.add(next);
- }
- }
-
- ResourceValue resValue = super.findResValue(reference, forceFrameworkOnly);
-
- if (resValue != null) {
- mLookupChain.add(resValue);
- }
-
- return resValue;
- }
-
- @Override
- public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
- boolean isFrameworkAttr) {
- ResourceValue value = super.findItemInStyle(style, itemName, isFrameworkAttr);
- if (value != null) {
- mLookupChain.add(value);
- }
- return value;
- }
-
- @Override
- public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
- ResourceValue value = super.findItemInTheme(attrName, isFrameworkAttr);
- if (value != null) {
- mLookupChain.add(value);
- }
- return value;
- }
-
- @Override
- public ResourceValue resolveValue(ResourceType type, String name, String value,
- boolean isFrameworkValue) {
- ResourceValue resourceValue = super.resolveValue(type, name, value, isFrameworkValue);
- if (resourceValue != null) {
- mLookupChain.add(resourceValue);
- }
- return resourceValue;
- }
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java b/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java
deleted file mode 100644
index ccb66d0..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java
+++ /dev/null
@@ -1,1197 +0,0 @@
-/*
- * Copyright (C) 2007 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.ide.common.resources.configuration;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.Density;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ScreenOrientation;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Iterators;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-
-
-/**
- * Represents the configuration for Resource Folders. All the properties have a default
- * value which means that the property is not set.
- */
-public final class FolderConfiguration implements Comparable<FolderConfiguration> {
-
- @SuppressWarnings("NullableProblems") // done in static-block below
- @NonNull
- private static final ResourceQualifier[] DEFAULT_QUALIFIERS;
-
- static {
- // get the default qualifiers.
- FolderConfiguration defaultConfig = new FolderConfiguration();
- defaultConfig.createDefault();
- DEFAULT_QUALIFIERS = defaultConfig.getQualifiers();
- }
-
- /** Splitter which can be used to split qualifiers */
- public static final Splitter QUALIFIER_SPLITTER = Splitter.on('-');
-
-
- private final ResourceQualifier[] mQualifiers;
-
- private static final int INDEX_COUNTRY_CODE = 0;
- private static final int INDEX_NETWORK_CODE = 1;
- private static final int INDEX_LOCALE = 2;
- private static final int INDEX_LAYOUT_DIR = 3;
- private static final int INDEX_SMALLEST_SCREEN_WIDTH = 4;
- private static final int INDEX_SCREEN_WIDTH = 5;
- private static final int INDEX_SCREEN_HEIGHT = 6;
- private static final int INDEX_SCREEN_LAYOUT_SIZE = 7;
- private static final int INDEX_SCREEN_RATIO = 8;
- private static final int INDEX_SCREEN_ROUND = 9;
- private static final int INDEX_SCREEN_ORIENTATION = 10;
- private static final int INDEX_UI_MODE = 11;
- private static final int INDEX_NIGHT_MODE = 12;
- private static final int INDEX_PIXEL_DENSITY = 13;
- private static final int INDEX_TOUCH_TYPE = 14;
- private static final int INDEX_KEYBOARD_STATE = 15;
- private static final int INDEX_TEXT_INPUT_METHOD = 16;
- private static final int INDEX_NAVIGATION_STATE = 17;
- private static final int INDEX_NAVIGATION_METHOD = 18;
- private static final int INDEX_SCREEN_DIMENSION = 19;
- private static final int INDEX_VERSION = 20;
- private static final int INDEX_COUNT = 21;
-
- public FolderConfiguration() {
- mQualifiers = new ResourceQualifier[INDEX_COUNT];
- }
-
- private FolderConfiguration(ResourceQualifier[] qualifiers) {
- this();
- System.arraycopy(qualifiers, 0, mQualifiers, 0, INDEX_COUNT);
- }
- /**
- * Creates a {@link FolderConfiguration} matching the folder segments.
- * @param folderSegments The segments of the folder name. The first segments should contain
- * the name of the folder
- * @return a FolderConfiguration object, or null if the folder name isn't valid..
- */
- @Nullable
- public static FolderConfiguration getConfig(@NonNull String[] folderSegments) {
- Iterator<String> iterator = Iterators.forArray(folderSegments);
- if (iterator.hasNext()) {
- // Skip the first segment: it should be just the base folder, such as "values" or
- // "layout"
- iterator.next();
- }
-
- return getConfigFromQualifiers(iterator);
- }
-
- /**
- * Creates a {@link FolderConfiguration} matching the folder segments.
- * @param folderSegments The segments of the folder name. The first segments should contain
- * the name of the folder
- * @return a FolderConfiguration object, or null if the folder name isn't valid..
- * @see FolderConfiguration#getConfig(String[])
- */
- @Nullable
- public static FolderConfiguration getConfig(@NonNull Iterable<String> folderSegments) {
- Iterator<String> iterator = folderSegments.iterator();
- if (iterator.hasNext()) {
- // Skip the first segment: it should be just the base folder, such as "values" or
- // "layout"
- iterator.next();
- }
-
- return getConfigFromQualifiers(iterator);
- }
-
- /**
- * Creates a {@link FolderConfiguration} matching the qualifiers.
- *
- * @param qualifiers the qualifiers.
- *
- * @return a FolderConfiguration object, or null if the folder name isn't valid..
- */
- @Nullable
- public static FolderConfiguration getConfigFromQualifiers(
- @NonNull Iterable<String> qualifiers) {
- return getConfigFromQualifiers(qualifiers.iterator());
- }
-
- /**
- * Creates a {@link FolderConfiguration} matching the qualifiers.
- *
- * @param qualifiers An iterator on the qualifiers.
- *
- * @return a FolderConfiguration object, or null if the folder name isn't valid..
- */
- @Nullable
- public static FolderConfiguration getConfigFromQualifiers(
- @NonNull Iterator<String> qualifiers) {
- FolderConfiguration config = new FolderConfiguration();
-
- // we are going to loop through the segments, and match them with the first
- // available qualifier. If the segment doesn't match we try with the next qualifier.
- // Because the order of the qualifier is fixed, we do not reset the first qualifier
- // after each successful segment.
- // If we run out of qualifier before processing all the segments, we fail.
-
- int qualifierIndex = 0;
- int qualifierCount = DEFAULT_QUALIFIERS.length;
-
- /*
- Process a series of qualifiers and parse them into a set of ResourceQualifiers
- in the new folder configuration.
-
- The basic loop is as follows:
-
- while (qualifiers.hasNext()) {
- String seg = qualifiers.next();
-
- while (qualifierIndex < qualifierCount &&
- !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
- qualifierIndex++;
- }
-
- // if we reached the end of the qualifier we didn't find a matching qualifier.
- if (qualifierIndex == qualifierCount) {
- return null;
- } else {
- qualifierIndex++; // already processed this one
- }
- }
-
- In other words, we process through the iterable, one segment at a time, and
- for that segment, we iterate up through the qualifiers and ask each one if
- they can handle it (via checkAndSet); if they can, we are done with that segment
- *and* that qualifier, so next time through the segment loop we won't keep retrying
- the same qualifiers. So, we are basically iterating through two lists (segments
- and qualifiers) at the same time).
-
- However, locales are a special exception to this: we want to combine *two* segments
- into a single qualifier when you specify both a language and a region.
- E.g. for "en-rUS-ldltr" we want a single LocaleQualifier holding both "en" and "rUS"
- and then a LayoutDirectionQualifier for ldltr.
-
- Therefore, we've unrolled the above loop: we process all identifiers up to
- the locale qualifier index.
-
- Then, at the locale qualifier index, IF we get a match, we don't increment
- the qualifierIndex: instead, we fetch the next segment, and if it matches
- as a region, we augment the locale qualifier and continue -- otherwise, we
- bail and process the next segment as usual.
-
- And then we finally iterate through the remaining qualifiers and segments; this
- is basically the first loop again, iterating from the post-locale qualifier
- up to the end.
- */
-
- if (!qualifiers.hasNext()) {
- return config;
- }
-
- while (qualifiers.hasNext()) {
- String seg = qualifiers.next();
- if (seg.isEmpty()) {
- return null; // Not a valid folder configuration
- }
-
- // TODO: Perform case normalization later (on a per qualifier basis)
- seg = seg.toLowerCase(Locale.US); // no-op if string is already in lower case
-
- while (qualifierIndex < INDEX_LOCALE &&
- !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
- qualifierIndex++;
- }
-
- // if we reached the end of the qualifier we didn't find a matching qualifier.
- if (qualifierIndex == INDEX_LOCALE) {
- // Ready for locale matching now; that requires some special
- // casing described below
-
- boolean handle = true;
- // Don't need to lowercase; qualifier will normalize case on its own
- if (DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
- qualifierIndex++;
- if (qualifiers.hasNext()) {
- seg = qualifiers.next();
- if (seg.isEmpty()) {
- return null; // Not a valid folder configuration
- }
- // Is the next qualifier a region? If so, amend the existing
- // LocaleQualifier
- if (LocaleQualifier.isRegionSegment(seg)) {
- LocaleQualifier localeQualifier = config.getLocaleQualifier();
- assert localeQualifier != null; // because checkAndSet returned true above
- localeQualifier.setRegionSegment(seg);
- handle = false;
- } else {
- // No, not a region, so perform normal processing
- seg = seg.toLowerCase(Locale.US); // no-op if string is already in lower case
- }
- } else {
- return config;
- }
- }
-
- if (handle) {
- while (qualifierIndex < qualifierCount &&
- !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
- qualifierIndex++;
- }
-
- // if we reached the end of the qualifier we didn't find a matching qualifier.
- if (qualifierIndex == qualifierCount) {
- // Ready for locale matching now; that requires some special
- // casing described below
- return null;
- } else {
- qualifierIndex++; // already processed this one
- }
- }
-
- break;
- } else {
- qualifierIndex++; // already processed this one
- }
- }
-
- // Same loop as above, but we continue from after the locales
- while (qualifiers.hasNext()) {
- String seg = qualifiers.next();
- if (seg.isEmpty()) {
- return null; // Not a valid folder configuration
- }
-
- seg = seg.toLowerCase(Locale.US); // no-op if string is already in lower case
-
- while (qualifierIndex < qualifierCount &&
- !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
- qualifierIndex++;
- }
-
- // if we reached the end of the qualifier we didn't find a matching qualifier.
- if (qualifierIndex == qualifierCount) {
- return null;
- } else {
- qualifierIndex++; // already processed this one
- }
- }
-
- return config;
- }
-
- /**
- * Creates a {@link FolderConfiguration} matching the given folder name.
- *
- * @param folderName the folder name
- * @return a FolderConfiguration object, or null if the folder name isn't valid..
- */
- @Nullable
- public static FolderConfiguration getConfigForFolder(@NonNull String folderName) {
- return getConfig(QUALIFIER_SPLITTER.split(folderName));
- }
-
- /**
- * Creates a copy of the given {@link FolderConfiguration}, that can be modified without
- * affecting the original.
- */
- @NonNull
- public static FolderConfiguration copyOf(@NonNull FolderConfiguration original) {
- return new FolderConfiguration(original.mQualifiers);
- }
-
- /**
- * Creates a {@link FolderConfiguration} matching the given qualifier string
- * (just the qualifiers; e.g. for a folder like "values-en-rUS" this would be "en-rUS").
- *
- * @param qualifierString the qualifier string
- * @return a FolderConfiguration object, or null if the qualifier string isn't valid..
- */
- @Nullable
- public static FolderConfiguration getConfigForQualifierString(@NonNull String qualifierString) {
- if (qualifierString.isEmpty()) {
- return new FolderConfiguration();
- } else {
- return getConfigFromQualifiers(QUALIFIER_SPLITTER.split(qualifierString));
- }
- }
-
- /**
- * Returns the number of {@link ResourceQualifier} that make up a Folder configuration.
- */
- public static int getQualifierCount() {
- return INDEX_COUNT;
- }
-
- /**
- * Sets the config from the qualifiers of a given <var>config</var>.
- * <p/>This is equivalent to <code>set(config, false)</code>
- * @param config the configuration to set
- *
- * @see #set(FolderConfiguration, boolean)
- */
- public void set(@Nullable FolderConfiguration config) {
- set(config, false /*nonFakeValuesOnly*/);
- }
-
- /**
- * Sets the config from the qualifiers of a given <var>config</var>.
- * @param config the configuration to set
- * @param nonFakeValuesOnly if set to true this ignore qualifiers for which the
- * current value is a fake value.
- *
- * @see ResourceQualifier#hasFakeValue()
- */
- public void set(@Nullable FolderConfiguration config, boolean nonFakeValuesOnly) {
- if (config != null) {
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- ResourceQualifier q = config.mQualifiers[i];
- if (!nonFakeValuesOnly || q == null || !q.hasFakeValue()) {
- mQualifiers[i] = q;
- }
- }
- }
- }
-
- /**
- * Reset the config.
- * <p/>This makes qualifiers at all indices <code>null</code>.
- */
- public void reset() {
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- mQualifiers[i] = null;
- }
- }
-
- /**
- * Removes the qualifiers from the receiver if they are present (and valid)
- * in the given configuration.
- */
- public void substract(@NonNull FolderConfiguration config) {
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- if (config.mQualifiers[i] != null && config.mQualifiers[i].isValid()) {
- mQualifiers[i] = null;
- }
- }
- }
-
- /**
- * Adds the non-qualifiers from the given config.
- * Qualifiers that are null in the given config do not change in the receiver.
- */
- public void add(@NonNull FolderConfiguration config) {
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- if (config.mQualifiers[i] != null) {
- mQualifiers[i] = config.mQualifiers[i];
- }
- }
- }
-
- /**
- * Returns the first invalid qualifier, or <code>null<code> if they are all valid (or if none
- * exists).
- */
- @Nullable
- public ResourceQualifier getInvalidQualifier() {
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- if (mQualifiers[i] != null && !mQualifiers[i].isValid()) {
- return mQualifiers[i];
- }
- }
-
- // all allocated qualifiers are valid, we return null.
- return null;
- }
-
- /**
- * Adds a qualifier to the {@link FolderConfiguration}
- * @param qualifier the {@link ResourceQualifier} to add.
- */
- public void addQualifier(@Nullable ResourceQualifier qualifier) {
- if (qualifier instanceof CountryCodeQualifier) {
- mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
-
- } else if (qualifier instanceof NetworkCodeQualifier) {
- mQualifiers[INDEX_NETWORK_CODE] = qualifier;
-
- } else if (qualifier instanceof LocaleQualifier) {
- mQualifiers[INDEX_LOCALE] = qualifier;
-
- } else if (qualifier instanceof LayoutDirectionQualifier) {
- mQualifiers[INDEX_LAYOUT_DIR] = qualifier;
-
- } else if (qualifier instanceof SmallestScreenWidthQualifier) {
- mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
-
- } else if (qualifier instanceof ScreenWidthQualifier) {
- mQualifiers[INDEX_SCREEN_WIDTH] = qualifier;
-
- } else if (qualifier instanceof ScreenHeightQualifier) {
- mQualifiers[INDEX_SCREEN_HEIGHT] = qualifier;
-
- } else if (qualifier instanceof ScreenSizeQualifier) {
- mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = qualifier;
-
- } else if (qualifier instanceof ScreenRatioQualifier) {
- mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
-
- } else if (qualifier instanceof ScreenRoundQualifier) {
- mQualifiers[INDEX_SCREEN_ROUND] = qualifier;
-
- } else if (qualifier instanceof ScreenOrientationQualifier) {
- mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
-
- } else if (qualifier instanceof UiModeQualifier) {
- mQualifiers[INDEX_UI_MODE] = qualifier;
-
- } else if (qualifier instanceof NightModeQualifier) {
- mQualifiers[INDEX_NIGHT_MODE] = qualifier;
-
- } else if (qualifier instanceof DensityQualifier) {
- mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
-
- } else if (qualifier instanceof TouchScreenQualifier) {
- mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
-
- } else if (qualifier instanceof KeyboardStateQualifier) {
- mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
-
- } else if (qualifier instanceof TextInputMethodQualifier) {
- mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
-
- } else if (qualifier instanceof NavigationStateQualifier) {
- mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
-
- } else if (qualifier instanceof NavigationMethodQualifier) {
- mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
-
- } else if (qualifier instanceof ScreenDimensionQualifier) {
- mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
-
- } else if (qualifier instanceof VersionQualifier) {
- mQualifiers[INDEX_VERSION] = qualifier;
-
- }
- }
-
- /**
- * Removes a given qualifier from the {@link FolderConfiguration}.
- * @param qualifier the {@link ResourceQualifier} to remove.
- */
- public void removeQualifier(@NonNull ResourceQualifier qualifier) {
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- if (mQualifiers[i] == qualifier) {
- mQualifiers[i] = null;
- return;
- }
- }
- }
-
- /**
- * Returns a qualifier by its index. The total number of qualifiers can be accessed by
- * {@link #getQualifierCount()}.
- * @param index the index of the qualifier to return.
- * @return the qualifier or null if there are none at the index.
- */
- @Nullable
- public ResourceQualifier getQualifier(int index) {
- return mQualifiers[index];
- }
-
- public void setCountryCodeQualifier(CountryCodeQualifier qualifier) {
- mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
- }
-
- @Nullable
- public CountryCodeQualifier getCountryCodeQualifier() {
- return (CountryCodeQualifier)mQualifiers[INDEX_COUNTRY_CODE];
- }
-
- public void setNetworkCodeQualifier(NetworkCodeQualifier qualifier) {
- mQualifiers[INDEX_NETWORK_CODE] = qualifier;
- }
-
- @Nullable
- public NetworkCodeQualifier getNetworkCodeQualifier() {
- return (NetworkCodeQualifier)mQualifiers[INDEX_NETWORK_CODE];
- }
-
- public void setLocaleQualifier(LocaleQualifier qualifier) {
- mQualifiers[INDEX_LOCALE] = qualifier;
- }
-
- @Nullable
- public LocaleQualifier getLocaleQualifier() {
- return (LocaleQualifier)mQualifiers[INDEX_LOCALE];
- }
-
- public void setLayoutDirectionQualifier(LayoutDirectionQualifier qualifier) {
- mQualifiers[INDEX_LAYOUT_DIR] = qualifier;
- }
-
- @Nullable
- public LayoutDirectionQualifier getLayoutDirectionQualifier() {
- return (LayoutDirectionQualifier)mQualifiers[INDEX_LAYOUT_DIR];
- }
-
- public void setSmallestScreenWidthQualifier(SmallestScreenWidthQualifier qualifier) {
- mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
- }
-
- @Nullable
- public SmallestScreenWidthQualifier getSmallestScreenWidthQualifier() {
- return (SmallestScreenWidthQualifier) mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH];
- }
-
- public void setScreenWidthQualifier(ScreenWidthQualifier qualifier) {
- mQualifiers[INDEX_SCREEN_WIDTH] = qualifier;
- }
-
- @Nullable
- public ScreenWidthQualifier getScreenWidthQualifier() {
- return (ScreenWidthQualifier) mQualifiers[INDEX_SCREEN_WIDTH];
- }
-
- public void setScreenHeightQualifier(ScreenHeightQualifier qualifier) {
- mQualifiers[INDEX_SCREEN_HEIGHT] = qualifier;
- }
-
- @Nullable
- public ScreenHeightQualifier getScreenHeightQualifier() {
- return (ScreenHeightQualifier) mQualifiers[INDEX_SCREEN_HEIGHT];
- }
-
- public void setScreenSizeQualifier(ScreenSizeQualifier qualifier) {
- mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = qualifier;
- }
-
- @Nullable
- public ScreenSizeQualifier getScreenSizeQualifier() {
- return (ScreenSizeQualifier)mQualifiers[INDEX_SCREEN_LAYOUT_SIZE];
- }
-
- public void setScreenRatioQualifier(ScreenRatioQualifier qualifier) {
- mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
- }
-
- @Nullable
- public ScreenRatioQualifier getScreenRatioQualifier() {
- return (ScreenRatioQualifier)mQualifiers[INDEX_SCREEN_RATIO];
- }
-
- public void setScreenRoundQualifier(ScreenRoundQualifier qualifier) {
- mQualifiers[INDEX_SCREEN_ROUND] = qualifier;
- }
-
- @Nullable
- public ScreenRoundQualifier getScreenRoundQualifier() {
- return (ScreenRoundQualifier)mQualifiers[INDEX_SCREEN_ROUND];
- }
-
- public void setScreenOrientationQualifier(ScreenOrientationQualifier qualifier) {
- mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
- }
-
- @Nullable
- public ScreenOrientationQualifier getScreenOrientationQualifier() {
- return (ScreenOrientationQualifier)mQualifiers[INDEX_SCREEN_ORIENTATION];
- }
-
- public void setUiModeQualifier(UiModeQualifier qualifier) {
- mQualifiers[INDEX_UI_MODE] = qualifier;
- }
-
- @Nullable
- public UiModeQualifier getUiModeQualifier() {
- return (UiModeQualifier)mQualifiers[INDEX_UI_MODE];
- }
-
- public void setNightModeQualifier(NightModeQualifier qualifier) {
- mQualifiers[INDEX_NIGHT_MODE] = qualifier;
- }
-
- @Nullable
- public NightModeQualifier getNightModeQualifier() {
- return (NightModeQualifier)mQualifiers[INDEX_NIGHT_MODE];
- }
-
- public void setDensityQualifier(DensityQualifier qualifier) {
- mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
- }
-
- @Nullable
- public DensityQualifier getDensityQualifier() {
- return (DensityQualifier)mQualifiers[INDEX_PIXEL_DENSITY];
- }
-
- public void setTouchTypeQualifier(TouchScreenQualifier qualifier) {
- mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
- }
-
- @Nullable
- public TouchScreenQualifier getTouchTypeQualifier() {
- return (TouchScreenQualifier)mQualifiers[INDEX_TOUCH_TYPE];
- }
-
- public void setKeyboardStateQualifier(KeyboardStateQualifier qualifier) {
- mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
- }
-
- @Nullable
- public KeyboardStateQualifier getKeyboardStateQualifier() {
- return (KeyboardStateQualifier)mQualifiers[INDEX_KEYBOARD_STATE];
- }
-
- public void setTextInputMethodQualifier(TextInputMethodQualifier qualifier) {
- mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
- }
-
- @Nullable
- public TextInputMethodQualifier getTextInputMethodQualifier() {
- return (TextInputMethodQualifier)mQualifiers[INDEX_TEXT_INPUT_METHOD];
- }
-
- public void setNavigationStateQualifier(NavigationStateQualifier qualifier) {
- mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
- }
-
- @Nullable
- public NavigationStateQualifier getNavigationStateQualifier() {
- return (NavigationStateQualifier)mQualifiers[INDEX_NAVIGATION_STATE];
- }
-
- public void setNavigationMethodQualifier(NavigationMethodQualifier qualifier) {
- mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
- }
-
- @Nullable
- public NavigationMethodQualifier getNavigationMethodQualifier() {
- return (NavigationMethodQualifier)mQualifiers[INDEX_NAVIGATION_METHOD];
- }
-
- public void setScreenDimensionQualifier(ScreenDimensionQualifier qualifier) {
- mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
- }
-
- @Nullable
- public ScreenDimensionQualifier getScreenDimensionQualifier() {
- return (ScreenDimensionQualifier)mQualifiers[INDEX_SCREEN_DIMENSION];
- }
-
- public void setVersionQualifier(VersionQualifier qualifier) {
- mQualifiers[INDEX_VERSION] = qualifier;
- }
-
- @Nullable
- public VersionQualifier getVersionQualifier() {
- return (VersionQualifier)mQualifiers[INDEX_VERSION];
- }
-
- /**
- * Normalize a folder configuration based on the API level of its qualifiers
- */
- public void normalize() {
- int minSdk = 1;
- for (ResourceQualifier qualifier : mQualifiers) {
- if (qualifier != null) {
- int min = qualifier.since();
- if (min > minSdk) {
- minSdk = min;
- }
- }
- }
-
- if (minSdk == 1) {
- return;
- }
-
- if (mQualifiers[INDEX_VERSION] == null ||
- ((VersionQualifier)mQualifiers[INDEX_VERSION]).getVersion() < minSdk) {
- mQualifiers[INDEX_VERSION] = new VersionQualifier(minSdk);
- }
- }
-
- /**
- * Updates the {@link SmallestScreenWidthQualifier}, {@link ScreenWidthQualifier}, and
- * {@link ScreenHeightQualifier} based on the (required) values of
- * {@link ScreenDimensionQualifier} {@link DensityQualifier}, and
- * {@link ScreenOrientationQualifier}.
- *
- * Also the density cannot be {@link Density#NODPI} as it's not valid on a device.
- */
- public void updateScreenWidthAndHeight() {
-
- ResourceQualifier sizeQ = mQualifiers[INDEX_SCREEN_DIMENSION];
- ResourceQualifier densityQ = mQualifiers[INDEX_PIXEL_DENSITY];
- ResourceQualifier orientQ = mQualifiers[INDEX_SCREEN_ORIENTATION];
-
- if (sizeQ != null && densityQ != null && orientQ != null) {
- Density density = ((DensityQualifier) densityQ).getValue();
- if (density == Density.NODPI || density == Density.ANYDPI) {
- return;
- }
-
- ScreenOrientation orientation = ((ScreenOrientationQualifier) orientQ).getValue();
-
- int size1 = ((ScreenDimensionQualifier) sizeQ).getValue1();
- int size2 = ((ScreenDimensionQualifier) sizeQ).getValue2();
-
- // make sure size1 is the biggest (should be the case, but make sure)
- if (size1 < size2) {
- int a = size1;
- size1 = size2;
- size2 = a;
- }
-
- // compute the dp. round them up since we want -w480dp to match a 480.5dp screen
- int dp1 = (int) Math.ceil(size1 * Density.DEFAULT_DENSITY / density.getDpiValue());
- int dp2 = (int) Math.ceil(size2 * Density.DEFAULT_DENSITY / density.getDpiValue());
-
- setSmallestScreenWidthQualifier(new SmallestScreenWidthQualifier(dp2));
-
- switch (orientation) {
- case PORTRAIT:
- setScreenWidthQualifier(new ScreenWidthQualifier(dp2));
- setScreenHeightQualifier(new ScreenHeightQualifier(dp1));
- break;
- case LANDSCAPE:
- setScreenWidthQualifier(new ScreenWidthQualifier(dp1));
- setScreenHeightQualifier(new ScreenHeightQualifier(dp2));
- break;
- case SQUARE:
- setScreenWidthQualifier(new ScreenWidthQualifier(dp2));
- setScreenHeightQualifier(new ScreenHeightQualifier(dp2));
- break;
- }
- }
- }
-
- /**
- * Returns whether an object is equals to the receiver.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj == this) {
- return true;
- }
-
- if (obj instanceof FolderConfiguration) {
- FolderConfiguration fc = (FolderConfiguration)obj;
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- ResourceQualifier qualifier = mQualifiers[i];
- ResourceQualifier fcQualifier = fc.mQualifiers[i];
- if (qualifier != null) {
- if (!qualifier.equals(fcQualifier)) {
- return false;
- }
- } else if (fcQualifier != null) {
- return false;
- }
- }
-
- return true;
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- return toString().hashCode();
- }
-
- /**
- * Returns whether the Configuration has only default values.
- */
- public boolean isDefault() {
- for (ResourceQualifier irq : mQualifiers) {
- if (irq != null) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Returns the name of a folder with the configuration.
- */
- @NonNull
- public String getFolderName(@NonNull ResourceFolderType folder) {
- StringBuilder result = new StringBuilder(folder.getName());
-
- for (ResourceQualifier qualifier : mQualifiers) {
- if (qualifier != null) {
- String segment = qualifier.getFolderSegment();
- if (segment != null && !segment.isEmpty()) {
- result.append(SdkConstants.RES_QUALIFIER_SEP);
- result.append(segment);
- }
- }
- }
-
- return result.toString();
- }
-
- /**
- * Returns the folder configuration as a unique key
- */
- @NonNull
- public String getUniqueKey() {
- StringBuilder result = new StringBuilder(100);
-
- for (ResourceQualifier qualifier : mQualifiers) {
- if (qualifier != null) {
- String segment = qualifier.getFolderSegment();
- if (segment != null && !segment.isEmpty()) {
- result.append(SdkConstants.RES_QUALIFIER_SEP);
- result.append(segment);
- }
- }
- }
-
- return result.toString();
- }
-
- /**
- * Returns {@link #toDisplayString()}.
- */
- @NonNull
- @Override
- public String toString() {
- return toDisplayString();
- }
-
- /**
- * Returns a string valid for display purpose.
- */
- @NonNull
- public String toDisplayString() {
- if (isDefault()) {
- return "default";
- }
-
- StringBuilder result = null;
- int index = 0;
- ResourceQualifier qualifier;
-
- while (index < INDEX_COUNT) {
- qualifier = mQualifiers[index++];
- if (qualifier != null) {
- if (result == null) {
- result = new StringBuilder();
- } else {
- result.append(", "); //$NON-NLS-1$
- }
- result.append(qualifier.getLongDisplayValue());
-
- }
- }
-
- return result == null ? "" : result.toString();
- }
-
- /**
- * Returns a string for display purposes which uses only the short names of the qualifiers
- */
- @NonNull
- public String toShortDisplayString() {
- if (isDefault()) {
- return "default";
- }
-
- StringBuilder result = new StringBuilder(100);
- int index = 0;
-
- while (index < INDEX_COUNT) {
- ResourceQualifier qualifier = mQualifiers[index++];
- if (qualifier != null) {
- if (result.length() > 0) {
- result.append(',');
- }
- result.append(qualifier.getShortDisplayValue());
- }
- }
-
- return result.toString();
- }
-
- @Override
- public int compareTo(@NonNull FolderConfiguration folderConfig) {
- // default are always at the top.
- if (isDefault()) {
- if (folderConfig.isDefault()) {
- return 0;
- }
- return -1;
- }
-
- // now we compare the qualifiers
- for (int i = 0 ; i < INDEX_COUNT; i++) {
- ResourceQualifier qualifier1 = mQualifiers[i];
- ResourceQualifier qualifier2 = folderConfig.mQualifiers[i];
-
- if (qualifier1 == null) {
- if (qualifier2 != null) {
- return -1;
- }
- } else {
- if (qualifier2 == null) {
- return 1;
- } else {
- int result = qualifier1.compareTo(qualifier2);
-
- if (result == 0) {
- continue;
- }
-
- return result;
- }
- }
- }
-
- // if we arrive here, all the qualifier matches
- return 0;
- }
-
- /**
- * Returns the best matching {@link Configurable} for this configuration.
- *
- * @param configurables the list of {@link Configurable} to choose from.
- *
- * @return an item from the given list of {@link Configurable} or null.
- *
- * See http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
- */
- @Nullable
- public Configurable findMatchingConfigurable(@Nullable List<? extends Configurable> configurables) {
- // Because we skip qualifiers where reference configuration doesn't have a valid qualifier,
- // we can end up with more than one match. In this case, we just take the first one.
- List<Configurable> matches = findMatchingConfigurables(configurables);
- return matches.isEmpty() ? null : matches.get(0);
- }
-
- /**
- * Tries to eliminate as many {@link Configurable}s as possible. It skips the
- * {@link ResourceQualifier} if it's not valid and assumes that all resources match it.
- *
- * @param configurables the list of {@code Configurable} to choose from.
- *
- * @return a list of items from the above list. This may be empty.
- */
- @NonNull
- public List<Configurable> findMatchingConfigurables(
- @Nullable List<? extends Configurable> configurables) {
- if (configurables == null) {
- return Collections.emptyList();
- }
-
- //
- // 1: eliminate resources that contradict the reference configuration
- // 2: pick next qualifier type
- // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4.
- // 4: eliminate resources that don't use this qualifier.
- // 5: if more than one resource left, go back to 2.
- //
- // The precedence of the qualifiers is more important than the number of qualifiers that
- // exactly match the device.
-
- // 1: eliminate resources that contradict
- ArrayList<Configurable> matchingConfigurables = new ArrayList<Configurable>();
- for (Configurable res : configurables) {
- final FolderConfiguration configuration = res.getConfiguration();
- if (configuration != null && configuration.isMatchFor(this)) {
- matchingConfigurables.add(res);
- }
- }
-
- // if there is at most one match, just take it
- if (matchingConfigurables.size() < 2) {
- return matchingConfigurables;
- }
-
- // 2. Loop on the qualifiers, and eliminate matches
- final int count = getQualifierCount();
- for (int q = 0 ; q < count ; q++) {
- // look to see if one configurable has this qualifier.
- // At the same time also record the best match value for the qualifier (if applicable).
-
- // The reference value, to find the best match.
- // Note that this qualifier could be null. In which case any qualifier found in the
- // possible match, will all be considered best match.
- ResourceQualifier referenceQualifier = getQualifier(q);
-
- // If referenceQualifier is null, we don't eliminate resources based on it.
- if (referenceQualifier == null) {
- continue;
- }
-
- boolean found = false;
- ResourceQualifier bestMatch = null; // this is to store the best match.
- for (Configurable configurable : matchingConfigurables) {
- ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q);
- if (qualifier != null) {
- // set the flag.
- found = true;
-
- // Now check for a best match. If the reference qualifier is null ,
- // any qualifier is a "best" match (we don't need to record all of them.
- // Instead the non compatible ones are removed below)
- if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
- bestMatch = qualifier;
- }
- }
- }
-
- // 4. If a configurable has a qualifier at the current index, remove all the ones that
- // do not have one, or whose qualifier value does not equal the best match found above
- // unless there's no reference qualifier, in which case they are all considered
- // "best" match.
- if (found) {
- for (int i = 0 ; i < matchingConfigurables.size(); ) {
- Configurable configurable = matchingConfigurables.get(i);
- FolderConfiguration configuration = configurable.getConfiguration();
- ResourceQualifier qualifier = configuration.getQualifier(q);
-
- if (qualifier == null) {
- // this resources has no qualifier of this type: rejected.
- matchingConfigurables.remove(configurable);
- } else if (bestMatch != null && !bestMatch.equals(qualifier)) {
- // there's a reference qualifier and there is a better match for it than
- // this resource, so we reject it.
- matchingConfigurables.remove(configurable);
- } else {
- // looks like we keep this resource, move on to the next one.
- //noinspection AssignmentToForLoopParameter
- i++;
- }
- }
-
- // at this point we may have run out of matching resources before going
- // through all the qualifiers.
- if (matchingConfigurables.size() < 2) {
- break;
- }
- }
- }
-
- // We've exhausted all the qualifiers. If we still have matching ones left, return all.
- return matchingConfigurables;
- }
-
- /**
- * Returns whether the configuration is a match for the given reference config.
- * <p/>A match means that, for each qualifier of this config
- * <ul>
- * <li>The reference config has no value set
- * <li>or, the qualifier of the reference config is a match. Depending on the qualifier type
- * this does not mean the same exact value.</li>
- * </ul>
- * @param referenceConfig The reference configuration to test against.
- * @return true if the configuration matches.
- */
- public boolean isMatchFor(@Nullable FolderConfiguration referenceConfig) {
- if (referenceConfig == null) {
- return false;
- }
-
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- ResourceQualifier testQualifier = mQualifiers[i];
- ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
-
- // it's only a non match if both qualifiers are non-null, and they don't match.
- if (testQualifier != null && referenceQualifier != null &&
- !testQualifier.isMatchFor(referenceQualifier)) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Returns the index of the first non null {@link ResourceQualifier} starting at index
- * <var>startIndex</var>
- * @param startIndex
- * @return -1 if no qualifier was found.
- */
- public int getHighestPriorityQualifier(int startIndex) {
- for (int i = startIndex ; i < INDEX_COUNT ; i++) {
- if (mQualifiers[i] != null) {
- return i;
- }
- }
-
- return -1;
- }
-
- /**
- * Create default qualifiers.
- * <p/>This creates qualifiers with no values for all indices.
- */
- public void createDefault() {
- mQualifiers[INDEX_COUNTRY_CODE] = new CountryCodeQualifier();
- mQualifiers[INDEX_NETWORK_CODE] = new NetworkCodeQualifier();
- mQualifiers[INDEX_LOCALE] = new LocaleQualifier();
- mQualifiers[INDEX_LAYOUT_DIR] = new LayoutDirectionQualifier();
- mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = new SmallestScreenWidthQualifier();
- mQualifiers[INDEX_SCREEN_WIDTH] = new ScreenWidthQualifier();
- mQualifiers[INDEX_SCREEN_HEIGHT] = new ScreenHeightQualifier();
- mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = new ScreenSizeQualifier();
- mQualifiers[INDEX_SCREEN_RATIO] = new ScreenRatioQualifier();
- mQualifiers[INDEX_SCREEN_ROUND] = new ScreenRoundQualifier();
- mQualifiers[INDEX_SCREEN_ORIENTATION] = new ScreenOrientationQualifier();
- mQualifiers[INDEX_UI_MODE] = new UiModeQualifier();
- mQualifiers[INDEX_NIGHT_MODE] = new NightModeQualifier();
- mQualifiers[INDEX_PIXEL_DENSITY] = new DensityQualifier();
- mQualifiers[INDEX_TOUCH_TYPE] = new TouchScreenQualifier();
- mQualifiers[INDEX_KEYBOARD_STATE] = new KeyboardStateQualifier();
- mQualifiers[INDEX_TEXT_INPUT_METHOD] = new TextInputMethodQualifier();
- mQualifiers[INDEX_NAVIGATION_STATE] = new NavigationStateQualifier();
- mQualifiers[INDEX_NAVIGATION_METHOD] = new NavigationMethodQualifier();
- mQualifiers[INDEX_SCREEN_DIMENSION] = new ScreenDimensionQualifier();
- mQualifiers[INDEX_VERSION] = new VersionQualifier();
- }
-
- /**
- * Returns an array of all the non null qualifiers.
- */
- @NonNull
- public ResourceQualifier[] getQualifiers() {
- int count = 0;
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- if (mQualifiers[i] != null) {
- count++;
- }
- }
-
- ResourceQualifier[] array = new ResourceQualifier[count];
- int index = 0;
- for (int i = 0 ; i < INDEX_COUNT ; i++) {
- if (mQualifiers[i] != null) {
- array[index++] = mQualifiers[i];
- }
- }
-
- return array;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LocaleQualifier.java b/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LocaleQualifier.java
deleted file mode 100644
index 26fddd0..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LocaleQualifier.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.resources.configuration;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.google.common.base.Splitter;
-
-import java.util.Iterator;
-import java.util.Locale;
-
-/**
- * A locale qualifier, which can be constructed from:
- * <ul>
- * <li>A plain 2-letter language descriptor</li>
- * <li>A 2-letter language descriptor followed by a -r 2 letter region qualifier</li>
- * <li>A plain 3-letter language descriptor</li>
- * <li>A 3-letter language descriptor followed by a -r 2 letter region qualifier</li>
- * <li>A BCP 47 language tag. The BCP-47 tag uses + instead of - as separators,
- * and has the prefix b+. Therefore, the BCP-47 tag "zh-Hans-CN" would be
- * written as "b+zh+Hans+CN" instead.</li>
- * </ul>
- */
-public final class LocaleQualifier extends ResourceQualifier {
- public static final String FAKE_VALUE = "__"; //$NON-NLS-1$
- public static final String NAME = "Locale";
- // TODO: Case insensitive check!
- public static final String BCP_47_PREFIX = "b+"; //$NON-NLS-1$
-
- @NonNull private String mFull;
- @NonNull private String mLanguage;
- @Nullable private String mRegion;
- @Nullable private String mScript;
-
- public LocaleQualifier() {
- mFull = "";
- }
-
- public LocaleQualifier(@NonNull String language) {
- assert language.length() == 2 || language.length() == 3;
- mLanguage = language;
- mFull = language;
- }
-
- public LocaleQualifier(@Nullable String full, @NonNull String language,
- @Nullable String region, @Nullable String script) {
- if (full == null) {
- if (region != null && region.length() == 3 || script != null) {
- StringBuilder sb = new StringBuilder(BCP_47_PREFIX);
- sb.append(language);
- if (region != null) {
- sb.append('+');
- sb.append(region);
- }
- if (script != null) {
- sb.append('+');
- sb.append(script);
- }
- full = sb.toString();
- } else if (region != null) {
- full = language + "-r" + region;
- } else {
- full = language;
- }
- }
- mFull = full;
- mLanguage = language;
- mRegion = region;
- mScript = script;
- }
-
- public static boolean isRegionSegment(@NonNull String segment) {
- return (segment.startsWith("r") || segment.startsWith("R")) && segment.length() == 3
- && Character.isLetter(segment.charAt(0)) && Character.isLetter(segment.charAt(1));
- }
-
- /**
- * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
- * <code>null</code> is returned.
- * @param segment the folder segment from which to create a qualifier.
- * @return a new {@link LocaleQualifier} object or <code>null</code>
- */
- @Nullable
- public static LocaleQualifier getQualifier(@NonNull String segment) {
- int length = segment.length();
- if (length == 2
- && Character.isLetter(segment.charAt(0))
- && Character.isLetter(segment.charAt(1))) { // to make sure we don't match e.g. "v4"
- segment = segment.toLowerCase(Locale.US);
- return new LocaleQualifier(segment, segment, null, null);
- } else if (length == 3
- && Character.isLetter(segment.charAt(0))
- && Character.isLetter(segment.charAt(1))
- && Character.isLetter(segment.charAt(2))) {
- segment = segment.toLowerCase(Locale.US);
- if ("car".equals(segment)) {
- // Special case: "car" is a valid 3 letter language code, but
- // it conflicts with the (much older) UI mode constant for
- // car dock mode, so this specific language string should not be recognized
- // as a 3 letter language string; it should match car dock mode instead.
- return null;
- }
- return new LocaleQualifier(segment, segment, null, null);
- } else if (segment.startsWith(BCP_47_PREFIX)) {
- return parseBcp47(segment);
- } else if (length == 6 && segment.charAt(2) == '-'
- && Character.toLowerCase(segment.charAt(3)) == 'r'
- && Character.isLetter(segment.charAt(0))
- && Character.isLetter(segment.charAt(1))
- && Character.isLetter(segment.charAt(4))
- && Character.isLetter(segment.charAt(5))) {
- String language = new String(new char[] {
- Character.toLowerCase(segment.charAt(0)),
- Character.toLowerCase(segment.charAt(1))
- });
- String region = new String(new char[] {
- Character.toUpperCase(segment.charAt(4)),
- Character.toUpperCase(segment.charAt(5))
- });
-
- return new LocaleQualifier(language + "-r" + region, language, region, null);
- } else if (length == 7 && segment.charAt(3) == '-'
- && Character.toLowerCase(segment.charAt(4)) == 'r'
- && Character.isLetter(segment.charAt(0))
- && Character.isLetter(segment.charAt(1))
- && Character.isLetter(segment.charAt(2))
- && Character.isLetter(segment.charAt(5))
- && Character.isLetter(segment.charAt(6))) {
- String language = new String(new char[] {
- Character.toLowerCase(segment.charAt(0)),
- Character.toLowerCase(segment.charAt(1)),
- Character.toLowerCase(segment.charAt(2))
- });
- String region = new String(new char[] {
- Character.toUpperCase(segment.charAt(5)),
- Character.toUpperCase(segment.charAt(6))
- });
- return new LocaleQualifier(language + "-r" + region, language, region, null);
- }
- return null;
- }
-
- /** Given a BCP-47 string, normalizes the case to the recommended casing */
- @NonNull
- public static String normalizeCase(@NonNull String segment) {
- /* According to the BCP-47 spec:
- o [ISO639-1] recommends that language codes be written in lowercase
- ('mn' Mongolian).
-
- o [ISO15924] recommends that script codes use lowercase with the
- initial letter capitalized ('Cyrl' Cyrillic).
-
- o [ISO3166-1] recommends that country codes be capitalized ('MN'
- Mongolia).
-
-
- An implementation can reproduce this format without accessing the
- registry as follows. All subtags, including extension and private
- use subtags, use lowercase letters with two exceptions: two-letter
- and four-letter subtags that neither appear at the start of the tag
- nor occur after singletons. Such two-letter subtags are all
- uppercase (as in the tags "en-CA-x-ca" or "sgn-BE-FR") and four-
- letter subtags are titlecase (as in the tag "az-Latn-x-latn").
- */
- if (isNormalizedCase(segment)) {
- return segment;
- }
-
- StringBuilder sb = new StringBuilder(segment.length());
- if (segment.startsWith(BCP_47_PREFIX)) {
- sb.append(BCP_47_PREFIX);
- assert segment.startsWith(BCP_47_PREFIX);
- int segmentBegin = BCP_47_PREFIX.length();
- int segmentLength = segment.length();
- int start = segmentBegin;
-
- int lastLength = -1;
- while (start < segmentLength) {
- if (start != segmentBegin) {
- sb.append('+');
- }
- int end = segment.indexOf('+', start);
- if (end == -1) {
- end = segmentLength;
- }
- int length = end - start;
- if ((length != 2 && length != 4) || start == segmentBegin || lastLength == 1) {
- for (int i = start; i < end; i++) {
- sb.append(Character.toLowerCase(segment.charAt(i)));
- }
- } else if (length == 2) {
- for (int i = start; i < end; i++) {
- sb.append(Character.toUpperCase(segment.charAt(i)));
- }
- } else {
- assert length == 4 : length;
- sb.append(Character.toUpperCase(segment.charAt(start)));
- for (int i = start + 1; i < end; i++) {
- sb.append(Character.toLowerCase(segment.charAt(i)));
- }
- }
-
- lastLength = length;
- start = end + 1;
- }
- } else if (segment.length() == 6) {
- // Language + region: ll-rRR
- sb.append(Character.toLowerCase(segment.charAt(0)));
- sb.append(Character.toLowerCase(segment.charAt(1)));
- sb.append(segment.charAt(2)); // -
- sb.append(Character.toLowerCase(segment.charAt(3))); // r
- sb.append(Character.toUpperCase(segment.charAt(4)));
- sb.append(Character.toUpperCase(segment.charAt(5)));
- } else if (segment.length() == 7) {
- // Language + region: lll-rRR
- sb.append(Character.toLowerCase(segment.charAt(0)));
- sb.append(Character.toLowerCase(segment.charAt(1)));
- sb.append(Character.toLowerCase(segment.charAt(2)));
- sb.append(segment.charAt(3)); // -
- sb.append(Character.toLowerCase(segment.charAt(4))); // r
- sb.append(Character.toUpperCase(segment.charAt(5)));
- sb.append(Character.toUpperCase(segment.charAt(6)));
- } else {
- sb.append(segment.toLowerCase(Locale.US));
- }
-
- return sb.toString();
- }
-
- /**
- * Given a BCP-47 string, determines whether the string is already
- * capitalized correctly (where "correct" means for readability; all strings
- * should be compared case insensitively)
- */
- @VisibleForTesting
- static boolean isNormalizedCase(@NonNull String segment) {
- if (segment.startsWith(BCP_47_PREFIX)) {
- assert segment.startsWith(BCP_47_PREFIX);
- int segmentBegin = BCP_47_PREFIX.length();
- int segmentLength = segment.length();
- int start = segmentBegin;
-
- int lastLength = -1;
- while (start < segmentLength) {
- int end = segment.indexOf('+', start);
- if (end == -1) {
- end = segmentLength;
- }
- int length = end - start;
- if ((length != 2 && length != 4) || start == segmentBegin || lastLength == 1) {
- if (isNotLowerCase(segment, start, end)) {
- return false;
- }
- } else if (length == 2) {
- if (isNotUpperCase(segment, start, end)) {
- return false;
- }
- } else {
- assert length == 4 : length;
- if (isNotUpperCase(segment, start, start + 1)) {
- return false;
- }
- if (isNotLowerCase(segment, start + 1, end)) {
- return false;
- }
- }
-
- lastLength = length;
- start = end + 1;
- }
-
- return true;
- } else if (segment.length() == 2) {
- // Just a language: ll
- return Character.isLowerCase(segment.charAt(0))
- && Character.isLowerCase(segment.charAt(1));
- } else if (segment.length() == 3) {
- // Just a language: lll
- return Character.isLowerCase(segment.charAt(0))
- && Character.isLowerCase(segment.charAt(1))
- && Character.isLowerCase(segment.charAt(2));
- } else if (segment.length() == 6) {
- // Language + region: ll-rRR
- return Character.isLowerCase(segment.charAt(0))
- && Character.isLowerCase(segment.charAt(1))
- && Character.isLowerCase(segment.charAt(3))
- && Character.isUpperCase(segment.charAt(4))
- && Character.isUpperCase(segment.charAt(5));
- } else if (segment.length() == 7) {
- // Language + region: lll-rRR
- return Character.isLowerCase(segment.charAt(0))
- && Character.isLowerCase(segment.charAt(1))
- && Character.isLowerCase(segment.charAt(2))
- && Character.isLowerCase(segment.charAt(4))
- && Character.isUpperCase(segment.charAt(5))
- && Character.isUpperCase(segment.charAt(6));
- }
-
- return true;
- }
-
- private static boolean isNotLowerCase(@NonNull String segment, int start, int end) {
- for (int i = start; i < end; i++) {
- if (Character.isUpperCase(segment.charAt(i))) {
- return true;
- }
- }
-
- return false;
- }
-
- private static boolean isNotUpperCase(@NonNull String segment, int start, int end) {
- for (int i = start; i < end; i++) {
- if (Character.isLowerCase(segment.charAt(i))) {
- return true;
- }
- }
-
- return false;
- }
-
- @NonNull
- public String getValue() {
- return mFull;
- }
-
- @Override
- public String getName() {
- return NAME;
- }
-
- @Override
- public String getShortName() {
- return NAME;
- }
-
- @Override
- public int since() {
- // This was added in Lollipop, but you can for example write b+en+US and aapt handles it
- // compatibly so we don't want to normalize this in normalize() to append -v21 etc
- return 1;
- }
-
- @Override
- public boolean isValid() {
- //noinspection StringEquality
- return mFull != FAKE_VALUE;
- }
-
- @Override
- public boolean hasFakeValue() {
- //noinspection StringEquality
- return mFull == FAKE_VALUE;
- }
-
- public boolean hasLanguage() {
- return !FAKE_VALUE.equals(mLanguage);
- }
-
- public boolean hasRegion() {
- return mRegion != null && !FAKE_VALUE.equals(mRegion);
- }
-
- @Override
- public boolean checkAndSet(@NonNull String value, @NonNull FolderConfiguration config) {
- LocaleQualifier qualifier = getQualifier(value);
- if (qualifier != null) {
- config.setLocaleQualifier(qualifier);
- return true;
- }
-
- return false;
- }
-
- /**
- * Used only when constructing the qualifier, don't use after it's been assigned to a
- * {@link FolderConfiguration}.
- */
- void setRegionSegment(@NonNull String segment) {
- assert segment.length() == 3 : segment;
- mRegion = new String(new char[] {
- Character.toUpperCase(segment.charAt(1)),
- Character.toUpperCase(segment.charAt(2))
- });
- mFull = mLanguage + "-r" + mRegion;
- }
-
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- LocaleQualifier qualifier = (LocaleQualifier) o;
-
- if (!mFull.equals(qualifier.mFull)) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return mFull.hashCode();
- }
-
- /**
- * Returns the string used to represent this qualifier in the folder name.
- */
- @Override
- public String getFolderSegment() {
- return mFull;
- }
-
- /** BCP 47 tag or "language,region", or language */
- @Override
- public String getShortDisplayValue() {
- if (mFull.startsWith(BCP_47_PREFIX)) {
- return mFull;
- } else if (mRegion != null) {
- return mLanguage + ',' + mRegion;
- } else {
- return mLanguage;
- }
- }
-
- /** Tag: language, or language-region, or BCP-47 tag */
- public String getTag() {
- if (mFull.startsWith(BCP_47_PREFIX)) {
- return mFull.substring(BCP_47_PREFIX.length()).replace('+','-');
- } else if (mRegion != null) {
- return mLanguage + '-' + mRegion;
- } else {
- return mLanguage;
- }
- }
-
- @Override
- public String getLongDisplayValue() {
- if (mFull.startsWith(BCP_47_PREFIX)) {
- return String.format("Locale %1$s", mFull);
- } else if (mRegion != null) {
- return String.format("Locale %1$s_%2$s", mLanguage, mRegion);
- } else //noinspection StringEquality
- if (mFull != FAKE_VALUE) {
- return String.format("Locale %1$s", mLanguage);
- }
-
- return ""; //$NON-NLS-1$
- }
-
- /**
- * Parse an Android BCP-47 string (which differs from BCP-47 in that
- * it has the prefix "b+" and the separator character has been changed from
- * - to +.
- *
- * @param qualifier the folder name to parse
- * @return a {@linkplain LocaleQualifier} holding the language, region and script
- * or null if not a valid Android BCP 47 tag
- */
- @Nullable
- public static LocaleQualifier parseBcp47(@NonNull String qualifier) {
- if (qualifier.startsWith(BCP_47_PREFIX)) {
- qualifier = normalizeCase(qualifier);
- Iterator<String> iterator = Splitter.on('+').split(qualifier).iterator();
- // Skip b+ prefix, already checked above
- iterator.next();
-
- if (iterator.hasNext()) {
- String language = iterator.next();
- String region = null;
- String script = null;
- if (language.length() >= 2 && language.length() <= 3) {
- if (iterator.hasNext()) {
- String next = iterator.next();
- if (next.length() == 4) {
- // Script specified; look for next
- script = next;
- if (iterator.hasNext()) {
- next = iterator.next();
- }
- } else if (next.length() >= 5) {
- // Past region: specifying a variant
- return new LocaleQualifier(qualifier, language, null, null);
- }
- if (next.length() >= 2 && next.length() <= 3) {
- region = next;
- }
- }
- if (script == null && (region == null || region.length() == 2)
- && !iterator.hasNext()) {
- // Switch from BCP 47 syntax to plain
- qualifier = language.toLowerCase(Locale.US);
- if (region != null) {
- qualifier = qualifier + "-r" + region.toUpperCase(Locale.US);
- }
- }
- return new LocaleQualifier(qualifier, language, region, script);
- }
- }
- }
-
- return null;
- }
-
- @NonNull
- public String getLanguage() {
- return mLanguage;
- }
-
- @Nullable
- public String getRegion() {
- return mRegion;
- }
-
- @Nullable
- public String getScript() {
- return mScript;
- }
-
- @NonNull
- public String getFull() {
- return mFull;
- }
-
- @Override
- public boolean isMatchFor(ResourceQualifier qualifier) {
- if (qualifier instanceof LocaleQualifier) {
- LocaleQualifier other = (LocaleQualifier)qualifier;
- if (!mLanguage.equals(other.mLanguage)) {
- return false;
- }
-
- if (mRegion != null && other.mRegion != null && !mRegion.equals(other.mRegion)) {
- return false;
- }
-
- if (mScript != null && other.mScript != null && !mScript.equals(other.mScript)) {
- return false;
- }
-
- return true;
- }
- return false;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java b/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
deleted file mode 100644
index e8dcd8b..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2009 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.ide.common.resources.configuration;
-
-import com.android.resources.ResourceEnum;
-import com.android.resources.ScreenSize;
-
-/**
- * Resource Qualifier for Screen Size. Size can be "small", "normal", "large" and "x-large"
- */
-public class ScreenSizeQualifier extends EnumBasedResourceQualifier {
-
- public static final String NAME = "Screen Size";
-
- private ScreenSize mValue = null;
-
-
- public ScreenSizeQualifier() {
- }
-
- public ScreenSizeQualifier(ScreenSize value) {
- mValue = value;
- }
-
- public ScreenSize getValue() {
- return mValue;
- }
-
- @Override
- ResourceEnum getEnumValue() {
- return mValue;
- }
-
- @Override
- public String getName() {
- return NAME;
- }
-
- @Override
- public String getShortName() {
- return "Size";
- }
-
- @Override
- public int since() {
- return 4;
- }
-
- @Override
- public boolean checkAndSet(String value, FolderConfiguration config) {
- ScreenSize size = ScreenSize.getEnum(value);
- if (size != null) {
- ScreenSizeQualifier qualifier = new ScreenSizeQualifier(size);
- config.setScreenSizeQualifier(qualifier);
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean isMatchFor(ResourceQualifier qualifier) {
- // This is a match only if the screen size is smaller than the qualifier's screen size.
- if (qualifier instanceof ScreenSizeQualifier) {
- int qualifierIndex = ScreenSize.getIndex(((ScreenSizeQualifier) qualifier).mValue);
- int index = ScreenSize.getIndex(mValue);
- if (index <= qualifierIndex) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
- if (compareTo == null) {
- return true;
- }
-
- ScreenSizeQualifier compareQ = (ScreenSizeQualifier) compareTo;
- int thisIndex = ScreenSize.getIndex(mValue);
- int compareIndex = ScreenSize.getIndex(compareQ.mValue);
-
- // Return true if this size is larger than reference size. Since isMatchFor() is called
- // before, it is guaranteed that the size will not be larger than the reference.
- return thisIndex > compareIndex;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java b/base/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java
deleted file mode 100644
index 6ecf95e..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.signing;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.utils.GrabProcessOutput;
-import com.android.utils.GrabProcessOutput.IProcessOutput;
-import com.android.utils.GrabProcessOutput.Wait;
-import com.android.utils.ILogger;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.security.KeyStore;
-import java.security.KeyStore.PrivateKeyEntry;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-
-/**
- * A Helper to create and read keystore/keys.
- */
-public final class KeystoreHelper {
-
- // Certificate CN value. This is a hard-coded value for the debug key.
- // Android Market checks against this value in order to refuse applications signed with
- // debug keys.
- private static final String CERTIFICATE_DESC = "CN=Android Debug,O=Android,C=US";
-
-
- /**
- * Returns the location of the default debug keystore.
- *
- * @return The location of the default debug keystore.
- * @throws AndroidLocationException if the location cannot be computed
- */
- @NonNull
- public static String defaultDebugKeystoreLocation() throws AndroidLocationException {
- //this is guaranteed to either return a non null value (terminated with a platform
- // specific separator), or throw.
- String folder = AndroidLocation.getFolder();
- return folder + "debug.keystore";
- }
-
- /**
- * Creates a new debug store with the location, keyalias, and passwords specified in the
- * config.
- *
- * @param signingConfig The signing config
- * @param logger a logger object to receive the log of the creation.
- * @throws KeytoolException
- */
- public static boolean createDebugStore(@Nullable String storeType, @NonNull File storeFile,
- @NonNull String storePassword, @NonNull String keyPassword,
- @NonNull String keyAlias,
- @NonNull ILogger logger) throws KeytoolException {
-
- return createNewStore(storeType, storeFile, storePassword, keyPassword, keyAlias,
- CERTIFICATE_DESC, 30 /* validity*/, logger);
- }
-
- /**
- * Creates a new store
- *
- * @param signingConfig the Signing Configuration
- * @param description description
- * @param validityYears
- * @param logger
- * @throws KeytoolException
- */
- private static boolean createNewStore(
- @Nullable String storeType,
- @NonNull File storeFile,
- @NonNull String storePassword,
- @NonNull String keyPassword,
- @NonNull String keyAlias,
- @NonNull String description,
- int validityYears,
- @NonNull final ILogger logger)
- throws KeytoolException {
-
- // get the executable name of keytool depending on the platform.
- String os = System.getProperty("os.name");
-
- String keytoolCommand;
- if (os.startsWith("Windows")) {
- keytoolCommand = "keytool.exe";
- } else {
- keytoolCommand = "keytool";
- }
-
- String javaHome = System.getProperty("java.home");
-
- if (javaHome != null && !javaHome.isEmpty()) {
- keytoolCommand = javaHome + File.separator + "bin" + File.separator + keytoolCommand;
- }
-
- // create the command line to call key tool to build the key with no user input.
- ArrayList<String> commandList = new ArrayList<String>();
- commandList.add(keytoolCommand);
- commandList.add("-genkey");
- commandList.add("-alias");
- commandList.add(keyAlias);
- commandList.add("-keyalg");
- commandList.add("RSA");
- commandList.add("-dname");
- commandList.add(description);
- commandList.add("-validity");
- commandList.add(Integer.toString(validityYears * 365));
- commandList.add("-keypass");
- commandList.add(keyPassword);
- commandList.add("-keystore");
- commandList.add(storeFile.getAbsolutePath());
- commandList.add("-storepass");
- commandList.add(storePassword);
- if (storeType != null) {
- commandList.add("-storetype");
- commandList.add(storeType);
- }
-
- String[] commandArray = commandList.toArray(new String[commandList.size()]);
-
- // launch the command line process
- int result = 0;
- try {
- Process process = Runtime.getRuntime().exec(commandArray);
- result = GrabProcessOutput.grabProcessOutput(
- process,
- Wait.WAIT_FOR_READERS,
- new IProcessOutput() {
- @Override
- public void out(@Nullable String line) {
- if (line != null) {
- logger.info(line);
- }
- }
-
- @Override
- public void err(@Nullable String line) {
- if (line != null) {
- logger.error(null /*throwable*/, line);
- }
- }
- });
- } catch (Exception e) {
- // create the command line as one string for debugging purposes
- StringBuilder builder = new StringBuilder();
- boolean firstArg = true;
- for (String arg : commandArray) {
- boolean hasSpace = arg.indexOf(' ') != -1;
-
- if (firstArg) {
- firstArg = false;
- } else {
- builder.append(' ');
- }
-
- if (hasSpace) {
- builder.append('"');
- }
-
- builder.append(arg);
-
- if (hasSpace) {
- builder.append('"');
- }
- }
-
- throw new KeytoolException("Failed to create key: " + e.getMessage(),
- javaHome, builder.toString());
- }
-
- return result == 0;
- }
-
- /**
- * Returns the CertificateInfo for the given signing configuration.
- *
- * @return the certificate info if it could be loaded.
- * @throws KeytoolException If the password is wrong.
- * @throws FileNotFoundException If the store file cannot be found.
- */
- @NonNull
- public static CertificateInfo getCertificateInfo(@Nullable String storeType, @NonNull File storeFile,
- @NonNull String storePassword, @NonNull String keyPassword,
- @NonNull String keyAlias)
- throws KeytoolException, FileNotFoundException {
-
- try {
- KeyStore keyStore = KeyStore.getInstance(storeType != null ?
- storeType : KeyStore.getDefaultType());
-
- FileInputStream fis = new FileInputStream(storeFile);
- keyStore.load(fis, storePassword.toCharArray());
- fis.close();
-
- char[] keyPasswordArray = keyPassword.toCharArray();
- PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
- keyAlias, new KeyStore.PasswordProtection(keyPasswordArray));
-
- if (entry == null) {
- throw new KeytoolException(
- String.format(
- "No key with alias '%1$s' found in keystore %2$s",
- keyAlias,
- storeFile.getAbsolutePath()));
- }
-
- return new CertificateInfo(
- entry.getPrivateKey(),
- (X509Certificate) entry.getCertificate());
- } catch (FileNotFoundException e) {
- throw e;
- } catch (Exception e) {
- throw new KeytoolException(
- String.format("Failed to read key %1$s from store \"%2$s\": %3$s",
- keyAlias, storeFile, e.getMessage()),
- e);
- }
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/Svg2Vector.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/Svg2Vector.java
deleted file mode 100644
index c3a1512..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/Svg2Vector.java
+++ /dev/null
@@ -1,658 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.annotations.NonNull;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Sets;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.*;
-import java.util.HashSet;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Converts SVG to VectorDrawable's XML
- */
-public class Svg2Vector {
- private static Logger logger = Logger.getLogger(Svg2Vector.class.getSimpleName());
-
- public static final String SVG_POLYGON = "polygon";
- public static final String SVG_RECT = "rect";
- public static final String SVG_CIRCLE = "circle";
- public static final String SVG_LINE = "line";
- public static final String SVG_PATH = "path";
- public static final String SVG_GROUP = "g";
- public static final String SVG_TRANSFORM = "transform";
- public static final String SVG_WIDTH = "width";
- public static final String SVG_HEIGHT = "height";
- public static final String SVG_VIEW_BOX = "viewBox";
- public static final String SVG_STYLE = "style";
- public static final String SVG_DISPLAY = "display";
-
- public static final String SVG_D = "d";
- public static final String SVG_STROKE_COLOR = "stroke";
- public static final String SVG_STROKE_OPACITY = "stroke-opacity";
- public static final String SVG_STROKE_LINEJOINE = "stroke-linejoin";
- public static final String SVG_STROKE_LINECAP = "stroke-linecap";
- public static final String SVG_STROKE_WIDTH = "stroke-width";
- public static final String SVG_FILL_COLOR = "fill";
- public static final String SVG_FILL_OPACITY = "fill-opacity";
- public static final String SVG_OPACITY = "opacity";
- public static final String SVG_CLIP = "clip";
- public static final String SVG_POINTS = "points";
-
- public static final ImmutableMap<String, String> presentationMap =
- ImmutableMap.<String, String>builder()
- .put(SVG_STROKE_COLOR, "android:strokeColor")
- .put(SVG_STROKE_OPACITY, "android:strokeAlpha")
- .put(SVG_STROKE_LINEJOINE, "android:strokeLinejoin")
- .put(SVG_STROKE_LINECAP, "android:strokeLinecap")
- .put(SVG_STROKE_WIDTH, "android:strokeWidth")
- .put(SVG_FILL_COLOR, "android:fillColor")
- .put(SVG_FILL_OPACITY, "android:fillAlpha")
- .put(SVG_CLIP, "android:clip").put(SVG_OPACITY, "android:fillAlpha")
- .build();
-
- // List all the Svg nodes that we don't support. Categorized by the types.
- private static final HashSet<String> unsupportedSvgNodes = Sets.newHashSet(
- // Animation elements
- "animate", "animateColor", "animateMotion", "animateTransform", "mpath", "set",
- // Container elements
- "a", "defs", "glyph", "marker", "mask", "missing-glyph", "pattern", "switch", "symbol",
- // Filter primitive elements
- "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix",
- "feDiffuseLighting", "feDisplacementMap", "feFlood", "feFuncA", "feFuncB", "feFuncG",
- "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology",
- "feOffset", "feSpecularLighting", "feTile", "feTurbulence",
- // Font elements
- "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri",
- "hkern", "vkern",
- // Gradient elements
- "linearGradient", "radialGradient", "stop",
- // Graphics elements
- "ellipse", "polyline", "text", "use",
- // Light source elements
- "feDistantLight", "fePointLight", "feSpotLight",
- // Structural elements
- "defs", "symbol", "use",
- // Text content elements
- "altGlyph", "altGlyphDef", "altGlyphItem", "glyph", "glyphRef", "textPath", "text", "tref",
- "tspan",
- // Text content child elements
- "altGlyph", "textPath", "tref", "tspan",
- // Uncategorized elements
- "clipPath", "color-profile", "cursor", "filter", "foreignObject", "script", "view");
-
- @NonNull
- private static SvgTree parse(File f) throws Exception {
- SvgTree svgTree = new SvgTree();
- Document doc = svgTree.parse(f);
- NodeList nSvgNode;
-
- // Parse svg elements
- nSvgNode = doc.getElementsByTagName("svg");
- if (nSvgNode.getLength() != 1) {
- throw new IllegalStateException("Not a proper SVG file");
- }
- Node rootNode = nSvgNode.item(0);
- for (int i = 0; i < nSvgNode.getLength(); i++) {
- Node nNode = nSvgNode.item(i);
- if (nNode.getNodeType() == Node.ELEMENT_NODE) {
- parseDimension(svgTree, nNode);
- }
- }
-
- if (svgTree.viewBox == null) {
- svgTree.logErrorLine("Missing \"viewBox\" in <svg> element", rootNode, SvgTree.SvgLogLevel.ERROR);
- return svgTree;
- }
-
- if ((svgTree.w == 0 || svgTree.h == 0) && svgTree.viewBox[2] > 0 && svgTree.viewBox[3] > 0) {
- svgTree.w = svgTree.viewBox[2];
- svgTree.h = svgTree.viewBox[3];
- }
-
- // Parse transformation information.
- // TODO: Properly handle transformation in the group level. In the "use" case, we treat
- // it as global for now.
- NodeList nUseTags;
- svgTree.matrix = new float[6];
- svgTree.matrix[0] = 1;
- svgTree.matrix[3] = 1;
-
- nUseTags = doc.getElementsByTagName("use");
- for (int temp = 0; temp < nUseTags.getLength(); temp++) {
- Node nNode = nUseTags.item(temp);
- if (nNode.getNodeType() == Node.ELEMENT_NODE) {
- parseTransformation(svgTree, nNode);
- }
- }
-
- SvgGroupNode root = new SvgGroupNode(svgTree, rootNode, "root");
- svgTree.setRoot(root);
-
- // Parse all the group and path node recursively.
- traverseSVGAndExtract(svgTree, root, rootNode);
-
- svgTree.dump(root);
-
- return svgTree;
- }
-
- private static void traverseSVGAndExtract(SvgTree svgTree, SvgGroupNode currentGroup, Node item) {
- // Recursively traverse all the group and path nodes
- NodeList allChildren = item.getChildNodes();
-
- for (int i = 0; i < allChildren.getLength(); i++) {
- Node currentNode = allChildren.item(i);
- String nodeName = currentNode.getNodeName();
- if (SVG_PATH.equals(nodeName) ||
- SVG_RECT.equals(nodeName) ||
- SVG_CIRCLE.equals(nodeName) ||
- SVG_POLYGON.equals(nodeName) ||
- SVG_LINE.equals(nodeName)) {
- SvgLeafNode child = new SvgLeafNode(svgTree, currentNode, nodeName + i);
-
- extractAllItemsAs(svgTree, child, currentNode);
-
- currentGroup.addChild(child);
- } else if (SVG_GROUP.equals(nodeName)) {
- SvgGroupNode childGroup = new SvgGroupNode(svgTree, currentNode, "child" + i);
- currentGroup.addChild(childGroup);
- traverseSVGAndExtract(svgTree, childGroup, currentNode);
- } else {
- // For other fancy tags, like <refs>, they can contain children too.
- // Report the unsupported nodes.
- if (unsupportedSvgNodes.contains(nodeName)) {
- svgTree.logErrorLine("<" + nodeName + "> is not supported", currentNode,
- SvgTree.SvgLogLevel.ERROR);
- }
- traverseSVGAndExtract(svgTree, currentGroup, currentNode);
- }
- }
-
- }
-
- private static void parseTransformation(SvgTree avg, Node nNode) {
- NamedNodeMap a = nNode.getAttributes();
- int len = a.getLength();
-
- for (int i = 0; i < len; i++) {
- Node n = a.item(i);
- String name = n.getNodeName();
- String value = n.getNodeValue();
- if (SVG_TRANSFORM.equals(name)) {
- if (value.startsWith("matrix(")) {
- value = value.substring("matrix(".length(), value.length() - 1);
- String[] sp = value.split(" ");
- for (int j = 0; j < sp.length; j++) {
- avg.matrix[j] = Float.parseFloat(sp[j]);
- }
- }
- } else if (name.equals("y")) {
- Float.parseFloat(value);
- } else if (name.equals("x")) {
- Float.parseFloat(value);
- }
-
- }
- }
-
- private static void parseDimension(SvgTree avg, Node nNode) {
- NamedNodeMap a = nNode.getAttributes();
- int len = a.getLength();
-
- for (int i = 0; i < len; i++) {
- Node n = a.item(i);
- String name = n.getNodeName();
- String value = n.getNodeValue();
- int subStringSize = value.length();
- if (subStringSize > 2) {
- if (value.endsWith("px")) {
- subStringSize = subStringSize - 2;
- }
- }
-
- if (SVG_WIDTH.equals(name)) {
- avg.w = Float.parseFloat(value.substring(0, subStringSize));
- } else if (SVG_HEIGHT.equals(name)) {
- avg.h = Float.parseFloat(value.substring(0, subStringSize));
- } else if (SVG_VIEW_BOX.equals(name)) {
- avg.viewBox = new float[4];
- String[] strbox = value.split(" ");
- for (int j = 0; j < avg.viewBox.length; j++) {
- avg.viewBox[j] = Float.parseFloat(strbox[j]);
- }
- }
- }
- if (avg.viewBox == null && avg.w != 0 && avg.h != 0) {
- avg.viewBox = new float[4];
- avg.viewBox[2] = avg.w;
- avg.viewBox[3] = avg.h;
- }
- }
-
- // Read the content from currentItem, and fill into "child"
- private static void extractAllItemsAs(SvgTree avg, SvgLeafNode child, Node currentItem) {
- Node currentGroup = currentItem.getParentNode();
-
- boolean hasNodeAttr = false;
- String styleContent = "";
- boolean nothingToDisplay = false;
-
- while (currentGroup != null && currentGroup.getNodeName().equals("g")) {
- // Parse the group's attributes.
- logger.log(Level.FINE, "Printing current parent");
- printlnCommon(currentGroup);
-
- NamedNodeMap attr = currentGroup.getAttributes();
- Node nodeAttr = attr.getNamedItem(SVG_STYLE);
- // Search for the "display:none", if existed, then skip this item.
- if (nodeAttr != null) {
- styleContent += nodeAttr.getTextContent() + ";";
- logger.log(Level.FINE, "styleContent is :" + styleContent + "at number group ");
- if (styleContent.contains("display:none")) {
- logger.log(Level.FINE, "Found none style, skip the whole group");
- nothingToDisplay = true;
- break;
- } else {
- hasNodeAttr = true;
- }
- }
-
- Node displayAttr = attr.getNamedItem(SVG_DISPLAY);
- if (displayAttr != null && "none".equals(displayAttr.getNodeValue())) {
- logger.log(Level.FINE, "Found display:none style, skip the whole group");
- nothingToDisplay = true;
- break;
- }
- currentGroup = currentGroup.getParentNode();
- }
-
- if (nothingToDisplay) {
- // Skip this current whole item.
- return;
- }
-
- logger.log(Level.FINE, "Print current item");
- printlnCommon(currentItem);
-
- if (hasNodeAttr && styleContent != null) {
- addStyleToPath(child, styleContent);
- }
-
- Node currentGroupNode = currentItem;
-
- if (SVG_PATH.equals(currentGroupNode.getNodeName())) {
- extractPathItem(avg, child, currentGroupNode);
- }
-
- if (SVG_RECT.equals(currentGroupNode.getNodeName())) {
- extractRectItem(avg, child, currentGroupNode);
- }
-
- if (SVG_CIRCLE.equals(currentGroupNode.getNodeName())) {
- extractCircleItem(avg, child, currentGroupNode);
- }
-
- if (SVG_POLYGON.equals(currentGroupNode.getNodeName())) {
- extractPolyItem(avg, child, currentGroupNode);
- }
-
- if (SVG_LINE.equals(currentGroupNode.getNodeName())) {
- extractLineItem(avg, child, currentGroupNode);
- }
- }
-
- private static void printlnCommon(Node n) {
- logger.log(Level.FINE, " nodeName=\"" + n.getNodeName() + "\"");
-
- String val = n.getNamespaceURI();
- if (val != null) {
- logger.log(Level.FINE, " uri=\"" + val + "\"");
- }
-
- val = n.getPrefix();
-
- if (val != null) {
- logger.log(Level.FINE, " pre=\"" + val + "\"");
- }
-
- val = n.getLocalName();
- if (val != null) {
- logger.log(Level.FINE, " local=\"" + val + "\"");
- }
-
- val = n.getNodeValue();
- if (val != null) {
- logger.log(Level.FINE, " nodeValue=");
- if (val.trim().equals("")) {
- // Whitespace
- logger.log(Level.FINE, "[WS]");
- } else {
- logger.log(Level.FINE, "\"" + n.getNodeValue() + "\"");
- }
- }
- }
-
- /**
- * Convert polygon element into a path.
- */
- private static void extractPolyItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
- logger.log(Level.FINE, "Rect found" + currentGroupNode.getTextContent());
- if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
-
- NamedNodeMap a = currentGroupNode.getAttributes();
- int len = a.getLength();
-
- for (int itemIndex = 0; itemIndex < len; itemIndex++) {
- Node n = a.item(itemIndex);
- String name = n.getNodeName();
- String value = n.getNodeValue();
- if (name.equals(SVG_STYLE)) {
- addStyleToPath(child, value);
- } else if (presentationMap.containsKey(name)) {
- child.fillPresentationAttributes(name, value);
- } else if (name.equals(SVG_POINTS)) {
- PathBuilder builder = new PathBuilder();
- String[] split = value.split("[\\s,]+");
- float baseX = Float.parseFloat(split[0]);
- float baseY = Float.parseFloat(split[1]);
- builder.absoluteMoveTo(baseX, baseY);
- for (int j = 2; j < split.length; j += 2) {
- float x = Float.parseFloat(split[j]);
- float y = Float.parseFloat(split[j + 1]);
- builder.relativeLineTo(x - baseX, y - baseY);
- baseX = x;
- baseY = y;
- }
- builder.relativeClose();
- child.setPathData(builder.toString());
- }
- }
- }
- }
-
- /**
- * Convert rectangle element into a path.
- */
- private static void extractRectItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
- logger.log(Level.FINE, "Rect found" + currentGroupNode.getTextContent());
-
- if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
- float x = 0;
- float y = 0;
- float width = Float.NaN;
- float height = Float.NaN;
-
- NamedNodeMap a = currentGroupNode.getAttributes();
- int len = a.getLength();
- boolean pureTransparent = false;
- for (int j = 0; j < len; j++) {
- Node n = a.item(j);
- String name = n.getNodeName();
- String value = n.getNodeValue();
- if (name.equals(SVG_STYLE)) {
- addStyleToPath(child, value);
- if (value.contains("opacity:0;")) {
- pureTransparent = true;
- }
- } else if (presentationMap.containsKey(name)) {
- child.fillPresentationAttributes(name, value);
- } else if (name.equals("clip-path") && value.startsWith("url(#SVGID_")) {
-
- } else if (name.equals("x")) {
- x = Float.parseFloat(value);
- } else if (name.equals("y")) {
- y = Float.parseFloat(value);
- } else if (name.equals("width")) {
- width = Float.parseFloat(value);
- } else if (name.equals("height")) {
- height = Float.parseFloat(value);
- } else if (name.equals("style")) {
-
- }
-
- }
-
- if (!pureTransparent && avg != null && !Float.isNaN(x) && !Float.isNaN(y)
- && !Float.isNaN(width)
- && !Float.isNaN(height)) {
- // "M x, y h width v height h -width z"
- PathBuilder builder = new PathBuilder();
- builder.absoluteMoveTo(x, y);
- builder.relativeHorizontalTo(width);
- builder.relativeVerticalTo(height);
- builder.relativeHorizontalTo(-width);
- builder.relativeClose();
- child.setPathData(builder.toString());
- }
- }
- }
-
- /**
- * Convert circle element into a path.
- */
- private static void extractCircleItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
- logger.log(Level.FINE, "circle found" + currentGroupNode.getTextContent());
-
- if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
- float cx = 0;
- float cy = 0;
- float radius = 0;
-
- NamedNodeMap a = currentGroupNode.getAttributes();
- int len = a.getLength();
- boolean pureTransparent = false;
- for (int j = 0; j < len; j++) {
- Node n = a.item(j);
- String name = n.getNodeName();
- String value = n.getNodeValue();
- if (name.equals(SVG_STYLE)) {
- addStyleToPath(child, value);
- if (value.contains("opacity:0;")) {
- pureTransparent = true;
- }
- } else if (presentationMap.containsKey(name)) {
- child.fillPresentationAttributes(name, value);
- } else if (name.equals("clip-path") && value.startsWith("url(#SVGID_")) {
-
- } else if (name.equals("cx")) {
- cx = Float.parseFloat(value);
- } else if (name.equals("cy")) {
- cy = Float.parseFloat(value);
- } else if (name.equals("r")) {
- radius = Float.parseFloat(value);
- }
-
- }
-
- if (!pureTransparent && avg != null && !Float.isNaN(cx) && !Float.isNaN(cy)) {
- // "M cx cy m -r, 0 a r,r 0 1,1 (r * 2),0 a r,r 0 1,1 -(r * 2),0"
- PathBuilder builder = new PathBuilder();
- builder.absoluteMoveTo(cx, cy);
- builder.relativeMoveTo(-radius, 0);
- builder.relativeArcTo(radius, radius, false, true, true, 2 * radius, 0);
- builder.relativeArcTo(radius, radius, false, true, true, -2 * radius, 0);
- child.setPathData(builder.toString());
- }
- }
- }
-
- /**
- * Convert line element into a path.
- */
- private static void extractLineItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
- logger.log(Level.FINE, "line found" + currentGroupNode.getTextContent());
-
- if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
- float x1 = 0;
- float y1 = 0;
- float x2 = 0;
- float y2 = 0;
-
- NamedNodeMap a = currentGroupNode.getAttributes();
- int len = a.getLength();
- boolean pureTransparent = false;
- for (int j = 0; j < len; j++) {
- Node n = a.item(j);
- String name = n.getNodeName();
- String value = n.getNodeValue();
- if (name.equals(SVG_STYLE)) {
- addStyleToPath(child, value);
- if (value.contains("opacity:0;")) {
- pureTransparent = true;
- }
- } else if (presentationMap.containsKey(name)) {
- child.fillPresentationAttributes(name, value);
- } else if (name.equals("clip-path") && value.startsWith("url(#SVGID_")) {
- // TODO: Handle clip path here.
- } else if (name.equals("x1")) {
- x1 = Float.parseFloat(value);
- } else if (name.equals("y1")) {
- y1 = Float.parseFloat(value);
- } else if (name.equals("x2")) {
- x2 = Float.parseFloat(value);
- } else if (name.equals("y2")) {
- y2 = Float.parseFloat(value);
- }
- }
-
- if (!pureTransparent && avg != null && !Float.isNaN(x1) && !Float.isNaN(y1)
- && !Float.isNaN(x2) && !Float.isNaN(y2)) {
- // "M x1, y1 L x2, y2"
- PathBuilder builder = new PathBuilder();
- builder.absoluteMoveTo(x1, y1);
- builder.absoluteLineTo(x2, y2);
- child.setPathData(builder.toString());
- }
- }
-
- }
-
- private static void extractPathItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
- logger.log(Level.FINE, "Path found " + currentGroupNode.getTextContent());
-
- if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
- Element eElement = (Element)currentGroupNode;
-
- NamedNodeMap a = currentGroupNode.getAttributes();
- int len = a.getLength();
-
- for (int j = 0; j < len; j++) {
- Node n = a.item(j);
- String name = n.getNodeName();
- String value = n.getNodeValue();
- if (name.equals(SVG_STYLE)) {
- addStyleToPath(child, value);
- } else if (presentationMap.containsKey(name)) {
- child.fillPresentationAttributes(name, value);
- } else if (name.equals(SVG_D)) {
- String pathData = value.replaceAll("(\\d)-", "$1,-");
- child.setPathData(pathData);
- }
-
- }
- }
- }
-
- private static void addStyleToPath(SvgLeafNode path, String value) {
- logger.log(Level.FINE, "Style found is " + value);
- if (value != null) {
- String[] parts = value.split(";");
- for (int k = parts.length - 1; k >= 0; k--) {
- String subStyle = parts[k];
- String[] nameValue = subStyle.split(":");
- if (nameValue.length == 2 && nameValue[0] != null && nameValue[1] != null) {
- if (presentationMap.containsKey(nameValue[0])) {
- path.fillPresentationAttributes(nameValue[0], nameValue[1]);
- } else if (nameValue[0].equals(SVG_OPACITY)) {
- // TODO: This is hacky, since we don't have a group level
- // android:opacity. This only works when the path didn't overlap.
- path.fillPresentationAttributes(SVG_FILL_OPACITY, nameValue[1]);
- }
- }
- }
- }
- }
-
- private static final String head = "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n";
-
- private static String getSizeString(float w, float h, float scaleFactor) {
- String size = " android:width=\"" + (int) (w * scaleFactor) + "dp\"\n" +
- " android:height=\"" + (int) (h * scaleFactor) + "dp\"\n";
- return size;
- }
-
- private static void writeFile(OutputStream outStream, SvgTree svgTree) throws IOException {
-
- OutputStreamWriter fw = new OutputStreamWriter(outStream);
- fw.write(head);
- float finalWidth = svgTree.w;
- float finalHeight = svgTree.h;
-
- fw.write(getSizeString(finalWidth, finalHeight, svgTree.mScaleFactor));
-
- fw.write(" android:viewportWidth=\"" + svgTree.w + "\"\n");
- fw.write(" android:viewportHeight=\"" + svgTree.h + "\">\n");
-
- svgTree.normalize();
- // TODO: this has to happen in the tree mode!!!
- writeXML(svgTree, fw);
- fw.write("</vector>\n");
-
- fw.close();
- }
-
- private static void writeXML(SvgTree svgTree, OutputStreamWriter fw) throws IOException {
- svgTree.getRoot().writeXML(fw);
- }
-
- /**
- * Convert a SVG file into VectorDrawable's XML content, if no error is found.
- *
- * @param inputSVG the input SVG file
- * @param outStream the converted VectorDrawable's content. This can be
- * empty if there is any error found during parsing
- * @return the error messages, which contain things like all the tags
- * VectorDrawble don't support or exception message.
- */
- public static String parseSvgToXml(File inputSVG, OutputStream outStream) {
- // Write all the error message during parsing into SvgTree. and return here as getErrorLog().
- // We will also log the exceptions here.
- String errorLog = null;
- try {
- SvgTree svgTree = parse(inputSVG);
- errorLog = svgTree.getErrorLog();
- // When there was anything in the input SVG file that we can't
- // convert to VectorDrawable, we logged them as errors.
- // After we logged all the errors, we skipped the XML file generation.
- if (svgTree.canConvertToVectorDrawable()) {
- writeFile(outStream, svgTree);
- }
- } catch (Exception e) {
- errorLog = "EXCEPTION in parsing " + inputSVG.getName() + ":\n" + e.getMessage();
- }
- return errorLog;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgGroupNode.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgGroupNode.java
deleted file mode 100644
index 3a0082d..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgGroupNode.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import org.w3c.dom.Node;
-
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Represent a SVG file's group element.
- */
-class SvgGroupNode extends SvgNode {
- private static Logger logger = Logger.getLogger(SvgGroupNode.class.getSimpleName());
- private static final String INDENT_LEVEL = " ";
- private ArrayList<SvgNode> mChildren = new ArrayList<SvgNode>();
-
- public SvgGroupNode(SvgTree svgTree, Node docNode, String name) {
- super(svgTree, docNode, name);
- }
-
- public void addChild(SvgNode child) {
- mChildren.add(child);
- }
-
- @Override
- public void dumpNode(String indent) {
- // Print the current group.
- logger.log(Level.FINE, indent + "current group is :" + getName());
-
- // Then print all the children.
- for (SvgNode node : mChildren) {
- node.dumpNode(indent + INDENT_LEVEL);
- }
- }
-
- @Override
- public boolean isGroupNode() {
- return true;
- }
-
- @Override
- public void transform(float a, float b, float c, float d, float e, float f) {
- for (SvgNode p : mChildren) {
- p.transform(a, b, c, d, e, f);
- }
- }
-
- @Override
- public void writeXML(OutputStreamWriter writer) throws IOException {
- for (SvgNode node : mChildren) {
- node.writeXML(writer);
- }
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgLeafNode.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgLeafNode.java
deleted file mode 100644
index 72bdd70..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgLeafNode.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.annotations.Nullable;
-import com.google.common.collect.ImmutableMap;
-import org.w3c.dom.Node;
-
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.HashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Represent a SVG file's leave element.
- */
-class SvgLeafNode extends SvgNode {
- private static Logger logger = Logger.getLogger(SvgLeafNode.class.getSimpleName());
-
- private String mPathData;
-
- // Key is the attributes for vector drawable, and the value is the converted from SVG.
- private HashMap<String, String> mVdAttributesMap = new HashMap<String, String>();
-
- public SvgLeafNode(SvgTree svgTree, Node node, String nodeName) {
- super(svgTree, node, nodeName);
- }
-
- private String getAttributeValues(ImmutableMap<String, String> presentationMap) {
- StringBuilder sb = new StringBuilder("/>\n");
- for (String key : mVdAttributesMap.keySet()) {
- String vectorDrawableAttr = presentationMap.get(key);
- String svgValue = mVdAttributesMap.get(key);
- String vdValue = svgValue.trim();
- // There are several cases we need to convert from SVG format to
- // VectorDrawable format. Like "none", "3px" or "rgb(255, 0, 0)"
- if ("none".equals(vdValue)) {
- vdValue = "#00000000";
- } else if (vdValue.endsWith("px")){
- vdValue = vdValue.substring(0, vdValue.length() - 2);
- } else if (vdValue.startsWith("rgb")) {
- vdValue = vdValue.substring(3, vdValue.length());
- vdValue = convertRGBToHex(vdValue);
- if (vdValue == null) {
- getTree().logErrorLine("Unsupported Color format " + vdValue, getDocumentNode(),
- SvgTree.SvgLogLevel.ERROR);
- }
- }
- String attr = "\n " + vectorDrawableAttr + "=\"" +
- vdValue + "\"";
- sb.insert(0, attr);
-
- }
- return sb.toString();
- }
-
- public static int clamp(int val, int min, int max) {
- return Math.max(min, Math.min(max, val));
- }
-
- /**
- * SVG allows using rgb(int, int, int) or rgb(float%, float%, float%) to
- * represent a color, but Android doesn't. Therefore, we need to convert
- * them into #RRGGBB format.
- * @param svgValue in either "(int, int, int)" or "(float%, float%, float%)"
- * @return #RRGGBB in hex format, or null, if an error is found.
- */
- @Nullable
- private String convertRGBToHex(String svgValue) {
- // We don't support color keyword yet.
- // http://www.w3.org/TR/SVG11/types.html#ColorKeywords
- String result = null;
- String functionValue = svgValue.trim();
- functionValue = svgValue.substring(1, functionValue.length() - 1);
- // After we cut the "(", ")", we can deal with the numbers.
- String[] numbers = functionValue.split(",");
- if (numbers.length != 3) {
- return null;
- }
- int[] color = new int[3];
- for (int i = 0; i < 3; i ++) {
- String number = numbers[i];
- number = number.trim();
- if (number.endsWith("%")) {
- float value = Float.parseFloat(number.substring(0, number.length() - 1));
- color[i] = clamp((int)(value * 255.0f / 100.0f), 0, 255);
- } else {
- int value = Integer.parseInt(number);
- color[i] = clamp(value, 0, 255);
- }
- }
- StringBuilder builder = new StringBuilder();
- builder.append("#");
- for (int i = 0; i < 3; i ++) {
- builder.append(String.format("%02X", color[i]));
- }
- result = builder.toString();
- assert result.length() == 7;
- return result;
- }
-
- @Override
- public void dumpNode(String indent) {
- logger.log(Level.FINE, indent + (mPathData != null ? mPathData : " null pathData ") +
- (mName != null ? mName : " null name "));
- }
-
- public void setPathData(String pathData) {
- mPathData = pathData;
- }
-
- @Override
- public boolean isGroupNode() {
- return false;
- }
-
- @Override
- public void transform(float a, float b, float c, float d, float e, float f) {
- if ("none".equals(mVdAttributesMap.get("fill")) || (mPathData == null)) {
- // Nothing to draw and transform, early return.
- return;
- }
- // TODO: We need to just apply the transformation to group.
- VdPath.Node[] n = VdParser.parsePath(mPathData);
- if (!(a == 1 && d == 1 && b == 0 && c == 0 && e == 0 && f == 0)) {
- VdPath.Node.transform(a, b, c, d, e, f, n);
- }
- mPathData = VdPath.Node.NodeListToString(n);
- }
-
- @Override
- public void writeXML(OutputStreamWriter writer) throws IOException {
- String fillColor = mVdAttributesMap.get(Svg2Vector.SVG_FILL_COLOR);
- String strokeColor = mVdAttributesMap.get(Svg2Vector.SVG_STROKE_COLOR);
- logger.log(Level.FINE, "fill color " + fillColor);
- boolean emptyFill = fillColor != null && ("none".equals(fillColor) || "#0000000".equals(fillColor));
- boolean emptyStroke = strokeColor == null || "none".equals(strokeColor);
- boolean emptyPath = mPathData == null;
- boolean nothingToDraw = emptyPath || emptyFill && emptyStroke;
- if (nothingToDraw) {
- return;
- }
-
- writer.write(" <path\n");
- if (!mVdAttributesMap.containsKey(Svg2Vector.SVG_FILL_COLOR)) {
- logger.log(Level.FINE, "ADDING FILL SVG_FILL_COLOR");
- writer.write(" android:fillColor=\"#FF000000\"\n");
- }
- writer.write(" android:pathData=\"" + mPathData + "\"");
- writer.write(getAttributeValues(Svg2Vector.presentationMap));
- }
-
- public void fillPresentationAttributes(String name, String value) {
- logger.log(Level.FINE, ">>>> PROP " + name + " = " + value);
- if (value.startsWith("url(")) {
- getTree().logErrorLine("Unsupported URL value: " + value, getDocumentNode(),
- SvgTree.SvgLogLevel.ERROR);
- return;
- }
- mVdAttributesMap.put(name, value);
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgNode.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgNode.java
deleted file mode 100644
index 494dafa..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgNode.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import org.w3c.dom.Node;
-
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-
-/**
- * Parent class for a SVG file's node, can be either group or leave element.
- */
-abstract class SvgNode {
-
- protected String mName;
- // Keep a reference to the tree in order to dump the error log.
- private SvgTree mSvgTree;
- // Use document node to get the line number for error reporting.
- private Node mDocumentNode;
-
- public SvgNode(SvgTree svgTree, Node node, String name) {
- mName = name;
- mSvgTree = svgTree;
- mDocumentNode = node;
- }
-
- protected SvgTree getTree() {
- return mSvgTree;
- }
-
- public String getName() {
- return mName;
- }
-
- public Node getDocumentNode() {
- return mDocumentNode;
- }
-
- /**
- * dump the current node's debug info.
- */
- public abstract void dumpNode(String indent);
-
- /**
- * Write the Node content into the VectorDrawable's XML file.
- */
- public abstract void writeXML(OutputStreamWriter writer) throws IOException;
-
- /**
- * @return true the node is a group node.
- */
- public abstract boolean isGroupNode();
-
- /**
- * Transform the current Node with the transformation matrix.
- */
- public abstract void transform(float a, float b, float c, float d, float e, float f);
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgTree.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgTree.java
deleted file mode 100644
index 86263e5..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgTree.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.blame.SourcePosition;
-import com.android.utils.PositionXmlParser;
-import com.google.common.base.Strings;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Represent the SVG file in an internal data structure as a tree.
- */
-class SvgTree {
- private static Logger logger = Logger.getLogger(SvgTree.class.getSimpleName());
-
- public float w;
- public float h;
- public float[] matrix;
- public float[] viewBox;
- public float mScaleFactor = 1;
-
- private SvgGroupNode mRoot;
- private String mFileName;
-
- private ArrayList<String> mErrorLines = new ArrayList<String>();
-
- public enum SvgLogLevel {
- ERROR,
- WARNING
- }
-
- public Document parse(File f) throws Exception {
- mFileName = f.getName();
- Document doc = PositionXmlParser.parse(new FileInputStream(f), false);
- return doc;
- }
-
- public void normalize() {
- if (matrix != null) {
- transform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
- }
-
- if (viewBox != null && (viewBox[0] != 0 || viewBox[1] != 0)) {
- transform(1, 0, 0, 1, -viewBox[0], -viewBox[1]);
- }
- logger.log(Level.FINE, "matrix=" + Arrays.toString(matrix));
- }
-
- private void transform(float a, float b, float c, float d, float e, float f) {
- mRoot.transform(a, b, c, d, e, f);
- }
-
- public void dump(SvgGroupNode root) {
- logger.log(Level.FINE, "current file is :" + mFileName);
- root.dumpNode("");
- }
-
- public void setRoot(SvgGroupNode root) {
- mRoot = root;
- }
-
- @Nullable
- public SvgGroupNode getRoot() {
- return mRoot;
- }
-
- public void logErrorLine(String s, Node node, SvgLogLevel level) {
- if (!Strings.isNullOrEmpty(s)) {
- if (node != null) {
- SourcePosition position = getPosition(node);
- mErrorLines.add(level.name() + "@ line " + (position.getStartLine() + 1) +
- " " + s + "\n");
- } else {
- mErrorLines.add(s);
- }
- }
- }
-
- /**
- * @return Error log. Empty string if there are no errors.
- */
- @NonNull
- public String getErrorLog() {
- StringBuilder errorBuilder = new StringBuilder();
- if (!mErrorLines.isEmpty()) {
- errorBuilder.append("In " + mFileName + ":\n");
- }
- for (String log : mErrorLines) {
- errorBuilder.append(log);
- }
- return errorBuilder.toString();
- }
-
- /**
- * @return true when there is no error found when parsing the SVG file.
- */
- public boolean canConvertToVectorDrawable() {
- return mErrorLines.isEmpty();
- }
-
- private SourcePosition getPosition(Node node) {
- return PositionXmlParser.getPosition(node);
- }
-
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdElement.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdElement.java
deleted file mode 100644
index 9422e8d..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdElement.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-/**
- * Used to represent one VectorDrawble's element, can be a group or path.
- */
-abstract class VdElement {
- String mName;
-
- public String getName() {
- return mName;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdGroup.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdGroup.java
deleted file mode 100644
index 5a6d37b..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdGroup.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import java.util.ArrayList;
-
-/**
- * Used to represent one VectorDrawble's group element.
- * TODO: Add group transformation here.
- */
-class VdGroup extends VdElement{
-
- public VdGroup() {
- mName = this.toString(); // to ensure paths have unique names
- }
- // Children can be either a {@link VdPath} or {@link VdGroup}
- private ArrayList<VdElement> mChildren = new ArrayList<VdElement>();
-
- public void add(VdElement pathOrGroup) {
- mChildren.add(pathOrGroup);
- }
-
- public ArrayList<VdElement> getChildren() {
- return mChildren;
- }
-
- public int size() {
- return mChildren.size();
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdIcon.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdIcon.java
deleted file mode 100644
index f61e4cb..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdIcon.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.ide.common.util.AssetUtil;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.net.URL;
-
-/**
- * VdIcon wrap every vector drawable from Material Library into an icon.
- * All of them are shown in a table for developer to pick.
- */
-public class VdIcon implements Icon, Comparable<VdIcon> {
- private VdTree mVdTree;
- private final String mName;
- private final URL mUrl;
-
- public VdIcon(URL url) {
- setDynamicIcon(url);
- mUrl = url;
- String fileName = url.getFile();
- mName = fileName.substring(fileName.lastIndexOf("/") + 1);
- }
-
- public String getName() {
- return mName;
- }
-
- public URL getURL() {
- return mUrl;
- }
-
- public void setDynamicIcon(URL url) {
- final VdParser p = new VdParser();
- try {
- mVdTree = p.parse(url.openStream(), null);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void paintIcon(Component c, Graphics g, int x, int y) {
- // We knew all the icons from Material library are square shape.
- int minSize = Math.min(c.getWidth(), c.getHeight());
- final BufferedImage image = AssetUtil.newArgbBufferedImage(minSize, minSize);
- mVdTree.drawIntoImage(image);
-
- // Draw in the center of the component.
- Rectangle rect = new Rectangle(0, 0, c.getWidth(), c.getHeight());
- AssetUtil.drawCenterInside((Graphics2D)g, image, rect);
- }
-
- @Override
- public int getIconWidth() {
- return (int) (mVdTree != null ? mVdTree.mPortWidth : 0);
- }
-
- @Override
- public int getIconHeight() {
- return (int) (mVdTree != null ? mVdTree.mPortHeight : 0);
- }
-
- @Override
- public int compareTo(VdIcon other) {
- return mName.compareTo(other.mName);
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdNodeRender.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdNodeRender.java
deleted file mode 100644
index 9cba65f..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdNodeRender.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import java.awt.geom.Path2D;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Given an array of VdPath.Node, generate a Path2D object.
- * In another word, this is the engine which converts the pathData into
- * a Path2D object, which is able to draw on Swing components.
- * The logic and math here are the same as PathParser.java in framework.
- */
-class VdNodeRender {
- private static Logger logger = Logger.getLogger(VdNodeRender.class
- .getSimpleName());
-
- public static void creatPath(VdPath.Node[] node, Path2D path) {
- float[] current = new float[6];
- char lastCmd = ' ';
- for (int i = 0; i < node.length; i++) {
- addCommand(path, current, node[i].type, lastCmd,node[i].params);
- lastCmd = node[i].type;
- }
- }
-
- private static void addCommand(Path2D path, float[] current, char cmd,
- char lastCmd, float[] val) {
-
- int incr = 2;
-
- float cx = current[0];
- float cy = current[1];
- float cpx = current[2];
- float cpy = current[3];
- float loopX = current[4];
- float loopY = current[5];
-
- switch (cmd) {
- case 'z':
- case 'Z':
- path.closePath();
- cx = loopX;
- cy = loopY;
- case 'm':
- case 'M':
- case 'l':
- case 'L':
- case 't':
- case 'T':
- incr = 2;
- break;
- case 'h':
- case 'H':
- case 'v':
- case 'V':
- incr = 1;
- break;
- case 'c':
- case 'C':
- incr = 6;
- break;
- case 's':
- case 'S':
- case 'q':
- case 'Q':
- incr = 4;
- break;
- case 'a':
- case 'A':
- incr = 7;
- }
-
- for (int k = 0; k < val.length; k += incr) {
- boolean reflectCtrl = false;
- float tempReflectedX, tempReflectedY;
-
- switch (cmd) {
- case 'm':
- cx += val[k + 0];
- cy += val[k + 1];
- path.moveTo(cx, cy);
- loopX = cx;
- loopY = cy;
- break;
- case 'M':
- cx = val[k + 0];
- cy = val[k + 1];
- path.moveTo(cx, cy);
- loopX = cx;
- loopY = cy;
- break;
- case 'l':
- cx += val[k + 0];
- cy += val[k + 1];
- path.lineTo(cx, cy);
- break;
- case 'L':
- cx = val[k + 0];
- cy = val[k + 1];
- path.lineTo(cx, cy);
- break;
- case 'z':
- case 'Z':
- path.closePath();
- cx = loopX;
- cy = loopY;
- break;
- case 'h':
- cx += val[k + 0];
- path.lineTo(cx, cy);
- break;
- case 'H':
- path.lineTo(val[k + 0], cy);
- cx = val[k + 0];
- break;
- case 'v':
- cy += val[k + 0];
- path.lineTo(cx, cy);
- break;
- case 'V':
- path.lineTo(cx, val[k + 0]);
- cy = val[k + 0];
- break;
- case 'c':
- path.curveTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
- cy + val[k + 3], cx + val[k + 4], cy + val[k + 5]);
- cpx = cx + val[k + 2];
- cpy = cy + val[k + 3];
- cx += val[k + 4];
- cy += val[k + 5];
- break;
- case 'C':
- path.curveTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
- val[k + 4], val[k + 5]);
- cx = val[k + 4];
- cy = val[k + 5];
- cpx = val[k + 2];
- cpy = val[k + 3];
- break;
- case 's':
- reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' || lastCmd == 'S');
- path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
- * cy - cpy : cy, cx + val[k + 0], cy + val[k + 1], cx
- + val[k + 2], cy + val[k + 3]);
-
- cpx = cx + val[k + 0];
- cpy = cy + val[k + 1];
- cx += val[k + 2];
- cy += val[k + 3];
- break;
- case 'S':
- reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' || lastCmd == 'S');
- path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
- * cy - cpy : cy, val[k + 0], val[k + 1], val[k + 2],
- val[k + 3]);
- cpx = (val[k + 0]);
- cpy = (val[k + 1]);
- cx = val[k + 2];
- cy = val[k + 3];
- break;
- case 'q':
- path.quadTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
- cy + val[k + 3]);
- cpx = cx + val[k + 0];
- cpy = cy + val[k + 1];
- // Note that we have to update cpx first, since cx will be updated here.
- cx += val[k + 2];
- cy += val[k + 3];
- break;
- case 'Q':
- path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- cx = val[k + 2];
- cy = val[k + 3];
- cpx = val[k + 0];
- cpy = val[k + 1];
- break;
- case 't':
- reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' || lastCmd == 'T');
- tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
- tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
- path.quadTo(tempReflectedX, tempReflectedY, cx + val[k + 0], cy + val[k + 1]);
- cpx = tempReflectedX;
- cpy = tempReflectedY;
- cx += val[k + 0];
- cy += val[k + 1];
- break;
- case 'T':
- reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' || lastCmd == 'T');
- tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
- tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
- path.quadTo(tempReflectedX, tempReflectedY, val[k + 0], val[k + 1]);
- cx = val[k + 0];
- cy = val[k + 1];
- cpx = tempReflectedX;
- cpy = tempReflectedY;
- break;
- case 'a':
- // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
- drawArc(path, cx, cy, val[k + 5] + cx, val[k + 6] + cy,
- val[k + 0], val[k + 1], val[k + 2], val[k + 3] != 0,
- val[k + 4] != 0);
- cx += val[k + 5];
- cy += val[k + 6];
- cpx = cx;
- cpy = cy;
-
- break;
- case 'A':
- drawArc(path, cx, cy, val[k + 5], val[k + 6], val[k + 0],
- val[k + 1], val[k + 2], val[k + 3] != 0,
- val[k + 4] != 0);
- cx = val[k + 5];
- cy = val[k + 6];
- cpx = cx;
- cpy = cy;
- break;
-
- }
- lastCmd = cmd;
- }
- current[0] = cx;
- current[1] = cy;
- current[2] = cpx;
- current[3] = cpy;
- current[4] = loopX;
- current[5] = loopY;
-
- }
-
- private static void drawArc(Path2D p, float x0, float y0, float x1,
- float y1, float a, float b, float theta, boolean isMoreThanHalf,
- boolean isPositiveArc) {
-
- logger.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1
- + ") {" + a + " " + b + "}");
- /* Convert rotation angle from degrees to radians */
- double thetaD = theta * Math.PI / 180.0f;
- /* Pre-compute rotation matrix entries */
- double cosTheta = Math.cos(thetaD);
- double sinTheta = Math.sin(thetaD);
- /* Transform (x0, y0) and (x1, y1) into unit space */
- /* using (inverse) rotation, followed by (inverse) scale */
- double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
- double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
- double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
- double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
- logger.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p
- + "," + y1p + ")");
- /* Compute differences and averages */
- double dx = x0p - x1p;
- double dy = y0p - y1p;
- double xm = (x0p + x1p) / 2;
- double ym = (y0p + y1p) / 2;
- /* Solve for intersecting unit circles */
- double dsq = dx * dx + dy * dy;
- if (dsq == 0.0) {
- logger.log(Level.FINE, " Points are coincident");
- return; /* Points are coincident */
- }
- double disc = 1.0 / dsq - 1.0 / 4.0;
- if (disc < 0.0) {
- logger.log(Level.FINE, "Points are too far apart " + dsq);
- float adjust = (float) (Math.sqrt(dsq) / 1.99999);
- drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta,
- isMoreThanHalf, isPositiveArc);
- return; /* Points are too far apart */
- }
- double s = Math.sqrt(disc);
- double sdx = s * dx;
- double sdy = s * dy;
- double cx;
- double cy;
- if (isMoreThanHalf == isPositiveArc) {
- cx = xm - sdy;
- cy = ym + sdx;
- } else {
- cx = xm + sdy;
- cy = ym - sdx;
- }
-
- double eta0 = Math.atan2((y0p - cy), (x0p - cx));
- logger.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , "
- + (x0p - cx) + ") = " + Math.toDegrees(eta0));
-
- double eta1 = Math.atan2((y1p - cy), (x1p - cx));
- logger.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , "
- + (x1p - cx) + ") = " + Math.toDegrees(eta1));
- double sweep = (eta1 - eta0);
- if (isPositiveArc != (sweep >= 0)) {
- if (sweep > 0) {
- sweep -= 2 * Math.PI;
- } else {
- sweep += 2 * Math.PI;
- }
- }
-
- cx *= a;
- cy *= b;
- double tcx = cx;
- cx = cx * cosTheta - cy * sinTheta;
- cy = tcx * sinTheta + cy * cosTheta;
- logger.log(
- Level.FINE,
- "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , "
- + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0
- + " , " + Math.toDegrees(thetaD) + " , "
- + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep));
-
- arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
- }
-
- /**
- * Converts an arc to cubic Bezier segments and records them in p.
- *
- * @param p The target for the cubic Bezier segments
- * @param cx The x coordinate center of the ellipse
- * @param cy The y coordinate center of the ellipse
- * @param a The radius of the ellipse in the horizontal direction
- * @param b The radius of the ellipse in the vertical direction
- * @param e1x E(eta1) x coordinate of the starting point of the arc
- * @param e1y E(eta2) y coordinate of the starting point of the arc
- * @param theta The angle that the ellipse bounding rectangle makes with the horizontal plane
- * @param start The start angle of the arc on the ellipse
- * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
- */
- private static void arcToBezier(Path2D p, double cx, double cy, double a,
- double b, double e1x, double e1y, double theta, double start,
- double sweep) {
- // Taken from equations at:
- // http://spaceroots.org/documents/ellipse/node8.html
- // and http://www.spaceroots.org/documents/ellipse/node22.html
-
- // Maximum of 45 degrees per cubic Bezier segment
- int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
-
- double eta1 = start;
- double cosTheta = Math.cos(theta);
- double sinTheta = Math.sin(theta);
- double cosEta1 = Math.cos(eta1);
- double sinEta1 = Math.sin(eta1);
- double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
- double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
- double anglePerSegment = sweep / numSegments;
- for (int i = 0; i < numSegments; i++) {
- double eta2 = eta1 + anglePerSegment;
- double sinEta2 = Math.sin(eta2);
- double cosEta2 = Math.cos(eta2);
- double e2x = cx + (a * cosTheta * cosEta2)
- - (b * sinTheta * sinEta2);
- double e2y = cy + (a * sinTheta * cosEta2)
- + (b * cosTheta * sinEta2);
- double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
- double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
- double tanDiff2 = Math.tan((eta2 - eta1) / 2);
- double alpha = Math.sin(eta2 - eta1)
- * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
- double q1x = e1x + alpha * ep1x;
- double q1y = e1y + alpha * ep1y;
- double q2x = e2x - alpha * ep2x;
- double q2y = e2y - alpha * ep2y;
-
- p.curveTo((float) q1x, (float) q1y, (float) q2x, (float) q2y,
- (float) e2x, (float) e2y);
- eta1 = eta2;
- e1x = e2x;
- e1y = e2y;
- ep1x = ep2x;
- ep1y = ep2y;
- }
- }
-
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdParser.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdParser.java
deleted file mode 100644
index a1a4106..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdParser.java
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.SdkConstants;
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Parse a VectorDrawble's XML file, and generate an internal tree representation,
- * which can be used for drawing / previewing.
- */
-class VdParser {
- private static Logger logger = Logger.getLogger(VdParser.class.getSimpleName());
-
- private static final String PATH_SHIFT_X = "shift-x";
- private static final String PATH_SHIFT_Y = "shift-y";
-
- private static final String SHAPE_VECTOR = "vector";
- private static final String SHAPE_PATH = "path";
- private static final String SHAPE_GROUP = "group";
-
- private static final String PATH_ID = "android:name";
- private static final String PATH_DESCRIPTION = "android:pathData";
- private static final String PATH_FILL = "android:fillColor";
- private static final String PATH_FILL_OPACTIY = "android:fillAlpha";
- private static final String PATH_STROKE = "android:strokeColor";
- private static final String PATH_STROKE_OPACTIY = "android:strokeAlpha";
-
- private static final String PATH_STROKE_WIDTH = "android:strokeWidth";
- private static final String PATH_ROTATION = "android:rotation";
- private static final String PATH_ROTATION_X = "android:pivotX";
- private static final String PATH_ROTATION_Y = "android:pivotY";
- private static final String PATH_TRIM_START = "android:trimPathStart";
- private static final String PATH_TRIM_END = "android:trimPathEnd";
- private static final String PATH_TRIM_OFFSET = "android:trimPathOffset";
- private static final String PATH_STROKE_LINECAP = "android:strokeLinecap";
- private static final String PATH_STROKE_LINEJOIN = "android:strokeLinejoin";
- private static final String PATH_STROKE_MITERLIMIT = "android:strokeMiterlimit";
- private static final String PATH_CLIP = "android:clipToPath";
- private static final String LINECAP_BUTT = "butt";
- private static final String LINECAP_ROUND = "round";
- private static final String LINECAP_SQUARE = "square";
- private static final String LINEJOIN_MITER = "miter";
- private static final String LINEJOIN_ROUND = "round";
- private static final String LINEJOIN_BEVEL = "bevel";
-
- interface ElemParser {
- void parse(VdTree path, Attributes attributes);
- }
-
- ElemParser mParseSize = new ElemParser() {
- @Override
- public void parse(VdTree tree, Attributes attributes) {
- parseSize(tree, attributes);
- }
- };
-
- ElemParser mParsePath = new ElemParser() {
- @Override
- public void parse(VdTree tree, Attributes attributes) {
- VdPath p = parsePathAttributes(attributes);
- tree.add(p);
- }
- };
-
- ElemParser mParseGroup = new ElemParser() {
- @Override
- public void parse(VdTree tree, Attributes attributes) {
- VdGroup g = parseGroupAttributes(attributes);
- tree.add(g);
- }
- };
-
- HashMap<String, ElemParser> tagSwitch = new HashMap<String, ElemParser>();
- {
- tagSwitch.put(SHAPE_VECTOR, mParseSize);
- tagSwitch.put(SHAPE_PATH, mParsePath);
- tagSwitch.put(SHAPE_GROUP, mParseGroup);
- // TODO: add <g> tag and start to build the tree.
- }
-
- // Note that the incoming file is the VectorDrawable's XML file, not the SVG.
- // TODO: Use Document to parse and make sure no big performance difference.
- public VdTree parse(InputStream is, StringBuilder vdErrorLog) {
- try {
- final VdTree tree = new VdTree();
- SAXParserFactory spf = SAXParserFactory.newInstance();
- SAXParser sp = spf.newSAXParser();
- XMLReader xr = sp.getXMLReader();
-
- xr.setContentHandler(new ContentHandler() {
- String space = " ";
-
- @Override
- public void setDocumentLocator(Locator locator) {
- }
-
- @Override
- public void startDocument() throws SAXException {
- }
-
- @Override
- public void endDocument() throws SAXException {
- }
-
- @Override
- public void startPrefixMapping(String s, String s2) throws SAXException {
- }
-
- @Override
- public void endPrefixMapping(String s) throws SAXException {
- }
-
- @Override
- public void startElement(String s, String s2, String s3, Attributes attributes)
- throws SAXException {
- String name = s3;
- if (tagSwitch.containsKey(name)) {
- tagSwitch.get(name).parse(tree, attributes);
- }
- space += " ";
- }
-
- @Override
- public void endElement(String s, String s2, String s3) throws SAXException {
- space = space.substring(1);
- }
-
- @Override
- public void characters(char[] chars, int i, int i2) throws SAXException {
- }
-
- @Override
- public void ignorableWhitespace(char[] chars, int i, int i2) throws SAXException {
- }
-
- @Override
- public void processingInstruction(String s, String s2) throws SAXException {
- }
-
- @Override
- public void skippedEntity(String s) throws SAXException {
- }
- });
- xr.parse(new InputSource(is));
- tree.parseFinish();
- return tree;
- } catch (Exception e) {
- vdErrorLog.append("Exception while parsing XML file:\n" + e.getMessage());
- return null;
- }
- }
-
- public VdParser() {
- }
-
- private static int nextStart(String s, int end) {
- char c;
-
- while (end < s.length()) {
- c = s.charAt(end);
- // Note that 'e' or 'E' are not valid path commands, but could be
- // used for floating point numbers' scientific notation.
- // Therefore, when searching for next command, we should ignore 'e'
- // and 'E'.
- if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
- && c != 'e' && c != 'E') {
- return end;
- }
- end++;
- }
- return end;
- }
-
- public static VdPath.Node[] parsePath(String value) {
- int start = 0;
- int end = 1;
-
- ArrayList<VdPath.Node> list = new ArrayList<VdPath.Node>();
- while (end < value.length()) {
- end = nextStart(value, end);
- String s = value.substring(start, end);
- float[] val = getFloats(s);
-
- addNode(list, s.charAt(0), val);
-
- start = end;
- end++;
- }
- if ((end - start) == 1 && start < value.length()) {
-
- addNode(list, value.charAt(start), new float[0]);
- }
- return list.toArray(new VdPath.Node[list.size()]);
- }
-
- private static class ExtractFloatResult {
- // We need to return the position of the next separator and whether the
- // next float starts with a '-' or a '.'.
- int mEndPosition;
- boolean mEndWithNegOrDot;
- }
-
- /**
- * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
- * end (exclusive). The original order of elements is preserved.
- * If {@code end} is greater than {@code original.length}, the result is padded
- * with the value {@code 0.0f}.
- *
- * @param original the original array
- * @param start the start index, inclusive
- * @param end the end index, exclusive
- * @return the new array
- * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
- * @throws IllegalArgumentException if {@code start > end}
- * @throws NullPointerException if {@code original == null}
- */
- private static float[] copyOfRange(float[] original, int start, int end) {
- if (start > end) {
- throw new IllegalArgumentException();
- }
- int originalLength = original.length;
- if (start < 0 || start > originalLength) {
- throw new ArrayIndexOutOfBoundsException();
- }
- int resultLength = end - start;
- int copyLength = Math.min(resultLength, originalLength - start);
- float[] result = new float[resultLength];
- System.arraycopy(original, start, result, 0, copyLength);
- return result;
- }
-
- /**
- * Calculate the position of the next comma or space or negative sign
- * @param s the string to search
- * @param start the position to start searching
- * @param result the result of the extraction, including the position of the
- * the starting position of next number, whether it is ending with a '-'.
- */
- private static void extract(String s, int start, ExtractFloatResult result) {
- // Now looking for ' ', ',', '.' or '-' from the start.
- int currentIndex = start;
- boolean foundSeparator = false;
- result.mEndWithNegOrDot = false;
- boolean secondDot = false;
- boolean isExponential = false;
- for (; currentIndex < s.length(); currentIndex++) {
- boolean isPrevExponential = isExponential;
- isExponential = false;
- char currentChar = s.charAt(currentIndex);
- switch (currentChar) {
- case ' ':
- case ',':
- foundSeparator = true;
- break;
- case '-':
- // The negative sign following a 'e' or 'E' is not a separator.
- if (currentIndex != start && !isPrevExponential) {
- foundSeparator = true;
- result.mEndWithNegOrDot = true;
- }
- break;
- case '.':
- if (!secondDot) {
- secondDot = true;
- } else {
- // This is the second dot, and it is considered as a separator.
- foundSeparator = true;
- result.mEndWithNegOrDot = true;
- }
- break;
- case 'e':
- case 'E':
- isExponential = true;
- break;
- }
- if (foundSeparator) {
- break;
- }
- }
- // When there is nothing found, then we put the end position to the end
- // of the string.
- result.mEndPosition = currentIndex;
- }
-
- /**
- * parse the floats in the string this is an optimized version of parseFloat(s.split(",|\\s"));
- *
- * @param s the string containing a command and list of floats
- * @return array of floats
- */
- private static float[] getFloats(String s) {
- if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
- return new float[0];
- }
- try {
- float[] results = new float[s.length()];
- int count = 0;
- int startPosition = 1;
- int endPosition = 0;
-
- ExtractFloatResult result = new ExtractFloatResult();
- int totalLength = s.length();
-
- // The startPosition should always be the first character of the
- // current number, and endPosition is the character after the current
- // number.
- while (startPosition < totalLength) {
- extract(s, startPosition, result);
- endPosition = result.mEndPosition;
-
- if (startPosition < endPosition) {
- results[count++] = Float.parseFloat(
- s.substring(startPosition, endPosition));
- }
-
- if (result.mEndWithNegOrDot) {
- // Keep the '-' or '.' sign with next number.
- startPosition = endPosition;
- } else {
- startPosition = endPosition + 1;
- }
- }
- return copyOfRange(results, 0, count);
- } catch (NumberFormatException e) {
- throw new RuntimeException("error in parsing \"" + s + "\"", e);
- }
- }
- // End of copy from PathParser.java
- ////////////////////////////////////////////////////////////////
- private static void addNode(ArrayList<VdPath.Node> list, char cmd, float[] val) {
- list.add(new VdPath.Node(cmd, val));
- }
-
- public VdTree parse(URL r, StringBuilder vdErrorLog) throws Exception {
- return parse(r.openStream(), vdErrorLog);
- }
-
- private void parseSize(VdTree vdTree, Attributes attributes) {
-
- Pattern pattern = Pattern.compile("^\\s*(\\d+(\\.\\d+)*)\\s*([a-zA-Z]+)\\s*$");
- HashMap<String, Integer> m = new HashMap<String, Integer>();
- m.put(SdkConstants.UNIT_PX, 1);
- m.put(SdkConstants.UNIT_DIP, 1);
- m.put(SdkConstants.UNIT_DP, 1);
- m.put(SdkConstants.UNIT_SP, 1);
- m.put(SdkConstants.UNIT_PT, 1);
- m.put(SdkConstants.UNIT_IN, 1);
- m.put(SdkConstants.UNIT_MM, 1);
- int len = attributes.getLength();
-
- for (int i = 0; i < len; i++) {
- String name = attributes.getQName(i);
- String value = attributes.getValue(i);
- Matcher matcher = pattern.matcher(value);
- float size = 0;
- if (matcher.matches()) {
- float v = Float.parseFloat(matcher.group(1));
- String unit = matcher.group(3).toLowerCase(Locale.getDefault());
- size = v;
- }
- // -- Extract dimension units.
-
- if ("android:width".equals(name)) {
- vdTree.mBaseWidth = size;
- } else if ("android:height".equals(name)) {
- vdTree.mBaseHeight = size;
- } else if ("android:viewportWidth".equals(name)) {
- vdTree.mPortWidth = Float.parseFloat(value);
- } else if ("android:viewportHeight".equals(name)) {
- vdTree.mPortHeight = Float.parseFloat(value);
- } else if ("android:alpha".equals(name)) {
- vdTree.mRootAlpha = Float.parseFloat(value);
- } else {
- continue;
- }
-
- }
- }
-
- private VdPath parsePathAttributes(Attributes attributes) {
- int len = attributes.getLength();
- VdPath vgPath = new VdPath();
-
- for (int i = 0; i < len; i++) {
- String name = attributes.getQName(i);
- String value = attributes.getValue(i);
- logger.log(Level.FINE, "name " + name + "value " + value);
- setNameValue(vgPath, name, value);
- }
- return vgPath;
- }
-
- private VdGroup parseGroupAttributes(Attributes attributes) {
- int len = attributes.getLength();
- VdGroup vgGroup = new VdGroup();
-
- for (int i = 0; i < len; i++) {
- String name = attributes.getQName(i);
- String value = attributes.getValue(i);
- logger.log(Level.FINE, "name " + name + "value " + value);
- }
- return vgGroup;
- }
-
- public void setNameValue(VdPath vgPath, String name, String value) {
- if (PATH_DESCRIPTION.equals(name)) {
- vgPath.mNode = parsePath(value);
- } else if (PATH_ID.equals(name)) {
- vgPath.mName = value;
- } else if (PATH_FILL.equals(name)) {
- vgPath.mFillColor = calculateColor(value);
- if (!Float.isNaN(vgPath.mFillOpacity)) {
- vgPath.mFillColor &= 0x00FFFFFF;
- vgPath.mFillColor |= ((int) (0xFF * vgPath.mFillOpacity)) << 24;
- }
- } else if (PATH_STROKE.equals(name)) {
- vgPath.mStrokeColor = calculateColor(value);
- if (!Float.isNaN(vgPath.mStrokeOpacity)) {
- vgPath.mStrokeColor &= 0x00FFFFFF;
- vgPath.mStrokeColor |= ((int) (0xFF * vgPath.mStrokeOpacity)) << 24;
- }
- } else if (PATH_FILL_OPACTIY.equals(name)) {
- vgPath.mFillOpacity = Float.parseFloat(value);
- vgPath.mFillColor &= 0x00FFFFFF;
- vgPath.mFillColor |= ((int) (0xFF * vgPath.mFillOpacity)) << 24;
- } else if (PATH_STROKE_OPACTIY.equals(name)) {
- vgPath.mStrokeOpacity = Float.parseFloat(value);
- vgPath.mStrokeColor &= 0x00FFFFFF;
- vgPath.mStrokeColor |= ((int) (0xFF * vgPath.mStrokeOpacity)) << 24;
- } else if (PATH_STROKE_WIDTH.equals(name)) {
- vgPath.mStrokeWidth = Float.parseFloat(value);
- } else if (PATH_ROTATION.equals(name)) {
- vgPath.mRotate = Float.parseFloat(value);
- } else if (PATH_SHIFT_X.equals(name)) {
- vgPath.mShiftX = Float.parseFloat(value);
- } else if (PATH_SHIFT_Y.equals(name)) {
- vgPath.mShiftY = Float.parseFloat(value);
- } else if (PATH_ROTATION_Y.equals(name)) {
- vgPath.mRotateY = Float.parseFloat(value);
- } else if (PATH_ROTATION_X.equals(name)) {
- vgPath.mRotateX = Float.parseFloat(value);
- } else if (PATH_CLIP.equals(name)) {
-
- vgPath.mClip = Boolean.parseBoolean(value);
-
- } else if (PATH_TRIM_START.equals(name)) {
- vgPath.mTrimPathStart = Float.parseFloat(value);
- } else if (PATH_TRIM_END.equals(name)) {
- vgPath.mTrimPathEnd = Float.parseFloat(value);
- } else if (PATH_TRIM_OFFSET.equals(name)) {
- vgPath.mTrimPathOffset = Float.parseFloat(value);
- } else if (PATH_STROKE_LINECAP.equals(name)) {
- if (LINECAP_BUTT.equals(value)) {
- vgPath.mStrokeLineCap = 0;
- } else if (LINECAP_ROUND.equals(value)) {
- vgPath.mStrokeLineCap = 1;
- } else if (LINECAP_SQUARE.equals(value)) {
- vgPath.mStrokeLineCap = 2;
- }
- } else if (PATH_STROKE_LINEJOIN.equals(name)) {
- if (LINEJOIN_MITER.equals(value)) {
- vgPath.mStrokeLineJoin = 0;
- } else if (LINEJOIN_ROUND.equals(value)) {
- vgPath.mStrokeLineJoin = 1;
- } else if (LINEJOIN_BEVEL.equals(value)) {
- vgPath.mStrokeLineJoin = 2;
- }
- } else if (PATH_STROKE_MITERLIMIT.equals(name)) {
- vgPath.mStrokeMiterlimit = Float.parseFloat(value);
- } else {
- logger.log(Level.FINE, ">>>>>> DID NOT UNDERSTAND ! \"" + name + "\" <<<<");
- }
-
- }
-
- private int calculateColor(String value) {
- int len = value.length();
- int ret;
- int k = 0;
- switch (len) {
- case 7: // #RRGGBB
- ret = (int) Long.parseLong(value.substring(1), 16);
- ret |= 0xFF000000;
- break;
- case 9: // #AARRGGBB
- ret = (int) Long.parseLong(value.substring(1), 16);
- break;
- case 4: // #RGB
- ret = (int) Long.parseLong(value.substring(1), 16);
-
- k |= ((ret >> 8) & 0xF) * 0x110000;
- k |= ((ret >> 4) & 0xF) * 0x1100;
- k |= ((ret) & 0xF) * 0x11;
- ret = k | 0xFF000000;
- break;
- case 5: // #ARGB
- ret = (int) Long.parseLong(value.substring(1), 16);
- k |= ((ret >> 16) & 0xF) * 0x11000000;
- k |= ((ret >> 8) & 0xF) * 0x110000;
- k |= ((ret >> 4) & 0xF) * 0x1100;
- k |= ((ret) & 0xF) * 0x11;
- break;
- default:
- return 0xFF000000;
- }
-
- logger.log(Level.FINE, "color = " + value + " = " + Integer.toHexString(ret));
- return ret;
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPath.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPath.java
deleted file mode 100644
index 6315fff..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPath.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import java.awt.geom.Path2D;
-import java.util.Arrays;
-
-/**
- * Used to represent one VectorDrawble's path element.
- */
-class VdPath extends VdElement{
- Node[] mNode = null;
- int mStrokeColor = 0;
- int mFillColor = 0;
- float mStrokeWidth = 0;
- float mRotate = 0;
- float mShiftX = 0;
- float mShiftY = 0;
- float mRotateX = 0;
- float mRotateY = 0;
- float trimPathStart = 0;
- float trimPathEnd = 1;
- float trimPathOffset = 0;
- int mStrokeLineCap = -1;
- int mStrokeLineJoin = -1;
- float mStrokeMiterlimit = -1;
- boolean mClip = false;
- float mStrokeOpacity = Float.NaN;
- float mFillOpacity = Float.NaN;
- float mTrimPathStart = 0;
- float mTrimPathEnd = 1;
- float mTrimPathOffset = 0;
-
- public void toPath(Path2D path) {
- path.reset();
- if (mNode != null) {
- VdNodeRender.creatPath(mNode, path);
- }
- }
-
- public static class Node {
- char type;
- float[] params;
-
- public Node(char type, float[] params) {
- this.type = type;
- this.params = params;
- }
-
- public Node(Node n) {
- this.type = n.type;
- this.params = Arrays.copyOf(n.params, n.params.length);
- }
-
- public static String NodeListToString(Node[] nodes) {
- String s = "";
- for (int i = 0; i < nodes.length; i++) {
- Node n = nodes[i];
- s += n.type;
- int len = n.params.length;
- for (int j = 0; j < len; j++) {
- if (j > 0) {
- s += ((j & 1) == 1) ? "," : " ";
- }
- // To avoid trailing zeros like 17.0, use this trick
- float value = n.params[j];
- if (value == (long) value) {
- s += String.valueOf((long) value);
- } else {
- s += String.valueOf(value);
- }
-
- }
- }
- return s;
- }
-
- public static void transform(float a,
- float b,
- float c,
- float d,
- float e,
- float f,
- Node[] nodes) {
- float[] pre = new float[2];
- for (int i = 0; i < nodes.length; i++) {
- nodes[i].transform(a, b, c, d, e, f, pre);
- }
- }
-
- public void transform(float a,
- float b,
- float c,
- float d,
- float e,
- float f,
- float[] pre) {
- int incr = 0;
- float[] tempParams;
- float[] origParams;
- switch (type) {
-
- case 'z':
- case 'Z':
- return;
- case 'M':
- case 'L':
- case 'T':
- incr = 2;
- pre[0] = params[params.length - 2];
- pre[1] = params[params.length - 1];
- for (int i = 0; i < params.length; i += incr) {
- matrix(a, b, c, d, e, f, i, i + 1);
- }
- break;
- case 'm':
- case 'l':
- case 't':
- incr = 2;
- pre[0] += params[params.length - 2];
- pre[1] += params[params.length - 1];
- for (int i = 0; i < params.length; i += incr) {
- matrix(a, b, c, d, 0, 0, i, i + 1);
- }
- break;
- case 'h':
- type = 'l';
- pre[0] += params[params.length - 1];
-
- tempParams = new float[params.length * 2];
- origParams = params;
- params = tempParams;
- for (int i = 0; i < params.length; i += 2) {
- params[i] = origParams[i / 2];
- params[i + 1] = 0;
- matrix(a, b, c, d, 0, 0, i, i + 1);
- }
-
- break;
- case 'H':
- type = 'L';
- pre[0] = params[params.length - 1];
- tempParams = new float[params.length * 2];
- origParams = params;
- params = tempParams;
- for (int i = 0; i < params.length; i += 2) {
- params[i] = origParams[i / 2];
- params[i + 1] = pre[1];
- matrix(a, b, c, d, e, f, i, i + 1);
- }
- break;
- case 'v':
- pre[1] += params[params.length - 1];
- type = 'l';
- tempParams = new float[params.length * 2];
- origParams = params;
- params = tempParams;
- for (int i = 0; i < params.length; i += 2) {
- params[i] = 0;
- params[i + 1] = origParams[i / 2];
- matrix(a, b, c, d, 0, 0, i, i + 1);
- }
- break;
- case 'V':
- type = 'L';
- pre[1] = params[params.length - 1];
- tempParams = new float[params.length * 2];
- origParams = params;
- params = tempParams;
- for (int i = 0; i < params.length; i += 2) {
- params[i] = pre[0];
- params[i + 1] = origParams[i / 2];
- matrix(a, b, c, d, e, f, i, i + 1);
- }
- break;
- case 'C':
- case 'S':
- case 'Q':
- pre[0] = params[params.length - 2];
- pre[1] = params[params.length - 1];
- for (int i = 0; i < params.length; i += 2) {
- matrix(a, b, c, d, e, f, i, i + 1);
- }
- break;
- case 's':
- case 'q':
- case 'c':
- pre[0] += params[params.length - 2];
- pre[1] += params[params.length - 1];
- for (int i = 0; i < params.length; i += 2) {
- matrix(a, b, c, d, 0, 0, i, i + 1);
- }
- break;
- case 'a':
- incr = 7;
- pre[0] += params[params.length - 2];
- pre[1] += params[params.length - 1];
- for (int i = 0; i < params.length; i += incr) {
- matrix(a, b, c, d, 0, 0, i, i + 1);
- double ang = Math.toRadians(params[i + 2]);
- params[i + 2] = (float) Math.toDegrees(ang + Math.atan2(b, d));
- matrix(a, b, c, d, 0, 0, i + 5, i + 6);
- }
- break;
- case 'A':
- incr = 7;
- pre[0] = params[params.length - 2];
- pre[1] = params[params.length - 1];
- for (int i = 0; i < params.length; i += incr) {
- matrix(a, b, c, d, e, f, i, i + 1);
- double ang = Math.toRadians(params[i + 2]);
- params[i + 2] = (float) Math.toDegrees(ang + Math.atan2(b, d));
- matrix(a, b, c, d, e, f, i + 5, i + 6);
- }
- break;
-
- }
- }
-
- void matrix(float a,
- float b,
- float c,
- float d,
- float e,
- float f,
- int offx,
- int offy) {
- float inx = (offx < 0) ? 1 : params[offx];
- float iny = (offy < 0) ? 1 : params[offy];
- float x = inx * a + iny * c + e;
- float y = inx * b + iny * d + f;
- if (offx >= 0) {
- params[offx] = x;
- }
- if (offy >= 0) {
- params[offy] = y;
- }
- }
- }
-
- public VdPath() {
- mName = this.toString(); // to ensure paths have unique names
- }
-
- /**
- * TODO: support rotation attribute for stroke width
- */
- public void transform(float a, float b, float c, float d, float e, float f) {
- mStrokeWidth *= Math.hypot(a + b, c + d);
- Node.transform(a, b, c, d, e, f, mNode);
- }
-}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPreview.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPreview.java
deleted file mode 100644
index d5721a9..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPreview.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.util.AssetUtil;
-import com.google.common.base.Charsets;
-import com.sun.org.apache.xml.internal.serialize.OutputFormat;
-import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-
-import java.awt.image.BufferedImage;
-import java.io.*;
-
-/**
- * Generate a Image based on the VectorDrawable's XML content.
- *
- * <p>This class also contains a main method, which can be used to preview a vector drawable file.
- */
-public class VdPreview {
-
- private static final String ANDROID_ALPHA = "android:alpha";
- private static final String ANDROID_AUTO_MIRRORED = "android:autoMirrored";
- private static final String ANDROID_HEIGHT = "android:height";
- private static final String ANDROID_WIDTH = "android:width";
- public static final int MAX_PREVIEW_IMAGE_SIZE = 4096;
- public static final int MIN_PREVIEW_IMAGE_SIZE = 1;
-
- /**
- * This encapsulates the information used to determine the preview image size.
- * The reason we have different ways here is that both Studio UI and build process need
- * to use this common code path to generate images for vectordrawable.
- * When {@value mUseWidth} is true, use {@code mImageMaxDimension} as the maximum
- * dimension value while keeping the aspect ratio.
- * Otherwise, use {@code mImageScale} to scale the image based on the XML's size information.
- */
- public static class TargetSize {
- private boolean mUseWidth;
-
- private int mImageMaxDimension;
- private float mImageScale;
-
- private TargetSize(boolean useWidth, int imageWidth, float imageScale) {
- mUseWidth = useWidth;
- mImageMaxDimension = imageWidth;
- mImageScale = imageScale;
- }
-
- public static TargetSize createSizeFromWidth(int imageWidth) {
- return new TargetSize(true, imageWidth, 0.0f);
- }
-
- public static TargetSize createSizeFromScale(float imageScale) {
- return new TargetSize(false, 0, imageScale);
- }
- }
-
- /**
- * Since we allow overriding the vector drawable's size, we also need to keep
- * the original size and aspect ratio.
- */
- public static class SourceSize {
- public int getHeight() {
- return mSourceHeight;
- }
-
- public int getWidth() {
- return mSourceWidth;
- }
-
- private int mSourceWidth;
- private int mSourceHeight;
- }
-
-
- /**
- * @return a format object for XML formatting.
- */
- @NonNull
- private static OutputFormat getPrettyPrintFormat() {
- OutputFormat format = new OutputFormat();
- format.setLineWidth(120);
- format.setIndenting(true);
- format.setIndent(4);
- format.setEncoding("UTF-8");
- format.setOmitComments(true);
- return format;
- }
-
- /**
- * Get the vector drawable's original size.
- */
- public static SourceSize getVdOriginalSize(@NonNull Document document) {
- Element root = document.getDocumentElement();
- SourceSize srcSize = new SourceSize();
- // Update attributes, note that attributes as width and height are required,
- // while others are optional.
- NamedNodeMap attr = root.getAttributes();
- Node nodeAttr = attr.getNamedItem(ANDROID_WIDTH);
- assert nodeAttr != null;
- srcSize.mSourceWidth = parseDimension(0, nodeAttr, false);
-
- nodeAttr = attr.getNamedItem(ANDROID_HEIGHT);
- assert nodeAttr != null;
- srcSize.mSourceHeight = parseDimension(0, nodeAttr, false);
- return srcSize;
- }
-
- /**
- * The UI can override some properties of the Vector drawable.
- * In order to override in an uniform way, we re-parse the XML file
- * and pick the appropriate attributes to override.
- *
- * @param document the parsed document of original VectorDrawable's XML file.
- * @param info incoming override information for VectorDrawable.
- * @param errorLog log for the parsing errors and warnings.
- * @param srcSize as an output, store the original size of the VectorDrawable
- * @return the overridden XML file in one string. If exception happens
- * or no attributes needs to be overriden, return null.
- */
- @Nullable
- public static String overrideXmlContent(@NonNull Document document,
- @NonNull VdOverrideInfo info,
- @Nullable StringBuilder errorLog) {
- boolean isXmlFileContentChanged = false;
- Element root = document.getDocumentElement();
-
- // Update attributes, note that attributes as width and height are required,
- // while others are optional.
- NamedNodeMap attr = root.getAttributes();
- if (info.needsOverrideWidth()) {
- Node nodeAttr = attr.getNamedItem(ANDROID_WIDTH);
- int overrideValue = info.getWidth();
- int originalValue = parseDimension(overrideValue, nodeAttr, true);
- if (originalValue != overrideValue) {
- isXmlFileContentChanged = true;
- }
- }
- if (info.needsOverrideHeight()) {
- Node nodeAttr = attr.getNamedItem(ANDROID_HEIGHT);
- int overrideValue = info.getHeight();
- int originalValue = parseDimension(overrideValue, nodeAttr, true);
- if (originalValue != overrideValue) {
- isXmlFileContentChanged = true;
- }
- }
- if (info.needsOverrideOpacity()) {
- Node nodeAttr = attr.getNamedItem(ANDROID_ALPHA);
- String opacityValue = String.format("%.2f", info.getOpacity() / 100.0f);
- if (nodeAttr != null) {
- nodeAttr.setTextContent(opacityValue);
- }
- else {
- root.setAttribute(ANDROID_ALPHA, opacityValue);
- }
- isXmlFileContentChanged = true;
- }
- // When auto mirror is set to true, then we always need to set it.
- // Because SVG has no such attribute at all.
- if (info.needsOverrideAutoMirrored()) {
- Node nodeAttr = attr.getNamedItem(ANDROID_AUTO_MIRRORED);
- if (nodeAttr != null) {
- nodeAttr.setTextContent("true");
- }
- else {
- root.setAttribute(ANDROID_AUTO_MIRRORED, "true");
- }
- isXmlFileContentChanged = true;
- }
-
- if (isXmlFileContentChanged) {
- // Prettify the XML string from the document.
- StringWriter stringOut = new StringWriter();
- XMLSerializer serial = new XMLSerializer(stringOut, getPrettyPrintFormat());
- try {
- serial.serialize(document);
- }
- catch (IOException e) {
- if (errorLog != null) {
- errorLog.append("Exception while parsing XML file:\n").append(e.getMessage());
- }
- }
- return stringOut.toString();
- } else {
- return null;
- }
- }
-
- /**
- * Query the dimension info and override it if needed.
- *
- * @param overrideValue the dimension value to override with.
- * @param nodeAttr the node who contains dimension info.
- * @param override if true then override the dimension.
- * @return the original dimension value.
- */
- private static int parseDimension(int overrideValue, Node nodeAttr, boolean override) {
- assert nodeAttr != null;
- String content = nodeAttr.getTextContent();
- assert content.endsWith("dp");
- int originalValue = Integer.parseInt(content.substring(0, content.length() - 2));
-
- if (override) {
- nodeAttr.setTextContent(overrideValue + "dp");
- }
- return originalValue;
- }
-
- /**
- * This generates an image according to the VectorDrawable's content {@code xmlFileContent}.
- * At the same time, {@vdErrorLog} captures all the errors found during parsing.
- * The size of image is determined by the {@code size}.
- *
- * @param targetSize the size of result image.
- * @param xmlFileContent VectorDrawable's XML file's content.
- * @param vdErrorLog log for the parsing errors and warnings.
- * @return an preview image according to the VectorDrawable's XML
- */
- @Nullable
- public static BufferedImage getPreviewFromVectorXml(@NonNull TargetSize targetSize,
- @Nullable String xmlFileContent,
- @Nullable StringBuilder vdErrorLog) {
- if (xmlFileContent == null || xmlFileContent.isEmpty()) {
- return null;
- }
- VdParser p = new VdParser();
- VdTree vdTree;
-
- InputStream inputStream = new ByteArrayInputStream(
- xmlFileContent.getBytes(Charsets.UTF_8));
- vdTree = p.parse(inputStream, vdErrorLog);
- if (vdTree == null) {
- return null;
- }
-
- // If the forceImageSize is set (>0), then we honor that.
- // Otherwise, we will ask the vectorDrawable for the prefer size, then apply the imageScale.
- float vdWidth = vdTree.getBaseWidth();
- float vdHeight = vdTree.getBaseHeight();
- float imageWidth;
- float imageHeight;
- int forceImageSize = targetSize.mImageMaxDimension;
- float imageScale = targetSize.mImageScale;
-
- if (forceImageSize > 0) {
- // The goal here is to generate an image within certain size, while keeping the
- // aspect ration as much as we can.
- // If it is scaling too much to fit in, we log an error.
- float maxVdSize = Math.max(vdWidth, vdHeight);
- float ratioToForceImageSize = forceImageSize / maxVdSize;
- float scaledWidth = ratioToForceImageSize * vdWidth;
- float scaledHeight = ratioToForceImageSize * vdHeight;
- imageWidth = Math.max(MIN_PREVIEW_IMAGE_SIZE, Math.min(MAX_PREVIEW_IMAGE_SIZE, scaledWidth));
- imageHeight = Math.max(MIN_PREVIEW_IMAGE_SIZE, Math.min(MAX_PREVIEW_IMAGE_SIZE, scaledHeight));
- if (scaledWidth != imageWidth || scaledHeight != imageHeight) {
- vdErrorLog.append("Invalid image size, can't fit in a square whose size is" + forceImageSize);
- }
- } else {
- imageWidth = vdWidth * imageScale;
- imageHeight = vdHeight * imageScale;
- }
-
- // Create the image according to the vectorDrawable's aspect ratio.
-
- BufferedImage image = AssetUtil.newArgbBufferedImage((int)imageWidth, (int)imageHeight);
- vdTree.drawIntoImage(image);
- return image;
- }
-
- public static void main(String[] args) {
- System.out.println("Hello from sdk-common-lib.");
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdTree.java b/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdTree.java
deleted file mode 100644
index 883b821..0000000
--- a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdTree.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.ide.common.util.AssetUtil;
-
-import java.awt.*;
-import java.awt.geom.Path2D;
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Used to represent the whole VectorDrawable XML file's tree.
- */
-class VdTree {
- private static Logger logger = Logger.getLogger(VdTree.class.getSimpleName());
-
- VdGroup mCurrentGroup = new VdGroup();
- ArrayList<VdElement> mChildren;
-
- float mBaseWidth = 1;
- float mBaseHeight = 1;
- float mPortWidth = 1;
- float mPortHeight = 1;
- float mRootAlpha = 1;
-
- /**
- * Ensure there is at least one animation for every path in group (linking
- * them by names) Build the "current" path based on the first group
- */
- void parseFinish() {
- mChildren = mCurrentGroup.getChildren();
- }
-
- void add(VdElement pathOrGroup) {
- mCurrentGroup.add(pathOrGroup);
- }
-
- float getBaseWidth(){
- return mBaseWidth;
- }
-
- float getBaseHeight(){
- return mBaseHeight;
- }
-
- private void drawInternal(Graphics g, int w, int h) {
- float scaleX = w / mPortWidth;
- float scaleY = h / mPortHeight;
- float minScale = Math.min(scaleX, scaleY);
-
- if (mChildren == null) {
- logger.log(Level.FINE, "no pathes");
- return;
- }
- ((Graphics2D) g).scale(scaleX, scaleY);
-
- Rectangle bounds = null;
- for (int i = 0; i < mChildren.size(); i++) {
- // TODO: do things differently when it is a path or group!!
- VdPath path = (VdPath) mChildren.get(i);
- logger.log(Level.FINE, "mCurrentPaths[" + i + "]=" + path.getName() +
- Integer.toHexString(path.mFillColor));
- if (mChildren.get(i) != null) {
- Rectangle r = drawPath(path, g, w, h, minScale);
- if (bounds == null) {
- bounds = r;
- } else {
- bounds.add(r);
- }
- }
- }
- logger.log(Level.FINE, "Rectangle " + bounds);
- logger.log(Level.FINE, "Port " + mPortWidth + "," + mPortHeight);
- double right = mPortWidth - bounds.getMaxX();
- double bot = mPortHeight - bounds.getMaxY();
- logger.log(Level.FINE, "x " + bounds.getMinX() + ", " + right);
- logger.log(Level.FINE, "y " + bounds.getMinY() + ", " + bot);
- }
-
- private Rectangle drawPath(VdPath path, Graphics canvas, int w, int h, float scale) {
-
- Path2D path2d = new Path2D.Double();
- Graphics2D g = (Graphics2D) canvas;
- path.toPath(path2d);
-
- // TODO: Use AffineTransform to apply group's transformation info.
- double theta = Math.toRadians(path.mRotate);
- g.rotate(theta, path.mRotateX, path.mRotateY);
- if (path.mClip) {
- logger.log(Level.FINE, "CLIP");
-
- g.setColor(Color.RED);
- g.fill(path2d);
-
- }
- if (path.mFillColor != 0) {
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.setColor(new Color(path.mFillColor, true));
- g.fill(path2d);
- }
- if (path.mStrokeColor != 0) {
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.setStroke(new BasicStroke(path.mStrokeWidth));
- g.setColor(new Color(path.mStrokeColor, true));
- g.draw(path2d);
- }
-
- g.rotate(-theta, path.mRotateX, path.mRotateY);
- return path2d.getBounds();
- }
-
- /**
- * Draw the VdTree into an image.
- * If the root alpha is less than 1.0, then draw into a temporary image,
- * then draw into the result image applying alpha blending.
- */
- public void drawIntoImage(BufferedImage image) {
- Graphics2D gFinal = (Graphics2D) image.getGraphics();
- int width = image.getWidth();
- int height = image.getHeight();
- gFinal.setColor(new Color(255, 255, 255, 0));
- gFinal.fillRect(0, 0, width, height);
-
- float rootAlpha = mRootAlpha;
- if (rootAlpha < 1.0) {
- BufferedImage alphaImage = AssetUtil.newArgbBufferedImage(width, height);
- Graphics2D gTemp = (Graphics2D)alphaImage.getGraphics();
- drawInternal(gTemp, width, height);
- gFinal.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, rootAlpha));
- gFinal.drawImage(alphaImage, 0, 0, null);
- gTemp.dispose();
- } else {
- drawInternal(gFinal, width, height);
- }
- gFinal.dispose();
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/blame/MessageJsonSerializerTest.java b/base/sdk-common/src/test/java/com/android/ide/common/blame/MessageJsonSerializerTest.java
deleted file mode 100644
index fdb803e..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/blame/MessageJsonSerializerTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import static org.junit.Assert.assertEquals;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.experimental.runners.Enclosed;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-
- at RunWith(Enclosed.class)
-public class MessageJsonSerializerTest {
-
- private static GsonBuilder sGsonBuilder = new GsonBuilder();
-
- static {
- MessageJsonSerializer.registerTypeAdapters(sGsonBuilder);
- }
-
- @RunWith(Parameterized.class)
- public static class DeserializeTest {
-
- private static Gson sGson;
-
- @Parameterized.Parameter(value = 0)
- public Message message;
-
- @Parameterized.Parameter(value = 1)
- public String serializedMessage;
-
- @Parameterized.Parameters(name = "fromJson(\"{1}\") should give {0}")
- public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][]{
- {new Message(
- Message.Kind.ERROR,
- "some error text",
- new SourceFilePosition(
- new SourceFile(new File("/path/file.java")),
- new SourcePosition(1, 3, 5))),
- "{"
- + "\"kind\":\"Error\", "
- + "\"text\":\"some error text\","
- + "\"sources\": {"
- + "\"file\":\"/path/file.java\","
- + "\"position\":{"
- + "\"startLine\":1,"
- + "\"startColumn\":3,"
- + "\"startOffset\":5"
- + "}"
- + "}}"
- }, {
- new Message(
- Message.Kind.ERROR,
- "errorText",
- new SourceFilePosition(
- new File("error/source"),
- new SourcePosition(1,2,3,4,5,6))
- ),
- "{\"kind\":\"ERROR\",\"text\":\"errorText\",\"sourcePath\":\"error/source\","
- + "\"position\":{\"startLine\":1,\"startColumn\":2,\"startOffset\":3,"
- + "\"endLine\":4,\"endColumn\":5,\"endOffset\":6},\"original\":\"\"}\n"
- }, {new Message(Message.Kind.SIMPLE, "something else", new SourceFilePosition(SourceFile.UNKNOWN, SourcePosition.UNKNOWN)),
- "{\"kind\":\"SIMPLE\","
- + "\"text\":\"something else\",\"position\":{},\"original\":\"something else\"}"
- }, {
- new Message(
- Message.Kind.SIMPLE,
- "Warning: AndroidManifest.xml already defines debuggable (in http://"
- + "schemas.android.com/apk/res/android); using existing value "
- + "in manifest.",
- SourceFilePosition.UNKNOWN),
- "{\"kind\":\"simple\",\"text\":\"Warning: AndroidManifest.xml already defines "
- + "debuggable (in http://schemas.android.com/apk/res/android); using "
- + "existing value in manifest.\",\"sources\":\"\"}"
- }, {
- new Message(
- Message.Kind.UNKNOWN,
- "Text.",
- SourceFilePosition.UNKNOWN),
- "{\"text\":\"Text.\"}",
-
- }});
- }
-
- @BeforeClass
- public static void initGson() {
- sGson = sGsonBuilder.create();
- }
-
- @AfterClass
- public static void removeGson() {
- sGson = null;
- }
-
- @Test
- public void check() {
- assertEquals(message, sGson.fromJson(serializedMessage, Message.class));
- }
-
-
- }
-
- @RunWith(Parameterized.class)
- public static class RoundTripTest {
-
- private static Gson sGson;
-
- @Parameterized.Parameter
- public Message message;
-
-
- @Parameterized.Parameters(name = "{0}")
- public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][]{{
- new Message(
- Message.Kind.ERROR,
- "some error text",
- "original error text",
- new SourceFilePosition(
- new SourceFile(new File("/path/file.java")),
- new SourcePosition(1, 3, 5)))
- }, {
- new Message(
- Message.Kind.SIMPLE,
- "something else",
- new SourceFilePosition(SourceFile.UNKNOWN, SourcePosition.UNKNOWN))
- }, {
- new Message(
- Message.Kind.SIMPLE,
- "Warning: AndroidManifest.xml already defines debuggable (in http://"
- + "schemas.android.com/apk/res/android); using existing value "
- + "in manifest.",
- SourceFilePosition.UNKNOWN)
- }});
- }
-
- @BeforeClass
- public static void initGson() {
- sGson = sGsonBuilder.create();
- }
-
- @AfterClass
- public static void removeGson() {
- sGson = null;
- }
-
- @Test
- public void check() {
- assertEquals(message, sGson.fromJson(sGson.toJson(message), Message.class));
- }
-
-
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/blame/ParsingProcessOutputHandlerTest.java b/base/sdk-common/src/test/java/com/android/ide/common/blame/ParsingProcessOutputHandlerTest.java
deleted file mode 100644
index c065001..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/blame/ParsingProcessOutputHandlerTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.blame;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.blame.parser.ParsingFailedException;
-import com.android.ide.common.blame.parser.PatternAwareOutputParser;
-import com.android.ide.common.blame.parser.ToolOutputParser;
-import com.android.ide.common.blame.parser.util.OutputLineReader;
-import com.android.ide.common.process.ProcessException;
-import com.android.ide.common.process.ProcessOutput;
-import com.android.ide.common.res2.RecordingLogger;
-import com.android.utils.ILogger;
-import com.google.common.base.Charsets;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-public class ParsingProcessOutputHandlerTest {
-
- private static ParsingProcessOutputHandler sParsingProcessOutputHandler;
-
- private static RecordingLogger sLogger;
-
- private static PatternAwareOutputParser sFakePatternParser;
-
- private static MessageReceiver sMessageReceiver;
-
- @BeforeClass
- public static void setUp() throws Exception {
- sLogger = new RecordingLogger();
- sFakePatternParser = new FakePatternAwareOutputParser();
-
- sMessageReceiver = Mockito.mock(MessageReceiver.class);
- sParsingProcessOutputHandler = new ParsingProcessOutputHandler(
- new ToolOutputParser(sFakePatternParser, sLogger),
- sMessageReceiver);
- }
-
- @Test
- public void testRewriteMessages() throws IOException, ProcessException {
-
- String original = "error example\ntwo line error\nnext line\nsomething else";
-
- sParsingProcessOutputHandler.handleOutput(processOutputFromErrorString(original));
-
- Mockito.verify(sMessageReceiver).receiveMessage(new Message(
- Message.Kind.ERROR,
- "errorText",
- "originalText",
- new SourceFilePosition(
- new SourceFile(FakePatternAwareOutputParser.ERROR_EXAMPLE_FILE),
- new SourcePosition(1, 2, 3, 4, 5, 6)
- )));
-
- Mockito.verify(sMessageReceiver).receiveMessage(new Message(
- Message.Kind.WARNING,
- "two line warning",
- new SourceFilePosition(
- new SourceFile(FakePatternAwareOutputParser.TWO_LINE_ERROR_FILE),
- new SourcePosition(1, 2, -1)
- )));
-
-
- Mockito.verify(sMessageReceiver).receiveMessage(new Message(
- Message.Kind.SIMPLE,
- "something else",
- SourceFilePosition.UNKNOWN));
- Mockito.verifyNoMoreInteractions(sMessageReceiver);
-
- String expected = "AGPBI: {"
- + "\"kind\":\"error\","
- + "\"text\":\"errorText\","
- + "\"sources\":[{"
- + "\"file\":\"" + FakePatternAwareOutputParser.ERROR_EXAMPLE_FILE.getAbsolutePath()
- + "\",\"position\":{\"startLine\":1,\"startColumn\":2,\"startOffset\":3,"
- + "\"endLine\":4,\"endColumn\":5,\"endOffset\":6}}],"
- + "\"original\":\"original_text\"}\n"
- + "AGPBI: {\"kind\":\"warning\","
- + "\"text\":\"two line warning\","
- + "\"sources\":[{\"file\":\"" +
- FakePatternAwareOutputParser.TWO_LINE_ERROR_FILE.getAbsolutePath() + "\","
- + "\"position\":{\"startLine\":1,\"startColumn\":2}}]}\n"
- + "AGPBI: {\"kind\":\"simple\","
- + "\"text\":\"something else\","
- + "\"sources\":[{}]}";
- }
-
- @Test
- public void parseException() throws IOException, ProcessException {
- String original = "two line error";
-
- sParsingProcessOutputHandler.handleOutput(processOutputFromErrorString(original));
-
- Mockito.verifyNoMoreInteractions(sMessageReceiver);
- }
-
- private static ProcessOutput processOutputFromErrorString(String original) throws IOException {
- ProcessOutput processOutput = sParsingProcessOutputHandler.createOutput();
- processOutput.getErrorOutput().write(original.getBytes(Charsets.UTF_8));
- return processOutput;
- }
-
- private static class FakePatternAwareOutputParser implements PatternAwareOutputParser {
- static final File ERROR_EXAMPLE_FILE = new File("error/source");
- static final File TWO_LINE_ERROR_FILE = new File("error/source/2");
- @Override
- public boolean parse(@NonNull String line, @NonNull OutputLineReader reader,
- @NonNull List<Message> messages, @NonNull ILogger logger)
- throws ParsingFailedException {
- if (line.equals("two line error")) {
- String nextLine = reader.readLine();
- if ("next line".equals(nextLine)) {
- messages.add(new Message(
- Message.Kind.WARNING,
- "two line warning",
- "two line warning",
- new SourceFilePosition(
- TWO_LINE_ERROR_FILE,
- new SourcePosition(1, 2, -1))));
- } else {
- throw new ParsingFailedException();
- }
- return true;
- }
- if (line.equals("error example")) {
- messages.add(
- new Message(
- Message.Kind.ERROR,
- "errorText",
- "original_text",
- new SourceFilePosition(
- ERROR_EXAMPLE_FILE,
- new SourcePosition(1, 2, 3, 4, 5, 6))));
- return true;
- }
- return false;
- }
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java b/base/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java
deleted file mode 100644
index 116e2cd..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.repository;
-
-import com.android.ide.common.res2.BaseTestCase;
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
-import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_LOWER;
-
-/**
- * Test class for {@see GradleCoordinate}
- */
-public class GradleCoordinateTest extends BaseTestCase {
-
- public void testParseCoordinateString() throws Exception {
- GradleCoordinate expected = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- GradleCoordinate actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
- assertEquals(expected, actual);
-
- expected = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
- assertEquals(expected, actual);
-
- expected = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.+");
- assertEquals(expected, actual);
-
- expected = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV);
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
- assertEquals(expected, actual);
-
- List<GradleCoordinate.RevisionComponent> revisionList =
- Lists.<GradleCoordinate.RevisionComponent>newArrayList(GradleCoordinate.PLUS_REV);
- expected = new GradleCoordinate("a.b.c", "package", revisionList,
- GradleCoordinate.ArtifactType.JAR);
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+ at jar");
- assertEquals(expected, actual);
-
- expected = new GradleCoordinate("a.b.c", "package", revisionList,
- GradleCoordinate.ArtifactType.AAR);
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+ at AAR");
- assertEquals(expected, actual);
-
- expected = new GradleCoordinate("a.b.c", "package",
- new GradleCoordinate.StringComponent("v1"),
- new GradleCoordinate.StringComponent("v2"));
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1.v2");
- assertEquals(expected, actual);
-
- expected = new GradleCoordinate("a.b.c", "package",
- GradleCoordinate.ListComponent.of(
- new GradleCoordinate.StringComponent("v1"),
- new GradleCoordinate.NumberComponent(1)));
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1-1");
- assertEquals(expected, actual);
-
- expected = new GradleCoordinate("a.b.c", "package",
- GradleCoordinate.ListComponent.of(
- new GradleCoordinate.StringComponent("v1"),
- new GradleCoordinate.NumberComponent(1)),
- new GradleCoordinate.NumberComponent(17),
- GradleCoordinate.ListComponent.of(
- new GradleCoordinate.NumberComponent(0),
- new GradleCoordinate.StringComponent("rc"),
- new GradleCoordinate.StringComponent("SNAPSHOT")));
- actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1-1.17.0-rc-SNAPSHOT");
- assertEquals(expected, actual);
- }
-
- public void testToString() throws Exception {
- String expected = "a.b.c:package:5.4.2";
- String actual = new GradleCoordinate("a.b.c", "package", 5, 4, 2).toString();
- assertEquals(expected, actual);
-
- expected = "a.b.c:package:5.4.+";
- actual = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE)
- .toString();
- assertEquals(expected, actual);
-
- expected = "a.b.c:package:5.+";
- actual = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE)
- .toString();
- assertEquals(expected, actual);
-
- expected = "a.b.c:package:+";
- actual = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV).toString();
- assertEquals(expected, actual);
-
- expected = "a.b.c:package:+ at jar";
- List<GradleCoordinate.RevisionComponent> revisionList =
- Lists.<GradleCoordinate.RevisionComponent>newArrayList(GradleCoordinate.PLUS_REV);
- actual = new GradleCoordinate("a.b.c", "package", revisionList,
- GradleCoordinate.ArtifactType.JAR).toString();
- assertEquals(expected, actual);
-
- expected = "a.b.c:package:+ at aar";
- actual = new GradleCoordinate("a.b.c", "package", revisionList,
- GradleCoordinate.ArtifactType.AAR).toString();
- assertEquals(expected, actual);
-
- expected = "com.google.maps.android:android-maps-utils:0.3";
- actual = GradleCoordinate.parseCoordinateString(expected).toString();
- assertEquals(expected, actual);
-
- expected = "a.b.c:package:v1.v2";
- actual = new GradleCoordinate("a.b.c", "package",
- new GradleCoordinate.StringComponent("v1"),
- new GradleCoordinate.StringComponent("v2")).toString();
- assertEquals(expected, actual);
-
- expected = "a.b.c:package:v1-1.17.0-rc-SNAPSHOT";
- actual = new GradleCoordinate("a.b.c", "package",
- GradleCoordinate.ListComponent.of(
- new GradleCoordinate.StringComponent("v1"),
- new GradleCoordinate.NumberComponent(1)),
- new GradleCoordinate.NumberComponent(17),
- GradleCoordinate.ListComponent.of(
- new GradleCoordinate.NumberComponent(0),
- new GradleCoordinate.StringComponent("rc"),
- new GradleCoordinate.StringComponent("SNAPSHOT"))).toString();
- assertEquals(expected, actual);
- }
-
- public void testIsSameArtifact() throws Exception {
- GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
- assertTrue(a.isSameArtifact(b));
- assertTrue(b.isSameArtifact(a));
-
- a = new GradleCoordinate("a.b", "package", 5, 4, 2);
- b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
- assertFalse(a.isSameArtifact(b));
- assertFalse(b.isSameArtifact(a));
-
- a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- b = new GradleCoordinate("a.b.c", "feature", 5, 5, 5);
- assertFalse(a.isSameArtifact(b));
- assertFalse(b.isSameArtifact(a));
- }
-
- public void testCompareVersions() {
- // Requirements order
- GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 4, 10);
- b = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
- b = new GradleCoordinate("a.b.c", "package", 6, 0, 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- b = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) == 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
-
- assertTrue((COMPARE_PLUS_HIGHER.compare(a, b) < 0) == ("package".compareTo("feature") < 0));
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- b = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- b = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
-
- a = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
- b = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
- assert a != null;
- assert b != null;
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
-
- a = GradleCoordinate.parseCoordinateString("a.b.c:package:5");
- b = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
- assert a != null;
- assert b != null;
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
-
- a = GradleCoordinate.parseCoordinateString("a.b.c:package:1.any");
- b = GradleCoordinate.parseCoordinateString("a.b.c:package:1.1");
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
-
- a = GradleCoordinate.parseCoordinateString("a.b.c:package:1-1");
- b = GradleCoordinate.parseCoordinateString("a.b.c:package:1-2");
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
- }
-
- public void testCompareSpecificity() {
- // Order of specificity
- GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(b, a) > 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 4, 10);
- b = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
- b = new GradleCoordinate("a.b.c", "package", 6, 0, 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- b = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) == 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
-
- assertTrue((COMPARE_PLUS_LOWER.compare(a, b) < 0) == ("package".compareTo("feature") < 0));
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- b = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
-
- a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
- b = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
-
- a = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
- b = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
- assert a != null;
- assert b != null;
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(b, a) < 0);
- }
-
- public void testGetVersions() {
- GradleCoordinate c = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- assertEquals(5, c.getMajorVersion());
- assertEquals(4, c.getMinorVersion());
- assertEquals(2, c.getMicroVersion());
- }
-
- public void testSameSharedDifferentLengths() {
- GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4);
- GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(b, a) > 0);
- }
-
- public void testSameSharedDifferentLengthsWithZeros() {
- GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4);
- GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 4, 0, 0, 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) == 0);
- assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) == 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(a, b) == 0);
- assertTrue(COMPARE_PLUS_LOWER.compare(b, a) == 0);
- }
-
- public void testParseVersionOnly() {
- String revision = "15.32.64";
- GradleCoordinate a = GradleCoordinate.parseVersionOnly(revision);
- assertEquals(revision, a.getFullRevision());
- String revisionB = "16.12.0-rc1";
- GradleCoordinate b = GradleCoordinate.parseVersionOnly(revisionB);
- assertEquals(revisionB, b.getFullRevision());
- assertTrue(b.isPreview());
- }
-
- public void testIsPreview_ignoresFalsePositives() {
- String revisionB = "16.12.0-march";
- GradleCoordinate b = GradleCoordinate.parseVersionOnly(revisionB);
- assertFalse(b.isPreview());
- }
-
- public void testLeadingZeroes() {
- // Regression test for https://code.google.com/p/android/issues/detail?id=74612
- // The Gradle dependency
- // compile 'com.google.android.gms:play-services:5.2.08'
- // is not the same as
- // compile 'com.google.android.gms:play-services:5.2.8'
- // So we have to keep string representations around
-
- GradleCoordinate v5_0_89 = GradleCoordinate.parseCoordinateString(
- "com.google.android.gms:play-services:5.0.89");
- assertNotNull(v5_0_89);
- assertEquals("com.google.android.gms:play-services:5.0.89", v5_0_89.toString());
- assertEquals("5.0.89", v5_0_89.getFullRevision());
-
- GradleCoordinate v5_2_08 = GradleCoordinate.parseCoordinateString(
- "com.google.android.gms:play-services:5.2.08");
- assertNotNull(v5_2_08);
- assertEquals("5.2.08", v5_2_08.getFullRevision());
-
- assertEquals("com.google.android.gms:play-services:5.2.08", v5_2_08.toString());
-
- // Same artifact: 5.2.08 == 5.2.8
- //noinspection ConstantConditions
- assertFalse(v5_2_08.equals(GradleCoordinate.parseCoordinateString(
- "com.google.android.gms:play-services:5.2.8")));
-
- assertEquals(
- GradleCoordinate.parseCoordinateString(
- "com.google.android.gms:play-services:5.2.08"),
- GradleCoordinate.parseCoordinateString(
- "com.google.android.gms:play-services:5.2.08"));
-
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/repository/ResourceVisibilityLookupTest.java b/base/sdk-common/src/test/java/com/android/ide/common/repository/ResourceVisibilityLookupTest.java
deleted file mode 100644
index a85cb0c..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/repository/ResourceVisibilityLookupTest.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.common.repository;
-
-import static com.android.SdkConstants.FN_PUBLIC_TXT;
-import static com.android.SdkConstants.FN_RESOURCE_TEXT;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-
-import com.android.builder.model.AndroidArtifact;
-import com.android.builder.model.AndroidLibrary;
-import com.android.builder.model.AndroidProject;
-import com.android.builder.model.Dependencies;
-import com.android.builder.model.Variant;
-import com.android.ide.common.resources.ResourceUrl;
-import com.android.resources.ResourceType;
-import com.android.testutils.TestUtils;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import junit.framework.TestCase;
-
-import org.easymock.IExpectationSetters;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-public class ResourceVisibilityLookupTest extends TestCase {
- public void test() throws IOException {
- AndroidLibrary library = createMockLibrary(
- ""
- + "int dimen activity_horizontal_margin 0x7f030000\n"
- + "int dimen activity_vertical_margin 0x7f030001\n"
- + "int id action_settings 0x7f060000\n"
- + "int layout activity_main 0x7f020000\n"
- + "int menu menu_main 0x7f050000\n"
- + "int string action_settings 0x7f040000\n"
- + "int string app_name 0x7f040001\n"
- + "int string hello_world 0x7f040002",
- ""
- + ""
- + "dimen activity_vertical\n"
- + "id action_settings\n"
- + "layout activity_main\n"
- );
-
- ResourceVisibilityLookup visibility = ResourceVisibilityLookup.create(library);
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
- assertFalse(visibility.isPrivate(ResourceType.ID, "action_settings"));
- assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
- //noinspection ConstantConditions
- assertTrue(visibility.isPrivate(ResourceUrl.parse("@dimen/activity_horizontal_margin")));
-
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical")); // public
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
- }
-
- public void testModelVersions() throws IOException {
- AndroidLibrary library = createMockLibrary(
- ""
- + "int dimen activity_horizontal_margin 0x7f030000\n"
- + "int dimen activity_vertical_margin 0x7f030001\n"
- + "int id action_settings 0x7f060000\n"
- + "int layout activity_main 0x7f020000\n"
- + "int menu menu_main 0x7f050000\n"
- + "int string action_settings 0x7f040000\n"
- + "int string app_name 0x7f040001\n"
- + "int string hello_world 0x7f040002",
- ""
- + ""
- + "dimen activity_vertical\n"
- + "id action_settings\n"
- + "layout activity_main\n"
- );
-
- AndroidArtifact mockArtifact = createMockArtifact(Collections.singletonList(library));
- Variant variant = createMockVariant(mockArtifact);
-
- AndroidProject project;
-
- project = createMockProject("1.0.1", 0);
- assertTrue(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
-
-
- project = createMockProject("1.1", 0);
- assertTrue(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
-
- project = createMockProject("1.2", 2);
- assertTrue(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
-
- project = createMockProject("1.3.0", 3);
- assertFalse(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
-
- project = createMockProject("2.5", 45);
- assertFalse(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
-
- ResourceVisibilityLookup visibility =new ResourceVisibilityLookup.Provider().get(project,
- variant);
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
- assertFalse(visibility.isPrivate(ResourceType.ID, "action_settings"));
- }
-
- public void testAllPrivate() throws IOException {
- AndroidLibrary library = createMockLibrary(
- ""
- + "int dimen activity_horizontal_margin 0x7f030000\n"
- + "int dimen activity_vertical_margin 0x7f030001\n"
- + "int id action_settings 0x7f060000\n"
- + "int layout activity_main 0x7f020000\n"
- + "int menu menu_main 0x7f050000\n"
- + "int string action_settings 0x7f040000\n"
- + "int string app_name 0x7f040001\n"
- + "int string hello_world 0x7f040002",
- ""
- );
-
- ResourceVisibilityLookup visibility = ResourceVisibilityLookup.create(library);
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
- assertTrue(visibility.isPrivate(ResourceType.ID, "action_settings"));
- assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical_margin"));
-
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
- }
-
- public void testNotDeclared() throws IOException {
- AndroidLibrary library = createMockLibrary("", null);
-
- ResourceVisibilityLookup visibility = ResourceVisibilityLookup.create(library);
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
- assertFalse(visibility.isPrivate(ResourceType.ID, "action_settings"));
- assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical"));
-
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
- }
-
- public void testCombined() throws IOException {
- AndroidLibrary library1 = createMockLibrary(
- ""
- + "int dimen activity_horizontal_margin 0x7f030000\n"
- + "int dimen activity_vertical_margin 0x7f030001\n"
- + "int id action_settings 0x7f060000\n"
- + "int layout activity_main 0x7f020000\n"
- + "int menu menu_main 0x7f050000\n"
- + "int string action_settings 0x7f040000\n"
- + "int string app_name 0x7f040001\n"
- + "int string hello_world 0x7f040002",
- ""
- );
- AndroidLibrary library2 = createMockLibrary(
- ""
- + "int layout foo 0x7f030001\n"
- + "int layout bar 0x7f060000\n",
- ""
- + "layout foo\n"
- );
-
- List<AndroidLibrary> androidLibraries = Arrays.asList(library1, library2);
- ResourceVisibilityLookup visibility = ResourceVisibilityLookup
- .create(androidLibraries, null);
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
- assertTrue(visibility.isPrivate(ResourceType.ID, "action_settings"));
- assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical_margin"));
- assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "foo"));
- assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "bar"));
-
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
- }
-
- public void testDependency() throws IOException {
- AndroidLibrary library1 = createMockLibrary(
- ""
- + "int dimen activity_horizontal_margin 0x7f030000\n"
- + "int dimen activity_vertical_margin 0x7f030001\n"
- + "int id action_settings 0x7f060000\n"
- + "int layout activity_main 0x7f020000\n"
- + "int menu menu_main 0x7f050000\n"
- + "int string action_settings 0x7f040000\n"
- + "int string app_name 0x7f040001\n"
- + "int string hello_world 0x7f040002",
- ""
- );
- AndroidLibrary library2 = createMockLibrary(
- ""
- + "int layout foo 0x7f030001\n"
- + "int layout bar 0x7f060000\n",
- ""
- + "layout foo\n",
- Collections.singletonList(library1)
- );
-
- List<AndroidLibrary> androidLibraries = Arrays.asList(library1, library2);
- ResourceVisibilityLookup visibility = ResourceVisibilityLookup
- .create(androidLibraries, null);
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
- assertTrue(visibility.isPrivate(ResourceType.ID, "action_settings"));
- assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
- assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical_margin"));
- assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "foo"));
- assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "bar"));
-
- assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
- }
-
- public void testManager() throws IOException {
- AndroidLibrary library = createMockLibrary(
- ""
- + "int dimen activity_horizontal_margin 0x7f030000\n"
- + "int dimen activity_vertical_margin 0x7f030001\n"
- + "int id action_settings 0x7f060000\n"
- + "int layout activity_main 0x7f020000\n"
- + "int menu menu_main 0x7f050000\n"
- + "int string action_settings 0x7f040000\n"
- + "int string app_name 0x7f040001\n"
- + "int string hello_world 0x7f040002",
- ""
- );
- ResourceVisibilityLookup.Provider provider = new ResourceVisibilityLookup.Provider();
- assertSame(provider.get(library), provider.get(library));
- assertTrue(provider.get(library).isPrivate(ResourceType.DIMEN,
- "activity_horizontal_margin"));
-
- AndroidArtifact artifact = createMockArtifact(Collections.singletonList(library));
- assertSame(provider.get(artifact), provider.get(artifact));
- assertTrue(provider.get(artifact).isPrivate(ResourceType.DIMEN,
- "activity_horizontal_margin"));
- }
-
- public static AndroidProject createMockProject(String modelVersion, int apiVersion) {
- AndroidProject project = createNiceMock(AndroidProject.class);
- expect(project.getApiVersion()).andReturn(apiVersion).anyTimes();
- expect(project.getModelVersion()).andReturn(modelVersion).anyTimes();
- replay(project);
-
- return project;
- }
-
- public static Variant createMockVariant(AndroidArtifact artifact) {
- Variant variant = createNiceMock(Variant.class);
- expect(variant.getMainArtifact()).andReturn(artifact).anyTimes();
- replay(variant);
- return variant;
-
- }
-
- public static AndroidArtifact createMockArtifact(List<AndroidLibrary> libraries) {
- Dependencies dependencies = createNiceMock(Dependencies.class);
- expect(dependencies.getLibraries()).andReturn(libraries).anyTimes();
- replay(dependencies);
-
- AndroidArtifact artifact = createNiceMock(AndroidArtifact.class);
- expect(artifact.getDependencies()).andReturn(dependencies).anyTimes();
- replay(artifact);
-
- return artifact;
- }
-
- public static AndroidLibrary createMockLibrary(String allResources, String publicResources)
- throws IOException {
- return createMockLibrary(allResources, publicResources,
- Collections.<AndroidLibrary>emptyList());
- }
-
- public static AndroidLibrary createMockLibrary(String allResources, String publicResources,
- List<AndroidLibrary> dependencies)
- throws IOException {
- final File tempDir = TestUtils.createTempDirDeletedOnExit();
-
- Files.write(allResources, new File(tempDir, FN_RESOURCE_TEXT), Charsets.UTF_8);
- File publicTxtFile = new File(tempDir, FN_PUBLIC_TXT);
- if (publicResources != null) {
- Files.write(publicResources, publicTxtFile, Charsets.UTF_8);
- }
- AndroidLibrary library = createNiceMock(AndroidLibrary.class);
- expect(library.getPublicResources()).andReturn(publicTxtFile).anyTimes();
-
- // Work around wildcard capture
- //expect(mock.getLibraryDependencies()).andReturn(dependencies).anyTimes();
- IExpectationSetters setter = expect(library.getLibraryDependencies());
- //noinspection unchecked
- setter.andReturn(dependencies);
- setter.anyTimes();
-
- replay(library);
- return library;
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/repository/SdkMavenRepositoryTest.java b/base/sdk-common/src/test/java/com/android/ide/common/repository/SdkMavenRepositoryTest.java
deleted file mode 100644
index e2665b3..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/repository/SdkMavenRepositoryTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.repository;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.local.LocalSdk;
-import com.android.utils.ILogger;
-import com.android.utils.StdLogger;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-
-public class SdkMavenRepositoryTest extends TestCase {
-
- public void testGetLocation() {
- assertNull(SdkMavenRepository.ANDROID.getRepositoryLocation(null, false));
-
- File sdkHome = getTestSdk();
- if (sdkHome == null) {
- return;
- }
-
- File android = SdkMavenRepository.ANDROID.getRepositoryLocation(sdkHome, true);
- assertNotNull(android);
-
- File google = SdkMavenRepository.GOOGLE.getRepositoryLocation(sdkHome, true);
- assertNotNull(google);
- }
-
- public void testGetBestMatch() {
- assertNull(SdkMavenRepository.ANDROID.getHighestInstalledVersion(
- null, "com.android.support", "support-v4", "19", false));
-
- File sdkHome = getTestSdk();
- if (sdkHome == null) {
- return;
- }
-
- GradleCoordinate gc1 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
- sdkHome, "com.android.support", "support-v4", "19", false);
- assertEquals(GradleCoordinate.parseCoordinateString(
- "com.android.support:support-v4:19.1.0"), gc1);
-
- GradleCoordinate gc2 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
- sdkHome, "com.android.support", "support-v4", "20", false);
- assertEquals(GradleCoordinate.parseCoordinateString(
- "com.android.support:support-v4:20.0.0"), gc2);
-
- /* These tests only applied when 21 was marked as a preview release; it no longer
- is. Re-enable when we get another preview platform.
- GradleCoordinate gc3 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
- sdkHome, "com.android.support", "support-v4", "22", false);
- assertNull(gc3);
-
- GradleCoordinate gc4 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
- sdkHome, "com.android.support", "support-v4", "21", true);
- assertEquals(GradleCoordinate.parseCoordinateString(
- "com.android.support:support-v4:21.0.0-rc1"), gc4);
- */
- }
-
- public void testIsInstalled() {
- assertFalse(SdkMavenRepository.ANDROID.isInstalled((File)null));
- assertFalse(SdkMavenRepository.ANDROID.isInstalled((LocalSdk)null));
-
- File sdkHome = getTestSdk();
- if (sdkHome == null) {
- return;
- }
-
- assertTrue(SdkMavenRepository.ANDROID.isInstalled(sdkHome));
- assertTrue(SdkMavenRepository.GOOGLE.isInstalled(sdkHome));
-
- ILogger logger = new StdLogger(StdLogger.Level.INFO);
- SdkManager sdkManager = SdkManager.createManager(sdkHome.getPath(), logger);
- assertNotNull(sdkManager);
- assertTrue(SdkMavenRepository.ANDROID.isInstalled(sdkManager.getLocalSdk()));
- assertTrue(SdkMavenRepository.GOOGLE.isInstalled(sdkManager.getLocalSdk()));
- }
-
- public void testGetDirName() {
- assertEquals("android", SdkMavenRepository.ANDROID.getDirName());
- assertEquals("google", SdkMavenRepository.GOOGLE.getDirName());
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testGetByGroupId() {
- assertSame(SdkMavenRepository.ANDROID, SdkMavenRepository.getByGroupId(
- GradleCoordinate.parseCoordinateString(
- "com.android.support:appcompat-v7:13.0.0").getGroupId()));
- assertSame(SdkMavenRepository.ANDROID, SdkMavenRepository.getByGroupId(
- GradleCoordinate.parseCoordinateString(
- "com.android.support.test:espresso:0.2").getGroupId()));
- assertSame(SdkMavenRepository.GOOGLE, SdkMavenRepository.getByGroupId(
- GradleCoordinate.parseCoordinateString(
- "com.google.android.gms:play-services:5.2.08").getGroupId()));
- assertSame(SdkMavenRepository.GOOGLE, SdkMavenRepository.getByGroupId(
- GradleCoordinate.parseCoordinateString(
- "com.google.android.gms:play-services-wearable:5.0.77").getGroupId()));
- assertNull(SdkMavenRepository.getByGroupId(GradleCoordinate.parseCoordinateString(
- "com.google.guava:guava:11.0.2").getGroupId()));
- }
-
- /**
- * Environment variable or system property containing the full path to an SDK install
- */
- public static final String SDK_PATH_PROPERTY = "ADT_TEST_SDK_PATH";
-
- @Nullable
- private static File getTestSdk() {
- String sdkHome = getTestSdkPath();
- if (sdkHome != null) {
- File file = new File(sdkHome);
- assertTrue(file.getPath(), file.isDirectory());
- return file;
- }
-
- return null;
- }
-
- @Nullable
- private static String getTestSdkPath() {
- String override = System.getProperty(SDK_PATH_PROPERTY);
- if (override != null) {
- assertTrue(override, new File(override).exists());
- return override;
- }
- override = System.getenv(SDK_PATH_PROPERTY);
- if (override != null) {
- return override;
- }
-
- return null;
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java
deleted file mode 100755
index 522bf8e..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java
+++ /dev/null
@@ -1,415 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.SdkConstants;
-import com.android.testutils.TestUtils;
-import com.google.common.collect.ListMultimap;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.regex.Pattern;
-
-public class AssetMergerTest extends BaseTestCase {
-
- private static AssetMerger sAssetMerger = null;
-
- public void testMergeByCount() throws Exception {
- AssetMerger merger = getAssetMerger();
-
- assertEquals(5, merger.size());
- }
-
- public void testMergedAssetsByName() throws Exception {
- AssetMerger merger = getAssetMerger();
-
- verifyResourceExists(merger,
- "foo/icon.png",
- "icon2.png",
- "main.xml",
- "values.xml",
- "foo/foo.dat"
- );
- }
-
- public void testMergeWrite() throws Exception {
- AssetMerger merger = getAssetMerger();
-
- File folder = getWrittenResources();
-
- RecordingLogger logger = new RecordingLogger();
-
- AssetSet writtenSet = new AssetSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
-
- checkLogger(logger);
-
- // compare the two maps, but not using the full map as the set loaded from the output
- // won't contains all versions of each AssetItem item.
- compareResourceMaps(merger, writtenSet, false /*full compare*/);
- }
-
- public void testMergeBlob() throws Exception {
- AssetMerger merger = getAssetMerger();
-
- File folder = Files.createTempDir();
- merger.writeBlobTo(folder, new MergedAssetWriter(Files.createTempDir()));
-
- AssetMerger loadedMerger = new AssetMerger();
- loadedMerger.loadFromBlob(folder, true /*incrementalState*/);
-
- compareResourceMaps(merger, loadedMerger, true /*full compare*/);
- }
-
- /**
- * Tests the path replacement in the merger.xml file loaded from testData/
- * @throws Exception
- */
- public void testLoadingTestPathReplacement() throws Exception {
- File root = TestUtils.getRoot("assets", "baseMerge");
- File fakeRoot = getMergedBlobFolder(root);
-
- AssetMerger assetMerger = new AssetMerger();
- assetMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
- checkSourceFolders(assetMerger);
-
- List<AssetSet> sets = assetMerger.getDataSets();
- for (AssetSet set : sets) {
- List<File> sourceFiles = set.getSourceFiles();
-
- // there should only be one
- assertEquals(1, sourceFiles.size());
-
- File sourceFile = sourceFiles.get(0);
- assertTrue(String.format("File %s is located in %s", sourceFile, root),
- sourceFile.getAbsolutePath().startsWith(root.getAbsolutePath()));
- }
- }
-
- public void testUpdate() throws Exception {
- File root = getIncMergeRoot("basicFiles");
- File fakeRoot = getMergedBlobFolder(root);
- AssetMerger assetMerger = new AssetMerger();
- assetMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
- checkSourceFolders(assetMerger);
-
- List<AssetSet> sets = assetMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // first set is the main one, no change here
- AssetSet mainSet = sets.get(0);
- File mainFolder = new File(root, "main");
-
- // touched/removed files:
- File mainTouched = new File(mainFolder, "touched.png");
- mainSet.updateWith(mainFolder, mainTouched, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- File mainRemoved = new File(mainFolder, "removed.png");
- mainSet.updateWith(mainFolder, mainRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- File mainAdded = new File(mainFolder, "added.png");
- mainSet.updateWith(mainFolder, mainAdded, FileStatus.NEW, logger);
- checkLogger(logger);
-
- // ----------------
- // second set is the overlay one
- AssetSet overlaySet = sets.get(1);
- File overlayFolder = new File(root, "overlay");
-
- // new/removed files:
- File overlayAdded = new File(new File(overlayFolder, "foo"), "overlay_added.png");
- overlaySet.updateWith(overlayFolder, overlayAdded, FileStatus.NEW, logger);
- checkLogger(logger);
-
- File overlayRemoved = new File(overlayFolder, "overlay_removed.png");
- overlaySet.updateWith(overlayFolder, overlayRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // validate for duplicates
- assetMerger.validateDataSets();
-
- // check the content.
- ListMultimap<String, AssetItem> mergedMap = assetMerger.getDataMap();
-
- // check untouched.png file is WRITTEN
- List<AssetItem> untouchedItem = mergedMap.get("untouched.png");
- assertEquals(1, untouchedItem.size());
- assertTrue(untouchedItem.get(0).isWritten());
- assertFalse(untouchedItem.get(0).isTouched());
- assertFalse(untouchedItem.get(0).isRemoved());
-
- // check touched.png file is TOUCHED
- List<AssetItem> touchedItem = mergedMap.get("touched.png");
- assertEquals(1, touchedItem.size());
- assertTrue(touchedItem.get(0).isWritten());
- assertTrue(touchedItem.get(0).isTouched());
- assertFalse(touchedItem.get(0).isRemoved());
-
- // check removed file is REMOVED
- List<AssetItem> removedItem = mergedMap.get("removed.png");
- assertEquals(1, removedItem.size());
- assertTrue(removedItem.get(0).isWritten());
- assertTrue(removedItem.get(0).isRemoved());
-
- // check new overlay: two objects, last one is TOUCHED
- List<AssetItem> overlayAddedItem = mergedMap.get("foo/overlay_added.png");
- assertEquals(2, overlayAddedItem.size());
- AssetItem newOverlay0 = overlayAddedItem.get(0);
- assertTrue(newOverlay0.isWritten());
- assertFalse(newOverlay0.isTouched());
- AssetItem newOverlay1 = overlayAddedItem.get(1);
- assertEquals(overlayAdded, newOverlay1.getSource().getFile());
- assertFalse(newOverlay1.isWritten());
- assertTrue(newOverlay1.isTouched());
-
- // check removed overlay: two objects, last one is removed
- List<AssetItem> overlayRemovedItem = mergedMap.get("overlay_removed.png");
- assertEquals(2, overlayRemovedItem.size());
- AssetItem overlayRemovedItem0 = overlayRemovedItem.get(0);
- assertFalse(overlayRemovedItem0.isWritten());
- assertFalse(overlayRemovedItem0.isTouched());
- AssetItem overlayRemovedItem1 = overlayRemovedItem.get(1);
- assertEquals(overlayRemoved, overlayRemovedItem1.getSource().getFile());
- assertTrue(overlayRemovedItem1.isWritten());
- assertTrue(overlayRemovedItem1.isRemoved());
-
- // write and check the result of writeResourceFolder
- // copy the current resOut which serves as pre incremental update state.
- File resFolder = getFolderCopy(new File(root, "assetOut"));
-
- // write the content of the resource merger.
- MergedAssetWriter writer = new MergedAssetWriter(resFolder);
- assetMerger.mergeData(writer, false /*doCleanUp*/);
-
- // Check the content by checking the colors. All files should be green
- checkImageColor(new File(resFolder, "untouched.png"), (int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "touched.png"), (int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "added.png"), (int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "overlay_removed.png"), (int) 0xFF00FF00);
- checkImageColor(new File(new File(resFolder, "foo"), "overlay_added.png"), (int) 0xFF00FF00);
-
- // also check the removed file is not there.
- assertFalse(new File(resFolder, "removed.png").isFile());
- }
-
- public void testCheckValidUpdate() throws Exception {
- // first merger
- AssetMerger merger1 = createMerger(new String[][] {
- new String[] { "main", ("/main/res1"), ("/main/res2") },
- new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
- });
-
- // 2nd merger with different order source files in sets.
- AssetMerger merger2 = createMerger(new String[][] {
- new String[] { "main", ("/main/res2"), ("/main/res1") },
- new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
- });
-
- assertTrue(merger1.checkValidUpdate(merger2.getDataSets()));
-
- // write merger1 on disk to test writing empty AssetSets.
- File folder = Files.createTempDir();
- merger1.writeBlobTo(folder, new MergedAssetWriter(Files.createTempDir()));
-
- // reload it
- AssetMerger loadedMerger = new AssetMerger();
- loadedMerger.loadFromBlob(folder, true /*incrementalState*/);
-
- String expected = merger1.toString();
- String actual = loadedMerger.toString();
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- expected = expected.replaceAll(Pattern.quote(File.separator), "/").
- replaceAll("[A-Z]:/", "/");
- actual = actual.replaceAll(Pattern.quote(File.separator), "/").
- replaceAll("[A-Z]:/", "/");
- assertEquals("Actual: " + actual + "\nExpected: " + expected, expected, actual);
- } else {
- assertTrue("Actual: " + actual + "\nExpected: " + expected,
- loadedMerger.checkValidUpdate(merger1.getDataSets()));
- }
- }
-
-
- public void testUpdateWithRemovedOverlay() throws Exception {
- // Test with removed overlay
- AssetMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- AssetMerger merger2 = createMerger(new String[][] {
- new String[] { "main", "/main/res2", "/main/res1" },
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testUpdateWithReplacedOverlays() throws Exception {
- // Test with different overlays
- AssetMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- AssetMerger merger2 = createMerger(new String[][] {
- new String[] { "main", "/main/res2", "/main/res1" },
- new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testUpdateWithReorderedOverlays() throws Exception {
- // Test with different overlays
- AssetMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
- new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- AssetMerger merger2 = createMerger(new String[][] {
- new String[] { "main", "/main/res2", "/main/res1" },
- new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
- new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testUpdateWithRemovedSourceFile() throws Exception {
- // Test with different source files
- AssetMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- AssetMerger merger2 = createMerger(new String[][] {
- new String[] { "main", "/main/res1" },
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testChangedIgnoredFile() throws Exception {
- AssetSet assetSet = AssetSetTest.getBaseAssetSet();
-
- AssetMerger assetMerger = new AssetMerger();
- assetMerger.addDataSet(assetSet);
-
- File root = TestUtils.getRoot("assets", "baseSet");
- File changedCVSFoo = new File(root, "CVS/foo.txt");
- FileValidity<AssetSet> fileValidity = assetMerger.findDataSetContaining(changedCVSFoo);
-
- assertEquals(FileValidity.FileStatus.IGNORED_FILE, fileValidity.status);
- }
-
- /**
- * Creates a fake merge with given sets.
- *
- * the data is an array of sets.
- *
- * Each set is [ setName, folder1, folder2, ...]
- *
- * @param data
- * @return
- */
- private static AssetMerger createMerger(String[][] data) {
- AssetMerger merger = new AssetMerger();
- for (String[] setData : data) {
- AssetSet set = new AssetSet(setData[0]);
- merger.addDataSet(set);
- for (int i = 1, n = setData.length; i < n; i++) {
- set.addSource(new File(setData[i]));
- }
- }
-
- return merger;
- }
-
- private static AssetMerger getAssetMerger()
- throws IOException, MergingException {
- if (sAssetMerger == null) {
- File root = TestUtils.getRoot("assets", "baseMerge");
-
- AssetSet res = AssetSetTest.getBaseAssetSet();
-
- RecordingLogger logger = new RecordingLogger();
-
- AssetSet overlay = new AssetSet("overlay");
- overlay.addSource(new File(root, "overlay"));
- overlay.loadFromFiles(logger);
-
- checkLogger(logger);
-
- sAssetMerger = new AssetMerger();
- sAssetMerger.addDataSet(res);
- sAssetMerger.addDataSet(overlay);
- }
-
- return sAssetMerger;
- }
-
- private static File getWrittenResources() throws MergingException, IOException {
- AssetMerger assetMerger = getAssetMerger();
-
- File folder = Files.createTempDir();
-
- MergedAssetWriter writer = new MergedAssetWriter(folder);
- assetMerger.mergeData(writer, false /*doCleanUp*/);
-
- return folder;
- }
-
- private File getIncMergeRoot(String name) throws IOException {
- File root = TestUtils.getCanonicalRoot("assets", "incMergeData");
- return new File(root, name);
- }
-
- private static File getFolderCopy(File folder) throws IOException {
- File dest = Files.createTempDir();
- copyFolder(folder, dest);
- return dest;
- }
-
- private static void copyFolder(File from, File to) throws IOException {
- if (from.isFile()) {
- Files.copy(from, to);
- } else if (from.isDirectory()) {
- if (!to.exists()) {
- to.mkdirs();
- }
-
- File[] children = from.listFiles();
- if (children != null) {
- for (File f : children) {
- copyFolder(f, new File(to, f.getName()));
- }
- }
- }
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java
deleted file mode 100644
index bed8a07..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import com.android.ide.common.blame.Message;
-import com.android.ide.common.blame.SourceFilePosition;
-import com.android.ide.common.blame.SourcePosition;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
-import org.junit.Test;
-import org.xml.sax.SAXParseException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
- at SuppressWarnings({"ThrowableInstanceNeverThrown", "ThrowableResultOfMethodCallIgnored"})
-public class MergingExceptionTest {
-
- private static final File file = new File("/some/random/path");
-
- @Test
- public void testGetMessage() {
- assertEquals("Error: My error message",
- MergingException.withMessage("My error message").build().getMessage());
- assertEquals("Error: My error message",
- MergingException.wrapException(new Exception()).withMessage(
- "Error: My error message").build().getMessage());
- assertEquals("Error: My error message",
- MergingException.withMessage("Error: My error message").build().getMessage());
- assertEquals("/some/random/path: Error: My error message",
- MergingException.wrapException(new Exception("My error message")).withFile(file)
- .build().getMessage());
- MergingException.Builder builder2 = MergingException
- .wrapException(new Exception("My error message")).withFile(file)
- .withPosition(new SourcePosition(49, -1, -1));
- assertEquals("/some/random/path:50: Error: My error message",
- builder2.build()
- .getMessage());
- MergingException.Builder builder1 = MergingException
- .wrapException(new Exception("My error message")).withFile(file)
- .withPosition(new SourcePosition(49, 3, -1));
- assertEquals("/some/random/path:50:4: Error: My error message",
- builder1.build()
- .getMessage());
- MergingException.Builder builder = MergingException
- .wrapException(new Exception("My error message")).withFile(file)
- .withPosition(new SourcePosition(49, 3, -1));
- assertEquals("/some/random/path:50:4: Error: My error message",
- builder.build()
- .getLocalizedMessage());
- assertEquals("/some/random/path: Error: My error message",
- MergingException.withMessage("/some/random/path: My error message").withFile(file)
- .build().getMessage());
- assertEquals("/some/random/path: Error: My error message",
- MergingException.withMessage("/some/random/path My error message").withFile(file)
- .build().getMessage());
-
- // end of string handling checks
- assertEquals("/some/random/path: Error: ",
- MergingException.withMessage("/some/random/path").withFile(file).build()
- .getMessage());
- assertEquals("/some/random/path: Error: ",
- MergingException.withMessage("/some/random/path").withFile(file).build().getMessage());
- assertEquals("/some/random/path: Error: ",
- MergingException.withMessage("/some/random/path:").withFile(file).build().getMessage());
- assertEquals("/some/random/path: Error: ",
- MergingException.withMessage("/some/random/path: ").withFile(file).build().getMessage());
- }
-
-
- @Test
- public void testWrapSaxParseExceptionWithLocation() {
- SAXParseException saxParseException = new SAXParseException("message", "", "", 5, 7);
- List<Message> messages = MergingException
- .wrapException(saxParseException).withFile(file).build().getMessages();
- assertEquals(new Message(Message.Kind.ERROR, "message",
- new SourceFilePosition(file, new SourcePosition(4, 6, -1))), messages.get(0));
- }
-
- @Test
- public void testWrapSaxParseExceptionWithoutLocation() {
- SAXParseException saxParseException = new SAXParseException("message", "", "", -1, -1);
-
- List<Message> messages = MergingException
- .wrapException(saxParseException).withFile(file).build().getMessages();
- assertEquals(new Message(Message.Kind.ERROR, "message",
- new SourceFilePosition(file, SourcePosition.UNKNOWN)), messages.get(0));
- }
-
- @Test
- public void testThrowIfNonEmpty() throws MergingException{
- MergingException.throwIfNonEmpty(ImmutableList.<Message>of());
- try {
- MergingException.throwIfNonEmpty(ImmutableList.<Message>of(
- new Message(Message.Kind.ERROR, "Message", SourceFilePosition.UNKNOWN)));
- fail();
- } catch (MergingException e) {
- // ok
- }
- }
-
-
- @Test
- public void testMergingExceptionWithNullMessage() {
- MergingException exception = MergingException.wrapException(new IOException()).build();
- Message message = Iterables.getOnlyElement(exception.getMessages());
- assertNotNull(message.getText());
- assertNotNull(message.getRawMessage());
- }
-
- @Test
- public void testConsumerExceptionWithNullMessage() {
- MergingException exception = new MergeConsumer.ConsumerException(new IOException());
- Message message = Iterables.getOnlyElement(exception.getMessages());
- assertNotNull(message.getText());
- assertNotNull(message.getRawMessage());
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java
deleted file mode 100644
index 0d5f6c4..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import junit.framework.TestCase;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-public class NodeUtilsTest extends TestCase {
-
- public void testBasicAttributes() throws Exception {
- Document document = createDocument();
-
- // create two nodes
- Node node1 = document.createElement("N1");
- Node node2 = document.createElement("N2");
-
- NodeUtils.addAttribute(document, node1, null, "foo", "bar");
- NodeUtils.addAttribute(document, node2, null, "foo", "bar");
- assertTrue(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
-
- NodeUtils.addAttribute(document, node1, null, "foo2", "bar2");
- assertFalse(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
-
- NodeUtils.addAttribute(document, node2, null, "foo2", "bar");
- assertFalse(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
- }
-
- public void testNamespaceAttributes() throws Exception {
- Document document = createDocument();
-
- // create two nodes
- Node node1 = document.createElement("N1");
- Node node2 = document.createElement("N2");
-
- NodeUtils.addAttribute(document, node1, "http://some.uri/", "foo", "bar");
- NodeUtils.addAttribute(document, node2, "http://some.uri/", "foo", "bar");
- assertTrue(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
-
- NodeUtils.addAttribute(document, node1, "http://some.uri/", "foo2", "bar2");
- NodeUtils.addAttribute(document, node2, "http://some.other.uri/", "foo2", "bar2");
- assertFalse(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
- }
-
- public void testNodesWithChildrenNodes() throws Exception {
- Document document = createDocument();
-
- // create two nodes
- Node node1 = document.createElement("some-node");
- Node node2 = document.createElement("some-node");
-
- Node child1a = document.createElement("child1");
- Node child1b = document.createElement("child2");
- node1.appendChild(child1a);
- node1.appendChild(child1b);
-
- Node child2a = document.createElement("child1");
- Node child2b = document.createElement("child2");
- node2.appendChild(child2a);
- node2.appendChild(child2b);
-
- assertTrue(NodeUtils.compareElementNode(node1, node2, true));
- }
-
- public void testNodesWithChildrenNodesInWrongOrder() throws Exception {
- Document document = createDocument();
-
- // create two nodes
- Node node1 = document.createElement("some-node");
- Node node2 = document.createElement("some-node");
-
- Node child1a = document.createElement("child1");
- Node child1b = document.createElement("child2");
- node1.appendChild(child1a);
- node1.appendChild(child1b);
-
- Node child2a = document.createElement("child2");
- Node child2b = document.createElement("child1");
- node2.appendChild(child2a);
- node2.appendChild(child2b);
-
- assertFalse(NodeUtils.compareElementNode(node1, node2, true));
- }
-
- public void testNodesWithChildrenNodesInOtherOrder() throws Exception {
- Document document = createDocument();
-
- // create two nodes
- Node node1 = document.createElement("some-node");
- Node node2 = document.createElement("some-node");
-
- Node child1a = document.createElement("child1");
- Node child1b = document.createElement("child2");
- node1.appendChild(child1a);
- node1.appendChild(child1b);
-
- Node child2a = document.createElement("child2");
- Node child2b = document.createElement("child1");
- node2.appendChild(child2a);
- node2.appendChild(child2b);
-
- assertTrue(NodeUtils.compareElementNode(node1, node2, false));
- }
-
- public void testAdoptNode() throws Exception {
- Document document = createDocument();
- Node rootNode = document.createElement("root");
- document.appendChild(rootNode);
-
- // create a single s
- Node node = document.createElement("some-node");
-
- // add some children
- Node child1 = document.createElement("child1");
- Node child2 = document.createElement("child2");
- node.appendChild(child1).appendChild(child2);
- Node cdata = document.createCDATASection("some <random> text");
- child2.appendChild(cdata);
-
- // add some attributes
- NodeUtils.addAttribute(document, node, null, "foo", "bar");
- NodeUtils.addAttribute(document, node, "http://some.uri", "foo2", "bar2");
- NodeUtils.addAttribute(document, child1, "http://some.other.uri", "blah", "test");
- NodeUtils.addAttribute(document, child2, "http://another.uri", "blah", "test");
-
- // create the other document to receive the adopted node. It must have a root node.
- Document document2 = createDocument();
- rootNode = document2.createElement("root");
- document2.appendChild(rootNode);
-
- assertTrue(NodeUtils.compareElementNode(node, NodeUtils.adoptNode(document2, node), true));
- }
-
- public void testDuplicateNode() throws Exception {
- Document document = createDocument();
- Node rootNode = document.createElement("root");
- document.appendChild(rootNode);
-
- // create a single s
- Node node = document.createElement("some-node");
-
- // add some children
- Node child1 = document.createElement("child1");
- Node child2 = document.createElement("child2");
- node.appendChild(child1).appendChild(child2);
- Node cdata = document.createCDATASection("some <random> text");
- child2.appendChild(cdata);
-
- // add some attributes
- NodeUtils.addAttribute(document, node, null, "foo", "bar");
- NodeUtils.addAttribute(document, node, "http://some.uri", "foo2", "bar2");
- NodeUtils.addAttribute(document, child1, "http://some.other.uri", "blah", "test");
- NodeUtils.addAttribute(document, child2, "http://another.uri", "blah", "test");
-
- // create the other document to receive the adopted node. It must have a root node.
- Document document2 = createDocument();
- rootNode = document2.createElement("root");
- document2.appendChild(rootNode);
-
- assertTrue(NodeUtils.compareElementNode(
- node,
- NodeUtils.duplicateNode(document2, node),
- false));
- }
-
- private static Document createDocument() throws ParserConfigurationException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- factory.setIgnoringComments(true);
- DocumentBuilder builder;
-
- builder = factory.newDocumentBuilder();
- return builder.newDocument();
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java
deleted file mode 100755
index d2d64e6..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java
+++ /dev/null
@@ -1,1704 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.FD_RES_DRAWABLE;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
-import static com.android.SdkConstants.TAG_ATTR;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.internal.PngCruncher;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.testutils.TestUtils;
-import com.android.utils.SdkUtils;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.Files;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-
-public class ResourceMergerTest extends BaseTestCase {
-
- @Mock
- PngCruncher mPngCruncher;
-
- @Mock
- ResourcePreprocessor mPreprocessor;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- }
-
- public void testMergeByCount() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- assertEquals(32, merger.size());
- }
-
- public void testMergeWithNormalizationByCount() throws Exception {
- ResourceMerger merger = getResourceMerger(true /*normalize*/);
-
- assertEquals(31, merger.size());
- }
-
- public void testMergedResourcesByName() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- verifyResourceExists(merger,
- "drawable/icon",
- "drawable-ldpi/icon",
- "drawable/icon2",
- "drawable/patch",
- "raw/foo",
- "layout/main",
- "layout/layout_ref",
- "layout/alias_replaced_by_file",
- "layout/file_replaced_by_alias",
- "drawable/color_drawable",
- "drawable/drawable_ref",
- "color/color",
- "string/basic_string",
- "string/xliff_string",
- "string/xliff_with_carriage_return",
- "string/styled_string",
- "style/style",
- "array/string_array",
- "attr/dimen_attr",
- "attr/string_attr",
- "attr/enum_attr",
- "attr/flag_attr",
- "attr/blah",
- "attr/blah2",
- "attr/flagAttr",
- "declare-styleable/declare_styleable",
- "dimen/dimen",
- "dimen-sw600dp/offset",
- "id/item_id",
- "integer/integer"
- );
- }
-
- public void testMergedResourcesWithNormalizationByName() throws Exception {
- ResourceMerger merger = getResourceMerger(true /*normalize*/);
-
- verifyResourceExists(merger,
- "drawable/icon",
- "drawable-ldpi-v4/icon",
- "drawable/icon2",
- "drawable/patch",
- "raw/foo",
- "layout/main",
- "layout/layout_ref",
- "layout/alias_replaced_by_file",
- "layout/file_replaced_by_alias",
- "drawable/color_drawable",
- "drawable/drawable_ref",
- "color/color",
- "string/basic_string",
- "string/xliff_string",
- "string/xliff_with_carriage_return",
- "string/styled_string",
- "style/style",
- "array/string_array",
- "attr/dimen_attr",
- "attr/string_attr",
- "attr/enum_attr",
- "attr/flag_attr",
- "attr/blah",
- "attr/blah2",
- "attr/flagAttr",
- "declare-styleable/declare_styleable",
- "dimen/dimen",
- "dimen-sw600dp-v13/offset",
- "id/item_id",
- "integer/integer"
- );
- }
-
- private static String getPlatformPath(String path) {
- return path.replaceAll("/", Matcher.quoteReplacement(File.separator));
- }
-
- public void testReplacedLayout() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
- ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap();
-
- List<ResourceItem> values = mergedMap.get("layout/main");
-
- // the overlay means there's 2 versions of this resource.
- assertEquals(2, values.size());
- ResourceItem mainLayout = values.get(1);
-
- ResourceFile sourceFile = mainLayout.getSource();
- assertTrue(sourceFile.getFile().getAbsolutePath()
- .endsWith(getPlatformPath("overlay/layout/main.xml")));
- }
-
- public void testReplacedAlias() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
- ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap();
-
- List<ResourceItem> values = mergedMap.get("layout/alias_replaced_by_file");
-
- // the overlay means there's 2 versions of this resource.
- assertEquals(2, values.size());
- ResourceItem layout = values.get(1);
-
- // since it's replaced by a file, there's no node.
- assertNull(layout.getValue());
- }
-
- public void testReplacedFile() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
- ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap();
-
- List<ResourceItem> values = mergedMap.get("layout/file_replaced_by_alias");
-
- // the overlay means there's 2 versions of this resource.
- assertEquals(2, values.size());
- ResourceItem layout = values.get(1);
-
- // since it's replaced by an alias, there's a node
- assertNotNull(layout.getValue());
- }
-
- public void testMergeWrite() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
- RecordingLogger logger = new RecordingLogger();
-
- File folder = getWrittenResources();
-
- ResourceSet writtenSet = new ResourceSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
-
- // compare the two maps, but not using the full map as the set loaded from the output
- // won't contains all versions of each ResourceItem item.
- compareResourceMaps(merger, writtenSet, false /*full compare*/);
- checkLogger(logger);
- }
-
- public void testXliffString() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- // check the result of the load
- List<ResourceItem> values = merger.getDataMap().get("string/xliff_string");
-
- assertEquals(1, values.size());
- ResourceItem string = values.get(0);
-
- // Even though the content is
- // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g>
- // The valueText is going to skip the <g> node so we skip them from the comparison.
- // What matters here is that the whitespaces are kept.
- assertEquals("Loaded String in merger",
- "%1$s %2$s",
- string.getValueText());
-
- File folder = getWrittenResources();
-
- RecordingLogger logger = new RecordingLogger();
- ResourceSet writtenSet = new ResourceSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
-
- values = writtenSet.getDataMap().get("string/xliff_string");
-
- assertEquals(1, values.size());
- string = values.get(0);
-
- // Even though the content is
- // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g>
- // The valueText is going to skip the <g> node so we skip them from the comparison.
- // What matters here is that the whitespaces are kept.
- assertEquals("Rewritten String through merger",
- "%1$s %2$s",
- string.getValueText());
- }
-
- public void testXliffStringWithCarriageReturn() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- // check the result of the load
- List<ResourceItem> values = merger.getDataMap().get("string/xliff_with_carriage_return");
-
- assertEquals(1, values.size());
- ResourceItem string = values.get(0);
-
- // Even though the content has xliff nodes
- // The valueText is going to skip the <g> node so we skip them from the comparison.
- // What matters here is that the whitespaces are kept.
- String value = string.getValueText();
- assertEquals("Loaded String in merger",
- "This is should be followed by whitespace:\n %1$s",
- value);
-
- File folder = getWrittenResources();
-
- RecordingLogger logger = new RecordingLogger();
- ResourceSet writtenSet = new ResourceSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
-
- values = writtenSet.getDataMap().get("string/xliff_with_carriage_return");
-
- assertEquals(1, values.size());
- string = values.get(0);
-
- // Even though the content has xliff nodes
- // The valueText is going to skip the <g> node so we skip them from the comparison.
- // What matters here is that the whitespaces are kept.
- String newValue = string.getValueText();
- assertEquals("Rewritten String through merger",
- value,
- newValue);
- }
-
- public void testNotMergedAttr() throws Exception {
- RecordingLogger logger = new RecordingLogger();
-
- File folder = getWrittenResources();
-
- ResourceSet writtenSet = new ResourceSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
-
- List<ResourceItem> items = writtenSet.getDataMap().get("attr/blah");
- assertEquals(1, items.size());
- assertTrue(items.get(0).getIgnoredFromDiskMerge());
-
- checkLogger(logger);
- }
-
- public void testMergedAttr() throws Exception {
- RecordingLogger logger = new RecordingLogger();
-
- File folder = getWrittenResources();
-
- ResourceSet writtenSet = new ResourceSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
-
- List<ResourceItem> items = writtenSet.getDataMap().get("attr/blah2");
- assertEquals(1, items.size());
- assertFalse(items.get(0).getIgnoredFromDiskMerge());
-
- checkLogger(logger);
- }
-
- public void testNotMergedAttrFromMerge() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- File folder = Files.createTempDir();
- merger.writeBlobTo(folder,
- getConsumer());
-
- ResourceMerger loadedMerger = new ResourceMerger();
- assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
-
- // check that attr/blah is ignoredFromDiskMerge.
- List<ResourceItem> items = loadedMerger.getDataSets().get(0).getDataMap().get("attr/blah");
- assertEquals(1, items.size());
- assertTrue(items.get(0).getIgnoredFromDiskMerge());
- }
-
- public void testWrittenDeclareStyleable() throws Exception {
- RecordingLogger logger = new RecordingLogger();
-
- File folder = getWrittenResources();
-
- ResourceSet writtenSet = new ResourceSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
-
- List<ResourceItem> items = writtenSet.getDataMap().get("declare-styleable/declare_styleable");
- assertEquals(1, items.size());
-
- Node styleableNode = items.get(0).getValue();
- assertNotNull(styleableNode);
-
- // inspect the node
- NodeList nodes = styleableNode.getChildNodes();
-
- boolean foundBlah = false;
- boolean foundAndroidColorForegroundInverse = false;
- boolean foundBlah2 = false;
-
- for (int i = 0, n = nodes.getLength(); i < n; i++) {
- Node node = nodes.item(i);
-
- if (node.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
-
- String nodeName = node.getLocalName();
- if (ResourceType.ATTR.getName().equals(nodeName)) {
- Attr attribute = (Attr) node.getAttributes().getNamedItemNS(null, ATTR_NAME);
-
- if (attribute != null) {
- String name = attribute.getValue();
- if ("blah".equals(name)) {
- foundBlah = true;
- } else if ("android:colorForegroundInverse".equals(name)) {
- foundAndroidColorForegroundInverse = true;
- } else if ("blah2".equals(name)) {
- foundBlah2 = true;
- }
- }
-
- }
- }
-
- assertTrue(foundBlah);
- assertTrue(foundAndroidColorForegroundInverse);
- assertTrue(foundBlah2);
-
- checkLogger(logger);
- }
-
- public void testMergeBlob() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- File folder = Files.createTempDir();
- merger.writeBlobTo(folder,
- getConsumer());
-
- ResourceMerger loadedMerger = new ResourceMerger();
- assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
-
- compareResourceMaps(merger, loadedMerger, true /*full compare*/);
- }
-
- /**
- * Tests the path replacement in the merger.xml file loaded from testData/
- * @throws Exception
- */
- public void testLoadingTestPathReplacement() throws Exception {
- File root = TestUtils.getRoot("resources", "baseMerge");
- File fakeRoot = getMergedBlobFolder(root);
-
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- for (ResourceSet set : sets) {
- List<File> sourceFiles = set.getSourceFiles();
-
- // there should only be one
- assertEquals(1, sourceFiles.size());
-
- File sourceFile = sourceFiles.get(0);
- assertTrue(String.format("File %s is located in %s", sourceFile, root),
- sourceFile.getAbsolutePath().startsWith(root.getAbsolutePath()));
- }
- }
-
- public void testUpdateWithBasicFiles() throws Exception {
- File root = getIncMergeRoot("basicFiles");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // first set is the main one, no change here
- ResourceSet mainSet = sets.get(0);
- File mainBase = new File(root, "main");
- File mainDrawable = new File(mainBase, "drawable");
- File mainDrawableLdpi = new File(mainBase, "drawable-ldpi");
-
- // touched/removed files:
- File mainDrawableTouched = new File(mainDrawable, "touched.png");
- mainSet.updateWith(mainBase, mainDrawableTouched, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- File mainDrawableRemoved = new File(mainDrawable, "removed.png");
- mainSet.updateWith(mainBase, mainDrawableRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- File mainDrawableLdpiRemoved = new File(mainDrawableLdpi, "removed.png");
- mainSet.updateWith(mainBase, mainDrawableLdpiRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // ----------------
- // second set is the overlay one
- ResourceSet overlaySet = sets.get(1);
- File overlayBase = new File(root, "overlay");
- File overlayDrawable = new File(overlayBase, "drawable");
- File overlayDrawableHdpi = new File(overlayBase, "drawable-hdpi");
-
- // new/removed files:
- File overlayDrawableNewOverlay = new File(overlayDrawable, "new_overlay.png");
- overlaySet.updateWith(overlayBase, overlayDrawableNewOverlay, FileStatus.NEW, logger);
- checkLogger(logger);
-
- File overlayDrawableRemovedOverlay = new File(overlayDrawable, "removed_overlay.png");
- overlaySet.updateWith(overlayBase, overlayDrawableRemovedOverlay, FileStatus.REMOVED,
- logger);
- checkLogger(logger);
-
- File overlayDrawableHdpiNewAlternate = new File(overlayDrawableHdpi, "new_alternate.png");
- overlaySet.updateWith(overlayBase, overlayDrawableHdpiNewAlternate, FileStatus.NEW, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the content.
- ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
-
- // check unchanged file is WRITTEN
- List<ResourceItem> drawableUntouched = mergedMap.get("drawable/untouched");
- assertEquals(1, drawableUntouched.size());
- assertTrue(drawableUntouched.get(0).isWritten());
- assertFalse(drawableUntouched.get(0).isTouched());
- assertFalse(drawableUntouched.get(0).isRemoved());
-
- // check replaced file is TOUCHED
- List<ResourceItem> drawableTouched = mergedMap.get("drawable/touched");
- assertEquals(1, drawableTouched.size());
- assertTrue(drawableTouched.get(0).isWritten());
- assertTrue(drawableTouched.get(0).isTouched());
- assertFalse(drawableTouched.get(0).isRemoved());
-
- // check removed file is REMOVED
- List<ResourceItem> drawableRemoved = mergedMap.get("drawable/removed");
- assertEquals(1, drawableRemoved.size());
- assertTrue(drawableRemoved.get(0).isWritten());
- assertTrue(drawableRemoved.get(0).isRemoved());
-
- // check new overlay: two objects, last one is TOUCHED
- List<ResourceItem> drawableNewOverlay = mergedMap.get("drawable/new_overlay");
- assertEquals(2, drawableNewOverlay.size());
- ResourceItem newOverlay = drawableNewOverlay.get(1);
- assertEquals(overlayDrawableNewOverlay, newOverlay.getSource().getFile());
- assertFalse(newOverlay.isWritten());
- assertTrue(newOverlay.isTouched());
-
- // check new alternate: one objects, last one is TOUCHED
- List<ResourceItem> drawableHdpiNewAlternate = mergedMap.get("drawable-hdpi/new_alternate");
- assertEquals(1, drawableHdpiNewAlternate.size());
- ResourceItem newAlternate = drawableHdpiNewAlternate.get(0);
- assertEquals(overlayDrawableHdpiNewAlternate, newAlternate.getSource().getFile());
- assertFalse(newAlternate.isWritten());
- assertTrue(newAlternate.isTouched());
-
- // write and check the result of writeResourceFolder
- // copy the current resOut which serves as pre incremental update state.
- File resFolder = getFolderCopy(new File(root, "resOut"));
-
- // write the content of the resource merger.
- MergedResourceWriter writer = getConsumer(resFolder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- // Check the content.
- checkImageColor(new File(resFolder, "drawable" + File.separator + "touched.png"),
- (int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "drawable" + File.separator + "untouched.png"),
- (int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "drawable" + File.separator + "new_overlay.png"),
- (int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "drawable" + File.separator + "removed_overlay.png"),
- (int) 0xFF00FF00);
- checkImageColor(new File(resFolder, "drawable-hdpi" + File.separator + "new_alternate.png"),
- (int) 0xFF00FF00);
- assertFalse(new File(resFolder, "drawable-ldpi" + File.separator + "removed.png").isFile());
- }
-
- public void testUpdateWithBasicValues() throws Exception {
- File root = getIncMergeRoot("basicValues");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // first set is the main one, no change here
- ResourceSet mainSet = sets.get(0);
- File mainBase = new File(root, "main");
- File mainValues = new File(mainBase, "values");
- File mainValuesEn = new File(mainBase, "values-en");
-
- // touched file:
- File mainValuesTouched = new File(mainValues, "values.xml");
- mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
-
- // removed files
- File mainValuesEnRemoved = new File(mainValuesEn, "values.xml");
- mainSet.updateWith(mainBase, mainValuesEnRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
-
- // ----------------
- // second set is the overlay one
- ResourceSet overlaySet = sets.get(1);
- File overlayBase = new File(root, "overlay");
- File overlayValues = new File(overlayBase, "values");
- File overlayValuesFr = new File(overlayBase, "values-fr");
-
- // new files:
- File overlayValuesNew = new File(overlayValues, "values.xml");
- overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.NEW, logger);
- checkLogger(logger);
-
- File overlayValuesFrNew = new File(overlayValuesFr, "values.xml");
- overlaySet.updateWith(overlayBase, overlayValuesFrNew, FileStatus.NEW, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the content.
- ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
-
- // check unchanged string is WRITTEN
- List<ResourceItem> valuesUntouched = mergedMap.get("string/untouched");
- assertEquals(1, valuesUntouched.size());
- assertTrue(valuesUntouched.get(0).isWritten());
- assertFalse(valuesUntouched.get(0).isTouched());
- assertFalse(valuesUntouched.get(0).isRemoved());
-
- // check replaced file is TOUCHED
- List<ResourceItem> valuesTouched = mergedMap.get("string/touched");
- assertEquals(1, valuesTouched.size());
- assertTrue(valuesTouched.get(0).isWritten());
- assertTrue(valuesTouched.get(0).isTouched());
- assertFalse(valuesTouched.get(0).isRemoved());
-
- // check removed file is REMOVED
- List<ResourceItem> valuesRemoved = mergedMap.get("string/removed");
- assertEquals(1, valuesRemoved.size());
- assertTrue(valuesRemoved.get(0).isWritten());
- assertTrue(valuesRemoved.get(0).isRemoved());
-
- valuesRemoved = mergedMap.get("string-en/removed");
- assertEquals(1, valuesRemoved.size());
- assertTrue(valuesRemoved.get(0).isWritten());
- assertTrue(valuesRemoved.get(0).isRemoved());
-
- // check new overlay: two objects, last one is TOUCHED
- List<ResourceItem> valuesNewOverlay = mergedMap.get("string/new_overlay");
- assertEquals(2, valuesNewOverlay.size());
- ResourceItem newOverlay = valuesNewOverlay.get(1);
- assertFalse(newOverlay.isWritten());
- assertTrue(newOverlay.isTouched());
-
- // check new alternate: one objects, last one is TOUCHED
- List<ResourceItem> valuesFrNewAlternate = mergedMap.get("string-fr/new_alternate");
- assertEquals(1, valuesFrNewAlternate.size());
- ResourceItem newAlternate = valuesFrNewAlternate.get(0);
- assertFalse(newAlternate.isWritten());
- assertTrue(newAlternate.isTouched());
-
- // write and check the result of writeResourceFolder
- // copy the current resOut which serves as pre incremental update state.
- File resFolder = getFolderCopy(new File(root, "resOut"));
-
- // write the content of the resource merger.
- MergedResourceWriter writer = getConsumer(resFolder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- // Check the content.
- // values/values.xml
- Map<String, String> map = quickStringOnlyValueFileParser(
- new File(resFolder, "values" + File.separator + "values.xml"));
- assertEquals("untouched", map.get("untouched"));
- assertEquals("touched", map.get("touched"));
- assertEquals("new_overlay", map.get("new_overlay"));
-
- // values-fr/values-fr.xml
- map = quickStringOnlyValueFileParser(
- new File(resFolder, "values-fr" + File.separator + "values-fr.xml"));
- assertEquals("new_alternate", map.get("new_alternate"));
-
- // deleted values-en/values-en.xml
- assertFalse(new File(resFolder, "values-en" + File.separator + "values-en.xml").isFile());
- }
-
- public void testUpdateWithBasicValues2() throws Exception {
- File root = getIncMergeRoot("basicValues2");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // first set is the main one, no change here
-
- // ----------------
- // second set is the overlay one
- ResourceSet overlaySet = sets.get(1);
- File overlayBase = new File(root, "overlay");
- File overlayValues = new File(overlayBase, "values");
-
- // new files:
- File overlayValuesNew = new File(overlayValues, "values.xml");
- overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the content.
- ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
-
- // check unchanged string is WRITTEN
- List<ResourceItem> valuesUntouched = mergedMap.get("string/untouched");
- assertEquals(1, valuesUntouched.size());
- assertTrue(valuesUntouched.get(0).isWritten());
- assertFalse(valuesUntouched.get(0).isTouched());
- assertFalse(valuesUntouched.get(0).isRemoved());
-
- // check removed_overlay is present twice.
- List<ResourceItem> valuesRemovedOverlay = mergedMap.get("string/removed_overlay");
- assertEquals(2, valuesRemovedOverlay.size());
- // first is untouched
- assertFalse(valuesRemovedOverlay.get(0).isWritten());
- assertFalse(valuesRemovedOverlay.get(0).isTouched());
- assertFalse(valuesRemovedOverlay.get(0).isRemoved());
- // other is removed
- assertTrue(valuesRemovedOverlay.get(1).isWritten());
- assertFalse(valuesRemovedOverlay.get(1).isTouched());
- assertTrue(valuesRemovedOverlay.get(1).isRemoved());
-
- // write and check the result of writeResourceFolder
- // copy the current resOut which serves as pre incremental update state.
- File resFolder = getFolderCopy(new File(root, "resOut"));
-
- // write the content of the resource merger.
- MergedResourceWriter writer = getConsumer(resFolder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- // Check the content.
- // values/values.xml
- Map<String, String> map = quickStringOnlyValueFileParser(
- new File(resFolder, "values" + File.separator + "values.xml"));
- assertEquals("untouched", map.get("untouched"));
- assertEquals("untouched", map.get("removed_overlay"));
- }
-
- public void testUpdateWithFilesVsValues() throws Exception {
- File root = getIncMergeRoot("filesVsValues");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(1, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // Load the main set
- ResourceSet mainSet = sets.get(0);
- File mainBase = new File(root, "main");
- File mainValues = new File(mainBase, ResourceFolderType.VALUES.getName());
- File mainLayout = new File(mainBase, ResourceFolderType.LAYOUT.getName());
-
- // touched file:
- File mainValuesTouched = new File(mainValues, "values.xml");
- mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // new file:
- File mainLayoutNew = new File(mainLayout, "alias_replaced_by_file.xml");
- mainSet.updateWith(mainBase, mainLayoutNew, FileStatus.NEW, logger);
- checkLogger(logger);
-
- // removed file
- File mainLayoutRemoved = new File(mainLayout, "file_replaced_by_alias.xml");
- mainSet.updateWith(mainBase, mainLayoutRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the content.
- ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
-
- // check layout/main is unchanged
- List<ResourceItem> layoutMain = mergedMap.get("layout/main");
- assertEquals(1, layoutMain.size());
- assertTrue(layoutMain.get(0).isWritten());
- assertFalse(layoutMain.get(0).isTouched());
- assertFalse(layoutMain.get(0).isRemoved());
-
- // check file_replaced_by_alias has 2 version, 2nd is TOUCHED, and contains a Node
- List<ResourceItem> layoutReplacedByAlias = mergedMap.get("layout/file_replaced_by_alias");
- assertEquals(2, layoutReplacedByAlias.size());
- // 1st one is removed version, as it already existed in the item multimap
- ResourceItem replacedByAlias = layoutReplacedByAlias.get(0);
- assertTrue(replacedByAlias.isWritten());
- assertFalse(replacedByAlias.isTouched());
- assertTrue(replacedByAlias.isRemoved());
- assertNull(replacedByAlias.getValue());
- assertEquals("file_replaced_by_alias.xml", replacedByAlias.getSource().getFile().getName());
- // 2nd version is the new one
- replacedByAlias = layoutReplacedByAlias.get(1);
- assertFalse(replacedByAlias.isWritten());
- assertTrue(replacedByAlias.isTouched());
- assertFalse(replacedByAlias.isRemoved());
- assertNotNull(replacedByAlias.getValue());
- assertEquals("values.xml", replacedByAlias.getSource().getFile().getName());
-
- // check alias_replaced_by_file has 2 version, 2nd is TOUCHED, and contains a Node
- List<ResourceItem> layoutReplacedByFile = mergedMap.get("layout/alias_replaced_by_file");
- // 1st one is removed version, as it already existed in the item multimap
- assertEquals(2, layoutReplacedByFile.size());
- ResourceItem replacedByFile = layoutReplacedByFile.get(0);
- assertTrue(replacedByFile.isWritten());
- assertFalse(replacedByFile.isTouched());
- assertTrue(replacedByFile.isRemoved());
- assertNotNull(replacedByFile.getValue());
- assertEquals("values.xml", replacedByFile.getSource().getFile().getName());
- // 2nd version is the new one
- replacedByFile = layoutReplacedByFile.get(1);
- assertFalse(replacedByFile.isWritten());
- assertTrue(replacedByFile.isTouched());
- assertFalse(replacedByFile.isRemoved());
- assertNull(replacedByFile.getValue());
- assertEquals("alias_replaced_by_file.xml", replacedByFile.getSource().getFile().getName());
-
- // write and check the result of writeResourceFolder
- // copy the current resOut which serves as pre incremental update state.
- File resFolder = getFolderCopy(new File(root, "resOut"));
-
- // write the content of the resource merger.
- MergedResourceWriter writer = getConsumer(resFolder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- // deleted layout/file_replaced_by_alias.xml
- assertFalse(new File(resFolder, "layout" + File.separator + "file_replaced_by_alias.xml")
- .isFile());
- // new file layout/alias_replaced_by_file.xml
- assertTrue(new File(resFolder, "layout" + File.separator + "alias_replaced_by_file.xml")
- .isFile());
-
- // quick load of the values file
- File valuesFile = new File(resFolder, "values" + File.separator + "values.xml");
- assertTrue(valuesFile.isFile());
- String content = Files.toString(valuesFile, Charsets.UTF_8);
- assertTrue(content.contains("name=\"file_replaced_by_alias\""));
- assertFalse(content.contains("name=\"alias_replaced_by_file\""));
- }
-
- public void testCheckValidUpdate() throws Exception {
- // first merger
- ResourceMerger merger1 = createMerger(new String[][] {
- new String[] { "main", ("/main/res1"), ("/main/res2") },
- new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
- });
-
- // 2nd merger with different order source files in sets.
- ResourceMerger merger2 = createMerger(new String[][] {
- new String[] { "main", ("/main/res2"), ("/main/res1") },
- new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
- });
-
- assertTrue(merger1.checkValidUpdate(merger2.getDataSets()));
-
- // write merger1 on disk to test writing empty ResourceSets.
- File folder = Files.createTempDir();
- merger1.writeBlobTo(folder,
- getConsumer());
-
- // reload it
- ResourceMerger loadedMerger = new ResourceMerger();
- assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
-
- String expected = merger1.toString();
- String actual = loadedMerger.toString();
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- expected = expected.replaceAll(Pattern.quote(File.separator), "/").
- replaceAll("[A-Z]:/", "/");
- actual = actual.replaceAll(Pattern.quote(File.separator), "/").
- replaceAll("[A-Z]:/", "/");
- assertEquals("Actual: " + actual + "\nExpected: " + expected, expected, actual);
- } else {
- assertTrue("Actual: " + actual + "\nExpected: " + expected,
- loadedMerger.checkValidUpdate(merger1.getDataSets()));
- }
- }
-
- public void testUpdateWithRemovedOverlay() throws Exception {
- // Test with removed overlay
- ResourceMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- ResourceMerger merger2 = createMerger(new String[][]{
- new String[]{"main", "/main/res2", "/main/res1"},
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testUpdateWithReplacedOverlays() throws Exception {
- // Test with different overlays
- ResourceMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- ResourceMerger merger2 = createMerger(new String[][] {
- new String[] { "main", "/main/res2", "/main/res1" },
- new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testUpdateWithReorderedOverlays() throws Exception {
- // Test with different overlays
- ResourceMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
- new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- ResourceMerger merger2 = createMerger(new String[][] {
- new String[] { "main", "/main/res2", "/main/res1" },
- new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
- new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testUpdateWithRemovedSourceFile() throws Exception {
- // Test with different source files
- ResourceMerger merger1 = createMerger(new String[][] {
- new String[] { "main", "/main/res1", "/main/res2" },
- });
-
- // 2nd merger with different order source files in sets.
- ResourceMerger merger2 = createMerger(new String[][]{
- new String[]{"main", "/main/res1"},
- });
-
- assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
- }
-
- public void testChangedIgnoredFile() throws Exception {
- ResourceSet res = ResourceSetTest.getBaseResourceSet(false /*normalize*/);
-
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.addDataSet(res);
-
- File root = TestUtils.getRoot("resources", "baseSet");
- File changedCVSFoo = new File(root, "CVS/foo.txt");
- FileValidity<ResourceSet> fileValidity = resourceMerger.findDataSetContaining(
- changedCVSFoo);
-
- assertEquals(FileValidity.FileStatus.IGNORED_FILE, fileValidity.status);
- }
-
- public void testIncDataForRemovedFile() throws Exception {
- File root = TestUtils.getCanonicalRoot("resources", "removedFile");
- File fakeBlobRoot = getMergedBlobFolder(root);
-
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(1, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // Load the main set
- ResourceSet mainSet = sets.get(0);
- File resBase = new File(root, "res");
- File resDrawable = new File(resBase, ResourceFolderType.DRAWABLE.getName());
-
- // removed file
- File resIconRemoved = new File(resDrawable, "removed.png");
- mainSet.updateWith(resBase, resIconRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the content.
- ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
-
- // check layout/main is unchanged
- List<ResourceItem> removedIcon = mergedMap.get("drawable/removed");
- assertEquals(1, removedIcon.size());
- assertTrue(removedIcon.get(0).isRemoved());
- assertTrue(removedIcon.get(0).isWritten());
- assertFalse(removedIcon.get(0).isTouched());
-
- // write and check the result of writeResourceFolder
- // copy the current resOut which serves as pre incremental update state.
- File outFolder = getFolderCopy(new File(root, "out"));
-
- // write the content of the resource merger.
- MergedResourceWriter writer = getConsumer(outFolder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- File outDrawableFolder = new File(outFolder, ResourceFolderType.DRAWABLE.getName());
-
- // check the files are correct
- assertFalse(new File(outDrawableFolder, "removed.png").isFile());
- assertTrue(new File(outDrawableFolder, "icon.png").isFile());
-
- // now write the blob
- File outBlobFolder = Files.createTempDir();
- resourceMerger.writeBlobTo(outBlobFolder, writer);
-
- // check the removed icon is not present.
- ResourceMerger resourceMerger2 = new ResourceMerger();
- assertTrue(resourceMerger2.loadFromBlob(outBlobFolder, true /*incrementalState*/));
-
- mergedMap = resourceMerger2.getDataMap();
- removedIcon = mergedMap.get("drawable/removed");
- assertTrue(removedIcon.isEmpty());
- }
-
- public void testMergedDeclareStyleable() throws Exception {
- File root = TestUtils.getRoot("resources", "declareStyleable");
-
- // load both base and overlay set
- File baseRoot = new File(root, "base");
- ResourceSet baseSet = new ResourceSet("main");
- baseSet.addSource(baseRoot);
- RecordingLogger logger = new RecordingLogger();
- baseSet.loadFromFiles(logger);
- checkLogger(logger);
-
- File overlayRoot = new File(root, "overlay");
- ResourceSet overlaySet = new ResourceSet("overlay");
- overlaySet.addSource(overlayRoot);
- logger = new RecordingLogger();
- overlaySet.loadFromFiles(logger);
- checkLogger(logger);
-
- // create a merger
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.addDataSet(baseSet);
- resourceMerger.addDataSet(overlaySet);
-
- // write the merge result.
- File folder = Files.createTempDir();
- folder.deleteOnExit();
-
- MergedResourceWriter writer = getConsumer(folder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- // load the result as a set.
- ResourceSet mergedSet = new ResourceSet("merged");
- mergedSet.addSource(folder);
- logger = new RecordingLogger();
- mergedSet.loadFromFiles(logger);
- checkLogger(logger);
-
- ListMultimap<String, ResourceItem> map = mergedSet.getDataMap();
- assertEquals(4, map.size());
-
- List<ResourceItem> items = map.get("declare-styleable/foo");
- assertNotNull(items);
- assertEquals(1, items.size());
-
- ResourceItem item = items.get(0);
- assertNotNull(item);
-
- // now we need to look at the item's value (which is the XML).
- // We're looking for 3 attributes.
- List<String> expectedAttrs = Lists.newArrayList("bar", "bar1", "boo");
- Node rootNode = item.getValue();
- assertNotNull(rootNode);
- NodeList sourceNodes = rootNode.getChildNodes();
- for (int i = 0, n = sourceNodes.getLength(); i < n; i++) {
- Node sourceNode = sourceNodes.item(i);
-
- if (sourceNode.getNodeType() != Node.ELEMENT_NODE ||
- !TAG_ATTR.equals(sourceNode.getLocalName())) {
- continue;
- }
-
- Attr attr = (Attr) sourceNode.getAttributes().getNamedItem(ATTR_NAME);
- if (attr == null) {
- continue;
- }
-
- String attrName = attr.getValue();
-
- assertTrue("Check expected " + attrName, expectedAttrs.contains(attrName));
- expectedAttrs.remove(attrName);
- }
-
- assertTrue("Check emptiness of " + expectedAttrs.toString(), expectedAttrs.isEmpty());
- }
-
- public void testUnchangedMergedItem() throws Exception {
- // locate the merger file that contains exactly the result of the source folders.
- File root = TestUtils.getRoot("resources", "declareStyleable");
- File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "unchanged_merger.xml"));
-
- // load a resource merger based on it.
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- // create a fake consumer
- FakeMergeConsumer consumer = new FakeMergeConsumer();
-
- // do the merge
- resourceMerger.mergeData(consumer, false /*doCleanUp*/);
-
- // test result of merger.
- assertTrue(consumer.touchedItems.isEmpty());
- assertTrue(consumer.removedItems.isEmpty());
- }
-
- public void testRemovedMergedItem() throws Exception {
- // locate the merger file that contains exactly the result of the source folders.
- File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
- File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "removed_merger.xml"));
-
- // load a resource merger based on it.
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- // we know have to tell the merger that the values files have been touched
- // to trigger the removal detection based on the original merger blob.
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // Load the main set
- ResourceSet mainSet = sets.get(0);
- File mainRoot = new File(root, "base");
- File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName());
-
- // trigger changed file event
- File touchedValueFile = new File(mainValues, "values.xml");
- mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // same with overlay set.
- ResourceSet overlaySet = sets.get(1);
- File overlayRoot = new File(root, "overlay");
- File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName());
-
- // trigger changed file event
- touchedValueFile = new File(overlayValues, "values.xml");
- overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // create a fake consumer
- FakeMergeConsumer consumer = new FakeMergeConsumer();
-
- // do the merge
- resourceMerger.mergeData(consumer, false /*doCleanUp*/);
-
- // test result of merger.
- assertTrue(consumer.touchedItems.isEmpty());
- assertEquals(1, consumer.removedItems.size());
- }
-
- public void testTouchedMergedItem() throws Exception {
- // locate the merger file that contains exactly the result of the source folders.
- File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
- File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "touched_merger.xml"));
-
- // load a resource merger based on it.
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- // we know have to tell the merger that the values files have been touched
- // to trigger the removal detection based on the original merger blob.
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // Load the main set
- ResourceSet mainSet = sets.get(0);
- File mainRoot = new File(root, "base");
- File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName());
-
- // trigger changed file event
- File touchedValueFile = new File(mainValues, "values.xml");
- mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // create a fake consumer
- FakeMergeConsumer consumer = new FakeMergeConsumer();
-
- // do the merge
- resourceMerger.mergeData(consumer, false /*doCleanUp*/);
-
- // test result of merger.
- assertEquals(1, consumer.touchedItems.size());
- assertTrue(consumer.removedItems.isEmpty());
- }
-
- public void testTouchedNoDiffMergedItem() throws Exception {
- // locate the merger file that contains exactly the result of the source folders.
- File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
- File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "touched_nodiff_merger.xml"));
-
- // load a resource merger based on it.
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- // we know have to tell the merger that the values files have been touched
- // to trigger the removal detection based on the original merger blob.
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // Load the overlay set
- ResourceSet overlaySet = sets.get(1);
- File overlayRoot = new File(root, "overlay");
- File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName());
-
- // trigger changed file event
- File touchedValueFile = new File(overlayValues, "values.xml");
- overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // create a fake consumer
- FakeMergeConsumer consumer = new FakeMergeConsumer();
-
- // do the merge
- resourceMerger.mergeData(consumer, false /*doCleanUp*/);
-
- // test result of merger.
- assertTrue(consumer.touchedItems.isEmpty());
- assertTrue(consumer.removedItems.isEmpty());
- }
-
- public void testRemovedOtherWithNoNoDiffTouchMergedItem() throws Exception {
- // test that when a non-merged resources is changed/removed, the result of the merge still
- // contain the merged items even if they were touched but had no change.
-
- // locate the merger file that contains exactly the result of the source folders.
- File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
- File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "removed_other_merger.xml"));
-
- // load a resource merger based on it.
- ResourceMerger resourceMerger = new ResourceMerger();
- assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
- checkSourceFolders(resourceMerger);
-
- // we know have to tell the merger that the values files have been touched
- // to trigger the removal detection based on the original merger blob.
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // Load the main set
- ResourceSet mainSet = sets.get(0);
- File mainRoot = new File(root, "base");
- File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName());
-
- // trigger changed file event
- File touchedValueFile = new File(mainValues, "values.xml");
- mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // same for overlay
- ResourceSet overlaySet = sets.get(1);
- File overlayRoot = new File(root, "overlay");
- File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName());
-
- // trigger changed file event
- touchedValueFile = new File(overlayValues, "values.xml");
- overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // create a fake consumer
- FakeMergeConsumer consumer = new FakeMergeConsumer();
-
- // do the merge
- resourceMerger.mergeData(consumer, false /*doCleanUp*/);
-
- // test result of merger.
- // only 3 items added since attr/bar isn't added (declared inline)
- assertEquals(3, consumer.addedItems.size());
- // no touched items
- assertTrue(consumer.touchedItems.isEmpty());
- // one removed string item
- assertEquals(1, consumer.removedItems.size());
- }
-
- public void testStringWhiteSpaces() throws Exception {
- File root = TestUtils.getRoot("resources", "stringWhiteSpaces");
-
- // load res folder
- ResourceSet baseSet = new ResourceSet("main");
- baseSet.addSource(root);
- RecordingLogger logger = new RecordingLogger();
- baseSet.loadFromFiles(logger);
- checkLogger(logger);
-
- // create a merger
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.addDataSet(baseSet);
-
- // write the merge result.
- File folder = Files.createTempDir();
- folder.deleteOnExit();
-
- MergedResourceWriter writer = getConsumer(folder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- // load the result as a set.
- ResourceSet mergedSet = new ResourceSet("merged");
- mergedSet.addSource(folder);
- logger = new RecordingLogger();
- mergedSet.loadFromFiles(logger);
- checkLogger(logger);
-
- ListMultimap<String, ResourceItem> originalItems = baseSet.getDataMap();
- ListMultimap<String, ResourceItem> mergedItems = mergedSet.getDataMap();
-
- for (Map.Entry<String, Collection<ResourceItem>> entry : originalItems.asMap().entrySet()) {
- Collection<ResourceItem> originalItemList = entry.getValue();
- Collection<ResourceItem> mergedItemList = mergedItems.asMap().get(entry.getKey());
-
- // the collection should only have a single items
- assertEquals(1, originalItemList.size());
- assertEquals(1, mergedItemList.size());
-
- ResourceItem originalItem = originalItemList.iterator().next();
- ResourceItem mergedItem = mergedItemList.iterator().next();
-
- assertTrue(originalItem.compareValueWith(mergedItem));
- }
- }
-
- /**
- * Creates a fake merge with given sets.
- *
- * the data is an array of sets.
- *
- * Each set is [ setName, folder1, folder2, ...]
- *
- * @param data the data sets
- * @return the merger
- */
- private static ResourceMerger createMerger(String[][] data) {
- ResourceMerger merger = new ResourceMerger();
- for (String[] setData : data) {
- ResourceSet set = new ResourceSet(setData[0]);
- merger.addDataSet(set);
- for (int i = 1, n = setData.length; i < n; i++) {
- set.addSource(new File(setData[i]));
- }
- }
-
- return merger;
- }
-
- private static ResourceMerger getResourceMerger(boolean normalize)
- throws MergingException, IOException {
- File root = TestUtils.getRoot("resources", "baseMerge");
-
- ResourceSet res = ResourceSetTest.getBaseResourceSet(normalize);
-
- RecordingLogger logger = new RecordingLogger();
-
- ResourceSet overlay = new ResourceSet("overlay");
- overlay.setNormalizeResources(normalize);
- overlay.addSource(new File(root, "overlay"));
- overlay.loadFromFiles(logger);
-
- checkLogger(logger);
-
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.addDataSet(res);
- resourceMerger.addDataSet(overlay);
-
- return resourceMerger;
- }
-
- private File getWrittenResources() throws MergingException, IOException {
- ResourceMerger resourceMerger = getResourceMerger(false /*normalize*/);
-
- File folder = Files.createTempDir();
-
- MergedResourceWriter writer = getConsumer(folder);
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
-
- return folder;
- }
-
- private static File getIncMergeRoot(String name) throws IOException {
- File root = TestUtils.getCanonicalRoot("resources", "incMergeData");
- return new File(root, name);
- }
-
- private static File getFolderCopy(File folder) throws IOException {
- File dest = Files.createTempDir();
- copyFolder(folder, dest);
- return dest;
- }
-
- private static void copyFolder(File from, File to) throws IOException {
- if (from.isFile()) {
- Files.copy(from, to);
- } else if (from.isDirectory()) {
- if (!to.exists()) {
- to.mkdirs();
- }
-
- File[] children = from.listFiles();
- if (children != null) {
- for (File f : children) {
- copyFolder(f, new File(to, f.getName()));
- }
- }
- }
- }
-
- private static Map<String, String> quickStringOnlyValueFileParser(File file)
- throws IOException, MergingException {
- Map<String, String> result = Maps.newHashMap();
-
- Document document = ValueResourceParser2.parseDocument(file);
-
- // get the root node
- Node rootNode = document.getDocumentElement();
- if (rootNode == null) {
- return Collections.emptyMap();
- }
-
- NodeList nodes = rootNode.getChildNodes();
-
- for (int i = 0, n = nodes.getLength(); i < n; i++) {
- Node node = nodes.item(i);
-
- if (node.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
- if (node.getNodeName().equals(SdkConstants.TAG_EAT_COMMENT)) {
- continue;
- }
-
- ResourceType type = ValueResourceParser2.getType(node, file);
- if (type != ResourceType.STRING) {
- throw new IllegalArgumentException("Only String resources supported.");
- }
- String name = ValueResourceParser2.getName(node);
-
- String value = null;
-
- NodeList nodeList = node.getChildNodes();
- nodeLoop: for (int ii = 0, nn = nodes.getLength(); ii < nn; ii++) {
- Node subNode = nodeList.item(ii);
-
- switch (subNode.getNodeType()) {
- case Node.COMMENT_NODE:
- break;
- case Node.TEXT_NODE:
- value = subNode.getNodeValue().trim(); // TODO: remove trim.
- break nodeLoop;
- case Node.ELEMENT_NODE:
- break;
- }
- }
-
- result.put(name, value != null ? value : "");
- }
-
- return result;
- }
-
- public void testAppendedSourceComment() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
- RecordingLogger logger = new RecordingLogger();
- File folder = getWrittenResources();
- ResourceSet writtenSet = new ResourceSet("unused");
- writtenSet.addSource(folder);
- writtenSet.loadFromFiles(logger);
- compareResourceMaps(merger, writtenSet, false /*full compare*/);
- checkLogger(logger);
-
- File layout = new File(folder, FD_RES_LAYOUT + File.separator + "main.xml");
- assertTrue(layout.exists());
- String layoutXml = Files.toString(layout, Charsets.UTF_8);
- assertTrue(layoutXml.contains("main.xml")); // in <!-- From: /full/path/to/main.xml -->
- int index = layoutXml.indexOf("From: ");
- assertTrue(index != -1);
- String path = layoutXml.substring(index + 6, layoutXml.indexOf(' ', index + 6));
- File file = SdkUtils.urlToFile(path);
- assertTrue(path, file.exists());
- assertFalse(Arrays.equals(Files.toByteArray(file), Files.toByteArray(layout)));
-
- // Also make sure .png files were NOT modified
- File root = TestUtils.getRoot("resources", "baseMerge");
- assertNotNull(root);
- File original = new File(root,
- "overlay/drawable-ldpi/icon.png".replace('/', File.separatorChar));
- File copied = new File(folder, FD_RES_DRAWABLE + File.separator + "icon.png");
- assertTrue(Arrays.equals(Files.toByteArray(original), Files.toByteArray(copied)));
- }
-
- public void testWritePermission() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- File folder = Files.createTempDir();
- boolean writable = folder.setWritable(false);
- if (!writable) {
- // Not supported on this platform
- return;
- }
- try {
- merger.writeBlobTo(folder,
- getConsumer());
- } catch (MergingException e) {
- File file = new File(folder, "merger.xml");
- assertEquals(file.getPath() + ": Error: (Permission denied)",
- e.getMessage());
- return;
- }
- fail("Exception not thrown as expected");
- }
-
- public void testWriteAndReadBlob() throws Exception {
- ResourceMerger merger = getResourceMerger(false /*normalize*/);
-
- File folder = Files.createTempDir();
- merger.writeBlobTo(folder,
- getConsumer());
-
- // new merger to read the blob
- ResourceMerger loadedMerger = new ResourceMerger();
- assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
- }
-
- public void testInvalidFileNames() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSet5");
- ResourceSet resourceSet = new ResourceSet("brokenSet5");
- resourceSet.addSource(root);
- RecordingLogger logger = new RecordingLogger();
-
- try {
- resourceSet.loadFromFiles(logger);
- } catch (MergingException e) {
- File file = new File(root, "layout" + File.separator + "ActivityMain.xml");
- file = file.getAbsoluteFile();
- assertEquals(
- file.getPath() +
- ": Error: 'A' is not a valid file-based resource name character: "
- + "File-based resource names must contain only lowercase a-z, 0-9,"
- + " or underscore",
- e.getMessage());
- return;
- }
- fail("Expected error");
- }
-
- public void testStricterInvalidFileNames() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSetDrawableFileName");
- ResourceSet resourceSet = new ResourceSet("brokenSetDrawableFileName");
- resourceSet.addSource(root);
- RecordingLogger logger = new RecordingLogger();
-
- try {
- resourceSet.loadFromFiles(logger);
- } catch (MergingException e) {
- File file = new File(root, "drawable" + File.separator + "1icon.png");
- file = file.getAbsoluteFile();
- assertEquals(
- file.getPath() +
- ": Error: The resource name must start with a letter",
- e.getMessage());
- return;
- }
- fail("Expected error");
- }
-
- public void testXmlParseError1() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSet6");
- try {
- ResourceSet resourceSet = new ResourceSet("brokenSet6");
- resourceSet.addSource(root);
- RecordingLogger logger = new RecordingLogger();
- resourceSet.loadFromFiles(logger);
-
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.addDataSet(resourceSet);
-
-
- MergedResourceWriter writer = getConsumer();
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
- } catch (MergingException e) {
- File file = new File(root, "values" + File.separator + "dimens.xml");
- file = file.getAbsoluteFile();
- assertEquals(file.getPath() + ":4:6: Error: The content of elements must consist "
- + "of well-formed character data or markup.",
- e.getMessage());
- return;
- }
- fail("Expected error");
- }
-
- public void testXmlParseError7() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSet7");
- try {
- ResourceSet resourceSet = new ResourceSet("brokenSet7");
- resourceSet.addSource(root);
- RecordingLogger logger = new RecordingLogger();
- resourceSet.loadFromFiles(logger);
-
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.addDataSet(resourceSet);
-
-
- MergedResourceWriter writer = getConsumer();
- resourceMerger.mergeData(writer, false /*doCleanUp*/);
- } catch (MergingException e) {
- File file = new File(root, "values" + File.separator + "dimens.xml");
- file = file.getAbsoluteFile();
- assertEquals(file.getPath() + ":2:17: Error: Open quote is expected for "
- + "attribute \"{1}\" associated with an element type \"name\".",
- e.getMessage());
- return;
- }
- fail("Expected error");
- }
-
-
- // create a fake consumer
- private static class FakeMergeConsumer implements MergeConsumer<ResourceItem> {
- final List<ResourceItem> addedItems = Lists.newArrayList();
- final List<ResourceItem> touchedItems = Lists.newArrayList();
- final List<ResourceItem> removedItems = Lists.newArrayList();
-
- @Override
- public void start(@NonNull DocumentBuilderFactory factory)
- throws ConsumerException {
- // do nothing
- }
-
- @Override
- public void end() throws ConsumerException {
- // do nothing
- }
-
- @Override
- public void addItem(@NonNull ResourceItem item) throws ConsumerException {
- // the default res merge writer calls this, so we should too.
- // this is to test that the merged item are properly created
- @SuppressWarnings("UnusedDeclaration")
- ResourceFile.FileType type = item.getSourceType();
-
- if (item.isTouched()) {
- touchedItems.add(item);
- }
-
- addedItems.add(item);
- }
-
- @Override
- public void removeItem(@NonNull ResourceItem removedItem,
- @Nullable ResourceItem replacedBy)
- throws ConsumerException {
- removedItems.add(removedItem);
- }
-
- @Override
- public boolean ignoreItemInMerge(ResourceItem item) {
- return item.getIgnoredFromDiskMerge();
- }
- }
-
- @NonNull
- private MergedResourceWriter getConsumer() {
- return getConsumer(Files.createTempDir());
- }
-
- @NonNull
- private MergedResourceWriter getConsumer(File tempDir) {
- return new MergedResourceWriter(
- tempDir,
- mPngCruncher,
- false,
- false,
- null,
- mPreprocessor);
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java
deleted file mode 100755
index 3656a27..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java
+++ /dev/null
@@ -1,756 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.ide.common.rendering.api.AttrResourceValue;
-import com.android.ide.common.rendering.api.ItemResourceValue;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.testutils.TestUtils;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Multimap;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-public class ResourceRepositoryTest extends BaseTestCase {
-
- public void testMergeByCount() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
-
- assertEquals(6, items.get(ResourceType.DRAWABLE).size());
- assertEquals(1, items.get(ResourceType.RAW).size());
- assertEquals(4, items.get(ResourceType.LAYOUT).size());
- assertEquals(1, items.get(ResourceType.COLOR).size());
- assertEquals(4, items.get(ResourceType.STRING).size());
- assertEquals(1, items.get(ResourceType.STYLE).size());
- assertEquals(1, items.get(ResourceType.ARRAY).size());
- assertEquals(7, items.get(ResourceType.ATTR).size());
- assertEquals(1, items.get(ResourceType.DECLARE_STYLEABLE).size());
- assertEquals(3, items.get(ResourceType.DIMEN).size());
- assertEquals(1, items.get(ResourceType.ID).size());
- assertEquals(1, items.get(ResourceType.INTEGER).size());
- }
-
- public void testMergedResourcesByName() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- // use ? between type and qualifier because of declare-styleable
- verifyResourceExists(repo,
- "drawable/icon",
- "drawable?ldpi/icon",
- "drawable/icon2",
- "drawable/patch",
- "drawable/color_drawable",
- "drawable/drawable_ref",
- "raw/foo",
- "layout/main",
- "layout/layout_ref",
- "layout/alias_replaced_by_file",
- "layout/file_replaced_by_alias",
- "color/color",
- "string/basic_string",
- "string/xliff_string",
- "string/styled_string",
- "style/style",
- "array/string_array",
- "attr/dimen_attr",
- "attr/string_attr",
- "attr/enum_attr",
- "attr/flag_attr",
- "attr/blah",
- "attr/blah2",
- "attr/flagAttr",
- "declare-styleable/declare_styleable",
- "dimen/dimen",
- "dimen?sw600dp/offset",
- "dimen?sw600dp-v13/offset",
- "id/item_id",
- "integer/integer"
- );
- }
-
- public void testBaseStringValue() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- List<ResourceItem> itemList = repo.getResourceItem(ResourceType.STRING, "basic_string");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
-
- ResourceValue value = itemList.get(0).getResourceValue(false);
- assertNotNull(value);
-
- assertEquals("overlay_string", value.getValue());
- }
-
- public void testBaseStyledStringValue() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- List<ResourceItem> itemList = repo.getResourceItem(ResourceType.STRING, "styled_string");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
-
- ResourceValue value = itemList.get(0).getResourceValue(false);
- assertNotNull(value);
-
- assertEquals("Forgot your username or password?\nVisit google.com/accounts/recovery.",
- value.getValue());
- }
-
- public void testBaseColorValue() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- List<ResourceItem> itemList = repo.getResourceItem(ResourceType.COLOR, "color");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
-
- ResourceValue value = itemList.get(0).getResourceValue(false);
- assertNotNull(value);
-
- assertEquals("#FFFFFFFF", value.getValue());
- }
-
- public void testBaseLayoutAliasValue() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- List<ResourceItem> itemList = repo.getResourceItem(ResourceType.LAYOUT, "layout_ref");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
-
- ResourceValue value = itemList.get(0).getResourceValue(false);
- assertNotNull(value);
-
- assertEquals("@layout/ref", value.getValue());
- }
-
- public void testBaseAttrValue() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- List<ResourceItem> itemList = repo.getResourceItem(ResourceType.ATTR, "flag_attr");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
-
- ResourceValue value = itemList.get(0).getResourceValue(false);
- assertNotNull(value);
- assertTrue(value instanceof AttrResourceValue);
- AttrResourceValue attrValue = (AttrResourceValue) value;
-
- Map<String, Integer> attrValues = attrValue.getAttributeValues();
- assertNotNull(attrValues);
- assertEquals(3, attrValues.size());
-
- Integer i = attrValues.get("normal");
- assertNotNull(i);
- assertEquals(Integer.valueOf(0), i);
-
- i = attrValues.get("bold");
- assertNotNull(i);
- assertEquals(Integer.valueOf(1), i);
-
- i = attrValues.get("italic");
- assertNotNull(i);
- assertEquals(Integer.valueOf(2), i);
- }
-
- public void testBaseStyleValue() throws Exception {
- ResourceRepository repo = getResourceRepository();
-
- List<ResourceItem> itemList = repo.getResourceItem(ResourceType.STYLE, "style");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
-
- ResourceValue value = itemList.get(0).getResourceValue(false);
- assertNotNull(value);
- assertTrue(value instanceof StyleResourceValue);
- StyleResourceValue styleResourceValue = (StyleResourceValue) value;
-
- assertEquals("@android:style/Holo.Light", styleResourceValue.getParentStyle());
-
- ItemResourceValue styleValue = styleResourceValue.getItem("singleLine", true /*framework*/);
- assertNotNull(styleValue);
- assertEquals("true", styleValue.getValue());
-
- styleValue = styleResourceValue.getItem("textAppearance", true /*framework*/);
- assertNotNull(styleValue);
- assertEquals("@style/TextAppearance.WindowTitle", styleValue.getValue());
-
- styleValue = styleResourceValue.getItem("shadowColor", true /*framework*/);
- assertNotNull(styleValue);
- assertEquals("#BB000000", styleValue.getValue());
-
- styleValue = styleResourceValue.getItem("shadowRadius", true /*framework*/);
- assertNotNull(styleValue);
- assertEquals("2.75", styleValue.getValue());
-
- styleValue = styleResourceValue.getItem("foo", false /*framework*/);
- assertNotNull(styleValue);
- assertEquals("foo", styleValue.getValue());
- }
-
- public void testUpdateWithBasicFiles() throws Exception {
- File root = getIncMergeRoot("basicFiles");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- // write the content in a repo.
- ResourceRepository repo = new ResourceRepository(false);
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- // checks the initial state of the repo
- Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
- ListMultimap<String, ResourceItem> drawables = items.get(ResourceType.DRAWABLE);
- assertNotNull("Drawable null check", drawables);
- assertEquals("Drawable size check", 6, drawables.size());
- verifyResourceExists(repo,
- "drawable/new_overlay",
- "drawable/removed",
- "drawable?ldpi/removed",
- "drawable/touched",
- "drawable/removed_overlay",
- "drawable/untouched");
-
- // Apply updates
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // first set is the main one, no change here
- ResourceSet mainSet = sets.get(0);
- File mainBase = new File(root, "main");
- File mainDrawable = new File(mainBase, "drawable");
- File mainDrawableLdpi = new File(mainBase, "drawable-ldpi");
-
- // touched/removed files:
- File mainDrawableTouched = new File(mainDrawable, "touched.png");
- mainSet.updateWith(mainBase, mainDrawableTouched, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- File mainDrawableRemoved = new File(mainDrawable, "removed.png");
- mainSet.updateWith(mainBase, mainDrawableRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- File mainDrawableLdpiRemoved = new File(mainDrawableLdpi, "removed.png");
- mainSet.updateWith(mainBase, mainDrawableLdpiRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // ----------------
- // second set is the overlay one
- ResourceSet overlaySet = sets.get(1);
- File overlayBase = new File(root, "overlay");
- File overlayDrawable = new File(overlayBase, "drawable");
- File overlayDrawableHdpi = new File(overlayBase, "drawable-hdpi");
-
- // new/removed files:
- File overlayDrawableNewOverlay = new File(overlayDrawable, "new_overlay.png");
- overlaySet.updateWith(overlayBase, overlayDrawableNewOverlay, FileStatus.NEW, logger);
- checkLogger(logger);
-
- File overlayDrawableRemovedOverlay = new File(overlayDrawable, "removed_overlay.png");
- overlaySet.updateWith(overlayBase, overlayDrawableRemovedOverlay, FileStatus.REMOVED,
- logger);
- checkLogger(logger);
-
- File overlayDrawableHdpiNewAlternate = new File(overlayDrawableHdpi, "new_alternate.png");
- overlaySet.updateWith(overlayBase, overlayDrawableHdpiNewAlternate, FileStatus.NEW, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the new content.
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- drawables = items.get(ResourceType.DRAWABLE);
- assertNotNull("Drawable null check", drawables);
- assertEquals("Drawable size check", 5, drawables.size());
- verifyResourceExists(repo,
- "drawable/new_overlay",
- "drawable/touched",
- "drawable/removed_overlay",
- "drawable/untouched",
- "drawable?hdpi/new_alternate");
- checkRemovedItems(resourceMerger);
- }
-
- public void testUpdateWithBasicValues() throws Exception {
- File root = getIncMergeRoot("basicValues");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- // write the content in a repo.
- ResourceRepository repo = new ResourceRepository(false);
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- // checks the initial state of the repo
- Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
- ListMultimap<String, ResourceItem> strings = items.get(ResourceType.STRING);
- assertNotNull("String null check", strings);
- assertEquals("String size check", 5, strings.size());
- verifyResourceExists(repo,
- "string/untouched",
- "string/touched",
- "string/removed",
- "string?en/removed",
- "string/new_overlay");
-
- // apply updates
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // first set is the main one, no change here
- ResourceSet mainSet = sets.get(0);
- File mainBase = new File(root, "main");
- File mainValues = new File(mainBase, "values");
- File mainValuesEn = new File(mainBase, "values-en");
-
- // touched file:
- File mainValuesTouched = new File(mainValues, "values.xml");
- mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // removed files
- File mainValuesEnRemoved = new File(mainValuesEn, "values.xml");
- mainSet.updateWith(mainBase, mainValuesEnRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // ----------------
- // second set is the overlay one
- ResourceSet overlaySet = sets.get(1);
- File overlayBase = new File(root, "overlay");
- File overlayValues = new File(overlayBase, "values");
- File overlayValuesFr = new File(overlayBase, "values-fr");
-
- // new files:
- File overlayValuesNew = new File(overlayValues, "values.xml");
- overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.NEW, logger);
- checkLogger(logger);
-
- File overlayValuesFrNew = new File(overlayValuesFr, "values.xml");
- overlaySet.updateWith(overlayBase, overlayValuesFrNew, FileStatus.NEW, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the new content.
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- strings = items.get(ResourceType.STRING);
- assertNotNull("String null check", strings);
- assertEquals("String size check", 4, strings.size());
- verifyResourceExists(repo,
- "string/untouched",
- "string/touched",
- "string/new_overlay",
- "string?fr/new_alternate");
- checkRemovedItems(resourceMerger);
- }
-
- public void testUpdateWithBasicValues2() throws Exception {
- File root = getIncMergeRoot("basicValues2");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(2, sets.size());
-
- // write the content in a repo.
- ResourceRepository repo = new ResourceRepository(false);
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- // checks the initial state of the repo
- Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
- ListMultimap<String, ResourceItem> strings = items.get(ResourceType.STRING);
- assertNotNull("String null check", strings);
- assertEquals("String size check", 2, strings.size());
- verifyResourceExists(repo,
- "string/untouched",
- "string/removed_overlay");
-
- // apply updates
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // first set is the main one, no change here
-
- // ----------------
- // second set is the overlay one
- ResourceSet overlaySet = sets.get(1);
- File overlayBase = new File(root, "overlay");
- File overlayValues = new File(overlayBase, "values");
-
- // new files:
- File overlayValuesNew = new File(overlayValues, "values.xml");
- overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the new content.
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- strings = items.get(ResourceType.STRING);
- assertNotNull("String null check", strings);
- assertEquals("String size check", 2, strings.size());
- verifyResourceExists(repo,
- "string/untouched",
- "string/removed_overlay");
- checkRemovedItems(resourceMerger);
- }
-
- public void testUpdateWithFilesVsValues() throws Exception {
- File root = getIncMergeRoot("filesVsValues");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
- checkSourceFolders(resourceMerger);
-
- List<ResourceSet> sets = resourceMerger.getDataSets();
- assertEquals(1, sets.size());
-
- // write the content in a repo.
- ResourceRepository repo = new ResourceRepository(false);
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- // checks the initial state of the repo
- Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
- ListMultimap<String, ResourceItem> layouts = items.get(ResourceType.LAYOUT);
- assertNotNull("String null check", layouts);
- assertEquals("String size check", 3, layouts.size());
- verifyResourceExists(repo,
- "layout/main",
- "layout/file_replaced_by_alias",
- "layout/alias_replaced_by_file");
-
- // apply updates
- RecordingLogger logger = new RecordingLogger();
-
- // ----------------
- // Load the main set
- ResourceSet mainSet = sets.get(0);
- File mainBase = new File(root, "main");
- File mainValues = new File(mainBase, ResourceFolderType.VALUES.getName());
- File mainLayout = new File(mainBase, ResourceFolderType.LAYOUT.getName());
-
- // touched file:
- File mainValuesTouched = new File(mainValues, "values.xml");
- mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
- checkLogger(logger);
-
- // new file:
- File mainLayoutNew = new File(mainLayout, "alias_replaced_by_file.xml");
- mainSet.updateWith(mainBase, mainLayoutNew, FileStatus.NEW, logger);
- checkLogger(logger);
-
- // removed file
- File mainLayoutRemoved = new File(mainLayout, "file_replaced_by_alias.xml");
- mainSet.updateWith(mainBase, mainLayoutRemoved, FileStatus.REMOVED, logger);
- checkLogger(logger);
-
- // validate for duplicates
- resourceMerger.validateDataSets();
-
- // check the new content.
- resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
-
- layouts = items.get(ResourceType.LAYOUT);
- assertNotNull("String null check", layouts);
- assertEquals("String size check", 3, layouts.size());
- verifyResourceExists(repo,
- "layout/main",
- "layout/file_replaced_by_alias",
- "layout/alias_replaced_by_file");
- checkRemovedItems(resourceMerger);
- }
-
- public void testUpdateFromOldFile() throws Exception {
- File root = getIncMergeRoot("oldMerge");
- File fakeRoot = getMergedBlobFolder(root);
- ResourceMerger resourceMerger = new ResourceMerger();
- assertFalse(resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/));
- }
-
- private static void checkRemovedItems(DataMap<? extends DataItem> dataMap) {
- for (DataItem item : dataMap.getDataMap().values()) {
- if (item.isRemoved()) {
- fail("Removed item found: " + item);
- }
- }
- }
-
- /**
- * Creates a fake merge with given sets.
- *
- * the data is an array of sets.
- *
- * Each set is [ setName, folder1, folder2, ...]
- *
- */
- private static ResourceMerger createMerger(String[][] data) {
- ResourceMerger merger = new ResourceMerger();
- for (String[] setData : data) {
- ResourceSet set = new ResourceSet(setData[0]);
- merger.addDataSet(set);
- for (int i = 1, n = setData.length; i < n; i++) {
- set.addSource(new File(setData[i]));
- }
- }
-
- return merger;
- }
-
- /**
- * Returns a merger with the baseSet and baseMerge content.
- */
- private static ResourceMerger getBaseResourceMerger()
- throws MergingException, IOException {
- File root = TestUtils.getRoot("resources", "baseMerge");
-
- ResourceSet res = ResourceSetTest.getBaseResourceSet(false /*normalize*/);
-
- RecordingLogger logger = new RecordingLogger();
-
- ResourceSet overlay = new ResourceSet("overlay");
- overlay.addSource(new File(root, "overlay"));
- overlay.loadFromFiles(logger);
-
- checkLogger(logger);
-
- ResourceMerger resourceMerger = new ResourceMerger();
- resourceMerger.addDataSet(res);
- resourceMerger.addDataSet(overlay);
-
- return resourceMerger;
- }
-
- /**
- * Returns a merger from incMergeData initialized from the files, not from the merger
- * state blog.
- */
- private static ResourceMerger getIncResourceMerger(String rootName, String... sets)
- throws MergingException, IOException {
-
- File root = getIncMergeRoot(rootName);
- RecordingLogger logger = new RecordingLogger();
-
- ResourceMerger resourceMerger = new ResourceMerger();
-
- for (String setName : sets) {
- ResourceSet resourceSet = new ResourceSet(setName);
- resourceSet.addSource(new File(root, setName));
- resourceSet.loadFromFiles(logger);
- checkLogger(logger);
-
- resourceMerger.addDataSet(resourceSet);
- }
-
- return resourceMerger;
- }
-
- private static ResourceRepository getResourceRepository()
- throws MergingException, IOException {
- ResourceMerger merger = getBaseResourceMerger();
-
- ResourceRepository repo = new ResourceRepository(false);
-
- merger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
- return repo;
- }
-
- private static File getIncMergeRoot(String name) throws IOException {
- File root = TestUtils.getCanonicalRoot("resources", "incMergeData");
- return new File(root, name);
- }
-
- private static void verifyResourceExists(ResourceRepository repository,
- String... dataItemKeys) {
- Map<ResourceType, ListMultimap<String, ResourceItem>> items = repository.getItems();
-
- for (String resKey : dataItemKeys) {
- String type, name, qualifier = "";
-
- int pos = resKey.indexOf('/');
- if (pos != -1) {
- name = resKey.substring(pos + 1);
- type = resKey.substring(0, pos);
- } else {
- throw new IllegalArgumentException("Invalid key " + resKey);
- }
-
- // use ? as a qualifier delimiter because of
- // declare-styleable
- pos = type.indexOf('?');
- if (pos != -1) {
- qualifier = type.substring(pos + 1);
- type = type.substring(0, pos);
- }
-
- ResourceType resourceType = ResourceType.getEnum(type);
- assertNotNull("Type check for " + resKey, resourceType);
-
- Multimap<String, ResourceItem> map = items.get(resourceType);
- assertNotNull("Map check for " + resKey, map);
-
- Collection<ResourceItem> list = map.get(name);
- int found = 0;
- for (ResourceItem resourceItem : list) {
- if (resourceItem.getName().equals(name)) {
- String fileQualifier = resourceItem.getSource() != null ?
- resourceItem.getSource().getQualifiers() : "";
-
- if (qualifier.equals(fileQualifier)) {
- found++;
- }
- }
- }
-
- assertEquals("Match for " + resKey, 1, found);
- }
- }
-
- // This utility method has been pretty handy in tracking down resource repository bugs
- // so keeping it for future potential use, but in unit test code so no runtime overhead
- @SuppressWarnings({"deprecation", "ConstantConditions"})
- public static String dumpRepository(ResourceRepository repository) {
- Map<ResourceType, ListMultimap<String, ResourceItem>> mItems = repository.getMap();
- Comparator<ResourceItem> comparator = new Comparator<ResourceItem>() {
- @Override
- public int compare(ResourceItem item1, ResourceItem item2) {
- assert item1.getType() == item2.getType();
- String qualifiers = item2.getSource().getQualifiers();
- return item1.getSource().getQualifiers().compareTo(qualifiers);
- }
- };
-
- StringBuilder sb = new StringBuilder(5000);
- sb.append("Resource Map Dump For Repository ").append(repository)
- .append("\n------------------------------------------------\n");
- for (ResourceType type : ResourceType.values()) {
- ListMultimap<String, ResourceItem> map = mItems.get(type);
- if (map == null) {
- continue;
- }
- sb.append(type.getName()).append(':').append('\n');
-
- List<String> keys = new ArrayList<String>(map.keySet());
- Collections.sort(keys);
- for (String key : keys) {
- List<ResourceItem> items = map.get(key);
- List<ResourceItem> sorted = new ArrayList<ResourceItem>(items);
- Collections.sort(sorted, comparator);
- sb.append(" ").append(type.getName()).append(" ").append(key).append(": ");
- boolean first = true;
- for (ResourceItem item : sorted) {
- if (first) {
- first = false;
- } else {
- sb.append(", ");
- }
- String qualifiers = item.getSource().getQualifiers();
- if (qualifiers.isEmpty()) {
- qualifiers = "default";
- }
- sb.append(qualifiers);
- }
-
- if (!sorted.isEmpty()) {
- ResourceItem item = sorted.get(0);
- ResourceValue resourceValue = item.getResourceValue(repository.isFramework());
- if (resourceValue == null) {
- sb.append(" <no value found>");
- } else {
- String value = resourceValue.getValue();
- if (value == null || value.isEmpty()) {
- if (resourceValue instanceof StyleResourceValue) {
- StyleResourceValue srv = (StyleResourceValue) resourceValue;
- sb.append(" parentStyle=").append(srv.getParentStyle())
- .append("\n");
- for (String name : srv.getNames()) {
- ItemResourceValue value1 = srv.getItem(name, false);
- ItemResourceValue value2 = srv.getItem(name, true);
- if (value1 != null) {
- Boolean framework = false;
- sb.append(" ");
- sb.append(name).append(" ").append(framework).append(" ");
- sb.append(" = ");
- sb.append('"');
- String strValue = value1.getValue();
- if (strValue != null) {
- sb.append(strValue.replace("\n", "\\n"));
- } else {
- sb.append("???");
- }
- sb.append('"');
- }
- if (value2 != null) {
- Boolean framework = true;
- sb.append(" ");
- sb.append(name).append(" ").append(framework).append(" ");
- sb.append(" = ");
- sb.append('"');
- String strValue = value2.getValue();
- if (strValue != null) {
- sb.append(strValue.replace("\n", "\\n"));
- } else {
- sb.append("???");
- }
- sb.append('"');
- }
- }
- } else {
- sb.append(" = \"\"");
- }
- } else {
- sb.append(" = ");
- sb.append('"');
- sb.append(value.replace("\n", "\\n"));
- sb.append('"');
- }
- }
- }
-
- sb.append("\n");
- }
-
- sb.append("\n\n");
- }
-
- return sb.toString();
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java
deleted file mode 100644
index 5390304..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.SdkConstants.FD_RES;
-import static com.android.SdkConstants.FD_RES_DRAWABLE;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
-import static com.android.SdkConstants.FD_RES_VALUES;
-
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.resources.TestResourceRepository;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.LocaleQualifier;
-import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
-import com.android.resources.ResourceType;
-import com.android.resources.ScreenOrientation;
-import com.android.utils.ILogger;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-
- at SuppressWarnings("javadoc")
-public class ResourceRepositoryTest2 extends TestCase {
- private File mTempDir;
- private File mRes;
- private ResourceMerger mResourceMerger;
- private ResourceRepository mRepository;
- private ILogger mLogger;
-
-
- @SuppressWarnings("ResultOfMethodCallIgnored")
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mTempDir = Files.createTempDir();
- mRes = new File(mTempDir, FD_RES);
- mRes.mkdirs();
- File layout = new File(mRes, FD_RES_LAYOUT);
- File layoutLand = new File(mRes, FD_RES_LAYOUT + "-land");
- File values = new File(mRes, FD_RES_VALUES);
- File valuesEs = new File(mRes, FD_RES_VALUES + "-es");
- File valuesEsUs = new File(mRes, FD_RES_VALUES + "-es-rUS");
- File valuesKok = new File(mRes, FD_RES_VALUES + "-b+kok");
- File valuesKokIn = new File(mRes, FD_RES_VALUES + "-b+kok+IN");
- File drawable = new File(mRes, FD_RES_DRAWABLE);
- layout.mkdirs();
- layoutLand.mkdirs();
- values.mkdirs();
- valuesEs.mkdirs();
- valuesEsUs.mkdirs();
- valuesKok.mkdirs();
- valuesKokIn.mkdirs();
- drawable.mkdirs();
- new File(layout, "layout1.xml").createNewFile();
- new File(layoutLand, "layout1.xml").createNewFile();
- new File(layoutLand, "only_land.xml").createNewFile();
- new File(layout, "layout2.xml").createNewFile();
- new File(drawable, "graphic.9.png").createNewFile();
- File strings = new File(values, "strings.xml");
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
- + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
- + " <string name=\"home_title\">Home Sample</string>\n"
- + " <string name=\"show_all_apps\">All</string>\n"
- + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
- + " <string name=\"menu_search\">Search</string>\n"
- + " <string name=\"menu_settings\">Settings</string>\n"
- + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
- + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
- + "</resources>\n", strings, Charsets.UTF_8);
-
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"show_all_apps\">Todo</string>\n"
- + "</resources>\n", new File(valuesEs, "strings.xml"), Charsets.UTF_8);
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"show_all_apps\">Todo</string>\n"
- + "</resources>\n", new File(valuesEsUs, "strings.xml"), Charsets.UTF_8);
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"show_all_apps\">Todo</string>\n"
- + "</resources>\n", new File(valuesKok, "strings.xml"), Charsets.UTF_8);
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"show_all_apps\">Todo</string>\n"
- + "</resources>\n", new File(valuesKokIn, "strings.xml"), Charsets.UTF_8);
-
- if ("testGetMatchingFileAliases".equals(getName())) {
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <item name=\"layout2\" type=\"layout\">@layout/indirect3</item>\n"
- + " <item name=\"indirect3\" type=\"layout\">@layout/indirect2</item>\n"
- + " <item name=\"indirect2\" type=\"layout\">@layout/indirect1</item>\n"
- + " <item name=\"indirect1\" type=\"layout\">@layout/layout1</item>\n"
- + "</resources>", new File(valuesEsUs, "refs.xml"), Charsets.UTF_8);
- }
-
- mResourceMerger = new ResourceMerger();
- ResourceSet resourceSet = new ResourceSet("main");
- resourceSet.addSource(mRes);
- resourceSet.loadFromFiles(mLogger = new RecordingLogger());
- mResourceMerger.addDataSet(resourceSet);
-
- mRepository = new ResourceRepository(false);
- mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- deleteFile(mTempDir);
- }
-
- private static void deleteFile(File dir) {
- if (dir.isDirectory()) {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File f : files) {
- deleteFile(f);
- }
- }
- } else if (dir.isFile()) {
- assertTrue(dir.getPath(), dir.delete());
- }
- }
-
- public void testBasic() throws Exception {
- assertFalse(mRepository.hasResourceItem("@layout/layout0"));
- assertTrue(mRepository.hasResourceItem("@layout/layout1"));
- assertFalse(mRepository.hasResourceItem(ResourceType.LAYOUT, "layout0"));
- assertTrue(mRepository.hasResourceItem(ResourceType.LAYOUT, "layout1"));
- assertFalse(mRepository.hasResourceItem(ResourceType.STRING, "layout1"));
- assertTrue(mRepository.hasResourceItem(ResourceType.STRING, "home_title"));
- assertFalse(mRepository.hasResourceItem(ResourceType.STRING, "home_title2"));
- assertFalse(mRepository.hasResourceItem(ResourceType.DRAWABLE, "graph"));
- assertTrue(mRepository.hasResourceItem(ResourceType.DRAWABLE, "graphic"));
- assertTrue(mRepository.hasResourceItem("@id/action_bar_refresh"));
- assertTrue(mRepository.hasResourceItem("@drawable/graphic"));
- assertTrue(mRepository.hasResourcesOfType(ResourceType.DRAWABLE));
- assertFalse(mRepository.hasResourcesOfType(ResourceType.ANIM));
-
- List<ResourceType> availableResourceTypes = mRepository.getAvailableResourceTypes();
- assertEquals(5, availableResourceTypes.size()); // layout, string, drawable, id, dimen
-
- Collection<String> allStrings = mRepository.getItemsOfType(ResourceType.STRING);
- assertEquals(7, allStrings.size());
-
- List<ResourceItem> itemList = mRepository.getResourceItem(ResourceType.STRING, "menu_settings");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
- for (ResourceItem item : itemList) {
- assertEquals("menu_settings", item.getName());
- assertEquals("@string/menu_settings", item.getXmlString(ResourceType.STRING, false));
- }
- //assertTrue(item.hasDefault());
-
- itemList = mRepository.getResourceItem(ResourceType.STRING, "show_all_apps");
- assertNotNull(itemList);
- assertTrue(itemList.size() > 1);
- for (ResourceItem item : itemList) {
- assertEquals("show_all_apps", item.getName());
- assertEquals("@string/show_all_apps", item.getXmlString(ResourceType.STRING, false));
- }
- //assertTrue(item.hasDefault());
- FolderConfiguration folderConfig = new FolderConfiguration();
- folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("en"));
- Map<ResourceType, Map<String, ResourceValue>> configuredItems = mRepository
- .getConfiguredResources(folderConfig);
- ResourceValue value = configuredItems.get(ResourceType.STRING).get("show_all_apps");
- assertNotNull(value);
- assertEquals("All", value.getValue());
- assertSame(ResourceType.STRING, value.getResourceType());
-
- folderConfig = new FolderConfiguration();
- folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es"));
- configuredItems = mRepository.getConfiguredResources(folderConfig);
- value = configuredItems.get(ResourceType.STRING).get("show_all_apps");
- assertNotNull(value);
- assertEquals("Todo", value.getValue());
- assertSame(ResourceType.STRING, value.getResourceType());
-
- itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "only_land");
- assertNotNull(itemList);
- //assertFalse(item.hasDefault());
- assertEquals(1, itemList.size());
- ResourceFile resourceFile = itemList.get(0).getSource();
- assertEquals("only_land.xml", resourceFile.getFile().getName());
- assertEquals(ScreenOrientation.LANDSCAPE.getResourceValue(), resourceFile.getQualifiers());
-
- itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "layout1");
- assertNotNull(itemList);
- //assertTrue(item.hasDefault());
- assertEquals(2, itemList.size());
-
- SortedSet<String> languages = mRepository.getLanguages();
- assertEquals(2, languages.size());
- assertTrue(languages.contains("es"));
- assertTrue(languages.contains("kok"));
- assertEquals(Collections.singleton("US"), mRepository.getRegions("es"));
- assertEquals(Collections.singleton("IN"), mRepository.getRegions("kok"));
-
-// List<ResourceFile> layouts = mRepository.getSourceFiles(ResourceType.LAYOUT, "layout1",
-// folderConfig);
-// assertNotNull(layouts);
-// assertEquals(1, layouts.size());
-// com.android.ide.common.resources.ResourceFile file1 = layouts.get(0);
-// assertEquals("layout1.xml", file1.getFile().getName());
-// assertSame(mRepository, file1.getRepository());
- }
-
- public void testGetConfiguredResources() throws Exception {
- FolderConfiguration folderConfig = new FolderConfiguration();
- folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es"));
- folderConfig.setScreenOrientationQualifier(
- new ScreenOrientationQualifier(ScreenOrientation.LANDSCAPE));
-
- Map<ResourceType, Map<String, ResourceValue>> configuredResources =
- mRepository.getConfiguredResources(folderConfig);
- Map<String, ResourceValue> strings = configuredResources.get(ResourceType.STRING);
- Map<String, ResourceValue> layouts = configuredResources.get(ResourceType.LAYOUT);
- Map<String, ResourceValue> ids = configuredResources.get(ResourceType.ID);
- Map<String, ResourceValue> dimens = configuredResources.get(ResourceType.DIMEN);
- assertEquals(1, ids.size());
- assertEquals(1, dimens.size());
- assertEquals("dialog_min_width_major", dimens.get("dialog_min_width_major").getName());
- assertEquals("45%", dimens.get("dialog_min_width_major").getValue());
- assertEquals("Todo", strings.get("show_all_apps").getValue());
- assertEquals(3, layouts.size());
- assertNotNull(layouts.get("layout1"));
-
- ResourceFile file = mRepository.getMatchingFile("dialog_min_width_major",
- ResourceType.DIMEN, folderConfig);
- assertNotNull(file);
-// file = mRepository.getMatchingFile("dialog_min_width_major", ResourceFolderType.VALUES,
-// folderConfig);
-// assertNotNull(file);
-// file = mRepository.getMatchingFile("layout1", ResourceFolderType.LAYOUT, folderConfig);
-// assertNotNull(file);
- file = mRepository.getMatchingFile("layout1", ResourceType.LAYOUT, folderConfig);
- assertNotNull(file);
- }
-
- public void testGetMatchingFileAliases() throws Exception {
- FolderConfiguration folderConfig = new FolderConfiguration();
- folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es"));
- folderConfig.setScreenOrientationQualifier(
- new ScreenOrientationQualifier(ScreenOrientation.LANDSCAPE));
-
- Map<ResourceType, Map<String, ResourceValue>> configuredResources =
- mRepository.getConfiguredResources(folderConfig);
- Map<String, ResourceValue> layouts = configuredResources.get(ResourceType.LAYOUT);
- assertEquals(6, layouts.size());
- assertNotNull(layouts.get("layout1"));
-
- folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es-rES"));
- ResourceFile file = mRepository.getMatchingFile("dialog_min_width_major",
- ResourceType.DIMEN, folderConfig);
- assertNotNull(file);
- file = mRepository.getMatchingFile("layout2", ResourceType.LAYOUT, folderConfig);
- assertNotNull(file);
- assertEquals("layout2.xml", file.getFile().getName());
- assertEquals("", file.getQualifiers());
-
- folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es-rUS"));
- file = mRepository.getMatchingFile("layout2", ResourceType.LAYOUT, folderConfig);
- assertNotNull(file);
- assertEquals("layout1.xml", file.getFile().getName());
- assertEquals("land", file.getQualifiers());
- }
-
- public void testUpdates() throws Exception {
- assertFalse(mRepository.hasResourcesOfType(ResourceType.ANIM));
- assertFalse(mRepository.hasResourcesOfType(ResourceType.MENU));
- assertFalse(mRepository.hasResourcesOfType(ResourceType.BOOL));
-
- assertTrue(mRepository.hasResourcesOfType(ResourceType.DRAWABLE));
- assertTrue(mRepository.hasResourceItem("@drawable/graphic"));
-
- // Delete the drawable graphic
- ResourceSet resourceSet = mResourceMerger.getDataSets().get(0);
-
- File drawableFolder = new File(mRes, FD_RES_DRAWABLE);
- File graphicFile = new File(drawableFolder, "graphic.9.png");
-
- resourceSet.updateWith(mRes, graphicFile, FileStatus.REMOVED, mLogger);
- mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
-
- assertFalse(mRepository.hasResourceItem("@drawable/graphic"));
- assertFalse(mRepository.hasResourcesOfType(ResourceType.DRAWABLE));
-
- // Delete one of the overridden layouts
- List<ResourceItem> itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "layout1");
- assertNotNull(itemList);
- assertTrue(itemList.size() > 1);
- assertTrue(mRepository.hasResourcesOfType(ResourceType.LAYOUT));
- assertTrue(mRepository.hasResourceItem("@layout/layout1"));
-
- File layoutFolder = new File(mRes, FD_RES_LAYOUT + "-land");
- File layoutFile = new File(layoutFolder, "layout1.xml");
-
- resourceSet.updateWith(mRes, layoutFile, FileStatus.REMOVED, mLogger);
- mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
-
- // We still have a layout1: only default now
- assertTrue(mRepository.hasResourceItem("@layout/layout1"));
- itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "layout1");
- assertNotNull(itemList);
- assertEquals(1, itemList.size());
-
- // change strings
- assertTrue(mRepository.hasResourceItem("@string/dummy"));
- assertFalse(mRepository.hasResourceItem("@string/myDummy"));
- itemList = mRepository.getResourceItem(ResourceType.STRING, "dummy");
- assertNotNull(itemList);
- assertNotNull(itemList.get(0));
- ResourceFile stringResFile = itemList.get(0).getSource();
- File stringFile = stringResFile.getFile();
- assertTrue(stringFile.exists());
- String strings = Files.toString(stringFile, Charsets.UTF_8);
- assertNotNull(strings);
- strings = strings.replace("name=\"dummy\"", "name=\"myDummy\"");
- Files.write(strings, stringFile, Charsets.UTF_8);
-
- resourceSet.updateWith(mRes, stringFile, FileStatus.CHANGED, mLogger);
- mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
-
- assertTrue(mRepository.hasResourceItem("@string/myDummy"));
- assertFalse(mRepository.hasResourceItem("@string/dummy"));
-
- // add files
- assertFalse(mRepository.hasResourceItem("@layout/layout5"));
- File layout = new File(mRes, FD_RES_LAYOUT);
- File newFile = new File(layout, "layout5.xml");
- boolean created = newFile.createNewFile();
- assertTrue(created);
- resourceSet.updateWith(mRes, newFile, FileStatus.NEW, mLogger);
- mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
- assertTrue(mRepository.hasResourceItem("@layout/layout5"));
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testXliff() throws Exception {
- ResourceRepository resources = TestResourceRepository.createRes2(false, new Object[]{
- "values/strings.xml", ""
- + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" >\n"
- + " <string name=\"share_with_application\">\n"
- + " Share your score of <xliff:g id=\"score\" example=\"1337\">%1$s</xliff:g>\n"
- + " with <xliff:g id=\"application_name\" example=\"Bluetooth\">%2$s</xliff:g>!\n"
- + " </string>\n"
- + " <string name=\"callDetailsDurationFormat\"><xliff:g id=\"minutes\" example=\"42\">%s</xliff:g> mins <xliff:g id=\"seconds\" example=\"28\">%s</xliff:g> secs</string>\n"
- + " <string name=\"description_call\">Call <xliff:g id=\"name\">%1$s</xliff:g></string>\n"
- + " <string name=\"other\"><xliff:g id=\"number_of_sessions\">%1$s</xliff:g> sessions removed from your schedule</string>\n"
- + " <!-- Format string used to add a suffix like \"KB\" or \"MB\" to a number\n"
- + " to display a size in kilobytes, megabytes, or other size units.\n"
- + " Some languages (like French) will want to add a space between\n"
- + " the placeholders. -->\n"
- + " <string name=\"fileSizeSuffix\"><xliff:g id=\"number\" example=\"123\">%1$s</xliff:g><xliff:g id=\"unit\" example=\"KB\">%2$s</xliff:g></string>"
- + "</resources>\n"
- });
- assertFalse(resources.isFramework());
- assertNotNull(resources);
-
- assertNotNull(resources);
- assertEquals("Share your score of (1337) with (Bluetooth)!",
- resources.getResourceItem(ResourceType.STRING, "share_with_application").get(0).getResourceValue(false).getValue());
- assertEquals("Call ${name}",
- resources.getResourceItem(ResourceType.STRING, "description_call").get(0).getResourceValue(false).getValue());
- assertEquals("(42) mins (28) secs",
- resources.getResourceItem(ResourceType.STRING, "callDetailsDurationFormat").get(0).getResourceValue(false).getValue());
- assertEquals("${number_of_sessions} sessions removed from your schedule",
- resources.getResourceItem(ResourceType.STRING, "other").get(0).getResourceValue(false).getValue());
- assertEquals("(123)(KB)",
- resources.getResourceItem(ResourceType.STRING, "fileSizeSuffix").get(0).getResourceValue(false).getValue());
-
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java
deleted file mode 100644
index f64e33c..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static java.io.File.separator;
-
-import com.android.testutils.TestUtils;
-
-import java.io.File;
-import java.io.IOException;
-
-public class ResourceSetTest extends BaseTestCase {
-
- public void testBaseResourceSetByCount() throws Exception {
- ResourceSet resourceSet = getBaseResourceSet(false /*normalize*/);
- assertEquals(29, resourceSet.size());
- }
-
- public void testBaseResourceSetByName() throws Exception {
- ResourceSet resourceSet = getBaseResourceSet(false /*normalize*/);
-
- verifyResourceExists(resourceSet,
- "drawable/icon",
- "drawable/patch",
- "raw/foo",
- "layout/main",
- "layout/layout_ref",
- "layout/alias_replaced_by_file",
- "layout/file_replaced_by_alias",
- "drawable/color_drawable",
- "drawable/drawable_ref",
- "color/color",
- "string/basic_string",
- "string/xliff_string",
- "string/styled_string",
- "style/style",
- "array/string_array",
- "attr/dimen_attr",
- "attr/string_attr",
- "attr/enum_attr",
- "attr/flag_attr",
- "attr/blah",
- "attr/blah2",
- "attr/flagAttr",
- "declare-styleable/declare_styleable",
- "dimen/dimen",
- "dimen-sw600dp/offset",
- "id/item_id",
- "integer/integer",
- "plurals/plurals"
- );
- }
-
- public void testBaseResourceSetWithNormalizationByName() throws Exception {
- ResourceSet resourceSet = getBaseResourceSet(true /*normalize*/);
-
- verifyResourceExists(resourceSet,
- "drawable/icon",
- "drawable/patch",
- "raw/foo",
- "layout/main",
- "layout/layout_ref",
- "layout/alias_replaced_by_file",
- "layout/file_replaced_by_alias",
- "drawable/color_drawable",
- "drawable/drawable_ref",
- "color/color",
- "string/basic_string",
- "string/xliff_string",
- "string/styled_string",
- "style/style",
- "array/string_array",
- "attr/dimen_attr",
- "attr/string_attr",
- "attr/enum_attr",
- "attr/flag_attr",
- "attr/blah",
- "attr/blah2",
- "attr/flagAttr",
- "declare-styleable/declare_styleable",
- "dimen/dimen",
- "dimen-sw600dp-v13/offset",
- "id/item_id",
- "integer/integer",
- "plurals/plurals"
- );
- }
-
- public void testDupResourceSet() throws Exception {
- File root = TestUtils.getRoot("resources", "dupSet");
-
- ResourceSet set = new ResourceSet("main");
- set.addSource(new File(root, "res1"));
- set.addSource(new File(root, "res2"));
- boolean gotException = false;
- RecordingLogger logger = new RecordingLogger();
- try {
- set.loadFromFiles(logger);
- } catch (DuplicateDataException e) {
- gotException = true;
-
-
-
-
- String message = e.getMessage();
- // Clean up paths etc for unit test
- int index = message.indexOf("dupSet");
- assertTrue(index != -1);
- String prefix = message.substring(0, index);
- message = message.replace(prefix, "<PREFIX>").replace('\\','/');
- assertEquals("<PREFIX>dupSet/res1/drawable/icon.png\t<PREFIX>dupSet/res2/drawable/icon.png: "
- + "Error: Duplicate resources", message);
- }
-
- checkLogger(logger);
- assertTrue(gotException);
- }
-
- public void testBrokenSet() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSet");
-
- ResourceSet set = new ResourceSet("main");
- set.addSource(root);
-
- boolean gotException = false;
- RecordingLogger logger = new RecordingLogger();
- try {
- set.loadFromFiles(logger);
- } catch (MergingException e) {
- gotException = true;
- assertEquals(new File(root, "values" + separator + "dimens.xml").getAbsolutePath() +
- ":1:1: Error: Content is not allowed in prolog.",
- e.getMessage());
- }
-
- assertTrue("ResourceSet processing should have failed, but didn't", gotException);
- assertFalse(logger.getErrorMsgs().isEmpty());
- }
-
- public void testBrokenSet2() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSet2");
-
- ResourceSet set = new ResourceSet("main");
- set.addSource(root);
-
- boolean gotException = false;
- RecordingLogger logger = new RecordingLogger();
- try {
- set.loadFromFiles(logger);
- } catch (MergingException e) {
- gotException = true;
- assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
- ": Error: Found item String/app_name more than one time",
- e.getMessage());
- }
-
- assertTrue("ResourceSet processing should have failed, but didn't", gotException);
- assertFalse(logger.getErrorMsgs().isEmpty());
- }
-
- public void testBrokenSet3() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSet3");
-
- ResourceSet set = new ResourceSet("main");
- set.addSource(root);
-
- boolean gotException = false;
- RecordingLogger logger = new RecordingLogger();
- try {
- set.loadFromFiles(logger);
- } catch (MergingException e) {
- gotException = true;
- assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
- ": Error: Found item Attr/d_common_attr more than one time",
- e.getMessage());
- }
-
- assertTrue("ResourceSet processing should have failed, but didn't", gotException);
- assertFalse(logger.getErrorMsgs().isEmpty());
- }
-
- public void testBrokenSet4() throws Exception {
- File root = TestUtils.getRoot("resources", "brokenSet4");
-
- ResourceSet set = new ResourceSet("main");
- set.addSource(root);
-
- boolean gotException = false;
- RecordingLogger logger = new RecordingLogger();
- try {
- set.loadFromFiles(logger);
- } catch (MergingException e) {
- gotException = true;
- assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
- ":7:6: Error: The element type \"declare-styleable\" "
- + "must be terminated by the matching end-tag \"</declare-styleable>\".",
- e.getMessage());
- }
-
- assertTrue("ResourceSet processing should have failed, but didn't", gotException);
- assertFalse(logger.getErrorMsgs().isEmpty());
- }
-
- static ResourceSet getBaseResourceSet(boolean normalize) throws MergingException, IOException {
- File root = TestUtils.getRoot("resources", "baseSet");
-
- ResourceSet resourceSet = new ResourceSet("main");
- resourceSet.setNormalizeResources(normalize);
- resourceSet.addSource(root);
- RecordingLogger logger = new RecordingLogger();
- resourceSet.loadFromFiles(logger);
-
- checkLogger(logger);
-
- return resourceSet;
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceNameValidatorTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceNameValidatorTest.java
deleted file mode 100644
index c1a7868..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceNameValidatorTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import com.android.resources.ResourceType;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-
- at RunWith(Parameterized.class)
-public class ValueResourceNameValidatorTest {
-
- public static File DUMMY_FILE = new File("DUMMY_FILE");
-
- @Parameterized.Parameters(name="name=\"{0}\", resourceType={1}, file={2} gives error {3}")
- public static Collection<Object[]> expected() {
- return Arrays.asList(new Object[][] {
- //{ resourceName, resourceType, sourceFile, expectedException }
- { "foo.png", ResourceType.DRAWABLE, DUMMY_FILE, null},
- { "foo.xml", ResourceType.DRAWABLE, DUMMY_FILE, null},
- { "foo.9.xml", ResourceType.DRAWABLE, DUMMY_FILE, null},
- { "foo", ResourceType.DRAWABLE, DUMMY_FILE, null},
- { "foo.png", ResourceType.DRAWABLE, DUMMY_FILE, null},
- { "foo.txt", ResourceType.RAW, DUMMY_FILE, null},
- { "foo.txt", ResourceType.DRAWABLE, DUMMY_FILE, null},
- { "foo.other.png", ResourceType.DRAWABLE, DUMMY_FILE, null},
- { "android:q", ResourceType.STRING, DUMMY_FILE, "':' is not a valid resource name character"},
- { "android:q", ResourceType.STRING, null, "':' is not a valid resource name character"},
- { "android:q", ResourceType.ATTR, DUMMY_FILE, null},
- { "foo.s_3", ResourceType.STRING, null, null},
- { "FOO$", ResourceType.STRING, null, null},
- { "1st", ResourceType.STRING, null, "The resource name must start with a letter"},
- { "Foo#", ResourceType.STRING, null, "'#' is not a valid resource name character"},
- { "void", ResourceType.STRING, null, "void is not a valid resource name (reserved Java keyword)"},
- });
- }
-
- @Parameterized.Parameter
- public String mResourceName;
-
- @Parameterized.Parameter(value=1)
- public ResourceType mResourceType;
-
- @Parameterized.Parameter(value=2)
- public File mSourceFile;
-
- @Parameterized.Parameter(value=3)
- public String mExpectedErrorMessage;
-
-
- @Test
- public void validate() {
- String errorMessage = null;
- try {
- ValueResourceNameValidator.validate(mResourceName, mResourceType, mSourceFile);
- } catch (MergingException e) {
- errorMessage = e.getMessage();
- }
- FileResourceNameValidatorTest.assertErrorMessageCorrect(
- mExpectedErrorMessage, errorMessage, mSourceFile);
- }
-
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java b/base/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java
deleted file mode 100644
index afb5bde..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.res2;
-
-import static com.android.ide.common.res2.ValueXmlHelper.escapeResourceString;
-import static com.android.ide.common.res2.ValueXmlHelper.isEscaped;
-import static com.android.ide.common.res2.ValueXmlHelper.unescapeResourceString;
-
-import junit.framework.TestCase;
-
-public class ValueXmlHelperTest extends TestCase {
-
- public void testEscapeStringShouldEscapeXmlSpecialCharacters() throws Exception {
- assertEquals("<", escapeResourceString("<"));
- assertEquals("&", escapeResourceString("&"));
- assertEquals("<", escapeResourceString("<", true));
- assertEquals("&", escapeResourceString("&", true));
- assertEquals("<", escapeResourceString("<", false));
- assertEquals("&", escapeResourceString("&", false));
- }
-
- public void testEscapeStringShouldEscapeQuotes() throws Exception {
- assertEquals("\\'", escapeResourceString("'"));
- assertEquals("\\\"", escapeResourceString("\""));
- assertEquals("\" ' \"", escapeResourceString(" ' "));
- }
-
- public void testEscapeStringShouldPreserveWhitespace() throws Exception {
- assertEquals("\"at end \"", escapeResourceString("at end "));
- assertEquals("\" at begin\"", escapeResourceString(" at begin"));
- }
-
- public void testEscapeStringShouldEscapeAtSignAndQuestionMarkOnlyAtBeginning()
- throws Exception {
- assertEquals("\\@text", escapeResourceString("@text"));
- assertEquals("a at text", escapeResourceString("a at text"));
- assertEquals("\\?text", escapeResourceString("?text"));
- assertEquals("a?text", escapeResourceString("a?text"));
- assertEquals("\" ?text\"", escapeResourceString(" ?text"));
- }
-
- public void testEscapeStringShouldEscapeJavaEscapeSequences() throws Exception {
- assertEquals("\\n", escapeResourceString("\n"));
- assertEquals("\\t", escapeResourceString("\t"));
- assertEquals("\\\\", escapeResourceString("\\"));
- }
-
- public void testTrim() throws Exception {
- assertEquals("", unescapeResourceString("", false, true));
- assertEquals("", unescapeResourceString(" \n ", false, true));
- assertEquals("test", unescapeResourceString(" test ", false, true));
- assertEquals(" test ", unescapeResourceString("\" test \"", false, true));
- assertEquals("test", unescapeResourceString("\n\t test \t\n ", false, true));
-
- assertEquals("test\n", unescapeResourceString(" test\\n ", false, true));
- assertEquals(" test\n ", unescapeResourceString("\" test\\n \"", false, true));
- assertEquals("te\\st", unescapeResourceString("\n\t te\\\\st \t\n ", false, true));
- assertEquals("te\\st", unescapeResourceString(" te\\\\st ", false, true));
- assertEquals("test", unescapeResourceString("\"\"\"test\"\" ", false, true));
- assertEquals("\"test\"", unescapeResourceString("\"\"\\\"test\\\"\" ", false, true));
- assertEquals("test ", unescapeResourceString("test\\ ", false, true));
- assertEquals("\\\\\\", unescapeResourceString("\\\\\\\\\\\\ ", false, true));
- assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\\\ ", false, true));
- }
-
- public void testNoTrim() throws Exception {
- assertEquals("", unescapeResourceString("", false, false));
- assertEquals(" \n ", unescapeResourceString(" \n ", false, false));
- assertEquals(" test ", unescapeResourceString(" test ", false, false));
- assertEquals("\" test \"", unescapeResourceString("\" test \"", false, false));
- assertEquals("\n\t test \t\n ", unescapeResourceString("\n\t test \t\n ", false, false));
-
- assertEquals(" test\n ", unescapeResourceString(" test\\n ", false, false));
- assertEquals("\" test\n \"", unescapeResourceString("\" test\\n \"", false, false));
- assertEquals("\n\t te\\st \t\n ", unescapeResourceString("\n\t te\\\\st \t\n ", false, false));
- assertEquals(" te\\st ", unescapeResourceString(" te\\\\st ", false, false));
- assertEquals("\"\"\"test\"\" ", unescapeResourceString("\"\"\"test\"\" ", false, false));
- assertEquals("\"\"\"test\"\" ", unescapeResourceString("\"\"\\\"test\\\"\" ", false, false));
- assertEquals("test ", unescapeResourceString("test\\ ", false, false));
- assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\ ", false, false));
- assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\\\ ", false, false));
- }
-
- public void testUnescapeStringShouldUnescapeXmlSpecialCharacters() throws Exception {
- assertEquals("<", unescapeResourceString("<", false, true));
- assertEquals(">", unescapeResourceString(">", false, true));
- assertEquals("<", unescapeResourceString("<", true, true));
- assertEquals("<", unescapeResourceString(" < ", true, true));
- assertEquals("\"", unescapeResourceString(" " ", true, true));
- assertEquals("'", unescapeResourceString(" ' ", true, true));
- assertEquals(">", unescapeResourceString(" > ", true, true));
- assertEquals("&", unescapeResourceString("&", false, true));
- assertEquals("&", unescapeResourceString("&", true, true));
- assertEquals("&", unescapeResourceString(" & ", true, true));
- assertEquals("!<", unescapeResourceString("!<", true, true));
- }
-
- public void testUnescapeStringShouldUnescapeQuotes() throws Exception {
- assertEquals("'", unescapeResourceString("\\'", false, true));
- assertEquals("\"", unescapeResourceString("\\\"", false, true));
- assertEquals(" ' ", unescapeResourceString("\" ' \"", false, true));
- }
-
- public void testUnescapeStringShouldPreserveWhitespace() throws Exception {
- assertEquals("at end ", unescapeResourceString("\"at end \"", false, true));
- assertEquals(" at begin", unescapeResourceString("\" at begin\"", false, true));
- }
-
- public void testUnescapeStringShouldUnescapeAtSignAndQuestionMarkOnlyAtBeginning()
- throws Exception {
- assertEquals("@text", unescapeResourceString("\\@text", false, true));
- assertEquals("a at text", unescapeResourceString("a at text", false, true));
- assertEquals("?text", unescapeResourceString("\\?text", false, true));
- assertEquals("a?text", unescapeResourceString("a?text", false, true));
- assertEquals(" ?text", unescapeResourceString("\" ?text\"", false, true));
- }
-
- public void testUnescapeStringShouldUnescapeJavaUnescapeSequences() throws Exception {
- assertEquals("\n", unescapeResourceString("\\n", false, true));
- assertEquals("\t", unescapeResourceString("\\t", false, true));
- assertEquals("\\", unescapeResourceString("\\\\", false, true));
- }
-
- public void testIsEscaped() throws Exception {
- assertFalse(isEscaped("", 0));
- assertFalse(isEscaped(" ", 0));
- assertFalse(isEscaped(" ", 1));
- assertFalse(isEscaped("x\\y ", 0));
- assertFalse(isEscaped("x\\y ", 1));
- assertTrue(isEscaped("x\\y ", 2));
- assertFalse(isEscaped("x\\y ", 3));
- assertFalse(isEscaped("x\\\\y ", 0));
- assertFalse(isEscaped("x\\\\y ", 1));
- assertTrue(isEscaped("x\\\\y ", 2));
- assertFalse(isEscaped("x\\\\y ", 3));
- assertFalse(isEscaped("\\\\\\\\y ", 0));
- assertTrue(isEscaped( "\\\\\\\\y ", 1));
- assertFalse(isEscaped("\\\\\\\\y ", 2));
- assertTrue(isEscaped( "\\\\\\\\y ", 3));
- assertFalse(isEscaped("\\\\\\\\y ", 4));
- }
-
- public void testRewriteSpaces() throws Exception {
- // Ensure that \n's in the input are rewritten as spaces, and multiple spaces
- // collapsed into a single one
- assertEquals("This is a test",
- unescapeResourceString("This is\na test", true, true));
- assertEquals("This is a test",
- unescapeResourceString("This is\n a test\n ", true, true));
- assertEquals("This is\na test",
- unescapeResourceString("\"This is\na test\"", true, true));
- assertEquals("Multiple words",
- unescapeResourceString("Multiple words", true, true));
- assertEquals("Multiple words",
- unescapeResourceString("\"Multiple words\"", true, true));
- assertEquals("This is a\n test",
- unescapeResourceString("This is a\\n test", true, true));
- assertEquals("This is a\n test",
- unescapeResourceString("This is\n a\\n test", true, true));
- }
-
- public void testHtmlEntities() throws Exception {
- assertEquals("Entity \u00a9 \u00a9 Copyright",
- unescapeResourceString("Entity © © Copyright", true, true));
- }
-
- public void testMarkupConcatenation() throws Exception {
- assertEquals("<b>Sign in</b> or register",
- unescapeResourceString("\n <b>Sign in</b>\n or register\n", true, true));
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java b/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java
deleted file mode 100644
index 6bb4a49..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.resources;
-
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.ArrayResourceValue;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.res2.ResourceRepository;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.resources.ResourceType;
-import com.google.common.collect.Lists;
-
-import junit.framework.TestCase;
-
-import java.util.List;
-import java.util.Map;
-
-public class ResourceItemResolverTest extends TestCase {
- @SuppressWarnings("ConstantConditions")
- public void test() throws Exception {
- final TestResourceRepository frameworkResources = TestResourceRepository.create(true,
- new Object[]{
- "values/strings.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"ok\">Ok</string>\n"
- + " <array name=\"my_fw_array\">\"\n"
- + " <item> fw_value1</item>\n" // also test trimming.
- + " <item>fw_value2\n</item>\n"
- + " <item>fw_value3</item>\n"
- + " </array>\n"
- + "</resources>\n",
-
- "values/themes.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"Theme\">\n"
- + " <item name=\"colorForeground\">@android:color/bright_foreground_dark</item>\n"
- + " <item name=\"colorBackground\">@android:color/background_dark</item>\n"
- + " </style>\n"
- + " <style name=\"Theme.Light\">\n"
- + " <item name=\"colorBackground\">@android:color/background_light</item>\n"
- + " <item name=\"colorForeground\">@color/bright_foreground_light</item>\n"
- + " </style>\n"
- + "</resources>\n",
-
- "values/colors.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <color name=\"background_dark\">#ff000000</color>\n"
- + " <color name=\"background_light\">#ffffffff</color>\n"
- + " <color name=\"bright_foreground_dark\">@android:color/background_light</color>\n"
- + " <color name=\"bright_foreground_light\">@android:color/background_dark</color>\n"
- + "</resources>\n",
- });
-
- final ResourceRepository appResources = TestResourceRepository.createRes2(false,
- new Object[]{
- "layout/layout1.xml", "<!--contents doesn't matter-->",
-
- "layout/layout2.xml", "<!--contents doesn't matter-->",
-
- "layout-land/layout1.xml", "<!--contents doesn't matter-->",
-
- "layout-land/only_land.xml", "<!--contents doesn't matter-->",
-
- "drawable/graphic.9.png", new byte[0],
-
- "values/styles.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n"
- + " <item name=\"android:textColor\">#999999</item>\n"
- + " <item name=\"foo\">?android:colorForeground</item>\n"
- + " </style>\n"
- + " <style name=\"MyTheme.Dotted1\" parent=\"\">\n"
- + " </style>"
- + " <style name=\"MyTheme.Dotted2\">\n"
- + " </style>"
- + " <style name=\"RandomStyle\">\n"
- + " </style>"
- + " <style name=\"RandomStyle2\" parent=\"RandomStyle\">\n"
- + " </style>"
- + "</resources>\n",
-
- "values/strings.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
- + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
- + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
- + " <string name=\"home_title\">Home Sample</string>\n"
- + " <string name=\"show_all_apps\">All</string>\n"
- + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
- + " <string name=\"menu_search\">Search</string>\n"
- + " <string name=\"menu_settings\">Settings</string>\n"
- + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
- + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
- + " <string name=\"xliff_string\">First: <xliff:g id=\"firstName\">%1$s</xliff:g> Last: <xliff:g id=\"lastName\">%2$s</xliff:g></string>\n"
- + " <array name=\"my_array\">\"\n"
- + " <item> value1</item>\n" // also test trimming.
- + " <item>value2\n</item>\n"
- + " <item>value3</item>\n"
- + " </array>\n"
- + "</resources>\n",
-
- "values-es/strings.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"show_all_apps\">Todo</string>\n"
- + "</resources>\n",
- });
-
- final FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
- assertFalse(appResources.isFramework());
- assertNotNull(config);
-
- final LayoutLog logger = new LayoutLog() {
- @Override
- public void warning(String tag, String message, Object data) {
- fail(message);
- }
-
- @Override
- public void fidelityWarning(String tag, String message, Throwable throwable,
- Object data) {
- fail(message);
- }
-
- @Override
- public void error(String tag, String message, Object data) {
- fail(message);
- }
-
- @Override
- public void error(String tag, String message, Throwable throwable, Object data) {
- fail(message);
- }
- };
-
- ResourceItemResolver.ResourceProvider provider = new ResourceItemResolver.ResourceProvider() {
- private ResourceResolver mResolver;
-
- @Nullable
- @Override
- public ResourceResolver getResolver(boolean createIfNecessary) {
- if (mResolver == null && createIfNecessary) {
- Map<ResourceType, Map<String, ResourceValue>> appResourceMap;
- appResourceMap = appResources.getConfiguredResources(config);
- Map<ResourceType, Map<String, ResourceValue>> frameworkResourcesMap =
- frameworkResources.getConfiguredResources(config);
- assertNotNull(appResourceMap);
- mResolver = ResourceResolver.create(appResourceMap, frameworkResourcesMap,
- "MyTheme", true);
- assertNotNull(mResolver);
- mResolver.setLogger(logger);
- }
-
- return mResolver;
- }
-
- @Nullable
- @Override
- public com.android.ide.common.resources.ResourceRepository getFrameworkResources() {
- return frameworkResources;
- }
-
- @Nullable
- @Override
- public ResourceRepository getAppResources() {
- return appResources;
- }
- };
-
- ResourceItemResolver resolver = new ResourceItemResolver(config, provider, logger);
-
- // findResValue
- assertNotNull(resolver.findResValue("@string/show_all_apps", false));
- assertNotNull(resolver.findResValue("@android:string/ok", false));
- assertNotNull(resolver.findResValue("@android:string/ok", true));
- assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
- assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
- assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
- false).getValue());
- assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
- assertEquals("@android:color/background_light",
- resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
- assertEquals("#ffffffff",
- resolver.findResValue("@android:color/background_light", true).getValue());
-
- // resolveResValue
- // android:color/bright_foreground_dark => @android:color/background_light => white
- assertEquals("Todo", resolver.resolveResValue(
- resolver.findResValue("@string/show_all_apps", false)).getValue());
- assertEquals("#ffffffff", resolver.resolveResValue(
- resolver.findResValue("@android:color/bright_foreground_dark", false)).getValue());
-
- // Test array values.
- ResourceValue resValue = resolver.findResValue("@array/my_array", false);
- assertTrue(resValue instanceof ArrayResourceValue);
- assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
- assertEquals("value1", ((ArrayResourceValue) resValue).getElement(0));
- assertEquals("value2", ((ArrayResourceValue) resValue).getElement(1));
- assertEquals("value3", ((ArrayResourceValue) resValue).getElement(2));
- resValue = resolver.findResValue("@android:array/my_fw_array", false);
- assertTrue(resValue instanceof ArrayResourceValue);
- assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
- assertEquals("fw_value1", ((ArrayResourceValue) resValue).getElement(0));
- assertEquals("fw_value2", ((ArrayResourceValue) resValue).getElement(1));
- assertEquals("fw_value3", ((ArrayResourceValue) resValue).getElement(2));
-
-
- // Now do everything over again, but this time without a resource resolver.
- // Also set a lookup chain.
- resolver = new ResourceItemResolver(config, frameworkResources, appResources,
- logger);
- List<ResourceValue> chain = Lists.newArrayList();
- resolver.setLookupChainList(chain);
-
- // findResValue
- assertNotNull(resolver.findResValue("@string/show_all_apps", false));
- assertNotNull(resolver.findResValue("@android:string/ok", false));
- assertNotNull(resolver.findResValue("@android:string/ok", true));
- assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
- assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
- assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
- false).getValue());
- assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
- assertEquals("@android:color/background_light",
- resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
- assertEquals("#ffffffff",
- resolver.findResValue("@android:color/background_light", true).getValue());
- assertEquals("Todo", resolver.resolveResValue(
- resolver.findResValue("@string/show_all_apps", false)).getValue());
-
- chain.clear();
- ResourceValue v = resolver.findResValue("@android:color/bright_foreground_dark", false);
- assertEquals("@android:color/bright_foreground_dark => @android:color/background_light",
- ResourceItemResolver.getDisplayString(ResourceType.COLOR, "bright_foreground_dark",
- true, chain));
- assertEquals("First: ${firstName} Last: ${lastName}",
- resolver.findResValue("@string/xliff_string", false).getValue());
- assertEquals("First: <xliff:g id=\"firstName\">%1$s</xliff:g> Last: <xliff:g id=\"lastName\">%2$s</xliff:g>",
- resolver.findResValue("@string/xliff_string", false).getRawXmlValue());
-
- chain.clear();
- assertEquals("#ffffffff", resolver.resolveResValue(v).getValue());
- assertEquals("@android:color/bright_foreground_dark => @android:color/background_light "
- + "=> #ffffffff",
- ResourceItemResolver.getDisplayString("@android:color/bright_foreground_dark",
- chain));
-
- // Try to resolve style attributes
- resolver = new ResourceItemResolver(config, provider, logger);
- resolver.setLookupChainList(chain);
- chain.clear();
- ResourceValue target = new ResourceValue(ResourceType.STRING, "dummy", false);
- target.setValue("?foo");
- assertEquals("#ff000000", resolver.resolveResValue(target).getValue());
- assertEquals("?foo => ?android:colorForeground => @color/bright_foreground_light => "
- + "@android:color/background_dark => #ff000000",
- ResourceItemResolver.getDisplayString("?foo", chain));
-
- // Test array values.
- resValue = resolver.findResValue("@array/my_array", false);
- assertTrue(resValue instanceof ArrayResourceValue);
- assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
- assertEquals("value1", ((ArrayResourceValue) resValue).getElement(0));
- assertEquals("value2", ((ArrayResourceValue) resValue).getElement(1));
- assertEquals("value3", ((ArrayResourceValue) resValue).getElement(2));
- resValue = resolver.findResValue("@android:array/my_fw_array", false);
- assertTrue(resValue instanceof ArrayResourceValue);
- assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
- assertEquals("fw_value1", ((ArrayResourceValue) resValue).getElement(0));
- assertEquals("fw_value2", ((ArrayResourceValue) resValue).getElement(1));
- assertEquals("fw_value3", ((ArrayResourceValue) resValue).getElement(2));
-
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java b/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java
deleted file mode 100644
index 78beca2..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java
+++ /dev/null
@@ -1,588 +0,0 @@
-package com.android.ide.common.resources;
-
-import com.android.ide.common.rendering.api.DensityBasedResourceValue;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.resources.Density;
-import com.android.resources.ResourceType;
-import com.google.common.collect.Lists;
-import junit.framework.TestCase;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class ResourceResolverTest extends TestCase {
- public void test() throws Exception {
- TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
- new Object[]{
- "values/strings.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"ok\">Ok</string>\n"
- + "</resources>\n",
-
- "values/themes.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"Theme\">\n"
- + " <item name=\"colorForeground\">@android:color/bright_foreground_dark</item>\n"
- + " <item name=\"colorBackground\">@android:color/background_dark</item>\n"
- + " </style>\n"
- + " <style name=\"Theme.Light\">\n"
- + " <item name=\"colorBackground\">@android:color/background_light</item>\n"
- + " <item name=\"colorForeground\">@color/bright_foreground_light</item>\n"
- + " </style>\n"
- + "</resources>\n",
-
- "values/colors.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <color name=\"background_dark\">#ff000000</color>\n"
- + " <color name=\"background_light\">#ffffffff</color>\n"
- + " <color name=\"bright_foreground_dark\">@android:color/background_light</color>\n"
- + " <color name=\"bright_foreground_light\">@android:color/background_dark</color>\n"
- + "</resources>\n",
-
- "values/ids.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <item name=\"some_framework_id\" type=\"id\" />\n"
- + "</resources>\n",
- });
-
- TestResourceRepository projectRepository = TestResourceRepository.create(false,
- new Object[]{
- "layout/layout1.xml", "<!--contents doesn't matter-->",
-
- "layout/layout2.xml", "<!--contents doesn't matter-->",
-
- "layout-land/layout1.xml", "<!--contents doesn't matter-->",
-
- "layout-land/onlyLand.xml", "<!--contents doesn't matter-->",
-
- "drawable/graphic.9.png", new byte[0],
-
- "mipmap-xhdpi/ic_launcher.png", new byte[0],
-
- "values/styles.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n"
- + " <item name=\"android:textColor\">#999999</item>\n"
- + " <item name=\"foo\">?android:colorForeground</item>\n"
- + " </style>\n"
- + " <style name=\"MyTheme.Dotted1\" parent=\"\">\n"
- + " </style>"
- + " <style name=\"MyTheme.Dotted2\">\n"
- + " </style>"
- + " <style name=\"RandomStyle\">\n"
- + " <item name=\"android:text\">© Copyright</item>\n"
- + " </style>"
- + " <style name=\"RandomStyle2\" parent=\"RandomStyle\">\n"
- + " </style>"
- + " <style name=\"Theme.FakeTheme\" parent=\"\">\n"
- + " </style>"
- + " <style name=\"Theme\" parent=\"RandomStyle\">\n"
- + " </style>"
- + "</resources>\n",
-
- "values/strings.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
- + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
- + " <string name=\"home_title\">Home Sample</string>\n"
- + " <string name=\"show_all_apps\">All</string>\n"
- + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
- + " <string name=\"menu_search\">Search</string>\n"
- + " <string name=\"menu_settings\">Settings</string>\n"
- + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
- + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
- + "</resources>\n",
-
- "values-es/strings.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"show_all_apps\">Todo</string>\n"
- + "</resources>\n",
- });
-
- assertFalse(projectRepository.isFrameworkRepository());
- FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
- assertNotNull(config);
- Map<ResourceType, Map<String, ResourceValue>> projectResources =
- projectRepository.getConfiguredResources(config);
- Map<ResourceType, Map<String, ResourceValue>> frameworkResources =
- frameworkRepository.getConfiguredResources(config);
- assertNotNull(projectResources);
- ResourceResolver resolver = ResourceResolver.create(projectResources, frameworkResources,
- "MyTheme", true);
- assertNotNull(resolver);
-
- LayoutLog logger = new LayoutLog() {
- @Override
- public void warning(String tag, String message, Object data) {
- fail(message);
- }
-
- @Override
- public void fidelityWarning(String tag, String message, Throwable throwable,
- Object data) {
- fail(message);
- }
-
- @Override
- public void error(String tag, String message, Object data) {
- fail(message);
- }
-
- @Override
- public void error(String tag, String message, Throwable throwable, Object data) {
- fail(message);
- }
- };
- resolver.setLogger(logger);
-
- assertEquals("MyTheme", resolver.getThemeName());
- assertTrue(resolver.isProjectTheme());
-
- // findResValue
- assertNotNull(resolver.findResValue("@string/show_all_apps", false));
- assertNotNull(resolver.findResValue("@android:string/ok", false));
- assertNotNull(resolver.findResValue("@android:string/ok", true));
- assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
- assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
- assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
- false).getValue());
- assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
- assertEquals("@android:color/background_light",
- resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
- assertEquals("#ffffffff",
- resolver.findResValue("@android:color/background_light", true).getValue());
- assertNull(resolver.findResValue("?attr/non_existent_style", false)); // shouldn't log an error.
- assertEquals(Density.XHIGH,
- ((DensityBasedResourceValue) resolver.findResValue("@mipmap/ic_launcher", false))
- .getResourceDensity()); // also ensures that returned value is instance of DensityBasedResourceValue
-
- // getTheme
- StyleResourceValue myTheme = resolver.getTheme("MyTheme", false);
- assertNotNull(myTheme);
- assertSame(resolver.findResValue("@style/MyTheme", false), myTheme);
- assertNull(resolver.getTheme("MyTheme", true));
- assertNull(resolver.getTheme("MyNonexistentTheme", true));
- StyleResourceValue themeLight = resolver.getTheme("Theme.Light", true);
- assertNotNull(themeLight);
- StyleResourceValue theme = resolver.getTheme("Theme", true);
- assertNotNull(theme);
-
- // getParent
- StyleResourceValue parent = resolver.getParent(myTheme);
- assertNotNull(parent);
- assertEquals("Theme.Light", parent.getName());
-
- // themeIsParentOf
- assertTrue(resolver.themeIsParentOf(themeLight, myTheme));
- assertFalse(resolver.themeIsParentOf(myTheme, themeLight));
- assertTrue(resolver.themeIsParentOf(theme, themeLight));
- assertFalse(resolver.themeIsParentOf(themeLight, theme));
- assertTrue(resolver.themeIsParentOf(theme, myTheme));
- assertFalse(resolver.themeIsParentOf(myTheme, theme));
- StyleResourceValue dotted1 = resolver.getTheme("MyTheme.Dotted1", false);
- assertNotNull(dotted1);
- StyleResourceValue dotted2 = resolver.getTheme("MyTheme.Dotted2", false);
- assertNotNull(dotted2);
- assertTrue(resolver.themeIsParentOf(myTheme, dotted2));
- assertFalse(resolver.themeIsParentOf(myTheme, dotted1)); // because parent=""
-
- // isTheme
- assertFalse(resolver.isTheme(resolver.findResValue("@style/RandomStyle", false), null));
- assertFalse(resolver.isTheme(resolver.findResValue("@style/RandomStyle2", false), null));
- assertFalse(resolver.isTheme(resolver.findResValue("@style/Theme.FakeTheme", false), null));
- assertFalse(resolver.isTheme(resolver.findResValue("@style/Theme", false), null));
- // check XML escaping in value resources
- StyleResourceValue randomStyle = (StyleResourceValue) resolver.findResValue(
- "@style/RandomStyle", false);
- assertEquals("\u00a9 Copyright", randomStyle.getItem("text", true).getValue());
- assertTrue(resolver.isTheme(resolver.findResValue("@style/MyTheme.Dotted2", false), null));
- assertFalse(resolver.isTheme(resolver.findResValue("@style/MyTheme.Dotted1", false),
- null));
- assertTrue(resolver.isTheme(resolver.findResValue("@style/MyTheme", false), null));
- assertTrue(resolver.isTheme(resolver.findResValue("@android:style/Theme.Light", false),
- null));
- assertTrue(resolver.isTheme(resolver.findResValue("@android:style/Theme", false), null));
-
- // findItemInStyle
- assertNotNull(resolver.findItemInStyle(myTheme, "colorForeground", true));
- assertEquals("@color/bright_foreground_light",
- resolver.findItemInStyle(myTheme, "colorForeground", true).getValue());
- assertNotNull(resolver.findItemInStyle(dotted2, "colorForeground", true));
- assertNull(resolver.findItemInStyle(dotted1, "colorForeground", true));
-
- // findItemInTheme
- assertNotNull(resolver.findItemInTheme("colorForeground", true));
- assertEquals("@color/bright_foreground_light",
- resolver.findItemInTheme("colorForeground", true).getValue());
- assertEquals("@color/bright_foreground_light",
- resolver.findResValue("?colorForeground", true).getValue());
- ResourceValue target = new ResourceValue(ResourceType.STRING, "dummy", false);
- target.setValue("?foo");
- assertEquals("#ff000000", resolver.resolveResValue(target).getValue());
-
- // getFrameworkResource
- assertNull(resolver.getFrameworkResource(ResourceType.STRING, "show_all_apps"));
- assertNotNull(resolver.getFrameworkResource(ResourceType.STRING, "ok"));
- assertEquals("Ok", resolver.getFrameworkResource(ResourceType.STRING, "ok").getValue());
-
- // getProjectResource
- assertNull(resolver.getProjectResource(ResourceType.STRING, "ok"));
- assertNotNull(resolver.getProjectResource(ResourceType.STRING, "show_all_apps"));
- assertEquals("Todo", resolver.getProjectResource(ResourceType.STRING,
- "show_all_apps").getValue());
-
-
- // resolveResValue
- // android:color/bright_foreground_dark => @android:color/background_light => white
- assertEquals("Todo", resolver.resolveResValue(
- resolver.findResValue("@string/show_all_apps", false)).getValue());
- assertEquals("#ffffffff", resolver.resolveResValue(
- resolver.findResValue("@android:color/bright_foreground_dark", false)).getValue());
-
- // resolveValue
- assertEquals("#ffffffff",
- resolver.resolveValue(ResourceType.STRING, "bright_foreground_dark",
- "@android:color/background_light", true).getValue());
- assertFalse(resolver.resolveValue(null, "id", "@+id/some_framework_id", false)
- .isFramework());
- // error expected.
- boolean failed = false;
- ResourceValue val = null;
- try {
- val = resolver.resolveValue(ResourceType.STRING, "bright_foreground_dark",
- "@color/background_light", false);
- } catch (AssertionError expected) {
- failed = true;
- }
- assertTrue("incorrect resource returned: " + val, failed);
-
- // themeExtends
- assertTrue(resolver.themeExtends("@android:style/Theme", "@android:style/Theme"));
- assertTrue(resolver.themeExtends("@android:style/Theme", "@android:style/Theme.Light"));
- assertFalse(resolver.themeExtends("@android:style/Theme.Light", "@android:style/Theme"));
- assertTrue(resolver.themeExtends("@style/MyTheme.Dotted2", "@style/MyTheme.Dotted2"));
- assertTrue(resolver.themeExtends("@style/MyTheme", "@style/MyTheme.Dotted2"));
- assertTrue(resolver.themeExtends("@android:style/Theme.Light", "@style/MyTheme.Dotted2"));
- assertTrue(resolver.themeExtends("@android:style/Theme", "@style/MyTheme.Dotted2"));
- assertFalse(resolver.themeExtends("@style/MyTheme.Dotted1", "@style/MyTheme.Dotted2"));
-
- // Switch to MyTheme.Dotted1 (to make sure the parent="" inheritance works properly.)
- // To do that we need to create a new resource resolver.
- resolver = ResourceResolver.create(projectResources, frameworkResources,
- "MyTheme.Dotted1", true);
- resolver.setLogger(logger);
- assertNotNull(resolver);
- assertEquals("MyTheme.Dotted1", resolver.getThemeName());
- assertTrue(resolver.isProjectTheme());
- assertNull(resolver.findItemInTheme("colorForeground", true));
-
- resolver = ResourceResolver.create(projectResources, frameworkResources,
- "MyTheme.Dotted2", true);
- resolver.setLogger(logger);
- assertNotNull(resolver);
- assertEquals("MyTheme.Dotted2", resolver.getThemeName());
- assertTrue(resolver.isProjectTheme());
- assertNotNull(resolver.findItemInTheme("colorForeground", true));
-
- // Test recording resolver
- List<ResourceValue> chain = Lists.newArrayList();
- resolver = ResourceResolver.create(projectResources, frameworkResources, "MyTheme", true);
- resolver = resolver.createRecorder(chain);
- assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
- ResourceValue v = resolver.findResValue("@android:color/bright_foreground_dark", false);
- chain.clear();
- assertEquals("#ffffffff", resolver.resolveResValue(v).getValue());
- assertEquals("@android:color/bright_foreground_dark => "
- + "@android:color/background_light => #ffffffff",
- ResourceItemResolver.getDisplayString("@android:color/bright_foreground_dark",
- chain));
-
- frameworkRepository.dispose();
- projectRepository.dispose();
- }
-
- public void testMissingMessage() throws Exception {
- TestResourceRepository projectRepository = TestResourceRepository.create(false,
- new Object[]{
- "values/colors.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <color name=\"loop1\">@color/loop1</color>\n"
- + " <color name=\"loop2a\">@color/loop2b</color>\n"
- + " <color name=\"loop2b\">@color/loop2a</color>\n"
- + "</resources>\n",
-
- });
-
- assertFalse(projectRepository.isFrameworkRepository());
- FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
- assertNotNull(config);
- Map<ResourceType, Map<String, ResourceValue>> projectResources =
- projectRepository.getConfiguredResources(config);
- assertNotNull(projectResources);
- ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
- "MyTheme", true);
- final AtomicBoolean wasWarned = new AtomicBoolean(false);
- LayoutLog logger = new LayoutLog() {
- @Override
- public void warning(String tag, String message, Object data) {
- if ("Couldn't resolve resource @android:string/show_all_apps".equals(message)) {
- wasWarned.set(true);
- } else {
- fail(message);
- }
- }
- };
- resolver.setLogger(logger);
- assertNull(resolver.findResValue("@string/show_all_apps", true));
- assertTrue(wasWarned.get());
- projectRepository.dispose();
- }
-
- public void testLoop() throws Exception {
- TestResourceRepository projectRepository = TestResourceRepository.create(false,
- new Object[]{
- "values/colors.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <color name=\"loop1\">@color/loop1</color>\n"
- + " <color name=\"loop2a\">@color/loop2b</color>\n"
- + " <color name=\"loop2b\">@color/loop2a</color>\n"
- + "</resources>\n",
-
- });
-
- assertFalse(projectRepository.isFrameworkRepository());
- FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
- assertNotNull(config);
- Map<ResourceType, Map<String, ResourceValue>> projectResources =
- projectRepository.getConfiguredResources(config);
- assertNotNull(projectResources);
- ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
- "MyTheme", true);
- assertNotNull(resolver);
-
- final AtomicBoolean wasWarned = new AtomicBoolean(false);
- LayoutLog logger = new LayoutLog() {
- @Override
- public void error(String tag, String message, Object data) {
- if (("Potential stack overflow trying to resolve "
- + "'@color/loop1': cyclic resource definitions?"
- + " Render may not be accurate.").equals(message)) {
- wasWarned.set(true);
- } else if (("Potential stack overflow trying to resolve "
- + "'@color/loop2b': cyclic resource definitions? "
- + "Render may not be accurate.").equals(message)) {
- wasWarned.set(true);
- } else {
- fail(message);
- }
- }
- };
- resolver.setLogger(logger);
-
- assertNotNull(resolver.findResValue("@color/loop1", false));
- resolver.resolveResValue(resolver.findResValue("@color/loop1", false));
- assertTrue(wasWarned.get());
-
- wasWarned.set(false);
- assertNotNull(resolver.findResValue("@color/loop2a", false));
- resolver.resolveResValue(resolver.findResValue("@color/loop2a", false));
- assertTrue(wasWarned.get());
-
- projectRepository.dispose();
- }
-
- public void testParentCycle() throws IOException {
- TestResourceRepository projectRepository = TestResourceRepository.create(false,
- new Object[]{
- "values/styles.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"ButtonStyle.Base\">\n"
- + " <item name=\"android:textColor\">#ff0000</item>\n"
- + " </style>\n"
- + " <style name=\"ButtonStyle\" parent=\"ButtonStyle.Base\">\n"
- + " <item name=\"android:layout_height\">40dp</item>\n"
- + " </style>\n"
- + "</resources>\n",
-
- "layouts/layout.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- + " android:layout_width=\"match_parent\"\n"
- + " android:layout_height=\"match_parent\">\n"
- + "\n"
- + " <TextView\n"
- + " style=\"@style/ButtonStyle\"\n"
- + " android:layout_width=\"wrap_content\"\n"
- + " android:layout_height=\"wrap_content\" />\n"
- + "\n"
- + "</RelativeLayout>\n",
-
- });
- assertFalse(projectRepository.isFrameworkRepository());
- FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
- assertNotNull(config);
- Map<ResourceType, Map<String, ResourceValue>> projectResources =
- projectRepository.getConfiguredResources(config);
- assertNotNull(projectResources);
- ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
- "ButtonStyle", true);
- assertNotNull(resolver);
-
- final AtomicBoolean wasWarned = new AtomicBoolean(false);
- LayoutLog logger = new LayoutLog() {
- @Override
- public void error(String tag, String message, Object data) {
- assertEquals("Cyclic style parent definitions: \"ButtonStyle\" specifies "
- + "parent \"ButtonStyle.Base\" implies parent \"ButtonStyle\"", message);
- assertEquals(LayoutLog.TAG_BROKEN, tag);
- wasWarned.set(true);
- }
- };
- resolver.setLogger(logger);
-
- StyleResourceValue buttonStyle = (StyleResourceValue) resolver.findResValue(
- "@style/ButtonStyle", false);
- ResourceValue textColor = resolver.findItemInStyle(buttonStyle, "textColor", true);
- assertNotNull(textColor);
- assertEquals("#ff0000", textColor.getValue());
- assertFalse(wasWarned.get());
- ResourceValue missing = resolver.findItemInStyle(buttonStyle, "missing", true);
- assertNull(missing);
- assertTrue(wasWarned.get());
-
- projectRepository.dispose();
- }
-
- public void testSetDeviceDefaults() throws Exception {
- TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
- new Object[] {
- "values/themes.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"Theme.Light\" parent=\"\">\n"
- + " <item name=\"android:textColor\">#ff0000</item>\n"
- + " </style>\n"
- + " <style name=\"Theme.Holo.Light\" parent=\"Theme.Light\">\n"
- + " <item name=\"android:textColor\">#00ff00</item>\n"
- + " </style>\n"
- + " <style name=\"Theme.DeviceDefault.Light\" parent=\"Theme.Holo.Light\"/>\n"
- + " <style name=\"Theme\" parent=\"\">\n"
- + " <item name=\"android:textColor\">#000000</item>\n"
- + " </style>\n"
- + " <style name=\"Theme.Holo\" parent=\"Theme\">\n"
- + " <item name=\"android:textColor\">#0000ff</item>\n"
- + " </style>\n"
- + " <style name=\"Theme.DeviceDefault\" parent=\"Theme.Holo\"/>\n"
- + "</resources>\n",
- });
-
- TestResourceRepository projectRepository = TestResourceRepository.create(false,
- new Object[] {
- "values/themes.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"AppTheme\" parent=\"android:Theme.DeviceDefault.Light\"/>\n"
- + " <style name=\"AppTheme.Dark\" parent=\"android:Theme.DeviceDefault\"/>\n"
- + "</resources>\n"
- });
-
- assertFalse(projectRepository.isFrameworkRepository());
- FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
- assertNotNull(config);
- Map<ResourceType, Map<String, ResourceValue>> projectResources = projectRepository
- .getConfiguredResources(config);
- Map<ResourceType, Map<String, ResourceValue>> frameworkResources = frameworkRepository
- .getConfiguredResources(config);
- assertNotNull(projectResources);
- ResourceResolver lightResolver = ResourceResolver.create(projectResources,
- frameworkResources, "AppTheme", true);
- assertNotNull(lightResolver);
- ResourceValue textColor = lightResolver.findItemInTheme("textColor", true);
- assertNotNull(textColor);
- assertEquals("#00ff00", textColor.getValue());
-
- lightResolver.setDeviceDefaults("Theme.Light", null);
- textColor = lightResolver.findItemInTheme("textColor", true);
- assertNotNull(textColor);
- assertEquals("#ff0000", textColor.getValue());
-
- ResourceResolver darkResolver = ResourceResolver.create(projectResources,
- frameworkResources, "AppTheme.Dark", true);
- assertNotNull(darkResolver);
- textColor = darkResolver.findItemInTheme("textColor", true);
- assertNotNull(textColor);
- assertEquals("#0000ff", textColor.getValue());
-
- darkResolver.setDeviceDefaults("Theme.Light", "Theme");
- textColor = darkResolver.findItemInTheme("textColor", true);
- assertNotNull(textColor);
- assertEquals("#000000", textColor.getValue());
- }
-
- public void testCycle() throws Exception {
- TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
- new Object[] {
- "values/themes.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"Theme.DeviceDefault.Light\"/>\n"
- + "</resources>\n",
- });
-
- TestResourceRepository projectRepository = TestResourceRepository.create(false,
- new Object[] {
- "values/themes.xml", ""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <style name=\"AppTheme\" parent=\"android:Theme.DeviceDefault.Light\"/>\n"
- + " <style name=\"AppTheme.Dark\" parent=\"android:Theme.DeviceDefault\"/>\n"
- + " <style name=\"foo\" parent=\"bar\"/>\n"
- + " <style name=\"bar\" parent=\"foo\"/>\n"
- + "</resources>\n"
- });
-
- assertFalse(projectRepository.isFrameworkRepository());
- FolderConfiguration config = FolderConfiguration.getConfigForFolder("values");
- assertNotNull(config);
- Map<ResourceType, Map<String, ResourceValue>> projectResources = projectRepository
- .getConfiguredResources(config);
- Map<ResourceType, Map<String, ResourceValue>> frameworkResources = frameworkRepository
- .getConfiguredResources(config);
- assertNotNull(projectResources);
- ResourceResolver resolver = ResourceResolver.create(projectResources,
- frameworkResources, "AppTheme", true);
-
- final AtomicBoolean wasWarned = new AtomicBoolean(false);
- LayoutLog logger = new LayoutLog() {
- @Override
- public void error(String tag, String message, Object data) {
- if ("Cyclic style parent definitions: \"foo\" specifies parent \"bar\" specifies parent \"foo\"".equals(message)) {
- wasWarned.set(true);
- } else {
- fail(message);
- }
- }
- };
- resolver.setLogger(logger);
- assertFalse(resolver.isTheme(resolver.findResValue("@style/foo", false), null));
- assertTrue(wasWarned.get());
-
- projectRepository.dispose();
-
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java b/base/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java
deleted file mode 100644
index 60e9ee6..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package com.android.ide.common.resources;
-
-import static com.android.SdkConstants.FD_RES;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.annotations.NonNull;
-import com.android.ide.common.res2.MergingException;
-import com.android.ide.common.res2.RecordingLogger;
-import com.android.ide.common.res2.ResourceMerger;
-import com.android.ide.common.res2.ResourceSet;
-import com.android.io.FolderWrapper;
-import com.android.io.IAbstractFolder;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-
-public class TestResourceRepository extends ResourceRepository {
- private final File mDir;
-
- TestResourceRepository(@NonNull IAbstractFolder resFolder, boolean isFrameworkRepository,
- File dir) {
- super(resFolder, isFrameworkRepository);
- mDir = dir;
- }
-
- @NonNull
- @Override
- protected ResourceItem createResourceItem(@NonNull String name) {
- return new TestResourceItem(name);
- }
-
- public File getDir() {
- return mDir;
- }
-
- public void dispose() {
- deleteFile(mDir);
- }
-
- private static void deleteFile(File dir) {
- if (dir.isDirectory()) {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File f : files) {
- deleteFile(f);
- }
- }
- } else if (dir.isFile()) {
- assertTrue(dir.getPath(), dir.delete());
- }
- }
-
- /**
- * Creates a resource repository for a resource folder whose contents is identified
- * by the pairs of relative paths and file contents
- */
- @SuppressWarnings("ResultOfMethodCallIgnored")
- @NonNull
- public static TestResourceRepository create(boolean isFramework, Object[] data)
- throws IOException {
- File dir = Files.createTempDir();
- File res = new File(dir, FD_RES);
- res.mkdirs();
-
- assertTrue("Expected even number of items (path,contents)", data.length % 2 == 0);
- for (int i = 0; i < data.length; i += 2) {
- Object relativePathObject = data[i];
- assertTrue(relativePathObject instanceof String);
- String relativePath = (String) relativePathObject;
- relativePath = relativePath.replace('/', File.separatorChar);
- File file = new File(res, relativePath);
- File parent = file.getParentFile();
- parent.mkdirs();
-
- Object fileContents = data[i + 1];
- if (fileContents instanceof String) {
- String text = (String) fileContents;
- Files.write(text, file, Charsets.UTF_8);
- } else if (fileContents instanceof byte[]) {
- byte[] bytes = (byte[]) fileContents;
- Files.write(bytes, file);
- } else {
- fail("File contents must be Strings or byte[]'s");
- }
- }
-
- IAbstractFolder resFolder = new FolderWrapper(dir, FD_RES);
- return new TestResourceRepository(resFolder, isFramework, dir);
- }
-
- /**
- * Creates a res2 resource repository for a resource folder whose contents is identified
- * by the pairs of relative paths and file contents
- *
- * @see #create(boolean, Object[])
- */
- @SuppressWarnings("ResultOfMethodCallIgnored")
- @NonNull
- public static com.android.ide.common.res2.ResourceRepository createRes2(
- boolean isFramework, Object[] data)
- throws IOException, MergingException {
- File dir = Files.createTempDir();
- File res = new File(dir, FD_RES);
- res.mkdirs();
-
- assertTrue("Expected even number of items (path,contents)", data.length % 2 == 0);
- for (int i = 0; i < data.length; i += 2) {
- Object relativePathObject = data[i];
- assertTrue(relativePathObject instanceof String);
- String relativePath = (String) relativePathObject;
- relativePath = relativePath.replace('/', File.separatorChar);
- File file = new File(res, relativePath);
- File parent = file.getParentFile();
- parent.mkdirs();
-
- Object fileContents = data[i + 1];
- if (fileContents instanceof String) {
- String text = (String) fileContents;
- Files.write(text, file, Charsets.UTF_8);
- } else if (fileContents instanceof byte[]) {
- byte[] bytes = (byte[]) fileContents;
- Files.write(bytes, file);
- } else {
- fail("File contents must be Strings or byte[]'s");
- }
- }
-
- File resFolder = new File(dir, FD_RES);
-
- ResourceMerger merger = new ResourceMerger();
- ResourceSet resourceSet = new ResourceSet("main");
- resourceSet.addSource(resFolder);
- resourceSet.loadFromFiles(new RecordingLogger());
- merger.addDataSet(resourceSet);
-
- com.android.ide.common.res2.ResourceRepository repository;
- repository = new com.android.ide.common.res2.ResourceRepository(isFramework);
- merger.mergeData(repository.createMergeConsumer(), true /*doCleanUp*/);
-
- return repository;
- }
-
- private static class TestResourceItem extends ResourceItem {
- TestResourceItem(String name) {
- super(name);
- }
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java b/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java
deleted file mode 100644
index bb29ac7..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.resources.configuration;
-
-import com.android.ide.common.res2.ResourceFile;
-import com.android.ide.common.res2.ResourceItem;
-import com.android.resources.Density;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenRound;
-import com.android.resources.UiMode;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-public class FolderConfigurationTest extends TestCase {
-
- /*
- * Test createDefault creates all the qualifiers.
- */
- public void testCreateDefault() {
- FolderConfiguration defaultConfig = new FolderConfiguration();
- defaultConfig.createDefault();
-
- // this is always valid and up to date.
- final int count = FolderConfiguration.getQualifierCount();
-
- // make sure all the qualifiers were created.
- for (int i = 0 ; i < count ; i++) {
- assertNotNull(defaultConfig.getQualifier(i));
- }
- }
-
- public void testSimpleResMatch() {
- runConfigMatchTest(
- "en-rGB-port-hdpi-notouch-12key",
- 3,
- "",
- "en",
- "fr-rCA",
- "en-port",
- "en-notouch-12key",
- "port-ldpi",
- "port-notouch-12key");
- }
-
- public void testIsMatchFor() {
- FolderConfiguration en = FolderConfiguration.getConfigForFolder("values-en");
- FolderConfiguration enUs = FolderConfiguration.getConfigForFolder("values-en-rUS");
- assertNotNull(en);
- assertNotNull(enUs);
- assertTrue(enUs.isMatchFor(enUs));
- assertTrue(en.isMatchFor(en));
- assertTrue(enUs.isMatchFor(en));
- assertTrue(en.isMatchFor(enUs));
- }
-
- public void testVersionResMatch() {
- runConfigMatchTest(
- "en-rUS-w600dp-h1024dp-large-port-mdpi-finger-nokeys-v12",
- 2,
- "",
- "large",
- "w540dp");
- }
-
- public void testVersionResMatchWithBcp47() {
- runConfigMatchTest(
- "b+kok+Knda+419+VARIANT-w600dp",
- 2,
- "",
- "large",
- "w540dp");
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testAddQualifier() {
- FolderConfiguration defaultConfig = new FolderConfiguration();
- defaultConfig.createDefault();
-
- final int count = FolderConfiguration.getQualifierCount();
- for (int i = 0 ; i < count ; i++) {
- FolderConfiguration empty = new FolderConfiguration();
-
- ResourceQualifier q = defaultConfig.getQualifier(i);
-
- empty.addQualifier(q);
-
- // check it was added
- assertNotNull(
- "addQualifier failed for " + q.getClass().getName(), empty.getQualifier(i));
- }
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testGetConfig1() {
- FolderConfiguration configForFolder =
- FolderConfiguration.getConfig(new String[] { "values", "en", "rUS" });
- assertNotNull(configForFolder);
- assertEquals("en", configForFolder.getLocaleQualifier().getLanguage());
- assertEquals("US", configForFolder.getLocaleQualifier().getRegion());
- assertNull(configForFolder.getScreenDimensionQualifier());
- assertNull(configForFolder.getLayoutDirectionQualifier());
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testInvalidRepeats() {
- assertNull(FolderConfiguration.getConfigForFolder("values-en-rUS-rES"));
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testGetConfig2() {
- FolderConfiguration configForFolder =
- FolderConfiguration.getConfigForFolder("values-en-rUS");
- assertNotNull(configForFolder);
- assertEquals("en", configForFolder.getLocaleQualifier().getLanguage());
- assertEquals("US", configForFolder.getLocaleQualifier().getRegion());
- assertNull(configForFolder.getScreenDimensionQualifier());
- assertNull(configForFolder.getLayoutDirectionQualifier());
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testGetConfigCaseInsensitive() {
- FolderConfiguration configForFolder =
- FolderConfiguration.getConfigForFolder("values-EN-rus");
- assertNotNull(configForFolder);
- assertEquals("en", configForFolder.getLocaleQualifier().getLanguage());
- assertEquals("US", configForFolder.getLocaleQualifier().getRegion());
- assertNull(configForFolder.getScreenDimensionQualifier());
- assertNull(configForFolder.getLayoutDirectionQualifier());
- assertEquals("layout-en-rUS", configForFolder.getFolderName(ResourceFolderType.LAYOUT));
-
- runConfigMatchTest(
- "en-rgb-Port-HDPI-notouch-12key",
- 3,
- "",
- "en",
- "fr-rCA",
- "en-port",
- "en-notouch-12key",
- "port-ldpi",
- "port-notouch-12key");
- }
-
- public void testToStrings() {
- FolderConfiguration configForFolder = FolderConfiguration.getConfigForFolder("values-en-rUS");
- assertNotNull(configForFolder);
- assertEquals("Locale en_US", configForFolder.toDisplayString());
- assertEquals("en,US", configForFolder.toShortDisplayString());
- assertEquals("layout-en-rUS", configForFolder.getFolderName(ResourceFolderType.LAYOUT));
- assertEquals("-en-rUS", configForFolder.getUniqueKey());
- }
-
- public void testNormalize() {
- // test normal qualifiers that all have the same min SDK
- doTestNormalize(4, "large");
- doTestNormalize(8, "notnight");
- doTestNormalize(13, "sw42dp");
- doTestNormalize(17, "ldrtl");
-
- // test we take the highest qualifier
- doTestNormalize(13, "sw42dp", "large");
-
- // test where different values have different minSdk
- /* Ambiguous now that aapt accepts 3 letter language codes; get clarification.
- doTestNormalize(8, "car");
- */
- doTestNormalize(13, "television");
- doTestNormalize(16, "appliance");
-
- // test case where there's already a higher -v# qualifier
- doTestNormalize(18, "sw42dp", "v18");
-
- // finally test that in some cases it won't add a -v# value.
- FolderConfiguration configForFolder = FolderConfiguration.getConfigFromQualifiers(
- Collections.singletonList("port"));
-
- assertNotNull(configForFolder);
-
- configForFolder.normalize();
- VersionQualifier versionQualifier = configForFolder.getVersionQualifier();
- assertNull(versionQualifier);
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testConfigMatch() {
- FolderConfiguration ref = new FolderConfiguration();
- ref.createDefault();
- ref.addQualifier(new ScreenOrientationQualifier(ScreenOrientation.PORTRAIT));
- List<Configurable> configurables = getConfigurable(
- "", // No qualifier
- "xhdpi", // A matching qualifier
- "land", // A non matching qualifier
- "xhdpi-v14", // Matching qualifier with ignored qualifier
- "v14" // Ignored qualifier
- );
- // First check that when all qualifiers are present, we match only one resource.
- List<Configurable> matchingConfigurables = ref.findMatchingConfigurables(configurables);
- assertEquals(ImmutableList.of(configurables.get(3)), matchingConfigurables);
-
- // Now remove the version qualifier and check that we "xhdpi" and "xhdpi-v14"
- ref.setVersionQualifier(null);
- matchingConfigurables = ref.findMatchingConfigurables(configurables);
- assertEquals(ImmutableSet.of(configurables.get(1), configurables.get(3)),
- ImmutableSet.copyOf(matchingConfigurables));
-
- }
-
- public void testIsRoundMatch() {
- FolderConfiguration configForFolder = FolderConfiguration
- .getConfigForFolder("values-en-round");
- assertNotNull(configForFolder);
- assertNotNull(configForFolder.getScreenRoundQualifier());
- assertEquals(ScreenRound.ROUND, configForFolder.getScreenRoundQualifier().getValue());
- runConfigMatchTest("en-rgb-Round-Port-HDPI-notouch-12key", 4,
- "",
- "en",
- "fr-rCa",
- "en-notround-hdpi",
- "en-notouch");
-
- runConfigMatchTest("en-rgb-Round-Port-HDPI-notouch-12key", 2,
- "",
- "en",
- "en-round-hdpi",
- "port-12key");
- }
-
- // --- helper methods
-
- private static final class MockConfigurable implements Configurable {
-
- private final FolderConfiguration mConfig;
-
- MockConfigurable(String config) {
- mConfig = FolderConfiguration.getConfig(getFolderSegments(config));
- }
-
- @Override
- public FolderConfiguration getConfiguration() {
- return mConfig;
- }
-
- @Override
- public String toString() {
- return mConfig.toString();
- }
- }
-
- private static void runConfigMatchTest(String refConfig, int resultIndex, String... configs) {
- FolderConfiguration reference = FolderConfiguration.getConfig(getFolderSegments(refConfig));
- assertNotNull(reference);
-
- List<Configurable> list = getConfigurable(configs);
-
- Configurable match = reference.findMatchingConfigurable(list);
- assertEquals(resultIndex, list.indexOf(match));
- }
-
- private static List<Configurable> getConfigurable(String... configs) {
- ArrayList<Configurable> list = new ArrayList<Configurable>();
-
- for (String config : configs) {
- list.add(new MockConfigurable(config));
- }
-
- return list;
- }
-
- private static String[] getFolderSegments(String config) {
- return (!config.isEmpty() ? "foo-" + config : "foo").split("-");
- }
-
- public void testSort1() {
- List<FolderConfiguration> configs = Lists.newArrayList();
- FolderConfiguration f1 = FolderConfiguration.getConfigForFolder("values-hdpi");
- FolderConfiguration f2 = FolderConfiguration.getConfigForFolder("values-v11");
- FolderConfiguration f3 = FolderConfiguration.getConfigForFolder("values-sp");
- FolderConfiguration f4 = FolderConfiguration.getConfigForFolder("values-v4");
- configs.add(f1);
- configs.add(f2);
- configs.add(f3);
- configs.add(f4);
- assertEquals(Arrays.asList(f1, f2, f3, f4), configs);
- Collections.sort(configs);
- assertEquals(Arrays.asList(f2, f4, f1, f3), configs);
- }
-
- public void testSort2() {
- // Test case from
- // http://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch
- List<FolderConfiguration> configs = Lists.newArrayList();
- for (String name : new String[] {
- "drawable",
- "drawable-en",
- "drawable-fr-rCA",
- "drawable-en-port",
- "drawable-en-notouch-12key",
- "drawable-port-ldpi",
- "drawable-port-notouch-12key"
- }) {
- FolderConfiguration config = FolderConfiguration.getConfigForFolder(name);
- assertNotNull(name, config);
- configs.add(config);
- }
- Collections.sort(configs);
- Collections.reverse(configs);
- //assertEquals("", configs.get(0).toDisplayString());
-
- List<String> strings = Lists.newArrayList();
- for (FolderConfiguration config : configs) {
- strings.add(config.getUniqueKey());
- }
- assertEquals("-fr-rCA,-en-port,-en-notouch-12key,-en,-port-ldpi,-port-notouch-12key,",
- Joiner.on(",").skipNulls().join(strings));
-
- }
-
- private void doTestNormalize(int expectedVersion, String... segments) {
- FolderConfiguration configForFolder = FolderConfiguration.getConfigFromQualifiers(
- Arrays.asList(segments));
-
- assertNotNull(configForFolder);
-
- configForFolder.normalize();
- VersionQualifier versionQualifier = configForFolder.getVersionQualifier();
- assertNotNull(versionQualifier);
- assertEquals(expectedVersion, versionQualifier.getVersion());
-
- }
-
- public void testCarModeAndLanguage() {
- FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-car");
- assertNotNull(config);
- assertNull(config.getLocaleQualifier());
- assertNotNull(config.getUiModeQualifier());
- assertEquals(UiMode.CAR, config.getUiModeQualifier().getValue());
-
- config = FolderConfiguration.getConfigForFolder("values-b+car");
- assertNotNull(config);
- assertNotNull(config.getLocaleQualifier());
- assertNull(config.getUiModeQualifier());
- assertEquals("car", config.getLocaleQualifier().getLanguage());
- }
-
- public void testIsMatchForBcp47() {
- FolderConfiguration blankFolder = FolderConfiguration.getConfigForFolder("values");
- FolderConfiguration enFolder = FolderConfiguration.getConfigForFolder("values-en");
- FolderConfiguration deFolder = FolderConfiguration.getConfigForFolder("values-de");
- FolderConfiguration deBcp47Folder = FolderConfiguration.getConfigForFolder("values-b+de");
- assertNotNull(enFolder);
- assertNotNull(deFolder);
- assertNotNull(deBcp47Folder);
- assertFalse(enFolder.isMatchFor(deFolder));
- assertFalse(deFolder.isMatchFor(enFolder));
- assertFalse(enFolder.isMatchFor(deBcp47Folder));
- assertFalse(deBcp47Folder.isMatchFor(enFolder));
-
- assertTrue(enFolder.isMatchFor(blankFolder));
- assertTrue(deFolder.isMatchFor(blankFolder));
- assertTrue(deBcp47Folder.isMatchFor(blankFolder));
- }
-
- public void testFindMatchingConfigurables() {
- ResourceItem itemBlank = new ResourceItem("foo", ResourceType.STRING, null) {
- @Override
- public String toString() {
- return "itemBlank";
- }
- };
- ResourceFile sourceBlank = new ResourceFile(new File("sourceBlank"), itemBlank, "");
- itemBlank.setSource(sourceBlank);
- FolderConfiguration configBlank = itemBlank.getConfiguration();
-
- ResourceItem itemEn = new ResourceItem("foo", ResourceType.STRING, null) {
- @Override
- public String toString() {
- return "itemEn";
- }
- };
- ResourceFile sourceEn = new ResourceFile(new File("sourceEn"), itemBlank, "en");
- itemEn.setSource(sourceEn);
- FolderConfiguration configEn = itemEn.getConfiguration();
-
- ResourceItem itemBcpEn = new ResourceItem("foo", ResourceType.STRING, null) {
- @Override
- public String toString() {
- return "itemBcpEn";
- }
- };
- ResourceFile sourceBcpEn = new ResourceFile(new File("sourceBcpEn"), itemBlank, "b+en");
- itemBcpEn.setSource(sourceBcpEn);
- FolderConfiguration configBcpEn = itemBcpEn.getConfiguration();
-
- ResourceItem itemDe = new ResourceItem("foo", ResourceType.STRING, null) {
- @Override
- public String toString() {
- return "itemDe";
- }
- };
-
- ResourceFile sourceDe = new ResourceFile(new File("sourceDe"), itemBlank, "de");
- itemDe.setSource(sourceDe);
- FolderConfiguration configDe = itemDe.getConfiguration();
-
- // "" matches everything
- assertEquals(Arrays.<Configurable>asList(itemBlank, itemBcpEn, itemEn, itemDe),
- configBlank.findMatchingConfigurables(
- Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
-
- // "de" matches only "" and "de"
- assertEquals(Arrays.<Configurable>asList(itemBlank, itemDe),
- configDe.findMatchingConfigurables(
- Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
-
- // "en" matches "en" and "b+en"
- assertTrue(configEn.isMatchFor(configBcpEn));
- assertTrue(configBcpEn.isMatchFor(configEn));
- assertEquals(Arrays.<Configurable>asList(itemBcpEn, itemEn),
- configEn.findMatchingConfigurables(
- Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
-
- // "b+en" matches "en and "b+en"
- assertEquals(Arrays.<Configurable>asList(itemBcpEn, itemEn),
- configBcpEn.findMatchingConfigurables(
- Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
- }
-
- public void testFromQualifierString() throws Exception {
- FolderConfiguration blankFolder = FolderConfiguration.getConfigForQualifierString("");
- FolderConfiguration enFolder = FolderConfiguration.getConfigForQualifierString("en");
- FolderConfiguration deFolder = FolderConfiguration.getConfigForQualifierString("de");
- FolderConfiguration deBcp47Folder = FolderConfiguration.getConfigForQualifierString("b+de");
- FolderConfiguration twoQualifiersFolder =
- FolderConfiguration.getConfigForQualifierString("de-hdpi");
-
- assertNotNull(enFolder);
- assertNotNull(deFolder);
- assertNotNull(deBcp47Folder);
- assertFalse(enFolder.isMatchFor(deFolder));
- assertFalse(deFolder.isMatchFor(enFolder));
- assertFalse(enFolder.isMatchFor(deBcp47Folder));
- assertFalse(deBcp47Folder.isMatchFor(enFolder));
-
- assertTrue(enFolder.isMatchFor(blankFolder));
- assertTrue(deFolder.isMatchFor(blankFolder));
- assertTrue(deBcp47Folder.isMatchFor(blankFolder));
-
- assertEquals("de", twoQualifiersFolder.getLocaleQualifier().getLanguage());
- assertEquals(Density.HIGH, twoQualifiersFolder.getDensityQualifier().getValue());
- }
-
- public void testCopyOf() throws Exception {
- FolderConfiguration deBcp47Folder = FolderConfiguration.getConfigForFolder("values-b+de");
- FolderConfiguration copy = FolderConfiguration.copyOf(deBcp47Folder);
- assertTrue(copy.isMatchFor(deBcp47Folder));
-
- copy.setLocaleQualifier(new LocaleQualifier("en"));
- assertEquals("en", copy.getLocaleQualifier().getLanguage());
- assertEquals("de", deBcp47Folder.getLocaleQualifier().getLanguage());
-
- copy.setDensityQualifier(new DensityQualifier(Density.HIGH));
- assertEquals(Density.HIGH, copy.getDensityQualifier().getValue());
- assertNull(deBcp47Folder.getDensityQualifier());
-
- FolderConfiguration blankFolder = FolderConfiguration.getConfigForFolder("values");
- copy = FolderConfiguration.copyOf(blankFolder);
- assertTrue(copy.isMatchFor(blankFolder));
-
- copy.setVersionQualifier(new VersionQualifier(21));
- assertEquals(21, copy.getVersionQualifier().getVersion());
- assertNull(blankFolder.getVersionQualifier());
- }
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LocaleQualifierTest.java b/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LocaleQualifierTest.java
deleted file mode 100644
index af065a1..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LocaleQualifierTest.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.resources.configuration;
-
-import static com.android.ide.common.resources.configuration.LocaleQualifier.FAKE_VALUE;
-import static com.android.ide.common.resources.configuration.LocaleQualifier.getQualifier;
-import static com.android.ide.common.resources.configuration.LocaleQualifier.isNormalizedCase;
-import static com.android.ide.common.resources.configuration.LocaleQualifier.normalizeCase;
-import static com.android.ide.common.resources.configuration.LocaleQualifier.parseBcp47;
-
-import junit.framework.TestCase;
-
-import java.util.Locale;
-
-public class LocaleQualifierTest extends TestCase {
-
- private FolderConfiguration config;
- private LocaleQualifier lq;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- config = new FolderConfiguration();
- lq = new LocaleQualifier();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- config = null;
- lq = null;
- }
-
- public void testCheckAndSet() {
- assertEquals(true, lq.checkAndSet("b+kok", config)); //$NON-NLS-1$
- assertTrue(config.getLocaleQualifier() != null);
- assertEquals("kok", config.getLocaleQualifier().toString()); //$NON-NLS-1$
- }
-
- public void testCheckAndSetCaseInsensitive() {
- assertEquals(true, lq.checkAndSet("b+KOK", config)); //$NON-NLS-1$
- assertTrue(config.getLocaleQualifier() != null);
- assertEquals("kok", config.getLocaleQualifier().toString()); //$NON-NLS-1$
- }
-
- public void testFailures() {
- assertEquals(false, lq.checkAndSet("", config)); //$NON-NLS-1$
- assertEquals(false, lq.checkAndSet("abcd", config)); //$NON-NLS-1$
- assertEquals(false, lq.checkAndSet("en-USofA", config)); //$NON-NLS-1$
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testGetQualifier() {
- assertNull(getQualifier("v4")); // version qualifier shouldn't match
-
- assertNull(getQualifier(""));
- assertEquals("en", getQualifier("en").getLanguage());
- assertNull(getQualifier("en").getRegion());
- assertNull(getQualifier("en").getScript());
- assertEquals("en", getQualifier("EN").getLanguage());
- assertEquals("en", getQualifier("EN").getFull());
-
- assertEquals("en", getQualifier("en-rUS").getLanguage());
- assertEquals("US", getQualifier("en-rUS").getRegion());
- assertNull(getQualifier("en-rUS").getScript());
- assertEquals("en", getQualifier("EN-RUS").getLanguage());
- assertEquals("en", getQualifier("EN-RUS").getLanguage());
- assertEquals("US", getQualifier("EN-RUS").getRegion());
- assertNull(getQualifier("EN-RUS").getScript());
- assertEquals("en-rUS", getQualifier("EN-RUS").getFull());
-
- assertEquals("eng", getQualifier("eng").getLanguage());
- assertNull(getQualifier("eng").getRegion());
- assertNull(getQualifier("eng").getScript());
- assertEquals("eng", getQualifier("ENG").getLanguage());
- assertEquals("eng", getQualifier("ENG").getFull());
-
- assertEquals("foo", getQualifier("foo").getLanguage());
- assertNull(getQualifier("car")); // car mode: not recognized as language
-
- assertEquals("eng", getQualifier("eng-rUS").getLanguage());
- assertEquals("US", getQualifier("eng-rUS").getRegion());
- assertNull(getQualifier("eng-rUS").getScript());
- assertEquals("eng", getQualifier("ENG-RUS").getLanguage());
- assertEquals("eng", getQualifier("ENG-RUS").getLanguage());
- assertEquals("US", getQualifier("ENG-RUS").getRegion());
- assertNull(getQualifier("ENG-RUS").getScript());
- assertEquals("eng-rUS", getQualifier("ENG-RUS").getFull());
- assertNull(getQualifier("eng-rUSA"));
-
- assertNull(getQualifier("kok-rIND"));
- assertEquals("kok", getQualifier("b+kok").getLanguage());
- assertNull(getQualifier("b+kok").getRegion());
- assertEquals("kok", getQualifier("b+kok+VARIANT").getLanguage());
- assertNull(getQualifier("b+kok+VARIANT").getRegion());
- assertEquals("kok", getQualifier("b+kok+Knda+419+VARIANT").getLanguage());
- assertEquals("419", getQualifier("b+kok+Knda+419+VARIANT").getRegion());
- assertEquals("Knda", getQualifier("b+kok+Knda+419+VARIANT").getScript());
- assertEquals("kok", getQualifier("b+kok+VARIANT").getLanguage());
- assertNull(getQualifier("b+kok+VARIANT").getRegion());
- assertEquals("kok", getQualifier("b+kok+IN").getLanguage());
- assertEquals("IN", getQualifier("b+kok+IN").getRegion());
- assertEquals("kok", getQualifier("b+kok+Knda").getLanguage());
- assertNull(getQualifier("b+kok+Knda").getRegion());
- assertEquals("kok", getQualifier("b+kok+Knda+419").getLanguage());
- assertEquals("419", getQualifier("b+kok+Knda+419").getRegion());
- assertEquals("b+kok+Knda+419", getQualifier("b+KOK+knda+419").getFull());
- }
-
- public void testSetRegion() {
- LocaleQualifier qualifier = getQualifier("en");
- assertNotNull(qualifier);
- qualifier.setRegionSegment("rUS");
- assertEquals("en", qualifier.getLanguage());
- assertEquals("US", qualifier.getRegion());
- assertEquals("en-rUS", qualifier.getFull());
-
- // Case check
- qualifier = getQualifier("EN");
- assertNotNull(qualifier);
- qualifier.setRegionSegment("Rus");
- assertEquals("en", qualifier.getLanguage());
- assertEquals("US", qualifier.getRegion());
- assertEquals("en-rUS", qualifier.getFull());
-
- // 3 letter language
- qualifier = getQualifier("eng");
- assertNotNull(qualifier);
- qualifier.setRegionSegment("rUS");
- assertEquals("eng", qualifier.getLanguage());
- assertEquals("US", qualifier.getRegion());
- assertEquals("eng-rUS", qualifier.getFull());
- }
-
- public void testEquals() {
- LocaleQualifier qualifier1 = getQualifier("b+KOK+knda+419");
- LocaleQualifier qualifier2 = getQualifier("b+kok+knda+419");
- assertNotNull(qualifier1);
- assertNotNull(qualifier2);
- assertEquals(qualifier1, qualifier2);
- assertEquals(qualifier2, qualifier1);
-
- qualifier2 = getQualifier("b+kok+knda");
- assertNotNull(qualifier2);
- assertFalse(qualifier1.equals(qualifier2));
- assertFalse(qualifier2.equals(qualifier1));
-
- // Equivalent, with different syntax
- qualifier1 = getQualifier("b+en+US");
- qualifier2 = getQualifier("en-rUS");
- assertNotNull(qualifier1);
- assertNotNull(qualifier1);
- assertNotNull(qualifier2);
- assertEquals(qualifier1, qualifier2);
- assertEquals(qualifier2, qualifier1);
-
- qualifier1 = getQualifier("b+eng+US");
- qualifier2 = getQualifier("eng-rUS");
- assertNotNull(qualifier1);
- assertNotNull(qualifier1);
- assertNotNull(qualifier2);
- assertEquals(qualifier1, qualifier2);
- assertEquals(qualifier2, qualifier1);
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testParseBcp47() {
-
- assertNull(parseBcp47("kok-rIN"));
- assertEquals("kok", parseBcp47("b+kok").getLanguage());
- assertNull(parseBcp47("b+kok").getRegion());
-
- assertEquals("kok", parseBcp47("b+kok+VARIANT").getLanguage());
- assertNull(parseBcp47("b+kok+VARIANT").getRegion());
-
- assertEquals("kok", parseBcp47("b+kok+Knda+419+VARIANT").getLanguage());
- assertEquals("419", parseBcp47("b+kok+Knda+419+VARIANT").getRegion());
- assertEquals("Knda", parseBcp47("b+kok+Knda+419+VARIANT").getScript());
-
- assertEquals("kok", parseBcp47("b+kok+VARIANT").getLanguage());
- assertNull(parseBcp47("b+kok+VARIANT").getRegion());
-
- assertEquals("kok", parseBcp47("b+kok+IN").getLanguage());
- assertEquals("IN", parseBcp47("b+kok+IN").getRegion());
-
- assertEquals("kok", parseBcp47("b+kok+Knda").getLanguage());
- assertEquals("Knda", parseBcp47("b+kok+Knda").getScript());
- assertNull(parseBcp47("b+kok+Knda").getRegion());
-
- assertEquals("kok", parseBcp47("b+kok+Knda+419").getLanguage());
- assertEquals("419", parseBcp47("b+kok+Knda+419").getRegion());
- assertEquals("Knda", parseBcp47("b+kok+Knda+419").getScript());
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testGetLanguageAndGetRegion() {
- assertEquals(true, lq.checkAndSet("b+kok", config)); //$NON-NLS-1$
- assertEquals("kok", config.getLocaleQualifier().getValue());
- assertEquals("kok", config.getLocaleQualifier().getLanguage());
- assertEquals("kok", config.getLocaleQualifier().getLanguage());
- assertNull("kok", config.getLocaleQualifier().getRegion());
-
- assertEquals(true, lq.checkAndSet("b+kok+VARIANT", config)); //$NON-NLS-1$
- assertEquals("b+kok+variant", config.getLocaleQualifier().getValue());
- assertEquals("kok", config.getLocaleQualifier().getLanguage());
- assertNull("kok", config.getLocaleQualifier().getRegion());
-
- assertEquals(true, lq.checkAndSet("b+kok+Knda+419+VARIANT", config)); //$NON-NLS-1$
- assertEquals("b+kok+Knda+419+variant", config.getLocaleQualifier().getValue());
- assertEquals("kok", config.getLocaleQualifier().getLanguage());
- assertEquals("419", config.getLocaleQualifier().getRegion());
-
- assertEquals(true, lq.checkAndSet("b+kok+IN", config)); //$NON-NLS-1$
- assertEquals("kok-rIN", config.getLocaleQualifier().getValue());
- assertEquals("kok", config.getLocaleQualifier().getLanguage());
- assertEquals("IN", config.getLocaleQualifier().getRegion());
-
- assertEquals(true, lq.checkAndSet("b+kok+Knda", config)); //$NON-NLS-1$
- assertEquals("b+kok+Knda", config.getLocaleQualifier().getValue());
- assertEquals("kok", config.getLocaleQualifier().getLanguage());
- assertNull(config.getLocaleQualifier().getRegion());
-
- assertEquals(true, lq.checkAndSet("b+kok+Knda+419", config)); //$NON-NLS-1$
- assertEquals("b+kok+Knda+419", config.getLocaleQualifier().getValue());
- assertEquals("kok", config.getLocaleQualifier().getLanguage());
- assertEquals("419", config.getLocaleQualifier().getRegion());
- }
-
- public void testIsNormalCase() {
- // Language
- assertFalse(isNormalizedCase("LL"));
- assertFalse(isNormalizedCase("Ll"));
- assertFalse(isNormalizedCase("lL"));
- assertFalse(isNormalizedCase("LLL"));
- assertTrue(isNormalizedCase("ll"));
- assertTrue(isNormalizedCase("lll"));
-
- // Language + Region
- assertFalse(isNormalizedCase("LL-rRR"));
- assertFalse(isNormalizedCase("ll-rrr"));
- assertFalse(isNormalizedCase("LL-rrr"));
- assertFalse(isNormalizedCase("ll-RRR"));
- assertFalse(isNormalizedCase("lL-frR"));
- assertFalse(isNormalizedCase("Ll-fRr"));
- assertFalse(isNormalizedCase("llL-frr"));
- assertTrue(isNormalizedCase("ll-rRR"));
- assertTrue(isNormalizedCase("lll-rRR"));
-
- // BCP 47
- assertFalse(isNormalizedCase("b+en+CA+x+ca".toLowerCase(Locale.US)));
- assertTrue(isNormalizedCase("b+en+CA+x+ca"));
- assertFalse(isNormalizedCase("b+sgn+BE+FR".toLowerCase(Locale.US)));
- assertTrue(isNormalizedCase("b+sgn+BE+FR"));
- assertFalse(isNormalizedCase("b+az+Latn+x+latn".toLowerCase(Locale.US)));
- assertTrue(isNormalizedCase("b+az+Latn+x+latn"));
- assertFalse(isNormalizedCase("b+MN+cYRL+mn".toLowerCase(Locale.US)));
- assertTrue(isNormalizedCase("b+mn+Cyrl+MN"));
- assertFalse(isNormalizedCase("b+zh+CN+a+myext+x+private".toLowerCase(Locale.US)));
- assertTrue(isNormalizedCase("b+zh+CN+a+myext+x+private"));
- }
-
- public void testNormalizeCase() {
- assertEquals("bb", normalizeCase("BB"));
- assertEquals("ll-rRR", normalizeCase("LL-Rrr"));
- assertEquals("lll-rRR", normalizeCase("LLL-Rrr"));
-
- assertEquals("b+en+CA+x+ca", normalizeCase("b+en+CA+x+ca".toLowerCase(Locale.US)));
- assertEquals("b+sgn+BE+FR", normalizeCase("b+sgn+BE+FR".toLowerCase(Locale.US)));
- assertEquals("b+az+Latn+x+latn", normalizeCase("b+az+Latn+x+latn".toLowerCase(Locale.US)));
- assertEquals("b+mn+Cyrl+MN", normalizeCase("b+MN+cYRL+mn".toLowerCase(Locale.US)));
- assertEquals("b+zh+CN+a+myext+x+private", normalizeCase(
- "b+zh+CN+a+myext+x+private".toLowerCase(Locale.US)));
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testIsMatchFor() {
- assertTrue(getQualifier("en").isMatchFor(getQualifier("en")));
- assertFalse(getQualifier("en").isMatchFor(getQualifier("fr")));
- assertFalse(getQualifier("fr").isMatchFor(getQualifier("en")));
-
- assertTrue(getQualifier("en-rUS").isMatchFor(getQualifier("en-rUS")));
- assertFalse(getQualifier("en-rUS").isMatchFor(getQualifier("en-rGB")));
- assertFalse(getQualifier("en-rGB").isMatchFor(getQualifier("en-rUS")));
- assertFalse(getQualifier("fr-rGB").isMatchFor(getQualifier("en-rGB")));
-
- assertTrue(getQualifier("en-rUS").isMatchFor(getQualifier("en-rUS")));
- assertTrue(getQualifier("en-rUS").isMatchFor(getQualifier("en")));
- assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("b+en+US")));
- assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("b+en")));
- assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("en")));
-
- assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("b+en+US")));
- assertTrue(getQualifier("b+en+Knda+US").isMatchFor(getQualifier("b+en+Knda+US")));
- assertTrue(getQualifier("b+en+Knda+US").isMatchFor(getQualifier("b+en")));
-
- // Apparently isMatchFor is a bit more general than you would think; it
- // can't restrict as shown in these two conditions because then other
- // configuration matching code will fail
- //assertFalse(getQualifier("en").isMatchFor(getQualifier("en-rUS")));
- //assertFalse(getQualifier("b+en").isMatchFor(getQualifier("b+en+Knda+US")));
- }
-
- @SuppressWarnings("ConstantConditions")
- public void testGetTag() {
- assertEquals("en-CA", getQualifier("b+en+CA".toLowerCase(Locale.US)).getTag());
- assertEquals("eng-CA", getQualifier("b+eng+CA".toLowerCase(Locale.US)).getTag());
- assertEquals("en-CA-x-ca", getQualifier("b+en+CA+x+ca".toLowerCase(Locale.US)).getTag());
- assertEquals("en", getQualifier("EN").getTag());
- assertEquals("en-US", getQualifier("EN-rUS").getTag());
- }
-
- public void testHasLanguage() {
- //noinspection ConstantConditions
- assertTrue(LocaleQualifier.getQualifier("b+en+CA+x+ca").hasLanguage());
- assertTrue(new LocaleQualifier("en").hasLanguage());
- assertFalse(new LocaleQualifier(FAKE_VALUE).hasLanguage());
- }
-
- public void testHasRegion() {
- //noinspection ConstantConditions
- assertTrue(LocaleQualifier.getQualifier("b+en+CA+x+ca").hasRegion());
- //noinspection ConstantConditions
- assertFalse(LocaleQualifier.getQualifier("b+en").hasRegion());
- assertFalse(new LocaleQualifier(FAKE_VALUE).hasRegion());
- assertFalse(new LocaleQualifier("", FAKE_VALUE, FAKE_VALUE, null).hasRegion());
- }
-}
\ No newline at end of file
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java b/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java
deleted file mode 100644
index d23d708..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2010 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.ide.common.resources.configuration;
-
-import com.android.resources.ScreenSize;
-
-import junit.framework.TestCase;
-
-public class ScreenSizeQualifierTest extends TestCase {
-
- private ScreenSizeQualifier ssq;
- private FolderConfiguration config;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- ssq = new ScreenSizeQualifier();
- config = new FolderConfiguration();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- ssq = null;
- config = null;
- }
-
- public void testSmall() {
- assertEquals(true, ssq.checkAndSet("small", config)); //$NON-NLS-1$
- assertTrue(config.getScreenSizeQualifier() != null);
- assertEquals(ScreenSize.SMALL, config.getScreenSizeQualifier().getValue());
- assertEquals("small", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
- }
-
- public void testNormal() {
- assertEquals(true, ssq.checkAndSet("normal", config)); //$NON-NLS-1$
- assertTrue(config.getScreenSizeQualifier() != null);
- assertEquals(ScreenSize.NORMAL, config.getScreenSizeQualifier().getValue());
- assertEquals("normal", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
- }
-
- public void testLarge() {
- assertEquals(true, ssq.checkAndSet("large", config)); //$NON-NLS-1$
- assertTrue(config.getScreenSizeQualifier() != null);
- assertEquals(ScreenSize.LARGE, config.getScreenSizeQualifier().getValue());
- assertEquals("large", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
- }
-
- public void testXLarge() {
- assertEquals(true, ssq.checkAndSet("xlarge", config)); //$NON-NLS-1$
- assertTrue(config.getScreenSizeQualifier() != null);
- assertEquals(ScreenSize.XLARGE, config.getScreenSizeQualifier().getValue());
- assertEquals("xlarge", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
- }
-
- public void testIsMatchFor() {
- // create qualifiers for small, normal, large and xlarge sizes.
- ScreenSizeQualifier smallQ = new ScreenSizeQualifier(ScreenSize.SMALL);
- ScreenSizeQualifier normalQ = new ScreenSizeQualifier(ScreenSize.NORMAL);
- ScreenSizeQualifier largeQ = new ScreenSizeQualifier(ScreenSize.LARGE);
- ScreenSizeQualifier xlargeQ = new ScreenSizeQualifier(ScreenSize.XLARGE);
-
- // test that every qualifier is a match for itself.
- assertTrue(smallQ.isMatchFor(smallQ));
- assertTrue(normalQ.isMatchFor(normalQ));
- assertTrue(largeQ.isMatchFor(largeQ));
- assertTrue(xlargeQ.isMatchFor(xlargeQ));
-
- // test that small screen sizes match the larger ones.
- assertTrue(smallQ.isMatchFor(smallQ));
- assertTrue(smallQ.isMatchFor(normalQ));
- assertTrue(smallQ.isMatchFor(largeQ));
- assertTrue(smallQ.isMatchFor(xlargeQ));
- assertTrue(normalQ.isMatchFor(normalQ));
- assertTrue(normalQ.isMatchFor(largeQ));
- assertTrue(normalQ.isMatchFor(xlargeQ));
- assertTrue(largeQ.isMatchFor(largeQ));
- assertTrue(largeQ.isMatchFor(xlargeQ));
- assertTrue(xlargeQ.isMatchFor(xlargeQ));
-
- // test that larger screen sizes don't match the smaller ones.
- assertFalse(normalQ.isMatchFor(smallQ));
- assertFalse(largeQ.isMatchFor(smallQ));
- assertFalse(largeQ.isMatchFor(normalQ));
- assertFalse(xlargeQ.isMatchFor(smallQ));
- assertFalse(xlargeQ.isMatchFor(normalQ));
- assertFalse(xlargeQ.isMatchFor(largeQ));
- }
-
- public void testIsBetterMatchThan() {
- // create qualifiers for small, normal, large and xlarge sizes.
- ScreenSizeQualifier smallQ = new ScreenSizeQualifier(ScreenSize.SMALL);
- ScreenSizeQualifier normalQ = new ScreenSizeQualifier(ScreenSize.NORMAL);
- ScreenSizeQualifier largeQ = new ScreenSizeQualifier(ScreenSize.LARGE);
- ScreenSizeQualifier xlargeQ = new ScreenSizeQualifier(ScreenSize.XLARGE);
-
- // test that each Q is a better match than all other valid Qs when the ref is the same Q.
- assertTrue(normalQ.isBetterMatchThan(smallQ, normalQ));
-
- assertTrue(largeQ.isBetterMatchThan(smallQ, largeQ));
- assertTrue(largeQ.isBetterMatchThan(normalQ, largeQ));
-
- assertTrue(xlargeQ.isBetterMatchThan(smallQ, xlargeQ));
- assertTrue(xlargeQ.isBetterMatchThan(normalQ, xlargeQ));
- assertTrue(xlargeQ.isBetterMatchThan(largeQ, xlargeQ));
-
- // test that higher screen size if preferable if there's no exact match.
- assertTrue(normalQ.isBetterMatchThan(smallQ, largeQ));
- assertFalse(smallQ.isBetterMatchThan(normalQ, largeQ));
-
- assertTrue(normalQ.isBetterMatchThan(smallQ, xlargeQ));
- assertTrue(largeQ.isBetterMatchThan(smallQ, xlargeQ));
- assertTrue(largeQ.isBetterMatchThan(normalQ, xlargeQ));
-
- assertFalse(smallQ.isBetterMatchThan(normalQ, xlargeQ));
- assertFalse(smallQ.isBetterMatchThan(largeQ, xlargeQ));
- assertFalse(normalQ.isBetterMatchThan(largeQ, xlargeQ));
- }
-
-}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/vectordrawable/VectorDrawbleGeneratorTest.java b/base/sdk-common/src/test/java/com/android/ide/common/vectordrawable/VectorDrawbleGeneratorTest.java
deleted file mode 100644
index 77664f7..0000000
--- a/base/sdk-common/src/test/java/com/android/ide/common/vectordrawable/VectorDrawbleGeneratorTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.vectordrawable;
-
-import com.android.ide.common.util.GeneratorTest;
-import com.android.testutils.TestUtils;
-import junit.framework.TestCase;
-
-import javax.imageio.ImageIO;
-import java.awt.image.BufferedImage;
-import java.io.*;
-
- at SuppressWarnings("javadoc")
-public class VectorDrawbleGeneratorTest extends GeneratorTest {
- private static final String TEST_DATA_REL_PATH =
- "tools/base/sdk-common/src/test/resources/testData/vectordrawable";
-
- @Override
- protected String getTestDataRelPath() {
- return TEST_DATA_REL_PATH;
- }
-
- private void checkVectorConversion(String testFileName) throws IOException {
- String imageName = testFileName + ".png";
- String svgName = testFileName + ".svg";
-
- String parentDir = "vectordrawable" + File.separator;
- File parentDirFile = TestUtils.getRoot("vectordrawable");
-
- File svgFile = new File(parentDirFile, svgName);
- OutputStream outStream = new ByteArrayOutputStream();
- try {
- Svg2Vector.parseSvgToXml(svgFile, outStream);
- }
- catch (Exception e) {
- TestCase.assertTrue("Failure: Exception in Svg2Vector.parseSvgToXml!", false);
- }
-
- final VdPreview.TargetSize imageTargetSize = VdPreview.TargetSize.createSizeFromWidth(24);
- StringBuilder builder = new StringBuilder();
- BufferedImage image = VdPreview.getPreviewFromVectorXml(imageTargetSize, outStream.toString(), builder);
-
- String pngPath = parentDir + imageName;
- File pngFile = new File(parentDirFile, imageName);
- InputStream is = new FileInputStream(pngFile);
- if (is == null) {
- // Generate golden images here.
- generateGoldenImage(getTargetDir(), image, pngPath, parentDir + imageName);
- } else {
- BufferedImage goldenImage = ImageIO.read(is);
- assertImageSimilar(pngPath, goldenImage, image, 1.0f);
- }
- }
-
- //public void testControlPoints01() throws Exception {
- // checkVectorConversion("test_control_points_01");
- //}
- //
- //public void testControlPoints02() throws Exception {
- // checkVectorConversion("test_control_points_02");
- //}
-
- public void testControlPoints03() throws Exception {
- checkVectorConversion("test_control_points_03");
- }
-
- public void testIconContentCut() throws Exception {
- checkVectorConversion("ic_content_cut_24px");
- }
-
- public void testIconInput() throws Exception {
- checkVectorConversion("ic_input_24px");
- }
-
- public void testIconLiveHelp() throws Exception {
- checkVectorConversion("ic_live_help_24px");
- }
-
- public void testIconLocalLibrary() throws Exception {
- checkVectorConversion("ic_local_library_24px");
- }
-
- public void testIconLocalPhone() throws Exception {
- checkVectorConversion("ic_local_phone_24px");
- }
-
- public void testIconMicOff() throws Exception {
- checkVectorConversion("ic_mic_off_24px");
- }
-
- public void testShapes() throws Exception {
- checkVectorConversion("ic_shapes");
- }
-
- public void testIconTempHigh() throws Exception {
- checkVectorConversion("ic_temp_high");
- }
-
- public void testIconPlusSign() throws Exception {
- checkVectorConversion("ic_plus_sign");
- }
-}
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.png
deleted file mode 100644
index a92c02a..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.png
deleted file mode 100644
index 2a564a2..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.png
deleted file mode 100644
index 081843c..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.png
deleted file mode 100644
index 674628d..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.png
deleted file mode 100644
index 58ea1c7..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.png
deleted file mode 100644
index a188c50..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.png
deleted file mode 100644
index f760018..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.png
deleted file mode 100644
index e97567b..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.png b/base/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.png
deleted file mode 100644
index 5a32de5..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.png b/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.png
deleted file mode 100644
index 50c19f1..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.png b/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.png
deleted file mode 100644
index 96b2f81..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.png and /dev/null differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.png b/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.png
deleted file mode 100644
index 940fa57..0000000
Binary files a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.png and /dev/null differ
diff --git a/base/sdklib/NOTICE b/base/sdklib/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/base/sdklib/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2005-2008, 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.
-
- 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.
-
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
diff --git a/base/sdklib/build.gradle b/base/sdklib/build.gradle
deleted file mode 100644
index bc76ff4..0000000
--- a/base/sdklib/build.gradle
+++ /dev/null
@@ -1,68 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'jacoco'
-apply plugin: 'sdk-java-lib'
-
-evaluationDependsOn(':base:dvlib')
-
-group = 'com.android.tools'
-archivesBaseName = 'sdklib'
-version = rootProject.ext.baseVersion
-
-dependencies {
- compile project(':base:layoutlib-api')
- compile project(':base:dvlib')
-
- compile 'com.google.code.gson:gson:2.2.4'
- compile 'org.apache.commons:commons-compress:1.8.1'
- compile 'org.apache.httpcomponents:httpclient:4.1.1'
- compile 'org.apache.httpcomponents:httpmime:4.1'
-
- testCompile project(':base:dvlib').sourceSets.test.output
- testCompile 'junit:junit:4.12'
-}
-
-test {
- testLogging {
- showStandardStreams = true
- showStackTraces = true
- exceptionFormat = "full"
- }
-}
-
-sourceSets {
- main.resources.srcDir 'src/main/java'
- test.resources.srcDir 'src/test/java'
-}
-
-task copyXsd(type: Copy) {
- from sourceSets.main.resources.srcDirs
- include '**/*.xsd'
-
- into new File(rootProject.buildDir, "repository-xsd")
- eachFile { details ->
- details.path = details.name
- }
-}
-
-// delete the destination folder first
-copyXsd.doFirst {
- File destFolder = file(rootProject.buildDir + "/repository-xsd")
- destFolder.deleteDir()
- destFolder.mkdirs()
-}
-
-// clean up after the copy task which creates empty folders.
-copyXsd.doLast {
- File destFolder = file(rootProject.buildDir + "/repository-xsd/com")
- destFolder.deleteDir()
-}
-
-//packageJavaLib.dependsOn copyXsd
-
-project.ext.pomName = 'Android Tools sdklib'
-project.ext.pomDesc = 'A library to parse and download the Android SDK.'
-
-apply from: "$rootDir/buildSrc/base/publish.gradle"
-apply from: "$rootDir/buildSrc/base/bintray.gradle"
-apply from: "$rootDir/buildSrc/base/javadoc.gradle"
-
diff --git a/base/sdklib/sdklib-base.iml b/base/sdklib/sdklib-base.iml
deleted file mode 100644
index 9a3eb80..0000000
--- a/base/sdklib/sdklib-base.iml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="module" module-name="common" exported="" />
- <orderEntry type="module" module-name="dvlib" exported="" />
- <orderEntry type="module" module-name="layoutlib-api-base" exported="" />
- <orderEntry type="library" exported="" name="http-client" level="project" />
- <orderEntry type="library" exported="" name="commons-compress" level="project" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="library" exported="" name="gson" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/sdklib/sdklib.iml b/base/sdklib/sdklib.iml
deleted file mode 100644
index 9240161..0000000
--- a/base/sdklib/sdklib.iml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/build" />
- </content>
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="inheritedJdk" />
- <orderEntry type="module" module-name="common" exported="" />
- <orderEntry type="module" module-name="dvlib" exported="" />
- <orderEntry type="module" module-name="layoutlib-api" exported="" />
- <orderEntry type="library" exported="" name="http-client" level="project" />
- <orderEntry type="library" exported="" name="commons-compress" level="project" />
- <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
- <orderEntry type="library" exported="" name="gson" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/base/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java b/base/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
deleted file mode 100755
index e4520be..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.base.Splitter;
-
-import java.util.List;
-
-
-/**
- * Helper methods to manipulate hash strings used by {@link IAndroidTarget#hashString()}.
- */
-public abstract class AndroidTargetHash {
-
- /**
- * Prefix used to build hash strings for platform targets
- * @see SdkManager#getTargetFromHashString(String)
- */
- public static final String PLATFORM_HASH_PREFIX = "android-";
-
- /**
- * String to compute hash for add-on targets. <br/>
- * Format is {@code vendor:name:apiVersion}. <br/>
- *
- * <em>Important<em/>: the vendor and name compontents are the display strings, not the
- * newer id strings.
- */
- public static final String ADD_ON_FORMAT = "%s:%s:%s"; //$NON-NLS-1$
-
- /**
- * String used to get a hash to the platform target.
- * This format is compatible with the PlatformPackage.installId().
- */
- static final String PLATFORM_HASH = PLATFORM_HASH_PREFIX + "%s";
-
- /**
- * Returns the hash string for a given platform version.
- *
- * @param version A non-null platform version.
- * @return A non-null hash string uniquely representing this platform target.
- */
- @NonNull
- public static String getPlatformHashString(@NonNull AndroidVersion version) {
- return String.format(AndroidTargetHash.PLATFORM_HASH, version.getApiString());
- }
-
- /**
- * Returns the {@link AndroidVersion} for the given hash string,
- * if it represents a platform. If the hash string represents a preview platform,
- * the returned {@link AndroidVersion} will have an unknown API level (set to 1
- * or a known matching API level.)
- *
- * @param hashString the hash string (e.g. "android-19" or "android-CUPCAKE")
- * or a pure API level for convenience (e.g. "19" instead of the proper "android-19")
- * @return a platform, or null
- */
- @Nullable
- public static AndroidVersion getPlatformVersion(@NonNull String hashString) {
- if (hashString.startsWith(PLATFORM_HASH_PREFIX)) {
- String suffix = hashString.substring(PLATFORM_HASH_PREFIX.length());
- if (!suffix.isEmpty()) {
- if (Character.isDigit(suffix.charAt(0))) {
- try {
- int api = Integer.parseInt(suffix);
- return new AndroidVersion(api, null);
- } catch (NumberFormatException ignore) {}
- } else {
- int api = SdkVersionInfo.getApiByBuildCode(suffix, false);
- if (api < 1) {
- api = 1;
- }
- return new AndroidVersion(api, suffix);
- }
- }
- } else if (!hashString.isEmpty() && Character.isDigit(hashString.charAt(0))) {
- // For convenience, interpret a single integer as the proper "android-NN" form.
- try {
- int api = Integer.parseInt(hashString);
- return new AndroidVersion(api, null);
- } catch (NumberFormatException ignore) {}
- }
-
- return null;
- }
-
- @Nullable
- public static AndroidVersion getAddOnVersion(@NonNull String hashString) {
- List<String> parts = Splitter.on(':').splitToList(hashString);
- if (parts.size() != 3) {
- return null;
- }
-
- String apiLevelPart = parts.get(2);
- try {
- int apiLevel = Integer.parseInt(apiLevelPart);
- return new AndroidVersion(apiLevel, null);
- } catch (NumberFormatException e) {
- return null;
- }
- }
-
- /**
- * Gets the API level from a hash string, either a platform version or add-on version.
- *
- * @see #getAddOnVersion(String)
- * @see #getPlatformVersion(String)
- */
- @Nullable
- public static AndroidVersion getVersionFromHash(@NonNull String hashString) {
- if (isPlatform(hashString)) {
- return getPlatformVersion(hashString);
- } else {
- return getAddOnVersion(hashString);
- }
- }
-
- /**
- * Returns the hash string for a given add-on.
- *
- * @param addonVendorDisplay A non-null vendor. When using an {@link IdDisplay} source,
- * this parameter should be the {@link IdDisplay#getDisplay()}.
- * @param addonNameDisplay A non-null add-on name. When using an {@link IdDisplay} source,
- * this parameter should be the {@link IdDisplay#getDisplay()}.
- * @param version A non-null platform version (the addon's base platform version)
- * @return A non-null hash string uniquely representing this add-on target.
- */
- public static String getAddonHashString(
- @NonNull String addonVendorDisplay,
- @NonNull String addonNameDisplay,
- @NonNull AndroidVersion version) {
- return String.format(ADD_ON_FORMAT,
- addonVendorDisplay,
- addonNameDisplay,
- version.getApiString());
- }
-
- /**
- * Returns the hash string for a given target (add-on or platform.)
- *
- * @param target A non-null target.
- * @return A non-null hash string uniquely representing this target.
- */
- public static String getTargetHashString(@NonNull IAndroidTarget target) {
- if (target.isPlatform()) {
- return getPlatformHashString(target.getVersion());
- } else {
- return getAddonHashString(
- target.getVendor(),
- target.getName(),
- target.getVersion());
- }
- }
-
- /**
- * Given a hash string, indicates whether this is a platform hash string.
- * If not, it's an addon hash string.
- *
- * @param hashString The hash string to test.
- * @return True if this hash string starts by the platform prefix.
- */
- public static boolean isPlatform(@NonNull String hashString) {
- return hashString.startsWith(PLATFORM_HASH_PREFIX);
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java b/base/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
deleted file mode 100644
index 49dffbc..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-import java.util.regex.Pattern;
-
-/**
- * Represents the version of a target or device.
- * <p/>
- * A version is defined by an API level and an optional code name.
- * <ul><li>Release versions of the Android platform are identified by their API level (integer),
- * (technically the code name for release version is "REL" but this class will return
- * <code>null<code> instead.)</li>
- * <li>Preview versions of the platform are identified by a code name. Their API level
- * is usually set to the value of the previous platform.</li></ul>
- * <p/>
- * While this class contains both values, its goal is to abstract them, so that code comparing 2+
- * versions doesn't have to deal with the logic of handle both values.
- * <p/>
- * There are some cases where ones may want to access the values directly. This can be done
- * with {@link #getApiLevel()} and {@link #getCodename()}.
- * <p/>
- * For generic UI display of the API version, {@link #getApiString()} is to be used.
- */
-public final class AndroidVersion implements Comparable<AndroidVersion> {
-
- private final int mApiLevel;
- private final String mCodename;
-
- /** The default AndroidVersion for minSdkVersion and targetSdkVersion if not specified */
- public static final AndroidVersion DEFAULT = new AndroidVersion(1, null);
-
- /**
- * Thrown when an {@link AndroidVersion} object could not be created.
- * @see AndroidVersion#AndroidVersion(Properties)
- */
- public static final class AndroidVersionException extends Exception {
- private static final long serialVersionUID = 1L;
-
- AndroidVersionException(String message, Throwable cause) {
- super(message, cause);
- }
- }
-
- /**
- * Creates an {@link AndroidVersion} with the given api level and codename.
- * Codename should be null for a release version, otherwise it's a preview codename.
- */
- public AndroidVersion(int apiLevel, @Nullable String codename) {
- mApiLevel = apiLevel;
- mCodename = sanitizeCodename(codename);
- }
-
- /**
- * Creates an {@link AndroidVersion} from {@link Properties}, with default values if the
- * {@link Properties} object doesn't contain the expected values.
- * <p/>The {@link Properties} is expected to have been filled with
- * {@link #saveProperties(Properties)}.
- */
- public AndroidVersion(@Nullable Properties properties,
- int defaultApiLevel,
- @Nullable String defaultCodeName) {
- if (properties == null) {
- mApiLevel = defaultApiLevel;
- mCodename = sanitizeCodename(defaultCodeName);
- } else {
- mApiLevel = Integer.parseInt(properties.getProperty(PkgProps.VERSION_API_LEVEL,
- Integer.toString(defaultApiLevel)));
- mCodename = sanitizeCodename(
- properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName));
- }
- }
-
- /**
- * Creates an {@link AndroidVersion} from {@link Properties}. The properties must contain
- * android version information, or an exception will be thrown.
- * @throws AndroidVersionException if no Android version information have been found
- *
- * @see #saveProperties(Properties)
- */
- public AndroidVersion(@NonNull Properties properties) throws AndroidVersionException {
- Exception error = null;
-
- String apiLevel = properties.getProperty(PkgProps.VERSION_API_LEVEL, null/*defaultValue*/);
- if (apiLevel != null) {
- try {
- mApiLevel = Integer.parseInt(apiLevel);
- mCodename = sanitizeCodename(properties.getProperty(PkgProps.VERSION_CODENAME,
- null/*defaultValue*/));
- return;
- } catch (NumberFormatException e) {
- error = e;
- }
- }
-
- // reaching here means the Properties object did not contain the apiLevel which is required.
- throw new AndroidVersionException(PkgProps.VERSION_API_LEVEL + " not found!", error);
- }
-
- /**
- * Creates an {@link AndroidVersion} from a string that may be an integer API
- * level or a string codename.
- * <p/>
- * <Em>Important</em>: An important limitation of this method is that cannot possible
- * recreate the API level integer from a pure string codename. This is only OK to use
- * if the caller can guarantee that only {@link #getApiString()} will be used later.
- * Wrong things will happen if the caller then tries to resolve the numeric
- * {@link #getApiLevel()}.
- *
- * @param apiOrCodename A non-null API integer or a codename in its "ALL_CAPS" format.
- * "REL" is notable not a valid codename.
- * @throws AndroidVersionException if the input isn't a pure integer or doesn't look like
- * a valid string codename.
- */
- public AndroidVersion(@NonNull String apiOrCodename) throws AndroidVersionException {
- int apiLevel = 0;
- String codename = null;
- try {
- apiLevel = Integer.parseInt(apiOrCodename);
- } catch (NumberFormatException ignore) {
- // We don't know the API level. Android platform codenames are all caps.
- // REL is a release-reserved keyword which we can use here.
-
- if (!SdkConstants.CODENAME_RELEASE.equals(apiOrCodename)) {
- if (Pattern.matches("[A-Z_]+", apiOrCodename)) {
- codename = apiOrCodename;
- }
- }
- }
-
- mApiLevel = apiLevel;
- mCodename = sanitizeCodename(codename);
-
- if (mApiLevel <= 0 && codename == null) {
- throw new AndroidVersionException(
- "Invalid android API or codename " + apiOrCodename, //$NON-NLS-1$
- null);
- }
- }
-
- public void saveProperties(@NonNull Properties props) {
- props.setProperty(PkgProps.VERSION_API_LEVEL, Integer.toString(mApiLevel));
- if (mCodename != null) {
- props.setProperty(PkgProps.VERSION_CODENAME, mCodename);
- }
- }
-
- /**
- * Returns the api level as an integer.
- * <p/>For target that are in preview mode, this can be superseded by
- * {@link #getCodename()}.
- * <p/>To display the API level in the UI, use {@link #getApiString()}, which will use the
- * codename if applicable.
- * @see #getCodename()
- * @see #getApiString()
- */
- public int getApiLevel() {
- return mApiLevel;
- }
-
- /**
- * Returns the API level as an integer. If this is a preview platform, it
- * will return the expected final version of the API rather than the current API
- * level. This is the "feature level" as opposed to the "release level" returned by
- * {@link #getApiLevel()} in the sense that it is useful when you want
- * to check the presence of a given feature from an API, and we consider the feature
- * present in preview platforms as well.
- *
- * @return the API level of this version, +1 for preview platforms
- */
- public int getFeatureLevel() {
- //noinspection VariableNotUsedInsideIf
- return mCodename != null ? mApiLevel + 1 : mApiLevel;
- }
-
- /**
- * Returns the version code name if applicable, null otherwise.
- * <p/>If the codename is non null, then the API level should be ignored, and this should be
- * used as a unique identifier of the target instead.
- */
- @Nullable
- public String getCodename() {
- return mCodename;
- }
-
- /**
- * Returns a string representing the API level and/or the code name.
- */
- @NonNull
- public String getApiString() {
- if (mCodename != null) {
- return mCodename;
- }
-
- return Integer.toString(mApiLevel);
- }
-
- /**
- * Returns whether or not the version is a preview version.
- */
- public boolean isPreview() {
- return mCodename != null;
- }
-
- /**
- * Checks whether a device running a version similar to the receiver can run a project compiled
- * for the given <var>version</var>.
- * <p/>
- * Be aware that this is not a perfect test, as other properties could break compatibility
- * despite this method returning true. For a more comprehensive test, see
- * {@link IAndroidTarget#canRunOn(IAndroidTarget)}.
- * <p/>
- * Nevertheless, when testing if an application can run on a device (where there is no
- * access to the list of optional libraries), this method can give a good indication of whether
- * there is a chance the application could run, or if there's a direct incompatibility.
- */
- public boolean canRun(@NonNull AndroidVersion appVersion) {
- // if the application is compiled for a preview version, the device must be running exactly
- // the same.
- if (appVersion.mCodename != null) {
- return appVersion.mCodename.equals(mCodename);
- }
-
- // otherwise, we check the api level (note that a device running a preview version
- // will have the api level of the previous platform).
- return mApiLevel >= appVersion.mApiLevel;
- }
-
- /**
- * Returns <code>true</code> if the AndroidVersion is an API level equals to
- * <var>apiLevel</var>.
- */
- public boolean equals(int apiLevel) {
- return mCodename == null && apiLevel == mApiLevel;
- }
-
- /**
- * Compares the receiver with either an {@link AndroidVersion} object or a {@link String}
- * object.
- * <p/>If <var>obj</var> is a {@link String}, then the method will first check if it's a string
- * representation of a number, in which case it'll compare it to the api level. Otherwise, it'll
- * compare it against the code name.
- * <p/>For all other type of object give as parameter, this method will return
- * <code>false</code>.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof AndroidVersion) {
- AndroidVersion version = (AndroidVersion)obj;
-
- if (mCodename == null) {
- return version.mCodename == null &&
- mApiLevel == version.mApiLevel;
- } else {
- return mCodename.equals(version.mCodename) &&
- mApiLevel == version.mApiLevel;
- }
-
- } else if (obj instanceof String) {
- // if we have a code name, this must match.
- if (mCodename != null) {
- return mCodename.equals(obj);
- }
-
- // else we try to convert to a int and compare to the api level
- try {
- int value = Integer.parseInt((String)obj);
- return value == mApiLevel;
- } catch (NumberFormatException e) {
- // not a number? we'll return false below.
- }
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- if (mCodename != null) {
- return mCodename.hashCode();
- }
-
- // there may be some collisions between the hashcode of the codename and the api level
- // but it's acceptable.
- return mApiLevel;
- }
-
- /**
- * Returns a string with the API Level and optional codename.
- * Useful for debugging.
- * For display purpose, please use {@link #getApiString()} instead.
- */
- @Override
- public String toString() {
- String s = String.format("API %1$d", mApiLevel); //$NON-NLS-1$
- if (isPreview()) {
- s += String.format(", %1$s preview", mCodename); //$NON-NLS-1$
- }
- return s;
- }
-
- /**
- * Compares this object with the specified object for order. Returns a
- * negative integer, zero, or a positive integer as this object is less
- * than, equal to, or greater than the specified object.
- *
- * @param o the Object to be compared.
- * @return a negative integer, zero, or a positive integer as this object is
- * less than, equal to, or greater than the specified object.
- */
- @Override
- public int compareTo(@NonNull AndroidVersion o) {
- return compareTo(o.mApiLevel, o.mCodename);
- }
-
- public int compareTo(int apiLevel, @Nullable String codename) {
- if (mCodename == null) {
- if (codename == null) {
- return mApiLevel - apiLevel;
- } else {
- if (mApiLevel == apiLevel) {
- return -1; // same api level but argument is a preview for next version
- }
-
- return mApiLevel - apiLevel;
- }
- } else {
- // 'this' is a preview
- if (mApiLevel == apiLevel) {
- if (codename == null) {
- return +1;
- } else {
- return mCodename.compareTo(codename); // strange case where the 2 previews
- // have different codename?
- }
- } else {
- return mApiLevel - apiLevel;
- }
- }
- }
-
- /**
- * Compares this version with the specified API and returns true if this version
- * is greater or equal than the requested API -- that is the current version is a
- * suitable min-api-level for the argument API.
- */
- public boolean isGreaterOrEqualThan(int api) {
- return compareTo(api, null /*codename*/) >= 0;
- }
-
- /**
- * Sanitizes the codename string according to the following rules:
- * - A codename should be {@code null} for a release version or it should be a non-empty
- * string for an actual preview.
- * - In input, spacing is trimmed since it is irrelevant.
- * - An empty string or the special codename "REL" means a release version
- * and is converted to {@code null}.
- *
- * @param codename A possible-null codename.
- * @return Null for a release version or a non-empty codename.
- */
- @Nullable
- private static String sanitizeCodename(@Nullable String codename) {
- if (codename != null) {
- codename = codename.trim();
- if (codename.isEmpty() || SdkConstants.CODENAME_RELEASE.equals(codename)) {
- codename = null;
- }
- }
- return codename;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java b/base/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
deleted file mode 100755
index df913ff..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import static com.android.SdkConstants.FD_LIB;
-import static com.android.SdkConstants.FN_AAPT;
-import static com.android.SdkConstants.FN_AIDL;
-import static com.android.SdkConstants.FN_BCC_COMPAT;
-import static com.android.SdkConstants.FN_DEXDUMP;
-import static com.android.SdkConstants.FN_DX;
-import static com.android.SdkConstants.FN_DX_JAR;
-import static com.android.SdkConstants.FN_JACK;
-import static com.android.SdkConstants.FN_JILL;
-import static com.android.SdkConstants.FN_LD_ARM;
-import static com.android.SdkConstants.FN_LD_MIPS;
-import static com.android.SdkConstants.FN_LD_X86;
-import static com.android.SdkConstants.FN_RENDERSCRIPT;
-import static com.android.SdkConstants.FN_SPLIT_SELECT;
-import static com.android.SdkConstants.FN_ZIPALIGN;
-import static com.android.SdkConstants.OS_FRAMEWORK_RS;
-import static com.android.SdkConstants.OS_FRAMEWORK_RS_CLANG;
-import static com.android.sdklib.BuildToolInfo.PathId.AAPT;
-import static com.android.sdklib.BuildToolInfo.PathId.AIDL;
-import static com.android.sdklib.BuildToolInfo.PathId.ANDROID_RS;
-import static com.android.sdklib.BuildToolInfo.PathId.ANDROID_RS_CLANG;
-import static com.android.sdklib.BuildToolInfo.PathId.BCC_COMPAT;
-import static com.android.sdklib.BuildToolInfo.PathId.DEXDUMP;
-import static com.android.sdklib.BuildToolInfo.PathId.DX;
-import static com.android.sdklib.BuildToolInfo.PathId.DX_JAR;
-import static com.android.sdklib.BuildToolInfo.PathId.JACK;
-import static com.android.sdklib.BuildToolInfo.PathId.JILL;
-import static com.android.sdklib.BuildToolInfo.PathId.LD_ARM;
-import static com.android.sdklib.BuildToolInfo.PathId.LD_MIPS;
-import static com.android.sdklib.BuildToolInfo.PathId.LD_X86;
-import static com.android.sdklib.BuildToolInfo.PathId.LLVM_RS_CC;
-import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT;
-import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.utils.ILogger;
-import com.google.common.collect.Maps;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Information on a specific build-tool folder.
- * <p/>
- * For unit tests, see:
- * - sdklib/src/test/.../LocalSdkTest
- * - sdklib/src/test/.../SdkManagerTest
- * - sdklib/src/test/.../BuildToolInfoTest
- */
-public class BuildToolInfo {
-
- /** Name of the file read by {@link #getRuntimeProps()} */
- private static final String FN_RUNTIME_PROPS = "runtime.properties";
-
- /**
- * Property in {@link #FN_RUNTIME_PROPS} indicating the desired runtime JVM.
- * Type: {@link NoPreviewRevision#toShortString()}, e.g. "1.7.0"
- */
- private static final String PROP_RUNTIME_JVM = "Runtime.Jvm";
-
- /**
- * First version with native multi-dex support.
- */
- public static final int SDK_LEVEL_FOR_MULTIDEX_NATIVE_SUPPORT = 21;
-
- public enum PathId {
- /** OS Path to the target's version of the aapt tool. */
- AAPT("1.0.0"),
- /** OS Path to the target's version of the aidl tool. */
- AIDL("1.0.0"),
- /** OS Path to the target's version of the dx tool. */
- DX("1.0.0"),
- /** OS Path to the target's version of the dx.jar file. */
- DX_JAR("1.0.0"),
- /** OS Path to the llvm-rs-cc binary for Renderscript. */
- LLVM_RS_CC("1.0.0"),
- /** OS Path to the Renderscript include folder. */
- ANDROID_RS("1.0.0"),
- /** OS Path to the Renderscript(clang) include folder. */
- ANDROID_RS_CLANG("1.0.0"),
-
- DEXDUMP("1.0.0"),
-
- // --- NEW IN 18.1.0 ---
-
- /** OS Path to the bcc_compat tool. */
- BCC_COMPAT("18.1.0"),
- /** OS Path to the ARM linker. */
- LD_ARM("18.1.0"),
- /** OS Path to the X86 linker. */
- LD_X86("18.1.0"),
- /** OS Path to the MIPS linker. */
- LD_MIPS("18.1.0"),
-
- // --- NEW IN 19.1.0 ---
- ZIP_ALIGN("19.1.0"),
-
- // --- NEW IN 21.x.y ---
- JACK("21.1.0"),
- JILL("21.1.0"),
-
- SPLIT_SELECT("22.0.0");
-
- /**
- * min revision this element was introduced.
- * Controls {@link BuildToolInfo#isValid(ILogger)}
- */
- private final FullRevision mMinRevision;
-
- /**
- * Creates the enum with a min revision in which this
- * tools appeared in the build tools.
- *
- * @param minRevision the min revision.
- */
- PathId(@NonNull String minRevision) {
- mMinRevision = FullRevision.parseRevision(minRevision);
- }
-
- /**
- * Returns whether the enum of present in a given rev of the build tools.
- *
- * @param fullRevision the build tools revision.
- * @return true if the tool is present.
- */
- boolean isPresentIn(@NonNull FullRevision fullRevision) {
- return fullRevision.compareTo(mMinRevision) >= 0;
- }
- }
-
- /** The build-tool revision. */
- @NonNull
- private final FullRevision mRevision;
-
- /** The path to the build-tool folder specific to this revision. */
- @NonNull
- private final File mPath;
-
- private final Map<PathId, String> mPaths = Maps.newEnumMap(PathId.class);
-
- public BuildToolInfo(@NonNull FullRevision revision, @NonNull File path) {
- mRevision = revision;
- mPath = path;
-
- add(AAPT, FN_AAPT);
- add(AIDL, FN_AIDL);
- add(DX, FN_DX);
- add(DX_JAR, FD_LIB + File.separator + FN_DX_JAR);
- add(LLVM_RS_CC, FN_RENDERSCRIPT);
- add(ANDROID_RS, OS_FRAMEWORK_RS);
- add(ANDROID_RS_CLANG, OS_FRAMEWORK_RS_CLANG);
- add(DEXDUMP, FN_DEXDUMP);
- add(BCC_COMPAT, FN_BCC_COMPAT);
- add(LD_ARM, FN_LD_ARM);
- add(LD_X86, FN_LD_X86);
- add(LD_MIPS, FN_LD_MIPS);
- add(ZIP_ALIGN, FN_ZIPALIGN);
- add(JACK, FN_JACK);
- add(JILL, FN_JILL);
- add(SPLIT_SELECT, FN_SPLIT_SELECT);
- }
-
- public BuildToolInfo(
- @NonNull FullRevision revision,
- @NonNull File mainPath,
- @NonNull File aapt,
- @NonNull File aidl,
- @NonNull File dx,
- @NonNull File dxJar,
- @NonNull File llmvRsCc,
- @NonNull File androidRs,
- @NonNull File androidRsClang,
- @Nullable File bccCompat,
- @Nullable File ldArm,
- @Nullable File ldX86,
- @Nullable File ldMips,
- @NonNull File zipAlign) {
- mRevision = revision;
- mPath = mainPath;
- add(AAPT, aapt);
- add(AIDL, aidl);
- add(DX, dx);
- add(DX_JAR, dxJar);
- add(LLVM_RS_CC, llmvRsCc);
- add(ANDROID_RS, androidRs);
- add(ANDROID_RS_CLANG, androidRsClang);
- add(ZIP_ALIGN, zipAlign);
-
- if (bccCompat != null) {
- add(BCC_COMPAT, bccCompat);
- } else if (BCC_COMPAT.isPresentIn(revision)) {
- throw new IllegalArgumentException("BCC_COMPAT required in " + revision.toString());
- }
- if (ldArm != null) {
- add(LD_ARM, ldArm);
- } else if (LD_ARM.isPresentIn(revision)) {
- throw new IllegalArgumentException("LD_ARM required in " + revision.toString());
- }
-
- if (ldX86 != null) {
- add(LD_X86, ldX86);
- } else if (LD_X86.isPresentIn(revision)) {
- throw new IllegalArgumentException("LD_X86 required in " + revision.toString());
- }
-
- if (ldMips != null) {
- add(LD_MIPS, ldMips);
- } else if (LD_MIPS.isPresentIn(revision)) {
- throw new IllegalArgumentException("LD_MIPS required in " + revision.toString());
- }
- }
-
- private void add(PathId id, String leaf) {
- add(id, new File(mPath, leaf));
- }
-
- private void add(PathId id, File path) {
- String str = path.getAbsolutePath();
- if (path.isDirectory() && str.charAt(str.length() - 1) != File.separatorChar) {
- str += File.separatorChar;
- }
- mPaths.put(id, str);
- }
-
- /**
- * Returns the revision.
- */
- @NonNull
- public FullRevision getRevision() {
- return mRevision;
- }
-
- /**
- * Returns the build-tool revision-specific folder.
- * <p/>
- * For compatibility reasons, use {@link #getPath(PathId)} if you need the path to a
- * specific tool.
- */
- @NonNull
- public File getLocation() {
- return mPath;
- }
-
- /**
- * Returns the path of a build-tool component.
- *
- * @param pathId the id representing the path to return.
- * @return The absolute path for that tool, with a / separator if it's a folder.
- * Null if the path-id is unknown.
- */
- public String getPath(PathId pathId) {
- assert pathId.isPresentIn(mRevision);
-
- return mPaths.get(pathId);
- }
-
- /**
- * Checks whether the build-tool is valid by verifying that the expected binaries
- * are actually present. This checks that all known paths point to a valid file
- * or directory.
- *
- * @param log An optional logger. If non-null, errors will be printed there.
- * @return True if the build-tool folder contains all the expected tools.
- */
- public boolean isValid(@Nullable ILogger log) {
- for (Map.Entry<PathId, String> entry : mPaths.entrySet()) {
- File f = new File(entry.getValue());
- // check if file is missing. It's only ok if the revision of the build-tools
- // is lower than the min rev of the element.
- if (!f.exists() && entry.getKey().isPresentIn(mRevision)) {
- if (log != null) {
- log.warning("Build-tool %1$s is missing %2$s at %3$s", //$NON-NLS-1$
- mRevision.toString(),
- entry.getKey(), f.getAbsolutePath());
- }
- return false;
- }
- }
- return true;
- }
-
- /**
- * Parses the build-tools runtime.props file, if present.
- *
- * @return The properties from runtime.props if present, otherwise an empty properties set.
- */
- @NonNull
- public Properties getRuntimeProps() {
- FileOp fop = new FileOp();
- return fop.loadProperties(new File(mPath, FN_RUNTIME_PROPS));
- }
-
- /**
- * Checks whether this build-tools package can run on the current JVM.
- *
- * @return True if the build-tools package has a Runtime.Jvm property and it is lesser or
- * equal to the current JVM version.
- * False if the property is present and the requirement is not met.
- * True if there's an error parsing either versions and the comparison cannot be made.
- */
- public boolean canRunOnJvm() {
- Properties props = getRuntimeProps();
- String required = props.getProperty(PROP_RUNTIME_JVM);
- if (required == null) {
- // No requirement ==> accepts.
- return true;
- }
- try {
- NoPreviewRevision requiredVersion = NoPreviewRevision.parseRevision(required);
- NoPreviewRevision currentVersion = getCurrentJvmVersion();
- return currentVersion.compareTo(requiredVersion) >= 0;
-
- } catch (NumberFormatException ignore) {
- // Either we failed to parse the property version or the running JVM version.
- // Right now take the relaxed policy of accepting it if we can't compare.
- return true;
- }
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- @Nullable
- protected NoPreviewRevision getCurrentJvmVersion() throws NumberFormatException {
- String javav = System.getProperty("java.version"); //$NON-NLS-1$
- // java Version is typically in the form "1.2.3_45" and we just need to keep up to "1.2.3"
- // since our revision numbers are in 3-parts form (1.2.3).
- Pattern p = Pattern.compile("((\\d+)(\\.\\d+)?(\\.\\d+)?).*"); //$NON-NLS-1$
- Matcher m = p.matcher(javav);
- if (m.matches()) {
- return NoPreviewRevision.parseRevision(m.group(1));
- }
- return null;
- }
-
- /**
- * Returns a debug representation suitable for unit-tests.
- * Note that unit-tests need to clean up the paths to avoid inconsistent results.
- */
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("<BuildToolInfo rev=").append(mRevision); //$NON-NLS-1$
- builder.append(", mPath=").append(mPath); //$NON-NLS-1$
- builder.append(", mPaths=").append(getPathString()); //$NON-NLS-1$
- builder.append(">"); //$NON-NLS-1$
- return builder.toString();
- }
-
- private String getPathString() {
- StringBuilder sb = new StringBuilder("{");
-
- for (Map.Entry<PathId, String> entry : mPaths.entrySet()) {
- if (entry.getKey().isPresentIn(mRevision)) {
- if (sb.length() > 1) {
- sb.append(", ");
- }
- sb.append(entry.getKey()).append('=').append(entry.getValue());
- }
- }
-
- sb.append('}');
-
- return sb.toString();
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java b/base/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java
deleted file mode 100644
index 6606bc8..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-
-
-/**
- * A version of Android that applications can target when building.
- */
-public interface IAndroidTarget extends Comparable<IAndroidTarget> {
-
- /** OS Path to the "android.jar" file. */
- int ANDROID_JAR = 1;
- /** OS Path to the "framework.aidl" file. */
- int ANDROID_AIDL = 2;
- /** OS Path to the "samples" folder which contains sample projects. */
- int SAMPLES = 4;
- /** OS Path to the "skins" folder which contains the emulator skins. */
- int SKINS = 5;
- /** OS Path to the "templates" folder which contains the templates for new projects. */
- int TEMPLATES = 6;
- /** OS Path to the "data" folder which contains data & libraries for the SDK tools. */
- int DATA = 7;
- /** OS Path to the "attrs.xml" file. */
- int ATTRIBUTES = 8;
- /** OS Path to the "attrs_manifest.xml" file. */
- int MANIFEST_ATTRIBUTES = 9;
- /** OS Path to the "data/layoutlib.jar" library. */
- int LAYOUT_LIB = 10;
- /** OS Path to the "data/res" folder. */
- int RESOURCES = 11;
- /** OS Path to the "data/fonts" folder. */
- int FONTS = 12;
- /** OS Path to the "data/widgets.txt" file. */
- int WIDGETS = 13;
- /** OS Path to the "data/activity_actions.txt" file. */
- int ACTIONS_ACTIVITY = 14;
- /** OS Path to the "data/broadcast_actions.txt" file. */
- int ACTIONS_BROADCAST = 15;
- /** OS Path to the "data/service_actions.txt" file. */
- int ACTIONS_SERVICE = 16;
- /** OS Path to the "data/categories.txt" file. */
- int CATEGORIES = 17;
- /** OS Path to the "sources" folder. */
- int SOURCES = 18;
- /** OS Path to the target specific docs */
- int DOCS = 19;
- /** OS Path to the "ant" folder which contains the ant build rules (ver 2 and above) */
- int ANT = 24;
- /** OS Path to the "uiautomator.jar" file. */
- int UI_AUTOMATOR_JAR = 27;
-
-
- /**
- * Return value for {@link #getUsbVendorId()} meaning no USB vendor IDs are defined by the
- * Android target.
- */
- int NO_USB_ID = 0;
-
- /** An optional library provided by an Android Target */
- interface OptionalLibrary {
- /** The name of the library, as used in the manifest (<uses-library>). */
- @NonNull
- String getName();
- /** Location of the jar file. */
- @NonNull
- File getJar();
- /** Description of the library. */
- @NonNull
- String getDescription();
- /** Whether the library requires a manifest entry */
- boolean isManifestEntryRequired();
- }
-
- /**
- * Returns the target location.
- */
- String getLocation();
-
- /**
- * Returns the name of the vendor of the target.
- */
- String getVendor();
-
- /**
- * Returns the name of the target.
- */
- String getName();
-
- /**
- * Returns the full name of the target, possibly including vendor name.
- */
- String getFullName();
-
- /**
- * Returns the name to be displayed when representing all the libraries this target contains.
- */
- String getClasspathName();
-
- /**
- * Returns the name to be displayed when representing all the libraries this target contains.
- */
- String getShortClasspathName();
-
- /**
- * Returns the description of the target.
- */
- String getDescription();
-
- /**
- * Returns the version of the target. This is guaranteed to be non-null.
- */
- @NonNull
- AndroidVersion getVersion();
-
- /**
- * Returns the platform version as a readable string.
- */
- String getVersionName();
-
- /** Returns the revision number for the target. */
- int getRevision();
-
- /**
- * Returns true if the target is a standard Android platform.
- */
- boolean isPlatform();
-
- /**
- * Returns the parent target. This is likely to only be non <code>null</code> if
- * {@link #isPlatform()} returns <code>false</code>
- */
- IAndroidTarget getParent();
-
- /**
- * Returns the path of a platform component.
- * @param pathId the id representing the path to return.
- * Any of the constants defined in the {@link IAndroidTarget} interface can be used.
- */
- String getPath(int pathId);
-
- /**
- * Returns the path of a platform component.
- * <p/>
- * This is like the legacy {@link #getPath(int)} method except it returns a {@link File}.
- *
- * @param pathId the id representing the path to return.
- * Any of the constants defined in the {@link IAndroidTarget} interface can be used.
- */
- File getFile(int pathId);
-
- /**
- * Returns a BuildToolInfo for backward compatibility. If an older SDK is used this will return
- * paths located in the platform-tools, otherwise it'll return paths located in the latest
- * build-tools.
- * @return a BuildToolInfo or null if none are available.
- */
- BuildToolInfo getBuildToolInfo();
-
- /**
- * Returns the boot classpath for this target.
- * In most case, this is similar to calling {@link #getPath(int)} with
- * {@link IAndroidTarget#ANDROID_JAR}.
- *
- * @return a non null list of the boot classpath.
- */
- @NonNull
- List<String> getBootClasspath();
-
- /**
- * Returns a list of optional libraries for this target.
- *
- * These libraries are not automatically added to the classpath.
- * Using them requires adding a <code>uses-library</code> entry in the manifest.
- *
- * @return a list of libraries.
- *
- * @see OptionalLibrary#getName()
- */
- @NonNull
- List<OptionalLibrary> getOptionalLibraries();
-
- /**
- * Returns the additional libraries for this target.
- *
- * These libraries are automatically added to the classpath, but using them requires
- * adding a <code>uses-library</code> entry in the manifest.
- *
- * @return a list of libraries.
- *
- * @see OptionalLibrary#getName()
- */
- @NonNull
- List<OptionalLibrary> getAdditionalLibraries();
-
- /**
- * Returns whether the target is able to render layouts.
- */
- boolean hasRenderingLibrary();
-
- /**
- * Returns the available skin folders for this target.
- * <p/>
- * To get the skin names, use {@link File#getName()}. <br/>
- * Skins come either from:
- * <ul>
- * <li>a platform ({@code sdk/platforms/N/skins/name})</li>
- * <li>an add-on ({@code sdk/addons/name/skins/name})</li>
- * <li>a tagged system-image ({@code sdk/system-images/platform-N/tag/abi/skins/name}.)</li>
- * </ul>
- * The array can be empty but not null.
- */
- @NonNull
- File[] getSkins();
-
- /**
- * Returns the default skin folder for this target.
- * <p/>
- * To get the skin name, use {@link File#getName()}.
- */
- @Nullable
- File getDefaultSkin();
-
- /**
- * Returns the list of libraries available for a given platform.
- *
- * @return an array of libraries provided by the platform or <code>null</code> if there is none.
- */
- String[] getPlatformLibraries();
-
- /**
- * Return the value of a given property for this target.
- * @return the property value or <code>null</code> if it was not found.
- */
- String getProperty(String name);
-
- /**
- * Returns the value of a given property for this target as an Integer value.
- * <p/> If the value is missing or is not an integer, the method will return the given default
- * value.
- * @param name the name of the property to return
- * @param defaultValue the default value to return.
- *
- * @see Integer#decode(String)
- */
- Integer getProperty(String name, Integer defaultValue);
-
- /**
- * Returns the value of a given property for this target as a Boolean value.
- * <p/> If the value is missing or is not an boolean, the method will return the given default
- * value.
- *
- * @param name the name of the property to return
- * @param defaultValue the default value to return.
- *
- * @see Boolean#valueOf(String)
- */
-
- Boolean getProperty(String name, Boolean defaultValue);
-
- /**
- * Returns all the properties associated with this target. This can be null if the target has
- * no properties.
- */
- Map<String, String> getProperties();
-
- /**
- * Returns the USB Vendor ID for the vendor of this target.
- * <p/>If the target defines no USB Vendor ID, then the method return 0.
- */
- int getUsbVendorId();
-
- /**
- * Returns an array of system images for this target.
- * The array can be empty but not null.
- */
- ISystemImage[] getSystemImages();
-
- /**
- * Returns the system image information for the given {@code tag} and {@code abiType}.
- *
- * @param tag A tag id-display.
- * @param abiType An ABI type string.
- * @return An existing {@link ISystemImage} for the requested {@code abiType}
- * or null if none exists for this type.
- */
- @Nullable
- ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType);
-
- /**
- * Returns whether the given target is compatible with the receiver.
- * <p/>
- * This means that a project using the receiver's target can run on the given target.
- * <br/>
- * Example:
- * <pre>
- * CupcakeTarget.canRunOn(DonutTarget) == true
- * </pre>.
- *
- * @param target the IAndroidTarget to test.
- */
- boolean canRunOn(IAndroidTarget target);
-
- /**
- * Returns a string able to uniquely identify a target.
- * Typically the target will encode information such as api level, whether it's a platform
- * or add-on, and if it's an add-on vendor and add-on name.
- * <p/>
- * See {@link AndroidTargetHash} for helper methods to manipulate hash strings.
- */
- String hashString();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/ISystemImage.java b/base/sdklib/src/main/java/com/android/sdklib/ISystemImage.java
deleted file mode 100755
index b39a908..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/ISystemImage.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.devices.Abi;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-
-import java.io.File;
-
-
-/**
- * Describes a system image as used by an {@link IAndroidTarget}.
- * A system image has an installation path, a location type and an ABI type.
- */
-public interface ISystemImage extends Comparable<ISystemImage> {
-
- /** Indicates the type of location for the system image folder in the SDK. */
- enum LocationType {
- /**
- * The system image is located in the legacy platform's {@link SdkConstants#FD_IMAGES}
- * folder.
- * <p/>
- * Used by both platform and add-ons.
- */
- IN_LEGACY_FOLDER,
-
- /**
- * The system image is located in a sub-directory of the platform's
- * {@link SdkConstants#FD_IMAGES} folder, allowing for multiple system
- * images within the platform.
- * <p/>
- * Used by both platform and add-ons.
- */
- IN_IMAGES_SUBFOLDER,
-
- /**
- * The system image is located in the new SDK's {@link SdkConstants#FD_SYSTEM_IMAGES}
- * folder. Supported as of Tools R14 and Repository XSD version 5.
- * <p/>
- * Used <em>only</em> by both platform up to Tools R22.6.
- * Supported for add-ons as of Tools R22.8.
- */
- IN_SYSTEM_IMAGE,
- }
-
- /** Returns the actual location of an installed system image. */
- @NonNull
- File getLocation();
-
- /** Indicates the location strategy for this system image in the SDK. */
- @NonNull
- LocationType getLocationType();
-
- /** Returns the tag of the system image. */
- @NonNull
- IdDisplay getTag();
-
- /** Returns the vendor for an add-on's system image, or null for a platform system-image. */
- @Nullable
- IdDisplay getAddonVendor();
-
- /**
- * Returns the ABI type.
- * See {@link Abi} for a full list.
- * Cannot be null nor empty.
- */
- @NonNull
- String getAbiType();
-
- /**
- * Returns the skins embedded in the system image. <br/>
- * Only supported by system images using {@link LocationType#IN_SYSTEM_IMAGE}. <br/>
- * The skins listed here are merged in the {@link IAndroidTarget#getSkins()} list.
- * @return A non-null skin list, possibly empty.
- */
- @NonNull
- File[] getSkins();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/SdkManager.java b/base/sdklib/src/main/java/com/android/sdklib/SdkManager.java
deleted file mode 100755
index 91b5703..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/SdkManager.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.internal.androidTarget.AddOnTarget;
-import com.android.sdklib.internal.androidTarget.PlatformTarget;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgType;
-import com.android.sdklib.repository.local.LocalExtraPkgInfo;
-import com.android.sdklib.repository.local.LocalPkgInfo;
-import com.android.sdklib.repository.local.LocalSdk;
-import com.android.utils.ILogger;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-/**
- * The SDK manager parses the SDK folder and gives access to the content.
- * @see PlatformTarget
- * @see AddOnTarget
- */
-public class SdkManager {
-
- @SuppressWarnings("unused")
- private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG") != null; //$NON-NLS-1$
-
- /** Preference file containing the usb ids for adb */
- private static final String ADB_INI_FILE = "adb_usb.ini"; //$NON-NLS-1$
- //0--------90--------90--------90--------90--------90--------90--------90--------9
- private static final String ADB_INI_HEADER =
- "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" + //$NON-NLS-1$
- "# USE 'android update adb' TO GENERATE.\n" + //$NON-NLS-1$
- "# 1 USB VENDOR ID PER LINE.\n"; //$NON-NLS-1$
-
- /** Embedded reference to the new local SDK object. */
- private final LocalSdk mLocalSdk;
-
- /**
- * Create a new {@link SdkManager} instance.
- * External users should use {@link #createManager(String, ILogger)}.
- *
- * @param osSdkPath the location of the SDK.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SdkManager(@NonNull String osSdkPath) {
- mLocalSdk = new LocalSdk(new File(osSdkPath));
- }
-
- /**
- * Creates an @{linkplain SdkManager} for an existing @{link LocalSdk}.
- *
- * @param localSdk the SDK to use with the SDK manager
- */
- private SdkManager(@NonNull LocalSdk localSdk) {
- mLocalSdk = localSdk;
- }
-
- /**
- * Creates an {@link SdkManager} for a given sdk location.
- * @param osSdkPath the location of the SDK.
- * @param log the ILogger object receiving warning/error from the parsing.
- * @return the created {@link SdkManager} or null if the location is not valid.
- */
- @Nullable
- public static SdkManager createManager(
- @NonNull String osSdkPath,
- @NonNull ILogger log) {
- try {
- SdkManager manager = new SdkManager(osSdkPath);
- manager.reloadSdk(log);
-
- return manager;
- } catch (Throwable throwable) {
- log.error(throwable, "Error parsing the sdk.");
- }
-
- return null;
- }
-
- /**
- * Creates an @{linkplain SdkManager} for an existing @{link LocalSdk}.
- *
- * @param localSdk the SDK to use with the SDK manager
- */
- @NonNull
- public static SdkManager createManager(@NonNull LocalSdk localSdk) {
- return new SdkManager(localSdk);
- }
-
- @NonNull
- public LocalSdk getLocalSdk() {
- return mLocalSdk;
- }
-
- /**
- * Reloads the content of the SDK.
- *
- * @param log the ILogger object receiving warning/error from the parsing.
- */
- public void reloadSdk(@NonNull ILogger log) {
- mLocalSdk.clearLocalPkg(PkgType.PKG_ALL);
- }
-
- /**
- * Checks whether any of the SDK platforms/add-ons/build-tools have changed on-disk
- * since we last loaded the SDK. This does not reload the SDK nor does it
- * change the underlying targets.
- *
- * @return True if at least one directory or source.prop has changed.
- */
- public boolean hasChanged() {
- return hasChanged(null);
- }
-
- /**
- * Checks whether any of the SDK platforms/add-ons/build-tools have changed on-disk
- * since we last loaded the SDK. This does not reload the SDK nor does it
- * change the underlying targets.
- *
- * @param log An optional logger used to print verbose info on what changed. Can be null.
- * @return True if at least one directory or source.prop has changed.
- */
- public boolean hasChanged(@Nullable ILogger log) {
- return mLocalSdk.hasChanged(EnumSet.of(PkgType.PKG_PLATFORM,
- PkgType.PKG_ADDON,
- PkgType.PKG_BUILD_TOOLS));
- }
-
- /**
- * Returns the location of the SDK.
- */
- @NonNull
- public String getLocation() {
- File f = mLocalSdk.getLocation();
- // Our LocalSdk is created with a file path, so we know the location won't be null.
- assert f != null;
- return f.getPath();
- }
-
- /**
- * Returns the targets (platforms & addons) that are available in the SDK.
- * The target list is created on demand the first time then cached.
- * It will not refreshed unless {@link #reloadSdk(ILogger)} is called.
- * <p/>
- * The array can be empty but not null.
- */
- @NonNull
- public IAndroidTarget[] getTargets() {
- return mLocalSdk.getTargets();
- }
-
- /**
- * Returns an unmodifiable set of known build-tools revisions. Can be empty but not null.
- * Deprecated. I don't think anything uses this.
- */
- @Deprecated
- @NonNull
- public Set<FullRevision> getBuildTools() {
- LocalPkgInfo[] pkgs = mLocalSdk.getPkgsInfos(PkgType.PKG_BUILD_TOOLS);
- TreeSet<FullRevision> bt = new TreeSet<FullRevision>();
- for (LocalPkgInfo pkg : pkgs) {
- IPkgDesc d = pkg.getDesc();
- if (d.hasFullRevision()) {
- bt.add(d.getFullRevision());
- }
- }
- return Collections.unmodifiableSet(bt);
- }
-
- /**
- * Returns the highest build-tool revision known. Can be null.
- *
- * @return The highest build-tool revision known, or null.
- */
- @Nullable
- public BuildToolInfo getLatestBuildTool() {
- return mLocalSdk.getLatestBuildTool();
- }
-
- /**
- * Returns the {@link BuildToolInfo} for the given revision.
- *
- * @param revision The requested revision.
- * @return A {@link BuildToolInfo}. Can be null if {@code revision} is null or is
- * not part of the known set returned by {@link #getBuildTools()}.
- */
- @Nullable
- public BuildToolInfo getBuildTool(@Nullable FullRevision revision) {
- return mLocalSdk.getBuildTool(revision);
- }
-
- /**
- * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
- *
- * @param hash the {@link IAndroidTarget} hash string.
- * @return The matching {@link IAndroidTarget} or null.
- */
- @Nullable
- public IAndroidTarget getTargetFromHashString(@Nullable String hash) {
- return mLocalSdk.getTargetFromHashString(hash);
- }
-
- /**
- * Updates adb with the USB devices declared in the SDK add-ons.
- * @throws AndroidLocationException
- * @throws IOException
- */
- public void updateAdb() throws AndroidLocationException, IOException {
- FileWriter writer = null;
- try {
- // get the android prefs location to know where to write the file.
- File adbIni = new File(AndroidLocation.getFolder(), ADB_INI_FILE);
- writer = new FileWriter(adbIni);
-
- // first, put all the vendor id in an HashSet to remove duplicate.
- HashSet<Integer> set = new HashSet<Integer>();
- IAndroidTarget[] targets = getTargets();
- for (IAndroidTarget target : targets) {
- if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
- set.add(target.getUsbVendorId());
- }
- }
-
- // write file header.
- writer.write(ADB_INI_HEADER);
-
- // now write the Id in a text file, one per line.
- for (Integer i : set) {
- writer.write(String.format("0x%04x\n", i)); //$NON-NLS-1$
- }
- } finally {
- if (writer != null) {
- writer.close();
- }
- }
- }
-
- /**
- * Returns the greatest {@link LayoutlibVersion} found amongst all platform
- * targets currently loaded in the SDK.
- * <p/>
- * We only started recording Layoutlib Versions recently in the platform meta data
- * so it's possible to have an SDK with many platforms loaded but no layoutlib
- * version defined.
- *
- * @return The greatest {@link LayoutlibVersion} or null if none is found.
- * @deprecated This does NOT solve the right problem and will be changed later.
- */
- @Deprecated
- @Nullable
- public LayoutlibVersion getMaxLayoutlibVersion() {
- LayoutlibVersion maxVersion = null;
-
- for (IAndroidTarget target : getTargets()) {
- if (target instanceof PlatformTarget) {
- LayoutlibVersion lv = ((PlatformTarget) target).getLayoutlibVersion();
- if (lv != null) {
- if (maxVersion == null || lv.compareTo(maxVersion) > 0) {
- maxVersion = lv;
- }
- }
- }
- }
-
- return maxVersion;
- }
-
- /**
- * Returns a map of the <em>root samples directories</em> located in the SDK/extras packages.
- * No guarantee is made that the extras' samples directory actually contain any valid samples.
- * The only guarantee is that the root samples directory actually exists.
- * The map is { File: Samples root directory => String: Extra package display name. }
- *
- * @return A non-null possibly empty map of extra samples directories and their associated
- * extra package display name.
- */
- @NonNull
- public Map<File, String> getExtraSamples() {
-
- LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(PkgType.PKG_EXTRA);
- Map<File, String> samples = new HashMap<File, String>();
-
- for (LocalPkgInfo info : pkgsInfos) {
- assert info instanceof LocalExtraPkgInfo;
-
- File root = info.getLocalDir();
- File path = new File(root, SdkConstants.FD_SAMPLES);
- if (path.isDirectory()) {
- samples.put(path, info.getListDescription());
- continue;
- }
- // Some old-style extras simply have a single "sample" directory.
- // Accept it if it contains an AndroidManifest.xml.
- path = new File(root, SdkConstants.FD_SAMPLE);
- if (path.isDirectory() &&
- new File(path, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) {
- samples.put(path, info.getListDescription());
- }
- }
-
- return samples;
- }
-
- /**
- * Returns a map of all the extras found in the <em>local</em> SDK with their major revision.
- * <p/>
- * Map keys are in the form "vendor-id/path-id". These ids uniquely identify an extra package.
- * The version is the incremental integer major revision of the package.
- *
- * @return A non-null possibly empty map of { string "vendor/path" => integer major revision }
- * @deprecated Starting with add-on schema 6, extras can have full revisions instead of just
- * major revisions. This API only returns the major revision. Callers should be modified
- * to use the new {code LocalSdk.getPkgInfo(PkgType.PKG_EXTRAS)} API instead.
- */
- @Deprecated
- @NonNull
- public Map<String, Integer> getExtrasVersions() {
- LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(PkgType.PKG_EXTRA);
- Map<String, Integer> extraVersions = new TreeMap<String, Integer>();
-
- for (LocalPkgInfo info : pkgsInfos) {
- assert info instanceof LocalExtraPkgInfo;
- if (info instanceof LocalExtraPkgInfo) {
- LocalExtraPkgInfo ei = (LocalExtraPkgInfo) info;
- IPkgDesc d = ei.getDesc();
- String vendor = d.getVendor().getId();
- String path = d.getPath();
- int majorRev = d.getFullRevision().getMajor();
-
- extraVersions.put(vendor + '/' + path, majorRev);
- }
- }
-
- return extraVersions;
- }
-
- /** Returns the platform tools version if installed, null otherwise. */
- @Nullable
- public String getPlatformToolsVersion() {
- LocalPkgInfo info = mLocalSdk.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
- IPkgDesc d = info == null ? null : info.getDesc();
- if (d != null && d.hasFullRevision()) {
- return d.getFullRevision().toShortString();
- }
-
- return null;
- }
-
-
- // -------------
-
- public static class LayoutlibVersion implements Comparable<LayoutlibVersion> {
- private final int mApi;
- private final int mRevision;
-
- public static final int NOT_SPECIFIED = 0;
-
- public LayoutlibVersion(int api, int revision) {
- mApi = api;
- mRevision = revision;
- }
-
- public int getApi() {
- return mApi;
- }
-
- public int getRevision() {
- return mRevision;
- }
-
- @Override
- public int compareTo(@NonNull LayoutlibVersion rhs) {
- boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED;
- int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0);
- int rhsValue = (rhs.mApi << 16) + (useRev ? rhs.mRevision : 0);
- return lhsValue - rhsValue;
- }
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/SdkVersionInfo.java b/base/sdklib/src/main/java/com/android/sdklib/SdkVersionInfo.java
deleted file mode 100755
index cfedbf1..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/SdkVersionInfo.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.sdklib;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Strings;
-
-import java.util.Locale;
-
-/** Information about available SDK Versions */
-public class SdkVersionInfo {
- /**
- * The highest known API level. Note that the tools may also look at the
- * installed platforms to see if they can find more recently released
- * platforms, e.g. when the tools have not yet been updated for a new
- * release. This number is used as a baseline and any more recent platforms
- * found can be used to increase the highest known number.
- */
- public static final int HIGHEST_KNOWN_API = 23;
-
- /**
- * Like {@link #HIGHEST_KNOWN_API} but does not include preview platforms
- */
- public static final int HIGHEST_KNOWN_STABLE_API = 22;
-
- /**
- * The lowest active API level in the ecosystem. This number will change over time
- * as the distribution of older platforms decreases.
- */
- public static final int LOWEST_ACTIVE_API = 8;
-
- /**
- * Returns the Android version and code name of the given API level, or null
- * if not known. The highest number (inclusive) that is supported
- * is {@link SdkVersionInfo#HIGHEST_KNOWN_API}.
- *
- * @param api the api level
- * @return a suitable version display name
- */
- @Nullable
- public static String getAndroidName(int api) {
- // See http://source.android.com/source/build-numbers.html
- String codeName = getCodeName(api);
- String name = getVersionString(api);
- if (name == null) {
- return String.format("API %1$d", api);
- } else if (codeName == null) {
- return String.format("API %1$d: Android %2$s", api, name);
- } else {
- return String.format("API %1$d: Android %2$s (%3$s)", api, name, codeName);
- }
- }
-
- @Nullable
- public static String getVersionString(int api) {
- switch (api) {
- case 1: return "1.0";
- case 2: return "1.1";
- case 3: return "1.5";
- case 4: return "1.6";
- case 5: return "2.0";
- case 6: return "2.0.1";
- case 7: return "2.1";
- case 8: return "2.2";
- case 9: return "2.3";
- case 10: return "2.3.3";
- case 11: return "3.0";
- case 12: return "3.1";
- case 13: return "3.2";
- case 14: return "4.0";
- case 15: return "4.0.3";
- case 16: return "4.1";
- case 17: return "4.2";
- case 18: return "4.3";
- case 19: return "4.4";
- case 20: return "4.4";
- case 21: return "5.0";
- case 22: return "5.1";
- case 23: return "5.X";
- // If you add more versions here, also update #getBuildCodes and
- // #HIGHEST_KNOWN_API
-
- default: return null;
- }
- }
-
- @Nullable
- public static String getCodeName(int api) {
- switch (api) {
- case 1:
- case 2:
- return null;
- case 3:
- return "Cupcake";
- case 4:
- return "Donut";
- case 5:
- case 6:
- case 7:
- return "Eclair";
- case 8:
- return "Froyo";
- case 9:
- case 10:
- return "Gingerbread";
- case 11:
- case 12:
- case 13:
- return "Honeycomb";
- case 14:
- case 15:
- return "IceCreamSandwich";
- case 16:
- case 17:
- case 18:
- return "Jelly Bean";
- case 19:
- return "KitKat";
- case 20:
- return "KitKat Wear";
- case 21:
- case 22:
- return "Lollipop";
- case 23:
- return "MNC";
-
- // If you add more versions here, also update #getBuildCodes and
- // #HIGHEST_KNOWN_API
-
- default: return null;
- }
- }
-
- /**
- * Returns the applicable build code (for
- * {@code android.os.Build.VERSION_CODES}) for the corresponding API level,
- * or null if it's unknown. The highest number (inclusive) that is supported
- * is {@link SdkVersionInfo#HIGHEST_KNOWN_API}.
- *
- * @param api the API level to look up a version code for
- * @return the corresponding build code field name, or null
- */
- @Nullable
- public static String getBuildCode(int api) {
- // See http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
- switch (api) {
- case 1: return "BASE"; //$NON-NLS-1$
- case 2: return "BASE_1_1"; //$NON-NLS-1$
- case 3: return "CUPCAKE"; //$NON-NLS-1$
- case 4: return "DONUT"; //$NON-NLS-1$
- case 5: return "ECLAIR"; //$NON-NLS-1$
- case 6: return "ECLAIR_0_1"; //$NON-NLS-1$
- case 7: return "ECLAIR_MR1"; //$NON-NLS-1$
- case 8: return "FROYO"; //$NON-NLS-1$
- case 9: return "GINGERBREAD"; //$NON-NLS-1$
- case 10: return "GINGERBREAD_MR1"; //$NON-NLS-1$
- case 11: return "HONEYCOMB"; //$NON-NLS-1$
- case 12: return "HONEYCOMB_MR1"; //$NON-NLS-1$
- case 13: return "HONEYCOMB_MR2"; //$NON-NLS-1$
- case 14: return "ICE_CREAM_SANDWICH"; //$NON-NLS-1$
- case 15: return "ICE_CREAM_SANDWICH_MR1"; //$NON-NLS-1$
- case 16: return "JELLY_BEAN"; //$NON-NLS-1$
- case 17: return "JELLY_BEAN_MR1"; //$NON-NLS-1$
- case 18: return "JELLY_BEAN_MR2"; //$NON-NLS-1$
- case 19: return "KITKAT"; //$NON-NLS-1$
- case 20: return "KITKAT_WATCH"; //$NON-NLS-1$
- case 21: return "LOLLIPOP"; //$NON-NLS-1$
- case 22: return "LOLLIPOP_MR1"; //$NON-NLS-1$
- case 23: return "MNC"; //$NON-NLS-1$
- // If you add more versions here, also update #getAndroidName and
- // #HIGHEST_KNOWN_API
- }
-
- return null;
- }
-
- /**
- * Returns the API level of the given build code (e.g. JELLY_BEAN_MR1 => 17), or -1 if not
- * recognized
- *
- * @param buildCode the build code name (not case sensitive)
- * @param recognizeUnknowns if true, treat an unrecognized code name as a newly released
- * platform the tools are not yet aware of, and set its API level to
- * some higher number than all the currently known API versions
- * @return the API level, or -1 if not recognized (unless recognizeUnknowns is true, in which
- * {@link #HIGHEST_KNOWN_API} plus one is returned
- */
- public static int getApiByBuildCode(@NonNull String buildCode, boolean recognizeUnknowns) {
- for (int api = 1; api <= HIGHEST_KNOWN_API; api++) {
- String code = getBuildCode(api);
- if (code != null && code.equalsIgnoreCase(buildCode)) {
- return api;
- }
- }
-
- if (buildCode.equalsIgnoreCase("L")) {
- return 21; // For now the Build class also provides this as an alias to Lollipop
- }
-
- return recognizeUnknowns ? HIGHEST_KNOWN_API + 1 : -1;
- }
-
- /**
- * Returns the API level of the given preview code name (e.g. JellyBeanMR2 => 17), or -1 if not
- * recognized
- *
- * @param previewName the preview name (not case sensitive)
- * @param recognizeUnknowns if true, treat an unrecognized code name as a newly released
- * platform the tools are not yet aware of, and set its API level to
- * some higher number than all the currently known API versions
- * @return the API level, or -1 if not recognized (unless recognizeUnknowns is true, in which
- * {@link #HIGHEST_KNOWN_API} plus one is returned
- */
- public static int getApiByPreviewName(@NonNull String previewName, boolean recognizeUnknowns) {
- // JellyBean => JELLY_BEAN
- String codeName = camelCaseToUnderlines(previewName).toUpperCase(Locale.US);
- return getApiByBuildCode(codeName, recognizeUnknowns);
- }
-
- /**
- * Converts a CamelCase word into an underlined_word
- *
- * @param string the CamelCase version of the word
- * @return the underlined version of the word
- */
- @NonNull
- public static String camelCaseToUnderlines(@NonNull String string) {
- if (string.isEmpty()) {
- return string;
- }
-
- StringBuilder sb = new StringBuilder(2 * string.length());
- int n = string.length();
- boolean lastWasUpperCase = Character.isUpperCase(string.charAt(0));
- for (int i = 0; i < n; i++) {
- char c = string.charAt(i);
- boolean isUpperCase = Character.isUpperCase(c);
- if (isUpperCase && !lastWasUpperCase) {
- sb.append('_');
- }
- lastWasUpperCase = isUpperCase;
- c = Character.toLowerCase(c);
- sb.append(c);
- }
-
- return sb.toString();
- }
-
- /**
- * Converts an underlined_word into a CamelCase word
- *
- * @param string the underlined word to convert
- * @return the CamelCase version of the word
- */
- @NonNull
- public static String underlinesToCamelCase(@NonNull String string) {
- StringBuilder sb = new StringBuilder(string.length());
- int n = string.length();
-
- int i = 0;
- @SuppressWarnings("SpellCheckingInspection")
- boolean upcaseNext = true;
- for (; i < n; i++) {
- char c = string.charAt(i);
- if (c == '_') {
- upcaseNext = true;
- } else {
- if (upcaseNext) {
- c = Character.toUpperCase(c);
- }
- upcaseNext = false;
- sb.append(c);
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Returns the {@link AndroidVersion} for a given version string, which is typically an API
- * level number, but can also be a codename for a <b>preview</b> platform. Note: This should
- * <b>not</b> be used to look up version names for build codes; for that, use {@link
- * #getApiByBuildCode(String, boolean)}. The primary difference between this method is that
- * {@link #getApiByBuildCode(String, boolean)} will return the final API number for a platform
- * (e.g. for "KITKAT" it will return 19) whereas this method will return the API number for the
- * codename as a preview platform (e.g. 18).
- *
- * @param apiOrPreviewName the version string
- * @param targets an optional array of installed targets, if available. If the version
- * string corresponds to a code name, this is used to search for a
- * corresponding API level.
- * @return an {@link com.android.sdklib.AndroidVersion}, or null if the version could not be
- * determined (e.g. an empty or invalid API number or an unknown code name)
- */
- @Nullable
- public static AndroidVersion getVersion(
- @Nullable String apiOrPreviewName,
- @Nullable IAndroidTarget[] targets) {
- if (Strings.isNullOrEmpty(apiOrPreviewName)) {
- return null;
- }
-
- if (Character.isDigit(apiOrPreviewName.charAt(0))) {
- try {
- int api = Integer.parseInt(apiOrPreviewName);
- if (api >= 1) {
- return new AndroidVersion(api, null);
- }
- return null;
- } catch (NumberFormatException e) {
- // Invalid version string
- return null;
- }
- }
-
- // Codename
- if (targets != null) {
- for (int i = targets.length - 1; i >= 0; i--) {
- IAndroidTarget target = targets[i];
- if (target.isPlatform()) {
- AndroidVersion version = target.getVersion();
- if (version.isPreview() && apiOrPreviewName.equalsIgnoreCase(version.getCodename())) {
- return new AndroidVersion(version.getApiLevel(), version.getCodename());
- }
- }
- }
- }
-
- int api = getApiByPreviewName(apiOrPreviewName, false);
- if (api != -1) {
- return new AndroidVersion(api - 1, apiOrPreviewName);
- }
-
- // Must be a future SDK platform
- return new AndroidVersion(HIGHEST_KNOWN_API, apiOrPreviewName);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/SystemImage.java b/base/sdklib/src/main/java/com/android/sdklib/SystemImage.java
deleted file mode 100755
index ded928f..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/SystemImage.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.devices.Abi;
-import com.android.sdklib.internal.androidTarget.PlatformTarget;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.base.Objects;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Locale;
-
-
-/**
- * Describes a system image as used by an {@link IAndroidTarget}.
- * A system image has an installation path, a location type, a tag and an ABI type.
- */
-public class SystemImage implements ISystemImage {
-
- public static final IdDisplay DEFAULT_TAG = new IdDisplay("default", //$NON-NLS-1$
- "Default"); //$NON-NLS-1$
-
- @Deprecated
- private final LocationType mLocationtype;
- private final IdDisplay mTag;
- private final IdDisplay mAddonVendor;
- private final String mAbiType;
- private final File mLocation;
- private final File[] mSkins;
-
- /**
- * Creates a {@link SystemImage} description for an existing platform system image folder.
- *
- * @param location The location of an installed system image.
- * @param locationType Where the system image folder is located for this ABI.
- * @param tag The tag of the system-image. Use {@link #DEFAULT_TAG} for backward compatibility.
- * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
- * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
- * {@link SdkConstants#ABI_MIPS}.
- * @param skins A non-null, possibly empty list of skins specific to this system image.
- */
- public SystemImage(
- @NonNull File location,
- @NonNull LocationType locationType,
- @NonNull IdDisplay tag,
- @NonNull String abiType,
- @NonNull File[] skins) {
- this(location, locationType, tag, null /*addonVendor*/, abiType, skins);
- }
-
- /**
- * Creates a {@link SystemImage} description for an existing system image folder,
- * for either platform or add-on.
- *
- * @param location The location of an installed system image.
- * @param locationType Where the system image folder is located for this ABI.
- * @param tagName The tag of the system-image.
- * For an add-on, the tag-id must match the add-on's name-id.
- * @param addonVendor Non-null add-on vendor name. Null for platforms.
- * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
- * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
- * {@link SdkConstants#ABI_MIPS}.
- * @param skins A non-null, possibly empty list of skins specific to this system image.
- */
- public SystemImage(
- @NonNull File location,
- @NonNull LocationType locationType,
- @NonNull IdDisplay tagName,
- @Nullable IdDisplay addonVendor,
- @NonNull String abiType,
- @NonNull File[] skins) {
- mLocation = location;
- mLocationtype = locationType;
- mTag = tagName;
- mAddonVendor = addonVendor;
- mAbiType = abiType;
- mSkins = skins;
- }
-
- /**
- * Creates a {@link SystemImage} description for a non-existing platform system image folder.
- * The actual location is computed based on the {@code locationType}.
- *
- * @param sdkManager The current SDK manager.
- * @param locationType Where the system image folder is located for this ABI.
- * @param tag The tag of the system-image. Use {@link #DEFAULT_TAG} for backward compatibility.
- * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
- * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
- * {@link SdkConstants#ABI_MIPS}.
- * @param skins A non-null, possibly empty list of skins specific to this system image.
- * @throws IllegalArgumentException if the {@code target} used for
- * {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} is not a {@link PlatformTarget}.
- */
- public SystemImage(
- @NonNull SdkManager sdkManager,
- @NonNull IAndroidTarget target,
- @NonNull LocationType locationType,
- @NonNull IdDisplay tag,
- @NonNull String abiType,
- @NonNull File[] skins) {
- this(sdkManager, target, locationType, tag, null /*addonVendor*/, abiType, skins);
- }
-
-
- /**
- * Creates a {@link SystemImage} description for a non-existing system image folder,
- * for either platform or add-on.
- * The actual location is computed based on the {@code locationType}.
- *
- * @param sdkManager The current SDK manager.
- * @param locationType Where the system image folder is located for this ABI.
- * @param tag The tag of the system-image. Use {@link #DEFAULT_TAG} for backward compatibility.
- * @param addonVendor Non-null add-on vendor name. Null for platforms.
- * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
- * {@link SdkConstants#ABI_ARMEABI_V7A}, {@link SdkConstants#ABI_INTEL_ATOM} or
- * {@link SdkConstants#ABI_MIPS}.
- * @param skins A non-null, possibly empty list of skins specific to this system image.
- * @throws IllegalArgumentException if the {@code target} used for
- * {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} is not a {@link PlatformTarget}.
- */
- public SystemImage(
- @NonNull SdkManager sdkManager,
- @NonNull IAndroidTarget target,
- @NonNull LocationType locationType,
- @NonNull IdDisplay tag,
- @Nullable IdDisplay addonVendor,
- @NonNull String abiType,
- @NonNull File[] skins) {
- mLocationtype = locationType;
- mTag = tag;
- mAddonVendor = addonVendor;
- mAbiType = abiType;
- mSkins = skins;
-
- File location = null;
- switch(locationType) {
- case IN_LEGACY_FOLDER:
- location = new File(target.getLocation(), SdkConstants.OS_IMAGES_FOLDER);
- break;
-
- case IN_IMAGES_SUBFOLDER:
- location = FileOp.append(target.getLocation(), SdkConstants.OS_IMAGES_FOLDER, abiType);
- break;
-
- case IN_SYSTEM_IMAGE:
- location = getCanonicalFolder(sdkManager.getLocation(),
- target.getVersion(),
- tag.getId(),
- addonVendor == null ? null : addonVendor.getId(),
- abiType);
- break;
- default:
- // This is not supposed to happen unless LocationType is
- // extended without adjusting this code.
- assert false : "SystemImage used with an incorrect locationType"; //$NON-NLS-1$
- }
- mLocation = location;
- }
-
- /**
- * Static helper method that returns the canonical path for a system-image that uses
- * the {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} location type.
- * <p/>
- * Such an image is located in {@code SDK/system-images/android-N/tag/abiType}.
- * For this reason this method requires the root SDK as well as the platform, tag abd ABI type.
- *
- * @param sdkOsPath The OS path to the SDK.
- * @param platformVersion The platform version.
- * @param tagId An optional tag. If null, not tag folder is used.
- * For legacy, use {@code SystemImage.DEFAULT_TAG.getId()}.
- * @param addonVendorId An optional vendor-id for an add-on. If null, it's a platform sys-img.
- * @param abiType An optional ABI type. If null, the parent directory is returned.
- * @return A file that represents the location of the canonical system-image folder
- * for this configuration.
- */
- @NonNull
- private static File getCanonicalFolder(
- @NonNull String sdkOsPath,
- @NonNull AndroidVersion platformVersion,
- @Nullable String tagId,
- @Nullable String addonVendorId,
- @Nullable String abiType) {
- File root;
- if (addonVendorId == null) {
- root = FileOp.append(
- sdkOsPath,
- SdkConstants.FD_SYSTEM_IMAGES,
- AndroidTargetHash.getPlatformHashString(platformVersion));
- if (tagId != null) {
- root = FileOp.append(root, tagId);
- }
- } else {
- root = FileOp.append(
- sdkOsPath,
- SdkConstants.FD_SYSTEM_IMAGES,
- AndroidTargetHash.getAddonHashString(addonVendorId, tagId, platformVersion));
- }
- if (abiType == null) {
- return root;
- } else {
- return FileOp.append(root, abiType);
- }
- }
-
- /** Returns the actual location of an installed system image. */
- @NonNull
- @Override
- public File getLocation() {
- return mLocation;
- }
-
- /**
- * Indicates the location strategy for this system image in the SDK.
- * @deprecated
- */
- @NonNull
- @Override
- public LocationType getLocationType() {
- return mLocationtype;
- }
-
- /** Returns the tag of the system image. */
- @NonNull
- @Override
- public IdDisplay getTag() {
- return mTag;
- }
-
- /** Returns the vendor for an add-on's system image, or null for a platform system-image. */
- @Nullable
- @Override
- public IdDisplay getAddonVendor() {
- return mAddonVendor;
- }
-
- /**
- * Returns the ABI type.
- * See {@link Abi} for a full list.
- * Cannot be null nor empty.
- */
- @NonNull
- @Override
- public String getAbiType() {
- return mAbiType;
- }
-
- @NonNull
- @Override
- public File[] getSkins() {
- return mSkins;
- }
-
- /**
- * Sort by tag & ABI name only. This is what matters from a user point of view.
- */
- @Override
- public int compareTo(ISystemImage other) {
- int t = this.getTag().compareTo(other.getTag());
- if (t != 0) {
- return t;
- }
- return this.getAbiType().compareToIgnoreCase(other.getAbiType());
- }
-
- /**
- * Generates a string representation suitable for debug purposes.
- * The string is not intended to be displayed to the user.
- *
- * {@inheritDoc}
- */
- @NonNull
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("SystemImage");
- if (mAddonVendor != null) {
- sb.append(" addon-vendor=").append(mAddonVendor.getId()).append(',');
- }
- sb.append(" tag=").append(mTag.getId());
- sb.append(", ABI=").append(mAbiType);
- sb.append(", location ")
- .append(mLocationtype.toString().replace('_', ' ').toLowerCase(Locale.US))
- .append("='")
- .append(mLocation)
- .append("'");
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof SystemImage)) {
- return false;
- }
- SystemImage other = (SystemImage)o;
- return mTag.equals(other.mTag) && mAbiType.equals(other.getAbiType()) && Objects.equal(mAddonVendor, other.mAddonVendor)
- && mLocation.equals(other.mLocation) && Arrays.equals(mSkins, other.mSkins);
- }
-
- @Override
- public int hashCode() {
- int hashCode = Objects.hashCode(mTag, mAbiType, mAddonVendor, mLocation);
- hashCode *= 37;
- hashCode += Arrays.hashCode(mSkins);
- return hashCode;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Abi.java b/base/sdklib/src/main/java/com/android/sdklib/devices/Abi.java
deleted file mode 100644
index c3ec95d..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/devices/Abi.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.devices;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-/**
- * ABI values that can appear in a device's xml <abi> field <em>and</em>
- * in a system-image abi.
- * <p/>
- * The CPU arch and model values are used to configure an AVD using a given ABI.
- */
-public enum Abi {
- // // ABI string // Display // CPU arch
- ARMEABI (SdkConstants.ABI_ARMEABI, "ARM", SdkConstants.CPU_ARCH_ARM),
- ARMEABI_V7A(SdkConstants.ABI_ARMEABI_V7A, "ARM", SdkConstants.CPU_ARCH_ARM, SdkConstants.CPU_MODEL_CORTEX_A8),
- ARM64_V8A (SdkConstants.ABI_ARM64_V8A, "ARM", SdkConstants.CPU_ARCH_ARM64),
- X86 (SdkConstants.ABI_INTEL_ATOM, "Intel Atom", SdkConstants.CPU_ARCH_INTEL_ATOM),
- X86_64 (SdkConstants.ABI_INTEL_ATOM64,"Intel Atom", SdkConstants.CPU_ARCH_INTEL_ATOM64),
- MIPS (SdkConstants.ABI_MIPS, "MIPS", SdkConstants.CPU_ARCH_MIPS),
- MIPS64 (SdkConstants.ABI_MIPS64, "MIPS", SdkConstants.CPU_ARCH_MIPS64);
-
- @NonNull private final String mAbi;
- @NonNull private final String mCpuArch;
- @Nullable private final String mCpuModel;
- @NonNull private final String mDisplayName;
-
- /**
- * Define an ABI with a given ABI code name, a display name and a CPU architecture.
- *
- * @param abi The ABI code name, used in the system-images and device definitions.
- * @param displayName The ABI "family" name. Typically used in the UI combined with the
- * code name, for example "ARM (armeabi-v7a)".
- * @param cpuArch The CPU architecture, used in the AVD configuration files.
- */
- Abi(@NonNull String abi, @NonNull String displayName, @NonNull String cpuArch) {
- this(abi, displayName, cpuArch, null);
- }
-
-
- /**
- * Define an ABI with a given ABI code name, a display name, a CPU architecture
- * and an optional CPU model.
- *
- * @param abi The ABI code name, used in the system-images and device definitions.
- * @param displayName The ABI "family" name. Typically used in the UI combined with the
- * code name, for example "ARM (armeabi-v7a)".
- * @param cpuArch The CPU architecture, used in the AVD configuration files.
- * @param cpuModel An optional CPU model, used in the AVD configuration files.
- * The current strategy is to leave this field out. The emulator, which uses the
- * AVD configuration files, doesn't seem to use it.
- */
- Abi(@NonNull String abi, @NonNull String displayName,
- @NonNull String cpuArch, @Nullable String cpuModel) {
- mAbi = abi;
- mDisplayName = displayName;
- mCpuArch = cpuArch;
- mCpuModel = cpuModel;
- }
-
- /**
- * Returns the ABI definition matching the given ABI code name.
- *
- * @param abi The ABI code name, used in the system-images and device definitions.
- * @return An existing {@link Abi} description or null.
- */
- @Nullable
- public static Abi getEnum(@NonNull String abi) {
- for (Abi a : values()) {
- if (a.mAbi.equals(abi)) {
- return a;
- }
- }
- return null;
- }
-
- /**
- * Returns the ABI code name, as used in the system-images and device definitions
- */
- @NonNull
- @Override
- public String toString() {
- return mAbi;
- }
-
- /**
- * Returns the CPU architecture, as used in the AVD configuration files
- */
- @NonNull
- public String getCpuArch() {
- return mCpuArch;
- }
-
- /**
- * Returns the optional CPU model, used in the AVD configuration files.
- * This is often null.
- */
- @Nullable
- public String getCpuModel() {
- return mCpuModel;
- }
-
- /**
- * Return the ABI "family" name for display.
- * Clients should typically display that combined with the code name,
- * for example "ARM (armeabi-v7a)".
- */
- @NonNull
- public String getDisplayName() {
- return mDisplayName;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java b/base/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
deleted file mode 100644
index f2b1734..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
+++ /dev/null
@@ -1,719 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.devices;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.resources.Keyboard;
-import com.android.resources.KeyboardState;
-import com.android.resources.Navigation;
-import com.android.sdklib.internal.avd.AvdManager;
-import com.android.sdklib.internal.avd.HardwareProperties;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.PkgProps;
-import com.android.utils.ILogger;
-import com.google.common.base.Charsets;
-import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.Table;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
-import com.google.common.io.Closeables;
-
-import org.xml.sax.SAXException;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-
-/**
- * Manager class for interacting with {@link Device}s within the SDK
- */
-public class DeviceManager {
-
- private static final String DEVICE_PROFILES_PROP = "DeviceProfiles";
- private static final Pattern PATH_PROPERTY_PATTERN =
- Pattern.compile('^' + PkgProps.EXTRA_PATH + '=' + DEVICE_PROFILES_PROP + '$');
- private ILogger mLog;
- private Table<String, String, Device> mVendorDevices;
- private Table<String, String, Device> mSysImgDevices;
- private Table<String, String, Device> mUserDevices;
- private Table<String, String, Device> mDefaultDevices;
- private final Object mLock = new Object();
- private final List<DevicesChangedListener> sListeners = new ArrayList<DevicesChangedListener>();
- private final String mOsSdkPath;
-
- public enum DeviceFilter {
- /** getDevices() flag to list default devices from the bundled devices.xml definitions. */
- DEFAULT,
- /** getDevices() flag to list user devices saved in the .android home folder. */
- USER,
- /** getDevices() flag to list vendor devices -- the bundled nexus.xml devices
- * as well as all those coming from extra packages. */
- VENDOR,
- /** getDevices() flag to list devices from system-images/platform-N/tag/abi/devices.xml */
- SYSTEM_IMAGES,
- }
-
- /** getDevices() flag to list all devices. */
- public static final EnumSet<DeviceFilter> ALL_DEVICES = EnumSet.allOf(DeviceFilter.class);
-
- public enum DeviceStatus {
- /**
- * The device exists unchanged from the given configuration
- */
- EXISTS,
- /**
- * A device exists with the given name and manufacturer, but has a different configuration
- */
- CHANGED,
- /**
- * There is no device with the given name and manufacturer
- */
- MISSING
- }
-
- /**
- * Creates a new instance of DeviceManager.
- *
- * @param sdkLocation Path to the current SDK. If null or invalid, vendor and system images
- * devices are ignored.
- * @param log SDK logger instance. Should be non-null.
- */
- public static DeviceManager createInstance(@Nullable File sdkLocation, @NonNull ILogger log) {
- // TODO consider using a cache and reusing the same instance of the device manager
- // for the same manager/log combo.
- return new DeviceManager(sdkLocation == null ? null : sdkLocation.getPath(), log);
- }
-
- /**
- * Creates a new instance of DeviceManager.
- *
- * @param osSdkPath Path to the current SDK. If null or invalid, vendor devices are ignored.
- * @param log SDK logger instance. Should be non-null.
- */
- private DeviceManager(@Nullable String osSdkPath, @NonNull ILogger log) {
- mOsSdkPath = osSdkPath;
- mLog = log;
- }
-
- /**
- * Interface implemented by objects which want to know when changes occur to the {@link Device}
- * lists.
- */
- public interface DevicesChangedListener {
- /**
- * Called after one of the {@link Device} lists has been updated.
- */
- void onDevicesChanged();
- }
-
- /**
- * Register a listener to be notified when the device lists are modified.
- *
- * @param listener The listener to add. Ignored if already registered.
- */
- public void registerListener(@NonNull DevicesChangedListener listener) {
- synchronized (sListeners) {
- if (!sListeners.contains(listener)) {
- sListeners.add(listener);
- }
- }
- }
-
- /**
- * Removes a listener from the notification list such that it will no longer receive
- * notifications when modifications to the {@link Device} list occur.
- *
- * @param listener The listener to remove.
- */
- public boolean unregisterListener(@NonNull DevicesChangedListener listener) {
- synchronized (sListeners) {
- return sListeners.remove(listener);
- }
- }
-
- @NonNull
- public DeviceStatus getDeviceStatus(@NonNull String name, @NonNull String manufacturer) {
- Device d = getDevice(name, manufacturer);
- if (d == null) {
- return DeviceStatus.MISSING;
- }
-
- return DeviceStatus.EXISTS;
- }
-
- @Nullable
- public Device getDevice(@NonNull String id, @NonNull String manufacturer) {
- initDevicesLists();
- Device d = mUserDevices.get(id, manufacturer);
- if (d != null) {
- return d;
- }
- d = mSysImgDevices.get(id, manufacturer);
- if (d != null) {
- return d;
- }
- d = mDefaultDevices.get(id, manufacturer);
- if (d != null) {
- return d;
- }
- d = mVendorDevices.get(id, manufacturer);
- return d;
- }
-
- @Nullable
- private Device getDeviceImpl(@NonNull Iterable<Device> devicesList,
- @NonNull String id,
- @NonNull String manufacturer) {
- for (Device d : devicesList) {
- if (d.getId().equals(id) && d.getManufacturer().equals(manufacturer)) {
- return d;
- }
- }
- return null;
- }
-
- /**
- * Returns the known {@link Device} list.
- *
- * @param deviceFilter One of the {@link DeviceFilter} constants.
- * @return A copy of the list of {@link Device}s. Can be empty but not null.
- */
- @NonNull
- public Collection<Device> getDevices(@NonNull DeviceFilter deviceFilter) {
- return getDevices(EnumSet.of(deviceFilter));
- }
-
- /**
- * Returns the known {@link Device} list.
- *
- * @param deviceFilter A combination of the {@link DeviceFilter} constants
- * or the constant {@link DeviceManager#ALL_DEVICES}.
- * @return A copy of the list of {@link Device}s. Can be empty but not null.
- */
- @NonNull
- public Collection<Device> getDevices(@NonNull EnumSet<DeviceFilter> deviceFilter) {
- initDevicesLists();
- Table<String, String, Device> devices = HashBasedTable.create();
- if (mUserDevices != null && (deviceFilter.contains(DeviceFilter.USER))) {
- devices.putAll(mUserDevices);
- }
- if (mDefaultDevices != null && (deviceFilter.contains(DeviceFilter.DEFAULT))) {
- devices.putAll(mDefaultDevices);
- }
- if (mVendorDevices != null && (deviceFilter.contains(DeviceFilter.VENDOR))) {
- devices.putAll(mVendorDevices);
- }
- if (mSysImgDevices != null && (deviceFilter.contains(DeviceFilter.SYSTEM_IMAGES))) {
- devices.putAll(mSysImgDevices);
- }
- return Collections.unmodifiableCollection(devices.values());
- }
-
- private void initDevicesLists() {
- boolean changed = initDefaultDevices();
- changed |= initVendorDevices();
- changed |= initSysImgDevices();
- changed |= initUserDevices();
- if (changed) {
- notifyListeners();
- }
- }
-
- /**
- * Initializes the {@link Device}s packaged with the SDK.
- * @return True if the list has changed.
- */
- private boolean initDefaultDevices() {
- synchronized (mLock) {
- if (mDefaultDevices != null) {
- return false;
- }
- InputStream stream = DeviceManager.class
- .getResourceAsStream(SdkConstants.FN_DEVICES_XML);
- try {
- assert stream != null : SdkConstants.FN_DEVICES_XML + " not bundled in sdklib.";
- mDefaultDevices = DeviceParser.parse(stream);
- return true;
- } catch (IllegalStateException e) {
- // The device builders can throw IllegalStateExceptions if
- // build gets called before everything is properly setup
- mLog.error(e, null);
- mDefaultDevices = HashBasedTable.create();
- } catch (Exception e) {
- mLog.error(e, "Error reading default devices");
- mDefaultDevices = HashBasedTable.create();
- } finally {
- Closeables.closeQuietly(stream);
- }
- }
- return false;
- }
-
- /**
- * Initializes all vendor-provided {@link Device}s: the bundled nexus.xml devices
- * as well as all those coming from extra packages.
- * @return True if the list has changed.
- */
- private boolean initVendorDevices() {
- synchronized (mLock) {
- if (mVendorDevices != null) {
- return false;
- }
-
- mVendorDevices = HashBasedTable.create();
-
- // Load builtin devices
- InputStream stream = DeviceManager.class.getResourceAsStream("nexus.xml");
- try {
- mVendorDevices.putAll(DeviceParser.parse(stream));
- } catch (Exception e) {
- mLog.error(e, "Could not load nexus devices");
- } finally {
- Closeables.closeQuietly(stream);
- }
-
- stream = DeviceManager.class.getResourceAsStream("wear.xml");
- try {
- mVendorDevices.putAll(DeviceParser.parse(stream));
- } catch (Exception e) {
- mLog.error(e, "Could not load wear devices");
- } finally {
- Closeables.closeQuietly(stream);
- }
-
- stream = DeviceManager.class.getResourceAsStream("tv.xml");
- try {
- mVendorDevices.putAll(DeviceParser.parse(stream));
- } catch (Exception e) {
- mLog.error(e, "Could not load tv devices");
- } finally {
- Closeables.closeQuietly(stream);
- }
-
- if (mOsSdkPath != null) {
- // Load devices from vendor extras
- File extrasFolder = new File(mOsSdkPath, SdkConstants.FD_EXTRAS);
- List<File> deviceDirs = getExtraDirs(extrasFolder);
- for (File deviceDir : deviceDirs) {
- File deviceXml = new File(deviceDir, SdkConstants.FN_DEVICES_XML);
- if (deviceXml.isFile()) {
- mVendorDevices.putAll(loadDevices(deviceXml));
- }
- }
- return true;
- }
- }
- return false;
- }
-
- /**
- * Initializes all system-image provided {@link Device}s.
- * @return True if the list has changed.
- */
- private boolean initSysImgDevices() {
- synchronized (mLock) {
- if (mSysImgDevices != null) {
- return false;
- }
- mSysImgDevices = HashBasedTable.create();
-
- if (mOsSdkPath == null) {
- return false;
- }
-
- // Load devices from tagged system-images
- // Path pattern is /sdk/system-images/<platform-N>/<tag>/<abi>/devices.xml
-
- FileOp fop = new FileOp();
- File sysImgFolder = new File(mOsSdkPath, SdkConstants.FD_SYSTEM_IMAGES);
-
- for (File platformFolder : fop.listFiles(sysImgFolder)) {
- if (!fop.isDirectory(platformFolder)) {
- continue;
- }
-
- for (File tagFolder : fop.listFiles(platformFolder)) {
- if (!fop.isDirectory(tagFolder)) {
- continue;
- }
-
- for (File abiFolder : fop.listFiles(tagFolder)) {
- if (!fop.isDirectory(abiFolder)) {
- continue;
- }
-
- File deviceXml = new File(abiFolder, SdkConstants.FN_DEVICES_XML);
- if (fop.isFile(deviceXml)) {
- mSysImgDevices.putAll(loadDevices(deviceXml));
- }
- }
- }
- }
- return true;
- }
- }
-
- /**
- * Initializes all user-created {@link Device}s
- * @return True if the list has changed.
- */
- private boolean initUserDevices() {
- synchronized (mLock) {
- if (mUserDevices != null) {
- return false;
- }
- // User devices should be saved out to
- // $HOME/.android/devices.xml
- mUserDevices = HashBasedTable.create();
- File userDevicesFile = null;
- try {
- userDevicesFile = new File(
- AndroidLocation.getFolder(),
- SdkConstants.FN_DEVICES_XML);
- if (userDevicesFile.exists()) {
- mUserDevices.putAll(DeviceParser.parse(userDevicesFile));
- return true;
- }
- } catch (AndroidLocationException e) {
- mLog.warning("Couldn't load user devices: %1$s", e.getMessage());
- } catch (SAXException e) {
- // Probably an old config file which we don't want to overwrite.
- if (userDevicesFile != null) {
- String base = userDevicesFile.getAbsoluteFile() + ".old";
- File renamedConfig = new File(base);
- int i = 0;
- while (renamedConfig.exists()) {
- renamedConfig = new File(base + '.' + (i++));
- }
- mLog.error(e, "Error parsing %1$s, backing up to %2$s",
- userDevicesFile.getAbsolutePath(),
- renamedConfig.getAbsolutePath());
- userDevicesFile.renameTo(renamedConfig);
- }
- } catch (ParserConfigurationException e) {
- mLog.error(e, "Error parsing %1$s",
- userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
- } catch (IOException e) {
- mLog.error(e, "Error parsing %1$s",
- userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
- }
- }
- return false;
- }
-
- public void addUserDevice(@NonNull Device d) {
- boolean changed = false;
- synchronized (mLock) {
- if (mUserDevices == null) {
- initUserDevices();
- assert mUserDevices != null;
- }
- if (mUserDevices != null) {
- mUserDevices.put(d.getId(), d.getManufacturer(), d);
- }
- changed = true;
- }
- if (changed) {
- notifyListeners();
- }
- }
-
- public void removeUserDevice(@NonNull Device d) {
- synchronized (mLock) {
- if (mUserDevices == null) {
- initUserDevices();
- assert mUserDevices != null;
- }
- if (mUserDevices != null) {
- if (mUserDevices.contains(d.getId(), d.getManufacturer())) {
- mUserDevices.remove(d.getId(), d.getManufacturer());
- notifyListeners();
- }
- }
- }
- }
-
- public void replaceUserDevice(@NonNull Device d) {
- synchronized (mLock) {
- if (mUserDevices == null) {
- initUserDevices();
- }
- removeUserDevice(d);
- addUserDevice(d);
- }
- }
-
- /**
- * Saves out the user devices to {@link SdkConstants#FN_DEVICES_XML} in
- * {@link AndroidLocation#getFolder()}.
- */
- public void saveUserDevices() {
- if (mUserDevices == null) {
- return;
- }
-
- File userDevicesFile = null;
- try {
- userDevicesFile = new File(AndroidLocation.getFolder(),
- SdkConstants.FN_DEVICES_XML);
- } catch (AndroidLocationException e) {
- mLog.warning("Couldn't find user directory: %1$s", e.getMessage());
- return;
- }
-
- if (mUserDevices.isEmpty()) {
- userDevicesFile.delete();
- return;
- }
-
- synchronized (mLock) {
- if (!mUserDevices.isEmpty()) {
- try {
- DeviceWriter.writeToXml(new FileOutputStream(userDevicesFile), mUserDevices.values());
- } catch (FileNotFoundException e) {
- mLog.warning("Couldn't open file: %1$s", e.getMessage());
- } catch (ParserConfigurationException e) {
- mLog.warning("Error writing file: %1$s", e.getMessage());
- } catch (TransformerFactoryConfigurationError e) {
- mLog.warning("Error writing file: %1$s", e.getMessage());
- } catch (TransformerException e) {
- mLog.warning("Error writing file: %1$s", e.getMessage());
- }
- }
- }
- }
-
- /**
- * Returns hardware properties (defined in hardware.ini) as a {@link Map}.
- *
- * @param s The {@link State} from which to derive the hardware properties.
- * @return A {@link Map} of hardware properties.
- */
- @NonNull
- public static Map<String, String> getHardwareProperties(@NonNull State s) {
- Hardware hw = s.getHardware();
- Map<String, String> props = new HashMap<String, String>();
- props.put(HardwareProperties.HW_MAINKEYS,
- getBooleanVal(hw.getButtonType().equals(ButtonType.HARD)));
- props.put(HardwareProperties.HW_TRACKBALL,
- getBooleanVal(hw.getNav().equals(Navigation.TRACKBALL)));
- props.put(HardwareProperties.HW_KEYBOARD,
- getBooleanVal(hw.getKeyboard().equals(Keyboard.QWERTY)));
- props.put(HardwareProperties.HW_DPAD,
- getBooleanVal(hw.getNav().equals(Navigation.DPAD)));
-
- Set<Sensor> sensors = hw.getSensors();
- props.put(HardwareProperties.HW_GPS, getBooleanVal(sensors.contains(Sensor.GPS)));
- props.put(HardwareProperties.HW_BATTERY,
- getBooleanVal(hw.getChargeType().equals(PowerType.BATTERY)));
- props.put(HardwareProperties.HW_ACCELEROMETER,
- getBooleanVal(sensors.contains(Sensor.ACCELEROMETER)));
- props.put(HardwareProperties.HW_ORIENTATION_SENSOR,
- getBooleanVal(sensors.contains(Sensor.GYROSCOPE)));
- props.put(HardwareProperties.HW_AUDIO_INPUT, getBooleanVal(hw.hasMic()));
- props.put(HardwareProperties.HW_SDCARD, getBooleanVal(!hw.getRemovableStorage().isEmpty()));
- props.put(HardwareProperties.HW_LCD_DENSITY,
- Integer.toString(hw.getScreen().getPixelDensity().getDpiValue()));
- props.put(HardwareProperties.HW_PROXIMITY_SENSOR,
- getBooleanVal(sensors.contains(Sensor.PROXIMITY_SENSOR)));
- return props;
- }
-
- /**
- * Returns the hardware properties defined in
- * {@link AvdManager#HARDWARE_INI} as a {@link Map}.
- *
- * This is intended to be dumped in the config.ini and already contains
- * the device name, manufacturer and device hash.
- *
- * @param d The {@link Device} from which to derive the hardware properties.
- * @return A {@link Map} of hardware properties.
- */
- @NonNull
- public static Map<String, String> getHardwareProperties(@NonNull Device d) {
- Map<String, String> props = getHardwareProperties(d.getDefaultState());
- for (State s : d.getAllStates()) {
- if (s.getKeyState().equals(KeyboardState.HIDDEN)) {
- props.put("hw.keyboard.lid", getBooleanVal(true));
- }
- }
-
- HashFunction md5 = Hashing.md5();
- Hasher hasher = md5.newHasher();
-
- ArrayList<String> keys = new ArrayList<String>(props.keySet());
- Collections.sort(keys);
- for (String key : keys) {
- if (key != null) {
- hasher.putString(key, Charsets.UTF_8);
- String value = props.get(key);
- hasher.putString(value == null ? "null" : value, Charsets.UTF_8);
- }
- }
- // store the hash method for potential future compatibility
- String hash = "MD5:" + hasher.hash().toString();
- props.put(AvdManager.AVD_INI_DEVICE_HASH_V2, hash);
- props.remove(AvdManager.AVD_INI_DEVICE_HASH_V1);
-
- props.put(AvdManager.AVD_INI_DEVICE_NAME, d.getId());
- props.put(AvdManager.AVD_INI_DEVICE_MANUFACTURER, d.getManufacturer());
- return props;
- }
-
- /**
- * Checks whether the the hardware props have changed.
- * If the hash is the same, returns null for success.
- * If the hash is not the same or there's not enough information to indicate it's
- * the same (e.g. if in the future we change the digest method), simply return the
- * new hash, indicating it would be best to update it.
- *
- * @param d The device.
- * @param hashV2 The previous saved AvdManager.AVD_INI_DEVICE_HASH_V2 property.
- * @return Null if the same, otherwise returns the new and different hash.
- */
- @Nullable
- public static String hasHardwarePropHashChanged(@NonNull Device d, @NonNull String hashV2) {
- Map<String, String> props = getHardwareProperties(d);
- String newHash = props.get(AvdManager.AVD_INI_DEVICE_HASH_V2);
-
- // Implementation detail: don't just return the hash and let the caller decide whether
- // the hash is the same. That's because the hash contains the digest method so if in
- // the future we decide to change it, we could potentially recompute the hash here
- // using an older digest method here and still determine its validity, whereas the
- // caller cannot determine that.
-
- if (newHash != null && newHash.equals(hashV2)) {
- return null;
- }
- return newHash;
- }
-
-
- /**
- * Takes a boolean and returns the appropriate value for
- * {@link HardwareProperties}
- *
- * @param bool The boolean value to turn into the appropriate
- * {@link HardwareProperties} value.
- * @return {@code HardwareProperties#BOOLEAN_YES} if true,
- * {@code HardwareProperties#BOOLEAN_NO} otherwise.
- */
- private static String getBooleanVal(boolean bool) {
- if (bool) {
- return HardwareProperties.BOOLEAN_YES;
- }
- return HardwareProperties.BOOLEAN_NO;
- }
-
- @NonNull
- private Table<String, String, Device> loadDevices(@NonNull File deviceXml) {
- try {
- return DeviceParser.parse(deviceXml);
- } catch (SAXException e) {
- mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath());
- } catch (ParserConfigurationException e) {
- mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath());
- } catch (IOException e) {
- mLog.error(e, "Error reading %1$s", deviceXml.getAbsolutePath());
- } catch (AssertionError e) {
- mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath());
- } catch (IllegalStateException e) {
- // The device builders can throw IllegalStateExceptions if
- // build gets called before everything is properly setup
- mLog.error(e, null);
- }
- return HashBasedTable.create();
- }
-
- private void notifyListeners() {
- synchronized (sListeners) {
- for (DevicesChangedListener listener : sListeners) {
- listener.onDevicesChanged();
- }
- }
- }
-
- /* Returns all of DeviceProfiles in the extras/ folder */
- @NonNull
- private List<File> getExtraDirs(@NonNull File extrasFolder) {
- List<File> extraDirs = new ArrayList<File>();
- // All OEM provided device profiles are in
- // $SDK/extras/$VENDOR/$ITEM/devices.xml
- if (extrasFolder != null && extrasFolder.isDirectory()) {
- for (File vendor : extrasFolder.listFiles()) {
- if (vendor.isDirectory()) {
- for (File item : vendor.listFiles()) {
- if (item.isDirectory() && isDevicesExtra(item)) {
- extraDirs.add(item);
- }
- }
- }
- }
- }
-
- return extraDirs;
- }
-
- /*
- * Returns whether a specific folder for a specific vendor is a
- * DeviceProfiles folder
- */
- private boolean isDevicesExtra(@NonNull File item) {
- File properties = new File(item, SdkConstants.FN_SOURCE_PROP);
- try {
- BufferedReader propertiesReader = new BufferedReader(new FileReader(properties));
- try {
- String line;
- while ((line = propertiesReader.readLine()) != null) {
- Matcher m = PATH_PROPERTY_PATTERN.matcher(line);
- if (m.matches()) {
- return true;
- }
- }
- } finally {
- propertiesReader.close();
- }
- } catch (IOException ignore) {
- }
- return false;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Storage.java b/base/sdklib/src/main/java/com/android/sdklib/devices/Storage.java
deleted file mode 100644
index 9ac2a7e..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/devices/Storage.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.devices;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-public class Storage {
- private long mNoBytes;
-
- public Storage(long amount, Unit unit) {
- mNoBytes = amount * unit.getNumberOfBytes();
- }
-
- public Storage(long amount) {
- this(amount, Unit.B);
- }
-
- /** Returns the amount of storage represented, in Bytes */
- public long getSize() {
- return getSizeAsUnit(Unit.B);
- }
-
- @NonNull
- public Storage deepCopy() {
- return new Storage(mNoBytes);
- }
-
- /**
- * Return the amount of storage represented by the instance in the given unit
- * @param unit The unit of the result.
- * @return The size of the storage in the given unit.
- */
- public long getSizeAsUnit(@NonNull Unit unit) {
- return mNoBytes / unit.getNumberOfBytes();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
-
- if (!(o instanceof Storage)) {
- return false;
- }
-
- Storage s = (Storage) o;
- if (s.getSize() == this.getSize()) {
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- return 31 * result + (int) (mNoBytes^(mNoBytes>>>32));
- }
-
- public enum Unit{
- B("B", "B", 1),
- KiB("KiB", "KB", 1024),
- MiB("MiB", "MB", 1024 * 1024),
- GiB("GiB", "GB", 1024 * 1024 * 1024),
- TiB("TiB", "TB", 1024l * 1024l * 1024l * 1024l);
-
- @NonNull
- private String mValue;
-
- @NonNull
- private String mDisplayValue;
-
- /** The number of bytes needed to have one of the given unit */
- private long mNoBytes;
-
- Unit(@NonNull String val, @NonNull String displayVal, long noBytes) {
- mValue = val;
- mDisplayValue = displayVal;
- mNoBytes = noBytes;
- }
-
- @Nullable
- public static Unit getEnum(@NonNull String val) {
- for (Unit v : values()) {
- if (v.mValue.equals(val)) {
- return v;
- }
- }
- return null;
- }
-
- public long getNumberOfBytes() {
- return mNoBytes;
- }
-
- @Override
- public String toString() {
- return mValue;
- }
-
- public String getDisplayValue() {
- return mDisplayValue;
- }
- }
-
- /**
- * Finds the largest {@link Unit} which can display the storage value as a positive integer
- * with no loss of accuracy.
- * @return The most appropriate {@link Unit}.
- */
- @NonNull
- public Unit getAppropriateUnits() {
- Unit optimalUnit = Unit.B;
- for (Unit unit : Unit.values()) {
- if (mNoBytes % unit.getNumberOfBytes() == 0) {
- optimalUnit = unit;
- } else {
- break;
- }
- }
- return optimalUnit;
- }
-
- @Override
- public String toString() {
- Unit u = getAppropriateUnits();
- return String.format("%d %s", getSizeAsUnit(u), u);
- }
-
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml b/base/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
deleted file mode 100644
index 52eef2d..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
+++ /dev/null
@@ -1,1073 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<d:devices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:d="http://schemas.android.com/sdk/devices/2">
-
- <d:device>
- <d:name>Nexus One</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>3.7</d:diagonal-length>
- <d:pixel-density>hdpi</d:pixel-density>
- <d:screen-ratio>long</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>480</d:x-dimension>
- <d:y-dimension>800</d:y-dimension>
- </d:dimensions>
- <d:xdpi>254</d:xdpi>
- <d:ydpi>254</d:ydpi>
- <d:touch>
- <d:multitouch>basic</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- </d:networking>
- <d:sensors>
- Accelerometer
- Compass
- GPS
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>trackball</d:nav>
- <d:ram unit="MiB">512</d:ram>
- <d:buttons>hard</d:buttons>
- <d:internal-storage unit="MiB">503</d:internal-storage>
- <d:removable-storage unit="MiB">0</d:removable-storage>
- <d:cpu>Qualcomm Scorpion</d:cpu>
- <d:gpu>Qualcomm Adreno 200</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_one</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>7-10</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
- <d:device>
- <d:name>Nexus S</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>4</d:diagonal-length>
- <d:pixel-density>hdpi</d:pixel-density>
- <d:screen-ratio>long</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>480</d:x-dimension>
- <d:y-dimension>800</d:y-dimension>
- </d:dimensions>
- <d:xdpi>235</d:xdpi>
- <d:ydpi>235</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Compass
- GPS
- Gyroscope
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="KiB">351428</d:ram>
- <d:buttons>hard</d:buttons>
- <d:internal-storage unit="MiB">503</d:internal-storage>
- <d:removable-storage unit="MiB">0</d:removable-storage>
- <d:cpu>Samsung Exynos 3110</d:cpu>
- <d:gpu>PowerVR SGX 540</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_s</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>9-16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>
- GL_EXT_debug_marker
- GL_OES_rgb8_rgba8
- GL_OES_depth24
- GL_OES_vertex_half_float
- GL_OES_texture_float
- GL_OES_texture_half_float
- GL_OES_element_index_uint
- GL_OES_mapbuffer
- GL_OES_fragment_precision_high
- GL_OES_compressed_ETC1_RGB8_texture
- GL_OES_EGL_image
- GL_OES_EGL_image_external
- GL_OES_required_internalformat
- GL_OES_depth_texture
- GL_OES_get_program_binary
- GL_OES_packed_depth_stencil
- GL_OES_standard_derivatives
- GL_OES_vertex_array_object
- GL_OES_egl_sync
- GL_EXT_multi_draw_arrays
- GL_EXT_texture_format_BGRA8888
- GL_EXT_discard_framebuffer
- GL_EXT_shader_texture_lod
- GL_IMG_shader_binary
- GL_IMG_texture_compression_pvrtc
- GL_IMG_texture_npot
- GL_IMG_texture_format_BGRA8888
- GL_IMG_read_format
- GL_IMG_program_binary
- GL_IMG_multisampled_render_to_texture
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Galaxy Nexus</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>4.65</d:diagonal-length> <!-- In inches -->
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>long</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>720</d:x-dimension>
- <d:y-dimension>1280</d:y-dimension>
- </d:dimensions>
- <d:xdpi>316</d:xdpi>
- <d:ydpi>316</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Bluetooth
- Wifi
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Gyroscope
- Compass
- GPS
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="GiB">1</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="GiB">16</d:internal-storage>
- <d:removable-storage unit="KiB"></d:removable-storage>
- <d:cpu>OMAP 4460</d:cpu> <!-- cpu type (Tegra3) freeform -->
- <d:gpu>PowerVR SGX540</d:gpu>
- <d:abi>
- armeabi
- armeabi-v7a
- </d:abi>
- <!--dock (car, desk, tv, none)-->
- <d:dock>
- </d:dock>
- <!-- plugged in (never, charge, always) -->
- <d:power-type>battery</d:power-type>
- <d:skin>galaxy_nexus</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>14-</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles>
- HSP
- HFP
- SPP
- A2DP
- AVRCP
- OPP
- PBAP
- GAVDP
- AVDTP
- HID
- HDP
- PAN
- </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <!--
- These can be gotten via
- javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
- -->
- <d:gl-extensions>
- GL_EXT_discard_framebuffer
- GL_EXT_multi_draw_arrays
- GL_EXT_shader_texture_lod
- GL_EXT_texture_format_BGRA8888
- GL_IMG_multisampled_render_to_texture
- GL_IMG_program_binary
- GL_IMG_read_format
- GL_IMG_shader_binary
- GL_IMG_texture_compression_pvrtc
- GL_IMG_texture_format_BGRA8888
- GL_IMG_texture_npot
- GL_OES_compressed_ETC1_RGB8_texture
- GL_OES_depth_texture
- GL_OES_depth24
- GL_OES_EGL_image
- GL_OES_EGL_image_external
- GL_OES_egl_sync
- GL_OES_element_index_uint
- GL_OES_fragment_precision_high
- GL_OES_get_program_binary
- GL_OES_mapbuffer
- GL_OES_packed_depth_stencil
- GL_OES_required_internalformat
- GL_OES_rgb8_rgba8
- GL_OES_standard_derivatives
- GL_OES_texture_float
- GL_OES_texture_half_float
- GL_OES_vertex_array_object
- GL_OES_vertex_half_float
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 7 (2012)</d:name>
- <d:id>Nexus 7</d:id>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>large</d:screen-size>
- <d:diagonal-length>7.0</d:diagonal-length>
- <d:pixel-density>tvdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>800</d:x-dimension>
- <d:y-dimension>1280</d:y-dimension>
- </d:dimensions>
- <d:xdpi>195</d:xdpi>
- <d:ydpi>200</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Compass
- GPS
- Gyroscope
- LightSensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="GiB">1</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="GiB">8</d:internal-storage>
- <d:removable-storage unit="MiB"> </d:removable-storage>
- <d:cpu> Tegra3 </d:cpu>
- <d:gpu> Tegra3 </d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_7</d:skin>
- </d:hardware>
-
- <d:software>
- <d:api-level>16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions> </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
-
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 4</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>4.7</d:diagonal-length>
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>768</d:x-dimension>
- <d:y-dimension>1280</d:y-dimension>
- </d:dimensions>
- <d:xdpi>320</d:xdpi>
- <d:ydpi>320</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Compass
- GPS
- Gyroscope
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="KiB">1953125</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="KiB">7811891</d:internal-storage>
- <d:removable-storage unit="MiB"></d:removable-storage>
- <d:cpu>Qualcomm Snapdragon S4 Pro</d:cpu>
- <d:gpu>Adreno 320</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock></d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_4</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles></d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>GL_EXT_debug_marker GL_AMD_compressed_ATC_texture
- GL_AMD_performance_monitor GL_AMD_program_binary_Z400 GL_EXT_robustness
- GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence
- GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
- GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
- GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
- GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_OES_standard_derivatives
- GL_OES_texture_3D GL_OES_texture_float GL_OES_texture_half_float
- GL_OES_texture_half_float_linear GL_OES_texture_npot GL_OES_vertex_half_float
- GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object GL_QCOM_alpha_test
- GL_QCOM_binning_control GL_QCOM_driver_control GL_QCOM_perfmon_global_mode
- GL_QCOM_extended_get GL_QCOM_extended_get2 GL_QCOM_tiled_rendering
- GL_QCOM_writeonly_rendering GL_EXT_sRGB
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 10</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>xlarge</d:screen-size>
- <d:diagonal-length>10.055</d:diagonal-length>
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>2560</d:x-dimension>
- <d:y-dimension>1600</d:y-dimension>
- </d:dimensions>
- <d:xdpi>300</d:xdpi>
- <d:ydpi>300</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Compass
- GPS
- Gyroscope
- LightSensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="KiB">1953125</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="KiB">15623782</d:internal-storage>
- <d:removable-storage unit="MiB"></d:removable-storage>
- <d:cpu>Dual-core A15</d:cpu>
- <d:gpu>Quad-core Mali T604</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock></d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_10</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles></d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>GL_EXT_debug_marker GL_ARM_rgba8 GL_ARM_mali_shader_binary
- GL_OES_depth24 GL_OES_depth_texture GL_OES_depth_texture_cube_map
- GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_EXT_read_format_bgra
- GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture
- GL_OES_standard_derivatives GL_OES_EGL_image GL_OES_EGL_image_external
- GL_OES_EGL_sync GL_OES_texture_npot GL_OES_vertex_half_float
- GL_OES_required_internalformat GL_OES_vertex_array_object GL_OES_mapbuffer
- GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg GL_EXT_texture_type_2_10_10_10_REV
- GL_OES_fbo_render_mipmap GL_OES_element_index_uint GL_EXT_shadow_samplers
- GL_EXT_occlusion_query_boolean GL_EXT_blend_minmax GL_EXT_discard_framebuffer
- GL_OES_get_program_binary GL_OES_texture_3D GL_EXT_texture_storage
- GL_EXT_multisampled_render_to_texture GL_OES_surfaceless_context
- GL_ARM_mali_program_binary
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape" default="true">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 7</d:name>
- <d:id>Nexus 7 2013</d:id>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>large</d:screen-size>
- <d:diagonal-length>7.02</d:diagonal-length>
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>1200</d:x-dimension>
- <d:y-dimension>1920</d:y-dimension>
- </d:dimensions>
- <d:xdpi>323</d:xdpi>
- <d:ydpi>323</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Compass
- GPS
- Gyroscope
- LightSensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="GiB">2</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="GiB">32</d:internal-storage>
- <d:removable-storage unit="MiB"> </d:removable-storage>
- <d:cpu> Qualcomm Snapdragon S4 Pro, 1.5GHz </d:cpu>
- <d:gpu> Adreno 320, 400MHz </d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock></d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_7_2013</d:skin>
- </d:hardware>
-
- <d:software>
- <d:api-level>18</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>3.0</d:gl-version>
- <d:gl-extensions>
- GL_AMD_compressed_ATC_texture GL_AMD_performance_monitor
- GL_AMD_program_binary_Z400 GL_EXT_debug_labelGL_EXT_debug_markerGL_EXT_robustness
- GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence
- GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
- GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
- GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
- GL_OES_packed_depth_stencil GL_OES_depth_texture_cube_map GL_OES_rgb8_rgba8
- GL_OES_standard_derivatives GL_OES_texture_3D GL_OES_texture_float
- GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot
- GL_OES_vertex_half_float GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object
- GL_QCOM_alpha_test GL_QCOM_binning_control GL_QCOM_driver_control
- GL_QCOM_perfmon_global_mode GL_QCOM_extended_get GL_QCOM_extended_get2
- GL_QCOM_tiled_rendering GL_QCOM_writeonly_rendering GL_EXT_sRGB
- GL_EXT_texture_filter_anisotropic GL_EXT_color_buffer_float
- GL_EXT_color_buffer_half_float
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
-
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 5</d:name>
- <d:id>Nexus 5</d:id>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>4.95</d:diagonal-length>
- <d:pixel-density>xxhdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>1080</d:x-dimension>
- <d:y-dimension>1920</d:y-dimension>
- </d:dimensions>
- <d:xdpi>445</d:xdpi>
- <d:ydpi>445</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Compass
- GPS
- Gyroscope
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="GiB">2</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="GiB">16</d:internal-storage>
- <d:removable-storage unit="MiB"></d:removable-storage>
- <d:cpu>Snapdragon 800 (MSM8974)</d:cpu>
- <d:gpu>Adreno 330</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock></d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_5</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>19</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles></d:bluetooth-profiles>
- <d:gl-version>3.0</d:gl-version>
- <d:gl-extensions>
- GL_AMD_compressed_ATC_texture GL_AMD_performance_monitor GL_AMD_program_binary_Z400
- GL_EXT_debug_label GL_EXT_debug_marker GL_EXT_discard_framebuffer
- GL_EXT_robustness GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV
- GL_NV_fence GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
- GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
- GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
- GL_OES_packed_depth_stencil GL_OES_depth_texture_cube_map GL_OES_rgb8_rgba8
- GL_OES_standard_derivatives GL_OES_texture_3D GL_OES_texture_float
- GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot
- GL_OES_vertex_half_float GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object
- GL_QCOM_alpha_test GL_QCOM_binning_control GL_QCOM_driver_control
- GL_QCOM_perfmon_global_mode GL_QCOM_extended_get GL_QCOM_extended_get2
- GL_QCOM_tiled_rendering GL_QCOM_writeonly_rendering GL_EXT_sRGB
- GL_EXT_texture_filter_anisotropic GL_EXT_color_buffer_float
- GL_EXT_color_buffer_half_float GL_EXT_disjoint_timer_query
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 6</d:name>
- <d:id>Nexus 6</d:id>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>5.96</d:diagonal-length>
- <d:pixel-density>560dpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>1440</d:x-dimension>
- <d:y-dimension>2560</d:y-dimension>
- </d:dimensions>
- <d:xdpi>493</d:xdpi>
- <d:ydpi>493</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Compass
- GPS
- Gyroscope
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="GiB">3</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="MiB">1968</d:internal-storage>
- <d:removable-storage unit="MiB">0</d:removable-storage>
- <d:cpu>Quad core Krait 450 CPU 2.7GHz (Qualcomm Snapdragon 805 SOC)</d:cpu>
- <d:gpu>Adreno 420</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_6</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>21</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>3.0</d:gl-version>
- <d:gl-extensions>
- GL_EXT_debug_marker GL_OES_EGL_image
- GL_OES_EGL_image_external GL_OES_EGL_sync
- GL_OES_vertex_half_float GL_OES_framebuffer_object
- GL_OES_rgb8_rgba8 GL_OES_compressed_ETC1_RGB8_texture
- GL_AMD_compressed_ATC_texture
- GL_KHR_texture_compression_astc_ldr
- GL_OES_texture_npot GL_EXT_texture_filter_anisotropic
- GL_EXT_texture_format_BGRA8888 GL_OES_texture_3D
- GL_EXT_color_buffer_float
- GL_EXT_color_buffer_half_float GL_QCOM_alpha_test
- GL_OES_depth24 GL_OES_packed_depth_stencil
- GL_OES_depth_texture GL_OES_depth_texture_cube_map
- GL_EXT_sRGB GL_OES_texture_float
- GL_OES_texture_float_linear GL_OES_texture_half_float
- GL_OES_texture_half_float_linear
- GL_EXT_texture_type_2_10_10_10_REV
- GL_EXT_texture_sRGB_decode GL_OES_element_index_uint
- GL_EXT_copy_image GL_EXT_geometry_shader
- GL_EXT_tessellation_shader GL_OES_texture_stencil8
- GL_EXT_shader_io_blocks GL_OES_shader_image_atomic
- GL_OES_sample_variables GL_EXT_texture_border_clamp
- GL_EXT_multisampled_render_to_texture
- GL_OES_shader_multisample_interpolation
- GL_EXT_draw_buffers_indexed GL_EXT_gpu_shader5
- GL_EXT_robustness GL_EXT_texture_buffer
- GL_OES_texture_storage_multisample_2d_array
- GL_OES_sample_shading GL_OES_get_program_binary
- GL_EXT_debug_label GL_KHR_blend_equation_advanced
- GL_KHR_blend_equation_advanced_coherent
- GL_QCOM_tiled_rendering
- GL_ANDROID_extension_pack_es31a
- GL_EXT_primitive_bounding_box
- GL_OES_standard_derivatives GL_OES_vertex_array_object
- GL_KHR_debug
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 9</d:name>
- <d:id>Nexus 9</d:id>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>xlarge</d:screen-size>
- <d:diagonal-length>8.86</d:diagonal-length>
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>2048</d:x-dimension>
- <d:y-dimension>1536</d:y-dimension>
- </d:dimensions>
- <d:xdpi>288.9949951171875</d:xdpi>
- <d:ydpi>288.9949951171875</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Compass
- GPS
- Gyroscope
- LightSensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="KiB">1879968</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="MiB">1545</d:internal-storage>
- <d:removable-storage unit="MiB">0</d:removable-storage>
- <d:cpu>64-bit NVIDIA Tegra K1 Dual Denver @ 2.3GHz</d:cpu>
- <d:gpu>192-core Kepler GPU</d:gpu>
- <d:abi>
- arm64-v8a
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>battery</d:power-type>
- <d:skin>nexus_9</d:skin>
- </d:hardware>
- <d:software>
- <d:api-level>21</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>3.1</d:gl-version>
- <d:gl-extensions>
- GL_EXT_debug_marker GL_EXT_blend_minmax
- GL_EXT_color_buffer_float
- GL_EXT_color_buffer_half_float GL_EXT_copy_image
- GL_EXT_debug_label GL_EXT_draw_buffers_indexed
- GL_EXT_frag_depth GL_EXT_geometry_point_size
- GL_EXT_geometry_shader GL_EXT_gpu_shader5
- GL_EXT_map_buffer_range GL_EXT_occlusion_query_boolean
- GL_EXT_primitive_bounding_box GL_EXT_robustness
- GL_EXT_separate_shader_objects
- GL_EXT_shader_implicit_conversions
- GL_EXT_shader_integer_mix GL_EXT_shader_io_blocks
- GL_EXT_shadow_samplers GL_EXT_sRGB
- GL_EXT_sRGB_write_control
- GL_EXT_tessellation_point_size
- GL_EXT_tessellation_shader GL_EXT_texture_border_clamp
- GL_EXT_texture_buffer GL_EXT_texture_compression_dxt1
- GL_EXT_texture_compression_s3tc
- GL_EXT_texture_cube_map_array
- GL_EXT_texture_filter_anisotropic
- GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg
- GL_EXT_texture_sRGB_decode GL_EXT_texture_storage
- GL_EXT_texture_view GL_EXT_unpack_subimage
- GL_KHR_debug GL_KHR_texture_compression_astc_ldr
- GL_NV_bgr GL_NV_bindless_texture
- GL_NV_blend_equation_advanced
- GL_NV_blend_equation_advanced_coherent
- GL_NV_copy_buffer GL_NV_copy_image GL_NV_draw_buffers
- GL_NV_draw_instanced GL_NV_draw_texture
- GL_NV_EGL_stream_consumer_external
- GL_NV_explicit_attrib_location
- GL_NV_fbo_color_attachments GL_NV_framebuffer_blit
- GL_NV_framebuffer_multisample
- GL_NV_generate_mipmap_sRGB GL_NV_instanced_arrays
- GL_NV_occlusion_query_samples
- GL_NV_non_square_matrices GL_NV_pack_subimage
- GL_NV_packed_float GL_NV_packed_float_linear
- GL_NV_path_rendering GL_NV_pixel_buffer_object
- GL_NV_read_buffer GL_NV_read_depth
- GL_NV_read_depth_stencil GL_NV_read_stencil
- GL_NV_secure_context GL_NV_shadow_samplers_array
- GL_NV_shadow_samplers_cube GL_NV_sRGB_formats
- GL_NV_texture_array GL_NV_texture_border_clamp
- GL_NV_texture_compression_latc
- GL_NV_texture_compression_s3tc
- GL_NV_texture_compression_s3tc_update
- GL_NV_timer_query GL_KHR_blend_equation_advanced
- GL_KHR_blend_equation_advanced_coherent
- GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth24
- GL_OES_depth32 GL_OES_depth_texture
- GL_OES_depth_texture_cube_map GL_OES_EGL_image
- GL_OES_EGL_image_external GL_OES_EGL_sync
- GL_OES_element_index_uint GL_OES_fbo_render_mipmap
- GL_OES_get_program_binary GL_OES_mapbuffer
- GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8
- GL_OES_sample_shading GL_OES_sample_variables
- GL_OES_shader_image_atomic
- GL_OES_shader_multisample_interpolation
- GL_OES_standard_derivatives GL_OES_surfaceless_context
- GL_OES_texture_npot GL_OES_texture_float
- GL_OES_texture_float_linear GL_OES_texture_half_float
- GL_OES_texture_half_float_linear
- GL_OES_texture_stencil8
- GL_OES_texture_storage_multisample_2d_array
- GL_OES_vertex_array_object GL_OES_vertex_half_float
- GL_ANDROID_extension_pack_es31a
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait">
- <d:description>The tablet in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape" default="true">
- <d:description>The tablet in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
-</d:devices>
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java b/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java
deleted file mode 100644
index 83a3e55..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib.internal.androidTarget;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * Represents an add-on target in the SDK.
- * An add-on extends a standard {@link PlatformTarget}.
- */
-public final class AddOnTarget implements IAndroidTarget {
-
- private final String mLocation;
- private final PlatformTarget mBasePlatform;
- private final String mName;
- private final ISystemImage[] mSystemImages;
- private final String mVendor;
- private final int mRevision;
- private final String mDescription;
- private final boolean mHasRenderingLibrary;
- private final boolean mHasRenderingResources;
-
- private File[] mSkins;
- private File mDefaultSkin;
- private ImmutableList<OptionalLibrary> mLibraries;
- private int mVendorId = NO_USB_ID;
-
- /**
- * Creates a new add-on
- * @param location the OS path location of the add-on
- * @param name the name of the add-on
- * @param vendor the vendor name of the add-on
- * @param revision the revision of the add-on
- * @param description the add-on description
- * @param systemImages list of supported system images. Can be null or empty.
- * @param libMap A map containing the optional libraries. The map key is the fully-qualified
- * library name. The value is a 2 string array with the .jar filename, and the description.
- * @param hasRenderingLibrary whether the addon has a custom layoutlib.jar
- * @param hasRenderingResources whether the add has custom framework resources.
- * @param basePlatform the platform the add-on is extending.
- */
- public AddOnTarget(
- String location,
- String name,
- String vendor,
- int revision,
- String description,
- ISystemImage[] systemImages,
- Map<String, String[]> libMap,
- boolean hasRenderingLibrary,
- boolean hasRenderingResources,
- PlatformTarget basePlatform) {
- if (!location.endsWith(File.separator)) {
- location = location + File.separator;
- }
-
- mLocation = location;
- mName = name;
- mVendor = vendor;
- mRevision = revision;
- mDescription = description;
- mHasRenderingLibrary = hasRenderingLibrary;
- mHasRenderingResources = hasRenderingResources;
- mBasePlatform = basePlatform;
-
- // If the add-on does not have any system-image of its own, the list here
- // is empty and it's up to the callers to query the parent platform.
- mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
- Arrays.sort(mSystemImages);
-
- // handle the optional libraries.
- if (libMap != null) {
- ImmutableList.Builder<OptionalLibrary> builder = ImmutableList.builder();
- for (Entry<String, String[]> entry : libMap.entrySet()) {
- String jarFile = entry.getValue()[0];
- String desc = entry.getValue()[1];
- builder.add(new OptionalLibraryImpl(
- entry.getKey(),
- new File(mLocation, SdkConstants.OS_ADDON_LIBS_FOLDER + jarFile),
- desc,
- true /*requireManifestEntry*/));
- }
- mLibraries = builder.build();
- } else {
- mLibraries = ImmutableList.of();
- }
- }
-
- @Override
- public String getLocation() {
- return mLocation;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- @Nullable
- public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
- for (ISystemImage sysImg : mSystemImages) {
- if (sysImg.getTag().equals(tag) && sysImg.getAbiType().equals(abiType)) {
- return sysImg;
- }
- }
- return null;
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- return mSystemImages;
- }
-
- @Override
- public String getVendor() {
- return mVendor;
- }
-
- @Override
- public String getFullName() {
- return String.format("%1$s (%2$s)", mName, mVendor);
- }
-
- @Override
- public String getClasspathName() {
- return String.format("%1$s [%2$s]", mName, mBasePlatform.getClasspathName());
- }
-
- @Override
- public String getShortClasspathName() {
- return String.format("%1$s [%2$s]", mName, mBasePlatform.getVersionName());
- }
-
- @Override
- public String getDescription() {
- return mDescription;
- }
-
- @NonNull
- @Override
- public AndroidVersion getVersion() {
- // this is always defined by the base platform
- return mBasePlatform.getVersion();
- }
-
- @Override
- public String getVersionName() {
- return mBasePlatform.getVersionName();
- }
-
- @Override
- public int getRevision() {
- return mRevision;
- }
-
- @Override
- public boolean isPlatform() {
- return false;
- }
-
- @Override
- public IAndroidTarget getParent() {
- return mBasePlatform;
- }
-
- @Override
- public String getPath(int pathId) {
- switch (pathId) {
- case SKINS:
- return mLocation + SdkConstants.OS_SKINS_FOLDER;
- case DOCS:
- return mLocation + SdkConstants.FD_DOCS + File.separator
- + SdkConstants.FD_DOCS_REFERENCE;
-
- case LAYOUT_LIB:
- if (mHasRenderingLibrary) {
- return mLocation + SdkConstants.FD_DATA + File.separator
- + SdkConstants.FN_LAYOUTLIB_JAR;
- }
- return mBasePlatform.getPath(pathId);
-
- case RESOURCES:
- if (mHasRenderingResources) {
- return mLocation + SdkConstants.FD_DATA + File.separator
- + SdkConstants.FD_RES;
- }
- return mBasePlatform.getPath(pathId);
-
- case FONTS:
- if (mHasRenderingResources) {
- return mLocation + SdkConstants.FD_DATA + File.separator
- + SdkConstants.FD_FONTS;
- }
- return mBasePlatform.getPath(pathId);
-
- case SAMPLES:
- // only return the add-on samples folder if there is actually a sample (or more)
- File sampleLoc = new File(mLocation, SdkConstants.FD_SAMPLES);
- if (sampleLoc.isDirectory()) {
- File[] files = sampleLoc.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.isDirectory();
- }
-
- });
- if (files != null && files.length > 0) {
- return sampleLoc.getAbsolutePath();
- }
- }
- //$FALL-THROUGH$
- default :
- return mBasePlatform.getPath(pathId);
- }
- }
-
- @Override
- public File getFile(int pathId) {
- return new File(getPath(pathId));
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- return mBasePlatform.getBuildToolInfo();
- }
-
- @Override @NonNull
- public List<String> getBootClasspath() {
- return mBasePlatform.getBootClasspath();
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getOptionalLibraries() {
- return mBasePlatform.getOptionalLibraries();
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getAdditionalLibraries() {
- return mLibraries;
- }
-
- @Override
- public boolean hasRenderingLibrary() {
- return mHasRenderingLibrary || mHasRenderingResources;
- }
-
- @NonNull
- @Override
- public File[] getSkins() {
- return mSkins;
- }
-
- @Nullable
- @Override
- public File getDefaultSkin() {
- return mDefaultSkin;
- }
-
- /**
- * Returns the list of libraries of the underlying platform.
- *
- * {@inheritDoc}
- */
- @Override
- public String[] getPlatformLibraries() {
- return mBasePlatform.getPlatformLibraries();
- }
-
- @Override
- public String getProperty(String name) {
- return mBasePlatform.getProperty(name);
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- return mBasePlatform.getProperty(name, defaultValue);
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- return mBasePlatform.getProperty(name, defaultValue);
- }
-
- @Override
- public Map<String, String> getProperties() {
- return mBasePlatform.getProperties();
- }
-
- @Override
- public int getUsbVendorId() {
- return mVendorId;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- // basic test
- if (target == this) {
- return true;
- }
-
- /*
- * The method javadoc indicates:
- * Returns whether the given target is compatible with the receiver.
- * <p/>A target is considered compatible if applications developed for the receiver can
- * run on the given target.
- */
-
- // The receiver is an add-on. There are 2 big use cases: The add-on has libraries
- // or the add-on doesn't (in which case we consider it a platform).
- if (mLibraries.isEmpty()) {
- return mBasePlatform.canRunOn(target);
- } else {
- // the only targets that can run the receiver are the same add-on in the same or later
- // versions.
- // first check: vendor/name
- if (!mVendor.equals(target.getVendor()) || !mName.equals(target.getName())) {
- return false;
- }
-
- // now check the version. At this point since we checked the add-on part,
- // we can revert to the basic check on version/codename which are done by the
- // base platform already.
- return mBasePlatform.canRunOn(target);
- }
-
- }
-
- @Override
- public String hashString() {
- return String.format(AndroidTargetHash.ADD_ON_FORMAT, mVendor, mName,
- mBasePlatform.getVersion().getApiString());
- }
-
- @Override
- public int hashCode() {
- return hashString().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof AddOnTarget) {
- AddOnTarget addon = (AddOnTarget)obj;
-
- return mVendor.equals(addon.mVendor) && mName.equals(addon.mName) &&
- mBasePlatform.getVersion().equals(addon.mBasePlatform.getVersion());
- }
-
- return false;
- }
-
- /*
- * Order by API level (preview/n count as between n and n+1).
- * At the same API level, order as: Platform first, then add-on ordered by vendor and then name
- * (non-Javadoc)
- * @see java.lang.Comparable#compareTo(java.lang.Object)
- */
- @Override
- public int compareTo(@NonNull IAndroidTarget target) {
- // quick check.
- if (this == target) {
- return 0;
- }
-
- int versionDiff = getVersion().compareTo(target.getVersion());
-
- // only if the version are the same do we care about platform/add-ons.
- if (versionDiff == 0) {
- // platforms go before add-ons.
- if (target.isPlatform()) {
- return +1;
- } else {
- AddOnTarget targetAddOn = (AddOnTarget)target;
-
- // both are add-ons of the same version. Compare per vendor then by name
- int vendorDiff = mVendor.compareTo(targetAddOn.mVendor);
- if (vendorDiff == 0) {
- return mName.compareTo(targetAddOn.mName);
- } else {
- return vendorDiff;
- }
- }
-
- }
-
- return versionDiff;
- }
-
- /**
- * Returns a string representation suitable for debugging.
- * The representation is not intended for display to the user.
- *
- * The representation is also purposely compact. It does not describe _all_ the properties
- * of the target, only a few key ones.
- *
- * @see #getDescription()
- */
- @Override
- public String toString() {
- return String.format("AddonTarget %1$s rev %2$d (based on %3$s)", //$NON-NLS-1$
- getVersion(),
- getRevision(),
- getParent().toString());
- }
-
- // ---- local methods.
-
- public void setSkins(@NonNull File[] skins, @NonNull File defaultSkin) {
- mDefaultSkin = defaultSkin;
-
- // we mix the add-on and base platform skins
- HashSet<File> skinSet = new HashSet<File>();
- skinSet.addAll(Arrays.asList(skins));
- skinSet.addAll(Arrays.asList(mBasePlatform.getSkins()));
-
- mSkins = skinSet.toArray(new File[skinSet.size()]);
- Arrays.sort(mSkins);
- }
-
- /**
- * Sets the USB vendor id in the add-on.
- */
- public void setUsbVendorId(int vendorId) {
- if (vendorId == 0) {
- throw new IllegalArgumentException( "VendorId must be > 0");
- }
-
- mVendorId = vendorId;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/MissingTarget.java b/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/MissingTarget.java
deleted file mode 100644
index 4454040..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/MissingTarget.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.androidTarget;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SdkVersionInfo;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A target that we don't have, but is referenced (e.g. by a system image).
- */
-public class MissingTarget implements IAndroidTarget {
-
- private final String mVendor;
-
- private final AndroidVersion mVersion;
-
- private final List<ISystemImage> mSystemImages = Lists.newArrayList();
-
- private final String mName;
-
- public MissingTarget(String vendor, String name, AndroidVersion version) {
- mVendor = vendor;
- mVersion = version;
- mName = name;
- }
-
- @Override
- public String getLocation() {
- return null;
- }
-
- @Override
- public String getVendor() {
- return mVendor;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public String getFullName() {
- return getName();
- }
-
- @Override
- public String getClasspathName() {
- return null;
- }
-
- @Override
- public String getShortClasspathName() {
- return null;
- }
-
- @Override
- public String getDescription() {
- return null;
- }
-
- @NonNull
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- @Override
- public String getVersionName() {
- return SdkVersionInfo.getAndroidName(getVersion().getApiLevel());
- }
-
- @Override
- public int getRevision() {
- return 0;
- }
-
- @Override
- public boolean isPlatform() {
- return mVendor == null;
- }
-
- @Override
- public IAndroidTarget getParent() {
- return null;
- }
-
- @Override
- public String getPath(int pathId) {
- return null;
- }
-
- @Override
- public File getFile(int pathId) {
- return null;
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- return null;
- }
-
- @NonNull
- @Override
- public List<String> getBootClasspath() {
- return ImmutableList.of();
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getOptionalLibraries() {
- return ImmutableList.of();
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getAdditionalLibraries() {
- return ImmutableList.of();
- }
-
- @Override
- public boolean hasRenderingLibrary() {
- return false;
- }
-
- @NonNull
- @Override
- public File[] getSkins() {
- return new File[0];
- }
-
- @Nullable
- @Override
- public File getDefaultSkin() {
- return null;
- }
-
- @Override
- public String[] getPlatformLibraries() {
- return new String[0];
- }
-
- @Override
- public String getProperty(String name) {
- return null;
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- return null;
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- return null;
- }
-
- @Override
- public Map<String, String> getProperties() {
- return null;
- }
-
- @Override
- public int getUsbVendorId() {
- return 0;
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- return mSystemImages.toArray(new ISystemImage[mSystemImages.size()]);
- }
-
- public void addSystemImage(ISystemImage image) {
- mSystemImages.add(image);
- }
-
- @Nullable
- @Override
- public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
- for (ISystemImage image : mSystemImages) {
- if (tag.equals(image.getTag()) && abiType.equals(image.getAbiType())) {
- return image;
- }
- }
- return null;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- return false;
- }
-
- @Override
- public String hashString() {
- return AndroidTargetHash.getTargetHashString(this);
- }
-
- @Override
- public int compareTo(IAndroidTarget o) {
- return 0;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof MissingTarget)) {
- return false;
- }
- MissingTarget other = (MissingTarget) obj;
- return Objects.equal(mVendor, other.mVendor) && Objects.equal(mVersion, other.mVersion);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mVendor, mVersion);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/OptionalLibraryImpl.java b/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/OptionalLibraryImpl.java
deleted file mode 100644
index 97f7eff..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/OptionalLibraryImpl.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.androidTarget;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.IAndroidTarget;
-
-import java.io.File;
-
-/**
- * Internal implementation of OptionalLibrary
- */
-public class OptionalLibraryImpl implements IAndroidTarget.OptionalLibrary {
-
- @NonNull
- private final String mLibraryName;
- @NonNull
- private final File mJarFile;
- @NonNull
- private final String mDescription;
- private final boolean mRequireManifestEntry;
-
- public OptionalLibraryImpl(
- @NonNull String libraryName,
- @NonNull File jarFile,
- @NonNull String description,
- boolean requireManifestEntry) {
- mLibraryName = libraryName;
- mJarFile = jarFile;
- mDescription = description;
- mRequireManifestEntry = requireManifestEntry;
- }
-
- @Override
- @NonNull
- public String getName() {
- return mLibraryName;
- }
-
- @Override
- @NonNull
- public File getJar() {
- return mJarFile;
- }
-
- @Override
- @NonNull
- public String getDescription() {
- return mDescription;
- }
-
- @Override
- public boolean isManifestEntryRequired() {
- return mRequireManifestEntry;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java b/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java
deleted file mode 100644
index cdd5a9e..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib.internal.androidTarget;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SdkManager.LayoutlibVersion;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.utils.SparseArray;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Represents a platform target in the SDK.
- */
-public final class PlatformTarget implements IAndroidTarget {
-
- private static final String PLATFORM_VENDOR = "Android Open Source Project";
-
- private static final String PLATFORM_NAME = "Android %s";
- private static final String PLATFORM_NAME_PREVIEW = "Android %s (Preview)";
-
- /** the OS path to the root folder of the platform component. */
- private final String mRootFolderOsPath;
- private final String mName;
- private final AndroidVersion mVersion;
- private final String mVersionName;
- private final int mRevision;
- private final Map<String, String> mProperties;
- private final SparseArray<String> mPaths = new SparseArray<String>();
- private File[] mSkins;
- private final ISystemImage[] mSystemImages;
- private final List<OptionalLibrary> mOptionalLibraries;
- private final LayoutlibVersion mLayoutlibVersion;
- private final BuildToolInfo mBuildToolInfo;
-
- /**
- * Creates a Platform target.
- *
- * @param sdkOsPath the root folder of the SDK
- * @param platformOSPath the root folder of the platform component
- * @param apiVersion the API Level + codename.
- * @param versionName the version name of the platform.
- * @param revision the revision of the platform component.
- * @param layoutlibVersion The {@link LayoutlibVersion}. May be null.
- * @param systemImages list of supported system images
- * @param properties the platform properties
- */
- public PlatformTarget(
- String sdkOsPath,
- String platformOSPath,
- AndroidVersion apiVersion,
- String versionName,
- int revision,
- LayoutlibVersion layoutlibVersion,
- ISystemImage[] systemImages,
- Map<String, String> properties,
- List<OptionalLibrary> optionalLibraries,
- @NonNull BuildToolInfo buildToolInfo) {
- if (!platformOSPath.endsWith(File.separator)) {
- platformOSPath = platformOSPath + File.separator;
- }
- mRootFolderOsPath = platformOSPath;
- mProperties = Collections.unmodifiableMap(properties);
- mVersion = apiVersion;
- mVersionName = versionName;
- mRevision = revision;
- mLayoutlibVersion = layoutlibVersion;
- mBuildToolInfo = buildToolInfo;
- mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
- Arrays.sort(mSystemImages);
- mOptionalLibraries = ImmutableList.copyOf(optionalLibraries);
-
- if (mVersion.isPreview()) {
- mName = String.format(PLATFORM_NAME_PREVIEW, mVersionName);
- } else {
- mName = String.format(PLATFORM_NAME, mVersionName);
- }
-
- // pre-build the path to the platform components
- mPaths.put(ANDROID_JAR, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_LIBRARY);
- mPaths.put(UI_AUTOMATOR_JAR, mRootFolderOsPath + SdkConstants.FN_UI_AUTOMATOR_LIBRARY);
- mPaths.put(SOURCES, mRootFolderOsPath + SdkConstants.FD_ANDROID_SOURCES);
- mPaths.put(ANDROID_AIDL, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_AIDL);
- mPaths.put(SAMPLES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_SAMPLES_FOLDER);
- mPaths.put(SKINS, mRootFolderOsPath + SdkConstants.OS_SKINS_FOLDER);
- mPaths.put(TEMPLATES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_TEMPLATES_FOLDER);
- mPaths.put(DATA, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER);
- mPaths.put(ATTRIBUTES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_ATTRS_XML);
- mPaths.put(MANIFEST_ATTRIBUTES,
- mRootFolderOsPath + SdkConstants.OS_PLATFORM_ATTRS_MANIFEST_XML);
- mPaths.put(RESOURCES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_RESOURCES_FOLDER);
- mPaths.put(FONTS, mRootFolderOsPath + SdkConstants.OS_PLATFORM_FONTS_FOLDER);
- mPaths.put(LAYOUT_LIB, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_LAYOUTLIB_JAR);
- mPaths.put(WIDGETS, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_WIDGETS);
- mPaths.put(ACTIONS_ACTIVITY, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_ACTIONS_ACTIVITY);
- mPaths.put(ACTIONS_BROADCAST, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_ACTIONS_BROADCAST);
- mPaths.put(ACTIONS_SERVICE, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_ACTIONS_SERVICE);
- mPaths.put(CATEGORIES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_CATEGORIES);
- mPaths.put(ANT, mRootFolderOsPath + SdkConstants.OS_PLATFORM_ANT_FOLDER);
- }
-
- /**
- * Returns the {@link LayoutlibVersion}. May be null.
- */
- public LayoutlibVersion getLayoutlibVersion() {
- return mLayoutlibVersion;
- }
-
- @Override
- @Nullable
- public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
- for (ISystemImage sysImg : mSystemImages) {
- if (sysImg.getTag().equals(tag) && sysImg.getAbiType().equals(abiType)) {
- return sysImg;
- }
- }
- return null;
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- return mSystemImages;
- }
-
- @Override
- public String getLocation() {
- return mRootFolderOsPath;
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * For Platform, the vendor name is always "Android".
- *
- * @see com.android.sdklib.IAndroidTarget#getVendor()
- */
- @Override
- public String getVendor() {
- return PLATFORM_VENDOR;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public String getFullName() {
- return mName;
- }
-
- @Override
- public String getClasspathName() {
- return mName;
- }
-
- @Override
- public String getShortClasspathName() {
- return mName;
- }
-
- /*
- * (non-Javadoc)
- *
- * Description for the Android platform is dynamically generated.
- *
- * @see com.android.sdklib.IAndroidTarget#getDescription()
- */
- @Override
- public String getDescription() {
- return String.format("Standard Android platform %s", mVersionName);
- }
-
- @NonNull
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- @Override
- public String getVersionName() {
- return mVersionName;
- }
-
- @Override
- public int getRevision() {
- return mRevision;
- }
-
- @Override
- public boolean isPlatform() {
- return true;
- }
-
- @Override
- public IAndroidTarget getParent() {
- return null;
- }
-
- @Override
- public String getPath(int pathId) {
- return mPaths.get(pathId);
- }
-
- @Override
- public File getFile(int pathId) {
- return new File(getPath(pathId));
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- return mBuildToolInfo;
- }
-
- @Override @NonNull
- public List<String> getBootClasspath() {
- return ImmutableList.of(getPath(IAndroidTarget.ANDROID_JAR));
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getOptionalLibraries() {
- return mOptionalLibraries;
- }
-
- /**
- * Always returns null, as a standard platform has no additional libraries.
- *
- * {@inheritDoc}
- * @see com.android.sdklib.IAndroidTarget#getAdditionalLibraries()
- */
- @NonNull
- @Override
- public List<OptionalLibrary> getAdditionalLibraries() {
- return ImmutableList.of();
- }
-
- /**
- * Returns whether the target is able to render layouts. This is always true for platforms.
- */
- @Override
- public boolean hasRenderingLibrary() {
- return true;
- }
-
- @NonNull
- @Override
- public File[] getSkins() {
- return mSkins;
- }
-
- @Nullable
- @Override
- public File getDefaultSkin() {
- // only one skin? easy.
- if (mSkins.length == 1) {
- return mSkins[0];
- }
-
- // look for the skin name in the platform props
- String skinName = mProperties.get(SdkConstants.PROP_SDK_DEFAULT_SKIN);
- if (skinName == null) {
- // otherwise try to find a good default.
- if (mVersion.getApiLevel() >= 4) {
- // at this time, this is the default skin for all older platforms that had 2+ skins.
- skinName = "WVGA800"; //$NON-NLS-1$
- } else {
- skinName = "HVGA"; // this is for 1.5 and earlier. //$NON-NLS-1$
- }
- }
-
- return new File(getFile(IAndroidTarget.SKINS), skinName);
- }
-
- /**
- * Currently always return a fixed list with "android.test.runner" in it.
- * <p/>
- * TODO change the fixed library list to be build-dependent later.
- * {@inheritDoc}
- */
- @Override
- public String[] getPlatformLibraries() {
- return new String[] { SdkConstants.ANDROID_TEST_RUNNER_LIB };
- }
-
- /**
- * The platform has no USB Vendor Id: always return {@link IAndroidTarget#NO_USB_ID}.
- * {@inheritDoc}
- */
- @Override
- public int getUsbVendorId() {
- return NO_USB_ID;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- // basic test
- if (target == this) {
- return true;
- }
-
- // if the platform has a codename (ie it's a preview of an upcoming platform), then
- // both platforms must be exactly identical.
- if (mVersion.getCodename() != null) {
- return mVersion.equals(target.getVersion());
- }
-
- // target is compatible wit the receiver as long as its api version number is greater or
- // equal.
- return target.getVersion().getApiLevel() >= mVersion.getApiLevel();
- }
-
- @Override
- public String hashString() {
- return AndroidTargetHash.getPlatformHashString(mVersion);
- }
-
- @Override
- public int hashCode() {
- return hashString().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof PlatformTarget) {
- PlatformTarget platform = (PlatformTarget)obj;
-
- return mVersion.equals(platform.getVersion());
- }
-
- return false;
- }
-
- /*
- * Order by API level (preview/n count as between n and n+1).
- * At the same API level, order as: Platform first, then add-on ordered by vendor and then name
- * (non-Javadoc)
- * @see java.lang.Comparable#compareTo(java.lang.Object)
- */
- @Override
- public int compareTo(IAndroidTarget target) {
- // quick check.
- if (this == target) {
- return 0;
- }
-
- int versionDiff = mVersion.compareTo(target.getVersion());
-
- // only if the version are the same do we care about add-ons.
- if (versionDiff == 0) {
- // platforms go before add-ons.
- if (target.isPlatform() == false) {
- return -1;
- }
- }
-
- return versionDiff;
- }
-
- /**
- * Returns a string representation suitable for debugging.
- * The representation is not intended for display to the user.
- *
- * The representation is also purposely compact. It does not describe _all_ the properties
- * of the target, only a few key ones.
- *
- * @see #getDescription()
- */
- @Override
- public String toString() {
- return String.format("PlatformTarget %1$s rev %2$d", //$NON-NLS-1$
- getVersion(),
- getRevision());
- }
-
- @Override
- public String getProperty(String name) {
- return mProperties.get(name);
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- try {
- String value = getProperty(name);
- if (value != null) {
- return Integer.decode(value);
- }
- } catch (NumberFormatException e) {
- // ignore, return default value;
- }
-
- return defaultValue;
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- String value = getProperty(name);
- if (value != null) {
- return Boolean.valueOf(value);
- }
-
- return defaultValue;
- }
-
- @Override
- public Map<String, String> getProperties() {
- return mProperties; // mProperties is unmodifiable.
- }
-
- // ---- platform only methods.
-
- public void setSkins(@NonNull File[] skins) {
- mSkins = skins;
- Arrays.sort(mSkins);
- }
-
- public void setSamplesPath(String osLocation) {
- mPaths.put(SAMPLES, osLocation);
- }
-
- public void setSourcesPath(String osLocation) {
- mPaths.put(SOURCES, osLocation);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java b/base/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java
deleted file mode 100755
index 1c682b3..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib.internal.avd;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.devices.Abi;
-import com.android.sdklib.devices.Device;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * An immutable structure describing an Android Virtual Device.
- */
-public final class AvdInfo implements Comparable<AvdInfo> {
-
- /**
- * Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid.
- */
- public enum AvdStatus {
- /** No error */
- OK,
- /** Missing 'path' property in the ini file */
- ERROR_PATH,
- /** Missing config.ini file in the AVD data folder */
- ERROR_CONFIG,
- /** Missing 'target' property in the ini file */
- ERROR_TARGET_HASH,
- /** Target was not resolved from its hash */
- ERROR_TARGET,
- /** Unable to parse config.ini */
- ERROR_PROPERTIES,
- /** System Image folder in config.ini doesn't exist */
- ERROR_IMAGE_DIR,
- /** The {@link Device} this AVD is based on has changed from its original configuration*/
- ERROR_DEVICE_CHANGED,
- /** The {@link Device} this AVD is based on is no longer available */
- ERROR_DEVICE_MISSING,
- /** the {@link SystemImage} this AVD is based on is no longer available */
- ERROR_IMAGE_MISSING
- }
-
- private final String mName;
- private final File mIniFile;
- private final String mFolderPath;
- private final String mTargetHash;
- private final IAndroidTarget mTarget;
- private final String mAbiType;
- /** An immutable map of properties. This must not be modified. Map can be empty. Never null. */
- private final Map<String, String> mProperties;
- private final AvdStatus mStatus;
- private final IdDisplay mTag;
-
- /**
- * Creates a new valid AVD info. Values are immutable.
- * <p/>
- * Such an AVD is available and can be used.
- * The error string is set to null.
- *
- * @param name The name of the AVD (for display or reference)
- * @param iniFile The path to the config.ini file
- * @param folderPath The path to the data directory
- * @param targetHash the target hash
- * @param target The target. Can be null, if the target was not resolved.
- * @param tag The tag id/display.
- * @param abiType Name of the abi.
- * @param properties The property map. If null, an empty map will be created.
- */
- public AvdInfo(@NonNull String name,
- @NonNull File iniFile,
- @NonNull String folderPath,
- @NonNull String targetHash,
- @Nullable IAndroidTarget target,
- @NonNull IdDisplay tag,
- @NonNull String abiType,
- @Nullable Map<String, String> properties) {
- this(name, iniFile, folderPath,
- targetHash, target, tag, abiType,
- properties, AvdStatus.OK);
- }
-
- /**
- * Creates a new <em>invalid</em> AVD info. Values are immutable.
- * <p/>
- * Such an AVD is not complete and cannot be used.
- * The error string must be non-null.
- *
- * @param name The name of the AVD (for display or reference)
- * @param iniFile The path to the config.ini file
- * @param folderPath The path to the data directory
- * @param targetHash the target hash
- * @param target The target. Can be null, if the target was not resolved.
- * @param tag The tag id/display.
- * @param abiType Name of the abi.
- * @param properties The property map. If null, an empty map will be created.
- * @param status The {@link AvdStatus} of this AVD. Cannot be null.
- */
- public AvdInfo(@NonNull String name,
- @NonNull File iniFile,
- @NonNull String folderPath,
- @NonNull String targetHash,
- @Nullable IAndroidTarget target,
- @NonNull IdDisplay tag,
- @NonNull String abiType,
- @Nullable Map<String, String> properties,
- @NonNull AvdStatus status) {
- mName = name;
- mIniFile = iniFile;
- mFolderPath = folderPath;
- mTargetHash = targetHash;
- mTarget = target;
- mTag = tag;
- mAbiType = abiType;
- mProperties = properties == null ? Collections.<String, String>emptyMap()
- : Collections.unmodifiableMap(properties);
- mStatus = status;
- }
-
- /** Returns the name of the AVD. */
- @NonNull
- public String getName() {
- return mName;
- }
-
- /** Returns the path of the AVD data directory. */
- @NonNull
- public String getDataFolderPath() {
- return mFolderPath;
- }
-
- /** Returns the tag id/display of the AVD. */
- @NonNull
- public IdDisplay getTag() {
- return mTag;
- }
-
- /** Returns the processor type of the AVD. */
- @NonNull
- public String getAbiType() {
- return mAbiType;
- }
-
- @NonNull
- public String getCpuArch() {
- String cpuArch = mProperties.get(AvdManager.AVD_INI_CPU_ARCH);
- if (cpuArch != null) {
- return cpuArch;
- }
-
- // legacy
- return SdkConstants.CPU_ARCH_ARM;
- }
-
- @NonNull
- public String getDeviceManufacturer() {
- String deviceManufacturer = mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
- if (deviceManufacturer != null && !deviceManufacturer.isEmpty()) {
- return deviceManufacturer;
- }
-
- return ""; // $NON-NLS-1$
- }
-
- @NonNull
- public String getDeviceName() {
- String deviceName = mProperties.get(AvdManager.AVD_INI_DEVICE_NAME);
- if (deviceName != null && !deviceName.isEmpty()) {
- return deviceName;
- }
-
- return ""; // $NON-NLS-1$
- }
-
- /** Convenience function to return a more user friendly name of the abi type. */
- @NonNull
- public static String getPrettyAbiType(@NonNull AvdInfo avdInfo) {
- return getPrettyAbiType(avdInfo.getTag(), avdInfo.getAbiType());
- }
-
- /** Convenience function to return a more user friendly name of the abi type. */
- @NonNull
- public static String getPrettyAbiType(@NonNull ISystemImage sysImg) {
- return getPrettyAbiType(sysImg.getTag(), sysImg.getAbiType());
- }
-
- /** Convenience function to return a more user friendly name of the abi type. */
- @NonNull
- public static String getPrettyAbiType(@NonNull IdDisplay tag, @NonNull String rawAbi) {
- String s = ""; // $NON-NLS-1$
-
- if (!SystemImage.DEFAULT_TAG.equals(tag)) {
- s = tag.getDisplay() + ' ';
- }
-
- Abi abi = Abi.getEnum(rawAbi);
- s += (abi == null ? rawAbi : abi.getDisplayName()) + " (" + rawAbi + ')';
-
- return s;
- }
-
- /**
- * Returns the target hash string.
- */
- @NonNull
- public String getTargetHash() {
- return mTargetHash;
- }
-
- /** Returns the target of the AVD, or <code>null</code> if it has not been resolved. */
- @Nullable
- public IAndroidTarget getTarget() {
- return mTarget;
- }
-
- /** Returns the {@link AvdStatus} of the receiver. */
- @NonNull
- public AvdStatus getStatus() {
- return mStatus;
- }
-
- /**
- * Helper method that returns the default AVD folder that would be used for a given
- * AVD name <em>if and only if</em> the AVD was created with the default choice.
- * <p/>
- * Callers must NOT use this to "guess" the actual folder from an actual AVD since
- * the purpose of the AVD .ini file is to be able to change this folder. Callers
- * should however use this to create a new {@link AvdInfo} to setup its data folder
- * to the default.
- * <p/>
- * The default is {@code getDefaultAvdFolder()/avdname.avd/}.
- * <p/>
- * For an actual existing AVD, callers must use {@link #getDataFolderPath()} instead.
- *
- * @param manager The AVD Manager, used to get the AVD storage path.
- * @param avdName The name of the desired AVD.
- * @param unique Whether to return the default or a unique variation of the default.
- * @throws AndroidLocationException if there's a problem getting android root directory.
- */
- @NonNull
- public static File getDefaultAvdFolder(@NonNull AvdManager manager, @NonNull String avdName,
- boolean unique)
- throws AndroidLocationException {
- String base = manager.getBaseAvdFolder();
- File result = new File(base, avdName + AvdManager.AVD_FOLDER_EXTENSION);
- if (unique) {
- int suffix = 0;
- while (result.exists()) {
- result = new File(base, String.format("%s_%d%s", avdName, (++suffix),
- AvdManager.AVD_FOLDER_EXTENSION));
- }
- }
- return result;
- }
-
- /** Compatibility forwarding until the usages in tools/swt are updated */
- @Deprecated
- @NonNull
- public static File getDefaultAvdFolder(@NonNull AvdManager manager, @NonNull String avdName)
- throws AndroidLocationException{
- return getDefaultAvdFolder(manager, avdName, false);
- }
-
- /**
- * Helper method that returns the .ini {@link File} for a given AVD name.
- * <p/>
- * The default is {@code getDefaultAvdFolder()/avdname.ini}.
- *
- * @param manager The AVD Manager, used to get the AVD storage path.
- * @param avdName The name of the desired AVD.
- * @throws AndroidLocationException if there's a problem getting android root directory.
- */
- @NonNull
- public static File getDefaultIniFile(@NonNull AvdManager manager, @NonNull String avdName)
- throws AndroidLocationException {
- String avdRoot = manager.getBaseAvdFolder();
- return new File(avdRoot, avdName + AvdManager.INI_EXTENSION);
- }
-
- /**
- * Returns the .ini {@link File} for this AVD.
- */
- @NonNull
- public File getIniFile() {
- return mIniFile;
- }
-
- /**
- * Helper method that returns the Config {@link File} for a given AVD name.
- */
- @NonNull
- public static File getConfigFile(@NonNull String path) {
- return new File(path, AvdManager.CONFIG_INI);
- }
-
- /**
- * Returns the Config {@link File} for this AVD.
- */
- @NonNull
- public File getConfigFile() {
- return getConfigFile(mFolderPath);
- }
-
- /**
- * Returns an unmodifiable map of properties for the AVD.
- * This can be empty but not null.
- * Callers must NOT try to modify this immutable map.
- */
- @NonNull
- public Map<String, String> getProperties() {
- return mProperties;
- }
-
- /**
- * Returns the error message for the AVD or <code>null</code> if {@link #getStatus()}
- * returns {@link AvdStatus#OK}
- */
- @Nullable
- public String getErrorMessage() {
- switch (mStatus) {
- case ERROR_PATH:
- return String.format("Missing AVD 'path' property in %1$s", getIniFile());
- case ERROR_CONFIG:
- return String.format("Missing config.ini file in %1$s", mFolderPath);
- case ERROR_TARGET_HASH:
- return String.format("Missing 'target' property in %1$s", getIniFile());
- case ERROR_TARGET:
- return String.format("Unknown target '%1$s' in %2$s",
- mTargetHash, getIniFile());
- case ERROR_PROPERTIES:
- return String.format("Failed to parse properties from %1$s",
- getConfigFile());
- case ERROR_IMAGE_DIR:
- return String.format(
- "Missing system image for %1$s%2$s %3$s. Run 'android update avd -n %4$s'",
- SystemImage.DEFAULT_TAG.equals(mTag) ? "" : (mTag.getDisplay() + " "),
- mAbiType,
- mTarget.getFullName(),
- mName);
- case ERROR_DEVICE_CHANGED:
- return String.format("%1$s %2$s configuration has changed since AVD creation",
- mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
- mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
- case ERROR_DEVICE_MISSING:
- return String.format("%1$s %2$s no longer exists as a device",
- mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
- mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
- case OK:
- assert false;
- return null;
- }
-
- return null;
- }
-
- /**
- * Compares this object with the specified object for order. Returns a
- * negative integer, zero, or a positive integer as this object is less
- * than, equal to, or greater than the specified object.
- *
- * @param o the Object to be compared.
- * @return a negative integer, zero, or a positive integer as this object is
- * less than, equal to, or greater than the specified object.
- */
- @Override
- public int compareTo(AvdInfo o) {
- // first handle possible missing targets (if the AVD failed to load for unresolved targets)
- if (mTarget == null && o != null && o.mTarget == null) {
- return 0;
- } if (mTarget == null) {
- return +1;
- } else if (o == null || o.mTarget == null) {
- return -1;
- }
-
- // then compare the targets
- int targetDiff = mTarget.compareTo(o.mTarget);
-
- if (targetDiff == 0) {
- // same target? compare on the avd name
- return mName.compareTo(o.mName);
- }
-
- return targetDiff;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java b/base/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
deleted file mode 100644
index 6e9ab71..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
+++ /dev/null
@@ -1,2196 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib.internal.avd;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.io.FileWrapper;
-import com.android.io.IAbstractFile;
-import com.android.io.StreamException;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.devices.Abi;
-import com.android.sdklib.devices.Device;
-import com.android.sdklib.devices.DeviceManager;
-import com.android.sdklib.devices.DeviceManager.DeviceStatus;
-import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.local.LocalSdk;
-import com.android.sdklib.repository.local.LocalSysImgPkgInfo;
-import com.android.utils.GrabProcessOutput;
-import com.android.utils.GrabProcessOutput.IProcessOutput;
-import com.android.utils.GrabProcessOutput.Wait;
-import com.android.utils.ILogger;
-import com.android.utils.NullLogger;
-import com.android.utils.Pair;
-import com.google.common.base.Charsets;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Android Virtual Device Manager to manage AVDs.
- */
-public class AvdManager {
-
- /**
- * Exception thrown when something is wrong with a target path.
- */
- private static final class InvalidTargetPathException extends Exception {
- private static final long serialVersionUID = 1L;
-
- InvalidTargetPathException(String message) {
- super(message);
- }
- }
-
- private static final Pattern INI_LINE_PATTERN =
- Pattern.compile("^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$"); //$NON-NLS-1$
-
- public static final String AVD_FOLDER_EXTENSION = ".avd"; //$NON-NLS-1$
-
- /** Charset encoding used by the avd.ini/config.ini. */
- public static final String AVD_INI_ENCODING = "avd.ini.encoding"; //$NON-NLS-1$
-
- /**
- * The *absolute* path to the AVD folder (which contains the #CONFIG_INI file).
- */
- public static final String AVD_INFO_ABS_PATH = "path"; //$NON-NLS-1$
-
- /**
- * The path to the AVD folder (which contains the #CONFIG_INI file) relative to
- * the {@link AndroidLocation#FOLDER_DOT_ANDROID}. This information is written
- * in the avd ini <b>only</b> if the AVD folder is located under the .android path
- * (that is the relative that has no backward {@code ..} references).
- */
- public static final String AVD_INFO_REL_PATH = "path.rel"; //$NON-NLS-1$
-
- /**
- * The {@link IAndroidTarget#hashString()} of the AVD.
- */
- public static final String AVD_INFO_TARGET = "target"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the tag id of the specific avd
- */
- public static final String AVD_INI_TAG_ID = "tag.id"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the tag display of the specific avd
- */
- public static final String AVD_INI_TAG_DISPLAY = "tag.display"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the abi type of the specific avd
- */
- public static final String AVD_INI_ABI_TYPE = "abi.type"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the CPU architecture of the specific avd
- */
- public static final String AVD_INI_CPU_ARCH = "hw.cpu.arch"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the CPU architecture of the specific avd
- */
- public static final String AVD_INI_CPU_MODEL = "hw.cpu.model"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the manufacturer of the device this avd was based on.
- */
- public static final String AVD_INI_DEVICE_MANUFACTURER = "hw.device.manufacturer"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the name of the device this avd was based on.
- */
- public static final String AVD_INI_DEVICE_NAME = "hw.device.name"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the SDK-relative path of the skin folder, if any,
- * or a 320x480 like constant for a numeric skin size.
- *
- * @see #NUMERIC_SKIN_SIZE
- */
- public static final String AVD_INI_SKIN_PATH = "skin.path"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the SDK-relative path of the skin folder to be selected if
- * skins for this device become enabled.
- */
- public static final String AVD_INI_BACKUP_SKIN_PATH = "skin.path.backup"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing an UI name for the skin.
- * This config key is ignored by the emulator. It is only used by the SDK manager or
- * tools to give a friendlier name to the skin.
- * If missing, use the {@link #AVD_INI_SKIN_PATH} key instead.
- */
- public static final String AVD_INI_SKIN_NAME = "skin.name"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing whether a dynamic skin should be displayed.
- */
- public static final String AVD_INI_SKIN_DYNAMIC = "skin.dynamic"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the path to the sdcard file.
- * If missing, the default name "sdcard.img" will be used for the sdcard, if there's such
- * a file.
- *
- * @see #SDCARD_IMG
- */
- public static final String AVD_INI_SDCARD_PATH = "sdcard.path"; //$NON-NLS-1$
- /**
- * AVD/config.ini key name representing the size of the SD card.
- * This property is for UI purposes only. It is not used by the emulator.
- *
- * @see #SDCARD_SIZE_PATTERN
- * @see #parseSdcardSize(String, String[])
- */
- public static final String AVD_INI_SDCARD_SIZE = "sdcard.size"; //$NON-NLS-1$
- /**
- * AVD/config.ini key name representing the first path where the emulator looks
- * for system images. Typically this is the path to the add-on system image or
- * the path to the platform system image if there's no add-on.
- * <p/>
- * The emulator looks at {@link #AVD_INI_IMAGES_1} before {@link #AVD_INI_IMAGES_2}.
- */
- public static final String AVD_INI_IMAGES_1 = "image.sysdir.1"; //$NON-NLS-1$
- /**
- * AVD/config.ini key name representing the second path where the emulator looks
- * for system images. Typically this is the path to the platform system image.
- *
- * @see #AVD_INI_IMAGES_1
- */
- public static final String AVD_INI_IMAGES_2 = "image.sysdir.2"; //$NON-NLS-1$
- /**
- * AVD/config.ini key name representing the presence of the snapshots file.
- * This property is for UI purposes only. It is not used by the emulator.
- *
- * @see #SNAPSHOTS_IMG
- */
- public static final String AVD_INI_SNAPSHOT_PRESENT = "snapshot.present"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing whether hardware OpenGLES emulation is enabled
- */
- public static final String AVD_INI_GPU_EMULATION = "hw.gpu.enabled"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing how to emulate the front facing camera
- */
- public static final String AVD_INI_CAMERA_FRONT = "hw.camera.front"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing how to emulate the rear facing camera
- */
- public static final String AVD_INI_CAMERA_BACK = "hw.camera.back"; //$NON-NLS-1$
-
- /**
- * AVD/config.ini key name representing the amount of RAM the emulated device should have
- */
- public static final String AVD_INI_RAM_SIZE = "hw.ramSize";
-
- /**
- * AVD/config.ini key name representing the amount of memory available to applications by default
- */
- public static final String AVD_INI_VM_HEAP_SIZE = "vm.heapSize";
-
- /**
- * AVD/config.ini key name representing the size of the data partition
- */
- public static final String AVD_INI_DATA_PARTITION_SIZE = "disk.dataPartition.size";
-
- /**
- * AVD/config.ini key name representing the hash of the device this AVD is based on. <br/>
- * This old hash is deprecated and shouldn't be used anymore.
- * It represents the Device.hashCode() and is not stable accross implementations.
- * @see #AVD_INI_DEVICE_HASH_V2
- */
- public static final String AVD_INI_DEVICE_HASH_V1 = "hw.device.hash";
-
- /**
- * AVD/config.ini key name representing the hash of the device hardware properties
- * actually present in the config.ini. This replaces {@link #AVD_INI_DEVICE_HASH_V1}.
- * <p/>
- * To find this hash, use
- * {@code DeviceManager.getHardwareProperties(device).get(AVD_INI_DEVICE_HASH_V2)}.
- */
- public static final String AVD_INI_DEVICE_HASH_V2 = "hw.device.hash2";
-
- /**
- * Pattern to match pixel-sized skin "names", e.g. "320x480".
- */
- public static final Pattern NUMERIC_SKIN_SIZE = Pattern.compile("([0-9]{2,})x([0-9]{2,})"); //$NON-NLS-1$
-
- private static final String USERDATA_IMG = "userdata.img"; //$NON-NLS-1$
- private static final String BOOT_PROP = "boot.prop"; //$NON-NLS-1$
- static final String CONFIG_INI = "config.ini"; //$NON-NLS-1$
- private static final String SDCARD_IMG = "sdcard.img"; //$NON-NLS-1$
- private static final String SNAPSHOTS_IMG = "snapshots.img"; //$NON-NLS-1$
-
- static final String INI_EXTENSION = ".ini"; //$NON-NLS-1$
- private static final Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + //$NON-NLS-1$
- INI_EXTENSION + "$", //$NON-NLS-1$
- Pattern.CASE_INSENSITIVE);
-
- private static final Pattern IMAGE_NAME_PATTERN = Pattern.compile("(.+)\\.img$", //$NON-NLS-1$
- Pattern.CASE_INSENSITIVE);
-
- /**
- * Pattern for matching SD Card sizes, e.g. "4K" or "16M".
- * Callers should use {@link #parseSdcardSize(String, String[])} instead of using this directly.
- */
- private static final Pattern SDCARD_SIZE_PATTERN = Pattern.compile("(\\d+)([KMG])"); //$NON-NLS-1$
-
- /**
- * Minimal size of an SDCard image file in bytes. Currently 9 MiB.
- */
-
- public static final long SDCARD_MIN_BYTE_SIZE = 9<<20;
- /**
- * Maximal size of an SDCard image file in bytes. Currently 1023 GiB.
- */
- public static final long SDCARD_MAX_BYTE_SIZE = 1023L<<30;
-
- /** The sdcard string represents a valid number but the size is outside of the allowed range. */
- public static final int SDCARD_SIZE_NOT_IN_RANGE = 0;
- /** The sdcard string looks like a size number+suffix but the number failed to decode. */
- public static final int SDCARD_SIZE_INVALID = -1;
- /** The sdcard string doesn't look like a size, it might be a path instead. */
- public static final int SDCARD_NOT_SIZE_PATTERN = -2;
-
- /** Regex used to validate characters that compose an AVD name. */
- public static final Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+"); //$NON-NLS-1$
-
- /** List of valid characters for an AVD name. Used for display purposes. */
- public static final String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -"; //$NON-NLS-1$
-
- public static final String HARDWARE_INI = "hardware.ini"; //$NON-NLS-1$
-
- /**
- * Status returned by {@link AvdManager#isAvdNameConflicting(String)}.
- */
- public enum AvdConflict {
- /** There is no known conflict for the given AVD name. */
- NO_CONFLICT,
- /** The AVD name conflicts with an existing valid AVD. */
- CONFLICT_EXISTING_AVD,
- /** The AVD name conflicts with an existing invalid AVD. */
- CONFLICT_INVALID_AVD,
- /**
- * The AVD name does not conflict with any known AVD however there are
- * files or directory that would cause a conflict if this were to be created.
- */
- CONFLICT_EXISTING_PATH,
- }
-
- // A map where the keys are the locations of the SDK and the values are the corresponding
- // AvdManagers. This prevents us from creating multiple AvdManagers for the same SDK and having
- // them get out of sync.
- private static final Map<String, AvdManager> mManagers =
- Collections.synchronizedMap(new WeakHashMap<String, AvdManager>());
-
- private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
- private AvdInfo[] mValidAvdList;
- private AvdInfo[] mBrokenAvdList;
- private final LocalSdk myLocalSdk;
- private final Map<ILogger, DeviceManager> myDeviceManagers =
- new HashMap<ILogger, DeviceManager>();
-
- /**
- * Creates an AVD Manager for a given SDK represented by a {@link LocalSdk}.
- * @param localSdk The SDK.
- * @param log The log object to receive the log of the initial loading of the AVDs.
- * This log object is not kept by this instance of AvdManager and each
- * method takes its own logger. The rationale is that the AvdManager
- * might be called from a variety of context, each with different
- * logging needs. Cannot be null.
- * @throws AndroidLocationException
- */
- protected AvdManager(@NonNull LocalSdk localSdk, @NonNull ILogger log)
- throws AndroidLocationException {
- myLocalSdk = localSdk;
- buildAvdList(mAllAvdList, log);
- }
-
- /**
- * Returns an AVD Manager for a given SDK represented by a {@link LocalSdk}.
- * One AVD Manager instance is created by SDK location and then cached and reused.
- *
- * @param localSdk The SDK.
- * @param log The log object to receive the log of the initial loading of the AVDs.
- * This log object is not kept by this instance of AvdManager and each
- * method takes its own logger. The rationale is that the AvdManager
- * might be called from a variety of context, each with different
- * logging needs. Cannot be null.
- * @throws AndroidLocationException
- */
- @NonNull
- public static AvdManager getInstance(@NonNull LocalSdk localSdk, @NonNull ILogger log)
- throws AndroidLocationException {
- synchronized(mManagers) {
- AvdManager manager;
- if ((manager = mManagers.get(localSdk.getLocation().getPath())) != null) {
- return manager;
- }
- manager = new AvdManager(localSdk, log);
-
- mManagers.put(localSdk.getLocation().getPath(), manager);
- return manager;
- }
- }
-
- /**
- * Returns the base folder where AVDs are created.
- *
- * @throws AndroidLocationException
- */
- @NonNull
- public String getBaseAvdFolder() throws AndroidLocationException {
- assert AndroidLocation.getFolder().endsWith(File.separator);
- return AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
- }
-
- /**
- * Returns the {@link LocalSdk} associated with the {@link AvdManager}.
- */
- @NonNull
- public LocalSdk getLocalSdk() {
- return myLocalSdk;
- }
-
- /**
- * Returns the {@link SdkManager} associated with the {@link AvdManager}.
- * Note: This is temporary and will be removed as SdkManager is phased out.
- * TODO: Remove this when SdkManager is removed
- */
- @NonNull
- @Deprecated
- public SdkManager getSdkManager() {
- return SdkManager.createManager(myLocalSdk.getPath(), NullLogger.getLogger());
- }
-
- /**
- * Parse the sdcard string to decode the size.
- * Returns:
- * <ul>
- * <li> The size in bytes > 0 if the sdcard string is a valid size in the allowed range.
- * <li> {@link #SDCARD_SIZE_NOT_IN_RANGE} (0)
- * if the sdcard string is a valid size NOT in the allowed range.
- * <li> {@link #SDCARD_SIZE_INVALID} (-1)
- * if the sdcard string is number that fails to parse correctly.
- * <li> {@link #SDCARD_NOT_SIZE_PATTERN} (-2)
- * if the sdcard string is not a number, in which case it's probably a file path.
- * </ul>
- *
- * @param sdcard The sdcard string, which can be a file path, a size string or something else.
- * @param parsedStrings If non-null, an array of 2 strings. The first string will be
- * filled with the parsed numeric size and the second one will be filled with the
- * parsed suffix. This is filled even if the returned size is deemed out of range or
- * failed to parse. The values are null if the sdcard is not a size pattern.
- * @return A size in byte if > 0, or {@link #SDCARD_SIZE_NOT_IN_RANGE},
- * {@link #SDCARD_SIZE_INVALID} or {@link #SDCARD_NOT_SIZE_PATTERN} as error codes.
- */
- public static long parseSdcardSize(@NonNull String sdcard, @Nullable String[] parsedStrings) {
-
- if (parsedStrings != null) {
- assert parsedStrings.length == 2;
- parsedStrings[0] = null;
- parsedStrings[1] = null;
- }
-
- Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard);
- if (m.matches()) {
- if (parsedStrings != null) {
- assert parsedStrings.length == 2;
- parsedStrings[0] = m.group(1);
- parsedStrings[1] = m.group(2);
- }
-
- // get the sdcard values for checks
- try {
- long sdcardSize = Long.parseLong(m.group(1));
-
- String sdcardSizeModifier = m.group(2);
- if ("K".equals(sdcardSizeModifier)) { //$NON-NLS-1$
- sdcardSize <<= 10;
- } else if ("M".equals(sdcardSizeModifier)) { //$NON-NLS-1$
- sdcardSize <<= 20;
- } else if ("G".equals(sdcardSizeModifier)) { //$NON-NLS-1$
- sdcardSize <<= 30;
- }
-
- if (sdcardSize < SDCARD_MIN_BYTE_SIZE ||
- sdcardSize > SDCARD_MAX_BYTE_SIZE) {
- return SDCARD_SIZE_NOT_IN_RANGE;
- }
-
- return sdcardSize;
- } catch (NumberFormatException e) {
- // This could happen if the number is too large to fit in a long.
- return SDCARD_SIZE_INVALID;
- }
- }
-
- return SDCARD_NOT_SIZE_PATTERN;
- }
-
- /**
- * Returns all the existing AVDs.
- * @return a newly allocated array containing all the AVDs.
- */
- @NonNull
- public AvdInfo[] getAllAvds() {
- synchronized (mAllAvdList) {
- return mAllAvdList.toArray(new AvdInfo[mAllAvdList.size()]);
- }
- }
-
- /**
- * Returns all the valid AVDs.
- * @return a newly allocated array containing all valid the AVDs.
- */
- @NonNull
- public AvdInfo[] getValidAvds() {
- synchronized (mAllAvdList) {
- if (mValidAvdList == null) {
- ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
- for (AvdInfo avd : mAllAvdList) {
- if (avd.getStatus() == AvdStatus.OK) {
- list.add(avd);
- }
- }
-
- mValidAvdList = list.toArray(new AvdInfo[list.size()]);
- }
- return mValidAvdList;
- }
- }
-
- /**
- * Returns all the broken AVDs.
- * @return a newly allocated array containing all the broken AVDs.
- */
- @NonNull
- public AvdInfo[] getBrokenAvds() {
- synchronized (mAllAvdList) {
- if (mBrokenAvdList == null) {
- ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
- for (AvdInfo avd : mAllAvdList) {
- if (avd.getStatus() != AvdStatus.OK) {
- list.add(avd);
- }
- }
- mBrokenAvdList = list.toArray(new AvdInfo[list.size()]);
- }
- return mBrokenAvdList;
- }
- }
-
- /**
- * Returns the {@link AvdInfo} matching the given <var>name</var>.
- * <p/>
- * The search is case-insensitive.
- *
- * @param name the name of the AVD to return
- * @param validAvdOnly if <code>true</code>, only look through the list of valid AVDs.
- * @return the matching AvdInfo or <code>null</code> if none were found.
- */
- @Nullable
- public AvdInfo getAvd(@Nullable String name, boolean validAvdOnly) {
-
- boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS;
-
- if (validAvdOnly) {
- for (AvdInfo info : getValidAvds()) {
- String name2 = info.getName();
- if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
- return info;
- }
- }
- } else {
- synchronized (mAllAvdList) {
- for (AvdInfo info : mAllAvdList) {
- String name2 = info.getName();
- if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
- return info;
- }
- }
- }
- }
-
- return null;
- }
-
- /**
- * Returns whether an emulator is currently running the AVD.
- */
- public boolean isAvdRunning(@NonNull AvdInfo info) {
- try {
- String pid = getAvdPid(info);
- if (pid != null) {
- String command;
- if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
- command = "cmd /c \"tasklist /FI \"PID eq " + pid + "\" | findstr " + pid
- + "\"";
- } else {
- command = "kill -0 " + pid;
- }
- try {
- Process p = Runtime.getRuntime().exec(command);
- // If the process ends with non-0 it means the process doesn't exist
- return p.waitFor() == 0;
- } catch (IOException e) {
- // To be safe return true
- return true;
- } catch (InterruptedException e) {
- // To be safe return true
- return true;
- }
- }
- }
- catch (IOException e) {
- // To be safe return true
- return true;
- }
- return false;
- }
-
- public void stopAvd(@NonNull AvdInfo info) {
- try {
- String pid = getAvdPid(info);
- if (pid != null) {
- String command;
- if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
- command = "cmd /c \"taskkill /PID " + pid + "\"";
- } else {
- command = "kill " + pid;
- }
- try {
- Process p = Runtime.getRuntime().exec(command);
- // If the process ends with non-0 it means the process doesn't exist
- p.waitFor();
- } catch (IOException e) {
- } catch (InterruptedException e) {
- }
- }
- }
- catch (IOException e) {
- }
- }
-
- private String getAvdPid(@NonNull AvdInfo info) throws IOException {
- // this is a file on Unix, and a directory on Windows.
- File f = new File(info.getDataFolderPath(), "userdata-qemu.img.lock"); //$NON-NLS-1$
- if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
- f = new File(f, "pid");
- }
- if (f.exists()) {
- return Files.toString(f, Charsets.UTF_8);
- }
- return null;
- }
-
-
-
- /**
- * Returns whether this AVD name would generate a conflict.
- *
- * @param name the name of the AVD to return
- * @return A pair of {@link AvdConflict} and the path or AVD name that conflicts.
- */
- @NonNull
- public Pair<AvdConflict, String> isAvdNameConflicting(@Nullable String name) {
-
- boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS;
-
- // Check whether we have a conflict with an existing or invalid AVD
- // known to the manager.
- synchronized (mAllAvdList) {
- for (AvdInfo info : mAllAvdList) {
- String name2 = info.getName();
- if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
- if (info.getStatus() == AvdStatus.OK) {
- return Pair.of(AvdConflict.CONFLICT_EXISTING_AVD, name2);
- } else {
- return Pair.of(AvdConflict.CONFLICT_INVALID_AVD, name2);
- }
- }
- }
- }
-
- // No conflict with known AVDs.
- // Are some existing files/folders in the way of creating this AVD?
-
- try {
- File file = AvdInfo.getDefaultIniFile(this, name);
- if (file.exists()) {
- return Pair.of(AvdConflict.CONFLICT_EXISTING_PATH, file.getPath());
- }
-
- file = AvdInfo.getDefaultAvdFolder(this, name, false);
- if (file.exists()) {
- return Pair.of(AvdConflict.CONFLICT_EXISTING_PATH, file.getPath());
- }
-
- } catch (AndroidLocationException e) {
- // ignore
- }
-
-
- return Pair.of(AvdConflict.NO_CONFLICT, null);
- }
-
- /**
- * Reloads the AVD list.
- * @param log the log object to receive action logs. Cannot be null.
- * @throws AndroidLocationException if there was an error finding the location of the
- * AVD folder.
- */
- public void reloadAvds(@NonNull ILogger log) throws AndroidLocationException {
- // build the list in a temp list first, in case the method throws an exception.
- // It's better than deleting the whole list before reading the new one.
- ArrayList<AvdInfo> allList = new ArrayList<AvdInfo>();
- buildAvdList(allList, log);
-
- synchronized (mAllAvdList) {
- mAllAvdList.clear();
- mAllAvdList.addAll(allList);
- mValidAvdList = mBrokenAvdList = null;
- }
- }
-
- /**
- * Creates a new AVD. It is expected that there is no existing AVD with this name already.
- *
- * @param avdFolder the data folder for the AVD. It will be created as needed.
- * Unless you want to locate it in a specific directory, the ideal default is
- * {@code AvdManager.AvdInfo.getAvdFolder}.
- * @param avdName the name of the AVD
- * @param target the target of the AVD
- * @param tag the tag of the AVD
- * @param abiType the abi type of the AVD
- * @param skinFolder the skin folder path to use, if specified. Can be null.
- * @param skinName the name of the skin. Can be null. Must have been verified by caller.
- * Can be a size in the form "NNNxMMM" or a directory name matching skinFolder.
- * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
- * an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
- * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults.
- * @param bootProps the optional boot properties for the AVD. Can be null.
- * @param createSnapshot If true copy a blank snapshot image into the AVD.
- * @param removePrevious If true remove any previous files.
- * @param editExisting If true, edit an existing AVD, changing only the minimum required.
- * This won't remove files unless required or unless {@code removePrevious} is set.
- * @param log the log object to receive action logs. Cannot be null.
- * @return The new {@link AvdInfo} in case of success (which has just been added to the
- * internal list) or null in case of failure.
- */
- @Nullable
- public AvdInfo createAvd(
- @NonNull File avdFolder,
- @NonNull String avdName,
- @NonNull IAndroidTarget target,
- @NonNull IdDisplay tag,
- @NonNull String abiType,
- @Nullable File skinFolder,
- @Nullable String skinName,
- @Nullable String sdcard,
- @Nullable Map<String,String> hardwareConfig,
- @Nullable Map<String,String> bootProps,
- boolean createSnapshot,
- boolean removePrevious,
- boolean editExisting,
- @NonNull ILogger log) {
- if (log == null) {
- throw new IllegalArgumentException("log cannot be null");
- }
-
- File iniFile = null;
- boolean needCleanup = false;
- try {
- if (avdFolder.exists()) {
- if (removePrevious) {
- // AVD already exists and removePrevious is set, try to remove the
- // directory's content first (but not the directory itself).
- try {
- deleteContentOf(avdFolder);
- } catch (SecurityException e) {
- log.error(e, "Failed to delete %1$s", avdFolder.getAbsolutePath());
- }
- } else if (!editExisting) {
- // AVD shouldn't already exist if removePrevious is false and
- // we're not editing an existing AVD.
- log.error(null,
- "Folder %1$s is in the way. Use --force if you want to overwrite.",
- avdFolder.getAbsolutePath());
- return null;
- }
- } else {
- // create the AVD folder.
- avdFolder.mkdir();
- // We're not editing an existing AVD.
- editExisting = false;
- }
-
- // actually write the ini file
- iniFile = createAvdIniFile(avdName, avdFolder, target, removePrevious);
-
- // writes the userdata.img in it.
-
- File userdataSrc = null;
-
- // Look for a system image in the add-on.
- // If we don't find one there, look in the base platform.
- ISystemImage systemImage = target.getSystemImage(tag, abiType);
-
- if (systemImage != null) {
- File imageFolder = systemImage.getLocation();
- userdataSrc = new File(imageFolder, USERDATA_IMG);
- }
-
- if ((userdataSrc == null || !userdataSrc.exists()) && !target.isPlatform()) {
- // If we don't find a system-image in the add-on, look into the platform.
-
- systemImage = target.getParent().getSystemImage(tag, abiType);
- if (systemImage != null) {
- File imageFolder = systemImage.getLocation();
- userdataSrc = new File(imageFolder, USERDATA_IMG);
- }
- }
-
- if (userdataSrc == null || !userdataSrc.exists()) {
- log.error(null,
- "Unable to find a '%1$s' file for ABI %2$s to copy into the AVD folder.",
- USERDATA_IMG,
- abiType);
- needCleanup = true;
- return null;
- }
-
- File userdataDest = new File(avdFolder, USERDATA_IMG);
-
- copyImageFile(userdataSrc, userdataDest);
-
- if (userdataDest.exists() == false) {
- log.error(null, "Unable to create '%1$s' file in the AVD folder.",
- userdataDest);
- needCleanup = true;
- return null;
- }
-
- // Config file.
- HashMap<String, String> values = new HashMap<String, String>();
-
- if (setImagePathProperties(target, tag, abiType, values, log) == false) {
- log.error(null, "Failed to set image path properties in the AVD folder.");
- needCleanup = true;
- return null;
- }
-
- // Create the snapshot file
- if (createSnapshot) {
- File snapshotDest = new File(avdFolder, SNAPSHOTS_IMG);
- if (snapshotDest.isFile() && editExisting) {
- log.info("Snapshot image already present, was not changed.\n");
-
- } else {
- File toolsLib = new File(myLocalSdk.getLocation(),
- SdkConstants.OS_SDK_TOOLS_LIB_EMULATOR_FOLDER);
- File snapshotBlank = new File(toolsLib, SNAPSHOTS_IMG);
- if (snapshotBlank.exists() == false) {
- log.error(null,
- "Unable to find a '%2$s%1$s' file to copy into the AVD folder.",
- SNAPSHOTS_IMG, toolsLib);
- needCleanup = true;
- return null;
- }
-
- copyImageFile(snapshotBlank, snapshotDest);
- }
- values.put(AVD_INI_SNAPSHOT_PRESENT, "true");
- }
-
- // Now the tag & abi type
- values.put(AVD_INI_TAG_ID, tag.getId());
- values.put(AVD_INI_TAG_DISPLAY, tag.getDisplay());
- values.put(AVD_INI_ABI_TYPE, abiType);
-
- // and the cpu arch.
- Abi abi = Abi.getEnum(abiType);
- if (abi != null) {
- values.put(AVD_INI_CPU_ARCH, abi.getCpuArch());
-
- String model = abi.getCpuModel();
- if (model != null) {
- values.put(AVD_INI_CPU_MODEL, model);
- }
- } else {
- log.error(null,
- "ABI %1$s is not supported by this version of the SDK Tools", abiType);
- needCleanup = true;
- return null;
- }
-
- // Now the skin.
- String skinPath = null;
-
- if (skinFolder == null && skinName == null) {
- // Nothing specified. Use the default from the target.
- skinFolder = target.getDefaultSkin();
- }
-
- if (skinFolder == null && skinName != null &&
- NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
- // Numeric skin size. Set both skinPath and skinName to the same size.
- skinPath = skinName;
-
- } else if (skinFolder != null && skinName == null) {
- // Skin folder is specified, but not skin name. Adjust it.
- skinName = skinFolder.getName();
-
- } else if (skinFolder == null && skinName != null) {
- // skin folder is not specified, but there's a non-numeric skin name.
- // check whether the skin is in the target.
- skinFolder = getSkinFolder(skinName, target);
- }
-
- if (skinFolder != null) {
- // skin does not exist!
- if (!skinFolder.exists()) {
- log.error(null, "Skin '%1$s' does not exist.", skinName);
- return null;
- }
-
- // if skinFolder is in the sdk, use the relative path
- if (skinFolder.getPath().startsWith(myLocalSdk.getLocation().getPath())) {
- try {
- skinPath = FileOp.makeRelative(myLocalSdk.getLocation(), skinFolder);
- } catch (IOException e) {
- // In case it fails, just use the absolute path
- skinPath = skinFolder.getAbsolutePath();
- }
- } else {
- // Skin isn't in the sdk. Just use the absolute path.
- skinPath = skinFolder.getAbsolutePath();
- }
- }
-
- // Set skin.name for display purposes in the AVD manager and
- // set skin.path for use by the emulator.
- if (skinName != null) {
- values.put(AVD_INI_SKIN_NAME, skinName);
- }
- if (skinPath != null) {
- values.put(AVD_INI_SKIN_PATH, skinPath);
- }
-
- if (sdcard != null && !sdcard.isEmpty()) {
- // Sdcard is possibly a size. In that case we create a file called 'sdcard.img'
- // in the AVD folder, and do not put any value in config.ini.
-
- long sdcardSize = parseSdcardSize(sdcard, null/*parsedStrings*/);
-
- if (sdcardSize == SDCARD_SIZE_NOT_IN_RANGE) {
- log.error(null, "SD Card size must be in the range 9 MiB..1023 GiB.");
- needCleanup = true;
- return null;
-
- } else if (sdcardSize == SDCARD_SIZE_INVALID) {
- log.error(null, "Unable to parse SD Card size");
- needCleanup = true;
- return null;
-
- } else if (sdcardSize == SDCARD_NOT_SIZE_PATTERN) {
- File sdcardFile = new File(sdcard);
- if (sdcardFile.isFile()) {
- // sdcard value is an external sdcard, so we put its path into the config.ini
- values.put(AVD_INI_SDCARD_PATH, sdcard);
- } else {
- log.error(null, "'%1$s' is not recognized as a valid sdcard value.\n"
- + "Value should be:\n" + "1. path to an sdcard.\n"
- + "2. size of the sdcard to create: <size>[K|M]", sdcard);
- needCleanup = true;
- return null;
- }
- } else {
- // create the sdcard.
- File sdcardFile = new File(avdFolder, SDCARD_IMG);
-
- boolean runMkSdcard = true;
- if (sdcardFile.exists()) {
- if (sdcardFile.length() == sdcardSize && editExisting) {
- // There's already an sdcard file with the right size and we're
- // not overriding it... so don't remove it.
- runMkSdcard = false;
- log.info("SD Card already present with same size, was not changed.\n");
- }
- }
-
- if (runMkSdcard) {
- String path = sdcardFile.getAbsolutePath();
-
- // execute mksdcard with the proper parameters.
- File toolsFolder = new File(myLocalSdk.getLocation(),
- SdkConstants.FD_TOOLS);
- File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
-
- if (mkSdCard.isFile() == false) {
- log.error(null, "'%1$s' is missing from the SDK tools folder.",
- mkSdCard.getName());
- needCleanup = true;
- return null;
- }
-
- if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log) == false) {
- log.error(null, "Failed to create sdcard in the AVD folder.");
- needCleanup = true;
- return null; // mksdcard output has already been displayed, no need to
- // output anything else.
- }
- }
-
- // add a property containing the size of the sdcard for display purpose
- // only when the dev does 'android list avd'
- values.put(AVD_INI_SDCARD_SIZE, sdcard);
- }
- }
-
- // add the hardware config to the config file.
- // priority order is:
- // - values provided by the user
- // - values provided by the skin
- // - values provided by the target (add-on only).
- // - values provided by the sys img
- // In order to follow this priority, we'll add the lowest priority values first and then
- // override by higher priority values.
- // In the case of a platform with override values from the user, the skin value might
- // already be there, but it's ok.
-
- HashMap<String, String> finalHardwareValues = new HashMap<String, String>();
-
- FileWrapper sysImgHardwareFile = new FileWrapper(systemImage.getLocation(),
- AvdManager.HARDWARE_INI);
- if (sysImgHardwareFile.isFile()) {
- Map<String, String> targetHardwareConfig = ProjectProperties.parsePropertyFile(
- sysImgHardwareFile, log);
-
- if (targetHardwareConfig != null) {
- finalHardwareValues.putAll(targetHardwareConfig);
- values.putAll(targetHardwareConfig);
- }
- }
-
- FileWrapper targetHardwareFile = new FileWrapper(target.getLocation(),
- AvdManager.HARDWARE_INI);
- if (targetHardwareFile.isFile()) {
- Map<String, String> targetHardwareConfig = ProjectProperties.parsePropertyFile(
- targetHardwareFile, log);
-
- if (targetHardwareConfig != null) {
- finalHardwareValues.putAll(targetHardwareConfig);
- values.putAll(targetHardwareConfig);
- }
- }
-
- // get the hardware properties for this skin
- if (skinFolder != null) {
- FileWrapper skinHardwareFile = new FileWrapper(skinFolder, AvdManager.HARDWARE_INI);
- if (skinHardwareFile.isFile()) {
- Map<String, String> skinHardwareConfig =
- ProjectProperties.parsePropertyFile(skinHardwareFile, log);
-
- if (skinHardwareConfig != null) {
- finalHardwareValues.putAll(skinHardwareConfig);
- values.putAll(skinHardwareConfig);
- }
- }
- }
-
- // finally put the hardware provided by the user.
- if (hardwareConfig != null) {
- finalHardwareValues.putAll(hardwareConfig);
- values.putAll(hardwareConfig);
- }
-
- File configIniFile = new File(avdFolder, CONFIG_INI);
- writeIniFile(configIniFile, values, true);
-
- if (bootProps != null && !bootProps.isEmpty()) {
- File bootPropsFile = new File(avdFolder, BOOT_PROP);
- writeIniFile(bootPropsFile, bootProps, false);
- }
-
- // Generate the log report first because we want to control where line breaks
- // are located when generating the hardware config list.
- StringBuilder report = new StringBuilder();
-
- if (target.isPlatform()) {
- if (editExisting) {
- report.append(String.format("Updated AVD '%1$s' based on %2$s",
- avdName, target.getName()));
- } else {
- report.append(String.format("Created AVD '%1$s' based on %2$s",
- avdName, target.getName()));
- }
- } else {
- if (editExisting) {
- report.append(String.format("Updated AVD '%1$s' based on %2$s (%3$s)", avdName,
- target.getName(), target.getVendor()));
- } else {
- report.append(String.format("Created AVD '%1$s' based on %2$s (%3$s)", avdName,
- target.getName(), target.getVendor()));
- }
- }
- report.append(String.format(", %s processor", AvdInfo.getPrettyAbiType(tag, abiType)));
-
- // display the chosen hardware config
- if (!finalHardwareValues.isEmpty()) {
- report.append(",\nwith the following hardware config:\n");
- List<String> keys = new ArrayList<String>(finalHardwareValues.keySet());
- Collections.sort(keys);
- for (String key : keys) {
- String value = finalHardwareValues.get(key);
- report.append(String.format("%s=%s\n", key, value));
- }
- } else {
- report.append("\n");
- }
-
- log.info(report.toString());
-
- // create the AvdInfo object, and add it to the list
- AvdInfo newAvdInfo = new AvdInfo(
- avdName,
- iniFile,
- avdFolder.getAbsolutePath(),
- target.hashString(),
- target,
- tag, abiType,
- values);
-
- AvdInfo oldAvdInfo = getAvd(avdName, false /*validAvdOnly*/);
-
- synchronized (mAllAvdList) {
- if (oldAvdInfo != null && (removePrevious || editExisting)) {
- mAllAvdList.remove(oldAvdInfo);
- }
- mAllAvdList.add(newAvdInfo);
- mValidAvdList = mBrokenAvdList = null;
- }
-
- if ((removePrevious || editExisting) &&
- newAvdInfo != null &&
- oldAvdInfo != null &&
- !oldAvdInfo.getDataFolderPath().equals(newAvdInfo.getDataFolderPath())) {
- log.warning("Removing previous AVD directory at %s",
- oldAvdInfo.getDataFolderPath());
- // Remove the old data directory
- File dir = new File(oldAvdInfo.getDataFolderPath());
- try {
- deleteContentOf(dir);
- dir.delete();
- } catch (SecurityException e) {
- log.error(e, "Failed to delete %1$s", dir.getAbsolutePath());
- }
- }
-
- return newAvdInfo;
- } catch (AndroidLocationException e) {
- log.error(e, null);
- } catch (IOException e) {
- log.error(e, null);
- } catch (SecurityException e) {
- log.error(e, null);
- } finally {
- if (needCleanup) {
- if (iniFile != null && iniFile.exists()) {
- iniFile.delete();
- }
-
- try {
- deleteContentOf(avdFolder);
- avdFolder.delete();
- } catch (SecurityException e) {
- log.error(e, "Failed to delete %1$s", avdFolder.getAbsolutePath());
- }
- }
- }
-
- return null;
- }
-
- /**
- * Copy the nominated file to the given destination.
- *
- * @throws FileNotFoundException
- * @throws IOException
- */
- private void copyImageFile(@NonNull File source, @NonNull File destination)
- throws FileNotFoundException, IOException {
- FileInputStream fis = new FileInputStream(source);
- FileOutputStream fos = new FileOutputStream(destination);
-
- byte[] buffer = new byte[4096];
- int count;
- while ((count = fis.read(buffer)) != -1) {
- fos.write(buffer, 0, count);
- }
-
- fos.close();
- fis.close();
- }
-
- /**
- * Returns the path to the target images folder as a relative path to the SDK, if the folder
- * is not empty. If the image folder is empty or does not exist, <code>null</code> is returned.
- * @throws InvalidTargetPathException if the target image folder is not in the current SDK.
- */
- private String getImageRelativePath(@NonNull IAndroidTarget target,
- @NonNull IdDisplay tag,
- @NonNull String abiType)
- throws InvalidTargetPathException {
-
- ISystemImage systemImage = target.getSystemImage(tag, abiType);
- if (systemImage == null) {
- // ABI Type is unknown for target
- return null;
- }
-
- File folder = systemImage.getLocation();
- String imageFullPath = folder.getAbsolutePath();
-
- // make this path relative to the SDK location
- String sdkLocation = myLocalSdk.getPath();
- if (!imageFullPath.startsWith(sdkLocation)) {
- // this really really should not happen.
- assert false;
- throw new InvalidTargetPathException("Target location is not inside the SDK.");
- }
-
- if (folder.isDirectory()) {
- String[] list = folder.list(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return IMAGE_NAME_PATTERN.matcher(name).matches();
- }
- });
-
- if (list.length > 0) {
- // Remove the SDK root path, e.g. /sdk/dir1/dir2 => /dir1/dir2
- imageFullPath = imageFullPath.substring(sdkLocation.length());
- // The path is relative, so it must not start with a file separator
- if (imageFullPath.charAt(0) == File.separatorChar) {
- imageFullPath = imageFullPath.substring(1);
- }
- // For compatibility with previous versions, we denote folders
- // by ending the path with file separator
- if (!imageFullPath.endsWith(File.separator)) {
- imageFullPath += File.separator;
- }
-
- return imageFullPath;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the path to the skin, as a relative path to the SDK.
- * @param skinName The name of the skin to find. Case-sensitive.
- * @param target The target where to find the skin.
- * @param log the log object to receive action logs. Cannot be null.
- */
- @Deprecated
- private String getSkinRelativePath(@NonNull String skinName,
- @NonNull IAndroidTarget target,
- @NonNull ILogger log) {
- if (log == null) {
- throw new IllegalArgumentException("log cannot be null");
- }
-
- // first look to see if the skin is in the target
- File skin = getSkinFolder(skinName, target);
-
- // skin really does not exist!
- if (skin.exists() == false) {
- log.error(null, "Skin '%1$s' does not exist.", skinName);
- return null;
- }
-
- // get the skin path
- String path = skin.getAbsolutePath();
-
- // make this path relative to the SDK location
-
- String sdkLocation = myLocalSdk.getPath();
- if (path.startsWith(sdkLocation) == false) {
- // this really really should not happen.
- log.error(null, "Target location is not inside the SDK.");
- assert false;
- return null;
- }
-
- path = path.substring(sdkLocation.length());
- if (path.charAt(0) == File.separatorChar) {
- path = path.substring(1);
- }
- return path;
- }
-
- /**
- * Returns the full absolute OS path to a skin specified by name for a given target.
- * @param skinName The name of the skin to find. Case-sensitive.
- * @param target The target where to find the skin.
- * @return a {@link File} that may or may not actually exist.
- */
- private File getSkinFolder(@NonNull String skinName, @NonNull IAndroidTarget target) {
- String path = target.getPath(IAndroidTarget.SKINS);
- File skin = new File(path, skinName);
-
- if (skin.exists() == false && target.isPlatform() == false) {
- target = target.getParent();
-
- path = target.getPath(IAndroidTarget.SKINS);
- skin = new File(path, skinName);
- }
-
- return skin;
- }
-
- /**
- * Creates the ini file for an AVD.
- *
- * @param name of the AVD.
- * @param avdFolder path for the data folder of the AVD.
- * @param target of the AVD.
- * @param removePrevious True if an existing ini file should be removed.
- * @throws AndroidLocationException if there's a problem getting android root directory.
- * @throws IOException if {@link File#getAbsolutePath()} fails.
- */
- private File createAvdIniFile(@NonNull String name,
- @NonNull File avdFolder,
- @NonNull IAndroidTarget target,
- boolean removePrevious)
- throws AndroidLocationException, IOException {
- File iniFile = AvdInfo.getDefaultIniFile(this, name);
-
- if (removePrevious) {
- if (iniFile.isFile()) {
- iniFile.delete();
- } else if (iniFile.isDirectory()) {
- deleteContentOf(iniFile);
- iniFile.delete();
- }
- }
-
- String absPath = avdFolder.getAbsolutePath();
- String relPath = null;
- String androidPath = AndroidLocation.getFolder();
- if (absPath.startsWith(androidPath)) {
- // Compute the AVD path relative to the android path.
- assert androidPath.endsWith(File.separator);
- relPath = absPath.substring(androidPath.length());
- }
-
- HashMap<String, String> values = new HashMap<String, String>();
- if (relPath != null) {
- values.put(AVD_INFO_REL_PATH, relPath);
- }
- values.put(AVD_INFO_ABS_PATH, absPath);
- values.put(AVD_INFO_TARGET, target.hashString());
- writeIniFile(iniFile, values, true);
-
- return iniFile;
- }
-
- /**
- * Creates the ini file for an AVD.
- *
- * @param info of the AVD.
- * @throws AndroidLocationException if there's a problem getting android root directory.
- * @throws IOException if {@link File#getAbsolutePath()} fails.
- */
- private File createAvdIniFile(@NonNull AvdInfo info)
- throws AndroidLocationException, IOException {
- return createAvdIniFile(info.getName(),
- new File(info.getDataFolderPath()),
- info.getTarget(),
- false /*removePrevious*/);
- }
-
- /**
- * Actually deletes the files of an existing AVD.
- * <p/>
- * This also remove it from the manager's list, The caller does not need to
- * call {@link #removeAvd(AvdInfo)} afterwards.
- * <p/>
- * This method is designed to somehow work with an unavailable AVD, that is an AVD that
- * could not be loaded due to some error. That means this method still tries to remove
- * the AVD ini file or its folder if it can be found. An error will be output if any of
- * these operations fail.
- *
- * @param avdInfo the information on the AVD to delete
- * @param log the log object to receive action logs. Cannot be null.
- * @return True if the AVD was deleted with no error.
- */
- public boolean deleteAvd(@NonNull AvdInfo avdInfo, @NonNull ILogger log) {
- try {
- boolean error = false;
-
- File f = avdInfo.getIniFile();
- if (f != null && f.exists()) {
- log.info("Deleting file %1$s\n", f.getCanonicalPath());
- if (!f.delete()) {
- log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
- error = true;
- }
- }
-
- String path = avdInfo.getDataFolderPath();
- if (path != null) {
- f = new File(path);
- if (f.exists()) {
- log.info("Deleting folder %1$s\n", f.getCanonicalPath());
- if (deleteContentOf(f) == false || f.delete() == false) {
- log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
- error = true;
- }
- }
- }
-
- removeAvd(avdInfo);
-
- if (error) {
- log.info("\nAVD '%1$s' deleted with errors. See errors above.\n",
- avdInfo.getName());
- } else {
- log.info("\nAVD '%1$s' deleted.\n", avdInfo.getName());
- return true;
- }
-
- } catch (IOException e) {
- log.error(e, null);
- } catch (SecurityException e) {
- log.error(e, null);
- }
- return false;
- }
-
- /**
- * Moves and/or rename an existing AVD and its files.
- * This also change it in the manager's list.
- * <p/>
- * The caller should make sure the name or path given are valid, do not exist and are
- * actually different than current values.
- *
- * @param avdInfo the information on the AVD to move.
- * @param newName the new name of the AVD if non null.
- * @param paramFolderPath the new data folder if non null.
- * @param log the log object to receive action logs. Cannot be null.
- * @return True if the move succeeded or there was nothing to do.
- * If false, this method will have had already output error in the log.
- */
- public boolean moveAvd(@NonNull AvdInfo avdInfo,
- @Nullable String newName,
- @Nullable String paramFolderPath,
- @NonNull ILogger log) {
-
- try {
- if (paramFolderPath != null) {
- File f = new File(avdInfo.getDataFolderPath());
- log.warning("Moving '%1$s' to '%2$s'.",
- avdInfo.getDataFolderPath(),
- paramFolderPath);
- if (!f.renameTo(new File(paramFolderPath))) {
- log.error(null, "Failed to move '%1$s' to '%2$s'.",
- avdInfo.getDataFolderPath(), paramFolderPath);
- return false;
- }
-
- // update AVD info
- AvdInfo info = new AvdInfo(
- avdInfo.getName(),
- avdInfo.getIniFile(),
- paramFolderPath,
- avdInfo.getTargetHash(),
- avdInfo.getTarget(),
- avdInfo.getTag(),
- avdInfo.getAbiType(),
- avdInfo.getProperties());
- replaceAvd(avdInfo, info);
-
- // update the ini file
- createAvdIniFile(info);
- }
-
- if (newName != null) {
- File oldIniFile = avdInfo.getIniFile();
- File newIniFile = AvdInfo.getDefaultIniFile(this, newName);
-
- log.warning("Moving '%1$s' to '%2$s'.", oldIniFile.getPath(), newIniFile.getPath());
- if (!oldIniFile.renameTo(newIniFile)) {
- log.error(null, "Failed to move '%1$s' to '%2$s'.",
- oldIniFile.getPath(), newIniFile.getPath());
- return false;
- }
-
- // update AVD info
- AvdInfo info = new AvdInfo(
- newName,
- avdInfo.getIniFile(),
- avdInfo.getDataFolderPath(),
- avdInfo.getTargetHash(),
- avdInfo.getTarget(),
- avdInfo.getTag(),
- avdInfo.getAbiType(),
- avdInfo.getProperties());
- replaceAvd(avdInfo, info);
- }
-
- log.info("AVD '%1$s' moved.\n", avdInfo.getName());
-
- } catch (AndroidLocationException e) {
- log.error(e, null);
- } catch (IOException e) {
- log.error(e, null);
- }
-
- // nothing to do or succeeded
- return true;
- }
-
- /**
- * Helper method to recursively delete a folder's content (but not the folder itself).
- *
- * @throws SecurityException like {@link File#delete()} does if file/folder is not writable.
- */
- private boolean deleteContentOf(File folder) throws SecurityException {
- File[] files = folder.listFiles();
- if (files != null) {
- for (File f : files) {
- if (f.isDirectory()) {
- if (deleteContentOf(f) == false) {
- return false;
- }
- }
- if (f.delete() == false) {
- return false;
- }
-
- }
- }
-
- return true;
- }
-
- /**
- * Returns a list of files that are potential AVD ini files.
- * <p/>
- * This lists the $HOME/.android/avd/<name>.ini files.
- * Such files are properties file than then indicate where the AVD folder is located.
- * <p/>
- * Note: the method is to be considered private. It is made protected so that
- * unit tests can easily override the AVD root.
- *
- * @return A new {@link File} array or null. The array might be empty.
- * @throws AndroidLocationException if there's a problem getting android root directory.
- */
- private File[] buildAvdFilesList() throws AndroidLocationException {
- File folder = new File(getBaseAvdFolder());
-
- // ensure folder validity.
- if (folder.isFile()) {
- throw new AndroidLocationException(
- String.format("%1$s is not a valid folder.", folder.getAbsolutePath()));
- } else if (folder.exists() == false) {
- // folder is not there, we create it and return
- folder.mkdirs();
- return null;
- }
-
- File[] avds = folder.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File parent, String name) {
- if (INI_NAME_PATTERN.matcher(name).matches()) {
- // check it's a file and not a folder
- boolean isFile = new File(parent, name).isFile();
- return isFile;
- }
-
- return false;
- }
- });
-
- return avds;
- }
-
- /**
- * Computes the internal list of available AVDs
- * @param allList the list to contain all the AVDs
- * @param log the log object to receive action logs. Cannot be null.
- *
- * @throws AndroidLocationException if there's a problem getting android root directory.
- */
- private void buildAvdList(ArrayList<AvdInfo> allList, ILogger log)
- throws AndroidLocationException {
- File[] avds = buildAvdFilesList();
- if (avds != null) {
- for (File avd : avds) {
- AvdInfo info = parseAvdInfo(avd, log);
- if (info != null && !allList.contains(info)) {
- allList.add(info);
- }
- }
- }
- }
-
- private DeviceManager getDeviceManager(ILogger logger) {
- DeviceManager manager = myDeviceManagers.get(logger);
- if (manager == null) {
- manager = DeviceManager.createInstance(myLocalSdk.getLocation(), logger);
- manager.registerListener(new DeviceManager.DevicesChangedListener() {
- @Override
- public void onDevicesChanged() {
- myDeviceManagers.clear();
- }
- });
- myDeviceManagers.put(logger, manager);
- }
- return manager;
- }
-
- /**
- * Parses an AVD .ini file to create an {@link AvdInfo}.
- *
- * @param iniPath The path to the AVD .ini file
- * @param log the log object to receive action logs. Cannot be null.
- * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is
- * valid or not.
- */
- private AvdInfo parseAvdInfo(File iniPath, ILogger log) {
- Map<String, String> map = parseIniFile(
- new FileWrapper(iniPath),
- log);
-
- String avdPath = map.get(AVD_INFO_ABS_PATH);
- String targetHash = map.get(AVD_INFO_TARGET);
-
- if (!(new File(avdPath).isDirectory())) {
- // Try to fallback on the relative path, if present.
- String relPath = map.get(AVD_INFO_REL_PATH);
- if (relPath != null) {
- try {
- String androidPath = AndroidLocation.getFolder();
- File f = new File(androidPath, relPath);
- if (f.isDirectory()) {
- avdPath = f.getAbsolutePath();
- }
- } catch (AndroidLocationException ignore) {}
- }
- }
-
- IAndroidTarget target = null;
- FileWrapper configIniFile = null;
- Map<String, String> properties = null;
-
- if (targetHash != null) {
- target = myLocalSdk.getTargetFromHashString(targetHash);
- }
-
- // load the AVD properties.
- if (avdPath != null) {
- configIniFile = new FileWrapper(avdPath, CONFIG_INI);
- }
-
- if (configIniFile != null) {
- if (!configIniFile.isFile()) {
- log.warning("Missing file '%1$s'.", configIniFile.getPath());
- } else {
- properties = parseIniFile(configIniFile, log);
- }
- }
-
- // get name
- String name = iniPath.getName();
- Matcher matcher = INI_NAME_PATTERN.matcher(iniPath.getName());
- if (matcher.matches()) {
- name = matcher.group(1);
- }
-
- // get tag
- IdDisplay tag = SystemImage.DEFAULT_TAG;
- String tagId = properties == null ? null : properties.get(AVD_INI_TAG_ID);
- if (tagId != null) {
- String tagDisp = properties == null ? null : properties.get(AVD_INI_TAG_DISPLAY);
- if (tagDisp == null || tagDisp.isEmpty()) {
- tagDisp = LocalSysImgPkgInfo.tagIdToDisplay(tagId);
- }
- tag = new IdDisplay(tagId, tagDisp);
- }
-
- // get abi type
- String abiType = properties == null ? null : properties.get(AVD_INI_ABI_TYPE);
- // for the avds created previously without enhancement, i.e. They are created based
- // on previous API Levels. They are supposed to have ARM processor type
- if (abiType == null) {
- abiType = SdkConstants.ABI_ARMEABI;
- }
-
- // check the image.sysdir are valid
- boolean validImageSysdir = true;
- if (properties != null) {
- String imageSysDir = properties.get(AVD_INI_IMAGES_1);
- if (imageSysDir != null) {
- File f = new File(myLocalSdk.getLocation(), imageSysDir);
- if (f.isDirectory() == false) {
- validImageSysdir = false;
- } else {
- imageSysDir = properties.get(AVD_INI_IMAGES_2);
- if (imageSysDir != null) {
- f = new File(myLocalSdk.getLocation(), imageSysDir);
- if (f.isDirectory() == false) {
- validImageSysdir = false;
- }
- }
- }
- }
- }
-
- // Check the system image from the target
- ISystemImage sysImage = target != null ? target.getSystemImage(tag, abiType) : null;
-
- // Get the device status if this AVD is associated with a device
- DeviceStatus deviceStatus = null;
- boolean updateHashV2 = false;
- if (properties != null) {
- String deviceName = properties.get(AVD_INI_DEVICE_NAME);
- String deviceMfctr = properties.get(AVD_INI_DEVICE_MANUFACTURER);
-
- Device d = null;
-
- if (deviceName != null && deviceMfctr != null) {
- DeviceManager devMan = getDeviceManager(log);
- d = devMan.getDevice(deviceName, deviceMfctr);
- deviceStatus = d == null ? DeviceStatus.MISSING : DeviceStatus.EXISTS;
-
- if (d != null) {
- updateHashV2 = true;
- String hashV2 = properties.get(AVD_INI_DEVICE_HASH_V2);
- if (hashV2 != null) {
- String newHashV2 = DeviceManager.hasHardwarePropHashChanged(d, hashV2);
- if (newHashV2 == null) {
- updateHashV2 = false;
- } else {
- properties.put(AVD_INI_DEVICE_HASH_V2, newHashV2);
- }
- }
-
- String hashV1 = properties.get(AVD_INI_DEVICE_HASH_V1);
- if (hashV1 != null) {
- // will recompute a hash v2 and save it below
- properties.remove(AVD_INI_DEVICE_HASH_V1);
- }
- }
- }
- }
-
-
- // TODO: What about missing sdcard, skins, etc?
-
- AvdStatus status;
-
- if (avdPath == null) {
- status = AvdStatus.ERROR_PATH;
- } else if (configIniFile == null) {
- status = AvdStatus.ERROR_CONFIG;
- } else if (targetHash == null) {
- status = AvdStatus.ERROR_TARGET_HASH;
- } else if (target == null) {
- status = AvdStatus.ERROR_TARGET;
- } else if (properties == null) {
- status = AvdStatus.ERROR_PROPERTIES;
- } else if (validImageSysdir == false) {
- status = AvdStatus.ERROR_IMAGE_DIR;
- } else if (deviceStatus == DeviceStatus.CHANGED) {
- status = AvdStatus.ERROR_DEVICE_CHANGED;
- } else if (deviceStatus == DeviceStatus.MISSING) {
- status = AvdStatus.ERROR_DEVICE_MISSING;
- } else if (sysImage == null) {
- status = AvdStatus.ERROR_IMAGE_MISSING;
- } else {
- status = AvdStatus.OK;
- }
-
- AvdInfo info = new AvdInfo(
- name,
- iniPath,
- avdPath,
- targetHash,
- target,
- tag,
- abiType,
- properties,
- status);
-
- if (updateHashV2) {
- try {
- return updateDeviceChanged(info, log);
- } catch (IOException ignore) {}
- }
-
- return info;
- }
-
- /**
- * Writes a .ini file from a set of properties, using UTF-8 encoding.
- * The keys are sorted.
- * The file should be read back later by {@link #parseIniFile(IAbstractFile, ILogger)}.
- *
- * @param iniFile The file to generate.
- * @param values The properties to place in the ini file.
- * @param addEncoding When true, add a property {@link #AVD_INI_ENCODING} indicating the
- * encoding used to write the file.
- * @throws IOException if {@link FileWriter} fails to open, write or close the file.
- */
- private static void writeIniFile(File iniFile, Map<String, String> values, boolean addEncoding)
- throws IOException {
-
- Charset charset = Charsets.UTF_8;
- OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(iniFile), charset);
- try {
- if (addEncoding) {
- // Write down the charset used in case we want to use it later.
- writer.write(String.format("%1$s=%2$s\n", AVD_INI_ENCODING, charset.name()));
- }
-
- ArrayList<String> keys = new ArrayList<String>(values.keySet());
- Collections.sort(keys);
-
- for (String key : keys) {
- String value = values.get(key);
- writer.write(String.format("%1$s=%2$s\n", key, value));
- }
- } finally {
- writer.close();
- }
- }
-
- /**
- * Parses a property file and returns a map of the content.
- * <p/>
- * If the file is not present, null is returned with no error messages sent to the log.
- * <p/>
- * Charset encoding will be either the system's default or the one specified by the
- * {@link #AVD_INI_ENCODING} key if present.
- *
- * @param propFile the property file to parse
- * @param log the ILogger object receiving warning/error from the parsing.
- * @return the map of (key,value) pairs, or null if the parsing failed.
- */
- private static Map<String, String> parseIniFile(
- @NonNull IAbstractFile propFile,
- @Nullable ILogger log) {
- return parseIniFileImpl(propFile, log, null /*charset*/);
- }
-
- /**
- * Implementation helper for the {@link #parseIniFile(IAbstractFile, ILogger)} method.
- * Don't call this one directly.
- *
- * @param propFile the property file to parse
- * @param log the ILogger object receiving warning/error from the parsing.
- * @param charset When a specific charset is specified, this will be used as-is.
- * When null, the default charset will first be used and if the key
- * {@link #AVD_INI_ENCODING} is found the parsing will restart using that specific
- * charset.
- * @return the map of (key,value) pairs, or null if the parsing failed.
- */
- private static Map<String, String> parseIniFileImpl(
- @NonNull IAbstractFile propFile,
- @Nullable ILogger log,
- @Nullable Charset charset) {
-
- BufferedReader reader = null;
- try {
- boolean canChangeCharset = false;
- if (charset == null) {
- canChangeCharset = true;
- charset = Charsets.ISO_8859_1;
- }
- reader = new BufferedReader(new InputStreamReader(propFile.getContents(), charset));
-
- String line = null;
- Map<String, String> map = new HashMap<String, String>();
- while ((line = reader.readLine()) != null) {
- line = line.trim();
- if (!line.isEmpty() && line.charAt(0) != '#') {
-
- Matcher m = INI_LINE_PATTERN.matcher(line);
- if (m.matches()) {
- // Note: we do NOT escape values.
- String key = m.group(1);
- String value = m.group(2);
-
- // If we find the charset encoding and it's not the same one and
- // it's a valid one, re-read the file using that charset.
- if (canChangeCharset &&
- AVD_INI_ENCODING.equals(key) &&
- !charset.name().equals(value) &&
- Charset.isSupported(value)) {
- charset = Charset.forName(value);
- return parseIniFileImpl(propFile, log, charset);
- }
-
- map.put(key, value);
- } else {
- if (log != null) {
- log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
- propFile.getOsLocation(),
- line);
- }
- return null;
- }
- }
- }
-
- return map;
- } catch (FileNotFoundException e) {
- // this should not happen since we usually test the file existence before
- // calling the method.
- // Return null below.
- } catch (IOException e) {
- if (log != null) {
- log.warning("Error parsing '%1$s': %2$s.",
- propFile.getOsLocation(),
- e.getMessage());
- }
- } catch (StreamException e) {
- if (log != null) {
- log.warning("Error parsing '%1$s': %2$s.",
- propFile.getOsLocation(),
- e.getMessage());
- }
- } finally {
- try {
- Closeables.close(reader, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen.
- }
- }
-
- return null;
- }
-
- /**
- * Invokes the tool to create a new SD card image file.
- *
- * @param toolLocation The path to the mksdcard tool.
- * @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}.
- * @param location The path of the new sdcard image file to generate.
- * @param log the log object to receive action logs. Cannot be null.
- * @return True if the sdcard could be created.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean createSdCard(String toolLocation, String size, String location, ILogger log) {
- try {
- String[] command = new String[3];
- command[0] = toolLocation;
- command[1] = size;
- command[2] = location;
- Process process = Runtime.getRuntime().exec(command);
-
- final ArrayList<String> errorOutput = new ArrayList<String>();
- final ArrayList<String> stdOutput = new ArrayList<String>();
-
- int status = GrabProcessOutput.grabProcessOutput(
- process,
- Wait.WAIT_FOR_READERS,
- new IProcessOutput() {
- @Override
- public void out(@Nullable String line) {
- if (line != null) {
- stdOutput.add(line);
- }
- }
-
- @Override
- public void err(@Nullable String line) {
- if (line != null) {
- errorOutput.add(line);
- }
- }
- });
-
- if (status == 0) {
- return true;
- } else {
- for (String error : errorOutput) {
- log.error(null, error);
- }
- }
-
- } catch (InterruptedException e) {
- // pass, print error below
- } catch (IOException e) {
- // pass, print error below
- }
-
- log.error(null, "Failed to create the SD card.");
- return false;
- }
-
- /**
- * Removes an {@link AvdInfo} from the internal list.
- *
- * @param avdInfo The {@link AvdInfo} to remove.
- * @return true if this {@link AvdInfo} was present and has been removed.
- */
- public boolean removeAvd(AvdInfo avdInfo) {
- synchronized (mAllAvdList) {
- if (mAllAvdList.remove(avdInfo)) {
- mValidAvdList = mBrokenAvdList = null;
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Updates an AVD with new path to the system image folders.
- * @param name the name of the AVD to update.
- * @param log the log object to receive action logs. Cannot be null.
- * @throws IOException
- */
- public void updateAvd(String name, ILogger log) throws IOException {
- // find the AVD to update. It should be be in the broken list.
- AvdInfo avd = null;
- synchronized (mAllAvdList) {
- for (AvdInfo info : mAllAvdList) {
- if (info.getName().equals(name)) {
- avd = info;
- break;
- }
- }
- }
-
- if (avd == null) {
- // not in the broken list, just return.
- log.error(null, "There is no Android Virtual Device named '%s'.", name);
- return;
- }
-
- updateAvd(avd, log);
- }
-
-
- /**
- * Updates an AVD with new path to the system image folders.
- * @param avd the AVD to update.
- * @param log the log object to receive action logs. Cannot be null.
- * @throws IOException
- */
- public AvdInfo updateAvd(AvdInfo avd, ILogger log) throws IOException {
- // get the properties. This is a unmodifiable Map.
- Map<String, String> oldProperties = avd.getProperties();
-
- // create a new map
- Map<String, String> properties = new HashMap<String, String>();
- if (oldProperties != null) {
- properties.putAll(oldProperties);
- }
-
- AvdStatus status;
-
- // create the path to the new system images.
- if (setImagePathProperties(avd.getTarget(),
- avd.getTag(),
- avd.getAbiType(),
- properties,
- log)) {
- if (properties.containsKey(AVD_INI_IMAGES_1)) {
- log.info("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1,
- properties.get(AVD_INI_IMAGES_1));
- }
-
- if (properties.containsKey(AVD_INI_IMAGES_2)) {
- log.info("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_2,
- properties.get(AVD_INI_IMAGES_2));
- }
-
- status = AvdStatus.OK;
- } else {
- log.error(null, "Unable to find non empty system images folders for %1$s",
- avd.getName());
- //FIXME: display paths to empty image folders?
- status = AvdStatus.ERROR_IMAGE_DIR;
- }
-
- return updateAvd(avd, properties, status, log);
- }
-
- public AvdInfo updateAvd(AvdInfo avd,
- Map<String, String> newProperties,
- AvdStatus status,
- ILogger log) throws IOException {
- // now write the config file
- File configIniFile = new File(avd.getDataFolderPath(), CONFIG_INI);
- writeIniFile(configIniFile, newProperties, true);
-
- // finally create a new AvdInfo for this unbroken avd and add it to the list.
- // instead of creating the AvdInfo object directly we reparse it, to detect other possible
- // errors
- // FIXME: We may want to create this AvdInfo by reparsing the AVD instead. This could detect other errors.
- AvdInfo newAvd = new AvdInfo(
- avd.getName(),
- avd.getIniFile(),
- avd.getDataFolderPath(),
- avd.getTargetHash(),
- avd.getTarget(),
- avd.getTag(),
- avd.getAbiType(),
- newProperties);
-
- replaceAvd(avd, newAvd);
-
- return newAvd;
- }
-
- /**
- * Updates the device-specific part of an AVD ini.
- * @param avd the AVD to update.
- * @param log the log object to receive action logs. Cannot be null.
- * @return The new AVD on success.
- * @throws IOException
- */
- public AvdInfo updateDeviceChanged(AvdInfo avd, ILogger log) throws IOException {
-
- // Overwrite the properties derived from the device and nothing else
- Map<String, String> properties = new HashMap<String, String>(avd.getProperties());
-
- DeviceManager devMan = getDeviceManager(log);
- Collection<Device> devices = devMan.getDevices(DeviceManager.ALL_DEVICES);
- String name = properties.get(AvdManager.AVD_INI_DEVICE_NAME);
- String manufacturer = properties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
-
- if (properties != null && devices != null && name != null && manufacturer != null) {
- for (Device d : devices) {
- if (d.getId().equals(name) && d.getManufacturer().equals(manufacturer)) {
- properties.putAll(DeviceManager.getHardwareProperties(d));
- try {
- return updateAvd(avd, properties, AvdStatus.OK, log);
- } catch (IOException e) {
- log.error(e, null);
- }
- }
- }
- } else {
- log.error(null, "Base device information incomplete or missing.");
- }
- return null;
- }
-
- /**
- * Sets the paths to the system images in a properties map.
- *
- * @param target the target in which to find the system images.
- * @param abiType the abi type of the avd to find
- * the architecture-dependent system images.
- * @param properties the properties in which to set the paths.
- * @param log the log object to receive action logs. Cannot be null.
- * @return true if success, false if some path are missing.
- */
- private boolean setImagePathProperties(IAndroidTarget target,
- IdDisplay tag,
- String abiType,
- Map<String, String> properties,
- ILogger log) {
- properties.remove(AVD_INI_IMAGES_1);
- properties.remove(AVD_INI_IMAGES_2);
-
- try {
- String property = AVD_INI_IMAGES_1;
-
- // First the image folders of the target itself
- String imagePath = getImageRelativePath(target, tag, abiType);
- if (imagePath != null) {
- properties.put(property, imagePath);
- property = AVD_INI_IMAGES_2;
- }
-
- // If the target is an add-on we need to add the Platform image as a backup.
- IAndroidTarget parent = target.getParent();
- if (parent != null) {
- imagePath = getImageRelativePath(parent, tag, abiType);
- if (imagePath != null) {
- properties.put(property, imagePath);
- }
- }
-
- // we need at least one path!
- return properties.containsKey(AVD_INI_IMAGES_1);
- } catch (InvalidTargetPathException e) {
- log.error(e, e.getMessage());
- }
-
- return false;
- }
-
- /**
- * Replaces an old {@link AvdInfo} with a new one in the lists storing them.
- * @param oldAvd the {@link AvdInfo} to remove.
- * @param newAvd the {@link AvdInfo} to add.
- */
- private void replaceAvd(AvdInfo oldAvd, AvdInfo newAvd) {
- synchronized (mAllAvdList) {
- mAllAvdList.remove(oldAvd);
- mAllAvdList.add(newAvd);
- mValidAvdList = mBrokenAvdList = null;
- }
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectCreator.java b/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectCreator.java
deleted file mode 100644
index c5712d7..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectCreator.java
+++ /dev/null
@@ -1,1571 +0,0 @@
-/*
- * Copyright (C) 2007 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.sdklib.internal.project;
-
-import com.android.SdkConstants;
-import com.android.io.FileWrapper;
-import com.android.io.FolderWrapper;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
-import com.android.utils.ILogger;
-import com.android.xml.AndroidManifest;
-import com.android.xml.AndroidXPathFactory;
-
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-
-/**
- * Creates the basic files needed to get an Android project up and running.
- */
-public class ProjectCreator {
-
- /** Version of the build.xml. Stored in version-tag */
- private static final int MIN_BUILD_VERSION_TAG = 1;
-
- /** Package path substitution string used in template files, i.e. "PACKAGE_PATH" */
- private static final String PH_PACKAGE_PATH = "PACKAGE_PATH";
- /** Package name substitution string used in template files, i.e. "PACKAGE" */
- private static final String PH_PACKAGE = "PACKAGE";
- /** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME".
- * @deprecated This is only used for older templates. For new ones see
- * {@link #PH_ACTIVITY_ENTRY_NAME}, and {@link #PH_ACTIVITY_CLASS_NAME}. */
- @Deprecated
- private static final String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
- /** Activity name substitution string used in manifest templates, i.e. "ACTIVITY_ENTRY_NAME".*/
- private static final String PH_ACTIVITY_ENTRY_NAME = "ACTIVITY_ENTRY_NAME";
- /** Activity name substitution string used in class templates, i.e. "ACTIVITY_CLASS_NAME".*/
- private static final String PH_ACTIVITY_CLASS_NAME = "ACTIVITY_CLASS_NAME";
- /** Activity FQ-name substitution string used in class templates, i.e. "ACTIVITY_FQ_NAME".*/
- private static final String PH_ACTIVITY_FQ_NAME = "ACTIVITY_FQ_NAME";
- /** Original Activity class name substitution string used in class templates, i.e.
- * "ACTIVITY_TESTED_CLASS_NAME".*/
- private static final String PH_ACTIVITY_TESTED_CLASS_NAME = "ACTIVITY_TESTED_CLASS_NAME";
- /** Project name substitution string used in template files, i.e. "PROJECT_NAME". */
- public static final String PH_PROJECT_NAME = "PROJECT_NAME";
- /** Application icon substitution string used in the manifest template */
- private static final String PH_ICON = "ICON";
- /** Version tag name substitution string used in template files, i.e. "VERSION_TAG". */
- private static final String PH_VERSION_TAG = "VERSION_TAG";
- /** Target name substitution string used in template files, i.e. "TARGET". */
- private static final String PH_TARGET = "TARGET";
- /** Gradle plugin substitution string used in the build.gradle template */
- private static final String PH_PLUGIN = "PLUGIN";
- /** Gradle artifact version substitution string used in the build.gradle template */
- private static final String PH_ARTIFACT_VERSION = "ARTIFACT_VERSION";
- /** Build tool revision substitution string used in the build.gradle template */
- private static final String PH_BUILD_TOOL_REV = "BUILD_TOOL_REV";
-
- /** The xpath to find a project name in an Ant build file. */
- private static final String XPATH_PROJECT_NAME = "/project/@name";
-
- /** Pattern for characters accepted in a project name. Since this will be used as a
- * directory name, we're being a bit conservative on purpose: dot and space cannot be used. */
- public static final Pattern RE_PROJECT_NAME = Pattern.compile("[a-zA-Z0-9_]+");
- /** List of valid characters for a project name. Used for display purposes. */
- public static final String CHARS_PROJECT_NAME = "a-z A-Z 0-9 _";
-
- /** Pattern for characters accepted in a package name. A package is list of Java identifier
- * separated by a dot. We need to have at least one dot (e.g. a two-level package name).
- * A Java identifier cannot start by a digit. */
- public static final Pattern RE_PACKAGE_NAME =
- Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+");
- /** List of valid characters for a project name. Used for display purposes. */
- public static final String CHARS_PACKAGE_NAME = "a-z A-Z 0-9 _";
-
- /** Pattern for characters accepted in an activity name, which is a Java identifier. */
- public static final Pattern RE_ACTIVITY_NAME =
- Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
- /** List of valid characters for a project name. Used for display purposes. */
- public static final String CHARS_ACTIVITY_NAME = "a-z A-Z 0-9 _";
-
- /** Gradle plugin to use with standard projects */
- private static final String PLUGIN_PROJECT = "android";
- /** Gradle plugin to use with library projects */
- private static final String PLUGIN_LIB_PROJECT = "android-library";
-
-
- public enum OutputLevel {
- /** Silent mode. Project creation will only display errors. */
- SILENT,
- /** Normal mode. Project creation will display what's being done, display
- * error but not warnings. */
- NORMAL,
- /** Verbose mode. Project creation will display what's being done, errors and warnings. */
- VERBOSE
- }
-
- /**
- * Exception thrown when a project creation fails, typically because a template
- * file cannot be written.
- */
- private static class ProjectCreateException extends Exception {
- /** default UID. This will not be serialized anyway. */
- private static final long serialVersionUID = 1L;
-
- @SuppressWarnings("unused")
- ProjectCreateException(String message) {
- super(message);
- }
-
- ProjectCreateException(Throwable t, String format, Object... args) {
- super(format != null ? String.format(format, args) : format, t);
- }
-
- ProjectCreateException(String format, Object... args) {
- super(String.format(format, args));
- }
- }
-
- /** The {@link OutputLevel} verbosity. */
- private final OutputLevel mLevel;
- /** Logger for errors and output. Cannot be null. */
- private final ILogger mLog;
- /** The OS path of the SDK folder. */
- private final String mSdkFolder;
- /** The {@link SdkManager} instance. */
- private final SdkManager mSdkManager;
-
- /**
- * Helper class to create android projects.
- *
- * @param sdkManager The {@link SdkManager} instance.
- * @param sdkFolder The OS path of the SDK folder.
- * @param level The {@link OutputLevel} verbosity.
- * @param log Logger for errors and output. Cannot be null.
- */
- public ProjectCreator(SdkManager sdkManager, String sdkFolder, OutputLevel level, ILogger log) {
- mSdkManager = sdkManager;
- mSdkFolder = sdkFolder;
- mLevel = level;
- mLog = log;
- }
-
- /**
- * Creates a new (ant) project.
- * <p/>
- * The caller should have already checked and sanitized the parameters.
- *
- * @param folderPath the folder of the project to create.
- * @param projectName the name of the project. The name must match the
- * {@link #RE_PROJECT_NAME} regex.
- * @param packageName the package of the project. The name must match the
- * {@link #RE_PACKAGE_NAME} regex.
- * @param activityEntry the activity of the project as it will appear in the manifest. Can be
- * null if no activity should be created. The name must match the
- * {@link #RE_ACTIVITY_NAME} regex.
- * @param target the project target.
- * @param library whether the project is a library.
- * @param pathToMainProject if non-null the project will be setup to test a main project
- * located at the given path.
- */
- public void createProject(String folderPath, String projectName,
- String packageName, String activityEntry, IAndroidTarget target, boolean library,
- String pathToMainProject) {
-
- // create project folder if it does not exist
- File projectFolder = checkNewProjectLocation(folderPath);
- if (projectFolder == null) {
- return;
- }
-
- try {
- boolean isTestProject = pathToMainProject != null;
-
- // first create the project properties.
-
- // location of the SDK goes in localProperty
- ProjectPropertiesWorkingCopy localProperties = ProjectProperties.create(folderPath,
- PropertyType.LOCAL);
- localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
- localProperties.save();
-
- // target goes in project properties
- ProjectPropertiesWorkingCopy projectProperties = ProjectProperties.create(folderPath,
- PropertyType.PROJECT);
- projectProperties.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
- if (library) {
- projectProperties.setProperty(ProjectProperties.PROPERTY_LIBRARY, "true");
- }
- projectProperties.save();
-
- // create a ant.properties file with just the application package
- ProjectPropertiesWorkingCopy antProperties = ProjectProperties.create(folderPath,
- PropertyType.ANT);
-
- if (isTestProject) {
- antProperties.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT,
- pathToMainProject);
- }
-
- antProperties.save();
-
- // create the map for place-holders of values to replace in the templates
- final HashMap<String, String> keywords = new HashMap<String, String>();
-
- // create the required folders.
- // compute src folder path
- final String packagePath =
- stripString(packageName.replace(".", File.separator),
- File.separatorChar);
-
- // put this path in the place-holder map for project files that needs to list
- // files manually.
- keywords.put(PH_PACKAGE_PATH, packagePath);
- keywords.put(PH_PACKAGE, packageName);
- keywords.put(PH_VERSION_TAG, Integer.toString(MIN_BUILD_VERSION_TAG));
-
-
- // compute some activity related information
- String fqActivityName = null, activityPath = null, activityClassName = null;
- String originalActivityEntry = activityEntry;
- String originalActivityClassName = null;
- if (activityEntry != null) {
- if (isTestProject) {
- // append Test so that it doesn't collide with the main project activity.
- activityEntry += "Test";
-
- // get the classname from the original activity entry.
- int pos = originalActivityEntry.lastIndexOf('.');
- if (pos != -1) {
- originalActivityClassName = originalActivityEntry.substring(pos + 1);
- } else {
- originalActivityClassName = originalActivityEntry;
- }
- }
-
- // get the fully qualified name of the activity
- fqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
- activityEntry);
-
- // get the activity path (replace the . to /)
- activityPath = stripString(fqActivityName.replace(".", File.separator),
- File.separatorChar);
-
- // remove the last segment, so that we only have the path to the activity, but
- // not the activity filename itself.
- activityPath = activityPath.substring(0,
- activityPath.lastIndexOf(File.separatorChar));
-
- // finally, get the class name for the activity
- activityClassName = fqActivityName.substring(fqActivityName.lastIndexOf('.') + 1);
- }
-
- // at this point we have the following for the activity:
- // activityEntry: this is the manifest entry. For instance .MyActivity
- // fqActivityName: full-qualified class name: com.foo.MyActivity
- // activityClassName: only the classname: MyActivity
- // originalActivityClassName: the classname of the activity being tested (if applicable)
-
- // Add whatever activity info is needed in the place-holder map.
- // Older templates only expect ACTIVITY_NAME to be the same (and unmodified for tests).
- if (target.getVersion().getApiLevel() < 4) { // legacy
- if (originalActivityEntry != null) {
- keywords.put(PH_ACTIVITY_NAME, originalActivityEntry);
- }
- } else {
- // newer templates make a difference between the manifest entries, classnames,
- // as well as the main and test classes.
- if (activityEntry != null) {
- keywords.put(PH_ACTIVITY_ENTRY_NAME, activityEntry);
- keywords.put(PH_ACTIVITY_CLASS_NAME, activityClassName);
- keywords.put(PH_ACTIVITY_FQ_NAME, fqActivityName);
- if (originalActivityClassName != null) {
- keywords.put(PH_ACTIVITY_TESTED_CLASS_NAME, originalActivityClassName);
- }
- }
- }
-
- // Take the project name from the command line if there's one
- if (projectName != null) {
- keywords.put(PH_PROJECT_NAME, projectName);
- } else {
- if (activityClassName != null) {
- // Use the activity class name as project name
- keywords.put(PH_PROJECT_NAME, activityClassName);
- } else {
- // We need a project name. Just pick up the basename of the project
- // directory.
- projectName = projectFolder.getName();
- keywords.put(PH_PROJECT_NAME, projectName);
- }
- }
-
- // create the source folder for the activity
- if (activityClassName != null) {
- String srcActivityFolderPath =
- SdkConstants.FD_SOURCES + File.separator + activityPath;
- File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
-
- String javaTemplate = isTestProject ? "java_tests_file.template"
- : "java_file.template";
- String activityFileName = activityClassName + ".java";
-
- installTargetTemplate(javaTemplate, new File(sourceFolder, activityFileName),
- keywords, target);
- } else {
- // we should at least create 'src'
- createDirs(projectFolder, SdkConstants.FD_SOURCES);
- }
-
- // create other useful folders
- File resourceFolder = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
- createDirs(projectFolder, SdkConstants.FD_OUTPUT);
- createDirs(projectFolder, SdkConstants.FD_NATIVE_LIBS);
-
- if (isTestProject == false) {
- /* Make res files only for non test projects */
- File valueFolder = createDirs(resourceFolder, SdkConstants.FD_RES_VALUES);
- installTargetTemplate("strings.template", new File(valueFolder, "strings.xml"),
- keywords, target);
-
- File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_RES_LAYOUT);
- installTargetTemplate("layout.template", new File(layoutFolder, "main.xml"),
- keywords, target);
-
- // create the icons
- if (installIcons(resourceFolder, target)) {
- keywords.put(PH_ICON, "android:icon=\"@drawable/ic_launcher\"");
- } else {
- keywords.put(PH_ICON, "");
- }
- }
-
- /* Make AndroidManifest.xml and build.xml files */
- String manifestTemplate = "AndroidManifest.template";
- if (isTestProject) {
- manifestTemplate = "AndroidManifest.tests.template";
- }
-
- installTargetTemplate(manifestTemplate,
- new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML),
- keywords, target);
-
- installTemplate("build.template",
- new File(projectFolder, SdkConstants.FN_BUILD_XML),
- keywords);
-
- // install the proguard config file.
- installTemplate(SdkConstants.FN_PROJECT_PROGUARD_FILE,
- new File(projectFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
- null /*keywords*/);
- } catch (Exception e) {
- mLog.error(e, null);
- }
- }
-
- /**
- * Creates a new (gradle) project.
- * <p/>
- * The caller should have already checked and sanitized the parameters.
- *
- * @param folderPath the folder of the project to create.
- * @param projectName the name of the project. The name must match the
- * {@link #RE_PROJECT_NAME} regex.
- * @param packageName the package of the project. The name must match the
- * {@link #RE_PACKAGE_NAME} regex.
- * @param activityEntry the activity of the project as it will appear in the manifest. Can be
- * null if no activity should be created. The name must match the
- * {@link #RE_ACTIVITY_NAME} regex.
- * @param target the project target.
- * @param library whether the project is a library.
- * @param artifactVersion the version of the gradle artifact in maven.
- */
- public void createGradleProject(String folderPath, String projectName,
- String packageName, String activityEntry, IAndroidTarget target, boolean library,
- String artifactVersion) {
-
- // create project folder if it does not exist
- File projectFolder = checkNewProjectLocation(folderPath);
- if (projectFolder == null) {
- return;
- }
-
- try {
- // first create the project properties.
-
- // location of the SDK goes in localProperty
- ProjectPropertiesWorkingCopy localProperties = ProjectProperties.create(folderPath,
- PropertyType.LOCAL);
- localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
- localProperties.save();
-
- // create the map for place-holders of values to replace in the templates
- final HashMap<String, String> keywords = new HashMap<String, String>();
- final HashMap<String, String> testKeywords = new HashMap<String, String>();
-
- // create the required folders.
- // compute src folder path
- final String packagePath =
- stripString(packageName.replace(".", File.separator),
- File.separatorChar);
-
- // put this path in the place-holder map for project files that needs to list
- // files manually.
- keywords.put(PH_PACKAGE_PATH, packagePath);
- keywords.put(PH_PACKAGE, packageName);
-
- testKeywords.put(PH_PACKAGE_PATH, packagePath);
- testKeywords.put(PH_PACKAGE, packageName);
-
- // compute some activity related information
- String activityPath = null, activityClassName = null;
- String testActivityPath = null, testActivityClassName = null;
- if (activityEntry != null) {
- // get the fully qualified name of the activity
- String fqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
- activityEntry);
-
- // get the activity path (replace the . to /)
- activityPath = stripString(fqActivityName.replace(".", File.separator),
- File.separatorChar);
-
- // remove the last segment, so that we only have the path to the activity, but
- // not the activity filename itself.
- activityPath = activityPath.substring(0,
- activityPath.lastIndexOf(File.separatorChar));
-
- // finally, get the class name for the activity
- activityClassName = fqActivityName.substring(fqActivityName.lastIndexOf('.') + 1);
-
- // at this point we have the following for the activity:
- // activityEntry: this is the manifest entry. For instance .MyActivity
- // fqActivityName: full-qualified class name: com.foo.MyActivity
- // activityClassName: only the classname: MyActivity
-
- // append Test so that it doesn't collide with the main project activity.
- String testActivityEntry = activityEntry + "Test";
-
- // get the fully qualified name of the test
- String testFqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
- testActivityEntry);
-
- // get the test path (replace the . to /)
- testActivityPath = stripString(testFqActivityName.replace(".", File.separator),
- File.separatorChar);
-
- // remove the last segment, so that we only have the path to the test, but
- // not the test filename itself.
- testActivityPath = testActivityPath.substring(0,
- testActivityPath.lastIndexOf(File.separatorChar));
-
- // finally, get the class name for the test
- testActivityClassName = testFqActivityName.substring(testFqActivityName.lastIndexOf('.') + 1);
-
- // at this point we have the following for the test:
- // testActivityEntry: this is the manifest entry. For instance .MyActivityTest
- // testFqActivityName: full-qualified class name: com.foo.MyActivityTest
- // testActivityClassName: only the classname: MyActivityTest
-
- // Add whatever activity info is needed in the place-holder map.
- // Older templates only expect ACTIVITY_NAME to be the same (and unmodified for tests).
- if (target.getVersion().getApiLevel() < 4) { // legacy
- keywords.put(PH_ACTIVITY_NAME, activityEntry);
- testKeywords.put(PH_ACTIVITY_NAME, activityEntry);
- } else {
- // newer templates make a difference between the manifest entries, classnames,
- // as well as the main and test classes.
- keywords.put(PH_ACTIVITY_ENTRY_NAME, activityEntry);
- keywords.put(PH_ACTIVITY_CLASS_NAME, activityClassName);
- keywords.put(PH_ACTIVITY_FQ_NAME, fqActivityName);
-
- testKeywords.put(PH_ACTIVITY_ENTRY_NAME, testActivityEntry);
- testKeywords.put(PH_ACTIVITY_CLASS_NAME, testActivityClassName);
- testKeywords.put(PH_ACTIVITY_FQ_NAME, testFqActivityName);
- testKeywords.put(PH_ACTIVITY_TESTED_CLASS_NAME, activityClassName);
- }
- }
-
- // Take the project name from the command line if there's one
- if (projectName != null) {
- keywords.put(PH_PROJECT_NAME, projectName);
- testKeywords.put(PH_PROJECT_NAME, projectName);
- } else {
- // Use the activity class name as project name, else just
- // pick up the basename of the project directory.
- keywords.put(PH_PROJECT_NAME, (activityClassName != null) ?
- activityClassName : projectFolder.getName());
- testKeywords.put(PH_PROJECT_NAME, (testActivityClassName != null) ?
- testActivityClassName : projectFolder.getName());
- }
-
- String srcMainPath = SdkConstants.FD_SOURCES + File.separator +
- SdkConstants.FD_MAIN;
- String srcTestPath = SdkConstants.FD_SOURCES + File.separator +
- SdkConstants.FD_TEST;
-
- // create the source folders for the activity
- String srcMainCodePath = srcMainPath + File.separator + SdkConstants.FD_JAVA;
- createDirs(projectFolder, srcMainCodePath);
- if (activityClassName != null) {
- String srcActivityFolderPath =
- srcMainCodePath + File.separator + activityPath;
- File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
-
- String activityFileName = activityClassName + ".java";
-
- installTargetTemplate("java_file.template",
- new File(sourceFolder, activityFileName), keywords, target);
- }
-
- // create the source folders for the test
- String srcTestCodePath = srcTestPath + File.separator + SdkConstants.FD_JAVA;
- createDirs(projectFolder, srcTestCodePath);
- if (testActivityClassName != null) {
- String srcActivityFolderPath =
- srcTestCodePath + File.separator + testActivityPath;
- File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
-
- String activityFileName = testActivityClassName + ".java";
-
- installTargetTemplate("java_tests_file.template",
- new File(sourceFolder, activityFileName), testKeywords, target);
- }
-
- // create the res xml files
- String srcMainResPath = srcMainPath + File.separator + SdkConstants.FD_RES;
- File resourceFolder = createDirs(projectFolder, srcMainResPath);
-
- File valueFolder = createDirs(resourceFolder, SdkConstants.FD_RES_VALUES);
- installTargetTemplate("strings.template", new File(valueFolder, "strings.xml"),
- keywords, target);
-
- File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_RES_LAYOUT);
- installTargetTemplate("layout.template", new File(layoutFolder, "main.xml"),
- keywords, target);
-
- // create the icons
- if (installIcons(resourceFolder, target)) {
- keywords.put(PH_ICON, "android:icon=\"@drawable/ic_launcher\"");
- } else {
- keywords.put(PH_ICON, "");
- }
-
- // Create the AndroidManifest.xml and build.gradle files
- installTargetTemplate("AndroidManifest.template",
- new File(projectFolder, srcMainPath + File.separator +
- SdkConstants.FN_ANDROID_MANIFEST_XML),
- keywords, target);
-
- String buildToolRev = mSdkManager.getLatestBuildTool().getRevision().toString();
-
- keywords.put(PH_BUILD_TOOL_REV, buildToolRev);
- keywords.put(PH_ARTIFACT_VERSION, artifactVersion);
- keywords.put(PH_TARGET, target.hashString());
- keywords.put(PH_PLUGIN, (library) ? PLUGIN_LIB_PROJECT : PLUGIN_PROJECT);
-
- installTemplate("build_gradle.template",
- new File(projectFolder, SdkConstants.FN_BUILD_GRADLE),
- keywords);
-
- // Create the gradle wrapper files
- createDirs(projectFolder, SdkConstants.FD_GRADLE_WRAPPER);
- installGradleWrapperFile(SdkConstants.FD_GRADLE_WRAPPER + File.separator
- + SdkConstants.FN_GRADLE_WRAPPER_JAR,
- projectFolder);
- installGradleWrapperFile(SdkConstants.FD_GRADLE_WRAPPER + File.separator
- + SdkConstants.FN_GRADLE_WRAPPER_PROPERTIES,
- projectFolder);
- installGradleWrapperFile(SdkConstants.FN_GRADLE_WRAPPER_WIN, projectFolder);
- installGradleWrapperFile(SdkConstants.FN_GRADLE_WRAPPER_UNIX, projectFolder);
- new File(projectFolder, SdkConstants.FN_GRADLE_WRAPPER_UNIX).setExecutable(true, false);
- } catch (Exception e) {
- mLog.error(e, null);
- }
- }
-
- private File checkNewProjectLocation(String folderPath) {
- File projectFolder = new File(folderPath);
- if (!projectFolder.exists()) {
-
- boolean created = false;
- Throwable t = null;
- try {
- created = projectFolder.mkdirs();
- } catch (Exception e) {
- t = e;
- }
-
- if (created) {
- println("Created project directory: %1$s", projectFolder);
- } else {
- mLog.error(t, "Could not create directory: %1$s", projectFolder);
- return null;
- }
- } else {
- Exception e = null;
- String error = null;
- try {
- String[] content = projectFolder.list();
- if (content == null) {
- error = "Project folder '%1$s' is not a directory.";
- } else if (content.length != 0) {
- error = "Project folder '%1$s' is not empty. Please consider using '%2$s update' instead.";
- }
- } catch (Exception e1) {
- e = e1;
- }
-
- if (e != null || error != null) {
- mLog.error(e, error, projectFolder, SdkConstants.androidCmdName());
- }
- }
- return projectFolder;
- }
-
- /**
- * Updates an existing project.
- * <p/>
- * Workflow:
- * <ul>
- * <li> Check AndroidManifest.xml is present (required)
- * <li> Check if there's a legacy properties file and convert it
- * <li> Check there's a project.properties with a target *or* --target was specified
- * <li> Update default.prop if --target was specified
- * <li> Refresh/create "sdk" in local.properties
- * <li> Build.xml: create if not present or if version-tag is found or not. version-tag:custom
- * prevent any overwrite. version-tag:[integer] will override. missing version-tag will query
- * the dev.
- * </ul>
- *
- * @param folderPath the folder of the project to update. This folder must exist.
- * @param target the project target. Can be null.
- * @param projectName The project name from --name. Can be null.
- * @param libraryPath the path to a library to add to the references. Can be null.
- * @return true if the project was successfully updated.
- */
- @SuppressWarnings("deprecation")
- public boolean updateProject(String folderPath, IAndroidTarget target, String projectName,
- String libraryPath) {
- // since this is an update, check the folder does point to a project
- FileWrapper androidManifest = checkProjectFolder(folderPath,
- SdkConstants.FN_ANDROID_MANIFEST_XML);
- if (androidManifest == null) {
- return false;
- }
-
- // get the parent folder.
- FolderWrapper projectFolder = (FolderWrapper) androidManifest.getParentFolder();
-
- boolean hasProguard = false;
-
- // Check there's a project.properties with a target *or* --target was specified
- IAndroidTarget originalTarget = null;
- boolean writeProjectProp = false;
- ProjectProperties props = ProjectProperties.load(projectFolder, PropertyType.PROJECT);
-
- if (props == null) {
- // no project.properties, try to load default.properties
- props = ProjectProperties.load(projectFolder, PropertyType.LEGACY_DEFAULT);
- writeProjectProp = true;
- }
-
- if (props != null) {
- String targetHash = props.getProperty(ProjectProperties.PROPERTY_TARGET);
- originalTarget = mSdkManager.getTargetFromHashString(targetHash);
-
- // if the project is already setup with proguard, we won't copy the proguard config.
- hasProguard = props.getProperty(ProjectProperties.PROPERTY_PROGUARD_CONFIG) != null;
- }
-
- if (originalTarget == null && target == null) {
- mLog.error(null,
- "The project either has no target set or the target is invalid.\n" +
- "Please provide a --target to the '%1$s update' command.",
- SdkConstants.androidCmdName());
- return false;
- }
-
- boolean saveProjectProps = false;
-
- ProjectPropertiesWorkingCopy propsWC = null;
-
- // Update default.prop if --target was specified
- if (target != null || writeProjectProp) {
- // we already attempted to load the file earlier, if that failed, create it.
- if (props == null) {
- propsWC = ProjectProperties.create(projectFolder, PropertyType.PROJECT);
- } else {
- propsWC = props.makeWorkingCopy(PropertyType.PROJECT);
- }
-
- // set or replace the target
- if (target != null) {
- propsWC.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
- }
- saveProjectProps = true;
- }
-
- if (libraryPath != null) {
- // At this point, the default properties already exists, either because they were
- // already there or because they were created with a new target
- if (propsWC == null) {
- assert props != null;
- propsWC = props.makeWorkingCopy();
- }
-
- // check the reference is valid
- File libProject = new File(libraryPath);
- String resolvedPath;
- if (libProject.isAbsolute() == false) {
- libProject = new File(projectFolder, libraryPath);
- try {
- resolvedPath = libProject.getCanonicalPath();
- } catch (IOException e) {
- mLog.error(e, "Unable to resolve path to library project: %1$s", libraryPath);
- return false;
- }
- } else {
- resolvedPath = libProject.getAbsolutePath();
- }
-
- println("Resolved location of library project to: %1$s", resolvedPath);
-
- // check the lib project exists
- if (checkProjectFolder(resolvedPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
- mLog.error(null, "No Android Manifest at: %1$s", resolvedPath);
- return false;
- }
-
- // look for other references to figure out the index
- int index = 1;
- while (true) {
- String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index);
- assert props != null;
- if (props == null) {
- // This should not happen yet SDK bug 20535 says it can, not sure how.
- break;
- }
- String ref = props.getProperty(propName);
- if (ref == null) {
- break;
- } else {
- index++;
- }
- }
-
- String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index);
- propsWC.setProperty(propName, libraryPath);
- saveProjectProps = true;
- }
-
- // save the default props if needed.
- if (saveProjectProps) {
- try {
- assert propsWC != null;
- propsWC.save();
- if (writeProjectProp) {
- println("Updated and renamed %1$s to %2$s",
- PropertyType.LEGACY_DEFAULT.getFilename(),
- PropertyType.PROJECT.getFilename());
- } else {
- println("Updated %1$s", PropertyType.PROJECT.getFilename());
- }
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.PROJECT.getFilename(),
- folderPath);
- return false;
- }
-
- if (writeProjectProp) {
- // need to delete the default prop file.
- ProjectProperties.delete(projectFolder, PropertyType.LEGACY_DEFAULT);
- }
- }
-
- // Refresh/create "sdk" in local.properties
- // because the file may already exists and contain other values (like apk config),
- // we first try to load it.
- props = ProjectProperties.load(projectFolder, PropertyType.LOCAL);
- if (props == null) {
- propsWC = ProjectProperties.create(projectFolder, PropertyType.LOCAL);
- } else {
- propsWC = props.makeWorkingCopy();
- }
-
- // set or replace the sdk location.
- propsWC.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
- try {
- propsWC.save();
- println("Updated %1$s", PropertyType.LOCAL.getFilename());
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.LOCAL.getFilename(),
- folderPath);
- return false;
- }
-
- // legacy: check if build.properties must be renamed to ant.properties.
- props = ProjectProperties.load(projectFolder, PropertyType.ANT);
- if (props == null) {
- props = ProjectProperties.load(projectFolder, PropertyType.LEGACY_BUILD);
- if (props != null) {
- try {
- // get a working copy with the new property type
- propsWC = props.makeWorkingCopy(PropertyType.ANT);
- propsWC.save();
-
- // delete the old file
- ProjectProperties.delete(projectFolder, PropertyType.LEGACY_BUILD);
-
- println("Renamed %1$s to %2$s",
- PropertyType.LEGACY_BUILD.getFilename(),
- PropertyType.ANT.getFilename());
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.ANT.getFilename(),
- folderPath);
- return false;
- }
- }
- }
-
- // Build.xml: create if not present or no <androidinit/> in it
- File buildXml = new File(projectFolder, SdkConstants.FN_BUILD_XML);
- boolean needsBuildXml = projectName != null || !buildXml.exists();
-
- // if it seems there's no need for a new build.xml, look for inside the file
- // to try to detect old ones that may need updating.
- if (!needsBuildXml) {
- // we are looking for version-tag: followed by either an integer or "custom".
- if (checkFileContainsRegexp(buildXml, "version-tag:\\s*custom") != null) { //$NON-NLS-1$
- println("%1$s: Found version-tag: custom. File will not be updated.",
- SdkConstants.FN_BUILD_XML);
- } else {
- Matcher m = checkFileContainsRegexp(buildXml, "version-tag:\\s*(\\d+)"); //$NON-NLS-1$
- if (m == null) {
- println("----------\n" +
- "%1$s: Failed to find version-tag string. File must be updated.\n" +
- "In order to not erase potential customizations, the file will not be automatically regenerated.\n" +
- "If no changes have been made to the file, delete it manually and run the command again.\n" +
- "If you have made customizations to the build process, the file must be manually updated.\n" +
- "It is recommended to:\n" +
- "\t* Copy current file to a safe location.\n" +
- "\t* Delete original file.\n" +
- "\t* Run command again to generate a new file.\n" +
- "\t* Port customizations to the new file, by looking at the new rules file\n" +
- "\t located at <SDK>/tools/ant/build.xml\n" +
- "\t* Update file to contain\n" +
- "\t version-tag: custom\n" +
- "\t to prevent file from being rewritten automatically by the SDK tools.\n" +
- "----------\n",
- SdkConstants.FN_BUILD_XML);
- } else {
- String versionStr = m.group(1);
- if (versionStr != null) {
- // can't fail due to regexp above.
- int version = Integer.parseInt(versionStr);
- if (version < MIN_BUILD_VERSION_TAG) {
- println("%1$s: Found version-tag: %2$d. Expected version-tag: %3$d: file must be updated.",
- SdkConstants.FN_BUILD_XML, version, MIN_BUILD_VERSION_TAG);
- needsBuildXml = true;
- }
- }
- }
- }
- }
-
- if (needsBuildXml) {
- // create the map for place-holders of values to replace in the templates
- final HashMap<String, String> keywords = new HashMap<String, String>();
-
- // put the current version-tag value
- keywords.put(PH_VERSION_TAG, Integer.toString(MIN_BUILD_VERSION_TAG));
-
- // if there was no project name on the command line, figure one out.
- if (projectName == null) {
- // otherwise, take it from the existing build.xml if it exists already.
- if (buildXml.exists()) {
- try {
- XPathFactory factory = XPathFactory.newInstance();
- XPath xpath = factory.newXPath();
-
- projectName = xpath.evaluate(XPATH_PROJECT_NAME,
- new InputSource(new FileInputStream(buildXml)));
- } catch (XPathExpressionException e) {
- // this is ok since we're going to recreate the file.
- mLog.error(e, "Unable to find existing project name from %1$s",
- SdkConstants.FN_BUILD_XML);
- } catch (FileNotFoundException e) {
- // can't happen since we check above.
- }
- }
-
- // if the project is still null, then we find another way.
- if (projectName == null) {
- extractPackageFromManifest(androidManifest, keywords);
- if (keywords.containsKey(PH_ACTIVITY_ENTRY_NAME)) {
- String activity = keywords.get(PH_ACTIVITY_ENTRY_NAME);
- // keep only the last segment if applicable
- int pos = activity.lastIndexOf('.');
- if (pos != -1) {
- activity = activity.substring(pos + 1);
- }
-
- // Use the activity as project name
- projectName = activity;
-
- println("No project name specified, using Activity name '%1$s'.\n" +
- "If you wish to change it, edit the first line of %2$s.",
- activity, SdkConstants.FN_BUILD_XML);
- } else {
- // We need a project name. Just pick up the basename of the project
- // directory.
- File projectCanonicalFolder = projectFolder;
- try {
- projectCanonicalFolder = projectCanonicalFolder.getCanonicalFile();
- } catch (IOException e) {
- // ignore, keep going
- }
-
- // Use the folder name as project name
- projectName = projectCanonicalFolder.getName();
-
- println("No project name specified, using project folder name '%1$s'.\n" +
- "If you wish to change it, edit the first line of %2$s.",
- projectName, SdkConstants.FN_BUILD_XML);
- }
- }
- }
-
- // put the project name in the map for replacement during the template installation.
- keywords.put(PH_PROJECT_NAME, projectName);
-
- if (mLevel == OutputLevel.VERBOSE) {
- println("Regenerating %1$s with project name %2$s",
- SdkConstants.FN_BUILD_XML,
- keywords.get(PH_PROJECT_NAME));
- }
-
- try {
- installTemplate("build.template", buildXml, keywords);
- } catch (ProjectCreateException e) {
- mLog.error(e, null);
- return false;
- }
- }
-
- if (hasProguard == false) {
- try {
- installTemplate(SdkConstants.FN_PROJECT_PROGUARD_FILE,
- // Write ProGuard config files with the extension .pro which
- // is what is used in the ProGuard documentation and samples
- new File(projectFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
- null /*placeholderMap*/);
- } catch (ProjectCreateException e) {
- mLog.error(e, null);
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Updates a test project with a new path to the main (tested) project.
- * @param folderPath the path of the test project.
- * @param pathToMainProject the path to the main project, relative to the test project.
- */
- @SuppressWarnings("deprecation")
- public void updateTestProject(final String folderPath, final String pathToMainProject,
- final SdkManager sdkManager) {
- // since this is an update, check the folder does point to a project
- if (checkProjectFolder(folderPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
- return;
- }
-
- // check the path to the main project is valid.
- File mainProject = new File(pathToMainProject);
- String resolvedPath;
- if (mainProject.isAbsolute() == false) {
- mainProject = new File(folderPath, pathToMainProject);
- try {
- resolvedPath = mainProject.getCanonicalPath();
- } catch (IOException e) {
- mLog.error(e, "Unable to resolve path to main project: %1$s", pathToMainProject);
- return;
- }
- } else {
- resolvedPath = mainProject.getAbsolutePath();
- }
-
- println("Resolved location of main project to: %1$s", resolvedPath);
-
- // check the main project exists
- if (checkProjectFolder(resolvedPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
- mLog.error(null, "No Android Manifest at: %1$s", resolvedPath);
- return;
- }
-
- // now get the target from the main project
- ProjectProperties projectProp = ProjectProperties.load(resolvedPath, PropertyType.PROJECT);
- if (projectProp == null) {
- // legacy support for older file name.
- projectProp = ProjectProperties.load(resolvedPath, PropertyType.LEGACY_DEFAULT);
- if (projectProp == null) {
- mLog.error(null, "No %1$s at: %2$s", PropertyType.PROJECT.getFilename(),
- resolvedPath);
- return;
- }
- }
-
- String targetHash = projectProp.getProperty(ProjectProperties.PROPERTY_TARGET);
- if (targetHash == null) {
- mLog.error(null, "%1$s in the main project has no target property.",
- PropertyType.PROJECT.getFilename());
- return;
- }
-
- IAndroidTarget target = sdkManager.getTargetFromHashString(targetHash);
- if (target == null) {
- mLog.error(null, "Main project target %1$s is not a valid target.", targetHash);
- return;
- }
-
- // update test-project does not support the --name parameter, therefore the project
- // name should generally not be passed to updateProject().
- // However if build.xml does not exist then updateProject() will recreate it. In this
- // case we will need the project name.
- // To do this, we look for the parent project name and add "test" to it.
- // If the main project does not have a project name (yet), then the default behavior
- // will be used (look for activity and then folder name)
- String projectName = null;
- XPathFactory factory = XPathFactory.newInstance();
- XPath xpath = factory.newXPath();
-
- File testBuildXml = new File(folderPath, SdkConstants.FN_BUILD_XML);
- if (testBuildXml.isFile() == false) {
- File mainBuildXml = new File(resolvedPath, SdkConstants.FN_BUILD_XML);
- if (mainBuildXml.isFile()) {
- try {
- // get the name of the main project and add Test to it.
- String mainProjectName = xpath.evaluate(XPATH_PROJECT_NAME,
- new InputSource(new FileInputStream(mainBuildXml)));
- projectName = mainProjectName + "Test";
- } catch (XPathExpressionException e) {
- // it's ok, updateProject() will figure out a name automatically.
- // We do log the error though as the build.xml file may be broken.
- mLog.warning("Failed to parse %1$s.\n" +
- "File may not be valid. Consider running 'android update project' on the main project.",
- mainBuildXml.getPath());
- } catch (FileNotFoundException e) {
- // should not happen since we check first.
- }
- }
- }
-
- // now update the project as if it's a normal project
- if (updateProject(folderPath, target, projectName, null /*libraryPath*/) == false) {
- // error message has already been displayed.
- return;
- }
-
- // add the test project specific properties.
- // At this point, we know build.prop has been renamed ant.prop
- ProjectProperties antProps = ProjectProperties.load(folderPath, PropertyType.ANT);
- ProjectPropertiesWorkingCopy antWorkingCopy;
- if (antProps == null) {
- antWorkingCopy = ProjectProperties.create(folderPath, PropertyType.ANT);
- } else {
- antWorkingCopy = antProps.makeWorkingCopy();
- }
-
- // set or replace the path to the main project
- antWorkingCopy.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT, pathToMainProject);
- try {
- antWorkingCopy.save();
- println("Updated %1$s", PropertyType.ANT.getFilename());
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.ANT.getFilename(),
- folderPath);
- return;
- }
- }
-
- /**
- * Checks whether the give <var>folderPath</var> is a valid project folder, and returns
- * a {@link FileWrapper} to the required file.
- * <p/>This checks that the folder exists and contains an AndroidManifest.xml file in it.
- * <p/>Any error are output using {@link #mLog}.
- * @param folderPath the folder to check
- * @param requiredFilename the file name of the file that's required.
- * @return a {@link FileWrapper} to the AndroidManifest.xml file, or null otherwise.
- */
- private FileWrapper checkProjectFolder(String folderPath, String requiredFilename) {
- // project folder must exist and be a directory, since this is an update
- FolderWrapper projectFolder = new FolderWrapper(folderPath);
- if (!projectFolder.isDirectory()) {
- mLog.error(null, "Project folder '%1$s' is not a valid directory.",
- projectFolder);
- return null;
- }
-
- // Check AndroidManifest.xml is present
- FileWrapper requireFile = new FileWrapper(projectFolder, requiredFilename);
- if (!requireFile.isFile()) {
- mLog.error(null,
- "%1$s is not a valid project (%2$s not found).",
- folderPath, requiredFilename);
- return null;
- }
-
- return requireFile;
- }
-
- /**
- * Looks for a given regex in a file and returns the matcher if any line of the input file
- * contains the requested regexp.
- *
- * @param file the file to search.
- * @param regexp the regexp to search for.
- *
- * @return a Matcher or null if the regexp is not found.
- */
- private Matcher checkFileContainsRegexp(File file, String regexp) {
- Pattern p = Pattern.compile(regexp);
-
- BufferedReader in = null;
- try {
- in = new BufferedReader(new FileReader(file));
- String line;
-
- while ((line = in.readLine()) != null) {
- Matcher m = p.matcher(line);
- if (m.find()) {
- return m;
- }
- }
-
- in.close();
- } catch (Exception e) {
- // ignore
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- return null;
- }
-
- /**
- * Extracts a "full" package & activity name from an AndroidManifest.xml.
- * <p/>
- * The keywords dictionary is always filed the package name under the key {@link #PH_PACKAGE}.
- * If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_ENTRY_NAME}.
- * When no activity is found, this key is not created.
- *
- * @param manifestFile The AndroidManifest.xml file
- * @param outKeywords Place where to put the out parameters: package and activity names.
- * @return True if the package/activity was parsed and updated in the keyword dictionary.
- */
- private boolean extractPackageFromManifest(File manifestFile,
- Map<String, String> outKeywords) {
- try {
- XPath xpath = AndroidXPathFactory.newXPath();
-
- InputSource source = new InputSource(new FileReader(manifestFile));
- String packageName = xpath.evaluate("/manifest/@package", source);
-
- source = new InputSource(new FileReader(manifestFile));
-
- // Select the "android:name" attribute of all <activity> nodes but only if they
- // contain a sub-node <intent-filter><action> with an "android:name" attribute which
- // is 'android.intent.action.MAIN' and an <intent-filter><category> with an
- // "android:name" attribute which is 'android.intent.category.LAUNCHER'
- String expression = String.format("/manifest/application/activity" +
- "[intent-filter/action/@%1$s:name='android.intent.action.MAIN' and " +
- "intent-filter/category/@%1$s:name='android.intent.category.LAUNCHER']" +
- "/@%1$s:name", AndroidXPathFactory.DEFAULT_NS_PREFIX);
-
- NodeList activityNames = (NodeList) xpath.evaluate(expression, source,
- XPathConstants.NODESET);
-
- // If we get here, both XPath expressions were valid so we're most likely dealing
- // with an actual AndroidManifest.xml file. The nodes may not have the requested
- // attributes though, if which case we should warn.
-
- if (packageName == null || packageName.isEmpty()) {
- mLog.error(null,
- "Missing <manifest package=\"...\"> in '%1$s'",
- manifestFile.getName());
- return false;
- }
-
- // Get the first activity that matched earlier. If there is no activity,
- // activityName is set to an empty string and the generated "combined" name
- // will be in the form "package." (with a dot at the end).
- String activityName = "";
- if (activityNames.getLength() > 0) {
- activityName = activityNames.item(0).getNodeValue();
- }
-
- if (mLevel == OutputLevel.VERBOSE && activityNames.getLength() > 1) {
- println("WARNING: There is more than one activity defined in '%1$s'.\n" +
- "Only the first one will be used. If this is not appropriate, you need\n" +
- "to specify one of these values manually instead:",
- manifestFile.getName());
-
- for (int i = 0; i < activityNames.getLength(); i++) {
- String name = activityNames.item(i).getNodeValue();
- name = combinePackageActivityNames(packageName, name);
- println("- %1$s", name);
- }
- }
-
- if (activityName.isEmpty()) {
- mLog.warning("Missing <activity %1$s:name=\"...\"> in '%2$s'.\n" +
- "No activity will be generated.",
- AndroidXPathFactory.DEFAULT_NS_PREFIX, manifestFile.getName());
- } else {
- outKeywords.put(PH_ACTIVITY_ENTRY_NAME, activityName);
- }
-
- outKeywords.put(PH_PACKAGE, packageName);
- return true;
-
- } catch (IOException e) {
- mLog.error(e, "Failed to read %1$s", manifestFile.getName());
- } catch (XPathExpressionException e) {
- Throwable t = e.getCause();
- mLog.error(t == null ? e : t,
- "Failed to parse %1$s",
- manifestFile.getName());
- }
-
- return false;
- }
-
- private String combinePackageActivityNames(String packageName, String activityName) {
- // Activity Name can have 3 forms:
- // - ".Name" means this is a class name in the given package name.
- // The full FQCN is thus packageName + ".Name"
- // - "Name" is an older variant of the former. Full FQCN is packageName + "." + "Name"
- // - "com.blah.Name" is a full FQCN. Ignore packageName and use activityName as-is.
- // To be valid, the package name should have at least two components. This is checked
- // later during the creation of the build.xml file, so we just need to detect there's
- // a dot but not at pos==0.
-
- int pos = activityName.indexOf('.');
- if (pos == 0) {
- return packageName + activityName;
- } else if (pos > 0) {
- return activityName;
- } else {
- return packageName + "." + activityName;
- }
- }
-
- /**
- * Installs a new file that is based on a template file provided by a given target.
- * Each match of each key from the place-holder map in the template will be replaced with its
- * corresponding value in the created file.
- *
- * @param templateName the name of to the template file
- * @param destFile the path to the destination file, relative to the project
- * @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @param target the Target of the project that will be providing the template.
- * @throws ProjectCreateException
- */
- private void installTargetTemplate(String templateName, File destFile,
- Map<String, String> placeholderMap, IAndroidTarget target)
- throws ProjectCreateException {
- // query the target for its template directory
- String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
- final String sourcePath = templateFolder + File.separator + templateName;
-
- installFullPathTemplate(sourcePath, destFile, placeholderMap);
- }
-
- /**
- * Installs a new file from the gradle wrapper template.
- *
- * @param templateName the name of the template file
- * @param projectFolder the path to the project folder
- * @throws ProjectCreateException
- */
- public void installGradleWrapperFile(String templateName, File projectFolder)
- throws ProjectCreateException {
- String templateFolder = mSdkFolder + File.separator +
- SdkConstants.OS_SDK_TOOLS_TEMPLATES_GRADLE_WRAPPER_FOLDER;
-
- installBinaryFile(new File(templateFolder, templateName),
- new File(projectFolder, templateName));
- }
-
- /**
- * Installs a new file that is based on a template file provided by the tools folder.
- * Each match of each key from the place-holder map in the template will be replaced with its
- * corresponding value in the created file.
- *
- * @param templateName the name of to the template file
- * @param destFile the path to the destination file, relative to the project
- * @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @throws ProjectCreateException
- */
- public void installTemplate(String templateName, File destFile,
- Map<String, String> placeholderMap)
- throws ProjectCreateException {
- // query the target for its template directory
- String templateFolder = mSdkFolder + File.separator + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
- final String sourcePath = templateFolder + File.separator + templateName;
-
- installFullPathTemplate(sourcePath, destFile, placeholderMap);
- }
-
- /**
- * Installs a new file that is based on a template.
- * Each match of each key from the place-holder map in the template will be replaced with its
- * corresponding value in the created file.
- *
- * @param sourcePath the full path to the source template file
- * @param destFile the destination file
- * @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @throws ProjectCreateException
- */
- private void installFullPathTemplate(String sourcePath, File destFile,
- Map<String, String> placeholderMap) throws ProjectCreateException {
-
- boolean existed = destFile.exists();
-
- try {
- BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
- BufferedReader in = new BufferedReader(new FileReader(sourcePath));
- String line;
-
- while ((line = in.readLine()) != null) {
- if (placeholderMap != null) {
- for (Map.Entry<String, String> entry : placeholderMap.entrySet()) {
- line = line.replace(entry.getKey(), entry.getValue());
- }
- }
-
- out.write(line);
- out.newLine();
- }
-
- out.close();
- in.close();
- } catch (Exception e) {
- throw new ProjectCreateException(e, "Could not access %1$s: %2$s",
- destFile, e.getMessage());
- }
-
- println("%1$s file %2$s",
- existed ? "Updated" : "Added",
- destFile);
- }
-
- /**
- * Installs the project icons.
- * @param resourceFolder the resource folder
- * @param target the target of the project.
- * @return true if any icon was installed.
- */
- private boolean installIcons(File resourceFolder, IAndroidTarget target)
- throws ProjectCreateException {
- // query the target for its template directory
- String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
-
- boolean installedIcon = false;
-
- installedIcon |= installIcon(templateFolder, "ic_launcher_xhdpi.png", resourceFolder,
- "drawable-xhdpi");
- installedIcon |= installIcon(templateFolder, "ic_launcher_hdpi.png", resourceFolder,
- "drawable-hdpi");
- installedIcon |= installIcon(templateFolder, "ic_launcher_mdpi.png", resourceFolder,
- "drawable-mdpi");
- installedIcon |= installIcon(templateFolder, "ic_launcher_ldpi.png", resourceFolder,
- "drawable-ldpi");
-
- return installedIcon;
- }
-
- /**
- * Installs an Icon in the project.
- * @return true if the icon was installed.
- */
- private boolean installIcon(String templateFolder, String iconName, File resourceFolder,
- String folderName) throws ProjectCreateException {
- File icon = new File(templateFolder, iconName);
- if (icon.exists()) {
- File drawable = createDirs(resourceFolder, folderName);
- installBinaryFile(icon, new File(drawable, "ic_launcher.png"));
- return true;
- }
-
- return false;
- }
-
- /**
- * Installs a binary file
- * @param source the source file to copy
- * @param destination the destination file to write
- * @throws ProjectCreateException
- */
- private void installBinaryFile(File source, File destination) throws ProjectCreateException {
- byte[] buffer = new byte[8192];
-
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try {
- fis = new FileInputStream(source);
- fos = new FileOutputStream(destination);
-
- int read;
- while ((read = fis.read(buffer)) != -1) {
- fos.write(buffer, 0, read);
- }
-
- } catch (FileNotFoundException e) {
- // shouldn't happen since we check before.
- } catch (IOException e) {
- throw new ProjectCreateException(e, "Failed to read binary file: %1$s",
- source.getAbsolutePath());
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- // ignore
- }
- }
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- }
-
- /**
- * Prints a message unless silence is enabled.
- * <p/>
- * This is just a convenience wrapper around {@link ILogger#info(String, Object...)} from
- * {@link #mLog} after testing if output level is {@link OutputLevel#VERBOSE}.
- *
- * @param format Format for String.format
- * @param args Arguments for String.format
- */
- private void println(String format, Object... args) {
- if (mLevel != OutputLevel.SILENT) {
- if (!format.endsWith("\n")) {
- format += "\n";
- }
- mLog.info(format, args);
- }
- }
-
- /**
- * Creates a new folder, along with any parent folders that do not exists.
- *
- * @param parent the parent folder
- * @param name the name of the directory to create.
- * @throws ProjectCreateException
- */
- private File createDirs(File parent, String name) throws ProjectCreateException {
- final File newFolder = new File(parent, name);
- boolean existedBefore = true;
-
- if (!newFolder.exists()) {
- if (!newFolder.mkdirs()) {
- throw new ProjectCreateException("Could not create directory: %1$s", newFolder);
- }
- existedBefore = false;
- }
-
- if (newFolder.isDirectory()) {
- if (!newFolder.canWrite()) {
- throw new ProjectCreateException("Path is not writable: %1$s", newFolder);
- }
- } else {
- throw new ProjectCreateException("Path is not a directory: %1$s", newFolder);
- }
-
- if (!existedBefore) {
- try {
- println("Created directory %1$s", newFolder.getCanonicalPath());
- } catch (IOException e) {
- throw new ProjectCreateException(
- "Could not determine canonical path of created directory", e);
- }
- }
-
- return newFolder;
- }
-
- /**
- * Strips the string of beginning and trailing characters (multiple
- * characters will be stripped, example stripString("..test...", '.')
- * results in "test";
- *
- * @param s the string to strip
- * @param strip the character to strip from beginning and end
- * @return the stripped string or the empty string if everything is stripped.
- */
- private static String stripString(String s, char strip) {
- final int sLen = s.length();
- int newStart = 0, newEnd = sLen - 1;
-
- while (newStart < sLen && s.charAt(newStart) == strip) {
- newStart++;
- }
- while (newEnd >= 0 && s.charAt(newEnd) == strip) {
- newEnd--;
- }
-
- /*
- * newEnd contains a char we want, and substring takes end as being
- * exclusive
- */
- newEnd++;
-
- if (newStart >= sLen || newEnd < 0) {
- return "";
- }
-
- return s.substring(newStart, newEnd);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java b/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java
deleted file mode 100644
index 7c57469..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib.internal.project;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.io.FolderWrapper;
-import com.android.io.IAbstractFile;
-import com.android.io.IAbstractFolder;
-import com.android.io.StreamException;
-import com.android.utils.ILogger;
-import com.google.common.io.Closeables;
-
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Class representing project properties for both ADT and Ant-based build.
- * <p/>The class is associated to a {@link PropertyType} that indicate which of the project
- * property file is represented.
- * <p/>To load an existing file, use {@link #load(IAbstractFolder, PropertyType)}.
- * <p/>The class is meant to be always in sync (or at least not newer) than the file it represents.
- * Once created, it can only be updated through {@link #reload()}
- *
- * <p/>The make modification or make new file, use a {@link ProjectPropertiesWorkingCopy} instance,
- * either through {@link #create(IAbstractFolder, PropertyType)} or through
- * {@link #makeWorkingCopy()}.
- *
- */
-public class ProjectProperties implements IPropertySource {
- protected static final Pattern PATTERN_PROP = Pattern.compile(
- "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
-
- /** The property name for the project target */
- public static final String PROPERTY_TARGET = "target";
- /** The property name for the renderscript build target */
- public static final String PROPERTY_RS_TARGET = "renderscript.target";
- /** The property name for the renderscript support mode */
- public static final String PROPERTY_RS_SUPPORT = "renderscript.support.mode";
- /** The version of the build tools to use to compile */
- public static final String PROPERTY_BUILD_TOOLS = "sdk.buildtools";
-
- public static final String PROPERTY_LIBRARY = "android.library";
- public static final String PROPERTY_LIB_REF = "android.library.reference.";
- private static final String PROPERTY_LIB_REF_REGEX = "android.library.reference.\\d+";
-
- public static final String PROPERTY_PROGUARD_CONFIG = "proguard.config";
- public static final String PROPERTY_RULES_PATH = "layoutrules.jars";
-
- public static final String PROPERTY_SDK = "sdk.dir";
- public static final String PROPERTY_NDK = "ndk.dir";
- // LEGACY - Kept so that we can actually remove it from local.properties.
- private static final String PROPERTY_SDK_LEGACY = "sdk-location";
-
- public static final String PROPERTY_SPLIT_BY_DENSITY = "split.density";
- public static final String PROPERTY_SPLIT_BY_ABI = "split.abi";
- public static final String PROPERTY_SPLIT_BY_LOCALE = "split.locale";
-
- public static final String PROPERTY_TESTED_PROJECT = "tested.project.dir";
-
- public static final String PROPERTY_BUILD_SOURCE_DIR = "source.dir";
- public static final String PROPERTY_BUILD_OUT_DIR = "out.dir";
-
- public static final String PROPERTY_PACKAGE = "package";
- public static final String PROPERTY_VERSIONCODE = "versionCode";
- public static final String PROPERTY_PROJECTS = "projects";
- public static final String PROPERTY_KEY_STORE = "key.store";
- public static final String PROPERTY_KEY_ALIAS = "key.alias";
-
- public enum PropertyType {
- ANT(SdkConstants.FN_ANT_PROPERTIES, BUILD_HEADER, new String[] {
- PROPERTY_BUILD_SOURCE_DIR, PROPERTY_BUILD_OUT_DIR
- }, null),
- PROJECT(SdkConstants.FN_PROJECT_PROPERTIES, DEFAULT_HEADER, new String[] {
- PROPERTY_TARGET, PROPERTY_LIBRARY, PROPERTY_LIB_REF_REGEX,
- PROPERTY_KEY_STORE, PROPERTY_KEY_ALIAS, PROPERTY_PROGUARD_CONFIG,
- PROPERTY_RULES_PATH
- }, null),
- LOCAL(SdkConstants.FN_LOCAL_PROPERTIES, LOCAL_HEADER, new String[] {
- PROPERTY_SDK
- },
- new String[] { PROPERTY_SDK_LEGACY }),
- @Deprecated
- LEGACY_DEFAULT("default.properties", null, null, null),
- @Deprecated
- LEGACY_BUILD("build.properties", null, null, null);
-
-
- private final String mFilename;
- private final String mHeader;
- private final Set<String> mKnownProps;
- private final Set<String> mRemovedProps;
-
- /**
- * Returns the PropertyTypes ordered the same way Ant order them.
- */
- public static PropertyType[] getOrderedTypes() {
- return new PropertyType[] {
- PropertyType.LOCAL, PropertyType.ANT, PropertyType.PROJECT
- };
- }
-
- PropertyType(String filename, String header, String[] validProps, String[] removedProps) {
- mFilename = filename;
- mHeader = header;
- HashSet<String> s = new HashSet<String>();
- if (validProps != null) {
- s.addAll(Arrays.asList(validProps));
- }
- mKnownProps = Collections.unmodifiableSet(s);
-
- s = new HashSet<String>();
- if (removedProps != null) {
- s.addAll(Arrays.asList(removedProps));
- }
- mRemovedProps = Collections.unmodifiableSet(s);
-
- }
-
- public String getFilename() {
- return mFilename;
- }
-
- public String getHeader() {
- return mHeader;
- }
-
- /**
- * Returns whether a given property is known for the property type.
- */
- public boolean isKnownProperty(String name) {
- for (String propRegex : mKnownProps) {
- if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns whether a given property should be removed for the property type.
- */
- public boolean isRemovedProperty(String name) {
- for (String propRegex : mRemovedProps) {
- if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
- return true;
- }
- }
-
- return false;
- }
- }
-
- private static final String LOCAL_HEADER =
-// 1-------10--------20--------30--------40--------50--------60--------70--------80
- "# This file is automatically generated by Android Tools.\n" +
- "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
- "#\n" +
- "# This file must *NOT* be checked into Version Control Systems,\n" +
- "# as it contains information specific to your local configuration.\n" +
- "\n";
-
- private static final String DEFAULT_HEADER =
-// 1-------10--------20--------30--------40--------50--------60--------70--------80
- "# This file is automatically generated by Android Tools.\n" +
- "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
- "#\n" +
- "# This file must be checked in Version Control Systems.\n" +
- "#\n" +
- "# To customize properties used by the Ant build system edit\n" +
- "# \"ant.properties\", and override values to adapt the script to your\n" +
- "# project structure.\n" +
- "#\n" +
- "# To enable ProGuard to shrink and obfuscate your code, uncomment this "
- + "(available properties: sdk.dir, user.home):\n" +
- // Note: always use / separators in the properties paths. Both Ant and
- // our ExportHelper will convert them properly according to the platform.
- "#" + PROPERTY_PROGUARD_CONFIG + "=${" + PROPERTY_SDK +"}/"
- + SdkConstants.FD_TOOLS + '/' + SdkConstants.FD_PROGUARD + '/'
- + SdkConstants.FN_ANDROID_PROGUARD_FILE + ':'
- + SdkConstants.FN_PROJECT_PROGUARD_FILE +'\n' +
- "\n";
-
- private static final String BUILD_HEADER =
-// 1-------10--------20--------30--------40--------50--------60--------70--------80
- "# This file is used to override default values used by the Ant build system.\n" +
- "#\n" +
- "# This file must be checked into Version Control Systems, as it is\n" +
- "# integral to the build system of your project.\n" +
- "\n" +
- "# This file is only used by the Ant script.\n" +
- "\n" +
- "# You can use this to override default values such as\n" +
- "# 'source.dir' for the location of your java source folder and\n" +
- "# 'out.dir' for the location of your output folder.\n" +
- "\n" +
- "# You can also use it define how the release builds are signed by declaring\n" +
- "# the following properties:\n" +
- "# 'key.store' for the location of your keystore and\n" +
- "# 'key.alias' for the name of the key to use.\n" +
- "# The password will be asked during the build when you use the 'release' target.\n" +
- "\n";
-
- protected final IAbstractFolder mProjectFolder;
- protected final Map<String, String> mProperties;
- protected final PropertyType mType;
-
- /**
- * Loads a project properties file and return a {@link ProjectProperties} object
- * containing the properties.
- *
- * @param projectFolderOsPath the project folder.
- * @param type One the possible {@link PropertyType}s.
- */
- public static ProjectProperties load(String projectFolderOsPath, PropertyType type) {
- IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath);
- return load(wrapper, type);
- }
-
- /**
- * Loads a project properties file and return a {@link ProjectProperties} object
- * containing the properties.
- *
- * @param projectFolder the project folder.
- * @param type One the possible {@link PropertyType}s.
- */
- public static ProjectProperties load(IAbstractFolder projectFolder, PropertyType type) {
- if (projectFolder.exists()) {
- IAbstractFile propFile = projectFolder.getFile(type.mFilename);
- if (propFile.exists()) {
- Map<String, String> map = parsePropertyFile(propFile, null /* log */);
- if (map != null) {
- return new ProjectProperties(projectFolder, map, type);
- }
- }
- }
- return null;
- }
-
- /**
- * Deletes a project properties file.
- *
- * @param projectFolder the project folder.
- * @param type One the possible {@link PropertyType}s.
- * @return true if success.
- */
- public static boolean delete(IAbstractFolder projectFolder, PropertyType type) {
- if (projectFolder.exists()) {
- IAbstractFile propFile = projectFolder.getFile(type.mFilename);
- if (propFile.exists()) {
- return propFile.delete();
- }
- }
-
- return false;
- }
-
- /**
- * Deletes a project properties file.
- *
- * @param projectFolderOsPath the project folder.
- * @param type One the possible {@link PropertyType}s.
- * @return true if success.
- */
- public static boolean delete(String projectFolderOsPath, PropertyType type) {
- IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath);
- return delete(wrapper, type);
- }
-
-
- /**
- * Creates a new project properties object, with no properties.
- * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
- * @param projectFolderOsPath the project folder.
- * @param type the type of property file to create
- *
- * @see #createEmpty(String, PropertyType)
- */
- public static ProjectPropertiesWorkingCopy create(@NonNull String projectFolderOsPath,
- @NonNull PropertyType type) {
- // create and return a ProjectProperties with an empty map.
- IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
- return create(folder, type);
- }
-
- /**
- * Creates a new project properties object, with no properties.
- * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
- * @param projectFolder the project folder.
- * @param type the type of property file to create
- *
- * @see #createEmpty(IAbstractFolder, PropertyType)
- */
- public static ProjectPropertiesWorkingCopy create(@NonNull IAbstractFolder projectFolder,
- @NonNull PropertyType type) {
- // create and return a ProjectProperties with an empty map.
- return new ProjectPropertiesWorkingCopy(projectFolder, new HashMap<String, String>(), type);
- }
-
- /**
- * Creates a new project properties object, with no properties.
- * <p/>Nothing can be added to it, unless a {@link ProjectPropertiesWorkingCopy} is created
- * first with {@link #makeWorkingCopy()}.
- * @param projectFolderOsPath the project folder.
- * @param type the type of property file to create
- *
- * @see #create(String, PropertyType)
- */
- public static ProjectProperties createEmpty(@NonNull String projectFolderOsPath,
- @NonNull PropertyType type) {
- // create and return a ProjectProperties with an empty map.
- IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
- return createEmpty(folder, type);
- }
-
- /**
- * Creates a new project properties object, with no properties.
- * <p/>Nothing can be added to it, unless a {@link ProjectPropertiesWorkingCopy} is created
- * first with {@link #makeWorkingCopy()}.
- * @param projectFolder the project folder.
- * @param type the type of property file to create
- *
- * @see #create(IAbstractFolder, PropertyType)
- */
- public static ProjectProperties createEmpty(@NonNull IAbstractFolder projectFolder,
- @NonNull PropertyType type) {
- // create and return a ProjectProperties with an empty map.
- return new ProjectProperties(projectFolder, new HashMap<String, String>(), type);
- }
-
- /**
- * Returns the location of this property file.
- */
- public IAbstractFile getFile() {
- return mProjectFolder.getFile(mType.mFilename);
- }
-
- /**
- * Creates and returns a copy of the current properties as a
- * {@link ProjectPropertiesWorkingCopy} that can be modified and saved.
- * @return a new instance of {@link ProjectPropertiesWorkingCopy}
- */
- public ProjectPropertiesWorkingCopy makeWorkingCopy() {
- return makeWorkingCopy(mType);
- }
-
- /**
- * Creates and returns a copy of the current properties as a
- * {@link ProjectPropertiesWorkingCopy} that can be modified and saved. This also allows
- * converting to a new type, by specifying a different {@link PropertyType}.
- *
- * @param type the {@link PropertyType} of the prop file to save.
- *
- * @return a new instance of {@link ProjectPropertiesWorkingCopy}
- */
- public ProjectPropertiesWorkingCopy makeWorkingCopy(PropertyType type) {
- // copy the current properties in a new map
- Map<String, String> propList = new HashMap<String, String>(mProperties);
-
- return new ProjectPropertiesWorkingCopy(mProjectFolder, propList, type);
- }
-
- /**
- * Returns the type of the property file.
- *
- * @see PropertyType
- */
- public PropertyType getType() {
- return mType;
- }
-
- /**
- * Returns the value of a property.
- * @param name the name of the property.
- * @return the property value or null if the property is not set.
- */
- @Override
- public synchronized String getProperty(String name) {
- return mProperties.get(name);
- }
-
- /**
- * Returns a set of the property keys. Unlike {@link Map#keySet()} this is not a view of the
- * map keys. Modifying the returned {@link Set} will not impact the underlying {@link Map}.
- */
- public synchronized Set<String> keySet() {
- return new HashSet<String>(mProperties.keySet());
- }
-
- /**
- * Reloads the properties from the underlying file.
- */
- public synchronized void reload() {
- if (mProjectFolder.exists()) {
- IAbstractFile propFile = mProjectFolder.getFile(mType.mFilename);
- if (propFile.exists()) {
- Map<String, String> map = parsePropertyFile(propFile, null /* log */);
- if (map != null) {
- mProperties.clear();
- mProperties.putAll(map);
- }
- }
- }
- }
-
- /**
- * Parses a property file (using UTF-8 encoding) and returns a map of the content.
- * <p/>
- * If the file is not present, null is returned with no error messages sent to the log.
- * <p/>
- * IMPORTANT: This method is now unfortunately used in multiple places to parse random
- * property files. This is NOT a safe practice since there is no corresponding method
- * to write property files unless you use {@link ProjectPropertiesWorkingCopy#save()}.
- * Code that writes INI or properties without at least using {@link #escape(String)} will
- * certainly not load back correct data. <br/>
- * Unless there's a strong legacy need to support existing files, new callers should
- * probably just use Java's {@link Properties} which has well defined semantics.
- * It's also a mistake to write/read property files using this code and expect it to
- * work with Java's {@link Properties} or external tools (e.g. ant) since there can be
- * differences in escaping and in character encoding.
- *
- * @param propFile the property file to parse
- * @param log the ILogger object receiving warning/error from the parsing.
- * @return the map of (key,value) pairs, or null if the parsing failed.
- */
- public static Map<String, String> parsePropertyFile(
- @NonNull IAbstractFile propFile,
- @Nullable ILogger log) {
- InputStream is = null;
- try {
- is = propFile.getContents();
- return parsePropertyStream(is,
- propFile.getOsLocation(),
- log);
- } catch (StreamException e) {
- if (log != null) {
- log.warning("Error parsing '%1$s': %2$s.",
- propFile.getOsLocation(),
- e.getMessage());
- }
- } finally {
- try {
- Closeables.close(is, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- }
-
-
- return null;
- }
-
- /**
- * Parses a property file (using UTF-8 encoding) and returns a map of the content.
- * <p/>
- * Always closes the given input stream on exit.
- * <p/>
- * IMPORTANT: This method is now unfortunately used in multiple places to parse random
- * property files. This is NOT a safe practice since there is no corresponding method
- * to write property files unless you use {@link ProjectPropertiesWorkingCopy#save()}.
- * Code that writes INI or properties without at least using {@link #escape(String)} will
- * certainly not load back correct data. <br/>
- * Unless there's a strong legacy need to support existing files, new callers should
- * probably just use Java's {@link Properties} which has well defined semantics.
- * It's also a mistake to write/read property files using this code and expect it to
- * work with Java's {@link Properties} or external tools (e.g. ant) since there can be
- * differences in escaping and in character encoding.
- *
- * @param propStream the input stream of the property file to parse.
- * @param propPath the file path, for display purposed in case of error.
- * @param log the ILogger object receiving warning/error from the parsing.
- * @return the map of (key,value) pairs, or null if the parsing failed.
- */
- public static Map<String, String> parsePropertyStream(
- @NonNull InputStream propStream,
- @NonNull String propPath,
- @Nullable ILogger log) {
- BufferedReader reader = null;
- try {
- //noinspection IOResourceOpenedButNotSafelyClosed
- reader = new BufferedReader(
- new InputStreamReader(propStream, SdkConstants.INI_CHARSET));
-
- String line = null;
- Map<String, String> map = new HashMap<String, String>();
- while ((line = reader.readLine()) != null) {
- line = line.trim();
- if (!line.isEmpty() && line.charAt(0) != '#') {
-
- Matcher m = PATTERN_PROP.matcher(line);
- if (m.matches()) {
- map.put(m.group(1), unescape(m.group(2)));
- } else {
- if (log != null) {
- log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
- propPath,
- line);
- }
- return null;
- }
- }
- }
-
- return map;
- } catch (FileNotFoundException e) {
- // this should not happen since we usually test the file existence before
- // calling the method.
- // Return null below.
- } catch (IOException e) {
- if (log != null) {
- log.warning("Error parsing '%1$s': %2$s.",
- propPath,
- e.getMessage());
- }
- } finally {
- try {
- Closeables.close(reader, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- try {
- Closeables.close(propStream, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- }
-
- return null;
- }
-
- /**
- * Private constructor.
- * <p/>
- * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
- * to instantiate.
- */
- protected ProjectProperties(
- @NonNull IAbstractFolder projectFolder,
- @NonNull Map<String, String> map,
- @NonNull PropertyType type) {
- mProjectFolder = projectFolder;
- mProperties = map;
- mType = type;
- }
-
- private static String unescape(String value) {
- return value.replaceAll("\\\\\\\\", "\\\\");
- }
-
- protected static String escape(String value) {
- return value.replaceAll("\\\\", "\\\\\\\\");
- }
-
- @Override
- public void debugPrint() {
- System.out.println("DEBUG PROJECTPROPERTIES: " + mProjectFolder);
- System.out.println("type: " + mType);
- for (Entry<String, String> entry : mProperties.entrySet()) {
- System.out.println(entry.getKey() + " -> " + entry.getValue());
- }
- System.out.println("<<< DEBUG PROJECTPROPERTIES");
-
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java b/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
deleted file mode 100644
index 4fac8f9..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.project;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.io.IAbstractFile;
-import com.android.io.IAbstractFolder;
-import com.android.io.StreamException;
-import com.google.common.io.Closeables;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.regex.Matcher;
-
-/**
- * A modifiable and savable copy of a {@link ProjectProperties}.
- * <p/>This copy gives access to modification method such as {@link #setProperty(String, String)}
- * and {@link #removeProperty(String)}.
- *
- * To get access to an instance, use {@link ProjectProperties#makeWorkingCopy()} or
- * {@link ProjectProperties#create(IAbstractFolder, PropertyType)}.
- */
-public class ProjectPropertiesWorkingCopy extends ProjectProperties {
-
- private static final Map<String, String> COMMENT_MAP = new HashMap<String, String>();
- static {
-// 1-------10--------20--------30--------40--------50--------60--------70--------80
- COMMENT_MAP.put(PROPERTY_TARGET,
- "# Project target.\n");
- COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY,
- "# Indicates whether an apk should be generated for each density.\n");
- COMMENT_MAP.put(PROPERTY_SDK,
- "# location of the SDK. This is only used by Ant\n" +
- "# For customization when using a Version Control System, please read the\n" +
- "# header note.\n");
- COMMENT_MAP.put(PROPERTY_PACKAGE,
- "# Package of the application being exported\n");
- COMMENT_MAP.put(PROPERTY_VERSIONCODE,
- "# Major version code\n");
- COMMENT_MAP.put(PROPERTY_PROJECTS,
- "# List of the Android projects being used for the export.\n" +
- "# The list is made of paths that are relative to this project,\n" +
- "# using forward-slash (/) as separator, and are separated by colons (:).\n");
- }
-
-
- /**
- * Sets a new properties. If a property with the same name already exists, it is replaced.
- * @param name the name of the property.
- * @param value the value of the property.
- */
- public synchronized void setProperty(String name, String value) {
- mProperties.put(name, value);
- }
-
- /**
- * Removes a property and returns its previous value (or null if the property did not exist).
- * @param name the name of the property to remove.
- */
- public synchronized String removeProperty(String name) {
- return mProperties.remove(name);
- }
-
- /**
- * Merges all properties from the given file into the current properties.
- * <p/>
- * This emulates the Ant behavior: existing properties are <em>not</em> overridden.
- * Only new undefined properties become defined.
- * <p/>
- * Typical usage:
- * <ul>
- * <li>Create a ProjectProperties with {@code PropertyType#ANT}
- * <li>Merge in values using {@code PropertyType#PROJECT}
- * <li>The result is that this contains all the properties from default plus those
- * overridden by the build.properties file.
- * </ul>
- *
- * @param type One the possible {@link ProjectProperties.PropertyType}s.
- * @return this object, for chaining.
- */
- public synchronized ProjectPropertiesWorkingCopy merge(PropertyType type) {
- if (mProjectFolder.exists() && mType != type) {
- IAbstractFile propFile = mProjectFolder.getFile(type.getFilename());
- if (propFile.exists()) {
- Map<String, String> map = parsePropertyFile(propFile, null /* log */);
- if (map != null) {
- for (Entry<String, String> entry : map.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue();
- if (!mProperties.containsKey(key) && value != null) {
- mProperties.put(key, value);
- }
- }
- }
- }
- }
- return this;
- }
-
-
- /**
- * Saves the property file, using UTF-8 encoding.
- * @throws IOException
- * @throws StreamException
- */
- public synchronized void save() throws IOException, StreamException {
- IAbstractFile toSave = mProjectFolder.getFile(mType.getFilename());
-
- // write the whole file in a byte array before dumping it in the file. This
- // This is so that if the file already existing
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- OutputStreamWriter writer = new OutputStreamWriter(baos, SdkConstants.INI_CHARSET);
-
- if (toSave.exists()) {
- InputStream contentStream = toSave.getContents();
- InputStreamReader isr = null;
- BufferedReader reader = null;
-
- try {
- contentStream = toSave.getContents();
- //noinspection IOResourceOpenedButNotSafelyClosed
- isr = new InputStreamReader(contentStream, SdkConstants.INI_CHARSET);
- //noinspection IOResourceOpenedButNotSafelyClosed
- reader = new BufferedReader(isr);
-
- // since we're reading the existing file and replacing values with new ones, or skipping
- // removed values, we need to record what properties have been visited, so that
- // we can figure later what new properties need to be added at the end of the file.
- Set<String> visitedProps = new HashSet<String>();
-
- String line = null;
- while ((line = reader.readLine()) != null) {
- // check if this is a line containing a property.
- if (!line.isEmpty() && line.charAt(0) != '#') {
-
- Matcher m = PATTERN_PROP.matcher(line);
- if (m.matches()) {
- String key = m.group(1);
- String value = m.group(2);
-
- // record the prop
- visitedProps.add(key);
-
- // check if this property must be removed.
- if (mType.isRemovedProperty(key)) {
- value = null;
- } else if (mProperties.containsKey(key)) { // if the property still exists.
- // put the new value.
- value = mProperties.get(key);
- } else {
- // property doesn't exist. Check if it's a known property.
- // if it's a known one, we'll remove it, otherwise, leave it untouched.
- if (mType.isKnownProperty(key)) {
- value = null;
- }
- }
-
- // if the value is still valid, write it down.
- if (value != null) {
- writeValue(writer, key, value, false /*addComment*/);
- }
- } else {
- // the line was wrong, let's just ignore it so that it's removed from the
- // file.
- }
- } else {
- // non-property line: just write the line in the output as-is.
- writer.append(line).append('\n');
- }
- }
-
- // now add the new properties.
- for (Entry<String, String> entry : mProperties.entrySet()) {
- if (!visitedProps.contains(entry.getKey())) {
- String value = entry.getValue();
- if (value != null) {
- writeValue(writer, entry.getKey(), value, true /*addComment*/);
- }
- }
- }
- } finally {
- try {
- Closeables.close(reader, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- try {
- Closeables.close(isr, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- try {
- Closeables.close(contentStream, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
- }
-
- } else {
- // new file, just write it all
-
- // write the header (can be null, for example for PropertyType.LEGACY_BUILD)
- if (mType.getHeader() != null) {
- writer.write(mType.getHeader());
- }
-
- // write the properties.
- for (Entry<String, String> entry : mProperties.entrySet()) {
- String value = entry.getValue();
- if (value != null) {
- writeValue(writer, entry.getKey(), value, true /*addComment*/);
- }
- }
- }
-
- writer.flush();
-
- // now put the content in the file.
- OutputStream filestream = toSave.getOutputStream();
- filestream.write(baos.toByteArray());
- filestream.flush();
- filestream.close();
- }
-
- private void writeValue(OutputStreamWriter writer, String key, String value,
- boolean addComment) throws IOException {
- if (addComment) {
- String comment = COMMENT_MAP.get(key);
- if (comment != null) {
- writer.write(comment);
- }
- }
-
- writer.write(String.format("%s=%s\n", key, escape(value)));
- }
-
- /**
- * Private constructor.
- * <p/>
- * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
- * to instantiate.
- */
- ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map,
- PropertyType type) {
- super(projectFolder, map, type);
- }
-
- @NonNull
- public ProjectProperties makeReadOnlyCopy() {
- // copy the current properties in a new map
- Map<String, String> propList = new HashMap<String, String>(mProperties);
-
- return new ProjectProperties(mProjectFolder, propList, mType);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/AdbWrapper.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/AdbWrapper.java
deleted file mode 100755
index ae256eb..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/AdbWrapper.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository;
-
-import com.android.SdkConstants;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * A lightweight wrapper to start & stop ADB.
- * This is <b>specific</b> to the SDK Manager install process.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class AdbWrapper {
-
- /*
- * Note: we could bring ddmlib in SdkManager for that purpose, however this allows us to
- * specialize the start/stop methods to our needs (e.g. a task monitor, etc.)
- */
-
- private final String mAdbOsLocation;
- private final ITaskMonitor mMonitor;
-
- /**
- * Creates a new lightweight ADB wrapper.
- *
- * @param osSdkPath The root OS path of the SDK. Cannot be null.
- * @param monitor A logger object. Cannot be null.
- */
- public AdbWrapper(String osSdkPath, ITaskMonitor monitor) {
- mMonitor = monitor;
-
- if (!osSdkPath.endsWith(File.separator)) {
- osSdkPath += File.separator;
- }
- mAdbOsLocation = osSdkPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER
- + SdkConstants.FN_ADB;
- }
-
- private void display(String format, Object...args) {
- mMonitor.log(format, args);
- }
-
- private void displayError(String format, Object...args) {
- mMonitor.logError(format, args);
- }
-
- /**
- * Starts the adb host side server.
- * @return true if success
- */
- public synchronized boolean startAdb() {
- if (mAdbOsLocation == null) {
- displayError("Error: missing path to ADB."); //$NON-NLS-1$
- return false;
- }
-
- Process proc;
- int status = -1;
-
- try {
- ProcessBuilder processBuilder = new ProcessBuilder(
- mAdbOsLocation,
- "start-server"); //$NON-NLS-1$
- proc = processBuilder.start();
- status = proc.waitFor();
-
- // Implementation note: normally on Windows we need to capture stderr/stdout
- // to make sure the process isn't blocked if it's output isn't read. However
- // in this case this happens to hang when reading stdout with no proper way
- // to properly close the streams. On the other hand the output from start
- // server is rather short and not very interesting so we just drop it.
-
- } catch (IOException ioe) {
- displayError("Unable to run 'adb': %1$s.", ioe.getMessage()); //$NON-NLS-1$
- // we'll return false;
- } catch (InterruptedException ie) {
- displayError("Unable to run 'adb': %1$s.", ie.getMessage()); //$NON-NLS-1$
- // we'll return false;
- }
-
- if (status != 0) {
- displayError(String.format(
- "Starting ADB server failed (code %d).", //$NON-NLS-1$
- status));
- return false;
- }
-
- display("Starting ADB server succeeded."); //$NON-NLS-1$
-
- return true;
- }
-
- /**
- * Stops the adb host side server.
- * @return true if success
- */
- public synchronized boolean stopAdb() {
- if (mAdbOsLocation == null) {
- displayError("Error: missing path to ADB."); //$NON-NLS-1$
- return false;
- }
-
- Process proc;
- int status = -1;
-
- try {
- String[] command = new String[2];
- command[0] = mAdbOsLocation;
- command[1] = "kill-server"; //$NON-NLS-1$
- proc = Runtime.getRuntime().exec(command);
- status = proc.waitFor();
-
- // See comment in startAdb about not needing/wanting to capture stderr/stdout.
- }
- catch (IOException ioe) {
- // we'll return false;
- }
- catch (InterruptedException ie) {
- // we'll return false;
- }
-
- // adb kill-server returns:
- // 0 if adb was running and was correctly killed.
- // 1 if adb wasn't running and thus wasn't killed.
- // This error case is not worth reporting.
-
- if (status != 0 && status != 1) {
- displayError(String.format(
- "Stopping ADB server failed (code %d).", //$NON-NLS-1$
- status));
- return false;
- }
-
- display("Stopping ADB server succeeded."); //$NON-NLS-1$
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/AddonsListFetcher.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/AddonsListFetcher.java
deleted file mode 100755
index 816c7f0..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/AddonsListFetcher.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.io.NonClosingInputStream;
-import com.android.io.NonClosingInputStream.CloseBehavior;
-import com.android.sdklib.repository.SdkAddonsListConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.net.ssl.SSLKeyException;
-import javax.xml.XMLConstants;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-/**
- * Fetches and loads an sdk-addons-list XML.
- * <p/>
- * Such an XML contains a simple list of add-ons site that are to be loaded by default by the
- * SDK Manager. <br/>
- * The XML must conform to the sdk-addons-list-N.xsd. <br/>
- * Constants used in the XML are defined in {@link SdkAddonsListConstants}.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class AddonsListFetcher {
-
- public enum SiteType {
- ADDON_SITE,
- SYS_IMG_SITE
- }
-
- /**
- * An immutable structure representing an add-on site.
- */
- public static class Site {
- private final String mUrl;
- private final String mUiName;
- private final SiteType mType;
-
- private Site(String url, String uiName, SiteType type) {
- mType = type;
- mUrl = url.trim();
- mUiName = uiName;
- }
-
- public String getUrl() {
- return mUrl;
- }
-
- public String getUiName() {
- return mUiName;
- }
-
- public SiteType getType() {
- return mType;
- }
-
- /** Returns a debug string representation of this object. Not for user display. */
- @Override
- public String toString() {
- return String.format("<%1$s URL='%2$s' Name='%3$s'>", //$NON-NLS-1$
- mType, mUrl, mUiName);
- }
- }
-
- /**
- * Fetches the addons list from the given URL.
- *
- * @param url The URL of an XML file resource that conforms to the latest sdk-addons-list-N.xsd.
- * For the default operation, use {@link SdkAddonsListConstants#URL_ADDON_LIST}.
- * Cannot be null.
- * @param cache The {@link DownloadCache} instance to use. Cannot be null.
- * @param monitor A monitor to report errors. Cannot be null.
- * @return An array of {@link Site} on success (possibly empty), or null on error.
- */
- public Site[] fetch(String url, DownloadCache cache, ITaskMonitor monitor) {
-
- url = url == null ? "" : url.trim();
-
- monitor.setProgressMax(6);
- monitor.setDescription("Fetching %1$s", url);
- monitor.incProgress(1);
-
- Exception[] exception = new Exception[] { null };
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- Document validatedDoc = null;
- String validatedUri = null;
-
- String[] defaultNames = new String[SdkAddonsListConstants.NS_LATEST_VERSION];
- for (int version = SdkAddonsListConstants.NS_LATEST_VERSION, i = 0;
- version >= 1;
- version--, i++) {
- defaultNames[i] = SdkAddonsListConstants.getDefaultName(version);
- }
-
- InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
- if (xml != null) {
- int version = getXmlSchemaVersion(xml);
- if (version == 0) {
- closeStream(xml);
- xml = null;
- }
- }
-
- String baseUrl = url;
- if (!baseUrl.endsWith("/")) { //$NON-NLS-1$
- int pos = baseUrl.lastIndexOf('/');
- if (pos > 0) {
- baseUrl = baseUrl.substring(0, pos + 1);
- }
- }
-
- // If we can't find the latest version, try earlier schema versions.
- if (xml == null && defaultNames.length > 0) {
- ITaskMonitor subMonitor = monitor.createSubMonitor(1);
- subMonitor.setProgressMax(defaultNames.length);
-
- for (String name : defaultNames) {
- String newUrl = baseUrl + name;
- if (newUrl.equals(url)) {
- continue;
- }
- xml = fetchXmlUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
- if (xml != null) {
- int version = getXmlSchemaVersion(xml);
- if (version == 0) {
- closeStream(xml);
- xml = null;
- } else {
- url = newUrl;
- subMonitor.incProgress(
- subMonitor.getProgressMax() - subMonitor.getProgress());
- break;
- }
- }
- }
- } else {
- monitor.incProgress(1);
- }
-
- if (xml != null) {
- monitor.setDescription("Validate XML");
-
- // Explore the XML to find the potential XML schema version
- int version = getXmlSchemaVersion(xml);
-
- if (version >= 1 && version <= SdkAddonsListConstants.NS_LATEST_VERSION) {
- // This should be a version we can handle. Try to validate it
- // and report any error as invalid XML syntax,
-
- String uri = validateXml(xml, url, version, validationError, validatorFound);
- if (uri != null) {
- // Validation was successful
- validatedDoc = getDocument(xml, monitor);
- validatedUri = uri;
-
- }
- } else if (version > SdkAddonsListConstants.NS_LATEST_VERSION) {
- // The schema used is more recent than what is supported by this tool.
- // We don't have an upgrade-path support yet, so simply ignore the document.
- closeStream(xml);
- return null;
- }
- }
-
- // If any exception was handled during the URL fetch, display it now.
- if (exception[0] != null) {
- String reason = null;
- if (exception[0] instanceof FileNotFoundException) {
- // FNF has no useful getMessage, so we need to special handle it.
- reason = "File not found";
- } else if (exception[0] instanceof UnknownHostException &&
- exception[0].getMessage() != null) {
- // This has no useful getMessage yet could really use one
- reason = String.format("Unknown Host %1$s", exception[0].getMessage());
- } else if (exception[0] instanceof SSLKeyException) {
- // That's a common error and we have a pref for it.
- reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
- } else if (exception[0].getMessage() != null) {
- reason = exception[0].getMessage();
- } else {
- // We don't know what's wrong. Let's give the exception class at least.
- reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
- }
-
- monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
- }
-
- if (validationError[0] != null) {
- monitor.logError("%s", validationError[0]); //$NON-NLS-1$
- }
-
- // Stop here if we failed to validate the XML. We don't want to load it.
- if (validatedDoc == null) {
- closeStream(xml);
- return null;
- }
-
- monitor.incProgress(1);
-
- Site[] result = null;
-
- if (xml != null) {
- monitor.setDescription("Parse XML");
- monitor.incProgress(1);
- result = parseAddonsList(validatedDoc, validatedUri, baseUrl, monitor);
- }
-
- // done
- monitor.incProgress(1);
-
- closeStream(xml);
- return result;
- }
-
- /**
- * Fetches the document at the given URL and returns it as a stream. Returns
- * null if anything wrong happens.
- *
- * @param urlString The URL to load, as a string.
- * @param monitor {@link ITaskMonitor} related to this URL.
- * @param outException If non null, where to store any exception that
- * happens during the fetch.
- * @see UrlOpener UrlOpener, which handles all URL logic.
- */
- private InputStream fetchXmlUrl(String urlString,
- DownloadCache cache,
- ITaskMonitor monitor,
- Exception[] outException) {
- try {
- InputStream xml = cache.openCachedUrl(urlString, monitor);
- if (xml != null) {
- xml.mark(500000);
- xml = new NonClosingInputStream(xml);
- ((NonClosingInputStream) xml).setCloseBehavior(CloseBehavior.RESET);
- }
- return xml;
- } catch (Exception e) {
- if (outException != null) {
- outException[0] = e;
- }
- }
-
- return null;
- }
-
- /**
- * Closes the stream, ignore any exception from InputStream.close().
- * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
- */
- private void closeStream(InputStream is) {
- if (is != null) {
- if (is instanceof NonClosingInputStream) {
- ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
- }
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- }
-
- /**
- * Manually parses the root element of the XML to extract the schema version
- * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
- * declaration.
- *
- * @return 1..{@link SdkAddonsListConstants#NS_LATEST_VERSION} for a valid schema version
- * or 0 if no schema could be found.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected int getXmlSchemaVersion(InputStream xml) {
- if (xml == null) {
- return 0;
- }
-
- // Get an XML document
- Document doc = null;
- try {
- assert xml.markSupported();
- xml.reset();
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // We don't want the default handler which prints errors to stderr.
- builder.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- doc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
-
- } catch (Exception e) {
- // Failed to reset XML stream
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- //--For debug--System.err.println("getXmlSchemaVersion exception: " + e.toString());
- }
-
- if (doc == null) {
- return 0;
- }
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-addons-list
- // xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(SdkAddonsListConstants.NS_PATTERN);
-
- String prefix = null;
- for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (SdkAddonsListConstants.NODE_SDK_ADDONS_LIST.equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null) {
- Matcher m = nsPattern.matcher(uri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- return Integer.parseInt(version);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
- }
- }
- }
- }
-
- return 0;
- }
-
- /**
- * Validates this XML against one of the requested SDK Repository schemas.
- * If the XML was correctly validated, returns the schema that worked.
- * If it doesn't validate, returns null and stores the error in outError[0].
- * If we can't find a validator, returns null and set validatorFound[0] to false.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected String validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
-
- if (xml == null) {
- return null;
- }
-
- try {
- Validator validator = getValidator(version);
-
- if (validator == null) {
- validatorFound[0] = Boolean.FALSE;
- outError[0] = String.format(
- "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
- url);
- return null;
- }
-
- validatorFound[0] = Boolean.TRUE;
-
- // Reset the stream if it supports that operation.
- assert xml.markSupported();
- xml.reset();
-
- // Validation throws a bunch of possible Exceptions on failure.
- validator.validate(new StreamSource(xml));
- return SdkAddonsListConstants.getSchemaUri(version);
-
- } catch (SAXParseException e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
- url,
- e.getLineNumber(),
- e.getColumnNumber(),
- e.toString());
-
- } catch (Exception e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nError: %2$s",
- url,
- e.toString());
- }
- return null;
- }
-
- /**
- * Helper method that returns a validator for our XSD, or null if the current Java
- * implementation can't process XSD schemas.
- *
- * @param version The version of the XML Schema.
- * See {@link SdkAddonsListConstants#getXsdStream(int)}
- */
- private Validator getValidator(int version) throws SAXException {
- InputStream xsdStream = SdkAddonsListConstants.getXsdStream(version);
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-
- if (factory == null) {
- return null;
- }
-
- // This may throw a SAX Exception if the schema itself is not a valid XSD
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
-
- Validator validator = schema == null ? null : schema.newValidator();
-
- return validator;
- }
-
- /**
- * Takes an XML document as a string as parameter and returns a DOM for it.
- *
- * On error, returns null and prints a (hopefully) useful message on the monitor.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(true);
- factory.setNamespaceAware(true);
-
- DocumentBuilder builder = factory.newDocumentBuilder();
- assert xml.markSupported();
- xml.reset();
- Document doc = builder.parse(new InputSource(xml));
-
- return doc;
- } catch (ParserConfigurationException e) {
- monitor.logError("Failed to create XML document builder");
-
- } catch (SAXException e) {
- monitor.logError("Failed to parse XML document");
-
- } catch (IOException e) {
- monitor.logError("Failed to read XML document");
- }
-
- return null;
- }
-
- /**
- * Parse all sites defined in the Addons list XML and returns an array of sites.
- *
- * @param doc The XML DOM to parse.
- * @param nsUri The addons-list schema URI of the document.
- * @param baseUrl The base URL of the caller (e.g. where addons-list-N.xml was fetched from.)
- * @param monitor A non-null monitor to print to.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Site[] parseAddonsList(
- Document doc,
- String nsUri,
- String baseUrl,
- ITaskMonitor monitor) {
-
- String testBaseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
- if (testBaseUrl != null) {
- if (testBaseUrl.length() <= 0 || !testBaseUrl.endsWith("/")) { //$NON-NLS-1$
- testBaseUrl = null;
- }
- }
-
- Node root = getFirstChild(doc, nsUri, SdkAddonsListConstants.NODE_SDK_ADDONS_LIST);
- if (root != null) {
- ArrayList<Site> sites = new ArrayList<Site>();
-
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
-
- String elementName = child.getLocalName();
- SiteType type = null;
-
- if (SdkAddonsListConstants.NODE_SYS_IMG_SITE.equals(elementName)) {
- type = SiteType.SYS_IMG_SITE;
-
- } else if (SdkAddonsListConstants.NODE_ADDON_SITE.equals(elementName)) {
- type = SiteType.ADDON_SITE;
- }
-
- // Not an addon-site nor a sys-img-site, don't process this.
- if (type == null) {
- continue;
- }
-
- Node url = getFirstChild(child, nsUri, SdkAddonsListConstants.NODE_URL);
- Node name = getFirstChild(child, nsUri, SdkAddonsListConstants.NODE_NAME);
-
- if (name != null && url != null) {
- String strUrl = url.getTextContent().trim();
- String strName = name.getTextContent().trim();
-
- if (testBaseUrl != null &&
- strUrl.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
- strUrl = testBaseUrl +
- strUrl.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
- } else if (!strUrl.startsWith("http://") && //$NON-NLS-1$
- !strUrl.startsWith("https://")) { //$NON-NLS-1$
- // This looks like a relative URL, add the fetcher's base URL to it.
- strUrl = baseUrl + strUrl;
- }
-
- if (!strUrl.isEmpty() && !strName.isEmpty()) {
- sites.add(new Site(strUrl, strName, type));
- }
- }
- }
- }
-
- return sites.toArray(new Site[sites.size()]);
- }
-
- return null;
- }
-
- /**
- * Returns the first child element with the given XML local name.
- * If xmlLocalName is null, returns the very first child element.
- */
- private Node getFirstChild(Node node, String nsUri, String xmlLocalName) {
-
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
- return child;
- }
- }
- }
-
- return null;
- }
-
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java
deleted file mode 100755
index b7f393a..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java
+++ /dev/null
@@ -1,848 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.utils.Pair;
-
-import org.apache.http.Header;
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.message.BasicHeader;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicInteger;
-
-
-/**
- * A simple cache for the XML resources handled by the SDK Manager.
- * <p/>
- * Callers should use {@link #openDirectUrl} to download "large files"
- * that should not be cached (like actual installation packages which are several MBs big)
- * and call {@link #openCachedUrl(String, ITaskMonitor)} to download small XML files.
- * <p/>
- * The cache can work in 3 different strategies (direct is a pass-through, fresh-cache is the
- * default and tries to update resources if they are older than 10 minutes by respecting
- * either ETag or Last-Modified, and finally server-cache is a strategy to always serve
- * cached entries if present.)
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class DownloadCache {
-
- /*
- * HTTP/1.1 references:
- * - Possible headers:
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
- * - Rules about conditional requests:
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
- * - Error codes:
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
- */
-
- private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null; //$NON-NLS-1$
-
- /** Key for the Status-Code in the info properties. */
- private static final String KEY_STATUS_CODE = "Status-Code"; //$NON-NLS-1$
- /** Key for the URL in the info properties. */
- private static final String KEY_URL = "URL"; //$NON-NLS-1$
-
- /** Prefix of binary files stored in the {@link SdkConstants#FD_CACHE} directory. */
- private static final String BIN_FILE_PREFIX = "sdkbin"; //$NON-NLS-1$
- /** Prefix of meta info files stored in the {@link SdkConstants#FD_CACHE} directory. */
- private static final String INFO_FILE_PREFIX = "sdkinf"; //$NON-NLS-1$
- /* Revision suffixed to the prefix. */
- private static final String REV_FILE_PREFIX = "-1_"; //$NON-NLS-1$
-
- /**
- * Minimum time before we consider a cached entry is potentially stale.
- * Expressed in milliseconds.
- * <p/>
- * When using the {@link Strategy#FRESH_CACHE}, the cache will not try to refresh
- * a cached file if it's has been saved more recently than this time.
- * When using the direct mode or the serve mode, the cache either doesn't serve
- * cached files or always serves caches files so this expiration delay is not used.
- * <p/>
- * Default is 10 minutes.
- * <p/>
- * TODO: change for a dynamic preference later.
- */
- private static final long MIN_TIME_EXPIRED_MS = 10*60*1000;
- /**
- * Maximum time before we consider a cache entry to be stale.
- * Expressed in milliseconds.
- * <p/>
- * When using the {@link Strategy#FRESH_CACHE}, entries that have no ETag
- * or Last-Modified will be refreshed if their file timestamp is older than
- * this value.
- * <p/>
- * Default is 4 hours.
- * <p/>
- * TODO: change for a dynamic preference later.
- */
- private static final long MAX_TIME_EXPIRED_MS = 4*60*60*1000;
-
- /**
- * The maximum file size we'll cache for "small" files.
- * 640KB is more than enough and is already a stretch since these are read in memory.
- * (The actual typical size of the files handled here is in the 4-64KB range.)
- */
- private static final int MAX_SMALL_FILE_SIZE = 640 * 1024;
-
- /**
- * HTTP Headers that are saved in an info file.
- * For HTTP/1.1 header names, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
- */
- private static final String[] INFO_HTTP_HEADERS = {
- HttpHeaders.LAST_MODIFIED,
- HttpHeaders.ETAG,
- HttpHeaders.CONTENT_LENGTH,
- HttpHeaders.DATE
- };
-
- private final IFileOp mFileOp;
- private final File mCacheRoot;
- private final Strategy mStrategy;
-
- public enum Strategy {
- /**
- * Exclusively serves data from the cache. If files are available in the
- * cache, serve them as is (without trying to refresh them). If files are
- * not available, they are <em>not</em> fetched at all.
- */
- ONLY_CACHE,
- /**
- * If the files are available in the cache, serve them as-is, otherwise
- * download them and return the cached version. No expiration or refresh
- * is attempted if a file is in the cache.
- */
- SERVE_CACHE,
- /**
- * If the files are available in the cache, check if there's an update
- * (either using an e-tag check or comparing to the default time expiration).
- * If files have expired or are not in the cache then download them and return
- * the cached version.
- */
- FRESH_CACHE,
- /**
- * Disables caching. URLs are always downloaded and returned directly.
- * Downloaded streams aren't cached locally.
- */
- DIRECT
- }
-
- /** Creates a default instance of the URL cache */
- public DownloadCache(@NonNull Strategy strategy) {
- this(new FileOp(), strategy);
- }
-
- /** Creates a default instance of the URL cache */
- public DownloadCache(@NonNull IFileOp fileOp, @NonNull Strategy strategy) {
- mFileOp = fileOp;
- mCacheRoot = initCacheRoot();
-
- // If this is defined in the environment, never use the cache. Useful for testing.
- if (System.getenv("SDKMAN_DISABLE_CACHE") != null) { //$NON-NLS-1$
- strategy = Strategy.DIRECT;
- }
-
- mStrategy = mCacheRoot == null ? Strategy.DIRECT : strategy;
- }
-
- @NonNull
- public Strategy getStrategy() {
- return mStrategy;
- }
-
- @Nullable
- public File getCacheRoot() {
- return mCacheRoot;
- }
-
- /**
- * Computes the size of the cached files.
- *
- * @return The sum of the byte size of the cached files.
- */
- public long getCurrentSize() {
- long size = 0;
-
- if (mCacheRoot != null) {
- File[] files = mFileOp.listFiles(mCacheRoot);
- for (File f : files) {
- if (mFileOp.isFile(f)) {
- String name = f.getName();
- if (name.startsWith(BIN_FILE_PREFIX) || name.startsWith(INFO_FILE_PREFIX)) {
- size += f.length();
- }
- }
- }
- }
-
- return size;
- }
-
- /**
- * Removes all cached files from the cache directory.
- */
- public void clearCache() {
- if (mCacheRoot != null) {
- File[] files = mFileOp.listFiles(mCacheRoot);
- for (File f : files) {
- if (mFileOp.isFile(f)) {
- String name = f.getName();
- if (name.startsWith(BIN_FILE_PREFIX) || name.startsWith(INFO_FILE_PREFIX)) {
- mFileOp.delete(f);
- }
- }
- }
- }
- }
-
- /**
- * Removes all obsolete cached files from the cache directory
- * that do not match the latest revision.
- */
- public void clearOldCache() {
- String prefix1 = BIN_FILE_PREFIX + REV_FILE_PREFIX;
- String prefix2 = INFO_FILE_PREFIX + REV_FILE_PREFIX;
- if (mCacheRoot != null) {
- File[] files = mFileOp.listFiles(mCacheRoot);
- for (File f : files) {
- if (mFileOp.isFile(f)) {
- String name = f.getName();
- if (name.startsWith(BIN_FILE_PREFIX) ||
- name.startsWith(INFO_FILE_PREFIX)) {
- if (!name.startsWith(prefix1) && !name.startsWith(prefix2)) {
- mFileOp.delete(f);
- }
- }
- }
- }
- }
- }
-
- /**
- * Returns the directory to be used as a cache.
- * Creates it if necessary.
- * Makes it possible to disable or override the cache location in unit tests.
- *
- * @return An existing directory to use as a cache root dir,
- * or null in case of error in which case the cache will be disabled.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- @Nullable
- protected File initCacheRoot() {
- try {
- File root = new File(AndroidLocation.getFolder());
- root = new File(root, SdkConstants.FD_CACHE);
- if (!mFileOp.exists(root)) {
- mFileOp.mkdirs(root);
- }
- return root;
- } catch (AndroidLocationException e) {
- // No root? Disable the cache.
- return null;
- }
- }
-
- /**
- * Calls {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
- * to actually perform a download.
- * <p/>
- * Isolated so that it can be overridden by unit tests.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- @NonNull
- protected Pair<InputStream, HttpResponse> openUrl(
- @NonNull String url,
- boolean needsMarkResetSupport,
- @NonNull ITaskMonitor monitor,
- @Nullable Header[] headers) throws IOException, CanceledByUserException {
- return UrlOpener.openUrl(url, needsMarkResetSupport, monitor, headers);
- }
-
-
- /**
- * Does a direct download of the given URL using {@link UrlOpener}.
- * This does not check the download cache and does not attempt to cache the file.
- * Instead the HttpClient library returns a progressive download stream.
- * <p/>
- * For details on realm authentication and user/password handling,
- * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
- * documentation.
- * <p/>
- * The resulting input stream may not support mark/reset.
- *
- * @param urlString the URL string to be opened.
- * @param headers An optional set of headers to pass when requesting the resource. Can be null.
- * @param monitor {@link ITaskMonitor} which is related to this URL
- * fetching.
- * @return Returns a pair with a {@link InputStream} and an {@link HttpResponse}.
- * The pair is never null.
- * The input stream can be null in case of error, although in general the
- * method will probably throw an exception instead.
- * The caller should look at the response code's status and only accept the
- * input stream if it's the desired code (e.g. 200 or 206).
- * @throws IOException Exception thrown when there are problems retrieving
- * the URL or its content.
- * @throws CanceledByUserException Exception thrown if the user cancels the
- * authentication dialog.
- */
- @NonNull
- public Pair<InputStream, HttpResponse> openDirectUrl(
- @NonNull String urlString,
- @Nullable Header[] headers,
- @NonNull ITaskMonitor monitor)
- throws IOException, CanceledByUserException {
- if (DEBUG) {
- System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
- }
- return openUrl(
- urlString,
- false /*needsMarkResetSupport*/,
- monitor,
- headers);
- }
-
- /**
- * This is a simplified convenience method that calls
- * {@link #openDirectUrl(String, Header[], ITaskMonitor)}
- * without passing any specific HTTP headers and returns the resulting input stream
- * and the HTTP status code.
- * See the original method's description for details on its behavior.
- * <p/>
- * {@link #openDirectUrl(String, Header[], ITaskMonitor)} can accept customized
- * HTTP headers to send with the requests and also returns the full HTTP
- * response -- status line with code and protocol and all headers.
- * <p/>
- * The resulting input stream may not support mark/reset.
- *
- * @param urlString the URL string to be opened.
- * @param monitor {@link ITaskMonitor} which is related to this URL
- * fetching.
- * @return Returns a pair with a {@link InputStream} and an HTTP status code.
- * The pair is never null.
- * The input stream can be null in case of error, although in general the
- * method will probably throw an exception instead.
- * The caller should look at the response code's status and only accept the
- * input stream if it's the desired code (e.g. 200 or 206).
- * @throws IOException Exception thrown when there are problems retrieving
- * the URL or its content.
- * @throws CanceledByUserException Exception thrown if the user cancels the
- * authentication dialog.
- * @see #openDirectUrl(String, Header[], ITaskMonitor)
- */
- @NonNull
- public Pair<InputStream, Integer> openDirectUrl(
- @NonNull String urlString,
- @NonNull ITaskMonitor monitor)
- throws IOException, CanceledByUserException {
- if (DEBUG) {
- System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
- }
- Pair<InputStream, HttpResponse> result = openUrl(
- urlString,
- false /*needsMarkResetSupport*/,
- monitor,
- null /*headers*/);
- return Pair.of(result.getFirst(), result.getSecond().getStatusLine().getStatusCode());
- }
-
- /**
- * Downloads a small file, typically XML manifests.
- * The current {@link Strategy} governs whether the file is served as-is
- * from the cache, potentially updated first or directly downloaded.
- * <p/>
- * For large downloads (e.g. installable archives) please do not invoke the
- * cache and instead use the {@link #openDirectUrl} method.
- * <p/>
- * For details on realm authentication and user/password handling,
- * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
- * documentation.
- *
- * @param urlString the URL string to be opened.
- * @param monitor {@link ITaskMonitor} which is related to this URL
- * fetching.
- * @return Returns an {@link InputStream} holding the URL content.
- * Returns null if there's no content (e.g. resource not found.)
- * Returns null if the document is not cached and strategy is {@link Strategy#ONLY_CACHE}.
- * @throws IOException Exception thrown when there are problems retrieving
- * the URL or its content.
- * @throws CanceledByUserException Exception thrown if the user cancels the
- * authentication dialog.
- */
- @NonNull
- public InputStream openCachedUrl(@NonNull String urlString, @NonNull ITaskMonitor monitor)
- throws IOException, CanceledByUserException {
- // Don't cache in direct mode.
- if (mStrategy == Strategy.DIRECT) {
- Pair<InputStream, HttpResponse> result = openUrl(
- urlString,
- true /*needsMarkResetSupport*/,
- monitor,
- null /*headers*/);
- return result.getFirst();
- }
-
- File cached = new File(mCacheRoot, getCacheFilename(urlString));
- File info = new File(mCacheRoot, getInfoFilename(cached.getName()));
-
- boolean useCached = mFileOp.exists(cached);
-
- if (useCached && mStrategy == Strategy.FRESH_CACHE) {
- // Check whether the file should be served from the cache or
- // refreshed first.
-
- long cacheModifiedMs = mFileOp.lastModified(cached); /* last mod time in epoch/millis */
- boolean checkCache = true;
-
- Properties props = readInfo(info);
- if (props == null) {
- // No properties, no chocolate for you.
- useCached = false;
- } else {
- long minExpiration = System.currentTimeMillis() - MIN_TIME_EXPIRED_MS;
- checkCache = cacheModifiedMs < minExpiration;
-
- if (!checkCache && DEBUG) {
- System.out.println(String.format(
- "%s : Too fresh [%,d ms], not checking yet.", //$NON-NLS-1$
- urlString, cacheModifiedMs - minExpiration));
- }
- }
-
- if (useCached && checkCache) {
- assert props != null;
-
- // Right now we only support 200 codes and will requery all 404s.
- String code = props.getProperty(KEY_STATUS_CODE, ""); //$NON-NLS-1$
- useCached = Integer.toString(HttpStatus.SC_OK).equals(code);
-
- if (!useCached && DEBUG) {
- System.out.println(String.format(
- "%s : cache disabled by code %s", //$NON-NLS-1$
- urlString, code));
- }
-
- if (useCached) {
- // Do we have a valid Content-Length? If so, it should match the file size.
- try {
- long length = Long.parseLong(props.getProperty(HttpHeaders.CONTENT_LENGTH,
- "-1")); //$NON-NLS-1$
- if (length >= 0) {
- useCached = length == mFileOp.length(cached);
-
- if (!useCached && DEBUG) {
- System.out.println(String.format(
- "%s : cache disabled by length mismatch %d, expected %d", //$NON-NLS-1$
- urlString, length, cached.length()));
- }
- }
- } catch (NumberFormatException ignore) {}
- }
-
- if (useCached) {
- // Do we have an ETag and/or a Last-Modified?
- String etag = props.getProperty(HttpHeaders.ETAG);
- String lastMod = props.getProperty(HttpHeaders.LAST_MODIFIED);
-
- if (etag != null || lastMod != null) {
- // Details on how to use them is defined at
- // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
- // Bottom line:
- // - if there's an ETag, it should be used first with an
- // If-None-Match header. That's a strong comparison for HTTP/1.1 servers.
- // - otherwise use a Last-Modified if an If-Modified-Since header exists.
- // In this case, we place both and the rules indicates a spec-abiding
- // server should strongly match ETag and weakly the Modified-Since.
-
- // TODO there are some servers out there which report ETag/Last-Mod
- // yet don't honor them when presented with a precondition. In this
- // case we should identify it in the reply and invalidate ETag support
- // for these servers and instead fallback on the pure-timeout case below.
-
- AtomicInteger statusCode = new AtomicInteger(0);
- InputStream is = null;
- List<Header> headers = new ArrayList<Header>(2);
-
- if (etag != null) {
- headers.add(new BasicHeader(HttpHeaders.IF_NONE_MATCH, etag));
- }
-
- if (lastMod != null) {
- headers.add(new BasicHeader(HttpHeaders.IF_MODIFIED_SINCE, lastMod));
- }
-
- if (!headers.isEmpty()) {
- is = downloadAndCache(urlString, monitor, cached, info,
- headers.toArray(new Header[headers.size()]),
- statusCode);
- }
-
- if (is != null && statusCode.get() == HttpStatus.SC_OK) {
- // The resource was modified, the server said there was something
- // new, which has been cached. We can return that to the caller.
- return is;
- }
-
- // If we get here, we should have is == null and code
- // could be:
- // - 304 for not-modified -- same resource, still available, in
- // which case we'll use the cached one.
- // - 404 -- resource doesn't exist anymore in which case there's
- // no point in retrying.
- // - For any other code, just retry a download.
-
- if (is != null) {
- try {
- is.close();
- } catch (Exception ignore) {}
- is = null;
- }
-
- if (statusCode.get() == HttpStatus.SC_NOT_MODIFIED) {
- // Cached file was not modified.
- // Change its timestamp for the next MIN_TIME_EXPIRED_MS check.
- cached.setLastModified(System.currentTimeMillis());
-
- // At this point useCached==true so we'll return
- // the cached file below.
- } else {
- // URL fetch returned something other than 200 or 304.
- // For 404, we're done, no need to check the server again.
- // For all other codes, we'll retry a download below.
- useCached = false;
- if (statusCode.get() == HttpStatus.SC_NOT_FOUND) {
- return null;
- }
- }
- } else {
- // If we don't have an Etag nor Last-Modified, let's use a
- // basic file timestamp and compare to a 1 hour threshold.
-
- long maxExpiration = System.currentTimeMillis() - MAX_TIME_EXPIRED_MS;
- useCached = cacheModifiedMs >= maxExpiration;
-
- if (!useCached && DEBUG) {
- System.out.println(String.format(
- "[%1$s] cache disabled by timestamp %2$tD %2$tT < %3$tD %3$tT", //$NON-NLS-1$
- urlString, cacheModifiedMs, maxExpiration));
- }
- }
- }
- }
- }
-
- if (useCached) {
- // The caller needs an InputStream that supports the reset() operation.
- // The default FileInputStream does not, so load the file into a byte
- // array and return that.
- try {
- InputStream is = readCachedFile(cached);
- if (is != null) {
- if (DEBUG) {
- System.out.println(String.format("%s : Use cached file", urlString)); //$NON-NLS-1$
- }
-
- return is;
- }
- } catch (IOException ignore) {}
- }
-
- if (!useCached && mStrategy == Strategy.ONLY_CACHE) {
- // We don't have a document to serve from the cache.
- if (DEBUG) {
- System.out.println(String.format("%s : file not in cache", urlString)); //$NON-NLS-1$
- }
- return null;
- }
-
- // If we're not using the cache, try to remove the cache and download again.
- try {
- mFileOp.delete(cached);
- mFileOp.delete(info);
- } catch (SecurityException ignore) {}
-
- return downloadAndCache(urlString, monitor, cached, info,
- null /*headers*/, null /*statusCode*/);
- }
-
-
-
- // --------------
-
- @Nullable
- private InputStream readCachedFile(@NonNull File cached) throws IOException {
- InputStream is = null;
-
- int inc = 65536;
- int curr = 0;
- long len = cached.length();
- assert len < Integer.MAX_VALUE;
- if (len >= MAX_SMALL_FILE_SIZE) {
- // This is supposed to cache small files, not 2+ GB files.
- return null;
- }
- byte[] result = new byte[(int) (len > 0 ? len : inc)];
-
- try {
- is = mFileOp.newFileInputStream(cached);
-
- int n;
- while ((n = is.read(result, curr, result.length - curr)) != -1) {
- curr += n;
- if (curr == result.length) {
- byte[] temp = new byte[curr + inc];
- System.arraycopy(result, 0, temp, 0, curr);
- result = temp;
- }
- }
-
- return new ByteArrayInputStream(result, 0, curr);
-
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- }
- }
-
- /**
- * Download, cache and return as an in-memory byte stream.
- * The download is only done if the server returns 200/OK.
- * On success, store an info file next to the download with
- * a few headers.
- * <p/>
- * This method deletes the cached file and the info file ONLY if it
- * attempted a download and it failed to complete. It doesn't erase
- * anything if there's no download because the server returned a 404
- * or 304 or similar.
- *
- * @return An in-memory byte buffer input stream for the downloaded
- * and locally cached file, or null if nothing was downloaded
- * (including if it was a 304 Not-Modified status code.)
- */
- @Nullable
- private InputStream downloadAndCache(
- @NonNull String urlString,
- @NonNull ITaskMonitor monitor,
- @NonNull File cached,
- @NonNull File info,
- @Nullable Header[] headers,
- @Nullable AtomicInteger outStatusCode)
- throws FileNotFoundException, IOException, CanceledByUserException {
- InputStream is = null;
- OutputStream os = null;
-
- int inc = 65536;
- int curr = 0;
- byte[] result = new byte[inc];
-
- try {
- Pair<InputStream, HttpResponse> r =
- openUrl(urlString, true /*needsMarkResetSupport*/, monitor, headers);
-
- is = r.getFirst();
- HttpResponse response = r.getSecond();
-
- if (DEBUG) {
- System.out.println(String.format("%s : fetch: %s => %s", //$NON-NLS-1$
- urlString,
- headers == null ? "" : Arrays.toString(headers), //$NON-NLS-1$
- response.getStatusLine()));
- }
-
- int code = response.getStatusLine().getStatusCode();
-
- if (outStatusCode != null) {
- outStatusCode.set(code);
- }
-
- if (code != HttpStatus.SC_OK) {
- // Only a 200 response code makes sense here.
- // Even the other 20x codes should not apply, e.g. no content or partial
- // content are not statuses we want to handle and should never happen.
- // (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1 for list)
- return null;
- }
-
- os = mFileOp.newFileOutputStream(cached);
-
- int n;
- while ((n = is.read(result, curr, result.length - curr)) != -1) {
- if (os != null && n > 0) {
- os.write(result, curr, n);
- }
-
- curr += n;
-
- if (os != null && curr > MAX_SMALL_FILE_SIZE) {
- // If the file size exceeds our "small file size" threshold,
- // stop caching. We don't want to fill the disk.
- try {
- os.close();
- } catch (IOException ignore) {}
- try {
- cached.delete();
- info.delete();
- } catch (SecurityException ignore) {}
- os = null;
- }
- if (curr == result.length) {
- byte[] temp = new byte[curr + inc];
- System.arraycopy(result, 0, temp, 0, curr);
- result = temp;
- }
- }
-
- // Close the output stream, signaling it was stored properly.
- if (os != null) {
- try {
- os.close();
- os = null;
-
- saveInfo(urlString, response, info);
- } catch (IOException ignore) {}
- }
-
- return new ByteArrayInputStream(result, 0, curr);
-
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- if (os != null) {
- try {
- os.close();
- } catch (IOException ignore) {}
- // If we get here with the output stream not null, it means there
- // was an issue and we don't want to keep that file. We'll try to
- // delete it.
- try {
- mFileOp.delete(cached);
- mFileOp.delete(info);
- } catch (SecurityException ignore) {}
- }
- }
- }
-
- /**
- * Saves part of the HTTP Response to the info file.
- */
- private void saveInfo(
- @NonNull String urlString,
- @NonNull HttpResponse response,
- @NonNull File info) throws IOException {
- Properties props = new Properties();
-
- // we don't need the status code & URL right now.
- // Save it in case we want to have it later (e.g. to differentiate 200 and 404.)
- props.setProperty(KEY_URL, urlString);
- props.setProperty(KEY_STATUS_CODE,
- Integer.toString(response.getStatusLine().getStatusCode()));
-
- for (String name : INFO_HTTP_HEADERS) {
- Header h = response.getFirstHeader(name);
- if (h != null) {
- props.setProperty(name, h.getValue());
- }
- }
-
- mFileOp.saveProperties(info, props, "## Meta data for SDK Manager cache. Do not modify."); //$NON-NLS-1$
- }
-
- /**
- * Reads the info properties file.
- * @return The properties found or null if there's no file or it can't be read.
- */
- @Nullable
- private Properties readInfo(@NonNull File info) {
- if (mFileOp.exists(info)) {
- return mFileOp.loadProperties(info);
- }
- return null;
- }
-
- /**
- * Computes the cache filename for the given URL.
- * The filename uses the {@link #BIN_FILE_PREFIX}, the full URL string's hashcode and
- * a sanitized portion of the URL filename. The returned filename is never
- * more than 64 characters to ensure maximum file system compatibility.
- *
- * @param urlString The download URL.
- * @return A leaf filename for the cached download file.
- */
- @NonNull
- private String getCacheFilename(@NonNull String urlString) {
-
- int code = 0;
- for (int i = 0, j = urlString.length(); i < j; i++) {
- code = code * 31 + urlString.charAt(i);
- }
- String hash = String.format("%08x", code);
-
- String leaf = urlString.toLowerCase(Locale.US);
- if (leaf.length() >= 2) {
- int index = urlString.lastIndexOf('/', leaf.length() - 2);
- leaf = urlString.substring(index + 1);
- }
-
- leaf = leaf.replaceAll("[^a-z0-9_-]+", "_");
- leaf = leaf.replaceAll("__+", "_");
-
- leaf = hash + '-' + leaf;
- String prefix = BIN_FILE_PREFIX + REV_FILE_PREFIX;
- int n = 64 - prefix.length();
- if (leaf.length() > n) {
- leaf = leaf.substring(0, n);
- }
-
- return prefix + leaf;
- }
-
- @NonNull
- private String getInfoFilename(@NonNull String cacheFilename) {
- return cacheFilename.replaceFirst(BIN_FILE_PREFIX, INFO_FILE_PREFIX);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITask.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITask.java
deleted file mode 100755
index 58e76f9..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITask.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository;
-
-
-/**
- * A task that executes and can update a monitor to display its status.
- * The task will generally be run in a separate thread.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface ITask {
- void run(ITaskMonitor monitor);
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITaskFactory.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITaskFactory.java
deleted file mode 100755
index ce7133a..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITaskFactory.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository;
-
-
-/**
- * A factory that can start and run new {@link ITask}s.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface ITaskFactory {
-
- /**
- * Starts a new task with a new {@link ITaskMonitor}.
- * <p/>
- * The task will execute in a thread and runs it own UI loop.
- * This means the task can perform UI operations using
- * {@code Display#asyncExec(Runnable)}.
- * <p/>
- * In either case, the method only returns when the task has finished.
- *
- * @param title The title of the task, displayed in the monitor if any.
- * @param task The task to run.
- */
- void start(String title, ITask task);
-
- /**
- * Starts a new task contributing to an already existing {@link ITaskMonitor}.
- * <p/>
- * To use this properly, you should use {@link ITaskMonitor#createSubMonitor(int)}
- * and give the sub-monitor to the new task with the number of work units you want
- * it to fill. The {@link #start} method will make sure to <em>fill</em> the progress
- * when the task is completed, in case the actual task did not.
- * <p/>
- * When a task is started from within a monitor, it reuses the thread
- * from the parent. Otherwise it starts a new thread and runs it own
- * UI loop. This means the task can perform UI operations using
- * {@code Display#asyncExec(Runnable)}.
- * <p/>
- * In either case, the method only returns when the task has finished.
- *
- * @param title The title of the task, displayed in the monitor if any.
- * @param parentMonitor The parent monitor. Can be null.
- * @param task The task to run and have it display on the monitor.
- */
- void start(String title, ITaskMonitor parentMonitor, ITask task);
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/LocalSdkParser.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/LocalSdkParser.java
deleted file mode 100755
index c756d1f..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/LocalSdkParser.java
+++ /dev/null
@@ -1,804 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.io.FileWrapper;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.androidTarget.PlatformTarget;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.internal.repository.packages.BuildToolPackage;
-import com.android.sdklib.internal.repository.packages.DocPackage;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformPackage;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.SamplePackage;
-import com.android.sdklib.internal.repository.packages.SourcePackage;
-import com.android.sdklib.internal.repository.packages.SystemImagePackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.AddonManifestIniProps;
-import com.android.sdklib.repository.descriptors.PkgType;
-import com.android.utils.ILogger;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Scans a local SDK to find which packages are currently installed.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class LocalSdkParser {
-
- private Package[] mPackages;
-
- /** Parse all SDK folders. */
- public static final int PARSE_ALL = PkgType.PKG_ALL_INT;
- /** Parse the SDK/tools folder. */
- public static final int PARSE_TOOLS = PkgType.PKG_TOOLS.getIntValue();
- /** Parse the SDK/platform-tools folder */
- public static final int PARSE_PLATFORM_TOOLS = PkgType.PKG_PLATFORM_TOOLS.getIntValue();
- /** Parse the SDK/docs folder. */
- public static final int PARSE_DOCS = PkgType.PKG_DOC.getIntValue();
- /**
- * Equivalent to parsing the SDK/platforms folder but does so
- * by using the <em>valid</em> targets loaded by the {@link SdkManager}.
- * Parsing the platforms also parses the SDK/system-images folder.
- */
- public static final int PARSE_PLATFORMS = PkgType.PKG_PLATFORM.getIntValue();
- /**
- * Equivalent to parsing the SDK/addons folder but does so
- * by using the <em>valid</em> targets loaded by the {@link SdkManager}.
- */
- public static final int PARSE_ADDONS = PkgType.PKG_ADDON.getIntValue();
- /** Parse the SDK/samples folder.
- * Note: this will not detect samples located in the SDK/extras packages. */
- public static final int PARSE_SAMPLES = PkgType.PKG_SAMPLE.getIntValue();
- /** Parse the SDK/sources folder. */
- public static final int PARSE_SOURCES = PkgType.PKG_SOURCE.getIntValue();
- /** Parse the SDK/extras folder. */
- public static final int PARSE_EXTRAS = PkgType.PKG_EXTRA.getIntValue();
- /** Parse the SDK/build-tools folder. */
- public static final int PARSE_BUILD_TOOLS = PkgType.PKG_BUILD_TOOLS.getIntValue();
-
- public LocalSdkParser() {
- // pass
- }
-
- /**
- * Returns the packages found by the last call to {@link #parseSdk}.
- * <p/>
- * This returns initially returns null.
- * Once the parseSdk() method has been called, this returns a possibly empty but non-null array.
- */
- public Package[] getPackages() {
- return mPackages;
- }
-
- /**
- * Clear the internal packages list. After this call, {@link #getPackages()} will return
- * null till {@link #parseSdk} is called.
- */
- public void clearPackages() {
- mPackages = null;
- }
-
- /**
- * Scan the give SDK to find all the packages already installed at this location.
- * <p/>
- * Store the packages internally. You can use {@link #getPackages()} to retrieve them
- * at any time later.
- * <p/>
- * Equivalent to calling {@code parseSdk(..., PARSE_ALL, ...); }
- *
- * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @param monitor A monitor to track progress. Cannot be null.
- * @return The packages found. Can be retrieved later using {@link #getPackages()}.
- */
- @NonNull
- public Package[] parseSdk(
- @NonNull String osSdkRoot,
- @NonNull SdkManager sdkManager,
- @NonNull ITaskMonitor monitor) {
- return parseSdk(osSdkRoot, sdkManager, PARSE_ALL, monitor);
- }
-
- /**
- * Scan the give SDK to find all the packages already installed at this location.
- * <p/>
- * Store the packages internally. You can use {@link #getPackages()} to retrieve them
- * at any time later.
- *
- * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @param parseFilter Either {@link #PARSE_ALL} or an ORed combination of the other
- * {@code PARSE_} constants to indicate what should be parsed.
- * @param monitor A monitor to track progress. Cannot be null.
- * @return The packages found. Can be retrieved later using {@link #getPackages()}.
- */
- @NonNull
- public Package[] parseSdk(
- @NonNull String osSdkRoot,
- @NonNull SdkManager sdkManager,
- int parseFilter,
- @NonNull ITaskMonitor monitor) {
- ArrayList<Package> packages = new ArrayList<Package>();
- HashSet<File> visited = new HashSet<File>();
-
- monitor.setProgressMax(11);
-
- File dir = null;
- Package pkg = null;
-
- if ((parseFilter & PARSE_DOCS) != 0) {
- dir = new File(osSdkRoot, SdkConstants.FD_DOCS);
- pkg = scanDoc(dir, monitor);
- if (pkg != null) {
- packages.add(pkg);
- visited.add(dir);
- }
- }
- monitor.incProgress(1);
-
- if ((parseFilter & PARSE_TOOLS) != 0) {
- dir = new File(osSdkRoot, SdkConstants.FD_TOOLS);
- pkg = scanTools(dir, monitor);
- if (pkg != null) {
- packages.add(pkg);
- visited.add(dir);
- }
- }
- monitor.incProgress(1);
-
- if ((parseFilter & PARSE_PLATFORM_TOOLS) != 0) {
- dir = new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
- pkg = scanPlatformTools(dir, monitor);
- if (pkg != null) {
- packages.add(pkg);
- visited.add(dir);
- }
- }
- monitor.incProgress(1);
-
- if ((parseFilter & PARSE_BUILD_TOOLS) != 0) {
- scanBuildTools(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
-
- // for platforms, add-ons and samples, rely on the SdkManager parser
- if ((parseFilter & (PARSE_ADDONS | PARSE_PLATFORMS)) != 0) {
- File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
-
- for(IAndroidTarget target : sdkManager.getTargets()) {
- Properties props = parseProperties(new File(target.getLocation(),
- SdkConstants.FN_SOURCE_PROP));
-
- try {
- pkg = null;
- if (target.isPlatform() && (parseFilter & PARSE_PLATFORMS) != 0) {
- pkg = PlatformPackage.create(target, props);
-
- if (samplesRoot.isDirectory()) {
- // Get the samples dir for a platform if it is located in the new
- // root /samples dir. We purposely ignore "old" samples that are
- // located under the platform dir.
- File samplesDir = new File(target.getPath(IAndroidTarget.SAMPLES));
- if (samplesDir.exists() &&
- samplesDir.getParentFile().equals(samplesRoot)) {
- Properties samplesProps = parseProperties(
- new File(samplesDir, SdkConstants.FN_SOURCE_PROP));
- if (samplesProps != null) {
- Package pkg2 = SamplePackage.create(target, samplesProps);
- packages.add(pkg2);
- }
- visited.add(samplesDir);
- }
- }
- } else if ((parseFilter & PARSE_ADDONS) != 0) {
- pkg = AddonPackage.create(target, props);
- }
-
- if (pkg != null) {
- for (ISystemImage systemImage : target.getSystemImages()) {
- if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) {
- File siDir = systemImage.getLocation();
- if (siDir.isDirectory()) {
- Properties siProps = parseProperties(
- new File(siDir, SdkConstants.FN_SOURCE_PROP));
- Package pkg2 = new SystemImagePackage(
- target.getVersion(),
- 0 /*rev*/, // use the one from siProps
- systemImage.getAbiType(),
- siProps,
- siDir.getAbsolutePath());
- packages.add(pkg2);
- visited.add(siDir);
- }
- }
- }
- }
-
- } catch (Exception e) {
- monitor.error(e, null);
- }
-
- if (pkg != null) {
- packages.add(pkg);
- visited.add(new File(target.getLocation()));
- }
- }
- }
- monitor.incProgress(1);
-
- if ((parseFilter & PARSE_PLATFORMS) != 0) {
- scanMissingSystemImages(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_ADDONS) != 0) {
- scanMissingAddons(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_SAMPLES) != 0) {
- scanMissingSamples(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_EXTRAS) != 0) {
- scanExtras(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_EXTRAS) != 0) {
- scanExtrasDirectory(osSdkRoot, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_SOURCES) != 0) {
- scanSources(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
-
- Collections.sort(packages);
-
- mPackages = packages.toArray(new Package[packages.size()]);
- return mPackages;
- }
-
- /**
- * Find any directory in the /extras/vendors/path folders for extra packages.
- * This isn't a recursive search.
- */
- private void scanExtras(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ILogger log) {
- File root = new File(sdkManager.getLocation(), SdkConstants.FD_EXTRAS);
-
- for (File vendor : listFilesNonNull(root)) {
- if (vendor.isDirectory()) {
- scanExtrasDirectory(vendor.getAbsolutePath(), visited, packages, log);
- }
- }
- }
-
- /**
- * Find any other directory in the given "root" directory that hasn't been visited yet
- * and assume they contain extra packages. This is <em>not</em> a recursive search.
- */
- private void scanExtrasDirectory(String extrasRoot,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ILogger log) {
- File root = new File(extrasRoot);
-
- for (File dir : listFilesNonNull(root)) {
- if (dir.isDirectory() && !visited.contains(dir)) {
- Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
- if (props != null) {
- try {
- Package pkg = ExtraPackage.create(
- null, //source
- props, //properties
- null, //vendor
- dir.getName(), //path
- 0, //revision
- null, //license
- null, //description
- null, //descUrl
- dir.getPath() //archiveOsPath
- );
-
- packages.add(pkg);
- visited.add(dir);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * Find any other sub-directories under the /samples root that hasn't been visited yet
- * and assume they contain sample packages. This is <em>not</em> a recursive search.
- * <p/>
- * The use case is to find samples dirs under /samples when their target isn't loaded.
- */
- private void scanMissingSamples(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ILogger log) {
- File root = new File(sdkManager.getLocation());
- root = new File(root, SdkConstants.FD_SAMPLES);
-
- for (File dir : listFilesNonNull(root)) {
- if (dir.isDirectory() && !visited.contains(dir)) {
- Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
- if (props != null) {
- try {
- Package pkg = SamplePackage.create(dir.getAbsolutePath(), props);
- packages.add(pkg);
- visited.add(dir);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * The sdk manager only lists valid addons. However here we also want to find "broken"
- * addons, i.e. addons that failed to load for some reason.
- * <p/>
- * Find any other sub-directories under the /add-ons root that hasn't been visited yet
- * and assume they contain broken addons.
- */
- private void scanMissingAddons(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ILogger log) {
- File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS);
-
- for (File dir : listFilesNonNull(addons)) {
- if (dir.isDirectory() && !visited.contains(dir)) {
- Pair<Map<String, String>, String> infos =
- parseAddonProperties(dir, sdkManager.getTargets(), log);
- Properties sourceProps =
- parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
-
- Map<String, String> addonProps = infos.getFirst();
- String error = infos.getSecond();
- try {
- Package pkg = AddonPackage.createBroken(dir.getAbsolutePath(),
- sourceProps,
- addonProps,
- error);
- packages.add(pkg);
- visited.add(dir);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
-
- /**
- * Parses the add-on properties and decodes any error that occurs when
- * loading an addon.
- *
- * @param addonDir the location of the addon directory.
- * @param targetList The list of Android target that were already loaded
- * from the SDK.
- * @param log the ILogger object receiving warning/error from the parsing.
- * @return A pair with the property map and an error string. Both can be
- * null but not at the same time. If a non-null error is present
- * then the property map must be ignored. The error should be
- * translatable as it might show up in the SdkManager UI.
- */
- @Deprecated // Copied from SdkManager.java, dup of LocalAddonPkgInfo.parseAddonProperties.
- @NonNull
- public static Pair<Map<String, String>, String> parseAddonProperties(
- @NonNull File addonDir, @NonNull IAndroidTarget[] targetList,
- @NonNull ILogger log) {
- Map<String, String> propertyMap = null;
- String error = null;
-
- FileWrapper addOnManifest = new FileWrapper(addonDir,
- SdkConstants.FN_MANIFEST_INI);
-
- do {
- if (!addOnManifest.isFile()) {
- error = String.format("File not found: %1$s",
- SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- propertyMap = ProjectProperties.parsePropertyFile(addOnManifest,
- log);
- if (propertyMap == null) {
- error = String.format("Failed to parse properties from %1$s",
- SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- // look for some specific values in the map.
- // we require name, vendor, and api
- String name = propertyMap.get(AddonManifestIniProps.ADDON_NAME);
- if (name == null) {
- error = String.format("'%1$s' is missing from %2$s.",
- AddonManifestIniProps.ADDON_NAME,
- SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- String vendor = propertyMap.get(AddonManifestIniProps.ADDON_VENDOR);
- if (vendor == null) {
- error = String.format("'%1$s' is missing from %2$s.",
- AddonManifestIniProps.ADDON_VENDOR,
- SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- String api = propertyMap.get(AddonManifestIniProps.ADDON_API);
- if (api == null) {
- error = String.format("'%1$s' is missing from %2$s.",
- AddonManifestIniProps.ADDON_API,
- SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- // Look for a platform that has a matching api level or codename.
- PlatformTarget baseTarget = null;
- for (IAndroidTarget target : targetList) {
- if (target.isPlatform() && target.getVersion().equals(api)) {
- baseTarget = (PlatformTarget) target;
- break;
- }
- }
-
- if (baseTarget == null) {
- error = String.format(
- "Unable to find base platform with API level '%1$s'",
- api);
- break;
- }
-
- // get the add-on revision
- String revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION);
- if (revision == null) {
- revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION_OLD);
- }
- if (revision != null) {
- try {
- Integer.parseInt(revision);
- } catch (NumberFormatException e) {
- // looks like revision does not parse to a number.
- error = String.format(
- "%1$s is not a valid number in %2$s.",
- AddonManifestIniProps.ADDON_REVISION,
- SdkConstants.FN_BUILD_PROP);
- break;
- }
- }
-
- } while (false);
-
- return Pair.of(propertyMap, error);
- }
-
-
- /**
- * The sdk manager only lists valid system image via its addons or platform targets.
- * However here we also want to find "broken" system images, that is system images
- * that are located in the sdk/system-images folder but somehow not loaded properly.
- */
- private void scanMissingSystemImages(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ILogger log) {
- File siRoot = new File(sdkManager.getLocation(), SdkConstants.FD_SYSTEM_IMAGES);
-
- // The system-images folder contains a list of platform folders.
- for (File platformDir : listFilesNonNull(siRoot)) {
- if (platformDir.isDirectory() && !visited.contains(platformDir)) {
- visited.add(platformDir);
-
- // In the platform directory, we expect a list of abi folders
- // or a list of tag/abi folders. Basically parse any folder that has
- // a source.prop file within 2 levels.
- List<File> propFiles = Lists.newArrayList();
-
- for (File dir1 : listFilesNonNull(platformDir)) {
- if (dir1.isDirectory() && !visited.contains(dir1)) {
- visited.add(dir1);
- File prop1 = new File(dir1, SdkConstants.FN_SOURCE_PROP);
- if (prop1.isFile()) {
- propFiles.add(prop1);
- } else {
- for (File dir2 : listFilesNonNull(dir1)) {
- if (dir2.isDirectory() && !visited.contains(dir2)) {
- visited.add(dir2);
- File prop2 = new File(dir2, SdkConstants.FN_SOURCE_PROP);
- if (prop2.isFile()) {
- propFiles.add(prop2);
- }
- }
- }
- }
- }
- }
-
- for (File propFile : propFiles) {
- Properties props = parseProperties(propFile);
- try {
- Package pkg = SystemImagePackage.createBroken(propFile.getParentFile(),
- props);
- packages.add(pkg);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * Scan the sources/folders and register valid as well as broken source packages.
- */
- private void scanSources(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ILogger log) {
- File srcRoot = new File(sdkManager.getLocation(), SdkConstants.FD_PKG_SOURCES);
-
- // The sources folder contains a list of platform folders.
- for (File platformDir : listFilesNonNull(srcRoot)) {
- if (platformDir.isDirectory() && !visited.contains(platformDir)) {
- visited.add(platformDir);
-
- // Ignore empty directories
- File[] srcFiles = platformDir.listFiles();
- if (srcFiles != null && srcFiles.length > 0) {
- Properties props =
- parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
-
- try {
- Package pkg = SourcePackage.create(platformDir, props);
- packages.add(pkg);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * Try to find a tools package at the given location.
- * Returns null if not found.
- */
- private Package scanTools(File toolFolder, ILogger log) {
- // Can we find some properties?
- Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
-
- // We're not going to check that all tools are present. At the very least
- // we should expect to find android and an emulator adapted to the current OS.
- boolean hasEmulator = false;
- boolean hasAndroid = false;
- String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
- String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
- for (File file : listFilesNonNull(toolFolder)) {
- String name = file.getName();
- if (SdkConstants.FN_EMULATOR.equals(name)) {
- hasEmulator = true;
- }
- if (android1.equals(name) || (android2 != null && android2.equals(name))) {
- hasAndroid = true;
- }
- }
-
- if (!hasAndroid || !hasEmulator) {
- return null;
- }
-
- // Create our package. use the properties if we found any.
- try {
- Package pkg = ToolPackage.create(
- null, //source
- props, //properties
- 0, //revision
- null, //license
- "Tools", //description
- null, //descUrl
- toolFolder.getPath() //archiveOsPath
- );
-
- return pkg;
- } catch (Exception e) {
- log.error(e, null);
- }
- return null;
- }
-
- /**
- * Try to find a platform-tools package at the given location.
- * Returns null if not found.
- */
- private Package scanPlatformTools(File platformToolsFolder, ILogger log) {
- // Can we find some properties?
- Properties props = parseProperties(new File(platformToolsFolder,
- SdkConstants.FN_SOURCE_PROP));
-
- // We're not going to check that all tools are present. At the very least
- // we should expect to find adb, aidl, aapt and dx (adapted to the current OS).
-
- if (platformToolsFolder.listFiles() == null) {
- // ListFiles is null if the directory doesn't even exist.
- // Not going to find anything in there...
- return null;
- }
-
- // Create our package. use the properties if we found any.
- try {
- Package pkg = PlatformToolPackage.create(
- null, //source
- props, //properties
- 0, //revision
- null, //license
- "Platform Tools", //description
- null, //descUrl
- platformToolsFolder.getPath() //archiveOsPath
- );
-
- return pkg;
- } catch (Exception e) {
- log.error(e, null);
- }
- return null;
- }
-
- /**
- * Scan the build-tool/folders and register valid as well as broken build tool packages.
- */
- private void scanBuildTools(
- SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ILogger log) {
- File buildToolRoot = new File(sdkManager.getLocation(), SdkConstants.FD_BUILD_TOOLS);
-
- // The build-tool root folder contains a list of revisioned folders.
- for (File buildToolDir : listFilesNonNull(buildToolRoot)) {
- if (buildToolDir.isDirectory() && !visited.contains(buildToolDir)) {
- visited.add(buildToolDir);
-
- // Ignore empty directories
- File[] srcFiles = buildToolDir.listFiles();
- if (srcFiles != null && srcFiles.length > 0) {
- Properties props =
- parseProperties(new File(buildToolDir, SdkConstants.FN_SOURCE_PROP));
-
- try {
- Package pkg = BuildToolPackage.create(buildToolDir, props);
- packages.add(pkg);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * Try to find a docs package at the given location.
- * Returns null if not found.
- */
- private Package scanDoc(File docFolder, ILogger log) {
- // Can we find some properties?
- Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
-
- // To start with, a doc folder should have an "index.html" to be acceptable.
- // We don't actually check the content of the file.
- if (new File(docFolder, "index.html").isFile()) {
- try {
- Package pkg = DocPackage.create(
- null, //source
- props, //properties
- 0, //apiLevel
- null, //codename
- 0, //revision
- null, //license
- null, //description
- null, //descUrl
- docFolder.getPath() //archiveOsPath
- );
-
- return pkg;
- } catch (Exception e) {
- log.error(e, null);
- }
- }
-
- return null;
- }
-
- /**
- * Parses the given file as properties file if it exists.
- * Returns null if the file does not exist, cannot be parsed or has no properties.
- */
- private Properties parseProperties(File propsFile) {
- FileInputStream fis = null;
- try {
- if (propsFile.exists()) {
- fis = new FileInputStream(propsFile);
-
- Properties props = new Properties();
- props.load(fis);
-
- // To be valid, there must be at least one property in it.
- if (!props.isEmpty()) {
- return props;
- }
- }
-
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
- return null;
- }
-
- /**
- * Helper method that calls {@link File#listFiles()} and returns
- * a non-null empty list if the input is not a directory or has
- * no files.
- */
- @NonNull
- private static File[] listFilesNonNull(@NonNull File dir) {
- if (dir.isDirectory()) {
- File[] files = dir.listFiles();
- if (files != null) {
- return files;
- }
- }
- return FileOp.EMPTY_FILE_ARRAY;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/NullTaskMonitor.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/NullTaskMonitor.java
deleted file mode 100755
index 2726954..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/NullTaskMonitor.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.utils.ILogger;
-import com.android.utils.NullLogger;
-
-
-/**
- * A no-op implementation of the {@link ITaskMonitor} interface.
- * <p/>
- * This can be passed to methods that require a monitor when the caller doesn't
- * have any UI to update or means to report tracked progress.
- * A custom {@link ILogger} is used. Clients could use {@link NullLogger} if
- * they really don't care about the logging either.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class NullTaskMonitor implements ITaskMonitor {
-
- private final ILogger mLog;
-
- /**
- * Creates a no-op {@link ITaskMonitor} that defers logging to the specified
- * logger.
- * <p/>
- * This can be passed to methods that require a monitor when the caller doesn't
- * have any UI to update or means to report tracked progress.
- *
- * @param log An {@link ILogger}. Must not be null. Consider using {@link NullLogger}.
- */
- public NullTaskMonitor(ILogger log) {
- mLog = log;
- }
-
- @Override
- public void setDescription(String format, Object...args) {
- // pass
- }
-
- @Override
- public void log(String format, Object...args) {
- mLog.info(format, args);
- }
-
- @Override
- public void logError(String format, Object...args) {
- mLog.error(null /*throwable*/, format, args);
- }
-
- @Override
- public void logVerbose(String format, Object...args) {
- mLog.verbose(format, args);
- }
-
- @Override
- public void setProgressMax(int max) {
- // pass
- }
-
- @Override
- public int getProgressMax() {
- return 0;
- }
-
- @Override
- public void incProgress(int delta) {
- // pass
- }
-
- /** Always return 1. */
- @Override
- public int getProgress() {
- return 1;
- }
-
- /** Always return false. */
- @Override
- public boolean isCancelRequested() {
- return false;
- }
-
- @Override
- public ITaskMonitor createSubMonitor(int tickCount) {
- return this;
- }
-
- /** Always return false. */
- @Override
- public boolean displayPrompt(final String title, final String message) {
- return false;
- }
-
- /** Always return null. */
- @Override
- public UserCredentials displayLoginCredentialsPrompt(String title, String message) {
- return null;
- }
-
- // --- ILogger ---
-
- @Override
- public void error(@Nullable Throwable t, @Nullable String errorFormat, Object... args) {
- mLog.error(t, errorFormat, args);
- }
-
- @Override
- public void warning(@NonNull String warningFormat, Object... args) {
- mLog.warning(warningFormat, args);
- }
-
- @Override
- public void info(@NonNull String msgFormat, Object... args) {
- mLog.info(msgFormat, args);
- }
-
- @Override
- public void verbose(@NonNull String msgFormat, Object... args) {
- mLog.verbose(msgFormat, args);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/SdkStats.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/SdkStats.java
deleted file mode 100755
index cb1af74..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/SdkStats.java
+++ /dev/null
@@ -1,627 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.io.NonClosingInputStream;
-import com.android.io.NonClosingInputStream.CloseBehavior;
-import com.android.sdklib.repository.SdkStatsConstants;
-import com.android.utils.SparseArray;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.UnknownHostException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.net.ssl.SSLKeyException;
-import javax.xml.XMLConstants;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-
-/**
- * Retrieves stats on platforms.
- * <p/>
- * This returns information stored on the repository in a different XML file
- * and isn't directly tied to the existence of the listed platforms.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkStats {
-
- public static class PlatformStatBase {
- private final int mApiLevel;
- private final String mVersionName;
- private final String mCodeName;
- private final float mShare;
-
- public PlatformStatBase(int apiLevel,
- String versionName,
- String codeName,
- float share) {
- mApiLevel = apiLevel;
- mVersionName = versionName;
- mCodeName = codeName;
- mShare = share;
- }
-
- /** The Android API Level for the platform. An int > 0. */
- public int getApiLevel() {
- return mApiLevel;
- }
-
- /** The official codename for this platform, for example "Cupcake". */
- public String getCodeName() {
- return mCodeName;
- }
-
- /** The official version name of this platform, for example "Android 1.5". */
- public String getVersionName() {
- return mVersionName;
- }
-
- /** An approximate share percentage of this platform and all the
- * platforms of lower API level. */
- public float getShare() {
- return mShare;
- }
-
- /** Returns a string representation of this object, for debugging purposes. */
- @Override
- public String toString() {
- return String.format("api=%d, code=%s, vers=%s, share=%.1f%%", //$NON-NLS-1$
- mApiLevel, mCodeName, mVersionName, mShare);
- }
- }
-
- public static class PlatformStat extends PlatformStatBase {
- private final float mAccumShare;
-
- public PlatformStat(int apiLevel,
- String versionName,
- String codeName,
- float share,
- float accumShare) {
- super(apiLevel, versionName, codeName, share);
- mAccumShare = accumShare;
- }
-
- public PlatformStat(PlatformStatBase base, float accumShare) {
- super(base.getApiLevel(),
- base.getVersionName(),
- base.getCodeName(),
- base.getShare());
- mAccumShare = accumShare;
- }
-
- /** The accumulated approximate share percentage of that platform. */
- public float getAccumShare() {
- return mAccumShare;
- }
-
- /** Returns a string representation of this object, for debugging purposes. */
- @Override
- public String toString() {
- return String.format("<Stat %s, accum=%.1f%%>", super.toString(), mAccumShare);
- }
- }
-
- private final SparseArray<PlatformStat> mStats = new SparseArray<SdkStats.PlatformStat>();
-
- public SdkStats() {
- }
-
- public SparseArray<PlatformStat> getStats() {
- return mStats;
- }
-
- public void load(DownloadCache cache, boolean forceHttp, ITaskMonitor monitor) {
-
- String url = SdkStatsConstants.URL_STATS;
-
- if (forceHttp) {
- url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- monitor.setProgressMax(5);
- monitor.setDescription("Fetching %1$s", url);
- monitor.incProgress(1);
-
- Exception[] exception = new Exception[] { null };
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- Document validatedDoc = null;
- String validatedUri = null;
-
- InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
-
- if (xml != null) {
- monitor.setDescription("Validate XML");
-
- // Explore the XML to find the potential XML schema version
- int version = getXmlSchemaVersion(xml);
-
- if (version >= 1 && version <= SdkStatsConstants.NS_LATEST_VERSION) {
- // This should be a version we can handle. Try to validate it
- // and report any error as invalid XML syntax,
-
- String uri = validateXml(xml, url, version, validationError, validatorFound);
- if (uri != null) {
- // Validation was successful
- validatedDoc = getDocument(xml, monitor);
- validatedUri = uri;
-
- }
- } else if (version > SdkStatsConstants.NS_LATEST_VERSION) {
- // The schema used is more recent than what is supported by this tool.
- // We don't have an upgrade-path support yet, so simply ignore the document.
- closeStream(xml);
- return;
- }
- }
-
- // If any exception was handled during the URL fetch, display it now.
- if (exception[0] != null) {
- String reason = null;
- if (exception[0] instanceof FileNotFoundException) {
- // FNF has no useful getMessage, so we need to special handle it.
- reason = "File not found";
- } else if (exception[0] instanceof UnknownHostException &&
- exception[0].getMessage() != null) {
- // This has no useful getMessage yet could really use one
- reason = String.format("Unknown Host %1$s", exception[0].getMessage());
- } else if (exception[0] instanceof SSLKeyException) {
- // That's a common error and we have a pref for it.
- reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
- } else if (exception[0].getMessage() != null) {
- reason = exception[0].getMessage();
- } else {
- // We don't know what's wrong. Let's give the exception class at least.
- reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
- }
-
- monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
- }
-
- if (validationError[0] != null) {
- monitor.logError("%s", validationError[0]); //$NON-NLS-1$
- }
-
- // Stop here if we failed to validate the XML. We don't want to load it.
- if (validatedDoc == null) {
- closeStream(xml);
- return;
- }
-
- monitor.incProgress(1);
-
- if (xml != null) {
- monitor.setDescription("Parse XML");
- monitor.incProgress(1);
- parseStatsDocument(validatedDoc, validatedUri, monitor);
- }
-
- // done
- monitor.incProgress(1);
- closeStream(xml);
- }
-
- /**
- * Fetches the document at the given URL and returns it as a stream. Returns
- * null if anything wrong happens.
- *
- * @param urlString The URL to load, as a string.
- * @param monitor {@link ITaskMonitor} related to this URL.
- * @param outException If non null, where to store any exception that
- * happens during the fetch.
- * @see UrlOpener UrlOpener, which handles all URL logic.
- */
- private InputStream fetchXmlUrl(String urlString,
- DownloadCache cache,
- ITaskMonitor monitor,
- Exception[] outException) {
- try {
- InputStream xml = cache.openCachedUrl(urlString, monitor);
- if (xml != null) {
- xml.mark(500000);
- xml = new NonClosingInputStream(xml);
- ((NonClosingInputStream) xml).setCloseBehavior(CloseBehavior.RESET);
- }
- return xml;
- } catch (Exception e) {
- if (outException != null) {
- outException[0] = e;
- }
- }
-
- return null;
- }
-
- /**
- * Closes the stream, ignore any exception from InputStream.close().
- * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
- */
- private void closeStream(InputStream is) {
- if (is != null) {
- if (is instanceof NonClosingInputStream) {
- ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
- }
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- }
-
- /**
- * Manually parses the root element of the XML to extract the schema version
- * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
- * declaration.
- *
- * @return 1..{@link SdkStatsConstants#NS_LATEST_VERSION} for a valid schema version
- * or 0 if no schema could be found.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected int getXmlSchemaVersion(InputStream xml) {
- if (xml == null) {
- return 0;
- }
-
- // Get an XML document
- Document doc = null;
- try {
- xml.reset();
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // We don't want the default handler which prints errors to stderr.
- builder.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- doc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
-
- } catch (Exception e) {
- // Failed to reset XML stream
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- }
-
- if (doc == null) {
- return 0;
- }
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-addons-list
- // xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(SdkStatsConstants.NS_PATTERN);
-
- String prefix = null;
- for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (SdkStatsConstants.NODE_SDK_STATS.equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null) {
- Matcher m = nsPattern.matcher(uri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- return Integer.parseInt(version);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
- }
- }
- }
- }
-
- return 0;
- }
-
- /**
- * Validates this XML against one of the requested SDK Repository schemas.
- * If the XML was correctly validated, returns the schema that worked.
- * If it doesn't validate, returns null and stores the error in outError[0].
- * If we can't find a validator, returns null and set validatorFound[0] to false.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected String validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
-
- if (xml == null) {
- return null;
- }
-
- try {
- Validator validator = getValidator(version);
-
- if (validator == null) {
- validatorFound[0] = Boolean.FALSE;
- outError[0] = String.format(
- "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
- url);
- return null;
- }
-
- validatorFound[0] = Boolean.TRUE;
-
- // Reset the stream if it supports that operation.
- xml.reset();
-
- // Validation throws a bunch of possible Exceptions on failure.
- validator.validate(new StreamSource(xml));
- return SdkStatsConstants.getSchemaUri(version);
-
- } catch (SAXParseException e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
- url,
- e.getLineNumber(),
- e.getColumnNumber(),
- e.toString());
-
- } catch (Exception e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nError: %2$s",
- url,
- e.toString());
- }
- return null;
- }
-
- /**
- * Helper method that returns a validator for our XSD, or null if the current Java
- * implementation can't process XSD schemas.
- *
- * @param version The version of the XML Schema.
- * See {@link SdkStatsConstants#getXsdStream(int)}
- */
- private Validator getValidator(int version) throws SAXException {
- InputStream xsdStream = SdkStatsConstants.getXsdStream(version);
- try {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-
- if (factory == null) {
- return null;
- }
-
- // This may throw a SAX Exception if the schema itself is not a valid XSD
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
-
- Validator validator = schema == null ? null : schema.newValidator();
-
- return validator;
- } finally {
- if (xsdStream != null) {
- try {
- xsdStream.close();
- } catch (IOException ignore) {}
- }
- }
- }
-
- /**
- * Takes an XML document as a string as parameter and returns a DOM for it.
- *
- * On error, returns null and prints a (hopefully) useful message on the monitor.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(true);
- factory.setNamespaceAware(true);
-
- DocumentBuilder builder = factory.newDocumentBuilder();
- xml.reset();
- Document doc = builder.parse(new InputSource(xml));
-
- return doc;
- } catch (ParserConfigurationException e) {
- monitor.logError("Failed to create XML document builder");
-
- } catch (SAXException e) {
- monitor.logError("Failed to parse XML document");
-
- } catch (IOException e) {
- monitor.logError("Failed to read XML document");
- }
-
- return null;
- }
-
- /**
- * Parses all valid platforms found in the XML.
- * Changes the stats array returned by {@link #getStats()}
- * (also returns the value directly, useful for unit tests.)
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SparseArray<PlatformStat> parseStatsDocument(
- Document doc,
- String nsUri,
- ITaskMonitor monitor) {
-
- String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
- if (baseUrl != null) {
- if (baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
- baseUrl = null;
- }
- }
-
- SparseArray<PlatformStatBase> platforms = new SparseArray<SdkStats.PlatformStatBase>();
- int maxApi = 0;
-
- Node root = getFirstChild(doc, nsUri, SdkStatsConstants.NODE_SDK_STATS);
- if (root != null) {
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- child.getLocalName().equals(SdkStatsConstants.NODE_PLATFORM)) {
-
- try {
- Node node = getFirstChild(child, nsUri, SdkStatsConstants.NODE_API_LEVEL);
- int apiLevel = Integer.parseInt(node.getTextContent().trim());
-
- if (apiLevel < 1) {
- // bad API level, ignore it.
- continue;
- }
-
- if (platforms.indexOfKey(apiLevel) >= 0) {
- // if we already loaded that API, ignore duplicates
- continue;
- }
-
- String codeName =
- getFirstChild(child, nsUri, SdkStatsConstants.NODE_CODENAME).
- getTextContent().trim();
- String versName =
- getFirstChild(child, nsUri, SdkStatsConstants.NODE_VERSION).
- getTextContent().trim();
-
- if (codeName == null || versName == null ||
- codeName.isEmpty() || versName.isEmpty()) {
- // bad names. ignore.
- continue;
- }
-
- node = getFirstChild(child, nsUri, SdkStatsConstants.NODE_SHARE);
- float percent = Float.parseFloat(node.getTextContent().trim());
-
- if (percent < 0 || percent > 100) {
- // invalid percentage. ignore.
- continue;
- }
-
- PlatformStatBase p = new PlatformStatBase(
- apiLevel, versName, codeName, percent);
- platforms.put(apiLevel, p);
-
- maxApi = apiLevel > maxApi ? apiLevel : maxApi;
-
- } catch (Exception ignore) {
- // Error parsing this platform. Ignore it.
- continue;
- }
- }
- }
- }
-
- mStats.clear();
-
- // Compute cumulative share percents & fill in final map
- for (int api = 1; api <= maxApi; api++) {
- PlatformStatBase p = platforms.get(api);
- if (p == null) {
- continue;
- }
-
- float sum = p.getShare();
- for (int j = api + 1; j <= maxApi; j++) {
- PlatformStatBase pj = platforms.get(j);
- if (pj != null) {
- sum += pj.getShare();
- }
- }
-
- mStats.put(api, new PlatformStat(p, sum));
- }
-
- return mStats;
- }
-
- /**
- * Returns the first child element with the given XML local name.
- * If xmlLocalName is null, returns the very first child element.
- */
- private Node getFirstChild(Node node, String nsUri, String xmlLocalName) {
-
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
- return child;
- }
- }
- }
-
- return null;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java
deleted file mode 100755
index e4401de..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.sdklib.internal.repository.archives;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.NoPreviewRevision;
-
-import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class ArchFilter {
-
- private static final String PROP_HOST_OS = "Archive.HostOs"; //$NON-NLS-1$
- private static final String PROP_HOST_BITS = "Archive.HostBits"; //$NON-NLS-1$
- private static final String PROP_JVM_BITS = "Archive.JvmBits"; //$NON-NLS-1$
- private static final String PROP_MIN_JVM_VERSION = "Archive.MinJvmVers"; //$NON-NLS-1$
-
- /**
- * The legacy property used to serialize {@link LegacyOs} in source.properties files.
- * <p/>
- * Replaced by {@code ArchFilter.PROP_HOST_OS}.
- */
- public static final String LEGACY_PROP_OS = "Archive.Os"; //$NON-NLS-1$
-
- /**
- * The legacy property used to serialize {@link LegacyArch} in source.properties files.
- * <p/>
- * Replaced by {@code ArchFilter.PROP_HOST_BITS} and {@code ArchFilter.PROP_JVM_BITS}.
- */
- public static final String LEGACY_PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$
-
- private final HostOs mHostOs;
- private final BitSize mHostBits;
- private final BitSize mJvmBits;
- private final NoPreviewRevision mMinJvmVersion;
-
- /**
- * Creates a new {@link ArchFilter} with the specified filter attributes.
- * <p/>
- * This filters represents the attributes requires for a package's {@link Archive} to
- * be installable on the current architecture. Not all fields are required -- those that
- * are not specified imply there is no limitation on that particular attribute.
- *
- *
- * @param hostOs The host OS or null if there's no limitation for this package.
- * @param hostBits The host bit size or null if there's no limitation for this package.
- * @param jvmBits The JVM bit size or null if there's no limitation for this package.
- * @param minJvmVersion The minimal JVM version required by this package
- * or null if there's no limitation for this package.
- */
- public ArchFilter(@Nullable HostOs hostOs,
- @Nullable BitSize hostBits,
- @Nullable BitSize jvmBits,
- @Nullable NoPreviewRevision minJvmVersion) {
- mHostOs = hostOs;
- mHostBits = hostBits;
- mJvmBits = jvmBits;
- mMinJvmVersion = minJvmVersion;
- }
-
- /**
- * Creates an {@link ArchFilter} using properties previously saved in a {@link Properties}
- * object, typically by the {@link ArchFilter#saveProperties(Properties)} method.
- * <p/>
- * Missing properties are set to null and will not filter.
- *
- * @param props A properties object previously filled by {@link #saveProperties(Properties)}.
- * If null, a default empty {@link ArchFilter} is created.
- */
- public ArchFilter(@Nullable Properties props) {
- HostOs hostOs = null;
- BitSize hostBits = null;
- BitSize jvmBits = null;
- NoPreviewRevision minJvmVers = null;
-
- if (props != null) {
- hostOs = HostOs .fromXmlName(props.getProperty(PROP_HOST_OS));
- hostBits = BitSize.fromXmlName(props.getProperty(PROP_HOST_BITS));
- jvmBits = BitSize.fromXmlName(props.getProperty(PROP_JVM_BITS));
-
- try {
- minJvmVers = NoPreviewRevision.parseRevision(props.getProperty(PROP_MIN_JVM_VERSION));
- } catch (NumberFormatException ignore) {}
-
- // Backward compatibility with older PROP_OS and PROP_ARCH values
- if (!props.containsKey(PROP_HOST_OS) && props.containsKey(LEGACY_PROP_OS)) {
- hostOs = HostOs.fromXmlName(props.getProperty(LEGACY_PROP_OS));
- }
- if (!props.containsKey(PROP_HOST_BITS) &&
- !props.containsKey(PROP_HOST_BITS) &&
- props.containsKey(LEGACY_PROP_ARCH)) {
- // We'll only handle the typical x86 and x86_64 values of the old PROP_ARCH
- // value and ignore the PPC value. "Any" is equivalent to keeping the new
- // attributes to null.
- String v = props.getProperty(LEGACY_PROP_ARCH).toLowerCase();
-
- if (v.indexOf("x86_64") > 0) {
- // JVM in 64-bit x86_64 mode so host-bits should be 64 too.
- hostBits = jvmBits = BitSize._64;
- } else if (v.indexOf("x86") > 0) {
- // JVM in 32-bit x86 mode, but host-bits could be either 32 or 64
- // so we don't set this one.
- jvmBits = BitSize._32;
- }
- }
- }
-
- mHostOs = hostOs;
- mHostBits = hostBits;
- mJvmBits = jvmBits;
- mMinJvmVersion = minJvmVers;
- }
-
- /** @return the host OS or null if there's no limitation for this package. */
- @Nullable
- public HostOs getHostOS() {
- return mHostOs;
- }
-
- /** @return the host bit size or null if there's no limitation for this package. */
- @Nullable
- public BitSize getHostBits() {
- return mHostBits;
- }
-
- /** @return the JVM bit size or null if there's no limitation for this package. */
- @Nullable
- public BitSize getJvmBits() {
- return mJvmBits;
- }
-
- /** @return the minimal JVM version required by this package
- * or null if there's no limitation for this package. */
- @Nullable
- public NoPreviewRevision getMinJvmVersion() {
- return mMinJvmVersion;
- }
-
- /**
- * Checks whether {@code this} {@link ArchFilter} is compatible with the right-hand side one.
- * <p/>
- * Typically this is used to check whether "this downloaded package is compatible with the
- * current architecture", which would be expressed as:
- * <pre>
- * DownloadedArchive.filter.isCompatibleWith(ArhFilter.getCurrent())
- * </pre>
- * For the host OS & bit size attribute, if the attributes are non-null they must be equal.
- * For the min-jvm-version, "this" version (the package we want to install) needs to be lower
- * or equal to the "required" (current host) version.
- *
- * @param required The requirements to meet.
- * @return True if this filter meets or exceeds the given requirements.
- */
- public boolean isCompatibleWith(@NonNull ArchFilter required) {
- if (mHostOs != null
- && required.mHostOs != null
- && !mHostOs.equals(required.mHostOs)) {
- return false;
- }
-
- if (mHostBits != null
- && required.mHostBits != null
- && !mHostBits.equals(required.mHostBits)) {
- return false;
- }
-
- if (mJvmBits != null
- && required.mJvmBits != null
- && !mJvmBits.equals(required.mJvmBits)) {
- return false;
- }
-
- if (mMinJvmVersion != null
- && required.mMinJvmVersion != null
- && mMinJvmVersion.compareTo(required.mMinJvmVersion) > 0) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns an {@link ArchFilter} that represents the current host platform.
- * @return an {@link ArchFilter} that represents the current host platform.
- */
- @NonNull
- public static ArchFilter getCurrent() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- HostOs hostOS = null;
- if (os.startsWith("Mac")) { //$NON-NLS-1$
- hostOS = HostOs.MACOSX;
- } else if (os.startsWith("Windows")) { //$NON-NLS-1$
- hostOS = HostOs.WINDOWS;
- } else if (os.startsWith("Linux")) { //$NON-NLS-1$
- hostOS = HostOs.LINUX;
- }
-
- BitSize jvmBits;
- String arch = System.getProperty("os.arch"); //$NON-NLS-1$
-
- if (arch.equalsIgnoreCase("x86_64") || //$NON-NLS-1$
- arch.equalsIgnoreCase("ia64") || //$NON-NLS-1$
- arch.equalsIgnoreCase("amd64")) { //$NON-NLS-1$
- jvmBits = BitSize._64;
- } else {
- jvmBits = BitSize._32;
- }
-
- // TODO figure out the host bit size.
- // When jvmBits is 64 we know it's surely 64
- // but that's not necessarily obvious when jvmBits is 32.
- BitSize hostBits = jvmBits;
-
- NoPreviewRevision minJvmVersion = null;
- String javav = System.getProperty("java.version"); //$NON-NLS-1$
- // java Version is typically in the form "1.2.3_45" and we just need to keep up to "1.2.3"
- // since our revision numbers are in 3-parts form (1.2.3).
- Pattern p = Pattern.compile("((\\d+)(\\.\\d+)?(\\.\\d+)?).*"); //$NON-NLS-1$
- Matcher m = p.matcher(javav);
- if (m.matches()) {
- minJvmVersion = NoPreviewRevision.parseRevision(m.group(1));
- }
-
- return new ArchFilter(hostOS, hostBits, jvmBits, minJvmVersion);
- }
-
- /**
- * Save this {@link ArchFilter} attributes into the the given {@link Properties} object.
- * These properties can later be given to the constructor that takes a {@link Properties} object.
- * <p/>
- * Null attributes are not saved in the properties.
- *
- * @param props A non-null properties object to fill with non-null attributes.
- */
- void saveProperties(@NonNull Properties props) {
- if (mHostOs != null) {
- props.setProperty(PROP_HOST_OS, mHostOs.getXmlName());
- }
- if (mHostBits != null) {
- props.setProperty(PROP_HOST_BITS, mHostBits.getXmlName());
- }
- if (mJvmBits != null) {
- props.setProperty(PROP_JVM_BITS, mJvmBits.getXmlName());
- }
- if (mMinJvmVersion != null) {
- props.setProperty(PROP_MIN_JVM_VERSION, mMinJvmVersion.toShortString());
- }
- }
-
- /** String for debug purposes. */
- @Override
- public String toString() {
- return "<ArchFilter mHostOs=" + mHostOs +
- ", mHostBits=" + mHostBits
- + ", mJvmBits=" + mJvmBits +
- ", mMinJvmVersion=" + mMinJvmVersion + ">";
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mHostOs == null) ? 0 : mHostOs.hashCode());
- result = prime * result + ((mHostBits == null) ? 0 : mHostBits.hashCode());
- result = prime * result + ((mJvmBits == null) ? 0 : mJvmBits.hashCode());
- result = prime * result + ((mMinJvmVersion == null) ? 0 : mMinJvmVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- ArchFilter other = (ArchFilter) obj;
- if (mHostBits != other.mHostBits) {
- return false;
- }
- if (mHostOs != other.mHostOs) {
- return false;
- }
- if (mJvmBits != other.mJvmBits) {
- return false;
- }
- if (mMinJvmVersion == null) {
- if (other.mMinJvmVersion != null) {
- return false;
- }
- } else if (!mMinJvmVersion.equals(other.mMinJvmVersion)) {
- return false;
- }
- return true;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/Archive.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/Archive.java
deleted file mode 100755
index 2e60dbb..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/Archive.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.archives;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.FileOp;
-
-import java.io.File;
-import java.util.Properties;
-
-
-/**
- * A {@link Archive} is the base class for "something" that can be downloaded from
- * the SDK repository.
- * <p/>
- * A package has some attributes (revision, description) and a list of archives
- * which represent the downloadable bits.
- * <p/>
- * Packages are offered by a {@link SdkSource} (a download site).
- * The {@link ArchiveInstaller} takes care of downloading, unpacking and installing an archive.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
-*/
- at Deprecated
-public class Archive implements IDescription, Comparable<Archive> {
-
- private final String mUrl;
- private final long mSize;
- private final String mChecksum;
- private final ChecksumType mChecksumType = ChecksumType.SHA1;
- private final Package mPackage;
- private final String mLocalOsPath;
- private final boolean mIsLocal;
- private final ArchFilter mArchFilter;
-
- /**
- * Creates a new remote archive.
- * This is typically called when inflating a remote-package info from XML meta-data.
- *
- * @param pkg The package that contains this archive. Typically not null.
- * @param archFilter The {@link ArchFilter} for the archive. Typically not null.
- * @param url The URL where the archive is available.
- * Typically not null but code should be able to handles both.
- * @param size The expected size in bytes of the archive to download.
- * @param checksum The expected checksum string of the archive. Currently only the
- * {@link ChecksumType#SHA1} format is supported.
- */
- public Archive(@Nullable Package pkg,
- @Nullable ArchFilter archFilter,
- @Nullable String url,
- long size,
- @NonNull String checksum) {
- mPackage = pkg;
- mArchFilter = archFilter != null ? archFilter : new ArchFilter(null);
- mUrl = url == null ? null : url.trim();
- mLocalOsPath = null;
- mSize = size;
- mChecksum = checksum;
- mIsLocal = false;
- }
-
- /**
- * Creates a new local archive.
- * This is typically called when inflating a local-package info by reading a local
- * source.properties file. In this case a few properties like the URL, checksum and
- * size are not defined.
- *
- * @param pkg The package that contains this archive. Cannot be null.
- * @param props A set of properties. Can be null.
- * @param localOsPath The OS path where the archive is installed if this represents a
- * local package. Null for a remote package.
- */
- @VisibleForTesting(visibility=Visibility.PACKAGE)
- public Archive(@NonNull Package pkg,
- @Nullable Properties props,
- @Nullable String localOsPath) {
- mPackage = pkg;
- mArchFilter = new ArchFilter(props);
- mUrl = null;
- mLocalOsPath = localOsPath;
- mSize = 0;
- mChecksum = "";
- mIsLocal = localOsPath != null;
- }
-
- /**
- * Save the properties of the current archive in the give {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- void saveProperties(@NonNull Properties props) {
- mArchFilter.saveProperties(props);
- }
-
- /**
- * Returns true if this is a locally installed archive.
- * Returns false if this is a remote archive that needs to be downloaded.
- */
- public boolean isLocal() {
- return mIsLocal;
- }
-
- /**
- * Returns the package that created and owns this archive.
- * It should generally not be null.
- */
- @Nullable
- public Package getParentPackage() {
- return mPackage;
- }
-
- /**
- * Returns the archive size, an int > 0.
- * Size will be 0 if this a local installed folder of unknown size.
- */
- public long getSize() {
- return mSize;
- }
-
- /**
- * Returns the SHA1 archive checksum, as a 40-char hex.
- * Can be empty but not null for local installed folders.
- */
- @NonNull
- public String getChecksum() {
- return mChecksum;
- }
-
- /**
- * Returns the checksum type, always {@link ChecksumType#SHA1} right now.
- */
- @NonNull
- public ChecksumType getChecksumType() {
- return mChecksumType;
- }
-
- /**
- * Returns the download archive URL, either absolute or relative to the repository xml.
- * Always return null for a local installed folder.
- * @see #getLocalOsPath()
- */
- @Nullable
- public String getUrl() {
- return mUrl;
- }
-
- /**
- * Returns the local OS folder where a local archive is installed.
- * Always return null for remote archives.
- * @see #getUrl()
- */
- @Nullable
- public String getLocalOsPath() {
- return mLocalOsPath;
- }
-
- /**
- * Returns the architecture filter.
- * This non-null filter indicates which host/jvm this archive is compatible with.
- */
- @NonNull
- public ArchFilter getArchFilter() {
- return mArchFilter;
- }
-
- /**
- * Generates a description of the {@link ArchFilter} supported by this archive.
- */
- public String getOsDescription() {
- StringBuilder sb = new StringBuilder();
-
- HostOs hos = mArchFilter.getHostOS();
- sb.append(hos == null ? "any OS" : hos.getUiName());
-
- BitSize jvmBits = mArchFilter.getJvmBits();
- if (jvmBits != null) {
- sb.append(", JVM ").append(jvmBits.getSize()).append("-bits");
- }
-
- BitSize hostBits = mArchFilter.getJvmBits();
- if (hostBits != null) {
- sb.append(", Host ").append(hostBits.getSize()).append("-bits");
- }
-
- return sb.toString();
- }
-
- /**
- * Returns the short description of the source, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging.
- * For UI display, use the {@link IDescription} interface.
- */
- @Override
- public String toString() {
- String s = getShortDescription();
- if (s != null) {
- return s;
- }
- return super.toString();
- }
-
- /**
- * Generates a short description for this archive.
- */
- @Override
- public String getShortDescription() {
- return String.format("Archive for %1$s", getOsDescription());
- }
-
- /**
- * Generates a longer description for this archive.
- */
- @Override
- public String getLongDescription() {
- return String.format("%1$s\n%2$s\n%3$s",
- getShortDescription(),
- getSizeDescription(),
- getSha1Description());
- }
-
- public String getSizeDescription() {
- long size = getSize();
- String sizeStr;
- if (size < 1024) {
- sizeStr = String.format("%d Bytes", size);
- } else if (size < 1024 * 1024) {
- sizeStr = String.format("%d KiB", Math.round(size / 1024.0));
- } else if (size < 1024 * 1024 * 1024) {
- sizeStr = String.format("%.1f MiB",
- Math.round(10.0 * size / (1024 * 1024.0))/ 10.0);
- } else {
- sizeStr = String.format("%.1f GiB",
- Math.round(10.0 * size / (1024 * 1024 * 1024.0))/ 10.0);
- }
-
- return String.format("Size: %1$s", sizeStr);
- }
-
- public String getSha1Description() {
- return String.format("SHA1: %1$s", getChecksum());
- }
-
- /**
- * Returns true if this archive can be installed on the current platform.
- */
- public boolean isCompatible() {
- ArchFilter current = ArchFilter.getCurrent();
- return mArchFilter.isCompatibleWith(current);
- }
-
- /**
- * Delete the archive folder if this is a local archive.
- */
- public void deleteLocal() {
- if (isLocal()) {
- new FileOp().deleteFileOrFolder(new File(getLocalOsPath()));
- }
- }
-
- /**
- * Archives are compared using their {@link Package} ordering.
- *
- * @see Package#compareTo(Package)
- */
- @Override
- public int compareTo(Archive rhs) {
- if (mPackage != null && rhs != null) {
- return mPackage.compareTo(rhs.getParentPackage());
- }
- return 0;
- }
-
- /**
- * Note: An {@link Archive}'s hash code does NOT depend on the parent {@link Package} hash code.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mArchFilter == null) ? 0 : mArchFilter.hashCode());
- result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode());
- result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode());
- result = prime * result + (mIsLocal ? 1231 : 1237);
- result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode());
- result = prime * result + (int) (mSize ^ (mSize >>> 32));
- result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode());
- return result;
- }
-
- /**
- * Note: An {@link Archive}'s equality does NOT depend on the parent {@link Package} equality.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof Archive)) {
- return false;
- }
- Archive other = (Archive) obj;
- if (mArchFilter == null) {
- if (other.mArchFilter != null) {
- return false;
- }
- } else if (!mArchFilter.equals(other.mArchFilter)) {
- return false;
- }
- if (mChecksum == null) {
- if (other.mChecksum != null) {
- return false;
- }
- } else if (!mChecksum.equals(other.mChecksum)) {
- return false;
- }
- if (mChecksumType == null) {
- if (other.mChecksumType != null) {
- return false;
- }
- } else if (!mChecksumType.equals(other.mChecksumType)) {
- return false;
- }
- if (mIsLocal != other.mIsLocal) {
- return false;
- }
- if (mLocalOsPath == null) {
- if (other.mLocalOsPath != null) {
- return false;
- }
- } else if (!mLocalOsPath.equals(other.mLocalOsPath)) {
- return false;
- }
- if (mSize != other.mSize) {
- return false;
- }
- if (mUrl == null) {
- if (other.mUrl != null) {
- return false;
- }
- } else if (!mUrl.equals(other.mUrl)) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
deleted file mode 100755
index d4aa340..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
+++ /dev/null
@@ -1,1220 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.archives;
-
-import com.android.SdkConstants;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.CanceledByUserException;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.utils.GrabProcessOutput;
-import com.android.utils.GrabProcessOutput.IProcessOutput;
-import com.android.utils.GrabProcessOutput.Wait;
-import com.android.utils.Pair;
-
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.apache.commons.compress.archivers.zip.ZipFile;
-import org.apache.http.Header;
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.message.BasicHeader;
-
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-/**
- * Performs the work of installing a given {@link Archive}.
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class ArchiveInstaller {
-
- private static final String PROP_STATUS_CODE = "StatusCode"; //$NON-NLS-1$
- public static final String ENV_VAR_IGNORE_COMPAT = "ANDROID_SDK_IGNORE_COMPAT"; //$NON-NLS-1$
-
- public static final int NUM_MONITOR_INC = 100;
-
- /** The current {@link FileOp} to use. Never null. */
- private final IFileOp mFileOp;
-
- /**
- * Generates an {@link ArchiveInstaller} that relies on the default {@link FileOp}.
- */
- public ArchiveInstaller() {
- mFileOp = new FileOp();
- }
-
- /**
- * Generates an {@link ArchiveInstaller} that relies on the given {@link FileOp}.
- *
- * @param fileUtils An alternate version of {@link FileOp} to use for file operations.
- */
- protected ArchiveInstaller(IFileOp fileUtils) {
- mFileOp = fileUtils;
- }
-
- /** Returns current {@link FileOp} to use. Never null. */
- protected IFileOp getFileOp() {
- return mFileOp;
- }
-
- /**
- * Install this {@link ArchiveReplacement}s.
- * A "replacement" is composed of the actual new archive to install
- * (c.f. {@link ArchiveReplacement#getNewArchive()} and an <em>optional</em>
- * archive being replaced (c.f. {@link ArchiveReplacement#getReplaced()}.
- * In the case of a new install, the later should be null.
- * <p/>
- * The new archive to install will be skipped if it is incompatible.
- *
- * @return True if the archive was installed, false otherwise.
- */
- public boolean install(ArchiveReplacement archiveInfo,
- String osSdkRoot,
- boolean forceHttp,
- SdkManager sdkManager,
- DownloadCache cache,
- ITaskMonitor monitor) {
-
- Archive newArchive = archiveInfo.getNewArchive();
- Package pkg = newArchive.getParentPackage();
-
- String name = pkg.getShortDescription();
-
- if (newArchive.isLocal()) {
- // This should never happen.
- monitor.log("Skipping already installed archive: %1$s for %2$s",
- name,
- newArchive.getOsDescription());
- return false;
- }
-
- // In detail mode, give us a way to force install of incompatible archives.
- boolean checkIsCompatible = System.getenv(ENV_VAR_IGNORE_COMPAT) == null;
-
- if (checkIsCompatible && !newArchive.isCompatible()) {
- monitor.log("Skipping incompatible archive: %1$s for %2$s",
- name,
- newArchive.getOsDescription());
- return false;
- }
-
- Pair<File, File> files = downloadFile(newArchive, osSdkRoot, cache, monitor, forceHttp);
- File tmpFile = files == null ? null : files.getFirst();
- File propsFile = files == null ? null : files.getSecond();
- if (tmpFile != null) {
- // Unarchive calls the pre/postInstallHook methods.
- if (unarchive(archiveInfo, osSdkRoot, tmpFile, sdkManager, monitor)) {
- monitor.log("Installed %1$s", name);
- // Delete the temp archive if it exists, only on success
- mFileOp.deleteFileOrFolder(tmpFile);
- mFileOp.deleteFileOrFolder(propsFile);
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Downloads an archive and returns the temp file with it.
- * Caller is responsible with deleting the temp file when done.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Pair<File, File> downloadFile(Archive archive,
- String osSdkRoot,
- DownloadCache cache,
- ITaskMonitor monitor,
- boolean forceHttp) {
-
- String pkgName = archive.getParentPackage().getShortDescription();
- monitor.setDescription("Downloading %1$s", pkgName);
- monitor.log("Downloading %1$s", pkgName);
-
- String link = archive.getUrl();
- if (!link.startsWith("http://") //$NON-NLS-1$
- && !link.startsWith("https://") //$NON-NLS-1$
- && !link.startsWith("ftp://")) { //$NON-NLS-1$
- // Make the URL absolute by prepending the source
- Package pkg = archive.getParentPackage();
- SdkSource src = pkg.getParentSource();
- if (src == null) {
- monitor.logError("Internal error: no source for archive %1$s", pkgName);
- return null;
- }
-
- // take the URL to the repository.xml and remove the last component
- // to get the base
- String repoXml = src.getUrl();
- int pos = repoXml.lastIndexOf('/');
- String base = repoXml.substring(0, pos + 1);
-
- link = base + link;
- }
-
- if (forceHttp) {
- link = link.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- // Get the basename of the file we're downloading, i.e. the last component
- // of the URL
- int pos = link.lastIndexOf('/');
- String base = link.substring(pos + 1);
-
- // Rather than create a real temp file in the system, we simply use our
- // temp folder (in the SDK base folder) and use the archive name for the
- // download. This allows us to reuse or continue downloads.
-
- File tmpFolder = getTempFolder(osSdkRoot);
- if (!mFileOp.isDirectory(tmpFolder)) {
- if (mFileOp.isFile(tmpFolder)) {
- mFileOp.deleteFileOrFolder(tmpFolder);
- }
- if (!mFileOp.mkdirs(tmpFolder)) {
- monitor.logError("Failed to create directory %1$s", tmpFolder.getPath());
- return null;
- }
- }
- File tmpFile = new File(tmpFolder, base);
-
- // property file were we'll keep partial/resume information for reuse.
- File propsFile = new File(tmpFolder, base + ".inf"); //$NON-NLS-1$
-
- // if the file exists, check its checksum & size. Use it if complete
- if (mFileOp.exists(tmpFile)) {
- if (mFileOp.length(tmpFile) == archive.getSize()) {
- String chksum = ""; //$NON-NLS-1$
- try {
- chksum = fileChecksum(archive.getChecksumType().getMessageDigest(),
- tmpFile,
- monitor);
- } catch (NoSuchAlgorithmException e) {
- // Ignore.
- }
- if (chksum.equalsIgnoreCase(archive.getChecksum())) {
- // File is good, let's use it.
- return Pair.of(tmpFile, propsFile);
- } else {
- // The file has the right size but the wrong content.
- // Just remove it and this will trigger a full download below.
- mFileOp.deleteFileOrFolder(tmpFile);
- }
- }
- }
-
- Header[] resumeHeaders = preparePartialDownload(archive, tmpFile, propsFile);
-
- if (fetchUrl(archive, resumeHeaders, tmpFile, propsFile, link, pkgName, cache, monitor)) {
- // Fetching was successful, let's use this file.
- return Pair.of(tmpFile, propsFile);
- }
- return null;
- }
-
- /**
- * Prepares to do a partial/resume download.
- *
- * @param archive The archive we're trying to download.
- * @param tmpFile The destination file to download (e.g. something.zip)
- * @param propsFile A properties file generated by the last partial download (e.g. .zip.inf)
- * @return Null in case we should perform a full download, or a set of headers
- * to resume a partial download.
- */
- private Header[] preparePartialDownload(Archive archive, File tmpFile, File propsFile) {
- // We need both the destination file and its properties to do a resume.
- if (mFileOp.isFile(tmpFile) && mFileOp.isFile(propsFile)) {
- // The caller already checked the case were the destination file has the
- // right size _and_ checksum, so we know at this point one of them is wrong
- // here.
- // We can obviously only resume a file if its size is smaller than expected.
- if (mFileOp.length(tmpFile) < archive.getSize()) {
- Properties props = mFileOp.loadProperties(propsFile);
-
- List<Header> headers = new ArrayList<Header>(2);
- headers.add(new BasicHeader(HttpHeaders.RANGE,
- String.format("bytes=%d-", mFileOp.length(tmpFile))));
-
- // Don't use the properties if there's not at least a 200 or 206 code from
- // the last download.
- int status = 0;
- try {
- status = Integer.parseInt(props.getProperty(PROP_STATUS_CODE));
- } catch (Exception ignore) {}
-
- if (status == HttpStatus.SC_OK || status == HttpStatus.SC_PARTIAL_CONTENT) {
- // Do we have an ETag and/or a Last-Modified?
- String etag = props.getProperty(HttpHeaders.ETAG);
- String lastMod = props.getProperty(HttpHeaders.LAST_MODIFIED);
-
- if (etag != null && !etag.isEmpty()) {
- headers.add(new BasicHeader(HttpHeaders.IF_MATCH, etag));
- } else if (lastMod != null && !lastMod.isEmpty()) {
- headers.add(new BasicHeader(HttpHeaders.IF_MATCH, lastMod));
- }
-
- return headers.toArray(new Header[headers.size()]);
- }
- }
- }
-
- // Existing file is either of different size or content.
- // Remove the existing file and request a full download.
- mFileOp.deleteFileOrFolder(tmpFile);
- mFileOp.deleteFileOrFolder(propsFile);
-
- return null;
- }
-
- /**
- * Computes the SHA-1 checksum of the content of the given file.
- * Returns an empty string on error (rather than null).
- */
- private String fileChecksum(MessageDigest digester, File tmpFile, ITaskMonitor monitor) {
- InputStream is = null;
- try {
- is = new FileInputStream(tmpFile);
-
- byte[] buf = new byte[65536];
- int n;
-
- while ((n = is.read(buf)) >= 0) {
- if (n > 0) {
- digester.update(buf, 0, n);
- }
- }
-
- return getDigestChecksum(digester);
-
- } catch (FileNotFoundException e) {
- // The FNF message is just the URL. Make it a bit more useful.
- monitor.logError("File not found: %1$s", e.getMessage());
-
- } catch (Exception e) {
- monitor.logError("%1$s", e.getMessage()); //$NON-NLS-1$
-
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- // pass
- }
- }
- }
-
- return ""; //$NON-NLS-1$
- }
-
- /**
- * Returns the SHA-1 from a {@link MessageDigest} as an hex string
- * that can be compared with {@link Archive#getChecksum()}.
- */
- private String getDigestChecksum(MessageDigest digester) {
- int n;
- // Create an hex string from the digest
- byte[] digest = digester.digest();
- n = digest.length;
- String hex = "0123456789abcdef"; //$NON-NLS-1$
- char[] hexDigest = new char[n * 2];
- for (int i = 0; i < n; i++) {
- int b = digest[i] & 0x0FF;
- hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
- hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
- }
-
- return new String(hexDigest);
- }
-
- /**
- * Actually performs the download.
- * Also computes the SHA1 of the file on the fly.
- * <p/>
- * Success is defined as downloading as many bytes as was expected and having the same
- * SHA1 as expected. Returns true on success or false if any of those checks fail.
- * <p/>
- * Increments the monitor by {@link #NUM_MONITOR_INC}.
- *
- * @param archive The archive we're trying to download.
- * @param resumeHeaders The headers to use for a partial resume, or null when fetching
- * a whole new file.
- * @param tmpFile The destination file to download (e.g. something.zip)
- * @param propsFile A properties file generated by the last partial download (e.g. .zip.inf)
- * @param urlString The URL as a string
- * @param pkgName The archive's package name, used for progress output.
- * @param cache The {@link DownloadCache} instance to use.
- * @param monitor The monitor to output the progress and errors.
- * @return True if we fetched the file successfully.
- * False if the download failed or was aborted.
- */
- private boolean fetchUrl(Archive archive,
- Header[] resumeHeaders,
- File tmpFile,
- File propsFile,
- String urlString,
- String pkgName,
- DownloadCache cache,
- ITaskMonitor monitor) {
-
- FileOutputStream os = null;
- InputStream is = null;
- int inc_remain = NUM_MONITOR_INC;
- try {
- Pair<InputStream, HttpResponse> result =
- cache.openDirectUrl(urlString, resumeHeaders, monitor);
-
- is = result.getFirst();
- HttpResponse resp = result.getSecond();
- int status = resp.getStatusLine().getStatusCode();
- if (status == HttpStatus.SC_NOT_FOUND) {
- throw new Exception("URL not found.");
- }
- if (is == null) {
- throw new Exception("No content.");
- }
-
-
- Properties props = new Properties();
- props.setProperty(PROP_STATUS_CODE, Integer.toString(status));
- if (resp.containsHeader(HttpHeaders.ETAG)) {
- props.setProperty(HttpHeaders.ETAG,
- resp.getFirstHeader(HttpHeaders.ETAG).getValue());
- }
- if (resp.containsHeader(HttpHeaders.LAST_MODIFIED)) {
- props.setProperty(HttpHeaders.LAST_MODIFIED,
- resp.getFirstHeader(HttpHeaders.LAST_MODIFIED).getValue());
- }
-
- try {
- mFileOp.saveProperties(propsFile, props, "## Android SDK Download."); //$NON-NLS-1$
- } catch (IOException ignore) {}
-
- // On success, status can be:
- // - 206 (Partial content), if resumeHeaders is not null (we asked for a partial
- // download, and we get partial content for that download => we'll need to append
- // to the existing file.)
- // - 200 (OK) meaning we're getting whole new content from scratch. This can happen
- // even if resumeHeaders is not null (typically means the server has a new version
- // of the file to serve.) In this case we reset the file and write from scratch.
-
- boolean append = status == HttpStatus.SC_PARTIAL_CONTENT;
- if (status != HttpStatus.SC_OK && !(append && resumeHeaders != null)) {
- throw new Exception(String.format("Unexpected HTTP Status %1$d", status));
- }
- MessageDigest digester = archive.getChecksumType().getMessageDigest();
-
- if (append) {
- // Seed the digest with the existing content.
- InputStream temp = null;
- try {
- temp = new FileInputStream(tmpFile);
-
- byte[] buf = new byte[65536];
- int n;
-
- while ((n = temp.read(buf)) >= 0) {
- if (n > 0) {
- digester.update(buf, 0, n);
- }
- }
- } catch (Exception ignore) {
- } finally {
- if (temp != null) {
- try {
- temp.close();
- } catch (IOException ignore) {}
- }
- }
- }
-
- // Open the output stream in append for a resume, or reset for a full download.
- os = new FileOutputStream(tmpFile, append);
-
- byte[] buf = new byte[65536];
- int n;
-
- long total = 0;
- long size = archive.getSize();
- if (append) {
- long len = mFileOp.length(tmpFile);
- int percent = (int) (len * 100 / size);
- size -= len;
- monitor.logVerbose(
- "Resuming %1$s download at %2$d (%3$d%%)", pkgName, len, percent);
- }
- long inc = size / NUM_MONITOR_INC;
- long next_inc = inc;
-
- long startMs = System.currentTimeMillis();
- long nextMs = startMs + 2000; // start update after 2 seconds
-
- while ((n = is.read(buf)) >= 0) {
- if (n > 0) {
- os.write(buf, 0, n);
- digester.update(buf, 0, n);
- }
-
- long timeMs = System.currentTimeMillis();
-
- total += n;
- if (total >= next_inc) {
- monitor.incProgress(1);
- inc_remain--;
- next_inc += inc;
- }
-
- if (timeMs > nextMs) {
- long delta = timeMs - startMs;
- if (total > 0 && delta > 0) {
- // percent left to download
- int percent = (int) (100 * total / size);
- // speed in KiB/s
- float speed = (float)total / (float)delta * (1000.f / 1024.f);
- // time left to download the rest at the current KiB/s rate
- int timeLeft = (speed > 1e-3) ?
- (int)(((size - total) / 1024.0f) / speed) :
- 0;
- String timeUnit = "seconds";
- if (timeLeft > 120) {
- timeUnit = "minutes";
- timeLeft /= 60;
- }
-
- monitor.setDescription(
- "Downloading %1$s (%2$d%%, %3$.0f KiB/s, %4$d %5$s left)",
- pkgName,
- percent,
- speed,
- timeLeft,
- timeUnit);
- }
- nextMs = timeMs + 1000; // update every second
- }
-
- if (monitor.isCancelRequested()) {
- monitor.log("Download aborted by user at %1$d bytes.", total);
- return false;
- }
-
- }
-
- if (total != size) {
- monitor.logError(
- "Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
- size, total);
- return false;
- }
-
- // Create an hex string from the digest
- String actual = getDigestChecksum(digester);
- String expected = archive.getChecksum();
- if (!actual.equalsIgnoreCase(expected)) {
- monitor.logError("Download finished with wrong checksum. Expected %1$s, got %2$s.",
- expected, actual);
- return false;
- }
-
- return true;
-
- } catch (CanceledByUserException e) {
- // HTTP Basic Auth or NTLM login was canceled by user.
- // Don't output an error in the log.
-
- } catch (FileNotFoundException e) {
- // The FNF message is just the URL. Make it a bit more useful.
- monitor.logError("URL not found: %1$s", e.getMessage());
-
- } catch (Exception e) {
- monitor.logError("Download interrupted: %1$s", e.getMessage()); //$NON-NLS-1$
-
- } finally {
- if (os != null) {
- try {
- os.close();
- } catch (IOException e) {
- // pass
- }
- }
-
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- // pass
- }
- }
- if (inc_remain > 0) {
- monitor.incProgress(inc_remain);
- }
- }
-
- return false;
- }
-
- /**
- * Install the given archive in the given folder.
- */
- private boolean unarchive(ArchiveReplacement archiveInfo,
- String osSdkRoot,
- File archiveFile,
- SdkManager sdkManager,
- ITaskMonitor monitor) {
- boolean success = false;
- Archive newArchive = archiveInfo.getNewArchive();
- Package pkg = newArchive.getParentPackage();
- String pkgName = pkg.getShortDescription();
- monitor.setDescription("Installing %1$s", pkgName);
- monitor.log("Installing %1$s", pkgName);
-
- // Ideally we want to always unzip in a temp folder which name depends on the package
- // type (e.g. addon, tools, etc.) and then move the folder to the destination folder.
- // If the destination folder exists, it will be renamed and deleted at the very
- // end if everything succeeded. This provides a nice atomic swap and should leave the
- // original folder untouched in case something wrong (e.g. program crash) in the
- // middle of the unzip operation.
- //
- // However that doesn't work on Windows, we always end up not being able to move the
- // new folder. There are actually 2 cases:
- // A- A process such as a the explorer is locking the *old* folder or a file inside
- // (e.g. adb.exe)
- // In this case we really shouldn't be tried to work around it and we need to let
- // the user know and let it close apps that access that folder.
- // B- A process is locking the *new* folder. Very often this turns to be a file indexer
- // or an anti-virus that is busy scanning the new folder that we just unzipped.
- //
- // So we're going to change the strategy:
- // 1- Try to move the old folder to a temp/old folder. This might fail in case of issue A.
- // Note: for platform-tools, we can try killing adb first.
- // If it still fails, we do nothing and ask the user to terminate apps that can be
- // locking that folder.
- // 2- Once the old folder is out of the way, we unzip the archive directly into the
- // optimal new location. We no longer unzip it in a temp folder and move it since we
- // know that's what fails in most of the cases.
- // 3- If the unzip fails, remove everything and try to restore the old folder by doing
- // a *copy* in place and not a folder move (which will likely fail too).
-
- String pkgKind = pkg.getClass().getSimpleName();
-
- File destFolder = null;
- File oldDestFolder = null;
-
- try {
- // -0- Compute destination directory and check install pre-conditions
-
- destFolder = pkg.getInstallFolder(osSdkRoot, sdkManager);
-
- if (destFolder == null) {
- // this should not seriously happen.
- monitor.log("Failed to compute installation directory for %1$s.", pkgName);
- return false;
- }
-
- if (!pkg.preInstallHook(newArchive, monitor, osSdkRoot, destFolder)) {
- monitor.log("Skipping archive: %1$s", pkgName);
- return false;
- }
-
- // -1- move old folder.
-
- if (mFileOp.exists(destFolder)) {
- // Create a new temp/old dir
- if (oldDestFolder == null) {
- oldDestFolder = getNewTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
- }
- if (oldDestFolder == null) {
- // this should not seriously happen.
- monitor.logError("Failed to find a temp directory in %1$s.", osSdkRoot);
- return false;
- }
-
- // Try to move the current dest dir to the temp/old one. Tell the user if it failed.
- while(true) {
- if (!moveFolder(destFolder, oldDestFolder)) {
- monitor.logError("Failed to rename directory %1$s to %2$s.",
- destFolder.getPath(), oldDestFolder.getPath());
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- boolean tryAgain = true;
-
- tryAgain = windowsDestDirLocked(osSdkRoot, destFolder, monitor);
-
- if (tryAgain) {
- // loop, trying to rename the temp dir into the destination
- continue;
- } else {
- return false;
- }
- }
- }
- break;
- }
- }
-
- assert !mFileOp.exists(destFolder);
-
- // -2- Unzip new content directly in place.
-
- if (!mFileOp.mkdirs(destFolder)) {
- monitor.logError("Failed to create directory %1$s", destFolder.getPath());
- return false;
- }
-
- if (!unzipFolder(archiveInfo,
- archiveFile,
- destFolder,
- monitor)) {
- return false;
- }
-
- if (!generateSourceProperties(newArchive, destFolder)) {
- monitor.logError("Failed to generate source.properties in directory %1$s",
- destFolder.getPath());
- return false;
- }
-
- // In case of success, if we were replacing an archive
- // and the older one had a different path, remove it now.
- Archive oldArchive = archiveInfo.getReplaced();
- if (oldArchive != null && oldArchive.isLocal()) {
- String oldPath = oldArchive.getLocalOsPath();
- File oldFolder = oldPath == null ? null : new File(oldPath);
- if (oldFolder == null && oldArchive.getParentPackage() != null) {
- oldFolder = oldArchive.getParentPackage().getInstallFolder(
- osSdkRoot, sdkManager);
- }
- if (oldFolder != null && mFileOp.exists(oldFolder) &&
- !oldFolder.equals(destFolder)) {
- monitor.logVerbose("Removing old archive at %1$s", oldFolder.getAbsolutePath());
- mFileOp.deleteFileOrFolder(oldFolder);
- }
- }
-
- success = true;
- pkg.postInstallHook(newArchive, monitor, destFolder);
- return true;
-
- } finally {
- if (!success) {
- // In case of failure, we try to restore the old folder content.
- if (oldDestFolder != null) {
- restoreFolder(oldDestFolder, destFolder);
- }
-
- // We also call the postInstallHool with a null directory to give a chance
- // to the archive to cleanup after preInstallHook.
- pkg.postInstallHook(newArchive, monitor, null /*installDir*/);
- }
-
- // Cleanup if the unzip folder is still set.
- mFileOp.deleteFileOrFolder(oldDestFolder);
- }
- }
-
- private boolean windowsDestDirLocked(
- String osSdkRoot,
- File destFolder,
- final ITaskMonitor monitor) {
- String msg = null;
-
- assert SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS;
-
- File findLockExe = FileOp.append(
- osSdkRoot, SdkConstants.FD_TOOLS, SdkConstants.FD_LIB, SdkConstants.FN_FIND_LOCK);
-
- if (mFileOp.exists(findLockExe)) {
- try {
- final StringBuilder result = new StringBuilder();
- String command[] = new String[] {
- findLockExe.getAbsolutePath(),
- destFolder.getAbsolutePath()
- };
- Process process = Runtime.getRuntime().exec(command);
- int retCode = GrabProcessOutput.grabProcessOutput(
- process,
- Wait.WAIT_FOR_READERS,
- new IProcessOutput() {
- @Override
- public void out(@Nullable String line) {
- if (line != null) {
- result.append(line).append("\n");
- }
- }
-
- @Override
- public void err(@Nullable String line) {
- if (line != null) {
- monitor.logError("[find_lock] Error: %1$s", line);
- }
- }
- });
-
- if (retCode == 0 && result.length() > 0) {
- // TODO create a better dialog
-
- String found = result.toString().trim();
- monitor.logError("[find_lock] Directory locked by %1$s", found);
-
- TreeSet<String> apps = new TreeSet<String>(Arrays.asList(
- found.split(Pattern.quote(";")))); //$NON-NLS-1$
- StringBuilder appStr = new StringBuilder();
- for (String app : apps) {
- appStr.append("\n - ").append(app.trim()); //$NON-NLS-1$
- }
-
- msg = String.format(
- "-= Warning ! =-\n" +
- "The following processes: %1$s\n" +
- "are locking the following directory: \n" +
- " %2$s\n" +
- "Please close these applications so that the installation can continue.\n" +
- "When ready, press YES to try again.",
- appStr.toString(),
- destFolder.getPath());
- }
-
- } catch (Exception e) {
- monitor.error(e, "[find_lock failed]");
- }
-
-
- }
-
- if (msg == null) {
- // Old way: simply display a generic text and let user figure it out.
- msg = String.format(
- "-= Warning ! =-\n" +
- "A folder failed to be moved. On Windows this " +
- "typically means that a program is using that folder (for " +
- "example Windows Explorer or your anti-virus software.)\n" +
- "Please momentarily deactivate your anti-virus software or " +
- "close any running programs that may be accessing the " +
- "directory '%1$s'.\n" +
- "When ready, press YES to try again.",
- destFolder.getPath());
- }
-
- boolean tryAgain = monitor.displayPrompt("SDK Manager: failed to install", msg);
- return tryAgain;
- }
-
- /**
- * Tries to rename/move a folder.
- * <p/>
- * Contract:
- * <ul>
- * <li> When we start, oldDir must exist and be a directory. newDir must not exist. </li>
- * <li> On successful completion, oldDir must not exists.
- * newDir must exist and have the same content. </li>
- * <li> On failure completion, oldDir must have the same content as before.
- * newDir must not exist. </li>
- * </ul>
- * <p/>
- * The simple "rename" operation on a folder can typically fail on Windows for a variety
- * of reason, in fact as soon as a single process holds a reference on a directory. The
- * most common case are the Explorer, the system's file indexer, Tortoise SVN cache or
- * an anti-virus that are busy indexing a new directory having been created.
- *
- * @param oldDir The old location to move. It must exist and be a directory.
- * @param newDir The new location where to move. It must not exist.
- * @return True if the move succeeded. On failure, we try hard to not have touched the old
- * directory in order not to loose its content.
- */
- private boolean moveFolder(File oldDir, File newDir) {
- // This is a simple folder rename that works on Linux/Mac all the time.
- //
- // On Windows this might fail if an indexer is busy looking at a new directory
- // (e.g. right after we unzip our archive), so it fails let's be nice and give
- // it a bit of time to succeed.
- for (int i = 0; i < 5; i++) {
- if (mFileOp.renameTo(oldDir, newDir)) {
- return true;
- }
- try {
- Thread.sleep(500 /*ms*/);
- } catch (InterruptedException e) {
- // ignore
- }
- }
-
- return false;
- }
-
- /**
- * Unzips a zip file into the given destination directory.
- *
- * The archive file MUST have a unique "root" folder.
- * This root folder is skipped when unarchiving.
- */
- @SuppressWarnings("unchecked")
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean unzipFolder(
- ArchiveReplacement archiveInfo,
- File archiveFile,
- File unzipDestFolder,
- ITaskMonitor monitor) {
-
- Archive newArchive = archiveInfo.getNewArchive();
- Package pkg = newArchive.getParentPackage();
- String pkgName = pkg.getShortDescription();
- long compressedSize = newArchive.getSize();
-
- ZipFile zipFile = null;
- try {
- zipFile = new ZipFile(archiveFile);
-
- // To advance the percent and the progress bar, we don't know the number of
- // items left to unzip. However we know the size of the archive and the size of
- // each uncompressed item. The zip file format overhead is negligible so that's
- // a good approximation.
- long incStep = compressedSize / NUM_MONITOR_INC;
- long incTotal = 0;
- long incCurr = 0;
- int lastPercent = 0;
-
- byte[] buf = new byte[65536];
-
- Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
- while (entries.hasMoreElements()) {
- ZipArchiveEntry entry = entries.nextElement();
-
- String name = entry.getName();
-
- // ZipFile entries should have forward slashes, but not all Zip
- // implementations can be expected to do that.
- name = name.replace('\\', '/');
-
- // Zip entries are always packages in a top-level directory (e.g. docs/index.html).
- int pos = name.indexOf('/');
- if (pos == -1) {
- // All zip entries should have a root folder.
- // This zip entry seems located at the root of the zip.
- // Rather than ignore the file, just place it at the root.
- } else if (pos == name.length() - 1) {
- // This is a zip *directory* entry in the form dir/, so essentially
- // it's the root directory of the SDK. It's safe to ignore that one
- // since we want to use our own root directory and we'll recreate
- // root directories as needed.
- // A direct consequence is that if a malformed archive has multiple
- // root directories, their content will all be merged together.
- continue;
- } else {
- // This is the expected behavior: the zip entry is in the form root/file
- // or root/dir/. We want to use our top-level directory so we drop the
- // first segment of the path name.
- name = name.substring(pos + 1);
- }
-
- File destFile = new File(unzipDestFolder, name);
-
- if (name.endsWith("/")) { //$NON-NLS-1$
- // Create directory if it doesn't exist yet. This allows us to create
- // empty directories.
- if (!mFileOp.isDirectory(destFile) && !mFileOp.mkdirs(destFile)) {
- monitor.logError("Failed to create directory %1$s",
- destFile.getPath());
- return false;
- }
- continue;
- } else if (name.indexOf('/') != -1) {
- // Otherwise it's a file in a sub-directory.
-
- // Sanity check: since we're always unzipping in a fresh temp folder
- // the destination file shouldn't already exist.
- if (mFileOp.exists(destFile)) {
- monitor.logVerbose("Duplicate file found: %1$s", name);
- }
-
- // Make sure the parent directory has been created.
- File parentDir = destFile.getParentFile();
- if (!mFileOp.isDirectory(parentDir)) {
- if (!mFileOp.mkdirs(parentDir)) {
- monitor.logError("Failed to create directory %1$s",
- parentDir.getPath());
- return false;
- }
- }
- }
-
- FileOutputStream fos = null;
- long remains = entry.getSize();
- try {
- fos = new FileOutputStream(destFile);
-
- // Java bug 4040920: do not rely on the input stream EOF and don't
- // try to read more than the entry's size.
- InputStream entryContent = zipFile.getInputStream(entry);
- int n;
- while (remains > 0 &&
- (n = entryContent.read(
- buf, 0, (int) Math.min(remains, buf.length))) != -1) {
- remains -= n;
- if (n > 0) {
- fos.write(buf, 0, n);
- }
- }
- } catch (EOFException e) {
- monitor.logError("Error uncompressing file %s. Size: %d bytes, Unwritten: %d bytes.",
- entry.getName(), entry.getSize(), remains);
- throw e;
- } finally {
- if (fos != null) {
- fos.close();
- }
- }
-
- pkg.postUnzipFileHook(newArchive, monitor, mFileOp, destFile, entry);
-
- // Increment progress bar to match. We update only between files.
- for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {
- monitor.incProgress(1);
- }
-
- int percent = (int) (100 * incTotal / compressedSize);
- if (percent != lastPercent) {
- monitor.setDescription("Unzipping %1$s (%2$d%%)", pkgName, percent);
- lastPercent = percent;
- }
-
- if (monitor.isCancelRequested()) {
- return false;
- }
- }
-
- return true;
-
- } catch (IOException e) {
- monitor.logError("Unzip failed: %1$s", e.getMessage());
-
- } finally {
- if (zipFile != null) {
- try {
- zipFile.close();
- } catch (IOException e) {
- // pass
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns an unused temp folder path in the form of osBasePath/temp/prefix.suffixNNN.
- * <p/>
- * This does not actually <em>create</em> the folder. It just scan the base path for
- * a free folder name to use and returns the file to use to reference it.
- * <p/>
- * This operation is not atomic so there's no guarantee the folder can't get
- * created in between. This is however unlikely and the caller can assume the
- * returned folder does not exist yet.
- * <p/>
- * Returns null if no such folder can be found (e.g. if all candidates exist,
- * which is rather unlikely) or if the base temp folder cannot be created.
- */
- private File getNewTempFolder(String osBasePath, String prefix, String suffix) {
- File baseTempFolder = getTempFolder(osBasePath);
-
- if (!mFileOp.isDirectory(baseTempFolder)) {
- if (mFileOp.isFile(baseTempFolder)) {
- mFileOp.deleteFileOrFolder(baseTempFolder);
- }
- if (!mFileOp.mkdirs(baseTempFolder)) {
- return null;
- }
- }
-
- for (int i = 1; i < 100; i++) {
- File folder = new File(baseTempFolder,
- String.format("%1$s.%2$s%3$02d", prefix, suffix, i)); //$NON-NLS-1$
- if (!mFileOp.exists(folder)) {
- return folder;
- }
- }
- return null;
- }
-
- /**
- * Returns the single fixed "temp" folder used by the SDK Manager.
- * This folder is always at osBasePath/temp.
- * <p/>
- * This does not actually <em>create</em> the folder.
- */
- private File getTempFolder(String osBasePath) {
- File baseTempFolder = new File(osBasePath, RepoConstants.FD_TEMP);
- return baseTempFolder;
- }
-
- /**
- * Generates a source.properties in the destination folder that contains all the infos
- * relevant to this archive, this package and the source so that we can reload them
- * locally later.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean generateSourceProperties(Archive archive, File unzipDestFolder) {
-
- // Create a version of Properties that returns a sorted key set.
- // This is used by Properties#saveProperties and should ensure the
- // properties are in a stable order. Unit tests rely on this fact.
- @SuppressWarnings("serial")
- Properties props = new Properties() {
- @Override
- public synchronized Enumeration<Object> keys() {
- Set<Object> sortedSet = new TreeSet<Object>(keySet());
- final Iterator<Object> it = sortedSet.iterator();
- return new Enumeration<Object>() {
- @Override
- public boolean hasMoreElements() {
- return it.hasNext();
- }
-
- @Override
- public Object nextElement() {
- return it.next();
- }
-
- };
- }
- };
-
- archive.saveProperties(props);
-
- Package pkg = archive.getParentPackage();
- if (pkg != null) {
- pkg.saveProperties(props);
- }
-
- try {
- mFileOp.saveProperties(
- new File(unzipDestFolder, SdkConstants.FN_SOURCE_PROP),
- props,
- "## Android Tool: Source of this archive."); //$NON-NLS-1$
- return true;
- } catch (IOException ignore) {
- return false;
- }
- }
-
- /**
- * Recursively restore srcFolder into destFolder by performing a copy of the file
- * content rather than rename/moves.
- *
- * @param srcFolder The source folder to restore.
- * @param destFolder The destination folder where to restore.
- * @return True if the folder was successfully restored, false if it was not at all or
- * only partially restored.
- */
- private boolean restoreFolder(File srcFolder, File destFolder) {
- boolean result = true;
-
- // Process sub-folders first
- File[] srcFiles = mFileOp.listFiles(srcFolder);
- if (srcFiles == null) {
- // Source does not exist. That is quite odd.
- return false;
- }
-
- if (mFileOp.isFile(destFolder)) {
- if (!mFileOp.delete(destFolder)) {
- // There's already a file in there where we want a directory and
- // we can't delete it. This is rather unexpected. Just give up on
- // that folder.
- return false;
- }
- } else if (!mFileOp.isDirectory(destFolder)) {
- mFileOp.mkdirs(destFolder);
- }
-
- // Get all the files and dirs of the current destination.
- // We are not going to clean up the destination first.
- // Instead we'll copy over and just remove any remaining files or directories.
- Set<File> destDirs = new HashSet<File>();
- Set<File> destFiles = new HashSet<File>();
- File[] files = mFileOp.listFiles(destFolder);
- if (files != null) {
- for (File f : files) {
- if (mFileOp.isDirectory(f)) {
- destDirs.add(f);
- } else {
- destFiles.add(f);
- }
- }
- }
-
- // First restore all source directories.
- for (File dir : srcFiles) {
- if (mFileOp.isDirectory(dir)) {
- File d = new File(destFolder, dir.getName());
- destDirs.remove(d);
- if (!restoreFolder(dir, d)) {
- result = false;
- }
- }
- }
-
- // Remove any remaining directories not processed above.
- for (File dir : destDirs) {
- mFileOp.deleteFileOrFolder(dir);
- }
-
- // Copy any source files over to the destination.
- for (File file : srcFiles) {
- if (mFileOp.isFile(file)) {
- File f = new File(destFolder, file.getName());
- destFiles.remove(f);
- try {
- mFileOp.copyFile(file, f);
- } catch (IOException e) {
- result = false;
- }
- }
- }
-
- // Remove any remaining files not processed above.
- for (File file : destFiles) {
- mFileOp.deleteFileOrFolder(file);
- }
-
- return result;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java
deleted file mode 100755
index 4cf85f2..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.packages.Package;
-
-
-/**
- * Represents an archive that we want to install and the archive that it is
- * going to replace, if any.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class ArchiveReplacement implements IDescription {
-
- private final Archive mNewArchive;
- private final Archive mReplaced;
-
- /**
- * Creates a new replacement where the {@code newArchive} will replace the
- * currently installed {@code replaced} archive.
- * When {@code newArchive} is not intended to replace anything (e.g. because
- * the user is installing a new package not present on her system yet), then
- * {@code replace} shall be null.
- *
- * @param newArchive A "new archive" to be installed. This is always an archive
- * that comes from a remote site. This <em>may</em> be null.
- * @param replaced An optional local archive that the new one will replace.
- * Can be null if this archive does not replace anything.
- */
- public ArchiveReplacement(Archive newArchive, Archive replaced) {
- mNewArchive = newArchive;
- mReplaced = replaced;
- }
-
- /**
- * Returns the "new archive" to be installed.
- * This <em>may</em> be null for missing archives.
- */
- public Archive getNewArchive() {
- return mNewArchive;
- }
-
- /**
- * Returns an optional local archive that the new one will replace.
- * Can be null if this archive does not replace anything.
- */
- public Archive getReplaced() {
- return mReplaced;
- }
-
- /**
- * Returns the long description of the parent package of the new archive, if not null.
- * Otherwise returns an empty string.
- */
- @Override
- public String getLongDescription() {
- if (mNewArchive != null) {
- Package p = mNewArchive.getParentPackage();
- if (p != null) {
- return p.getLongDescription();
- }
- }
- return "";
- }
-
- /**
- * Returns the short description of the parent package of the new archive, if not null.
- * Otherwise returns an empty string.
- */
- @Override
- public String getShortDescription() {
- if (mNewArchive != null) {
- Package p = mNewArchive.getParentPackage();
- if (p != null) {
- return p.getShortDescription();
- }
- }
- return "";
- }
-
- /**
- * Returns the short description of the parent package of the new archive, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging. For UI display, use the {@link IDescription}
- * interface.
- */
- @Override
- public String toString() {
- if (mNewArchive != null) {
- Package p = mNewArchive.getParentPackage();
- if (p != null) {
- return p.getShortDescription();
- }
- }
- return super.toString();
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/BitSize.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/BitSize.java
deleted file mode 100755
index 5f72c76..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/BitSize.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-/**
- * The Architecture that this archive can be downloaded on.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public enum BitSize {
- _32(32),
- _64(64);
-
- private final int mSize;
-
- BitSize(int size) {
- mSize = size;
- }
-
- /** Returns the size of the architecture. */
- public int getSize() {
- return mSize;
- }
-
- /** Returns the XML name of the bit size. */
- @NonNull
- public String getXmlName() {
- return Integer.toString(mSize);
- }
- /**
- * Returns the enum value matching the given XML name.
- * @return A valid {@link HostOs} constant or null if not a valid XML name.
- */
- @Nullable
- public static BitSize fromXmlName(@Nullable String xmlName) {
- if (xmlName != null) {
- for (BitSize v : values()) {
- if (v.getXmlName().equalsIgnoreCase(xmlName)) {
- return v;
- }
- }
- }
- return null;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ChecksumType.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ChecksumType.java
deleted file mode 100755
index 9304934..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ChecksumType.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.archives;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/**
- * The checksum type.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public enum ChecksumType {
- /** A SHA1 checksum, represented as a 40-hex string. */
- SHA1("SHA-1"); //$NON-NLS-1$
-
- private final String mAlgorithmName;
-
- /**
- * Constructs a {@link ChecksumType} with the algorithm name
- * suitable for {@link MessageDigest#getInstance(String)}.
- * <p/>
- * These names are officially documented at
- * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest
- */
- ChecksumType(String algorithmName) {
- mAlgorithmName = algorithmName;
- }
-
- /**
- * Returns a new {@link MessageDigest} instance for this checksum type.
- * @throws NoSuchAlgorithmException if this algorithm is not available.
- */
- public MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
- return MessageDigest.getInstance(mAlgorithmName);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/HostOs.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/HostOs.java
deleted file mode 100755
index 6c7d443..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/HostOs.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-
-import java.util.Locale;
-
-/**
- * The OS that this archive can be downloaded on. <br/>
- * The represents a "host" where the SDK tools and the SDK Manager can run,
- * not the Android device targets.
- * <p/>
- * The actual OS requirements for the SDK are listed at
- * <a href="http://d.android.com/sdk">http://d.android.com/sdk</a>
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public enum HostOs {
- /** Any of the Unix-like host OSes. */
- LINUX("Linux"),
- /** Any variation of MacOS X. */
- MACOSX("MacOS X"),
- /** Any variation of Windows. */
- WINDOWS("Windows");
-
- private final String mUiName;
-
- HostOs(@NonNull String uiName) {
- mUiName = uiName;
- }
-
- /**
- * Returns the UI name of the OS.
- */
- @NonNull
- public String getUiName() {
- return mUiName;
- }
-
- /**
- * Returns the XML name of the OS.
- * @returns Null, windows, macosx or linux.
- */
- @NonNull
- public String getXmlName() {
- return toString().toLowerCase(Locale.US);
- }
-
- /**
- * Returns the enum value matching the given XML name.
- * @return A valid {@link HostOs} constnat or null if not a valid XML name.
- */
- @Nullable
- public static HostOs fromXmlName(@Nullable String xmlName) {
- if (xmlName != null) {
- for (HostOs v : values()) {
- if (v.getXmlName().equalsIgnoreCase(xmlName)) {
- return v;
- }
- }
- }
- return null;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyArch.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyArch.java
deleted file mode 100755
index 51d78dc..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyArch.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-
-
-/**
- * The legacy Architecture that this archive can be downloaded on.
- * <p/>
- * This attribute was used for the <archive> element in repo schema 1-9.
- * add-on schema 1-6 and sys-img schema 1-2.
- * Starting with repo schema 10, add-on schema 7 and sys-img schema 3, this is replaced
- * by the <host-bit> and <jvm-bit> elements and {@link ArchFilter}.
- *
- * @see HostOs
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public enum LegacyArch {
- ANY("Any"),
- PPC("PowerPC"),
- X86("x86"),
- X86_64("x86_64");
-
- private final String mUiName;
-
- LegacyArch(String uiName) {
- mUiName = uiName;
- }
-
- /** Returns the UI name of the architecture. */
- public String getUiName() {
- return mUiName;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyOs.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyOs.java
deleted file mode 100755
index 2f096b4..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/LegacyOs.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-
-
-/**
- * The legacy OS that this archive can be downloaded on.
- * <p/>
- * This attribute was used for the <archive> element in repo schema 1-9.
- * add-on schema 1-6 and sys-img schema 1-2.
- * Starting with repo schema 10, add-on schema 7 and sys-img schema 3, this is replaced
- * by the <host-os> element and {@link ArchFilter}.
- *
- * @see HostOs
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public enum LegacyOs {
- ANY("Any"),
- LINUX("Linux"),
- MACOSX("MacOS X"),
- WINDOWS("Windows");
-
- private final String mUiName;
-
- LegacyOs(String uiName) {
- mUiName = uiName;
- }
-
- /** Returns the UI name of the OS. */
- public String getUiName() {
- return mUiName;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/AddonPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/AddonPackage.java
deleted file mode 100755
index f5c404d..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/AddonPackage.java
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.IAndroidTarget.OptionalLibrary;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.AddonManifestIniProps;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.android.sdklib.repository.local.LocalAddonPkgInfo;
-import com.android.utils.Pair;
-import com.google.common.collect.ImmutableList;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents an add-on XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class AddonPackage extends MajorRevisionPackage
- implements IAndroidVersionProvider, IPlatformDependency,
- IExactApiLevelDependency, ILayoutlibVersion {
-
- private final String mVendorId;
- private final String mVendorDisplay;
- private final String mNameId;
- private final String mDisplayName;
- private final AndroidVersion mVersion;
- private final IPkgDesc mPkgDesc;
-
- /**
- * The helper handling the layoutlib version.
- */
- private final LayoutlibVersionMixin mLayoutlibVersion;
-
- /** An add-on library. */
- public static class Lib {
- private final String mName;
- private final String mDescription;
-
- public Lib(String name, String description) {
- mName = name;
- mDescription = description;
- }
-
- public String getName() {
- return mName;
- }
-
- public String getDescription() {
- return mDescription;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
- result = prime * result + ((mName == null) ? 0 : mName.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof Lib)) {
- return false;
- }
- Lib other = (Lib) obj;
- if (mDescription == null) {
- if (other.mDescription != null) {
- return false;
- }
- } else if (!mDescription.equals(other.mDescription)) {
- return false;
- }
- if (mName == null) {
- if (other.mName != null) {
- return false;
- }
- } else if (!mName.equals(other.mName)) {
- return false;
- }
- return true;
- }
- }
-
- private final Lib[] mLibs;
-
- /**
- * Creates a new add-on package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public AddonPackage(
- SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- // --- name id/display ---
- // addon-4.xsd introduces the name-id, name-display, vendor-id and vendor-display.
- // These are not optional but we still need to support a fallback for older addons
- // that only provide name and vendor. If the addon provides neither set of fields,
- // it will simply not work as expected.
-
- String nameId = PackageParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME_ID);
- String nameDisp = PackageParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME_DISPLAY);
- String name = PackageParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME);
-
- // The old <name> is equivalent to the new <name-display>
- if (nameDisp.isEmpty()) {
- nameDisp = name;
- }
-
- // For a missing id, we simply use a sanitized version of the display name
- if (nameId.isEmpty()) {
- nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(!name.isEmpty() ? name : nameDisp);
- }
-
- assert !nameId.isEmpty();
- assert !nameDisp.isEmpty();
-
- mNameId = nameId.trim();
- mDisplayName = nameDisp.trim();
-
- // --- vendor id/display ---
- // Same processing for vendor id vs display
-
- String vendorId = PackageParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR_ID);
- String vendorDisp = PackageParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR_DISPLAY);
- String vendor = PackageParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR);
-
- // The old <vendor> is equivalent to the new <vendor-display>
- if (vendorDisp.isEmpty()) {
- vendorDisp = vendor;
- }
-
- // For a missing id, we simply use a sanitized version of the display vendor
- if (vendorId.isEmpty()) {
- boolean hasVendor = !vendor.isEmpty();
- vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
- }
-
- assert !vendorId.isEmpty();
- assert !vendorDisp.isEmpty();
-
- mVendorId = vendorId.trim();
- mVendorDisplay = vendorDisp.trim();
-
- // --- other attributes
-
- int apiLevel =
- PackageParserUtils.getXmlInt(packageNode, SdkAddonConstants.NODE_API_LEVEL, 0);
- mVersion = new AndroidVersion(apiLevel, null /*codeName*/);
-
- mLibs = parseLibs(
- PackageParserUtils.findChildElement(packageNode, SdkAddonConstants.NODE_LIBS));
-
- mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
-
- mPkgDesc = setDescriptions(
- PkgDesc.Builder.newAddon(mVersion, (MajorRevision) getRevision(),
- new IdDisplay(mVendorId, mVendorDisplay),
- new IdDisplay(mNameId, mDisplayName)))
- .create();
- }
-
- /**
- * Creates a new platform package based on an actual {@link IAndroidTarget} (which
- * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(IAndroidTarget target, Properties props) {
- return new AddonPackage(target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected AddonPackage(IAndroidTarget target, Properties props) {
- this(null /*source*/, target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected AddonPackage(SdkSource source, IAndroidTarget target, Properties props) {
- super( source, //source
- props, //properties
- target.getRevision(), //revision
- null, //license
- target.getDescription(), //description
- null, //descUrl
- target.getLocation() //archiveOsPath
- );
-
- // --- name id/display ---
- // addon-4.xsd introduces the name-id, name-display, vendor-id and vendor-display.
- // These are not optional but we still need to support a fallback for older addons
- // that only provide name and vendor. If the addon provides neither set of fields,
- // it will simply not work as expected.
-
- String nameId = getProperty(props, PkgProps.ADDON_NAME_ID, ""); //$NON-NLS-1$
- String nameDisp = getProperty(props, PkgProps.ADDON_NAME_DISPLAY, ""); //$NON-NLS-1$
- String name = getProperty(props, PkgProps.ADDON_NAME, target.getName());
-
- // The old <name> is equivalent to the new <name-display>
- //noinspection ConstantConditions
- if (nameDisp.isEmpty()) {
- nameDisp = name;
- }
-
- // For a missing id, we simply use a sanitized version of the display name
- //noinspection ConstantConditions
- if (nameId.isEmpty()) {
- nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(!name.isEmpty() ? name : nameDisp);
- }
-
- assert !nameId.isEmpty();
- assert !nameDisp.isEmpty();
-
- mNameId = nameId.trim();
- mDisplayName = nameDisp.trim();
-
- // --- vendor id/display ---
- // Same processing for vendor id vs display
-
- String vendorId = getProperty(props, PkgProps.ADDON_VENDOR_ID, ""); //$NON-NLS-1$
- String vendorDisp = getProperty(props, PkgProps.ADDON_VENDOR_DISPLAY, ""); //$NON-NLS-1$
- String vendor = getProperty(props, PkgProps.ADDON_VENDOR, target.getVendor());
-
- // The old <vendor> is equivalent to the new <vendor-display>
- //noinspection ConstantConditions
- if (vendorDisp.isEmpty()) {
- vendorDisp = vendor;
- }
-
- // For a missing id, we simply use a sanitized version of the display vendor
- //noinspection ConstantConditions
- if (vendorId.isEmpty()) {
- //noinspection ConstantConditions
- boolean hasVendor = !vendor.isEmpty();
- vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
- }
-
- assert !vendorId.isEmpty();
- assert !vendorDisp.isEmpty();
-
- mVendorId = vendorId.trim();
- mVendorDisplay = vendorDisp.trim();
-
- // --- other attributes
-
- mVersion = target.getVersion();
- mLayoutlibVersion = new LayoutlibVersionMixin(props);
-
- List<OptionalLibrary> optLibs = target.getAdditionalLibraries();
- if (optLibs.isEmpty()) {
- mLibs = new Lib[0];
- } else {
- mLibs = new Lib[optLibs.size()];
- for (int i = 0; i < optLibs.size(); i++) {
- OptionalLibrary optionalLibrary = optLibs.get(i);
- mLibs[i] = new Lib(optionalLibrary.getName(), optionalLibrary.getDescription());
- }
- }
-
- mPkgDesc = setDescriptions(
- PkgDesc.Builder.newAddon(mVersion, (MajorRevision) getRevision(),
- new IdDisplay(mVendorId, mVendorDisplay),
- new IdDisplay(mNameId, mDisplayName)))
- .create();
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Creates a broken addon which we know failed to load properly.
- *
- * @param archiveOsPath The absolute OS path of the addon folder.
- * @param sourceProps The properties parsed from the addon's source.properties. Can be null.
- * @param addonProps The properties parsed from the addon manifest (NOT the source.properties).
- * @param error The error indicating why this addon failed to be loaded.
- */
- public static Package createBroken(
- String archiveOsPath,
- Properties sourceProps,
- Map<String, String> addonProps,
- String error) {
-
-
- String nameId = getProperty(sourceProps, PkgProps.ADDON_NAME_ID, null);
- String nameDisp = getProperty(sourceProps, PkgProps.ADDON_NAME_DISPLAY, null);
- if (nameDisp == null) {
- nameDisp = getProperty(sourceProps, PkgProps.ADDON_NAME_DISPLAY, null);
- }
- if (nameDisp == null) {
- nameDisp = addonProps.get(AddonManifestIniProps.ADDON_NAME);
- }
- if (nameDisp == null) {
- nameDisp = "Unknown";
- }
- if (nameId == null) {
- nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(nameDisp);
- }
-
- String vendorId = getProperty(sourceProps, PkgProps.ADDON_VENDOR_ID, null);
- String vendorDisp = getProperty(sourceProps, PkgProps.ADDON_VENDOR_DISPLAY, null);
- if (vendorDisp == null) {
- vendorDisp = getProperty(sourceProps, PkgProps.ADDON_VENDOR_DISPLAY, null);
- }
- if (vendorDisp == null) {
- vendorDisp = addonProps.get(AddonManifestIniProps.ADDON_VENDOR);
- }
- if (vendorDisp == null) {
- vendorDisp = "Unknown";
- }
- if (vendorId == null) {
- vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(vendorDisp);
- }
-
- String api = addonProps.get(AddonManifestIniProps.ADDON_API);
- String revision = addonProps.get(AddonManifestIniProps.ADDON_REVISION);
-
- String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",
- nameDisp,
- vendorDisp,
- api,
- revision);
-
- String longDesc = String.format(
- "%1$s\n" +
- "[*] Addon failed to load: %2$s",
- shortDesc,
- error);
-
- int apiLevel = IExactApiLevelDependency.API_LEVEL_INVALID;
-
- try {
- apiLevel = Integer.parseInt(api);
- } catch(NumberFormatException ignore) {}
-
- int intRevision = MajorRevision.MISSING_MAJOR_REV;
- try {
- intRevision = Integer.parseInt(revision);
- } catch (NumberFormatException ignore) {}
-
- IPkgDesc desc = PkgDesc.Builder
- .newAddon(new AndroidVersion(apiLevel, null),
- new MajorRevision(intRevision),
- new IdDisplay(vendorId, vendorDisp),
- new IdDisplay(nameId, nameDisp))
- .setDescriptionShort(shortDesc)
- .create();
-
- return new BrokenPackage(null/*props*/, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- apiLevel,
- archiveOsPath,
- desc);
- }
-
- @Override
- public int getExactApiLevel() {
- return mVersion.getApiLevel();
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- mLayoutlibVersion.saveProperties(props);
-
- props.setProperty(PkgProps.ADDON_NAME_ID, mNameId);
- props.setProperty(PkgProps.ADDON_NAME_DISPLAY, mDisplayName);
- props.setProperty(PkgProps.ADDON_VENDOR_ID, mVendorId);
- props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY, mVendorDisplay);
- }
-
- /**
- * Parses a <libs> element.
- */
- private Lib[] parseLibs(Node libsNode) {
- ArrayList<Lib> libs = new ArrayList<Lib>();
-
- if (libsNode != null) {
- String nsUri = libsNode.getNamespaceURI();
- for(Node child = libsNode.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
-
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- SdkRepoConstants.NODE_LIB.equals(child.getLocalName())) {
- libs.add(parseLib(child));
- }
- }
- }
-
- return libs.toArray(new Lib[libs.size()]);
- }
-
- /**
- * Parses a <lib> element from a <libs> container.
- */
- private Lib parseLib(Node libNode) {
- return new Lib(PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),
- PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));
- }
-
- /** Returns the vendor id, a string, for add-on packages. */
- @NonNull
- public String getVendorId() {
- return mVendorId;
- }
-
- /** Returns the vendor, a string for display purposes. */
- @NonNull
- public String getDisplayVendor() {
- return mVendorDisplay;
- }
-
- /** Returns the name id, a string, for add-on packages or for libraries. */
- @NonNull
- public String getNameId() {
- return mNameId;
- }
-
- /** Returns the name, a string for display purposes. */
- @NonNull
- public String getDisplayName() {
- return mDisplayName;
- }
-
- /**
- * Returns the version of the platform dependency of this package.
- * <p/>
- * An add-on has the same {@link AndroidVersion} as the platform it depends on.
- */
- @Override @NonNull
- public AndroidVersion getAndroidVersion() {
- return mVersion;
- }
-
- /** Returns the libs defined in this add-on. Can be an empty array but not null. */
- @NonNull
- public Lib[] getLibs() {
- return mLibs;
- }
-
- /**
- * Returns the layoutlib version.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link ILayoutlibVersion#LAYOUTLIB_API_NOT_SPECIFIED} (0)
- * if the layoutlib version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-addon-2.xsd
- */
- @NonNull
- @Override
- public Pair<Integer, Integer> getLayoutlibVersion() {
- return mLayoutlibVersion.getLayoutlibVersion();
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For add-ons, we use "addon-vendor-name-N" where N is the base platform API.
- * <p/>
- * {@inheritDoc}
- */
- @NonNull
- @Override
- public String installId() {
- return encodeAddonName();
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- return String.format("%1$s%2$s",
- getDisplayName(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return String.format("%1$s, Android API %2$s, revision %3$s%4$s",
- getDisplayName(),
- mVersion.getApiString(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the <description> field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = String.format("%1$s, Android API %2$s, revision %3$s%4$s\nBy %5$s",
- getDisplayName(),
- mVersion.getApiString(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "", //$NON-NLS-2$
- getDisplayVendor());
-
- String d = getDescription();
- if (d != null && !d.isEmpty()) {
- s += '\n' + d;
- }
-
- s += String.format("\nRequires SDK Platform Android API %1$s",
- mVersion.getApiString());
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * An add-on package is typically installed in SDK/add-ons/"addon-name"-"api-level".
- * The name needs to be sanitized to be acceptable as a directory name.
- * However if we can find a different directory under SDK/add-ons that already
- * has this add-ons installed, we'll use that one.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);
-
- // First find if this add-on is already installed. If so, reuse the same directory.
- for (IAndroidTarget target : sdkManager.getTargets()) {
- if (!target.isPlatform() && target.getVersion().equals(mVersion)) {
- // Starting with addon-4.xsd, the addon source.properties differentiate
- // between ids and display strings. However the addon target which relies
- // on the manifest.ini does not so we need to cover both cases.
- // TODO fix when we get rid of manifest.ini for addons
- if ((target.getName().equals(getNameId()) &&
- target.getVendor().equals(getVendorId())) ||
- (target.getName().equals(getDisplayName()) &&
- target.getVendor().equals(getDisplayVendor()))) {
- return new File(target.getLocation());
- }
- }
- }
-
- // Compute a folder directory using the addon declared name and vendor strings.
- String name = encodeAddonName();
-
- for (int i = 0; i < 100; i++) {
- String name2 = i == 0 ? name : String.format("%s-%d", name, i); //$NON-NLS-1$
- File folder = new File(addons, name2);
- if (!folder.exists()) {
- return folder;
- }
- }
-
- // We shouldn't really get here. I mean, seriously, we tried hard enough.
- return null;
- }
-
- private String encodeAddonName() {
- String name = String.format("addon-%s-%s-%s", //$NON-NLS-1$
- getNameId(), getVendorId(), mVersion.getApiString());
- name = name.toLowerCase(Locale.US);
- name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- return name;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof AddonPackage) {
- AddonPackage newPkg = (AddonPackage)pkg;
-
- // check they are the same add-on.
- if (getNameId().equals(newPkg.getNameId()) &&
- getAndroidVersion().equals(newPkg.getAndroidVersion())) {
- // Check the vendor-id field.
- if (getVendorId().equals(newPkg.getVendorId())) {
- return true;
- }
-
- // When loading addons from the v3 schema that only had a <vendor>
- // field, the vendor field has been converted to vendor-display so
- // as a transition mechanism we should test this also.
- // TODO: in a couple iterations of the SDK Manager, remove this check
- // and only compare using the vendor-id field.
- return getDisplayVendor().equals(newPkg.getDisplayVendor());
- }
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
- result = prime * result + Arrays.hashCode(mLibs);
- result = prime * result + ((mDisplayName == null) ? 0 : mDisplayName.hashCode());
- result = prime * result + ((mVendorDisplay == null) ? 0 : mVendorDisplay.hashCode());
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof AddonPackage)) {
- return false;
- }
- AddonPackage other = (AddonPackage) obj;
- if (mLayoutlibVersion == null) {
- if (other.mLayoutlibVersion != null) {
- return false;
- }
- } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
- return false;
- }
- if (!Arrays.equals(mLibs, other.mLibs)) {
- return false;
- }
- if (mNameId == null) {
- if (other.mNameId != null) {
- return false;
- }
- } else if (!mNameId.equals(other.mNameId)) {
- return false;
- }
- if (mVendorId == null) {
- if (other.mVendorId != null) {
- return false;
- }
- } else if (!mVendorId.equals(other.mVendorId)) {
- return false;
- }
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-
- /**
- * For addon packages, we want to add vendor|name to the sorting key
- * <em>before<em/> the revision number.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
- s = s.substring(0, pos) +
- "|vid:" + getVendorId() + //$NON-NLS-1$
- "|nid:" + getNameId() + //$NON-NLS-1$
- s.substring(pos);
- return s;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BrokenPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BrokenPackage.java
deleted file mode 100755
index 3ee5ed2..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BrokenPackage.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.packages;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * Represents an SDK repository package that is incomplete.
- * It has a distinct icon and a specific error that is supposed to help the user on how to fix it.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class BrokenPackage extends MajorRevisionPackage
- implements IExactApiLevelDependency, IMinApiLevelDependency {
-
- /**
- * The minimal API level required by this package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- private final int mMinApiLevel;
-
- /**
- * The exact API level required by this package, if > 0,
- * or {@link #API_LEVEL_INVALID} if there is no such requirement.
- */
- private final int mExactApiLevel;
-
- private final String mShortDescription;
- private final String mLongDescription;
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new "broken" package that represents a package that we failed to load,
- * for whatever error indicated in {@code longDescription}.
- * There is also an <em>optional</em> API level dependency that can be specified.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- BrokenPackage(@Nullable Properties props,
- @NonNull String shortDescription,
- @NonNull String longDescription,
- int minApiLevel,
- int exactApiLevel,
- @Nullable String archiveOsPath,
- @NonNull IPkgDesc pkgDesc) {
- super( null, //source
- props, //properties
- 0, //revision will be taken from props
- null, //license
- longDescription, //description
- null, //descUrl
- archiveOsPath //archiveOsPath
- );
- mShortDescription = shortDescription;
- mLongDescription = longDescription;
- mMinApiLevel = minApiLevel;
- mExactApiLevel = exactApiLevel;
- mPkgDesc = pkgDesc;
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- * <p/>
- * Base implementation override: We don't actually save properties for a broken package.
- */
- @Override
- public void saveProperties(Properties props) {
- // Nop. We don't actually save properties for a broken package.
- }
-
- /**
- * Returns the minimal API level required by this package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public int getMinApiLevel() {
- return mMinApiLevel;
- }
-
- /**
- * Returns the exact API level required by this package, if > 0,
- * or {@link #API_LEVEL_INVALID} if the value was missing.
- */
- @Override
- public int getExactApiLevel() {
- return mExactApiLevel;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For broken packages, we return an empty string. These are not installable.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return ""; //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- return mShortDescription;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- return mShortDescription;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description uses what was given to the constructor.
- * If it's missing, it will use whatever the XML contains for the <description> field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
-
- String s = mLongDescription;
- if (s != null && !s.isEmpty()) {
- return s;
- }
-
- s = getDescription();
- if (s != null && !s.isEmpty()) {
- return s;
- }
- return getShortDescription();
- }
-
- /**
- * We should not be attempting to install a broken package.
- *
- * {@inheritDoc}
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- // We should not be attempting to install a broken package.
- return null;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof BrokenPackage) {
- return mShortDescription.equals(((BrokenPackage) pkg).mShortDescription) &&
- getDescription().equals(pkg.getDescription()) &&
- getMinApiLevel() == ((BrokenPackage) pkg).getMinApiLevel();
- }
-
- return false;
- }
-
- @Override
- public boolean preInstallHook(Archive archive,
- ITaskMonitor monitor,
- String osSdkRoot,
- File installFolder) {
- // Nothing specific to do.
- return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
- }
-
- /**
- * Computes a hash of the installed content (in case of successful install.)
- *
- * {@inheritDoc}
- */
- @Override
- public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
- // Nothing specific to do.
- super.postInstallHook(archive, monitor, installFolder);
- }
-
- /**
- * Similar to {@link BuildToolPackage#comparisonKey()}, but we need to use
- * {@link #getPkgDesc} instead of {@link #getRevision()}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- FullRevision rev = getPkgDesc().getFullRevision();
- if (rev != null) {
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
- String reverseSort = String.format("|rr:%1$04d.%2$04d.%3$04d.", //$NON-NLS-1$
- 9999 - rev.getMajor(),
- 9999 - rev.getMinor(),
- 9999 - rev.getMicro());
-
- s = s.substring(0, pos) + reverseSort + s.substring(pos);
- }
- return s;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BuildToolPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BuildToolPackage.java
deleted file mode 100755
index 2aa30ae..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/BuildToolPackage.java
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.PreciseRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
-/**
- * Represents a build-tool XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class BuildToolPackage extends FullRevisionPackage {
-
- /** The base value returned by {@link BuildToolPackage#installId()}. */
- private static final String INSTALL_ID_BASE = SdkConstants.FD_BUILD_TOOLS + '-';
-
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new build-tool package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public BuildToolPackage(
- SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newBuildTool(getRevision())
- )
- .create();
- }
-
- /**
- * Creates either a valid {@link BuildToolPackage} or a {@link BrokenPackage}.
- * <p/>
- * If the build-tool directory contains valid properties,
- * this creates a new {@link BuildToolPackage} with the reversion listed in the properties.
- * Otherwise returns a new {@link BrokenPackage} with some explanation on what failed.
- * <p/>
- * Note that the folder name is not enforced. A build-tool directory must have a
- * a source.props with a revision property and a few expected binaries inside to be
- * valid.
- *
- * @param buildToolDir The SDK/build-tool/revision folder
- * @param props The properties located in {@code buildToolDir} or null if not found.
- * @return A new {@link BuildToolPackage} or a new {@link BrokenPackage}.
- */
- public static Package create(File buildToolDir, Properties props) {
- String error = null;
-
- // Try to load the reversion from the sources.props.
- // If we don't find them, the package is broken.
- if (props == null) {
- error = String.format("Missing file %1$s in build-tool/%2$s",
- SdkConstants.FN_SOURCE_PROP,
- buildToolDir.getName());
- }
-
- // Check we can find the revision in the source properties
- FullRevision rev = null;
- if (error == null) {
- String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
-
- if (revStr != null) {
- try {
- rev = FullRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- if (rev == null) {
- error = String.format("Missing revision property in %1$s",
- SdkConstants.FN_SOURCE_PROP);
- }
- }
-
- if (error == null) {
- // Check the directory contains the expected binaries.
-
- if (!buildToolDir.isDirectory()) {
- error = String.format("build-tool/%1$s folder is missing",
- buildToolDir.getName());
- } else {
- File[] files = buildToolDir.listFiles();
- if (files == null || files.length == 0) {
- error = String.format("build-tool/%1$s folder is empty",
- buildToolDir.getName());
- } else {
- Set<String> names = new HashSet<String>();
- for (File file : files) {
- names.add(file.getName());
- }
- for (String name : new String[] { SdkConstants.FN_AAPT,
- SdkConstants.FN_AIDL,
- SdkConstants.FN_DX } ) {
- if (!names.contains(name)) {
- if (error == null) {
- error = String.format("build-tool/%1$s folder is missing ",
- buildToolDir.getName());
- } else {
- error += ", ";
- }
- error += name;
- }
- }
- }
- }
- }
-
- if (error == null && rev != null) {
- BuildToolPackage pkg = new BuildToolPackage(
- null, //source
- props,
- 0, //revision (extracted from props)
- null, //license
- null, //description
- null, //descUrl
- buildToolDir.getAbsolutePath());
-
- if (pkg.hasCompatibleArchive()) {
- return pkg;
- } else {
- error = "Package is not compatible with current OS";
- }
- }
-
-
- StringBuilder sb = new StringBuilder("Broken Build-Tools Package");
- if (rev != null) {
- sb.append(String.format(", revision %1$s", rev.toShortString()));
- }
-
- String shortDesc = sb.toString();
-
- if (error != null) {
- sb.append('\n').append(error);
- }
-
- String longDesc = sb.toString();
-
- IPkgDesc desc = PkgDesc.Builder
- .newBuildTool(rev != null ? rev : new FullRevision(FullRevision.MISSING_MAJOR_REV))
- .setDescriptionShort(shortDesc)
- .create();
-
- return new BrokenPackage(props, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- IExactApiLevelDependency.API_LEVEL_INVALID,
- buildToolDir.getAbsolutePath(),
- desc);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected BuildToolPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder.newBuildTool(getRevision())).create();
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For build-tools, we use "build-tools-" followed by the full revision string
- * where spaces are changed to underscore to be more script-friendly.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return getPkgDesc().getInstallId();
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- return String.format("Android SDK Build-tools%1$s",
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return String.format("Android SDK Build-tools, revision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /** Returns a long description for an {@link IDescription}. */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A build-tool package is typically installed in SDK/build-tools/revision.
- * Revision spaces are replaced by underscores for ease of use in command-line.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- File folder = new File(osSdkRoot, SdkConstants.FD_BUILD_TOOLS);
- StringBuilder sb = new StringBuilder();
-
- PreciseRevision revision = getPkgDesc().getPreciseRevision();
- int[] version = revision.toIntArray(false);
- for (int i = 0; i < version.length; i++) {
- sb.append(version[i]);
- if (i != version.length - 1) {
- sb.append('.');
- }
- }
- if (getPkgDesc().getPreciseRevision().isPreview()) {
- sb.append(PkgDesc.PREVIEW_SUFFIX);
- }
-
- folder = new File(folder, sb.toString());
- return folder;
- }
-
- /**
- * Check whether 2 platform-tool packages are the same <em>and</em> have the
- * same preview bit.
- */
- @Override
- public boolean sameItemAs(Package pkg) {
- // Implementation note: here we don't want to care about the preview number
- // so we ignore the preview when calling sameItemAs(); however we do care
- // about both packages being either previews or not previews (i.e. the type
- // must match but the preview number doesn't need to.)
- // The end result is that a package such as "1.2 rc 4" will be an update for "1.2 rc 3".
- return sameItemAs(pkg, PreviewComparison.COMPARE_TYPE);
- }
-
- @Override
- public boolean sameItemAs(Package pkg, PreviewComparison comparePreview) {
- // Contrary to other package types, build-tools do not "update themselves"
- // so 2 build tools with 2 different revisions are not the same item.
- if (pkg instanceof BuildToolPackage) {
- BuildToolPackage rhs = (BuildToolPackage) pkg;
- return rhs.getRevision().compareTo(getRevision(), comparePreview) == 0;
- }
- return false;
- }
-
- /**
- * For build-tool package use their revision number like version numbers and
- * we want them sorted from higher to lower. To do that, insert a fake revision
- * number using 9999-value into the sorting key.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
-
- FullRevision rev = getRevision();
- String reverseSort = String.format("|rr:%1$04d.%2$04d.%3$04d.", //$NON-NLS-1$
- 9999 - rev.getMajor(),
- 9999 - rev.getMinor(),
- 9999 - rev.getMicro());
-
- s = s.substring(0, pos) + reverseSort + s.substring(pos);
- return s;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/DocPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/DocPackage.java
deleted file mode 100755
index 3582f5b..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/DocPackage.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a doc XML node in an SDK repository.
- * <p/>
- * Note that a doc package has a version and thus implements {@link IAndroidVersionProvider}.
- * However there is no mandatory dependency that limits installation so this does not
- * implement {@link IPlatformDependency}.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class DocPackage extends MajorRevisionPackage implements IAndroidVersionProvider {
-
- private final AndroidVersion mVersion;
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new doc package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public DocPackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel =
- PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.isEmpty()) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mPkgDesc = setDescriptions(
- PkgDesc.Builder.newDoc(mVersion, (MajorRevision) getRevision()))
- .create();
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(SdkSource source,
- Properties props,
- int apiLevel,
- String codename,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- return new DocPackage(source, props, apiLevel, codename, revision, license, description,
- descUrl, archiveOsPath);
- }
-
- private DocPackage(SdkSource source,
- Properties props,
- int apiLevel,
- String codename,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
- mVersion = new AndroidVersion(props, apiLevel, codename);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder.newDoc(mVersion, (MajorRevision) getRevision()))
- .create();
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- }
-
- /**
- * Returns the version, for platform, add-on and doc packages.
- * Can be 0 if this is a local package of unknown api-level.
- */
- @Override @NonNull
- public AndroidVersion getAndroidVersion() {
- return mVersion;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For docs, we use "doc-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "doc-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
- if (mVersion.isPreview()) {
- return String.format("Documentation for Android '%1$s' Preview SDK%2$s",
- mVersion.getCodename(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Documentation for Android SDK%2$s",
- mVersion.getApiLevel(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- if (mVersion.isPreview()) {
- return String.format("Documentation for Android '%1$s' Preview SDK, revision %2$s%3$s",
- mVersion.getCodename(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Documentation for Android SDK, API %1$d, revision %2$s%3$s",
- mVersion.getApiLevel(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the <description> field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A "doc" package should always be located in SDK/docs.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return new File(osSdkRoot, SdkConstants.FD_DOCS);
- }
-
- /**
- * Consider doc packages to be the same if they cover the same API level,
- * regardless of their revision number.
- */
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof DocPackage) {
- AndroidVersion rev2 = ((DocPackage) pkg).getAndroidVersion();
- return this.getAndroidVersion().equals(rev2);
- }
-
- return false;
- }
-
- /**
- * {@inheritDoc}
- * <hr>
- * Doc packages are a bit different since there can only be one doc installed at
- * the same time.
- * <p/>
- * We now consider that docs for different APIs are NOT updates, e.g. doc for API N+1
- * is no longer considered an update for doc API N.
- * However docs that have the same API version (API level + codename) are considered
- * updates if they have a higher revision number (so 15 rev 2 is an update for 15 rev 1,
- * but is not an update for 14 rev 1.)
- */
- @Override
- public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
- // check they are the same kind of object
- if (!(replacementPackage instanceof DocPackage)) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- DocPackage replacementDoc = (DocPackage)replacementPackage;
-
- AndroidVersion replacementVersion = replacementDoc.getAndroidVersion();
-
- // Check if they're the same exact (api and codename)
- if (replacementVersion.equals(mVersion)) {
- // exact same version, so check the revision level
- if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
- return UpdateInfo.UPDATE;
- }
- } else {
- // not the same version? we check if they have the same api level and the new one
- // is a preview, in which case it's also an update (since preview have the api level
- // of the _previous_ version.)
- if (replacementVersion.getApiLevel() == mVersion.getApiLevel() &&
- replacementVersion.isPreview()) {
- return UpdateInfo.UPDATE;
- }
- }
-
- // not an upgrade but not incompatible either.
- return UpdateInfo.NOT_UPDATE;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof DocPackage)) {
- return false;
- }
- DocPackage other = (DocPackage) obj;
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ExtraPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ExtraPackage.java
deleted file mode 100755
index fccbd21..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ExtraPackage.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.LocalSdkParser;
-import com.android.sdklib.internal.repository.NullTaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.sdklib.repository.descriptors.IPkgDescExtra;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDescExtra;
-import com.android.sdklib.repository.local.LocalExtraPkgInfo;
-import com.android.utils.NullLogger;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Properties;
-import java.util.regex.Pattern;
-
-/**
- * Represents a extra XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class ExtraPackage extends NoPreviewRevisionPackage
- implements IMinApiLevelDependency, IMinToolsDependency {
-
- /** Mixin handling the min-tools dependency. */
- private final MinToolsMixin mMinToolsMixin;
-
- /**
- * The extra display name. Used in the UI to represent the package. It can be anything.
- */
- private final String mDisplayName;
-
- /**
- * The vendor id + name.
- * The id is a simple alphanumeric string [a-zA-Z0-9_-].
- * The display name is used in the UI to represent the vendor. It can be anything.
- */
- private final IdDisplay mVendor;
-
- /**
- * The sub-folder name. It must be a non-empty single-segment path.
- */
- private final String mPath;
-
- /**
- * The optional old_paths, if any. If present, this is a list of old "path" values that
- * we'd like to migrate to the current "path" name for this extra.
- */
- private final String mOldPaths;
-
- /**
- * The minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- private final int mMinApiLevel;
-
- /**
- * The project-files listed by this extra package.
- * The array can be empty but not null.
- */
- private final String[] mProjectFiles;
-
- private final IPkgDescExtra mPkgDesc;
-
- /**
- * Creates a new tool package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public ExtraPackage(
- SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mMinToolsMixin = new MinToolsMixin(packageNode);
-
- mPath = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);
-
- // Read name-display, vendor-display and vendor-id, introduced in addon-4.xsd.
- // These are not optional, they are mandatory in addon-4 but we still treat them
- // as optional so that we can fallback on using <vendor> which was the only one
- // defined in addon-3.xsd.
- String name =
- PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_NAME_DISPLAY);
- String vname =
- PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_DISPLAY);
- String vid =
- PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_ID);
-
- if (vid.isEmpty()) {
- // If vid is missing, use the old <vendor> attribute.
- // Note that in a valid XML, vendor-id cannot be an empty string.
- // The only reason vid can be empty is when <vendor-id> is missing, which
- // happens in an addon-3 schema, in which case the old <vendor> needs to be used.
- String vendor = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR);
- vid = sanitizeLegacyVendor(vendor);
- if (vname.isEmpty()) {
- vname = vendor;
- }
- }
- if (vname.isEmpty()) {
- // The vendor-display name can be empty, in which case we use the vendor-id.
- vname = vid;
- }
- mVendor = new IdDisplay(vid.trim(), vname.trim());
-
- if (name.isEmpty()) {
- // If name is missing, use the <path> attribute as done in an addon-3 schema.
- name = LocalExtraPkgInfo.getPrettyName(mVendor, mPath);
- }
- mDisplayName = name.trim();
-
- mMinApiLevel = PackageParserUtils.getXmlInt(
- packageNode, RepoConstants.NODE_MIN_API_LEVEL, MIN_API_LEVEL_NOT_SPECIFIED);
-
- mProjectFiles = parseProjectFiles(
- PackageParserUtils.findChildElement(packageNode, RepoConstants.NODE_PROJECT_FILES));
-
- mOldPaths = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);
-
- mPkgDesc =
- (IPkgDescExtra)setDescriptions(PkgDesc.Builder.newExtra(mVendor, mPath, mDisplayName,
- getOldPaths(), getRevision())).create();
- }
-
- private String[] parseProjectFiles(Node projectFilesNode) {
- ArrayList<String> paths = new ArrayList<String>();
-
- if (projectFilesNode != null) {
- String nsUri = projectFilesNode.getNamespaceURI();
- for(Node child = projectFilesNode.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
-
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- RepoConstants.NODE_PATH.equals(child.getLocalName())) {
- String path = child.getTextContent();
- if (path != null) {
- path = path.trim();
- if (!path.isEmpty()) {
- paths.add(path);
- }
- }
- }
- }
- }
-
- return paths.toArray(new String[paths.size()]);
- }
-
- /**
- * Manually create a new package with one archive and the given attributes or properties.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(SdkSource source,
- Properties props,
- String vendor,
- String path,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- ExtraPackage ep = new ExtraPackage(source, props, vendor, path, revision, license,
- description, descUrl, archiveOsPath);
- return ep;
- }
-
- /**
- * Constructor used to create a mock {@link ExtraPackage}.
- * Most of the attributes here are optional.
- * When not defined, they will be extracted from the {@code props} properties.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected ExtraPackage(SdkSource source,
- Properties props,
- String vendorId,
- String path,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
-
- mMinToolsMixin = new MinToolsMixin(
- source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
-
- // The path argument comes before whatever could be in the properties
- mPath = path != null ? path : getProperty(props, PkgProps.EXTRA_PATH, path);
-
- String name = getProperty(props, PkgProps.EXTRA_NAME_DISPLAY, ""); //$NON-NLS-1$
- String vname = getProperty(props, PkgProps.EXTRA_VENDOR_DISPLAY, ""); //$NON-NLS-1$
- String vid = vendorId != null ? vendorId :
- getProperty(props, PkgProps.EXTRA_VENDOR_ID, ""); //$NON-NLS-1$
-
- if (vid == null || vid.isEmpty()) {
- // If vid is missing, use the old <vendor> attribute.
- // <vendor> did not exist prior to schema repo-v3 and tools r8.
- String vendor = getProperty(props, PkgProps.EXTRA_VENDOR, ""); //$NON-NLS-1$
- vid = sanitizeLegacyVendor(vendor);
- if (vname == null || vname.isEmpty()) {
- vname = vendor;
- }
- }
- if (vname == null || vname.isEmpty()) {
- // The vendor-display name can be empty, in which case we use the vendor-id.
- vname = vid;
- }
- mVendor = new IdDisplay(vid.trim(), vname.trim());
-
- if (name == null || name.isEmpty()) {
- // If name is missing, use the <path> attribute as done in an addon-3 schema.
- name = LocalExtraPkgInfo.getPrettyName(mVendor, mPath);
- }
- mDisplayName = name.trim();
-
- mOldPaths = getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);
-
- mMinApiLevel = getPropertyInt(props, PkgProps.EXTRA_MIN_API_LEVEL,
- MIN_API_LEVEL_NOT_SPECIFIED);
-
- String projectFiles = getProperty(props, PkgProps.EXTRA_PROJECT_FILES, null);
- ArrayList<String> filePaths = new ArrayList<String>();
- if (projectFiles != null && !projectFiles.isEmpty()) {
- for (String filePath : projectFiles.split(Pattern.quote(File.pathSeparator))) {
- filePath = filePath.trim();
- if (!filePath.isEmpty()) {
- filePaths.add(filePath);
- }
- }
- }
-
- mProjectFiles = filePaths.toArray(new String[filePaths.size()]);
-
- mPkgDesc = (IPkgDescExtra) setDescriptions(PkgDesc.Builder
- .newExtra(mVendor, mPath, mDisplayName, getOldPaths(), getRevision()))
- .create();
- }
-
- @Override
- @NonNull
- public IPkgDescExtra getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
- mMinToolsMixin.saveProperties(props);
-
- props.setProperty(PkgProps.EXTRA_PATH, mPath);
- props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, mDisplayName);
- props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, mVendor.getDisplay());
- props.setProperty(PkgProps.EXTRA_VENDOR_ID, mVendor.getId());
-
- if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
- props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
- }
-
- if (mProjectFiles.length > 0) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mProjectFiles.length; i++) {
- if (i > 0) {
- sb.append(File.pathSeparatorChar);
- }
- sb.append(mProjectFiles[i]);
- }
- props.setProperty(PkgProps.EXTRA_PROJECT_FILES, sb.toString());
- }
-
- if (mOldPaths != null && !mOldPaths.isEmpty()) {
- props.setProperty(PkgProps.EXTRA_OLD_PATHS, mOldPaths);
- }
- }
-
- /**
- * The minimal revision of the tools package required by this extra package, if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public FullRevision getMinToolsRevision() {
- return mMinToolsMixin.getMinToolsRevision();
- }
-
- /**
- * Returns the minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public int getMinApiLevel() {
- return mMinApiLevel;
- }
-
- /**
- * The project-files listed by this extra package.
- * The array can be empty but not null.
- * <p/>
- * IMPORTANT: directory separators are NOT translated and may not match
- * the {@link File#separatorChar} of the current platform. It's up to the
- * user to adequately interpret the paths.
- * Similarly, no guarantee is made on the validity of the paths.
- * Users are expected to apply all usual sanity checks such as removing
- * "./" and "../" and making sure these paths don't reference files outside
- * of the installed archive.
- *
- * @since sdk-repository-4.xsd or sdk-addon-2.xsd
- */
- public String[] getProjectFiles() {
- return mProjectFiles;
- }
-
- /**
- * Returns the old_paths, a list of obsolete path names for the extra package.
- * <p/>
- * These can be used by the installer to migrate an extra package using one of the
- * old paths into the new path.
- * <p/>
- * These can also be used to recognize "old" renamed packages as the same as
- * the current one.
- *
- * @return A list of old paths. Can be empty but not null.
- */
- public String[] getOldPaths() {
- return PkgDescExtra.convertOldPaths(mOldPaths);
- }
-
- /**
- * Returns the sanitized path folder name. It is a single-segment path.
- * <p/>
- * The package is installed in SDK/extras/vendor_name/path_name.
- */
- public String getPath() {
- // The XSD specifies the XML vendor and path should only contain [a-zA-Z0-9]+
- // and cannot be empty. Let's be defensive and enforce that anyway since things
- // like "____" are still valid values that we don't want to allow.
-
- // Sanitize the path
- String path = mPath.replaceAll("[^a-zA-Z0-9-]+", "_"); //$NON-NLS-1$
- if (path.isEmpty() || path.equals("_")) { //$NON-NLS-1$
- int h = path.hashCode();
- path = String.format("extra%08x", h); //$NON-NLS-1$
- }
-
- return path;
- }
-
- /**
- * Returns the vendor id.
- */
- public String getVendorId() {
- return mVendor.getId();
- }
-
- public String getVendorDisplay() {
- return mVendor.getDisplay();
- }
-
- public String getDisplayName() {
- return mDisplayName;
- }
-
- /** Transforms the legacy vendor name into a usable vendor id. */
- private String sanitizeLegacyVendor(String vendorDisplay) {
- // The XSD specifies the XML vendor and path should only contain [a-zA-Z0-9]+
- // and cannot be empty. Let's be defensive and enforce that anyway since things
- // like "____" are still valid values that we don't want to allow.
-
- if (vendorDisplay != null && !vendorDisplay.isEmpty()) {
- String vendor = vendorDisplay.trim();
- // Sanitize the vendor
- vendor = vendor.replaceAll("[^a-zA-Z0-9-]+", "_"); //$NON-NLS-1$
- if (vendor.equals("_")) { //$NON-NLS-1$
- int h = vendor.hashCode();
- vendor = String.format("vendor%08x", h); //$NON-NLS-1$
- }
-
- return vendor;
- }
-
- return ""; //$NON-NLS-1$
-
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For extras, we use "extra-vendor-path".
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return String.format("extra-%1$s-%2$s", //$NON-NLS-1$
- getVendorId(),
- getPath());
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- String s = String.format("%1$s%2$s",
- getDisplayName(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
-
- return s;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- String s = String.format("%1$s, revision %2$s%3$s",
- getDisplayName(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
-
- return s;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the <description> field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = String.format("%1$s, revision %2$s%3$s\nBy %4$s",
- getDisplayName(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "", //$NON-NLS-2$
- getVendorDisplay());
-
- String d = getDescription();
- if (d != null && !d.isEmpty()) {
- s += '\n' + d;
- }
-
- if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
- s += String.format("\nRequires tools revision %1$s",
- getMinToolsRevision().toShortString());
- }
-
- if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
- s += String.format("\nRequires SDK Platform Android API %1$s", getMinApiLevel());
- }
-
- File localPath = getLocalArchivePath();
- if (localPath != null) {
- // For a local archive, also put the install path in the long description.
- // This should help users locate the extra on their drive.
- s += String.format("\nLocation: %1$s", localPath.getAbsolutePath());
- } else {
- // For a non-installed archive, indicate where it would be installed.
- s += String.format("\nInstall path: %1$s",
- getInstallSubFolder(null/*sdk root*/).getPath());
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A "tool" package should always be located in SDK/tools.
- *
- * @param osSdkRoot The OS path of the SDK root folder. Must NOT be null.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * Not used in this implementation.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
-
- // First find if this extra is already installed. If so, reuse the same directory.
- LocalSdkParser localParser = new LocalSdkParser();
- Package[] pkgs = localParser.parseSdk(
- osSdkRoot,
- sdkManager,
- LocalSdkParser.PARSE_EXTRAS,
- new NullTaskMonitor(NullLogger.getLogger()));
-
- for (Package pkg : pkgs) {
- if (sameItemAs(pkg) && pkg instanceof ExtraPackage) {
- File localPath = ((ExtraPackage) pkg).getLocalArchivePath();
- if (localPath != null) {
- return localPath;
- }
- }
- }
-
- return getInstallSubFolder(osSdkRoot);
- }
-
- /**
- * Computes the "sub-folder" install path, relative to the given SDK root.
- * For an extra package, this is generally ".../extra/vendor-id/path".
- *
- * @param osSdkRoot The OS path of the SDK root folder if known.
- * This CAN be null, in which case the path will start at /extra.
- * @return Either /extra/vendor/path or sdk-root/extra/vendor-id/path.
- */
- private File getInstallSubFolder(@Nullable String osSdkRoot) {
- // The /extras dir at the root of the SDK
- File path = new File(osSdkRoot, SdkConstants.FD_EXTRAS);
-
- String vendor = getVendorId();
- if (vendor != null && !vendor.isEmpty()) {
- path = new File(path, vendor);
- }
-
- String name = getPath();
- if (name != null && !name.isEmpty()) {
- path = new File(path, name);
- }
-
- return path;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- // Extra packages are similar if they have the same path and vendor
- if (pkg instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) pkg;
- return PkgDescExtra.compatibleVendorAndPath(mPkgDesc, ep.mPkgDesc);
- }
-
- return false;
- }
-
- /**
- * For extra packages, we want to add vendor|path to the sorting key
- * <em>before<em/> the revision number.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
- s = s.substring(0, pos) +
- "|ve:" + getVendorId() + //$NON-NLS-1$
- "|pa:" + getPath() + //$NON-NLS-1$
- s.substring(pos);
- return s;
- }
-
- // ---
-
- /**
- * If this package is installed, returns the install path of the archive if valid.
- * Returns null if not installed or if the path does not exist.
- */
- private File getLocalArchivePath() {
- Archive[] archives = getArchives();
- if (archives.length == 1 && archives[0].isLocal()) {
- File path = new File(archives[0].getLocalOsPath());
- if (path.isDirectory()) {
- return path;
- }
- }
-
- return null;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = mMinToolsMixin.hashCode(super.hashCode());
- result = prime * result + mMinApiLevel;
- result = prime * result + ((mPath == null) ? 0 : mPath.hashCode());
- result = prime * result + Arrays.hashCode(mProjectFiles);
- result = prime * result + ((mVendor == null) ? 0 : mVendor.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof ExtraPackage)) {
- return false;
- }
- ExtraPackage other = (ExtraPackage) obj;
- if (mMinApiLevel != other.mMinApiLevel) {
- return false;
- }
- if (mPath == null) {
- if (other.mPath != null) {
- return false;
- }
- } else if (!mPath.equals(other.mPath)) {
- return false;
- }
- if (!Arrays.equals(mProjectFiles, other.mProjectFiles)) {
- return false;
- }
- if (mVendor == null) {
- if (other.mVendor != null) {
- return false;
- }
- } else if (!mVendor.equals(other.mVendor)) {
- return false;
- }
- return mMinToolsMixin.equals(obj);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
deleted file mode 100755
index 5e9b631..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a package in an SDK repository that has a {@link FullRevision},
- * which is a multi-part revision number (major.minor.micro) and an optional preview revision.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public abstract class FullRevisionPackage extends Package
- implements IFullRevisionProvider {
-
- private final FullRevision mPreviewVersion;
-
- /**
- * Creates a new package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- FullRevisionPackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mPreviewVersion = PackageParserUtils.parseFullRevisionElement(
- PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_REVISION));
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public FullRevisionPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source, props, revision, license, description, descUrl, archiveOsPath);
-
- FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- rev = new FullRevision(revision);
- }
- mPreviewVersion = rev;
- }
-
- @Override
- public FullRevision getRevision() {
- return mPreviewVersion;
- }
-
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
- props.setProperty(PkgProps.PKG_REVISION, mPreviewVersion.toShortString());
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mPreviewVersion == null) ? 0 : mPreviewVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof FullRevisionPackage)) {
- return false;
- }
- FullRevisionPackage other = (FullRevisionPackage) obj;
- if (mPreviewVersion == null) {
- if (other.mPreviewVersion != null) {
- return false;
- }
- } else if (!mPreviewVersion.equals(other.mPreviewVersion)) {
- return false;
- }
- return true;
- }
-
- /**
- * Computes whether the given package is a suitable update for the current package.
- * <p/>
- * A specific case here is that a release package can update a preview, whereas
- * a preview can only update another preview.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
- if (replacementPackage == null) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check they are the same item, ignoring the preview bit.
- if (!sameItemAs(replacementPackage, PreviewComparison.IGNORE)) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // a preview cannot update a non-preview
- if (!getRevision().isPreview() && replacementPackage.getRevision().isPreview()) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check revision number
- if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
- return UpdateInfo.UPDATE;
- }
-
- // not an upgrade but not incompatible either.
- return UpdateInfo.NOT_UPDATE;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
deleted file mode 100755
index 0f1a53f..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-
-/**
- * Interface for packages that provide an {@link AndroidVersion}.
- * <p/>
- * Note that {@link IPlatformDependency} is a similar interface, but with a different semantic.
- * The {@link IPlatformDependency} denotes that a given package can only be installed if the
- * requested platform is present, whereas this interface denotes that the given package simply
- * has a version, which is not necessarily a dependency.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IAndroidVersionProvider {
-
- /**
- * Returns the android version, for platform, add-on and doc packages.
- * Can be 0 if this is a local package of unknown api-level.
- */
- @NonNull
- AndroidVersion getAndroidVersion();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IExactApiLevelDependency.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IExactApiLevelDependency.java
deleted file mode 100755
index 7ed2065..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IExactApiLevelDependency.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.RepoConstants;
-
-/**
- * Interface used to decorate a {@link Package} that has a dependency
- * on a specific API level, e.g. which XML has a {@code <api-level>} element.
- * <p/>
- * For example an add-on package requires a platform with an exact API level to be installed
- * at the same time.
- * This is not the same as {@link IMinApiLevelDependency} which requests that a platform with at
- * least the requested API level be present or installed at the same time.
- * <p/>
- * Such package requires the {@code <api-level>} element. It is not an optional
- * property, however it can be invalid.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IExactApiLevelDependency {
-
- /**
- * The value of {@link #getExactApiLevel()} when the {@link RepoConstants#NODE_API_LEVEL}
- * was not specified in the XML source.
- */
- int API_LEVEL_INVALID = 0;
-
- /**
- * Returns the exact API level required by this package, if > 0,
- * or {@link #API_LEVEL_INVALID} if the value was missing.
- * <p/>
- * This attribute is mandatory and should not be normally missing.
- * It can only happen when dealing with an invalid repository XML.
- */
- int getExactApiLevel();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
deleted file mode 100755
index 6da200c..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
-
-
-
-/**
- * Interface for packages that provide a {@link FullRevision},
- * which is a multi-part revision number (major.minor.micro) and an optional preview revision.
- * <p/>
- * This interface is a tag. It indicates that {@link Package#getRevision()} returns a
- * {@link FullRevision} instead of a limited {@link MajorRevision}. <br/>
- * The preview version number is available via {@link Package#getRevision()}.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IFullRevisionProvider {
-
- /**
- * Returns whether the given package represents the same item as the current package.
- * <p/>
- * Two packages are considered the same if they represent the same thing, except for the
- * revision number.
- * @param pkg The package to compare
- * @param comparePreview How to compare previews.
- * @return true if the items are the same.
- */
- boolean sameItemAs(Package pkg, PreviewComparison comparePreview);
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java
deleted file mode 100755
index 30b2112..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.utils.Pair;
-
-/**
- * Interface used to decorate a {@link Package} that provides a version for layout lib.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface ILayoutlibVersion {
-
- int LAYOUTLIB_API_NOT_SPECIFIED = 0;
- int LAYOUTLIB_REV_NOT_SPECIFIED = 0;
-
- /**
- * Returns the layoutlib version. Mandatory starting with repository XSD rev 4.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
- * version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-repository-4.xsd
- */
- Pair<Integer, Integer> getLayoutlibVersion();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinApiLevelDependency.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinApiLevelDependency.java
deleted file mode 100755
index 60e6d34..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinApiLevelDependency.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.SdkRepoConstants;
-
-/**
- * Interface used to decorate a {@link Package} that has a dependency
- * on a minimal API level, e.g. which XML has a <code><min-api-level></code> element.
- * <p/>
- * A package that has this dependency can only be installed if a platform with at least the
- * requested API level is present or installed at the same time.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IMinApiLevelDependency {
-
- /**
- * The value of {@link #getMinApiLevel()} when the {@link SdkRepoConstants#NODE_MIN_API_LEVEL}
- * was not specified in the XML source.
- */
- int MIN_API_LEVEL_NOT_SPECIFIED = 0;
-
- /**
- * Returns the minimal API level required by this package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- int getMinApiLevel();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
deleted file mode 100755
index 49c84f8..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-/**
- * Interface used to decorate a {@link Package} that has a dependency
- * on a minimal platform-tools revision, e.g. which XML has a
- * <code><min-platform-tools-rev></code> element.
- * <p/>
- * A package that has this dependency can only be installed if the requested platform-tools
- * revision is present or installed at the same time.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IMinPlatformToolsDependency {
-
- /**
- * The value of {@link #getMinPlatformToolsRevision()} when the
- * {@link SdkRepoConstants#NODE_MIN_PLATFORM_TOOLS_REV} was not specified in the XML source.
- * Since this is a required attribute in the XML schema, it can only happen when dealing
- * with an invalid repository XML.
- */
- FullRevision MIN_PLATFORM_TOOLS_REV_INVALID =
- new FullRevision(FullRevision.MISSING_MAJOR_REV);
-
- /**
- * The minimal revision of the tools package required by this package if > 0,
- * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.
- * <p/>
- * This attribute is mandatory and should not be normally missing.
- * It can only happen when dealing with an invalid repository XML.
- */
- FullRevision getMinPlatformToolsRevision();
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
deleted file mode 100755
index ebdbf59..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-/**
- * Interface used to decorate a {@link Package} that has a dependency
- * on a minimal tools revision, e.g. which XML has a <code><min-tools-rev></code> element.
- * <p/>
- * A package that has this dependency can only be installed if the requested tools revision
- * is present or installed at the same time.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IMinToolsDependency {
-
- /**
- * The value of {@link #getMinToolsRevision()} when the
- * {@link SdkRepoConstants#NODE_MIN_TOOLS_REV} was not specified in the XML source.
- */
- FullRevision MIN_TOOLS_REV_NOT_SPECIFIED =
- new FullRevision(FullRevision.MISSING_MAJOR_REV);
-
- /**
- * The minimal revision of the tools package required by this extra package if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- FullRevision getMinToolsRevision();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IPlatformDependency.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
deleted file mode 100755
index ccbb267..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.AndroidVersion;
-
-/**
- * Interface used to decorate a {@link Package} that has a dependency
- * on a specific platform (API level and/or code name).
- * <p/>
- * A package that has this dependency can only be installed if a platform with at least the
- * requested API level is present or installed at the same time.
- * <p/>
- * Note that although this interface looks like {@link IAndroidVersionProvider}, it does
- * not convey the same semantic since {@link IAndroidVersionProvider} does <em>not</em>
- * imply any dependency being a limiting factor as far as installation is concerned.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IPlatformDependency {
-
- /** Returns the version of the platform dependency of this package. */
- AndroidVersion getAndroidVersion();
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
deleted file mode 100755
index 7d18eda..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Node;
-
-import java.util.Properties;
-
-/**
- * Helper class to handle the layoutlib version provided by a package.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class LayoutlibVersionMixin implements ILayoutlibVersion {
-
- /**
- * The layoutlib version.
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
- * version isn't specified.
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- */
- private final Pair<Integer, Integer> mLayoutlibVersion;
-
- /**
- * Parses an XML node to process the {@code <layoutlib>} element.
- *
- * The layoutlib element is new in the XSD rev 4, so we need to cope with it missing
- * in earlier XMLs.
- */
- public LayoutlibVersionMixin(Node pkgNode) {
-
- int api = LAYOUTLIB_API_NOT_SPECIFIED;
- int rev = LAYOUTLIB_REV_NOT_SPECIFIED;
-
- Node layoutlibNode =
- PackageParserUtils.findChildElement(pkgNode, RepoConstants.NODE_LAYOUT_LIB);
-
- if (layoutlibNode != null) {
- api = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_API, 0);
- rev = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_REVISION, 0);
- }
-
- mLayoutlibVersion = Pair.of(api, rev);
- }
-
- /**
- * Parses the layoutlib version optionally available in the given {@link Properties}.
- */
- public LayoutlibVersionMixin(Properties props) {
- int layoutlibApi = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_API,
- LAYOUTLIB_API_NOT_SPECIFIED);
- int layoutlibRev = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_REV,
- LAYOUTLIB_REV_NOT_SPECIFIED);
- mLayoutlibVersion = Pair.of(layoutlibApi, layoutlibRev);
- }
-
- /**
- * Stores the layoutlib version in the given {@link Properties}.
- */
- void saveProperties(Properties props) {
- if (mLayoutlibVersion.getFirst().intValue() != LAYOUTLIB_API_NOT_SPECIFIED) {
- props.setProperty(PkgProps.LAYOUTLIB_API, mLayoutlibVersion.getFirst().toString());
- props.setProperty(PkgProps.LAYOUTLIB_REV, mLayoutlibVersion.getSecond().toString());
- }
- }
-
- /**
- * Returns the layoutlib version.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
- * version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-repository-4.xsd and sdk-addon-2.xsd
- */
- @Override
- public Pair<Integer, Integer> getLayoutlibVersion() {
- return mLayoutlibVersion;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof LayoutlibVersionMixin)) {
- return false;
- }
- LayoutlibVersionMixin other = (LayoutlibVersionMixin) obj;
- if (mLayoutlibVersion == null) {
- if (other.mLayoutlibVersion != null) {
- return false;
- }
- } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
deleted file mode 100755
index 38bc3a0..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a package in an SDK repository that has a {@link MajorRevision},
- * which is a single major revision number (not minor, micro or previews).
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public abstract class MajorRevisionPackage extends Package {
-
- private final MajorRevision mRevision;
-
- /**
- * Creates a new package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- MajorRevisionPackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mRevision = new MajorRevision(
- PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_REVISION, 0));
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MajorRevisionPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source, props, revision, license, description, descUrl, archiveOsPath);
-
- String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
-
- MajorRevision rev = null;
- if (revStr != null) {
- try {
- rev = MajorRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
- if (rev == null) {
- rev = new MajorRevision(revision);
- }
-
- mRevision = rev;
- }
-
- /**
- * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
- * Can be 0 if this is a local package of unknown revision.
- */
- @Override
- public FullRevision getRevision() {
- return mRevision;
- }
-
-
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
- props.setProperty(PkgProps.PKG_REVISION, mRevision.toString());
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mRevision == null) ? 0 : mRevision.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof MajorRevisionPackage)) {
- return false;
- }
- MajorRevisionPackage other = (MajorRevisionPackage) obj;
- if (mRevision == null) {
- if (other.mRevision != null) {
- return false;
- }
- } else if (!mRevision.equals(other.mRevision)) {
- return false;
- }
- return true;
- }
-
- @Override
- public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
- if (replacementPackage == null) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check they are the same item.
- if (!sameItemAs(replacementPackage)) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check revision number
- if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
- return UpdateInfo.UPDATE;
- }
-
- // not an upgrade but not incompatible either.
- return UpdateInfo.NOT_UPDATE;
- }
-
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsMixin.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsMixin.java
deleted file mode 100755
index b85ba8a..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsMixin.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.util.Properties;
-
-/**
- * Represents an XML node in an SDK repository that has a min-tools-rev requirement.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-class MinToolsMixin implements IMinToolsDependency {
-
- /**
- * The minimal revision of the tools package required by this extra package, if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- private final FullRevision mMinToolsRevision;
-
- /**
- * Creates a new mixin from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param packageNode The XML element being parsed.
- */
- MinToolsMixin(Node packageNode) {
-
- mMinToolsRevision = PackageParserUtils.parseFullRevisionElement(
- PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_MIN_TOOLS_REV));
- }
-
- /**
- * Manually create a new mixin with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MinToolsMixin(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
-
- String revStr = Package.getProperty(props, PkgProps.MIN_TOOLS_REV, null);
-
- FullRevision rev = MIN_TOOLS_REV_NOT_SPECIFIED;
- if (revStr != null) {
- try {
- rev = FullRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- mMinToolsRevision = rev;
- }
-
- /**
- * The minimal revision of the tools package required by this extra package, if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public FullRevision getMinToolsRevision() {
- return mMinToolsRevision;
- }
-
- public void saveProperties(Properties props) {
- if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
- props.setProperty(PkgProps.MIN_TOOLS_REV, getMinToolsRevision().toShortString());
- }
- }
-
- @Override
- public int hashCode() {
- return hashCode(super.hashCode());
- }
-
- int hashCode(int superHashCode) {
- final int prime = 31;
- int result = superHashCode;
- result = prime * result + ((mMinToolsRevision == null) ? 0 : mMinToolsRevision.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof IMinToolsDependency)) {
- return false;
- }
- IMinToolsDependency other = (IMinToolsDependency) obj;
- if (mMinToolsRevision == null) {
- if (other.getMinToolsRevision() != null) {
- return false;
- }
- } else if (!mMinToolsRevision.equals(other.getMinToolsRevision())) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
deleted file mode 100755
index 8ea682a..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-
-import org.w3c.dom.Node;
-
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents an XML node in an SDK repository that has a min-tools-rev requirement.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public abstract class MinToolsPackage extends MajorRevisionPackage implements IMinToolsDependency {
-
- private final MinToolsMixin mMinToolsMixin;
-
- /**
- * Creates a new package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- MinToolsPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mMinToolsMixin = new MinToolsMixin(packageNode);
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MinToolsPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source, props, revision, license, description, descUrl, archiveOsPath);
-
- mMinToolsMixin = new MinToolsMixin(
- source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
- }
-
- /**
- * The minimal revision of the tools package required by this extra package, if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public FullRevision getMinToolsRevision() {
- return mMinToolsMixin.getMinToolsRevision();
- }
-
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
- mMinToolsMixin.saveProperties(props);
- }
-
- @Override
- public int hashCode() {
- return mMinToolsMixin.hashCode(super.hashCode());
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof MinToolsPackage)) {
- return false;
- }
- return mMinToolsMixin.equals(obj);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionPackage.java
deleted file mode 100755
index fc5a5dd..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionPackage.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a package in an SDK repository that has a {@link NoPreviewRevision},
- * which is a single major.minor.micro revision number and no preview.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public abstract class NoPreviewRevisionPackage extends Package {
-
- private final NoPreviewRevision mRevision;
-
- /**
- * Creates a new package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- NoPreviewRevisionPackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mRevision = PackageParserUtils.parseNoPreviewRevisionElement(
- PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_REVISION));
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public NoPreviewRevisionPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source, props, revision, license, description, descUrl, archiveOsPath);
-
- String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
-
- NoPreviewRevision rev = null;
- if (revStr != null) {
- try {
- rev = NoPreviewRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
- if (rev == null) {
- rev = new NoPreviewRevision(revision);
- }
-
- mRevision = rev;
- }
-
- /**
- * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
- * Can be 0 if this is a local package of unknown revision.
- */
- @Override
- public NoPreviewRevision getRevision() {
- return mRevision;
- }
-
-
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
- props.setProperty(PkgProps.PKG_REVISION, mRevision.toString());
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mRevision == null) ? 0 : mRevision.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof NoPreviewRevisionPackage)) {
- return false;
- }
- NoPreviewRevisionPackage other = (NoPreviewRevisionPackage) obj;
- if (mRevision == null) {
- if (other.mRevision != null) {
- return false;
- }
- } else if (!mRevision.equals(other.mRevision)) {
- return false;
- }
- return true;
- }
-
- @Override
- public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
- if (replacementPackage == null) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check they are the same item.
- if (!sameItemAs(replacementPackage)) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check revision number
- if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
- return UpdateInfo.UPDATE;
- }
-
- // not an upgrade but not incompatible either.
- return UpdateInfo.NOT_UPDATE;
- }
-
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/Package.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/Package.java
deleted file mode 100755
index 4cf6f9b..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/Package.java
+++ /dev/null
@@ -1,902 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.repository.IListDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkAddonSource;
-import com.android.sdklib.internal.repository.sources.SdkRepoSource;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.*;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * A {@link Package} is the base class for "something" that can be downloaded from
- * the SDK repository.
- * <p/>
- * A package has some attributes (revision, description) and a list of archives
- * which represent the downloadable bits.
- * <p/>
- * Packages are contained by a {@link SdkSource} (a download site).
- * <p/>
- * Derived classes must implement the {@link IDescription} methods.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public abstract class Package implements IDescription, IListDescription, Comparable<Package> {
-
- private final String mObsolete;
- private final License mLicense;
- private final String mListDisplay;
- private final String mDescription;
- private final String mDescUrl;
- @Deprecated
- private final String mReleaseNote;
- @Deprecated
- private final String mReleaseUrl;
- private final Archive[] mArchives;
- private final SdkSource mSource;
-
-
- // figure if we'll need to set the unix permissions
- private static final boolean sUsingUnixPerm =
- SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ||
- SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX;
-
- /**
- * Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can
- * differentiate between a package that is totally incompatible, and one that is the same item
- * but just not an update.
- * @see #canBeUpdatedBy(Package)
- */
- public enum UpdateInfo {
- /** Means that the 2 packages are not the same thing */
- INCOMPATIBLE,
- /** Means that the 2 packages are the same thing but one does not upgrade the other.
- * </p>
- * TODO: this name is confusing. We need to dig deeper. */
- NOT_UPDATE,
- /** Means that the 2 packages are the same thing, and one is the upgrade of the other */
- UPDATE
- }
-
- /**
- * Creates a new package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- mSource = source;
- mListDisplay =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_LIST_DISPLAY);
- mDescription =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);
- mDescUrl =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);
- mReleaseNote =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);
- mReleaseUrl =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);
- mObsolete =
- PackageParserUtils.getOptionalXmlString(packageNode, SdkRepoConstants.NODE_OBSOLETE);
-
- mLicense = parseLicense(packageNode, licenses);
- mArchives = parseArchives(
- PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_ARCHIVES));
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public Package(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
-
- if (description == null) {
- description = "";
- }
- if (descUrl == null) {
- descUrl = "";
- }
-
- license = getProperty(props, PkgProps.PKG_LICENSE, license);
- if (license != null) {
- mLicense = new License(license, getProperty(props, PkgProps.PKG_LICENSE_REF, null));
- }
- else {
- mLicense = null;
- }
- mListDisplay = getProperty(props, PkgProps.PKG_LIST_DISPLAY, ""); //$NON-NLS-1$
- mDescription = getProperty(props, PkgProps.PKG_DESC, description);
- mDescUrl = getProperty(props, PkgProps.PKG_DESC_URL, descUrl);
- mReleaseNote = getProperty(props, PkgProps.PKG_RELEASE_NOTE, ""); //$NON-NLS-1$
- mReleaseUrl = getProperty(props, PkgProps.PKG_RELEASE_URL, ""); //$NON-NLS-1$
- mObsolete = getProperty(props, PkgProps.PKG_OBSOLETE, null);
-
- // If source is null and we can find a source URL in the properties, generate
- // a dummy source just to store the URL. This allows us to easily remember where
- // a package comes from.
- String srcUrl = getProperty(props, PkgProps.PKG_SOURCE_URL, null);
- if (props != null && source == null && srcUrl != null) {
- // Both Addon and Extra packages can come from an addon source.
- // For Extras, we can tell by looking at the source URL.
- if (this instanceof AddonPackage ||
- ((this instanceof ExtraPackage) &&
- srcUrl.endsWith(SdkAddonConstants.URL_DEFAULT_FILENAME))) {
- source = new SdkAddonSource(srcUrl, null /*uiName*/);
- } else {
- source = new SdkRepoSource(srcUrl, null /*uiName*/);
- }
- }
- mSource = source;
-
- // Note: if archiveOsPath is non-null, this makes a local archive (e.g. a locally
- // installed package.) If it's null, this makes a remote archive.
- mArchives = initializeArchives(props, archiveOsPath);
- }
-
- /**
- * Returns the {@link IPkgDesc} describing this package's meta data.
- *
- * @return A non-null {@link IPkgDesc}.
- */
- @NonNull
- public abstract IPkgDesc getPkgDesc();
-
- /**
- * Called by the constructor to get the initial {@link #mArchives} array.
- * <p/>
- * This is invoked by the local-package constructor and allows mock testing
- * classes to override the archives created.
- * This is an <em>implementation</em> details and clients must <em>not</em>
- * rely on this.
- *
- * @return Always return a non-null array. The array may be empty.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return new Archive[] {
- new Archive(this,
- props,
- archiveOsPath) };
- }
-
- /**
- * Utility method that returns a property from a {@link Properties} object.
- * Returns the default value if props is null or if the property is not defined.
- *
- * @param props The {@link Properties} to search into.
- * If null, the default value is returned.
- * @param propKey The name of the property. Must not be null.
- * @param defaultValue The default value to return if {@code props} is null or if the
- * key is not found. Can be null.
- * @return The string value of the given key in the properties, or null if the key
- * isn't found or if {@code props} is null.
- */
- @Nullable
- static String getProperty(
- @Nullable Properties props,
- @NonNull String propKey,
- @Nullable String defaultValue) {
- return PackageParserUtils.getProperty(props, propKey, defaultValue);
- }
-
- /**
- * Utility method that returns an integer property from a {@link Properties} object.
- * Returns the default value if props is null or if the property is not defined or
- * cannot be parsed to an integer.
- *
- * @param props The {@link Properties} to search into.
- * If null, the default value is returned.
- * @param propKey The name of the property. Must not be null.
- * @param defaultValue The default value to return if {@code props} is null or if the
- * key is not found. Can be null.
- * @return The integer value of the given key in the properties, or the {@code defaultValue}.
- */
- static int getPropertyInt(
- @Nullable Properties props,
- @NonNull String propKey,
- int defaultValue) {
- return PackageParserUtils.getPropertyInt(props, propKey, defaultValue);
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- public void saveProperties(@NonNull Properties props) {
- if (mLicense != null) {
- String license = mLicense.getLicense();
- if (license != null && !license.isEmpty()) {
- props.setProperty(PkgProps.PKG_LICENSE, license);
- }
- String licenseRef = mLicense.getLicenseRef();
- if (licenseRef != null && !licenseRef.isEmpty()) {
- props.setProperty(PkgProps.PKG_LICENSE_REF, licenseRef);
- }
- }
- if (mListDisplay != null && !mListDisplay.isEmpty()) {
- props.setProperty(PkgProps.PKG_LIST_DISPLAY, mListDisplay);
- }
- if (mDescription != null && !mDescription.isEmpty()) {
- props.setProperty(PkgProps.PKG_DESC, mDescription);
- }
- if (mDescUrl != null && !mDescUrl.isEmpty()) {
- props.setProperty(PkgProps.PKG_DESC_URL, mDescUrl);
- }
-
- if (mReleaseNote != null && !mReleaseNote.isEmpty()) {
- props.setProperty(PkgProps.PKG_RELEASE_NOTE, mReleaseNote);
- }
- if (mReleaseUrl != null && !mReleaseUrl.isEmpty()) {
- props.setProperty(PkgProps.PKG_RELEASE_URL, mReleaseUrl);
- }
- if (mObsolete != null) {
- props.setProperty(PkgProps.PKG_OBSOLETE, mObsolete);
- }
- if (mSource != null) {
- props.setProperty(PkgProps.PKG_SOURCE_URL, mSource.getUrl());
- }
- }
-
- /**
- * Parses the uses-licence node of this package, if any, and returns the license
- * definition if there's one. Returns null if there's no uses-license element or no
- * license of this name defined.
- */
- @Nullable
- private License parseLicense(@NonNull Node packageNode, @NonNull Map<String, String> licenses) {
- Node usesLicense =
- PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_USES_LICENSE);
- if (usesLicense != null) {
- Node ref = usesLicense.getAttributes().getNamedItem(SdkRepoConstants.ATTR_REF);
- if (ref != null) {
- String licenseRef = ref.getNodeValue();
- return new License(licenses.get(licenseRef), licenseRef);
- }
- }
- return null;
- }
-
- /**
- * Parses an XML node to process the <archives> element.
- * Always return a non-null array. The array may be empty.
- */
- @NonNull
- private Archive[] parseArchives(@NonNull Node archivesNode) {
- ArrayList<Archive> archives = new ArrayList<Archive>();
-
- if (archivesNode != null) {
- String nsUri = archivesNode.getNamespaceURI();
- for(Node child = archivesNode.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
-
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- SdkRepoConstants.NODE_ARCHIVE.equals(child.getLocalName())) {
- archives.add(parseArchive(child));
- }
- }
- }
-
- return archives.toArray(new Archive[archives.size()]);
- }
-
- /**
- * Parses one <archive> element from an <archives> container.
- */
- @NonNull
- private Archive parseArchive(@NonNull Node archiveNode) {
- Archive a = new Archive(
- this,
- PackageParserUtils.parseArchFilter(archiveNode),
- PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),
- PackageParserUtils.getXmlLong (archiveNode, SdkRepoConstants.NODE_SIZE, 0),
- PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)
- );
-
- return a;
- }
-
- /**
- * Returns the source that created (and owns) this package. Can be null.
- */
- @Nullable
- public SdkSource getParentSource() {
- return mSource;
- }
-
- /**
- * Returns true if the package is deemed obsolete, that is it contains an
- * actual <code><obsolete></code> element.
- */
- public boolean isObsolete() {
- return mObsolete != null;
- }
-
- /**
- * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
- * Can be 0 if this is a local package of unknown revision.
- */
- @NonNull
- public abstract FullRevision getRevision();
-
- /**
- * Returns the optional description for all packages (platform, add-on, tool, doc) or
- * for a lib. It is null if the element has not been specified in the repository XML.
- */
- @Nullable
- public License getLicense() {
- return mLicense;
- }
-
- /**
- * Returns the optional description for all packages (platform, add-on, tool, doc) or
- * for a lib. This is the raw description available from the XML meta data and is typically
- * only used internally.
- * <p/>
- * For actual display in the UI, use the methods from {@link IDescription} instead.
- * <p/>
- * Can be empty but not null.
- */
- @NonNull
- public String getDescription() {
- return mDescription;
- }
-
- /**
- * Returns the optional list-display for all packages as defined in the XML meta data
- * and is typically only used internally.
- * <p/>
- * For actual display in the UI, use {@link IListDescription} instead.
- * <p/>
- * Can be empty but not null.
- */
- @NonNull
- public String getListDisplay() {
- return mListDisplay;
- }
-
- /**
- * Returns the optional description URL for all packages (platform, add-on, tool, doc).
- * Can be empty but not null.
- */
- @NonNull
- public String getDescUrl() {
- return mDescUrl;
- }
-
- /**
- * Returns the optional release note for all packages (platform, add-on, tool, doc) or
- * for a lib. Can be empty but not null.
- */
- @NonNull
- public String getReleaseNote() {
- return mReleaseNote;
- }
-
- /**
- * Returns the optional release note URL for all packages (platform, add-on, tool, doc).
- * Can be empty but not null.
- */
- @NonNull
- public String getReleaseNoteUrl() {
- return mReleaseUrl;
- }
-
- /**
- * Returns the archives defined in this package.
- * Can be an empty array but not null.
- */
- @NonNull
- public Archive[] getArchives() {
- return mArchives;
- }
-
- /**
- * Returns true if this package contains the exact given archive.
- * Important: This compares object references, not object equality.
- */
- public boolean hasArchive(Archive archive) {
- for (Archive a : mArchives) {
- if (a == archive) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns whether the {@link Package} has at least one {@link Archive} compatible with
- * the host platform.
- */
- public boolean hasCompatibleArchive() {
- for (Archive archive : mArchives) {
- if (archive.isCompatible()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns a short, reasonably unique string identifier that can be used
- * to identify this package when installing from the command-line interface.
- * {@code 'android list sdk'} will show these IDs and then in turn they can
- * be provided to {@code 'android update sdk --no-ui --filter'} to select
- * some specific packages.
- * <p/>
- * The identifiers must have the following properties: <br/>
- * - They must contain only simple alphanumeric characters. <br/>
- * - Commas, whitespace and any special character that could be obviously problematic
- * to a shell interface should be avoided (so dash/underscore are OK, but things
- * like colon, pipe or dollar should be avoided.) <br/>
- * - The name must be consistent across calls and reasonably unique for the package
- * type. Collisions can occur but should be rare. <br/>
- * - Different package types should have a clearly different name pattern. <br/>
- * - The revision number should not be included, as this would prevent updates
- * from being automated (which is the whole point.) <br/>
- * - It must remain reasonably human readable. <br/>
- * - If no such id can exist (for example for a local package that cannot be installed)
- * then an empty string should be returned. Don't return null.
- * <p/>
- * Important: This is <em>not</em> a strong unique identifier for the package.
- * If you need a strong unique identifier, you should use {@link #comparisonKey()}
- * and the {@link Comparable} interface.
- */
- @NonNull
- public abstract String installId();
-
- /**
- * Returns the short description of the source, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging.
- * For UI display, use the {@link IDescription} interface.
- */
- @NonNull
- @Override
- public String toString() {
- String s = getShortDescription();
- if (s != null) {
- return s;
- }
- return super.toString();
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * Should not be empty. Can never be null.
- * <p/>
- * Derived classes should use {@link #getListDisplay()} if it's not empty.
- * <p/>
- * When it is empty, the default behavior is to recompute a string that depends
- * on the package type.
- * <p/>
- * In both cases, the string should indicate whether the package is marked as obsolete.
- * <p/>
- * Note that this is the "base" name for the package with no specific revision nor API
- * mentioned as this is likely used in a table that will already provide these details.
- * In contrast, {@link #getShortDescription()} should be used if you want more details
- * such as the package revision number or the API, if applicable, all in the same string.
- */
- @NonNull
- @Override
- public abstract String getListDescription();
-
- /**
- * Returns a short description for an {@link IDescription}.
- * Can be empty but not null.
- */
- @NonNull
- @Override
- public abstract String getShortDescription();
-
- /**
- * Returns a long description for an {@link IDescription}.
- * Can be empty but not null.
- */
- @NonNull
- @Override
- public String getLongDescription() {
- StringBuilder sb = new StringBuilder();
-
- String s = getDescription();
- if (s != null) {
- sb.append(s);
- }
- if (sb.length() > 0) {
- sb.append("\n");
- }
-
- sb.append(String.format("Revision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : ""));
-
- s = getDescUrl();
- if (s != null && !s.isEmpty()) {
- sb.append(String.format("\n\nMore information at %1$s", s));
- }
-
- s = getReleaseNote();
- if (s != null && !s.isEmpty()) {
- sb.append("\n\nRelease note:\n").append(s);
- }
-
- s = getReleaseNoteUrl();
- if (s != null && !s.isEmpty()) {
- sb.append("\nRelease note URL: ").append(s);
- }
-
- return sb.toString();
- }
-
- /**
- * A package is local (that is 'installed locally') if it contains a single
- * archive that is local. If not local, it's a remote package, only available
- * on a remote source for download and installation.
- */
- public boolean isLocal() {
- return mArchives.length == 1 && mArchives[0].isLocal();
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * Some types of packages install in a fix location, for example docs and tools.
- * In this case the returned folder may already exist with a different archive installed
- * at the desired location. <br/>
- * For other packages types, such as add-on or platform, the folder name is only partially
- * relevant to determine the content and thus a real check will be done to provide an
- * existing or new folder depending on the current content of the SDK.
- * <p/>
- * Note that the installer *will* create all directories returned here just before
- * installation so this method must not attempt to create them.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @NonNull
- public abstract File getInstallFolder(String osSdkRoot, SdkManager sdkManager);
-
- /**
- * Hook called right before an archive is installed. The archive has already
- * been downloaded successfully and will be installed in the directory specified by
- * <var>installFolder</var> when this call returns.
- * <p/>
- * The hook lets the package decide if installation of this specific archive should
- * be continue. The installer will still install the remaining packages if possible.
- * <p/>
- * The base implementation always return true.
- * <p/>
- * Note that the installer *will* create all directories specified by
- * {@link #getInstallFolder} just before installation, so they must not be
- * created here. This is also called before the previous install dir is removed
- * so the previous content is still there during upgrade.
- *
- * @param archive The archive that will be installed
- * @param monitor The {@link ITaskMonitor} to display errors.
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param installFolder The folder where the archive will be installed. Note that this
- * is <em>not</em> the folder where the archive was temporary
- * unzipped. The installFolder, if it exists, contains the old
- * archive that will soon be replaced by the new one.
- * @return True if installing this archive shall continue, false if it should be skipped.
- */
- public boolean preInstallHook(Archive archive, ITaskMonitor monitor,
- String osSdkRoot, File installFolder) {
- // Nothing to do in base class.
- return true;
- }
-
- /**
- * Hook called right after a file has been unzipped (during an install).
- * <p/>
- * The base class implementation makes sure to properly adjust set executable
- * permission on Linux and MacOS system if the zip entry was marked as +x.
- *
- * @param archive The archive that is being installed.
- * @param monitor The {@link ITaskMonitor} to display errors.
- * @param fileOp The {@link IFileOp} used by the archive installer.
- * @param unzippedFile The file that has just been unzipped in the install temp directory.
- * @param zipEntry The {@link ZipArchiveEntry} that has just been unzipped.
- */
- public void postUnzipFileHook(
- Archive archive,
- ITaskMonitor monitor,
- IFileOp fileOp,
- File unzippedFile,
- ZipArchiveEntry zipEntry) {
-
- // if needed set the permissions.
- if (sUsingUnixPerm && fileOp.isFile(unzippedFile)) {
- // get the mode and test if it contains the executable bit
- int mode = zipEntry.getUnixMode();
- if ((mode & 0111) != 0) {
- try {
- fileOp.setExecutablePermission(unzippedFile);
- } catch (IOException ignore) {}
- }
- }
-
- }
-
- /**
- * Hook called right after an archive has been installed.
- *
- * @param archive The archive that has been installed.
- * @param monitor The {@link ITaskMonitor} to display errors.
- * @param installFolder The folder where the archive was successfully installed.
- * Null if the installation failed, in case the archive needs to
- * do some cleanup after <code>preInstallHook</code>.
- */
- public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
- // Nothing to do in base class.
- }
-
- /**
- * Returns whether the give package represents the same item as the current package.
- * <p/>
- * Two packages are considered the same if they represent the same thing, except for the
- * revision number.
- * @param pkg the package to compare.
- * @return true if the item as equivalent.
- */
- public abstract boolean sameItemAs(Package pkg);
-
- /**
- * Computes whether the given package is a suitable update for the current package.
- * <p/>
- * An update is just that: a new package that supersedes the current one. If the new
- * package does not represent the same item or if it has the same or lower revision as the
- * current one, it's not an update.
- *
- * @param replacementPackage The potential replacement package.
- * @return One of the {@link UpdateInfo} values.
- *
- * @see #sameItemAs(Package)
- */
- @NonNull
- public abstract UpdateInfo canBeUpdatedBy(Package replacementPackage);
-
- /**
- * Returns an ordering <b>suitable for display</b> like this: <br/>
- * - Tools <br/>
- * - Platform-Tools <br/>
- * - Built-Tools <br/>
- * - Docs. <br/>
- * - Platform n preview <br/>
- * - Platform n <br/>
- * - Platform n-1 <br/>
- * - Samples packages <br/>
- * - Add-on based on n preview <br/>
- * - Add-on based on n <br/>
- * - Add-on based on n-1 <br/>
- * - Extra packages <br/>
- * <p/>
- * Important: this must NOT be used to compare if two packages are the same thing.
- * This is achieved by {@link #sameItemAs(Package)} or {@link #canBeUpdatedBy(Package)}.
- * <p/>
- * The order done here is suitable for display, and this may not be the appropriate
- * order when comparing whether packages are equal or of greater revision -- if you need
- * to compare revisions, then use {@link #getRevision()}{@code .compareTo(rev)} directly.
- * <p/>
- * This {@link #compareTo(Package)} method is purely an implementation detail to
- * perform the right ordering of the packages in the list of available or installed packages.
- * <p/>
- * <em>Important</em>: Derived classes should consider overriding {@link #comparisonKey()}
- * instead of this method.
- */
- @Override
- public int compareTo(Package other) {
- String s1 = this.comparisonKey();
- String s2 = other.comparisonKey();
-
- int r = s1.compareTo(s2);
- return r;
- }
-
- /**
- * Computes a comparison key for each package used by {@link #compareTo(Package)}.
- * The key is a string.
- * The base package class return a string that encodes the package type,
- * the revision number and the platform version, if applicable, in the form:
- * <pre>
- * t:N|v:NNNN.P|r:NNNN|
- * </pre>
- * All fields must start by a "letter colon" prefix and end with a vertical pipe (|, ASCII 124).
- * <p/>
- * The string format <em>may</em> change between releases and clients should not
- * store them outside of the session or expect them to be consistent between
- * different releases. They are purely an internal implementation details of the
- * {@link #compareTo(Package)} method.
- * <p/>
- * Derived classes should get the string from the super class and then append
- * or <em>insert</em> their own |-separated content.
- * For example an extra vendor name & path can be inserted before the revision
- * number, since it has more sorting weight.
- */
- @NonNull
- protected String comparisonKey() {
-
- StringBuilder sb = new StringBuilder();
-
- sb.append("t:"); //$NON-NLS-1$
- if (this instanceof ToolPackage) {
- sb.append(0);
- } else if (this instanceof PlatformToolPackage) {
- sb.append(1);
- } else if (this instanceof BuildToolPackage) {
- sb.append(2);
- } else if (this instanceof DocPackage) {
- sb.append(3);
- } else if (this instanceof PlatformPackage) {
- sb.append(4);
- } else if (this instanceof SamplePackage) {
- sb.append(5);
- } else if ((this instanceof SystemImagePackage) && ((SystemImagePackage) this).isPlatform()) {
- sb.append(6);
- } else if (this instanceof AddonPackage) {
- sb.append(7);
- } else if (this instanceof SystemImagePackage) {
- sb.append(8);
- } else {
- // extras and everything else
- sb.append(9);
- }
-
-
- // We insert the package version here because it is more important
- // than the revision number.
- // In the list display, we want package version to be sorted
- // top-down, so we'll use 10k-api as the sorting key. The day we
- // reach 10k APIs, we'll need to revisit this.
- sb.append("|v:"); //$NON-NLS-1$
- if (this instanceof IAndroidVersionProvider) {
- AndroidVersion v = ((IAndroidVersionProvider) this).getAndroidVersion();
-
- sb.append(String.format("%1$04d.%2$d", //$NON-NLS-1$
- 10000 - v.getApiLevel(),
- v.isPreview() ? 1 : 0
- ));
- }
-
- // Append revision number
- // Note: pad revision numbers to 4 digits (e.g. make sure 12>3 by comparing 0012 and 0003)
- sb.append("|r:"); //$NON-NLS-1$
- FullRevision rev = getRevision();
- sb.append(String.format("%1$04d.%2$04d.%3$04d.", //$NON-NLS-1$
- rev.getMajor(),
- rev.getMinor(),
- rev.getMicro()));
- // Hack: When comparing packages for installation purposes, we want to treat
- // "final releases" packages as more important than rc/preview packages.
- // However like for the API level above, when sorting for list display purposes
- // we want the final release package listed before its rc/preview packages.
- if (rev.isPreview()) {
- sb.append(rev.getPreview());
- } else {
- sb.append('0'); // 0=Final (!preview), to make "18.0" < "18.1" (18 Final < 18 RC1)
- }
-
- sb.append('|');
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + Arrays.hashCode(mArchives);
- result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode());
- result = prime * result + getRevision().hashCode();
- result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof Package)) {
- return false;
- }
- Package other = (Package) obj;
- if (!Arrays.equals(mArchives, other.mArchives)) {
- return false;
- }
- if (mObsolete == null) {
- if (other.mObsolete != null) {
- return false;
- }
- } else if (!mObsolete.equals(other.mObsolete)) {
- return false;
- }
- if (!getRevision().equals(other.getRevision())) {
- return false;
- }
- if (mSource == null) {
- if (other.mSource != null) {
- return false;
- }
- } else if (!mSource.equals(other.mSource)) {
- return false;
- }
- return true;
- }
-
- // TODO(jbakermalone): This is moved here from the more logical location in PkgDesc.Builder since Package will soon be forked into
- // studio and this version deprecated, whereas PkgDesc will not.
- protected PkgDesc.Builder setDescriptions(PkgDesc.Builder builder) {
- builder.setDescriptionShort(getShortDescription());
- builder.setDescriptionUrl(getDescUrl());
- builder.setListDisplay(getListDisplay());
- builder.setIsObsolete(isObsolete());
- builder.setLicense(getLicense());
- return builder;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PackageParserUtils.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
deleted file mode 100755
index eeee4a9..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.repository.archives.ArchFilter;
-import com.android.sdklib.internal.repository.archives.BitSize;
-import com.android.sdklib.internal.repository.archives.HostOs;
-import com.android.sdklib.internal.repository.archives.LegacyArch;
-import com.android.sdklib.internal.repository.archives.LegacyOs;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.util.Properties;
-
-/**
- * Misc utilities to help extracting elements and attributes out of a repository XML document.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class PackageParserUtils {
-
- /**
- * Parse the {@link ArchFilter} of an <archive> element..
- * <p/>
- * Starting with repo schema 10, add-on schema 7 and sys-img schema 3, this is done using
- * specific optional elements contained within the <archive> element.
- * <p/>
- * If none of the new element are defined, for backward compatibility we try to find
- * the previous style XML attributes "os" and "arch" in the <archive> element.
- *
- * @param archiveNode
- * @return A new {@link ArchFilter}
- */
- @NonNull
- public static ArchFilter parseArchFilter(@NonNull Node archiveNode) {
- String hos = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_HOST_OS);
- String hb = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_HOST_BITS);
- String jb = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_JVM_BITS);
- String mjv = PackageParserUtils.getOptionalXmlString(archiveNode, SdkRepoConstants.NODE_MIN_JVM_VERSION);
-
- if (hos != null || hb != null || jb != null || mjv != null) {
- NoPreviewRevision rev = null;
- try {
- rev = NoPreviewRevision.parseRevision(mjv);
- } catch (NumberFormatException ignore) {}
-
- return new ArchFilter(
- HostOs.fromXmlName(hos),
- BitSize.fromXmlName(hb),
- BitSize.fromXmlName(jb),
- rev);
- }
-
- Properties props = new Properties();
-
- LegacyOs o = (LegacyOs) PackageParserUtils.getEnumAttribute(
- archiveNode, SdkRepoConstants.LEGACY_ATTR_OS, LegacyOs.values(), null);
- if (o != null) {
- props.setProperty(ArchFilter.LEGACY_PROP_OS, o.toString());
- }
-
- LegacyArch a = (LegacyArch) PackageParserUtils.getEnumAttribute(
- archiveNode, SdkRepoConstants.LEGACY_ATTR_ARCH, LegacyArch.values(), null);
- if (a != null) {
- props.setProperty(ArchFilter.LEGACY_PROP_ARCH, a.toString());
- }
-
- return new ArchFilter(props);
- }
-
- /**
- * Parses a full revision element such as <revision> or <min-tools-rev>.
- * This supports both the single-integer format as well as the full revision
- * format with major/minor/micro/preview sub-elements.
- *
- * @param revisionNode The node to parse.
- * @return A new {@link FullRevision}. If parsing failed, major is set to
- * {@link FullRevision#MISSING_MAJOR_REV}.
- */
- public static FullRevision parseFullRevisionElement(Node revisionNode) {
- // This needs to support two modes:
- // - For repository XSD >= 7, <revision> contains sub-elements such as <major> or <minor>.
- // - Otherwise for repository XSD < 7, <revision> contains an integer.
- // The <major> element is mandatory, so it's easy to distinguish between both cases.
- int major = FullRevision.MISSING_MAJOR_REV,
- minor = FullRevision.IMPLICIT_MINOR_REV,
- micro = FullRevision.IMPLICIT_MICRO_REV,
- preview = FullRevision.NOT_A_PREVIEW;
-
- if (revisionNode != null) {
- if (PackageParserUtils.findChildElement(revisionNode,
- SdkRepoConstants.NODE_MAJOR_REV) != null) {
- // <revision> has a <major> sub-element, so it's a repository XSD >= 7.
- major = PackageParserUtils.getXmlInt(revisionNode,
- SdkRepoConstants.NODE_MAJOR_REV, FullRevision.MISSING_MAJOR_REV);
- minor = PackageParserUtils.getXmlInt(revisionNode,
- SdkRepoConstants.NODE_MINOR_REV, FullRevision.IMPLICIT_MINOR_REV);
- micro = PackageParserUtils.getXmlInt(revisionNode,
- SdkRepoConstants.NODE_MICRO_REV, FullRevision.IMPLICIT_MICRO_REV);
- preview = PackageParserUtils.getXmlInt(revisionNode,
- SdkRepoConstants.NODE_PREVIEW, FullRevision.NOT_A_PREVIEW);
- } else {
- try {
- String majorStr = revisionNode.getTextContent().trim();
- major = Integer.parseInt(majorStr);
- } catch (Exception e) {
- }
- }
- }
-
- return new FullRevision(major, minor, micro, preview);
- }
-
- /**
- * Parses a no-preview revision element such as <revision>>.
- * This supports both the single-integer format as well as the full revision
- * format with major/minor/micro sub-elements.
- *
- * @param revisionNode The node to parse.
- * @return A new {@link NoPreviewRevision}. If parsing failed, major is set to
- * {@link FullRevision#MISSING_MAJOR_REV}.
- */
- public static NoPreviewRevision parseNoPreviewRevisionElement(Node revisionNode) {
- // This needs to support two modes:
- // - For addon XSD >= 6, <revision> contains sub-elements such as <major> or <minor>.
- // - Otherwise for addon XSD < 6, <revision> contains an integer.
- // The <major> element is mandatory, so it's easy to distinguish between both cases.
- int major = FullRevision.MISSING_MAJOR_REV,
- minor = FullRevision.IMPLICIT_MINOR_REV,
- micro = FullRevision.IMPLICIT_MICRO_REV;
-
- if (revisionNode != null) {
- if (PackageParserUtils.findChildElement(revisionNode,
- SdkRepoConstants.NODE_MAJOR_REV) != null) {
- // <revision> has a <major> sub-element, so it's a repository XSD >= 7.
- major = PackageParserUtils.getXmlInt(revisionNode,
- SdkRepoConstants.NODE_MAJOR_REV, FullRevision.MISSING_MAJOR_REV);
- minor = PackageParserUtils.getXmlInt(revisionNode,
- SdkRepoConstants.NODE_MINOR_REV, FullRevision.IMPLICIT_MINOR_REV);
- micro = PackageParserUtils.getXmlInt(revisionNode,
- SdkRepoConstants.NODE_MICRO_REV, FullRevision.IMPLICIT_MICRO_REV);
- } else {
- try {
- String majorStr = revisionNode.getTextContent().trim();
- major = Integer.parseInt(majorStr);
- } catch (Exception e) {
- }
- }
- }
-
- return new NoPreviewRevision(major, minor, micro);
- }
-
- /**
- * Returns the first child element with the given XML local name and the same NS URI.
- * If xmlLocalName is null, returns the very first child element.
- */
- public static Node findChildElement(Node node, String xmlLocalName) {
- if (node != null) {
- String nsUri = node.getNamespaceURI();
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- String nsUriChild = child.getNamespaceURI();
- if ((nsUri == null && nsUriChild == null) ||
- (nsUri != null && nsUri.equals(nsUriChild))) {
- if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {
- return child;
- }
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Retrieves the value of that XML element as a string.
- * Returns an empty string whether the element is missing or empty,
- * so you can't tell the difference.
- * <p/>
- * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the
- * element is missing versus empty.
- *
- * @param node The XML <em>parent</em> node to parse.
- * @param xmlLocalName The XML local name to find in the parent node.
- * @return The text content of the element. Returns an empty string whether the element
- * is missing or empty, so you can't tell the difference.
- */
- public static String getXmlString(Node node, String xmlLocalName) {
- return getXmlString(node, xmlLocalName, ""); //$NON-NLS-1$
- }
-
- /**
- * Retrieves the value of that XML element as a string.
- * Returns the defaultValue if the element is missing or empty.
- * <p/>
- * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the
- * element is missing versus empty.
- *
- * @param node The XML <em>parent</em> node to parse.
- * @param xmlLocalName The XML local name to find in the parent node.
- * @param defaultValue A default value to return if the element is missing.
- * @return The text content of the element
- * or the defaultValue if the element is missing or empty.
- */
- public static String getXmlString(Node node, String xmlLocalName, String defaultValue) {
- Node child = findChildElement(node, xmlLocalName);
- String content = child == null ? null : child.getTextContent();
- return content == null || content.isEmpty() ? defaultValue : content;
- }
-
- /**
- * Retrieves the value of that XML element as a string.
- * Returns null when the element is missing, so you can tell between a missing element
- * and an empty one.
- * <p/>
- * Note: use {@link #getXmlString(Node, String)} if you don't need to know when the
- * element is missing versus empty.
- *
- * @param node The XML <em>parent</em> node to parse.
- * @param xmlLocalName The XML local name to find in the parent node.
- * @return The text content of the element. Returns null when the element is missing.
- * Returns an empty string whether the element is present but empty.
- */
- public static String getOptionalXmlString(Node node, String xmlLocalName) {
- Node child = findChildElement(node, xmlLocalName);
- return child == null ? null : child.getTextContent(); //$NON-NLS-1$
- }
-
- /**
- * Retrieves the value of that XML element as an integer.
- * Returns the default value when the element is missing or is not an integer.
- */
- public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {
- String s = getXmlString(node, xmlLocalName);
- try {
- return Integer.parseInt(s);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- /**
- * Retrieves the value of that XML element as a long.
- * Returns the default value when the element is missing or is not an integer.
- */
- public static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {
- String s = getXmlString(node, xmlLocalName);
- try {
- return Long.parseLong(s);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- /**
- * Retrieve an attribute which value must match one of the given enums using a
- * case-insensitive name match.
- *
- * Returns defaultValue if the attribute does not exist or its value does not match
- * the given enum values.
- */
- public static Object getEnumAttribute(
- Node archiveNode,
- String attrName,
- Object[] values,
- Object defaultValue) {
-
- Node attr = archiveNode.getAttributes().getNamedItem(attrName);
- if (attr != null) {
- String found = attr.getNodeValue();
- for (Object value : values) {
- if (value.toString().equalsIgnoreCase(found)) {
- return value;
- }
- }
- }
-
- return defaultValue;
- }
-
- /**
- * Utility method that returns a property from a {@link Properties} object.
- * Returns the default value if props is null or if the property is not defined.
- *
- * @param props The {@link Properties} to search into.
- * If null, the default value is returned.
- * @param propKey The name of the property. Must not be null.
- * @param defaultValue The default value to return if {@code props} is null or if the
- * key is not found. Can be null.
- * @return The string value of the given key in the properties, or null if the key
- * isn't found or if {@code props} is null.
- */
- @Nullable
- public static String getProperty(
- @Nullable Properties props,
- @NonNull String propKey,
- @Nullable String defaultValue) {
- if (props == null) {
- return defaultValue;
- }
- return props.getProperty(propKey, defaultValue);
- }
-
- /**
- * Utility method that returns an integer property from a {@link Properties} object.
- * Returns the default value if props is null or if the property is not defined or
- * cannot be parsed to an integer.
- *
- * @param props The {@link Properties} to search into.
- * If null, the default value is returned.
- * @param propKey The name of the property. Must not be null.
- * @param defaultValue The default value to return if {@code props} is null or if the
- * key is not found. Can be null.
- * @return The integer value of the given key in the properties, or the {@code defaultValue}.
- */
- public static int getPropertyInt(
- @Nullable Properties props,
- @NonNull String propKey,
- int defaultValue) {
- String s = props != null ? props.getProperty(propKey, null) : null;
- if (s != null) {
- try {
- return Integer.parseInt(s);
- } catch (Exception ignore) {}
- }
- return defaultValue;
- }
-
- /**
- * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a full
- * revision (major.minor.micro.preview).
- *
- * @param props The properties to parse.
- * @return A {@link FullRevision} or null if there is no such property or it couldn't be parsed.
- * @param propKey The name of the property. Must not be null.
- */
- @Nullable
- public static FullRevision getPropertyFull(
- @Nullable Properties props,
- @NonNull String propKey) {
- String revStr = getProperty(props, propKey, null);
-
- FullRevision rev = null;
- if (revStr != null) {
- try {
- rev = FullRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- return rev;
- }
-
- /**
- * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a major
- * revision (major integer, no minor/micro/preview parts.)
- *
- * @param props The properties to parse.
- * @return A {@link MajorRevision} or null if there is no such property or it couldn't be parsed.
- * @param propKey The name of the property. Must not be null.
- */
- @Nullable
- public static MajorRevision getPropertyMajor(
- @Nullable Properties props,
- @NonNull String propKey) {
- String revStr = getProperty(props, propKey, null);
-
- MajorRevision rev = null;
- if (revStr != null) {
- try {
- rev = MajorRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- return rev;
- }
-
- /**
- * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a no-preview
- * revision (major.minor.micro integers but no preview part.)
- *
- * @param props The properties to parse.
- * @return A {@link NoPreviewRevision} or
- * null if there is no such property or it couldn't be parsed.
- * @param propKey The name of the property. Must not be null.
- */
- @Nullable
- public static NoPreviewRevision getPropertyNoPreview(
- @Nullable Properties props,
- @NonNull String propKey) {
- String revStr = getProperty(props, propKey, null);
-
- NoPreviewRevision rev = null;
- if (revStr != null) {
- try {
- rev = NoPreviewRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- return rev;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformPackage.java
deleted file mode 100755
index ff4e4bb..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformPackage.java
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a platform XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class PlatformPackage extends MinToolsPackage
- implements IAndroidVersionProvider, ILayoutlibVersion {
-
- /** The package version, for platform, add-on and doc packages. */
- private final AndroidVersion mVersion;
-
- /** The version, a string, for platform packages. */
- private final String mVersionName;
-
- /** The ABI of the system-image included in this platform. Can be null but not empty. */
- private final String mIncludedAbi;
-
- /** The helper handling the layoutlib version. */
- private final LayoutlibVersionMixin mLayoutlibVersion;
-
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new platform package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public PlatformPackage(
- SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mVersionName =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VERSION);
-
- int apiLevel =
- PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.isEmpty()) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mIncludedAbi = PackageParserUtils.getOptionalXmlString(packageNode,
- SdkRepoConstants.NODE_ABI_INCLUDED);
-
- mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newPlatform(mVersion, (MajorRevision) getRevision(), getMinToolsRevision()))
- .create();
- }
-
- /**
- * Creates a new platform package based on an actual {@link IAndroidTarget} (which
- * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(@NonNull IAndroidTarget target, @Nullable Properties props) {
- return new PlatformPackage(target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected PlatformPackage(@NonNull IAndroidTarget target, @Nullable Properties props) {
- this(null /*source*/, target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected PlatformPackage(@Nullable SdkSource source,
- @NonNull IAndroidTarget target,
- @Nullable Properties props) {
- super( source, //source
- props, //properties
- target.getRevision(), //revision
- null, //license
- target.getDescription(), //description
- null, //descUrl
- target.getLocation() //archiveOsPath
- );
-
- mVersion = target.getVersion();
- mVersionName = target.getVersionName();
- mLayoutlibVersion = new LayoutlibVersionMixin(props);
- mIncludedAbi = props == null ? null : props.getProperty(PkgProps.PLATFORM_INCLUDED_ABI);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newPlatform(mVersion, (MajorRevision) getRevision(), getMinToolsRevision()))
- .create();
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- mLayoutlibVersion.saveProperties(props);
-
- if (mVersionName != null) {
- props.setProperty(PkgProps.PLATFORM_VERSION, mVersionName);
- }
-
- if (mIncludedAbi != null) {
- props.setProperty(PkgProps.PLATFORM_INCLUDED_ABI, mIncludedAbi);
- }
-
- }
-
- /** Returns the version, a string, for platform packages. */
- public String getVersionName() {
- return mVersionName;
- }
-
- /** Returns the package version, for platform, add-on and doc packages. */
- @Override @NonNull
- public AndroidVersion getAndroidVersion() {
- return mVersion;
- }
-
- /**
- * Returns the ABI of the system-image included in this platform.
- *
- * @return Null if the platform does not include any system-image.
- * Otherwise should be a valid non-empty ABI string (e.g. "x86" or "armeabi-v7a").
- */
- public String getIncludedAbi() {
- return mIncludedAbi;
- }
-
- /**
- * Returns the layoutlib version. Mandatory starting with repository XSD rev 4.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link ILayoutlibVersion#LAYOUTLIB_API_NOT_SPECIFIED} (0)
- * if the layoutlib version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-repository-4.xsd
- */
- @Override
- public Pair<Integer, Integer> getLayoutlibVersion() {
- return mLayoutlibVersion.getLayoutlibVersion();
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For platforms, we use "android-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return AndroidTargetHash.getPlatformHashString(mVersion);
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- String s;
- if (mVersion.isPreview()) {
- s = String.format("SDK Platform Android %1$s Preview%2$s",
- getVersionName(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- } else {
- s = String.format("SDK Platform Android %1$s%2$s",
- getVersionName(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- }
-
- return s;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- String s;
- if (mVersion.isPreview()) {
- s = String.format("SDK Platform Android %1$s Preview, revision %2$s%3$s",
- getVersionName(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- } else {
- s = String.format("SDK Platform Android %1$s, API %2$d, revision %3$s%4$s",
- getVersionName(),
- mVersion.getApiLevel(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- }
-
- return s;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the <description> field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A platform package is typically installed in SDK/platforms/android-"version".
- * However if we can find a different directory under SDK/platform that already
- * has this platform version installed, we'll use that one.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
-
- // First find if this platform is already installed. If so, reuse the same directory.
- for (IAndroidTarget target : sdkManager.getTargets()) {
- if (target.isPlatform() && target.getVersion().equals(mVersion)) {
- return new File(target.getLocation());
- }
- }
-
- File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);
- File folder = new File(platforms,
- String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$
-
- return folder;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof PlatformPackage) {
- PlatformPackage newPkg = (PlatformPackage)pkg;
-
- // check they are the same version.
- return newPkg.getAndroidVersion().equals(this.getAndroidVersion());
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result +
- ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- result = prime * result + ((mVersionName == null) ? 0 : mVersionName.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof PlatformPackage)) {
- return false;
- }
- PlatformPackage other = (PlatformPackage) obj;
- if (mLayoutlibVersion == null) {
- if (other.mLayoutlibVersion != null) {
- return false;
- }
- } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
- return false;
- }
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- if (mVersionName == null) {
- if (other.mVersionName != null) {
- return false;
- }
- } else if (!mVersionName.equals(other.mVersionName)) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
deleted file mode 100755
index 85c6953..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.AdbWrapper;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
-/**
- * Represents a platform-tool XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class PlatformToolPackage extends FullRevisionPackage {
-
- /** The value returned by {@link PlatformToolPackage#installId()}. */
- public static final String INSTALL_ID = "platform-tools"; //$NON-NLS-1$
- /** The value returned by {@link PlatformToolPackage#installId()}. */
- public static final String INSTALL_ID_PREVIEW = "platform-tools-preview"; //$NON-NLS-1$
-
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new platform-tool package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public PlatformToolPackage(SdkSource source, Node packageNode,
- String nsUri, Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder.newPlatformTool(getRevision())).create();
- }
-
- /**
- * Manually create a new package with one archive and the given attributes or properties.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
-
- PlatformToolPackage ptp = new PlatformToolPackage(source, props, revision, license,
- description, descUrl, archiveOsPath);
-
- File platformToolsFolder = new File(archiveOsPath);
- String error = null;
- if (!platformToolsFolder.isDirectory()) {
- error = "platform-tools folder is missing";
- } else {
- File[] files = platformToolsFolder.listFiles();
- if (files == null || files.length == 0) {
- error = "platform-tools folder is empty";
- } else {
- Set<String> names = new HashSet<String>();
- for (File file : files) {
- names.add(file.getName());
- }
-
- // Package-tools revision 17+ matches sdk-repository-8 and above
- // and only requires adb (other tools moved to the build-tool packages.)
- String[] expected = new String[] { SdkConstants.FN_ADB };
- if (ptp.getRevision().getMajor() < 17) {
- // Platform-tools before revision 17 should have adb, aapt, aidl and dx.
- expected = new String[] { SdkConstants.FN_ADB,
- SdkConstants.FN_AAPT,
- SdkConstants.FN_AIDL,
- SdkConstants.FN_DX };
- }
-
- for (String name : expected) {
- if (!names.contains(name)) {
- if (error == null) {
- error = "platform-tools folder is missing ";
- } else {
- error += ", ";
- }
- error += name;
- }
- }
- }
- }
-
- if (error != null) {
- String shortDesc = ptp.getShortDescription() + " [*]"; //$NON-NLS-1$
-
- String longDesc = String.format(
- "Broken Platform-Tools Package: %1$s\n" +
- "[*] Package cannot be used due to error: %2$s",
- description,
- error);
-
- BrokenPackage ba = new BrokenPackage(props, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- IExactApiLevelDependency.API_LEVEL_INVALID,
- archiveOsPath,
- PkgDesc.Builder.newPlatformTool(ptp.getRevision())
- .setDescriptionShort(shortDesc)
- .create());
- return ba;
- }
-
-
- return ptp;
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected PlatformToolPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder.newPlatformTool(getRevision())).create();
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For platform-tools, we use "platform-tools" or "platform-tools-preview" since
- * this package type is unique.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- if (getRevision().isPreview()) {
- return INSTALL_ID_PREVIEW;
- } else {
- return INSTALL_ID;
- }
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- return String.format("Android SDK Platform-tools%1$s",
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return String.format("Android SDK Platform-tools, revision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /** Returns a long description for an {@link IDescription}. */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A "platform-tool" package should always be located in SDK/platform-tools.
- * There can be only one installed at once.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
- }
-
- /**
- * Check whether 2 platform-tool packages are the same <em>and</em> have the
- * same preview bit.
- */
- @Override
- public boolean sameItemAs(Package pkg) {
- return sameItemAs(pkg, PreviewComparison.COMPARE_TYPE);
- }
-
- @Override
- public boolean sameItemAs(Package pkg, PreviewComparison comparePreview) {
- // only one platform-tool package so any platform-tool package is the same item.
- if (pkg instanceof PlatformToolPackage) {
- switch (comparePreview) {
- case IGNORE:
- return true;
-
- case COMPARE_NUMBER:
- // Voluntary break-through.
- case COMPARE_TYPE:
- // There's only one platform-tools so the preview number doesn't matter;
- // however previews can only match previews by default so both cases
- // are treated the same.
- return pkg.getRevision().isPreview() == getRevision().isPreview();
- }
- }
- return false;
- }
-
- /**
- * Hook called right before an archive is installed.
- * This is used here to stop ADB before trying to replace the platform-tool package.
- *
- * @param archive The archive that will be installed
- * @param monitor The {@link ITaskMonitor} to display errors.
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param installFolder The folder where the archive will be installed. Note that this
- * is <em>not</em> the folder where the archive was temporary
- * unzipped. The installFolder, if it exists, contains the old
- * archive that will soon be replaced by the new one.
- * @return True if installing this archive shall continue, false if it should be skipped.
- */
- @Override
- public boolean preInstallHook(Archive archive, ITaskMonitor monitor,
- String osSdkRoot, File installFolder) {
- AdbWrapper aw = new AdbWrapper(osSdkRoot, monitor);
- aw.stopAdb();
- return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SamplePackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SamplePackage.java
deleted file mode 100755
index d31805e..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SamplePackage.java
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a sample XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SamplePackage extends MinToolsPackage
- implements IAndroidVersionProvider, IMinApiLevelDependency {
-
- /** The matching platform version. */
- private final AndroidVersion mVersion;
-
- /**
- * The minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- private final int mMinApiLevel;
-
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new sample package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public SamplePackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel =
- PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.isEmpty()) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mMinApiLevel = PackageParserUtils.getXmlInt(packageNode,
- SdkRepoConstants.NODE_MIN_API_LEVEL,
- MIN_API_LEVEL_NOT_SPECIFIED);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newSample(mVersion, (MajorRevision) getRevision(), getMinToolsRevision()))
- .create();
- }
-
- /**
- * Creates a new sample package based on an actual {@link IAndroidTarget} (which
- * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
- * <p/>
- * The target <em>must</em> have an existing sample directory that uses the /samples
- * root form rather than the old form where the samples dir was located under the
- * platform dir.
- * <p/>
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual samples path location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(IAndroidTarget target, Properties props) {
- return new SamplePackage(target, props);
- }
-
- private SamplePackage(IAndroidTarget target, Properties props) {
- super( null, //source
- props, //properties
- 0, //revision will be taken from props
- null, //license
- null, //description
- null, //descUrl
- target.getPath(IAndroidTarget.SAMPLES) //archiveOsPath
- );
-
- mVersion = target.getVersion();
-
- mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
- MIN_API_LEVEL_NOT_SPECIFIED);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newSample(mVersion, (MajorRevision) getRevision(), getMinToolsRevision()))
- .create();
- }
-
- /**
- * Creates a new sample package from an actual directory path and previously
- * saved properties.
- * <p/>
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual samples path location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- *
- * @throws AndroidVersionException if the {@link AndroidVersion} can't be restored
- * from properties.
- */
- public static Package create(String archiveOsPath, Properties props)
- throws AndroidVersionException {
- return new SamplePackage(archiveOsPath, props);
- }
-
- private SamplePackage(String archiveOsPath, Properties props) throws AndroidVersionException {
- super(null, //source
- props, //properties
- 0, //revision will be taken from props
- null, //license
- null, //description
- null, //descUrl
- archiveOsPath //archiveOsPath
- );
-
- mVersion = new AndroidVersion(props);
-
- mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
- MIN_API_LEVEL_NOT_SPECIFIED);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newSample(mVersion, (MajorRevision) getRevision(), getMinToolsRevision()))
- .create();
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
-
- if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
- props.setProperty(PkgProps.SAMPLE_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
- }
- }
-
- /**
- * Returns the minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public int getMinApiLevel() {
- return mMinApiLevel;
- }
-
- /** Returns the matching platform version. */
- @Override @NonNull
- public AndroidVersion getAndroidVersion() {
- return mVersion;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For samples, we use "sample-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "sample-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- String s = String.format("Samples for SDK API %1$s%2$s%3$s",
- mVersion.getApiString(),
- mVersion.isPreview() ? " Preview" : "",
- isObsolete() ? " (Obsolete)" : "");
- return s;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- String s = String.format("Samples for SDK API %1$s%2$s, revision %3$s%4$s",
- mVersion.getApiString(),
- mVersion.isPreview() ? " Preview" : "",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- return s;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the <description> field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A sample package is typically installed in SDK/samples/android-"version".
- * However if we can find a different directory that already has this sample
- * version installed, we'll use that one.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
-
- // The /samples dir at the root of the SDK
- File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
-
- // First find if this sample is already installed. If so, reuse the same directory.
- for (IAndroidTarget target : sdkManager.getTargets()) {
- if (target.isPlatform() &&
- target.getVersion().equals(mVersion)) {
- String p = target.getPath(IAndroidTarget.SAMPLES);
- File f = new File(p);
- if (f.isDirectory()) {
- // We *only* use this directory if it's using the "new" location
- // under SDK/samples. We explicitly do not reuse the "old" location
- // under SDK/platform/android-N/samples.
- if (f.getParentFile().equals(samplesRoot)) {
- return f;
- }
- }
- }
- }
-
- // Otherwise, get a suitable default
- File folder = new File(samplesRoot,
- String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$
-
- for (int n = 1; folder.exists(); n++) {
- // Keep trying till we find an unused directory.
- folder = new File(samplesRoot,
- String.format("android-%s_%d", getAndroidVersion().getApiString(), n)); //$NON-NLS-1$
- }
-
- return folder;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof SamplePackage) {
- SamplePackage newPkg = (SamplePackage)pkg;
-
- // check they are the same version.
- return newPkg.getAndroidVersion().equals(this.getAndroidVersion());
- }
-
- return false;
- }
-
- /**
- * Makes sure the base /samples folder exists before installing.
- *
- * {@inheritDoc}
- */
- @Override
- public boolean preInstallHook(Archive archive,
- ITaskMonitor monitor,
- String osSdkRoot,
- File installFolder) {
-
- if (installFolder != null && installFolder.isDirectory()) {
- // Get the hash computed during the last installation
- String storedHash = readContentHash(installFolder);
- if (storedHash != null && !storedHash.isEmpty()) {
-
- // Get the hash of the folder now
- String currentHash = computeContentHash(installFolder);
-
- if (!storedHash.equals(currentHash)) {
- // The hashes differ. The content was modified.
- // Ask the user if we should still wipe the old samples.
-
- String pkgName = archive.getParentPackage().getShortDescription();
-
- String msg = String.format(
- "-= Warning ! =-\n" +
- "You are about to replace the content of the folder:\n " +
- " %1$s\n" +
- "by the new package:\n" +
- " %2$s.\n" +
- "\n" +
- "However it seems that the content of the existing samples " +
- "has been modified since it was last installed. Are you sure " +
- "you want to DELETE the existing samples? This cannot be undone.\n" +
- "Please select YES to delete the existing sample and replace them " +
- "by the new ones.\n" +
- "Please select NO to skip this package. You can always install it later.",
- installFolder.getAbsolutePath(),
- pkgName);
-
- // Returns true if we can wipe & replace.
- return monitor.displayPrompt("SDK Manager: overwrite samples?", msg);
- }
- }
- }
-
- // The default is to allow installation
- return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
- }
-
- /**
- * Computes a hash of the installed content (in case of successful install.)
- *
- * {@inheritDoc}
- */
- @Override
- public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
- super.postInstallHook(archive, monitor, installFolder);
-
- if (installFolder != null) {
- String h = computeContentHash(installFolder);
- saveContentHash(installFolder, h);
- }
- }
-
- /**
- * Set all the files from a sample package as read-only so that
- * users don't end up modifying sources by mistake in Eclipse
- * (samples are copied if using the NPW > Create from sample.)
- */
- @Override
- public void postUnzipFileHook(
- Archive archive,
- ITaskMonitor monitor,
- IFileOp fileOp,
- File unzippedFile,
- ZipArchiveEntry zipEntry) {
- super.postUnzipFileHook(archive, monitor, fileOp, unzippedFile, zipEntry);
-
- if (fileOp.isFile(unzippedFile) &&
- !SdkConstants.FN_SOURCE_PROP.equals(unzippedFile.getName())) {
- fileOp.setReadOnly(unzippedFile);
- }
- }
-
- /**
- * Reads the hash from the properties file, if it exists.
- * Returns null if something goes wrong, e.g. there's no property file or
- * it doesn't contain our hash. Returns an empty string if the hash wasn't
- * correctly computed last time by {@link #saveContentHash(File, String)}.
- */
- private String readContentHash(File folder) {
- Properties props = new Properties();
-
- FileInputStream fis = null;
- try {
- File f = new File(folder, SdkConstants.FN_CONTENT_HASH_PROP);
- if (f.isFile()) {
- fis = new FileInputStream(f);
- props.load(fis);
- return props.getProperty("content-hash", null); //$NON-NLS-1$
- }
- } catch (Exception e) {
- // ignore
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
-
- return null;
- }
-
- /**
- * Saves the hash using a properties file
- */
- private void saveContentHash(File folder, String hash) {
- Properties props = new Properties();
-
- props.setProperty("content-hash", hash == null ? "" : hash); //$NON-NLS-1$ //$NON-NLS-2$
-
- FileOutputStream fos = null;
- try {
- File f = new File(folder, SdkConstants.FN_CONTENT_HASH_PROP);
- fos = new FileOutputStream(f);
- props.store( fos, "## Android - hash of this archive."); //$NON-NLS-1$
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- /**
- * Computes a hash of the files names and sizes installed in the folder
- * using the SHA-1 digest.
- * Returns null if the digest algorithm is not available.
- */
- private String computeContentHash(File installFolder) {
- MessageDigest md = null;
- try {
- // SHA-1 is a standard algorithm.
- // http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppB
- md = MessageDigest.getInstance("SHA-1"); //$NON-NLS-1$
- } catch (NoSuchAlgorithmException e) {
- // We're unlikely to get there unless this JVM is not spec conforming
- // in which case there won't be any hash available.
- }
-
- if (md != null) {
- hashDirectoryContent(installFolder, md);
- return getDigestHexString(md);
- }
-
- return null;
- }
-
- /**
- * Computes a hash of the *content* of this directory. The hash only uses
- * the files names and the file sizes.
- */
- private void hashDirectoryContent(File folder, MessageDigest md) {
- if (folder == null || md == null || !folder.isDirectory()) {
- return;
- }
-
- for (File f : folder.listFiles()) {
- if (f.isDirectory()) {
- hashDirectoryContent(f, md);
-
- } else {
- String name = f.getName();
-
- // Skip the file we use to store the content hash
- if (name == null || SdkConstants.FN_CONTENT_HASH_PROP.equals(name)) {
- continue;
- }
-
- try {
- md.update(name.getBytes(SdkConstants.UTF_8));
- } catch (UnsupportedEncodingException e) {
- // There is no valid reason for UTF-8 to be unsupported. Ignore.
- }
- try {
- long len = f.length();
- md.update((byte) (len & 0x0FF));
- md.update((byte) ((len >> 8) & 0x0FF));
- md.update((byte) ((len >> 16) & 0x0FF));
- md.update((byte) ((len >> 24) & 0x0FF));
-
- } catch (SecurityException e) {
- // Might happen if file is not readable. Ignore.
- }
- }
- }
- }
-
- /**
- * Returns a digest as an hex string.
- */
- private String getDigestHexString(MessageDigest digester) {
- // Create an hex string from the digest
- byte[] digest = digester.digest();
- int n = digest.length;
- String hex = "0123456789abcdef"; //$NON-NLS-1$
- char[] hexDigest = new char[n * 2];
- for (int i = 0; i < n; i++) {
- int b = digest[i] & 0x0FF;
- hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
- hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
- }
-
- return new String(hexDigest);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SourcePackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SourcePackage.java
deleted file mode 100755
index 6942431..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SourcePackage.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a source XML node in an SDK repository.
- * <p/>
- * Note that a source package has a version and thus implements {@link IAndroidVersionProvider}.
- * However there is no mandatory dependency that limits installation so this does not
- * implement {@link IPlatformDependency}.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SourcePackage extends MajorRevisionPackage implements IAndroidVersionProvider {
-
- /** The package version, for platform, add-on and doc packages. */
- private final AndroidVersion mVersion;
-
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new source package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public SourcePackage(
- SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel =
- PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.isEmpty()) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mPkgDesc = setDescriptions(PkgDesc.Builder.newSource(mVersion, (MajorRevision)getRevision())).create();
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SourcePackage(
- AndroidVersion platformVersion,
- int revision,
- Properties props,
- String localOsPath) {
- this(null /*source*/, platformVersion, revision, props, localOsPath);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SourcePackage(
- SdkSource source,
- AndroidVersion platformVersion,
- int revision,
- Properties props,
- String localOsPath) {
- super( source, //source
- props, //properties
- revision, //revision
- null, //license
- null, //description
- null, //descUrl
- localOsPath //archiveOsPath
- );
- mVersion = platformVersion;
-
- mPkgDesc = setDescriptions(PkgDesc.Builder.newSource(mVersion, (MajorRevision) getRevision())).create();
- }
-
- /**
- * Creates either a valid {@link SourcePackage} or a {@link BrokenPackage}.
- * <p/>
- * If the source directory contains valid properties, this creates a new {@link SourcePackage}
- * with the android version listed in the properties.
- * Otherwise returns a new {@link BrokenPackage} with some explanation on what failed.
- *
- * @param srcDir The SDK/sources/android-N folder
- * @param props The properties located in {@code srcDir} or null if not found.
- * @return A new {@link SourcePackage} or a new {@link BrokenPackage}.
- */
- public static Package create(File srcDir, Properties props) {
- AndroidVersion version = null;
- String error = null;
-
- // Try to load the android version from the sources.props.
- // If we don't find them, it would explain why this package is broken.
- if (props == null) {
- error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP);
- } else {
- try {
- version = new AndroidVersion(props);
- // The constructor will extract the revision from the properties
- // and it will not consider a missing revision as being fatal.
- return new SourcePackage(version, 0 /*revision*/, props, srcDir.getAbsolutePath());
- } catch (AndroidVersionException e) {
- error = String.format("Invalid file %1$s: %2$s",
- SdkConstants.FN_SOURCE_PROP,
- e.getMessage());
- }
- }
-
- if (version == null) {
- try {
- // Try to parse the first number out of the platform folder name.
- // This is just a wild guess in case we can create a broken package using that info.
- String platform = srcDir.getParentFile().getName();
- platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- int pos = platform.indexOf(' ');
- if (pos >= 0) {
- platform = platform.substring(0, pos);
- }
- int apiLevel = Integer.parseInt(platform);
- version = new AndroidVersion(apiLevel, null /*codename*/);
- } catch (Exception ignore) {
- }
- }
-
- StringBuilder sb = new StringBuilder("Broken Source Package");
- if (version != null) {
- sb.append(String.format(", API %1$s", version.getApiString()));
- }
-
- String shortDesc = sb.toString();
-
- if (error != null) {
- sb.append('\n').append(error);
- }
-
- String longDesc = sb.toString();
-
- IPkgDesc desc = PkgDesc.Builder
- .newSource(version != null ? version : new AndroidVersion(0, null),
- new MajorRevision(MajorRevision.MISSING_MAJOR_REV))
- .setDescriptionShort(shortDesc)
- .create();
-
- return new BrokenPackage(props, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
- srcDir.getAbsolutePath(),
- desc);
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
- mVersion.saveProperties(props);
- }
-
- /**
- * Returns the android version of this package.
- */
- @Override @NonNull
- public AndroidVersion getAndroidVersion() {
- return mVersion;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For sources, we use "source-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "source-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- if (mVersion.isPreview()) {
- return String.format("Sources for Android '%1$s' Preview SDK%2$s",
- mVersion.getCodename(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Sources for Android SDK%2$s",
- mVersion.getApiLevel(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- if (mVersion.isPreview()) {
- return String.format("Sources for Android '%1$s' Preview SDK, revision %2$s%3$s",
- mVersion.getCodename(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Sources for Android SDK, API %1$d, revision %2$s%3$s",
- mVersion.getApiLevel(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the {@code description} field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A sources package is typically installed in SDK/sources/platform.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- File folder = new File(osSdkRoot, SdkConstants.FD_PKG_SOURCES);
- folder = new File(folder, "android-" + mVersion.getApiString()); //$NON-NLS-1$
- return folder;
- }
-
- /**
- * Set all the files from a source package as read-only
- * so that users don't end up modifying sources by mistake in Eclipse.
- */
- @Override
- public void postUnzipFileHook(
- Archive archive,
- ITaskMonitor monitor,
- IFileOp fileOp,
- File unzippedFile,
- ZipArchiveEntry zipEntry) {
- super.postUnzipFileHook(archive, monitor, fileOp, unzippedFile, zipEntry);
-
- if (fileOp.isFile(unzippedFile) &&
- !SdkConstants.FN_SOURCE_PROP.equals(unzippedFile.getName())) {
- fileOp.setReadOnly(unzippedFile);
- }
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof SourcePackage) {
- SourcePackage newPkg = (SourcePackage)pkg;
-
- // check they are the same version.
- return getAndroidVersion().equals(newPkg.getAndroidVersion());
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof SourcePackage)) {
- return false;
- }
- SourcePackage other = (SourcePackage) obj;
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SystemImagePackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
deleted file mode 100755
index c5ce04a..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
+++ /dev/null
@@ -1,621 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.devices.Abi;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.*;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.android.sdklib.repository.local.LocalSysImgPkgInfo;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.regex.Pattern;
-
-/**
- * Represents a system-image XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SystemImagePackage extends MajorRevisionPackage
- implements IAndroidVersionProvider, IPlatformDependency {
-
- /** The package version, for platform, add-on and doc packages. */
- private final AndroidVersion mVersion;
-
- /** The ABI of the system-image. Must not be null nor empty. */
- private final String mAbi;
-
- private final IPkgDesc mPkgDesc;
-
- private final IdDisplay mTag;
- private final IdDisplay mAddonVendor;
-
- /**
- * Creates a new system-image package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public SystemImagePackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel =
- PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName =
- PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.isEmpty()) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mAbi = PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);
-
- // tag id
- String tagId = PackageParserUtils.getXmlString(packageNode,
- SdkSysImgConstants.ATTR_TAG_ID,
- SystemImage.DEFAULT_TAG.getId());
- String tagDisp = PackageParserUtils.getOptionalXmlString(packageNode,
- SdkSysImgConstants.ATTR_TAG_DISPLAY);
- if (tagDisp == null || tagDisp.isEmpty()) {
- tagDisp = LocalSysImgPkgInfo.tagIdToDisplay(tagId);
- }
- assert tagId != null;
- assert tagDisp != null;
- mTag = new IdDisplay(tagId, tagDisp);
-
-
- Node addonNode =
- PackageParserUtils.findChildElement(packageNode, SdkSysImgConstants.NODE_ADD_ON);
-
- IPkgDesc desc = null;
- IdDisplay vendor = null;
-
- if (addonNode == null) {
- // A platform system-image
- desc = setDescriptions(PkgDesc.Builder
- .newSysImg(mVersion, mTag, mAbi, (MajorRevision) getRevision()))
- .create();
- } else {
- // An add-on system-image
- String vendorId = PackageParserUtils.getXmlString(
- addonNode,
- SdkAddonConstants.NODE_VENDOR_ID);
- String vendorDisp = PackageParserUtils.getXmlString(
- addonNode,
- SdkAddonConstants.NODE_VENDOR_DISPLAY,
- vendorId);
-
- assert !vendorId.isEmpty();
- assert !vendorDisp.isEmpty();
-
- vendor = new IdDisplay(vendorId, vendorDisp);
-
- desc = setDescriptions(PkgDesc.Builder
- .newAddonSysImg(mVersion, vendor, mTag, mAbi, (MajorRevision) getRevision()))
- .create();
- }
-
- mPkgDesc = desc;
- mAddonVendor = vendor;
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- public SystemImagePackage(
- AndroidVersion platformVersion,
- int revision,
- String abi,
- Properties props,
- String localOsPath) {
- this(null /*source*/, platformVersion, revision, abi, props, localOsPath);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SystemImagePackage(
- SdkSource source,
- AndroidVersion platformVersion,
- int revision,
- String abi,
- Properties props,
- String localOsPath) {
- super( source, //source
- props, //properties
- revision, //revision
- null, //license
- null, //description
- null, //descUrl
- localOsPath //archiveOsPath
- );
- mVersion = platformVersion;
- if (abi == null && props != null) {
- abi = props.getProperty(PkgProps.SYS_IMG_ABI);
- }
- assert abi != null : "To use this SystemImagePackage constructor you must pass an ABI as a parameter or as a PROP_ABI property";
- mAbi = abi;
-
- mTag = LocalSysImgPkgInfo.extractTagFromProps(props);
-
- String vendorId = getProperty(props, PkgProps.ADDON_VENDOR_ID, null);
- String vendorDisp = getProperty(props, PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
-
- IPkgDesc desc = null;
- IdDisplay vendor = null;
-
- if (vendorId == null) {
- // A platform system-image
- desc = setDescriptions(PkgDesc.Builder.newSysImg(mVersion, mTag, mAbi, (MajorRevision)getRevision())).create();
- }
- else {
- // An add-on system-image
- assert !vendorId.isEmpty();
- assert !vendorDisp.isEmpty();
-
- vendor = new IdDisplay(vendorId, vendorDisp);
-
- desc = setDescriptions(PkgDesc.Builder.newAddonSysImg(mVersion, vendor, mTag, mAbi, (MajorRevision)getRevision())).create();
- }
-
- mPkgDesc = desc;
- mAddonVendor = vendor;
- }
-
- /**
- * Creates a {@link BrokenPackage} representing a system image that failed to load
- * with the regular {@link SdkManager} workflow.
- *
- * @param abiDir The SDK/system-images/android-N/tag/abi folder
- * @param props The properties located in {@code abiDir} or null if not found.
- * @return A new {@link BrokenPackage} that represents this installed package.
- */
- public static Package createBroken(File abiDir, Properties props) {
- AndroidVersion version = null;
- String abiType = abiDir.getName();
- String error = null;
- IdDisplay tag = null;
-
- // Try to load the android version, tag & ABI from the sources.props.
- // If we don't find them, it would explain why this package is broken.
- if (props == null) {
- error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP);
- } else {
- try {
- version = new AndroidVersion(props);
-
- tag = LocalSysImgPkgInfo.extractTagFromProps(props);
- String abi = props.getProperty(PkgProps.SYS_IMG_ABI);
- if (abi != null) {
- abiType = abi;
- } else {
- error = String.format("Invalid file %1$s: Missing property %2$s",
- SdkConstants.FN_SOURCE_PROP,
- PkgProps.SYS_IMG_ABI);
- }
- } catch (AndroidVersionException e) {
- error = String.format("Invalid file %1$s: %2$s",
- SdkConstants.FN_SOURCE_PROP,
- e.getMessage());
- }
- }
-
- try {
- // Try to parse the first number out of the platform folder name.
- // Also try to parse the tag if not known yet.
- // Folder structure should be:
- // Tools < 22.6 / API < 20: sdk/system-images/android-N/abi/
- // Tools >=22.6 / API >=20: sdk/system-images/android-N/tag/abi/
- String[] segments = abiDir.getAbsolutePath().split(Pattern.quote(File.separator));
- int len = segments.length;
- for (int i = len - 2; version == null && i >= 0; i--) {
- if (SdkConstants.FD_SYSTEM_IMAGES.equals(segments[i])) {
- if (version == null) {
- String platform = segments[i+1];
- try {
- platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- int pos = platform.indexOf(' ');
- if (pos >= 0) {
- platform = platform.substring(0, pos);
- }
- int apiLevel = Integer.parseInt(platform);
- version = new AndroidVersion(apiLevel, null /*codename*/);
- } catch (Exception ignore) {}
- }
- if (tag == null && i+2 < len) {
- // If we failed to find a tag id in the properties, check whether
- // we can guess one from the system image folder path. It should
- // match the limited tag id character set and not be one of the
- // known ABIs.
- String abiOrTag = segments[i+2].trim();
- if (abiOrTag.matches("[A-Za-z0-9_-]+")) {
- if (Abi.getEnum(abiOrTag) == null) {
- tag = new IdDisplay(abiOrTag,
- LocalSysImgPkgInfo.tagIdToDisplay(abiOrTag));
- }
- }
- }
- }
- }
- } catch (Exception ignore) {}
-
- String vendorId = getProperty(props, PkgProps.ADDON_VENDOR_ID, null);
- String vendorDisp = getProperty(props, PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
-
- StringBuilder sb = new StringBuilder("Broken ");
- sb.append(getAbiDisplayNameInternal(abiType)).append(' ');
- if (tag != null && !tag.getId().equals(SystemImage.DEFAULT_TAG.getId())) {
- sb.append(tag).append(' ');
- }
- sb.append("System Image");
- if (vendorDisp != null) {
- sb.append(", by ").append(vendorDisp);
- }
- if (version != null) {
- sb.append(String.format(", API %1$s", version.getApiString()));
- }
-
- String shortDesc = sb.toString();
-
- if (error != null) {
- sb.append('\n').append(error);
- }
-
- String longDesc = sb.toString();
-
- if (tag == null) {
- // No tag? Use the default.
- tag = SystemImage.DEFAULT_TAG;
- }
- assert tag != null;
-
- IPkgDesc desc = PkgDesc.Builder
- .newSysImg(version != null ? version : new AndroidVersion(0, null),
- tag,
- abiType,
- new MajorRevision(MajorRevision.MISSING_MAJOR_REV))
- .setDescriptionShort(shortDesc)
- .create();
-
- return new BrokenPackage(props, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
- abiDir.getAbsolutePath(),
- desc);
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- props.setProperty(PkgProps.SYS_IMG_ABI, mAbi);
- props.setProperty(PkgProps.SYS_IMG_TAG_ID, mTag.getId());
- props.setProperty(PkgProps.SYS_IMG_TAG_DISPLAY, mTag.getDisplay());
-
- if (mAddonVendor != null) {
- props.setProperty(PkgProps.ADDON_VENDOR_ID, mAddonVendor.getId());
- props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY, mAddonVendor.getDisplay());
- }
- }
-
- /** Returns the tag of the system-image. */
- @NonNull
- public IdDisplay getTag() {
- return mTag;
- }
-
- /** Returns the ABI of the system-image. Cannot be null nor empty. */
- public String getAbi() {
- return mAbi;
- }
-
- /** Returns a display-friendly name for the ABI of the system-image. */
- public String getAbiDisplayName() {
- return getAbiDisplayNameInternal(mAbi);
- }
-
- private static String getAbiDisplayNameInternal(String abi) {
- return abi.replace("armeabi", "ARM EABI") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("arm64", "ARM 64") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("x86", "Intel x86 Atom") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("x86_64", "Intel x86_64 Atom") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("mips", "MIPS") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- /**
- * Returns the version of the platform dependency of this package.
- * <p/>
- * A system-image has the same {@link AndroidVersion} as the platform it depends on.
- */
- @NonNull
- @Override
- public AndroidVersion getAndroidVersion() {
- return mVersion;
- }
-
- /**
- * Returns true if the system-image belongs to a standard Android platform.
- * In this case {@link #getAddonVendor()} returns null.
- * <p/.
- * Returns false if the system-image belongs to an add-on.
- * In this case {@link #getAndroidVersion()} returns a non-null {@link IdDisplay}.
- */
- public boolean isPlatform() {
- return mAddonVendor == null;
- }
-
- /**
- * Returns the add-on vendor if this is an add-on system image.
- * Returns null if this is a platform system-image.
- */
- @Nullable
- public IdDisplay getAddonVendor() {
- return mAddonVendor;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For system images, we use "sysimg-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- StringBuilder sb = new StringBuilder("sys-img-"); //$NON-NLS-1$
- sb.append(getAbi()).append('-');
- if (!isPlatform()) {
- sb.append("addon-");
- }
- sb.append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId());
- sb.append('-');
- if (!isPlatform()) {
- sb.append(getAddonVendor().getId()).append('-');
- }
- sb.append(getAndroidVersion().getApiString());
-
- String s = sb.toString();
- s = s.toLowerCase(Locale.US).replaceAll("[^a-z0-9_.-]+", "_").replaceAll("_+", "_");
- return s;
-
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s%2$s", ld, isObsolete() ? " (Obsolete)" : "");
- }
-
- boolean isDefaultTag = SystemImage.DEFAULT_TAG.equals(mTag);
- return String.format("%1$s%2$s System Image%3$s",
- isDefaultTag ? "" : (mTag.getDisplay() + " "),
- getAbiDisplayName(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, %2$s API %3$s, revision %4$s%5$s",
- ld,
- mAddonVendor == null ? "Android" : mAddonVendor.getDisplay(),
- mVersion.getApiString(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- boolean isDefaultTag = SystemImage.DEFAULT_TAG.equals(mTag);
- return String.format("%1$s%2$s System Image, %3$s API %4$s, revision %5$s%6$s",
- isDefaultTag ? "" : (mTag.getDisplay() + " "),
- getAbiDisplayName(),
- mAddonVendor == null ? "Android" : mAddonVendor.getDisplay(),
- mVersion.getApiString(),
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the {@code description} field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- s += String.format("\nRequires SDK Platform Android API %1$s",
- mVersion.getApiString());
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A system-image package is typically installed in SDK/systems/platform/tag/abi.
- * The name needs to be sanitized to be acceptable as a directory name.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- File folder = new File(osSdkRoot, SdkConstants.FD_SYSTEM_IMAGES);
- folder = new File(folder, AndroidTargetHash.getPlatformHashString(mVersion));
-
- // Computes a folder directory using the sanitized tag & abi strings.
- String tag = mTag.getId();
- tag = tag.toLowerCase(Locale.US);
- tag = tag.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- tag = tag.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- tag = tag.replaceAll("-+", "-"); //$NON-NLS-1$ //$NON-NLS-2$
-
- folder = new File(folder, tag);
-
- String abi = mAbi;
- abi = abi.toLowerCase(Locale.US);
- abi = abi.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- abi = abi.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- abi = abi.replaceAll("-+", "-"); //$NON-NLS-1$ //$NON-NLS-2$
-
- folder = new File(folder, abi);
- return folder;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof SystemImagePackage) {
- SystemImagePackage newPkg = (SystemImagePackage)pkg;
-
- // check they are the same tag, abi and version.
- return getTag().equals(newPkg.getTag()) &&
- getAbi().equals(newPkg.getAbi()) &&
- getAndroidVersion().equals(newPkg.getAndroidVersion()) &&
- (mAddonVendor == newPkg.mAddonVendor ||
- (mAddonVendor != null && mAddonVendor.equals(newPkg.mAddonVendor)));
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mAddonVendor == null) ? 0 : mAddonVendor.hashCode());
- result = prime * result + ((mTag == null) ? 0 : mTag.hashCode());
- result = prime * result + ((mAbi == null) ? 0 : mAbi.hashCode());
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof SystemImagePackage)) {
- return false;
- }
- SystemImagePackage other = (SystemImagePackage) obj;
- if (mAddonVendor == null) {
- if (other.mAddonVendor != null) {
- return false;
- }
- } else if (!mAddonVendor.equals(other.mAddonVendor)) {
- return false;
- }
- if (mTag == null) {
- if (other.mTag != null) {
- return false;
- }
- } else if (!mTag.equals(other.mTag)) {
- return false;
- }
- if (mAbi == null) {
- if (other.mAbi != null) {
- return false;
- }
- } else if (!mAbi.equals(other.mAbi)) {
- return false;
- }
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-
- /**
- * For sys img packages, we want to add tag/abi to the sorting key
- * <em>before<em/> the revision number.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
- s = s.substring(0, pos) +
- "|vend:" + (mAddonVendor == null ? "" : mAddonVendor.getId()) + //$NON-NLS-1$ //$NON-NLS-2$
- "|tag:" + getTag().getId() + //$NON-NLS-1$
- "|abi:" + getAbiDisplayName() + //$NON-NLS-1$
- s.substring(pos);
- return s;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ToolPackage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ToolPackage.java
deleted file mode 100755
index 1b8b3b8..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ToolPackage.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.android.utils.GrabProcessOutput;
-import com.android.utils.GrabProcessOutput.IProcessOutput;
-import com.android.utils.GrabProcessOutput.Wait;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a tool XML node in an SDK repository.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class ToolPackage extends FullRevisionPackage implements IMinPlatformToolsDependency {
-
- /** The value returned by {@link ToolPackage#installId()}. */
- public static final String INSTALL_ID = "tools"; //$NON-NLS-1$
- /** The value returned by {@link ToolPackage#installId()}. */
- private static final String INSTALL_ID_PREVIEW = "tools-preview"; //$NON-NLS-1$
-
- /**
- * The minimal revision of the platform-tools package required by this package
- * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.
- */
- private final FullRevision mMinPlatformToolsRevision;
-
- private final IPkgDesc mPkgDesc;
-
- /**
- * Creates a new tool package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public ToolPackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mMinPlatformToolsRevision = PackageParserUtils.parseFullRevisionElement(
- PackageParserUtils.findChildElement(packageNode,
- SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV));
-
- if (mMinPlatformToolsRevision.equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {
- // This revision number is mandatory starting with sdk-repository-3.xsd
- // and did not exist before. Complain if the URI has level >= 3.
- if (SdkRepoConstants.versionGreaterOrEqualThan(nsUri, 3)) {
- throw new IllegalArgumentException(
- String.format("Missing %1$s element in %2$s package",
- SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV,
- SdkRepoConstants.NODE_PLATFORM_TOOL));
- }
- }
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newTool(getRevision(), mMinPlatformToolsRevision))
- .create();
- }
-
- /**
- * Manually create a new package with one archive and the given attributes or properties.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- return new ToolPackage(source, props, revision, license, description,
- descUrl, archiveOsPath);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected ToolPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
-
- // Setup min-platform-tool
- String revStr = getProperty(props, PkgProps.MIN_PLATFORM_TOOLS_REV, null);
-
- FullRevision rev = MIN_PLATFORM_TOOLS_REV_INVALID;
- if (revStr != null) {
- try {
- rev = FullRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- mMinPlatformToolsRevision = rev;
-
- mPkgDesc = setDescriptions(PkgDesc.Builder
- .newTool(getRevision(), mMinPlatformToolsRevision))
- .create();
- }
-
- @Override
- @NonNull
- public IPkgDesc getPkgDesc() {
- return mPkgDesc;
- }
-
- @Override
- public FullRevision getMinPlatformToolsRevision() {
- return mMinPlatformToolsRevision;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For tools, we use "tools" or "tools-preview" since this package is unique.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- if (getRevision().isPreview()) {
- return INSTALL_ID_PREVIEW;
- } else {
- return INSTALL_ID;
- }
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String ld = getListDisplay();
- return String.format("%1$s%2$s",
- ld.isEmpty() ? "Android SDK Tools" : ld,
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String ld = getListDisplay();
- if (!ld.isEmpty()) {
- return String.format("%1$s, revision %2$s%3$s",
- ld,
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return String.format("Android SDK Tools, revision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /** Returns a long description for an {@link IDescription}. */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.isEmpty()) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$s%2$s",
- getRevision().toShortString(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A "tool" package should always be located in SDK/tools.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return new File(osSdkRoot, SdkConstants.FD_TOOLS);
- }
-
- /**
- * Check whether 2 tool packages are the same <em>and</em> have the
- * same preview bit.
- */
- @Override
- public boolean sameItemAs(Package pkg) {
- return sameItemAs(pkg, PreviewComparison.COMPARE_TYPE);
- }
-
- @Override
- public boolean sameItemAs(Package pkg, PreviewComparison comparePreview) {
- // only one tool package so any platform-tool package is the same item.
- if (pkg instanceof ToolPackage) {
- switch (comparePreview) {
- case IGNORE:
- return true;
-
- case COMPARE_NUMBER:
- // Voluntary break-through.
- case COMPARE_TYPE:
- // There's only one tool so the preview number doesn't matter;
- // however previews can only match previews by default so both cases
- // are treated the same.
- return pkg.getRevision().isPreview() == getRevision().isPreview();
- }
- }
- return false;
- }
-
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- if (!getMinPlatformToolsRevision().equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {
- props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
- getMinPlatformToolsRevision().toShortString());
- }
- }
-
- /**
- * The tool package executes tools/lib/post_tools_install[.bat|.sh]
- * {@inheritDoc}
- */
- @Override
- public void postInstallHook(Archive archive, final ITaskMonitor monitor, File installFolder) {
- super.postInstallHook(archive, monitor, installFolder);
-
- if (installFolder == null) {
- return;
- }
-
- File libDir = new File(installFolder, SdkConstants.FD_LIB);
- if (!libDir.isDirectory()) {
- return;
- }
-
- String scriptName = "post_tools_install"; //$NON-NLS-1$
- String shell = ""; //$NON-NLS-1$
- if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
- shell = "cmd.exe /c "; //$NON-NLS-1$
- scriptName += ".bat"; //$NON-NLS-1$
- } else {
- scriptName += ".sh"; //$NON-NLS-1$
- }
-
- File scriptFile = new File(libDir, scriptName);
- if (!scriptFile.isFile()) {
- return;
- }
-
- int status = -1;
-
- try {
- Process proc = Runtime.getRuntime().exec(
- shell + scriptName, // command
- null, // environment
- libDir); // working dir
-
- final String tag = scriptName;
- status = GrabProcessOutput.grabProcessOutput(
- proc,
- Wait.WAIT_FOR_PROCESS,
- new IProcessOutput() {
- @Override
- public void out(@Nullable String line) {
- if (line != null) {
- monitor.log("[%1$s] %2$s", tag, line);
- }
- }
-
- @Override
- public void err(@Nullable String line) {
- if (line != null) {
- monitor.logError("[%1$s] Error: %2$s", tag, line);
- }
- }
- });
-
- } catch (Exception e) {
- monitor.logError("Exception: %s", e.toString());
- }
-
- if (status != 0) {
- monitor.logError("Failed to execute %s", scriptName);
- return;
- }
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result
- + ((mMinPlatformToolsRevision == null) ? 0 : mMinPlatformToolsRevision.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof ToolPackage)) {
- return false;
- }
- ToolPackage other = (ToolPackage) obj;
- if (mMinPlatformToolsRevision == null) {
- if (other.mMinPlatformToolsRevision != null) {
- return false;
- }
- } else if (!mMinPlatformToolsRevision.equals(other.mMinPlatformToolsRevision)) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkAddonSource.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
deleted file mode 100755
index a77b128..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.sources;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.repository.SdkAddonConstants;
-
-import org.w3c.dom.Document;
-
-import java.io.InputStream;
-
-
-/**
- * An sdk-addon source, i.e. a download site for addons and extra packages.
- * A repository describes one or more {@link Package}s available for download.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkAddonSource extends SdkSource {
-
- /**
- * Constructs a new source for the given repository URL.
- * @param url The source URL. Cannot be null. If the URL ends with a /, the default
- * addon.xml filename will be appended automatically.
- * @param uiName The UI-visible name of the source. Can be null.
- */
- public SdkAddonSource(String url, String uiName) {
- super(url, uiName);
- }
-
- /**
- * Returns true if this is an addon source.
- * We only load addons and extras from these sources.
- */
- @Override
- public boolean isAddonSource() {
- return true;
- }
-
- /**
- * Returns true if this is a system-image source.
- * We only load system-images from these sources.
- */
- @Override
- public boolean isSysImgSource() {
- return false;
- }
-
- @Override
- protected String[] getDefaultXmlFileUrls() {
- return new String[] { SdkAddonConstants.URL_DEFAULT_FILENAME };
- }
-
- @Override
- protected int getNsLatestVersion() {
- return SdkAddonConstants.NS_LATEST_VERSION;
- }
-
- @Override
- protected String getNsUri() {
- return SdkAddonConstants.NS_URI;
- }
-
- @Override
- protected String getNsPattern() {
- return SdkAddonConstants.NS_PATTERN;
- }
-
- @Override
- protected String getSchemaUri(int version) {
- return SdkAddonConstants.getSchemaUri(version);
- }
-
- @Override
- protected String getRootElementName() {
- return SdkAddonConstants.NODE_SDK_ADDON;
- }
-
- @Override
- protected InputStream getXsdStream(int version) {
- return SdkAddonConstants.getXsdStream(version);
- }
-
- /**
- * This kind of schema does not support forward-evolution of the <tool> element.
- *
- * @param xml The input XML stream. Can be null.
- * @return Always null.
- * @null This implementation always return null.
- */
- @Override
- protected Document findAlternateToolsXml(@Nullable InputStream xml) {
- return null;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkRepoSource.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
deleted file mode 100755
index 3d2efb5..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
+++ /dev/null
@@ -1,528 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.sources;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.repository.archives.ArchFilter;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PackageParserUtils;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.Text;
-import org.xml.sax.ErrorHandler;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-
-/**
- * An sdk-repository source, i.e. a download site.
- * A repository describes one or more {@link Package}s available for download.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkRepoSource extends SdkSource {
-
- /**
- * Constructs a new source for the given repository URL.
- * @param url The source URL. Cannot be null. If the URL ends with a /, the default
- * repository.xml filename will be appended automatically.
- * @param uiName The UI-visible name of the source. Can be null.
- */
- public SdkRepoSource(String url, String uiName) {
- super(url, uiName);
- }
-
- /**
- * Returns true if this is an addon source.
- * We only load addons and extras from these sources.
- */
- @Override
- public boolean isAddonSource() {
- return false;
- }
-
- /**
- * Returns true if this is a system-image source.
- * We only load system-images from these sources.
- */
- @Override
- public boolean isSysImgSource() {
- return false;
- }
-
- private static String[] sDefaults = null; // lazily allocated in getDefaultXmlFileUrls
-
- @Override
- protected String[] getDefaultXmlFileUrls() {
- if (sDefaults == null) {
- String[] values = new String[SdkRepoConstants.NS_LATEST_VERSION
- - SdkRepoConstants.NS_SERVER_MIN_VERSION
- + 2];
- int k = 0;
- for (int i = SdkRepoConstants.NS_LATEST_VERSION;
- i >= SdkRepoConstants.NS_SERVER_MIN_VERSION;
- i--) {
- values[k++] = String.format(SdkRepoConstants.URL_FILENAME_PATTERN, i);
- }
- values[k++] = SdkRepoConstants.URL_DEFAULT_FILENAME;
- assert k == values.length;
- sDefaults = values;
- }
-
- return sDefaults;
- }
-
- @Override
- protected int getNsLatestVersion() {
- return SdkRepoConstants.NS_LATEST_VERSION;
- }
-
- @Override
- protected String getNsUri() {
- return SdkRepoConstants.NS_URI;
- }
-
- @Override
- protected String getNsPattern() {
- return SdkRepoConstants.NS_PATTERN;
- }
-
- @Override
- protected String getSchemaUri(int version) {
- return SdkRepoConstants.getSchemaUri(version);
- }
-
- @Override
- protected String getRootElementName() {
- return SdkRepoConstants.NODE_SDK_REPOSITORY;
- }
-
- @Override
- protected InputStream getXsdStream(int version) {
- return SdkRepoConstants.getXsdStream(version);
- }
-
- /**
- * The purpose of this method is to support forward evolution of our schema.
- * <p/>
- * At this point, we know that xml does not point to any schema that this version of
- * the tool knows how to process, so it's not one of the possible 1..N versions of our
- * XSD schema.
- * <p/>
- * We thus try to interpret the byte stream as a possible XML stream. It may not be
- * one at all in the first place. If it looks anything line an XML schema, we try to
- * find its <tool> and the <platform-tools> elements. If we find any,
- * we recreate a suitable document that conforms to what we expect from our XSD schema
- * with only those elements.
- * <p/>
- * To be valid, the <tool> and the <platform-tools> elements must have at
- * least one <archive> compatible with this platform.
- * <p/>
- * Starting the sdk-repository schema v3, <tools> has a <min-platform-tools-rev>
- * node, so technically the corresponding XML schema will be usable only if there's a
- * <platform-tools> with the request revision number. We don't enforce that here, as
- * this is done at install time.
- * <p/>
- * If we don't find anything suitable, we drop the whole thing.
- *
- * @param xml The input XML stream. Can be null.
- * @return Either a new XML document conforming to our schema with at least one <tool>
- * and <platform-tools> element or null.
- * @throws IOException if InputStream.reset() fails
- * @null Can return null on failure.
- */
- @Override
- protected Document findAlternateToolsXml(@Nullable InputStream xml) throws IOException {
- return findAlternateToolsXml(xml, null /*errorHandler*/);
- }
-
- /**
- * An alternate version of {@link #findAlternateToolsXml(InputStream)} that allows
- * the caller to specify the XML error handler. The default from the underlying Java
- * XML Xerces parser will dump to stdout/stderr, which is not convenient during unit tests.
- *
- * @param xml The input XML stream. Can be null.
- * @param errorHandler An optional XML error handler. If null, the default will be used.
- * @return Either a new XML document conforming to our schema with at least one <tool>
- * and <platform-tools> element or null.
- * @throws IOException if InputStream.reset() fails
- * @null Can return null on failure.
- * @see #findAlternateToolsXml(InputStream) findAlternateToolsXml() provides more details.
- */
- protected Document findAlternateToolsXml(
- @Nullable InputStream xml,
- @Nullable ErrorHandler errorHandler)
- throws IOException {
- if (xml == null) {
- return null;
- }
-
- // Reset the stream if it supports that operation.
- assert xml.markSupported();
- xml.reset();
-
- // Get an XML document
-
- Document oldDoc = null;
- Document newDoc = null;
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- if (errorHandler != null) {
- builder.setErrorHandler(errorHandler);
- }
-
- oldDoc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
- newDoc = builder.newDocument();
-
- } catch (Exception e) {
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- }
-
- if (oldDoc == null || newDoc == null) {
- return null;
- }
-
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-repository
- // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(getNsPattern());
-
- Node oldRoot = null;
- String prefix = null;
- for (Node child = oldDoc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (SdkRepoConstants.NODE_SDK_REPOSITORY.equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null && nsPattern.matcher(uri).matches()) {
- oldRoot = child;
- break;
- }
- }
- }
- }
- }
-
- // we must have found the root node, and it must have an XML namespace prefix.
- if (oldRoot == null || prefix == null || prefix.isEmpty()) {
- return null;
- }
-
- final String ns = getNsUri();
- Element newRoot = newDoc.createElementNS(ns, getRootElementName());
- newRoot.setPrefix(prefix);
- newDoc.appendChild(newRoot);
- int numTool = 0;
-
- // Find any inner <tool> or <platform-tool> nodes and extract their required parameters
-
- String[] elementNames = {
- SdkRepoConstants.NODE_TOOL,
- SdkRepoConstants.NODE_PLATFORM_TOOL,
- SdkRepoConstants.NODE_LICENSE
- };
-
- Element element = null;
- while ((element = findChild(oldRoot, element, prefix, elementNames)) != null) {
- boolean isElementValid = false;
-
- String name = element.getLocalName();
- if (name == null) {
- name = element.getNodeName();
-
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- name = name.substring(pos + 1);
- }
- }
-
- // To be valid, the tool or platform-tool element must have:
- // - a <revision> element with a number
- // - a <min-platform-tools-rev> element with a number for a <tool> element
- // - an <archives> element with one or more <archive> elements inside
- // - one of the <archive> elements must have an "os" and "arch" attributes
- // compatible with the current platform. Only keep the first such element found.
- // - the <archive> element must contain a <size>, a <checksum> and a <url>.
- // - none of the above for a license element
-
- if (SdkRepoConstants.NODE_LICENSE.equals(name)) {
- isElementValid = true;
-
- } else {
- try {
- Node revision = findChild(element, null, prefix, RepoConstants.NODE_REVISION);
- Node archives = findChild(element, null, prefix, RepoConstants.NODE_ARCHIVES);
-
- if (revision == null || archives == null) {
- continue;
- }
-
- // check revision contains a number
- try {
- String content = revision.getTextContent();
- content = content.trim();
- int rev = Integer.parseInt(content);
- if (rev < 1) {
- continue;
- }
- } catch (NumberFormatException ignore) {
- continue;
- }
-
- if (SdkRepoConstants.NODE_TOOL.equals(name)) {
- Node minPTRev = findChild(element, null, prefix,
- RepoConstants.NODE_MIN_PLATFORM_TOOLS_REV);
-
- if (minPTRev == null) {
- continue;
- }
-
- // check min-platform-tools-rev contains a number
- try {
- String content = minPTRev.getTextContent();
- content = content.trim();
- int rev = Integer.parseInt(content);
- if (rev < 1) {
- continue;
- }
- } catch (NumberFormatException ignore) {
- continue;
- }
- }
-
- Node archive = null;
- while ((archive = findChild(archives,
- archive,
- prefix,
- RepoConstants.NODE_ARCHIVE)) != null) {
- try {
- ArchFilter af = PackageParserUtils.parseArchFilter(archive);
- if (af == null || !af.isCompatibleWith(ArchFilter.getCurrent())) {
- continue;
- }
-
- Node node = findChild(archive, null, prefix, RepoConstants.NODE_URL);
- String url = node == null ? null : node.getTextContent().trim();
- if (url == null || url.isEmpty()) {
- continue;
- }
-
- node = findChild(archive, null, prefix, RepoConstants.NODE_SIZE);
- long size = 0;
- try {
- size = Long.parseLong(node.getTextContent());
- } catch (Exception e) {
- // pass
- }
- if (size < 1) {
- continue;
- }
-
- node = findChild(archive, null, prefix, RepoConstants.NODE_CHECKSUM);
- // double check that the checksum element contains a type=sha1 attribute
- if (node == null) {
- continue;
- }
- NamedNodeMap attrs = node.getAttributes();
- Node typeNode = attrs.getNamedItem(RepoConstants.ATTR_TYPE);
- if (typeNode == null ||
- !RepoConstants.ATTR_TYPE.equals(typeNode.getNodeName()) ||
- !RepoConstants.SHA1_TYPE.equals(typeNode.getNodeValue())) {
- continue;
- }
- String sha1 = node == null ? null : node.getTextContent().trim();
- if (sha1 == null ||
- sha1.length() != RepoConstants.SHA1_CHECKSUM_LEN) {
- continue;
- }
-
- isElementValid = true;
-
- } catch (Exception ignore1) {
- // For debugging it is useful to re-throw the exception.
- // For end-users, not so much. It would be nice to make it
- // happen automatically during unit tests.
- if (System.getenv("TESTING") != null ||
- System.getProperty("THROW_DEEP_EXCEPTION_DURING_TESTING") != null) {
- throw new RuntimeException(ignore1);
- }
- }
- } // while <archive>
- } catch (Exception ignore2) {
- // For debugging it is useful to re-throw the exception.
- // For end-users, not so much. It would be nice to make it
- // happen automatically during unit tests.
- if (System.getenv("TESTING") != null ||
- System.getProperty("THROW_DEEP_EXCEPTION_DURING_TESTING") != null) {
- throw new RuntimeException(ignore2);
- }
- }
- }
-
- if (isElementValid) {
- duplicateNode(newRoot, element, SdkRepoConstants.NS_URI, prefix);
- numTool++;
- }
- } // while <tool>
-
- return numTool > 0 ? newDoc : null;
- }
-
- /**
- * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given
- * element child in a root XML node.
- */
- private Element findChild(Node rootNode, Node after, String prefix, String[] nodeNames) {
- for (int i = 0; i < nodeNames.length; i++) {
- if (nodeNames[i].indexOf(':') < 0) {
- nodeNames[i] = prefix + ":" + nodeNames[i];
- }
- }
- Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling();
- for(; child != null; child = child.getNextSibling()) {
- if (child.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
- for (String nodeName : nodeNames) {
- if (nodeName.equals(child.getNodeName())) {
- return (Element) child;
- }
- }
- }
- return null;
- }
-
- /**
- * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given
- * element child in a root XML node.
- */
- private Node findChild(Node rootNode, Node after, String prefix, String nodeName) {
- return findChild(rootNode, after, prefix, new String[] { nodeName });
- }
-
- /**
- * Helper method used by {@link #findAlternateToolsXml(InputStream)} to duplicate a node
- * and attach it to the given root in the new document.
- */
- private Element duplicateNode(Element newRootNode, Element oldNode,
- String namespaceUri, String prefix) {
- // The implementation here is more or less equivalent to
- //
- // newRoot.appendChild(newDoc.importNode(oldNode, deep=true))
- //
- // except we can't just use importNode() since we need to deal with the fact
- // that the old document is not namespace-aware yet the new one is.
-
- Document newDoc = newRootNode.getOwnerDocument();
- Element newNode = null;
-
- String nodeName = oldNode.getNodeName();
- int pos = nodeName.indexOf(':');
- if (pos > 0 && pos < nodeName.length() - 1) {
- nodeName = nodeName.substring(pos + 1);
- newNode = newDoc.createElementNS(namespaceUri, nodeName);
- newNode.setPrefix(prefix);
- } else {
- newNode = newDoc.createElement(nodeName);
- }
-
- newRootNode.appendChild(newNode);
-
- // Merge in all the attributes
- NamedNodeMap attrs = oldNode.getAttributes();
- for (int i = 0; i < attrs.getLength(); i++) {
- Attr attr = (Attr) attrs.item(i);
- Attr newAttr = null;
-
- String attrName = attr.getNodeName();
- pos = attrName.indexOf(':');
- if (pos > 0 && pos < attrName.length() - 1) {
- attrName = attrName.substring(pos + 1);
- newAttr = newDoc.createAttributeNS(namespaceUri, attrName);
- newAttr.setPrefix(prefix);
- } else {
- newAttr = newDoc.createAttribute(attrName);
- }
-
- newAttr.setNodeValue(attr.getNodeValue());
-
- if (pos > 0) {
- newNode.getAttributes().setNamedItemNS(newAttr);
- } else {
- newNode.getAttributes().setNamedItem(newAttr);
- }
- }
-
- // Merge all child elements and texts
- for (Node child = oldNode.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- duplicateNode(newNode, (Element) child, namespaceUri, prefix);
-
- } else if (child.getNodeType() == Node.TEXT_NODE) {
- Text newText = newDoc.createTextNode(child.getNodeValue());
- newNode.appendChild(newText);
- }
- }
-
- return newNode;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSource.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSource.java
deleted file mode 100755
index 54ebd34..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSource.java
+++ /dev/null
@@ -1,999 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.sources;
-
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.io.NonClosingInputStream;
-import com.android.io.NonClosingInputStream.CloseBehavior;
-import com.android.sdklib.internal.repository.CanceledByUserException;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.internal.repository.packages.BuildToolPackage;
-import com.android.sdklib.internal.repository.packages.DocPackage;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformPackage;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.SamplePackage;
-import com.android.sdklib.internal.repository.packages.SourcePackage;
-import com.android.sdklib.internal.repository.packages.SystemImagePackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.net.ssl.SSLKeyException;
-import javax.xml.XMLConstants;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-/**
- * An sdk-addon or sdk-repository source, i.e. a download site.
- * It may be a full repository or an add-on only repository.
- * A repository describes one or {@link Package}s available for download.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
-
- private String mUrl;
-
- private Package[] mPackages;
- private String mDescription;
- private String mFetchError;
- private final String mUiName;
-
- private static final SdkSourceProperties sSourcesProps = new SdkSourceProperties();
-
- /**
- * Constructs a new source for the given repository URL.
- * @param url The source URL. Cannot be null. If the URL ends with a /, the default
- * repository.xml filename will be appended automatically.
- * @param uiName The UI-visible name of the source. Can be null.
- */
- public SdkSource(String url, String uiName) {
-
- // URLs should not be null and should not have whitespace.
- if (url == null) {
- url = "";
- }
- url = url.trim();
-
- // if the URL ends with a /, it must be "directory" resource,
- // in which case we automatically add the default file that will
- // looked for. This way it will be obvious to the user which
- // resource we are actually trying to fetch.
- if (url.endsWith("/")) { //$NON-NLS-1$
- String[] names = getDefaultXmlFileUrls();
- if (names.length > 0) {
- url += names[0];
- }
- }
-
- if (uiName == null) {
- uiName = sSourcesProps.getProperty(SdkSourceProperties.KEY_NAME, url, null);
- } else {
- sSourcesProps.setProperty(SdkSourceProperties.KEY_NAME, url, uiName);
- }
-
- mUrl = url;
- mUiName = uiName;
- setDefaultDescription();
- }
-
- /**
- * Returns true if this is an addon source.
- * We only load addons and extras from these sources.
- */
- public abstract boolean isAddonSource();
-
- /**
- * Returns true if this is a system-image source.
- * We only load system-images from these sources.
- */
- public abstract boolean isSysImgSource();
-
-
- /**
- * Returns the basename of the default URLs to try to download the
- * XML manifest.
- * E.g. this is typically SdkRepoConstants.URL_DEFAULT_XML_FILE
- * or SdkAddonConstants.URL_DEFAULT_XML_FILE
- */
- protected abstract String[] getDefaultXmlFileUrls();
-
- /** Returns SdkRepoConstants.NS_LATEST_VERSION or SdkAddonConstants.NS_LATEST_VERSION. */
- protected abstract int getNsLatestVersion();
-
- /** Returns SdkRepoConstants.NS_URI or SdkAddonConstants.NS_URI. */
- protected abstract String getNsUri();
-
- /** Returns SdkRepoConstants.NS_PATTERN or SdkAddonConstants.NS_PATTERN. */
- protected abstract String getNsPattern();
-
- /** Returns SdkRepoConstants.getSchemaUri() or SdkAddonConstants.getSchemaUri(). */
- protected abstract String getSchemaUri(int version);
-
- /* Returns SdkRepoConstants.NODE_SDK_REPOSITORY or SdkAddonConstants.NODE_SDK_ADDON. */
- protected abstract String getRootElementName();
-
- /** Returns SdkRepoConstants.getXsdStream() or SdkAddonConstants.getXsdStream(). */
- protected abstract InputStream getXsdStream(int version);
-
- /**
- * In case we fail to load an XML, examine the XML to see if it matches a <b>future</b>
- * schema that as at least a <code>tools</code> node that we could load to update the
- * SDK Manager.
- *
- * @param xml The input XML stream. Can be null.
- * @return Null on failure, otherwise returns an XML DOM with just the tools we
- * need to update this SDK Manager.
- * @null Can return null on failure.
- */
- protected abstract Document findAlternateToolsXml(@Nullable InputStream xml)
- throws IOException;
-
- /**
- * Two repo source are equal if they have the same URL.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof SdkSource) {
- SdkSource rs = (SdkSource) obj;
- return rs.getUrl().equals(this.getUrl());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return mUrl.hashCode();
- }
-
- /**
- * Implementation of the {@link Comparable} interface.
- * Simply compares the URL using the string's default ordering.
- */
- @Override
- public int compareTo(SdkSource rhs) {
- return this.getUrl().compareTo(rhs.getUrl());
- }
-
- /**
- * Returns the UI-visible name of the source. Can be null.
- */
- public String getUiName() {
- return mUiName;
- }
-
- /** Returns the URL of the XML file for this source. */
- public String getUrl() {
- return mUrl;
- }
-
- /**
- * Returns the list of known packages found by the last call to load().
- * This is null when the source hasn't been loaded yet -- caller should
- * then call {@link #load} to load the packages.
- */
- public Package[] getPackages() {
- return mPackages;
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void setPackages(Package[] packages) {
- mPackages = packages;
-
- if (mPackages != null) {
- // Order the packages.
- Arrays.sort(mPackages, null);
- }
- }
-
- /**
- * Clear the internal packages list. After this call, {@link #getPackages()} will return
- * null till load() is called.
- */
- public void clearPackages() {
- setPackages(null);
- }
-
- /**
- * Indicates if the source is enabled.
- * <p/>
- * A 3rd-party add-on source can be disabled by the user to prevent from loading it.
- *
- * @return True if the source is enabled (default is true).
- */
- public boolean isEnabled() {
- // A URL is enabled if it's not in the disabled list.
- return sSourcesProps.getProperty(SdkSourceProperties.KEY_DISABLED, mUrl, null) == null;
- }
-
- /**
- * Changes whether the source is marked as enabled.
- * <p/>
- * When <em>changing</em> the enable state, the current package list is purged
- * and the next {@code load} will either return an empty list (if disabled) or
- * the actual package list (if enabled.)
- *
- * @param enabled True for the source to be enabled (can be loaded), false otherwise.
- */
- public void setEnabled(boolean enabled) {
- if (enabled != isEnabled()) {
- // First we clear the current package list, which will force the
- // next load() to actually set the package list as desired.
- clearPackages();
-
- sSourcesProps.setProperty(SdkSourceProperties.KEY_DISABLED, mUrl,
- enabled ? null /*remove*/ : "disabled"); //$NON-NLS-1$
- }
- }
-
- /**
- * Returns the short description of the source, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging.
- * For UI display, use the {@link IDescription} interface.
- */
- @Override
- public String toString() {
- String s = getShortDescription();
- if (s != null) {
- return s;
- }
- return super.toString();
- }
-
- @Override
- public String getShortDescription() {
-
- if (mUiName != null && !mUiName.isEmpty()) {
-
- String host = "malformed URL";
-
- try {
- URL u = new URL(mUrl);
- host = u.getHost();
- } catch (MalformedURLException e) {
- }
-
- return String.format("%1$s (%2$s)", mUiName, host);
-
- }
- return mUrl;
- }
-
- @Override
- public String getLongDescription() {
- // Note: in a normal workflow, mDescription is filled by setDefaultDescription().
- // However for packages made by unit tests or such, this can be null.
- return mDescription == null ? "" : mDescription; //$NON-NLS-1$
- }
-
- /**
- * Returns the last fetch error description.
- * If there was no error, returns null.
- */
- public String getFetchError() {
- return mFetchError;
- }
-
- /**
- * Tries to fetch the repository index for the given URL and updates the package list.
- * When a source is disabled, this create an empty non-null package list.
- * <p/>
- * Callers can get the package list using {@link #getPackages()} after this. It will be
- * null in case of error, in which case {@link #getFetchError()} can be used to an
- * error message.
- */
- public void load(DownloadCache cache, ITaskMonitor monitor, boolean forceHttp) {
-
- setDefaultDescription();
- monitor.setProgressMax(7);
-
- if (!isEnabled()) {
- setPackages(new Package[0]);
- mDescription += "\nSource is disabled.";
- monitor.incProgress(7);
- return;
- }
-
- String url = mUrl;
- if (forceHttp) {
- url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- monitor.setDescription("Fetching URL: %1$s", url);
- monitor.incProgress(1);
-
- mFetchError = null;
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- Exception[] exception = new Exception[] { null };
- Document validatedDoc = null;
- boolean usingAlternateXml = false;
- boolean usingAlternateUrl = false;
- String validatedUri = null;
-
- String[] defaultNames = getDefaultXmlFileUrls();
- String firstDefaultName = defaultNames.length > 0 ? defaultNames[0] : "";
-
- InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
- if (xml != null) {
- int version = getXmlSchemaVersion(xml);
- if (version == 0) {
- closeStream(xml);
- xml = null;
- }
- }
-
- // FIXME: this is a quick fix to support an alternate upgrade path.
- // The whole logic below needs to be updated.
- if (xml == null && defaultNames.length > 0) {
- ITaskMonitor subMonitor = monitor.createSubMonitor(1);
- subMonitor.setProgressMax(defaultNames.length);
-
- String baseUrl = url;
- if (!baseUrl.endsWith("/")) {
- int pos = baseUrl.lastIndexOf('/');
- if (pos > 0) {
- baseUrl = baseUrl.substring(0, pos + 1);
- }
- }
-
- for (String name : defaultNames) {
- String newUrl = baseUrl + name;
- if (newUrl.equals(url)) {
- continue;
- }
- xml = fetchXmlUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
- if (xml != null) {
- int version = getXmlSchemaVersion(xml);
- if (version == 0) {
- closeStream(xml);
- xml = null;
- } else {
- url = newUrl;
- subMonitor.incProgress(
- subMonitor.getProgressMax() - subMonitor.getProgress());
- break;
- }
- }
- }
- } else {
- monitor.incProgress(1);
- }
-
- // If the original URL can't be fetched
- // and the URL doesn't explicitly end with our filename
- // and it wasn't an HTTP authentication operation canceled by the user
- // then make another tentative after changing the URL.
- if (xml == null
- && !url.endsWith(firstDefaultName)
- && !(exception[0] instanceof CanceledByUserException)) {
- if (!url.endsWith("/")) { //$NON-NLS-1$
- url += "/"; //$NON-NLS-1$
- }
- url += firstDefaultName;
-
- xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
- usingAlternateUrl = true;
- } else {
- monitor.incProgress(1);
- }
-
- // FIXME this needs to revisited.
- if (xml != null) {
- monitor.setDescription("Validate XML: %1$s", url);
-
- ITaskMonitor subMonitor = monitor.createSubMonitor(2);
- subMonitor.setProgressMax(2);
- for (int tryOtherUrl = 0; tryOtherUrl < 2; tryOtherUrl++) {
- // Explore the XML to find the potential XML schema version
- int version = getXmlSchemaVersion(xml);
-
- if (version >= 1 && version <= getNsLatestVersion()) {
- // This should be a version we can handle. Try to validate it
- // and report any error as invalid XML syntax,
-
- String uri = validateXml(xml, url, version, validationError, validatorFound);
- if (uri != null) {
- // Validation was successful
- validatedDoc = getDocument(xml, monitor);
- validatedUri = uri;
-
- if (usingAlternateUrl && validatedDoc != null) {
- // If the second tentative succeeded, indicate it in the console
- // with the URL that worked.
- monitor.log("Repository found at %1$s", url);
-
- // Keep the modified URL
- mUrl = url;
- }
- } else if (validatorFound[0].equals(Boolean.FALSE)) {
- // Validation failed because this JVM lacks a proper XML Validator
- mFetchError = validationError[0];
- } else {
- // We got a validator but validation failed. We know there's
- // what looks like a suitable root element with a suitable XMLNS
- // so it must be a genuine error of an XML not conforming to the schema.
- }
- } else if (version > getNsLatestVersion()) {
- // The schema used is more recent than what is supported by this tool.
- // Tell the user to upgrade, pointing him to the right version of the tool
- // package.
-
- try {
- validatedDoc = findAlternateToolsXml(xml);
- } catch (IOException e) {
- // Failed, will be handled below.
- }
- if (validatedDoc != null) {
- validationError[0] = null; // remove error from XML validation
- validatedUri = getNsUri();
- usingAlternateXml = true;
- }
-
- } else if (version < 1 && tryOtherUrl == 0 && !usingAlternateUrl) {
- // This is obviously not one of our documents.
- mFetchError = String.format(
- "Failed to validate the XML for the repository at URL '%1$s'",
- url);
-
- // If we haven't already tried the alternate URL, let's do it now.
- // We don't capture any fetch exception that happen during the second
- // fetch in order to avoid hiding any previous fetch errors.
- if (!url.endsWith(firstDefaultName)) {
- if (!url.endsWith("/")) { //$NON-NLS-1$
- url += "/"; //$NON-NLS-1$
- }
- url += firstDefaultName;
-
- closeStream(xml);
- xml = fetchXmlUrl(url, cache, subMonitor.createSubMonitor(1),
- null /* outException */);
- subMonitor.incProgress(1);
- // Loop to try the alternative document
- if (xml != null) {
- usingAlternateUrl = true;
- continue;
- }
- }
- } else if (version < 1 && usingAlternateUrl && mFetchError == null) {
- // The alternate URL is obviously not a valid XML either.
- // We only report the error if we failed to produce one earlier.
- mFetchError = String.format(
- "Failed to validate the XML for the repository at URL '%1$s'",
- url);
- }
-
- // If we get here either we succeeded or we ran out of alternatives.
- break;
- }
- }
-
- // If any exception was handled during the URL fetch, display it now.
- if (exception[0] != null) {
- mFetchError = "Failed to fetch URL";
-
- String reason = null;
- if (exception[0] instanceof FileNotFoundException) {
- // FNF has no useful getMessage, so we need to special handle it.
- reason = "File not found";
- mFetchError += ": " + reason;
- } else if (exception[0] instanceof SSLKeyException) {
- // That's a common error and we have a pref for it.
- reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
- mFetchError += ": HTTPS SSL error";
- } else if (exception[0].getMessage() != null) {
- reason =
- exception[0].getClass().getSimpleName().replace("Exception", "") //$NON-NLS-1$ //$NON-NLS-2$
- + ' '
- + exception[0].getMessage();
- } else {
- reason = exception[0].toString();
- }
-
- monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
- }
-
- if (validationError[0] != null) {
- monitor.logError("%s", validationError[0]); //$NON-NLS-1$
- }
-
- // Stop here if we failed to validate the XML. We don't want to load it.
- if (validatedDoc == null) {
- return;
- }
-
- if (usingAlternateXml) {
- // We found something using the "alternate" XML schema (that is the one made up
- // to support schema upgrades). That means the user can only install the tools
- // and needs to upgrade them before it download more stuff.
-
- // Is the manager running from inside ADT?
- // We check that com.android.ide.eclipse.adt.AdtPlugin exists using reflection.
-
- boolean isADT = false;
- try {
- Class<?> adt = Class.forName("com.android.ide.eclipse.adt.AdtPlugin"); //$NON-NLS-1$
- isADT = (adt != null);
- } catch (ClassNotFoundException e) {
- // pass
- }
-
- String info;
- if (isADT) {
- info = "This repository requires a more recent version of ADT. Please update the Eclipse Android plugin.";
- mDescription = "This repository requires a more recent version of ADT, the Eclipse Android plugin.\nYou must update it before you can see other new packages.";
-
- } else {
- info = "This repository requires a more recent version of the Tools. Please update.";
- mDescription = "This repository requires a more recent version of the Tools.\nYou must update it before you can see other new packages.";
- }
-
- mFetchError = mFetchError == null ? info : mFetchError + ". " + info;
- }
-
- monitor.incProgress(1);
-
- if (xml != null) {
- monitor.setDescription("Parse XML: %1$s", url);
- monitor.incProgress(1);
- parsePackages(validatedDoc, validatedUri, monitor);
- if (mPackages == null || mPackages.length == 0) {
- mDescription += "\nNo packages found.";
- } else if (mPackages.length == 1) {
- mDescription += "\nOne package found.";
- } else {
- mDescription += String.format("\n%1$d packages found.", mPackages.length);
- }
- }
-
- // done
- monitor.incProgress(1);
- closeStream(xml);
- }
-
- private void setDefaultDescription() {
- if (isAddonSource()) {
- String desc = "";
-
- if (mUiName != null) {
- desc += "Add-on Provider: " + mUiName;
- desc += "\n";
- }
- desc += "Add-on URL: " + mUrl;
-
- mDescription = desc;
- } else {
- mDescription = String.format("SDK Source: %1$s", mUrl);
- }
- }
-
- /**
- * Fetches the document at the given URL and returns it as a string. Returns
- * null if anything wrong happens and write errors to the monitor.
- *
- * @param urlString The URL to load, as a string.
- * @param monitor {@link ITaskMonitor} related to this URL.
- * @param outException If non null, where to store any exception that
- * happens during the fetch.
- */
- private InputStream fetchXmlUrl(String urlString,
- DownloadCache cache,
- ITaskMonitor monitor,
- Exception[] outException) {
- try {
- InputStream xml = cache.openCachedUrl(urlString, monitor);
- if (xml != null) {
- xml.mark(500000);
- xml = new NonClosingInputStream(xml);
- ((NonClosingInputStream) xml).setCloseBehavior(CloseBehavior.RESET);
- }
- return xml;
- } catch (Exception e) {
- if (outException != null) {
- outException[0] = e;
- }
- }
-
- return null;
- }
-
- /**
- * Closes the stream, ignore any exception from InputStream.close().
- * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
- */
- private void closeStream(InputStream is) {
- if (is != null) {
- if (is instanceof NonClosingInputStream) {
- ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
- }
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- }
-
- /**
- * Validates this XML against one of the requested SDK Repository schemas.
- * If the XML was correctly validated, returns the schema that worked.
- * If it doesn't validate, returns null and stores the error in outError[0].
- * If we can't find a validator, returns null and set validatorFound[0] to false.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected String validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
-
- if (xml == null) {
- return null;
- }
-
- try {
- Validator validator = getValidator(version);
-
- if (validator == null) {
- validatorFound[0] = Boolean.FALSE;
- outError[0] = String.format(
- "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
- url);
- return null;
- }
-
- validatorFound[0] = Boolean.TRUE;
-
- // Reset the stream if it supports that operation.
- assert xml.markSupported();
- xml.reset();
-
- // Validation throws a bunch of possible Exceptions on failure.
- validator.validate(new StreamSource(xml));
- return getSchemaUri(version);
-
- } catch (SAXParseException e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
- url,
- e.getLineNumber(),
- e.getColumnNumber(),
- e.toString());
-
- } catch (Exception e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nError: %2$s",
- url,
- e.toString());
- }
- return null;
- }
-
- /**
- * Manually parses the root element of the XML to extract the schema version
- * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N"
- * declaration.
- *
- * @return 1..{@link SdkRepoConstants#NS_LATEST_VERSION} for a valid schema version
- * or 0 if no schema could be found.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected int getXmlSchemaVersion(InputStream xml) {
- if (xml == null) {
- return 0;
- }
-
- // Get an XML document
- Document doc = null;
- try {
- assert xml.markSupported();
- xml.reset();
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // We don't want the default handler which prints errors to stderr.
- builder.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- doc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
-
- } catch (Exception e) {
- // Failed to reset XML stream
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- }
-
- if (doc == null) {
- return 0;
- }
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-repository
- // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(getNsPattern());
-
- String prefix = null;
- for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (getRootElementName().equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null) {
- Matcher m = nsPattern.matcher(uri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- return Integer.parseInt(version);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
- }
- }
- }
- }
-
- return 0;
- }
-
- /**
- * Helper method that returns a validator for our XSD, or null if the current Java
- * implementation can't process XSD schemas.
- *
- * @param version The version of the XML Schema.
- * See {@link SdkRepoConstants#getXsdStream(int)}
- */
- private Validator getValidator(int version) throws SAXException {
- InputStream xsdStream = getXsdStream(version);
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-
- if (factory == null) {
- return null;
- }
-
- // This may throw a SAX Exception if the schema itself is not a valid XSD
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
-
- Validator validator = schema == null ? null : schema.newValidator();
-
- // We don't want the default handler, which by default dumps errors to stderr.
- validator.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- return validator;
- }
-
- /**
- * Parse all packages defined in the SDK Repository XML and creates
- * a new mPackages array with them.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
-
- Node root = getFirstChild(doc, nsUri, getRootElementName());
- if (root != null) {
-
- ArrayList<Package> packages = new ArrayList<Package>();
-
- // Parse license definitions
- HashMap<String, String> licenses = new HashMap<String, String>();
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- child.getLocalName().equals(RepoConstants.NODE_LICENSE)) {
- Node id = child.getAttributes().getNamedItem(RepoConstants.ATTR_ID);
- if (id != null) {
- licenses.put(id.getNodeValue(), child.getTextContent());
- }
- }
- }
-
- // Parse packages
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- String name = child.getLocalName();
- Package p = null;
-
- try {
- // We can load add-on and extra packages from all sources, either
- // internal or user sources.
- if (SdkAddonConstants.NODE_ADD_ON.equals(name)) {
- p = new AddonPackage(this, child, nsUri, licenses);
-
- } else if (SdkAddonConstants.NODE_EXTRA.equals(name)) {
- p = new ExtraPackage(this, child, nsUri, licenses);
-
- } else if (!isAddonSource()) {
- // We only load platform, doc and tool packages from internal
- // sources, never from user sources.
- if (SdkRepoConstants.NODE_PLATFORM.equals(name)) {
- p = new PlatformPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_DOC.equals(name)) {
- p = new DocPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_TOOL.equals(name)) {
- p = new ToolPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_PLATFORM_TOOL.equals(name)) {
- p = new PlatformToolPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_BUILD_TOOL.equals(name)) {
- p = new BuildToolPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_SAMPLE.equals(name)) {
- p = new SamplePackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_SYSTEM_IMAGE.equals(name)) {
- p = new SystemImagePackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_SOURCE.equals(name)) {
- p = new SourcePackage(this, child, nsUri, licenses);
- }
- }
-
- if (p != null) {
- packages.add(p);
- monitor.logVerbose("Found %1$s", p.getShortDescription());
- }
- } catch (Exception e) {
- // Ignore invalid packages
- monitor.logError("Ignoring invalid %1$s element: %2$s", name, e.toString());
- }
- }
- }
-
- setPackages(packages.toArray(new Package[packages.size()]));
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Returns the first child element with the given XML local name.
- * If xmlLocalName is null, returns the very first child element.
- */
- private Node getFirstChild(Node node, String nsUri, String xmlLocalName) {
-
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
- return child;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Takes an XML document as a string as parameter and returns a DOM for it.
- *
- * On error, returns null and prints a (hopefully) useful message on the monitor.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(true);
- factory.setNamespaceAware(true);
-
- DocumentBuilder builder = factory.newDocumentBuilder();
- assert xml.markSupported();
- xml.reset();
- Document doc = builder.parse(new InputSource(xml));
-
- return doc;
- } catch (ParserConfigurationException e) {
- monitor.logError("Failed to create XML document builder");
-
- } catch (SAXException e) {
- monitor.logError("Failed to parse XML document");
-
- } catch (IOException e) {
- monitor.logError("Failed to read XML document");
- }
-
- return null;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java
deleted file mode 100755
index 263e9d5..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.sources;
-
-import com.android.sdklib.repository.IDescription;
-
-
-/**
- * The category of a given {@link SdkSource} (which represents a download site).
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public enum SdkSourceCategory implements IDescription {
-
- /**
- * The default canonical and official Android repository.
- */
- ANDROID_REPO("Android Repository", true),
-
- /**
- * Repositories contributed by the SDK_UPDATER_URLS env var,
- * only used for local debugging.
- */
- GETENV_REPOS("Custom Repositories", false),
-
- /**
- * All third-party add-ons fetched from the Android repository.
- */
- ADDONS_3RD_PARTY("Third party Add-ons", true),
-
- /**
- * All add-ons contributed locally by the user via the "Add Add-on Site" button.
- */
- USER_ADDONS("User Add-ons", false),
-
- /**
- * Add-ons contributed by the SDK_UPDATER_USER_URLS env var,
- * only used for local debugging.
- */
- GETENV_ADDONS("Custom Add-ons", false);
-
-
- private final String mUiName;
- private final boolean mAlwaysDisplay;
-
- SdkSourceCategory(String uiName, boolean alwaysDisplay) {
- mUiName = uiName;
- mAlwaysDisplay = alwaysDisplay;
- }
-
- /**
- * Returns the UI-visible name of the category. Displayed in the available package tree.
- * Cannot be null nor empty.
- */
- public String getUiName() {
- return mUiName;
- }
-
- /**
- * True if this category must always be displayed by the available package tree, even
- * if empty.
- * When false, the category must not be displayed when empty.
- */
- public boolean getAlwaysDisplay() {
- return mAlwaysDisplay;
- }
-
- @Override
- public String getLongDescription() {
- return getUiName();
- }
-
- @Override
- public String getShortDescription() {
- return getUiName();
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java
deleted file mode 100755
index fb8014c..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.sources;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Properties;
-
-/**
- * Properties for individual sources which are persisted by a local settings file.
- * <p/>
- * All instances of {@link SdkSourceProperties} share the same singleton storage.
- * The persisted setting file is loaded as necessary, however callers must persist
- * it at some point by calling {@link #save()}.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkSourceProperties {
-
- /**
- * An internal file version number, in case we want to change the format later.
- */
- private static final String KEY_VERSION = "@version@"; //$NON-NLS-1$
- /**
- * The last known UI name of the source.
- */
- public static final String KEY_NAME = "@name@"; //$NON-NLS-1$
- /**
- * A non-null string if the source is disabled. Null if the source is enabled.
- */
- public static final String KEY_DISABLED = "@disabled@"; //$NON-NLS-1$
-
- private static final Properties sSourcesProperties = new Properties();
- private static final String SRC_FILENAME = "sites-settings.cfg"; //$NON-NLS-1$
-
- private static boolean sModified = false;
-
- public SdkSourceProperties() {
- }
-
- public void save() {
- synchronized (sSourcesProperties) {
- if (sModified && !sSourcesProperties.isEmpty()) {
- saveLocked();
- sModified = false;
- }
- }
- }
-
- /**
- * Retrieves a property for the given source URL and the given key type.
- * <p/>
- * Implementation detail: this loads the persistent settings file as needed.
- *
- * @param key The kind of property to retrieve for that source URL.
- * @param sourceUrl The source URL.
- * @param defaultValue The default value to return, if the property isn't found. Can be null.
- * @return The non-null string property for the key/sourceUrl or the default value.
- */
- @Nullable
- public String getProperty(@NonNull String key,
- @NonNull String sourceUrl,
- @Nullable String defaultValue) {
- String value = defaultValue;
-
- synchronized (sSourcesProperties) {
- if (sSourcesProperties.isEmpty()) {
- loadLocked();
- }
-
- value = sSourcesProperties.getProperty(key + sourceUrl, defaultValue);
- }
-
- return value;
- }
-
- /**
- * Sets or remove a property for the given source URL and the given key type.
- * <p/>
- * Implementation detail: this does <em>not</em> save the persistent settings file.
- * Somehow the caller will need to call the {@link #save()} method later.
- *
- * @param key The kind of property to retrieve for that source URL.
- * @param sourceUrl The source URL.
- * @param value The new value to set (if non null) or null to remove an existing property.
- */
- public void setProperty(String key, String sourceUrl, String value) {
- synchronized (sSourcesProperties) {
- if (sSourcesProperties.isEmpty()) {
- loadLocked();
- }
-
- key += sourceUrl;
-
- String old = sSourcesProperties.getProperty(key);
- if (value == null) {
- if (old != null) {
- sSourcesProperties.remove(key);
- sModified = true;
- }
- } else if (old == null || !old.equals(value)) {
- sSourcesProperties.setProperty(key, value);
- sModified = true;
- }
- }
- }
-
- /**
- * Returns an internal string representation of the underlying Properties map,
- * sorted by ascending keys. Useful for debugging and testing purposes only.
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder("<SdkSourceProperties"); //$NON-NLS-1$
- synchronized (sSourcesProperties) {
- List<Object> keys = Collections.list(sSourcesProperties.keys());
- Collections.sort(keys, new Comparator<Object>() {
- @Override
- public int compare(Object o1, Object o2) {
- return o1.toString().compareTo(o2.toString());
- }});
-
- for (Object key : keys) {
- sb.append('\n').append(key)
- .append(" = ").append(sSourcesProperties.get(key)); //$NON-NLS-1$
- }
- }
- sb.append('>');
- return sb.toString();
- }
-
- /** Load state from persistent file. Expects sSourcesProperties to be synchronized. */
- private void loadLocked() {
- // Load state from persistent file
- if (loadProperties()) {
- // If it lacks our magic version key, don't use it
- if (sSourcesProperties.getProperty(KEY_VERSION) == null) {
- sSourcesProperties.clear();
- }
-
- sModified = false;
- }
-
- if (sSourcesProperties.isEmpty()) {
- // Nothing was loaded. Initialize the storage with a version
- // identified. This isn't currently checked back, but we might
- // want it later if we decide to change the way this works.
- // The version key is chosen on purpose to not match any valid URL.
- sSourcesProperties.setProperty(KEY_VERSION, "1"); //$NON-NLS-1$ //$NON-NLS-2$
- }
- }
-
- /**
- * Load properties from default file. Extracted so that it can be mocked in tests.
- *
- * @return True if actually loaded the file. False if there was an IO error or no
- * file and nothing was loaded.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean loadProperties() {
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
- if (f.exists()) {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(f);
- sSourcesProperties.load(fis);
- } catch (IOException ignore) {
- // nop
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException ignore) {}
- }
- }
-
- return true;
- }
- } catch (AndroidLocationException ignore) {
- // nop
- }
- return false;
- }
-
- /**
- * Save file to disk. Expects sSourcesProperties to be synchronized.
- * Made accessible for testing purposes.
- * For public usage, please use {@link #save()} instead.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void saveLocked() {
- // Persist it to the file
- FileOutputStream fos = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
-
- fos = new FileOutputStream(f);
-
- sSourcesProperties.store(fos,"## Sites Settings for Android SDK Manager");//$NON-NLS-1$
-
- } catch (AndroidLocationException ignore) {
- // nop
- } catch (IOException ignore) {
- // nop
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException ignore) {}
- }
- }
- }
-
- /** Empty current property list. Made accessible for testing purposes. */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void clear() {
- synchronized (sSourcesProperties) {
- sSourcesProperties.clear();
- sModified = false;
- }
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSources.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSources.java
deleted file mode 100755
index 7c663ca..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSources.java
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.sources;
-
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.repository.SdkSysImgConstants;
-import com.android.utils.ILogger;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.Iterator;
-import java.util.Properties;
-import java.util.Map.Entry;
-
-/**
- * A list of sdk-repository and sdk-addon sources, sorted by {@link SdkSourceCategory}.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkSources {
-
- private static final String KEY_COUNT = "count";
-
- private static final String KEY_SRC = "src";
-
- private static final String SRC_FILENAME = "repositories.cfg"; //$NON-NLS-1$
-
- private final EnumMap<SdkSourceCategory, ArrayList<SdkSource>> mSources =
- new EnumMap<SdkSourceCategory, ArrayList<SdkSource>>(SdkSourceCategory.class);
-
- private ArrayList<Runnable> mChangeListeners; // lazily initialized
-
-
- public SdkSources() {
- }
-
- /**
- * Adds a new source to the Sources list.
- * <p/>
- * Implementation detail: {@link SdkSources} doesn't invoke {@link #notifyChangeListeners()}
- * directly. Callers who use {@code add()} are responsible for notifying the listeners once
- * they are done modifying the sources list. The intent is to notify the listeners only once
- * at the end, not for every single addition.
- */
- public void add(SdkSourceCategory category, SdkSource source) {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(category);
- if (list == null) {
- list = new ArrayList<SdkSource>();
- mSources.put(category, list);
- }
-
- list.add(source);
- }
- }
-
- /**
- * Removes a source from the Sources list.
- * <p/>
- * Callers who remove entries are responsible for notifying the listeners using
- * {@link #notifyChangeListeners()} once they are done modifying the sources list.
- */
- public void remove(SdkSource source) {
- synchronized (mSources) {
- Iterator<Entry<SdkSourceCategory, ArrayList<SdkSource>>> it =
- mSources.entrySet().iterator();
- while (it.hasNext()) {
- Entry<SdkSourceCategory, ArrayList<SdkSource>> entry = it.next();
- ArrayList<SdkSource> list = entry.getValue();
-
- if (list.remove(source)) {
- if (list.isEmpty()) {
- // remove the entry since the source list became empty
- it.remove();
- }
- }
- }
- }
- }
-
- /**
- * Removes all the sources in the given category.
- * <p/>
- * Callers who remove entries are responsible for notifying the listeners using
- * {@link #notifyChangeListeners()} once they are done modifying the sources list.
- */
- public void removeAll(SdkSourceCategory category) {
- synchronized (mSources) {
- mSources.remove(category);
- }
- }
-
- /**
- * Returns a set of all categories that must be displayed. This includes all
- * categories that are to be always displayed as well as all categories which
- * have at least one source.
- * Might return a empty array, but never returns null.
- */
- public SdkSourceCategory[] getCategories() {
- ArrayList<SdkSourceCategory> cats = new ArrayList<SdkSourceCategory>();
-
- for (SdkSourceCategory cat : SdkSourceCategory.values()) {
- if (cat.getAlwaysDisplay()) {
- cats.add(cat);
- } else {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(cat);
- if (list != null && !list.isEmpty()) {
- cats.add(cat);
- }
- }
- }
- }
-
- return cats.toArray(new SdkSourceCategory[cats.size()]);
- }
-
- /**
- * Returns a new array of sources attached to the given category.
- * Might return an empty array, but never returns null.
- */
- public SdkSource[] getSources(SdkSourceCategory category) {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(category);
- if (list == null) {
- return new SdkSource[0];
- } else {
- return list.toArray(new SdkSource[list.size()]);
- }
- }
- }
-
- /**
- * Returns true if there are sources for the given category.
- */
- public boolean hasSources(SdkSourceCategory category) {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(category);
- return list != null && !list.isEmpty();
- }
- }
-
- /**
- * Returns an array of the sources across all categories. This is never null.
- */
- public SdkSource[] getAllSources() {
- synchronized (mSources) {
- int n = 0;
-
- for (ArrayList<SdkSource> list : mSources.values()) {
- n += list.size();
- }
-
- SdkSource[] sources = new SdkSource[n];
-
- int i = 0;
- for (ArrayList<SdkSource> list : mSources.values()) {
- for (SdkSource source : list) {
- sources[i++] = source;
- }
- }
-
- return sources;
- }
- }
-
- /**
- * Each source keeps a local cache of whatever it loaded recently.
- * This calls {@link SdkSource#clearPackages()} on all the available sources,
- * and the next call to {@link SdkSource#getPackages()} will actually reload
- * the remote package list.
- */
- public void clearAllPackages() {
- synchronized (mSources) {
- for (ArrayList<SdkSource> list : mSources.values()) {
- for (SdkSource source : list) {
- source.clearPackages();
- }
- }
- }
- }
-
- /**
- * Returns the category of a given source, or null if the source is unknown.
- * <p/>
- * Note that this method uses object identity to find a given source, and does
- * not identify sources by their URL like {@link #hasSourceUrl(SdkSource)} does.
- * <p/>
- * The search is O(N), which should be acceptable on the expectedly small source list.
- */
- public SdkSourceCategory getCategory(SdkSource source) {
- if (source != null) {
- synchronized (mSources) {
- for (Entry<SdkSourceCategory, ArrayList<SdkSource>> entry : mSources.entrySet()) {
- if (entry.getValue().contains(source)) {
- return entry.getKey();
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Returns true if there's already a similar source in the sources list
- * under any category.
- * <p/>
- * Important: The match is NOT done on object identity.
- * Instead, this searches for a <em>similar</em> source, based on
- * {@link SdkSource#equals(Object)} which compares the source URLs.
- * <p/>
- * The search is O(N), which should be acceptable on the expectedly small source list.
- */
- public boolean hasSourceUrl(SdkSource source) {
- synchronized (mSources) {
- for (ArrayList<SdkSource> list : mSources.values()) {
- for (SdkSource s : list) {
- if (s.equals(source)) {
- return true;
- }
- }
- }
- return false;
- }
- }
-
- /**
- * Returns true if there's already a similar source in the sources list
- * under the specified category.
- * <p/>
- * Important: The match is NOT done on object identity.
- * Instead, this searches for a <em>similar</em> source, based on
- * {@link SdkSource#equals(Object)} which compares the source URLs.
- * <p/>
- * The search is O(N), which should be acceptable on the expectedly small source list.
- */
- public boolean hasSourceUrl(SdkSourceCategory category, SdkSource source) {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(category);
- if (list != null) {
- for (SdkSource s : list) {
- if (s.equals(source)) {
- return true;
- }
- }
- }
- return false;
- }
- }
-
- /**
- * Loads all user sources. This <em>replaces</em> all existing user sources
- * by the ones from the property file.
- * <p/>
- * This calls {@link #notifyChangeListeners()} at the end of the operation.
- */
- public void loadUserAddons(ILogger log) {
- // Implementation detail: synchronize on the sources list to make sure that
- // a- the source list doesn't change while we load/save it, and most important
- // b- to make sure it's not being saved while loaded or the reverse.
- // In most cases we do these operation from the UI thread so it's not really
- // that necessary. This is more a protection in case of someone calls this
- // from a worker thread by mistake.
- synchronized (mSources) {
- // Remove all existing user sources
- removeAll(SdkSourceCategory.USER_ADDONS);
-
- // Load new user sources from property file
- FileInputStream fis = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
- if (f.exists()) {
- fis = new FileInputStream(f);
-
- Properties props = new Properties();
- props.load(fis);
-
- int count = Integer.parseInt(props.getProperty(KEY_COUNT, "0"));
-
- for (int i = 0; i < count; i++) {
- String url = props.getProperty(String.format("%s%02d", KEY_SRC, i)); //$NON-NLS-1$
- if (url != null) {
- // FIXME: this code originally only dealt with add-on XML sources.
- // Now we'd like it to deal with system-image sources too, but we
- // don't know which kind of object it is (at least not without
- // trying to fetch it.) As a temporary workaround, just take a
- // guess based on the leaf URI name. However ideally what we can
- // simply do is add a checkbox "is system-image XML" in the user
- // dialog and pass this info down here. Another alternative is to
- // make a "dynamic" source object that tries to guess its type once
- // the URI has been fetched.
- SdkSource s;
- if (url.endsWith(SdkSysImgConstants.URL_DEFAULT_FILENAME)) {
- s = new SdkSysImgSource(url, null/*uiName*/);
- } else {
- s = new SdkAddonSource(url, null/*uiName*/);
- }
- if (!hasSourceUrl(s)) {
- add(SdkSourceCategory.USER_ADDONS, s);
- }
- }
- }
- }
-
- } catch (NumberFormatException e) {
- log.error(e, null);
-
- } catch (AndroidLocationException e) {
- log.error(e, null);
-
- } catch (IOException e) {
- log.error(e, null);
-
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
- }
- notifyChangeListeners();
- }
-
- /**
- * Saves all the user sources.
- * @param log Logger. Cannot be null.
- */
- public void saveUserAddons(ILogger log) {
- // See the implementation detail note in loadUserAddons() about the synchronization.
- synchronized (mSources) {
- FileOutputStream fos = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
-
- fos = new FileOutputStream(f);
-
- Properties props = new Properties();
-
- int count = 0;
- for (SdkSource s : getSources(SdkSourceCategory.USER_ADDONS)) {
- props.setProperty(String.format("%s%02d", KEY_SRC, count), //$NON-NLS-1$
- s.getUrl());
- count++;
- }
- props.setProperty(KEY_COUNT, Integer.toString(count));
-
- props.store( fos, "## User Sources for Android SDK Manager"); //$NON-NLS-1$
-
- } catch (AndroidLocationException e) {
- log.error(e, null);
-
- } catch (IOException e) {
- log.error(e, null);
-
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
- }
- }
- }
-
- /**
- * Adds a listener that will be notified when the sources list has changed.
- *
- * @param changeListener A non-null listener to add. Ignored if already present.
- * @see SdkSources#notifyChangeListeners()
- */
- public void addChangeListener(Runnable changeListener) {
- assert changeListener != null;
- if (mChangeListeners == null) {
- mChangeListeners = new ArrayList<Runnable>();
- }
- synchronized (mChangeListeners) {
- if (changeListener != null && !mChangeListeners.contains(changeListener)) {
- mChangeListeners.add(changeListener);
- }
- }
- }
-
- /**
- * Removes a listener from the list of listeners to notify when the sources change.
- *
- * @param changeListener A listener to remove. Ignored if not previously added.
- */
- public void removeChangeListener(Runnable changeListener) {
- if (mChangeListeners != null && changeListener != null) {
- synchronized (mChangeListeners) {
- mChangeListeners.remove(changeListener);
- }
- }
- }
-
- /**
- * Invoke all the registered change listeners, if any.
- * <p/>
- * This <em>may</em> be called from a worker thread, in which case the runnable
- * should take care of only updating UI from a main thread.
- */
- public void notifyChangeListeners() {
- if (mChangeListeners == null) {
- return;
- }
- synchronized (mChangeListeners) {
- for (Runnable runnable : mChangeListeners) {
- try {
- runnable.run();
- } catch (Throwable ignore) {
- assert ignore == null :
- "A SdkSource.ChangeListener failed with an exception: " + ignore.toString();
- }
- }
- }
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSysImgSource.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSysImgSource.java
deleted file mode 100755
index 1c90b4c..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkSysImgSource.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.sources;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.repository.SdkSysImgConstants;
-
-import org.w3c.dom.Document;
-
-import java.io.InputStream;
-
-
-/**
- * An sdk-sys-img source, i.e. a download site for system-image packages.
- * A repository describes one or more {@link Package}s available for download.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkSysImgSource extends SdkSource {
-
- /**
- * Constructs a new source for the given repository URL.
- * @param url The source URL. Cannot be null. If the URL ends with a /, the default
- * sys-img.xml filename will be appended automatically.
- * @param uiName The UI-visible name of the source. Can be null.
- */
- public SdkSysImgSource(String url, String uiName) {
- super(url, uiName);
- }
-
- /**
- * Returns true if this is an addon source.
- * We only load addons and extras from these sources.
- */
- @Override
- public boolean isAddonSource() {
- return false;
- }
-
- /**
- * Returns true if this is a system-image source.
- * We only load system-images from these sources.
- */
- @Override
- public boolean isSysImgSource() {
- return true;
- }
-
-
- @Override
- protected String[] getDefaultXmlFileUrls() {
- return new String[] { SdkSysImgConstants.URL_DEFAULT_FILENAME };
- }
-
- @Override
- protected int getNsLatestVersion() {
- return SdkSysImgConstants.NS_LATEST_VERSION;
- }
-
- @Override
- protected String getNsUri() {
- return SdkSysImgConstants.NS_URI;
- }
-
- @Override
- protected String getNsPattern() {
- return SdkSysImgConstants.NS_PATTERN;
- }
-
- @Override
- protected String getSchemaUri(int version) {
- return SdkSysImgConstants.getSchemaUri(version);
- }
-
- @Override
- protected String getRootElementName() {
- return SdkSysImgConstants.NODE_SDK_SYS_IMG;
- }
-
- @Override
- protected InputStream getXsdStream(int version) {
- return SdkSysImgConstants.getXsdStream(version);
- }
-
- /**
- * This kind of schema does not support forward-evolution of the <tool> element.
- *
- * @param xml The input XML stream. Can be null.
- * @return Always null.
- * @null This implementation always return null.
- */
- @Override
- protected Document findAlternateToolsXml(@Nullable InputStream xml) {
- return null;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/ArchiveInfo.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/ArchiveInfo.java
deleted file mode 100755
index c209838..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/ArchiveInfo.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.updater;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.ArchiveReplacement;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * Represents an archive that we want to install.
- * Note that the installer deals with archives whereas the user mostly sees packages
- * but as far as we are concerned for installation there's a 1-to-1 mapping.
- * <p/>
- * A new archive is always a remote archive that needs to be downloaded and then
- * installed. It can replace an existing local one. It can also depends on another
- * (new or local) archive, which means the dependent archive needs to be successfully
- * installed first. Finally this archive can also be a dependency for another one.
- * <p/>
- * The accepted and rejected flags are used by {@code SdkUpdaterChooserDialog} to follow
- * user choices. The installer should never install something that is not accepted.
- * <p/>
- * <em>Note</em>: There is currently no logic to support more than one level of
- * dependency, either here or in the {@code SdkUpdaterChooserDialog}, since we currently
- * have no need for it.
- *
- * @see ArchiveInfo#ArchiveInfo(Archive, Archive, ArchiveInfo[])
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class ArchiveInfo extends ArchiveReplacement implements Comparable<ArchiveInfo> {
-
- private final ArchiveInfo[] mDependsOn;
- private final ArrayList<ArchiveInfo> mDependencyFor = new ArrayList<ArchiveInfo>();
- private boolean mAccepted;
- private boolean mRejected;
-
- /**
- * Creates a new replacement where the {@code newArchive} will replace the
- * currently installed {@code replaced} archive.
- * When {@code newArchive} is not intended to replace anything (e.g. because
- * the user is installing a new package not present on her system yet), then
- * {@code replace} shall be null.
- *
- * @param newArchive A "new archive" to be installed. This is always an archive
- * that comes from a remote site. This <em>may</em> be null.
- * @param replaced An optional local archive that the new one will replace.
- * Can be null if this archive does not replace anything.
- * @param dependsOn An optional new or local dependency, that is an archive that
- * <em>this</em> archive depends upon. In other words, we can only install
- * this archive if the dependency has been successfully installed. It also
- * means we need to install the dependency first. Can be null or empty.
- * However it cannot contain nulls.
- */
- public ArchiveInfo(
- @Nullable Archive newArchive,
- @Nullable Archive replaced,
- @Nullable ArchiveInfo[] dependsOn) {
- super(newArchive, replaced);
- mDependsOn = dependsOn;
- }
-
- /**
- * Returns an optional new or local dependency, that is an archive that <em>this</em>
- * archive depends upon. In other words, we can only install this archive if the
- * dependency has been successfully installed. It also means we need to install the
- * dependency first.
- * <p/>
- * This array can be null or empty. It can't contain nulls though.
- */
- @Nullable
- public ArchiveInfo[] getDependsOn() {
- return mDependsOn;
- }
-
- /**
- * Returns true if this new archive is a dependency for <em>another</em> one that we
- * want to install.
- */
- public boolean isDependencyFor() {
- return !mDependencyFor.isEmpty();
- }
-
- /**
- * Adds an {@link ArchiveInfo} for which <em>this</em> package is a dependency.
- * This means the package added here depends on this package.
- */
- @NonNull
- public ArchiveInfo addDependencyFor(ArchiveInfo dependencyFor) {
- if (!mDependencyFor.contains(dependencyFor)) {
- mDependencyFor.add(dependencyFor);
- }
-
- return this;
- }
-
- /**
- * Returns the list of {@link ArchiveInfo} for which <em>this</em> package is a dependency.
- * This means the packages listed here depend on this package.
- * <p/>
- * Implementation detail: this is the internal mutable list. Callers should not modify it.
- * This list can be empty but is never null.
- */
- @NonNull
- public Collection<ArchiveInfo> getDependenciesFor() {
- return mDependencyFor;
- }
-
- /**
- * Sets whether this archive was accepted (either manually by the user or
- * automatically if it doesn't have a license) for installation.
- */
- public void setAccepted(boolean accepted) {
- mAccepted = accepted;
- }
-
- /**
- * Returns whether this archive was accepted (either manually by the user or
- * automatically if it doesn't have a license) for installation.
- */
- public boolean isAccepted() {
- return mAccepted;
- }
-
- /**
- * Sets whether this archive was rejected manually by the user.
- * An archive can neither accepted nor rejected.
- */
- public void setRejected(boolean rejected) {
- mRejected = rejected;
- }
-
- /**
- * Returns whether this archive was rejected manually by the user.
- * An archive can neither accepted nor rejected.
- */
- public boolean isRejected() {
- return mRejected;
- }
-
- /**
- * ArchiveInfos are compared using ther "new archive" ordering.
- *
- * @see Archive#compareTo(Archive)
- */
- @Override
- public int compareTo(ArchiveInfo rhs) {
- if (getNewArchive() != null && rhs != null) {
- return getNewArchive().compareTo(rhs.getNewArchive());
- }
- return 0;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/ISettingsPage.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/ISettingsPage.java
deleted file mode 100755
index 1da1b01..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/ISettingsPage.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.updater;
-
-import com.android.sdklib.internal.repository.DownloadCache;
-
-import java.net.URL;
-import java.util.Properties;
-
-/**
- * Interface that a settings page must implement.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface ISettingsPage {
-
- /**
- * Java system setting picked up by {@link URL} for http proxy port.
- * Type: String.
- */
- String KEY_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$
-
- /**
- * Java system setting picked up by {@link URL} for http proxy host.
- * Type: String.
- */
- String KEY_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$
-
- /**
- * Setting to force using http:// instead of https:// connections.
- * Type: Boolean.
- * Default: False.
- */
- String KEY_FORCE_HTTP = "sdkman.force.http"; //$NON-NLS-1$
-
- /**
- * Setting to display only packages that are new or updates.
- * Type: Boolean.
- * Default: True.
- */
- String KEY_SHOW_UPDATE_ONLY = "sdkman.show.update.only"; //$NON-NLS-1$
-
- /**
- * Setting to ask for permission before restarting ADB.
- * Type: Boolean.
- * Default: False.
- */
- String KEY_ASK_ADB_RESTART = "sdkman.ask.adb.restart"; //$NON-NLS-1$
-
- /**
- * Setting to use the {@link DownloadCache}, for small manifest XML files.
- * Type: Boolean.
- * Default: True.
- */
- String KEY_USE_DOWNLOAD_CACHE = "sdkman.use.dl.cache"; //$NON-NLS-1$
-
- /**
- * Setting to enabling previews in the package list
- * Type: Boolean.
- * Default: True.
- */
- String KEY_ENABLE_PREVIEWS = "sdkman.enable.previews2"; //$NON-NLS-1$
-
- /**
- * Setting to set the density of the monitor.
- * Type: Integer.
- * Default: -1
- */
- String KEY_MONITOR_DENSITY = "sdkman.monitor.density"; //$NON-NLS-1$
-
- /** Loads settings from the given {@link Properties} container and update the page UI. */
- void loadSettings(Properties inSettings);
-
- /** Called by the application to retrieve settings from the UI and store them in
- * the given {@link Properties} container. */
- void retrieveSettings(Properties outSettings);
-
- /**
- * Called by the application to give a callback that the page should invoke when
- * settings have changed.
- */
- void setOnSettingsChanged(SettingsChangedCallback settingsChangedCallback);
-
- /**
- * Callback used to notify the application that settings have changed and need to be
- * applied.
- */
- interface SettingsChangedCallback {
- /**
- * Invoked by the settings page when settings have changed and need to be
- * applied. The application will call {@link ISettingsPage#retrieveSettings(Properties)}
- * and apply the new settings.
- */
- void onSettingsChanged(ISettingsPage page);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/IUpdaterData.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/IUpdaterData.java
deleted file mode 100755
index b3a74f2..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/IUpdaterData.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.updater;
-
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.avd.AvdManager;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.ITaskFactory;
-import com.android.utils.ILogger;
-
-
-/**
- * Interface used to retrieve some parameters from an {@link UpdaterData} instance.
- * Useful mostly for unit tests purposes.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public interface IUpdaterData {
-
- ITaskFactory getTaskFactory();
-
- ILogger getSdkLog();
-
- DownloadCache getDownloadCache();
-
- SdkManager getSdkManager();
-
- AvdManager getAvdManager();
-
- SettingsController getSettingsController();
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PackageLoader.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PackageLoader.java
deleted file mode 100755
index c0314d4..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PackageLoader.java
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.updater;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.internal.repository.AddonsListFetcher;
-import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.NullTaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
-import com.android.sdklib.internal.repository.sources.SdkAddonSource;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
-import com.android.sdklib.internal.repository.sources.SdkSources;
-import com.android.sdklib.internal.repository.sources.SdkSysImgSource;
-import com.android.sdklib.repository.SdkAddonsListConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Loads packages fetched from the remote SDK Repository and keeps track
- * of their state compared with the current local SDK installation.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class PackageLoader {
-
- /** The update data context. Never null. */
- private final UpdaterData mUpdaterData;
-
- /**
- * The {@link DownloadCache} override. Can be null, in which case the one from
- * {@link UpdaterData} is used instead.
- * @see #getDownloadCache()
- */
- private final DownloadCache mOverrideCache;
-
- /**
- * 0 = need to fetch remote addons list once..
- * 1 = fetch succeeded, don't need to do it any more.
- * -1= fetch failed, do it again only if the user requests a refresh
- * or changes the force-http setting.
- */
- private int mStateFetchRemoteAddonsList;
-
-
- /**
- * Interface for the callback called by
- * {@link PackageLoader#loadPackages(boolean, ISourceLoadedCallback)}.
- * <p/>
- * After processing each source, the package loader calls {@link #onUpdateSource}
- * with the list of packages found in that source.
- * By returning true from {@link #onUpdateSource}, the client tells the loader to
- * continue and process the next source. By returning false, it tells to stop loading.
- * <p/>
- * The {@link #onLoadCompleted()} method is guaranteed to be called at the end, no
- * matter how the loader stopped, so that the client can clean up or perform any
- * final action.
- */
- public interface ISourceLoadedCallback {
- /**
- * After processing each source, the package loader calls this method with the
- * list of packages found in that source.
- * By returning true from {@link #onUpdateSource}, the client tells
- * the loader to continue and process the next source.
- * By returning false, it tells to stop loading.
- * <p/>
- * <em>Important</em>: This method is called from a sub-thread, so clients which
- * try to access any UI widgets must wrap their calls into
- * {@code Display.syncExec(Runnable)} or {@code Display.asyncExec(Runnable)}.
- *
- * @param packages All the packages loaded from the source. Never null.
- * @return True if the load operation should continue, false if it should stop.
- */
- boolean onUpdateSource(SdkSource source, Package[] packages);
-
- /**
- * This method is guaranteed to be called at the end, no matter how the
- * loader stopped, so that the client can clean up or perform any final action.
- */
- void onLoadCompleted();
- }
-
- /**
- * Interface describing the task of installing a specific package.
- * For details on the operation,
- * see {@link PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask)}.
- *
- * @see PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask)
- */
- public interface IAutoInstallTask {
- /**
- * Invoked by the loader once a source has been loaded and its package
- * definitions are known. The method should return the {@code packages}
- * array and can modify it if necessary.
- * The loader will call {@link #acceptPackage(Package)} on all the packages returned.
- *
- * @param source The source of the packages. Null for the locally installed packages.
- * @param packages The packages found in the source.
- */
- Package[] filterLoadedSource(SdkSource source, Package[] packages);
-
- /**
- * Called by the install task for every package available (new ones, updates as well
- * as existing ones that don't have a potential update.)
- * The method should return true if this is a package that should be installed.
- * <p/>
- * <em>Important</em>: This method is called from a sub-thread, so clients who try
- * to access any UI widgets must wrap their calls into {@code Display.syncExec(Runnable)}
- * or {@code Display.asyncExec(Runnable)}.
- */
- boolean acceptPackage(Package pkg);
-
- /**
- * Called when the accepted package has been installed, successfully or not.
- * If an already installed (aka existing) package has been accepted, this will
- * be called with a 'true' success and the actual install paths.
- * <p/>
- * <em>Important</em>: This method is called from a sub-thread, so clients who try
- * to access any UI widgets must wrap their calls into {@code Display.syncExec(Runnable)}
- * or {@code Display.asyncExec(Runnable)}.
- */
- void setResult(boolean success, Map<Package, File> installPaths);
-
- /**
- * Called when the task is done iterating and completed.
- */
- void taskCompleted();
- }
-
- /**
- * Creates a new PackageManager associated with the given {@link UpdaterData}
- * and using the {@link UpdaterData}'s default {@link DownloadCache}.
- *
- * @param updaterData The {@link UpdaterData}. Must not be null.
- */
- public PackageLoader(UpdaterData updaterData) {
- mUpdaterData = updaterData;
- mOverrideCache = null;
- }
-
- /**
- * Creates a new PackageManager associated with the given {@link UpdaterData}
- * but using the specified {@link DownloadCache} instead of the one from
- * {@link UpdaterData}.
- *
- * @param updaterData The {@link UpdaterData}. Must not be null.
- * @param cache The {@link DownloadCache} to use instead of the one from {@link UpdaterData}.
- */
- public PackageLoader(UpdaterData updaterData, DownloadCache cache) {
- mUpdaterData = updaterData;
- mOverrideCache = cache;
- }
-
- public UpdaterData getUpdaterData() {
- return mUpdaterData;
- }
-
- /**
- * Runs a runnable on the UI thread.
- * The base implementation just runs the runnable right away.
- *
- * @param r Non-null runnable.
- */
- protected void runOnUiThread(@NonNull Runnable r) {
- r.run();
- }
-
- /**
- * Loads all packages from the remote repository.
- * This runs in an {@link ITask}. The call is blocking.
- * <p/>
- * The callback is called with each set of {@link PkgItem} found in each source.
- * The caller is responsible to accumulate the packages given to the callback
- * after each source is finished loaded. In return the callback tells the loader
- * whether to continue loading sources.
- * <p/>
- * Normally this method doesn't access the remote source if it's already
- * been loaded in the in-memory source (e.g. don't fetch twice).
- *
- * @param overrideExisting Set this to true when the caller wants to
- * check for updates and discard any existing source already
- * loaded in memory. It should be false for normal use.
- * @param sourceLoadedCallback The callback to invoke for each loaded source.
- */
- public void loadPackages(
- final boolean overrideExisting,
- final ISourceLoadedCallback sourceLoadedCallback) {
- try {
- if (mUpdaterData == null) {
- return;
- }
-
- mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() {
- @Override
- public void run(ITaskMonitor monitor) {
- monitor.setProgressMax(10);
-
- // get local packages and offer them to the callback
- Package[] localPkgs =
- mUpdaterData.getInstalledPackages(monitor.createSubMonitor(1));
- if (localPkgs == null) {
- localPkgs = new Package[0];
- }
- if (!sourceLoadedCallback.onUpdateSource(null, localPkgs)) {
- return;
- }
-
- // get remote packages
- boolean forceHttp =
- mUpdaterData.getSettingsController().getSettings().getForceHttp();
- loadRemoteAddonsList(monitor.createSubMonitor(1));
-
- SdkSource[] sources = mUpdaterData.getSources().getAllSources();
- try {
- if (sources != null && sources.length > 0) {
- ITaskMonitor subMonitor = monitor.createSubMonitor(8);
- subMonitor.setProgressMax(sources.length);
- for (SdkSource source : sources) {
- Package[] pkgs = source.getPackages();
- if (pkgs == null || overrideExisting) {
- source.load(getDownloadCache(),
- subMonitor.createSubMonitor(1),
- forceHttp);
- pkgs = source.getPackages();
- }
- if (pkgs == null) {
- continue;
- }
-
- // Notify the callback a new source has finished loading.
- // If the callback requests so, stop right away.
- if (!sourceLoadedCallback.onUpdateSource(source, pkgs)) {
- return;
- }
- }
- }
- } catch(Exception e) {
- monitor.logError("Loading source failed: %1$s", e.toString());
- } finally {
- monitor.setDescription("Done loading packages.");
- }
- }
- });
- } finally {
- sourceLoadedCallback.onLoadCompleted();
- }
- }
-
- /**
- * Load packages, source by source using
- * {@link #loadPackages(boolean, ISourceLoadedCallback)},
- * and executes the given {@link IAutoInstallTask} on the current package list.
- * That is for each package known, the install task is queried to find if
- * the package is the one to be installed or updated.
- * <p/>
- * - If an already installed package is accepted by the task, it is returned. <br/>
- * - If a new package (remotely available but not installed locally) is accepted,
- * the user will be <em>prompted</em> for permission to install it. <br/>
- * - If an existing package has updates, the install task will be accept if it
- * accepts one of the updating packages, and if yes the the user will be
- * <em>prompted</em> for permission to install it. <br/>
- * <p/>
- * Only one package can be accepted, after which the task is completed.
- * There is no direct return value, {@link IAutoInstallTask#setResult} is called on the
- * result of the accepted package.
- * When the task is completed, {@link IAutoInstallTask#taskCompleted()} is called.
- * <p/>
- * The call is blocking. Although the name says "Task", this is not an {@link ITask}
- * running in its own thread but merely a synchronous call.
- *
- * @param installFlags Flags for installation such as
- * {@link UpdaterData#TOOLS_MSG_UPDATED_FROM_ADT}.
- * @param installTask The task to perform.
- */
- public void loadPackagesWithInstallTask(
- final int installFlags,
- final IAutoInstallTask installTask) {
-
- loadPackages(false /*overrideExisting*/, new ISourceLoadedCallback() {
- List<Archive> mArchivesToInstall = new ArrayList<Archive>();
- Map<Package, File> mInstallPaths = new HashMap<Package, File>();
-
- @Override
- public boolean onUpdateSource(SdkSource source, Package[] packages) {
- packages = installTask.filterLoadedSource(source, packages);
- if (packages == null || packages.length == 0) {
- // Tell loadPackages() to process the next source.
- return true;
- }
-
- for (Package pkg : packages) {
- if (pkg.isLocal()) {
- // This is a local (aka installed) package
- if (installTask.acceptPackage(pkg)) {
- // If the caller is accepting an installed package,
- // return a success and give the package's install path
- Archive[] a = pkg.getArchives();
- // an installed package should have one local compatible archive
- if (a.length == 1 && a[0].isCompatible()) {
- mInstallPaths.put(pkg, new File(a[0].getLocalOsPath()));
- }
- }
-
- } else {
- // This is a remote package
- if (installTask.acceptPackage(pkg)) {
- // The caller is accepting this remote package. We'll install it.
- for (Archive archive : pkg.getArchives()) {
- if (archive.isCompatible()) {
- mArchivesToInstall.add(archive);
- break;
- }
- }
- }
- }
- }
-
- // Tell loadPackages() to process the next source.
- return true;
- }
-
- @Override
- public void onLoadCompleted() {
- if (!mArchivesToInstall.isEmpty()) {
- installArchives(mArchivesToInstall);
- }
- if (mInstallPaths == null) {
- installTask.setResult(false, null);
- } else {
- installTask.setResult(true, mInstallPaths);
- }
-
- installTask.taskCompleted();
- }
-
- /**
- * Shows the UI of the install selector.
- * If the package is then actually installed, refresh the local list and
- * notify the install task of the installation path.
- *
- * @param archivesToInstall The archives to install.
- */
- private void installArchives(final List<Archive> archivesToInstall) {
- // Actually install the new archives that we just found.
- // This will display some UI so we need a shell's sync exec.
-
- final List<Archive> installedArchives = new ArrayList<Archive>();
-
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- List<Archive> archives =
- mUpdaterData.updateOrInstallAll_WithGUI(
- archivesToInstall,
- true /* includeObsoletes */,
- installFlags);
-
- if (archives != null) {
- installedArchives.addAll(archives);
- }
- }
- });
-
- if (installedArchives.isEmpty()) {
- // We failed to install anything.
- mInstallPaths = null;
- return;
- }
-
- // The local package list has changed, make sure to refresh it
- mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog());
- mUpdaterData.getLocalSdkParser().clearPackages();
- final Package[] localPkgs = mUpdaterData.getInstalledPackages(
- new NullTaskMonitor(mUpdaterData.getSdkLog()));
-
- // Scan the installed package list to find the install paths.
- for (Archive installedArchive : installedArchives) {
- Package pkg = installedArchive.getParentPackage();
-
- for (Package localPkg : localPkgs) {
- if (localPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
- Archive[] localArchive = localPkg.getArchives();
- if (localArchive.length == 1 && localArchive[0].isCompatible()) {
- mInstallPaths.put(
- localPkg,
- new File(localArchive[0].getLocalOsPath()));
- }
- }
- }
- }
- }
- });
- }
-
-
- /**
- * Loads the remote add-ons list.
- */
- public void loadRemoteAddonsList(ITaskMonitor monitor) {
-
- if (mStateFetchRemoteAddonsList != 0) {
- return;
- }
-
- mUpdaterData.getTaskFactory().start("Load Add-ons List", monitor, new ITask() {
- @Override
- public void run(ITaskMonitor subMonitor) {
- loadRemoteAddonsListInTask(subMonitor);
- }
- });
- }
-
- private void loadRemoteAddonsListInTask(ITaskMonitor monitor) {
- mStateFetchRemoteAddonsList = -1;
-
- String url = SdkAddonsListConstants.URL_ADDON_LIST;
-
- // We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined
- String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
- if (baseUrl != null) {
- if (!baseUrl.isEmpty() && baseUrl.endsWith("/")) { //$NON-NLS-1$
- if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
- url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
- }
- } else {
- monitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$
- }
- }
-
- if (mUpdaterData.getSettingsController().getSettings().getForceHttp()) {
- url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- // Hook to bypass loading 3rd party addons lists.
- boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null;
-
- AddonsListFetcher fetcher = new AddonsListFetcher();
- Site[] sites = fetcher.fetch(url, getDownloadCache(), monitor);
- if (sites != null) {
- SdkSources sources = mUpdaterData.getSources();
- sources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
-
- if (fetch3rdParties) {
- for (Site s : sites) {
- switch (s.getType()) {
- case ADDON_SITE:
- sources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
- new SdkAddonSource(s.getUrl(), s.getUiName()));
- break;
- case SYS_IMG_SITE:
- sources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
- new SdkSysImgSource(s.getUrl(), s.getUiName()));
- break;
- }
- }
- }
-
- sources.notifyChangeListeners();
-
- mStateFetchRemoteAddonsList = 1;
- }
-
- monitor.setDescription("Fetched Add-ons List successfully");
- }
-
- /**
- * Returns the {@link DownloadCache} to use.
- *
- * @return Returns {@link #mOverrideCache} if not null; otherwise returns the
- * one from {@link UpdaterData} is used instead.
- */
- private DownloadCache getDownloadCache() {
- return mOverrideCache != null ? mOverrideCache : mUpdaterData.getDownloadCache();
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PkgItem.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PkgItem.java
deleted file mode 100755
index 60f7aa0..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/PkgItem.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.updater;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-
-/**
- * A {@link PkgItem} represents one main {@link Package} combined with its state
- * and an optional update package.
- * <p/>
- * The main package is final and cannot change since it's what "defines" this PkgItem.
- * The state or update package can change later.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class PkgItem implements Comparable<PkgItem> {
- private final PkgState mState;
- private final Package mMainPkg;
- private Package mUpdatePkg;
- private boolean mChecked;
-
- /**
- * The state of the a given {@link PkgItem}, that is the relationship between
- * a given remote package and the local repository.
- */
- public enum PkgState {
- // Implementation detail: if this is changed then PackageDiffLogic#STATES
- // and PackageDiffLogic#processSource() need to be changed accordingly.
-
- /**
- * Package is locally installed and may or may not have an update.
- */
- INSTALLED,
-
- /**
- * There's a new package available on the remote site that isn't installed locally.
- */
- NEW
- }
-
- /**
- * Create a new {@link PkgItem} for this main package.
- * The main package is final and cannot change since it's what "defines" this PkgItem.
- * The state or update package can change later.
- */
- public PkgItem(Package mainPkg, PkgState state) {
- mMainPkg = mainPkg;
- mState = state;
- assert mMainPkg != null;
- }
-
- public boolean isObsolete() {
- return mMainPkg.isObsolete();
- }
-
- public boolean isChecked() {
- return mChecked;
- }
-
- public void setChecked(boolean checked) {
- mChecked = checked;
- }
-
- public Package getUpdatePkg() {
- return mUpdatePkg;
- }
-
- public boolean hasUpdatePkg() {
- return mUpdatePkg != null;
- }
-
- public String getName() {
- return mMainPkg.getListDescription();
- }
-
- public FullRevision getRevision() {
- return mMainPkg.getRevision();
- }
-
- /**
- * @deprecated Use {@link #getMainPackage()} with the {@link IDescription} interface instead.
- */
- @Deprecated
- public String getDescription() {
- return mMainPkg.getLongDescription();
- }
-
- public Package getMainPackage() {
- return mMainPkg;
- }
-
- public PkgState getState() {
- return mState;
- }
-
- public SdkSource getSource() {
- return mMainPkg.getParentSource();
- }
-
- @Nullable
- public AndroidVersion getAndroidVersion() {
- return mMainPkg instanceof IAndroidVersionProvider ?
- ((IAndroidVersionProvider) mMainPkg).getAndroidVersion() :
- null;
- }
-
- public Archive[] getArchives() {
- return mMainPkg.getArchives();
- }
-
- @Override
- public int compareTo(PkgItem pkg) {
- return getMainPackage().compareTo(pkg.getMainPackage());
- }
-
- /**
- * Returns true if this package or its updating packages contains
- * the exact given archive.
- * Important: This compares object references, not object equality.
- */
- public boolean hasArchive(Archive archive) {
- if (mMainPkg.hasArchive(archive)) {
- return true;
- }
- if (mUpdatePkg != null && mUpdatePkg.hasArchive(archive)) {
- return true;
- }
- return false;
- }
-
- /**
- * Returns true if the main package has at least one archive
- * compatible with the current platform.
- */
- public boolean hasCompatibleArchive() {
- return mMainPkg.hasCompatibleArchive();
- }
-
- /**
- * Checks whether the main packages are of the same type and are
- * not an update of each other and have the same revision number.
- */
- public boolean isSameMainPackageAs(Package pkg) {
- if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
- // package revision numbers must match
- return mMainPkg.getRevision().equals(pkg.getRevision());
- }
- return false;
- }
-
- /**
- * Checks whether the update packages are of the same type and are
- * not an update of each other and have the same revision numbers.
- */
- public boolean isSameUpdatePackageAs(Package pkg) {
- if (mUpdatePkg != null && mUpdatePkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
- // package revision numbers must match
- return mUpdatePkg.getRevision().equals(pkg.getRevision());
- }
- return false;
- }
-
- /**
- * Checks whether too {@link PkgItem} are the same.
- * This checks both items have the same state, both main package are similar
- * and that they have the same updating packages.
- */
- public boolean isSameItemAs(PkgItem item) {
- if (this == item) {
- return true;
- }
- boolean same = this.mState == item.mState;
- if (same) {
- same = isSameMainPackageAs(item.getMainPackage());
- }
-
- if (same) {
- // check updating packages are the same
- Package p1 = this.mUpdatePkg;
- Package p2 = item.getUpdatePkg();
- same = (p1 == p2) || (p1 == null && p2 == null) || (p1 != null && p2 != null);
-
- if (same && p1 != null) {
- same = p1.canBeUpdatedBy(p2) == UpdateInfo.NOT_UPDATE;
- }
- }
-
- return same;
- }
-
- /**
- * Equality is defined as {@link #isSameItemAs(PkgItem)}: state, main package
- * and update package must be the similar.
- */
- @Override
- public boolean equals(Object obj) {
- return (obj instanceof PkgItem) && this.isSameItemAs((PkgItem) obj);
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mState == null) ? 0 : mState.hashCode());
- result = prime * result + ((mMainPkg == null) ? 0 : mMainPkg.hashCode());
- result = prime * result + ((mUpdatePkg == null) ? 0 : mUpdatePkg.hashCode());
- return result;
- }
-
- /**
- * Check whether the 'pkg' argument is an update for this package.
- * If it is, record it as an updating package.
- * If there's already an updating package, only keep the most recent update.
- * Returns true if it is update (even if there was already an update and this
- * ended up not being the most recent), false if incompatible or not an update.
- *
- * This should only be used for installed packages.
- */
- public boolean mergeUpdate(Package pkg) {
- if (mUpdatePkg == pkg) {
- return true;
- }
- if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
- if (mUpdatePkg == null) {
- mUpdatePkg = pkg;
- } else if (mUpdatePkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
- // If we have more than one, keep only the most recent update
- mUpdatePkg = pkg;
- }
- return true;
- }
-
- return false;
- }
-
- public void removeUpdate() {
- mUpdatePkg = null;
- }
-
- /** Returns a string representation of this item, useful when debugging. */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append('<');
-
- if (mChecked) {
- sb.append(" * "); //$NON-NLS-1$
- }
-
- sb.append(mState.toString());
-
- if (mMainPkg != null) {
- sb.append(", pkg:"); //$NON-NLS-1$
- sb.append(mMainPkg.toString());
- }
-
- if (mUpdatePkg != null) {
- sb.append(", updated by:"); //$NON-NLS-1$
- sb.append(mUpdatePkg.toString());
- }
-
- sb.append('>');
- return sb.toString();
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterLogic.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterLogic.java
deleted file mode 100755
index ff2000f..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterLogic.java
+++ /dev/null
@@ -1,1568 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.updater;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.internal.repository.packages.BuildToolPackage;
-import com.android.sdklib.internal.repository.packages.DocPackage;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
-import com.android.sdklib.internal.repository.packages.IExactApiLevelDependency;
-import com.android.sdklib.internal.repository.packages.IMinApiLevelDependency;
-import com.android.sdklib.internal.repository.packages.IMinPlatformToolsDependency;
-import com.android.sdklib.internal.repository.packages.IMinToolsDependency;
-import com.android.sdklib.internal.repository.packages.IPlatformDependency;
-import com.android.sdklib.internal.repository.packages.MinToolsPackage;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
-import com.android.sdklib.internal.repository.packages.PlatformPackage;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.SamplePackage;
-import com.android.sdklib.internal.repository.packages.SystemImagePackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.internal.repository.sources.SdkSources;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * The logic to compute which packages to install, based on the choices
- * made by the user. This adds required packages as needed.
- * <p/>
- * When the user doesn't provide a selection, looks at local package to find
- * those that can be updated and compute dependencies too.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkUpdaterLogic {
-
- private final IUpdaterData mUpdaterData;
-
- public SdkUpdaterLogic(IUpdaterData updaterData) {
- mUpdaterData = updaterData;
- }
-
- /**
- * Retrieves an unfiltered list of all remote archives.
- * The archives are guaranteed to be compatible with the current platform.
- */
- public List<ArchiveInfo> getAllRemoteArchives(
- SdkSources sources,
- Package[] localPkgs,
- boolean includeAll) {
-
- List<Package> remotePkgs = new ArrayList<Package>();
- SdkSource[] remoteSources = sources.getAllSources();
- fetchRemotePackages(remotePkgs, remoteSources);
-
- ArrayList<Archive> archives = new ArrayList<Archive>();
- for (Package remotePkg : remotePkgs) {
- // Only look for non-obsolete updates unless requested to include them
- if (includeAll || !remotePkg.isObsolete()) {
- // Found a suitable update. Only accept the remote package
- // if it provides at least one compatible archive
-
- addArchives:
- for (Archive a : remotePkg.getArchives()) {
- if (a.isCompatible()) {
-
- // If we're trying to add a package for revision N,
- // make sure we don't also have a package for revision N-1.
- for (int i = archives.size() - 1; i >= 0; i--) {
- Package pkgFound = archives.get(i).getParentPackage();
- if (pkgFound.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
- // This package can update one we selected earlier.
- // Remove the one that can be updated by this new one.
- archives.remove(i);
- } else if (remotePkg.canBeUpdatedBy(pkgFound) == UpdateInfo.UPDATE) {
- // There is a package in the list that is already better
- // than the one we want to add, so don't add it.
- break addArchives;
- }
- }
-
- archives.add(a);
- break;
- }
- }
- }
- }
-
- ArrayList<ArchiveInfo> result = new ArrayList<ArchiveInfo>();
-
- ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
-
- for (Archive a : archives) {
- insertArchive(a,
- result,
- archives,
- remotePkgs,
- remoteSources,
- localArchives,
- false /*automated*/);
- }
-
- return result;
- }
-
- /**
- * Compute which packages to install by taking the user selection
- * and adding required packages as needed.
- *
- * When the user doesn't provide a selection, looks at local packages to find
- * those that can be updated and compute dependencies too.
- */
- public List<ArchiveInfo> computeUpdates(
- Collection<Archive> selectedArchives,
- SdkSources sources,
- Package[] localPkgs,
- boolean includeAll) {
-
- List<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
- List<Package> remotePkgs = new ArrayList<Package>();
- SdkSource[] remoteSources = sources.getAllSources();
-
- // Create ArchiveInfos out of local (installed) packages.
- ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
-
- // If we do not have a specific list of archives to install (that is the user
- // selected "update all" rather than request specific packages), then we try to
- // find updates based on the *existing* packages.
- if (selectedArchives == null) {
- selectedArchives = findUpdates(
- localArchives,
- remotePkgs,
- remoteSources,
- includeAll);
- }
-
- // Once we have a list of packages to install, we try to solve all their
- // dependencies by automatically adding them to the list of things to install.
- // This works on the list provided either by the user directly or the list
- // computed from potential updates.
- for (Archive a : selectedArchives) {
- insertArchive(a,
- archives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- false /*automated*/);
- }
-
- // Finally we need to look at *existing* packages which are not being updated
- // and check if they have any missing dependencies and suggest how to fix
- // these dependencies.
- fixMissingLocalDependencies(
- archives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- return archives;
- }
-
- private double getRevisionRank(FullRevision rev) {
- int p = rev.isPreview() ? 999 : 999 - rev.getPreview();
- return rev.getMajor() +
- rev.getMinor() / 1000.d +
- rev.getMicro() / 1000000.d +
- p / 1000000000.d;
- }
-
- /**
- * Finds new packages that the user does not have in his/her local SDK
- * and adds them to the list of archives to install.
- * <p/>
- * The default is to only find "new" platforms, that is anything more
- * recent than the highest platform currently installed.
- * A side effect is that for an empty SDK install this will list *all*
- * platforms available (since there's no "highest" installed platform.)
- * <p/>
- * This also adds "silent" dependencies. For example the user probably
- * needs to have at least one version of the build-tools package although
- * there is nothing that directly depends on it. So if the user doesn't have
- * any installed or selected version, selected the most recent one as a
- * candidate for install.
- *
- * @param archives The in-out list of archives to install. Typically the
- * list is not empty at first as it should contain any archives that is
- * already scheduled for install. This method will add to the list.
- * @param sources The list of all sources, to fetch them as necessary.
- * @param localPkgs The list of all currently installed packages.
- * @param includeAll When true, this will list all platforms.
- * (included these lower than the highest installed one) as well as
- * all obsolete packages of these platforms.
- */
- public void addNewPlatforms(
- Collection<ArchiveInfo> archives,
- SdkSources sources,
- Package[] localPkgs,
- boolean includeAll) {
-
- // Create ArchiveInfos out of local (installed) packages.
- ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
-
- // Find the highest platform installed
- double currentBuildToolScore = 0;
- double currentPlatformScore = 0;
- double currentSampleScore = 0;
- double currentAddonScore = 0;
- double currentDocScore = 0;
- HashMap<String, Double> currentExtraScore = new HashMap<String, Double>();
- if (!includeAll) {
- if (localPkgs != null) {
- for (Package p : localPkgs) {
- double rev = getRevisionRank(p.getRevision());
- int api = 0;
- boolean isPreview = false;
- if (p instanceof IAndroidVersionProvider) {
- AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();
- api = vers.getApiLevel();
- isPreview = vers.isPreview();
- }
-
- // The score is 1000*api + (999 if preview) + rev
- // This allows previews to rank above a non-preview and
- // allows revisions to rank appropriately.
- double score = api * 1000 + (isPreview ? 999 : 0) + rev;
-
- if (p instanceof BuildToolPackage) {
- currentBuildToolScore = Math.max(currentBuildToolScore, score);
- } else if (p instanceof PlatformPackage) {
- currentPlatformScore = Math.max(currentPlatformScore, score);
- } else if (p instanceof SamplePackage) {
- currentSampleScore = Math.max(currentSampleScore, score);
- } else if (p instanceof AddonPackage) {
- currentAddonScore = Math.max(currentAddonScore, score);
- } else if (p instanceof ExtraPackage) {
- currentExtraScore.put(((ExtraPackage) p).getPath(), score);
- } else if (p instanceof DocPackage) {
- currentDocScore = Math.max(currentDocScore, score);
- }
- }
- }
- }
-
- SdkSource[] remoteSources = sources.getAllSources();
- ArrayList<Package> remotePkgs = new ArrayList<Package>();
- fetchRemotePackages(remotePkgs, remoteSources);
-
- Package suggestedDoc = null;
- Package suggestedBuildTool = null;
-
- for (Package p : remotePkgs) {
- // Skip obsolete packages unless requested to include them.
- if (p.isObsolete() && !includeAll) {
- continue;
- }
-
- double rev = getRevisionRank(p.getRevision());
- int api = 0;
- boolean isPreview = false;
- if (p instanceof IAndroidVersionProvider) {
- AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();
- api = vers.getApiLevel();
- isPreview = vers.isPreview();
- }
-
- double score = api * 1000 + (isPreview ? 999 : 0) + rev;
-
- boolean shouldAdd = false;
- if (p instanceof BuildToolPackage) {
- // We don't want all the build-tool packages, only the most recent one
- // if not is currently installed.
- if (currentBuildToolScore == 0 && score > currentBuildToolScore) {
- suggestedBuildTool = p;
- currentBuildToolScore = score;
- }
- } else if (p instanceof PlatformPackage) {
- shouldAdd = score > currentPlatformScore;
- } else if (p instanceof SamplePackage) {
- shouldAdd = score > currentSampleScore;
- } else if (p instanceof AddonPackage) {
- shouldAdd = score > currentAddonScore;
- } else if (p instanceof ExtraPackage) {
- String key = ((ExtraPackage) p).getPath();
- shouldAdd = !currentExtraScore.containsKey(key) ||
- score > currentExtraScore.get(key).doubleValue();
- } else if (p instanceof DocPackage) {
- // We don't want all the doc, only the most recent one
- if (score > currentDocScore) {
- suggestedDoc = p;
- currentDocScore = score;
- }
- }
-
- if (shouldAdd) {
- // We should suggest this package for installation.
- for (Archive a : p.getArchives()) {
- if (a.isCompatible()) {
- insertArchive(a,
- archives,
- null /*selectedArchives*/,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
-
- if (p instanceof PlatformPackage && (score >= currentPlatformScore)) {
- // We just added a new platform *or* we are visiting the highest currently
- // installed platform. In either case we want to make sure it either has
- // its own system image or that we provide one by default.
- PlatformPackage pp = (PlatformPackage) p;
- if (pp.getIncludedAbi() == null) {
- for (Package p2 : remotePkgs) {
- if (!(p2 instanceof SystemImagePackage) ||
- ((SystemImagePackage)p2).isPlatform() ||
- (p2.isObsolete() && !includeAll)) {
- continue;
- }
- SystemImagePackage sip = (SystemImagePackage) p2;
- if (sip.getAndroidVersion().equals(pp.getAndroidVersion())) {
- for (Archive a : sip.getArchives()) {
- if (a.isCompatible()) {
- insertArchive(a,
- archives,
- null /*selectedArchives*/,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
- }
- }
- }
- }
-
- if (suggestedDoc != null) {
- // We should suggest this package for installation.
- for (Archive a : suggestedDoc.getArchives()) {
- if (a.isCompatible()) {
- insertArchive(a,
- archives,
- null /*selectedArchives*/,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
-
- if (suggestedBuildTool != null) {
- // We should suggest this package for installation.
- for (Archive a : suggestedBuildTool.getArchives()) {
- if (a.isCompatible()) {
- insertArchive(a,
- archives,
- null /*selectedArchives*/,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
- }
-
- /**
- * Create a array of {@link ArchiveInfo} based on all local (already installed)
- * packages. The array is always non-null but may be empty.
- * <p/>
- * The local {@link ArchiveInfo} are guaranteed to have one non-null archive
- * that you can retrieve using {@link ArchiveInfo#getNewArchive()}.
- */
- public ArchiveInfo[] createLocalArchives(Package[] localPkgs) {
-
- if (localPkgs != null) {
- ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();
- for (Package p : localPkgs) {
- // Only accept packages that have one compatible archive.
- // Local package should have 1 and only 1 compatible archive anyway.
- for (Archive a : p.getArchives()) {
- if (a != null && a.isCompatible()) {
- // We create an "installed" archive info to wrap the local package.
- // Note that dependencies are not computed since right now we don't
- // deal with more than one level of dependencies and installed archives
- // are deemed implicitly accepted anyway.
- list.add(new LocalArchiveInfo(a));
- }
- }
- }
-
- return list.toArray(new ArchiveInfo[list.size()]);
- }
-
- return new ArchiveInfo[0];
- }
-
- /**
- * Find suitable updates to all current local packages.
- * <p/>
- * Returns a list of potential updates for *existing* packages. This does NOT solve
- * dependencies for the new packages.
- * <p/>
- * Always returns a non-null collection, which can be empty.
- */
- private Collection<Archive> findUpdates(
- ArchiveInfo[] localArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- boolean includeAll) {
- ArrayList<Archive> updates = new ArrayList<Archive>();
-
- fetchRemotePackages(remotePkgs, remoteSources);
-
- for (ArchiveInfo ai : localArchives) {
- Archive na = ai.getNewArchive();
- if (na == null) {
- continue;
- }
- Package localPkg = na.getParentPackage();
-
- for (Package remotePkg : remotePkgs) {
- // Only look for non-obsolete updates unless requested to include them
- if ((includeAll || !remotePkg.isObsolete()) &&
- localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
- // Found a suitable update. Only accept the remote package
- // if it provides at least one compatible archive
-
- addArchives:
- for (Archive a : remotePkg.getArchives()) {
- if (a.isCompatible()) {
-
- // If we're trying to add a package for revision N,
- // make sure we don't also have a package for revision N-1.
- for (int i = updates.size() - 1; i >= 0; i--) {
- Package pkgFound = updates.get(i).getParentPackage();
- if (pkgFound.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
- // This package can update one we selected earlier.
- // Remove the one that can be updated by this new one.
- updates.remove(i);
- } else if (remotePkg.canBeUpdatedBy(pkgFound) ==
- UpdateInfo.UPDATE) {
- // There is a package in the list that is already better
- // than the one we want to add, so don't add it.
- break addArchives;
- }
- }
-
- updates.add(a);
- break;
- }
- }
- }
- }
- }
-
- return updates;
- }
-
- /**
- * Check all local archives which are NOT being updated and see if they
- * miss any dependency. If they do, try to fix that dependency by selecting
- * an appropriate package.
- */
- private void fixMissingLocalDependencies(
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives) {
-
- nextLocalArchive: for (ArchiveInfo ai : localArchives) {
- Archive a = ai.getNewArchive();
- Package p = a == null ? null : a.getParentPackage();
- if (p == null) {
- continue;
- }
-
- // Is this local archive being updated?
- for (ArchiveInfo ai2 : outArchives) {
- if (ai2.getReplaced() == a) {
- // this new archive will replace the current local one,
- // so we don't have to care about fixing dependencies (since the
- // new archive should already have had its dependencies resolved)
- continue nextLocalArchive;
- }
- }
-
- // find dependencies for the local archive and add them as needed
- // to the outArchives collection.
- ArchiveInfo[] deps = findDependency(p,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- if (deps != null) {
- // The already installed archive has a missing dependency, which we
- // just selected for install. Make sure we remember the dependency
- // so that we can enforce it later in the UI.
- for (ArchiveInfo aid : deps) {
- aid.addDependencyFor(ai);
- }
- }
- }
- }
-
- private ArchiveInfo insertArchive(Archive archive,
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives,
- boolean automated) {
- Package p = archive.getParentPackage();
-
- // Is this an update?
- Archive updatedArchive = null;
- for (ArchiveInfo ai : localArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package lp = a.getParentPackage();
-
- if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
- updatedArchive = a;
- }
- }
- }
-
- // Find dependencies and adds them as needed to outArchives
- ArchiveInfo[] deps = findDependency(p,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- // Make sure it's not a dup
- ArchiveInfo ai = null;
-
- for (ArchiveInfo ai2 : outArchives) {
- Archive a2 = ai2.getNewArchive();
- if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) {
- ai = ai2;
- break;
- }
- }
-
- if (ai == null) {
- ai = new ArchiveInfo(
- archive, //newArchive
- updatedArchive, //replaced
- deps //dependsOn
- );
- outArchives.add(ai);
- }
-
- if (deps != null) {
- for (ArchiveInfo d : deps) {
- d.addDependencyFor(ai);
- }
- }
-
- return ai;
- }
-
- /**
- * Resolves dependencies for a given package.
- *
- * Returns null if no dependencies were found.
- * Otherwise return an array of {@link ArchiveInfo}, which is guaranteed to have
- * at least size 1 and contain no null elements.
- */
- private ArchiveInfo[] findDependency(Package pkg,
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives) {
-
- // Current dependencies can be:
- // - addon: *always* depends on platform of same API level
- // - platform: *might* depends on tools of rev >= min-tools-rev
- // - extra: *might* depends on platform with api >= min-api-level
-
- Set<ArchiveInfo> aiFound = new HashSet<ArchiveInfo>();
-
- if (pkg instanceof IPlatformDependency) {
- ArchiveInfo ai = findPlatformDependency(
- (IPlatformDependency) pkg,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- if (ai != null) {
- aiFound.add(ai);
- }
- }
-
- if (pkg instanceof IMinToolsDependency) {
-
- ArchiveInfo ai = findToolsDependency(
- (IMinToolsDependency) pkg,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- if (ai != null) {
- aiFound.add(ai);
- }
- }
-
- if (pkg instanceof IMinPlatformToolsDependency) {
-
- ArchiveInfo ai = findPlatformToolsDependency(
- (IMinPlatformToolsDependency) pkg,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- if (ai != null) {
- aiFound.add(ai);
- }
- }
-
- if (pkg instanceof IMinApiLevelDependency) {
-
- ArchiveInfo ai = findMinApiLevelDependency(
- (IMinApiLevelDependency) pkg,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- if (ai != null) {
- aiFound.add(ai);
- }
- }
-
- if (pkg instanceof IExactApiLevelDependency) {
-
- ArchiveInfo ai = findExactApiLevelDependency(
- (IExactApiLevelDependency) pkg,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives);
-
- if (ai != null) {
- aiFound.add(ai);
- }
- }
-
- if (!aiFound.isEmpty()) {
- ArchiveInfo[] result = aiFound.toArray(new ArchiveInfo[aiFound.size()]);
- Arrays.sort(result);
- return result;
- }
-
- return null;
- }
-
- /**
- * Resolves dependencies on tools.
- *
- * A platform or an extra package can both have a min-tools-rev, in which case it
- * depends on having a tools package of the requested revision.
- * Finds the tools dependency. If found, add it to the list of things to install.
- * Returns the archive info dependency, if any.
- */
- public ArchiveInfo findToolsDependency(
- IMinToolsDependency pkg,
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives) {
- // This is the requirement to match.
- FullRevision rev = pkg.getMinToolsRevision();
-
- if (rev.equals(MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED)) {
- // Well actually there's no requirement.
- return null;
- }
-
- // First look in locally installed packages.
- for (ArchiveInfo ai : localArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
- // We found one already installed.
- return null;
- }
- }
- }
- }
-
- // Look in archives already scheduled for install
- for (ArchiveInfo ai : outArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
- // The dependency is already scheduled for install, nothing else to do.
- return ai;
- }
- }
- }
- }
-
- // Otherwise look in the selected archives.
- if (selectedArchives != null) {
- for (Archive a : selectedArchives) {
- Package p = a.getParentPackage();
- if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
- // It's not already in the list of things to install, so add it now
- return insertArchive(a,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
- }
-
- // Finally nothing matched, so let's look at all available remote packages
- fetchRemotePackages(remotePkgs, remoteSources);
- FullRevision localRev = rev;
- Archive localArch = null;
- for (Package p : remotePkgs) {
- if (p instanceof ToolPackage) {
- FullRevision r = ((ToolPackage) p).getRevision();
- if (r.compareTo(localRev) >= 0) {
- // It's not already in the list of things to install, so add the
- // first compatible archive we can find.
- for (Archive a : p.getArchives()) {
- if (a.isCompatible()) {
- localRev = r;
- localArch = a;
- break;
- }
- }
- }
- }
- }
- if (localArch != null) {
- return insertArchive(localArch,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
-
- }
-
- // We end up here if nothing matches. We don't have a good platform to match.
- // We need to indicate this extra depends on a missing platform archive
- // so that it can be impossible to install later on.
- return new MissingArchiveInfo(MissingArchiveInfo.TITLE_TOOL, rev);
- }
-
- /**
- * Resolves dependencies on platform-tools.
- *
- * A tool package can have a min-platform-tools-rev, in which case it depends on
- * having a platform-tool package of the requested revision.
- * Finds the platform-tool dependency. If found, add it to the list of things to install.
- * Returns the archive info dependency, if any.
- */
- public ArchiveInfo findPlatformToolsDependency(
- IMinPlatformToolsDependency pkg,
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives) {
- // This is the requirement to match.
- FullRevision rev = pkg.getMinPlatformToolsRevision();
- boolean findMax = false;
- int compareThreshold = 0;
- ArchiveInfo aiMax = null;
- Archive aMax = null;
-
- if (rev.equals(IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID)) {
- // The requirement is invalid, which is not supposed to happen since this
- // property is mandatory. However in a typical upgrade scenario we can end
- // up with the previous updater managing a new package and not dealing
- // correctly with the new unknown property.
- // So instead we parse all the existing and remote packages and try to find
- // the max available revision and we'll use it.
- findMax = true;
- // When findMax is false, we want r.compareTo(rev) >= 0.
- // When findMax is true, we want r.compareTo(rev) > 0 (so >= 1).
- compareThreshold = 1;
- }
-
- // First look in locally installed packages.
- for (ArchiveInfo ai : localArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformToolPackage) {
- FullRevision r = ((PlatformToolPackage) p).getRevision();
- if (findMax && r.compareTo(rev) > compareThreshold) {
- rev = r;
- aiMax = ai;
- } else if (!findMax && r.compareTo(rev) >= compareThreshold) {
- // We found one already installed.
- return null;
- }
- }
- }
- }
-
- // Because of previews, we can have more than 1 choice, so get the local max.
- FullRevision localRev = rev;
- ArchiveInfo localAiMax = null;
- // Look in archives already scheduled for install
- for (ArchiveInfo ai : outArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformToolPackage) {
- FullRevision r = ((PlatformToolPackage) p).getRevision();
- // If computing dependencies for a non-preview package, don't offer preview dependencies
- if (r.isPreview() && !rev.isPreview()) {
- continue;
- }
- if (r.compareTo(localRev) >= compareThreshold) {
- localRev = r;
- localAiMax = ai;
- }
- }
- }
- }
- if (localAiMax != null) {
- if (findMax) {
- rev = localRev;
- aiMax = localAiMax;
- } else {
- // The dependency is already scheduled for install, nothing else to do.
- return localAiMax;
- }
- }
-
-
- // Otherwise look in the selected archives.
- localRev = rev;
- Archive localAMax = null;
- if (selectedArchives != null) {
- for (Archive a : selectedArchives) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformToolPackage) {
- FullRevision r = ((PlatformToolPackage) p).getRevision();
- // If computing dependencies for a non-preview package, don't offer preview dependencies
- if (r.isPreview() && !rev.isPreview()) {
- continue;
- }
-
- if (r.compareTo(localRev) >= compareThreshold) {
- localRev = r;
- localAiMax = null;
- localAMax = a;
- }
- }
- }
- if (localAMax != null) {
- if (findMax) {
- rev = localRev;
- aiMax = null;
- aMax = localAMax;
- } else {
- // It's not already in the list of things to install, so add it now
- return insertArchive(localAMax,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
-
- // Finally nothing matched, so let's look at all available remote packages
- fetchRemotePackages(remotePkgs, remoteSources);
- localRev = rev;
- localAMax = null;
- for (Package p : remotePkgs) {
- if (p instanceof PlatformToolPackage) {
- FullRevision r = ((PlatformToolPackage) p).getRevision();
- // If computing dependencies for a non-preview package, don't offer preview dependencies
- if (r.isPreview() && !rev.isPreview()) {
- continue;
- }
-
- if (r.compareTo(rev) >= 0) {
- // Make sure there's at least one valid archive here
- for (Archive a : p.getArchives()) {
- if (a.isCompatible()) {
- if (r.compareTo(localRev) >= compareThreshold) {
- localRev = r;
- localAiMax = null;
- localAMax = a;
- break;
- }
- }
- }
- }
- }
- }
- if (localAMax != null) {
- if (findMax) {
- rev = localRev;
- aiMax = null;
- aMax = localAMax;
- } else {
- // It's not already in the list of things to install, so add the
- // first compatible archive we can find.
- return insertArchive(localAMax,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
-
- if (findMax) {
- if (aMax != null) {
- return insertArchive(aMax,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- } else if (aiMax != null) {
- return aiMax;
- }
- }
-
- // We end up here if nothing matches. We don't have a good platform to match.
- // We need to indicate this package depends on a missing platform archive
- // so that it can be impossible to install later on.
- return new MissingArchiveInfo(MissingArchiveInfo.TITLE_PLATFORM_TOOL, rev);
- }
-
- /**
- * Resolves dependencies on platform for an add-on.
- * Resolves dependencies on a system-image on its base platform or add-on.
- *
- * An add-on depends on having a platform with the same API level.
- *
- * Finds the platform dependency. If found, add it to the list of things to install.
- * Returns the archive info dependency, if any.
- */
- public ArchiveInfo findPlatformDependency(
- IPlatformDependency pkg,
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives) {
- // This is the requirement to match.
- AndroidVersion v = pkg.getAndroidVersion();
-
-
- // The dependency package must be a PlatformPackage or an AddonPackage.
- // For an add-on, we also need to have the same vendor and name-id.
- Class<? extends Package> expectedClass = PlatformPackage.class;
- IdDisplay addonVendor = null;
- IdDisplay addonTag = null;
- if (pkg instanceof SystemImagePackage && !((SystemImagePackage) pkg).isPlatform()) {
- expectedClass = AddonPackage.class;
- addonVendor = ((SystemImagePackage) pkg).getAddonVendor();
- addonTag = ((SystemImagePackage) pkg).getTag();
- }
-
- // Find a platform or addon that would satisfy the requirement.
-
- // First look in locally installed packages.
- for (ArchiveInfo ai : localArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (expectedClass.isInstance(p)) {
- if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
- if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
- if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
- !((AddonPackage) p).getNameId().equals(addonTag.getId())) {
- continue;
- }
- }
- // We found one already installed.
- return null;
- }
- }
- }
- }
-
- // Look in archives already scheduled for install
- for (ArchiveInfo ai : outArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (expectedClass.isInstance(p)) {
- if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
- if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
- if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
- !((AddonPackage) p).getNameId().equals(addonTag.getId())) {
- continue;
- }
- }
- // The dependency is already scheduled for install, nothing else to do.
- return ai;
- }
- }
- }
- }
-
- // Otherwise look in the selected archives.
- if (selectedArchives != null) {
- for (Archive a : selectedArchives) {
- Package p = a.getParentPackage();
- if (expectedClass.isInstance(p)) {
- if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
- if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
- if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
- !((AddonPackage) p).getNameId().equals(addonTag.getId())) {
- continue;
- }
- }
- // It's not already in the list of things to install, so add it now
- return insertArchive(a,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
- }
-
- // Finally nothing matched, so let's look at all available remote packages
- fetchRemotePackages(remotePkgs, remoteSources);
- for (Package p : remotePkgs) {
- if (expectedClass.isInstance(p)) {
- if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
- if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
- if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
- !((AddonPackage) p).getNameId().equals(addonTag.getId())) {
- continue;
- }
- }
- // It's not already in the list of things to install, so add the
- // first compatible archive we can find.
- for (Archive a : p.getArchives()) {
- if (a.isCompatible()) {
- return insertArchive(a,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
- }
- }
-
- // We end up here if nothing matches. We don't have a good platform to match.
- // We need to indicate this addon depends on a missing platform archive
- // so that it can be impossible to install later on.
- return new MissingPlatformArchiveInfo(pkg.getAndroidVersion());
- }
-
- /**
- * Resolves platform dependencies for extras.
- * An extra depends on having a platform with a minimun API level.
- *
- * We try to return the highest API level available above the specified minimum.
- * Note that installed packages have priority so if one installed platform satisfies
- * the dependency, we'll use it even if there's a higher API platform available but
- * not installed yet.
- *
- * Finds the platform dependency. If found, add it to the list of things to install.
- * Returns the archive info dependency, if any.
- */
- protected ArchiveInfo findMinApiLevelDependency(
- IMinApiLevelDependency pkg,
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives) {
-
- int api = pkg.getMinApiLevel();
-
- if (api == IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED) {
- return null;
- }
-
- // Find a platform that would satisfy the requirement.
-
- // First look in locally installed packages.
- for (ArchiveInfo ai : localArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
- // We found one already installed.
- return null;
- }
- }
- }
- }
-
- // Look in archives already scheduled for install
- int foundApi = 0;
- ArchiveInfo foundAi = null;
-
- for (ArchiveInfo ai : outArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
- if (api > foundApi) {
- foundApi = api;
- foundAi = ai;
- }
- }
- }
- }
- }
-
- if (foundAi != null) {
- // The dependency is already scheduled for install, nothing else to do.
- return foundAi;
- }
-
- // Otherwise look in the selected archives *or* available remote packages
- // and takes the best out of the two sets.
- foundApi = 0;
- Archive foundArchive = null;
- if (selectedArchives != null) {
- for (Archive a : selectedArchives) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
- if (api > foundApi) {
- foundApi = api;
- foundArchive = a;
- }
- }
- }
- }
- }
-
- // Finally nothing matched, so let's look at all available remote packages
- fetchRemotePackages(remotePkgs, remoteSources);
- for (Package p : remotePkgs) {
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
- if (api > foundApi) {
- // It's not already in the list of things to install, so add the
- // first compatible archive we can find.
- for (Archive a : p.getArchives()) {
- if (a.isCompatible()) {
- foundApi = api;
- foundArchive = a;
- }
- }
- }
- }
- }
- }
-
- if (foundArchive != null) {
- // It's not already in the list of things to install, so add it now
- return insertArchive(foundArchive,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
-
- // We end up here if nothing matches. We don't have a good platform to match.
- // We need to indicate this extra depends on a missing platform archive
- // so that it can be impossible to install later on.
- return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
- }
-
- /**
- * Resolves platform dependencies for add-ons.
- * An add-ons depends on having a platform with an exact specific API level.
- *
- * Finds the platform dependency. If found, add it to the list of things to install.
- * Returns the archive info dependency, if any.
- */
- public ArchiveInfo findExactApiLevelDependency(
- IExactApiLevelDependency pkg,
- Collection<ArchiveInfo> outArchives,
- Collection<Archive> selectedArchives,
- Collection<Package> remotePkgs,
- SdkSource[] remoteSources,
- ArchiveInfo[] localArchives) {
-
- int api = pkg.getExactApiLevel();
-
- if (api == IExactApiLevelDependency.API_LEVEL_INVALID) {
- return null;
- }
-
- // Find a platform that would satisfy the requirement.
-
- // First look in locally installed packages.
- for (ArchiveInfo ai : localArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
- // We found one already installed.
- return null;
- }
- }
- }
- }
-
- // Look in archives already scheduled for install
-
- for (ArchiveInfo ai : outArchives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
- return ai;
- }
- }
- }
- }
-
- // Otherwise look in the selected archives.
- if (selectedArchives != null) {
- for (Archive a : selectedArchives) {
- Package p = a.getParentPackage();
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
- // It's not already in the list of things to install, so add it now
- return insertArchive(a,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
- }
-
- // Finally nothing matched, so let's look at all available remote packages
- fetchRemotePackages(remotePkgs, remoteSources);
- for (Package p : remotePkgs) {
- if (p instanceof PlatformPackage) {
- if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
- // It's not already in the list of things to install, so add the
- // first compatible archive we can find.
- for (Archive a : p.getArchives()) {
- if (a.isCompatible()) {
- return insertArchive(a,
- outArchives,
- selectedArchives,
- remotePkgs,
- remoteSources,
- localArchives,
- true /*automated*/);
- }
- }
- }
- }
- }
-
- // We end up here if nothing matches. We don't have a good platform to match.
- // We need to indicate this extra depends on a missing platform archive
- // so that it can be impossible to install later on.
- return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
- }
-
- /**
- * Fetch all remote packages only if really needed.
- * <p/>
- * This method takes a list of sources. Each source is only fetched once -- that is each
- * source keeps the list of packages that we fetched from the remote XML file. If the list
- * is null, it means this source has never been fetched so we'll do it once here. Otherwise
- * we rely on the cached list of packages from this source.
- * <p/>
- * This method also takes a remote package list as input, which it will fill out.
- * If a source has already been fetched, we'll add its packages to the remote package list
- * if they are not already present. Otherwise, the source will be fetched and the packages
- * added to the list.
- *
- * @param remotePkgs An in-out list of packages available from remote sources.
- * This list must not be null.
- * It can be empty or already contain some packages.
- * @param remoteSources A list of available remote sources to fetch from.
- */
- protected void fetchRemotePackages(
- final Collection<Package> remotePkgs,
- final SdkSource[] remoteSources) {
- if (!remotePkgs.isEmpty()) {
- return;
- }
-
- // First check if there's any remote source we need to fetch.
- // This will bring the task window, so we rather not display it unless
- // necessary.
- boolean needsFetch = false;
- for (final SdkSource remoteSrc : remoteSources) {
- Package[] pkgs = remoteSrc.getPackages();
- if (pkgs == null) {
- // This source has never been fetched. We'll do it below.
- needsFetch = true;
- } else {
- // This source has already been fetched and we know its package list.
- // We still need to make sure all of its packages are present in the
- // remotePkgs list.
-
- nextPackage: for (Package pkg : pkgs) {
- for (Archive a : pkg.getArchives()) {
- // Only add a package if it contains at least one compatible archive
- // and is not already in the remote package list.
- if (a.isCompatible()) {
- if (!remotePkgs.contains(pkg)) {
- remotePkgs.add(pkg);
- continue nextPackage;
- }
- }
- }
- }
- }
- }
-
- if (!needsFetch) {
- return;
- }
-
- final boolean forceHttp = mUpdaterData.getSettingsController().getSettings().getForceHttp();
-
- mUpdaterData.getTaskFactory().start("Refresh Sources", new ITask() {
- @Override
- public void run(ITaskMonitor monitor) {
- for (SdkSource remoteSrc : remoteSources) {
- Package[] pkgs = remoteSrc.getPackages();
-
- if (pkgs == null) {
- remoteSrc.load(mUpdaterData.getDownloadCache(), monitor, forceHttp);
- pkgs = remoteSrc.getPackages();
- }
-
- if (pkgs != null) {
- nextPackage: for (Package pkg : pkgs) {
- for (Archive a : pkg.getArchives()) {
- // Only add a package if it contains at least one compatible archive
- // and is not already in the remote package list.
- if (a.isCompatible()) {
- if (!remotePkgs.contains(pkg)) {
- remotePkgs.add(pkg);
- continue nextPackage;
- }
- }
- }
- }
- }
- }
- }
- });
- }
-
-
- /**
- * A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed
- * "local" package/archive.
- * <p/>
- * In this case, the "new Archive" is still expected to be non null and the
- * "replaced Archive" is null. Installed archives are always accepted and never
- * rejected.
- * <p/>
- * Dependencies are not set.
- */
- private static class LocalArchiveInfo extends ArchiveInfo {
-
- public LocalArchiveInfo(Archive localArchive) {
- super(localArchive, null /*replaced*/, null /*dependsOn*/);
- }
-
- /** Installed archives are always accepted. */
- @Override
- public boolean isAccepted() {
- return true;
- }
-
- /** Installed archives are never rejected. */
- @Override
- public boolean isRejected() {
- return false;
- }
- }
-
- /**
- * A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a
- * package/archive that we <em>really</em> need as a dependency but that we don't have.
- * <p/>
- * This is currently used for addons and extras in case we can't find a matching base platform.
- * <p/>
- * This kind of archive has specific properties: the new archive to install is null,
- * there are no dependencies and no archive is being replaced. The info can never be
- * accepted and is always rejected.
- */
- private static class MissingPlatformArchiveInfo extends ArchiveInfo {
-
- private final AndroidVersion mVersion;
-
- /**
- * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
- * given platform version is missing.
- */
- public MissingPlatformArchiveInfo(AndroidVersion version) {
- super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
- mVersion = version;
- }
-
- /** Missing archives are never accepted. */
- @Override
- public boolean isAccepted() {
- return false;
- }
-
- /** Missing archives are always rejected. */
- @Override
- public boolean isRejected() {
- return true;
- }
-
- @Override
- public String getShortDescription() {
- return String.format("Missing SDK Platform Android%1$s, API %2$d",
- mVersion.isPreview() ? " Preview" : "",
- mVersion.getApiLevel());
- }
- }
-
- /**
- * A {@link MissingArchiveInfo} is an {@link ArchiveInfo} that represents a
- * package/archive that we <em>really</em> need as a dependency but that we don't have.
- * <p/>
- * This is currently used for extras in case we can't find a matching tool revision
- * or when a platform-tool is missing.
- * <p/>
- * This kind of archive has specific properties: the new archive to install is null,
- * there are no dependencies and no archive is being replaced. The info can never be
- * accepted and is always rejected.
- */
- private static class MissingArchiveInfo extends ArchiveInfo {
-
- private final FullRevision mRevision;
- private final String mTitle;
-
- public static final String TITLE_TOOL = "Tools";
- public static final String TITLE_PLATFORM_TOOL = "Platform-tools";
-
- /**
- * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
- * given platform version is missing.
- *
- * @param title Typically "Tools" or "Platform-tools".
- * @param revision The required revision.
- */
- public MissingArchiveInfo(String title, FullRevision revision) {
- super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
- mTitle = title;
- mRevision = revision;
- }
-
- /** Missing archives are never accepted. */
- @Override
- public boolean isAccepted() {
- return false;
- }
-
- /** Missing archives are always rejected. */
- @Override
- public boolean isRejected() {
- return true;
- }
-
- @Override
- public String getShortDescription() {
- return String.format("Missing Android SDK %1$s, revision %2$s",
- mTitle,
- mRevision.toShortString());
- }
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterNoWindow.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterNoWindow.java
deleted file mode 100755
index 8183e1b..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SdkUpdaterNoWindow.java
+++ /dev/null
@@ -1,715 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.updater;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskFactory;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.NullTaskMonitor;
-import com.android.sdklib.internal.repository.UserCredentials;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.utils.ILogger;
-import com.android.utils.IReaderLogger;
-import com.android.utils.NullLogger;
-import com.android.utils.Pair;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
-/**
- * Performs an update using only a non-interactive console output with no GUI.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SdkUpdaterNoWindow {
-
- /** The {@link UpdaterData} to use. */
- private final UpdaterData mUpdaterData;
- /** The {@link ILogger} logger to use. */
- private final ILogger mSdkLog;
- /** The reply to any question asked by the update process. Currently this will
- * be yes/no for ability to replace modified samples or restart ADB. */
- private final boolean mForce;
-
- /**
- * Creates an UpdateNoWindow object that will update using the given SDK root
- * and outputs to the given SDK logger.
- *
- * @param osSdkRoot The OS path of the SDK folder to update.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @param sdkLog A logger object, that should ideally output to a write-only console.
- * @param force The reply to any question asked by the update process. Currently this will
- * be yes/no for ability to replace modified samples or restart ADB.
- * @param useHttp True to force using HTTP instead of HTTPS for downloads.
- * @param proxyPort An optional HTTP/HTTPS proxy port. Can be null.
- * @param proxyHost An optional HTTP/HTTPS proxy host. Can be null.
- */
- public SdkUpdaterNoWindow(String osSdkRoot,
- SdkManager sdkManager,
- ILogger sdkLog,
- boolean force,
- boolean useHttp,
- String proxyHost,
- String proxyPort) {
- mSdkLog = sdkLog;
- mForce = force;
- mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);
-
- // Read and apply settings from settings file, so that http/https proxy is set
- // and let the command line args override them as necessary.
- SettingsController settingsController = mUpdaterData.getSettingsController();
- settingsController.loadSettings();
- settingsController.applySettings();
- setupProxy(proxyHost, proxyPort);
-
- // Change the in-memory settings to force the http/https mode
- settingsController.setSetting(ISettingsPage.KEY_FORCE_HTTP, useHttp);
-
- // Use a factory that only outputs to the given ILogger.
- mUpdaterData.setTaskFactory(new ConsoleTaskFactory());
-
- // Check that the AVD Manager has been correctly initialized. This is done separately
- // from the constructor in the GUI-based UpdaterWindowImpl to give time to the UI to
- // initialize before displaying a message box. Since we don't have any GUI here
- // we can call it whenever we want.
- if (mUpdaterData.checkIfInitFailed()) {
- return;
- }
-
- // Setup the default sources including the getenv overrides.
- mUpdaterData.setupDefaultSources();
-
- mUpdaterData.getLocalSdkParser().parseSdk(
- osSdkRoot,
- sdkManager,
- new NullTaskMonitor(sdkLog));
- }
-
- /**
- * Performs the actual update.
- *
- * @param pkgFilter A list of {@link SdkRepoConstants#NODES} to limit the type of packages
- * we can update. A null or empty list means to update everything possible.
- * @param includeAll True to list and install all packages, including obsolete ones.
- * @param dryMode True to check what would be updated/installed but do not actually
- * download or install anything.
- * @param acceptLicense SDK licenses to automatically accept.
- * @deprecated Use {@link #updateAll(java.util.ArrayList, boolean, boolean, String, boolean)}
- * instead
- */
- @Deprecated
- public void updateAll(
- ArrayList<String> pkgFilter,
- boolean includeAll,
- boolean dryMode,
- String acceptLicense) {
- updateAll(pkgFilter, includeAll, dryMode, acceptLicense, false);
- }
-
- /**
- * Performs the actual update.
- *
- * @param pkgFilter A list of {@link SdkRepoConstants#NODES} to limit the type of packages
- * we can update. A null or empty list means to update everything possible.
- * @param includeAll True to list and install all packages, including obsolete ones.
- * @param dryMode True to check what would be updated/installed but do not actually
- * download or install anything.
- * @param acceptLicense SDK licenses to automatically accept.
- * @param includeDependencies If true, also include any required dependencies
- */
- public void updateAll(
- ArrayList<String> pkgFilter,
- boolean includeAll,
- boolean dryMode,
- String acceptLicense,
- boolean includeDependencies) {
- mUpdaterData.updateOrInstallAll_NoGUI(pkgFilter, includeAll, dryMode, acceptLicense,
- includeDependencies);
- }
-
- /**
- * Lists remote packages available for install using 'android update sdk --no-ui'.
- *
- * @param includeAll True to list and install all packages, including obsolete ones.
- * @param extendedOutput True to display more details on each package.
- */
- public void listRemotePackages(boolean includeAll, boolean extendedOutput) {
- mUpdaterData.listRemotePackages_NoGUI(includeAll, extendedOutput);
- }
-
- /**
- * Installs a platform package given its target hash string.
- * <p/>
- * This does not work for add-ons right now, just basic platforms.
- *
- * @param hashString The hash string of the platform to install.
- * @return A boolean indicating whether the installation was successful (meaning the package
- * was either already present, or got installed or updated properly) and a {@link File}
- * with the path to the root folder of the package. The file is null when the boolean
- * is false, otherwise it should point to an existing valid folder.
- */
- public Pair<Boolean, File> installPlatformPackage(String hashString) {
-
- // TODO right now we really need the caller to use a reader-logger to
- // handle license confirmations. This isn't optimal and should be addressed
- // when we provide a proper UI for this.
- assert mSdkLog instanceof IReaderLogger;
-
- SdkManager sm = mUpdaterData.getSdkManager();
- IAndroidTarget target = sm.getTargetFromHashString(hashString);
-
- if (target == null) {
- // Otherwise try to install it.
- // This currently only works for platforms since the package's "install id"
- // is an exactly match with the IAndroidTarget hash string.
- ArrayList<String> filter = new ArrayList<String>();
- filter.add(hashString);
- List<Archive> installed = mUpdaterData.updateOrInstallAll_NoGUI(
- filter,
- true, //includeAll
- false, //dryMode
- null); //acceptLicense
-
- if (installed != null) {
- sm.reloadSdk(new NullLogger());
- target = sm.getTargetFromHashString(hashString);
- }
- }
-
- if (target != null) {
- // Return existing target
- return Pair.of(Boolean.TRUE, new File(target.getLocation()));
- }
-
- return null;
- }
-
- // -----
-
- /**
- * Sets both the HTTP and HTTPS proxy system properties, overriding the ones
- * from the settings with these values if they are defined.
- */
- private void setupProxy(String proxyHost, String proxyPort) {
-
- // The system property constants can be found in the Java SE documentation at
- // http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
- final String JAVA_PROP_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$
- final String JAVA_PROP_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$
- final String JAVA_PROP_HTTPS_PROXY_HOST = "https.proxyHost"; //$NON-NLS-1$
- final String JAVA_PROP_HTTPS_PROXY_PORT = "https.proxyPort"; //$NON-NLS-1$
-
- Properties props = System.getProperties();
-
- if (proxyHost != null && !proxyHost.isEmpty()) {
- props.setProperty(JAVA_PROP_HTTP_PROXY_HOST, proxyHost);
- props.setProperty(JAVA_PROP_HTTPS_PROXY_HOST, proxyHost);
- }
- if (proxyPort != null && !proxyPort.isEmpty()) {
- props.setProperty(JAVA_PROP_HTTP_PROXY_PORT, proxyPort);
- props.setProperty(JAVA_PROP_HTTPS_PROXY_PORT, proxyPort);
- }
- }
-
- /**
- * A custom implementation of {@link ITaskFactory} that
- * provides {@link ConsoleTaskMonitor} objects.
- */
- private class ConsoleTaskFactory implements ITaskFactory {
- @Override
- public void start(String title, ITask task) {
- start(title, null /*parentMonitor*/, task);
- }
-
- @Override
- public void start(String title, ITaskMonitor parentMonitor, ITask task) {
- if (parentMonitor == null) {
- task.run(new ConsoleTaskMonitor(title, task));
- } else {
- // Use all the reminder of the parent monitor.
- if (parentMonitor.getProgressMax() == 0) {
- parentMonitor.setProgressMax(1);
- }
-
- ITaskMonitor sub = parentMonitor.createSubMonitor(
- parentMonitor.getProgressMax() - parentMonitor.getProgress());
- try {
- task.run(sub);
- } finally {
- int delta =
- sub.getProgressMax() - sub.getProgress();
- if (delta > 0) {
- sub.incProgress(delta);
- }
- }
- }
- }
- }
-
- /**
- * A custom implementation of {@link ITaskMonitor} that defers all output to the
- * super {@link SdkUpdaterNoWindow#mSdkLog}.
- */
- private class ConsoleTaskMonitor implements ITaskMonitor {
-
- private static final double MAX_COUNT = 10000.0;
- private double mIncCoef = 0;
- private double mValue = 0;
- private String mLastDesc = null;
- private String mLastProgressBase = null;
-
- /**
- * Creates a new {@link ConsoleTaskMonitor} with the given title.
- */
- public ConsoleTaskMonitor(String title, ITask task) {
- mSdkLog.info("%s:\n", title);
- }
-
- /**
- * Sets the description in the current task dialog.
- */
- @Override
- public void setDescription(String format, Object...args) {
-
- String last = mLastDesc;
- String line = String.format(" " + format, args); //$NON-NLS-1$
-
- // If the description contains a %, it generally indicates a recurring
- // progress so we want a \r at the end.
- int pos = line.indexOf('%');
- if (pos > -1) {
- String base = line.trim();
- if (mLastProgressBase != null && base.startsWith(mLastProgressBase)) {
- line = " " + base.substring(mLastProgressBase.length()); //$NON-NLS-1$
- }
- line += '\r';
- } else {
- mLastProgressBase = line.trim();
- line += '\n';
- }
-
- // Skip line if it's the same as the last one.
- if (last != null && last.equals(line.trim())) {
- return;
- }
- mLastDesc = line.trim();
-
- // If the last line terminated with a \r but the new one doesn't, we need to
- // insert a \n to avoid erasing the previous line.
- if (last != null &&
- last.endsWith("\r") && //$NON-NLS-1$
- !line.endsWith("\r")) { //$NON-NLS-1$
- line = '\n' + line;
- }
-
- mSdkLog.info("%s", line); //$NON-NLS-1$
- }
-
- @Override
- public void log(String format, Object...args) {
- setDescription(" " + format, args); //$NON-NLS-1$
- }
-
- @Override
- public void logError(String format, Object...args) {
- setDescription(format, args);
- }
-
- @Override
- public void logVerbose(String format, Object...args) {
- // The ConsoleTask does not display verbose log messages.
- }
-
- // --- ILogger ---
-
- @Override
- public void error(@Nullable Throwable t, @Nullable String errorFormat, Object... args) {
- mSdkLog.error(t, errorFormat, args);
- }
-
- @Override
- public void warning(@NonNull String warningFormat, Object... args) {
- mSdkLog.warning(warningFormat, args);
- }
-
- @Override
- public void info(@NonNull String msgFormat, Object... args) {
- mSdkLog.info(msgFormat, args);
- }
-
- @Override
- public void verbose(@NonNull String msgFormat, Object... args) {
- mSdkLog.verbose(msgFormat, args);
- }
-
- /**
- * Sets the max value of the progress bar.
- *
- * Weird things will happen if setProgressMax is called multiple times
- * *after* {@link #incProgress(int)}: we don't try to adjust it on the
- * fly.
- */
- @Override
- public void setProgressMax(int max) {
- assert max > 0;
- // Always set the dialog's progress max to 10k since it only handles
- // integers and we want to have a better inner granularity. Instead
- // we use the max to compute a coefficient for inc deltas.
- mIncCoef = max > 0 ? MAX_COUNT / max : 0;
- assert mIncCoef > 0;
- }
-
- @Override
- public int getProgressMax() {
- return mIncCoef > 0 ? (int) (MAX_COUNT / mIncCoef) : 0;
- }
-
- /**
- * Increments the current value of the progress bar.
- */
- @Override
- public void incProgress(int delta) {
- if (delta > 0 && mIncCoef > 0) {
- internalIncProgress(delta * mIncCoef);
- }
- }
-
- private void internalIncProgress(double realDelta) {
- mValue += realDelta;
- // max value is 10k, so 10k/100 == 100%.
- // Experimentation shows that it is not really useful to display this
- // progression since during download the description line will change.
- // mSdkLog.printf(" [%3d%%]\r", ((int)mValue) / 100);
- }
-
- /**
- * Returns the current value of the progress bar,
- * between 0 and up to {@link #setProgressMax(int)} - 1.
- */
- @Override
- public int getProgress() {
- assert mIncCoef > 0;
- return mIncCoef > 0 ? (int)(mValue / mIncCoef) : 0;
- }
-
- /**
- * Returns true if the "Cancel" button was selected.
- */
- @Override
- public boolean isCancelRequested() {
- return false;
- }
-
- /**
- * Display a yes/no question dialog box.
- *
- * This implementation allow this to be called from any thread, it
- * makes sure the dialog is opened synchronously in the ui thread.
- *
- * @param title The title of the dialog box
- * @param message The error message
- * @return true if YES was clicked.
- */
- @Override
- public boolean displayPrompt(final String title, final String message) {
- // TODO Make it interactive if mForce==false
- mSdkLog.info("\n%1$s\n%2$s\n%3$s", //$NON-NLS-1$
- title,
- message,
- mForce ? "--force used, will reply yes\n" :
- "Note: you can use --force to override to yes.\n");
- if (mForce) {
- return true;
- }
-
- while (true) {
- mSdkLog.info("%1$s", "[y/n] =>"); //$NON-NLS-1$
- try {
- byte[] readBuffer = new byte[2048];
- String reply = readLine(readBuffer).trim();
- mSdkLog.info("\n"); //$NON-NLS-1$
- if (!reply.isEmpty() && reply.length() <= 3) {
- char c = reply.charAt(0);
- if (c == 'y' || c == 'Y') {
- return true;
- } else if (c == 'n' || c == 'N') {
- return false;
- }
- }
- mSdkLog.info("Unknown reply '%s'. Please use y[es]/n[o].\n"); //$NON-NLS-1$
-
- } catch (IOException e) {
- // Exception. Be conservative and say no.
- mSdkLog.info("\n"); //$NON-NLS-1$
- return false;
- }
- }
- }
-
- /**
- * Displays a prompt message to the user and read two values,
- * login/password.
- * <p>
- * <i>Asks user for login/password information.</i>
- * <p>
- * This method shows a question in the standard output, asking for login
- * and password.</br>
- * <b>Method Output:</b></br>
- * Title</br>
- * Message</br>
- * Login: (Wait for user input)</br>
- * Password: (Wait for user input)</br>
- * <p>
- *
- * @param title The title of the iteration.
- * @param message The message to be displayed.
- * @return A {@link Pair} holding the entered login and password. The
- * <b>first element</b> is always the <b>Login</b>, and the
- * <b>second element</b> is always the <b>Password</b>. This
- * method will never return null, in case of error the pair will
- * be filled with empty strings.
- * @see ITaskMonitor#displayLoginCredentialsPrompt(String, String)
- */
- @Override
- public UserCredentials displayLoginCredentialsPrompt(String title, String message) {
- String login = ""; //$NON-NLS-1$
- String password = ""; //$NON-NLS-1$
- String workstation = ""; //$NON-NLS-1$
- String domain = ""; //$NON-NLS-1$
-
- mSdkLog.info("\n%1$s\n%2$s", title, message);
- byte[] readBuffer = new byte[2048];
- try {
- mSdkLog.info("\nLogin: ");
- login = readLine(readBuffer);
- mSdkLog.info("\nPassword: ");
- password = readLine(readBuffer);
- mSdkLog.info("\nIf your proxy uses NTLM authentication, provide the following information. Leave blank otherwise.");
- mSdkLog.info("\nWorkstation: ");
- workstation = readLine(readBuffer);
- mSdkLog.info("\nDomain: ");
- domain = readLine(readBuffer);
-
- /*
- * TODO: Implement a way to don't echo the typed password On
- * Java 5 there's no simple way to do this. There's just a
- * workaround which is output backspaces on each keystroke.
- * A good alternative is to use Java 6 java.io.Console
- */
- } catch (IOException e) {
- // Reset login/pass to empty Strings.
- login = ""; //$NON-NLS-1$
- password = ""; //$NON-NLS-1$
- workstation = ""; //$NON-NLS-1$
- domain = ""; //$NON-NLS-1$
- //Just print the error to console.
- mSdkLog.info("\nError occurred during login/pass query: %s\n", e.getMessage());
- }
-
- return new UserCredentials(login, password, workstation, domain);
- }
-
- /**
- * Reads current console input in the given buffer.
- *
- * @param buffer Buffer to hold the user input. Must be larger than the largest
- * expected input. Cannot be null.
- * @return A new string. May be empty but not null.
- * @throws IOException in case the buffer isn't long enough.
- */
- private String readLine(byte[] buffer) throws IOException {
-
- int count;
- if (mSdkLog instanceof IReaderLogger) {
- count = ((IReaderLogger) mSdkLog).readLine(buffer);
- } else {
- count = System.in.read(buffer);
- }
-
- // is the input longer than the buffer?
- if (count == buffer.length && buffer[count-1] != 10) {
- throw new IOException(String.format(
- "Input is longer than the buffer size, (%1$s) bytes", buffer.length));
- }
-
- // ignore end whitespace
- while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) {
- count--;
- }
-
- return new String(buffer, 0, count);
- }
-
- /**
- * Creates a sub-monitor that will use up to tickCount on the progress bar.
- * tickCount must be 1 or more.
- */
- @Override
- public ITaskMonitor createSubMonitor(int tickCount) {
- assert mIncCoef > 0;
- assert tickCount > 0;
- return new ConsoleSubTaskMonitor(this, null, mValue, tickCount * mIncCoef);
- }
- }
-
- private interface IConsoleSubTaskMonitor extends ITaskMonitor {
- void subIncProgress(double realDelta);
- }
-
- private static class ConsoleSubTaskMonitor implements IConsoleSubTaskMonitor {
-
- private final ConsoleTaskMonitor mRoot;
- private final IConsoleSubTaskMonitor mParent;
- private final double mStart;
- private final double mSpan;
- private double mSubValue;
- private double mSubCoef;
-
- /**
- * Creates a new sub task monitor which will work for the given range [start, start+span]
- * in its parent.
- *
- * @param root The ProgressTask root
- * @param parent The immediate parent. Can be the null or another sub task monitor.
- * @param start The start value in the root's coordinates
- * @param span The span value in the root's coordinates
- */
- public ConsoleSubTaskMonitor(ConsoleTaskMonitor root,
- IConsoleSubTaskMonitor parent,
- double start,
- double span) {
- mRoot = root;
- mParent = parent;
- mStart = start;
- mSpan = span;
- mSubValue = start;
- }
-
- @Override
- public boolean isCancelRequested() {
- return mRoot.isCancelRequested();
- }
-
- @Override
- public void setDescription(String format, Object... args) {
- mRoot.setDescription(format, args);
- }
-
- @Override
- public void log(String format, Object... args) {
- mRoot.log(format, args);
- }
-
- @Override
- public void logError(String format, Object... args) {
- mRoot.logError(format, args);
- }
-
- @Override
- public void logVerbose(String format, Object... args) {
- mRoot.logVerbose(format, args);
- }
-
- @Override
- public void setProgressMax(int max) {
- assert max > 0;
- mSubCoef = max > 0 ? mSpan / max : 0;
- assert mSubCoef > 0;
- }
-
- @Override
- public int getProgressMax() {
- return mSubCoef > 0 ? (int) (mSpan / mSubCoef) : 0;
- }
-
- @Override
- public int getProgress() {
- assert mSubCoef > 0;
- return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0;
- }
-
- @Override
- public void incProgress(int delta) {
- if (delta > 0 && mSubCoef > 0) {
- subIncProgress(delta * mSubCoef);
- }
- }
-
- @Override
- public void subIncProgress(double realDelta) {
- mSubValue += realDelta;
- if (mParent != null) {
- mParent.subIncProgress(realDelta);
- } else {
- mRoot.internalIncProgress(realDelta);
- }
- }
-
- @Override
- public boolean displayPrompt(String title, String message) {
- return mRoot.displayPrompt(title, message);
- }
-
- @Override
- public UserCredentials displayLoginCredentialsPrompt(String title, String message) {
- return mRoot.displayLoginCredentialsPrompt(title, message);
- }
-
- @Override
- public ITaskMonitor createSubMonitor(int tickCount) {
- assert mSubCoef > 0;
- assert tickCount > 0;
- return new ConsoleSubTaskMonitor(mRoot,
- this,
- mSubValue,
- tickCount * mSubCoef);
- }
-
- // --- ILogger ---
-
- @Override
- public void error(@Nullable Throwable t, @Nullable String errorFormat, Object... args) {
- mRoot.error(t, errorFormat, args);
- }
-
- @Override
- public void warning(@NonNull String warningFormat, Object... args) {
- mRoot.warning(warningFormat, args);
- }
-
- @Override
- public void info(@NonNull String msgFormat, Object... args) {
- mRoot.info(msgFormat, args);
- }
-
- @Override
- public void verbose(@NonNull String msgFormat, Object... args) {
- mRoot.verbose(msgFormat, args);
- }
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SettingsController.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SettingsController.java
deleted file mode 100755
index c15cbf7..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/SettingsController.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.updater;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.utils.ILogger;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Properties;
-
-/**
- * Controller class to get settings values. Settings are kept in-memory.
- * Users of this class must first load the settings before changing them and save
- * them when modified.
- * <p/>
- * Settings are enumerated by constants in {@link ISettingsPage}.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class SettingsController {
-
- private static final String SETTINGS_FILENAME = "androidtool.cfg"; //$NON-NLS-1$
-
- private final IFileOp mFileOp;
- private final ILogger mSdkLog;
- private final Settings mSettings;
-
- public interface OnChangedListener {
- void onSettingsChanged(@NonNull SettingsController controller,
- @NonNull Settings oldSettings);
- }
- private final List<OnChangedListener> mChangedListeners = new ArrayList<OnChangedListener>(1);
-
- /** The currently associated {@link ISettingsPage}. Can be null. */
- private ISettingsPage mSettingsPage;
-
-
- /**
- * Constructs a new default {@link SettingsController}.
- *
- * @param sdkLog A non-null logger to use.
- */
- public SettingsController(@NonNull ILogger sdkLog) {
- this(new FileOp(), sdkLog);
- }
-
- /**
- * Constructs a new default {@link SettingsController}.
- *
- * @param fileOp A non-null {@link FileOp} to perform file operations (to load/save settings.)
- * @param sdkLog A non-null logger to use.
- */
- public SettingsController(@NonNull IFileOp fileOp, @NonNull ILogger sdkLog) {
- this(fileOp, sdkLog, new Settings());
- }
-
- /**
- * Specialized constructor that wraps an existing {@link Settings} instance.
- * This is mostly used in unit-tests to override settings that are being used.
- * Normal usage should NOT need to call this constructor.
- *
- * @param fileOp A non-null {@link FileOp} to perform file operations (to load/save settings)
- * @param sdkLog A non-null logger to use.
- * @param settings A non-null {@link Settings} to use as-is. It is not duplicated.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SettingsController(@NonNull IFileOp fileOp,
- @NonNull ILogger sdkLog,
- @NonNull Settings settings) {
- mFileOp = fileOp;
- mSdkLog = sdkLog;
- mSettings = settings;
- }
-
- @NonNull
- public Settings getSettings() {
- return mSettings;
- }
-
- public void registerOnChangedListener(@Nullable OnChangedListener listener) {
- if (listener != null && !mChangedListeners.contains(listener)) {
- mChangedListeners.add(listener);
- }
- }
-
- public void unregisterOnChangedListener(@Nullable OnChangedListener listener) {
- if (listener != null) {
- mChangedListeners.remove(listener);
- }
- }
-
- //--- Access to settings ------------
-
-
- public static class Settings {
- private final Properties mProperties;
-
- /** Initialize an empty set of settings. */
- public Settings() {
- mProperties = new Properties();
- }
-
- /** Duplicates a set of settings. */
- public Settings(@NonNull Settings settings) {
- this();
- for (Entry<Object, Object> entry : settings.mProperties.entrySet()) {
- mProperties.put(entry.getKey(), entry.getValue());
- }
- }
-
- /**
- * Specialized constructor for unit-tests that wraps an existing
- * {@link Properties} instance. The properties instance is not duplicated,
- * it's merely used as-is and changes will be reflected directly.
- */
- protected Settings(@NonNull Properties properties) {
- mProperties = properties;
- }
-
- /**
- * Returns the value of the {@link ISettingsPage#KEY_FORCE_HTTP} setting.
- *
- * @see ISettingsPage#KEY_FORCE_HTTP
- */
- public boolean getForceHttp() {
- return Boolean.parseBoolean(mProperties.getProperty(ISettingsPage.KEY_FORCE_HTTP));
- }
-
- /**
- * Returns the value of the {@link ISettingsPage#KEY_ASK_ADB_RESTART} setting.
- *
- * @see ISettingsPage#KEY_ASK_ADB_RESTART
- */
- public boolean getAskBeforeAdbRestart() {
- return Boolean.parseBoolean(mProperties.getProperty(ISettingsPage.KEY_ASK_ADB_RESTART));
- }
-
- /**
- * Returns the value of the {@link ISettingsPage#KEY_USE_DOWNLOAD_CACHE} setting.
- *
- * @see ISettingsPage#KEY_USE_DOWNLOAD_CACHE
- */
- public boolean getUseDownloadCache() {
- return Boolean.parseBoolean(
- mProperties.getProperty(
- ISettingsPage.KEY_USE_DOWNLOAD_CACHE,
- Boolean.TRUE.toString()));
- }
-
- /**
- * Returns the value of the {@link ISettingsPage#KEY_SHOW_UPDATE_ONLY} setting.
- *
- * @see ISettingsPage#KEY_SHOW_UPDATE_ONLY
- */
- public boolean getShowUpdateOnly() {
- return Boolean.parseBoolean(
- mProperties.getProperty(
- ISettingsPage.KEY_SHOW_UPDATE_ONLY,
- Boolean.TRUE.toString()));
- }
-
- /**
- * Returns the value of the {@link ISettingsPage#KEY_ENABLE_PREVIEWS} setting.
- *
- * @see ISettingsPage#KEY_ENABLE_PREVIEWS
- */
- public boolean getEnablePreviews() {
- return Boolean.parseBoolean(
- mProperties.getProperty(
- ISettingsPage.KEY_ENABLE_PREVIEWS,
- Boolean.TRUE.toString()));
- }
-
- /**
- * Returns the value of the {@link ISettingsPage#KEY_MONITOR_DENSITY} setting
- * @see ISettingsPage#KEY_MONITOR_DENSITY
- */
- public int getMonitorDensity() {
- String value = mProperties.getProperty(ISettingsPage.KEY_MONITOR_DENSITY, null);
- if (value == null) {
- return -1;
- }
-
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- return -1;
- }
- }
- }
-
- /**
- * Sets the value of the {@link ISettingsPage#KEY_SHOW_UPDATE_ONLY} setting.
- *
- * @param enabled True if only compatible non-obsolete update items should be shown.
- * @see ISettingsPage#KEY_SHOW_UPDATE_ONLY
- */
- public void setShowUpdateOnly(boolean enabled) {
- setSetting(ISettingsPage.KEY_SHOW_UPDATE_ONLY, enabled);
- }
-
- /**
- * Sets the value of the {@link ISettingsPage#KEY_MONITOR_DENSITY} setting.
- *
- * @param density the density of the monitor
- * @see ISettingsPage#KEY_MONITOR_DENSITY
- */
- public void setMonitorDensity(int density) {
- mSettings.mProperties.setProperty(
- ISettingsPage.KEY_MONITOR_DENSITY, Integer.toString(density));
- }
-
- /**
- * Internal helper to set a boolean setting.
- */
- void setSetting(@NonNull String key, boolean value) {
- mSettings.mProperties.setProperty(key, Boolean.toString(value));
- }
-
- //--- Controller methods -------------
-
- /**
- * Associate the given {@link ISettingsPage} with this {@link SettingsController}.
- * <p/>
- * This loads the current properties into the setting page UI.
- * It then associates the SettingsChanged callback with this controller.
- * <p/>
- * If the setting page given is null, it will be unlinked from controller.
- *
- * @param settingsPage An {@link ISettingsPage} to associate with the controller.
- */
- public void setSettingsPage(@Nullable ISettingsPage settingsPage) {
- mSettingsPage = settingsPage;
-
- if (settingsPage != null) {
- settingsPage.loadSettings(mSettings.mProperties);
-
- settingsPage.setOnSettingsChanged(new ISettingsPage.SettingsChangedCallback() {
- @Override
- public void onSettingsChanged(ISettingsPage page) {
- SettingsController.this.onSettingsChanged();
- }
- });
- }
- }
-
- /**
- * Load settings from the settings file.
- */
- public void loadSettings() {
-
- String path = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SETTINGS_FILENAME);
- path = f.getPath();
-
- Properties props = mFileOp.loadProperties(f);
- mSettings.mProperties.clear();
- mSettings.mProperties.putAll(props);
-
- // Properly reformat some settings to enforce their default value when missing.
- setShowUpdateOnly(mSettings.getShowUpdateOnly());
- setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, mSettings.getAskBeforeAdbRestart());
- setSetting(ISettingsPage.KEY_USE_DOWNLOAD_CACHE, mSettings.getUseDownloadCache());
- setSetting(ISettingsPage.KEY_ENABLE_PREVIEWS, mSettings.getEnablePreviews());
-
- } catch (Exception e) {
- if (mSdkLog != null) {
- mSdkLog.error(e,
- "Failed to load settings from .android folder. Path is '%1$s'.",
- path);
- }
- }
- }
-
- /**
- * Saves settings to the settings file.
- */
- public void saveSettings() {
-
- String path = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SETTINGS_FILENAME);
- path = f.getPath();
- mFileOp.saveProperties(f, mSettings.mProperties, "## Settings for Android Tool"); //$NON-NLS-1$
-
- } catch (Exception e) {
- if (mSdkLog != null) {
- // This is important enough that we want to really nag the user about it
- String reason = null;
-
- if (e instanceof FileNotFoundException) {
- reason = "File not found";
- } else if (e instanceof AndroidLocationException) {
- reason = ".android folder not found, please define ANDROID_SDK_HOME";
- } else if (e.getMessage() != null) {
- reason = String.format("%1$s: %2$s",
- e.getClass().getSimpleName(),
- e.getMessage());
- } else {
- reason = e.getClass().getName();
- }
-
- mSdkLog.error(e, "Failed to save settings file '%1$s': %2$s", path, reason);
- }
- }
- }
-
- /**
- * When settings have changed: retrieve the new settings, apply them, save them
- * and notify on-settings-changed listeners.
- */
- private void onSettingsChanged() {
- if (mSettingsPage == null) {
- return;
- }
-
- Settings oldSettings = new Settings(mSettings);
- mSettingsPage.retrieveSettings(mSettings.mProperties);
- applySettings();
- saveSettings();
-
- for (OnChangedListener listener : mChangedListeners) {
- try {
- listener.onSettingsChanged(this, oldSettings);
- } catch (Throwable ignore) {}
- }
- }
-
- /**
- * Applies the current settings.
- */
- public void applySettings() {
- Properties props = System.getProperties();
-
- // Get the configured HTTP proxy settings
- String proxyHost = mSettings.mProperties.getProperty(ISettingsPage.KEY_HTTP_PROXY_HOST,
- ""); //$NON-NLS-1$
- String proxyPort = mSettings.mProperties.getProperty(ISettingsPage.KEY_HTTP_PROXY_PORT,
- ""); //$NON-NLS-1$
-
- // Set both the HTTP and HTTPS proxy system properties.
- // The system property constants can be found in the Java SE documentation at
- // http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
- final String JAVA_PROP_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$
- final String JAVA_PROP_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$
- final String JAVA_PROP_HTTPS_PROXY_HOST = "https.proxyHost"; //$NON-NLS-1$
- final String JAVA_PROP_HTTPS_PROXY_PORT = "https.proxyPort"; //$NON-NLS-1$
-
- // Only change the proxy if have something in the preferences.
- // Do not erase the default settings by empty values.
- if (proxyHost != null && !proxyHost.isEmpty()) {
- props.setProperty(JAVA_PROP_HTTP_PROXY_HOST, proxyHost);
- props.setProperty(JAVA_PROP_HTTPS_PROXY_HOST, proxyHost);
- }
- if (proxyPort != null && !proxyPort.isEmpty()) {
- props.setProperty(JAVA_PROP_HTTP_PROXY_PORT, proxyPort);
- props.setProperty(JAVA_PROP_HTTPS_PROXY_PORT, proxyPort);
- }
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/UpdaterData.java b/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/UpdaterData.java
deleted file mode 100755
index c1b691c..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/updater/UpdaterData.java
+++ /dev/null
@@ -1,1336 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.updater;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.avd.AvdManager;
-import com.android.sdklib.internal.repository.AdbWrapper;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskFactory;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.LocalSdkParser;
-import com.android.sdklib.internal.repository.NullTaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.repository.License;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.internal.repository.sources.SdkRepoSource;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
-import com.android.sdklib.internal.repository.sources.SdkSources;
-import com.android.sdklib.internal.repository.updater.SettingsController.OnChangedListener;
-import com.android.sdklib.repository.ISdkChangeListener;
-import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.util.LineUtil;
-import com.android.utils.ILogger;
-import com.android.utils.IReaderLogger;
-import com.android.utils.SparseIntArray;
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-/**
- * Data shared by the SDK Manager updaters.
- *
- * @deprecated
- * com.android.sdklib.internal.repository has moved into Studio as
- * com.android.tools.idea.sdk.remote.internal.
- */
- at Deprecated
-public class UpdaterData implements IUpdaterData {
-
- public static final int NO_TOOLS_MSG = 0;
- public static final int TOOLS_MSG_UPDATED_FROM_ADT = 1;
- public static final int TOOLS_MSG_UPDATED_FROM_SDKMAN = 2;
-
- private String mOsSdkRoot;
-
- private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
- /** Holds all sources. Do not use this directly.
- * Instead use {@link #getSources()} so that unit tests can override this as needed. */
- private final SdkSources mSources = new SdkSources();
- /** Holds settings. Do not use this directly.
- * Instead use {@link #getSettingsController()} so that unit tests can override this. */
- private final SettingsController mSettingsController;
- private final ArrayList<ISdkChangeListener> mListeners = new ArrayList<ISdkChangeListener>();
- private final ILogger mSdkLog;
- private ITaskFactory mTaskFactory;
-
- private SdkManager mSdkManager;
- private AvdManager mAvdManager;
- /**
- * The current {@link PackageLoader} to use.
- * Lazily created in {@link #getPackageLoader()}.
- */
- private PackageLoader mPackageLoader;
- /**
- * The current {@link DownloadCache} to use.
- * Lazily created in {@link #getDownloadCache()}.
- */
- private DownloadCache mDownloadCache;
- private AndroidLocationException mAvdManagerInitError;
-
- /**
- * Creates a new updater data.
- *
- * @param sdkLog Logger. Cannot be null.
- * @param osSdkRoot The OS path to the SDK root.
- */
- public UpdaterData(String osSdkRoot, ILogger sdkLog) {
- mOsSdkRoot = osSdkRoot;
- mSdkLog = sdkLog;
-
- mSettingsController = initSettingsController();
- initSdk();
- }
-
- // ----- getters, setters ----
-
- public String getOsSdkRoot() {
- return mOsSdkRoot;
- }
-
- @Override
- public DownloadCache getDownloadCache() {
- if (mDownloadCache == null) {
- mDownloadCache = new DownloadCache(
- getSettingsController().getSettings().getUseDownloadCache() ?
- DownloadCache.Strategy.FRESH_CACHE :
- DownloadCache.Strategy.DIRECT);
- }
- return mDownloadCache;
- }
-
- public void setTaskFactory(ITaskFactory taskFactory) {
- mTaskFactory = taskFactory;
- }
-
- @Override
- public ITaskFactory getTaskFactory() {
- return mTaskFactory;
- }
-
- public SdkSources getSources() {
- return mSources;
- }
-
- public LocalSdkParser getLocalSdkParser() {
- return mLocalSdkParser;
- }
-
- @Override
- public ILogger getSdkLog() {
- return mSdkLog;
- }
-
- @Override
- public SdkManager getSdkManager() {
- return mSdkManager;
- }
-
- @Override
- public AvdManager getAvdManager() {
- return mAvdManager;
- }
-
- @Override
- public SettingsController getSettingsController() {
- return mSettingsController;
- }
-
- /** Adds a listener ({@link ISdkChangeListener}) that is notified when the SDK is reloaded. */
- public void addListeners(ISdkChangeListener listener) {
- if (mListeners.contains(listener) == false) {
- mListeners.add(listener);
- }
- }
-
- /** Removes a listener ({@link ISdkChangeListener}) that is notified when the SDK is reloaded. */
- public void removeListener(ISdkChangeListener listener) {
- mListeners.remove(listener);
- }
-
- public PackageLoader getPackageLoader() {
- // The package loader is lazily initialized here.
- if (mPackageLoader == null) {
- mPackageLoader = new PackageLoader(this);
- }
- return mPackageLoader;
- }
-
- /**
- * Check if any error occurred during initialization.
- * If it did, display an error message.
- *
- * @return True if an error occurred, false if we should continue.
- */
- public boolean checkIfInitFailed() {
- if (mAvdManagerInitError != null) {
- String example;
- if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
- example = "%USERPROFILE%"; //$NON-NLS-1$
- } else {
- example = "~"; //$NON-NLS-1$
- }
-
- String error = String.format(
- "The AVD manager normally uses the user's profile directory to store " +
- "AVD files. However it failed to find the default profile directory. " +
- "\n" +
- "To fix this, please set the environment variable ANDROID_SDK_HOME to " +
- "a valid path such as \"%s\".",
- example);
-
- displayInitError(error);
-
- return true;
- }
- return false;
- }
-
- protected void displayInitError(String error) {
- mSdkLog.error(null /* Throwable */, "%s", error); //$NON-NLS-1$
- }
-
- // -----
-
- /**
- * Runs a runnable on the UI thread.
- * The base implementation just runs the runnable right away.
- *
- * @param r Non-null runnable.
- */
- protected void runOnUiThread(@NonNull Runnable r) {
- r.run();
- }
-
- /**
- * Initializes the {@link SdkManager} and the {@link AvdManager}.
- * Extracted so that we can override this in unit tests.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void initSdk() {
- setSdkManager(SdkManager.createManager(mOsSdkRoot, mSdkLog));
- try {
- mAvdManager = null;
- mAvdManager = AvdManager.getInstance(mSdkManager.getLocalSdk(), mSdkLog);
- } catch (AndroidLocationException e) {
- mSdkLog.error(e, "Unable to read AVDs: " + e.getMessage()); //$NON-NLS-1$
-
- // Note: we used to continue here, but the thing is that
- // mAvdManager==null so nothing is really going to work as
- // expected. Let's just display an error later in checkIfInitFailed()
- // and abort right there. This step is just too early in the SWT
- // setup process to display a message box yet.
-
- mAvdManagerInitError = e;
- }
-
- // notify listeners.
- broadcastOnSdkReload();
- }
-
- /**
- * Initializes the {@link SettingsController}
- * Extracted so that we can override this in unit tests.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SettingsController initSettingsController() {
- SettingsController settingsController = new SettingsController(mSdkLog);
- settingsController.registerOnChangedListener(new OnChangedListener() {
- @Override
- public void onSettingsChanged(
- SettingsController controller,
- SettingsController.Settings oldSettings) {
-
- // Reset the download cache if it doesn't match the right strategy.
- // The cache instance gets lazily recreated later in getDownloadCache().
- if (mDownloadCache != null) {
- if (controller.getSettings().getUseDownloadCache() &&
- mDownloadCache.getStrategy() != DownloadCache.Strategy.FRESH_CACHE) {
- mDownloadCache = null;
- } else if (!controller.getSettings().getUseDownloadCache() &&
- mDownloadCache.getStrategy() != DownloadCache.Strategy.DIRECT) {
- mDownloadCache = null;
- }
- }
- }
- });
- return settingsController;
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void setSdkManager(SdkManager sdkManager) {
- mSdkManager = sdkManager;
- }
-
- /**
- * Reloads the SDK content (targets).
- * <p/>
- * This also reloads the AVDs in case their status changed.
- * <p/>
- * This does not notify the listeners ({@link ISdkChangeListener}).
- */
- public void reloadSdk() {
- // reload SDK
- mSdkManager.reloadSdk(mSdkLog);
-
- // reload AVDs
- if (mAvdManager != null) {
- try {
- mAvdManager.reloadAvds(mSdkLog);
- } catch (AndroidLocationException e) {
- // FIXME
- }
- }
-
- mLocalSdkParser.clearPackages();
-
- // notify listeners
- broadcastOnSdkReload();
- }
-
- /**
- * Reloads the AVDs.
- * <p/>
- * This does not notify the listeners.
- */
- public void reloadAvds() {
- // reload AVDs
- if (mAvdManager != null) {
- try {
- mAvdManager.reloadAvds(mSdkLog);
- } catch (AndroidLocationException e) {
- mSdkLog.error(e, null);
- }
- }
- }
-
- /**
- * Sets up the default sources: <br/>
- * - the default google SDK repository, <br/>
- * - the user sources from prefs <br/>
- * - the extra repo URLs from the environment, <br/>
- * - and finally the extra user repo URLs from the environment.
- */
- public void setupDefaultSources() {
- SdkSources sources = getSources();
-
- // Load the conventional sources.
- // For testing, the env var can be set to replace the default root download URL.
- // It must end with a / and its the location where the updater will look for
- // the repository.xml, addons_list.xml and such files.
-
- String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
- if (baseUrl == null || baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
- baseUrl = SdkRepoConstants.URL_GOOGLE_SDK_SITE;
- }
-
- sources.add(SdkSourceCategory.ANDROID_REPO,
- new SdkRepoSource(baseUrl,
- SdkSourceCategory.ANDROID_REPO.getUiName()));
-
- // Load user sources (this will also notify change listeners but this operation is
- // done early enough that there shouldn't be any anyway.)
- sources.loadUserAddons(getSdkLog());
- }
-
- /**
- * Returns the list of installed packages, parsing them if this has not yet been done.
- * <p/>
- * The package list is cached in the {@link LocalSdkParser} and will be reset when
- * {@link #reloadSdk()} is invoked.
- */
- public Package[] getInstalledPackages(ITaskMonitor monitor) {
- LocalSdkParser parser = getLocalSdkParser();
-
- Package[] packages = parser.getPackages();
-
- if (packages == null) {
- // load on demand the first time
- packages = parser.parseSdk(getOsSdkRoot(), getSdkManager(), monitor);
- }
-
- return packages;
- }
- /**
- * Install the list of given {@link Archive}s. This is invoked by the user selecting some
- * packages in the remote page and then clicking "install selected".
- *
- * @param archives The archives to install. Incompatible ones will be skipped.
- * @param flags Optional flags for the installer, such as {@link #NO_TOOLS_MSG}.
- * @return A list of archives that have been installed. Can be empty but not null.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected List<Archive> installArchives(final List<ArchiveInfo> archives, final int flags) {
- if (mTaskFactory == null) {
- throw new IllegalArgumentException("Task Factory is null");
- }
-
- // this will accumulate all the packages installed.
- final List<Archive> newlyInstalledArchives = new ArrayList<Archive>();
-
- final boolean forceHttp = getSettingsController().getSettings().getForceHttp();
-
- // sort all archives based on their dependency level.
- Collections.sort(archives, new InstallOrderComparator());
-
- mTaskFactory.start("Installing Archives", new ITask() {
- @Override
- public void run(ITaskMonitor monitor) {
-
- final int progressPerArchive = 2 * ArchiveInstaller.NUM_MONITOR_INC;
- monitor.setProgressMax(1 + archives.size() * progressPerArchive);
- monitor.setDescription("Preparing to install archives");
-
- boolean installedAddon = false;
- boolean installedTools = false;
- boolean installedPlatformTools = false;
- boolean preInstallHookInvoked = false;
-
- // Mark all current local archives as already installed.
- HashSet<Archive> installedArchives = new HashSet<Archive>();
- for (Package p : getInstalledPackages(monitor.createSubMonitor(1))) {
- for (Archive a : p.getArchives()) {
- installedArchives.add(a);
- }
- }
-
- int numInstalled = 0;
- nextArchive: for (ArchiveInfo ai : archives) {
- Archive archive = ai.getNewArchive();
- if (archive == null) {
- // This is not supposed to happen.
- continue nextArchive;
- }
-
- int nextProgress = monitor.getProgress() + progressPerArchive;
- try {
- if (monitor.isCancelRequested()) {
- break;
- }
-
- ArchiveInfo[] adeps = ai.getDependsOn();
- if (adeps != null) {
- for (ArchiveInfo adep : adeps) {
- Archive na = adep.getNewArchive();
- if (na == null) {
- // This archive depends on a missing archive.
- // We shouldn't get here.
- // Skip it.
- monitor.log("Skipping '%1$s'; it depends on a missing package.",
- archive.getParentPackage().getShortDescription());
- continue nextArchive;
- } else if (!installedArchives.contains(na)) {
- // This archive depends on another one that was not installed.
- // We shouldn't get here.
- // Skip it.
- monitor.logError("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
- archive.getParentPackage().getShortDescription(),
- adep.getShortDescription());
- continue nextArchive;
- }
- }
- }
-
- if (!preInstallHookInvoked) {
- preInstallHookInvoked = true;
- broadcastPreInstallHook();
- }
-
- ArchiveInstaller installer = createArchiveInstaler();
- if (installer.install(ai,
- mOsSdkRoot,
- forceHttp,
- mSdkManager,
- getDownloadCache(),
- monitor)) {
- // We installed this archive.
- newlyInstalledArchives.add(archive);
- installedArchives.add(archive);
- numInstalled++;
-
- // If this package was replacing an existing one, the old one
- // is no longer installed.
- installedArchives.remove(ai.getReplaced());
-
- // Check if we successfully installed a platform-tool or add-on package.
- if (archive.getParentPackage() instanceof AddonPackage) {
- installedAddon = true;
- } else if (archive.getParentPackage() instanceof ToolPackage) {
- installedTools = true;
- } else if (archive.getParentPackage() instanceof PlatformToolPackage) {
- installedPlatformTools = true;
- }
- }
-
- } catch (Throwable t) {
- // Display anything unexpected in the monitor.
- String msg = t.getMessage();
- if (msg != null) {
- msg = String.format("Unexpected Error installing '%1$s': %2$s: %3$s",
- archive.getParentPackage().getShortDescription(),
- t.getClass().getCanonicalName(), msg);
- } else {
- // no error info? get the stack call to display it
- // At least that'll give us a better bug report.
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- t.printStackTrace(new PrintStream(baos));
-
- msg = String.format("Unexpected Error installing '%1$s'\n%2$s",
- archive.getParentPackage().getShortDescription(),
- baos.toString());
- }
-
- monitor.log( "%1$s", msg); //$NON-NLS-1$
- mSdkLog.error(t, "%1$s", msg); //$NON-NLS-1$
- } finally {
-
- // Always move the progress bar to the desired position.
- // This allows internal methods to not have to care in case
- // they abort early
- monitor.incProgress(nextProgress - monitor.getProgress());
- }
- }
-
- if (installedAddon) {
- // Update the USB vendor ids for adb
- try {
- mSdkManager.updateAdb();
- monitor.log("Updated ADB to support the USB devices declared in the SDK add-ons.");
- } catch (Exception e) {
- mSdkLog.error(e, "Update ADB failed");
- monitor.logError("failed to update adb to support the USB devices declared in the SDK add-ons.");
- }
- }
-
- if (preInstallHookInvoked) {
- broadcastPostInstallHook();
- }
-
- if (installedAddon || installedPlatformTools) {
- // We need to restart ADB. Actually since we don't know if it's even
- // running, maybe we should just kill it and not start it.
- // Note: it turns out even under Windows we don't need to kill adb
- // before updating the tools folder, as adb.exe is (surprisingly) not
- // locked.
-
- askForAdbRestart(monitor);
- }
-
- if (installedTools) {
- notifyToolsNeedsToBeRestarted(flags);
- }
-
- if (numInstalled == 0) {
- monitor.setDescription("Done. Nothing was installed.");
- } else {
- monitor.setDescription("Done. %1$d %2$s installed.",
- numInstalled,
- numInstalled == 1 ? "package" : "packages");
-
- //notify listeners something was installed, so that they can refresh
- reloadSdk();
- }
- }
- });
-
- return newlyInstalledArchives;
- }
-
- /**
- * A comparator to sort all the {@link ArchiveInfo} based on their
- * dependency level. This forces the installer to install first all packages
- * with no dependency, then those with one level of dependency, etc.
- */
- private static class InstallOrderComparator implements Comparator<ArchiveInfo> {
-
- private final Map<ArchiveInfo, Integer> mOrders = new HashMap<ArchiveInfo, Integer>();
-
- @Override
- public int compare(ArchiveInfo o1, ArchiveInfo o2) {
- int n1 = getDependencyOrder(o1);
- int n2 = getDependencyOrder(o2);
-
- return n1 - n2;
- }
-
- private int getDependencyOrder(ArchiveInfo ai) {
- if (ai == null) {
- return 0;
- }
-
- // reuse cached value, if any
- Integer cached = mOrders.get(ai);
- if (cached != null) {
- return cached.intValue();
- }
-
- ArchiveInfo[] deps = ai.getDependsOn();
- if (deps == null) {
- return 0;
- }
-
- // compute dependencies, recursively
- int n = deps.length;
-
- for (ArchiveInfo dep : deps) {
- n += getDependencyOrder(dep);
- }
-
- // cache it
- mOrders.put(ai, Integer.valueOf(n));
-
- return n;
- }
-
- }
-
- /**
- * Attempts to restart ADB.
- * <p/>
- * If the "ask before restart" setting is set (the default), prompt the user whether
- * now is a good time to restart ADB.
- */
- protected void askForAdbRestart(ITaskMonitor monitor) {
- // Restart ADB if we don't need to ask.
- if (!getSettingsController().getSettings().getAskBeforeAdbRestart()) {
- AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor);
- adb.stopAdb();
- adb.startAdb();
- }
- }
-
- protected void notifyToolsNeedsToBeRestarted(int flags) {
-
- String msg = null;
- if ((flags & TOOLS_MSG_UPDATED_FROM_ADT) == TOOLS_MSG_UPDATED_FROM_ADT) {
- msg =
- "The Android SDK and AVD Manager that you are currently using has been updated. " +
- "Please also run Eclipse > Help > Check for Updates to see if the Android " +
- "plug-in needs to be updated.";
-
- } else if ((flags & TOOLS_MSG_UPDATED_FROM_SDKMAN) == TOOLS_MSG_UPDATED_FROM_SDKMAN) {
- msg =
- "The Android SDK and AVD Manager that you are currently using has been updated. " +
- "It is recommended that you now close the manager window and re-open it. " +
- "If you use Eclipse, please run Help > Check for Updates to see if the Android " +
- "plug-in needs to be updated.";
- } else if ((flags & NO_TOOLS_MSG) == NO_TOOLS_MSG) {
- return;
- }
- mSdkLog.info("%s", msg); //$NON-NLS-1$
- }
-
- /**
- * Fetches all archives available on the known remote sources.
- *
- * Used by {@link UpdaterData#listRemotePackages_NoGUI} and
- * {@link UpdaterData#updateOrInstallAll_NoGUI}.
- *
- * @param includeAll True to list and install all packages, including obsolete ones.
- * @return A list of potential {@link ArchiveInfo} to install.
- */
- private List<ArchiveInfo> getRemoteArchives_NoGUI(boolean includeAll) {
- refreshSources(true);
- getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
-
- List<ArchiveInfo> archives;
- SdkUpdaterLogic ul = new SdkUpdaterLogic(this);
-
- if (includeAll) {
- archives = ul.getAllRemoteArchives(
- getSources(),
- getLocalSdkParser().getPackages(),
- includeAll);
-
- } else {
- archives = ul.computeUpdates(
- null /*selectedArchives*/,
- getSources(),
- getLocalSdkParser().getPackages(),
- includeAll);
-
- ul.addNewPlatforms(
- archives,
- getSources(),
- getLocalSdkParser().getPackages(),
- includeAll);
- }
-
- Collections.sort(archives);
- return archives;
- }
-
- /**
- * Lists remote packages available for install using
- * {@link UpdaterData#updateOrInstallAll_NoGUI}.
- *
- * @param includeAll True to list and install all packages, including obsolete ones.
- * @param extendedOutput True to display more details on each package.
- */
- public void listRemotePackages_NoGUI(boolean includeAll, boolean extendedOutput) {
-
- List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);
-
- mSdkLog.info("Packages available for installation or update: %1$d\n", archives.size());
-
- int index = 1;
- for (ArchiveInfo ai : archives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p != null) {
- if (extendedOutput) {
- mSdkLog.info("----------\n");
- mSdkLog.info("id: %1$d or \"%2$s\"\n", index, p.installId());
- mSdkLog.info(" Type: %1$s\n",
- p.getClass().getSimpleName().replaceAll("Package", "")); //$NON-NLS-1$ //$NON-NLS-2$
- String desc = LineUtil.reformatLine(" Desc: %s\n",
- p.getLongDescription());
- mSdkLog.info("%s", desc); //$NON-NLS-1$
- } else {
- mSdkLog.info("%1$ 4d- %2$s\n",
- index,
- p.getShortDescription());
- }
- index++;
- }
- }
- }
- }
-
- /**
- * Tries to update all the *existing* local packages.
- * This version *requires* to be run with a GUI.
- * <p/>
- * There are two modes of operation:
- * <ul>
- * <li>If selectedArchives is null, refreshes all sources, compares the available remote
- * packages with the current local ones and suggest updates to be done to the user (including
- * new platforms that the users doesn't have yet).
- * <li>If selectedArchives is not null, this represents a list of archives/packages that
- * the user wants to install or update, so just process these.
- * </ul>
- *
- * @param selectedArchives The list of remote archives to consider for the update.
- * This can be null, in which case a list of remote archive is fetched from all
- * available sources.
- * @param includeObsoletes True if obsolete packages should be used when resolving what
- * to update.
- * @param flags Optional flags for the installer, such as {@link #NO_TOOLS_MSG}.
- * @return A list of archives that have been installed. Can be null if nothing was done.
- */
- public List<Archive> updateOrInstallAll_WithGUI(
- Collection<Archive> selectedArchives,
- boolean includeObsoletes,
- int flags) {
- // FIXME revisit this logic. This is just an transitional implementation
- // while I refactor the way the sdk manager works internally.
-
- SdkUpdaterLogic ul = new SdkUpdaterLogic(this);
- List<ArchiveInfo> archives = ul.computeUpdates(
- selectedArchives,
- getSources(),
- getLocalSdkParser().getPackages(),
- includeObsoletes);
-
- if (selectedArchives == null) {
- getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
- ul.addNewPlatforms(
- archives,
- getSources(),
- getLocalSdkParser().getPackages(),
- includeObsoletes);
- }
-
- Collections.sort(archives);
-
- if (!archives.isEmpty()) {
- return installArchives(archives, flags);
- }
- return null;
- }
-
- /**
- * Tries to update all the *existing* local packages.
- * This version is intended to run without a GUI and
- * only outputs to the current {@link ILogger}.
- *
- * @param pkgFilter A list of {@link SdkRepoConstants#NODES} or {@link Package#installId()}
- * or package indexes to limit the packages we can update or install.
- * A null or empty list means to update everything possible.
- * @param includeAll True to list and install all packages, including obsolete ones.
- * @param dryMode True to check what would be updated/installed but do not actually
- * download or install anything.
- * @param acceptLicense SDK licenses to automatically accept.
- * @return A list of archives that have been installed. Can be null if nothing was done.
- * @deprecated Use {@link #updateOrInstallAll_NoGUI(java.util.Collection, boolean, boolean, String, boolean)}
- * instead
- */
- @Deprecated
- public List<Archive> updateOrInstallAll_NoGUI(
- Collection<String> pkgFilter,
- boolean includeAll,
- boolean dryMode,
- String acceptLicense) {
- return updateOrInstallAll_NoGUI(pkgFilter, includeAll, dryMode, acceptLicense, false);
- }
-
- /**
- * Tries to update all the *existing* local packages.
- * This version is intended to run without a GUI and
- * only outputs to the current {@link ILogger}.
- *
- * @param pkgFilter A list of {@link SdkRepoConstants#NODES} or {@link Package#installId()}
- * or package indexes to limit the packages we can update or install.
- * A null or empty list means to update everything possible.
- * @param includeAll True to list and install all packages, including obsolete ones.
- * @param dryMode True to check what would be updated/installed but do not actually
- * download or install anything.
- * @param acceptLicense SDK licenses to automatically accept.
- * @param includeDependencies If true, also include any required dependencies
- * @return A list of archives that have been installed. Can be null if nothing was done.
- */
- public List<Archive> updateOrInstallAll_NoGUI(
- Collection<String> pkgFilter,
- boolean includeAll,
- boolean dryMode,
- String acceptLicense,
- boolean includeDependencies) {
-
- List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);
-
- // Filter the selected archives to only keep the ones matching the filter
- if (pkgFilter != null && !pkgFilter.isEmpty() && archives != null && !archives.isEmpty()) {
- // Map filter types to an SdkRepository Package type,
- // e.g. create a map "platform" => PlatformPackage.class
- HashMap<String, Class<? extends Package>> pkgMap =
- new HashMap<String, Class<? extends Package>>();
-
- mapFilterToPackageClass(pkgMap, SdkRepoConstants.NODES);
- mapFilterToPackageClass(pkgMap, SdkAddonConstants.NODES);
-
- // Prepare a map install-id => package instance
- HashMap<String, Package> installIdMap = new HashMap<String, Package>();
- for (ArchiveInfo ai : archives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p != null) {
- String iid = p.installId().toLowerCase(Locale.US);
- if (iid != null && !iid.isEmpty() && !installIdMap.containsKey(iid)) {
- installIdMap.put(iid, p);
- }
- }
- }
- }
-
- // Now intersect this with the pkgFilter requested by the user, in order to
- // only keep the classes that the user wants to install.
- // We also create a set with the package indices requested by the user
- // and a set of install-ids requested by the user.
-
- HashSet<Class<? extends Package>> userFilteredClasses =
- new HashSet<Class<? extends Package>>();
- SparseIntArray userFilteredIndices = new SparseIntArray();
- Set<String> userFilteredInstallIds = new HashSet<String>();
-
- for (String iid : pkgFilter) {
- // The install-id is not case-sensitive.
- iid = iid.toLowerCase(Locale.US);
-
- if (installIdMap.containsKey(iid)) {
- userFilteredInstallIds.add(iid);
-
- } else if (iid.replaceAll("[0-9]+", "").isEmpty()) {//$NON-NLS-1$ //$NON-NLS-2$
- // An all-digit number is a package index requested by the user.
- int index = Integer.parseInt(iid);
- userFilteredIndices.put(index, index);
-
- } else if (pkgMap.containsKey(iid)) {
- userFilteredClasses.add(pkgMap.get(iid));
-
- } else {
- // This should not happen unless there's a mismatch in the package map.
- mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", iid);
- }
- }
-
- // we don't need the maps anymore
- pkgMap = null;
- installIdMap = null;
-
- // Now filter the remote archives list to keep:
- // - any package which class matches userFilteredClasses
- // - any package index which matches userFilteredIndices
- // - any package install id which matches userFilteredInstallIds
-
- int index = 1;
- for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
- boolean keep = false;
- ArchiveInfo ai = it.next();
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p != null) {
- if (userFilteredInstallIds.contains(p.installId().toLowerCase(Locale.US)) ||
- userFilteredClasses.contains(p.getClass()) ||
- userFilteredIndices.get(index) > 0) {
- keep = true;
- }
-
- index++;
- }
- }
-
- if (!keep) {
- it.remove();
- }
- }
-
- if (archives.isEmpty()) {
- mSdkLog.info(LineUtil.reflowLine(
- "Warning: The package filter removed all packages. There is nothing to install.\nPlease consider trying to update again without a package filter.\n"));
- return null;
- }
- }
-
- if (archives != null && !archives.isEmpty()) {
- if (includeDependencies) {
- List<ArchiveInfo> dependencies = getDependencies(archives);
- if (!dependencies.isEmpty()) {
- List<ArchiveInfo> combined = Lists.newArrayList();
- combined.addAll(dependencies);
- combined.addAll(archives);
- archives = combined;
- }
- }
- if (dryMode) {
- mSdkLog.info("Packages selected for install:\n");
- for (ArchiveInfo ai : archives) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p != null) {
- mSdkLog.info("- %1$s\n", p.getShortDescription());
- }
- }
- }
- mSdkLog.info("\nDry mode is on so nothing is actually being installed.\n");
- } else {
- if (acceptLicense(archives, acceptLicense, 100 /* numRetries */)) {
- return installArchives(archives, NO_TOOLS_MSG);
- }
- }
- } else {
- mSdkLog.info("There is nothing to install or update.\n");
- }
-
- return null;
- }
-
- /**
- * Computes the transitive dependencies of the given list of archives. This will only
- * include dependencies that also need to be installed, not satisfied dependencies.
- */
- private static List<ArchiveInfo> getDependencies(@NonNull List<ArchiveInfo> archives) {
- List<ArchiveInfo> dependencies = Lists.newArrayList();
- for (ArchiveInfo archive : archives) {
- addDependencies(dependencies, archive, Sets.<ArchiveInfo>newHashSet());
- }
- return dependencies;
- }
-
- private static void addDependencies(@NonNull List<ArchiveInfo> dependencies,
- @NonNull ArchiveInfo archive,
- @NonNull Set<ArchiveInfo> visited) {
- if (visited.contains(archive)) {
- return;
- }
- visited.add(archive);
-
- ArchiveInfo[] dependsOn = archive.getDependsOn();
- if (dependsOn != null) {
- for (ArchiveInfo dependency : dependsOn) {
- if (!dependencies.contains(dependency)) {
- dependencies.add(dependency);
- addDependencies(dependencies, dependency, visited);
- }
- }
- }
- }
-
- /**
- * Validates that all archive licenses are accepted.
- * <p/>
- * There are 2 cases: <br/>
- * - When {@code acceptLicenses} is given, the licenses specified are automatically
- * accepted and all those not specified are automatically rejected. <br/>
- * - When {@code acceptLicenses} is empty or null, licenses are collected and there's
- * an input prompt on StdOut to ask a yes/no question. To output, this uses the
- * current {@link #mSdkLog} which should be configured to send
- * {@link ILogger#info(String, Object...)} directly to {@link System#out}. <br/>
- *
- * Finally only accepted licenses are kept in the archive list.
- *
- * @param archives The archives to validate.
- * @param acceptLicenseIds A comma-separated list of licenses ids already approved.
- * @param numRetries The number of times the command-line will ask to accept a given
- * license when the input doesn't match the expected y/n/yes/no answer.
- * Use 0 for infinite. Useful for unit-tests. Once the number of retries
- * is reached, the license is assumed as rejected.
- * @return True if there are any archives left to install.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- boolean acceptLicense(
- List<ArchiveInfo> archives,
- String acceptLicenseIds,
- final int numRetries) {
- TreeSet<String> acceptedLids = new TreeSet<String>();
- if (acceptLicenseIds != null) {
- acceptedLids.addAll(Arrays.asList(acceptLicenseIds.split(","))); //$NON-NLS-1$
- }
- boolean automated = !acceptedLids.isEmpty();
-
- TreeSet<String> rejectedLids = new TreeSet<String>();
- TreeMap<String, License> lidToAccept = new TreeMap<String, License>();
- TreeMap<String, List<String>> lidPkgNames = new TreeMap<String, List<String>>();
-
- // Find the licenses needed. Include those already accepted.
- for (ArchiveInfo ai : archives) {
- License lic = getArchiveInfoLicense(ai);
- if (lic == null) {
- continue;
- }
- String lid = getLicenseId(lic);
- if (!acceptedLids.contains(lid)) {
- if (automated) {
- // Automatically reject those not already accepted
- rejectedLids.add(lid);
- } else {
- // Queue it to ask for it to be accepted
- lidToAccept.put(lid, lic);
- List<String> list = lidPkgNames.get(lid);
- if (list == null) {
- list = new ArrayList<String>();
- lidPkgNames.put(lid, list);
- }
- list.add(ai.getShortDescription());
- }
- }
- }
-
- // Ask for each license that needs to be asked manually for confirmation
- nextEntry: for (Map.Entry<String, License> entry : lidToAccept.entrySet()) {
- String lid = entry.getKey();
- License lic = entry.getValue();
- mSdkLog.info("-------------------------------\n");
- mSdkLog.info("License id: %1$s\n", lid);
- mSdkLog.info("Used by: \n - %1$s\n",
- Joiner.on("\n - ").skipNulls().join(lidPkgNames.get(lid)));
- mSdkLog.info("-------------------------------\n\n");
- mSdkLog.info("%1$s\n", lic.getLicense());
-
- int retries = numRetries;
- tryAgain: while(true) {
- try {
- mSdkLog.info("Do you accept the license '%1$s' [y/n]: ", lid);
-
- byte[] buffer = new byte[256];
- if (mSdkLog instanceof IReaderLogger) {
- ((IReaderLogger) mSdkLog).readLine(buffer);
- } else {
- System.in.read(buffer);
- }
- mSdkLog.info("\n");
-
- String reply = new String(buffer, Charsets.UTF_8);
- reply = reply.trim().toLowerCase(Locale.US);
-
- if ("y".equals(reply) || "yes".equals(reply)) {
- acceptedLids.add(lid);
- continue nextEntry;
-
- } else if ("n".equals(reply) || "no".equals(reply)) {
- break tryAgain;
-
- } else {
- mSdkLog.info("Unknown response '%1$s'.\n", reply);
- if (--retries == 0) {
- mSdkLog.info("Max number of retries exceeded. Rejecting '%1$s'\n", lid);
- break tryAgain;
- }
- continue tryAgain;
- }
-
- } catch (IOException e) {
- // Panic. Don't install anything.
- e.printStackTrace();
- return false;
- }
- }
- rejectedLids.add(lid);
- }
-
- // Finally remove all archive which license is rejected or not accepted.
- for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
- ArchiveInfo ai = it.next();
- License lic = getArchiveInfoLicense(ai);
- if (lic == null) {
- continue;
- }
- String lid = getLicenseId(lic);
- if (rejectedLids.contains(lid) || !acceptedLids.contains(lid)) {
- mSdkLog.info("Package %1$s not installed due to rejected license '%2$s'.\n",
- ai.getShortDescription(),
- lid);
- it.remove();
- }
- }
-
-
- return !archives.isEmpty();
- }
-
- private License getArchiveInfoLicense(ArchiveInfo ai) {
- Archive a = ai.getNewArchive();
- if (a != null) {
- Package p = a.getParentPackage();
- if (p != null) {
- License lic = p.getLicense();
- if (lic != null &&
- lic.getLicenseRef() != null &&
- !lic.getLicense().isEmpty() &&
- lic.getLicense() != null &&
- !lic.getLicense().isEmpty()) {
- return lic;
- }
- }
- }
-
- return null;
- }
-
- private String getLicenseId(License lic) {
- return String.format("%1$s-%2$08x", //$NON-NLS-1$
- lic.getLicenseRef(),
- lic.getLicense().hashCode());
- }
-
- @SuppressWarnings("unchecked")
- private void mapFilterToPackageClass(
- HashMap<String, Class<? extends Package>> inOutPkgMap,
- String[] nodes) {
-
- // Automatically find the classes matching the node names
- ClassLoader classLoader = getClass().getClassLoader();
- String basePackage = Package.class.getPackage().getName();
-
- for (String node : nodes) {
- // Capitalize the name
- String name = node.substring(0, 1).toUpperCase() + node.substring(1);
-
- // We can have one dash at most in a name. If it's present, we'll try
- // with the dash or with the next letter capitalized.
- int dash = name.indexOf('-');
- if (dash > 0) {
- name = name.replaceFirst("-", "");
- }
-
- for (int alternatives = 0; alternatives < 2; alternatives++) {
-
- String fqcn = basePackage + '.' + name + "Package"; //$NON-NLS-1$
- try {
- Class<? extends Package> clazz =
- (Class<? extends Package>) classLoader.loadClass(fqcn);
- if (clazz != null) {
- inOutPkgMap.put(node, clazz);
- continue;
- }
- } catch (Throwable ignore) {
- }
-
- if (alternatives == 0 && dash > 0) {
- // Try an alternative where the next letter after the dash
- // is converted to an upper case.
- name = name.substring(0, dash) +
- name.substring(dash, dash + 1).toUpperCase() +
- name.substring(dash + 1);
- } else {
- break;
- }
- }
- }
- }
-
- /**
- * Refresh all sources. This is invoked either internally (reusing an existing monitor)
- * or as a UI callback on the remote page "Refresh" button (in which case the monitor is
- * null and a new task should be created.)
- *
- * @param forceFetching When true, load sources that haven't been loaded yet.
- * When false, only refresh sources that have been loaded yet.
- */
- public void refreshSources(final boolean forceFetching) {
- assert mTaskFactory != null;
-
- final boolean forceHttp = getSettingsController().getSettings().getForceHttp();
-
- mTaskFactory.start("Refresh Sources", new ITask() {
- @Override
- public void run(ITaskMonitor monitor) {
-
- getPackageLoader().loadRemoteAddonsList(monitor);
-
- SdkSource[] sources = getSources().getAllSources();
- monitor.setDescription("Refresh Sources");
- monitor.setProgressMax(monitor.getProgress() + sources.length);
- for (SdkSource source : sources) {
- if (forceFetching ||
- source.getPackages() != null ||
- source.getFetchError() != null) {
- source.load(getDownloadCache(), monitor.createSubMonitor(1), forceHttp);
- }
- monitor.incProgress(1);
- }
- }
- });
- }
-
- /**
- * Safely invoke all the registered {@link ISdkChangeListener#onSdkLoaded()}.
- * This can be called from any thread.
- */
- public void broadcastOnSdkLoaded() {
- if (!mListeners.isEmpty()) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- for (ISdkChangeListener listener : mListeners) {
- try {
- listener.onSdkLoaded();
- } catch (Throwable t) {
- mSdkLog.error(t, null);
- }
- }
- }
- });
- }
- }
-
- /**
- * Safely invoke all the registered {@link ISdkChangeListener#onSdkReload()}.
- * This can be called from any thread.
- */
- private void broadcastOnSdkReload() {
- if (!mListeners.isEmpty()) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- for (ISdkChangeListener listener : mListeners) {
- try {
- listener.onSdkReload();
- } catch (Throwable t) {
- mSdkLog.error(t, null);
- }
- }
- }
- });
- }
- }
-
- /**
- * Safely invoke all the registered {@link ISdkChangeListener#preInstallHook()}.
- * This can be called from any thread.
- */
- private void broadcastPreInstallHook() {
- if (!mListeners.isEmpty()) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- for (ISdkChangeListener listener : mListeners) {
- try {
- listener.preInstallHook();
- } catch (Throwable t) {
- mSdkLog.error(t, null);
- }
- }
- }
- });
- }
- }
-
- /**
- * Safely invoke all the registered {@link ISdkChangeListener#postInstallHook()}.
- * This can be called from any thread.
- */
- private void broadcastPostInstallHook() {
- if (!mListeners.isEmpty()) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- for (ISdkChangeListener listener : mListeners) {
- try {
- listener.postInstallHook();
- } catch (Throwable t) {
- mSdkLog.error(t, null);
- }
- }
- }
- });
- }
- }
-
- /**
- * Internal helper to return a new {@link ArchiveInstaller}.
- * This allows us to override the installer for unit-testing.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected ArchiveInstaller createArchiveInstaler() {
- return new ArchiveInstaller();
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/io/FileOp.java b/base/sdklib/src/main/java/com/android/sdklib/io/FileOp.java
deleted file mode 100755
index fe29e8a..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/io/FileOp.java
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.io;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.google.common.io.Closer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.regex.Pattern;
-
-
-/**
- * Wraps some common {@link File} operations on files and folders.
- * <p/>
- * This makes it possible to override/mock/stub some file operations in unit tests.
- */
-public class FileOp implements IFileOp {
-
- public static final File[] EMPTY_FILE_ARRAY = new File[0];
-
- /**
- * Reflection method for File.setExecutable(boolean, boolean). Only present in Java 6.
- */
- private static Method sFileSetExecutable = null;
-
- /**
- * Parameters to call File.setExecutable through reflection.
- */
- private static final Object[] sFileSetExecutableParams = new Object[] {
- Boolean.TRUE, Boolean.FALSE };
-
- // static initialization of sFileSetExecutable.
- static {
- try {
- sFileSetExecutable = File.class.getMethod("setExecutable", //$NON-NLS-1$
- boolean.class, boolean.class);
-
- } catch (SecurityException e) {
- // do nothing we'll use chmod instead
- } catch (NoSuchMethodException e) {
- // do nothing we'll use chmod instead
- }
- }
-
- /**
- * Appends the given {@code segments} to the {@code base} file.
- *
- * @param base A base file, non-null.
- * @param segments Individual folder or filename segments to append to the base file.
- * @return A new file representing the concatenation of the base path with all the segments.
- */
- public static File append(@NonNull File base, @NonNull String...segments) {
- for (String segment : segments) {
- base = new File(base, segment);
- }
- return base;
- }
-
- /**
- * Appends the given {@code segments} to the {@code base} file.
- *
- * @param base A base file path, non-empty and non-null.
- * @param segments Individual folder or filename segments to append to the base path.
- * @return A new file representing the concatenation of the base path with all the segments.
- */
- public static File append(@NonNull String base, @NonNull String...segments) {
- return append(new File(base), segments);
- }
-
- /**
- * Helper to delete a file or a directory.
- * For a directory, recursively deletes all of its content.
- * Files that cannot be deleted right away are marked for deletion on exit.
- * It's ok for the file or folder to not exist at all.
- * The argument can be null.
- */
- @Override
- public void deleteFileOrFolder(@NonNull File fileOrFolder) {
- if (fileOrFolder != null) {
- if (isDirectory(fileOrFolder)) {
- // Must delete content recursively first
- File[] files = fileOrFolder.listFiles();
- if (files != null) {
- for (File item : files) {
- deleteFileOrFolder(item);
- }
- }
- }
-
- // Don't try to delete it if it doesn't exist.
- if (!exists(fileOrFolder)) {
- return;
- }
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- // Trying to delete a resource on windows might fail if there's a file
- // indexer locking the resource. Generally retrying will be enough to
- // make it work.
- //
- // Try for half a second before giving up.
-
- for (int i = 0; i < 5; i++) {
- if (fileOrFolder.delete()) {
- return;
- }
-
- try {
- Thread.sleep(100 /*ms*/);
- } catch (InterruptedException e) {
- // Ignore.
- }
- }
-
- fileOrFolder.deleteOnExit();
-
- } else {
- // On Linux or Mac, just straight deleting it should just work.
-
- if (!fileOrFolder.delete()) {
- fileOrFolder.deleteOnExit();
- }
- }
- }
- }
-
- /**
- * Sets the executable Unix permission (+x) on a file or folder.
- * <p/>
- * This attempts to use File#setExecutable through reflection if
- * it's available.
- * If this is not available, this invokes a chmod exec instead,
- * so there is no guarantee of it being fast.
- * <p/>
- * Caller must make sure to not invoke this under Windows.
- *
- * @param file The file to set permissions on.
- * @throws IOException If an I/O error occurs
- */
- @Override
- public void setExecutablePermission(@NonNull File file) throws IOException {
-
- if (sFileSetExecutable != null) {
- try {
- sFileSetExecutable.invoke(file, sFileSetExecutableParams);
- return;
- } catch (IllegalArgumentException e) {
- // we'll run chmod below
- } catch (IllegalAccessException e) {
- // we'll run chmod below
- } catch (InvocationTargetException e) {
- // we'll run chmod below
- }
- }
-
- Runtime.getRuntime().exec(new String[] {
- "chmod", "+x", file.getAbsolutePath() //$NON-NLS-1$ //$NON-NLS-2$
- });
- }
-
- @Override
- public void setReadOnly(@NonNull File file) {
- file.setReadOnly();
- }
-
- /**
- * Copies a binary file.
- *
- * @param source the source file to copy.
- * @param dest the destination file to write.
- * @throws FileNotFoundException if the source file doesn't exist.
- * @throws IOException if there's a problem reading or writing the file.
- */
- @Override
- public void copyFile(@NonNull File source, @NonNull File dest) throws IOException {
- byte[] buffer = new byte[8192];
-
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try {
- fis = new FileInputStream(source);
- fos = new FileOutputStream(dest);
-
- int read;
- while ((read = fis.read(buffer)) != -1) {
- fos.write(buffer, 0, read);
- }
-
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- }
- }
-
- /**
- * Checks whether 2 binary files are the same.
- *
- * @param file1 the source file to copy
- * @param file2 the destination file to write
- * @throws FileNotFoundException if the source files don't exist.
- * @throws IOException if there's a problem reading the files.
- */
- @Override
- public boolean isSameFile(@NonNull File file1, @NonNull File file2) throws IOException {
-
- if (file1.length() != file2.length()) {
- return false;
- }
-
- FileInputStream fis1 = null;
- FileInputStream fis2 = null;
-
- try {
- fis1 = new FileInputStream(file1);
- fis2 = new FileInputStream(file2);
-
- byte[] buffer1 = new byte[8192];
- byte[] buffer2 = new byte[8192];
-
- int read1;
- while ((read1 = fis1.read(buffer1)) != -1) {
- int read2 = 0;
- while (read2 < read1) {
- int n = fis2.read(buffer2, read2, read1 - read2);
- if (n == -1) {
- break;
- }
- }
-
- if (read2 != read1) {
- return false;
- }
-
- if (!Arrays.equals(buffer1, buffer2)) {
- return false;
- }
- }
- } finally {
- if (fis2 != null) {
- try {
- fis2.close();
- } catch (IOException e) {
- // ignore
- }
- }
- if (fis1 != null) {
- try {
- fis1.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- return true;
- }
-
- /** Invokes {@link File#isFile()} on the given {@code file}. */
- @Override
- public boolean isFile(@NonNull File file) {
- return file.isFile();
- }
-
- /** Invokes {@link File#isDirectory()} on the given {@code file}. */
- @Override
- public boolean isDirectory(@NonNull File file) {
- return file.isDirectory();
- }
-
- /** Invokes {@link File#exists()} on the given {@code file}. */
- @Override
- public boolean exists(@NonNull File file) {
- return file.exists();
- }
-
- /** Invokes {@link File#length()} on the given {@code file}. */
- @Override
- public long length(@NonNull File file) {
- return file.length();
- }
-
- /**
- * Invokes {@link File#delete()} on the given {@code file}.
- * Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
- */
- @Override
- public boolean delete(@NonNull File file) {
- return file.delete();
- }
-
- /** Invokes {@link File#mkdirs()} on the given {@code file}. */
- @Override
- public boolean mkdirs(@NonNull File file) {
- return file.mkdirs();
- }
-
- /**
- * Invokes {@link File#listFiles()} on the given {@code file}.
- * Contrary to the Java API, this returns an empty array instead of null when the
- * directory does not exist.
- */
- @Override
- @NonNull
- public File[] listFiles(@NonNull File file) {
- File[] r = file.listFiles();
- if (r == null) {
- return EMPTY_FILE_ARRAY;
- } else {
- return r;
- }
- }
-
- /** Invokes {@link File#renameTo(File)} on the given files. */
- @Override
- public boolean renameTo(@NonNull File oldFile, @NonNull File newFile) {
- return oldFile.renameTo(newFile);
- }
-
- /** Creates a new {@link OutputStream} for the given {@code file}. */
- @Override
- @NonNull
- public OutputStream newFileOutputStream(@NonNull File file) throws FileNotFoundException {
- return new FileOutputStream(file);
- }
-
- /** Creates a new {@link InputStream} for the given {@code file}. */
- @Override
- @NonNull
- public InputStream newFileInputStream(@NonNull File file) throws FileNotFoundException {
- return new FileInputStream(file);
- }
-
- @Override
- @NonNull
- public Properties loadProperties(@NonNull File file) {
- Properties props = new Properties();
- Closer closer = Closer.create();
- try {
- FileInputStream fis = closer.register(new FileInputStream(file));
- props.load(fis);
- } catch (IOException ignore) {
- } finally {
- try {
- closer.close();
- } catch (IOException e) {
- }
- }
- return props;
- }
-
- @Override
- public void saveProperties(
- @NonNull File file,
- @NonNull Properties props,
- @NonNull String comments) throws IOException {
- Closer closer = Closer.create();
- try {
- OutputStream fos = closer.register(newFileOutputStream(file));
- props.store(fos, comments);
- } catch (Throwable e) {
- throw closer.rethrow(e);
- } finally {
- closer.close();
- }
- }
-
- @Override
- public long lastModified(@NonNull File file) {
- return file.lastModified();
- }
-
- /**
- * Computes a relative path from "toBeRelative" relative to "baseDir".
- *
- * Rule:
- * - let relative2 = makeRelative(path1, path2)
- * - then pathJoin(path1 + relative2) == path2 after canonicalization.
- *
- * Principle:
- * - let base = /c1/c2.../cN/a1/a2../aN
- * - let toBeRelative = /c1/c2.../cN/b1/b2../bN
- * - result is removes the common paths, goes back from aN to cN then to bN:
- * - result = ../..../../1/b2../bN
- *
- * @param baseDir The base directory to be relative to.
- * @param toBeRelative The file or directory to make relative to the base.
- * @return A path that makes toBeRelative relative to baseDir.
- * @throws IOException If drive letters don't match on Windows or path canonicalization fails.
- */
- @NonNull
- public static String makeRelative(@NonNull File baseDir, @NonNull File toBeRelative)
- throws IOException {
- return makeRelativeImpl(
- baseDir.getCanonicalPath(),
- toBeRelative.getCanonicalPath(),
- SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS,
- File.separator);
- }
-
- /**
- * Implementation detail of makeRelative to make it testable
- * Independently of the platform.
- */
- @NonNull
- static String makeRelativeImpl(@NonNull String path1,
- @NonNull String path2,
- boolean isWindows,
- @NonNull String dirSeparator)
- throws IOException {
- if (isWindows) {
- // Check whether both path are on the same drive letter, if any.
- String p1 = path1;
- String p2 = path2;
- char drive1 = (p1.length() >= 2 && p1.charAt(1) == ':') ? p1.charAt(0) : 0;
- char drive2 = (p2.length() >= 2 && p2.charAt(1) == ':') ? p2.charAt(0) : 0;
- if (drive1 != drive2) {
- // Either a mix of UNC vs drive or not the same drives.
- throw new IOException("makeRelative: incompatible drive letters");
- }
- }
-
- String[] segments1 = path1.split(Pattern.quote(dirSeparator));
- String[] segments2 = path2.split(Pattern.quote(dirSeparator));
-
- int len1 = segments1.length;
- int len2 = segments2.length;
- int len = Math.min(len1, len2);
- int start = 0;
- for (; start < len; start++) {
- // On Windows should compare in case-insensitive.
- // Mac & Linux file systems can be both type, although their default
- // is generally to have a case-sensitive file system.
- if (( isWindows && !segments1[start].equalsIgnoreCase(segments2[start])) ||
- (!isWindows && !segments1[start].equals(segments2[start]))) {
- break;
- }
- }
-
- StringBuilder result = new StringBuilder();
- for (int i = start; i < len1; i++) {
- result.append("..").append(dirSeparator);
- }
- while (start < len2) {
- result.append(segments2[start]);
- if (++start < len2) {
- result.append(dirSeparator);
- }
- }
-
- return result.toString();
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java b/base/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java
deleted file mode 100755
index 4ed4ab1..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.io;
-
-import com.android.annotations.NonNull;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Properties;
-
-
-/**
- * Wraps some common {@link File} operations on files and folders.
- * <p/>
- * This makes it possible to override/mock/stub some file operations in unit tests.
- */
-public interface IFileOp {
-
- /**
- * Helper to delete a file or a directory.
- * For a directory, recursively deletes all of its content.
- * Files that cannot be deleted right away are marked for deletion on exit.
- * It's ok for the file or folder to not exist at all.
- * The argument can be null.
- */
- void deleteFileOrFolder(@NonNull File fileOrFolder);
-
- /**
- * Sets the executable Unix permission (+x) on a file or folder.
- * <p/>
- * This attempts to use File#setExecutable through reflection if
- * it's available.
- * If this is not available, this invokes a chmod exec instead,
- * so there is no guarantee of it being fast.
- * <p/>
- * Caller must make sure to not invoke this under Windows.
- *
- * @param file The file to set permissions on.
- * @throws IOException If an I/O error occurs
- */
- void setExecutablePermission(@NonNull File file) throws IOException;
-
- /**
- * Sets the file or directory as read-only.
- *
- * @param file The file or directory to set permissions on.
- */
- void setReadOnly(@NonNull File file);
-
- /**
- * Copies a binary file.
- *
- * @param source the source file to copy.
- * @param dest the destination file to write.
- * @throws FileNotFoundException if the source file doesn't exist.
- * @throws IOException if there's a problem reading or writing the file.
- */
- void copyFile(@NonNull File source, @NonNull File dest) throws IOException;
-
- /**
- * Checks whether 2 binary files are the same.
- *
- * @param file1 the source file to copy
- * @param file2 the destination file to write
- * @throws FileNotFoundException if the source files don't exist.
- * @throws IOException if there's a problem reading the files.
- */
- boolean isSameFile(@NonNull File file1, @NonNull File file2)
- throws IOException;
-
- /** Invokes {@link File#exists()} on the given {@code file}. */
- boolean exists(@NonNull File file);
-
- /** Invokes {@link File#isFile()} on the given {@code file}. */
- boolean isFile(@NonNull File file);
-
- /** Invokes {@link File#isDirectory()} on the given {@code file}. */
- boolean isDirectory(@NonNull File file);
-
- /** Invokes {@link File#length()} on the given {@code file}. */
- long length(@NonNull File file);
-
- /**
- * Invokes {@link File#delete()} on the given {@code file}.
- * Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
- */
- boolean delete(@NonNull File file);
-
- /** Invokes {@link File#mkdirs()} on the given {@code file}. */
- boolean mkdirs(@NonNull File file);
-
- /**
- * Invokes {@link File#listFiles()} on the given {@code file}.
- * Contrary to the Java API, this returns an empty array instead of null when the
- * directory does not exist.
- */
- @NonNull
- File[] listFiles(@NonNull File file);
-
- /** Invokes {@link File#renameTo(File)} on the given files. */
- boolean renameTo(@NonNull File oldDir, @NonNull File newDir);
-
- /** Creates a new {@link OutputStream} for the given {@code file}. */
- @NonNull
- OutputStream newFileOutputStream(@NonNull File file)
- throws FileNotFoundException;
-
- /** Creates a new {@link InputStream} for the given {@code file}. */
- @NonNull
- InputStream newFileInputStream(@NonNull File file)
- throws FileNotFoundException;
-
- /**
- * Load {@link Properties} from a file. Returns an empty property set on error.
- *
- * @param file A non-null file to load from. File may not exist.
- * @return A new {@link Properties} with the properties loaded from the file,
- * or an empty property set in case of error.
- */
- @NonNull
- Properties loadProperties(@NonNull File file);
-
- /**
- * Saves (write, store) the given {@link Properties} into the given {@link File}.
- *
- * @param file A non-null file to write to.
- * @param props The properties to write.
- * @param comments A non-null description of the properly list, written in the file.
- * @throws IOException if the write operation failed.
- */
- void saveProperties(
- @NonNull File file,
- @NonNull Properties props,
- @NonNull String comments) throws IOException;
-
- /**
- * Returns the lastModified attribute of the file.
- *
- * @see File#lastModified()
- * @param file The non-null file of which to retrieve the lastModified attribute.
- * @return The last-modified attribute of the file, in milliseconds since The Epoch.
- */
- long lastModified(@NonNull File file);
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/FullRevision.java b/base/sdklib/src/main/java/com/android/sdklib/repository/FullRevision.java
deleted file mode 100755
index e71499f..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/FullRevision.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-import com.android.annotations.NonNull;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-
-/**
- * Package multi-part revision number composed of a tuple
- * (major.minor.micro) and an optional preview revision
- * (the lack of a preview number indicates it's not a preview
- * but a final package.)
- *
- * @see MajorRevision
- */
-public class FullRevision implements Comparable<FullRevision> {
-
- public static final int MISSING_MAJOR_REV = 0;
- public static final int IMPLICIT_MINOR_REV = 0;
- public static final int IMPLICIT_MICRO_REV = 0;
- public static final int NOT_A_PREVIEW = 0;
-
- /** Only major revision specified: 1 term */
- protected static final int PRECISION_MAJOR = 1;
- /** Only major and minor revisions specified: 2 terms (x.y) */
- protected static final int PRECISION_MINOR = 2;
- /** Major, minor and micro revisions specified: 3 terms (x.y.z) */
- protected static final int PRECISION_MICRO = 3;
- /** Major, minor, micro and preview revisions specified: 4 terms (x.y.z-rcN) */
- protected static final int PRECISION_PREVIEW = 4;
-
- public static final FullRevision NOT_SPECIFIED = new FullRevision(MISSING_MAJOR_REV);
-
- private static final Pattern FULL_REVISION_PATTERN =
- // 1=major 2=minor 3=micro 4=separator 5=previewType 6=preview
- Pattern.compile("\\s*([0-9]+)(?:\\.([0-9]+)(?:\\.([0-9]+))?)?([\\s-]*)?(?:(rc|alpha|beta)([0-9]+))?\\s*");
-
- protected static final String DEFAULT_SEPARATOR = " ";
-
- private final int mMajor;
- private final int mMinor;
- private final int mMicro;
- private final int mPreview;
- private final String mPreviewSeparator;
- private final PreviewType mPreviewType;
-
- public enum PreviewType {
- ALPHA("alpha"),
- BETA("beta"),
- RC("rc")
- ;
-
- final String name;
-
- PreviewType(String name) {
- this.name = name;
- }
- }
-
- public FullRevision(int major) {
- this(major, IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV);
- }
-
- public FullRevision(int major, int minor, int micro) {
- this(major, minor, micro, NOT_A_PREVIEW);
- }
-
- public FullRevision(int major, int minor, int micro, int preview) {
- this(major, minor, micro, PreviewType.RC, preview, DEFAULT_SEPARATOR);
- }
-
- public FullRevision(int major, int minor, int micro, @NonNull PreviewType previewType,
- int preview, @NonNull String previewSeparator) {
- mMajor = major;
- mMinor = minor;
- mMicro = micro;
- mPreview = preview;
- mPreviewSeparator = previewSeparator;
- mPreviewType = previewType;
- }
-
- public int getMajor() {
- return mMajor;
- }
-
- public int getMinor() {
- return mMinor;
- }
-
- public int getMicro() {
- return mMicro;
- }
-
- @NonNull
- protected String getSeparator() {
- return mPreviewSeparator;
- }
-
- public boolean isPreview() {
- return mPreview > NOT_A_PREVIEW;
- }
-
- public int getPreview() {
- return mPreview;
- }
-
- /**
- * Parses a string of format "major.minor.micro rcPreview" and returns
- * a new {@link FullRevision} for it. All the fields except major are
- * optional.
- * <p/>
- * The parsing is equivalent to the pseudo-BNF/regexp:
- * <pre>
- * Major/Minor/Micro/Preview := [0-9]+
- * Revision := Major ('.' Minor ('.' Micro)? )? \s* ('rc'Preview)?
- * </pre>
- *
- * @param revision A non-null revision to parse.
- * @return A new non-null {@link FullRevision}.
- * @throws NumberFormatException if the parsing failed.
- */
- @NonNull
- public static FullRevision parseRevision(@NonNull String revision)
- throws NumberFormatException {
- return parseRevisionImpl(revision, true /*supportMinorMicro*/, true /*supportPreview*/,
- false /*keepPrevision*/);
- }
-
- @NonNull
- protected static FullRevision parseRevisionImpl(@NonNull String revision,
- boolean supportMinorMicro,
- boolean supportPreview,
- boolean keepPrecision)
- throws NumberFormatException {
- if (revision == null) {
- throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$
- }
-
- Throwable cause = null;
- String error = null;
- try {
- Matcher m = FULL_REVISION_PATTERN.matcher(revision);
- if (m != null && m.matches()) {
- int major = Integer.parseInt(m.group(1));
-
- int minor = IMPLICIT_MINOR_REV;
- int micro = IMPLICIT_MICRO_REV;
- int preview = NOT_A_PREVIEW;
- int precision = PRECISION_MAJOR;
- String previewSeparator = " ";
- PreviewType previewType = PreviewType.RC;
-
- String s = m.group(2);
- if (s != null) {
- if (!supportMinorMicro) {
- error = " -- Minor number not supported"; //$NON-NLS-1$
- } else {
- minor = Integer.parseInt(s);
- precision = PRECISION_MINOR;
- }
- }
-
- s = m.group(3);
- if (s != null) {
- if (!supportMinorMicro) {
- error = " -- Micro number not supported"; //$NON-NLS-1$
- } else {
- micro = Integer.parseInt(s);
- precision = PRECISION_MICRO;
- }
- }
-
- s = m.group(6);
- if (s != null) {
- if (!supportPreview) {
- error = " -- Preview number not supported"; //$NON-NLS-1$
- } else {
- preview = Integer.parseInt(s);
- previewSeparator = m.group(4);
- precision = PRECISION_PREVIEW;
-
- String previewTypeName = m.group(5);
- for (PreviewType pt : PreviewType.values()) {
- if (pt.name.equals(previewTypeName)) {
- previewType = pt;
- break;
- }
- }
- }
- }
-
- if (error == null) {
- if (keepPrecision) {
- return new PreciseRevision(major, minor, micro, preview, precision,
- previewSeparator);
- } else {
- return new FullRevision(major, minor, micro, previewType, preview, previewSeparator);
- }
- }
- }
- } catch (Throwable t) {
- cause = t;
- }
-
- NumberFormatException n = new NumberFormatException(
- "Invalid revision: " //$NON-NLS-1$
- + revision
- + (error == null ? "" : error));
- if (cause != null) {
- n.initCause(cause);
- }
- throw n;
- }
-
- /**
- * Returns the version in a fixed format major.minor.micro
- * with an optional "rc preview#". For example it would
- * return "18.0.0", "18.1.0" or "18.1.2 rc5".
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(mMajor)
- .append('.').append(mMinor)
- .append('.').append(mMicro);
-
- if (mPreview != NOT_A_PREVIEW) {
- sb.append(mPreviewSeparator).append(mPreviewType.name).append(mPreview);
- }
-
- return sb.toString();
- }
-
- /**
- * Returns the version in a dynamic format "major.minor.micro rc#".
- * This is similar to {@link #toString()} except it omits minor, micro
- * or preview versions when they are zero.
- * For example it would return "18 rc1" instead of "18.0.0 rc1",
- * or "18.1 rc2" instead of "18.1.0 rc2".
- */
- public String toShortString() {
- StringBuilder sb = new StringBuilder();
- sb.append(mMajor);
- if (mMinor > 0 || mMicro > 0) {
- sb.append('.').append(mMinor);
- }
- if (mMicro > 0) {
- sb.append('.').append(mMicro);
- }
- if (mPreview != NOT_A_PREVIEW) {
- sb.append(mPreviewSeparator).append(mPreviewType.name).append(mPreview);
- }
-
- return sb.toString();
- }
-
- /**
- * Returns the version number as an integer array, in the form
- * [major, minor, micro] or [major, minor, micro, preview].
- *
- * This is useful to initialize an instance of
- * {@code org.apache.tools.ant.util.DeweyDecimal} using a
- * {@link FullRevision}.
- *
- * @param includePreview If true the output will contain 4 fields
- * to include the preview number (even if 0.) If false the output
- * will contain only 3 fields (major, minor and micro.)
- * @return A new int array, never null, with either 3 or 4 fields.
- */
- public int[] toIntArray(boolean includePreview) {
- int size = includePreview ? 4 : 3;
- int[] result = new int[size];
- result[0] = mMajor;
- result[1] = mMinor;
- result[2] = mMicro;
- if (result.length > 3) {
- result[3] = mPreview;
- }
- return result;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + mMajor;
- result = prime * result + mMinor;
- result = prime * result + mMicro;
- result = prime * result + mPreview;
- result = prime * result + mPreviewType.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(Object rhs) {
- if (this == rhs) {
- return true;
- }
- if (rhs == null) {
- return false;
- }
- if (!(rhs instanceof FullRevision)) {
- return false;
- }
- FullRevision other = (FullRevision) rhs;
- if (mMajor != other.mMajor) {
- return false;
- }
- if (mMinor != other.mMinor) {
- return false;
- }
- if (mMicro != other.mMicro) {
- return false;
- }
- if (mPreview != other.mPreview) {
- return false;
- }
- if (mPreviewType != other.mPreviewType) {
- return false;
- }
- return true;
- }
-
- /**
- * Trivial comparison of a version, e.g 17.1.2 < 18.0.0.
- *
- * Note that preview/release candidate are released before their final version,
- * so "18.0.0 rc1" comes below "18.0.0". The best way to think of it as if the
- * lack of preview number was "+inf":
- * "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0"
- * and more than "18.1.2.4"
- *
- * @param rhs The right-hand side {@link FullRevision} to compare with.
- * @return <0 if lhs < rhs; 0 if lhs==rhs; >0 if lhs > rhs.
- */
- @Override
- public int compareTo(FullRevision rhs) {
- return compareTo(rhs, PreviewComparison.COMPARE_NUMBER);
- }
-
- /**
- * Trivial comparison of a version, e.g 17.1.2 < 18.0.0.
- *
- * Note that preview/release candidate are released before their final version,
- * so "18.0.0 rc1" comes below "18.0.0". The best way to think of it as if the
- * lack of preview number was "+inf":
- * "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0"
- * and more than "18.1.2.4"
- *
- * @param rhs The right-hand side {@link FullRevision} to compare with.
- * @param comparePreview How to compare the preview value.
- * @return <0 if lhs < rhs; 0 if lhs==rhs; >0 if lhs > rhs.
- */
- public int compareTo(FullRevision rhs, PreviewComparison comparePreview) {
- int delta = mMajor - rhs.mMajor;
- if (delta != 0) {
- return delta;
- }
-
- delta = mMinor - rhs.mMinor;
- if (delta != 0) {
- return delta;
- }
-
- delta = mMicro - rhs.mMicro;
- if (delta != 0) {
- return delta;
- }
-
- int p1, p2;
- switch (comparePreview) {
- case IGNORE:
- // Nothing to compare.
- break;
-
- case COMPARE_NUMBER:
- if (!mPreviewType.equals(rhs.mPreviewType)) {
- return mPreviewType.compareTo(rhs.mPreviewType);
- }
-
- p1 = mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : mPreview;
- p2 = rhs.mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : rhs.mPreview;
- delta = p1 - p2;
- break;
-
- case COMPARE_TYPE:
- p1 = mPreview == NOT_A_PREVIEW ? 1 : 0;
- p2 = rhs.mPreview == NOT_A_PREVIEW ? 1 : 0;
- delta = p1 - p2;
- break;
- }
- return delta;
- }
-
- /** Indicates how to compare the preview field in
- * {@link FullRevision#compareTo(FullRevision, PreviewComparison)} */
- public enum PreviewComparison {
- /** Both revisions must have exactly the same preview number. */
- COMPARE_NUMBER,
- /** Both revisions must have the same preview type (both must be previews
- * or both must not be previews, but the actual number is irrelevant.)
- * This is the most typical choice used to find updates of the same type. */
- COMPARE_TYPE,
- /** The preview field is ignored and not used in the comparison. */
- IGNORE
- }
-
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/IDescription.java b/base/sdklib/src/main/java/com/android/sdklib/repository/IDescription.java
deleted file mode 100755
index 1e44964..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/IDescription.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.repository;
-
-/**
- * Interface for elements that can provide a description of themselves.
- */
-public interface IDescription {
-
- /**
- * Returns a description of the given element. Cannot be null.
- * <p/>
- * A description is a multi-line of text, typically much more
- * elaborate than what {@link Object#toString()} would provide.
- */
- String getShortDescription();
-
- /**
- * Returns a description of the given element. Cannot be null.
- * <p/>
- * A description is a multi-line of text, typically much more
- * elaborate than what {@link Object#toString()} would provide.
- */
- String getLongDescription();
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/IListDescription.java b/base/sdklib/src/main/java/com/android/sdklib/repository/IListDescription.java
deleted file mode 100755
index c3ae365..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/IListDescription.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-/**
- * Interface for elements that can provide a description of themselves.
- */
-public interface IListDescription {
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * Should not be empty. Must never be null.
- * <p/>
- * Note that this is the "base" name for the package
- * with no specific revision nor API mentioned.
- * In contrast, {@link IDescription#getShortDescription()} should be used if you
- * want more details such as the package revision number or the API, if applicable.
- */
- String getListDescription();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/ISdkChangeListener.java b/base/sdklib/src/main/java/com/android/sdklib/repository/ISdkChangeListener.java
deleted file mode 100755
index 5c0cab8..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/ISdkChangeListener.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.repository;
-
-
-/**
- * Interface for listeners on SDK modifications by the SDK Manager UI.
- * This notifies when the SDK manager is first loading the SDK or before/after it installed
- * a package.
- */
-public interface ISdkChangeListener {
- /**
- * Invoked when the content of the SDK is being loaded by the SDK Manager UI
- * for the first time.
- * This is generally followed by a call to {@link #onSdkReload()}
- * or by a call to {@link #preInstallHook()}.
- */
- void onSdkLoaded();
-
- /**
- * Invoked when the SDK Manager UI is about to start installing packages.
- * This will be followed by a call to {@link #postInstallHook()}.
- */
- void preInstallHook();
-
- /**
- * Invoked when the SDK Manager UI is done installing packages.
- * Some new packages might have been installed or the user might have cancelled the operation.
- * This is generally followed by a call to {@link #onSdkReload()}.
- */
- void postInstallHook();
-
- /**
- * Invoked when the content of the SDK is being reloaded by the SDK Manager UI,
- * typically after a package was installed. The SDK content might or might not
- * have changed.
- */
- void onSdkReload();
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/License.java b/base/sdklib/src/main/java/com/android/sdklib/repository/License.java
deleted file mode 100755
index 7040ec1..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/License.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.repository;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.SdkManager;
-import com.android.utils.FileUtils;
-import com.google.common.base.Charsets;
-import com.google.common.hash.Hashing;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-
-/**
- * License text, with an optional license XML reference.
- */
-public class License {
- private final String mLicense;
- private final String mLicenseRef;
- private final String mLicenseHash;
-
- private static final String LICENSE_DIR = "licenses";
-
- public License(@NonNull String license, @Nullable String licenseRef) {
- mLicense = license;
- mLicenseRef = licenseRef;
- mLicenseHash = Hashing.sha1().hashBytes(mLicense.getBytes()).toString();
- }
-
- /** Returns the license text. Never null. */
- @NonNull
- public String getLicense() {
- return mLicense;
- }
-
- /** Returns the hash of the license text. Never null. */
- @NonNull
- public String getLicenseHash() {
- return mLicenseHash;
- }
-
- /**
- * Returns the license XML reference.
- * Could be null, e.g. in tests or synthetic packages
- * recreated from local source.properties.
- */
- @Nullable
- public String getLicenseRef() {
- return mLicenseRef;
- }
-
- /**
- * Returns a string representation of the license, useful for debugging.
- * This is not designed to be shown to the user.
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("<License ref:")
- .append(mLicenseRef)
- .append(", text:")
- .append(mLicense)
- .append(">");
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result
- + ((mLicense == null) ? 0 : mLicense.hashCode());
- result = prime * result
- + ((mLicenseRef == null) ? 0 : mLicenseRef.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof License)) {
- return false;
- }
- License other = (License) obj;
- if (mLicense == null) {
- if (other.mLicense != null) {
- return false;
- }
- } else if (!mLicense.equals(other.mLicense)) {
- return false;
- }
- if (mLicenseRef == null) {
- if (other.mLicenseRef != null) {
- return false;
- }
- } else if (!mLicenseRef.equals(other.mLicenseRef)) {
- return false;
- }
- return true;
- }
-
- /**
- * Checks whether this license has previously been accepted.
- * @param sdkRoot The root directory of the Android SDK
- * @return true if this license has already been accepted
- */
- public boolean checkAccepted(@Nullable File sdkRoot) {
- if (sdkRoot == null) {
- return false;
- }
- File licenseDir = new File(sdkRoot, LICENSE_DIR);
- File licenseFile = new File(licenseDir, mLicenseRef == null ? mLicenseHash : mLicenseRef);
- if (!licenseFile.exists()) {
- return false;
- }
- try {
- String hash = Files.readFirstLine(licenseFile, Charsets.UTF_8);
- return hash.equals(mLicenseHash);
- } catch (IOException e) {
- return false;
- }
- }
-
- /**
- * Marks this license as accepted.
- *
- * @param sdkRoot The root directory of the Android SDK
- * @return true if the acceptance was persisted successfully.
- */
- public boolean setAccepted(@Nullable File sdkRoot) {
- if (sdkRoot == null) {
- return false;
- }
- if (checkAccepted(sdkRoot)) {
- return true;
- }
- File licenseDir = new File(sdkRoot, LICENSE_DIR);
- if (licenseDir.exists() && !licenseDir.isDirectory()) {
- return false;
- }
- if (!licenseDir.exists()) {
- licenseDir.mkdir();
- }
- File licenseFile = new File(licenseDir, mLicenseRef == null ? mLicenseHash : mLicenseRef);
- try {
- Files.write(mLicenseHash, licenseFile, Charsets.UTF_8);
- }
- catch (IOException e) {
- return false;
- }
- return true;
- }
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/MajorRevision.java b/base/sdklib/src/main/java/com/android/sdklib/repository/MajorRevision.java
deleted file mode 100755
index b6d2557..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/MajorRevision.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-import com.android.annotations.NonNull;
-
-
-/**
- * Package revision number composed of a <em>single</em> major revision.
- * <p/>
- * Contrary to a {@link FullRevision}, a {@link MajorRevision} does not
- * provide minor, micro and preview revision numbers -- these are all
- * set to zero.
- */
-public class MajorRevision extends FullRevision {
-
- public MajorRevision(FullRevision fullRevision) {
- super(fullRevision.getMajor(), IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV);
- }
-
- public MajorRevision(int major) {
- super(major, IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV);
- }
-
- @Override
- public String toString() {
- return super.toShortString();
- }
-
- /**
- * Parses a single-integer string and returns a new {@link MajorRevision} for it.
- *
- * @param revision A non-null revision to parse.
- * @return A new non-null {@link MajorRevision}.
- * @throws NumberFormatException if the parsing failed.
- */
- @NonNull
- public static MajorRevision parseRevision(@NonNull String revision)
- throws NumberFormatException {
- FullRevision r = parseRevisionImpl(
- revision, false /*supportMinorMicro*/, false /*supportPreview*/,
- false /*keepPrecision*/);
- return new MajorRevision(r.getMajor());
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/NoPreviewRevision.java b/base/sdklib/src/main/java/com/android/sdklib/repository/NoPreviewRevision.java
deleted file mode 100755
index 0280cb2..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/NoPreviewRevision.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-import com.android.annotations.NonNull;
-
-
-/**
- * Package multi-part revision number composed of a tuple
- * (major.minor.micro) but without support for any optional preview number.
- *
- * @see FullRevision
- */
-public class NoPreviewRevision extends FullRevision {
-
- public NoPreviewRevision(int major) {
- this(major, IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV);
- }
-
- public NoPreviewRevision(int major, int minor, int micro) {
- super(major, minor, micro, NOT_A_PREVIEW);
- }
-
- /**
- * Parses a string of format "major.minor.micro" and returns
- * a new {@link NoPreviewRevision} for it. All the fields except major are
- * optional.
- * <p/>
- * The parsing is equivalent to the pseudo-BNF/regexp:
- * <pre>
- * Major/Minor/Micro/Preview := [0-9]+
- * Revision := Major ('.' Minor ('.' Micro)? )? \s*
- * </pre>
- *
- * @param revision A non-null revision to parse.
- * @return A new non-null {@link NoPreviewRevision}.
- * @throws NumberFormatException if the parsing failed.
- */
- @NonNull
- public static NoPreviewRevision parseRevision(@NonNull String revision)
- throws NumberFormatException {
- FullRevision r = parseRevisionImpl(
- revision, true /*supportMinorMicro*/, false /*supportPreview*/,
- false /*keepPrecision*/);
- return new NoPreviewRevision(r.getMajor(), r.getMinor(), r.getMicro());
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java b/base/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java
deleted file mode 100755
index 3126984..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-
-
-/**
- * Public constants used by the repository when saving {@code source.properties}
- * files in local packages.
- * <p/>
- * These constants are public and part of the SDK Manager public API.
- * Once published we can't change them arbitrarily since various parts
- * of our build process depend on them.
- */
-public class PkgProps {
-
- // Base Package
- public static final String PKG_REVISION = "Pkg.Revision"; //$NON-NLS-1$
- public static final String PKG_LICENSE = "Pkg.License"; //$NON-NLS-1$
- public static final String PKG_LICENSE_REF = "Pkg.LicenseRef"; //$NON-NLS-1$
- public static final String PKG_DESC = "Pkg.Desc"; //$NON-NLS-1$
- public static final String PKG_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$
- public static final String PKG_RELEASE_NOTE = "Pkg.RelNote"; //$NON-NLS-1$
- public static final String PKG_RELEASE_URL = "Pkg.RelNoteUrl"; //$NON-NLS-1$
- public static final String PKG_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$
- public static final String PKG_OBSOLETE = "Pkg.Obsolete"; //$NON-NLS-1$
- public static final String PKG_LIST_DISPLAY = "Pkg.ListDisplay"; //$NON-NLS-1$
-
- // AndroidVersion
-
- public static final String VERSION_API_LEVEL = "AndroidVersion.ApiLevel";//$NON-NLS-1$
- /** Code name of the platform if the platform is not final */
- public static final String VERSION_CODENAME = "AndroidVersion.CodeName";//$NON-NLS-1$
-
-
- // AddonPackage
-
- public static final String ADDON_NAME = "Addon.Name"; //$NON-NLS-1$
- public static final String ADDON_NAME_ID = "Addon.NameId"; //$NON-NLS-1$
- public static final String ADDON_NAME_DISPLAY = "Addon.NameDisplay"; //$NON-NLS-1$
-
- public static final String ADDON_VENDOR = "Addon.Vendor"; //$NON-NLS-1$
- public static final String ADDON_VENDOR_ID = "Addon.VendorId"; //$NON-NLS-1$
- public static final String ADDON_VENDOR_DISPLAY = "Addon.VendorDisplay"; //$NON-NLS-1$
-
- // DocPackage
-
- // ExtraPackage
-
- public static final String EXTRA_PATH = "Extra.Path"; //$NON-NLS-1$
- public static final String EXTRA_OLD_PATHS = "Extra.OldPaths"; //$NON-NLS-1$
- public static final String EXTRA_MIN_API_LEVEL = "Extra.MinApiLevel"; //$NON-NLS-1$
- public static final String EXTRA_PROJECT_FILES = "Extra.ProjectFiles"; //$NON-NLS-1$
- public static final String EXTRA_VENDOR = "Extra.Vendor"; //$NON-NLS-1$
- public static final String EXTRA_VENDOR_ID = "Extra.VendorId"; //$NON-NLS-1$
- public static final String EXTRA_VENDOR_DISPLAY = "Extra.VendorDisplay"; //$NON-NLS-1$
- public static final String EXTRA_NAME_DISPLAY = "Extra.NameDisplay"; //$NON-NLS-1$
-
- // ILayoutlibVersion
-
- public static final String LAYOUTLIB_API = "Layoutlib.Api"; //$NON-NLS-1$
- public static final String LAYOUTLIB_REV = "Layoutlib.Revision"; //$NON-NLS-1$
-
- // MinToolsPackage
-
- public static final String MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
-
- // PlatformPackage
-
- public static final String PLATFORM_VERSION = "Platform.Version"; //$NON-NLS-1$
- /** Code name of the platform. This has no bearing on the package being a preview or not. */
- public static final String PLATFORM_CODENAME = "Platform.CodeName"; //$NON-NLS-1$
- public static final String PLATFORM_INCLUDED_ABI = "Platform.Included.Abi"; //$NON-NLS-1$
-
- // ToolPackage
-
- public static final String MIN_PLATFORM_TOOLS_REV = "Platform.MinPlatformToolsRev";//$NON-NLS-1$
- public static final String MIN_BUILD_TOOLS_REV = "Platform.MinBuildToolsRev"; //$NON-NLS-1$
-
-
- // SamplePackage
-
- public static final String SAMPLE_MIN_API_LEVEL = "Sample.MinApiLevel"; //$NON-NLS-1$
-
- // SystemImagePackage
-
- public static final String SYS_IMG_ABI = "SystemImage.Abi"; //$NON-NLS-1$
- public static final String SYS_IMG_TAG_ID = "SystemImage.TagId"; //$NON-NLS-1$
- public static final String SYS_IMG_TAG_DISPLAY = "SystemImage.TagDisplay"; //$NON-NLS-1$
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/PreciseRevision.java b/base/sdklib/src/main/java/com/android/sdklib/repository/PreciseRevision.java
deleted file mode 100755
index 81e9744..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/PreciseRevision.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-import com.android.annotations.NonNull;
-
-/**
- * A {@link FullRevision} which distinguishes between x and x.0, x.0.0, x.y.0, etc; it basically
- * keeps track of the precision of the revision string.
- * <p>
- * This is vital when referencing Gradle artifact numbers,
- * since versions x.y.0 and version x.y are not the same.
- */
-public class PreciseRevision extends FullRevision {
- private final int mPrecision;
-
- /**
- * Parses a string of format "major.minor.micro rcPreview" and returns
- * a new {@link com.android.sdklib.repository.PreciseRevision} for it.
- *
- * All the fields except major are optional.
- * <p/>
- * @param revision A non-null revision to parse.
- * @return A new non-null {@link com.android.sdklib.repository.PreciseRevision}.
- * @throws NumberFormatException if the parsing failed.
- */
- @NonNull
- public static PreciseRevision parseRevision(@NonNull String revision)
- throws NumberFormatException {
- return (PreciseRevision) parseRevisionImpl(revision, true /*supportMinorMicro*/,
- true /*supportPreview*/, true /*keepPrevision*/);
- }
-
- public PreciseRevision(int major) {
- this(major, IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV, NOT_A_PREVIEW, PRECISION_MAJOR,
- DEFAULT_SEPARATOR);
- }
-
- public PreciseRevision(int major, int minor) {
- this(major, minor, IMPLICIT_MICRO_REV, NOT_A_PREVIEW, PRECISION_MINOR, DEFAULT_SEPARATOR);
- }
-
- public PreciseRevision(int major, int minor, int micro) {
- this(major, minor, micro, NOT_A_PREVIEW, PRECISION_MICRO, DEFAULT_SEPARATOR);
- }
-
- public PreciseRevision(int major, int minor, int micro, int preview) {
- this(major, minor, micro, preview, PRECISION_PREVIEW, DEFAULT_SEPARATOR);
- }
-
- PreciseRevision(int major, int minor, int micro, int preview, int precision,
- String separator) {
- this(major, minor, micro, preview, precision, separator, PreviewType.RC);
- }
-
- PreciseRevision(int major, int minor, int micro, int preview, int precision,
- String separator, FullRevision.PreviewType previewType) {
- super(major, minor, micro, previewType, preview, separator);
- mPrecision = precision;
- }
-
- /**
- * Returns the version in a fixed format major.minor.micro
- * with an optional "rc preview#". For example it would
- * return "18.0.0", "18.1.0" or "18.1.2 rc5".
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(getMajor());
-
- if (mPrecision >= PRECISION_MINOR) {
- sb.append('.').append(getMinor());
- if (mPrecision >= PRECISION_MICRO) {
- sb.append('.').append(getMicro());
- if (mPrecision >= PRECISION_PREVIEW && isPreview()) {
- sb.append(getSeparator()).append("rc").append(getPreview());
- }
- }
- }
-
- return sb.toString();
- }
-
- @Override
- public String toShortString() {
- return toString();
- }
-
- @Override
- public int[] toIntArray(boolean includePreview) {
- int[] result;
- if (mPrecision >= PRECISION_PREVIEW) {
- if (includePreview) {
- result = new int[mPrecision];
- result[3] = getPreview();
- } else {
- result = new int[mPrecision - 1];
- }
- } else {
- result = new int[mPrecision];
- }
- result[0] = getMajor();
- if (mPrecision >= PRECISION_MINOR) {
- result[1] = getMinor();
- if (mPrecision >= PRECISION_MICRO) {
- result[2] = getMicro();
- }
- }
-
- return result;
- }
-
- @Override
- public int hashCode() {
- return 31 * super.hashCode() + mPrecision;
- }
-
- @Override
- public boolean equals(Object rhs) {
- boolean equals = super.equals(rhs);
- if (equals) {
- if (!(rhs instanceof PreciseRevision)) {
- return false;
- }
- PreciseRevision other = (PreciseRevision) rhs;
- return mPrecision == other.mPrecision;
- }
- return false;
- }
-
- public int compareTo(PreciseRevision rhs, PreviewComparison comparePreview) {
- int delta = super.compareTo(rhs, comparePreview);
- if (delta == 0) {
- return mPrecision - rhs.mPrecision;
- }
- return delta;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/RepoConstants.java b/base/sdklib/src/main/java/com/android/sdklib/repository/RepoConstants.java
deleted file mode 100755
index 44f6f15..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/RepoConstants.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-import java.io.InputStream;
-
-
-
-/**
- * Public constants common to the sdk-repository and sdk-addon XML Schemas.
- * @deprecated moved to studio
- */
-public class RepoConstants {
-
- /** The license definition. */
- public static final String NODE_LICENSE = "license"; //$NON-NLS-1$
- /** The optional uses-license for all packages or for a lib. */
- public static final String NODE_USES_LICENSE = "uses-license"; //$NON-NLS-1$
- /** The revision, an int > 0, for all packages. */
- public static final String NODE_REVISION = "revision"; //$NON-NLS-1$
- /** The optional description for all packages or for a lib. */
- public static final String NODE_DESCRIPTION = "description"; //$NON-NLS-1$
- /** The optional description URL for all packages. */
- public static final String NODE_DESC_URL = "desc-url"; //$NON-NLS-1$
- /** The optional release note for all packages. */
- public static final String NODE_RELEASE_NOTE = "release-note"; //$NON-NLS-1$
- /** The optional release note URL for all packages. */
- public static final String NODE_RELEASE_URL = "release-url"; //$NON-NLS-1$
- /** The optional obsolete qualifier for all packages. */
- public static final String NODE_OBSOLETE = "obsolete"; //$NON-NLS-1$
- /** The optional project-files provided by extra packages. */
- public static final String NODE_PROJECT_FILES = "project-files"; //$NON-NLS-1$
-
- /** A system-image package. */
- public static final String NODE_SYSTEM_IMAGE = "system-image"; //$NON-NLS-1$
-
- /* An included-ABI element for a system-image package. */
- public static final String NODE_ABI_INCLUDED = "included-abi"; //$NON-NLS-1$
- /* An ABI element for a system-image package. */
- public static final String NODE_ABI = "abi"; //$NON-NLS-1$
-
- /** The optional minimal tools revision required by platform & extra packages. */
- public static final String NODE_MIN_TOOLS_REV = "min-tools-rev"; //$NON-NLS-1$
- /** The optional minimal platform-tools revision required by tool packages. */
- public static final String NODE_MIN_PLATFORM_TOOLS_REV = "min-platform-tools-rev"; //$NON-NLS-1$
- /** The optional minimal API level required by extra packages. */
- public static final String NODE_MIN_API_LEVEL = "min-api-level"; //$NON-NLS-1$
-
- /** The version, a string, for platform packages. */
- public static final String NODE_VERSION = "version"; //$NON-NLS-1$
- /** The api-level, an int > 0, for platform, add-on and doc packages. */
- public static final String NODE_API_LEVEL = "api-level"; //$NON-NLS-1$
- /** The codename, a string, for platform packages. */
- public static final String NODE_CODENAME = "codename"; //$NON-NLS-1$
- /** The *old* vendor, a string, for add-on and extra packages.
- * Replaced by {@link #NODE_VENDOR_DISPLAY} and {@link #NODE_VENDOR_ID} in addon-v4.xsd. */
- public static final String NODE_VENDOR = "vendor"; //$NON-NLS-1$
- /** The vendor display string, for add-on and extra packages. */
- public static final String NODE_VENDOR_DISPLAY = "vendor-display"; //$NON-NLS-1$
- /** The unique vendor id string, for add-on and extra packages. */
- public static final String NODE_VENDOR_ID = "vendor-id"; //$NON-NLS-1$
- /** The name, a string, for add-on packages or for libraries.
- * Replaced by {@link #NODE_NAME_DISPLAY} and {@link #NODE_NAME_ID} in addon-v4.xsd. */
- public static final String NODE_NAME = "name"; //$NON-NLS-1$
- /** The name display string, for add-on packages or for libraries. */
- public static final String NODE_NAME_DISPLAY = "name-display"; //$NON-NLS-1$
- /** The unique name id string, for add-on packages or for libraries. */
- public static final String NODE_NAME_ID = "name-id"; //$NON-NLS-1$
- /** The optional string used to display a package in a list view. */
- public static final String NODE_LIST_DISPLAY = "list-display"; //$NON-NLS-1$
-
- /** A layoutlib package. */
- public static final String NODE_LAYOUT_LIB = "layoutlib"; //$NON-NLS-1$
- /** The API integer for a layoutlib element. */
- public static final String NODE_API = "api"; //$NON-NLS-1$
-
- /** The libs container, optional for an add-on. */
- public static final String NODE_LIBS = "libs"; //$NON-NLS-1$
- /** A lib element in a libs container. */
- public static final String NODE_LIB = "lib"; //$NON-NLS-1$
-
- /** The path segment, a string, for extra packages. */
- public static final String NODE_PATH = "path"; //$NON-NLS-1$
-
- /** The old_path segments, a string, for extra packages. */
- public static final String NODE_OLD_PATHS = "old-paths"; //$NON-NLS-1$
-
- /** The archives container, for all packages. */
- public static final String NODE_ARCHIVES = "archives"; //$NON-NLS-1$
- /** An archive element, for the archives container. */
- public static final String NODE_ARCHIVE = "archive"; //$NON-NLS-1$
-
- /** An archive size, an int > 0. */
- public static final String NODE_SIZE = "size"; //$NON-NLS-1$
- /** A sha1 archive checksum, as a 40-char hex. */
- public static final String NODE_CHECKSUM = "checksum"; //$NON-NLS-1$
- /** A download archive URL, either absolute or relative to the repository xml. */
- public static final String NODE_URL = "url"; //$NON-NLS-1$
-
- /**
- * Optional element to indicate an archive is only suitable for the specified OS. <br/>
- * Values: windows | macosx | linux.
- * @since repo-10, addon-7 and sys-img-3.
- * @replaces {@link #LEGACY_ATTR_OS}
- */
- public static final String NODE_HOST_OS = "host-os"; //$NON-NLS-1$
- /**
- * Optional element to indicate an archive is only suitable for the specified host bit size.<br/>
- * Values: 32 | 64.
- * @since repo-10, addon-7 and sys-img-3.
- */
- public static final String NODE_HOST_BITS = "host-bits"; //$NON-NLS-1$
- /**
- * Optional element to indicate an archive is only suitable for the specified JVM bit size.<br/>
- * Values: 32 | 64.
- * @since repo-10, addon-7 and sys-img-3.
- * @replaces {@link #LEGACY_ATTR_ARCH}
- */
- public static final String NODE_JVM_BITS = "jvm-bits"; //$NON-NLS-1$
- /**
- * Optional element to indicate an archive is only suitable for a JVM equal or greater than
- * the specified value. <br/>
- * Value format: [1-9](\.[0-9]{1,2}){0,2}, e.g. "1.6", "1.7.0", "1.10" or "2"
- * @since repo-10, addon-7 and sys-img-3.
- */
- public static final String NODE_MIN_JVM_VERSION = "min-jvm-version"; //$NON-NLS-1$
-
-
- /** An archive checksum type, mandatory. */
- public static final String ATTR_TYPE = "type"; //$NON-NLS-1$
- /**
- * An archive OS attribute, mandatory. <br/>
- * Use {@link #NODE_HOST_OS} instead in repo-10, addon-7 and sys-img-3.
- */
- public static final String LEGACY_ATTR_OS = "os"; //$NON-NLS-1$
- /**
- * An optional archive Architecture attribute. <br/>
- * Use {@link #NODE_JVM_BITS} instead in repo-10, addon-7 and sys-img-3.
- */
- public static final String LEGACY_ATTR_ARCH = "arch"; //$NON-NLS-1$
-
- /** A license definition ID. */
- public static final String ATTR_ID = "id"; //$NON-NLS-1$
- /** A license reference. */
- public static final String ATTR_REF = "ref"; //$NON-NLS-1$
-
-
- /** Type of a sha1 checksum. */
- public static final String SHA1_TYPE = "sha1"; //$NON-NLS-1$
-
- /** Length of a string representing a SHA1 checksum; always 40 characters long. */
- public static final int SHA1_CHECKSUM_LEN = 40;
-
- /**
- * Temporary folder used to hold downloads and extract archives during installation.
- * This folder will be located in the SDK.
- */
- public static final String FD_TEMP = "temp"; //$NON-NLS-1$
-
- /**
- * Returns a stream to the requested XML Schema.
- * This is an internal helper. Users of the library should call
- * {@link SdkRepoConstants#getXsdStream(String, int)} or
- * {@link SdkAddonConstants#getXsdStream(String, int)}.
- *
- * @param rootElement The root of the filename of the XML schema.
- * This is by convention the same as the root element declared by the schema.
- * @param version The XML schema revision number, an integer >= 1.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- * @see SdkRepoConstants#getXsdStream(int)
- * @see SdkAddonConstants#getXsdStream(int)
- */
- protected static InputStream getXsdStream(String rootElement, int version) {
- String filename = String.format("%1$s-%2$02d.xsd", rootElement, version); //$NON-NLS-1$
-
- InputStream stream = null;
- try {
- stream = RepoConstants.class.getResourceAsStream(filename);
- } catch (Exception e) {
- // Some implementations seem to return null on failure,
- // others throw an exception. We want to return null.
- }
- if (stream == null) {
- // Try the alternate schemas that are not published yet.
- // This allows us to internally test with new schemas before the
- // public repository uses it.
- filename = String.format("-%1$s-%2$02d.xsd", rootElement, version); //$NON-NLS-1$
- try {
- stream = RepoConstants.class.getResourceAsStream(filename);
- } catch (Exception e) {
- // Some implementations seem to return null on failure,
- // others throw an exception. We want to return null.
- }
- }
-
- return stream;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java b/base/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java
deleted file mode 100755
index 805f85b..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-addon XML Schema.
- * @deprecated moved to studio
- */
-public class SdkAddonConstants extends RepoConstants {
-
- /**
- * The latest version of the sdk-addon XML Schema.
- * Valid version numbers are between 1 and this number, included.
- */
- public static final int NS_LATEST_VERSION = 7;
-
- /**
- * The default name looked for by SdkSource when trying to load an
- * sdk-addon XML if the URL doesn't match an existing resource.
- */
- public static final String URL_DEFAULT_FILENAME = "addon.xml"; //$NON-NLS-1$
-
- /** The base of our sdk-addon XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/addon/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-addon XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([0-9]+)"; //$NON-NLS-1$
-
- /** The XML namespace of the latest sdk-addon XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-addon element */
- public static final String NODE_SDK_ADDON = "sdk-addon"; //$NON-NLS-1$
-
- /** An add-on package. */
- public static final String NODE_ADD_ON = "add-on"; //$NON-NLS-1$
-
- /** An extra package. */
- public static final String NODE_EXTRA = "extra"; //$NON-NLS-1$
-
- /**
- * List of possible nodes in a repository XML. Used to populate options automatically
- * in the no-GUI mode.
- */
- public static final String[] NODES = {
- NODE_ADD_ON,
- NODE_EXTRA
- };
-
- /**
- * Returns a stream to the requested {@code sdk-addon} XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- return getXsdStream(NODE_SDK_ADDON, version);
- }
-
- /**
- * Returns the URI of the sdk-addon schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonsListConstants.java b/base/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonsListConstants.java
deleted file mode 100755
index 3468b77..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonsListConstants.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-addons-list XML Schema.
- * @deprecated moved to studio
- */
-public class SdkAddonsListConstants {
-
- /** The base of our sdk-addons-list XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/addons-list/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-addons-list XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
-
- /** The latest version of the sdk-addons-list XML Schema.
- * Valid version numbers are between 1 and this number, included. */
- public static final int NS_LATEST_VERSION = 2;
-
- /** The XML namespace of the latest sdk-addons-list XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
-
- /** The canonical URL filename for addons-list XML files. */
- public static final String URL_DEFAULT_FILENAME = getDefaultName(NS_LATEST_VERSION);
-
- /** The URL where to find the official addons list fle. */
- public static final String URL_ADDON_LIST =
- SdkRepoConstants.URL_GOOGLE_SDK_SITE + URL_DEFAULT_FILENAME;
-
-
-
- /** The root sdk-addons-list element */
- public static final String NODE_SDK_ADDONS_LIST = "sdk-addons-list"; //$NON-NLS-1$
-
- /** An add-on site. */
- public static final String NODE_ADDON_SITE = "addon-site"; //$NON-NLS-1$
-
- /** A system image site. */
- public static final String NODE_SYS_IMG_SITE = "sys-img-site"; //$NON-NLS-1$
-
- /** The UI-visible name of the add-on site. */
- public static final String NODE_NAME = "name"; //$NON-NLS-1$
-
- /**
- * The URL of the site.
- * <p/>
- * This can be either the exact URL of the an XML resource conforming
- * to the latest sdk-addon-N.xsd schema, or it can be the URL of a
- * 'directory', in which case the manager will look for a resource
- * named 'addon.xml' at this location.
- * <p/>
- * Examples:
- * <pre>
- * http://www.example.com/android/my_addons.xml
- * or
- * http://www.example.com/android/
- * </pre>
- * In the second example, the manager will actually look for
- * http://www.example.com/android/addon.xml
- */
- public static final String NODE_URL = "url"; //$NON-NLS-1$
-
- /**
- * Returns a stream to the requested sdk-addon XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- String filename = String.format("sdk-addons-list-%d.xsd", version); //$NON-NLS-1$
- return SdkAddonsListConstants.class.getResourceAsStream(filename);
- }
-
- /**
- * Returns the URI of the sdk-addon schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-
- public static String getDefaultName(int version) {
- return String.format("addons_list-%1$d.xml", version); //$NON-NLS-1$
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkRepoConstants.java b/base/sdklib/src/main/java/com/android/sdklib/repository/SdkRepoConstants.java
deleted file mode 100755
index 7b32543..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkRepoConstants.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-
-import com.android.annotations.NonNull;
-
-import java.io.InputStream;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Public constants for the sdk-repository XML Schema.
- * @deprecated moved to studio
- */
-public class SdkRepoConstants extends RepoConstants {
-
- /**
- * The latest version of the sdk-repository XML Schema.
- * Valid version numbers are between 1 and this number, included.
- */
- public static final int NS_LATEST_VERSION = 11;
-
- /**
- * The min version of the sdk-repository XML Schema we'll try to load.
- * When looking for a repository-N.xml on the server, we'll check from
- * {@link #NS_LATEST_VERSION} down to this revision.
- * We only introduced the "repository-N.xml" pattern start with revision
- * 5, so we know that <em>our</em> server will never contain a repository
- * XML with a schema version lower than this one.
- */
- public static final int NS_SERVER_MIN_VERSION = 5;
-
- /**
- * The URL of the official Google sdk-repository site.
- * The URL ends with a /, allowing easy concatenation.
- * */
- public static final String URL_GOOGLE_SDK_SITE =
- "https://dl.google.com/android/repository/"; //$NON-NLS-1$
-
- /**
- * The default name looked for by SdkSource when trying to load an
- * sdk-repository XML if the URL doesn't match an existing resource.
- */
- public static final String URL_DEFAULT_FILENAME = "repository.xml"; //$NON-NLS-1$
-
- /**
- * The pattern name looked by {@link SdkSource} when trying to load
- * an sdk-repository XML that is specific to a given XSD revision.
- * <p/>
- * This must be used with {@link String#format(String, Object...)} with
- * one integer parameter between 1 and {@link #NS_LATEST_VERSION}.
- */
- public static final String URL_FILENAME_PATTERN = "repository-%1$d.xml"; //$NON-NLS-1$
-
- /** The base of our sdk-repository XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/repository/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-repository XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([0-9]+)"; //$NON-NLS-1$
-
- /** The XML namespace of the latest sdk-repository XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-repository element */
- public static final String NODE_SDK_REPOSITORY = "sdk-repository"; //$NON-NLS-1$
-
- /* The major revision for tool and platform-tool package
- * (the full revision number is revision.minor.micro + preview#.)
- * Mandatory int > 0. 0 when missing, which should not happen in
- * a valid document. */
- public static final String NODE_MAJOR_REV = "major"; //$NON-NLS-1$
- /* The minor revision for tool and platform-tool package
- * (the full revision number is revision.minor.micro + preview#.)
- * Optional int >= 0. Implied to be 0 when missing. */
- public static final String NODE_MINOR_REV = "minor"; //$NON-NLS-1$
- /* The micro revision for tool and platform-tool package
- * (the full revision number is revision.minor.micro + preview#.)
- * Optional int >= 0. Implied to be 0 when missing. */
- public static final String NODE_MICRO_REV = "micro"; //$NON-NLS-1$
- /* The preview revision for tool and platform-tool package.
- * Int > 0, only present for "preview / release candidate" packages. */
- public static final String NODE_PREVIEW = "preview"; //$NON-NLS-1$
-
- /** A platform package. */
- public static final String NODE_PLATFORM = "platform"; //$NON-NLS-1$
- /** A tool package. */
- public static final String NODE_TOOL = "tool"; //$NON-NLS-1$
- /** A platform-tool package. */
- public static final String NODE_PLATFORM_TOOL = "platform-tool"; //$NON-NLS-1$
- /** A build-tool package. */
- public static final String NODE_BUILD_TOOL = "build-tool"; //$NON-NLS-1$
- /** A doc package. */
- public static final String NODE_DOC = "doc"; //$NON-NLS-1$
- /** A sample package. */
- public static final String NODE_SAMPLE = "sample"; //$NON-NLS-1$
- /** A source package. */
- public static final String NODE_SOURCE = "source"; //$NON-NLS-1$
-
- /**
- * List of possible nodes in a repository XML. Used to populate options automatically
- * in the no-GUI mode.
- */
- public static final String[] NODES = {
- NODE_PLATFORM,
- NODE_SYSTEM_IMAGE,
- NODE_TOOL,
- NODE_PLATFORM_TOOL,
- NODE_DOC,
- NODE_SAMPLE,
- NODE_SOURCE,
- };
-
- /**
- * Returns a stream to the requested {@code sdk-repository} XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- return getXsdStream(NODE_SDK_REPOSITORY, version);
- }
-
- /**
- * Returns the URI of the SDK Repository schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-
- /**
- * Checks whether the schema version is greater or equal to the specified one.
- *
- * @param nsUri A non-null sdk-repository schema URI.
- * @param minVersion The minimum version accepted.
- * @return True if the URI is valid and has at least the required version. False otherwise.
- */
- public static boolean versionGreaterOrEqualThan(@NonNull String nsUri, int minVersion) {
- Pattern nsPattern = Pattern.compile(SdkRepoConstants.NS_PATTERN);
- Matcher m = nsPattern.matcher(nsUri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- return Integer.parseInt(version) >= minVersion;
- } catch (NumberFormatException e) {
- }
- }
- return false;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkStatsConstants.java b/base/sdklib/src/main/java/com/android/sdklib/repository/SdkStatsConstants.java
deleted file mode 100755
index df3e5d6..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkStatsConstants.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-stats XML Schema.
- * @deprecated moved to studio
- */
-public class SdkStatsConstants {
-
- /** The canonical URL filename for addons-list XML files. */
- public static final String URL_DEFAULT_FILENAME = "stats-1.xml"; //$NON-NLS-1$
-
- /** The URL where to find the official addons list fle. */
- public static final String URL_STATS =
- SdkRepoConstants.URL_GOOGLE_SDK_SITE + URL_DEFAULT_FILENAME;
-
- /** The base of our sdk-addons-list XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/stats/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-stats XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
-
- /** The latest version of the sdk-stats XML Schema.
- * Valid version numbers are between 1 and this number, included. */
- public static final int NS_LATEST_VERSION = 1;
-
- /** The XML namespace of the latest sdk-stats XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-stats element */
- public static final String NODE_SDK_STATS = "sdk-stats"; //$NON-NLS-1$
-
- /** A platform stat. */
- public static final String NODE_PLATFORM = "platform"; //$NON-NLS-1$
-
- /** The Android API Level for the platform. An int > 0. */
- public static final String NODE_API_LEVEL = "api-level"; //$NON-NLS-1$
-
- /** The official codename for this platform, for example "Cupcake". */
- public static final String NODE_CODENAME = "codename"; //$NON-NLS-1$
-
- /** The official version name of this platform, for example "Android 1.5". */
- public static final String NODE_VERSION = "version"; //$NON-NLS-1$
-
- /**
- * The <em>approximate</em> share percentage of that platform.
- * See the caveat in sdk-stats-1.xsd about value freshness and accuracy.
- */
- public static final String NODE_SHARE = "share"; //$NON-NLS-1$
-
- /**
- * Returns a stream to the requested sdk-stats XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- String filename = String.format("sdk-stats-%d.xsd", version); //$NON-NLS-1$
- return SdkStatsConstants.class.getResourceAsStream(filename);
- }
-
- /**
- * Returns the URI of the sdk-stats schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkSysImgConstants.java b/base/sdklib/src/main/java/com/android/sdklib/repository/SdkSysImgConstants.java
deleted file mode 100755
index d3e5752..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/SdkSysImgConstants.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-sys-img XML Schema.
- * @deprecated moved to studio
- */
-public class SdkSysImgConstants extends RepoConstants {
-
- /**
- * The default name looked for by SdkSource when trying to load an
- * sdk-sys-img XML if the URL doesn't match an existing resource.
- */
- public static final String URL_DEFAULT_FILENAME = "sys-img.xml"; //$NON-NLS-1$
-
- /** The base of our sdk-sys-img XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/sys-img/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-sys-img XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([0-9]+)"; //$NON-NLS-1$
-
- /**
- * The latest version of the sdk-sys-img XML Schema.
- * Valid version numbers are between 1 and this number, included.
- */
- public static final int NS_LATEST_VERSION = 3;
-
- /** The XML namespace of the latest sdk-sys-img XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-sys-img element */
- public static final String NODE_SDK_SYS_IMG = "sdk-sys-img"; //$NON-NLS-1$
-
- /** A system-image tag id. */
- public static final String ATTR_TAG_ID = "tag-id"; //$NON-NLS-1$
- /** The user-visible display part of a system-image tag id. Optional. */
- public static final String ATTR_TAG_DISPLAY = "tag-display"; //$NON-NLS-1$
-
- /** An add-on sub-element, indicating this is an add-on system image. */
- public static final String NODE_ADD_ON = SdkAddonConstants.NODE_ADD_ON;
-
- /**
- * List of possible nodes in a repository XML. Used to populate options automatically
- * in the no-GUI mode.
- */
- public static final String[] NODES = {
- NODE_SYSTEM_IMAGE,
- };
-
- /**
- * Returns a stream to the requested {@code sdk-sys-img} XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- return getXsdStream(NODE_SDK_SYS_IMG, version);
- }
-
- /**
- * Returns the URI of the sdk-sys-img schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgCapabilities.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgCapabilities.java
deleted file mode 100755
index b481e6a..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgCapabilities.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-
-/**
- * {@link IPkgCapabilities} describe which attributes are available for each kind of
- * SDK Manager package type.
- * <p/>
- * To query packages capabilities, rely on {@code PkgType.hasXxx()} or {@code PkgDesc.hasXxx()}.
- *
- * @see PkgType
- * @see PkgDesc
- */
-public interface IPkgCapabilities {
-
- /**
- * Indicates whether this package type has a {@link FullRevision}.
- * @return True if this package type has a {@link FullRevision}.
- */
- boolean hasFullRevision();
-
- /**
- * Indicates whether this package type has a {@link MajorRevision}.
- * @return True if this package type has a {@link MajorRevision}.
- */
- boolean hasMajorRevision();
-
- /**
- * Indicates whether this package type has a {@link AndroidVersion}.
- * @return True if this package type has a {@link AndroidVersion}.
- */
- boolean hasAndroidVersion();
-
- /**
- * Indicates whether this package type has a path.
- * @return True if this package type has a path.
- */
- boolean hasPath();
-
- /**
- * Indicates whether this package type has a tag.
- * @return True if this package type has a tag id-display tuple.
- */
- boolean hasTag();
-
- /**
- * Indicates whether this package type has a vendor id.
- * @return True if this package type has a vendor id.
- */
- boolean hasVendor();
-
- /**
- * Indicates whether this package type has a {@code min-tools-rev} attribute.
- * @return True if this package type has a {@code min-tools-rev} attribute.
- */
- boolean hasMinToolsRev();
-
- /**
- * Indicates whether this package type has a {@code min-platform-tools-rev} attribute.
- * @return True if this package type has a {@code min-platform-tools-rev} attribute.
- */
- boolean hasMinPlatformToolsRev();
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java
deleted file mode 100755
index 5effda3..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.IListDescription;
-import com.android.sdklib.repository.License;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PreciseRevision;
-
-import java.io.File;
-
-/**
- * {@link IPkgDesc} keeps information on individual SDK packages
- * (both local or remote packages definitions.)
- * <br/>
- * Packages have different attributes depending on their type.
- * <p/>
- * To create a new {@link IPkgDesc}, use one of the package-specific constructors
- * provided by {@code PkgDesc.Builder.newXxx()}.
- * <p/>
- * To query packages capabilities, rely on {@link #getType()} and the {@code IPkgDesc.hasXxx()}
- * methods provided by {@link IPkgDesc}.
- */
-public interface IPkgDesc extends Comparable<IPkgDesc>, IPkgCapabilities, IListDescription {
-
- /**
- * Returns the type of the package.
- * @return Returns one of the {@link PkgType} constants.
- */
- @NonNull
- PkgType getType();
-
- /**
- * Returns the list-display meta data of this package.
- * @return The list-display data, if available, or null.
- */
- @Nullable
- String getListDisplay();
-
- @Nullable
- String getDescriptionShort();
-
- @Nullable
- String getDescriptionUrl();
-
- @Nullable
- License getLicense();
-
- boolean isObsolete();
-
- /**
- * Returns the package's {@link FullRevision} or null.
- * @return A non-null value if {@link #hasFullRevision()} is true; otherwise a null value.
- */
- @Nullable
- FullRevision getFullRevision();
-
- /**
- * Returns the package's {@link MajorRevision} or null.
- * @return A non-null value if {@link #hasMajorRevision()} is true; otherwise a null value.
- */
- @Nullable
- MajorRevision getMajorRevision();
-
- /**
- * Returns the package's revision or null. This will come from the {@link FullRevision} or
- * {@link MajorRevision}, with the precision set as appropriate.
- * @return A representation of {@link #getMajorRevision()} or {@link #getFullRevision()},
- * depending on which one exists.
- */
- @NonNull
- PreciseRevision getPreciseRevision();
-
- /**
- * Returns the package's {@link AndroidVersion} or null.
- * @return A non-null value if {@link #hasAndroidVersion()} is true; otherwise a null value.
- */
- @Nullable
- AndroidVersion getAndroidVersion();
-
- /**
- * Returns the package's path string or null.
- * <p/>
- * For {@link PkgType#PKG_SYS_IMAGE}, the path is the system-image ABI. <br/>
- * For {@link PkgType#PKG_PLATFORM}, the path is the platform hash string. <br/>
- * For {@link PkgType#PKG_ADDON}, the path is the platform hash string. <br/>
- * For {@link PkgType#PKG_EXTRA}, the path is the extra-path string. <br/>
- *
- * @return A non-null value if {@link #hasPath()} is true; otherwise a null value.
- */
- @Nullable
- String getPath();
-
- /**
- * Returns the package's tag id-display tuple or null.
- *
- * @return A non-null tag if {@link #hasTag()} is true; otherwise a null value.
- */
- @Nullable
- IdDisplay getTag();
-
- /**
- * Returns the package's vendor-id string or null.
- * @return A non-null value if {@link #hasVendor()} is true; otherwise a null value.
- */
- @Nullable
- IdDisplay getVendor();
-
- /**
- * Returns the package's {@code min-tools-rev} or null.
- * @return A non-null value if {@link #hasMinToolsRev()} is true; otherwise a null value.
- */
- @Nullable
- FullRevision getMinToolsRev();
-
- /**
- * Returns the package's {@code min-platform-tools-rev} or null.
- * @return A non-null value if {@link #hasMinPlatformToolsRev()} is true; otherwise null.
- */
- @Nullable
- FullRevision getMinPlatformToolsRev();
-
- /**
- * Indicates whether <em>this</em> package descriptor is an update for the given
- * existing descriptor. Preview versions are never considered updates for non-
- * previews, and vice versa.
- *
- * @param existingDesc A non-null existing descriptor.
- * @return True if this package is an update for the given one.
- */
- boolean isUpdateFor(@NonNull IPkgDesc existingDesc);
-
- /**
- * Indicates whether <em>this</em> package descriptor is an update for the given
- * existing descriptor, using the given comparison method.
- *
- * @param existingDesc A non-null existing descriptor.
- * @param previewComparison The {@link FullRevision.PreviewComparison} method to use
- * when comparing the packages.
- * @return True if this package is an update for the given one.
- */
- boolean isUpdateFor(@NonNull IPkgDesc existingDesc,
- @NonNull FullRevision.PreviewComparison previewComparison);
-
- /**
- * Returns a stable string id that can be used to reference this package, including
- * a suffix indicating that this package is a preview if it is.
- */
- @NonNull
- String getInstallId();
-
- /**
- * Returns a stable string id that can be used to reference this package, which
- * excludes the preview suffix.
- */
- String getBaseInstallId();
-
- /**
- * Returns the canonical location where such a package would be installed.
- * @param sdkLocation The root of the SDK.
- * @return the canonical location where such a package would be installed.
- */
- @NonNull
- File getCanonicalInstallFolder(@NonNull File sdkLocation);
-
- /**
- * @return True if the revision of this package is a preview.
- */
- boolean isPreview();
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescAddon.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescAddon.java
deleted file mode 100755
index 02c0ad5..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescAddon.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.annotations.NonNull;
-
-/**
- * {@link IPkgDescAddon} keeps information on individual add-on SDK packages
- * (both local or remote packages definitions.) The base {@link IPkgDesc} tries
- * to present a unified interface to package attributes and this interface
- * adds methods specific to extras.
- * <p/>
- * To create a new {@link IPkgDescAddon},
- * use {@link PkgDesc.Builder#newAddon(com.android.sdklib.AndroidVersion, com.android.sdklib.repository.MajorRevision, IdDisplay, IdDisplay)}.
- * <p/>
- * To query generic packages capabilities, rely on {@link #getType()} and the
- * {@code IPkgDesc.hasXxx()} methods provided by {@link IPkgDesc}.
- */
-public interface IPkgDescAddon extends IPkgDesc {
-
- /**
- * Returns the id/display name of the add-on.
- * @return A non-null id/display name for the add-on
- */
- @NonNull IdDisplay getName();
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java
deleted file mode 100755
index 68c4bd7..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.NoPreviewRevision;
-
-/**
- * {@link IPkgDescExtra} keeps information on individual extra SDK packages
- * (both local or remote packages definitions.) The base {@link IPkgDesc} tries
- * to present a unified interface to package attributes and this interface
- * adds methods specific to extras.
- * <p/>
- * To create a new {@link IPkgDescExtra},
- * use {@link PkgDesc.Builder#newExtra(IdDisplay, String, String, String[], NoPreviewRevision)}.
- * <p/>
- * The extra's revision is a {@link NoPreviewRevision}; the attribute is however
- * accessed via {@link IPkgDesc#getFullRevision()} instead of introducing a new
- * custom method.
- * <p/>
- * To query generic packages capabilities, rely on {@link #getType()} and the
- * {@code IPkgDesc.hasXxx()} methods provided by {@link IPkgDesc}.
- */
-public interface IPkgDescExtra extends IPkgDesc {
- /**
- * Returns an optional list of older paths for this extra package.
- * @return A non-null, possibly empty, for old paths previously used for the same extra.
- */
- @NonNull String[] getOldPaths();
-
- /**
- * Returns the display name of the Extra.
- * @return A non-null name for the Extra, used for display purposes.
- */
- @NonNull String getNameDisplay();
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IdDisplay.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IdDisplay.java
deleted file mode 100755
index 0278ba5..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IdDisplay.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.annotations.NonNull;
-
-/**
- * Immutable structure that represents a tuple (id-string + display-string.)
- */
-public final class IdDisplay implements Comparable<IdDisplay> {
-
- private final String mId;
- private final String mDisplay;
-
- /**
- * Creates a new immutable tuple (id-string + display-string.)
- *
- * @param id The non-null id string.
- * @param display The non-null display string.
- */
- public IdDisplay(@NonNull String id, @NonNull String display) {
- mId = id;
- mDisplay = display;
- }
-
- @NonNull
- public String getId() {
- return mId;
- }
-
- @NonNull
- public String getDisplay() {
- return mDisplay;
- }
-
- /**
- * {@link IdDisplay} instances are the same if they have the same id.
- * The display value is not used for comparison or ordering.
- */
- @Override
- public int compareTo(IdDisplay tag) {
- return mId.compareTo(tag.mId);
- }
-
- /**
- * Hash code of {@link IdDisplay} instances only rely on the id hash code.
- */
- @Override
- public int hashCode() {
- return mId.hashCode();
- }
-
- /**
- * Equality of {@link IdDisplay} instances only rely on the id equality.
- * The display value is not used for comparison or ordering.
- */
- @Override
- public boolean equals(Object obj) {
- return (obj instanceof IdDisplay) && mId.equals(((IdDisplay)obj).mId);
- }
-
- /**
- * Returns a string representation for *debug* purposes only, not for UI display.
- */
- @Override
- public String toString() {
- return String.format("%1$s [%2$s]", mId, mDisplay);
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java
deleted file mode 100755
index 6c1e152..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java
+++ /dev/null
@@ -1,1206 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.FullRevision.PreviewComparison;
-import com.android.sdklib.repository.License;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.PreciseRevision;
-
-import java.io.File;
-import java.util.Locale;
-
-/**
- * {@link PkgDesc} keeps information on individual SDK packages
- * (both local or remote packages definitions.)
- * <br/>
- * Packages have different attributes depending on their type.
- * <p/>
- * To create a new {@link PkgDesc}, use one of the package-specific constructors
- * provided here.
- * <p/>
- * To query packages capabilities, rely on {@link #getType()} and the {@code PkgDesc.hasXxx()}
- * methods provided in the base {@link PkgDesc}.
- */
-public class PkgDesc implements IPkgDesc {
- public static final String PREVIEW_SUFFIX = "-preview";
- private final PkgType mType;
- private final FullRevision mFullRevision;
- private final MajorRevision mMajorRevision;
- private final AndroidVersion mAndroidVersion;
- private final String mPath;
- private final IdDisplay mTag;
- private final IdDisplay mVendor;
- private final FullRevision mMinToolsRev;
- private final FullRevision mMinPlatformToolsRev;
- private final IIsUpdateFor mCustomIsUpdateFor;
- private final IGetPath mCustomPath;
-
- private final License mLicense;
- private final String mListDisplay;
- private final String mDescriptionShort;
- private final String mDescriptionUrl;
- private final boolean mIsObsolete;
-
- protected PkgDesc(@NonNull PkgType type,
- @Nullable License license,
- @Nullable String listDisplay,
- @Nullable String descriptionShort,
- @Nullable String descriptionUrl,
- boolean isObsolete,
- @Nullable FullRevision fullRevision,
- @Nullable MajorRevision majorRevision,
- @Nullable AndroidVersion androidVersion,
- @Nullable String path,
- @Nullable IdDisplay tag,
- @Nullable IdDisplay vendor,
- @Nullable FullRevision minToolsRev,
- @Nullable FullRevision minPlatformToolsRev,
- @Nullable IIsUpdateFor customIsUpdateFor,
- @Nullable IGetPath customPath) {
- mType = type;
- mIsObsolete = isObsolete;
- mLicense = license;
- mListDisplay = listDisplay;
- mDescriptionShort = descriptionShort;
- mDescriptionUrl = descriptionUrl;
- mFullRevision = fullRevision;
- mMajorRevision = majorRevision;
- mAndroidVersion = androidVersion;
- mPath = path;
- mTag = tag;
- mVendor = vendor;
- mMinToolsRev = minToolsRev;
- mMinPlatformToolsRev = minPlatformToolsRev;
- mCustomIsUpdateFor = customIsUpdateFor;
- mCustomPath = customPath;
- }
-
- @NonNull
- @Override
- public PkgType getType() {
- return mType;
- }
-
- @Override
- @Nullable
- public String getListDisplay() {
- return mListDisplay;
- }
-
- @Override
- @Nullable
- public String getDescriptionShort() {
- return mDescriptionShort;
- }
-
- @Override
- @Nullable
- public String getDescriptionUrl() {
- return mDescriptionUrl;
- }
-
- @Override
- @Nullable
- public License getLicense() {
- return mLicense;
- }
-
- @Override
- public boolean isObsolete() {
- return mIsObsolete;
- }
-
- @Override
- public final boolean hasFullRevision() {
- return getType().hasFullRevision();
- }
-
- @Override
- public final boolean hasMajorRevision() {
- return getType().hasMajorRevision();
- }
-
- @Override
- public final boolean hasAndroidVersion() {
- return getType().hasAndroidVersion();
- }
-
- @Override
- public final boolean hasPath() {
- return getType().hasPath();
- }
-
- @Override
- public final boolean hasTag() {
- return getType().hasTag();
- }
-
- @Override
- public boolean hasVendor() {
- return getType().hasVendor();
- }
-
- @Override
- public final boolean hasMinToolsRev() {
- return getType().hasMinToolsRev();
- }
-
- @Override
- public final boolean hasMinPlatformToolsRev() {
- return getType().hasMinPlatformToolsRev();
- }
-
- @Nullable
- @Override
- public FullRevision getFullRevision() {
- return mFullRevision;
- }
-
- @Nullable
- @Override
- public MajorRevision getMajorRevision() {
- return mMajorRevision;
- }
-
- @NonNull
- @Override
- public final PreciseRevision getPreciseRevision() {
- if (mMajorRevision == null) {
- return new PreciseRevision(mFullRevision.getMajor(), mFullRevision.getMinor(),
- mFullRevision.getMicro(), mFullRevision.getPreview());
- }
- return new PreciseRevision(mMajorRevision.getMajor());
- }
-
- @Nullable
- @Override
- public AndroidVersion getAndroidVersion() {
- return mAndroidVersion;
- }
-
- @Override
- public boolean isPreview() {
- return getPreciseRevision().isPreview();
- }
-
- @Nullable
- @Override
- public String getPath() {
- if (mCustomPath != null) {
- return mCustomPath.getPath(this);
- } else {
- return mPath;
- }
- }
-
- @Nullable
- @Override
- public IdDisplay getTag() {
- return mTag;
- }
-
- @Nullable
- @Override
- public IdDisplay getVendor() {
- return mVendor;
- }
-
- @Nullable
- @Override
- public FullRevision getMinToolsRev() {
- return mMinToolsRev;
- }
-
- @Nullable
- @Override
- public FullRevision getMinPlatformToolsRev() {
- return mMinPlatformToolsRev;
- }
-
- @Override
- public String getInstallId() {
- String id = getBaseInstallId();
- if (getPreciseRevision().isPreview()) {
- return id + PREVIEW_SUFFIX;
- }
- return id;
- }
-
- @Override
- public String getBaseInstallId() {
- StringBuilder sb = new StringBuilder();
-
- /* iid patterns:
- tools, platform-tools => FOLDER
- build-tools => FOLDER-REV
- doc, sample, source => ENUM-API
- extra => ENUM-VENDOR.id-PATH
- platform => android-API
- add-on => addon-NAME.id-VENDOR.id-API
- platform sys-img => sys-img-ABI-TAG|android-API
- add-on sys-img => sys-img-ABI-addon-NAME.id-VENDOR.id-API
- */
-
- switch (mType) {
- case PKG_TOOLS:
- case PKG_PLATFORM_TOOLS:
- sb.append(mType.getFolderName());
- break;
-
- case PKG_BUILD_TOOLS:
- sb.append(mType.getFolderName()).append('-');
- // Add version number without the preview revision number. This is to make preview
- // packages be updatable to the next revision.
- int[] version = getPreciseRevision().toIntArray(false);
- for (int i = 0; i < version.length; i++) {
- sb.append(version[i]);
- if (i != version.length - 1) {
- sb.append('.');
- }
- }
- break;
-
- case PKG_DOC:
- sb.append("doc");
- break;
-
- case PKG_SAMPLE:
- case PKG_SOURCE:
- sb.append(mType.toString().toLowerCase(Locale.US).replace("pkg_", ""));
- sb.append('-').append(getAndroidVersion().getApiString());
- break;
-
- case PKG_EXTRA:
- sb.append("extra-")
- .append(getVendor().getId())
- .append('-')
- .append(getPath());
- break;
-
- case PKG_PLATFORM:
- sb.append(AndroidTargetHash.PLATFORM_HASH_PREFIX)
- .append(getAndroidVersion().getApiString());
- break;
-
- case PKG_ADDON:
- sb.append("addon-")
- .append(((IPkgDescAddon)this).getName().getId())
- .append('-')
- .append(getVendor().getId())
- .append('-')
- .append(getAndroidVersion().getApiString());
- break;
-
- case PKG_SYS_IMAGE:
- sb.append("sys-img-")
- .append(getPath()) // path==ABI for sys-img
- .append('-')
- .append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
- .append('-')
- .append(getAndroidVersion().getApiString());
- break;
-
- case PKG_ADDON_SYS_IMAGE:
- sb.append("sys-img-")
- .append(getPath()) // path==ABI for sys-img
- .append("-addon-")
- .append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
- .append('-')
- .append(getVendor().getId())
- .append('-')
- .append(getAndroidVersion().getApiString());
- break;
-
- case PKG_NDK:
- sb.append("ndk");
- break;
-
- default:
- throw new IllegalArgumentException("IID not defined for type " + mType.toString());
- }
-
- return sanitize(sb.toString());
- }
-
- @Override
- public File getCanonicalInstallFolder(@NonNull File sdkLocation) {
- File f = FileOp.append(sdkLocation, mType.getFolderName());
-
- /* folder patterns:
- tools, platform-tools, doc => FOLDER
- build-tools, add-on => FOLDER/IID
- platform, sample, source => FOLDER/android-API
- platform sys-img => FOLDER/android-API/TAG/ABI
- add-on sys-img => FOLDER/addon-NAME.id-VENDOR.id-API/ABI
- extra => FOLDER/VENDOR.id/PATH
- */
-
- switch (mType) {
- case PKG_TOOLS:
- case PKG_PLATFORM_TOOLS:
- case PKG_DOC:
- // no-op, top-folder is all what is needed here
- break;
-
- case PKG_BUILD_TOOLS:
- case PKG_ADDON:
- f = FileOp.append(f, getInstallId());
- break;
-
- case PKG_PLATFORM:
- case PKG_SAMPLE:
- case PKG_SOURCE:
- f = FileOp.append(f, AndroidTargetHash.PLATFORM_HASH_PREFIX + sanitize(
- getAndroidVersion().getApiString()));
- break;
-
- case PKG_SYS_IMAGE:
- f = FileOp.append(f,
- AndroidTargetHash.PLATFORM_HASH_PREFIX + sanitize(
- getAndroidVersion().getApiString()),
- sanitize(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android"
- : getTag().getId()),
- sanitize(getPath())); // path==abi
- break;
-
- case PKG_ADDON_SYS_IMAGE:
- String name = "addon-"
- + (SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
- + '-'
- + getVendor().getId()
- + '-'
- + getAndroidVersion().getApiString();
- f = FileOp.append(f,
- sanitize(name),
- sanitize(getPath())); // path==abi
- break;
-
- case PKG_EXTRA:
- f = FileOp.append(f,
- sanitize(getVendor().getId()),
- sanitize(getPath()));
- break;
-
- default:
- throw new IllegalArgumentException(
- "CanonicalFolder not defined for type " + mType.toString());
- }
-
- return f;
- }
-
- //---- Updating ----
-
- /**
- * Computes the most general case of {@link #isUpdateFor(IPkgDesc)}.
- * Individual package types use this and complement with their own specific cases
- * as needed.
- *
- * @param existingDesc A non-null package descriptor to compare with.
- * @return True if this package descriptor would generally update the given one.
- */
- @Override
- public boolean isUpdateFor(@NonNull IPkgDesc existingDesc) {
- return isUpdateFor(existingDesc, PreviewComparison.COMPARE_NUMBER);
- }
-
- @Override
- public boolean isUpdateFor(@NonNull IPkgDesc existingDesc,
- @NonNull PreviewComparison previewComparison) {
- if (mCustomIsUpdateFor != null) {
- return mCustomIsUpdateFor.isUpdateFor(this, existingDesc);
- } else {
- return isGenericUpdateFor(existingDesc, previewComparison);
- }
- }
-
- /**
- * Computes the most general case of {@link #isUpdateFor(IPkgDesc)}.
- * Individual package types use this and complement with their own specific cases
- * as needed.
- *
- * @param existingDesc A non-null package descriptor to compare with.
- * @param previewComparison The type of preview comparison to do.
- * @return True if this package descriptor would generally update the given one.
- */
- private boolean isGenericUpdateFor(@NonNull IPkgDesc existingDesc,
- PreviewComparison previewComparison) {
-
- if (existingDesc == null || !getType().equals(existingDesc.getType())) {
- return false;
- }
-
- // Packages that have an Android version can generally only be updated
- // for the same Android version (otherwise it's a new artifact.)
- if (hasAndroidVersion() && !getAndroidVersion().equals(existingDesc.getAndroidVersion())) {
- return false;
- }
-
- // Packages that have a vendor id need the same vendor id on both sides
- if (hasVendor() && !getVendor().equals(existingDesc.getVendor())) {
- return false;
- }
-
- // Packages that have a tag id need the same tag id on both sides
- if (hasTag() && !getTag().getId().equals(existingDesc.getTag().getId())) {
- return false;
- }
-
- // Packages that have a path can generally only be updated if both use the same path
- if (hasPath()) {
- if (this instanceof IPkgDescExtra) {
- // Extra package handle paths differently, they need to use the old_path
- // to allow upgrade compatibility.
- if (!PkgDescExtra.compatibleVendorAndPath((IPkgDescExtra) this,
- (IPkgDescExtra) existingDesc)) {
- return false;
- }
- } else if (!getPath().equals(existingDesc.getPath())) {
- return false;
- }
- }
-
- // Packages that have a major version are generally updates if it increases.
- if (hasMajorRevision() &&
- getMajorRevision().compareTo(existingDesc.getMajorRevision()) > 0) {
- return true;
- }
-
- // Packages that have a full revision are generally updates if it increases
- // but keeps the same kind of preview (e.g. previews are only updates by previews.)
- if (hasFullRevision() &&
- (previewComparison == PreviewComparison.IGNORE
- || existingDesc.isPreview() == isPreview())) {
- // If both packages match in their preview type (both previews or both not previews)
- // then is the RC/preview number an update?
- return getFullRevision().compareTo(existingDesc.getFullRevision(),
- PreviewComparison.COMPARE_NUMBER) > 0;
- }
-
- return false;
- }
-
-
- //---- Ordering ----
-
- /**
- * Compares this descriptor to another one.
- * All fields must match for equality.
- * <p/>
- * This is must not be used an indication that a package is a suitable update for another one.
- * The comparison order is however suitable for sorting packages for display purposes.
- */
- @Override
- public int compareTo(@NonNull IPkgDesc o) {
- int t1 = getType().getIntValue();
- int t2 = o.getType().getIntValue();
- if (t1 != t2) {
- return t1 - t2;
- }
-
- if (hasAndroidVersion() && o.hasAndroidVersion()) {
- t1 = getAndroidVersion().compareTo(o.getAndroidVersion());
- if (t1 != 0) {
- return t1;
- }
- }
-
- if (hasVendor() && o.hasVendor()) {
- t1 = getVendor().compareTo(o.getVendor());
- if (t1 != 0) {
- return t1;
- }
- }
-
- if (hasTag() && o.hasTag()) {
- t1 = getTag().compareTo(o.getTag());
- if (t1 != 0) {
- return t1;
- }
- }
-
- if (hasPath() && o.hasPath()) {
- t1 = getPath().compareTo(o.getPath());
- if (t1 != 0) {
- return t1;
- }
- }
-
- if (hasFullRevision() && o.hasFullRevision()) {
- t1 = getFullRevision().compareTo(o.getFullRevision());
- if (t1 != 0) {
- return t1;
- }
- }
-
- if (hasMajorRevision() && o.hasMajorRevision()) {
- t1 = getMajorRevision().compareTo(o.getMajorRevision());
- if (t1 != 0) {
- return t1;
- }
- }
-
- if (hasMinToolsRev() && o.hasMinToolsRev()) {
- t1 = getMinToolsRev().compareTo(o.getMinToolsRev());
- if (t1 != 0) {
- return t1;
- }
- }
-
- if (hasMinPlatformToolsRev() && o.hasMinPlatformToolsRev()) {
- t1 = getMinPlatformToolsRev().compareTo(o.getMinPlatformToolsRev());
- if (t1 != 0) {
- return t1;
- }
- }
-
- return 0;
- }
-
- // --- display description ----
-
- @NonNull
- @Override
- public String getListDescription() {
- if (mListDisplay != null && !mListDisplay.isEmpty()) {
- return mListDisplay;
- }
-
- return patternReplaceImpl(getType().getListDisplayPattern());
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected String patternReplaceImpl(String result) {
- // Flags for list description pattern string, used in PkgType:
- // $MAJ $FULL $API $PATH $TAG $VEND $NAME (for extras)
-
- result = result
- .replace("$MAJ", hasMajorRevision() ? getMajorRevision().toShortString() : "");
- result = result
- .replace("$FULL", hasFullRevision() ? getFullRevision().toShortString() : "");
- result = result
- .replace("$API", hasAndroidVersion() ? getAndroidVersion().getApiString() : "");
- result = result.replace("$PATH", hasPath() ? getPath() : "");
- result = result.replace("$TAG", hasTag() && !getTag().equals(SystemImage.DEFAULT_TAG) ?
- getTag().getDisplay() : "");
- result = result.replace("$VEND", hasVendor() ? getVendor().getDisplay() : "");
- String name = "";
- if (this instanceof IPkgDescExtra) {
- name = ((IPkgDescExtra) this).getNameDisplay();
- } else if (this instanceof IPkgDescAddon) {
- name = ((IPkgDescAddon) this).getName().getDisplay();
- }
- result = result.replace("$NAME", name);
-
- // Evaluate replacements.
- // {|choice1|choice2|...|choiceN|} gets replaced by the first non-empty choice.
- for (int start = result.indexOf("{|");
- start >= 0;
- start = result.indexOf("{|")) {
- int end = result.indexOf('}', start);
- int last = start + 1;
- for (int pipe = result.indexOf('|', last+1);
- pipe > start;
- last = pipe, pipe = result.indexOf('|', last+1)) {
- if (pipe - last > 1) {
- result = result.substring(0, start) +
- result.substring(last+1, pipe) +
- result.substring(end+1);
- break;
- }
- }
- }
-
- // Evaluate conditions.
- // {?value>1:text to use} -- uses the text if value is greater than 1.
- // Simplification: this is our only test right now so hard-code it instead of
- // using a generic expression evaluation.
- for (int start = result.indexOf("{?");
- start >= 0;
- start = result.indexOf("{?")) {
- int end = result.indexOf('}', start);
- int op = result.indexOf(">1:");
- if (op > start) {
- String value = "";
- try {
- FullRevision i = FullRevision.parseRevision(result.substring(start+2, op));
- if (i.compareTo(new FullRevision(1)) > 0) {
- value = result.substring(op+3, end);
- }
- } catch (NumberFormatException e) {
- value = "ERROR " + e.getMessage() + " in " + result.substring(start, end+1);
- }
- result = result.substring(0, start) +
- value +
- result.substring(end+1);
- }
- }
-
-
- return result;
- }
-
- /** String representation for debugging purposes. */
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("<PkgDesc"); //NON-NLS-1$
-
- builder.append(" Type="); //NON-NLS-1$
- builder.append(getType().toString()
- .toLowerCase(Locale.US)
- .replace("pkg_", "")); //NON-NLS-1$ //NON-NLS-2$
-
- if (hasAndroidVersion()) {
- builder.append(" Android=").append(getAndroidVersion()); //NON-NLS-1$
- }
-
- if (hasVendor()) {
- builder.append(" Vendor=").append(getVendor().toString()); //NON-NLS-1$
- }
-
- if (hasTag()) {
- builder.append(" Tag=").append(getTag()); //NON-NLS-1$
- }
-
- if (hasPath()) {
- builder.append(" Path=").append(getPath()); //NON-NLS-1$
- }
-
- if (hasFullRevision()) {
- builder.append(" FullRev=").append(getFullRevision()); //NON-NLS-1$
- }
-
- if (hasMajorRevision()) {
- builder.append(" MajorRev=").append(getMajorRevision()); //NON-NLS-1$
- }
-
- if (hasMinToolsRev()) {
- builder.append(" MinToolsRev=").append(getMinToolsRev()); //NON-NLS-1$
- }
-
- if (hasMinPlatformToolsRev()) {
- builder.append(" MinPlatToolsRev=").append(getMinPlatformToolsRev()); //NON-NLS-1$
- }
-
- if (mListDisplay != null) {
- builder.append(" ListDisp=").append(mListDisplay); //NON-NLS-1$
- }
-
- if (mDescriptionShort != null) {
- builder.append(" DescShort=").append(mDescriptionShort); //NON-NLS-1$
- }
-
- if (mDescriptionUrl != null) {
- builder.append(" DescUrl=").append(mDescriptionUrl); //NON-NLS-1$
- }
-
- if (mLicense != null) {
- builder.append(" License['").append(mLicense.getLicenseRef()) //NON-NLS-1$
- .append("]=") //NON-NLS-1$
- .append(mLicense.getLicense().length()).append(" chars"); //NON-NLS-1$
- }
-
- if (isObsolete()) {
- builder.append(" Obsolete=yes"); //NON-NLS-1$
- }
-
- builder.append('>');
- return builder.toString();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + (hasAndroidVersion() ? getAndroidVersion().hashCode() : 0);
- result = prime * result + (hasVendor() ? getVendor() .hashCode() : 0);
- result = prime * result + (hasTag() ? getTag() .hashCode() : 0);
- result = prime * result + (hasPath() ? getPath() .hashCode() : 0);
- result = prime * result + (hasFullRevision() ? getFullRevision() .hashCode() : 0);
- result = prime * result + (hasMajorRevision() ? getMajorRevision() .hashCode() : 0);
- result = prime * result + (hasMinToolsRev() ? getMinToolsRev() .hashCode() : 0);
- result = prime * result + (hasMinPlatformToolsRev() ?
- getMinPlatformToolsRev().hashCode() : 0);
-
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof IPkgDesc)) return false;
- IPkgDesc rhs = (IPkgDesc) obj;
-
- if (hasAndroidVersion() != rhs.hasAndroidVersion()) {
- return false;
- }
- if (hasAndroidVersion() && !getAndroidVersion().equals(rhs.getAndroidVersion())) {
- return false;
- }
-
- if (hasTag() != rhs.hasTag()) {
- return false;
- }
- if (hasTag() && !getTag().equals(rhs.getTag())) {
- return false;
- }
-
- if (hasPath() != rhs.hasPath()) {
- return false;
- }
- if (hasPath() && !getPath().equals(rhs.getPath())) {
- return false;
- }
-
- if (hasFullRevision() != rhs.hasFullRevision()) {
- return false;
- }
- if (hasFullRevision() && !getFullRevision().equals(rhs.getFullRevision())) {
- return false;
- }
-
- if (hasMajorRevision() != rhs.hasMajorRevision()) {
- return false;
- }
- if (hasMajorRevision() && !getMajorRevision().equals(rhs.getMajorRevision())) {
- return false;
- }
-
- if (hasMinToolsRev() != rhs.hasMinToolsRev()) {
- return false;
- }
- if (hasMinToolsRev() && !getMinToolsRev().equals(rhs.getMinToolsRev())) {
- return false;
- }
-
- if (hasMinPlatformToolsRev() != rhs.hasMinPlatformToolsRev()) {
- return false;
- }
- if (hasMinPlatformToolsRev() &&
- !getMinPlatformToolsRev().equals(rhs.getMinPlatformToolsRev())) {
- return false;
- }
-
- return true;
- }
-
-
- // ---- Constructors -----
-
- public interface IIsUpdateFor {
- boolean isUpdateFor(@NonNull PkgDesc thisPkgDesc, @NonNull IPkgDesc existingDesc);
- }
-
- public interface IGetPath {
- String getPath(@NonNull PkgDesc thisPkgDesc);
- }
-
- public static class Builder {
- private final PkgType mType;
- private FullRevision mFullRevision;
- private MajorRevision mMajorRevision;
- private AndroidVersion mAndroidVersion;
- private String mPath;
- private IdDisplay mTag;
- private IdDisplay mVendor;
- private FullRevision mMinToolsRev;
- private FullRevision mMinPlatformToolsRev;
- private IIsUpdateFor mCustomIsUpdateFor;
- private IGetPath mCustomPath;
- private String[] mOldPaths;
- private String mNameDisplay;
- private IdDisplay mNameIdDisplay;
-
- private License mLicense;
- private String mListDisplay;
- private String mDescriptionShort;
- private String mDescriptionUrl;
- private boolean mIsObsolete;
-
-
- private Builder(PkgType type) {
- mType = type;
- }
-
- /**
- * Creates a new tool package descriptor.
- *
- * @param revision The revision of the tool package.
- * @param minPlatformToolsRev The {@code min-platform-tools-rev}.
- * Use {@link FullRevision#NOT_SPECIFIED} to indicate there is no requirement.
- * @return A {@link PkgDesc} describing this tool package.
- */
- @NonNull
- public static Builder newTool(@NonNull FullRevision revision,
- @NonNull FullRevision minPlatformToolsRev) {
- Builder p = new Builder(PkgType.PKG_TOOLS);
- p.mFullRevision = revision;
- p.mMinPlatformToolsRev = minPlatformToolsRev;
- return p;
- }
-
- /**
- * Creates a new platform-tool package descriptor.
- *
- * @param revision The revision of the platform-tool package.
- * @return A {@link PkgDesc} describing this platform-tool package.
- */
- @NonNull
- public static Builder newPlatformTool(@NonNull FullRevision revision) {
- Builder p = new Builder(PkgType.PKG_PLATFORM_TOOLS);
- p.mFullRevision = revision;
- return p;
- }
-
- /**
- * Creates a new build-tool package descriptor.
- *
- * @param revision The revision of the build-tool package.
- * @return A {@link PkgDesc} describing this build-tool package.
- */
- @NonNull
- public static Builder newBuildTool(@NonNull FullRevision revision) {
- Builder p = new Builder(PkgType.PKG_BUILD_TOOLS);
- p.mFullRevision = revision;
- p.mCustomIsUpdateFor = new IIsUpdateFor() {
- @Override
- public boolean isUpdateFor(PkgDesc thisPkgDesc, IPkgDesc existingDesc) {
- // Generic test checks that the preview type is the same (both previews or not).
- // Build tool is different in that the full revision must be an exact match
- // and not an increase.
- return thisPkgDesc
- .isGenericUpdateFor(existingDesc, PreviewComparison.COMPARE_NUMBER) &&
- thisPkgDesc.getFullRevision().compareTo(
- existingDesc.getFullRevision(),
- PreviewComparison.COMPARE_TYPE) == 0;
- }
- };
- return p;
- }
-
- /**
- * Creates a new doc package descriptor.
- *
- * @param revision The revision of the doc package.
- * @return A {@link PkgDesc} describing this doc package.
- */
- @NonNull
- public static Builder newDoc(@NonNull AndroidVersion version,
- @NonNull MajorRevision revision) {
- Builder p = new Builder(PkgType.PKG_DOC);
- p.mAndroidVersion = version;
- p.mMajorRevision = revision;
- p.mCustomIsUpdateFor = new IIsUpdateFor() {
- @Override
- public boolean isUpdateFor(PkgDesc thisPkgDesc, IPkgDesc existingDesc) {
- if (existingDesc == null ||
- !thisPkgDesc.getType().equals(existingDesc.getType())) {
- return false;
- }
-
- // This package is unique in the SDK. It's an update if the API is newer
- // or the revision is newer for the same API.
- int diff = thisPkgDesc.getAndroidVersion().compareTo(
- existingDesc.getAndroidVersion());
- return diff > 0 ||
- (diff == 0 && thisPkgDesc.getMajorRevision().compareTo(
- existingDesc.getMajorRevision()) > 0);
- }
- };
- return p;
- }
-
- /**
- * Creates a new extra package descriptor.
- *
- * @param vendor The vendor id string of the extra package.
- * @param path The path id string of the extra package.
- * @param displayName The display name. If missing, caller should build one using the path.
- * @param oldPaths An optional list of older paths for this extra package.
- * @param revision The revision of the extra package.
- * @return A {@link PkgDesc} describing this extra package.
- */
- @NonNull
- public static Builder newExtra(@NonNull IdDisplay vendor,
- @NonNull String path,
- @Nullable String displayName,
- @Nullable String[] oldPaths,
- @NonNull NoPreviewRevision revision) {
- Builder p = new Builder(PkgType.PKG_EXTRA);
- p.mFullRevision = revision;
- p.mVendor = vendor;
- p.mPath = path;
- p.mNameDisplay = displayName;
- p.mOldPaths = oldPaths;
- return p;
- }
-
- /**
- * Creates a new platform package descriptor.
- *
- * @param version The android version of the platform package.
- * @param revision The revision of the extra package.
- * @param minToolsRev An optional {@code min-tools-rev}.
- * Use {@link FullRevision#NOT_SPECIFIED} to indicate
- * there is no requirement.
- * @return A {@link PkgDesc} describing this platform package.
- */
- @NonNull
- public static Builder newPlatform(@NonNull AndroidVersion version,
- @NonNull MajorRevision revision,
- @NonNull FullRevision minToolsRev) {
- Builder p = new Builder(PkgType.PKG_PLATFORM);
- p.mAndroidVersion = version;
- p.mMajorRevision = revision;
- p.mMinToolsRev = minToolsRev;
- p.mCustomPath = new IGetPath() {
- @Override
- public String getPath(PkgDesc thisPkgDesc) {
- /** The "path" of a Platform is its Target Hash. */
- return AndroidTargetHash.getPlatformHashString(thisPkgDesc.getAndroidVersion());
- }
- };
- return p;
- }
-
- /**
- * Create a new add-on package descriptor.
- * <p/>
- * The vendor id and the name id provided are used to compute the add-on's
- * target hash.
- *
- * @param version The android version of the add-on package.
- * @param revision The revision of the add-on package.
- * @param addonVendor The vendor id/display of the add-on package.
- * @param addonName The name id/display of the add-on package.
- * @return A {@link PkgDesc} describing this add-on package.
- */
- @NonNull
- public static Builder newAddon(@NonNull AndroidVersion version,
- @NonNull MajorRevision revision,
- @NonNull IdDisplay addonVendor,
- @NonNull IdDisplay addonName) {
- Builder p = new Builder(PkgType.PKG_ADDON);
- p.mAndroidVersion = version;
- p.mMajorRevision = revision;
- p.mVendor = addonVendor;
- p.mNameIdDisplay = addonName;
- return p;
- }
-
- /**
- * Create a new platform system-image package descriptor.
- * <p/>
- * For system-images, {@link PkgDesc#getPath()} returns the ABI.
- *
- * @param version The android version of the system-image package.
- * @param tag The tag of the system-image package.
- * @param abi The ABI of the system-image package.
- * @param revision The revision of the system-image package.
- * @return A {@link PkgDesc} describing this system-image package.
- */
- @NonNull
- public static Builder newSysImg(@NonNull AndroidVersion version,
- @NonNull IdDisplay tag,
- @NonNull String abi,
- @NonNull MajorRevision revision) {
- Builder p = new Builder(PkgType.PKG_SYS_IMAGE);
- p.mAndroidVersion = version;
- p.mMajorRevision = revision;
- p.mTag = tag;
- p.mPath = abi;
- p.mVendor = null;
- return p;
- }
-
- /**
- * Create a new add-on system-image package descriptor.
- * <p/>
- * For system-images, {@link PkgDesc#getPath()} returns the ABI.
- *
- * @param version The android version of the system-image package.
- * @param addonVendor The vendor id/display of an associated add-on.
- * @param addonName The tag of the system-image package is the add-on name.
- * @param abi The ABI of the system-image package.
- * @param revision The revision of the system-image package.
- * @return A {@link PkgDesc} describing this system-image package.
- */
- @NonNull
- public static Builder newAddonSysImg(@NonNull AndroidVersion version,
- @NonNull IdDisplay addonVendor,
- @NonNull IdDisplay addonName,
- @NonNull String abi,
- @NonNull MajorRevision revision) {
- Builder p = new Builder(PkgType.PKG_ADDON_SYS_IMAGE);
- p.mAndroidVersion = version;
- p.mMajorRevision = revision;
- p.mTag = addonName;
- p.mPath = abi;
- p.mVendor = addonVendor;
- return p;
- }
-
- /**
- * Create a new source package descriptor.
- *
- * @param version The android version of the source package.
- * @param revision The revision of the source package.
- * @return A {@link PkgDesc} describing this source package.
- */
- @NonNull
- public static Builder newSource(@NonNull AndroidVersion version,
- @NonNull MajorRevision revision) {
- Builder p = new Builder(PkgType.PKG_SOURCE);
- p.mAndroidVersion = version;
- p.mMajorRevision = revision;
- return p;
- }
-
- /**
- * Create a new sample package descriptor.
- *
- * @param version The android version of the sample package.
- * @param revision The revision of the sample package.
- * @param minToolsRev An optional {@code min-tools-rev}.
- * Use {@link FullRevision#NOT_SPECIFIED} to indicate
- * there is no requirement.
- * @return A {@link PkgDesc} describing this sample package.
- */
- @NonNull
- public static Builder newSample(@NonNull AndroidVersion version,
- @NonNull MajorRevision revision,
- @NonNull FullRevision minToolsRev) {
- Builder p = new Builder(PkgType.PKG_SAMPLE);
- p.mAndroidVersion = version;
- p.mMajorRevision = revision;
- p.mMinToolsRev = minToolsRev;
- return p;
- }
-
- /**
- * Creates a new NDK package descriptor.
- *
- * @param revision The revision of the NDK package.
- * @return A {@link PkgDesc} describing this NDK package.
- */
- @NonNull
- public static Builder newNdk(@NonNull FullRevision revision) {
- Builder p = new Builder(PkgType.PKG_NDK);
- p.mFullRevision = revision;
- return p;
- }
-
- public Builder setLicense(@Nullable License license) {
- mLicense = license;
- return this;
- }
-
- public Builder setListDisplay(@Nullable String text) {
- mListDisplay = text;
- return this;
- }
-
- public Builder setDescriptionShort(@Nullable String text) {
- mDescriptionShort = text;
- return this;
- }
-
- public Builder setDescriptionUrl(@Nullable String text) {
- mDescriptionUrl = text;
- return this;
- }
-
- public Builder setIsObsolete(boolean isObsolete) {
- mIsObsolete = isObsolete;
- return this;
- }
-
- public IPkgDesc create() {
- if (mType == PkgType.PKG_ADDON) {
- return new PkgDescAddon(
- mType,
- mLicense,
- mListDisplay,
- mDescriptionShort,
- mDescriptionUrl,
- mIsObsolete,
- mMajorRevision,
- mAndroidVersion,
- mVendor,
- mNameIdDisplay);
- }
-
- if (mType == PkgType.PKG_EXTRA) {
- return new PkgDescExtra(
- mType,
- mLicense,
- mListDisplay,
- mDescriptionShort,
- mDescriptionUrl,
- mIsObsolete,
- mFullRevision,
- mMajorRevision,
- mAndroidVersion,
- mPath,
- mTag,
- mVendor,
- mMinToolsRev,
- mMinPlatformToolsRev,
- mNameDisplay,
- mOldPaths);
- }
-
- return new PkgDesc(
- mType,
- mLicense,
- mListDisplay,
- mDescriptionShort,
- mDescriptionUrl,
- mIsObsolete,
- mFullRevision,
- mMajorRevision,
- mAndroidVersion,
- mPath,
- mTag,
- mVendor,
- mMinToolsRev,
- mMinPlatformToolsRev,
- mCustomIsUpdateFor,
- mCustomPath);
- }
- }
-
- // ---- Helpers -----
-
- @NonNull
- private static String sanitize(@NonNull String str) {
- str = str.toLowerCase(Locale.US).replaceAll("[^a-z0-9_.-]+", "_").replaceAll("_+", "_");
- return str;
- }
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescAddon.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescAddon.java
deleted file mode 100755
index 9694d1f..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescAddon.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidTargetHash;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.License;
-import com.android.sdklib.repository.MajorRevision;
-
-/**
- * Implementation detail of {@link PkgDesc} for add-ons.
- * Do not use this class directly.
- * To create an instance use {@link PkgDesc.Builder#newAddon} instead.
- */
-final class PkgDescAddon extends PkgDesc implements IPkgDescAddon {
-
- private final IdDisplay mAddonName;
-
- /**
- * Add-on descriptor.
- * The following attributes are mandatory:
- */
- PkgDescAddon(@NonNull PkgType type,
- @Nullable License license,
- @Nullable String listDisplay,
- @Nullable String descriptionShort,
- @Nullable String descriptionUrl,
- boolean isObsolete,
- @NonNull MajorRevision majorRevision,
- @NonNull AndroidVersion androidVersion,
- @NonNull IdDisplay addonVendor,
- @NonNull IdDisplay addonName) {
- super(type,
- license,
- listDisplay,
- descriptionShort,
- descriptionUrl,
- isObsolete,
- null, //fullRevision
- majorRevision,
- androidVersion,
- AndroidTargetHash.getAddonHashString(addonVendor.getDisplay(),
- addonName.getDisplay(),
- androidVersion),
- null, //tag
- addonVendor,
- null, //minToolsRev
- null, //minPlatformToolsRev
- null, //customIsUpdateFor
- null); //customPath
-
- mAddonName = addonName;
- }
-
- @NonNull
- @Override
- public IdDisplay getName() {
- return mAddonName;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java
deleted file mode 100755
index b5f2efb..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.License;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-
-/**
- * Implementation detail of {@link IPkgDescExtra} for extra packages.
- */
-public final class PkgDescExtra extends PkgDesc implements IPkgDescExtra {
-
- private final String[] mOldPaths;
- private final String mNameDisplay;
-
- PkgDescExtra(@NonNull PkgType type,
- @Nullable License license,
- @Nullable String listDisplay,
- @Nullable String descriptionShort,
- @Nullable String descriptionUrl,
- boolean isObsolete,
- @Nullable FullRevision fullRevision,
- @Nullable MajorRevision majorRevision,
- @Nullable AndroidVersion androidVersion,
- @Nullable String path,
- @Nullable IdDisplay tag,
- @Nullable IdDisplay vendor,
- @Nullable FullRevision minToolsRev,
- @Nullable FullRevision minPlatformToolsRev,
- @Nullable String nameDisplay,
- @Nullable final String[] oldPaths) {
- super(type,
- license,
- listDisplay,
- descriptionShort,
- descriptionUrl,
- isObsolete,
- fullRevision,
- majorRevision,
- androidVersion,
- path,
- tag,
- vendor,
- minToolsRev,
- minPlatformToolsRev,
- null, //customIsUpdateFor
- null); //customPath
- mNameDisplay = nameDisplay;
- mOldPaths = oldPaths != null ? oldPaths : new String[0];
- }
-
- @NonNull
- @Override
- public String[] getOldPaths() {
- return mOldPaths;
- }
-
- @NonNull
- @Override
- public String getNameDisplay() {
- return mNameDisplay == null ? String.format("Unknown (%s)", getInstallId()) : mNameDisplay;
- }
-
- // ---- Helpers ----
-
- /**
- * Helper method that converts the old_paths property string into the
- * an old paths array.
- *
- * @param oldPathsProperty A possibly-null old_path property string.
- * @return A list of old paths split by their separator. Can be empty but not null.
- */
- @NonNull
- public static String[] convertOldPaths(@Nullable String oldPathsProperty) {
- if (oldPathsProperty == null || oldPathsProperty.isEmpty()) {
- return new String[0];
- }
- return oldPathsProperty.split(";"); //$NON-NLS-1$
- }
-
- /**
- * Helper to computhe whether the extra path of both {@link IPkgDescExtra}s
- * are compatible with each other, which means they are either equal or are
- * matched between existing path and the potential old paths list.
- * <p/>
- * This also covers backward compatibility -- in earlier schemas the vendor id was
- * merged into the path string when reloading installed extras.
- *
- * @param lhs A non-null {@link IPkgDescExtra}.
- * @param rhs Another non-null {@link IPkgDescExtra}.
- * @return true if the paths are compatible.
- */
- public static boolean compatibleVendorAndPath(
- @NonNull IPkgDescExtra lhs,
- @NonNull IPkgDescExtra rhs) {
- String[] epOldPaths = rhs.getOldPaths();
- int lenEpOldPaths = epOldPaths.length;
- for (int indexEp = -1; indexEp < lenEpOldPaths; indexEp++) {
- if (sameVendorAndPath(
- lhs.getVendor().getId(), lhs.getPath(),
- rhs.getVendor().getId(), indexEp < 0 ? rhs.getPath() : epOldPaths[indexEp])) {
- return true;
- }
- }
-
- String[] thisOldPaths = lhs.getOldPaths();
- int lenThisOldPaths = thisOldPaths.length;
- for (int indexThis = -1; indexThis < lenThisOldPaths; indexThis++) {
- if (sameVendorAndPath(
- lhs.getVendor().getId(), indexThis < 0 ? lhs.getPath() : thisOldPaths[indexThis],
- rhs.getVendor().getId(), rhs.getPath())) {
- return true;
- }
- }
-
- return false;
- }
-
- private static boolean sameVendorAndPath(
- @Nullable String thisVendor, @Nullable String thisPath,
- @Nullable String otherVendor, @Nullable String otherPath) {
- // To be backward compatible, we need to support the old vendor-path form
- // in either the current or the remote package.
- //
- // The vendor test below needs to account for an old installed package
- // (e.g. with an install path of vendor-name) that has then been updated
- // in-place and thus when reloaded contains the vendor name in both the
- // path and the vendor attributes.
- if (otherPath != null && thisPath != null && thisVendor != null) {
- if (otherPath.equals(thisVendor + '-' + thisPath) &&
- (otherVendor == null ||
- otherVendor.isEmpty() ||
- otherVendor.equals(thisVendor))) {
- return true;
- }
- }
- if (thisPath != null && otherPath != null && otherVendor != null) {
- if (thisPath.equals(otherVendor + '-' + otherPath) &&
- (thisVendor == null ||
- thisVendor.isEmpty() ||
- thisVendor.equals(otherVendor))) {
- return true;
- }
- }
-
-
- if (thisPath != null && thisPath.equals(otherPath)) {
- if ((thisVendor == null && otherVendor == null) ||
- (thisVendor != null && thisVendor.equals(otherVendor))) {
- return true;
- }
- }
-
- return false;
- }
-
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java b/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java
deleted file mode 100755
index 9f0ccc2..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.internal.repository.LocalSdkParser;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.local.LocalSdk;
-
-import java.util.EnumSet;
-
-/**
- * Package types handled by the {@link LocalSdk}.
- * <p/>
- * Integer bit values are provided via {@link #getIntValue()} for backward
- * compatibility with the older {@link LocalSdkParser} class.
- * The integer bit values also indicate the natural ordering of the packages.
- */
-public enum PkgType implements IPkgCapabilities {
-
- // Boolean attributes below, in that order:
- // maj-r, full-r, api, path, tag, vend, min-t-r, min-pt-r
- //
- // Corresponding flags for list description pattern string:
- // $MAJ $FULL $API $PATH $TAG $VEND $NAME (for extras & add-ons)
- //
- //
-
- /** Filter the SDK/tools folder.
- * Has {@link FullRevision}. */
- PKG_TOOLS(0x0001, SdkConstants.FD_TOOLS,
- "Android SDK Tools $FULL",
- false, true /*full-r*/, false, false, false, false, false, true /*min-pt-r*/),
-
- /** Filter the SDK/platform-tools folder.
- * Has {@link FullRevision}. */
- PKG_PLATFORM_TOOLS(0x0002, SdkConstants.FD_PLATFORM_TOOLS,
- "Android SDK Platform-Tools $FULL",
- false, true /*full-r*/, false, false, false, false, false, false),
-
- /** Filter the SDK/build-tools folder.
- * Has {@link FullRevision}. */
- PKG_BUILD_TOOLS(0x0004, SdkConstants.FD_BUILD_TOOLS,
- "Android SDK Build-Tools $FULL",
- false, true /*full-r*/, false, false, false, false, false, false),
-
- /** Filter the SDK/docs folder.
- * Has {@link MajorRevision}. */
- PKG_DOC(0x0010, SdkConstants.FD_DOCS,
- "Documentation for Android SDK",
- true /*maj-r*/, false, true /*api*/, false, false, false, false, false),
-
- /** Filter the SDK/platforms.
- * Has {@link AndroidVersion}. Has {@link MajorRevision}.
- * Path returns the platform's target hash. */
- PKG_PLATFORM(0x0100, SdkConstants.FD_PLATFORMS,
- "Android SDK Platform $API{?$MAJ>1:, rev $MAJ}",
- true /*maj-r*/, false, true /*api*/, true /*path*/, false, false, true /*min-t-r*/, false),
-
- /** Filter the SDK/system-images/android.
- * Has {@link AndroidVersion}. Has {@link MajorRevision}. Has tag.
- * Path returns the system image ABI. */
- PKG_SYS_IMAGE(0x0200, SdkConstants.FD_SYSTEM_IMAGES,
- "$PATH System Image, Android $API{?$MAJ>1:, rev $MAJ}",
- true /*maj-r*/, false, true /*api*/, true /*path*/, true /*tag*/, false /*vend*/, false, false),
-
- /** Filter the SDK/addons.
- * Has {@link AndroidVersion}. Has {@link MajorRevision}.
- * Path returns the add-on's target hash. */
- PKG_ADDON(0x0400, SdkConstants.FD_ADDONS,
- "{|$NAME|$VEND $PATH|}, Android $API{?$MAJ>1:, rev $MAJ}",
- true /*maj-r*/, false, true /*api*/, true /*path*/, false, true /*vend*/, false, false),
-
- /** Filter the SDK/system-images/addons.
- * Has {@link AndroidVersion}. Has {@link MajorRevision}. Has tag.
- * Path returns the system image ABI. */
- PKG_ADDON_SYS_IMAGE(0x0800, SdkConstants.FD_SYSTEM_IMAGES,
- "{|$NAME|$VEND $PATH|} System Image, Android $API{?$MAJ>1:, rev $MAJ}",
- true /*maj-r*/, false, true /*api*/, true /*path*/, true /*tag*/, true /*vend*/, false, false),
-
- /** Filter the SDK/samples folder.
- * Note: this will not detect samples located in the SDK/extras packages.
- * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
- PKG_SAMPLE(0x1000, SdkConstants.FD_SAMPLES,
- "Samples for Android $API{?$MAJ>1:, rev $MAJ}",
- true /*maj-r*/, false, true /*api*/, false, false, false, true /*min-t-r*/, false),
-
- /** Filter the SDK/sources folder.
- * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
- PKG_SOURCE(0x2000, SdkConstants.FD_ANDROID_SOURCES,
- "Sources for Android $API{?$MAJ>1:, rev $MAJ}",
- true /*maj-r*/, false, true /*api*/, false, false, false, false, false),
-
- /** Filter the SDK/extras folder.
- * Has {@code Path}. Has {@link MajorRevision}.
- * Path returns the combined vendor id + extra path.
- * Cast the descriptor to {@link IPkgDescExtra} to get extra's specific attributes. */
- PKG_EXTRA(0x4000, SdkConstants.FD_EXTRAS,
- "{|$NAME|$VEND $PATH|}{?$FULL>1:, rev $FULL}",
- false, true /*full-r*/, false, true /*path*/, false, true /*vend*/, false, false),
-
- /** The SDK/ndk folder. */
- PKG_NDK(0x8000, SdkConstants.FD_NDK, "",
- false, true, false, false, false, false, false, false);
-
- /** A collection of all the known PkgTypes. */
- public static final EnumSet<PkgType> PKG_ALL = EnumSet.allOf(PkgType.class);
-
- /** Integer value matching all available pkg types, for the old LocalSdkParer. */
- public static final int PKG_ALL_INT = 0xFFFF;
-
- private int mIntValue;
- private String mFolderName;
-
- private final boolean mHasMajorRevision;
- private final boolean mHasFullRevision;
- private final boolean mHasAndroidVersion;
- private final boolean mHasPath;
- private final boolean mHasTag;
- private final boolean mHasVendor;
- private final boolean mHasMinToolsRev;
- private final boolean mHasMinPlatformToolsRev;
- private final String mListDisplayPattern;
-
- PkgType(int intValue,
- @NonNull String folderName,
- @NonNull String listDisplayPattern,
- boolean hasMajorRevision,
- boolean hasFullRevision,
- boolean hasAndroidVersion,
- boolean hasPath,
- boolean hasTag,
- boolean hasVendor,
- boolean hasMinToolsRev,
- boolean hasMinPlatformToolsRev) {
- mIntValue = intValue;
- mFolderName = folderName;
- mListDisplayPattern = listDisplayPattern;
- mHasMajorRevision = hasMajorRevision;
- mHasFullRevision = hasFullRevision;
- mHasAndroidVersion = hasAndroidVersion;
- mHasPath = hasPath;
- mHasTag = hasTag;
- mHasVendor = hasVendor;
- mHasMinToolsRev = hasMinToolsRev;
- mHasMinPlatformToolsRev = hasMinPlatformToolsRev;
- }
-
- /** Returns the integer value matching the type, compatible with the old LocalSdkParer. */
- public int getIntValue() {
- return mIntValue;
- }
-
- /** Returns the name of SDK top-folder where this type of package is stored. */
- @NonNull
- public String getFolderName() {
- return mFolderName;
- }
-
- @Override
- public boolean hasMajorRevision() {
- return mHasMajorRevision;
- }
-
- @Override
- public boolean hasFullRevision() {
- return mHasFullRevision;
- }
-
- @Override
- public boolean hasAndroidVersion() {
- return mHasAndroidVersion;
- }
-
- @Override
- public boolean hasPath() {
- return mHasPath;
- }
-
- @Override
- public boolean hasTag() {
- return mHasTag;
- }
-
- @Override
- public boolean hasVendor() {
- return mHasVendor;
- }
-
- @Override
- public boolean hasMinToolsRev() {
- return mHasMinToolsRev;
- }
-
- @Override
- public boolean hasMinPlatformToolsRev() {
- return mHasMinPlatformToolsRev;
- }
-
- /*
- * Returns a pattern string used by {@link PkgDesc#getListDescription()} to
- * compute a default list-display representation string for this package.
- */
- public String getListDisplayPattern() {
- return mListDisplayPattern;
- }
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java
deleted file mode 100755
index 7dda921..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.internal.androidTarget.AddOnTarget;
-import com.android.sdklib.internal.androidTarget.PlatformTarget;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.AddonManifestIniProps;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.descriptors.*;
-import com.android.utils.Pair;
-import com.google.common.base.Objects;
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.TreeMultimap;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
- at SuppressWarnings("MethodMayBeStatic")
-public class LocalAddonPkgInfo extends LocalPlatformPkgInfo {
-
- private static final Pattern PATTERN_LIB_DATA = Pattern.compile(
- "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
-
- // usb ids are 16-bit hexadecimal values.
- private static final Pattern PATTERN_USB_IDS = Pattern.compile(
- "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
-
- @NonNull
- private final IPkgDescAddon mAddonDesc;
-
- public LocalAddonPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull AndroidVersion version,
- @NonNull MajorRevision revision,
- @NonNull IdDisplay vendor,
- @NonNull IdDisplay name) {
- super(localSdk, localDir, sourceProps, version, revision, FullRevision.NOT_SPECIFIED);
- mAddonDesc = (IPkgDescAddon) PkgDesc.Builder.newAddon(version, revision, vendor, name)
- .create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mAddonDesc;
- }
-
- /** The "path" of an add-on is its Target Hash. */
- @Override
- @NonNull
- public String getTargetHash() {
- return getDesc().getPath();
- }
-
- //-----
-
- /**
- * Computes a sanitized name-id based on an addon name-display.
- * This is used to provide compatibility with older add-ons that lacks the new fields.
- *
- * @param displayName A name-display field or a old-style name field.
- * @return A non-null sanitized name-id that fits in the {@code [a-zA-Z0-9_-]+} pattern.
- */
- public static String sanitizeDisplayToNameId(@NonNull String displayName) {
- String name = displayName.toLowerCase(Locale.US);
- name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
-
- // Trim leading and trailing underscores
- if (name.length() > 1) {
- name = name.replaceAll("^_+", ""); //$NON-NLS-1$ //$NON-NLS-2$
- }
- if (name.length() > 1) {
- name = name.replaceAll("_+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
- }
- return name;
- }
-
- //-----
-
- /**
- * Creates the AddOnTarget. Invoked by {@link #getAndroidTarget()}.
- */
- @Override
- @Nullable
- protected IAndroidTarget createAndroidTarget() {
- LocalSdk sdk = getLocalSdk();
- IFileOp fileOp = sdk.getFileOp();
-
- // Parse the addon properties to ensure we can load it.
- Pair<Map<String, String>, String> infos = parseAddonProperties();
-
- Map<String, String> propertyMap = infos.getFirst();
- String error = infos.getSecond();
-
- if (error != null) {
- appendLoadError("Ignoring add-on '%1$s': %2$s", getLocalDir().getName(), error);
- return null;
- }
-
- // Since error==null we're not supposed to encounter any issues loading this add-on.
- try {
- assert propertyMap != null;
-
- String api = propertyMap.get(AddonManifestIniProps.ADDON_API);
- String name = propertyMap.get(AddonManifestIniProps.ADDON_NAME);
- String vendor = propertyMap.get(AddonManifestIniProps.ADDON_VENDOR);
-
- assert api != null;
- assert name != null;
- assert vendor != null;
-
- PlatformTarget baseTarget = null;
-
- // Look for a platform that has a matching api level or codename.
- LocalPkgInfo plat = sdk.getPkgInfo(PkgType.PKG_PLATFORM,
- getDesc().getAndroidVersion());
- if (plat instanceof LocalPlatformPkgInfo) {
- baseTarget = (PlatformTarget) ((LocalPlatformPkgInfo) plat).getAndroidTarget();
- }
- assert baseTarget != null;
-
- // get the optional description
- String description = propertyMap.get(AddonManifestIniProps.ADDON_DESCRIPTION);
-
- // get the add-on revision
- int revisionValue = 1;
- String revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION);
- if (revision == null) {
- revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION_OLD);
- }
- if (revision != null) {
- revisionValue = Integer.parseInt(revision);
- }
-
- // get the optional libraries
- String librariesValue = propertyMap.get(AddonManifestIniProps.ADDON_LIBRARIES);
- Map<String, String[]> libMap = null;
-
- if (librariesValue != null) {
- librariesValue = librariesValue.trim();
- if (!librariesValue.isEmpty()) {
- // split in the string into the libraries name
- String[] libraries = librariesValue.split(";"); //$NON-NLS-1$
- if (libraries.length > 0) {
- libMap = new HashMap<String, String[]>();
- for (String libName : libraries) {
- libName = libName.trim();
-
- // get the library data from the properties
- String libData = propertyMap.get(libName);
-
- if (libData != null) {
- // split the jar file from the description
- Matcher m = PATTERN_LIB_DATA.matcher(libData);
- if (m.matches()) {
- libMap.put(libName, new String[] {
- m.group(1), m.group(2) });
- } else {
- appendLoadError(
- "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
- libName, libData);
- }
- } else {
- appendLoadError(
- "Ignoring library '%1$s', missing property value",
- libName, libData);
- }
- }
- }
- }
- }
-
- // get the abi list.
- ISystemImage[] systemImages = getAddonSystemImages(fileOp);
-
- // check whether the add-on provides its own rendering info/library.
- boolean hasRenderingLibrary = false;
- boolean hasRenderingResources = false;
-
- File dataFolder = new File(getLocalDir(), SdkConstants.FD_DATA);
- if (fileOp.isDirectory(dataFolder)) {
- hasRenderingLibrary =
- fileOp.isFile(new File(dataFolder, SdkConstants.FN_LAYOUTLIB_JAR));
- hasRenderingResources =
- fileOp.isDirectory(new File(dataFolder, SdkConstants.FD_RES)) &&
- fileOp.isDirectory(new File(dataFolder, SdkConstants.FD_FONTS));
- }
-
- AddOnTarget target = new AddOnTarget(
- getLocalDir().getAbsolutePath(),
- name,
- vendor,
- revisionValue,
- description,
- systemImages,
- libMap,
- hasRenderingLibrary,
- hasRenderingResources,
- baseTarget);
-
- // parse the legacy skins, located under SDK/addons/addon-name/skins/[skin-name]
- // and merge with the system-image skins, if any, merging them by name.
- File targetSkinFolder = target.getFile(IAndroidTarget.SKINS);
-
- Map<String, File> skinsMap = new TreeMap<String, File>();
-
- for (File f : PackageParserUtils.parseSkinFolder(targetSkinFolder, fileOp)) {
- skinsMap.put(f.getName().toLowerCase(Locale.US), f);
- }
- for (ISystemImage si : systemImages) {
- for (File f : si.getSkins()) {
- skinsMap.put(f.getName().toLowerCase(Locale.US), f);
- }
- }
-
- List<File> skins = new ArrayList<File>(skinsMap.values());
- Collections.sort(skins);
-
- // get the default skin
- File defaultSkin = null;
- String defaultSkinName = propertyMap.get(AddonManifestIniProps.ADDON_DEFAULT_SKIN);
- if (defaultSkinName != null) {
- defaultSkin = new File(targetSkinFolder, defaultSkinName);
- } else {
- // No default skin name specified, use the first one from the addon
- // or the default from the platform.
- if (skins.size() == 1) {
- defaultSkin = skins.get(0);
- } else {
- defaultSkin = baseTarget.getDefaultSkin();
- }
- }
-
- // get the USB ID (if available)
- int usbVendorId = convertId(propertyMap.get(AddonManifestIniProps.ADDON_USB_VENDOR));
- if (usbVendorId != IAndroidTarget.NO_USB_ID) {
- target.setUsbVendorId(usbVendorId);
- }
-
- target.setSkins(skins.toArray(new File[skins.size()]), defaultSkin);
-
- return target;
-
- } catch (Exception e) {
- appendLoadError("Ignoring add-on '%1$s': error %2$s.",
- getLocalDir().getName(), e.toString());
- }
-
- return null;
-
- }
-
- /**
- * Parses the add-on properties and decodes any error that occurs when loading an addon.
- *
- * @return A pair with the property map and an error string. Both can be null but not at the
- * same time. If a non-null error is present then the property map must be ignored. The error
- * should be translatable as it might show up in the SdkManager UI.
- */
- @NonNull
- private Pair<Map<String, String>, String> parseAddonProperties() {
- Map<String, String> propertyMap = null;
- String error = null;
-
- IFileOp fileOp = getLocalSdk().getFileOp();
- File addOnManifest = new File(getLocalDir(), SdkConstants.FN_MANIFEST_INI);
-
- do {
- if (!fileOp.isFile(addOnManifest)) {
- error = String.format("File not found: %1$s", SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- try {
- propertyMap = ProjectProperties.parsePropertyStream(
- fileOp.newFileInputStream(addOnManifest),
- addOnManifest.getPath(),
- null /*log*/);
- if (propertyMap == null) {
- error = String.format("Failed to parse properties from %1$s",
- SdkConstants.FN_MANIFEST_INI);
- break;
- }
- } catch (FileNotFoundException e) {
- // this can happen if the system fails to open the file because of too many
- // open files.
- error = String.format("Failed to parse properties from %1$s: %2$s",
- SdkConstants.FN_MANIFEST_INI, e.getMessage());
- break;
- }
-
- // look for some specific values in the map.
- // we require name, vendor, and api
- String name = propertyMap.get(AddonManifestIniProps.ADDON_NAME);
- if (name == null) {
- error = addonManifestWarning(AddonManifestIniProps.ADDON_NAME);
- break;
- }
-
- String vendor = propertyMap.get(AddonManifestIniProps.ADDON_VENDOR);
- if (vendor == null) {
- error = addonManifestWarning(AddonManifestIniProps.ADDON_VENDOR);
- break;
- }
-
- String api = propertyMap.get(AddonManifestIniProps.ADDON_API);
- if (api == null) {
- error = addonManifestWarning(AddonManifestIniProps.ADDON_API);
- break;
- }
-
- // Look for a platform that has a matching api level or codename.
- IAndroidTarget baseTarget = null;
- LocalPkgInfo plat = getLocalSdk().getPkgInfo(PkgType.PKG_PLATFORM,
- getDesc().getAndroidVersion());
- if (plat instanceof LocalPlatformPkgInfo) {
- baseTarget = ((LocalPlatformPkgInfo) plat).getAndroidTarget();
- }
-
- if (baseTarget == null) {
- error = String.format("Unable to find base platform with API level '%1$s'", api);
- break;
- }
-
- // get the add-on revision
- String revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION);
- if (revision == null) {
- revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION_OLD);
- }
- if (revision != null) {
- try {
- Integer.parseInt(revision);
- } catch (NumberFormatException e) {
- // looks like revision does not parse to a number.
- error = String.format("%1$s is not a valid number in %2$s.",
- AddonManifestIniProps.ADDON_REVISION, SdkConstants.FN_BUILD_PROP);
- break;
- }
- }
-
- } while(false);
-
- return Pair.of(propertyMap, error);
- }
-
- /**
- * Prepares a warning about the addon being ignored due to a missing manifest value.
- * This string will show up in the SdkManager UI.
- *
- * @param valueName The missing manifest value, for display.
- */
- @NonNull
- private static String addonManifestWarning(@NonNull String valueName) {
- return String.format("'%1$s' is missing from %2$s.",
- valueName, SdkConstants.FN_MANIFEST_INI);
- }
-
- /**
- * Converts a string representation of an hexadecimal ID into an int.
- * @param value the string to convert.
- * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the conversion failed.
- */
- private int convertId(@Nullable String value) {
- if (value != null && !value.isEmpty()) {
- if (PATTERN_USB_IDS.matcher(value).matches()) {
- String v = value.substring(2);
- try {
- return Integer.parseInt(v, 16);
- } catch (NumberFormatException e) {
- // this shouldn't happen since we check the pattern above, but this is safer.
- // the method will return 0 below.
- }
- }
- }
-
- return IAndroidTarget.NO_USB_ID;
- }
-
- /**
- * Get all the system images supported by an add-on target.
- * For an add-on, we first look in the new sdk/system-images folders then we look
- * for sub-folders in the addon/images directory.
- * If none are found but the directory exists and is not empty, assume it's a legacy
- * arm eabi system image.
- * If any given API appears twice or more, the first occurrence wins.
- * <p/>
- * Note that it's OK for an add-on to have no system-images at all, since it can always
- * rely on the ones from its base platform.
- *
- * @param fileOp File operation wrapper.
- * @return an array of ISystemImage containing all the system images for the target.
- * The list can be empty but not null.
- */
- @NonNull
- private ISystemImage[] getAddonSystemImages(IFileOp fileOp) {
- Set<ISystemImage> found = new TreeSet<ISystemImage>();
- SetMultimap<IdDisplay, String> tagToAbiFound = TreeMultimap.create();
-
-
- // Look in the system images folders:
- // - SDK/system-image/platform/addon-id-tag/abi
- // - SDK/system-image/addon-id-tag/abi (many abi possible)
- // Optional: look for skins under
- // - SDK/system-image/platform/addon-id-tag/abi/skins/skin-name
- // - SDK/system-image/addon-id-tag/abi/skins/skin-name
- // If we find multiple occurrences of the same platform/abi, the first one read wins.
-
- LocalPkgInfo[] sysImgInfos = getLocalSdk().getPkgsInfos(PkgType.PKG_ADDON_SYS_IMAGE);
- for (LocalPkgInfo pkg : sysImgInfos) {
- IPkgDesc d = pkg.getDesc();
- if (pkg instanceof LocalAddonSysImgPkgInfo &&
- d.hasVendor() &&
- mAddonDesc.getVendor().equals(d.getVendor()) &&
- mAddonDesc.getName().equals(d.getTag()) &&
- Objects.equal(mAddonDesc.getAndroidVersion(), pkg.getDesc().getAndroidVersion())) {
- final IdDisplay tag = mAddonDesc.getName();
- final String abi = d.getPath();
- if (abi != null && !tagToAbiFound.containsEntry(tag, abi)) {
- found.add(((LocalAddonSysImgPkgInfo)pkg).getSystemImage());
- tagToAbiFound.put(tag, abi);
- }
- }
- }
-
- // Look for sub-directories:
- // - SDK/addons/addon-name/images/abi (multiple abi possible)
- // - SDK/addons/addon-name/armeabi (legacy support)
- boolean useLegacy = true;
- boolean hasImgFiles = false;
- final IdDisplay defaultTag = SystemImage.DEFAULT_TAG;
-
- File imagesDir = new File(getLocalDir(), SdkConstants.OS_IMAGES_FOLDER);
- File[] files = fileOp.listFiles(imagesDir);
- for (File file : files) {
- if (fileOp.isDirectory(file)) {
- useLegacy = false;
- String abi = file.getName();
- if (!tagToAbiFound.containsEntry(defaultTag, abi)) {
- found.add(new SystemImage(
- file,
- LocationType.IN_IMAGES_SUBFOLDER,
- SystemImage.DEFAULT_TAG,
- mAddonDesc.getVendor(),
- abi,
- FileOp.EMPTY_FILE_ARRAY));
- tagToAbiFound.put(defaultTag, abi);
- }
- } else if (!hasImgFiles && fileOp.isFile(file)) {
- if (file.getName().endsWith(".img")) { //$NON-NLS-1$
- // The legacy images folder is only valid if it contains some .img files
- hasImgFiles = true;
- }
- }
- }
-
- if (useLegacy &&
- hasImgFiles &&
- fileOp.isDirectory(imagesDir) &&
- !tagToAbiFound.containsEntry(defaultTag, SdkConstants.ABI_ARMEABI)) {
- // We found no sub-folder system images but it looks like the top directory
- // has some img files in it. It must be a legacy ARM EABI system image folder.
- found.add(new SystemImage(
- imagesDir,
- LocationType.IN_LEGACY_FOLDER,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI,
- FileOp.EMPTY_FILE_ARRAY));
- }
-
- return found.toArray(new ISystemImage[found.size()]);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java
deleted file mode 100755
index e02c8e9..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * Local add-on system-image package, for a given addon's {@link AndroidVersion} and given ABI.
- * The system-image tag is the add-on name.
- * The package itself has a major revision.
- * There should be only one for a given android platform version & ABI.
- */
-public class LocalAddonSysImgPkgInfo extends LocalPkgInfo {
-
-
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalAddonSysImgPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull AndroidVersion version,
- @Nullable IdDisplay addonVendor,
- @Nullable IdDisplay addonName,
- @NonNull String abi,
- @NonNull MajorRevision revision) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newAddonSysImg(version, addonVendor, addonName, abi, revision)
- .create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-
- public ISystemImage getSystemImage() {
- return LocalSysImgPkgInfo.getSystemImage(mDesc, getLocalDir(), getLocalSdk().getFileOp());
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java
deleted file mode 100755
index 0251cc3..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-public class LocalBuildToolPkgInfo extends LocalPkgInfo {
-
-
- @Nullable
- private final BuildToolInfo mBuildToolInfo;
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalBuildToolPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull FullRevision revision,
- @Nullable BuildToolInfo btInfo) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newBuildTool(revision).create();
- mBuildToolInfo = btInfo;
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-
- @Nullable
- public BuildToolInfo getBuildToolInfo() {
- return mBuildToolInfo;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java
deleted file mode 100755
index 7e3a791..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.zip.Adler32;
-
-/**
- * Keeps information on a visited directory to quickly determine if it
- * has changed later. A directory has changed if its timestamp has been
- * modified, or if an underlying source.properties file has changed in
- * timestamp or checksum.
- * <p/>
- * Note that depending on the filesystem & OS, the content of the files in
- * a directory can change without the directory's last-modified property
- * changing. To have a consistent behavior between OSes, we compute a quick
- * checksum using all the files & directories modified timestamps.
- * The content of files is not included though, except for the checksum on
- * the source.property file since this one is the most important for the SDK.
- * <p/>
- * The {@link #hashCode()} and {@link #equals(Object)} methods directly
- * defer to the underlying File object. This allows the DirInfo to be placed
- * into a map and still call {@link Map#containsKey(Object)} with a File
- * object to check whether there's a corresponding DirInfo in the map.
- */
-class LocalDirInfo {
- @NonNull
- private final IFileOp mFileOp;
- @NonNull
- private final File mDir;
- private final long mDirModifiedTS;
- private final long mDirChecksum;
- private final long mPropsModifiedTS;
- private final long mPropsChecksum;
-
- /**
- * Creates a new immutable {@link LocalDirInfo}.
- *
- * @param fileOp The {@link FileOp} to use for all file-based interactions.
- * @param dir The platform/addon directory of the target. It should be a directory.
- */
- public LocalDirInfo(@NonNull IFileOp fileOp, @NonNull File dir) {
- mFileOp = fileOp;
- mDir = dir;
- mDirModifiedTS = mFileOp.lastModified(dir);
-
- // Capture some info about the source.properties file if it exists.
- // We use propsModifiedTS == 0 to mean there is no props file.
- long propsChecksum = 0;
- long propsModifiedTS = 0;
- File props = new File(dir, SdkConstants.FN_SOURCE_PROP);
- if (mFileOp.isFile(props)) {
- propsModifiedTS = mFileOp.lastModified(props);
- propsChecksum = getFileChecksum(props);
- }
- mPropsModifiedTS = propsModifiedTS;
- mPropsChecksum = propsChecksum;
- mDirChecksum = getDirChecksum(mDir);
- }
-
- /**
- * Checks whether the directory/source.properties attributes have changed.
- *
- * @return True if the directory modified timestamp or
- * its source.property files have changed.
- */
- public boolean hasChanged() {
- // Does platform directory still exist?
- if (!mFileOp.isDirectory(mDir)) {
- return true;
- }
- // Has platform directory modified-timestamp changed?
- if (mDirModifiedTS != mFileOp.lastModified(mDir)) {
- return true;
- }
-
- File props = new File(mDir, SdkConstants.FN_SOURCE_PROP);
-
- // The directory did not have a props file if target was null or
- // if mPropsModifiedTS is 0.
- boolean hadProps = mPropsModifiedTS != 0;
-
- // Was there a props file and it vanished, or there wasn't and there's one now?
- if (hadProps != mFileOp.isFile(props)) {
- return true;
- }
-
- if (hadProps) {
- // Has source.props file modified-timestamp changed?
- if (mPropsModifiedTS != mFileOp.lastModified(props)) {
- return true;
- }
- // Had the content of source.props changed?
- if (mPropsChecksum != getFileChecksum(props)) {
- return true;
- }
- }
-
- // Has the deep directory checksum changed?
- if (mDirChecksum != getDirChecksum(mDir)) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Computes an adler32 checksum (source.props are small files, so this
- * should be OK with an acceptable collision rate.)
- */
- private long getFileChecksum(@NonNull File file) {
- InputStream fis = null;
- try {
- fis = mFileOp.newFileInputStream(file);
- Adler32 a = new Adler32();
- byte[] buf = new byte[1024];
- int n;
- while ((n = fis.read(buf)) > 0) {
- a.update(buf, 0, n);
- }
- return a.getValue();
- } catch (Exception ignore) {
- } finally {
- try {
- if (fis != null) {
- fis.close();
- }
- } catch(Exception ignore) {}
- }
- return 0;
- }
-
- /**
- * Computes a checksum using the last-modified attributes of all
- * the files and <em>first-level</em>directories in this root directory.
- * <p/>
- * Heuristic: the SDK Manager updates package by replacing whole directories
- * so we don't need to do a recursive deep-first checksum of all files. Only
- * the top-level of the package directory should be sufficient to detect
- * SDK updates.
- */
- private long getDirChecksum(@NonNull File dir) {
- long checksum = mFileOp.lastModified(dir);
-
- // Get the file & directory list sorted by case-insensitive name
- // to make the checksum more consistent.
- File[] files = mFileOp.listFiles(dir);
- Arrays.sort(files, new Comparator<File>() {
- @Override
- public int compare(File o1, File o2) {
- return o1.getName().compareToIgnoreCase(o2.getName());
- }
- });
- for (File file : files) {
- checksum = 31 * checksum | mFileOp.lastModified(file);
- }
- return checksum;
- }
-
- /** Returns a visual representation of this object for debugging. */
- @Override
- public String toString() {
- String s = String.format("<DirInfo %1$s TS=%2$d", mDir, mDirModifiedTS); //$NON-NLS-1$
- if (mPropsModifiedTS != 0) {
- s += String.format(" | Props TS=%1$d, Chksum=%2$s", //$NON-NLS-1$
- mPropsModifiedTS, mPropsChecksum);
- }
- return s + ">"; //$NON-NLS-1$
- }
-
- /**
- * Returns the hashCode of the underlying File object.
- * <p/>
- * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
- * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
- * return the properties of the underlying File object.
- *
- * @see File#hashCode()
- */
- @Override
- public int hashCode() {
- return mDir.hashCode();
- }
-
- /**
- * Checks equality of the underlying File object.
- * <p/>
- * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
- * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
- * return the properties of the underlying File object.
- *
- * @see File#equals(Object)
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof File) {
- return mDir.equals(obj);
- } else if (obj instanceof LocalDirInfo) {
- return mDir.equals(((LocalDirInfo) obj).mDir);
- } else if (obj instanceof MapComparator) {
- return mDir.equals(((MapComparator) obj).mDir);
- }
- return false;
- }
-
- /**
- * Helper for Map.contains() to make sure we're comparing the inner directory File
- * object and not the outer wrapper itself.
- */
- public static class MapComparator {
- private final File mDir;
-
- public MapComparator(File dir) {
- mDir = dir;
- }
-
- /**
- * Returns the hashCode of the underlying File object.
- * <p/>
- * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
- * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
- * return the properties of the underlying File object.
- *
- * @see File#hashCode()
- */
- @Override
- public int hashCode() {
- return mDir.hashCode();
- }
-
- /**
- * Checks equality of the underlying File object.
- * <p/>
- * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
- * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
- * return the properties of the underlying File object.
- *
- * @see File#equals(Object)
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof File) {
- return mDir.equals(obj);
- } else if (obj instanceof LocalDirInfo) {
- return mDir.equals(((LocalDirInfo) obj).mDir);
- } else if (obj instanceof MapComparator) {
- return mDir.equals(((MapComparator) obj).mDir);
- }
- return false;
- }
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java
deleted file mode 100755
index 004f450..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-public class LocalDocPkgInfo extends LocalPkgInfo {
-
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalDocPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull AndroidVersion version,
- @NonNull MajorRevision revision) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newDoc(version, revision).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.java
deleted file mode 100755
index 5ec811b..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IPkgDescExtra;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-public class LocalExtraPkgInfo extends LocalPkgInfo {
-
- @NonNull
- private final IPkgDescExtra mDesc;
-
- public LocalExtraPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull IdDisplay vendor,
- @NonNull String path,
- @Nullable String displayName,
- @NonNull String[] oldPaths,
- @NonNull NoPreviewRevision revision) {
- super(localSdk, localDir, sourceProps);
- mDesc = (IPkgDescExtra) PkgDesc.Builder.newExtra(
- vendor,
- path,
- displayName,
- oldPaths,
- revision).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-
- @NonNull
- public String[] getOldPaths() {
- return mDesc.getOldPaths();
- }
-
- // --- helpers ---
-
- /**
- * Used to produce a suitable name-display based on the extra's path
- * and vendor display string in addon-3 schemas.
- *
- * @param vendor The vendor id of the extra.
- * @param extraPath The non-null path of the extra.
- * @return A non-null display name based on the extra's path id.
- */
- public static String getPrettyName(@Nullable IdDisplay vendor, @NonNull String extraPath) {
- String name = extraPath;
-
- // In the past, we used to save the extras in a folder vendor-path,
- // and that "vendor" would end up in the path when we reload the extra from
- // disk. Detect this and compensate.
- String disp = vendor == null ? null : vendor.getDisplay();
- if (disp != null && !disp.isEmpty()) {
- if (name.startsWith(disp + "-")) { //$NON-NLS-1$
- name = name.substring(disp.length() + 1);
- }
- }
-
- // Uniformize all spaces in the name
- if (name != null) {
- name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- }
- if (name == null || name.isEmpty()) {
- name = "Unknown Extra";
- }
-
- if (disp != null && !disp.isEmpty()) {
- name = disp + " " + name; //$NON-NLS-1$
- name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- // Look at all lower case characters in range [1..n-1] and replace them by an upper
- // case if they are preceded by a space. Also upper cases the first character of the
- // string.
- boolean changed = false;
- char[] chars = name.toCharArray();
- for (int n = chars.length - 1, i = 0; i < n; i++) {
- if (Character.isLowerCase(chars[i]) && (i == 0 || chars[i - 1] == ' ')) {
- chars[i] = Character.toUpperCase(chars[i]);
- changed = true;
- }
- }
- if (changed) {
- name = new String(chars);
- }
-
- // Special case: reformat a few typical acronyms.
- name = name.replaceAll(" Usb ", " USB "); //$NON-NLS-1$
- name = name.replaceAll(" Api ", " API "); //$NON-NLS-1$
-
- return name;
- }
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalNdkPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalNdkPkgInfo.java
deleted file mode 100644
index 8858300..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalNdkPkgInfo.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * Local package representing the Android NDK
- */
-public class LocalNdkPkgInfo extends LocalPkgInfo {
- @NonNull
- private final IPkgDesc mDesc;
-
- protected LocalNdkPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull FullRevision revision) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newNdk(revision).setDescriptionShort("Android NDK").setListDisplay("Android NDK").create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java
deleted file mode 100755
index 4bdc308..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.IDescription;
-import com.android.sdklib.repository.IListDescription;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * Information about a locally installed package.
- * <p/>
- * Local package information is retrieved via the {@link LocalSdk} object.
- * Clients should not need to create instances of {@link LocalPkgInfo} directly.
- * Instead please use the {@link LocalSdk} methods to parse and retrieve packages.
- * <p/>
- */
-public abstract class LocalPkgInfo
- implements IDescription, IListDescription, Comparable<LocalPkgInfo> {
-
- private final LocalSdk mLocalSdk;
- private final File mLocalDir;
- private final Properties mSourceProperties;
-
- private String mLoadError;
-
- protected LocalPkgInfo(@NonNull LocalSdk localSdk, @NonNull File localDir, @NonNull Properties sourceProps) {
- mLocalSdk = localSdk;
- mLocalDir = localDir;
- mSourceProperties = sourceProps;
- }
-
- //---- Attributes ----
-
- @NonNull
- public LocalSdk getLocalSdk() {
- return mLocalSdk;
- }
-
- @NonNull
- public File getLocalDir() {
- return mLocalDir;
- }
-
- @NonNull
- public Properties getSourceProperties() {
- return mSourceProperties;
- }
-
- @Nullable
- public String getLoadError() {
- return mLoadError;
- }
-
- // ----
-
- /**
- * Returns the {@link IPkgDesc} describing this package.
- */
- @NonNull
- public abstract IPkgDesc getDesc();
-
-
- //---- Ordering ----
-
- /**
- * Comparison is solely done based on the {@link IPkgDesc}.
- * <p/>
- * Other local attributes (local directory, source properties)
- * are <em>not used</em> in the comparison. Consequently {@link #compareTo(LocalPkgInfo)}
- * does not match {@link #equals(Object)} and the {@link #hashCode()} properties.
- */
- @Override
- public int compareTo(@NonNull LocalPkgInfo o) {
- return getDesc().compareTo(o.getDesc());
- }
-
- /**
- * String representation for debugging purposes.
- */
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append('<').append(this.getClass().getSimpleName()).append(' ');
- builder.append(getDesc().toString());
- builder.append('>');
- return builder.toString();
- }
-
- /**
- * Computes a hash code specific to this instance based on the underlying
- * {@link IPkgDesc} but also specific local properties such a local directory,
- * and actual source properties.
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((getDesc() == null) ? 0 : getDesc().hashCode());
- result = prime * result + ((mLocalDir == null) ? 0 : mLocalDir.hashCode());
- result = prime * result + ((mSourceProperties == null) ? 0 : mSourceProperties.hashCode());
- return result;
- }
-
- /**
- * Computes object equality to this instance based on the underlying
- * {@link IPkgDesc} but also specific local properties such a local directory,
- * update available and actual source properties. This is different from
- * the behavior of {@link #compareTo(LocalPkgInfo)} which only uses the
- * {@link IPkgDesc} for ordering.
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof LocalPkgInfo)) {
- return false;
- }
- LocalPkgInfo other = (LocalPkgInfo)obj;
-
- if (!getDesc().equals(other.getDesc())) {
- return false;
- }
- if (mLocalDir == null) {
- if (other.mLocalDir != null) {
- return false;
- }
- }
- else if (!mLocalDir.equals(other.mLocalDir)) {
- return false;
- }
- if (mSourceProperties == null) {
- if (other.mSourceProperties != null) {
- return false;
- }
- }
- else if (!mSourceProperties.equals(other.mSourceProperties)) {
- return false;
- }
- return true;
- }
-
-
- //---- Package Management ----
-
- /**
- * A "broken" package is installed but is not fully operational.
- * <p/>
- * For example an addon that lacks its underlying platform or a tool package
- * that lacks some of its binaries or essentially files.
- * <p/>
- * Operational code should generally ignore broken packages.
- * Only the SDK Updater cares about displaying them so that they can be fixed.
- */
- public boolean hasLoadError() {
- return mLoadError != null;
- }
-
- void appendLoadError(@NonNull String format, Object... params) {
- String loadError = String.format(format, params);
- if (mLoadError == null) {
- mLoadError = loadError;
- }
- else {
- mLoadError = mLoadError + '\n' + loadError;
- }
- }
-
- @NonNull
- @Override
- public String getListDescription() {
- return getDesc().getListDescription();
- }
-
- @Override
- public String getShortDescription() {
- // TODO revisit to differentiate from list-description depending
- // on how we'll use it in the sdkman UI.
- return getListDescription();
- }
-
- @Override
- public String getLongDescription() {
- StringBuilder sb = new StringBuilder();
- IPkgDesc desc = getDesc();
-
- sb.append(desc.getListDescription()).append('\n');
-
- if (desc.hasVendor()) {
- assert desc.getVendor() != null;
- sb.append("By ").append(desc.getVendor().getDisplay()).append('\n');
- }
-
- if (desc.hasMinPlatformToolsRev()) {
- assert desc.getMinPlatformToolsRev() != null;
- sb.append("Requires Platform-Tools revision ").append(desc.getMinPlatformToolsRev().toShortString()).append('\n');
- }
-
- if (desc.hasMinToolsRev()) {
- assert desc.getMinToolsRev() != null;
- sb.append("Requires Tools revision ").append(desc.getMinToolsRev().toShortString()).append('\n');
- }
-
- sb.append("Location: ").append(mLocalDir.getAbsolutePath());
-
- return sb.toString();
- }
-
- /**
- * Deletes the files in the SDK corresponding to this package.
- */
- public void delete() {
- new FileOp().deleteFileOrFolder(getLocalDir());
- }
-
-}
-
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java
deleted file mode 100755
index 534677e..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.IAndroidTarget.OptionalLibrary;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.SdkManager.LayoutlibVersion;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.internal.androidTarget.OptionalLibraryImpl;
-import com.android.sdklib.internal.androidTarget.PlatformTarget;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.android.sdklib.repository.descriptors.PkgType;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.TreeMultimap;
-import com.google.common.io.Files;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeSet;
-
- at SuppressWarnings("ConstantConditions")
-public class LocalPlatformPkgInfo extends LocalPkgInfo {
-
- public static final String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$
- public static final String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$
- public static final String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$
-
- @NonNull
- private final IPkgDesc mDesc;
-
- /** Android target, lazyly loaded from #getAndroidTarget */
- private IAndroidTarget mTarget;
- private boolean mLoaded;
-
- public LocalPlatformPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull AndroidVersion version,
- @NonNull MajorRevision revision,
- @NonNull FullRevision minToolsRev) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newPlatform(version, revision, minToolsRev).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-
- /** The "path" of a Platform is its Target Hash. */
- @NonNull
- public String getTargetHash() {
- return getDesc().getPath();
- }
-
- @Nullable
- public IAndroidTarget getAndroidTarget() {
- if (!mLoaded) {
- mTarget = createAndroidTarget();
- mLoaded = true;
- }
- return mTarget;
- }
-
- public boolean isLoaded() {
- return mLoaded;
- }
-
- //-----
-
- /**
- * Creates the PlatformTarget. Invoked by {@link #getAndroidTarget()}.
- */
- @SuppressWarnings("ConstantConditions")
- @Nullable
- protected IAndroidTarget createAndroidTarget() {
- LocalSdk sdk = getLocalSdk();
- IFileOp fileOp = sdk.getFileOp();
- File platformFolder = getLocalDir();
- File buildProp = new File(platformFolder, SdkConstants.FN_BUILD_PROP);
- File sourcePropFile = new File(platformFolder, SdkConstants.FN_SOURCE_PROP);
-
- if (!fileOp.isFile(buildProp) || !fileOp.isFile(sourcePropFile)) {
- appendLoadError("Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
- platformFolder.getName(),
- SdkConstants.FN_BUILD_PROP);
- return null;
- }
-
- Map<String, String> platformProp = new HashMap<String, String>();
-
- // add all the property files
- Map<String, String> map = null;
-
- try {
- map = ProjectProperties.parsePropertyStream(
- fileOp.newFileInputStream(buildProp),
- buildProp.getPath(),
- null /*log*/);
- if (map != null) {
- platformProp.putAll(map);
- }
- } catch (FileNotFoundException ignore) {}
-
- try {
- map = ProjectProperties.parsePropertyStream(
- fileOp.newFileInputStream(sourcePropFile),
- sourcePropFile.getPath(),
- null /*log*/);
- if (map != null) {
- platformProp.putAll(map);
- }
- } catch (FileNotFoundException ignore) {}
-
- File sdkPropFile = new File(platformFolder, SdkConstants.FN_SDK_PROP);
- if (fileOp.isFile(sdkPropFile)) { // obsolete platforms don't have this.
- try {
- map = ProjectProperties.parsePropertyStream(
- fileOp.newFileInputStream(sdkPropFile),
- sdkPropFile.getPath(),
- null /*log*/);
- if (map != null) {
- platformProp.putAll(map);
- }
- } catch (FileNotFoundException ignore) {}
- }
-
- // look for some specific values in the map.
-
- // api level
- int apiNumber;
- String stringValue = platformProp.get(PROP_VERSION_SDK);
- if (stringValue == null) {
- appendLoadError("Ignoring platform '%1$s': %2$s is missing from '%3$s'",
- platformFolder.getName(), PROP_VERSION_SDK,
- SdkConstants.FN_BUILD_PROP);
- return null;
- } else {
- try {
- apiNumber = Integer.parseInt(stringValue);
- } catch (NumberFormatException e) {
- // looks like apiNumber does not parse to a number.
- // Ignore this platform.
- appendLoadError(
- "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
- platformFolder.getName(), PROP_VERSION_SDK,
- SdkConstants.FN_BUILD_PROP);
- return null;
- }
- }
-
- // Codename must be either null or a platform codename.
- // REL means it's a release version and therefore the codename should be null.
- AndroidVersion apiVersion =
- new AndroidVersion(apiNumber, platformProp.get(PROP_VERSION_CODENAME));
-
- // version string
- String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
- if (apiName == null) {
- apiName = platformProp.get(PROP_VERSION_RELEASE);
- }
- if (apiName == null) {
- appendLoadError(
- "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
- platformFolder.getName(), PROP_VERSION_RELEASE,
- SdkConstants.FN_BUILD_PROP);
- return null;
- }
-
- // platform rev number & layoutlib version are extracted from the source.properties
- // saved by the SDK Manager when installing the package.
-
- int revision = 1;
- LayoutlibVersion layoutlibVersion = null;
- try {
- revision = Integer.parseInt(platformProp.get(PkgProps.PKG_REVISION));
- } catch (NumberFormatException e) {
- // do nothing, we'll keep the default value of 1.
- }
-
- try {
- String propApi = platformProp.get(PkgProps.LAYOUTLIB_API);
- String propRev = platformProp.get(PkgProps.LAYOUTLIB_REV);
- int llApi = propApi == null ? LayoutlibVersion.NOT_SPECIFIED :
- Integer.parseInt(propApi);
- int llRev = propRev == null ? LayoutlibVersion.NOT_SPECIFIED :
- Integer.parseInt(propRev);
- if (llApi > LayoutlibVersion.NOT_SPECIFIED &&
- llRev >= LayoutlibVersion.NOT_SPECIFIED) {
- layoutlibVersion = new LayoutlibVersion(llApi, llRev);
- }
- } catch (NumberFormatException e) {
- // do nothing, we'll ignore the layoutlib version if it's invalid
- }
-
- // api number and name look valid, perform a few more checks
- String err = checkPlatformContent(fileOp, platformFolder);
- if (err != null) {
- appendLoadError("%s", err); //$NLN-NLS-1$
- return null;
- }
-
- ISystemImage[] systemImages = getPlatformSystemImages(fileOp, platformFolder, apiVersion);
-
- // create the target.
- PlatformTarget pt = new PlatformTarget(
- sdk.getLocation().getPath(),
- platformFolder.getAbsolutePath(),
- apiVersion,
- apiName,
- revision,
- layoutlibVersion,
- systemImages,
- platformProp,
- getOptionalLibraries(platformFolder),
- sdk.getLatestBuildTool());
-
- // add the skins from the platform. Make a copy to not modify the original collection.
- List<File> skins = new ArrayList<File>(PackageParserUtils.parseSkinFolder(pt.getFile(IAndroidTarget.SKINS), fileOp));
-
- // add the system-image specific skins, if any.
- for (ISystemImage systemImage : systemImages) {
- skins.addAll(Arrays.asList(systemImage.getSkins()));
- }
-
- pt.setSkins(skins.toArray(new File[skins.size()]));
-
- // add path to the non-legacy samples package if it exists
- LocalPkgInfo samples = sdk.getPkgInfo(PkgType.PKG_SAMPLE, getDesc().getAndroidVersion());
- if (samples != null) {
- pt.setSamplesPath(samples.getLocalDir().getAbsolutePath());
- }
-
- // add path to the non-legacy sources package if it exists
- LocalPkgInfo sources = sdk.getPkgInfo(PkgType.PKG_SOURCE, getDesc().getAndroidVersion());
- if (sources != null) {
- pt.setSourcesPath(sources.getLocalDir().getAbsolutePath());
- }
-
- return pt;
- }
-
- /**
- * Get all the system images supported by a platform target.
- * For a platform, we first look in the new sdk/system-images folders then we
- * look for sub-folders in the platform/images directory and/or the one legacy
- * folder.
- * If any given API appears twice or more, the first occurrence wins.
- *
- * @param fileOp File operation wrapper.
- * @param platformDir Root of the platform target being loaded.
- * @param apiVersion API level + codename of platform being loaded.
- * @return an array of ISystemImage containing all the system images for the target.
- * The list can be empty but not null.
- */
- @NonNull
- private ISystemImage[] getPlatformSystemImages(IFileOp fileOp,
- File platformDir,
- AndroidVersion apiVersion) {
- Set<ISystemImage> found = new TreeSet<ISystemImage>();
- SetMultimap<IdDisplay, String> tagToAbiFound = TreeMultimap.create();
-
-
- // Look in the SDK/system-image/platform-n/tag/abi folders.
- // Look in the SDK/system-image/platform-n/abi folders.
- // If we find multiple occurrences of the same platform/abi, the first one read wins.
-
- LocalPkgInfo[] sysImgInfos = getLocalSdk().getPkgsInfos(PkgType.PKG_SYS_IMAGE);
- for (LocalPkgInfo pkg : sysImgInfos) {
- IPkgDesc d = pkg.getDesc();
- if (pkg instanceof LocalSysImgPkgInfo &&
- !d.hasVendor() &&
- apiVersion.equals(d.getAndroidVersion())) {
- IdDisplay tag = d.getTag();
- String abi = d.getPath();
- if (tag != null && abi != null && !tagToAbiFound.containsEntry(tag, abi)) {
- found.add(((LocalSysImgPkgInfo)pkg).getSystemImage());
- tagToAbiFound.put(tag, abi);
- }
- }
- }
-
- // Look in either the platform/images/abi or the legacy folder
- File imgDir = new File(platformDir, SdkConstants.OS_IMAGES_FOLDER);
- File[] files = fileOp.listFiles(imgDir);
- boolean useLegacy = true;
- boolean hasImgFiles = false;
- final IdDisplay defaultTag = SystemImage.DEFAULT_TAG;
-
- // Look for sub-directories
- for (File file : files) {
- if (fileOp.isDirectory(file)) {
- useLegacy = false;
- String abi = file.getName();
- if (!tagToAbiFound.containsEntry(defaultTag, abi)) {
- found.add(new SystemImage(
- file,
- LocationType.IN_IMAGES_SUBFOLDER,
- defaultTag,
- abi,
- FileOp.EMPTY_FILE_ARRAY));
- tagToAbiFound.put(defaultTag, abi);
- }
- } else if (!hasImgFiles && fileOp.isFile(file)) {
- if (file.getName().endsWith(".img")) { //$NON-NLS-1$
- hasImgFiles = true;
- }
- }
- }
-
- if (useLegacy &&
- hasImgFiles &&
- fileOp.isDirectory(imgDir) &&
- !tagToAbiFound.containsEntry(defaultTag, SdkConstants.ABI_ARMEABI)) {
- // We found no sub-folder system images but it looks like the top directory
- // has some img files in it. It must be a legacy ARM EABI system image folder.
- found.add(new SystemImage(
- imgDir,
- LocationType.IN_LEGACY_FOLDER,
- defaultTag,
- SdkConstants.ABI_ARMEABI,
- FileOp.EMPTY_FILE_ARRAY));
- }
-
- return found.toArray(new ISystemImage[found.size()]);
- }
-
- private List<OptionalLibrary> getOptionalLibraries(@NonNull File platformDir) {
- File optionalDir = new File(platformDir, "optional");
- if (!optionalDir.isDirectory()) {
- return Collections.emptyList();
- }
-
- File optionalJson = new File(optionalDir, "optional.json");
- if (!optionalJson.isFile()) {
- return Collections.emptyList();
- }
-
- return getLibsFromJson(optionalJson);
- }
-
- public static class Library {
- String name;
- String jar;
- boolean manifest;
- }
-
-
- @VisibleForTesting
- static List<OptionalLibrary> getLibsFromJson(@NonNull File jsonFile) {
-
- Gson gson = new Gson();
-
- try {
- Type collectionType = new TypeToken<Collection<Library>>() {
- }.getType();
- Collection<Library> libs = gson
- .fromJson(Files.newReader(jsonFile, Charsets.UTF_8), collectionType);
-
- // convert into the right format.
- List<OptionalLibrary> optionalLibraries = Lists.newArrayListWithCapacity(libs.size());
-
- File rootFolder = jsonFile.getParentFile();
- for (Library lib : libs) {
- optionalLibraries.add(new OptionalLibraryImpl(
- lib.name,
- new File(rootFolder, lib.jar),
- lib.name,
- lib.manifest));
- }
-
- return optionalLibraries;
- } catch (FileNotFoundException e) {
- // shouldn't happen since we've checked the file is here, but can happen in
- // some cases (too many files open).
- return Collections.emptyList();
- }
- }
-
- /** List of items in the platform to check when parsing it. These paths are relative to the
- * platform root folder. */
- private static final String[] sPlatformContentList = new String[] {
- SdkConstants.FN_FRAMEWORK_LIBRARY,
- SdkConstants.FN_FRAMEWORK_AIDL,
- };
-
- /**
- * Checks the given platform has all the required files, and returns true if they are all
- * present.
- * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
- * aidl(.exe), dx(.bat), and dx.jar
- *
- * @param fileOp File operation wrapper.
- * @param platform The folder containing the platform.
- * @return An error description if platform is rejected; null if no error is detected.
- */
- @NonNull
- private static String checkPlatformContent(IFileOp fileOp, @NonNull File platform) {
- for (String relativePath : sPlatformContentList) {
- File f = new File(platform, relativePath);
- if (!fileOp.exists(f)) {
- return String.format(
- "Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
- platform.getName(), relativePath);
- }
- }
- return null;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.java
deleted file mode 100755
index 281c64e..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-public class LocalPlatformToolPkgInfo extends LocalPkgInfo {
-
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalPlatformToolPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull FullRevision revision) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newPlatformTool(revision).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java
deleted file mode 100755
index d8159d6..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * Local sample package, for a given platform's {@link AndroidVersion}.
- * The package itself has a major revision.
- * There should be only one for a given android platform version.
- */
-public class LocalSamplePkgInfo extends LocalPkgInfo {
-
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalSamplePkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull AndroidVersion version,
- @NonNull MajorRevision revision,
- @NonNull FullRevision minToolsRev) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newSample(version, revision, minToolsRev).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java
deleted file mode 100755
index 144ed5b..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java
+++ /dev/null
@@ -1,1239 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.annotations.concurrency.GuardedBy;
-import com.android.sdklib.*;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.internal.androidTarget.MissingTarget;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDescExtra;
-import com.android.sdklib.repository.descriptors.PkgType;
-import com.google.common.collect.*;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.*;
-
-/**
- * This class keeps information on the current locally installed SDK.
- * It tries to lazily load information as much as possible.
- * <p/>
- * Packages are accessed by their type and a main query attribute, depending on the
- * package type. There are different versions of {@link #getPkgInfo} which depend on the
- * query attribute.
- *
- * <table border='1' cellpadding='3'>
- * <tr>
- * <th>Type</th>
- * <th>Query parameter</th>
- * <th>Getter</th>
- * </tr>
- *
- * <tr>
- * <td>Tools</td>
- * <td>Unique instance</td>
- * <td>{@code getPkgInfo(PkgType.PKG_TOOLS)} => {@link LocalPkgInfo}</td>
- * </tr>
- *
- * <tr>
- * <td>Platform-Tools</td>
- * <td>Unique instance</td>
- * <td>{@code getPkgInfo(PkgType.PKG_PLATFORM_TOOLS)} => {@link LocalPkgInfo}</td>
- * </tr>
- *
- * <tr>
- * <td>Docs</td>
- * <td>Unique instance</td>
- * <td>{@code getPkgInfo(PkgType.PKG_DOCS)} => {@link LocalPkgInfo}</td>
- * </tr>
- *
- * <tr>
- * <td>Build-Tools</td>
- * <td>{@link FullRevision}</td>
- * <td>{@code getLatestBuildTool()} => {@link BuildToolInfo}, <br/>
- * or {@code getBuildTool(FullRevision)} => {@link BuildToolInfo}, <br/>
- * or {@code getPkgInfo(PkgType.PKG_BUILD_TOOLS, FullRevision)} => {@link LocalPkgInfo}, <br/>
- * or {@code getPkgsInfos(PkgType.PKG_BUILD_TOOLS)} => {@link LocalPkgInfo}[]</td>
- * </tr>
- *
- * <tr>
- * <td>Extras</td>
- * <td>String vendor/path</td>
- * <td>{@code getExtra(String)} => {@link LocalExtraPkgInfo}, <br/>
- * or {@code getPkgInfo(PkgType.PKG_EXTRAS, String)} => {@link LocalPkgInfo}, <br/>
- * or {@code getPkgsInfos(PkgType.PKG_EXTRAS)} => {@link LocalPkgInfo}[]</td>
- * </tr>
- *
- * <tr>
- * <td>Sources</td>
- * <td>{@link AndroidVersion}</td>
- * <td>{@code getPkgInfo(PkgType.PKG_SOURCES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
- * or {@code getPkgsInfos(PkgType.PKG_SOURCES)} => {@link LocalPkgInfo}[]</td>
- * </tr>
- *
- * <tr>
- * <td>Samples</td>
- * <td>{@link AndroidVersion}</td>
- * <td>{@code getPkgInfo(PkgType.PKG_SAMPLES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
- * or {@code getPkgsInfos(PkgType.PKG_SAMPLES)} => {@link LocalPkgInfo}[]</td>
- * </tr>
- *
- * <tr>
- * <td>Platforms</td>
- * <td>{@link AndroidVersion}</td>
- * <td>{@code getPkgInfo(PkgType.PKG_PLATFORMS, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
- * or {@code getPkgInfo(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
- * or {@code getPkgsInfos(PkgType.PKG_PLATFORMS)} => {@link LocalPkgInfo}[], <br/>
- * or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
- * </tr>
- *
- * <tr>
- * <td>Add-ons</td>
- * <td>{@link AndroidVersion} x String vendor/path</td>
- * <td>{@code getPkgInfo(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
- * or {@code getPkgsInfos(PkgType.PKG_ADDONS)} => {@link LocalPkgInfo}[], <br/>
- * or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
- * </tr>
- *
- * <tr>
- * <td>System images</td>
- * <td>{@link AndroidVersion} x {@link String} ABI</td>
- * <td>{@code getPkgsInfos(PkgType.PKG_SYS_IMAGES)} => {@link LocalPkgInfo}[]</td>
- * </tr>
- *
- * </table>
- *
- * Apps/libraries that use it are encouraged to keep an existing instance around
- * (using a singleton or similar mechanism).
- * <p/>
- * Threading: All accessor methods are synchronized on the same internal lock so
- * it's safe to call them from any thread, even concurrently. <br/>
- * A method like {@code getPkgsInfos} returns a copy of its data array, which objects are
- * not altered after creation, so its value is not influenced by the internal state after
- * it returns.
- * <p/>
- *
- * Implementation Background:
- * <ul>
- * <li> The sdk manager has a set of "Package" classes that cover both local
- * and remote SDK operations.
- * <li> Goal was to split it in 2 cleanly separated parts: {@link LocalSdk} parses sdk on disk,
- * and a separate class wraps the downloaded manifest (this is now handled within Studio only)
- * <li> The local SDK should be a singleton accessible somewhere, so there will be one in ADT
- * (via the Sdk instance), one in Studio, and one in the command line tool. <br/>
- * Right now there's a bit of mess with some classes creating a temp LocalSdkParser,
- * some others using an SdkManager instance, and that needs to be sorted out.
- * <li> As a transition, the SdkManager instance wraps a LocalSdk and uses this. Eventually the
- * SdkManager.java class will go away (its name is totally misleading, for starters.)
- * <li> The current LocalSdkParser stays as-is for compatibility purposes and the goal is also
- * to totally remove it when the SdkManager class goes away.
- * </ul>
- * @version 2 of the {@code SdkManager} class, essentially.
- */
-public class LocalSdk {
-
- /** Location of the SDK. Maybe null. Can be changed. */
- private File mSdkRoot;
- /** File operation object. (Used for overriding in mock testing.) */
- private final IFileOp mFileOp;
- /** List of package information loaded so far. Lazily populated. */
- @GuardedBy(value="mLocalPackages")
- private final Multimap<PkgType, LocalPkgInfo> mLocalPackages = TreeMultimap.create();
- /** Directories already parsed into {@link #mLocalPackages}. */
- @GuardedBy(value="mLocalPackages")
- private final Multimap<PkgType, LocalDirInfo> mVisitedDirs = HashMultimap.create();
- /** A legacy build-tool for older platform-tools < 17. */
- private BuildToolInfo mLegacyBuildTools;
- /** Cache of targets from local sdk. See {@link #getTargets()}. */
- @GuardedBy(value="mLocalPackages")
- private List<IAndroidTarget> mCachedTargets = null;
- private Set<MissingTarget> mCachedMissingTargets = null;
-
- /**
- * Creates an initial LocalSdk instance with an unknown location.
- */
- public LocalSdk() {
- mFileOp = new FileOp();
- }
-
- /**
- * Creates an initial LocalSdk instance for a known SDK location.
- *
- * @param sdkRoot The location of the SDK root folder.
- */
- public LocalSdk(@NonNull File sdkRoot) {
- this();
- setLocation(sdkRoot);
- }
-
- /**
- * Creates an initial LocalSdk instance with an unknown location.
- * This is designed for unit tests to override the {@link FileOp} being used.
- *
- * @param fileOp The alternate {@link FileOp} to use for all file-based interactions.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- public LocalSdk(@NonNull IFileOp fileOp) {
- mFileOp = fileOp;
- }
-
- /*
- * Returns the current IFileOp being used.
- */
- @NonNull
- public IFileOp getFileOp() {
- return mFileOp;
- }
-
- /**
- * Sets or changes the SDK root location. This also clears any cached information.
- *
- * @param sdkRoot The location of the SDK root folder.
- */
- public void setLocation(@NonNull File sdkRoot) {
- assert sdkRoot != null;
- mSdkRoot = sdkRoot;
- clearLocalPkg(PkgType.PKG_ALL);
- }
-
- /**
- * Location of the SDK. Maybe null. Can be changed.
- *
- * @return The location of the SDK. Null if not initialized yet.
- */
- @Nullable
- public File getLocation() {
- return mSdkRoot;
- }
-
- /**
- * Location of the SDK. Maybe null. Can be changed.
- * The getLocation() API replaces this function.
- * @return The location of the SDK. Null if not initialized yet.
- */
- @Deprecated
- @Nullable
- public String getPath() {
- return mSdkRoot != null ? mSdkRoot.getPath() : null;
- }
-
- /**
- * Clear the tracked visited folders & the cached {@link LocalPkgInfo} for the
- * given filter types.
- *
- * @param filters A set of PkgType constants or {@link PkgType#PKG_ALL} to clear everything.
- */
- public void clearLocalPkg(@NonNull EnumSet<PkgType> filters) {
- mLegacyBuildTools = null;
-
- synchronized (mLocalPackages) {
- for (PkgType filter : filters) {
- mVisitedDirs.removeAll(filter);
- mLocalPackages.removeAll(filter);
- }
-
- // Clear the targets if the platforms or addons are being cleared
- if (filters.contains(PkgType.PKG_PLATFORM) || filters.contains(PkgType.PKG_ADDON)) {
- mCachedMissingTargets = null;
- mCachedTargets = null;
- }
- }
- }
-
- /**
- * Check the tracked visited folders to see if anything has changed for the
- * requested filter types.
- * This does not refresh or reload any package information.
- *
- * @param filters A set of PkgType constants or {@link PkgType#PKG_ALL} to clear everything.
- */
- public boolean hasChanged(@NonNull EnumSet<PkgType> filters) {
- for (PkgType filter : filters) {
- Collection<LocalDirInfo> dirInfos;
- synchronized (mLocalPackages) {
- dirInfos = mVisitedDirs.get(filter);
- for(LocalDirInfo dirInfo : dirInfos) {
- if (dirInfo.hasChanged()) {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- //--------- Generic querying ---------
-
-
- /**
- * Retrieves information on a package identified by an {@link IPkgDesc}.
- *
- * @param descriptor {@link IPkgDesc} describing a package.
- * @return The first package found with the same descriptor or null.
- */
- @Nullable
- public LocalPkgInfo getPkgInfo(@NonNull IPkgDesc descriptor) {
-
- for (LocalPkgInfo pkg : getPkgsInfos(EnumSet.of(descriptor.getType()))) {
- IPkgDesc d = pkg.getDesc();
- if (d.equals(descriptor)) {
- return pkg;
- }
- }
-
- return null;
- }
-
- /**
- * Retrieves information on a package identified by an {@link AndroidVersion}.
- *
- * Note: don't use this for {@link PkgType#PKG_SYS_IMAGE} since there can be more than
- * one ABI and this method only returns a single package per filter type.
- *
- * @param filter {@link PkgType#PKG_PLATFORM}, {@link PkgType#PKG_SAMPLE}
- * or {@link PkgType#PKG_SOURCE}.
- * @param version The {@link AndroidVersion} specific for this package type.
- * @return An existing package information or null if not found.
- */
- @Nullable
- public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull AndroidVersion version) {
- assert filter == PkgType.PKG_PLATFORM ||
- filter == PkgType.PKG_SAMPLE ||
- filter == PkgType.PKG_SOURCE;
-
- for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
- IPkgDesc d = pkg.getDesc();
- if (d.hasAndroidVersion() && d.getAndroidVersion().equals(version)) {
- return pkg;
- }
- }
-
- return null;
- }
-
- /**
- * Retrieves information on a package identified by its {@link FullRevision}.
- * <p/>
- * Note that {@link PkgType#PKG_TOOLS} and {@link PkgType#PKG_PLATFORM_TOOLS}
- * are unique in a local SDK so you'll want to use {@link #getPkgInfo(PkgType)}
- * to retrieve them instead.
- *
- * @param filter {@link PkgType#PKG_BUILD_TOOLS}.
- * @param revision The {@link FullRevision} uniquely identifying this package.
- * @return An existing package information or null if not found.
- */
- @Nullable
- public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull FullRevision revision) {
-
- assert filter == PkgType.PKG_BUILD_TOOLS;
-
- for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
- IPkgDesc d = pkg.getDesc();
- if (d.hasFullRevision() && d.getFullRevision().equals(revision)) {
- return pkg;
- }
- }
- return null;
- }
-
- /**
- * Retrieves information on a package identified by its {@link String} path.
- * <p/>
- * For add-ons and platforms, the path is the target hash string
- * (see {@link AndroidTargetHash} for helpers methods to generate this string.)
- *
- * @param filter {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM}.
- * @param path The vendor/path uniquely identifying this package.
- * @return An existing package information or null if not found.
- */
- @Nullable
- public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull String path) {
-
- assert filter == PkgType.PKG_ADDON ||
- filter == PkgType.PKG_PLATFORM;
-
- for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
- IPkgDesc d = pkg.getDesc();
- if (d.hasPath() && path.equals(d.getPath())) {
- return pkg;
- }
- }
- return null;
- }
-
- /**
- * Retrieves information on a package identified by both vendor and path strings.
- * <p/>
- * For add-ons the path is target hash string
- * (see {@link AndroidTargetHash} for helpers methods to generate this string.)
- *
- * @param filter {@link PkgType#PKG_EXTRA}, {@link PkgType#PKG_ADDON}.
- * @param vendor The vendor id of the extra package.
- * @param path The path uniquely identifying this package for its vendor.
- * @return An existing package information or null if not found.
- */
- @Nullable
- public LocalPkgInfo getPkgInfo(@NonNull PkgType filter,
- @NonNull String vendor,
- @NonNull String path) {
-
- assert filter == PkgType.PKG_EXTRA ||
- filter == PkgType.PKG_ADDON;
-
- for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
- IPkgDesc d = pkg.getDesc();
- if (d.hasVendor() && vendor.equals(d.getVendor().getId())) {
- if (d.hasPath() && path.equals(d.getPath())) {
- return pkg;
- }
- }
- }
- return null;
- }
-
- /**
- * Retrieves information on an extra package identified by its {@link String} vendor/path.
- *
- * @param vendor The vendor id of the extra package.
- * @param path The path uniquely identifying this package for its vendor.
- * @return An existing extra package information or null if not found.
- */
- @Nullable
- public LocalExtraPkgInfo getExtra(@NonNull String vendor, @NonNull String path) {
- return (LocalExtraPkgInfo) getPkgInfo(PkgType.PKG_EXTRA, vendor, path);
- }
-
- /**
- * For unique local packages.
- * Returns the cached LocalPkgInfo for the requested type.
- * Loads it from disk if not cached.
- *
- * @param filter {@link PkgType#PKG_TOOLS} or {@link PkgType#PKG_PLATFORM_TOOLS}
- * or {@link PkgType#PKG_DOC}.
- * @return null if the package is not installed.
- */
- @Nullable
- public LocalPkgInfo getPkgInfo(@NonNull PkgType filter) {
- if (filter != PkgType.PKG_TOOLS &&
- filter != PkgType.PKG_PLATFORM_TOOLS &&
- filter != PkgType.PKG_DOC &&
- filter != PkgType.PKG_NDK) {
- assert false;
- return null;
- }
-
- LocalPkgInfo info = null;
- synchronized (mLocalPackages) {
- Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
- assert existing.size() <= 1;
- if (!existing.isEmpty()) {
- return existing.iterator().next();
- }
-
- File uniqueDir = new File(mSdkRoot, filter.getFolderName());
-
- if (!mVisitedDirs.containsEntry(filter, new LocalDirInfo.MapComparator(uniqueDir))) {
- switch(filter) {
- case PKG_TOOLS:
- info = scanTools(uniqueDir);
- break;
- case PKG_PLATFORM_TOOLS:
- info = scanPlatformTools(uniqueDir);
- break;
- case PKG_DOC:
- info = scanDoc(uniqueDir);
- break;
- case PKG_NDK:
- info = scanNdk(uniqueDir);
- default:
- break;
- }
- }
-
- // Whether we have found a valid pkg or not, this directory has been visited.
- mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, uniqueDir));
-
- if (info != null) {
- mLocalPackages.put(filter, info);
- }
- }
-
- return info;
- }
-
- /**
- * Retrieve all the info about the requested package type.
- * This is used for the package types that have one or more instances, each with different
- * versions.
- * The resulting array is sorted according to the PkgInfo's sort order.
- * <p/>
- * Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS} and
- * {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is
- * more efficient to use {@link #getPkgInfo(PkgType)} to query them.
- *
- * @param filter One of {@link PkgType} constants.
- * @return A list (possibly empty) of matching installed packages. Never returns null.
- */
- @NonNull
- public LocalPkgInfo[] getPkgsInfos(@NonNull PkgType filter) {
- return getPkgsInfos(EnumSet.of(filter));
- }
-
- /**
- * Retrieve all the info about the requested package types.
- * This is used for the package types that have one or more instances, each with different
- * versions.
- * The resulting array is sorted according to the PkgInfo's sort order.
- * <p/>
- * To force the LocalSdk parser to load <b>everything</b>, simply call this method
- * with the {@link PkgType#PKG_ALL} argument to load all the known package types.
- * <p/>
- * Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS} and
- * {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is
- * more efficient to use {@link #getPkgInfo(PkgType)} to query them.
- *
- * @param filters One or more of {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM},
- * {@link PkgType#PKG_BUILD_TOOLS}, {@link PkgType#PKG_EXTRA},
- * {@link PkgType#PKG_SOURCE}, {@link PkgType#PKG_SYS_IMAGE}
- * @return A list (possibly empty) of matching installed packages. Never returns null.
- */
- @NonNull
- public LocalPkgInfo[] getPkgsInfos(@NonNull EnumSet<PkgType> filters) {
- List<LocalPkgInfo> list = Lists.newArrayList();
-
- for (PkgType filter : filters) {
- if (filter == PkgType.PKG_TOOLS ||
- filter == PkgType.PKG_PLATFORM_TOOLS ||
- filter == PkgType.PKG_DOC ||
- filter == PkgType.PKG_NDK) {
- LocalPkgInfo info = getPkgInfo(filter);
- if (info != null) {
- list.add(info);
- }
- } else {
- synchronized (mLocalPackages) {
- Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
- assert existing != null; // Multimap returns an empty set if not found
-
- if (!existing.isEmpty()) {
- list.addAll(existing);
- continue;
- }
-
- File subDir = new File(mSdkRoot, filter.getFolderName());
-
- if (!mVisitedDirs.containsEntry(filter, new LocalDirInfo.MapComparator(subDir))) {
- switch(filter) {
- case PKG_BUILD_TOOLS:
- scanBuildTools(subDir, existing);
- break;
-
- case PKG_PLATFORM:
- scanPlatforms(subDir, existing);
- break;
-
- case PKG_SYS_IMAGE:
- scanSysImages(subDir, existing, false);
- break;
-
- case PKG_ADDON_SYS_IMAGE:
- scanSysImages(subDir, existing, true);
- break;
-
- case PKG_ADDON:
- scanAddons(subDir, existing);
- break;
-
- case PKG_SAMPLE:
- scanSamples(subDir, existing);
- break;
-
- case PKG_SOURCE:
- scanSources(subDir, existing);
- break;
-
- case PKG_EXTRA:
- scanExtras(subDir, existing);
- break;
-
- case PKG_TOOLS:
- case PKG_PLATFORM_TOOLS:
- case PKG_DOC:
- case PKG_NDK:
- break;
- default:
- throw new IllegalArgumentException(
- "Unsupported pkg type " + filter.toString());
- }
- mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, subDir));
- list.addAll(existing);
- }
- }
- }
- }
-
- Collections.sort(list);
- return list.toArray(new LocalPkgInfo[list.size()]);
- }
-
- //---------- Package-specific querying --------
-
- /**
- * Returns the {@link BuildToolInfo} for the given revision.
- *
- * @param revision The requested revision.
- * @return A {@link BuildToolInfo}. Can be null if {@code revision} is null or is
- * not part of the known set returned by {@code getPkgsInfos(PkgType.PKG_BUILD_TOOLS)}.
- */
- @Nullable
- public BuildToolInfo getBuildTool(@Nullable FullRevision revision) {
- LocalPkgInfo pkg = getPkgInfo(PkgType.PKG_BUILD_TOOLS, revision);
- if (pkg instanceof LocalBuildToolPkgInfo) {
- return ((LocalBuildToolPkgInfo) pkg).getBuildToolInfo();
- }
- return null;
- }
-
- /**
- * Returns the highest build-tool revision known, or null if there are are no build-tools.
- * <p/>
- * If no specific build-tool package is installed but the platform-tools is lower than 17,
- * then this creates and returns a "legacy" built-tool package using platform-tools.
- * (We only split build-tools out of platform-tools starting with revision 17,
- * before they were both the same thing.)
- *
- * @return The highest build-tool revision known, or null.
- */
- @Nullable
- public BuildToolInfo getLatestBuildTool() {
- if (mLegacyBuildTools != null) {
- return mLegacyBuildTools;
- }
-
- LocalPkgInfo[] pkgs = getPkgsInfos(PkgType.PKG_BUILD_TOOLS);
-
- if (pkgs.length == 0) {
- LocalPkgInfo ptPkg = getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
- if (ptPkg instanceof LocalPlatformToolPkgInfo &&
- ptPkg.getDesc().getFullRevision().compareTo(new FullRevision(17)) < 0) {
- // older SDK, create a compatible build-tools
- mLegacyBuildTools = createLegacyBuildTools((LocalPlatformToolPkgInfo) ptPkg);
- return mLegacyBuildTools;
- }
- return null;
- }
-
- assert pkgs.length > 0;
-
- // Note: the pkgs come from a TreeMultimap so they should already be sorted.
- // Just in case, sort them again.
- Arrays.sort(pkgs);
-
- // LocalBuildToolPkgInfo's comparator sorts on its FullRevision so we just
- // need to take the latest element.
- LocalPkgInfo pkg = pkgs[pkgs.length - 1];
- if (pkg instanceof LocalBuildToolPkgInfo) {
- return ((LocalBuildToolPkgInfo) pkg).getBuildToolInfo();
- }
-
- return null;
- }
-
- @NonNull
- private BuildToolInfo createLegacyBuildTools(@NonNull LocalPlatformToolPkgInfo ptInfo) {
- File platformTools = new File(getLocation(), SdkConstants.FD_PLATFORM_TOOLS);
- File platformToolsLib = ptInfo.getLocalDir();
- File platformToolsRs = new File(platformTools, SdkConstants.FN_FRAMEWORK_RENDERSCRIPT);
-
- return new BuildToolInfo(
- ptInfo.getDesc().getFullRevision(),
- platformTools,
- new File(platformTools, SdkConstants.FN_AAPT),
- new File(platformTools, SdkConstants.FN_AIDL),
- new File(platformTools, SdkConstants.FN_DX),
- new File(platformToolsLib, SdkConstants.FN_DX_JAR),
- new File(platformTools, SdkConstants.FN_RENDERSCRIPT),
- new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE),
- new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE_CLANG),
- null,
- null,
- null,
- null,
- new File(platformTools, SdkConstants.FN_ZIPALIGN));
- }
-
- /**
- * Returns the targets (platforms & addons) that are available in the SDK.
- * The target list is created on demand the first time then cached.
- * It will not refreshed unless {@link #clearLocalPkg} is called to clear platforms
- * and/or add-ons.
- * <p/>
- * The array can be empty but not null.
- */
- @NonNull
- public IAndroidTarget[] getTargets() {
- synchronized (mLocalPackages) {
- if (mCachedTargets == null) {
- List<IAndroidTarget> result = Lists.newArrayList();
- LocalPkgInfo[] pkgsInfos = getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM,
- PkgType.PKG_ADDON));
- for (LocalPkgInfo info : pkgsInfos) {
- assert info instanceof LocalPlatformPkgInfo;
- IAndroidTarget target = ((LocalPlatformPkgInfo) info).getAndroidTarget();
- if (target != null) {
- result.add(target);
- }
- }
- mCachedTargets = result;
- }
- return mCachedTargets.toArray(new IAndroidTarget[mCachedTargets.size()]);
- }
- }
-
- public IAndroidTarget[] getTargets(boolean includeMissing) {
- IAndroidTarget[] result = getTargets();
- if (includeMissing) {
- result = ObjectArrays.concat(result, getMissingTargets(), IAndroidTarget.class);
- }
- return result;
- }
-
- @NonNull
- public IAndroidTarget[] getMissingTargets() {
- synchronized (mLocalPackages) {
- if (mCachedMissingTargets == null) {
- Map<MissingTarget, MissingTarget> result = Maps.newHashMap();
- Set<ISystemImage> seen = Sets.newHashSet();
- for (IAndroidTarget target : getTargets()) {
- Collections.addAll(seen, target.getSystemImages());
- }
- for (LocalPkgInfo local : getPkgsInfos(PkgType.PKG_ADDON_SYS_IMAGE)) {
- LocalAddonSysImgPkgInfo info = (LocalAddonSysImgPkgInfo)local;
- ISystemImage image = info.getSystemImage();
- if (!seen.contains(image)) {
- addOrphanedSystemImage(image, info.getDesc(), result);
- }
- }
- for (LocalPkgInfo local : getPkgsInfos(PkgType.PKG_SYS_IMAGE)) {
- LocalSysImgPkgInfo info = (LocalSysImgPkgInfo)local;
- ISystemImage image = info.getSystemImage();
- if (!seen.contains(image)) {
- addOrphanedSystemImage(image, info.getDesc(), result);
- }
- }
- mCachedMissingTargets = result.keySet();
- }
- return mCachedMissingTargets.toArray(new IAndroidTarget[mCachedMissingTargets.size()]);
- }
- }
-
- private static void addOrphanedSystemImage(ISystemImage image, IPkgDesc desc, Map<MissingTarget, MissingTarget> targets) {
- IdDisplay vendor = desc.getVendor();
- MissingTarget target = new MissingTarget(vendor == null ? null : vendor.getDisplay(), desc.getTag().getDisplay(), desc.getAndroidVersion());
- MissingTarget existing = targets.get(target);
- if (existing == null) {
- existing = target;
- targets.put(target, target);
- }
- existing.addSystemImage(image);
- }
-
- /**
- * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
- *
- * @param hash the {@link IAndroidTarget} hash string.
- * @return The matching {@link IAndroidTarget} or null.
- */
- @Nullable
- public IAndroidTarget getTargetFromHashString(@Nullable String hash) {
- if (hash != null) {
- IAndroidTarget[] targets = getTargets(true);
- for (IAndroidTarget target : targets) {
- if (target != null && hash.equals(AndroidTargetHash.getTargetHashString(target))) {
- return target;
- }
- }
- }
- return null;
- }
-
- // -------------
-
- /**
- * Try to find a tools package at the given location.
- * Returns null if not found.
- */
- private LocalToolPkgInfo scanTools(File toolFolder) {
- // Can we find some properties?
- Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
- FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- return null;
- }
-
- FullRevision minPlatToolsRev =
- PackageParserUtils.getPropertyFull(props, PkgProps.MIN_PLATFORM_TOOLS_REV);
- if (minPlatToolsRev == null) {
- minPlatToolsRev = FullRevision.NOT_SPECIFIED;
- }
-
- LocalToolPkgInfo info = new LocalToolPkgInfo(this, toolFolder, props, rev, minPlatToolsRev);
-
- // We're not going to check that all tools are present. At the very least
- // we should expect to find android and an emulator adapted to the current OS.
- boolean hasEmulator = false;
- boolean hasAndroid = false;
- String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
- String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
- File[] files = mFileOp.listFiles(toolFolder);
- for (File file : files) {
- String name = file.getName();
- if (SdkConstants.FN_EMULATOR.equals(name)) {
- hasEmulator = true;
- }
- if (android1.equals(name) || (android2 != null && android2.equals(name))) {
- hasAndroid = true;
- }
- }
- if (!hasAndroid) {
- info.appendLoadError("Missing %1$s", SdkConstants.androidCmdName());
- }
- if (!hasEmulator) {
- info.appendLoadError("Missing %1$s", SdkConstants.FN_EMULATOR);
- }
-
- return info;
- }
-
- /**
- * Try to find a platform-tools package at the given location.
- * Returns null if not found.
- */
- private LocalPlatformToolPkgInfo scanPlatformTools(File ptFolder) {
- // Can we find some properties?
- Properties props = parseProperties(new File(ptFolder, SdkConstants.FN_SOURCE_PROP));
- FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- return null;
- }
-
- LocalPlatformToolPkgInfo info = new LocalPlatformToolPkgInfo(this, ptFolder, props, rev);
- return info;
- }
-
- /**
- * Try to find a docs package at the given location.
- * Returns null if not found.
- */
- private LocalDocPkgInfo scanDoc(File docFolder) {
- // Can we find some properties?
- Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
- MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- return null;
- }
-
- try {
- AndroidVersion vers = new AndroidVersion(props);
- LocalDocPkgInfo info = new LocalDocPkgInfo(this, docFolder, props, vers, rev);
-
- // To start with, a doc folder should have an "index.html" to be acceptable.
- // We don't actually check the content of the file.
- if (!mFileOp.isFile(new File(docFolder, "index.html"))) {
- info.appendLoadError("Missing index.html");
- }
- return info;
-
- } catch (AndroidVersionException e) {
- return null; // skip invalid or missing android version.
- }
- }
-
- /**
- * Try to find an NDK package at the given location.
- * Returns null if not found.
- */
- @Nullable
- private LocalNdkPkgInfo scanNdk(@NonNull File ndkFolder) {
- // Can we find some properties?
- Properties props = parseProperties(new File(ndkFolder, SdkConstants.FN_SOURCE_PROP));
- FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- return null;
- }
-
- return new LocalNdkPkgInfo(this, ndkFolder, props, rev);
- }
-
-
- /**
- * Helper used by scanXyz methods below to check whether a directory should be visited.
- * It can be skipped if it's not a directory or if it's already marked as visited in
- * mVisitedDirs for the given package type -- in which case the directory is added to
- * the visited map.
- *
- * @param pkgType The package type being scanned.
- * @param directory The file or directory to check.
- * @return False if directory can/should be skipped.
- * True if directory should be visited, in which case it's registered in mVisitedDirs.
- */
- private boolean shouldVisitDir(@NonNull PkgType pkgType, @NonNull File directory) {
- if (!mFileOp.isDirectory(directory)) {
- return false;
- }
- synchronized (mLocalPackages) {
- if (mVisitedDirs.containsEntry(pkgType, new LocalDirInfo.MapComparator(directory))) {
- return false;
- }
- mVisitedDirs.put(pkgType, new LocalDirInfo(mFileOp, directory));
- }
- return true;
- }
-
- private void scanBuildTools(File collectionDir, Collection<LocalPkgInfo> outCollection) {
- // The build-tool root folder contains a list of per-revision folders.
- for (File buildToolDir : mFileOp.listFiles(collectionDir)) {
- if (!shouldVisitDir(PkgType.PKG_BUILD_TOOLS, buildToolDir)) {
- continue;
- }
-
- Properties props = parseProperties(new File(buildToolDir, SdkConstants.FN_SOURCE_PROP));
- FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- continue; // skip, no revision
- }
-
- BuildToolInfo btInfo = new BuildToolInfo(rev, buildToolDir);
- LocalBuildToolPkgInfo pkgInfo =
- new LocalBuildToolPkgInfo(this, buildToolDir, props, rev, btInfo);
- outCollection.add(pkgInfo);
- }
- }
-
- private void scanPlatforms(File collectionDir, Collection<LocalPkgInfo> outCollection) {
- for (File platformDir : mFileOp.listFiles(collectionDir)) {
- if (!shouldVisitDir(PkgType.PKG_PLATFORM, platformDir)) {
- continue;
- }
-
- Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
- MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- continue; // skip, no revision
- }
-
- FullRevision minToolsRev =
- PackageParserUtils.getPropertyFull(props, PkgProps.MIN_TOOLS_REV);
- if (minToolsRev == null) {
- minToolsRev = FullRevision.NOT_SPECIFIED;
- }
-
- try {
- AndroidVersion vers = new AndroidVersion(props);
-
- LocalPlatformPkgInfo pkgInfo =
- new LocalPlatformPkgInfo(this, platformDir, props, vers, rev, minToolsRev);
- outCollection.add(pkgInfo);
-
- } catch (AndroidVersionException e) {
- continue; // skip invalid or missing android version.
- }
- }
- }
-
- private void scanAddons(File collectionDir, Collection<LocalPkgInfo> outCollection) {
- for (File addonDir : mFileOp.listFiles(collectionDir)) {
- if (!shouldVisitDir(PkgType.PKG_ADDON, addonDir)) {
- continue;
- }
-
- Properties props = parseProperties(new File(addonDir, SdkConstants.FN_SOURCE_PROP));
- MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- continue; // skip, no revision
- }
-
- try {
- AndroidVersion vers = new AndroidVersion(props);
-
- // Starting with addon-4.xsd, we have vendor-id and name-id available
- // in the add-on source properties so we'll use that directly.
-
- String nameId = props.getProperty(PkgProps.ADDON_NAME_ID);
- String nameDisp = props.getProperty(PkgProps.ADDON_NAME_DISPLAY);
- String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID);
- String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY);
-
- if (nameId == null) {
- // Support earlier add-ons that only had a name display attribute
- nameDisp = props.getProperty(PkgProps.ADDON_NAME, "Unknown");
- nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(nameDisp);
- }
-
- if (nameId != null && nameDisp == null) {
- nameDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
- }
-
- if (vendorId != null && vendorDisp == null) {
- vendorDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
- }
-
- if (vendorId == null) {
- // Support earlier add-ons that only had a vendor display attribute
- vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR, "Unknown");
- vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(vendorDisp);
- }
-
- LocalAddonPkgInfo pkgInfo = new LocalAddonPkgInfo(
- this, addonDir, props, vers, rev,
- new IdDisplay(vendorId, vendorDisp),
- new IdDisplay(nameId, nameDisp));
- outCollection.add(pkgInfo);
-
- } catch (AndroidVersionException e) {
- continue; // skip invalid or missing android version.
- }
- }
- }
-
- private void scanSysImages(
- File collectionDir,
- Collection<LocalPkgInfo> outCollection,
- boolean scanAddons) {
- List<File> propFiles = Lists.newArrayList();
- PkgType type = scanAddons ? PkgType.PKG_ADDON_SYS_IMAGE : PkgType.PKG_SYS_IMAGE;
-
- // Create a list of folders that contains a source.properties file matching these patterns:
- // sys-img/target/tag/abi
- // sys-img/target/abis
- // sys-img/add-on-target/abi
- // sys-img/target/add-on/abi
- for (File platformDir : mFileOp.listFiles(collectionDir)) {
- if (!shouldVisitDir(type, platformDir)) {
- continue;
- }
-
- for (File dir1 : mFileOp.listFiles(platformDir)) {
- // dir1 might be either a tag or an abi folder.
- if (!shouldVisitDir(type, dir1)) {
- continue;
- }
-
- File prop1 = new File(dir1, SdkConstants.FN_SOURCE_PROP);
- if (mFileOp.isFile(prop1)) {
- // dir1 was a legacy abi folder.
- if (!propFiles.contains(prop1)) {
- propFiles.add(prop1);
- }
- } else {
- File[] dir1Files = mFileOp.listFiles(dir1);
- for (File dir2 : dir1Files) {
- // dir2 should be an abi folder in a tag folder.
- if (!shouldVisitDir(type, dir2)) {
- continue;
- }
-
- File prop2 = new File(dir2, SdkConstants.FN_SOURCE_PROP);
- if (mFileOp.isFile(prop2)) {
- if (!propFiles.contains(prop2)) {
- propFiles.add(prop2);
- }
- }
- }
- }
- }
- }
-
- for (File propFile : propFiles) {
- Properties props = parseProperties(propFile);
- MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- continue; // skip, no revision
- }
-
- try {
- AndroidVersion vers = new AndroidVersion(props);
-
- IdDisplay tag = LocalSysImgPkgInfo.extractTagFromProps(props);
- String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID, null);
- File abiDir = propFile.getParentFile();
-
- if (vendorId == null && !scanAddons) {
- LocalSysImgPkgInfo pkgInfo =
- new LocalSysImgPkgInfo(this, abiDir, props, vers, tag, abiDir.getName(), rev);
- outCollection.add(pkgInfo);
-
- } else if (vendorId != null && scanAddons) {
- String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
- IdDisplay vendor = new IdDisplay(vendorId, vendorDisp);
-
- LocalAddonSysImgPkgInfo pkgInfo =
- new LocalAddonSysImgPkgInfo(
- this, abiDir, props, vers, vendor, tag, abiDir.getName(), rev);
- outCollection.add(pkgInfo);
- }
-
- } catch (AndroidVersionException e) {
- continue; // skip invalid or missing android version.
- }
- }
- }
-
- private void scanSamples(File collectionDir, Collection<LocalPkgInfo> outCollection) {
- for (File platformDir : mFileOp.listFiles(collectionDir)) {
- if (!shouldVisitDir(PkgType.PKG_SAMPLE, platformDir)) {
- continue;
- }
-
- Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
- MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- continue; // skip, no revision
- }
-
- FullRevision minToolsRev =
- PackageParserUtils.getPropertyFull(props, PkgProps.MIN_TOOLS_REV);
- if (minToolsRev == null) {
- minToolsRev = FullRevision.NOT_SPECIFIED;
- }
-
- try {
- AndroidVersion vers = new AndroidVersion(props);
-
- LocalSamplePkgInfo pkgInfo =
- new LocalSamplePkgInfo(this, platformDir, props, vers, rev, minToolsRev);
- outCollection.add(pkgInfo);
- } catch (AndroidVersionException e) {
- continue; // skip invalid or missing android version.
- }
- }
- }
-
- private void scanSources(File collectionDir, Collection<LocalPkgInfo> outCollection) {
- // The build-tool root folder contains a list of per-revision folders.
- for (File platformDir : mFileOp.listFiles(collectionDir)) {
- if (!shouldVisitDir(PkgType.PKG_SOURCE, platformDir)) {
- continue;
- }
-
- Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
- MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- continue; // skip, no revision
- }
-
- try {
- AndroidVersion vers = new AndroidVersion(props);
-
- LocalSourcePkgInfo pkgInfo =
- new LocalSourcePkgInfo(this, platformDir, props, vers, rev);
- outCollection.add(pkgInfo);
- } catch (AndroidVersionException e) {
- continue; // skip invalid or missing android version.
- }
- }
- }
-
- private void scanExtras(File collectionDir, Collection<LocalPkgInfo> outCollection) {
- for (File vendorDir : mFileOp.listFiles(collectionDir)) {
- if (!shouldVisitDir(PkgType.PKG_EXTRA, vendorDir)) {
- continue;
- }
-
- for (File extraDir : mFileOp.listFiles(vendorDir)) {
- if (!shouldVisitDir(PkgType.PKG_EXTRA, extraDir)) {
- continue;
- }
-
- Properties props = parseProperties(new File(extraDir, SdkConstants.FN_SOURCE_PROP));
- NoPreviewRevision rev =
- PackageParserUtils.getPropertyNoPreview(props, PkgProps.PKG_REVISION);
- if (rev == null) {
- continue; // skip, no revision
- }
-
- String oldPaths =
- PackageParserUtils.getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);
-
- String vendorId = vendorDir.getName();
- String vendorDisp = props.getProperty(PkgProps.EXTRA_VENDOR_DISPLAY);
- if (vendorDisp == null || vendorDisp.isEmpty()) {
- vendorDisp = vendorId;
- }
-
- String displayName = props.getProperty(PkgProps.EXTRA_NAME_DISPLAY, null);
-
- LocalExtraPkgInfo pkgInfo = new LocalExtraPkgInfo(
- this,
- extraDir,
- props,
- new IdDisplay(vendorId, vendorDisp),
- extraDir.getName(),
- displayName,
- PkgDescExtra.convertOldPaths(oldPaths),
- rev);
- outCollection.add(pkgInfo);
- }
- }
- }
-
- /**
- * Parses the given file as properties file if it exists.
- * Returns null if the file does not exist, cannot be parsed or has no properties.
- */
- private Properties parseProperties(File propsFile) {
- InputStream fis = null;
- try {
- if (mFileOp.exists(propsFile)) {
- fis = mFileOp.newFileInputStream(propsFile);
-
- Properties props = new Properties();
- props.load(fis);
-
- // To be valid, there must be at least one property in it.
- if (!props.isEmpty()) {
- return props;
- }
- }
- } catch (IOException e) {
- // Ignore
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {}
- }
- }
- return null;
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java
deleted file mode 100755
index eb822de..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * Local source package, for a given platform's {@link AndroidVersion}.
- * The package itself has a major revision.
- * There should be only one for a given android platform version.
- */
-public class LocalSourcePkgInfo extends LocalPkgInfo {
-
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalSourcePkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull AndroidVersion version,
- @NonNull MajorRevision revision) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newSource(version, revision).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java
deleted file mode 100755
index f9e8148..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-import com.google.common.base.Objects;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Properties;
-
-/**
- * Local system-image package, for a given platform's {@link AndroidVersion}
- * and given ABI.
- * The package itself has a major revision.
- * There should be only one for a given android platform version & ABI.
- */
-public class LocalSysImgPkgInfo extends LocalPkgInfo {
-
-
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalSysImgPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull AndroidVersion version,
- @Nullable IdDisplay tag,
- @NonNull String abi,
- @NonNull MajorRevision revision) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newSysImg(version, tag, abi, revision).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-
- /**
- * Extracts the tag id & display from the properties.
- * If missing, uses the "default" tag id.
- */
- @NonNull
- public static IdDisplay extractTagFromProps(Properties props) {
- if (props != null) {
- String tagId = props.getProperty(PkgProps.SYS_IMG_TAG_ID, SystemImage.DEFAULT_TAG.getId());
- String tagDisp = props.getProperty(PkgProps.SYS_IMG_TAG_DISPLAY, ""); //$NON-NLS-1$
- if (tagDisp == null || tagDisp.isEmpty()) {
- tagDisp = tagIdToDisplay(tagId);
- }
- assert tagId != null;
- assert tagDisp != null;
- return new IdDisplay(tagId, tagDisp);
- }
- return SystemImage.DEFAULT_TAG;
- }
-
- /**
- * Computes a display-friendly tag string based on the tag id.
- * This is typically used when there's no tag-display attribute.
- *
- * @param tagId A non-null tag id to sanitize for display.
- * @return The tag id with all non-alphanum symbols replaced by spaces and trimmed.
- */
- @NonNull
- public static String tagIdToDisplay(@NonNull String tagId) {
- String name;
- name = tagId.replaceAll("[^A-Za-z0-9]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
- name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
- name = name.trim();
-
- if (!name.isEmpty()) {
- char c = name.charAt(0);
- if (!Character.isUpperCase(c)) {
- StringBuilder sb = new StringBuilder(name);
- sb.replace(0, 1, String.valueOf(c).toUpperCase(Locale.US));
- name = sb.toString();
- }
- }
- return name;
- }
-
- public SystemImage getSystemImage() {
- return getSystemImage(mDesc, getLocalDir(), getLocalSdk().getFileOp());
- }
-
- static SystemImage getSystemImage(IPkgDesc desc, File localDir, @NonNull IFileOp fileOp) {
- final IdDisplay tag = desc.getTag();
- final String abi = desc.getPath();
- List<File> parsedSkins = PackageParserUtils.parseSkinFolder(new File(localDir, SdkConstants.FD_SKINS), fileOp);
- File[] skins = FileOp.EMPTY_FILE_ARRAY;
- if (!parsedSkins.isEmpty()) {
- skins = parsedSkins.toArray(new File[parsedSkins.size()]);
- }
-
- return new SystemImage(
- localDir,
- ISystemImage.LocationType.IN_SYSTEM_IMAGE,
- tag,
- desc.getVendor(),
- abi,
- skins);
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java
deleted file mode 100755
index 91306ea..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-public class LocalToolPkgInfo extends LocalPkgInfo {
-
- @NonNull
- private final IPkgDesc mDesc;
-
- public LocalToolPkgInfo(@NonNull LocalSdk localSdk,
- @NonNull File localDir,
- @NonNull Properties sourceProps,
- @NonNull FullRevision revision,
- @NonNull FullRevision minPlatformToolsRev) {
- super(localSdk, localDir, sourceProps);
- mDesc = PkgDesc.Builder.newTool(revision, minPlatformToolsRev).create();
- }
-
- @NonNull
- @Override
- public IPkgDesc getDesc() {
- return mDesc;
- }
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/local/PackageParserUtils.java b/base/sdklib/src/main/java/com/android/sdklib/repository/local/PackageParserUtils.java
deleted file mode 100644
index e8cc4b2..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/repository/local/PackageParserUtils.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.sdklib.repository.local;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-
-/**
- * Misc utilities to help extracting elements and attributes out of a repository XML document.
- */
-class PackageParserUtils {
- /**
- * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a full
- * revision (major.minor.micro.preview).
- *
- * @param props The properties to parse.
- * @return A {@link FullRevision} or null if there is no such property or it couldn't be parsed.
- * @param propKey The name of the property. Must not be null.
- */
- @Nullable
- public static FullRevision getPropertyFull(
- @Nullable Properties props,
- @NonNull String propKey) {
- String revStr = getProperty(props, propKey, null);
-
- FullRevision rev = null;
- if (revStr != null) {
- try {
- rev = FullRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- return rev;
- }
-
- /**
- * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a major
- * revision (major integer, no minor/micro/preview parts.)
- *
- * @param props The properties to parse.
- * @return A {@link MajorRevision} or null if there is no such property or it couldn't be parsed.
- * @param propKey The name of the property. Must not be null.
- */
- @Nullable
- public static MajorRevision getPropertyMajor(
- @Nullable Properties props,
- @NonNull String propKey) {
- String revStr = getProperty(props, propKey, null);
-
- MajorRevision rev = null;
- if (revStr != null) {
- try {
- rev = MajorRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- return rev;
- }
-
- /**
- * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a no-preview
- * revision (major.minor.micro integers but no preview part.)
- *
- * @param props The properties to parse.
- * @return A {@link NoPreviewRevision} or
- * null if there is no such property or it couldn't be parsed.
- * @param propKey The name of the property. Must not be null.
- */
- @Nullable
- public static NoPreviewRevision getPropertyNoPreview(
- @Nullable Properties props,
- @NonNull String propKey) {
- String revStr = getProperty(props, propKey, null);
-
- NoPreviewRevision rev = null;
- if (revStr != null) {
- try {
- rev = NoPreviewRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- return rev;
- }
-
-
- /**
- * Utility method that returns a property from a {@link Properties} object.
- * Returns the default value if props is null or if the property is not defined.
- *
- * @param props The {@link Properties} to search into.
- * If null, the default value is returned.
- * @param propKey The name of the property. Must not be null.
- * @param defaultValue The default value to return if {@code props} is null or if the
- * key is not found. Can be null.
- * @return The string value of the given key in the properties, or null if the key
- * isn't found or if {@code props} is null.
- */
- @Nullable
- public static String getProperty(
- @Nullable Properties props,
- @NonNull String propKey,
- @Nullable String defaultValue) {
- if (props == null) {
- return defaultValue;
- }
- return props.getProperty(propKey, defaultValue);
- }
-
-
- /**
- * Parses the skin folder and builds the skin list.
- * @param skinRootFolder The path to the skin root folder.
- */
- @NonNull
- public static List<File> parseSkinFolder(@NonNull File skinRootFolder, @NonNull IFileOp fileOp) {
- if (fileOp.isDirectory(skinRootFolder)) {
- ArrayList<File> skinList = new ArrayList<File>();
-
- File[] files = fileOp.listFiles(skinRootFolder);
-
- for (File skinFolder : files) {
- if (fileOp.isDirectory(skinFolder)) {
- // check for layout file
- File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
-
- if (fileOp.isFile(layout)) {
- // for now we don't parse the content of the layout and
- // simply add the directory to the list.
- skinList.add(skinFolder);
- }
- }
- }
-
- Collections.sort(skinList);
- return skinList;
- }
-
- return Collections.emptyList();
- }
-
-}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java b/base/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java
deleted file mode 100644
index 28918da..0000000
--- a/base/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java
+++ /dev/null
@@ -1,976 +0,0 @@
-/*
- * Copyright (C) 2008 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.sdklib.util;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.utils.ILogger;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map.Entry;
-
-/**
- * Parses the command-line and stores flags needed or requested.
- * <p/>
- * This is a base class. To be useful you want to:
- * <ul>
- * <li>override it.
- * <li>pass an action array to the constructor.
- * <li>define flags for your actions.
- * </ul>
- * <p/>
- * To use, call {@link #parseArgs(String[])} and then
- * call {@link #getValue(String, String, String)}.
- */
-public class CommandLineParser {
-
- /*
- * Steps needed to add a new action:
- * - Each action is defined as a "verb object" followed by parameters.
- * - Either reuse a VERB_ constant or define a new one.
- * - Either reuse an OBJECT_ constant or define a new one.
- * - Add a new entry to mAction with a one-line help summary.
- * - In the constructor, add a define() call for each parameter (either mandatory
- * or optional) for the given action.
- */
-
- /** Internal verb name for internally hidden flags. */
- public static final String GLOBAL_FLAG_VERB = "@@internal@@"; //$NON-NLS-1$
-
- /** String to use when the verb doesn't need any object. */
- public static final String NO_VERB_OBJECT = ""; //$NON-NLS-1$
-
- /** The global help flag. */
- public static final String KEY_HELP = "help";
- /** The global verbose flag. */
- public static final String KEY_VERBOSE = "verbose";
- /** The global silent flag. */
- public static final String KEY_SILENT = "silent";
-
- /** Verb requested by the user. Null if none specified, which will be an error. */
- private String mVerbRequested;
- /** Direct object requested by the user. Can be null. */
- private String mDirectObjectRequested;
-
- /**
- * Action definitions.
- * <p/>
- * This list serves two purposes: first it is used to know which verb/object
- * actions are acceptable on the command-line; second it provides a summary
- * for each action that is printed in the help.
- * <p/>
- * Each entry is a string array with:
- * <ul>
- * <li> the verb.
- * <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object).
- * <li> a description.
- * <li> an alternate form for the object (e.g. plural).
- * </ul>
- */
- private final String[][] mActions;
-
- private static final int ACTION_VERB_INDEX = 0;
- private static final int ACTION_OBJECT_INDEX = 1;
- private static final int ACTION_DESC_INDEX = 2;
- private static final int ACTION_ALT_OBJECT_INDEX = 3;
-
- /**
- * The map of all defined arguments.
- * <p/>
- * The key is a string "verb/directObject/longName".
- */
- private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
- /** Logger */
- private final ILogger mLog;
-
- /**
- * Constructs a new command-line processor.
- *
- * @param logger An SDK logger object. Must not be null.
- * @param actions The list of actions recognized on the command-line.
- * See the javadoc of {@link #mActions} for more details.
- *
- * @see #mActions
- */
- public CommandLineParser(ILogger logger, String[][] actions) {
- mLog = logger;
- mActions = actions;
-
- /*
- * usage should fit in 80 columns, including the space to print the options:
- * " -v --verbose 7890123456789012345678901234567890123456789012345678901234567890"
- */
-
- define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
- "Verbose mode, shows errors, warnings and all messages.",
- false);
- define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
- "Silent mode, shows errors only.",
- false);
- define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
- "Help on a specific command.",
- false);
- }
-
- /**
- * Indicates if this command-line can work when no verb is specified.
- * The default is false, which generates an error when no verb/object is specified.
- * Derived implementations can set this to true if they can deal with a lack
- * of verb/action.
- */
- public boolean acceptLackOfVerb() {
- return false;
- }
-
-
- //------------------
- // Helpers to get flags values
-
- /** Helper that returns true if --verbose was requested. */
- public boolean isVerbose() {
- return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
- }
-
- /** Helper that returns true if --silent was requested. */
- public boolean isSilent() {
- return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
- }
-
- /** Helper that returns true if --help was requested. */
- public boolean isHelpRequested() {
- return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
- }
-
- /** Returns the verb name from the command-line. Can be null. */
- public String getVerb() {
- return mVerbRequested;
- }
-
- /** Returns the direct object name from the command-line. Can be null. */
- public String getDirectObject() {
- return mDirectObjectRequested;
- }
-
- //------------------
-
- /**
- * Raw access to parsed parameter values.
- * <p/>
- * The default is to scan all parameters. Parameters that have been explicitly set on the
- * command line are returned first. Otherwise one with a non-null value is returned.
- * <p/>
- * Both a verb and a direct object filter can be specified. When they are non-null they limit
- * the scope of the search.
- * <p/>
- * If nothing has been found, return the last default value seen matching the filter.
- *
- * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
- * verbs that match the direct object condition will be examined and the first
- * value set will be used.
- * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
- * all possible direct objects that match the verb condition will be examined and
- * the first value set will be used.
- * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
- * @return The current value object stored in the parameter, which depends on the argument mode.
- */
- public Object getValue(String verb, String directObject, String longFlagName) {
-
- if (verb != null && directObject != null) {
- String key = verb + '/' + directObject + '/' + longFlagName;
- Arg arg = mArguments.get(key);
- return arg.getCurrentValue();
- }
-
- Object lastDefault = null;
- for (Arg arg : mArguments.values()) {
- if (arg.getLongArg().equals(longFlagName)) {
- if (verb == null || arg.getVerb().equals(verb)) {
- if (directObject == null || arg.getDirectObject().equals(directObject)) {
- if (arg.isInCommandLine()) {
- return arg.getCurrentValue();
- }
- if (arg.getCurrentValue() != null) {
- lastDefault = arg.getCurrentValue();
- }
- }
- }
- }
- }
-
- return lastDefault;
- }
-
- /**
- * Internal setter for raw parameter value.
- * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
- * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
- * @param longFlagName The long flag name for the given action.
- * @param value The new current value object stored in the parameter, which depends on the
- * argument mode.
- */
- protected void setValue(String verb, String directObject, String longFlagName, Object value) {
- String key = verb + '/' + directObject + '/' + longFlagName;
- Arg arg = mArguments.get(key);
- arg.setCurrentValue(value);
- }
-
- /**
- * Parses the command-line arguments.
- * <p/>
- * This method will exit and not return if a parsing error arise.
- *
- * @param args The arguments typically received by a main method.
- */
- public void parseArgs(String[] args) {
- String errorMsg = null;
- String verb = null;
- String directObject = null;
-
- try {
- int n = args.length;
- for (int i = 0; i < n; i++) {
- Arg arg = null;
- String a = args[i];
- if (a.startsWith("--")) { //$NON-NLS-1$
- arg = findLongArg(verb, directObject, a.substring(2));
- } else if (a.startsWith("-")) { //$NON-NLS-1$
- arg = findShortArg(verb, directObject, a.substring(1));
- }
-
- // No matching argument name found
- if (arg == null) {
- // Does it looks like a dashed parameter?
- if (a.startsWith("-")) { //$NON-NLS-1$
- if (verb == null || directObject == null) {
- // It looks like a dashed parameter and we don't have a a verb/object
- // set yet, the parameter was just given too early.
-
- errorMsg = String.format(
- "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
- a);
- return;
- } else {
- // It looks like a dashed parameter but it is unknown by this
- // verb-object combination
-
- errorMsg = String.format(
- "Flag '%1$s' is not valid for '%2$s %3$s'.",
- a, verb, directObject);
- return;
- }
- }
-
- if (verb == null) {
- // Fill verb first. Find it.
- for (String[] actionDesc : mActions) {
- if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
- verb = a;
- break;
- }
- }
-
- // Error if it was not a valid verb
- if (verb == null) {
- errorMsg = String.format(
- "Expected verb after global parameters but found '%1$s' instead.",
- a);
- return;
- }
-
- } else if (directObject == null) {
- // Then fill the direct object. Find it.
- for (String[] actionDesc : mActions) {
- if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
- if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
- directObject = a;
- break;
- } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
- actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
- // if the alternate form exist and is used, we internally
- // only memorize the default direct object form.
- directObject = actionDesc[ACTION_OBJECT_INDEX];
- break;
- }
- }
- }
-
- // Error if it was not a valid object for that verb
- if (directObject == null) {
- errorMsg = String.format(
- "Expected verb after global parameters but found '%1$s' instead.",
- a);
- return;
-
- }
- } else {
- // The argument is not a dashed parameter and we already
- // have a verb/object. Must be some extra unknown argument.
- errorMsg = String.format(
- "Argument '%1$s' is not recognized.",
- a);
- }
- } else if (arg != null) {
- // This argument was present on the command line
- arg.setInCommandLine(true);
-
- // Process keyword
- Object error = null;
- if (arg.getMode().needsExtra()) {
- if (i+1 >= n) {
- errorMsg = String.format("Missing argument for flag %1$s.", a);
- return;
- }
-
- while (i+1 < n) {
- String b = args[i+1];
-
- if (arg.getMode() != Mode.STRING_ARRAY) {
- // We never accept something that looks like a valid argument
- // unless we see -- first
- Arg dummyArg = null;
- if (b.startsWith("--")) { //$NON-NLS-1$
- dummyArg = findLongArg(verb, directObject, b.substring(2));
- } else if (b.startsWith("-")) { //$NON-NLS-1$
- dummyArg = findShortArg(verb, directObject, b.substring(1));
- }
- if (dummyArg != null) {
- errorMsg = String.format(
- "Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.",
- a, b);
- return;
- }
- }
-
- error = arg.getMode().process(arg, b);
- if (error == Accept.CONTINUE) {
- i++;
- } else if (error == Accept.ACCEPT_AND_STOP) {
- i++;
- break;
- } else if (error == Accept.REJECT_AND_STOP) {
- break;
- } else if (error instanceof String) {
- // We stop because of an error
- break;
- }
- }
- } else {
- error = arg.getMode().process(arg, null);
-
- if (isHelpRequested()) {
- // The --help flag was requested. We'll continue the usual processing
- // so that we can find the optional verb/object words. Those will be
- // used to print specific help.
- // Setting a non-null error message triggers printing the help, however
- // there is no specific error to print.
- errorMsg = ""; //$NON-NLS-1$
- }
- }
-
- if (error instanceof String) {
- errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
- return;
- }
- }
- }
-
- if (errorMsg == null) {
- if (verb == null && !acceptLackOfVerb()) {
- errorMsg = "Missing verb name.";
- } else if (verb != null) {
- if (directObject == null) {
- // Make sure this verb has an optional direct object
- for (String[] actionDesc : mActions) {
- if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
- actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
- directObject = NO_VERB_OBJECT;
- break;
- }
- }
-
- if (directObject == null) {
- errorMsg = String.format("Missing object name for verb '%1$s'.", verb);
- return;
- }
- }
-
- // Validate that all mandatory arguments are non-null for this action
- String missing = null;
- boolean plural = false;
- for (Entry<String, Arg> entry : mArguments.entrySet()) {
- Arg arg = entry.getValue();
- if (arg.getVerb().equals(verb) &&
- arg.getDirectObject().equals(directObject)) {
- if (arg.isMandatory() && arg.getCurrentValue() == null) {
- if (missing == null) {
- missing = "--" + arg.getLongArg(); //$NON-NLS-1$
- } else {
- missing += ", --" + arg.getLongArg(); //$NON-NLS-1$
- plural = true;
- }
- }
- }
- }
-
- if (missing != null) {
- errorMsg = String.format(
- "The %1$s %2$s must be defined for action '%3$s %4$s'",
- plural ? "parameters" : "parameter",
- missing,
- verb,
- directObject);
- }
-
- mVerbRequested = verb;
- mDirectObjectRequested = directObject;
- }
- }
- } finally {
- if (errorMsg != null) {
- printHelpAndExitForAction(verb, directObject, errorMsg);
- }
- }
- }
-
- /**
- * Finds an {@link Arg} given an action name and a long flag name.
- * @return The {@link Arg} found or null.
- */
- protected Arg findLongArg(String verb, String directObject, String longName) {
- if (verb == null) {
- verb = GLOBAL_FLAG_VERB;
- }
- if (directObject == null) {
- directObject = NO_VERB_OBJECT;
- }
- String key = verb + '/' + directObject + '/' + longName; //$NON-NLS-1$
- return mArguments.get(key);
- }
-
- /**
- * Finds an {@link Arg} given an action name and a short flag name.
- * @return The {@link Arg} found or null.
- */
- protected Arg findShortArg(String verb, String directObject, String shortName) {
- if (verb == null) {
- verb = GLOBAL_FLAG_VERB;
- }
- if (directObject == null) {
- directObject = NO_VERB_OBJECT;
- }
-
- for (Entry<String, Arg> entry : mArguments.entrySet()) {
- Arg arg = entry.getValue();
- if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
- if (shortName.equals(arg.getShortArg())) {
- return arg;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Prints the help/usage and exits.
- *
- * @param errorFormat Optional error message to print prior to usage using String.format
- * @param args Arguments for String.format
- */
- public void printHelpAndExit(String errorFormat, Object... args) {
- printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
- }
-
- /**
- * Prints the help/usage and exits.
- *
- * @param verb If null, displays help for all verbs. If not null, display help only
- * for that specific verb. In all cases also displays general usage and action list.
- * @param directObject If null, displays help for all verb objects.
- * If not null, displays help only for that specific action
- * In all cases also display general usage and action list.
- * @param errorFormat Optional error message to print prior to usage using String.format
- * @param args Arguments for String.format
- */
- public void printHelpAndExitForAction(String verb, String directObject,
- String errorFormat, Object... args) {
- if (errorFormat != null && !errorFormat.isEmpty()) {
- stderr(errorFormat, args);
- }
-
- /*
- * usage should fit in 80 columns
- * 12345678901234567890123456789012345678901234567890123456789012345678901234567890
- */
- stdout("\n" +
- "Usage:\n" +
- " android [global options] %s [action options]\n" +
- "\n" +
- "Global options:",
- verb == null ? "action" :
- verb + (directObject == null ? "" : " " + directObject)); //$NON-NLS-1$
- listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
-
- if (verb == null || directObject == null) {
- stdout("\nValid actions are composed of a verb and an optional direct object:");
- for (String[] action : mActions) {
- if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
- stdout("- %1$6s %2$-13s: %3$s",
- action[ACTION_VERB_INDEX],
- action[ACTION_OBJECT_INDEX],
- action[ACTION_DESC_INDEX]);
- }
- }
- }
-
- // Only print details if a verb/object is requested
- if (verb != null) {
- for (String[] action : mActions) {
- if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
- if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
- stdout("\nAction \"%1$s %2$s\":",
- action[ACTION_VERB_INDEX],
- action[ACTION_OBJECT_INDEX]);
- stdout(" %1$s", action[ACTION_DESC_INDEX]);
- stdout("Options:");
- listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
- }
- }
- }
- }
-
- exit();
- }
-
- /**
- * Internal helper to print all the option flags for a given action name.
- */
- protected void listOptions(String verb, String directObject) {
- int numOptions = 0;
- int longArgLen = 8;
-
- for (Entry<String, Arg> entry : mArguments.entrySet()) {
- Arg arg = entry.getValue();
- if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
- int n = arg.getLongArg().length();
- if (n > longArgLen) {
- longArgLen = n;
- }
- }
- }
-
- for (Entry<String, Arg> entry : mArguments.entrySet()) {
- Arg arg = entry.getValue();
- if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
-
- String value = ""; //$NON-NLS-1$
- String required = ""; //$NON-NLS-1$
- if (arg.isMandatory()) {
- required = " [required]";
-
- } else {
- if (arg.getDefaultValue() instanceof String[]) {
- for (String v : (String[]) arg.getDefaultValue()) {
- if (!value.isEmpty()) {
- value += ", ";
- }
- value += v;
- }
- } else if (arg.getDefaultValue() != null) {
- Object v = arg.getDefaultValue();
- if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) {
- value = v.toString();
- }
- }
- if (!value.isEmpty()) {
- value = " [Default: " + value + "]";
- }
- }
-
- // Java doesn't support * for printf variable width, so we'll insert the long arg
- // width "manually" in the printf format string.
- String longArgWidth = Integer.toString(longArgLen + 2);
-
- // Print a line in the form " -1_letter_arg --long_arg description"
- // where either the 1-letter arg or the long arg are optional.
- String output = String.format(
- " %1$-2s %2$-" + longArgWidth + "s: %3$s%4$s%5$s", //$NON-NLS-1$ //$NON-NLS-2$
- !arg.getShortArg().isEmpty() ?
- "-" + arg.getShortArg() : //$NON-NLS-1$
- "", //$NON-NLS-1$
- !arg.getLongArg().isEmpty() ?
- "--" + arg.getLongArg() : //$NON-NLS-1$
- "", //$NON-NLS-1$
- arg.getDescription(),
- value,
- required);
- stdout(output);
- numOptions++;
- }
- }
-
- if (numOptions == 0) {
- stdout(" No options");
- }
- }
-
- //----
-
- protected enum Accept {
- CONTINUE,
- ACCEPT_AND_STOP,
- REJECT_AND_STOP,
- }
-
- /**
- * The mode of an argument specifies the type of variable it represents,
- * whether an extra parameter is required after the flag and how to parse it.
- */
- protected enum Mode {
- /** Argument value is a Boolean. Default value is a Boolean. */
- BOOLEAN {
- @Override
- public boolean needsExtra() {
- return false;
- }
- @Override
- public Object process(Arg arg, String extra) {
- // Toggle the current value
- arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
- return Accept.ACCEPT_AND_STOP;
- }
- },
-
- /** Argument value is an Integer. Default value is an Integer. */
- INTEGER {
- @Override
- public boolean needsExtra() {
- return true;
- }
- @Override
- public Object process(Arg arg, String extra) {
- try {
- arg.setCurrentValue(Integer.parseInt(extra));
- return null;
- } catch (NumberFormatException e) {
- return String.format("Failed to parse '%1$s' as an integer: %2$s", extra,
- e.getMessage());
- }
- }
- },
-
- /** Argument value is a String. Default value is a String[]. */
- ENUM {
- @Override
- public boolean needsExtra() {
- return true;
- }
- @Override
- public Object process(Arg arg, String extra) {
- StringBuilder desc = new StringBuilder();
- String[] values = (String[]) arg.getDefaultValue();
- for (String value : values) {
- if (value.equals(extra)) {
- arg.setCurrentValue(extra);
- return Accept.ACCEPT_AND_STOP;
- }
-
- if (desc.length() != 0) {
- desc.append(", ");
- }
- desc.append(value);
- }
-
- return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
- }
- },
-
- /** Argument value is a String. Default value is a null. */
- STRING {
- @Override
- public boolean needsExtra() {
- return true;
- }
- @Override
- public Object process(Arg arg, String extra) {
- arg.setCurrentValue(extra);
- return Accept.ACCEPT_AND_STOP;
- }
- },
-
- /** Argument value is a {@link List}<String>. Default value is an empty list. */
- STRING_ARRAY {
- @Override
- public boolean needsExtra() {
- return true;
- }
- @Override
- public Object process(Arg arg, String extra) {
- // For simplification, a string array doesn't accept something that
- // starts with a dash unless a pure -- was seen before.
- if (extra != null) {
- Object v = arg.getCurrentValue();
- if (v == null) {
- ArrayList<String> a = new ArrayList<String>();
- arg.setCurrentValue(a);
- v = a;
- }
- if (v instanceof List<?>) {
- @SuppressWarnings("unchecked") List<String> a = (List<String>) v;
-
- if (extra.equals("--") ||
- !extra.startsWith("-") ||
- (extra.startsWith("-") && a.contains("--"))) {
- a.add(extra);
- return Accept.CONTINUE;
- } else if (a.isEmpty()) {
- return "No values provided";
- }
- }
- }
- return Accept.REJECT_AND_STOP;
- }
- };
-
- /**
- * Returns true if this mode requires an extra parameter.
- */
- public abstract boolean needsExtra();
-
- /**
- * Processes the flag for this argument.
- *
- * @param arg The argument being processed.
- * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
- * @return {@link Accept#CONTINUE} if this argument can use multiple values and
- * wishes to receive more.
- * Or {@link Accept#ACCEPT_AND_STOP} if this was the last value accepted by the argument.
- * Or {@link Accept#REJECT_AND_STOP} if this was value was reject and the argument
- * stops accepting new values with no error.
- * Or a string in case of error.
- * Never returns null.
- */
- public abstract Object process(Arg arg, String extra);
- }
-
- /**
- * An argument accepted by the command-line, also called "a flag".
- * Arguments must have a short version (one letter), a long version name and a description.
- * They can have a default value, or it can be null.
- * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String
- * or a String array (in which case the first item is the current by default.)
- */
- protected static class Arg {
- /** Verb for that argument. Never null. */
- private final String mVerb;
- /** Direct Object for that argument. Never null, but can be empty string. */
- private final String mDirectObject;
- /** The 1-letter short name of the argument, e.g. -v. */
- private final String mShortName;
- /** The long name of the argument, e.g. --verbose. */
- private final String mLongName;
- /** A description. Never null. */
- private final String mDescription;
- /** A default value. Can be null. */
- private final Object mDefaultValue;
- /** The argument mode (type + process method). Never null. */
- private final Mode mMode;
- /** True if this argument is mandatory for this verb/directobject. */
- private final boolean mMandatory;
- /** Current value. Initially set to the default value. */
- private Object mCurrentValue;
- /** True if the argument has been used on the command line. */
- private boolean mInCommandLine;
-
- /**
- * Creates a new argument flag description.
- *
- * @param mode The {@link Mode} for the argument.
- * @param mandatory True if this argument is mandatory for this action.
- * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}.
- * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}.
- * @param shortName The one-letter short argument name. Can be empty but not null.
- * @param longName The long argument name. Can be empty but not null.
- * @param description The description. Cannot be null.
- * @param defaultValue The default value (or values), which depends on the selected
- * {@link Mode}. Can be null.
- */
- public Arg(Mode mode,
- boolean mandatory,
- @NonNull String verb,
- @NonNull String directObject,
- @NonNull String shortName,
- @NonNull String longName,
- @NonNull String description,
- @Nullable Object defaultValue) {
- mMode = mode;
- mMandatory = mandatory;
- mVerb = verb;
- mDirectObject = directObject;
- mShortName = shortName;
- mLongName = longName;
- mDescription = description;
- mDefaultValue = defaultValue;
- mInCommandLine = false;
- if (defaultValue instanceof String[]) {
- mCurrentValue = ((String[])defaultValue)[0];
- } else {
- mCurrentValue = mDefaultValue;
- }
- }
-
- /** Return true if this argument is mandatory for this verb/directobject. */
- public boolean isMandatory() {
- return mMandatory;
- }
-
- /** Returns the 1-letter short name of the argument, e.g. -v. */
- public String getShortArg() {
- return mShortName;
- }
-
- /** Returns the long name of the argument, e.g. --verbose. */
- public String getLongArg() {
- return mLongName;
- }
-
- /** Returns the description. Never null. */
- public String getDescription() {
- return mDescription;
- }
-
- /** Returns the verb for that argument. Never null. */
- public String getVerb() {
- return mVerb;
- }
-
- /** Returns the direct Object for that argument. Never null, but can be empty string. */
- public String getDirectObject() {
- return mDirectObject;
- }
-
- /** Returns the default value. Can be null. */
- public Object getDefaultValue() {
- return mDefaultValue;
- }
-
- /** Returns the current value. Initially set to the default value. Can be null. */
- public Object getCurrentValue() {
- return mCurrentValue;
- }
-
- /** Sets the current value. Can be null. */
- public void setCurrentValue(Object currentValue) {
- mCurrentValue = currentValue;
- }
-
- /** Returns the argument mode (type + process method). Never null. */
- public Mode getMode() {
- return mMode;
- }
-
- /** Returns true if the argument has been used on the command line. */
- public boolean isInCommandLine() {
- return mInCommandLine;
- }
-
- /** Sets if the argument has been used on the command line. */
- public void setInCommandLine(boolean inCommandLine) {
- mInCommandLine = inCommandLine;
- }
- }
-
- /**
- * Internal helper to define a new argument for a give action.
- *
- * @param mode The {@link Mode} for the argument.
- * @param mandatory The argument is required (never if {@link Mode#BOOLEAN})
- * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}.
- * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}.
- * @param shortName The one-letter short argument name. Can be empty but not null.
- * @param longName The long argument name. Can be empty but not null.
- * @param description The description. Cannot be null.
- * @param defaultValue The default value (or values), which depends on the selected
- * {@link Mode}.
- */
- protected void define(Mode mode,
- boolean mandatory,
- @NonNull String verb,
- @NonNull String directObject,
- @NonNull String shortName,
- @NonNull String longName,
- @NonNull String description,
- @Nullable Object defaultValue) {
- assert verb != null;
- assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory
-
- // We should always have at least a short or long name, ideally both but never none.
- assert shortName != null;
- assert longName != null;
- assert !shortName.isEmpty() || !longName.isEmpty();
-
- if (directObject == null) {
- directObject = NO_VERB_OBJECT;
- }
-
- String key = verb + '/' + directObject + '/' + longName;
- mArguments.put(key, new Arg(mode, mandatory,
- verb, directObject, shortName, longName, description, defaultValue));
- }
-
- /**
- * Exits in case of error.
- * This is protected so that it can be overridden in unit tests.
- */
- protected void exit() {
- System.exit(1);
- }
-
- /**
- * Prints a line to stdout.
- * This is protected so that it can be overridden in unit tests.
- *
- * @param format The string to be formatted. Cannot be null.
- * @param args Format arguments.
- */
- protected void stdout(String format, Object...args) {
- String output = String.format(format, args);
- output = LineUtil.reflowLine(output);
- mLog.info("%s\n", output); //$NON-NLS-1$
- }
-
- /**
- * Prints a line to stderr.
- * This is protected so that it can be overridden in unit tests.
- *
- * @param format The string to be formatted. Cannot be null.
- * @param args Format arguments.
- */
- protected void stderr(String format, Object...args) {
- mLog.error(null, format, args);
- }
-
- /**
- * Returns the logger object.
- * @return the logger object.
- */
- protected ILogger getLog() {
- return mLog;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java b/base/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
deleted file mode 100755
index c458467..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.sdklib.internal.androidTarget.MockAddonTarget;
-import com.android.sdklib.internal.androidTarget.MockPlatformTarget;
-
-import junit.framework.TestCase;
-
-public class AndroidTargetHashTest extends TestCase {
-
- public final void testGetPlatformHashString() {
- assertEquals("android-10",
- AndroidTargetHash.getPlatformHashString(new AndroidVersion(10, null)));
-
- assertEquals("android-CODE_NAME",
- AndroidTargetHash.getPlatformHashString(new AndroidVersion(10, "CODE_NAME")));
- }
-
- public final void testGetAddonHashString() {
- assertEquals("The Vendor Inc.:My Addon:10",
- AndroidTargetHash.getAddonHashString(
- "The Vendor Inc.",
- "My Addon",
- new AndroidVersion(10, null)));
- }
-
- public final void testGetTargetHashString() {
- MockPlatformTarget t = new MockPlatformTarget(10, 1);
- assertEquals("android-10", AndroidTargetHash.getTargetHashString(t));
- MockAddonTarget a = new MockAddonTarget("My Addon", t, 2);
- assertEquals("vendor 10:My Addon:10", AndroidTargetHash.getTargetHashString(a));
- }
-
- public void testGetPlatformVersion() {
- assertNull(AndroidTargetHash.getPlatformVersion("blah-5"));
- assertNull(AndroidTargetHash.getPlatformVersion("5-blah"));
- assertNull(AndroidTargetHash.getPlatformVersion("android-"));
-
- AndroidVersion version = AndroidTargetHash.getPlatformVersion("android-5");
- assertNotNull(version);
- assertEquals(5, version.getApiLevel());
- assertNull(version.getCodename());
-
- version = AndroidTargetHash.getPlatformVersion("5");
- assertNotNull(version);
- assertEquals(5, version.getApiLevel());
- assertNull(version.getCodename());
-
- version = AndroidTargetHash.getPlatformVersion("android-CUPCAKE");
- assertNotNull(version);
- assertEquals(3, version.getApiLevel());
- assertEquals("CUPCAKE", version.getCodename());
-
- version = AndroidTargetHash.getPlatformVersion("android-KITKAT");
- assertNotNull(version);
- assertEquals(19, version.getApiLevel());
- assertEquals("KITKAT", version.getCodename());
-
- version = AndroidTargetHash.getPlatformVersion("android-UNKNOWN");
- assertNotNull(version);
- assertEquals(1, version.getApiLevel());
- assertEquals("UNKNOWN", version.getCodename());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java b/base/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java
deleted file mode 100755
index 4d0d255..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.utils.ILogger;
-
-import java.util.Properties;
-
-public class BuildToolInfoTest extends SdkManagerTestCase {
-
- /**
- * Wraps an *existing* build-tool info object to expose some of its internals for testing.
- */
- public static class BuildToolInfoWrapper extends BuildToolInfo {
-
- private final BuildToolInfo mInfo;
- private NoPreviewRevision mOverrideJvmVersion;
-
- public BuildToolInfoWrapper(BuildToolInfo info) {
- super(info.getRevision(), info.getLocation());
- mInfo = info;
- }
-
- @Override
- public String getPath(PathId pathId) {
- return mInfo.getPath(pathId);
- }
-
- @Override
- public Properties getRuntimeProps() {
- return mInfo.getRuntimeProps();
- }
-
- @Override
- public boolean isValid(ILogger log) {
- return mInfo.isValid(log);
- }
-
- @Override
- public boolean canRunOnJvm() {
- // This runs canRunOnJvm on *this* instance so that it can
- // access the overridden getCurrentJvmVersion below rather than
- // the original that we are wrapping.
- return super.canRunOnJvm();
- }
-
- @Override
- protected NoPreviewRevision getCurrentJvmVersion() throws NumberFormatException {
- if (mOverrideJvmVersion != null) {
- return mOverrideJvmVersion;
- }
- return mInfo.getCurrentJvmVersion();
- }
-
- public void overrideJvmVersion(@Nullable NoPreviewRevision jvmVersion) {
- mOverrideJvmVersion = jvmVersion;
- }
- }
-
- public void testGetCurrentJvmVersion() {
- SdkManager sdkman = getSdkManager();
- BuildToolInfo bt = sdkman.getBuildTool(new FullRevision(18, 3, 4, 5));
- assertNotNull(bt);
-
- // Check the actual JVM running this test.
- NoPreviewRevision curr = bt.getCurrentJvmVersion();
- // We can reasonably expect this to at least run with JVM 1.5 or more
- assertTrue(curr.compareTo(new FullRevision(1, 5, 0)) > 0);
- // and we can reasonably expect to not be running with JVM 42.0.0
- assertTrue(curr.compareTo(new FullRevision(42, 0, 0)) < 0);
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/LayoutlibVersionTest.java b/base/sdklib/src/test/java/com/android/sdklib/LayoutlibVersionTest.java
deleted file mode 100755
index 0c197aa..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/LayoutlibVersionTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib;
-
-import com.android.sdklib.SdkManager.LayoutlibVersion;
-
-import junit.framework.TestCase;
-
-/**
- * Unit test for {@link LayoutlibVersion}.
- */
-public class LayoutlibVersionTest extends TestCase {
-
- public void testLayoutlibVersion_create() {
- LayoutlibVersion lv = new LayoutlibVersion(1, 2);
- assertEquals(1, lv.getApi());
- assertEquals(2, lv.getRevision());
- }
-
- public void testLayoutlibVersion_compare() {
- assertTrue(new LayoutlibVersion(1, 1).compareTo(new LayoutlibVersion(1, 1)) == 0);
- assertTrue(new LayoutlibVersion(1, 2).compareTo(new LayoutlibVersion(1, 1)) > 0);
- assertTrue(new LayoutlibVersion(1, 1).compareTo(new LayoutlibVersion(1, 2)) < 0);
- assertTrue(new LayoutlibVersion(2, 2).compareTo(new LayoutlibVersion(1, 3)) > 0);
-
- // the lack of an API (== 0) naturally sorts as the lowest value possible.
- assertTrue(new LayoutlibVersion(0, 1).compareTo(new LayoutlibVersion(0, 2)) < 0);
- assertTrue(new LayoutlibVersion(0, 1).compareTo(new LayoutlibVersion(1, 2)) < 0);
- assertTrue(new LayoutlibVersion(0, 3).compareTo(new LayoutlibVersion(1, 2)) < 0);
- assertTrue(new LayoutlibVersion(1, 2).compareTo(new LayoutlibVersion(0, 3)) > 0);
-
- // if we lack the revision number, we don't use it in comparison
- assertTrue(new LayoutlibVersion(2, 0).compareTo(new LayoutlibVersion(2, 2)) == 0);
- assertTrue(new LayoutlibVersion(2, 2).compareTo(new LayoutlibVersion(2, 0)) == 0);
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java b/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
deleted file mode 100755
index db90c56..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-
-import com.android.SdkConstants;
-import com.android.sdklib.BuildToolInfoTest.BuildToolInfoWrapper;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.SdkManager.LayoutlibVersion;
-import com.android.sdklib.internal.androidTarget.PlatformTarget;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.collect.Sets;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-/** Setup will build an SDK Manager local install matching the latest repository-N.xsd. */
-public class SdkManagerTest extends SdkManagerTestCase {
-
- @SuppressWarnings("deprecation")
- public void testSdkManager_LayoutlibVersion() {
- SdkManager sdkman = getSdkManager();
- IAndroidTarget t = sdkman.getTargets()[0];
-
- assertTrue(t instanceof PlatformTarget);
-
- LayoutlibVersion lv = ((PlatformTarget) t).getLayoutlibVersion();
- assertNotNull(lv);
- assertEquals(5, lv.getApi());
- assertEquals(2, lv.getRevision());
-
- assertSame(lv, sdkman.getMaxLayoutlibVersion());
- }
-
- public void testSdkManager_getBuildTools() {
- SdkManager sdkman = getSdkManager();
-
- Set<FullRevision> v = sdkman.getBuildTools();
- // Make sure we get a stable set -- hashmap order isn't stable and can't be used in tests.
- if (!(v instanceof TreeSet<?>)) {
- v = Sets.newTreeSet(v);
- }
-
- assertEquals("", getLog().toString()); // no errors in the logger
- assertEquals("[3.0.0, 3.0.1, 18.3.4 rc5]", Arrays.toString(v.toArray()));
-
- assertEquals(new FullRevision(18, 3, 4, 5),
- sdkman.getLatestBuildTool().getRevision());
-
- // Get infos, first one that doesn't exist returns null.
- assertNull(sdkman.getBuildTool(new FullRevision(1)));
-
- // Now some that exist.
- BuildToolInfo i = sdkman.getBuildTool(new FullRevision(3, 0, 0));
- assertEquals(
- "<BuildToolInfo rev=3.0.0, " +
- "mPath=$SDK/build-tools/3.0.0, " +
- "mPaths={" +
- "AAPT=$SDK/build-tools/3.0.0/aapt, " +
- "AIDL=$SDK/build-tools/3.0.0/aidl, " +
- "DX=$SDK/build-tools/3.0.0/dx, " +
- "DX_JAR=$SDK/build-tools/3.0.0/lib/dx.jar, " +
- "LLVM_RS_CC=$SDK/build-tools/3.0.0/llvm-rs-cc, " +
- "ANDROID_RS=$SDK/build-tools/3.0.0/renderscript/include/, " +
- "ANDROID_RS_CLANG=$SDK/build-tools/3.0.0/renderscript/clang-include/, " +
- "DEXDUMP=$SDK/build-tools/3.0.0/dexdump}>",
- cleanPath(sdkman, i.toString()));
-
- i = sdkman.getBuildTool(new FullRevision(18, 3, 4, 5));
- assertEquals(
- "<BuildToolInfo rev=18.3.4 rc5, " +
- "mPath=$SDK/build-tools/18.3.4 rc5, " +
- "mPaths={" +
- "AAPT=$SDK/build-tools/18.3.4 rc5/aapt, " +
- "AIDL=$SDK/build-tools/18.3.4 rc5/aidl, " +
- "DX=$SDK/build-tools/18.3.4 rc5/dx, " +
- "DX_JAR=$SDK/build-tools/18.3.4 rc5/lib/dx.jar, " +
- "LLVM_RS_CC=$SDK/build-tools/18.3.4 rc5/llvm-rs-cc, " +
- "ANDROID_RS=$SDK/build-tools/18.3.4 rc5/renderscript/include/, " +
- "ANDROID_RS_CLANG=$SDK/build-tools/18.3.4 rc5/renderscript/clang-include/, " +
- "DEXDUMP=$SDK/build-tools/18.3.4 rc5/dexdump, " +
- "BCC_COMPAT=$SDK/build-tools/18.3.4 rc5/bcc_compat, " +
- "LD_ARM=$SDK/build-tools/18.3.4 rc5/arm-linux-androideabi-ld, " +
- "LD_X86=$SDK/build-tools/18.3.4 rc5/i686-linux-android-ld, " +
- "LD_MIPS=$SDK/build-tools/18.3.4 rc5/mipsel-linux-android-ld" +
- "}>",
- cleanPath(sdkman, i.toString()));
- }
-
- public void testSdkManager_BuildTools_canRunOnJvm() throws IOException {
- SdkManager sdkman = getSdkManager();
- BuildToolInfo bt = sdkman.getBuildTool(new FullRevision(18, 3, 4, 5));
- assertNotNull(bt);
-
- // By default there is no runtime.properties file and no Runtime.Jvm value.
- // Since there is no requirement, this build-tool package can run everywhere.
- Properties props1 = bt.getRuntimeProps();
- assertTrue(props1.isEmpty());
- assertTrue(bt.canRunOnJvm());
-
- // We know our tests require at least a JVM 1.5 to run so this build-tool can run here.
- createFileProps("runtime.properties", bt.getLocation(), "Runtime.Jvm", "1.5.0");
- Properties props15 = bt.getRuntimeProps();
- assertFalse(props15.isEmpty());
- assertTrue(bt.canRunOnJvm());
-
- createFileProps("runtime.properties", bt.getLocation(), "Runtime.Jvm", "42.0.0");
- Properties props42 = bt.getRuntimeProps();
- assertFalse(props42.isEmpty());
-
- BuildToolInfoWrapper wrap = new BuildToolInfoTest.BuildToolInfoWrapper(bt);
-
- // Let's assume a real JVM 42.0.0 doesn't exist yet
- wrap.overrideJvmVersion(new NoPreviewRevision(1, 6, 0));
- assertFalse(wrap.canRunOnJvm());
-
- // Let's assume a real JVM 42.0.0 and above exists
- wrap.overrideJvmVersion(new NoPreviewRevision(42, 0, 0));
- assertTrue(wrap.canRunOnJvm());
-
- wrap.overrideJvmVersion(new NoPreviewRevision(42, 0, 1));
- assertTrue(wrap.canRunOnJvm());
-
- wrap.overrideJvmVersion(new NoPreviewRevision(42, 1, 1));
- assertTrue(wrap.canRunOnJvm());
-
- wrap.overrideJvmVersion(new NoPreviewRevision(43, 1, 1));
- assertTrue(wrap.canRunOnJvm());
-
- }
-
- public void testSdkManager_SystemImage() throws Exception {
- SdkManager sdkman = getSdkManager();
- assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
- IAndroidTarget t = sdkman.getTargets()[0];
-
- // By default SdkManagerTestCase creates an SDK with one platform containing
- // a legacy armeabi system image.
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in legacy folder='$SDK/platforms/v0_0/images']",
- cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
-
- // 1- add a few "platform subfolders" system images and reload the SDK.
- // This disables the "legacy" mode, which means that although the armeabi
- // target from above is present, it is no longer visible.
-
- makeSystemImageFolder(new SystemImage(sdkman, t,
- LocationType.IN_IMAGES_SUBFOLDER,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI_V7A,
- FileOp.EMPTY_FILE_ARRAY), null);
- makeSystemImageFolder(new SystemImage(sdkman, t,
- LocationType.IN_IMAGES_SUBFOLDER,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_INTEL_ATOM,
- FileOp.EMPTY_FILE_ARRAY), null);
-
- sdkman.reloadSdk(getLog());
- assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
- t = sdkman.getTargets()[0];
-
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi-v7a, location in images subfolder='$SDK/platforms/v0_0/images/armeabi-v7a', " +
- "SystemImage tag=default, ABI=x86, location in images subfolder='$SDK/platforms/v0_0/images/x86']",
- cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
-
- // 2- add arm + arm v7a images using the new SDK/system-images.
- // The x86 one from the platform subfolder is still visible.
- // The armeabi one from the legacy folder is overridden by the new one.
- // The armeabi-v7a one from the platform subfolder is overridden by the new one.
-
- makeSystemImageFolder(new SystemImage(sdkman, t,
- LocationType.IN_SYSTEM_IMAGE,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI,
- FileOp.EMPTY_FILE_ARRAY), null);
- makeSystemImageFolder(new SystemImage(sdkman, t,
- LocationType.IN_SYSTEM_IMAGE,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI_V7A,
- FileOp.EMPTY_FILE_ARRAY), null);
-
- sdkman.reloadSdk(getLog());
- assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
- t = sdkman.getTargets()[0];
-
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in system image='$SDK/system-images/android-0/default/armeabi', " +
- "SystemImage tag=default, ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/default/armeabi-v7a', " +
- "SystemImage tag=default, ABI=x86, location in images subfolder='$SDK/platforms/v0_0/images/x86']",
- cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
-
- // 3- add an arm v7a image with a custom tag. It exists in parallel with the default one.
-
- makeSystemImageFolder(new SystemImage(sdkman, t,
- LocationType.IN_SYSTEM_IMAGE,
- new IdDisplay("tag-1", "My Tag 1"),
- SdkConstants.ABI_ARMEABI_V7A,
- FileOp.EMPTY_FILE_ARRAY), null);
-
- sdkman.reloadSdk(getLog());
- assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
- t = sdkman.getTargets()[0];
-
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in system image='$SDK/system-images/android-0/default/armeabi', " +
- "SystemImage tag=default, ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/default/armeabi-v7a', " +
- "SystemImage tag=default, ABI=x86, location in images subfolder='$SDK/platforms/v0_0/images/x86', " +
- "SystemImage tag=tag-1, ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/tag-1/armeabi-v7a']",
- cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
- }
-
- public void testSdkManager_SystemImage_LegacyOverride() throws Exception {
- SdkManager sdkman = getSdkManager();
- assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
- IAndroidTarget t = sdkman.getTargets()[0];
-
- // By default SdkManagerTestCase creates an SDK with one platform containing
- // a legacy armeabi system image.
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in legacy folder='$SDK/platforms/v0_0/images']",
- cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
-
- // Now add a different ABI system image in the new system-images folder.
- // This does not hide the legacy one as long as the ABI type is different
- // (to contrast: having at least one sub-folder in the platform's legacy images folder
- // will hide the legacy system image. Whereas this does not happen with the new type.)
-
- makeSystemImageFolder(new SystemImage(sdkman, t,
- LocationType.IN_SYSTEM_IMAGE,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_INTEL_ATOM,
- FileOp.EMPTY_FILE_ARRAY), null);
-
- sdkman.reloadSdk(getLog());
- assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
- t = sdkman.getTargets()[0];
-
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in legacy folder='$SDK/platforms/v0_0/images', " +
- "SystemImage tag=default, ABI=x86, location in system image='$SDK/system-images/android-0/default/x86']",
- cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
-
- // Now if we have one new system-image using the same ABI type, it will override the
- // legacy one. This gives us a good path for updates.
-
- makeSystemImageFolder(new SystemImage(sdkman, t,
- LocationType.IN_SYSTEM_IMAGE,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI,
- FileOp.EMPTY_FILE_ARRAY), null);
-
-
- sdkman.reloadSdk(getLog());
- assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
- t = sdkman.getTargets()[0];
-
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in system image='$SDK/system-images/android-0/default/armeabi', " +
- "SystemImage tag=default, ABI=x86, location in system image='$SDK/system-images/android-0/default/x86']",
- cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
- }
-
- /**
- * Sanitizes the paths used when testing results.
- * <p/>
- * Some methods return absolute paths to the SDK.
- * However the SDK path is actually a randomized location.
- * We clean it by replacing it by the constant '$SDK'.
- * Also all the Windows path separators are converted to unix-like / separators
- * and ".exe" and ".bat" are removed (e.g. for build-tools binaries).
- */
- private String cleanPath(SdkManager sdkman, String string) {
- return string
- .replaceAll(Pattern.quote(sdkman.getLocation()), "\\$SDK") //$NON-NLS-1$
- .replaceAll("\\.(?:bat|exe)", "") //$NON-NLS-1$ //$NON-NLS-2$
- .replace('\\', '/');
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTest7.java b/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTest7.java
deleted file mode 100755
index 6c305ee..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTest7.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-
-import com.android.sdklib.repository.FullRevision;
-import com.google.common.collect.Sets;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.TreeSet;
-
-public class SdkManagerTest7 extends SdkManagerTestCase {
-
- /** Setup will build an SDK Manager local install matching a repository-7.xsd. */
- @Override
- public void setUp() throws Exception {
- super.setUp(7);
- }
-
- public void testSdkManager_getBuildTools() {
- // There is no build-tools folder in this repository.
- SdkManager sdkman = getSdkManager();
-
- Set<FullRevision> v = sdkman.getBuildTools();
- // Make sure we get a stable set -- hashmap order isn't stable and can't be used in tests.
- if (!(v instanceof TreeSet<?>)) {
- v = Sets.newTreeSet(v);
- }
-
- assertEquals("", getLog().toString()); // no errors in the logger
- assertEquals("[]", Arrays.toString(v.toArray()));
-
- assertNull(sdkman.getBuildTool(new FullRevision(1)));
- assertNull(sdkman.getBuildTool(new FullRevision(3, 0, 0)));
- assertNull(sdkman.getBuildTool(new FullRevision(12, 3, 4, 5)));
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java b/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
deleted file mode 100755
index e3c945f..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
+++ /dev/null
@@ -1,588 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.resources.Density;
-import com.android.resources.Keyboard;
-import com.android.resources.KeyboardState;
-import com.android.resources.Navigation;
-import com.android.resources.NavigationState;
-import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenRatio;
-import com.android.resources.ScreenSize;
-import com.android.resources.TouchScreen;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.devices.ButtonType;
-import com.android.sdklib.devices.Device;
-import com.android.sdklib.devices.Device.Builder;
-import com.android.sdklib.devices.DeviceWriter;
-import com.android.sdklib.devices.Hardware;
-import com.android.sdklib.devices.Multitouch;
-import com.android.sdklib.devices.PowerType;
-import com.android.sdklib.devices.Screen;
-import com.android.sdklib.devices.ScreenType;
-import com.android.sdklib.devices.Software;
-import com.android.sdklib.devices.State;
-import com.android.sdklib.devices.Storage;
-import com.android.sdklib.devices.Storage.Unit;
-import com.android.sdklib.internal.avd.AvdManager;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.mock.MockLog;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.repository.local.LocalPlatformPkgInfo;
-import com.android.sdklib.repository.local.LocalSysImgPkgInfo;
-import com.android.utils.ILogger;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Base Test case that allocates a temporary SDK, a temporary AVD base folder with an SdkManager and
- * an AvdManager that points to them. <p/> Also overrides the {@link AndroidLocation} to point to
- * temp one.
- */
-public abstract class SdkManagerTestCase extends AndroidLocationTestCase {
-
- protected static final String TARGET_DIR_NAME_0 = "v0_0";
-
- private File mFakeSdk;
-
- private MockLog mLog;
-
- private SdkManager mSdkManager;
-
- private AvdManager mAvdManager;
-
- private int mRepoXsdLevel;
-
- /**
- * Returns the {@link MockLog} for this test case.
- */
- public MockLog getLog() {
- return mLog;
- }
-
- /**
- * Returns the {@link SdkManager} for this test case.
- */
- public SdkManager getSdkManager() {
- return mSdkManager;
- }
-
- /**
- * Returns the {@link AvdManager} for this test case.
- */
- public AvdManager getAvdManager() {
- return mAvdManager;
- }
-
- /**
- * Sets up a {@link MockLog}, a fake SDK in a temporary directory and an AVD Manager pointing to
- * an initially-empty AVD directory.
- */
- public void setUp(int repoXsdLevel) throws Exception {
- super.setUp();
- mRepoXsdLevel = repoXsdLevel;
- mLog = new MockLog();
- makeFakeSdk();
- createSdkAvdManagers();
- }
-
- /**
- * Recreate the SDK and AVD Managers from scratch even if they already existed. Useful for tests
- * that want to reset their state without recreating the android-home or the fake SDK. The SDK
- * will be reparsed.
- */
- protected void createSdkAvdManagers() throws AndroidLocationException {
- mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog);
- assertNotNull("SdkManager location was invalid", mSdkManager);
- // Note: it's safe to use the default AvdManager implementation since makeFakeAndroidHome
- // above overrides the ANDROID_HOME folder to use a temp folder; consequently all
- // the AVDs created here will be located in this temp folder and will not alter
- // or pollute the default user's AVD folder.
- mAvdManager = new AvdManager(mSdkManager.getLocalSdk(), mLog) {
- @Override
- protected boolean createSdCard(
- String toolLocation,
- String size,
- String location,
- ILogger log) {
- if (new File(toolLocation).exists()) {
- log.info("[EXEC] %1$s %2$s %3$s\n", toolLocation, size, location);
- return true;
- } else {
- log.error(null, "Failed to create the SD card.\n");
- return false;
- }
-
- }
- };
- }
-
- /**
- * Sets up a {@link MockLog}, a fake SDK in a temporary directory and an AVD Manager pointing to
- * an initially-empty AVD directory.
- */
- @Override
- public void setUp() throws Exception {
- setUp(SdkRepoConstants.NS_LATEST_VERSION);
- }
-
- /**
- * Removes the temporary SDK and AVD directories.
- */
- @Override
- public void tearDown() throws Exception {
- tearDownSdk();
- super.tearDown();
- }
-
- /**
- * Build enough of a skeleton SDK to make the tests pass. <p/> Ideally this wouldn't touch the
- * file system but the current structure of the SdkManager and AvdManager makes this
- * impossible.
- */
- private void makeFakeSdk() throws IOException {
- // First we create a temp file to "reserve" the temp directory name we want to use.
- mFakeSdk = File.createTempFile(
- "sdk_" + this.getClass().getSimpleName() + '_' + this.getName(), null);
- // Then erase the file and make the directory
- mFakeSdk.delete();
- mFakeSdk.mkdirs();
-
- File addonsDir = new File(mFakeSdk, SdkConstants.FD_ADDONS);
- addonsDir.mkdir();
-
- File toolsDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_FOLDER);
- toolsDir.mkdir();
- createSourceProps(toolsDir, PkgProps.PKG_REVISION, "1.0.1");
- new File(toolsDir, SdkConstants.androidCmdName()).createNewFile();
- new File(toolsDir, SdkConstants.FN_EMULATOR).createNewFile();
- new File(toolsDir, SdkConstants.mkSdCardCmdName()).createNewFile();
-
- makePlatformTools(new File(mFakeSdk, SdkConstants.FD_PLATFORM_TOOLS));
-
- if (mRepoXsdLevel >= 8) {
- makeBuildTools(mFakeSdk);
- }
-
- File toolsLibEmuDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator");
- toolsLibEmuDir.mkdirs();
- new File(toolsLibEmuDir, "snapshots.img").createNewFile();
- File platformsDir = new File(mFakeSdk, SdkConstants.FD_PLATFORMS);
-
- // Creating a fake target here on down
- File targetDir = makeFakeTargetInternal(platformsDir);
- makeFakeLegacySysImg(targetDir, SdkConstants.ABI_ARMEABI);
-
- makeFakeSkin(targetDir, "HVGA");
- makeFakeSourceInternal(mFakeSdk);
- }
-
- private void tearDownSdk() {
- deleteDir(mFakeSdk);
- }
-
- /**
- * Creates the system image folder and places a fake userdata.img in it.
- *
- * @param systemImage A system image with a valid location.
- */
- protected void makeSystemImageFolder(ISystemImage systemImage, String deviceId)
- throws Exception {
- File sysImgDir = systemImage.getLocation();
- String vendor = systemImage.getAddonVendor() == null ? null
- : systemImage.getAddonVendor().getId();
- if (systemImage.getLocationType() == LocationType.IN_LEGACY_FOLDER) {
- // legacy mode. Path should look like SDK/platforms/platform-N/userdata.img
- makeFakeLegacySysImg(sysImgDir.getParentFile(), systemImage.getAbiType());
-
- } else if (systemImage.getLocationType() == LocationType.IN_IMAGES_SUBFOLDER) {
- // not-so-legacy mode.
- // Path should look like SDK/platforms/platform-N/images/userdata.img
- makeFakeSysImgInternal(
- sysImgDir,
- systemImage.getTag().getId(),
- systemImage.getAbiType(),
- deviceId,
- vendor);
-
- } else if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) {
- // system-image folder mode.
- // Path should like SDK/system-images/platform-N/tag/abi/userdata.img+source.properties
- makeFakeSysImgInternal(
- sysImgDir,
- systemImage.getTag().getId(),
- systemImage.getAbiType(),
- deviceId,
- vendor);
- }
- }
-
- /**
- * Creates the system image folder and places a fake userdata.img in it. This must be called
- * after {@link #setUp()} so that it can use the temp fake SDK folder, and consequently you do
- * not need to specify the SDK root.
- *
- * @param targetDir The targetDir segment of the sys-image folder. Use {@link
- * #TARGET_DIR_NAME_0} to match the default single platform.
- * @param tagId An optional tag id. Use null for legacy no-tag system images.
- * @param abiType The abi for the system image.
- * @return The directory of the system-image/tag/abi created.
- * @throws IOException if the file fails to be created.
- */
- @NonNull
- protected File makeSystemImageFolder(
- @NonNull String targetDir,
- @Nullable String tagId,
- @NonNull String abiType) throws Exception {
- File sysImgDir = new File(mFakeSdk, SdkConstants.FD_SYSTEM_IMAGES);
- sysImgDir = new File(sysImgDir, targetDir);
- if (tagId != null) {
- sysImgDir = new File(sysImgDir, tagId);
- }
- sysImgDir = new File(sysImgDir, abiType);
-
- makeFakeSysImgInternal(sysImgDir, tagId, abiType, null, null);
- return sysImgDir;
- }
-
- //----
-
- private void createTextFile(File dir, String filepath, String... lines) throws IOException {
- File file = new File(dir, filepath);
-
- File parent = file.getParentFile();
- if (!parent.isDirectory()) {
- parent.mkdirs();
- }
-
- if (!file.isFile()) {
- assertTrue(file.createNewFile());
- }
- if (lines != null && lines.length > 0) {
- FileWriter out = new FileWriter(file);
- for (String line : lines) {
- out.write(line);
- }
- out.close();
- }
- }
-
- /**
- * Utility used by {@link #makeFakeSdk()} to create a fake target with API 0, rev 0.
- */
- private File makeFakeTargetInternal(File platformsDir) throws IOException {
- File targetDir = new File(platformsDir, TARGET_DIR_NAME_0);
- targetDir.mkdirs();
- new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile();
- new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile();
-
- createSourceProps(targetDir,
- PkgProps.PKG_REVISION, "1",
- PkgProps.PLATFORM_VERSION, "0.0",
- PkgProps.VERSION_API_LEVEL, "0",
- PkgProps.LAYOUTLIB_API, "5",
- PkgProps.LAYOUTLIB_REV, "2");
-
- createFileProps(SdkConstants.FN_BUILD_PROP, targetDir,
- LocalPlatformPkgInfo.PROP_VERSION_RELEASE, "0.0",
- LocalPlatformPkgInfo.PROP_VERSION_SDK, "0",
- LocalPlatformPkgInfo.PROP_VERSION_CODENAME, "REL");
-
- return targetDir;
- }
-
- /**
- * Utility to create a fake *legacy* sys image in a platform folder. Legacy system images follow
- * that path pattern: $SDK/platforms/platform-N/images/userdata.img
- *
- * They have no source.properties file in that directory.
- */
- private void makeFakeLegacySysImg(
- @NonNull File platformDir,
- @NonNull String abiType) throws IOException {
- File imagesDir = new File(platformDir, "images");
- imagesDir.mkdirs();
- new File(imagesDir, "userdata.img").createNewFile();
- }
-
- /**
- * Utility to create a fake sys image in the system-images folder.
- *
- * "modern" (as in "not legacy") system-images follow that path pattern:
- * $SDK/system-images/platform-N/abi/source.properties $SDK/system-images/platform-N/abi/userdata.img
- * or $SDK/system-images/platform-N/tag/abi/source.properties $SDK/system-images/platform-N/tag/abi/userdata.img
- *
- * The tag id is optional and was only introduced in API 20 / Tools 22.6. The platform-N and the
- * tag folder names are irrelevant as the info from source.properties matters most.
- */
- private void makeFakeSysImgInternal(
- @NonNull File sysImgDir,
- @Nullable String tagId,
- @NonNull String abiType,
- @Nullable String deviceId,
- @Nullable String deviceMfg) throws Exception {
- sysImgDir.mkdirs();
- new File(sysImgDir, "userdata.img").createNewFile();
-
- if (tagId == null) {
- createSourceProps(sysImgDir,
- PkgProps.PKG_REVISION, "0",
- PkgProps.VERSION_API_LEVEL, "0",
- PkgProps.SYS_IMG_ABI, abiType);
- } else {
- String tagDisplay = LocalSysImgPkgInfo.tagIdToDisplay(tagId);
- createSourceProps(sysImgDir,
- PkgProps.PKG_REVISION, "0",
- PkgProps.VERSION_API_LEVEL, "0",
- PkgProps.SYS_IMG_TAG_ID, tagId,
- PkgProps.SYS_IMG_TAG_DISPLAY, tagDisplay,
- PkgProps.SYS_IMG_ABI, abiType,
- PkgProps.PKG_LIST_DISPLAY,
- "Sys-Img v0 for (" + tagDisplay + ", " + abiType + ")");
-
- // create a devices.xml file
- List<Device> devices = new ArrayList<Device>();
- Builder b = new Device.Builder();
- b.setName("Mock " + tagDisplay + " Device Name");
- b.setId(deviceId == null ? "MockDevice-" + tagId : deviceId);
- b.setManufacturer(deviceMfg == null ? "Mock " + tagDisplay + " OEM" : deviceMfg);
-
- Software sw = new Software();
- sw.setGlVersion("4.2");
- sw.setLiveWallpaperSupport(false);
- sw.setMaxSdkLevel(42);
- sw.setMinSdkLevel(1);
- sw.setStatusBar(true);
-
- Screen sc = new Screen();
- sc.setDiagonalLength(7);
- sc.setMechanism(TouchScreen.FINGER);
- sc.setMultitouch(Multitouch.JAZZ_HANDS);
- sc.setPixelDensity(Density.HIGH);
- sc.setRatio(ScreenRatio.NOTLONG);
- sc.setScreenType(ScreenType.CAPACITIVE);
- sc.setSize(ScreenSize.LARGE);
- sc.setXDimension(5);
- sc.setXdpi(100);
- sc.setYDimension(4);
- sc.setYdpi(100);
-
- Hardware hw = new Hardware();
- hw.setButtonType(ButtonType.SOFT);
- hw.setChargeType(PowerType.BATTERY);
- hw.setCpu(abiType);
- hw.setGpu("pixelpushing");
- hw.setHasMic(true);
- hw.setKeyboard(Keyboard.QWERTY);
- hw.setNav(Navigation.NONAV);
- hw.setRam(new Storage(512, Unit.MiB));
- hw.setScreen(sc);
-
- State st = new State();
- st.setName("portrait");
- st.setDescription("Portrait");
- st.setDefaultState(true);
- st.setOrientation(ScreenOrientation.PORTRAIT);
- st.setKeyState(KeyboardState.SOFT);
- st.setNavState(NavigationState.HIDDEN);
- st.setHardware(hw);
-
- b.addSoftware(sw);
- b.addState(st);
-
- devices.add(b.build());
-
- File f = new File(sysImgDir, "devices.xml");
- FileOutputStream fos = new FileOutputStream(f);
- DeviceWriter.writeToXml(fos, devices);
- fos.close();
- }
- }
-
- /**
- * Utility to make a fake skin for the given target
- */
- protected void makeFakeSkin(File targetDir, String skinName) throws IOException {
- File skinFolder = FileOp.append(targetDir, "skins", skinName);
- skinFolder.mkdirs();
-
- // To be detected properly, the skin folder should have a "layout" file.
- // Its content is however not parsed.
- FileWriter out = new FileWriter(new File(skinFolder, "layout"));
- out.write("parts {\n}\n");
- out.close();
- }
-
- /**
- * Utility to create a fake source with a few files in the given sdk folder.
- */
- private void makeFakeSourceInternal(File sdkDir) throws IOException {
- File sourcesDir = FileOp.append(sdkDir, SdkConstants.FD_PKG_SOURCES, "android-0");
- sourcesDir.mkdirs();
-
- createSourceProps(sourcesDir, PkgProps.VERSION_API_LEVEL, "0");
-
- File dir1 = FileOp.append(sourcesDir, "src", "com", "android");
- dir1.mkdirs();
- FileOp.append(dir1, "File1.java").createNewFile();
- FileOp.append(dir1, "File2.java").createNewFile();
-
- FileOp.append(sourcesDir, "res", "values").mkdirs();
- FileOp.append(sourcesDir, "res", "values", "styles.xml").createNewFile();
- }
-
- private void makePlatformTools(File platformToolsDir) throws IOException {
- platformToolsDir.mkdir();
- createSourceProps(platformToolsDir, PkgProps.PKG_REVISION, "17.1.2");
-
- // platform-tools revision >= 17 requires only an adb file to be valid.
- new File(platformToolsDir, SdkConstants.FN_ADB).createNewFile();
- }
-
- private void makeBuildTools(File sdkDir) throws IOException {
- for (String revision : new String[]{"3.0.0", "3.0.1", "18.3.4 rc5"}) {
- createFakeBuildTools(sdkDir, "ANY", revision);
- }
- }
-
- /**
- * Adds a new fake build tools to the SDK In the given SDK/build-tools folder.
- *
- * @param sdkDir The SDK top folder. Must already exist.
- * @param os The OS. One of HostOs#toString() or "ANY".
- * @param revision The "x.y.z rc r" revision number from {@link FullRevision#toShortString()}.
- */
- protected void createFakeBuildTools(File sdkDir, String os, String revision)
- throws IOException {
- File buildToolsTopDir = new File(sdkDir, SdkConstants.FD_BUILD_TOOLS);
- buildToolsTopDir.mkdir();
- File buildToolsDir = new File(buildToolsTopDir, revision);
- createSourceProps(buildToolsDir,
- PkgProps.PKG_REVISION, revision,
- "Archive.Os", os);
-
- FullRevision fullRevision = FullRevision.parseRevision(revision);
-
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.AAPT, SdkConstants.FN_AAPT);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.AIDL, SdkConstants.FN_AIDL);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.DX, SdkConstants.FN_DX);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.DX_JAR, SdkConstants.FD_LIB + File.separator +
- SdkConstants.FN_DX_JAR);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.LLVM_RS_CC, SdkConstants.FN_RENDERSCRIPT);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.ANDROID_RS, SdkConstants.OS_FRAMEWORK_RS + File.separator +
- "placeholder.txt");
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.ANDROID_RS_CLANG,
- SdkConstants.OS_FRAMEWORK_RS_CLANG + File.separator +
- "placeholder.txt");
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.BCC_COMPAT, SdkConstants.FN_BCC_COMPAT);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.LD_ARM, SdkConstants.FN_LD_ARM);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.LD_MIPS, SdkConstants.FN_LD_MIPS);
- createFakeBuildToolsFile(
- buildToolsDir, fullRevision,
- BuildToolInfo.PathId.LD_X86, SdkConstants.FN_LD_X86);
- }
-
- private void createFakeBuildToolsFile(@NonNull File dir,
- @NonNull FullRevision buildToolsRevision,
- @NonNull BuildToolInfo.PathId pathId,
- @NonNull String filepath)
- throws IOException {
-
- if (pathId.isPresentIn(buildToolsRevision)) {
- createTextFile(dir, filepath);
- }
- }
-
-
- protected void createSourceProps(File parentDir, String... paramValuePairs) throws IOException {
- createFileProps(SdkConstants.FN_SOURCE_PROP, parentDir, paramValuePairs);
- }
-
- protected void createFileProps(String fileName, File parentDir, String... paramValuePairs)
- throws IOException {
- File sourceProp = new File(parentDir, fileName);
- parentDir = sourceProp.getParentFile();
- if (!parentDir.isDirectory()) {
- assertTrue(parentDir.mkdirs());
- }
- if (!sourceProp.isFile()) {
- assertTrue(sourceProp.createNewFile());
- }
- FileWriter out = new FileWriter(sourceProp);
- int n = paramValuePairs.length;
- assertTrue("paramValuePairs must have an even length, format [param=value]+", n % 2 == 0);
- for (int i = 0; i < n; i += 2) {
- out.write(paramValuePairs[i] + '=' + paramValuePairs[i + 1] + '\n');
- }
- out.close();
-
- }
-
-
- /**
- * Recursive delete directory. Mostly for fake SDKs.
- *
- * @param root directory to delete
- */
- protected void deleteDir(File root) {
- if (root.exists()) {
- for (File file : root.listFiles()) {
- if (file.isDirectory()) {
- deleteDir(file);
- } else {
- file.delete();
- }
- }
- root.delete();
- }
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java b/base/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java
deleted file mode 100755
index 9bc9140..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.devices;
-
-import com.android.resources.Keyboard;
-import com.android.resources.Navigation;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SdkManagerTestCase;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.devices.Device.Builder;
-import com.android.sdklib.devices.DeviceManager.DeviceFilter;
-import com.android.sdklib.devices.DeviceManager.DeviceStatus;
-import com.android.sdklib.mock.MockLog;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-
-import java.io.File;
-import java.util.*;
-
-public class DeviceManagerTest extends SdkManagerTestCase {
-
- private DeviceManager dm;
-
- private MockLog log;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- dm = createDeviceManager();
- }
-
- private DeviceManager createDeviceManager() {
- log = super.getLog();
- File sdkLocation = getSdkManager().getLocalSdk().getLocation();
- return DeviceManager.createInstance(sdkLocation, log);
- }
-
- /**
- * Returns a list of just the devices' display names, for unit test comparisons.
- */
- private static String listDisplayName(Device device) {
- if (device == null) {
- return null;
- }
- return device.getDisplayName();
- }
-
- /**
- * Returns a list of just the devices' display names, for unit test comparisons.
- */
- private static List<String> listDisplayNames(Collection<Device> devices) {
- if (devices == null) {
- return null;
- }
- List<String> names = new ArrayList<String>();
- for (Device d : devices) {
- names.add(listDisplayName(d));
- }
- Collections.sort(names);
- return names;
- }
-
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
- public final void testGetDevices_Default() {
- // no user devices defined in the test's custom .android home folder
- assertEquals("[]", dm.getDevices(DeviceFilter.USER).toString());
- assertEquals("", log.toString());
-
- // no system-images devices defined in the SDK by default
- assertEquals("[]", dm.getDevices(DeviceFilter.SYSTEM_IMAGES).toString());
- assertEquals("", log.toString());
-
- // this list comes from devices.xml bundled in the JAR
- // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
- assertEquals(
- "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, " +
- "3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, " +
- "3.7\" FWVGA slider, 3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), " +
- "4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, " +
- "7\" WSVGA (Tablet)]",
- listDisplayNames(dm.getDevices(DeviceFilter.DEFAULT)).toString());
- assertEquals("", log.toString());
-
- assertEquals("2.7\" QVGA",
- listDisplayName(dm.getDevice("2.7in QVGA", "Generic")));
-
- // this list comes from the nexus.xml bundled in the JAR
- // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
- assertEquals("[Android TV (1080p), Android TV (720p), Android Wear Round, " +
- "Android Wear Round Chin, Android Wear Square, " +
- "Galaxy Nexus, Nexus 10, Nexus 4, Nexus 5, Nexus 6, Nexus 7, " +
- "Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
- listDisplayNames(dm.getDevices(DeviceFilter.VENDOR)).toString());
- assertEquals("", log.toString());
-
- assertEquals("Nexus One",
- listDisplayName(dm.getDevice("Nexus One", "Google")));
-
- assertEquals(
- "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
- "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
- "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
- "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet), Android TV (1080p), "
- +
- "Android TV (720p), Android Wear Round, Android Wear Round Chin, " +
- "Android Wear Square, Galaxy Nexus, Nexus 10, Nexus 4, Nexus 5, " +
- "Nexus 6, Nexus 7, Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
- listDisplayNames(dm.getDevices(DeviceManager.ALL_DEVICES)).toString());
- assertEquals("", log.toString());
- }
-
- public final void testGetDevice() {
- // get a definition from the bundled devices.xml file
- Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
- assertEquals("7\" WSVGA (Tablet)", d1.getDisplayName());
- assertEquals("", log.toString());
-
- // get a definition from the bundled nexus.xml file
- Device d2 = dm.getDevice("Nexus One", "Google");
- assertEquals("Nexus One", d2.getDisplayName());
- assertEquals("", log.toString());
- }
-
- public final void testGetDevices_UserDevice() {
-
- Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
-
- Builder b = new Device.Builder(d1);
- b.setId("MyCustomTablet");
- b.setName("My Custom Tablet");
- b.setManufacturer("OEM");
-
- Device d2 = b.build();
-
- dm.addUserDevice(d2);
- dm.saveUserDevices();
-
- assertEquals("My Custom Tablet", dm.getDevice("MyCustomTablet", "OEM").getDisplayName());
- assertEquals("", log.toString());
-
- // create a new device manager, forcing it reload all files
- dm = null;
- DeviceManager dm2 = createDeviceManager();
-
- assertEquals("My Custom Tablet", dm2.getDevice("MyCustomTablet", "OEM").getDisplayName());
- assertEquals("", log.toString());
-
- // 1 user device defined in the test's custom .android home folder
- assertEquals("[My Custom Tablet]",
- listDisplayNames(dm2.getDevices(DeviceFilter.USER)).toString());
- assertEquals("", log.toString());
-
- // no system-images devices defined in the SDK by default
- assertEquals("[]",
- listDisplayNames(dm2.getDevices(DeviceFilter.SYSTEM_IMAGES)).toString());
- assertEquals("", log.toString());
-
- // this list comes from devices.xml bundled in the JAR
- // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
- assertEquals(
- "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
- "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
- "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
- "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet)]",
- listDisplayNames(dm2.getDevices(DeviceFilter.DEFAULT)).toString());
- assertEquals("", log.toString());
-
- // this list comes from the nexus.xml bundled in the JAR
- // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
- assertEquals("[Android TV (1080p), Android TV (720p), Android Wear Round, " +
- "Android Wear Round Chin, Android Wear Square, Galaxy Nexus, " +
- "Nexus 10, Nexus 4, Nexus 5, Nexus 6, Nexus 7, Nexus 7 (2012), " +
- "Nexus 9, Nexus One, Nexus S]",
- listDisplayNames(dm2.getDevices(DeviceFilter.VENDOR)).toString());
- assertEquals("", log.toString());
-
- assertEquals(
- "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
- "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
- "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
- "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet), Android TV (1080p), "
- +
- "Android TV (720p), Android Wear Round, Android Wear Round Chin, " +
- "Android Wear Square, Galaxy Nexus, My Custom Tablet, Nexus 10, " +
- "Nexus 4, Nexus 5, Nexus 6, Nexus 7, Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
- listDisplayNames(dm2.getDevices(DeviceManager.ALL_DEVICES)).toString());
- assertEquals("", log.toString());
- }
-
- public final void testGetDevices_SysImgDevice() throws Exception {
- // this adds a devices.xml with one device
- makeSystemImageFolder(TARGET_DIR_NAME_0, "tag-1", "x86");
-
- // no user devices defined in the test's custom .android home folder
- assertEquals("[]", listDisplayNames(dm.getDevices(DeviceFilter.USER)).toString());
- assertEquals("", log.toString());
-
- // find the system-images specific device added by makeSystemImageFolder above
- // using both the getDevices() API and the device-specific getDevice() API.
- assertEquals("[Mock Tag 1 Device Name]",
- listDisplayNames(dm.getDevices(DeviceFilter.SYSTEM_IMAGES)).toString());
- assertEquals("", log.toString());
-
- assertEquals("Mock Tag 1 Device Name",
- listDisplayName(dm.getDevice("MockDevice-tag-1", "Mock Tag 1 OEM")));
-
- // this list comes from devices.xml bundled in the JAR
- // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
- assertEquals(
- "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
- "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
- "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
- "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet)]",
- listDisplayNames(dm.getDevices(DeviceFilter.DEFAULT)).toString());
- assertEquals("", log.toString());
-
- // this list comes from the nexus.xml bundled in the JAR
- // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
- assertEquals(
- "[Android TV (1080p), Android TV (720p), Android Wear Round, Android Wear Round Chin, "
- +
- "Android Wear Square, Galaxy Nexus, Nexus 10, Nexus 4, Nexus 5, Nexus 6, Nexus 7, "
- +
- "Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
- listDisplayNames(dm.getDevices(DeviceFilter.VENDOR)).toString());
- assertEquals("", log.toString());
-
- assertEquals(
- "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
- "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
- "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
- "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet), Android TV (1080p), "
- +
- "Android TV (720p), Android Wear Round, Android Wear Round Chin, " +
- "Android Wear Square, Galaxy Nexus, Mock Tag 1 Device Name, Nexus 10, Nexus 4, "
- +
- "Nexus 5, Nexus 6, Nexus 7, Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
- listDisplayNames(dm.getDevices(DeviceManager.ALL_DEVICES)).toString());
- assertEquals("", log.toString());
- }
-
- public final void testGetDeviceStatus() {
- // get a definition from the bundled devices.xml file
- assertEquals(DeviceStatus.EXISTS,
- dm.getDeviceStatus("7in WSVGA (Tablet)", "Generic"));
-
- // get a definition from the bundled oem file
- assertEquals(DeviceStatus.EXISTS,
- dm.getDeviceStatus("Nexus One", "Google"));
-
- // try a device that does not exist
- assertEquals(DeviceStatus.MISSING,
- dm.getDeviceStatus("My Device", "Custom OEM"));
- }
-
- public final void testHasHardwarePropHashChanged_Generic() {
- final Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
-
- assertEquals("MD5:750a657019b49e621c42ce9a20c2cc30",
- DeviceManager.hasHardwarePropHashChanged(
- d1,
- "invalid"));
-
- assertEquals(null,
- DeviceManager.hasHardwarePropHashChanged(d1, "MD5:750a657019b49e621c42ce9a20c2cc30"));
-
- // change the device hardware props, this should change the hash
- d1.getDefaultHardware().setNav(Navigation.TRACKBALL);
-
- assertEquals("MD5:9c4dd5018987da51f7166f139f4361a2",
- DeviceManager.hasHardwarePropHashChanged(d1, "MD5:750a657019b49e621c42ce9a20c2cc30"));
-
- // change the property back, should revert its hash to the previous one
- d1.getDefaultHardware().setNav(Navigation.NONAV);
-
- assertEquals(null,
- DeviceManager.hasHardwarePropHashChanged(d1, "MD5:750a657019b49e621c42ce9a20c2cc30"));
- }
-
- public final void testHasHardwarePropHashChanged_Oem() {
- final Device d2 = dm.getDevice("Nexus One", "Google");
-
- assertEquals("MD5:36362a51e6c830c2ab515a312c9ecbff",
- DeviceManager.hasHardwarePropHashChanged(
- d2,
- "invalid"));
-
- assertEquals(null,
- DeviceManager.hasHardwarePropHashChanged(
- d2,
- "MD5:36362a51e6c830c2ab515a312c9ecbff"));
-
- // change the device hardware props, this should change the hash
- d2.getDefaultHardware().setKeyboard(Keyboard.QWERTY);
-
- assertEquals("MD5:f8f4b390755f2f58dfeb7d3020cd87db",
- DeviceManager.hasHardwarePropHashChanged(
- d2,
- "MD5:36362a51e6c830c2ab515a312c9ecbff"));
-
- // change the property back, should revert its hash to the previous one
- d2.getDefaultHardware().setKeyboard(Keyboard.NOKEY);
-
- assertEquals(null,
- DeviceManager.hasHardwarePropHashChanged(
- d2,
- "MD5:36362a51e6c830c2ab515a312c9ecbff"));
- }
-
- public final void testDeviceOverrides() throws Exception {
- try {
- File location = getSdkManager().getLocalSdk().getLocation();
- SystemImage imageWithDevice = new SystemImage(
- new File(location, "system-images/android-22/android-wear/x86"),
- ISystemImage.LocationType.IN_SYSTEM_IMAGE,
- new IdDisplay("android-wear", "android-wear"), new IdDisplay("Google", "Google1"),
- "x86", new File[]{});
- DeviceManager manager = DeviceManager.createInstance(location, log);
- int count = manager.getDevices(EnumSet.allOf(DeviceFilter.class)).size();
- Device d = manager.getDevice("wear_round", "Google");
- assertEquals(d.getDisplayName(), "Android Wear Round");
-
- makeSystemImageFolder(imageWithDevice, "wear_round");
- manager = DeviceManager.createInstance(location, log);
-
- d = manager.getDevice("wear_round", "Google");
- assertEquals(d.getDisplayName(), "Mock Android wear Device Name");
-
- Device d1 = dm.getDevice("wear_round", "Google");
-
- Builder b = new Device.Builder(d1);
- b.setName("Custom");
-
- Device d2 = b.build();
-
- manager.addUserDevice(d2);
- manager.saveUserDevices();
-
- d = manager.getDevice("wear_round", "Google");
- assertEquals(d.getDisplayName(), "Custom");
- assertEquals(count, manager.getDevices(EnumSet.allOf(DeviceFilter.class)).size());
- }
- finally {
- super.setUp();
- }
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockAddonTarget.java b/base/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockAddonTarget.java
deleted file mode 100755
index d10bb4f..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockAddonTarget.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.androidTarget;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A mock AddonTarget.
- * This reimplements the minimum needed from the interface for our limited testing needs.
- */
-public class MockAddonTarget implements IAndroidTarget {
-
- private final IAndroidTarget mParentTarget;
- private final int mRevision;
- private final String mName;
- private ISystemImage[] mSystemImages;
- private ImmutableList<OptionalLibrary> mOptionalLibraries = ImmutableList.of();
-
- public MockAddonTarget(String name, IAndroidTarget parentTarget, int revision) {
- mName = name;
- mParentTarget = parentTarget;
- mRevision = revision;
- }
-
- @Override
- public String getClasspathName() {
- return getName();
- }
-
- @Override
- public String getShortClasspathName() {
- return getName();
- }
-
- @Override
- public File getDefaultSkin() {
- return null;
- }
-
- @Override
- public String getDescription() {
- return getName();
- }
-
- @Override
- public String getFullName() {
- return getName();
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- if (mSystemImages == null) {
- SystemImage si = new SystemImage(
- FileOp.append(getLocation(), SdkConstants.OS_IMAGES_FOLDER),
- LocationType.IN_LEGACY_FOLDER,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI,
- FileOp.EMPTY_FILE_ARRAY);
- mSystemImages = new SystemImage[] { si };
- }
- return mSystemImages;
- }
-
- @Override
- @Nullable
- public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
- if (SystemImage.DEFAULT_TAG.equals(tag) && SdkConstants.ABI_ARMEABI.equals(abiType)) {
- return getSystemImages()[0];
- }
- return null;
- }
-
- @Override
- public String getLocation() {
- return "/sdk/add-ons/addon-" + mName;
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getAdditionalLibraries() {
- return mOptionalLibraries;
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getOptionalLibraries() {
- return ImmutableList.of();
- }
-
- public void setOptionalLibraries(ImmutableList<OptionalLibrary> libraries) {
- mOptionalLibraries = libraries;
- }
-
- @Override
- public IAndroidTarget getParent() {
- return mParentTarget;
- }
-
- @Override
- public String getPath(int pathId) {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public File getFile(int pathId) {
- return new File(getPath(pathId));
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- return null;
- }
-
- @Override @NonNull
- public List<String> getBootClasspath() {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public String[] getPlatformLibraries() {
- return null;
- }
-
- @Override
- public String getProperty(String name) {
- return null;
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- return defaultValue;
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- return defaultValue;
- }
-
- @Override
- public Map<String, String> getProperties() {
- return null;
- }
-
- @Override
- public int getRevision() {
- return mRevision;
- }
-
- @NonNull
- @Override
- public File[] getSkins() {
- return FileOp.EMPTY_FILE_ARRAY;
- }
-
- @Override
- public int getUsbVendorId() {
- return 0;
- }
-
- @NonNull
- @Override
- public AndroidVersion getVersion() {
- return mParentTarget.getVersion();
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public String getVendor() {
- return mParentTarget.getVendor();
- }
-
- @Override
- public String getVersionName() {
- return String.format("mock-addon-%1$d", getVersion().getApiLevel());
- }
-
- @Override
- public String hashString() {
- return getVersionName();
- }
-
- /** Returns false for an addon. */
- @Override
- public boolean isPlatform() {
- return false;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public int compareTo(IAndroidTarget o) {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public boolean hasRenderingLibrary() {
- return false;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockPlatformTarget.java b/base/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockPlatformTarget.java
deleted file mode 100755
index 37cd184..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockPlatformTarget.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.androidTarget;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.descriptors.IdDisplay;
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A mock PlatformTarget.
- * This reimplements the minimum needed from the interface for our limited testing needs.
- */
-public class MockPlatformTarget implements IAndroidTarget {
-
- private final int mApiLevel;
- private final int mRevision;
- private ISystemImage[] mSystemImages;
-
- public MockPlatformTarget(int apiLevel, int revision) {
- mApiLevel = apiLevel;
- mRevision = revision;
- }
-
- @Override
- public String getClasspathName() {
- return getName();
- }
-
- @Override
- public String getShortClasspathName() {
- return getName();
- }
-
- @Override
- public File getDefaultSkin() {
- return null;
- }
-
- @Override
- public String getDescription() {
- return getName();
- }
-
- @Override
- public String getFullName() {
- return getName();
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- if (mSystemImages == null) {
- SystemImage si = new SystemImage(
- FileOp.append(getLocation(), SdkConstants.OS_IMAGES_FOLDER),
- LocationType.IN_LEGACY_FOLDER,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI,
- FileOp.EMPTY_FILE_ARRAY);
- mSystemImages = new SystemImage[] { si };
- }
- return mSystemImages;
- }
-
- @Override
- @Nullable
- public ISystemImage getSystemImage(@NonNull IdDisplay tag, @NonNull String abiType) {
- if (SystemImage.DEFAULT_TAG.equals(tag) && SdkConstants.ABI_ARMEABI.equals(abiType)) {
- return getSystemImages()[0];
- }
- return null;
- }
-
- @Override
- public String getLocation() {
- return "/sdk/platforms/android-" + getVersion().getApiString();
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getOptionalLibraries() {
- return ImmutableList.of();
- }
-
- @NonNull
- @Override
- public List<OptionalLibrary> getAdditionalLibraries() {
- return ImmutableList.of();
- }
-
- @Override
- public IAndroidTarget getParent() {
- return null;
- }
-
- @Override
- public String getPath(int pathId) {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public File getFile(int pathId) {
- return new File(getPath(pathId));
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- return null;
- }
-
- @Override @NonNull
- public List<String> getBootClasspath() {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public String[] getPlatformLibraries() {
- return null;
- }
-
- @Override
- public String getProperty(String name) {
- return null;
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- return defaultValue;
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- return defaultValue;
- }
-
- @Override
- public Map<String, String> getProperties() {
- return null;
- }
-
- @Override
- public int getRevision() {
- return mRevision;
- }
-
- @NonNull
- @Override
- public File[] getSkins() {
- return FileOp.EMPTY_FILE_ARRAY;
- }
-
- @Override
- public int getUsbVendorId() {
- return 0;
- }
-
- /**
- * Returns a vendor that depends on the parent *platform* API.
- * This works well in Unit Tests where we'll typically have different
- * platforms as unique identifiers.
- */
- @Override
- public String getVendor() {
- return "vendor " + Integer.toString(mApiLevel);
- }
-
- /**
- * Create a synthetic name using the target API level.
- */
- @Override
- public String getName() {
- return "platform r" + Integer.toString(mApiLevel);
- }
-
- @NonNull
- @Override
- public AndroidVersion getVersion() {
- return new AndroidVersion(mApiLevel, null /*codename*/);
- }
-
- @Override
- public String getVersionName() {
- return String.format("android-%1$d", mApiLevel);
- }
-
- @Override
- public String hashString() {
- return getVersionName();
- }
-
- /** Returns true for a platform. */
- @Override
- public boolean isPlatform() {
- return true;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public int compareTo(IAndroidTarget o) {
- throw new UnsupportedOperationException("Implement this as needed for tests");
- }
-
- @Override
- public boolean hasRenderingLibrary() {
- return false;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/AddonsListFetcherTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/AddonsListFetcherTest.java
deleted file mode 100755
index 3d77f01..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/AddonsListFetcherTest.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.internal.repository;
-
-import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
-import com.android.sdklib.repository.SdkAddonsListConstants;
-
-import org.w3c.dom.Document;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link AddonsListFetcher}
- */
-public class AddonsListFetcherTest extends TestCase {
-
- /**
- * An internal helper class to give us visibility to the protected members we want
- * to test.
- */
- private static class MockAddonsListFetcher extends AddonsListFetcher {
-
- public Site[] _parseAddonsList(Document doc,
- String nsUri,
- String baseUrl,
- ITaskMonitor monitor) {
- return super.parseAddonsList(doc, nsUri, baseUrl, monitor);
- }
-
- public int _getXmlSchemaVersion(InputStream xml) {
- return super.getXmlSchemaVersion(xml);
- }
-
- public String _validateXml(InputStream xml,
- String url,
- int version,
- String[] outError,
- Boolean[] validatorFound) {
- return super.validateXml(xml, url, version, outError, validatorFound);
- }
-
- public Document _getDocument(InputStream xml, ITaskMonitor monitor) {
- return super.getDocument(xml, monitor);
- }
-
- }
-
- private MockAddonsListFetcher mFetcher;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mFetcher = new MockAddonsListFetcher();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- mFetcher = null;
- }
-
- /**
- * Validate we can load a valid addon schema version 1
- */
- public void testLoadAddonsListXml_1() throws Exception {
- InputStream xmlStream =
- getTestResource("/com/android/sdklib/testdata/addons_list_sample_1.xml");
-
- // guess the version from the XML document
- int version = mFetcher._getXmlSchemaVersion(xmlStream);
- assertEquals(1, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://addons_list.xml";
-
- String uri = mFetcher._validateXml(
- xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonsListConstants.getSchemaUri(1), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mFetcher._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the sites
- Site[] result = mFetcher._parseAddonsList(doc, uri, "http://base/url/", monitor);
-
- assertEquals("", monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
- assertEquals("", monitor.getCapturedVerboseLog());
-
- // check the sites we found...
- // The XML file is UTF-8 so we support character sets (but the Java source file is
- // not, so we use the \\u notation to create the Unicode String)
- assertEquals(
- "[<ADDON_SITE URL='http://www.example.com/my_addons.xml' Name='My Example Add-ons.'>, " +
- "<ADDON_SITE URL='http://www.example.co.jp/addons.xml' Name='\u3042\u308A\u304C\u3068\u3046\u3054\u3056\u3044\u307E\u3059\u3002'>, " +
- "<ADDON_SITE URL='http://www.example.com/' Name='Example of directory URL.'>, " +
- "<ADDON_SITE URL='http://base/url/relative_url.xml' Name='Relative URL.'>]",
- Arrays.toString(result));
- assertEquals(4, result.length);
- }
-
- /**
- * Validate we can load a valid addon schema version 2
- */
- public void testLoadAddonsListXml_2() throws Exception {
- InputStream xmlStream =
- getTestResource("/com/android/sdklib/testdata/addons_list_sample_2.xml");
-
- // guess the version from the XML document
- int version = mFetcher._getXmlSchemaVersion(xmlStream);
- assertEquals(2, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://addons_list.xml";
-
- String uri = mFetcher._validateXml(
- xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonsListConstants.getSchemaUri(2), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mFetcher._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the sites
- Site[] result = mFetcher._parseAddonsList(doc, uri, "http://base/url/", monitor);
-
- assertEquals("", monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
- assertEquals("", monitor.getCapturedVerboseLog());
-
- // check the sites we found...
- // The XML file is UTF-8 so we support character sets (but the Java source file is
- // not, so we use the \\u notation to create the Unicode String)
- assertEquals(
- "[<ADDON_SITE URL='http://www.example.com/my_addons.xml' Name='My Example Add-ons.'>, " +
- "<ADDON_SITE URL='http://www.example.co.jp/addons.xml' Name='\u3042\u308A\u304C\u3068\u3046\u3054\u3056\u3044\u307E\u3059\u3002'>, " +
- "<ADDON_SITE URL='http://www.example.com/' Name='Example of directory URL.'>, " +
- "<SYS_IMG_SITE URL='http://www.example.com/' Name='Example of sys-img URL using the default xml filename.'>, " +
- "<SYS_IMG_SITE URL='http://www.example.com/specific_file.xml' Name='Example of sys-img URL using a specific xml filename.'>, " +
- "<ADDON_SITE URL='http://base/url/relative/url.xml' Name='Relative URL.'>]",
- Arrays.toString(result));
- assertEquals(6, result.length);
- }
-
- /**
- * Validate there isn't a next-version we haven't tested yet
- */
- public void testLoadAddonsListXml_3() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addons_list_sample_3.xml");
- assertNull("There is a sample for addons-list-3.xsd but there is not corresponding unit test", xmlStream);
- }
-
- // IMPORTANT: Each time you add a test here for a new version, you should
- // also add a test in ValidateAddonsListXmlTest.
-
- /**
- * Returns an SdkLib file resource as a {@link ByteArrayInputStream},
- * which has the advantage that we can use {@link InputStream#reset()} on it
- * at any time to read it multiple times.
- * <p/>
- * The default for getResourceAsStream() is to return a {@link FileInputStream} that
- * does not support reset(), yet we need it in the tested code.
- *
- * @throws IOException if some I/O read fails
- */
- private ByteArrayInputStream getTestResource(String filename) throws IOException {
- InputStream xmlStream = this.getClass().getResourceAsStream(filename);
- if (xmlStream == null) {
- return null;
- }
- try {
- byte[] data = new byte[8192];
- int offset = 0;
- int n;
-
- while ((n = xmlStream.read(data, offset, data.length - offset)) != -1) {
- offset += n;
-
- if (offset == data.length) {
- byte[] newData = new byte[offset + 8192];
- System.arraycopy(data, 0, newData, 0, offset);
- data = newData;
- }
- }
-
- return new ByteArrayInputStream(data, 0, offset);
- } finally {
- if (xmlStream != null) {
- xmlStream.close();
- }
- }
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java
deleted file mode 100755
index 43b5011..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.AndroidLocationTestCase;
-import com.android.sdklib.internal.repository.DownloadCache.Strategy;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.io.MockFileOp;
-import com.android.utils.Pair;
-import com.google.common.base.Charsets;
-
-import org.apache.http.Header;
-import org.apache.http.HttpResponse;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.StatusLine;
-import org.apache.http.message.BasicHttpResponse;
-import org.apache.http.message.BasicStatusLine;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-public class DownloadCacheTest extends AndroidLocationTestCase {
-
- private MockFileOp mFileOp;
- private MockMonitor mMonitor;
-
- /**
- * A private version of DownloadCache that never calls {@link UrlOpener}.
- */
- private static class NoDownloadCache extends DownloadCache {
-
- private final Map<String, Pair<InputStream, HttpResponse>> mReplies =
- new HashMap<String, Pair<InputStream,HttpResponse>>();
-
- public NoDownloadCache(@NonNull Strategy strategy) {
- super(strategy);
- }
-
- public NoDownloadCache(@NonNull IFileOp fileOp, @NonNull Strategy strategy) {
- super(fileOp, strategy);
- }
-
- @Override
- protected Pair<InputStream, HttpResponse> openUrl(
- @NonNull String url,
- boolean needsMarkResetSupport,
- @NonNull ITaskMonitor monitor,
- @Nullable Header[] headers) throws IOException, CanceledByUserException {
-
- Pair<InputStream, HttpResponse> reply = mReplies.get(url);
- if (reply != null) {
- return reply;
- }
-
- // http-client's behavior is to return a FNF instead of 404.
- throw new FileNotFoundException(url);
- }
-
- public void registerResponse(@NonNull String url, int httpCode, @Nullable String content) {
- InputStream is = null;
- if (content != null) {
- is = new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
- }
-
- ProtocolVersion p = new ProtocolVersion("HTTP", 1, 1);
- StatusLine statusLine = new BasicStatusLine(p, httpCode, "Code " + httpCode);
- HttpResponse httpResponse = new BasicHttpResponse(statusLine);
- Pair<InputStream, HttpResponse> reply = Pair.of(is, httpResponse);
-
- mReplies.put(url, reply);
- }
-
- }
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mFileOp = new MockFileOp();
- mMonitor = new MockMonitor();
- }
-
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
- public void testMissingResource() throws Exception {
- // Downloads must fail when using the only-cache strategy and there's nothing in the cache.
- // In that case, it returns null to indicate the resource is simply not found.
- // Since the mock implementation always returns a 404 and no stream, there is no
- // difference between the various cache strategies.
-
- mFileOp.reset();
- NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
- InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNull(is1);
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
- assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
-
- // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first
- mFileOp.reset();
- NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
-
- try {
- d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- fail("Expected: NoDownloadCache.openCachedUrl should have thrown a FileNotFoundException");
- } catch (FileNotFoundException e) {
- assertEquals("http://www.example.com/download1.xml", e.getMessage());
- }
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
-
- // Try again but this time we'll define a 404 reply to test the rest of the code path.
- mFileOp.reset();
- d2.registerResponse("http://www.example.com/download1.xml", 404, null);
- InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNull(is2);
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
-
- mFileOp.reset();
- NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
- d3.registerResponse("http://www.example.com/download1.xml", 404, null);
- InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNull(is3);
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
-
- mFileOp.reset();
- NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
- d4.registerResponse("http://www.example.com/download1.xml", 404, null);
- InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNull(is4);
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
- }
-
- public void testExistingResource() throws Exception {
- // The resource exists but only-cache doesn't hit the network so it will
- // fail when the resource is not cached.
- mFileOp.reset();
- NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
- d1.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
- InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNull(is1);
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
- assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
-
- // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first
- mFileOp.reset();
- NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
- d2.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
- InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNotNull(is2);
- assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
-
- mFileOp.reset();
- NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
- d3.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
- InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNotNull(is3);
- assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertEquals(
- "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>, " +
- "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
- "#<creation timestamp>\n" +
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n" +
- "'>]",
- sanitize(d3, Arrays.toString(mFileOp.getOutputStreams())));
-
- mFileOp.reset();
- NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
- d4.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
- InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- assertNotNull(is4);
- assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertEquals(
- "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>, " +
- "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
- "#<creation timestamp>\n" +
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n" +
- "'>]",
- sanitize(d4, Arrays.toString(mFileOp.getOutputStreams())));
- }
-
- public void testCachedResource() throws Exception {
- mFileOp.reset();
- NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
- d1.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d1.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
- 123456L,
- "This is the cached content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d1.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
- 123456L,
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n");
- InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- // Only-cache strategy returns the value from the cache, not the actual resource.
- assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is1, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
- // The cache hasn't been modified, only read
- assertEquals("[]", sanitize(d1, Arrays.toString(mFileOp.getOutputStreams())));
-
- // Direct ignores the cache.
- mFileOp.reset();
- NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
- d2.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d2.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
- 123456L,
- "This is the cached content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d2.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
- 123456L,
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n");
- InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- // Direct strategy ignores the cache.
- assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertTrue(mFileOp.hasRecordedExistingFolder(d2.getCacheRoot()));
- // Direct strategy doesn't update the cache.
- assertEquals("[]", sanitize(d2, Arrays.toString(mFileOp.getOutputStreams())));
-
- // Serve-cache reads from the cache if available, ignoring its freshness (here the timestamp
- // is way older than the 10-minute freshness encoded in the DownloadCache.)
- mFileOp.reset();
- NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
- d3.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d3.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
- 123456L,
- "This is the cached content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d3.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
- 123456L,
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n");
- InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- // We get content from the cache.
- assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertTrue(mFileOp.hasRecordedExistingFolder(d3.getCacheRoot()));
- // Cache isn't updated since nothing fresh was read.
- assertEquals("[]", sanitize(d3, Arrays.toString(mFileOp.getOutputStreams())));
-
- // fresh-cache reads the cache, finds it stale (here the timestamp
- // is way older than the 10-minute freshness encoded in the DownloadCache)
- // and will fetch the new resource instead and update the cache.
- mFileOp.reset();
- NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
- d4.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d4.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
- 123456L,
- "This is the cached content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d4.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
- 123456L,
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n");
- InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- // Cache is discarded, actual resource is returned.
- assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertTrue(mFileOp.hasRecordedExistingFolder(d4.getCacheRoot()));
- // Cache isn updated since something fresh was read.
- assertEquals(
- "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'This is the new content'>, " +
- "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
- "#<creation timestamp>\n" +
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n" +
- "'>]",
- sanitize(d4, Arrays.toString(mFileOp.getOutputStreams())));
-
- // fresh-cache reads the cache, finds it still valid stale (less than 10-minute old),
- // and uses the cached resource.
- mFileOp.reset();
- NoDownloadCache d5 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
- d5.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d5.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
- System.currentTimeMillis() - 1000,
- "This is the cached content");
- mFileOp.recordExistingFile(
- mFileOp.getAgnosticAbsPath(FileOp.append(d5.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
- System.currentTimeMillis() - 1000,
- "URL=http\\://www.example.com/download1.xml\n" +
- "Status-Code=200\n");
- InputStream is5 = d5.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
- // Cache is used.
- assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is5, Charsets.UTF_8)).readLine());
- assertEquals("", mMonitor.getAllCapturedLogs());
- assertTrue(mFileOp.hasRecordedExistingFolder(d5.getCacheRoot()));
- // Cache isn't updated since nothing fresh was read.
- assertEquals("[]", sanitize(d5, Arrays.toString(mFileOp.getOutputStreams())));
- }
-
- // --------
-
- @Nullable
- private String sanitize(@NonNull DownloadCache dc, @Nullable String msg) {
- if (msg != null) {
- msg = msg.replace("\r\n", "\n");
-
- String absRoot = mFileOp.getAgnosticAbsPath(dc.getCacheRoot());
- msg = msg.replace(absRoot, "$CACHE");
-
- // Cached files also contain a creation timestamp which we need to find and remove.
- msg = msg.replaceAll("\n#[A-Z][A-Za-z0-9: ]+20[0-9]{2}\n", "\n#<creation timestamp>\n");
- }
- return msg;
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/LocalSdkParserTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/LocalSdkParserTest.java
deleted file mode 100755
index 672d69d..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/LocalSdkParserTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository;
-
-import com.android.SdkConstants;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.SdkManagerTestCase;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.internal.androidTarget.PlatformTarget;
-import com.android.sdklib.internal.repository.archives.ArchFilter;
-import com.android.sdklib.internal.repository.archives.HostOs;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.io.FileOp;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.regex.Pattern;
-
-public class LocalSdkParserTest extends SdkManagerTestCase {
-
- private SdkManager mSdkMan;
- private LocalSdkParser mParser;
- private MockMonitor mMonitor;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- mSdkMan = getSdkManager();
- mParser = new LocalSdkParser();
- mMonitor = new MockMonitor();
- }
-
- public void testLocalSdkParser_SystemImages() throws Exception {
- // By default SdkManagerTestCase creates an SDK with one platform containing
- // a legacy armeabi system image (this is not a separate system image package)
-
- assertEquals(
- "[Android SDK Tools, revision 1.0.1, " +
- "Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3, " +
- "SDK Platform Android 0.0, API 0, revision 1, " +
- "Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor))));
-
- assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1, " +
- "Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_PLATFORMS | LocalSdkParser.PARSE_SOURCES,
- mMonitor))));
-
- assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1]",
- Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_PLATFORMS,
- mMonitor)));
-
- assertEquals(
- "[Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_SOURCES,
- mMonitor)));
-
- assertEquals(
- "[Android SDK Tools, revision 1.0.1]",
- Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_TOOLS,
- mMonitor)));
-
- assertEquals(
- "[Android SDK Platform-tools, revision 17.1.2]",
- Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_PLATFORM_TOOLS,
- mMonitor)));
-
- assertEquals(
- "[Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_BUILD_TOOLS,
- mMonitor))));
-
- // Now add a few "platform subfolders" system images and reload the SDK.
- // This disables the "legacy" mode but it still doesn't create any system image package
-
- IAndroidTarget t = mSdkMan.getTargets()[0];
- makeSystemImageFolder(new SystemImage(mSdkMan, t,
- LocationType.IN_IMAGES_SUBFOLDER,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI_V7A,
- FileOp.EMPTY_FILE_ARRAY), null);
- makeSystemImageFolder(new SystemImage(mSdkMan, t,
- LocationType.IN_IMAGES_SUBFOLDER,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_INTEL_ATOM,
- FileOp.EMPTY_FILE_ARRAY), null);
-
- mSdkMan.reloadSdk(getLog());
- t = mSdkMan.getTargets()[0];
-
- assertEquals(
- "[Android SDK Tools, revision 1.0.1, " +
- "Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3, " +
- "SDK Platform Android 0.0, API 0, revision 1, " +
- "Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor))));
-
- // Now add arm + arm v7a images using the new SDK/system-images.
- // The local parser will find the 2 system image packages which are associated
- // with the PlatformTarget in the SdkManager.
-
- makeSystemImageFolder(new SystemImage(mSdkMan, t,
- LocationType.IN_SYSTEM_IMAGE,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI,
- FileOp.EMPTY_FILE_ARRAY), null);
- makeSystemImageFolder(new SystemImage(mSdkMan, t,
- LocationType.IN_SYSTEM_IMAGE,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_ARMEABI_V7A,
- FileOp.EMPTY_FILE_ARRAY), null);
-
- mSdkMan.reloadSdk(getLog());
-
- assertEquals(
- "[Android SDK Tools, revision 1.0.1, " +
- "Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3, " +
- "SDK Platform Android 0.0, API 0, revision 1, " +
- "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
- "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
- "Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor))));
-
- // Now add an x86 image using the new SDK/system-images.
- // Now this time we do NOT reload the SdkManager instance. Instead the parser
- // will find an unused system image and load it as a "broken package".
-
- makeSystemImageFolder(new SystemImage(mSdkMan, t,
- LocationType.IN_SYSTEM_IMAGE,
- SystemImage.DEFAULT_TAG,
- SdkConstants.ABI_INTEL_ATOM,
- FileOp.EMPTY_FILE_ARRAY), null);
-
- assertEquals(
- "[Android SDK Tools, revision 1.0.1, " +
- "Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3, " +
- "SDK Platform Android 0.0, API 0, revision 1, " +
- "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
- "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
- "Sources for Android SDK, API 0, revision 0, " +
- "Broken Intel x86 Atom System Image, API 0]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(), mSdkMan, mMonitor))));
-
- assertEquals(
- "[Android SDK Tools, revision 1.0.1, " +
- "Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3, " +
- "SDK Platform Android 0.0, API 0, revision 1, " +
- "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
- "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
- "Sources for Android SDK, API 0, revision 0, " +
- "Broken Intel x86 Atom System Image, API 0]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_ALL,
- mMonitor))));
-
- assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1, " +
- "Sys-Img v0 for (Default, armeabi-v7a), Android API 0, revision 0, " +
- "Sys-Img v0 for (Default, armeabi), Android API 0, revision 0, " +
- "Sources for Android SDK, API 0, revision 0, " +
- "Broken Intel x86 Atom System Image, API 0]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_PLATFORMS | // platform also loads system-images
- LocalSdkParser.PARSE_SOURCES,
- mMonitor))));
-
- assertEquals(
- "[Sources for Android SDK, API 0, revision 0]",
- Arrays.toString(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_SOURCES,
- mMonitor)));
- }
-
- public void testLocalSdkParser_Platform_DefaultSkin() throws Exception {
- IAndroidTarget[] targets = mSdkMan.getTargets();
-
- assertEquals(
- "[PlatformTarget API 0 rev 1]",
- Arrays.toString(targets));
-
- PlatformTarget p = (PlatformTarget) targets[0];
-
- assertEquals("[SDK/platforms/v0_0/skins/HVGA]",
- sanitizeInput(p.getSkins()));
-
- assertEquals("[SystemImage tag=default, ABI=armeabi, location in legacy folder='SDK/platforms/v0_0/images']",
- sanitizeInput(p.getSystemImages()));
-
- // the in-platform system image has no skins
- assertEquals("[]",
- sanitizeInput(p.getSystemImages()[0].getSkins()));
- }
-
- public void testLocalSdkParser_Platform_CustomSkin() throws Exception {
- // add a new-style system-image with a tag and an embedded custom skin
- File siArm = makeSystemImageFolder(TARGET_DIR_NAME_0, "tag-1", "x86");
- makeFakeSkin(siArm, "Tag1ArmSkin");
-
- IAndroidTarget[] targets = mSdkMan.getTargets();
-
- assertEquals(
- "[PlatformTarget API 0 rev 1]",
- Arrays.toString(targets));
-
- PlatformTarget p = (PlatformTarget) targets[0];
-
- assertEquals(
- "[SDK/platforms/v0_0/skins/HVGA, " +
- "SDK/system-images/v0_0/tag-1/x86/skins/Tag1ArmSkin]",
- sanitizeInput(p.getSkins()));
-
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in legacy folder='SDK/platforms/v0_0/images', " +
- "SystemImage tag=tag-1, ABI=x86, location in system image='SDK/system-images/v0_0/tag-1/x86']",
- sanitizeInput(p.getSystemImages()));
-
- // the in-platform system image has no skins, the second one has a custom skin
- assertEquals("[]",
- sanitizeInput(p.getSystemImages()[0].getSkins()));
- assertEquals("[SDK/system-images/v0_0/tag-1/x86/skins/Tag1ArmSkin]",
- sanitizeInput(p.getSystemImages()[1].getSkins()));
- }
-
- public void testLocalSdkParser_BuildTools_InvalidOs() throws Exception {
- assertEquals(
- "[Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3, " +
- "Platform Tools, revision 17.1.2, Tools, revision 1.0.1]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_BUILD_TOOLS |
- LocalSdkParser.PARSE_EXTRAS,
- mMonitor))));
-
- // We have many OS possible. Choose 2 that do not match the current platform.
- ArchFilter current = ArchFilter.getCurrent();
- HostOs others[] = new HostOs[2];
- int i = 0;
- for (HostOs o : HostOs.values()) {
- if (o != current.getHostOS() && i < others.length) {
- others[i++] = o;
- }
- }
- createFakeBuildTools(new File(mSdkMan.getLocation()), others[0].toString(), "5.0.1");
- createFakeBuildTools(new File(mSdkMan.getLocation()), others[1].toString(), "5.0.2");
-
- assertEquals(
- "[Android SDK Build-tools, revision 18.3.4 rc5, " +
- "Android SDK Build-tools, revision 3.0.1, " +
- "Android SDK Build-tools, revision 3, " +
- "Broken Build-Tools Package, revision 5.0.2, " +
- "Broken Build-Tools Package, revision 5.0.1, " +
- "Platform Tools, revision 17.1.2, Tools, revision 1.0.1]",
- Arrays.toString(sort(mParser.parseSdk(mSdkMan.getLocation(),
- mSdkMan,
- LocalSdkParser.PARSE_BUILD_TOOLS |
- LocalSdkParser.PARSE_EXTRAS,
- mMonitor))));
- }
-
- private static Package[] sort(Package[] pkg) {
- // Sort packages to ensure stable unit test output
- pkg = Arrays.copyOf(pkg, pkg.length);
- Arrays.sort(pkg);
- return pkg;
- }
-
- private String sanitizeInput(Object[] array) {
- String input = Arrays.toString(array);
- String sdkPath = mSdkMan.getLocation();
- input = input.replaceAll(Pattern.quote(sdkPath), "SDK");
- input = input.replace(File.separatorChar, '/');
- return input;
- }
-}
-
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockDownloadCache.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockDownloadCache.java
deleted file mode 100755
index eb832f2..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockDownloadCache.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.utils.Pair;
-
-import org.apache.http.Header;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.message.BasicHttpResponse;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-/** A mock UpdaterData that simply records what would have been installed. */
-public class MockDownloadCache extends DownloadCache {
-
- private final File mCacheRoot;
-
- /** Map url => payload bytes, http code response.
- * If the payload pair is null, an exception such as FNF is thrown. */
- private final Map<String, Payload> mDirectPayloads = new HashMap<String, Payload>();
- /** Map url => payload bytes, http code response.
- * If the payload pair is null, an exception such as FNF is thrown. */
- private final Map<String, Payload> mCachedPayloads = new HashMap<String, Payload>();
-
- private final Map<String, Integer> mDirectHits = new TreeMap<String, Integer>();
- private final Map<String, Integer> mCachedHits = new TreeMap<String, Integer>();
-
- private Strategy mOverrideStrategy;
-
- public static final int THROW_FNF = -1;
-
- /**
- * Creates a download cache with a {@code DIRECT} strategy and
- * no root {@code $HOME/.android} folder, which effectively disables the cache.
- */
- public MockDownloadCache() {
- super(DownloadCache.Strategy.DIRECT);
- mCacheRoot = null;
- }
-
- /**
- * Creates a download with the given strategy and the given cache root.
- */
- public MockDownloadCache(DownloadCache.Strategy strategy, File cacheRoot) {
- super(strategy);
- mCacheRoot = cacheRoot;
- }
-
- @Override
- protected File initCacheRoot() {
- return mCacheRoot;
- }
-
- /**
- * Override the {@link DownloadCache.Strategy} of the cache.
- * This lets us set it temporarily to {@link DownloadCache.Strategy#ONLY_CACHE},
- * which will force {@link #openCachedUrl(String, ITaskMonitor)} to throw an FNF,
- * essentially simulating an empty cache at first.
- * <p/>
- * Setting it back to null reverts the behavior to its default.
- */
- public void overrideStrategy(DownloadCache.Strategy strategy) {
- mOverrideStrategy = strategy;
- }
-
- /**
- * Register a direct payload response.
- *
- * @param url The URL to match.
- * @param httpCode The expected response code.
- * Use {@link #THROW_FNF} to mean an FNF should be thrown (which is what the
- * httpClient stack seems to return instead of {@link HttpStatus#SC_NOT_FOUND}.)
- * @param content The payload to return.
- * As a shortcut a null will be replaced by an empty byte array.
- */
- public void registerDirectPayload(String url, int httpCode, byte[] content) {
- mDirectPayloads.put(url, new Payload(httpCode, content));
- }
-
- /**
- * Register a cached payload response.
- *
- * @param url The URL to match.
- * @param content The payload to return or null to throw a FNF.
- */
- public void registerCachedPayload(String url, byte[] content) {
- mCachedPayloads.put(url,
- new Payload(content == null ? THROW_FNF : HttpStatus.SC_OK, content));
- }
-
- public String[] getDirectHits() {
- ArrayList<String> list = new ArrayList<String>();
- synchronized (mDirectHits) {
- for (Entry<String, Integer> entry : mDirectHits.entrySet()) {
- list.add(String.format("<%1$s : %2$d>",
- entry.getKey(), entry.getValue().intValue()));
- }
- }
- return list.toArray(new String[list.size()]);
- }
-
- public String[] getCachedHits() {
- ArrayList<String> list = new ArrayList<String>();
- synchronized (mCachedHits) {
- for (Entry<String, Integer> entry : mCachedHits.entrySet()) {
- list.add(String.format("<%1$s : %2$d>",
- entry.getKey(), entry.getValue().intValue()));
- }
- }
- return list.toArray(new String[list.size()]);
- }
-
- public void clearDirectHits() {
- synchronized (mDirectHits) {
- mDirectHits.clear();
- }
- }
-
- public void clearCachedHits() {
- synchronized (mCachedHits) {
- mCachedHits.clear();
- }
- }
-
- /**
- * Override openDirectUrl to return one of the registered payloads or throw a FNF exception.
- * This totally ignores the cache's {@link DownloadCache.Strategy}.
- */
- @Override
- public Pair<InputStream, HttpResponse> openDirectUrl(
- @NonNull String urlString,
- @Nullable Header[] headers,
- @NonNull ITaskMonitor monitor) throws IOException, CanceledByUserException {
-
- synchronized (mDirectHits) {
- Integer count = mDirectHits.get(urlString);
- mDirectHits.put(urlString, (count == null ? 0 : count.intValue()) + 1);
- }
-
- Payload payload = mDirectPayloads.get(urlString);
-
- if (payload == null || payload.mHttpCode == THROW_FNF) {
- throw new FileNotFoundException(urlString);
- }
-
- byte[] content = payload.mContent;
- if (content == null) {
- content = new byte[0];
- }
-
- InputStream is = new ByteArrayInputStream(content);
- HttpResponse hr = new BasicHttpResponse(
- new ProtocolVersion("HTTP", 1, 1),
- payload.mHttpCode,
- "Http-Code-" + payload.mHttpCode);
-
- return Pair.of(is, hr);
- }
-
- /**
- * Override openCachedUrl to return one of the registered payloads or throw a FNF exception.
- * This totally ignores the cache's {@link DownloadCache.Strategy}.
- * It will however throw a FNF if {@link #overrideStrategy(Strategy)} is set to
- * {@link DownloadCache.Strategy#ONLY_CACHE}.
- */
- @Override
- public InputStream openCachedUrl(String urlString, ITaskMonitor monitor)
- throws IOException, CanceledByUserException {
-
- synchronized (mCachedHits) {
- Integer count = mCachedHits.get(urlString);
- mCachedHits.put(urlString, (count == null ? 0 : count.intValue()) + 1);
- }
-
- if (Strategy.ONLY_CACHE.equals(mOverrideStrategy)) {
- // Override the cache to read only "local cached" data.
- // In this first phase, we assume there's nothing cached.
- // TODO register first-pass files later.
- throw new FileNotFoundException(urlString);
- }
-
- Payload payload = mCachedPayloads.get(urlString);
-
- if (payload == null || payload.mHttpCode != HttpStatus.SC_OK) {
- throw new FileNotFoundException(urlString);
- }
-
- byte[] content = payload.mContent;
- if (content == null) {
- content = new byte[0];
- }
-
- return new ByteArrayInputStream(content);
- }
-
- private static class Payload {
- final byte[] mContent;
- final int mHttpCode;
-
- Payload(int httpCode, byte[] content) {
- mHttpCode = httpCode;
- mContent = content;
- }
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockEmptySdkManager.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockEmptySdkManager.java
deleted file mode 100755
index 4a45a38..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockEmptySdkManager.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.internal.repository;
-
-import com.android.sdklib.SdkManager;
-
-/**
- * An invalid SDK Manager, just good enough to avoid passing a null reference.
- */
-public class MockEmptySdkManager extends SdkManager {
- public MockEmptySdkManager(String osSdkPath) {
- super(osSdkPath);
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/SdkStatsTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/SdkStatsTest.java
deleted file mode 100755
index fcefaa4..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/SdkStatsTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.internal.repository;
-
-import com.android.sdklib.internal.repository.SdkStats.PlatformStat;
-import com.android.sdklib.repository.SdkStatsConstants;
-import com.android.utils.SparseArray;
-
-import org.w3c.dom.Document;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link SdkStats}
- */
-public class SdkStatsTest extends TestCase {
-
- /**
- * An internal helper class to give us visibility to the protected members we want
- * to test.
- */
- private static class MockSdkStats extends SdkStats {
-
- public SparseArray<PlatformStat> _parseStatsDocument(Document doc,
- String nsUri,
- ITaskMonitor monitor) {
- return super.parseStatsDocument(doc, nsUri, monitor);
- }
-
- public int _getXmlSchemaVersion(InputStream xml) {
- return super.getXmlSchemaVersion(xml);
- }
-
- public String _validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
- return super.validateXml(xml, url, version, outError, validatorFound);
- }
-
- public Document _getDocument(InputStream xml, ITaskMonitor monitor) {
- return super.getDocument(xml, monitor);
- }
-
- }
-
- private MockSdkStats mStats;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mStats = new MockSdkStats();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- mStats = null;
- }
-
- /**
- * Validate we can still load a valid addon schema version 1
- */
- public void testLoadSample_1() throws Exception {
- InputStream xmlStream =
- getTestResource("/com/android/sdklib/testdata/stats_sample_1.xml");
-
- // guess the version from the XML document
- int version = mStats._getXmlSchemaVersion(xmlStream);
- assertEquals(1, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://stats.xml";
-
- String uri = mStats._validateXml(
- xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkStatsConstants.getSchemaUri(1), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mStats._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the sites
- SparseArray<PlatformStat> result = mStats._parseStatsDocument(doc, uri, monitor);
-
- assertEquals("", monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
- assertEquals("", monitor.getCapturedVerboseLog());
-
- // check what we found
- assertEquals(3, result.size());
- int len = result.size();
-
- int[] keys = new int[len];
- PlatformStat[] stats = new PlatformStat[len];
- for (int i = 0; i < len; i++) {
- keys[i] = result.keyAt(i);
- stats[i] = result.valueAt(i);
- }
-
- assertEquals(
- "[3, 5, 42]",
- Arrays.toString(keys));
-
- assertEquals(
- "[<Stat api=3, code=Vanilla, vers=Android 0.5, share=0.1%, accum=100.0%>, " +
- "<Stat api=5, code=Coffee, vers=Android 42.0, share=25.8%, accum=99.9%>, " +
- "<Stat api=42, code=Chocolate, vers=Android 32.64, share=74.1%, accum=74.1%>]",
- Arrays.toString(stats));
- }
-
- /**
- * Returns an SdkLib file resource as a {@link ByteArrayInputStream},
- * which has the advantage that we can use {@link InputStream#reset()} on it
- * at any time to read it multiple times.
- * <p/>
- * The default for getResourceAsStream() is to return a {@link FileInputStream} that
- * does not support reset(), yet we need it in the tested code.
- *
- * @throws IOException if some I/O read fails
- */
- private ByteArrayInputStream getTestResource(String filename) throws IOException {
- InputStream xmlStream = this.getClass().getResourceAsStream(filename);
-
- try {
- byte[] data = new byte[8192];
- int offset = 0;
- int n;
-
- while ((n = xmlStream.read(data, offset, data.length - offset)) != -1) {
- offset += n;
-
- if (offset == data.length) {
- byte[] newData = new byte[offset + 8192];
- System.arraycopy(data, 0, newData, 0, offset);
- data = newData;
- }
- }
-
- return new ByteArrayInputStream(data, 0, offset);
- } finally {
- if (xmlStream != null) {
- xmlStream.close();
- }
- }
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchFilterTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchFilterTest.java
deleted file mode 100755
index eaa6802..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchFilterTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-
-import junit.framework.TestCase;
-
-public class ArchFilterTest extends TestCase {
-
- public void testGetCurrent() {
- ArchFilter f1 = makeCurrent("Windows 7", "amd64", "1.7.0_51");
- assertEquals(HostOs.WINDOWS, f1.getHostOS());
- assertEquals(BitSize._64, f1.getHostBits());
- assertEquals(BitSize._64, f1.getJvmBits());
- assertEquals(new FullRevision(1, 7, 0), f1.getMinJvmVersion());
-
- ArchFilter f2 = makeCurrent("Mac OS X", "x86_64", "1.7.0_51");
- assertEquals(HostOs.MACOSX, f2.getHostOS());
- assertEquals(BitSize._64, f2.getHostBits());
- assertEquals(BitSize._64, f2.getJvmBits());
- assertEquals(new FullRevision(1, 7, 0), f2.getMinJvmVersion());
-
- ArchFilter f3 = makeCurrent("Linux", "x86", "1.6.42_43");
- assertEquals(HostOs.LINUX, f3.getHostOS());
- assertEquals(BitSize._32, f3.getHostBits());
- assertEquals(BitSize._32, f3.getJvmBits());
- assertEquals(new FullRevision(1, 6, 42), f3.getMinJvmVersion());
- }
-
- public void testIsCompatibleWith() {
- ArchFilter f1 = makeCurrent("Windows 7", "amd64", "1.7.0_51");
-
- assertTrue(new ArchFilter(null, null, null, null).isCompatibleWith(f1));
-
- assertTrue (new ArchFilter(HostOs.WINDOWS, null, null, null).isCompatibleWith(f1));
- assertFalse(new ArchFilter(HostOs.MACOSX , null, null, null).isCompatibleWith(f1));
- assertFalse(new ArchFilter(HostOs.LINUX , null, null, null).isCompatibleWith(f1));
-
- assertTrue (new ArchFilter(null, BitSize._64, null, null).isCompatibleWith(f1));
- assertFalse(new ArchFilter(null, BitSize._32, null, null).isCompatibleWith(f1));
-
- assertTrue (new ArchFilter(null, null, BitSize._64, null).isCompatibleWith(f1));
- assertFalse(new ArchFilter(null, null, BitSize._32, null).isCompatibleWith(f1));
-
- assertTrue (new ArchFilter(null, null, null, new NoPreviewRevision(1, 6, 42)).isCompatibleWith(f1));
- assertTrue (new ArchFilter(null, null, null, new NoPreviewRevision(1, 7, 0)).isCompatibleWith(f1));
- assertFalse(new ArchFilter(null, null, null, new NoPreviewRevision(1, 7, 1)).isCompatibleWith(f1));
- assertFalse(new ArchFilter(null, null, null, new NoPreviewRevision(1, 8, 0)).isCompatibleWith(f1));
- assertFalse(new ArchFilter(null, null, null, new NoPreviewRevision(2, 0, 0)).isCompatibleWith(f1));
- }
-
- // ---- helpers ---
-
- /**
- * {@link ArchFilter#getCurrent()} uses java system properties to find the
- * current architecture attributes. This method temporarily overrides
- * System properties, calls {@link ArchFilter#getCurrent()} and then reset
- * the properties.
- *
- * @param osName The override value for the System "os.name" property.
- * @param osArch The override value for the System "os.arch" property.
- * @param javaVersion The override value for the System "java.version" property.
- * @return A new {@link ArchFilter}
- */
- @NonNull
- private ArchFilter makeCurrent(@NonNull String osName,
- @NonNull String osArch,
- @NonNull String javaVersion) {
- String oldOsName = System.getProperty("os.name");
- String oldOsArch = System.getProperty("os.arch");
- String oldJavaVers = System.getProperty("java.version");
- try {
- System.setProperty("os.name", osName);
- System.setProperty("os.arch", osArch);
- System.setProperty("java.version", javaVersion);
-
- return ArchFilter.getCurrent();
- } finally {
- System.setProperty("os.name", oldOsName);
- System.setProperty("os.arch", oldOsArch);
- System.setProperty("java.version", oldJavaVers);
- }
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
deleted file mode 100755
index 25e08b5..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.MockEmptySdkManager;
-import com.android.sdklib.internal.repository.MockMonitor;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.MockEmptyPackage;
-import com.android.sdklib.internal.repository.packages.MockExtraPackage;
-import com.android.sdklib.internal.repository.sources.SdkRepoSource;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.io.MockFileOp;
-import com.android.sdklib.repository.PkgProps;
-import com.android.utils.Pair;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.regex.Pattern;
-
-import junit.framework.TestCase;
-
-/**
- * Unit tests for {@link ArchiveInstaller}.
- */
-public class ArchiveInstallerTest extends TestCase {
-
- private MockMonitor mMon;
- private String mSdkRoot;
- private MockFileOp mFile;
- private MockArchiveInstaller mArchInst;
- private MockEmptySdkManager mSdkMan;
-
- private class MockArchiveInstaller extends ArchiveInstaller {
-
- private Map<Archive, File> mDownloadMap = new HashMap<Archive, File>();
-
- public MockArchiveInstaller(IFileOp fileUtils) {
- super(fileUtils);
- }
-
- public void setDownloadResponse(Archive archive, File response) {
- mDownloadMap.put(archive, response);
- }
-
- @Override
- protected Pair<File, File> downloadFile(
- Archive archive,
- String osSdkRoot,
- DownloadCache cache,
- ITaskMonitor monitor,
- boolean forceHttp) {
- File file = mDownloadMap.get(archive);
- // register the file as "created"
- ArchiveInstallerTest.this.mFile.recordExistingFile(file);
- return Pair.of(file, null);
- }
-
- @Override
- protected boolean unzipFolder(
- ArchiveReplacement archiveInfo,
- File archiveFile,
- File unzipDestFolder,
- ITaskMonitor monitor) {
- // Claim the unzip works if the input archiveFile is one we know about
- // and the destination actually exists.
-
- if (getFileOp().isDirectory(unzipDestFolder) &&
- mDownloadMap.values().contains(archiveFile)) {
- return true;
- }
-
- return false;
- }
-
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mFile = new MockFileOp();
- mArchInst = new MockArchiveInstaller(mFile);
- mSdkRoot = "/sdk";
- mSdkMan = new MockEmptySdkManager(mSdkRoot);
- mMon = new MockMonitor();
- }
-
- // ----
-
- /** Test we don't try to install a local archive. */
- public void testInstall_SkipLocalArchive() throws Exception {
- MockEmptyPackage p = new MockEmptyPackage("testPkg");
- ArchiveReplacement ar = new ArchiveReplacement(p.getArchives()[0], null /*replaced*/);
-
- assertFalse(mArchInst.install(ar, mSdkRoot, false /*forceHttp*/, mSdkMan,
- null /*UrlCache*/, mMon));
- assertTrue(mMon.getCapturedLog().indexOf("Skipping already installed archive") != -1);
- }
-
- /** Test we can install a simple new archive. */
- public void testInstall_NewArchive() throws Exception {
- SdkSource src1 = new SdkRepoSource("http://repo.example.com/url", "repo1");
- MockEmptyPackage p = createRemoteEmptyPackage(src1, "testPkg");
- ArchiveReplacement ar = new ArchiveReplacement(p.getArchives()[0], null /*replaced*/);
-
- // associate the File that will be "downloaded" for this archive
- mArchInst.setDownloadResponse(
- p.getArchives()[0], createFile("/sdk", "tmp", "download1.zip"));
-
- assertTrue(mArchInst.install(ar, mSdkRoot, false /*forceHttp*/, mSdkMan,
- null /*UrlCache*/, mMon));
-
- // check what was created
- assertEquals(
- "[/sdk/mock/testPkg/source.properties]",
- Arrays.toString(mFile.getExistingFiles()));
-
- assertEquals(
- "[/, /sdk, /sdk/mock, /sdk/mock/testPkg]",
- Arrays.toString(mFile.getExistingFolders()));
-
- assertEquals(
- "[</sdk/mock/testPkg/source.properties: '### Android Tool: Source of this archive.\n" +
- "#...date...\n" +
- "Pkg.Revision=0\n" +
- "Pkg.SourceUrl=http\\://repo.example.com/url\n" +
- "'>]",
- stripDate(Arrays.toString(mFile.getOutputStreams())));
-
- assertEquals(
- "Installing 'testPkg'\n" +
- "Installed 'testPkg'\n",
- mMon.getCapturedLog());
- }
-
- /** Test we can replace and rename an Extra package. */
- public void testInstall_InstallExtraArchive() throws Exception {
- SdkSource src1 = new SdkRepoSource("http://repo.example.com/url", "repo1");
-
- MockExtraPackage newPkg = createRemoteExtraPackage(src1, "vendor1", "oldPath", 2, 1);
- MockExtraPackage oldPkg = new MockExtraPackage(src1, "vendor1", "oldPath", 1, 1);
-
- // old pkg is installed, so its directory & files exists
- mFile.recordExistingFile("/sdk/extras/vendor1/oldPath/source.properties");
- mFile.recordExistingFolder("/sdk/extras/vendor1/oldPath");
-
- ArchiveReplacement ar = new ArchiveReplacement(
- newPkg.getArchives()[0],
- oldPkg.getArchives()[0]);
-
- // associate the File that will be "downloaded" for this archive
- mArchInst.setDownloadResponse(
- newPkg.getArchives()[0], createFile("/sdk", "tmp", "download1.zip"));
-
- assertTrue(mArchInst.install(ar, mSdkRoot, false /*forceHttp*/, mSdkMan,
- null /*UrlCache*/, mMon));
-
- // check what was created
- assertEquals(
- "[/sdk/extras/vendor1/oldPath/source.properties]",
- Arrays.toString(mFile.getExistingFiles()));
-
- // This created the /sdk/temp folder to put the oldPath package whilst we unzipped
- // the new one. The oldPath dir was then cleaned up but we still leave the root
- // temp dir around.
- assertEquals(
- "[/, /sdk, /sdk/extras, /sdk/extras/vendor1, /sdk/extras/vendor1/oldPath, /sdk/temp]",
- Arrays.toString(mFile.getExistingFolders()));
-
- assertEquals(
- (
- "[</sdk/extras/vendor1/oldPath/source.properties: " +
- "'### Android Tool: Source of this archive.\n" +
- "#...date...\n" +
- "Extra.NameDisplay=Vendor1 OldPath\n" +
- "Extra.Path=oldPath\n" +
- "Extra.VendorDisplay=vendor1\n" +
- "Extra.VendorId=vendor1\n" +
- "Pkg.Desc=desc\n" +
- "Pkg.DescUrl=url\n" +
- "Pkg.Revision=2.0.0\n" +
- "Pkg.SourceUrl=http\\://repo.example.com/url\n" +
- "'>]"),
- stripDate(Arrays.toString(mFile.getOutputStreams())));
-
- assertEquals(
- "Installing Vendor1 OldPath, revision 2\n" +
- "Installed Vendor1 OldPath, revision 2\n",
- mMon.getCapturedLog());
- }
-
- /** Test we can replace and rename an Extra package. */
- public void testInstall_InstallRenamedExtraArchive() throws Exception {
- SdkSource src1 = new SdkRepoSource("http://repo.example.com/url", "repo1");
-
- MockExtraPackage newPkg = createRemoteExtraPackage(
- src1,
- "vendor1",
- "newPath",
- "oldPath",
- 2, // revision
- 1); // min_platform_tools_rev
- ExtraPackage oldPkg = (ExtraPackage) ExtraPackage.create(
- src1, // source
- null, // props
- "vendor1", // vendor
- "oldPath", // path
- 1, // revision
- null, // license
- null, // description
- null, // descUrl
- "/sdk/extras/vendor1/oldPath" // archiveOsPath
- );
-
- // old pkg is installed, so its directory & files exists
- mFile.recordExistingFile("/sdk/extras/vendor1/oldPath/source.properties");
- mFile.recordExistingFolder("/sdk/extras/vendor1/oldPath");
-
- ArchiveReplacement ar = new ArchiveReplacement(
- newPkg.getArchives()[0],
- oldPkg.getArchives()[0]);
-
- // associate the File that will be "downloaded" for this archive
- mArchInst.setDownloadResponse(
- newPkg.getArchives()[0], createFile("/sdk", "tmp", "download1.zip"));
-
- assertTrue(mArchInst.install(ar, mSdkRoot, false /*forceHttp*/, mSdkMan,
- null /*UrlCache*/, mMon));
-
- // check what was created
- assertEquals(
- "[/sdk/extras/vendor1/newPath/source.properties]",
- Arrays.toString(mFile.getExistingFiles()));
-
- // oldPath directory has been deleted, we only have newPath now.
- // No sdk/temp dir was created since we didn't have to move the old package dir out
- // of the way.
- assertEquals(
- "[/, /sdk, /sdk/extras, /sdk/extras/vendor1, /sdk/extras/vendor1/newPath]",
- Arrays.toString(mFile.getExistingFolders()));
-
- assertEquals(
- (
- "[</sdk/extras/vendor1/newPath/source.properties: " +
- "'### Android Tool: Source of this archive.\n" +
- "#...date...\n" +
- "Extra.NameDisplay=Vendor1 NewPath\n" +
- "Extra.OldPaths=oldPath\n" +
- "Extra.Path=newPath\n" +
- "Extra.VendorDisplay=vendor1\n" +
- "Extra.VendorId=vendor1\n" +
- "Pkg.Desc=desc\n" +
- "Pkg.DescUrl=url\n" +
- "Pkg.Revision=2.0.0\n" +
- "Pkg.SourceUrl=http\\://repo.example.com/url\n" +
- "'>]"),
- stripDate(Arrays.toString(mFile.getOutputStreams())));
-
- assertEquals(
- "Installing Vendor1 NewPath, revision 2\n" +
- "Installed Vendor1 NewPath, revision 2\n",
- mMon.getCapturedLog());
- }
-
- // ----
-
- /**
- * Helper creator method to create a {@link MockEmptyPackage} with no local
- * archive associated.
- */
- private static MockEmptyPackage createRemoteEmptyPackage(SdkSource source, String testHandle) {
- return new MockEmptyPackage(source, testHandle, 0 /*revision*/) {
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- // Create one remote archive for this package
- return new Archive[] {
- new Archive(
- this,
- null, // arch
- "http://some.source/some_url",
- 1234, // size
- "abcdef") // sha1
- };
- }
- };
- }
-
- /**
- * Helper creator method to create a {@link MockExtraPackage} with no local
- * archive associated.
- */
- private static MockExtraPackage createRemoteExtraPackage(
- SdkSource source,
- String vendor,
- String path,
- int revision,
- int min_platform_tools_rev) {
- return new MockExtraPackage(source, vendor, path, revision, min_platform_tools_rev) {
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- // Create one remote archive for this package
- return new Archive[] {
- new Archive(
- this,
- null, // arch
- "http://some.source/some_url",
- 1234, // size
- "abcdef") // sha1
- };
- }
- };
- }
-
- /**
- * Helper creator method to create a {@link MockExtraPackage} with no local
- * archive associated and a specific oldPaths attribute.
- */
- private static MockExtraPackage createRemoteExtraPackage(
- SdkSource source,
- String vendor,
- String newPath,
- String oldPaths,
- int revision,
- int min_platform_tools_rev) {
- Properties props = new Properties();
- props.setProperty(PkgProps.EXTRA_OLD_PATHS, oldPaths);
- props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
- Integer.toString((min_platform_tools_rev)));
- return new MockExtraPackage(source, props, vendor, newPath, revision) {
- @Override
- protected Archive[] initializeArchives(
- Properties props2,
- String archiveOsPath) {
- // Create one remote archive for this package
- return new Archive[] {
- new Archive(
- this,
- null, // arch
- "http://some.source/some_url",
- 1234, // size
- "abcdef") // sha1
- };
- }
- };
- }
-
- private File createFile(String...segments) {
- File f = null;
- for (String segment : segments) {
- if (f == null) {
- f = new File(segment);
- } else {
- f = new File(f, segment);
- }
- }
-
- return f;
- }
-
- /**
- * Strips the second line of the string.
- * The source.properties generated by Java contain the generation data on the
- * second line and this is of course not suitable for unit tests.
- */
- private String stripDate(String string) {
- // We know it's a date if it looks like:
- // \n # ... YYYY\n
- Pattern p = Pattern.compile("\n#[^#][^\n]*[0-9]{4}\\w*\n", Pattern.DOTALL);
- string = p.matcher(string.replaceAll("\r\n", "\n")).replaceAll("\n#...date...\n");
- return string;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveTest.java
deleted file mode 100755
index ce0e415..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import junit.framework.TestCase;
-
-public class ArchiveTest extends TestCase {
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- public void testShortDescription() throws Exception {
- Archive a = new Archive(
- null, //pkg,
- new ArchFilter(HostOs.WINDOWS, null, null, null), //arch,
- null, //url
- 0, //size
- null); //checksum
- assertEquals("Archive for Windows", a.getShortDescription());
-
- a = new Archive(
- null, //pkg,
- new ArchFilter(HostOs.LINUX, null, null, null), //arch,
- null, //url
- 0, //size
- null); //checksum
- assertEquals("Archive for Linux", a.getShortDescription());
-
- a = new Archive(
- null, //pkg,
- new ArchFilter(HostOs.MACOSX, null, null, null), //arch,
- null, //url
- 0, //size
- null); //checksum
- assertEquals("Archive for MacOS X", a.getShortDescription());
-
- a = new Archive(
- null, //pkg,
- null, //arch,
- null, //url
- 0, //size
- null); //checksum
- assertEquals("Archive for any OS", a.getShortDescription());
- }
-
- public void testLongDescription() throws Exception {
- Archive a = new Archive(
- null, //pkg,
- new ArchFilter(HostOs.WINDOWS, null, null, null), //arch,
- null, //url
- 900, //size
- "1234567890ABCDEF"); //checksum
- assertEquals(
- "Archive for Windows\n" +
- "Size: 900 Bytes\n" +
- "SHA1: 1234567890ABCDEF",
- a.getLongDescription());
-
- a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, 1100, "1234567890ABCDEF");
- assertEquals(
- "Archive for Windows\n" +
- "Size: 1 KiB\n" +
- "SHA1: 1234567890ABCDEF",
- a.getLongDescription());
-
- a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, 1900, "1234567890ABCDEF");
- assertEquals(
- "Archive for Windows\n" +
- "Size: 2 KiB\n" +
- "SHA1: 1234567890ABCDEF",
- a.getLongDescription());
-
- a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, (long)2e6, "1234567890ABCDEF");
- assertEquals(
- "Archive for Windows\n" +
- "Size: 1.9 MiB\n" +
- "SHA1: 1234567890ABCDEF",
- a.getLongDescription());
-
- a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, (long)19e6, "1234567890ABCDEF");
- assertEquals(
- "Archive for Windows\n" +
- "Size: 18.1 MiB\n" +
- "SHA1: 1234567890ABCDEF",
- a.getLongDescription());
-
- a = new Archive(null, new ArchFilter(HostOs.WINDOWS, null, null, null), null, (long)18e9, "1234567890ABCDEF");
- assertEquals(
- "Archive for Windows\n" +
- "Size: 16.8 GiB\n" +
- "SHA1: 1234567890ABCDEF",
- a.getLongDescription());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/BitSizeTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/BitSizeTest.java
deleted file mode 100755
index 9f70a6a..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/BitSizeTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import junit.framework.TestCase;
-
-public class BitSizeTest extends TestCase {
-
- public void testGetSize() {
- assertEquals(32, BitSize._32.getSize());
- assertEquals(64, BitSize._64.getSize());
- }
-
- public void testGetXmlName() {
- assertEquals("32", BitSize._32.getXmlName());
- assertEquals("64", BitSize._64.getXmlName());
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/HostOsTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/HostOsTest.java
deleted file mode 100755
index c7ba507..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/HostOsTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.archives;
-
-import junit.framework.TestCase;
-
-public class HostOsTest extends TestCase {
-
- public void testGetUiName() {
- assertEquals("Windows", HostOs.WINDOWS.getUiName());
- assertEquals("MacOS X", HostOs.MACOSX .getUiName());
- assertEquals("Linux", HostOs.LINUX .getUiName());
- }
-
- public void testGetXmlName() {
- assertEquals("windows", HostOs.WINDOWS.getXmlName());
- assertEquals("macosx", HostOs.MACOSX .getXmlName());
- assertEquals("linux", HostOs.LINUX .getXmlName());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/AddonSystemImagePackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/AddonSystemImagePackageTest.java
deleted file mode 100755
index 134dc7d..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/AddonSystemImagePackageTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-public class AddonSystemImagePackageTest extends PackageTest {
-
- private static class SysImgPackageFakeArchive extends SystemImagePackage {
- protected SysImgPackageFakeArchive(
- AndroidVersion platformVersion,
- int revision,
- String addonVendorId,
- String addonNameId,
- String abi,
- Properties props) {
- super(platformVersion,
- revision,
- abi,
- props,
- String.format("/sdk/system-images/addon-%s-%s-%s/%s",
- addonNameId,
- addonVendorId,
- platformVersion.getApiString(),
- abi));
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
- }
- }
-
- private SystemImagePackage createSystemImagePackage(Properties props)
- throws AndroidVersionException {
- SystemImagePackage p = new SysImgPackageFakeArchive(
- new AndroidVersion(props),
- 1 /*revision*/,
- props.getProperty(PkgProps.ADDON_VENDOR_ID),
- props.getProperty(PkgProps.SYS_IMG_TAG_ID),
- props.getProperty(PkgProps.SYS_IMG_ABI),
- props);
- return p;
- }
-
- @Override
- protected Properties createExpectedProps() {
- Properties props = super.createExpectedProps();
-
- // SystemImagePackage properties
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
- props.setProperty(PkgProps.SYS_IMG_ABI, "armeabi-v7a");
-
- // Addon-specific SystemImagePackage properties
- props.setProperty(PkgProps.ADDON_VENDOR_ID, "vendor_id");
- props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY, "Vendor Name");
- props.setProperty(PkgProps.SYS_IMG_TAG_ID, "addon_name");
- props.setProperty(PkgProps.SYS_IMG_TAG_DISPLAY, "Add-on Name");
-
- return props;
- }
-
- protected void testCreatedSystemImagePackage(SystemImagePackage p) {
- super.testCreatedPackage(p);
-
- // SystemImagePackage properties
- assertEquals("API 5", p.getAndroidVersion().toString());
- assertEquals("armeabi-v7a", p.getAbi());
- }
-
- // ----
-
- @Override
- public final void testCreate() throws Exception {
- Properties props = createExpectedProps();
- SystemImagePackage p = createSystemImagePackage(props);
-
- testCreatedSystemImagePackage(p);
- }
-
- @Override
- public void testSaveProperties() throws Exception {
- Properties expected = createExpectedProps();
- SystemImagePackage p = createSystemImagePackage(expected);
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- public void testSameItemAs() throws Exception {
- Properties props1 = createExpectedProps();
- SystemImagePackage p1 = createSystemImagePackage(props1);
- assertTrue(p1.sameItemAs(p1));
-
- // different abi, same version
- Properties props2 = new Properties(props1);
- props2.setProperty(PkgProps.SYS_IMG_ABI, "x86");
- SystemImagePackage p2 = createSystemImagePackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // different abi, different version
- props2.setProperty(PkgProps.VERSION_API_LEVEL, "6");
- p2 = createSystemImagePackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // same abi, different version
- Properties props3 = new Properties(props1);
- props3.setProperty(PkgProps.VERSION_API_LEVEL, "6");
- SystemImagePackage p3 = createSystemImagePackage(props3);
- assertFalse(p1.sameItemAs(p3));
- assertFalse(p3.sameItemAs(p1));
- }
-
- public void testInstallId() throws Exception {
- Properties props = createExpectedProps();
- SystemImagePackage p = createSystemImagePackage(props);
-
- assertEquals("sys-img-armeabi-v7a-addon-addon_name-vendor_id-5", p.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BrokenPackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BrokenPackageTest.java
deleted file mode 100755
index 052292f..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BrokenPackageTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import junit.framework.TestCase;
-
-public class BrokenPackageTest extends TestCase {
-
- private BrokenPackage m;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- m = new BrokenPackage(null /*props*/,
- "short description",
- "long description",
- 12, // min api level
- 13, // exact api level
- "os/path",
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 4),
- FullRevision.NOT_SPECIFIED).create());
- }
-
- public final void testGetShortDescription() {
- assertEquals("short description", m.getShortDescription());
- }
-
- public final void testGetLongDescription() {
- assertEquals("long description", m.getLongDescription());
- }
-
- public final void testGetMinApiLevel() {
- assertEquals(12, m.getMinApiLevel());
- }
-
- public final void testGetExactApiLevel() {
- assertEquals(13, m.getExactApiLevel());
- }
-
- public void testInstallId() {
- assertEquals("", m.installId());
- }
-
- public final void testGetPkgDesc() {
- assertEquals(
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 4),
- FullRevision.NOT_SPECIFIED).create(),
- m.getPkgDesc());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BuildToolPackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BuildToolPackageTest.java
deleted file mode 100755
index b3233ea..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/BuildToolPackageTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-import junit.framework.TestCase;
-
-public class BuildToolPackageTest extends TestCase {
-
- private static class BuildToolPackageFakeArchive extends BuildToolPackage {
- protected BuildToolPackageFakeArchive(Properties props) {
- super(null, // source
- props,
- 0, //ignored, the one from props will be used
- null, //license
- null, //description
- null, //descUrl
- "/sdk/tools");
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return super.initializeArchives(props, PackageTest.LOCAL_ARCHIVE_PATH);
- }
- }
-
- private Properties createExpectedProps(boolean isPreview) {
- Properties props = PackageTest.createDefaultProps();
-
- props.setProperty(PkgProps.PKG_REVISION,
- new FullRevision(1, 2, 3, isPreview ? 4 : 0).toShortString());
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
-
- return props;
- }
-
- // ----
-
- public void testInstallId() throws Exception {
- Properties props1 = createExpectedProps(true);
- BuildToolPackage p1 = new BuildToolPackageFakeArchive(props1);
- assertEquals("build-tools-1.2.3-preview", p1.installId());
-
- Properties props2 = createExpectedProps(false);
- BuildToolPackage p2 = new BuildToolPackageFakeArchive(props2);
- assertEquals("build-tools-1.2.3", p2.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_Base.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_Base.java
deleted file mode 100755
index b82637c..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_Base.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-public class ExtraPackageTest_Base extends PackageTest {
-
- @Override
- public void testCreate() {
- Properties props = createExpectedProps();
-
- MockExtraPackage p = new MockExtraPackage(
- null, //source
- props,
- "vendor",
- "the_path",
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- LOCAL_ARCHIVE_PATH
- );
-
- testCreatedPackage(p);
- }
-
- @Override
- public void testSaveProperties() {
- Properties expected = createExpectedProps();
-
- MockExtraPackage p = new MockExtraPackage(
- null, //source
- expected,
- "vendor",
- "the_path",
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- LOCAL_ARCHIVE_PATH
- );
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- @Override
- protected Properties createExpectedProps() {
- Properties props = super.createExpectedProps();
-
- props.setProperty(PkgProps.EXTRA_VENDOR_ID, "vendor");
- props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "vendor");
- props.setProperty(PkgProps.EXTRA_PATH, "the_path");
- props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "Vendor The Path");
-
- // Extra revision is now a NoPreviewRevision and writes its full major.minor.micro
- props.setProperty(PkgProps.PKG_REVISION, "42.0.0");
-
- // MinToolsPackage properties
- props.setProperty(PkgProps.MIN_TOOLS_REV, "3.0.1");
-
- return props;
- }
-
- protected void testCreatedMinToolsPackage(MockExtraPackage p) {
- super.testCreatedPackage(p);
-
- // MinToolsPackage properties
- assertEquals("3.0.1", p.getMinToolsRevision().toShortString());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v3.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v3.java
deleted file mode 100755
index 3d92d48..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v3.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.PkgProps;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Properties;
-
-/**
- * Tests {@link ExtraPackage} using anddon-3.xsd: it has a {@code <path>} and {@code <vendor>}.
- * (it lacks name-display, vendor-id and vendor-display which are in addon-4.xsd)
- */
-public class ExtraPackageTest_v3 extends ExtraPackageTest_Base {
-
- private static final char PS = File.pathSeparatorChar;
-
- private ExtraPackage createExtraPackage(Properties props) {
- ExtraPackage p = (ExtraPackage) ExtraPackage.create(
- null, //source
- props,
- null, //vendor
- null, //path
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- "/local/archive/path" //archiveOsPath
- );
- return p;
- }
-
- /** Properties used to "load" the package. When saved, they become different. */
- private Properties createLoadedProps() {
- Properties props = super.createExpectedProps();
-
- // ExtraPackage properties
- props.setProperty(PkgProps.EXTRA_VENDOR, "vendor");
- props.setProperty(PkgProps.EXTRA_PATH, "the_path");
- props.setProperty(PkgProps.EXTRA_OLD_PATHS, "old_path1;oldpath2");
- props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, "11");
- props.setProperty(PkgProps.EXTRA_PROJECT_FILES,
- "path1.jar" + PS + "dir2/jar 2.jar" + PS + "dir/3/path");
-
- return props;
- }
-
- /** Properties saved by the package. They differ from loaded ones in name and vendor. */
- private Properties createSavedProps() {
- Properties props = super.createExpectedProps();
-
- // ExtraPackage properties
- props.setProperty(PkgProps.EXTRA_VENDOR_ID, "vendor");
- props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "vendor");
- props.setProperty(PkgProps.EXTRA_PATH, "the_path");
- props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "Vendor The Path");
- props.setProperty(PkgProps.EXTRA_OLD_PATHS, "old_path1;oldpath2");
- props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, "11");
- props.setProperty(PkgProps.EXTRA_PROJECT_FILES,
- "path1.jar" + PS + "dir2/jar 2.jar" + PS + "dir/3/path");
-
- return props;
- }
-
- protected void testCreatedExtraPackage(ExtraPackage p) {
- super.testCreatedPackage(p);
-
- // Package properties
- // vendor becomes both vendor-id and vendor-display
- assertEquals("vendor", p.getVendorId());
- assertEquals("vendor", p.getVendorDisplay());
- assertEquals("the_path", p.getPath());
- // path and vendor are combined in the default display name
- assertEquals("Vendor The Path", p.getDisplayName());
- assertEquals("[old_path1, oldpath2]", Arrays.toString(p.getOldPaths()));
- assertEquals(11, p.getMinApiLevel());
- assertEquals(
- "[path1.jar, dir2/jar 2.jar, dir/3/path]",
- Arrays.toString(p.getProjectFiles()));
- }
-
- // ----
-
- @Override
- public final void testCreate() {
- Properties props = createLoadedProps();
- ExtraPackage p = createExtraPackage(props);
-
- testCreatedExtraPackage(p);
- }
-
- @Override
- public void testSaveProperties() {
- Properties props = createLoadedProps();
- ExtraPackage p = createExtraPackage(props);
-
- Properties props2 = new Properties();
- p.saveProperties(props2);
-
- assertEquals(props2, createSavedProps());
- }
-
- public void testSameItemAs() {
- Properties props1 = createLoadedProps();
- ExtraPackage p1 = createExtraPackage(props1);
- assertTrue(p1.sameItemAs(p1));
-
- // different vendor, same path
- Properties props2 = new Properties(props1);
- props2.setProperty(PkgProps.EXTRA_VENDOR_ID, "");
- props2.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "");
- props2.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "");
- props2.setProperty(PkgProps.EXTRA_VENDOR, "vendor2");
- ExtraPackage p2 = createExtraPackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // different vendor, different path
- props2.setProperty(PkgProps.EXTRA_PATH, "new_path2");
- p2 = createExtraPackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // same vendor, but single path using the old paths from p1
- Properties props3 = new Properties(props1);
- props3.setProperty(PkgProps.EXTRA_OLD_PATHS, "");
- props3.setProperty(PkgProps.EXTRA_PATH, "old_path1");
- ExtraPackage p3 = createExtraPackage(props3);
- assertTrue(p1.sameItemAs(p3));
- assertTrue(p3.sameItemAs(p1));
-
- props3.setProperty(PkgProps.EXTRA_PATH, "oldpath2");
- p3 = createExtraPackage(props3);
- assertTrue(p1.sameItemAs(p3));
- assertTrue(p3.sameItemAs(p1));
-
- // same vendor, different old paths but there's a path=>old_path match
- Properties props4 = new Properties(props1);
- props4.setProperty(PkgProps.EXTRA_OLD_PATHS, "new_path4;new_path5");
- props4.setProperty(PkgProps.EXTRA_PATH, "old_path1");
- ExtraPackage p4 = createExtraPackage(props4);
- assertTrue(p1.sameItemAs(p4));
- assertTrue(p4.sameItemAs(p1));
-
- // same vendor, incompatible paths
- Properties props5 = new Properties(props1);
- // and the only match is between old_paths, which doesn't count.
- props5.setProperty(PkgProps.EXTRA_OLD_PATHS, "old_path1;new_path5");
- props5.setProperty(PkgProps.EXTRA_PATH, "new_path4");
- ExtraPackage p5 = createExtraPackage(props5);
- assertFalse(p1.sameItemAs(p5));
- assertFalse(p5.sameItemAs(p1));
- }
-
- public void testInstallId() {
- Properties props = createLoadedProps();
- ExtraPackage p = createExtraPackage(props);
-
- assertEquals("extra-vendor-the_path", p.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v4.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v4.java
deleted file mode 100755
index 4bd1e1b..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v4.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.PkgProps;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Properties;
-
-/**
- * Tests {@link ExtraPackage} using anddon-4.xsd:
- * it has name-display, vendor-id and vendor-display.
- */
-public class ExtraPackageTest_v4 extends ExtraPackageTest_Base {
-
- private static final char PS = File.pathSeparatorChar;
-
- private ExtraPackage createExtraPackage(Properties props) {
- ExtraPackage p = (ExtraPackage) ExtraPackage.create(
- null, //source
- props,
- null, //vendor
- null, //path
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- "/local/archive/path" //archiveOsPath
- );
- return p;
- }
-
- @Override
- protected Properties createExpectedProps() {
- Properties props = super.createExpectedProps();
-
- // ExtraPackage properties
- props.setProperty(PkgProps.EXTRA_VENDOR_ID, "the_vendor");
- props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "The Company, Inc.");
- props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "Some Extra Package");
- props.setProperty(PkgProps.EXTRA_PATH, "the_path");
- props.setProperty(PkgProps.EXTRA_OLD_PATHS, "old_path1;oldpath2");
- props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, "11");
- props.setProperty(PkgProps.EXTRA_PROJECT_FILES,
- "path1.jar" + PS + "dir2/jar 2.jar" + PS + "dir/3/path");
-
- return props;
- }
-
- protected void testCreatedExtraPackage(ExtraPackage p) {
- super.testCreatedPackage(p);
-
- // Package properties
- assertEquals("the_vendor", p.getVendorId());
- assertEquals("The Company, Inc.", p.getVendorDisplay());
- assertEquals("Some Extra Package", p.getDisplayName());
- assertEquals("the_path", p.getPath());
- assertEquals("[old_path1, oldpath2]", Arrays.toString(p.getOldPaths()));
- assertEquals(11, p.getMinApiLevel());
- assertEquals(
- "[path1.jar, dir2/jar 2.jar, dir/3/path]",
- Arrays.toString(p.getProjectFiles()));
- }
-
- // ----
-
- @Override
- public final void testCreate() {
- Properties props = createExpectedProps();
- ExtraPackage p = createExtraPackage(props);
-
- testCreatedExtraPackage(p);
- }
-
- @Override
- public void testSaveProperties() {
- Properties expected = createExpectedProps();
- ExtraPackage p = createExtraPackage(expected);
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- public void testSameItemAs() {
- Properties props1 = createExpectedProps();
- ExtraPackage p1 = createExtraPackage(props1);
- assertTrue(p1.sameItemAs(p1));
-
- // different vendor, same path
- Properties props2 = new Properties(props1);
- props2.setProperty(PkgProps.EXTRA_VENDOR_ID, "vendor2");
- props2.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "Another Vendor Name");
- ExtraPackage p2 = createExtraPackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // different vendor, different path
- props2.setProperty(PkgProps.EXTRA_PATH, "new_path2");
- p2 = createExtraPackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // same vendor, but single path using the old paths from p1
- Properties props3 = new Properties(props1);
- props3.setProperty(PkgProps.EXTRA_OLD_PATHS, "");
- props3.setProperty(PkgProps.EXTRA_PATH, "old_path1");
- ExtraPackage p3 = createExtraPackage(props3);
- assertTrue(p1.sameItemAs(p3));
- assertTrue(p3.sameItemAs(p1));
-
- props3.setProperty(PkgProps.EXTRA_PATH, "oldpath2");
- p3 = createExtraPackage(props3);
- assertTrue(p1.sameItemAs(p3));
- assertTrue(p3.sameItemAs(p1));
-
- // same vendor, different old paths but there's a path=>old_path match
- Properties props4 = new Properties(props1);
- props4.setProperty(PkgProps.EXTRA_OLD_PATHS, "new_path4;new_path5");
- props4.setProperty(PkgProps.EXTRA_PATH, "old_path1");
- ExtraPackage p4 = createExtraPackage(props4);
- assertTrue(p1.sameItemAs(p4));
- assertTrue(p4.sameItemAs(p1));
-
- // same vendor, incompatible paths
- Properties props5 = new Properties(props1);
- // and the only match is between old_paths, which doesn't count.
- props5.setProperty(PkgProps.EXTRA_OLD_PATHS, "old_path1;new_path5");
- props5.setProperty(PkgProps.EXTRA_PATH, "new_path4");
- ExtraPackage p5 = createExtraPackage(props5);
- assertFalse(p1.sameItemAs(p5));
- assertFalse(p5.sameItemAs(p1));
- }
-
- public void testInstallId() {
- Properties props = createExpectedProps();
- ExtraPackage p = createExtraPackage(props);
-
- assertEquals("extra-the_vendor-the_path", p.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java
deleted file mode 100755
index 621ac0a..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Properties;
-
-import junit.framework.TestCase;
-
-public class FullRevisionPackageTest extends TestCase {
-
- /**
- * Helper that creates the {@link Properties} from a {@link FullRevision}
- * as expected by {@link FullRevisionPackage}.
- */
- public static Properties createProps(FullRevision revision) {
- Properties props = new Properties();
- if (revision != null) {
- props.setProperty(PkgProps.PKG_REVISION, revision.toString());
- }
- return props;
- }
-
- public void testCompareTo() throws Exception {
- // Test order of full revision packages.
- //
- // Note that Package.compareTo() is designed to return the desired
- // ordering for a list display and as such a final/release package
- // needs to be listed before its rc/preview package.
- //
- // This differs from the order used by FullRevision.compareTo().
-
- ArrayList<Package> list = new ArrayList<Package>();
-
- list.add(new MockToolPackage(null, new FullRevision(1, 0, 0, 0), 8));
- list.add(new MockToolPackage(null, new FullRevision(1, 0, 0, 1), 8));
- list.add(new MockToolPackage(null, new FullRevision(1, 0, 1, 0), 8));
- list.add(new MockToolPackage(null, new FullRevision(1, 0, 1, 1), 8));
- list.add(new MockToolPackage(null, new FullRevision(1, 1, 0, 0), 8));
- list.add(new MockToolPackage(null, new FullRevision(1, 1, 0, 1), 8));
- list.add(new MockToolPackage(null, new FullRevision(2, 1, 1, 0), 8));
- list.add(new MockToolPackage(null, new FullRevision(2, 1, 1, 1), 8));
-
- Collections.sort(list);
-
- assertEquals(
- "[Android SDK Tools, revision 1, " +
- "Android SDK Tools, revision 1 rc1, " +
- "Android SDK Tools, revision 1.0.1, " +
- "Android SDK Tools, revision 1.0.1 rc1, " +
- "Android SDK Tools, revision 1.1, " +
- "Android SDK Tools, revision 1.1 rc1, " +
- "Android SDK Tools, revision 2.1.1, " +
- "Android SDK Tools, revision 2.1.1 rc1]",
- Arrays.toString(list.toArray()));
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/FullRevisionTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
deleted file mode 100755
index bd67412..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-
-import junit.framework.TestCase;
-
-import java.util.Arrays;
-
-public class FullRevisionTest extends TestCase {
-
- public final void testFullRevision() {
- FullRevision p = new FullRevision(5);
- assertEquals(5, p.getMajor());
- assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
- assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(FullRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("5", p.toShortString());
- assertEquals(p, FullRevision.parseRevision("5"));
- assertEquals("5.0.0", p.toString());
- assertEquals(p, FullRevision.parseRevision("5.0.0"));
- assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5, 0, 0, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new FullRevision(5, 0, 0, 6);
- assertEquals(5, p.getMajor());
- assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
- assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(6, p.getPreview());
- assertTrue (p.isPreview());
- assertEquals("5 rc6", p.toShortString());
- assertEquals(p, FullRevision.parseRevision("5 rc6"));
- assertEquals("5.0.0 rc6", p.toString());
- assertEquals(p, FullRevision.parseRevision("5.0.0 rc6"));
- assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5, 0, 0, 6]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new FullRevision(1, 2, 0, FullRevision.PreviewType.ALPHA, 1, "-");
- assertEquals(1, p.getMajor());
- assertEquals(2, p.getMinor());
- assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(1, p.getPreview());
- assertTrue(p.isPreview());
- assertEquals("1.2-alpha1", p.toShortString());
- assertEquals(p, FullRevision.parseRevision("1.2-alpha1"));
- assertEquals("1.2.0-alpha1", p.toString());
- assertEquals(p, FullRevision.parseRevision("1.2.0-alpha1"));
- assertFalse(p.equals(FullRevision.parseRevision("1.2.0-rc1")));
- assertTrue(p.compareTo(FullRevision.parseRevision("1.2.0-rc1")) < 0);
- assertEquals("[1, 2, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[1, 2, 0, 1]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new FullRevision(1, 1, 0, FullRevision.PreviewType.BETA, 4, "-");
- assertEquals(1, p.getMajor());
- assertEquals(1, p.getMinor());
- assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(4, p.getPreview());
- assertTrue(p.isPreview());
- assertEquals("1.1-beta4", p.toShortString());
- assertEquals(p, FullRevision.parseRevision("1.1-beta4"));
- assertEquals("1.1.0-beta4", p.toString());
- assertEquals(p, FullRevision.parseRevision("1.1.0-beta4"));
- assertEquals("[1, 1, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[1, 1, 0, 4]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- assertTrue(FullRevision.parseRevision("1.1.0-beta4").compareTo(FullRevision.parseRevision("1.1.0-alpha5")) > 0);
- assertTrue(FullRevision.parseRevision("1.1.0-beta4").compareTo(FullRevision.parseRevision("1.1.0-beta3")) > 0);
- assertTrue(FullRevision.parseRevision("1.1.0-beta4").compareTo(FullRevision.parseRevision("1.1.0-beta5")) < 0);
- assertTrue(FullRevision.parseRevision("1.1.0-beta4").compareTo(FullRevision.parseRevision("1.1.0-rc1")) < 0);
- assertTrue(FullRevision.parseRevision("1.1.0-alpha5").compareTo(FullRevision.parseRevision("1.1.0-beta4")) < 0);
- assertTrue(FullRevision.parseRevision("1.1.0-beta3").compareTo(FullRevision.parseRevision("1.1.0-beta4")) < 0);
- assertTrue(FullRevision.parseRevision("1.1.0-beta5").compareTo(FullRevision.parseRevision("1.1.0-beta4")) > 0);
- assertTrue(FullRevision.parseRevision("1.1.0-rc1").compareTo(FullRevision.parseRevision("1.1.0-beta4")) > 0);
- assertTrue(FullRevision.parseRevision("1.1.0").compareTo(FullRevision.parseRevision("1.1.0-alpha1")) > 0);
- assertTrue(FullRevision.parseRevision("1.1.0").compareTo(FullRevision.parseRevision("1.1.0-beta4")) > 0);
- assertTrue(FullRevision.parseRevision("1.1.0").compareTo(FullRevision.parseRevision("1.1.0-rc1")) > 0);
- assertTrue(FullRevision.parseRevision("1.1.0-alpha1").compareTo(FullRevision.parseRevision("1.1.0")) < 0);
- assertTrue(FullRevision.parseRevision("1.1.0-beta4").compareTo(FullRevision.parseRevision("1.1.0")) < 0);
- assertTrue(FullRevision.parseRevision("1.1.0-rc1").compareTo(FullRevision.parseRevision("1.1.0")) < 0);
-
- p = new FullRevision(6, 7, 0);
- assertEquals(6, p.getMajor());
- assertEquals(7, p.getMinor());
- assertEquals(0, p.getMicro());
- assertEquals(0, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("6.7", p.toShortString());
- assertEquals(p, FullRevision.parseRevision("6.7"));
- assertEquals("6.7.0", p.toString());
- assertEquals(p, FullRevision.parseRevision("6.7.0"));
- assertEquals("[6, 7, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[6, 7, 0, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new FullRevision(10, 11, 12, FullRevision.NOT_A_PREVIEW);
- assertEquals(10, p.getMajor());
- assertEquals(11, p.getMinor());
- assertEquals(12, p.getMicro());
- assertEquals(0, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("10.11.12", p.toShortString());
- assertEquals("10.11.12", p.toString());
- assertEquals(p, FullRevision.parseRevision("10.11.12"));
- assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[10, 11, 12, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new FullRevision(10, 11, 12, 13);
- assertEquals(10, p.getMajor());
- assertEquals(11, p.getMinor());
- assertEquals(12, p.getMicro());
- assertEquals(13, p.getPreview());
- assertTrue (p.isPreview());
- assertEquals("10.11.12 rc13", p.toShortString());
- assertEquals("10.11.12 rc13", p.toString());
- assertEquals(p, FullRevision.parseRevision("10.11.12 rc13"));
- assertEquals(p, FullRevision.parseRevision(" 10.11.12 rc13"));
- assertEquals(p, FullRevision.parseRevision("10.11.12 rc13 "));
- assertEquals(p, FullRevision.parseRevision(" 10.11.12 rc13 "));
- assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[10, 11, 12, 13]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
- }
-
- public final void testParseError() {
- String errorMsg = null;
- try {
- FullRevision.parseRevision("not a number");
- fail("FullRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: not a number", errorMsg);
-
- errorMsg = null;
- try {
- FullRevision.parseRevision("5 .6 .7");
- fail("FullRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5 .6 .7", errorMsg);
-
- errorMsg = null;
- try {
- FullRevision.parseRevision("5.0.0 preview 1");
- fail("FullRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5.0.0 preview 1", errorMsg);
-
- errorMsg = null;
- try {
- FullRevision.parseRevision(" 5.1.2 rc 42 ");
- fail("FullRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5.1.2 rc 42 ", errorMsg);
- }
-
- public final void testCompareTo() {
- FullRevision s4 = new FullRevision(4);
- FullRevision i4 = new FullRevision(4);
- FullRevision g5 = new FullRevision(5, 1, 0, 6);
- FullRevision y5 = new FullRevision(5);
- FullRevision c5 = new FullRevision(5, 1, 0, 6);
- FullRevision o5 = new FullRevision(5, 0, 0, 7);
- FullRevision p5 = new FullRevision(5, 1, 0, 0);
-
- assertEquals(s4, i4); // 4.0.0-0 == 4.0.0-0
- assertEquals(g5, c5); // 5.1.0-6 == 5.1.0-6
-
- assertFalse(y5.equals(p5)); // 5.0.0-0 != 5.1.0-0
- assertFalse(g5.equals(p5)); // 5.1.0-6 != 5.1.0-0
- assertTrue (s4.compareTo(i4) == 0); // 4.0.0-0 == 4.0.0-0
- assertTrue (s4.compareTo(y5) < 0); // 4.0.0-0 < 5.0.0-0
- assertTrue (y5.compareTo(y5) == 0); // 5.0.0-0 == 5.0.0-0
- assertTrue (y5.compareTo(p5) < 0); // 5.0.0-0 < 5.1.0-0
- assertTrue (o5.compareTo(y5) < 0); // 5.0.0-7 < 5.0.0-0
- assertTrue (p5.compareTo(p5) == 0); // 5.1.0-0 == 5.1.0-0
- assertTrue (c5.compareTo(p5) < 0); // 5.1.0-6 < 5.1.0-0
- assertTrue (p5.compareTo(c5) > 0); // 5.1.0-0 > 5.1.0-6
- assertTrue (p5.compareTo(o5) > 0); // 5.1.0-0 > 5.0.0-7
- assertTrue (c5.compareTo(o5) > 0); // 5.1.0-6 > 5.0.0-7
- assertTrue (o5.compareTo(o5) == 0); // 5.0.0-7 > 5.0.0-7
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java
deleted file mode 100755
index a63550a..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-
-import junit.framework.TestCase;
-
-public class MajorRevisionTest extends TestCase {
-
- public final void testMajorRevision() {
- MajorRevision p = new MajorRevision(5);
- assertEquals(5, p.getMajor());
- assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
- assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(FullRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("5", p.toShortString());
- assertEquals(p, MajorRevision.parseRevision("5"));
- assertEquals("5", p.toString());
-
- assertEquals(new FullRevision(5, 0, 0, 0), p);
- }
-
- public final void testParseError() {
- String errorMsg = null;
- try {
- MajorRevision.parseRevision("5.0.0");
- fail("MajorRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5.0.0 -- Micro number not supported", errorMsg);
- }
-
- public final void testCompareTo() {
- MajorRevision s4 = new MajorRevision(4);
- MajorRevision i4 = new MajorRevision(4);
- FullRevision g5 = new FullRevision (5, 1, 0, 6);
- MajorRevision y5 = new MajorRevision(5);
- FullRevision c5 = new FullRevision (5, 1, 0, 6);
- FullRevision o5 = new FullRevision (5, 0, 0, 7);
- FullRevision p5 = new FullRevision (5, 1, 0, 0);
-
- assertEquals(s4, i4); // 4.0.0-0 == 4.0.0-0
- assertEquals(g5, c5); // 5.1.0-6 == 5.1.0-6
-
- assertFalse(y5.equals(p5)); // 5.0.0-0 != 5.1.0-0
- assertFalse(g5.equals(p5)); // 5.1.0-6 != 5.1.0-0
- assertTrue (s4.compareTo(i4) == 0); // 4.0.0-0 == 4.0.0-0
- assertTrue (s4.compareTo(y5) < 0); // 4.0.0-0 < 5.0.0-0
- assertTrue (y5.compareTo(y5) == 0); // 5.0.0-0 == 5.0.0-0
- assertTrue (y5.compareTo(p5) < 0); // 5.0.0-0 < 5.1.0-0
- assertTrue (o5.compareTo(y5) < 0); // 5.0.0-7 < 5.0.0-0
- assertTrue (p5.compareTo(p5) == 0); // 5.1.0-0 == 5.1.0-0
- assertTrue (c5.compareTo(p5) < 0); // 5.1.0-6 < 5.1.0-0
- assertTrue (p5.compareTo(c5) > 0); // 5.1.0-0 > 5.1.0-6
- assertTrue (p5.compareTo(o5) > 0); // 5.1.0-0 > 5.0.0-7
- assertTrue (c5.compareTo(o5) > 0); // 5.1.0-6 > 5.0.0-7
- assertTrue (o5.compareTo(o5) == 0); // 5.0.0-7 > 5.0.0-7
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
deleted file mode 100755
index 87533e3..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-public class MinToolsPackageTest extends PackageTest {
-
- /** Local class used to test the abstract MinToolsPackage class */
- protected static class MockMinToolsPackage extends MinToolsPackage {
- public MockMinToolsPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
- }
-
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public String getListDescription() {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public String getShortDescription() {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public String installId() {
- return ""; //$NON-NLS-1$
- }
-
- @Override
- public IPkgDesc getPkgDesc() {
- return PkgDesc.Builder.newTool(
- new FullRevision(1, 2, 3, 4),
- FullRevision.NOT_SPECIFIED).create();
- }
- }
-
- @Override
- public void testCreate() {
- Properties props = createExpectedProps();
-
- MockMinToolsPackage p = new MockMinToolsPackage(
- null, //source
- props,
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- LOCAL_ARCHIVE_PATH
- );
-
- testCreatedPackage(p);
- }
-
- @Override
- public void testSaveProperties() {
- Properties expected = createExpectedProps();
-
- MockMinToolsPackage p = new MockMinToolsPackage(
- null, //source
- expected,
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- LOCAL_ARCHIVE_PATH
- );
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- @Override
- protected Properties createExpectedProps() {
- Properties props = super.createExpectedProps();
-
- // MinToolsPackage properties
- props.setProperty(PkgProps.MIN_TOOLS_REV, "3.0.1");
-
- return props;
- }
-
- protected void testCreatedMinToolsPackage(MockMinToolsPackage p) {
- super.testCreatedPackage(p);
-
- // MinToolsPackage properties
- assertEquals("3.0.1", p.getMinToolsRevision().toShortString());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockAddonPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockAddonPackage.java
deleted file mode 100755
index 37b743f..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockAddonPackage.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.internal.androidTarget.MockAddonTarget;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-/**
- * A mock {@link AddonPackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockAddonPackage extends AddonPackage {
-
- /**
- * Creates a {@link MockAddonTarget} with the requested base platform and addon revision
- * and then a {@link MockAddonPackage} wrapping it and a default name of "addon".
- *
- * By design, this package contains one and only one archive.
- */
- public MockAddonPackage(MockPlatformPackage basePlatform, int revision) {
- this("addon", basePlatform, revision); //$NON-NLS-1$
- }
-
- /**
- * Creates a {@link MockAddonTarget} with the requested base platform and addon revision
- * and then a {@link MockAddonPackage} wrapping it.
- *
- * By design, this package contains one and only one archive.
- */
- public MockAddonPackage(String name, MockPlatformPackage basePlatform, int revision) {
- super(new MockAddonTarget(name, basePlatform.getTarget(), revision), null /*props*/);
- }
-
- public MockAddonPackage(
- SdkSource source,
- String name,
- MockPlatformPackage basePlatform,
- int revision) {
- super(source,
- new MockAddonTarget(name, basePlatform.getTarget(), revision),
- createProperties(name, basePlatform.getTarget()));
- }
-
- private static Properties createProperties(String name, IAndroidTarget baseTarget) {
- String vendor = baseTarget.getVendor();
- Properties props = new Properties();
- props.setProperty(PkgProps.ADDON_NAME_ID, name);
- props.setProperty(PkgProps.ADDON_NAME_DISPLAY,
- String.format("The %1$s from %2$s", //$NON-NLS-1$
- name, vendor));
- props.setProperty(PkgProps.ADDON_VENDOR_ID,
- String.format("vendor-id-%1$s", vendor)); //$NON-NLS-1$
- props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY,
- String.format("The %1$s", vendor)); //$NON-NLS-1$
- return props;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBrokenPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBrokenPackage.java
deleted file mode 100755
index 37173b6..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBrokenPackage.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-
-/**
- * A mock {@link BrokenPackage} for testing.
- * <p/>
- * By design, this package contains one and only one archive.
- */
-public class MockBrokenPackage extends BrokenPackage {
-
- public MockBrokenPackage(int minApiLevel, int exactApiLevel) {
- this(createDesription(minApiLevel, exactApiLevel), // short description
- createDesription(minApiLevel, exactApiLevel), // long description
- minApiLevel,
- exactApiLevel);
- }
-
- private static String createDesription(int minApiLevel, int exactApiLevel) {
- String s = "Broken package";
- s += exactApiLevel == BrokenPackage.API_LEVEL_INVALID ? " (No API level)" :
- String.format(" for API %d", exactApiLevel);
- s += minApiLevel == BrokenPackage.MIN_API_LEVEL_NOT_SPECIFIED ? "" :
- String.format(", min API %d", minApiLevel);
- return s;
- }
-
- public MockBrokenPackage(
- String shortDescription,
- String longDescription,
- int minApiLevel,
- int exactApiLevel) {
- super(null /*props*/,
- shortDescription,
- longDescription,
- minApiLevel,
- exactApiLevel,
- "/sdk/broken/package" /*osPath*/,
- PkgDesc.Builder.newTool(
- new FullRevision(1, 2, 3, 4),
- FullRevision.NOT_SPECIFIED).create());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBuildToolPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBuildToolPackage.java
deleted file mode 100755
index eb9f69c..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockBuildToolPackage.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-
-/**
- * A mock {@link BuildToolPackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockBuildToolPackage extends BuildToolPackage {
-
- /**
- * Creates a {@link MockBuildToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockBuildToolPackage(int revision) {
- this(null /*source*/, revision);
- }
-
- /**
- * Creates a {@link MockBuildToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockBuildToolPackage(SdkSource source, int revision) {
- super(
- source, // source,
- null, // props,
- revision,
- null, // license,
- "desc", // description,
- "url", // descUrl,
- "foo" // archiveOsPath
- );
- }
-
- /**
- * Creates a {@link MockBuildToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockBuildToolPackage(SdkSource source, FullRevision revision) {
- super(
- source, // source,
- FullRevisionPackageTest.createProps(revision), // props,
- revision.getMajor(),
- null, // license,
- "desc", // description,
- "url", // descUrl,
- source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
- );
- }
-}
-
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
deleted file mode 100755
index ad15ef0..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * A mock empty package, of no particular subpackage type.
- * {@link #sameItemAs(Package)} will return true if these packages have the same handle.
- */
-public class MockEmptyPackage extends MajorRevisionPackage {
- private final String mTestHandle;
-
- /**
- * Creates a new {@link MockEmptyPackage} with a local archive.
- *
- * @param testHandle The comparison handle for {@link #sameItemAs(Package)}.
- */
- public MockEmptyPackage(String testHandle) {
- super(
- null /*source*/,
- null /*props*/,
- 0 /*revision*/,
- null /*license*/,
- null /*description*/,
- null /*descUrl*/,
- "/sdk/tmp/empty_pkg" /*archiveOsPath*/
- );
- mTestHandle = testHandle;
- }
-
- /**
- * Creates a new {@link MockEmptyPackage} with a local archive.
- *
- * @param testHandle The comparison handle for {@link #sameItemAs(Package)}.
- * @param props The package properties.
- */
- public MockEmptyPackage(String testHandle, Properties props) {
- super(
- null /*source*/,
- props,
- 0 /*revision*/,
- null /*license*/,
- null /*description*/,
- null /*descUrl*/,
- "/sdk/tmp/empty_pkg" /*archiveOsPath*/
- );
- mTestHandle = testHandle;
- }
-
- /**
- * Creates a new {@link MockEmptyPackage} with a local archive.
- *
- * @param testHandle The comparison handle for {@link #sameItemAs(Package)}.
- * @param revision The revision of the package, printed in the short description.
- */
- public MockEmptyPackage(String testHandle, int revision) {
- super(
- null /*source*/,
- null /*props*/,
- revision,
- null /*license*/,
- null /*description*/,
- null /*descUrl*/,
- "/sdk/tmp/empty_pkg" /*archiveOsPath*/
- );
- mTestHandle = testHandle;
- }
-
- /**
- * Creates a new {@link MockEmptyPackage} with a local archive.
- *
- * @param source The source associate with this package.
- * @param testHandle The comparison handle for {@link #sameItemAs(Package)}.
- * @param revision The revision of the package, printed in the short description.
- */
- public MockEmptyPackage(SdkSource source, String testHandle, int revision) {
- super(
- source,
- null /*props*/,
- revision,
- null /*license*/,
- null /*description*/,
- null /*descUrl*/,
- "/sdk/tmp/empty_pkg" /*archiveOsPath*/
- );
- mTestHandle = testHandle;
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return new Archive[] {
- new Archive(this, props, archiveOsPath) {
- @Override
- public String toString() {
- return mTestHandle;
- }
- } };
- }
-
- @Override
- public IPkgDesc getPkgDesc() {
- return PkgDesc.Builder.newTool(
- new FullRevision(1, 2, 3, 4),
- FullRevision.NOT_SPECIFIED).create();
- }
-
- public Archive getLocalArchive() {
- return getArchives()[0];
- }
-
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return new File(new File(osSdkRoot, "mock"), mTestHandle);
- }
-
- @Override
- public String installId() {
- return "mock-empty-" + mTestHandle; //$NON-NLS-1$
- }
-
- @Override
- public String getListDescription() {
- String ld = super.getListDisplay();
- return ld.isEmpty() ? this.getClass().getSimpleName() : ld;
- }
-
- @Override
- public String getShortDescription() {
- StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
- sb.append(" '").append(mTestHandle).append('\'');
- if (getRevision().getMajor() > 0) {
- sb.append(" rev=").append(getRevision());
- }
- return sb.toString();
- }
-
- /** Returns true if these packages have the same handle. */
- @Override
- public boolean sameItemAs(Package pkg) {
- return (pkg instanceof MockEmptyPackage) &&
- mTestHandle.equals(((MockEmptyPackage) pkg).mTestHandle);
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mTestHandle == null) ? 0 : mTestHandle.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof MockEmptyPackage)) {
- return false;
- }
- MockEmptyPackage other = (MockEmptyPackage) obj;
- if (mTestHandle == null) {
- if (other.mTestHandle != null) {
- return false;
- }
- } else if (!mTestHandle.equals(other.mTestHandle)) {
- return false;
- }
- return true;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockExtraPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
deleted file mode 100755
index 3b9cd89..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-/**
- * A mock {@link ExtraPackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockExtraPackage extends ExtraPackage {
-
- /**
- * Creates a {@link MockExtraPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockExtraPackage(String vendor, String path, int revision, int min_platform_tools_rev) {
- this(null /*source*/, vendor, path, revision, min_platform_tools_rev);
- }
-
- public MockExtraPackage(
- SdkSource source,
- Properties props,
- String vendor,
- String path,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(
- source,
- props,
- vendor,
- path,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
- }
-
- public MockExtraPackage(
- SdkSource source,
- Properties props,
- String vendor,
- String path,
- int revision) {
- super(
- source,
- props, // props,
- vendor,
- path,
- revision,
- null, // license,
- "desc", // description,
- "url", // descUrl,
- source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
- );
- }
-
- public MockExtraPackage(
- SdkSource source,
- String vendor,
- String path,
- int revision,
- int min_platform_tools_rev) {
- this(source, createProps(min_platform_tools_rev), vendor, path, revision);
- }
-
- private static Properties createProps(int min_platform_tools_rev) {
- Properties props = new Properties();
- props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
- Integer.toString((min_platform_tools_rev)));
- return props;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformPackage.java
deleted file mode 100755
index 1b14a08..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformPackage.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.internal.androidTarget.MockPlatformTarget;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-/**
- * A mock {@link PlatformPackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockPlatformPackage extends PlatformPackage {
-
- private final IAndroidTarget mTarget;
-
- /**
- * Creates a {@link MockPlatformTarget} with the requested API and revision
- * and then a {@link MockPlatformPackage} wrapping it.
- *
- * By design, this package contains one and only one archive.
- */
- public MockPlatformPackage(int apiLevel, int revision) {
- this(null /*source*/, new MockPlatformTarget(apiLevel, revision), null /*props*/);
- }
-
- /**
- * Creates a {@link MockPlatformTarget} with the requested API and revision
- * and then a {@link MockPlatformPackage} wrapping it.
- *
- * Also sets the min-tools-rev of the platform using a major revision integer.
- *
- * By design, this package contains one and only one archive.
- */
- public MockPlatformPackage(int apiLevel, int revision, int min_tools_rev) {
- this(null /*source*/,
- new MockPlatformTarget(apiLevel, revision),
- createProps(min_tools_rev));
- }
-
- /**
- * Creates a {@link MockPlatformTarget} with the requested API and revision
- * and then a {@link MockPlatformPackage} wrapping it.
- *
- * Also sets the min-tools-rev of the platform using a {@link FullRevision}.
- *
- * By design, this package contains one and only one archive.
- */
- public MockPlatformPackage(int apiLevel, int revision, FullRevision min_tools_rev) {
- this(null /*source*/,
- new MockPlatformTarget(apiLevel, revision),
- createProps(min_tools_rev));
- }
-
- public MockPlatformPackage(SdkSource source, int apiLevel, int revision, int min_tools_rev) {
- this(source, new MockPlatformTarget(apiLevel, revision), createProps(min_tools_rev));
- }
-
- /** A little trick to be able to capture the target new after passing it to the super. */
- private MockPlatformPackage(SdkSource source, IAndroidTarget target, Properties props) {
- super(source, target, props);
- mTarget = target;
- }
-
- private static Properties createProps(int min_tools_rev) {
- Properties props = new Properties();
- props.setProperty(PkgProps.MIN_TOOLS_REV, Integer.toString((min_tools_rev)));
- return props;
- }
-
- private static Properties createProps(FullRevision min_tools_rev) {
- Properties props = new Properties();
- props.setProperty(PkgProps.MIN_TOOLS_REV, min_tools_rev.toShortString());
- return props;
- }
-
- public IAndroidTarget getTarget() {
- return mTarget;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
deleted file mode 100755
index 79af428..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-
-/**
- * A mock {@link PlatformToolPackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockPlatformToolPackage extends PlatformToolPackage {
-
- /**
- * Creates a {@link MockPlatformToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockPlatformToolPackage(int revision) {
- this(null /*source*/, revision);
- }
-
- /**
- * Creates a {@link MockPlatformToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockPlatformToolPackage(SdkSource source, int revision) {
- super(
- source, // source,
- null, // props,
- revision,
- null, // license,
- "desc", // description,
- "url", // descUrl,
- source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
- );
- }
-
- /**
- * Creates a {@link MockPlatformToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockPlatformToolPackage(SdkSource source, FullRevision revision) {
- super(
- source, // source,
- FullRevisionPackageTest.createProps(revision), // props,
- revision.getMajor(),
- null, // license,
- "desc", // description,
- "url", // descUrl,
- source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
- );
- }
-}
-
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSourcePackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSourcePackage.java
deleted file mode 100755
index 115b1a1..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSourcePackage.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-
-
-/**
- * A mock {@link MockSourcePackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockSourcePackage extends SourcePackage {
- /**
- * Creates a {@link MockSourcePackage} using the given base platform version
- * and package revision.
- *
- * By design, this package contains one and only one archive.
- */
- public MockSourcePackage(AndroidVersion version, int revision) {
- super(version,
- revision,
- null /*props*/,
- String.format("/sdk/sources/android-%s", version.getApiString()));
- }
-
- /**
- * Creates a {@link MockSourcePackage} using the given version,
- * sdk source and package revision.
- *
- * By design, this package contains one and only one archive.
- */
- public MockSourcePackage(
- SdkSource source,
- AndroidVersion version,
- int revision) {
- super(source,
- version,
- revision,
- null /*props*/,
- String.format("/sdk/sources/android-%s", version.getApiString()));
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
deleted file mode 100755
index c09c081..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-
-
-/**
- * A mock {@link SystemImagePackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockSystemImagePackage extends SystemImagePackage {
- /**
- * Creates a {@link MockSystemImagePackage} using the given base platform version
- * and package revision.
- *
- * By design, this package contains one and only one archive.
- */
- public MockSystemImagePackage(MockPlatformPackage basePlatform, int revision, String abi) {
- super(basePlatform.getAndroidVersion(),
- revision,
- abi,
- null /*props*/,
- String.format("/sdk/system-images/android-%s/%s",
- basePlatform.getAndroidVersion().getApiString(), abi));
- }
-
- /**
- * Creates a {@link MockSystemImagePackage} using the given base platform version,
- * sdk source and package revision.
- *
- * By design, this package contains one and only one archive.
- */
- public MockSystemImagePackage(
- SdkSource source,
- MockPlatformPackage basePlatform,
- int revision,
- String abi) {
- super(source,
- basePlatform.getAndroidVersion(),
- revision,
- abi,
- null /*props*/,
- String.format("/sdk/system-images/android-%s/%s",
- basePlatform.getAndroidVersion().getApiString(), abi));
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockToolPackage.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockToolPackage.java
deleted file mode 100755
index 19fcf54..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockToolPackage.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2009 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.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-/**
- * A mock {@link ToolPackage} for testing.
- *
- * By design, this package contains one and only one archive.
- */
-public class MockToolPackage extends ToolPackage {
-
- /**
- * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockToolPackage(int revision, int min_platform_tools_rev) {
- this(null /*source*/, revision, min_platform_tools_rev);
- }
-
- /**
- * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockToolPackage(SdkSource source, int revision, int minPlatformToolsRev) {
- super(
- source, // source,
- createProps(null /*version*/, minPlatformToolsRev), // props,
- revision,
- null, // license,
- "desc", // description,
- "url", // descUrl,
- source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
- );
- }
-
- /**
- * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockToolPackage(
- SdkSource source,
- FullRevision revision,
- int minPlatformToolsRev) {
- super(
- source, // source,
- createProps(revision, minPlatformToolsRev), // props,
- revision.getMajor(),
- null, // license,
- "desc", // description,
- "url", // descUrl,
- source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
- );
- }
-
- /**
- * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
- * for everything else.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MockToolPackage(
- SdkSource source,
- FullRevision revision,
- FullRevision minPlatformToolsRev) {
- super(
- source, // source,
- createProps(revision, minPlatformToolsRev), // props,
- revision.getMajor(),
- null, // license,
- "desc", // description,
- "url", // descUrl,
- source == null ? "foo" : null // archiveOsPath, null for remote non-instaled pkgs
- );
- }
-
- private static Properties createProps(FullRevision revision, int minPlatformToolsRev) {
- Properties props = FullRevisionPackageTest.createProps(revision);
- props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
- Integer.toString((minPlatformToolsRev)));
- return props;
- }
-
- private static Properties createProps(FullRevision revision, FullRevision minPlatformToolsRev) {
- Properties props = FullRevisionPackageTest.createProps(revision);
- props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
- minPlatformToolsRev.toShortString());
- return props;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionTest.java
deleted file mode 100755
index fb3225d..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.NoPreviewRevision;
-
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-public class NoPreviewRevisionTest extends TestCase {
-
- public final void testNoPreviewRevision() {
- NoPreviewRevision p = new NoPreviewRevision(5);
- assertEquals(5, p.getMajor());
- assertEquals(NoPreviewRevision.IMPLICIT_MINOR_REV, p.getMinor());
- assertEquals(NoPreviewRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(NoPreviewRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("5", p.toShortString());
- assertEquals(p, NoPreviewRevision.parseRevision("5"));
- assertEquals("5.0.0", p.toString());
- assertEquals(p, NoPreviewRevision.parseRevision("5.0.0"));
- assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5, 0, 0, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new NoPreviewRevision(5, 0, 0);
- assertEquals(5, p.getMajor());
- assertEquals(NoPreviewRevision.IMPLICIT_MINOR_REV, p.getMinor());
- assertEquals(NoPreviewRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(NoPreviewRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("5", p.toShortString());
- assertEquals(p, NoPreviewRevision.parseRevision("5"));
- assertEquals("5.0.0", p.toString());
- assertEquals(p, NoPreviewRevision.parseRevision("5.0.0"));
- assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5, 0, 0, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new NoPreviewRevision(6, 7, 0);
- assertEquals(6, p.getMajor());
- assertEquals(7, p.getMinor());
- assertEquals(0, p.getMicro());
- assertEquals(0, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("6.7", p.toShortString());
- assertEquals(p, NoPreviewRevision.parseRevision("6.7"));
- assertEquals("6.7.0", p.toString());
- assertEquals(p, NoPreviewRevision.parseRevision("6.7.0"));
- assertEquals("[6, 7, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[6, 7, 0, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new NoPreviewRevision(10, 11, 12);
- assertEquals(10, p.getMajor());
- assertEquals(11, p.getMinor());
- assertEquals(12, p.getMicro());
- assertEquals(0, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("10.11.12", p.toShortString());
- assertEquals("10.11.12", p.toString());
- assertEquals(p, NoPreviewRevision.parseRevision("10.11.12"));
- assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[10, 11, 12, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new NoPreviewRevision(10, 11, 12);
- assertEquals(10, p.getMajor());
- assertEquals(11, p.getMinor());
- assertEquals(12, p.getMicro());
- assertEquals(NoPreviewRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse (p.isPreview());
- assertEquals("10.11.12", p.toShortString());
- assertEquals("10.11.12", p.toString());
- assertEquals(p, NoPreviewRevision.parseRevision("10.11.12"));
- assertEquals(p, NoPreviewRevision.parseRevision(" 10.11.12"));
- assertEquals(p, NoPreviewRevision.parseRevision("10.11.12 "));
- assertEquals(p, NoPreviewRevision.parseRevision(" 10.11.12 "));
- assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[10, 11, 12, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
- }
-
- public final void testParseError() {
- String errorMsg = null;
- try {
- NoPreviewRevision.parseRevision("not a number");
- fail("NoPreviewRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: not a number", errorMsg);
-
- errorMsg = null;
- try {
- NoPreviewRevision.parseRevision("5 .6 .7");
- fail("NoPreviewRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5 .6 .7", errorMsg);
-
- errorMsg = null;
- try {
- NoPreviewRevision.parseRevision("5.0.0 preview 1");
- fail("NoPreviewRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5.0.0 preview 1", errorMsg);
-
- errorMsg = null;
- try {
- NoPreviewRevision.parseRevision(" 5.1.2 rc 42 ");
- fail("NoPreviewRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5.1.2 rc 42 ", errorMsg);
- }
-
- public final void testCompareTo() {
- NoPreviewRevision a4 = new NoPreviewRevision(4);
- NoPreviewRevision b4 = new NoPreviewRevision(4, 1, 0);
- NoPreviewRevision c5 = new NoPreviewRevision(5);
- NoPreviewRevision d5 = new NoPreviewRevision(5, 1, 0);
-
- NoPreviewRevision t4 = new NoPreviewRevision(4, 0, 0);
- NoPreviewRevision u5 = new NoPreviewRevision(5, 0, 0);
-
- assertEquals(a4, t4); // 4.0.0 == 4.0.0
- assertFalse(a4.equals(b4)); // 4.0.0 != 4.1.0
- assertTrue (a4.compareTo(b4) < 0); // 4.0.0 < 4.1.0
- assertTrue (a4.compareTo(c5) < 0); // 4.0.0 < 5.0.0
- assertTrue (a4.compareTo(d5) < 0); // 4.0.0 < 5.1.0
-
- assertTrue (b4.compareTo(a4) > 0); // 4.1.0 > 4.0.0
- assertTrue (b4.compareTo(c5) < 0); // 4.1.0 < 5.0.0
- assertTrue (b4.compareTo(d5) < 0); // 4.1.0 < 5.1.0
-
- assertEquals(c5, u5); // 5.0.0 == 5.0.0
- assertFalse(c5.equals(d5)); // 5.0.0 != 5.1.0
- assertTrue (c5.compareTo(a4) > 0); // 5.0.0 > 4.0.0
- assertTrue (c5.compareTo(b4) > 0); // 5.0.0 > 4.1.0
- assertTrue (c5.compareTo(d5) < 0); // 5.0.0 < 5.1.0
-
- assertTrue (d5.compareTo(a4) > 0); // 5.1.0 > 4.0.0
- assertTrue (d5.compareTo(b4) > 0); // 5.1.0 > 4.1.0
- assertTrue (d5.compareTo(c5) > 0); // 5.1.0 > 5.0.0
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PackageTest.java
deleted file mode 100755
index eb9819c..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PackageTest.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.archives.ArchFilter;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.sources.SdkRepoSource;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.descriptors.IPkgDesc;
-import com.android.sdklib.repository.descriptors.PkgDesc;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Properties;
-
-import junit.framework.TestCase;
-
-public class PackageTest extends TestCase {
-
- protected static final String LOCAL_ARCHIVE_PATH = "/local/archive/path";
-
- /** Local class used to test the abstract Package class */
- protected static class MockPackage extends MajorRevisionPackage {
- public MockPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOsPath);
- }
-
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public String getListDescription() {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public String getShortDescription() {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- throw new UnsupportedOperationException("abstract method not used in test"); //$NON-NLS-1$
- }
-
- @Override
- public String installId() {
- return "mock-pkg"; //$NON-NLS-1$
- }
-
- @Override
- public IPkgDesc getPkgDesc() {
- return PkgDesc.Builder.newTool(
- new FullRevision(1, 2, 3, 4),
- FullRevision.NOT_SPECIFIED).create();
- }
- }
-
- public void testCreate() throws Exception {
- Properties props = createExpectedProps();
-
- Package p = new MockPackage(
- null, //source
- props,
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- LOCAL_ARCHIVE_PATH //archiveOsPath
- );
-
- testCreatedPackage(p);
- }
-
- public void testSaveProperties() throws Exception {
- Properties expected = createExpectedProps();
-
- Package p = new MockPackage(
- null, //source
- expected,
- -1, //revision
- null, //license
- null, //description
- null, //descUrl
- LOCAL_ARCHIVE_PATH //archiveOsPath
- );
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- /**
- * Sets the properties used by {@link #testCreate()} and
- * {@link #testSaveProperties()}.
- * This is protected and used by derived classes to perform
- * a similar creation test.
- */
- protected Properties createExpectedProps() {
- return createDefaultProps();
- }
-
- /**
- * Similar to {@link #createExpectedProps()} but static so that
- * it can be reused by test not deriving from {@link PackageTest}.
- */
- public static Properties createDefaultProps() {
- Properties props = new Properties();
-
- // Package properties
- props.setProperty(PkgProps.PKG_REVISION, "42");
- props.setProperty(PkgProps.PKG_LICENSE, "The License");
- props.setProperty(PkgProps.PKG_DESC, "Some description.");
- props.setProperty(PkgProps.PKG_DESC_URL, "http://description/url");
- props.setProperty(PkgProps.PKG_LIST_DISPLAY, "Some description.");
- props.setProperty(PkgProps.PKG_RELEASE_NOTE, "Release Note");
- props.setProperty(PkgProps.PKG_RELEASE_URL, "http://release/note");
- props.setProperty(PkgProps.PKG_SOURCE_URL, "http://source/url");
- props.setProperty(PkgProps.PKG_OBSOLETE, "true");
- return props;
- }
-
- /**
- * Tests the values set via {@link #createExpectedProps()} after the
- * package has been created in {@link #testCreate()}.
- * This is protected and used by derived classes to perform
- * a similar creation test.
- */
- protected void testCreatedPackage(Package p) {
- // Package properties
- assertEquals("42", p.getRevision().toShortString());
- assertEquals("<License ref:null, text:The License>", p.getLicense().toString());
- assertEquals("Some description.", p.getDescription());
- assertEquals("http://description/url", p.getDescUrl());
- assertEquals("Release Note", p.getReleaseNote());
- assertEquals("http://release/note", p.getReleaseNoteUrl());
- assertEquals(new SdkRepoSource("http://source/url", null /*uiName*/), p.getParentSource());
- assertTrue(p.isObsolete());
-
- assertNotNull(p.getArchives());
- assertEquals(1, p.getArchives().length);
- Archive a = p.getArchives()[0];
- assertNotNull(a);
- assertEquals(new ArchFilter(null), a.getArchFilter());
- assertEquals(LOCAL_ARCHIVE_PATH, a.getLocalOsPath());
- }
-
- // ----
-
- public void testCompareTo() throws Exception {
- ArrayList<Package> list = new ArrayList<Package>();
- MockPlatformPackage p1;
-
- list.add(p1 = new MockPlatformPackage(1, 2));
- list.add(new MockAddonPackage(p1, 3));
- list.add(new MockSystemImagePackage(p1, 4, "x86"));
- list.add(new MockBrokenPackage(BrokenPackage.MIN_API_LEVEL_NOT_SPECIFIED, 1));
- list.add(new MockExtraPackage("vendor", "path", 5, 6));
- list.add(new MockToolPackage(7, 8));
-
- Collections.sort(list);
-
- assertEquals(
- "[Android SDK Tools, revision 7, " +
- "SDK Platform Android android-1, API 1, revision 2, " +
- "Intel x86 Atom System Image, Android API 1, revision 4, " +
- "addon, Android API 1, revision 3, " +
- "Broken package for API 1, " +
- "Vendor Path, revision 5]",
- Arrays.toString(list.toArray()));
- }
-
- public void testGetPropertyInt() {
- Properties p = new Properties();
-
- assertEquals(42, Package.getPropertyInt(p, "key", 42));
-
- p.setProperty("key", "");
- assertEquals(43, Package.getPropertyInt(p, "key", 43));
-
- p.setProperty("key", "I am not a number");
- assertEquals(44, Package.getPropertyInt(p, "key", 44));
-
- p.setProperty("key", "6");
- assertEquals(6, Package.getPropertyInt(p, "key", 45));
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
deleted file mode 100755
index 6a579e9..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.internal.androidTarget.MockPlatformTarget;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-public class PlatformPackageTest extends MinToolsPackageTest {
-
- private static class PlatformPackageWithFakeArchive extends PlatformPackage {
- protected PlatformPackageWithFakeArchive(IAndroidTarget target, Properties props) {
- super(target, props);
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
- }
- }
-
- private PlatformPackage createPlatformPackage(Properties props) {
- PlatformPackage p = new PlatformPackageWithFakeArchive(
- new MockPlatformTarget(5 /*apiLevel*/, 1 /*revision*/),
- props);
-
- return p;
- }
-
- @Override
- protected Properties createExpectedProps() {
- Properties props = super.createExpectedProps();
-
- // PlatformPackage properties
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
- props.setProperty(PkgProps.PLATFORM_VERSION, "android-5");
- props.setProperty(PkgProps.PLATFORM_INCLUDED_ABI, "armeabi");
-
- return props;
- }
-
- protected void testCreatedPlatformPackage(PlatformPackage p) {
- super.testCreatedPackage(p);
-
- // Package properties
- assertEquals("API 5", p.getAndroidVersion().toString());
- assertEquals("armeabi", p.getIncludedAbi());
- }
-
- // ----
-
- @Override
- public final void testCreate() {
- Properties props = createExpectedProps();
- PlatformPackage p = createPlatformPackage(props);
-
- testCreatedPlatformPackage(p);
- }
-
- @Override
- public void testSaveProperties() {
- Properties expected = createExpectedProps();
- PlatformPackage p = createPlatformPackage(expected);
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- public void testInstallId() {
- Properties props = createExpectedProps();
- PlatformPackage p = createPlatformPackage(props);
-
- assertEquals("android-5", p.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformSystemImagePackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformSystemImagePackageTest.java
deleted file mode 100755
index b6852e6..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformSystemImagePackageTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-public class PlatformSystemImagePackageTest extends PackageTest {
-
- private static class SysImgPackageFakeArchive extends SystemImagePackage {
- protected SysImgPackageFakeArchive(
- AndroidVersion platformVersion,
- int revision,
- String abi,
- Properties props) {
- super(platformVersion,
- revision,
- abi,
- props,
- String.format("/sdk/system-images/android-%s/%s",
- platformVersion.getApiString(), abi));
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
- }
- }
-
- private SystemImagePackage createSystemImagePackage(Properties props)
- throws AndroidVersionException {
- SystemImagePackage p = new SysImgPackageFakeArchive(
- new AndroidVersion(props),
- 1 /*revision*/,
- null /*abi*/,
- props);
- return p;
- }
-
- @Override
- protected Properties createExpectedProps() {
- Properties props = super.createExpectedProps();
-
- // SystemImagePackage properties
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
- props.setProperty(PkgProps.SYS_IMG_ABI, "armeabi-v7a");
- props.setProperty(PkgProps.SYS_IMG_TAG_ID, "default");
- props.setProperty(PkgProps.SYS_IMG_TAG_DISPLAY, "Default");
-
- return props;
- }
-
- protected void testCreatedSystemImagePackage(SystemImagePackage p) {
- super.testCreatedPackage(p);
-
- // SystemImagePackage properties
- assertEquals("API 5", p.getAndroidVersion().toString());
- assertEquals("armeabi-v7a", p.getAbi());
- }
-
- // ----
-
- @Override
- public final void testCreate() throws Exception {
- Properties props = createExpectedProps();
- SystemImagePackage p = createSystemImagePackage(props);
-
- testCreatedSystemImagePackage(p);
- }
-
- @Override
- public void testSaveProperties() throws Exception {
- Properties expected = createExpectedProps();
- SystemImagePackage p = createSystemImagePackage(expected);
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- public void testSameItemAs() throws Exception {
- Properties props1 = createExpectedProps();
- SystemImagePackage p1 = createSystemImagePackage(props1);
- assertTrue(p1.sameItemAs(p1));
-
- // different abi, same version
- Properties props2 = new Properties(props1);
- props2.setProperty(PkgProps.SYS_IMG_ABI, "x86");
- SystemImagePackage p2 = createSystemImagePackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // different abi, different version
- props2.setProperty(PkgProps.VERSION_API_LEVEL, "6");
- p2 = createSystemImagePackage(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
-
- // same abi, different version
- Properties props3 = new Properties(props1);
- props3.setProperty(PkgProps.VERSION_API_LEVEL, "6");
- SystemImagePackage p3 = createSystemImagePackage(props3);
- assertFalse(p1.sameItemAs(p3));
- assertFalse(p3.sameItemAs(p1));
- }
-
- public void testInstallId() throws Exception {
- Properties props = createExpectedProps();
- SystemImagePackage p = createSystemImagePackage(props);
-
- assertEquals("sys-img-armeabi-v7a-android-5", p.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformToolPackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformToolPackageTest.java
deleted file mode 100755
index d82b5f9..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PlatformToolPackageTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-import junit.framework.TestCase;
-
-public class PlatformToolPackageTest extends TestCase {
-
- private static class PlatformToolPackageFakeArchive extends PlatformToolPackage {
- protected PlatformToolPackageFakeArchive(Properties props) {
- super(null, // source
- props,
- 0, //ignored, the one from props will be used
- null, //license
- null, //description
- null, //descUrl
- "/sdk/tools");
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return super.initializeArchives(props, PackageTest.LOCAL_ARCHIVE_PATH);
- }
- }
-
- private Properties createExpectedProps(boolean isPreview) {
- Properties props = PackageTest.createDefaultProps();
-
- props.setProperty(PkgProps.PKG_REVISION,
- new FullRevision(1, 2, 3, isPreview ? 4 : 0).toShortString());
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
-
- return props;
- }
-
- // ----
-
- public void testInstallId() throws Exception {
- Properties props1 = createExpectedProps(true);
- PlatformToolPackage p1 = new PlatformToolPackageFakeArchive(props1);
- assertEquals("platform-tools-preview", p1.installId());
-
- Properties props2 = createExpectedProps(false);
- PlatformToolPackage p2 = new PlatformToolPackageFakeArchive(props2);
- assertEquals("platform-tools", p2.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PreciseRevisionTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PreciseRevisionTest.java
deleted file mode 100644
index b07d422..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/PreciseRevisionTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.repository.PreciseRevision;
-
-import org.junit.Assert;
-import junit.framework.TestCase;
-
-import java.util.Arrays;
-
-public class PreciseRevisionTest extends TestCase {
-
- public final void testPreciseRevision() {
-
- Assert.assertEquals("5", PreciseRevision.parseRevision("5").toString());
- assertEquals("5.0", PreciseRevision.parseRevision("5.0").toString());
- assertEquals("5.0.0", PreciseRevision.parseRevision("5.0.0").toString());
- assertEquals("5.1.4", PreciseRevision.parseRevision("5.1.4").toString());
-
- PreciseRevision p = new PreciseRevision(5);
- assertEquals(5, p.getMajor());
- assertEquals(PreciseRevision.IMPLICIT_MINOR_REV, p.getMinor());
- assertEquals(PreciseRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(PreciseRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse(p.isPreview());
- assertEquals("5", p.toShortString());
- assertEquals(p, PreciseRevision.parseRevision("5"));
- assertEquals("5", p.toString());
- assertEquals(p, PreciseRevision.parseRevision("5"));
- assertEquals("[5]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new PreciseRevision(5, 0);
- assertEquals(5, p.getMajor());
- assertEquals(0, p.getMinor());
- assertEquals(PreciseRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(PreciseRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse(p.isPreview());
- assertEquals("5.0", p.toShortString());
- assertEquals(new PreciseRevision(5), PreciseRevision.parseRevision("5"));
- assertEquals("5.0", p.toString());
- assertEquals(p, PreciseRevision.parseRevision("5.0"));
- assertEquals("[5, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new PreciseRevision(5, 0, 0);
- assertEquals(5, p.getMajor());
- assertEquals(0, p.getMinor());
- assertEquals(0, p.getMicro());
- assertEquals(PreciseRevision.NOT_A_PREVIEW, p.getPreview());
- assertFalse(p.isPreview());
- assertEquals("5.0.0", p.toShortString());
- assertEquals(new PreciseRevision(5), PreciseRevision.parseRevision("5"));
- assertEquals("5.0.0", p.toString());
- assertEquals(p, PreciseRevision.parseRevision("5.0.0"));
- assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new PreciseRevision(5, 0, 0, 6);
- assertEquals(5, p.getMajor());
- assertEquals(PreciseRevision.IMPLICIT_MINOR_REV, p.getMinor());
- assertEquals(PreciseRevision.IMPLICIT_MICRO_REV, p.getMicro());
- assertEquals(6, p.getPreview());
- assertTrue(p.isPreview());
- assertEquals("5.0.0 rc6", p.toShortString());
- assertEquals("5.0.0 rc6", p.toString());
- assertEquals(p, PreciseRevision.parseRevision("5.0.0 rc6"));
- assertEquals("5.0.0-rc6", PreciseRevision.parseRevision("5.0.0-rc6").toString());
- assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[5, 0, 0, 6]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new PreciseRevision(6, 7, 0);
- assertEquals(6, p.getMajor());
- assertEquals(7, p.getMinor());
- assertEquals(0, p.getMicro());
- assertEquals(0, p.getPreview());
- assertFalse(p.isPreview());
- assertEquals("6.7.0", p.toShortString());
- assertFalse(p.equals(PreciseRevision.parseRevision("6.7")));
- assertEquals(new PreciseRevision(6, 7), PreciseRevision.parseRevision("6.7"));
- assertEquals("6.7.0", p.toString());
- assertEquals(p, PreciseRevision.parseRevision("6.7.0"));
- assertEquals("[6, 7, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[6, 7, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new PreciseRevision(10, 11, 12, PreciseRevision.NOT_A_PREVIEW);
- assertEquals(10, p.getMajor());
- assertEquals(11, p.getMinor());
- assertEquals(12, p.getMicro());
- assertEquals(0, p.getPreview());
- assertFalse(p.isPreview());
- assertEquals("10.11.12", p.toShortString());
- assertEquals("10.11.12", p.toString());
- assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[10, 11, 12, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
-
- p = new PreciseRevision(10, 11, 12, 13);
- assertEquals(10, p.getMajor());
- assertEquals(11, p.getMinor());
- assertEquals(12, p.getMicro());
- assertEquals(13, p.getPreview());
- assertTrue (p.isPreview());
- assertEquals("10.11.12 rc13", p.toShortString());
- assertEquals("10.11.12 rc13", p.toString());
- assertEquals(p, PreciseRevision.parseRevision("10.11.12 rc13"));
- assertEquals(p, PreciseRevision.parseRevision(" 10.11.12 rc13"));
- assertEquals(p, PreciseRevision.parseRevision("10.11.12 rc13 "));
- assertEquals(p, PreciseRevision.parseRevision(" 10.11.12 rc13 "));
- assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
- assertEquals("[10, 11, 12, 13]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
- }
-
- public final void testParseError() {
- String errorMsg = null;
- try {
- PreciseRevision.parseRevision("not a number");
- fail("PreciseRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: not a number", errorMsg);
-
- errorMsg = null;
- try {
- PreciseRevision.parseRevision("5 .6 .7");
- fail("PreciseRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5 .6 .7", errorMsg);
-
- errorMsg = null;
- try {
- PreciseRevision.parseRevision("5.0.0 preview 1");
- fail("PreciseRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5.0.0 preview 1", errorMsg);
-
- errorMsg = null;
- try {
- PreciseRevision.parseRevision(" 5.1.2 rc 42 ");
- fail("PreciseRevision.parseRevision should thrown NumberFormatException");
- } catch (NumberFormatException e) {
- errorMsg = e.getMessage();
- }
- assertEquals("Invalid revision: 5.1.2 rc 42 ", errorMsg);
- }
-
- public final void testCompareTo() {
- PreciseRevision s4 = new PreciseRevision(4);
- PreciseRevision i4 = new PreciseRevision(4);
- PreciseRevision g5 = new PreciseRevision(5, 1, 0, 6);
- PreciseRevision y5 = new PreciseRevision(5);
- PreciseRevision c5 = new PreciseRevision(5, 1, 0, 6);
- PreciseRevision o5 = new PreciseRevision(5, 0, 0, 7);
- PreciseRevision p5 = new PreciseRevision(5, 1, 0, 0);
-
- assertEquals(s4, i4); // 4.0.0-0 == 4.0.0-0
- assertEquals(g5, c5); // 5.1.0-6 == 5.1.0-6
-
- assertFalse(y5.equals(p5)); // 5.0.0-0 != 5.1.0-0
- assertFalse(g5.equals(p5)); // 5.1.0-6 != 5.1.0-0
- assertTrue (s4.compareTo(i4) == 0); // 4.0.0-0 == 4.0.0-0
- assertTrue (s4.compareTo(y5) < 0); // 4.0.0-0 < 5.0.0-0
- assertTrue (y5.compareTo(y5) == 0); // 5.0.0-0 == 5.0.0-0
- assertTrue (y5.compareTo(p5) < 0); // 5.0.0-0 < 5.1.0-0
- assertTrue (o5.compareTo(y5) < 0); // 5.0.0-7 < 5.0.0-0
- assertTrue (p5.compareTo(p5) == 0); // 5.1.0-0 == 5.1.0-0
- assertTrue (c5.compareTo(p5) < 0); // 5.1.0-6 < 5.1.0-0
- assertTrue (p5.compareTo(c5) > 0); // 5.1.0-0 > 5.1.0-6
- assertTrue (p5.compareTo(o5) > 0); // 5.1.0-0 > 5.0.0-7
- assertTrue (c5.compareTo(o5) > 0); // 5.1.0-6 > 5.0.0-7
- assertTrue (o5.compareTo(o5) == 0); // 5.0.0-7 > 5.0.0-7
- }
-}
\ No newline at end of file
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SourcePackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
deleted file mode 100755
index 08d1ef9..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-public class SourcePackageTest extends PackageTest {
-
- private static class SourcePackageFakeArchive extends SourcePackage {
- protected SourcePackageFakeArchive(
- AndroidVersion platformVersion,
- int revision,
- Properties props) {
- super(platformVersion,
- revision,
- props,
- String.format("/sdk/sources/android-%s", platformVersion.getApiString()));
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return super.initializeArchives(props, LOCAL_ARCHIVE_PATH);
- }
- }
-
- private SourcePackage createSourcePackageTest(Properties props) throws AndroidVersionException {
- SourcePackage p = new SourcePackageFakeArchive(
- new AndroidVersion(props),
- 1 /*revision*/,
- props);
- return p;
- }
-
- @Override
- protected Properties createExpectedProps() {
- Properties props = super.createExpectedProps();
-
- // SourcePackageTest properties
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
-
- return props;
- }
-
- protected void testCreatedSourcePackageTest(SourcePackage p) {
- super.testCreatedPackage(p);
-
- // SourcePackageTest properties
- assertEquals("API 5", p.getAndroidVersion().toString());
- }
-
- // ----
-
- @Override
- public final void testCreate() throws Exception {
- Properties props = createExpectedProps();
- SourcePackage p = createSourcePackageTest(props);
-
- testCreatedSourcePackageTest(p);
- }
-
- @Override
- public void testSaveProperties() throws Exception {
- Properties expected = createExpectedProps();
- SourcePackage p = createSourcePackageTest(expected);
-
- Properties actual = new Properties();
- p.saveProperties(actual);
-
- assertEquals(expected, actual);
- }
-
- public void testSameItemAs() throws Exception {
- Properties props1 = createExpectedProps();
- SourcePackage p1 = createSourcePackageTest(props1);
- assertTrue(p1.sameItemAs(p1));
-
- // different version
- Properties props2 = new Properties(props1);
- props2.setProperty(PkgProps.VERSION_API_LEVEL, "6");
- SourcePackage p2 = createSourcePackageTest(props2);
- assertFalse(p1.sameItemAs(p2));
- assertFalse(p2.sameItemAs(p1));
- }
-
- public void testInstallId() throws Exception {
- Properties props = createExpectedProps();
- SourcePackage p = createSourcePackageTest(props);
-
- assertEquals("source-5", p.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ToolPackageTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ToolPackageTest.java
deleted file mode 100755
index 8e9f49a..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ToolPackageTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.packages;
-
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-
-import java.util.Properties;
-
-import junit.framework.TestCase;
-
-public class ToolPackageTest extends TestCase {
-
- private static class ToolPackageFakeArchive extends ToolPackage {
- protected ToolPackageFakeArchive(Properties props) {
- super(null, // source
- props,
- 0, //ignored, the one from props will be used
- null, //license
- null, //description
- null, //descUrl
- "/sdk/tools");
- }
-
- @Override
- protected Archive[] initializeArchives(
- Properties props,
- String archiveOsPath) {
- return super.initializeArchives(props, PackageTest.LOCAL_ARCHIVE_PATH);
- }
- }
-
- private Properties createExpectedProps(boolean isPreview) {
- Properties props = PackageTest.createDefaultProps();
-
- props.setProperty(PkgProps.PKG_REVISION,
- new FullRevision(1, 2, 3, isPreview ? 4 : 0).toShortString());
- props.setProperty(PkgProps.VERSION_API_LEVEL, "5");
-
- return props;
- }
-
- // ----
-
- public void testInstallId() throws Exception {
- Properties props1 = createExpectedProps(true);
- ToolPackage p1 = new ToolPackageFakeArchive(props1);
- assertEquals("tools-preview", p1.installId());
-
- Properties props2 = createExpectedProps(false);
- ToolPackage p2 = new ToolPackageFakeArchive(props2);
- assertEquals("tools", p2.installId());
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
deleted file mode 100755
index d05c6ee..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
+++ /dev/null
@@ -1,1156 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.internal.repository.sources;
-
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.MockEmptySdkManager;
-import com.android.sdklib.internal.repository.MockMonitor;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.IMinToolsDependency;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Document;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link SdkAddonSource}.
- */
-public class SdkAddonSourceTest extends TestCase {
-
- /**
- * An internal helper class to give us visibility to the protected members we want
- * to test.
- */
- private static class MockSdkAddonSource extends SdkAddonSource {
- public MockSdkAddonSource() {
- super("fake-url", null /*uiName*/);
- }
-
- public Document _findAlternateToolsXml(InputStream xml) {
- return super.findAlternateToolsXml(xml);
- }
-
- public boolean _parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
- return super.parsePackages(doc, nsUri, monitor);
- }
-
- public int _getXmlSchemaVersion(InputStream xml) {
- return super.getXmlSchemaVersion(xml);
- }
-
- public String _validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
- return super.validateXml(xml, url, version, outError, validatorFound);
- }
-
- public Document _getDocument(InputStream xml, ITaskMonitor monitor) {
- return super.getDocument(xml, monitor);
- }
-
- }
-
- private MockSdkAddonSource mSource;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mSource = new MockSdkAddonSource();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- mSource = null;
- }
-
- public void testFindAlternateToolsXml_Errors() throws Exception {
- // Support null as input
- Document result = mSource._findAlternateToolsXml(null);
- assertNull(result);
-
- // Support an empty input
- String str = "";
- ByteArrayInputStream input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertNull(result);
-
- // Support a random string as input
- str = "Some random string, not even HTML nor XML";
- input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertNull(result);
-
- // Support an HTML input, e.g. a typical 404 document as returned by DL
- str = "<html><head> " +
- "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"> " +
- "<title>404 Not Found</title> " + "<style><!--" + "body {font-family: arial,sans-serif}" +
- "div.nav { ... blah blah more css here ... color: green}" +
- "//--></style> " + "<script><!--" + "var rc=404;" + "//-->" + "</script> " + "</head> " +
- "<body text=#000000 bgcolor=#ffffff> " +
- "<table border=0 cellpadding=2 cellspacing=0 width=100%><tr><td rowspan=3 width=1% nowrap> " +
- "<b><font face=times color=#0039b6 size=10>G</font><font face=times color=#c41200 size=10>o</font><font face=times color=#f3c518 size=10>o</font><font face=times color=#0039b6 size=10>g</font><font face=times color=#30a72f size=10>l</font><font face=times color=#c41200 size=10>e</font> </b> " +
- "<td> </td></tr> " +
- "<tr><td bgcolor=\"#3366cc\"><font face=arial,sans-serif color=\"#ffffff\"><b>Error</b></td></tr> " +
- "<tr><td> </td></tr></table> " + "<blockquote> " + "<H1>Not Found</H1> " +
- "The requested URL <code>/404</code> was not found on this server." + " " + "<p> " +
- "</blockquote> " +
- "<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=\"#3366cc\"><img alt=\"\" width=1 height=4></td></tr></table> " +
- "</body></html> ";
- input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertNull(result);
-
- // Support some random XML document, totally unrelated to our sdk-repository schema
- str = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
- "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" +
- " package=\"some.cool.app\" android:versionName=\"1.6.04\" android:versionCode=\"1604\">" +
- " <application android:label=\"@string/app_name\" android:icon=\"@drawable/icon\"/>" +
- "</manifest>";
- input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertNull(result);
- }
-
- /**
- * Validate that findAlternateToolsXml doesn't work for addon even
- * when trying to load a valid addon xml.
- */
- public void testFindAlternateToolsXml_1() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_1.xml");
-
- Document result = mSource._findAlternateToolsXml(xmlStream);
- assertNull(result);
- }
-
- /**
- * Validate we can load a valid add-on schema version 1
- */
- public void testLoadAddonXml_1() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_1.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(1, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonConstants.getSchemaUri(1), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found My First add-on, Android API 1, revision 1\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found G USB Driver, revision 43 (Obsolete)\n" +
- "Found Android Vendor Extra API Dep, revision 2 (Obsolete)\n" +
- "Found Unknown Extra, revision 2 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 11 packages with each at least
- // one archive.
- Package[] pkgs = mSource.getPackages();
- assertEquals(6, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the extra packages path, vendor, install folder
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- extraPaths.add(ep.getPath());
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
- }
- }
- assertEquals(
- "[extra_api_dep, usb_driver, extra0000005f]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[android_vendor/android_vendor, " +
- "g/g, " +
- "vendor0000005f/____]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/g/usb_driver, " +
- "SDK/extras/vendor0000005f/extra0000005f]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[This add-on has no libraries, " +
- "My Second add-on, " +
- "My First add-on, " +
- "Android Vendor Extra API Dep (Obsolete), " +
- "G USB Driver (Obsolete), " +
- "Unknown Extra (Obsolete)]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can still load a valid add-on schema version 2
- */
- public void testLoadAddonXml_2() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_2.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(2, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonConstants.getSchemaUri(2), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found My First add-on, Android API 1, revision 1\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found G USB Driver, revision 43 (Obsolete)\n" +
- "Found Android Vendor Extra API Dep, revision 2 (Obsolete)\n" +
- "Found Unknown Extra, revision 2 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 11 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(6, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
- }
- }
- assertEquals(
- "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
- "Pair [first=0, second=0], " + // for #2 "My Second add-on"
- "Pair [first=5, second=0]]", // for #1 "My First add-on"
- Arrays.toString(layoutlibVers.toArray()));
-
-
- // Check the extra packages path, vendor, install folder
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- extraPaths.add(ep.getPath());
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
- }
- }
- assertEquals(
- "[extra_api_dep, usb_driver, extra0000005f]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[android_vendor/android_vendor, " +
- "g/g, " +
- "vendor0000005f/____]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/g/usb_driver, " +
- "SDK/extras/vendor0000005f/extra0000005f]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[This add-on has no libraries, " +
- "My Second add-on, " +
- "My First add-on, " +
- "Android Vendor Extra API Dep (Obsolete), " +
- "G USB Driver (Obsolete), " +
- "Unknown Extra (Obsolete)]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can load a valid add-on schema version 3
- */
- public void testLoadAddonXml_3() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_3.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(3, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonConstants.getSchemaUri(3), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found My First add-on, Android API 1, revision 1\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found G USB Driver, revision 43 (Obsolete)\n" +
- "Found Android Vendor Extra API Dep, revision 2 (Obsolete)\n" +
- "Found Unknown Extra, revision 2 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 6 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(6, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
- }
- }
- assertEquals(
- "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
- "Pair [first=0, second=0], " + // for #2 "My Second add-on"
- "Pair [first=5, second=0]]", // for #1 "My First add-on"
- Arrays.toString(layoutlibVers.toArray()));
-
-
- // Check the extra packages: path, vendor, install folder, old-paths
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
- assertEquals(
- "[extra_api_dep [path1, old_path2, oldPath3], " +
- "usb_driver [], " +
- "extra0000005f []]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[android_vendor/android_vendor, " +
- "g/g, " +
- "vendor0000005f/____]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/g/usb_driver, " +
- "SDK/extras/vendor0000005f/extra0000005f]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- assertEquals(
- "[[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
- "[], " +
- "[]]",
- Arrays.toString(extraFilePaths.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[This add-on has no libraries, " +
- "My Second add-on, " +
- "My First add-on, " +
- "Android Vendor Extra API Dep (Obsolete), " +
- "G USB Driver (Obsolete), " +
- "Unknown Extra (Obsolete)]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can load a valid add-on schema version 4
- */
- public void testLoadAddonXml_4() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_4.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(4, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonConstants.getSchemaUri(4), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found My First add-on, Android API 1, revision 1\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found Random name, not an id!, revision 43 (Obsolete)\n" +
- "Found Yet another extra, by Android, revision 2\n" +
- "Found . -..- - .-. .-, revision 2 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 6 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(6, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the addon packages: vendor/name id vs display
- ArrayList<String> addonNames = new ArrayList<String>();
- ArrayList<String> addonVendors = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- AddonPackage ap = (AddonPackage) p;
- addonNames.add(ap.getNameId() + "/" + ap.getDisplayName());
- addonVendors.add(ap.getVendorId() + "/" + ap.getDisplayVendor());
- }
- }
- // Addons are sorted by addon/vendor id and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[no_libs/This add-on has no libraries, " +
- "My_Second_add-on/My Second add-on, " +
- "My_First_add-on/My First add-on]",
- Arrays.toString(addonNames.toArray()));
- assertEquals(
- "[Joe_Bar/Joe Bar, " +
- "John_Deer/John Deer, " +
- "John_Doe/John Doe]",
- Arrays.toString(addonVendors.toArray()));
-
- // Check the layoutlib of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
- }
- }
- assertEquals(
- "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
- "Pair [first=0, second=0], " + // for #2 "My Second add-on"
- "Pair [first=5, second=0]]", // for #1 "My First add-on"
- Arrays.toString(layoutlibVers.toArray()));
-
-
- // Check the extra packages: path, vendor, install folder, old-paths
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
- // Extras are sorted by vendor-id/path and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[extra0000005f [], " + // for extra #3
- "extra_api_dep [path1, old_path2, oldPath3], " + // for extra #2
- "usb_driver []]", // for extra #1
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[____/____, " +
- "android_vendor/Android Vendor, " +
- "cyclop/The big bus]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/____/extra0000005f, " +
- "SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/cyclop/usb_driver]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- assertEquals(
- "[[], " +
- "[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
- "[]]",
- Arrays.toString(extraFilePaths.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[This add-on has no libraries, " +
- "My Second add-on, " +
- "My First add-on, " +
- ". -..- - .-. .- (Obsolete), " +
- "Yet another extra, by Android, " +
- "Random name, not an id! (Obsolete)]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can load a valid add-on schema version 5
- */
- public void testLoadAddonXml_5() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_5.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(5, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonConstants.getSchemaUri(5), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found My First add-on, Android API 1, revision 1\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found Random name, not an id!, revision 43 (Obsolete)\n" +
- "Found Yet another extra, by Android, revision 2\n" +
- "Found . -..- - .-. .-, revision 2 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 6 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(6, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the addon packages: vendor/name id vs display
- ArrayList<String> addonNames = new ArrayList<String>();
- ArrayList<String> addonVendors = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- AddonPackage ap = (AddonPackage) p;
- addonNames.add(ap.getNameId() + "/" + ap.getDisplayName());
- addonVendors.add(ap.getVendorId() + "/" + ap.getDisplayVendor());
- }
- }
- // Addons are sorted by addon/vendor id and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[no_libs/This add-on has no libraries, " +
- "My_Second_add-on/My Second add-on, " +
- "My_First_add-on/My First add-on]",
- Arrays.toString(addonNames.toArray()));
- assertEquals(
- "[Joe_Bar/Joe Bar, " +
- "John_Deer/John Deer, " +
- "John_Doe/John Doe]",
- Arrays.toString(addonVendors.toArray()));
-
- // Check the layoutlib of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
- }
- }
- assertEquals(
- "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
- "Pair [first=0, second=0], " + // for #2 "My Second add-on"
- "Pair [first=5, second=0]]", // for #1 "My First add-on"
- Arrays.toString(layoutlibVers.toArray()));
-
-
- // Check the extra packages: path, vendor, install folder, old-paths
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
- // Extras are sorted by vendor-id/path and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[extra0000005f [], " + // for extra #3
- "extra_api_dep [path1, old_path2, oldPath3], " + // for extra #2
- "usb_driver []]", // for extra #1
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[____/____, " +
- "android_vendor/Android Vendor, " +
- "cyclop/The big bus]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/____/extra0000005f, " +
- "SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/cyclop/usb_driver]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- assertEquals(
- "[[], " +
- "[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
- "[]]",
- Arrays.toString(extraFilePaths.toArray()));
-
-
- // Check the min-tools-rev
- ArrayList<String> minToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinToolsDependency) {
- minToolsRevs.add(p.getListDescription() + ": " +
- ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[. -..- - .-. .- (Obsolete): 3.0.1, " +
- "Yet another extra, by Android: 3, " +
- "Random name, not an id! (Obsolete): 3.2.1 rc42]",
- Arrays.toString(minToolsRevs.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[This add-on has no libraries, " +
- "My Second add-on, " +
- "My First add-on, " +
- ". -..- - .-. .- (Obsolete), " +
- "Yet another extra, by Android, " +
- "Random name, not an id! (Obsolete)]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can load a valid add-on schema version 6
- */
- public void testLoadAddonXml_6() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_6.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(6, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonConstants.getSchemaUri(6), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found My First add-on, Android API 1, revision 1\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found Random name, not an id!, revision 43.42.41 (Obsolete)\n" +
- "Found Yet another extra, by Android, revision 2.0.1\n" +
- "Found . -..- - .-. .-, revision 2 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 6 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(6, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the addon packages: vendor/name id vs display
- ArrayList<String> addonNames = new ArrayList<String>();
- ArrayList<String> addonVendors = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- AddonPackage ap = (AddonPackage) p;
- addonNames.add(ap.getNameId() + "/" + ap.getDisplayName());
- addonVendors.add(ap.getVendorId() + "/" + ap.getDisplayVendor());
- }
- }
- // Addons are sorted by addon/vendor id and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[no_libs/This add-on has no libraries, " +
- "My_Second_add-on/My Second add-on, " +
- "My_First_add-on/My First add-on]",
- Arrays.toString(addonNames.toArray()));
- assertEquals(
- "[Joe_Bar/Joe Bar, " +
- "John_Deer/John Deer, " +
- "John_Doe/John Doe]",
- Arrays.toString(addonVendors.toArray()));
-
- // Check the layoutlib of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
- }
- }
- assertEquals(
- "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
- "Pair [first=0, second=0], " + // for #2 "My Second add-on"
- "Pair [first=5, second=0]]", // for #1 "My First add-on"
- Arrays.toString(layoutlibVers.toArray()));
-
-
- // Check the extra packages: path, vendor, install folder, old-paths
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
- // Extras are sorted by vendor-id/path and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[extra0000005f [], " + // for extra #3
- "extra_api_dep [path1, old_path2, oldPath3], " + // for extra #2
- "usb_driver []]", // for extra #1
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[____/____, " +
- "android_vendor/Android Vendor, " +
- "cyclop/The big bus]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/____/extra0000005f, " +
- "SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/cyclop/usb_driver]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- assertEquals(
- "[[], " +
- "[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
- "[]]",
- Arrays.toString(extraFilePaths.toArray()));
-
-
- // Check the min-tools-rev
- ArrayList<String> minToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinToolsDependency) {
- minToolsRevs.add(p.getListDescription() + ": " +
- ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[. -..- - .-. .- (Obsolete): 3.0.1, " +
- "Yet another extra, by Android: 3, " +
- "Random name, not an id! (Obsolete): 3.2.1 rc42]",
- Arrays.toString(minToolsRevs.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[This add-on has no libraries, " +
- "My Second add-on, " +
- "My First add-on, " +
- ". -..- - .-. .- (Obsolete), " +
- "Yet another extra, by Android, " +
- "Random name, not an id! (Obsolete)]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can load a valid add-on schema version 6
- */
- public void testLoadAddonXml_7() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_7.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(7, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkAddonConstants.getSchemaUri(7), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found My First Add-on for API 5, rev 0, revision 1\n" +
- "Found My Second Add-on for API 2.r42, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found Random name, not an id!, revision 43.42.41 (Obsolete)\n" +
- "Found Another extra with min-API 42, revision 2.0.1\n" +
- "Found Extra '____' for API _$1_, by _%2_, revision 2 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 6 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(6, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the addon packages: vendor/name id vs display
- ArrayList<String> addonNames = new ArrayList<String>();
- ArrayList<String> addonVendors = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- AddonPackage ap = (AddonPackage) p;
- addonNames.add(ap.getNameId() + "/" + ap.getDisplayName());
- addonVendors.add(ap.getVendorId() + "/" + ap.getDisplayVendor());
- }
- }
- // Addons are sorted by addon/vendor id and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[no_libs/This add-on has no libraries, " +
- "My_Second_add-on/My Second add-on, " +
- "My_First_add-on/My First add-on]",
- Arrays.toString(addonNames.toArray()));
- assertEquals(
- "[Joe_Bar/Joe Bar, " +
- "John_Deer/John Deer, " +
- "John_Doe/John Doe]",
- Arrays.toString(addonVendors.toArray()));
-
- // Check the layoutlib of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- for (Package p : pkgs) {
- if (p instanceof AddonPackage) {
- layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
- }
- }
- assertEquals(
- "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
- "Pair [first=0, second=0], " + // for #2 "My Second add-on"
- "Pair [first=5, second=0]]", // for #1 "My First add-on"
- Arrays.toString(layoutlibVers.toArray()));
-
-
- // Check the extra packages: path, vendor, install folder, old-paths
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
- // Extras are sorted by vendor-id/path and thus their order differs from the
- // XML or the parsed package list.
- assertEquals(
- "[extra0000005f [], " + // for extra #3
- "extra_api_dep [path1, old_path2, oldPath3], " + // for extra #2
- "usb_driver []]", // for extra #1
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[____/____, " +
- "android_vendor/Android Vendor, " +
- "cyclop/The big bus]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/____/extra0000005f, " +
- "SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/cyclop/usb_driver]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- assertEquals(
- "[[], " +
- "[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
- "[]]",
- Arrays.toString(extraFilePaths.toArray()));
-
-
- // Check the min-tools-rev
- ArrayList<String> minToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinToolsDependency) {
- minToolsRevs.add(p.getListDescription() + ": " +
- ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[Extra '____' for API _$1_, by _%2_ (Obsolete): 3.0.1, " +
- "Another extra with min-API 42: 3, " +
- "Random name, not an id! (Obsolete): 3.2.1 rc42]",
- Arrays.toString(minToolsRevs.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[This add-on has no libraries, " +
- "My Second Add-on for API 2.r42, " +
- "My First Add-on for API 5, rev 0, " +
- "Extra '____' for API _$1_, by _%2_ (Obsolete), " +
- "Another extra with min-API 42, " +
- "Random name, not an id! (Obsolete)]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate there isn't a next-version we haven't tested yet
- */
- public void testLoadAddonXml_8() throws Exception {
- InputStream xmlStream = xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_8.xml");
- assertNull("There is a sample for addon-8.xsd but there is not corresponding unit test", xmlStream);
- }
-
-
- // ---- helpers ----
-
- /**
- * Returns an SdkLib file resource as a {@link ByteArrayInputStream},
- * which has the advantage that we can use {@link InputStream#reset()} on it
- * at any time to read it multiple times.
- * <p/>
- * The default for getResourceAsStream() is to return a {@link FileInputStream} that
- * does not support reset(), yet we need it in the tested code.
- *
- * @throws IOException if some I/O read fails
- */
- private ByteArrayInputStream getTestResource(String filename) throws IOException {
- InputStream xmlStream = this.getClass().getResourceAsStream(filename);
- if (xmlStream == null) {
- return null;
- }
- try {
- byte[] data = new byte[8192];
- int offset = 0;
- int n;
-
- while ((n = xmlStream.read(data, offset, data.length - offset)) != -1) {
- offset += n;
-
- if (offset == data.length) {
- byte[] newData = new byte[offset + 8192];
- System.arraycopy(data, 0, newData, 0, offset);
- data = newData;
- }
- }
-
- return new ByteArrayInputStream(data, 0, offset);
- } finally {
- if (xmlStream != null) {
- xmlStream.close();
- }
- }
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
deleted file mode 100755
index b8190db..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
+++ /dev/null
@@ -1,1717 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.internal.repository.sources;
-
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.MockEmptySdkManager;
-import com.android.sdklib.internal.repository.MockMonitor;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.IMinPlatformToolsDependency;
-import com.android.sdklib.internal.repository.packages.IMinToolsDependency;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformPackage;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.SourcePackage;
-import com.android.sdklib.internal.repository.packages.SystemImagePackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Document;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link SdkRepoSource}
- */
-public class SdkRepoSourceTest extends TestCase {
-
- /**
- * An internal helper class to give us visibility to the protected members we want
- * to test.
- */
- private static class MockSdkRepoSource extends SdkRepoSource {
- public MockSdkRepoSource() {
- super("fake-url", null /*uiName*/);
- }
-
- /**
- * Returns a pair of Document (which can be null) and the captured stdout/stderr output
- * (which is the empty string by default).
- */
- public Pair<Document, String> _findAlternateToolsXml(InputStream xml) throws IOException {
-
- final StringBuilder output = new StringBuilder();
- Document doc = super.findAlternateToolsXml(xml, new ErrorHandler() {
- @Override
- public void warning(SAXParseException exception) throws SAXException {
- output.append("WARN: " + exception.getMessage()).append('\n');
- }
-
- @Override
- public void fatalError(SAXParseException exception) throws SAXException {
- output.append("FATAL: " + exception.getMessage()).append('\n');
- }
-
- @Override
- public void error(SAXParseException exception) throws SAXException {
- output.append("ERROR: " + exception.getMessage()).append('\n');
- }
- });
-
- return Pair.of(doc, output.toString());
- }
-
- public boolean _parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
- return super.parsePackages(doc, nsUri, monitor);
- }
-
- public int _getXmlSchemaVersion(InputStream xml) {
- return super.getXmlSchemaVersion(xml);
- }
-
- public String _validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
- return super.validateXml(xml, url, version, outError, validatorFound);
- }
-
- public Document _getDocument(InputStream xml, ITaskMonitor monitor) {
- return super.getDocument(xml, monitor);
- }
-
- }
-
- private MockSdkRepoSource mSource;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- System.setProperty("THROW_DEEP_EXCEPTION_DURING_TESTING", "1");
-
- mSource = new MockSdkRepoSource();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- mSource = null;
- }
-
- public void testFindAlternateToolsXml_Errors() throws Exception {
- // Support null as input
- Pair<Document, String> result = mSource._findAlternateToolsXml(null);
- assertEquals(Pair.of((Document) null, ""), result);
-
- // Support an empty input
- String str = "";
- ByteArrayInputStream input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertEquals(
- Pair.of((Document) null, "FATAL: Premature end of file.\n"),
- result);
-
- // Support a random string as input
- str = "Some random string, not even HTML nor XML";
- input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertEquals(
- Pair.of((Document) null, "FATAL: Content is not allowed in prolog.\n"),
- result);
-
- // Support an HTML input, e.g. a typical 404 document as returned by DL
- str = "<html><head> " +
- "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"> " +
- "<title>404 Not Found</title> " + "<style><!--" + "body {font-family: arial,sans-serif}" +
- "div.nav { ... blah blah more css here ... color: green}" +
- "//--></style> " + "<script><!--" + "var rc=404;" + "//-->" + "</script> " + "</head> " +
- "<body text=#000000 bgcolor=#ffffff> " +
- "<table border=0 cellpadding=2 cellspacing=0 width=100%><tr><td rowspan=3 width=1% nowrap> " +
- "<b><font face=times color=#0039b6 size=10>G</font><font face=times color=#c41200 size=10>o</font><font face=times color=#f3c518 size=10>o</font><font face=times color=#0039b6 size=10>g</font><font face=times color=#30a72f size=10>l</font><font face=times color=#c41200 size=10>e</font> </b> " +
- "<td> </td></tr> " +
- "<tr><td bgcolor=\"#3366cc\"><font face=arial,sans-serif color=\"#ffffff\"><b>Error</b></td></tr> " +
- "<tr><td> </td></tr></table> " + "<blockquote> " + "<H1>Not Found</H1> " +
- "The requested URL <code>/404</code> was not found on this server." + " " + "<p> " +
- "</blockquote> " +
- "<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=\"#3366cc\"><img alt=\"\" width=1 height=4></td></tr></table> " +
- "</body></html> ";
- input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertEquals(
- Pair.of((Document) null, "FATAL: The element type \"meta\" must be terminated by the matching end-tag \"</meta>\".\n"),
- result);
-
- // Support some random XML document, totally unrelated to our sdk-repository schema
- str = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
- "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" +
- " package=\"some.cool.app\" android:versionName=\"1.6.04\" android:versionCode=\"1604\">" +
- " <application android:label=\"@string/app_name\" android:icon=\"@drawable/icon\"/>" +
- "</manifest>";
- input = new ByteArrayInputStream(str.getBytes());
- result = mSource._findAlternateToolsXml(input);
- assertEquals(Pair.of((Document) null, ""), result);
- }
-
- /**
- * Validate we can load a new schema version 3 using the "alternate future tool" mode.
- */
- public void testFindAlternateToolsXml_3() throws Exception {
- InputStream xmlStream = getTestResource(
- "/com/android/sdklib/testdata/repository_sample_03.xml");
-
- Pair<Document, String> result = mSource._findAlternateToolsXml(xmlStream);
- assertNotNull(result.getFirst());
- assertEquals("", result.getSecond());
- MockMonitor monitor = new MockMonitor();
- assertTrue(mSource._parsePackages(result.getFirst(), SdkRepoConstants.NS_URI, monitor));
-
- assertEquals("Found Android SDK Tools, revision 1\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 2 tool packages and 1
- // platform-tools package, with at least 1 archive each.
- Package[] pkgs = mSource.getPackages();
- assertEquals(3, pkgs.length);
- for (Package p : pkgs) {
- assertTrue((p instanceof ToolPackage) || (p instanceof PlatformToolPackage));
- assertTrue(p.getArchives().length >= 1);
- }
- }
-
- /**
- * Validate we can still load an old repository in schema version 1
- */
- public void testLoadRepoXml_01() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_01.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(1, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(1), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found My First add-on, Android API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found Usb Driver, revision 43\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 11 packages with each at least
- // one archive.
- Package[] pkgs = mSource.getPackages();
- assertEquals(11, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the extra packages path, vendor, install folder
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- extraPaths.add(ep.getPath());
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
- }
- }
- assertEquals(
- "[usb_driver]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[/]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- "[SDK/extras/usb_driver]".replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- }
-
- /**
- * Validate we can still load an old repository in schema version 2
- */
- public void testLoadRepoXml_02() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_02.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(2, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(2), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found My First add-on, Android API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found My Second add-on, Android API 2, revision 42\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found This add-on has no libraries, Android API 4, revision 3\n" +
- "Found Usb Driver, revision 43 (Obsolete)\n" +
- "Found Extra API Dep, revision 2 (Obsolete)\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- Package[] pkgs = mSource.getPackages();
- assertEquals(13, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the extra packages path, vendor, install folder
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- extraPaths.add(ep.getPath());
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
- }
- }
- assertEquals(
- "[extra_api_dep, usb_driver]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[/, /]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- "[SDK/extras/extra_api_dep, SDK/extras/usb_driver]".replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 3
- */
- public void testLoadRepoXml_03() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_03.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(3, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(3), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3\n" +
- "Found A USB Driver, revision 43 (Obsolete)\n" +
- "Found Android Vendor Extra API Dep, revision 2 (Obsolete)\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- Package[] pkgs = mSource.getPackages();
- assertEquals(11, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the extra packages path, vendor, install folder
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- extraPaths.add(ep.getPath());
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
- }
- }
- assertEquals(
- "[extra_api_dep, usb_driver]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[android_vendor/android_vendor, a/a]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- "[SDK/extras/android_vendor/extra_api_dep, SDK/extras/a/usb_driver]"
- .replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 4
- */
- public void testLoadRepoXml_04() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_04.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(4, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(4), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3\n" +
- "Found A USB Driver, revision 43 (Obsolete)\n" +
- "Found Android Vendor Extra API Dep, revision 2 (Obsolete)\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(11, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- for (Package p : pkgs) {
- if (p instanceof PlatformPackage) {
- layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
- }
- }
- assertEquals(
- "[Pair [first=1, second=0], " + // platform API 5 preview
- "Pair [first=5, second=31415], " + // platform API 2
- "Pair [first=5, second=0]]", // platform API 1
- Arrays.toString(layoutlibVers.toArray()));
-
- // Check the extra packages path, vendor, install folder and project-files
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- extraPaths.add(ep.getPath());
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
- assertEquals(
- "[extra_api_dep, usb_driver]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[android_vendor/android_vendor, a/a]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- "[SDK/extras/android_vendor/extra_api_dep, SDK/extras/a/usb_driver]"
- .replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- assertEquals(
- "[[v8/veggies_8.jar, readme.txt, dir1/dir 2 with space/mylib.jar], " +
- "[]]",
- Arrays.toString(extraFilePaths.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 5
- */
- public void testLoadRepoXml_05() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_05.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(5, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(5), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found Sources for Android SDK, API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found Sources for Android SDK, API 2, revision 2\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3\n" +
- "Found A USB Driver, revision 43 (Obsolete)\n" +
- "Found Android Vendor Extra API Dep, revision 2 (Obsolete)\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +
- "Found ARM EABI System Image, Android API 42, revision 12\n" +
- "Found Sources for Android SDK, API 42, revision 12\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(17, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib & included-abi of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- ArrayList<String> includedAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof PlatformPackage) {
- layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
- String abi = ((PlatformPackage) p).getIncludedAbi();
- includedAbi.add(abi == null ? "(null)" : abi);
- }
- }
- assertEquals(
- "[Pair [first=1, second=0], " + // platform API 5 preview
- "Pair [first=5, second=31415], " + // platform API 2
- "Pair [first=5, second=0]]", // platform API 1
- Arrays.toString(layoutlibVers.toArray()));
- assertEquals(
- "[(null), " + // platform API 5 preview
- "x86, " + // platform API 2
- "armeabi]", // platform API 1
- Arrays.toString(includedAbi.toArray()));
-
- // Check the extra packages path, vendor, install folder, project-files, old-paths
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
- assertEquals(
- "[extra_api_dep [path1, old_path2, oldPath3], " +
- "usb_driver []]",
- Arrays.toString(extraPaths.toArray()));
- assertEquals(
- "[android_vendor/android_vendor, " +
- "a/a]",
- Arrays.toString(extraVendors.toArray()));
- assertEquals(
- ("[SDK/extras/android_vendor/extra_api_dep, " +
- "SDK/extras/a/usb_driver]").replace('/', File.separatorChar),
- Arrays.toString(extraInstall.toArray()));
- assertEquals(
- "[[v8/veggies_8.jar, readme.txt, dir1/dir 2 with space/mylib.jar], " +
- "[]]",
- Arrays.toString(extraFilePaths.toArray()));
-
- // Check the system-image packages
- ArrayList<String> sysImgVersionAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- String v = sip.getAndroidVersion().getApiString();
- String a = sip.getAbi();
- sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
- }
- }
- assertEquals(
- "[42 armeabi, " +
- "2 armeabi-v7a, " +
- "2 x86]",
- Arrays.toString(sysImgVersionAbi.toArray()));
-
- // Check the source packages
- ArrayList<String> sourceVersion = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SourcePackage) {
- SourcePackage sp = (SourcePackage) p;
- String v = sp.getAndroidVersion().getApiString();
- sourceVersion.add(v);
- }
- }
- assertEquals(
- "[42, 2, 1]",
- Arrays.toString(sourceVersion.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 6
- */
- public void testLoadRepoXml_06() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_06.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(6, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(6), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found Sources for Android SDK, API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found Sources for Android SDK, API 2, revision 2\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +
- "Found ARM EABI System Image, Android API 42, revision 12\n" +
- "Found MIPS System Image, Android API 42, revision 12\n" +
- "Found Sources for Android SDK, API 42, revision 12\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(16, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib & included-abi of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- ArrayList<String> includedAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof PlatformPackage) {
- layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
- String abi = ((PlatformPackage) p).getIncludedAbi();
- includedAbi.add(abi == null ? "(null)" : abi);
- }
- }
- assertEquals(
- "[Pair [first=1, second=0], " + // platform API 5 preview
- "Pair [first=5, second=31415], " + // platform API 2
- "Pair [first=5, second=0]]", // platform API 1
- Arrays.toString(layoutlibVers.toArray()));
- assertEquals(
- "[(null), " + // platform API 5 preview
- "x86, " + // platform API 2
- "armeabi]", // platform API 1
- Arrays.toString(includedAbi.toArray()));
-
- // Check the extra packages path, vendor, install folder, project-files, old-paths
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
-
- // There are no extra packages anymore in repository-6
- assertEquals("[]", Arrays.toString(extraPaths.toArray()));
- assertEquals("[]", Arrays.toString(extraVendors.toArray()));
- assertEquals("[]", Arrays.toString(extraInstall.toArray()));
- assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
-
- // Check the system-image packages
- ArrayList<String> sysImgVersionAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- String v = sip.getAndroidVersion().getApiString();
- String a = sip.getAbi();
- sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
- }
- }
- assertEquals(
- "[42 armeabi, " +
- "42 mips, " +
- "2 armeabi-v7a, " +
- "2 x86]",
- Arrays.toString(sysImgVersionAbi.toArray()));
-
- // Check the source packages
- ArrayList<String> sourceVersion = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SourcePackage) {
- SourcePackage sp = (SourcePackage) p;
- String v = sp.getAndroidVersion().getApiString();
- sourceVersion.add(v);
- }
- }
- assertEquals(
- "[42, 2, 1]",
- Arrays.toString(sourceVersion.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 7
- */
- public void testLoadRepoXml_07() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_07.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(7, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(7), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found Sources for Android SDK, API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found Sources for Android SDK, API 2, revision 2\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1.2.3 rc4\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3 rc5\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +
- "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +
- "Found ARM EABI System Image, Android API 42, revision 12\n" +
- "Found MIPS System Image, Android API 42, revision 12\n" +
- "Found Sources for Android SDK, API 42, revision 12\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(17, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib & included-abi of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- ArrayList<String> includedAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof PlatformPackage) {
- layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
- String abi = ((PlatformPackage) p).getIncludedAbi();
- includedAbi.add(abi == null ? "(null)" : abi);
- }
- }
- assertEquals(
- "[Pair [first=1, second=0], " + // platform API 5 preview
- "Pair [first=5, second=31415], " + // platform API 2
- "Pair [first=5, second=0]]", // platform API 1
- Arrays.toString(layoutlibVers.toArray()));
- assertEquals(
- "[(null), " + // platform API 5 preview
- "x86, " + // platform API 2
- "armeabi]", // platform API 1
- Arrays.toString(includedAbi.toArray()));
-
- // Check the extra packages path, vendor, install folder, project-files, old-paths
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
-
- // There are no extra packages anymore in repository-6
- assertEquals("[]", Arrays.toString(extraPaths.toArray()));
- assertEquals("[]", Arrays.toString(extraVendors.toArray()));
- assertEquals("[]", Arrays.toString(extraInstall.toArray()));
- assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
-
-
- // Check the system-image packages
- ArrayList<String> sysImgVersionAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- String v = sip.getAndroidVersion().getApiString();
- String a = sip.getAbi();
- sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
- }
- }
- assertEquals(
- "[42 armeabi, " +
- "42 mips, " +
- "2 armeabi-v7a, " +
- "2 x86]",
- Arrays.toString(sysImgVersionAbi.toArray()));
-
-
- // Check the source packages
- ArrayList<String> sourceVersion = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SourcePackage) {
- SourcePackage sp = (SourcePackage) p;
- String v = sp.getAndroidVersion().getApiString();
- sourceVersion.add(v);
- }
- }
- assertEquals(
- "[42, 2, 1]",
- Arrays.toString(sourceVersion.toArray()));
-
-
- // Check the min-tools-rev
- ArrayList<String> minToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinToolsDependency) {
- minToolsRevs.add(p.getListDescription() + ": " +
- ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[SDK Platform Android Pastry Preview: 0, " +
- "SDK Platform Android 1.1: 0, " +
- "SDK Platform Android 1.0: 2.0.1, " +
- "Samples for SDK API 14 (Obsolete): 5, " +
- "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",
- Arrays.toString(minToolsRevs.toArray()));
-
-
- // Check the min-platform-tools-rev
- ArrayList<String> minPlatToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinPlatformToolsDependency) {
- minPlatToolsRevs.add(p.getListDescription() + ": " +
- ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[Android SDK Tools: 4, " +
- "Android SDK Tools: 4 rc5]",
- Arrays.toString(minPlatToolsRevs.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 8
- */
- public void testLoadRepoXml_08() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_08.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(8, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(8), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found Sources for Android SDK, API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found Sources for Android SDK, API 2, revision 2\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1.2.3 rc4\n" +
- "Found Android SDK Build-tools, revision 3 rc5\n" +
- "Found Android SDK Build-tools, revision 3.0.1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3 rc5\n" +
- "Found Android SDK Build-tools, revision 3\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +
- "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +
- "Found ARM EABI System Image, Android API 42, revision 12\n" +
- "Found MIPS System Image, Android API 42, revision 12\n" +
- "Found Sources for Android SDK, API 42, revision 12\n" +
- "Found Android SDK Build-tools, revision 12.13.14\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(21, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib & included-abi of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- ArrayList<String> includedAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof PlatformPackage) {
- layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
- String abi = ((PlatformPackage) p).getIncludedAbi();
- includedAbi.add(abi == null ? "(null)" : abi);
- }
- }
- assertEquals(
- "[Pair [first=1, second=0], " + // platform API 5 preview
- "Pair [first=5, second=31415], " + // platform API 2
- "Pair [first=5, second=0]]", // platform API 1
- Arrays.toString(layoutlibVers.toArray()));
- assertEquals(
- "[(null), " + // platform API 5 preview
- "x86, " + // platform API 2
- "armeabi]", // platform API 1
- Arrays.toString(includedAbi.toArray()));
-
- // Check the extra packages path, vendor, install folder, project-files, old-paths
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
-
- // There are no extra packages anymore in repository-6
- assertEquals("[]", Arrays.toString(extraPaths.toArray()));
- assertEquals("[]", Arrays.toString(extraVendors.toArray()));
- assertEquals("[]", Arrays.toString(extraInstall.toArray()));
- assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
-
-
- // Check the system-image packages
- ArrayList<String> sysImgVersionAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- String v = sip.getAndroidVersion().getApiString();
- String a = sip.getAbi();
- sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
- }
- }
- assertEquals(
- "[42 armeabi, " +
- "42 mips, " +
- "2 armeabi-v7a, " +
- "2 x86]",
- Arrays.toString(sysImgVersionAbi.toArray()));
-
-
- // Check the source packages
- ArrayList<String> sourceVersion = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SourcePackage) {
- SourcePackage sp = (SourcePackage) p;
- String v = sp.getAndroidVersion().getApiString();
- sourceVersion.add(v);
- }
- }
- assertEquals(
- "[42, 2, 1]",
- Arrays.toString(sourceVersion.toArray()));
-
-
- // Check the min-tools-rev
- ArrayList<String> minToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinToolsDependency) {
- minToolsRevs.add(p.getListDescription() + ": " +
- ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[SDK Platform Android Pastry Preview: 0, " +
- "SDK Platform Android 1.1: 0, " +
- "SDK Platform Android 1.0: 2.0.1, " +
- "Samples for SDK API 14 (Obsolete): 5, " +
- "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",
- Arrays.toString(minToolsRevs.toArray()));
-
-
- // Check the min-platform-tools-rev
- ArrayList<String> minPlatToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinPlatformToolsDependency) {
- minPlatToolsRevs.add(p.getListDescription() + ": " +
- ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[Android SDK Tools: 4, " +
- "Android SDK Tools: 4 rc5]",
- Arrays.toString(minPlatToolsRevs.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[Android SDK Tools, " +
- "Android SDK Tools, " +
- "Android SDK Platform-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Documentation for Android SDK, " +
- "Documentation for Android SDK, " +
- "SDK Platform Android Pastry Preview, " +
- "SDK Platform Android 1.1, " +
- "SDK Platform Android 1.0, " +
- "Samples for SDK API 14 (Obsolete), " +
- "Samples for SDK API 14 (Obsolete), " +
- "ARM EABI System Image, " +
- "MIPS System Image, " +
- "ARM EABI v7a System Image, " +
- "Intel x86 Atom System Image, " +
- "Sources for Android SDK, " +
- "Sources for Android SDK, " +
- "Sources for Android SDK]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 9
- */
- public void testLoadRepoXml_09() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_09.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(9, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(9), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- // Verbose log order matches the XML order and not the sorted display order.
- assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +
- "Found Documentation for Android SDK, API 1, revision 1\n" +
- "Found Sources for Android SDK, API 1, revision 1\n" +
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found Custom Thing ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found Sources for Android SDK, API 2, revision 2\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Android SDK Tools, revision 1.2.3 rc4\n" +
- "Found Android SDK Build-tools, revision 3 rc5\n" +
- "Found Android SDK Build-tools, revision 3.0.1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3 rc5\n" +
- "Found Android SDK Build-tools, revision 3\n" +
- "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +
- "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +
- "Found Variant 1 ARM EABI System Image, Android API 42, revision 12\n" +
- "Found Variant 1 MIPS System Image, Android API 42, revision 12\n" +
- "Found Variant 2 MIPS System Image, Android API 42, revision 12\n" +
- "Found Sources for Android SDK, API 42, revision 12\n" +
- "Found Android SDK Build-tools, revision 12.13.14\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- // Order is defined by
- // com.android.sdklib.internal.repository.packages.Package.comparisonKey()
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(23, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib & included-abi of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- ArrayList<String> includedAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof PlatformPackage) {
- layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
- String abi = ((PlatformPackage) p).getIncludedAbi();
- includedAbi.add(abi == null ? "(null)" : abi);
- }
- }
- assertEquals(
- "[Pair [first=1, second=0], " + // platform API 5 preview
- "Pair [first=5, second=31415], " + // platform API 2
- "Pair [first=5, second=0]]", // platform API 1
- Arrays.toString(layoutlibVers.toArray()));
- assertEquals(
- "[(null), " + // platform API 5 preview
- "x86, " + // platform API 2
- "armeabi]", // platform API 1
- Arrays.toString(includedAbi.toArray()));
-
- // Check the extra packages path, vendor, install folder, project-files, old-paths
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
-
- // There are no extra packages anymore in repository-6
- assertEquals("[]", Arrays.toString(extraPaths.toArray()));
- assertEquals("[]", Arrays.toString(extraVendors.toArray()));
- assertEquals("[]", Arrays.toString(extraInstall.toArray()));
- assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
-
-
- // Check the system-image packages
- ArrayList<String> sysImgInfo = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
- sip.getAndroidVersion().getApiString(),
- sip.getAbi(),
- sip.getTag()));
- }
- }
- assertEquals(
- "[42 armeabi: variant-1 [Variant 1], " +
- "42 mips: variant-1 [Variant 1], " +
- "42 mips: variant-2 [Variant 2], " +
- "2 armeabi-v7a: coolThing [Custom Thing], " +
- "2 armeabi-v7a: default [Default], " +
- "2 x86: default [Default]]",
- Arrays.toString(sysImgInfo.toArray()));
-
-
- // Check the source packages
- ArrayList<String> sourceVersion = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SourcePackage) {
- SourcePackage sp = (SourcePackage) p;
- String v = sp.getAndroidVersion().getApiString();
- sourceVersion.add(v);
- }
- }
- assertEquals(
- "[42, 2, 1]",
- Arrays.toString(sourceVersion.toArray()));
-
-
- // Check the min-tools-rev
- ArrayList<String> minToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinToolsDependency) {
- minToolsRevs.add(p.getListDescription() + ": " +
- ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[SDK Platform Android Pastry Preview: 0, " +
- "SDK Platform Android 1.1: 0, " +
- "SDK Platform Android 1.0: 2.0.1, " +
- "Samples for SDK API 14 (Obsolete): 5, " +
- "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",
- Arrays.toString(minToolsRevs.toArray()));
-
-
- // Check the min-platform-tools-rev
- ArrayList<String> minPlatToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinPlatformToolsDependency) {
- minPlatToolsRevs.add(p.getListDescription() + ": " +
- ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[Android SDK Tools: 4, " +
- "Android SDK Tools: 4 rc5]",
- Arrays.toString(minPlatToolsRevs.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[Android SDK Tools, " +
- "Android SDK Tools, " +
- "Android SDK Platform-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Documentation for Android SDK, " +
- "Documentation for Android SDK, " +
- "SDK Platform Android Pastry Preview, " +
- "SDK Platform Android 1.1, " +
- "SDK Platform Android 1.0, " +
- "Samples for SDK API 14 (Obsolete), " +
- "Samples for SDK API 14 (Obsolete), " +
- "Variant 1 ARM EABI System Image, " +
- "Variant 1 MIPS System Image, " +
- "Variant 2 MIPS System Image, " +
- "Custom Thing ARM EABI v7a System Image, " +
- "ARM EABI v7a System Image, " +
- "Intel x86 Atom System Image, " +
- "Sources for Android SDK, " +
- "Sources for Android SDK, " +
- "Sources for Android SDK]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate what we can load from repository in schema version 10
- */
- public void testLoadRepoXml_10() throws Exception {
- String filename = "/com/android/sdklib/testdata/repository_sample_10.xml";
- InputStream xmlStream = getTestResource(filename);
- assertNotNull("Missing test file: " + filename, xmlStream);
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(10, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkRepoConstants.getSchemaUri(10), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- // Verbose log order matches the XML order and not the sorted display order.
- assertEquals("Found The first Android platform ever, revision 3\n" + // list-display
- "Found Doc for first platform, revision 1\n" + // list-display
- "Found Sources for first platform, revision 1\n" + // list-display
- "Found SDK Platform Android 1.1, API 2, revision 12\n" +
- "Found Sources for Android SDK, API 2, revision 2\n" +
- "Found SDK Platform Android Pastry Preview, revision 3\n" +
- "Found Tools in version 1.2.3.4, revision 1.2.3 rc4\n" + // list-display
- "Found Build tools v3 (preview 5), revision 3 rc5\n" + // list-display
- "Found Android SDK Build-tools, revision 3.0.1\n" +
- "Found Documentation for Android SDK, API 2, revision 42\n" +
- "Found Android SDK Tools, revision 42\n" +
- "Found Android SDK Platform-tools, revision 3 rc5\n" +
- "Found Android SDK Build-tools, revision 3\n" +
- "Found Samples from Android 14, revision 24 (Obsolete)\n" + // list-display
- "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +
- "Found Sources for Android SDK, API 42, revision 12\n" +
- "Found Android SDK Build-tools, revision 12.13.14\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found... we expected to find 13 packages with each at least
- // one archive.
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- // Order is defined by
- // com.android.sdklib.internal.repository.packages.Package.comparisonKey()
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(17, pkgs.length);
- for (Package p : pkgs) {
- assertTrue(p.getArchives().length >= 1);
- }
-
- // Check the layoutlib & included-abi of the platform packages.
- ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
- ArrayList<String> includedAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof PlatformPackage) {
- layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());
- String abi = ((PlatformPackage) p).getIncludedAbi();
- includedAbi.add(abi == null ? "(null)" : abi);
- }
- }
- assertEquals(
- "[Pair [first=1, second=0], " + // platform API 5 preview
- "Pair [first=5, second=31415], " + // platform API 2
- "Pair [first=5, second=0]]", // platform API 1
- Arrays.toString(layoutlibVers.toArray()));
- assertEquals(
- "[(null), " + // platform API 5 preview
- "x86, " + // platform API 2
- "armeabi]", // platform API 1
- Arrays.toString(includedAbi.toArray()));
-
- // Check the extra packages path, vendor, install folder, project-files, old-paths
-
- final String osSdkPath = "SDK";
- final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
-
- ArrayList<String> extraPaths = new ArrayList<String>();
- ArrayList<String> extraVendors = new ArrayList<String>();
- ArrayList<File> extraInstall = new ArrayList<File>();
- ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
- for (Package p : pkgs) {
- if (p instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) p;
- // combine path and old-paths in the form "path [old_path1, old_path2]"
- extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
- extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
- extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
-
- ArrayList<String> filePaths = new ArrayList<String>();
- for (String filePath : ep.getProjectFiles()) {
- filePaths.add(filePath);
- }
- extraFilePaths.add(filePaths);
- }
- }
-
- // There are no extra packages anymore in repository-6
- assertEquals("[]", Arrays.toString(extraPaths.toArray()));
- assertEquals("[]", Arrays.toString(extraVendors.toArray()));
- assertEquals("[]", Arrays.toString(extraInstall.toArray()));
- assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));
-
-
- // Check the system-image packages -- there can't be any in schema 10 anymore
- ArrayList<String> sysImgInfo = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
- sip.getAndroidVersion().getApiString(),
- sip.getAbi(),
- sip.getTag()));
- }
- }
- assertEquals("[]", Arrays.toString(sysImgInfo.toArray()));
-
-
- // Check the source packages
- ArrayList<String> sourceVersion = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SourcePackage) {
- SourcePackage sp = (SourcePackage) p;
- String v = sp.getAndroidVersion().getApiString();
- sourceVersion.add(v);
- }
- }
- assertEquals(
- "[42, 2, 1]",
- Arrays.toString(sourceVersion.toArray()));
-
-
- // Check the min-tools-rev
- ArrayList<String> minToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinToolsDependency) {
- minToolsRevs.add(p.getListDescription() + ": " +
- ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[SDK Platform Android Pastry Preview: 0, " +
- "SDK Platform Android 1.1: 0, " +
- "The first Android platform ever: 2.0.1, " + // list-display
- "Samples from Android 14 (Obsolete): 5, " + // list-display
- "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",
- Arrays.toString(minToolsRevs.toArray()));
-
-
- // Check the min-platform-tools-rev
- ArrayList<String> minPlatToolsRevs = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof IMinPlatformToolsDependency) {
- minPlatToolsRevs.add(p.getListDescription() + ": " +
- ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());
- }
- }
- assertEquals(
- "[Tools in version 1.2.3.4: 4, " + // list-display
- "Android SDK Tools: 4 rc5]",
- Arrays.toString(minPlatToolsRevs.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[Tools in version 1.2.3.4, " + // list-display
- "Android SDK Tools, " +
- "Android SDK Platform-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Android SDK Build-tools, " +
- "Build tools v3 (preview 5), " + // list-display
- "Documentation for Android SDK, " +
- "Doc for first platform, " + // list-display
- "SDK Platform Android Pastry Preview, " +
- "SDK Platform Android 1.1, " +
- "The first Android platform ever, " + // list-display
- "Samples from Android 14 (Obsolete), " + // list-display
- "Samples for SDK API 14 (Obsolete), " +
- "Sources for Android SDK, " +
- "Sources for Android SDK, " +
- "Sources for first platform]", // list-display
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate there isn't a next-version we haven't tested yet
- */
- public void testLoadRepoXml_11() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/repository_sample_11.xml");
- assertNull("There is a sample for repository-11.xsd but there is not corresponding unit test", xmlStream);
- }
-
- // ---- helper ---
-
- /**
- * Returns an SdkLib file resource as a {@link ByteArrayInputStream},
- * which has the advantage that we can use {@link InputStream#reset()} on it
- * at any time to read it multiple times.
- * <p/>
- * The default for getResourceAsStream() is to return a {@link FileInputStream} that
- * does not support reset(), yet we need it in the tested code.
- *
- * @throws IOException if some I/O read fails
- */
- private ByteArrayInputStream getTestResource(String filename) throws IOException {
- InputStream xmlStream = this.getClass().getResourceAsStream(filename);
- if (xmlStream == null) {
- return null;
- }
- try {
- byte[] data = new byte[8192];
- int offset = 0;
- int n;
-
- while ((n = xmlStream.read(data, offset, data.length - offset)) != -1) {
- offset += n;
-
- if (offset == data.length) {
- byte[] newData = new byte[offset + 8192];
- System.arraycopy(data, 0, newData, 0, offset);
- data = newData;
- }
- }
-
- return new ByteArrayInputStream(data, 0, offset);
- } finally {
- if (xmlStream != null) {
- xmlStream.close();
- }
- }
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSourcePropertiesTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSourcePropertiesTest.java
deleted file mode 100755
index 1336bfc..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSourcePropertiesTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.internal.repository.sources;
-
-
-import junit.framework.TestCase;
-
-public class SdkSourcePropertiesTest extends TestCase {
-
- private static class MockSdkSourceProperties extends SdkSourceProperties {
- private int mLoadCount;
- private int mSaveCount;
-
- public MockSdkSourceProperties() {
- clear();
- }
-
- public int getLoadCount() {
- return mLoadCount;
- }
-
- public int getSaveCount() {
- return mSaveCount;
- }
-
- @Override
- protected boolean loadProperties() {
- // Don't actually load anthing.
- mLoadCount++;
- return false;
- }
-
- @Override
- protected void saveLocked() {
- // Don't actually save anything.
- mSaveCount++;
- }
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- public final void testSdkSourceProperties() {
- MockSdkSourceProperties m = new MockSdkSourceProperties();
-
- assertEquals(0, m.getLoadCount());
- assertEquals(0, m.getSaveCount());
- assertEquals(
- "<SdkSourceProperties>",
- m.toString());
-
- assertNull(m.getProperty(SdkSourceProperties.KEY_DISABLED, "http://example.com/1", null));
- assertEquals("None",
- m.getProperty(SdkSourceProperties.KEY_NAME, "http://example.com/2", "None"));
- assertEquals(1, m.getLoadCount());
- assertEquals(0, m.getSaveCount());
- assertEquals(
- "<SdkSourceProperties\n" +
- "@version@ = 1>",
- m.toString());
-
- m.setProperty(SdkSourceProperties.KEY_DISABLED, "http://example.com/1", "disabled");
- assertEquals("disabled",
- m.getProperty(SdkSourceProperties.KEY_DISABLED, "http://example.com/1", "None"));
- assertNull(m.getProperty(SdkSourceProperties.KEY_NAME, "http://example.com/1", null));
- assertEquals(
- "<SdkSourceProperties\n" +
- "@disabled at http://example.com/1 = disabled\n" +
- "@version@ = 1>",
- m.toString());
-
- m.setProperty(SdkSourceProperties.KEY_NAME, "http://example.com/2", "Site Name");
- assertEquals("Site Name",
- m.getProperty(SdkSourceProperties.KEY_NAME, "http://example.com/2", null));
- assertNull(m.getProperty(SdkSourceProperties.KEY_DISABLED, "http://example.com/2", null));
- assertEquals(1, m.getLoadCount());
- assertEquals(0, m.getSaveCount());
- assertEquals(
- "<SdkSourceProperties\n" +
- "@disabled at http://example.com/1 = disabled\n" +
- "@name at http://example.com/2 = Site Name\n" +
- "@version@ = 1>",
- m.toString());
-
- m.save();
- assertEquals(1, m.getSaveCount());
-
- // saving a 2nd time doesn't do anything if no property has been modified
- m.save();
- assertEquals(1, m.getSaveCount());
-
- // setting things to the same value doesn't actually mark the properties as modified
- m.setProperty(SdkSourceProperties.KEY_DISABLED, "http://example.com/1", "disabled");
- m.setProperty(SdkSourceProperties.KEY_NAME, "http://example.com/2", "Site Name");
- m.save();
- assertEquals(1, m.getSaveCount());
-
- m.setProperty(SdkSourceProperties.KEY_DISABLED, "http://example.com/1", "not disabled");
- m.setProperty(SdkSourceProperties.KEY_NAME, "http://example.com/2", "New Name");
- assertEquals(
- "<SdkSourceProperties\n" +
- "@disabled at http://example.com/1 = not disabled\n" +
- "@name at http://example.com/2 = New Name\n" +
- "@version@ = 1>",
- m.toString());
- m.save();
- assertEquals(2, m.getSaveCount());
-
- // setting a value to null deletes it
- m.setProperty(SdkSourceProperties.KEY_NAME, "http://example.com/2", null);
- assertEquals(
- "<SdkSourceProperties\n" +
- "@disabled at http://example.com/1 = not disabled\n" +
- "@version@ = 1>",
- m.toString());
-
- m.save();
- assertEquals(1, m.getLoadCount());
- assertEquals(3, m.getSaveCount());
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSysImgSourceTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSysImgSourceTest.java
deleted file mode 100755
index d78a262..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkSysImgSourceTest.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.internal.repository.sources;
-
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.MockMonitor;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.SystemImagePackage;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.SdkSysImgConstants;
-
-import org.w3c.dom.Document;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link SdkSysImgSource}.
- */
-public class SdkSysImgSourceTest extends TestCase {
-
- /**
- * An internal helper class to give us visibility to the protected members we want
- * to test.
- */
- private static class MockSdkSysImgSource extends SdkSysImgSource {
- public MockSdkSysImgSource() {
- super("fake-url", null /*uiName*/);
- }
-
- public Document _findAlternateToolsXml(InputStream xml) {
- return super.findAlternateToolsXml(xml);
- }
-
- public boolean _parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
- return super.parsePackages(doc, nsUri, monitor);
- }
-
- public int _getXmlSchemaVersion(InputStream xml) {
- return super.getXmlSchemaVersion(xml);
- }
-
- public String _validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
- return super.validateXml(xml, url, version, outError, validatorFound);
- }
-
- public Document _getDocument(InputStream xml, ITaskMonitor monitor) {
- return super.getDocument(xml, monitor);
- }
-
- }
-
- private MockSdkSysImgSource mSource;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mSource = new MockSdkSysImgSource();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- mSource = null;
- }
-
- /**
- * Validate that findAlternateToolsXml doesn't work for this source even
- * when trying to load a valid xml. That's because finding alternate tools
- * is not supported by this kind of source.
- */
- public void testFindAlternateToolsXml_1() throws Exception {
- InputStream xmlStream =
- getTestResource("/com/android/sdklib/testdata/sys_img_sample_1.xml");
-
- Document result = mSource._findAlternateToolsXml(xmlStream);
- assertNull(result);
- }
-
- /**
- * Validate we can load a valid schema version 1
- */
- public void testLoadSysImgXml_1() throws Exception {
- InputStream xmlStream =
- getTestResource("/com/android/sdklib/testdata/sys_img_sample_1.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(1, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkSysImgConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkSysImgConstants.getSchemaUri(1), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- assertEquals(
- "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found ARM EABI System Image, Android API 42, revision 12\n" +
- "Found MIPS System Image, Android API 42, revision 12\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found...
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
-
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(4, pkgs.length);
- for (Package p : pkgs) {
- // We expected to find packages with each at least one archive.
- assertTrue(p.getArchives().length >= 1);
- // And only system images are supported by this source
- assertTrue(p instanceof SystemImagePackage);
- }
-
- // Check the system-image packages
- ArrayList<String> sysImgVersionAbi = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- String v = sip.getAndroidVersion().getApiString();
- String a = sip.getAbi();
- sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$
- }
- }
- assertEquals(
- "[42 armeabi, " +
- "42 mips, " +
- "2 armeabi-v7a, " +
- "2 x86]",
- Arrays.toString(sysImgVersionAbi.toArray()));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[ARM EABI System Image, " +
- "MIPS System Image, " +
- "ARM EABI v7a System Image, " +
- "Intel x86 Atom System Image]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can load a valid schema version 2
- */
- public void testLoadSysImgXml_2() throws Exception {
- InputStream xmlStream =
- getTestResource("/com/android/sdklib/testdata/sys_img_sample_2.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(2, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkSysImgConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkSysImgConstants.getSchemaUri(2), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- // Verbose log order matches the XML order and not the sorted display order.
- assertEquals(
- "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found Another tag name ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found ARM EABI System Image, Android API 42, revision 12\n" +
- "Found MIPS System Image, Android API 42, revision 12\n" +
- "Found This is an arbitrary string, MIPS System Image, Android API 44, revision 14\n" +
- "Found Tag name is Sanitized if Display is Missing MIPS System Image, Android API 45, revision 15\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found...
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- // Order is defined by
- // com.android.sdklib.internal.repository.packages.SystemImagePackage.comparisonKey()
-
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(7, pkgs.length);
- for (Package p : pkgs) {
- // We expected to find packages with each at least one archive.
- assertTrue(p.getArchives().length >= 1);
- // And only system images are supported by this source
- assertTrue(p instanceof SystemImagePackage);
- }
-
- // Check the system-image packages
- ArrayList<String> sysImgInfo = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
- sip.getAndroidVersion().getApiString(),
- sip.getAbi(),
- sip.getTag()));
- }
- }
- assertEquals(
- "[45 mips: tag-name---is-Sanitized----if-Display-is-Missing [Tag name is Sanitized if Display is Missing], " +
- "44 mips: mips-only [This is an arbitrary string,], " +
- "42 armeabi: default [Default], " +
- "42 mips: default [Default], " +
- "2 armeabi-v7a: default [Ignored in description for default tag], " +
- "2 x86: default [Default], " +
- "2 armeabi-v7a: other [Another tag name]]",
- Arrays.toString(sysImgInfo.toArray()));
-
- // Check the default install-paths of the packages
- ArrayList<File> sysImgPath = new ArrayList<File>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- sysImgPath.add(sip.getInstallFolder("root", null /*sdkManager*/)); //$NON-NLS-1$
- }
- }
- assertEquals(Arrays.toString(new File[] {
- FileOp.append("root", "system-images", "android-45", "tag-name-is-sanitized-if-display-is-missing", "mips"),
- FileOp.append("root", "system-images", "android-44", "mips-only", "mips"),
- FileOp.append("root", "system-images", "android-42", "default", "armeabi"),
- FileOp.append("root", "system-images", "android-42", "default", "mips"),
- FileOp.append("root", "system-images", "android-2" , "default", "armeabi-v7a"),
- FileOp.append("root", "system-images", "android-2" , "default", "x86"),
- FileOp.append("root", "system-images", "android-2" , "other", "armeabi-v7a"),
- }).replace(File.separatorChar, '/'),
- Arrays.toString(sysImgPath.toArray()).replace(File.separatorChar, '/'));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[Tag name is Sanitized if Display is Missing MIPS System Image, " +
- "This is an arbitrary string, " +
- "MIPS System Image, " +
- "ARM EABI System Image, " +
- "MIPS System Image, " +
- "ARM EABI v7a System Image, " +
- "Intel x86 Atom System Image, " +
- "Another tag name ARM EABI v7a System Image]",
- Arrays.toString(listDescs.toArray()));
- }
-
- /**
- * Validate we can load a valid schema version 3
- */
- public void testLoadSysImgXml_3() throws Exception {
- InputStream xmlStream =
- getTestResource("/com/android/sdklib/testdata/sys_img_sample_3.xml");
-
- // guess the version from the XML document
- int version = mSource._getXmlSchemaVersion(xmlStream);
- assertEquals(3, version);
-
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- String url = "not-a-valid-url://" + SdkSysImgConstants.URL_DEFAULT_FILENAME;
-
- String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
- assertEquals(Boolean.TRUE, validatorFound[0]);
- assertEquals(null, validationError[0]);
- assertEquals(SdkSysImgConstants.getSchemaUri(3), uri);
-
- // Validation was successful, load the document
- MockMonitor monitor = new MockMonitor();
- Document doc = mSource._getDocument(xmlStream, monitor);
- assertNotNull(doc);
-
- // Get the packages
- assertTrue(mSource._parsePackages(doc, uri, monitor));
-
- // Verbose log order matches the XML order and not the sorted display order.
- assertEquals(
- "Found System Image for x86 CPU for API 2, Android API 2, revision 1\n" +
- "Found System Image for x86-64 CPU for API 2, Android API 2, revision 1\n" +
- "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found ARM 64 v8a System Image, Android API 2, revision 2\n" +
- "Found Another tag name ARM EABI v7a System Image, Android API 2, revision 2\n" +
- "Found ARM EABI System Image, Android API 42, revision 12\n" +
- "Found MIPS64 System Image, Android API 42, revision 12\n" +
- "Found MIPS system image for tag MIPS-only, Android API 44, revision 14\n" +
- "Found Tag name is Sanitized if Display is Missing MIPS System Image, Android API 45, revision 15\n" +
- "Found x86 System Image for some add-on, Acme Vendor Inc. API 2, revision 1\n" +
- "Found Some Add-on ARM EABI v7a System Image, Acme Vendor Inc. API 2, revision 2\n" +
- "Found Some Add-on Intel x86 Atom_64 System Image, Acme Vendor Inc. API 2, revision 3\n" +
- "Found Some Add-on ARM 64 v8a System Image, Acme Vendor Inc. API 2, revision 4\n" +
- "Found Some Add-on MIPS System Image, Acme Vendor Inc. API 2, revision 5\n" +
- "Found Some Add-on MIPS64 System Image, Acme Vendor Inc. API 2, revision 6\n",
- monitor.getCapturedVerboseLog());
- assertEquals("", monitor.getCapturedLog());
- assertEquals("", monitor.getCapturedErrorLog());
-
- // check the packages we found...
- // Note the order doesn't necessary match the one from the
- // assertEquald(getCapturedVerboseLog) because packages are sorted using the
- // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
- // Order is defined by
- // com.android.sdklib.internal.repository.packages.SystemImagePackage.comparisonKey()
-
- Package[] pkgs = mSource.getPackages();
-
- assertEquals(15, pkgs.length);
- for (Package p : pkgs) {
- // We expected to find packages with each at least one archive.
- assertTrue(p.getArchives().length >= 1);
- // And only system images are supported by this source
- assertTrue(p instanceof SystemImagePackage);
- }
-
- // Check the system-image packages
- ArrayList<String> sysImgInfo = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- sysImgInfo.add(String.format("%1$s %2$s: %3$s", //$NON-NLS-1$
- sip.getAndroidVersion().getApiString(),
- sip.getAbi(),
- sip.getTag()));
- }
- }
- assertEquals(
- "[45 mips: tag-name---is-Sanitized----if-Display-is-Missing [Tag name is Sanitized if Display is Missing]\n" +
- "44 mips: mips-only [This is an arbitrary string,]\n" +
- "42 armeabi: default [Default]\n" +
- "42 mips64: default [Default]\n" +
- "2 arm64-v8a: default [Ignored in description for default tag]\n" +
- "2 armeabi-v7a: default [Ignored in description for default tag]\n" +
- "2 x86_64: default [Default]\n" +
- "2 x86: default [Default]\n" +
- "2 armeabi-v7a: other [Another tag name]\n" +
- "2 arm64-v8a: some-addon [Some Add-on]\n" +
- "2 armeabi-v7a: some-addon [Some Add-on]\n" +
- "2 x86_64: some-addon [Some Add-on]\n" +
- "2 x86: some-addon [Some Add-on]\n" +
- "2 mips64: some-addon [Some Add-on]\n" +
- "2 mips: some-addon [Some Add-on]]",
- Arrays.toString(sysImgInfo.toArray()).replace(", ", "\n"));
-
- // Check the default install-paths of the packages
- ArrayList<File> sysImgPath = new ArrayList<File>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- sysImgPath.add(sip.getInstallFolder("root", null /*sdkManager*/)); //$NON-NLS-1$
- }
- }
- assertEquals(Arrays.toString(new File[] {
- FileOp.append("root", "system-images", "android-45", "tag-name-is-sanitized-if-display-is-missing", "mips"),
- FileOp.append("root", "system-images", "android-44", "mips-only", "mips"),
- FileOp.append("root", "system-images", "android-42", "default", "armeabi"),
- FileOp.append("root", "system-images", "android-42", "default", "mips64"),
- FileOp.append("root", "system-images", "android-2" , "default", "arm64-v8a"),
- FileOp.append("root", "system-images", "android-2" , "default", "armeabi-v7a"),
- FileOp.append("root", "system-images", "android-2" , "default", "x86_64"),
- FileOp.append("root", "system-images", "android-2" , "default", "x86"),
- FileOp.append("root", "system-images", "android-2" , "other", "armeabi-v7a"),
- FileOp.append("root", "system-images", "android-2" , "some-addon", "arm64-v8a"),
- FileOp.append("root", "system-images", "android-2" , "some-addon", "armeabi-v7a"),
- FileOp.append("root", "system-images", "android-2" , "some-addon", "x86_64"),
- FileOp.append("root", "system-images", "android-2" , "some-addon", "x86"),
- FileOp.append("root", "system-images", "android-2" , "some-addon", "mips64"),
- FileOp.append("root", "system-images", "android-2" , "some-addon", "mips"),
- }).replace(File.separatorChar, '/').replace(", ", "\n"),
- Arrays.toString(sysImgPath.toArray()).replace(File.separatorChar, '/').replace(", ", "\n"));
-
- // Check the list display of the packages
- ArrayList<String> listDescs = new ArrayList<String>();
- for (Package p : pkgs) {
- listDescs.add(p.getListDescription());
- }
- assertEquals(
- "[Tag name is Sanitized if Display is Missing MIPS System Image\n" +
- "MIPS system image for tag MIPS-only\n" + // list-display override
- "ARM EABI System Image\n" +
- "MIPS64 System Image\n" +
- "ARM 64 v8a System Image\n" +
- "ARM EABI v7a System Image\n" +
- "System Image for x86-64 CPU for API 2\n" + // list-display override
- "System Image for x86 CPU for API 2\n" + // list-display override
- "Another tag name ARM EABI v7a System Image\n" +
- "Some Add-on ARM 64 v8a System Image\n" +
- "Some Add-on ARM EABI v7a System Image\n" +
- "Some Add-on Intel x86 Atom_64 System Image\n" +
- "x86 System Image for some add-on\n" + // list-display override
- "Some Add-on MIPS64 System Image\n" +
- "Some Add-on MIPS System Image]",
- Arrays.toString(listDescs.toArray()).replace(", ", "\n"));
-
- // Check platfomr vs add-ons system-images
- ArrayList<String> addonProps = new ArrayList<String>();
- for (Package p : pkgs) {
- if (p instanceof SystemImagePackage) {
- SystemImagePackage sip = (SystemImagePackage) p;
- String s = sip.isPlatform() ? "Platform" : "Addon: ";
- if (!sip.isPlatform()) {
- s += sip.getTag().toString() + " by " + sip.getAddonVendor().toString();
- }
- addonProps.add(s);
- }
- }
- assertEquals(
- "[Platform\n" +
- "Platform\n" +
- "Platform\n" +
- "Platform\n" +
- "Platform\n" +
- "Platform\n" +
- "Platform\n" +
- "Platform\n" +
- "Platform\n" +
- "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
- "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
- "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
- "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
- "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]\n" +
- "Addon: some-addon [Some Add-on] by some-vendor [Acme Vendor Inc.]]",
- Arrays.toString(addonProps.toArray()).replace(", ", "\n"));
-
- }
-
- /**
- * Validate there isn't a next-version we haven't tested yet
- */
- public void testLoadSysImgXml_4() throws Exception {
- InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/sys_img_sample_4.xml");
- assertNull("There is a sample for sys-img-4.xsd but there is not corresponding unit test", xmlStream);
- }
-
-
- //-----
-
- /**
- * Returns an SdkLib file resource as a {@link ByteArrayInputStream},
- * which has the advantage that we can use {@link InputStream#reset()} on it
- * at any time to read it multiple times.
- * <p/>
- * The default for getResourceAsStream() is to return a {@link FileInputStream} that
- * does not support reset(), yet we need it in the tested code.
- *
- * @throws IOException if some I/O read fails
- */
- private ByteArrayInputStream getTestResource(String filename) throws IOException {
- InputStream xmlStream = this.getClass().getResourceAsStream(filename);
- if (xmlStream == null) {
- return null;
- }
- try {
- byte[] data = new byte[8192];
- int offset = 0;
- int n;
-
- while ((n = xmlStream.read(data, offset, data.length - offset)) != -1) {
- offset += n;
-
- if (offset == data.length) {
- byte[] newData = new byte[offset + 8192];
- System.arraycopy(data, 0, newData, 0, offset);
- data = newData;
- }
- }
-
- return new ByteArrayInputStream(data, 0, offset);
- } finally {
- if (xmlStream != null) {
- xmlStream.close();
- }
- }
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/SettingsControllerTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/SettingsControllerTest.java
deleted file mode 100755
index 63b774a..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/SettingsControllerTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.updater;
-
-import com.android.sdklib.AndroidLocationTestCase;
-import com.android.sdklib.internal.repository.updater.ISettingsPage.SettingsChangedCallback;
-import com.android.sdklib.internal.repository.updater.SettingsController.OnChangedListener;
-import com.android.sdklib.internal.repository.updater.SettingsController.Settings;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.mock.MockLog;
-
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class SettingsControllerTest extends AndroidLocationTestCase {
-
- private IFileOp mFileOp;
- private MockLog mMockLog;
- private SettingsController m;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mFileOp = new FileOp();
- mMockLog = new MockLog();
- m = new SettingsController(mFileOp, mMockLog);
- }
-
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
- public final void testDefaultSettings() {
- m.loadSettings();
- Settings s = m.getSettings();
- assertFalse(s.getAskBeforeAdbRestart());
- assertTrue(s.getEnablePreviews());
- assertFalse(s.getForceHttp());
- assertTrue (s.getShowUpdateOnly());
- assertTrue (s.getUseDownloadCache());
- assertEquals(-1, s.getMonitorDensity());
- }
-
- public final void testSetSettings() {
- m.loadSettings();
-
- Settings s1 = m.getSettings();
- assertFalse(s1.getAskBeforeAdbRestart());
- assertTrue (s1.getEnablePreviews());
- assertFalse(s1.getForceHttp());
- assertTrue (s1.getShowUpdateOnly());
- assertTrue (s1.getUseDownloadCache());
- assertEquals(-1, s1.getMonitorDensity());
-
- m.setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, true);
- m.setSetting(ISettingsPage.KEY_ENABLE_PREVIEWS, false);
- m.setSetting(ISettingsPage.KEY_FORCE_HTTP, true);
- m.setShowUpdateOnly(false);
- m.setSetting(ISettingsPage.KEY_USE_DOWNLOAD_CACHE, false);
- m.setMonitorDensity(320);
-
- Settings s2 = m.getSettings();
- assertSame(s2, s1);
- assertTrue (s2.getAskBeforeAdbRestart());
- assertFalse(s2.getEnablePreviews());
- assertTrue (s2.getForceHttp());
- assertFalse(s2.getShowUpdateOnly());
- assertFalse(s2.getUseDownloadCache());
- assertEquals(320, s2.getMonitorDensity());
-
- m.saveSettings();
-
- // create a new instance
- SettingsController m3 = new SettingsController(mFileOp, mMockLog);
- m3.loadSettings();
-
- Settings s3 = m3.getSettings();
- assertNotSame(s3, s1);
- assertTrue (s3.getAskBeforeAdbRestart());
- assertFalse(s3.getEnablePreviews());
- assertTrue (s3.getForceHttp());
- assertFalse(s3.getShowUpdateOnly());
- assertFalse(s3.getUseDownloadCache());
- assertEquals(320, s3.getMonitorDensity());
- }
-
- public final void testSettingsPage() {
- final AtomicBoolean pageLoadSettingsCalled = new AtomicBoolean(false);
- final AtomicBoolean pageRetrieveSettingsCalled = new AtomicBoolean(false);
- final AtomicBoolean pageSetOnSettingsChangedCalled = new AtomicBoolean(false);
- final AtomicReference<SettingsChangedCallback> pageSettingsChangedCallback =
- new AtomicReference<ISettingsPage.SettingsChangedCallback>();
-
- ISettingsPage mockPage = new ISettingsPage() {
- @Override
- public void loadSettings(Properties inSettings) {
- pageLoadSettingsCalled.set(true);
- }
-
- @Override
- public void setOnSettingsChanged(SettingsChangedCallback settingsChangedCallback) {
- pageSetOnSettingsChangedCalled.set(true);
- pageSettingsChangedCallback.set(settingsChangedCallback);
- }
-
- @Override
- public void retrieveSettings(Properties outSettings) {
- pageRetrieveSettingsCalled.set(true);
- }
- };
-
- // Setting the page loads settings into it and then registers a changed-callback
- // that will call m.onSettingsChanged.
- m.setSettingsPage(mockPage);
- assertTrue(pageLoadSettingsCalled.get());
- assertTrue(pageSetOnSettingsChangedCalled.get());
- assertFalse(pageRetrieveSettingsCalled.get());
-
- final AtomicBoolean listener1Called = new AtomicBoolean(false);
-
- OnChangedListener listener1 = new OnChangedListener() {
- @Override
- public void onSettingsChanged(SettingsController controller, Settings oldSettings) {
- listener1Called.set(true);
- }
- };
-
- final AtomicBoolean listener2Called = new AtomicBoolean(false);
-
- OnChangedListener listener2 = new OnChangedListener() {
- @Override
- public void onSettingsChanged(SettingsController controller, Settings oldSettings) {
- listener1Called.set(true);
- }
- };
-
- m.registerOnChangedListener(listener1);
- m.registerOnChangedListener(listener2);
- m.unregisterOnChangedListener(listener2);
- m.unregisterOnChangedListener(listener2);
- assertFalse(listener1Called.get());
- assertFalse(listener2Called.get());
-
- // When the settings page changes, it calls the callback that it was given
- // (which we captured earlier)
- assertNotNull(pageSettingsChangedCallback.get());
- pageSettingsChangedCallback.get().onSettingsChanged(mockPage);
- // That triggers SettingsController.onSettingsChanged which calls retrieve() on the
- // page to get the settings and save them and then call all the registered listeners
- // with the settings.
- assertTrue(pageRetrieveSettingsCalled.get());
- assertTrue(listener1Called.get());
- assertFalse(listener2Called.get());
-
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/UpdaterDataTest.java b/base/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/UpdaterDataTest.java
deleted file mode 100755
index 9dfe12e..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/updater/UpdaterDataTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.repository.updater;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.SdkManagerTestCase;
-import com.android.sdklib.internal.repository.packages.MockEmptyPackage;
-import com.android.sdklib.mock.MockLog;
-import com.android.sdklib.repository.PkgProps;
-import com.android.utils.IReaderLogger;
-import com.google.common.base.Charsets;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Properties;
-
-public class UpdaterDataTest extends SdkManagerTestCase {
-
- private static class MockReaderLogger implements IReaderLogger {
- private final MockLog mLog;
- private final LinkedList<String> mInputLines = new LinkedList<String>();
-
- public MockReaderLogger(MockLog log, String...inputLines) {
- mLog = log;
- mInputLines.addAll(Arrays.asList(inputLines));
- }
-
- @Override
- public void warning(String msgFormat, Object... args) {
- mLog.warning(msgFormat, args);
- }
-
- @Override
- public void verbose(String msgFormat, Object... args) {
- mLog.verbose(msgFormat, args);
- }
-
- @Override
- public void info(String msgFormat, Object... args) {
- mLog.info(msgFormat, args);
- }
-
- @Override
- public void error(Throwable t, String msgFormat, Object... args) {
- mLog.error(t, msgFormat, args);
- }
-
- @Override
- public int readLine(byte[] inputBuffer) throws IOException {
- if (!mInputLines.isEmpty()) {
- String line = mInputLines.remove();
- byte[] bytes = line.getBytes(Charsets.UTF_8);
- int len = bytes.length;
- if (inputBuffer.length < len) {
- len = inputBuffer.length;
- }
- System.arraycopy(bytes, 0, inputBuffer, 0, len);
- for (int a = len, n = inputBuffer.length; a < n; a++) {
- inputBuffer[a] = 0;
- }
- return len;
- }
- return 0;
- }
-
- public String[] getUnreadInput() {
- return mInputLines.toArray(new String[mInputLines.size()]);
- }
-
- // -- from mock log --
-
- @Override
- public String toString() {
- return mLog.toString();
- }
-
- @NonNull
- public List<String> getMessages() {
- return mLog.getMessages();
- }
-
- public void clear() {
- mLog.clear();
- }
- }
-
- public final void testAcceptLicenses_Empty() {
- SdkManager sdkman = getSdkManager();
- MockReaderLogger inputLog = new MockReaderLogger(new MockLog(), "");
- UpdaterData data = new UpdaterData(sdkman.getLocation(), inputLog);
- String acceptLicenses = null;
- List<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
- data.acceptLicense(archives , acceptLicenses, 3);
- assertTrue(archives.isEmpty());
- assertEquals("[]", Arrays.toString(inputLog.getMessages().toArray()));
- assertEquals("[]", Arrays.toString(inputLog.getUnreadInput()));
- }
-
- public final void testAcceptLicenses_NoAnswer() {
- SdkManager sdkman = getSdkManager();
- MockReaderLogger inputLog = new MockReaderLogger(new MockLog(), "");
- UpdaterData data = new UpdaterData(sdkman.getLocation(), inputLog);
- String acceptLicenses = null;
- List<ArchiveInfo> infos = new ArrayList<ArchiveInfo>();
-
- Properties props = new Properties();
- props.setProperty(PkgProps.PKG_LICENSE_REF, "sdk-license");
- props.setProperty(PkgProps.PKG_LICENSE, "This is the license text.\nEtc etc.\n");
- MockEmptyPackage p = new MockEmptyPackage("test", props);
- infos.add(new ArchiveInfo(p.getLocalArchive(), null, null));
-
- assertEquals("[MockEmptyPackage 'test']", Arrays.toString(infos.toArray()));
- data.acceptLicense(infos , acceptLicenses, 3);
- assertEquals(
- "[P -------------------------------\n" +
- ", P License id: sdk-license-dddb8a39\n" +
- ", P Used by: \n" +
- " - MockEmptyPackage 'test'\n" +
- ", P -------------------------------\n" +
- "\n" +
- ", P This is the license text.\n" +
- "Etc etc.\n" +
- "\n" +
- ", P Do you accept the license 'sdk-license-dddb8a39' [y/n]: , P \n" +
- ", P Unknown response ''.\n" +
- ", P Do you accept the license 'sdk-license-dddb8a39' [y/n]: , P \n" +
- ", P Unknown response ''.\n" +
- ", P Do you accept the license 'sdk-license-dddb8a39' [y/n]: , P \n" +
- ", P Unknown response ''.\n" +
- ", P Max number of retries exceeded. Rejecting 'sdk-license-dddb8a39'\n" +
- ", P Package MockEmptyPackage 'test' not installed due to rejected license 'sdk-license-dddb8a39'.\n" +
- "]", Arrays.toString(inputLog.getMessages().toArray()));
- assertEquals("[]", Arrays.toString(infos.toArray()));
- assertEquals("[]", Arrays.toString(inputLog.getUnreadInput()));
- }
-
-
- public final void testAcceptLicenses_Answer() {
- SdkManager sdkman = getSdkManager();
- MockReaderLogger inputLog = new MockReaderLogger(new MockLog(), "yes");
- UpdaterData data = new UpdaterData(sdkman.getLocation(), inputLog);
- String acceptLicenses = null;
- List<ArchiveInfo> infos = new ArrayList<ArchiveInfo>();
-
- Properties props = new Properties();
- props.setProperty(PkgProps.PKG_LICENSE_REF, "sdk-license");
- props.setProperty(PkgProps.PKG_LICENSE, "This is the license text.\nEtc etc.\n");
- MockEmptyPackage p = new MockEmptyPackage("test", props);
- infos.add(new ArchiveInfo(p.getLocalArchive(), null, null));
-
- assertEquals("[MockEmptyPackage 'test']", Arrays.toString(infos.toArray()));
- data.acceptLicense(infos , acceptLicenses, 3);
- assertEquals(
- "[P -------------------------------\n" +
- ", P License id: sdk-license-dddb8a39\n" +
- ", P Used by: \n" +
- " - MockEmptyPackage 'test'\n" +
- ", P -------------------------------\n" +
- "\n" +
- ", P This is the license text.\n" +
- "Etc etc.\n" +
- "\n" +
- ", P Do you accept the license 'sdk-license-dddb8a39' [y/n]: , P \n" +
- "]", Arrays.toString(inputLog.getMessages().toArray()));
- assertEquals("[MockEmptyPackage 'test']", Arrays.toString(infos.toArray()));
- assertEquals("[]", Arrays.toString(inputLog.getUnreadInput()));
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java b/base/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java
deleted file mode 100755
index e2c5d14..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java
+++ /dev/null
@@ -1,634 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.io;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-
-/**
- * Mock version of {@link FileOp} that wraps some common {@link File}
- * operations on files and folders.
- * <p/>
- * This version does not perform any file operation. Instead it records a textual
- * representation of all the file operations performed.
- * <p/>
- * To avoid cross-platform path issues (e.g. Windows path), the methods here should
- * always use rooted (aka absolute) unix-looking paths, e.g. "/dir1/dir2/file3".
- * When processing {@link File}, you can convert them using {@link #getAgnosticAbsPath(File)}.
- */
-public class MockFileOp implements IFileOp {
-
- private final Map<String, FileInfo> mExistingFiles = Maps.newTreeMap();
- private final Set<String> mExistingFolders = Sets.newTreeSet();
- private final List<StringOutputStream> mOutputStreams = new ArrayList<StringOutputStream>();
-
- public MockFileOp() {
- }
-
- /** Resets the internal state, as if the object had been newly created. */
- public void reset() {
- mExistingFiles.clear();
- mExistingFolders.clear();
- mOutputStreams.clear();
- }
-
- @NonNull
- public String getAgnosticAbsPath(@NonNull File file) {
- return getAgnosticAbsPath(file.getAbsolutePath());
- }
-
- @NonNull
- public String getAgnosticAbsPath(@NonNull String path) {
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- // Try to convert the windows-looking path to a unix-looking one
- path = path.replace('\\', '/');
- path = path.replaceAll("^[A-Z]:", ""); //$NON-NLS-1$ //$NON-NLS-2$
- }
- return path;
- }
-
- /**
- * Records a new absolute file path.
- * Parent folders are not automatically created.
- */
- public void recordExistingFile(@NonNull File file) {
- recordExistingFile(getAgnosticAbsPath(file), 0, (byte[])null);
- }
-
- /**
- * Records a new absolute file path.
- * Parent folders are not automatically created.
- * <p/>
- * The syntax should always look "unix-like", e.g. "/dir/file".
- * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
- * @param absFilePath A unix-like file path, e.g. "/dir/file"
- */
- public void recordExistingFile(@NonNull String absFilePath) {
- recordExistingFile(absFilePath, 0, (byte[])null);
- }
-
- /**
- * Records a new absolute file path & its input stream content.
- * Parent folders are not automatically created.
- * <p/>
- * The syntax should always look "unix-like", e.g. "/dir/file".
- * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
- * @param absFilePath A unix-like file path, e.g. "/dir/file"
- * @param inputStream A non-null byte array of content to return
- * via {@link #newFileInputStream(File)}.
- */
- public void recordExistingFile(@NonNull String absFilePath, @Nullable byte[] inputStream) {
- recordExistingFile(absFilePath, 0, inputStream);
- }
-
- /**
- * Records a new absolute file path & its input stream content.
- * Parent folders are not automatically created.
- * <p/>
- * The syntax should always look "unix-like", e.g. "/dir/file".
- * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
- * @param absFilePath A unix-like file path, e.g. "/dir/file"
- * @param content A non-null UTF-8 content string to return
- * via {@link #newFileInputStream(File)}.
- */
- public void recordExistingFile(@NonNull String absFilePath, @NonNull String content) {
- recordExistingFile(absFilePath, 0, content.getBytes(Charsets.UTF_8));
- }
-
- /**
- * Records a new absolute file path & its input stream content.
- * Parent folders are not automatically created.
- * <p/>
- * The syntax should always look "unix-like", e.g. "/dir/file".
- * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
- * @param absFilePath A unix-like file path, e.g. "/dir/file"
- * @param inputStream A non-null byte array of content to return
- * via {@link #newFileInputStream(File)}.
- */
- public void recordExistingFile(@NonNull String absFilePath,
- long lastModified,
- @Nullable byte[] inputStream) {
- mExistingFiles.put(absFilePath, new FileInfo(lastModified, inputStream));
- }
-
- /**
- * Records a new absolute file path & its input stream content.
- * Parent folders are not automatically created.
- * <p/>
- * The syntax should always look "unix-like", e.g. "/dir/file".
- * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
- * @param absFilePath A unix-like file path, e.g. "/dir/file"
- * @param content A non-null UTF-8 content string to return
- * via {@link #newFileInputStream(File)}.
- */
- public void recordExistingFile(@NonNull String absFilePath,
- long lastModified,
- @NonNull String content) {
- recordExistingFile(absFilePath, lastModified, content.getBytes(Charsets.UTF_8));
- }
-
- /**
- * Records a new absolute folder path.
- * Parent folders are not automatically created.
- */
- public void recordExistingFolder(File folder) {
- mExistingFolders.add(getAgnosticAbsPath(folder));
- }
-
- /**
- * Records a new absolute folder path.
- * Parent folders are not automatically created.
- * <p/>
- * The syntax should always look "unix-like", e.g. "/dir/file".
- * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
- * @param absFolderPath A unix-like folder path, e.g. "/dir/file"
- */
- public void recordExistingFolder(String absFolderPath) {
- mExistingFolders.add(absFolderPath);
- }
-
- /**
- * Returns true if a file with the given path has been recorded.
- */
- public boolean hasRecordedExistingFile(File file) {
- return mExistingFiles.containsKey(getAgnosticAbsPath(file));
- }
-
- /**
- * Returns true if a folder with the given path has been recorded.
- */
- public boolean hasRecordedExistingFolder(File folder) {
- return mExistingFolders.contains(getAgnosticAbsPath(folder));
- }
-
- /**
- * Returns the list of paths added using {@link #recordExistingFile(String)}
- * and eventually updated by {@link #delete(File)} operations.
- * <p/>
- * The returned list is sorted by alphabetic absolute path string.
- */
- @NonNull
- public String[] getExistingFiles() {
- Set<String> files = mExistingFiles.keySet();
- return files.toArray(new String[files.size()]);
- }
-
- /**
- * Returns the list of folder paths added using {@link #recordExistingFolder(String)}
- * and eventually updated {@link #delete(File)} or {@link #mkdirs(File)} operations.
- * <p/>
- * The returned list is sorted by alphabetic absolute path string.
- */
- @NonNull
- public String[] getExistingFolders() {
- return mExistingFolders.toArray(new String[mExistingFolders.size()]);
- }
-
- /**
- * Returns the {@link StringOutputStream#toString()} as an array, in creation order.
- * Array can be empty but not null.
- */
- @NonNull
- public String[] getOutputStreams() {
- int n = mOutputStreams.size();
- String[] result = new String[n];
- for (int i = 0; i < n; i++) {
- result[i] = mOutputStreams.get(i).toString();
- }
- return result;
- }
-
- /**
- * Helper to delete a file or a directory.
- * For a directory, recursively deletes all of its content.
- * Files that cannot be deleted right away are marked for deletion on exit.
- * The argument can be null.
- */
- @Override
- public void deleteFileOrFolder(@NonNull File fileOrFolder) {
- if (fileOrFolder != null) {
- if (isDirectory(fileOrFolder)) {
- // Must delete content recursively first
- for (File item : listFiles(fileOrFolder)) {
- deleteFileOrFolder(item);
- }
- }
- delete(fileOrFolder);
- }
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * <em>Note: this mock version does nothing.</em>
- */
- @Override
- public void setExecutablePermission(@NonNull File file) throws IOException {
- // pass
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * <em>Note: this mock version does nothing.</em>
- */
- @Override
- public void setReadOnly(@NonNull File file) {
- // pass
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * <em>Note: this mock version does nothing.</em>
- */
- @Override
- public void copyFile(@NonNull File source, @NonNull File dest) throws IOException {
- // pass
- throw new UnsupportedOperationException("MockFileUtils.copyFile is not supported."); //$NON-NLS-1$
- }
-
- /**
- * Checks whether 2 binary files are the same.
- *
- * @param file1 the source file to copy
- * @param file2 the destination file to write
- * @throws FileNotFoundException if the source files don't exist.
- * @throws IOException if there's a problem reading the files.
- */
- @Override
- public boolean isSameFile(@NonNull File file1, @NonNull File file2) throws IOException {
- String path1 = getAgnosticAbsPath(file1);
- String path2 = getAgnosticAbsPath(file2);
- FileInfo fi1 = mExistingFiles.get(path1);
- FileInfo fi2 = mExistingFiles.get(path2);
-
- if (fi1 == null) {
- throw new FileNotFoundException("[isSameFile] Mock file not defined: " + path1);
- }
-
- if (fi1 == fi2) {
- return true;
- }
-
- if (fi2 == null) {
- throw new FileNotFoundException("[isSameFile] Mock file not defined: " + path2);
- }
-
- byte[] content1 = fi1.getContent();
- byte[] content2 = fi2.getContent();
-
- if (content1 == null) {
- throw new IOException("[isSameFile] Mock file has no content: " + path1);
- }
- if (content2 == null) {
- throw new IOException("[isSameFile] Mock file has no content: " + path2);
- }
-
- return Arrays.equals(content1, content2);
- }
-
- /** Invokes {@link File#isFile()} on the given {@code file}. */
- @Override
- public boolean isFile(@NonNull File file) {
- String path = getAgnosticAbsPath(file);
- return mExistingFiles.containsKey(path);
- }
-
- /** Invokes {@link File#isDirectory()} on the given {@code file}. */
- @Override
- public boolean isDirectory(@NonNull File file) {
- String path = getAgnosticAbsPath(file);
- if (mExistingFolders.contains(path)) {
- return true;
- }
-
- // If we defined a file or folder as a child of the requested file path,
- // then the directory exists implicitely.
- Pattern pathRE = Pattern.compile(
- Pattern.quote(path + (path.endsWith("/") ? "" : '/')) + //$NON-NLS-1$ //$NON-NLS-2$
- ".*"); //$NON-NLS-1$
-
- for (String folder : mExistingFolders) {
- if (pathRE.matcher(folder).matches()) {
- return true;
- }
- }
- for (String filePath : mExistingFiles.keySet()) {
- if (pathRE.matcher(filePath).matches()) {
- return true;
- }
- }
-
- return false;
- }
-
- /** Invokes {@link File#exists()} on the given {@code file}. */
- @Override
- public boolean exists(@NonNull File file) {
- return isFile(file) || isDirectory(file);
- }
-
- /** Invokes {@link File#length()} on the given {@code file}. */
- @Override
- public long length(@NonNull File file) {
- throw new UnsupportedOperationException("MockFileUtils.length is not supported."); //$NON-NLS-1$
- }
-
- @Override
- public boolean delete(@NonNull File file) {
- String path = getAgnosticAbsPath(file);
-
- if (mExistingFiles.remove(path) != null) {
- return true;
- }
-
- boolean hasSubfiles = false;
- for (String folder : mExistingFolders) {
- if (folder.startsWith(path) && !folder.equals(path)) {
- // the File.delete operation is not recursive and would fail to remove
- // a root dir that is not empty.
- return false;
- }
- }
- if (!hasSubfiles) {
- for (String filePath : mExistingFiles.keySet()) {
- if (filePath.startsWith(path) && !filePath.equals(path)) {
- // the File.delete operation is not recursive and would fail to remove
- // a root dir that is not empty.
- return false;
- }
- }
- }
-
- return mExistingFolders.remove(path);
- }
-
- /** Invokes {@link File#mkdirs()} on the given {@code file}. */
- @Override
- public boolean mkdirs(@NonNull File file) {
- for (; file != null; file = file.getParentFile()) {
- String path = getAgnosticAbsPath(file);
- mExistingFolders.add(path);
- }
- return true;
- }
-
- /**
- * Invokes {@link File#listFiles()} on the given {@code file}.
- * The returned list is sorted by alphabetic absolute path string.
- * Might return an empty array but never null.
- */
- @NonNull
- @Override
- public File[] listFiles(@NonNull File file) {
- TreeSet<File> files = new TreeSet<File>();
-
- String path = getAgnosticAbsPath(file);
- Pattern pathRE = Pattern.compile(
- Pattern.quote(path + (path.endsWith("/") ? "" : '/')) + //$NON-NLS-1$ //$NON-NLS-2$
- ".*"); //$NON-NLS-1$
-
- for (String folder : mExistingFolders) {
- if (pathRE.matcher(folder).matches()) {
- files.add(new File(folder));
- }
- }
- for (String filePath : mExistingFiles.keySet()) {
- if (pathRE.matcher(filePath).matches()) {
- files.add(new File(filePath));
- }
- }
- return files.toArray(new File[files.size()]);
- }
-
- /** Invokes {@link File#renameTo(File)} on the given files. */
- @Override
- public boolean renameTo(@NonNull File oldFile, @NonNull File newFile) {
- boolean renamed = false;
-
- String oldPath = getAgnosticAbsPath(oldFile);
- String newPath = getAgnosticAbsPath(newFile);
- Pattern pathRE = Pattern.compile(
- "^(" + Pattern.quote(oldPath) + //$NON-NLS-1$
- ")($|/.*)"); //$NON-NLS-1$
-
- Set<String> newFolders = Sets.newTreeSet();
- for (Iterator<String> it = mExistingFolders.iterator(); it.hasNext(); ) {
- String folder = it.next();
- Matcher m = pathRE.matcher(folder);
- if (m.matches()) {
- it.remove();
- String newFolder = newPath + m.group(2);
- newFolders.add(newFolder);
- renamed = true;
- }
- }
- mExistingFolders.addAll(newFolders);
- newFolders.clear();
-
- Map<String, FileInfo> newFiles = Maps.newTreeMap();
- for (Iterator<Entry<String, FileInfo>> it = mExistingFiles.entrySet().iterator();
- it.hasNext(); ) {
- Entry<String, FileInfo> entry = it.next();
- String filePath = entry.getKey();
- Matcher m = pathRE.matcher(filePath);
- if (m.matches()) {
- it.remove();
- String newFilePath = newPath + m.group(2);
- newFiles.put(newFilePath, entry.getValue());
- renamed = true;
- }
- }
- mExistingFiles.putAll(newFiles);
-
- return renamed;
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * <em>TODO: we might want to overload this to read mock properties instead of a real file.</em>
- */
- @NonNull
- @Override
- public Properties loadProperties(@NonNull File file) {
- Properties props = new Properties();
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(file);
- props.load(fis);
- } catch (IOException ignore) {
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (Exception ignore) {}
- }
- }
- return props;
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * <em>Note that this uses the mock version of {@link #newFileOutputStream(File)} and thus
- * records the write rather than actually performing it.</em>
- */
- @Override
- public void saveProperties(
- @NonNull File file,
- @NonNull Properties props,
- @NonNull String comments) throws IOException {
- OutputStream fos = null;
- try {
- fos = newFileOutputStream(file);
- props.store(fos, comments);
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- /**
- * Returns an OutputStream that will capture the bytes written and associate
- * them with the given file.
- */
- @NonNull
- @Override
- public OutputStream newFileOutputStream(@NonNull File file) throws FileNotFoundException {
- StringOutputStream os = new StringOutputStream(file);
- mOutputStreams.add(os);
- return os;
- }
-
- /**
- * An {@link OutputStream} that will capture the stream as an UTF-8 string once properly closed
- * and associate it to the given {@link File}.
- */
- public class StringOutputStream extends ByteArrayOutputStream {
- private String mData;
- private final File mFile;
-
- public StringOutputStream(File file) {
- mFile = file;
- recordExistingFile(file);
- }
-
- public File getFile() {
- return mFile;
- }
-
- /** Can be null if the stream has never been properly closed. */
- public String getData() {
- return mData;
- }
-
- /** Once the stream is properly closed, convert the byte array to an UTF-8 string */
- @Override
- public void close() throws IOException {
- super.close();
- mData = new String(toByteArray(), "UTF-8"); //$NON-NLS-1$
- }
-
- /** Returns a string representation suitable for unit tests validation. */
- @Override
- public synchronized String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append('<').append(getAgnosticAbsPath(mFile)).append(": "); //$NON-NLS-1$
- if (mData == null) {
- sb.append("(stream not closed properly)>"); //$NON-NLS-1$
- } else {
- sb.append('\'').append(mData).append("'>"); //$NON-NLS-1$
- }
- return sb.toString();
- }
- }
-
- @NonNull
- @Override
- public InputStream newFileInputStream(@NonNull File file) throws FileNotFoundException {
- FileInfo fi = mExistingFiles.get(getAgnosticAbsPath(file));
- if (fi != null) {
- byte[] content = fi.getContent();
- if (content != null) {
- return new ByteArrayInputStream(content);
- }
- }
- throw new FileNotFoundException("Mock file has no content: " + getAgnosticAbsPath(file));
- }
-
- @Override
- public long lastModified(@NonNull File file) {
- FileInfo fi = mExistingFiles.get(getAgnosticAbsPath(file));
- if (fi != null) {
- return fi.getLastModified();
- }
- return 0;
- }
-
- // -----
-
- private static class FileInfo {
- private long mLastModified;
- private byte[] mContent;
-
- public FileInfo(long lastModified, @Nullable byte[] content) {
- mLastModified = lastModified;
- mContent = content;
- }
-
- public long getLastModified() {
- return mLastModified;
- }
-
- @Nullable
- public byte[] getContent() {
- return mContent;
- }
-
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/io/MockFileOpTest.java b/base/sdklib/src/test/java/com/android/sdklib/io/MockFileOpTest.java
deleted file mode 100755
index 9739058..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/io/MockFileOpTest.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.io;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-/**
- * Unit-test for the {@link MockFileOp}, which is a mock of {@link FileOp} that doesn't
- * touch the file system. Just testing the test.
- */
-public class MockFileOpTest extends TestCase {
-
- private MockFileOp m;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- m = new MockFileOp();
- }
-
- private File createFile(String...segments) {
- File f = null;
- for (String segment : segments) {
- if (f == null) {
- f = new File(segment);
- } else {
- f = new File(f, segment);
- }
- }
-
- return f;
- }
-
- public void testIsFile() {
- File f1 = createFile("/dir1", "file1");
- assertFalse(m.isFile(f1));
-
- m.recordExistingFile("/dir1/file1");
- assertTrue(m.isFile(f1));
-
- assertEquals(
- "[/dir1/file1]",
- Arrays.toString(m.getExistingFiles()));
- }
-
- public void testIsDirectory() {
- File d4 = createFile("/dir1", "dir2", "dir3", "dir4");
- File f7 = createFile("/dir1", "dir2", "dir6", "file7");
- assertFalse(m.isDirectory(d4));
-
- m.recordExistingFolder("/dir1/dir2/dir3/dir4");
- m.recordExistingFile("/dir1/dir2/dir6/file7");
- assertTrue(m.isDirectory(d4));
- assertFalse(m.isDirectory(f7));
-
- // any intermediate directory exists implicitly
- assertTrue(m.isDirectory(createFile("/")));
- assertTrue(m.isDirectory(createFile("/dir1")));
- assertTrue(m.isDirectory(createFile("/dir1", "dir2")));
- assertTrue(m.isDirectory(createFile("/dir1", "dir2", "dir3")));
- assertTrue(m.isDirectory(createFile("/dir1", "dir2", "dir6")));
-
- assertEquals(
- "[/dir1/dir2/dir3/dir4]",
- Arrays.toString(m.getExistingFolders()));
- }
-
- public void testDelete() {
- m.recordExistingFolder("/dir1");
- m.recordExistingFile("/dir1/file1");
- m.recordExistingFile("/dir1/file2");
-
- assertEquals(
- "[/dir1/file1, /dir1/file2]",
- Arrays.toString(m.getExistingFiles()));
-
- assertTrue(m.delete(createFile("/dir1", "file1")));
- assertFalse(m.delete(createFile("/dir1", "file3")));
- assertFalse(m.delete(createFile("/dir2", "file2")));
- assertEquals(
- "[/dir1/file2]",
- Arrays.toString(m.getExistingFiles()));
-
- // deleting a directory with files in it fails
- assertFalse(m.delete(createFile("/dir1")));
- // but it works if the directory is empty
- assertTrue(m.delete(createFile("/dir1", "file2")));
- assertTrue(m.delete(createFile("/dir1")));
- }
-
- public void testListFiles() {
- m.recordExistingFolder("/dir1");
- m.recordExistingFile("/dir1/file1");
- m.recordExistingFile("/dir1/file2");
- m.recordExistingFile("/dir1/dir2/file3");
- m.recordExistingFile("/dir4/file4");
-
- assertEquals(
- "[]",
- m.getAgnosticAbsPath(Arrays.toString(m.listFiles(createFile("/not_a_dir")))));
-
- assertEquals(
- "[/dir1/dir2/file3]",
- m.getAgnosticAbsPath(Arrays.toString(m.listFiles(createFile("/dir1", "dir2")))));
-
- assertEquals(
- "[/dir1/dir2/file3, /dir1/file1, /dir1/file2]",
- m.getAgnosticAbsPath(Arrays.toString(m.listFiles(createFile("/dir1")))));
- }
-
- public void testMkDirs() {
- assertEquals("[]", Arrays.toString(m.getExistingFolders()));
-
- assertTrue(m.mkdirs(createFile("/dir1")));
- assertEquals("[/, /dir1]", Arrays.toString(m.getExistingFolders()));
-
- m.recordExistingFolder("/dir1");
- assertEquals("[/, /dir1]", Arrays.toString(m.getExistingFolders()));
-
- assertTrue(m.mkdirs(createFile("/dir1/dir2/dir3")));
- assertEquals(
- "[/, /dir1, /dir1/dir2, /dir1/dir2/dir3]",
- Arrays.toString(m.getExistingFolders()));
- }
-
- public void testRenameTo() {
- m.recordExistingFile("/dir1/dir2/dir6/file7");
- m.recordExistingFolder("/dir1/dir2/dir3/dir4");
-
- assertEquals("[/dir1/dir2/dir6/file7]", Arrays.toString(m.getExistingFiles()));
- assertEquals("[/dir1/dir2/dir3/dir4]", Arrays.toString(m.getExistingFolders()));
-
- assertTrue(m.renameTo(createFile("/dir1", "dir2"), createFile("/dir1", "newDir2")));
- assertEquals("[/dir1/newDir2/dir6/file7]", Arrays.toString(m.getExistingFiles()));
- assertEquals("[/dir1/newDir2/dir3/dir4]", Arrays.toString(m.getExistingFolders()));
-
- assertTrue(m.renameTo(
- createFile("/dir1", "newDir2", "dir6", "file7"),
- createFile("/dir1", "newDir2", "dir6", "newFile7")));
- assertTrue(m.renameTo(
- createFile("/dir1", "newDir2", "dir3", "dir4"),
- createFile("/dir1", "newDir2", "dir3", "newDir4")));
- assertEquals("[/dir1/newDir2/dir6/newFile7]", Arrays.toString(m.getExistingFiles()));
- assertEquals("[/dir1/newDir2/dir3/newDir4]", Arrays.toString(m.getExistingFolders()));
- }
-
- public void testNewFileOutputStream() throws Exception {
- assertEquals("[]", Arrays.toString(m.getOutputStreams()));
-
- File f = createFile("/dir1", "dir2", "simple ascii");
- OutputStream os = m.newFileOutputStream(f);
- assertNotNull(os);
- os.write("regular ascii".getBytes("UTF-8"));
- os.close();
-
- f = createFile("/dir1", "dir2", "utf-8 test");
- os = m.newFileOutputStream(f);
- assertNotNull(os);
- os.write("nihongo in UTF-8: 日本語".getBytes("UTF-8"));
- os.close();
-
- f = createFile("/dir1", "dir2", "forgot to close");
- os = m.newFileOutputStream(f);
- assertNotNull(os);
- os.write("wrote stuff but not closing the stream".getBytes("UTF-8"));
-
- assertEquals(
- "[</dir1/dir2/simple ascii: 'regular ascii'>, " +
- "</dir1/dir2/utf-8 test: 'nihongo in UTF-8: 日本語'>, " +
- "</dir1/dir2/forgot to close: (stream not closed properly)>]",
- Arrays.toString(m.getOutputStreams()));
- }
-
- public void testMakeRelative() throws Exception {
- assertEquals("dir3",
- FileOp.makeRelativeImpl("/dir1/dir2",
- "/dir1/dir2/dir3",
- false, "/"));
-
- assertEquals("../../../dir3",
- FileOp.makeRelativeImpl("/dir1/dir2/dir4/dir5/dir6",
- "/dir1/dir2/dir3",
- false, "/"));
-
- assertEquals("dir3/dir4/dir5/dir6",
- FileOp.makeRelativeImpl("/dir1/dir2/",
- "/dir1/dir2/dir3/dir4/dir5/dir6",
- false, "/"));
-
- // case-sensitive on non-Windows.
- assertEquals("../DIR2/dir3/DIR4/dir5/DIR6",
- FileOp.makeRelativeImpl("/dir1/dir2/",
- "/dir1/DIR2/dir3/DIR4/dir5/DIR6",
- false, "/"));
-
- // same path: empty result.
- assertEquals("",
- FileOp.makeRelativeImpl("/dir1/dir2/dir3",
- "/dir1/dir2/dir3",
- false, "/"));
-
- // same drive letters on Windows
- assertEquals("..\\..\\..\\dir3",
- FileOp.makeRelativeImpl("C:\\dir1\\dir2\\dir4\\dir5\\dir6",
- "C:\\dir1\\dir2\\dir3",
- true, "\\"));
-
- // not case-sensitive on Windows, results will be mixed.
- assertEquals("dir3/DIR4/dir5/DIR6",
- FileOp.makeRelativeImpl("/DIR1/dir2/",
- "/dir1/DIR2/dir3/DIR4/dir5/DIR6",
- true, "/"));
-
- // UNC path on Windows
- assertEquals("..\\..\\..\\dir3",
- FileOp.makeRelativeImpl("\\\\myserver.domain\\dir1\\dir2\\dir4\\dir5\\dir6",
- "\\\\myserver.domain\\dir1\\dir2\\dir3",
- true, "\\"));
-
- // different drive letters are not supported
- try {
- FileOp.makeRelativeImpl("C:\\dir1\\dir2\\dir4\\dir5\\dir6",
- "D:\\dir1\\dir2\\dir3",
- true, "\\");
- fail("Expected: IOException. Actual: no exception.");
- } catch (IOException e) {
- assertEquals("makeRelative: incompatible drive letters", e.getMessage());
- }
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/CaptureErrorHandler.java b/base/sdklib/src/test/java/com/android/sdklib/repository/CaptureErrorHandler.java
deleted file mode 100755
index 0cc7a48..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/CaptureErrorHandler.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-import static org.junit.Assert.fail;
-
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-/**
- * A SAX error handler that captures the errors and warnings.
- * This allows us to capture *all* errors and just not get an exception on the first one.
- */
-class CaptureErrorHandler implements ErrorHandler {
-
- private String mWarnings = "";
- private String mErrors = "";
-
- public String getErrors() {
- return mErrors;
- }
-
- public String getWarnings() {
- return mWarnings;
- }
-
- /**
- * Verifies if the handler captures some errors or warnings.
- * Prints them on stderr.
- * Also fails the unit test if any error was generated.
- */
- public void verify() {
- if (!mWarnings.isEmpty()) {
- System.err.println(mWarnings);
- }
-
- if (!mErrors.isEmpty()) {
- System.err.println(mErrors);
- fail(mErrors);
- }
- }
-
- /**
- * @throws SAXException
- */
- @Override
- public void error(SAXParseException ex) throws SAXException {
- mErrors += "Error: " + ex.getMessage() + "\n";
- }
-
- /**
- * @throws SAXException
- */
- @Override
- public void fatalError(SAXParseException ex) throws SAXException {
- mErrors += "Fatal Error: " + ex.getMessage() + "\n";
- }
-
- /**
- * @throws SAXException
- */
- @Override
- public void warning(SAXParseException ex) throws SAXException {
- mWarnings += "Warning: " + ex.getMessage() + "\n";
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java
deleted file mode 100755
index 471a4e8..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-import com.android.annotations.Nullable;
-
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.InputStream;
-import java.io.StringReader;
-
-import javax.xml.XMLConstants;
-import javax.xml.transform.Source;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-/**
- * Tests local validation of an SDK Addon sample XMLs using an XML Schema validator.
- */
-public class ValidateAddonXmlTest extends ValidateTestCase {
-
- private static String OPEN_TAG_ADDON =
- "<r:sdk-addon xmlns:r=\"http://schemas.android.com/sdk/android/addon/" +
- Integer.toString(SdkAddonConstants.NS_LATEST_VERSION) +
- "\">";
- private static String CLOSE_TAG_ADDON = "</r:sdk-addon>";
-
- // --- Tests ------------
-
- /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
- public void testAddonLatestVersionNumber() throws Exception {
- CaptureErrorHandler handler = new CaptureErrorHandler();
-
- // There should be a schema matching NS_LATEST_VERSION
- assertNotNull(getAddonValidator(SdkAddonConstants.NS_LATEST_VERSION, handler));
-
- // There should NOT be a schema with NS_LATEST_VERSION+1
- assertNull(
- String.format(
- "There's an ADDON XSD at version %d but SdkAddonConstants.NS_LATEST_VERSION is still set to %d.",
- SdkAddonConstants.NS_LATEST_VERSION + 1,
- SdkAddonConstants.NS_LATEST_VERSION),
- getAddonValidator(SdkAddonConstants.NS_LATEST_VERSION + 1, handler));
- }
-
- /** Validate the XSD version 1 */
- public void testValidateAddonXsd1() throws Exception {
- validateXsd(SdkAddonConstants.getXsdStream(1));
- }
-
- /** Validate a valid sample using namespace version 1 using an InputStream */
- public void testValidateLocalAddonFile1() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_1.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getAddonValidator(1, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 2 */
- public void testValidateAddonXsd2() throws Exception {
- validateXsd(SdkAddonConstants.getXsdStream(2));
- }
-
- /** Validate a valid sample using namespace version 2 using an InputStream */
- public void testValidateLocalAddonFile2() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_2.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getAddonValidator(2, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 3 */
- public void testValidateAddonXsd3() throws Exception {
- validateXsd(SdkAddonConstants.getXsdStream(3));
- }
-
- /** Validate a valid sample using namespace version 3 using an InputStream */
- public void testValidateLocalAddonFile3() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_3.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getAddonValidator(3, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 4 */
- public void testValidateAddonXsd4() throws Exception {
- validateXsd(SdkAddonConstants.getXsdStream(4));
- }
-
- /** Validate a valid sample using namespace version 4 using an InputStream */
- public void testValidateLocalAddonFile4() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_4.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getAddonValidator(4, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 5 */
- public void testValidateAddonXsd5() throws Exception {
- validateXsd(SdkAddonConstants.getXsdStream(5));
- }
-
- /** Validate a valid sample using namespace version 5 using an InputStream */
- public void testValidateLocalAddonFile5() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_5.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getAddonValidator(5, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 6 */
- public void testValidateAddonXsd6() throws Exception {
- validateXsd(SdkAddonConstants.getXsdStream(6));
- }
-
- /** Validate a valid sample using namespace version 6 using an InputStream */
- public void testValidateLocalAddonFile6() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_6.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getAddonValidator(6, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 7 */
- public void testValidateAddonXsd7() throws Exception {
- validateXsd(SdkAddonConstants.getXsdStream(7));
- }
-
- /** Validate a valid sample using namespace version 7 using an InputStream */
- public void testValidateLocalAddonFile7() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_7.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getAddonValidator(7, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Make sure we don't have a next-version sample that is not validated yet */
- public void testValidateLocalAddonFile8() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addon_sample_8.xml");
- assertNull(xmlStream);
- }
-
- // IMPORTANT: each time you add a test here, you should add a corresponding
- // test in SdkAddonSourceTest to validate the XML content is parsed correctly.
-
- // ----
-
- /**
- * An addon does not support a codename.
- * There used to be a typo in the repository.XSD versions 1-2 & the addon XSD versions 1-2
- * where addons had an optional element 'codename'. This was a typo and it's been fixed.
- */
- public void testAddonCodename() throws Exception {
- // we define a license named "lic1" and then reference "lic2" instead
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_ADDON +
- "<r:license id=\"lic1\"> some license </r:license> " +
- "<r:add-on> <r:uses-license ref=\"lic1\" /> <r:revision>1</r:revision> " +
- "<r:name-id>AddonName</r:name-id> <r:name-display>The Addon Name</r:name-display> " +
- "<r:vendor-id>AddonVendor</r:vendor-id> <r:vendor-display>The Addon Vendor</r:vendor-display> " +
- "<r:api-level>42</r:api-level> " +
- "<r:codename>Addons do not support codenames</r:codenames> " +
- "<r:libs><r:lib><r:name>com.example.LibName</r:name></r:lib></r:libs> " +
- "<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
- "<r:url>url</r:url> </r:archive> </r:archives> </r:add-on>" +
- CLOSE_TAG_ADDON;
-
- Source source = new StreamSource(new StringReader(document));
-
- // don't capture the validator errors, we want it to fail and catch the exception
- Validator validator = getAddonValidator(SdkAddonConstants.NS_LATEST_VERSION, null);
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect a parse error referring to this grammar rule
- assertRegex("cvc-complex-type.2.4.a: Invalid content was found starting with element 'r:codename'.*",
- e.getMessage());
- return;
- }
- // If we get here, the validator has not failed as we expected it to.
- fail();
- }
-
- /** A document with a slash in an extra path. */
- public void testExtraPathWithSlash() throws Exception {
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_ADDON +
- "<r:extra> <r:revision><r:major>1</r:major></r:revision> <r:path>path/cannot\\contain\\segments</r:path> " +
- "<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
- "<r:url>url</r:url> </r:archive> </r:archives> </r:extra>" +
- CLOSE_TAG_ADDON;
-
- Source source = new StreamSource(new StringReader(document));
-
- // don't capture the validator errors, we want it to fail and catch the exception
- Validator validator = getAddonValidator(SdkAddonConstants.NS_LATEST_VERSION, null);
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect a parse error referring to this grammar rule
- assertRegex("cvc-pattern-valid: Value 'path/cannot\\\\contain\\\\segments' is not facet-valid with respect to pattern.*",
- e.getMessage());
- return;
- }
- // If we get here, the validator has not failed as we expected it to.
- fail();
- }
-
- // --- Helpers ------------
-
- /**
- * Helper method that returns a validator for our Addon XSD
- *
- * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
- * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
- * which will most likely print errors to stderr.
- */
- private Validator getAddonValidator(int version, @Nullable CaptureErrorHandler handler)
- throws SAXException {
- Validator validator = null;
- InputStream xsdStream = SdkAddonConstants.getXsdStream(version);
- if (xsdStream != null) {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
- validator = schema.newValidator();
- if (handler != null) {
- validator.setErrorHandler(handler);
- }
- }
-
- return validator;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonsListXmlTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonsListXmlTest.java
deleted file mode 100755
index 8bb335d..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonsListXmlTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-import com.android.annotations.Nullable;
-
-import org.xml.sax.SAXException;
-
-import java.io.InputStream;
-
-import javax.xml.XMLConstants;
-import javax.xml.transform.Source;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-/**
- * Tests local validation of an SDK Addon-List sample XMLs using an XML Schema validator.
- */
-public class ValidateAddonsListXmlTest extends ValidateTestCase {
-
- // --- Tests ------------
-
- /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
- public void testAddonLatestVersionNumber() throws Exception {
- CaptureErrorHandler handler = new CaptureErrorHandler();
-
- // There should be a schema matching NS_LATEST_VERSION
- assertNotNull(getValidator(SdkAddonsListConstants.NS_LATEST_VERSION, handler));
-
- // There should NOT be a schema with NS_LATEST_VERSION+1
- assertNull(
- String.format(
- "There's an ADDON XSD at version %d but SdkAddonsListConstants.NS_LATEST_VERSION is still set to %d.",
- SdkAddonsListConstants.NS_LATEST_VERSION + 1,
- SdkAddonsListConstants.NS_LATEST_VERSION),
- getValidator(SdkAddonsListConstants.NS_LATEST_VERSION + 1, handler));
- }
-
- /** Validate the XSD version 1 */
- public void testValidateAddonsListXsd1() throws Exception {
- validateXsd(SdkAddonsListConstants.getXsdStream(1));
- }
-
- /** Validate a valid sample using namespace version 1 using an InputStream */
- public void testValidateLocalAddonsListFile1() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addons_list_sample_1.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(1, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 2 */
- public void testValidateAddonsListXsd2() throws Exception {
- validateXsd(SdkAddonsListConstants.getXsdStream(2));
- }
-
- /** Validate a valid sample using namespace version 2 using an InputStream */
- public void testValidateLocalAddonsListFile2() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addons_list_sample_2.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(2, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Make sure we don't have a next-version sample that is not validated yet */
- public void testValidateLocalAddonsListFile3() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/addons_list_sample_3.xml");
- assertNull(xmlStream);
- }
-
- // IMPORTANT: each time you add a test here, you should add a corresponding
- // test in AddonsListFetcherTest to validate the XML content is parsed correctly.
-
- // --- Helpers ------------
-
- /**
- * Helper method that returns a validator for our Addons-List XSD
- *
- * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
- * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
- * which will most likely print errors to stderr.
- */
- private Validator getValidator(int version, @Nullable CaptureErrorHandler handler)
- throws SAXException {
- Validator validator = null;
- InputStream xsdStream = SdkAddonsListConstants.getXsdStream(version);
- if (xsdStream != null) {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
- validator = schema.newValidator();
- if (handler != null) {
- validator.setErrorHandler(handler);
- }
- }
-
- return validator;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateRepositoryXmlTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
deleted file mode 100755
index 20fd409..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-import com.android.annotations.Nullable;
-
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.InputStream;
-import java.io.StringReader;
-
-import javax.xml.XMLConstants;
-import javax.xml.transform.Source;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-/**
- * Tests local validation of an SDK Repository sample XMLs using an XML Schema validator.
- *
- * References:
- * http://www.ibm.com/developerworks/xml/library/x-javaxmlvalidapi.html
- */
-public class ValidateRepositoryXmlTest extends ValidateTestCase {
-
- private static String OPEN_TAG_REPO =
- "<r:sdk-repository xmlns:r=\"http://schemas.android.com/sdk/android/repository/" +
- Integer.toString(SdkRepoConstants.NS_LATEST_VERSION) +
- "\">";
- private static String CLOSE_TAG_REPO = "</r:sdk-repository>";
-
- // --- Tests ------------
-
- /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
- public void testRepoLatestVersionNumber() throws Exception {
- CaptureErrorHandler handler = new CaptureErrorHandler();
-
- // There should be a schema matching NS_LATEST_VERSION
- assertNotNull(getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, handler));
-
- // There should NOT be a schema with NS_LATEST_VERSION+1
- assertNull(
- String.format(
- "There's a REPO XSD at version %d but SdkRepoConstants.NS_LATEST_VERSION is still set to %d.",
- SdkRepoConstants.NS_LATEST_VERSION + 1,
- SdkRepoConstants.NS_LATEST_VERSION),
- getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION + 1, handler));
- }
-
- /** Validate the XSD version 1 */
- public void testValidateRepositoryXsd1() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(1));
- }
-
- /** Validate a valid sample using namespace version 1 using an InputStream */
- public void testValidateLocalRepositoryFile1() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_01.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(1, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 2 */
- public void testValidateRepositoryXsd2() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(2));
- }
-
- /** Validate a valid sample using namespace version 2 using an InputStream */
- public void testValidateLocalRepositoryFile2() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_02.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(2, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 3 */
- public void testValidateRepositoryXsd3() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(3));
- }
-
- /** Validate a valid sample using namespace version 3 using an InputStream */
- public void testValidateLocalRepositoryFile3() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_03.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(3, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 4 */
- public void testValidateRepositoryXsd4() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(4));
- }
-
- /** Validate a valid sample using namespace version 4 using an InputStream */
- public void testValidateLocalRepositoryFile4() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_04.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(4, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 5 */
- public void testValidateRepositoryXsd5() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(5));
- }
-
- /** Validate a valid sample using namespace version 5 using an InputStream */
- public void testValidateLocalRepositoryFile5() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_05.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(5, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 6 */
- public void testValidateRepositoryXsd6() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(6));
- }
-
- /** Validate a valid sample using namespace version 6 using an InputStream */
- public void testValidateLocalRepositoryFile6() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_06.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(6, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 7 */
- public void testValidateRepositoryXsd7() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(7));
- }
-
- /** Validate a valid sample using namespace version 7 using an InputStream */
- public void testValidateLocalRepositoryFile7() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_07.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(7, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 8 */
- public void testValidateRepositoryXsd8() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(8));
- }
-
- /** Validate a valid sample using namespace version 8 using an InputStream */
- public void testValidateLocalRepositoryFile8() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_08.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(8, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 9 */
- public void testValidateRepositoryXsd9() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(9));
- }
-
- /** Validate a valid sample using namespace version 9 using an InputStream */
- public void testValidateLocalRepositoryFile9() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_09.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(9, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 10 */
- public void testValidateRepositoryXsd10() throws Exception {
- validateXsd(SdkRepoConstants.getXsdStream(10));
- }
-
- /** Validate a valid sample using namespace version 10 using an InputStream */
- public void testValidateLocalRepositoryFile10() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_10.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(10, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Make sure we don't have a next-version sample that is not validated yet */
- public void testValidateLocalRepositoryFile11() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/repository_sample_11.xml");
- assertNull(xmlStream);
- }
-
- // IMPORTANT: each time you add a test here, you should add a corresponding
- // test in SdkRepoSourceTest to validate the XML content is parsed correctly.
-
-
- // ---
-
- /** A document should at least have a root to be valid */
- public void testEmptyXml() throws Exception {
- String document = "<?xml version=\"1.0\"?>";
-
- Source source = new StreamSource(new StringReader(document));
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, handler);
-
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect to get this specific exception message
- assertRegex("Premature end of file.*", e.getMessage());
- return;
- }
- // We shouldn't get here
- handler.verify();
- fail();
- }
-
- /** A document with a root element containing no platform, addon, etc., is valid. */
- public void testEmptyRootXml() throws Exception {
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_REPO +
- CLOSE_TAG_REPO;
-
- Source source = new StreamSource(new StringReader(document));
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** A document with an unknown element. */
- public void testUnknownContentXml() throws Exception {
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_REPO +
- "<r:unknown />" +
- CLOSE_TAG_REPO;
-
- Source source = new StreamSource(new StringReader(document));
-
- // don't capture the validator errors, we want it to fail and catch the exception
- Validator validator = getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, null);
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect a parse expression referring to this grammar rule
- assertRegex("cvc-complex-type.2.4.a: Invalid content was found.*", e.getMessage());
- return;
- }
- // If we get here, the validator has not failed as we expected it to.
- fail();
- }
-
- /** A document with an incomplete element. */
- public void testIncompleteContentXml() throws Exception {
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_REPO +
- "<r:platform> <r:api-level>1</r:api-level> <r:libs /> </r:platform>" +
- CLOSE_TAG_REPO;
-
- Source source = new StreamSource(new StringReader(document));
-
- // don't capture the validator errors, we want it to fail and catch the exception
- Validator validator = getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, null);
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect a parse error referring to this grammar rule
- assertRegex("cvc-complex-type.2.4.a: Invalid content was found.*", e.getMessage());
- return;
- }
- // If we get here, the validator has not failed as we expected it to.
- fail();
- }
-
- /** A document with a wrong type element. */
- public void testWrongTypeContentXml() throws Exception {
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_REPO +
- "<r:platform> <r:api-level>NotAnInteger</r:api-level> <r:libs /> </r:platform>" +
- CLOSE_TAG_REPO;
-
- Source source = new StreamSource(new StringReader(document));
-
- // don't capture the validator errors, we want it to fail and catch the exception
- Validator validator = getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, null);
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect a parse error referring to this grammar rule
- assertRegex("cvc-datatype-valid.1.2.1: 'NotAnInteger' is not a valid value.*",
- e.getMessage());
- return;
- }
- // If we get here, the validator has not failed as we expected it to.
- fail();
- }
-
- /** A document with an unknown license id. */
- public void testLicenseIdNotFound() throws Exception {
- // we define a license named "lic1" and then reference "lic2" instead
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_REPO +
- "<r:license id=\"lic1\"> some license </r:license> " +
- "<r:tool> <r:uses-license ref=\"lic2\" /> <r:revision> <r:major>1</r:major> </r:revision> " +
- "<r:min-platform-tools-rev> <r:major>1</r:major> </r:min-platform-tools-rev> " +
- "<r:archives> <r:archive> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
- "<r:url>url</r:url> </r:archive> </r:archives> </r:tool>" +
- CLOSE_TAG_REPO;
-
- Source source = new StreamSource(new StringReader(document));
-
- // don't capture the validator errors, we want it to fail and catch the exception
- Validator validator = getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, null);
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect a parse error referring to this grammar rule
- assertRegex("cvc-id.1: There is no ID/IDREF binding for IDREF 'lic2'.*",
- e.getMessage());
- return;
- }
- // If we get here, the validator has not failed as we expected it to.
- fail();
- }
-
- /** The latest XSD repository-6 should fail when an 'extra' is present. */
- public void testExtraPathWithSlash() throws Exception {
- String document = "<?xml version=\"1.0\"?>" +
- OPEN_TAG_REPO +
- "<r:extra> <r:revision>1</r:revision> <r:path>path</r:path> " +
- "<r:archives> <r:archive> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
- "<r:url>url</r:url> </r:archive> </r:archives> </r:extra>" +
- CLOSE_TAG_REPO;
-
- Source source = new StreamSource(new StringReader(document));
-
- // don't capture the validator errors, we want it to fail and catch the exception
- Validator validator = getRepoValidator(SdkRepoConstants.NS_LATEST_VERSION, null);
- try {
- validator.validate(source);
- } catch (SAXParseException e) {
- // We expect a parse error referring to this grammar rule
- assertRegex("cvc-complex-type.2.4.a: Invalid content was found starting with element 'r:extra'.*",
- e.getMessage());
- return;
- }
- // If we get here, the validator has not failed as we expected it to.
- fail();
- }
-
- // --- Helpers ------------
-
- /**
- * Helper method that returns a validator for our Repository XSD
- *
- * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
- * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
- * which will most likely print errors to stderr.
- */
- private Validator getRepoValidator(int version, @Nullable CaptureErrorHandler handler)
- throws SAXException {
- Validator validator = null;
- InputStream xsdStream = SdkRepoConstants.getXsdStream(version);
- if (xsdStream != null) {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
- validator = schema.newValidator();
-
- if (handler != null) {
- validator.setErrorHandler(handler);
- }
- }
-
- return validator;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateSysImgXmlTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateSysImgXmlTest.java
deleted file mode 100755
index 6fecada..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateSysImgXmlTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-import com.android.annotations.Nullable;
-
-import org.xml.sax.SAXException;
-
-import java.io.InputStream;
-
-import javax.xml.XMLConstants;
-import javax.xml.transform.Source;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-/**
- * Tests local validation of an SDK Repository sample XMLs using an XML Schema validator.
- *
- * References:
- * http://www.ibm.com/developerworks/xml/library/x-javaxmlvalidapi.html
- */
-public class ValidateSysImgXmlTest extends ValidateTestCase {
-
- // --- Tests ------------
-
- /** Validates that NS_LATEST_VERSION points to the max available XSD schema. */
- public void testSysImgLatestVersionNumber() throws Exception {
- CaptureErrorHandler handler = new CaptureErrorHandler();
-
- // There should be a schema matching NS_LATEST_VERSION
- assertNotNull(getValidator(SdkSysImgConstants.NS_LATEST_VERSION, handler));
-
- // There should NOT be a schema with NS_LATEST_VERSION+1
- assertNull(
- String.format(
- "There's a REPO XSD at version %d but SdkSysImgConstants.NS_LATEST_VERSION is still set to %d.",
- SdkSysImgConstants.NS_LATEST_VERSION + 1,
- SdkSysImgConstants.NS_LATEST_VERSION),
- getValidator(SdkSysImgConstants.NS_LATEST_VERSION + 1, handler));
- }
-
- /** Validate the XSD version 1 */
- public void testValidateSysImgXsd1() throws Exception {
- validateXsd(SdkSysImgConstants.getXsdStream(1));
- }
-
- /** Validate a valid sample using namespace version 1 using an InputStream */
- public void testValidateLocalSysImgFile1() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/sys_img_sample_1.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(1, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 2 */
- public void testValidateSysImgXsd2() throws Exception {
- validateXsd(SdkSysImgConstants.getXsdStream(2));
- }
-
- /** Validate a valid sample using namespace version 2 using an InputStream */
- public void testValidateLocalSysImgFile2() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/sys_img_sample_2.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(2, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Validate the XSD version 3 */
- public void testValidateSysImgXsd3() throws Exception {
- validateXsd(SdkSysImgConstants.getXsdStream(3));
- }
-
- /** Validate a valid sample using namespace version 3 using an InputStream */
- public void testValidateLocalSysImgFile3() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/sys_img_sample_3.xml");
- Source source = new StreamSource(xmlStream);
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- Validator validator = getValidator(3, handler);
- validator.validate(source);
- handler.verify();
- }
-
- /** Make sure we don't have a next-version sample that is not validated yet */
- public void testValidateLocalSysImgFile4() throws Exception {
- InputStream xmlStream = this.getClass().getResourceAsStream(
- "/com/android/sdklib/testdata/sys_img_sample_4.xml");
- assertNull(xmlStream);
- }
-
- // IMPORTANT: each time you add a test here, you should add a corresponding
- // test in SdkSysImgSourceTest to validate the XML content is parsed correctly.
-
-
- // --- Helpers ------------
-
- /**
- * Helper method that returns a validator for our Repository XSD
- *
- * @param version The version number, in range {@code 1..NS_LATEST_VERSION}
- * @param handler A {@link CaptureErrorHandler}. If null the default will be used,
- * which will most likely print errors to stderr.
- */
- private Validator getValidator(int version, @Nullable CaptureErrorHandler handler)
- throws SAXException {
- Validator validator = null;
- InputStream xsdStream = SdkSysImgConstants.getXsdStream(version);
- if (xsdStream != null) {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- Schema schema = factory.newSchema(new StreamSource(xsdStream));
- validator = schema.newValidator();
-
- if (handler != null) {
- validator.setErrorHandler(handler);
- }
- }
-
- return validator;
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateTestCase.java b/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateTestCase.java
deleted file mode 100755
index 26d031e..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/ValidateTestCase.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.sdklib.repository;
-
-import org.w3c.dom.ls.LSInput;
-import org.w3c.dom.ls.LSResourceResolver;
-import org.xml.sax.SAXException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-
-import javax.xml.XMLConstants;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-import junit.framework.TestCase;
-
-abstract class ValidateTestCase extends TestCase {
-
- /**
- * Validates an XSD stream against the w3.org XSD schema.
- */
- protected void validateXsd(InputStream repoXsdStream) throws SAXException, IOException {
- final Class<? extends ValidateTestCase> clazz = this.getClass();
- InputStream xsdXsdStream = clazz.getResourceAsStream(
- "/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd");
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- factory.setResourceResolver(new LSResourceResolver() {
- @Override
- public LSInput resolveResource(
- String type,
- String namespaceURI,
- final String publicId,
- final String systemId,
- final String baseURI) {
- if (systemId != null) {
- String resName = "/com/android/sdklib/testdata/www.w3.org/2001/";
- int pos = systemId.lastIndexOf('/');
- if (pos < 0) {
- resName += systemId;
- } else {
- resName += systemId.substring(pos + 1);
- }
- final InputStream stream = clazz.getResourceAsStream(resName);
- if (stream == null) {
- fail("XSD validation requires missing file: " + resName);
- }
- return new LSInput() {
- @SuppressWarnings("hiding")
- @Override
- public void setSystemId(String systemId) {}
-
- @Override
- public void setStringData(String stringData) {}
-
- @SuppressWarnings("hiding")
- @Override
- public void setPublicId(String publicId) {}
-
- @Override
- public void setEncoding(String encoding) {}
-
- @Override
- public void setCharacterStream(Reader characterStream) {}
-
- @Override
- public void setCertifiedText(boolean certifiedText) {}
-
- @Override
- public void setByteStream(InputStream byteStream) {}
-
- @SuppressWarnings("hiding")
- @Override
- public void setBaseURI(String baseURI) {}
-
- @Override
- public String getSystemId() {
- return systemId;
- }
-
- @Override
- public String getStringData() {
- return null;
- }
-
- @Override
- public String getPublicId() {
- return publicId;
- }
-
- @Override
- public String getEncoding() {
- return null;
- }
-
- @Override
- public Reader getCharacterStream() {
- return null;
- }
-
- @Override
- public boolean getCertifiedText() {
- return false;
- }
-
- @Override
- public InputStream getByteStream() {
- return stream;
- }
-
- @Override
- public String getBaseURI() {
- return baseURI;
- }
- };
- }
- return null;
- }
- });
- Schema schema = factory.newSchema(new StreamSource(xsdXsdStream));
- Validator validator = schema.newValidator();
-
- CaptureErrorHandler handler = new CaptureErrorHandler();
- validator.setErrorHandler(handler);
-
- validator.validate(new StreamSource(repoXsdStream));
- handler.verify();
- }
-
- /** An helper that validates a string against an expected regexp. */
- protected void assertRegex(String expectedRegexp, String actualString) {
- assertNotNull(actualString);
- assertTrue(
- String.format("Regexp Assertion Failed:\nExpected: %s\nActual: %s\n",
- expectedRegexp, actualString),
- actualString.matches(expectedRegexp));
- }
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java
deleted file mode 100755
index e01b81a..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java
+++ /dev/null
@@ -1,936 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.NoPreviewRevision;
-
-import java.io.File;
-import java.util.Arrays;
-
-import junit.framework.TestCase;
-
-public class PkgDescTest extends TestCase {
-
- public final File mRoot = new File("/sdk");
-
- public final void testPkgDescTool_NotPreview() {
- IPkgDesc p = PkgDesc.Builder.newTool(
- new FullRevision(1, 2, 3),
- new FullRevision(5, 6, 7, 8)).create();
-
- assertEquals(PkgType.PKG_TOOLS, p.getType());
-
- assertTrue (p.hasFullRevision());
- assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
- assertFalse (p.getFullRevision().isPreview());
-
- assertFalse(p.hasMajorRevision());
- assertNull (p.getMajorRevision());
-
- assertFalse(p.hasAndroidVersion());
- assertNull (p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertTrue (p.hasMinPlatformToolsRev());
- assertEquals(new FullRevision(5, 6, 7, 8), p.getMinPlatformToolsRev());
-
- assertEquals("tools", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "tools"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=tools FullRev=1.2.3 MinPlatToolsRev=5.6.7 rc8>", p.toString());
- assertEquals("Android SDK Tools 1.2.3", p.getListDescription());
- }
-
- public final void testPkgDescTool_Preview() {
- IPkgDesc p = PkgDesc.Builder.newTool(
- new FullRevision(1, 2, 3, 4),
- new FullRevision(5, 6, 7, 8)).create();
-
- assertEquals(PkgType.PKG_TOOLS, p.getType());
-
- assertTrue (p.hasFullRevision());
- assertEquals(new FullRevision(1, 2, 3, 4), p.getFullRevision());
- assertTrue (p.getFullRevision().isPreview());
-
- assertFalse(p.hasMajorRevision());
- assertNull (p.getMajorRevision());
-
- assertFalse(p.hasAndroidVersion());
- assertNull (p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertTrue (p.hasMinPlatformToolsRev());
- assertEquals(new FullRevision(5, 6, 7, 8), p.getMinPlatformToolsRev());
-
- assertEquals("tools-preview", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "tools"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=tools FullRev=1.2.3 rc4 MinPlatToolsRev=5.6.7 rc8>", p.toString());
- assertEquals("Android SDK Tools 1.2.3 rc4", p.getListDescription());
- }
-
- public final void testPkgDescTool_Update() {
- final FullRevision min5670 = new FullRevision(5, 6, 7, 0);
- final IPkgDesc f123 =
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 0), min5670).create();
- final IPkgDesc f123b =
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 0), min5670).create();
-
- // can't update itself
- assertFalse(f123 .isUpdateFor(f123b));
- assertFalse(f123b.isUpdateFor(f123));
- assertTrue (f123 .compareTo(f123b) == 0);
- assertTrue (f123b.compareTo(f123 ) == 0);
-
- // min-platform-tools-rev isn't used for updates checks
- final FullRevision min5680 = new FullRevision(5, 6, 8, 0);
- final IPkgDesc f123c =
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 0), min5680).create();
- assertFalse(f123c.isUpdateFor(f123));
- // but it's used for comparisons
- assertTrue (f123c.compareTo(f123) > 0);
-
- // full revision is used for updated checks
- final IPkgDesc f124 =
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 4, 0), min5670).create();
- assertTrue (f124.isUpdateFor(f123));
- assertFalse(f123.isUpdateFor(f124));
- assertTrue (f124.compareTo(f123) > 0);
-
- final IPkgDesc f122 =
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 2, 0), min5670).create();
- assertTrue (f123.isUpdateFor(f122));
- assertFalse(f122.isUpdateFor(f123));
- assertTrue (f122.compareTo(f123) < 0);
-
- // previews are not updated by final packages
- final FullRevision min5671 = new FullRevision(5, 6, 7, 1);
- final IPkgDesc p1231 =
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 1), min5671).create();
- assertFalse(p1231.isUpdateFor(f122));
- assertFalse(f122 .isUpdateFor(p1231));
- assertFalse(p1231.isUpdateFor(f122, FullRevision.PreviewComparison.COMPARE_NUMBER));
- assertFalse(p1231.isUpdateFor(f122, FullRevision.PreviewComparison.COMPARE_TYPE));
- // ...unless we ignore them explicitly
- assertTrue(p1231.isUpdateFor(f122, FullRevision.PreviewComparison.IGNORE));
-
- // but previews are used for comparisons
- assertTrue (p1231.compareTo(f122 ) > 0);
- assertTrue (f123 .compareTo(p1231) > 0);
-
- final IPkgDesc p1232 =
- PkgDesc.Builder.newTool(new FullRevision(1, 2, 3, 2), min5671).create();
- assertTrue (p1232.isUpdateFor(p1231));
- assertFalse(p1231.isUpdateFor(p1232));
- assertTrue (p1232.compareTo(p1231) > 0);
- }
-
- //----
-
- public final void testPkgDescPlatformTool_NotPreview() {
- IPkgDesc p = PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3)).create();
-
- assertEquals(PkgType.PKG_PLATFORM_TOOLS, p.getType());
-
- assertTrue (p.hasFullRevision());
- assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
- assertFalse (p.getFullRevision().isPreview());
-
- assertFalse(p.hasMajorRevision());
- assertNull (p.getMajorRevision());
-
- assertFalse(p.hasAndroidVersion());
- assertNull (p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("platform-tools", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "platform-tools"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=platform_tools FullRev=1.2.3>", p.toString());
- assertEquals("Android SDK Platform-Tools 1.2.3", p.getListDescription());
- }
-
- public final void testPkgDescPlatformTool_Preview() {
- IPkgDesc p = PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 4)).create();
-
- assertEquals(PkgType.PKG_PLATFORM_TOOLS, p.getType());
-
- assertTrue (p.hasFullRevision());
- assertEquals(new FullRevision(1, 2, 3, 4), p.getFullRevision());
- assertTrue (p.getFullRevision().isPreview());
-
- assertFalse(p.hasMajorRevision());
- assertNull (p.getMajorRevision());
-
- assertFalse(p.hasAndroidVersion());
- assertNull (p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("platform-tools-preview", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "platform-tools"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=platform_tools FullRev=1.2.3 rc4>", p.toString());
- assertEquals("Android SDK Platform-Tools 1.2.3 rc4", p.getListDescription());
- }
-
- public final void testPkgDescPlatformTool_Update() {
- final IPkgDesc f123 =
- PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 0)).create();
- final IPkgDesc f123b =
- PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 0)).create();
-
- // can't update itself
- assertFalse(f123 .isUpdateFor(f123b));
- assertFalse(f123b.isUpdateFor(f123));
- assertTrue (f123 .compareTo(f123b) == 0);
- assertTrue (f123b.compareTo(f123 ) == 0);
-
- // full revision is used for updated checks
- final IPkgDesc f124 =
- PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 4, 0)).create();
- assertTrue (f124.isUpdateFor(f123));
- assertFalse(f123.isUpdateFor(f124));
- assertTrue (f124.compareTo(f123) > 0);
-
- final IPkgDesc f122 =
- PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 2, 0)).create();
- assertTrue (f123.isUpdateFor(f122));
- assertFalse(f122.isUpdateFor(f123));
- assertTrue (f122.compareTo(f123) < 0);
-
- // previews are not updated by final packages
- final IPkgDesc p1231 =
- PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 1)).create();
- assertFalse(p1231.isUpdateFor(f122));
- assertFalse(f122 .isUpdateFor(p1231));
- // but previews are used for comparisons
- assertTrue (p1231.compareTo(f122 ) > 0);
- assertTrue (f123 .compareTo(p1231) > 0);
-
- final IPkgDesc p1232 =
- PkgDesc.Builder.newPlatformTool(new FullRevision(1, 2, 3, 2)).create();
- assertTrue (p1232.isUpdateFor(p1231));
- assertFalse(p1231.isUpdateFor(p1232));
- assertTrue (p1232.compareTo(p1231) > 0);
- }
-
- //----
-
- public final void testPkgDescDoc() throws Exception {
- IPkgDesc p =
- PkgDesc.Builder.newDoc(new AndroidVersion("19"), new MajorRevision(1)).create();
-
- assertEquals(PkgType.PKG_DOC, p.getType());
-
- assertFalse(p.hasFullRevision());
- assertNull(p.getFullRevision());
-
- assertTrue(p.hasMajorRevision());
- assertEquals(new MajorRevision(1), p.getMajorRevision());
-
- assertTrue(p.hasAndroidVersion());
- assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull(p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull(p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull(p.getMinPlatformToolsRev());
-
- assertEquals("doc", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "docs"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=doc Android=API 19 MajorRev=1>", p.toString());
- assertEquals("Documentation for Android SDK", p.getListDescription());
- }
-
- public final void testPkgDescDoc_Update() throws Exception {
- final AndroidVersion api19 = new AndroidVersion("19");
- final MajorRevision rev1 = new MajorRevision(1);
- final IPkgDesc p19_1 = PkgDesc.Builder.newDoc(api19, rev1).create();
- final IPkgDesc p19_1b = PkgDesc.Builder.newDoc(api19, rev1).create();
-
- // can't update itself
- assertFalse(p19_1 .isUpdateFor(p19_1b));
- assertFalse(p19_1b.isUpdateFor(p19_1));
- assertTrue (p19_1 .compareTo(p19_1b) == 0);
- assertTrue (p19_1b.compareTo(p19_1 ) == 0);
-
- final IPkgDesc p19_2 = PkgDesc.Builder.newDoc(api19, new MajorRevision(2)).create();
- assertTrue (p19_2.isUpdateFor(p19_1));
- assertTrue (p19_2.compareTo(p19_1) > 0);
-
- final IPkgDesc p18_1 = PkgDesc.Builder.newDoc(new AndroidVersion("18"), rev1).create();
- assertTrue (p19_1.isUpdateFor(p18_1));
- assertFalse(p18_1.isUpdateFor(p19_1));
- assertTrue (p19_1.compareTo(p18_1) > 0);
- }
-
- //----
-
- public final void testPkgDescBuildTool_NotPreview() {
- IPkgDesc p = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3)).create();
-
- assertEquals(PkgType.PKG_BUILD_TOOLS, p.getType());
-
- assertTrue (p.hasFullRevision());
- assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
- assertFalse (p.getFullRevision().isPreview());
-
- assertFalse(p.hasMajorRevision());
- assertNull (p.getMajorRevision());
-
- assertFalse(p.hasAndroidVersion());
- assertNull (p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("build-tools-1.2.3", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "build-tools", "build-tools-1.2.3"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=build_tools FullRev=1.2.3>", p.toString());
- assertEquals("Android SDK Build-Tools 1.2.3", p.getListDescription());
- }
-
- public final void testPkgDescBuildTool_Preview() {
- IPkgDesc p = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 4)).create();
-
- assertEquals(PkgType.PKG_BUILD_TOOLS, p.getType());
-
- assertTrue (p.hasFullRevision());
- assertEquals(new FullRevision(1, 2, 3, 4), p.getFullRevision());
- assertTrue (p.getFullRevision().isPreview());
-
- assertFalse(p.hasMajorRevision());
- assertNull (p.getMajorRevision());
-
- assertFalse(p.hasAndroidVersion());
- assertNull (p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("build-tools-1.2.3-preview", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "build-tools", "build-tools-1.2.3-preview"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=build_tools FullRev=1.2.3 rc4>", p.toString());
- assertEquals("Android SDK Build-Tools 1.2.3 rc4", p.getListDescription());
- }
-
- public final void testPkgDescBuildTool_Update() {
- final IPkgDesc f123 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 0)).create();
- final IPkgDesc f123b = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 0)).create();
-
- // can't update itself
- assertFalse(f123 .isUpdateFor(f123b));
- assertFalse(f123b.isUpdateFor(f123));
- assertTrue (f123 .compareTo(f123b) == 0);
- assertTrue (f123b.compareTo(f123 ) == 0);
-
- // build-tools is different as full revisions are installed side by side
- // so they don't update each other (except for the preview bit, see below.)
- final IPkgDesc f124 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 4, 0)).create();
- assertFalse(f124.isUpdateFor(f123));
- assertFalse(f123.isUpdateFor(f124));
- // comparison is still done on the full revision.
- assertTrue (f124.compareTo(f123) > 0);
-
- final IPkgDesc f122 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 2, 0)).create();
- assertFalse(f123.isUpdateFor(f122));
- assertFalse(f122.isUpdateFor(f123));
- assertTrue (f122.compareTo(f123) < 0);
-
- // previews are not updated by final packages
- final IPkgDesc p1231 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 1)).create();
- assertFalse(p1231.isUpdateFor(f122));
- assertFalse(f122 .isUpdateFor(p1231));
- // but previews are used for comparisons
- assertTrue (p1231.compareTo(f122 ) > 0);
- assertTrue (f123 .compareTo(p1231) > 0);
-
- // previews do update other packages that have the same major.minor.micro.
- final IPkgDesc p1232 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 3, 2)).create()
- ;
- assertTrue (p1232.isUpdateFor(p1231));
- assertFalse(p1231.isUpdateFor(p1232));
- assertTrue (p1232.compareTo(p1231) > 0);
-
- final IPkgDesc p1222 = PkgDesc.Builder.newBuildTool(new FullRevision(1, 2, 2, 2)).create();
- assertFalse(p1232.isUpdateFor(p1222));
- }
-
- //----
-
- public final void testPkgDescExtra() {
- IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
- IPkgDesc p = PkgDesc.Builder
- .newExtra(vendor,
- "extra_path",
- "My Extra",
- new String[] { "old_path1", "old_path2" },
- new NoPreviewRevision(1, 2, 3))
- .create();
-
- assertEquals(PkgType.PKG_EXTRA, p.getType());
-
- assertTrue (p.hasFullRevision());
- assertEquals(new FullRevision(1, 2, 3), p.getFullRevision());
-
- assertFalse(p.hasMajorRevision());
- assertNull (p.getMajorRevision());
-
- assertFalse(p.hasAndroidVersion());
- assertNull (p.getAndroidVersion());
-
- assertTrue (p.hasPath());
- assertEquals("extra_path", p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("extra-vendor-extra_path", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "extras", "vendor", "extra_path"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=extra Vendor=vendor [The Vendor] Path=extra_path FullRev=1.2.3>", p.toString());
- assertEquals("My Extra, rev 1.2.3", p.getListDescription());
-
- IPkgDescExtra e = (IPkgDescExtra) p;
- assertEquals("vendor [The Vendor]", e.getVendor().toString());
- assertEquals("extra_path", e.getPath());
- assertEquals("[old_path1, old_path2]", Arrays.toString(e.getOldPaths()));
- }
-
- public final void testPkgDescExtra_Update() {
- IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
- final NoPreviewRevision rev123 = new NoPreviewRevision(1, 2, 3);
- final IPkgDesc p123 = PkgDesc.Builder
- .newExtra(vendor, "extra_path", "My Extra", new String[0], rev123)
- .create();
- final IPkgDesc p123b = PkgDesc.Builder
- .newExtra(vendor, "extra_path", "My Extra", new String[0], rev123)
- .create();
-
- // can't update itself
- assertFalse(p123 .isUpdateFor(p123b));
- assertFalse(p123b.isUpdateFor(p123));
- assertTrue (p123 .compareTo(p123b) == 0);
- assertTrue (p123b.compareTo(p123 ) == 0);
-
- // updates a lesser revision of the same vendor/path
- final NoPreviewRevision rev124 = new NoPreviewRevision(1, 2, 4);
- final IPkgDesc p124 = PkgDesc.Builder
- .newExtra(vendor, "extra_path", "My Extra", new String[0], rev124)
- .create();
- assertTrue (p124.isUpdateFor(p123));
- assertTrue (p124.compareTo(p123) > 0);
-
- // does not update a different vendor
- IdDisplay vendor2 = new IdDisplay("different-vendor", "Not the same Vendor");
- final IPkgDesc a124 = PkgDesc.Builder
- .newExtra(vendor2, "extra_path", "My Extra", new String[0], rev124)
- .create();
- assertFalse(a124.isUpdateFor(p123));
- assertTrue (a124.compareTo(p123) < 0);
-
- // does not update a different extra path
- final IPkgDesc n124 = PkgDesc.Builder
- .newExtra(vendor, "no_va", "Oye Como Va", new String[0], rev124)
- .create();
- assertFalse(n124.isUpdateFor(p123));
- assertTrue (n124.compareTo(p123) > 0);
- // unless the old_paths mechanism is used to provide a way to update the path
- final IPkgDesc o124 = PkgDesc.Builder
- .newExtra(vendor, "no_va", "Oye Como Va", new String[] { "extra_path" }, rev124)
- .create();
- assertTrue (o124.isUpdateFor(p123));
- assertTrue (o124.compareTo(p123) > 0);
- }
-
- //----
-
- public final void testPkgDescSource() throws Exception {
- IPkgDesc p =
- PkgDesc.Builder.newSource(new AndroidVersion("19"), new MajorRevision(1)).create();
-
- assertEquals(PkgType.PKG_SOURCE, p.getType());
-
- assertFalse(p.hasFullRevision());
- assertNull (p.getFullRevision());
-
- assertTrue (p.hasMajorRevision());
- assertEquals(new MajorRevision(1), p.getMajorRevision());
-
- assertTrue (p.hasAndroidVersion());
- assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("source-19", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "sources", "android-19"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals("<PkgDesc Type=source Android=API 19 MajorRev=1>", p.toString());
- assertEquals("Sources for Android 19", p.getListDescription());
- }
-
- public final void testPkgDescSource_Update() throws Exception {
- final AndroidVersion api19 = new AndroidVersion("19");
- final MajorRevision rev1 = new MajorRevision(1);
- final IPkgDesc p19_1 = PkgDesc.Builder.newSource(api19, rev1).create();
- final IPkgDesc p19_1b = PkgDesc.Builder.newSource(api19, rev1).create();
-
- // can't update itself
- assertFalse(p19_1 .isUpdateFor(p19_1b));
- assertFalse(p19_1b.isUpdateFor(p19_1));
- assertTrue (p19_1 .compareTo(p19_1b) == 0);
- assertTrue (p19_1b.compareTo(p19_1 ) == 0);
-
- // updates a lesser revision of the same API
- final IPkgDesc p19_2 = PkgDesc.Builder.newSource(api19, new MajorRevision(2)).create();
- assertTrue (p19_2.isUpdateFor(p19_1));
- assertTrue (p19_2.compareTo(p19_1) > 0);
-
- // does not update a different API
- final IPkgDesc p18_1 = PkgDesc.Builder.newSource(new AndroidVersion("18"), rev1).create();
- assertFalse(p19_2.isUpdateFor(p18_1));
- assertFalse(p18_1.isUpdateFor(p19_2));
- assertTrue (p19_2.compareTo(p18_1) > 0);
- }
-
- //----
-
- public final void testPkgDescSample() throws Exception {
- IPkgDesc p = PkgDesc.Builder.newSample(new AndroidVersion("19"),
- new MajorRevision(1),
- new FullRevision(5, 6, 7, 8)).create();
-
- assertEquals(PkgType.PKG_SAMPLE, p.getType());
-
- assertFalse(p.hasFullRevision());
- assertNull (p.getFullRevision());
-
- assertTrue (p.hasMajorRevision());
- assertEquals(new MajorRevision(1), p.getMajorRevision());
-
- assertTrue (p.hasAndroidVersion());
- assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
-
- assertFalse(p.hasPath());
- assertNull (p.getPath());
-
- assertTrue (p.hasMinToolsRev());
- assertEquals(new FullRevision(5, 6, 7, 8), p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("sample-19", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "samples", "android-19"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals(
- "<PkgDesc Type=sample Android=API 19 MajorRev=1 MinToolsRev=5.6.7 rc8>",
- p.toString());
- assertEquals("Samples for Android 19", p.getListDescription());
- }
-
- public final void testPkgDescSample_Update() throws Exception {
- final FullRevision min5670 = new FullRevision(5, 6, 7, 0);
- final AndroidVersion api19 = new AndroidVersion("19");
- final MajorRevision rev1 = new MajorRevision(1);
- final IPkgDesc p19_1 = PkgDesc.Builder.newSample(api19, rev1, min5670).create();
- final IPkgDesc p19_1b = PkgDesc.Builder.newSample(api19, rev1, min5670).create();
-
- // can't update itself
- assertFalse(p19_1 .isUpdateFor(p19_1b));
- assertFalse(p19_1b.isUpdateFor(p19_1));
- assertTrue (p19_1 .compareTo(p19_1b) == 0);
- assertTrue (p19_1b.compareTo(p19_1 ) == 0);
-
- // min-tools-rev isn't used for updates checks
- final FullRevision min5680 = new FullRevision(5, 6, 8, 0);
- final IPkgDesc p19_1c = PkgDesc.Builder.newSample(api19, rev1, min5680).create();
- assertFalse(p19_1c.isUpdateFor(p19_1));
- // but it's used for comparisons
- assertTrue (p19_1c.compareTo(p19_1) > 0);
-
- // updates a lesser revision of the same API
- final IPkgDesc p19_2 =
- PkgDesc.Builder.newSample(api19, new MajorRevision(2), min5670).create();
- assertTrue (p19_2.isUpdateFor(p19_1));
- assertTrue (p19_2.compareTo(p19_1) > 0);
-
- // does not update a different API
- final IPkgDesc p18_1 =
- PkgDesc.Builder.newSample(new AndroidVersion("18"), rev1, min5670).create();
- assertFalse(p19_2.isUpdateFor(p18_1));
- assertFalse(p18_1.isUpdateFor(p19_2));
- assertTrue (p19_2.compareTo(p18_1) > 0);
- }
-
- //----
-
- public final void testPkgDescPlatform() throws Exception {
- IPkgDesc p = PkgDesc.Builder.newPlatform(new AndroidVersion("19"),
- new MajorRevision(1),
- new FullRevision(5, 6, 7, 8)).create();
-
- assertEquals(PkgType.PKG_PLATFORM, p.getType());
-
- assertFalse(p.hasFullRevision());
- assertNull (p.getFullRevision());
-
- assertTrue (p.hasMajorRevision());
- assertEquals(new MajorRevision(1), p.getMajorRevision());
-
- assertTrue (p.hasAndroidVersion());
- assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
-
- assertTrue (p.hasPath());
- assertEquals("android-19", p.getPath());
-
- assertTrue (p.hasMinToolsRev());
- assertEquals(new FullRevision(5, 6, 7, 8), p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("android-19", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "platforms", "android-19"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals(
- "<PkgDesc Type=platform Android=API 19 Path=android-19 MajorRev=1 MinToolsRev=5.6.7 rc8>",
- p.toString());
- assertEquals("Android SDK Platform 19", p.getListDescription());
- }
-
- public final void testPkgDescPlatform_Update() throws Exception {
- final FullRevision min5670 = new FullRevision(5, 6, 7, 0);
- final AndroidVersion api19 = new AndroidVersion("19");
- final MajorRevision rev1 = new MajorRevision(1);
- final IPkgDesc p19_1 = PkgDesc.Builder.newPlatform(api19, rev1, min5670).create();
- final IPkgDesc p19_1b = PkgDesc.Builder.newPlatform(api19, rev1, min5670).create();
-
- // can't update itself
- assertFalse(p19_1 .isUpdateFor(p19_1b));
- assertFalse(p19_1b.isUpdateFor(p19_1));
- assertTrue (p19_1 .compareTo(p19_1b) == 0);
- assertTrue (p19_1b.compareTo(p19_1 ) == 0);
-
- // min-tools-rev isn't used for updates checks
- final FullRevision min5680 = new FullRevision(5, 6, 8, 0);
- final IPkgDesc p19_1c = PkgDesc.Builder.newPlatform(api19, rev1, min5680).create();
- assertFalse(p19_1c.isUpdateFor(p19_1));
- // but it's used for comparisons
- assertTrue (p19_1c.compareTo(p19_1) > 0);
-
- // updates a lesser revision of the same API
- final IPkgDesc p19_2 =
- PkgDesc.Builder.newPlatform(api19, new MajorRevision(2), min5670).create();
- assertTrue (p19_2.isUpdateFor(p19_1));
- assertTrue (p19_2.compareTo(p19_1) > 0);
-
- // does not update a different API
- final IPkgDesc p18_1 =
- PkgDesc.Builder.newPlatform(new AndroidVersion("18"), rev1, min5670).create();
- assertFalse(p19_2.isUpdateFor(p18_1));
- assertFalse(p18_1.isUpdateFor(p19_2));
- assertTrue (p19_2.compareTo(p18_1) > 0);
- }
-
- //----
-
- public final void testPkgDescAddon() throws Exception {
- IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
- IdDisplay name = new IdDisplay("addon_name", "The Add-on");
- IPkgDesc p1 = PkgDesc.Builder
- .newAddon(new AndroidVersion("19"), new MajorRevision(1), vendor, name)
- .create();
-
- assertEquals(PkgType.PKG_ADDON, p1.getType());
-
- assertFalse(p1.hasFullRevision());
- assertNull (p1.getFullRevision());
-
- assertTrue (p1.hasMajorRevision());
- assertEquals(new MajorRevision(1), p1.getMajorRevision());
-
- assertTrue (p1.hasAndroidVersion());
- assertEquals(new AndroidVersion("19"), p1.getAndroidVersion());
-
- assertTrue (p1.hasPath());
- assertEquals("The Vendor:The Add-on:19", p1.getPath());
-
- assertFalse(p1.hasMinToolsRev());
- assertNull (p1.getMinToolsRev());
-
- assertFalse(p1.hasMinPlatformToolsRev());
- assertNull (p1.getMinPlatformToolsRev());
-
- assertTrue(p1.hasVendor());
- assertEquals(new IdDisplay("vendor", "only the id is compared with"), p1.getVendor());
-
- assertEquals(new IdDisplay("addon_name", "ignored"), ((IPkgDescAddon) p1).getName());
-
- assertEquals("addon-addon_name-vendor-19", p1.getInstallId());
- assertEquals(FileOp.append(mRoot, "add-ons", "addon-addon_name-vendor-19"),
- p1.getCanonicalInstallFolder(mRoot));
-
- assertEquals(
- "<PkgDesc Type=addon Android=API 19 Vendor=vendor [The Vendor] Path=The Vendor:The Add-on:19 MajorRev=1>",
- p1.toString());
- assertEquals("The Add-on, Android 19", p1.getListDescription());
- }
-
- public final void testPkgDescAddon_Update() throws Exception {
- final AndroidVersion api19 = new AndroidVersion("19");
- final MajorRevision rev1 = new MajorRevision(1);
- IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
- IdDisplay name = new IdDisplay("addon_name", "The Add-on");
- final IPkgDesc p19_1 = PkgDesc.Builder.newAddon(api19, rev1, vendor, name)
- .create();
- final IPkgDesc p19_1b = PkgDesc.Builder.newAddon(api19, rev1, vendor, name)
- .create();
-
- // can't update itself
- assertFalse(p19_1 .isUpdateFor(p19_1b));
- assertFalse(p19_1b.isUpdateFor(p19_1));
- assertTrue (p19_1 .compareTo(p19_1b) == 0);
- assertTrue (p19_1b.compareTo(p19_1 ) == 0);
-
- // updates a lesser revision of the same API
- final MajorRevision rev2 = new MajorRevision(2);
- final IPkgDesc p19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor, name)
- .create();
- assertTrue (p19_2.isUpdateFor(p19_1));
- assertTrue (p19_2.compareTo(p19_1) > 0);
-
- // does not update a different API
- final AndroidVersion api18 = new AndroidVersion("18");
- final IPkgDesc p18_1 = PkgDesc.Builder.newAddon(api18, rev2, vendor, name)
- .create();
- assertFalse(p19_2.isUpdateFor(p18_1));
- assertFalse(p18_1.isUpdateFor(p19_2));
- assertTrue (p19_2.compareTo(p18_1) > 0);
-
- // does not update a different vendor
- IdDisplay vendor2 = new IdDisplay("another_vendor", "Another Vendor");
- final IPkgDesc a19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor2, name)
- .create();
- assertFalse(a19_2.isUpdateFor(p19_1));
- assertTrue (a19_2.compareTo(p19_1) < 0);
-
- // does not update a different add-on name
- IdDisplay name2 = new IdDisplay("another_name", "Another Add-on");
- final IPkgDesc n19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor, name2)
- .create();
- assertFalse(n19_2.isUpdateFor(p19_1));
- assertTrue (n19_2.compareTo(p19_1) < 0);
- }
-
- //----
-
- public final void testPkgDescSysImg_Platform() throws Exception {
- IdDisplay tag = new IdDisplay("tag", "My Tag");
- IPkgDesc p = PkgDesc.Builder.newSysImg(
- new AndroidVersion("19"),
- tag,
- "eabi",
- new MajorRevision(1)).create();
-
- assertEquals(PkgType.PKG_SYS_IMAGE, p.getType());
-
- assertFalse(p.hasFullRevision());
- assertNull (p.getFullRevision());
-
- assertTrue (p.hasMajorRevision());
- assertEquals(new MajorRevision(1), p.getMajorRevision());
-
- assertTrue (p.hasAndroidVersion());
- assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
-
- assertTrue (p.hasPath());
- assertEquals("eabi", p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertEquals("sys-img-eabi-tag-19", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "system-images", "android-19", "tag", "eabi"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals(
- "<PkgDesc Type=sys_image Android=API 19 Tag=tag [My Tag] Path=eabi MajorRev=1>",
- p.toString());
- assertEquals("eabi System Image, Android 19", p.getListDescription());
- }
-
- public final void testPkgDescSysImg_Platform_Update() throws Exception {
- IdDisplay tag1 = new IdDisplay("tag1", "My Tag 1");
- final AndroidVersion api19 = new AndroidVersion("19");
- final MajorRevision rev1 = new MajorRevision(1);
- final IPkgDesc p19_1 = PkgDesc.Builder.newSysImg(api19, tag1, "eabi", rev1).create();
- final IPkgDesc p19_1b = PkgDesc.Builder.newSysImg(api19, tag1, "eabi", rev1).create();
-
- // can't update itself
- assertFalse(p19_1 .isUpdateFor(p19_1b));
- assertFalse(p19_1b.isUpdateFor(p19_1));
- assertTrue (p19_1 .compareTo(p19_1b) == 0);
- assertTrue (p19_1b.compareTo(p19_1 ) == 0);
-
- // updates a lesser revision of the same API
- final IPkgDesc p19_2 =
- PkgDesc.Builder.newSysImg(api19, tag1, "eabi", new MajorRevision(2)).create();
- assertTrue (p19_2.isUpdateFor(p19_1));
- assertTrue (p19_2.compareTo(p19_1) > 0);
-
- // does not update a different API
- final IPkgDesc p18_1 =
- PkgDesc.Builder.newSysImg(new AndroidVersion("18"), tag1, "eabi", rev1).create();
- assertFalse(p19_2.isUpdateFor(p18_1));
- assertFalse(p18_1.isUpdateFor(p19_2));
- assertTrue (p19_2.compareTo(p18_1) > 0);
-
- // does not update a different ABI
- final IPkgDesc p19_2c =
- PkgDesc.Builder.newSysImg(api19, tag1, "ppc", new MajorRevision(2)).create();
- assertFalse(p19_2c.isUpdateFor(p19_1));
- assertTrue (p19_2c.compareTo(p19_1) > 0);
-
- // does not update a different tag
- IdDisplay tag2 = new IdDisplay("tag2", "My Tag 2");
- final IPkgDesc p19_t2 =
- PkgDesc.Builder.newSysImg(api19, tag2, "eabi", new MajorRevision(2)).create();
- assertFalse(p19_t2.isUpdateFor(p19_1));
- assertTrue (p19_t2.compareTo(p19_1) > 0);
- }
-
- public final void testPkgDescSysImg_Addon() throws Exception {
- IdDisplay vendor = new IdDisplay("vendor", "The Vendor");
- IdDisplay name = new IdDisplay("addon_name", "The Add-on");
- IPkgDesc p = PkgDesc.Builder.newAddonSysImg(
- new AndroidVersion("19"),
- vendor,
- name,
- "eabi",
- new MajorRevision(1)).create();
-
- assertEquals(PkgType.PKG_ADDON_SYS_IMAGE, p.getType());
-
- assertFalse(p.hasFullRevision());
- assertNull (p.getFullRevision());
-
- assertTrue (p.hasMajorRevision());
- assertEquals(new MajorRevision(1), p.getMajorRevision());
-
- assertTrue (p.hasAndroidVersion());
- assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
-
- assertTrue (p.hasPath());
- assertEquals("eabi", p.getPath());
-
- assertFalse(p.hasMinToolsRev());
- assertNull (p.getMinToolsRev());
-
- assertFalse(p.hasMinPlatformToolsRev());
- assertNull (p.getMinPlatformToolsRev());
-
- assertTrue(p.hasVendor());
- assertEquals(new IdDisplay("vendor", "only the id is compared with"), p.getVendor());
-
- assertTrue(p.hasTag());
- assertEquals(new IdDisplay("addon_name", "ignored"), p.getTag());
-
- assertEquals("sys-img-eabi-addon-addon_name-vendor-19", p.getInstallId());
- assertEquals(FileOp.append(mRoot, "system-images", "addon-addon_name-vendor-19", "eabi"),
- p.getCanonicalInstallFolder(mRoot));
-
- assertEquals(
- "<PkgDesc Type=addon_sys_image Android=API 19 Vendor=vendor [The Vendor] Tag=addon_name [The Add-on] Path=eabi MajorRev=1>",
- p.toString());
- assertEquals("The Vendor eabi System Image, Android 19", p.getListDescription());
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java
deleted file mode 100755
index 0103d0d..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.descriptors;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import junit.framework.TestCase;
-
-public class PkgTypeTest extends TestCase {
-
- public final void testPkgTypeTool() {
- IPkgCapabilities p = PkgType.PKG_TOOLS;
- assertFalse(p.hasMajorRevision());
- assertTrue (p.hasFullRevision());
- assertFalse(p.hasAndroidVersion());
- assertFalse(p.hasPath());
- assertFalse(p.hasTag());
- assertFalse(p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertTrue (p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypePlatformTool() {
- IPkgCapabilities p = PkgType.PKG_PLATFORM_TOOLS;
- assertFalse(p.hasMajorRevision());
- assertTrue (p.hasFullRevision());
- assertFalse(p.hasAndroidVersion());
- assertFalse(p.hasPath());
- assertFalse(p.hasTag());
- assertFalse(p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeDoc() {
- IPkgCapabilities p = PkgType.PKG_DOC;
- assertTrue (p.hasMajorRevision());
- assertFalse(p.hasFullRevision());
- assertTrue (p.hasAndroidVersion());
- assertFalse(p.hasPath());
- assertFalse(p.hasTag());
- assertFalse(p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeBuildTool() {
- IPkgCapabilities p = PkgType.PKG_BUILD_TOOLS;
-
- assertTrue (p.hasFullRevision());
- assertFalse(p.hasAndroidVersion());
- assertFalse(p.hasPath());
- assertFalse(p.hasTag());
- assertFalse(p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeExtra() {
- IPkgCapabilities p = PkgType.PKG_EXTRA;
-
- assertFalse(p.hasMajorRevision());
- assertTrue (p.hasFullRevision());
- assertFalse(p.hasAndroidVersion());
- assertTrue (p.hasPath());
- assertFalse(p.hasTag());
- assertTrue (p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeSource() throws Exception {
- IPkgCapabilities p = PkgType.PKG_SOURCE;
-
- assertTrue (p.hasMajorRevision());
- assertFalse(p.hasFullRevision());
- assertTrue (p.hasAndroidVersion());
- assertFalse(p.hasPath());
- assertFalse(p.hasTag());
- assertFalse(p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeSample() throws Exception {
- IPkgCapabilities p = PkgType.PKG_SAMPLE;
-
- assertTrue (p.hasMajorRevision());
- assertFalse(p.hasFullRevision());
- assertTrue (p.hasAndroidVersion());
- assertFalse(p.hasPath());
- assertFalse(p.hasTag());
- assertFalse(p.hasVendor());
- assertTrue (p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypePlatform() throws Exception {
- IPkgCapabilities p = PkgType.PKG_PLATFORM;
-
- assertTrue (p.hasMajorRevision());
- assertFalse(p.hasFullRevision());
- assertTrue (p.hasAndroidVersion());
- assertTrue (p.hasPath()); // platform path is its hash string
- assertFalse(p.hasTag());
- assertFalse(p.hasVendor());
- assertTrue (p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeAddon() throws Exception {
- IPkgCapabilities p = PkgType.PKG_ADDON;
-
- assertTrue (p.hasMajorRevision());
- assertFalse(p.hasFullRevision());
- assertTrue (p.hasAndroidVersion());
- assertTrue (p.hasPath()); // add-on path is its hash string
- assertFalse(p.hasTag());
- assertTrue (p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeSysImg() throws Exception {
- IPkgCapabilities p = PkgType.PKG_SYS_IMAGE;
-
- assertTrue (p.hasMajorRevision());
- assertFalse(p.hasFullRevision());
- assertTrue (p.hasAndroidVersion());
- assertTrue (p.hasPath()); // sys-img path is its ABI string
- assertTrue (p.hasTag());
- assertFalse(p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgTypeAddonSysImg() throws Exception {
- IPkgCapabilities p = PkgType.PKG_ADDON_SYS_IMAGE;
-
- assertTrue (p.hasMajorRevision());
- assertFalse(p.hasFullRevision());
- assertTrue (p.hasAndroidVersion());
- assertTrue (p.hasPath()); // sys-img path is its ABI string
- assertTrue (p.hasTag());
- assertTrue (p.hasVendor());
- assertFalse(p.hasMinToolsRev());
- assertFalse(p.hasMinPlatformToolsRev());
- }
-
- public final void testPkgType_UniqueIntValues() {
- // Check all types have a unique int value
- Map<Integer, PkgType> ints = new HashMap<Integer, PkgType>();
- for (PkgType type : PkgType.values()) {
- Integer i = type.getIntValue();
- if (ints.containsKey(i)) {
- fail(String.format("Int value 0x%04x defined by both PkgType.%s and PkgType.%s",
- i, type, ints.get(i)));
- }
- ints.put(i, type);
- }
- }
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java
deleted file mode 100755
index dea1c2e..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.sdklib.io.FileOp;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.nio.charset.Charset;
-
-import junit.framework.TestCase;
-
-public class LocalDirInfoTest extends TestCase {
-
- private final FileOp mFOp = new FileOp();
- private File mTempDir;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTempDir = File.createTempFile("test", "dir");
- assertTrue(mTempDir.delete());
- assertTrue(mTempDir.mkdirs());
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mFOp.deleteFileOrFolder(mTempDir);
- mTempDir = null;
- }
-
- // Test: start with empty directory, removing the dir marks it as changed.
- public final void testHasChanged_Empty() {
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Removing the dir marks it as changed
- mFOp.deleteFileOrFolder(mTempDir);
- assertTrue(di.hasChanged());
- }
-
- // Test: start with empty directory, adding a file marks it as changed.
- public final void testHasChanged_AddFile() throws Exception {
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Adding a file inside the dir marks it as changed
- createFileContent(mTempDir, "some_file.txt", "whatever content");
- assertTrue(di.hasChanged());
- }
-
- // Test: removing any file marks it as changed.
- public final void testHasChanged_RemoveFile() throws Exception {
- File f = createFileContent(mTempDir, "some_file.txt", "whatever content");
-
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Removing a file inside the dir marks it as changed
- mFOp.deleteFileOrFolder(f);
- assertTrue(di.hasChanged());
- }
-
- // Test: start with empty directory, adding a directory marks it as changed.
- public final void testHasChanged_AddDir() throws Exception {
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Adding a file inside the dir marks it as changed
- File dir = new File(mTempDir, "some_dir");
- assertTrue(dir.mkdirs());
- assertTrue(di.hasChanged());
- }
-
- // Test: removing any directory marks it as changed.
- public final void testHasChanged_RemoveDir() throws Exception {
- File dir = new File(mTempDir, "some_dir");
- assertTrue(dir.mkdirs());
-
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Removing a dir marks it as changed
- mFOp.deleteFileOrFolder(dir);
- assertTrue(di.hasChanged());
- }
-
- // Test: directory that contains a source.properties, change source.properties's content
- public final void testHasChanged_SourceProps_Changed() throws Exception {
- createFileContent(mTempDir, "source.properties", "key=value");
-
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Change source.properties's content
- createFileContent(mTempDir, "source.properties", "other_key=other_value");
- assertTrue(di.hasChanged());
- }
-
- // Test: directory that contains a source.properties, change source.properties's timestamp
- public final void testHasChanged_SourceProps_Timestamp() throws Exception {
- createFileContent(mTempDir, "source.properties", "key=value");
-
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Recreate source.properties with the same content, this changes its timestamp.
- // Note: the last-modified resolution on Linux is 1 second on ext3/ext4 file systems
- // so we need at least 1 second in between both edits other they will have the same
- // last-modified value.
- Thread.sleep(1100);
- createFileContent(mTempDir, "source.properties", "key=value");
- assertTrue(di.hasChanged());
- }
-
- // Test: directory that contains a source.properties, change delete source.properties
- public final void testHasChanged_SourceProps_Deleted() throws Exception {
- File sp = createFileContent(mTempDir, "source.properties", "key=value");
-
- LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
- assertFalse(di.hasChanged());
-
- // Removing the source.properties marks it as changed
- mFOp.deleteFileOrFolder(sp);
- assertTrue(di.hasChanged());
- }
-
- //---- Helpers
-
- // Creates a new file with the specified name, in the specified
- // parent directory with the given UTF-8 content.
- private static File createFileContent(File parentDir,
- String fileName,
- String fileContent) throws IOException {
- File f = new File(parentDir, fileName);
- OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(f),
- Charset.forName("UTF-8"));
- try {
- fw.write(fileContent);
- } finally {
- fw.close();
- }
- return f;
- }
-
-
-}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalPlatformPkgInfoTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalPlatformPkgInfoTest.java
deleted file mode 100644
index c161e40..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalPlatformPkgInfoTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import static org.junit.Assert.*;
-
-import com.android.annotations.NonNull;
-import com.android.sdklib.IAndroidTarget;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-public class LocalPlatformPkgInfoTest {
-
- @Test
- public void testOptionalLibWith1() throws Exception {
- String content =
- "[\n" +
- " {\n" +
- " \"name\": \"org.apache.http.legacy\",\n" +
- " \"jar\": \"org.apache.http.legacy.jar\",\n" +
- " \"manifest\": false\n" +
- " }\n" +
- "]\n";
-
- File json = getJsonFile(content);
-
- List<IAndroidTarget.OptionalLibrary> libs = LocalPlatformPkgInfo.getLibsFromJson(json);
-
- assertEquals(1, libs.size());
- IAndroidTarget.OptionalLibrary lib = libs.get(0);
- assertEquals("org.apache.http.legacy", lib.getName());
- assertEquals(new File(json.getParentFile(), "org.apache.http.legacy.jar"), lib.getJar());
- assertEquals(false, lib.isManifestEntryRequired());
- }
-
- @NonNull
- private static File getJsonFile(String content) throws IOException {
- File json = File.createTempFile("testGetLibsFromJson", "");
- json.deleteOnExit();
-
- Files.write(content, json, Charsets.UTF_8);
- return json;
- }
-
-
-}
\ No newline at end of file
diff --git a/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java b/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java
deleted file mode 100755
index 15c6b37..0000000
--- a/base/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java
+++ /dev/null
@@ -1,1134 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository.local;
-
-import com.android.SdkConstants;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.BuildToolInfo;
-import com.android.sdklib.BuildToolInfo.PathId;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.io.MockFileOp;
-import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.MajorRevision;
-import com.android.sdklib.repository.descriptors.PkgType;
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.regex.Pattern;
-
- at SuppressWarnings("MethodMayBeStatic")
-public class LocalSdkTest extends TestCase {
-
- private MockFileOp mFOp;
- private LocalSdk mLS;
-
- @Override
- protected void setUp() {
- mFOp = new MockFileOp();
- mLS = new LocalSdk(mFOp);
- mLS.setLocation(new File("/sdk"));
- }
-
- public final void testLocalSdkTest_allPkgTypes() {
- // Make sure getPkgInfo() can handle all defined package types.
- for(PkgType type : PkgType.values()) {
- mLS.getPkgsInfos(EnumSet.of(type));
- }
-
- // And do the same thing differently, using PKG_ALL
- assertNotNull(mLS.getPkgsInfos(PkgType.PKG_ALL));
- }
-
- public final void testLocalSdkTest_getLocation() {
- MockFileOp fop = new MockFileOp();
- LocalSdk ls = new LocalSdk(fop);
- assertNull(ls.getLocation());
- ls.setLocation(new File("/sdk"));
- assertEquals(new File("/sdk"), ls.getLocation());
- }
-
- public final void testLocalSdkTest_getPkgInfo_Tools() {
- // check empty
- assertNull(mLS.getPkgInfo(PkgType.PKG_TOOLS));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/tools");
- mFOp.recordExistingFile("/sdk/tools/source.properties",
- "Pkg.License=Terms and Conditions\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=22.3.4\n" +
- "Platform.MinPlatformToolsRev=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
- mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.androidCmdName(), "placeholder");
- mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.FN_EMULATOR, "placeholder");
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_TOOLS);
- assertNotNull(pi);
- assertTrue(pi instanceof LocalToolPkgInfo);
- assertEquals(new File("/sdk/tools"), pi.getLocalDir());
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new FullRevision(22, 3, 4), pi.getDesc().getFullRevision());
- assertEquals(
- "<LocalToolPkgInfo <PkgDesc Type=tools FullRev=22.3.4 MinPlatToolsRev=18.0.0>>",
- pi.toString());
- assertEquals("Android SDK Tools 22.3.4", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
- }
-
- public final void testLocalSdkTest_getPkgInfo_PlatformTools() {
- // check empty
- assertNull(mLS.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/platform-tools");
- mFOp.recordExistingFile("/sdk/platform-tools/source.properties",
- "Pkg.License=Terms and Conditions\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=18.19.20\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
- assertNotNull(pi);
- assertTrue(pi instanceof LocalPlatformToolPkgInfo);
- assertEquals(new File("/sdk/platform-tools"), pi.getLocalDir());
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new FullRevision(18, 19, 20), pi.getDesc().getFullRevision());
- assertEquals("<LocalPlatformToolPkgInfo <PkgDesc Type=platform_tools FullRev=18.19.20>>", pi.toString());
- assertEquals("Android SDK Platform-Tools 18.19.20", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Docs() {
- // check empty
- assertNull(mLS.getPkgInfo(PkgType.PKG_DOC));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/docs");
- mFOp.recordExistingFile("/sdk/docs/source.properties",
- "Pkg.License=Terms and Conditions\n" +
- "Archive.Os=ANY\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.Revision=2\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
- mFOp.recordExistingFile("/sdk/docs/index.html", "placeholder");
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_DOC);
- assertNotNull(pi);
- assertTrue(pi instanceof LocalDocPkgInfo);
- assertEquals(new File("/sdk/docs"), pi.getLocalDir());
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new MajorRevision(2), pi.getDesc().getMajorRevision());
- assertEquals("<LocalDocPkgInfo <PkgDesc Type=doc Android=API 18 MajorRev=2>>", pi.toString());
- assertEquals("Documentation for Android SDK", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
- }
-
- public final void testLocalSdkTest_getPkgInfo_BuildTools() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_BUILD_TOOLS)));
-
- // We haven't defined any mock build-tools so the API will return
- // a legacy build-tools based on top of platform tools if there's one with
- // a revision < 17.
- mFOp.recordExistingFolder("/sdk/platform-tools");
- mFOp.recordExistingFile("/sdk/platform-tools/source.properties",
- "Pkg.License=Terms and Conditions\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=16\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
-
- // -- get latest build tool in legacy/compatibility mode
-
- BuildToolInfo bt = mLS.getLatestBuildTool();
- assertNotNull(bt);
- assertEquals(new FullRevision(16), bt.getRevision());
- assertEquals(new File("/sdk/platform-tools"), bt.getLocation());
- assertEquals("/sdk/platform-tools/" + SdkConstants.FN_AAPT,
- mFOp.getAgnosticAbsPath(bt.getPath(PathId.AAPT)));
-
- // clearing local packages also clears the legacy build-tools
- mLS.clearLocalPkg(PkgType.PKG_ALL);
-
- // setup fake files
- mFOp.recordExistingFolder("/sdk/build-tools");
- mFOp.recordExistingFolder("/sdk/build-tools/17");
- mFOp.recordExistingFolder("/sdk/build-tools/18.1.2");
- mFOp.recordExistingFolder("/sdk/build-tools/12.2.3");
- mFOp.recordExistingFile("/sdk/build-tools/17/source.properties",
- "Pkg.License=Terms and Conditions\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=17\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
- mFOp.recordExistingFile("/sdk/build-tools/18.1.2/source.properties",
- "Pkg.License=Terms and Conditions\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=18.1.2\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
- mFOp.recordExistingFile("/sdk/build-tools/12.2.3/source.properties",
- "Pkg.License=Terms and Conditions\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=12.2.3\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n" +
- "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
-
- // -- get latest build tool 18.1.2
-
- BuildToolInfo bt18a = mLS.getLatestBuildTool();
- assertNotNull(bt18a);
- assertEquals(new FullRevision(18, 1, 2), bt18a.getRevision());
- assertEquals(new File("/sdk/build-tools/18.1.2"), bt18a.getLocation());
- assertEquals("/sdk/build-tools/18.1.2/" + SdkConstants.FN_AAPT,
- mFOp.getAgnosticAbsPath(bt18a.getPath(PathId.AAPT)));
-
- // -- get specific build tools by version
-
- BuildToolInfo bt18b = mLS.getBuildTool(new FullRevision(18, 1, 2));
- assertSame(bt18a, bt18b);
-
- BuildToolInfo bt17 = mLS.getBuildTool(new FullRevision(17));
- assertNotNull(bt17);
- assertEquals(new FullRevision(17), bt17.getRevision());
- assertEquals(new File("/sdk/build-tools/17"), bt17.getLocation());
- assertEquals("/sdk/build-tools/17/" + SdkConstants.FN_AAPT,
- mFOp.getAgnosticAbsPath(bt17.getPath(PathId.AAPT)));
-
- assertNull(mLS.getBuildTool(new FullRevision(0)));
- assertNull(mLS.getBuildTool(new FullRevision(16, 17, 18)));
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_BUILD_TOOLS, new FullRevision(18, 1, 2));
- assertNotNull(pi);
- assertTrue(pi instanceof LocalBuildToolPkgInfo);
- assertSame(bt18a, ((LocalBuildToolPkgInfo)pi).getBuildToolInfo());
- assertEquals(new File("/sdk/build-tools/18.1.2"), pi.getLocalDir());
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new FullRevision(18, 1, 2), pi.getDesc().getFullRevision());
- assertEquals("Android SDK Build-Tools 18.1.2", pi.getListDescription());
-
- // -- get all build-tools and iterate, sorted by revision.
-
- assertEquals("[<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=12.2.3>>, " +
- "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=17.0.0>>, " +
- "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools FullRev=18.1.2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_BUILD_TOOLS)));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Extra() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_EXTRA)));
- assertNull(mLS.getPkgInfo(PkgType.PKG_EXTRA, "vendor1", "path1"));
- assertNull(mLS.getExtra("vendor1", "path1"));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/extras");
- mFOp.recordExistingFolder("/sdk/extras/vendor1");
- mFOp.recordExistingFolder("/sdk/extras/vendor1/path1");
- mFOp.recordExistingFolder("/sdk/extras/vendor1/path2");
- mFOp.recordExistingFolder("/sdk/extras/vendor2");
- mFOp.recordExistingFolder("/sdk/extras/vendor2/path1");
- mFOp.recordExistingFolder("/sdk/extras/vendor2/path2");
- mFOp.recordExistingFolder("/sdk/extras/vendor3");
- mFOp.recordExistingFolder("/sdk/extras/vendor3/path3");
- mFOp.recordExistingFile("/sdk/extras/vendor1/path1/source.properties",
- "Extra.NameDisplay=Android Support Library\n" +
- "Extra.VendorDisplay=First Vendor\n" +
- "Extra.VendorId=vendor1\n" +
- "Extra.Path=path1\n" +
- "Extra.OldPaths=compatibility\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=11\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/extras/vendor1/path2/source.properties",
- "Extra.NameDisplay=Some Extra\n" +
- "Extra.VendorDisplay=First Vendor\n" +
- "Extra.VendorId=vendor1\n" +
- "Extra.Path=path2\n" +
- "Archive.Os=ANY\n" +
- "Pkg.Revision=21\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/extras/vendor2/path1/source.properties",
- "Extra.NameDisplay=Another Extra\n" +
- "Extra.VendorDisplay=Another Vendor\n" +
- "Extra.VendorId=vendor2\n" +
- "Extra.Path=path1\n" +
- "Extra.OldPaths=compatibility\n" +
- "Archive.Os=WINDOWS\n" +
- "Pkg.Revision=21\n" +
- "Archive.Arch=ANY\n");
-
- LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_EXTRA, "vendor1", "path1");
- assertNotNull(pi1);
- assertTrue(pi1 instanceof LocalExtraPkgInfo);
- assertEquals(
- "vendor1 [First Vendor]",
- ((LocalExtraPkgInfo)pi1).getDesc().getVendor().toString());
- assertEquals(
- "path1",
- ((LocalExtraPkgInfo)pi1).getDesc().getPath());
- assertEquals(new File("/sdk/extras/vendor1/path1"), pi1.getLocalDir());
- assertSame(mLS, pi1.getLocalSdk());
- assertEquals(null, pi1.getLoadError());
- assertEquals(new FullRevision(11), pi1.getDesc().getFullRevision());
- assertEquals("Android Support Library, rev 11", pi1.getListDescription());
- assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
-
- LocalExtraPkgInfo pi2 = mLS.getExtra("vendor1", "path1");
- assertSame(pi1, pi2);
-
- // -- get all extras and iterate, sorted by revision.
-
- assertEquals("[<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor1 [First Vendor] Path=path1 FullRev=11.0.0>>, " +
- "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor1 [First Vendor] Path=path2 FullRev=21.0.0>>, " +
- "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor2 [Another Vendor] Path=path1 FullRev=21.0.0>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_EXTRA)));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Sources() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SOURCE)));
- assertNull(mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(18, null)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/sources");
- mFOp.recordExistingFolder("/sdk/sources/android-CUPCAKE");
- mFOp.recordExistingFolder("/sdk/sources/android-18");
- mFOp.recordExistingFolder("/sdk/sources/android-42");
- mFOp.recordExistingFile("/sdk/sources/android-CUPCAKE/source.properties",
- "Archive.Os=ANY\n" +
- "AndroidVersion.ApiLevel=3\n" +
- "AndroidVersion.CodeName=CUPCAKE\n" +
- "Pkg.Revision=1\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/sources/android-18/source.properties",
- "Archive.Os=ANY\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.Revision=2\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/sources/android-42/source.properties",
- "Archive.Os=ANY\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.Revision=3\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n");
-
- LocalPkgInfo pi18 = mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(18, null));
- assertNotNull(pi18);
- assertTrue(pi18 instanceof LocalSourcePkgInfo);
- assertSame(mLS, pi18.getLocalSdk());
- assertEquals(null, pi18.getLoadError());
- assertEquals(new AndroidVersion(18, null), pi18.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(2), pi18.getDesc().getMajorRevision());
- assertEquals("Sources for Android 18, rev 2", pi18.getListDescription());
-
- LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(3, "CUPCAKE"));
- assertNotNull(pi1);
- assertEquals(new AndroidVersion(3, "CUPCAKE"), pi1.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(1), pi1.getDesc().getMajorRevision());
- assertEquals("Sources for Android CUPCAKE", pi1.getListDescription());
- assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
-
- // -- get all extras and iterate, sorted by revision.
-
- assertEquals("[<LocalSourcePkgInfo <PkgDesc Type=source Android=API 3, CUPCAKE preview MajorRev=1>>, " +
- "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=2>>, " +
- "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 42 MajorRev=3>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SOURCE)));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Samples() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SAMPLE)));
- assertNull(mLS.getPkgInfo(PkgType.PKG_SAMPLE, new AndroidVersion(18, null)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/samples");
- mFOp.recordExistingFolder("/sdk/samples/android-18");
- mFOp.recordExistingFolder("/sdk/samples/android-42");
- mFOp.recordExistingFile("/sdk/samples/android-18/source.properties",
- "Archive.Os=ANY\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.Revision=2\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/samples/android-42/source.properties",
- "Archive.Os=ANY\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.Revision=3\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n");
-
- LocalPkgInfo pi18 = mLS.getPkgInfo(PkgType.PKG_SAMPLE, new AndroidVersion(18, null));
- assertNotNull(pi18);
- assertTrue(pi18 instanceof LocalSamplePkgInfo);
- assertSame(mLS, pi18.getLocalSdk());
- assertEquals(null, pi18.getLoadError());
- assertEquals(new AndroidVersion(18, null), pi18.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(2), pi18.getDesc().getMajorRevision());
- assertEquals("Samples for Android 18, rev 2", pi18.getListDescription());
- assertSame(pi18, mLS.getPkgInfo(pi18.getDesc()));
-
- // -- get all extras and iterate, sorted by revision.
-
- assertEquals(
- "[<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 18 MajorRev=2 MinToolsRev=0.0.0>>, " +
- "<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 42 MajorRev=3 MinToolsRev=0.0.0>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SAMPLE)));
- }
-
- public final void testLocalSdkTest_getPkgInfo_SysImages() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/system-images");
- mFOp.recordExistingFolder("/sdk/system-images/android-18");
- mFOp.recordExistingFolder("/sdk/system-images/android-18/armeabi-v7a");
- mFOp.recordExistingFolder("/sdk/system-images/android-18/x86");
- mFOp.recordExistingFolder("/sdk/system-images/android-42");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/armeabi");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/x86");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/mips");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/somedir/armeabi-v7a");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-1/x86");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins/skinA");
- mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins/skinB");
- // without tags
- mFOp.recordExistingFile("/sdk/system-images/android-18/armeabi-v7a/source.properties",
- "Pkg.Revision=1\n" +
- "SystemImage.Abi=armeabi-v7a\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-18/x86/source.properties",
- "Pkg.Revision=2\n" +
- "SystemImage.Abi=x86\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-42/x86/source.properties",
- "Pkg.Revision=3\n" +
- "SystemImage.Abi=x86\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-42/mips/source.properties",
- "Pkg.Revision=4\n" +
- "SystemImage.Abi=mips\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-42/armeabi-v7a/source.properties",
- "Pkg.Revision=5\n" +
- "SystemImage.Abi=armeabi-v7a\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- // with tags
- mFOp.recordExistingFile("/sdk/system-images/android-42/somedir/armeabi-v7a/source.properties",
- "Pkg.Revision=6\n" +
- "SystemImage.TagId=default\n" + // Prop TagId is used instead of the "somedir" name
- "SystemImage.Abi=armeabi-v7a\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-42/tag-1/x86/source.properties",
- "Pkg.Revision=7\n" +
- "SystemImage.TagId=tag-1\n" +
- "SystemImage.TagDisplay=My Tag 1\n" +
- "SystemImage.Abi=x86\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-42/tag-2/mips/source.properties",
- "Pkg.Revision=8\n" +
- "SystemImage.TagId=tag-2\n" +
- "SystemImage.TagDisplay=My Tag 2\n" +
- "SystemImage.Abi=mips\n" +
- "AndroidVersion.ApiLevel=42\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-42/tag-2/mips/skins/skinA/layout",
- "part {\n" +
- "}\n");
- mFOp.recordExistingFile("/sdk/system-images/android-42/tag-2/mips/skins/skinB/layout",
- "part {\n" +
- "}\n");
-
- assertEquals("[<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=armeabi-v7a MajorRev=1>>, " +
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=default [Default] Path=x86 MajorRev=2>>, " +
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=default [Default] Path=armeabi-v7a MajorRev=6>>, " +
- // Tag=default Path=armeabi-v7a MajorRev=5 is overriden by the MajorRev=6 above
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=default [Default] Path=mips MajorRev=4>>, " +
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=default [Default] Path=x86 MajorRev=3>>, " +
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=tag-1 [My Tag 1] Path=x86 MajorRev=7>>, " +
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 Tag=tag-2 [My Tag 2] Path=mips MajorRev=8>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
-
- LocalPkgInfo pi = mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)[0];
- assertNotNull(pi);
- assertTrue(pi instanceof LocalSysImgPkgInfo);
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new MajorRevision(1), pi.getDesc().getMajorRevision());
- assertEquals("armeabi-v7a", pi.getDesc().getPath());
- assertEquals("armeabi-v7a System Image, Android 18", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Platforms() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_PLATFORM)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- recordPlatform18(mFOp);
-
- assertEquals(
- "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_PLATFORM)));
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
- assertNotNull(pi);
- assertTrue(pi instanceof LocalPlatformPkgInfo);
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(1), pi.getDesc().getMajorRevision());
- assertEquals("Android SDK Platform 18", pi.getListDescription());
-
- IAndroidTarget t1 = ((LocalPlatformPkgInfo)pi).getAndroidTarget();
- assertNotNull(t1);
-
- LocalPkgInfo pi2 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, "android-18");
- assertSame(pi, pi2);
-
- IAndroidTarget t2 = mLS.getTargetFromHashString("android-18");
- assertSame(t1, t2);
- }
-
- public final void testLocalSdkTest_getPkgInfo_Platforms_SysImages_Skins() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- recordPlatform18(mFOp);
-
- mFOp.recordExistingFolder("/sdk/system-images");
- mFOp.recordExistingFolder("/sdk/system-images/android-18");
- mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-1/x86");
- mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips");
- mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins");
- mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins/skinA");
- mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins/skinB");
- mFOp.recordExistingFile("/sdk/system-images/android-18/tag-1/x86/source.properties",
- "Pkg.Revision=7\n" +
- "SystemImage.TagId=tag-1\n" +
- "SystemImage.TagDisplay=My Tag 1\n" +
- "SystemImage.Abi=x86\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-18/tag-2/mips/source.properties",
- "Pkg.Revision=8\n" +
- "SystemImage.TagId=tag-2\n" +
- "SystemImage.TagDisplay=My Tag 2\n" +
- "SystemImage.Abi=mips\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/android-18/tag-2/mips/skins/skinA/layout",
- "part {\n" +
- "}\n");
- mFOp.recordExistingFile("/sdk/system-images/android-18/tag-2/mips/skins/skinB/layout",
- "part {\n" +
- "}\n");
-
- assertEquals(
- "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-1 [My Tag 1] Path=x86 MajorRev=7>>, " +
- "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 Tag=tag-2 [My Tag 2] Path=mips MajorRev=8>>]",
- Arrays.toString(
- mLS.getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SYS_IMAGE))));
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
- assertNotNull(pi);
- assertTrue(pi instanceof LocalPlatformPkgInfo);
-
- IAndroidTarget t = ((LocalPlatformPkgInfo)pi).getAndroidTarget();
- assertNotNull(t);
-
- assertEquals(
- "[SystemImage tag=tag-1, ABI=x86, location in system image='/sdk/system-images/android-18/tag-1/x86', " +
- "SystemImage tag=tag-2, ABI=mips, location in system image='/sdk/system-images/android-18/tag-2/mips']",
- sanitizePath(Arrays.toString(t.getSystemImages())));
-
- assertEquals("/sdk/platforms/android-18/skins/WVGA800",
- sanitizePath(t.getDefaultSkin().toString()));
-
- assertEquals(
- "[/sdk/system-images/android-18/tag-2/mips/skins/skinA, " +
- "/sdk/system-images/android-18/tag-2/mips/skins/skinB]",
- sanitizePath(Arrays.toString(t.getSkins())));
-
- // check the skins paths from the system image also match what's in the platform
- assertEquals(
- "[/sdk/system-images/android-18/tag-2/mips/skins/skinA, " +
- "/sdk/system-images/android-18/tag-2/mips/skins/skinB]",
- sanitizePath(Arrays.toString(t.getSystemImages()[1].getSkins())));
-
- assertEquals("Android SDK Platform 18", pi.getListDescription());
- }
-
- private String sanitizePath(String path) {
- // On Windows the "/sdk" paths get transformed into an absolute "C:\\sdk"
- // so we sanitize them back to "/sdk". On Linux/Mac, this is mostly a no-op.
- String sdk = mLS.getLocation().getAbsolutePath();
- path = path.replaceAll(Pattern.quote(sdk), "/sdk");
- path = path.replace(File.separatorChar, '/');
- return path;
- }
-
- public final void testLocalSdkTest_getPkgInfo_Platforms_Sources() {
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- recordPlatform18(mFOp);
- assertEquals(
- "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>]",
- Arrays.toString(
- mLS.getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SOURCE))));
-
- // By default, IAndroidTarget returns the legacy path to a platform source,
- // whether that directory exist or not.
- LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
- IAndroidTarget t1 = ((LocalPlatformPkgInfo)pi1).getAndroidTarget();
- assertEquals("/sdk/platforms/android-18/sources",
- mFOp.getAgnosticAbsPath(t1.getPath(IAndroidTarget.SOURCES)));
- assertEquals("Android SDK Platform 18", pi1.getListDescription());
- assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
-
- // However if a separate sources package folder is installed, it is returned instead.
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- mFOp.recordExistingFolder("/sdk/sources");
- mFOp.recordExistingFolder("/sdk/sources/android-18");
- mFOp.recordExistingFile("/sdk/sources/android-18/source.properties",
- "Archive.Os=ANY\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.Revision=2\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Arch=ANY\n");
-
- LocalPkgInfo pi2 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
- IAndroidTarget t2 = ((LocalPlatformPkgInfo)pi2).getAndroidTarget();
- assertEquals("[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
- "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(
- EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SOURCE))));
- assertEquals("Android SDK Platform 18", pi2.getListDescription());
- assertSame(pi2, mLS.getPkgInfo(pi2.getDesc()));
-
- assertEquals("/sdk/sources/android-18",
- mFOp.getAgnosticAbsPath(t2.getPath(IAndroidTarget.SOURCES)));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Addon_NoSysImg() {
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- recordPlatform18(mFOp);
- mFOp.recordExistingFolder("/sdk/add-ons");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/source.properties",
- "Pkg.Revision=2\n" +
- "Addon.VendorId=vendor\n" +
- "Addon.VendorDisplay=Some Vendor\n" +
- "Addon.NameId=name\n" +
- "Addon.NameDisplay=Some Name\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
- "revision=2\n" +
- "name=Some Name\n" +
- "name-id=name\n" +
- "vendor=Some Vendor\n" +
- "vendor-id=vendor\n" +
- "api=18\n" +
- "libraries=com.foo.lib1;com.blah.lib2\n" +
- "com.foo.lib1=foo.jar;API for Foo\n" +
- "com.blah.lib2=blah.jar;API for Blah\n");
-
- assertEquals(
- "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
- assertEquals(
- "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
- "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
- assertNotNull(pi);
- assertTrue(pi instanceof LocalAddonPkgInfo);
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(2), pi.getDesc().getMajorRevision());
- assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
- assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
-
- IAndroidTarget t = mLS.getTargetFromHashString("Some Vendor:Some Name:18");
- assertSame(t, ((LocalAddonPkgInfo) pi).getAndroidTarget());
- assertNotNull(t);
-
- assertEquals(
- "[]",
- sanitizePath(Arrays.toString(t.getSystemImages())));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Addon_SysImgInLegacyFolder() {
- // "Legacy sys-img" means there's only one sys-img of armeabi type directly
- // in the folder addons/addon-name/images. This case is only supported for
- // backward compatibility and we default to it when there's an images/ folder
- // in the addon and that folder doesn't contain per-ABI subfolders and instead
- // contains at least one .img file.
-
-
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- recordPlatform18(mFOp);
- mFOp.recordExistingFolder("/sdk/add-ons");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_one");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_two");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/source.properties",
- "Pkg.Revision=2\n" +
- "Addon.VendorId=vendor\n" +
- "Addon.VendorDisplay=Some Vendor\n" +
- "Addon.NameId=name\n" +
- "Addon.NameDisplay=Some Name\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
- "revision=2\n" +
- "name=Some Name\n" +
- "name-id=name\n" +
- "vendor=Some Vendor\n" +
- "vendor-id=vendor\n" +
- "api=18\n" +
- "libraries=com.foo.lib1;com.blah.lib2\n" +
- "com.foo.lib1=foo.jar;API for Foo\n" +
- "com.blah.lib2=blah.jar;API for Blah\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/images/system.img",
- "placeholder\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_one/layout",
- "parts {\n" +
- "}\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_two/layout",
- "parts {\n" +
- "}\n");
-
- assertEquals(
- "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
- assertEquals(
- "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
- "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
- assertNotNull(pi);
- assertTrue(pi instanceof LocalAddonPkgInfo);
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(2), pi.getDesc().getMajorRevision());
- assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
- assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
-
- IAndroidTarget t = mLS.getTargetFromHashString("Some Vendor:Some Name:18");
- assertSame(t, ((LocalAddonPkgInfo) pi).getAndroidTarget());
- assertNotNull(t);
-
- assertEquals(
- "[SystemImage tag=default, ABI=armeabi, location in legacy folder='/sdk/add-ons/addon-vendor_name-2/images']",
- sanitizePath(Arrays.toString(t.getSystemImages())));
-
- assertEquals(
- "[/sdk/add-ons/addon-vendor_name-2/skins/skin_one, " +
- "/sdk/add-ons/addon-vendor_name-2/skins/skin_two]",
- sanitizePath(Arrays.toString(t.getSkins())));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Addon_SysImgInSubfolder() {
- // "sys-img in subfolder" means there is an addons/addon-name/images/ folder
- // which in turns contains any number of folders named after the system-image ABI.
-
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- recordPlatform18(mFOp);
- mFOp.recordExistingFolder("/sdk/add-ons");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images/armeabi-v7a");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images/x86");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_one");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_two");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/source.properties",
- "Pkg.Revision=2\n" +
- "Addon.VendorId=vendor\n" +
- "Addon.VendorDisplay=Some Vendor\n" +
- "Addon.NameId=name\n" +
- "Addon.NameDisplay=Some Name\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
- "revision=2\n" +
- "name=Some Name\n" +
- "name-id=name\n" +
- "vendor=Some Vendor\n" +
- "vendor-id=vendor\n" +
- "api=18\n" +
- "libraries=com.foo.lib1;com.blah.lib2\n" +
- "com.foo.lib1=foo.jar;API for Foo\n" +
- "com.blah.lib2=blah.jar;API for Blah\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/images/armeabi-v7a/build.prop",
- "ro.build.id=a18\n" +
- "ro.build.display.id=addon_armeabi-v7a-18\n" +
- "ro.build.version.sdk=18\n" +
- "ro.build.version.codename=REL\n" +
- "ro.product.brand=generic_armeabi-v7a\n" +
- "ro.product.name=google_sdk_armeabi-v7a\n" +
- "ro.product.device=generic_armeabi-v7a\n" +
- "ro.product.board=\n" +
- "ro.product.cpu.abi=armeabi-v7a\n" +
- "ro.product.manufacturer=unknown\n" +
- "ro.product.locale.language=en\n" +
- "ro.product.locale.region=US\n" +
- "ro.build.product=generic_armeabi-v7a\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/images/x86/build.prop",
- "ro.build.id=a18\n" +
- "ro.build.display.id=addon_x86-18\n" +
- "ro.build.version.sdk=18\n" +
- "ro.build.version.codename=REL\n" +
- "ro.product.brand=generic_x86\n" +
- "ro.product.name=google_sdk_x86\n" +
- "ro.product.device=generic_x86\n" +
- "ro.product.board=\n" +
- "ro.product.cpu.abi=x86\n" +
- "ro.product.manufacturer=unknown\n" +
- "ro.product.locale.language=en\n" +
- "ro.product.locale.region=US\n" +
- "ro.build.product=generic_x86\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_one/layout",
- "parts {\n" +
- "}\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_two/layout",
- "parts {\n" +
- "}\n");
-
- assertEquals(
- "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
- assertEquals(
- "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
- "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
- assertNotNull(pi);
- assertTrue(pi instanceof LocalAddonPkgInfo);
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(2), pi.getDesc().getMajorRevision());
- assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
- assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
-
- IAndroidTarget t = mLS.getTargetFromHashString("Some Vendor:Some Name:18");
- assertSame(t, ((LocalAddonPkgInfo) pi).getAndroidTarget());
- assertNotNull(t);
-
- assertEquals(
- "[SystemImage addon-vendor=vendor, tag=default, ABI=armeabi-v7a, " +
- "location in images subfolder='/sdk/add-ons/addon-vendor_name-2/images/armeabi-v7a', " +
- "SystemImage addon-vendor=vendor, tag=default, ABI=x86, " +
- "location in images subfolder='/sdk/add-ons/addon-vendor_name-2/images/x86']",
- sanitizePath(Arrays.toString(t.getSystemImages())));
-
- assertEquals(
- "[/sdk/add-ons/addon-vendor_name-2/skins/skin_one, " +
- "/sdk/add-ons/addon-vendor_name-2/skins/skin_two]",
- sanitizePath(Arrays.toString(t.getSkins())));
- }
-
- public final void testLocalSdkTest_getPkgInfo_Addon_SysImgFolder() {
- // sys-img stored separately in the SDK/system-images/addon-id-name/abi/ folder.
-
- // check empty
- assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
-
- // setup fake files
- mLS.clearLocalPkg(PkgType.PKG_ALL);
- recordPlatform18(mFOp);
- mFOp.recordExistingFolder("/sdk/add-ons");
- mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
- mFOp.recordExistingFolder("/sdk/system-images");
- mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2");
- mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/armeabi-v7a");
- mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/x86");
- mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/skins");
- mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/skins/skin_one");
- mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/x86/skins");
- mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/x86/skins/skin_two");
- mFOp.recordExistingFile ("/sdk/add-ons/addon-vendor_name-2/source.properties",
- "Pkg.Revision=2\n" +
- "Addon.VendorId=vendor\n" +
- "Addon.VendorDisplay=Some Vendor\n" +
- "Addon.NameId=name\n" +
- "Addon.NameDisplay=Some Name\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
- "revision=2\n" +
- "name=Some Name\n" +
- "name-id=name\n" +
- "vendor=Some Vendor\n" +
- "vendor-id=vendor\n" +
- "api=18\n" +
- "libraries=com.foo.lib1;com.blah.lib2\n" +
- "com.foo.lib1=foo.jar;API for Foo\n" +
- "com.blah.lib2=blah.jar;API for Blah\n");
- mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/source.properties",
- "Pkg.Revision=1\n" +
- "Addon.VendorId=vendor\n" +
- "Addon.VendorDisplay=Some Vendor\n" +
- "SystemImage.TagId=name\n" +
- "SystemImage.TagDisplay=Some Name\n" +
- "SystemImage.Abi=armeabi-v7a\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/build.prop",
- "ro.build.id=a18\n" +
- "ro.build.display.id=addon_armeabi-v7a-18\n" +
- "ro.build.version.sdk=18\n" +
- "ro.build.version.codename=REL\n" +
- "ro.product.brand=generic_armeabi-v7a\n" +
- "ro.product.name=google_sdk_armeabi-v7a\n" +
- "ro.product.device=generic_armeabi-v7a\n" +
- "ro.product.board=\n" +
- "ro.product.cpu.abi=armeabi-v7a\n" +
- "ro.product.manufacturer=unknown\n" +
- "ro.product.locale.language=en\n" +
- "ro.product.locale.region=US\n" +
- "ro.build.product=generic_armeabi-v7a\n");
- mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/x86/source.properties",
- "Pkg.Revision=1\n" +
- "Addon.VendorId=vendor\n" +
- "Addon.VendorDisplay=Some Vendor\n" +
- "SystemImage.TagId=name\n" +
- "SystemImage.TagDisplay=Some Name\n" +
- "SystemImage.Abi=x86\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/x86/build.prop",
- "ro.build.id=a18\n" +
- "ro.build.display.id=addon_x86-18\n" +
- "ro.build.version.sdk=18\n" +
- "ro.build.version.codename=REL\n" +
- "ro.product.brand=generic_x86\n" +
- "ro.product.name=google_sdk_x86\n" +
- "ro.product.device=generic_x86\n" +
- "ro.product.board=\n" +
- "ro.product.cpu.abi=x86\n" +
- "ro.product.manufacturer=unknown\n" +
- "ro.product.locale.language=en\n" +
- "ro.product.locale.region=US\n" +
- "ro.build.product=generic_x86\n");
- mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/skins/skin_one/layout",
- "parts {\n" +
- "}\n");
- mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/x86/skins/skin_two/layout",
- "parts {\n" +
- "}\n");
-
- assertEquals(
- "[<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=armeabi-v7a MajorRev=1>>, " +
- "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=x86 MajorRev=1>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON_SYS_IMAGE)));
- assertEquals(
- "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
- assertEquals(
- "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 MajorRev=1 MinToolsRev=21.0.0>>, " +
- "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 MajorRev=2>>, " +
- "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=armeabi-v7a MajorRev=1>>, " +
- "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=x86 MajorRev=1>>]",
- Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
-
- LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
- assertNotNull(pi);
- assertTrue(pi instanceof LocalAddonPkgInfo);
- assertSame(mLS, pi.getLocalSdk());
- assertEquals(null, pi.getLoadError());
- assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
- assertEquals(new MajorRevision(2), pi.getDesc().getMajorRevision());
- assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
- assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
- assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
-
- IAndroidTarget t = mLS.getTargetFromHashString("Some Vendor:Some Name:18");
- assertSame(t, ((LocalAddonPkgInfo) pi).getAndroidTarget());
- assertNotNull(t);
-
- assertEquals(
- "[SystemImage addon-vendor=vendor, tag=name, ABI=armeabi-v7a, location in system image='/sdk/system-images/addon-vendor_name-2/armeabi-v7a', " +
- "SystemImage addon-vendor=vendor, tag=name, ABI=x86, location in system image='/sdk/system-images/addon-vendor_name-2/x86']",
- sanitizePath(Arrays.toString(t.getSystemImages())));
-
- assertEquals(
- "[/sdk/system-images/addon-vendor_name-2/armeabi-v7a/skins/skin_one, " +
- "/sdk/system-images/addon-vendor_name-2/x86/skins/skin_two]",
- sanitizePath(Arrays.toString(t.getSkins())));
-
- }
-
- //-----
-
- private void recordPlatform18(MockFileOp fop) {
- fop.recordExistingFolder("/sdk/platforms");
- fop.recordExistingFolder("/sdk/platforms/android-18");
- fop.recordExistingFile ("/sdk/platforms/android-18/android.jar");
- fop.recordExistingFile ("/sdk/platforms/android-18/framework.aidl");
- fop.recordExistingFile ("/sdk/platforms/android-18/source.properties",
- "Pkg.Revision=1\n" +
- "Platform.Version=4.3\n" +
- "AndroidVersion.ApiLevel=18\n" +
- "Layoutlib.Api=10\n" +
- "Layoutlib.Revision=1\n" +
- "Platform.MinToolsRev=21\n" +
- "Pkg.LicenseRef=android-sdk-license\n" +
- "Archive.Os=ANY\n" +
- "Archive.Arch=ANY\n");
- fop.recordExistingFile("/sdk/platforms/android-18/sdk.properties",
- "sdk.ant.templates.revision=1\n" +
- "sdk.skin.default=WVGA800\n");
- fop.recordExistingFile("/sdk/platforms/android-18/build.prop",
- "ro.build.id=JB_MR2\n" +
- "ro.build.display.id=sdk-eng 4.3 JB_MR2 819563 test-keys\n" +
- "ro.build.version.incremental=819563\n" +
- "ro.build.version.sdk=18\n" +
- "ro.build.version.codename=REL\n" +
- "ro.build.version.release=4.3\n" +
- "ro.build.date=Tue Sep 10 18:43:31 UTC 2013\n" +
- "ro.build.date.utc=1378838611\n" +
- "ro.build.type=eng\n" +
- "ro.build.tags=test-keys\n" +
- "ro.product.model=sdk\n" +
- "ro.product.name=sdk\n" +
- "ro.product.board=\n" +
- "ro.product.cpu.abi=armeabi-v7a\n" +
- "ro.product.cpu.abi2=armeabi\n" +
- "ro.product.locale.language=en\n" +
- "ro.product.locale.region=US\n" +
- "ro.wifi.channels=\n" +
- "ro.board.platform=\n" +
- "# ro.build.product is obsolete; use ro.product.device\n" +
- "# Do not try to parse ro.build.description or .fingerprint\n" +
- "ro.build.description=sdk-eng 4.3 JB_MR2 819563 test-keys\n" +
- "ro.build.fingerprint=generic/sdk/generic:4.3/JB_MR2/819563:eng/test-keys\n" +
- "ro.build.characteristics=default\n" +
- "rild.libpath=/system/lib/libreference-ril.so\n" +
- "rild.libargs=-d /dev/ttyS0\n" +
- "ro.config.notification_sound=OnTheHunt.ogg\n" +
- "ro.config.alarm_alert=Alarm_Classic.ogg\n" +
- "ro.kernel.android.checkjni=1\n" +
- "xmpp.auto-presence=true\n" +
- "ro.config.nocheckin=yes\n" +
- "net.bt.name=Android\n" +
- "dalvik.vm.stack-trace-file=/data/anr/traces.txt\n" +
- "ro.build.user=generic\n" +
- "ro.build.host=generic\n" +
- "ro.product.brand=generic\n" +
- "ro.product.manufacturer=generic\n" +
- "ro.product.device=generic\n" +
- "ro.build.product=generic\n");
- }
-}
diff --git a/base/templates/activities/AlwaysOnWearActivity/recipe.xml.ftl b/base/templates/activities/AlwaysOnWearActivity/recipe.xml.ftl
deleted file mode 100644
index 278df15..0000000
--- a/base/templates/activities/AlwaysOnWearActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
-
- <instantiate from="res/layout/blank_activity.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="src/app_package/BlankActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/AlwaysOnWearActivity/root/AndroidManifest.xml.ftl b/base/templates/activities/AlwaysOnWearActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index a0e3c80..0000000
--- a/base/templates/activities/AlwaysOnWearActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <application>
- <uses-library android:name="com.google.android.wearable" android:required="false" />
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- android:theme="@android:style/Theme.DeviceDefault.Light"
- >
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/activities/AlwaysOnWearActivity/template.xml b/base/templates/activities/AlwaysOnWearActivity/template.xml
deleted file mode 100644
index d3aeeb1..0000000
--- a/base/templates/activities/AlwaysOnWearActivity/template.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Always On Wear Activity"
- minApi="20"
- minBuildApi="20"
- description="Creates an always on activity for Android Wear">
-
- <category value="Activity" />
- <formfactor value="Wear" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="true"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it the default launchable activity" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_thumb.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/AlwaysOnWearActivity/template_thumb.png b/base/templates/activities/AlwaysOnWearActivity/template_thumb.png
deleted file mode 100644
index 42ebcf2..0000000
Binary files a/base/templates/activities/AlwaysOnWearActivity/template_thumb.png and /dev/null differ
diff --git a/base/templates/activities/AndroidTVActivity/recipe.xml.ftl b/base/templates/activities/AndroidTVActivity/recipe.xml.ftl
deleted file mode 100644
index 1e4ea3c..0000000
--- a/base/templates/activities/AndroidTVActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <dependency mavenUrl="com.android.support:appcompat-v7:${targetApi}.+"/>
- <dependency mavenUrl="com.squareup.picasso:picasso:2.3.2"/>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/colors.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
-
- <merge from="res/values/themes.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/themes.xml" />
-
- <copy from="res/drawable"
- to="${escapeXmlAttribute(resOut)}/drawable" />
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
- <copy from="res/drawable-xxhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
-
- <instantiate from="res/layout/activity_main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="res/layout/activity_details.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${detailsLayoutName}.xml" />
-
- <instantiate from="res/layout/playback_controls.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/playback_controls.xml" />
-
- <instantiate from="src/app_package/MainActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <instantiate from="src/app_package/MainFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${mainFragment}.java" />
-
- <instantiate from="src/app_package/DetailsActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${detailsActivity}.java" />
-
- <instantiate from="src/app_package/VideoDetailsFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${detailsFragment}.java" />
-
- <instantiate from="src/app_package/Movie.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/Movie.java" />
-
- <instantiate from="src/app_package/MovieList.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/MovieList.java" />
-
- <instantiate from="src/app_package/PicassoBackgroundManagerTarget.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/PicassoBackgroundManagerTarget.java" />
-
- <instantiate from="src/app_package/CardPresenter.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/CardPresenter.java" />
-
- <instantiate from="src/app_package/DetailsDescriptionPresenter.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/DetailsDescriptionPresenter.java" />
-
- <instantiate from="src/app_package/PlaybackOverlayActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/PlaybackOverlayActivity.java" />
-
- <instantiate from="src/app_package/PlaybackOverlayFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/PlaybackOverlayFragment.java" />
-
- <instantiate from="src/app_package/Utils.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/Utils.java" />
-
- <instantiate from="src/app_package/ErrorFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/ErrorFragment.java" />
-
- <instantiate from="src/app_package/BrowseErrorActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/BrowseErrorActivity.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/AndroidTVActivity/root/AndroidManifest.xml.ftl b/base/templates/activities/AndroidTVActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 77e1995..0000000
--- a/base/templates/activities/AndroidTVActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,41 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
-
- <uses-feature
- android:name="android.hardware.touchscreen"
- android:required="false" />
-
- <uses-feature android:name="android.software.leanback"
- android:required="true" />
-
- <application>
-
- <activity android:name="${packageName}.${activityClass}"
- android:icon="@drawable/app_icon_your_company"
- android:logo="@drawable/app_icon_your_company"
- android:screenOrientation="landscape"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
- </intent-filter>
- </activity>
-
- <activity android:name="${packageName}.${detailsActivity}" />
- <activity android:name="PlaybackOverlayActivity" />
- <activity android:name="BrowseErrorActivity" />
-
- </application>
-
-</manifest>
diff --git a/base/templates/activities/AndroidTVActivity/root/res/layout/activity_details.xml.ftl b/base/templates/activities/AndroidTVActivity/root/res/layout/activity_details.xml.ftl
deleted file mode 100644
index 6be6353..0000000
--- a/base/templates/activities/AndroidTVActivity/root/res/layout/activity_details.xml.ftl
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/details_fragment"
- android:name="${packageName}.${detailsFragment}"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${detailsActivity}"
- tools:deviceIds="tv" />
diff --git a/base/templates/activities/AndroidTVActivity/root/res/layout/activity_main.xml.ftl b/base/templates/activities/AndroidTVActivity/root/res/layout/activity_main.xml.ftl
deleted file mode 100644
index 3d95289..0000000
--- a/base/templates/activities/AndroidTVActivity/root/res/layout/activity_main.xml.ftl
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/main_browse_fragment"
- android:name="${packageName}.${mainFragment}"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${activityClass}"
- tools:deviceIds="tv"
- tools:ignore="MergeRootFrame" />
diff --git a/base/templates/activities/AndroidTVActivity/root/res/layout/playback_controls.xml.ftl b/base/templates/activities/AndroidTVActivity/root/res/layout/playback_controls.xml.ftl
deleted file mode 100644
index dc128ca..0000000
--- a/base/templates/activities/AndroidTVActivity/root/res/layout/playback_controls.xml.ftl
+++ /dev/null
@@ -1,39 +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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <VideoView android:id="@+id/videoView"
- android:layout_width="match_parent"
- android:layout_alignParentRight="true"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_alignParentBottom="true"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:layout_centerInParent="true">
- </VideoView>
-
- <fragment
- android:id="@+id/playback_controls_fragment"
- android:name="${packageName}.PlaybackOverlayFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
-</FrameLayout>
diff --git a/base/templates/activities/AndroidTVActivity/root/res/values/colors.xml.ftl b/base/templates/activities/AndroidTVActivity/root/res/values/colors.xml.ftl
deleted file mode 100644
index c8f17b7..0000000
--- a/base/templates/activities/AndroidTVActivity/root/res/values/colors.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<resources>
- <color name="background_gradient_start">#000000</color>
- <color name="background_gradient_end">#DDDDDD</color>
- <color name="fastlane_background">#0096a6</color>
- <color name="search_opaque">#ffaa3f</color>
- <color name="detail_background">#0096a6</color>
- <color name="soft_opaque">#30000000</color>
- <color name="img_soft_opaque">#30FF0000</color>
- <color name="img_full_opaque">#00000000</color>
- <color name="black_opaque">#AA000000</color>
- <color name="black">#59000000</color>
- <color name="white">#FFFFFF</color>
- <color name="orange_transparent">#AAFADCA7</color>
- <color name="orange">#FADCA7</color>
- <color name="yellow">#EEFF41</color>
- <color name="default_background">#3d3d3d</color>
-</resources>
diff --git a/base/templates/activities/AndroidTVActivity/root/res/values/strings.xml.ftl b/base/templates/activities/AndroidTVActivity/root/res/values/strings.xml.ftl
deleted file mode 100644
index 0a48010..0000000
--- a/base/templates/activities/AndroidTVActivity/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,40 +0,0 @@
-<resources>
- <string name="app_name">Leanback ${escapeXmlString(activityTitle)}</string>
- <string name="browse_title"><![CDATA[Videos by Your Company]]></string>
- <string name="related_movies">Related Videos</string>
- <string name="vertical_grid_title"><![CDATA[Vertical Video Grid]]></string>
- <string name="error">Error</string>
- <string name="ok">OK</string>
- <string name="pause">Pause</string>
- <string name="play">Play</string>
- <string name="stop">Stop</string>
- <string name="init_text">00:00</string>
- <string name="play_pause_description">Play Pause Button</string>
- <string name="loading">Loading…</string>
- <string name="no_video_found">No video was found</string>
- <string name="about_app">About DemoCast Player</string>
- <string name="version">Version: %1$s</string>
- <string name="popular_header">Popular Videos</string>
- <string name="preferences">PREFERENCES</string>
- <string name="grid_view">Grid View</string>
- <string name="error_fragment">Error Fragment</string>
- <string name="personal_settings">Personal Settings</string>
- <string name="watch_trailer_1">Watch trailer</string>
- <string name="watch_trailer_2">FREE</string>
- <string name="rent_1">Rent By Day</string>
- <string name="rent_2">From $1.99</string>
- <string name="buy_1">Buy and Own</string>
- <string name="buy_2">AT $9.99</string>
- <string name="movie">Movie</string>
- <string name="should_start">shouldStart</string>
- <string name="start_position">startPosition</string>
- <string name="search_results">Search Results</string>
-
- <!-- Error messages -->
- <string name="video_error_media_load_timeout">Media loading timed out</string>
- <string name="video_error_server_inaccessible">Media server was not reachable</string>
- <string name="video_error_unknown_error">Failed to load video</string>
- <string name="error_fragment_message">An error occurred</string>
- <string name="dismiss_error">Dismiss</string>
- <string name="oops">Oops</string>
-</resources>
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/CardPresenter.java.ftl b/base/templates/activities/AndroidTVActivity/root/src/app_package/CardPresenter.java.ftl
deleted file mode 100644
index e83e168..0000000
--- a/base/templates/activities/AndroidTVActivity/root/src/app_package/CardPresenter.java.ftl
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package ${packageName};
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.Presenter;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import java.net.URI;
-
-/*
- * A CardPresenter is used to generate Views and bind Objects to them on demand.
- * It contains an Image CardView
- */
-public class CardPresenter extends Presenter {
- private static final String TAG = "CardPresenter";
-
- private static Context mContext;
- private static int CARD_WIDTH = 313;
- private static int CARD_HEIGHT = 176;
-
- static class ViewHolder extends Presenter.ViewHolder {
- private Movie mMovie;
- private ImageCardView mCardView;
- private Drawable mDefaultCardImage;
- private PicassoImageCardViewTarget mImageCardViewTarget;
-
- public ViewHolder(View view) {
- super(view);
- mCardView = (ImageCardView) view;
- mImageCardViewTarget = new PicassoImageCardViewTarget(mCardView);
- mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
- }
-
- public void setMovie(Movie m) {
- mMovie = m;
- }
-
- public Movie getMovie() {
- return mMovie;
- }
-
- public ImageCardView getCardView() {
- return mCardView;
- }
-
- protected void updateCardViewImage(URI uri) {
- Picasso.with(mContext)
- .load(uri.toString())
- .resize(Utils.convertDpToPixel(mContext, CARD_WIDTH),
- Utils.convertDpToPixel(mContext, CARD_HEIGHT))
- .error(mDefaultCardImage)
- .into(mImageCardViewTarget);
- }
- }
-
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent) {
- Log.d(TAG, "onCreateViewHolder");
- mContext = parent.getContext();
-
- ImageCardView cardView = new ImageCardView(mContext);
- cardView.setFocusable(true);
- cardView.setFocusableInTouchMode(true);
- cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
- return new ViewHolder(cardView);
- }
-
- @Override
- public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
- Movie movie = (Movie) item;
- ((ViewHolder) viewHolder).setMovie(movie);
-
- Log.d(TAG, "onBindViewHolder");
- if (movie.getCardImageUrl() != null) {
- ((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
- ((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
- ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
- ((ViewHolder) viewHolder).updateCardViewImage(movie.getCardImageURI());
- }
- }
-
- @Override
- public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
- Log.d(TAG, "onUnbindViewHolder");
- }
-
- @Override
- public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
- // TO DO
- }
-
- public static class PicassoImageCardViewTarget implements Target {
- private ImageCardView mImageCardView;
-
- public PicassoImageCardViewTarget(ImageCardView imageCardView) {
- mImageCardView = imageCardView;
- }
-
- @Override
- public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
- Drawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
- mImageCardView.setMainImage(bitmapDrawable);
- }
-
- @Override
- public void onBitmapFailed(Drawable drawable) {
- mImageCardView.setMainImage(drawable);
- }
-
- @Override
- public void onPrepareLoad(Drawable drawable) {
- // Do nothing, default_background manager has its own transitions
- }
- }
-}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/ErrorFragment.java.ftl b/base/templates/activities/AndroidTVActivity/root/src/app_package/ErrorFragment.java.ftl
deleted file mode 100644
index c776c76..0000000
--- a/base/templates/activities/AndroidTVActivity/root/src/app_package/ErrorFragment.java.ftl
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package ${packageName};
-
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-
-/*
- * This class demonstrates how to extend ErrorFragment
- */
-public class ErrorFragment extends android.support.v17.leanback.app.ErrorFragment {
- private static final String TAG = "ErrorFragment";
- private static final boolean TRANSLUCENT = true;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- Log.d(TAG, "onCreate");
- super.onCreate(savedInstanceState);
- setTitle(getResources().getString(R.string.app_name));
- }
-
- void setErrorContent() {
- setImageDrawable(getResources().getDrawable(R.drawable.lb_ic_sad_cloud));
- setMessage(getResources().getString(R.string.error_fragment_message));
- setDefaultBackground(TRANSLUCENT);
-
- setButtonText(getResources().getString(R.string.dismiss_error));
- setButtonClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View arg0) {
- getFragmentManager().beginTransaction().remove(ErrorFragment.this).commit();
- }
- });
- }
-}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/MainFragment.java.ftl b/base/templates/activities/AndroidTVActivity/root/src/app_package/MainFragment.java.ftl
deleted file mode 100644
index 3cae7ae..0000000
--- a/base/templates/activities/AndroidTVActivity/root/src/app_package/MainFragment.java.ftl
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package ${packageName};
-
-import java.net.URI;
-import java.util.Collections;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.BrowseFragment;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v4.app.ActivityOptionsCompat;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-public class ${mainFragment} extends BrowseFragment {
- private static final String TAG = "${truncate(mainFragment,23)}";
-
- private static final int BACKGROUND_UPDATE_DELAY = 300;
- private static final int GRID_ITEM_WIDTH = 200;
- private static final int GRID_ITEM_HEIGHT = 200;
- private static final int NUM_ROWS = 6;
- private static final int NUM_COLS = 15;
-
- private ArrayObjectAdapter mRowsAdapter;
- private Drawable mDefaultBackground;
- private Target mBackgroundTarget;
- private DisplayMetrics mMetrics;
- private Timer mBackgroundTimer;
- private final Handler mHandler = new Handler();
- private URI mBackgroundURI;
- Movie mMovie;
- CardPresenter mCardPresenter;
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- Log.i(TAG, "onCreate");
- super.onActivityCreated(savedInstanceState);
-
- prepareBackgroundManager();
-
- setupUIElements();
-
- loadRows();
-
- setupEventListeners();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (null != mBackgroundTimer) {
- Log.d(TAG, "onDestroy: " + mBackgroundTimer.toString());
- mBackgroundTimer.cancel();
- }
- }
-
- private void loadRows() {
- List<Movie> list = MovieList.setupMovies();
-
- mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
- mCardPresenter = new CardPresenter();
-
- int i;
- for (i = 0; i < NUM_ROWS; i++) {
- if (i != 0) {
- Collections.shuffle(list);
- }
- ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(mCardPresenter);
- for (int j = 0; j < NUM_COLS; j++) {
- listRowAdapter.add(list.get(j % 5));
- }
- <#if buildApi gte 22>
- HeaderItem header = new HeaderItem(i, MovieList.MOVIE_CATEGORY[i]);
- <#else>
- HeaderItem header = new HeaderItem(i, MovieList.MOVIE_CATEGORY[i], null);
- </#if>
- mRowsAdapter.add(new ListRow(header, listRowAdapter));
- }
-
- <#if buildApi gte 22>
- HeaderItem gridHeader = new HeaderItem(i, "PREFERENCES");
- <#else>
- HeaderItem gridHeader = new HeaderItem(i, "PREFERENCES", null);
- </#if>
-
- GridItemPresenter mGridPresenter = new GridItemPresenter();
- ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
- gridRowAdapter.add(getResources().getString(R.string.grid_view));
- gridRowAdapter.add(getString(R.string.error_fragment));
- gridRowAdapter.add(getResources().getString(R.string.personal_settings));
- mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
-
- setAdapter(mRowsAdapter);
-
- }
-
- private void prepareBackgroundManager() {
-
- BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
- backgroundManager.attach(getActivity().getWindow());
- mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
-
- mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
-
- mMetrics = new DisplayMetrics();
- getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
- }
-
- private void setupUIElements() {
- // setBadgeDrawable(getActivity().getResources().getDrawable(
- // R.drawable.videos_by_google_banner));
- setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent
- // over title
- setHeadersState(HEADERS_ENABLED);
- setHeadersTransitionOnBackEnabled(true);
-
- // set fastLane (or headers) background color
- setBrandColor(getResources().getColor(R.color.fastlane_background));
- // set search icon color
- setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
- }
-
- private void setupEventListeners() {
- setOnSearchClickedListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View view) {
- Toast.makeText(getActivity(), "Implement your own in-app search", Toast.LENGTH_LONG)
- .show();
- }
- });
-
- setOnItemViewClickedListener(new ItemViewClickedListener());
- setOnItemViewSelectedListener(new ItemViewSelectedListener());
- }
-
- private final class ItemViewClickedListener implements OnItemViewClickedListener {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
-
- if (item instanceof Movie) {
- Movie movie = (Movie) item;
- Log.d(TAG, "Item: " + item.toString());
- Intent intent = new Intent(getActivity(), ${detailsActivity}.class);
- intent.putExtra(${detailsActivity}.MOVIE, movie);
-
- Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
- getActivity(),
- ((ImageCardView) itemViewHolder.view).getMainImageView(),
- ${detailsActivity}.SHARED_ELEMENT_NAME).toBundle();
- getActivity().startActivity(intent, bundle);
- } else if (item instanceof String) {
- if (((String) item).indexOf(getString(R.string.error_fragment)) >= 0) {
- Intent intent = new Intent(getActivity(), BrowseErrorActivity.class);
- startActivity(intent);
- } else {
- Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
- .show();
- }
- }
- }
- }
-
- private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
- @Override
- public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- if (item instanceof Movie) {
- mBackgroundURI = ((Movie) item).getBackgroundImageURI();
- startBackgroundTimer();
- }
-
- }
- }
-
- protected void setDefaultBackground(Drawable background) {
- mDefaultBackground = background;
- }
-
- protected void setDefaultBackground(int resourceId) {
- mDefaultBackground = getResources().getDrawable(resourceId);
- }
-
- protected void updateBackground(URI uri) {
- Picasso.with(getActivity())
- .load(uri.toString())
- .resize(mMetrics.widthPixels, mMetrics.heightPixels)
- .centerCrop()
- .error(mDefaultBackground)
- .into(mBackgroundTarget);
- }
-
- protected void updateBackground(Drawable drawable) {
- BackgroundManager.getInstance(getActivity()).setDrawable(drawable);
- }
-
- protected void clearBackground() {
- BackgroundManager.getInstance(getActivity()).setDrawable(mDefaultBackground);
- }
-
- private void startBackgroundTimer() {
- if (null != mBackgroundTimer) {
- mBackgroundTimer.cancel();
- }
- mBackgroundTimer = new Timer();
- mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
- }
-
- private class UpdateBackgroundTask extends TimerTask {
-
- @Override
- public void run() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (mBackgroundURI != null) {
- updateBackground(mBackgroundURI);
- }
- }
- });
-
- }
- }
-
- private class GridItemPresenter extends Presenter {
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent) {
- TextView view = new TextView(parent.getContext());
- view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
- view.setFocusable(true);
- view.setFocusableInTouchMode(true);
- view.setBackgroundColor(getResources().getColor(R.color.default_background));
- view.setTextColor(Color.WHITE);
- view.setGravity(Gravity.CENTER);
- return new ViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(ViewHolder viewHolder, Object item) {
- ((TextView) viewHolder.view).setText((String) item);
- }
-
- @Override
- public void onUnbindViewHolder(ViewHolder viewHolder) {
- }
- }
-
-}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayActivity.java.ftl b/base/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayActivity.java.ftl
deleted file mode 100644
index 7089fdb..0000000
--- a/base/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayActivity.java.ftl
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package ${packageName};
-
-import android.app.Activity;
-import android.media.MediaPlayer;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.widget.FrameLayout;
-import android.widget.VideoView;
-
-/**
- * PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment
- */
-public class PlaybackOverlayActivity extends Activity implements
- PlaybackOverlayFragment.OnPlayPauseClickedListener {
- private static final String TAG = "PlaybackOverlayActivity";
-
- private static final double MEDIA_HEIGHT = 0.95;
- private static final double MEDIA_WIDTH = 0.95;
- private static final double MEDIA_TOP_MARGIN = 0.025;
- private static final double MEDIA_RIGHT_MARGIN = 0.025;
- private static final double MEDIA_BOTTOM_MARGIN = 0.025;
- private static final double MEDIA_LEFT_MARGIN = 0.025;
-
- private VideoView mVideoView;
- private PlaybackState mPlaybackState = PlaybackState.IDLE;
-
- /**
- * Called when the activity is first created.
- */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.playback_controls);
- loadViews();
- //setupCallbacks();
- overScan();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mVideoView.suspend();
- }
-
- /**
- * Implementation of OnPlayPauseClickedListener
- */
- public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) {
- mVideoView.setVideoPath(movie.getVideoUrl());
-
- if (position == 0 || mPlaybackState == PlaybackState.IDLE) {
- setupCallbacks();
- mPlaybackState = PlaybackState.IDLE;
- }
-
- if (playPause && mPlaybackState != PlaybackState.PLAYING) {
- mPlaybackState = PlaybackState.PLAYING;
- if (position > 0) {
- mVideoView.seekTo(position);
- mVideoView.start();
- }
- } else {
- mPlaybackState = PlaybackState.PAUSED;
- mVideoView.pause();
- }
- }
-
- private void loadViews() {
- mVideoView = (VideoView) findViewById(R.id.videoView);
- }
-
- private void overScan() {
- DisplayMetrics metrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(metrics);
- int w = (int) (metrics.widthPixels * MEDIA_WIDTH);
- int h = (int) (metrics.heightPixels * MEDIA_HEIGHT);
- int marginLeft = (int) (metrics.widthPixels * MEDIA_LEFT_MARGIN);
- int marginTop = (int) (metrics.heightPixels * MEDIA_TOP_MARGIN);
- int marginRight = (int) (metrics.widthPixels * MEDIA_RIGHT_MARGIN);
- int marginBottom = (int) (metrics.heightPixels * MEDIA_BOTTOM_MARGIN);
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
- lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
- mVideoView.setLayoutParams(lp);
- }
-
- private void setupCallbacks() {
-
- mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- String msg = "";
- if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
- msg = getString(R.string.video_error_media_load_timeout);
- } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
- msg = getString(R.string.video_error_server_inaccessible);
- } else {
- msg = getString(R.string.video_error_unknown_error);
- }
- mVideoView.stopPlayback();
- mPlaybackState = PlaybackState.IDLE;
- return false;
- }
- });
-
- mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(MediaPlayer mp) {
- if (mPlaybackState == PlaybackState.PLAYING) {
- mVideoView.start();
- }
- }
- });
-
- mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- mPlaybackState = PlaybackState.IDLE;
- }
- });
-
- }
-
- /*
- * List of various states that we can be in
- */
- public static enum PlaybackState {
- PLAYING, PAUSED, BUFFERING, IDLE;
- }
-
-}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayFragment.java.ftl b/base/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayFragment.java.ftl
deleted file mode 100644
index 31ef35c..0000000
--- a/base/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayFragment.java.ftl
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package ${packageName};
-
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.media.MediaMetadataRetriever;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.ShuffleAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.SkipNextAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.SkipPreviousAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/*
- * Class for video playback with media control
- */
-public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment {
- private static final String TAG = "PlaybackControlsFragmnt";
-
- private static Context sContext;
-
- private static final boolean SHOW_DETAIL = true;
- private static final boolean HIDE_MORE_ACTIONS = false;
- private static final int PRIMARY_CONTROLS = 5;
- private static final boolean SHOW_IMAGE = PRIMARY_CONTROLS <= 5;
- private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;
- private static final int CARD_WIDTH = 200;
- private static final int CARD_HEIGHT = 240;
- private static final int DEFAULT_UPDATE_PERIOD = 1000;
- private static final int UPDATE_PERIOD = 16;
- private static final int SIMULATED_BUFFERED_TIME = 10000;
-
- private ArrayObjectAdapter mRowsAdapter;
- private ArrayObjectAdapter mPrimaryActionsAdapter;
- private ArrayObjectAdapter mSecondaryActionsAdapter;
- private PlayPauseAction mPlayPauseAction;
- private RepeatAction mRepeatAction;
- private ThumbsUpAction mThumbsUpAction;
- private ThumbsDownAction mThumbsDownAction;
- private ShuffleAction mShuffleAction;
- private FastForwardAction mFastForwardAction;
- private RewindAction mRewindAction;
- private SkipNextAction mSkipNextAction;
- private SkipPreviousAction mSkipPreviousAction;
- private PlaybackControlsRow mPlaybackControlsRow;
- private ArrayList<Movie> mItems = new ArrayList<Movie>();
- private int mCurrentItem;
- private Handler mHandler;
- private Runnable mRunnable;
- private Movie mSelectedMovie;
- private PicassoPlaybackControlsRowTarget mPlaybackControlsRowTarget;
-
- OnPlayPauseClickedListener mCallback;
-
- // Container Activity must implement this interface
- public interface OnPlayPauseClickedListener {
- public void onFragmentPlayPause(Movie movie, int position, Boolean playPause);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- Log.i(TAG, "onCreate");
- super.onCreate(savedInstanceState);
- sContext = getActivity();
-
- mItems = new ArrayList<Movie>();
- mSelectedMovie = (Movie) getActivity()
- .getIntent().getSerializableExtra(${detailsActivity}.MOVIE);
-
- List<Movie> movies = MovieList.list;
-
- ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
-
- for (int j = 0; j < movies.size(); j++) {
- mItems.add(movies.get(j));
- if (mSelectedMovie.getTitle().contentEquals(movies.get(j).getTitle())) {
- mCurrentItem = j;
- }
- }
-
- mHandler = new Handler();
-
- setBackgroundType(BACKGROUND_TYPE);
- setFadingEnabled(false);
-
- setupRows();
-
- setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
- @Override
- public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- Log.i(TAG, "onItemSelected: " + item + " row " + row);
- }
- });
- setOnItemViewClickedListener(new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- Log.i(TAG, "onItemClicked: " + item + " row " + row);
- }
- });
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- // This makes sure that the container activity has implemented
- // the callback interface. If not, it throws an exception
- try {
- mCallback = (OnPlayPauseClickedListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement OnPlayPauseClickedListener");
- }
- }
-
- private void setupRows() {
-
- ClassPresenterSelector ps = new ClassPresenterSelector();
-
- PlaybackControlsRowPresenter playbackControlsRowPresenter;
- if (SHOW_DETAIL) {
- playbackControlsRowPresenter = new PlaybackControlsRowPresenter(
- new DescriptionPresenter());
- } else {
- playbackControlsRowPresenter = new PlaybackControlsRowPresenter();
- }
- playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
- public void onActionClicked(Action action) {
- if (action.getId() == mPlayPauseAction.getId()) {
- if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
- startProgressAutomation();
- setFadingEnabled(true);
- mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
- mPlaybackControlsRow.getCurrentTime(), true);
- } else {
- stopProgressAutomation();
- setFadingEnabled(false);
- mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
- mPlaybackControlsRow.getCurrentTime(), false);
- }
- } else if (action.getId() == mSkipNextAction.getId()) {
- next();
- } else if (action.getId() == mSkipPreviousAction.getId()) {
- prev();
- } else if (action.getId() == mFastForwardAction.getId()) {
- Toast.makeText(getActivity(), "TODO: Fast Forward", Toast.LENGTH_SHORT).show();
- } else if (action.getId() == mRewindAction.getId()) {
- Toast.makeText(getActivity(), "TODO: Rewind", Toast.LENGTH_SHORT).show();
- }
- if (action instanceof PlaybackControlsRow.MultiAction) {
- ((PlaybackControlsRow.MultiAction) action).nextIndex();
- notifyChanged(action);
- }
- }
- });
- playbackControlsRowPresenter.setSecondaryActionsHidden(HIDE_MORE_ACTIONS);
-
- ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
- ps.addClassPresenter(ListRow.class, new ListRowPresenter());
- mRowsAdapter = new ArrayObjectAdapter(ps);
-
- addPlaybackControlsRow();
- addOtherRows();
-
- setAdapter(mRowsAdapter);
- }
-
- private int getDuration() {
- Movie movie = mItems.get(mCurrentItem);
- MediaMetadataRetriever mmr = new MediaMetadataRetriever();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- mmr.setDataSource(movie.getVideoUrl(), new HashMap<String, String>());
- } else {
- mmr.setDataSource(movie.getVideoUrl());
- }
- String time = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
- long duration = Long.parseLong(time);
- return (int) duration;
- }
-
- private void addPlaybackControlsRow() {
- if (SHOW_DETAIL) {
- mPlaybackControlsRow = new PlaybackControlsRow(mSelectedMovie);
- } else {
- mPlaybackControlsRow = new PlaybackControlsRow();
- }
- mRowsAdapter.add(mPlaybackControlsRow);
-
- updatePlaybackRow(mCurrentItem);
-
- ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
- mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
- mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
- mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
- mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
-
- mPlayPauseAction = new PlayPauseAction(sContext);
- mRepeatAction = new RepeatAction(sContext);
- mThumbsUpAction = new ThumbsUpAction(sContext);
- mThumbsDownAction = new ThumbsDownAction(sContext);
- mShuffleAction = new ShuffleAction(sContext);
- mSkipNextAction = new PlaybackControlsRow.SkipNextAction(sContext);
- mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(sContext);
- mFastForwardAction = new PlaybackControlsRow.FastForwardAction(sContext);
- mRewindAction = new PlaybackControlsRow.RewindAction(sContext);
-
- if (PRIMARY_CONTROLS > 5) {
- mPrimaryActionsAdapter.add(mThumbsUpAction);
- } else {
- mSecondaryActionsAdapter.add(mThumbsUpAction);
- }
- mPrimaryActionsAdapter.add(mSkipPreviousAction);
- if (PRIMARY_CONTROLS > 3) {
- mPrimaryActionsAdapter.add(new PlaybackControlsRow.RewindAction(sContext));
- }
- mPrimaryActionsAdapter.add(mPlayPauseAction);
- if (PRIMARY_CONTROLS > 3) {
- mPrimaryActionsAdapter.add(new PlaybackControlsRow.FastForwardAction(sContext));
- }
- mPrimaryActionsAdapter.add(mSkipNextAction);
-
- mSecondaryActionsAdapter.add(mRepeatAction);
- mSecondaryActionsAdapter.add(mShuffleAction);
- if (PRIMARY_CONTROLS > 5) {
- mPrimaryActionsAdapter.add(mThumbsDownAction);
- } else {
- mSecondaryActionsAdapter.add(mThumbsDownAction);
- }
- mSecondaryActionsAdapter.add(new PlaybackControlsRow.HighQualityAction(sContext));
- mSecondaryActionsAdapter.add(new PlaybackControlsRow.ClosedCaptioningAction(sContext));
- }
-
- private void notifyChanged(Action action) {
- ArrayObjectAdapter adapter = mPrimaryActionsAdapter;
- if (adapter.indexOf(action) >= 0) {
- adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
- return;
- }
- adapter = mSecondaryActionsAdapter;
- if (adapter.indexOf(action) >= 0) {
- adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
- return;
- }
- }
-
- private void updatePlaybackRow(int index) {
- if (mPlaybackControlsRow.getItem() != null) {
- Movie item = (Movie) mPlaybackControlsRow.getItem();
- item.setTitle(mItems.get(mCurrentItem).getTitle());
- item.setStudio(mItems.get(mCurrentItem).getStudio());
- }
- if (SHOW_IMAGE) {
- mPlaybackControlsRowTarget = new PicassoPlaybackControlsRowTarget(mPlaybackControlsRow);
- updateVideoImage(mItems.get(mCurrentItem).getCardImageURI());
- }
- mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
- mPlaybackControlsRow.setTotalTime(getDuration());
- mPlaybackControlsRow.setCurrentTime(0);
- mPlaybackControlsRow.setBufferedProgress(0);
- }
-
- private void addOtherRows() {
- ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
- for (Movie movie : mItems) {
- listRowAdapter.add(movie);
- }
- <#if buildApi gte 22>
- HeaderItem header = new HeaderItem(0, getString(R.string.related_movies));
- <#else>
- HeaderItem header = new HeaderItem(0, getString(R.string.related_movies), null);
- </#if>
- mRowsAdapter.add(new ListRow(header, listRowAdapter));
-
- }
-
- private int getUpdatePeriod() {
- if (getView() == null || mPlaybackControlsRow.getTotalTime() <= 0) {
- return DEFAULT_UPDATE_PERIOD;
- }
- return Math.max(UPDATE_PERIOD, mPlaybackControlsRow.getTotalTime() / getView().getWidth());
- }
-
- private void startProgressAutomation() {
- mRunnable = new Runnable() {
- @Override
- public void run() {
- int updatePeriod = getUpdatePeriod();
- int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod;
- int totalTime = mPlaybackControlsRow.getTotalTime();
- mPlaybackControlsRow.setCurrentTime(currentTime);
- mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
-
- if (totalTime > 0 && totalTime <= currentTime) {
- next();
- }
- mHandler.postDelayed(this, updatePeriod);
- }
- };
- mHandler.postDelayed(mRunnable, getUpdatePeriod());
- }
-
- private void next() {
- if (++mCurrentItem >= mItems.size()) {
- mCurrentItem = 0;
- }
-
- if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
- mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
- }
- else {
- mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
- }
- updatePlaybackRow(mCurrentItem);
- }
-
- private void prev() {
- if (--mCurrentItem < 0) {
- mCurrentItem = mItems.size() - 1;
- }
- if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
- mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
- }
- else {
- mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
- }
- updatePlaybackRow(mCurrentItem);
- }
-
- private void stopProgressAutomation() {
- if (mHandler != null && mRunnable != null) {
- mHandler.removeCallbacks(mRunnable);
- }
- }
-
- @Override
- public void onStop() {
- stopProgressAutomation();
- super.onStop();
- }
-
- static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
- @Override
- protected void onBindDescription(ViewHolder viewHolder, Object item) {
- viewHolder.getTitle().setText(((Movie) item).getTitle());
- viewHolder.getSubtitle().setText(((Movie) item).getStudio());
- }
- }
-
- public static class PicassoPlaybackControlsRowTarget implements Target {
- PlaybackControlsRow mPlaybackControlsRow;
-
- public PicassoPlaybackControlsRowTarget(PlaybackControlsRow playbackControlsRow) {
- mPlaybackControlsRow = playbackControlsRow;
- }
-
- @Override
- public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
- Drawable bitmapDrawable = new BitmapDrawable(sContext.getResources(), bitmap);
- mPlaybackControlsRow.setImageDrawable(bitmapDrawable);
- }
-
- @Override
- public void onBitmapFailed(Drawable drawable) {
- mPlaybackControlsRow.setImageDrawable(drawable);
- }
-
- @Override
- public void onPrepareLoad(Drawable drawable) {
- // Do nothing, default_background manager has its own transitions
- }
- }
-
- protected void updateVideoImage(URI uri) {
- Picasso.with(sContext)
- .load(uri.toString())
- .resize(Utils.convertDpToPixel(sContext, CARD_WIDTH),
- Utils.convertDpToPixel(sContext, CARD_HEIGHT))
- .into(mPlaybackControlsRowTarget);
- }
-
-}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/Utils.java.ftl b/base/templates/activities/AndroidTVActivity/root/src/app_package/Utils.java.ftl
deleted file mode 100644
index 1d03bf1..0000000
--- a/base/templates/activities/AndroidTVActivity/root/src/app_package/Utils.java.ftl
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package ${packageName};
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.graphics.Point;
-import android.view.Display;
-import android.view.WindowManager;
-import android.widget.Toast;
-
-/**
- * A collection of utility methods, all static.
- */
-public class Utils {
-
- /*
- * Making sure public utility methods remain static
- */
- private Utils() {
- }
-
- /**
- * Returns the screen/display size
- */
- public static Point getDisplaySize(Context context) {
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
- return size;
- }
-
- /**
- * Shows a (long) toast
- */
- public static void showToast(Context context, String msg) {
- Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
- }
-
- /**
- * Shows a (long) toast.
- */
- public static void showToast(Context context, int resourceId) {
- Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
- }
-
- public static int convertDpToPixel(Context ctx, int dp) {
- float density = ctx.getResources().getDisplayMetrics().density;
- return Math.round((float) dp * density);
- }
-
- /**
- * Formats time in milliseconds to hh:mm:ss string format.
- */
- public static String formatMillis(int millis) {
- String result = "";
- int hr = millis / 3600000;
- millis %= 3600000;
- int min = millis / 60000;
- millis %= 60000;
- int sec = millis / 1000;
- if (hr > 0) {
- result += hr + ":";
- }
- if (min >= 0) {
- if (min > 9) {
- result += min + ":";
- } else {
- result += "0" + min + ":";
- }
- }
- if (sec > 9) {
- result += sec;
- } else {
- result += "0" + sec;
- }
- return result;
- }
-}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/VideoDetailsFragment.java.ftl b/base/templates/activities/AndroidTVActivity/root/src/app_package/VideoDetailsFragment.java.ftl
deleted file mode 100644
index 1d3d2a7..0000000
--- a/base/templates/activities/AndroidTVActivity/root/src/app_package/VideoDetailsFragment.java.ftl
+++ /dev/null
@@ -1,200 +0,0 @@
-package ${packageName};
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.Collections;
-import java.util.List;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
-import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v4.app.ActivityOptionsCompat;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-public class ${detailsFragment} extends DetailsFragment {
- private static final String TAG = "${truncate(detailsFragment,23)}";
-
- private static final int ACTION_WATCH_TRAILER = 1;
- private static final int ACTION_RENT = 2;
- private static final int ACTION_BUY = 3;
-
- private static final int DETAIL_THUMB_WIDTH = 274;
- private static final int DETAIL_THUMB_HEIGHT = 274;
-
- private static final int NUM_COLS = 10;
-
- private static final String MOVIE = "Movie";
-
- private Movie mSelectedMovie;
-
- private Drawable mDefaultBackground;
- private Target mBackgroundTarget;
- private DisplayMetrics mMetrics;
- private DetailsOverviewRowPresenter mDorPresenter;
- private DetailRowBuilderTask mDetailRowBuilderTask;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- Log.i(TAG, "onCreate DetailsFragment");
- super.onCreate(savedInstanceState);
-
- mDorPresenter =
- new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
-
- BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
- backgroundManager.attach(getActivity().getWindow());
- mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
-
- mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
-
- mMetrics = new DisplayMetrics();
- getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
-
- mSelectedMovie = (Movie) getActivity().getIntent().getSerializableExtra(MOVIE);
- mDetailRowBuilderTask = (DetailRowBuilderTask) new DetailRowBuilderTask().execute(mSelectedMovie);
- mDorPresenter.setSharedElementEnterTransition(getActivity(),
- ${detailsActivity}.SHARED_ELEMENT_NAME);
-
- updateBackground(mSelectedMovie.getBackgroundImageURI());
- setOnItemViewClickedListener(new ItemViewClickedListener());
-
- }
-
- @Override
- public void onStop() {
- mDetailRowBuilderTask.cancel(true);
- super.onStop();
- }
-
- private class DetailRowBuilderTask extends AsyncTask<Movie, Integer, DetailsOverviewRow> {
- @Override
- protected DetailsOverviewRow doInBackground(Movie... movies) {
- mSelectedMovie = movies[0];
-
- DetailsOverviewRow row = new DetailsOverviewRow(mSelectedMovie);
- try {
- Bitmap poster = Picasso.with(getActivity())
- .load(mSelectedMovie.getCardImageUrl())
- .resize(Utils.convertDpToPixel(getActivity().getApplicationContext(), DETAIL_THUMB_WIDTH),
- Utils.convertDpToPixel(getActivity().getApplicationContext(), DETAIL_THUMB_HEIGHT))
- .centerCrop()
- .get();
- row.setImageBitmap(getActivity(), poster);
- } catch (IOException e) {
- }
-
- row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(
- R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));
- row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),
- getResources().getString(R.string.rent_2)));
- row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),
- getResources().getString(R.string.buy_2)));
- return row;
- }
-
- @Override
- protected void onPostExecute(DetailsOverviewRow detailRow) {
- ClassPresenterSelector ps = new ClassPresenterSelector();
- // set detail background and style
- mDorPresenter.setBackgroundColor(getResources().getColor(R.color.detail_background));
- mDorPresenter.setStyleLarge(true);
- mDorPresenter.setOnActionClickedListener(new OnActionClickedListener() {
- @Override
- public void onActionClicked(Action action) {
- if (action.getId() == ACTION_WATCH_TRAILER) {
- Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);
- intent.putExtra(getResources().getString(R.string.movie), mSelectedMovie);
- intent.putExtra(getResources().getString(R.string.should_start), true);
- startActivity(intent);
- }
- else {
- Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
- }
- }
- });
-
- ps.addClassPresenter(DetailsOverviewRow.class, mDorPresenter);
- ps.addClassPresenter(ListRow.class,
- new ListRowPresenter());
-
- ArrayObjectAdapter adapter = new ArrayObjectAdapter(ps);
- adapter.add(detailRow);
-
- String subcategories[] = {
- getString(R.string.related_movies)
- };
- List<Movie> list = MovieList.list;
- Collections.shuffle(list);
- ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
- for (int j = 0; j < NUM_COLS; j++) {
- listRowAdapter.add(list.get(j % 5));
- }
-
- <#if buildApi gte 22>
- HeaderItem header = new HeaderItem(0, subcategories[0]);
- <#else>
- HeaderItem header = new HeaderItem(0, subcategories[0], null);
- </#if>
-
- adapter.add(new ListRow(header, listRowAdapter));
-
- setAdapter(adapter);
- }
-
- }
-
- private final class ItemViewClickedListener implements OnItemViewClickedListener {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
-
- if (item instanceof Movie) {
- Movie movie = (Movie) item;
- Log.d(TAG, "Item: " + item.toString());
- Intent intent = new Intent(getActivity(), ${detailsActivity}.class);
- intent.putExtra(${detailsActivity}.MOVIE, movie);
-
- Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
- getActivity(),
- ((ImageCardView) itemViewHolder.view).getMainImageView(),
- ${detailsActivity}.SHARED_ELEMENT_NAME).toBundle();
- getActivity().startActivity(intent, bundle);
- }
- }
- }
-
- protected void updateBackground(URI uri) {
- Log.d(TAG, "uri" + uri);
- Log.d(TAG, "metrics" + mMetrics.toString());
- Picasso.with(getActivity())
- .load(uri.toString())
- .resize(mMetrics.widthPixels, mMetrics.heightPixels)
- .error(mDefaultBackground)
- .into(mBackgroundTarget);
- }
-
-}
diff --git a/base/templates/activities/AndroidTVActivity/template-leanback-TV.png b/base/templates/activities/AndroidTVActivity/template-leanback-TV.png
deleted file mode 100644
index dccb72c..0000000
Binary files a/base/templates/activities/AndroidTVActivity/template-leanback-TV.png and /dev/null differ
diff --git a/base/templates/activities/AndroidTVActivity/template.xml b/base/templates/activities/AndroidTVActivity/template.xml
deleted file mode 100644
index 5b696b2..0000000
--- a/base/templates/activities/AndroidTVActivity/template.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="5"
- name="Android TV Activity"
- minApi="7"
- minBuildApi="19"
- description="Creates a new Android TV activity using Leanback Support library.">
-
- <category value="Activity" />
- <formfactor value="TV" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Main Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="mainFragment"
- name="Main Fragment"
- type="string"
- constraints="class|unique|nonempty"
- default="MainFragment"
- suggest="MainFragment"
- help="The name of the main fragment." />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="Title"
- suggest="${activityClass} Title"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="detailsActivity"
- name="Details Activity"
- type="string"
- constraints="class|unique|nonempty"
- default="DetailsActivity"
- suggest="DetailsActivity"
- help="The name of the details activity." />
-
- <parameter
- id="detailsLayoutName"
- name="Details Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(detailsActivity)}"
- default="details"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="detailsFragment"
- name="Details Fragment"
- type="string"
- constraints="class|unique|nonempty"
- default="VideoDetailsFragment"
- suggest="VideoDetailsFragment"
- help="The name of the details fragment." />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template-leanback-TV.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/BlankActivity/recipe.xml.ftl b/base/templates/activities/BlankActivity/recipe.xml.ftl
deleted file mode 100644
index 18565ac..0000000
--- a/base/templates/activities/BlankActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <instantiate from="res/layout/activity_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="src/app_package/SimpleActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl b/base/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
deleted file mode 100644
index d6aeee3..0000000
--- a/base/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
+++ /dev/null
@@ -1,40 +0,0 @@
-package ${packageName};
-
-import ${superClassFqcn};
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-public class ${activityClass} extends ${superClass} {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.${menuName}, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_settings) {
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-}
diff --git a/base/templates/activities/BlankActivity/template.xml b/base/templates/activities/BlankActivity/template.xml
deleted file mode 100644
index ce45c37..0000000
--- a/base/templates/activities/BlankActivity/template.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="4"
- name="Blank Activity"
- minApi="7"
- minBuildApi="14"
- description="Creates a new blank activity with an action bar.">
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="MainActivity"
- suggest="${activityClass}"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="menuName"
- name="Menu Resource Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="menu_${classToResource(activityClass)}"
- default="menu_main"
- help="The name of the resource file to create for the menu items" />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_blank_activity.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/BlankActivity/template_blank_activity.png b/base/templates/activities/BlankActivity/template_blank_activity.png
deleted file mode 100644
index 975f490..0000000
Binary files a/base/templates/activities/BlankActivity/template_blank_activity.png and /dev/null differ
diff --git a/base/templates/activities/BlankActivityWithFragment/globals.xml.ftl b/base/templates/activities/BlankActivityWithFragment/globals.xml.ftl
deleted file mode 100644
index 06453f7..0000000
--- a/base/templates/activities/BlankActivityWithFragment/globals.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="fragmentClass" value="${activityClass}Fragment" />
- <global id="manifestOut" value="${manifestDir}" />
-<#if hasDependency('com.android.support:appcompat-v7')>
- <global id="appCompat" type="boolean" value="true" />
- <global id="superClass" type="string" value="<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
- <global id="superClassFqcn" type="string" value="android.support.v7.app.<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
-<#else>
- <global id="appCompat" type="boolean" value="false" />
- <global id="superClass" type="string" value="Activity"/>
- <global id="superClassFqcn" type="string" value="android.app.Activity"/>
-</#if>
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/BlankActivityWithFragment/recipe.xml.ftl b/base/templates/activities/BlankActivityWithFragment/recipe.xml.ftl
deleted file mode 100644
index 5a02a4f..0000000
--- a/base/templates/activities/BlankActivityWithFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <instantiate from="res/layout/activity_fragment_container.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="res/layout/fragment_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-
- <instantiate from="src/app_package/SimpleActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <instantiate from="src/app_package/SimpleActivityFragment.ftl"
- to="${escapeXmlAttribute(srcOut)}/${fragmentClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}Fragment.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl b/base/templates/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl
deleted file mode 100644
index 8e7ee88..0000000
--- a/base/templates/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/fragment"
- android:name="${packageName}.${fragmentClass}"
- tools:layout="@layout/${fragmentLayoutName}"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
diff --git a/base/templates/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl b/base/templates/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl
deleted file mode 100644
index 429834b..0000000
--- a/base/templates/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl
+++ /dev/null
@@ -1,16 +0,0 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
- tools:context="${relativePackage}.${fragmentClass}">
-
- <TextView
- android:text="@string/hello_world"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-</RelativeLayout>
diff --git a/base/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl b/base/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl
deleted file mode 100644
index ec2c685..0000000
--- a/base/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-package ${packageName};
-
-import ${superClassFqcn};
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-public class ${activityClass} extends ${superClass} {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
- }
-
- <#include "include_options_menu.java.ftl">
-}
diff --git a/base/templates/activities/BlankActivityWithFragment/template.xml b/base/templates/activities/BlankActivityWithFragment/template.xml
deleted file mode 100644
index ec45482..0000000
--- a/base/templates/activities/BlankActivityWithFragment/template.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="4"
- name="Blank Activity with Fragment"
- minApi="7"
- minBuildApi="14"
- description="Creates a new blank activity, with an action bar and a contained Fragment.">
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="fragmentLayoutName"
- name="Fragment Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="fragment_${classToResource(activityClass)}"
- default="fragment_main"
- help="The name of the layout to create for the activity's content fragment"/>
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="MainActivity"
- suggest="${activityClass}"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="menuName"
- name="Menu Resource Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="menu_${classToResource(activityClass)}"
- default="menu_main"
- help="The name of the resource file to create for the menu items" />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_blank_activity_fragment.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/BlankWearActivity/recipe.xml.ftl b/base/templates/activities/BlankWearActivity/recipe.xml.ftl
deleted file mode 100644
index 18adb94..0000000
--- a/base/templates/activities/BlankWearActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="res/layout/blank_activity.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="res/layout/round.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${roundLayout}.xml" />
- <instantiate from="res/layout/rect.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${rectLayout}.xml" />
-
- <instantiate from="src/app_package/BlankActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/BlankWearActivity/template.xml b/base/templates/activities/BlankWearActivity/template.xml
deleted file mode 100644
index 293fcdc..0000000
--- a/base/templates/activities/BlankWearActivity/template.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Blank Wear Activity"
- minApi="20"
- minBuildApi="20"
- description="Creates a blank activity for Android Wear">
-
- <category value="Activity" />
- <formfactor value="Wear" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="roundLayout"
- name="Round Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="round_${activityToLayout(activityClass)}"
- default="round"
- help="The name of the round layout to create for the activity" />
-
-
- <parameter
- id="rectLayout"
- name="Rectangular Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="rect_${activityToLayout(activityClass)}"
- default="rect"
- help="The name of the rectangular layout to create for the activity" />
-
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="true"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it the default launchable activity" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>templates-WatchViewStub-Wear.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/BlankWearActivity/templates-WatchViewStub-Wear.png b/base/templates/activities/BlankWearActivity/templates-WatchViewStub-Wear.png
deleted file mode 100644
index 1800b23..0000000
Binary files a/base/templates/activities/BlankWearActivity/templates-WatchViewStub-Wear.png and /dev/null differ
diff --git a/base/templates/activities/EmptyActivity/recipe.xml.ftl b/base/templates/activities/EmptyActivity/recipe.xml.ftl
deleted file mode 100644
index 41088d9..0000000
--- a/base/templates/activities/EmptyActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <copy from="res/layout/activity_simple.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="src/app_package/SimpleActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/FullscreenActivity/globals.xml.ftl b/base/templates/activities/FullscreenActivity/globals.xml.ftl
deleted file mode 100644
index f60259b..0000000
--- a/base/templates/activities/FullscreenActivity/globals.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="simpleName" value="${activityToLayout(activityClass)}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/FullscreenActivity/recipe.xml.ftl b/base/templates/activities/FullscreenActivity/recipe.xml.ftl
deleted file mode 100644
index 30c9236..0000000
--- a/base/templates/activities/FullscreenActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:support-v4:${targetApi}.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/attrs.xml"
- to="${escapeXmlAttribute(resOut)}/values/attrs.xml" />
- <merge from="res/values/colors.xml"
- to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
- <merge from="res/values/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
- <merge from="res/values-v11/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
- <instantiate from="res/layout/activity_fullscreen.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="src/app_package/FullscreenActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <instantiate from="src/app_package/util/SystemUiHider.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/util/SystemUiHider.java" />
- <instantiate from="src/app_package/util/SystemUiHiderBase.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderBase.java" />
- <instantiate from="src/app_package/util/SystemUiHiderHoneycomb.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderHoneycomb.java" />
-
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl b/base/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
deleted file mode 100644
index 4364a41..0000000
--- a/base/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
+++ /dev/null
@@ -1,200 +0,0 @@
-package ${packageName};
-
-import ${packageName}.util.SystemUiHider;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.view.MotionEvent;
-import android.view.View;
-<#if parentActivityClass != "">
-import android.view.MenuItem;
-import android.support.v4.app.NavUtils;
-</#if>
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * An example full-screen activity that shows and hides the system UI (i.e.
- * status bar and navigation/system bar) with user interaction.
- *
- * @see SystemUiHider
- */
-public class ${activityClass} extends Activity {
- /**
- * Whether or not the system UI should be auto-hidden after
- * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
- */
- private static final boolean AUTO_HIDE = true;
-
- /**
- * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
- * user interaction before hiding the system UI.
- */
- private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
-
- /**
- * If set, will toggle the system UI visibility upon interaction. Otherwise,
- * will show the system UI visibility upon interaction.
- */
- private static final boolean TOGGLE_ON_CLICK = true;
-
- /**
- * The flags to pass to {@link SystemUiHider#getInstance}.
- */
- private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
-
- /**
- * The instance of the {@link SystemUiHider} for this activity.
- */
- private SystemUiHider mSystemUiHider;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.${layoutName});
- <#if parentActivityClass != "">
- setupActionBar();
- </#if>
-
- final View controlsView = findViewById(R.id.fullscreen_content_controls);
- final View contentView = findViewById(R.id.fullscreen_content);
-
- // Set up an instance of SystemUiHider to control the system UI for
- // this activity.
- mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
- mSystemUiHider.setup();
- mSystemUiHider
- .setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
- // Cached values.
- int mControlsHeight;
- int mShortAnimTime;
-
- @Override
- @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
- public void onVisibilityChange(boolean visible) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
- // If the ViewPropertyAnimator API is available
- // (Honeycomb MR2 and later), use it to animate the
- // in-layout UI controls at the bottom of the
- // screen.
- if (mControlsHeight == 0) {
- mControlsHeight = controlsView.getHeight();
- }
- if (mShortAnimTime == 0) {
- mShortAnimTime = getResources().getInteger(
- android.R.integer.config_shortAnimTime);
- }
- controlsView.animate()
- .translationY(visible ? 0 : mControlsHeight)
- .setDuration(mShortAnimTime);
- } else {
- // If the ViewPropertyAnimator APIs aren't
- // available, simply show or hide the in-layout UI
- // controls.
- controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
- if (visible && AUTO_HIDE) {
- // Schedule a hide().
- delayedHide(AUTO_HIDE_DELAY_MILLIS);
- }
- }
- });
-
- // Set up the user interaction to manually show or hide the system UI.
- contentView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (TOGGLE_ON_CLICK) {
- mSystemUiHider.toggle();
- } else {
- mSystemUiHider.show();
- }
- }
- });
-
- // Upon interacting with UI controls, delay any scheduled hide()
- // operations to prevent the jarring behavior of controls going away
- // while interacting with the UI.
- findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
- }
-
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
-
- // Trigger the initial hide() shortly after the activity has been
- // created, to briefly hint to the user that UI controls
- // are available.
- delayedHide(100);
- }
-
- <#if parentActivityClass != "">
- /**
- * Set up the {@link android.app.ActionBar}, if the API is available.
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private void setupActionBar() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- // Show the Up button in the action bar.
- getActionBar().setDisplayHomeAsUpEnabled(true);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- if (id == android.R.id.home) {
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- // TODO: If Settings has multiple levels, Up should navigate up
- // that hierarchy.
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
- </#if>
-
- /**
- * Touch listener to use for in-layout UI controls to delay hiding the
- * system UI. This is to prevent the jarring behavior of controls going away
- * while interacting with activity UI.
- */
- View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
- @Override
- public boolean onTouch(View view, MotionEvent motionEvent) {
- if (AUTO_HIDE) {
- delayedHide(AUTO_HIDE_DELAY_MILLIS);
- }
- return false;
- }
- };
-
- Handler mHideHandler = new Handler();
- Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mSystemUiHider.hide();
- }
- };
-
- /**
- * Schedules a call to hide() in [delay] milliseconds, canceling any
- * previously scheduled calls.
- */
- private void delayedHide(int delayMillis) {
- mHideHandler.removeCallbacks(mHideRunnable);
- mHideHandler.postDelayed(mHideRunnable, delayMillis);
- }
-}
diff --git a/base/templates/activities/FullscreenActivity/root/src/app_package/util/SystemUiHider.java.ftl b/base/templates/activities/FullscreenActivity/root/src/app_package/util/SystemUiHider.java.ftl
deleted file mode 100644
index 6eefabf..0000000
--- a/base/templates/activities/FullscreenActivity/root/src/app_package/util/SystemUiHider.java.ftl
+++ /dev/null
@@ -1,172 +0,0 @@
-package ${packageName}.util;
-
-import android.app.Activity;
-import android.os.Build;
-import android.view.View;
-
-/**
- * A utility class that helps with showing and hiding system UI such as the
- * status bar and navigation/system bar. This class uses backward-compatibility
- * techniques described in <a href=
- * "http://developer.android.com/training/backward-compatible-ui/index.html">
- * Creating Backward-Compatible UIs</a> to ensure that devices running any
- * version of Android OS are supported. More specifically, there are separate
- * implementations of this abstract class: for newer devices,
- * {@link #getInstance} will return a {@link SystemUiHiderHoneycomb} instance,
- * while on older devices {@link #getInstance} will return a
- * {@link SystemUiHiderBase} instance.
- * <p>
- * For more on system bars, see <a href=
- * "http://developer.android.com/design/get-started/ui-overview.html#system-bars"
- * > System Bars</a>.
- *
- * @see android.view.View#setSystemUiVisibility(int)
- * @see android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN
- */
-public abstract class SystemUiHider {
- /**
- * When this flag is set, the
- * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}
- * flag will be set on older devices, making the status bar "float" on top
- * of the activity layout. This is most useful when there are no controls at
- * the top of the activity layout.
- * <p>
- * This flag isn't used on newer devices because the <a
- * href="http://developer.android.com/design/patterns/actionbar.html">action
- * bar</a>, the most important structural element of an Android app, should
- * be visible and not obscured by the system UI.
- */
- public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1;
-
- /**
- * When this flag is set, {@link #show()} and {@link #hide()} will toggle
- * the visibility of the status bar. If there is a navigation bar, show and
- * hide will toggle low profile mode.
- */
- public static final int FLAG_FULLSCREEN = 0x2;
-
- /**
- * When this flag is set, {@link #show()} and {@link #hide()} will toggle
- * the visibility of the navigation bar, if it's present on the device and
- * the device allows hiding it. In cases where the navigation bar is present
- * but cannot be hidden, show and hide will toggle low profile mode.
- */
- public static final int FLAG_HIDE_NAVIGATION = FLAG_FULLSCREEN | 0x4;
-
- /**
- * The activity associated with this UI hider object.
- */
- protected Activity mActivity;
-
- /**
- * The view on which {@link View#setSystemUiVisibility(int)} will be called.
- */
- protected View mAnchorView;
-
- /**
- * The current UI hider flags.
- *
- * @see #FLAG_FULLSCREEN
- * @see #FLAG_HIDE_NAVIGATION
- * @see #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES
- */
- protected int mFlags;
-
- /**
- * The current visibility callback.
- */
- protected OnVisibilityChangeListener mOnVisibilityChangeListener = sDummyListener;
-
- /**
- * Creates and returns an instance of {@link SystemUiHider} that is
- * appropriate for this device. The object will be either a
- * {@link SystemUiHiderBase} or {@link SystemUiHiderHoneycomb} depending on
- * the device.
- *
- * @param activity The activity whose window's system UI should be
- * controlled by this class.
- * @param anchorView The view on which
- * {@link View#setSystemUiVisibility(int)} will be called.
- * @param flags Either 0 or any combination of {@link #FLAG_FULLSCREEN},
- * {@link #FLAG_HIDE_NAVIGATION}, and
- * {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES}.
- */
- public static SystemUiHider getInstance(Activity activity, View anchorView, int flags) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- return new SystemUiHiderHoneycomb(activity, anchorView, flags);
- } else {
- return new SystemUiHiderBase(activity, anchorView, flags);
- }
- }
-
- protected SystemUiHider(Activity activity, View anchorView, int flags) {
- mActivity = activity;
- mAnchorView = anchorView;
- mFlags = flags;
- }
-
- /**
- * Sets up the system UI hider. Should be called from
- * {@link Activity#onCreate}.
- */
- public abstract void setup();
-
- /**
- * Returns whether or not the system UI is visible.
- */
- public abstract boolean isVisible();
-
- /**
- * Hide the system UI.
- */
- public abstract void hide();
-
- /**
- * Show the system UI.
- */
- public abstract void show();
-
- /**
- * Toggle the visibility of the system UI.
- */
- public void toggle() {
- if (isVisible()) {
- hide();
- } else {
- show();
- }
- }
-
- /**
- * Registers a callback, to be triggered when the system UI visibility
- * changes.
- */
- public void setOnVisibilityChangeListener(OnVisibilityChangeListener listener) {
- if (listener == null) {
- listener = sDummyListener;
- }
-
- mOnVisibilityChangeListener = listener;
- }
-
- /**
- * A dummy no-op callback for use when there is no other listener set.
- */
- private static OnVisibilityChangeListener sDummyListener = new OnVisibilityChangeListener() {
- @Override
- public void onVisibilityChange(boolean visible) {
- }
- };
-
- /**
- * A callback interface used to listen for system UI visibility changes.
- */
- public interface OnVisibilityChangeListener {
- /**
- * Called when the system UI visibility has changed.
- *
- * @param visible True if the system UI is visible.
- */
- public void onVisibilityChange(boolean visible);
- }
-}
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/globals.xml.ftl b/base/templates/activities/GoogleAdMobAdsActivity/globals.xml.ftl
deleted file mode 100644
index 09e5c5e..0000000
--- a/base/templates/activities/GoogleAdMobAdsActivity/globals.xml.ftl
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
-<#if hasDependency('com.android.support:appcompat-v7')>
- <global id="appCompat" type="boolean" value="true" />
- <global id="superClass" type="string" value="<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
- <global id="superClassFqcn" type="string" value="android.support.v7.app.<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
-<#else>
- <global id="appCompat" type="boolean" value="false" />
- <global id="superClass" type="string" value="Activity"/>
- <global id="superClassFqcn" type="string" value="android.app.Activity"/>
-</#if>
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/recipe.xml.ftl b/base/templates/activities/GoogleAdMobAdsActivity/recipe.xml.ftl
deleted file mode 100644
index 12b954d..0000000
--- a/base/templates/activities/GoogleAdMobAdsActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.google.android.gms:play-services-ads:7.0.0" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <instantiate from="res/layout/activity_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="src/app_package/SimpleActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/root/AndroidManifest.xml.ftl b/base/templates/activities/GoogleAdMobAdsActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 415020a..0000000
--- a/base/templates/activities/GoogleAdMobAdsActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,36 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <!-- Include required permissions for Google Mobile Ads to run. -->
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
- <application>
- <!--This meta-data tag is required to use Google Play Services. -->
- <meta-data android:name="com.google.android.gms.version"
- android:value="@integer/google_play_services_version" />
-
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- <!--Include the AdActivity configChanges and theme. -->
- <activity android:name="com.google.android.gms.ads.AdActivity"
- android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
- android:theme="@android:style/Theme.Translucent" />
- </application>
-
-</manifest>
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/template.xml b/base/templates/activities/GoogleAdMobAdsActivity/template.xml
deleted file mode 100644
index ca85a5b..0000000
--- a/base/templates/activities/GoogleAdMobAdsActivity/template.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="4"
- name="Google AdMob Ads Activity"
- minApi="7"
- minBuildApi="14"
- description="Creates an activity with AdMob Ad fragment.">
-
- <category value="Google" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="MainActivity"
- suggest="${activityClass}"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="menuName"
- name="Menu Resource Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="menu_${classToResource(activityClass)}"
- default="menu_main"
- help="The name of the resource file to create for the menu items" />
-
- <parameter
- id="adFormat"
- name="Ad Format"
- type="enum"
- default="interstitial"
- help="Select Interstitial Ad or Banner Ad." >
- <option id="interstitial">Interstitial</option>
- <option id="banner">Banner</option>
- </parameter>
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_admob_activity.png</thumb>
- <!-- attributes act as selectors based on chosen parameters -->
- <thumb adFormat="interstitial">template_admob_activity_interstitial.png</thumb>
- <thumb adFormat="banner">template_admob_activity_banner.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity.png b/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity.png
deleted file mode 100644
index 646f6ac..0000000
Binary files a/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity.png and /dev/null differ
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_banner.png b/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_banner.png
deleted file mode 100644
index ce0c65c..0000000
Binary files a/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_banner.png and /dev/null differ
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_interstitial.png b/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_interstitial.png
deleted file mode 100644
index 5c8bb21..0000000
Binary files a/base/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_interstitial.png and /dev/null differ
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/template_blank_activity.png b/base/templates/activities/GoogleAdMobAdsActivity/template_blank_activity.png
deleted file mode 100644
index 975f490..0000000
Binary files a/base/templates/activities/GoogleAdMobAdsActivity/template_blank_activity.png and /dev/null differ
diff --git a/base/templates/activities/GoogleMapsActivity/globals.xml.ftl b/base/templates/activities/GoogleMapsActivity/globals.xml.ftl
deleted file mode 100644
index c78ddf5..0000000
--- a/base/templates/activities/GoogleMapsActivity/globals.xml.ftl
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="projectOut" value="." />
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="debugResOut" value="${projectOut}/src/debug/res" />
- <global id="releaseResOut" value="${projectOut}/src/release/res" />
- <global id="resOut" value="${resDir}" />
- <global id="simpleName" value="${activityToLayout(activityClass)}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/GoogleMapsActivity/recipe.xml.ftl b/base/templates/activities/GoogleMapsActivity/recipe.xml.ftl
deleted file mode 100644
index 926c013..0000000
--- a/base/templates/activities/GoogleMapsActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
- <dependency mavenUrl="com.android.support:appcompat-v7:${targetApi}.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/layout/activity_map.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="src/app_package/MapActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <merge from="debugRes/values/google_maps_api.xml.ftl"
- to="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
-
- <merge from="releaseRes/values/google_maps_api.xml.ftl"
- to="${escapeXmlAttribute(releaseResOut)}/values/google_maps_api.xml" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <!-- Display the API key instructions. -->
- <open file="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
-</recipe>
diff --git a/base/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl b/base/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 7c3e9e8..0000000
--- a/base/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,37 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
- <!--
- The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
- Google Maps Android API v2, but you must specify either coarse or fine
- location permissions for the 'MyLocation' functionality.
- -->
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
- <application>
-
- <!--
- The API key for Google Maps-based APIs is defined as a string resource.
- (See the file "res/values/google_maps_api.xml").
- Note that the API key is linked to the encryption key used to sign the APK.
- You need a different API key for each encryption key, including the release key that is used to
- sign the APK for publishing.
- You can define the keys for the debug and release targets in src/debug/ and src/release/.
- -->
- <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key"/>
-
- <activity android:name="${relativePackage}.${activityClass}"
- android:label="@string/title_${simpleName}">
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
-
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/activities/GoogleMapsActivity/template.xml b/base/templates/activities/GoogleMapsActivity/template.xml
deleted file mode 100644
index 5db66fc..0000000
--- a/base/templates/activities/GoogleMapsActivity/template.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- name="Google Maps Activity"
- description="Creates a new activity with a Google Map"
- minApi="9"
- minBuildApi="14">
-
- <dependency name="android-support-v4" revision="8" />
-
- <category value="Google" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MapsActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_map"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="Map"
- help="The name of the activity." />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <thumbs>
- <thumb>template_map_activity.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/GoogleMapsActivity/template_map_activity.png b/base/templates/activities/GoogleMapsActivity/template_map_activity.png
deleted file mode 100644
index 212b31e..0000000
Binary files a/base/templates/activities/GoogleMapsActivity/template_map_activity.png and /dev/null differ
diff --git a/base/templates/activities/GoogleMapsWearActivity/globals.xml.ftl b/base/templates/activities/GoogleMapsWearActivity/globals.xml.ftl
deleted file mode 100644
index 58b383e..0000000
--- a/base/templates/activities/GoogleMapsWearActivity/globals.xml.ftl
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="projectOut" value="." />
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="debugResOut" value="${projectOut}/src/debug/res" />
- <global id="releaseResOut" value="${projectOut}/src/release/res" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/GoogleMapsWearActivity/recipe.xml.ftl b/base/templates/activities/GoogleMapsWearActivity/recipe.xml.ftl
deleted file mode 100644
index b02fd4a..0000000
--- a/base/templates/activities/GoogleMapsWearActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <dependency mavenUrl="com.google.android.gms:play-services-wearable:7.5.0" />
- <dependency mavenUrl="com.google.android.gms:play-services-maps:7.5.0" />
- <dependency mavenUrl="com.google.android.support:wearable:1.2.0" />
-
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/layout/activity_map.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="res/layout/activity_map.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="src/app_package/MapActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <merge from="debugRes/values/google_maps_api.xml.ftl"
- to="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
-
- <merge from="releaseRes/values/google_maps_api.xml.ftl"
- to="${escapeXmlAttribute(releaseResOut)}/values/google_maps_api.xml" />
-
- <!-- Display the API key instructions. -->
- <open file="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
-</recipe>
diff --git a/base/templates/activities/GoogleMapsWearActivity/root/AndroidManifest.xml.ftl b/base/templates/activities/GoogleMapsWearActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 64e82fe..0000000
--- a/base/templates/activities/GoogleMapsWearActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,38 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <!--
- The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
- Google Maps Android API v2, but you must specify either coarse or fine
- location permissions for the 'MyLocation' functionality.
- -->
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
- <application>
-
- <!--
- The API key for Google Maps-based APIs is defined as a string resource.
- (See the file "res/values/google_maps_api.xml").
- Note that the API key is linked to the encryption key used to sign the APK.
- You need a different API key for each encryption key, including the release key that is used to
- sign the APK for publishing.
- You can define the keys for the debug and release targets in src/debug/ and src/release/.
- -->
- <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key"/>
-
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- >
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/activities/GoogleMapsWearActivity/template.xml b/base/templates/activities/GoogleMapsWearActivity/template.xml
deleted file mode 100644
index 0c30f2b..0000000
--- a/base/templates/activities/GoogleMapsWearActivity/template.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Google Maps Wear Activity"
- minApi="20"
- minBuildApi="20"
- description="Creates an new activity with a Google Map for Android Wear">
-
- <category value="Google" />
- <formfactor value="Wear" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MapsActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_map"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="true"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it the default launchable activity" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_thumb.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/GoogleMapsWearActivity/template_thumb.png b/base/templates/activities/GoogleMapsWearActivity/template_thumb.png
deleted file mode 100644
index 4153368..0000000
Binary files a/base/templates/activities/GoogleMapsWearActivity/template_thumb.png and /dev/null differ
diff --git a/base/templates/activities/GooglePlayServicesActivity/globals.xml.ftl b/base/templates/activities/GooglePlayServicesActivity/globals.xml.ftl
deleted file mode 100644
index 4bf836f..0000000
--- a/base/templates/activities/GooglePlayServicesActivity/globals.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/GooglePlayServicesActivity/recipe.xml.ftl b/base/templates/activities/GooglePlayServicesActivity/recipe.xml.ftl
deleted file mode 100644
index 2a13c74..0000000
--- a/base/templates/activities/GooglePlayServicesActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="src/app_package/activity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-</recipe>
diff --git a/base/templates/activities/GooglePlayServicesActivity/root/AndroidManifest.xml.ftl b/base/templates/activities/GooglePlayServicesActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 0001419..0000000
--- a/base/templates/activities/GooglePlayServicesActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- >
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
-
- <meta-data
- android:name="com.google.android.gms.version"
- android:value="@integer/google_play_services_version" />
- </application>
-
-</manifest>
diff --git a/base/templates/activities/GooglePlayServicesActivity/root/res/values/strings.xml.ftl b/base/templates/activities/GooglePlayServicesActivity/root/res/values/strings.xml.ftl
deleted file mode 100644
index 24849de..0000000
--- a/base/templates/activities/GooglePlayServicesActivity/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,5 +0,0 @@
-<resources>
- <#if !isNewProject>
- <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
- </#if>
-</resources>
diff --git a/base/templates/activities/GooglePlayServicesActivity/root/src/app_package/activity.java.ftl b/base/templates/activities/GooglePlayServicesActivity/root/src/app_package/activity.java.ftl
deleted file mode 100644
index ff87c41..0000000
--- a/base/templates/activities/GooglePlayServicesActivity/root/src/app_package/activity.java.ftl
+++ /dev/null
@@ -1,202 +0,0 @@
-package ${packageName};
-
-import android.app.Activity;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.Intent;
-import android.content.IntentSender.SendIntentException;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesUtil;
-import com.google.android.gms.common.api.GoogleApiClient;
-
-<#if includeCast>
-import com.google.android.gms.cast.Cast;
-</#if>
-<#if includeDrive>
-import com.google.android.gms.drive.Drive;
-</#if>
-<#if includeGames>
-import com.google.android.gms.games.Games;
-</#if>
-<#if includePlus>
-import com.google.android.gms.plus.Plus;
-</#if>
-<#if includeWallet>
-import com.google.android.gms.wallet.Wallet;
-</#if>
-
-public class ${activityClass} extends Activity implements
- GoogleApiClient.ConnectionCallbacks,
- GoogleApiClient.OnConnectionFailedListener {
-
- private static final String TAG = "${truncate(activityClass,23)}";
-
- private static final String KEY_IN_RESOLUTION = "is_in_resolution";
-
- /**
- * Request code for auto Google Play Services error resolution.
- */
- protected static final int REQUEST_CODE_RESOLUTION = 1;
-
- /**
- * Google API client.
- */
- private GoogleApiClient mGoogleApiClient;
-
- /**
- * Determines if the client is in a resolution state, and
- * waiting for resolution intent to return.
- */
- private boolean mIsInResolution;
-
- /**
- * Called when the activity is starting. Restores the activity state.
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mIsInResolution = savedInstanceState.getBoolean(KEY_IN_RESOLUTION, false);
- }
- }
-
- /**
- * Called when the Activity is made visible.
- * A connection to Play Services need to be initiated as
- * soon as the activity is visible. Registers {@code ConnectionCallbacks}
- * and {@code OnConnectionFailedListener} on the
- * activities itself.
- */
- @Override
- protected void onStart() {
- super.onStart();
- if (mGoogleApiClient == null) {
- mGoogleApiClient = new GoogleApiClient.Builder(this)
- <#if includeCast>
- .addApi(Cast.API)
- </#if>
- <#if includeDrive>
- .addApi(Drive.API)
- </#if>
- <#if includeGames>
- .addApi(Games.API)
- </#if>
- <#if includePlus>
- .addApi(Plus.API)
- </#if>
- <#if includeWallet>
- .addApi(Wallet.API)
- </#if>
- <#if includeDrive>
- .addScope(Drive.SCOPE_FILE)
- </#if>
- <#if includeGames>
- .addScope(Games.SCOPE_GAMES)
- </#if>
- <#if includePlus>
- .addScope(Plus.SCOPE_PLUS_LOGIN)
- </#if>
- // Optionally, add additional APIs and scopes if required.
- .addConnectionCallbacks(this)
- .addOnConnectionFailedListener(this)
- .build();
- }
- mGoogleApiClient.connect();
- }
-
- /**
- * Called when activity gets invisible. Connection to Play Services needs to
- * be disconnected as soon as an activity is invisible.
- */
- @Override
- protected void onStop() {
- if (mGoogleApiClient != null) {
- mGoogleApiClient.disconnect();
- }
- super.onStop();
- }
-
- /**
- * Saves the resolution state.
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(KEY_IN_RESOLUTION, mIsInResolution);
- }
-
- /**
- * Handles Google Play Services resolution callbacks.
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- switch (requestCode) {
- case REQUEST_CODE_RESOLUTION:
- retryConnecting();
- break;
- }
- }
-
- private void retryConnecting() {
- mIsInResolution = false;
- if (!mGoogleApiClient.isConnecting()) {
- mGoogleApiClient.connect();
- }
- }
-
- /**
- * Called when {@code mGoogleApiClient} is connected.
- */
- @Override
- public void onConnected(Bundle connectionHint) {
- Log.i(TAG, "GoogleApiClient connected");
- // TODO: Start making API requests.
- }
-
- /**
- * Called when {@code mGoogleApiClient} connection is suspended.
- */
- @Override
- public void onConnectionSuspended(int cause) {
- Log.i(TAG, "GoogleApiClient connection suspended");
- retryConnecting();
- }
-
- /**
- * Called when {@code mGoogleApiClient} is trying to connect but failed.
- * Handle {@code result.getResolution()} if there is a resolution
- * available.
- */
- @Override
- public void onConnectionFailed(ConnectionResult result) {
- Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
- if (!result.hasResolution()) {
- // Show a localized error dialog.
- GooglePlayServicesUtil.getErrorDialog(
- result.getErrorCode(), this, 0, new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- retryConnecting();
- }
- }).show();
- return;
- }
- // If there is an existing resolution error being displayed or a resolution
- // activity has started before, do nothing and wait for resolution
- // progress to be completed.
- if (mIsInResolution) {
- return;
- }
- mIsInResolution = true;
- try {
- result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
- } catch (SendIntentException e) {
- Log.e(TAG, "Exception while starting resolution activity", e);
- retryConnecting();
- }
- }
-}
diff --git a/base/templates/activities/GooglePlayServicesActivity/template.xml b/base/templates/activities/GooglePlayServicesActivity/template.xml
deleted file mode 100644
index 0d467a6..0000000
--- a/base/templates/activities/GooglePlayServicesActivity/template.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="1"
- name="Google Play Services Activity"
- description="Creates a new activity that initiates and connects to the Google Play Services unified client."
- minApi="7"
- minBuildApi="7">
-
- <category value="Google" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- default="GooglePlayServicesActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="GooglePlayServicesActivity"
- suggest="${activityClass}"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <parameter
- id="includeGames"
- name="Enable Google Play Games Services"
- type="boolean"
- default="false">
- </parameter>
-
- <parameter
- id="includePlus"
- name="Enable Google+ APIs"
- type="boolean"
- default="false">
- </parameter>
-
- <parameter
- id="includeDrive"
- name="Enable Google Drive APIs"
- type="boolean"
- default="false">
- </parameter>
-
- <parameter
- id="includeCast"
- name="Enable Google Cast APIs"
- type="boolean"
- default="false">
- </parameter>
-
- <parameter
- id="includeWallet"
- name="Enable Google Wallet Instant Buy APIs"
- type="boolean"
- default="false">
- </parameter>
-
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_play_services_activity.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/activities/GooglePlayServicesActivity/template_play_services_activity.png b/base/templates/activities/GooglePlayServicesActivity/template_play_services_activity.png
deleted file mode 100644
index fffaac4..0000000
Binary files a/base/templates/activities/GooglePlayServicesActivity/template_play_services_activity.png and /dev/null differ
diff --git a/base/templates/activities/LoginActivity/globals.xml.ftl b/base/templates/activities/LoginActivity/globals.xml.ftl
deleted file mode 100644
index c77e9ac..0000000
--- a/base/templates/activities/LoginActivity/globals.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0"?>
-<globals>
-<#if hasDependency('com.android.support:appcompat-v7')>
- <global id="appCompat" type="boolean" value="true" />
- <global id="superClass" type="string" value="<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
- <global id="superClassFqcn" type="string" value="android.support.v7.app.<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
-<#else>
- <global id="appCompat" type="boolean" value="false" />
- <global id="superClass" type="string" value="Activity"/>
- <global id="superClassFqcn" type="string" value="android.app.Activity"/>
-</#if>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="simpleName" value="${activityToLayout(activityClass)}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/LoginActivity/recipe.xml.ftl b/base/templates/activities/LoginActivity/recipe.xml.ftl
deleted file mode 100644
index b9fbb48..0000000
--- a/base/templates/activities/LoginActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
- <dependency mavenUrl="com.android.support:appcompat-v7:${targetApi}.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
-
- <instantiate from="res/layout/activity_login.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
-
- <instantiate from="src/app_package/LoginActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <#if includeGooglePlus>
- <instantiate from="src/app_package/PlusBaseActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/PlusBaseActivity.java" />
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
-</recipe>
diff --git a/base/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl b/base/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
deleted file mode 100644
index 0571d4b..0000000
--- a/base/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
+++ /dev/null
@@ -1,451 +0,0 @@
-package ${packageName};
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.TargetApi;
-<#if !includeGooglePlus>import android.app.Activity;</#if>
-import android.app.LoaderManager.LoaderCallbacks;
-import android.content.ContentResolver;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.AsyncTask;
-<#if minApiLevel lt 14>import android.os.Build.VERSION;</#if>
-import android.os.Build;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.inputmethod.EditorInfo;
-import android.widget.ArrayAdapter;
-import android.widget.AutoCompleteTextView;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-<#if includeGooglePlus>
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesUtil;
-import com.google.android.gms.common.SignInButton;
-</#if>
-import java.util.ArrayList;
-import java.util.List;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * A login screen that offers login via email/password<#if includeGooglePlus> and via Google+ sign in</#if>.
-<#if includeGooglePlus> * <p/>
- * ************ IMPORTANT SETUP NOTES: ************
- * In order for Google+ sign in to work with your app, you must first go to:
- * https://developers.google.com/+/mobile/android/getting-started#step_1_enable_the_google_api
- * and follow the steps in "Step 1" to create an OAuth 2.0 client for your package.</#if>
- */
-public class ${activityClass} extends <#if includeGooglePlus>PlusBase</#if>Activity implements LoaderCallbacks<Cursor>{
-
- /**
- * A dummy authentication store containing known user names and passwords.
- * TODO: remove after connecting to a real authentication system.
- */
- private static final String[] DUMMY_CREDENTIALS = new String[]{
- "foo at example.com:hello", "bar at example.com:world"
- };
- /**
- * Keep track of the login task to ensure we can cancel it if requested.
- */
- private UserLoginTask mAuthTask = null;
-
- // UI references.
- private AutoCompleteTextView mEmailView;
- private EditText mPasswordView;
- private View mProgressView;<#if includeGooglePlus>
- private View mEmailLoginFormView;
- private SignInButton mPlusSignInButton;
- private View mSignOutButtons;</#if>
- private View mLoginFormView;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_login);
-<#if parentActivityClass != "">
- setupActionBar();
-</#if>
-<#if includeGooglePlus>
-
- // Find the Google+ sign in button.
- mPlusSignInButton = (SignInButton) findViewById(R.id.plus_sign_in_button);
- if (supportsGooglePlayServices()) {
- // Set a listener to connect the user when the G+ button is clicked.
- mPlusSignInButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- signIn();
- }
- });
- } else {
- // Don't offer G+ sign in if the app's version is too low to support Google Play
- // Services.
- mPlusSignInButton.setVisibility(View.GONE);
- return;
- }
-</#if>
-
- // Set up the login form.
- mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
- populateAutoComplete();
-
- mPasswordView = (EditText) findViewById(R.id.password);
- mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
- @Override
- public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
- if (id == R.id.login || id == EditorInfo.IME_NULL) {
- attemptLogin();
- return true;
- }
- return false;
- }
- });
-
- Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
- mEmailSignInButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- attemptLogin();
- }
- });
-
- mLoginFormView = findViewById(R.id.login_form);
- mProgressView = findViewById(R.id.login_progress);<#if includeGooglePlus>
- mEmailLoginFormView = findViewById(R.id.email_login_form);
- mSignOutButtons = findViewById(R.id.plus_sign_out_buttons);</#if>
- }
-
- private void populateAutoComplete() {
-<#if minApiLevel gte 14>
- getLoaderManager().initLoader(0, null, this);
-<#else>
- if (VERSION.SDK_INT >= 14) {
- // Use ContactsContract.Profile (API 14+)
- getLoaderManager().initLoader(0, null, this);
- } else if (VERSION.SDK_INT >= 8) {
- // Use AccountManager (API 8+)
- new SetupEmailAutoCompleteTask().execute(null, null);
- }
-</#if>
- }
-
- <#if parentActivityClass != "">
- /**
- * Set up the {@link android.app.ActionBar}, if the API is available.
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private void setupActionBar() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- // Show the Up button in the action bar.
- getActionBar().setDisplayHomeAsUpEnabled(true);
- }
- }
- </#if>
-
- /**
- * Attempts to sign in or register the account specified by the login form.
- * If there are form errors (invalid email, missing fields, etc.), the
- * errors are presented and no actual login attempt is made.
- */
- public void attemptLogin() {
- if (mAuthTask != null) {
- return;
- }
-
- // Reset errors.
- mEmailView.setError(null);
- mPasswordView.setError(null);
-
- // Store values at the time of the login attempt.
- String email = mEmailView.getText().toString();
- String password = mPasswordView.getText().toString();
-
- boolean cancel = false;
- View focusView = null;
-
- // Check for a valid password, if the user entered one.
- if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
- mPasswordView.setError(getString(R.string.error_invalid_password));
- focusView = mPasswordView;
- cancel = true;
- }
-
- // Check for a valid email address.
- if (TextUtils.isEmpty(email)) {
- mEmailView.setError(getString(R.string.error_field_required));
- focusView = mEmailView;
- cancel = true;
- } else if (!isEmailValid(email)) {
- mEmailView.setError(getString(R.string.error_invalid_email));
- focusView = mEmailView;
- cancel = true;
- }
-
- if (cancel) {
- // There was an error; don't attempt login and focus the first
- // form field with an error.
- focusView.requestFocus();
- } else {
- // Show a progress spinner, and kick off a background task to
- // perform the user login attempt.
- showProgress(true);
- mAuthTask = new UserLoginTask(email, password);
- mAuthTask.execute((Void) null);
- }
- }
- private boolean isEmailValid(String email) {
- //TODO: Replace this with your own logic
- return email.contains("@");
- }
-
- private boolean isPasswordValid(String password) {
- //TODO: Replace this with your own logic
- return password.length() > 4;
- }
-
- /**
- * Shows the progress UI and hides the login form.
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
- public void showProgress(final boolean show) {
- // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
- // for very easy animations. If available, use these APIs to fade-in
- // the progress spinner.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
- int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
-
- mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
- mLoginFormView.animate().setDuration(shortAnimTime).alpha(
- show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
- }
- });
-
- mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
- mProgressView.animate().setDuration(shortAnimTime).alpha(
- show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- });
- } else {
- // The ViewPropertyAnimator APIs are not available, so simply show
- // and hide the relevant UI components.
- mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
- mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
- }
- }
-<#if includeGooglePlus>
-
- @Override
- protected void onPlusClientSignIn() {
- //Set up sign out and disconnect buttons.
- Button signOutButton = (Button) findViewById(R.id.plus_sign_out_button);
- signOutButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- signOut();
- }
- });
- Button disconnectButton = (Button) findViewById(R.id.plus_disconnect_button);
- disconnectButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- revokeAccess();
- }
- });
- }
-
- @Override
- protected void onPlusClientBlockingUI(boolean show) {
- showProgress(show);
- }
-
- @Override
- protected void updateConnectButtonState() {
- //TODO: Update this logic to also handle the user logged in by email.
- boolean connected = getPlusClient().isConnected();
-
- mSignOutButtons.setVisibility(connected ? View.VISIBLE : View.GONE);
- mPlusSignInButton.setVisibility(connected ? View.GONE : View.VISIBLE);
- mEmailLoginFormView.setVisibility(connected ? View.GONE : View.VISIBLE);
- }
-
- @Override
- protected void onPlusClientRevokeAccess() {
- // TODO: Access to the user's G+ account has been revoked. Per the developer terms, delete
- // any stored user data here.
- }
-
- @Override
- protected void onPlusClientSignOut() {
-
- }
-
- /**
- * Check if the device supports Google Play Services. It's best
- * practice to check first rather than handling this as an error case.
- *
- * @return whether the device supports Google Play Services
- */
- private boolean supportsGooglePlayServices() {
- return GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) ==
- ConnectionResult.SUCCESS;
- }
-</#if>
-
- @Override
- public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
- return new CursorLoader(this,
- // Retrieve data rows for the device user's 'profile' contact.
- Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
- ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
-
- // Select only email addresses.
- ContactsContract.Contacts.Data.MIMETYPE +
- " = ?", new String[]{ContactsContract.CommonDataKinds.Email
- .CONTENT_ITEM_TYPE},
-
- // Show primary email addresses first. Note that there won't be
- // a primary email address if the user hasn't specified one.
- ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
- List<String> emails = new ArrayList<String>();
- cursor.moveToFirst();
- while (!cursor.isAfterLast()) {
- emails.add(cursor.getString(ProfileQuery.ADDRESS));
- cursor.moveToNext();
- }
-
- addEmailsToAutoComplete(emails);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> cursorLoader) {
-
- }
-
- private interface ProfileQuery {
- String[] PROJECTION = {
- ContactsContract.CommonDataKinds.Email.ADDRESS,
- ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
- };
-
- int ADDRESS = 0;
- int IS_PRIMARY = 1;
- }
-
-<#if minApiLevel lt 14>
- /**
- * Use an AsyncTask to fetch the user's email addresses on a background thread, and update
- * the email text field with results on the main UI thread.
- */
- class SetupEmailAutoCompleteTask extends AsyncTask<Void, Void, List<String>> {
-
- @Override
- protected List<String> doInBackground(Void... voids) {
- ArrayList<String> emailAddressCollection = new ArrayList<String>();
-
- // Get all emails from the user's contacts and copy them to a list.
- ContentResolver cr = getContentResolver();
- Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
- null, null, null);
- while (emailCur.moveToNext()) {
- String email = emailCur.getString(emailCur.getColumnIndex(ContactsContract
- .CommonDataKinds.Email.DATA));
- emailAddressCollection.add(email);
- }
- emailCur.close();
-
- return emailAddressCollection;
- }
-
- @Override
- protected void onPostExecute(List<String> emailAddressCollection) {
- addEmailsToAutoComplete(emailAddressCollection);
- }
- }
-</#if>
-
- private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
- //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
- ArrayAdapter<String> adapter =
- new ArrayAdapter<String>(LoginActivity.this,
- android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
-
- mEmailView.setAdapter(adapter);
- }
-
- /**
- * Represents an asynchronous login/registration task used to authenticate
- * the user.
- */
- public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
-
- private final String mEmail;
- private final String mPassword;
-
- UserLoginTask(String email, String password) {
- mEmail = email;
- mPassword = password;
- }
-
- @Override
- protected Boolean doInBackground(Void... params) {
- // TODO: attempt authentication against a network service.
-
- try {
- // Simulate network access.
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- return false;
- }
-
- for (String credential : DUMMY_CREDENTIALS) {
- String[] pieces = credential.split(":");
- if (pieces[0].equals(mEmail)) {
- // Account exists, return true if the password matches.
- return pieces[1].equals(mPassword);
- }
- }
-
- // TODO: register the new account here.
- return true;
- }
-
- @Override
- protected void onPostExecute(final Boolean success) {
- mAuthTask = null;
- showProgress(false);
-
- if (success) {
- finish();
- } else {
- mPasswordView.setError(getString(R.string.error_incorrect_password));
- mPasswordView.requestFocus();
- }
- }
-
- @Override
- protected void onCancelled() {
- mAuthTask = null;
- showProgress(false);
- }
- }
-}
-
diff --git a/base/templates/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl b/base/templates/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl
deleted file mode 100644
index 943d562..0000000
--- a/base/templates/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl
+++ /dev/null
@@ -1,283 +0,0 @@
-package ${packageName};
-
-import ${superClassFqcn};
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesClient;
-import com.google.android.gms.common.Scopes;
-import com.google.android.gms.plus.PlusClient;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * A base class to wrap communication with the Google Play Services PlusClient.
- */
-public abstract class PlusBaseActivity extends ${superClass}
- implements GooglePlayServicesClient.ConnectionCallbacks,
- GooglePlayServicesClient.OnConnectionFailedListener {
-
- private static final String TAG = PlusBaseActivity.class.getSimpleName();
-
- // A magic number we will use to know that our sign-in error resolution activity has completed
- private static final int OUR_REQUEST_CODE = 49404;
-
- // A flag to stop multiple dialogues appearing for the user
- private boolean mAutoResolveOnFail;
-
- // A flag to track when a connection is already in progress
- public boolean mPlusClientIsConnecting = false;
-
- // This is the helper object that connects to Google Play Services.
- private PlusClient mPlusClient;
-
- // The saved result from {@link #onConnectionFailed(ConnectionResult)}. If a connection
- // attempt has been made, this is non-null.
- // If this IS null, then the connect method is still running.
- private ConnectionResult mConnectionResult;
-
- /**
- * Called when the {@link PlusClient} revokes access to this app.
- */
- protected abstract void onPlusClientRevokeAccess();
-
- /**
- * Called when the PlusClient is successfully connected.
- */
- protected abstract void onPlusClientSignIn();
-
- /**
- * Called when the {@link PlusClient} is disconnected.
- */
- protected abstract void onPlusClientSignOut();
-
- /**
- * Called when the {@link PlusClient} is blocking the UI. If you have a progress bar widget,
- * this tells you when to show or hide it.
- */
- protected abstract void onPlusClientBlockingUI(boolean show);
-
- /**
- * Called when there is a change in connection state. If you have "Sign in"/ "Connect",
- * "Sign out"/ "Disconnect", or "Revoke access" buttons, this lets you know when their states
- * need to be updated.
- */
- protected abstract void updateConnectButtonState();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Initialize the PlusClient connection.
- // Scopes indicate the information about the user your application will be able to access.
- mPlusClient =
- new PlusClient.Builder(this, this, this).setScopes(Scopes.PLUS_LOGIN,
- Scopes.PLUS_ME).build();
- }
-
- /**
- * Try to sign in the user.
- */
- public void signIn() {
- if (!mPlusClient.isConnected()) {
- // Show the dialog as we are now signing in.
- setProgressBarVisible(true);
- // Make sure that we will start the resolution (e.g. fire the intent and pop up a
- // dialog for the user) for any errors that come in.
- mAutoResolveOnFail = true;
- // We should always have a connection result ready to resolve,
- // so we can start that process.
- if (mConnectionResult != null) {
- startResolution();
- } else {
- // If we don't have one though, we can start connect in
- // order to retrieve one.
- initiatePlusClientConnect();
- }
- }
-
- updateConnectButtonState();
- }
-
- /**
- * Connect the {@link PlusClient} only if a connection isn't already in progress. This will
- * call back to {@link #onConnected(android.os.Bundle)} or
- * {@link #onConnectionFailed(com.google.android.gms.common.ConnectionResult)}.
- */
- private void initiatePlusClientConnect() {
- if (!mPlusClient.isConnected() && !mPlusClient.isConnecting()) {
- mPlusClient.connect();
- }
- }
-
- /**
- * Disconnect the {@link PlusClient} only if it is connected (otherwise, it can throw an error.)
- * This will call back to {@link #onDisconnected()}.
- */
- private void initiatePlusClientDisconnect() {
- if (mPlusClient.isConnected()) {
- mPlusClient.disconnect();
- }
- }
-
- /**
- * Sign out the user (so they can switch to another account).
- */
- public void signOut() {
-
- // We only want to sign out if we're connected.
- if (mPlusClient.isConnected()) {
- // Clear the default account in order to allow the user to potentially choose a
- // different account from the account chooser.
- mPlusClient.clearDefaultAccount();
-
- // Disconnect from Google Play Services, then reconnect in order to restart the
- // process from scratch.
- initiatePlusClientDisconnect();
-
- Log.v(TAG, "Sign out successful!");
- }
-
- updateConnectButtonState();
- }
-
- /**
- * Revoke Google+ authorization completely.
- */
- public void revokeAccess() {
-
- if (mPlusClient.isConnected()) {
- // Clear the default account as in the Sign Out.
- mPlusClient.clearDefaultAccount();
-
- // Revoke access to this entire application. This will call back to
- // onAccessRevoked when it is complete, as it needs to reach the Google
- // authentication servers to revoke all tokens.
- mPlusClient.revokeAccessAndDisconnect(new PlusClient.OnAccessRevokedListener() {
- public void onAccessRevoked(ConnectionResult result) {
- updateConnectButtonState();
- onPlusClientRevokeAccess();
- }
- });
- }
-
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- initiatePlusClientConnect();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- initiatePlusClientDisconnect();
- }
-
- public boolean isPlusClientConnecting() {
- return mPlusClientIsConnecting;
- }
-
- private void setProgressBarVisible(boolean flag) {
- mPlusClientIsConnecting = flag;
- onPlusClientBlockingUI(flag);
- }
-
- /**
- * A helper method to flip the mResolveOnFail flag and start the resolution
- * of the ConnectionResult from the failed connect() call.
- */
- private void startResolution() {
- try {
- // Don't start another resolution now until we have a result from the activity we're
- // about to start.
- mAutoResolveOnFail = false;
- // If we can resolve the error, then call start resolution and pass it an integer tag
- // we can use to track.
- // This means that when we get the onActivityResult callback we'll know it's from
- // being started here.
- mConnectionResult.startResolutionForResult(this, OUR_REQUEST_CODE);
- } catch (IntentSender.SendIntentException e) {
- // Any problems, just try to connect() again so we get a new ConnectionResult.
- mConnectionResult = null;
- initiatePlusClientConnect();
- }
- }
-
- /**
- * An earlier connection failed, and we're now receiving the result of the resolution attempt
- * by PlusClient.
- *
- * @see #onConnectionFailed(ConnectionResult)
- */
- @Override
- protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
- updateConnectButtonState();
- if (requestCode == OUR_REQUEST_CODE && responseCode == RESULT_OK) {
- // If we have a successful result, we will want to be able to resolve any further
- // errors, so turn on resolution with our flag.
- mAutoResolveOnFail = true;
- // If we have a successful result, let's call connect() again. If there are any more
- // errors to resolve we'll get our onConnectionFailed, but if not,
- // we'll get onConnected.
- initiatePlusClientConnect();
- } else if (requestCode == OUR_REQUEST_CODE && responseCode != RESULT_OK) {
- // If we've got an error we can't resolve, we're no longer in the midst of signing
- // in, so we can stop the progress spinner.
- setProgressBarVisible(false);
- }
- }
-
- /**
- * Successfully connected (called by PlusClient)
- */
- @Override
- public void onConnected(Bundle connectionHint) {
- updateConnectButtonState();
- setProgressBarVisible(false);
- onPlusClientSignIn();
- }
-
- /**
- * Successfully disconnected (called by PlusClient)
- */
- @Override
- public void onDisconnected() {
- updateConnectButtonState();
- onPlusClientSignOut();
- }
-
- /**
- * Connection failed for some reason (called by PlusClient)
- * Try and resolve the result. Failure here is usually not an indication of a serious error,
- * just that the user's input is needed.
- *
- * @see #onActivityResult(int, int, Intent)
- */
- @Override
- public void onConnectionFailed(ConnectionResult result) {
- updateConnectButtonState();
-
- // Most of the time, the connection will fail with a user resolvable result. We can store
- // that in our mConnectionResult property ready to be used when the user clicks the
- // sign-in button.
- if (result.hasResolution()) {
- mConnectionResult = result;
- if (mAutoResolveOnFail) {
- // This is a local helper function that starts the resolution of the problem,
- // which may be showing the user an account chooser or similar.
- startResolution();
- }
- }
- }
-
- public PlusClient getPlusClient() {
- return mPlusClient;
- }
-
-}
diff --git a/base/templates/activities/MasterDetailFlow/globals.xml.ftl b/base/templates/activities/MasterDetailFlow/globals.xml.ftl
deleted file mode 100644
index 3ab716f..0000000
--- a/base/templates/activities/MasterDetailFlow/globals.xml.ftl
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0"?>
-<globals>
-<#if hasDependency('com.android.support:appcompat-v7')>
- <global id="appCompat" type="boolean" value="true" />
- <global id="superClass" type="string" value="<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
- <global id="superClassFqcn" type="string" value="android.support.v7.app.<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
-<#else>
- <global id="appCompat" type="boolean" value="false" />
- <global id="superClass" type="string" value="Activity"/>
- <global id="superClassFqcn" type="string" value="android.app.Activity"/>
-</#if>
- <global id="Support" value="${(hasDependency('com.android.support:appcompat-v7'))?string('Support','')}" />
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="CollectionName" value="${extractLetters(objectKind)}List" />
- <global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" />
- <global id="DetailName" value="${extractLetters(objectKind)}Detail" />
- <global id="detail_name" value="${extractLetters(objectKind?lower_case)}_detail" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/MasterDetailFlow/recipe.xml.ftl b/base/templates/activities/MasterDetailFlow/recipe.xml.ftl
deleted file mode 100644
index 5e2099f..0000000
--- a/base/templates/activities/MasterDetailFlow/recipe.xml.ftl
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:support-v4:${targetApi}.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <#if minApiLevel lt 13>
- <merge from="res/values-large/refs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
- <merge from="res/values-sw600dp/refs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
- </#if>
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="res/layout/activity_content_detail.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/activity_${detail_name}.xml" />
- <instantiate from="res/layout/activity_content_list.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/activity_${collection_name}.xml" />
- <#if minApiLevel lt 13>
- <instantiate from="res/layout/activity_content_twopane.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" />
- <#else>
- <instantiate from="res/layout/activity_content_twopane.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout-sw600dp/activity_${extractLetters(objectKind?lower_case)}_list.xml" />
- </#if>
- <instantiate from="res/layout/fragment_content_detail.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
-
- <instantiate from="src/app_package/ContentDetailActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${DetailName}Activity.java" />
- <instantiate from="src/app_package/ContentDetailFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
- <instantiate from="src/app_package/ContentListActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${CollectionName}Activity.java" />
- <instantiate from="src/app_package/ContentListFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${CollectionName}Fragment.java" />
- <instantiate from="src/app_package/dummy/DummyContent.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
-</recipe>
diff --git a/base/templates/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl b/base/templates/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
deleted file mode 100644
index 724ab30..0000000
--- a/base/templates/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
+++ /dev/null
@@ -1,39 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
- android:baselineAligned="false"
- android:divider="?android:attr/dividerHorizontal"
- android:orientation="horizontal"
- android:showDividers="middle"
- tools:context="${relativePackage}.${CollectionName}Activity">
-
- <!--
- This layout is a two-pane layout for the ${objectKindPlural}
- master/detail flow.
- <#if minApiLevel lt 13>See res/values-large/refs.xml and
- res/values-sw600dp/refs.xml for an example of layout aliases
- that replace the single-pane version of the layout with
- this two-pane version.
-
- For more on layout aliases, see:
- http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters</#if>
- -->
-
- <fragment
- android:id="@+id/${collection_name}"
- android:name="${packageName}.${CollectionName}Fragment"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- tools:layout="@android:layout/list_content" />
-
- <FrameLayout
- android:id="@+id/${detail_name}_container"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="3" />
-
-</LinearLayout>
diff --git a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl b/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
deleted file mode 100644
index 7eca5ce..0000000
--- a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
+++ /dev/null
@@ -1,80 +0,0 @@
-package ${packageName};
-
-import ${superClassFqcn};
-import android.content.Intent;
-import android.os.Bundle;
-<#if minApiLevel lt 16>import android.support.v4.app.NavUtils;</#if>
-import android.view.MenuItem;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * An activity representing a single ${objectKind} detail screen. This
- * activity is only used on handset devices. On tablet-size devices,
- * item details are presented side-by-side with a list of items
- * in a {@link ${CollectionName}Activity}.
- * <p>
- * This activity is mostly just a 'shell' activity containing nothing
- * more than a {@link ${DetailName}Fragment}.
- */
-public class ${DetailName}Activity extends ${superClass} {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_${detail_name});
-
- // Show the Up button in the action bar.
- get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
-
- // savedInstanceState is non-null when there is fragment state
- // saved from previous configurations of this activity
- // (e.g. when rotating the screen from portrait to landscape).
- // In this case, the fragment will automatically be re-added
- // to its container so we don't need to manually add it.
- // For more information, see the Fragments API guide at:
- //
- // http://developer.android.com/guide/components/fragments.html
- //
- if (savedInstanceState == null) {
- // Create the detail fragment and add it to the activity
- // using a fragment transaction.
- Bundle arguments = new Bundle();
- arguments.putString(${DetailName}Fragment.ARG_ITEM_ID,
- getIntent().getStringExtra(${DetailName}Fragment.ARG_ITEM_ID));
- ${DetailName}Fragment fragment = new ${DetailName}Fragment();
- fragment.setArguments(arguments);
- get${Support}FragmentManager().beginTransaction()
- .add(R.id.${detail_name}_container, fragment)
- .commit();
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- if (id == android.R.id.home) {
-<#if minApiLevel lt 16>
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
-<#else>
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- navigateUpTo(new Intent(this, ${CollectionName}Activity.class));
-</#if>
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-}
diff --git a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl b/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
deleted file mode 100644
index fd4dbf9..0000000
--- a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
+++ /dev/null
@@ -1,63 +0,0 @@
-package ${packageName};
-
-import android.os.Bundle;
-import android.<#if appCompat>support.v4.</#if>app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-import ${packageName}.dummy.DummyContent;
-
-/**
- * A fragment representing a single ${objectKind} detail screen.
- * This fragment is either contained in a {@link ${CollectionName}Activity}
- * in two-pane mode (on tablets) or a {@link ${DetailName}Activity}
- * on handsets.
- */
-public class ${DetailName}Fragment extends Fragment {
- /**
- * The fragment argument representing the item ID that this fragment
- * represents.
- */
- public static final String ARG_ITEM_ID = "item_id";
-
- /**
- * The dummy content this fragment is presenting.
- */
- private DummyContent.DummyItem mItem;
-
- /**
- * Mandatory empty constructor for the fragment manager to instantiate the
- * fragment (e.g. upon screen orientation changes).
- */
- public ${DetailName}Fragment() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (getArguments().containsKey(ARG_ITEM_ID)) {
- // Load the dummy content specified by the fragment
- // arguments. In a real-world scenario, use a Loader
- // to load content from a content provider.
- mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment_${detail_name}, container, false);
-
- // Show the dummy content as text in a TextView.
- if (mItem != null) {
- ((TextView) rootView.findViewById(R.id.${detail_name})).setText(mItem.content);
- }
-
- return rootView;
- }
-}
diff --git a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl b/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
deleted file mode 100644
index 477d718..0000000
--- a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
+++ /dev/null
@@ -1,108 +0,0 @@
-package ${packageName};
-
-import android.content.Intent;
-import android.os.Bundle;
-import <#if appCompat>android.support.v4.app.FragmentActivity<#else>android.app.Activity</#if>;
-<#if (parentActivityClass != "" && minApiLevel lt 16)>import android.support.v4.app.NavUtils;</#if>
-<#if parentActivityClass != "">import android.view.MenuItem;</#if>
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * An activity representing a list of ${objectKindPlural}. This activity
- * has different presentations for handset and tablet-size devices. On
- * handsets, the activity presents a list of items, which when touched,
- * lead to a {@link ${DetailName}Activity} representing
- * item details. On tablets, the activity presents the list of items and
- * item details side-by-side using two vertical panes.
- * <p>
- * The activity makes heavy use of fragments. The list of items is a
- * {@link ${CollectionName}Fragment} and the item details
- * (if present) is a {@link ${DetailName}Fragment}.
- * <p>
- * This activity also implements the required
- * {@link ${CollectionName}Fragment.Callbacks} interface
- * to listen for item selections.
- */
-public class ${CollectionName}Activity extends ${(appCompat)?string('Fragment','')}Activity
- implements ${CollectionName}Fragment.Callbacks {
-
- /**
- * Whether or not the activity is in two-pane mode, i.e. running on a tablet
- * device.
- */
- private boolean mTwoPane;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_${collection_name});
- <#if parentActivityClass != "">
- // Show the Up button in the action bar.
- get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
- </#if>
-
- if (findViewById(R.id.${detail_name}_container) != null) {
- // The detail container view will be present only in the
- // large-screen layouts (res/values-large and
- // res/values-sw600dp). If this view is present, then the
- // activity should be in two-pane mode.
- mTwoPane = true;
-
- // In two-pane mode, list items should be given the
- // 'activated' state when touched.
- ((${CollectionName}Fragment) get${Support}FragmentManager()
- .findFragmentById(R.id.${collection_name}))
- .setActivateOnItemClick(true);
- }
-
- // TODO: If exposing deep links into your app, handle intents here.
- }
- <#if parentActivityClass != "">
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- if (id == android.R.id.home) {
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- ${(minApiLevel lt 16)?string('NavUtils.','')}navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
- </#if>
-
- /**
- * Callback method from {@link ${CollectionName}Fragment.Callbacks}
- * indicating that the item with the given ID was selected.
- */
- @Override
- public void onItemSelected(String id) {
- if (mTwoPane) {
- // In two-pane mode, show the detail view in this activity by
- // adding or replacing the detail fragment using a
- // fragment transaction.
- Bundle arguments = new Bundle();
- arguments.putString(${DetailName}Fragment.ARG_ITEM_ID, id);
- ${DetailName}Fragment fragment = new ${DetailName}Fragment();
- fragment.setArguments(arguments);
- get${Support}FragmentManager().beginTransaction()
- .replace(R.id.${detail_name}_container, fragment)
- .commit();
-
- } else {
- // In single-pane mode, simply start the detail activity
- // for the selected item ID.
- Intent detailIntent = new Intent(this, ${DetailName}Activity.class);
- detailIntent.putExtra(${DetailName}Fragment.ARG_ITEM_ID, id);
- startActivity(detailIntent);
- }
- }
-}
diff --git a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl b/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
deleted file mode 100644
index 93fd1ac..0000000
--- a/base/templates/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
+++ /dev/null
@@ -1,153 +0,0 @@
-package ${packageName};
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.<#if Support?has_content>support.v4.</#if>app.ListFragment;
-import android.view.View;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-import ${packageName}.dummy.DummyContent;
-
-/**
- * A list fragment representing a list of ${objectKindPlural}. This fragment
- * also supports tablet devices by allowing list items to be given an
- * 'activated' state upon selection. This helps indicate which item is
- * currently being viewed in a {@link ${DetailName}Fragment}.
- * <p>
- * Activities containing this fragment MUST implement the {@link Callbacks}
- * interface.
- */
-public class ${CollectionName}Fragment extends ListFragment {
-
- /**
- * The serialization (saved instance state) Bundle key representing the
- * activated item position. Only used on tablets.
- */
- private static final String STATE_ACTIVATED_POSITION = "activated_position";
-
- /**
- * The fragment's current callback object, which is notified of list item
- * clicks.
- */
- private Callbacks mCallbacks = sDummyCallbacks;
-
- /**
- * The current activated item position. Only used on tablets.
- */
- private int mActivatedPosition = ListView.INVALID_POSITION;
-
- /**
- * A callback interface that all activities containing this fragment must
- * implement. This mechanism allows activities to be notified of item
- * selections.
- */
- public interface Callbacks {
- /**
- * Callback for when an item has been selected.
- */
- public void onItemSelected(String id);
- }
-
- /**
- * A dummy implementation of the {@link Callbacks} interface that does
- * nothing. Used only when this fragment is not attached to an activity.
- */
- private static Callbacks sDummyCallbacks = new Callbacks() {
- @Override
- public void onItemSelected(String id) {
- }
- };
-
- /**
- * Mandatory empty constructor for the fragment manager to instantiate the
- * fragment (e.g. upon screen orientation changes).
- */
- public ${CollectionName}Fragment() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // TODO: replace with a real list adapter.
- setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
- getActivity(),
- android.R.layout.simple_list_item_activated_1,
- android.R.id.text1,
- DummyContent.ITEMS));
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- // Restore the previously serialized activated item position.
- if (savedInstanceState != null
- && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
- setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- // Activities containing this fragment must implement its callbacks.
- if (!(activity instanceof Callbacks)) {
- throw new IllegalStateException("Activity must implement fragment's callbacks.");
- }
-
- mCallbacks = (Callbacks) activity;
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
-
- // Reset the active callbacks interface to the dummy implementation.
- mCallbacks = sDummyCallbacks;
- }
-
- @Override
- public void onListItemClick(ListView listView, View view, int position, long id) {
- super.onListItemClick(listView, view, position, id);
-
- // Notify the active callbacks interface (the activity, if the
- // fragment is attached to one) that an item has been selected.
- mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mActivatedPosition != ListView.INVALID_POSITION) {
- // Serialize and persist the activated item position.
- outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
- }
- }
-
- /**
- * Turns on activate-on-click mode. When this mode is on, list items will be
- * given the 'activated' state when touched.
- */
- public void setActivateOnItemClick(boolean activateOnItemClick) {
- // When setting CHOICE_MODE_SINGLE, ListView will automatically
- // give items the 'activated' state when touched.
- getListView().setChoiceMode(activateOnItemClick
- ? ListView.CHOICE_MODE_SINGLE
- : ListView.CHOICE_MODE_NONE);
- }
-
- private void setActivatedPosition(int position) {
- if (position == ListView.INVALID_POSITION) {
- getListView().setItemChecked(mActivatedPosition, false);
- } else {
- getListView().setItemChecked(position, true);
- }
-
- mActivatedPosition = position;
- }
-}
diff --git a/base/templates/activities/NavigationDrawerActivity/globals.xml.ftl b/base/templates/activities/NavigationDrawerActivity/globals.xml.ftl
deleted file mode 100644
index f9e6aa8..0000000
--- a/base/templates/activities/NavigationDrawerActivity/globals.xml.ftl
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
-<#if hasDependency('com.android.support:appcompat-v7')>
- <global id="appCompat" type="boolean" value="true" />
- <global id="superClass" type="string" value="<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
- <global id="superClassFqcn" type="string" value="android.support.v7.app.<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
-<#else>
- <global id="appCompat" type="boolean" value="false" />
- <global id="superClass" type="string" value="Activity"/>
- <global id="superClassFqcn" type="string" value="android.app.Activity"/>
-</#if>
- <!-- e.g. getSupportActionBar vs. getActionBar -->
- <global id="Support" value="${(hasDependency('com.android.support:appcompat-v7'))?string('Support','')}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="menuName" value="${classToResource(activityClass)}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/NavigationDrawerActivity/recipe.xml.ftl b/base/templates/activities/NavigationDrawerActivity/recipe.xml.ftl
deleted file mode 100644
index c3a162c..0000000
--- a/base/templates/activities/NavigationDrawerActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if !(hasDependency('com.android.support:support-v4'))><dependency mavenUrl="com.android.support:support-v4:${targetApi}.+"/></#if>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <!-- TODO: switch on Holo Dark v. Holo Light -->
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
- <copy from="res/drawable-xxhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
-
- <instantiate from="res/menu/global.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/global.xml" />
-
- <instantiate from="res/layout/activity_drawer.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- <instantiate from="res/layout/fragment_navigation_drawer.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${navigationDrawerLayout}.xml" />
-
- <instantiate from="res/layout/fragment_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-
- <instantiate from="src/app_package/DrawerActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <instantiate from="src/app_package/NavigationDrawerFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/NavigationDrawerFragment.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl b/base/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
deleted file mode 100644
index 0ee2754..0000000
--- a/base/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
+++ /dev/null
@@ -1,86 +0,0 @@
-package ${packageName};
-
-import android.app.Activity;
-<#if appCompat>import ${superClassFqcn};</#if>
-import android.<#if appCompat>support.v7.</#if>app.ActionBar;
-import android.<#if appCompat>support.v4.</#if>app.Fragment;
-import android.<#if appCompat>support.v4.</#if>app.FragmentManager;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.support.v4.widget.DrawerLayout;
-import android.widget.ArrayAdapter;
-import android.widget.TextView;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-public class ${activityClass} extends ${superClass}
- implements NavigationDrawerFragment.NavigationDrawerCallbacks {
-
- /**
- * Fragment managing the behaviors, interactions and presentation of the navigation drawer.
- */
- private NavigationDrawerFragment mNavigationDrawerFragment;
-
- /**
- * Used to store the last screen title. For use in {@link #restoreActionBar()}.
- */
- private CharSequence mTitle;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
-
- mNavigationDrawerFragment = (NavigationDrawerFragment)
- get${Support}FragmentManager().findFragmentById(R.id.navigation_drawer);
- mTitle = getTitle();
-
- // Set up the drawer.
- mNavigationDrawerFragment.setUp(
- R.id.navigation_drawer,
- (DrawerLayout) findViewById(R.id.drawer_layout));
- }
-
- @Override
- public void onNavigationDrawerItemSelected(int position) {
- // update the main content by replacing fragments
- FragmentManager fragmentManager = get${Support}FragmentManager();
- fragmentManager.beginTransaction()
- .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
- .commit();
- }
-
- public void onSectionAttached(int number) {
- switch (number) {
- case 1:
- mTitle = getString(R.string.title_section1);
- break;
- case 2:
- mTitle = getString(R.string.title_section2);
- break;
- case 3:
- mTitle = getString(R.string.title_section3);
- break;
- }
- }
-
- public void restoreActionBar() {
- ActionBar actionBar = get${Support}ActionBar();
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setTitle(mTitle);
- }
-
- <#include "include_options_menu.java.ftl">
-
- <#include "include_fragment.java.ftl">
-
-}
diff --git a/base/templates/activities/SettingsActivity/globals.xml.ftl b/base/templates/activities/SettingsActivity/globals.xml.ftl
deleted file mode 100644
index f60259b..0000000
--- a/base/templates/activities/SettingsActivity/globals.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="simpleName" value="${activityToLayout(activityClass)}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/SettingsActivity/recipe.xml.ftl b/base/templates/activities/SettingsActivity/recipe.xml.ftl
deleted file mode 100644
index 83413e0..0000000
--- a/base/templates/activities/SettingsActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:support-v4:${targetApi}.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <copy from="res/xml/pref_data_sync.xml"
- to="${escapeXmlAttribute(resOut)}/xml/pref_data_sync.xml" />
- <copy from="res/xml/pref_general.xml"
- to="${escapeXmlAttribute(resOut)}/xml/pref_general.xml" />
- <merge from="res/xml/pref_headers.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/xml/pref_headers.xml" />
- <copy from="res/xml/pref_notification.xml"
- to="${escapeXmlAttribute(resOut)}/xml/pref_notification.xml" />
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
-
- <instantiate from="src/app_package/SettingsActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-</recipe>
diff --git a/base/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl b/base/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
deleted file mode 100644
index 8e8ced6..0000000
--- a/base/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
+++ /dev/null
@@ -1,310 +0,0 @@
-package ${packageName};
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.preference.RingtonePreference;
-import android.text.TextUtils;
-<#if parentActivityClass != "">
-import android.view.MenuItem;
-import android.support.v4.app.NavUtils;
-</#if>
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-import java.util.List;
-
-<#assign includeSimple=(minApiLevel?? && minApiLevel lt 10)>
-
-/**
- * A {@link PreferenceActivity} that presents a set of application settings. On
- * handset devices, settings are presented as a single list. On tablets,
- * settings are split by category, with category headers shown to the left of
- * the list of settings.
- * <p>
- * See <a href="http://developer.android.com/design/patterns/settings.html">
- * Android Design: Settings</a> for design guidelines and the <a
- * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
- * API Guide</a> for more information on developing a Settings UI.
- */
-public class ${activityClass} extends PreferenceActivity {
- <#if includeSimple>
- /**
- * Determines whether to always show the simplified settings UI, where
- * settings are presented in a single list. When false, settings are shown
- * as a master/detail two-pane view on tablets. When true, a single pane is
- * shown on tablets.
- */
- private static final boolean ALWAYS_SIMPLE_PREFS = false;
- </#if>
-
- <#if parentActivityClass != "">
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setupActionBar();
- }
-
- /**
- * Set up the {@link android.app.ActionBar}, if the API is available.
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private void setupActionBar() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- // Show the Up button in the action bar.
- getActionBar().setDisplayHomeAsUpEnabled(true);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- if (id == android.R.id.home) {
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- // TODO: If Settings has multiple levels, Up should navigate up
- // that hierarchy.
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
- </#if>
-
-
- <#if includeSimple>
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
-
- setupSimplePreferencesScreen();
- }
-
- /**
- * Shows the simplified settings UI if the device configuration if the
- * device configuration dictates that a simplified, single-pane UI should be
- * shown.
- */
- private void setupSimplePreferencesScreen() {
- if (!isSimplePreferences(this)) {
- return;
- }
-
- // In the simplified UI, fragments are not used at all and we instead
- // use the older PreferenceActivity APIs.
-
- // Add 'general' preferences.
- addPreferencesFromResource(R.xml.pref_general);
-
- // Add 'notifications' preferences, and a corresponding header.
- PreferenceCategory fakeHeader = new PreferenceCategory(this);
- fakeHeader.setTitle(R.string.pref_header_notifications);
- getPreferenceScreen().addPreference(fakeHeader);
- addPreferencesFromResource(R.xml.pref_notification);
-
- // Add 'data and sync' preferences, and a corresponding header.
- fakeHeader = new PreferenceCategory(this);
- fakeHeader.setTitle(R.string.pref_header_data_sync);
- getPreferenceScreen().addPreference(fakeHeader);
- addPreferencesFromResource(R.xml.pref_data_sync);
-
- // Bind the summaries of EditText/List/Dialog/Ringtone preferences to
- // their values. When their values change, their summaries are updated
- // to reflect the new value, per the Android Design guidelines.
- bindPreferenceSummaryToValue(findPreference("example_text"));
- bindPreferenceSummaryToValue(findPreference("example_list"));
- bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
- bindPreferenceSummaryToValue(findPreference("sync_frequency"));
- }
- </#if>
-
- /** {@inheritDoc} */
- @Override
- public boolean onIsMultiPane() {
- return isXLargeTablet(this)<#if includeSimple> && !isSimplePreferences(this)</#if>;
- }
-
- /**
- * Helper method to determine if the device has an extra-large screen. For
- * example, 10" tablets are extra-large.
- */
- private static boolean isXLargeTablet(Context context) {
- return (context.getResources().getConfiguration().screenLayout
- & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
- }
-
- <#if includeSimple>
- /**
- * Determines whether the simplified settings UI should be shown. This is
- * true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
- * doesn't have newer APIs like {@link PreferenceFragment}, or the device
- * doesn't have an extra-large screen. In these cases, a single-pane
- * "simplified" settings UI should be shown.
- */
- private static boolean isSimplePreferences(Context context) {
- return ALWAYS_SIMPLE_PREFS
- || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
- || !isXLargeTablet(context);
- }
- </#if>
-
- /** {@inheritDoc} */
- @Override
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- public void onBuildHeaders(List<Header> target) {
- <#if includeSimple>
- if (!isSimplePreferences(this)) {
- loadHeadersFromResource(R.xml.pref_headers, target);
- }
- <#else>
- loadHeadersFromResource(R.xml.pref_headers, target);
- </#if>
- }
-
- /**
- * A preference value change listener that updates the preference's summary
- * to reflect its new value.
- */
- private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(Preference preference, Object value) {
- String stringValue = value.toString();
-
- if (preference instanceof ListPreference) {
- // For list preferences, look up the correct display value in
- // the preference's 'entries' list.
- ListPreference listPreference = (ListPreference) preference;
- int index = listPreference.findIndexOfValue(stringValue);
-
- // Set the summary to reflect the new value.
- preference.setSummary(
- index >= 0
- ? listPreference.getEntries()[index]
- : null);
-
- } else if (preference instanceof RingtonePreference) {
- // For ringtone preferences, look up the correct display value
- // using RingtoneManager.
- if (TextUtils.isEmpty(stringValue)) {
- // Empty values correspond to 'silent' (no ringtone).
- preference.setSummary(R.string.pref_ringtone_silent);
-
- } else {
- Ringtone ringtone = RingtoneManager.getRingtone(
- preference.getContext(), Uri.parse(stringValue));
-
- if (ringtone == null) {
- // Clear the summary if there was a lookup error.
- preference.setSummary(null);
- } else {
- // Set the summary to reflect the new ringtone display
- // name.
- String name = ringtone.getTitle(preference.getContext());
- preference.setSummary(name);
- }
- }
-
- } else {
- // For all other preferences, set the summary to the value's
- // simple string representation.
- preference.setSummary(stringValue);
- }
- return true;
- }
- };
-
- /**
- * Binds a preference's summary to its value. More specifically, when the
- * preference's value is changed, its summary (line of text below the
- * preference title) is updated to reflect the value. The summary is also
- * immediately updated upon calling this method. The exact display format is
- * dependent on the type of preference.
- *
- * @see #sBindPreferenceSummaryToValueListener
- */
- private static void bindPreferenceSummaryToValue(Preference preference) {
- // Set the listener to watch for value changes.
- preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
-
- // Trigger the listener immediately with the preference's
- // current value.
- sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
- PreferenceManager
- .getDefaultSharedPreferences(preference.getContext())
- .getString(preference.getKey(), ""));
- }
-
- /**
- * This fragment shows general preferences only. It is used when the
- * activity is showing a two-pane settings UI.
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- public static class GeneralPreferenceFragment extends PreferenceFragment {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.pref_general);
-
- // Bind the summaries of EditText/List/Dialog/Ringtone preferences
- // to their values. When their values change, their summaries are
- // updated to reflect the new value, per the Android Design
- // guidelines.
- bindPreferenceSummaryToValue(findPreference("example_text"));
- bindPreferenceSummaryToValue(findPreference("example_list"));
- }
- }
-
- /**
- * This fragment shows notification preferences only. It is used when the
- * activity is showing a two-pane settings UI.
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- public static class NotificationPreferenceFragment extends PreferenceFragment {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.pref_notification);
-
- // Bind the summaries of EditText/List/Dialog/Ringtone preferences
- // to their values. When their values change, their summaries are
- // updated to reflect the new value, per the Android Design
- // guidelines.
- bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
- }
- }
-
- /**
- * This fragment shows data and sync preferences only. It is used when the
- * activity is showing a two-pane settings UI.
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- public static class DataSyncPreferenceFragment extends PreferenceFragment {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.pref_data_sync);
-
- // Bind the summaries of EditText/List/Dialog/Ringtone preferences
- // to their values. When their values change, their summaries are
- // updated to reflect the new value, per the Android Design
- // guidelines.
- bindPreferenceSummaryToValue(findPreference("sync_frequency"));
- }
- }
-}
diff --git a/base/templates/activities/TabbedActivity/globals.xml.ftl b/base/templates/activities/TabbedActivity/globals.xml.ftl
deleted file mode 100644
index ca4effb..0000000
--- a/base/templates/activities/TabbedActivity/globals.xml.ftl
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
-<#if hasDependency('com.android.support:appcompat-v7')>
- <global id="appCompat" type="boolean" value="true" />
- <global id="superClass" type="string" value="<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
- <global id="superClassFqcn" type="string" value="android.support.v7.app.<#if buildApi gte 22>AppCompat<#else>ActionBar</#if>Activity"/>
-<#else>
- <global id="appCompat" type="boolean" value="false" />
- <global id="superClass" type="string" value="Activity"/>
- <global id="superClassFqcn" type="string" value="android.app.Activity"/>
-</#if>
- <!-- e.g. getSupportActionBar vs. getActionBar -->
- <global id="Support" value="${(hasDependency('com.android.support:appcompat-v7'))?string('Support','')}" />
- <global id="hasViewPager" type="boolean" value="${(features == 'pager' || features == 'tabs')?string}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/activities/TabbedActivity/recipe.xml.ftl b/base/templates/activities/TabbedActivity/recipe.xml.ftl
deleted file mode 100644
index eacbd75..0000000
--- a/base/templates/activities/TabbedActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if !(hasDependency('com.android.support:appcompat-v7'))><dependency mavenUrl="com.android.support:support-v13:${targetApi}.+"/></#if>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <!-- Decide what kind of layout(s) to add -->
- <#if hasViewPager>
- <instantiate from="res/layout/activity_pager.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <#else>
- <instantiate from="res/layout/activity_fragment_container.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- </#if>
-
- <instantiate from="res/layout/fragment_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-
- <!-- Decide which activity code to add -->
- <#if features == "tabs" || features == "pager">
- <instantiate from="src/app_package/TabsAndPagerActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <#elseif features == "spinner">
- <instantiate from="src/app_package/DropdownActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-</recipe>
diff --git a/base/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl b/base/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
deleted file mode 100644
index 050457c..0000000
--- a/base/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
+++ /dev/null
@@ -1,88 +0,0 @@
-package ${packageName};
-
-import ${superClassFqcn};
-import android.<#if appCompat>support.v7.</#if>app.ActionBar;
-import android.<#if appCompat>support.v4.</#if>app.Fragment;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.TextView;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-public class ${activityClass} extends ${superClass} implements ActionBar.OnNavigationListener {
-
- /**
- * The serialization (saved instance state) Bundle key representing the
- * current dropdown position.
- */
- private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
-
- // Set up the action bar to show a dropdown list.
- final ActionBar actionBar = get${Support}ActionBar();
- actionBar.setDisplayShowTitleEnabled(false);
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- <#if parentActivityClass != "">
- // Show the Up button in the action bar.
- actionBar.setDisplayHomeAsUpEnabled(true);
- </#if>
-
- // Set up the dropdown list navigation in the action bar.
- actionBar.setListNavigationCallbacks(
- // Specify a SpinnerAdapter to populate the dropdown list.
- new ArrayAdapter<String>(
- actionBar.getThemedContext(),
- android.R.layout.simple_list_item_1,
- android.R.id.text1,
- new String[] {
- getString(R.string.title_section1),
- getString(R.string.title_section2),
- getString(R.string.title_section3),
- }),
- this);
- }
-
- @Override
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- // Restore the previously serialized current dropdown position.
- if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
- get${Support}ActionBar().setSelectedNavigationItem(
- savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- // Serialize the current dropdown position.
- outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
- get${Support}ActionBar().getSelectedNavigationIndex());
- }
-
- <#include "include_options_menu.java.ftl">
-
- @Override
- public boolean onNavigationItemSelected(int position, long id) {
- // When the given dropdown item is selected, show its contents in the
- // container view.
- get${Support}FragmentManager().beginTransaction()
- .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
- .commit();
- return true;
- }
-
- <#include "include_fragment.java.ftl">
-
-}
diff --git a/base/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl b/base/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
deleted file mode 100644
index a135605..0000000
--- a/base/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
+++ /dev/null
@@ -1,141 +0,0 @@
-package ${packageName};
-
-import java.util.Locale;
-
-import ${superClassFqcn};
-import android.<#if appCompat>support.v7.</#if>app.ActionBar;
-import android.<#if appCompat>support.v4.</#if>app.Fragment;
-import android.<#if appCompat>support.v4.</#if>app.FragmentManager;
-import android.<#if appCompat>support.v4.</#if>app.FragmentTransaction;
-import android.support.${(appCompat)?string('v4','v13')}.app.FragmentPagerAdapter;
-import android.os.Bundle;
-import android.support.v4.view.ViewPager;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-public class ${activityClass} extends ${superClass}<#if features == 'tabs'> implements ActionBar.TabListener</#if> {
-
- /**
- * The {@link android.support.v4.view.PagerAdapter} that will provide
- * fragments for each of the sections. We use a
- * {@link FragmentPagerAdapter} derivative, which will keep every
- * loaded fragment in memory. If this becomes too memory intensive, it
- * may be best to switch to a
- * {@link android.support.${(appCompat)?string('v4','v13')}.app.FragmentStatePagerAdapter}.
- */
- SectionsPagerAdapter mSectionsPagerAdapter;
-
- /**
- * The {@link ViewPager} that will host the section contents.
- */
- ViewPager mViewPager;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
-
- <#if features == 'tabs'>
- // Set up the action bar.
- final ActionBar actionBar = get${Support}ActionBar();
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);</#if>
-
- // Create the adapter that will return a fragment for each of the three
- // primary sections of the activity.
- mSectionsPagerAdapter = new SectionsPagerAdapter(get${Support}FragmentManager());
-
- // Set up the ViewPager with the sections adapter.
- mViewPager = (ViewPager) findViewById(R.id.pager);
- mViewPager.setAdapter(mSectionsPagerAdapter);
-
- <#if features == 'tabs'>
- // When swiping between different sections, select the corresponding
- // tab. We can also use ActionBar.Tab#select() to do this if we have
- // a reference to the Tab.
- mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
- @Override
- public void onPageSelected(int position) {
- actionBar.setSelectedNavigationItem(position);
- }
- });
-
- // For each of the sections in the app, add a tab to the action bar.
- for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
- // Create a tab with text corresponding to the page title defined by
- // the adapter. Also specify this Activity object, which implements
- // the TabListener interface, as the callback (listener) for when
- // this tab is selected.
- actionBar.addTab(
- actionBar.newTab()
- .setText(mSectionsPagerAdapter.getPageTitle(i))
- .setTabListener(this));
- }
- </#if>
- }
-
- <#include "include_options_menu.java.ftl">
-
- <#if features == 'tabs'>@Override
- public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- // When the given tab is selected, switch to the corresponding page in
- // the ViewPager.
- mViewPager.setCurrentItem(tab.getPosition());
- }
-
- @Override
- public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- }
-
- @Override
- public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- }</#if>
-
- /**
- * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
- * one of the sections/tabs/pages.
- */
- public class SectionsPagerAdapter extends FragmentPagerAdapter {
-
- public SectionsPagerAdapter(FragmentManager fm) {
- super(fm);
- }
-
- @Override
- public Fragment getItem(int position) {
- // getItem is called to instantiate the fragment for the given page.
- // Return a PlaceholderFragment (defined as a static inner class below).
- return PlaceholderFragment.newInstance(position + 1);
- }
-
- @Override
- public int getCount() {
- // Show 3 total pages.
- return 3;
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- Locale l = Locale.getDefault();
- switch (position) {
- case 0:
- return getString(R.string.title_section1).toUpperCase(l);
- case 1:
- return getString(R.string.title_section2).toUpperCase(l);
- case 2:
- return getString(R.string.title_section3).toUpperCase(l);
- }
- return null;
- }
- }
-
- <#include "include_fragment.java.ftl">
-
-}
diff --git a/base/templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl b/base/templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
deleted file mode 100644
index fe68673..0000000
--- a/base/templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
+++ /dev/null
@@ -1,22 +0,0 @@
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.${menuName}, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_settings) {
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
diff --git a/base/templates/activities/TabbedActivity/template.xml b/base/templates/activities/TabbedActivity/template.xml
deleted file mode 100644
index e457a19..0000000
--- a/base/templates/activities/TabbedActivity/template.xml
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="4"
- name="Tabbed Activity"
- minApi="7"
- minBuildApi="14"
- description="Creates a new blank activity, with an action bar and navigational elements such as tabs or horizontal swipe.">
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="fragmentLayoutName"
- name="Fragment Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="fragment_${classToResource(activityClass)}"
- default="fragment_main"
- help="The name of the layout to create for the activity's content fragment" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="MainActivity"
- suggest="${activityClass}"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="menuName"
- name="Menu Resource Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="menu_${classToResource(activityClass)}"
- default="menu_main"
- help="The name of the resource file to create for the menu items" />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="features"
- name="Navigation Style"
- type="enum"
- default="pager"
- help="Additional features to include, such as a fragment, swipe views, or a navigation drawer" >
- <option id="pager">Swipe Views (ViewPager)</option>
- <option id="tabs">Action Bar Tabs (with ViewPager)</option>
- <option id="spinner">Action Bar Spinner</option>
- </parameter>
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_blank_activity_pager.png</thumb>
- <!-- attributes act as selectors based on chosen parameters -->
- <thumb features="tabs">template_blank_activity_tabs.png</thumb>
- <thumb features="pager">template_blank_activity_pager.png</thumb>
- <thumb features="spinner">template_blank_activity_dropdown.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/activities/BlankActivity/recipe.xml.ftl b/base/templates/eclipse/activities/BlankActivity/recipe.xml.ftl
deleted file mode 100644
index cc4e5b5..0000000
--- a/base/templates/eclipse/activities/BlankActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <instantiate from="res/layout/activity_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="src/app_package/SimpleActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/activities/BlankActivity/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/BlankActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index af1d2d6..0000000
--- a/base/templates/eclipse/activities/BlankActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl b/base/templates/eclipse/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl
deleted file mode 100644
index e522310..0000000
--- a/base/templates/eclipse/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl
+++ /dev/null
@@ -1,16 +0,0 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
- tools:context="${relativePackage}.${activityClass}">
-
- <TextView
- android:text="@string/hello_world"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-</RelativeLayout>
diff --git a/base/templates/eclipse/activities/BlankActivity/root/res/values/strings.xml.ftl b/base/templates/eclipse/activities/BlankActivity/root/res/values/strings.xml.ftl
deleted file mode 100644
index 721bd51..0000000
--- a/base/templates/eclipse/activities/BlankActivity/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,9 +0,0 @@
-<resources>
- <#if !isNewProject>
- <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
- </#if>
-
- <string name="hello_world">Hello world!</string>
- <string name="action_settings">Settings</string>
-
-</resources>
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/recipe.xml.ftl b/base/templates/eclipse/activities/BlankActivityWithFragment/recipe.xml.ftl
deleted file mode 100644
index 3a2bd11..0000000
--- a/base/templates/eclipse/activities/BlankActivityWithFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <instantiate from="res/layout/activity_fragment_container.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="res/layout/fragment_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-
- <instantiate from="src/app_package/SimpleActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl
deleted file mode 100644
index af1d2d6..0000000
--- a/base/templates/eclipse/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl b/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl
deleted file mode 100644
index 47c8224..0000000
--- a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl
+++ /dev/null
@@ -1,5 +0,0 @@
-<resources>
- <!-- Default screen margins, per the Android Design guidelines. -->
- <dimen name="activity_horizontal_margin">16dp</dimen>
- <dimen name="activity_vertical_margin">16dp</dimen>
-</resources>
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl b/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl
deleted file mode 100644
index 991c89c..0000000
--- a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<resources>
- <#if !isNewProject>
- <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
- </#if>
- <string name="hello_world">Hello world!</string>
- <string name="action_settings">Settings</string>
-</resources>
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/template_blank_activity_fragment.png b/base/templates/eclipse/activities/BlankActivityWithFragment/template_blank_activity_fragment.png
deleted file mode 100644
index 53b310d..0000000
Binary files a/base/templates/eclipse/activities/BlankActivityWithFragment/template_blank_activity_fragment.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/EmptyActivity/globals.xml.ftl b/base/templates/eclipse/activities/EmptyActivity/globals.xml.ftl
deleted file mode 100644
index 4bf836f..0000000
--- a/base/templates/eclipse/activities/EmptyActivity/globals.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/eclipse/activities/EmptyActivity/recipe.xml.ftl b/base/templates/eclipse/activities/EmptyActivity/recipe.xml.ftl
deleted file mode 100644
index 41088d9..0000000
--- a/base/templates/eclipse/activities/EmptyActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <copy from="res/layout/activity_simple.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="src/app_package/SimpleActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/activities/EmptyActivity/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/EmptyActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 8054909..0000000
--- a/base/templates/eclipse/activities/EmptyActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- >
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/EmptyActivity/root/res/layout/activity_simple.xml b/base/templates/eclipse/activities/EmptyActivity/root/res/layout/activity_simple.xml
deleted file mode 100644
index 39d12f0..0000000
--- a/base/templates/eclipse/activities/EmptyActivity/root/res/layout/activity_simple.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${activityClass}">
-
- <TextView
- android:text="@string/hello_world"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-</RelativeLayout>
diff --git a/base/templates/eclipse/activities/EmptyActivity/root/res/values/strings.xml.ftl b/base/templates/eclipse/activities/EmptyActivity/root/res/values/strings.xml.ftl
deleted file mode 100644
index a76fbc6..0000000
--- a/base/templates/eclipse/activities/EmptyActivity/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,6 +0,0 @@
-<resources>
- <#if !isNewProject>
- <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
- </#if>
- <string name="hello_world">Hello world!</string>
-</resources>
diff --git a/base/templates/eclipse/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl b/base/templates/eclipse/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
deleted file mode 100644
index 2df6530..0000000
--- a/base/templates/eclipse/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
+++ /dev/null
@@ -1,15 +0,0 @@
-package ${packageName};
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-
-public class ${activityClass} extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
- }
-}
diff --git a/base/templates/eclipse/activities/EmptyActivity/template.xml b/base/templates/eclipse/activities/EmptyActivity/template.xml
deleted file mode 100644
index 73d7bc9..0000000
--- a/base/templates/eclipse/activities/EmptyActivity/template.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="4"
- name="Empty Activity"
- minApi="7"
- minBuildApi="14"
- description="Creates a new empty activity">
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="MainActivity"
- suggest="${activityClass}"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_blank_activity.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/activities/EmptyActivity/template_blank_activity.png b/base/templates/eclipse/activities/EmptyActivity/template_blank_activity.png
deleted file mode 100644
index d6ace2c..0000000
Binary files a/base/templates/eclipse/activities/EmptyActivity/template_blank_activity.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/FullscreenActivity/recipe.xml.ftl b/base/templates/eclipse/activities/FullscreenActivity/recipe.xml.ftl
deleted file mode 100644
index bdc8a19..0000000
--- a/base/templates/eclipse/activities/FullscreenActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:support-v4:19.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/attrs.xml"
- to="${escapeXmlAttribute(resOut)}/values/attrs.xml" />
- <merge from="res/values/colors.xml"
- to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
- <merge from="res/values/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
- <merge from="res/values-v11/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
- <instantiate from="res/layout/activity_fullscreen.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="src/app_package/FullscreenActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <instantiate from="src/app_package/util/SystemUiHider.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/util/SystemUiHider.java" />
- <instantiate from="src/app_package/util/SystemUiHiderBase.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderBase.java" />
- <instantiate from="src/app_package/util/SystemUiHiderHoneycomb.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderHoneycomb.java" />
-
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 4c5b799..0000000
--- a/base/templates/eclipse/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,26 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${simpleName}"
- </#if>
- android:configChanges="orientation|keyboardHidden|screenSize"
- android:theme="@style/FullscreenTheme"
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/res/values-v11/styles.xml b/base/templates/eclipse/activities/FullscreenActivity/root/res/values-v11/styles.xml
deleted file mode 100644
index f72515d..0000000
--- a/base/templates/eclipse/activities/FullscreenActivity/root/res/values-v11/styles.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<resources>
-
- <style name="FullscreenTheme" parent="android:Theme.Holo">
- <item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
- <item name="android:windowActionBarOverlay">true</item>
- <item name="android:windowBackground">@null</item>
- <item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
- <item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
- </style>
-
- <style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
- <item name="android:background">@color/black_overlay</item>
- </style>
-
-</resources>
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/res/values/styles.xml b/base/templates/eclipse/activities/FullscreenActivity/root/res/values/styles.xml
deleted file mode 100644
index e95ba03..0000000
--- a/base/templates/eclipse/activities/FullscreenActivity/root/res/values/styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<resources>
-
- <style name="FullscreenTheme" parent="android:Theme.NoTitleBar">
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:windowBackground">@null</item>
- <item name="metaButtonBarStyle">@style/ButtonBar</item>
- <item name="metaButtonBarButtonStyle">@style/ButtonBarButton</item>
- </style>
-
- <!-- Backward-compatible version of ?android:attr/buttonBarStyle -->
- <style name="ButtonBar">
- <item name="android:paddingLeft">2dp</item>
- <item name="android:paddingTop">5dp</item>
- <item name="android:paddingRight">2dp</item>
- <item name="android:paddingBottom">0dp</item>
- <item name="android:background">@android:drawable/bottom_bar</item>
- </style>
-
- <!-- Backward-compatible version of ?android:attr/buttonBarButtonStyle -->
- <style name="ButtonBarButton" />
-
-</resources>
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderBase.java.ftl b/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderBase.java.ftl
deleted file mode 100644
index da08842..0000000
--- a/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderBase.java.ftl
+++ /dev/null
@@ -1,63 +0,0 @@
-package ${packageName}.util;
-
-import android.app.Activity;
-import android.view.View;
-import android.view.WindowManager;
-
-/**
- * A base implementation of {@link SystemUiHider}. Uses APIs available in all
- * API levels to show and hide the status bar.
- */
-public class SystemUiHiderBase extends SystemUiHider {
- /**
- * Whether or not the system UI is currently visible. This is a cached value
- * from calls to {@link #hide()} and {@link #show()}.
- */
- private boolean mVisible = true;
-
- /**
- * Constructor not intended to be called by clients. Use
- * {@link SystemUiHider#getInstance} to obtain an instance.
- */
- protected SystemUiHiderBase(Activity activity, View anchorView, int flags) {
- super(activity, anchorView, flags);
- }
-
- @Override
- public void setup() {
- if ((mFlags & FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) == 0) {
- mActivity.getWindow().setFlags(
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
- }
- }
-
- @Override
- public boolean isVisible() {
- return mVisible;
- }
-
- @Override
- public void hide() {
- if ((mFlags & FLAG_FULLSCREEN) != 0) {
- mActivity.getWindow().setFlags(
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- }
- mOnVisibilityChangeListener.onVisibilityChange(false);
- mVisible = false;
- }
-
- @Override
- public void show() {
- if ((mFlags & FLAG_FULLSCREEN) != 0) {
- mActivity.getWindow().setFlags(
- 0,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- }
- mOnVisibilityChangeListener.onVisibilityChange(true);
- mVisible = true;
- }
-}
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl b/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl
deleted file mode 100644
index 2e2d8a9..0000000
--- a/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl
+++ /dev/null
@@ -1,133 +0,0 @@
-package ${packageName}.util;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.os.Build;
-import android.view.View;
-import android.view.WindowManager;
-
-/**
- * An API 11+ implementation of {@link SystemUiHider}. Uses APIs available in
- * Honeycomb and later (specifically {@link View#setSystemUiVisibility(int)}) to
- * show and hide the system UI.
- */
- at TargetApi(Build.VERSION_CODES.HONEYCOMB)
-public class SystemUiHiderHoneycomb extends SystemUiHiderBase {
- /**
- * Flags for {@link View#setSystemUiVisibility(int)} to use when showing the
- * system UI.
- */
- private int mShowFlags;
-
- /**
- * Flags for {@link View#setSystemUiVisibility(int)} to use when hiding the
- * system UI.
- */
- private int mHideFlags;
-
- /**
- * Flags to test against the first parameter in
- * {@link android.view.View.OnSystemUiVisibilityChangeListener#onSystemUiVisibilityChange(int)}
- * to determine the system UI visibility state.
- */
- private int mTestFlags;
-
- /**
- * Whether or not the system UI is currently visible. This is cached from
- * {@link android.view.View.OnSystemUiVisibilityChangeListener}.
- */
- private boolean mVisible = true;
-
- /**
- * Constructor not intended to be called by clients. Use
- * {@link SystemUiHider#getInstance} to obtain an instance.
- */
- protected SystemUiHiderHoneycomb(Activity activity, View anchorView, int flags) {
- super(activity, anchorView, flags);
-
- mShowFlags = View.SYSTEM_UI_FLAG_VISIBLE;
- mHideFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
- mTestFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
-
- if ((mFlags & FLAG_FULLSCREEN) != 0) {
- // If the client requested fullscreen, add flags relevant to hiding
- // the status bar. Note that some of these constants are new as of
- // API 16 (Jelly Bean). It is safe to use them, as they are inlined
- // at compile-time and do nothing on pre-Jelly Bean devices.
- mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_FULLSCREEN;
- }
-
- if ((mFlags & FLAG_HIDE_NAVIGATION) != 0) {
- // If the client requested hiding navigation, add relevant flags.
- mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- mTestFlags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public void setup() {
- mAnchorView.setOnSystemUiVisibilityChangeListener(mSystemUiVisibilityChangeListener);
- }
-
- /** {@inheritDoc} */
- @Override
- public void hide() {
- mAnchorView.setSystemUiVisibility(mHideFlags);
- }
-
- /** {@inheritDoc} */
- @Override
- public void show() {
- mAnchorView.setSystemUiVisibility(mShowFlags);
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isVisible() {
- return mVisible;
- }
-
- private View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener
- = new View.OnSystemUiVisibilityChangeListener() {
- @Override
- public void onSystemUiVisibilityChange(int vis) {
- // Test against mTestFlags to see if the system UI is visible.
- if ((vis & mTestFlags) != 0) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
- // Pre-Jelly Bean, we must manually hide the action bar
- // and use the old window flags API.
- mActivity.getActionBar().hide();
- mActivity.getWindow().setFlags(
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- }
-
- // Trigger the registered listener and cache the visibility
- // state.
- mOnVisibilityChangeListener.onVisibilityChange(false);
- mVisible = false;
-
- } else {
- mAnchorView.setSystemUiVisibility(mShowFlags);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
- // Pre-Jelly Bean, we must manually show the action bar
- // and use the old window flags API.
- mActivity.getActionBar().show();
- mActivity.getWindow().setFlags(
- 0,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- }
-
- // Trigger the registered listener and cache the visibility
- // state.
- mOnVisibilityChangeListener.onVisibilityChange(true);
- mVisible = true;
- }
- }
- };
-}
diff --git a/base/templates/eclipse/activities/FullscreenActivity/template.xml b/base/templates/eclipse/activities/FullscreenActivity/template.xml
deleted file mode 100644
index 996fc6d..0000000
--- a/base/templates/eclipse/activities/FullscreenActivity/template.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="4"
- name="Fullscreen Activity"
- description="Creates a new activity that toggles the visibility of the system UI (status and navigation bars) and action bar upon user interaction."
- minApi="4"
- minBuildApi="16">
- <dependency name="android-support-v4" revision="8" />
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- default="FullscreenActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_fullscreen"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="FullscreenActivity"
- suggest="${activityClass}"
- help="The name of the activity." />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <thumbs>
- <thumb>template_fullscreen_activity.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/activities/FullscreenActivity/template_fullscreen_activity.png b/base/templates/eclipse/activities/FullscreenActivity/template_fullscreen_activity.png
deleted file mode 100644
index a8597b2..0000000
Binary files a/base/templates/eclipse/activities/FullscreenActivity/template_fullscreen_activity.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/LoginActivity/recipe.xml.ftl b/base/templates/eclipse/activities/LoginActivity/recipe.xml.ftl
deleted file mode 100644
index 43198a0..0000000
--- a/base/templates/eclipse/activities/LoginActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
- <dependency mavenUrl="com.android.support:appcompat-v7:19.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
-
- <instantiate from="res/layout/activity_login.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
-
- <instantiate from="src/app_package/LoginActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <#if includeGooglePlus>
- <instantiate from="src/app_package/PlusBaseActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/PlusBaseActivity.java" />
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
-</recipe>
diff --git a/base/templates/eclipse/activities/LoginActivity/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/LoginActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index a206ed4..0000000
--- a/base/templates/eclipse/activities/LoginActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,37 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-<#if includeGooglePlus>
- <!-- To access Google+ APIs: -->
- <uses-permission android:name="android.permission.INTERNET" />
-
- <!-- To retrieve OAuth 2.0 tokens or invalidate tokens to disconnect a user. This disconnect
- option is required to comply with the Google+ Sign-In developer policies -->
- <uses-permission android:name="android.permission.USE_CREDENTIALS" />
-
- <!-- To retrieve the account name (email) as part of sign-in: -->
- <uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
-
- <!-- To auto-complete the email text field in the login form with the user's emails --><#if !includeGooglePlus>
- <uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
- <uses-permission android:name="android.permission.READ_PROFILE" />
- <uses-permission android:name="android.permission.READ_CONTACTS" />
-
- <application>
- <activity android:name=".${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${simpleName}"
- </#if>
- android:windowSoftInputMode="adjustResize|<#if includeGooglePlus>stateHidden<#else>stateVisible</#if>"
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- </activity>
-<#if includeGooglePlus>
- <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
-</#if>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/LoginActivity/root/res/layout/activity_login.xml.ftl b/base/templates/eclipse/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
deleted file mode 100644
index 45143f5..0000000
--- a/base/templates/eclipse/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
+++ /dev/null
@@ -1,108 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="${relativePackage}.${activityClass}">
-
- <!-- Login progress -->
- <ProgressBar
- android:id="@+id/login_progress"
- style="?android:attr/progressBarStyleLarge"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="8dp"
- android:visibility="gone"/>
-
- <ScrollView
- android:id="@+id/login_form"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
-<#if includeGooglePlus>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <com.google.android.gms.common.SignInButton
- android:id="@+id/plus_sign_in_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="32dp"/>
-
- <LinearLayout
- android:id="@+id/plus_sign_out_buttons"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:weightSum="2">
-
- <Button
- android:id="@+id/plus_sign_out_button"
- style="?android:textAppearanceSmall"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:text="@string/plus_sign_out"/>
-
- <Button
- android:id="@+id/plus_disconnect_button"
- style="?android:textAppearanceSmall"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:text="@string/plus_disconnect"/>
-
- </LinearLayout>
-</#if>
-
- <LinearLayout
- android:id="@+id/email_login_form"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <AutoCompleteTextView
- android:id="@+id/email"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/prompt_email"
- android:inputType="textEmailAddress"
- android:maxLines="1"
- android:singleLine="true"/>
-
- <EditText
- android:id="@+id/password"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/prompt_password"
- android:imeActionId="@+id/login"
- android:imeActionLabel="@string/action_sign_in_short"
- android:imeOptions="actionUnspecified"
- android:inputType="textPassword"
- android:maxLines="1"
- android:singleLine="true"/>
-
- <Button
- android:id="@+id/email_sign_in_button"
- style="?android:textAppearanceSmall"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:text="@string/action_sign_in"
- android:textStyle="bold"/>
-
- </LinearLayout>
-<#if includeGooglePlus>
- </LinearLayout>
-</#if>
- </ScrollView>
-
-</LinearLayout>
-
diff --git a/base/templates/eclipse/activities/LoginActivity/root/res/values/strings.xml.ftl b/base/templates/eclipse/activities/LoginActivity/root/res/values/strings.xml.ftl
deleted file mode 100644
index 97c23b6..0000000
--- a/base/templates/eclipse/activities/LoginActivity/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<resources>
- <#if !isNewProject>
- <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
- </#if>
-
- <!-- Strings related to login -->
- <string name="prompt_email">Email</string>
- <string name="prompt_password">Password (optional)</string>
- <string name="action_sign_in">Sign in or register</string>
- <string name="action_sign_in_short">Sign in</string>
-<#if includeGooglePlus> <string name="plus_sign_out">Switch Google+ account</string>
- <string name="plus_disconnect">Disconnect from Google+</string></#if>
- <string name="error_invalid_email">This email address is invalid</string>
- <string name="error_invalid_password">This password is too short</string>
- <string name="error_incorrect_password">This password is incorrect</string>
- <string name="error_field_required">This field is required</string>
-</resources>
diff --git a/base/templates/eclipse/activities/LoginActivity/template.xml b/base/templates/eclipse/activities/LoginActivity/template.xml
deleted file mode 100644
index 03ea755..0000000
--- a/base/templates/eclipse/activities/LoginActivity/template.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="5"
- name="Login Activity"
- description="Creates a new login activity, allowing users to optionally sign in with Google+ or enter an email address and password to log in to or register with your application."
- minApi="8"
- minBuildApi="14">
-
- <dependency name="android-support-v4" revision="8" />
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- default="LoginActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_login"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="Sign in"
- help="The name of the activity." />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <parameter
- id="includeGooglePlus"
- name="Include Google+ sign in"
- type="boolean"
- default="true" />
-
- <thumbs>
- <thumb>template_login_activity.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/activities/LoginActivity/template_login_activity.png b/base/templates/eclipse/activities/LoginActivity/template_login_activity.png
deleted file mode 100644
index 0f9bfc0..0000000
Binary files a/base/templates/eclipse/activities/LoginActivity/template_login_activity.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/recipe.xml.ftl b/base/templates/eclipse/activities/MasterDetailFlow/recipe.xml.ftl
deleted file mode 100644
index 25d12bd..0000000
--- a/base/templates/eclipse/activities/MasterDetailFlow/recipe.xml.ftl
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:support-v4:19.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values-large/refs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
- <merge from="res/values-sw600dp/refs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="res/layout/activity_content_detail.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/activity_${detail_name}.xml" />
- <instantiate from="res/layout/activity_content_list.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/activity_${collection_name}.xml" />
- <instantiate from="res/layout/activity_content_twopane.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" />
- <instantiate from="res/layout/fragment_content_detail.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
-
- <instantiate from="src/app_package/ContentDetailActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${DetailName}Activity.java" />
- <instantiate from="src/app_package/ContentDetailFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
- <instantiate from="src/app_package/ContentListActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${CollectionName}Activity.java" />
- <instantiate from="src/app_package/ContentListFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${CollectionName}Fragment.java" />
- <instantiate from="src/app_package/dummy/DummyContent.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 3fb177b..0000000
--- a/base/templates/eclipse/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,31 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
- <application>
- <activity android:name="${relativePackage}.${CollectionName}Activity"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${collection_name}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
-
- <activity android:name="${relativePackage}.${DetailName}Activity"
- android:label="@string/title_${detail_name}"
- <#if buildApi gte 16>android:parentActivityName="${relativePackage}.${CollectionName}Activity"</#if>>
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${relativePackage}.${CollectionName}Activity" />
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl b/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
deleted file mode 100644
index 91f931a..0000000
--- a/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/${detail_name}_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${DetailName}Activity"
- tools:ignore="MergeRootFrame" />
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl b/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
deleted file mode 100644
index 8777431..0000000
--- a/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
+++ /dev/null
@@ -1,10 +0,0 @@
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/${collection_name}"
- android:name="${packageName}.${CollectionName}Fragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
- tools:context="${relativePackage}.${CollectionName}Activity"
- tools:layout="@android:layout/list_content" />
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl b/base/templates/eclipse/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
deleted file mode 100644
index 97215c3..0000000
--- a/base/templates/eclipse/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
+++ /dev/null
@@ -1,10 +0,0 @@
-<resources>
- <!--
- Layout alias to replace the single-pane version of the layout with a
- two-pane version on Large screens.
-
- For more on layout aliases, see:
- http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
- -->
- <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
-</resources>
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl b/base/templates/eclipse/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
deleted file mode 100644
index d592404..0000000
--- a/base/templates/eclipse/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
- <!--
- Layout alias to replace the single-pane version of the layout with a
- two-pane version on screens with a smallest width (smallest dimension)
- of at least 600 density-independent pixels (dips).
-
- For more on layout aliases, see:
- http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
- -->
- <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
-</resources>
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/template.xml b/base/templates/eclipse/activities/MasterDetailFlow/template.xml
deleted file mode 100644
index b8b2587..0000000
--- a/base/templates/eclipse/activities/MasterDetailFlow/template.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="5"
- name="Master/Detail Flow"
- minApi="4"
- description="Creates a new master/detail flow, allowing users to view a collection of objects as well as details for each object. This flow is presented using two columns on tablet-size screens and one column on handsets and smaller screens. This template creates two activities, a master fragment, and a detail fragment."
- category="Activity">
-
- <dependency name="android-support-v4" revision="8" />
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <thumbs>
- <thumb>template_master_detail.png</thumb>
- </thumbs>
-
- <parameter
- id="objectKind"
- name="Object Kind"
- type="string"
- constraints="nonempty"
- default="Item"
- help="Other examples are 'Person', 'Book', etc." />
-
- <parameter
- id="objectKindPlural"
- name="Object Kind Plural"
- type="string"
- constraints="nonempty"
- default="Items"
- help="Other examples are 'People', 'Books', etc." />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- suggest="${objectKindPlural}"
- default="Items" />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, the primary activity in the flow will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/template_master_detail.png b/base/templates/eclipse/activities/MasterDetailFlow/template_master_detail.png
deleted file mode 100644
index f9d3f23..0000000
Binary files a/base/templates/eclipse/activities/MasterDetailFlow/template_master_detail.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/recipe.xml.ftl b/base/templates/eclipse/activities/NavigationDrawerActivity/recipe.xml.ftl
deleted file mode 100644
index 77e66fd..0000000
--- a/base/templates/eclipse/activities/NavigationDrawerActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
- <#if !appCompat><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <!-- TODO: switch on Holo Dark v. Holo Light -->
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
- <copy from="res/drawable-xxhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
-
- <instantiate from="res/menu/global.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/global.xml" />
-
- <instantiate from="res/layout/activity_drawer.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- <instantiate from="res/layout/fragment_navigation_drawer.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${navigationDrawerLayout}.xml" />
-
- <instantiate from="res/layout/fragment_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-
- <instantiate from="src/app_package/DrawerActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <instantiate from="src/app_package/NavigationDrawerFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/NavigationDrawerFragment.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index af1d2d6..0000000
--- a/base/templates/eclipse/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl b/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
deleted file mode 100644
index eabb6b1..0000000
--- a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
+++ /dev/null
@@ -1,31 +0,0 @@
-<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
-<android.support.v4.widget.DrawerLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/drawer_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${activityClass}">
-
- <!-- As the main content view, the view below consumes the entire
- space available using match_parent in both dimensions. -->
- <FrameLayout
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- <!-- android:layout_gravity="start" tells DrawerLayout to treat
- this as a sliding drawer on the left side for left-to-right
- languages and on the right side for right-to-left languages.
- If you're not building against API 17 or higher, use
- android:layout_gravity="left" instead. -->
- <!-- The drawer is given a fixed width in dp and extends the full height of
- the container. -->
- <fragment android:id="@+id/navigation_drawer"
- android:layout_width="@dimen/navigation_drawer_width"
- android:layout_height="match_parent"
- android:layout_gravity="<#if buildApi gte 17>start<#else>left</#if>"
- android:name="${packageName}.NavigationDrawerFragment"
- tools:layout="@layout/${navigationDrawerLayout}" />
-
-</android.support.v4.widget.DrawerLayout>
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/template.xml b/base/templates/eclipse/activities/NavigationDrawerActivity/template.xml
deleted file mode 100644
index 9329c7b..0000000
--- a/base/templates/eclipse/activities/NavigationDrawerActivity/template.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="4"
- name="Navigation Drawer Activity"
- minApi="7"
- minBuildApi="14"
- description="Creates a new Activity with a Navigation Drawer.">
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- suggest="${layoutToActivity(layoutName)}"
- default="MainActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="layoutName"
- name="Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="${activityToLayout(activityClass)}"
- default="activity_main"
- help="The name of the layout to create for the activity" />
-
- <parameter
- id="fragmentLayoutName"
- name="Fragment Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- suggest="fragment_${classToResource(activityClass)}"
- default="fragment_main"
- help="The name of the layout to create for the activity's content fragment"/>
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="MainActivity"
- suggest="${activityClass}"
- help="The name of the activity. For launcher activities, the application title." />
-
- <parameter
- id="isLauncher"
- name="Launcher Activity"
- type="boolean"
- default="false"
- help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <parameter
- id="navigationDrawerLayout"
- name="Navigation Drawer Fragment Name"
- type="string"
- constraints="layout|unique"
- default="fragment_navigation_drawer"/>
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_blank_activity_drawer.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/template_blank_activity_drawer.png b/base/templates/eclipse/activities/NavigationDrawerActivity/template_blank_activity_drawer.png
deleted file mode 100644
index 25ab6bc..0000000
Binary files a/base/templates/eclipse/activities/NavigationDrawerActivity/template_blank_activity_drawer.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/SettingsActivity/recipe.xml.ftl b/base/templates/eclipse/activities/SettingsActivity/recipe.xml.ftl
deleted file mode 100644
index 060d7b3..0000000
--- a/base/templates/eclipse/activities/SettingsActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:support-v4:19.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <copy from="res/xml/pref_data_sync.xml"
- to="${escapeXmlAttribute(resOut)}/xml/pref_data_sync.xml" />
- <copy from="res/xml/pref_general.xml"
- to="${escapeXmlAttribute(resOut)}/xml/pref_general.xml" />
- <merge from="res/xml/pref_headers.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/xml/pref_headers.xml" />
- <copy from="res/xml/pref_notification.xml"
- to="${escapeXmlAttribute(resOut)}/xml/pref_notification.xml" />
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
-
- <instantiate from="src/app_package/SettingsActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-</recipe>
diff --git a/base/templates/eclipse/activities/SettingsActivity/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/SettingsActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index b0048b2..0000000
--- a/base/templates/eclipse/activities/SettingsActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,18 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${simpleName}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_general.xml b/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_general.xml
deleted file mode 100644
index c49cbed..0000000
--- a/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_general.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-
- <CheckBoxPreference
- android:key="example_checkbox"
- android:title="@string/pref_title_social_recommendations"
- android:summary="@string/pref_description_social_recommendations"
- android:defaultValue="true" />
-
- <!-- NOTE: EditTextPreference accepts EditText attributes. -->
- <!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
- <EditTextPreference
- android:key="example_text"
- android:title="@string/pref_title_display_name"
- android:defaultValue="@string/pref_default_display_name"
- android:selectAllOnFocus="true"
- android:inputType="textCapWords"
- android:capitalize="words"
- android:singleLine="true"
- android:maxLines="1" />
-
- <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
- dismiss it. -->
- <!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
- <ListPreference
- android:key="example_list"
- android:title="@string/pref_title_add_friends_to_messages"
- android:defaultValue="-1"
- android:entries="@array/pref_example_list_titles"
- android:entryValues="@array/pref_example_list_values"
- android:negativeButtonText="@null"
- android:positiveButtonText="@null" />
-
-</PreferenceScreen>
diff --git a/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl b/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl
deleted file mode 100644
index a3da325..0000000
--- a/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- These settings headers are only used on tablets. -->
-
- <header
- android:fragment="${packageName}.${activityClass}$GeneralPreferenceFragment"
- android:title="@string/pref_header_general" />
-
- <header
- android:fragment="${packageName}.${activityClass}$NotificationPreferenceFragment"
- android:title="@string/pref_header_notifications" />
-
- <header
- android:fragment="${packageName}.${activityClass}$DataSyncPreferenceFragment"
- android:title="@string/pref_header_data_sync" />
-
-</preference-headers>
diff --git a/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_notification.xml b/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_notification.xml
deleted file mode 100644
index b4b8cae..0000000
--- a/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_notification.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- A 'parent' preference, which enables/disables child preferences (below)
- when checked/unchecked. -->
- <CheckBoxPreference
- android:key="notifications_new_message"
- android:title="@string/pref_title_new_message_notifications"
- android:defaultValue="true" />
-
- <!-- Allows the user to choose a ringtone in the 'notification' category. -->
- <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
- <!-- NOTE: RingtonePreference's summary should be set to its value by the activity code. -->
- <RingtonePreference
- android:dependency="notifications_new_message"
- android:key="notifications_new_message_ringtone"
- android:title="@string/pref_title_ringtone"
- android:ringtoneType="notification"
- android:defaultValue="content://settings/system/notification_sound" />
-
- <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
- <CheckBoxPreference
- android:dependency="notifications_new_message"
- android:key="notifications_new_message_vibrate"
- android:title="@string/pref_title_vibrate"
- android:defaultValue="true" />
-
-</PreferenceScreen>
diff --git a/base/templates/eclipse/activities/SettingsActivity/template.xml b/base/templates/eclipse/activities/SettingsActivity/template.xml
deleted file mode 100644
index cedb287..0000000
--- a/base/templates/eclipse/activities/SettingsActivity/template.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="4"
- name="Settings Activity"
- description="Creates a new application settings activity that presents alternative layouts on handset and tablet-size screens."
- minApi="4"
- minBuildApi="11"
- category="Activity">
-
- <dependency name="android-support-v4" revision="8" />
-
- <category value="Activity" />
- <formfactor value="Mobile" />
-
- <parameter
- id="activityClass"
- name="Activity Name"
- type="string"
- constraints="class|unique|nonempty"
- default="SettingsActivity"
- help="The name of the activity class to create" />
-
- <parameter
- id="activityTitle"
- name="Title"
- type="string"
- constraints="nonempty"
- default="Settings"
- help="The name of the activity." />
-
- <parameter
- id="parentActivityClass"
- name="Hierarchical Parent"
- type="string"
- constraints="activity|exists|empty"
- default=""
- help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_settings_activity.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/activities/SettingsActivity/template_settings_activity.png b/base/templates/eclipse/activities/SettingsActivity/template_settings_activity.png
deleted file mode 100644
index c1a65cb..0000000
Binary files a/base/templates/eclipse/activities/SettingsActivity/template_settings_activity.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/TabbedActivity/recipe.xml.ftl b/base/templates/eclipse/activities/TabbedActivity/recipe.xml.ftl
deleted file mode 100644
index 72966fd..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/recipe.xml.ftl
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
- <#if !appCompat && hasViewPager><dependency mavenUrl="com.android.support:support-v13:19.+"/></#if>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/menu/main.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-w820dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
-
- <!-- Decide what kind of layout(s) to add -->
- <#if hasViewPager>
- <instantiate from="res/layout/activity_pager.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
-
- <#else>
- <instantiate from="res/layout/activity_fragment_container.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- </#if>
-
- <instantiate from="res/layout/fragment_simple.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-
- <!-- Decide which activity code to add -->
- <#if features == "tabs" || features == "pager">
- <instantiate from="src/app_package/TabsAndPagerActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- <#elseif features == "spinner">
- <instantiate from="src/app_package/DropdownActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
-
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/AndroidManifest.xml.ftl b/base/templates/eclipse/activities/TabbedActivity/root/AndroidManifest.xml.ftl
deleted file mode 100644
index af1d2d6..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <application>
- <activity android:name="${relativePackage}.${activityClass}"
- <#if isNewProject>
- android:label="@string/app_name"
- <#else>
- android:label="@string/title_${activityToLayout(activityClass)}"
- </#if>
- <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
- <#if parentActivityClass != "">
- <meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value="${parentActivityClass}" />
- </#if>
- <#if isLauncher>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </#if>
- </activity>
- </application>
-
-</manifest>
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl b/base/templates/eclipse/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
deleted file mode 100644
index e296a03..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
+++ /dev/null
@@ -1,6 +0,0 @@
-<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${activityClass}" />
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl b/base/templates/eclipse/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
deleted file mode 100644
index 6fa8741..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
+++ /dev/null
@@ -1,16 +0,0 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
- tools:context="${relativePackage}.${activityClass}$PlaceholderFragment">
-
- <TextView
- <#if hasViewPager>android:id="@+id/section_label"<#else>android:text="@string/hello_world"</#if>
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-</RelativeLayout>
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/res/menu/main.xml.ftl b/base/templates/eclipse/activities/TabbedActivity/root/res/menu/main.xml.ftl
deleted file mode 100644
index 27f6aaa..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/res/menu/main.xml.ftl
+++ /dev/null
@@ -1,9 +0,0 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
- xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
- xmlns:tools="http://schemas.android.com/tools"
- tools:context="${relativePackage}.${activityClass}" >
- <item android:id="@+id/action_settings"
- android:title="@string/action_settings"
- android:orderInCategory="100"
- ${(appCompat)?string('app','android')}:showAsAction="never" />
-</menu>
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/res/values-w820dp/dimens.xml b/base/templates/eclipse/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
deleted file mode 100644
index 63fc816..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<resources>
- <!-- Example customization of dimensions originally defined in res/values/dimens.xml
- (such as screen margins) for screens with more than 820dp of available width. This
- would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
- <dimen name="activity_horizontal_margin">64dp</dimen>
-</resources>
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/res/values/dimens.xml.ftl b/base/templates/eclipse/activities/TabbedActivity/root/res/values/dimens.xml.ftl
deleted file mode 100644
index 47c8224..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/res/values/dimens.xml.ftl
+++ /dev/null
@@ -1,5 +0,0 @@
-<resources>
- <!-- Default screen margins, per the Android Design guidelines. -->
- <dimen name="activity_horizontal_margin">16dp</dimen>
- <dimen name="activity_vertical_margin">16dp</dimen>
-</resources>
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/res/values/strings.xml.ftl b/base/templates/eclipse/activities/TabbedActivity/root/res/values/strings.xml.ftl
deleted file mode 100644
index 8e9cef0..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,14 +0,0 @@
-<resources>
- <#if !isNewProject>
- <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
- </#if>
-
- <string name="title_section1">Section 1</string>
- <string name="title_section2">Section 2</string>
- <string name="title_section3">Section 3</string>
-
- <string name="hello_world">Hello world!</string>
-
- <string name="action_settings">Settings</string>
-
-</resources>
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl b/base/templates/eclipse/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
deleted file mode 100644
index f5c65b6..0000000
--- a/base/templates/eclipse/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
+++ /dev/null
@@ -1,36 +0,0 @@
- /**
- * A placeholder fragment containing a simple view.
- */
- public static class PlaceholderFragment extends Fragment {
- /**
- * The fragment argument representing the section number for this
- * fragment.
- */
- private static final String ARG_SECTION_NUMBER = "section_number";
-
- /**
- * Returns a new instance of this fragment for the given section
- * number.
- */
- public static PlaceholderFragment newInstance(int sectionNumber) {
- PlaceholderFragment fragment = new PlaceholderFragment();
- Bundle args = new Bundle();
- args.putInt(ARG_SECTION_NUMBER, sectionNumber);
- fragment.setArguments(args);
- return fragment;
- }
-
- public PlaceholderFragment() {
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
- <#if hasSections?has_content>
- TextView textView = (TextView) rootView.findViewById(R.id.section_label);
- textView.setText(Integer.toString(getArguments().getInt(ARG_SECTION_NUMBER)));
- </#if>
- return rootView;
- }
- }
diff --git a/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_dropdown.png b/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_dropdown.png
deleted file mode 100644
index 6204340..0000000
Binary files a/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_dropdown.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_pager.png b/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_pager.png
deleted file mode 100644
index f0792e6..0000000
Binary files a/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_pager.png and /dev/null differ
diff --git a/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_tabs.png b/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_tabs.png
deleted file mode 100644
index e57460b..0000000
Binary files a/base/templates/eclipse/activities/TabbedActivity/template_blank_activity_tabs.png and /dev/null differ
diff --git a/base/templates/eclipse/other/AidlFile/recipe.xml.ftl b/base/templates/eclipse/other/AidlFile/recipe.xml.ftl
deleted file mode 100644
index ef6e31c..0000000
--- a/base/templates/eclipse/other/AidlFile/recipe.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <instantiate from="src/app_package/interface.aidl.ftl"
- to="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
- <open file="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
-</recipe>
diff --git a/base/templates/eclipse/other/AidlFolder/recipe.xml.ftl b/base/templates/eclipse/other/AidlFolder/recipe.xml.ftl
deleted file mode 100644
index 93eef52..0000000
--- a/base/templates/eclipse/other/AidlFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/aidl/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/AndroidManifest/recipe.xml.ftl b/base/templates/eclipse/other/AndroidManifest/recipe.xml.ftl
deleted file mode 100644
index 747c6b3..0000000
--- a/base/templates/eclipse/other/AndroidManifest/recipe.xml.ftl
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFile>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/AppWidget/recipe.xml.ftl b/base/templates/eclipse/other/AppWidget/recipe.xml.ftl
deleted file mode 100644
index 5810eae..0000000
--- a/base/templates/eclipse/other/AppWidget/recipe.xml.ftl
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <copy from="res/drawable-nodpi/example_appwidget_preview.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_appwidget_preview.png" />
- <instantiate from="res/layout/appwidget.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${class_name}.xml" />
-
- <#if configurable>
- <instantiate from="res/layout/appwidget_configure.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${class_name}_configure.xml" />
- </#if>
-
- <instantiate from="res/xml/appwidget_info.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/xml/${class_name}_info.xml" />
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
- <merge from="res/values-v14/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-v14/dimens.xml" />
- <merge from="res/values/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
-
- <instantiate from="src/app_package/AppWidget.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <#if configurable>
- <instantiate from="src/app_package/AppWidgetConfigureActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}ConfigureActivity.java" />
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/eclipse/other/AssetsFolder/recipe.xml.ftl b/base/templates/eclipse/other/AssetsFolder/recipe.xml.ftl
deleted file mode 100644
index f2042bf..0000000
--- a/base/templates/eclipse/other/AssetsFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/assets/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/BlankFragment/recipe.xml.ftl b/base/templates/eclipse/other/BlankFragment/recipe.xml.ftl
deleted file mode 100644
index 61764c7..0000000
--- a/base/templates/eclipse/other/BlankFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if useSupport><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
- <merge from="res/values/strings.xml" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <#if includeLayout>
- <instantiate from="res/layout/fragment_blank.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
-
- <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <instantiate from="src/app_package/BlankFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/eclipse/other/BroadcastReceiver/recipe.xml.ftl b/base/templates/eclipse/other/BroadcastReceiver/recipe.xml.ftl
deleted file mode 100644
index d889414..0000000
--- a/base/templates/eclipse/other/BroadcastReceiver/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/BroadcastReceiver.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/eclipse/other/ContentProvider/recipe.xml.ftl b/base/templates/eclipse/other/ContentProvider/recipe.xml.ftl
deleted file mode 100644
index 9bc3e61..0000000
--- a/base/templates/eclipse/other/ContentProvider/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/ContentProvider.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/eclipse/other/CustomView/recipe.xml.ftl b/base/templates/eclipse/other/CustomView/recipe.xml.ftl
deleted file mode 100644
index ce605b2..0000000
--- a/base/templates/eclipse/other/CustomView/recipe.xml.ftl
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="res/values/attrs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/attrs_${view_class}.xml" />
- <instantiate from="res/layout/sample.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
-
- <instantiate from="src/app_package/CustomView.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/other/Daydream/recipe.xml.ftl b/base/templates/eclipse/other/Daydream/recipe.xml.ftl
deleted file mode 100644
index 5b99917..0000000
--- a/base/templates/eclipse/other/Daydream/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <copy from="res/layout-v17/dream.xml"
- to="${escapeXmlAttribute(resOut)}/layout-v17/${class_name}.xml" />
-
- <instantiate from="src/app_package/DreamService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-<#if configurable>
- <copy from="res/xml/dream_prefs.xml"
- to="${escapeXmlAttribute(resOut)}/xml/${prefs_name}.xml" />
-
- <instantiate from="src/app_package/SettingsActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${settingsClassName}.java" />
-
- <instantiate from="res/xml/xml_dream.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/xml/${info_name}.xml" />
-</#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/eclipse/other/IntentService/recipe.xml.ftl b/base/templates/eclipse/other/IntentService/recipe.xml.ftl
deleted file mode 100644
index 197fc1f..0000000
--- a/base/templates/eclipse/other/IntentService/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/IntentService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/eclipse/other/JavaFolder/recipe.xml.ftl b/base/templates/eclipse/other/JavaFolder/recipe.xml.ftl
deleted file mode 100644
index a470666..0000000
--- a/base/templates/eclipse/other/JavaFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/java/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/JniFolder/recipe.xml.ftl b/base/templates/eclipse/other/JniFolder/recipe.xml.ftl
deleted file mode 100644
index 29e7e98..0000000
--- a/base/templates/eclipse/other/JniFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/jni/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/LayoutResourceFile/recipe.xml.ftl b/base/templates/eclipse/other/LayoutResourceFile/recipe.xml.ftl
deleted file mode 100644
index a73adc7..0000000
--- a/base/templates/eclipse/other/LayoutResourceFile/recipe.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <instantiate from="res/layout.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/other/ListFragment/recipe.xml.ftl b/base/templates/eclipse/other/ListFragment/recipe.xml.ftl
deleted file mode 100644
index 5236d3c..0000000
--- a/base/templates/eclipse/other/ListFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if useSupport><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
-<#if switchGrid == true>
- <merge from="res/values/refs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/refs.xml" />
- <merge from="res/values/refs_lrg.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
- <merge from="res/values/refs_lrg.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
-
- <instantiate from="res/layout/fragment_grid.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout}_grid.xml" />
-
- <instantiate from="res/layout/fragment_list.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout}_list.xml" />
-</#if>
-
- <instantiate from="src/app_package/ListFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <instantiate from="src/app_package/dummy/DummyContent.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/eclipse/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl b/base/templates/eclipse/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl
deleted file mode 100644
index 3545ba3..0000000
--- a/base/templates/eclipse/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl
+++ /dev/null
@@ -1,55 +0,0 @@
-package ${packageName}.dummy;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Helper class for providing sample content for user interfaces created by
- * Android template wizards.
- * <p>
- * TODO: Replace all uses of this class before publishing your app.
- */
-public class DummyContent {
-
- /**
- * An array of sample (dummy) items.
- */
- public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
-
- /**
- * A map of sample (dummy) items, by ID.
- */
- public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
-
- static {
- // Add 3 sample items.
- addItem(new DummyItem("1", "Item 1"));
- addItem(new DummyItem("2", "Item 2"));
- addItem(new DummyItem("3", "Item 3"));
- }
-
- private static void addItem(DummyItem item) {
- ITEMS.add(item);
- ITEM_MAP.put(item.id, item);
- }
-
- /**
- * A dummy item representing a piece of content.
- */
- public static class DummyItem {
- public String id;
- public String content;
-
- public DummyItem(String id, String content) {
- this.id = id;
- this.content = content;
- }
-
- @Override
- public String toString() {
- return content;
- }
- }
-}
diff --git a/base/templates/eclipse/other/Notification/recipe.xml.ftl b/base/templates/eclipse/other/Notification/recipe.xml.ftl
deleted file mode 100644
index c08a91f..0000000
--- a/base/templates/eclipse/other/Notification/recipe.xml.ftl
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <dependency mavenUrl="com.android.support:support-v4:19.+"/>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <#if expandedStyle == "picture">
- <copy from="res/drawable-nodpi/example_picture_large.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
- <#else>
- <copy from="res/drawable-nodpi/example_picture_small.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
- </#if>
-
- <#if moreActions>
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
- </#if>
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="src/app_package/NotificationHelper.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/eclipse/other/PlusOneFragment/recipe.xml.ftl b/base/templates/eclipse/other/PlusOneFragment/recipe.xml.ftl
deleted file mode 100644
index 443404d..0000000
--- a/base/templates/eclipse/other/PlusOneFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <dependency mavenUrl="com.android.support:support-v4:19.+"/>
- <dependency mavenUrl="com.google.android.gms:play-services:4.2.42"/>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/layout/fragment_plus_one.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
-
- <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <instantiate from="src/app_package/PlusOneFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/eclipse/other/ResFolder/recipe.xml.ftl b/base/templates/eclipse/other/ResFolder/recipe.xml.ftl
deleted file mode 100644
index abfda2d..0000000
--- a/base/templates/eclipse/other/ResFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/res/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/ResourcesFolder/recipe.xml.ftl b/base/templates/eclipse/other/ResourcesFolder/recipe.xml.ftl
deleted file mode 100644
index 2045012..0000000
--- a/base/templates/eclipse/other/ResourcesFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/resources/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/RsFolder/recipe.xml.ftl b/base/templates/eclipse/other/RsFolder/recipe.xml.ftl
deleted file mode 100644
index e2766e4..0000000
--- a/base/templates/eclipse/other/RsFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/rs/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/eclipse/other/Service/recipe.xml.ftl b/base/templates/eclipse/other/Service/recipe.xml.ftl
deleted file mode 100644
index 9e66da5..0000000
--- a/base/templates/eclipse/other/Service/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/Service.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/eclipse/other/ValueResourceFile/recipe.xml.ftl b/base/templates/eclipse/other/ValueResourceFile/recipe.xml.ftl
deleted file mode 100644
index 8895ae1..0000000
--- a/base/templates/eclipse/other/ValueResourceFile/recipe.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <instantiate from="res/values.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
- <open file="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
-</recipe>
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/recipe.xml.ftl b/base/templates/eclipse/projects/NewAndroidApplication/recipe.xml.ftl
deleted file mode 100644
index 73bf014..0000000
--- a/base/templates/eclipse/projects/NewAndroidApplication/recipe.xml.ftl
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<#if copyIcons>
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
-</#if>
- <instantiate from="res/values/styles.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
-<#if buildApi gte 11 && baseTheme != "none">
- <instantiate from="res/values-v11/styles_hc.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
-</#if>
-<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
- <instantiate from="res/values-v14/styles_ics.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
-</#if>
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-</recipe>
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/template.xml b/base/templates/eclipse/projects/NewAndroidApplication/template.xml
deleted file mode 100644
index 0d9b234..0000000
--- a/base/templates/eclipse/projects/NewAndroidApplication/template.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="1"
- revision="2"
- name="Android Application"
- description="Creates a new Android application.">
- <dependency name="android-support-v4" revision="8" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <category value="Applications" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Application title"
- type="string"
- constraints="nonempty"
- default="My Application" />
-
- <parameter
- id="baseTheme"
- name="Base Theme"
- type="enum"
- default="holo_light_darkactionbar"
- help="The base user interface theme for the application">
- <option id="none">None</option>
- <option id="holo_dark" minBuildApi="11">Holo Dark</option>
- <option id="holo_light" minBuildApi="11">Holo Light</option>
- <option id="holo_light_darkactionbar" minBuildApi="14" default="true">Holo Light with Dark Action Bar</option>
- </parameter>
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="copyIcons"
- name="Include launcher icons"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/recipe.xml.ftl b/base/templates/eclipse/projects/NewAndroidLibrary/recipe.xml.ftl
deleted file mode 100644
index 73bf014..0000000
--- a/base/templates/eclipse/projects/NewAndroidLibrary/recipe.xml.ftl
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<#if copyIcons>
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
-</#if>
- <instantiate from="res/values/styles.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
-<#if buildApi gte 11 && baseTheme != "none">
- <instantiate from="res/values-v11/styles_hc.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
-</#if>
-<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
- <instantiate from="res/values-v14/styles_ics.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
-</#if>
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-</recipe>
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/template.xml b/base/templates/eclipse/projects/NewAndroidLibrary/template.xml
deleted file mode 100644
index 9d13db5..0000000
--- a/base/templates/eclipse/projects/NewAndroidLibrary/template.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="1"
- revision="2"
- name="Android Library"
- description="Creates a new Android library.">
- <dependency name="android-support-v4" revision="8" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <category value="Applications" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Library title"
- type="string"
- constraints="nonempty"
- default="My Library"/>
-
- <parameter
- id="baseTheme"
- name="Base Theme"
- type="enum"
- default="holo_light_darkactionbar"
- help="The base user interface theme for the library">
- <option id="none">None</option>
- <option id="holo_dark" minBuildApi="11">Holo Dark</option>
- <option id="holo_light" minBuildApi="11">Holo Light</option>
- <option id="holo_light_darkactionbar" minBuildApi="14" default="true">Holo Light with Dark Action Bar</option>
- </parameter>
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="copyIcons"
- name="Include launcher icons"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/eclipse/projects/NewJavaLibrary/recipe.xml.ftl b/base/templates/eclipse/projects/NewJavaLibrary/recipe.xml.ftl
deleted file mode 100644
index ea1e83f..0000000
--- a/base/templates/eclipse/projects/NewJavaLibrary/recipe.xml.ftl
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <instantiate from="/src/library_package/Placeholder.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/gradle-projects/AndroidWearModule/recipe.xml.ftl b/base/templates/gradle-projects/AndroidWearModule/recipe.xml.ftl
deleted file mode 100644
index d23fc63..0000000
--- a/base/templates/gradle-projects/AndroidWearModule/recipe.xml.ftl
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-<#if !createActivity>
- <mkdir at="${escapeXmlAttribute(srcOut)}" />
-</#if>
-
- <dependency mavenUrl="com.google.android.support:wearable:+" />
- <dependency mavenUrl="com.google.android.gms:play-services-wearable:+" />
-
- <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
-
- <merge from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<#if makeIgnore>
- <copy from="module_ignore"
- to="${escapeXmlAttribute(projectOut)}/.gitignore" />
-</#if>
-<#if enableProGuard>
- <instantiate from="proguard-rules.txt.ftl"
- to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
-</#if>
- <mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
- <copy from="res/mipmap-hdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
- <copy from="res/mipmap-mdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
- <copy from="res/mipmap-xhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
- <copy from="res/mipmap-xxhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-</recipe>
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/AndroidManifest.xml.ftl b/base/templates/gradle-projects/AndroidWearModule/root/AndroidManifest.xml.ftl
deleted file mode 100644
index c43f9e4..0000000
--- a/base/templates/gradle-projects/AndroidWearModule/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="${packageName}">
- <uses-feature android:name="android.hardware.type.watch" />
- <application android:allowBackup="true"
- android:label="@string/app_name"
- android:icon="@mipmap/ic_launcher"
- android:theme="@android:style/Theme.DeviceDefault">
-
- </application>
-
-</manifest>
diff --git a/base/templates/gradle-projects/AndroidWearModule/template.xml b/base/templates/gradle-projects/AndroidWearModule/template.xml
deleted file mode 100644
index df0107d..0000000
--- a/base/templates/gradle-projects/AndroidWearModule/template.xml
+++ /dev/null
@@ -1,99 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Android Wear Module"
- description="Creates a new Android Wear Module."
- minApi="20"
- minBuildApi="20">
-
- <category value="Application" />
-
- <formfactor value="Wear" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="app_package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Module title"
- type="string"
- constraints="nonempty"
- default="My Wear App" />
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="4.4W" />
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="20" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"
- default="20" />
-
- <!--
- Usually the same as targetApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="targetApiString"
- name="Target API"
- type="string"
- constraints="apilevel"
- default="4.4W" />
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="20" />
-
- <!--
- Usually the same as buildApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="buildApiString"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="4.4W" />
-
- <parameter
- id="makeIgnore"
- name="Create .gitignore file"
- type="boolean"
- default="true" />
-
- <parameter
- id="enableProGuard"
- name="Enable ProGuard"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/gradle-projects/AndroidWearModule/template_new_project.png b/base/templates/gradle-projects/AndroidWearModule/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/AndroidWearModule/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle-projects/ImportExistingProject/template.xml b/base/templates/gradle-projects/ImportExistingProject/template.xml
deleted file mode 100644
index 0ef0c72..0000000
--- a/base/templates/gradle-projects/ImportExistingProject/template.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Import Existing Project"
- description="Import existing Eclipse ADT or Gradle project as a module">
-
- <category value="Application" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-</template>
diff --git a/base/templates/gradle-projects/ImportExistingProject/template_new_project.png b/base/templates/gradle-projects/ImportExistingProject/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/ImportExistingProject/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/recipe.xml.ftl b/base/templates/gradle-projects/NewAndroidAutoProject/recipe.xml.ftl
deleted file mode 100644
index 316065f..0000000
--- a/base/templates/gradle-projects/NewAndroidAutoProject/recipe.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/build.gradle" />
-
-<#if makeIgnore>
- <copy from="project_ignore"
- to="${escapeXmlAttribute(topOut)}/.gitignore" />
-</#if>
-
- <instantiate from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
-
- <instantiate from="gradle.properties.ftl"
- to="${escapeXmlAttribute(topOut)}/gradle.properties" />
-
- <copy from="${templateRoot}/gradle/wrapper"
- to="${escapeXmlAttribute(topOut)}/" />
-
-<#if sdkDir??>
- <instantiate from="local.properties.ftl"
- to="${escapeXmlAttribute(topOut)}/local.properties" />
-</#if>
-</recipe>
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/root/build.gradle.ftl b/base/templates/gradle-projects/NewAndroidAutoProject/root/build.gradle.ftl
deleted file mode 100644
index 5ac9a20..0000000
--- a/base/templates/gradle-projects/NewAndroidAutoProject/root/build.gradle.ftl
+++ /dev/null
@@ -1,29 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- jcenter()
-<#if mavenUrl != "mavenCentral">
- maven {
- url '${mavenUrl}'
- }
-</#if>
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- jcenter()
-<#if mavenUrl != "mavenCentral">
- maven {
- url '${mavenUrl}'
- }
-</#if>
- }
-}
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/template.xml b/base/templates/gradle-projects/NewAndroidAutoProject/template.xml
deleted file mode 100644
index 1bf59ca..0000000
--- a/base/templates/gradle-projects/NewAndroidAutoProject/template.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Android Auto Project"
- description="Creates a new Android Auto project.">
-
- <category value="Application" />
- <formfactor value="Car" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <parameter
- id="makeIgnore"
- name="Create .gitignore file"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/template_new_project.png b/base/templates/gradle-projects/NewAndroidAutoProject/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/NewAndroidAutoProject/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle-projects/NewAndroidModule/globals.xml.ftl b/base/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
deleted file mode 100644
index 32a9164..0000000
--- a/base/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="topOut" value="." />
- <global id="projectOut" value="." />
- <!-- Use appcompat if compiling with Lollipop and supporting pre-Lollipop versions -->
- <global id="appCompat" type="boolean" value="${(hasDependency('com.android.support:appcompat-v7'))?string}" />
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="testOut" value="androidTest/${slashedPackageName(packageName)}" />
- <global id="unitTestOut" value="${escapeXmlAttribute(projectOut)}/src/test/java/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="mavenUrl" value="mavenCentral" />
- <global id="buildToolsVersion" value="18.0.1" />
- <global id="gradlePluginVersion" value="0.6.+" />
- <global id="junitVersion" value="4.12" />
- <global id="unitTestsSupported" type="boolean" value="${(compareVersions(gradlePluginVersion, '1.1.0') >= 0)?string}" />
-</globals>
diff --git a/base/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl b/base/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
deleted file mode 100644
index d960f16..0000000
--- a/base/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:${targetApi}.+"/></#if>
-
-<#if !createActivity>
- <mkdir at="${escapeXmlAttribute(srcOut)}" />
-</#if>
-
- <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
-
- <merge from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
-<#if copyIcons && !isLibraryProject>
- <copy from="res/mipmap-hdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
- <copy from="res/mipmap-mdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
- <copy from="res/mipmap-xhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
- <copy from="res/mipmap-xxhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
-</#if>
-<#if makeIgnore>
- <copy from="module_ignore"
- to="${escapeXmlAttribute(projectOut)}/.gitignore" />
-</#if>
-<#if enableProGuard>
- <instantiate from="proguard-rules.txt.ftl"
- to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
-</#if>
-<#if !(isLibraryProject??) || !isLibraryProject>
- <instantiate from="res/values/styles.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
-<#if buildApi gte 21 && !(appCompat)>
- <copy from="res/values-v21/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values-v21/styles.xml" />
-</#if>
-</#if>
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="test/app_package/ApplicationTest.java.ftl"
- to="${testOut}/ApplicationTest.java" />
-
-<#if unitTestsSupported>
- <instantiate from="test/app_package/ExampleUnitTest.java.ftl"
- to="${unitTestOut}/ExampleUnitTest.java" />
-</#if>
-
-</recipe>
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl b/base/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 22ba868..0000000
--- a/base/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,13 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="${packageName}">
-
- <application <#if minApiLevel gte 4 && buildApi gte 4>android:allowBackup="true"</#if>
- android:label="@string/app_name"<#if copyIcons && !isLibraryProject>
- android:icon="@mipmap/ic_launcher"<#elseif assetName??>
- android:icon="@drawable/${assetName}"</#if>
- <#if baseTheme != "none" && !isLibraryProject>
- android:theme="@style/AppTheme"</#if>>
-
- </application>
-
-</manifest>
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/values-v21/styles.xml b/base/templates/gradle-projects/NewAndroidModule/root/res/values-v21/styles.xml
deleted file mode 100644
index dba3c41..0000000
--- a/base/templates/gradle-projects/NewAndroidModule/root/res/values-v21/styles.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <style name="AppTheme" parent="android:Theme.Material.Light">
- </style>
-</resources>
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl b/base/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl
deleted file mode 100644
index 99f89e1..0000000
--- a/base/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="<#if
- appCompat>Theme.AppCompat<#else
- >android:Theme.Holo</#if><#if
- baseTheme?contains("light")>.Light<#if
- baseTheme?contains("darkactionbar")>.DarkActionBar</#if></#if>">
- <!-- Customize your theme here. -->
- </style>
-
-</resources>
diff --git a/base/templates/gradle-projects/NewAndroidModule/template.xml b/base/templates/gradle-projects/NewAndroidModule/template.xml
deleted file mode 100644
index ba6154f..0000000
--- a/base/templates/gradle-projects/NewAndroidModule/template.xml
+++ /dev/null
@@ -1,115 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="4"
- name="Android Module"
- description="Creates a new Android module.">
-
- <category value="Application" />
-
- <formfactor value="Mobile" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="app_package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Module title"
- type="string"
- constraints="nonempty"
- default="My Module" />
-
- <parameter
- id="baseTheme"
- name="Base Theme"
- type="enum"
- default="holo_light_darkactionbar"
- help="The base user interface theme for the module">
- <option id="none">None</option>
- <option id="holo_dark" minBuildApi="11">Holo Dark</option>
- <option id="holo_light" minBuildApi="11">Holo Light</option>
- <option id="holo_light_darkactionbar" minBuildApi="14">Holo Light with Dark Action Bar</option>
- </parameter>
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="8" />
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="8" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"
- default="19" />
-
- <!--
- Usually the same as targetApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="targetApiString"
- name="Target API"
- type="string"
- constraints="apilevel"
- default="19" />
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="19" />
-
- <!--
- Usually the same as buildApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="buildApiString"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="19" />
-
- <parameter
- id="copyIcons"
- name="Include launcher icons"
- type="boolean"
- default="true" />
-
- <parameter
- id="makeIgnore"
- name="Create .gitignore file"
- type="boolean"
- default="true" />
-
- <parameter
- id="enableProGuard"
- name="Enable ProGuard"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/gradle-projects/NewAndroidModule/template_new_project.png b/base/templates/gradle-projects/NewAndroidModule/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/NewAndroidModule/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle-projects/NewAndroidProject/globals.xml.ftl b/base/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
deleted file mode 100644
index da1c39a..0000000
--- a/base/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="topOut" value="." />
- <global id="mavenUrl" value="mavenCentral" />
-</globals>
diff --git a/base/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl b/base/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
deleted file mode 100644
index 316065f..0000000
--- a/base/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/build.gradle" />
-
-<#if makeIgnore>
- <copy from="project_ignore"
- to="${escapeXmlAttribute(topOut)}/.gitignore" />
-</#if>
-
- <instantiate from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
-
- <instantiate from="gradle.properties.ftl"
- to="${escapeXmlAttribute(topOut)}/gradle.properties" />
-
- <copy from="${templateRoot}/gradle/wrapper"
- to="${escapeXmlAttribute(topOut)}/" />
-
-<#if sdkDir??>
- <instantiate from="local.properties.ftl"
- to="${escapeXmlAttribute(topOut)}/local.properties" />
-</#if>
-</recipe>
diff --git a/base/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl b/base/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
deleted file mode 100644
index 5ac9a20..0000000
--- a/base/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
+++ /dev/null
@@ -1,29 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- jcenter()
-<#if mavenUrl != "mavenCentral">
- maven {
- url '${mavenUrl}'
- }
-</#if>
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- jcenter()
-<#if mavenUrl != "mavenCentral">
- maven {
- url '${mavenUrl}'
- }
-</#if>
- }
-}
diff --git a/base/templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl b/base/templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl
deleted file mode 100644
index 1d3591c..0000000
--- a/base/templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl
+++ /dev/null
@@ -1,18 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-# Default value: -Xmx10248m -XX:MaxPermSize=256m
-# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
diff --git a/base/templates/gradle-projects/NewAndroidProject/root/local.properties.ftl b/base/templates/gradle-projects/NewAndroidProject/root/local.properties.ftl
deleted file mode 100644
index 9dcbf9b..0000000
--- a/base/templates/gradle-projects/NewAndroidProject/root/local.properties.ftl
+++ /dev/null
@@ -1,10 +0,0 @@
-## This file is automatically generated by Android Studio.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file should *NOT* be checked into Version Control Systems,
-# as it contains information specific to your local configuration.
-#
-# Location of the SDK. This is only used by Gradle.
-# For customization when using a Version Control System, please read the
-# header note.
-sdk.dir=${escapePropertyValue(sdkDir)}
\ No newline at end of file
diff --git a/base/templates/gradle-projects/NewAndroidProject/root/project_ignore b/base/templates/gradle-projects/NewAndroidProject/root/project_ignore
deleted file mode 100644
index 9c4de58..0000000
--- a/base/templates/gradle-projects/NewAndroidProject/root/project_ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-.gradle
-/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
-/build
-/captures
diff --git a/base/templates/gradle-projects/NewAndroidProject/template.xml b/base/templates/gradle-projects/NewAndroidProject/template.xml
deleted file mode 100644
index d7c0430..0000000
--- a/base/templates/gradle-projects/NewAndroidProject/template.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Android Project"
- description="Creates a new Android project.">
-
- <category value="Application" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <parameter
- id="makeIgnore"
- name="Create .gitignore file"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/gradle-projects/NewAndroidProject/template_new_project.png b/base/templates/gradle-projects/NewAndroidProject/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/NewAndroidProject/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/recipe.xml.ftl b/base/templates/gradle-projects/NewAndroidTVModule/recipe.xml.ftl
deleted file mode 100644
index 1f0c822..0000000
--- a/base/templates/gradle-projects/NewAndroidTVModule/recipe.xml.ftl
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:recyclerview-v7:${targetApi}.+" />
- <dependency mavenUrl="com.android.support:leanback-v17:${targetApi}.+" />
- <mkdir at="${escapeXmlAttribute(srcOut)}" />
-
- <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
-
- <merge from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<#if copyIcons>
- <mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
- <copy from="res/mipmap-hdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
- <copy from="res/mipmap-mdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
- <copy from="res/mipmap-xhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
- <copy from="res/mipmap-xxhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
-</#if>
-<#if makeIgnore>
- <copy from="module_ignore"
- to="${escapeXmlAttribute(projectOut)}/.gitignore" />
-</#if>
-<#if enableProGuard>
- <instantiate from="proguard-rules.txt.ftl"
- to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
-</#if>
- <instantiate from="res/values/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="test/app_package/ApplicationTest.java.ftl"
- to="${testOut}/ApplicationTest.java" />
-
-</recipe>
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/AndroidManifest.xml.ftl b/base/templates/gradle-projects/NewAndroidTVModule/root/AndroidManifest.xml.ftl
deleted file mode 100644
index e793724..0000000
--- a/base/templates/gradle-projects/NewAndroidTVModule/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="${packageName}">
-
- <application android:allowBackup="true"
- android:label="@string/app_name"<#if copyIcons>
- android:icon="@mipmap/ic_launcher"<#else>
- android:icon="@drawable/${assetName}"</#if>
- android:theme="@style/Theme.Leanback">
-
- </application>
-
-</manifest>
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/template.xml b/base/templates/gradle-projects/NewAndroidTVModule/template.xml
deleted file mode 100644
index 19995a8..0000000
--- a/base/templates/gradle-projects/NewAndroidTVModule/template.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Android TV Module"
- description="Creates a new Android TV module."
- minApi="21"
- minBuildApi="21">
-
- <category value="Application" />
-
- <formfactor value="TV" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="app_package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Module title"
- type="string"
- constraints="nonempty"
- default="My Module" />
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"/>
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="21" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"/>
-
- <!--
- Usually the same as targetApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="targetApiString"
- name="Target API"
- type="string"
- constraints="apilevel"/>
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"/>
-
- <!--
- Usually the same as buildApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="buildApiString"
- name="Build API level"
- type="string"
- constraints="apilevel" />
-
- <parameter
- id="copyIcons"
- name="Include launcher icons"
- type="boolean"
- default="true" />
-
- <parameter
- id="makeIgnore"
- name="Create .gitignore file"
- type="boolean"
- default="true" />
-
- <parameter
- id="enableProGuard"
- name="Enable ProGuard"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/template_new_project.png b/base/templates/gradle-projects/NewAndroidTVModule/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/NewAndroidTVModule/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle-projects/NewGlassModule/recipe.xml.ftl b/base/templates/gradle-projects/NewGlassModule/recipe.xml.ftl
deleted file mode 100644
index 4896c9d..0000000
--- a/base/templates/gradle-projects/NewGlassModule/recipe.xml.ftl
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <mkdir at="${escapeXmlAttribute(srcOut)}" />
-
- <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
-
- <merge from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<#if copyIcons>
- <mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
- <copy from="res/mipmap-hdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
- <copy from="res/mipmap-mdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
- <copy from="res/mipmap-xhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
- <copy from="res/mipmap-xxhdpi"
- to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
-</#if>
-<#if makeIgnore>
- <copy from="module_ignore"
- to="${escapeXmlAttribute(projectOut)}/.gitignore" />
-</#if>
-<#if enableProGuard>
- <instantiate from="proguard-rules.txt.ftl"
- to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
-</#if>
- <instantiate from="res/values/styles.xml"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="test/app_package/ApplicationTest.java.ftl"
- to="${testOut}/ApplicationTest.java" />
-
-</recipe>
diff --git a/base/templates/gradle-projects/NewGlassModule/root/AndroidManifest.xml.ftl b/base/templates/gradle-projects/NewGlassModule/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 1f5b625..0000000
--- a/base/templates/gradle-projects/NewGlassModule/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="${packageName}">
-
- <application android:allowBackup="true"
- android:label="@string/app_name"<#if copyIcons>
- android:icon="@mipmap/ic_launcher"<#else>
- android:icon="@drawable/${assetName}"</#if>
- android:theme="@style/AppTheme">
-
- </application>
-
-</manifest>
diff --git a/base/templates/gradle-projects/NewGlassModule/template.xml b/base/templates/gradle-projects/NewGlassModule/template.xml
deleted file mode 100644
index d4d9d3e..0000000
--- a/base/templates/gradle-projects/NewGlassModule/template.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Glass Module"
- description="Creates a new Glass module."
- minApi="19"
- minBuildApi="19">
-
- <category value="Application" />
-
- <formfactor value="Glass" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="app_package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Module title"
- type="string"
- constraints="nonempty"
- default="My Module" />
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"/>
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="21" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"/>
-
- <!--
- Usually the same as targetApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="targetApiString"
- name="Target API"
- type="string"
- constraints="apilevel"/>
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"/>
-
- <!--
- Usually the same as buildApi, but when targeting a preview platform this is the code name instead
- -->
- <parameter
- id="buildApiString"
- name="Build API level"
- type="string"
- constraints="apilevel" />
-
- <parameter
- id="copyIcons"
- name="Include launcher icons"
- type="boolean"
- default="true" />
-
- <parameter
- id="makeIgnore"
- name="Create .gitignore file"
- type="boolean"
- default="true" />
-
- <parameter
- id="enableProGuard"
- name="Enable ProGuard"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/gradle-projects/NewGlassModule/template_new_project.png b/base/templates/gradle-projects/NewGlassModule/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/NewGlassModule/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl b/base/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
deleted file mode 100644
index 0a92c5e..0000000
--- a/base/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="src/library_package/Placeholder.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-<#if makeIgnore>
- <copy from="gitignore"
- to="${escapeXmlAttribute(projectOut)}/.gitignore" />
-</#if>
-
- <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
-</recipe>
diff --git a/base/templates/gradle-projects/NewJavaLibrary/root/settings.gradle.ftl b/base/templates/gradle-projects/NewJavaLibrary/root/settings.gradle.ftl
deleted file mode 100644
index b12004b..0000000
--- a/base/templates/gradle-projects/NewJavaLibrary/root/settings.gradle.ftl
+++ /dev/null
@@ -1 +0,0 @@
-include ':${projectName}'
diff --git a/base/templates/gradle-projects/NewJavaLibrary/template.xml b/base/templates/gradle-projects/NewJavaLibrary/template.xml
deleted file mode 100644
index 9cda96e..0000000
--- a/base/templates/gradle-projects/NewJavaLibrary/template.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="4"
- name="Java Library"
- description="Creates a new Java library.">
-
- <category value="Application" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <parameter
- id="projectName"
- name="Library name"
- type="string"
- constraints="nonempty|module|unique"
- default="lib"/>
-
- <parameter
- id="packageName"
- name="Java package name"
- type="string"
- constraints="nonempty|package"
- default="com.example"/>
-
- <parameter
- id="className"
- name="Java class name"
- type="string"
- constraints="nonempty|class"
- default="MyClass"/>
-
- <parameter
- id="makeIgnore"
- name="Create .gitignore file"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/gradle-projects/NewJavaLibrary/template_new_project.png b/base/templates/gradle-projects/NewJavaLibrary/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
Binary files a/base/templates/gradle-projects/NewJavaLibrary/template_new_project.png and /dev/null differ
diff --git a/base/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties b/base/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 0c71e76..0000000
--- a/base/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Wed Apr 10 15:27:10 PDT 2013
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/base/templates/gradle/wrapper/gradlew b/base/templates/gradle/wrapper/gradlew
deleted file mode 100755
index 91a7e26..0000000
--- a/base/templates/gradle/wrapper/gradlew
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/base/templates/other/AidlFile/recipe.xml.ftl b/base/templates/other/AidlFile/recipe.xml.ftl
deleted file mode 100644
index ef6e31c..0000000
--- a/base/templates/other/AidlFile/recipe.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <instantiate from="src/app_package/interface.aidl.ftl"
- to="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
- <open file="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
-</recipe>
diff --git a/base/templates/other/AidlFile/template.xml b/base/templates/other/AidlFile/template.xml
deleted file mode 100644
index bd42e84..0000000
--- a/base/templates/other/AidlFile/template.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="AIDL File"
- description="Creates a new Android Interface Description Language file."
- >
-
- <category value="AIDL" />
-
- <parameter
- id="interfaceName"
- name="Interface Name"
- type="string"
- constraints="class|unique|nonempty"
- default="IMyAidlInterface"
- help="Name of the Interface." />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/AidlFolder/recipe.xml.ftl b/base/templates/other/AidlFolder/recipe.xml.ftl
deleted file mode 100644
index 93eef52..0000000
--- a/base/templates/other/AidlFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/aidl/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/AidlFolder/template.xml b/base/templates/other/AidlFolder/template.xml
deleted file mode 100644
index 6155f0e..0000000
--- a/base/templates/other/AidlFolder/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="AIDL Folder"
- description="Creates a source root for Android Interface Description Language files."
- >
-
- <category value="Folder" />
-
- <parameter
- id="remapFolder"
- name="Change Folder Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the folder location to another folder within the module." />
-
- <parameter
- id="newLocation"
- name="New Folder Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/aidl/"
- help="The location for the new folder"
- visibility="remapFolder" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/AndroidAutoMediaService/globals.xml.ftl b/base/templates/other/AndroidAutoMediaService/globals.xml.ftl
deleted file mode 100644
index 4bf836f..0000000
--- a/base/templates/other/AndroidAutoMediaService/globals.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/other/AndroidAutoMediaService/recipe.xml.ftl b/base/templates/other/AndroidAutoMediaService/recipe.xml.ftl
deleted file mode 100644
index 061d7ba..0000000
--- a/base/templates/other/AndroidAutoMediaService/recipe.xml.ftl
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <#if useCustomTheme>
- <merge from="res/values-v21/styles.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-v21/styles.xml" />
- </#if>
-
- <copy from="res/xml/automotive_app_desc.xml"
- to="${escapeXmlAttribute(resOut)}/xml/automotive_app_desc.xml" />
-
- <instantiate from="src/app_package/MusicService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${mediaBrowserServiceName}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${mediaBrowserServiceName}.java" />
-
- <#if useCustomTheme>
- <open file="${escapeXmlAttribute(resOut)}/values-v21/styles.xml" />
- </#if>
-</recipe>
diff --git a/base/templates/other/AndroidAutoMediaService/template.xml b/base/templates/other/AndroidAutoMediaService/template.xml
deleted file mode 100644
index 08d6fa3..0000000
--- a/base/templates/other/AndroidAutoMediaService/template.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Media service"
- description="Create a MediaBrowserService and adds the required metadata for Android Auto"
- minApi="21">
-
- <category value="Android Auto" />
- <formfactor value="Car" />
-
- <parameter
- id="mediaBrowserServiceName"
- name="Class name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyMusicService"
- help="The name of the service that will extend MediaBrowserService and contain the logic to browse and playback media" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- help="The package where the media service will be created" />
-
- <parameter
- id="useCustomTheme"
- name="Use a custom theme for Android Auto colors?"
- type="boolean"
- constraints="package"
- help="Android Auto apps can define a different set of colors that will be used exclusively when running on Android Auto" />
-
- <parameter
- id="customThemeName"
- name="Android Auto custom theme name"
- type="String"
- default="CarTheme"
- visibility="useCustomTheme"
- constraints="nonempty"/>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
- <thumbs>
- <thumb>templates-mediaService-Auto.png</thumb>
- </thumbs>
-
-</template>
diff --git a/base/templates/other/AndroidAutoMediaService/templates-mediaService-Auto.png b/base/templates/other/AndroidAutoMediaService/templates-mediaService-Auto.png
deleted file mode 100644
index 48c4ef5..0000000
Binary files a/base/templates/other/AndroidAutoMediaService/templates-mediaService-Auto.png and /dev/null differ
diff --git a/base/templates/other/AndroidAutoMessagingService/globals.xml.ftl b/base/templates/other/AndroidAutoMessagingService/globals.xml.ftl
deleted file mode 100644
index 4bf836f..0000000
--- a/base/templates/other/AndroidAutoMessagingService/globals.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/other/AndroidAutoMessagingService/recipe.xml.ftl b/base/templates/other/AndroidAutoMessagingService/recipe.xml.ftl
deleted file mode 100644
index dddb1e5..0000000
--- a/base/templates/other/AndroidAutoMessagingService/recipe.xml.ftl
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <dependency mavenUrl="com.android.support:support-v4:21.0.2"/>
- <dependency mavenUrl="com.android.support:support-v13:21.0.2"/>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <copy from="res/xml/automotive_app_desc.xml"
- to="${escapeXmlAttribute(resOut)}/xml/automotive_app_desc.xml" />
-
- <instantiate from="src/app_package/MessagingService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${serviceName}.java" />
-
- <instantiate from="src/app_package/MessageReadReceiver.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${readReceiverName}.java" />
-
- <instantiate from="src/app_package/MessageReplyReceiver.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${replyReceiverName}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${serviceName}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${readReceiverName}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${replyReceiverName}.java" />
-</recipe>
diff --git a/base/templates/other/AndroidAutoMessagingService/template.xml b/base/templates/other/AndroidAutoMessagingService/template.xml
deleted file mode 100644
index 3412a8e..0000000
--- a/base/templates/other/AndroidAutoMessagingService/template.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Messaging service"
- description="Create a service that sends notifications compatible with Android Auto"
- minApi="21">
-
- <category value="Android Auto" />
- <formfactor value="Car" />
-
- <parameter
- id="serviceName"
- name="Service class name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyMessagingService"
- help="The name of the service that will handle incoming messages and send corresponding notifications." />
-
- <parameter
- id="readReceiverName"
- name="Read receiver class name"
- type="string"
- constraints="class|unique|nonempty"
- default="MessageReadReceiver"
- help="The broadcast receiver that will handle Read notifications." />
-
- <parameter
- id="replyReceiverName"
- name="Reply receiver class name"
- type="string"
- constraints="class|unique|nonempty"
- default="MessageReplyReceiver"
- help="The broadcast receiver that will handle Reply notifications." />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- help="The package where the messaging service will be created." />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
- <thumbs>
- <thumb>templates-messagingService-Auto.png</thumb>
- </thumbs>
-
-</template>
diff --git a/base/templates/other/AndroidAutoMessagingService/templates-messagingService-Auto.png b/base/templates/other/AndroidAutoMessagingService/templates-messagingService-Auto.png
deleted file mode 100644
index 02f611a..0000000
Binary files a/base/templates/other/AndroidAutoMessagingService/templates-messagingService-Auto.png and /dev/null differ
diff --git a/base/templates/other/AndroidManifest/recipe.xml.ftl b/base/templates/other/AndroidManifest/recipe.xml.ftl
deleted file mode 100644
index 747c6b3..0000000
--- a/base/templates/other/AndroidManifest/recipe.xml.ftl
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFile>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/AndroidManifest/template.xml b/base/templates/other/AndroidManifest/template.xml
deleted file mode 100644
index afc0aaa..0000000
--- a/base/templates/other/AndroidManifest/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Android Manifest File"
- description="Creates an Android Manifest XML File."
- >
-
- <category value="Other" />
-
- <parameter
- id="remapFile"
- name="Change File Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the file location to another destination within the module." />
-
- <parameter
- id="newLocation"
- name="New File Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/AndroidManifest.xml"
- help="The location for the new file"
- visibility="remapFile" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/AppWidget/recipe.xml.ftl b/base/templates/other/AppWidget/recipe.xml.ftl
deleted file mode 100644
index 5810eae..0000000
--- a/base/templates/other/AppWidget/recipe.xml.ftl
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <copy from="res/drawable-nodpi/example_appwidget_preview.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_appwidget_preview.png" />
- <instantiate from="res/layout/appwidget.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${class_name}.xml" />
-
- <#if configurable>
- <instantiate from="res/layout/appwidget_configure.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${class_name}_configure.xml" />
- </#if>
-
- <instantiate from="res/xml/appwidget_info.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/xml/${class_name}_info.xml" />
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
- <merge from="res/values-v14/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-v14/dimens.xml" />
- <merge from="res/values/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
-
- <instantiate from="src/app_package/AppWidget.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <#if configurable>
- <instantiate from="src/app_package/AppWidgetConfigureActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}ConfigureActivity.java" />
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl b/base/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl
deleted file mode 100644
index d273f19..0000000
--- a/base/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl
+++ /dev/null
@@ -1,65 +0,0 @@
-package ${packageName};
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.Context;
-import android.widget.RemoteViews;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * Implementation of App Widget functionality.
-<#if configurable>
- * App Widget Configuration implemented in {@link ${className}ConfigureActivity ${className}ConfigureActivity}
-</#if>
- */
-public class ${className} extends AppWidgetProvider {
-
- @Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- // There may be multiple widgets active, so update all of them
- final int N = appWidgetIds.length;
- for (int i=0; i<N; i++) {
- updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
- }
- }
-
-<#if configurable>
- @Override
- public void onDeleted(Context context, int[] appWidgetIds) {
- // When the user deletes the widget, delete the preference associated with it.
- final int N = appWidgetIds.length;
- for (int i=0; i<N; i++) {
- ${className}ConfigureActivity.deleteTitlePref(context, appWidgetIds[i]);
- }
- }
-</#if>
-
- @Override
- public void onEnabled(Context context) {
- // Enter relevant functionality for when the first widget is created
- }
-
- @Override
- public void onDisabled(Context context) {
- // Enter relevant functionality for when the last widget is disabled
- }
-
- static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
- int appWidgetId) {
-
-<#if configurable>
- CharSequence widgetText = ${className}ConfigureActivity.loadTitlePref(context, appWidgetId);
-<#else>
- CharSequence widgetText = context.getString(R.string.appwidget_text);
-</#if>
- // Construct the RemoteViews object
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.${class_name});
- views.setTextViewText(R.id.appwidget_text, widgetText);
-
- // Instruct the widget manager to update the widget
- appWidgetManager.updateAppWidget(appWidgetId, views);
- }
-}
-
diff --git a/base/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl b/base/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
deleted file mode 100644
index c51f367..0000000
--- a/base/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
+++ /dev/null
@@ -1,103 +0,0 @@
-package ${packageName};
-
-import android.app.Activity;
-import android.appwidget.AppWidgetManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.EditText;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * The configuration screen for the {@link ${className} ${className}} AppWidget.
- */
-public class ${className}ConfigureActivity extends Activity {
-
- int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
- EditText mAppWidgetText;
- private static final String PREFS_NAME = "${packageName}.${className}";
- private static final String PREF_PREFIX_KEY = "appwidget_";
-
- public ${className}ConfigureActivity() {
- super();
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- // Set the result to CANCELED. This will cause the widget host to cancel
- // out of the widget placement if the user presses the back button.
- setResult(RESULT_CANCELED);
-
- setContentView(R.layout.${class_name}_configure);
- mAppWidgetText = (EditText)findViewById(R.id.appwidget_text);
- findViewById(R.id.add_button).setOnClickListener(mOnClickListener);
-
- // Find the widget id from the intent.
- Intent intent = getIntent();
- Bundle extras = intent.getExtras();
- if (extras != null) {
- mAppWidgetId = extras.getInt(
- AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- }
-
- // If this activity was started with an intent without an app widget ID, finish with an error.
- if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
- finish();
- return;
- }
-
- mAppWidgetText.setText(loadTitlePref(${className}ConfigureActivity.this, mAppWidgetId));
- }
-
- View.OnClickListener mOnClickListener = new View.OnClickListener() {
- public void onClick(View v) {
- final Context context = ${className}ConfigureActivity.this;
-
- // When the button is clicked, store the string locally
- String widgetText = mAppWidgetText.getText().toString();
- saveTitlePref(context,mAppWidgetId,widgetText);
-
- // It is the responsibility of the configuration activity to update the app widget
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- ${className}.updateAppWidget(context, appWidgetManager, mAppWidgetId);
-
- // Make sure we pass back the original appWidgetId
- Intent resultValue = new Intent();
- resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
- setResult(RESULT_OK, resultValue);
- finish();
- }
- };
-
- // Write the prefix to the SharedPreferences object for this widget
- static void saveTitlePref(Context context, int appWidgetId, String text) {
- SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
- prefs.putString(PREF_PREFIX_KEY + appWidgetId, text);
- prefs.commit();
- }
-
- // Read the prefix from the SharedPreferences object for this widget.
- // If there is no preference saved, get the default from a resource
- static String loadTitlePref(Context context, int appWidgetId) {
- SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
- String titleValue = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null);
- if (titleValue != null) {
- return titleValue;
- } else {
- return context.getString(R.string.appwidget_text);
- }
- }
-
- static void deleteTitlePref(Context context, int appWidgetId) {
- SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
- prefs.remove(PREF_PREFIX_KEY + appWidgetId);
- prefs.commit();
- }
-}
-
diff --git a/base/templates/other/AppWidget/template.xml b/base/templates/other/AppWidget/template.xml
deleted file mode 100644
index 13659b7..0000000
--- a/base/templates/other/AppWidget/template.xml
+++ /dev/null
@@ -1,148 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="App Widget"
- description="Creates a new App Widget"
- minApi="4"
- minBuildApi="16">
-
- <category value="Widget" />
-
- <parameter
- id="className"
- name="Class Name"
- type="string"
- constraints="class|unique|nonempty"
- default="NewAppWidget"
- help="The name of the App Widget to create" />
-
- <parameter
- id="placement"
- name="Placement"
- type="enum"
- default="homescreen"
- help="Make the widget available on the Home-screen and/or on the Keyguard. Keyguard placement is only supported in Android 4.2 and above; this setting is ignored on earlier versions and defaults to Home-screen.">
- <option id="both">Home-screen and Keyguard</option>
- <option id="homescreen">Home-screen only</option>
- <option id="keyguard" >Keyguard only (API 17+)</option>
- </parameter>
-
- <parameter
- id="resizable"
- name="Resizable (API 12+)"
- type="enum"
- default="both"
- help="Allow the user to resize the widget. Feature only available on Android 3.1 and above.">
- <option id="both">Horizontally and vertically</option>
- <option id="horizontal">Only horizontally</option>
- <option id="vertical" >Only vertically</option>
- <option id="none">Not resizable</option>
- </parameter>
-
- <parameter
- id="minWidth"
- name="Minimum Width (cells)"
- type="enum"
- default="1">
- <option id="1">1</option>
- <option id="2" >2</option>
- <option id="3" >3</option>
- <option id="4" >4</option>
- </parameter>
-
- <parameter
- id="minHeight"
- name="Minimum Height (cells)"
- type="enum"
- default="1">
- <option id="1">1</option>
- <option id="2" >2</option>
- <option id="3" >3</option>
- <option id="4" >4</option>
- </parameter>
-
- <parameter
- id="configurable"
- name="Configuration Screen"
- type="boolean"
- default="false"
- help="Generates a widget configuration activity" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-
- <thumbs>
- <thumb>thumbs/template_widget_3x3_vh.png</thumb>
-
- <thumb minWidth="1" minHeight="1" resizable="none" >thumbs/template_widget_1x1.png</thumb>
- <thumb minWidth="2" minHeight="1" resizable="none" >thumbs/template_widget_2x1.png</thumb>
- <thumb minWidth="3" minHeight="1" resizable="none" >thumbs/template_widget_3x1.png</thumb>
- <thumb minWidth="4" minHeight="1" resizable="none" >thumbs/template_widget_4x1.png</thumb>
- <thumb minWidth="1" minHeight="2" resizable="none" >thumbs/template_widget_1x2.png</thumb>
- <thumb minWidth="2" minHeight="2" resizable="none" >thumbs/template_widget_2x2.png</thumb>
- <thumb minWidth="3" minHeight="2" resizable="none" >thumbs/template_widget_3x2.png</thumb>
- <thumb minWidth="4" minHeight="2" resizable="none" >thumbs/template_widget_4x2.png</thumb>
- <thumb minWidth="1" minHeight="3" resizable="none" >thumbs/template_widget_1x3.png</thumb>
- <thumb minWidth="2" minHeight="3" resizable="none" >thumbs/template_widget_2x3.png</thumb>
- <thumb minWidth="3" minHeight="3" resizable="none" >thumbs/template_widget_3x3.png</thumb>
- <thumb minWidth="4" minHeight="3" resizable="none" >thumbs/template_widget_4x3.png</thumb>
- <thumb minWidth="1" minHeight="4" resizable="none" >thumbs/template_widget_1x4.png</thumb>
- <thumb minWidth="2" minHeight="4" resizable="none" >thumbs/template_widget_2x4.png</thumb>
- <thumb minWidth="3" minHeight="4" resizable="none" >thumbs/template_widget_3x4.png</thumb>
- <thumb minWidth="4" minHeight="4" resizable="none" >thumbs/template_widget_4x4.png</thumb>
-
- <thumb minWidth="1" minHeight="1" resizable="horizontal">thumbs/template_widget_1x1_h.png</thumb>
- <thumb minWidth="2" minHeight="1" resizable="horizontal">thumbs/template_widget_2x1_h.png</thumb>
- <thumb minWidth="3" minHeight="1" resizable="horizontal">thumbs/template_widget_3x1_h.png</thumb>
- <thumb minWidth="4" minHeight="1" resizable="horizontal">thumbs/template_widget_4x1_h.png</thumb>
- <thumb minWidth="1" minHeight="2" resizable="horizontal">thumbs/template_widget_1x2_h.png</thumb>
- <thumb minWidth="2" minHeight="2" resizable="horizontal">thumbs/template_widget_2x2_h.png</thumb>
- <thumb minWidth="3" minHeight="2" resizable="horizontal">thumbs/template_widget_3x2_h.png</thumb>
- <thumb minWidth="4" minHeight="2" resizable="horizontal">thumbs/template_widget_4x2_h.png</thumb>
- <thumb minWidth="1" minHeight="3" resizable="horizontal">thumbs/template_widget_1x3_h.png</thumb>
- <thumb minWidth="2" minHeight="3" resizable="horizontal">thumbs/template_widget_2x3_h.png</thumb>
- <thumb minWidth="3" minHeight="3" resizable="horizontal">thumbs/template_widget_3x3_h.png</thumb>
- <thumb minWidth="4" minHeight="3" resizable="horizontal">thumbs/template_widget_4x3_h.png</thumb>
- <thumb minWidth="1" minHeight="4" resizable="horizontal">thumbs/template_widget_1x4_h.png</thumb>
- <thumb minWidth="2" minHeight="4" resizable="horizontal">thumbs/template_widget_2x4_h.png</thumb>
- <thumb minWidth="3" minHeight="4" resizable="horizontal">thumbs/template_widget_3x4_h.png</thumb>
- <thumb minWidth="4" minHeight="4" resizable="horizontal">thumbs/template_widget_4x4_h.png</thumb>
-
- <thumb minWidth="1" minHeight="1" resizable="vertical" >thumbs/template_widget_1x1_v.png</thumb>
- <thumb minWidth="2" minHeight="1" resizable="vertical" >thumbs/template_widget_2x1_v.png</thumb>
- <thumb minWidth="3" minHeight="1" resizable="vertical" >thumbs/template_widget_3x1_v.png</thumb>
- <thumb minWidth="4" minHeight="1" resizable="vertical" >thumbs/template_widget_4x1_v.png</thumb>
- <thumb minWidth="1" minHeight="2" resizable="vertical" >thumbs/template_widget_1x2_v.png</thumb>
- <thumb minWidth="2" minHeight="2" resizable="vertical" >thumbs/template_widget_2x2_v.png</thumb>
- <thumb minWidth="3" minHeight="2" resizable="vertical" >thumbs/template_widget_3x2_v.png</thumb>
- <thumb minWidth="4" minHeight="2" resizable="vertical" >thumbs/template_widget_4x2_v.png</thumb>
- <thumb minWidth="1" minHeight="3" resizable="vertical" >thumbs/template_widget_1x3_v.png</thumb>
- <thumb minWidth="2" minHeight="3" resizable="vertical" >thumbs/template_widget_2x3_v.png</thumb>
- <thumb minWidth="3" minHeight="3" resizable="vertical" >thumbs/template_widget_3x3_v.png</thumb>
- <thumb minWidth="4" minHeight="3" resizable="vertical" >thumbs/template_widget_4x3_v.png</thumb>
- <thumb minWidth="1" minHeight="4" resizable="vertical" >thumbs/template_widget_1x4_v.png</thumb>
- <thumb minWidth="2" minHeight="4" resizable="vertical" >thumbs/template_widget_2x4_v.png</thumb>
- <thumb minWidth="3" minHeight="4" resizable="vertical" >thumbs/template_widget_3x4_v.png</thumb>
- <thumb minWidth="4" minHeight="4" resizable="vertical" >thumbs/template_widget_4x4_v.png</thumb>
-
- <thumb minWidth="1" minHeight="1" resizable="both" >thumbs/template_widget_1x1_vh.png</thumb>
- <thumb minWidth="2" minHeight="1" resizable="both" >thumbs/template_widget_2x1_vh.png</thumb>
- <thumb minWidth="3" minHeight="1" resizable="both" >thumbs/template_widget_3x1_vh.png</thumb>
- <thumb minWidth="4" minHeight="1" resizable="both" >thumbs/template_widget_4x1_vh.png</thumb>
- <thumb minWidth="1" minHeight="2" resizable="both" >thumbs/template_widget_1x2_vh.png</thumb>
- <thumb minWidth="2" minHeight="2" resizable="both" >thumbs/template_widget_2x2_vh.png</thumb>
- <thumb minWidth="3" minHeight="2" resizable="both" >thumbs/template_widget_3x2_vh.png</thumb>
- <thumb minWidth="4" minHeight="2" resizable="both" >thumbs/template_widget_4x2_vh.png</thumb>
- <thumb minWidth="1" minHeight="3" resizable="both" >thumbs/template_widget_1x3_vh.png</thumb>
- <thumb minWidth="2" minHeight="3" resizable="both" >thumbs/template_widget_2x3_vh.png</thumb>
- <thumb minWidth="3" minHeight="3" resizable="both" >thumbs/template_widget_3x3_vh.png</thumb>
- <thumb minWidth="4" minHeight="3" resizable="both" >thumbs/template_widget_4x3_vh.png</thumb>
- <thumb minWidth="1" minHeight="4" resizable="both" >thumbs/template_widget_1x4_vh.png</thumb>
- <thumb minWidth="2" minHeight="4" resizable="both" >thumbs/template_widget_2x4_vh.png</thumb>
- <thumb minWidth="3" minHeight="4" resizable="both" >thumbs/template_widget_3x4_vh.png</thumb>
- <thumb minWidth="4" minHeight="4" resizable="both" >thumbs/template_widget_4x4_vh.png</thumb>
- </thumbs>
-
-</template>
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x1.png b/base/templates/other/AppWidget/thumbs/template_widget_1x1.png
deleted file mode 100644
index 8b34a24..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x1.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x1_h.png b/base/templates/other/AppWidget/thumbs/template_widget_1x1_h.png
deleted file mode 100644
index 38ce687..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x1_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x1_v.png b/base/templates/other/AppWidget/thumbs/template_widget_1x1_v.png
deleted file mode 100644
index 0aedac7..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x1_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x1_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_1x1_vh.png
deleted file mode 100644
index 301ee0f..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x1_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x2.png b/base/templates/other/AppWidget/thumbs/template_widget_1x2.png
deleted file mode 100644
index 0e4181d..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x2.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x2_h.png b/base/templates/other/AppWidget/thumbs/template_widget_1x2_h.png
deleted file mode 100644
index 37f3b94..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x2_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x2_v.png b/base/templates/other/AppWidget/thumbs/template_widget_1x2_v.png
deleted file mode 100644
index 2d13903..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x2_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x2_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_1x2_vh.png
deleted file mode 100644
index 431f929..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x2_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x3.png b/base/templates/other/AppWidget/thumbs/template_widget_1x3.png
deleted file mode 100644
index b0fb55a..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x3.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x3_h.png b/base/templates/other/AppWidget/thumbs/template_widget_1x3_h.png
deleted file mode 100644
index 14fdc46..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x3_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x3_v.png b/base/templates/other/AppWidget/thumbs/template_widget_1x3_v.png
deleted file mode 100644
index 136b8de..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x3_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x3_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_1x3_vh.png
deleted file mode 100644
index 5e18856..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x3_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x4.png b/base/templates/other/AppWidget/thumbs/template_widget_1x4.png
deleted file mode 100644
index 2922d34..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x4.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x4_h.png b/base/templates/other/AppWidget/thumbs/template_widget_1x4_h.png
deleted file mode 100644
index 462c802..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x4_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x4_v.png b/base/templates/other/AppWidget/thumbs/template_widget_1x4_v.png
deleted file mode 100644
index f239e73..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x4_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_1x4_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_1x4_vh.png
deleted file mode 100644
index b05e168..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_1x4_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x1.png b/base/templates/other/AppWidget/thumbs/template_widget_2x1.png
deleted file mode 100644
index 9e14ef8..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x1.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x1_h.png b/base/templates/other/AppWidget/thumbs/template_widget_2x1_h.png
deleted file mode 100644
index 3a8019e..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x1_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x1_v.png b/base/templates/other/AppWidget/thumbs/template_widget_2x1_v.png
deleted file mode 100644
index d09ff28..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x1_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x1_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_2x1_vh.png
deleted file mode 100644
index b6093e1..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x1_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x2.png b/base/templates/other/AppWidget/thumbs/template_widget_2x2.png
deleted file mode 100644
index 2894704..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x2.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x2_h.png b/base/templates/other/AppWidget/thumbs/template_widget_2x2_h.png
deleted file mode 100644
index a2ab77c..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x2_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x2_v.png b/base/templates/other/AppWidget/thumbs/template_widget_2x2_v.png
deleted file mode 100644
index c09f1f7..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x2_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x2_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_2x2_vh.png
deleted file mode 100644
index 21becb2..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x2_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x3.png b/base/templates/other/AppWidget/thumbs/template_widget_2x3.png
deleted file mode 100644
index 3226127..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x3.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x3_h.png b/base/templates/other/AppWidget/thumbs/template_widget_2x3_h.png
deleted file mode 100644
index db2037a..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x3_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x3_v.png b/base/templates/other/AppWidget/thumbs/template_widget_2x3_v.png
deleted file mode 100644
index af21176..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x3_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x3_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_2x3_vh.png
deleted file mode 100644
index e0edfb4..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x3_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x4.png b/base/templates/other/AppWidget/thumbs/template_widget_2x4.png
deleted file mode 100644
index dfcda22..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x4.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x4_h.png b/base/templates/other/AppWidget/thumbs/template_widget_2x4_h.png
deleted file mode 100644
index dc21139..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x4_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x4_v.png b/base/templates/other/AppWidget/thumbs/template_widget_2x4_v.png
deleted file mode 100644
index 6bfc884..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x4_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_2x4_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_2x4_vh.png
deleted file mode 100644
index 922aeee..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_2x4_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x1.png b/base/templates/other/AppWidget/thumbs/template_widget_3x1.png
deleted file mode 100644
index bb394b9..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x1.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x1_h.png b/base/templates/other/AppWidget/thumbs/template_widget_3x1_h.png
deleted file mode 100644
index 47b19c3..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x1_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x1_v.png b/base/templates/other/AppWidget/thumbs/template_widget_3x1_v.png
deleted file mode 100644
index 5575850..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x1_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x1_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_3x1_vh.png
deleted file mode 100644
index c4a5f3f..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x1_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x2.png b/base/templates/other/AppWidget/thumbs/template_widget_3x2.png
deleted file mode 100644
index 200fba4..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x2.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x2_h.png b/base/templates/other/AppWidget/thumbs/template_widget_3x2_h.png
deleted file mode 100644
index b027430..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x2_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x2_v.png b/base/templates/other/AppWidget/thumbs/template_widget_3x2_v.png
deleted file mode 100644
index b350ae8..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x2_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x2_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_3x2_vh.png
deleted file mode 100644
index 129b706..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x2_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x3.png b/base/templates/other/AppWidget/thumbs/template_widget_3x3.png
deleted file mode 100644
index 30dfb4b..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x3.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x3_h.png b/base/templates/other/AppWidget/thumbs/template_widget_3x3_h.png
deleted file mode 100644
index 9b062e9..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x3_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x3_v.png b/base/templates/other/AppWidget/thumbs/template_widget_3x3_v.png
deleted file mode 100644
index af8b494..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x3_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x3_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_3x3_vh.png
deleted file mode 100644
index bc92413..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x3_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x4.png b/base/templates/other/AppWidget/thumbs/template_widget_3x4.png
deleted file mode 100644
index 1759b62..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x4.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x4_h.png b/base/templates/other/AppWidget/thumbs/template_widget_3x4_h.png
deleted file mode 100644
index e09fa7e..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x4_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x4_v.png b/base/templates/other/AppWidget/thumbs/template_widget_3x4_v.png
deleted file mode 100644
index e6451fe..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x4_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_3x4_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_3x4_vh.png
deleted file mode 100644
index 376611f..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_3x4_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x1.png b/base/templates/other/AppWidget/thumbs/template_widget_4x1.png
deleted file mode 100644
index 75031c8..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x1.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x1_h.png b/base/templates/other/AppWidget/thumbs/template_widget_4x1_h.png
deleted file mode 100644
index 7a4b81f..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x1_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x1_v.png b/base/templates/other/AppWidget/thumbs/template_widget_4x1_v.png
deleted file mode 100644
index 2c8c604..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x1_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x1_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_4x1_vh.png
deleted file mode 100644
index 0b43cd7..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x1_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x2.png b/base/templates/other/AppWidget/thumbs/template_widget_4x2.png
deleted file mode 100644
index 8328141..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x2.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x2_h.png b/base/templates/other/AppWidget/thumbs/template_widget_4x2_h.png
deleted file mode 100644
index d83062c..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x2_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x2_v.png b/base/templates/other/AppWidget/thumbs/template_widget_4x2_v.png
deleted file mode 100644
index 561f47a..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x2_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x2_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_4x2_vh.png
deleted file mode 100644
index 132ccd1..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x2_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x3.png b/base/templates/other/AppWidget/thumbs/template_widget_4x3.png
deleted file mode 100644
index c3ea452..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x3.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x3_h.png b/base/templates/other/AppWidget/thumbs/template_widget_4x3_h.png
deleted file mode 100644
index 57d84ce..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x3_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x3_v.png b/base/templates/other/AppWidget/thumbs/template_widget_4x3_v.png
deleted file mode 100644
index e8d93fc..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x3_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x3_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_4x3_vh.png
deleted file mode 100644
index 71dfbb3..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x3_vh.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x4.png b/base/templates/other/AppWidget/thumbs/template_widget_4x4.png
deleted file mode 100644
index 340244f..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x4.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x4_h.png b/base/templates/other/AppWidget/thumbs/template_widget_4x4_h.png
deleted file mode 100644
index f20c14e..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x4_h.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x4_v.png b/base/templates/other/AppWidget/thumbs/template_widget_4x4_v.png
deleted file mode 100644
index d490c79..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x4_v.png and /dev/null differ
diff --git a/base/templates/other/AppWidget/thumbs/template_widget_4x4_vh.png b/base/templates/other/AppWidget/thumbs/template_widget_4x4_vh.png
deleted file mode 100644
index 52e1b4d..0000000
Binary files a/base/templates/other/AppWidget/thumbs/template_widget_4x4_vh.png and /dev/null differ
diff --git a/base/templates/other/AssetsFolder/recipe.xml.ftl b/base/templates/other/AssetsFolder/recipe.xml.ftl
deleted file mode 100644
index f2042bf..0000000
--- a/base/templates/other/AssetsFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/assets/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/AssetsFolder/template.xml b/base/templates/other/AssetsFolder/template.xml
deleted file mode 100644
index d659f88..0000000
--- a/base/templates/other/AssetsFolder/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Assets Folder"
- description="Creates a source root for assets which will be included in the APK."
- >
-
- <category value="Folder" />
-
- <parameter
- id="remapFolder"
- name="Change Folder Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the folder location to another folder within the module." />
-
- <parameter
- id="newLocation"
- name="New Folder Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/assets/"
- help="The location for the new folder"
- visibility="remapFolder" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/BlankFragment/globals.xml.ftl b/base/templates/other/BlankFragment/globals.xml.ftl
deleted file mode 100644
index e4cf72f..0000000
--- a/base/templates/other/BlankFragment/globals.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="useSupport" type="boolean" value="${(minApiLevel lt 11)?string}" />
- <global id="resOut" value="${resDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/other/BlankFragment/recipe.xml.ftl b/base/templates/other/BlankFragment/recipe.xml.ftl
deleted file mode 100644
index 67c4310..0000000
--- a/base/templates/other/BlankFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if useSupport><dependency mavenUrl="com.android.support:support-v4:${targetApi}.+"/></#if>
- <merge from="res/values/strings.xml" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <#if includeLayout>
- <instantiate from="res/layout/fragment_blank.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
-
- <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
- </#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <instantiate from="src/app_package/BlankFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl b/base/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
deleted file mode 100644
index 322f37f..0000000
--- a/base/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
+++ /dev/null
@@ -1,132 +0,0 @@
-package ${packageName};
-
-<#if includeCallbacks>import android.app.Activity;</#if>
-<#if includeCallbacks>import android.net.Uri;</#if>
-import android.os.Bundle;
-import android<#if useSupport>.support.v4</#if>.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-<#if !includeLayout>import android.widget.TextView;</#if>
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * A simple {@link Fragment} subclass.
-<#if includeCallbacks>
- * Activities that contain this fragment must implement the
- * {@link ${className}.OnFragmentInteractionListener} interface
- * to handle interaction events.
-</#if>
-<#if includeFactory>
- * Use the {@link ${className}#newInstance} factory method to
- * create an instance of this fragment.
-</#if>
- *
- */
-public class ${className} extends Fragment {
-<#if includeFactory>
- // TODO: Rename parameter arguments, choose names that match
- // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
- private static final String ARG_PARAM1 = "param1";
- private static final String ARG_PARAM2 = "param2";
-
- // TODO: Rename and change types of parameters
- private String mParam1;
- private String mParam2;
-</#if>
-
-<#if includeCallbacks>
- private OnFragmentInteractionListener mListener;
-</#if>
-
-<#if includeFactory>
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment ${className}.
- */
- // TODO: Rename and change types and number of parameters
- public static ${className} newInstance(String param1, String param2) {
- ${className} fragment = new ${className}();
- Bundle args = new Bundle();
- args.putString(ARG_PARAM1, param1);
- args.putString(ARG_PARAM2, param2);
- fragment.setArguments(args);
- return fragment;
- }
-</#if>
- public ${className}() {
- // Required empty public constructor
- }
-
-<#if includeFactory>
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (getArguments() != null) {
- mParam1 = getArguments().getString(ARG_PARAM1);
- mParam2 = getArguments().getString(ARG_PARAM2);
- }
- }
-</#if>
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
-<#if includeLayout>
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.${fragmentName}, container, false);
-<#else>
- TextView textView = new TextView(getActivity());
- textView.setText(R.string.hello_blank_fragment);
- return textView;
-</#if>
- }
-
-<#if includeCallbacks>
- // TODO: Rename method, update argument and hook method into UI event
- public void onButtonPressed(Uri uri) {
- if (mListener != null) {
- mListener.onFragmentInteraction(uri);
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try {
- mListener = (OnFragmentInteractionListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement OnFragmentInteractionListener");
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mListener = null;
- }
-
- /**
- * This interface must be implemented by activities that contain this
- * fragment to allow an interaction in this fragment to be communicated
- * to the activity and potentially other fragments contained in that
- * activity.
- * <p>
- * See the Android Training lesson <a href=
- * "http://developer.android.com/training/basics/fragments/communicating.html"
- * >Communicating with Other Fragments</a> for more information.
- */
- public interface OnFragmentInteractionListener {
- // TODO: Update argument type and name
- public void onFragmentInteraction(Uri uri);
- }
-</#if>
-
-}
diff --git a/base/templates/other/BlankFragment/template.xml b/base/templates/other/BlankFragment/template.xml
deleted file mode 100644
index d9779ef..0000000
--- a/base/templates/other/BlankFragment/template.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Fragment (Blank)"
- description="Creates a blank fragment that is compatible back to API level 4."
- minApi="7"
- minBuildApi="8">
-
- <category value="Fragment" />
-
- <dependency name="android-support-v4" revision="8" />
-
- <parameter
- id="className"
- name="Fragment Name"
- type="string"
- constraints="class|nonempty|unique"
- default="BlankFragment"
- help="The name of the fragment class to create" />
-
- <parameter
- id="includeLayout"
- name="Create layout XML?"
- type="boolean"
- default="true"
- help="Generate a layout XML for the fragment" />
-
- <parameter
- id="fragmentName"
- name="Fragment Layout Name"
- type="string"
- constraints="layout|nonempty|unique"
- default="fragment_blank"
- visibility="includeLayout"
- suggest="fragment_${classToResource(className)}"
- help="The name of the layout to create" />
-
- <parameter
- id="includeFactory"
- name="Include fragment factory methods?"
- type="boolean"
- default="true"
- help="Generate static fragment factory methods for easy instantiation" />
-
- <parameter
- id="includeCallbacks"
- name="Include interface callbacks?"
- type="boolean"
- default="true"
- help="Generate event callbacks for communication with an Activity or other fragments" />
-
- <thumbs>
- <thumb>template_blank_fragment.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/BlankFragment/template_blank_fragment.png b/base/templates/other/BlankFragment/template_blank_fragment.png
deleted file mode 100644
index 69a02bb..0000000
Binary files a/base/templates/other/BlankFragment/template_blank_fragment.png and /dev/null differ
diff --git a/base/templates/other/BroadcastReceiver/recipe.xml.ftl b/base/templates/other/BroadcastReceiver/recipe.xml.ftl
deleted file mode 100644
index d889414..0000000
--- a/base/templates/other/BroadcastReceiver/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/BroadcastReceiver.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/other/BroadcastReceiver/template.xml b/base/templates/other/BroadcastReceiver/template.xml
deleted file mode 100644
index 159ff75..0000000
--- a/base/templates/other/BroadcastReceiver/template.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Broadcast Receiver"
- description="Creates a new broadcast receiver component and adds it to your Android manifest.">
-
- <parameter
- id="className"
- name="Class Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyReceiver" />
-
- <parameter
- id="isExported"
- name="Exported"
- type="boolean"
- default="true"
- help="Whether or not the broadcast receiver can receive messages from sources outside its application" />
-
- <parameter
- id="isEnabled"
- name="Enabled"
- type="boolean"
- default="true"
- help="Whether or not the broadcast receiver can be instantiated by the system" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/ContentProvider/recipe.xml.ftl b/base/templates/other/ContentProvider/recipe.xml.ftl
deleted file mode 100644
index 9bc3e61..0000000
--- a/base/templates/other/ContentProvider/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/ContentProvider.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/other/ContentProvider/template.xml b/base/templates/other/ContentProvider/template.xml
deleted file mode 100644
index dc7e1ea..0000000
--- a/base/templates/other/ContentProvider/template.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Content Provider"
- description="Creates a new content provider component and adds it to your Android manifest.">
-
- <parameter
- id="className"
- name="Class Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyContentProvider" />
-
- <parameter
- id="authorities"
- name="URI Authorities"
- type="string"
- constraints="nonempty"
- default=""
- help="A list of one or more URI authorities that identify data under the purview of the content provider. " />
-
- <parameter
- id="isExported"
- name="Exported"
- type="boolean"
- default="true"
- help="Whether or not the content provider can be used by components of other applications " />
-
- <parameter
- id="isEnabled"
- name="Enabled"
- type="boolean"
- default="true"
- help="Whether or not the content provider can be instantiated by the system " />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/CustomView/recipe.xml.ftl b/base/templates/other/CustomView/recipe.xml.ftl
deleted file mode 100644
index ce605b2..0000000
--- a/base/templates/other/CustomView/recipe.xml.ftl
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="res/values/attrs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/attrs_${view_class}.xml" />
- <instantiate from="res/layout/sample.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
-
- <instantiate from="src/app_package/CustomView.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
-</recipe>
diff --git a/base/templates/other/CustomView/template.xml b/base/templates/other/CustomView/template.xml
deleted file mode 100644
index 666d319..0000000
--- a/base/templates/other/CustomView/template.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Custom View"
- description="Creates a new custom view that extends android.view.View and exposes custom attributes.">
-
- <category value="UI Component" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <parameter
- id="viewClass"
- name="View Class"
- type="string"
- constraints="class|unique|nonempty"
- default="MyView"
- help="By convention, should end in 'View'" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/Daydream/recipe.xml.ftl b/base/templates/other/Daydream/recipe.xml.ftl
deleted file mode 100644
index 5b99917..0000000
--- a/base/templates/other/Daydream/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <copy from="res/layout-v17/dream.xml"
- to="${escapeXmlAttribute(resOut)}/layout-v17/${class_name}.xml" />
-
- <instantiate from="src/app_package/DreamService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-<#if configurable>
- <copy from="res/xml/dream_prefs.xml"
- to="${escapeXmlAttribute(resOut)}/xml/${prefs_name}.xml" />
-
- <instantiate from="src/app_package/SettingsActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${settingsClassName}.java" />
-
- <instantiate from="res/xml/xml_dream.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/xml/${info_name}.xml" />
-</#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/other/Daydream/root/src/app_package/DreamService.java.ftl b/base/templates/other/Daydream/root/src/app_package/DreamService.java.ftl
deleted file mode 100644
index 81738bd..0000000
--- a/base/templates/other/Daydream/root/src/app_package/DreamService.java.ftl
+++ /dev/null
@@ -1,143 +0,0 @@
-package ${packageName};
-
-import java.util.Random;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.content.SharedPreferences;
-import android.graphics.Point;
-import android.os.Build;
-import android.preference.PreferenceManager;
-import android.service.dreams.DreamService;
-import android.view.ViewPropertyAnimator;
-import android.view.animation.LinearInterpolator;
-import android.widget.TextView;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-
-/**
- * This class is a sample implementation of a DreamService. When activated, a
- * TextView will repeatedly, move from the left to the right of screen, at a
- * random y-value.
-<#if configurable>
- * The generated {@link BlahDreamServiceSettingsActivity} allows
- * the user to change the text which is displayed.
-</#if>
- * <p />
- * Daydreams are only available on devices running API v17+.
- */
- at TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-public class ${className} extends DreamService {
-
- private static final TimeInterpolator sInterpolator = new LinearInterpolator();
-
- private final AnimatorListener mAnimListener = new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // Start animation again
- startTextViewScrollAnimation();
- }
-
- };
-
- private final Random mRandom = new Random();
- private final Point mPointSize = new Point();
-
- private TextView mDreamTextView;
- private ViewPropertyAnimator mAnimator;
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- // Exit dream upon user touch?
-<#if isInteractive>
- setInteractive(true);
-<#else>
- setInteractive(false);
-</#if>
-
- // Hide system UI?
-<#if isFullscreen>
- setFullscreen(true);
-<#else>
- setFullscreen(false);
-</#if>
-
- // Keep screen at full brightness?
-<#if isScreenBright>
- setScreenBright(true);
-<#else>
- setScreenBright(false);
-</#if>
-
- // Set the content view, just like you would with an Activity.
- setContentView(R.layout.${class_name});
-
- mDreamTextView = (TextView) findViewById(R.id.dream_text);
- mDreamTextView.setText(getTextFromPreferences());
- }
-
- @Override
- public void onDreamingStarted() {
- super.onDreamingStarted();
-
- // TODO: Begin animations or other behaviors here.
-
- startTextViewScrollAnimation();
- }
-
- @Override
- public void onDreamingStopped() {
- super.onDreamingStopped();
-
- // TODO: Stop anything that was started in onDreamingStarted()
-
- mAnimator.cancel();
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- // TODO: Dismantle resources
- // (for example, detach from handlers and listeners).
- }
-
- private String getTextFromPreferences() {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- return prefs.getString(getString(R.string.pref_dream_text_key),
- getString(R.string.pref_dream_text_default));
- }
-
- private void startTextViewScrollAnimation() {
- // Refresh Size of Window
- getWindowManager().getDefaultDisplay().getSize(mPointSize);
-
- final int windowWidth = mPointSize.x;
- final int windowHeight = mPointSize.y;
-
- // Move TextView so it's moved all the way to the left
- mDreamTextView.setTranslationX(-mDreamTextView.getWidth());
-
- // Move TextView to random y value
- final int yRange = windowHeight - mDreamTextView.getHeight();
- mDreamTextView.setTranslationY(mRandom.nextInt(yRange));
-
- // Create an Animator and keep a reference to it
- mAnimator = mDreamTextView.animate().translationX(windowWidth)
- .setDuration(3000)
- .setStartDelay(500)
- .setListener(mAnimListener)
- .setInterpolator(sInterpolator);
-
- // Start the animation
- mAnimator.start();
- }
-
-}
diff --git a/base/templates/other/Daydream/template.xml b/base/templates/other/Daydream/template.xml
deleted file mode 100644
index 491d009..0000000
--- a/base/templates/other/Daydream/template.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Daydream"
- description="Creates a new Daydream service component, for use on devices running Android 4.2 and later."
- minBuildApi="17">
-
- <parameter
- id="className"
- name="Class Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyDaydreamService" />
-
- <parameter
- id="isInteractive"
- name="Interactive?"
- type="boolean"
- default="false"
- help="Whether or not the Daydream is interactive. Touching anywhere on a non-interactive Daydreams dismisses it." />
-
- <parameter
- id="isFullscreen"
- name="Fullscreen?"
- type="boolean"
- default="true"
- help="Whether or not the Daydream should be in fullscreen mode (in which case system UI will be hidden)." />
-
- <parameter
- id="isScreenBright"
- name="Bright Screen?"
- type="boolean"
- default="true"
- help="Whether or not the screen should be bright when this Daydream is running. The screen will be dim otherwise." />
-
- <parameter
- id="configurable"
- name="Configuration Activity"
- type="boolean"
- default="false"
- help="Whether or not to generate an associated settings Activity." />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/DisplayNotification/globals.xml.ftl b/base/templates/other/DisplayNotification/globals.xml.ftl
deleted file mode 100644
index 4bf836f..0000000
--- a/base/templates/other/DisplayNotification/globals.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/other/DisplayNotification/recipe.xml.ftl b/base/templates/other/DisplayNotification/recipe.xml.ftl
deleted file mode 100644
index c92cbf7..0000000
--- a/base/templates/other/DisplayNotification/recipe.xml.ftl
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/layout/activity_display.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${displayActivityLayout}.xml" />
- <instantiate from="src/app_package/StubActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${stubActivityClass}.java" />
-
- <instantiate from="src/app_package/DisplayActivity.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${displayActivityClass}.java" />
-
- <instantiate from="src/app_package/BroadcastReceiver.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${receiverClass}.java" />
-
- <merge from="res/values/strings.xml"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${displayActivityClass}.java" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${displayActivityLayout}.xml" />
-</recipe>
diff --git a/base/templates/other/DisplayNotification/template.xml b/base/templates/other/DisplayNotification/template.xml
deleted file mode 100644
index 881be92..0000000
--- a/base/templates/other/DisplayNotification/template.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Display Notification"
- description="Create an Activity to display inside a custom display notification and a BroadcastReceiver to post it"
- minApi="20">
-
- <category value="Wear" />
- <formfactor value="Wear" />
-
- <parameter
- id="displayActivityClass"
- name="Display Activity Class"
- type="string"
- constraints="activity|unique|nonempty"
- default="MyDisplayActivity"
- help="The name of the Activity that will be displayed for the notification" />
-
- <parameter
- id="displayActivityLayout"
- name="Display Activity Layout Name"
- type="string"
- constraints="layout|unique|nonempty"
- default="activity_display"
- help="The layout name for the Activity that will be displayed for the notification" />
-
- <parameter
- id="receiverClass"
- name="Receiver Class"
- type="string"
- constraints="class|unique|nonempty"
- default="MyPostNotificationReceiver"
- help="The name of the BroadcastReceiver that will post the notification" />
-
- <parameter
- id="stubActivityClass"
- name="Launcher Stub Class"
- type="string"
- constraints="activity|unique|nonempty"
- default="MyStubBroadcastActivity"
- help="The name of the Stub Activity that sends the broadcast for testing purposes" />
-
- <parameter
- id="isExported"
- name="Exported"
- type="boolean"
- default="true"
- help="Whether or not the broadcast receiver can receive messages from sources outside its application" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"/>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
- <thumbs>
- <thumb>templates-activityView-Wear.png</thumb>
- </thumbs>
-
-</template>
diff --git a/base/templates/other/DisplayNotification/templates-activityView-Wear.png b/base/templates/other/DisplayNotification/templates-activityView-Wear.png
deleted file mode 100644
index 2adb3df..0000000
Binary files a/base/templates/other/DisplayNotification/templates-activityView-Wear.png and /dev/null differ
diff --git a/base/templates/other/IntentService/recipe.xml.ftl b/base/templates/other/IntentService/recipe.xml.ftl
deleted file mode 100644
index 197fc1f..0000000
--- a/base/templates/other/IntentService/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/IntentService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/other/IntentService/template.xml b/base/templates/other/IntentService/template.xml
deleted file mode 100644
index 7a0cc60..0000000
--- a/base/templates/other/IntentService/template.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Service (IntentService)"
- description="Creates a new intent service class."
- minApi="3"
- minBuildApi="3">
-
- <category value="Service" />
-
- <parameter
- id="className"
- name="Class Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyIntentService" />
-
- <parameter
- id="includeHelper"
- name="Include helper start methods?"
- type="boolean"
- default="true"
- help="Generate static helper methods to start the service e.g. MyIntentService.startAction()" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/JavaFolder/recipe.xml.ftl b/base/templates/other/JavaFolder/recipe.xml.ftl
deleted file mode 100644
index a470666..0000000
--- a/base/templates/other/JavaFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/java/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/JavaFolder/template.xml b/base/templates/other/JavaFolder/template.xml
deleted file mode 100644
index df0cd6f..0000000
--- a/base/templates/other/JavaFolder/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Java Folder"
- description="Creates a source root for Java files."
- >
-
- <category value="Folder" />
-
- <parameter
- id="remapFolder"
- name="Change Folder Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the folder location to another folder within the module." />
-
- <parameter
- id="newLocation"
- name="New Folder Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/java/"
- help="The location for the new folder"
- visibility="remapFolder" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/JniFolder/recipe.xml.ftl b/base/templates/other/JniFolder/recipe.xml.ftl
deleted file mode 100644
index 29e7e98..0000000
--- a/base/templates/other/JniFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/jni/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/JniFolder/template.xml b/base/templates/other/JniFolder/template.xml
deleted file mode 100644
index 0067618..0000000
--- a/base/templates/other/JniFolder/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="JNI Folder"
- description="Creates a source root for Java Native Interface files."
- >
-
- <category value="Folder" />
-
- <parameter
- id="remapFolder"
- name="Change Folder Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the folder location to another folder within the module." />
-
- <parameter
- id="newLocation"
- name="New Folder Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/jni/"
- help="The location for the new folder"
- visibility="remapFolder" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/LayoutResourceFile/recipe.xml.ftl b/base/templates/other/LayoutResourceFile/recipe.xml.ftl
deleted file mode 100644
index a73adc7..0000000
--- a/base/templates/other/LayoutResourceFile/recipe.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <instantiate from="res/layout.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
- <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
-</recipe>
diff --git a/base/templates/other/LayoutResourceFile/template.xml b/base/templates/other/LayoutResourceFile/template.xml
deleted file mode 100644
index abca0d2..0000000
--- a/base/templates/other/LayoutResourceFile/template.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Layout XML File"
- description="Creates a new XML layout file."
- >
-
- <category value="XML" />
-
- <parameter
- id="layoutName"
- name="Layout File Name"
- type="string"
- constraints="layout|unique|nonempty"
- default="layout"
- help="Name of the layout XML file." />
-
- <parameter
- id="rootTag"
- name="Root Tag"
- type="string"
- constraints="nonempty"
- default="LinearLayout"
- help="The root XML tag for the new file" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/ListFragment/globals.xml.ftl b/base/templates/other/ListFragment/globals.xml.ftl
deleted file mode 100644
index df79532..0000000
--- a/base/templates/other/ListFragment/globals.xml.ftl
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="useSupport" type="boolean" value="${(minApiLevel lt 11)?string}" />
- <global id="SupportPackage" value="${(minApiLevel lt 11)?string('.support.v4','')}" />
- <global id="resOut" value="${resDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="collection_name" value="${extractLetters(objectKind?lower_case)}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/other/ListFragment/recipe.xml.ftl b/base/templates/other/ListFragment/recipe.xml.ftl
deleted file mode 100644
index 089521f..0000000
--- a/base/templates/other/ListFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if useSupport><dependency mavenUrl="com.android.support:support-v4:${targetApi}.+"/></#if>
-<#if switchGrid == true>
- <merge from="res/values/refs.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/refs.xml" />
- <merge from="res/values/refs_lrg.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
- <merge from="res/values/refs_lrg.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
-
- <instantiate from="res/layout/fragment_grid.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout_grid}.xml" />
-
- <instantiate from="res/layout/fragment_list.xml"
- to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout_list}.xml" />
-</#if>
-
- <instantiate from="src/app_package/ListFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <instantiate from="src/app_package/dummy/DummyContent.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/other/ListFragment/root/res/layout/fragment_grid.xml b/base/templates/other/ListFragment/root/res/layout/fragment_grid.xml
deleted file mode 100644
index 333ea8e..0000000
--- a/base/templates/other/ListFragment/root/res/layout/fragment_grid.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${className}">
-
- <GridView
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:numColumns="2" />
-
- <TextView
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center" />
-
-</FrameLayout>
diff --git a/base/templates/other/ListFragment/root/res/layout/fragment_list.xml b/base/templates/other/ListFragment/root/res/layout/fragment_list.xml
deleted file mode 100644
index cc2aa02..0000000
--- a/base/templates/other/ListFragment/root/res/layout/fragment_list.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="${relativePackage}.${className}">
-
- <ListView
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- <TextView
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center" />
-
-</FrameLayout>
diff --git a/base/templates/other/ListFragment/root/res/values-large/refs_lrg.xml.ftl b/base/templates/other/ListFragment/root/res/values-large/refs_lrg.xml.ftl
deleted file mode 100644
index 052d4e7..0000000
--- a/base/templates/other/ListFragment/root/res/values-large/refs_lrg.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<resources>
-
- <!--
- Layout alias to replace the single-pane version of the layout with a
- two-pane version on Large screens.
-
- For more on layout aliases, see:
- http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
- -->
- <item name="${fragment_layout}" type="layout">@layout/${fragment_layout_grid}</item>
-
-</resources>
\ No newline at end of file
diff --git a/base/templates/other/ListFragment/root/res/values-sw600dp/refs_lrg.xml.ftl b/base/templates/other/ListFragment/root/res/values-sw600dp/refs_lrg.xml.ftl
deleted file mode 100644
index 052d4e7..0000000
--- a/base/templates/other/ListFragment/root/res/values-sw600dp/refs_lrg.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<resources>
-
- <!--
- Layout alias to replace the single-pane version of the layout with a
- two-pane version on Large screens.
-
- For more on layout aliases, see:
- http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
- -->
- <item name="${fragment_layout}" type="layout">@layout/${fragment_layout_grid}</item>
-
-</resources>
\ No newline at end of file
diff --git a/base/templates/other/ListFragment/root/res/values/refs.xml.ftl b/base/templates/other/ListFragment/root/res/values/refs.xml.ftl
deleted file mode 100644
index e730d8a..0000000
--- a/base/templates/other/ListFragment/root/res/values/refs.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<resources>
-
- <!--
- Layout alias to replace the single-pane version of the layout with a
- two-pane version on Large screens.
-
- For more on layout aliases, see:
- http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
- -->
- <item name="${fragment_layout}" type="layout">@layout/${fragment_layout_list}</item>
-
-</resources>
\ No newline at end of file
diff --git a/base/templates/other/ListFragment/root/res/values/refs_lrg.xml.ftl b/base/templates/other/ListFragment/root/res/values/refs_lrg.xml.ftl
deleted file mode 100644
index 052d4e7..0000000
--- a/base/templates/other/ListFragment/root/res/values/refs_lrg.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<resources>
-
- <!--
- Layout alias to replace the single-pane version of the layout with a
- two-pane version on Large screens.
-
- For more on layout aliases, see:
- http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
- -->
- <item name="${fragment_layout}" type="layout">@layout/${fragment_layout_grid}</item>
-
-</resources>
\ No newline at end of file
diff --git a/base/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl b/base/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl
deleted file mode 100644
index 01379d2..0000000
--- a/base/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl
+++ /dev/null
@@ -1,196 +0,0 @@
-package ${packageName};
-
-import android.app.Activity;
-import android.os.Bundle;
-<#if switchGrid == true>
-import android${SupportPackage}.app.Fragment;
-import android.view.LayoutInflater;
-<#else>
-import android${SupportPackage}.app.ListFragment;
-</#if>
-import android.view.View;
-<#if switchGrid == true>
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-</#if>
-import android.widget.ArrayAdapter;
-<#if switchGrid == true>
-import android.widget.ListAdapter;
-import android.widget.TextView;
-<#else>
-import android.widget.ListView;
-</#if>
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-import ${packageName}.dummy.DummyContent;
-
-/**
- * A fragment representing a list of Items.
- * <p />
-<#if switchGrid == true>
- * Large screen devices (such as tablets) are supported by replacing the ListView
- * with a GridView.
-</#if>
- * <p />
- * Activities containing this fragment MUST implement the {@link OnFragmentInteractionListener}
- * interface.
- */
-<#if switchGrid == true>
-public class ${className} extends Fragment implements AbsListView.OnItemClickListener {
-<#else>
-public class ${className} extends ListFragment {
-</#if>
-
-<#if includeFactory>
- // TODO: Rename parameter arguments, choose names that match
- // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
- private static final String ARG_PARAM1 = "param1";
- private static final String ARG_PARAM2 = "param2";
-
- // TODO: Rename and change types of parameters
- private String mParam1;
- private String mParam2;
-
-</#if>
- private OnFragmentInteractionListener mListener;
-
-<#if switchGrid == true>
- /**
- * The fragment's ListView/GridView.
- */
- private AbsListView mListView;
-
- /**
- * The Adapter which will be used to populate the ListView/GridView with
- * Views.
- */
- private ListAdapter mAdapter;
-
-</#if>
-<#if includeFactory>
- // TODO: Rename and change types of parameters
- public static ${className} newInstance(String param1, String param2) {
- ${className} fragment = new ${className}();
- Bundle args = new Bundle();
- args.putString(ARG_PARAM1, param1);
- args.putString(ARG_PARAM2, param2);
- fragment.setArguments(args);
- return fragment;
- }
-
-</#if>
- /**
- * Mandatory empty constructor for the fragment manager to instantiate the
- * fragment (e.g. upon screen orientation changes).
- */
- public ${className}() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-<#if includeFactory>
- if (getArguments() != null) {
- mParam1 = getArguments().getString(ARG_PARAM1);
- mParam2 = getArguments().getString(ARG_PARAM2);
- }
-</#if>
-
- // TODO: Change Adapter to display your content
-<#if switchGrid == true>
- mAdapter = new ArrayAdapter<DummyContent.DummyItem>(getActivity(),
- android.R.layout.simple_list_item_1, android.R.id.text1, DummyContent.ITEMS);
-<#else>
- setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(getActivity(),
- android.R.layout.simple_list_item_1, android.R.id.text1, DummyContent.ITEMS));
-</#if>
- }
-
-<#if switchGrid == true>
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.${fragment_layout}, container, false);
-
- // Set the adapter
- mListView = (AbsListView) view.findViewById(android.R.id.list);
- ((AdapterView<ListAdapter>) mListView).setAdapter(mAdapter);
-
- // Set OnItemClickListener so we can be notified on item clicks
- mListView.setOnItemClickListener(this);
-
- return view;
- }
-</#if>
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try {
- mListener = (OnFragmentInteractionListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement OnFragmentInteractionListener");
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mListener = null;
- }
-
-<#if switchGrid == true>
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (null != mListener) {
- // Notify the active callbacks interface (the activity, if the
- // fragment is attached to one) that an item has been selected.
- mListener.onFragmentInteraction(DummyContent.ITEMS.get(position).id);
- }
- }
-
- /**
- * The default content for this Fragment has a TextView that is shown when
- * the list is empty. If you would like to change the text, call this method
- * to supply the text it should use.
- */
- public void setEmptyText(CharSequence emptyText) {
- View emptyView = mListView.getEmptyView();
-
- if (emptyView instanceof TextView) {
- ((TextView) emptyView).setText(emptyText);
- }
- }
-<#else>
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- super.onListItemClick(l, v, position, id);
-
- if (null != mListener) {
- // Notify the active callbacks interface (the activity, if the
- // fragment is attached to one) that an item has been selected.
- mListener.onFragmentInteraction(DummyContent.ITEMS.get(position).id);
- }
- }
-</#if>
-
- /**
- * This interface must be implemented by activities that contain this
- * fragment to allow an interaction in this fragment to be communicated
- * to the activity and potentially other fragments contained in that
- * activity.
- * <p>
- * See the Android Training lesson <a href=
- * "http://developer.android.com/training/basics/fragments/communicating.html"
- * >Communicating with Other Fragments</a> for more information.
- */
- public interface OnFragmentInteractionListener {
- // TODO: Update argument type and name
- public void onFragmentInteraction(String id);
- }
-
-}
diff --git a/base/templates/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl b/base/templates/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl
deleted file mode 100644
index 3545ba3..0000000
--- a/base/templates/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl
+++ /dev/null
@@ -1,55 +0,0 @@
-package ${packageName}.dummy;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Helper class for providing sample content for user interfaces created by
- * Android template wizards.
- * <p>
- * TODO: Replace all uses of this class before publishing your app.
- */
-public class DummyContent {
-
- /**
- * An array of sample (dummy) items.
- */
- public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
-
- /**
- * A map of sample (dummy) items, by ID.
- */
- public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
-
- static {
- // Add 3 sample items.
- addItem(new DummyItem("1", "Item 1"));
- addItem(new DummyItem("2", "Item 2"));
- addItem(new DummyItem("3", "Item 3"));
- }
-
- private static void addItem(DummyItem item) {
- ITEMS.add(item);
- ITEM_MAP.put(item.id, item);
- }
-
- /**
- * A dummy item representing a piece of content.
- */
- public static class DummyItem {
- public String id;
- public String content;
-
- public DummyItem(String id, String content) {
- this.id = id;
- this.content = content;
- }
-
- @Override
- public String toString() {
- return content;
- }
- }
-}
diff --git a/base/templates/other/ListFragment/template.xml b/base/templates/other/ListFragment/template.xml
deleted file mode 100644
index c1ef322..0000000
--- a/base/templates/other/ListFragment/template.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Fragment (List)"
- description="Creates a new empty fragment containing a list that can optionally change to a grid when on large screens. Compatible back to API level 4."
- minApi="7"
- minBuildApi="8">
-
- <category value="Fragment" />
-
- <dependency name="android-support-v4" revision="8" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <parameter
- id="objectKind"
- name="Object Kind"
- type="string"
- constraints="nonempty"
- default="Item"
- help="Other examples are 'Person', 'Book', etc." />
-
- <parameter
- id="className"
- type="string"
- constraints="nonempty|class|unique"
- suggest="${extractLetters(objectKind)}Fragment"
- name="Fragment class name" />
-
- <parameter
- id="includeFactory"
- name="Include fragment factory methods?"
- type="boolean"
- default="true"
- help="Generate static fragment factory methods for easy instantiation" />
-
- <parameter
- id="switchGrid"
- name="Switch to grid view on large screens?"
- type="boolean"
- default="true" />
-
- <parameter
- id="fragment_layout"
- type="string"
- constraints="layout|nonempty|unique"
- suggest="fragment_${extractLetters(objectKind?lower_case)}"
- name="Fragment alias name"
- visibility="switchGrid" />
-
- <parameter
- id="fragment_layout_grid"
- type="string"
- constraints="layout|nonempty|unique"
- suggest="fragment_${extractLetters(objectKind?lower_case)}_grid"
- name="Grid layout file name"
- visibility="switchGrid" />
-
- <parameter
- id="fragment_layout_list"
- type="string"
- constraints="layout|nonempty|unique"
- suggest="fragment_${extractLetters(objectKind?lower_case)}_list"
- name="List layout file name"
- visibility="switchGrid" />
-
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>templates_list_fragment.png</thumb>
- </thumbs>
-
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/ListFragment/templates_list_fragment.png b/base/templates/other/ListFragment/templates_list_fragment.png
deleted file mode 100644
index 92ac799..0000000
Binary files a/base/templates/other/ListFragment/templates_list_fragment.png and /dev/null differ
diff --git a/base/templates/other/Notification/recipe.xml.ftl b/base/templates/other/Notification/recipe.xml.ftl
deleted file mode 100644
index 17f21a9..0000000
--- a/base/templates/other/Notification/recipe.xml.ftl
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <dependency mavenUrl="com.android.support:support-v4:${targetApi}.+"/>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <#if expandedStyle == "picture">
- <copy from="res/drawable-nodpi/example_picture_large.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
- <#else>
- <copy from="res/drawable-nodpi/example_picture_small.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
- </#if>
-
- <#if moreActions>
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
- </#if>
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <instantiate from="src/app_package/NotificationHelper.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/other/Notification/template.xml b/base/templates/other/Notification/template.xml
deleted file mode 100644
index 2a13999..0000000
--- a/base/templates/other/Notification/template.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Notification"
- description="Creates a new helper class that can show or cancel a status bar notification."
- minApi="4">
-
- <category value="UI Component" />
-
- <dependency name="android-support-v4" revision="10" />
-
- <parameter
- id="className"
- name="Class Name"
- type="string"
- constraints="class|unique|nonempty"
- default="NewMessageNotification"
- help="The name of the notification helper class to create, e.g. 'NewMessageNotification'." />
-
- <parameter
- id="expandedStyle"
- name="Style when Expanded"
- type="enum"
- default="text"
- help="The expanded notification style to use for devices running Android 4.1 or later." >
- <option id="none">None</option>
- <option id="text">More text</option>
- <option id="picture">Picture</option>
- <option id="list">List</option>
- </parameter>
-
- <parameter
- id="moreActions"
- name="Show Additional Actions"
- type="boolean"
- default="true"
- help="If true, this notification will contain additional actions when expanded on devices running Android 4.1 or later." />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
- <thumbs>
- <thumb>template_notification_text_actions.png</thumb>
- <thumb expandedStyle="none">template_notification_none.png</thumb>
- <thumb expandedStyle="none" moreActions="true">template_notification_none_actions.png</thumb>
- <thumb expandedStyle="text">template_notification_text.png</thumb>
- <thumb expandedStyle="text" moreActions="true">template_notification_text_actions.png</thumb>
- <thumb expandedStyle="list">template_notification_list.png</thumb>
- <thumb expandedStyle="list" moreActions="true">template_notification_list_actions.png</thumb>
- <thumb expandedStyle="picture">template_notification_picture.png</thumb>
- <thumb expandedStyle="picture" moreActions="true">template_notification_picture_actions.png</thumb>
- </thumbs>
-
- <icons
- type="notification"
- name="ic_stat_${camelCaseToUnderscore(className?replace('notification','','i'))}" />
-</template>
diff --git a/base/templates/other/Notification/template_notification_list.png b/base/templates/other/Notification/template_notification_list.png
deleted file mode 100644
index f858daa..0000000
Binary files a/base/templates/other/Notification/template_notification_list.png and /dev/null differ
diff --git a/base/templates/other/Notification/template_notification_list_actions.png b/base/templates/other/Notification/template_notification_list_actions.png
deleted file mode 100644
index a095525..0000000
Binary files a/base/templates/other/Notification/template_notification_list_actions.png and /dev/null differ
diff --git a/base/templates/other/Notification/template_notification_none.png b/base/templates/other/Notification/template_notification_none.png
deleted file mode 100644
index abbee9d..0000000
Binary files a/base/templates/other/Notification/template_notification_none.png and /dev/null differ
diff --git a/base/templates/other/Notification/template_notification_none_actions.png b/base/templates/other/Notification/template_notification_none_actions.png
deleted file mode 100644
index 69a4a50..0000000
Binary files a/base/templates/other/Notification/template_notification_none_actions.png and /dev/null differ
diff --git a/base/templates/other/Notification/template_notification_picture.png b/base/templates/other/Notification/template_notification_picture.png
deleted file mode 100644
index d535661..0000000
Binary files a/base/templates/other/Notification/template_notification_picture.png and /dev/null differ
diff --git a/base/templates/other/Notification/template_notification_picture_actions.png b/base/templates/other/Notification/template_notification_picture_actions.png
deleted file mode 100644
index 300431f..0000000
Binary files a/base/templates/other/Notification/template_notification_picture_actions.png and /dev/null differ
diff --git a/base/templates/other/Notification/template_notification_text.png b/base/templates/other/Notification/template_notification_text.png
deleted file mode 100644
index 790ecc0..0000000
Binary files a/base/templates/other/Notification/template_notification_text.png and /dev/null differ
diff --git a/base/templates/other/Notification/template_notification_text_actions.png b/base/templates/other/Notification/template_notification_text_actions.png
deleted file mode 100644
index 6514069..0000000
Binary files a/base/templates/other/Notification/template_notification_text_actions.png and /dev/null differ
diff --git a/base/templates/other/PlusOneFragment/recipe.xml.ftl b/base/templates/other/PlusOneFragment/recipe.xml.ftl
deleted file mode 100644
index 87f3130..0000000
--- a/base/templates/other/PlusOneFragment/recipe.xml.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <dependency mavenUrl="com.android.support:support-v4:${targetApi}.+"/>
- <dependency mavenUrl="com.google.android.gms:play-services:4.2.42"/>
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <instantiate from="res/layout/fragment_plus_one.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
-
- <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
-
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
- <instantiate from="src/app_package/PlusOneFragment.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
-
-</recipe>
diff --git a/base/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl b/base/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
deleted file mode 100644
index 8351bcb..0000000
--- a/base/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
+++ /dev/null
@@ -1,147 +0,0 @@
-package ${packageName};
-
-<#if includeCallbacks>import android.app.Activity;</#if>
-<#if includeCallbacks>import android.net.Uri;</#if>
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-<#if applicationPackage??>
-import ${applicationPackage}.R;
-</#if>
-import com.google.android.gms.plus.PlusOneButton;
-
-/**
- * A fragment with a Google +1 button.
-<#if includeCallbacks>
- * Activities that contain this fragment must implement the
- * {@link ${className}.OnFragmentInteractionListener} interface
- * to handle interaction events.
-</#if>
-<#if includeFactory>
- * Use the {@link ${className}#newInstance} factory method to
- * create an instance of this fragment.
-</#if>
- *
- */
-public class ${className} extends Fragment {
-<#if includeFactory>
- // TODO: Rename parameter arguments, choose names that match
- // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
- private static final String ARG_PARAM1 = "param1";
- private static final String ARG_PARAM2 = "param2";
-
- // TODO: Rename and change types of parameters
- private String mParam1;
- private String mParam2;
-</#if>
-
- // The URL to +1. Must be a valid URL.
- private final String PLUS_ONE_URL = "http://developer.android.com";
-
- // The request code must be 0 or greater.
- private static final int PLUS_ONE_REQUEST_CODE = 0;
-
- private PlusOneButton mPlusOneButton;
-
-<#if includeCallbacks>
- private OnFragmentInteractionListener mListener;
-</#if>
-
-<#if includeFactory>
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment ${className}.
- */
- // TODO: Rename and change types and number of parameters
- public static ${className} newInstance(String param1, String param2) {
- ${className} fragment = new ${className}();
- Bundle args = new Bundle();
- args.putString(ARG_PARAM1, param1);
- args.putString(ARG_PARAM2, param2);
- fragment.setArguments(args);
- return fragment;
- }
-</#if>
- public ${className}() {
- // Required empty public constructor
- }
-
-<#if includeFactory>
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (getArguments() != null) {
- mParam1 = getArguments().getString(ARG_PARAM1);
- mParam2 = getArguments().getString(ARG_PARAM2);
- }
- }
-</#if>
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- View view = inflater.inflate(R.layout.fragment_${classToResource(className)}, container, false);
-
- //Find the +1 button
- mPlusOneButton = (PlusOneButton) view.findViewById(R.id.plus_one_button);
-
- return view;
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- // Refresh the state of the +1 button each time the activity receives focus.
- mPlusOneButton.initialize(PLUS_ONE_URL, PLUS_ONE_REQUEST_CODE);
- }
-
-<#if includeCallbacks>
- // TODO: Rename method, update argument and hook method into UI event
- public void onButtonPressed(Uri uri) {
- if (mListener != null) {
- mListener.onFragmentInteraction(uri);
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try {
- mListener = (OnFragmentInteractionListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement OnFragmentInteractionListener");
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mListener = null;
- }
-
- /**
- * This interface must be implemented by activities that contain this
- * fragment to allow an interaction in this fragment to be communicated
- * to the activity and potentially other fragments contained in that
- * activity.
- * <p>
- * See the Android Training lesson <a href=
- * "http://developer.android.com/training/basics/fragments/communicating.html"
- * >Communicating with Other Fragments</a> for more information.
- */
- public interface OnFragmentInteractionListener {
- // TODO: Update argument type and name
- public void onFragmentInteraction(Uri uri);
- }
-</#if>
-
-}
diff --git a/base/templates/other/PlusOneFragment/template.xml b/base/templates/other/PlusOneFragment/template.xml
deleted file mode 100644
index 618d92d..0000000
--- a/base/templates/other/PlusOneFragment/template.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="3"
- revision="1"
- name="Fragment (with a +1 button)"
- description="Creates a fragment with a Google Plus +1 button."
- minApi="8"
- minBuildApi="8">
-
- <category value="Fragment" />
-
- <dependency name="android-support-v4" revision="8" />
-
- <category value="Other" />
-
- <parameter
- id="className"
- name="Fragment Name"
- type="string"
- constraints="class|nonempty|unique"
- default="PlusOneFragment"
- help="The name of the fragment class to create" />
-
- <parameter
- id="includeFactory"
- name="Include fragment factory methods?"
- type="boolean"
- default="true"
- help="Generate static fragment factory methods for easy instantiation" />
-
- <parameter
- id="includeCallbacks"
- name="Include interface callbacks?"
- type="boolean"
- default="true"
- help="Generate event callbacks for communication with an Activity or other fragments" />
-
- <thumbs>
- <thumb>templates_plusone_fragment.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/PlusOneFragment/templates_plusone_fragment.png b/base/templates/other/PlusOneFragment/templates_plusone_fragment.png
deleted file mode 100644
index 47c1dcc..0000000
Binary files a/base/templates/other/PlusOneFragment/templates_plusone_fragment.png and /dev/null differ
diff --git a/base/templates/other/ResFolder/recipe.xml.ftl b/base/templates/other/ResFolder/recipe.xml.ftl
deleted file mode 100644
index abfda2d..0000000
--- a/base/templates/other/ResFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/res/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/ResFolder/template.xml b/base/templates/other/ResFolder/template.xml
deleted file mode 100644
index 1ecd5b1..0000000
--- a/base/templates/other/ResFolder/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Res Folder"
- description="Creates a source root for Android Resource files."
- >
-
- <category value="Folder" />
-
- <parameter
- id="remapFolder"
- name="Change Folder Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the folder location to another folder within the module." />
-
- <parameter
- id="newLocation"
- name="New Folder Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/res/"
- help="The location for the new folder"
- enabled="remapFolder" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/ResourcesFolder/recipe.xml.ftl b/base/templates/other/ResourcesFolder/recipe.xml.ftl
deleted file mode 100644
index 2045012..0000000
--- a/base/templates/other/ResourcesFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/resources/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/ResourcesFolder/template.xml b/base/templates/other/ResourcesFolder/template.xml
deleted file mode 100644
index c5a9c1b..0000000
--- a/base/templates/other/ResourcesFolder/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Java Resources Folder"
- description="Creates a source root for Java Resource (NOT Android resource) files."
- >
-
- <category value="Folder" />
-
- <parameter
- id="remapFolder"
- name="Change Folder Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the folder location to another folder within the module." />
-
- <parameter
- id="newLocation"
- name="New Folder Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/resources/"
- help="The location for the new folder"
- visibility="remapFolder" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/RsFolder/recipe.xml.ftl b/base/templates/other/RsFolder/recipe.xml.ftl
deleted file mode 100644
index e2766e4..0000000
--- a/base/templates/other/RsFolder/recipe.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <#if remapFolder>
- <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
- <merge from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <#else>
- <mkdir at="${escapeXmlAttribute(manifestOut)}/rs/" />
- </#if>
-
-</recipe>
diff --git a/base/templates/other/RsFolder/template.xml b/base/templates/other/RsFolder/template.xml
deleted file mode 100644
index 0725931..0000000
--- a/base/templates/other/RsFolder/template.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="RenderScript Folder"
- description="Creates a source root for RenderScript files."
- >
-
- <category value="Folder" />
-
- <parameter
- id="remapFolder"
- name="Change Folder Location"
- type="boolean"
- constraints=""
- default="false"
- help="Change the folder location to another folder within the module." />
-
- <parameter
- id="newLocation"
- name="New Folder Location"
- type="string"
- constraints="nonempty|source_set_folder|unique"
- suggest="src/${sourceProviderName}/rs/"
- help="The location for the new folder"
- visibility="remapFolder" />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/Service/recipe.xml.ftl b/base/templates/other/Service/recipe.xml.ftl
deleted file mode 100644
index 9e66da5..0000000
--- a/base/templates/other/Service/recipe.xml.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <instantiate from="src/app_package/Service.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${className}.java" />
- <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
-</recipe>
diff --git a/base/templates/other/Service/template.xml b/base/templates/other/Service/template.xml
deleted file mode 100644
index 96ad2a1..0000000
--- a/base/templates/other/Service/template.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Service"
- description="Creates a new service component and adds it to your Android manifest.">
-
- <category value="Service" />
-
- <parameter
- id="className"
- name="Class Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyService" />
-
- <parameter
- id="isExported"
- name="Exported"
- type="boolean"
- default="true"
- help="Whether or not components of other applications can invoke the service or interact with it" />
-
- <parameter
- id="isEnabled"
- name="Enabled"
- type="boolean"
- default="true"
- help="Whether or not the service can be instantiated by the system" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/ValueResourceFile/recipe.xml.ftl b/base/templates/other/ValueResourceFile/recipe.xml.ftl
deleted file mode 100644
index 8895ae1..0000000
--- a/base/templates/other/ValueResourceFile/recipe.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <instantiate from="res/values.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
- <open file="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
-</recipe>
diff --git a/base/templates/other/ValueResourceFile/template.xml b/base/templates/other/ValueResourceFile/template.xml
deleted file mode 100644
index b270aae..0000000
--- a/base/templates/other/ValueResourceFile/template.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="1"
- name="Values XML File"
- description="Creates a new XML values file."
- >
-
- <category value="XML" />
-
- <parameter
- id="fileName"
- name="Values File Name"
- type="string"
- constraints="unique|nonempty"
- default="values"
- help="Name of the values XML file." />
-
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/templates/other/WatchFaceService/analog_round.png b/base/templates/other/WatchFaceService/analog_round.png
deleted file mode 100644
index 4f80f2d..0000000
Binary files a/base/templates/other/WatchFaceService/analog_round.png and /dev/null differ
diff --git a/base/templates/other/WatchFaceService/analog_square.png b/base/templates/other/WatchFaceService/analog_square.png
deleted file mode 100644
index db490d0..0000000
Binary files a/base/templates/other/WatchFaceService/analog_square.png and /dev/null differ
diff --git a/base/templates/other/WatchFaceService/digital_round.png b/base/templates/other/WatchFaceService/digital_round.png
deleted file mode 100644
index f70112f..0000000
Binary files a/base/templates/other/WatchFaceService/digital_round.png and /dev/null differ
diff --git a/base/templates/other/WatchFaceService/digital_square.png b/base/templates/other/WatchFaceService/digital_square.png
deleted file mode 100644
index 8c8ed97..0000000
Binary files a/base/templates/other/WatchFaceService/digital_square.png and /dev/null differ
diff --git a/base/templates/other/WatchFaceService/globals.xml.ftl b/base/templates/other/WatchFaceService/globals.xml.ftl
deleted file mode 100644
index 4bf836f..0000000
--- a/base/templates/other/WatchFaceService/globals.xml.ftl
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="manifestOut" value="${manifestDir}" />
- <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
- <global id="resOut" value="${resDir}" />
- <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
-</globals>
diff --git a/base/templates/other/WatchFaceService/recipe.xml.ftl b/base/templates/other/WatchFaceService/recipe.xml.ftl
deleted file mode 100644
index 33c5e09..0000000
--- a/base/templates/other/WatchFaceService/recipe.xml.ftl
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
-
- <dependency mavenUrl="com.google.android.support:wearable:1.1.+" />
-
- <merge from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
- <merge from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-
- <merge from="res/values/colors.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
-
- <merge from="res/values/dimens.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
-
- <copy from="res/xml/watch_face.xml"
- to="${escapeXmlAttribute(resOut)}/xml/watch_face.xml" />
-
-<#if style == "analog">
- <copy from="res/drawable-nodpi/preview_analog.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/preview_analog.png" />
-<#elseif style == "digital">
- <copy from="res/drawable-nodpi/preview_digital.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/preview_digital.png" />
- <copy from="res/drawable-nodpi/preview_digital_circular.png"
- to="${escapeXmlAttribute(resOut)}/drawable-nodpi/preview_digital_circular.png" />
-</#if>
-
-<#if style == "analog">
- <instantiate from="src/app_package/MyAnalogWatchFaceService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${serviceClass}.java" />
-<#elseif style == "digital">
- <instantiate from="src/app_package/MyDigitalWatchFaceService.java.ftl"
- to="${escapeXmlAttribute(srcOut)}/${serviceClass}.java" />
-</#if>
-
- <open file="${escapeXmlAttribute(srcOut)}/${serviceClass}.java" />
-</recipe>
diff --git a/base/templates/other/WatchFaceService/root/AndroidManifest.xml.ftl b/base/templates/other/WatchFaceService/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 9d0ba84..0000000
--- a/base/templates/other/WatchFaceService/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <uses-feature android:name="android.hardware.type.watch" />
-
- <!-- Required to act as a custom watch face. -->
- <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <application>
- <service
- android:name="${relativePackage}.${serviceClass}"
-<#if style == "analog">
- android:label="@string/my_analog_name"
-<#elseif style == "digital">
- android:label="@string/my_digital_name"
-</#if>
- android:permission="android.permission.BIND_WALLPAPER" >
- <meta-data
- android:name="android.service.wallpaper"
- android:resource="@xml/watch_face" />
- <meta-data
- android:name="com.google.android.wearable.watchface.preview"
-<#if style == "analog">
- android:resource="@drawable/preview_analog" />
-<#elseif style == "digital">
- android:resource="@drawable/preview_digital" />
-</#if>
- <meta-data
- android:name="com.google.android.wearable.watchface.preview_circular"
-<#if style == "analog">
- android:resource="@drawable/preview_analog" />
-<#elseif style == "digital">
- android:resource="@drawable/preview_digital_circular" />
-</#if>
- <intent-filter>
- <action android:name="android.service.wallpaper.WallpaperService" />
- <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
- </intent-filter>
- </service>
-
- <meta-data
- android:name="com.google.android.gms.version"
- android:value="@integer/google_play_services_version" />
- </application>
-</manifest>
diff --git a/base/templates/other/WatchFaceService/root/res/values/colors.xml.ftl b/base/templates/other/WatchFaceService/root/res/values/colors.xml.ftl
deleted file mode 100644
index 47be7a2..0000000
--- a/base/templates/other/WatchFaceService/root/res/values/colors.xml.ftl
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-<#if style == "analog">
- <color name="analog_background">#000000</color>
- <color name="analog_hands">#cccccc</color>
-<#elseif style == "digital">
- <color name="digital_background">#000000</color>
- <color name="digital_text">#ffffff</color>
-</#if>
-</resources>
diff --git a/base/templates/other/WatchFaceService/root/src/app_package/MyAnalogWatchFaceService.java.ftl b/base/templates/other/WatchFaceService/root/src/app_package/MyAnalogWatchFaceService.java.ftl
deleted file mode 100644
index 81395cb..0000000
--- a/base/templates/other/WatchFaceService/root/src/app_package/MyAnalogWatchFaceService.java.ftl
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ${packageName};
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.wearable.watchface.CanvasWatchFaceService;
-import android.support.wearable.watchface.WatchFaceStyle;
-import android.text.format.Time;
-import android.view.SurfaceHolder;
-
-import java.lang.ref.WeakReference;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On
- * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode.
- */
-public class ${serviceClass} extends CanvasWatchFaceService {
- /**
- * Update rate in milliseconds for interactive mode. We update once a second to advance the
- * second hand.
- */
- private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
-
- /**
- * Handler message id for updating the time periodically in interactive mode.
- */
- private static final int MSG_UPDATE_TIME = 0;
-
- @Override
- public Engine onCreateEngine() {
- return new Engine();
- }
-
- private class Engine extends CanvasWatchFaceService.Engine {
- Paint mBackgroundPaint;
- Paint mHandPaint;
- boolean mAmbient;
- Time mTime;
-
- final Handler mUpdateTimeHandler = new EngineHandler(this);
-
- final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mTime.clear(intent.getStringExtra("time-zone"));
- mTime.setToNow();
- }
- };
- boolean mRegisteredTimeZoneReceiver = false;
-
- /**
- * Whether the display supports fewer bits for each color in ambient mode. When true, we
- * disable anti-aliasing in ambient mode.
- */
- boolean mLowBitAmbient;
-
- @Override
- public void onCreate(SurfaceHolder holder) {
- super.onCreate(holder);
-
- setWatchFaceStyle(new WatchFaceStyle.Builder(${serviceClass}.this)
- .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
- .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
- .setShowSystemUiTime(false)
- .build());
-
- Resources resources = ${serviceClass}.this.getResources();
-
- mBackgroundPaint = new Paint();
- mBackgroundPaint.setColor(resources.getColor(R.color.analog_background));
-
- mHandPaint = new Paint();
- mHandPaint.setColor(resources.getColor(R.color.analog_hands));
- mHandPaint.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke));
- mHandPaint.setAntiAlias(true);
- mHandPaint.setStrokeCap(Paint.Cap.ROUND);
-
- mTime = new Time();
- }
-
- @Override
- public void onDestroy() {
- mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
- super.onDestroy();
- }
-
- @Override
- public void onPropertiesChanged(Bundle properties) {
- super.onPropertiesChanged(properties);
- mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
- }
-
- @Override
- public void onTimeTick() {
- super.onTimeTick();
- invalidate();
- }
-
- @Override
- public void onAmbientModeChanged(boolean inAmbientMode) {
- super.onAmbientModeChanged(inAmbientMode);
- if (mAmbient != inAmbientMode) {
- mAmbient = inAmbientMode;
- if (mLowBitAmbient) {
- mHandPaint.setAntiAlias(!inAmbientMode);
- }
- invalidate();
- }
-
- // Whether the timer should be running depends on whether we're visible (as well as
- // whether we're in ambient mode), so we may need to start or stop the timer.
- updateTimer();
- }
-
- @Override
- public void onDraw(Canvas canvas, Rect bounds) {
- mTime.setToNow();
-
- int width = bounds.width();
- int height = bounds.height();
-
- // Draw the background.
- canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
-
- // Find the center. Ignore the window insets so that, on round watches with a
- // "chin", the watch face is centered on the entire screen, not just the usable
- // portion.
- float centerX = width / 2f;
- float centerY = height / 2f;
-
- float secRot = mTime.second / 30f * (float) Math.PI;
- int minutes = mTime.minute;
- float minRot = minutes / 30f * (float) Math.PI;
- float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
-
- float secLength = centerX - 20;
- float minLength = centerX - 40;
- float hrLength = centerX - 80;
-
- if (!mAmbient) {
- float secX = (float) Math.sin(secRot) * secLength;
- float secY = (float) -Math.cos(secRot) * secLength;
- canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mHandPaint);
- }
-
- float minX = (float) Math.sin(minRot) * minLength;
- float minY = (float) -Math.cos(minRot) * minLength;
- canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mHandPaint);
-
- float hrX = (float) Math.sin(hrRot) * hrLength;
- float hrY = (float) -Math.cos(hrRot) * hrLength;
- canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHandPaint);
- }
-
- @Override
- public void onVisibilityChanged(boolean visible) {
- super.onVisibilityChanged(visible);
-
- if (visible) {
- registerReceiver();
-
- // Update time zone in case it changed while we weren't visible.
- mTime.clear(TimeZone.getDefault().getID());
- mTime.setToNow();
- } else {
- unregisterReceiver();
- }
-
- // Whether the timer should be running depends on whether we're visible (as well as
- // whether we're in ambient mode), so we may need to start or stop the timer.
- updateTimer();
- }
-
- private void registerReceiver() {
- if (mRegisteredTimeZoneReceiver) {
- return;
- }
- mRegisteredTimeZoneReceiver = true;
- IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
- ${serviceClass}.this.registerReceiver(mTimeZoneReceiver, filter);
- }
-
- private void unregisterReceiver() {
- if (!mRegisteredTimeZoneReceiver) {
- return;
- }
- mRegisteredTimeZoneReceiver = false;
- ${serviceClass}.this.unregisterReceiver(mTimeZoneReceiver);
- }
-
- /**
- * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
- * or stops it if it shouldn't be running but currently is.
- */
- private void updateTimer() {
- mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
- if (shouldTimerBeRunning()) {
- mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
- }
- }
-
- /**
- * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
- * only run when we're visible and in interactive mode.
- */
- private boolean shouldTimerBeRunning() {
- return isVisible() && !isInAmbientMode();
- }
-
- /**
- * Handle updating the time periodically in interactive mode.
- */
- private void handleUpdateTimeMessage() {
- invalidate();
- if (shouldTimerBeRunning()) {
- long timeMs = System.currentTimeMillis();
- long delayMs = INTERACTIVE_UPDATE_RATE_MS
- - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
- mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
- }
- }
- }
-
- private static class EngineHandler extends Handler {
- private final WeakReference<${serviceClass}.Engine> mWeakReference;
-
- public EngineHandler(${serviceClass}.Engine reference) {
- mWeakReference = new WeakReference<>(reference);
- }
-
- @Override
- public void handleMessage(Message msg) {
- ${serviceClass}.Engine engine = mWeakReference.get();
- if (engine != null) {
- switch (msg.what) {
- case MSG_UPDATE_TIME:
- engine.handleUpdateTimeMessage();
- break;
- }
- }
- }
- }
-}
diff --git a/base/templates/other/WatchFaceService/root/src/app_package/MyDigitalWatchFaceService.java.ftl b/base/templates/other/WatchFaceService/root/src/app_package/MyDigitalWatchFaceService.java.ftl
deleted file mode 100644
index ac00af2..0000000
--- a/base/templates/other/WatchFaceService/root/src/app_package/MyDigitalWatchFaceService.java.ftl
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ${packageName};
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.wearable.watchface.CanvasWatchFaceService;
-import android.support.wearable.watchface.WatchFaceStyle;
-import android.text.format.Time;
-import android.view.SurfaceHolder;
-import android.view.WindowInsets;
-
-import java.lang.ref.WeakReference;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Digital watch face with seconds. In ambient mode, the seconds aren't displayed. On devices with
- * low-bit ambient mode, the text is drawn without anti-aliasing in ambient mode.
- */
-public class ${serviceClass} extends CanvasWatchFaceService {
- private static final Typeface NORMAL_TYPEFACE =
- Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
-
- /**
- * Update rate in milliseconds for interactive mode. We update once a second since seconds are
- * displayed in interactive mode.
- */
- private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
-
- /**
- * Handler message id for updating the time periodically in interactive mode.
- */
- private static final int MSG_UPDATE_TIME = 0;
-
- @Override
- public Engine onCreateEngine() {
- return new Engine();
- }
-
- private class Engine extends CanvasWatchFaceService.Engine {
- final Handler mUpdateTimeHandler = new EngineHandler(this);
-
- final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mTime.clear(intent.getStringExtra("time-zone"));
- mTime.setToNow();
- }
- };
-
- boolean mRegisteredTimeZoneReceiver = false;
-
- Paint mBackgroundPaint;
- Paint mTextPaint;
-
- boolean mAmbient;
-
- Time mTime;
-
- float mXOffset;
- float mYOffset;
-
- /**
- * Whether the display supports fewer bits for each color in ambient mode. When true, we
- * disable anti-aliasing in ambient mode.
- */
- boolean mLowBitAmbient;
-
- @Override
- public void onCreate(SurfaceHolder holder) {
- super.onCreate(holder);
-
- setWatchFaceStyle(new WatchFaceStyle.Builder(${serviceClass}.this)
- .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
- .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
- .setShowSystemUiTime(false)
- .build());
- Resources resources = ${serviceClass}.this.getResources();
- mYOffset = resources.getDimension(R.dimen.digital_y_offset);
-
- mBackgroundPaint = new Paint();
- mBackgroundPaint.setColor(resources.getColor(R.color.digital_background));
-
- mTextPaint = new Paint();
- mTextPaint = createTextPaint(resources.getColor(R.color.digital_text));
-
- mTime = new Time();
- }
-
- @Override
- public void onDestroy() {
- mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
- super.onDestroy();
- }
-
- private Paint createTextPaint(int textColor) {
- Paint paint = new Paint();
- paint.setColor(textColor);
- paint.setTypeface(NORMAL_TYPEFACE);
- paint.setAntiAlias(true);
- return paint;
- }
-
- @Override
- public void onVisibilityChanged(boolean visible) {
- super.onVisibilityChanged(visible);
-
- if (visible) {
- registerReceiver();
-
- // Update time zone in case it changed while we weren't visible.
- mTime.clear(TimeZone.getDefault().getID());
- mTime.setToNow();
- } else {
- unregisterReceiver();
- }
-
- // Whether the timer should be running depends on whether we're visible (as well as
- // whether we're in ambient mode), so we may need to start or stop the timer.
- updateTimer();
- }
-
- private void registerReceiver() {
- if (mRegisteredTimeZoneReceiver) {
- return;
- }
- mRegisteredTimeZoneReceiver = true;
- IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
- ${serviceClass}.this.registerReceiver(mTimeZoneReceiver, filter);
- }
-
- private void unregisterReceiver() {
- if (!mRegisteredTimeZoneReceiver) {
- return;
- }
- mRegisteredTimeZoneReceiver = false;
- ${serviceClass}.this.unregisterReceiver(mTimeZoneReceiver);
- }
-
- @Override
- public void onApplyWindowInsets(WindowInsets insets) {
- super.onApplyWindowInsets(insets);
-
- // Load resources that have alternate values for round watches.
- Resources resources = ${serviceClass}.this.getResources();
- boolean isRound = insets.isRound();
- mXOffset = resources.getDimension(isRound
- ? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset);
- float textSize = resources.getDimension(isRound
- ? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
-
- mTextPaint.setTextSize(textSize);
- }
-
- @Override
- public void onPropertiesChanged(Bundle properties) {
- super.onPropertiesChanged(properties);
- mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
- }
-
- @Override
- public void onTimeTick() {
- super.onTimeTick();
- invalidate();
- }
-
- @Override
- public void onAmbientModeChanged(boolean inAmbientMode) {
- super.onAmbientModeChanged(inAmbientMode);
- if (mAmbient != inAmbientMode) {
- mAmbient = inAmbientMode;
- if (mLowBitAmbient) {
- mTextPaint.setAntiAlias(!inAmbientMode);
- }
- invalidate();
- }
-
- // Whether the timer should be running depends on whether we're visible (as well as
- // whether we're in ambient mode), so we may need to start or stop the timer.
- updateTimer();
- }
-
- @Override
- public void onDraw(Canvas canvas, Rect bounds) {
- // Draw the background.
- canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
-
- // Draw H:MM in ambient mode or H:MM:SS in interactive mode.
- mTime.setToNow();
- String text = mAmbient
- ? String.format("%d:%02d", mTime.hour, mTime.minute)
- : String.format("%d:%02d:%02d", mTime.hour, mTime.minute, mTime.second);
- canvas.drawText(text, mXOffset, mYOffset, mTextPaint);
- }
-
- /**
- * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
- * or stops it if it shouldn't be running but currently is.
- */
- private void updateTimer() {
- mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
- if (shouldTimerBeRunning()) {
- mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
- }
- }
-
- /**
- * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
- * only run when we're visible and in interactive mode.
- */
- private boolean shouldTimerBeRunning() {
- return isVisible() && !isInAmbientMode();
- }
-
- /**
- * Handle updating the time periodically in interactive mode.
- */
- private void handleUpdateTimeMessage() {
- invalidate();
- if (shouldTimerBeRunning()) {
- long timeMs = System.currentTimeMillis();
- long delayMs = INTERACTIVE_UPDATE_RATE_MS
- - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
- mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
- }
- }
- }
-
- private static class EngineHandler extends Handler {
- private final WeakReference<${serviceClass}.Engine> mWeakReference;
-
- public EngineHandler(${serviceClass}.Engine reference) {
- mWeakReference = new WeakReference<>(reference);
- }
-
- @Override
- public void handleMessage(Message msg) {
- ${serviceClass}.Engine engine = mWeakReference.get();
- if (engine != null) {
- switch (msg.what) {
- case MSG_UPDATE_TIME:
- engine.handleUpdateTimeMessage();
- break;
- }
- }
- }
- }
-}
diff --git a/base/templates/other/WatchFaceService/template.xml b/base/templates/other/WatchFaceService/template.xml
deleted file mode 100644
index 982f2b7..0000000
--- a/base/templates/other/WatchFaceService/template.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="4"
- revision="2"
- name="Watch Face"
- minApi="21"
- minBuildApi="21"
- description="Creates a watch face for Android Wear">
-
- <category value="Wear" />
- <formfactor value="Wear" />
-
- <parameter
- id="serviceClass"
- name="Service Name"
- type="string"
- constraints="class|unique|nonempty"
- default="MyWatchFace"
- help="The name of the service class to create" />
-
- <parameter
- id="style"
- name="Style"
- type="enum"
- default="analog"
- help="Watch face style">
- <option id="analog">Analog</option>
- <option id="digital">Digital</option>
- </parameter>
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package"
- default="com.mycompany.myapp" />
-
- <!-- 128x128 thumbnails relative to template.xml -->
- <thumbs>
- <!-- default thumbnail is required -->
- <thumb>template_thumbnail.png</thumb>
- <thumb style="analog">analog_round.png</thumb>
- <thumb style="digital">digital_square.png</thumb>
- </thumbs>
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/base/testutils/.settings/org.eclipse.jdt.core.prefs b/base/testutils/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index ea66196..0000000
--- a/base/testutils/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,98 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
-org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning
-org.eclipse.jdt.core.compiler.problem.nullReference=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning
-org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
-org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=enabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/base/testutils/src/main/java/com/android/testutils/SdkTestCase.java b/base/testutils/src/main/java/com/android/testutils/SdkTestCase.java
deleted file mode 100644
index 75877a5..0000000
--- a/base/testutils/src/main/java/com/android/testutils/SdkTestCase.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.testutils;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-import com.google.common.io.InputSupplier;
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Common test case for SDK unit tests. Contains a number of general utility methods
- * to help writing test cases, such as looking up a temporary directory, comparing golden
- * files, computing string diffs, etc.
- */
- at SuppressWarnings("javadoc")
-public abstract class SdkTestCase extends TestCase {
- /** Update golden files if different from the actual results */
- private static final boolean UPDATE_DIFFERENT_FILES = false;
- /** Create golden files if missing */
- private static final boolean UPDATE_MISSING_FILES = true;
- private static File sTempDir = null;
- protected static Set<File> sCleanDirs = Sets.newHashSet();
-
- protected String getTestDataRelPath() {
- fail("Must be overridden");
- return null;
- }
-
- public static int getCaretOffset(String fileContent, String caretLocation) {
- assertTrue(caretLocation, caretLocation.contains("^")); //$NON-NLS-1$
-
- int caretDelta = caretLocation.indexOf("^"); //$NON-NLS-1$
- assertTrue(caretLocation, caretDelta != -1);
-
- // String around caret/range without the range and caret marker characters
- String caretContext;
- if (caretLocation.contains("[^")) { //$NON-NLS-1$
- caretDelta--;
- assertTrue(caretLocation, caretLocation.startsWith("[^", caretDelta)); //$NON-NLS-1$
- int caretRangeEnd = caretLocation.indexOf(']', caretDelta + 2);
- assertTrue(caretLocation, caretRangeEnd != -1);
- caretContext = caretLocation.substring(0, caretDelta)
- + caretLocation.substring(caretDelta + 2, caretRangeEnd)
- + caretLocation.substring(caretRangeEnd + 1);
- } else {
- caretContext = caretLocation.substring(0, caretDelta)
- + caretLocation.substring(caretDelta + 1); // +1: skip "^"
- }
-
- int caretContextIndex = fileContent.indexOf(caretContext);
- assertTrue("Caret content " + caretContext + " not found in file",
- caretContextIndex != -1);
- return caretContextIndex + caretDelta;
- }
-
- public static String addSelection(String newFileContents, int selectionBegin, int selectionEnd) {
- // Insert selection markers -- [ ] for the selection range, ^ for the caret
- String newFileWithCaret;
- if (selectionBegin < selectionEnd) {
- newFileWithCaret = newFileContents.substring(0, selectionBegin) + "[^"
- + newFileContents.substring(selectionBegin, selectionEnd) + "]"
- + newFileContents.substring(selectionEnd);
- } else {
- // Selected range
- newFileWithCaret = newFileContents.substring(0, selectionBegin) + "^"
- + newFileContents.substring(selectionBegin);
- }
-
- return newFileWithCaret;
- }
-
- public static String getCaretContext(String file, int offset) {
- int windowSize = 20;
- int begin = Math.max(0, offset - windowSize / 2);
- int end = Math.min(file.length(), offset + windowSize / 2);
-
- return "..." + file.substring(begin, offset) + "^" + file.substring(offset, end) + "...";
- }
-
- /** Get the location to write missing golden files to */
- protected File getTargetDir() {
- // Set $ADT_SDK_SOURCE_PATH to point to your git "sdk" directory; if done, then
- // if you run a unit test which refers to a golden file which does not exist, it
- // will be created directly into the test data directory and you can rerun the
- // test
- // and it should pass (after you verify that the golden file contains the correct
- // result of course).
- String sdk = System.getenv("ADT_SDK_SOURCE_PATH");
- if (sdk != null) {
- File sdkPath = new File(sdk);
- if (sdkPath.exists()) {
- File testData = new File(sdkPath, getTestDataRelPath().replace('/',
- File.separatorChar));
- if (testData.exists()) {
- addCleanupDir(testData);
- return testData;
- }
- }
- }
- return getTempDir();
- }
-
- public static File getTempDir() {
- if (sTempDir == null) {
- File base = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
- base = new File("/tmp"); //$NON-NLS-1$
- }
-
- // On Windows, we don't want to pollute the temp folder (which is generally
- // already incredibly busy). So let's create a temp folder for the results.
-
- Calendar c = Calendar.getInstance();
- String name = String.format("sdkTests_%1$tF_%1$tT", c).replace(':', '-'); //$NON-NLS-1$
- File tmpDir = new File(base, name);
- if (!tmpDir.exists() && tmpDir.mkdir()) {
- sTempDir = tmpDir;
- } else {
- sTempDir = base;
- }
- addCleanupDir(sTempDir);
- }
-
- return sTempDir;
- }
-
- protected String removeSessionData(String data) {
- return data;
- }
-
- protected InputStream getTestResource(String relativePath, boolean expectExists) {
- String path = "testdata" + File.separator + relativePath; //$NON-NLS-1$
- InputStream stream = SdkTestCase.class.getResourceAsStream(path);
- if (!expectExists && stream == null) {
- return null;
- }
- return stream;
- }
-
- @SuppressWarnings("resource")
- protected String readTestFile(String relativePath, boolean expectExists) throws IOException {
- InputStream stream = getTestResource(relativePath, expectExists);
- if (expectExists) {
- assertNotNull(relativePath + " does not exist", stream);
- } else if (stream == null) {
- return null;
- }
-
- String xml = new String(ByteStreams.toByteArray(stream), Charsets.UTF_8);
- try {
- Closeables.close(stream, true /* swallowIOException */);
- } catch (IOException e) {
- // cannot happen
- }
-
- assertTrue(!xml.isEmpty());
-
- // Remove any references to the project name such that we are isolated from
- // that in golden file.
- // Appears in strings.xml etc.
- xml = removeSessionData(xml);
-
- return xml;
- }
-
- protected void assertEqualsGolden(String basename, String actual) throws IOException {
- assertEqualsGolden(basename, actual, basename.substring(basename.lastIndexOf('.') + 1));
- }
-
- protected void assertEqualsGolden(String basename, String actual, String newExtension)
- throws IOException {
- String testName = getName();
- if (testName.startsWith("test")) {
- testName = testName.substring(4);
- if (Character.isUpperCase(testName.charAt(0))) {
- testName = Character.toLowerCase(testName.charAt(0)) + testName.substring(1);
- }
- }
- String expectedName;
- String extension = basename.substring(basename.lastIndexOf('.') + 1);
- if (newExtension == null) {
- newExtension = extension;
- }
- expectedName = basename.substring(0, basename.indexOf('.'))
- + "-expected-" + testName + '.' + newExtension;
- String expected = readTestFile(expectedName, false);
- if (expected == null) {
- File expectedPath = new File(
- UPDATE_MISSING_FILES ? getTargetDir() : getTempDir(), expectedName);
- Files.write(actual, expectedPath, Charsets.UTF_8);
- System.out.println("Expected - written to " + expectedPath + ":\n");
- System.out.println(actual);
- fail("Did not find golden file (" + expectedName + "): Wrote contents as "
- + expectedPath);
- } else {
- if (!expected.replaceAll("\r\n", "\n").equals(actual.replaceAll("\r\n", "\n"))) {
- File expectedPath = new File(getTempDir(), expectedName);
- File actualPath = new File(getTempDir(),
- expectedName.replace("expected", "actual"));
- Files.write(expected, expectedPath, Charsets.UTF_8);
- Files.write(actual, actualPath, Charsets.UTF_8);
- // Also update data dir with the current value
- if (UPDATE_DIFFERENT_FILES) {
- Files.write(actual, new File(getTargetDir(), expectedName), Charsets.UTF_8);
- }
- System.out.println("The files differ: diff " + expectedPath + " "
- + actualPath);
- assertEquals("The files differ - see " + expectedPath + " versus " + actualPath,
- expected, actual);
- }
- }
- }
-
- /** Creates a diff of two strings */
- public static String getDiff(String before, String after) {
- return getDiff(before.split("\n"), after.split("\n"));
- }
-
- public static String getDiff(String[] before, String[] after) {
- // Based on the LCS section in http://introcs.cs.princeton.edu/java/96optimization/
- StringBuilder sb = new StringBuilder();
-
- int n = before.length;
- int m = after.length;
-
- // Compute longest common subsequence of x[i..m] and y[j..n] bottom up
- int[][] lcs = new int[n + 1][m + 1];
- for (int i = n - 1; i >= 0; i--) {
- for (int j = m - 1; j >= 0; j--) {
- if (before[i].equals(after[j])) {
- lcs[i][j] = lcs[i + 1][j + 1] + 1;
- } else {
- lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1]);
- }
- }
- }
-
- int i = 0;
- int j = 0;
- while ((i < n) && (j < m)) {
- if (before[i].equals(after[j])) {
- i++;
- j++;
- } else {
- sb.append("@@ -");
- sb.append(Integer.toString(i + 1));
- sb.append(" +");
- sb.append(Integer.toString(j + 1));
- sb.append('\n');
- while (i < n && j < m && !before[i].equals(after[j])) {
- if (lcs[i + 1][j] >= lcs[i][j + 1]) {
- sb.append('-');
- if (!before[i].trim().isEmpty()) {
- sb.append(' ');
- }
- sb.append(before[i]);
- sb.append('\n');
- i++;
- } else {
- sb.append('+');
- if (!after[j].trim().isEmpty()) {
- sb.append(' ');
- }
- sb.append(after[j]);
- sb.append('\n');
- j++;
- }
- }
- }
- }
-
- if (i < n || j < m) {
- assert i == n || j == m;
- sb.append("@@ -");
- sb.append(Integer.toString(i + 1));
- sb.append(" +");
- sb.append(Integer.toString(j + 1));
- sb.append('\n');
- for (; i < n; i++) {
- sb.append('-');
- if (!before[i].trim().isEmpty()) {
- sb.append(' ');
- }
- sb.append(before[i]);
- sb.append('\n');
- }
- for (; j < m; j++) {
- sb.append('+');
- if (!after[j].trim().isEmpty()) {
- sb.append(' ');
- }
- sb.append(after[j]);
- sb.append('\n');
- }
- }
-
- return sb.toString();
- }
-
- protected void deleteFile(File dir) {
- TestUtils.deleteFile(dir);
- }
-
- protected File makeTestFile(String name, String relative,
- final InputStream contents) throws IOException {
- return makeTestFile(getTargetDir(), name, relative, contents);
- }
-
- protected File makeTestFile(File dir, String name, String relative,
- final InputStream contents) throws IOException {
- if (relative != null) {
- dir = new File(dir, relative);
- if (!dir.exists()) {
- boolean mkdir = dir.mkdirs();
- assertTrue(dir.getPath(), mkdir);
- }
- } else if (!dir.exists()) {
- boolean mkdir = dir.mkdirs();
- assertTrue(dir.getPath(), mkdir);
- }
- File tempFile = new File(dir, name);
- if (tempFile.exists()) {
- tempFile.delete();
- }
-
- Files.copy(new InputSupplier<InputStream>() {
- @Override
- public InputStream getInput() throws IOException {
- return contents;
- }
- }, tempFile);
-
- return tempFile;
- }
-
- /**
- * Test file description, which can copy from resource directory or from
- * a specified hardcoded string literal, and copy into a target directory
- */
- public class TestFile {
- public String sourceRelativePath;
- public String targetRelativePath;
- public String contents;
-
- public TestFile() {
- }
-
- public TestFile withSource(@NonNull String source) {
- contents = source;
- return this;
- }
-
- public TestFile from(@NonNull String from) {
- sourceRelativePath = from;
- return this;
- }
-
- public TestFile to(@NonNull String to) {
- targetRelativePath = to;
- return this;
- }
-
- public TestFile copy(@NonNull String relativePath) {
- // Support replacing filenames and paths with a => syntax, e.g.
- // dir/file.txt=>dir2/dir3/file2.java
- // will read dir/file.txt from the test data and write it into the target
- // directory as dir2/dir3/file2.java
- String targetPath = relativePath;
- int replaceIndex = relativePath.indexOf("=>"); //$NON-NLS-1$
- if (replaceIndex != -1) {
- // foo=>bar
- targetPath = relativePath.substring(replaceIndex + "=>".length());
- relativePath = relativePath.substring(0, replaceIndex);
- }
- sourceRelativePath = relativePath;
- targetRelativePath = targetPath;
- return this;
- }
-
- @NonNull
- public File createFile(@NonNull File targetDir) throws IOException {
- InputStream stream;
- if (contents != null) {
- stream = new ByteArrayInputStream(contents.getBytes(Charsets.UTF_8));
- } else {
- stream = getTestResource(sourceRelativePath, true);
- }
- assertNotNull(sourceRelativePath + " does not exist", stream);
- int index = targetRelativePath.lastIndexOf('/');
- String relative = null;
- String name = targetRelativePath;
- if (index != -1) {
- name = targetRelativePath.substring(index + 1);
- relative = targetRelativePath.substring(0, index);
- }
-
- return makeTestFile(targetDir, name, relative, stream);
- }
- }
-
- protected File getTestfile(File targetDir, String relativePath) throws IOException {
- // Support replacing filenames and paths with a => syntax, e.g.
- // dir/file.txt=>dir2/dir3/file2.java
- // will read dir/file.txt from the test data and write it into the target
- // directory as dir2/dir3/file2.java
-
- String targetPath = relativePath;
- int replaceIndex = relativePath.indexOf("=>"); //$NON-NLS-1$
- if (replaceIndex != -1) {
- // foo=>bar
- targetPath = relativePath.substring(replaceIndex + "=>".length());
- relativePath = relativePath.substring(0, replaceIndex);
- }
-
- InputStream stream = getTestResource(relativePath, true);
- assertNotNull(relativePath + " does not exist", stream);
- int index = targetPath.lastIndexOf('/');
- String relative = null;
- String name = targetPath;
- if (index != -1) {
- name = targetPath.substring(index + 1);
- relative = targetPath.substring(0, index);
- }
-
- return makeTestFile(targetDir, name, relative, stream);
- }
-
- protected static void addCleanupDir(File dir) {
- sCleanDirs.add(dir);
- try {
- sCleanDirs.add(dir.getCanonicalFile());
- } catch (IOException e) {
- fail(e.getLocalizedMessage());
- }
- sCleanDirs.add(dir.getAbsoluteFile());
- }
-
- protected String cleanup(String result) {
- List<File> sorted = new ArrayList<File>(sCleanDirs);
- // Process dirs in order such that we match longest substrings first
- Collections.sort(sorted, new Comparator<File>() {
- @Override
- public int compare(File file1, File file2) {
- String path1 = file1.getPath();
- String path2 = file2.getPath();
- int delta = path2.length() - path1.length();
- if (delta != 0) {
- return delta;
- } else {
- return path1.compareTo(path2);
- }
- }
- });
-
- for (File dir : sorted) {
- if (result.contains(dir.getPath())) {
- result = result.replace(dir.getPath(), "/TESTROOT");
- }
- }
-
- // The output typically contains a few directory/filenames.
- // On Windows we need to change the separators to the unix-style
- // forward slash to make the test as OS-agnostic as possible.
- if (File.separatorChar != '/') {
- result = result.replace(File.separatorChar, '/');
- }
-
- return result;
- }
-
- /** Get the location to write missing golden files to */
- protected File findSrcDir() {
- // Set $ANDROID_SRC to point to your git AOSP working tree
- String rootPath = System.getenv("ANDROID_SRC");
- if (rootPath == null) {
- String sdk = System.getenv("ADT_SDK_SOURCE_PATH");
- if (sdk != null) {
- File root = new File(sdk);
- if (root.exists()) {
- return root.getName().equals("sdk") ? root.getParentFile() : root;
- }
- }
- } else {
- File root = new File(rootPath);
- if (root.exists()) {
- return root;
- }
- }
-
- return null;
- }
-
- protected File findSrcRelativeDir(String relative) {
- // Set $ANDROID_SRC to point to your git AOSP working tree
- File root = findSrcDir();
- if (root != null) {
- File testData = new File(root, relative.replace('/', File.separatorChar));
- if (testData.exists()) {
- return testData;
- }
- }
-
- return null;
- }
-}
diff --git a/base/build-system/.gitignore b/build-system/.gitignore
similarity index 100%
rename from base/build-system/.gitignore
rename to build-system/.gitignore
diff --git a/base/build-system/builder-model/NOTICE b/build-system/builder-model/NOTICE
similarity index 100%
rename from base/build-system/builder-model/NOTICE
rename to build-system/builder-model/NOTICE
diff --git a/base/build-system/builder-model/build.gradle b/build-system/builder-model/build.gradle
similarity index 100%
rename from base/build-system/builder-model/build.gradle
rename to build-system/builder-model/build.gradle
diff --git a/base/build-system/builder-model/builder-model.iml b/build-system/builder-model/builder-model.iml
similarity index 100%
rename from base/build-system/builder-model/builder-model.iml
rename to build-system/builder-model/builder-model.iml
diff --git a/base/build-system/builder-model/src/main/java/com/android/build/FilterData.java b/build-system/builder-model/src/main/java/com/android/build/FilterData.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/build/FilterData.java
rename to build-system/builder-model/src/main/java/com/android/build/FilterData.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/build/OutputFile.java b/build-system/builder-model/src/main/java/com/android/build/OutputFile.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/build/OutputFile.java
rename to build-system/builder-model/src/main/java/com/android/build/OutputFile.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/build/VariantOutput.java b/build-system/builder-model/src/main/java/com/android/build/VariantOutput.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/build/VariantOutput.java
rename to build-system/builder-model/src/main/java/com/android/build/VariantOutput.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java
new file mode 100644
index 0000000..0c11ef2
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Options for aapt.
+ */
+public interface AaptOptions {
+ /**
+ * Returns the value for the --ignore-assets option, or null
+ */
+ String getIgnoreAssets();
+
+ /**
+ * Returns the list of values for the -0 (disabled compression) option, or null
+ */
+ Collection<String> getNoCompress();
+
+ /**
+ * passes the --error-on-missing-config-entry parameter to the aapt command, by default false.
+ */
+ boolean getFailOnMissingConfigEntry();
+
+ /**
+ * Returns the list of additional parameters to pass.
+ */
+ List<String> getAdditionalParameters();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/AdbOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/AdbOptions.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/AdbOptions.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/AdbOptions.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java
new file mode 100644
index 0000000..fb7d5ab
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An Android Artifact.
+ *
+ * This is the entry point for the output of a {@link Variant}. This can be more than one
+ * output in the case of multi-apk where more than one APKs are generated from the same set
+ * of sources.
+ *
+ */
+public interface AndroidArtifact extends BaseArtifact {
+
+ @NonNull
+ Collection<AndroidArtifactOutput> getOutputs();
+
+ /**
+ * Returns whether the output file is signed. This is always false for the main artifact
+ * of a library project.
+ *
+ * @return true if the app is signed.
+ */
+ boolean isSigned();
+
+ /**
+ * Returns the name of the {@link SigningConfig} used for the signing. If none are setup or
+ * if this is the main artifact of a library project, then this is null.
+ *
+ * @return the name of the setup signing config.
+ */
+ @Nullable
+ String getSigningConfigName();
+
+ /**
+ * Returns the application id of this artifact.
+ *
+ * @return the application id.
+ */
+ @NonNull
+ String getApplicationId();
+
+ /**
+ * Returns the name of the task used to generate the source code. The actual value might
+ * depend on the build system front end.
+ *
+ * @return the name of the code generating task.
+ */
+ @NonNull
+ String getSourceGenTaskName();
+
+ /**
+ * Returns all the source folders that are generated. This is typically folders for the R,
+ * the aidl classes, and the renderscript classes.
+ *
+ * Deprecated, as of 1.2, present in super interface.
+ *
+ * @return a list of folders.
+ */
+ @NonNull
+ @Override
+ Collection<File> getGeneratedSourceFolders();
+
+ /**
+ * Returns all the resource folders that are generated. This is typically the renderscript
+ * output and the merged resources.
+ *
+ * @return a list of folder.
+ */
+ @NonNull
+ Collection<File> getGeneratedResourceFolders();
+
+ /**
+ * Returns the ABI filters associated with the artifact, or null if there are no filters.
+ *
+ * If the list contains values, then the artifact only contains these ABIs and excludes
+ * others.
+ */
+ @Nullable
+ Set<String> getAbiFilters();
+
+ /**
+ * Returns the native libraries associated with the artifact.
+ */
+ @Nullable
+ Collection<NativeLibrary> getNativeLibraries();
+
+ /**
+ * Map of Build Config Fields where the key is the field name.
+ *
+ * @return a non-null map of class fields (possibly empty).
+ */
+ @NonNull
+ Map<String, ClassField> getBuildConfigFields();
+
+ /**
+ * Map of generated res values where the key is the res name.
+ *
+ * @return a non-null map of class fields (possibly empty).
+ */
+ @NonNull
+ Map<String, ClassField> getResValues();
+
+ /**
+ * Returns the InstantRun feature related model.
+ * @return the model for all InstantRun related information.
+ */
+ @NonNull
+ InstantRun getInstantRun();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifactOutput.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifactOutput.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifactOutput.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifactOutput.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidLibrary.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidLibrary.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/AndroidLibrary.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/AndroidLibrary.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
new file mode 100644
index 0000000..ccefe13
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Entry point for the model of the Android Projects. This models a single module, whether
+ * the module is an app project or a library project.
+ */
+public interface AndroidProject {
+ // Injectable properties to use with -P
+ // Sent by Studio 1.0 ONLY
+ String PROPERTY_BUILD_MODEL_ONLY = "android.injected.build.model.only";
+ // Sent by Studio 1.1+
+ String PROPERTY_BUILD_MODEL_ONLY_ADVANCED = "android.injected.build.model.only.advanced";
+ // Sent by Studio 1.5+
+ String PROPERTY_BUILD_API = "android.injected.build.api";
+ String PROPERTY_BUILD_ABI = "android.injected.build.abi";
+ String PROPERTY_BUILD_DENSITY = "android.injected.build.density";
+
+ String PROPERTY_INVOKED_FROM_IDE = "android.injected.invoked.from.ide";
+
+ String PROPERTY_SIGNING_STORE_FILE = "android.injected.signing.store.file";
+ String PROPERTY_SIGNING_STORE_PASSWORD = "android.injected.signing.store.password";
+ String PROPERTY_SIGNING_KEY_ALIAS = "android.injected.signing.key.alias";
+ String PROPERTY_SIGNING_KEY_PASSWORD = "android.injected.signing.key.password";
+ String PROPERTY_SIGNING_STORE_TYPE = "android.injected.signing.store.type";
+
+ String PROPERTY_SIGNING_COLDSWAP_MODE = "android.injected.coldswap.mode";
+
+ // InstantDev related properties, must be ',' separated list of OptionalCompilationStep values.
+ String OPTIONAL_COMPILATION_STEPS = "android.optional.compilation";
+
+ String PROPERTY_APK_LOCATION = "android.injected.apk.location";
+
+ String ARTIFACT_MAIN = "_main_";
+ String ARTIFACT_ANDROID_TEST = "_android_test_";
+ String ARTIFACT_UNIT_TEST = "_unit_test_";
+
+ String FD_INTERMEDIATES = "intermediates";
+ String FD_LOGS = "logs";
+ String FD_OUTPUTS = "outputs";
+ String FD_GENERATED = "generated";
+
+ int GENERATION_ORIGINAL = 1;
+ int GENERATION_COMPONENT = 2;
+
+ /**
+ * Returns the model version. This is a string in the format X.Y.Z
+ *
+ * @return a string containing the model version.
+ */
+ @NonNull
+ String getModelVersion();
+
+ /**
+ * Returns the model api version.
+ * <p/>
+ * This is different from {@link #getModelVersion()} in a way that new model
+ * version might increment model version but keep existing api. That means that
+ * code which was built against particular 'api version' might be safely re-used for all
+ * new model versions as long as they don't change the api.
+ * <p/>
+ * Every new model version is assumed to return an 'api version' value which
+ * is equal or greater than the value used by the previous model version.
+ *
+ * @return model's api version
+ */
+ int getApiVersion();
+
+ /**
+ * Returns the name of the module.
+ *
+ * @return the name of the module.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns whether this is a library.
+ * @return true for a library module.
+ */
+ boolean isLibrary();
+
+ /**
+ * Returns the {@link ProductFlavorContainer} for the 'main' default config.
+ *
+ * @return the product flavor.
+ */
+ @NonNull
+ ProductFlavorContainer getDefaultConfig();
+
+ /**
+ * Returns a list of all the {@link BuildType} in their container.
+ *
+ * @return a list of build type containers.
+ */
+ @NonNull
+ Collection<BuildTypeContainer> getBuildTypes();
+
+ /**
+ * Returns a list of all the {@link ProductFlavor} in their container.
+ *
+ * @return a list of product flavor containers.
+ */
+ @NonNull
+ Collection<ProductFlavorContainer> getProductFlavors();
+
+ /**
+ * Returns a list of all the variants.
+ *
+ * This does not include test variant. Test variants are additional artifacts in their
+ * respective variant info.
+ *
+ * @return a list of the variants.
+ */
+ @NonNull
+ Collection<Variant> getVariants();
+
+ /**
+ * Returns a list of all the flavor dimensions, may be empty.
+ *
+ * @return a list of the flavor dimensions.
+ */
+ @NonNull
+ Collection<String> getFlavorDimensions();
+
+ /**
+ * Returns a list of extra artifacts meta data. This does not include the main artifact.
+ *
+ * @return a list of extra artifacts
+ */
+ @NonNull
+ Collection<ArtifactMetaData> getExtraArtifacts();
+
+ /**
+ * Returns the compilation target as a string. This is the full extended target hash string.
+ * (see com.android.sdklib.IAndroidTarget#hashString())
+ *
+ * @return the target hash string
+ */
+ @NonNull
+ String getCompileTarget();
+
+ /**
+ * Returns the boot classpath matching the compile target. This is typically android.jar plus
+ * other optional libraries.
+ *
+ * @return a list of jar files.
+ */
+ @NonNull
+ Collection<String> getBootClasspath();
+
+ /**
+ * Returns a list of folders or jar files that contains the framework source code.
+ */
+ @NonNull
+ Collection<File> getFrameworkSources();
+
+ /**
+ * Returns the collection of toolchains used to create any native libraries.
+ *
+ * @return collection of toolchains.
+ */
+ @NonNull
+ Collection<NativeToolchain> getNativeToolchains();
+
+ /**
+ * Returns a list of {@link SigningConfig}.
+ */
+ @NonNull
+ Collection<SigningConfig> getSigningConfigs();
+
+ /**
+ * Returns the aapt options.
+ */
+ @NonNull
+ AaptOptions getAaptOptions();
+
+ /**
+ * Returns the lint options.
+ */
+ @NonNull
+ LintOptions getLintOptions();
+
+ /**
+ * Returns the dependencies that were not successfully resolved. The returned list gets
+ * populated only if the system property {@link #PROPERTY_BUILD_MODEL_ONLY} has been
+ * set to {@code true}.
+ * <p>
+ * Each value of the collection has the format group:name:version, for example:
+ * com.google.guava:guava:15.0.2
+ *
+ * @return the dependencies that were not successfully resolved.
+ * @deprecated use {@link #getSyncIssues()}
+ */
+ @Deprecated
+ @NonNull
+ Collection<String> getUnresolvedDependencies();
+
+ /**
+ * Returns issues found during sync. The returned list gets
+ * populated only if the system property {@link #PROPERTY_BUILD_MODEL_ONLY} has been
+ * set to {@code true}.
+ */
+ @NonNull
+ Collection<SyncIssue> getSyncIssues();
+
+ /**
+ * Returns the compile options for Java code.
+ */
+ @NonNull
+ JavaCompileOptions getJavaCompileOptions();
+
+ /**
+ * Returns the build folder of this project.
+ */
+ @NonNull
+ File getBuildFolder();
+
+ /**
+ * Returns the resource prefix to use, if any. This is an optional prefix which can
+ * be set and which is used by the defaults to automatically choose new resources
+ * with a certain prefix, warn if resources are not using the given prefix, etc.
+ * This helps work with resources in the app namespace where there could otherwise
+ * be unintentional duplicated resource names between unrelated libraries.
+ *
+ * @return the optional resource prefix, or null if not set
+ */
+ @Nullable
+ String getResourcePrefix();
+
+ /**
+ * Returns the build tools version used by this module.
+ * @return the build tools version.
+ */
+ @NonNull
+ String getBuildToolsVersion();
+
+ /**
+ * Returns the generation of the plugin.
+ *
+ * 1: original plugin
+ * 2: component based plugin (AKA experimental)
+ * @return the generation value
+ */
+ int getPluginGeneration();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/ApiVersion.java b/build-system/builder-model/src/main/java/com/android/builder/model/ApiVersion.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/ApiVersion.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/ApiVersion.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/ArtifactMetaData.java b/build-system/builder-model/src/main/java/com/android/builder/model/ArtifactMetaData.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/ArtifactMetaData.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/ArtifactMetaData.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/BaseArtifact.java b/build-system/builder-model/src/main/java/com/android/builder/model/BaseArtifact.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/BaseArtifact.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/BaseArtifact.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java b/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java
new file mode 100644
index 0000000..234f9d9
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base config object for Build Type and Product flavor.
+ */
+public interface BaseConfig {
+
+ @NonNull
+ String getName();
+
+ /**
+ * Returns the application id suffix applied to this base config.
+ * To get the final application id, use {@link AndroidArtifact#getApplicationId()}.
+ *
+ * @return the application id
+ */
+ @Nullable
+ String getApplicationIdSuffix();
+
+ /**
+ * Map of Build Config Fields where the key is the field name.
+ *
+ * @return a non-null map of class fields (possibly empty).
+ */
+ @NonNull
+ Map<String, ClassField> getBuildConfigFields();
+
+ /**
+ * Map of generated res values where the key is the res name.
+ *
+ * @return a non-null map of class fields (possibly empty).
+ */
+ @NonNull
+ Map<String, ClassField> getResValues();
+
+ /**
+ * Returns the collection of proguard rule files.
+ *
+ * <p>These files are only applied to the production code.
+ *
+ * @return a non-null collection of files.
+ * @see #getTestProguardFiles()
+ */
+ @NonNull
+ Collection<File> getProguardFiles();
+
+ /**
+ * Returns the collection of proguard rule files for consumers of the library to use.
+ *
+ * @return a non-null collection of files.
+ */
+ @NonNull
+ Collection<File> getConsumerProguardFiles();
+
+ /**
+ * Returns the collection of proguard rule files to use for the test APK.
+ *
+ * @return a non-null collection of files.
+ */
+ @NonNull
+ Collection<File> getTestProguardFiles();
+
+ /**
+ * Returns the map of key value pairs for placeholder substitution in the android manifest file.
+ *
+ * This map will be used by the manifest merger.
+ * @return the map of key value pairs.
+ */
+ @NonNull
+ Map<String, Object> getManifestPlaceholders();
+
+ /**
+ * Returns whether multi-dex is enabled.
+ *
+ * This can be null if the flag is not set, in which case the default value is used.
+ */
+ @Nullable
+ Boolean getMultiDexEnabled();
+
+ @Nullable
+ File getMultiDexKeepFile();
+
+ @Nullable
+ File getMultiDexKeepProguard();
+
+ /**
+ * Returns the optional jarjar rule files, or empty if jarjar should be skipped.
+ * If more than one file is provided, the rule files will be merged in order with last one
+ * win in case of rule redefinition.
+ * Can only be used with Jack toolchain.
+ * @return the optional jarjar rule file.
+ */
+ @NonNull
+ List<File> getJarJarRuleFiles();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java b/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java
new file mode 100644
index 0000000..4b85dc9
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * a Build Type. This is only the configuration of the build type.
+ *
+ * It does not include the sources or the dependencies. Those are available on the container
+ * or in the artifact info.
+ *
+ * @see BuildTypeContainer
+ * @see AndroidArtifact#getDependencies()
+ */
+public interface BuildType extends BaseConfig {
+
+ /**
+ * Returns the name of the build type.
+ *
+ * @return the name of the build type.
+ */
+ @Override
+ @NonNull
+ String getName();
+
+ /**
+ * Returns whether the build type is configured to generate a debuggable apk.
+ *
+ * @return true if the apk is debuggable
+ */
+ boolean isDebuggable();
+
+ /**
+ * Returns whether the build type is configured to be build with support for code coverage.
+ *
+ * @return true if code coverage is enabled.
+ */
+ boolean isTestCoverageEnabled();
+
+ /**
+ * Returns whether the build type is configured to be build with support for pseudolocales.
+ *
+ * @return true if code coverage is enabled.
+ */
+ boolean isPseudoLocalesEnabled();
+
+ /**
+ * Returns whether the build type is configured to generate an apk with debuggable native code.
+ *
+ * @return true if the apk is debuggable
+ */
+ boolean isJniDebuggable();
+
+ /**
+ * Returns whether the build type is configured to generate an apk with debuggable
+ * renderscript code.
+ *
+ * @return true if the apk is debuggable
+ */
+ boolean isRenderscriptDebuggable();
+
+ /**
+ * Returns the optimization level of the renderscript compilation.
+ *
+ * @return the optimization level.
+ */
+ int getRenderscriptOptimLevel();
+
+ /**
+ * Returns the version name suffix.
+ *
+ * @return the version name suffix.
+ */
+ @Nullable
+ String getVersionNameSuffix();
+
+ /**
+ * Returns whether minification is enabled for this build type.
+ *
+ * @return true if minification is enabled.
+ */
+ boolean isMinifyEnabled();
+
+ /**
+ * Return whether zipalign is enabled for this build type.
+ *
+ * @return true if zipalign is enabled.
+ */
+ boolean isZipAlignEnabled();
+
+ /**
+ * Returns whether the variant embeds the micro app.
+ */
+ boolean isEmbedMicroApp();
+
+ /**
+ * Returns the associated signing config or null if none are set on the build type.
+ */
+ @Nullable
+ SigningConfig getSigningConfig();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/BuildTypeContainer.java b/build-system/builder-model/src/main/java/com/android/builder/model/BuildTypeContainer.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/BuildTypeContainer.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/BuildTypeContainer.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/ClassField.java b/build-system/builder-model/src/main/java/com/android/builder/model/ClassField.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/ClassField.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/ClassField.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/DataBindingOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/DataBindingOptions.java
new file mode 100644
index 0000000..59fe0fe
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/DataBindingOptions.java
@@ -0,0 +1,38 @@
+/*
+ * 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.builder.model;
+
+/**
+ * Options for data binding
+ */
+public interface DataBindingOptions {
+
+ /**
+ * The version of data binding to use.
+ */
+ String getVersion();
+
+ /**
+ * Whether to enable data binding.
+ */
+ boolean isEnabled();
+
+ /**
+ * Whether to add the default data binding adapters.
+ */
+ boolean getAddDefaultAdapters();
+}
\ No newline at end of file
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/Dependencies.java b/build-system/builder-model/src/main/java/com/android/builder/model/Dependencies.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/Dependencies.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/Dependencies.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/DimensionAware.java b/build-system/builder-model/src/main/java/com/android/builder/model/DimensionAware.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/DimensionAware.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/DimensionAware.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/InstantRun.java b/build-system/builder-model/src/main/java/com/android/builder/model/InstantRun.java
new file mode 100644
index 0000000..175d77e
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/InstantRun.java
@@ -0,0 +1,47 @@
+/*
+ * 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.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * Model for InstantRun related information.
+ */
+public interface InstantRun {
+
+ /**
+ * Returns the name of the task used to generate the incremental .dex files from the last
+ * build.
+ */
+ @NonNull
+ String getIncrementalAssembleTaskName();
+
+ /**
+ * Returns the last incremental build information, including success or failure, verifier
+ * reason for requesting a restart, etc...
+ * @return a file location, possibly not existing.
+ */
+ @NonNull
+ File getInfoFile();
+
+ /**
+ * Whether the owner artifact supports Instant Run. This may depend on the toolchain used.
+ */
+ boolean isSupportedByArtifact();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/JavaArtifact.java b/build-system/builder-model/src/main/java/com/android/builder/model/JavaArtifact.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/JavaArtifact.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/JavaArtifact.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/JavaCompileOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/JavaCompileOptions.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/JavaCompileOptions.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/JavaCompileOptions.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java b/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java
new file mode 100644
index 0000000..8cb0c5b
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/JavaLibrary.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A Java library.
+ */
+public interface JavaLibrary extends Library {
+ /**
+ * Returns the library's jar file.
+ */
+ @NonNull
+ File getJarFile();
+
+ /**
+ * Returns the direct dependencies of this library.
+ */
+ @NonNull
+ List<? extends JavaLibrary> getDependencies();
+
+ /**
+ * Returns whether the dependency is provided.
+ */
+ boolean isProvided();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/Library.java b/build-system/builder-model/src/main/java/com/android/builder/model/Library.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/Library.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/Library.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
new file mode 100644
index 0000000..cce4a2c
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Options for lint.
+ * Example:
+ *
+ * <pre>
+ *
+ * android {
+ * lintOptions {
+ * // set to true to turn off analysis progress reporting by lint
+ * quiet true
+ * // if true, stop the gradle build if errors are found
+ * abortOnError false
+ * // set to true to have all release builds run lint on issues with severity=fatal
+ * // and abort the build (controlled by abortOnError above) if fatal issues are found
+ * checkReleaseBuilds true
+ * // if true, only report errors
+ * ignoreWarnings true
+ * // if true, emit full/absolute paths to files with errors (true by default)
+ * //absolutePaths true
+ * // if true, check all issues, including those that are off by default
+ * checkAllWarnings true
+ * // if true, treat all warnings as errors
+ * warningsAsErrors true
+ * // turn off checking the given issue id's
+ * disable 'TypographyFractions','TypographyQuotes'
+ * // turn on the given issue id's
+ * enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
+ * // check *only* the given issue id's
+ * check 'NewApi', 'InlinedApi'
+ * // if true, don't include source code lines in the error output
+ * noLines true
+ * // if true, show all locations for an error, do not truncate lists, etc.
+ * showAll true
+ * // whether lint should include full issue explanations in the text error output
+ * explainIssues false
+ * // Fallback lint configuration (default severities, etc.)
+ * lintConfig file("default-lint.xml")
+ * // if true, generate a text report of issues (false by default)
+ * textReport true
+ * // location to write the output; can be a file or 'stdout' or 'stderr'
+ * //textOutput 'stdout'
+ * textOutput file("lint-results.txt")
+ * // if true, generate an XML report for use by for example Jenkins
+ * xmlReport true
+ * // file to write report to (if not specified, defaults to lint-results.xml)
+ * xmlOutput file("lint-report.xml")
+ * // if true, generate an HTML report (with issue explanations, sourcecode, etc)
+ * htmlReport true
+ * // optional path to report (default will be lint-results.html in the builddir)
+ * htmlOutput file("lint-report.html")
+ * // Set the severity of the given issues to fatal (which means they will be
+ * // checked during release builds (even if the lint target is not included)
+ * fatal 'NewApi', 'InlineApi'
+ * // Set the severity of the given issues to error
+ * error 'Wakelock', 'TextViewEdits'
+ * // Set the severity of the given issues to warning
+ * warning 'ResourceAsColor'
+ * // Set the severity of the given issues to ignore (same as disabling the check)
+ * ignore 'TypographyQuotes'
+ * // Set the severity of the given issues to informational
+ * informational 'StopShip'
+ * }
+ * }
+ * </pre>
+ */
+public interface LintOptions {
+ /**
+ * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
+ * To suppress a given issue, add the lint issue id to the returned set.
+ */
+ @NonNull
+ Set<String> getDisable();
+
+ /**
+ * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
+ * To enable a given issue, add the lint issue id to the returned set.
+ */
+ @NonNull
+ Set<String> getEnable();
+
+ /**
+ * Returns the exact set of issues to check, or null to run the issues that are enabled
+ * by default plus any issues enabled via {@link #getEnable} and without issues disabled
+ * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
+ */
+ @Nullable
+ Set<String> getCheck();
+
+ /** Whether lint should abort the build if errors are found */
+ boolean isAbortOnError();
+
+ /**
+ * Whether lint should display full paths in the error output. By default the paths
+ * are relative to the path lint was invoked from.
+ */
+ boolean isAbsolutePaths();
+
+ /**
+ * Whether lint should include the source lines in the output where errors occurred
+ * (true by default)
+ */
+ boolean isNoLines();
+
+ /**
+ * Returns whether lint should be quiet (for example, not write informational messages
+ * such as paths to report files written)
+ */
+ boolean isQuiet();
+
+ /** Returns whether lint should check all warnings, including those off by default */
+ boolean isCheckAllWarnings();
+
+ /** Returns whether lint will only check for errors (ignoring warnings) */
+ boolean isIgnoreWarnings();
+
+ /** Returns whether lint should treat all warnings as errors */
+ boolean isWarningsAsErrors();
+
+ /** Returns whether lint should include explanations for issue errors. (Note that
+ * HTML and XML reports intentionally do this unconditionally, ignoring this setting.) */
+ boolean isExplainIssues();
+
+ /**
+ * Returns whether lint should include all output (e.g. include all alternate
+ * locations, not truncating long messages, etc.)
+ */
+ boolean isShowAll();
+
+ /**
+ * Returns an optional path to a lint.xml configuration file
+ */
+ @Nullable
+ File getLintConfig();
+
+ /** Whether we should write an text report. Default false. The location can be
+ * controlled by {@link #getTextOutput()}. */
+ boolean getTextReport();
+
+ /**
+ * The optional path to where a text report should be written. The special value
+ * "stdout" can be used to point to standard output.
+ */
+ @Nullable
+ File getTextOutput();
+
+ /** Whether we should write an HTML report. Default true. The location can be
+ * controlled by {@link #getHtmlOutput()}. */
+ boolean getHtmlReport();
+
+ /** The optional path to where an HTML report should be written */
+ @Nullable
+ File getHtmlOutput();
+
+ /** Whether we should write an XML report. Default true. The location can be
+ * controlled by {@link #getXmlOutput()}. */
+ boolean getXmlReport();
+
+ /** The optional path to where an XML report should be written */
+ @Nullable
+ File getXmlOutput();
+
+ /**
+ * Returns whether lint should check for fatal errors during release builds. Default is true.
+ * If issues with severity "fatal" are found, the release build is aborted.
+ */
+ boolean isCheckReleaseBuilds();
+
+ /**
+ * An optional map of severity overrides. The map maps from issue id's to the corresponding
+ * severity to use, which must be "fatal", "error", "warning", or "ignore".
+ *
+ * @return a map of severity overrides, or null. The severities are one of the constants
+ * {@link #SEVERITY_FATAL}, {@link #SEVERITY_ERROR}, {@link #SEVERITY_WARNING},
+ * {@link #SEVERITY_INFORMATIONAL}, {@link #SEVERITY_IGNORE}
+ */
+ @Nullable
+ Map<String, Integer> getSeverityOverrides();
+
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#FATAL */
+ int SEVERITY_FATAL = 1;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#ERROR */
+ int SEVERITY_ERROR = 2;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#WARNING */
+ int SEVERITY_WARNING = 3;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#INFORMATIONAL */
+ int SEVERITY_INFORMATIONAL = 4;
+ /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#IGNORE */
+ int SEVERITY_IGNORE = 5;
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/MavenCoordinates.java b/build-system/builder-model/src/main/java/com/android/builder/model/MavenCoordinates.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/MavenCoordinates.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/MavenCoordinates.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/NativeAndroidProject.java b/build-system/builder-model/src/main/java/com/android/builder/model/NativeAndroidProject.java
new file mode 100644
index 0000000..e7ad1b0
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/NativeAndroidProject.java
@@ -0,0 +1,86 @@
+/*
+ * 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.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Entry point for the model of the Android native support.
+ */
+public interface NativeAndroidProject {
+
+ /**
+ * Returns the model version. This is a string in the format X.Y.Z
+ */
+ @NonNull
+ String getModelVersion();
+
+ /**
+ * Returns the model api version.
+ * <p/>
+ * This is different from {@link #getModelVersion()} in a way that new model
+ * version might increment model version but keep existing api. That means that
+ * code which was built against particular 'api version' might be safely re-used for all
+ * new model versions as long as they don't change the api.
+ * <p/>
+ * Every new model version is assumed to return an 'api version' value which
+ * is equal or greater than the value used by the previous model version.
+ */
+ int getApiVersion();
+
+ /**
+ * Returns the name of the module.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns a collection of files that affects the build.
+ */
+ @NonNull
+ Collection<File> getBuildFiles();
+
+ /**
+ * Returns a collection of native artifacts.
+ */
+ @NonNull
+ Collection<NativeArtifact> getArtifacts();
+
+ /**
+ * Returns a collection of toolchains.
+ */
+ @NonNull
+ Collection<NativeToolchain> getToolChains();
+
+ /**
+ * Returns a collection of all compile settings.
+ */
+ @NonNull
+ Collection<NativeSettings> getSettings();
+
+ /**
+ * Return a map of file extension to each file type.
+ *
+ * The key is the file extension, the value is either "c" or "c++".
+ */
+ @NonNull
+ Map<String, String> getFileExtensions();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/NativeArtifact.java b/build-system/builder-model/src/main/java/com/android/builder/model/NativeArtifact.java
new file mode 100644
index 0000000..5f446f2
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/NativeArtifact.java
@@ -0,0 +1,74 @@
+/*
+ * 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.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * A native artifact.
+ *
+ * Represent an native library.
+ */
+public interface NativeArtifact {
+
+ /**
+ * Returns the name of the artifact.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns the toolchain used for compilation.
+ */
+ @NonNull
+ String getToolChain();
+
+ /**
+ * Return the group this artifact is associated with.
+ */
+ @NonNull
+ String getGroupName();
+
+ /**
+ * Returns thes source folders for the artifact.
+ *
+ * All files in the folders are assumed to be used for compilation of the artifact.
+ */
+ @NonNull
+ Collection<NativeFolder> getSourceFolders();
+
+ /**
+ * Returns the source files.
+ */
+ @NonNull
+ Collection<NativeFile> getSourceFiles();
+
+ /**
+ * Return the folders container headers exported for the library.
+ */
+ @NonNull
+ Collection<File> getExportedHeaders();
+
+ /**
+ * Returns the output file.
+ */
+ @NonNull
+ File getOutputFile();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/NativeFile.java b/build-system/builder-model/src/main/java/com/android/builder/model/NativeFile.java
new file mode 100644
index 0000000..245741b
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/NativeFile.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import java.io.File;
+
+/**
+ * A native source file with compile settings.
+ */
+public interface NativeFile {
+
+ /**
+ * The source file.
+ */
+ File getFilePath();
+
+ /**
+ * The name of a {@link NativeSettings} for the source file.
+ */
+ String getSettingsName();
+
+ /**
+ * The working directory for the compiler.
+ */
+ File getWorkingDirectory();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/NativeFolder.java b/build-system/builder-model/src/main/java/com/android/builder/model/NativeFolder.java
new file mode 100644
index 0000000..8dfb2db
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/NativeFolder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * A native source folder with compile settings for each file type.
+ */
+public interface NativeFolder {
+
+ /**
+ * Folder containing the source files.
+ */
+ File getFolderPath();
+
+ /**
+ * The compile settings for each file type.
+ *
+ * The key is the file type, which can be "c" or "c++". The value is the name of a
+ * {@link NativeSettings}.
+ */
+ Map<String, String> getPerLanguageSettings();
+
+ /**
+ * The working directory for the compiler.
+ */
+ File getWorkingDirectory();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/NativeLibrary.java b/build-system/builder-model/src/main/java/com/android/builder/model/NativeLibrary.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/NativeLibrary.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/NativeLibrary.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/NativeSettings.java b/build-system/builder-model/src/main/java/com/android/builder/model/NativeSettings.java
new file mode 100644
index 0000000..7ba7d82
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/NativeSettings.java
@@ -0,0 +1,29 @@
+/*
+ * 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.builder.model;
+
+import java.util.List;
+
+/**
+ * Compilation settings for a native source file.
+ */
+public interface NativeSettings {
+
+ String getName();
+
+ List<String> getCompilerFlags();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/NativeToolchain.java b/build-system/builder-model/src/main/java/com/android/builder/model/NativeToolchain.java
new file mode 100644
index 0000000..ed31686
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/NativeToolchain.java
@@ -0,0 +1,56 @@
+/*
+ * 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.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * Toolchain for building a native library.
+ */
+public interface NativeToolchain {
+
+ /**
+ * Returns the name of the toolchain.
+ *
+ * e.g. "x86_64", "arm-linux-androideabi"
+ *
+ * @return name of the toolchain.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns the full path of the C compiler.
+ * May be null if project do not contain C sources.
+ *
+ * @return the C compiler path.
+ */
+ @Nullable
+ File getCCompilerExecutable();
+
+ /**
+ * Returns the full path of the C++ compiler.
+ * May be null if project do not contain C++ sources.
+ *
+ * @return the C++ compiler path.
+ */
+ @Nullable
+ File getCppCompilerExecutable();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/PackagingOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/PackagingOptions.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/PackagingOptions.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/PackagingOptions.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java b/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java
new file mode 100644
index 0000000..0adbf58
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * a Product Flavor. This is only the configuration of the flavor.
+ *
+ * It does not include the sources or the dependencies. Those are available on the container
+ * or in the artifact info.
+ *
+ * @see ProductFlavorContainer
+ * @see BaseArtifact#getDependencies()
+ */
+public interface ProductFlavor extends BaseConfig, DimensionAware {
+
+ /**
+ * Returns the name of the flavor.
+ *
+ * @return the name of the flavor.
+ */
+ @Override
+ @NonNull
+ String getName();
+
+ /**
+ * Returns the name of the product flavor. This is only the value set on this product flavor.
+ * To get the final application id name, use {@link AndroidArtifact#getApplicationId()}.
+ *
+ * @return the application id.
+ */
+ @Nullable
+ String getApplicationId();
+
+ /**
+ * Returns the version code associated with this flavor or null if none have been set.
+ * This is only the value set on this product flavor, not necessarily the actual
+ * version code used.
+ *
+ * @return the version code, or null if not specified
+ */
+ @Nullable
+ Integer getVersionCode();
+
+ /**
+ * Returns the version name. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getMergedFlavor()} as well as
+ * {@link BuildType#getVersionNameSuffix()}
+ *
+ * @return the version name.
+ */
+ @Nullable
+ String getVersionName();
+
+ /**
+ * Returns the minSdkVersion. This is only the value set on this product flavor.
+ *
+ * @return the minSdkVersion, or null if not specified
+ */
+ @Nullable
+ ApiVersion getMinSdkVersion();
+
+ /**
+ * Returns the targetSdkVersion. This is only the value set on this product flavor.
+ *
+ * @return the targetSdkVersion, or null if not specified
+ */
+ @Nullable
+ ApiVersion getTargetSdkVersion();
+
+ /**
+ * Returns the maxSdkVersion. This is only the value set on this produce flavor.
+ *
+ * @return the maxSdkVersion, or null if not specified
+ */
+ @Nullable
+ Integer getMaxSdkVersion();
+
+ /**
+ * Returns the renderscript target api. This is only the value set on this product flavor.
+ * TODO: make final renderscript target api available through the model
+ *
+ * @return the renderscript target api, or null if not specified
+ */
+ @Nullable
+ Integer getRenderscriptTargetApi();
+
+ /**
+ * Returns whether the renderscript code should be compiled in support mode to
+ * make it compatible with older versions of Android.
+ *
+ * @return true if support mode is enabled, false if not, and null if not specified.
+ */
+ @Nullable
+ Boolean getRenderscriptSupportModeEnabled();
+
+ /**
+ * Returns whether the renderscript code should be compiled to generate C/C++ bindings.
+ * @return true for C/C++ generation, false for Java, null if not specified.
+ */
+ @Nullable
+ Boolean getRenderscriptNdkModeEnabled();
+
+ /**
+ * Returns the test application id. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getExtraAndroidArtifacts()} with
+ * {@link AndroidProject#ARTIFACT_ANDROID_TEST} and then
+ * {@link AndroidArtifact#getApplicationId()}
+ *
+ * @return the test package name.
+ */
+ @Nullable
+ String getTestApplicationId();
+
+ /**
+ * Returns the test instrumentation runner. This is only the value set on this product flavor.
+ * TODO: make test instrumentation runner available through the model.
+ *
+ * @return the test package name.
+ */
+ @Nullable
+ String getTestInstrumentationRunner();
+
+ /**
+ * Returns the arguments for the test instrumentation runner.
+ */
+ @NonNull
+ Map<String, String> getTestInstrumentationRunnerArguments();
+
+ /**
+ * Returns the handlingProfile value. This is only the value set on this product flavor.
+ *
+ * @return the handlingProfile value.
+ */
+ @Nullable
+ Boolean getTestHandleProfiling();
+
+ /**
+ * Returns the functionalTest value. This is only the value set on this product flavor.
+ *
+ * @return the functionalTest value.
+ */
+ @Nullable
+ Boolean getTestFunctionalTest();
+
+ /**
+ * Returns the resource configuration for this variant.
+ *
+ * This is the list of -c parameters for aapt.
+ *
+ * @return the resource configuration options.
+ */
+ @NonNull
+ Collection<String> getResourceConfigurations();
+
+ /**
+ * Returns the associated signing config or null if none are set on the product flavor.
+ */
+ @Nullable
+ SigningConfig getSigningConfig();
+
+ @NonNull
+ VectorDrawablesOptions getVectorDrawables();
+}
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavorContainer.java b/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavorContainer.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavorContainer.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavorContainer.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/SigningConfig.java b/build-system/builder-model/src/main/java/com/android/builder/model/SigningConfig.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/SigningConfig.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/SigningConfig.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/SourceProvider.java b/build-system/builder-model/src/main/java/com/android/builder/model/SourceProvider.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/SourceProvider.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/SourceProvider.java
diff --git a/base/build-system/builder-model/src/main/java/com/android/builder/model/SourceProviderContainer.java b/build-system/builder-model/src/main/java/com/android/builder/model/SourceProviderContainer.java
similarity index 100%
rename from base/build-system/builder-model/src/main/java/com/android/builder/model/SourceProviderContainer.java
rename to build-system/builder-model/src/main/java/com/android/builder/model/SourceProviderContainer.java
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/SyncIssue.java b/build-system/builder-model/src/main/java/com/android/builder/model/SyncIssue.java
new file mode 100644
index 0000000..68deb7a
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/SyncIssue.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * Class representing a sync issue.
+ * The goal is to make these issues not fail the sync but instead report them at the end
+ * of a successful sync.
+ */
+public interface SyncIssue {
+ int SEVERITY_WARNING = 1;
+ int SEVERITY_ERROR = 2;
+
+ // Generic error with no data payload, and no expected quick fix in IDE.
+ int TYPE_GENERIC = 0;
+
+ // data is expiration data
+ int TYPE_PLUGIN_OBSOLETE = 1;
+
+ // data is dependency coordinate
+ int TYPE_UNRESOLVED_DEPENDENCY = 2;
+
+ // data is dependency coordinate
+ int TYPE_DEPENDENCY_IS_APK = 3;
+
+ // data is dependency coordinate
+ int TYPE_DEPENDENCY_IS_APKLIB = 4;
+
+ // data is local file
+ int TYPE_NON_JAR_LOCAL_DEP = 5;
+
+ // data is dependency coordinate/path
+ int TYPE_NON_JAR_PACKAGE_DEP = 6;
+
+ // data is dependency coordinate/path
+ int TYPE_NON_JAR_PROVIDED_DEP = 7;
+
+ // data is dependency coordinate/path
+ int TYPE_JAR_DEPEND_ON_AAR = 8;
+
+ /**
+ * Mismatch dependency version between tested and test
+ * app. Data is dep coordinate without the version (groupId:artifactId)
+ */
+ int TYPE_MISMATCH_DEP = 9;
+
+ // data is dependency coordinate
+ int TYPE_OPTIONAL_LIB_NOT_FOUND = 10;
+
+ // data is the component that does not support Jack. Data is variant name.
+ int TYPE_JACK_IS_NOT_SUPPORTED = 11;
+
+ // data is the min version of Gradle
+ int TYPE_GRADLE_TOO_OLD = 12;
+
+ int TYPE_MAX = 12; // increment when adding new types.
+
+ /**
+ * Returns the severity of the issue.
+ */
+ int getSeverity();
+
+ /**
+ * Returns the type of the issue.
+ */
+ int getType();
+
+ /**
+ * Returns the data of the issue.
+ *
+ * This is a machine-readable string used by the IDE for known issue types.
+ */
+ @Nullable
+ String getData();
+
+ /**
+ * Returns the a user-readable message for the issue.
+ *
+ * This is used by IDEs that do not recognize the issue type (ie older IDE released before
+ * the type was added to the plugin).
+ */
+ @NonNull
+ String getMessage();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java b/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java
new file mode 100644
index 0000000..67db098
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A build Variant.
+ *
+ * This is the combination of a Build Type and 0+ Product Flavors (exactly one for each existing
+ * Flavor Dimension).
+ *
+ * Build Types and Flavors both contribute source folders, so this Variant is the direct
+ * representation of a set of source folders (and configuration parameters) used to build something.
+ *
+ * However the output of a Variant is not a single item.
+ *
+ * First there can be several artifacts.
+ * - Main Artifact: this is the main Android output(s). The app or the library being generated.
+ * - Extra Android Artifacts: these are ancillary artifacts, most likely test app(s).
+ * - Extra Java artifacts: these are pure-Java ancillary artifacts (junit support for instance).
+ */
+public interface Variant {
+
+ /**
+ * Returns the name of the variant. It is made up of the build type and flavors (if applicable)
+ *
+ * @return the name of the variant.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns a display name for the variant. It is made up of the build type and flavors
+ * (if applicable)
+ *
+ * @return the name.
+ */
+ @NonNull
+ String getDisplayName();
+
+ /**
+ * Returns the main artifact for this variant.
+ *
+ * @return the artifact.
+ */
+ @NonNull
+ AndroidArtifact getMainArtifact();
+
+ @NonNull
+ Collection<AndroidArtifact> getExtraAndroidArtifacts();
+
+ @NonNull
+ Collection<JavaArtifact> getExtraJavaArtifacts();
+
+ /**
+ * Returns the build type. All variants have a build type, so this is never null.
+ *
+ * @return the name of the build type.
+ */
+ @NonNull
+ String getBuildType();
+
+ /**
+ * Returns the flavors for this variants. This can be empty if no flavors are configured.
+ *
+ * @return a list of flavors which can be empty.
+ */
+ @NonNull
+ List<String> getProductFlavors();
+
+ /**
+ * The result of the merge of all the flavors and of the main default config. If no flavors
+ * are defined then this is the same as the default config.
+ *
+ * This is directly a ProductFlavor instance of a ProductFlavorContainer since this a composite
+ * of existing ProductFlavors.
+ *
+ * @return the merged flavors.
+ *
+ * @see AndroidProject#getDefaultConfig()
+ */
+ @NonNull
+ ProductFlavor getMergedFlavor();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/VectorDrawablesOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/VectorDrawablesOptions.java
new file mode 100644
index 0000000..5fbe886
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/VectorDrawablesOptions.java
@@ -0,0 +1,33 @@
+/*
+ * 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.builder.model;
+
+import com.android.annotations.Nullable;
+
+import java.util.Set;
+
+/**
+ * Options for build-time support for vector drawables.
+ */
+public interface VectorDrawablesOptions {
+
+ @Nullable
+ Set<String> getGeneratedDensities();
+
+ @Nullable
+ Boolean getUseSupportLibrary();
+}
diff --git a/base/build-system/builder-test-api/build.gradle b/build-system/builder-test-api/build.gradle
similarity index 100%
rename from base/build-system/builder-test-api/build.gradle
rename to build-system/builder-test-api/build.gradle
diff --git a/base/build-system/builder-test-api/builder-test-api.iml b/build-system/builder-test-api/builder-test-api.iml
similarity index 100%
rename from base/build-system/builder-test-api/builder-test-api.iml
rename to build-system/builder-test-api/builder-test-api.iml
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfig.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfig.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfig.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfig.java
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProvider.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProvider.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProvider.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProvider.java
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProviderImpl.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProviderImpl.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProviderImpl.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConfigProviderImpl.java
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConnector.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConnector.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConnector.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConnector.java
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceException.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceException.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceException.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceException.java
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceProvider.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceProvider.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceProvider.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceProvider.java
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestException.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestException.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestException.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestException.java
diff --git a/base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestServer.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestServer.java
similarity index 100%
rename from base/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestServer.java
rename to build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestServer.java
diff --git a/base/build-system/builder-test-api/src/test/java/com/android/builder/testing/api/DeviceConfigTest.java b/build-system/builder-test-api/src/test/java/com/android/builder/testing/api/DeviceConfigTest.java
similarity index 100%
rename from base/build-system/builder-test-api/src/test/java/com/android/builder/testing/api/DeviceConfigTest.java
rename to build-system/builder-test-api/src/test/java/com/android/builder/testing/api/DeviceConfigTest.java
diff --git a/base/build-system/builder/.settings/org.eclipse.jdt.core.prefs b/build-system/builder/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/build-system/builder/.settings/org.eclipse.jdt.core.prefs
rename to build-system/builder/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/build-system/builder/.settings/org.moreunit.prefs b/build-system/builder/.settings/org.moreunit.prefs
similarity index 100%
rename from base/build-system/builder/.settings/org.moreunit.prefs
rename to build-system/builder/.settings/org.moreunit.prefs
diff --git a/base/build-system/builder/MODULE_LICENSE_APACHE2 b/build-system/builder/MODULE_LICENSE_APACHE2
similarity index 100%
copy from base/build-system/builder/MODULE_LICENSE_APACHE2
copy to build-system/builder/MODULE_LICENSE_APACHE2
diff --git a/base/build-system/builder/NOTICE b/build-system/builder/NOTICE
similarity index 100%
rename from base/build-system/builder/NOTICE
rename to build-system/builder/NOTICE
diff --git a/build-system/builder/build.gradle b/build-system/builder/build.gradle
new file mode 100644
index 0000000..b20cb4f
--- /dev/null
+++ b/build-system/builder/build.gradle
@@ -0,0 +1,108 @@
+apply plugin: 'java'
+apply plugin: 'clone-artifacts'
+apply plugin: 'jacoco'
+
+evaluationDependsOn(':base:builder-model')
+evaluationDependsOn(':base:builder-test-api')
+
+dependencies {
+ compile project(':base:builder-model')
+ compile project(':base:builder-test-api')
+
+ compile project(':base:sdklib')
+ compile project(':base:sdk-common')
+ compile project(':base:common')
+ compile project(':base:manifest-merger')
+ compile project(':base:ddmlib')
+
+ compile project(':base:jack:jack-api')
+ compile project(':base:jack:jill-api')
+
+ compile 'com.squareup:javawriter:2.5.0'
+ compile 'org.bouncycastle:bcpkix-jdk15on:1.48'
+ compile 'org.bouncycastle:bcprov-jdk15on:1.48'
+ compile 'org.ow2.asm:asm:5.0.3'
+ compile 'org.ow2.asm:asm-tree:5.0.3'
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-all:1.9.5'
+ testCompile 'com.google.truth:truth:0.28'
+ testCompile project(':base:testutils')
+ testCompile project(':base:sdklib').sourceSets.test.output
+}
+
+test {
+ maxParallelForks = Runtime.runtime.availableProcessors() / 2
+}
+
+group = 'com.android.tools.build'
+archivesBaseName = 'builder'
+version = rootProject.ext.buildVersion
+
+project.ext.pomName = 'Android Builder library'
+project.ext.pomDesc = 'Library to build Android applications.'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
+
+jar.manifest.attributes("Builder-Version": version)
+
+def generated = new File("${project.buildDir}/generated/java")
+
+sourceSets {
+ main {
+ java {
+ srcDir generated
+ }
+ }
+}
+
+task generateVersionConstantsJava {
+ inputs.property("apiVersion", apiVersion)
+ inputs.property("nativeApiVersion", nativeApiVersion)
+ inputs.property("version", version)
+ ext.versionFile = new File(generated, "com/android/builder/Version.java")
+ outputs.file(versionFile)
+}
+generateVersionConstantsJava << {
+ versionFile.parentFile.mkdirs()
+ versionFile.text = """
+package com.android.builder;
+
+public final class Version {
+ private Version() {}
+ public static final String ANDROID_GRADLE_PLUGIN_VERSION = "$version";
+ public static final int BUILDER_MODEL_API_VERSION = $apiVersion;
+ public static final int BUILDER_NATIVE_MODEL_API_VERSION = $nativeApiVersion;
+}
+"""
+}
+
+tasks.compileJava.dependsOn generateVersionConstantsJava
+
+configurations {
+ provided
+ sourcesProvided
+}
+
+dependencies {
+ provided(project(':base:profile')) {
+ transitive = false
+ }
+ sourcesProvided(project(path:':base:profile', configuration:'sourcesOnly')) {
+ transitive = false
+ }
+}
+
+sourceSets.main.compileClasspath += [configurations.provided]
+sourceSets.test.runtimeClasspath += [configurations.provided]
+tasks.compileJava.dependsOn(configurations.provided)
+tasks.sourcesJar.dependsOn(configurations.sourcesProvided)
+
+tasks.jar {
+ from({zipTree(configurations.provided.singleFile)})
+}
+tasks.sourcesJar {
+ from({zipTree(configurations.sourcesProvided.singleFile)})
+}
diff --git a/build-system/builder/builder.iml b/build-system/builder/builder.iml
new file mode 100644
index 0000000..c5dbee6
--- /dev/null
+++ b/build-system/builder/builder.iml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$/../../../../out/build/base/builder/build/generated/java">
+ <sourceFolder url="file://$MODULE_DIR$/../../../../out/build/base/builder/build/generated/java" isTestSource="false" />
+ </content>
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="profile" exported="" />
+ <orderEntry type="module" module-name="builder-test-api" exported="" />
+ <orderEntry type="module" module-name="builder-model" exported="" />
+ <orderEntry type="module" module-name="testutils" scope="TEST" />
+ <orderEntry type="module" module-name="ddmlib" exported="" />
+ <orderEntry type="library" exported="" name="javawriter" level="project" />
+ <orderEntry type="library" exported="" name="bouncy-castle" level="project" />
+ <orderEntry type="library" name="mockito" level="project" />
+ <orderEntry type="module" module-name="sdk-common-base" />
+ <orderEntry type="module" module-name="manifest-merger-base" />
+ <orderEntry type="library" exported="" name="asm-tools" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="assetstudio-base" />
+ <orderEntry type="module" module-name="jack-api" exported="" />
+ <orderEntry type="module" module-name="jill-api" exported="" />
+ <orderEntry type="library" exported="" name="guava-tools" level="project" />
+ <orderEntry type="library" name="truth" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/base/build-system/builder/src/main/java/com/android/builder/compiling/BuildConfigGenerator.java b/build-system/builder/src/main/java/com/android/builder/compiling/BuildConfigGenerator.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/compiling/BuildConfigGenerator.java
rename to build-system/builder/src/main/java/com/android/builder/compiling/BuildConfigGenerator.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/compiling/DependencyFileProcessor.java b/build-system/builder/src/main/java/com/android/builder/compiling/DependencyFileProcessor.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/compiling/DependencyFileProcessor.java
rename to build-system/builder/src/main/java/com/android/builder/compiling/DependencyFileProcessor.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/compiling/ResValueGenerator.java b/build-system/builder/src/main/java/com/android/builder/compiling/ResValueGenerator.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/compiling/ResValueGenerator.java
rename to build-system/builder/src/main/java/com/android/builder/compiling/ResValueGenerator.java
diff --git a/build-system/builder/src/main/java/com/android/builder/core/AaptPackageProcessBuilder.java b/build-system/builder/src/main/java/com/android/builder/core/AaptPackageProcessBuilder.java
new file mode 100644
index 0000000..6bca2ea
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/AaptPackageProcessBuilder.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.dependency.SymbolFileProvider;
+import com.android.builder.model.AaptOptions;
+import com.android.ide.common.process.ProcessEnvBuilder;
+import com.android.ide.common.process.ProcessInfo;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.resources.Density;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.utils.ILogger;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Builds the ProcessInfo necessary for an aapt package invocation
+ */
+public class AaptPackageProcessBuilder extends ProcessEnvBuilder<AaptPackageProcessBuilder> {
+
+ @NonNull private final File mManifestFile;
+ @NonNull private final AaptOptions mOptions;
+ @Nullable private File mResFolder;
+ @Nullable private File mAssetsFolder;
+ private boolean mVerboseExec = false;
+ @Nullable private String mSourceOutputDir;
+ @Nullable private String mSymbolOutputDir;
+ @Nullable private List<? extends SymbolFileProvider> mLibraries;
+ @Nullable private String mResPackageOutput;
+ @Nullable private String mProguardOutput;
+ @Nullable private VariantType mType;
+ private boolean mDebuggable = false;
+ private boolean mPseudoLocalesEnabled = false;
+ @Nullable private Collection<String> mResourceConfigs;
+ @Nullable Collection<String> mSplits;
+ @Nullable String mPackageForR;
+ @Nullable String mPreferredDensity;
+
+ /**
+ *
+ * @param manifestFile the location of the manifest file
+ * @param options the {@link com.android.builder.model.AaptOptions}
+ */
+ public AaptPackageProcessBuilder(
+ @NonNull File manifestFile,
+ @NonNull AaptOptions options) {
+ checkNotNull(manifestFile, "manifestFile cannot be null.");
+ checkNotNull(options, "options cannot be null.");
+ mManifestFile = manifestFile;
+ mOptions = options;
+ }
+
+ @NonNull
+ public File getManifestFile() {
+ return mManifestFile;
+ }
+
+ /**
+ * @param resFolder the merged res folder
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setResFolder(@NonNull File resFolder) {
+ if (!resFolder.isDirectory()) {
+ throw new RuntimeException("resFolder parameter is not a directory");
+ }
+ mResFolder = resFolder;
+ return this;
+ }
+
+ /**
+ * @param assetsFolder the merged asset folder
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setAssetsFolder(@NonNull File assetsFolder) {
+ if (!assetsFolder.isDirectory()) {
+ throw new RuntimeException("assetsFolder parameter is not a directory");
+ }
+ mAssetsFolder = assetsFolder;
+ return this;
+ }
+
+ /**
+ * @param sourceOutputDir optional source folder to generate R.java
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setSourceOutputDir(@Nullable String sourceOutputDir) {
+ mSourceOutputDir = sourceOutputDir;
+ return this;
+ }
+
+ @Nullable
+ public String getSourceOutputDir() {
+ return mSourceOutputDir;
+ }
+
+ /**
+ * @param symbolOutputDir the folder to write symbols into
+ * @ itself
+ */
+ public AaptPackageProcessBuilder setSymbolOutputDir(@Nullable String symbolOutputDir) {
+ mSymbolOutputDir = symbolOutputDir;
+ return this;
+ }
+
+ @Nullable
+ public String getSymbolOutputDir() {
+ return mSymbolOutputDir;
+ }
+
+ /**
+ * @param libraries the flat list of libraries
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setLibraries(
+ @NonNull List<? extends SymbolFileProvider> libraries) {
+ mLibraries = libraries;
+ return this;
+ }
+
+ @NonNull
+ public List<? extends SymbolFileProvider> getLibraries() {
+ return mLibraries == null ? ImmutableList.<SymbolFileProvider>of() : mLibraries;
+ }
+
+ /**
+ * @param resPackageOutput optional filepath for packaged resources
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setResPackageOutput(@Nullable String resPackageOutput) {
+ mResPackageOutput = resPackageOutput;
+ return this;
+ }
+
+ /**
+ * @param proguardOutput optional filepath for proguard file to generate
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setProguardOutput(@Nullable String proguardOutput) {
+ mProguardOutput = proguardOutput;
+ return this;
+ }
+
+ /**
+ * @param type the type of the variant being built
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setType(@NonNull VariantType type) {
+ this.mType = type;
+ return this;
+ }
+
+ @Nullable
+ public VariantType getType() {
+ return mType;
+ }
+
+ /**
+ * @param debuggable whether the app is debuggable
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setDebuggable(boolean debuggable) {
+ this.mDebuggable = debuggable;
+ return this;
+ }
+
+ /**
+ * @param resourceConfigs a list of resource config filters to pass to aapt.
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setResourceConfigs(@NonNull Collection<String> resourceConfigs) {
+ this.mResourceConfigs = resourceConfigs;
+ return this;
+ }
+
+ /**
+ * @param splits optional list of split dimensions values (like a density or an abi). This
+ * will be used by aapt to generate the corresponding pure split apks.
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setSplits(@NonNull Collection<String> splits) {
+ this.mSplits = splits;
+ return this;
+ }
+
+
+ public AaptPackageProcessBuilder setVerbose() {
+ mVerboseExec = true;
+ return this;
+ }
+
+ /**
+ * @param packageForR Package override to generate the R class in a different package.
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setPackageForR(@NonNull String packageForR) {
+ this.mPackageForR = packageForR;
+ return this;
+ }
+
+ public AaptPackageProcessBuilder setPseudoLocalesEnabled(boolean pseudoLocalesEnabled) {
+ mPseudoLocalesEnabled = pseudoLocalesEnabled;
+ return this;
+ }
+
+ /**
+ * Specifies a preference for a particular density. Resources that do not match this density
+ * and have variants that are a closer match are removed.
+ * @param density the preferred density
+ * @return itself
+ */
+ public AaptPackageProcessBuilder setPreferredDensity(String density) {
+ mPreferredDensity = density;
+ return this;
+ }
+
+ @Nullable
+ String getPackageForR() {
+ return mPackageForR;
+ }
+
+ public ProcessInfo build(
+ @NonNull BuildToolInfo buildToolInfo,
+ @NonNull IAndroidTarget target,
+ @NonNull ILogger logger) {
+
+ // if both output types are empty, then there's nothing to do and this is an error
+ checkArgument(mSourceOutputDir != null || mResPackageOutput != null,
+ "No output provided for aapt task");
+ if (mSymbolOutputDir != null || mSourceOutputDir != null) {
+ checkNotNull(mLibraries,
+ "libraries cannot be null if symbolOutputDir or sourceOutputDir is non-null");
+ }
+
+ // check resConfigs and split settings coherence.
+ checkResConfigsVersusSplitSettings(logger);
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.addEnvironments(mEnvironment);
+
+ String aapt = buildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
+ if (aapt == null || !new File(aapt).isFile()) {
+ throw new IllegalStateException("aapt is missing");
+ }
+
+ builder.setExecutable(aapt);
+ builder.addArgs("package");
+
+ if (mVerboseExec) {
+ builder.addArgs("-v");
+ }
+
+ builder.addArgs("-f");
+ builder.addArgs("--no-crunch");
+
+ // inputs
+ builder.addArgs("-I", target.getPath(IAndroidTarget.ANDROID_JAR));
+
+ builder.addArgs("-M", mManifestFile.getAbsolutePath());
+
+ if (mResFolder != null) {
+ builder.addArgs("-S", mResFolder.getAbsolutePath());
+ }
+
+ if (mAssetsFolder != null) {
+ builder.addArgs("-A", mAssetsFolder.getAbsolutePath());
+ }
+
+ // outputs
+ if (mSourceOutputDir != null) {
+ builder.addArgs("-m");
+ builder.addArgs("-J", mSourceOutputDir);
+ }
+
+ if (mResPackageOutput != null) {
+ builder.addArgs("-F", mResPackageOutput);
+ }
+
+ if (mProguardOutput != null) {
+ builder.addArgs("-G", mProguardOutput);
+ }
+
+ if (mSplits != null) {
+ for (String split : mSplits) {
+
+ builder.addArgs("--split", split);
+ }
+ }
+
+ // options controlled by build variants
+
+ if (mDebuggable) {
+ builder.addArgs("--debug-mode");
+ }
+
+ if (mType != VariantType.ANDROID_TEST) {
+ if (mPackageForR != null) {
+ builder.addArgs("--custom-package", mPackageForR);
+ logger.verbose("Custom package for R class: '%s'", mPackageForR);
+ }
+ }
+
+ if (mPseudoLocalesEnabled) {
+ if (buildToolInfo.getRevision().getMajor() >= 21) {
+ builder.addArgs("--pseudo-localize");
+ } else {
+ throw new RuntimeException(
+ "Pseudolocalization is only available since Build Tools version 21.0.0,"
+ + " please upgrade or turn it off.");
+ }
+ }
+
+ // library specific options
+ if (mType == VariantType.LIBRARY) {
+ builder.addArgs("--non-constant-id");
+ }
+
+ // AAPT options
+ String ignoreAssets = mOptions.getIgnoreAssets();
+ if (ignoreAssets != null) {
+ builder.addArgs("--ignore-assets", ignoreAssets);
+ }
+
+ if (mOptions.getFailOnMissingConfigEntry()) {
+ if (buildToolInfo.getRevision().getMajor() > 20) {
+ builder.addArgs("--error-on-missing-config-entry");
+ } else {
+ throw new IllegalStateException("aaptOptions:failOnMissingConfigEntry cannot be used"
+ + " with SDK Build Tools revision earlier than 21.0.0");
+ }
+ }
+
+ // never compress apks.
+ builder.addArgs("-0", "apk");
+
+ // add custom no-compress extensions
+ Collection<String> noCompressList = mOptions.getNoCompress();
+ if (noCompressList != null) {
+ for (String noCompress : noCompressList) {
+ builder.addArgs("-0", noCompress);
+ }
+ }
+ List<String> additionalParameters = mOptions.getAdditionalParameters();
+ if (!isNullOrEmpty(additionalParameters)) {
+ builder.addArgs(additionalParameters);
+ }
+
+ List<String> resourceConfigs = new ArrayList<String>();
+ if (!isNullOrEmpty(mResourceConfigs)) {
+ resourceConfigs.addAll(mResourceConfigs);
+ }
+ if (buildToolInfo.getRevision().getMajor() < 21 && mPreferredDensity != null) {
+ resourceConfigs.add(mPreferredDensity);
+ // when adding a density filter, also always add the nodpi option.
+ resourceConfigs.add(Density.NODPI.getResourceValue());
+ }
+
+
+ // separate the density and language resource configs, since starting in 21, the
+ // density resource configs should be passed with --preferred-density to ensure packaging
+ // of scalable resources when no resource for the preferred density is present.
+ List<String> otherResourceConfigs = new ArrayList<String>();
+ List<String> densityResourceConfigs = new ArrayList<String>();
+ if (!resourceConfigs.isEmpty()) {
+ if (buildToolInfo.getRevision().getMajor() >= 21) {
+ for (String resourceConfig : resourceConfigs) {
+ if (Density.getEnum(resourceConfig) != null) {
+ densityResourceConfigs.add(resourceConfig);
+ } else {
+ otherResourceConfigs.add(resourceConfig);
+ }
+ }
+ } else {
+ // before 21, everything is passed with -c option.
+ otherResourceConfigs = resourceConfigs;
+ }
+ }
+ if (!otherResourceConfigs.isEmpty()) {
+ Joiner joiner = Joiner.on(',');
+ builder.addArgs("-c", joiner.join(otherResourceConfigs));
+ }
+ if (!densityResourceConfigs.isEmpty()) {
+ if (densityResourceConfigs.size() != 1) {
+ throw new RuntimeException("Cannot filter assets for multiple densities using "
+ + "SDK build tools 21 or later. Consider using apk splits instead.");
+ }
+ builder.addArgs(
+ "--preferred-density", Iterables.getOnlyElement(densityResourceConfigs));
+ }
+
+ if (buildToolInfo.getRevision().getMajor() >= 21 && mPreferredDensity != null) {
+ if (!isNullOrEmpty(mResourceConfigs)) {
+ Collection<String> densityResConfig = getDensityResConfigs(mResourceConfigs);
+ if (!densityResConfig.isEmpty()) {
+ throw new RuntimeException(String.format(
+ "When using splits in tools 21 and above, resConfigs should not contain "
+ + "any densities. Right now, it contains \"%1$s\"\n"
+ + "Suggestion: remove these from resConfigs from build.gradle",
+ Joiner.on("\",\"").join(densityResConfig)));
+ }
+ }
+ builder.addArgs("--preferred-density", mPreferredDensity);
+ }
+
+ if (buildToolInfo.getRevision().getMajor() < 21 && mPreferredDensity != null) {
+ logger.warning(String.format("Warning : Project is building density based multiple APKs"
+ + " but using tools version %1$s, you should upgrade to build-tools 21 or above"
+ + " to ensure proper packaging of resources.",
+ buildToolInfo.getRevision().getMajor()));
+ }
+
+ if (mSymbolOutputDir != null &&
+ (mType == VariantType.LIBRARY || !mLibraries.isEmpty())) {
+ builder.addArgs("--output-text-symbols", mSymbolOutputDir);
+ }
+
+ // All the vector XML files that are outside of an "-anydpi-v21" directory were left there
+ // intentionally, for the support library to consume. Leave them alone.
+ if (buildToolInfo.getRevision().getMajor() >= 23) {
+ builder.addArgs("--no-version-vectors");
+ }
+
+ return builder.createProcess();
+ }
+
+ private void checkResConfigsVersusSplitSettings(ILogger logger) {
+ if (isNullOrEmpty(mResourceConfigs) || isNullOrEmpty(mSplits)) {
+ return;
+ }
+
+ // only consider the Density related resConfig settings.
+ Collection<String> resConfigs = getDensityResConfigs(mResourceConfigs);
+ List<String> splits = new ArrayList<String>(mSplits);
+ splits.removeAll(resConfigs);
+ if (!splits.isEmpty()) {
+ // some splits are required, yet the resConfigs do not contain the split density value
+ // which mean that the resulting split file would be empty, flag this as an error.
+ throw new RuntimeException(String.format(
+ "Splits for densities \"%1$s\" were configured, yet the resConfigs settings does"
+ + " not include such splits. The resulting split APKs would be empty.\n"
+ + "Suggestion : exclude those splits in your build.gradle : \n"
+ + "splits {\n"
+ + " density {\n"
+ + " enable true\n"
+ + " exclude \"%2$s\"\n"
+ + " }\n"
+ + "}\n"
+ + "OR add them to the resConfigs list.",
+ Joiner.on(",").join(splits),
+ Joiner.on("\",\"").join(splits)));
+ }
+ resConfigs.removeAll(mSplits);
+ if (!resConfigs.isEmpty()) {
+ // there are densities present in the resConfig but not in splits, which mean that those
+ // densities will be packaged in the main APK
+ throw new RuntimeException(String.format(
+ "Inconsistent density configuration, with \"%1$s\" present on "
+ + "resConfig settings, while only \"%2$s\" densities are requested "
+ + "in splits APK density settings.\n"
+ + "Suggestion : remove extra densities from the resConfig : \n"
+ + "defaultConfig {\n"
+ + " resConfigs \"%2$s\"\n"
+ + "}\n"
+ + "OR remove such densities from the split's exclude list.\n",
+ Joiner.on(",").join(resConfigs),
+ Joiner.on("\",\"").join(mSplits)));
+ }
+ }
+
+ private static boolean isNullOrEmpty(@Nullable Collection<?> collection) {
+ return collection == null || collection.isEmpty();
+ }
+
+ private static Collection<String> getDensityResConfigs(Collection<String> resourceConfigs) {
+ return Collections2.filter(new ArrayList<String>(resourceConfigs),
+ new Predicate<String>() {
+ @Override
+ public boolean apply(@Nullable String input) {
+ return Density.getEnum(input) != null;
+ }
+ });
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java b/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java
new file mode 100644
index 0000000..2967d8e
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java
@@ -0,0 +1,2147 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_DEX;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FD_RES_XML;
+import static com.android.builder.core.BuilderConstants.ANDROID_WEAR;
+import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK;
+import static com.android.manifmerger.ManifestMerger2.Invoker;
+import static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.compiling.DependencyFileProcessor;
+import com.android.builder.core.BuildToolsServiceLoader.BuildToolServiceLoader;
+import com.android.builder.dependency.ManifestDependency;
+import com.android.builder.dependency.SymbolFileProvider;
+import com.android.builder.internal.ClassFieldImpl;
+import com.android.builder.internal.SymbolLoader;
+import com.android.builder.internal.SymbolWriter;
+import com.android.builder.internal.TestManifestGenerator;
+import com.android.builder.internal.compiler.AidlProcessor;
+import com.android.builder.internal.compiler.DexWrapper;
+import com.android.builder.internal.compiler.JackConversionCache;
+import com.android.builder.internal.compiler.LeafFolderGatherer;
+import com.android.builder.internal.compiler.PreDexCache;
+import com.android.builder.internal.compiler.RenderScriptProcessor;
+import com.android.builder.internal.compiler.SourceSearcher;
+import com.android.builder.internal.incremental.DependencyData;
+import com.android.builder.internal.packaging.Packager;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.packaging.PackagerException;
+import com.android.builder.packaging.SealedPackageException;
+import com.android.builder.packaging.SigningException;
+import com.android.builder.sdk.SdkInfo;
+import com.android.builder.sdk.TargetInfo;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.ide.common.internal.AaptCruncher;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.process.CachedProcessOutputHandler;
+import com.android.ide.common.process.JavaProcessExecutor;
+import com.android.ide.common.process.JavaProcessInfo;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.ide.common.process.ProcessInfo;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.ide.common.process.ProcessResult;
+import com.android.ide.common.signing.CertificateInfo;
+import com.android.ide.common.signing.KeystoreHelper;
+import com.android.ide.common.signing.KeytoolException;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.jack.api.ConfigNotSupportedException;
+import com.android.jack.api.JackProvider;
+import com.android.jack.api.v01.Api01CompilationTask;
+import com.android.jack.api.v01.Api01Config;
+import com.android.jack.api.v01.CompilationException;
+import com.android.jack.api.v01.ConfigurationException;
+import com.android.jack.api.v01.MultiDexKind;
+import com.android.jack.api.v01.ReporterKind;
+import com.android.jack.api.v01.UnrecoverableException;
+import com.android.jill.api.JillProvider;
+import com.android.jill.api.v01.Api01TranslationTask;
+import com.android.jill.api.v01.TranslationException;
+import com.android.manifmerger.ManifestMerger2;
+import com.android.manifmerger.MergingReport;
+import com.android.manifmerger.PlaceholderHandler;
+import com.android.repository.Revision;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.IAndroidTarget.OptionalLibrary;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.android.utils.SdkUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * This is the main builder class. It is given all the data to process the build (such as
+ * {@link DefaultProductFlavor}s, {@link DefaultBuildType} and dependencies) and use them when doing specific
+ * build steps.
+ *
+ * To use:
+ * create a builder with {@link #AndroidBuilder(String, String, ProcessExecutor, JavaProcessExecutor, ErrorReporter, ILogger, boolean)}
+ *
+ * then build steps can be done with
+ * {@link #mergeManifests(File, List, List, String, int, String, String, String, Integer, String, String, ManifestMerger2.MergeType, Map, File)}
+ * {@link #processTestManifest(String, String, String, String, String, Boolean, Boolean, File, List, Map, File, File)}
+ * {@link #processResources(AaptPackageProcessBuilder, boolean, ProcessOutputHandler)}
+ * {@link #compileAllAidlFiles(List, File, File, Collection, List, DependencyFileProcessor, ProcessOutputHandler)}
+ * {@link #convertByteCode(Collection, File, boolean, File, DexOptions, List, boolean, boolean, ProcessOutputHandler)}
+ * {@link #packageApk(String, Set, Collection, Collection, Set, boolean, SigningConfig, String)}
+ *
+ * Java compilation is not handled but the builder provides the bootclasspath with
+ * {@link #getBootClasspath(boolean)}.
+ */
+public class AndroidBuilder {
+
+ private static final Revision MIN_BUILD_TOOLS_REV = new Revision(19, 1, 0);
+
+ private static final DependencyFileProcessor sNoOpDependencyFileProcessor = new DependencyFileProcessor() {
+ @Override
+ public DependencyData processFile(@NonNull File dependencyFile) {
+ return null;
+ }
+ };
+
+ @NonNull
+ private final String mProjectId;
+ @NonNull
+ private final ILogger mLogger;
+
+ @NonNull
+ private final ProcessExecutor mProcessExecutor;
+ @NonNull
+ private final JavaProcessExecutor mJavaProcessExecutor;
+ @NonNull
+ private final ErrorReporter mErrorReporter;
+
+ private final boolean mVerboseExec;
+
+ @Nullable private String mCreatedBy;
+
+
+ private SdkInfo mSdkInfo;
+ private TargetInfo mTargetInfo;
+
+ private List<File> mBootClasspathFiltered;
+ private List<File> mBootClasspathAll;
+ @NonNull
+ private List<LibraryRequest> mLibraryRequests = ImmutableList.of();
+
+ /**
+ * Creates an AndroidBuilder.
+ * <p/>
+ * <var>verboseExec</var> is needed on top of the ILogger due to remote exec tools not being
+ * able to output info and verbose messages separately.
+ *
+ * @param createdBy the createdBy String for the apk manifest.
+ * @param logger the Logger
+ * @param verboseExec whether external tools are launched in verbose mode
+ */
+ public AndroidBuilder(
+ @NonNull String projectId,
+ @Nullable String createdBy,
+ @NonNull ProcessExecutor processExecutor,
+ @NonNull JavaProcessExecutor javaProcessExecutor,
+ @NonNull ErrorReporter errorReporter,
+ @NonNull ILogger logger,
+ boolean verboseExec) {
+ mProjectId = checkNotNull(projectId);
+ mCreatedBy = createdBy;
+ mProcessExecutor = checkNotNull(processExecutor);
+ mJavaProcessExecutor = checkNotNull(javaProcessExecutor);
+ mErrorReporter = checkNotNull(errorReporter);
+ mLogger = checkNotNull(logger);
+ mVerboseExec = verboseExec;
+ }
+
+ /**
+ * Sets the SdkInfo and the targetInfo on the builder. This is required to actually
+ * build (some of the steps).
+ *
+ * @param sdkInfo the SdkInfo
+ * @param targetInfo the TargetInfo
+ *
+ * @see com.android.builder.sdk.SdkLoader
+ */
+ public void setTargetInfo(
+ @NonNull SdkInfo sdkInfo,
+ @NonNull TargetInfo targetInfo,
+ @NonNull Collection<LibraryRequest> libraryRequests) {
+ mSdkInfo = sdkInfo;
+ mTargetInfo = targetInfo;
+
+ if (mTargetInfo.getBuildTools().getRevision().compareTo(MIN_BUILD_TOOLS_REV) < 0) {
+ throw new IllegalArgumentException(String.format(
+ "The SDK Build Tools revision (%1$s) is too low for project '%2$s'. Minimum required is %3$s",
+ mTargetInfo.getBuildTools().getRevision(), mProjectId, MIN_BUILD_TOOLS_REV));
+ }
+
+ mLibraryRequests = ImmutableList.copyOf(libraryRequests);
+ }
+
+ /**
+ * Returns the SdkInfo, if set.
+ */
+ @Nullable
+ public SdkInfo getSdkInfo() {
+ return mSdkInfo;
+ }
+
+ /**
+ * Returns the TargetInfo, if set.
+ */
+ @Nullable
+ public TargetInfo getTargetInfo() {
+ return mTargetInfo;
+ }
+
+ @NonNull
+ public ILogger getLogger() {
+ return mLogger;
+ }
+
+ @NonNull
+ public ErrorReporter getErrorReporter() {
+ return mErrorReporter;
+ }
+
+ /**
+ * Returns the compilation target, if set.
+ */
+ @Nullable
+ public IAndroidTarget getTarget() {
+ checkState(mTargetInfo != null,
+ "Cannot call getTarget() before setTargetInfo() is called.");
+ return mTargetInfo.getTarget();
+ }
+
+ /**
+ * Returns whether the compilation target is a preview.
+ */
+ public boolean isPreviewTarget() {
+ checkState(mTargetInfo != null,
+ "Cannot call isTargetAPreview() before setTargetInfo() is called.");
+ return mTargetInfo.getTarget().getVersion().isPreview();
+ }
+
+ public String getTargetCodename() {
+ checkState(mTargetInfo != null,
+ "Cannot call getTargetCodename() before setTargetInfo() is called.");
+ return mTargetInfo.getTarget().getVersion().getCodename();
+ }
+
+ @NonNull
+ public File getDxJar() {
+ checkState(mTargetInfo != null,
+ "Cannot call getDxJar() before setTargetInfo() is called.");
+ return new File(mTargetInfo.getBuildTools().getPath(BuildToolInfo.PathId.DX_JAR));
+ }
+
+ /**
+ * Helper method to get the boot classpath to be used during compilation.
+ *
+ * @param includeOptionalLibraries if true, optional libraries are included even if not
+ * required by the project setup.
+ */
+ @NonNull
+ public List<File> getBootClasspath(boolean includeOptionalLibraries) {
+ if (includeOptionalLibraries) {
+ return computeFullBootClasspath();
+ }
+
+ return computeFilteredBootClasspath();
+ }
+
+ private List<File> computeFilteredBootClasspath() {
+ // computes and caches the filtered boot classpath.
+ // Changes here should be applied to #computeFullClasspath()
+
+ if (mBootClasspathFiltered == null) {
+ checkState(mTargetInfo != null,
+ "Cannot call getBootClasspath() before setTargetInfo() is called.");
+ List<File> classpath = Lists.newArrayList();
+ IAndroidTarget target = mTargetInfo.getTarget();
+
+ for (String p : target.getBootClasspath()) {
+ classpath.add(new File(p));
+ }
+
+ List<LibraryRequest> requestedLibs = Lists.newArrayList(mLibraryRequests);
+
+ // add additional libraries if any
+ List<OptionalLibrary> libs = target.getAdditionalLibraries();
+ for (OptionalLibrary lib : libs) {
+ // add it always for now
+ classpath.add(lib.getJar());
+
+ // remove from list of requested if match
+ LibraryRequest requestedLib = findMatchingLib(lib.getName(), requestedLibs);
+ if (requestedLib != null) {
+ requestedLibs.remove(requestedLib);
+ }
+ }
+
+ // add optional libraries if needed.
+ List<OptionalLibrary> optionalLibraries = target.getOptionalLibraries();
+ for (OptionalLibrary lib : optionalLibraries) {
+ // search if requested
+ LibraryRequest requestedLib = findMatchingLib(lib.getName(), requestedLibs);
+ if (requestedLib != null) {
+ // add to classpath
+ classpath.add(lib.getJar());
+
+ // remove from requested list.
+ requestedLibs.remove(requestedLib);
+ }
+ }
+
+ // look for not found requested libraries.
+ for (LibraryRequest library : requestedLibs) {
+ mErrorReporter.handleSyncError(
+ library.getName(),
+ SyncIssue.TYPE_OPTIONAL_LIB_NOT_FOUND,
+ "Unable to find optional library: " + library.getName());
+ }
+
+ // add annotations.jar if needed.
+ if (target.getVersion().getApiLevel() <= 15) {
+ classpath.add(mSdkInfo.getAnnotationsJar());
+ }
+
+ mBootClasspathFiltered = ImmutableList.copyOf(classpath);
+ }
+
+ return mBootClasspathFiltered;
+ }
+
+ private List<File> computeFullBootClasspath() {
+ // computes and caches the full boot classpath.
+ // Changes here should be applied to #computeFilteredClasspath()
+
+ if (mBootClasspathAll == null) {
+ checkState(mTargetInfo != null,
+ "Cannot call getBootClasspath() before setTargetInfo() is called.");
+
+ List<File> classpath = Lists.newArrayList();
+
+ IAndroidTarget target = mTargetInfo.getTarget();
+
+ for (String p : target.getBootClasspath()) {
+ classpath.add(new File(p));
+ }
+
+ // add additional libraries if any
+ List<OptionalLibrary> libs = target.getAdditionalLibraries();
+ for (OptionalLibrary lib : libs) {
+ classpath.add(lib.getJar());
+ }
+
+ // add optional libraries if any
+ List<OptionalLibrary> optionalLibraries = target.getOptionalLibraries();
+ for (OptionalLibrary lib : optionalLibraries) {
+ classpath.add(lib.getJar());
+ }
+
+ // add annotations.jar if needed.
+ if (target.getVersion().getApiLevel() <= 15) {
+ classpath.add(mSdkInfo.getAnnotationsJar());
+ }
+
+ mBootClasspathAll = ImmutableList.copyOf(classpath);
+ }
+
+ return mBootClasspathAll;
+ }
+
+ @Nullable
+ private static LibraryRequest findMatchingLib(@NonNull String name, @NonNull List<LibraryRequest> libraries) {
+ for (LibraryRequest library : libraries) {
+ if (name.equals(library.getName())) {
+ return library;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Helper method to get the boot classpath to be used during compilation.
+ *
+ * @param includeOptionalLibraries if true, optional libraries are included even if not
+ * required by the project setup.
+ */
+ @NonNull
+ public List<String> getBootClasspathAsStrings(boolean includeOptionalLibraries) {
+ List<File> classpath = getBootClasspath(includeOptionalLibraries);
+
+ // convert to Strings.
+ List<String> results = Lists.newArrayListWithCapacity(classpath.size());
+ for (File f : classpath) {
+ results.add(f.getAbsolutePath());
+ }
+
+ return results;
+ }
+
+ /**
+ * Returns the jar file for the renderscript mode.
+ *
+ * This may return null if the SDK has not been loaded yet.
+ *
+ * @return the jar file, or null.
+ *
+ * @see #setTargetInfo(SdkInfo, TargetInfo, Collection)
+ */
+ @Nullable
+ public File getRenderScriptSupportJar() {
+ if (mTargetInfo != null) {
+ return RenderScriptProcessor.getSupportJar(
+ mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the compile classpath for this config. If the config tests a library, this
+ * will include the classpath of the tested config.
+ *
+ * If the SDK was loaded, this may include the renderscript support jar.
+ *
+ * @return a non null, but possibly empty set.
+ */
+ @NonNull
+ public Set<File> getCompileClasspath(@NonNull VariantConfiguration<?,?,?> variantConfiguration) {
+ Set<File> compileClasspath = variantConfiguration.getCompileClasspath();
+
+ if (variantConfiguration.getRenderscriptSupportModeEnabled()) {
+ File renderScriptSupportJar = getRenderScriptSupportJar();
+
+ Set<File> fullJars = Sets.newHashSetWithExpectedSize(compileClasspath.size() + 1);
+ fullJars.addAll(compileClasspath);
+ if (renderScriptSupportJar != null) {
+ fullJars.add(renderScriptSupportJar);
+ }
+ compileClasspath = fullJars;
+ }
+
+ return compileClasspath;
+ }
+
+ /**
+ * Returns the list of packaged jars for this config. If the config tests a library, this
+ * will include the jars of the tested config
+ *
+ * If the SDK was loaded, this may include the renderscript support jar.
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public Set<File> getAllPackagedJars(@NonNull VariantConfiguration<?,?,?> variantConfiguration) {
+ Set<File> packagedJars = Sets.newHashSet(variantConfiguration.getAllPackagedJars());
+
+ if (variantConfiguration.getRenderscriptSupportModeEnabled()) {
+ File renderScriptSupportJar = getRenderScriptSupportJar();
+
+ if (renderScriptSupportJar != null) {
+ packagedJars.add(renderScriptSupportJar);
+ }
+ }
+
+ return packagedJars;
+ }
+
+ /**
+ * Returns the list of packaged jars for this config. If the config tests a library, this
+ * will include the jars of the tested config
+ *
+ * If the SDK was loaded, this may include the renderscript support jar.
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public Set<File> getAdditionalPackagedJars(@NonNull VariantConfiguration<?,?,?> variantConfiguration) {
+
+ if (variantConfiguration.getRenderscriptSupportModeEnabled()) {
+ File renderScriptSupportJar = getRenderScriptSupportJar();
+
+ if (renderScriptSupportJar != null) {
+ return ImmutableSet.of(renderScriptSupportJar);
+ }
+ }
+
+ return ImmutableSet.of();
+ }
+
+ /**
+ * Returns the native lib folder for the renderscript mode.
+ *
+ * This may return null if the SDK has not been loaded yet.
+ *
+ * @return the folder, or null.
+ *
+ * @see #setTargetInfo(SdkInfo, TargetInfo, Collection)
+ */
+ @Nullable
+ public File getSupportNativeLibFolder() {
+ if (mTargetInfo != null) {
+ return RenderScriptProcessor.getSupportNativeLibFolder(
+ mTargetInfo.getBuildTools().getLocation().getAbsolutePath());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an {@link PngCruncher} using aapt underneath
+ * @return an PngCruncher object
+ */
+ @NonNull
+ public PngCruncher getAaptCruncher(ProcessOutputHandler processOutputHandler) {
+ checkState(mTargetInfo != null,
+ "Cannot call getAaptCruncher() before setTargetInfo() is called.");
+ return new AaptCruncher(
+ mTargetInfo.getBuildTools().getPath(BuildToolInfo.PathId.AAPT),
+ mProcessExecutor,
+ processOutputHandler);
+ }
+
+ @NonNull
+ public ProcessExecutor getProcessExecutor() {
+ return mProcessExecutor;
+ }
+
+ @NonNull
+ public ProcessResult executeProcess(@NonNull ProcessInfo processInfo,
+ @NonNull ProcessOutputHandler handler) {
+ return mProcessExecutor.execute(processInfo, handler);
+ }
+
+ @NonNull
+ public static ClassField createClassField(@NonNull String type, @NonNull String name, @NonNull String value) {
+ return new ClassFieldImpl(type, name, value);
+ }
+
+ // Temporary trampoline
+ public static String formatXml(@NonNull org.w3c.dom.Node node, boolean endWithNewline) {
+ return XmlPrettyPrinter.prettyPrint(node, endWithNewline);
+ }
+
+ /**
+ * Invoke the Manifest Merger version 2.
+ */
+ public void mergeManifests(
+ @NonNull File mainManifest,
+ @NonNull List<File> manifestOverlays,
+ @NonNull List<? extends ManifestDependency> libraries,
+ String packageOverride,
+ int versionCode,
+ String versionName,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @Nullable Integer maxSdkVersion,
+ @NonNull String outManifestLocation,
+ @Nullable String outAaptSafeManifestLocation,
+ @Nullable String outInstantRunManifestLocation,
+ ManifestMerger2.MergeType mergeType,
+ Map<String, Object> placeHolders,
+ List<Invoker.Feature> optionalFeatures,
+ @Nullable File reportFile) {
+
+ try {
+
+ Invoker manifestMergerInvoker =
+ ManifestMerger2.newMerger(mainManifest, mLogger, mergeType)
+ .setPlaceHolderValues(placeHolders)
+ .addFlavorAndBuildTypeManifests(
+ manifestOverlays.toArray(new File[manifestOverlays.size()]))
+ .addLibraryManifests(collectLibraries(libraries))
+ .withFeatures(optionalFeatures.toArray(
+ new Invoker.Feature[optionalFeatures.size()]))
+ .setMergeReportFile(reportFile);
+
+ if (mergeType == ManifestMerger2.MergeType.APPLICATION) {
+ manifestMergerInvoker.withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
+ }
+
+ //noinspection VariableNotUsedInsideIf
+ if (outAaptSafeManifestLocation != null) {
+ manifestMergerInvoker.withFeatures(Invoker.Feature.MAKE_AAPT_SAFE);
+ }
+
+ setInjectableValues(manifestMergerInvoker,
+ packageOverride, versionCode, versionName,
+ minSdkVersion, targetSdkVersion, maxSdkVersion);
+
+ MergingReport mergingReport = manifestMergerInvoker.merge();
+ mLogger.info("Merging result:" + mergingReport.getResult());
+ switch (mergingReport.getResult()) {
+ case WARNING:
+ mergingReport.log(mLogger);
+ // fall through since these are just warnings.
+ case SUCCESS:
+ String xmlDocument = mergingReport.getMergedDocument(
+ MergingReport.MergedManifestKind.MERGED);
+ String annotatedDocument = mergingReport.getMergedDocument(
+ MergingReport.MergedManifestKind.BLAME);
+ if (annotatedDocument != null) {
+ mLogger.verbose(annotatedDocument);
+ }
+ save(xmlDocument, new File(outManifestLocation));
+ mLogger.info("Merged manifest saved to " + outManifestLocation);
+
+ if (outAaptSafeManifestLocation != null) {
+ save(mergingReport.getMergedDocument(MergingReport.MergedManifestKind.AAPT_SAFE),
+ new File(outAaptSafeManifestLocation));
+ }
+
+ if (outInstantRunManifestLocation != null) {
+ String instantRunMergedManifest = mergingReport.getMergedDocument(
+ MergingReport.MergedManifestKind.INSTANT_RUN);
+ if (instantRunMergedManifest != null) {
+ save(instantRunMergedManifest, new File(outInstantRunManifestLocation));
+ }
+ }
+ break;
+ case ERROR:
+ mergingReport.log(mLogger);
+ throw new RuntimeException(mergingReport.getReportString());
+ default:
+ throw new RuntimeException("Unhandled result type : "
+ + mergingReport.getResult());
+ }
+ } catch (ManifestMerger2.MergeFailureException e) {
+ // TODO: unacceptable.
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the {@link com.android.manifmerger.ManifestMerger2.SystemProperty} that can be injected
+ * in the manifest file.
+ */
+ private static void setInjectableValues(
+ ManifestMerger2.Invoker<?> invoker,
+ String packageOverride,
+ int versionCode,
+ String versionName,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @Nullable Integer maxSdkVersion) {
+
+ if (!Strings.isNullOrEmpty(packageOverride)) {
+ invoker.setOverride(SystemProperty.PACKAGE, packageOverride);
+ }
+ if (versionCode > 0) {
+ invoker.setOverride(SystemProperty.VERSION_CODE,
+ String.valueOf(versionCode));
+ }
+ if (!Strings.isNullOrEmpty(versionName)) {
+ invoker.setOverride(SystemProperty.VERSION_NAME, versionName);
+ }
+ if (!Strings.isNullOrEmpty(minSdkVersion)) {
+ invoker.setOverride(SystemProperty.MIN_SDK_VERSION, minSdkVersion);
+ }
+ if (!Strings.isNullOrEmpty(targetSdkVersion)) {
+ invoker.setOverride(SystemProperty.TARGET_SDK_VERSION, targetSdkVersion);
+ }
+ if (maxSdkVersion != null) {
+ invoker.setOverride(SystemProperty.MAX_SDK_VERSION, maxSdkVersion.toString());
+ }
+ }
+
+ /**
+ * Saves the {@link com.android.manifmerger.XmlDocument} to a file in UTF-8 encoding.
+ * @param xmlDocument xml document to save.
+ * @param out file to save to.
+ */
+ private static void save(String xmlDocument, File out) {
+ try {
+ Files.createParentDirs(out);
+ Files.write(xmlDocument, out, Charsets.UTF_8);
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Collect the list of libraries' manifest files.
+ * @param libraries declared dependencies
+ * @return a list of files and names for the libraries' manifest files.
+ */
+ private static ImmutableList<Pair<String, File>> collectLibraries(
+ List<? extends ManifestDependency> libraries) {
+
+ ImmutableList.Builder<Pair<String, File>> manifestFiles = ImmutableList.builder();
+ if (libraries != null) {
+ collectLibraries(libraries, manifestFiles);
+ }
+ return manifestFiles.build();
+ }
+
+ /**
+ * recursively calculate the list of libraries to merge the manifests files from.
+ * @param libraries the dependencies
+ * @param manifestFiles list of files and names identifiers for the libraries' manifest files.
+ */
+ private static void collectLibraries(List<? extends ManifestDependency> libraries,
+ ImmutableList.Builder<Pair<String, File>> manifestFiles) {
+
+ for (ManifestDependency library : libraries) {
+ manifestFiles.add(Pair.of(library.getName(), library.getManifest()));
+ List<? extends ManifestDependency> manifestDependencies = library
+ .getManifestDependencies();
+ if (!manifestDependencies.isEmpty()) {
+ collectLibraries(manifestDependencies, manifestFiles);
+ }
+ }
+ }
+
+ /**
+ * Creates the manifest for a test variant
+ *
+ * @param testApplicationId the application id of the test application
+ * @param minSdkVersion the minSdkVersion of the test application
+ * @param targetSdkVersion the targetSdkVersion of the test application
+ * @param testedApplicationId the application id of the tested application
+ * @param instrumentationRunner the name of the instrumentation runner
+ * @param handleProfiling whether or not the Instrumentation object will turn profiling on and off
+ * @param functionalTest whether or not the Instrumentation class should run as a functional test
+ * @param testManifestFile optionally user provided AndroidManifest.xml for testing application
+ * @param libraries the library dependency graph
+ * @param outManifest the output location for the merged manifest
+ *
+ * @see VariantConfiguration#getApplicationId()
+ * @see VariantConfiguration#getTestedConfig()
+ * @see VariantConfiguration#getMinSdkVersion()
+ * @see VariantConfiguration#getTestedApplicationId()
+ * @see VariantConfiguration#getInstrumentationRunner()
+ * @see VariantConfiguration#getHandleProfiling()
+ * @see VariantConfiguration#getFunctionalTest()
+ * @see VariantConfiguration#getDirectLibraries()
+ */
+ public void processTestManifest(
+ @NonNull String testApplicationId,
+ @NonNull String minSdkVersion,
+ @NonNull String targetSdkVersion,
+ @NonNull String testedApplicationId,
+ @NonNull String instrumentationRunner,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest,
+ @Nullable File testManifestFile,
+ @NonNull List<? extends ManifestDependency> libraries,
+ @NonNull Map<String, Object> manifestPlaceholders,
+ @NonNull File outManifest,
+ @NonNull File tmpDir) throws IOException {
+ checkNotNull(testApplicationId, "testApplicationId cannot be null.");
+ checkNotNull(testedApplicationId, "testedApplicationId cannot be null.");
+ checkNotNull(instrumentationRunner, "instrumentationRunner cannot be null.");
+ checkNotNull(handleProfiling, "handleProfiling cannot be null.");
+ checkNotNull(functionalTest, "functionalTest cannot be null.");
+ checkNotNull(libraries, "libraries cannot be null.");
+ checkNotNull(outManifest, "outManifestLocation cannot be null.");
+
+ // These temp files are only need in the middle of processing manifests; delete
+ // them when they're done. We're not relying on File#deleteOnExit for this
+ // since in the Gradle daemon for example that would leave the files around much
+ // longer than we want.
+ File tempFile1 = null;
+ File tempFile2 = null;
+ try {
+ FileUtils.mkdirs(tmpDir);
+ File generatedTestManifest = libraries.isEmpty() && testManifestFile == null
+ ? outManifest
+ : (tempFile1 = File.createTempFile("manifestMerger", ".xml", tmpDir));
+
+ mLogger.verbose("Generating in %1$s", generatedTestManifest.getAbsolutePath());
+ generateTestManifest(
+ testApplicationId,
+ minSdkVersion,
+ targetSdkVersion.equals("-1") ? null : targetSdkVersion,
+ testedApplicationId,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest,
+ generatedTestManifest);
+
+ if (testManifestFile != null) {
+ tempFile2 = File.createTempFile("manifestMerger", ".xml", tmpDir);
+ mLogger.verbose("Merging user supplied manifest in %1$s",
+ generatedTestManifest.getAbsolutePath());
+ Invoker invoker = ManifestMerger2.newMerger(
+ testManifestFile, mLogger, ManifestMerger2.MergeType.APPLICATION)
+ .setOverride(SystemProperty.PACKAGE, testApplicationId)
+ .setPlaceHolderValues(manifestPlaceholders)
+ .setPlaceHolderValue(PlaceholderHandler.INSTRUMENTATION_RUNNER,
+ instrumentationRunner)
+ .addLibraryManifests(generatedTestManifest);
+
+ invoker.setOverride(SystemProperty.MIN_SDK_VERSION, minSdkVersion);
+
+ if (!targetSdkVersion.equals("-1")) {
+ invoker.setOverride(SystemProperty.TARGET_SDK_VERSION, targetSdkVersion);
+ }
+ MergingReport mergingReport = invoker.merge();
+ if (libraries.isEmpty()) {
+ handleMergingResult(mergingReport, outManifest);
+ } else {
+ handleMergingResult(mergingReport, tempFile2);
+ generatedTestManifest = tempFile2;
+ }
+ }
+
+ if (!libraries.isEmpty()) {
+ MergingReport mergingReport = ManifestMerger2.newMerger(
+ generatedTestManifest, mLogger, ManifestMerger2.MergeType.APPLICATION)
+ .withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)
+ .setOverride(SystemProperty.PACKAGE, testApplicationId)
+ .addLibraryManifests(collectLibraries(libraries))
+ .setPlaceHolderValues(manifestPlaceholders)
+ .merge();
+
+ handleMergingResult(mergingReport, outManifest);
+ }
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (tempFile1 != null) {
+ FileUtils.delete(tempFile1);
+ }
+ if (tempFile2 != null) {
+ FileUtils.delete(tempFile2);
+ }
+ }
+ }
+
+ private void handleMergingResult(@NonNull MergingReport mergingReport, @NonNull File outFile) {
+ switch (mergingReport.getResult()) {
+ case WARNING:
+ mergingReport.log(mLogger);
+ // fall through since these are just warnings.
+ case SUCCESS:
+ try {
+ String annotatedDocument = mergingReport.getMergedDocument(
+ MergingReport.MergedManifestKind.BLAME);
+ if (annotatedDocument != null) {
+ mLogger.verbose(annotatedDocument);
+ } else {
+ mLogger.verbose("No blaming records from manifest merger");
+ }
+ } catch (Exception e) {
+ mLogger.error(e, "cannot print resulting xml");
+ }
+ String finalMergedDocument = mergingReport
+ .getMergedDocument(MergingReport.MergedManifestKind.MERGED);
+ if (finalMergedDocument == null) {
+ throw new RuntimeException("No result from manifest merger");
+ }
+ try {
+ Files.write(finalMergedDocument, outFile, Charsets.UTF_8);
+ } catch (IOException e) {
+ mLogger.error(e, "Cannot write resulting xml");
+ throw new RuntimeException(e);
+ }
+ mLogger.info("Merged manifest saved to " + outFile);
+ break;
+ case ERROR:
+ mergingReport.log(mLogger);
+ throw new RuntimeException(mergingReport.getReportString());
+ default:
+ throw new RuntimeException("Unhandled result type : "
+ + mergingReport.getResult());
+ }
+ }
+
+ private static void generateTestManifest(
+ @NonNull String testApplicationId,
+ @Nullable String minSdkVersion,
+ @Nullable String targetSdkVersion,
+ @NonNull String testedApplicationId,
+ @NonNull String instrumentationRunner,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest,
+ @NonNull File outManifestLocation) {
+ TestManifestGenerator generator = new TestManifestGenerator(
+ outManifestLocation,
+ testApplicationId,
+ minSdkVersion,
+ targetSdkVersion,
+ testedApplicationId,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest);
+ try {
+ generator.generate();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Process the resources and generate R.java and/or the packaged resources.
+ *
+ * @param aaptCommand aapt command invocation parameters.
+ * @param enforceUniquePackageName if true method will fail if some libraries share the same
+ * package name
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws ProcessException
+ */
+ public void processResources(
+ @NonNull AaptPackageProcessBuilder aaptCommand,
+ boolean enforceUniquePackageName,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws IOException, InterruptedException, ProcessException {
+
+ checkState(mTargetInfo != null,
+ "Cannot call processResources() before setTargetInfo() is called.");
+
+ // launch aapt: create the command line
+ ProcessInfo processInfo = aaptCommand.build(
+ mTargetInfo.getBuildTools(), mTargetInfo.getTarget(), mLogger);
+
+ ProcessResult result = mProcessExecutor.execute(processInfo, processOutputHandler);
+ result.rethrowFailure().assertNormalExitValue();
+
+ // If the project has libraries, R needs to be created for each library.
+ if (aaptCommand.getSourceOutputDir() != null
+ && !aaptCommand.getLibraries().isEmpty()) {
+ SymbolLoader fullSymbolValues = null;
+
+ // First pass processing the libraries, collecting them by packageName,
+ // and ignoring the ones that have the same package name as the application
+ // (since that R class was already created).
+ String appPackageName = aaptCommand.getPackageForR();
+ if (appPackageName == null) {
+ appPackageName = VariantConfiguration.getManifestPackage(aaptCommand.getManifestFile());
+ }
+
+ // list of all the symbol loaders per package names.
+ Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
+
+ for (SymbolFileProvider lib : aaptCommand.getLibraries()) {
+ if (lib.isOptional()) {
+ continue;
+ }
+ String packageName = VariantConfiguration.getManifestPackage(lib.getManifest());
+ if (appPackageName == null) {
+ continue;
+ }
+
+ if (appPackageName.equals(packageName)) {
+ if (enforceUniquePackageName) {
+ String msg = String.format(
+ "Error: A library uses the same package as this project: %s",
+ packageName);
+ throw new RuntimeException(msg);
+ }
+
+ // ignore libraries that have the same package name as the app
+ continue;
+ }
+
+ File rFile = lib.getSymbolFile();
+ // if the library has no resource, this file won't exist.
+ if (rFile.isFile()) {
+
+ // load the full values if that's not already been done.
+ // Doing it lazily allow us to support the case where there's no
+ // resources anywhere.
+ if (fullSymbolValues == null) {
+ fullSymbolValues = new SymbolLoader(new File(aaptCommand.getSymbolOutputDir(), "R.txt"),
+ mLogger);
+ fullSymbolValues.load();
+ }
+
+ SymbolLoader libSymbols = new SymbolLoader(rFile, mLogger);
+ libSymbols.load();
+
+
+ // store these symbols by associating them with the package name.
+ libMap.put(packageName, libSymbols);
+ }
+ }
+
+ // now loop on all the package name, merge all the symbols to write, and write them
+ for (String packageName : libMap.keySet()) {
+ Collection<SymbolLoader> symbols = libMap.get(packageName);
+
+ if (enforceUniquePackageName && symbols.size() > 1) {
+ String msg = String.format(
+ "Error: more than one library with package name '%s'", packageName);
+ throw new RuntimeException(msg);
+ }
+
+ SymbolWriter writer = new SymbolWriter(aaptCommand.getSourceOutputDir(), packageName,
+ fullSymbolValues);
+ for (SymbolLoader symbolLoader : symbols) {
+ writer.addSymbolsToWrite(symbolLoader);
+ }
+ writer.write();
+ }
+ }
+ }
+
+ public void generateApkData(
+ @NonNull File apkFile,
+ @NonNull File outResFolder,
+ @NonNull String mainPkgName,
+ @NonNull String resName) throws ProcessException, IOException {
+
+ // need to run aapt to get apk information
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ String aapt = buildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
+ if (aapt == null) {
+ throw new IllegalStateException(
+ "Unable to get aapt location from Build Tools " + buildToolInfo.getRevision());
+ }
+
+ ApkInfoParser parser = new ApkInfoParser(new File(aapt), mProcessExecutor);
+ ApkInfoParser.ApkInfo apkInfo = parser.parseApk(apkFile);
+
+ if (!apkInfo.getPackageName().equals(mainPkgName)) {
+ throw new RuntimeException("The main and the micro apps do not have the same package name.");
+ }
+
+ String content = String.format(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<wearableApp package=\"%1$s\">\n" +
+ " <versionCode>%2$s</versionCode>\n" +
+ " <versionName>%3$s</versionName>\n" +
+ " <rawPathResId>%4$s</rawPathResId>\n" +
+ "</wearableApp>",
+ apkInfo.getPackageName(),
+ apkInfo.getVersionCode(),
+ apkInfo.getVersionName(),
+ resName);
+
+ // xml folder
+ File resXmlFile = new File(outResFolder, FD_RES_XML);
+ FileUtils.mkdirs(resXmlFile);
+
+ Files.write(content,
+ new File(resXmlFile, ANDROID_WEAR_MICRO_APK + DOT_XML),
+ Charsets.UTF_8);
+ }
+
+ public static void generateApkDataEntryInManifest(
+ int minSdkVersion,
+ int targetSdkVersion,
+ @NonNull File manifestFile)
+ throws InterruptedException, LoggedErrorException, IOException {
+
+ StringBuilder content = new StringBuilder();
+ content.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
+ .append("<manifest package=\"\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n")
+ .append(" <uses-sdk android:minSdkVersion=\"")
+ .append(minSdkVersion).append("\"");
+ if (targetSdkVersion != -1) {
+ content.append(" android:targetSdkVersion=\"").append(targetSdkVersion).append("\"");
+ }
+ content.append("/>\n");
+ content.append(" <application>\n")
+ .append(" <meta-data android:name=\"" + ANDROID_WEAR + "\"\n")
+ .append(" android:resource=\"@xml/" + ANDROID_WEAR_MICRO_APK)
+ .append("\" />\n")
+ .append(" </application>\n")
+ .append("</manifest>\n");
+
+ Files.write(content, manifestFile, Charsets.UTF_8);
+ }
+
+ /**
+ * Compiles all the aidl files found in the given source folders.
+ *
+ * @param sourceFolders all the source folders to find files to compile
+ * @param sourceOutputDir the output dir in which to generate the source code
+ * @param packagedOutputDir the output dir for the AIDL files that will be packaged in an aar
+ * @param packageWhiteList a list of AIDL FQCNs that are not parcelable that should also be
+ * packaged in an aar
+ * @param importFolders import folders
+ * @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
+ * of the compilation.
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void compileAllAidlFiles(@NonNull List<File> sourceFolders,
+ @NonNull File sourceOutputDir,
+ @Nullable File packagedOutputDir,
+ @Nullable Collection<String> packageWhiteList,
+ @NonNull List<File> importFolders,
+ @Nullable DependencyFileProcessor dependencyFileProcessor,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws IOException, InterruptedException, LoggedErrorException, ProcessException {
+ checkNotNull(sourceFolders, "sourceFolders cannot be null.");
+ checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
+ checkNotNull(importFolders, "importFolders cannot be null.");
+ checkState(mTargetInfo != null,
+ "Cannot call compileAllAidlFiles() before setTargetInfo() is called.");
+
+ IAndroidTarget target = mTargetInfo.getTarget();
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
+ if (aidl == null || !new File(aidl).isFile()) {
+ throw new IllegalStateException("aidl is missing");
+ }
+
+ List<File> fullImportList = Lists.newArrayListWithCapacity(
+ sourceFolders.size() + importFolders.size());
+ fullImportList.addAll(sourceFolders);
+ fullImportList.addAll(importFolders);
+
+ AidlProcessor processor = new AidlProcessor(
+ aidl,
+ target.getPath(IAndroidTarget.ANDROID_AIDL),
+ fullImportList,
+ sourceOutputDir,
+ packagedOutputDir,
+ packageWhiteList,
+ dependencyFileProcessor != null ?
+ dependencyFileProcessor : sNoOpDependencyFileProcessor,
+ mProcessExecutor,
+ processOutputHandler);
+
+ SourceSearcher searcher = new SourceSearcher(sourceFolders, "aidl");
+ searcher.setUseExecutor(true);
+ searcher.search(processor);
+ }
+
+ /**
+ * Compiles the given aidl file.
+ *
+ * @param aidlFile the AIDL file to compile
+ * @param sourceOutputDir the output dir in which to generate the source code
+ * @param importFolders all the import folders, including the source folders.
+ * @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
+ * of the compilation.
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void compileAidlFile(@NonNull File sourceFolder,
+ @NonNull File aidlFile,
+ @NonNull File sourceOutputDir,
+ @Nullable File packagedOutputDir,
+ @Nullable Collection<String> packageWhitelist,
+ @NonNull List<File> importFolders,
+ @Nullable DependencyFileProcessor dependencyFileProcessor,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws IOException, InterruptedException, LoggedErrorException, ProcessException {
+ checkNotNull(aidlFile, "aidlFile cannot be null.");
+ checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
+ checkNotNull(importFolders, "importFolders cannot be null.");
+ checkState(mTargetInfo != null,
+ "Cannot call compileAidlFile() before setTargetInfo() is called.");
+
+ IAndroidTarget target = mTargetInfo.getTarget();
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
+ if (aidl == null || !new File(aidl).isFile()) {
+ throw new IllegalStateException("aidl is missing");
+ }
+
+ AidlProcessor processor = new AidlProcessor(
+ aidl,
+ target.getPath(IAndroidTarget.ANDROID_AIDL),
+ importFolders,
+ sourceOutputDir,
+ packagedOutputDir,
+ packageWhitelist,
+ dependencyFileProcessor != null ?
+ dependencyFileProcessor : sNoOpDependencyFileProcessor,
+ mProcessExecutor,
+ processOutputHandler);
+
+ processor.processFile(sourceFolder, aidlFile);
+ }
+
+ /**
+ * Compiles all the renderscript files found in the given source folders.
+ *
+ * Right now this is the only way to compile them as the renderscript compiler requires all
+ * renderscript files to be passed for all compilation.
+ *
+ * Therefore whenever a renderscript file or header changes, all must be recompiled.
+ *
+ * @param sourceFolders all the source folders to find files to compile
+ * @param importFolders all the import folders.
+ * @param sourceOutputDir the output dir in which to generate the source code
+ * @param resOutputDir the output dir in which to generate the bitcode file
+ * @param targetApi the target api
+ * @param debugBuild whether the build is debug
+ * @param optimLevel the optimization level
+ * @param ndkMode whether the renderscript code should be compiled to generate C/C++ bindings
+ * @param supportMode support mode flag to generate .so files.
+ * @param abiFilters ABI filters in case of support mode
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void compileAllRenderscriptFiles(
+ @NonNull List<File> sourceFolders,
+ @NonNull List<File> importFolders,
+ @NonNull File sourceOutputDir,
+ @NonNull File resOutputDir,
+ @NonNull File objOutputDir,
+ @NonNull File libOutputDir,
+ int targetApi,
+ boolean debugBuild,
+ int optimLevel,
+ boolean ndkMode,
+ boolean supportMode,
+ @Nullable Set<String> abiFilters,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws InterruptedException, ProcessException, LoggedErrorException, IOException {
+ checkNotNull(sourceFolders, "sourceFolders cannot be null.");
+ checkNotNull(importFolders, "importFolders cannot be null.");
+ checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
+ checkNotNull(resOutputDir, "resOutputDir cannot be null.");
+ checkState(mTargetInfo != null,
+ "Cannot call compileAllRenderscriptFiles() before setTargetInfo() is called.");
+
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ String renderscript = buildToolInfo.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
+ if (renderscript == null || !new File(renderscript).isFile()) {
+ throw new IllegalStateException("llvm-rs-cc is missing");
+ }
+
+ RenderScriptProcessor processor = new RenderScriptProcessor(
+ sourceFolders,
+ importFolders,
+ sourceOutputDir,
+ resOutputDir,
+ objOutputDir,
+ libOutputDir,
+ buildToolInfo,
+ targetApi,
+ debugBuild,
+ optimLevel,
+ ndkMode,
+ supportMode,
+ abiFilters);
+ processor.build(mProcessExecutor, processOutputHandler);
+ }
+
+ /**
+ * Computes and returns the leaf folders based on a given file extension.
+ *
+ * This looks through all the given root import folders, and recursively search for leaf
+ * folders containing files matching the given extensions. All the leaf folders are gathered
+ * and returned in the list.
+ *
+ * @param extension the extension to search for.
+ * @param importFolders an array of list of root folders.
+ * @return a list of leaf folder, never null.
+ */
+ @NonNull
+ public static List<File> getLeafFolders(@NonNull String extension, List<File>... importFolders) {
+ List<File> results = Lists.newArrayList();
+
+ if (importFolders != null) {
+ for (List<File> folders : importFolders) {
+ SourceSearcher searcher = new SourceSearcher(folders, extension);
+ searcher.setUseExecutor(false);
+ LeafFolderGatherer processor = new LeafFolderGatherer();
+ try {
+ searcher.search(processor);
+ } catch (InterruptedException e) {
+ // wont happen as we're not using the executor, and our processor
+ // doesn't throw those.
+ } catch (IOException e) {
+ // wont happen as we're not using the executor, and our processor
+ // doesn't throw those.
+ } catch (LoggedErrorException e) {
+ // wont happen as we're not using the executor, and our processor
+ // doesn't throw those.
+ } catch (ProcessException e) {
+ // wont happen as we're not using the executor, and our processor
+ // doesn't throw those.
+ }
+
+ results.addAll(processor.getFolders());
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * Converts the bytecode to Dalvik format
+ * @param inputs the input files
+ * @param outDexFolder the location of the output folder
+ * @param dexOptions dex options
+ * @param additionalParameters list of additional parameters to give to dx
+ * @param incremental true if it should attempt incremental dex if applicable
+ * @param instantRunMode true if we are invoking dex to convert classes while creating
+ * instant-run related artifacts.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws ProcessException
+ */
+ public void convertByteCode(
+ @NonNull Collection<File> inputs,
+ @NonNull File outDexFolder,
+ boolean multidex,
+ @Nullable File mainDexList,
+ @NonNull DexOptions dexOptions,
+ @Nullable List<String> additionalParameters,
+ boolean incremental,
+ boolean optimize,
+ @NonNull ProcessOutputHandler processOutputHandler,
+ final boolean instantRunMode)
+ throws IOException, InterruptedException, ProcessException {
+ checkNotNull(inputs, "inputs cannot be null.");
+ checkNotNull(outDexFolder, "outDexFolder cannot be null.");
+ checkNotNull(dexOptions, "dexOptions cannot be null.");
+ checkArgument(outDexFolder.isDirectory(), "outDexFolder must be a folder");
+ checkState(mTargetInfo != null,
+ "Cannot call convertByteCode() before setTargetInfo() is called.");
+
+ ImmutableList.Builder<File> verifiedInputs = ImmutableList.builder();
+ for (File input : inputs) {
+ if (checkLibraryClassesJar(input)) {
+ verifiedInputs.add(input);
+ }
+ }
+
+ DexProcessBuilder builder = new DexProcessBuilder(outDexFolder);
+
+ builder.setVerbose(mVerboseExec)
+ .setIncremental(incremental)
+ .setNoOptimize(!optimize)
+ .setMultiDex(multidex)
+ .setMainDexList(mainDexList)
+ .addInputs(verifiedInputs.build());
+
+ if (additionalParameters != null) {
+ builder.additionalParameters(additionalParameters);
+ }
+
+ runDexer(builder, dexOptions, processOutputHandler, instantRunMode);
+ }
+
+ private static final Object LOCK_FOR_DEX = new Object();
+ private static final AtomicInteger DEX_PROCESS_COUNT = new AtomicInteger(4);
+ private static ExecutorService sDexExecutorService = null;
+
+ private void runDexer(
+ @NonNull final DexProcessBuilder builder,
+ @NonNull final DexOptions dexOptions,
+ @NonNull final ProcessOutputHandler processOutputHandler,
+ final boolean instantRunMode)
+ throws ProcessException, IOException, InterruptedException {
+
+
+ Revision buildToolsVersion = mTargetInfo.getBuildTools().getRevision();
+
+ if (shouldDexInProcess(builder, dexOptions, buildToolsVersion, instantRunMode, getLogger())) {
+ File dxJar = new File(
+ mTargetInfo.getBuildTools().getPath(BuildToolInfo.PathId.DX_JAR));
+ DexWrapper dexWrapper = DexWrapper.obtain(dxJar);
+ try {
+ ProcessResult result =
+ dexWrapper.run(builder, dexOptions, processOutputHandler, mLogger);
+ result.assertNormalExitValue();
+ } finally {
+ dexWrapper.release();
+ }
+ } else {
+
+ // allocate the executorService if necessary
+ synchronized (LOCK_FOR_DEX) {
+ if (sDexExecutorService == null) {
+ if (dexOptions.getMaxProcessCount() != null) {
+ DEX_PROCESS_COUNT.set(dexOptions.getMaxProcessCount());
+ }
+ getLogger().info("Allocated dexExecutorService of size %d", DEX_PROCESS_COUNT
+ .get());
+ sDexExecutorService = Executors.newFixedThreadPool(DEX_PROCESS_COUNT.get());
+ } else {
+ // check whether our executor service has the same number of max processes as
+ // this module requests, and print a warning if necessary.
+ if (dexOptions.getMaxProcessCount() != null
+ && dexOptions.getMaxProcessCount() != DEX_PROCESS_COUNT.get()) {
+ getLogger().warning(
+ "dexOptions is specifying a maximum number of %1$d concurrent dx processes,"
+ + " but the Gradle daemon was initialized with %2$d.\n"
+ + "To initialize with a different maximum value,"
+ + " first stop the Gradle daemon by calling ‘gradlew —-stop’.",
+ dexOptions.getMaxProcessCount(),
+ DEX_PROCESS_COUNT.get());
+ }
+ }
+ }
+
+ try {
+ final String submission = Joiner.on(',').join(builder.getInputs());
+ // this is a hack, we always spawn a new process for dependencies.jar so it does
+ // get built in parallel with the slices, this is only valid for InstantRun mode.
+ if (submission.contains("dependencies.jar")) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ JavaProcessInfo javaProcessInfo = builder.build(mTargetInfo.getBuildTools(), dexOptions);
+ ProcessResult result = mJavaProcessExecutor.execute(javaProcessInfo,
+ processOutputHandler);
+ result.rethrowFailure().assertNormalExitValue();
+ getLogger().info("Dexing " + submission + " took " + stopwatch.toString());
+ } else {
+ sDexExecutorService.submit(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ JavaProcessInfo javaProcessInfo = builder
+ .build(mTargetInfo.getBuildTools(), dexOptions);
+ ProcessResult result = mJavaProcessExecutor.execute(javaProcessInfo,
+ processOutputHandler);
+ result.rethrowFailure().assertNormalExitValue();
+ getLogger().info(
+ "Dexing " + submission + " took " + stopwatch.toString());
+ return null;
+ }
+ }).get();
+ }
+ } catch (ExecutionException e) {
+ throw new ProcessException(e);
+ }
+ }
+ }
+
+ /**
+ * Determine whether to dex in process.
+ *
+ * If dexOptions.dexInProcess is true then throw RuntimeExceptions if conditions are not met.
+ *
+ * Otherwise if instantRunMode is enabled, quietly try to check if only a small number of
+ * files will be dexed.
+ *
+ */
+ @VisibleForTesting
+ static boolean shouldDexInProcess(
+ @NonNull DexProcessBuilder builder,
+ @NonNull DexOptions dexOptions,
+ @NonNull Revision buildToolsVersion,
+ boolean instantRunMode,
+ @NonNull ILogger logger) {
+
+ return false;
+
+ //
+ //// Version that supports all flags that we know about, including numThreads.
+ //Revision minimumBuildTools = DexProcessBuilder.FIXED_DX_MERGER;
+ //
+ //if (buildToolsVersion.compareTo(minimumBuildTools) < 0) {
+ // throw new RuntimeException("Running dex in-process requires build tools "
+ // + minimumBuildTools.toShortString());
+ //}
+ //
+ //if (!DexWrapper.noMainDexOnClasspath()) {
+ // logger.warning("dx.jar is on Android Gradle plugin classpath, which will cause issues "
+ // + "with dexing in process, reverted to out of process.");
+ // return false;
+ //}
+ //
+ //// Requested memory for dex
+ //long requestedHeapSize = parseHeapSize(dexOptions.getJavaMaxHeapSize());
+ //long maxMemory = Runtime.getRuntime().maxMemory();
+ ////TODO: Is this the right heuristic?
+ //
+ //if (requestedHeapSize > maxMemory) {
+ // String dslRequest = dexOptions.getJavaMaxHeapSize();
+ // logger.warning(String.format(
+ // "To run dex in process, the Gradle daemon needs a larger heap. "
+ // + "It currently has %1$d MB.\n"
+ // + "For faster builds, increase the maximum heap size for the "
+ // + "Gradle daemon to more than %2$s.\n"
+ // + "To do this, set org.gradle.jvmargs=-Xmx%2$s in the "
+ // + "project gradle.properties.\n"
+ // + "For more information see "
+ // + "https://docs.gradle.org/current/userguide/build_environment.html\n",
+ // maxMemory / (1024 * 1024),
+ // (dslRequest == null) ? "1G" :
+ // dslRequest + " " + "as specified in dexOptions.javaMaxHeapSize"));
+ // return false;
+ //}
+ //return true;
+ }
+
+ private static final long DEFAULT_DEX_HEAP_SIZE = 1024 * 1024 * 1024; // 1 GiB
+
+ @VisibleForTesting
+ static long parseHeapSize(@Nullable String sizeParameter) {
+ if (sizeParameter == null) {
+ return DEFAULT_DEX_HEAP_SIZE;
+ }
+ long multiplier = 1;
+ if (SdkUtils.endsWithIgnoreCase(sizeParameter ,"k")) {
+ multiplier = 1024;
+ } else if (SdkUtils.endsWithIgnoreCase(sizeParameter ,"m")) {
+ multiplier = 1024 * 1024;
+ } else if (SdkUtils.endsWithIgnoreCase(sizeParameter ,"g")) {
+ multiplier = 1024 * 1024 * 1024;
+ }
+
+ if (multiplier != 1) {
+ sizeParameter = sizeParameter.substring(0, sizeParameter.length() - 1);
+ }
+
+ try {
+ return multiplier * Long.parseLong(sizeParameter);
+ } catch (NumberFormatException e) {
+ return DEFAULT_DEX_HEAP_SIZE;
+ }
+ }
+
+ public Set<String> createMainDexList(
+ @NonNull File allClassesJarFile,
+ @NonNull File jarOfRoots) throws ProcessException {
+
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+
+ String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
+ if (dx == null || !new File(dx).isFile()) {
+ throw new IllegalStateException("dx.jar is missing");
+ }
+
+ builder.setClasspath(dx);
+ builder.setMain("com.android.multidex.ClassReferenceListBuilder");
+
+ builder.addArgs(jarOfRoots.getAbsolutePath());
+ builder.addArgs(allClassesJarFile.getAbsolutePath());
+
+ CachedProcessOutputHandler processOutputHandler = new CachedProcessOutputHandler();
+
+ mJavaProcessExecutor.execute(builder.createJavaProcess(), processOutputHandler)
+ .rethrowFailure()
+ .assertNormalExitValue();
+
+ String content = processOutputHandler.getProcessOutput().getStandardOutputAsString();
+
+ return Sets.newHashSet(Splitter.on('\n').split(content));
+ }
+
+ /**
+ * Converts the bytecode to Dalvik format, using the {@link PreDexCache} layer.
+ *
+ * @param inputFile the input file
+ * @param outFile the output file or folder if multi-dex is enabled.
+ * @param multiDex whether multidex is enabled.
+ * @param dexOptions dex options
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws ProcessException
+ */
+ public void preDexLibrary(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ boolean multiDex,
+ @NonNull DexOptions dexOptions,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws IOException, InterruptedException, ProcessException {
+ checkState(mTargetInfo != null,
+ "Cannot call preDexLibrary() before setTargetInfo() is called.");
+
+ PreDexCache.getCache().preDexLibrary(
+ this,
+ inputFile,
+ outFile,
+ multiDex,
+ dexOptions,
+ processOutputHandler);
+ }
+
+ /**
+ * Converts the bytecode to Dalvik format, ignoring the {@link PreDexCache} layer.
+ *
+ * @param inputFile the input file
+ * @param outFile the output file or folder if multi-dex is enabled.
+ * @param multiDex whether multidex is enabled.
+ * @param dexOptions the dex options
+ * @return the list of generated files.
+ *
+ * @throws ProcessException
+ */
+ @NonNull
+ public ImmutableList<File> preDexLibraryNoCache(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ boolean multiDex,
+ @NonNull DexOptions dexOptions,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws ProcessException, IOException, InterruptedException {
+ checkNotNull(inputFile, "inputFile cannot be null.");
+ checkNotNull(outFile, "outFile cannot be null.");
+ checkNotNull(dexOptions, "dexOptions cannot be null.");
+
+ try {
+ if (!checkLibraryClassesJar(inputFile)) {
+ return ImmutableList.of();
+ }
+ } catch(IOException e) {
+ throw new RuntimeException("Exception while checking library jar", e);
+ }
+ DexProcessBuilder builder = new DexProcessBuilder(outFile);
+
+ builder.setVerbose(mVerboseExec)
+ .setMultiDex(multiDex)
+ .addInput(inputFile);
+
+ runDexer(builder, dexOptions, processOutputHandler, false /* instantRunMode */);
+
+ if (multiDex) {
+ File[] files = outFile.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+ return name.endsWith(DOT_DEX);
+ }
+ });
+
+ if (files == null || files.length == 0) {
+ throw new RuntimeException("No dex files created at " + outFile.getAbsolutePath());
+ }
+
+ return ImmutableList.copyOf(files);
+ } else {
+ return ImmutableList.of(outFile);
+ }
+ }
+
+ /**
+ * Returns true if the library (jar or folder) contains class files, false otherwise.
+ */
+ private static boolean checkLibraryClassesJar(@NonNull File input) throws IOException {
+
+ if (!input.exists()) {
+ return false;
+ }
+
+ if (input.isDirectory()) {
+ return checkFolder(input);
+ }
+
+ ZipFile zipFile = new ZipFile(input);
+ try {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while(entries.hasMoreElements()) {
+ String name = entries.nextElement().getName();
+ if (name.endsWith(DOT_CLASS) || name.endsWith(DOT_DEX)) {
+ return true;
+ }
+ }
+ return false;
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ /**
+ * Returns true if this folder or one of its subfolder contains a class file, false otherwise.
+ */
+ private static boolean checkFolder(@NonNull File folder) {
+ File[] subFolders = folder.listFiles();
+ if (subFolders != null) {
+ for (File childFolder : subFolders) {
+ if (childFolder.isFile()) {
+ String name = childFolder.getName();
+ if (name.endsWith(DOT_CLASS) || name.endsWith(DOT_DEX)) {
+ return true;
+ }
+ }
+ if (childFolder.isDirectory()) {
+ // if childFolder returns false, continue search otherwise return success.
+ if (checkFolder(childFolder)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Converts java source code into android byte codes using the jack integration APIs.
+ * Jack will run in memory.
+ */
+ public boolean convertByteCodeUsingJackApis(
+ @NonNull File dexOutputFolder,
+ @NonNull File jackOutputFile,
+ @NonNull Collection<File> classpath,
+ @NonNull Collection<File> packagedLibraries,
+ @NonNull Collection<File> sourceFiles,
+ @Nullable Collection<File> proguardFiles,
+ @Nullable File mappingFile,
+ @NonNull Collection<File> jarJarRulesFiles,
+ @Nullable File incrementalDir,
+ @Nullable File javaResourcesFolder,
+ boolean multiDex,
+ int minSdkVersion) {
+
+ BuildToolServiceLoader buildToolServiceLoader
+ = BuildToolsServiceLoader.INSTANCE.forVersion(mTargetInfo.getBuildTools());
+
+ Api01CompilationTask compilationTask = null;
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ Optional<JackProvider> jackProvider = buildToolServiceLoader
+ .getSingleService(getLogger(), BuildToolsServiceLoader.JACK);
+ if (jackProvider.isPresent()) {
+ Api01Config config;
+
+ // Get configuration object
+ try {
+ config = jackProvider.get().createConfig(Api01Config.class);
+
+ config.setClasspath(new ArrayList<File>(classpath));
+ config.setOutputDexDir(dexOutputFolder);
+ config.setOutputJackFile(jackOutputFile);
+ config.setImportedJackLibraryFiles(new ArrayList<File>(packagedLibraries));
+
+ if (proguardFiles != null) {
+ config.setProguardConfigFiles(new ArrayList<File>(proguardFiles));
+ }
+
+ if (!jarJarRulesFiles.isEmpty()) {
+ config.setJarJarConfigFiles(ImmutableList.copyOf(jarJarRulesFiles));
+ }
+
+ if (multiDex) {
+ if (minSdkVersion < BuildToolInfo.SDK_LEVEL_FOR_MULTIDEX_NATIVE_SUPPORT) {
+ config.setMultiDexKind(MultiDexKind.LEGACY);
+ } else {
+ config.setMultiDexKind(MultiDexKind.NATIVE);
+ }
+ }
+
+ config.setSourceEntries(new ArrayList<File>(sourceFiles));
+ if (mappingFile != null) {
+ config.setProperty("jack.obfuscation.mapping.dump", "true");
+ config.setObfuscationMappingOutputFile(mappingFile);
+ }
+
+ config.setProperty("jack.import.resource.policy", "keep-first");
+
+ config.setReporter(ReporterKind.DEFAULT, outputStream);
+
+ // set the incremental dir if set and either already exists or can be created.
+ if (incrementalDir != null) {
+ if (!incrementalDir.exists() && !incrementalDir.mkdirs()) {
+ mLogger.warning("Cannot create %1$s directory, "
+ + "jack incremental support disabled", incrementalDir);
+ }
+ if (incrementalDir.exists()) {
+ config.setIncrementalDir(incrementalDir);
+ }
+ }
+ if (javaResourcesFolder != null) {
+ ArrayList<File> folders = Lists.newArrayListWithExpectedSize(3);
+ folders.add(javaResourcesFolder);
+ config.setResourceDirs(folders);
+ }
+
+ compilationTask = config.getTask();
+ } catch (ConfigNotSupportedException e1) {
+ mLogger.warning("Jack APIs v01 not supported");
+ } catch (ConfigurationException e) {
+ mLogger.error(e,
+ "Jack APIs v01 configuration failed, reverting to native process");
+ }
+ }
+
+ if (compilationTask == null) {
+ return false;
+ }
+
+ // Run the compilation
+ try {
+ compilationTask.run();
+ mLogger.info(outputStream.toString());
+ return true;
+ } catch (CompilationException e) {
+ mLogger.error(e, outputStream.toString());
+ } catch (UnrecoverableException e) {
+ mLogger.error(e,
+ "Something out of Jack control has happened: " + e.getMessage());
+ } catch (ConfigurationException e) {
+ mLogger.error(e, outputStream.toString());
+ }
+ } catch (ClassNotFoundException e) {
+ getLogger().warning("Cannot load Jack APIs v01 " + e.getMessage());
+ getLogger().warning("Reverting to native process invocation");
+ }
+ return false;
+ }
+
+ public void convertByteCodeWithJack(
+ @NonNull File dexOutputFolder,
+ @NonNull File jackOutputFile,
+ @NonNull String classpath,
+ @NonNull Collection<File> packagedLibraries,
+ @NonNull File ecjOptionFile,
+ @Nullable Collection<File> proguardFiles,
+ @Nullable File mappingFile,
+ @NonNull Collection<File> jarJarRuleFiles,
+ boolean multiDex,
+ int minSdkVersion,
+ boolean debugLog,
+ String javaMaxHeapSize,
+ @NonNull ProcessOutputHandler processOutputHandler) throws ProcessException {
+ JackProcessBuilder builder = new JackProcessBuilder();
+
+ builder.setDebugLog(debugLog)
+ .setVerbose(mVerboseExec)
+ .setJavaMaxHeapSize(javaMaxHeapSize)
+ .setClasspath(classpath)
+ .setDexOutputFolder(dexOutputFolder)
+ .setJackOutputFile(jackOutputFile)
+ .addImportFiles(packagedLibraries)
+ .setEcjOptionFile(ecjOptionFile);
+
+ if (proguardFiles != null) {
+ builder.addProguardFiles(proguardFiles).setMappingFile(mappingFile);
+ }
+
+ if (multiDex) {
+ builder.setMultiDex(true).setMinSdkVersion(minSdkVersion);
+ }
+
+ builder.setJarJarRuleFiles(jarJarRuleFiles);
+
+ mJavaProcessExecutor.execute(
+ builder.build(mTargetInfo.getBuildTools()), processOutputHandler)
+ .rethrowFailure().assertNormalExitValue();
+ }
+
+ /**
+ * Converts the bytecode of a library to the jack format
+ * @param inputFile the input file
+ * @param outFile the location of the output classes.dex file
+ * @param dexOptions dex options
+ *
+ * @throws ProcessException
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void convertLibraryToJack(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull DexOptions dexOptions,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws ProcessException, IOException, InterruptedException {
+ checkState(mTargetInfo != null,
+ "Cannot call preJackLibrary() before setTargetInfo() is called.");
+
+ BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
+
+ JackConversionCache.getCache().convertLibrary(
+ inputFile,
+ outFile,
+ dexOptions,
+ buildToolInfo,
+ mVerboseExec,
+ mJavaProcessExecutor,
+ processOutputHandler,
+ mLogger);
+ }
+
+ public static List<File> convertLibaryToJackUsingApis(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull DexOptions dexOptions,
+ @NonNull BuildToolInfo buildToolInfo,
+ boolean verbose,
+ @NonNull JavaProcessExecutor processExecutor,
+ @NonNull ProcessOutputHandler processOutputHandler,
+ @NonNull ILogger logger) throws ProcessException {
+
+ BuildToolServiceLoader buildToolServiceLoader = BuildToolsServiceLoader.INSTANCE
+ .forVersion(buildToolInfo);
+ if (System.getenv("USE_JACK_API") != null) {
+ try {
+ Optional<JillProvider> jillProviderOptional = buildToolServiceLoader
+ .getSingleService(logger, BuildToolsServiceLoader.JILL);
+
+ if (jillProviderOptional.isPresent()) {
+ com.android.jill.api.v01.Api01Config config =
+ jillProviderOptional.get().createConfig(
+ com.android.jill.api.v01.Api01Config.class);
+
+ config.setInputJavaBinaryFile(inputFile);
+ config.setOutputJackFile(outFile);
+ config.setVerbose(verbose);
+
+ Api01TranslationTask translationTask = config.getTask();
+ translationTask.run();
+
+ return ImmutableList.of(outFile);
+ }
+
+ } catch (ClassNotFoundException e) {
+ logger.warning("Cannot find the jill tool in the classpath, reverting to native");
+ } catch (com.android.jill.api.ConfigNotSupportedException e) {
+ logger.warning(e.getMessage() + ", reverting to native");
+ } catch (com.android.jill.api.v01.ConfigurationException e) {
+ logger.warning(e.getMessage() + ", reverting to native");
+ } catch (TranslationException e) {
+ logger.error(e, "In process translation failed, reverting to native, file a bug");
+ }
+ }
+ return convertLibraryToJack(inputFile, outFile, dexOptions, buildToolInfo, verbose,
+ processExecutor, processOutputHandler, logger);
+ }
+
+ public static List<File> convertLibraryToJack(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull DexOptions dexOptions,
+ @NonNull BuildToolInfo buildToolInfo,
+ boolean verbose,
+ @NonNull JavaProcessExecutor processExecutor,
+ @NonNull ProcessOutputHandler processOutputHandler,
+ @NonNull ILogger logger)
+ throws ProcessException {
+ checkNotNull(inputFile, "inputFile cannot be null.");
+ checkNotNull(outFile, "outFile cannot be null.");
+ checkNotNull(dexOptions, "dexOptions cannot be null.");
+
+ // launch dx: create the command line
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+
+ String jill = buildToolInfo.getPath(BuildToolInfo.PathId.JILL);
+ if (jill == null || !new File(jill).isFile()) {
+ throw new IllegalStateException("jill.jar is missing");
+ }
+
+ builder.setClasspath(jill);
+ builder.setMain("com.android.jill.Main");
+
+ if (dexOptions.getJavaMaxHeapSize() != null) {
+ builder.addJvmArg("-Xmx" + dexOptions.getJavaMaxHeapSize());
+ }
+ builder.addArgs(inputFile.getAbsolutePath());
+ builder.addArgs("--output");
+ builder.addArgs(outFile.getAbsolutePath());
+
+ if (verbose) {
+ builder.addArgs("--verbose");
+ }
+
+ logger.verbose(builder.toString());
+ JavaProcessInfo javaProcessInfo = builder.createJavaProcess();
+ ProcessResult result = processExecutor.execute(javaProcessInfo, processOutputHandler);
+ result.rethrowFailure().assertNormalExitValue();
+
+ return Collections.singletonList(outFile);
+ }
+
+ /**
+ * Packages the apk.
+ *
+ * @param androidResPkgLocation the location of the packaged resource file
+ * @param dexFolders the folder(s) with the dex file(s).
+ * @param javaResourcesLocations the processed Java resource folders and/or jars
+ * @param jniLibsLocations the folders containing jni shared libraries
+ * @param abiFilters optional ABI filter
+ * @param jniDebugBuild whether the app should include jni debug data
+ * @param signingConfig the signing configuration
+ * @param outApkLocation location of the APK.
+ * @throws DuplicateFileException
+ * @throws FileNotFoundException if the store location was not found
+ * @throws KeytoolException
+ * @throws PackagerException
+ * @throws SigningException when the key cannot be read from the keystore
+ *
+ */
+ public void packageApk(
+ @NonNull String androidResPkgLocation,
+ @NonNull Set<File> dexFolders,
+ @NonNull Collection<File> javaResourcesLocations,
+ @NonNull Collection<File> jniLibsLocations,
+ @NonNull Set<String> abiFilters,
+ boolean jniDebugBuild,
+ @Nullable SigningConfig signingConfig,
+ @NonNull String outApkLocation,
+ int minSdkVersion)
+ throws DuplicateFileException, FileNotFoundException,
+ KeytoolException, PackagerException, SigningException {
+ checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
+ checkNotNull(outApkLocation, "outApkLocation cannot be null.");
+
+ CertificateInfo certificateInfo = null;
+ if (signingConfig != null && signingConfig.isSigningReady()) {
+ //noinspection ConstantConditions - isSigningReady() called above.
+ certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
+ signingConfig.getStoreFile(), signingConfig.getStorePassword(),
+ signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
+ }
+
+ try {
+ Packager packager = new Packager(
+ outApkLocation, androidResPkgLocation,
+ certificateInfo, mCreatedBy, mLogger,
+ minSdkVersion);
+
+ // add dex folder to the apk root.
+ if (!dexFolders.isEmpty()) {
+ packager.addDexFiles(dexFolders);
+ }
+
+ packager.setJniDebugMode(jniDebugBuild);
+
+ // add the output of the java resource merger
+ for (File javaResourcesLocation : javaResourcesLocations) {
+ packager.addResources(javaResourcesLocation);
+ }
+
+ // and the output of the native lib merger.
+ for (File jniLibsLocation : jniLibsLocations) {
+ packager.addNativeLibraries(jniLibsLocation, abiFilters);
+ }
+
+ packager.sealApk();
+ } catch (SealedPackageException e) {
+ // shouldn't happen since we control the package from start to end.
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates a new split APK containing only code, this will only be functional on
+ * MarshMallow and above devices.
+ */
+ public void packageCodeSplitApk(
+ @NonNull String androidResPkgLocation,
+ @NonNull File dexFile,
+ @Nullable SigningConfig signingConfig,
+ @NonNull String outApkLocation) throws
+ FileNotFoundException, KeytoolException, PackagerException, DuplicateFileException {
+
+ CertificateInfo certificateInfo = null;
+ if (signingConfig != null && signingConfig.isSigningReady()) {
+ //noinspection ConstantConditions - isSigningReady() called above.
+ certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
+ signingConfig.getStoreFile(), signingConfig.getStorePassword(),
+ signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
+ }
+
+ try {
+ Packager packager = new Packager(
+ outApkLocation, androidResPkgLocation,
+ certificateInfo, mCreatedBy, mLogger,
+ 23 /* minSdkVersion, MarshMallow */);
+
+ packager.addFile(dexFile, "classes.dex");
+ packager.sealApk();
+ } catch (SealedPackageException e) {
+ // shouldn't happen since we control the package from start to end.
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ /**
+ * Signs a single jar file using the passed {@link SigningConfig}.
+ * @param in the jar file to sign.
+ * @param signingConfig the signing configuration
+ * @param out the file path for the signed jar.
+ * @throws IOException
+ * @throws KeytoolException
+ * @throws SigningException
+ * @throws NoSuchAlgorithmException
+ * @throws SignedJarBuilder.IZipEntryFilter.ZipAbortException
+ * @throws com.android.builder.signing.SigningException
+ */
+ public void signApk(File in, SigningConfig signingConfig, File out)
+ throws IOException, KeytoolException, SigningException, NoSuchAlgorithmException,
+ SignedJarBuilder.IZipEntryFilter.ZipAbortException,
+ com.android.builder.signing.SigningException {
+
+ CertificateInfo certificateInfo = null;
+ if (signingConfig != null && signingConfig.isSigningReady()) {
+ //noinspection ConstantConditions - isSigningReady() called above.
+ certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig.getStoreType(),
+ signingConfig.getStoreFile(), signingConfig.getStorePassword(),
+ signingConfig.getKeyPassword(), signingConfig.getKeyAlias());
+ }
+
+ SignedJarBuilder signedJarBuilder = new SignedJarBuilder(
+ new FileOutputStream(out),
+ certificateInfo != null ? certificateInfo.getKey() : null,
+ certificateInfo != null ? certificateInfo.getCertificate() : null,
+ Packager.getLocalVersion(), mCreatedBy, 1 /* minSdkVersion */);
+
+
+ signedJarBuilder.writeZip(new FileInputStream(in));
+ signedJarBuilder.close();
+
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/ApkInfoParser.java b/build-system/builder/src/main/java/com/android/builder/core/ApkInfoParser.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/core/ApkInfoParser.java
rename to build-system/builder/src/main/java/com/android/builder/core/ApkInfoParser.java
diff --git a/build-system/builder/src/main/java/com/android/builder/core/BuildToolsServiceLoader.java b/build-system/builder/src/main/java/com/android/builder/core/BuildToolsServiceLoader.java
new file mode 100644
index 0000000..08187e7
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/BuildToolsServiceLoader.java
@@ -0,0 +1,254 @@
+/*
+ * 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.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.jack.api.JackProvider;
+import com.android.jill.api.JillProvider;
+import com.android.sdklib.BuildToolInfo;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+
+/**
+ * {@link ServiceLoader} helpers for tools located in the SDK's build-tools folders.
+ *
+ * This utility will cache {@link ServiceLoader} instances per build-tools version and per target
+ * service type.
+ */
+public enum BuildToolsServiceLoader {
+
+ /**
+ * Singleton instance to request {@link ServiceLoader} instances from.
+ */
+ INSTANCE;
+
+ /**
+ * private cache data for a particular build-tool version.
+ */
+ private static final class LoadedBuildTool {
+ private final Revision version;
+ private final BuildToolServiceLoader serviceLoader;
+
+ private LoadedBuildTool(Revision version,
+ BuildToolServiceLoader serviceLoader) {
+ this.version = version;
+ this.serviceLoader = serviceLoader;
+ }
+ }
+
+ private final List<LoadedBuildTool> loadedBuildTools = new ArrayList<LoadedBuildTool>();
+
+ /**
+ * Load a built-tools version specific {@link ServiceLoader} helper.
+ * @param buildToolInfo the requested build-tools information
+ * @return an initialized {@link BuildToolsServiceLoader.BuildToolServiceLoader} to get
+ * instances of {@link ServiceLoader} from.
+ */
+ @NonNull
+ public synchronized BuildToolServiceLoader forVersion(BuildToolInfo buildToolInfo) {
+
+ Optional<LoadedBuildTool> loadedBuildToolOptional =
+ findVersion(buildToolInfo.getRevision());
+
+ if (loadedBuildToolOptional.isPresent()) {
+ return loadedBuildToolOptional.get().serviceLoader;
+ }
+
+ LoadedBuildTool loadedBuildTool = new LoadedBuildTool(buildToolInfo.getRevision(),
+ new BuildToolServiceLoader(buildToolInfo));
+ loadedBuildTools.add(loadedBuildTool);
+ return loadedBuildTool.serviceLoader;
+ }
+
+ @NonNull
+ private Optional<LoadedBuildTool> findVersion(Revision version) {
+ for (LoadedBuildTool loadedBuildTool : loadedBuildTools) {
+ if (loadedBuildTool.version.equals(version)) {
+ return Optional.of(loadedBuildTool);
+ }
+ }
+ return Optional.absent();
+ }
+
+ /**
+ * Abstract notion of what a service is. A service must be declared in one of the classpath
+ * provided jar files. The service declaration must conforms to {@link ServiceLoader} contract.
+ *
+ * @param <T> the type of service.
+ */
+ public static class Service<T> {
+
+ private final Collection<String> classpath;
+ private final Class<T> serviceClass;
+
+ protected Service(Collection<String> classpath, Class<T> serviceClass) {
+ this.classpath = classpath;
+ this.serviceClass = serviceClass;
+ }
+
+ public Collection<String> getClasspath() {
+ return classpath;
+ }
+ public Class<T> getServiceClass() {
+ return serviceClass;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("serviceClass", serviceClass)
+ .add("classpath", Joiner.on(",").join(classpath))
+ .toString();
+ }
+ }
+
+ /**
+ * Jack service description.
+ */
+ public static final Service<JackProvider> JACK =
+ new Service<JackProvider>(ImmutableList.of("jack.jar"), JackProvider.class);
+
+ /**
+ * Jill service description.
+ */
+ public static final Service<JillProvider> JILL =
+ new Service<JillProvider>(ImmutableList.of("jill.jar"), JillProvider.class);
+
+ /**
+ * build-tools version specific {@link ServiceLoader} helper.
+ */
+ public static final class BuildToolServiceLoader {
+
+ /**
+ * private cache data for a single {@link ServiceLoader} instance.
+ * @param <T> the service loader type.
+ */
+ private static final class LoadedServiceLoader<T> {
+ private final Class<T> serviceType;
+ private final ServiceLoader<T> serviceLoader;
+
+ private LoadedServiceLoader(Class<T> serviceType, ServiceLoader<T> serviceLoader) {
+ this.serviceType = serviceType;
+ this.serviceLoader = serviceLoader;
+ }
+ }
+
+ private final BuildToolInfo buildToolInfo;
+ private final List<LoadedServiceLoader> loadedServicesLoaders =
+ new ArrayList<LoadedServiceLoader>();
+
+ private BuildToolServiceLoader(BuildToolInfo buildToolInfo) {
+ this.buildToolInfo = buildToolInfo;
+ }
+
+ /**
+ * Returns a newly allocated or existing {@link ServiceLoader} instance for the passed
+ * {@link com.android.builder.core.BuildToolsServiceLoader.Service} type in the context
+ * of the build-tools version this instance was created for.
+ *
+ * @param serviceType the requested service type encapsulation.
+ * @param <T> the type of service
+ * @return a {@link ServiceLoader} instance for the T service type.
+ * @throws ClassNotFoundException
+ */
+ @NonNull
+ public synchronized <T> ServiceLoader<T> getServiceLoader(Service<T> serviceType)
+ throws ClassNotFoundException {
+
+ Optional<ServiceLoader<T>> serviceLoaderOptional =
+ getLoadedServiceLoader(serviceType.getServiceClass());
+ if (serviceLoaderOptional.isPresent()) {
+ return serviceLoaderOptional.get();
+ }
+
+ File buildToolLocation = buildToolInfo.getLocation();
+ if (System.getenv("USE_JACK_LOCATION") != null) {
+ buildToolLocation = new File(System.getenv("USE_JACK_LOCATION"));
+ }
+ URL[] urls = new URL[serviceType.classpath.size()];
+ int i = 0;
+ for (String classpathItem : serviceType.getClasspath()) {
+ File jarFile = new File(buildToolLocation, classpathItem);
+ try {
+ urls[i++] = jarFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ ClassLoader cl = new URLClassLoader(urls, serviceType.getServiceClass().getClassLoader());
+ ServiceLoader<T> serviceLoader = ServiceLoader.load(serviceType.getServiceClass(), cl);
+ loadedServicesLoaders.add(new LoadedServiceLoader<T>(
+ serviceType.getServiceClass(), serviceLoader));
+ return serviceLoader;
+ }
+
+ /**
+ * Return the first service instance for the requested service type or
+ * {@link Optional#absent()} if none exist.
+ * @param logger to log resolution.
+ * @param serviceType the requested service type encapsulation.
+ * @param <T> the requested service class type.
+ * @return the instance of T or null of none exist in this context.
+ * @throws ClassNotFoundException
+ */
+ @NonNull
+ public synchronized <T> Optional<T> getSingleService(
+ ILogger logger,
+ Service<T> serviceType) throws ClassNotFoundException {
+ logger.verbose("Looking for %1$s", serviceType);
+ ServiceLoader<T> serviceLoader = getServiceLoader(serviceType);
+ logger.verbose("Got a serviceLoader %1$d",
+ Integer.toHexString(System.identityHashCode(serviceLoader)));
+ Iterator<T> serviceIterator = serviceLoader.iterator();
+ logger.verbose("Service Iterator = %1$s ", serviceIterator);
+ if (serviceIterator.hasNext()) {
+ T service = serviceIterator.next();
+ logger.verbose("Got it from %1$s, loaded service = %2$s, type = %3$s",
+ serviceIterator, service, service.getClass());
+ return Optional.of(service);
+ } else {
+ logger.info("Cannot find service implementation %1$s" + serviceType);
+ return Optional.absent();
+ }
+ }
+
+ @NonNull
+ @SuppressWarnings("unchecked")
+ private <T> Optional<ServiceLoader<T>> getLoadedServiceLoader(Class<T> serviceType) {
+ for (LoadedServiceLoader<?> loadedServiceLoader : loadedServicesLoaders) {
+ if (loadedServiceLoader.serviceType.equals(serviceType)) {
+ return Optional.of((ServiceLoader<T>) loadedServiceLoader.serviceLoader);
+ }
+ }
+ return Optional.absent();
+ }
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/BuilderConstants.java b/build-system/builder/src/main/java/com/android/builder/core/BuilderConstants.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/core/BuilderConstants.java
rename to build-system/builder/src/main/java/com/android/builder/core/BuilderConstants.java
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java
new file mode 100644
index 0000000..85a4af4
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultApiVersion.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.ApiVersion;
+
+/**
+ * Basic implementation of ApiVersion
+ */
+public class DefaultApiVersion implements ApiVersion {
+
+ private final int mApiLevel;
+
+ @Nullable
+ private final String mCodename;
+
+ public DefaultApiVersion(int apiLevel, @Nullable String codename) {
+ mApiLevel = apiLevel;
+ mCodename = codename;
+ }
+
+ public DefaultApiVersion(int apiLevel) {
+ this(apiLevel, null);
+ }
+
+ public DefaultApiVersion(@NonNull String codename) {
+ this(1, codename);
+ }
+
+ @NonNull
+ public static ApiVersion create(@NonNull Object value) {
+ if (value instanceof Integer) {
+ return new DefaultApiVersion((Integer) value, null);
+ } else if (value instanceof String) {
+ return new DefaultApiVersion(1, (String) value);
+ }
+
+ return new DefaultApiVersion(1, null);
+ }
+
+ @Override
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getCodename() {
+ return mCodename;
+ }
+
+ @NonNull
+ @Override
+ public String getApiString() {
+ return mCodename != null ? mCodename : Integer.toString(mApiLevel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DefaultApiVersion that = (DefaultApiVersion) o;
+
+ if (mApiLevel != that.mApiLevel) {
+ return false;
+ }
+ if (mCodename != null ? !mCodename.equals(that.mCodename) : that.mCodename != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mApiLevel;
+ result = 31 * result + (mCodename != null ? mCodename.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultApiVersion{" +
+ "mApiLevel=" + mApiLevel +
+ ", mCodename='" + mCodename + '\'' +
+ '}';
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java
new file mode 100644
index 0000000..d12f5b5
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultBuildType.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.BaseConfigImpl;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.SigningConfig;
+import com.google.common.base.Objects;
+
+public class DefaultBuildType extends BaseConfigImpl implements BuildType {
+ private static final long serialVersionUID = 1L;
+
+ private final String mName;
+ private boolean mDebuggable = false;
+ private boolean mPseudoLocalesEnabled = false;
+ private boolean mTestCoverageEnabled = false;
+ private boolean mJniDebuggable = false;
+ private boolean mRenderscriptDebuggable = false;
+ private int mRenderscriptOptimLevel = 3;
+ private String mVersionNameSuffix = null;
+ private boolean mMinifyEnabled = false;
+ private SigningConfig mSigningConfig = null;
+ private boolean mEmbedMicroApp = true;
+
+ private boolean mZipAlignEnabled = true;
+
+ public DefaultBuildType(@NonNull String name) {
+ mName = name;
+ }
+
+ public DefaultBuildType initWith(DefaultBuildType that) {
+ _initWith(that);
+
+ setDebuggable(that.isDebuggable());
+ setTestCoverageEnabled(that.isTestCoverageEnabled());
+ setJniDebuggable(that.isJniDebuggable());
+ setRenderscriptDebuggable(that.isRenderscriptDebuggable());
+ setRenderscriptOptimLevel(that.getRenderscriptOptimLevel());
+ setVersionNameSuffix(that.getVersionNameSuffix());
+ setMinifyEnabled(that.isMinifyEnabled() );
+ setZipAlignEnabled(that.isZipAlignEnabled());
+ setSigningConfig(that.getSigningConfig());
+ setEmbedMicroApp(that.isEmbedMicroApp());
+ setPseudoLocalesEnabled(that.isPseudoLocalesEnabled());
+
+ return this;
+ }
+
+ /**
+ * Name of this build type.
+ */
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Whether this build type should generate a debuggable apk. */
+ @NonNull
+ public BuildType setDebuggable(boolean debuggable) {
+ mDebuggable = debuggable;
+ return this;
+ }
+
+ /** Whether this build type should generate a debuggable apk. */
+ @Override
+ public boolean isDebuggable() {
+ // Accessing coverage data requires a debuggable package.
+ return mDebuggable || mTestCoverageEnabled;
+ }
+
+
+ public void setTestCoverageEnabled(boolean testCoverageEnabled) {
+ mTestCoverageEnabled = testCoverageEnabled;
+ }
+
+ /**
+ * Whether test coverage is enabled for this build type.
+ *
+ * <p>If enabled this uses Jacoco to capture coverage and creates a report in the build
+ * directory.
+ *
+ * <p>The version of Jacoco can be configured with:
+ * <pre>
+ * android {
+ * jacoco {
+ * version = '0.6.2.201302030002'
+ * }
+ * }
+ * </pre>
+ *
+ */
+ @Override
+ public boolean isTestCoverageEnabled() {
+ return mTestCoverageEnabled;
+ }
+
+ public void setPseudoLocalesEnabled(boolean pseudoLocalesEnabled) {
+ mPseudoLocalesEnabled = pseudoLocalesEnabled;
+ }
+
+ /**
+ * Whether to generate pseudo locale in the APK.
+ *
+ * <p>If enabled, 2 fake pseudo locales (en-XA and ar-XB) will be added to the APK to help
+ * test internationalization support in the app.
+ */
+ @Override
+ public boolean isPseudoLocalesEnabled() {
+ return mPseudoLocalesEnabled;
+ }
+
+ /**
+ * Whether this build type is configured to generate an APK with debuggable native code.
+ */
+ @NonNull
+ public BuildType setJniDebuggable(boolean jniDebugBuild) {
+ mJniDebuggable = jniDebugBuild;
+ return this;
+ }
+
+ /**
+ * Whether this build type is configured to generate an APK with debuggable native code.
+ */
+ @Override
+ public boolean isJniDebuggable() {
+ return mJniDebuggable;
+ }
+
+ /**
+ * Whether the build type is configured to generate an apk with debuggable RenderScript code.
+ */
+ @Override
+ public boolean isRenderscriptDebuggable() {
+ return mRenderscriptDebuggable;
+ }
+
+ /**
+ * Whether the build type is configured to generate an apk with debuggable RenderScript code.
+ */
+ public BuildType setRenderscriptDebuggable(boolean renderscriptDebugBuild) {
+ mRenderscriptDebuggable = renderscriptDebugBuild;
+ return this;
+ }
+
+ /**
+ * Optimization level to use by the renderscript compiler.
+ */
+ @Override
+ public int getRenderscriptOptimLevel() {
+ return mRenderscriptOptimLevel;
+ }
+
+ /** Optimization level to use by the renderscript compiler. */
+ public void setRenderscriptOptimLevel(int renderscriptOptimLevel) {
+ mRenderscriptOptimLevel = renderscriptOptimLevel;
+ }
+
+ /** Version name suffix. */
+ @NonNull
+ public BuildType setVersionNameSuffix(@Nullable String versionNameSuffix) {
+ mVersionNameSuffix = versionNameSuffix;
+ return this;
+ }
+
+ /** Version name suffix. */
+ @Override
+ @Nullable
+ public String getVersionNameSuffix() {
+ return mVersionNameSuffix;
+ }
+
+ /** Whether Minify is enabled for this build type. */
+ @NonNull
+ public BuildType setMinifyEnabled(boolean enabled) {
+ mMinifyEnabled = enabled;
+ return this;
+ }
+
+ /** Whether Minify is enabled for this build type. */
+ @Override
+ public boolean isMinifyEnabled() {
+ return mMinifyEnabled;
+ }
+
+
+ /** Whether zipalign is enabled for this build type. */
+ @NonNull
+ public BuildType setZipAlignEnabled(boolean zipAlign) {
+ mZipAlignEnabled = zipAlign;
+ return this;
+ }
+
+ /** Whether zipalign is enabled for this build type. */
+ @Override
+ public boolean isZipAlignEnabled() {
+ return mZipAlignEnabled;
+ }
+
+ /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
+ @NonNull
+ public BuildType setSigningConfig(@Nullable SigningConfig signingConfig) {
+ mSigningConfig = signingConfig;
+ return this;
+ }
+
+ /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
+ @Override
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ return mSigningConfig;
+ }
+
+ /**
+ * Whether a linked Android Wear app should be embedded in variant using this build type.
+ *
+ * <p>Wear apps can be linked with the following code:
+ *
+ * <pre>
+ * dependencies {
+ * freeWearApp project(:wear:free') // applies to variant using the free flavor
+ * wearApp project(':wear:base') // applies to all other variants
+ * }
+ * </pre>
+ */
+ @Override
+ public boolean isEmbedMicroApp() {
+ return mEmbedMicroApp;
+ }
+
+ public void setEmbedMicroApp(boolean embedMicroApp) {
+ mEmbedMicroApp = embedMicroApp;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ DefaultBuildType buildType = (DefaultBuildType) o;
+
+ return Objects.equal(mName, buildType.mName) &&
+ mDebuggable == buildType.mDebuggable &&
+ mTestCoverageEnabled == buildType.mTestCoverageEnabled &&
+ mJniDebuggable == buildType.mJniDebuggable &&
+ mPseudoLocalesEnabled == buildType.mPseudoLocalesEnabled &&
+ mRenderscriptDebuggable == buildType.mRenderscriptDebuggable &&
+ mRenderscriptOptimLevel == buildType.mRenderscriptOptimLevel &&
+ mMinifyEnabled == buildType.mMinifyEnabled &&
+ mZipAlignEnabled == buildType.mZipAlignEnabled &&
+ mEmbedMicroApp == buildType.mEmbedMicroApp &&
+ Objects.equal(mVersionNameSuffix, buildType.mVersionNameSuffix) &&
+ Objects.equal(mSigningConfig, buildType.mSigningConfig);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(
+ super.hashCode(),
+ mName,
+ mDebuggable,
+ mTestCoverageEnabled,
+ mJniDebuggable,
+ mPseudoLocalesEnabled,
+ mRenderscriptDebuggable,
+ mRenderscriptOptimLevel,
+ mVersionNameSuffix,
+ mMinifyEnabled,
+ mZipAlignEnabled,
+ mSigningConfig,
+ mEmbedMicroApp);
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", mName)
+ .add("debuggable", mDebuggable)
+ .add("testCoverageEnabled", mTestCoverageEnabled)
+ .add("jniDebuggable", mJniDebuggable)
+ .add("pseudoLocalesEnabled", mPseudoLocalesEnabled)
+ .add("renderscriptDebuggable", mRenderscriptDebuggable)
+ .add("renderscriptOptimLevel", mRenderscriptOptimLevel)
+ .add("versionNameSuffix", mVersionNameSuffix)
+ .add("minifyEnabled", mMinifyEnabled)
+ .add("zipAlignEnabled", mZipAlignEnabled)
+ .add("signingConfig", mSigningConfig)
+ .add("embedMicroApp", mEmbedMicroApp)
+ .add("mBuildConfigFields", getBuildConfigFields())
+ .add("mResValues", getResValues())
+ .add("mProguardFiles", getProguardFiles())
+ .add("mConsumerProguardFiles", getConsumerProguardFiles())
+ .add("mManifestPlaceholders", getManifestPlaceholders())
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java
new file mode 100644
index 0000000..519d49f
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultManifestParser.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.io.FileWrapper;
+import com.android.io.StreamException;
+import com.android.utils.XmlUtils;
+import com.android.xml.AndroidManifest;
+import com.android.xml.AndroidXPathFactory;
+import com.google.common.base.Optional;
+
+import org.apache.http.annotation.ThreadSafe;
+import org.xml.sax.InputSource;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+
+ at ThreadSafe
+class DefaultManifestParser implements ManifestParser {
+
+ Optional<Object> mMinSdkVersion;
+ Optional<Object> mTargetSdkVersion;
+ Optional<Integer> mVersionCode;
+ Optional<String> mPackage;
+ Optional<String> mVersionName;
+
+ @Nullable
+ @Override
+ public synchronized String getPackage(@NonNull File manifestFile) {
+ if (mPackage == null) {
+ mPackage = Optional.fromNullable(getStringValue(manifestFile, "/manifest/@package"));
+ }
+ return mPackage.orNull();
+ }
+
+ @Nullable
+ @Override
+ public synchronized String getVersionName(@NonNull File manifestFile) {
+ if (mVersionName == null) {
+ mVersionName = Optional.fromNullable(
+ getStringValue(manifestFile, "/manifest/@android:versionName"));
+ }
+ return mVersionName.orNull();
+ }
+
+ @Override
+ public synchronized int getVersionCode(@NonNull File manifestFile) {
+ if (mVersionCode == null) {
+ mVersionCode = Optional.absent();
+ try {
+ String value = getStringValue(manifestFile, "/manifest/@android:versionCode");
+ if (value != null) {
+ mVersionCode = Optional.of(Integer.valueOf(value));
+ }
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ return mVersionCode.or(-1);
+ }
+
+ @Override
+ @NonNull
+ public synchronized Object getMinSdkVersion(@NonNull File manifestFile) {
+ if (mMinSdkVersion == null) {
+ try {
+ mMinSdkVersion = Optional.fromNullable(
+ AndroidManifest.getMinSdkVersion(new FileWrapper(manifestFile)));
+ } catch (XPathExpressionException e) {
+ throw new RuntimeException(e);
+ } catch (StreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return mMinSdkVersion.or(1);
+ }
+
+ @Override
+ @NonNull
+ public Object getTargetSdkVersion(@NonNull File manifestFile) {
+ if (mTargetSdkVersion == null) {
+ try {
+ mTargetSdkVersion =
+ Optional.fromNullable(AndroidManifest.getTargetSdkVersion(
+ new FileWrapper(manifestFile)));
+ } catch (XPathExpressionException e) {
+ return new RuntimeException(e);
+ } catch (StreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return mTargetSdkVersion.or(-1);
+ }
+
+ private static String getStringValue(@NonNull File file, @NonNull String xPath) {
+ XPath xpath = AndroidXPathFactory.newXPath();
+
+ try {
+ InputSource source = new InputSource(XmlUtils.getUtfReader(file));
+ return xpath.evaluate(xPath, source);
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java
new file mode 100644
index 0000000..d798809
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultProductFlavor.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.BaseConfigImpl;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The configuration of a product flavor.
+ *
+ * This is also used to describe the default configuration of all builds, even those that
+ * do not contain any flavors.
+ */
+public class DefaultProductFlavor extends BaseConfigImpl implements ProductFlavor {
+ private static final long serialVersionUID = 1L;
+
+ private final String mName;
+ @Nullable
+ private String mDimension;
+ @Nullable
+ private ApiVersion mMinSdkVersion;
+ @Nullable
+ private ApiVersion mTargetSdkVersion;
+ @Nullable
+ private Integer mMaxSdkVersion;
+ @Nullable
+ private Integer mRenderscriptTargetApi;
+ @Nullable
+ private Boolean mRenderscriptSupportModeEnabled;
+ @Nullable
+ private Boolean mRenderscriptNdkModeEnabled;
+ @Nullable
+ private Integer mVersionCode;
+ @Nullable
+ private String mVersionName;
+ @Nullable
+ private String mApplicationId;
+ @Nullable
+ private String mTestApplicationId;
+ @Nullable
+ private String mTestInstrumentationRunner;
+ @NonNull
+ private Map<String, String> mTestInstrumentationRunnerArguments = Maps.newHashMap();
+ @Nullable
+ private Boolean mTestHandleProfiling;
+ @Nullable
+ private Boolean mTestFunctionalTest;
+ @Nullable
+ private SigningConfig mSigningConfig;
+ @Nullable
+ private Set<String> mResourceConfiguration;
+ @NonNull
+ private DefaultVectorDrawablesOptions mVectorDrawablesOptions = new DefaultVectorDrawablesOptions();
+
+ /**
+ * Creates a ProductFlavor with a given name.
+ *
+ * Names can be important when dealing with flavor groups.
+ * @param name the name of the flavor.
+ *
+ * @see BuilderConstants#MAIN
+ */
+ public DefaultProductFlavor(@NonNull String name) {
+ mName = name;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ public void setDimension(@NonNull String dimension) {
+ mDimension = dimension;
+ }
+
+ /** Name of the dimension this product flavor belongs to. */
+ @Nullable
+ @Override
+ public String getDimension() {
+ return mDimension;
+ }
+
+ /**
+ * Sets the application id.
+ */
+ @NonNull
+ public ProductFlavor setApplicationId(String applicationId) {
+ mApplicationId = applicationId;
+ return this;
+ }
+
+ /**
+ * Returns the application ID.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename">ApplicationId versus PackageName</a>
+ */
+ @Override
+ @Nullable
+ public String getApplicationId() {
+ return mApplicationId;
+ }
+
+ /**
+ * Sets the version code.
+ *
+ * @param versionCode the version code
+ * @return the flavor object
+ */
+ @NonNull
+ public ProductFlavor setVersionCode(Integer versionCode) {
+ mVersionCode = versionCode;
+ return this;
+ }
+
+ /**
+ * Version code.
+ *
+ * <p>See <a href="http://developer.android.com/tools/publishing/versioning.html">Versioning Your Application</a>
+ */
+ @Override
+ @Nullable
+ public Integer getVersionCode() {
+ return mVersionCode;
+ }
+
+ /**
+ * Sets the version name.
+ *
+ * @param versionName the version name
+ * @return the flavor object
+ */
+ @NonNull
+ public ProductFlavor setVersionName(String versionName) {
+ mVersionName = versionName;
+ return this;
+ }
+
+ /**
+ * Version name.
+ *
+ * <p>See <a href="http://developer.android.com/tools/publishing/versioning.html">Versioning Your Application</a>
+ */
+ @Override
+ @Nullable
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ /**
+ * Sets the minSdkVersion to the given value.
+ */
+ @NonNull
+ public ProductFlavor setMinSdkVersion(ApiVersion minSdkVersion) {
+ mMinSdkVersion = minSdkVersion;
+ return this;
+ }
+
+ /**
+ * Min SDK version.
+ */
+ @Nullable
+ @Override
+ public ApiVersion getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ /** Sets the targetSdkVersion to the given value. */
+ @NonNull
+ public ProductFlavor setTargetSdkVersion(@Nullable ApiVersion targetSdkVersion) {
+ mTargetSdkVersion = targetSdkVersion;
+ return this;
+ }
+
+ /**
+ * Target SDK version.
+ */
+ @Nullable
+ @Override
+ public ApiVersion getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @NonNull
+ public ProductFlavor setMaxSdkVersion(Integer maxSdkVersion) {
+ mMaxSdkVersion = maxSdkVersion;
+ return this;
+ }
+
+ @Nullable
+ @Override
+ public Integer getMaxSdkVersion() {
+ return mMaxSdkVersion;
+ }
+
+ @Override
+ @Nullable
+ public Integer getRenderscriptTargetApi() {
+ return mRenderscriptTargetApi;
+ }
+
+ /** Sets the renderscript target API to the given value. */
+ public void setRenderscriptTargetApi(@Nullable Integer renderscriptTargetApi) {
+ mRenderscriptTargetApi = renderscriptTargetApi;
+ }
+
+ @Override
+ @Nullable
+ public Boolean getRenderscriptSupportModeEnabled() {
+ return mRenderscriptSupportModeEnabled;
+ }
+
+ /**
+ * Sets whether the renderscript code should be compiled in support mode to make it compatible
+ * with older versions of Android.
+ */
+ public ProductFlavor setRenderscriptSupportModeEnabled(Boolean renderscriptSupportMode) {
+ mRenderscriptSupportModeEnabled = renderscriptSupportMode;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public Boolean getRenderscriptNdkModeEnabled() {
+ return mRenderscriptNdkModeEnabled;
+ }
+
+
+ /** Sets whether the renderscript code should be compiled to generate C/C++ bindings. */
+ public ProductFlavor setRenderscriptNdkModeEnabled(Boolean renderscriptNdkMode) {
+ mRenderscriptNdkModeEnabled = renderscriptNdkMode;
+ return this;
+ }
+
+ /** Sets the test application ID. */
+ @NonNull
+ public ProductFlavor setTestApplicationId(String applicationId) {
+ mTestApplicationId = applicationId;
+ return this;
+ }
+
+ /**
+ * Test application ID.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename">ApplicationId versus PackageName</a>
+ */
+ @Override
+ @Nullable
+ public String getTestApplicationId() {
+ return mTestApplicationId;
+ }
+
+ /** Sets the test instrumentation runner to the given value. */
+ @NonNull
+ public ProductFlavor setTestInstrumentationRunner(String testInstrumentationRunner) {
+ mTestInstrumentationRunner = testInstrumentationRunner;
+ return this;
+ }
+
+ /**
+ * Test instrumentation runner class name.
+ *
+ * <p>This is a fully qualified class name of the runner, e.g.
+ * <code>android.test.InstrumentationTestRunner</code>
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
+ * instrumentation</a>.
+ */
+ @Override
+ @Nullable
+ public String getTestInstrumentationRunner() {
+ return mTestInstrumentationRunner;
+ }
+
+ /** Sets the test instrumentation runner custom arguments. */
+ @NonNull
+ public ProductFlavor setTestInstrumentationRunnerArguments(
+ @NonNull Map<String, String> testInstrumentationRunnerArguments) {
+ mTestInstrumentationRunnerArguments = checkNotNull(testInstrumentationRunnerArguments);
+ return this;
+ }
+
+ /**
+ * Test instrumentation runner custom arguments.
+ *
+ * e.g. <code>[key: "value"]</code> will give
+ * <code>adb shell am instrument -w <b>-e key value</b> com.example</code>...".
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
+ * instrumentation</a>.
+ *
+ * <p>Test runner arguments can also be specified from the command line:
+ *
+ * <p><pre>
+ * ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.size=medium
+ * ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.foo=bar
+ * </pre>
+ */
+ @Override
+ @NonNull
+ public Map<String, String> getTestInstrumentationRunnerArguments() {
+ return mTestInstrumentationRunnerArguments;
+ }
+
+ /**
+ * See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
+ * instrumentation</a>.
+ */
+ @Override
+ @Nullable
+ public Boolean getTestHandleProfiling() {
+ return mTestHandleProfiling;
+ }
+
+ @NonNull
+ public ProductFlavor setTestHandleProfiling(boolean handleProfiling) {
+ mTestHandleProfiling = handleProfiling;
+ return this;
+ }
+
+ /**
+ * See <a href="http://developer.android.com/guide/topics/manifest/instrumentation-element.html">
+ * instrumentation</a>.
+ */
+ @Override
+ @Nullable
+ public Boolean getTestFunctionalTest() {
+ return mTestFunctionalTest;
+ }
+
+ @NonNull
+ public ProductFlavor setTestFunctionalTest(boolean functionalTest) {
+ mTestFunctionalTest = functionalTest;
+ return this;
+ }
+
+ /**
+ * Signing config used by this product flavor.
+ */
+ @Override
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ return mSigningConfig;
+ }
+
+ /** Sets the signing configuration. e.g.: {@code signingConfig signingConfigs.myConfig} */
+ @NonNull
+ public ProductFlavor setSigningConfig(SigningConfig signingConfig) {
+ mSigningConfig = signingConfig;
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public DefaultVectorDrawablesOptions getVectorDrawables() {
+ return mVectorDrawablesOptions;
+ }
+
+ /**
+ * Adds a res config filter (for instance 'hdpi')
+ */
+ public void addResourceConfiguration(@NonNull String configuration) {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ mResourceConfiguration.add(configuration);
+ }
+
+ /**
+ * Adds a res config filter (for instance 'hdpi')
+ */
+ public void addResourceConfigurations(@NonNull String... configurations) {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ mResourceConfiguration.addAll(Arrays.asList(configurations));
+ }
+
+ /**
+ * Adds a res config filter (for instance 'hdpi')
+ */
+ public void addResourceConfigurations(@NonNull Collection<String> configurations) {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ mResourceConfiguration.addAll(configurations);
+ }
+
+ /**
+ * Adds a res config filter (for instance 'hdpi')
+ */
+ @NonNull
+ @Override
+ public Collection<String> getResourceConfigurations() {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ return mResourceConfiguration;
+ }
+
+ /**
+ * Merges two flavors on top of one another and returns a new object with the result.
+ *
+ * The behavior is that if a value is present in the overlay, then it is used, otherwise
+ * we use the value from the base.
+ *
+ * @param base the flavor to merge on top of
+ * @param overlay the flavor to apply on top of the base.
+ *
+ * @return a new ProductFlavor that represents the merge.
+ */
+ @NonNull
+ static ProductFlavor mergeFlavors(@NonNull ProductFlavor base, @NonNull ProductFlavor overlay) {
+ DefaultProductFlavor flavor = new DefaultProductFlavor("");
+
+ flavor.mMinSdkVersion = chooseNotNull(
+ overlay.getMinSdkVersion(),
+ base.getMinSdkVersion());
+ flavor.mTargetSdkVersion = chooseNotNull(
+ overlay.getTargetSdkVersion(),
+ base.getTargetSdkVersion());
+ flavor.mMaxSdkVersion = chooseNotNull(
+ overlay.getMaxSdkVersion(),
+ base.getMaxSdkVersion());
+
+ flavor.mRenderscriptTargetApi = chooseNotNull(
+ overlay.getRenderscriptTargetApi(),
+ base.getRenderscriptTargetApi());
+ flavor.mRenderscriptSupportModeEnabled = chooseNotNull(
+ overlay.getRenderscriptSupportModeEnabled(),
+ base.getRenderscriptSupportModeEnabled());
+ flavor.mRenderscriptNdkModeEnabled = chooseNotNull(
+ overlay.getRenderscriptNdkModeEnabled(),
+ base.getRenderscriptNdkModeEnabled());
+
+ flavor.mVersionCode = chooseNotNull(overlay.getVersionCode(), base.getVersionCode());
+ flavor.mVersionName = chooseNotNull(overlay.getVersionName(), base.getVersionName());
+
+ flavor.mApplicationId = chooseNotNull(overlay.getApplicationId(), base.getApplicationId());
+
+ if (!Strings.isNullOrEmpty(overlay.getApplicationIdSuffix())) {
+ String baseSuffix = chooseNotNull(base.getApplicationIdSuffix(), "");
+ if (overlay.getApplicationIdSuffix().charAt(0) == '.') {
+ flavor.setApplicationIdSuffix(baseSuffix + overlay.getApplicationIdSuffix());
+ } else {
+ flavor.setApplicationIdSuffix(baseSuffix + '.' + overlay.getApplicationIdSuffix());
+ }
+ }
+
+ flavor.mTestApplicationId = chooseNotNull(
+ overlay.getTestApplicationId(),
+ base.getTestApplicationId());
+ flavor.mTestInstrumentationRunner = chooseNotNull(
+ overlay.getTestInstrumentationRunner(),
+ base.getTestInstrumentationRunner());
+
+ flavor.mTestInstrumentationRunnerArguments.putAll(
+ base.getTestInstrumentationRunnerArguments());
+ flavor.mTestInstrumentationRunnerArguments.putAll(
+ overlay.getTestInstrumentationRunnerArguments());
+
+ flavor.mTestHandleProfiling = chooseNotNull(
+ overlay.getTestHandleProfiling(),
+ base.getTestHandleProfiling());
+
+ flavor.mTestFunctionalTest = chooseNotNull(
+ overlay.getTestFunctionalTest(),
+ base.getTestFunctionalTest());
+
+ flavor.mSigningConfig = chooseNotNull(
+ overlay.getSigningConfig(),
+ base.getSigningConfig());
+
+ flavor.addResourceConfigurations(base.getResourceConfigurations());
+ flavor.addResourceConfigurations(overlay.getResourceConfigurations());
+
+ flavor.addManifestPlaceholders(base.getManifestPlaceholders());
+ flavor.addManifestPlaceholders(overlay.getManifestPlaceholders());
+
+ flavor.addResValues(base.getResValues());
+ flavor.addResValues(overlay.getResValues());
+
+ flavor.addBuildConfigFields(base.getBuildConfigFields());
+ flavor.addBuildConfigFields(overlay.getBuildConfigFields());
+
+ flavor.setMultiDexEnabled(chooseNotNull(
+ overlay.getMultiDexEnabled(), base.getMultiDexEnabled()));
+
+ flavor.setMultiDexKeepFile(chooseNotNull(
+ overlay.getMultiDexKeepFile(), base.getMultiDexKeepFile()));
+
+ flavor.setMultiDexKeepProguard(chooseNotNull(
+ overlay.getMultiDexKeepProguard(), base.getMultiDexKeepProguard()));
+
+ flavor.setJarJarRuleFiles(ImmutableList.<File>builder()
+ .addAll(overlay.getJarJarRuleFiles())
+ .addAll(base.getJarJarRuleFiles())
+ .build());
+
+ flavor.getVectorDrawables().setGeneratedDensities(
+ chooseNotNull(
+ overlay.getVectorDrawables().getGeneratedDensities(),
+ base.getVectorDrawables().getGeneratedDensities()));
+
+ flavor.getVectorDrawables().setUseSupportLibrary(
+ chooseNotNull(
+ overlay.getVectorDrawables().getUseSupportLibrary(),
+ base.getVectorDrawables().getUseSupportLibrary()));
+
+ return flavor;
+ }
+
+ /**
+ * Clone a given product flavor.
+ *
+ * @param productFlavor the flavor to clone.
+ *
+ * @return a new instance that is a clone of the flavor.
+ */
+ @NonNull
+ static ProductFlavor clone(@NonNull ProductFlavor productFlavor) {
+ DefaultProductFlavor flavor = new DefaultProductFlavor(productFlavor.getName());
+ flavor._initWith(productFlavor);
+ flavor.mDimension = productFlavor.getDimension();
+ flavor.mMinSdkVersion = productFlavor.getMinSdkVersion();
+ flavor.mTargetSdkVersion = productFlavor.getTargetSdkVersion();
+ flavor.mMaxSdkVersion = productFlavor.getMaxSdkVersion();
+ flavor.mRenderscriptTargetApi = productFlavor.getRenderscriptTargetApi();
+ flavor.mRenderscriptSupportModeEnabled = productFlavor.getRenderscriptSupportModeEnabled();
+ flavor.mRenderscriptNdkModeEnabled = productFlavor.getRenderscriptNdkModeEnabled();
+
+ flavor.mVersionCode = productFlavor.getVersionCode();
+ flavor.mVersionName = productFlavor.getVersionName();
+
+ flavor.mApplicationId = productFlavor.getApplicationId();
+
+ flavor.mTestApplicationId = productFlavor.getTestApplicationId();
+ flavor.mTestInstrumentationRunner = productFlavor.getTestInstrumentationRunner();
+ flavor.mTestInstrumentationRunnerArguments = productFlavor.getTestInstrumentationRunnerArguments();
+ flavor.mTestHandleProfiling = productFlavor.getTestHandleProfiling();
+ flavor.mTestFunctionalTest = productFlavor.getTestFunctionalTest();
+
+ flavor.mSigningConfig = productFlavor.getSigningConfig();
+
+ flavor.mVectorDrawablesOptions =
+ DefaultVectorDrawablesOptions.copyOf(productFlavor.getVectorDrawables());
+
+ flavor.addResourceConfigurations(productFlavor.getResourceConfigurations());
+ flavor.addManifestPlaceholders(productFlavor.getManifestPlaceholders());
+
+ flavor.addResValues(productFlavor.getResValues());
+ flavor.addBuildConfigFields(productFlavor.getBuildConfigFields());
+
+ flavor.setMultiDexEnabled(productFlavor.getMultiDexEnabled());
+
+ flavor.setMultiDexKeepFile(productFlavor.getMultiDexKeepFile());
+ flavor.setMultiDexKeepProguard(productFlavor.getMultiDexKeepProguard());
+ flavor.setJarJarRuleFiles(ImmutableList.copyOf(productFlavor.getJarJarRuleFiles()));
+
+ return flavor;
+ }
+
+ private static <T> T chooseNotNull(T overlay, T base) {
+ return overlay != null ? overlay : base;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ DefaultProductFlavor that = (DefaultProductFlavor) o;
+
+ return Objects.equal(mDimension, that.mDimension) &&
+ Objects.equal(mApplicationId, that.mApplicationId) &&
+ Objects.equal(mMaxSdkVersion, that.mMaxSdkVersion) &&
+ Objects.equal(mMinSdkVersion, that.mMinSdkVersion) &&
+ Objects.equal(mName, that.mName) &&
+ Objects.equal(mRenderscriptNdkModeEnabled, that.mRenderscriptNdkModeEnabled) &&
+ Objects.equal(mRenderscriptSupportModeEnabled,
+ that.mRenderscriptSupportModeEnabled) &&
+ Objects.equal(mRenderscriptTargetApi, that.mRenderscriptTargetApi) &&
+ Objects.equal(mResourceConfiguration, that.mResourceConfiguration) &&
+ Objects.equal(mSigningConfig, that.mSigningConfig) &&
+ Objects.equal(mTargetSdkVersion, that.mTargetSdkVersion) &&
+ Objects.equal(mTestApplicationId, that.mTestApplicationId) &&
+ Objects.equal(mTestFunctionalTest, that.mTestFunctionalTest) &&
+ Objects.equal(mTestHandleProfiling, that.mTestHandleProfiling) &&
+ Objects.equal(mTestInstrumentationRunner, that.mTestInstrumentationRunner) &&
+ Objects.equal(mTestInstrumentationRunnerArguments,
+ that.mTestInstrumentationRunnerArguments) &&
+ Objects.equal(mVersionCode, that.mVersionCode) &&
+ Objects.equal(mVersionName, that.mVersionName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(
+ super.hashCode(),
+ mName,
+ mDimension,
+ mMinSdkVersion,
+ mTargetSdkVersion,
+ mMaxSdkVersion,
+ mRenderscriptTargetApi,
+ mRenderscriptSupportModeEnabled,
+ mRenderscriptNdkModeEnabled,
+ mVersionCode,
+ mVersionName,
+ mApplicationId,
+ mTestApplicationId,
+ mTestInstrumentationRunner,
+ mTestInstrumentationRunnerArguments,
+ mTestHandleProfiling,
+ mTestFunctionalTest,
+ mSigningConfig,
+ mResourceConfiguration);
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", mName)
+ .add("dimension", mDimension)
+ .add("minSdkVersion", mMinSdkVersion)
+ .add("targetSdkVersion", mTargetSdkVersion)
+ .add("renderscriptTargetApi", mRenderscriptTargetApi)
+ .add("renderscriptSupportModeEnabled", mRenderscriptSupportModeEnabled)
+ .add("renderscriptNdkModeEnabled", mRenderscriptNdkModeEnabled)
+ .add("versionCode", mVersionCode)
+ .add("versionName", mVersionName)
+ .add("applicationId", mApplicationId)
+ .add("testApplicationId", mTestApplicationId)
+ .add("testInstrumentationRunner", mTestInstrumentationRunner)
+ .add("testInstrumentationRunnerArguments", mTestInstrumentationRunnerArguments)
+ .add("testHandleProfiling", mTestHandleProfiling)
+ .add("testFunctionalTest", mTestFunctionalTest)
+ .add("signingConfig", mSigningConfig)
+ .add("resConfig", mResourceConfiguration)
+ .add("mBuildConfigFields", getBuildConfigFields())
+ .add("mResValues", getResValues())
+ .add("mProguardFiles", getProguardFiles())
+ .add("mConsumerProguardFiles", getConsumerProguardFiles())
+ .add("mManifestPlaceholders", getManifestPlaceholders())
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DefaultVectorDrawablesOptions.java b/build-system/builder/src/main/java/com/android/builder/core/DefaultVectorDrawablesOptions.java
new file mode 100644
index 0000000..1b180cd
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DefaultVectorDrawablesOptions.java
@@ -0,0 +1,94 @@
+/*
+ * 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.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.VectorDrawablesOptions;
+import com.google.common.base.Objects;
+import com.google.common.collect.Sets;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * Default implementation of {@link VectorDrawablesOptions}.
+ */
+public class DefaultVectorDrawablesOptions implements VectorDrawablesOptions, Serializable {
+
+ @Nullable
+ private Set<String> mGeneratedDensities;
+
+ @Nullable
+ private Boolean mUseSupportLibrary;
+
+ @NonNull
+ public static DefaultVectorDrawablesOptions copyOf(@NonNull VectorDrawablesOptions original) {
+ DefaultVectorDrawablesOptions options = new DefaultVectorDrawablesOptions();
+
+ options.setGeneratedDensities(original.getGeneratedDensities());
+ options.setUseSupportLibrary(original.getUseSupportLibrary());
+
+ return options;
+ }
+
+ /**
+ * Densities used when generating PNGs from vector drawables at build time. For the PNGs to be
+ * generated, minimum SDK has to be below 21.
+ *
+ * <p>If set to an empty collection, all special handling of vector drawables will be
+ * disabled.
+ *
+ * <p>See <a href="http://developer.android.com/guide/practices/screens_support.html">
+ * Supporting Multiple Screens</a>.
+ */
+ @Nullable
+ @Override
+ public Set<String> getGeneratedDensities() {
+ return mGeneratedDensities;
+ }
+
+ public void setGeneratedDensities(@Nullable Iterable<String> densities) {
+ if (densities == null) {
+ mGeneratedDensities = null;
+ } else {
+ mGeneratedDensities = Sets.newHashSet(densities);
+ }
+ }
+
+ @Override
+ @Nullable
+ public Boolean getUseSupportLibrary() {
+ return mUseSupportLibrary;
+ }
+
+ public void useSupportLibrary(@Nullable Boolean useSupportLibrary) {
+ setUseSupportLibrary(useSupportLibrary);
+ }
+
+ public void setUseSupportLibrary(@Nullable Boolean useSupportLibrary) {
+ mUseSupportLibrary = useSupportLibrary;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("mGeneratedDensities", mGeneratedDensities)
+ .add("mUseSupportLibrary", mUseSupportLibrary)
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java b/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java
new file mode 100644
index 0000000..e7c658c
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DexOptions.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import com.android.annotations.Nullable;
+
+public interface DexOptions {
+
+ boolean getIncremental();
+ boolean getPreDexLibraries();
+ boolean getJumboMode();
+
+ @Nullable
+ String getJavaMaxHeapSize();
+ @Nullable
+ Integer getThreadCount();
+ @Nullable
+ Integer getMaxProcessCount();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/DexProcessBuilder.java b/build-system/builder/src/main/java/com/android/builder/core/DexProcessBuilder.java
new file mode 100644
index 0000000..bea5180
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/DexProcessBuilder.java
@@ -0,0 +1,273 @@
+/*
+ * 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.builder.core;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.process.JavaProcessInfo;
+import com.android.ide.common.process.ProcessEnvBuilder;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.repository.Revision;
+import com.android.sdklib.BuildToolInfo;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A builder to create a dex-specific ProcessInfoBuilder
+ */
+public class DexProcessBuilder extends ProcessEnvBuilder<DexProcessBuilder> {
+ public static final Revision MIN_MULTIDEX_BUILD_TOOLS_REV = new Revision(21, 0, 0);
+ public static final Revision MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV = new Revision(22, 0, 2);
+ public static final Revision FIXED_DX_MERGER = new Revision(23, 0, 2);
+
+ @NonNull
+ private final File mOutputFile;
+ private boolean mVerbose = false;
+ private boolean mIncremental = false;
+ private boolean mNoOptimize = false;
+ private boolean mMultiDex = false;
+ private File mMainDexList = null;
+ private Set<File> mInputs = Sets.newHashSet();
+ private List<String> mAdditionalParams = null;
+
+ public DexProcessBuilder(@NonNull File outputFile) {
+ mOutputFile = outputFile;
+ }
+
+ @NonNull
+ public DexProcessBuilder setVerbose(boolean verbose) {
+ mVerbose = verbose;
+ return this;
+ }
+
+ @NonNull
+ public DexProcessBuilder setIncremental(boolean incremental) {
+ mIncremental = incremental;
+ return this;
+ }
+
+ @NonNull
+ public DexProcessBuilder setNoOptimize(boolean noOptimize) {
+ mNoOptimize = noOptimize;
+ return this;
+ }
+
+ @NonNull
+ public DexProcessBuilder setMultiDex(boolean multiDex) {
+ mMultiDex = multiDex;
+ return this;
+ }
+
+ @NonNull
+ public DexProcessBuilder setMainDexList(File mainDexList) {
+ mMainDexList = mainDexList;
+ return this;
+ }
+
+ @NonNull
+ public DexProcessBuilder addInput(File input) {
+ mInputs.add(input);
+ return this;
+ }
+
+ @NonNull
+ public DexProcessBuilder addInputs(@NonNull Collection<File> inputs) {
+ mInputs.addAll(inputs);
+ return this;
+ }
+
+ @NonNull
+ public DexProcessBuilder additionalParameters(@NonNull List<String> params) {
+ if (mAdditionalParams == null) {
+ mAdditionalParams = Lists.newArrayListWithExpectedSize(params.size());
+ }
+
+ mAdditionalParams.addAll(params);
+
+ return this;
+ }
+
+ @NonNull
+ public File getOutputFile() {
+ return mOutputFile;
+ }
+
+ public boolean isVerbose() {
+ return mVerbose;
+ }
+
+ public boolean isIncremental() {
+ return mIncremental;
+ }
+
+ public boolean isNoOptimize() {
+ return mNoOptimize;
+ }
+
+ public boolean isMultiDex() {
+ return mMultiDex;
+ }
+
+ public File getMainDexList() {
+ return mMainDexList;
+ }
+
+ public Set<File> getInputs() {
+ return mInputs;
+ }
+
+ @NonNull
+ public JavaProcessInfo build(
+ @NonNull BuildToolInfo buildToolInfo,
+ @NonNull DexOptions dexOptions) throws ProcessException {
+
+ Revision buildToolsRevision = buildToolInfo.getRevision();
+ checkState(
+ !mMultiDex
+ || buildToolsRevision.compareTo(MIN_MULTIDEX_BUILD_TOOLS_REV) >= 0,
+ "Multi dex requires Build Tools " +
+ MIN_MULTIDEX_BUILD_TOOLS_REV.toString() +
+ " / Current: " +
+ buildToolsRevision.toShortString());
+
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.addEnvironments(mEnvironment);
+
+ String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
+ if (dx == null || !new File(dx).isFile()) {
+ throw new IllegalStateException("dx.jar is missing");
+ }
+
+ builder.setClasspath(dx);
+ builder.setMain("com.android.dx.command.Main");
+
+ if (dexOptions.getJavaMaxHeapSize() != null) {
+ builder.addJvmArg("-Xmx" + dexOptions.getJavaMaxHeapSize());
+ } else {
+ builder.addJvmArg("-Xmx1024M");
+ }
+
+ builder.addArgs("--dex");
+
+ if (mVerbose) {
+ builder.addArgs("--verbose");
+ }
+
+ if (dexOptions.getJumboMode()) {
+ builder.addArgs("--force-jumbo");
+ }
+
+ if (mIncremental) {
+ builder.addArgs("--incremental", "--no-strict");
+ }
+
+ if (mNoOptimize) {
+ builder.addArgs("--no-optimize");
+ }
+
+ // only change thread count is build tools is 22.0.2+
+ if (buildToolsRevision.compareTo(MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV) >= 0) {
+ Integer threadCount = dexOptions.getThreadCount();
+ if (threadCount == null) {
+ builder.addArgs("--num-threads=4");
+ } else {
+ builder.addArgs("--num-threads=" + threadCount);
+ }
+ }
+
+ if (mMultiDex) {
+ builder.addArgs("--multi-dex");
+
+ if (mMainDexList != null ) {
+ builder.addArgs("--main-dex-list", mMainDexList.getAbsolutePath());
+ }
+ }
+
+ if (mAdditionalParams != null) {
+ for (String arg : mAdditionalParams) {
+ builder.addArgs(arg);
+ }
+ }
+
+
+ builder.addArgs("--output", mOutputFile.getAbsolutePath());
+
+ // input
+ builder.addArgs(getFilesToAdd(buildToolsRevision));
+
+ return builder.createJavaProcess();
+ }
+
+ @NonNull
+ public List<String> getFilesToAdd(@Nullable Revision buildToolsRevision)
+ throws ProcessException {
+ // remove non-existing files.
+ Set<File> existingFiles = Sets.filter(mInputs, new Predicate<File>() {
+ @Override
+ public boolean apply(@Nullable File input) {
+ return input != null && input.exists();
+ }
+ });
+
+ if (existingFiles.isEmpty()) {
+ throw new ProcessException("No files to pass to dex.");
+ }
+
+ Collection<File> files = existingFiles;
+
+ // sort the inputs
+ if (buildToolsRevision != null && buildToolsRevision.compareTo(FIXED_DX_MERGER) < 0) {
+ List<File> sortedList = Lists.newArrayList(existingFiles);
+ Collections.sort(sortedList, new Comparator<File>() {
+ @Override
+ public int compare(File file, File file2) {
+ boolean file2IsDir = file2.isDirectory();
+ if (file.isDirectory()) {
+ return file2IsDir ? 0 : -1;
+ } else if (file2IsDir) {
+ return 1;
+ }
+
+ long diff = file.length() - file2.length();
+ return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
+ }
+ });
+
+ files = sortedList;
+ }
+
+ // convert to String-based paths.
+ List<String> filePathList = Lists.newArrayListWithCapacity(files.size());
+ for (File f : files) {
+ filePathList.add(f.getAbsolutePath());
+ }
+
+ return filePathList;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/ErrorReporter.java b/build-system/builder/src/main/java/com/android/builder/core/ErrorReporter.java
new file mode 100644
index 0000000..85c9fcb
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/ErrorReporter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SyncIssue;
+import com.android.ide.common.blame.MessageReceiver;
+
+/**
+ * An error reporter for project evaluation and execution.
+ *
+ * The behavior of the reporter must vary depending on the evaluation mode
+ * ({@link ErrorReporter.EvaluationMode}), indicating whether
+ * the IDE is querying the project or not.
+ */
+public abstract class ErrorReporter implements MessageReceiver {
+
+ public enum EvaluationMode {
+ /** Standard mode, errors should be breaking */
+ STANDARD,
+ /**
+ * IDE mode. Errors should not be breaking and should generate a SyncIssue instead.
+ */
+ IDE,
+ /** Legacy IDE mode (Studio 1.0), where SyncIssue are not understood by the IDE. */
+ IDE_LEGACY
+ }
+
+ @NonNull
+ private final EvaluationMode mMode;
+
+ protected ErrorReporter(@NonNull EvaluationMode mode) {
+ mMode = mode;
+ }
+
+ @NonNull
+ public EvaluationMode getMode() {
+ return mMode;
+ }
+
+ /**
+ * Reports an error.
+ *
+ * <p>The behavior of this method depends on whether the project is being evaluated by
+ * an IDE or from the command line. If it's the former, the error will simply be recorder
+ * and displayed after the sync properly finishes. If it's the latter, then the evaluation
+ * is aborted right away.
+ *
+ * @param data a data representing the source of the error. This goes hand in hand with the
+ * <var>type</var>, and is not meant to be readable. Instead a (possibly translated)
+ * message is created from this data and type.
+ * @param type the type of the error.
+ * @param msg a human readable error (for command line output, or if an older IDE doesn't know
+ * this particular issue type.)
+ * @return a SyncIssue if the error is only recorded.
+ *
+ * @see SyncIssue
+ */
+ @NonNull
+ public final SyncIssue handleSyncError(@Nullable String data, int type, @NonNull String msg) {
+ return handleSyncIssue(data, type, SyncIssue.SEVERITY_ERROR, msg);
+ }
+
+ /**
+ * Reports a warning.
+ *
+ * <p>Behaves similar to {@link #handleSyncError(String, int, String)} but does not abort the
+ * build.
+ *
+ * @see #handleSyncError(String, int, String)
+ */
+ @NonNull
+ public final SyncIssue handleSyncWarning(@Nullable String data, int type, @NonNull String msg) {
+ return handleSyncIssue(data, type, SyncIssue.SEVERITY_WARNING, msg);
+ }
+
+ @NonNull
+ protected abstract SyncIssue handleSyncIssue(
+ @Nullable String data,
+ int type,
+ int severity,
+ @NonNull String msg);
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/JackProcessBuilder.java b/build-system/builder/src/main/java/com/android/builder/core/JackProcessBuilder.java
new file mode 100644
index 0000000..14fb6e6
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/JackProcessBuilder.java
@@ -0,0 +1,222 @@
+/*
+ * 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.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.process.JavaProcessInfo;
+import com.android.ide.common.process.ProcessEnvBuilder;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.sdklib.BuildToolInfo;
+import com.android.repository.Revision;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A builder to create a Jack-specific ProcessInfoBuilder
+ */
+public class JackProcessBuilder extends ProcessEnvBuilder<JackProcessBuilder> {
+
+ static final Revision JACK_MIN_REV = new Revision(21, 1, 0);
+
+ private boolean mDebugLog = false;
+ private boolean mVerbose = false;
+ private String mClasspath = null;
+ private File mDexOutputFolder = null;
+ private File mJackOutputFile = null;
+ private List<File> mImportFiles = null;
+ private List<File> mProguardFiles = null;
+ private String mJavaMaxHeapSize = null;
+ private File mMappingFile = null;
+ private boolean mMultiDex = false;
+ private int mMinSdkVersion = 21;
+ private File mEcjOptionFile = null;
+ private Collection<File> mJarJarRuleFiles = null;
+ private File mIncrementalDir = null;
+
+ public JackProcessBuilder() {
+ }
+
+ @NonNull
+ public JackProcessBuilder setDebugLog(boolean debugLog) {
+ mDebugLog = debugLog;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setVerbose(boolean verbose) {
+ mVerbose = verbose;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setJavaMaxHeapSize(String javaMaxHeapSize) {
+ mJavaMaxHeapSize = javaMaxHeapSize;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setClasspath(String classpath) {
+ mClasspath = classpath;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setDexOutputFolder(File dexOutputFolder) {
+ mDexOutputFolder = dexOutputFolder;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setJackOutputFile(File jackOutputFile) {
+ mJackOutputFile = jackOutputFile;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder addImportFiles(@NonNull Collection<File> importFiles) {
+ if (mImportFiles == null) {
+ mImportFiles = Lists.newArrayListWithExpectedSize(importFiles.size());
+ }
+
+ mImportFiles.addAll(importFiles);
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder addProguardFiles(@NonNull Collection<File> proguardFiles) {
+ if (mProguardFiles == null) {
+ mProguardFiles = Lists.newArrayListWithExpectedSize(proguardFiles.size());
+ }
+
+ mProguardFiles.addAll(proguardFiles);
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setMappingFile(File mappingFile) {
+ mMappingFile = mappingFile;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setMultiDex(boolean multiDex) {
+ mMultiDex = multiDex;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setMinSdkVersion(int minSdkVersion) {
+ mMinSdkVersion = minSdkVersion;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setEcjOptionFile(File ecjOptionFile) {
+ mEcjOptionFile = ecjOptionFile;
+ return this;
+ }
+
+ @NonNull
+ public JackProcessBuilder setJarJarRuleFiles(@NonNull Collection<File> jarJarRuleFiles) {
+ mJarJarRuleFiles = jarJarRuleFiles;
+ return this;
+ }
+
+ @NonNull
+ public JavaProcessInfo build(@NonNull BuildToolInfo buildToolInfo) throws ProcessException {
+
+ Revision revision = buildToolInfo.getRevision();
+ if (revision.compareTo(JACK_MIN_REV) < 0) {
+ throw new ProcessException(
+ "Jack requires Build Tools " + JACK_MIN_REV.toString() +
+ " or later");
+ }
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.addEnvironments(mEnvironment);
+
+ String jackJar = buildToolInfo.getPath(BuildToolInfo.PathId.JACK);
+ if (jackJar == null || !new File(jackJar).isFile()) {
+ throw new IllegalStateException("jack.jar is missing");
+ }
+
+ builder.setClasspath(jackJar);
+ builder.setMain("com.android.jack.Main");
+
+ if (mJavaMaxHeapSize != null) {
+ builder.addJvmArg("-Xmx" + mJavaMaxHeapSize);
+ } else {
+ builder.addJvmArg("-Xmx1024M");
+ }
+
+ if (mDebugLog) {
+ builder.addArgs("--verbose", "debug");
+ } else if (mVerbose) {
+ builder.addArgs("--verbose", "info");
+ }
+
+ builder.addArgs("--classpath", mClasspath);
+
+ if (mImportFiles != null) {
+ for (File lib : mImportFiles) {
+ builder.addArgs("--import", lib.getAbsolutePath());
+ }
+ }
+
+ builder.addArgs("--output-dex", mDexOutputFolder.getAbsolutePath());
+
+ builder.addArgs("--output-jack", mJackOutputFile.getAbsolutePath());
+
+ builder.addArgs("-D", "jack.import.resource.policy=keep-first");
+
+ builder.addArgs("-D", "jack.reporter=sdk");
+
+ if (mProguardFiles != null && !mProguardFiles.isEmpty()) {
+ for (File file : mProguardFiles) {
+ builder.addArgs("--config-proguard", file.getAbsolutePath());
+ }
+ }
+
+ if (mMappingFile != null) {
+ builder.addArgs("-D", "jack.obfuscation.mapping.dump=true");
+ builder.addArgs("-D", "jack.obfuscation.mapping.dump.file=" + mMappingFile.getAbsolutePath());
+ }
+
+ if (mMultiDex) {
+ builder.addArgs("--multi-dex");
+ if (mMinSdkVersion < 21) {
+ builder.addArgs("legacy");
+ } else {
+ builder.addArgs("native");
+ }
+ }
+
+ if (mJarJarRuleFiles != null) {
+ for (File jarjarRuleFile : mJarJarRuleFiles) {
+ builder.addArgs("--config-jarjar", jarjarRuleFile.getAbsolutePath());
+ }
+ }
+
+ builder.addArgs("@" + mEcjOptionFile.getAbsolutePath());
+
+ return builder.createJavaProcess();
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/LibraryRequest.java b/build-system/builder/src/main/java/com/android/builder/core/LibraryRequest.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/core/LibraryRequest.java
rename to build-system/builder/src/main/java/com/android/builder/core/LibraryRequest.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/core/ManifestParser.java b/build-system/builder/src/main/java/com/android/builder/core/ManifestParser.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/core/ManifestParser.java
rename to build-system/builder/src/main/java/com/android/builder/core/ManifestParser.java
diff --git a/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java b/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java
new file mode 100644
index 0000000..1dddcd9
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/VariantConfiguration.java
@@ -0,0 +1,2204 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.dependency.DependencyContainer;
+import com.android.builder.dependency.JarDependency;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.SourceProvider;
+import com.android.ide.common.res2.AssetSet;
+import com.android.ide.common.res2.ResourceSet;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.utils.StringHelper;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Variant configuration.
+ *
+ * Variants are made from the combination of:
+ *
+ * - a build type (base interface BuildType), and its associated sources.
+ * - a default configuration (base interface ProductFlavor), and its associated sources.
+ * - a optional list of product flavors (base interface ProductFlavor) and their associated sources.
+ * - dependencies (both jar and aar).
+ *
+ * @param <T> the type used for the Build Type.
+ * @param <D> the type used for the default config
+ * @param <F> the type used for the product flavors.
+ */
+public class VariantConfiguration<T extends BuildType, D extends ProductFlavor, F extends ProductFlavor> {
+
+ // per variant, as is caches some manifest data specific to this variant.
+ private final ManifestParser sManifestParser = new DefaultManifestParser();
+
+ /**
+ * Full, unique name of the variant in camel case, including BuildType and Flavors (and Test)
+ */
+ private String mFullName;
+ /**
+ * Flavor Name of the variant, including all flavors in camel case (starting with a lower
+ * case).
+ */
+ private String mFlavorName;
+ /**
+ * Full, unique name of the variant, including BuildType, flavors and test, dash separated.
+ * (similar to full name but with dashes)
+ */
+ private String mBaseName;
+ /**
+ * Unique directory name (can include multiple folders) for the variant, based on build type,
+ * flavor and test.
+ * This always uses forward slashes ('/') as separator on all platform.
+ *
+ */
+ private String mDirName;
+ private List<String> mDirSegments;
+
+ @NonNull
+ private final D mDefaultConfig;
+ @NonNull
+ private final SourceProvider mDefaultSourceProvider;
+
+ @NonNull
+ private final T mBuildType;
+ /** SourceProvider for the BuildType. Can be null */
+ @Nullable
+ private final SourceProvider mBuildTypeSourceProvider;
+
+ private final List<String> mFlavorDimensionNames = Lists.newArrayList();
+ private final List<F> mFlavors = Lists.newArrayList();
+ private final List<SourceProvider> mFlavorSourceProviders = Lists.newArrayList();
+
+ /** Variant specific source provider, may be null */
+ @Nullable
+ private SourceProvider mVariantSourceProvider;
+
+ /** MultiFlavors specific source provider, may be null */
+ @Nullable
+ private SourceProvider mMultiFlavorSourceProvider;
+
+ @NonNull
+ private final VariantType mType;
+
+ /**
+ * Optional tested config in case this variant is used for testing another variant.
+ *
+ * @see VariantType#isForTesting()
+ */
+ private final VariantConfiguration<T, D, F> mTestedConfig;
+
+ /**
+ * An optional output that is only valid if the type is Type#LIBRARY so that the test
+ * for the library can use the library as if it was a normal dependency.
+ */
+ private LibraryDependency mOutput;
+
+ @NonNull
+ private ProductFlavor mMergedFlavor;
+
+ /**
+ * Jar dependencies.
+ */
+ private final Set<JarDependency> mJarDependencies = Sets.newHashSet();
+
+ /**
+ * Local Jar dependencies.
+ */
+ private final Set<JarDependency> mLocalJars = Sets.newHashSet();
+
+ /**
+ * List of direct library dependencies. Each object defines its own dependencies.
+ */
+ private final List<LibraryDependency> mDirectLibraries = Lists.newArrayList();
+
+ /**
+ * List of all library dependencies in a flat list.
+ *
+ * <p>The order is based on the order needed to call aapt: earlier libraries override resources
+ * of latter ones.
+ */
+ private final List<LibraryDependency> mFlatLibraries = Lists.newArrayList();
+
+ /**
+ * Variant-specific build Config fields.
+ */
+ private final Map<String, ClassField> mBuildConfigFields = Maps.newTreeMap();
+
+ /**
+ * Variant-specific res values.
+ */
+ private final Map<String, ClassField> mResValues = Maps.newTreeMap();
+
+ /**
+ * Signing Override to be used instead of any signing config provided by Build Type or
+ * Product Flavors.
+ */
+ private final SigningConfig mSigningConfigOverride;
+
+
+ /**
+ * Parses the manifest file and return the package name.
+ * @param manifestFile the manifest file
+ * @return the package name found or null
+ */
+ @Nullable
+ public static String getManifestPackage(@NonNull File manifestFile) {
+ return new DefaultManifestParser().getPackage(manifestFile);
+ }
+
+ /**
+ * Creates the configuration with the base source sets for a given {@link VariantType}. Meant
+ * for non-testing variants.
+ *
+ * @param defaultConfig the default configuration. Required.
+ * @param defaultSourceProvider the default source provider. Required
+ * @param buildType the build type for this variant. Required.
+ * @param buildTypeSourceProvider the source provider for the build type.
+ * @param type the type of the project.
+ * @param signingConfigOverride an optional Signing override to be used for signing.
+ */
+ public VariantConfiguration(
+ @NonNull D defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull T buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull VariantType type,
+ @Nullable SigningConfig signingConfigOverride) {
+ this(
+ defaultConfig, defaultSourceProvider,
+ buildType, buildTypeSourceProvider,
+ type, null /*testedConfig*/, signingConfigOverride);
+ }
+
+ /**
+ * Creates the configuration with the base source sets, and an optional tested variant.
+ *
+ * @param defaultConfig the default configuration. Required.
+ * @param defaultSourceProvider the default source provider. Required
+ * @param buildType the build type for this variant. Required.
+ * @param buildTypeSourceProvider the source provider for the build type.
+ * @param type the type of the project.
+ * @param testedConfig the reference to the tested project. Required if type is Type.ANDROID_TEST
+ * @param signingConfigOverride an optional Signing override to be used for signing.
+ */
+ public VariantConfiguration(
+ @NonNull D defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull T buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull VariantType type,
+ @Nullable VariantConfiguration<T, D, F> testedConfig,
+ @Nullable SigningConfig signingConfigOverride) {
+ checkNotNull(defaultConfig);
+ checkNotNull(defaultSourceProvider);
+ checkNotNull(buildType);
+ checkNotNull(type);
+ checkArgument(
+ !type.isForTesting() || testedConfig != null,
+ "You have to specify the tested variant for this variant type.");
+ checkArgument(
+ type.isForTesting() || testedConfig == null,
+ "This variant type doesn't need a tested variant.");
+
+ mDefaultConfig = checkNotNull(defaultConfig);
+ mDefaultSourceProvider = checkNotNull(defaultSourceProvider);
+ mBuildType = checkNotNull(buildType);
+ mBuildTypeSourceProvider = buildTypeSourceProvider;
+ mType = checkNotNull(type);
+ mTestedConfig = testedConfig;
+ mSigningConfigOverride = signingConfigOverride;
+ mMergedFlavor = DefaultProductFlavor.clone(mDefaultConfig);
+ }
+
+ /**
+ * Returns the full, unique name of the variant in camel case (starting with a lower case),
+ * including BuildType, Flavors and Test (if applicable).
+ *
+ * @return the name of the variant
+ */
+ @NonNull
+ public String getFullName() {
+ if (mFullName == null) {
+ StringBuilder sb = new StringBuilder();
+ String flavorName = getFlavorName();
+ if (!flavorName.isEmpty()) {
+ sb.append(flavorName);
+ sb.append(StringHelper.capitalize(mBuildType.getName()));
+ } else {
+ sb.append(mBuildType.getName());
+ }
+
+ if (mType.isForTesting()) {
+ sb.append(mType.getSuffix());
+ }
+
+ mFullName = sb.toString();
+ }
+
+ return mFullName;
+ }
+
+ /**
+ * Returns a full name that includes the given splits name.
+ * @param splitName the split name
+ * @return a unique name made up of the variant and split names.
+ */
+ @NonNull
+ public String computeFullNameWithSplits(@NonNull String splitName) {
+ StringBuilder sb = new StringBuilder();
+ String flavorName = getFlavorName();
+ if (!flavorName.isEmpty()) {
+ sb.append(flavorName);
+ sb.append(StringHelper.capitalize(splitName));
+ } else {
+ sb.append(splitName);
+ }
+
+ sb.append(StringHelper.capitalize(mBuildType.getName()));
+
+ if (mType.isForTesting()) {
+ sb.append(mType.getSuffix());
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the flavor name of the variant, including all flavors in camel case (starting
+ * with a lower case). If the variant has no flavor, then an empty string is returned.
+ *
+ * @return the flavor name or an empty string.
+ */
+ @NonNull
+ public String getFlavorName() {
+ if (mFlavorName == null) {
+ if (mFlavors.isEmpty()) {
+ mFlavorName = "";
+ } else {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (F flavor : mFlavors) {
+ sb.append(first ? flavor.getName() : StringHelper.capitalize(flavor.getName()));
+ first = false;
+ }
+
+ mFlavorName = sb.toString();
+ }
+ }
+
+ return mFlavorName;
+ }
+
+ /**
+ * Returns the full, unique name of the variant, including BuildType, flavors and test,
+ * dash separated. (similar to full name but with dashes)
+ *
+ * @return the name of the variant
+ */
+ @NonNull
+ public String getBaseName() {
+ if (mBaseName == null) {
+ StringBuilder sb = new StringBuilder();
+
+ if (!mFlavors.isEmpty()) {
+ for (ProductFlavor pf : mFlavors) {
+ sb.append(pf.getName()).append('-');
+ }
+ }
+
+ sb.append(mBuildType.getName());
+
+ if (mType.isForTesting()) {
+ sb.append('-').append(mType.getPrefix());
+ }
+
+ mBaseName = sb.toString();
+ }
+
+ return mBaseName;
+ }
+
+ /**
+ * Returns a base name that includes the given splits name.
+ * @param splitName the split name
+ * @return a unique name made up of the variant and split names.
+ */
+ @NonNull
+ public String computeBaseNameWithSplits(@NonNull String splitName) {
+ StringBuilder sb = new StringBuilder();
+
+ if (!mFlavors.isEmpty()) {
+ for (ProductFlavor pf : mFlavors) {
+ sb.append(pf.getName()).append('-');
+ }
+ }
+
+ sb.append(splitName).append('-');
+ sb.append(mBuildType.getName());
+
+ if (mType.isForTesting()) {
+ sb.append('-').append(mType.getPrefix());
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns a unique directory name (can include multiple folders) for the variant,
+ * based on build type, flavor and test.
+ *
+ * <p>This always uses forward slashes ('/') as separator on all platform.
+ *
+ * @return the directory name for the variant
+ */
+ @NonNull
+ public String getDirName() {
+ if (mDirName == null) {
+ StringBuilder sb = new StringBuilder();
+
+ if (mType.isForTesting()) {
+ sb.append(mType.getPrefix()).append("/");
+ }
+
+ if (!mFlavors.isEmpty()) {
+ boolean first = true;
+ for (F flavor : mFlavors) {
+ sb.append(first ? flavor.getName() : StringHelper.capitalize(flavor.getName()));
+ first = false;
+ }
+
+ sb.append('/').append(mBuildType.getName());
+
+ } else {
+ sb.append(mBuildType.getName());
+ }
+
+ mDirName = sb.toString();
+
+ }
+
+ return mDirName;
+ }
+
+ /**
+ * Returns a unique directory name (can include multiple folders) for the variant,
+ * based on build type, flavor and test.
+ *
+ * @return the directory name for the variant
+ */
+ @NonNull
+ public Collection<String> getDirectorySegments() {
+ if (mDirSegments == null) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+
+ if (mType.isForTesting()) {
+ builder.add(mType.getPrefix());
+ }
+
+ if (!mFlavors.isEmpty()) {
+ StringBuilder sb = new StringBuilder(mFlavors.size() * 10);
+ for (F flavor : mFlavors) {
+ StringHelper.appendCamelCase(sb, flavor.getName());
+ }
+ builder.add(sb.toString());
+
+ builder.add(mBuildType.getName());
+
+ } else {
+ builder.add(mBuildType.getName());
+ }
+
+ mDirSegments = builder.build();
+ }
+
+ return mDirSegments;
+ }
+ /**
+ * Returns a unique directory name (can include multiple folders) for the variant,
+ * based on build type, flavor and test, and splits.
+ *
+ * <p>This always uses forward slashes ('/') as separator on all platform.
+ *
+ * @return the directory name for the variant
+ */
+ @NonNull
+ public String computeDirNameWithSplits(@NonNull String... splitNames) {
+ StringBuilder sb = new StringBuilder();
+
+ if (mType.isForTesting()) {
+ sb.append(mType.getPrefix()).append("/");
+ }
+
+ if (!mFlavors.isEmpty()) {
+ for (F flavor : mFlavors) {
+ sb.append(flavor.getName());
+ }
+
+ sb.append('/');
+ }
+
+ for (String splitName : splitNames) {
+ if (splitName != null) {
+ sb.append(splitName).append('/');
+ }
+ }
+
+ sb.append(mBuildType.getName());
+
+ return sb.toString();
+ }
+
+ /**
+ * Return the names of the applied flavors.
+ *
+ * The list contains the dimension names as well.
+ *
+ * @return the list, possibly empty if there are no flavors.
+ */
+ @NonNull
+ public List<String> getFlavorNamesWithDimensionNames() {
+ if (mFlavors.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<String> names;
+ int count = mFlavors.size();
+
+ if (count > 1) {
+ names = Lists.newArrayListWithCapacity(count * 2);
+
+ for (int i = 0 ; i < count ; i++) {
+ names.add(mFlavors.get(i).getName());
+ names.add(mFlavorDimensionNames.get(i));
+ }
+
+ } else {
+ names = Collections.singletonList(mFlavors.get(0).getName());
+ }
+
+ return names;
+ }
+
+
+ /**
+ * Add a new configured ProductFlavor.
+ *
+ * If multiple flavors are added, the priority follows the order they are added when it
+ * comes to resolving Android resources overlays (ie earlier added flavors supersedes
+ * latter added ones).
+ *
+ * @param productFlavor the configured product flavor
+ * @param sourceProvider the source provider for the product flavor
+ * @param dimensionName the name of the dimension associated with the flavor
+ *
+ * @return the config object
+ */
+ @NonNull
+ public VariantConfiguration addProductFlavor(
+ @NonNull F productFlavor,
+ @NonNull SourceProvider sourceProvider,
+ @NonNull String dimensionName) {
+
+ mFlavors.add(productFlavor);
+ mFlavorSourceProviders.add(sourceProvider);
+ mFlavorDimensionNames.add(dimensionName);
+
+ mMergedFlavor = DefaultProductFlavor.mergeFlavors(mMergedFlavor, productFlavor);
+
+ return this;
+ }
+
+ /**
+ * Sets the variant-specific source provider.
+ * @param sourceProvider the source provider for the product flavor
+ *
+ * @return the config object
+ */
+ public VariantConfiguration setVariantSourceProvider(@Nullable SourceProvider sourceProvider) {
+ mVariantSourceProvider = sourceProvider;
+ return this;
+ }
+
+ /**
+ * Sets the variant-specific source provider.
+ * @param sourceProvider the source provider for the product flavor
+ *
+ * @return the config object
+ */
+ public VariantConfiguration setMultiFlavorSourceProvider(@Nullable SourceProvider sourceProvider) {
+ mMultiFlavorSourceProvider = sourceProvider;
+ return this;
+ }
+
+ /**
+ * Returns the variant specific source provider
+ * @return the source provider or null if none has been provided.
+ */
+ @Nullable
+ public SourceProvider getVariantSourceProvider() {
+ return mVariantSourceProvider;
+ }
+
+ @Nullable
+ public SourceProvider getMultiFlavorSourceProvider() {
+ return mMultiFlavorSourceProvider;
+ }
+
+ /**
+ * Sets the dependencies
+ *
+ * @param container a DependencyContainer.
+ * @return the config object
+ */
+ @NonNull
+ public VariantConfiguration setDependencies(@NonNull DependencyContainer container) {
+ // Output of mTestedConfig will not be initialized until the tasks for the tested config are
+ // created. If library output has never been added to mDirectLibraries, checked the output
+ // of the mTestedConfig to see if the tasks are now created.
+ if (mTestedConfig != null &&
+ mTestedConfig.mType == VariantType.LIBRARY &&
+ mTestedConfig.mOutput != null &&
+ !mDirectLibraries.contains(mTestedConfig.mOutput)) {
+ mDirectLibraries.add(mTestedConfig.mOutput);
+ }
+
+ mDirectLibraries.addAll(container.getAndroidDependencies());
+ mJarDependencies.addAll(container.getJarDependencies());
+ mLocalJars.addAll(container.getLocalDependencies());
+
+ resolveIndirectLibraryDependencies(mDirectLibraries, mFlatLibraries);
+
+ return this;
+ }
+
+ /**
+ * Returns the list of external/module jar dependencies
+ * @return a non null collection of Jar dependencies.
+ */
+ @NonNull
+ public Collection<JarDependency> getJarDependencies() {
+ return mJarDependencies;
+ }
+
+ /**
+ * Returns the list of local jar dependencies
+ * @return a non null collection of Jar dependencies.
+ */
+ @NonNull
+ public Collection<JarDependency> getLocalJarDependencies() {
+ return mLocalJars;
+ }
+
+ /**
+ * Sets the output of this variant. This is required when the variant is a library so that
+ * the variant that tests this library can properly include the tested library in its own
+ * package.
+ *
+ * @param output the output of the library as an LibraryDependency that will provides the
+ * location of all the created items.
+ * @return the config object
+ */
+ @NonNull
+ public VariantConfiguration setOutput(LibraryDependency output) {
+ mOutput = output;
+ return this;
+ }
+
+ /**
+ * Returns the {@link LibraryDependency} that this library variant produces. Used so that
+ * related test variants can use it as a dependency. Returns null if this is not a library
+ * variant.
+ *
+ * @see #mOutput
+ */
+ @Nullable
+ public LibraryDependency getOutput() {
+ return mOutput;
+ }
+
+ @NonNull
+ public D getDefaultConfig() {
+ return mDefaultConfig;
+ }
+
+ @NonNull
+ public SourceProvider getDefaultSourceSet() {
+ return mDefaultSourceProvider;
+ }
+
+ @NonNull
+ public ProductFlavor getMergedFlavor() {
+ return mMergedFlavor;
+ }
+
+ @NonNull
+ public T getBuildType() {
+ return mBuildType;
+ }
+
+ /**
+ * The SourceProvider for the BuildType. Can be null.
+ */
+ @Nullable
+ public SourceProvider getBuildTypeSourceSet() {
+ return mBuildTypeSourceProvider;
+ }
+
+ public boolean hasFlavors() {
+ return !mFlavors.isEmpty();
+ }
+
+ /**
+ * Returns the product flavors. Items earlier in the list override later items.
+ */
+ @NonNull
+ public List<F> getProductFlavors() {
+ return mFlavors;
+ }
+
+ /**
+ * Returns the list of SourceProviders for the flavors.
+ *
+ * The list is ordered from higher priority to lower priority.
+ *
+ * @return the list of Source Providers for the flavors. Never null.
+ */
+ @NonNull
+ public List<SourceProvider> getFlavorSourceProviders() {
+ return mFlavorSourceProviders;
+ }
+
+ /**
+ * Returns the direct library dependencies
+ */
+ @NonNull
+ public List<LibraryDependency> getDirectLibraries() {
+ return mDirectLibraries;
+ }
+
+ /**
+ * Returns all the library dependencies, direct and transitive.
+ */
+ @NonNull
+ public List<LibraryDependency> getAllLibraries() {
+ return mFlatLibraries;
+ }
+
+ @NonNull
+ public VariantType getType() {
+ return mType;
+ }
+
+ @Nullable
+ public VariantConfiguration getTestedConfig() {
+ return mTestedConfig;
+ }
+
+ /**
+ * Resolves a given list of libraries, finds out if they depend on other libraries, and
+ * returns a flat list of all the direct and indirect dependencies in the proper order (first
+ * is higher priority when calling aapt).
+ * @param directDependencies the libraries to resolve
+ * @param outFlatDependencies where to store all the libraries.
+ */
+ @VisibleForTesting
+ static void resolveIndirectLibraryDependencies(List<LibraryDependency> directDependencies,
+ List<LibraryDependency> outFlatDependencies) {
+ if (directDependencies == null) {
+ return;
+ }
+ // loop in the inverse order to resolve dependencies on the libraries, so that if a library
+ // is required by two higher level libraries it can be inserted in the correct place
+ for (int i = directDependencies.size() - 1 ; i >= 0 ; i--) {
+ LibraryDependency library = directDependencies.get(i);
+
+ // get its libraries
+ Collection<LibraryDependency> dependencies = library.getDependencies();
+ List<LibraryDependency> depList = Lists.newArrayList(dependencies);
+
+ // resolve the dependencies for those libraries
+ resolveIndirectLibraryDependencies(depList, outFlatDependencies);
+
+ // and add the current one (if needed) in front (higher priority)
+ if (!outFlatDependencies.contains(library)) {
+ outFlatDependencies.add(0, library);
+ }
+ }
+ }
+
+ /**
+ * Returns the original application ID before any overrides from flavors.
+ * If the variant is a test variant, then the application ID is the one coming from the
+ * configuration of the tested variant, and this call is similar to {@link #getApplicationId()}
+ * @return the original application ID
+ */
+ @Nullable
+ public String getOriginalApplicationId() {
+ if (mType.isForTesting()) {
+ return getApplicationId();
+ }
+
+ return getPackageFromManifest();
+ }
+
+ /**
+ * Returns the application ID for this variant. This could be coming from the manifest or
+ * could be overridden through the product flavors and/or the build type.
+ * @return the application ID
+ */
+ @NonNull
+ public String getApplicationId() {
+ String id;
+
+ if (mType.isForTesting()) {
+ checkState(mTestedConfig != null);
+
+ id = mMergedFlavor.getTestApplicationId();
+ String testedPackage = mTestedConfig.getApplicationId();
+ if (id == null) {
+ id = testedPackage + ".test";
+ } else {
+ if (id.equals(testedPackage)) {
+ throw new RuntimeException(String.format("Application and test application id "
+ + "cannot be the same: both are '%s' for %s",
+ id, getFullName()));
+ }
+ }
+
+ } else {
+ // first get package override.
+ id = getIdOverride();
+ // if it's null, this means we just need the default package
+ // from the manifest since both flavor and build type do nothing.
+ if (id == null) {
+ id = getPackageFromManifest();
+ }
+ }
+
+ if (id == null) {
+ throw new RuntimeException("Failed to get application id for " + getFullName());
+ }
+
+ return id;
+ }
+
+ @Nullable
+ public String getTestedApplicationId() {
+ if (mType.isForTesting()) {
+ checkState(mTestedConfig != null);
+ if (mTestedConfig.mType == VariantType.LIBRARY) {
+ return getApplicationId();
+ } else {
+ return mTestedConfig.getApplicationId();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the application id override value coming from the Product Flavor and/or the
+ * Build Type. If the package/id is not overridden then this returns null.
+ *
+ * @return the id override or null
+ */
+ @Nullable
+ public String getIdOverride() {
+ String idName = mMergedFlavor.getApplicationId();
+ String idSuffix = Objects.firstNonNull(mMergedFlavor.getApplicationIdSuffix(), "");
+
+ String buildTypeIdSuffix = mBuildType.getApplicationIdSuffix();
+ if (!Strings.isNullOrEmpty(buildTypeIdSuffix)) {
+ if (buildTypeIdSuffix.charAt(0) == '.') {
+ idSuffix = idSuffix + buildTypeIdSuffix;
+ } else {
+ idSuffix = idSuffix + '.' + buildTypeIdSuffix;
+ }
+ }
+
+ if (!idSuffix.isEmpty()) {
+ if (idName == null) {
+ idName = getPackageFromManifest();
+ }
+
+ if (idSuffix.charAt(0) == '.') {
+ idName = idName + idSuffix;
+ } else {
+ idName = idName + '.' + idSuffix;
+ }
+ }
+
+ return idName;
+ }
+
+ /**
+ * Returns the version name for this variant. This could be coming from the manifest or
+ * could be overridden through the product flavors, and can have a suffix specified by
+ * the build type.
+ *
+ * @return the version name
+ */
+ @Nullable
+ public String getVersionName() {
+ String versionName = mMergedFlavor.getVersionName();
+ String versionSuffix = mBuildType.getVersionNameSuffix();
+
+ if (versionName == null && !mType.isForTesting()) {
+ versionName = getVersionNameFromManifest();
+ }
+
+ if (versionSuffix != null && !versionSuffix.isEmpty()) {
+ versionName = Strings.nullToEmpty(versionName) + versionSuffix;
+ }
+
+ return versionName;
+ }
+
+ /**
+ * Returns the version code for this variant. This could be coming from the manifest or
+ * could be overridden through the product flavors, and can have a suffix specified by
+ * the build type.
+ *
+ * @return the version code or -1 if there was non defined.
+ */
+ public int getVersionCode() {
+ int versionCode = mMergedFlavor.getVersionCode() != null ?
+ mMergedFlavor.getVersionCode() : -1;
+
+ if (versionCode == -1 && !mType.isForTesting()) {
+ versionCode = getVersionCodeFromManifest();
+ }
+
+ return versionCode;
+ }
+
+ private static final String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner";
+ private static final String MULTIDEX_TEST_RUNNER = "com.android.test.runner.MultiDexTestRunner";
+ private static final Boolean DEFAULT_HANDLE_PROFILING = false;
+ private static final Boolean DEFAULT_FUNCTIONAL_TEST = false;
+
+ /**
+ * Returns the instrumentationRunner to use to test this variant, or if the
+ * variant is a test, the one to use to test the tested variant.
+ * @return the instrumentation test runner name
+ */
+ @NonNull
+ public String getInstrumentationRunner() {
+ VariantConfiguration config = this;
+ if (mType.isForTesting()) {
+ config = getTestedConfig();
+ checkState(config != null);
+ }
+ String runner = config.mMergedFlavor.getTestInstrumentationRunner();
+ if (runner != null) {
+ return runner;
+ }
+
+ if (isMultiDexEnabled() && isLegacyMultiDexMode()) {
+ return MULTIDEX_TEST_RUNNER;
+ }
+
+ return DEFAULT_TEST_RUNNER;
+ }
+
+ /**
+ * Returns the instrumentationRunner arguments to use to test this variant, or if the
+ * variant is a test, the ones to use to test the tested variant
+ */
+ @NonNull
+ public Map<String, String> getInstrumentationRunnerArguments() {
+ VariantConfiguration config = this;
+ if (mType.isForTesting()) {
+ config = getTestedConfig();
+ checkState(config != null);
+ }
+ return config.mMergedFlavor.getTestInstrumentationRunnerArguments();
+ }
+
+ /**
+ * Returns handleProfiling value to use to test this variant, or if the
+ * variant is a test, the one to use to test the tested variant.
+ * @return the handleProfiling value
+ */
+ @NonNull
+ public Boolean getHandleProfiling() {
+ VariantConfiguration config = this;
+ if (mType.isForTesting()) {
+ config = getTestedConfig();
+ checkState(config != null);
+ }
+ Boolean handleProfiling = config.mMergedFlavor.getTestHandleProfiling();
+ return handleProfiling != null ? handleProfiling : DEFAULT_HANDLE_PROFILING;
+ }
+
+ /**
+ * Returns functionalTest value to use to test this variant, or if the
+ * variant is a test, the one to use to test the tested variant.
+ * @return the functionalTest value
+ */
+ @NonNull
+ public Boolean getFunctionalTest() {
+ VariantConfiguration config = this;
+ if (mType.isForTesting()) {
+ config = getTestedConfig();
+ checkState(config != null);
+ }
+ Boolean functionalTest = config.mMergedFlavor.getTestFunctionalTest();
+ return functionalTest != null ? functionalTest : DEFAULT_FUNCTIONAL_TEST;
+ }
+
+ /**
+ * Reads the package name from the manifest. This is unmodified by the build type.
+ */
+ @Nullable
+ public String getPackageFromManifest() {
+ checkState(!mType.isForTesting());
+
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ String packageName = sManifestParser.getPackage(manifestLocation);
+ if (packageName == null) {
+ throw new RuntimeException(String.format("Cannot read packageName from %1$s",
+ mDefaultSourceProvider.getManifestFile().getAbsolutePath()));
+ }
+ return packageName;
+ }
+
+ /**
+ * Reads the version name from the manifest.
+ */
+ @Nullable
+ public String getVersionNameFromManifest() {
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ return sManifestParser.getVersionName(manifestLocation);
+ }
+
+ /**
+ * Reads the version code from the manifest.
+ */
+ public int getVersionCodeFromManifest() {
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ return sManifestParser.getVersionCode(manifestLocation);
+ }
+
+ /**
+ * Return the minSdkVersion for this variant.
+ *
+ * This uses both the value from the manifest (if present), and the override coming
+ * from the flavor(s) (if present).
+ * @return the minSdkVersion
+ */
+ @NonNull
+ public ApiVersion getMinSdkVersion() {
+ if (mTestedConfig != null) {
+ return mTestedConfig.getMinSdkVersion();
+ }
+
+ ApiVersion minSdkVersion = mMergedFlavor.getMinSdkVersion();
+ if (minSdkVersion == null) {
+ // read it from the main manifest
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ minSdkVersion = DefaultApiVersion.create(
+ sManifestParser.getMinSdkVersion(manifestLocation));
+ }
+
+ return minSdkVersion;
+ }
+
+ /**
+ * Return the targetSdkVersion for this variant.
+ *
+ * This uses both the value from the manifest (if present), and the override coming
+ * from the flavor(s) (if present).
+ * @return the targetSdkVersion
+ */
+ @NonNull
+ public ApiVersion getTargetSdkVersion() {
+ if (mTestedConfig != null) {
+ return mTestedConfig.getTargetSdkVersion();
+ }
+ ApiVersion targetSdkVersion = mMergedFlavor.getTargetSdkVersion();
+ if (targetSdkVersion == null) {
+ // read it from the main manifest
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ targetSdkVersion = DefaultApiVersion.create(
+ sManifestParser.getTargetSdkVersion(manifestLocation));
+ }
+
+ return targetSdkVersion;
+ }
+
+ @Nullable
+ public File getMainManifest() {
+ File defaultManifest = mDefaultSourceProvider.getManifestFile();
+
+ // this could not exist in a test project.
+ if (defaultManifest.isFile()) {
+ return defaultManifest;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier
+ * items are meant to be overridden by later items.
+ *
+ * @return a list of source provider
+ */
+ @NonNull
+ public List<SourceProvider> getSortedSourceProviders() {
+ List<SourceProvider> providers = Lists.newArrayList();
+
+ // first the default source provider
+ providers.add(mDefaultSourceProvider);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
+ providers.add(mFlavorSourceProviders.get(n));
+ }
+
+ // multiflavor specific overrides flavor
+ if (mMultiFlavorSourceProvider != null) {
+ providers.add(mMultiFlavorSourceProvider);
+ }
+
+ // build type overrides flavors
+ if (mBuildTypeSourceProvider != null) {
+ providers.add(mBuildTypeSourceProvider);
+ }
+
+ // variant specific overrides all
+ if (mVariantSourceProvider != null) {
+ providers.add(mVariantSourceProvider);
+ }
+
+ return providers;
+ }
+
+ @NonNull
+ public List<File> getManifestOverlays() {
+ List<File> inputs = Lists.newArrayList();
+
+ if (mVariantSourceProvider != null) {
+ File variantLocation = mVariantSourceProvider.getManifestFile();
+ if (variantLocation.isFile()) {
+ inputs.add(variantLocation);
+ }
+ }
+
+ if (mBuildTypeSourceProvider != null) {
+ File typeLocation = mBuildTypeSourceProvider.getManifestFile();
+ if (typeLocation.isFile()) {
+ inputs.add(typeLocation);
+ }
+ }
+
+ if (mMultiFlavorSourceProvider != null) {
+ File variantLocation = mMultiFlavorSourceProvider.getManifestFile();
+ if (variantLocation.isFile()) {
+ inputs.add(variantLocation);
+ }
+ }
+
+ for (SourceProvider sourceProvider : mFlavorSourceProviders) {
+ File f = sourceProvider.getManifestFile();
+ if (f.isFile()) {
+ inputs.add(f);
+ }
+ }
+
+ return inputs;
+ }
+
+ /**
+ * Returns the dynamic list of {@link ResourceSet} based on the configuration, its dependencies,
+ * as well as tested config if applicable (test of a library).
+ *
+ * The list is ordered in ascending order of importance, meaning the first set is meant to be
+ * overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
+ * {@link com.android.ide.common.res2.ResourceMerger}.
+ *
+ * @param generatedResFolders a list of generated res folders
+ * @param includeDependencies whether to include in the result the resources of the dependencies
+ *
+ * @return a list ResourceSet.
+ */
+ @NonNull
+ public List<ResourceSet> getResourceSets(@NonNull List<File> generatedResFolders,
+ boolean includeDependencies,
+ boolean validateEnabled) {
+ List<ResourceSet> resourceSets = Lists.newArrayList();
+
+ // the list of dependency must be reversed to use the right overlay order.
+ if (includeDependencies) {
+ for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
+ LibraryDependency dependency = mFlatLibraries.get(n);
+ if (!dependency.isOptional()) {
+ File resFolder = dependency.getResFolder();
+ if (resFolder.isDirectory()) {
+ ResourceSet resourceSet =
+ new ResourceSet(dependency.getFolder().getName(), validateEnabled);
+ resourceSet.addSource(resFolder);
+ resourceSet.setFromDependency(true);
+ resourceSets.add(resourceSet);
+ }
+ }
+ }
+ }
+
+ Collection<File> mainResDirs = mDefaultSourceProvider.getResDirectories();
+
+ // the main + generated res folders are in the same ResourceSet
+ ResourceSet resourceSet = new ResourceSet(BuilderConstants.MAIN, validateEnabled);
+ resourceSet.addSources(mainResDirs);
+ if (!generatedResFolders.isEmpty()) {
+ for (File generatedResFolder : generatedResFolders) {
+ resourceSet.addSource(generatedResFolder);
+
+ }
+ }
+ resourceSets.add(resourceSet);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
+ SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
+
+ Collection<File> flavorResDirs = sourceProvider.getResDirectories();
+ // we need the same of the flavor config, but it's in a different list.
+ // This is fine as both list are parallel collections with the same number of items.
+ resourceSet = new ResourceSet(sourceProvider.getName(), validateEnabled);
+ resourceSet.addSources(flavorResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ // multiflavor specific overrides flavor
+ if (mMultiFlavorSourceProvider != null) {
+ Collection<File> variantResDirs = mMultiFlavorSourceProvider.getResDirectories();
+ resourceSet = new ResourceSet(getFlavorName(), validateEnabled);
+ resourceSet.addSources(variantResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ // build type overrides the flavors
+ if (mBuildTypeSourceProvider != null) {
+ Collection<File> typeResDirs = mBuildTypeSourceProvider.getResDirectories();
+ resourceSet = new ResourceSet(mBuildType.getName(), validateEnabled);
+ resourceSet.addSources(typeResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ // variant specific overrides all
+ if (mVariantSourceProvider != null) {
+ Collection<File> variantResDirs = mVariantSourceProvider.getResDirectories();
+ resourceSet = new ResourceSet(getFullName(), validateEnabled);
+ resourceSet.addSources(variantResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ return resourceSets;
+ }
+
+ /**
+ * Returns the dynamic list of {@link AssetSet} based on the configuration, its dependencies,
+ * as well as tested config if applicable (test of a library).
+ *
+ * The list is ordered in ascending order of importance, meaning the first set is meant to be
+ * overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
+ * {@link com.android.ide.common.res2.AssetMerger}.
+ *
+ * @return a list ResourceSet.
+ */
+ @NonNull
+ public List<AssetSet> getAssetSets(@NonNull List<File> generatedResFolders,
+ boolean includeDependencies) {
+ List<AssetSet> assetSets = Lists.newArrayList();
+
+ if (includeDependencies) {
+ // the list of dependency must be reversed to use the right overlay order.
+ for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
+ LibraryDependency dependency = mFlatLibraries.get(n);
+ File assetFolder = dependency.getAssetsFolder();
+ if (assetFolder.isDirectory()) {
+ AssetSet assetSet = new AssetSet(dependency.getFolder().getName());
+ assetSet.addSource(assetFolder);
+ assetSets.add(assetSet);
+ }
+ }
+ }
+
+ Collection<File> mainResDirs = mDefaultSourceProvider.getAssetsDirectories();
+
+ // the main + generated asset folders are in the same AssetSet
+ AssetSet assetSet = new AssetSet(BuilderConstants.MAIN);
+ assetSet.addSources(mainResDirs);
+ if (!generatedResFolders.isEmpty()) {
+ for (File generatedResFolder : generatedResFolders) {
+ assetSet.addSource(generatedResFolder);
+ }
+ }
+ assetSets.add(assetSet);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
+ SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
+
+ Collection<File> flavorResDirs = sourceProvider.getAssetsDirectories();
+ // we need the same of the flavor config, but it's in a different list.
+ // This is fine as both list are parallel collections with the same number of items.
+ assetSet = new AssetSet(mFlavors.get(n).getName());
+ assetSet.addSources(flavorResDirs);
+ assetSets.add(assetSet);
+ }
+
+ // multiflavor specific overrides flavor
+ if (mMultiFlavorSourceProvider != null) {
+ Collection<File> variantResDirs = mMultiFlavorSourceProvider.getAssetsDirectories();
+ assetSet = new AssetSet(getFlavorName());
+ assetSet.addSources(variantResDirs);
+ assetSets.add(assetSet);
+ }
+
+ // build type overrides flavors
+ if (mBuildTypeSourceProvider != null) {
+ Collection<File> typeResDirs = mBuildTypeSourceProvider.getAssetsDirectories();
+ assetSet = new AssetSet(mBuildType.getName());
+ assetSet.addSources(typeResDirs);
+ assetSets.add(assetSet);
+ }
+
+ // variant specific overrides all
+ if (mVariantSourceProvider != null) {
+ Collection<File> variantResDirs = mVariantSourceProvider.getAssetsDirectories();
+ assetSet = new AssetSet(getFullName());
+ assetSet.addSources(variantResDirs);
+ assetSets.add(assetSet);
+ }
+
+ return assetSets;
+ }
+
+ /**
+ * Returns the dynamic list of {@link AssetSet} based on the configuration, its dependencies,
+ * as well as tested config if applicable (test of a library).
+ *
+ * The list is ordered in ascending order of importance, meaning the first set is meant to be
+ * overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
+ * {@link com.android.ide.common.res2.AssetMerger}.
+ *
+ * @return a list ResourceSet.
+ */
+ @NonNull
+ public List<AssetSet> getJniLibsSets() {
+ List<AssetSet> jniSets = Lists.newArrayList();
+
+ Collection<File> mainJniLibsDirs = mDefaultSourceProvider.getJniLibsDirectories();
+
+ // the main + generated asset folders are in the same AssetSet
+ AssetSet jniSet = new AssetSet(BuilderConstants.MAIN);
+ jniSet.addSources(mainJniLibsDirs);
+ jniSets.add(jniSet);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
+ SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
+
+ Collection<File> flavorJniDirs = sourceProvider.getJniLibsDirectories();
+ // we need the same of the flavor config, but it's in a different list.
+ // This is fine as both list are parallel collections with the same number of items.
+ jniSet = new AssetSet(mFlavors.get(n).getName());
+ jniSet.addSources(flavorJniDirs);
+ jniSets.add(jniSet);
+ }
+
+ // multiflavor specific overrides flavor
+ if (mMultiFlavorSourceProvider != null) {
+ Collection<File> variantJniDirs = mMultiFlavorSourceProvider.getJniLibsDirectories();
+ jniSet = new AssetSet(getFlavorName());
+ jniSet.addSources(variantJniDirs);
+ jniSets.add(jniSet);
+ }
+
+ // build type overrides flavors
+ if (mBuildTypeSourceProvider != null) {
+ Collection<File> typeJniDirs = mBuildTypeSourceProvider.getJniLibsDirectories();
+ jniSet = new AssetSet(mBuildType.getName());
+ jniSet.addSources(typeJniDirs);
+ jniSets.add(jniSet);
+ }
+
+ // variant specific overrides all
+ if (mVariantSourceProvider != null) {
+ Collection<File> variantJniDirs = mVariantSourceProvider.getJniLibsDirectories();
+ jniSet = new AssetSet(getFullName());
+ jniSet.addSources(variantJniDirs);
+ jniSets.add(jniSet);
+ }
+
+ return jniSets;
+ }
+
+ public int getRenderscriptTarget() {
+ ProductFlavor mergedFlavor = getMergedFlavor();
+
+ int targetApi = mergedFlavor.getRenderscriptTargetApi() != null ?
+ mergedFlavor.getRenderscriptTargetApi() : -1;
+ ApiVersion apiVersion = getMinSdkVersion();
+ int minSdk = apiVersion.getApiLevel();
+ if (apiVersion.getCodename() != null) {
+ minSdk = SdkVersionInfo.getApiByBuildCode(apiVersion.getCodename(), true);
+ }
+
+ return targetApi > minSdk ? targetApi : minSdk;
+ }
+
+ /**
+ * Returns all the renderscript import folder that are outside of the current project.
+ */
+ @NonNull
+ public List<File> getRenderscriptImports() {
+ List<File> list = Lists.newArrayList();
+
+ for (LibraryDependency lib : mFlatLibraries) {
+ File rsLib = lib.getRenderscriptFolder();
+ if (rsLib.isDirectory()) {
+ list.add(rsLib);
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns all the renderscript source folder from the main config, the flavors and the
+ * build type.
+ *
+ * @return a list of folders.
+ */
+ @NonNull
+ public List<File> getRenderscriptSourceList() {
+ List<SourceProvider> providers = getSortedSourceProviders();
+
+ List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
+
+ for (SourceProvider provider : providers) {
+ sourceList.addAll(provider.getRenderscriptDirectories());
+ }
+
+ return sourceList;
+ }
+
+ /**
+ * Returns all the aidl import folder that are outside of the current project.
+ */
+ @NonNull
+ public List<File> getAidlImports() {
+ List<File> list = Lists.newArrayList();
+
+ for (LibraryDependency lib : mFlatLibraries) {
+ File aidlLib = lib.getAidlFolder();
+ if (aidlLib.isDirectory()) {
+ list.add(aidlLib);
+ }
+ }
+
+ return list;
+ }
+
+ @NonNull
+ public List<File> getAidlSourceList() {
+ List<SourceProvider> providers = getSortedSourceProviders();
+
+ List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
+
+ for (SourceProvider provider : providers) {
+ sourceList.addAll(provider.getAidlDirectories());
+ }
+
+ return sourceList;
+ }
+
+ @NonNull
+ public List<File> getJniSourceList() {
+ List<SourceProvider> providers = getSortedSourceProviders();
+
+ List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size());
+
+ for (SourceProvider provider : providers) {
+ sourceList.addAll(provider.getCDirectories());
+ }
+
+ return sourceList;
+ }
+
+ /**
+ * Returns the compile classpath for this config. If the config tests a library, this
+ * will include the classpath of the tested config
+ *
+ * @return a non null, but possibly empty set.
+ */
+ @NonNull
+ public Set<File> getCompileClasspath() {
+ Set<File> classpath = Sets.newHashSetWithExpectedSize(
+ mJarDependencies.size() + mLocalJars.size() + mFlatLibraries.size());
+
+ for (LibraryDependency lib : mFlatLibraries) {
+ classpath.add(lib.getJarFile());
+ for (File jarFile : lib.getLocalJars()) {
+ classpath.add(jarFile);
+ }
+ }
+
+ for (JarDependency jar : mJarDependencies) {
+ if (jar.isCompiled()) {
+ classpath.add(jar.getJarFile());
+ }
+ }
+
+ for (JarDependency jar : mLocalJars) {
+ if (jar.isCompiled()) {
+ classpath.add(jar.getJarFile());
+ }
+ }
+
+ return classpath;
+ }
+
+ /**
+ * Returns the list of packaged jars for this config. If the config tests a library, this
+ * will include the jars of the tested config
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public Set<File> getAllPackagedJars() {
+ Set<File> jars = Sets.newHashSetWithExpectedSize(
+ mJarDependencies.size() + mLocalJars.size() + mFlatLibraries.size());
+
+ for (JarDependency jar : mJarDependencies) {
+ File jarFile = jar.getJarFile();
+ if (jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+
+ jars.addAll(getLocalPackagedJars());
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ if (!libraryDependency.isOptional()) {
+ File libJar = libraryDependency.getJarFile();
+ if (libJar.exists()) {
+ jars.add(libJar);
+ }
+ for (File jarFile : libraryDependency.getLocalJars()) {
+ if (jarFile.isFile()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+ }
+
+ return jars;
+ }
+
+ /**
+ * Returns the list of packaged jars for this config that is not coming from subprojects..
+ *
+ * If the config tests a library, this will include the jars of the tested config
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public ImmutableSet<File> getExternalPackagedJars() {
+ ImmutableSet.Builder<File> jars = ImmutableSet.builder();
+
+ for (JarDependency jar : mJarDependencies) {
+ if (jar.getProjectPath() == null) {
+ File jarFile = jar.getJarFile();
+ if (jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ // only take the external android libraries.
+ if (!libraryDependency.isOptional() && libraryDependency.getProject() == null) {
+ File libJar = libraryDependency.getJarFile();
+ if (libJar.exists()) {
+ jars.add(libJar);
+ }
+
+ // also grab the local jars
+ for (File jarFile : libraryDependency.getLocalJars()) {
+ if (jarFile.isFile()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+ }
+
+ return jars.build();
+ }
+
+ /**
+ * Returns the list of external packaged jars for this config.
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public ImmutableSet<File> getExternalPackagedJniJars() {
+ ImmutableSet.Builder<File> jars = ImmutableSet.builder();
+
+ for (JarDependency jar : mJarDependencies) {
+ if (jar.getProjectPath() == null) {
+ File jarFile = jar.getJarFile();
+ if (jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+
+ return jars.build();
+ }
+
+ /**
+ * Returns the packaged local Jars
+ *
+ * @return a non null, but possibly empty immutable set.
+ */
+ @NonNull
+ public ImmutableSet<File> getLocalPackagedJars() {
+ ImmutableSet.Builder<File> jars = ImmutableSet.builder();
+
+ // For tests of library projects, the local jars are showing both in
+ // the tested library bundle and in the test variant. This removes
+ // them from the test variant where they don't belong anyway.
+ Set<File> testedlocalJars = null;
+ if (mTestedConfig != null && mTestedConfig.getType() == VariantType.LIBRARY) {
+ testedlocalJars = mTestedConfig.getLocalPackagedJars();
+ }
+
+ for (JarDependency jar : mLocalJars) {
+ File jarFile = jar.getJarFile();
+ if (testedlocalJars == null || !testedlocalJars.contains(jarFile)) {
+ if (jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+
+ return jars.build();
+ }
+
+ /**
+ * Returns the packaged sub-project Jars, coming from Android or Java modules.
+ *
+ * @return a non null, but possibly empty immutable set.
+ */
+ @NonNull
+ public ImmutableSet<File> getSubProjectPackagedJars() {
+ ImmutableSet.Builder<File> jars = ImmutableSet.builder();
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ // only take the sub-project android libraries.
+ if (!libraryDependency.isOptional() && libraryDependency.getProject() != null) {
+ File libJar = libraryDependency.getJarFile();
+ if (libJar.exists()) {
+ jars.add(libJar);
+ }
+ }
+ }
+
+ for (JarDependency jarDependency : mJarDependencies) {
+ if (jarDependency.getProjectPath() != null) {
+ File jarFile = jarDependency.getJarFile();
+ if (jarDependency.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+
+ return jars.build();
+ }
+
+ /**
+ * Returns the packaged sub-project local Jars
+ *
+ * @return a non null, but possibly empty immutable set.
+ */
+ @NonNull
+ public ImmutableSet<File> getSubProjectLocalPackagedJars() {
+ ImmutableSet.Builder<File> jars = ImmutableSet.builder();
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ // only take the sub-project android libraries.
+ if (!libraryDependency.isOptional() && libraryDependency.getProject() != null) {
+ for (File jarFile : libraryDependency.getLocalJars()) {
+ if (jarFile.isFile()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+ }
+
+ return jars.build();
+ }
+
+ /**
+ * Returns the sub-project jni libs
+ *
+ * @return a non null, but possibly empty immutable set.
+ */
+ @NonNull
+ public ImmutableSet<File> getSubProjectJniLibFolders() {
+ ImmutableSet.Builder<File> jniDirectories = ImmutableSet.builder();
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ // only take the sub-project android libraries.
+ if (!libraryDependency.isOptional() && libraryDependency.getProject() != null) {
+ File jniDir = libraryDependency.getJniFolder();
+ if (jniDir.exists()) {
+ jniDirectories.add(jniDir);
+ }
+ }
+ }
+
+ return jniDirectories.build();
+ }
+
+ /**
+ * Returns the list of sub-project packaged jars for this config.
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public ImmutableSet<File> getSubProjectPackagedJniJars() {
+ ImmutableSet.Builder<File> jars = ImmutableSet.builder();
+
+ for (JarDependency jar : mJarDependencies) {
+ if (jar.getProjectPath() != null) {
+ File jarFile = jar.getJarFile();
+ if (jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+
+ return jars.build();
+ }
+
+ /**
+ * Returns the external jni lib folders
+ *
+ * @return a non null, but possibly empty immutable set.
+ */
+ @NonNull
+ public ImmutableSet<File> getExternalAarJniLibFolders() {
+ ImmutableSet.Builder<File> jniDirectories = ImmutableSet.builder();
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ // only take the external android libraries.
+ if (!libraryDependency.isOptional() && libraryDependency.getProject() == null) {
+ File jniDir = libraryDependency.getJniFolder();
+ if (jniDir.exists()) {
+ jniDirectories.add(jniDir);
+ }
+ }
+ }
+
+ return jniDirectories.build();
+ }
+
+ /**
+ * Returns the list of provided-only jars for this config.
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public List<File> getProvidedOnlyJars() {
+ Set<File> jars = Sets.newHashSetWithExpectedSize(
+ mJarDependencies.size() + mLocalJars.size() + mFlatLibraries.size());
+
+ for (JarDependency jar : mJarDependencies) {
+ File jarFile = jar.getJarFile();
+ if (jar.isCompiled() && !jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+
+ for (JarDependency jar : mLocalJars) {
+ File jarFile = jar.getJarFile();
+ if (jar.isCompiled() && !jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ if (libraryDependency.isOptional()) {
+ File libJar = libraryDependency.getJarFile();
+ if (libJar.exists()) {
+ jars.add(libJar);
+ }
+ for (File jarFile : libraryDependency.getLocalJars()) {
+ if (jarFile.isFile()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+ }
+
+ return Lists.newArrayList(jars);
+ }
+
+ @Nullable
+ public String resolveLibraryName(@NonNull File jarFile) {
+
+ for (JarDependency jar : mJarDependencies) {
+ if (jarFile.equals(jar.getJarFile())) {
+ if (jar.getResolvedCoordinates() != null) {
+ return jar.getResolvedCoordinates().toString();
+ }
+
+ return "unresolved-ext-jar-" + jarFile.getName() + "-" + jarFile.getPath().hashCode();
+ }
+ }
+
+ for (JarDependency jar : mLocalJars) {
+ if (jarFile.equals(jar.getJarFile())) {
+ return "local-jar-" + jarFile.getName() + "-" + jarFile.getPath().hashCode();
+ }
+ }
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ if (jarFile.equals(libraryDependency.getJarFile())) {
+ if (libraryDependency.getResolvedCoordinates() != null) {
+ return libraryDependency.getResolvedCoordinates().toString();
+ }
+
+ return "unresolved-lib-" + jarFile.getName() + "-" + jarFile.getPath().hashCode();
+ }
+
+ for (File localjar : libraryDependency.getLocalJars()) {
+ if (jarFile.equals(localjar)) {
+ if (libraryDependency.getResolvedCoordinates() != null) {
+ return libraryDependency.getResolvedCoordinates().toString() + ":" + jarFile.getName();
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Adds a variant-specific BuildConfig field.
+ * @param type the type of the field
+ * @param name the name of the field
+ * @param value the value of the field
+ */
+ public void addBuildConfigField(@NonNull String type, @NonNull String name, @NonNull String value) {
+ ClassField classField = AndroidBuilder.createClassField(type, name, value);
+ mBuildConfigFields.put(name, classField);
+ }
+
+ /**
+ * Adds a variant-specific res value.
+ * @param type the type of the field
+ * @param name the name of the field
+ * @param value the value of the field
+ */
+ public void addResValue(@NonNull String type, @NonNull String name, @NonNull String value) {
+ ClassField classField = AndroidBuilder.createClassField(type, name, value);
+ mResValues.put(name, classField);
+ }
+
+ /**
+ * Returns a list of items for the BuildConfig class.
+ *
+ * Items can be either fields (instance of {@link com.android.builder.model.ClassField})
+ * or comments (instance of String).
+ *
+ * @return a list of items.
+ */
+ @NonNull
+ public List<Object> getBuildConfigItems() {
+ List<Object> fullList = Lists.newArrayList();
+
+ // keep track of the names already added. This is because we show where the items
+ // come from so we cannot just put everything a map and let the new ones override the
+ // old ones.
+ Set<String> usedFieldNames = Sets.newHashSet();
+
+ Collection<ClassField> list = mBuildConfigFields.values();
+ if (!list.isEmpty()) {
+ fullList.add("Fields from the variant");
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+
+ list = mBuildType.getBuildConfigFields().values();
+ if (!list.isEmpty()) {
+ fullList.add("Fields from build type: " + mBuildType.getName());
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+
+ for (F flavor : mFlavors) {
+ list = flavor.getBuildConfigFields().values();
+ if (!list.isEmpty()) {
+ fullList.add("Fields from product flavor: " + flavor.getName());
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+ }
+
+ list = mDefaultConfig.getBuildConfigFields().values();
+ if (!list.isEmpty()) {
+ fullList.add("Fields from default config.");
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+
+ return fullList;
+ }
+
+ /**
+ * Return the merged build config fields for the variant.
+ *
+ * This is made of of the variant-specific fields overlayed on top of the build type ones,
+ * the flavors ones, and the default config ones.
+ *
+ * @return a map of merged fields
+ */
+ @NonNull
+ public Map<String, ClassField> getMergedBuildConfigFields() {
+ Map<String, ClassField> mergedMap = Maps.newHashMap();
+
+ // start from the lowest priority and just add it all. Higher priority fields
+ // will replace lower priority ones.
+
+ mergedMap.putAll(mDefaultConfig.getBuildConfigFields());
+ for (int i = mFlavors.size() - 1; i >= 0 ; i--) {
+ mergedMap.putAll(mFlavors.get(i).getBuildConfigFields());
+ }
+
+ mergedMap.putAll(mBuildType.getBuildConfigFields());
+ mergedMap.putAll(mBuildConfigFields);
+
+ return mergedMap;
+ }
+
+ /**
+ * Return the merged res values for the variant.
+ *
+ * This is made of of the variant-specific fields overlayed on top of the build type ones,
+ * the flavors ones, and the default config ones.
+ *
+ * @return a map of merged fields
+ */
+ @NonNull
+ public Map<String, ClassField> getMergedResValues() {
+ Map<String, ClassField> mergedMap = Maps.newHashMap();
+
+ // start from the lowest priority and just add it all. Higher priority fields
+ // will replace lower priority ones.
+
+ mergedMap.putAll(mDefaultConfig.getResValues());
+ for (int i = mFlavors.size() - 1; i >= 0 ; i--) {
+ mergedMap.putAll(mFlavors.get(i).getResValues());
+ }
+
+ mergedMap.putAll(mBuildType.getResValues());
+ mergedMap.putAll(mResValues);
+
+ return mergedMap;
+ }
+
+ /**
+ * Fills a list of Object from a given list of ClassField only if the name isn't in a set.
+ * Each new item added adds its name to the list.
+ * @param outList the out list
+ * @param usedFieldNames the list of field names already in the list
+ * @param list the list to copy items from
+ */
+ private static void fillFieldList(
+ @NonNull List<Object> outList,
+ @NonNull Set<String> usedFieldNames,
+ @NonNull Collection<ClassField> list) {
+ for (ClassField f : list) {
+ String name = f.getName();
+ if (!usedFieldNames.contains(name)) {
+ usedFieldNames.add(f.getName());
+ outList.add(f);
+ }
+ }
+ }
+
+ /**
+ * Returns a list of generated resource values.
+ *
+ * Items can be either fields (instance of {@link com.android.builder.model.ClassField})
+ * or comments (instance of String).
+ *
+ * @return a list of items.
+ */
+ @NonNull
+ public List<Object> getResValues() {
+ List<Object> fullList = Lists.newArrayList();
+
+ // keep track of the names already added. This is because we show where the items
+ // come from so we cannot just put everything a map and let the new ones override the
+ // old ones.
+ Set<String> usedFieldNames = Sets.newHashSet();
+
+ Collection<ClassField> list = mResValues.values();
+ if (!list.isEmpty()) {
+ fullList.add("Values from the variant");
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+
+ list = mBuildType.getResValues().values();
+ if (!list.isEmpty()) {
+ fullList.add("Values from build type: " + mBuildType.getName());
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+
+ for (F flavor : mFlavors) {
+ list = flavor.getResValues().values();
+ if (!list.isEmpty()) {
+ fullList.add("Values from product flavor: " + flavor.getName());
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+ }
+
+ list = mDefaultConfig.getResValues().values();
+ if (!list.isEmpty()) {
+ fullList.add("Values from default config.");
+ fillFieldList(fullList, usedFieldNames, list);
+ }
+
+ return fullList;
+ }
+
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ if (mSigningConfigOverride != null) {
+ return mSigningConfigOverride;
+ }
+
+ SigningConfig signingConfig = mBuildType.getSigningConfig();
+ if (signingConfig != null) {
+ return signingConfig;
+ }
+ return mMergedFlavor.getSigningConfig();
+ }
+
+ public boolean isSigningReady() {
+ SigningConfig signingConfig = getSigningConfig();
+ return signingConfig != null && signingConfig.isSigningReady();
+ }
+
+ /**
+ * Returns the proguard config files coming from the project but also from the dependencies.
+ *
+ * Note that if the method is set to include config files coming from libraries, they will
+ * only be included if the aars have already been unzipped.
+ *
+ * @param includeLibraries whether to include the library dependencies.
+ * @return a non null list of proguard files.
+ */
+ @NonNull
+ public Set<File> getProguardFiles(boolean includeLibraries, List<File> defaultProguardConfig) {
+ Set<File> fullList = Sets.newHashSet();
+
+ // add the config files from the build type, main config and flavors
+ fullList.addAll(mDefaultConfig.getProguardFiles());
+ fullList.addAll(mBuildType.getProguardFiles());
+
+ for (F flavor : mFlavors) {
+ fullList.addAll(flavor.getProguardFiles());
+ }
+
+ if (fullList.isEmpty()) {
+ fullList.addAll(defaultProguardConfig);
+ }
+
+ // now add the one coming from the library dependencies
+ if (includeLibraries) {
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ File proguardRules = libraryDependency.getProguardRules();
+ if (proguardRules.exists()) {
+ fullList.add(proguardRules);
+ }
+ }
+ }
+
+ return fullList;
+ }
+
+ /**
+ * Returns the proguard config files to be used for the test APK.
+ */
+ @NonNull
+ public Set<File> getTestProguardFiles() {
+ Set<File> fullList = Sets.newHashSet();
+
+ // add the config files from the build type, main config and flavors
+ fullList.addAll(mDefaultConfig.getTestProguardFiles());
+ fullList.addAll(mBuildType.getTestProguardFiles());
+
+ for (F flavor : mFlavors) {
+ fullList.addAll(flavor.getTestProguardFiles());
+ }
+
+ return fullList;
+ }
+
+ @NonNull
+ public List<Object> getConsumerProguardFiles() {
+ List<Object> fullList = Lists.newArrayList();
+
+ // add the config files from the build type, main config and flavors
+ fullList.addAll(mDefaultConfig.getConsumerProguardFiles());
+ fullList.addAll(mBuildType.getConsumerProguardFiles());
+
+ for (F flavor : mFlavors) {
+ fullList.addAll(flavor.getConsumerProguardFiles());
+ }
+
+ return fullList;
+ }
+
+ public boolean isTestCoverageEnabled() {
+ return mBuildType.isTestCoverageEnabled();
+ }
+
+ /**
+ * Returns the merged manifest placeholders. All product flavors are merged first, then build
+ * type specific placeholders are added and potentially overrides product flavors values.
+ * @return the merged manifest placeholders for a build variant.
+ */
+ @NonNull
+ public Map<String, Object> getManifestPlaceholders() {
+ Map<String, Object> mergedFlavorsPlaceholders = mMergedFlavor.getManifestPlaceholders();
+ // so far, blindly override the build type placeholders
+ mergedFlavorsPlaceholders.putAll(mBuildType.getManifestPlaceholders());
+ return mergedFlavorsPlaceholders;
+ }
+
+ public boolean isMultiDexEnabled() {
+ Boolean value = mBuildType.getMultiDexEnabled();
+ if (value != null) {
+ return value;
+ }
+
+ value = mMergedFlavor.getMultiDexEnabled();
+ if (value != null) {
+ return value;
+ }
+
+ return false;
+ }
+
+ public File getMultiDexKeepFile() {
+ File value = mBuildType.getMultiDexKeepFile();
+ if (value != null) {
+ return value;
+ }
+
+ value = mMergedFlavor.getMultiDexKeepFile();
+ if (value != null) {
+ return value;
+ }
+
+ return null;
+ }
+
+ public File getMultiDexKeepProguard() {
+ File value = mBuildType.getMultiDexKeepProguard();
+ if (value != null) {
+ return value;
+ }
+
+ value = mMergedFlavor.getMultiDexKeepProguard();
+ if (value != null) {
+ return value;
+ }
+
+ return null;
+ }
+
+ public boolean isLegacyMultiDexMode() {
+ return isMultiDexEnabled() && getMinSdkVersion().getApiLevel() < 21;
+ }
+
+ /**
+ * Returns the renderscript support mode.
+ */
+ public boolean getRenderscriptSupportModeEnabled() {
+ Boolean value = mMergedFlavor.getRenderscriptSupportModeEnabled();
+ if (value != null) {
+ return value;
+ }
+
+ // default is false.
+ return false;
+ }
+
+ /**
+ * Returns the renderscript NDK mode.
+ */
+ public boolean getRenderscriptNdkModeEnabled() {
+ Boolean value = mMergedFlavor.getRenderscriptNdkModeEnabled();
+ if (value != null) {
+ return value;
+ }
+
+ // default is false.
+ return false;
+ }
+
+ @NonNull
+ public Collection<File> getJarJarRuleFiles() {
+ ImmutableList.Builder<File> jarjarRuleFiles = ImmutableList.builder();
+ jarjarRuleFiles.addAll(getMergedFlavor().getJarJarRuleFiles());
+ jarjarRuleFiles.addAll(mBuildType.getJarJarRuleFiles());
+ return jarjarRuleFiles.build();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/core/VariantType.java b/build-system/builder/src/main/java/com/android/builder/core/VariantType.java
new file mode 100644
index 0000000..5eb9c4c
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/core/VariantType.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ArtifactMetaData;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Type of a variant.
+ */
+public enum VariantType {
+ DEFAULT(false),
+ LIBRARY(true),
+ ANDROID_TEST(
+ "androidTest",
+ "AndroidTest",
+ true,
+ AndroidProject.ARTIFACT_ANDROID_TEST,
+ ArtifactMetaData.TYPE_ANDROID),
+ UNIT_TEST(
+ "test",
+ "UnitTest",
+ false,
+ AndroidProject.ARTIFACT_UNIT_TEST,
+ ArtifactMetaData.TYPE_JAVA),
+ ;
+
+ public static ImmutableList<VariantType> getTestingTypes() {
+ ImmutableList.Builder<VariantType> result = ImmutableList.builder();
+ for (VariantType variantType : values()) {
+ if (variantType.isForTesting()) {
+ result.add(variantType);
+ }
+ }
+ return result.build();
+ }
+
+ private final boolean mIsForTesting;
+ private final String mPrefix;
+ private final String mSuffix;
+ private final boolean isSingleBuildType;
+ private final String mArtifactName;
+ private final int mArtifactType;
+ private final boolean exportsDataBindingClassList;
+
+ /** App or library variant. */
+ VariantType(boolean exportsDataBindingClassList) {
+ this.mIsForTesting = false;
+ this.mPrefix = "";
+ this.mSuffix = "";
+ this.mArtifactName = AndroidProject.ARTIFACT_MAIN;
+ this.mArtifactType = ArtifactMetaData.TYPE_ANDROID;
+ this.isSingleBuildType = false;
+ this.exportsDataBindingClassList = exportsDataBindingClassList;
+ }
+
+ /** Testing variant. */
+ VariantType(
+ String prefix,
+ String suffix,
+ boolean isSingleBuildType,
+ String artifactName,
+ int artifactType) {
+ this.mArtifactName = artifactName;
+ this.mArtifactType = artifactType;
+ this.mIsForTesting = true;
+ this.mPrefix = prefix;
+ this.mSuffix = suffix;
+ this.isSingleBuildType = isSingleBuildType;
+ this.exportsDataBindingClassList = false;
+ }
+
+ /**
+ * Returns true if the variant is automatically generated for testing purposed, false
+ * otherwise.
+ */
+ public boolean isForTesting() {
+ return mIsForTesting;
+ }
+
+ /**
+ * Returns prefix used for naming source directories. This is an empty string in
+ * case of non-testing variants and a camel case string otherwise, e.g. "androidTest".
+ */
+ @NonNull
+ public String getPrefix() {
+ return mPrefix;
+ }
+
+ /**
+ * Returns suffix used for naming Gradle tasks. This is an empty string in
+ * case of non-testing variants and a camel case string otherwise, e.g. "AndroidTest".
+ */
+ @NonNull
+ public String getSuffix() {
+ return mSuffix;
+ }
+
+ /**
+ * Returns the name used in the builder model for artifacts that correspond to this variant
+ * type.
+ */
+ @NonNull
+ public String getArtifactName() {
+ return mArtifactName;
+ }
+
+ /**
+ * Returns the artifact type used in the builder model.
+ */
+ public int getArtifactType() {
+ return mArtifactType;
+ }
+
+ /**
+ * Whether the artifact type supports only a single build type.
+ */
+ public boolean isSingleBuildType() {
+ return isSingleBuildType;
+ }
+
+ /**
+ * Whether the artifact type should export the data binding class list.
+ */
+ public boolean isExportDataBindingClassList() {
+ return exportsDataBindingClassList;
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/dependency/DependencyContainer.java b/build-system/builder/src/main/java/com/android/builder/dependency/DependencyContainer.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/dependency/DependencyContainer.java
rename to build-system/builder/src/main/java/com/android/builder/dependency/DependencyContainer.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/dependency/JarDependency.java b/build-system/builder/src/main/java/com/android/builder/dependency/JarDependency.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/dependency/JarDependency.java
rename to build-system/builder/src/main/java/com/android/builder/dependency/JarDependency.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/dependency/LibraryBundle.java b/build-system/builder/src/main/java/com/android/builder/dependency/LibraryBundle.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/dependency/LibraryBundle.java
rename to build-system/builder/src/main/java/com/android/builder/dependency/LibraryBundle.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/dependency/LibraryDependency.java b/build-system/builder/src/main/java/com/android/builder/dependency/LibraryDependency.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/dependency/LibraryDependency.java
rename to build-system/builder/src/main/java/com/android/builder/dependency/LibraryDependency.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/dependency/ManifestDependency.java b/build-system/builder/src/main/java/com/android/builder/dependency/ManifestDependency.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/dependency/ManifestDependency.java
rename to build-system/builder/src/main/java/com/android/builder/dependency/ManifestDependency.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/dependency/ManifestProvider.java b/build-system/builder/src/main/java/com/android/builder/dependency/ManifestProvider.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/dependency/ManifestProvider.java
rename to build-system/builder/src/main/java/com/android/builder/dependency/ManifestProvider.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/dependency/SymbolFileProvider.java b/build-system/builder/src/main/java/com/android/builder/dependency/SymbolFileProvider.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/dependency/SymbolFileProvider.java
rename to build-system/builder/src/main/java/com/android/builder/dependency/SymbolFileProvider.java
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java b/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java
new file mode 100644
index 0000000..d328e91
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.BaseConfig;
+import com.android.builder.model.ClassField;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An object that contain a BuildConfig configuration
+ */
+public abstract class BaseConfigImpl implements Serializable, BaseConfig {
+ private static final long serialVersionUID = 1L;
+
+ private String mApplicationIdSuffix = null;
+ private final Map<String, ClassField> mBuildConfigFields = Maps.newTreeMap();
+ private final Map<String, ClassField> mResValues = Maps.newTreeMap();
+ private final List<File> mProguardFiles = Lists.newArrayList();
+ private final List<File> mConsumerProguardFiles = Lists.newArrayList();
+ private final List<File> mTestProguardFiles = Lists.newArrayList();
+ private final Map<String, Object> mManifestPlaceholders = Maps.newHashMap();
+ @Nullable
+ private Boolean mMultiDexEnabled;
+
+ @Nullable
+ private File mMultiDexKeepProguard;
+
+ @Nullable
+ private File mMultiDexKeepFile;
+
+ @NonNull
+ private List<File> mJarJarRuleFiles = Lists.newArrayList();
+
+ /**
+ * Application id suffix applied to this base config.
+ */
+ @NonNull
+ public BaseConfigImpl setApplicationIdSuffix(@Nullable String applicationIdSuffix) {
+ mApplicationIdSuffix = applicationIdSuffix;
+ return this;
+ }
+
+ /**
+ * Application id suffix applied to this base config.
+ */
+ @Override
+ @Nullable
+ public String getApplicationIdSuffix() {
+ return mApplicationIdSuffix;
+ }
+
+ /**
+ * Adds a BuildConfig field.
+ */
+ public void addBuildConfigField(@NonNull ClassField field) {
+ mBuildConfigFields.put(field.getName(), field);
+ }
+
+ /**
+ * Adds a generated resource value.
+ */
+ public void addResValue(@NonNull ClassField field) { mResValues.put(field.getName(), field);
+ }
+
+ /**
+ * Adds a generated resource value.
+ */
+ public void addResValues(@NonNull Map<String, ClassField> values) {
+ mResValues.putAll(values);
+ }
+
+ /**
+ * Returns the BuildConfig fields.
+ */
+ @Override
+ @NonNull
+ public Map<String, ClassField> getBuildConfigFields() {
+ return mBuildConfigFields;
+ }
+
+ /**
+ * Adds BuildConfig fields.
+ */
+ public void addBuildConfigFields(@NonNull Map<String, ClassField> fields) {
+ mBuildConfigFields.putAll(fields);
+ }
+
+ /**
+ * Returns the generated resource values.
+ */
+ @NonNull
+ @Override
+ public Map<String, ClassField> getResValues() {
+ return mResValues;
+ }
+
+ /**
+ * Returns ProGuard configuration files to be used.
+ *
+ * <p>There are 2 default rules files
+ * <ul>
+ * <li>proguard-android.txt
+ * <li>proguard-android-optimize.txt
+ * </ul>
+ * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
+ * full path to the files. They are identical except for enabling optimizations.
+ *
+ * <p>See similarly named methods to specify the files.
+ */
+ @Override
+ @NonNull
+ public List<File> getProguardFiles() {
+ return mProguardFiles;
+ }
+
+ /**
+ * ProGuard rule files to be included in the published AAR.
+ *
+ * <p>These proguard rule files will then be used by any application project that consumes the
+ * AAR (if ProGuard is enabled).
+ *
+ * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
+ *
+ * <p>This is only valid for Library project. This is ignored in Application project.
+ */
+ @Override
+ @NonNull
+ public List<File> getConsumerProguardFiles() {
+ return mConsumerProguardFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getTestProguardFiles() {
+ return mTestProguardFiles;
+ }
+
+ /**
+ * Returns the manifest placeholders.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger#TOC-Placeholder-support">
+ * Manifest merger</a>.
+ */
+ @NonNull
+ @Override
+ public Map<String, Object> getManifestPlaceholders() {
+ return mManifestPlaceholders;
+ }
+
+ /**
+ * Adds manifest placeholders.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger#TOC-Placeholder-support">
+ * Manifest merger</a>.
+ */
+ public void addManifestPlaceholders(@NonNull Map<String, Object> manifestPlaceholders) {
+ mManifestPlaceholders.putAll(manifestPlaceholders);
+ }
+
+ /**
+ * Sets a new set of manifest placeholders.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger#TOC-Placeholder-support">
+ * Manifest merger</a>.
+ */
+ public void setManifestPlaceholders(@NonNull Map<String, Object> manifestPlaceholders) {
+ mManifestPlaceholders.clear();
+ this.mManifestPlaceholders.putAll(manifestPlaceholders);
+ }
+
+ protected void _initWith(@NonNull BaseConfig that) {
+ setBuildConfigFields(that.getBuildConfigFields());
+ setResValues(that.getResValues());
+
+ mApplicationIdSuffix = that.getApplicationIdSuffix();
+
+ mProguardFiles.clear();
+ mProguardFiles.addAll(that.getProguardFiles());
+
+ mConsumerProguardFiles.clear();
+ mConsumerProguardFiles.addAll(that.getConsumerProguardFiles());
+
+ mTestProguardFiles.clear();
+ mTestProguardFiles.addAll(that.getTestProguardFiles());
+
+ mManifestPlaceholders.clear();
+ mManifestPlaceholders.putAll(that.getManifestPlaceholders());
+
+ mMultiDexEnabled = that.getMultiDexEnabled();
+
+ mMultiDexKeepFile = that.getMultiDexKeepFile();
+ mMultiDexKeepProguard = that.getMultiDexKeepProguard();
+
+ mJarJarRuleFiles = that.getJarJarRuleFiles();
+ }
+
+ private void setBuildConfigFields(@NonNull Map<String, ClassField> fields) {
+ mBuildConfigFields.clear();
+ mBuildConfigFields.putAll(fields);
+ }
+
+ private void setResValues(@NonNull Map<String, ClassField> fields) {
+ mResValues.clear();
+ mResValues.putAll(fields);
+ }
+
+ /**
+ * Whether Multi-Dex is enabled for this variant.
+ */
+ @Override
+ @Nullable
+ public Boolean getMultiDexEnabled() {
+ return mMultiDexEnabled;
+ }
+
+ public void setMultiDexEnabled(@Nullable Boolean multiDex) {
+ mMultiDexEnabled = multiDex;
+ }
+
+ @Override
+ @Nullable
+ public File getMultiDexKeepFile() {
+ return mMultiDexKeepFile;
+ }
+
+ public void setMultiDexKeepFile(@Nullable File file) {
+ mMultiDexKeepFile = file;
+ }
+
+ @Override
+ @Nullable
+ public File getMultiDexKeepProguard() {
+ return mMultiDexKeepProguard;
+ }
+
+ public void setMultiDexKeepProguard(@Nullable File file) {
+ mMultiDexKeepProguard = file;
+ }
+
+ public void setJarJarRuleFiles(@NonNull List<File> files) {
+ mJarJarRuleFiles = files;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJarJarRuleFiles() {
+ return mJarJarRuleFiles;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof BaseConfigImpl)) {
+ return false;
+ }
+
+ BaseConfigImpl that = (BaseConfigImpl) o;
+
+ return Objects.equal(mApplicationIdSuffix, that.mApplicationIdSuffix) &&
+ Objects.equal(mBuildConfigFields, that.mBuildConfigFields) &&
+ Objects.equal(mConsumerProguardFiles, that.mConsumerProguardFiles) &&
+ Objects.equal(mManifestPlaceholders, that.mManifestPlaceholders) &&
+ Objects.equal(mMultiDexEnabled, that.mMultiDexEnabled) &&
+ Objects.equal(mMultiDexKeepFile, that.mMultiDexKeepFile) &&
+ Objects.equal(mMultiDexKeepProguard, that.mMultiDexKeepProguard) &&
+ Objects.equal(mProguardFiles, that.mProguardFiles) &&
+ Objects.equal(mResValues, that.mResValues) &&
+ Objects.equal(mJarJarRuleFiles, that.mJarJarRuleFiles);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(
+ mApplicationIdSuffix,
+ mBuildConfigFields,
+ mResValues,
+ mProguardFiles,
+ mConsumerProguardFiles,
+ mManifestPlaceholders,
+ mMultiDexEnabled,
+ mMultiDexKeepFile,
+ mMultiDexKeepProguard,
+ mJarJarRuleFiles);
+ }
+
+ @Override
+ public String toString() {
+ return "BaseConfigImpl{" +
+ "applicationIdSuffix=" + mApplicationIdSuffix +
+ ", mBuildConfigFields=" + mBuildConfigFields +
+ ", mResValues=" + mResValues +
+ ", mProguardFiles=" + mProguardFiles +
+ ", mConsumerProguardFiles=" + mConsumerProguardFiles +
+ ", mManifestPlaceholders=" + mManifestPlaceholders +
+ ", mMultiDexEnabled=" + mMultiDexEnabled +
+ ", mMultiDexKeepFile=" + mMultiDexKeepFile +
+ ", mMultiDexKeepProguard=" + mMultiDexKeepProguard +
+ ", mJarJarRuleFiles=" + mJarJarRuleFiles +
+ '}';
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/ClassFieldImpl.java b/build-system/builder/src/main/java/com/android/builder/internal/ClassFieldImpl.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/ClassFieldImpl.java
rename to build-system/builder/src/main/java/com/android/builder/internal/ClassFieldImpl.java
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java b/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java
new file mode 100644
index 0000000..7210ae8
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.utils.SparseArray;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Fake IAndroidTarget used for SDK prebuilts in the Android source tree.
+ */
+public class FakeAndroidTarget implements IAndroidTarget {
+ private final String mSdkLocation;
+ private final SparseArray<String> mPaths = new SparseArray<String>();
+ private final List<String> mBootClasspath = Lists.newArrayListWithExpectedSize(2);
+ private final int mApiLevel;
+
+ public FakeAndroidTarget(String sdkLocation, String target) {
+ mSdkLocation = sdkLocation;
+ mApiLevel = getApiLevel(target);
+
+ if ("unstubbed".equals(target)) {
+ mBootClasspath.add(mSdkLocation +
+ "/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar");
+ mBootClasspath.add(mSdkLocation +
+ "/out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar");
+
+ // pre-build the path to the platform components
+ mPaths.put(ANDROID_JAR, mSdkLocation + "/prebuilts/sdk/current/" +
+ SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(ANDROID_AIDL, mSdkLocation + "/prebuilts/sdk/renderscript/" +
+ SdkConstants.FN_FRAMEWORK_AIDL);
+ } else {
+ String apiPrebuilts;
+
+ if ("current".equals(target)) {
+ apiPrebuilts = mSdkLocation + "/prebuilts/sdk/current/";
+ } else {
+ apiPrebuilts = mSdkLocation + "/prebuilts/sdk/" + Integer.toString(mApiLevel) + "/";
+ }
+
+ // pre-build the path to the platform components
+ mBootClasspath.add(apiPrebuilts + SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(ANDROID_JAR, apiPrebuilts + SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(ANDROID_AIDL, apiPrebuilts + SdkConstants.FN_FRAMEWORK_AIDL);
+ }
+ }
+
+ private int getApiLevel(String target) {
+ if (target.startsWith("android-")) {
+ return Integer.parseInt(target.substring("android-".length()));
+ }
+
+ // We don't actually know the API level at this point since the mode is "current"
+ // or "unstubbed". This API is only called to check if annotations.jar needs to be
+ // added to the classpath, so by putting a large value we make sure annotations.jar
+ // isn't used.
+ return 99;
+ }
+
+ @Override
+ public String getPath(int pathId) {
+ return mPaths.get(pathId);
+ }
+
+ @Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ // this is not used internally since we properly query for the right Build Tools from
+ // the SdkManager.
+ return null;
+ }
+
+ @Override @NonNull
+ public List<String> getBootClasspath() {
+ return mBootClasspath;
+ }
+
+ @Override
+ public String getLocation() {
+ return mSdkLocation;
+ }
+
+ @Override
+ public String getVendor() {
+ return "android";
+ }
+
+ @Override
+ public String getName() {
+ return "android";
+ }
+
+ @Override
+ public String getFullName() {
+ return "android";
+ }
+
+ @Override
+ public String getClasspathName() {
+ return "android";
+ }
+
+ @Override
+ public String getShortClasspathName() {
+ return "android";
+ }
+
+ @Override
+ public String getDescription() {
+ return "android";
+ }
+
+ @NonNull
+ @Override
+ public AndroidVersion getVersion() {
+ return new AndroidVersion(mApiLevel, null);
+ }
+
+ @Override
+ public String getVersionName() {
+ return "Android API level " + mApiLevel;
+ }
+
+ @Override
+ public int getRevision() {
+ return 1;
+ }
+
+ @Override
+ public boolean isPlatform() {
+ return true;
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return null;
+ }
+
+ @Override
+ public boolean hasRenderingLibrary() {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public File[] getSkins() {
+ return new File[0];
+ }
+
+ @Override
+ public File getDefaultSkin() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getAdditionalLibraries() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getOptionalLibraries() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public String[] getPlatformLibraries() {
+ return new String[0];
+ }
+
+ @Override
+ public String getProperty(String name) {
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return null;
+ }
+
+ @Override
+ public boolean canRunOn(IAndroidTarget target) {
+ return false;
+ }
+
+ @Override
+ public String hashString() {
+ return "android-" + mApiLevel;
+ }
+
+ @Override
+ public int compareTo(IAndroidTarget iAndroidTarget) {
+ FakeAndroidTarget that = (FakeAndroidTarget) iAndroidTarget;
+ return mSdkLocation.compareTo(that.mSdkLocation);
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/InstallUtils.java b/build-system/builder/src/main/java/com/android/builder/internal/InstallUtils.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/InstallUtils.java
rename to build-system/builder/src/main/java/com/android/builder/internal/InstallUtils.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/SymbolLoader.java b/build-system/builder/src/main/java/com/android/builder/internal/SymbolLoader.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/SymbolLoader.java
rename to build-system/builder/src/main/java/com/android/builder/internal/SymbolLoader.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java b/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java
rename to build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/TemplateProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/TemplateProcessor.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/TemplateProcessor.java
rename to build-system/builder/src/main/java/com/android/builder/internal/TemplateProcessor.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/TestManifestGenerator.java b/build-system/builder/src/main/java/com/android/builder/internal/TestManifestGenerator.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/TestManifestGenerator.java
rename to build-system/builder/src/main/java/com/android/builder/internal/TestManifestGenerator.java
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java
new file mode 100644
index 0000000..4ab1b21
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.compiling.DependencyFileProcessor;
+import com.android.builder.internal.incremental.DependencyData;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.ide.common.process.ProcessResult;
+import com.android.repository.io.FileOpUtils;
+import com.android.utils.FileUtils;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A Source File processor for AIDL files. This compiles each aidl file found by the SourceSearcher.
+ */
+public class AidlProcessor implements SourceSearcher.SourceFileProcessor {
+
+ @NonNull
+ private final String mAidlExecutable;
+ @NonNull
+ private final String mFrameworkLocation;
+ @NonNull
+ private final List<File> mImportFolders;
+ @NonNull
+ private final File mSourceOutputDir;
+ @Nullable
+ private final File mPackagedOutputDir;
+ @Nullable
+ private Collection<String> mPackageWhiteList;
+ @NonNull
+ private final DependencyFileProcessor mDependencyFileProcessor;
+ @NonNull
+ private final ProcessExecutor mProcessExecutor;
+ @NonNull
+ private final ProcessOutputHandler mProcessOutputHandler;
+
+ public AidlProcessor(
+ @NonNull String aidlExecutable,
+ @NonNull String frameworkLocation,
+ @NonNull List<File> importFolders,
+ @NonNull File sourceOutputDir,
+ @Nullable File packagedOutputDir,
+ @Nullable Collection<String> packageWhiteList,
+ @NonNull DependencyFileProcessor dependencyFileProcessor,
+ @NonNull ProcessExecutor processExecutor,
+ @NonNull ProcessOutputHandler processOutputHandler) {
+ mAidlExecutable = aidlExecutable;
+ mFrameworkLocation = frameworkLocation;
+ mImportFolders = importFolders;
+ mSourceOutputDir = sourceOutputDir;
+ mPackagedOutputDir = packagedOutputDir;
+ mPackageWhiteList = packageWhiteList;
+ mDependencyFileProcessor = dependencyFileProcessor;
+ mProcessExecutor = processExecutor;
+ mProcessOutputHandler = processOutputHandler;
+ }
+
+ @Override
+ public void processFile(@NonNull File sourceFolder, @NonNull File sourceFile)
+ throws ProcessException, IOException {
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+
+ builder.setExecutable(mAidlExecutable);
+
+ builder.addArgs("-p" + mFrameworkLocation);
+ builder.addArgs("-o" + mSourceOutputDir.getAbsolutePath());
+
+ // add all the library aidl folders to access parcelables that are in libraries
+ for (File f : mImportFolders) {
+ builder.addArgs("-I" + f.getAbsolutePath());
+ }
+
+ // create a temp file for the dependency
+ File depFile = File.createTempFile("aidl", ".d");
+ builder.addArgs("-d" + depFile.getAbsolutePath());
+
+ builder.addArgs(sourceFile.getAbsolutePath());
+
+ ProcessResult result = mProcessExecutor.execute(
+ builder.createProcess(), mProcessOutputHandler);
+ result.rethrowFailure().assertNormalExitValue();
+
+ // send the dependency file to the processor.
+ DependencyData data = mDependencyFileProcessor.processFile(depFile);
+
+ if (mPackagedOutputDir != null && data != null) {
+
+ boolean isParcelable = data.getOutputFiles().isEmpty();
+
+ String relative = FileOpUtils
+ .makeRelative(sourceFolder, sourceFile, FileOpUtils.create());
+ boolean isWhiteListed =
+ mPackageWhiteList != null && mPackageWhiteList.contains(relative);
+
+ if (isParcelable || isWhiteListed) {
+ // looks like a parcelable or is white-listed.
+ // Store it in the 2ndary output of the DependencyData object.
+
+ File destFile = new File(mPackagedOutputDir, relative);
+ //noinspection ResultOfMethodCallIgnored
+ destFile.getParentFile().mkdirs();
+ Files.copy(sourceFile, destFile);
+ data.addSecondaryOutputFile(destFile.getPath());
+ }
+ }
+
+ FileUtils.delete(depFile);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexKey.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexKey.java
new file mode 100644
index 0000000..5ad6cb0
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexKey.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.android.repository.Revision;
+
+import java.io.File;
+
+/**
+ * Key to store Item/StoredItem in maps.
+ * The key contains the element that are used for the dex call:
+ * - source file
+ * - build tools revision
+ * - jumbo mode
+ */
+ at Immutable
+class DexKey extends PreProcessCache.Key {
+ private final boolean mJumboMode;
+
+ static DexKey of(@NonNull File sourceFile, @NonNull Revision buildToolsRevision,
+ boolean jumboMode) {
+ return new DexKey(sourceFile, buildToolsRevision, jumboMode);
+ }
+
+ private DexKey(@NonNull File sourceFile, @NonNull Revision buildToolsRevision,
+ boolean jumboMode) {
+ super(sourceFile, buildToolsRevision);
+ mJumboMode = jumboMode;
+ }
+
+ boolean isJumboMode() {
+ return mJumboMode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof DexKey)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ DexKey dexKey = (DexKey) o;
+
+ if (mJumboMode != dexKey.mJumboMode) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (mJumboMode ? 1 : 0);
+ return result;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexWrapper.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexWrapper.java
new file mode 100644
index 0000000..97d3aab
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/DexWrapper.java
@@ -0,0 +1,406 @@
+/*
+ * 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.builder.internal.compiler;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.builder.core.DexOptions;
+import com.android.builder.core.DexProcessBuilder;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutput;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.ide.common.process.ProcessResult;
+import com.android.utils.ILogger;
+import com.google.common.base.Objects;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Throwables;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Closer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper to access dx.jar through reflection.
+ *
+ * <p/>Since there is no proper api to call the method in the dex library, this wrapper is going to
+ * access it through reflection.
+ */
+public class DexWrapper {
+
+ private static final LoadingCache<File, LinkedBlockingDeque<DexWrapper>> CACHE;
+
+ private static final String DEX_MAIN = "com.android.dx.command.dexer.Main";
+
+ private static final String DEX_CONSOLE = "com.android.dx.command.DxConsole";
+
+ private static final String DEX_ARGS = "com.android.dx.command.dexer.Main$Arguments";
+
+ private static final String MAIN_RUN = "run";
+
+ private Constructor<?> mArgConstructor;
+
+ private Field mAddToDexFutures;
+
+ private Field mArgFileNames;
+
+ private Field mArgJarOutput;
+
+ private Field mArgOutName;
+
+ private Field mArgVerbose;
+
+ private Field mClassesInMainDex;
+
+ private Field mConsoleErr;
+
+ private Field mConsoleOut;
+
+ private Field mDexOutputArrays;
+
+ private Field mDexOutputFutures;
+
+ private Field mForceJumbo;
+
+ private Field mHumanOutWriter;
+
+ private Field mMainDexListFile;
+
+ private Field mMaxFieldIdsInProcess;
+
+ private Field mMaxMethodIdsInProcess;
+
+ private Field mMinimumFileAge;
+
+ private Field mMultiDex;
+
+ private Field mNumThreads;
+
+ private Field mOptimize;
+
+ private File mDexJar;
+
+ private Method mClearList;
+
+ private Method mRunMethod;
+
+ private Method mSetOut;
+
+ static {
+ CACHE = CacheBuilder.newBuilder()
+ .expireAfterWrite(3, TimeUnit.HOURS)
+ .build(new CacheLoader<File, LinkedBlockingDeque<DexWrapper>>() {
+ @Override
+ public LinkedBlockingDeque<DexWrapper> load(@NonNull File jarFile)
+ throws Exception {
+ int poolSize = Integer.getInteger("android.dexerPoolSize", 4);
+
+ LinkedBlockingDeque<DexWrapper> deque =
+ new LinkedBlockingDeque<DexWrapper>(poolSize);
+
+ for (int i = 0; i < poolSize; i++) {
+ deque.push(new DexWrapper(jarFile));
+ }
+
+ return deque;
+ }
+ });
+ }
+
+ private Class<?> mMainClass;
+
+ /**
+ * Returns true if the dx.jar main class can be loaded from this class' classloader, false
+ * otherwise.
+ */
+ public static boolean noMainDexOnClasspath() {
+ try {
+ DexWrapper.class.getClassLoader().loadClass(DEX_MAIN);
+ } catch (ClassNotFoundException e) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get an instance of {@link DexWrapper} for the given dx.jar file.
+ *
+ * <p>Can block if the whole dexer pool is in use.
+ */
+ public static DexWrapper obtain(File jarFile) {
+ try {
+ return CACHE.get(jarFile).takeFirst();
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Releases the given {@link DexWrapper} back to the pool, so that other threads can use it.
+ */
+ public void release() {
+ try {
+ CACHE.get(mDexJar).putFirst(this);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private DexWrapper(File jarFile) {
+ mDexJar = jarFile;
+ }
+
+ /**
+ * Loads the dex library from a file path.
+ *
+ * @param dxJarFile the location of the dx.jar file.
+ */
+ private void loadDex(@NonNull File dxJarFile, @NonNull ILogger logger) {
+ logger.info("Loading jar into dexer: %s", mDexJar.getAbsolutePath());
+
+ try {
+ if (!dxJarFile.isFile()) {
+ throw new RuntimeException("dx.jar not found at : " + dxJarFile);
+ }
+ URL url = dxJarFile.toURI().toURL();
+ @SuppressWarnings("resource")
+ URLClassLoader loader = new URLClassLoader(new URL[]{url},
+ DexWrapper.class.getClassLoader());
+ // get the classes.
+ mMainClass = loader.loadClass(DEX_MAIN);
+ Class<?> consoleClass = loader.loadClass(DEX_CONSOLE);
+ Class<?> argClass = loader.loadClass(DEX_ARGS);
+ Class<?> systemClass = loader.loadClass("java.lang.System");
+ Class<?> listClass = loader.loadClass("java.util.List");
+
+ // Now get the fields/methods we need:
+ mArgConstructor = argClass.getConstructor();
+
+ mRunMethod = mMainClass.getMethod(MAIN_RUN, argClass);
+ mSetOut = systemClass.getMethod("setOut", loader.loadClass("java.io.PrintStream"));
+ mClearList = listClass.getMethod("clear");
+
+ mArgOutName = argClass.getField("outName");
+ mArgJarOutput = argClass.getField("jarOutput");
+ mArgFileNames = argClass.getField("fileNames");
+ mArgVerbose = argClass.getField("verbose");
+ mOptimize = argClass.getField("optimize");
+ mMultiDex = argClass.getField("multiDex");
+ mForceJumbo = argClass.getField("forceJumbo");
+ mMainDexListFile = argClass.getField("mainDexListFile");
+ mNumThreads = argClass.getField("numThreads");
+
+ mConsoleOut = consoleClass.getField("out");
+ mConsoleErr = consoleClass.getField("err");
+
+ mAddToDexFutures = getPrivateStaticField("addToDexFutures");
+ mClassesInMainDex = getPrivateStaticField("classesInMainDex");
+ mDexOutputArrays = getPrivateStaticField("dexOutputArrays");
+ mDexOutputFutures = getPrivateStaticField("dexOutputFutures");
+ mHumanOutWriter = getPrivateStaticField("humanOutWriter");
+ mMaxFieldIdsInProcess = getPrivateStaticField("maxFieldIdsInProcess");
+ mMaxMethodIdsInProcess = getPrivateStaticField("maxMethodIdsInProcess");
+ mMinimumFileAge = getPrivateStaticField("minimumFileAge");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Field getPrivateStaticField(String addToDexFutures) throws NoSuchFieldException {
+ Field declaredField = mMainClass.getDeclaredField(addToDexFutures);
+ declaredField.setAccessible(true);
+ return declaredField;
+ }
+
+ /**
+ * Runs the dex command. The wrapper must have been initialized via {@link #loadDex(File,
+ * ILogger)} first.
+ *
+ * @return the integer return code of com.android.dx.command.dexer.Main.run()
+ */
+ public synchronized ProcessResult run(
+ @NonNull DexProcessBuilder processBuilder,
+ @NonNull DexOptions dexOptions,
+ @NonNull ProcessOutputHandler outputHandler,
+ @NonNull ILogger logger) throws IOException, ProcessException {
+ if (mRunMethod == null) {
+ loadDex(mDexJar, logger);
+ }
+
+ assert mArgOutName != null;
+ assert mArgJarOutput != null;
+ assert mArgFileNames != null;
+ assert mArgVerbose != null;
+ assert mConsoleOut != null;
+ assert mConsoleErr != null;
+
+ ProcessOutput processOutput = outputHandler.createOutput();
+
+ Closer closer = Closer.create();
+ try {
+ PrintStream err = closer.register(new PrintStream(processOutput.getErrorOutput()));
+ PrintStream out = closer.register(new PrintStream(processOutput.getStandardOutput()));
+
+ // Set the streams.
+ mConsoleErr.set(null /* obj: static field */, err);
+ mConsoleOut.set(null /* obj: static field */, out);
+ mSetOut.invoke(null /* obj: static field */, out);
+
+ // Create the Arguments object.
+ Object args = mArgConstructor.newInstance();
+ setOutput(args, processBuilder);
+ setInputs(args, processBuilder);
+ setOtherOptions(args, processBuilder, dexOptions);
+
+ clearState();
+
+ // Call the run method.
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ Object res = mRunMethod.invoke(null /* obj: static method */, args);
+ stopwatch.stop();
+
+ logger.info(
+ "Dexing %s in-process: %s",
+ processBuilder.getOutputFile().getPath(),
+ stopwatch);
+
+ if (res instanceof Integer) {
+ return new DexProcessResult((Integer) res);
+ }
+ throw new ProcessException("Dex returned value of unknown type: " + res);
+ } catch (InvocationTargetException e) {
+ String exceptionMessage = e.getTargetException().getMessage();
+ logger.error(null /* throwable */, "Exception while dexing files: " + exceptionMessage);
+ throw Throwables.propagate(e.getTargetException());
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ } finally {
+ closer.close();
+ outputHandler.handleOutput(processOutput);
+ }
+ }
+
+ private static class DexProcessResult implements ProcessResult {
+
+ private int mExitValue;
+
+ public DexProcessResult(int exitValue) {
+ mExitValue = exitValue;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult assertNormalExitValue()
+ throws ProcessException {
+ if (mExitValue != 0) {
+ throw new ProcessException(
+ String.format("Return code %d for dex process", mExitValue));
+ }
+
+ return this;
+ }
+
+ @Override
+ public int getExitValue() {
+ return mExitValue;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult rethrowFailure()
+ throws ProcessException {
+ return assertNormalExitValue();
+ }
+ }
+
+ /**
+ * Clears all state stored in static fields.
+ */
+ private void clearState() throws IllegalAccessException, InvocationTargetException {
+ mClearList.invoke(mAddToDexFutures.get(null));
+ mClearList.invoke(mDexOutputFutures.get(null));
+ mMaxMethodIdsInProcess.set(null, 0);
+ mMaxFieldIdsInProcess.set(null, 0);
+ mMinimumFileAge.set(null, 0);
+ mClassesInMainDex.set(null, null);
+ mClearList.invoke(mDexOutputArrays.get(null));
+ mHumanOutWriter.set(null, null);
+ }
+
+ private void setInputs(@NonNull Object args, @NonNull DexProcessBuilder processBuilder)
+ throws IllegalAccessException, ProcessException {
+ mArgFileNames.set(args, Iterables.toArray(processBuilder.getFilesToAdd(null), String.class));
+ }
+
+ private void setOutput(@NonNull Object args, @NonNull DexProcessBuilder processBuilder)
+ throws IllegalAccessException {
+ if (processBuilder.getOutputFile().isDirectory() && !processBuilder.isMultiDex()) {
+ mArgOutName.set(args, new File(processBuilder.getOutputFile(), "classes.dex").getPath());
+ mArgJarOutput.set(args, false);
+ } else {
+ String outputFileAbsolutePath = processBuilder.getOutputFile().getAbsolutePath();
+ mArgOutName.set(args, outputFileAbsolutePath);
+ mArgJarOutput.set(args, outputFileAbsolutePath.endsWith(SdkConstants.DOT_JAR));
+ }
+ }
+
+ private void setOtherOptions(
+ @NonNull Object args,
+ @NonNull DexProcessBuilder processBuilder,
+ @NonNull DexOptions dexOptions) throws IllegalAccessException {
+ mArgVerbose.set(args, processBuilder.isVerbose());
+ mOptimize.set(args, !processBuilder.isNoOptimize());
+ mMultiDex.set(args, processBuilder.isMultiDex());
+ if (processBuilder.getMainDexList() != null) {
+ mMainDexListFile.set(args, processBuilder.getMainDexList().getPath());
+ }
+
+ mNumThreads.set(args, Objects.firstNonNull(dexOptions.getThreadCount(), 4));
+ mForceJumbo.set(args, dexOptions.getJumboMode());
+
+ // TODO: remove it from DexProcessBuilder, it's never set to true?
+ checkArgument(!processBuilder.isIncremental(), "--incremental is not supported.");
+ }
+
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/FileGatherer.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/FileGatherer.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/compiler/FileGatherer.java
rename to build-system/builder/src/main/java/com/android/builder/internal/compiler/FileGatherer.java
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/JackConversionCache.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/JackConversionCache.java
new file mode 100644
index 0000000..cfe6422
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/JackConversionCache.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.android.annotations.NonNull;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.DexOptions;
+import com.android.ide.common.process.JavaProcessExecutor;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.sdklib.BuildToolInfo;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.google.common.io.Files;
+
+import org.w3c.dom.NamedNodeMap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Cache for jar -> jack conversion, using the Jill tool.
+ *
+ * Since we cannot yet have a single task for each library that needs to be run through Jill
+ * (because there is no task-level parallelization), this class allows reusing the output of
+ * the jill process for a library in a project in other projects.
+ *
+ * Because different project could use different build-tools, both the library to be converted
+ * and the version of the build tools are used as keys in the cache.
+ *
+ * The API is fairly simple, just call {@link #convertLibrary(File, File, DexOptions, BuildToolInfo, boolean, JavaProcessExecutor, ProcessOutputHandler)}
+ *
+ * The call will be blocking until the conversion happened, either through actually running Jill or
+ * through copying the output of a previous Jill run.
+ *
+ * After a build a call to {@link #clear(java.io.File, com.android.utils.ILogger)} with a file
+ * will allow saving the known converted libraries for future reuse.
+ */
+public class JackConversionCache extends PreProcessCache<PreProcessCache.Key> {
+
+ private static final JackConversionCache sSingleton = new JackConversionCache();
+
+ public static JackConversionCache getCache() {
+ return sSingleton;
+ }
+
+ @NonNull
+ @Override
+ protected KeyFactory<Key> getKeyFactory() {
+ return new KeyFactory<Key>() {
+ @Override
+ public Key of(@NonNull File sourceFile, @NonNull Revision revision,
+ @NonNull NamedNodeMap attrMap) {
+ return Key.of(sourceFile, revision);
+ }
+ };
+ }
+
+ /**
+ * Converts a given library to a given output with Jill, using a specific version of the
+ * build-tools.
+ *
+ * @param inputFile the jar to pre-dex
+ * @param outFile the output file.
+ * @param dexOptions the dex options to run pre-dex
+ * @param buildToolInfo the build tools info
+ * @param verbose verbose flag
+ * @param processExecutor the java process executor.
+ * @throws ProcessException
+ */
+ public void convertLibrary(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull DexOptions dexOptions,
+ @NonNull BuildToolInfo buildToolInfo,
+ boolean verbose,
+ @NonNull JavaProcessExecutor processExecutor,
+ @NonNull ProcessOutputHandler processOutputHandler,
+ @NonNull ILogger logger)
+ throws ProcessException, InterruptedException, IOException {
+
+ Key itemKey = Key.of(inputFile, buildToolInfo.getRevision());
+
+ Pair<PreProcessCache.Item, Boolean> pair = getItem(itemKey);
+ Item item = pair.getFirst();
+
+ // if this is a new item
+ if (pair.getSecond()) {
+ try {
+ // haven't process this file yet so do it and record it.
+ List<File> files = AndroidBuilder.convertLibaryToJackUsingApis(
+ inputFile,
+ outFile,
+ dexOptions,
+ buildToolInfo,
+ verbose,
+ processExecutor,
+ processOutputHandler,
+ logger);
+ item.getOutputFiles().addAll(files);
+
+ incrementMisses();
+ } catch (ProcessException exception) {
+ // in case of error, delete (now obsolete) output file
+ outFile.delete();
+ // and rethrow the error
+ throw exception;
+ } finally {
+ // enable other threads to use the output of this pre-dex.
+ // if something was thrown they'll handle the missing output file.
+ item.getLatch().countDown();
+ }
+ } else {
+ // wait until the file is pre-dexed by the first thread.
+ item.getLatch().await();
+
+ // check that the generated file actually exists
+ // while the api allow for 2+ files, there's only ever one in this case.
+ File fromFile = item.getOutputFiles().get(0);
+
+ if (fromFile.isFile()) {
+ // file already pre-dex, just copy the output.
+ // while the api allow for 2+ files, there's only ever one in this case.
+ Files.copy(fromFile, outFile);
+ incrementHits();
+ }
+ }
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/LeafFolderGatherer.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/LeafFolderGatherer.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/compiler/LeafFolderGatherer.java
rename to build-system/builder/src/main/java/com/android/builder/internal/compiler/LeafFolderGatherer.java
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java
new file mode 100644
index 0000000..18bf0de
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreDexCache.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.DexOptions;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.repository.Revision;
+import com.android.utils.FileUtils;
+import com.android.utils.Pair;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Pre Dexing cache.
+ *
+ * Since we cannot yet have a single task for each library that needs to be pre-dexed (because
+ * there is no task-level parallelization), this class allows reusing the output of the pre-dexing
+ * of a library in a project to write the output of the pre-dexing of the same library in
+ * a different project.
+ *
+ * Because different project could use different build-tools, both the library to pre-dex and the
+ * version of the build tools are used as keys in the cache.
+ *
+ * The API is fairly simple, just call {@link #preDexLibrary(AndroidBuilder, File, File, boolean, DexOptions, ProcessOutputHandler)}
+ *
+ * The call will be blocking until the pre-dexing happened, either through actual pre-dexing or
+ * through copying the output of a previous pre-dex run.
+ *
+ * After a build a call to {@link #clear(java.io.File, com.android.utils.ILogger)} with a file
+ * will allow saving the known pre-dexed libraries for future reuse.
+ */
+public class PreDexCache extends PreProcessCache<DexKey> {
+
+ private static final String ATTR_JUMBO_MODE = "jumboMode";
+
+ private static final PreDexCache sSingleton = new PreDexCache();
+
+ public static PreDexCache getCache() {
+ return sSingleton;
+ }
+
+ @Override
+ @NonNull
+ protected KeyFactory<DexKey> getKeyFactory() {
+ return new KeyFactory<DexKey>() {
+ @Override
+ public DexKey of(@NonNull File sourceFile, @NonNull Revision revision,
+ @NonNull NamedNodeMap attrMap) {
+ return DexKey.of(sourceFile, revision,
+ Boolean.parseBoolean(attrMap.getNamedItem(ATTR_JUMBO_MODE).getNodeValue()));
+ }
+ };
+ }
+
+ /**
+ * Pre-dex a given library to a given output with a specific version of the build-tools.
+ *
+ * @param builder {@link AndroidBuilder} instance used to dex the library
+ * @param inputFile the jar to pre-dex
+ * @param outFile the output file or folder (if multi-dex is enabled), must exist
+ * @param multiDex whether mutli-dex is enabled
+ * @param dexOptions the dex options to run pre-dex
+ * @throws IOException
+ * @throws ProcessException
+ * @throws InterruptedException
+ */
+ public void preDexLibrary(
+ @NonNull AndroidBuilder builder,
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ boolean multiDex,
+ @NonNull DexOptions dexOptions,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws IOException, ProcessException, InterruptedException {
+ checkState(!multiDex || outFile.isDirectory());
+ checkState(builder.getTargetInfo() != null);
+
+ DexKey itemKey = DexKey.of(
+ inputFile,
+ builder.getTargetInfo().getBuildTools().getRevision(),
+ dexOptions.getJumboMode());
+
+ Pair<Item, Boolean> pair = getItem(itemKey);
+ Item item = pair.getFirst();
+
+ // if this is a new item
+ if (pair.getSecond()) {
+ try {
+ // haven't process this file yet so do it and record it.
+ List<File> files = builder.preDexLibraryNoCache(
+ inputFile,
+ outFile,
+ multiDex,
+ dexOptions,
+ processOutputHandler);
+
+ item.getOutputFiles().clear();
+ item.getOutputFiles().addAll(files);
+
+ incrementMisses();
+ } catch (ProcessException exception) {
+ // in case of error, delete (now obsolete) output file
+ FileUtils.deleteIfExists(outFile);
+ // and rethrow the error
+ throw exception;
+ } finally {
+ // enable other threads to use the output of this pre-dex.
+ // if something was thrown they'll handle the missing output file.
+ item.getLatch().countDown();
+ }
+ } else {
+ // wait until the file is pre-dexed by the first thread.
+ item.getLatch().await();
+
+ // check that the generated file actually exists
+ if (item.areOutputFilesPresent()) {
+ if (multiDex) {
+ // output should be a folder
+ for (File sourceFile : item.getOutputFiles()) {
+ File destFile = new File(outFile, sourceFile.getName());
+ checkSame(sourceFile, destFile);
+ Files.copy(sourceFile, destFile);
+ }
+
+ } else {
+ // file already pre-dex, just copy the output.
+ if (item.getOutputFiles().isEmpty()) {
+ throw new RuntimeException(item.toString());
+ }
+ checkSame(item.getOutputFiles().get(0), outFile);
+ Files.copy(item.getOutputFiles().get(0), outFile);
+ }
+ incrementHits();
+
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ protected Node createItemNode(
+ @NonNull Document document,
+ @NonNull DexKey itemKey,
+ @NonNull BaseItem item) throws IOException {
+ Node itemNode = super.createItemNode(document, itemKey, item);
+
+ if (itemNode != null) {
+ Attr attr = document.createAttribute(ATTR_JUMBO_MODE);
+ attr.setValue(Boolean.toString(itemKey.isJumboMode()));
+ itemNode.getAttributes().setNamedItem(attr);
+ }
+
+ return itemNode;
+ }
+
+ private static void checkSame(@NonNull File source, @NonNull File dest) {
+ if (source.equals(dest)) {
+ Logger.getAnonymousLogger().info(
+ String.format("%s l:%d ts:%d", source, source.length(), source.lastModified()));
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreProcessCache.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreProcessCache.java
new file mode 100644
index 0000000..8b7f989
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/PreProcessCache.java
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.annotations.concurrency.Immutable;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ */
+public abstract class PreProcessCache<T extends PreProcessCache.Key> {
+
+ private static final String NODE_ITEMS = "items";
+ private static final String NODE_ITEM = "item";
+ private static final String NODE_DEX = "dex";
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_JAR = "jar";
+ private static final String ATTR_DEX = "dex";
+ private static final String ATTR_SHA1 = "sha1";
+ private static final String ATTR_REVISION = "revision";
+
+ private static final String XML_VERSION = "2";
+
+ protected interface BaseItem {
+ @NonNull
+ File getSourceFile();
+
+ @NonNull
+ List<File> getOutputFiles();
+
+ @Nullable
+ HashCode getSourceHash();
+
+ boolean areOutputFilesPresent();
+
+ }
+
+ /**
+ * Items representing jar/dex files that have been processed during a build.
+ */
+ @Immutable
+ protected static class Item implements BaseItem {
+ @NonNull
+ private final File mSourceFile;
+ @NonNull
+ private final List<File> mOutputFiles;
+ @NonNull
+ private final CountDownLatch mLatch;
+
+ Item(
+ @NonNull File sourceFile,
+ @NonNull List<File> outputFiles,
+ @NonNull CountDownLatch latch) {
+ mSourceFile = sourceFile;
+ mOutputFiles = Lists.newArrayList(outputFiles);
+ mLatch = latch;
+ }
+
+ Item(
+ @NonNull File sourceFile,
+ @NonNull CountDownLatch latch) {
+ mSourceFile = sourceFile;
+ mOutputFiles = Lists.newArrayList();
+ mLatch = latch;
+ }
+
+ @Override
+ @NonNull
+ public File getSourceFile() {
+ return mSourceFile;
+ }
+
+ @Override
+ @NonNull
+ public List<File> getOutputFiles() {
+ return mOutputFiles;
+ }
+
+ @Nullable
+ @Override
+ public HashCode getSourceHash() {
+ return null;
+ }
+
+ @NonNull
+ protected CountDownLatch getLatch() {
+ return mLatch;
+ }
+
+ @Override
+ public boolean areOutputFilesPresent() {
+ boolean filesOk = !mOutputFiles.isEmpty();
+ for (File outputFile : mOutputFiles) {
+ filesOk &= outputFile.isFile();
+ }
+ return filesOk;
+ }
+
+ @Override
+ public String toString() {
+ return "Item{" +
+ "mOutputFiles=" + mOutputFiles +
+ ", mSourceFile=" + mSourceFile +
+ '}';
+ }
+ }
+
+ /**
+ * Items representing jar/dex files that have been processed in a previous build, then were
+ * stored in a cache file and then reloaded during the current build.
+ */
+ @Immutable
+ protected static class StoredItem implements BaseItem {
+ @NonNull
+ private final File mSourceFile;
+ @NonNull
+ private final List<File> mOutputFiles;
+ @NonNull
+ private final HashCode mSourceHash;
+
+ StoredItem(
+ @NonNull File sourceFile,
+ @NonNull List<File> outputFiles,
+ @NonNull HashCode sourceHash) {
+ mSourceFile = sourceFile;
+ mOutputFiles = Lists.newArrayList(outputFiles);
+ mSourceHash = sourceHash;
+ }
+
+ @Override
+ @NonNull
+ public File getSourceFile() {
+ return mSourceFile;
+ }
+
+ @Override
+ @NonNull
+ public List<File> getOutputFiles() {
+ return mOutputFiles;
+ }
+
+ @Override
+ @NonNull
+ public HashCode getSourceHash() {
+ return mSourceHash;
+ }
+
+ @Override
+ public boolean areOutputFilesPresent() {
+ boolean filesOk = !mOutputFiles.isEmpty();
+ for (File outputFile : mOutputFiles) {
+ filesOk &= outputFile.isFile();
+ }
+ return filesOk;
+ }
+
+ @Override
+ public String toString() {
+ return "StoredItem{" +
+ "mSourceFile=" + mSourceFile +
+ ", mOutputFiles=" + mOutputFiles +
+ ", mSourceHash=" + mSourceHash +
+ '}';
+ }
+ }
+
+ /**
+ * Key to store Item/StoredItem in maps.
+ * The key contains the element that are used for the dex call:
+ * - source file
+ * - build tools revision
+ * - jumbo mode
+ */
+ @Immutable
+ protected static class Key {
+ @NonNull
+ private final File mSourceFile;
+ @NonNull
+ private final Revision mBuildToolsRevision;
+
+ public static Key of(@NonNull File sourceFile, @NonNull Revision buildToolsRevision) {
+ return new Key(sourceFile, buildToolsRevision);
+ }
+
+ protected Key(@NonNull File sourceFile, @NonNull Revision buildToolsRevision) {
+ mSourceFile = sourceFile;
+ mBuildToolsRevision = buildToolsRevision;
+ }
+
+ @NonNull
+ public Revision getBuildToolsRevision() {
+ return mBuildToolsRevision;
+ }
+
+ @NonNull
+ public File getSourceFile() {
+ return mSourceFile;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Key)) {
+ return false;
+ }
+
+ Key key = (Key) o;
+
+ if (!mBuildToolsRevision.equals(key.mBuildToolsRevision)) {
+ return false;
+ }
+ if (!mSourceFile.equals(key.mSourceFile)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mSourceFile, mBuildToolsRevision);
+ }
+ }
+
+ protected interface KeyFactory<T> {
+ T of(@NonNull File sourceFile, @NonNull Revision revision, @NonNull NamedNodeMap attrMap);
+ }
+
+ @GuardedBy("this")
+ private boolean mLoaded = false;
+
+ @GuardedBy("this")
+ private final Map<T, Item> mMap = Maps.newHashMap();
+ @GuardedBy("this")
+ private final Map<T, StoredItem> mStoredItems = Maps.newHashMap();
+
+ @GuardedBy("this")
+ private int mMisses = 0;
+ @GuardedBy("this")
+ private int mHits = 0;
+
+ @NonNull
+ protected abstract KeyFactory<T> getKeyFactory();
+
+ /**
+ * Loads the stored item. This can be called several times (per subproject), so only
+ * the first call should do something.
+ */
+ public synchronized void load(@NonNull File itemStorage) {
+ if (mLoaded) {
+ return;
+ }
+
+ loadItems(itemStorage);
+
+ mLoaded = true;
+ }
+
+ /**
+ * Returns an {@link Item} loaded from the cache. If no item can be found this, throws an
+ * exception.
+ *
+ * @param itemKey the key of the item
+ * @return a pair of item, boolean
+ */
+ protected synchronized Pair<Item, Boolean> getItem(@NonNull T itemKey) {
+
+ // get the item
+ Item item = mMap.get(itemKey);
+
+ boolean newItem = false;
+
+ if (item == null) {
+ // check if we have a stored version.
+ StoredItem storedItem = mStoredItems.get(itemKey);
+
+ File inputFile = itemKey.getSourceFile();
+
+ if (storedItem != null) {
+ // check the sha1 is still valid, and the pre-dex files are still there.
+ if (storedItem.areOutputFilesPresent() &&
+ storedItem.getSourceHash().equals(getHash(inputFile))) {
+
+ Logger.getAnonymousLogger().info("Cached result for getItem(" + inputFile + "): "
+ + storedItem.getOutputFiles());
+ for (File f : storedItem.getOutputFiles()) {
+ Logger.getAnonymousLogger().info(
+ String.format("%s l:%d ts:%d", f, f.length(), f.lastModified()));
+ }
+
+ // create an item where the outFile is the one stored since it
+ // represent the pre-dexed library already.
+ // Next time this lib needs to be pre-dexed, we'll use the item
+ // rather than the stored item, allowing us to not compute the sha1 again.
+ // Use a 0-count latch since there is nothing to do.
+ item = new Item(inputFile, storedItem.getOutputFiles(), new CountDownLatch(0));
+ }
+ }
+
+ // if we didn't find a valid stored item, create a new one.
+ if (item == null) {
+ item = new Item(inputFile, new CountDownLatch(1));
+ newItem = true;
+ }
+
+ mMap.put(itemKey, item);
+ }
+
+ return Pair.of(item, newItem);
+ }
+
+ @Nullable
+ private static HashCode getHash(@NonNull File file) {
+ try {
+ return Files.hash(file, Hashing.sha1());
+ } catch (IOException ignored) {
+ }
+
+ return null;
+ }
+
+ public synchronized void clear(@Nullable File itemStorage, @Nullable ILogger logger) throws
+ IOException {
+ if (!mMap.isEmpty()) {
+ if (itemStorage != null) {
+ saveItems(itemStorage);
+ }
+
+ if (logger != null) {
+ logger.info("PREDEX CACHE HITS: " + mHits);
+ logger.info("PREDEX CACHE MISSES: " + mMisses);
+ }
+ }
+
+ mMap.clear();
+ mStoredItems.clear();
+ mHits = 0;
+ mMisses = 0;
+ }
+
+ private synchronized void loadItems(@NonNull File itemStorage) {
+ if (!itemStorage.isFile()) {
+ return;
+ }
+
+ try {
+ Document document = XmlUtils.parseUtfXmlFile(itemStorage, true);
+
+ // get the root node
+ Node rootNode = document.getDocumentElement();
+ if (rootNode == null || !NODE_ITEMS.equals(rootNode.getLocalName())) {
+ return;
+ }
+
+
+ // check the version of the XML
+ NamedNodeMap rootAttrMap = rootNode.getAttributes();
+ Node versionAttr = rootAttrMap.getNamedItem(ATTR_VERSION);
+ if (versionAttr == null || !XML_VERSION.equals(versionAttr.getNodeValue())) {
+ return;
+ }
+
+ NodeList nodes = rootNode.getChildNodes();
+
+ for (int i = 0, n = nodes.getLength(); i < n; i++) {
+ Node node = nodes.item(i);
+
+ if (node.getNodeType() != Node.ELEMENT_NODE ||
+ !NODE_ITEM.equals(node.getLocalName())) {
+ continue;
+ }
+
+ NamedNodeMap attrMap = node.getAttributes();
+
+ File sourceFile = new File(attrMap.getNamedItem(ATTR_JAR).getNodeValue());
+ Revision revision = Revision.parseRevision(attrMap.getNamedItem(
+ ATTR_REVISION).getNodeValue());
+
+ List<File> outputFiles = Lists.newArrayList();
+ NodeList dexNodes = node.getChildNodes();
+ for (int j = 0, m = dexNodes.getLength(); j < m; j++) {
+ Node dexNode = dexNodes.item(j);
+
+ if (dexNode.getNodeType() != Node.ELEMENT_NODE ||
+ !NODE_DEX.equals(dexNode.getLocalName())) {
+ continue;
+ }
+
+ NamedNodeMap dexAttrMap = dexNode.getAttributes();
+ outputFiles.add(new File(dexAttrMap.getNamedItem(ATTR_DEX).getNodeValue()));
+ }
+
+ StoredItem item = new StoredItem(
+ sourceFile,
+ outputFiles,
+ HashCode.fromString(attrMap.getNamedItem(ATTR_SHA1).getNodeValue()));
+
+ T key = getKeyFactory().of(sourceFile, revision, attrMap);
+
+ mStoredItems.put(key, item);
+ }
+ } catch (Exception ignored) {
+ // if we fail to read parts or any of the file, all it'll do is fail to reuse an
+ // already pre-dexed library, so that's not a super big deal.
+ }
+ }
+
+ protected synchronized void saveItems(@NonNull File itemStorage) throws IOException {
+ // write "compact" blob
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder;
+
+ try {
+ builder = factory.newDocumentBuilder();
+ Document document = builder.newDocument();
+
+ Node rootNode = document.createElement(NODE_ITEMS);
+ document.appendChild(rootNode);
+
+ // Set the version
+ Attr attr = document.createAttribute(ATTR_VERSION);
+ attr.setValue(XML_VERSION);
+ rootNode.getAttributes().setNamedItem(attr);
+
+ Set<T> keys = Sets.newHashSetWithExpectedSize(mMap.size() + mStoredItems.size());
+ keys.addAll(mMap.keySet());
+ keys.addAll(mStoredItems.keySet());
+
+ for (T key : keys) {
+ Item item = mMap.get(key);
+
+ if (item != null) {
+
+ Node itemNode = createItemNode(document,
+ key,
+ item);
+ if (itemNode != null) {
+ rootNode.appendChild(itemNode);
+ }
+
+ } else {
+ StoredItem storedItem = mStoredItems.get(key);
+ // check that the source file still exists in order to avoid
+ // storing libraries that are gone.
+ if (storedItem != null &&
+ storedItem.getSourceFile().isFile() &&
+ storedItem.areOutputFilesPresent()) {
+ Node itemNode = createItemNode(document,
+ key,
+ storedItem);
+ if (itemNode != null) {
+ rootNode.appendChild(itemNode);
+ }
+ }
+ }
+ }
+
+ String content = XmlPrettyPrinter.prettyPrint(document, true);
+
+ itemStorage.getParentFile().mkdirs();
+ Files.write(content, itemStorage, Charsets.UTF_8);
+ } catch (ParserConfigurationException e) {
+ }
+ }
+
+ @Nullable
+ protected Node createItemNode(
+ @NonNull Document document,
+ @NonNull T itemKey,
+ @NonNull BaseItem item) throws IOException {
+ if (!item.areOutputFilesPresent()) {
+ return null;
+ }
+
+ Node itemNode = document.createElement(NODE_ITEM);
+
+ Attr attr = document.createAttribute(ATTR_JAR);
+ attr.setValue(item.getSourceFile().getPath());
+ itemNode.getAttributes().setNamedItem(attr);
+
+ attr = document.createAttribute(ATTR_REVISION);
+ attr.setValue(itemKey.getBuildToolsRevision().toString());
+ itemNode.getAttributes().setNamedItem(attr);
+
+ HashCode hashCode = item.getSourceHash();
+ if (hashCode == null) {
+ try {
+ hashCode = Files.hash(item.getSourceFile(), Hashing.sha1());
+ } catch (IOException ex) {
+ // If we can't compute the hash for whatever reason, simply skip this entry.
+ return null;
+ }
+ }
+ attr = document.createAttribute(ATTR_SHA1);
+ attr.setValue(hashCode.toString());
+ itemNode.getAttributes().setNamedItem(attr);
+
+ for (File dexFile : item.getOutputFiles()) {
+
+ Node dexNode = document.createElement(NODE_DEX);
+ itemNode.appendChild(dexNode);
+
+ attr = document.createAttribute(ATTR_DEX);
+ attr.setValue(dexFile.getPath());
+ dexNode.getAttributes().setNamedItem(attr);
+ }
+
+ return itemNode;
+ }
+
+ protected synchronized void incrementMisses() {
+ mMisses++;
+ }
+
+ protected synchronized void incrementHits() {
+ mHits++;
+ }
+
+ @VisibleForTesting
+ /*package*/ synchronized int getMisses() {
+ return mMisses;
+ }
+
+ @VisibleForTesting
+ /*package*/ synchronized int getHits() {
+ return mHits;
+ }
+
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/RenderScriptProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/RenderScriptProcessor.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/compiler/RenderScriptProcessor.java
rename to build-system/builder/src/main/java/com/android/builder/internal/compiler/RenderScriptProcessor.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/compiler/SourceSearcher.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/SourceSearcher.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/compiler/SourceSearcher.java
rename to build-system/builder/src/main/java/com/android/builder/internal/compiler/SourceSearcher.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyData.java b/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyData.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyData.java
rename to build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyData.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyDataStore.java b/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyDataStore.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyDataStore.java
rename to build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyDataStore.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/packaging/JavaResourceProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/JavaResourceProcessor.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/packaging/JavaResourceProcessor.java
rename to build-system/builder/src/main/java/com/android/builder/internal/packaging/JavaResourceProcessor.java
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java
new file mode 100644
index 0000000..e12c234
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java
@@ -0,0 +1,635 @@
+/*
+ * Copyright (C) 2010 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.builder.internal.packaging;
+
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.FN_APK_CLASSES_DEX;
+import static com.android.SdkConstants.FN_APK_CLASSES_N_DEX;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.packaging.JavaResourceProcessor.IArchiveBuilder;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.packaging.PackagerException;
+import com.android.builder.packaging.SealedPackageException;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter;
+import com.android.ide.common.signing.CertificateInfo;
+import com.android.utils.ILogger;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Closeables;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class making the final app package.
+ * The inputs are:
+ * - packaged resources (output of aapt)
+ * - code file (ouput of dx)
+ * - Java resources coming from the project, its libraries, and its jar files
+ * - Native libraries from the project or its library.
+ *
+ */
+public final class Packager implements IArchiveBuilder {
+
+ /**
+ * Filter to detect duplicate entries
+ *
+ */
+ private final class DuplicateZipFilter implements IZipEntryFilter {
+ private File mInputFile;
+
+ void reset(File inputFile) {
+ mInputFile = inputFile;
+ }
+
+ @Override
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ mLogger.verbose("=> %s", archivePath);
+
+ File duplicate = checkFileForDuplicate(archivePath);
+ if (duplicate != null) {
+ // we have a duplicate but it might be the same source file, in this case,
+ // we just ignore the duplicate, and of course, we don't add it again.
+ File potentialDuplicate = new File(mInputFile, archivePath);
+ if (!duplicate.getAbsolutePath().equals(potentialDuplicate.getAbsolutePath())) {
+ throw new DuplicateFileException(archivePath, duplicate, mInputFile);
+ }
+ return false;
+ } else {
+ mAddedFiles.put(archivePath, mInputFile);
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * A filter to filter out binary files like .class
+ */
+ private static final class NoJavaClassZipFilter implements IZipEntryFilter {
+ @NonNull
+ private final IZipEntryFilter parentFilter;
+
+ private NoJavaClassZipFilter(@NonNull IZipEntryFilter parentFilter) {
+ this.parentFilter = parentFilter;
+ }
+
+
+ @Override
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ return parentFilter.checkEntry(archivePath) && !archivePath.endsWith(DOT_CLASS);
+ }
+ }
+
+ /**
+ * A filter to filter out unwanted ABIs.
+ */
+ private static final class NativeLibZipFilter implements IZipEntryFilter {
+ @NonNull
+ private final IZipEntryFilter parentFilter;
+ @NonNull
+ private final Set<String> acceptedAbis;
+ private final boolean mJniDebugMode;
+
+ private final Pattern mAbiPattern = Pattern.compile("lib/([^/]+)/[^/]+");
+ private final Pattern mFilenamePattern = Pattern.compile(".*\\.so");
+
+ private NativeLibZipFilter(
+ @NonNull Set<String> acceptedAbis,
+ @NonNull IZipEntryFilter parentFilter,
+ boolean jniDebugMode) {
+ this.acceptedAbis = acceptedAbis;
+ this.parentFilter = parentFilter;
+ this.mJniDebugMode = jniDebugMode;
+ }
+
+ @Override
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ if (!parentFilter.checkEntry(archivePath)) {
+ return false;
+ }
+
+ // extract abi from path and convert.
+ Matcher m = mAbiPattern.matcher(archivePath);
+
+ // if the ABI is accepted, check the 3rd segment
+ if (m.matches() && (acceptedAbis.isEmpty() || acceptedAbis.contains(m.group(1)))) {
+ // remove the beginning of the path (lib/<abi>/)
+ String filename = archivePath.substring(5 + m.group(1).length());
+ return mFilenamePattern.matcher(filename).matches() ||
+ (mJniDebugMode &&
+ (SdkConstants.FN_GDBSERVER.equals(filename) ||
+ SdkConstants.FN_GDB_SETUP.equals(filename)));
+ }
+
+ return false;
+ }
+ }
+
+ private SignedJarBuilder mBuilder = null;
+ private final ILogger mLogger;
+ private boolean mJniDebugMode = false;
+ private boolean mIsSealed = false;
+
+ private final DuplicateZipFilter mNoDuplicateFilter = new DuplicateZipFilter();
+ private final NoJavaClassZipFilter mNoJavaClassZipFilter = new NoJavaClassZipFilter(mNoDuplicateFilter);
+ private final HashMap<String, File> mAddedFiles = new HashMap<String, File>();
+
+ /**
+ * Creates a new instance.
+ *
+ * This creates a new builder that will create the specified output file, using the two
+ * mandatory given input files.
+ *
+ * An optional debug keystore can be provided. If set, it is expected that the store password
+ * is 'android' and the key alias and password are 'androiddebugkey' and 'android'.
+ *
+ * An optional {@link ILogger} can also be provided for verbose output. If null, there will
+ * be no output.
+ *
+ * @param apkLocation the file to create
+ * @param resLocation the file representing the packaged resource file.
+ * @param certificateInfo the signing information used to sign the package. Optional the OS path to the debug keystore, if needed or null.
+ * @param logger the logger.
+ * @param minSdkVersion minSdkVersion of the package.
+ * @throws com.android.builder.packaging.PackagerException
+ */
+ public Packager(
+ @NonNull String apkLocation,
+ @Nullable String resLocation,
+ @Nullable CertificateInfo certificateInfo,
+ @Nullable String createdBy,
+ @NonNull ILogger logger,
+ int minSdkVersion) throws PackagerException {
+
+ try {
+ File apkFile = new File(apkLocation);
+ checkOutputFile(apkFile);
+
+ File resFile = null;
+ if (resLocation != null) {
+ resFile = new File(resLocation);
+ checkInputFile(resFile);
+ }
+
+ mLogger = logger;
+
+ mBuilder = new SignedJarBuilder(
+ new FileOutputStream(apkFile, false /* append */),
+ certificateInfo != null ? certificateInfo.getKey() : null,
+ certificateInfo != null ? certificateInfo.getCertificate() : null,
+ getLocalVersion(),
+ createdBy,
+ minSdkVersion);
+
+ mLogger.verbose("Packaging %s", apkFile.getName());
+
+ // add the resources
+ if (resFile != null) {
+ addZipFile(resFile);
+ }
+
+ } catch (PackagerException e) {
+ if (mBuilder != null) {
+ mBuilder.cleanUp();
+ }
+ throw e;
+ } catch (Exception e) {
+ if (mBuilder != null) {
+ mBuilder.cleanUp();
+ }
+ throw new PackagerException(e);
+ }
+ }
+
+ public void addDexFiles(@NonNull Set<File> dexFolders)
+ throws DuplicateFileException, SealedPackageException, PackagerException {
+ // If there is a single folder that's either no multi-dex or pre-21 multidex (where
+ // dx has merged them all into 2+ dex files).
+ // IF there are 2+ folders then we are directly adding the pre-dexing output.
+ if (dexFolders.size() == 1 ) {
+ File[] dexFiles = Iterables.getOnlyElement(dexFolders).listFiles(
+ new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+ return name.endsWith(SdkConstants.DOT_DEX);
+ }
+ });
+
+ if (dexFiles != null) {
+ for (File dexFile : dexFiles) {
+ addFile(dexFile, dexFile.getName());
+ }
+ }
+ } else {
+ // in 21+ mode we can simply include all the dex files, and rename them as we
+ // go so that their indices are contiguous.
+ int dexIndex = 1;
+ for (File folderEntry : dexFolders) {
+ dexIndex = addContentOfDexFolder(folderEntry, dexIndex);
+ }
+ }
+ }
+
+ private int addContentOfDexFolder(@NonNull File dexFolder, int dexIndex)
+ throws PackagerException, SealedPackageException, DuplicateFileException {
+ File[] dexFiles = dexFolder.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+ return name.endsWith(SdkConstants.DOT_DEX);
+ }
+ });
+
+ if (dexFiles != null) {
+ for (File dexFile : dexFiles) {
+ addFile(dexFile,
+ dexIndex == 1 ?
+ FN_APK_CLASSES_DEX :
+ String.format(FN_APK_CLASSES_N_DEX, dexIndex));
+ dexIndex++;
+ }
+ }
+
+ return dexIndex;
+ }
+
+
+ /**
+ * Sets the JNI debug mode. In debug mode, when native libraries are present, the packaging
+ * will also include one or more copies of gdbserver in the final APK file.
+ *
+ * These are used for debugging native code, to ensure that gdbserver is accessible to the
+ * application.
+ *
+ * There will be one version of gdbserver for each ABI supported by the application.
+ *
+ * the gbdserver files are placed in the libs/abi/ folders automatically by the NDK.
+ *
+ * @param jniDebugMode the jni-debug mode flag.
+ */
+ public void setJniDebugMode(boolean jniDebugMode) {
+ mJniDebugMode = jniDebugMode;
+ }
+
+ /**
+ * Adds a file to the APK at a given path
+ * @param file the file to add
+ * @param archivePath the path of the file inside the APK archive.
+ * @throws PackagerException if an error occurred
+ * @throws com.android.builder.packaging.SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ @Override
+ public void addFile(File file, String archivePath) throws PackagerException,
+ SealedPackageException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ try {
+ doAddFile(file, archivePath, null);
+ } catch (DuplicateFileException e) {
+ mBuilder.cleanUp();
+ throw e;
+ } catch (Exception e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", file);
+ }
+ }
+
+ /**
+ * Adds the content from a zip file.
+ * All file keep the same path inside the archive.
+ * @param zipFile the zip File.
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ void addZipFile(File zipFile) throws PackagerException, SealedPackageException,
+ DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ FileInputStream fis = null;
+ try {
+ mLogger.verbose("%s:", zipFile);
+
+ // reset the filter with this input.
+ mNoDuplicateFilter.reset(zipFile);
+
+ // ask the builder to add the content of the file.
+ fis = new FileInputStream(zipFile);
+ mBuilder.writeZip(fis, mNoDuplicateFilter, null /* ZipEntryExtractor */);
+ } catch (DuplicateFileException e) {
+ mBuilder.cleanUp();
+ throw e;
+ } catch (Exception e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", zipFile);
+ } finally {
+ try {
+ Closeables.close(fis, true /* swallowIOException */);
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Adds all resources from a merged folder or jar file. There cannot be any duplicates and all
+ * files present must be added unless it is a "binary" file like a .class or .dex (jack
+ * produces the classes.dex in the same location as the obfuscated resources).
+ * @param jarFileOrDirectory a jar file or directory reference.
+ * @throws PackagerException could not add an entry to the package.
+ * @throws DuplicateFileException if an entry with the same name was already present in the
+ * package being built while adding the jarFileOrDirectory content.
+ */
+ public void addResources(@NonNull File jarFileOrDirectory)
+ throws PackagerException, DuplicateFileException, SealedPackageException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ mNoDuplicateFilter.reset(jarFileOrDirectory);
+ InputStream fis = null;
+ try {
+ if (jarFileOrDirectory.isDirectory()) {
+ addResourcesFromDirectory(jarFileOrDirectory, "");
+ } else {
+ fis = new BufferedInputStream(new FileInputStream(jarFileOrDirectory));
+ mBuilder.writeZip(fis, mNoJavaClassZipFilter, null /* ZipEntryExtractor */);
+ }
+ } catch (DuplicateFileException e) {
+ mBuilder.cleanUp();
+ throw e;
+ } catch (Exception e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", jarFileOrDirectory);
+ } finally {
+ try {
+ if (fis != null) {
+ Closeables.close(fis, true /* swallowIOException */);
+ }
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+
+ private void addResourcesFromDirectory(@NonNull File directory, String path)
+ throws IOException, IZipEntryFilter.ZipAbortException {
+ File[] directoryFiles = directory.listFiles();
+ if (directoryFiles == null) {
+ return;
+ }
+ for (File file : directoryFiles) {
+ String entryName = path.isEmpty() ? file.getName() : path + "/" + file.getName();
+ if (file.isDirectory()) {
+ addResourcesFromDirectory(file, entryName);
+ } else {
+ doAddFile(file, entryName, null);
+ }
+ }
+ }
+
+ /**
+ * Adds the native libraries from a directory or jar file.
+ *
+ * The content must be the various ABI folders.
+ *
+ * This may or may not copy gdbserver into the apk based on whether the debug mode is set.
+ *
+ * @param jarFileOrDirectory a jar file or directory reference.
+ * @param abiFilters a list of abi filters to include. If empty, all abis are included.
+ *
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ *
+ * @see #setJniDebugMode(boolean)
+ */
+ public void addNativeLibraries(
+ @NonNull File jarFileOrDirectory,
+ @NonNull Set<String> abiFilters)
+ throws PackagerException, SealedPackageException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ mLogger.verbose("Native Libraries input: %s", jarFileOrDirectory);
+
+ NativeLibZipFilter filter = new NativeLibZipFilter(
+ abiFilters, mNoDuplicateFilter, mJniDebugMode);
+ mNoDuplicateFilter.reset(jarFileOrDirectory);
+
+ InputStream fis = null;
+ try {
+ if (jarFileOrDirectory.isDirectory()) {
+ addNativeLibrariesFromDirectory(jarFileOrDirectory, "", filter);
+ } else {
+ fis = new BufferedInputStream(new FileInputStream(jarFileOrDirectory));
+ mBuilder.writeZip(fis, filter, null /* ZipEntryExtractor */);
+ }
+ } catch (DuplicateFileException e) {
+ mBuilder.cleanUp();
+ throw e;
+ } catch (Exception e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", jarFileOrDirectory);
+ } finally {
+ try {
+ if (fis != null) {
+ Closeables.close(fis, true /* swallowIOException */);
+ }
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+
+ private void addNativeLibrariesFromDirectory(
+ @NonNull File directory,
+ @NonNull String path,
+ @NonNull NativeLibZipFilter zipFilter)
+ throws IOException, IZipEntryFilter.ZipAbortException {
+ File[] directoryFiles = directory.listFiles();
+ if (directoryFiles == null) {
+ return;
+ }
+ for (File file : directoryFiles) {
+ String entryName = path.isEmpty() ? file.getName() : path + "/" + file.getName();
+ if (file.isDirectory()) {
+ addNativeLibrariesFromDirectory(file, entryName, zipFilter);
+ } else {
+ doAddFile(file, entryName, zipFilter);
+ }
+ }
+ }
+
+ /**
+ * Seals the APK, and signs it if necessary.
+ *
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ */
+ public void sealApk() throws PackagerException, SealedPackageException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ // close and sign the application package.
+ try {
+ mBuilder.close();
+ mIsSealed = true;
+ } catch (Exception e) {
+ throw new PackagerException(e, "Failed to seal APK");
+ } finally {
+ mBuilder.cleanUp();
+ mAddedFiles.clear();
+ }
+ }
+
+ private void doAddFile(
+ @NonNull File file,
+ @NonNull String archivePath,
+ @Nullable IZipEntryFilter filter) throws IZipEntryFilter.ZipAbortException,
+ IOException {
+ if (filter == null) {
+ filter = mNoJavaClassZipFilter;
+ }
+
+ if (!filter.checkEntry(archivePath)) {
+ return;
+ }
+
+ mAddedFiles.put(archivePath, file);
+ mBuilder.writeFile(file, archivePath);
+ }
+
+ /**
+ * Checks if the given path in the APK archive has not already been used and if it has been,
+ * then returns a {@link File} object for the source of the duplicate
+ * @param archivePath the archive path to test.
+ * @return A File object of either a file at the same location or an archive that contains a
+ * file that was put at the same location.
+ */
+ private File checkFileForDuplicate(String archivePath) {
+ return mAddedFiles.get(archivePath);
+ }
+
+ /**
+ * Checks an output {@link File} object.
+ * This checks the following:
+ * - the file is not an existing directory.
+ * - if the file exists, that it can be modified.
+ * - if it doesn't exists, that a new file can be created.
+ * @param file the File to check
+ * @throws PackagerException If the check fails
+ */
+ private static void checkOutputFile(File file) throws PackagerException {
+ if (file.isDirectory()) {
+ throw new PackagerException("%s is a directory!", file);
+ }
+
+ if (file.exists()) { // will be a file in this case.
+ if (!file.canWrite()) {
+ throw new PackagerException("Cannot write %s", file);
+ }
+ } else {
+ try {
+ if (!file.createNewFile()) {
+ throw new PackagerException("Failed to create %s", file);
+ }
+ } catch (IOException e) {
+ throw new PackagerException(
+ "Failed to create '%1$ss': %2$s", file, e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Checks an input {@link File} object.
+ * This checks the following:
+ * - the file is not an existing directory.
+ * - that the file exists (if <var>throwIfDoesntExist</var> is <code>false</code>) and can
+ * be read.
+ * @param file the File to check
+ * @throws FileNotFoundException if the file is not here.
+ * @throws PackagerException If the file is a folder or a file that cannot be read.
+ */
+ private static void checkInputFile(File file) throws FileNotFoundException, PackagerException {
+ if (file.isDirectory()) {
+ throw new PackagerException("%s is a directory!", file);
+ }
+
+ if (file.exists()) {
+ if (!file.canRead()) {
+ throw new PackagerException("Cannot read %s", file);
+ }
+ } else {
+ throw new FileNotFoundException(String.format("%s does not exist", file));
+ }
+ }
+
+ public static String getLocalVersion() {
+ Class clazz = Packager.class;
+ String className = clazz.getSimpleName() + ".class";
+ String classPath = clazz.getResource(className).toString();
+ if (!classPath.startsWith("jar")) {
+ // Class not from JAR, unlikely
+ return null;
+ }
+ try {
+ String manifestPath = classPath.substring(0, classPath.lastIndexOf('!') + 1) +
+ "/META-INF/MANIFEST.MF";
+
+ URLConnection jarConnection = new URL(manifestPath).openConnection();
+ jarConnection.setUseCaches(false);
+ InputStream jarInputStream = jarConnection.getInputStream();
+ Attributes attr = new Manifest(jarInputStream).getMainAttributes();
+ jarInputStream.close();
+ return attr.getValue("Builder-Version");
+ } catch (MalformedURLException ignored) {
+ } catch (IOException ignored) {
+ }
+
+ return null;
+ }
+
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/AlignmentRule.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/AlignmentRule.java
new file mode 100644
index 0000000..a863eca
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/AlignmentRule.java
@@ -0,0 +1,79 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Preconditions;
+
+import java.util.regex.Pattern;
+
+/**
+ * An alignment rule defines how should some files be aligned in a zip file. A rule is defined
+ * by two properties: a pattern and an alignment value.
+ * <p>
+ * The pattern is applied to the file name and defines which files this rule applies to. Note that
+ * the pattern is <em>not</em> applied to the <em>path</em>, only to the file name.
+ * The value defines the alignment of data. So,
+ * for example, an alignment of {@code 1024} means that the data needs to start in a byte {@code b}
+ * such that {@code b % 1024 == 0}.
+ */
+public class AlignmentRule {
+
+ /**
+ * File name pattern.
+ */
+ @NonNull
+ private Pattern mPattern;
+
+ /**
+ * Alignment value.
+ */
+ private int mAlignment;
+
+ /**
+ * Creates a new alignment rule.
+ *
+ * @param pattern the pattern to apply to file names to decide whether the rules applies or not
+ * to a file; this will be checked using {@code matches()}, not {@code find()}
+ * @param alignment the alignment value, must be non-negative
+ */
+ public AlignmentRule(@NonNull Pattern pattern, int alignment) {
+ Preconditions.checkArgument(alignment > 0, "alignment (%s) <= 0", alignment);
+
+ mPattern = pattern;
+ mAlignment = alignment;
+ }
+
+ /**
+ * Obtains the pattern used to match files.
+ *
+ * @return the pattern
+ */
+ @NonNull
+ public Pattern getPattern() {
+ return mPattern;
+ }
+
+ /**
+ * Obtains the alignment value for the file.
+ *
+ * @return the alignment
+ */
+ public int getAlignment() {
+ return mAlignment;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/AlignmentRules.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/AlignmentRules.java
new file mode 100644
index 0000000..17c2600
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/AlignmentRules.java
@@ -0,0 +1,99 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Verify;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Alignment rules maintains a list of {@link AlignmentRule} and allows checking the alignment for
+ * a file. Rules in the list are kept in order and the first rule to apply to a file will be the
+ * one used.
+ */
+public class AlignmentRules {
+
+ /**
+ * Default alignment to return if no rule matches a file.
+ */
+ private static final int DEFAULT_ALIGNMENT = 1;
+
+ /**
+ * The alignment rules.
+ */
+ @NonNull
+ private List<AlignmentRule> mRules;
+
+ /**
+ * Creates a new empty set of rules.
+ */
+ public AlignmentRules() {
+ mRules = Lists.newArrayList();
+ }
+
+ /**
+ * Adds a new alignment rule to the end of the list.
+ *
+ * @param rule the rule to add
+ */
+ public void add(@NonNull AlignmentRule rule) {
+ mRules.add(rule);
+ }
+
+ /**
+ * Finds the alignment of a file with a certain path.
+ *
+ * @param path the path
+ * @return the alignment or {@code 1} if there are no rules for this file, never returns less
+ * than {@code 1}
+ */
+ public int alignment(@NonNull String path) {
+ /*
+ * Remove the trailing separator, if there is one.
+ */
+ if (path.endsWith(Character.toString(ZFile.SEPARATOR))) {
+ path = path.substring(0, path.length() - 1);
+ }
+
+ /*
+ * The zip specification guarantees that there are no absolute paths in the zip. This means
+ * that any separator, if it exists, cannot be the first character. (See section 4.4.17.)
+ */
+ int lastSlashIdx = path.lastIndexOf(ZFile.SEPARATOR);
+ Verify.verify(lastSlashIdx != 0);
+ if (lastSlashIdx > 0) {
+ path = path.substring(lastSlashIdx + 1);
+ }
+
+ /*
+ * Now path, does not contain any separator and is the file name. Check if any rule applies
+ * or return the default value if no rule does.
+ */
+ Verify.verify(path.indexOf(ZFile.SEPARATOR) == -1);
+ for (AlignmentRule rule : mRules) {
+ if (rule.getPattern().matcher(path).matches()) {
+ int alignment = rule.getAlignment();
+ Verify.verify(alignment >= 1);
+ return alignment;
+ }
+ }
+
+ return DEFAULT_ALIGNMENT;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ByteArrayEntrySource.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ByteArrayEntrySource.java
new file mode 100644
index 0000000..e31340d
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ByteArrayEntrySource.java
@@ -0,0 +1,60 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Entry source that gets its data from a byte array. The most important use is to keep deflated
+ * files in memory before storing them in the zip file.
+ */
+public class ByteArrayEntrySource implements EntrySource {
+ /**
+ * The byte data.
+ */
+ @NonNull
+ private byte[] mData;
+
+ /**
+ * Creates a new source.
+ *
+ * @param data the data to use as source
+ */
+ public ByteArrayEntrySource(@NonNull byte[] data) {
+ mData = data;
+ }
+
+ @NonNull
+ @Override
+ public InputStream open() throws IOException {
+ return new ByteArrayInputStream(mData);
+ }
+
+ @Override
+ public long size() {
+ return mData.length;
+ }
+
+ @Override
+ public EntrySource innerCompressed() {
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CentralDirectory.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CentralDirectory.java
new file mode 100644
index 0000000..a4e681c
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CentralDirectory.java
@@ -0,0 +1,407 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.builder.internal.packaging.zip.utils.CachedSupplier;
+import com.android.builder.internal.packaging.zip.utils.MsDosDateTimeUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteSource;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Representation of the central directory of a zip archive.
+ */
+class CentralDirectory {
+
+ /**
+ * Field in the central directory with the central directory signature.
+ */
+ private static final ZipField.F4 F_SIGNATURE = new ZipField.F4(0, 0x02014b50, "Signature");
+
+ /**
+ * Field in the central directory with the "made by" code.
+ */
+ private static final ZipField.F2 F_MADE_BY = new ZipField.F2(F_SIGNATURE.endOffset(),
+ "Made by", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the central directory with the minimum version required to extract the entry.
+ */
+ private static final ZipField.F2 F_VERSION_EXTRACT = new ZipField.F2(F_MADE_BY.endOffset(),
+ "Version to extract", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the central directory with the GP bit flag.
+ */
+ private static final ZipField.F2 F_GP_BIT = new ZipField.F2(F_VERSION_EXTRACT.endOffset(),
+ "GP bit");
+
+ /**
+ * Field in the central directory with the code of the compression method. See
+ * {@link CompressionMethod#fromCode(long)}.
+ */
+ private static final ZipField.F2 F_METHOD = new ZipField.F2(F_GP_BIT.endOffset(), "Method");
+
+ /**
+ * Field in the central directory with the last modification time in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packTime(long)}).
+ */
+ private static final ZipField.F2 F_LAST_MOD_TIME = new ZipField.F2(F_METHOD.endOffset(),
+ "Last modification time");
+
+ /**
+ * Field in the central directory with the last modification date in MS-DOS format. See
+ * {@link MsDosDateTimeUtils#packDate(long)}.
+ */
+ private static final ZipField.F2 F_LAST_MOD_DATE = new ZipField.F2(F_LAST_MOD_TIME.endOffset(),
+ "Last modification date");
+
+ /**
+ * Field in the central directory with the CRC32 checksum of the entry. This will be zero for
+ * directories and files with no content.
+ */
+ private static final ZipField.F4 F_CRC32 = new ZipField.F4(F_LAST_MOD_DATE.endOffset(),
+ "CRC32");
+
+ /**
+ * Field in the central directory with the entry's compressed size, <em>i.e.</em>, the file on
+ * the archive. This will be the same as the uncompressed size if the method is
+ * {@link CompressionMethod#STORE}.
+ */
+ private static final ZipField.F4 F_COMPRESSED_SIZE = new ZipField.F4(F_CRC32.endOffset(),
+ "Compressed size", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the central directory with the entry's uncompressed size, <em>i.e.</em>, the size
+ * the file will have when extracted from the zip. This will be zero for directories and empty
+ * files and will be the same as the compressed size if the method is
+ * {@link CompressionMethod#STORE}.
+ */
+ private static final ZipField.F4 F_UNCOMPRESSED_SIZE = new ZipField.F4(
+ F_COMPRESSED_SIZE.endOffset(), "Uncompressed size", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the central directory with the length of the file name. The file name is stored
+ * after the offset field ({@link #F_OFFSET}). The number of characters in the file name are
+ * stored in this field.
+ */
+ private static final ZipField.F2 F_FILE_NAME_LENGTH = new ZipField.F2(
+ F_UNCOMPRESSED_SIZE.endOffset(), "File name length",
+ new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the central directory with the length of the extra field. The extra field is
+ * stored after the file name ({@link #F_FILE_NAME_LENGTH}). The contents of this field are
+ * partially defined in the zip specification but we do not parse it.
+ */
+ private static final ZipField.F2 F_EXTRA_FIELD_LENGTH = new ZipField.F2(
+ F_FILE_NAME_LENGTH.endOffset(), "Extra field length",
+ new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the central directory with the length of the comment. The comment is stored after
+ * the extra field ({@link #F_EXTRA_FIELD_LENGTH}). We do not parse the comment.
+ */
+ private static final ZipField.F2 F_COMMENT_LENGTH = new ZipField.F2(
+ F_EXTRA_FIELD_LENGTH.endOffset(), "Comment length", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Number of the disk where the central directory starts. Because we do not support multi-file
+ * archives, this field has to have value {@code 0}.
+ */
+ private static final ZipField.F2 F_DISK_NUMBER_START = new ZipField.F2(
+ F_COMMENT_LENGTH.endOffset(), 0, "Disk start");
+
+ /**
+ * Internal attributes. This field can only contain one bit set, the {@link #ASCII_BIT}.
+ */
+ private static final ZipField.F2 F_INTERNAL_ATTRIBUTES = new ZipField.F2(
+ F_DISK_NUMBER_START.endOffset(), "Int attributes");
+
+ /**
+ * External attributes. This field is ignored.
+ */
+ private static final ZipField.F4 F_EXTERNAL_ATTRIBUTES = new ZipField.F4(
+ F_INTERNAL_ATTRIBUTES.endOffset(), "Ext attributes");
+
+ /**
+ * Offset into the archive where the entry starts. This is the offset to the local header
+ * (see {@link StoredEntry} for information on the local header), not to the file data itself.
+ * The file data, if there is any, will be stored after the local header.
+ */
+ private static final ZipField.F4 F_OFFSET = new ZipField.F4(F_EXTERNAL_ATTRIBUTES.endOffset(),
+ "Offset", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Maximum supported version to extract.
+ */
+ private static final int MAX_VERSION_TO_EXTRACT = 20;
+
+ /**
+ * Bit that can be set on the internal attributes stating that the file is an ASCII file. We
+ * don't do anything with this information, but we check that nothing unexpected appears in the
+ * internal attributes.
+ */
+ private static final int ASCII_BIT = 1;
+
+ /**
+ * Contains all entries in the directory mapped from their names.
+ */
+ @NonNull
+ private final Map<String, StoredEntry> mEntries;
+
+ /**
+ * The file where this directory belongs to.
+ */
+ @NonNull
+ private final ZFile mFile;
+
+ /**
+ * Supplier that provides a byte representation of the central directory.
+ */
+ @NonNull
+ private final CachedSupplier<byte[]> mBytesSupplier;
+
+ /**
+ * Creates a new, empty, central directory, for a given zip file.
+ *
+ * @param file the file
+ */
+ CentralDirectory(@NonNull ZFile file) {
+ mEntries = Maps.newHashMap();
+ mFile = file;
+ mBytesSupplier = new CachedSupplier<byte[]>() {
+ @Override
+ protected byte[] compute() throws IOException {
+ return computeByteRepresentation();
+ }
+ };
+ }
+
+ /**
+ * Reads the central directory data from a zip file, parses it, and creates the in-memory
+ * structure representing the directory.
+ *
+ * @param bytes the data of the central directory
+ * @param count the number of entries expected in the central directory (usually read from the
+ * {@link Eocd}).
+ * @param file the zip file this central directory belongs to
+ * @return the central directory
+ * @throws IOException failed to read data from the zip, or the central directory is corrupted
+ * or has unsupported features
+ */
+ static CentralDirectory makeFromData(@NonNull ByteSource bytes, int count, @NonNull ZFile file)
+ throws IOException {
+ Preconditions.checkNotNull(bytes, "bytes == null");
+ Preconditions.checkArgument(count >= 0, "count < 0");
+
+ long totalRead = 0;
+
+ CentralDirectory directory = new CentralDirectory(file);
+
+ for (int i = 0; i < count; i++) {
+ try {
+ totalRead += directory.readEntry(bytes.slice(totalRead, bytes.size()));
+ } catch (IOException e) {
+ throw new IOException("Failed to read directory entry index " + i + " (total "
+ + "directory bytes read: " + totalRead + ").", e);
+ }
+ }
+
+ if (totalRead != bytes.size()) {
+ throw new IOException("Directory has a total of " + bytes.size() + " bytes but after "
+ + "reading all " + count + " entries, only " + totalRead + " bytes were read.");
+ }
+
+ return directory;
+ }
+
+ /**
+ * Creates a new central directory from the entries. This is used to build a new central
+ * directory from entries in the zip file.
+ *
+ * @param entries the entries in the zip file
+ * @param file the zip file itself
+ * @return the created central directory
+ */
+ static CentralDirectory makeFromEntries(@NonNull Set<StoredEntry> entries,
+ @NonNull ZFile file) {
+ CentralDirectory directory = new CentralDirectory(file);
+ for (StoredEntry entry : entries) {
+ CentralDirectoryHeader cdr = entry.getCentralDirectoryHeader();
+ Preconditions.checkArgument(!directory.mEntries.containsKey(cdr.getName()),
+ "Duplicate filename");
+ directory.mEntries.put(cdr.getName(), entry);
+ }
+
+ return directory;
+ }
+
+ /**
+ * Reads the next entry from the central directory and adds it to {@link #mEntries}.
+ *
+ * @param bytes the central directory's data, starting at the beginning of the next entry to
+ * read
+ * @return the number of bytes read
+ * @throws IOException failed to read the directory entry, either because of an I/O error,
+ * because it is corrupt or contains unsupported features
+ */
+ private long readEntry(@NonNull ByteSource bytes) throws IOException {
+ Preconditions.checkNotNull(bytes, "bytes == null");
+
+ F_SIGNATURE.verify(bytes);
+ long madeBy = F_MADE_BY.read(bytes);
+
+ long versionNeededToExtract = F_VERSION_EXTRACT.read(bytes);
+ if (versionNeededToExtract > MAX_VERSION_TO_EXTRACT) {
+ throw new IOException("Unknown version needed to extract in zip directory entry: "
+ + versionNeededToExtract + ".");
+ }
+
+ long gpBit = F_GP_BIT.read(bytes);
+ GPFlags flags = GPFlags.from(gpBit);
+
+ long methodCode = F_METHOD.read(bytes);
+ CompressionMethod method = CompressionMethod.fromCode(methodCode);
+ if (method == null) {
+ throw new IOException("Unknown method in zip directory entry: " + methodCode + ".");
+ }
+
+ long lastModTime = F_LAST_MOD_TIME.read(bytes);
+ long lastModDate = F_LAST_MOD_DATE.read(bytes);
+ long crc32 = F_CRC32.read(bytes);
+ long compressedSize = F_COMPRESSED_SIZE.read(bytes);
+ long uncompressedSize = F_UNCOMPRESSED_SIZE.read(bytes);
+ long fileNameLength = F_FILE_NAME_LENGTH.read(bytes);
+ long extraFieldLength = F_EXTRA_FIELD_LENGTH.read(bytes);
+ long fileCommentLength = F_COMMENT_LENGTH.read(bytes);
+
+ F_DISK_NUMBER_START.verify(bytes);
+ long internalAttributes = F_INTERNAL_ATTRIBUTES.read(bytes);
+ if ((internalAttributes & ~ASCII_BIT) != 0) {
+ throw new IOException("Invalid internal attributes: " + internalAttributes + ".");
+ }
+
+ long externalAttributes = F_EXTERNAL_ATTRIBUTES.read(bytes);
+ long offset = F_OFFSET.read(bytes);
+
+ long expectedSize = F_OFFSET.endOffset() + fileNameLength + extraFieldLength
+ + fileCommentLength;
+
+ if (bytes.size() < expectedSize) {
+ throw new IOException("Directory entry should have "
+ + expectedSize + " bytes (name = " + fileNameLength + ", extra = "
+ + extraFieldLength + ", comment = " + fileCommentLength + "), but it has "
+ + bytes.size() + ".");
+ }
+
+ ByteSource fileNameBytes = bytes.slice(F_OFFSET.endOffset(), fileNameLength);
+ String fileName = fileNameBytes.asCharSource(Charsets.US_ASCII).read();
+
+ CentralDirectoryHeader centralDirectoryHeader = new CentralDirectoryHeader(fileName,
+ compressedSize, uncompressedSize, method);
+ centralDirectoryHeader.setMadeBy(madeBy);
+ centralDirectoryHeader.setVersionExtract(versionNeededToExtract);
+ centralDirectoryHeader.setGpBit(flags);
+ centralDirectoryHeader.setLastModTime(lastModTime);
+ centralDirectoryHeader.setLastModDate(lastModDate);
+ centralDirectoryHeader.setCrc32(crc32);
+ centralDirectoryHeader.setInternalAttributes(internalAttributes);
+ centralDirectoryHeader.setExternalAttributes(externalAttributes);
+ centralDirectoryHeader.setOffset(offset);
+
+ centralDirectoryHeader.setExtraField(bytes.slice(F_OFFSET.endOffset() + fileNameLength,
+ extraFieldLength).read());
+ centralDirectoryHeader.setComment(bytes.slice(F_OFFSET.endOffset() + fileNameLength
+ + extraFieldLength, fileCommentLength).read());
+
+ StoredEntry entry = new StoredEntry(centralDirectoryHeader, mFile);
+ if (mEntries.containsKey(fileName)) {
+ throw new IOException("File file contains duplicate file '" + fileName + "'.");
+ }
+
+ mEntries.put(fileName, entry);
+
+ return expectedSize;
+ }
+
+ /**
+ * Obtains all the entries in the central directory.
+ *
+ * @return all entries on a non-modifiable map
+ */
+ @NonNull
+ Map<String, StoredEntry> getEntries() {
+ return ImmutableMap.copyOf(mEntries);
+ }
+
+ /**
+ * Obtains the byte representation of the central directory.
+ *
+ * @return a byte array containing the whole central directory
+ * @throws IOException failed to write the byte array
+ */
+ byte[] toBytes() throws IOException {
+ return mBytesSupplier.get();
+ }
+
+ /**
+ * Computes the byte representation of the central directory.
+ *
+ * @return a byte array containing the whole central directory
+ * @throws IOException failed to write the byte array
+ */
+ private byte[] computeByteRepresentation() throws IOException {
+ ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+
+ for (StoredEntry entry : mEntries.values()) {
+ CentralDirectoryHeader cdr = entry.getCentralDirectoryHeader();
+
+ F_SIGNATURE.write(bytesOut);
+ F_MADE_BY.write(bytesOut, cdr.getMadeBy());
+ F_VERSION_EXTRACT.write(bytesOut, cdr.getVersionExtract());
+ F_GP_BIT.write(bytesOut, cdr.getGpBit().getValue());
+ F_METHOD.write(bytesOut, cdr.getMethod().methodCode);
+ F_LAST_MOD_TIME.write(bytesOut, cdr.getLastModTime());
+ F_LAST_MOD_DATE.write(bytesOut, cdr.getLastModDate());
+ F_CRC32.write(bytesOut, cdr.getCrc32());
+ F_COMPRESSED_SIZE.write(bytesOut, cdr.getCompressedSize());
+ F_UNCOMPRESSED_SIZE.write(bytesOut, cdr.getUncompressedSize());
+ F_FILE_NAME_LENGTH.write(bytesOut, cdr.getName().length());
+ F_EXTRA_FIELD_LENGTH.write(bytesOut, cdr.getExtraField().length);
+ F_COMMENT_LENGTH.write(bytesOut, cdr.getComment().length);
+ F_DISK_NUMBER_START.write(bytesOut);
+ F_INTERNAL_ATTRIBUTES.write(bytesOut, cdr.getInternalAttributes());
+ F_EXTERNAL_ATTRIBUTES.write(bytesOut, cdr.getExternalAttributes());
+ F_OFFSET.write(bytesOut, cdr.getOffset());
+
+ bytesOut.write(cdr.getName().getBytes(Charsets.US_ASCII));
+ bytesOut.write(cdr.getExtraField());
+ bytesOut.write(cdr.getComment());
+ }
+
+ return bytesOut.toByteArray();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CentralDirectoryHeader.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CentralDirectoryHeader.java
new file mode 100644
index 0000000..1e5e095
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CentralDirectoryHeader.java
@@ -0,0 +1,438 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.builder.internal.packaging.zip.utils.MsDosDateTimeUtils;
+import com.google.common.base.Preconditions;
+
+import java.util.Arrays;
+
+/**
+ * The Central Directory Header contains information about files stored in the zip. Instances of
+ * this class contain information for files that already are in the zip and, for which the data was
+ * read from the Central Directory. But some instances of this class are used for new files.
+ * Because instances of this class can refer to files not yet on the zip, some of the fields may
+ * not be filled in, or may be filled in with default values.
+ */
+public class CentralDirectoryHeader implements Cloneable {
+ /**
+ * Name of the file.
+ */
+ @NonNull
+ private String mName;
+
+ /**
+ * CRC32 of the data. 0 if not yet computed.
+ */
+ private long mCrc32;
+
+ /**
+ * Size of the file compressed. 0 if the file has no data.
+ */
+ private long mCompressedSize;
+
+ /**
+ * Size of the file uncompressed. 0 if the file has no data.
+ */
+ private long mUncompressedSize;
+
+ /**
+ * The compression method.
+ */
+ @NonNull
+ private CompressionMethod mMethod;
+
+ /**
+ * Code of the program that made the zip. We actually don't care about this.
+ */
+ private long mMadeBy;
+
+ /**
+ * Version needed to extract the zip.
+ */
+ private long mVersionExtract;
+
+ /**
+ * General-purpose bit flag.
+ */
+ @NonNull
+ private GPFlags mGpBit;
+
+ /**
+ * Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packTime(long)}).
+ */
+ private long mLastModTime;
+
+ /**
+ * Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packDate(long)}).
+ */
+ private long mLastModDate;
+
+ /**
+ * Extra data field contents. This field follows a specific structure according to the
+ * specification, but we don't use its contents.
+ */
+ @NonNull
+ private byte[] mExtraField;
+
+ /**
+ * File comment.
+ */
+ @NonNull
+ private byte[] mComment;
+
+ /**
+ * File internal attributes.
+ */
+ private long mInternalAttributes;
+
+ /**
+ * File external attributes.
+ */
+ private long mExternalAttributes;
+
+ /**
+ * Offset in the file where the data is located. This will be -1 if the header corresponds to
+ * a new file that is not yet written in the zip and, therefore, has no written data.
+ */
+ private long mOffset;
+
+ /**
+ * Creates data for a file.
+ *
+ * @param name the file name
+ * @param compressedSize the compressed file size
+ * @param uncompressedSize the uncompressed file size
+ * @param method the compression method used on the file
+ */
+ CentralDirectoryHeader(@NonNull String name, long compressedSize, long uncompressedSize,
+ @NonNull CompressionMethod method) {
+ mName = name;
+ mCompressedSize = compressedSize;
+ mUncompressedSize = uncompressedSize;
+ mMethod = method;
+ mCrc32 = 0;
+
+ if (method == CompressionMethod.STORE) {
+ Preconditions.checkArgument(uncompressedSize == compressedSize,
+ "File data with STORE but compressed size != uncompressed size ("
+ + compressedSize + " vs " + uncompressedSize + " )");
+ }
+
+ /*
+ * Set sensible defaults for the rest.
+ */
+ mMadeBy = 0;
+ mGpBit = GPFlags.makeDefault();
+ mLastModTime = MsDosDateTimeUtils.packCurrentTime();
+ mLastModDate = MsDosDateTimeUtils.packCurrentDate();
+ mExtraField = new byte[0];
+ mComment = new byte[0];
+ mInternalAttributes = 0;
+ mExternalAttributes = 0;
+ mOffset = -1;
+
+ if (name.endsWith("/") || method == CompressionMethod.DEFLATE) {
+ /*
+ * Directories and compressed files only in version 2.0.
+ */
+ mVersionExtract = 20;
+ } else {
+ mVersionExtract = 10;
+ }
+ }
+
+ /**
+ * Obtains the name of the file.
+ *
+ * @return the name
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Obtains the size of the uncompressed file.
+ *
+ * @return the size of the file
+ */
+ public long getUncompressedSize() {
+ return mUncompressedSize;
+ }
+
+ /**
+ * Obtains the size of the compressed file.
+ *
+ * @return the size of the file
+ */
+ public long getCompressedSize() {
+ return mCompressedSize;
+ }
+
+ /**
+ * Sets the compression method. Can only be called if the compression method is
+ * {@link CompressionMethod#DEFLATE}.
+ *
+ * @param compressedSize the compressed data size
+ */
+ public void setCompressedSize(long compressedSize) {
+ Preconditions.checkState(mMethod == CompressionMethod.DEFLATE, "Cannot set the compressed "
+ + "size if compression method is not DEFLATE.");
+
+ mCompressedSize = compressedSize;
+ }
+
+ /**
+ * Obtains the compression method to use.
+ *
+ * @return the compression method
+ */
+ @NonNull
+ public CompressionMethod getMethod() {
+ return mMethod;
+ }
+
+ /**
+ * Sets the compression method. The compression method can only be set to
+ * {@link CompressionMethod#STORE} if the compressed and uncompressed sizes are equal.
+ *
+ * @param method the compression method
+ */
+ public void setMethod(@NonNull CompressionMethod method) {
+ if (method == CompressionMethod.STORE) {
+ Preconditions.checkState(mCompressedSize == mUncompressedSize, "Cannot mark a file "
+ + "as STORE if the compressed size (%s) is not the same as the uncompressed "
+ + "size (%s).", mCompressedSize, mUncompressedSize);
+ }
+
+ mMethod = method;
+ }
+
+ /**
+ * Obtains the CRC32 of the data.
+ *
+ * @return the CRC32, 0 if not yet computed
+ */
+ public long getCrc32() {
+ return mCrc32;
+ }
+
+ /**
+ * Sets the CRC32 of the data.
+ *
+ * @param crc32 the CRC 32
+ */
+ public void setCrc32(long crc32) {
+ mCrc32 = crc32;
+ }
+
+ /**
+ * Obtains the code of the program that made the zip.
+ *
+ * @return the code
+ */
+ public long getMadeBy() {
+ return mMadeBy;
+ }
+
+ /**
+ * Sets the code of the progtram that made the zip.
+ *
+ * @param madeBy the code
+ */
+ public void setMadeBy(long madeBy) {
+ mMadeBy = madeBy;
+ }
+
+ /**
+ * Obtains the version needed to extract the entry.
+ * @return the version number
+ */
+ public long getVersionExtract() {
+ return mVersionExtract;
+ }
+
+ /**
+ * Sets the version needed to extract the entry.
+ *
+ * @param versionExtract the version number
+ */
+ public void setVersionExtract(long versionExtract) {
+ mVersionExtract = versionExtract;
+ }
+
+ /**
+ * Obtains the general-purpose bit flag.
+ *
+ * @return the bit flag
+ */
+ @NonNull
+ public GPFlags getGpBit() {
+ return mGpBit;
+ }
+
+ /**
+ * Sets the general-purpose bit flag.
+ *
+ * @param gpBit the bit flag
+ */
+ public void setGpBit(@NonNull GPFlags gpBit) {
+ mGpBit = gpBit;
+ }
+
+ /**
+ * Obtains the last modification time of the entry.
+ *
+ * @return the last modification time in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packTime(long)})
+ */
+ public long getLastModTime() {
+ return mLastModTime;
+ }
+
+ /**
+ * Sets the last modification time of the entry.
+ *
+ * @param lastModTime the last modification time in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packTime(long)})
+ */
+ public void setLastModTime(long lastModTime) {
+ mLastModTime = lastModTime;
+ }
+
+ /**
+ * Obtains the last modification date of the entry.
+ *
+ * @return the last modification date in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packDate(long)})
+ */
+ public long getLastModDate() {
+ return mLastModDate;
+ }
+
+ /**
+ * Sets the last modification date of the entry.
+ *
+ * @param lastModDate the last modification date in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packDate(long)})
+ */
+ public void setLastModDate(long lastModDate) {
+ mLastModDate = lastModDate;
+ }
+
+ /**
+ * Obtains the data in the extra field.
+ *
+ * @return the data (returns an empty array if there is none)
+ */
+ @NonNull
+ public byte[] getExtraField() {
+ return mExtraField;
+ }
+
+ /**
+ * Sets the data in the extra field.
+ *
+ * @param extraField the data to set
+ */
+ public void setExtraField(@NonNull byte[] extraField) {
+ mExtraField = extraField;
+ }
+
+ /**
+ * Obtains the entry's comment.
+ *
+ * @return the comment (returns an empty array if there is no comment)
+ */
+ @NonNull
+ public byte[] getComment() {
+ return mComment;
+ }
+
+ /**
+ * Sets the entry's comment.
+ *
+ * @param comment the comment
+ */
+ public void setComment(@NonNull byte[] comment) {
+ mComment = comment;
+ }
+
+ /**
+ * Obtains the entry's internal attributes.
+ *
+ * @return the entry's internal attributes
+ */
+ public long getInternalAttributes() {
+ return mInternalAttributes;
+ }
+
+ /**
+ * Sets the entry's internal attributes.
+ *
+ * @param internalAttributes the entry's internal attributes
+ */
+ public void setInternalAttributes(long internalAttributes) {
+ mInternalAttributes = internalAttributes;
+ }
+
+ /**
+ * Obtains the entry's external attributes.
+ *
+ * @return the entry's external attributes
+ */
+ public long getExternalAttributes() {
+ return mExternalAttributes;
+ }
+
+ /**
+ * Sets the entry's external attributes.
+ *
+ * @param externalAttributes the entry's external attributes
+ */
+ public void setExternalAttributes(long externalAttributes) {
+ mExternalAttributes = externalAttributes;
+ }
+
+ /**
+ * Obtains the offset in the zip file where this entry's data is.
+ *
+ * @return the offset or {@code -1} if the file is new and has no data in the zip yet
+ */
+ public long getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Sets the offset in the zip file where this entry's data is.
+ *
+ * @param offset the offset or {@code -1} if the file is new and has no data in the zip yet
+ */
+ public void setOffset(long offset) {
+ mOffset = offset;
+ }
+
+ @Override
+ protected CentralDirectoryHeader clone() throws CloneNotSupportedException {
+ CentralDirectoryHeader cdr = (CentralDirectoryHeader) super.clone();
+ cdr.mExtraField = Arrays.copyOf(mExtraField, mExtraField.length);
+ cdr.mComment = Arrays.copyOf(mComment, mComment.length);
+ return cdr;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CompressionMethod.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CompressionMethod.java
new file mode 100644
index 0000000..9984d3e
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/CompressionMethod.java
@@ -0,0 +1,65 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.Nullable;
+
+/**
+ * Enumeration with all known compression methods.
+ */
+public enum CompressionMethod {
+ /**
+ * STORE method: data is stored without any compression.
+ */
+ STORE(0),
+
+ /**
+ * DEFLATE method: data is stored compressed using the DEFLATE algorithm.
+ */
+ DEFLATE(8);
+
+ /**
+ * Code, within the zip file, that identifies this compression method.
+ */
+ int methodCode;
+
+ /**
+ * Creates a new compression method.
+ *
+ * @param methodCode the code used in the zip file that identifies the compression method
+ */
+ CompressionMethod(int methodCode) {
+ this.methodCode = methodCode;
+ }
+
+ /**
+ * Obtains the compression method that corresponds to the provided code.
+ *
+ * @param code the code
+ * @return the method or {@code null} if no method has the provided code
+ */
+ @Nullable
+ static CompressionMethod fromCode(long code) {
+ for (CompressionMethod method : values()) {
+ if (method.methodCode == code) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/DataDescriptorType.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/DataDescriptorType.java
new file mode 100644
index 0000000..f762852
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/DataDescriptorType.java
@@ -0,0 +1,58 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+/**
+ * Type of data descriptor that an entry has. Data descriptors are used if the CRC and sizing data
+ * is not known when the data is being written and cannot be placed in the file's local header.
+ * In those cases, after the file data itself, a data descriptor is placed after the entry's
+ * contents.
+ * <p>
+ * While the zip specification says the data descriptor should be used but it is optional. We
+ * record also whether the data descriptor contained the 4-byte signature at the start of the
+ * block or not.
+ */
+public enum DataDescriptorType {
+ /**
+ * The entry has no data descriptor.
+ */
+ NO_DATA_DESCRIPTOR(0),
+
+ /**
+ * The entry has a data descriptor that does not contain a signature.
+ */
+ DATA_DESCRIPTOR_WITHOUT_SIGNATURE(12),
+
+ /**
+ * The entry has a data descriptor that contains a signature.
+ */
+ DATA_DESCRIPTOR_WITH_SIGNATURE(16);
+
+ /**
+ * The number of bytes the data descriptor spans.
+ */
+ public int size;
+
+ /**
+ * Creates a new data descriptor.
+ *
+ * @param size the number of bytes the data descriptor spans
+ */
+ DataDescriptorType(int size) {
+ this.size = size;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/EntrySource.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/EntrySource.java
new file mode 100644
index 0000000..220619f
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/EntrySource.java
@@ -0,0 +1,58 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Data source for an entry in the zip. Data sources allow abstracting from where data is to be
+ * stored in a zip. {@code EntrySource}s do not contain the data themselves but rather allow
+ * opening a stream that provides the source data.
+ */
+public interface EntrySource {
+ /**
+ * Obtains a stream where the source data can be read from. This method can be called multiple
+ * times and should return a different stream. It can be seen as a factory method.
+ *
+ * @return the stream that should return exactly {@link #size()} bytes
+ * @throws IOException failed to open the source
+ */
+ @NonNull
+ InputStream open() throws IOException;
+
+ /**
+ * Obtains the size of the entry source.
+ *
+ * @return the number of bytes in the source
+ */
+ long size();
+
+ /**
+ * If this source is based on expansion of an inner, compressed source, obtain the inner
+ * source. This may be used to perform optimizations such as avoiding
+ * decompression-for-compression.
+ *
+ * @return the inner compressed source, if any, or {@code null} if this source is already
+ * compressed or has no inner source
+ */
+ @Nullable
+ EntrySource innerCompressed();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/Eocd.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/Eocd.java
new file mode 100644
index 0000000..6a85360
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/Eocd.java
@@ -0,0 +1,268 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.builder.internal.packaging.zip.utils.CachedSupplier;
+import com.android.builder.internal.utils.IOExceptionWrapper;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.io.ByteSource;
+import com.google.common.primitives.Ints;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * End Of Central Directory record in a zip file.
+ */
+class Eocd {
+ /**
+ * Field in the record: the record signature, fixed at this value by the specification.
+ */
+ private static final ZipField.F4 F_SIGNATURE = new ZipField.F4(0, 0x06054b50, "EOCD signature");
+
+ /**
+ * Field in the record: the number of the disk where the EOCD is located. It has to be zero
+ * because we do not support multi-file archives.
+ */
+ private static final ZipField.F2 F_NUMBER_OF_DISK = new ZipField.F2(F_SIGNATURE.endOffset(), 0,
+ "Number of this disk");
+
+ /**
+ * Field in the record: the number of the disk where the Central Directory starts. Has to be
+ * zero because we do not support multi-file archives.
+ */
+ private static final ZipField.F2 F_DISK_CD_START = new ZipField.F2(F_NUMBER_OF_DISK.endOffset(),
+ 0, "Disk where CD starts");
+
+ /**
+ * Field in the record: the number of entries in the Central Directory on this disk. Because
+ * we do not support multi-file archives, this is the same as {@link #F_RECORDS_TOTAL}.
+ */
+ private static final ZipField.F2 F_RECORDS_DISK = new ZipField.F2(F_DISK_CD_START.endOffset(),
+ "Record on disk count", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the record: the total number of entries in the Central Directory.
+ */
+ private static final ZipField.F2 F_RECORDS_TOTAL = new ZipField.F2(F_RECORDS_DISK.endOffset(),
+ "Total records", new ZipFieldInvariantNonNegative(),
+ new ZipFieldInvariantMaxValue(Integer.MAX_VALUE));
+
+ /**
+ * Field in the record: number of bytes of the Central Directory.
+ * This is not private because it is required in unit tests.
+ */
+ @VisibleForTesting
+ static final ZipField.F4 F_CD_SIZE = new ZipField.F4(F_RECORDS_TOTAL.endOffset(),
+ "Directory size", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the record: offset, from the archive start, where the Central Directory starts.
+ * This is not private because it is required in unit tests.
+ */
+ @VisibleForTesting
+ static final ZipField.F4 F_CD_OFFSET = new ZipField.F4(F_CD_SIZE.endOffset(),
+ "Directory offset", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Field in the record: number of bytes of the file comment (located at the end of the EOCD
+ * record).
+ */
+ private static final ZipField.F2 F_COMMENT_SIZE = new ZipField.F2(F_CD_OFFSET.endOffset(),
+ "File comment size", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Number of entries in the central directory.
+ */
+ private final int mTotalRecords;
+
+ /**
+ * Offset from the beginning of the archive where the Central Directory is located.
+ */
+ private final long mDirectoryOffset;
+
+ /**
+ * Number of bytes of the Central Directory.
+ */
+ private final long mDirectorySize;
+
+ /**
+ * Contents of the EOCD comment.
+ */
+ @NonNull
+ private final byte[] mComment;
+
+ /**
+ * Supplier of the byte representation of the EOCD.
+ */
+ @NonNull
+ private final CachedSupplier<byte[]> mByteSupplier;
+
+ /**
+ * Creates a new EOCD, reading it from a byte source. This method will parse the byte source
+ * and obtain the EOCD. It will check that the byte source starts with the EOCD signature.
+ *
+ * @param bytes the byte source with the EOCD data
+ * @throws IOException failed to read information or the EOCD data is corrupt or invalid
+ */
+ Eocd(@NonNull ByteSource bytes) throws IOException {
+ Preconditions.checkNotNull(bytes, "bytes == null");
+
+ /*
+ * Read the EOCD record.
+ */
+ F_SIGNATURE.verify(bytes);
+ F_NUMBER_OF_DISK.verify(bytes);
+ F_DISK_CD_START.verify(bytes);
+ long totalRecords1 = F_RECORDS_DISK.read(bytes);
+ long totalRecords2 = F_RECORDS_TOTAL.read(bytes);
+ long directorySize = F_CD_SIZE.read(bytes);
+ long directoryOffset = F_CD_OFFSET.read(bytes);
+ long commentSize = F_COMMENT_SIZE.read(bytes);
+
+ /*
+ * Some sanity checks.
+ */
+ if (totalRecords1 != totalRecords2) {
+ throw new IOException("Zip states records split in multiple disks, which is not "
+ + "supported.");
+ }
+
+ Verify.verify(totalRecords1 <= Integer.MAX_VALUE);
+
+ mTotalRecords = Ints.checkedCast(totalRecords1);
+ mDirectorySize = directorySize;
+ mDirectoryOffset = directoryOffset;
+
+ if (bytes.size() < F_COMMENT_SIZE.endOffset() + commentSize) {
+ throw new IOException("Corrupt EOCD record: not enough data for comment (comment "
+ + "size is " + commentSize + ").");
+ }
+
+ mComment = bytes.slice(F_COMMENT_SIZE.endOffset(), commentSize).read();
+ mByteSupplier = new CachedSupplier<byte[]>() {
+ @Override
+ protected byte[] compute() throws IOException {
+ return computeByteRepresentation();
+ }
+ };
+ }
+
+ /**
+ * Creates a new EOCD. This is used when generating an EOCD for an Central Directory that has
+ * just been generated. The EOCD will be generated without any comment.
+ *
+ * @param totalRecords total number of records in the directory
+ * @param directoryOffset offset, since beginning of archive, where the Central Directory is
+ * located
+ * @param directorySize number of bytes of the Central Directory
+ */
+ Eocd(int totalRecords, long directoryOffset, long directorySize) {
+ Preconditions.checkArgument(totalRecords >= 0, "totalRecords < 0");
+ Preconditions.checkArgument(directoryOffset >= 0, "directoryOffset < 0");
+ Preconditions.checkArgument(directorySize >= 0, "directorySize < 0");
+
+ mTotalRecords = totalRecords;
+ mDirectoryOffset = directoryOffset;
+ mDirectorySize = directorySize;
+ mComment = new byte[0];
+ mByteSupplier = new CachedSupplier<byte[]>() {
+ @Override
+ protected byte[] compute() {
+ try {
+ return computeByteRepresentation();
+ } catch (IOException e) {
+ throw new IOExceptionWrapper(e);
+ }
+ }
+ };
+ }
+
+ /**
+ * Obtains the number of records in the Central Directory.
+ *
+ * @return the number of records
+ */
+ int getTotalRecords() {
+ return mTotalRecords;
+ }
+
+ /**
+ * Obtains the offset since the beginning of the zip archive where the Central Directory is
+ * located.
+ *
+ * @return the offset where the Central Directory is located
+ */
+ long getDirectoryOffset() {
+ return mDirectoryOffset;
+ }
+
+ /**
+ * Obtains the size of the Central Directory.
+ *
+ * @return the number of bytes that make up the Central Directory
+ */
+ long getDirectorySize() {
+ return mDirectorySize;
+ }
+
+ /**
+ * Obtains the size of the EOCD.
+ *
+ * @return the size, in bytes, of the EOCD
+ */
+ long getEocdSize() {
+ return F_COMMENT_SIZE.endOffset() + mComment.length;
+ }
+
+ /**
+ * Generates the EOCD data.
+ *
+ * @return a byte representation of the EOCD that has exactly {@link #getEocdSize()} bytes
+ * @throws IOException failed to generate the EOCD data
+ */
+ @NonNull
+ byte[] toBytes() throws IOException {
+ return mByteSupplier.get();
+ }
+
+ /**
+ * Computes the byte representation of the EOCD.
+ *
+ * @return a byte representation of the EOCD that has exactly {@link #getEocdSize()} bytes
+ * @throws IOException failed to generate the EOCD data
+ */
+ @NonNull
+ private byte[] computeByteRepresentation() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ F_SIGNATURE.write(out);
+ F_NUMBER_OF_DISK.write(out);
+ F_DISK_CD_START.write(out);
+ F_RECORDS_DISK.write(out, mTotalRecords);
+ F_RECORDS_TOTAL.write(out, mTotalRecords);
+ F_CD_SIZE.write(out, mDirectorySize);
+ F_CD_OFFSET.write(out, mDirectoryOffset);
+ F_COMMENT_SIZE.write(out, mComment.length);
+ out.write(mComment);
+
+ return out.toByteArray();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileEntrySource.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileEntrySource.java
new file mode 100644
index 0000000..1b3c4ab
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileEntrySource.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Entry source that gets data from a file.
+ */
+public class FileEntrySource implements EntrySource {
+
+ /**
+ * File file.
+ */
+ @NonNull
+ private File mFile;
+
+ /**
+ * Creates a new entry source.
+ *
+ * @param file the file where data comes from
+ */
+ public FileEntrySource(@NonNull File file) {
+ mFile = file;
+ }
+
+ @NonNull
+ @Override
+ public InputStream open() throws IOException {
+ return new FileInputStream(mFile);
+ }
+
+ @Override
+ public long size() {
+ return mFile.length();
+ }
+
+ @Override
+ public EntrySource innerCompressed() {
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileUseMap.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileUseMap.java
new file mode 100644
index 0000000..f1f62f8
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileUseMap.java
@@ -0,0 +1,382 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * The file use map keeps track of which parts of the zip file are used which parts are not.
+ * It essentially maintains an ordered set of entries ({@link FileUseMapEntry}). Each entry either has
+ * some data (an entry, the Central Directory, the EOCD) or is a free entry.
+ * <p>
+ * For example: [0-95, "foo/"][95-260, "xpto"][260-310, free][310-360, Central Directory]
+ * [360-390,EOCD]
+ * <p>
+ * There are a couple of invariants in this structure: there are no gaps between map entries. The
+ * map is fully covered up to its size. There are no two free entries next to each other, this
+ * is guaranteed by coalescing the entries upon removal (see {@link #coalesce(FileUseMapEntry)}).
+ */
+class FileUseMap {
+ /**
+ * Size of the file according to the map.
+ */
+ private long mSize;
+
+ /**
+ * Tree with all intervals. Contains coverage from 0 up to {@link #mSize}. If
+ * {@link #mSize} is zero then this set is empty. This is the only situation in which the map
+ * will be empty.
+ */
+ @NonNull
+ private TreeSet<FileUseMapEntry<?>> mMap;
+
+ /**
+ * Creates a new, empty file map.
+ *
+ * @param size the size of the file
+ */
+ FileUseMap(int size) {
+ Preconditions.checkArgument(size >= 0, "size < 0");
+
+ mSize = size;
+ mMap = new TreeSet<FileUseMapEntry<?>>(FileUseMapEntry.COMPARE_BY_START);
+
+ if (size > 0) {
+ mMap.add(FileUseMapEntry.makeFree(0, size));
+ }
+ }
+
+ /**
+ * Adds a new file to the map. The interval specified by <em>entry</em> must fit inside an
+ * empty entry in the map. That entry will be replaced by entry and additional free entries
+ * will be added before and after if needed to make sure no spaces exist on the map.
+ *
+ * @param entry the entry to add
+ */
+ private void add(@NonNull FileUseMapEntry<?> entry) {
+ Preconditions.checkArgument(entry.getStart() < mSize, "entry.getStart() >= mSize");
+ Preconditions.checkArgument(entry.getEnd() <= mSize, "entry.getEnd() > mSize");
+ Preconditions.checkArgument(!entry.isFree(), "entry.isFree()");
+
+ FileUseMapEntry container = findContainer(entry);
+ Verify.verify(container.isFree(), "!container.isFree()");
+
+ Set<FileUseMapEntry<?>> replacements = split(container, entry);
+ mMap.remove(container);
+ mMap.addAll(replacements);
+ }
+
+ /**
+ * Removes a file from the map, replacing it with an empty one that is then coalesced with
+ * neighbors (if the neighbors are free).
+ *
+ * @param entry the entry
+ */
+ void remove(@NonNull FileUseMapEntry<?> entry) {
+ Preconditions.checkState(mMap.contains(entry), "!mMap.contains(entry)");
+
+ mMap.remove(entry);
+ entry = FileUseMapEntry.makeFree(entry.getStart(), entry.getEnd());
+ mMap.add(entry);
+ coalesce(entry);
+ }
+
+ /**
+ * Adds a new file to the map. The interval specified by (<em>start</em>,<em>end</em>) must fit
+ * inside an empty entry in the map. That entry will be replaced by entry and additional free
+ * entries will be added before and after if needed to make sure no spaces exist on the map.
+ * <p>
+ * The entry cannot extend beyong the end of the map. If necessary, extend the map using
+ * {@link #extend(long)}.
+ * <p>
+ * It is assumed that (<em>start</em>,<em>end</em>) will fall in an empty block in the map.
+ *
+ * @param start the start of this entry
+ * @param end the end of the entry
+ * @param store extra data to store with the entry
+ * @param <T> the type of data to store in the entry
+ * @return the new entry
+ */
+ <T> FileUseMapEntry<T> add(long start, long end, @NonNull T store) {
+ Preconditions.checkArgument(start >= 0, "start < 0");
+ Preconditions.checkArgument(end > start, "end < start");
+ Preconditions.checkArgument(store != null, "store != null");
+
+ FileUseMapEntry<T> entry = FileUseMapEntry.makeUsed(start, end, store);
+ add(entry);
+ return entry;
+ }
+
+ /**
+ * Finds the entry that fully contains the given one. It is assumed that one exists.
+ *
+ * @param entry the entry whose container we're looking for
+ * @return the container
+ */
+ @NonNull
+ private FileUseMapEntry<?> findContainer(@NonNull FileUseMapEntry<?> entry) {
+ FileUseMapEntry container = mMap.floor(entry);
+ Verify.verifyNotNull(container);
+ Verify.verify(container.getStart() <= entry.getStart());
+ Verify.verify(container.getEnd() >= entry.getEnd());
+
+ return container;
+ }
+
+ /**
+ * Splits a container to add an entry, adding new free entries before and after the provided
+ * entry if needed.
+ *
+ * @param container the container entry, a free entry that is in {@link #mMap} that that
+ * encloses <em>entry</em>
+ * @param entry the entry that will be used to split <em>container</em>
+ * @return a set of non-overlapping entries that completely covers <em>container</em> and that
+ * includes <em>entry</em>
+ */
+ @NonNull
+ private static Set<FileUseMapEntry<?>> split(@NonNull FileUseMapEntry<?> container,
+ @NonNull FileUseMapEntry<?> entry) {
+ Preconditions.checkArgument(container.isFree(), "!container.isFree()");
+
+ long farStart = container.getStart();
+ long start = entry.getStart();
+ long end = entry.getEnd();
+ long farEnd = container.getEnd();
+
+ Verify.verify(farStart <= start, "farStart > start");
+ Verify.verify(start < end, "start >= end");
+ Verify.verify(farEnd >= end, "farEnd < end");
+
+ Set<FileUseMapEntry<?>> result = Sets.newHashSet();
+ if (farStart < start) {
+ result.add(FileUseMapEntry.makeFree(farStart, start));
+ }
+
+ result.add(entry);
+
+ if (end < farEnd) {
+ result.add(FileUseMapEntry.makeFree(end, farEnd));
+ }
+
+ return result;
+ }
+
+ /**
+ * Coalesces a free entry replacing it and neighboring free entries with a single, larger
+ * entry. This method does nothing if <em>entry</em> does not have free neighbors.
+ *
+ * @param entry the free entry to coalesce with neighbors
+ */
+ private void coalesce(@NonNull FileUseMapEntry<?> entry) {
+ FileUseMapEntry<?> prevToMerge = null;
+ long start = entry.getStart();
+ if (start > 0) {
+ /*
+ * See if we have a previous entry to merge with this one.
+ */
+ prevToMerge = mMap.floor(FileUseMapEntry.makeFree(start - 1, start));
+ Verify.verifyNotNull(prevToMerge);
+ if (!prevToMerge.isFree()) {
+ prevToMerge = null;
+ }
+ }
+
+ FileUseMapEntry<?> nextToMerge = null;
+ long end = entry.getEnd();
+ if (end < mSize) {
+ /*
+ * See if we have a next entry to merge with this one.
+ */
+ nextToMerge = mMap.ceiling(FileUseMapEntry.makeFree(end, end + 1));
+ Verify.verifyNotNull(nextToMerge);
+ if (!nextToMerge.isFree()) {
+ nextToMerge = null;
+ }
+ }
+
+ if (prevToMerge == null && nextToMerge == null) {
+ return;
+ }
+
+ long newStart = start;
+ if (prevToMerge != null) {
+ newStart = prevToMerge.getStart();
+ mMap.remove(prevToMerge);
+ }
+
+ long newEnd = end;
+ if (nextToMerge != null) {
+ newEnd = nextToMerge.getEnd();
+ mMap.remove(nextToMerge);
+ }
+
+ mMap.remove(entry);
+ mMap.add(FileUseMapEntry.makeFree(newStart, newEnd));
+ }
+
+ /**
+ * Truncates map removing the top entry if it is free and reducing the map's size.
+ */
+ void truncate() {
+ if (mSize == 0) {
+ return;
+ }
+
+ /*
+ * Find the last entry.
+ */
+ FileUseMapEntry<?> last = mMap.last();
+ Verify.verifyNotNull(last, "last == null");
+ if (last.isFree()) {
+ mMap.remove(last);
+ mSize = last.getStart();
+ }
+ }
+
+ /**
+ * Obtains the size of the map.
+ *
+ * @return the size
+ */
+ long size() {
+ return mSize;
+ }
+
+ /**
+ * Obtains the largest used offset in the map. This will be size of the map after truncation.
+ *
+ * @return the size of the file discounting the last block if it is empty
+ */
+ long usedSize() {
+ if (mSize == 0) {
+ return 0;
+ }
+
+ /*
+ * Find the last entry to see if it is an empty entry. If it is, we need to remove its size
+ * from the returned value.
+ */
+ FileUseMapEntry<?> last = mMap.last();
+ Verify.verifyNotNull(last, "last == null");
+ if (last.isFree()) {
+ mMap.remove(last);
+ return last.getStart();
+ }
+
+ return mSize;
+ }
+
+ /**
+ * Extends the map to guarantee it has at least <em>size</em> bytes. If the current size is
+ * as large as <em>size</em>, this method does nothing.
+ *
+ * @param size the new size of the map that cannot be smaller that the current size
+ */
+ void extend(long size) {
+ Preconditions.checkArgument(size >= mSize, "size < mSize");
+
+ if (mSize == size) {
+ return;
+ }
+
+ FileUseMapEntry<?> newBlock = FileUseMapEntry.makeFree(mSize, size);
+ mMap.add(newBlock);
+
+ mSize = size;
+
+ coalesce(newBlock);
+ }
+
+ /**
+ * Locates a free area in the map with at least <em>size</em> bytes such that
+ * {@code ((start + alignOffset) % align == 0}. This method will try a
+ * best-fit algorithm. If no free contiguous block exists in the map that can hold the provided
+ * size then the first free index at the end of the map is provided. This means that the map
+ * may need to be extended before data can be added.
+ *
+ * @param size the size of the contiguous area requested
+ * @param alignOffset an offset to which alignment needs to be computed (see method description)
+ * @param align alignment at the offset (see method description)
+ * @return the location of the contiguous area; this may be located at the end of the map
+ */
+ long locateFree(long size, long alignOffset, long align) {
+ Preconditions.checkArgument(size > 0, "size <= 0");
+
+ FileUseMapEntry<?> best = null;
+ long bestExtraSize = 0;
+ for (FileUseMapEntry<?> curr : mMap) {
+ /*
+ * We don't care about blocks that aren't free.
+ */
+ if (!curr.isFree()) {
+ continue;
+ }
+
+ /*
+ * Compute any extra size we need in this block to make sure we verify the alignment.
+ * There must be a better to do this...
+ */
+ long extraSize = (align - ((curr.getStart() + alignOffset) % align)) % align;
+
+ /*
+ * We don't care about blocks where we don't fit in.
+ */
+ if (curr.getSize() < (size + extraSize)) {
+ continue;
+ }
+
+ /*
+ * We don't care about blocks that are bigger than the best so far (otherwise this
+ * wouldn't be a best-fit algorithm).
+ */
+ if (best != null && best.getSize() < curr.getSize()) {
+ continue;
+ }
+
+ best = curr;
+ bestExtraSize = extraSize;
+ }
+
+ /*
+ * If no entry that could hold size is found, get the first free byte.
+ */
+ long firstFree = mSize;
+ if (best == null && !mMap.isEmpty()) {
+ FileUseMapEntry<?> last = mMap.last();
+ if (last.isFree()) {
+ firstFree = last.getStart();
+ }
+ }
+
+ /*
+ * We're done: either we found something or we didn't, in which the new entry needs to
+ * be added to the end of the map.
+ */
+ if (best == null) {
+ long extra = (align - ((firstFree + alignOffset) % align)) % align;
+ return firstFree + extra;
+ } else {
+ return best.getStart() + bestExtraSize;
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileUseMapEntry.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileUseMapEntry.java
new file mode 100644
index 0000000..fa49db9
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/FileUseMapEntry.java
@@ -0,0 +1,162 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.Ints;
+
+import java.util.Comparator;
+
+/**
+ * Represents an entry in the {@link FileUseMap}. Each entry contains an interval of bytes. The
+ * end of the interval is exclusive.
+ * <p>
+ * Entries can either be free or used. Used entries <em>must</em> store an object. Free entries
+ * do not store anything.
+ * <p>
+ * File map entries are used to keep track of which parts of a file map are used and not.
+ * @param <T> the type of data stored
+ */
+class FileUseMapEntry<T> {
+
+ /**
+ * Comparator that compares entries by their start date.
+ */
+ public static final Comparator<FileUseMapEntry<?>> COMPARE_BY_START =
+ new Comparator<FileUseMapEntry<?>>() {
+ @Override
+ public int compare(@NonNull FileUseMapEntry<?> o1, @NonNull FileUseMapEntry<?> o2) {
+ return Ints.checkedCast(o1.getStart() - o2.getStart());
+ }
+ };
+
+ /**
+ * The first byte in the entry.
+ */
+ private final long mStart;
+
+ /**
+ * The first byte no longer in the entry.
+ */
+ private final long mEnd;
+
+ /**
+ * The stored data. If {@code null} then this entry represents a free entry.
+ */
+ @Nullable
+ private final T mStore;
+
+ /**
+ * Creates a new map entry.
+ *
+ * @param start the start of the entry
+ * @param end the end of the entry (first byte no longer in the entry)
+ * @param store the data to store in the entry or {@code null} if this is a free entry
+ */
+ private FileUseMapEntry(long start, long end, @Nullable T store) {
+ Preconditions.checkArgument(start >= 0, "start < 0");
+ Preconditions.checkArgument(end > start, "end <= start");
+
+ mStart = start;
+ mEnd = end;
+ mStore = store;
+ }
+
+ /**
+ * Creates a new free entry.
+ *
+ * @param start the start of the entry
+ * @param end the end of the entry (first byte no longer in the entry)
+ * @return the entry
+ */
+ public static FileUseMapEntry<Object> makeFree(long start, long end) {
+ return new FileUseMapEntry<Object>(start, end, null);
+ }
+
+ /**
+ * Creates a new used entry.
+ *
+ * @param start the start of the entry
+ * @param end the end of the entry (first byte no longer in the entry)
+ * @param store the data to store in the entry
+ * @param <T> the type of data to store in the entry
+ * @return the entry
+ */
+ public static <T> FileUseMapEntry<T> makeUsed(long start, long end, @NonNull T store) {
+ Preconditions.checkNotNull(store, "store == null");
+ return new FileUseMapEntry<T>(start, end, store);
+ }
+
+ /**
+ * Obtains the first byte in the entry.
+ *
+ * @return the first byte in the entry (if the same value as {@link #getEnd()} then the entry
+ * is empty and contains no data)
+ */
+ long getStart() {
+ return mStart;
+ }
+
+ /**
+ * Obtains the first byte no longer in the entry.
+ *
+ * @return the first byte no longer in the entry
+ */
+ long getEnd() {
+ return mEnd;
+ }
+
+ /**
+ * Obtains the size of the entry.
+ *
+ * @return the number of bytes contained in the entry
+ */
+ long getSize() {
+ return mEnd - mStart;
+ }
+
+ /**
+ * Determines if this is a free entry.
+ *
+ * @return is this entry free?
+ */
+ boolean isFree() {
+ return mStore == null;
+ }
+
+ /**
+ * Obtains the data stored in the entry.
+ *
+ * @return the data stored or {@code null} if this entry is a free entry
+ */
+ @Nullable
+ T getStore() {
+ return mStore;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("start", mStart)
+ .add("end", mEnd)
+ .add("store", mStore)
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/GPFlags.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/GPFlags.java
new file mode 100644
index 0000000..9ff47c0
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/GPFlags.java
@@ -0,0 +1,156 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.google.common.primitives.Ints;
+
+import java.io.IOException;
+
+/**
+ * General purpose bit flags. Contains the encoding of the zip's general purpose bits.
+ */
+class GPFlags {
+
+ /**
+ * Is the entry encrypted?
+ */
+ private static final int BIT_ENCRYPTION = 1;
+
+ /**
+ * What is the compression method?
+ */
+ private static final int BIT_METHOD = (1 << 1) | (1 << 2);
+
+ /**
+ * Has CRC computation been deferred and, therefore, does a data description block exist?
+ */
+ private static final int BIT_DEFERRED_CRC = (1 << 3);
+
+ /**
+ * Is enhanced deflating used?
+ */
+ private static final int BIT_ENHANCED_DEFLATING = (1 << 4);
+
+ /**
+ * Does the entry contain patched data?
+ */
+ private static final int BIT_PATCHED_DATA = (1 << 5);
+
+ /**
+ * Is strong encryption used?
+ */
+ private static final int BIT_STRONG_ENCRYPTION = (1 << 6) | (1 << 13);
+
+ /**
+ * Unused bits.
+ */
+ private static final int BIT_UNUSED = (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10)
+ | (1 << 11) | (1 << 14) | (1 << 15);
+
+ /**
+ * Bit flag value.
+ */
+ private final long mValue;
+
+ /**
+ * Has the CRC computation beeen deferred?
+ */
+ private boolean mDeferredCrc;
+
+ /**
+ * Creates a new flags object.
+ *
+ * @param value the value of the bit mask
+ */
+ private GPFlags(long value) {
+ mValue = value;
+
+ mDeferredCrc = ((value & BIT_DEFERRED_CRC) != 0);
+ }
+
+ /**
+ * Obtains the flags value.
+ *
+ * @return the value of the bit mask
+ */
+ public long getValue() {
+ return mValue;
+ }
+
+ /**
+ * Is the CRC computation deferred?
+ *
+ * @return is the CRC computation deferred?
+ */
+ public boolean isDeferredCrc() {
+ return mDeferredCrc;
+ }
+
+ /**
+ * Creates a new default bit mask.
+ *
+ * @return the new bit mask
+ */
+ @NonNull
+ static GPFlags makeDefault() {
+ return new GPFlags(0);
+ }
+
+ /**
+ * Creates the flag information from a byte. This method will also validate that only
+ * supported options are defined in the flag.
+ *
+ * @param bits the bit mask
+ * @return the created flag information
+ * @throws IOException unsupported options are used in the bit mask
+ */
+ @NonNull
+ static GPFlags from(long bits) throws IOException {
+ if ((bits & BIT_ENCRYPTION) != 0) {
+ throw new IOException("Zip files with encrypted of entries not supported.");
+ }
+
+ if ((bits & BIT_ENHANCED_DEFLATING) != 0) {
+ throw new IOException("Enhanced deflating not supported.");
+ }
+
+ if ((bits & BIT_PATCHED_DATA) != 0) {
+ throw new IOException("Compressed patched data not supported.");
+ }
+
+ if ((bits & BIT_STRONG_ENCRYPTION) != 0) {
+ throw new IOException("Strong encryption not supported.");
+ }
+
+ if ((bits & BIT_UNUSED) != 0) {
+ throw new IOException("Unused bits set in directory entry. Weird. I don't know what's "
+ + "going on.");
+ }
+
+ int methodBit = Ints.checkedCast(bits & BIT_METHOD);
+ if (methodBit != 0) {
+ throw new IOException("Unsupported method bit: " + methodBit + ".");
+ }
+
+ if ((bits & 0xffffffff00000000L) != 0) {
+ throw new IOException("Unsupported bits after 32.");
+ }
+
+ return new GPFlags(bits);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/InflaterEntrySource.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/InflaterEntrySource.java
new file mode 100644
index 0000000..6d31144
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/InflaterEntrySource.java
@@ -0,0 +1,82 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Preconditions;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * Entry source that inflates an inner source. The inner source must contain delfated data.
+ */
+class InflaterEntrySource implements EntrySource {
+ /**
+ * The inner source of deflated data.
+ */
+ @NonNull
+ private EntrySource mDeflatedSource;
+
+ /**
+ * Size of uncompressed data.
+ */
+ private long mUncompressedSize;
+
+ /**
+ * Creates a new source.
+ *
+ * @param deflatedSource the source of deflated data
+ * @param uncompressedSize the size of deflated data after inflation
+ */
+ InflaterEntrySource(@NonNull EntrySource deflatedSource, long uncompressedSize) {
+ Preconditions.checkArgument(uncompressedSize >= 0, "uncompressedSize (%s) < 0",
+ uncompressedSize);
+
+ mDeflatedSource = deflatedSource;
+ mUncompressedSize = uncompressedSize;
+ }
+
+ @NonNull
+ @Override
+ public InputStream open() throws IOException {
+ InputStream rawStream = mDeflatedSource.open();
+
+ /*
+ * The extra byte is a dummy byte required by the inflater. Weirdo.
+ * (see the java.util.Inflater documentation). Looks like a hack...
+ * "Oh, I need an extra dummy byte to allow for some... err... optimizations..."
+ */
+ ByteArrayInputStream hackByte = new ByteArrayInputStream(new byte[] { 0 });
+ return new InflaterInputStream(new SequenceInputStream(rawStream, hackByte),
+ new Inflater(true));
+ }
+
+ @Override
+ public long size() {
+ return mUncompressedSize;
+ }
+
+ @Override
+ public EntrySource innerCompressed() {
+ return mDeflatedSource;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/StoredEntry.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/StoredEntry.java
new file mode 100644
index 0000000..c3addfd
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/StoredEntry.java
@@ -0,0 +1,544 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.primitives.Ints;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A stored entry represents a file in the zip. The entry may or may not be written to the zip
+ * file.
+ * <p>
+ * Stored entries provide the operations that are related to the files themselves, not to the zip.
+ * It is through the {@code StoredEntry} class that entries can be deleted ({@link #delete()},
+ * open ({@link #open()}) or realigned ({@link #realign()}).
+ * <p>
+ * Entries are not created directly. They are created using
+ * {@link ZFile#add(String, EntrySource, CompressionMethod)} and obtained from the zip file
+ * using {@link ZFile#get(String)} or {@link ZFile#entries()}.
+ * <p>
+ * Most of the data in the an entry is in the Central Directory Header. This includes the name,
+ * compression method, file compressed and uncompressed sizes, CRC32 checksum, etc. The CDH can
+ * be obtained using the {@link #getCentralDirectoryHeader()} method.
+ */
+public class StoredEntry {
+ /**
+ * Signature of the data descriptor.
+ */
+ private static final int DATA_DESC_SIGNATURE = 0x08074b50;
+
+ /**
+ * Local header field: signature.
+ */
+ private static final ZipField.F4 F_LOCAL_SIGNATURE = new ZipField.F4(0, 0x04034b50,
+ "Signature");
+
+ /**
+ * Local header field: version to extract, should match the CDH's.
+ */
+ private static final ZipField.F2 F_VERSION_EXTRACT = new ZipField.F2(
+ F_LOCAL_SIGNATURE.endOffset(), "Version to extract",
+ new ZipFieldInvariantNonNegative());
+
+ /**
+ * Local header field: GP bit flag, should match the CDH's.
+ */
+ private static final ZipField.F2 F_GP_BIT = new ZipField.F2(F_VERSION_EXTRACT.endOffset(),
+ "GP bit flag");
+
+ /**
+ * Local header field: compression method, should match the CDH's.
+ */
+ private static final ZipField.F2 F_METHOD = new ZipField.F2(F_GP_BIT.endOffset(),
+ "Compression method", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Local header field: last modification time, should match the CDH's.
+ */
+ private static final ZipField.F2 F_LAST_MOD_TIME = new ZipField.F2(F_METHOD.endOffset(),
+ "Last modification time");
+
+ /**
+ * Local header field: last modification time, should match the CDH's.
+ */
+ private static final ZipField.F2 F_LAST_MOD_DATE = new ZipField.F2(F_LAST_MOD_TIME.endOffset(),
+ "Last modification date");
+
+ /**
+ * Local header field: CRC32 checksum, should match the CDH's. 0 if there is no data.
+ */
+ private static final ZipField.F4 F_CRC32 = new ZipField.F4(F_LAST_MOD_DATE.endOffset(),
+ "CRC32");
+
+ /**
+ * Local header field: compressed size, size the data takes in the zip file.
+ */
+ private static final ZipField.F4 F_COMPRESSED_SIZE = new ZipField.F4(F_CRC32.endOffset(),
+ "Compressed size", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Local header field: uncompressed size, size the data takes after extraction.
+ */
+ private static final ZipField.F4 F_UNCOMPRESSED_SIZE = new ZipField.F4(
+ F_COMPRESSED_SIZE.endOffset(), "Uncompressed size", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Local header field: length of the file name.
+ */
+ private static final ZipField.F2 F_FILE_NAME_LENGTH = new ZipField.F2(
+ F_UNCOMPRESSED_SIZE.endOffset(), "@File name length",
+ new ZipFieldInvariantNonNegative());
+
+ /**
+ * Local header filed: length of the extra field.
+ */
+ private static final ZipField.F2 F_EXTRA_LENGTH = new ZipField.F2(
+ F_FILE_NAME_LENGTH.endOffset(), "Extra length", new ZipFieldInvariantNonNegative());
+
+ /**
+ * Local header size (fixed part, not counting file name or extra field).
+ */
+ private static final int FIXED_LOCAL_FILE_HEADER_SIZE = F_EXTRA_LENGTH.endOffset();
+
+ /**
+ * Type of entry.
+ */
+ @NonNull
+ private StoredEntryType mType;
+
+ /**
+ * The central directory header with information about the file.
+ */
+ @NonNull
+ private CentralDirectoryHeader mCdh;
+
+ /**
+ * The file this entry is associated with
+ */
+ @NonNull
+ private ZFile mFile;
+
+ /**
+ * Has this entry been deleted?
+ */
+ private boolean mDeleted;
+
+ /**
+ * Extra field specified in the local directory.
+ */
+ private byte[] mLocalExtra;
+
+ /**
+ * Type of data descriptor associated with the entry.
+ */
+ @NonNull
+ private DataDescriptorType mDataDescriptorType;
+
+ /**
+ * Source for this entry's data. If this entry is a directory, this source has to have zero
+ * size.
+ */
+ @NonNull
+ private EntrySource mSource;
+
+ /**
+ * Creates a new stored entry.
+ *
+ * @param header the header with the entry information; if the header does not contain an
+ * offset it means that this entry is not yet written in the zip file
+ * @param file the zip file containing the entry
+ * @throws IOException failed to create the entry
+ */
+ StoredEntry(@NonNull CentralDirectoryHeader header, @NonNull ZFile file) throws IOException {
+ mCdh = header;
+ mFile = file;
+ mDeleted = false;
+
+ if (header.getOffset() >= 0) {
+ readLocalHeader();
+
+ /*
+ * Since the file is already in the zip, dynamically create a source that will read
+ * the file from the zip when needed.
+ */
+ mSource = createSourceFromZip();
+ } else {
+ /*
+ * New file, no data defined yet. Create a dummy source. This will eventually be
+ * replaced with a setSource(). Note that this source has a problem as its size()
+ * method will return a value different from mCdh.compressedSize(), which is
+ * inconsistent. However, we expect this source to be replaced with something useful
+ * before anyone calls open()...
+ */
+ mSource = new ByteArrayEntrySource(new byte[0]);
+
+ /*
+ * There is no local extra data for new files.
+ */
+ mLocalExtra = new byte[0];
+ }
+
+ /*
+ * It seems that zip utilities store directories as names ending with "/".
+ * This seems to be respected by all zip utilities but I could not find there anywhere
+ * in the specification.
+ */
+ if (mCdh.getName().endsWith(Character.toString(ZFile.SEPARATOR))) {
+ mType = StoredEntryType.DIRECTORY;
+ Verify.verify(mSource.size() == 0, "Directory source has %s bytes.", mSource.size());
+ Verify.verify(mCdh.getCrc32() == 0, "Directory has CRC32 = %s.", mCdh.getCrc32());
+ Verify.verify(mCdh.getUncompressedSize() == 0, "Directory has uncompressed size = %s.",
+ mCdh.getUncompressedSize());
+ Verify.verify(mCdh.getCompressedSize() == 0, "Directory has compressed size = %s.",
+ mCdh.getCompressedSize());
+ } else {
+ mType = StoredEntryType.FILE;
+ }
+
+ /*
+ * By default we assume there is no data descriptor but we may override this later on if
+ * the CRC is marked as deferred in the header's GP Bit.
+ */
+ mDataDescriptorType = DataDescriptorType.NO_DATA_DESCRIPTOR;
+ if (header.getGpBit().isDeferredCrc()) {
+ /*
+ * If the deferred CRC bit exists, then we have an extra descriptor field. This extra
+ * field may have a signature.
+ */
+ Verify.verify(header.getOffset() >= 0, "Files that are not on disk cannot have the "
+ + "deferred CRC bit set.");
+ readDataDescriptorRecord();
+ }
+ }
+
+ /**
+ * Obtains the size of the local header of this entry.
+ *
+ * @return the local header size in bytes
+ */
+ int getLocalHeaderSize() {
+ Preconditions.checkState(!mDeleted, "mDeleted");
+ return FIXED_LOCAL_FILE_HEADER_SIZE + mCdh.getName().length() + mLocalExtra.length;
+ }
+
+ /**
+ * Obtains the size of the whole entry on disk, including local header and data descriptor.
+ *
+ * @return the number of bytes
+ */
+ long getInFileSize() {
+ Preconditions.checkState(!mDeleted, "mDeleted");
+ return mCdh.getCompressedSize() + getLocalHeaderSize() + mDataDescriptorType.size;
+ }
+
+ /**
+ * Obtains a stream that allows reading from the entry.
+ *
+ * @return a stream that will return as many bytes as the uncompressed entry size
+ * @throws IOException failed to open the stream
+ */
+ @NonNull
+ public InputStream open() throws IOException {
+ return mSource.open();
+ }
+
+ /**
+ * Obtains the contents of the file.
+ *
+ * @return a byte array with the contents of the file (uncompressed if the file was compressed)
+ * @throws IOException failed to read the file
+ */
+ @NonNull
+ public byte[] read() throws IOException {
+ InputStream is = open();
+ boolean threw = true;
+ try {
+ byte[] r = ByteStreams.toByteArray(is);
+ threw = false;
+ return r;
+ } finally {
+ Closeables.close(is, threw);
+ }
+ }
+
+ /**
+ * Obtains the type of entry.
+ *
+ * @return the type of entry
+ */
+ @NonNull
+ public StoredEntryType getType() {
+ Preconditions.checkState(!mDeleted, "mDeleted");
+ return mType;
+ }
+
+ /**
+ * Deletes this entry from the zip file. Invoking this method doesn't update the zip itself.
+ * To eventually write updates to disk, {@link ZFile#update()} must be called.
+ *
+ * @throws IOException failed to delete the entry
+ */
+ public void delete() throws IOException {
+ delete(true);
+ }
+
+ /**
+ * Deletes this entry from the zip file. Invoking this method doesn't update the zip itself.
+ * To eventually write updates to disk, {@link ZFile#update()} must be called.
+ *
+ * @param notify should listeners be notified of the deletion? This will only be
+ * {@code false} if the entry is being removed as part of a replacement
+ * @throws IOException failed to delete the entry
+ */
+ void delete(boolean notify) throws IOException {
+ Preconditions.checkState(!mDeleted, "mDeleted");
+ mFile.delete(this, notify);
+ mDeleted = true;
+ }
+
+ /**
+ * Obtains the CDH associated with this entry.
+ *
+ * @return the CDH
+ */
+ @NonNull
+ public CentralDirectoryHeader getCentralDirectoryHeader() {
+ return mCdh;
+ }
+
+ /**
+ * Reads the file's local header and verifies that it matches the Central Directory
+ * Header provided in the constructor. This method should only be called if the entry already
+ * exists on disk; new entries do not have local headers.
+ * <p>
+ * This method will define the {@link #mLocalExtra} field that is only defined in the
+ * local descriptor.
+ *
+ * @throws IOException failed to read the local header
+ */
+ private void readLocalHeader() throws IOException {
+ byte[] localHeader = new byte[FIXED_LOCAL_FILE_HEADER_SIZE];
+ mFile.directFullyRead(mCdh.getOffset(), localHeader);
+
+ ByteSource byteSource = ByteSource.wrap(localHeader);
+ F_LOCAL_SIGNATURE.verify(byteSource);
+ F_VERSION_EXTRACT.verify(byteSource, mCdh.getVersionExtract());
+ F_GP_BIT.verify(byteSource, mCdh.getGpBit().getValue());
+ F_METHOD.verify(byteSource, mCdh.getMethod().methodCode);
+ F_LAST_MOD_TIME.verify(byteSource, mCdh.getLastModTime());
+ F_LAST_MOD_DATE.verify(byteSource, mCdh.getLastModDate());
+ if (mCdh.getGpBit().isDeferredCrc()) {
+ F_CRC32.verify(byteSource, 0);
+ F_COMPRESSED_SIZE.verify(byteSource, 0);
+ F_UNCOMPRESSED_SIZE.verify(byteSource, 0);
+ } else {
+ F_CRC32.verify(byteSource, mCdh.getCrc32());
+ F_COMPRESSED_SIZE.verify(byteSource, mCdh.getCompressedSize());
+ F_UNCOMPRESSED_SIZE.verify(byteSource, mCdh.getUncompressedSize());
+ }
+
+ F_FILE_NAME_LENGTH.verify(byteSource, mCdh.getName().length());
+ long extraLength = F_EXTRA_LENGTH.read(byteSource);
+ long fileNameStart = mCdh.getOffset() + F_EXTRA_LENGTH.endOffset();
+ byte[] fileNameData = new byte[mCdh.getName().length()];
+ mFile.directFullyRead(fileNameStart, fileNameData);
+ String fileName = new String(fileNameData, Charsets.US_ASCII);
+ if (!fileName.equals(mCdh.getName())) {
+ throw new IOException("Central directory reports file as being named '" + mCdh.getName()
+ + "' but local header reports file being named '" + fileName + "'.");
+ }
+
+ long localExtraStart = fileNameStart + fileName.length();
+ mLocalExtra = new byte[Ints.checkedCast(extraLength)];
+ mFile.directFullyRead(localExtraStart, mLocalExtra);
+ }
+
+ /**
+ * Reads the data descriptor record. This method can only be invoked once it is established
+ * that a data descriptor does exist. It will read the data descriptor and check that the data
+ * described there matches the data provided in the Central Directory.
+ * <p>
+ * This method will set the {@link #mDataDescriptorType} field to the appropriate type of
+ * data descriptor record.
+ *
+ * @throws IOException failed to read the data descriptor record
+ */
+ private void readDataDescriptorRecord() throws IOException {
+ long ddStart = mCdh.getOffset() + FIXED_LOCAL_FILE_HEADER_SIZE
+ + mCdh.getName().length() + mLocalExtra.length + mCdh.getCompressedSize();
+ byte ddData[] = new byte[DataDescriptorType.DATA_DESCRIPTOR_WITH_SIGNATURE.size];
+ mFile.directFullyRead(ddStart, ddData);
+
+ ByteSource ddSource = ByteSource.wrap(ddData);
+
+ ZipField.F4 signatureField = new ZipField.F4(0, "Data descriptor signature");
+ long sig = signatureField.read(ddSource);
+ if (sig == DATA_DESC_SIGNATURE) {
+ mDataDescriptorType = DataDescriptorType.DATA_DESCRIPTOR_WITH_SIGNATURE;
+ ddSource = ddSource.slice(4, ddSource.size());
+ } else {
+ mDataDescriptorType = DataDescriptorType.DATA_DESCRIPTOR_WITHOUT_SIGNATURE;
+ }
+
+ ZipField.F4 crc32Field = new ZipField.F4(0, "CRC32");
+ ZipField.F4 compressedField = new ZipField.F4(crc32Field.endOffset(), "CRC32");
+ ZipField.F4 uncompressedField = new ZipField.F4(compressedField.endOffset(), "CRC32");
+
+ crc32Field.verify(ddSource, mCdh.getCrc32());
+ compressedField.verify(ddSource, mCdh.getCompressedSize());
+ uncompressedField.verify(ddSource, mCdh.getUncompressedSize());
+ }
+
+ /**
+ * Changes the data source for this entry. This is used when creating an entry and setting the
+ * source that will contain its data. Eventually, when the zip is written, this source is
+ * replaced with one created by calling {@link #createSourceFromZip()}. This method can only
+ * be called for files, not for directories.
+ *
+ * @param source the source that defines the contents of this entry
+ */
+ void setSource(@NonNull EntrySource source) {
+ Preconditions.checkArgument(source.size() == mCdh.getUncompressedSize(),
+ "Source has incorrect size (source size is %s, uncompressed size is %s)",
+ source.size(), mCdh.getUncompressedSize());
+
+ mSource = source;
+ }
+
+ /**
+ * Creates a new {@link #mSource} that reads file data from the zip file.
+ *
+ * @return the source
+ */
+ @NonNull
+ EntrySource createSourceFromZip() {
+ Preconditions.checkState(mCdh.getOffset() >= 0, "No data in zip. Cannot create source.");
+
+ /*
+ * Create a source that will return whatever is on the zip file.
+ */
+ EntrySource source = new EntrySource() {
+ @NonNull
+ @Override
+ public InputStream open() throws IOException {
+ Preconditions.checkState(!mDeleted, "mDeleted");
+
+ long dataStart = mCdh.getOffset() + getLocalHeaderSize();
+ long dataEnd = dataStart + mCdh.getCompressedSize();
+
+ return mFile.directOpen(dataStart, dataEnd);
+ }
+
+ @Override
+ public long size() {
+ return mCdh.getCompressedSize();
+ }
+
+ @Override
+ public EntrySource innerCompressed() {
+ return null;
+ }
+ };
+
+ /*
+ * If the contents are deflated, wrap that source in an inflater source so we get the
+ * uncompressed data.
+ */
+ if (mCdh.getMethod() == CompressionMethod.DEFLATE) {
+ source = new InflaterEntrySource(source, mCdh.getUncompressedSize());
+ }
+
+ return source;
+ }
+
+ /**
+ * Obtains the source data for this entry. This method can only be called for files, it
+ * cannot be called for directories.
+ *
+ * @return the entry source
+ */
+ @NonNull
+ EntrySource getSource() {
+ return mSource;
+ }
+
+ /**
+ * Obtains the type of data descriptor used in the entry.
+ *
+ * @return the type of data descriptor
+ */
+ @NonNull
+ public DataDescriptorType getDataDescriptorType() {
+ return mDataDescriptorType;
+ }
+
+ /**
+ * Obtains the local header data.
+ *
+ * @return the header data
+ * @throws IOException failed to get header byte data
+ */
+ @NonNull
+ byte[] toHeaderData() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ F_LOCAL_SIGNATURE.write(out);
+ F_VERSION_EXTRACT.write(out, mCdh.getVersionExtract());
+ F_GP_BIT.write(out, mCdh.getGpBit().getValue());
+ F_METHOD.write(out, mCdh.getMethod().methodCode);
+ F_LAST_MOD_TIME.write(out, mCdh.getLastModTime());
+ F_LAST_MOD_DATE.write(out, mCdh.getLastModDate());
+ F_CRC32.write(out, mCdh.getCrc32());
+ F_COMPRESSED_SIZE.write(out, mCdh.getCompressedSize());
+ F_UNCOMPRESSED_SIZE.write(out, mCdh.getUncompressedSize());
+ F_FILE_NAME_LENGTH.write(out, mCdh.getName().length());
+ F_EXTRA_LENGTH.write(out, mLocalExtra.length);
+
+ out.write(mCdh.getName().getBytes(Charsets.US_ASCII));
+ out.write(mLocalExtra);
+
+ return out.toByteArray();
+ }
+
+ /**
+ * Requests that this entry be realigned. If this entry is already aligned according to the
+ * rules in {@link ZFile} then this method does nothing. Otherwise it will move the file's data
+ * into memory and place it in a different area of the zip.
+ *
+ * @return has this file been changed? Note that if the entry has not yet been written on the
+ * file, realignment does not count as a change as nothing needs to be updated in the file;
+ * also, if the entry has been changed, this object may have been marked as deleted and a new
+ * stored entry may need to be fetched from the file
+ * @throws IOException failed to realign the entry; the entry may no longer exist in the zip
+ * file
+ */
+ public boolean realign() throws IOException {
+ Preconditions.checkState(!mDeleted, "Entry has been deleted.");
+
+ return mFile.realign(this);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/StoredEntryType.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/StoredEntryType.java
new file mode 100644
index 0000000..22983ab
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/StoredEntryType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+/**
+ * Type of stored entry.
+ */
+public enum StoredEntryType {
+ /**
+ * Entry is a file.
+ */
+ FILE,
+
+ /**
+ * Entry is a directory.
+ */
+ DIRECTORY
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZFile.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZFile.java
new file mode 100644
index 0000000..a953784
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZFile.java
@@ -0,0 +1,1693 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.packaging.zip.utils.CachedFileContents;
+import com.android.builder.internal.packaging.zip.utils.LittleEndianUtils;
+import com.android.builder.internal.packaging.zip.utils.RandomAccessFileUtils;
+import com.android.builder.internal.utils.IOExceptionFunction;
+import com.android.builder.internal.utils.IOExceptionRunnable;
+import com.android.utils.FileUtils;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Closer;
+import com.google.common.primitives.Ints;
+import com.google.common.io.Files;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+/**
+ * The {@code ZFile} provides the main interface for interacting with zip files. A {@code ZFile}
+ * can be created on a new file or in an existing file. Once created, files can be added or removed
+ * from the zip file.
+ * <p>
+ * Changes in the zip file are always deferred. Any change requested is made in memory and written
+ * to disk only when {@link #update()} or {@link #close()} is invoked.
+ * <p>
+ * Zip files are open initially in read-only mode and will switch to read-write when needed. This
+ * is done automatically. Because modifications to the file are done in-memory, the zip file can
+ * be manipulated when closed. When invoking {@link #update()} or {@link #close()} the zip file
+ * will be reopen and changes will be written. However, the zip file cannot be modified outside
+ * the control of {@code ZFile}. So, if a {@code ZFile} is closed, modified outside and then a file
+ * is added or removed from the zip file, when reopening the zip file, {@link ZFile} will detect
+ * the outside modification and will fail.
+ * <p>
+ * In memory manipulation means that files added to the zip file are kept in memory until written
+ * to disk. This provides much faster operation and allows better zip file allocation (see below).
+ * It may, however, increase the memory footprint of the application. When adding large files, if
+ * memory consumption is a concern, a call to {@link #update()} will actually write the file to
+ * disk and discard the memory buffer.
+ * <p>
+ * {@code ZFile} keeps track of allocation inside of the zip file. If a file is deleted, its space
+ * is marked as freed and will be reused for an added file if it fits in the space. Allocation of
+ * files to empty areas is done using a <em>best fit</em> algorithm. When adding a file, if it
+ * doesn't fit in any free area, the zip file will be extended.
+ * <p>
+ * {@code ZFile} provides a fast way to merge data from another zip file
+ * (see {@link #mergeFrom(ZFile, Set)}) avoiding recompression and copying of equal files. When
+ * merging, patterns of files may be provided that are ignored. This allows handling special files
+ * in the merging process, such as files in {@code META-INF}.
+ * <p>
+ * When adding files to the zip file, unless files are explicitly required to be stored, files will
+ * be deflated. However, deflating will not occur if the deflated file is larger then the stored
+ * file, <em>e.g.</em> if compression would yield a bigger file.
+ * <p>
+ * Because {@code ZFile} was designed to be used in a build system and not as general-purpose
+ * zip utility, it is very strict (and unforgiving) about the zip format and unsupported features.
+ * <p>
+ * {@code ZFile} supports <em>alignment</em>. Alignment means that file data (not entries -- the
+ * local header must be discounted) must start at offsets that are multiple of a number -- the
+ * alginment. Alignment is defined by setting rules in an {@link AlignmentRules} object that can
+ * be obtained using {@link #getAlignmentRules()}.
+ * <p>
+ * When a file is added to the zip, the alignment rules will be checked and alignment will be
+ * honored when positioning the file in the zip. This means that unused spaces in the zip may
+ * be generated as a result. However, alignment of existing entries will not be changed.
+ * <p>
+ * Entries can be realigned individually (see {@link StoredEntry#realign()} or the full zip file
+ * may be realigned (see {@link #realign()}). When realigning the full zip entries that are already
+ * realigned will not be affected.
+ * <p>
+ * Because realignment may cause files to move in the zip, realignment is done in-memory meaning
+ * that files that need to change location will moved to memory and will only be flushed when
+ * either {@link #update()} or {@link #close()} are called.
+ * <p>
+ * To allow whole-apk signing, the {@code ZFile} allows the central directory location to be
+ * offset by a fixed amount. This amount can be set using the {@link #setExtraDirectoryOffset(long)}
+ * method. Setting a non-zero value will add extra (unused) space in the zip file before the
+ * central directory. This value can be changed at any time and it will force the central directory
+ * rewritten when the file is updated or closed.
+ * <p>
+ * {@code ZFile} provides an extension mechanism to allow objects to register with the file
+ * and be notified when changes to the file happen. This should be used
+ * to add extra features to the zip file while providing strong decoupling. See
+ * {@link ZFileExtension}, {@link ZFile#addZFileExtension(ZFileExtension)} and
+ * {@link ZFile#removeZFileExtension(ZFileExtension)}.
+ * <p>
+ * This class is <strong>not</strong> thread-safe. Neither are any of the classes associated with
+ * it in this package.
+ */
+public class ZFile implements Closeable {
+
+ /**
+ * The file separator in paths in the zip file. This is fixed by the zip specification
+ * (section 4.4.17).
+ */
+ public static final char SEPARATOR = '/';
+
+ /**
+ * Minimum size the EOCD can have.
+ */
+ private static final int MIN_EOCD_SIZE = 22;
+
+ /**
+ * Number of bytes of the Zip64 EOCD locator record.
+ */
+ private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
+
+ /**
+ * How many bytes to look back from the end of the file to look for the EOCD signature.
+ */
+ private static final int LAST_BYTES_TO_READ = 65535 + MIN_EOCD_SIZE;
+
+ /**
+ * Signature of the Zip64 EOCD locator record.
+ */
+ private static final int ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50;
+
+ /**
+ * Size of buffer for I/O operations.
+ */
+ private static final int IO_BUFFER_SIZE = 1024 * 1024;
+
+ /**
+ * File zip file.
+ */
+ @NonNull
+ private final File mFile;
+
+ /**
+ * The random access file used to access the zip file. This will be {@code null} if and only
+ * if {@link #mState} is {@link ZipFileState#CLOSED}.
+ */
+ @Nullable
+ private RandomAccessFile mRaf;
+
+ /**
+ * The map containing the in-memory contents of the zip file. It keeps track of which parts of
+ * the zip file are used and which are not.
+ */
+ @NonNull
+ private final FileUseMap mMap;
+
+ /**
+ * The EOCD entry. Will be {@code null} if there is no EOCD (because the zip is new) or the
+ * one that exists on disk is no longer valid (because the zip has been changed).
+ */
+ @Nullable
+ private FileUseMapEntry<Eocd> mEocdEntry;
+
+ /**
+ * The Central Directory entry. Will be {@code null} if there is no Central Directory (because
+ * the zip is new) or because the one that exists on disk is no longer valid (because the zip
+ * has been changed).
+ */
+ @Nullable
+ private FileUseMapEntry<CentralDirectory> mDirectoryEntry;
+
+ /**
+ * All entries in the zip file. It includes in-memory changes and may not reflect what is
+ * written on disk.
+ */
+ @NonNull
+ private final Map<String, FileUseMapEntry<StoredEntry>> mEntries;
+
+ /**
+ * Current state of the zip file.
+ */
+ @NonNull
+ private ZipFileState mState;
+
+ /**
+ * Are the in-memory changes that have not been written to the zip file?
+ */
+ private boolean mDirty;
+
+ /**
+ * Non-{@code null} only if the file is currently closed. Used to detect if the zip is
+ * modified outside this object's control. If the file has never been written, this will
+ * be {@code null} even if it is closed.
+ */
+ @Nullable
+ private CachedFileContents<Object> mClosedControl;
+
+ /**
+ * The set of alignment rules.
+ */
+ @NonNull
+ private final AlignmentRules mAlignmentRules;
+
+ /**
+ * Extensions registered with the file.
+ */
+ @NonNull
+ private final List<ZFileExtension> mExtensions;
+
+ /**
+ * When notifying extensions, extensions may request that some runnables are executed. This
+ * list collects all runnables by the order they were requested. Together with
+ * {@link #mIsNotifying}, it is used to avoid reordering notifications.
+ */
+ @NonNull
+ private final List<IOExceptionRunnable> mToRun;
+
+ /**
+ * {@code true} when {@link #notify(IOExceptionFunction)} is notifying extensions. Used
+ * to avoid reordering notifications.
+ */
+ private boolean mIsNotifying;
+
+ /**
+ * An extra offset for the central directory location. {@code 0} if the central directory
+ * should be written in its standard location.
+ */
+ private long mExtraDirectoryOffset;
+
+
+ /**
+ * Creates a new zip file. If the zip file does not exist, then no file is created at this
+ * point and {@code ZFile} will contain an empty structure. However, an (empty) zip file will
+ * be created if either {@link #update()} or {@link #close()} are used. If a zip file exists,
+ * it will be parsed and read.
+ *
+ * @param file the zip file
+ * @throws IOException some file exists but could not be read
+ */
+ public ZFile(@NonNull File file) throws IOException {
+ mFile = file;
+ mMap = new FileUseMap(0);
+ mDirty = false;
+ mClosedControl = null;
+ mAlignmentRules = new AlignmentRules();
+ mExtensions = Lists.newArrayList();
+ mToRun = Lists.newArrayList();
+
+ if (file.exists()) {
+ mState = ZipFileState.OPEN_RO;
+ mRaf = new RandomAccessFile(file, "r");
+ } else {
+ mState = ZipFileState.CLOSED;
+ mRaf = null;
+ mDirty = true;
+ }
+
+ mEntries = Maps.newHashMap();
+ mExtraDirectoryOffset = 0;
+
+ try {
+ if (mState != ZipFileState.CLOSED) {
+ long rafSize = mRaf.length();
+ if (rafSize > Integer.MAX_VALUE) {
+ throw new IOException("File exceeds size limit of " + Integer.MAX_VALUE + ".");
+ }
+
+ mMap.extend(Ints.checkedCast(rafSize));
+ readData();
+
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Nullable
+ @Override
+ public IOExceptionRunnable apply(ZFileExtension input) throws IOException {
+ return input.open();
+ }
+ });
+ }
+ } catch (IOException e) {
+ throw new IOException("Failed to read zip file '" + file.getAbsolutePath() + "'.", e);
+ }
+ }
+
+ /**
+ * Obtains all entries in the file. Entries themselves may be or not written in disk. However,
+ * all of them can be open for reading.
+ *
+ * @return all entries in the zip
+ */
+ @NonNull
+ public Set<StoredEntry> entries() {
+ Set<StoredEntry> entries = Sets.newHashSet();
+ for (FileUseMapEntry<StoredEntry> mapEntry : mEntries.values()) {
+ entries.add(mapEntry.getStore());
+ }
+
+ return entries;
+ }
+
+ /**
+ * Obtains an entry at a given path in the zip.
+ *
+ * @param path the path
+ * @return the entry at the path or {@code null} if none exists
+ */
+ @Nullable
+ public StoredEntry get(@NonNull String path) {
+ FileUseMapEntry<StoredEntry> found = mEntries.get(path);
+ if (found == null) {
+ return null;
+ }
+
+ return found.getStore();
+ }
+
+ /**
+ * Reads all the data in the zip file, except the contents of the entries themselves. This
+ * method will populate the directory and maps in the instance variables.
+ *
+ * @throws IOException failed to read the zip file
+ */
+ private void readData() throws IOException {
+ Preconditions.checkState(mState != ZipFileState.CLOSED, "mState == ZipFileState.CLOSED");
+ Preconditions.checkState(mRaf != null, "mRaf == null");
+
+ readEocd();
+ readCentralDirectory();
+
+ /*
+ * Compute where the last file ends. We will need this to compute thee extra offset.
+ */
+ long entryEndOffset;
+ long directoryStartOffset;
+
+ if (mDirectoryEntry != null) {
+ CentralDirectory directory = mDirectoryEntry.getStore();
+ assert directory != null;
+
+ entryEndOffset = 0;
+
+ for (StoredEntry entry : directory.getEntries().values()) {
+ long start = entry.getCentralDirectoryHeader().getOffset();
+ long end = start + entry.getInFileSize();
+
+ FileUseMapEntry<StoredEntry> mapEntry = mMap.add(start, end, entry);
+ mEntries.put(entry.getCentralDirectoryHeader().getName(), mapEntry);
+
+ if (end > entryEndOffset) {
+ entryEndOffset = end;
+ }
+ }
+
+ directoryStartOffset = mDirectoryEntry.getStart();
+ } else {
+ /*
+ * No directory means an empty zip file. Use the start of the EOCD to compute
+ * an existing offset.
+ */
+ Verify.verifyNotNull(mEocdEntry);
+ assert mEocdEntry != null;
+ directoryStartOffset = mEocdEntry.getStart();
+ entryEndOffset = 0;
+ }
+
+ long extraOffset = directoryStartOffset - entryEndOffset;
+ Verify.verify(extraOffset >= 0, "extraOffset (%s) < 0", extraOffset);
+ setExtraDirectoryOffset(extraOffset);
+ }
+
+ /**
+ * Finds the EOCD marker and reads it. It will populate the {@link #mEocdEntry} variable.
+ *
+ * @throws IOException failed to read the EOCD
+ */
+ private void readEocd() throws IOException {
+ Preconditions.checkState(mState != ZipFileState.CLOSED, "mState == ZipFileState.CLOSED");
+ Preconditions.checkState(mRaf != null, "mRaf == null");
+
+ /*
+ * Read the last part of the zip into memory. If we don't find the EOCD signature by then,
+ * the file is corrupt.
+ */
+ int lastToRead = LAST_BYTES_TO_READ;
+ if (lastToRead > mRaf.length()) {
+ lastToRead = Ints.checkedCast(mRaf.length());
+ }
+
+ byte[] last = new byte[lastToRead];
+ directFullyRead(mRaf.length() - lastToRead, last);
+
+ byte[] eocdSignature = new byte[] { 0x06, 0x05, 0x4b, 0x50 };
+
+ /*
+ * Start endIdx at the first possible location where the signature can be located and then
+ * move backwards. Because the EOCD must have at least MIN_EOCD size, the first byte of the
+ * signature (and first byte of the EOCD) must be located at last.length - MIN_EOCD_SIZE.
+ *
+ * Because the EOCD signature may exist in the file comment, when we find a signature we
+ * will try to read the Eocd. If we fail, we continue searching for the signature. However,
+ * we will keep the last exception in case we don't find any signature.
+ */
+ Eocd eocd = null;
+ int foundEocdSignature = -1;
+ IOException errorFindingSignature = null;
+ int eocdStart = -1;
+
+ for (int endIdx = last.length - MIN_EOCD_SIZE; endIdx >= 0; endIdx--) {
+ /*
+ * Remember: little endian...
+ */
+ if (last[endIdx] == eocdSignature[3]
+ && last[endIdx + 1] == eocdSignature[2]
+ && last[endIdx + 2] == eocdSignature[1]
+ && last[endIdx + 3] == eocdSignature[0]) {
+
+ /*
+ * We found a signature. Try to read the EOCD record.
+ */
+
+ foundEocdSignature = endIdx;
+ ByteSource eocdBytes = ByteSource.wrap(last).slice(foundEocdSignature, last.length);
+
+ try {
+ eocd = new Eocd(eocdBytes);
+ eocdStart = Ints.checkedCast(mRaf.length() - lastToRead + foundEocdSignature);
+
+ /*
+ * Make sure the EOCD takes the whole file up to the end.
+ */
+ if (eocdStart + eocd.getEocdSize() != mRaf.length()) {
+ throw new IOException("EOCD starts at " + eocdStart + " and has "
+ + eocd.getEocdSize() + " but file ends at " + mRaf.length() + ".");
+ }
+ } catch (IOException e) {
+ errorFindingSignature = e;
+ foundEocdSignature = -1;
+ eocd = null;
+ }
+ }
+ }
+
+ if (foundEocdSignature == -1) {
+ throw new IOException("EOCD signature not found in the last "
+ + lastToRead + " bytes of the file.", errorFindingSignature);
+ }
+
+ Verify.verify(eocdStart >= 0);
+
+ /*
+ * Look for the Zip64 central directory locator. If we find it, then this file is a Zip64
+ * file and we do not support it.
+ */
+ int zip64LocatorStart = eocdStart - ZIP64_EOCD_LOCATOR_SIZE;
+ if (zip64LocatorStart >= 0) {
+ byte possibleZip64Locator[] = new byte[4];
+ directFullyRead(zip64LocatorStart, possibleZip64Locator);
+ if (LittleEndianUtils.readUnsigned4Le(ByteSource.wrap(possibleZip64Locator)) ==
+ ZIP64_EOCD_LOCATOR_SIGNATURE) {
+ throw new IOException("Zip64 EOCD locator found but Zip64 format is not "
+ + "supported.");
+ }
+ }
+
+ mEocdEntry = mMap.add(eocdStart, eocdStart + eocd.getEocdSize(), eocd);
+ }
+
+ /**
+ * Reads the zip's central directory and populates the {@link #mDirectoryEntry} variable. This
+ * method can only be called after the EOCD has been read. If the central directory is empty
+ * (if there are no files on the zip archive), then {@link #mDirectoryEntry} will be set to
+ * {@code null}.
+ *
+ * @throws IOException failed to read the central directory
+ */
+ private void readCentralDirectory() throws IOException {
+ Preconditions.checkNotNull(mEocdEntry, "mEocdEntry == null");
+ Preconditions.checkNotNull(mEocdEntry.getStore(), "mEocdEntry.getStore() == null");
+ Preconditions.checkState(mState != ZipFileState.CLOSED, "mState == ZipFileState.CLOSED");
+ Preconditions.checkState(mRaf != null, "mRaf == null");
+ Preconditions.checkState(mDirectoryEntry == null, "mDirectoryEntry != null");
+
+ Eocd eocd = mEocdEntry.getStore();
+
+ long dirSize = eocd.getDirectorySize();
+ if (dirSize > Integer.MAX_VALUE) {
+ throw new IOException("Cannot read central directory with size " + dirSize + ".");
+ }
+
+ if (eocd.getDirectoryOffset() + dirSize != mEocdEntry.getStart()) {
+ throw new IOException("Central directory is stored in [" + eocd.getDirectoryOffset()
+ + " - " + (eocd.getDirectoryOffset() + dirSize) + "] and EOCD starts at "
+ + mEocdEntry.getStart() + ".");
+ }
+
+ byte[] directoryData = new byte[Ints.checkedCast(dirSize)];
+ directFullyRead(eocd.getDirectoryOffset(), directoryData);
+
+ CentralDirectory directory = CentralDirectory.makeFromData(ByteSource.wrap(directoryData),
+ eocd.getTotalRecords(), this);
+ if (eocd.getDirectorySize() > 0) {
+ mDirectoryEntry = mMap.add(eocd.getDirectoryOffset(), eocd.getDirectoryOffset()
+ + eocd.getDirectorySize(), directory);
+ }
+ }
+
+ /**
+ * Opens a portion of the zip for reading. The zip must be open for this method to be invoked.
+ * Note that if the zip has not been updated, the individual zip entries may not have been
+ * written yet.
+ *
+ * @param start the index within the zip file to start reading
+ * @param end the index within the zip file to end reading (the actual byte pointed by
+ * <em>end</em> will not be read)
+ * @return a stream that will read the portion of the file; no decompression is done, data is
+ * returned <em>as is</em>
+ * @throws IOException failed to open the zip file
+ */
+ @NonNull
+ public InputStream directOpen(final long start, final long end) throws IOException {
+ Preconditions.checkState(mState != ZipFileState.CLOSED, "mState == ZipFileState.CLOSED");
+ Preconditions.checkState(mRaf != null, "mRaf == null");
+ Preconditions.checkArgument(start >= 0, "start < 0");
+ Preconditions.checkArgument(end >= start, "end < start");
+ Preconditions.checkArgument(end <= mRaf.length(), "end > mRaf.length()");
+
+ return new InputStream() {
+ long mCurr = start;
+
+ @Override
+ public int read() throws IOException {
+ if (mCurr == end) {
+ return -1;
+ }
+
+ byte b[] = new byte[1];
+ int r = directRead(mCurr, b);
+ if (r > 0) {
+ mCurr++;
+ return b[0];
+ } else {
+ return -1;
+ }
+ }
+
+ @Override
+ public int read(@NonNull byte[] b, int off, int len) throws IOException {
+ Preconditions.checkNotNull(b, "b == null");
+ Preconditions.checkArgument(off >= 0, "off < 0");
+ Preconditions.checkArgument(off <= b.length, "off > b.length");
+ Preconditions.checkArgument(len >= 0, "len < 0");
+ Preconditions.checkArgument(off + len <= b.length, "off + len > b.length");
+
+ long availableToRead = end - mCurr;
+ long toRead = Math.min(len, availableToRead);
+
+ if (toRead == 0) {
+ return -1;
+ }
+
+ if (toRead > Integer.MAX_VALUE) {
+ throw new IOException("Cannot read " + toRead + " bytes.");
+ }
+
+ int r = directRead(mCurr, b, off, Ints.checkedCast(toRead));
+ if (r > 0) {
+ mCurr += r;
+ }
+
+ return r;
+ }
+ };
+ }
+
+ /**
+ * Deletes an entry from the zip. This method does not actually delete anything on disk. It
+ * just changes in-memory structures. Use {@link #update()} to update the contents on disk.
+ *
+ * @param entry the entry to delete
+ * @param notify should listeners be notified of the deletion? This will only be
+ * {@code false} if the entry is being removed as part of a replacement
+ * @throws IOException failed to delete the entry
+ */
+ void delete(@NonNull final StoredEntry entry, boolean notify) throws IOException {
+ String path = entry.getCentralDirectoryHeader().getName();
+ FileUseMapEntry<StoredEntry> mapEntry = mEntries.get(path);
+ Preconditions.checkNotNull(mapEntry, "mapEntry == null");
+ Preconditions.checkArgument(entry == mapEntry.getStore(), "entry != mapEntry.getStore()");
+
+ mDirty = true;
+
+ mMap.remove(mapEntry);
+ mEntries.remove(path);
+
+ if (notify) {
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Override
+ public IOExceptionRunnable apply(ZFileExtension input) throws IOException {
+ return input.removed(entry);
+ }
+ });
+ }
+ }
+
+ /**
+ * Updates the file writing new entries and removing deleted entries. This will force
+ * reopening the file as read/write if the file wasn't open in read/write mode.
+ */
+ public void update() throws IOException {
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Nullable
+ @Override
+ public IOExceptionRunnable apply(@Nullable ZFileExtension input) throws IOException {
+ Verify.verifyNotNull(input);
+ assert input != null;
+ return input.beforeUpdate();
+ }
+ });
+
+ if (!mDirty) {
+ return;
+ }
+
+ reopenRw();
+
+ /*
+ * Write new files in the zip. We identify new files because they don't have an offset
+ * in the zip where they are written although we already know, by their location in the
+ * file map, where they will be written to.
+ */
+ for (FileUseMapEntry<StoredEntry> entry : mEntries.values()) {
+ StoredEntry entryStore = entry.getStore();
+ assert entryStore != null;
+ if (entryStore.getCentralDirectoryHeader().getOffset() == -1) {
+ writeEntry(entry.getStore(), entry.getStart());
+ }
+ }
+
+ deleteDirectoryAndEocd();
+ mMap.truncate();
+
+ computeCentralDirectory();
+ computeEocd();
+
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Nullable
+ @Override
+ public IOExceptionRunnable apply(@Nullable ZFileExtension input) throws IOException {
+ Verify.verifyNotNull(input);
+ assert input != null;
+ input.entriesWritten();
+ return null;
+ }
+ });
+
+ appendCentralDirectory();
+ appendEocd();
+
+ Verify.verifyNotNull(mRaf);
+ mRaf.setLength(mMap.size());
+
+ mDirty = false;
+
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Nullable
+ @Override
+ public IOExceptionRunnable apply(@Nullable ZFileExtension input) throws IOException {
+ Verify.verifyNotNull(input);
+ assert input != null;
+ input.updated();
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Updates the file and closes it.
+ */
+ @Override
+ public void close() throws IOException {
+ update();
+ innerClose();
+
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Override
+ public IOExceptionRunnable apply(ZFileExtension input) throws IOException {
+ input.closed();
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Removes the Central Directory and EOCD from the file. This will free space for new entries
+ * as well as allowing the zip file to be truncated if files have been removed.
+ */
+ private void deleteDirectoryAndEocd() {
+ if (mDirectoryEntry != null) {
+ mMap.remove(mDirectoryEntry);
+ mDirectoryEntry = null;
+ }
+
+ if (mEocdEntry != null) {
+ mMap.remove(mEocdEntry);
+ mEocdEntry = null;
+ }
+ }
+
+ /**
+ * Writes an entry's data in the zip file. This includes everything: the local header and
+ * the data itself. After writing, the entry is updated with the offset and its source replaced
+ * with a source that reads from the zip file.
+ *
+ * @param entry the entry to write
+ * @param offset the offset at which the entry should be written
+ * @throws IOException failed to write the entry
+ */
+ private void writeEntry(@NonNull StoredEntry entry, long offset) throws IOException {
+ Preconditions.checkArgument(entry.getDataDescriptorType()
+ == DataDescriptorType. NO_DATA_DESCRIPTOR, "Cannot write entries with a data "
+ + "descriptor.");
+ Preconditions.checkNotNull(mRaf, "mRaf == null");
+ Preconditions.checkState(mState == ZipFileState.OPEN_RW, "mState != ZipFileState.OPEN_RW");
+
+ /*
+ * Place the cursor and write the local header.
+ */
+ byte[] headerData = entry.toHeaderData();
+ directWrite(offset, headerData);
+
+ /*
+ * Get the source data. If a compressed source exists, that's the one we want.
+ */
+ EntrySource source = entry.getSource();
+ if (source.innerCompressed() != null) {
+ source = source.innerCompressed();
+ assert source != null;
+ }
+
+ Verify.verify(source.innerCompressed() == null);
+
+ /*
+ * Write the source data.
+ */
+ byte[] chunk = new byte[IO_BUFFER_SIZE];
+ int r;
+ long writeOffset = offset + headerData.length;
+ InputStream is = source.open();
+ while ((r = is.read(chunk)) >= 0) {
+ directWrite(writeOffset, chunk, 0, r);
+ writeOffset += r;
+ }
+
+ is.close();
+
+ /*
+ * Set the entry's offset and create the entry source.
+ */
+ entry.getCentralDirectoryHeader().setOffset(offset);
+ entry.createSourceFromZip();
+ }
+
+ /**
+ * Computes the central directory. The central directory must not have been computed yet. When
+ * this method finishes, the central directory has been computed {@link #mDirectoryEntry},
+ * unless the directory is empty in which case {@link #mDirectoryEntry}
+ * is left as {@code null}. Nothing is written to disk as a result of this method's invocation.
+ *
+ * @throws IOException failed to append the central directory
+ */
+ private void computeCentralDirectory() throws IOException {
+ Preconditions.checkState(mState == ZipFileState.OPEN_RW, "mState != ZipFileState.OPEN_RW");
+ Preconditions.checkNotNull(mRaf, "mRaf == null");
+ Preconditions.checkState(mDirectoryEntry == null, "mDirectoryEntry == null");
+
+ Set<StoredEntry> newStored = Sets.newHashSet();
+ for (FileUseMapEntry<StoredEntry> mapEntry : mEntries.values()) {
+ newStored.add(mapEntry.getStore());
+ }
+
+ CentralDirectory newDirectory = CentralDirectory.makeFromEntries(newStored, this);
+ byte[] newDirectoryBytes = newDirectory.toBytes();
+ long directoryOffset = mMap.size() + mExtraDirectoryOffset;
+
+ mMap.extend(directoryOffset+ newDirectoryBytes.length);
+
+ if (newDirectoryBytes.length > 0) {
+ mDirectoryEntry = mMap.add(directoryOffset, directoryOffset + newDirectoryBytes.length,
+ newDirectory);
+ }
+ }
+
+ /**
+ * Writes the central directory to the end of the zip file. {@link #mDirectoryEntry} may be
+ * {@code null} only if there are no files in the archive.
+ *
+ * @throws IOException failed to append the central directory
+ */
+ private void appendCentralDirectory() throws IOException {
+ Preconditions.checkState(mState == ZipFileState.OPEN_RW, "mState != ZipFileState.OPEN_RW");
+ Preconditions.checkNotNull(mRaf, "mRaf == null");
+
+ if (mEntries.isEmpty()) {
+ Preconditions.checkState(mDirectoryEntry == null, "mDirectoryEntry != null");
+ return;
+ }
+
+ Preconditions.checkNotNull(mDirectoryEntry, "mDirectoryEntry != null");
+
+ CentralDirectory newDirectory = mDirectoryEntry.getStore();
+ Verify.verifyNotNull(newDirectory, "newDirectory != null");
+
+ byte[] newDirectoryBytes = newDirectory.toBytes();
+ long directoryOffset = mDirectoryEntry.getStart();
+
+ /*
+ * It is fine to seek beyond the end of file. Seeking beyond the end of file will not extend
+ * the file. Even if we do not have any directory data to write, the extend() call below
+ * will force the file to be extended leaving exactly mExtraDirectoryOffset bytes empty at
+ * the beginning.
+ */
+ directWrite(directoryOffset, newDirectoryBytes);
+ }
+
+ /**
+ * Obtains the byte array representation of the central directory. The central directory must
+ * have been already computed. If there are no entries in the zip, the central directory will be
+ * empty.
+ *
+ * @return the byte representation, or an empty array if there are no entries in the zip
+ * @throws IOException failed to compute the central directory byte representation
+ */
+ @NonNull
+ public byte[] getCentralDirectoryBytes() throws IOException {
+ if (mEntries.isEmpty()) {
+ Preconditions.checkState(mDirectoryEntry == null, "mDirectoryEntry != null");
+ return new byte[0];
+ }
+
+ Preconditions.checkNotNull(mDirectoryEntry, "mDirectoryEntry == null");
+
+ CentralDirectory cd = mDirectoryEntry.getStore();
+ Verify.verifyNotNull(cd, "cd == null");
+ return cd.toBytes();
+ }
+
+ /**
+ * Computes the EOCD. This creates a new {@link #mEocdEntry}. The
+ * central directory must already be written. If {@link #mDirectoryEntry} is {@code null}, then
+ * the zip file must not have any entries.
+ *
+ * @throws IOException failed to write the EOCD
+ */
+ private void computeEocd() throws IOException {
+ Preconditions.checkState(mState == ZipFileState.OPEN_RW, "mState != ZipFileState.OPEN_RW");
+ Preconditions.checkNotNull(mRaf, "mRaf == null");
+ if (mDirectoryEntry == null) {
+ Preconditions.checkState(mEntries.isEmpty(),
+ "mDirectoryEntry == null && !mEntries.isEmpty()");
+ }
+
+ long dirStart;
+ long dirSize = 0;
+
+ if (mDirectoryEntry != null) {
+ CentralDirectory directory = mDirectoryEntry.getStore();
+ assert directory != null;
+
+ dirStart = mDirectoryEntry.getStart();
+ dirSize = mDirectoryEntry.getSize();
+ Verify.verify(directory.getEntries().size() == mEntries.size());
+ } else {
+ /*
+ * If we do not have a directory, then we must leave any requested offset empty.
+ */
+ dirStart = mExtraDirectoryOffset;
+ }
+
+ Eocd eocd = new Eocd(mEntries.size(), dirStart, dirSize);
+
+ byte[] eocdBytes = eocd.toBytes();
+ long eocdOffset = mMap.size();
+
+ mMap.extend(eocdOffset + eocdBytes.length);
+
+ mEocdEntry = mMap.add(eocdOffset, eocdOffset + eocdBytes.length, eocd);
+ }
+
+ /**
+ * Writes the EOCD to the end of the zip file. This creates a new {@link #mEocdEntry}. The
+ * central directory must already be written. If {@link #mDirectoryEntry} is {@code null}, then
+ * the zip file must not have any entries.
+ *
+ * @throws IOException failed to write the EOCD
+ */
+ private void appendEocd() throws IOException {
+ Preconditions.checkState(mState == ZipFileState.OPEN_RW, "mState != ZipFileState.OPEN_RW");
+ Preconditions.checkNotNull(mRaf, "mRaf == null");
+ Preconditions.checkNotNull(mEocdEntry, "mEocdEntry == null");
+
+ Eocd eocd = mEocdEntry.getStore();
+ Verify.verifyNotNull(eocd, "eocd == null");
+
+ byte[] eocdBytes = eocd.toBytes();
+ long eocdOffset = mEocdEntry.getStart();
+
+ directWrite(eocdOffset, eocdBytes);
+ }
+
+ /**
+ * Obtains the byte array representation of the EOCD. The EOCD must have already been computed
+ * for this method to be invoked.
+ *
+ * @return the byte representation of the EOCD
+ * @throws IOException failed to obtain the byte representation of the EOCD
+ */
+ @NonNull
+ public byte[] getEocdBytes() throws IOException {
+ Preconditions.checkNotNull(mEocdEntry, "mEocdEntry == null");
+
+ Eocd eocd = mEocdEntry.getStore();
+ Verify.verifyNotNull(eocd, "eocd == null");
+ return eocd.toBytes();
+ }
+
+ /**
+ * Closes the file, if it is open.
+ *
+ * @throws IOException failed to close the file
+ */
+ private void innerClose() throws IOException {
+ if (mState == ZipFileState.CLOSED) {
+ return;
+ }
+
+ Verify.verifyNotNull(mRaf, "mRaf == null");
+
+ mRaf.close();
+ mRaf = null;
+ mState = ZipFileState.CLOSED;
+ if (mClosedControl == null) {
+ mClosedControl = new CachedFileContents<Object>(mFile);
+ }
+
+ mClosedControl.closed(null);
+ }
+
+ /**
+ * Opens (or reopens) the zip file as read-write. This method will ensure that
+ * {@link #mRaf} is not null and open for writing.
+ *
+ * @throws IOException failed to open the file, failed to close it or the file was closed and
+ * has been modified outside the control of this object
+ */
+ private void reopenRw() throws IOException {
+ if (mState == ZipFileState.OPEN_RW) {
+ return;
+ }
+
+ boolean wasClosed;
+ if (mState == ZipFileState.OPEN_RO) {
+ /*
+ * ReadAccessFile does not have a way to reopen as RW so we have to close it and
+ * open it again.
+ */
+ innerClose();
+ wasClosed = false;
+ } else {
+ wasClosed = true;
+ }
+
+ Verify.verify(mState == ZipFileState.CLOSED, "mState != ZpiFileState.CLOSED");
+ Verify.verify(mRaf == null, "mRaf != null");
+
+ if (mClosedControl != null && !mClosedControl.isValid()) {
+ throw new IOException("File '" + mFile.getAbsolutePath() + "' has been modified "
+ + "by an external application.");
+ }
+
+ mRaf = new RandomAccessFile(mFile, "rw");
+ mState = ZipFileState.OPEN_RW;
+
+ if (wasClosed) {
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Nullable
+ @Override
+ public IOExceptionRunnable apply(ZFileExtension input) throws IOException {
+ return input.open();
+ }
+ });
+ }
+ }
+
+ /**
+ * Adds a file to the archive.
+ * <p>
+ * Adding the file will not update the archive immediately. Updating will only happen
+ * when the {@link #update()} method is invoked.
+ * <p>
+ * Adding a file with the same name as an existing file will replace that file in the
+ * archive.
+ *
+ * @param name the file name (<em>i.e.</em>, path); paths should be defined using slashes
+ * and the name should not end in slash
+ * @param source the source for the file's data
+ * @param method the compression method to use for the file; even if
+ * {@link CompressionMethod#DEFLATE} is provided, {@link CompressionMethod#STORE} will be used
+ * if the result is smaller
+ * @throws IOException failed to read the source data
+ */
+ public void add(@NonNull String name, @NonNull EntrySource source,
+ @NonNull CompressionMethod method) throws IOException {
+ /*
+ * Create the data structure with information about the file. Assume we will store (and
+ * not compress) the file. We may need to change this later on.
+ */
+ CentralDirectoryHeader newFileData = new CentralDirectoryHeader(name, source.size(),
+ source.size(), CompressionMethod.STORE);
+
+ /*
+ * If we could be deflating, compress upfront so we can know whether the compressed data
+ * is smaller or larger than the uncompressed data. storeData will either be {@code null}
+ * if we didn't even try to read from the source, or will contain the raw data or
+ * compressed data if we read from the source. newMethod will have the actual method that
+ * will be used.
+ */
+ byte[] storeData = null;
+ if (method == CompressionMethod.DEFLATE) {
+ ByteArrayOutputStream sourceDataBytes = new ByteArrayOutputStream();
+
+ InputStream sourceIn = source.open();
+ ByteStreams.copy(sourceIn, sourceDataBytes);
+
+ storeData = sourceDataBytes.toByteArray();
+
+ byte[] deflatedData = deflate(storeData);
+ if (deflatedData.length < storeData.length) {
+ storeData = deflatedData;
+ newFileData.setMethod(CompressionMethod.DEFLATE);
+ newFileData.setCompressedSize(deflatedData.length);
+ }
+
+ newFileData.setCrc32(Hashing.crc32().hashBytes(storeData).padToLong());
+ }
+
+ /*
+ * If we are changing the data we're storing (by compressing), replace the source with
+ * a new one.
+ */
+ if (storeData != null) {
+ source = new ByteArrayEntrySource(storeData);
+ if (newFileData.getMethod() == CompressionMethod.DEFLATE) {
+ source = new InflaterEntrySource(source, newFileData.getUncompressedSize());
+ }
+ }
+
+ add(newFileData, source);
+ }
+
+ /**
+ * Adds a new file to the archive. This will not write anything to the zip file, the change is
+ * in-memory only.
+ *
+ * @param newFileData the data for the new file, including correct sizes and CRC32 data. The
+ * offset should be set to {@code -1} because the data should not exist anywhere.
+ * @param source the data source
+ * @throws IOException failed to add the file
+ */
+ private void add(@NonNull CentralDirectoryHeader newFileData, @NonNull EntrySource source)
+ throws IOException {
+ /*
+ * If there is a file with the same name in the archive, remove it. We remove it by
+ * calling delete() on the entry (this is the public API to remove a file from the archive).
+ * StoredEntry.delete() will call ZFile.delete(StoredEntry) to perform data structure
+ * cleanup.
+ */
+ FileUseMapEntry<StoredEntry> toReplace = mEntries.get(newFileData.getName());
+ final StoredEntry replaceStore;
+ if (toReplace != null) {
+ replaceStore = toReplace.getStore();
+ assert replaceStore != null;
+ replaceStore.delete(false);
+ } else {
+ replaceStore = null;
+ }
+
+ /*
+ * Create the new entry and sets its data source. Offset should be set to -1 automatically
+ * because this is a new file. With offset set to -1, StoredEntry does not try to verify the
+ * local header. Since this is a new file, there is no local header and not checking it is
+ * what we want to happen.
+ */
+ Verify.verify(newFileData.getOffset() == -1);
+ final StoredEntry newEntry = new StoredEntry(newFileData, this);
+ newEntry.setSource(source);
+
+ /*
+ * Find a location in the zip where this entry will be added to and create the map entry.
+ * But before looking for the new location, delete the Central Directory and EOCD to make
+ * space for the new entry. We don't want to add the entry *after* the Central Directory
+ * because we would have to update the Central Directory when updating the file and this
+ * would create a hole in the zip. Me no like holes. Holes are evil.
+ */
+ deleteDirectoryAndEocd();
+ long size = newEntry.getInFileSize();
+ int localHeaderSize = newEntry.getLocalHeaderSize();
+ int alignment = mAlignmentRules.alignment(newEntry.getCentralDirectoryHeader().getName());
+ long newOffset = mMap.locateFree(size, localHeaderSize, alignment);
+ long newEnd = newOffset + newEntry.getInFileSize();
+ if (newEnd > mMap.size()) {
+ mMap.extend(newEnd);
+ }
+
+ FileUseMapEntry<StoredEntry> fileUseMapEntry = mMap.add(newOffset, newEnd, newEntry);
+ mEntries.put(newFileData.getName(), fileUseMapEntry);
+
+ mDirty = true;
+
+ notify(new IOExceptionFunction<ZFileExtension, IOExceptionRunnable>() {
+ @Nullable
+ @Override
+ public IOExceptionRunnable apply(ZFileExtension input) {
+ return input.added(newEntry, replaceStore);
+ }
+ });
+ }
+
+ /**
+ * Performs in-memory deflation of a byte array.
+ *
+ * @param in the input data
+ * @return the deflated data
+ * @throws IOException failed to deflate
+ */
+ @NonNull
+ private static byte[] deflate(@NonNull byte[] in) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
+
+ Closer closer = Closer.create();
+ try {
+ DeflaterOutputStream dos = closer.register(new DeflaterOutputStream(output, deflater));
+ dos.write(in);
+ } catch (IOException e) {
+ throw closer.rethrow(e);
+ } finally {
+ closer.close();
+ }
+
+ return output.toByteArray();
+ }
+
+ /**
+ * Adds all files from another zip file, maintaining their compression. Files specified in
+ * <em>src</em> that are already on this file will replace the ones in this file. However, if
+ * their sizes and checksums are equal, they will be ignored.
+ * <p>
+ * This method will not perform any changes in itself, it will only update in-memory data
+ * structures. To actually write the zip file, invoke either {@link #update()} or
+ * {@link #close()}.
+ *
+ * @param src the source archive
+ * @param ignorePatterns file name patterns in <em>src</em> that should be ignored by merging;
+ * merging will behave as if these files were not there; file name matching is done by using
+ * {@code matches()}
+ * @throws IOException failed to read from <em>src</em> or write on the output
+ */
+ public void mergeFrom(@NonNull ZFile src, @NonNull Set<Pattern> ignorePatterns)
+ throws IOException {
+ nextEntry: for (StoredEntry fromEntry : src.entries()) {
+ for (Pattern p : ignorePatterns) {
+ if (p.matcher(fromEntry.getCentralDirectoryHeader().getName()).matches()) {
+ continue nextEntry;
+ }
+ }
+
+ boolean replaceCurrent = true;
+ String path = fromEntry.getCentralDirectoryHeader().getName();
+ FileUseMapEntry<StoredEntry> currentEntry = mEntries.get(path);
+
+ if (currentEntry != null) {
+ long fromSize = fromEntry.getCentralDirectoryHeader().getUncompressedSize();
+ long fromCrc = fromEntry.getCentralDirectoryHeader().getCrc32();
+
+ StoredEntry currentStore = currentEntry.getStore();
+ assert currentStore != null;
+
+ long currentSize = currentStore.getCentralDirectoryHeader().getUncompressedSize();
+ long currentCrc = currentStore.getCentralDirectoryHeader().getCrc32();
+
+ if (fromSize == currentSize && fromCrc == currentCrc) {
+ replaceCurrent = false;
+ }
+ }
+
+ if (replaceCurrent) {
+ CentralDirectoryHeader fromCdr = fromEntry.getCentralDirectoryHeader();
+ CentralDirectoryHeader newFileData = new CentralDirectoryHeader(
+ fromCdr.getName(), fromCdr.getCompressedSize(),
+ fromCdr.getUncompressedSize(),
+ fromEntry.getCentralDirectoryHeader().getMethod());
+
+ /*
+ * Read the data (read directly the compressed source if there is one).
+ */
+ EntrySource fromSource = fromEntry.getSource();
+ boolean usingCompressed;
+ EntrySource compressedSource = fromSource.innerCompressed();
+ if (compressedSource == null) {
+ Verify.verify(newFileData.getMethod() == CompressionMethod.STORE);
+ usingCompressed = false;
+ } else {
+ fromSource = compressedSource;
+ usingCompressed = true;
+ }
+
+ InputStream fromInput = fromSource.open();
+ long sourceSize = fromSource.size();
+ if (sourceSize > Integer.MAX_VALUE) {
+ throw new IOException("Cannot read source with " + sourceSize + " bytes.");
+ }
+
+ byte data[] = new byte[Ints.checkedCast(sourceSize)];
+ int read = 0;
+ while (read < data.length) {
+ int r = fromInput.read(data, read, data.length - read);
+ Verify.verify(r >= 0, "There should be at least 'size' bytes in the stream.");
+ read += r;
+ }
+
+ /*
+ * Build the new source and wrap it around an inflater source if data came from
+ * a compressed source.
+ */
+ EntrySource newSource = new ByteArrayEntrySource(data);
+ if (usingCompressed) {
+ newSource = new InflaterEntrySource(newSource, fromCdr.getUncompressedSize());
+ }
+
+ /*
+ * Add will replace any current entry with the same name.
+ */
+ add(newFileData, newSource);
+ }
+ }
+ }
+
+ /**
+ * Forcibly marks this zip file as touched, forcing it to be updated when {@link #update()}
+ * or {@link #close()} are invoked.
+ */
+ public void touch() {
+ mDirty = true;
+ }
+
+ /**
+ * Obtains the set of alignment rules in use by this file. Note that changes to the rules
+ * will only apply in general to new files (see class description for details).
+ *
+ * @return the rules that can be changed
+ */
+ @NonNull
+ public AlignmentRules getAlignmentRules() {
+ return mAlignmentRules;
+ }
+
+ /**
+ * Realigns all entries in the zip. This is equivalent to call {@link StoredEntry#realign()}
+ * for all entries in the zip file.
+ *
+ * @return has any entry been changed? Note that for entries that have not yet been written on
+ * the file, realignment does not count as a change as nothing needs to be updated in the file;
+ * entries that have been updated may have been recreated and the existing references outside
+ * of {@code ZFile} may refer to {@link StoredEntry}s that are no longer valid
+ * @throws IOException failed to realign the zip; some entries in the zip may have been lost
+ * due to the I/O error
+ */
+ public boolean realign() throws IOException {
+ boolean anyChanges = false;
+ for (StoredEntry entry : entries()) {
+ anyChanges |= entry.realign();
+ }
+
+ return anyChanges;
+ }
+
+ /**
+ * Realigns a stored entry, if necessary. Realignment is done by removing and re-adding the file
+ * if it was not aligned.
+ *
+ * @param entry the entry to realign
+ * @return has the entry been changed? Note that if the entry has not yet been written on the
+ * file, realignment does not count as a change as nothing needs to be updated in the file
+ * @throws IOException failed to read/write an entry; the entry may no longer exist in the
+ * file
+ */
+ boolean realign(@NonNull StoredEntry entry) throws IOException {
+ int expectedAlignment = mAlignmentRules.alignment(
+ entry.getCentralDirectoryHeader().getName());
+
+ FileUseMapEntry<StoredEntry> mapEntry = mEntries.get(
+ entry.getCentralDirectoryHeader().getName());
+ Verify.verify(entry == mapEntry.getStore());
+ long currentDataOffset = mapEntry.getStart() + entry.getLocalHeaderSize();
+
+ long disalignment = currentDataOffset % expectedAlignment;
+ if (disalignment == 0) {
+ /*
+ * Good. File is aligned properly.
+ */
+ return false;
+ }
+
+ if (entry.getCentralDirectoryHeader().getOffset() == -1) {
+ /*
+ * File is not aligned but it is not written. We do not really need to do much other
+ * than find another place in the map.
+ */
+ mMap.remove(mapEntry);
+ long newStart = mMap.locateFree(mapEntry.getSize(), entry.getLocalHeaderSize(),
+ expectedAlignment);
+ mapEntry = mMap.add(newStart, newStart + entry.getInFileSize(), entry);
+ mEntries.put(entry.getCentralDirectoryHeader().getName(), mapEntry);
+
+ /*
+ * Just for safety. We're modifying the in-memory structures but the file should
+ * already be marked as dirty.
+ */
+ Verify.verify(mDirty);
+
+ return false;
+
+ }
+
+ /*
+ * Get the entry data source, but check if we have a compressed one (we don't want to
+ * inflate & deflate).
+ */
+ EntrySource source = entry.getSource();
+ boolean sourceDeflated = false;
+ if (source.innerCompressed() != null) {
+ source = source.innerCompressed();
+ assert source != null;
+ sourceDeflated = true;
+ Verify.verify(entry.getCentralDirectoryHeader().getMethod()
+ == CompressionMethod.DEFLATE);
+ } else {
+ Verify.verify(entry.getCentralDirectoryHeader().getMethod() == CompressionMethod.STORE);
+ }
+
+ InputStream is = source.open();
+ boolean threw = true;
+ byte entryData[] = null;
+ try {
+ entryData = ByteStreams.toByteArray(is);
+ threw = false;
+ } finally {
+ Closeables.close(is, threw);
+ }
+
+ CentralDirectoryHeader cdh;
+ try {
+ cdh = entry.getCentralDirectoryHeader().clone();
+ } catch (CloneNotSupportedException e) {
+ Verify.verify(false);
+ return false;
+ }
+
+ cdh.setOffset(-1);
+
+ EntrySource newSource = new ByteArrayEntrySource(entryData);
+ if (sourceDeflated) {
+ newSource = new InflaterEntrySource(newSource, cdh.getUncompressedSize());
+ }
+
+ /*
+ * Add the new file. This will replace the existing one.
+ */
+ add(cdh, newSource);
+ return true;
+ }
+
+ /**
+ * Adds an extension to this zip file.
+ *
+ * @param extension the listener to add
+ */
+ public void addZFileExtension(@NonNull ZFileExtension extension) {
+ mExtensions.add(extension);
+ }
+
+ /**
+ * Removes an extension from this zip file.
+ *
+ * @param extension the listener to remove
+ */
+ public void removeZFileExtension(@NonNull ZFileExtension extension) {
+ mExtensions.remove(extension);
+ }
+
+ /**
+ * Notifies all extensions, collecting their execution requests and running them.
+ *
+ * @param function the function to apply to all listeners, it will generally invoke the
+ * notification method on the listener and return the result of that invocation
+ * @throws IOException failed to process some extensions
+ */
+ private void notify(@NonNull IOExceptionFunction<ZFileExtension, IOExceptionRunnable> function)
+ throws IOException {
+ for (ZFileExtension fl : Lists.newArrayList(mExtensions)) {
+ IOExceptionRunnable r = function.apply(fl);
+ if (r != null) {
+ mToRun.add(r);
+ }
+ }
+
+ if (!mIsNotifying) {
+ mIsNotifying = true;
+
+ try {
+ while (!mToRun.isEmpty()) {
+ IOExceptionRunnable r = mToRun.remove(0);
+ r.run();
+ }
+ } finally {
+ mIsNotifying = false;
+ }
+ }
+ }
+
+ /**
+ * Directly writes data in the zip file. <strong>Incorrect use of this method may corrupt the
+ * zip file</strong>. Invoking this method may force the zip to be reopened in read/write
+ * mode.
+ *
+ * @param offset the offset at which data should be written
+ * @param data the data to write, may be an empty array
+ * @param start start offset in {@code data} where data to write is located
+ * @param count number of bytes of data to write
+ * @throws IOException failed to write the data
+ */
+ public void directWrite(long offset, @NonNull byte[] data, int start, int count)
+ throws IOException {
+ Preconditions.checkArgument(offset >= 0, "offset < 0");
+ Preconditions.checkArgument(start >= 0, "start >= 0");
+ Preconditions.checkArgument(count >= 0, "count >= 0");
+
+ if (data.length == 0) {
+ return;
+ }
+
+ Preconditions.checkArgument(start <= data.length, "start > data.length");
+ Preconditions.checkArgument(start + count <= data.length, "start + count > data.length");
+
+ reopenRw();
+ assert mRaf != null;
+
+ mRaf.seek(offset);
+ mRaf.write(data, start, count);
+ }
+
+ /**
+ * Same as {@code directWrite(offset, data, 0, data.length)}.
+ *
+ * @param offset the offset at which data should be written
+ * @param data the data to write, may be an empty array
+ * @throws IOException failed to write the data
+ */
+ public void directWrite(long offset, @NonNull byte[] data) throws IOException {
+ directWrite(offset, data, 0, data.length);
+ }
+
+ /**
+ * Directly reads data from the zip file. Invoking this method may force the zip to be reopened
+ * in read/write mode.
+ *
+ * @param offset the offset at which data should be written
+ * @param data the array where read data should be stored
+ * @param start start position in the array where to write data to
+ * @param count how many bytes of data can be written
+ * @return how many bytes of data have been written or {@code -1} if there are no more bytes
+ * to be read
+ * @throws IOException failed to write the data
+ */
+ public int directRead(long offset, @NonNull byte[] data, int start, int count)
+ throws IOException {
+ Preconditions.checkArgument(offset >= 0, "offset < 0");
+ Preconditions.checkArgument(start >= 0, "start >= 0");
+ Preconditions.checkArgument(count >= 0, "count >= 0");
+
+ if (data.length == 0) {
+ return 0;
+ }
+
+ Preconditions.checkArgument(start <= data.length, "start > data.length");
+ Preconditions.checkArgument(start + count <= data.length, "start + count > data.length");
+
+ /*
+ * Only force a reopen if the file is closed.
+ */
+ if (mRaf == null) {
+ reopenRw();
+ assert mRaf != null;
+ }
+
+ mRaf.seek(offset);
+ return mRaf.read(data, start, count);
+ }
+
+ /**
+ * Same as {@code directRead(offset, data, 0, data.length)}.
+ *
+ * @param offset the offset at which data should be read
+ * @param data receives the read data, may be an empty array
+ * @throws IOException failed to read the data
+ */
+ public int directRead(long offset, @NonNull byte[] data) throws IOException {
+ return directRead(offset, data, 0, data.length);
+ }
+
+ /**
+ * Reads exactly @code data.length} bytes of data, failing if it was not possible to read all
+ * the requested data.
+ *
+ * @param offset the offset at which to start reading
+ * @param data the array that receives the data read
+ * @throws IOException failed to read some data or there is not enough data to read
+ */
+ public void directFullyRead(long offset, @NonNull byte[] data) throws IOException {
+ Preconditions.checkArgument(offset >= 0, "offset < 0");
+ Preconditions.checkNotNull(mRaf, "File is closed");
+
+ mRaf.seek(offset);
+ RandomAccessFileUtils.fullyRead(mRaf, data);
+ }
+
+ /**
+ * Adds all files and directories recursively.
+ *
+ * @param file a file or directory; if it is a directory, all files and directories will be
+ * added recursively
+ * @param method a function that decides what compression method to apply to each file
+ * @throws IOException failed to some (or all ) of the files
+ */
+ public void addAllRecursively(@NonNull File file,
+ @NonNull Function<File, CompressionMethod> method) throws IOException {
+ /*
+ * The case of file.isFile() is different because if file.isFile() we will add it to the
+ * zip in the root. However, if file.isDirectory() we won't add it and add its chilren.
+ */
+ if (file.isFile()) {
+ CompressionMethod cm = Verify.verifyNotNull(method.apply(file),
+ "method.apply() returned null");
+
+ add(file.getName(), new FileEntrySource(file), cm);
+ return;
+ }
+
+ for (File f : Files.fileTreeTraverser().preOrderTraversal(file).skip(1)) {
+ String path = FileUtils.relativePath(f, file);
+ path = FileUtils.toSystemIndependentPath(path);
+
+ EntrySource source;
+ CompressionMethod cm;
+ if (f.isDirectory()) {
+ source = new ByteArrayEntrySource(new byte[0]);
+ cm = CompressionMethod.STORE;
+ } else {
+ source = new FileEntrySource(f);
+ cm = method.apply(f);
+ Verify.verifyNotNull(cm, "method.apply() returned null");
+ }
+
+ add(path, source, cm);
+ }
+ }
+
+ /**
+ * Obtains the offset at which the central directory exists, or at which it will be written
+ * if the zip file were to be flushed immediately.
+ *
+ * @return the offset, in bytes, where the central directory is or will be written; this value
+ * includes any extra offset for the central directory
+ */
+ public long getCentralDirectoryOffset() {
+ if (mDirectoryEntry != null) {
+ return mDirectoryEntry.getStart();
+ }
+
+ /*
+ * If there are no entries, the central directory is written at the start of the file.
+ */
+ if (mEntries.isEmpty()) {
+ return mExtraDirectoryOffset;
+ }
+
+ /*
+ * The Central Directory is written after all entries. This will be at the end of the file
+ * if the
+ */
+ return mMap.usedSize() + mExtraDirectoryOffset;
+ }
+
+ /**
+ * Obtains the size of the central directory, if the central directory is written in the zip
+ * file.
+ *
+ * @return the size of the central directory or {@code -1} if the central directory has not
+ * been computed
+ */
+ public long getCentralDirectorySize() {
+ if (mDirectoryEntry != null) {
+ return mDirectoryEntry.getSize();
+ }
+
+ if (mEntries.isEmpty()) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /**
+ * Obtains the offset of the EOCD record, if the EOCD has been written to the file.
+ *
+ * @return the offset of the EOCD or {@code -1} if none exists yet
+ */
+ public long getEocdOffset() {
+ if (mEocdEntry == null) {
+ return -1;
+ }
+
+ return mEocdEntry.getStart();
+ }
+
+ /**
+ * Obtains the size of the EOCD record, if the EOCD has been written to the file.
+ *
+ * @return the size of the EOCD of {@code -1} it none exists yet
+ */
+ public long getEocdSize() {
+ if (mEocdEntry == null) {
+ return -1;
+ }
+
+ return mEocdEntry.getSize();
+ }
+
+ /**
+ * Sets an extra offset for the central directory. See class description for details. Changing
+ * this value will mark the file as dirty and force a rewrite of the central directory when
+ * updated.
+ *
+ * @param offset the offset or {@code 0} to write the central directory at its current location
+ */
+ public void setExtraDirectoryOffset(long offset) {
+ Preconditions.checkArgument(offset >= 0, "offset < 0");
+
+ if (mExtraDirectoryOffset != offset) {
+ mExtraDirectoryOffset = offset;
+ mDirty = true;
+ }
+ }
+
+ /**
+ * Obtains the extra offset for the central directory. See class description for details.
+ *
+ * @return the offset or {@code 0} if no offset is set
+ */
+ public long getExtraDirectoryOffset() {
+ return mExtraDirectoryOffset;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZFileExtension.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZFileExtension.java
new file mode 100644
index 0000000..ad2fada
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZFileExtension.java
@@ -0,0 +1,148 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.utils.IOExceptionRunnable;
+
+import java.io.IOException;
+
+/**
+ * An extension of a {@link ZFile}. Extensions are notified when files are open, updated, closed and
+ * when files are added or removed from the zip. These notifications are received after the zip
+ * has been updated in memory for open, when files are added or removed and when the zip has been
+ * updated on disk or closed.
+ * <p>
+ * An extension is also notified before the file is updated, allowing it to modify the file before
+ * the update happens. If it does, then all extensions are notified of the changes on the zip file.
+ * Because the order of the notifications is preserved, all extensions are notified in the same
+ * order. For example, if two extensions E1 and E2 are registered and they both add a file at
+ * update time, this would be the flow:
+ * <ul>
+ * <li>E1 receives {@code beforeUpdate} notification.</li>
+ * <li>E1 adds file F1 to the zip (notifying the addition is suspended because another
+ * notification is in progress).</li>
+ * <li>E2 receives {@code beforeUpdate} notification.</li>
+ * <li>E2 adds file F2 to the zip (notifying the addition is suspended because another
+ * notification is in progress).</li>
+ * <li>E1 is notified F1 was added.</li>
+ * <li>E2 is notified F1 was added.</li>
+ * <li>E1 is notified F2 was added.</li>
+ * <li>E2 is notified F2 was added.</li>
+ * <li>(zip file is updated on disk)</li>
+ * <li>E1 is notified the zip was updated.</li>
+ * <li>E2 is notified the zip was updated.</li>
+ * </ul>
+ * <p>
+ * An extension should not modify the zip file when notified of changes. If allowed, this would
+ * break event notification order in case multiple extensions are registered with the zip file.
+ * To allow performing changes to the zip file, all notification method return a
+ * {@code IOExceptionRunnable} that is invoked when {@link ZFile} has finished notifying all
+ * extensions.
+ */
+public abstract class ZFileExtension {
+
+ /**
+ * The zip file has been open and the zip's contents have been read. The default implementation
+ * does nothing and returns {@code null}.
+ *
+ * @return an optional runnable to run when notification of all listeners has ended
+ * @throws IOException failed to process the event
+ */
+ @Nullable
+ public IOExceptionRunnable open() throws IOException {
+ return null;
+ }
+
+ /**
+ * The zip will be updated. This method allows the extension to register changes to the zip
+ * file before the file is written. The default implementation does nothing and returns
+ * {@code null}.
+ * <p>
+ * After this notification is received, the extension will receive further
+ * {@link #added(StoredEntry, StoredEntry)} and {@link #removed(StoredEntry)} notifications if
+ * it or other extensions add or remove files before update.
+ * <p>
+ * When no more files are updated, the {@link #entriesWritten()} notification is sent.
+ *
+ * @return an optional runnable to run when notification of all listeners has ended
+ * @throws IOException failed to process the event
+ */
+ @Nullable
+ public IOExceptionRunnable beforeUpdate() throws IOException {
+ return null;
+ }
+
+ /**
+ * This notification is sent when all entries have been written in the file but the central
+ * directory and the EOCD have not yet been written. The central directory and EOCD have
+ * been computed already and can no longer be modified. No entries should be added, removed or
+ * updated during this notification. No updates to the zip file that affect the central
+ * directory and/or the EOCD can be made.
+ * <p>
+ * After this notification, {@link #updated()} is sent.
+ *
+ * @throws IOException failed to process the event
+ */
+ public void entriesWritten() throws IOException {
+ }
+
+ /**
+ * The zip file has been updated on disk. The default implementation does nothing.
+ *
+ * @return an optional runnable to run when notification of all listeners has ended
+ * @throws IOException failed to perform update tasks
+ */
+ public void updated() throws IOException {
+ }
+
+ /**
+ * The zip file has been closed. Note that if {@link ZFile#close()} requires that the zip file
+ * be updated (because it had in-memory changes), {@link #updated()} will be called before
+ * this method. The default implementation does nothing.
+ */
+ public void closed() {
+ }
+
+ /**
+ * A new entry has been added to the zip, possibly replacing an entry in there. The
+ * default implementation does nothing and returns {@code null}.
+ *
+ * @param entry the entry that was added
+ * @param replaced the entry that was replaced, if any
+ * @return an optional runnable to run when notification of all listeners has ended
+ */
+ @Nullable
+ public IOExceptionRunnable added(@NonNull StoredEntry entry, @Nullable StoredEntry replaced) {
+ return null;
+ }
+
+ /**
+ * An entry has been removed from the zip. This method is not invoked for entries that have
+ * been replaced. Those entries are notified using <em>replaced</em> in
+ * {@link #added(StoredEntry, StoredEntry)}. The default implementation does nothing and
+ * returns {@code null}.
+ *
+ * @param entry the entry that was deleted
+ * @return an optional runnable to run when notification of all listeners has ended
+ */
+ @Nullable
+ public IOExceptionRunnable removed(@NonNull StoredEntry entry) {
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipField.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipField.java
new file mode 100644
index 0000000..e7a47b7
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipField.java
@@ -0,0 +1,296 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.packaging.zip.utils.LittleEndianUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteSource;
+import com.google.common.primitives.Ints;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Set;
+
+/**
+ * The ZipField class represents a field in a record in a zip file. Zip files are made with records
+ * that have fields. This class makes it easy to read, write and verify field values.
+ * <p>
+ * There are two main types of fields: 2-byte fields and 4-byte fields. We represent each one as
+ * a subclass of {@code ZipField}, {@code F2} for the 2-byte field and {@code F4} for the 4-byte
+ * field. Because Java's {@code int} data type is guaranteed to be 4-byte, all methods use Java's
+ * native {@link int} as data type.
+ * <p>
+ * For each field we can either read, write or verify. Verification is used for fields whose value
+ * we know. Some fields, <em>e.g.</em> signature fields, have fixed value. Other fields have
+ * variable values, but in some situations we know which value they have. For example, the last
+ * modification time of a file's local header will have to match the value of the file's
+ * modification time as stored in the central directory.
+ * <p>
+ * Because records are compact, <em>i.e.</em> fields are stored sequentially with no empty spaces,
+ * fields are generally created in the sequence they exist and the end offset of a field is used
+ * as the offset of the next one. The end of a field can be obtained by invoking
+ * {@link #endOffset()}. This allows creating fields in sequence without doing offset computation:
+ * <pre>
+ * ZipField.F2 firstField = new ZipField.F2(0, "First field");
+ * ZipField.F4 secondField = new ZipField(firstField.endOffset(), "Second field");
+ * </pre>
+ */
+abstract class ZipField {
+
+ /**
+ * Field name. Used for providing (more) useful error messages.
+ */
+ @NonNull
+ private final String mName;
+
+ /**
+ * Offset of the file in the record.
+ */
+ protected final int mOffset;
+
+ /**
+ * Size of the field. Only 2 or 4 allowed.
+ */
+ private final int mSize;
+
+ /**
+ * If a fixed value exists for the field, then this attribute will contain that value.
+ */
+ @Nullable
+ private final Long mExpected;
+
+ /**
+ * All invariants that this field must verify.
+ */
+ @NonNull
+ private Set<ZipFieldInvariant> mInvariants;
+
+ /**
+ * Creates a new field that does not contain a fixed value.
+ *
+ * @param offset the field's offset in the record
+ * @param size the field size
+ * @param name the field's name
+ * @param invariants the invariants that must be verified by the field
+ */
+ ZipField(int offset, int size, @NonNull String name, ZipFieldInvariant... invariants) {
+ Preconditions.checkArgument(offset >= 0, "offset >= 0");
+ Preconditions.checkArgument(size == 2 || size == 4, "size != 2 && size != 4");
+
+ mName = name;
+ mOffset = offset;
+ mSize = size;
+ mExpected = null;
+ mInvariants = Sets.newHashSet(invariants);
+ }
+
+ /**
+ * Creates a new field that contains a fixed value.
+ *
+ * @param offset the field's offset in the record
+ * @param size the field size
+ * @param expected the expected field value
+ * @param name the field's name
+ */
+ ZipField(int offset, int size, long expected, @NonNull String name) {
+ Preconditions.checkArgument(offset >= 0, "offset >= 0");
+ Preconditions.checkArgument(size == 2 || size == 4, "size != 2 && size != 4");
+
+ mName = name;
+ mOffset = offset;
+ mSize = size;
+ mExpected = expected;
+ mInvariants = Sets.newHashSet();
+ }
+
+ /**
+ * Checks whether a value verifies the field's invariants. Nothing happens if the value verifies
+ * the invariants.
+ *
+ * @param value the value
+ * @throws IOException the invariants are not verified
+ */
+ private void checkVerifiesInvariants(long value) throws IOException {
+ for (ZipFieldInvariant invariant : mInvariants) {
+ if (!invariant.isValid(value)) {
+ throw new IOException("Value " + value + " of field " + mName + " is invalid "
+ + "(fails '" + invariant.getName() + "'.");
+ }
+ }
+ }
+
+ /**
+ * Reads a field value.
+ *
+ * @param bytes the byte source with the record data
+ * @return the value of the field
+ * @throws IOException failed to read the field
+ */
+ long read(@NonNull ByteSource bytes) throws IOException {
+ Preconditions.checkNotNull(bytes, "bytes == null");
+
+ if (bytes.size() < mSize + mOffset) {
+ throw new IOException("Not enough data: expected to read " + mSize + " bytes at "
+ + "offset " + mOffset + ".");
+ }
+
+ long r;
+ if (mSize == 2) {
+ r = LittleEndianUtils.readUnsigned2Le(bytes.slice(mOffset, bytes.size()));
+ } else {
+ Verify.verify(mSize == 4);
+ r = LittleEndianUtils.readUnsigned4Le(bytes.slice(mOffset, bytes.size()));
+ }
+
+ checkVerifiesInvariants(r);
+ return r;
+ }
+
+ /**
+ * Verifies that the field has the expected value. The field must have been created with the
+ * constructor that defines the expected value.
+ *
+ * @param bytes the byte source with the record data
+ * @throws IOException failed to read the field or the field does not have the expected value
+ */
+ void verify(@NonNull ByteSource bytes) throws IOException {
+ Preconditions.checkState(mExpected != null, "mExpected == null");
+ verify(bytes, mExpected);
+ }
+
+ /**
+ * Verifies that the field has an expected value.
+ *
+ * @param bytes the byte source with the record data
+ * @param expected the value we expect the field to have; if this field has invariants, the
+ * value must verify them
+ * @throws IOException failed to read the data or the field does not have the expected value
+ */
+ void verify(@NonNull ByteSource bytes, long expected) throws IOException {
+ checkVerifiesInvariants(expected);
+ long r = read(bytes);
+ if (r != expected) {
+ throw new IOException("Incorrect value for field '" + mName + "': value is " +
+ r + " but " + expected + " expected.");
+ }
+ }
+
+ /**
+ * Writes the value of the field.
+ *
+ * @param output where to write the field; the field will be written at the beginning of the
+ * stream
+ * @param value the value to write
+ * @throws IOException failed to write the value in the stream
+ */
+ void write(@NonNull OutputStream output, long value) throws IOException {
+ checkVerifiesInvariants(value);
+
+ Preconditions.checkArgument(value >= 0, "value (%s) < 0", value);
+
+ if (mSize == 2) {
+ Preconditions.checkArgument(value <= 0x0000ffff, "value (%s) > 0x0000ffff", value);
+ LittleEndianUtils.writeUnsigned2Le(output, Ints.checkedCast(value));
+ } else {
+ Verify.verify(mSize == 4);
+ Preconditions.checkArgument(value <= 0x00000000ffffffffL,
+ "value (%s) > 0x00000000ffffffffL", value);
+ LittleEndianUtils.writeUnsigned4Le(output, value);
+ }
+ }
+
+ /**
+ * Writes the value of the field. The field must have an expected value set in the constructor.
+ *
+ * @param output where to write the field; the field will be written at the beginning of the
+ * stream
+ * @throws IOException failed to write the value in the stream
+ */
+ void write(@NonNull OutputStream output) throws IOException {
+ Preconditions.checkState(mExpected != null, "mExpected == null");
+ write(output, mExpected);
+ }
+
+ /**
+ * Obtains the offset at which the field ends. This is the exact offset at which the next
+ * field starts.
+ *
+ * @return the end offset
+ */
+ int endOffset() {
+ return mOffset + mSize;
+ }
+
+ /**
+ * Concrete implementation of {@link ZipField} that represents a 2-byte field.
+ */
+ static class F2 extends ZipField {
+
+ /**
+ * Creates a new field.
+ *
+ * @param offset the field's offset in the record
+ * @param name the field's name
+ * @param invariants the invariants that must be verified by the field
+ */
+ F2(int offset, @NonNull String name, ZipFieldInvariant... invariants) {
+ super(offset, 2, name, invariants);
+ }
+
+ /**
+ * Creates a new field that contains a fixed value.
+ *
+ * @param offset the field's offset in the record
+ * @param expected the expected field value
+ * @param name the field's name
+ */
+ F2(int offset, long expected, @NonNull String name) {
+ super(offset, 2, expected, name);
+ }
+ }
+
+ /**
+ * Concrete implementation of {@link ZipField} that represents a 4-byte field.
+ */
+ static class F4 extends ZipField {
+ /**
+ * Creates a new field.
+ *
+ * @param offset the field's offset in the record
+ * @param name the field's name
+ * @param invariants the invariants that must be verified by the field
+ */
+ F4(int offset, @NonNull String name, ZipFieldInvariant... invariants) {
+ super(offset, 4, name, invariants);
+ }
+
+ /**
+ * Creates a new field that contains a fixed value.
+ *
+ * @param offset the field's offset in the record
+ * @param expected the expected field value
+ * @param name the field's name
+ */
+ F4(int offset, long expected, @NonNull String name) {
+ super(offset, 4, expected, name);
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariant.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariant.java
new file mode 100644
index 0000000..0ae76cd
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariant.java
@@ -0,0 +1,39 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+/**
+ * A field rule defines an invariant (<em>i.e.</em>, a constraint) that has to be verified by a
+ * field value.
+ */
+interface ZipFieldInvariant {
+
+ /**
+ * Evalutes the invariant against a value.
+ *
+ * @param value the value to check the invariant
+ * @return is the invariant valid?
+ */
+ boolean isValid(long value);
+
+ /**
+ * Obtains the name of the invariant. Used for information purposes.
+ *
+ * @return the name of the invariant
+ */
+ String getName();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariantMaxValue.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariantMaxValue.java
new file mode 100644
index 0000000..437e2bb
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariantMaxValue.java
@@ -0,0 +1,47 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+/**
+ * Invariant checking a zip field does not exceed a threshold.
+ */
+class ZipFieldInvariantMaxValue implements ZipFieldInvariant {
+
+ /**
+ * The maximum value allowed.
+ */
+ private long mMax;
+
+ /**
+ * Creates a new invariant.
+ *
+ * @param max the maximum value allowed for the field
+ */
+ ZipFieldInvariantMaxValue(int max) {
+ mMax = max;
+ }
+
+ @Override
+ public boolean isValid(long value) {
+ return value <= mMax;
+ }
+
+ @Override
+ public String getName() {
+ return "Maximum value " + mMax;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariantNonNegative.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariantNonNegative.java
new file mode 100644
index 0000000..e4d68d8
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFieldInvariantNonNegative.java
@@ -0,0 +1,33 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+/**
+ * Invariant that verifies a field's value is not negative.
+ */
+class ZipFieldInvariantNonNegative implements ZipFieldInvariant {
+
+ @Override
+ public boolean isValid(long value) {
+ return value >= 0;
+ }
+
+ @Override
+ public String getName() {
+ return "Is positive";
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFileState.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFileState.java
new file mode 100644
index 0000000..b3b9990
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/ZipFileState.java
@@ -0,0 +1,37 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+/**
+ * The {@code ZipFileState} enumeration holds the state of a {@link ZFile}.
+ */
+enum ZipFileState {
+ /**
+ * Zip file is closed.
+ */
+ CLOSED,
+
+ /**
+ * File file is open in read-only mode.
+ */
+ OPEN_RO,
+
+ /**
+ * File file is open in read-write mode.
+ */
+ OPEN_RW
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/CachedFileContents.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/CachedFileContents.java
new file mode 100644
index 0000000..df17c21
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/CachedFileContents.java
@@ -0,0 +1,114 @@
+/*
+ * 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.builder.internal.packaging.zip.utils;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * A cache for file contents. The cache allows closing a file and saving in memory its contents
+ * (or some related information). It can then be used to check if the contents are still valid
+ * at some later time. Typical usage flow is:
+ * <p>
+ * <pre>
+ * Object fileRepresentation = // ...
+ * File toWrite = // ...
+ * // Write file contents and update in memory representation
+ * CachedFileContents<Object> contents = new CachedFileContents<Object>(toWrite);
+ * contents.closed(fileRepresentation);
+ *
+ * // Later, when data is needed:
+ * if (contents.isValid()) {
+ * fileRepresentation = contents.getCache();
+ * } else {
+ * // Re-read the file and recreate the file representation
+ * }
+ * </pre>
+ * @param <T> the type of cached contents
+ */
+public class CachedFileContents<T> {
+
+ /**
+ * The file.
+ */
+ @NonNull
+ private File mFile;
+
+ /**
+ * Time when last closed (time when {@link #closed(Object)} was invoked).
+ */
+ private long mLastClosed;
+
+ /**
+ * Cached data associated with the file.
+ */
+ @Nullable
+ private T mCache;
+
+ /**
+ * Creates a new contents. When the file is written, {@link #closed(Object)} should be invoked
+ * to set the cache.
+ *
+ * @param file the file
+ */
+ public CachedFileContents(@NonNull File file) {
+ mFile = file;
+ }
+
+ /**
+ * Should be called when the file's contents are set and the file closed. This will save the
+ * cache and register the file's timestamp to later detect if it has been modified.
+ * <p>
+ * This method can be called as many times as the file has been written.
+ *
+ * @param cache an optional cache to save
+ */
+ public void closed(T cache) {
+ mCache = cache;
+ mLastClosed = mFile.lastModified();
+ }
+
+ /**
+ * Are the cached contents still valid? If this method determines that the file has been
+ * modified since the last time {@link #closed(Object)} was invoked.
+ *
+ * @return are the cached contents still valid? If this method returns {@code false}, the
+ * cache is cleared
+ */
+ public boolean isValid() {
+ if (mFile.exists() && mFile.lastModified() == mLastClosed) {
+ return true;
+ } else {
+ mCache = null;
+ return false;
+ }
+ }
+
+ /**
+ * Obtains the cached data set with {@link #closed(Object)} if the file has not been modified
+ * since {@link #closed(Object)} was invoked.
+ *
+ * @return the last cached data or {@code null} if the file has been modified since
+ * {@link #closed(Object)} has been invoked
+ */
+ @Nullable
+ public T getCache() {
+ return mCache;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/CachedSupplier.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/CachedSupplier.java
new file mode 100644
index 0000000..24ca9c7
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/CachedSupplier.java
@@ -0,0 +1,110 @@
+/*
+ * 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.builder.internal.packaging.zip.utils;
+
+import java.io.IOException;
+
+/**
+ * Supplier that will cache a computed value and always supply the same value. It can be used to
+ * lazily compute data. For example:
+ * <pre>
+ * CachedSupplier<Integer> value = new CachedSupplier<Integer>() {
+ * protected Integer compute() {
+ * Integer result;
+ * // Do some expensive computation.
+ * return result;
+ * }
+ * }
+ *
+ * if (a) {
+ * // We need the result of the expensive computation.
+ * Integer r = value.get();
+ * }
+ *
+ * if (b) {
+ * // We also need the result of the expensive computation.
+ * Integer r = value.get();
+ * }
+ *
+ * // If neither a nor b are true, we avoid doing the computation at all.
+ * </pre>
+ */
+public abstract class CachedSupplier<T> {
+
+ /**
+ * The cached data, {@code null} if computation resulted in {@code null}.
+ */
+ private T mCached;
+
+ /**
+ * Is the current data in {@link #mCached} valid?
+ */
+ private boolean mValid;
+
+ /**
+ * Creates a new supplier.
+ */
+ public CachedSupplier() {
+ mValid = false;
+ }
+
+
+ /**
+ * Obtains the value.
+ *
+ * @return the value, either cached (if one exists) or computed
+ * @throws IOException failed to compute the value
+ */
+ public synchronized T get() throws IOException {
+ if (!mValid) {
+ mCached = compute();
+ mValid = true;
+ }
+
+ return mCached;
+ }
+
+ /**
+ * Computes the supplier value. This method is only invoked once.
+ *
+ * @return the result of the computation, {@code null} is allowed and, if returned, then
+ * {@link #get()} will also return {@code null}
+ * @throws IOException failed to compute the value
+ */
+ protected abstract T compute() throws IOException;
+
+ /**
+ * Resets the cache forcing a {@link #compute()} next time {@link #get()} is invoked.
+ */
+ public synchronized void reset() {
+ mValid = false;
+ }
+
+ /**
+ * In some cases, we may be able to precompute the cache value (or load it from somewhere we
+ * had previously stored it). This method allows the cache value to be loaded.
+ * <p>
+ * If this method is invoked, then an invocation of {@link #get()} will not trigger an
+ * invocation of {@link #compute()}.
+ *
+ * @param t the new cache contents; will replace any currently cache content, if one exists
+ */
+ public synchronized void precomputed(T t) {
+ mCached = t;
+ mValid = true;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/LittleEndianUtils.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/LittleEndianUtils.java
new file mode 100644
index 0000000..5f968d7
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/LittleEndianUtils.java
@@ -0,0 +1,126 @@
+/*
+ * 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.builder.internal.packaging.zip.utils;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.io.ByteSource;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Utilities to read and write 16 and 32 bit integers with support for little-endian
+ * encoding, as used in zip files. Zip files actually use unsigned data types. We use Java's native
+ * (signed) data types but will use long (64 bit) to ensure we can fit the whole range.
+ */
+public class LittleEndianUtils {
+ /**
+ * Utility class, no constructor.
+ */
+ private LittleEndianUtils() {
+ }
+
+ /**
+ * Reads 4 bytes in little-endian format and converts them into a 32-bit value.
+ *
+ * @param bytes from where should the bytes be read; the first 4 bytes of the source will be
+ * read
+ * @return the 32-bit value
+ * @throws IOException failed to read the value
+ */
+ public static long readUnsigned4Le(@NonNull ByteSource bytes) throws IOException {
+ Preconditions.checkNotNull(bytes, "bytes == null");
+
+ if (bytes.size() < 4) {
+ throw new EOFException("Not enough data: 4 bytes expected, " + bytes.size()
+ + " available.");
+ }
+
+ byte b[] = bytes.read();
+ Verify.verify(b.length >= 4);
+ long r = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16)
+ | ((b[3] & 0xffL) << 24);
+ Verify.verify(r >= 0);
+ Verify.verify(r <= 0x00000000ffffffffL);
+ return r;
+ }
+
+ /**
+ * Reads 2 bytes in little-endian format and converts them into a 16-bit value.
+ *
+ * @param bytes from where should the bytes be read; the first 2 bytes of the source will be
+ * read
+ * @return the 16-bit value
+ * @throws IOException failed to read the value
+ */
+ public static int readUnsigned2Le(@NonNull ByteSource bytes) throws IOException {
+ Preconditions.checkNotNull(bytes, "bytes == null");
+
+ if (bytes.size() < 2) {
+ throw new EOFException("Not enough data: 2 bytes expected, " + bytes.size()
+ + " available.");
+ }
+
+ byte b[] = bytes.read();
+ Verify.verify(b.length >= 2);
+ int r = (b[0] & 0xff) | ((b[1] & 0xff) << 8);
+
+ Verify.verify(r >= 0);
+ Verify.verify(r <= 0x0000ffff);
+ return r;
+ }
+
+ /**
+ * Writes 4 bytes in little-endian format, converting them from a 32-bit value.
+ *
+ * @param output the output stream where the bytes will be written
+ * @param value the 32-bit value to convert
+ * @throws IOException failed to write the value data
+ */
+ public static void writeUnsigned4Le(@NonNull OutputStream output, long value)
+ throws IOException {
+ Preconditions.checkNotNull(output, "output == null");
+ Preconditions.checkArgument(value >= 0, "value (%s) < 0", value);
+ Preconditions.checkArgument(value <= 0x00000000ffffffffL,
+ "value (%s) > 0x00000000ffffffffL", value);
+
+ output.write((byte) (value & 0xff));
+ output.write((byte) ((value >> 8) & 0xff));
+ output.write((byte) ((value >> 16) & 0xff));
+ output.write((byte) ((value >> 24) & 0xff));
+ }
+
+ /**
+ * Writes 2 bytes in little-endian format, converting them from a 16-bit value.
+ *
+ * @param output the output stream where the bytes will be written
+ * @param value the 16-bit value to convert
+ * @throws IOException failed to write the value data
+ */
+ public static void writeUnsigned2Le(@NonNull OutputStream output, int value)
+ throws IOException {
+ Preconditions.checkNotNull(output, "output == null");
+ Preconditions.checkArgument(value >= 0, "value (%s) < 0", value);
+ Preconditions.checkArgument(value <= 0x0000ffff, "value (%s) > 0x0000ffff", value);
+
+ output.write((byte) (value & 0xff));
+ output.write((byte) ((value >> 8) & 0xff));
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/MsDosDateTimeUtils.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/MsDosDateTimeUtils.java
new file mode 100644
index 0000000..53744fd
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/MsDosDateTimeUtils.java
@@ -0,0 +1,107 @@
+/*
+ * 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.builder.internal.packaging.zip.utils;
+
+import com.google.common.base.Verify;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Yes. This actually refers to MS-DOS in 2015. That's all I have to say about legacy stuff.
+ */
+public class MsDosDateTimeUtils {
+ /**
+ * Utility class: no constructor.
+ */
+ private MsDosDateTimeUtils() {
+ }
+
+ /**
+ * Packs java time value into an MS-DOS time value.
+ * @param time the time value
+ * @return the MS-DOS packed time
+ */
+ public static int packTime(long time) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(new Date(time));
+
+ int seconds = c.get(Calendar.SECOND);
+ int minutes = c.get(Calendar.MINUTE);
+ int hours = c.get(Calendar.HOUR_OF_DAY);
+
+ /*
+ * Here is how MS-DOS packs a time value:
+ * 0-4: seconds (divided by 2 because we only have 5 bits = 32 different numbers)
+ * 5-10: minutes (6 bits = 64 possible values)
+ * 11-15: hours (5 bits = 32 possible values)
+ *
+ * source: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724247(v=vs.85).aspx
+ */
+ return (hours << 11) & (minutes << 5) & (seconds / 2);
+ }
+
+ /**
+ * Packs the current time value into an MS-DOS time value.
+ * @return the MS-DOS packed time
+ */
+ public static int packCurrentTime() {
+ return packTime(new Date().getTime());
+ }
+
+ /**
+ * Packs java time value into an MS-DOS date value.
+ * @param time the time value
+ * @return the MS-DOS packed date
+ */
+ public static int packDate(long time) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(new Date(time));
+
+ /*
+ * Even MS-DOS used 1 for January. Someone wasn't really thinking when they decided on Java
+ * it would start at 0...
+ */
+ int day = c.get(Calendar.DAY_OF_MONTH);
+ int month = c.get(Calendar.MONTH) + 1;
+
+ /*
+ * MS-DOS counts years starting from 1980. Since its launch date was in 81, it was obviously
+ * not necessary to talk about dates earlier than that.
+ */
+ int year = c.get(Calendar.YEAR) - 1980;
+ Verify.verify(year >= 0 && year < 128);
+
+ /*
+ * Here is how MS-DOS packs a date value:
+ * 0-4: day (4 bits = 32 values)
+ * 5-8: month (3 bits = 16 values)
+ * 9-15: year (7 bits = 128 values)
+ *
+ * source: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724247(v=vs.85).aspx
+ */
+ return (year << 9) & (month << 5) & day;
+ }
+
+ /**
+ * Packs the current time value into an MS-DOS date value.
+ * @return the MS-DOS packed date
+ */
+ public static int packCurrentDate() {
+ return packDate(new Date().getTime());
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/RandomAccessFileUtils.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/RandomAccessFileUtils.java
new file mode 100644
index 0000000..3f55ad4
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/zip/utils/RandomAccessFileUtils.java
@@ -0,0 +1,57 @@
+/*
+ * 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.builder.internal.packaging.zip.utils;
+
+import com.android.annotations.NonNull;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Utility class with utility methods for random access files.
+ */
+public class RandomAccessFileUtils {
+ /**
+ * Utility class: no constructor.
+ */
+ private RandomAccessFileUtils() {
+ }
+
+ /**
+ * Reads from an random access file until the provided array is filled.
+ * @param raf the file to read data from
+ * @param data the array that will receive the data
+ * @throws IOException
+ */
+ public static void fullyRead(@NonNull RandomAccessFile raf, @NonNull byte[] data)
+ throws IOException {
+ int r;
+ int p = 0;
+
+ while ((r = raf.read(data, p, data.length - p)) > 0) {
+ p += r;
+ if (p == data.length) {
+ break;
+ }
+ }
+
+ if (p < data.length) {
+ throw new IOException("Failed to read " + data.length + " bytes from file. Only "
+ + p + " bytes could be read.");
+ }
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/internal/testing/CustomTestRunListener.java b/build-system/builder/src/main/java/com/android/builder/internal/testing/CustomTestRunListener.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/internal/testing/CustomTestRunListener.java
rename to build-system/builder/src/main/java/com/android/builder/internal/testing/CustomTestRunListener.java
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java b/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java
new file mode 100644
index 0000000..8dcbbe8
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.testing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.testing.TestData;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.ddmlib.InstallException;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.utils.ILogger;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Basic Callable to run tests on a given {@link DeviceConnector} using
+ * {@link RemoteAndroidTestRunner}.
+ *
+ * The boolean return value is true if success.
+ */
+public class SimpleTestCallable implements Callable<Boolean> {
+
+ public static final String FILE_COVERAGE_EC = "coverage.ec";
+
+ @NonNull
+ private final String projectName;
+ @NonNull
+ private final DeviceConnector device;
+ @NonNull
+ private final String flavorName;
+ @NonNull
+ private final TestData testData;
+ @NonNull
+ private final File resultsDir;
+ @NonNull
+ private final File coverageDir;
+ @NonNull
+ private final File testApk;
+ @NonNull
+ private final List<File> testedApks;
+ @NonNull
+ private final ILogger logger;
+
+ private final int timeoutInMs;
+
+ public SimpleTestCallable(
+ @NonNull DeviceConnector device,
+ @NonNull String projectName,
+ @NonNull String flavorName,
+ @NonNull File testApk,
+ @NonNull List<File> testedApks,
+ @NonNull TestData testData,
+ @NonNull File resultsDir,
+ @NonNull File coverageDir,
+ int timeoutInMs,
+ @NonNull ILogger logger) {
+ this.projectName = projectName;
+ this.device = device;
+ this.flavorName = flavorName;
+ this.resultsDir = resultsDir;
+ this.coverageDir = coverageDir;
+ this.testApk = testApk;
+ this.testedApks = testedApks;
+ this.testData = testData;
+ this.timeoutInMs = timeoutInMs;
+ this.logger = logger;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ String deviceName = device.getName();
+ boolean isInstalled = false;
+
+ CustomTestRunListener runListener = new CustomTestRunListener(
+ deviceName, projectName, flavorName, logger);
+ runListener.setReportDir(resultsDir);
+
+ long time = System.currentTimeMillis();
+ boolean success = false;
+
+ String coverageFile = "/data/data/" + testData.getTestedApplicationId() + "/" + FILE_COVERAGE_EC;
+
+ try {
+ device.connect(timeoutInMs, logger);
+
+ if (!testedApks.isEmpty()) {
+ logger.verbose("DeviceConnector '%s': installing %s", deviceName,
+ Joiner.on(',').join(testedApks));
+ if (testedApks.size() > 1 && device.getApiLevel() < 21) {
+ throw new InstallException("Internal error, file a bug, multi-apk applications"
+ + " require a device with API level 21+");
+ }
+ if (device.getApiLevel() >= 21) {
+ device.installPackages(testedApks,
+ ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger);
+ } else {
+ device.installPackage(testedApks.get(0),
+ ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger);
+ }
+ }
+
+ logger.verbose("DeviceConnector '%s': installing %s", deviceName, testApk);
+ if (device.getApiLevel() >= 21) {
+ device.installPackages(ImmutableList.of(testApk),
+ ImmutableList.<String>of() /* installOptions */,timeoutInMs, logger);
+ } else {
+ device.installPackage(testApk,
+ ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger);
+ }
+ isInstalled = true;
+
+ RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(
+ testData.getApplicationId(),
+ testData.getInstrumentationRunner(),
+ device);
+
+ for (Map.Entry<String, String> argument:
+ testData.getInstrumentationRunnerArguments().entrySet()) {
+ runner.addInstrumentationArg(argument.getKey(), argument.getValue());
+ }
+
+ if (testData.isTestCoverageEnabled()) {
+ runner.addInstrumentationArg("coverage", "true");
+ runner.addInstrumentationArg("coverageFile", coverageFile);
+ }
+
+ runner.setRunName(deviceName);
+ runner.setMaxtimeToOutputResponse(timeoutInMs);
+
+ runner.run(runListener);
+
+ TestRunResult testRunResult = runListener.getRunResult();
+
+ success = true;
+
+ // for now throw an exception if no tests.
+ // TODO return a status instead of allow merging of multi-variants/multi-device reports.
+ if (testRunResult.getNumTests() == 0) {
+ CustomTestRunListener fakeRunListener = new CustomTestRunListener(
+ deviceName, projectName, flavorName, logger);
+ fakeRunListener.setReportDir(resultsDir);
+
+ // create a fake test output
+ Map<String, String> emptyMetrics = Collections.emptyMap();
+ TestIdentifier fakeTest = new TestIdentifier(device.getClass().getName(), "No tests found.");
+ fakeRunListener.testStarted(fakeTest);
+ fakeRunListener.testFailed(
+ fakeTest,
+ "No tests found. This usually means that your test classes are"
+ + " not in the form that your test runner expects (e.g. don't inherit from"
+ + " TestCase or lack @Test annotations).");
+ fakeRunListener.testEnded(fakeTest, emptyMetrics);
+
+ // end the run to generate the XML file.
+ fakeRunListener.testRunEnded(System.currentTimeMillis() - time, emptyMetrics);
+ return false;
+ }
+
+ return !testRunResult.hasFailedTests() && !testRunResult.isRunFailure();
+ } catch (Exception e) {
+ Map<String, String> emptyMetrics = Collections.emptyMap();
+
+ // create a fake test output
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintWriter pw = new PrintWriter(baos, true);
+ e.printStackTrace(pw);
+ TestIdentifier fakeTest = new TestIdentifier(device.getClass().getName(), "runTests");
+ runListener.testStarted(fakeTest);
+ runListener.testFailed(fakeTest , baos.toString());
+ runListener.testEnded(fakeTest, emptyMetrics);
+
+ // end the run to generate the XML file.
+ runListener.testRunEnded(System.currentTimeMillis() - time, emptyMetrics);
+
+ // and throw
+ throw e;
+ } finally {
+ if (isInstalled) {
+ // Get the coverage if needed.
+ if (success && testData.isTestCoverageEnabled()) {
+ String temporaryCoverageCopy = "/data/local/tmp/"
+ + testData.getTestedApplicationId() + "." + FILE_COVERAGE_EC;
+
+ MultiLineReceiver outputReceiver = new MultiLineReceiver() {
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ logger.info(line);
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ };
+
+ logger.verbose("DeviceConnector '%s': fetching coverage data from %s",
+ deviceName, coverageFile);
+ device.executeShellCommand("run-as " + testData.getTestedApplicationId()
+ + " cat " + coverageFile + " | cat > " + temporaryCoverageCopy,
+ outputReceiver,
+ 30, TimeUnit.SECONDS);
+ device.pullFile(
+ temporaryCoverageCopy,
+ new File(coverageDir, FILE_COVERAGE_EC).getPath());
+ device.executeShellCommand("rm " + temporaryCoverageCopy,
+ outputReceiver,
+ 30, TimeUnit.SECONDS);
+ }
+
+ // uninstall the apps
+ // This should really not be null, because if it was the build
+ // would have broken before.
+ uninstall(testApk, testData.getApplicationId(), deviceName);
+
+ if (!testedApks.isEmpty()) {
+ for (File testedApk : testedApks) {
+ uninstall(testedApk, testData.getTestedApplicationId(), deviceName);
+ }
+ }
+ }
+
+ device.disconnect(timeoutInMs, logger);
+ }
+ }
+
+ private void uninstall(@NonNull File apkFile, @Nullable String packageName,
+ @NonNull String deviceName)
+ throws DeviceException {
+ if (packageName != null) {
+ logger.verbose("DeviceConnector '%s': uninstalling %s", deviceName, packageName);
+ device.uninstallPackage(packageName, timeoutInMs, logger);
+ } else {
+ logger.verbose("DeviceConnector '%s': unable to uninstall %s: unable to get package name",
+ deviceName, apkFile);
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionFunction.java b/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionFunction.java
new file mode 100644
index 0000000..f120c82
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.utils;
+
+import com.android.annotations.Nullable;
+
+import java.io.IOException;
+
+/**
+ * Function that can throw an I/O Exception
+ */
+public interface IOExceptionFunction<F, T> {
+
+ /**
+ * Applies the function to the given input.
+ * @param input the input
+ * @return the function result
+ */
+ @Nullable T apply(@Nullable F input) throws IOException;
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionRunnable.java b/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionRunnable.java
new file mode 100644
index 0000000..facb4fd
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionRunnable.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.utils;
+
+import java.io.IOException;
+
+/**
+ * Runnable that can throw I/O exceptions.
+ */
+public interface IOExceptionRunnable {
+
+ /**
+ * Runs the runnable.
+ * @throws IOException failed to run
+ */
+ void run() throws IOException;
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionWrapper.java b/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionWrapper.java
new file mode 100644
index 0000000..bb0423e
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/utils/IOExceptionWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.builder.internal.utils;
+
+import com.android.annotations.NonNull;
+
+import java.io.IOException;
+
+/**
+ * Runtime exception used to encapsulate an IO Exception. This is used to allow throwing I/O
+ * exceptions in functional interfaces that do not allow it and catching the exception afterwards.
+ */
+public class IOExceptionWrapper extends RuntimeException {
+
+ /**
+ * Creates a new exception.
+ *
+ * @param e the I/O exception to encapsulate
+ */
+ public IOExceptionWrapper(@NonNull IOException e) {
+ super(e);
+ }
+
+ @Override
+ @NonNull
+ public IOException getCause() {
+ return (IOException) super.getCause();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java b/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
new file mode 100644
index 0000000..e20707b
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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.builder.packaging;
+
+import com.android.annotations.NonNull;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * An exception thrown during packaging of an APK file.
+ */
+public final class DuplicateFileException extends ZipAbortException {
+ private static final long serialVersionUID = 1L;
+ @NonNull
+ private final String mArchivePath;
+ @NonNull
+ private final List<File> mSourceFiles;
+
+
+ public DuplicateFileException(@NonNull String archivePath, @NonNull File... sourceFiles) {
+ super();
+ mArchivePath = archivePath;
+ this.mSourceFiles = ImmutableList.copyOf(sourceFiles);
+ }
+
+ public DuplicateFileException(@NonNull String archivePath, @NonNull List<File> sourceFiles) {
+ super();
+ mArchivePath = archivePath;
+ this.mSourceFiles = ImmutableList.copyOf(sourceFiles);
+ }
+
+ @NonNull
+ public String getArchivePath() {
+ return mArchivePath;
+ }
+
+ @NonNull
+ public List<File> getSourceFiles() {
+ return mSourceFiles;
+ }
+
+ @Override
+ public String getMessage() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("Duplicate files copied in APK ").append(mArchivePath).append('\n');
+ int index = 1;
+ for (File file : mSourceFiles) {
+ sb.append("\tFile").append(index++).append(": ").append(file).append('\n');
+ }
+
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/builder/src/main/java/com/android/builder/packaging/PackagerException.java b/build-system/builder/src/main/java/com/android/builder/packaging/PackagerException.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/packaging/PackagerException.java
rename to build-system/builder/src/main/java/com/android/builder/packaging/PackagerException.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/packaging/SealedPackageException.java b/build-system/builder/src/main/java/com/android/builder/packaging/SealedPackageException.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/packaging/SealedPackageException.java
rename to build-system/builder/src/main/java/com/android/builder/packaging/SealedPackageException.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/packaging/SigningException.java b/build-system/builder/src/main/java/com/android/builder/packaging/SigningException.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/packaging/SigningException.java
rename to build-system/builder/src/main/java/com/android/builder/packaging/SigningException.java
diff --git a/build-system/builder/src/main/java/com/android/builder/png/AaptProcess.java b/build-system/builder/src/main/java/com/android/builder/png/AaptProcess.java
new file mode 100644
index 0000000..9d2c206
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/AaptProcess.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.png;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.tasks.BooleanLatch;
+import com.android.builder.tasks.Job;
+import com.android.ide.common.process.ProcessException;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.ILogger;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * interface to the aapt long running process.
+ */
+public class AaptProcess {
+
+ private static final int DEFAULT_SLAVE_APPT_TIMEOUT_IN_SECONDS = 5;
+ private static final int SLAVE_AAPT_TIMEOUT_IN_SECONDS =
+ System.getenv("SLAVE_AAPT_TIMEOUT") == null
+ ? DEFAULT_SLAVE_APPT_TIMEOUT_IN_SECONDS
+ : Integer.parseInt(System.getenv("SLAVE_AAPT_TIMEOUT"));
+
+ private final String mAaptLocation;
+ private final Process mProcess;
+ private final ILogger mLogger;
+
+ private final ProcessOutputFacade mProcessOutputFacade = new ProcessOutputFacade();
+ private final List<String> mMessages = new ArrayList<String>();
+ private final AtomicBoolean mReady = new AtomicBoolean(false);
+ private final BooleanLatch mReadyLatch = new BooleanLatch();
+ private final OutputStreamWriter mWriter;
+
+ private AaptProcess(
+ @NonNull String aaptLocation, @NonNull Process process, @NonNull ILogger iLogger)
+ throws InterruptedException {
+ mAaptLocation = aaptLocation;
+ mProcess = process;
+ mLogger = iLogger;
+ GrabProcessOutput.grabProcessOutput(process, GrabProcessOutput.Wait.ASYNC,
+ mProcessOutputFacade);
+ mWriter = new OutputStreamWriter(mProcess.getOutputStream());
+ }
+
+ /**
+ * Notifies the slave process of a new crunching request, do not block on completion, the
+ * notification will be issued through the job parameter's
+ * {@link com.android.builder.tasks.Job#finished()} or
+ * {@link com.android.builder.tasks.Job#error(Exception)} ()}
+ * functions.
+ *
+ * @param in the source file to crunch
+ * @param out where to place the crunched file
+ * @param job the job to notify when the crunching is finished successfully or not.
+ * @throws IOException
+ */
+ public void crunch(@NonNull File in, @NonNull File out, @NonNull Job<AaptProcess> job)
+ throws IOException {
+
+ mLogger.verbose("Process(%1$d) %2$s job:%3$s", + hashCode(), in.getName(), job.toString());
+ if (!mReady.get()) {
+ throw new RuntimeException("AAPT process not ready to receive commands");
+ }
+ NotifierProcessOutput notifier =
+ new NotifierProcessOutput(job, mProcessOutputFacade, mLogger);
+
+ mLogger.verbose("Processs(%1$d) length = %2$d:$3$d",
+ hashCode(), in.getAbsolutePath().length(), out.getAbsolutePath().length());
+ mProcessOutputFacade.setNotifier(notifier);
+ mWriter.write("s\n");
+ mWriter.write(in.getAbsolutePath());
+ mWriter.write("\n");
+ mWriter.write(out.getAbsolutePath());
+ mWriter.write("\n");
+ mWriter.flush();
+ mLogger.verbose("Processed(%1$d) %2$s job:%3$s", hashCode(), in.getName(), job.toString());
+ mMessages.add(String.format("Process(%1$d) processed %2$s, job: %3$s",
+ hashCode(), in.getName(), job.toString()));
+ }
+
+ public void waitForReady() throws InterruptedException {
+ if (!mReadyLatch.await(TimeUnit.NANOSECONDS.convert(
+ SLAVE_AAPT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS))) {
+ throw new RuntimeException(String.format(
+ "Timed out while waiting for slave aapt process, make sure "
+ + "the aapt execute at %1$s can run successfully (some anti-virus may "
+ + "block it) or try setting environment variable SLAVE_AAPT_TIMEOUT to a "
+ + "value bigger than %2$d seconds",
+ mAaptLocation, SLAVE_AAPT_TIMEOUT_IN_SECONDS));
+ }
+
+ mLogger.verbose("Slave %1$s is ready", hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("hashcode", hashCode())
+ .add("\nlocation", mAaptLocation)
+ .add("\nready", mReady.get())
+ .add("\nprocess", mProcess.hashCode())
+ .toString();
+ }
+
+ /**
+ * Shutdowns the slave process and release all resources.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void shutdown() throws IOException, InterruptedException {
+
+ mReady.set(false);
+ mWriter.write("quit\n");
+ mWriter.flush();
+ mProcess.waitFor();
+ mLogger.verbose("Process (%1$s) processed %2$s files", hashCode(),
+ mMessages.size());
+ for (String message : mMessages) {
+ mLogger.verbose(message);
+ }
+ }
+
+ public static class Builder {
+ private final String mAaptLocation;
+ private final ILogger mLogger;
+ public Builder(@NonNull String aaptPath, @NonNull ILogger iLogger) {
+ mAaptLocation = aaptPath;
+ mLogger = iLogger;
+ }
+
+ public AaptProcess start() throws IOException, InterruptedException {
+ String[] command = new String[] {
+ mAaptLocation,
+ "m",
+ };
+
+ mLogger.verbose("Trying to start %1$s", command[0]);
+ Process process = new ProcessBuilder(command).start();
+ AaptProcess aaptProcess = new AaptProcess(mAaptLocation, process, mLogger);
+ mLogger.verbose("Started %1$d", aaptProcess.hashCode());
+ return aaptProcess;
+ }
+ }
+
+ private class ProcessOutputFacade implements GrabProcessOutput.IProcessOutput {
+ @Nullable NotifierProcessOutput notifier = null;
+
+ synchronized void setNotifier(@NonNull NotifierProcessOutput notifierProcessOutput) {
+ //noinspection VariableNotUsedInsideIf
+ if (notifier != null) {
+ throw new RuntimeException("Notifier already set, threading issue");
+ }
+ notifier = notifierProcessOutput;
+ }
+
+ @Override
+ public String toString() {
+ return "Facade for " + String.valueOf(AaptProcess.this.hashCode());
+ }
+
+ synchronized void reset() {
+ notifier = null;
+ }
+
+ @Nullable
+ synchronized NotifierProcessOutput getNotifier() {
+ return notifier;
+ }
+
+ @Override
+ public synchronized void out(@Nullable String line) {
+
+ // an empty message or aapt startup message are ignored.
+ if (Strings.isNullOrEmpty(line)) {
+ return;
+ }
+ if (line.equals("Ready")) {
+ AaptProcess.this.mReady.set(true);
+ AaptProcess.this.mReadyLatch.signal();
+ return;
+ }
+ NotifierProcessOutput delegate = getNotifier();
+ mLogger.verbose("AAPT out(%1$s): %2$s", toString(), line);
+ if (delegate != null) {
+ mLogger.verbose("AAPT out(%1$s): -> %2$s", toString(), delegate.mJob);
+ delegate.out(line);
+ } else {
+ mLogger.error(null, "AAPT out(%1$s) : No Delegate set : lost message:%2$s",
+ toString(), line);
+ }
+ }
+
+ @Override
+ public synchronized void err(@Nullable String line) {
+
+ if (Strings.isNullOrEmpty(line)) {
+ return;
+ }
+ NotifierProcessOutput delegate = getNotifier();
+ if (delegate != null) {
+ mLogger.verbose("AAPT1 err(%1$s): %2$s -> %3$s", toString(), line,
+ delegate.mJob);
+ delegate.err(line);
+ } else {
+ if (!mReady.get()) {
+ if (line.equals("ERROR: Unknown command 'm'")) {
+ throw new RuntimeException("Invalid aapt version, version 21 or above is required");
+ }
+ mLogger.verbose("AAPT err(%1$s): %2$s", toString(), line);
+ mLogger.error(null, "AAPT err(%1$s): %2$s", toString(), line);
+ } else {
+ mLogger.error(null, "AAPT err(%1$s) : No Delegate set : lost message:%2$s",
+ toString(), line);
+ }
+ }
+ }
+
+ Process getProcess() {
+ return mProcess;
+ }
+ }
+
+ private static class NotifierProcessOutput implements GrabProcessOutput.IProcessOutput {
+
+ @NonNull private final Job<AaptProcess> mJob;
+ @NonNull private final ProcessOutputFacade mOwner;
+ @NonNull private final ILogger mLogger;
+ @NonNull private final AtomicBoolean mInError = new AtomicBoolean(false);
+ @SuppressWarnings("StringBufferField")
+ @NonNull private final StringBuilder mErrorBuilder = new StringBuilder();
+
+ NotifierProcessOutput(
+ @NonNull Job<AaptProcess> job,
+ @NonNull ProcessOutputFacade owner,
+ @NonNull ILogger iLogger) {
+ mOwner = owner;
+ mJob = job;
+ mLogger = iLogger;
+ }
+
+ @Override
+ public void out(@Nullable String line) {
+ if (line != null) {
+ mLogger.verbose("AAPT notify(%1$s): %2$s", mJob, line);
+ if (line.equalsIgnoreCase("Done")) {
+ mOwner.reset();
+ if (mInError.get()) {
+ mLogger.verbose("Job is in error mode, cause : %1$s", mErrorBuilder.toString());
+ mJob.error(new ProcessException(mErrorBuilder.toString()));
+ } else {
+ mJob.finished();
+ }
+ } else if (line.equalsIgnoreCase("Error")) {
+ mInError.set(true);
+ } else {
+ mLogger.verbose("AAPT(%1$s) discarded: %2$s", mJob, line);
+ }
+ }
+ }
+
+ @Override
+ public void err(@Nullable String line) {
+ if (line != null) {
+ if (mInError.get()) {
+ mErrorBuilder.append(line);
+ }
+ mLogger.verbose("AAPT warning(%1$s), Job(%2$s): %3$s",
+ mOwner.getProcess().hashCode(), mJob, line);
+ mLogger.warning("AAPT: %1$s", line);
+
+ }
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/QueuedCruncher.java b/build-system/builder/src/main/java/com/android/builder/png/QueuedCruncher.java
new file mode 100644
index 0000000..f018f69
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/QueuedCruncher.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.png;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.builder.tasks.Job;
+import com.android.builder.tasks.JobContext;
+import com.android.builder.tasks.QueueThreadContext;
+import com.android.builder.tasks.Task;
+import com.android.builder.tasks.WorkQueue;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.internal.PngException;
+import com.android.utils.ILogger;
+import com.google.common.base.Objects;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * implementation of {@link com.android.ide.common.internal.PngCruncher} that queues request and
+ * use a pool or aapt server processes to serve those.
+ */
+public class QueuedCruncher implements PngCruncher {
+
+ // use an enum to ensure singleton.
+ public enum Builder {
+ INSTANCE;
+
+ private final Map<String, QueuedCruncher> sInstances =
+ new ConcurrentHashMap<String, QueuedCruncher>();
+ private final Object sLock = new Object();
+
+ /**
+ * Creates a new {@link com.android.builder.png.QueuedCruncher} or return an existing one
+ * based on the underlying AAPT executable location.
+ * @param aaptLocation the APPT executable location.
+ * @param logger the logger to use
+ * @return a new of existing instance of the {@link com.android.builder.png.QueuedCruncher}
+ */
+ public QueuedCruncher newCruncher(
+ @NonNull String aaptLocation,
+ @NonNull ILogger logger) {
+ synchronized (sLock) {
+ logger.info("QueuedCruncher is using %1$s", aaptLocation);
+ if (!sInstances.containsKey(aaptLocation)) {
+ QueuedCruncher queuedCruncher = new QueuedCruncher(aaptLocation, logger);
+ sInstances.put(aaptLocation, queuedCruncher);
+ }
+ return sInstances.get(aaptLocation);
+ }
+ }
+ }
+
+
+ @NonNull private final String mAaptLocation;
+ @NonNull private final ILogger mLogger;
+ // Queue responsible for handling all passed jobs with a pool of worker threads.
+ @NonNull private final WorkQueue<AaptProcess> mCrunchingRequests;
+ // list of outstanding jobs.
+ @NonNull private final Map<Integer, ConcurrentLinkedQueue<Job<AaptProcess>>> mOutstandingJobs =
+ new ConcurrentHashMap<Integer, ConcurrentLinkedQueue<Job<AaptProcess>>>();
+ // list of finished jobs.
+ @NonNull private final Map<Integer, ConcurrentLinkedQueue<Job<AaptProcess>>> mDoneJobs =
+ new ConcurrentHashMap<Integer, ConcurrentLinkedQueue<Job<AaptProcess>>>();
+ // ref count of active users, if it drops to zero, that means there are no more active users
+ // and the queue should be shutdown.
+ @NonNull private final AtomicInteger refCount = new AtomicInteger(0);
+
+ // per process unique key provider to remember which users enlisted which requests.
+ @NonNull private final AtomicInteger keyProvider = new AtomicInteger(0);
+
+
+ private QueuedCruncher(
+ @NonNull final String aaptLocation,
+ @NonNull ILogger iLogger) {
+ mAaptLocation = aaptLocation;
+ mLogger = iLogger;
+ QueueThreadContext<AaptProcess> queueThreadContext = new QueueThreadContext<AaptProcess>() {
+
+ // move this to a TLS, but do not store instances of AaptProcess in it.
+ @NonNull private final Map<String, AaptProcess> mAaptProcesses =
+ new ConcurrentHashMap<String, AaptProcess>();
+
+ @Override
+ public void creation(@NonNull Thread t) throws IOException {
+ try {
+ AaptProcess aaptProcess = new AaptProcess.Builder(mAaptLocation, mLogger).start();
+ assert aaptProcess != null;
+ mLogger.verbose("Thread(%1$s): created aapt slave, Process(%2$s)",
+ Thread.currentThread().getName(), aaptProcess.hashCode());
+ aaptProcess.waitForReady();
+ mAaptProcesses.put(t.getName(), aaptProcess);
+ } catch (InterruptedException e) {
+ mLogger.error(e, "Cannot start slave process");
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void runTask(@NonNull Job<AaptProcess> job) throws Exception {
+ job.runTask(
+ new JobContext<AaptProcess>(
+ mAaptProcesses.get(Thread.currentThread().getName())));
+ mOutstandingJobs.get(((QueuedJob) job).key).remove(job);
+ mDoneJobs.get(((QueuedJob) job).key).add(job);
+ }
+
+ @Override
+ public void destruction(@NonNull Thread t) throws IOException, InterruptedException {
+
+ AaptProcess aaptProcess = mAaptProcesses.get(Thread.currentThread().getName());
+ if (aaptProcess != null) {
+ mLogger.verbose("Thread(%1$s): notify aapt slave shutdown, Process(%2$s)",
+ Thread.currentThread().getName(), aaptProcess.hashCode());
+ aaptProcess.shutdown();
+ mAaptProcesses.remove(t.getName());
+ mLogger.verbose("Thread(%1$s): Process(%2$d), after shutdown queue_size=%3$d",
+ Thread.currentThread().getName(),
+ aaptProcess.hashCode(),
+ mAaptProcesses.size());
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ if (!mAaptProcesses.isEmpty()) {
+ mLogger.warning("Process list not empty");
+ for (Map.Entry<String, AaptProcess> aaptProcessEntry : mAaptProcesses
+ .entrySet()) {
+ mLogger.warning("Thread(%1$s): queue not cleaned", aaptProcessEntry.getKey());
+ try {
+ aaptProcessEntry.getValue().shutdown();
+ } catch (Exception e) {
+ mLogger.error(e, "while shutting down" + aaptProcessEntry.getKey());
+ }
+ }
+ }
+ mAaptProcesses.clear();
+ }
+ };
+ mCrunchingRequests = new WorkQueue<AaptProcess>(
+ mLogger, queueThreadContext, "png-cruncher", 5, 2f);
+ }
+
+ private static final class QueuedJob extends Job<AaptProcess> {
+
+ private final int key;
+ public QueuedJob(int key, String jobTile, Task<AaptProcess> task) {
+ super(jobTile, task);
+ this.key = key;
+ }
+ }
+
+ @Override
+ public void crunchPng(int key, @NonNull final File from, @NonNull final File to)
+ throws PngException {
+
+ if (from.getAbsolutePath().length() > 240
+ && SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ throw new PngException("File path too long on Windows, keep below 240 characters : "
+ + from.getAbsolutePath());
+ }
+ if (to.getAbsolutePath().length() > 240
+ && SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ throw new PngException("File path too long on Windows, keep below 240 characters : "
+ + to.getAbsolutePath());
+ }
+
+ try {
+ final Job<AaptProcess> aaptProcessJob = new QueuedJob(
+ key,
+ "Cruncher " + from.getName(),
+ new Task<AaptProcess>() {
+ @Override
+ public void run(@NonNull Job<AaptProcess> job,
+ @NonNull JobContext<AaptProcess> context) throws IOException {
+ AaptProcess aapt = context.getPayload();
+ if (aapt == null) {
+ mLogger.error(null /* throwable */,
+ "Thread(%1$s) has a null payload",
+ Thread.currentThread().getName());
+ return;
+ }
+ mLogger.verbose("Thread(%1$s): submitting job %2$s to %3$d",
+ Thread.currentThread().getName(),
+ job.getJobTitle(),
+ aapt.hashCode());
+ aapt.crunch(from, to, job);
+ mLogger.verbose("Thread(%1$s): submitted job %2$s",
+ Thread.currentThread().getName(), job.getJobTitle());
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("from", from.getName())
+ .add("to", to.getAbsolutePath())
+ .toString();
+ }
+ });
+ mOutstandingJobs.get(key).add(aaptProcessJob);
+ mCrunchingRequests.push(aaptProcessJob);
+ } catch (InterruptedException e) {
+ // Restore the interrupted status
+ Thread.currentThread().interrupt();
+ throw new PngException(e);
+ }
+ }
+
+ private void waitForAll(int key) throws InterruptedException {
+ mLogger.verbose("Thread(%1$s): begin waitForAll", Thread.currentThread().getName());
+ ConcurrentLinkedQueue<Job<AaptProcess>> jobs = mOutstandingJobs.get(key);
+ Job<AaptProcess> aaptProcessJob = jobs.poll();
+ boolean hasExceptions = false;
+ while (aaptProcessJob != null) {
+ mLogger.verbose("Thread(%1$s) : wait for {%2$s)", Thread.currentThread().getName(),
+ aaptProcessJob.toString());
+ if (!aaptProcessJob.await()) {
+ throw new RuntimeException(
+ "Crunching " + aaptProcessJob.getJobTitle() + " failed, see logs");
+ }
+ if (aaptProcessJob.getFailureReason() != null) {
+ mLogger.verbose("Exception while crunching png : " + aaptProcessJob.toString()
+ + " : " + aaptProcessJob.getFailureReason());
+ hasExceptions = true;
+ }
+ aaptProcessJob = jobs.poll();
+ }
+ // process done jobs to retrieve potential issues.
+ jobs = mDoneJobs.get(key);
+ aaptProcessJob = jobs.poll();
+ while(aaptProcessJob != null) {
+ if (aaptProcessJob.getFailureReason() != null) {
+ mLogger.verbose("Exception while crunching png : " + aaptProcessJob.toString()
+ + " : " + aaptProcessJob.getFailureReason());
+ hasExceptions = true;
+ }
+ aaptProcessJob = jobs.poll();
+ }
+ if (hasExceptions) {
+ throw new RuntimeException("Some file crunching failed, see logs for details");
+ }
+ mLogger.verbose("Thread(%1$s): end waitForAll", Thread.currentThread().getName());
+ }
+
+ @Override
+ public synchronized int start() {
+ // increment our reference count.
+ refCount.incrementAndGet();
+ // get a unique key for the lifetime of this process.
+ int key = keyProvider.incrementAndGet();
+ mOutstandingJobs.put(key, new ConcurrentLinkedQueue<Job<AaptProcess>>());
+ mDoneJobs.put(key, new ConcurrentLinkedQueue<Job<AaptProcess>>());
+ return key;
+ }
+
+ @Override
+ public synchronized void end(int key) throws InterruptedException {
+ long startTime = System.currentTimeMillis();
+ try {
+ waitForAll(key);
+ mOutstandingJobs.get(key).clear();
+ mLogger.verbose("Job finished in %1$d", System.currentTimeMillis() - startTime);
+ } finally {
+ // even if we have failures, we need to shutdown property the sub processes.
+ if (refCount.decrementAndGet() == 0) {
+ mCrunchingRequests.shutdown();
+ mLogger.verbose("Shutdown finished in %1$d",
+ System.currentTimeMillis() - startTime);
+ }
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/png/VectorDrawableRenderer.java b/build-system/builder/src/main/java/com/android/builder/png/VectorDrawableRenderer.java
new file mode 100644
index 0000000..9a8a30f
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/png/VectorDrawableRenderer.java
@@ -0,0 +1,213 @@
+/*
+ * 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.builder.png;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.res2.ResourcePreprocessor;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.VersionQualifier;
+import com.android.ide.common.vectordrawable.VdPreview;
+import com.android.resources.Density;
+import com.android.resources.ResourceFolderType;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.imageio.ImageIO;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * Generates PNG images (and XML copies) from VectorDrawable files.
+ */
+public class VectorDrawableRenderer implements ResourcePreprocessor {
+ /** Projects with minSdk set to this or higher don't need to generate PNGs. */
+ public static final int MIN_SDK_WITH_VECTOR_SUPPORT = 21;
+
+ private final ILogger mLogger;
+ private final int mMinSdk;
+ private final File mOutputDir;
+ private final Collection<Density> mDensities;
+
+ public VectorDrawableRenderer(
+ int minSdk,
+ File outputDir,
+ Collection<Density> densities,
+ ILogger logger) {
+ mMinSdk = minSdk;
+ mOutputDir = outputDir;
+ mDensities = densities;
+ mLogger = logger;
+ }
+
+ @Override
+ public boolean needsPreprocessing(File resourceFile) {
+ return mMinSdk < MIN_SDK_WITH_VECTOR_SUPPORT
+ && isXml(resourceFile)
+ && isInDrawable(resourceFile)
+ && getEffectiveVersion(resourceFile) < MIN_SDK_WITH_VECTOR_SUPPORT
+ && isRootVector(resourceFile);
+ }
+
+ @Override
+ public Collection<File> getFilesToBeGenerated(File inputXmlFile) {
+ Collection<File> filesToBeGenerated = Lists.newArrayList();
+ FolderConfiguration originalConfiguration = getFolderConfiguration(inputXmlFile);
+
+ if (originalConfiguration.getDensityQualifier() != null
+ && originalConfiguration.getDensityQualifier().getValue() == Density.NODPI) {
+ // If the files uses nodpi, just leave it alone.
+ filesToBeGenerated.add(new File(
+ getDirectory(originalConfiguration),
+ inputXmlFile.getName()));
+ } else if (originalConfiguration.getDensityQualifier() != null
+ && originalConfiguration.getDensityQualifier().getValue() != Density.ANYDPI) {
+ // If the density is specified, generate one png and one xml.
+ filesToBeGenerated.add(new File(
+ getDirectory(originalConfiguration),
+ inputXmlFile.getName().replace(".xml", ".png")));
+
+ originalConfiguration.setVersionQualifier(
+ new VersionQualifier(MIN_SDK_WITH_VECTOR_SUPPORT));
+ filesToBeGenerated.add(new File(
+ getDirectory(originalConfiguration),
+ inputXmlFile.getName()));
+ } else {
+ // Otherwise, generate one xml and N pngs, one per density.
+ for (Density density : mDensities) {
+ FolderConfiguration newConfiguration = FolderConfiguration.copyOf(originalConfiguration);
+ newConfiguration.setDensityQualifier(new DensityQualifier(density));
+
+ filesToBeGenerated.add(new File(
+ getDirectory(newConfiguration),
+ inputXmlFile.getName().replace(".xml", ".png")));
+ }
+
+ originalConfiguration.setDensityQualifier(new DensityQualifier(Density.ANYDPI));
+ originalConfiguration.setVersionQualifier(
+ new VersionQualifier(MIN_SDK_WITH_VECTOR_SUPPORT));
+
+ filesToBeGenerated.add(
+ new File(getDirectory(originalConfiguration), inputXmlFile.getName()));
+ }
+
+ return filesToBeGenerated;
+ }
+
+ @NonNull
+ private File getDirectory(FolderConfiguration newConfiguration) {
+ return new File(
+ mOutputDir,
+ newConfiguration.getFolderName(ResourceFolderType.DRAWABLE));
+ }
+
+ @Override
+ public void generateFile(File toBeGenerated, File original) throws IOException {
+ Files.createParentDirs(toBeGenerated);
+
+ if (isXml(toBeGenerated)) {
+ Files.copy(original, toBeGenerated);
+ } else {
+ mLogger.info(
+ "Generating PNG: [%s] from [%s]",
+ toBeGenerated.getAbsolutePath(),
+ original.getAbsolutePath());
+
+ FolderConfiguration folderConfiguration = getFolderConfiguration(toBeGenerated);
+ checkState(folderConfiguration.getDensityQualifier() != null);
+ Density density = folderConfiguration.getDensityQualifier().getValue();
+
+ String xmlContent = Files.toString(original, Charsets.UTF_8);
+ float scaleFactor = density.getDpiValue() / (float) Density.MEDIUM.getDpiValue();
+ if (scaleFactor <= 0) {
+ scaleFactor = 1.0f;
+ }
+
+ final VdPreview.TargetSize imageSize = VdPreview.TargetSize.createSizeFromScale(scaleFactor);
+ BufferedImage image = VdPreview.getPreviewFromVectorXml(imageSize, xmlContent, null);
+ checkState(image != null, "Generating the image failed.");
+ ImageIO.write(image, "png", toBeGenerated);
+ }
+ }
+
+ @NonNull
+ private static FolderConfiguration getFolderConfiguration(@NonNull File inputXmlFile) {
+ String parentName = inputXmlFile.getParentFile().getName();
+ FolderConfiguration originalConfiguration =
+ FolderConfiguration.getConfigForFolder(parentName);
+ checkArgument(
+ originalConfiguration != null,
+ "Invalid resource folder name [%s].",
+ parentName);
+ return originalConfiguration;
+ }
+
+ private static boolean isInDrawable(@NonNull File inputXmlFile) {
+ ResourceFolderType folderType =
+ ResourceFolderType.getFolderType(inputXmlFile.getParentFile().getName());
+
+ return folderType == ResourceFolderType.DRAWABLE;
+ }
+
+ /**
+ * Parse the root element of the file, return true if it is a vector.
+ * TODO: Use SAX parser to only look at the root tag.
+ */
+ private static boolean isRootVector(File resourceFile) {
+ DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+ boolean result = false;
+ try {
+ DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+ Document doc = dBuilder.parse(resourceFile);
+ Element root = doc.getDocumentElement();
+ if (root != null && root.getNodeName().equalsIgnoreCase("vector")) {
+ result = true;
+ }
+ } catch (Exception e) {
+ Throwables.propagate(e);
+ }
+
+ return result;
+ }
+
+ private static boolean isXml(File resourceFile) {
+ return Files.getFileExtension(resourceFile.getName()).equals("xml");
+ }
+
+ private static int getEffectiveVersion(File resourceFile) {
+ FolderConfiguration configuration = getFolderConfiguration(resourceFile);
+ if (configuration.getVersionQualifier() == null) {
+ configuration.createDefault();
+ }
+ //noinspection ConstantConditions - handled above.
+ return configuration.getVersionQualifier().getVersion();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java b/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java
new file mode 100644
index 0000000..f99f421
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/DefaultSdkLoader.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.sdk;
+
+import static com.android.SdkConstants.FD_EXTRAS;
+import static com.android.SdkConstants.FD_M2_REPOSITORY;
+import static com.android.SdkConstants.FD_PLATFORM_TOOLS;
+import static com.android.SdkConstants.FD_SUPPORT;
+import static com.android.SdkConstants.FD_TOOLS;
+import static com.android.SdkConstants.FN_ADB;
+import static com.android.SdkConstants.FN_ANNOTATIONS_JAR;
+
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.LoggerProgressIndicatorWrapper;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Singleton-based implementation of SdkLoader for a standard SDK
+ */
+public class DefaultSdkLoader implements SdkLoader {
+
+ private static DefaultSdkLoader sLoader;
+
+ @NonNull
+ private final File mSdkLocation;
+ private AndroidSdkHandler mSdkHandler;
+ private SdkInfo mSdkInfo;
+ private final ImmutableList<File> mRepositories;
+
+ public static synchronized SdkLoader getLoader(
+ @NonNull File sdkLocation) {
+ if (sLoader == null) {
+ sLoader = new DefaultSdkLoader(sdkLocation);
+ } else if (!sdkLocation.equals(sLoader.mSdkLocation)) {
+ throw new IllegalStateException("Already created an SDK Loader with different SDK Path");
+ }
+
+ return sLoader;
+ }
+
+ public static synchronized void unload() {
+ sLoader = null;
+ }
+
+ @Override
+ @NonNull
+ public TargetInfo getTargetInfo(
+ @NonNull String targetHash,
+ @NonNull Revision buildToolRevision,
+ @NonNull ILogger logger) {
+ init(logger);
+
+ ProgressIndicator progress = new LoggerProgressIndicatorWrapper(logger);
+ IAndroidTarget target = mSdkHandler.getAndroidTargetManager(progress)
+ .getTargetFromHashString(targetHash, progress);
+ if (target == null) {
+ throw new IllegalStateException("failed to find target with hash string '" + targetHash + "' in: " + mSdkLocation);
+ }
+
+ BuildToolInfo buildToolInfo = mSdkHandler.getBuildToolInfo(buildToolRevision, progress);
+ if (buildToolInfo == null) {
+ throw new IllegalStateException("failed to find Build Tools revision "
+ + buildToolRevision.toString());
+ }
+
+ return new TargetInfo(target, buildToolInfo);
+ }
+
+ @Override
+ @NonNull
+ public SdkInfo getSdkInfo(@NonNull ILogger logger) {
+ init(logger);
+ return mSdkInfo;
+ }
+
+ @Override
+ @NonNull
+ public ImmutableList<File> getRepositories() {
+ return mRepositories;
+ }
+
+ private DefaultSdkLoader(@NonNull File sdkLocation) {
+ mSdkLocation = sdkLocation;
+ mRepositories = computeRepositories();
+ }
+
+ private synchronized void init(@NonNull ILogger logger) {
+ if (mSdkHandler == null) {
+ // Intentionally don't use sdk handler caching mechanism
+ mSdkHandler = new AndroidSdkHandler(mSdkLocation, FileOpUtils.create());
+
+ File toolsFolder = new File(mSdkLocation, FD_TOOLS);
+ File supportToolsFolder = new File(toolsFolder, FD_SUPPORT);
+ File platformTools = new File(mSdkLocation, FD_PLATFORM_TOOLS);
+
+ mSdkInfo = new SdkInfo(
+ new File(supportToolsFolder, FN_ANNOTATIONS_JAR),
+ new File(platformTools, FN_ADB));
+ }
+ }
+
+ @NonNull
+ public ImmutableList<File> computeRepositories() {
+ List<File> repositories = Lists.newArrayListWithExpectedSize(2);
+
+ File androidRepo = new File(mSdkLocation, FD_EXTRAS + File.separator + "android"
+ + File.separator + FD_M2_REPOSITORY);
+ if (androidRepo.isDirectory()) {
+ repositories.add(androidRepo);
+ }
+
+ File googleRepo = new File(mSdkLocation, FD_EXTRAS + File.separator + "google"
+ + File.separator + FD_M2_REPOSITORY);
+ if (googleRepo.isDirectory()) {
+ repositories.add(googleRepo);
+ }
+
+ return ImmutableList.copyOf(repositories);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java b/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java
new file mode 100644
index 0000000..57a3d04
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/PlatformLoader.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.sdk;
+
+import static com.android.SdkConstants.FN_AAPT;
+import static com.android.SdkConstants.FN_AIDL;
+import static com.android.SdkConstants.FN_BCC_COMPAT;
+import static com.android.SdkConstants.FN_RENDERSCRIPT;
+import static com.android.SdkConstants.FN_ZIPALIGN;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.builder.internal.FakeAndroidTarget;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+
+/**
+ * Singleton-based implementation of SdkLoader for a platform-based SDK.
+ *
+ * Platform-based SDK are in the Android source tree in AOSP, using a different
+ * folder layout for all the files.
+ */
+public class PlatformLoader implements SdkLoader {
+
+ private static PlatformLoader sLoader;
+
+ @NonNull
+ private final File mTreeLocation;
+
+ private File mHostToolsFolder;
+ private SdkInfo mSdkInfo;
+ @NonNull
+ private final ImmutableList<File> mRepositories;
+
+ public static synchronized SdkLoader getLoader(
+ @NonNull File treeLocation) {
+ if (sLoader == null) {
+ sLoader = new PlatformLoader(treeLocation);
+ } else if (!treeLocation.equals(sLoader.mTreeLocation)) {
+ throw new IllegalStateException("Already created an SDK Loader with different SDK Path");
+ }
+
+ return sLoader;
+ }
+
+ public static synchronized void unload() {
+ sLoader = null;
+ }
+
+ @NonNull
+ @Override
+ public TargetInfo getTargetInfo(@NonNull String targetHash,
+ @NonNull Revision buildToolRevision, @NonNull ILogger logger) {
+ init(logger);
+
+ IAndroidTarget androidTarget = new FakeAndroidTarget(mTreeLocation.getPath(), targetHash);
+
+ File hostTools = getHostToolsFolder();
+
+ BuildToolInfo buildToolInfo = new BuildToolInfo(
+ buildToolRevision,
+ mTreeLocation,
+ new File(hostTools, FN_AAPT),
+ new File(hostTools, FN_AIDL),
+ new File(mTreeLocation, "prebuilts/sdk/tools/dx"),
+ new File(mTreeLocation, "prebuilts/sdk/tools/lib/dx.jar"),
+ new File(hostTools, FN_RENDERSCRIPT),
+ new File(mTreeLocation, "prebuilts/sdk/renderscript/include"),
+ new File(mTreeLocation, "prebuilts/sdk/renderscript/clang-include"),
+ new File(hostTools, FN_BCC_COMPAT),
+ new File(hostTools, "arm-linux-androideabi-ld"),
+ new File(hostTools, "i686-linux-android-ld"),
+ new File(hostTools, "mipsel-linux-android-ld"),
+ new File(hostTools, FN_ZIPALIGN));
+
+ return new TargetInfo(androidTarget, buildToolInfo);
+ }
+
+ @NonNull
+ @Override
+ public SdkInfo getSdkInfo(@NonNull ILogger logger) {
+ init(logger);
+ return mSdkInfo;
+ }
+
+ @Override
+ @NonNull
+ public ImmutableList<File> getRepositories() {
+ return mRepositories;
+ }
+
+ private PlatformLoader(@NonNull File treeLocation) {
+ mTreeLocation = treeLocation;
+ mRepositories = ImmutableList.of(new File(mTreeLocation + "/prebuilts/sdk/m2repository"));
+ }
+
+ private synchronized void init(@NonNull ILogger logger) {
+ if (mSdkInfo == null) {
+ String host;
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ host = "darwin-x86";
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ host = "linux-x86";
+ } else {
+ throw new IllegalStateException(
+ "Windows is not supported for platform development");
+ }
+
+ mSdkInfo = new SdkInfo(
+ new File(mTreeLocation, "out/host/" + host + "/framework/annotations.jar"),
+ new File(mTreeLocation, "out/host/" + host + "/bin/adb"));
+ }
+ }
+
+ @NonNull
+ private synchronized File getHostToolsFolder() {
+ if (mHostToolsFolder == null) {
+ File tools = new File(mTreeLocation, "prebuilts/sdk/tools");
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ mHostToolsFolder = new File(tools, "darwin/bin");
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ mHostToolsFolder = new File(tools, "linux/bin");
+ } else {
+ throw new IllegalStateException(
+ "Windows is not supported for platform development");
+ }
+
+ if (!mHostToolsFolder.isDirectory()) {
+ throw new IllegalStateException("Host tools folder missing: " +
+ mHostToolsFolder.getAbsolutePath());
+ }
+ }
+
+ return mHostToolsFolder;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java b/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java
new file mode 100644
index 0000000..80cacaa
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/SdkInfo.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.sdk;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * General information about the SDK.
+ */
+public class SdkInfo {
+
+ @NonNull
+ private final File mAnnotationJar;
+ @NonNull
+ private final File mAdb;
+
+ SdkInfo(@NonNull File annotationJar,
+ @NonNull File adb) {
+ mAnnotationJar = annotationJar;
+ mAdb = adb;
+ }
+
+ /**
+ * Returns the location of the annotations jar for compilation targets that are <= 15.
+ */
+ @NonNull
+ public File getAnnotationsJar() {
+ return mAnnotationJar;
+ }
+
+ /**
+ * Returns the location of the adb tool.
+ */
+ @NonNull
+ public File getAdb() {
+ return mAdb;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java b/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java
new file mode 100644
index 0000000..4c6da12
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/sdk/SdkLoader.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.sdk;
+
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+
+/**
+ * A loader for the SDK. It's able to provide general SDK information
+ * ({@link #getSdkInfo(com.android.utils.ILogger)}, or {@link #getRepositories()}), or
+ * target-specific information
+ * ({@link #getTargetInfo(String, Revision, com.android.utils.ILogger)}).
+ */
+public interface SdkLoader {
+
+ /**
+ * Returns information about a build target.
+ *
+ * This requires loading/parsing the SDK.
+ *
+ * @param targetHash the compilation target hash string.
+ * @param buildToolRevision the build tools revision.
+ * @param logger a logger to output messages.
+ * @return the target info.
+ */
+ @NonNull
+ TargetInfo getTargetInfo(
+ @NonNull String targetHash,
+ @NonNull Revision buildToolRevision,
+ @NonNull ILogger logger);
+
+ /**
+ * Returns generic SDK information.
+ *
+ * This requires loading/parsing the SDK.
+ *
+ * @param logger a logger to output messages.
+ * @return the sdk info.
+ */
+ @NonNull
+ SdkInfo getSdkInfo(@NonNull ILogger logger);
+
+ /**
+ * Returns the location of artifact repositories built-in the SDK.
+ * @return a non null list of repository folders.
+ */
+ @NonNull
+ ImmutableList<File> getRepositories();
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/sdk/TargetInfo.java b/build-system/builder/src/main/java/com/android/builder/sdk/TargetInfo.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/sdk/TargetInfo.java
rename to build-system/builder/src/main/java/com/android/builder/sdk/TargetInfo.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.java b/build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.java
rename to build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.java
diff --git a/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java b/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java
new file mode 100644
index 0000000..51cb42a
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2008 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.builder.signing;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.security.DigestOutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * A Jar file builder with signature support.
+ */
+public class SignedJarBuilder {
+
+ private final int mMinSdkVersion;
+
+ /** Write to another stream and track how many bytes have been
+ * written.
+ */
+ private static class CountOutputStream extends FilterOutputStream {
+ private int mCount = 0;
+
+ public CountOutputStream(OutputStream out) {
+ super(out);
+ mCount = 0;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ super.write(b);
+ mCount++;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ super.write(b, off, len);
+ mCount += len;
+ }
+
+ public int size() {
+ return mCount;
+ }
+ }
+
+ private JarOutputStream mOutputJar;
+ private PrivateKey mKey;
+ private X509Certificate mCertificate;
+ private Manifest mManifest;
+ private String mDigestAttributeName;
+ private String mDigestManifestAttributeName;
+ private String mMessageDigestAlgorithm;
+ private MessageDigest mMessageDigest;
+
+ private byte[] mBuffer = new byte[4096];
+
+ /**
+ * Classes which implement this interface provides a method to check whether a file should
+ * be added to a Jar file.
+ */
+ public interface IZipEntryFilter {
+
+ /**
+ * An exception thrown during packaging of a zip file into APK file.
+ * This is typically thrown by implementations of
+ * {@link IZipEntryFilter#checkEntry(String)}.
+ */
+ class ZipAbortException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ZipAbortException() {
+ super();
+ }
+
+ public ZipAbortException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public ZipAbortException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public ZipAbortException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+
+ /**
+ * Checks a file for inclusion in a Jar archive.
+ * @param archivePath the archive file path of the entry
+ * @return <code>true</code> if the file should be included.
+ * @throws ZipAbortException if writing the file should be aborted.
+ */
+ boolean checkEntry(String archivePath) throws ZipAbortException;
+ }
+
+
+ /**
+ * Classes which implement this interface provides a method to check whether a file should
+ * be merged and extracted from the zip.
+ */
+ public interface ZipEntryExtractor {
+
+ boolean checkEntry(String archivePath);
+
+ void extract(String archivePath, InputStream zis) throws IOException;
+ }
+
+ /**
+ * Creates a {@link SignedJarBuilder} with a given output stream, and signing information.
+ * <p/>If either <code>key</code> or <code>certificate</code> is <code>null</code> then
+ * the archive will not be signed.
+ * @param out the {@link OutputStream} where to write the Jar archive.
+ * @param key the {@link PrivateKey} used to sign the archive, or <code>null</code>.
+ * @param certificate the {@link X509Certificate} used to sign the archive, or
+ * <code>null</code>.
+ * @param minSdkVersion minSdkVersion of the package contained in this JAR.
+ * @throws IOException
+ * @throws NoSuchAlgorithmException
+ */
+ public SignedJarBuilder(@NonNull OutputStream out,
+ @Nullable PrivateKey key,
+ @Nullable X509Certificate certificate,
+ @Nullable String builtBy,
+ @Nullable String createdBy,
+ int minSdkVersion)
+ throws IOException, NoSuchAlgorithmException {
+ mMinSdkVersion = minSdkVersion;
+ mOutputJar = new JarOutputStream(new BufferedOutputStream(out));
+ mOutputJar.setLevel(9);
+ mKey = key;
+ mCertificate = certificate;
+
+ if (mKey != null && mCertificate != null) {
+ mManifest = new Manifest();
+ Attributes main = mManifest.getMainAttributes();
+ main.putValue("Manifest-Version", "1.0");
+ if (builtBy != null) {
+ main.putValue("Built-By", builtBy);
+ }
+ if (createdBy != null) {
+ main.putValue("Created-By", createdBy);
+ }
+
+ String digestAttributeDigestAlgorithm;
+ if (mMinSdkVersion < 18) {
+ // Android 2.3 (API Level 9) to 4.2 (API Level 17) (inclusive) do not support SHA-2
+ // JAR signatures.
+ mMessageDigestAlgorithm = "SHA-1";
+ // Moreover, platforms prior to API Level 18, without the additional
+ // Digest-Algorithms attribute, only support SHA or SHA1 algorithm names in .SF and
+ // MANIFEST.MF attributes.
+ digestAttributeDigestAlgorithm = "SHA1";
+ } else {
+ mMessageDigestAlgorithm = "SHA-256";
+ digestAttributeDigestAlgorithm = mMessageDigestAlgorithm;
+ }
+ mDigestAttributeName = digestAttributeDigestAlgorithm + "-Digest";
+ mDigestManifestAttributeName = digestAttributeDigestAlgorithm + "-Digest-Manifest";
+ mMessageDigest = MessageDigest.getInstance(mMessageDigestAlgorithm);
+ }
+ }
+
+ /**
+ * Writes a new {@link File} into the archive.
+ * @param inputFile the {@link File} to write.
+ * @param jarPath the filepath inside the archive.
+ * @throws IOException
+ */
+ public void writeFile(File inputFile, String jarPath) throws IOException {
+ // Get an input stream on the file.
+ FileInputStream fis = new FileInputStream(inputFile);
+ try {
+
+ // create the zip entry
+ JarEntry entry = new JarEntry(jarPath);
+ entry.setTime(inputFile.lastModified());
+
+ writeEntry(fis, entry);
+ } finally {
+ // close the file stream used to read the file
+ fis.close();
+ }
+ }
+
+ /**
+ * Copies the content of a Jar/Zip archive into the receiver archive.
+ * @param input the {@link InputStream} for the Jar/Zip to copy.
+ * @throws IOException
+ * @throws ZipAbortException if the {@link IZipEntryFilter} filter indicated that the write
+ * must be aborted.
+ */
+ public void writeZip(InputStream input) throws IOException, ZipAbortException {
+ writeZip(input, null, null);
+ }
+
+ /**
+ * Copies the content of a Jar/Zip archive into the receiver archive.
+ * <p/>An optional {@link IZipEntryFilter} allows to selectively choose which files
+ * to copy over.
+ * @param input the {@link InputStream} for the Jar/Zip to copy.
+ * @param filter the filter or <code>null</code>
+ * @throws IOException
+ * @throws ZipAbortException if the {@link IZipEntryFilter} filter indicated that the write
+ * must be aborted.
+ */
+ public void writeZip(InputStream input, IZipEntryFilter filter, ZipEntryExtractor extractor)
+ throws IOException, ZipAbortException {
+ ZipInputStream zis = new ZipInputStream(input);
+
+ try {
+ // loop on the entries of the intermediary package and put them in the final package.
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ String name = entry.getName();
+
+ // do not take directories or anything inside a potential META-INF folder.
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ // ignore some of the content in META-INF/ but not all
+ if (name.startsWith("META-INF/")) {
+ // ignore the manifest file.
+ String subName = name.substring(9);
+ if ("MANIFEST.MF".equals(subName)) {
+ continue;
+ }
+
+ // special case for Maven meta-data because we really don't care about them in apks.
+ if (name.startsWith("META-INF/maven/")) {
+ continue;
+ }
+
+
+ // check for subfolder
+ int index = subName.indexOf('/');
+ if (index == -1) {
+ // no sub folder, ignores signature files.
+ if (subName.endsWith(".SF") || name.endsWith(".RSA") || name.endsWith(".DSA")) {
+ continue;
+ }
+ }
+ }
+
+ // if we have a filter, we check the entry to see if it's a file that should be extracted.
+ if (extractor != null && extractor.checkEntry(name)) {
+ extractor.extract(name, zis);
+ continue;
+ }
+
+ // if we have a filter, we check the entry against it
+ if (filter != null && !filter.checkEntry(name)) {
+ continue;
+ }
+
+ JarEntry newEntry;
+
+ // Preserve the STORED method of the input entry.
+ if (entry.getMethod() == JarEntry.STORED) {
+ newEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ newEntry = new JarEntry(name);
+ }
+
+ writeEntry(zis, newEntry);
+
+ zis.closeEntry();
+ }
+ } finally {
+ zis.close();
+ }
+ }
+
+ /**
+ * Closes the Jar archive by creating the manifest, and signing the archive.
+ * @throws IOException
+ * @throws SigningException
+ */
+ public void close() throws IOException, SigningException {
+ if (mManifest != null) {
+ // write the manifest to the jar file
+ mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
+ mManifest.write(mOutputJar);
+
+ try {
+ // CERT.SF
+ mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeSignatureFile(baos);
+ byte[] signedData = baos.toByteArray();
+ mOutputJar.write(signedData);
+
+ // CERT.*
+ mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm()));
+ writeSignatureBlock(new CMSProcessableByteArray(signedData), mCertificate, mKey);
+ } catch (Exception e) {
+ throw new SigningException(e);
+ }
+ }
+
+ mOutputJar.close();
+ mOutputJar = null;
+ }
+
+ /**
+ * Clean up of the builder for interrupted workflow.
+ * This does nothing if {@link #close()} was called successfully.
+ */
+ public void cleanUp() {
+ if (mOutputJar != null) {
+ try {
+ mOutputJar.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ }
+
+ /**
+ * Adds an entry to the output jar, and write its content from the {@link InputStream}
+ * @param input The input stream from where to write the entry content.
+ * @param entry the entry to write in the jar.
+ * @throws IOException
+ */
+ private void writeEntry(InputStream input, JarEntry entry) throws IOException {
+ // add the entry to the jar archive
+ mOutputJar.putNextEntry(entry);
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ int count;
+ while ((count = input.read(mBuffer)) != -1) {
+ mOutputJar.write(mBuffer, 0, count);
+
+ // update the digest
+ if (mMessageDigest != null) {
+ mMessageDigest.update(mBuffer, 0, count);
+ }
+ }
+
+ // close the entry for this file
+ mOutputJar.closeEntry();
+
+ if (mManifest != null) {
+ // update the manifest for this entry.
+ Attributes attr = mManifest.getAttributes(entry.getName());
+ if (attr == null) {
+ attr = new Attributes();
+ mManifest.getEntries().put(entry.getName(), attr);
+ }
+ attr.putValue(mDigestAttributeName,
+ new String(Base64.encode(mMessageDigest.digest()), "ASCII"));
+ }
+ }
+
+ /** Writes a .SF file with a digest to the manifest. */
+ private void writeSignatureFile(OutputStream out)
+ throws IOException, GeneralSecurityException {
+ Manifest sf = new Manifest();
+ Attributes main = sf.getMainAttributes();
+ main.putValue("Signature-Version", "1.0");
+ main.putValue("Created-By", "1.0 (Android)");
+
+ MessageDigest md = MessageDigest.getInstance(mMessageDigestAlgorithm);
+ PrintStream print = new PrintStream(
+ new DigestOutputStream(new ByteArrayOutputStream(), md),
+ true, SdkConstants.UTF_8);
+
+ // Digest of the entire manifest
+ mManifest.write(print);
+ print.flush();
+ main.putValue(mDigestManifestAttributeName,
+ new String(Base64.encode(md.digest()), "ASCII"));
+
+ Map<String, Attributes> entries = mManifest.getEntries();
+ for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
+ // Digest of the manifest stanza for this entry.
+ print.print("Name: " + entry.getKey() + "\r\n");
+ for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
+ print.print(att.getKey() + ": " + att.getValue() + "\r\n");
+ }
+ print.print("\r\n");
+ print.flush();
+
+ Attributes sfAttr = new Attributes();
+ sfAttr.putValue(mDigestAttributeName, new String(Base64.encode(md.digest()), "ASCII"));
+ sf.getEntries().put(entry.getKey(), sfAttr);
+ }
+ CountOutputStream cout = new CountOutputStream(out);
+ sf.write(cout);
+
+ // A bug in the java.util.jar implementation of Android platforms
+ // up to version 1.6 will cause a spurious IOException to be thrown
+ // if the length of the signature file is a multiple of 1024 bytes.
+ // As a workaround, add an extra CRLF in this case.
+ if ((cout.size() % 1024) == 0) {
+ cout.write('\r');
+ cout.write('\n');
+ }
+ }
+
+ /** Write the certificate file with a digital signature. */
+ private void writeSignatureBlock(CMSTypedData data, X509Certificate publicKey,
+ PrivateKey privateKey)
+ throws IOException,
+ CertificateEncodingException,
+ OperatorCreationException,
+ CMSException {
+
+ ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+ certList.add(publicKey);
+ JcaCertStore certs = new JcaCertStore(certList);
+
+ CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+ ContentSigner sha1Signer =
+ new JcaContentSignerBuilder(getSignatureAlgorithm(privateKey)).build(privateKey);
+ gen.addSignerInfoGenerator(
+ new JcaSignerInfoGeneratorBuilder(
+ new JcaDigestCalculatorProviderBuilder()
+ .build())
+ .setDirectSignature(true)
+ .build(sha1Signer, publicKey));
+ gen.addCertificates(certs);
+ CMSSignedData sigData = gen.generate(data, false);
+
+ ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
+ try {
+ DEROutputStream dos = new DEROutputStream(mOutputJar);
+ try {
+ dos.writeObject(asn1.readObject());
+ } finally {
+ dos.flush();
+ dos.close();
+ }
+ } finally {
+ asn1.close();
+ }
+ }
+
+ private String getSignatureAlgorithm(PrivateKey privateKey) {
+ String keyAlgorithm = privateKey.getAlgorithm();
+ String digestAlgorithm = mMessageDigestAlgorithm.replace("-", "");
+
+ if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+ // Digest algorithms in JCA Signature algorithms do not use the hyphen.
+ // For example, SHA-256 becomes SHA256withRSA.
+ return digestAlgorithm + "withRSA";
+ } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+ checkArgument(
+ mMinSdkVersion >= 18,
+ "ECDSA signatures are not supported on API levels older than 18. Please increase "
+ + "your minSdkVersion or use RSA.");
+ return digestAlgorithm + "withECDSA";
+ } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
+ return digestAlgorithm + "withDSA";
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported key algorithm for signing: " + keyAlgorithm);
+ }
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/signing/SigningException.java b/build-system/builder/src/main/java/com/android/builder/signing/SigningException.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/signing/SigningException.java
rename to build-system/builder/src/main/java/com/android/builder/signing/SigningException.java
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java
new file mode 100644
index 0000000..cc5c62b
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.testing.api.DeviceConfig;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.SyncException;
+import com.android.ddmlib.TimeoutException;
+import com.android.utils.ILogger;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Local device connected to with ddmlib. This is a wrapper around {@link IDevice}.
+ */
+public class ConnectedDevice extends DeviceConnector {
+
+ private final IDevice iDevice;
+ private final ILogger mLogger;
+ private final long mTimeout;
+ private final TimeUnit mTimeUnit;
+
+
+ public ConnectedDevice(@NonNull IDevice iDevice, @NonNull ILogger logger,
+ long timeout, @NonNull TimeUnit timeUnit) {
+ this.iDevice = iDevice;
+ mLogger = logger;
+ mTimeout = timeout;
+ mTimeUnit = timeUnit;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ String version = getNullableProperty(IDevice.PROP_BUILD_VERSION);
+ boolean emulator = iDevice.isEmulator();
+
+ String name;
+ if (emulator) {
+ name = iDevice.getAvdName() != null ?
+ iDevice.getAvdName() + "(AVD)" :
+ iDevice.getSerialNumber();
+ } else {
+ String model = getNullableProperty(IDevice.PROP_DEVICE_MODEL);
+ name = model != null ? model : iDevice.getSerialNumber();
+ }
+
+ return version != null ? name + " - " + version : name;
+ }
+
+ @Override
+ public void connect(int timeout, ILogger logger) throws TimeoutException {
+ // nothing to do here
+ }
+
+ @Override
+ public void disconnect(int timeout, ILogger logger) throws TimeoutException {
+ // nothing to do here
+ }
+
+ @Override
+ public void installPackage(@NonNull File apkFile,
+ @NonNull Collection<String> options,
+ int timeout,
+ ILogger logger) throws DeviceException {
+ try {
+ iDevice.installPackage(apkFile.getAbsolutePath(), true /*reinstall*/,
+ options.isEmpty() ? null : options.toArray(new String[options.size()]));
+ } catch (Exception e) {
+ logger.error(e, "Unable to install " + apkFile.getAbsolutePath());
+ throw new DeviceException(e);
+ }
+ }
+
+ @Override
+ public void installPackages(@NonNull List<File> splitApkFiles,
+ @NonNull Collection<String> options,
+ int timeoutInMs,
+ ILogger logger)
+ throws DeviceException {
+
+ try {
+ iDevice.installPackages(splitApkFiles, true /*reinstall*/,
+ ImmutableList.copyOf(options), timeoutInMs, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ List<String> apkFileNames = Lists.transform(splitApkFiles, new Function<File, String>() {
+ @Override
+ public String apply(@Nullable File input) {
+ return input != null ? input.getAbsolutePath() : null;
+ }
+ });
+ logger.error(e, "Unable to install " + Joiner.on(',').join(apkFileNames));
+ throw new DeviceException(e);
+ }
+ }
+
+ @Override
+ public void uninstallPackage(@NonNull String packageName, int timeout, ILogger logger) throws DeviceException {
+ try {
+ iDevice.uninstallPackage(packageName);
+ } catch (Exception e) {
+ logger.error(e, "Unable to uninstall " + packageName);
+ throw new DeviceException(e);
+ }
+ }
+
+ @Override
+ public void executeShellCommand(String command, IShellOutputReceiver receiver,
+ long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
+ throws TimeoutException, AdbCommandRejectedException,
+ ShellCommandUnresponsiveException, IOException {
+ iDevice.executeShellCommand(command, receiver, maxTimeToOutputResponse, maxTimeUnits);
+ }
+
+ @NonNull
+ @Override
+ public Future<String> getSystemProperty(@NonNull String name) {
+ return iDevice.getSystemProperty(name);
+ }
+
+ @Override
+ public void pullFile(String remote, String local) throws IOException {
+ try {
+ iDevice.pullFile(remote, local);
+
+ } catch (TimeoutException e) {
+ throw new IOException(String.format("Failed to pull %s from device", remote), e);
+ } catch (AdbCommandRejectedException e) {
+ throw new IOException(String.format("Failed to pull %s from device", remote), e);
+ } catch (SyncException e) {
+ throw new IOException(String.format("Failed to pull %s from device", remote), e);
+ }
+ }
+
+ @NonNull
+ @Override
+ public String getSerialNumber() {
+ return iDevice.getSerialNumber();
+ }
+
+ @Override
+ public int getApiLevel() {
+ String sdkVersion = getNullableProperty(IDevice.PROP_BUILD_API_LEVEL);
+ if (sdkVersion != null) {
+ try {
+ return Integer.valueOf(sdkVersion);
+ } catch (NumberFormatException ignored) { }
+ }
+
+ // can't get it, return 0.
+ return 0;
+ }
+
+ @Override
+ public String getApiCodeName() {
+ String codeName = getNullableProperty(IDevice.PROP_BUILD_CODENAME);
+ if (codeName != null) {
+ // if this is a release platform return null.
+ if ("REL".equals(codeName)) {
+ return null;
+ }
+
+ // else return the codename
+ return codeName;
+ }
+
+ // can't get it, return 0.
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public IDevice.DeviceState getState() {
+ return iDevice.getState();
+ }
+
+ @NonNull
+ @Override
+ public List<String> getAbis() {
+ /* Try abiList (implemented in L onwards) otherwise fall back to abi and abi2. */
+ String abiList = getNullableProperty(IDevice.PROP_DEVICE_CPU_ABI_LIST);
+ if(abiList != null) {
+ return Lists.newArrayList(Splitter.on(',').split(abiList));
+ } else {
+ List<String> abis = Lists.newArrayListWithExpectedSize(2);
+ String abi = getNullableProperty(IDevice.PROP_DEVICE_CPU_ABI);
+ if (abi != null) {
+ abis.add(abi);
+ }
+
+ abi = getNullableProperty(IDevice.PROP_DEVICE_CPU_ABI2);
+ if (abi != null) {
+ abis.add(abi);
+ }
+
+ return abis;
+ }
+ }
+
+ @Override
+ public int getDensity() {
+ String densityValue = getNullableProperty(IDevice.PROP_DEVICE_DENSITY);
+ if (densityValue == null) {
+ densityValue = getNullableProperty(IDevice.PROP_DEVICE_EMULATOR_DENSITY);
+ }
+ if (densityValue == null) {
+ mLogger.info("Unable to get density for device %1$s", getName());
+ return -1;
+ }
+ try {
+ return Integer.parseInt(densityValue);
+ } catch (NumberFormatException e) {
+ mLogger.info("Unable to get density for device %1$s. "
+ + "Density value %2$s could not be parsed.", getName(), densityValue);
+ return -1;
+ }
+ }
+
+ @Override
+ public int getHeight() {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int getWidth() {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public String getLanguage() {
+ return getNullableProperty(IDevice.PROP_DEVICE_LANGUAGE);
+ }
+
+ @Override
+ public String getRegion() {
+ return getNullableProperty(IDevice.PROP_DEVICE_REGION);
+ }
+
+ @Override
+ @NonNull
+ public String getProperty(@NonNull String propertyName) {
+ //TODO: Is this method needed in ConnectedDevice?
+ // If it is, what should its failure semantic be?
+ return Preconditions.checkNotNull(getNullableProperty(propertyName));
+ }
+
+ @Nullable
+ private String getNullableProperty(@NonNull String propertyName) {
+ try {
+ Future<String> property = iDevice.getSystemProperty(propertyName);
+ if (mTimeout > 0) {
+ return property.get(mTimeout, mTimeUnit);
+ } else {
+ return property.get();
+ }
+ } catch (InterruptedException e) {
+ return null;
+ } catch (ExecutionException e) {
+ return null;
+ } catch (java.util.concurrent.TimeoutException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ @Override
+ public DeviceConfig getDeviceConfig() throws DeviceException {
+ final List<String> output = new ArrayList<String>();
+ final MultiLineReceiver receiver = new MultiLineReceiver() {
+ @Override
+ public void processNewLines(String[] lines) {
+ output.addAll(Arrays.asList(lines));
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ };
+ try {
+ executeShellCommand("am get-config", receiver, mTimeout, mTimeUnit);
+ return DeviceConfig.Builder.parse(output);
+ } catch (Exception e) {
+ throw new DeviceException(e);
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java
new file mode 100644
index 0000000..5715b21
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.android.annotations.NonNull;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.DdmPreferences;
+import com.android.ddmlib.IDevice;
+import com.android.utils.ILogger;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * DeviceProvider for locally connected devices. Basically returns the list of devices that
+ * are currently connected at the time {@link #init()} is called.
+ */
+public class ConnectedDeviceProvider extends DeviceProvider {
+
+ @NonNull
+ private final File adbLocation;
+
+ private final long timeOut;
+
+ @NonNull
+ private final TimeUnit timeOutUnit;
+
+ @NonNull
+ private final ILogger iLogger;
+
+ @NonNull
+ private final List<ConnectedDevice> localDevices = Lists.newArrayList();
+
+ /**
+ * @param timeOutInMs The time out for each adb command, where 0 means wait forever.
+ */
+ public ConnectedDeviceProvider(@NonNull File adbLocation, int timeOutInMs,
+ @NonNull ILogger logger) {
+ this.adbLocation = adbLocation;
+ this.timeOut = timeOutInMs;
+ timeOutUnit = TimeUnit.MILLISECONDS;
+ iLogger = logger;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return "connected";
+ }
+
+ @Override
+ @NonNull
+ public List<? extends DeviceConnector> getDevices() {
+ return localDevices;
+ }
+
+ @Override
+ public void init() throws DeviceException {
+ // TODO: make all timeouts explicit in IDevice
+ DdmPreferences.setTimeOut((int) timeOutUnit.toMillis(timeOut));
+ AndroidDebugBridge.initIfNeeded(false /*clientSupport*/);
+
+ AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
+ adbLocation.getAbsolutePath(), false /*forceNewBridge*/);
+
+ if (bridge == null) {
+ throw new DeviceException("Could not create ADB Bridge. "
+ + "ADB location: " + adbLocation.getAbsolutePath());
+ }
+
+ long getDevicesCountdown = timeOutUnit.toMillis(timeOut);
+ final int sleepTime = 1000;
+ while (!bridge.hasInitialDeviceList() && getDevicesCountdown >= 0) {
+ try {
+ Thread.sleep(sleepTime);
+ } catch (InterruptedException e) {
+ throw new DeviceException(e);
+ }
+ // If timeOut is 0, wait forever.
+ if (timeOut != 0) {
+ getDevicesCountdown -= sleepTime;
+ }
+ }
+
+ if (!bridge.hasInitialDeviceList()) {
+ throw new DeviceException("Timeout getting device list.");
+ }
+
+ IDevice[] devices = bridge.getDevices();
+
+ if (devices.length == 0) {
+ throw new DeviceException("No connected devices!");
+ }
+
+ final String androidSerialsEnv = System.getenv("ANDROID_SERIAL");
+ final boolean isValidSerial = androidSerialsEnv != null && !androidSerialsEnv.isEmpty();
+
+ final Set<String> serials;
+ if (isValidSerial) {
+ serials = Sets.newHashSet(Splitter.on(',').split(androidSerialsEnv));
+ } else {
+ serials = Collections.emptySet();
+ }
+
+ final List<IDevice> filteredDevices = Lists.newArrayListWithCapacity(devices.length);
+ for (IDevice iDevice : devices) {
+ if (!isValidSerial || serials.contains(iDevice.getSerialNumber())) {
+ serials.remove(iDevice.getSerialNumber());
+ filteredDevices.add(iDevice);
+ }
+ }
+
+ if (!serials.isEmpty()) {
+ throw new DeviceException(String.format(
+ "Connected device with serial%s '%s' not found!",
+ serials.size() == 1 ? "" : "s",
+ Joiner.on("', '").join(serials)));
+ }
+
+ for (IDevice device : filteredDevices) {
+ if (device.getState() == IDevice.DeviceState.ONLINE) {
+ localDevices.add(new ConnectedDevice(device, iLogger, timeOut, timeOutUnit));
+ } else {
+ iLogger.info(
+ "Skipping device '%s' (%s): Device is %s%s.",
+ device.getName(), device.getSerialNumber(), device.getState(),
+ device.getState() == IDevice.DeviceState.UNAUTHORIZED ? ",\n"
+ + " see http://d.android.com/tools/help/adb.html#Enabling" : "");
+ }
+ }
+
+ if (localDevices.isEmpty()) {
+ if (isValidSerial) {
+ throw new DeviceException(String.format(
+ "Connected device with serial $1%s is not online.",
+ androidSerialsEnv));
+ } else {
+ throw new DeviceException("No online devices found.");
+ }
+ }
+ }
+
+ @Override
+ public void terminate() throws DeviceException {
+ // nothing to be done here.
+ }
+
+ @Override
+ public int getTimeoutInMs() {
+ return (int) timeOutUnit.toMillis(timeOut);
+ }
+
+ @Override
+ public boolean isConfigured() {
+ return true;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/MockableJarGenerator.java b/build-system/builder/src/main/java/com/android/builder/testing/MockableJarGenerator.java
new file mode 100644
index 0000000..33eb41e
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/MockableJarGenerator.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.InnerClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InsnNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TypeInsnNode;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+/**
+ * Given a "standard" android.jar, creates a "mockable" version, where all classes and methods
+ * are not final. Optionally makes all methods return "default" values, instead of throwing the
+ * infamous "Stub!" exceptions.
+ *
+ * <p>ATTENTION! If you change this class, please update the gradle tasks to reflect that in the
+ * generated file name (e.g. add a "v2"). This will force Gradle to re-generate the jars in
+ * existing projects.
+ */
+public class MockableJarGenerator {
+ private static final int EMPTY_FLAGS = 0;
+ private static final String CONSTRUCTOR = "<init>";
+ private static final String CLASS_CONSTRUCTOR = "<clinit>";
+
+ private static final ImmutableSet<String> ENUM_METHODS = ImmutableSet.of(
+ CLASS_CONSTRUCTOR, "valueOf", "values");
+
+ private static final ImmutableSet<Type> INTEGER_LIKE_TYPES = ImmutableSet.of(
+ Type.INT_TYPE, Type.BYTE_TYPE, Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.SHORT_TYPE);
+
+ private final boolean returnDefaultValues;
+ private final ImmutableSet<String> prefixesToSkip = ImmutableSet.of(
+ "java.", "javax.", "org.xml.", "org.w3c.", "junit.", "org.apache.commons.logging");
+
+ public MockableJarGenerator(boolean returnDefaultValues) {
+ this.returnDefaultValues = returnDefaultValues;
+ }
+
+ public void createMockableJar(File input, File output) throws IOException {
+ Preconditions.checkState(
+ output.createNewFile(),
+ "Output file [%s] already exists.",
+ output.getAbsolutePath());
+
+ JarFile androidJar = null;
+ JarOutputStream outputStream = null;
+ try {
+ androidJar = new JarFile(input);
+ outputStream = new JarOutputStream(new FileOutputStream(output));
+
+ for (JarEntry entry : Collections.list(androidJar.entries())) {
+ InputStream inputStream = androidJar.getInputStream(entry);
+
+ if (entry.getName().endsWith(".class")) {
+ if (!skipClass(entry.getName().replace("/", "."))) {
+ rewriteClass(entry, inputStream, outputStream);
+ }
+ } else {
+ outputStream.putNextEntry(entry);
+ ByteStreams.copy(inputStream, outputStream);
+ }
+
+ inputStream.close();
+ }
+ } finally {
+ if (androidJar != null) {
+ androidJar.close();
+ }
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+ }
+
+ private boolean skipClass(String className) {
+ for (String prefix : prefixesToSkip) {
+ if (className.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Writes a modified *.class file to the output JAR file.
+ */
+ private void rewriteClass(
+ JarEntry entry,
+ InputStream inputStream,
+ JarOutputStream outputStream) throws IOException {
+ ClassReader classReader = new ClassReader(inputStream);
+ ClassNode classNode = new ClassNode(Opcodes.ASM5);
+
+ classReader.accept(classNode, EMPTY_FLAGS);
+
+ modifyClass(classNode);
+
+ ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+ classNode.accept(classWriter);
+
+ outputStream.putNextEntry(new ZipEntry(entry.getName()));
+ outputStream.write(classWriter.toByteArray());
+ }
+
+ /**
+ * Modifies a {@link ClassNode} to clear final flags and rewrite byte code.
+ */
+ @SuppressWarnings("unchecked")
+ private void modifyClass(ClassNode classNode) {
+ // Make the class not final.
+ classNode.access &= ~Opcodes.ACC_FINAL;
+
+ List<MethodNode> methodNodes = classNode.methods;
+ for (MethodNode methodNode : methodNodes) {
+ methodNode.access &= ~Opcodes.ACC_FINAL;
+ fixMethodBody(methodNode, classNode);
+ }
+
+ List<FieldNode> fieldNodes = classNode.fields;
+ for (FieldNode fieldNode : fieldNodes) {
+ // Make public instance fields non-final. This is needed e.g. to "mock" SyncResult.stats.
+ if ((fieldNode.access & Opcodes.ACC_PUBLIC) != 0 &&
+ (fieldNode.access & Opcodes.ACC_STATIC) == 0) {
+ fieldNode.access &= ~Opcodes.ACC_FINAL;
+ }
+ }
+
+ List<InnerClassNode> innerClasses = classNode.innerClasses;
+ for (InnerClassNode innerClassNode : innerClasses) {
+ innerClassNode.access &= ~Opcodes.ACC_FINAL;
+ }
+ }
+
+ /**
+ * Rewrites the method bytecode to remove the "Stub!" exception.
+ */
+ private void fixMethodBody(MethodNode methodNode, ClassNode classNode) {
+ if ((methodNode.access & Opcodes.ACC_NATIVE) != 0
+ || (methodNode.access & Opcodes.ACC_ABSTRACT) != 0) {
+ // Abstract and native method don't have bodies to rewrite.
+ return;
+ }
+
+ if ((classNode.access & Opcodes.ACC_ENUM) != 0 && ENUM_METHODS.contains(methodNode.name)) {
+ // Don't break enum classes.
+ return;
+ }
+
+ Type returnType = Type.getReturnType(methodNode.desc);
+ InsnList instructions = methodNode.instructions;
+
+ if (methodNode.name.equals(CONSTRUCTOR)) {
+ // Keep the call to parent constructor, delete the exception after that.
+
+ boolean deadCode = false;
+ for (AbstractInsnNode instruction : instructions.toArray()) {
+ if (!deadCode) {
+ if (instruction.getOpcode() == Opcodes.INVOKESPECIAL) {
+ instructions.insert(instruction, new InsnNode(Opcodes.RETURN));
+ // Start removing all following instructions.
+ deadCode = true;
+ }
+ } else {
+ instructions.remove(instruction);
+ }
+ }
+ } else {
+ instructions.clear();
+
+ if (returnDefaultValues || methodNode.name.equals(CLASS_CONSTRUCTOR)) {
+ if (INTEGER_LIKE_TYPES.contains(returnType)) {
+ instructions.add(new InsnNode(Opcodes.ICONST_0));
+ } else if (returnType.equals(Type.LONG_TYPE)) {
+ instructions.add(new InsnNode(Opcodes.LCONST_0));
+ } else if (returnType.equals(Type.FLOAT_TYPE)) {
+ instructions.add(new InsnNode(Opcodes.FCONST_0));
+ } else if (returnType.equals(Type.DOUBLE_TYPE)) {
+ instructions.add(new InsnNode(Opcodes.DCONST_0));
+ } else {
+ instructions.add(new InsnNode(Opcodes.ACONST_NULL));
+ }
+
+ instructions.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
+ } else {
+ instructions.insert(throwExceptionsList(methodNode, classNode));
+ }
+ }
+ }
+
+ private static InsnList throwExceptionsList(MethodNode methodNode, ClassNode classNode) {
+ try {
+ String runtimeException = Type.getInternalName(RuntimeException.class);
+ Constructor<RuntimeException> constructor =
+ RuntimeException.class.getConstructor(String.class);
+
+ InsnList instructions = new InsnList();
+ instructions.add(
+ new TypeInsnNode(Opcodes.NEW, runtimeException));
+ instructions.add(new InsnNode(Opcodes.DUP));
+
+ String className = classNode.name.replace('/', '.');
+ instructions.add(new LdcInsnNode("Method " + methodNode.name + " in " + className
+ + " not mocked. "
+ + "See http://g.co/androidstudio/not-mocked for details."));
+ instructions.add(new MethodInsnNode(
+ Opcodes.INVOKESPECIAL,
+ runtimeException,
+ CONSTRUCTOR,
+ Type.getType(constructor).getDescriptor(),
+ false));
+ instructions.add(new InsnNode(Opcodes.ATHROW));
+
+ return instructions;
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/base/build-system/builder/src/main/java/com/android/builder/testing/SimpleTestRunner.java b/build-system/builder/src/main/java/com/android/builder/testing/SimpleTestRunner.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/testing/SimpleTestRunner.java
rename to build-system/builder/src/main/java/com/android/builder/testing/SimpleTestRunner.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/testing/TestData.java b/build-system/builder/src/main/java/com/android/builder/testing/TestData.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/testing/TestData.java
rename to build-system/builder/src/main/java/com/android/builder/testing/TestData.java
diff --git a/base/build-system/builder/src/main/java/com/android/builder/testing/TestRunner.java b/build-system/builder/src/main/java/com/android/builder/testing/TestRunner.java
similarity index 100%
rename from base/build-system/builder/src/main/java/com/android/builder/testing/TestRunner.java
rename to build-system/builder/src/main/java/com/android/builder/testing/TestRunner.java
diff --git a/base/build-system/builder/src/main/resources/com/android/builder/internal/AndroidManifest.template b/build-system/builder/src/main/resources/com/android/builder/internal/AndroidManifest.template
similarity index 100%
rename from base/build-system/builder/src/main/resources/com/android/builder/internal/AndroidManifest.template
rename to build-system/builder/src/main/resources/com/android/builder/internal/AndroidManifest.template
diff --git a/base/build-system/builder/src/test/java/com/android/builder/compiling/BuildConfigGeneratorTest.java b/build-system/builder/src/test/java/com/android/builder/compiling/BuildConfigGeneratorTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/compiling/BuildConfigGeneratorTest.java
rename to build-system/builder/src/test/java/com/android/builder/compiling/BuildConfigGeneratorTest.java
diff --git a/build-system/builder/src/test/java/com/android/builder/core/AaptPackageProcessBuilderTest.java b/build-system/builder/src/test/java/com/android/builder/core/AaptPackageProcessBuilderTest.java
new file mode 100644
index 0000000..48fa310
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/core/AaptPackageProcessBuilderTest.java
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.core;
+
+import com.android.annotations.NonNull;
+import com.android.builder.dependency.SymbolFileProvider;
+import com.android.builder.model.AaptOptions;
+import com.android.ide.common.process.ProcessInfo;
+import com.android.repository.Revision;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.android.sdklib.repositoryv2.targets.AndroidTargetManager;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for {@link AaptPackageProcessBuilder} class
+ */
+public class AaptPackageProcessBuilderTest extends TestCase {
+
+ @Mock
+ AaptOptions mAaptOptions;
+
+ BuildToolInfo mBuildToolInfo;
+ IAndroidTarget mIAndroidTarget;
+
+ ILogger mLogger = new StdLogger(StdLogger.Level.VERBOSE);
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ AndroidSdkHandler handler = AndroidSdkHandler.getInstance(getSdkDir());
+ mBuildToolInfo = handler.getLatestBuildTool(progress);
+ if (mBuildToolInfo == null) {
+ throw new RuntimeException("Test requires build-tools 21");
+ }
+ AndroidTargetManager targetManager = handler.getAndroidTargetManager(progress);
+ for (IAndroidTarget iAndroidTarget : targetManager.getTargets(progress)) {
+ if (iAndroidTarget.getVersion().getApiLevel() == 21) {
+ mIAndroidTarget = iAndroidTarget;
+ }
+ }
+ if (mIAndroidTarget == null) {
+ throw new RuntimeException("Test requires android-21");
+ }
+ }
+
+ public void testAndroidManifestPackaging() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir");
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(mBuildToolInfo, mIAndroidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertTrue(virtualAndroidManifestFile.getAbsolutePath().equals(
+ command.get(command.indexOf("-M") + 1)));
+ assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
+ assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
+ }
+
+ public void testResourcesPackaging() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT);
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(mBuildToolInfo, mIAndroidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertTrue(virtualAndroidManifestFile.getAbsolutePath().equals(
+ command.get(command.indexOf("-M") + 1)));
+ assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
+ assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
+ assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
+ assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
+ assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
+ assertTrue("com.example.package.forR".equals(command.get(command.indexOf("--custom-package") + 1)));
+
+ assertTrue(command.indexOf("-f") != -1);
+ assertTrue(command.indexOf("--no-crunch") != -1);
+ assertTrue(command.indexOf("-0") != -1);
+ assertTrue(command.indexOf("apk") != -1);
+ }
+
+ public void testResourcesPackagingForTest() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.ANDROID_TEST);
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(mBuildToolInfo, mIAndroidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertTrue(virtualAndroidManifestFile.getAbsolutePath().equals(
+ command.get(command.indexOf("-M") + 1)));
+ assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
+ assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
+ assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
+ assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
+ assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
+ assertTrue(command.indexOf("--custom-package") == -1);
+
+ assertTrue(command.indexOf("-f") != -1);
+ assertTrue(command.indexOf("--no-crunch") != -1);
+ assertTrue(command.indexOf("-0") != -1);
+ assertTrue(command.indexOf("apk") != -1);
+ }
+
+ public void testResourcesPackagingForLibrary() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.LIBRARY);
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(mBuildToolInfo, mIAndroidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertTrue(virtualAndroidManifestFile.getAbsolutePath().equals(
+ command.get(command.indexOf("-M") + 1)));
+ assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
+ assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
+ assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
+ assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
+ assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
+
+ assertTrue(command.indexOf("--non-constant-id") != -1);
+
+ assertTrue(command.indexOf("-f") != -1);
+ assertTrue(command.indexOf("--no-crunch") != -1);
+ assertTrue(command.indexOf("-0") != -1);
+ assertTrue(command.indexOf("apk") != -1);
+ }
+
+
+ public void testSplitResourcesPackaging() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setSplits(ImmutableList.of("mdpi", "hdpi"));
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(mBuildToolInfo, mIAndroidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertTrue(virtualAndroidManifestFile.getAbsolutePath().equals(
+ command.get(command.indexOf("-M") + 1)));
+ assertTrue("/path/to/non/existent/dir".equals(command.get(command.indexOf("-F") + 1)));
+ assertTrue(command.get(command.indexOf("-I") + 1).contains("android.jar"));
+ assertTrue("/path/to/res/folder".equals(command.get(command.indexOf("-S") + 1)));
+ assertTrue("/path/to/assets/folder".equals(command.get(command.indexOf("-A") + 1)));
+ assertTrue("path/to/source/output/dir".equals(command.get(command.indexOf("-J") + 1)));
+ assertTrue("com.example.package.forR".equals(command.get(command.indexOf("--custom-package") + 1)));
+
+ assertTrue(command.indexOf("-f") != -1);
+ assertTrue(command.indexOf("--no-crunch") != -1);
+ assertTrue(command.indexOf("-0") != -1);
+ assertTrue(command.indexOf("apk") != -1);
+
+ assertTrue("--split".equals(command.get(command.indexOf("mdpi") - 1)));
+ assertTrue("--split".equals(command.get(command.indexOf("hdpi") - 1)));
+ assertTrue(command.indexOf("--preferred-density") == -1);
+ }
+
+ public void testPre21ResourceConfigsAndPreferredDensity() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setResourceConfigs(ImmutableList.of("res1", "res2"))
+ .setPreferredDensity("xhdpi");
+
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(20, 0, 0));
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() < 20) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android-20");
+ }
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(buildToolInfo, androidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertTrue("res1,res2,xhdpi,nodpi".equals(command.get(command.indexOf("-c") + 1)));
+ assertTrue(command.indexOf("--preferred-density") == -1);
+ }
+
+ public void testPost21ResourceConfigsAndPreferredDensity() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setResourceConfigs(ImmutableList.of("res1", "res2"))
+ .setPreferredDensity("xhdpi");
+
+
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(21, 0, 0));
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() < 21) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android-21");
+ }
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(buildToolInfo, androidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertEquals("res1,res2", command.get(command.indexOf("-c") + 1));
+ assertEquals("xhdpi", command.get(command.indexOf("--preferred-density") + 1));
+ }
+
+ public void testResConfigAndSplitConflict() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setResourceConfigs(
+ ImmutableList.of("nodpi", "en", "fr", "mdpi", "hdpi", "xxhdpi", "xxxhdpi"))
+ .setSplits(ImmutableList.of("xhdpi"))
+ .setPreferredDensity("xhdpi");
+
+
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(21, 0, 0));
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() < 21) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android-21");
+ }
+
+ try {
+ aaptPackageProcessBuilder.build(buildToolInfo, androidTarget, mLogger);
+ } catch(Exception expected) {
+ assertEquals("Splits for densities \"xhdpi\" were configured, yet the resConfigs settings does not include such splits. The resulting split APKs would be empty.\n"
+ + "Suggestion : exclude those splits in your build.gradle : \n"
+ + "splits {\n"
+ + " density {\n"
+ + " enable true\n"
+ + " exclude \"xhdpi\"\n"
+ + " }\n"
+ + "}\n"
+ + "OR add them to the resConfigs list.", expected.getMessage());
+ }
+ }
+
+ public void testResConfigAndSplitConflict2() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setResourceConfigs(ImmutableList.of("xxxhdpi"))
+ .setSplits(ImmutableList.of("hdpi", "mdpi", "xxhdpi"))
+ .setPreferredDensity("xhdpi");
+
+
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(21, 0, 0));
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() < 21) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android-21");
+ }
+
+ try {
+ aaptPackageProcessBuilder.build(buildToolInfo, androidTarget, mLogger);
+ } catch(Exception expected) {
+ assertEquals("Splits for densities \"hdpi,mdpi,xxhdpi\" were configured, yet the "
+ + "resConfigs settings does not include such splits. The resulting split APKs "
+ + "would be empty.\n"
+ + "Suggestion : exclude those splits in your build.gradle : \n"
+ + "splits {\n"
+ + " density {\n"
+ + " enable true\n"
+ + " exclude \"hdpi\",\"mdpi\",\"xxhdpi\"\n"
+ + " }\n"
+ + "}\n"
+ + "OR add them to the resConfigs list.", expected.getMessage());
+ }
+ }
+
+ public void testResConfigAndSplitNoConflict() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setResourceConfigs(ImmutableList
+ .of("en", "fr", "es", "de", "it", "mdpi", "hdpi", "xhdpi", "xxhdpi"))
+ .setSplits(ImmutableList.of("mdpi", "hdpi", "xhdpi", "xxhdpi"));
+
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(20, 0, 0));
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() >= 20) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android 20");
+ }
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(buildToolInfo, androidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertEquals("en,fr,es,de,it,mdpi,hdpi,xhdpi,xxhdpi",
+ command.get(command.indexOf("-c") + 1));
+ assertTrue("--split".equals(command.get(command.indexOf("mdpi") - 1)));
+ assertTrue("--split".equals(command.get(command.indexOf("hdpi") - 1)));
+ assertTrue("--split".equals(command.get(command.indexOf("xhdpi") - 1)));
+ assertTrue("--split".equals(command.get(command.indexOf("xxhdpi") - 1)));
+ assertEquals(-1, command.indexOf("xxxhdpi"));
+ }
+
+ public void testResConfigWithPreferredDensityFlags() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setResourceConfigs(ImmutableList
+ .of("en", "fr", "es", "de", "it", "hdpi"));
+
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(21, 0, 0));
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() >= 21) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android-21");
+ }
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(buildToolInfo, androidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertEquals("en,fr,es,de,it",
+ command.get(command.indexOf("-c") + 1));
+ assertEquals(-1, command.indexOf("mdpi"));
+ assertTrue("--preferred-density".equals(command.get(command.indexOf("hdpi") - 1)));
+ assertEquals(-1, command.indexOf("xhdpi"));
+ assertEquals(-1, command.indexOf("xxhdpi"));
+ assertEquals(-1, command.indexOf("xxxhdpi"));
+ }
+
+
+ public void testResConfigAndPreferredDensityConflict() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ .setResourceConfigs(ImmutableList.of( "en", "fr", "es", "de", "it", "hdpi"))
+ .setSplits(ImmutableList.of("hdpi"))
+ .setPreferredDensity("hdpi");
+
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(21, 0, 0));
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() >= 21) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android-21");
+ }
+
+ try {
+ aaptPackageProcessBuilder.build(buildToolInfo, androidTarget, mLogger);
+ } catch (Exception expected) {
+ assertEquals("When using splits in tools 21 and above, resConfigs should not contain "
+ + "any densities. Right now, it contains \"hdpi\"\n"
+ + "Suggestion: remove these from resConfigs from build.gradle", expected.getMessage());
+ }
+ }
+
+ public void testResConfigAndPreferredDensityNoConflict() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+ File assetsFolder = Mockito.mock(File.class);
+ Mockito.when(assetsFolder.isDirectory()).thenReturn(true);
+ Mockito.when(assetsFolder.getAbsolutePath()).thenReturn("/path/to/assets/folder");
+ File resFolder = Mockito.mock(File.class);
+ Mockito.when(resFolder.isDirectory()).thenReturn(true);
+ Mockito.when(resFolder.getAbsolutePath()).thenReturn("/path/to/res/folder");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir")
+ .setAssetsFolder(assetsFolder)
+ .setResFolder(resFolder)
+ .setPackageForR("com.example.package.forR")
+ .setSourceOutputDir("path/to/source/output/dir")
+ .setLibraries(ImmutableList.of(Mockito.mock(SymbolFileProvider.class)))
+ .setType(VariantType.DEFAULT)
+ // only languages, no density...
+ .setResourceConfigs(ImmutableList.of("en", "fr", "es", "de", "it"))
+ .setPreferredDensity("hdpi");
+
+ BuildToolInfo buildToolInfo = getBuildToolInfo(new Revision(21, 0, 0));
+ if (buildToolInfo == null) {
+ throw new RuntimeException("Test requires build-tools 21");
+ }
+ IAndroidTarget androidTarget = null;
+ for (IAndroidTarget iAndroidTarget : getTargets()) {
+ if (iAndroidTarget.getVersion().getApiLevel() >= 21) {
+ androidTarget = iAndroidTarget;
+ break;
+ }
+ }
+ if (androidTarget == null) {
+ throw new RuntimeException("Test requires pre android-21");
+ }
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(buildToolInfo, androidTarget, mLogger);
+
+ List<String> command = processInfo.getArgs();
+
+ assertEquals("en,fr,es,de,it", command.get(command.indexOf("-c") + 1));
+ }
+
+ public void testEnvironment() {
+ File virtualAndroidManifestFile = new File("/path/to/non/existent/file");
+
+ AaptPackageProcessBuilder aaptPackageProcessBuilder =
+ new AaptPackageProcessBuilder(virtualAndroidManifestFile, mAaptOptions);
+ aaptPackageProcessBuilder.setResPackageOutput("/path/to/non/existent/dir");
+
+ // add an env to the builder
+ aaptPackageProcessBuilder.addEnvironment("foo", "bar");
+
+ ProcessInfo processInfo = aaptPackageProcessBuilder
+ .build(mBuildToolInfo, mIAndroidTarget, mLogger);
+
+ Map<String, Object> env = processInfo.getEnvironment();
+ assertEquals(1, env.size());
+ assertNotNull(env.get("foo"));
+ assertEquals("bar", env.get("foo"));
+ }
+
+ /**
+ * Returns the SDK folder as built from the Android source tree.
+ * @return the SDK
+ */
+ @NonNull
+ protected static File getSdkDir() {
+ String androidHome = System.getenv("ANDROID_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ }
+ }
+
+ throw new IllegalStateException("SDK not defined with ANDROID_HOME");
+ }
+
+ private static BuildToolInfo getBuildToolInfo(Revision revision) {
+ AndroidSdkHandler handler = AndroidSdkHandler.getInstance(getSdkDir());
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ BuildToolInfo buildToolInfo = handler.getBuildToolInfo(revision, progress);
+ if (buildToolInfo == null) {
+ throw new RuntimeException("Test requires build-tools " + revision);
+ }
+ return buildToolInfo;
+ }
+
+ private static Collection<IAndroidTarget> getTargets() {
+ AndroidSdkHandler handler = AndroidSdkHandler.getInstance(getSdkDir());
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ Collection<IAndroidTarget> targets = handler.getAndroidTargetManager(progress)
+ .getTargets(progress);
+ return targets;
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/core/AndroidBuilderTest.java b/build-system/builder/src/test/java/com/android/builder/core/AndroidBuilderTest.java
new file mode 100644
index 0000000..b6a2887
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/core/AndroidBuilderTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.builder.core;
+
+import static com.android.builder.core.AndroidBuilder.parseHeapSize;
+import static com.android.builder.core.AndroidBuilder.shouldDexInProcess;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+ at RunWith(MockitoJUnitRunner.class)
+public class AndroidBuilderTest {
+
+ @ClassRule
+ public static TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private static File smallJar;
+ private static File smallJar2;
+ private static File largeJar;
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ ILogger logger;
+
+ @Mock
+ DexProcessBuilder processBuilder;
+
+ @Mock
+ DexOptions dexOptions;
+
+ @Before
+ public void initLoggerMock() {
+ logger = mock(ILogger.class, withSettings().verboseLogging());
+ }
+
+ @BeforeClass
+ public static void createJars() throws IOException {
+ smallJar = tempFolder.newFile("smallClasses.jar");
+ createJar(smallJar, 5);
+ smallJar2 = tempFolder.newFile("smallClasses2.jar");
+ createJar(smallJar2, 6);
+ largeJar = tempFolder.newFile("largeClasses.jar");
+ createJar(largeJar, 100);
+ }
+
+ private static void createJar(File file, int number) throws IOException {
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file));
+ try {
+ for (int i = 0; i < number; i++) {
+ jarOutputStream.putNextEntry(new ZipEntry(String.format("AnObject%1$d.class", i)));
+ jarOutputStream.closeEntry();
+ }
+ } finally {
+ //noinspection ThrowFromFinallyBlock
+ jarOutputStream.close();
+ }
+ }
+
+ @Test
+ public void checkHeapSizeParser() {
+ assertEquals(123L, parseHeapSize("123"));
+ assertEquals(2048L, parseHeapSize("2k"));
+ assertEquals(2048L, parseHeapSize("2K"));
+ assertEquals(1024L * 1024L * 7L, parseHeapSize("7M"));
+ assertEquals(1024L * 1024L * 1024L * 17L, parseHeapSize("17g"));
+ }
+
+ @Test
+ public void testShouldDexInProcess() {
+ // a ridiculously small number to ensure dex in process not be disabled due to memory needs.
+ when(dexOptions.getJavaMaxHeapSize()).thenReturn("1024");
+ assertFalse(shouldDexInProcess(
+ processBuilder, dexOptions, new Revision(23, 0, 2), false, logger));
+ }
+
+ @Test
+ public void testDisabledDexInProcess() {
+ assertFalse(shouldDexInProcess(
+ processBuilder, dexOptions, new Revision(23, 0, 2), false, logger));
+ assertFalse(shouldDexInProcess(
+ processBuilder, dexOptions, new Revision(23, 0, 2), false, logger));
+ }
+
+
+
+ @Test
+ @Ignore
+ public void testExplicitInvalidDexInProcess() {
+ expectedException.expectMessage(containsString("23.0.2"));
+ shouldDexInProcess(
+ processBuilder, dexOptions, new Revision(23, 0, 1), false, logger);
+ }
+
+ @Test
+ @Ignore
+ public void notEnoughMemoryForDexInProcess() {
+ when(dexOptions.getJavaMaxHeapSize()).thenReturn("10000G");
+ shouldDexInProcess(processBuilder, dexOptions, new Revision(23, 0, 2), false, logger);
+ }
+
+ @Test
+ public void instantRunWithOldBuildTools() {
+ assertFalse(shouldDexInProcess(
+ processBuilder, dexOptions, new Revision(23, 0, 1), true, logger));
+ }
+
+
+ @Test
+ public void lowMemoryWithInstantRunAndManyClasses() {
+ when(dexOptions.getJavaMaxHeapSize()).thenReturn("10000G");
+ when(processBuilder.getInputs()).thenReturn(Collections.singleton(largeJar));
+ assertFalse(shouldDexInProcess(
+ processBuilder, dexOptions, new Revision(23, 0, 2), true, logger));
+ }
+
+ @Test
+ public void lowMemoryWithInstantRunWithMultipleInputs() {
+ when(dexOptions.getJavaMaxHeapSize()).thenReturn("10000G");
+ when(processBuilder.getInputs()).thenReturn(ImmutableSet.of(smallJar, smallJar2));
+ assertFalse(shouldDexInProcess(
+ processBuilder, dexOptions, new Revision(23, 0, 2), true, logger));
+ }
+
+
+
+}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/core/ApkInfoParserTest.java b/build-system/builder/src/test/java/com/android/builder/core/ApkInfoParserTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/core/ApkInfoParserTest.java
rename to build-system/builder/src/test/java/com/android/builder/core/ApkInfoParserTest.java
diff --git a/base/build-system/builder/src/test/java/com/android/builder/core/DefaultProductFlavorTest.java b/build-system/builder/src/test/java/com/android/builder/core/DefaultProductFlavorTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/core/DefaultProductFlavorTest.java
rename to build-system/builder/src/test/java/com/android/builder/core/DefaultProductFlavorTest.java
diff --git a/base/build-system/builder/src/test/java/com/android/builder/core/MockSourceProvider.java b/build-system/builder/src/test/java/com/android/builder/core/MockSourceProvider.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/core/MockSourceProvider.java
rename to build-system/builder/src/test/java/com/android/builder/core/MockSourceProvider.java
diff --git a/base/build-system/builder/src/test/java/com/android/builder/core/VariantConfigurationTest.java b/build-system/builder/src/test/java/com/android/builder/core/VariantConfigurationTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/core/VariantConfigurationTest.java
rename to build-system/builder/src/test/java/com/android/builder/core/VariantConfigurationTest.java
diff --git a/base/build-system/builder/src/test/java/com/android/builder/internal/SymbolLoaderTest.java b/build-system/builder/src/test/java/com/android/builder/internal/SymbolLoaderTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/internal/SymbolLoaderTest.java
rename to build-system/builder/src/test/java/com/android/builder/internal/SymbolLoaderTest.java
diff --git a/base/build-system/builder/src/test/java/com/android/builder/internal/SymbolWriterTest.java b/build-system/builder/src/test/java/com/android/builder/internal/SymbolWriterTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/internal/SymbolWriterTest.java
rename to build-system/builder/src/test/java/com/android/builder/internal/SymbolWriterTest.java
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java b/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java
new file mode 100644
index 0000000..c9e15a8
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/compiler/PreDexCacheTest.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import static com.android.SdkConstants.FN_AAPT;
+import static com.android.SdkConstants.FN_AIDL;
+import static com.android.SdkConstants.FN_BCC_COMPAT;
+import static com.android.SdkConstants.FN_DX;
+import static com.android.SdkConstants.FN_DX_JAR;
+import static com.android.SdkConstants.FN_RENDERSCRIPT;
+import static com.android.SdkConstants.FN_ZIPALIGN;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.DexOptions;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.core.LibraryRequest;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.sdk.SdkInfo;
+import com.android.builder.sdk.TargetInfo;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.process.JavaProcessExecutor;
+import com.android.ide.common.process.JavaProcessInfo;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.ide.common.process.ProcessInfo;
+import com.android.ide.common.process.ProcessOutput;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.ide.common.process.ProcessResult;
+import com.android.repository.Revision;
+import com.android.sdklib.BuildToolInfo;
+import com.android.utils.FileUtils;
+import com.android.utils.NullLogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+public class PreDexCacheTest extends TestCase {
+
+ private static final String DEX_DATA = "**";
+
+ private AndroidBuilder mAndroidBuilder;
+
+ /**
+ * implement a fake java process executor to intercept the call to dex and replace it
+ * with something else.
+ */
+ private static class FakeJavaProcessExecutor implements JavaProcessExecutor {
+
+ @NonNull
+ @Override
+ public ProcessResult execute(
+ @NonNull JavaProcessInfo javaProcessInfo,
+ @NonNull ProcessOutputHandler processOutputHandler) {
+
+ List<String> command = javaProcessInfo.getArgs();
+
+ ProcessException processException = null;
+
+ try {
+ // small delay to test multi-threading.
+ Thread.sleep(1000);
+
+ // input file is the last file in the command
+ File input = new File(command.get(command.size() - 1));
+ if (!input.isFile()) {
+ throw new FileNotFoundException(input.getPath());
+ }
+
+ // loop on the command to find --output
+ String output = null;
+ for (int i = 0; i < command.size(); i++) {
+ if ("--output".equals(command.get(i))) {
+ output = command.get(i + 1);
+ break;
+ }
+ }
+
+ if (output == null) {
+ throw new IOException("Failed to find output in dex commands");
+ }
+
+ // read the source content
+ JarFile jarFile = new JarFile(input);
+ JarEntry jarEntry = jarFile.getJarEntry("content.class");
+ assert jarEntry != null;
+ InputStream contentStream = jarFile.getInputStream(jarEntry);
+ byte[] content = new byte[256];
+ int read = contentStream.read(content);
+ contentStream.close();
+ jarFile.close();
+
+ String line = new String(content, 0, read, Charsets.UTF_8);
+
+ // write it
+ Files.write(DEX_DATA + line + DEX_DATA, new File(output), Charsets.UTF_8);
+ } catch (Exception e) {
+ //noinspection ThrowableInstanceNeverThrown
+ processException = new ProcessException(null, e);
+ }
+
+ final ProcessException rethrow = processException;
+ return new ProcessResult() {
+ @NonNull
+ @Override
+ public ProcessResult assertNormalExitValue() throws ProcessException {
+ return this;
+ }
+
+ @Override
+ public int getExitValue() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult rethrowFailure() throws ProcessException {
+ if (rethrow != null) {
+ throw rethrow;
+ }
+ return this;
+ }
+ };
+ }
+ }
+
+ /**
+ * Fake executor that fails to execute
+ */
+ private static class FailingExecutor implements JavaProcessExecutor {
+
+ @NonNull
+ @Override
+ public ProcessResult execute(@NonNull JavaProcessInfo javaProcessInfo,
+ @NonNull ProcessOutputHandler processOutputHandler) {
+ try {
+ Thread.sleep(1000);
+ throw new IOException("foo");
+ } catch (final Exception e) {
+ return new ProcessResult() {
+ @NonNull
+ @Override
+ public ProcessResult assertNormalExitValue() throws ProcessException {
+ return this;
+ }
+
+ @Override
+ public int getExitValue() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult rethrowFailure() throws ProcessException {
+ throw new ProcessException(null, e);
+ }
+ };
+ }
+ }
+ }
+
+ private static class FakeProcessOutputHandler implements ProcessOutputHandler {
+
+ @NonNull
+ @Override
+ public ProcessOutput createOutput() {
+ return null;
+ }
+
+ @Override
+ public void handleOutput(@NonNull ProcessOutput processOutput) throws ProcessException {
+
+ }
+ }
+
+ private static class FakeDexOptions implements DexOptions {
+
+ @Override
+ public boolean getIncremental() {
+ return false;
+ }
+
+ @Override
+ public boolean getPreDexLibraries() {
+ return false;
+ }
+
+ @Override
+ public boolean getJumboMode() {
+ return false;
+ }
+
+ @Override
+ @Nullable
+ public String getJavaMaxHeapSize() {
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public Integer getThreadCount() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Integer getMaxProcessCount() {
+ return null;
+ }
+ }
+
+ private static class FakeProcessExecutor implements ProcessExecutor {
+ @NonNull
+ @Override
+ public ProcessResult execute(@NonNull ProcessInfo processInfo,
+ @NonNull ProcessOutputHandler processOutputHandler) {
+ throw new RuntimeException("fake");
+ }
+ }
+
+ private static class FakeErrorReporter extends ErrorReporter {
+ protected FakeErrorReporter(@NonNull EvaluationMode mode) {
+ super(mode);
+ }
+
+ @NonNull
+ @Override
+ public SyncIssue handleSyncIssue(
+ @Nullable String data, int type, int severity, @NonNull String msg) {
+ throw new RuntimeException("fake");
+ }
+
+ @Override
+ public void receiveMessage(@NonNull Message message) {
+ throw new RuntimeException("fake");
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mAndroidBuilder = new AndroidBuilder(
+ "testProject",
+ getClass().getName(),
+ new FakeProcessExecutor(),
+ new FakeJavaProcessExecutor(),
+ new FakeErrorReporter(ErrorReporter.EvaluationMode.STANDARD),
+ new NullLogger(),
+ true);
+
+ TargetInfo targetInfo = mock(TargetInfo.class);
+ when(targetInfo.getBuildTools()).thenReturn(getBuildToolInfo());
+
+ mAndroidBuilder.setTargetInfo(
+ mock(SdkInfo.class),
+ targetInfo,
+ ImmutableList.<LibraryRequest>of());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ File toolFolder = mAndroidBuilder.getTargetInfo().getBuildTools().getLocation();
+ FileUtils.deleteFolder(toolFolder);
+
+ PreDexCache.getCache().clear(null, null);
+
+ super.tearDown();
+ }
+
+ public void testSinglePreDexLibrary() throws IOException, ProcessException, InterruptedException {
+ String content = "Some Content";
+ File input = createInputFile(content);
+
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+
+ PreDexCache.getCache().preDexLibrary(
+ mAndroidBuilder,
+ input,
+ output,
+ false /*multidex*/,
+ new FakeDexOptions(),
+ new FakeProcessOutputHandler());
+
+ checkOutputFile(content, output);
+ }
+
+ public void testThreadedPreDexLibrary() throws IOException, InterruptedException {
+ String content = "Some Content";
+ final File input = createInputFile(content);
+ input.deleteOnExit();
+
+ Thread[] threads = new Thread[3];
+ final File[] outputFiles = new File[threads.length];
+
+ final JavaProcessExecutor javaProcessExecutor = new FakeJavaProcessExecutor();
+ final DexOptions dexOptions = new FakeDexOptions();
+
+ for (int i = 0 ; i < threads.length ; i++) {
+ final int ii = i;
+ threads[i] = new Thread() {
+ @Override
+ public void run() {
+ try {
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+ outputFiles[ii] = output;
+
+ PreDexCache.getCache().preDexLibrary(
+ mAndroidBuilder,
+ input,
+ output,
+ false /*multidex*/,
+ dexOptions,
+ new FakeProcessOutputHandler());
+ } catch (Exception ignored) {
+
+ }
+ }
+ };
+
+ threads[i].start();
+ }
+
+ // wait on the threads.
+ for (Thread thread : threads) {
+ thread.join();
+ }
+
+ // check the output.
+ for (File outputFile : outputFiles) {
+ checkOutputFile(content, outputFile);
+ }
+
+ // now check the cache
+ PreDexCache cache = PreDexCache.getCache();
+ assertEquals(1, cache.getMisses());
+ assertEquals(threads.length - 1, cache.getHits());
+ }
+
+ public void testThreadedPreDexLibraryWithError() throws IOException, InterruptedException {
+ String content = "Some Content";
+ final File input = createInputFile(content);
+ input.deleteOnExit();
+
+ Thread[] threads = new Thread[3];
+ final File[] outputFiles = new File[threads.length];
+
+ final JavaProcessExecutor javaProcessExecutor = new FakeJavaProcessExecutor();
+ final JavaProcessExecutor javaProcessExecutorWithError = new FailingExecutor();
+ final DexOptions dexOptions = new FakeDexOptions();
+
+ final AtomicInteger threadDoneCount = new AtomicInteger();
+
+ for (int i = 0 ; i < threads.length ; i++) {
+ final int ii = i;
+ threads[i] = new Thread() {
+ @Override
+ public void run() {
+ try {
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+ outputFiles[ii] = output;
+
+ AndroidBuilder builder = new AndroidBuilder(
+ "testProject",
+ getClass().getName(),
+ new FakeProcessExecutor(),
+ ii == 0 ? javaProcessExecutorWithError : javaProcessExecutor,
+ new FakeErrorReporter(ErrorReporter.EvaluationMode.STANDARD),
+ new NullLogger(),
+ true);
+
+ PreDexCache.getCache().preDexLibrary(
+ builder,
+ input,
+ output,
+ false /*multidex*/,
+ dexOptions,
+ new FakeProcessOutputHandler());
+ } catch (Exception ignored) {
+
+ }
+ threadDoneCount.incrementAndGet();
+ }
+ };
+
+ threads[i].start();
+ }
+
+ // wait on the threads, long enough but stop after a while
+ for (Thread thread : threads) {
+ thread.join(5000);
+ }
+
+ // if the test fail, we'll have two threads still blocked on the countdownlatch.
+ assertEquals(3, threadDoneCount.get());
+ }
+
+
+ public void testReload() throws IOException, ProcessException, InterruptedException {
+ final JavaProcessExecutor javaProcessExecutor = new FakeJavaProcessExecutor();
+ final DexOptions dexOptions = new FakeDexOptions();
+
+ // convert one file.
+ String content = "Some Content";
+ File input = createInputFile(content);
+
+ File output = File.createTempFile("predex", ".jar");
+ output.deleteOnExit();
+
+ PreDexCache.getCache().preDexLibrary(
+ mAndroidBuilder,
+ input,
+ output,
+ false /*multidex*/,
+ dexOptions,
+ new FakeProcessOutputHandler());
+
+ checkOutputFile(content, output);
+
+ // store the cache
+ File cacheXml = File.createTempFile("predex", ".xml");
+ cacheXml.deleteOnExit();
+ PreDexCache.getCache().clear(cacheXml, null);
+
+ // reload.
+ PreDexCache.getCache().load(cacheXml);
+
+ // re-pre-dex into another file.
+ File output2 = File.createTempFile("predex", ".jar");
+ output2.deleteOnExit();
+
+ PreDexCache.getCache().preDexLibrary(
+ mAndroidBuilder,
+ input,
+ output2,
+ false /*multidex*/,
+ dexOptions,
+ new FakeProcessOutputHandler());
+
+ // check the output
+ checkOutputFile(content, output2);
+
+ // check the hit/miss
+ PreDexCache cache = PreDexCache.getCache();
+ assertEquals(0, cache.getMisses());
+ assertEquals(1, cache.getHits());
+ }
+
+ private static File createInputFile(String content) throws IOException {
+ File input = File.createTempFile("predex", ".jar");
+ input.deleteOnExit();
+
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(input));
+ try {
+ jarOutputStream.putNextEntry(new ZipEntry("content.class"));
+ jarOutputStream.write(content.getBytes(Charsets.UTF_8));
+ jarOutputStream.closeEntry();
+ } finally {
+ jarOutputStream.close();
+ }
+
+ return input;
+ }
+
+ private static void checkOutputFile(String content, File output) throws IOException {
+ List<String> lines = Files.readLines(output, Charsets.UTF_8);
+
+ assertEquals(1, lines.size());
+ assertEquals(DEX_DATA + content + DEX_DATA, lines.get(0));
+ }
+
+ /**
+ * Create a fake build tool info where the dx tool actually exists (even if it's not used).
+ */
+ private static BuildToolInfo getBuildToolInfo() throws IOException {
+ File toolDir = Files.createTempDir();
+
+ // create a dx.jar file.
+ File dx = new File(toolDir, FN_DX_JAR);
+ Files.write("dx!", dx, Charsets.UTF_8);
+
+ return new BuildToolInfo(
+ new Revision(21, 0, 1),
+ toolDir,
+ new File(toolDir, FN_AAPT),
+ new File(toolDir, FN_AIDL),
+ new File(toolDir, FN_DX),
+ dx,
+ new File(toolDir, FN_RENDERSCRIPT),
+ new File(toolDir, "include"),
+ new File(toolDir, "clang-include"),
+ new File(toolDir, FN_BCC_COMPAT),
+ new File(toolDir, "arm-linux-androideabi-ld"),
+ new File(toolDir, "i686-linux-android-ld"),
+ new File(toolDir, "mipsel-linux-android-ld"),
+ new File(toolDir, FN_ZIPALIGN));
+ }
+}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataStoreTest.java b/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataStoreTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataStoreTest.java
rename to build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataStoreTest.java
diff --git a/base/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataTest.java b/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataTest.java
rename to build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataTest.java
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/AlignmentTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/AlignmentTest.java
new file mode 100644
index 0000000..5baef11
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/AlignmentTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+public class AlignmentTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void addAlignedFile() throws Exception {
+ File newZFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ byte testBytes[] = "This is some text.".getBytes(Charsets.US_ASCII);
+
+ ZFile zf = new ZFile(newZFile);
+ zf.getAlignmentRules().add(new AlignmentRule(Pattern.compile(".*\\.txt"), 1024));
+ zf.add("test.txt", new ByteArrayEntrySource(testBytes), CompressionMethod.STORE);
+ zf.close();
+
+ byte found[] = FileUtils.readSegment(newZFile, 1024, testBytes.length);
+ assertArrayEquals(testBytes, found);
+ }
+
+ @Test
+ public void addNonAlignedFile() throws Exception {
+ File newZFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ byte testBytes[] = "This is some text.".getBytes(Charsets.US_ASCII);
+
+ ZFile zf = new ZFile(newZFile);
+ zf.getAlignmentRules().add(new AlignmentRule(Pattern.compile(".*\\.txt"), 1024));
+ zf.add("test.txt.foo", new ByteArrayEntrySource(testBytes), CompressionMethod.STORE);
+ zf.close();
+
+ assertTrue(newZFile.length() < 1024);
+ }
+
+ @Test
+ public void realignSingleFile() throws Exception {
+ File newZFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ byte testBytes0[] = "Text number 1".getBytes(Charsets.US_ASCII);
+ byte testBytes1[] = "Text number 2, which is actually 1".getBytes(Charsets.US_ASCII);
+
+ ZFile zf = new ZFile(newZFile);
+ zf.add("file0.txt", new ByteArrayEntrySource(testBytes0), CompressionMethod.STORE);
+ zf.add("file1.txt", new ByteArrayEntrySource(testBytes1), CompressionMethod.STORE);
+ zf.close();
+
+ StoredEntry se0 = zf.get("file0.txt");
+ assertNotNull(se0);
+ long offset0 = se0.getCentralDirectoryHeader().getOffset();
+
+ StoredEntry se1 = zf.get("file1.txt");
+ assertNotNull(se1);
+
+ assertTrue(newZFile.length() < 1024);
+
+ zf.getAlignmentRules().add(new AlignmentRule(Pattern.compile(".*\\.txt"), 1024));
+ se1.realign();
+ zf.close();
+
+ se0 = zf.get("file0.txt");
+ assertNotNull(se0);
+ assertEquals(offset0, se0.getCentralDirectoryHeader().getOffset());
+
+ se1 = zf.get("file1.txt");
+ assertNotNull(se1);
+ assertTrue(se1.getCentralDirectoryHeader().getOffset() > 950);
+ assertTrue(se1.getCentralDirectoryHeader().getOffset() < 1024);
+ assertArrayEquals(testBytes1, FileUtils.readSegment(newZFile, 1024, testBytes1.length));
+
+ assertTrue(newZFile.length() > 1024);
+ }
+
+ @Test
+ public void realignFile() throws Exception {
+ File newZFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ byte testBytes0[] = "Text number 1".getBytes(Charsets.US_ASCII);
+ byte testBytes1[] = "Text number 2, which is actually 1".getBytes(Charsets.US_ASCII);
+
+ ZFile zf = new ZFile(newZFile);
+ zf.add("file0.txt", new ByteArrayEntrySource(testBytes0), CompressionMethod.STORE);
+ zf.add("file1.txt", new ByteArrayEntrySource(testBytes1), CompressionMethod.STORE);
+ zf.close();
+
+ assertTrue(newZFile.length() < 1024);
+
+ zf.getAlignmentRules().add(new AlignmentRule(Pattern.compile(".*\\.txt"), 1024));
+ zf.realign();
+ zf.close();
+
+ StoredEntry se0 = zf.get("file0.txt");
+ assertNotNull(se0);
+ long off0 = 1024;
+
+ StoredEntry se1 = zf.get("file1.txt");
+ assertNotNull(se1);
+ long off1 = 2048;
+
+ /*
+ * ZFile does not guarantee any order.
+ */
+ if (se1.getCentralDirectoryHeader().getOffset() <
+ se0.getCentralDirectoryHeader().getOffset()) {
+ off0 = 2048;
+ off1 = 1024;
+ }
+
+ assertArrayEquals(testBytes0, FileUtils.readSegment(newZFile, off0, testBytes0.length));
+ assertArrayEquals(testBytes1, FileUtils.readSegment(newZFile, off1, testBytes1.length));
+ }
+
+ @Test
+ public void realignAlignedEntry() throws Exception {
+ File newZFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ byte testBytes[] = "This is some text.".getBytes(Charsets.US_ASCII);
+
+ ZFile zf = new ZFile(newZFile);
+ zf.getAlignmentRules().add(new AlignmentRule(Pattern.compile(".*\\.txt"), 1024));
+ zf.add("test.txt", new ByteArrayEntrySource(testBytes), CompressionMethod.STORE);
+ zf.close();
+
+ assertArrayEquals(testBytes, FileUtils.readSegment(newZFile, 1024, testBytes.length));
+
+ int flen = (int) newZFile.length();
+
+ StoredEntry entry = zf.get("test.txt");
+ assertNotNull(entry);
+ assertFalse(entry.realign());
+
+ zf.close();
+ assertEquals(flen, (int) newZFile.length());
+ assertArrayEquals(testBytes, FileUtils.readSegment(newZFile, 1024, testBytes.length));
+ }
+
+ @Test
+ public void alignmentRulesDoNotAffectAddedFiles() throws Exception {
+ File newZFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ byte testBytes0[] = "Text number 1".getBytes(Charsets.US_ASCII);
+ byte testBytes1[] = "Text number 2, which is actually 1".getBytes(Charsets.US_ASCII);
+
+ ZFile zf = new ZFile(newZFile);
+ zf.add("file0.txt", new ByteArrayEntrySource(testBytes0), CompressionMethod.STORE);
+ zf.getAlignmentRules().add(new AlignmentRule(Pattern.compile(".*\\.txt"), 1024));
+ zf.add("file1.txt", new ByteArrayEntrySource(testBytes1), CompressionMethod.STORE);
+ zf.close();
+
+ StoredEntry se0 = zf.get("file0.txt");
+ assertNotNull(se0);
+ assertEquals(0, se0.getCentralDirectoryHeader().getOffset());
+
+ StoredEntry se1 = zf.get("file1.txt");
+ assertNotNull(se1);
+ assertArrayEquals(testBytes1, FileUtils.readSegment(newZFile, 1024, testBytes1.length));
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/LinuxUnzipTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/LinuxUnzipTest.java
new file mode 100644
index 0000000..8ccfe6c
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/LinuxUnzipTest.java
@@ -0,0 +1,27 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import org.junit.Before;
+
+public class LinuxUnzipTest extends ZipToolsTestCase {
+ @Before
+ public void setUp() {
+ configure("linux-zip.zip", new String[] { "/usr/bin/unzip", "-v" },
+ "^\\s*(\\d+)\\s+(?:Stored|Defl:N).*\\s(\\S+)\\S*$", 2, 1, true);
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/Windows7ZipTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/Windows7ZipTest.java
new file mode 100644
index 0000000..872ede4
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/Windows7ZipTest.java
@@ -0,0 +1,27 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import org.junit.Before;
+
+public class Windows7ZipTest extends ZipToolsTestCase {
+ @Before
+ public void setUp() {
+ configure("windows-7zip.zip", new String[] { "c:\\Program Files\\7-Zip\\7z.exe", "l" },
+ "^(?:\\S+\\s+){3}(\\d+)\\S+\\d+\\s+(\\S+)\\s*$", 2, 1, true);
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/WindowsCompressedFoldersTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/WindowsCompressedFoldersTest.java
new file mode 100644
index 0000000..59abb98
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/WindowsCompressedFoldersTest.java
@@ -0,0 +1,27 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import org.junit.Before;
+
+public class WindowsCompressedFoldersTest extends ZipToolsTestCase {
+ @Before
+ public void setUp() {
+ configure("windows-cf.zip", new String[] { "cannot use compressed folders from cmd line "
+ + "to list zip contents" }, "", 1, 2, false);
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZFileNotificationTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZFileNotificationTest.java
new file mode 100644
index 0000000..ab23aa0
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZFileNotificationTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.utils.IOExceptionRunnable;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class ZFileNotificationTest {
+ private static class KeepListener extends ZFileExtension {
+ public int open;
+ public int beforeUpdated;
+ public int updated;
+ public int closed;
+ public List<Pair<StoredEntry, StoredEntry>> added;
+ public List<StoredEntry> removed;
+ public IOExceptionRunnable returnRunnable;
+
+ KeepListener() {
+ reset();
+ }
+
+ @Nullable
+ @Override
+ public IOExceptionRunnable open() {
+ open++;
+ return returnRunnable;
+ }
+
+ @Nullable
+ @Override
+ public IOExceptionRunnable beforeUpdate() {
+ beforeUpdated++;
+ return returnRunnable;
+ }
+
+ @Override
+ public void updated() {
+ updated++;
+ }
+
+ @Override
+ public void closed() {
+ closed++;
+ }
+
+ @Nullable
+ @Override
+ public IOExceptionRunnable added(@NonNull StoredEntry entry, @Nullable StoredEntry replaced) {
+ added.add(Pair.of(entry, replaced));
+ return returnRunnable;
+ }
+
+ @Nullable
+ @Override
+ public IOExceptionRunnable removed(@NonNull StoredEntry entry) {
+ removed.add(entry);
+ return returnRunnable;
+ }
+
+ void reset() {
+ open = 0;
+ beforeUpdated = 0;
+ updated = 0;
+ closed = 0;
+ added = Lists.newArrayList();
+ removed = Lists.newArrayList();
+ }
+
+ void assertClear() {
+ assertEquals(0, open);
+ assertEquals(0, beforeUpdated);
+ assertEquals(0, updated);
+ assertEquals(0, closed);
+ assertEquals(0, added.size());
+ assertEquals(0, removed.size());
+ }
+ }
+
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void notifyAddFile() throws Exception {
+ ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+ KeepListener kl = new KeepListener();
+ zf.addZFileExtension(kl);
+
+ kl.assertClear();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+ assertEquals(1, kl.added.size());
+ StoredEntry addedSe = kl.added.get(0).getFirst();
+ assertNull(kl.added.get(0).getSecond());
+ kl.added.clear();
+ kl.assertClear();
+
+ StoredEntry foo = zf.get("foo");
+ assertNotNull(foo);
+
+ assertSame(foo, addedSe);
+
+ zf.close();
+ }
+
+ @Test
+ public void notifyRemoveFile() throws Exception {
+ ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+ KeepListener kl = new KeepListener();
+ zf.addZFileExtension(kl);
+
+ kl.assertClear();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+ kl.reset();
+
+ StoredEntry foo = zf.get("foo");
+ assertNotNull(foo);
+
+ foo.delete();
+ assertEquals(1, kl.removed.size());
+ assertSame(foo, kl.removed.get(0));
+ kl.removed.clear();
+ kl.assertClear();
+
+ zf.close();
+ }
+
+ @Test
+ public void notifyUpdateFile() throws Exception {
+ ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+ KeepListener kl = new KeepListener();
+ zf.addZFileExtension(kl);
+
+ kl.assertClear();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+ StoredEntry foo1 = zf.get("foo");
+ kl.reset();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 2, 3 }), CompressionMethod.DEFLATE);
+ StoredEntry foo2 = zf.get("foo");
+
+ assertEquals(1, kl.added.size());
+ assertSame(foo2, kl.added.get(0).getFirst());
+ assertSame(foo1, kl.added.get(0).getSecond());
+
+ kl.added.clear();
+ kl.assertClear();
+
+ zf.close();
+ }
+
+ @Test
+ public void notifyOpenUpdateClose() throws Exception {
+ ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+ KeepListener kl = new KeepListener();
+ zf.addZFileExtension(kl);
+
+ kl.assertClear();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+ kl.reset();
+ zf.close();
+
+ assertEquals(1, kl.open);
+ kl.open = 0;
+ assertEquals(1, kl.beforeUpdated);
+ assertEquals(1, kl.updated);
+ kl.beforeUpdated = 0;
+ kl.updated = 0;
+ assertEquals(1, kl.closed);
+ kl.closed = 0;
+ kl.assertClear();
+ }
+
+ @Test
+ public void notifyOpenUpdate() throws Exception {
+ ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+ KeepListener kl = new KeepListener();
+ zf.addZFileExtension(kl);
+
+ kl.assertClear();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+ kl.reset();
+ zf.update();
+
+ assertEquals(1, kl.open);
+ kl.open = 0;
+ assertEquals(1, kl.beforeUpdated);
+ assertEquals(1, kl.updated);
+ kl.beforeUpdated = 0;
+ kl.updated = 0;
+ kl.assertClear();
+
+ zf.close();
+ }
+
+ @Test
+ public void notifyUpdate() throws Exception {
+ ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+ KeepListener kl = new KeepListener();
+ zf.addZFileExtension(kl);
+
+ kl.assertClear();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+ zf.update();
+ kl.reset();
+
+ zf.add("bar", new ByteArrayEntrySource(new byte[] { 2, 3 }), CompressionMethod.DEFLATE);
+ kl.reset();
+
+ zf.update();
+ assertEquals(1, kl.beforeUpdated);
+ assertEquals(1, kl.updated);
+ kl.beforeUpdated = 0;
+ kl.updated = 0;
+ kl.assertClear();
+
+ zf.close();
+ }
+
+ @Test
+ public void removedListenersAreNotNotified() throws Exception {
+ ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+ KeepListener kl = new KeepListener();
+ zf.addZFileExtension(kl);
+
+ kl.assertClear();
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+ assertEquals(1, kl.added.size());
+ kl.added.clear();
+ kl.assertClear();
+
+ zf.removeZFileExtension(kl);
+
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 2, 3 }), CompressionMethod.DEFLATE);
+ kl.assertClear();
+
+ zf.close();
+ }
+
+ @Test
+ public void actionsExecutedAtEndOfNotification() throws Exception {
+ final ZFile zf = new ZFile(new File(mTemporaryFolder.getRoot(), "a.zip"));
+
+ final IOException death[] = new IOException[1];
+
+ final KeepListener kl1 = new KeepListener();
+ zf.addZFileExtension(kl1);
+ kl1.returnRunnable = new IOExceptionRunnable() {
+ private boolean once = false;
+
+ @Override
+ public void run() {
+ if (once) {
+ return;
+ }
+
+ once = true;
+
+ try {
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 1, 2 }),
+ CompressionMethod.DEFLATE);
+ } catch (IOException e ) {
+ death[0] = e;
+ }
+ }
+ };
+
+ final KeepListener kl2 = new KeepListener();
+ zf.addZFileExtension(kl2);
+ kl2.returnRunnable = new IOExceptionRunnable() {
+ private boolean once = false;
+
+ @Override
+ public void run() {
+ if (once) {
+ return;
+ }
+
+ once = true;
+ try {
+ zf.add("bar", new ByteArrayEntrySource(new byte[] { 1, 2 }),
+ CompressionMethod.DEFLATE);
+ } catch (IOException e ) {
+ death[0] = e;
+ }
+ }
+ };
+
+ kl1.assertClear();
+ kl2.assertClear();
+
+ zf.add("xpto", new ByteArrayEntrySource(new byte[] { 1, 2 }), CompressionMethod.DEFLATE);
+
+ assertEquals(3, kl1.added.size());
+ kl1.added.clear();
+ kl1.assertClear();
+ assertEquals(3, kl2.added.size());
+ kl2.added.clear();
+ kl2.assertClear();
+
+ assertNull(death[0]);
+
+ zf.close();
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZFileTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZFileTest.java
new file mode 100644
index 0000000..492675d
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZFileTest.java
@@ -0,0 +1,916 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.android.builder.internal.packaging.zip.utils.CachedFileContents;
+import com.android.testutils.TestUtils;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Random;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+ at Ignore("Broken on java 7/8. Already fixed upstream.")
+public class ZFileTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ private File cloneRsrc(String rsrcName, String fname) throws Exception {
+ File packagingRoot = TestUtils.getRoot("packaging");
+ String rsrcPath = packagingRoot.getAbsolutePath() + "/" + rsrcName;
+ File rsrcFile = new File(rsrcPath);
+ File result = mTemporaryFolder.newFile(fname);
+ Files.copy(rsrcFile, result);
+ return result;
+ }
+
+ @Test
+ public void readNonExistingFile() throws Exception {
+ File temporaryDir = Files.createTempDir();
+ File zf = new File(temporaryDir, "a");
+ ZFile azf = new ZFile(zf);
+ azf.touch();
+ azf.close();
+ assertTrue(zf.exists());
+ }
+
+ @Test(expected = IOException.class)
+ public void readExistingEmptyFile() throws Exception {
+ File temporaryDir = Files.createTempDir();
+ File zf = new File(temporaryDir, "a");
+ Files.write(new byte[0], zf);
+ @SuppressWarnings("unused")
+ ZFile azf = new ZFile(zf);
+ }
+
+ @Test
+ public void readAlmostEmptyZip() throws Exception {
+ File zf = cloneRsrc("empty-zip.zip", "a");
+
+ ZFile azf = new ZFile(zf);
+ assertEquals(1, azf.entries().size());
+
+ StoredEntry z = azf.get("z/");
+ assertNotNull(z);
+ assertSame(StoredEntryType.DIRECTORY, z.getType());
+ }
+
+ @Test
+ public void readZipWithTwoFilesOneDirectory() throws Exception {
+ File zf = cloneRsrc("simple-zip.zip", "a");
+ ZFile azf = new ZFile(zf);
+ assertEquals(3, azf.entries().size());
+
+ StoredEntry e0 = azf.get("dir/");
+ assertNotNull(e0);
+ assertSame(StoredEntryType.DIRECTORY, e0.getType());
+
+ StoredEntry e1 = azf.get("dir/inside");
+ assertNotNull(e1);
+ assertSame(StoredEntryType.FILE, e1.getType());
+ ByteArrayOutputStream e1BytesOut = new ByteArrayOutputStream();
+ ByteStreams.copy(e1.open(), e1BytesOut);
+ byte e1Bytes[] = e1BytesOut.toByteArray();
+ String e1Txt = new String(e1Bytes, Charsets.US_ASCII);
+ assertEquals("inside", e1Txt);
+
+ StoredEntry e2 = azf.get("file.txt");
+ assertNotNull(e2);
+ assertSame(StoredEntryType.FILE, e2.getType());
+ ByteArrayOutputStream e2BytesOut = new ByteArrayOutputStream();
+ ByteStreams.copy(e2.open(), e2BytesOut);
+ byte e2Bytes[] = e2BytesOut.toByteArray();
+ String e2Txt = new String(e2Bytes, Charsets.US_ASCII);
+ assertEquals("file with more text to allow deflating to be useful", e2Txt);
+ }
+
+ @Test
+ public void readOnlyZipSupport() throws Exception {
+ File testZip = cloneRsrc("empty-zip.zip", "tz");
+
+ assertTrue(testZip.setWritable(false));
+
+ ZFile zf = new ZFile(testZip);
+ assertEquals(1, zf.entries().size());
+ }
+
+ @Test
+ public void removeFileFromZip() throws Exception {
+ File zipFile = mTemporaryFolder.newFile("test.zip");
+
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+ try {
+ ZipEntry entry = new ZipEntry("foo/");
+ entry.setMethod(ZipEntry.STORED);
+ entry.setSize(0);
+ entry.setCompressedSize(0);
+ entry.setCrc(0);
+ zos.putNextEntry(entry);
+ zos.putNextEntry(new ZipEntry("foo/bar"));
+ zos.write(new byte[]{1, 2, 3, 4});
+ zos.closeEntry();
+ } finally {
+ zos.close();
+ }
+
+ ZFile zf = new ZFile(zipFile);
+ assertEquals(2, zf.entries().size());
+ for (StoredEntry e : zf.entries()) {
+ if (e.getType() == StoredEntryType.FILE) {
+ e.delete();
+ }
+ }
+
+ zf.update();
+
+ ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
+ try {
+ ZipEntry e1 = zis.getNextEntry();
+ assertNotNull(e1);
+
+ assertEquals("foo/", e1.getName());
+
+ ZipEntry e2 = zis.getNextEntry();
+ assertNull(e2);
+ } finally {
+ zis.close();
+ }
+ }
+
+ @Test
+ public void addFileToZip() throws Exception {
+ File zipFile = mTemporaryFolder.newFile("test.zip");
+
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+ try {
+ ZipEntry fooDir = new ZipEntry("foo/");
+ fooDir.setCrc(0);
+ fooDir.setCompressedSize(0);
+ fooDir.setSize(0);
+ fooDir.setMethod(ZipEntry.STORED);
+ zos.putNextEntry(fooDir);
+ zos.closeEntry();
+ } finally {
+ zos.close();
+ }
+
+ ZFile zf = new ZFile(zipFile);
+ assertEquals(1, zf.entries().size());
+
+
+ zf.update();
+
+ ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
+ try {
+ ZipEntry e1 = zis.getNextEntry();
+ assertNotNull(e1);
+
+ assertEquals("foo/", e1.getName());
+
+ ZipEntry e2 = zis.getNextEntry();
+ assertNull(e2);
+ } finally {
+ zis.close();
+ }
+ }
+
+ @Test
+ public void createNewZip() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ ZFile zf = new ZFile(zipFile);
+ zf.add("foo", new ByteArrayEntrySource(new byte[] { 0, 1 }), CompressionMethod.DEFLATE);
+ zf.close();
+
+ ZipFile jzf = new ZipFile(zipFile);
+ try {
+ assertEquals(1, jzf.size());
+
+ ZipEntry fooEntry = jzf.getEntry("foo");
+ assertNotNull(fooEntry);
+ assertEquals("foo", fooEntry.getName());
+ assertEquals(2, fooEntry.getSize());
+
+ InputStream is = jzf.getInputStream(fooEntry);
+ assertEquals(0, is.read());
+ assertEquals(1, is.read());
+ assertEquals(-1, is.read());
+
+ is.close();
+ } finally {
+ jzf.close();
+ }
+ }
+
+ @Test
+ public void mergeZip() throws Exception {
+ File aZip = cloneRsrc("simple-zip.zip", "a.zip");
+
+ File merged = new File(mTemporaryFolder.getRoot(), "r.zip");
+ ZFile mergedZf = new ZFile(merged);
+ mergedZf.mergeFrom(new ZFile(aZip), Sets.<Pattern>newHashSet());
+ mergedZf.close();
+
+ assertEquals(3, mergedZf.entries().size());
+
+ StoredEntry e0 = mergedZf.get("dir/");
+ assertNotNull(e0);
+ assertSame(StoredEntryType.DIRECTORY, e0.getType());
+
+ StoredEntry e1 = mergedZf.get("dir/inside");
+ assertNotNull(e1);
+ assertSame(StoredEntryType.FILE, e1.getType());
+ ByteArrayOutputStream e1BytesOut = new ByteArrayOutputStream();
+ ByteStreams.copy(e1.open(), e1BytesOut);
+ byte e1Bytes[] = e1BytesOut.toByteArray();
+ String e1Txt = new String(e1Bytes, Charsets.US_ASCII);
+ assertEquals("inside", e1Txt);
+
+ StoredEntry e2 = mergedZf.get("file.txt");
+ assertNotNull(e2);
+ assertSame(StoredEntryType.FILE, e2.getType());
+ ByteArrayOutputStream e2BytesOut = new ByteArrayOutputStream();
+ ByteStreams.copy(e2.open(), e2BytesOut);
+ byte e2Bytes[] = e2BytesOut.toByteArray();
+ String e2Txt = new String(e2Bytes, Charsets.US_ASCII);
+ assertEquals("file with more text to allow deflating to be useful", e2Txt);
+
+ CachedFileContents<Object> changeDetector = new CachedFileContents<Object>(merged);
+ changeDetector.closed(null);
+
+ File bZip = cloneRsrc("simple-zip.zip", "b.zip");
+
+ mergedZf.mergeFrom(new ZFile(bZip), Sets.<Pattern>newHashSet());
+ mergedZf.close();
+
+ assertTrue(changeDetector.isValid());
+ }
+
+ @Test
+ public void replaceFileWithSmallerInMiddle() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+ try {
+ zos.putNextEntry(new ZipEntry("file1"));
+ zos.write(new byte[]{1, 2, 3, 4, 5});
+ zos.putNextEntry(new ZipEntry("file2"));
+ zos.write(new byte[]{6, 7, 8});
+ zos.putNextEntry(new ZipEntry("file3"));
+ zos.write(new byte[]{9, 0, 1, 2, 3, 4});
+ } finally {
+ zos.close();
+ }
+
+ int totalSize = (int) zipFile.length();
+
+ ZFile zf = new ZFile(zipFile);
+ assertEquals(3, zf.entries().size());
+
+ StoredEntry file2 = zf.get("file2");
+ assertNotNull(file2);
+ assertEquals(3, file2.getCentralDirectoryHeader().getUncompressedSize());
+
+ assertArrayEquals(new byte[] { 6, 7, 8 }, file2.read());
+
+ zf.add("file2", new ByteArrayEntrySource(new byte[] { 11, 12 }), CompressionMethod.DEFLATE);
+ zf.close();
+
+ int newTotalSize = (int) zipFile.length();
+ assertTrue(newTotalSize + " == " + totalSize, newTotalSize == totalSize);
+
+ file2 = zf.get("file2");
+ assertNotNull(file2);
+ assertArrayEquals(new byte[] { 11, 12, }, file2.read());
+
+ ZFile zf2 = new ZFile(zipFile);
+ file2 = zf2.get("file2");
+ assertNotNull(file2);
+ assertArrayEquals(new byte[] { 11, 12, }, file2.read());
+ }
+
+ @Test
+ public void replaceFileWithSmallerAtEnd() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+ try {
+ zos.putNextEntry(new ZipEntry("file1"));
+ zos.write(new byte[]{1, 2, 3, 4, 5});
+ zos.putNextEntry(new ZipEntry("file2"));
+ zos.write(new byte[]{6, 7, 8});
+ zos.putNextEntry(new ZipEntry("file3"));
+ zos.write(new byte[]{9, 0, 1, 2, 3, 4});
+ } finally {
+ zos.close();
+ }
+
+ int totalSize = (int) zipFile.length();
+
+ ZFile zf = new ZFile(zipFile);
+ assertEquals(3, zf.entries().size());
+
+ StoredEntry file3 = zf.get("file3");
+ assertNotNull(file3);
+ assertEquals(6, file3.getCentralDirectoryHeader().getUncompressedSize());
+
+ assertArrayEquals(new byte[] { 9, 0, 1, 2, 3, 4 }, file3.read());
+
+ zf.add("file3", new ByteArrayEntrySource(new byte[] { 11, 12 }), CompressionMethod.DEFLATE);
+ zf.close();
+
+ int newTotalSize = (int) zipFile.length();
+ assertTrue(newTotalSize + " < " + totalSize, newTotalSize < totalSize);
+
+ file3 = zf.get("file3");
+ assertNotNull(file3);
+ assertArrayEquals(new byte[] { 11, 12, }, file3.read());
+
+ ZFile zf2 = new ZFile(zipFile);
+ file3 = zf2.get("file3");
+ assertNotNull(file3);
+ assertArrayEquals(new byte[] { 11, 12, }, file3.read());
+ }
+
+ @Test
+ public void replaceFileWithBiggerAtBegin() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+ try {
+ zos.putNextEntry(new ZipEntry("file1"));
+ zos.write(new byte[]{1, 2, 3, 4, 5});
+ zos.putNextEntry(new ZipEntry("file2"));
+ zos.write(new byte[]{6, 7, 8});
+ zos.putNextEntry(new ZipEntry("file3"));
+ zos.write(new byte[]{9, 0, 1, 2, 3, 4});
+ } finally {
+ zos.close();
+ }
+
+ int totalSize = (int) zipFile.length();
+
+ ZFile zf = new ZFile(zipFile);
+ assertEquals(3, zf.entries().size());
+
+ StoredEntry file1 = zf.get("file1");
+ assertNotNull(file1);
+ assertEquals(5, file1.getCentralDirectoryHeader().getUncompressedSize());
+
+ assertArrayEquals(new byte[] { 1, 2, 3, 4, 5 }, file1.read());
+
+ /*
+ * Need some data because java zip API uses data descriptors which we don't and makes the
+ * entries bigger (meaning just adding a couple of bytes would still fit in the same
+ * place).
+ */
+ byte[] newData = new byte[100];
+ Random r = new Random();
+ r.nextBytes(newData);
+
+ zf.add("file1", new ByteArrayEntrySource(newData), CompressionMethod.DEFLATE);
+ zf.close();
+
+ int newTotalSize = (int) zipFile.length();
+ assertTrue(newTotalSize + " > " + totalSize, newTotalSize > totalSize);
+
+ file1 = zf.get("file1");
+ assertNotNull(file1);
+ assertArrayEquals(newData, file1.read());
+
+ ZFile zf2 = new ZFile(zipFile);
+ file1 = zf2.get("file1");
+ assertNotNull(file1);
+ assertArrayEquals(newData, file1.read());
+ }
+
+ @Test
+ public void replaceFileWithBiggerAtEnd() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip");
+
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+ try {
+ zos.putNextEntry(new ZipEntry("file1"));
+ zos.write(new byte[]{1, 2, 3, 4, 5});
+ zos.putNextEntry(new ZipEntry("file2"));
+ zos.write(new byte[]{6, 7, 8});
+ zos.putNextEntry(new ZipEntry("file3"));
+ zos.write(new byte[]{9, 0, 1, 2, 3, 4});
+ } finally {
+ zos.close();
+ }
+
+ int totalSize = (int) zipFile.length();
+
+ ZFile zf = new ZFile(zipFile);
+ assertEquals(3, zf.entries().size());
+
+ StoredEntry file3 = zf.get("file3");
+ assertNotNull(file3);
+ assertEquals(6, file3.getCentralDirectoryHeader().getUncompressedSize());
+
+ assertArrayEquals(new byte[] { 9, 0, 1, 2, 3, 4 }, file3.read());
+
+ /*
+ * Need some data because java zip API uses data descriptors which we don't and makes the
+ * entries bigger (meaning just adding a couple of bytes would still fit in the same
+ * place).
+ */
+ byte[] newData = new byte[100];
+ Random r = new Random();
+ r.nextBytes(newData);
+
+ zf.add("file3", new ByteArrayEntrySource(newData), CompressionMethod.DEFLATE);
+ zf.close();
+
+ int newTotalSize = (int) zipFile.length();
+ assertTrue(newTotalSize + " > " + totalSize, newTotalSize > totalSize);
+
+ file3 = zf.get("file3");
+ assertNotNull(file3);
+ assertArrayEquals(newData, file3.read());
+
+ ZFile zf2 = new ZFile(zipFile);
+ file3 = zf2.get("file3");
+ assertNotNull(file3);
+ assertArrayEquals(newData, file3.read());
+ }
+
+ @Test
+ public void ignoredFilesDuringMerge() throws Exception {
+ File zip1 = mTemporaryFolder.newFile("t1.zip");
+ ZipOutputStream zos1 = new ZipOutputStream(new FileOutputStream(zip1));
+ try {
+ zos1.putNextEntry(new ZipEntry("only_in_1"));
+ zos1.write(new byte[] { 1, 2 });
+ zos1.putNextEntry(new ZipEntry("overridden_by_2"));
+ zos1.write(new byte[] { 2, 3 });
+ zos1.putNextEntry(new ZipEntry("not_overridden_by_2"));
+ zos1.write(new byte[] { 3, 4 });
+ } finally {
+ zos1.close();
+ }
+
+ File zip2 = mTemporaryFolder.newFile("t2.zip");
+ ZipOutputStream zos2 = new ZipOutputStream(new FileOutputStream(zip2));
+ try {
+ zos2.putNextEntry(new ZipEntry("only_in_2"));
+ zos2.write(new byte[] { 4, 5 });
+ zos2.putNextEntry(new ZipEntry("overridden_by_2"));
+ zos2.write(new byte[] { 5, 6 });
+ zos2.putNextEntry(new ZipEntry("not_overridden_by_2"));
+ zos2.write(new byte[] { 6, 7 });
+ zos2.putNextEntry(new ZipEntry("ignored_in_2"));
+ zos2.write(new byte[] { 7, 8 });
+ } finally {
+ zos2.close();
+ }
+
+ Set<Pattern> ignoreFiles = Sets.newHashSet();
+ ignoreFiles.add(Pattern.compile("not.*"));
+ ignoreFiles.add(Pattern.compile(".*gnored.*"));
+
+ ZFile zf1 = new ZFile(zip1);
+ ZFile zf2 = new ZFile(zip2);
+ zf1.mergeFrom(zf2, ignoreFiles);
+
+ StoredEntry only_in_1 = zf1.get("only_in_1");
+ assertNotNull(only_in_1);
+ assertArrayEquals(new byte[] { 1, 2 }, only_in_1.read());
+
+ StoredEntry overridden_by_2 = zf1.get("overridden_by_2");
+ assertNotNull(overridden_by_2);
+ assertArrayEquals(new byte[] { 5, 6 }, overridden_by_2.read());
+
+ StoredEntry not_overridden_by_2 = zf1.get("not_overridden_by_2");
+ assertNotNull(not_overridden_by_2);
+ assertArrayEquals(new byte[] { 3, 4 }, not_overridden_by_2.read());
+
+ StoredEntry only_in_2 = zf1.get("only_in_2");
+ assertNotNull(only_in_2);
+ assertArrayEquals(new byte[] { 4, 5 }, only_in_2.read());
+
+ StoredEntry ignored_in_2 = zf1.get("ignored_in_2");
+ assertNull(ignored_in_2);
+ }
+
+ @Test
+ public void addingFileDoesNotAddDirectoriesAutomatically() throws Exception {
+ File zip = new File(mTemporaryFolder.getRoot(), "z.zip");
+ ZFile zf = new ZFile(zip);
+ zf.add("a/b/c", new ByteArrayEntrySource(new byte[] { 1, 2, 3 }),
+ CompressionMethod.DEFLATE);
+ zf.update();
+ assertEquals(1, zf.entries().size());
+
+ StoredEntry c = zf.get("a/b/c");
+ assertNotNull(c);
+ assertEquals(3, c.read().length);
+
+ zf.close();
+ }
+
+ @Test
+ public void zipFileWithEocdSignatureInComment() throws Exception {
+ File zip = mTemporaryFolder.newFile("f.zip");
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip));
+ try {
+ zos.putNextEntry(new ZipEntry("a"));
+ zos.write(new byte[] { 1, 2, 3 });
+ zos.setComment("Random comment with XXXX weird characters. There must be enough "
+ + "characters to survive skipping back the EOCD size.");
+ } finally {
+ zos.close();
+ }
+
+ byte zipBytes[] = Files.toByteArray(zip);
+ boolean didX4 = false;
+ for (int i = 0; i < zipBytes.length - 3; i++) {
+ boolean x4 = true;
+ for (int j = 0; j < 4; j++) {
+ if (zipBytes[i + j] != 'X') {
+ x4 = false;
+ break;
+ }
+ }
+
+ if (x4) {
+ zipBytes[i] = (byte) 0x50;
+ zipBytes[i + 1] = (byte) 0x4b;
+ zipBytes[i + 2] = (byte) 0x05;
+ zipBytes[i + 3] = (byte) 0x06;
+ didX4 = true;
+ break;
+ }
+ }
+
+ assertTrue(didX4);
+
+ Files.write(zipBytes, zip);
+
+ ZFile zf = new ZFile(zip);
+ assertEquals(1, zf.entries().size());
+ StoredEntry a = zf.get("a");
+ assertNotNull(a);
+ assertArrayEquals(new byte[] { 1, 2, 3 }, a.read());
+
+ }
+
+ @Test
+ public void addFileRecursively() throws Exception {
+ File tdir = mTemporaryFolder.newFolder();
+ File tfile = new File(tdir, "blah-blah");
+ Files.write("blah", tfile, Charsets.US_ASCII);
+
+ File zip = new File(tdir, "f.zip");
+ ZFile zf = new ZFile(zip);
+ zf.addAllRecursively(tfile, new Function<File, CompressionMethod>() {
+ @Override
+ public CompressionMethod apply(File input) {
+ return CompressionMethod.DEFLATE;
+ }
+ });
+
+ StoredEntry blahEntry = zf.get("blah-blah");
+ assertNotNull(blahEntry);
+ String contents = new String(blahEntry.read(), Charsets.US_ASCII);
+ assertEquals("blah", contents);
+ zf.close();
+ }
+
+ @Test
+ public void addDirectoryRecursively() throws Exception {
+ File tdir = mTemporaryFolder.newFolder();
+
+ String boom = Strings.repeat("BOOM!", 100);
+ String kaboom = Strings.repeat("KABOOM!", 100);
+
+ Files.write(boom, new File(tdir, "danger"), Charsets.US_ASCII);
+ Files.write(kaboom, new File(tdir, "do not touch"), Charsets.US_ASCII);
+ File safeDir = new File(tdir, "safe");
+ assertTrue(safeDir.mkdir());
+
+ String iLoveChocolate = Strings.repeat("I love chocolate! ", 200);
+ String iLoveOrange = Strings.repeat("I love orange! ", 50);
+ String loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean vitae "
+ + "turpis quis justo scelerisque vulputate in et magna. Suspendisse eleifend "
+ + "ultricies nisi, placerat consequat risus accumsan et. Pellentesque habitant "
+ + "morbi tristique senectus et netus et malesuada fames ac turpis egestas. "
+ + "Integer vitae leo purus. Nulla facilisi. Duis ligula libero, lacinia a "
+ + "malesuada a, viverra tempor sapien. Donec eget consequat sapien, ultrices"
+ + "interdum diam. Maecenas ipsum erat, suscipit at iaculis a, mollis nec risus. "
+ + "Quisque tristique ac velit sed auctor. Nulla lacus diam, tristique id sem non, "
+ + "pellentesque commodo mauris.";
+
+ Files.write(iLoveChocolate, new File(safeDir, "eat.sweet"), Charsets.US_ASCII);
+ Files.write(iLoveOrange, new File(safeDir, "eat.fruit"), Charsets.US_ASCII);
+ Files.write(loremIpsum, new File(safeDir, "bedtime.reading.txt"), Charsets.US_ASCII);
+
+ File zip = new File(tdir, "f.zip");
+ ZFile zf = new ZFile(zip);
+ zf.addAllRecursively(tdir, new Function<File, CompressionMethod>() {
+ @Override
+ public CompressionMethod apply(File input) {
+ if (input.getName().startsWith("eat.")) {
+ return CompressionMethod.STORE;
+ } else {
+ return CompressionMethod.DEFLATE;
+ }
+ }
+ });
+
+ assertEquals(6, zf.entries().size());
+
+ StoredEntry boomEntry = zf.get("danger");
+ assertNotNull(boomEntry);
+ assertEquals(CompressionMethod.DEFLATE, boomEntry.getCentralDirectoryHeader().getMethod());
+ assertEquals(boom, new String(boomEntry.read(), Charsets.US_ASCII));
+
+ StoredEntry kaboomEntry = zf.get("do not touch");
+ assertNotNull(kaboomEntry);
+ assertEquals(CompressionMethod.DEFLATE,
+ kaboomEntry.getCentralDirectoryHeader().getMethod());
+ assertEquals(kaboom, new String(kaboomEntry.read(), Charsets.US_ASCII));
+
+ StoredEntry safeEntry = zf.get("safe/");
+ assertNotNull(safeEntry);
+ assertEquals(0, safeEntry.read().length);
+
+ StoredEntry chocolateEntry = zf.get("safe/eat.sweet");
+ assertNotNull(chocolateEntry);
+ assertEquals(CompressionMethod.STORE,
+ chocolateEntry.getCentralDirectoryHeader().getMethod());
+ assertEquals(iLoveChocolate, new String(chocolateEntry.read(), Charsets.US_ASCII));
+
+ StoredEntry orangeEntry = zf.get("safe/eat.fruit");
+ assertNotNull(orangeEntry);
+ assertEquals(CompressionMethod.STORE,
+ orangeEntry.getCentralDirectoryHeader().getMethod());
+ assertEquals(iLoveOrange, new String(orangeEntry.read(), Charsets.US_ASCII));
+
+ StoredEntry loremIpsumEntry = zf.get("safe/bedtime.reading.txt");
+ assertNotNull(loremIpsumEntry);
+ assertEquals(CompressionMethod.DEFLATE,
+ loremIpsumEntry.getCentralDirectoryHeader().getMethod());
+ assertEquals(loremIpsum, new String(loremIpsumEntry.read(), Charsets.US_ASCII));
+
+ zf.close();
+ }
+
+ @Test
+ public void extraDirectoryOffsetEmptyFile() throws Exception {
+ File zipNoOffsetFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+ File zipWithOffsetFile = new File(mTemporaryFolder.getRoot(), "b.zip");
+
+ ZFile zipNoOffset = new ZFile(zipNoOffsetFile);
+ ZFile zipWithOffset = new ZFile(zipWithOffsetFile);
+ zipWithOffset.setExtraDirectoryOffset(31);
+
+ zipNoOffset.close();
+ zipWithOffset.close();
+
+ long zipNoOffsetSize = zipNoOffsetFile.length();
+ long zipWithOffsetSize = zipWithOffsetFile.length();
+
+ assertEquals(zipNoOffsetSize + 31, zipWithOffsetSize);
+
+ /*
+ * EOCD with no comment has 22 bytes.
+ */
+ assertEquals(0, zipNoOffset.getCentralDirectoryOffset());
+ assertEquals(0, zipNoOffset.getCentralDirectorySize());
+ assertEquals(0, zipNoOffset.getEocdOffset());
+ assertEquals(22, zipNoOffset.getEocdSize());
+ assertEquals(31, zipWithOffset.getCentralDirectoryOffset());
+ assertEquals(0, zipWithOffset.getCentralDirectorySize());
+ assertEquals(31, zipWithOffset.getEocdOffset());
+ assertEquals(22, zipWithOffset.getEocdSize());
+
+ /*
+ * The EOCDs should not differ up until the end of the Central Directory size and should
+ * not differ after the offset
+ */
+ int p1Start = 0;
+ int p1Size = Eocd.F_CD_SIZE.endOffset();
+ int p2Start = Eocd.F_CD_OFFSET.endOffset();
+ int p2Size = (int) zipNoOffsetSize - p2Start;
+
+ byte[] noOffsetData1 = FileUtils.readSegment(zipNoOffsetFile, p1Start, p1Size);
+ byte[] noOffsetData2 = FileUtils.readSegment(zipNoOffsetFile, p2Start, p2Size);
+ byte[] withOffsetData1 = FileUtils.readSegment(zipWithOffsetFile, 31, p1Size);
+ byte[] withOffsetData2 = FileUtils.readSegment(zipWithOffsetFile, 31 + p2Start, p2Size);
+
+ assertArrayEquals(noOffsetData1, withOffsetData1);
+ assertArrayEquals(noOffsetData2, withOffsetData2);
+
+ ZFile readWithOffset = new ZFile(zipWithOffsetFile);
+ assertEquals(0, readWithOffset.entries().size());
+ }
+
+ @Test
+ public void extraDirectoryOffsetNonEmptyFile() throws Exception {
+ File zipNoOffsetFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+ File zipWithOffsetFile = new File(mTemporaryFolder.getRoot(), "b.zip");
+
+ ZFile zipNoOffset = new ZFile(zipNoOffsetFile);
+ ZFile zipWithOffset = new ZFile(zipWithOffsetFile);
+ zipWithOffset.setExtraDirectoryOffset(37);
+
+ zipNoOffset.add("x", new ByteArrayEntrySource(new byte[] { 1, 2 }),
+ CompressionMethod.DEFLATE);
+ zipWithOffset.add("x", new ByteArrayEntrySource(new byte[] { 1, 2 }),
+ CompressionMethod.DEFLATE);
+
+ zipNoOffset.close();
+ zipWithOffset.close();
+
+ long zipNoOffsetSize = zipNoOffsetFile.length();
+ long zipWithOffsetSize = zipWithOffsetFile.length();
+
+ assertEquals(zipNoOffsetSize + 37, zipWithOffsetSize);
+
+ /*
+ * Local file header has 30 bytes + name.
+ * Central directory entry has 46 bytes + name
+ * EOCD with no comment has 22 bytes.
+ */
+ assertEquals(30 + 1 + 2, zipNoOffset.getCentralDirectoryOffset());
+ int cdSize = (int) zipNoOffset.getCentralDirectorySize();
+ assertEquals(46 + 1, cdSize);
+ assertEquals(30 + 1 + 2 + cdSize, zipNoOffset.getEocdOffset());
+ assertEquals(22, zipNoOffset.getEocdSize());
+ assertEquals(30 + 1 + 2 + 37, zipWithOffset.getCentralDirectoryOffset());
+ assertEquals(cdSize, zipWithOffset.getCentralDirectorySize());
+ assertEquals(30 + 1 + 2 + 37 + cdSize, zipWithOffset.getEocdOffset());
+ assertEquals(22, zipWithOffset.getEocdSize());
+
+ /*
+ * The files should be equal: until the end of the first entry, from the beginning of the
+ * central directory until the offset field in the EOCD and after the offset field.
+ */
+ int p1Start = 0;
+ int p1Size = 30 + 1 + 2;
+ int p2Start = 30 + 1 + 2;
+ int p2Size = cdSize + Eocd.F_CD_SIZE.endOffset();
+ int p3Start = p2Start + cdSize + Eocd.F_CD_OFFSET.endOffset();
+ int p3Size = 22 - Eocd.F_CD_OFFSET.endOffset();
+
+ byte[] noOffsetData1 = FileUtils.readSegment(zipNoOffsetFile, p1Start, p1Size);
+ byte[] noOffsetData2 = FileUtils.readSegment(zipNoOffsetFile, p2Start, p2Size);
+ byte[] noOffsetData3 = FileUtils.readSegment(zipNoOffsetFile, p3Start, p3Size);
+ byte[] withOffsetData1 = FileUtils.readSegment(zipWithOffsetFile, p1Start, p1Size);
+ byte[] withOffsetData2 = FileUtils.readSegment(zipWithOffsetFile, 37 + p2Start, p2Size);
+ byte[] withOffsetData3 = FileUtils.readSegment(zipWithOffsetFile, 37 + p3Start, p3Size);
+
+ assertArrayEquals(noOffsetData1, withOffsetData1);
+ assertArrayEquals(noOffsetData2, withOffsetData2);
+ assertArrayEquals(noOffsetData3, withOffsetData3);
+
+ ZFile readWithOffset = new ZFile(zipWithOffsetFile);
+ assertEquals(1, readWithOffset.entries().size());
+ }
+
+ @Test
+ public void changeExtraDirectoryOffset() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+
+ ZFile zip = new ZFile(zipFile);
+ zip.add("x", new ByteArrayEntrySource(new byte[] { 1, 2 }),
+ CompressionMethod.DEFLATE);
+ zip.close();
+
+ long noOffsetSize = zipFile.length();
+
+ zip.setExtraDirectoryOffset(177);
+ zip.close();
+
+ long withOffsetSize = zipFile.length();
+
+ assertEquals(noOffsetSize + 177, withOffsetSize);
+ }
+
+ @Test
+ public void computeOffsetWhenReadingEmptyFile() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+
+ ZFile zip = new ZFile(zipFile);
+ zip.setExtraDirectoryOffset(18);
+ zip.close();
+
+ zip = new ZFile(zipFile);
+ assertEquals(18, zip.getExtraDirectoryOffset());
+
+ zip.close();
+ }
+
+ @Test
+ public void computeOffsetWhenReadingNonEmptyFile() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+
+ ZFile zip = new ZFile(zipFile);
+ zip.setExtraDirectoryOffset(287);
+ zip.add("x", new ByteArrayEntrySource(new byte[] { 1, 2 }),
+ CompressionMethod.DEFLATE);
+ zip.close();
+
+ zip = new ZFile(zipFile);
+ assertEquals(287, zip.getExtraDirectoryOffset());
+
+ zip.close();
+ }
+
+ @Test
+ public void obtainingCDAndEocdWhenEntriesWrittenOnEmptyZip() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+
+ final byte[][] cd = new byte[1][];
+ final byte[][] eocd = new byte[1][];
+
+ final ZFile zip = new ZFile(zipFile);
+ zip.addZFileExtension(new ZFileExtension() {
+ @Override
+ public void entriesWritten() throws IOException {
+ cd[0] = zip.getCentralDirectoryBytes();
+ eocd[0] = zip.getEocdBytes();
+ }
+ });
+
+ zip.close();
+
+ assertNotNull(cd[0]);
+ assertEquals(0, cd[0].length);
+ assertNotNull(eocd[0]);
+ assertEquals(22, eocd[0].length);
+ }
+
+ @Test
+ public void obtainingCDAndEocdWhenEntriesWrittenOnNonEmptyZip() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+
+ final byte[][] cd = new byte[1][];
+ final byte[][] eocd = new byte[1][];
+
+ final ZFile zip = new ZFile(zipFile);
+ zip.add("foo", new ByteArrayEntrySource(new byte[0]), CompressionMethod.DEFLATE);
+ zip.addZFileExtension(new ZFileExtension() {
+ @Override
+ public void entriesWritten() throws IOException {
+ cd[0] = zip.getCentralDirectoryBytes();
+ eocd[0] = zip.getEocdBytes();
+ }
+ });
+
+ zip.close();
+
+ /*
+ * Central directory entry has 46 bytes + name
+ * EOCD with no comment has 22 bytes.
+ */
+ assertNotNull(cd[0]);
+ assertEquals(46 + 3, cd[0].length);
+ assertNotNull(eocd[0]);
+ assertEquals(22, eocd[0].length);
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZipToolsTestCase.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZipToolsTestCase.java
new file mode 100644
index 0000000..6dd94bc
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/ZipToolsTestCase.java
@@ -0,0 +1,183 @@
+/*
+ * 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.builder.internal.packaging.zip;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.testutils.TestUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class ZipToolsTestCase {
+ @Nullable
+ private String mZipFile;
+
+ @Nullable
+ private List<String> mUnzipCommand;
+
+ @Nullable
+ private String mUnzipLineRegex;
+
+ private int mUnzipRegexNameGroup;
+
+ private int mUnzipRegexSizeGroup;
+
+ private boolean mToolStoresDirectories;
+
+ @Rule
+ @NonNull
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ protected void configure(@NonNull String zipFile, @NonNull String unzipCommand[],
+ @NonNull String unzipLineRegex, int nameGroup, int sizeGroup,
+ boolean toolStoresDirectories) {
+ mZipFile = zipFile;
+ mUnzipCommand = Arrays.asList(unzipCommand);
+ mUnzipLineRegex = unzipLineRegex;
+ mUnzipRegexNameGroup = nameGroup;
+ mUnzipRegexSizeGroup = sizeGroup;
+ mToolStoresDirectories = toolStoresDirectories;
+ }
+
+ private File rsrcFile(@NonNull String name) {
+ File packagingRoot = TestUtils.getRoot("packaging");
+ String rsrcPath = packagingRoot.getAbsolutePath() + "/" + name;
+ File rsrcFile = new File(rsrcPath);
+ return rsrcFile;
+ }
+
+ private File cloneZipFile() throws Exception {
+ File zfile = mTemporaryFolder.newFile("file.zip");
+ Files.copy(rsrcFile(mZipFile), zfile);
+ return zfile;
+ }
+
+ private void assertFileInZip(@NonNull ZFile zfile, @NonNull String name) throws Exception {
+ StoredEntry root = zfile.get(name);
+ assertNotNull(root);
+
+ InputStream is = root.open();
+ byte[] inZipData = ByteStreams.toByteArray(is);
+ is.close();
+
+ byte[] inFileData = Files.toByteArray(rsrcFile(name));
+ assertArrayEquals(inFileData, inZipData);
+ }
+
+ @Test
+ public void zfileReadsZipFile() throws Exception {
+ ZFile zf = new ZFile(cloneZipFile());
+
+ if (mToolStoresDirectories) {
+ assertEquals(6, zf.entries().size());
+ } else {
+ assertEquals(4, zf.entries().size());
+ }
+
+ assertFileInZip(zf, "root");
+ assertFileInZip(zf, "images/lena.png");
+ assertFileInZip(zf, "text-files/rfc2460.txt");
+ assertFileInZip(zf, "text-files/wikipedia.html");
+ }
+
+ @Test
+ public void toolReadsZfFile() throws Exception {
+ testReadZFile(false);
+ }
+
+ @Test
+ public void toolReadsAlignedZfFile() throws Exception {
+ testReadZFile(true);
+ }
+
+ private void testReadZFile(boolean align) throws Exception {
+ String unzipcmd = mUnzipCommand.get(0);
+ Assume.assumeTrue(new File(unzipcmd).canExecute());
+
+ File zfile = new File (mTemporaryFolder.getRoot(), "zfile.zip");
+ ZFile zf = new ZFile(zfile);
+
+ if (align) {
+ zf.getAlignmentRules().add(new AlignmentRule(Pattern.compile(".*"), 500));
+ }
+
+ zf.add("root", new FileEntrySource(rsrcFile("root")), CompressionMethod.DEFLATE);
+ zf.add("images/", new ByteArrayEntrySource(new byte[0]), CompressionMethod.DEFLATE);
+ zf.add("images/lena.png", new FileEntrySource(rsrcFile("images/lena.png")),
+ CompressionMethod.DEFLATE);
+ zf.add("text-files/", new ByteArrayEntrySource(new byte[0]), CompressionMethod.DEFLATE);
+ zf.add("text-files/rfc2460.txt", new FileEntrySource(rsrcFile("text-files/rfc2460.txt")),
+ CompressionMethod.DEFLATE);
+ zf.add("text-files/wikipedia.html",
+ new FileEntrySource(rsrcFile("text-files/wikipedia.html")),
+ CompressionMethod.DEFLATE);
+ zf.close();
+
+ List<String> command = Lists.newArrayList(mUnzipCommand);
+ command.add(zfile.getAbsolutePath());
+ ProcessBuilder pb = new ProcessBuilder(command);
+ Process proc = pb.start();
+ InputStream is = proc.getInputStream();
+ byte output[] = ByteStreams.toByteArray(is);
+ String text = new String(output, Charsets.US_ASCII);
+ String lines[] = text.split("\n");
+ Map<String, Integer> sizes = Maps.newHashMap();
+ for (String l : lines) {
+ Matcher m = Pattern.compile(mUnzipLineRegex).matcher(l);
+ if (m.matches()) {
+ String sizeTxt = m.group(mUnzipRegexSizeGroup);
+ int size = Integer.parseInt(sizeTxt);
+ String name = m.group(mUnzipRegexNameGroup);
+ sizes.put(name, size);
+ }
+ }
+
+ assertEquals(6, sizes.size());
+ assertTrue(sizes.containsKey("images/"));
+ assertEquals(0, sizes.get("images/").intValue());
+ assertTrue(sizes.containsKey("text-files/"));
+ assertEquals(0, sizes.get("text-files/").intValue());
+ assertTrue(sizes.containsKey("root"));
+ assertEquals(rsrcFile("root").length(), (long) sizes.get("root"));
+ assertEquals(rsrcFile("images/lena.png").length(), (long) sizes.get("images/lena.png"));
+ assertEquals(rsrcFile("text-files/rfc2460.txt").length(), (long) sizes.get(
+ "text-files/rfc2460.txt"));
+ assertEquals(rsrcFile("text-files/wikipedia.html").length(), (long) sizes.get(
+ "text-files/wikipedia.html"));
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/utils/CachedFileContentsTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/utils/CachedFileContentsTest.java
new file mode 100644
index 0000000..3d63125
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/utils/CachedFileContentsTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.builder.internal.packaging.zip.utils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+
+public class CachedFileContentsTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ private static void waitFilesystemTime() throws Exception {
+ /*
+ * How much time to wait until we are sure that the file system will update the last
+ * modified timestamp of a file. This is usually related to the accuracy of last timestamps.
+ * In modern windows systems 100ms be more than enough (NTFS has 100us accuracy --
+ * see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx).
+ * In linux it will depend on the filesystem. ext4 has 1ns accuracy (if inodes are 256 byte
+ * or larger), but ext3 has 1 second.
+ */
+ Thread.sleep(2000);
+ }
+
+ @Test
+ public void createFileAndCheckWithNoChanges() throws Exception {
+ File f = mTemporaryFolder.newFile("test");
+ Files.write("abc", f, Charsets.US_ASCII);
+
+ Object cache = new Object();
+
+ CachedFileContents<Object> cachedFile = new CachedFileContents<Object>(f);
+ cachedFile.closed(cache);
+
+ waitFilesystemTime();
+
+ assertTrue(cachedFile.isValid());
+ assertSame(cache, cachedFile.getCache());
+ }
+
+ @Test
+ public void createFileAndCheckChanges() throws Exception {
+ File f = mTemporaryFolder.newFile("test");
+ Files.write("abc", f, Charsets.US_ASCII);
+
+ Object cache = new Object();
+
+ CachedFileContents<Object> cachedFile = new CachedFileContents<Object>(f);
+ cachedFile.closed(cache);
+
+ waitFilesystemTime();
+
+ Files.write("def", f, Charsets.US_ASCII);
+
+ assertFalse(cachedFile.isValid());
+ assertNull(cachedFile.getCache());
+ }
+
+ @Test
+ public void createFileUpdateAndCheckChanges() throws Exception {
+ File f = mTemporaryFolder.newFile("test");
+ Files.write("abc", f, Charsets.US_ASCII);
+
+ Object cache = new Object();
+
+ CachedFileContents<Object> cachedFile = new CachedFileContents<Object>(f);
+ cachedFile.closed(cache);
+
+ waitFilesystemTime();
+
+ Files.write("def", f, Charsets.US_ASCII);
+ cachedFile.closed(cache);
+
+ assertTrue(cachedFile.isValid());
+ assertSame(cache, cachedFile.getCache());
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/utils/LittleEndianUtilsTest.java b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/utils/LittleEndianUtilsTest.java
new file mode 100644
index 0000000..6dbe502
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/packaging/zip/utils/LittleEndianUtilsTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.builder.internal.packaging.zip.utils;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+import com.google.common.io.ByteSource;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Random;
+
+public class LittleEndianUtilsTest {
+ @Test
+ public void read2Le() throws Exception {
+ assertEquals(0x0102, LittleEndianUtils.readUnsigned2Le(ByteSource.wrap(
+ new byte[] { 2, 1 })));
+ assertEquals(0xfedc, LittleEndianUtils.readUnsigned2Le(ByteSource.wrap(
+ new byte[] { (byte) 0xdc, (byte) 0xfe })));
+ }
+
+ @Test
+ public void write2Le() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ LittleEndianUtils.writeUnsigned2Le(out, 0x0102);
+ assertArrayEquals(new byte[] { 2, 1 }, out.toByteArray());
+
+ out = new ByteArrayOutputStream();
+ LittleEndianUtils.writeUnsigned2Le(out, 0xfedc);
+ assertArrayEquals(new byte[] { (byte) 0xdc, (byte) 0xfe }, out.toByteArray());
+ }
+
+ @Test
+ public void readWrite2Le() throws Exception {
+ Random r = new Random();
+
+ int range = 0x0000ffff;
+
+ int[] data = new int[1000];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = r.nextInt(range);
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (int d : data) {
+ LittleEndianUtils.writeUnsigned2Le(out, d);
+ }
+
+ ByteSource in = ByteSource.wrap(out.toByteArray());
+ for (int i = 0; i < data.length; i++) {
+ assertEquals(data[i], LittleEndianUtils.readUnsigned2Le(in.slice(i * 2, 2)));
+ }
+ }
+
+ @Test
+ public void read4Le() throws Exception {
+ assertEquals(0x01020304, LittleEndianUtils.readUnsigned4Le(ByteSource.wrap(
+ new byte[] { 4, 3, 2, 1 })));
+ assertEquals(0xfedcba98L, LittleEndianUtils.readUnsigned4Le(ByteSource.wrap(
+ new byte[] { (byte) 0x98, (byte) 0xba, (byte) 0xdc, (byte) 0xfe })));
+ }
+
+ @Test
+ public void write4Le() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ LittleEndianUtils.writeUnsigned4Le(out, 0x01020304);
+ assertArrayEquals(new byte[] { 4, 3, 2, 1 }, out.toByteArray());
+
+ out = new ByteArrayOutputStream();
+ LittleEndianUtils.writeUnsigned4Le(out, 0xfedcba98L);
+ assertArrayEquals(new byte[] { (byte) 0x98, (byte) 0xba, (byte) 0xdc, (byte) 0xfe },
+ out.toByteArray());
+ }
+
+ @Test
+ public void readWrite4Le() throws Exception {
+ Random r = new Random();
+
+ long[] data = new long[1000];
+ for (int i = 0; i < data.length; i++) {
+ do {
+ data[i] = r.nextInt() - (long) Integer.MIN_VALUE;
+ } while (data[i] < 0);
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (long d : data) {
+ LittleEndianUtils.writeUnsigned4Le(out, d);
+ }
+
+ ByteSource in = ByteSource.wrap(out.toByteArray());
+ for (int i = 0; i < data.length; i++) {
+ assertEquals(data[i], LittleEndianUtils.readUnsigned4Le(in.slice(i * 4, 4)));
+ }
+ }
+}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/png/ByteUtils.java b/build-system/builder/src/test/java/com/android/builder/png/ByteUtils.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/png/ByteUtils.java
rename to build-system/builder/src/test/java/com/android/builder/png/ByteUtils.java
diff --git a/base/build-system/builder/src/test/java/com/android/builder/png/Chunk.java b/build-system/builder/src/test/java/com/android/builder/png/Chunk.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/png/Chunk.java
rename to build-system/builder/src/test/java/com/android/builder/png/Chunk.java
diff --git a/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTest.java b/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTest.java
new file mode 100644
index 0000000..78c2190
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.builder.png;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.internal.AaptCruncher;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.internal.PngException;
+import com.android.ide.common.process.DefaultProcessExecutor;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+import com.google.common.collect.Maps;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.zip.DataFormatException;
+
+ at RunWith(Parameterized.class)
+public class NinePatchAaptProcessorTest {
+
+ private static Map<File, File> mSourceAndCrunchedFiles;
+
+ private static AtomicLong sClassStartTime = new AtomicLong();
+ private static final AtomicInteger sCruncherKey = new AtomicInteger();
+ private static final PngCruncher sCruncher = getCruncher();
+
+ private final File mFile;
+
+ public NinePatchAaptProcessorTest(File file, String testName) {
+ mFile = file;
+ }
+
+ @BeforeClass
+ public static void setup() {
+ mSourceAndCrunchedFiles = Maps.newHashMap();
+ sCruncherKey.set(sCruncher.start());
+ }
+
+ @Test
+ public void run() throws PngException, IOException {
+ File outFile = NinePatchAaptProcessorTestUtils.crunchFile(
+ sCruncherKey.get(), mFile, sCruncher);
+ mSourceAndCrunchedFiles.put(mFile, outFile);
+ }
+
+
+ @AfterClass
+ public static void tearDownAndCheck() throws IOException, DataFormatException {
+ NinePatchAaptProcessorTestUtils.tearDownAndCheck(
+ sCruncherKey.get(), mSourceAndCrunchedFiles, sCruncher, sClassStartTime);
+ mSourceAndCrunchedFiles = null;
+ }
+
+ @NonNull
+ private static PngCruncher getCruncher() {
+ ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
+ ProcessExecutor processExecutor = new DefaultProcessExecutor(logger);
+ ProcessOutputHandler processOutputHandler = new LoggedProcessOutputHandler(logger);
+ File aapt = NinePatchAaptProcessorTestUtils.getAapt(Revision.parseRevision("22.0.1"));
+ return new AaptCruncher(aapt.getAbsolutePath(), processExecutor, processOutputHandler);
+ }
+
+ @Parameterized.Parameters(name = "{1}")
+ public static Collection<Object[]> getNinePatches() {
+ Collection<Object[]> params = NinePatchAaptProcessorTestUtils.getNinePatches();
+ sClassStartTime.set(System.currentTimeMillis());
+ return params;
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTestUtils.java b/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTestUtils.java
new file mode 100644
index 0000000..2706ad6
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/NinePatchAaptProcessorTestUtils.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.png;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.internal.PngException;
+import com.android.repository.Revision;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.testutils.TestUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.junit.Assert;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.zip.DataFormatException;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Utilities common to tests for both the synchronous and the asynchronous Aapt processor.
+ */
+public class NinePatchAaptProcessorTestUtils {
+
+ /**
+ * Signature of a PNG file.
+ */
+ public static final byte[] SIGNATURE = new byte[]{
+ (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
+
+ /**
+ * Returns the lastest build tools that's at least the passed version.
+ * @param revision the minimum required build tools version.
+ * @return the latest build tools.
+ * @throws RuntimeException if the latest build tools is older than revision.
+ */
+ static File getAapt(Revision revision) {
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ BuildToolInfo buildToolInfo =
+ AndroidSdkHandler.getInstance(getSdkDir()).getBuildToolInfo(revision, progress);
+ if (buildToolInfo == null) {
+ throw new RuntimeException("Test requires build-tools " + revision.toShortString());
+ }
+ return new File(buildToolInfo.getPath(BuildToolInfo.PathId.AAPT));
+ }
+
+
+ public static void tearDownAndCheck(int cruncherKey, Map<File, File> sourceAndCrunchedFiles,
+ PngCruncher cruncher, AtomicLong classStartTime)
+ throws IOException, DataFormatException {
+ long startTime = System.currentTimeMillis();
+ try {
+ cruncher.end(cruncherKey);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(
+ "waiting for requests completion : " + (System.currentTimeMillis() - startTime));
+ System.out.println("total time : " + (System.currentTimeMillis() - classStartTime.get()));
+ System.out.println("Comparing crunched files");
+ long comparisonStartTime = System.currentTimeMillis();
+ for (Map.Entry<File, File> sourceAndCrunched : sourceAndCrunchedFiles.entrySet()) {
+ System.out.println(sourceAndCrunched.getKey().getName());
+ File crunched = new File(sourceAndCrunched.getKey().getParent(),
+ sourceAndCrunched.getKey().getName() + getControlFileSuffix());
+
+ //copyFile(sourceAndCrunched.getValue(), crunched);
+ Map<String, Chunk> testedChunks = compareChunks(crunched, sourceAndCrunched.getValue());
+
+ try {
+ compareImageContent(crunched, sourceAndCrunched.getValue(), false);
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed with " + testedChunks.get("IHDR"), e);
+ }
+ }
+ System.out.println("Done comparing crunched files " + (System.currentTimeMillis()
+ - comparisonStartTime));
+ }
+
+ protected static String getControlFileSuffix() {
+ return ".crunched.aapt";
+ }
+
+ private static void copyFile(File source, File dest)
+ throws IOException {
+ FileChannel inputChannel = null;
+ FileChannel outputChannel = null;
+ try {
+ inputChannel = new FileInputStream(source).getChannel();
+ outputChannel = new FileOutputStream(dest).getChannel();
+ outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
+ } finally {
+ inputChannel.close();
+ outputChannel.close();
+ }
+ }
+
+
+ @NonNull
+ static File crunchFile(int crunchKey, @NonNull File file, PngCruncher aaptCruncher)
+ throws PngException, IOException {
+ File outFile = File.createTempFile("pngWriterTest", ".png");
+ outFile.deleteOnExit();
+ try {
+ aaptCruncher.crunchPng(crunchKey, file, outFile);
+ } catch (PngException e) {
+ e.printStackTrace();
+ throw e;
+ }
+ System.out.println("crunch " + file.getPath());
+ return outFile;
+ }
+
+
+ private static Map<String, Chunk> compareChunks(@NonNull File original, @NonNull File tested)
+ throws
+ IOException, DataFormatException {
+ Map<String, Chunk> originalChunks = readChunks(original);
+ Map<String, Chunk> testedChunks = readChunks(tested);
+
+ compareChunk(originalChunks, testedChunks, "IHDR");
+ compareChunk(originalChunks, testedChunks, "npLb");
+ compareChunk(originalChunks, testedChunks, "npTc");
+
+ return testedChunks;
+ }
+
+ private static void compareChunk(
+ @NonNull Map<String, Chunk> originalChunks,
+ @NonNull Map<String, Chunk> testedChunks,
+ @NonNull String chunkType) {
+ assertEquals(originalChunks.get(chunkType), testedChunks.get(chunkType));
+ }
+
+ public static Collection<Object[]> getNinePatches() {
+ File pngFolder = getPngFolder();
+ File ninePatchFolder = new File(pngFolder, "ninepatch");
+
+ File[] files = ninePatchFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.getPath().endsWith(SdkConstants.DOT_9PNG);
+ }
+ });
+ if (files != null) {
+ ImmutableList.Builder<Object[]> params = ImmutableList.builder();
+ for (File file : files) {
+ params.add(new Object[]{file, file.getName()});
+ }
+ return params.build();
+ }
+
+ return ImmutableList.of();
+ }
+
+ protected static void compareImageContent(@NonNull File originalFile, @NonNull File createdFile,
+ boolean is9Patch)
+ throws IOException {
+ BufferedImage originalImage = ImageIO.read(originalFile);
+ BufferedImage createdImage = ImageIO.read(createdFile);
+
+ int originalWidth = originalImage.getWidth();
+ int originalHeight = originalImage.getHeight();
+
+ int createdWidth = createdImage.getWidth();
+ int createdHeight = createdImage.getHeight();
+
+ // compare sizes taking into account if the image is a 9-patch
+ // in which case the original is bigger by 2 since it has the patch area still.
+ Assert.assertEquals(originalWidth, createdWidth + (is9Patch ? 2 : 0));
+ Assert.assertEquals(originalHeight, createdHeight + (is9Patch ? 2 : 0));
+
+ // get the file content
+ // always use the created Size. And for the original image, if 9-patch, just take
+ // the image minus the 1-pixel border all around.
+ int[] originalContent = new int[createdWidth * createdHeight];
+ if (is9Patch) {
+ originalImage
+ .getRGB(1, 1, createdWidth, createdHeight, originalContent, 0, createdWidth);
+ } else {
+ originalImage
+ .getRGB(0, 0, createdWidth, createdHeight, originalContent, 0, createdWidth);
+ }
+
+ int[] createdContent = new int[createdWidth * createdHeight];
+ createdImage.getRGB(0, 0, createdWidth, createdHeight, createdContent, 0, createdWidth);
+
+ for (int y = 0; y < createdHeight; y++) {
+ for (int x = 0; x < createdWidth; x++) {
+ int originalRGBA = originalContent[y * createdWidth + x];
+ int createdRGBA = createdContent[y * createdWidth + x];
+ Assert.assertEquals(
+ String.format("%dx%d: 0x%08x : 0x%08x", x, y, originalRGBA, createdRGBA),
+ originalRGBA,
+ createdRGBA);
+ }
+ }
+ }
+
+ @NonNull
+ protected static Map<String, Chunk> readChunks(@NonNull File file) throws IOException {
+ Map<String, Chunk> chunks = Maps.newHashMap();
+
+ byte[] fileBuffer = Files.toByteArray(file);
+ ByteBuffer buffer = ByteBuffer.wrap(fileBuffer);
+
+ byte[] sig = new byte[8];
+ buffer.get(sig);
+
+ assertTrue(Arrays.equals(sig, SIGNATURE));
+
+ byte[] data, type;
+ int len;
+ int crc32;
+
+ while (buffer.hasRemaining()) {
+ len = buffer.getInt();
+
+ type = new byte[4];
+ buffer.get(type);
+
+ data = new byte[len];
+ buffer.get(data);
+
+ // crc
+ crc32 = buffer.getInt();
+
+ Chunk chunk = new Chunk(type, data, crc32);
+ chunks.put(chunk.getTypeAsString(), chunk);
+ }
+
+ return chunks;
+ }
+
+ /**
+ * Returns the SDK folder as built from the Android source tree.
+ *
+ * @return the SDK
+ */
+ @NonNull
+ protected static File getSdkDir() {
+ String androidHome = System.getenv("ANDROID_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ }
+ }
+
+ throw new IllegalStateException("SDK not defined with ANDROID_HOME");
+ }
+
+ @NonNull
+ protected static File getFile(@NonNull String name) {
+ return new File(getPngFolder(), name);
+ }
+
+ @NonNull
+ protected static File getPngFolder() {
+ File folder = TestUtils.getRoot("png");
+ assertTrue(folder.isDirectory());
+ return folder;
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/png/NinePatchAsyncAaptProcessTest.java b/build-system/builder/src/test/java/com/android/builder/png/NinePatchAsyncAaptProcessTest.java
new file mode 100644
index 0000000..ed8dc90
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/NinePatchAsyncAaptProcessTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.png;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.internal.PngException;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+import com.google.common.collect.Maps;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.zip.DataFormatException;
+
+/**
+ * Asynchronous version of the aapt cruncher test.
+ */
+ at RunWith(Parameterized.class)
+public class NinePatchAsyncAaptProcessTest {
+
+ private static Map<File, File> mSourceAndCrunchedFiles;
+
+ private static final AtomicLong sClassStartTime = new AtomicLong();
+ private static final AtomicInteger sCruncherKey = new AtomicInteger();
+ private static final PngCruncher sCruncher = getCruncher();
+
+ private final File mFile;
+
+ public NinePatchAsyncAaptProcessTest(File file, String testName) {
+ mFile = file;
+ }
+
+ @BeforeClass
+ public static void setup() {
+ mSourceAndCrunchedFiles = Maps.newHashMap();
+ }
+
+ @Test
+ public void run() throws PngException, IOException {
+ File outFile = NinePatchAaptProcessorTestUtils.crunchFile(
+ sCruncherKey.get(), mFile, sCruncher);
+ mSourceAndCrunchedFiles.put(mFile, outFile);
+ }
+
+ @AfterClass
+ public static void tearDownAndCheck()
+ throws IOException, DataFormatException, InterruptedException {
+
+ NinePatchAaptProcessorTestUtils.tearDownAndCheck(
+ sCruncherKey.get(), mSourceAndCrunchedFiles, sCruncher, sClassStartTime);
+ mSourceAndCrunchedFiles = null;
+ }
+
+ @NonNull
+ private static PngCruncher getCruncher() {
+ ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
+ File aapt = NinePatchAaptProcessorTestUtils.getAapt(Revision.parseRevision("22.0.1"));
+ return QueuedCruncher.Builder.INSTANCE.newCruncher(aapt.getAbsolutePath(), logger);
+ }
+
+ @Parameters(name = "{1}")
+ public static Collection<Object[]> getNinePatches() {
+ Collection<Object[]> params = NinePatchAaptProcessorTestUtils.getNinePatches();
+ sClassStartTime.set(System.currentTimeMillis());
+ sCruncherKey.set(sCruncher.start());
+ return params;
+ }
+}
\ No newline at end of file
diff --git a/build-system/builder/src/test/java/com/android/builder/png/VectorDrawableRendererTest.java b/build-system/builder/src/test/java/com/android/builder/png/VectorDrawableRendererTest.java
new file mode 100644
index 0000000..9f04b16
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/png/VectorDrawableRendererTest.java
@@ -0,0 +1,302 @@
+/*
+ * 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.builder.png;
+
+import static java.nio.charset.Charset.defaultCharset;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.resources.Density;
+import com.android.utils.FileUtils;
+import com.android.utils.NullLogger;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link VectorDrawableRenderer}.
+ */
+public class VectorDrawableRendererTest {
+ @Rule
+ public TemporaryFolder tmpFolder = new TemporaryFolder();
+
+ private VectorDrawableRenderer mRenderer;
+
+ private File mRes;
+
+ private File mOutput;
+
+ private Set<Density> mDensities;
+
+ @Before
+ public void setUp() throws Exception {
+ mDensities = ImmutableSet.of(Density.HIGH, Density.MEDIUM, Density.LOW);
+ mOutput = new File("output");
+ mRenderer = new VectorDrawableRenderer(19, mOutput, mDensities, new NullLogger());
+ mRes = tmpFolder.newFolder("app", "src", "main", "res");
+ }
+
+ @Test
+ public void commonCase() throws Exception {
+ File drawable = new File(mRes, "drawable");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-hdpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-mdpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-ldpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-anydpi-v21", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void noDensities() throws Exception {
+ mRenderer = new VectorDrawableRenderer(
+ 19, mOutput, Collections.<Density>emptySet(), new NullLogger());
+ File drawable = new File(mRes, "drawable");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-anydpi-v21", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void languageQualifier() throws Exception {
+ File drawable = new File(mRes, "drawable-fr");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-fr-hdpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-fr-mdpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-fr-ldpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-fr-anydpi-v21", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void versionQualifier() throws Exception {
+ File drawable = new File(mRes, "drawable-v16");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-hdpi-v16", "icon.png"),
+ FileUtils.join(mOutput, "drawable-mdpi-v16", "icon.png"),
+ FileUtils.join(mOutput, "drawable-ldpi-v16", "icon.png"),
+ FileUtils.join(mOutput, "drawable-anydpi-v21", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void densityQualifier() throws Exception {
+ File drawable = new File(mRes, "drawable-hdpi");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-hdpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-hdpi-v21", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void anyDpi() throws Exception {
+ File drawable = new File(mRes, "drawable-anydpi");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-ldpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-mdpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-hdpi", "icon.png"),
+ FileUtils.join(mOutput, "drawable-anydpi-v21", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void anyDpi_version() throws Exception {
+ File drawable = new File(mRes, "drawable-anydpi-v16");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-ldpi-v16", "icon.png"),
+ FileUtils.join(mOutput, "drawable-mdpi-v16", "icon.png"),
+ FileUtils.join(mOutput, "drawable-hdpi-v16", "icon.png"),
+ FileUtils.join(mOutput, "drawable-anydpi-v21", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void noDpi() throws Exception {
+ File drawable = new File(mRes, "drawable-nodpi");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-nodpi", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void noDpi_version() throws Exception {
+ File drawable = new File(mRes, "drawable-nodpi-v16");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ Collection<File> result = mRenderer.getFilesToBeGenerated(input);
+
+ Assert.assertEquals(
+ ImmutableSet.of(
+ FileUtils.join(mOutput, "drawable-nodpi-v16", "icon.xml")),
+ ImmutableSet.copyOf(result));
+ }
+
+ @Test
+ public void needsPreprocessing() throws Exception {
+ File drawable = new File(mRes, "drawable");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ assertTrue(mRenderer.needsPreprocessing(input));
+ }
+
+ @Test
+ public void needsPreprocessing_v21() throws Exception {
+ File drawableV21 = new File(mRes, "drawable-v21");
+ File input = new File(drawableV21, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ assertFalse(mRenderer.needsPreprocessing(input));
+ }
+
+ @Test
+ public void needsPreprocessing_anydpi_v21() throws Exception {
+ File drawableV21 = new File(mRes, "drawable-anydpi-v21");
+ File input = new File(drawableV21, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ assertFalse(mRenderer.needsPreprocessing(input));
+ }
+
+ @Test
+ public void needsPreprocessing_v16() throws Exception {
+ File drawableV16 = new File(mRes, "drawable-v16");
+ File input = new File(drawableV16, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ assertTrue(mRenderer.needsPreprocessing(input));
+ }
+
+ @Test
+ public void needsPreprocessing_nonVector() throws Exception {
+ File drawable = new File(mRes, "drawable");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<bitmap android:src=\"@drawable/icon\" />", input, defaultCharset());
+
+ assertFalse(mRenderer.needsPreprocessing(input));
+ }
+
+ @Test
+ public void needsPreprocessing_notDrawable() throws Exception {
+ File values = new File(mRes, "values");
+ File input = new File(values, "strings.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<resources></resources>", input, defaultCharset());
+
+ assertFalse(mRenderer.needsPreprocessing(input));
+ }
+
+ @Test
+ public void needsPreprocessing_minSdk() throws Exception {
+ mRenderer = new VectorDrawableRenderer(21, mOutput, mDensities, new NullLogger());
+ File drawable = new File(mRes, "drawable");
+ File input = new File(drawable, "icon.xml");
+
+ Files.createParentDirs(input);
+ Files.write("<vector></vector>", input, defaultCharset());
+
+ assertFalse(mRenderer.needsPreprocessing(input));
+ }
+}
\ No newline at end of file
diff --git a/build-system/builder/src/test/java/com/android/builder/testing/ConnectedDeviceTest.java b/build-system/builder/src/test/java/com/android/builder/testing/ConnectedDeviceTest.java
new file mode 100644
index 0000000..ac21ee0
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/testing/ConnectedDeviceTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.builder.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.IDevice;
+import com.android.utils.ILogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+ at RunWith(MockitoJUnitRunner.class)
+public class ConnectedDeviceTest {
+
+ @Mock
+ public IDevice mIDevice;
+
+ @Mock
+ public ILogger mLogger;
+
+ private ConnectedDevice mDevice;
+
+ @Before
+ public void createDevice() {
+ when(mIDevice.getSystemProperty(anyString())).thenReturn(futureOf( null));
+ mDevice = new ConnectedDevice(mIDevice, mLogger, 10000, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void testGetAbisForLAndAbove() {
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_CPU_ABI_LIST))
+ .thenReturn(futureOf("x86,x86_64"));
+ assertThat(mDevice.getAbis()).containsExactly("x86", "x86_64");
+ }
+
+
+ @Test
+ public void testGetSingleAbiForPreL() {
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_CPU_ABI)).thenReturn(futureOf("x86"));
+ assertThat(mDevice.getAbis()).containsExactly("x86");
+ }
+
+ @Test
+ public void testGetAbisForPreL() {
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_CPU_ABI)).thenReturn(futureOf("x86"));
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_CPU_ABI2))
+ .thenReturn(futureOf("x86_64"));
+ assertThat(mDevice.getAbis()).containsExactly("x86", "x86_64");
+ }
+
+ @Test
+ public void testGetDensityFromDevice() {
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_DENSITY)).thenReturn(futureOf("480"));
+ assertThat(mDevice.getDensity()).isEqualTo(480);
+ }
+
+ @Test
+ public void testGetDensityFromEmulator() {
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_EMULATOR_DENSITY))
+ .thenReturn(futureOf("380"));
+ assertThat(mDevice.getDensity()).isEqualTo(380);
+ }
+
+ @Test
+ public void testGetDensityTimeout() {
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_DENSITY)).thenReturn(TIMEOUT_FUTURE);
+ assertThat(mDevice.getDensity()).isEqualTo(-1);
+ }
+
+
+ @Test
+ public void testGetDensityInfiniteTimeout() {
+ ConnectedDevice device = new ConnectedDevice(mIDevice, mLogger, 0, TimeUnit.MILLISECONDS);
+ when(mIDevice.getSystemProperty(IDevice.PROP_DEVICE_DENSITY))
+ .thenReturn(noTimeoutFutureOf("480"));
+ assertThat(device.getDensity()).isEqualTo(480);
+ }
+
+
+ private abstract static class TestFuture implements Future<String> {
+
+ boolean isDone = false;
+
+ @Override
+ public final boolean isDone() {
+ return isDone;
+ }
+
+ @Override
+ public final boolean cancel(boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public final boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public String get() throws InterruptedException, ExecutionException {
+ throw new AssertionError("Should not call get().");
+ }
+
+ @Override
+ public String get(long timeout, @NonNull TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ throw new AssertionError("Should not call get(long, TimeUnit).");
+ }
+
+ }
+
+ private static TestFuture futureOf(final String value) {
+ return new TestFuture() {
+ @Override
+ public final String get(long timeout, @NonNull TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ isDone = true;
+ return value;
+ }
+ };
+ }
+
+ private static TestFuture noTimeoutFutureOf(final String value) {
+ return new TestFuture() {
+ @Override
+ public String get() throws InterruptedException, ExecutionException {
+ isDone = true;
+ return value;
+ }
+ };
+ }
+
+ private static final Future<String> TIMEOUT_FUTURE = new TestFuture() {
+ @Override
+ public final String get(long timeout, @NonNull TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ isDone = true;
+ throw new TimeoutException("Future expected to time out");
+ }
+ };
+
+
+
+
+
+}
diff --git a/base/build-system/builder/src/test/java/com/android/builder/testing/MockableJarGeneratorTest.java b/build-system/builder/src/test/java/com/android/builder/testing/MockableJarGeneratorTest.java
similarity index 100%
rename from base/build-system/builder/src/test/java/com/android/builder/testing/MockableJarGeneratorTest.java
rename to build-system/builder/src/test/java/com/android/builder/testing/MockableJarGeneratorTest.java
diff --git a/base/build-system/builder/src/test/resources/testData/core/aapt20.txt b/build-system/builder/src/test/resources/testData/core/aapt20.txt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/core/aapt20.txt
rename to build-system/builder/src/test/resources/testData/core/aapt20.txt
diff --git a/base/build-system/builder/src/test/resources/testData/core/aapt21.txt b/build-system/builder/src/test/resources/testData/core/aapt21.txt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/core/aapt21.txt
rename to build-system/builder/src/test/resources/testData/core/aapt21.txt
diff --git a/base/build-system/builder/src/test/resources/testData/dependencyData/no_output.d b/build-system/builder/src/test/resources/testData/dependencyData/no_output.d
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/dependencyData/no_output.d
rename to build-system/builder/src/test/resources/testData/dependencyData/no_output.d
diff --git a/base/build-system/builder/src/test/resources/testData/dependencyData/secondary_files.d b/build-system/builder/src/test/resources/testData/dependencyData/secondary_files.d
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/dependencyData/secondary_files.d
rename to build-system/builder/src/test/resources/testData/dependencyData/secondary_files.d
diff --git a/base/build-system/builder/src/test/resources/testData/dependencyData/windows_mode1.d b/build-system/builder/src/test/resources/testData/dependencyData/windows_mode1.d
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/dependencyData/windows_mode1.d
rename to build-system/builder/src/test/resources/testData/dependencyData/windows_mode1.d
diff --git a/base/build-system/builder/src/test/resources/testData/dependencyData/windows_mode2.d b/build-system/builder/src/test/resources/testData/dependencyData/windows_mode2.d
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/dependencyData/windows_mode2.d
rename to build-system/builder/src/test/resources/testData/dependencyData/windows_mode2.d
diff --git a/build-system/builder/src/test/resources/testData/packaging/images/lena.png b/build-system/builder/src/test/resources/testData/packaging/images/lena.png
new file mode 100644
index 0000000..59ef68a
Binary files /dev/null and b/build-system/builder/src/test/resources/testData/packaging/images/lena.png differ
diff --git a/build-system/builder/src/test/resources/testData/packaging/root b/build-system/builder/src/test/resources/testData/packaging/root
new file mode 100644
index 0000000..11ba934
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/packaging/root
@@ -0,0 +1 @@
+A text file in the root.
\ No newline at end of file
diff --git a/build-system/builder/src/test/resources/testData/packaging/text-files/rfc2460.txt b/build-system/builder/src/test/resources/testData/packaging/text-files/rfc2460.txt
new file mode 100644
index 0000000..de7b7fa
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/packaging/text-files/rfc2460.txt
@@ -0,0 +1,2187 @@
+
+
+
+
+
+
+Network Working Group S. Deering
+Request for Comments: 2460 Cisco
+Obsoletes: 1883 R. Hinden
+Category: Standards Track Nokia
+ December 1998
+
+
+ Internet Protocol, Version 6 (IPv6)
+ Specification
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (1998). All Rights Reserved.
+
+Abstract
+
+ This document specifies version 6 of the Internet Protocol (IPv6),
+ also sometimes referred to as IP Next Generation or IPng.
+
+Table of Contents
+
+ 1. Introduction..................................................2
+ 2. Terminology...................................................3
+ 3. IPv6 Header Format............................................4
+ 4. IPv6 Extension Headers........................................6
+ 4.1 Extension Header Order...................................7
+ 4.2 Options..................................................9
+ 4.3 Hop-by-Hop Options Header...............................11
+ 4.4 Routing Header..........................................12
+ 4.5 Fragment Header.........................................18
+ 4.6 Destination Options Header..............................23
+ 4.7 No Next Header..........................................24
+ 5. Packet Size Issues...........................................24
+ 6. Flow Labels..................................................25
+ 7. Traffic Classes..............................................25
+ 8. Upper-Layer Protocol Issues..................................27
+ 8.1 Upper-Layer Checksums...................................27
+ 8.2 Maximum Packet Lifetime.................................28
+ 8.3 Maximum Upper-Layer Payload Size........................28
+ 8.4 Responding to Packets Carrying Routing Headers..........29
+
+
+
+Deering & Hinden Standards Track [Page 1]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ Appendix A. Semantics and Usage of the Flow Label Field.........30
+ Appendix B. Formatting Guidelines for Options...................32
+ Security Considerations.........................................35
+ Acknowledgments.................................................35
+ Authors' Addresses..............................................35
+ References......................................................35
+ Changes Since RFC-1883..........................................36
+ Full Copyright Statement........................................39
+
+1. Introduction
+
+ IP version 6 (IPv6) is a new version of the Internet Protocol,
+ designed as the successor to IP version 4 (IPv4) [RFC-791]. The
+ changes from IPv4 to IPv6 fall primarily into the following
+ categories:
+
+ o Expanded Addressing Capabilities
+
+ IPv6 increases the IP address size from 32 bits to 128 bits, to
+ support more levels of addressing hierarchy, a much greater
+ number of addressable nodes, and simpler auto-configuration of
+ addresses. The scalability of multicast routing is improved by
+ adding a "scope" field to multicast addresses. And a new type
+ of address called an "anycast address" is defined, used to send
+ a packet to any one of a group of nodes.
+
+ o Header Format Simplification
+
+ Some IPv4 header fields have been dropped or made optional, to
+ reduce the common-case processing cost of packet handling and
+ to limit the bandwidth cost of the IPv6 header.
+
+ o Improved Support for Extensions and Options
+
+ Changes in the way IP header options are encoded allows for
+ more efficient forwarding, less stringent limits on the length
+ of options, and greater flexibility for introducing new options
+ in the future.
+
+ o Flow Labeling Capability
+
+ A new capability is added to enable the labeling of packets
+ belonging to particular traffic "flows" for which the sender
+ requests special handling, such as non-default quality of
+ service or "real-time" service.
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 2]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ o Authentication and Privacy Capabilities
+
+ Extensions to support authentication, data integrity, and
+ (optional) data confidentiality are specified for IPv6.
+
+ This document specifies the basic IPv6 header and the initially-
+ defined IPv6 extension headers and options. It also discusses packet
+ size issues, the semantics of flow labels and traffic classes, and
+ the effects of IPv6 on upper-layer protocols. The format and
+ semantics of IPv6 addresses are specified separately in [ADDRARCH].
+ The IPv6 version of ICMP, which all IPv6 implementations are required
+ to include, is specified in [ICMPv6].
+
+2. Terminology
+
+ node - a device that implements IPv6.
+
+ router - a node that forwards IPv6 packets not explicitly
+ addressed to itself. [See Note below].
+
+ host - any node that is not a router. [See Note below].
+
+ upper layer - a protocol layer immediately above IPv6. Examples are
+ transport protocols such as TCP and UDP, control
+ protocols such as ICMP, routing protocols such as OSPF,
+ and internet or lower-layer protocols being "tunneled"
+ over (i.e., encapsulated in) IPv6 such as IPX,
+ AppleTalk, or IPv6 itself.
+
+ link - a communication facility or medium over which nodes can
+ communicate at the link layer, i.e., the layer
+ immediately below IPv6. Examples are Ethernets (simple
+ or bridged); PPP links; X.25, Frame Relay, or ATM
+ networks; and internet (or higher) layer "tunnels",
+ such as tunnels over IPv4 or IPv6 itself.
+
+ neighbors - nodes attached to the same link.
+
+ interface - a node's attachment to a link.
+
+ address - an IPv6-layer identifier for an interface or a set of
+ interfaces.
+
+ packet - an IPv6 header plus payload.
+
+ link MTU - the maximum transmission unit, i.e., maximum packet
+ size in octets, that can be conveyed over a link.
+
+
+
+
+Deering & Hinden Standards Track [Page 3]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ path MTU - the minimum link MTU of all the links in a path between
+ a source node and a destination node.
+
+ Note: it is possible, though unusual, for a device with multiple
+ interfaces to be configured to forward non-self-destined packets
+ arriving from some set (fewer than all) of its interfaces, and to
+ discard non-self-destined packets arriving from its other interfaces.
+ Such a device must obey the protocol requirements for routers when
+ receiving packets from, and interacting with neighbors over, the
+ former (forwarding) interfaces. It must obey the protocol
+ requirements for hosts when receiving packets from, and interacting
+ with neighbors over, the latter (non-forwarding) interfaces.
+
+3. IPv6 Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Version| Traffic Class | Flow Label |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Payload Length | Next Header | Hop Limit |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Source Address +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Destination Address +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Version 4-bit Internet Protocol version number = 6.
+
+ Traffic Class 8-bit traffic class field. See section 7.
+
+ Flow Label 20-bit flow label. See section 6.
+
+ Payload Length 16-bit unsigned integer. Length of the IPv6
+ payload, i.e., the rest of the packet following
+ this IPv6 header, in octets. (Note that any
+
+
+
+
+
+Deering & Hinden Standards Track [Page 4]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ extension headers [section 4] present are
+ considered part of the payload, i.e., included
+ in the length count.)
+
+ Next Header 8-bit selector. Identifies the type of header
+ immediately following the IPv6 header. Uses the
+ same values as the IPv4 Protocol field [RFC-1700
+ et seq.].
+
+ Hop Limit 8-bit unsigned integer. Decremented by 1 by
+ each node that forwards the packet. The packet
+ is discarded if Hop Limit is decremented to
+ zero.
+
+ Source Address 128-bit address of the originator of the packet.
+ See [ADDRARCH].
+
+ Destination Address 128-bit address of the intended recipient of the
+ packet (possibly not the ultimate recipient, if
+ a Routing header is present). See [ADDRARCH]
+ and section 4.4.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 5]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+4. IPv6 Extension Headers
+
+ In IPv6, optional internet-layer information is encoded in separate
+ headers that may be placed between the IPv6 header and the upper-
+ layer header in a packet. There are a small number of such extension
+ headers, each identified by a distinct Next Header value. As
+ illustrated in these examples, an IPv6 packet may carry zero, one, or
+ more extension headers, each identified by the Next Header field of
+ the preceding header:
+
+ +---------------+------------------------
+ | IPv6 header | TCP header + data
+ | |
+ | Next Header = |
+ | TCP |
+ +---------------+------------------------
+
+
+ +---------------+----------------+------------------------
+ | IPv6 header | Routing header | TCP header + data
+ | | |
+ | Next Header = | Next Header = |
+ | Routing | TCP |
+ +---------------+----------------+------------------------
+
+
+ +---------------+----------------+-----------------+-----------------
+ | IPv6 header | Routing header | Fragment header | fragment of TCP
+ | | | | header + data
+ | Next Header = | Next Header = | Next Header = |
+ | Routing | Fragment | TCP |
+ +---------------+----------------+-----------------+-----------------
+
+ With one exception, extension headers are not examined or processed
+ by any node along a packet's delivery path, until the packet reaches
+ the node (or each of the set of nodes, in the case of multicast)
+ identified in the Destination Address field of the IPv6 header.
+ There, normal demultiplexing on the Next Header field of the IPv6
+ header invokes the module to process the first extension header, or
+ the upper-layer header if no extension header is present. The
+ contents and semantics of each extension header determine whether or
+ not to proceed to the next header. Therefore, extension headers must
+ be processed strictly in the order they appear in the packet; a
+ receiver must not, for example, scan through a packet looking for a
+ particular kind of extension header and process that header prior to
+ processing all preceding ones.
+
+
+
+
+
+Deering & Hinden Standards Track [Page 6]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ The exception referred to in the preceding paragraph is the Hop-by-
+ Hop Options header, which carries information that must be examined
+ and processed by every node along a packet's delivery path, including
+ the source and destination nodes. The Hop-by-Hop Options header,
+ when present, must immediately follow the IPv6 header. Its presence
+ is indicated by the value zero in the Next Header field of the IPv6
+ header.
+
+ If, as a result of processing a header, a node is required to proceed
+ to the next header but the Next Header value in the current header is
+ unrecognized by the node, it should discard the packet and send an
+ ICMP Parameter Problem message to the source of the packet, with an
+ ICMP Code value of 1 ("unrecognized Next Header type encountered")
+ and the ICMP Pointer field containing the offset of the unrecognized
+ value within the original packet. The same action should be taken if
+ a node encounters a Next Header value of zero in any header other
+ than an IPv6 header.
+
+ Each extension header is an integer multiple of 8 octets long, in
+ order to retain 8-octet alignment for subsequent headers. Multi-
+ octet fields within each extension header are aligned on their
+ natural boundaries, i.e., fields of width n octets are placed at an
+ integer multiple of n octets from the start of the header, for n = 1,
+ 2, 4, or 8.
+
+ A full implementation of IPv6 includes implementation of the
+ following extension headers:
+
+ Hop-by-Hop Options
+ Routing (Type 0)
+ Fragment
+ Destination Options
+ Authentication
+ Encapsulating Security Payload
+
+ The first four are specified in this document; the last two are
+ specified in [RFC-2402] and [RFC-2406], respectively.
+
+4.1 Extension Header Order
+
+ When more than one extension header is used in the same packet, it is
+ recommended that those headers appear in the following order:
+
+ IPv6 header
+ Hop-by-Hop Options header
+ Destination Options header (note 1)
+ Routing header
+ Fragment header
+
+
+
+Deering & Hinden Standards Track [Page 7]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ Authentication header (note 2)
+ Encapsulating Security Payload header (note 2)
+ Destination Options header (note 3)
+ upper-layer header
+
+ note 1: for options to be processed by the first destination
+ that appears in the IPv6 Destination Address field
+ plus subsequent destinations listed in the Routing
+ header.
+
+ note 2: additional recommendations regarding the relative
+ order of the Authentication and Encapsulating
+ Security Payload headers are given in [RFC-2406].
+
+ note 3: for options to be processed only by the final
+ destination of the packet.
+
+ Each extension header should occur at most once, except for the
+ Destination Options header which should occur at most twice (once
+ before a Routing header and once before the upper-layer header).
+
+ If the upper-layer header is another IPv6 header (in the case of IPv6
+ being tunneled over or encapsulated in IPv6), it may be followed by
+ its own extension headers, which are separately subject to the same
+ ordering recommendations.
+
+ If and when other extension headers are defined, their ordering
+ constraints relative to the above listed headers must be specified.
+
+ IPv6 nodes must accept and attempt to process extension headers in
+ any order and occurring any number of times in the same packet,
+ except for the Hop-by-Hop Options header which is restricted to
+ appear immediately after an IPv6 header only. Nonetheless, it is
+ strongly advised that sources of IPv6 packets adhere to the above
+ recommended order until and unless subsequent specifications revise
+ that recommendation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 8]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+4.2 Options
+
+ Two of the currently-defined extension headers -- the Hop-by-Hop
+ Options header and the Destination Options header -- carry a variable
+ number of type-length-value (TLV) encoded "options", of the following
+ format:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
+ | Option Type | Opt Data Len | Option Data
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
+
+ Option Type 8-bit identifier of the type of option.
+
+ Opt Data Len 8-bit unsigned integer. Length of the Option
+ Data field of this option, in octets.
+
+ Option Data Variable-length field. Option-Type-specific
+ data.
+
+ The sequence of options within a header must be processed strictly in
+ the order they appear in the header; a receiver must not, for
+ example, scan through the header looking for a particular kind of
+ option and process that option prior to processing all preceding
+ ones.
+
+ The Option Type identifiers are internally encoded such that their
+ highest-order two bits specify the action that must be taken if the
+ processing IPv6 node does not recognize the Option Type:
+
+ 00 - skip over this option and continue processing the header.
+
+ 01 - discard the packet.
+
+ 10 - discard the packet and, regardless of whether or not the
+ packet's Destination Address was a multicast address, send an
+ ICMP Parameter Problem, Code 2, message to the packet's
+ Source Address, pointing to the unrecognized Option Type.
+
+ 11 - discard the packet and, only if the packet's Destination
+ Address was not a multicast address, send an ICMP Parameter
+ Problem, Code 2, message to the packet's Source Address,
+ pointing to the unrecognized Option Type.
+
+ The third-highest-order bit of the Option Type specifies whether or
+ not the Option Data of that option can change en-route to the
+ packet's final destination. When an Authentication header is present
+
+
+
+
+
+Deering & Hinden Standards Track [Page 9]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ in the packet, for any option whose data may change en-route, its
+ entire Option Data field must be treated as zero-valued octets when
+ computing or verifying the packet's authenticating value.
+
+ 0 - Option Data does not change en-route
+
+ 1 - Option Data may change en-route
+
+ The three high-order bits described above are to be treated as part
+ of the Option Type, not independent of the Option Type. That is, a
+ particular option is identified by a full 8-bit Option Type, not just
+ the low-order 5 bits of an Option Type.
+
+ The same Option Type numbering space is used for both the Hop-by-Hop
+ Options header and the Destination Options header. However, the
+ specification of a particular option may restrict its use to only one
+ of those two headers.
+
+ Individual options may have specific alignment requirements, to
+ ensure that multi-octet values within Option Data fields fall on
+ natural boundaries. The alignment requirement of an option is
+ specified using the notation xn+y, meaning the Option Type must
+ appear at an integer multiple of x octets from the start of the
+ header, plus y octets. For example:
+
+ 2n means any 2-octet offset from the start of the header.
+ 8n+2 means any 8-octet offset from the start of the header,
+ plus 2 octets.
+
+ There are two padding options which are used when necessary to align
+ subsequent options and to pad out the containing header to a multiple
+ of 8 octets in length. These padding options must be recognized by
+ all IPv6 implementations:
+
+ Pad1 option (alignment requirement: none)
+
+ +-+-+-+-+-+-+-+-+
+ | 0 |
+ +-+-+-+-+-+-+-+-+
+
+ NOTE! the format of the Pad1 option is a special case -- it does
+ not have length and value fields.
+
+ The Pad1 option is used to insert one octet of padding into the
+ Options area of a header. If more than one octet of padding is
+ required, the PadN option, described next, should be used, rather
+ than multiple Pad1 options.
+
+
+
+
+Deering & Hinden Standards Track [Page 10]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ PadN option (alignment requirement: none)
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
+ | 1 | Opt Data Len | Option Data
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
+
+ The PadN option is used to insert two or more octets of padding
+ into the Options area of a header. For N octets of padding, the
+ Opt Data Len field contains the value N-2, and the Option Data
+ consists of N-2 zero-valued octets.
+
+ Appendix B contains formatting guidelines for designing new options.
+
+4.3 Hop-by-Hop Options Header
+
+ The Hop-by-Hop Options header is used to carry optional information
+ that must be examined by every node along a packet's delivery path.
+ The Hop-by-Hop Options header is identified by a Next Header value of
+ 0 in the IPv6 header, and has the following format:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
+ | |
+ . .
+ . Options .
+ . .
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Next Header 8-bit selector. Identifies the type of header
+ immediately following the Hop-by-Hop Options
+ header. Uses the same values as the IPv4
+ Protocol field [RFC-1700 et seq.].
+
+ Hdr Ext Len 8-bit unsigned integer. Length of the Hop-by-
+ Hop Options header in 8-octet units, not
+ including the first 8 octets.
+
+ Options Variable-length field, of length such that the
+ complete Hop-by-Hop Options header is an integer
+ multiple of 8 octets long. Contains one or more
+ TLV-encoded options, as described in section
+ 4.2.
+
+ The only hop-by-hop options defined in this document are the Pad1 and
+ PadN options specified in section 4.2.
+
+
+
+
+Deering & Hinden Standards Track [Page 11]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+4.4 Routing Header
+
+ The Routing header is used by an IPv6 source to list one or more
+ intermediate nodes to be "visited" on the way to a packet's
+ destination. This function is very similar to IPv4's Loose Source
+ and Record Route option. The Routing header is identified by a Next
+ Header value of 43 in the immediately preceding header, and has the
+ following format:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len | Routing Type | Segments Left |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ . .
+ . type-specific data .
+ . .
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Next Header 8-bit selector. Identifies the type of header
+ immediately following the Routing header. Uses
+ the same values as the IPv4 Protocol field
+ [RFC-1700 et seq.].
+
+ Hdr Ext Len 8-bit unsigned integer. Length of the Routing
+ header in 8-octet units, not including the first
+ 8 octets.
+
+ Routing Type 8-bit identifier of a particular Routing header
+ variant.
+
+ Segments Left 8-bit unsigned integer. Number of route
+ segments remaining, i.e., number of explicitly
+ listed intermediate nodes still to be visited
+ before reaching the final destination.
+
+ type-specific data Variable-length field, of format determined by
+ the Routing Type, and of length such that the
+ complete Routing header is an integer multiple
+ of 8 octets long.
+
+ If, while processing a received packet, a node encounters a Routing
+ header with an unrecognized Routing Type value, the required behavior
+ of the node depends on the value of the Segments Left field, as
+ follows:
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 12]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ If Segments Left is zero, the node must ignore the Routing header
+ and proceed to process the next header in the packet, whose type
+ is identified by the Next Header field in the Routing header.
+
+ If Segments Left is non-zero, the node must discard the packet and
+ send an ICMP Parameter Problem, Code 0, message to the packet's
+ Source Address, pointing to the unrecognized Routing Type.
+
+ If, after processing a Routing header of a received packet, an
+ intermediate node determines that the packet is to be forwarded onto
+ a link whose link MTU is less than the size of the packet, the node
+ must discard the packet and send an ICMP Packet Too Big message to
+ the packet's Source Address.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 13]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ The Type 0 Routing header has the following format:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len | Routing Type=0| Segments Left |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Address[1] +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Address[2] +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ . . .
+ . . .
+ . . .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Address[n] +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Next Header 8-bit selector. Identifies the type of header
+ immediately following the Routing header. Uses
+ the same values as the IPv4 Protocol field
+ [RFC-1700 et seq.].
+
+ Hdr Ext Len 8-bit unsigned integer. Length of the Routing
+ header in 8-octet units, not including the first
+ 8 octets. For the Type 0 Routing header, Hdr
+ Ext Len is equal to two times the number of
+ addresses in the header.
+
+ Routing Type 0.
+
+
+
+Deering & Hinden Standards Track [Page 14]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ Segments Left 8-bit unsigned integer. Number of route
+ segments remaining, i.e., number of explicitly
+ listed intermediate nodes still to be visited
+ before reaching the final destination.
+
+ Reserved 32-bit reserved field. Initialized to zero for
+ transmission; ignored on reception.
+
+ Address[1..n] Vector of 128-bit addresses, numbered 1 to n.
+
+ Multicast addresses must not appear in a Routing header of Type 0, or
+ in the IPv6 Destination Address field of a packet carrying a Routing
+ header of Type 0.
+
+ A Routing header is not examined or processed until it reaches the
+ node identified in the Destination Address field of the IPv6 header.
+ In that node, dispatching on the Next Header field of the immediately
+ preceding header causes the Routing header module to be invoked,
+ which, in the case of Routing Type 0, performs the following
+ algorithm:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 15]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ if Segments Left = 0 {
+ proceed to process the next header in the packet, whose type is
+ identified by the Next Header field in the Routing header
+ }
+ else if Hdr Ext Len is odd {
+ send an ICMP Parameter Problem, Code 0, message to the Source
+ Address, pointing to the Hdr Ext Len field, and discard the
+ packet
+ }
+ else {
+ compute n, the number of addresses in the Routing header, by
+ dividing Hdr Ext Len by 2
+
+ if Segments Left is greater than n {
+ send an ICMP Parameter Problem, Code 0, message to the Source
+ Address, pointing to the Segments Left field, and discard the
+ packet
+ }
+ else {
+ decrement Segments Left by 1;
+ compute i, the index of the next address to be visited in
+ the address vector, by subtracting Segments Left from n
+
+ if Address [i] or the IPv6 Destination Address is multicast {
+ discard the packet
+ }
+ else {
+ swap the IPv6 Destination Address and Address[i]
+
+ if the IPv6 Hop Limit is less than or equal to 1 {
+ send an ICMP Time Exceeded -- Hop Limit Exceeded in
+ Transit message to the Source Address and discard the
+ packet
+ }
+ else {
+ decrement the Hop Limit by 1
+
+ resubmit the packet to the IPv6 module for transmission
+ to the new destination
+ }
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 16]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ As an example of the effects of the above algorithm, consider the
+ case of a source node S sending a packet to destination node D, using
+ a Routing header to cause the packet to be routed via intermediate
+ nodes I1, I2, and I3. The values of the relevant IPv6 header and
+ Routing header fields on each segment of the delivery path would be
+ as follows:
+
+ As the packet travels from S to I1:
+
+ Source Address = S Hdr Ext Len = 6
+ Destination Address = I1 Segments Left = 3
+ Address[1] = I2
+ Address[2] = I3
+ Address[3] = D
+
+ As the packet travels from I1 to I2:
+
+ Source Address = S Hdr Ext Len = 6
+ Destination Address = I2 Segments Left = 2
+ Address[1] = I1
+ Address[2] = I3
+ Address[3] = D
+
+ As the packet travels from I2 to I3:
+
+ Source Address = S Hdr Ext Len = 6
+ Destination Address = I3 Segments Left = 1
+ Address[1] = I1
+ Address[2] = I2
+ Address[3] = D
+
+ As the packet travels from I3 to D:
+
+ Source Address = S Hdr Ext Len = 6
+ Destination Address = D Segments Left = 0
+ Address[1] = I1
+ Address[2] = I2
+ Address[3] = I3
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 17]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+4.5 Fragment Header
+
+ The Fragment header is used by an IPv6 source to send a packet larger
+ than would fit in the path MTU to its destination. (Note: unlike
+ IPv4, fragmentation in IPv6 is performed only by source nodes, not by
+ routers along a packet's delivery path -- see section 5.) The
+ Fragment header is identified by a Next Header value of 44 in the
+ immediately preceding header, and has the following format:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Reserved | Fragment Offset |Res|M|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identification |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Next Header 8-bit selector. Identifies the initial header
+ type of the Fragmentable Part of the original
+ packet (defined below). Uses the same values as
+ the IPv4 Protocol field [RFC-1700 et seq.].
+
+ Reserved 8-bit reserved field. Initialized to zero for
+ transmission; ignored on reception.
+
+ Fragment Offset 13-bit unsigned integer. The offset, in 8-octet
+ units, of the data following this header,
+ relative to the start of the Fragmentable Part
+ of the original packet.
+
+ Res 2-bit reserved field. Initialized to zero for
+ transmission; ignored on reception.
+
+ M flag 1 = more fragments; 0 = last fragment.
+
+ Identification 32 bits. See description below.
+
+ In order to send a packet that is too large to fit in the MTU of the
+ path to its destination, a source node may divide the packet into
+ fragments and send each fragment as a separate packet, to be
+ reassembled at the receiver.
+
+ For every packet that is to be fragmented, the source node generates
+ an Identification value. The Identification must be different than
+ that of any other fragmented packet sent recently* with the same
+ Source Address and Destination Address. If a Routing header is
+ present, the Destination Address of concern is that of the final
+ destination.
+
+
+
+
+
+Deering & Hinden Standards Track [Page 18]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ * "recently" means within the maximum likely lifetime of a packet,
+ including transit time from source to destination and time spent
+ awaiting reassembly with other fragments of the same packet.
+ However, it is not required that a source node know the maximum
+ packet lifetime. Rather, it is assumed that the requirement can
+ be met by maintaining the Identification value as a simple, 32-
+ bit, "wrap-around" counter, incremented each time a packet must
+ be fragmented. It is an implementation choice whether to
+ maintain a single counter for the node or multiple counters,
+ e.g., one for each of the node's possible source addresses, or
+ one for each active (source address, destination address)
+ combination.
+
+ The initial, large, unfragmented packet is referred to as the
+ "original packet", and it is considered to consist of two parts, as
+ illustrated:
+
+ original packet:
+
+ +------------------+----------------------//-----------------------+
+ | Unfragmentable | Fragmentable |
+ | Part | Part |
+ +------------------+----------------------//-----------------------+
+
+ The Unfragmentable Part consists of the IPv6 header plus any
+ extension headers that must be processed by nodes en route to the
+ destination, that is, all headers up to and including the Routing
+ header if present, else the Hop-by-Hop Options header if present,
+ else no extension headers.
+
+ The Fragmentable Part consists of the rest of the packet, that is,
+ any extension headers that need be processed only by the final
+ destination node(s), plus the upper-layer header and data.
+
+ The Fragmentable Part of the original packet is divided into
+ fragments, each, except possibly the last ("rightmost") one, being an
+ integer multiple of 8 octets long. The fragments are transmitted in
+ separate "fragment packets" as illustrated:
+
+ original packet:
+
+ +------------------+--------------+--------------+--//--+----------+
+ | Unfragmentable | first | second | | last |
+ | Part | fragment | fragment | .... | fragment |
+ +------------------+--------------+--------------+--//--+----------+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 19]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ fragment packets:
+
+ +------------------+--------+--------------+
+ | Unfragmentable |Fragment| first |
+ | Part | Header | fragment |
+ +------------------+--------+--------------+
+
+ +------------------+--------+--------------+
+ | Unfragmentable |Fragment| second |
+ | Part | Header | fragment |
+ +------------------+--------+--------------+
+ o
+ o
+ o
+ +------------------+--------+----------+
+ | Unfragmentable |Fragment| last |
+ | Part | Header | fragment |
+ +------------------+--------+----------+
+
+ Each fragment packet is composed of:
+
+ (1) The Unfragmentable Part of the original packet, with the
+ Payload Length of the original IPv6 header changed to contain
+ the length of this fragment packet only (excluding the length
+ of the IPv6 header itself), and the Next Header field of the
+ last header of the Unfragmentable Part changed to 44.
+
+ (2) A Fragment header containing:
+
+ The Next Header value that identifies the first header of
+ the Fragmentable Part of the original packet.
+
+ A Fragment Offset containing the offset of the fragment,
+ in 8-octet units, relative to the start of the
+ Fragmentable Part of the original packet. The Fragment
+ Offset of the first ("leftmost") fragment is 0.
+
+ An M flag value of 0 if the fragment is the last
+ ("rightmost") one, else an M flag value of 1.
+
+ The Identification value generated for the original
+ packet.
+
+ (3) The fragment itself.
+
+ The lengths of the fragments must be chosen such that the resulting
+ fragment packets fit within the MTU of the path to the packets'
+ destination(s).
+
+
+
+Deering & Hinden Standards Track [Page 20]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ At the destination, fragment packets are reassembled into their
+ original, unfragmented form, as illustrated:
+
+ reassembled original packet:
+
+ +------------------+----------------------//------------------------+
+ | Unfragmentable | Fragmentable |
+ | Part | Part |
+ +------------------+----------------------//------------------------+
+
+ The following rules govern reassembly:
+
+ An original packet is reassembled only from fragment packets that
+ have the same Source Address, Destination Address, and Fragment
+ Identification.
+
+ The Unfragmentable Part of the reassembled packet consists of all
+ headers up to, but not including, the Fragment header of the first
+ fragment packet (that is, the packet whose Fragment Offset is
+ zero), with the following two changes:
+
+ The Next Header field of the last header of the Unfragmentable
+ Part is obtained from the Next Header field of the first
+ fragment's Fragment header.
+
+ The Payload Length of the reassembled packet is computed from
+ the length of the Unfragmentable Part and the length and offset
+ of the last fragment. For example, a formula for computing the
+ Payload Length of the reassembled original packet is:
+
+ PL.orig = PL.first - FL.first - 8 + (8 * FO.last) + FL.last
+
+ where
+ PL.orig = Payload Length field of reassembled packet.
+ PL.first = Payload Length field of first fragment packet.
+ FL.first = length of fragment following Fragment header of
+ first fragment packet.
+ FO.last = Fragment Offset field of Fragment header of
+ last fragment packet.
+ FL.last = length of fragment following Fragment header of
+ last fragment packet.
+
+ The Fragmentable Part of the reassembled packet is constructed
+ from the fragments following the Fragment headers in each of the
+ fragment packets. The length of each fragment is computed by
+ subtracting from the packet's Payload Length the length of the
+
+
+
+
+
+Deering & Hinden Standards Track [Page 21]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ headers between the IPv6 header and fragment itself; its relative
+ position in Fragmentable Part is computed from its Fragment Offset
+ value.
+
+ The Fragment header is not present in the final, reassembled
+ packet.
+
+ The following error conditions may arise when reassembling fragmented
+ packets:
+
+ If insufficient fragments are received to complete reassembly of a
+ packet within 60 seconds of the reception of the first-arriving
+ fragment of that packet, reassembly of that packet must be
+ abandoned and all the fragments that have been received for that
+ packet must be discarded. If the first fragment (i.e., the one
+ with a Fragment Offset of zero) has been received, an ICMP Time
+ Exceeded -- Fragment Reassembly Time Exceeded message should be
+ sent to the source of that fragment.
+
+ If the length of a fragment, as derived from the fragment packet's
+ Payload Length field, is not a multiple of 8 octets and the M flag
+ of that fragment is 1, then that fragment must be discarded and an
+ ICMP Parameter Problem, Code 0, message should be sent to the
+ source of the fragment, pointing to the Payload Length field of
+ the fragment packet.
+
+ If the length and offset of a fragment are such that the Payload
+ Length of the packet reassembled from that fragment would exceed
+ 65,535 octets, then that fragment must be discarded and an ICMP
+ Parameter Problem, Code 0, message should be sent to the source of
+ the fragment, pointing to the Fragment Offset field of the
+ fragment packet.
+
+ The following conditions are not expected to occur, but are not
+ considered errors if they do:
+
+ The number and content of the headers preceding the Fragment
+ header of different fragments of the same original packet may
+ differ. Whatever headers are present, preceding the Fragment
+ header in each fragment packet, are processed when the packets
+ arrive, prior to queueing the fragments for reassembly. Only
+ those headers in the Offset zero fragment packet are retained in
+ the reassembled packet.
+
+ The Next Header values in the Fragment headers of different
+ fragments of the same original packet may differ. Only the value
+ from the Offset zero fragment packet is used for reassembly.
+
+
+
+
+Deering & Hinden Standards Track [Page 22]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+4.6 Destination Options Header
+
+ The Destination Options header is used to carry optional information
+ that need be examined only by a packet's destination node(s). The
+ Destination Options header is identified by a Next Header value of 60
+ in the immediately preceding header, and has the following format:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
+ | |
+ . .
+ . Options .
+ . .
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Next Header 8-bit selector. Identifies the type of header
+ immediately following the Destination Options
+ header. Uses the same values as the IPv4
+ Protocol field [RFC-1700 et seq.].
+
+ Hdr Ext Len 8-bit unsigned integer. Length of the
+ Destination Options header in 8-octet units, not
+ including the first 8 octets.
+
+ Options Variable-length field, of length such that the
+ complete Destination Options header is an
+ integer multiple of 8 octets long. Contains one
+ or more TLV-encoded options, as described in
+ section 4.2.
+
+ The only destination options defined in this document are the Pad1
+ and PadN options specified in section 4.2.
+
+ Note that there are two possible ways to encode optional destination
+ information in an IPv6 packet: either as an option in the Destination
+ Options header, or as a separate extension header. The Fragment
+ header and the Authentication header are examples of the latter
+ approach. Which approach can be used depends on what action is
+ desired of a destination node that does not understand the optional
+ information:
+
+ o If the desired action is for the destination node to discard
+ the packet and, only if the packet's Destination Address is not
+ a multicast address, send an ICMP Unrecognized Type message to
+ the packet's Source Address, then the information may be
+ encoded either as a separate header or as an option in the
+
+
+
+Deering & Hinden Standards Track [Page 23]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ Destination Options header whose Option Type has the value 11
+ in its highest-order two bits. The choice may depend on such
+ factors as which takes fewer octets, or which yields better
+ alignment or more efficient parsing.
+
+ o If any other action is desired, the information must be encoded
+ as an option in the Destination Options header whose Option
+ Type has the value 00, 01, or 10 in its highest-order two bits,
+ specifying the desired action (see section 4.2).
+
+4.7 No Next Header
+
+ The value 59 in the Next Header field of an IPv6 header or any
+ extension header indicates that there is nothing following that
+ header. If the Payload Length field of the IPv6 header indicates the
+ presence of octets past the end of a header whose Next Header field
+ contains 59, those octets must be ignored, and passed on unchanged if
+ the packet is forwarded.
+
+5. Packet Size Issues
+
+ IPv6 requires that every link in the internet have an MTU of 1280
+ octets or greater. On any link that cannot convey a 1280-octet
+ packet in one piece, link-specific fragmentation and reassembly must
+ be provided at a layer below IPv6.
+
+ Links that have a configurable MTU (for example, PPP links [RFC-
+ 1661]) must be configured to have an MTU of at least 1280 octets; it
+ is recommended that they be configured with an MTU of 1500 octets or
+ greater, to accommodate possible encapsulations (i.e., tunneling)
+ without incurring IPv6-layer fragmentation.
+
+ From each link to which a node is directly attached, the node must be
+ able to accept packets as large as that link's MTU.
+
+ It is strongly recommended that IPv6 nodes implement Path MTU
+ Discovery [RFC-1981], in order to discover and take advantage of path
+ MTUs greater than 1280 octets. However, a minimal IPv6
+ implementation (e.g., in a boot ROM) may simply restrict itself to
+ sending packets no larger than 1280 octets, and omit implementation
+ of Path MTU Discovery.
+
+ In order to send a packet larger than a path's MTU, a node may use
+ the IPv6 Fragment header to fragment the packet at the source and
+ have it reassembled at the destination(s). However, the use of such
+ fragmentation is discouraged in any application that is able to
+ adjust its packets to fit the measured path MTU (i.e., down to 1280
+ octets).
+
+
+
+Deering & Hinden Standards Track [Page 24]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ A node must be able to accept a fragmented packet that, after
+ reassembly, is as large as 1500 octets. A node is permitted to
+ accept fragmented packets that reassemble to more than 1500 octets.
+ An upper-layer protocol or application that depends on IPv6
+ fragmentation to send packets larger than the MTU of a path should
+ not send packets larger than 1500 octets unless it has assurance that
+ the destination is capable of reassembling packets of that larger
+ size.
+
+ In response to an IPv6 packet that is sent to an IPv4 destination
+ (i.e., a packet that undergoes translation from IPv6 to IPv4), the
+ originating IPv6 node may receive an ICMP Packet Too Big message
+ reporting a Next-Hop MTU less than 1280. In that case, the IPv6 node
+ is not required to reduce the size of subsequent packets to less than
+ 1280, but must include a Fragment header in those packets so that the
+ IPv6-to-IPv4 translating router can obtain a suitable Identification
+ value to use in resulting IPv4 fragments. Note that this means the
+ payload may have to be reduced to 1232 octets (1280 minus 40 for the
+ IPv6 header and 8 for the Fragment header), and smaller still if
+ additional extension headers are used.
+
+6. Flow Labels
+
+ The 20-bit Flow Label field in the IPv6 header may be used by a
+ source to label sequences of packets for which it requests special
+ handling by the IPv6 routers, such as non-default quality of service
+ or "real-time" service. This aspect of IPv6 is, at the time of
+ writing, still experimental and subject to change as the requirements
+ for flow support in the Internet become clearer. Hosts or routers
+ that do not support the functions of the Flow Label field are
+ required to set the field to zero when originating a packet, pass the
+ field on unchanged when forwarding a packet, and ignore the field
+ when receiving a packet.
+
+ Appendix A describes the current intended semantics and usage of the
+ Flow Label field.
+
+7. Traffic Classes
+
+ The 8-bit Traffic Class field in the IPv6 header is available for use
+ by originating nodes and/or forwarding routers to identify and
+ distinguish between different classes or priorities of IPv6 packets.
+ At the point in time at which this specification is being written,
+ there are a number of experiments underway in the use of the IPv4
+ Type of Service and/or Precedence bits to provide various forms of
+ "differentiated service" for IP packets, other than through the use
+ of explicit flow set-up. The Traffic Class field in the IPv6 header
+ is intended to allow similar functionality to be supported in IPv6.
+
+
+
+Deering & Hinden Standards Track [Page 25]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ It is hoped that those experiments will eventually lead to agreement
+ on what sorts of traffic classifications are most useful for IP
+ packets. Detailed definitions of the syntax and semantics of all or
+ some of the IPv6 Traffic Class bits, whether experimental or intended
+ for eventual standardization, are to be provided in separate
+ documents.
+
+ The following general requirements apply to the Traffic Class field:
+
+ o The service interface to the IPv6 service within a node must
+ provide a means for an upper-layer protocol to supply the value
+ of the Traffic Class bits in packets originated by that upper-
+ layer protocol. The default value must be zero for all 8 bits.
+
+ o Nodes that support a specific (experimental or eventual
+ standard) use of some or all of the Traffic Class bits are
+ permitted to change the value of those bits in packets that
+ they originate, forward, or receive, as required for that
+ specific use. Nodes should ignore and leave unchanged any bits
+ of the Traffic Class field for which they do not support a
+ specific use.
+
+ o An upper-layer protocol must not assume that the value of the
+ Traffic Class bits in a received packet are the same as the
+ value sent by the packet's source.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 26]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+8. Upper-Layer Protocol Issues
+
+8.1 Upper-Layer Checksums
+
+ Any transport or other upper-layer protocol that includes the
+ addresses from the IP header in its checksum computation must be
+ modified for use over IPv6, to include the 128-bit IPv6 addresses
+ instead of 32-bit IPv4 addresses. In particular, the following
+ illustration shows the TCP and UDP "pseudo-header" for IPv6:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Source Address +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Destination Address +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Upper-Layer Packet Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | zero | Next Header |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ o If the IPv6 packet contains a Routing header, the Destination
+ Address used in the pseudo-header is that of the final
+ destination. At the originating node, that address will be in
+ the last element of the Routing header; at the recipient(s),
+ that address will be in the Destination Address field of the
+ IPv6 header.
+
+ o The Next Header value in the pseudo-header identifies the
+ upper-layer protocol (e.g., 6 for TCP, or 17 for UDP). It will
+ differ from the Next Header value in the IPv6 header if there
+ are extension headers between the IPv6 header and the upper-
+ layer header.
+
+ o The Upper-Layer Packet Length in the pseudo-header is the
+ length of the upper-layer header and data (e.g., TCP header
+ plus TCP data). Some upper-layer protocols carry their own
+
+
+
+Deering & Hinden Standards Track [Page 27]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ length information (e.g., the Length field in the UDP header);
+ for such protocols, that is the length used in the pseudo-
+ header. Other protocols (such as TCP) do not carry their own
+ length information, in which case the length used in the
+ pseudo-header is the Payload Length from the IPv6 header, minus
+ the length of any extension headers present between the IPv6
+ header and the upper-layer header.
+
+ o Unlike IPv4, when UDP packets are originated by an IPv6 node,
+ the UDP checksum is not optional. That is, whenever
+ originating a UDP packet, an IPv6 node must compute a UDP
+ checksum over the packet and the pseudo-header, and, if that
+ computation yields a result of zero, it must be changed to hex
+ FFFF for placement in the UDP header. IPv6 receivers must
+ discard UDP packets containing a zero checksum, and should log
+ the error.
+
+ The IPv6 version of ICMP [ICMPv6] includes the above pseudo-header in
+ its checksum computation; this is a change from the IPv4 version of
+ ICMP, which does not include a pseudo-header in its checksum. The
+ reason for the change is to protect ICMP from misdelivery or
+ corruption of those fields of the IPv6 header on which it depends,
+ which, unlike IPv4, are not covered by an internet-layer checksum.
+ The Next Header field in the pseudo-header for ICMP contains the
+ value 58, which identifies the IPv6 version of ICMP.
+
+8.2 Maximum Packet Lifetime
+
+ Unlike IPv4, IPv6 nodes are not required to enforce maximum packet
+ lifetime. That is the reason the IPv4 "Time to Live" field was
+ renamed "Hop Limit" in IPv6. In practice, very few, if any, IPv4
+ implementations conform to the requirement that they limit packet
+ lifetime, so this is not a change in practice. Any upper-layer
+ protocol that relies on the internet layer (whether IPv4 or IPv6) to
+ limit packet lifetime ought to be upgraded to provide its own
+ mechanisms for detecting and discarding obsolete packets.
+
+8.3 Maximum Upper-Layer Payload Size
+
+ When computing the maximum payload size available for upper-layer
+ data, an upper-layer protocol must take into account the larger size
+ of the IPv6 header relative to the IPv4 header. For example, in
+ IPv4, TCP's MSS option is computed as the maximum packet size (a
+ default value or a value learned through Path MTU Discovery) minus 40
+ octets (20 octets for the minimum-length IPv4 header and 20 octets
+ for the minimum-length TCP header). When using TCP over IPv6, the
+ MSS must be computed as the maximum packet size minus 60 octets,
+
+
+
+
+Deering & Hinden Standards Track [Page 28]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ because the minimum-length IPv6 header (i.e., an IPv6 header with no
+ extension headers) is 20 octets longer than a minimum-length IPv4
+ header.
+
+8.4 Responding to Packets Carrying Routing Headers
+
+ When an upper-layer protocol sends one or more packets in response to
+ a received packet that included a Routing header, the response
+ packet(s) must not include a Routing header that was automatically
+ derived by "reversing" the received Routing header UNLESS the
+ integrity and authenticity of the received Source Address and Routing
+ header have been verified (e.g., via the use of an Authentication
+ header in the received packet). In other words, only the following
+ kinds of packets are permitted in response to a received packet
+ bearing a Routing header:
+
+ o Response packets that do not carry Routing headers.
+
+ o Response packets that carry Routing headers that were NOT
+ derived by reversing the Routing header of the received packet
+ (for example, a Routing header supplied by local
+ configuration).
+
+ o Response packets that carry Routing headers that were derived
+ by reversing the Routing header of the received packet IF AND
+ ONLY IF the integrity and authenticity of the Source Address
+ and Routing header from the received packet have been verified
+ by the responder.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 29]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+Appendix A. Semantics and Usage of the Flow Label Field
+
+ A flow is a sequence of packets sent from a particular source to a
+ particular (unicast or multicast) destination for which the source
+ desires special handling by the intervening routers. The nature of
+ that special handling might be conveyed to the routers by a control
+ protocol, such as a resource reservation protocol, or by information
+ within the flow's packets themselves, e.g., in a hop-by-hop option.
+ The details of such control protocols or options are beyond the scope
+ of this document.
+
+ There may be multiple active flows from a source to a destination, as
+ well as traffic that is not associated with any flow. A flow is
+ uniquely identified by the combination of a source address and a
+ non-zero flow label. Packets that do not belong to a flow carry a
+ flow label of zero.
+
+ A flow label is assigned to a flow by the flow's source node. New
+ flow labels must be chosen (pseudo-)randomly and uniformly from the
+ range 1 to FFFFF hex. The purpose of the random allocation is to
+ make any set of bits within the Flow Label field suitable for use as
+ a hash key by routers, for looking up the state associated with the
+ flow.
+
+ All packets belonging to the same flow must be sent with the same
+ source address, destination address, and flow label. If any of those
+ packets includes a Hop-by-Hop Options header, then they all must be
+ originated with the same Hop-by-Hop Options header contents
+ (excluding the Next Header field of the Hop-by-Hop Options header).
+ If any of those packets includes a Routing header, then they all must
+ be originated with the same contents in all extension headers up to
+ and including the Routing header (excluding the Next Header field in
+ the Routing header). The routers or destinations are permitted, but
+ not required, to verify that these conditions are satisfied. If a
+ violation is detected, it should be reported to the source by an ICMP
+ Parameter Problem message, Code 0, pointing to the high-order octet
+ of the Flow Label field (i.e., offset 1 within the IPv6 packet).
+
+ The maximum lifetime of any flow-handling state established along a
+ flow's path must be specified as part of the description of the
+ state-establishment mechanism, e.g., the resource reservation
+ protocol or the flow-setup hop-by-hop option. A source must not re-
+ use a flow label for a new flow within the maximum lifetime of any
+ flow-handling state that might have been established for the prior
+ use of that flow label.
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 30]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ When a node stops and restarts (e.g., as a result of a "crash"), it
+ must be careful not to use a flow label that it might have used for
+ an earlier flow whose lifetime may not have expired yet. This may be
+ accomplished by recording flow label usage on stable storage so that
+ it can be remembered across crashes, or by refraining from using any
+ flow labels until the maximum lifetime of any possible previously
+ established flows has expired. If the minimum time for rebooting the
+ node is known, that time can be deducted from the necessary waiting
+ period before starting to allocate flow labels.
+
+ There is no requirement that all, or even most, packets belong to
+ flows, i.e., carry non-zero flow labels. This observation is placed
+ here to remind protocol designers and implementors not to assume
+ otherwise. For example, it would be unwise to design a router whose
+ performance would be adequate only if most packets belonged to flows,
+ or to design a header compression scheme that only worked on packets
+ that belonged to flows.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 31]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+Appendix B. Formatting Guidelines for Options
+
+ This appendix gives some advice on how to lay out the fields when
+ designing new options to be used in the Hop-by-Hop Options header or
+ the Destination Options header, as described in section 4.2. These
+ guidelines are based on the following assumptions:
+
+ o One desirable feature is that any multi-octet fields within the
+ Option Data area of an option be aligned on their natural
+ boundaries, i.e., fields of width n octets should be placed at
+ an integer multiple of n octets from the start of the Hop-by-
+ Hop or Destination Options header, for n = 1, 2, 4, or 8.
+
+ o Another desirable feature is that the Hop-by-Hop or Destination
+ Options header take up as little space as possible, subject to
+ the requirement that the header be an integer multiple of 8
+ octets long.
+
+ o It may be assumed that, when either of the option-bearing
+ headers are present, they carry a very small number of options,
+ usually only one.
+
+ These assumptions suggest the following approach to laying out the
+ fields of an option: order the fields from smallest to largest, with
+ no interior padding, then derive the alignment requirement for the
+ entire option based on the alignment requirement of the largest field
+ (up to a maximum alignment of 8 octets). This approach is
+ illustrated in the following examples:
+
+ Example 1
+
+ If an option X required two data fields, one of length 8 octets and
+ one of length 4 octets, it would be laid out as follows:
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Option Type=X |Opt Data Len=12|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + 8-octet field +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 32]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ Its alignment requirement is 8n+2, to ensure that the 8-octet field
+ starts at a multiple-of-8 offset from the start of the enclosing
+ header. A complete Hop-by-Hop or Destination Options header
+ containing this one option would look as follows:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len=1 | Option Type=X |Opt Data Len=12|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + 8-octet field +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Example 2
+
+ If an option Y required three data fields, one of length 4 octets,
+ one of length 2 octets, and one of length 1 octet, it would be laid
+ out as follows:
+
+ +-+-+-+-+-+-+-+-+
+ | Option Type=Y |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Opt Data Len=7 | 1-octet field | 2-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Its alignment requirement is 4n+3, to ensure that the 4-octet field
+ starts at a multiple-of-4 offset from the start of the enclosing
+ header. A complete Hop-by-Hop or Destination Options header
+ containing this one option would look as follows:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len=1 | Pad1 Option=0 | Option Type=Y |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Opt Data Len=7 | 1-octet field | 2-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | PadN Option=1 |Opt Data Len=2 | 0 | 0 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 33]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ Example 3
+
+ A Hop-by-Hop or Destination Options header containing both options X
+ and Y from Examples 1 and 2 would have one of the two following
+ formats, depending on which option appeared first:
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len=3 | Option Type=X |Opt Data Len=12|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + 8-octet field +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | PadN Option=1 |Opt Data Len=1 | 0 | Option Type=Y |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Opt Data Len=7 | 1-octet field | 2-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | PadN Option=1 |Opt Data Len=2 | 0 | 0 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Header | Hdr Ext Len=3 | Pad1 Option=0 | Option Type=Y |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Opt Data Len=7 | 1-octet field | 2-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | PadN Option=1 |Opt Data Len=4 | 0 | 0 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 0 | 0 | Option Type=X |Opt Data Len=12|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 4-octet field |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + 8-octet field +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 34]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+Security Considerations
+
+ The security features of IPv6 are described in the Security
+ Architecture for the Internet Protocol [RFC-2401].
+
+Acknowledgments
+
+ The authors gratefully acknowledge the many helpful suggestions of
+ the members of the IPng working group, the End-to-End Protocols
+ research group, and the Internet Community At Large.
+
+Authors' Addresses
+
+ Stephen E. Deering
+ Cisco Systems, Inc.
+ 170 West Tasman Drive
+ San Jose, CA 95134-1706
+ USA
+
+ Phone: +1 408 527 8213
+ Fax: +1 408 527 8254
+ EMail: deering at cisco.com
+
+
+ Robert M. Hinden
+ Nokia
+ 232 Java Drive
+ Sunnyvale, CA 94089
+ USA
+
+ Phone: +1 408 990-2004
+ Fax: +1 408 743-5677
+ EMail: hinden at iprg.nokia.com
+
+References
+
+ [RFC-2401] Kent, S. and R. Atkinson, "Security Architecture for the
+ Internet Protocol", RFC 2401, November 1998.
+
+ [RFC-2402] Kent, S. and R. Atkinson, "IP Authentication Header",
+ RFC 2402, November 1998.
+
+ [RFC-2406] Kent, S. and R. Atkinson, "IP Encapsulating Security
+ Protocol (ESP)", RFC 2406, November 1998.
+
+ [ICMPv6] Conta, A. and S. Deering, "ICMP for the Internet
+ Protocol Version 6 (IPv6)", RFC 2463, December 1998.
+
+
+
+
+Deering & Hinden Standards Track [Page 35]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ [ADDRARCH] Hinden, R. and S. Deering, "IP Version 6 Addressing
+ Architecture", RFC 2373, July 1998.
+
+ [RFC-1981] McCann, J., Mogul, J. and S. Deering, "Path MTU
+ Discovery for IP version 6", RFC 1981, August 1996.
+
+ [RFC-791] Postel, J., "Internet Protocol", STD 5, RFC 791,
+ September 1981.
+
+ [RFC-1700] Reynolds, J. and J. Postel, "Assigned Numbers", STD 2,
+ RFC 1700, October 1994. See also:
+ http://www.iana.org/numbers.html
+
+ [RFC-1661] Simpson, W., "The Point-to-Point Protocol (PPP)", STD
+ 51, RFC 1661, July 1994.
+
+CHANGES SINCE RFC-1883
+
+ This memo has the following changes from RFC-1883. Numbers identify
+ the Internet-Draft version in which the change was made.
+
+ 02) Removed all references to jumbograms and the Jumbo Payload
+ option (moved to a separate document).
+
+ 02) Moved most of Flow Label description from section 6 to (new)
+ Appendix A.
+
+ 02) In Flow Label description, now in Appendix A, corrected maximum
+ Flow Label value from FFFFFF to FFFFF (i.e., one less "F") due
+ to reduction of size of Flow Label field from 24 bits to 20
+ bits.
+
+ 02) Renumbered (relettered?) the previous Appendix A to be Appendix
+ B.
+
+ 02) Changed the wording of the Security Considerations section to
+ avoid dependency loop between this spec and the IPsec specs.
+
+ 02) Updated R. Hinden's email address and company affiliation.
+
+
+ --------------------------------------------------------
+
+ 01) In section 3, changed field name "Class" to "Traffic Class" and
+ increased its size from 4 to 8 bits. Decreased size of Flow
+ Label field from 24 to 20 bits to compensate for increase in
+ Traffic Class field.
+
+
+
+
+Deering & Hinden Standards Track [Page 36]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ 01) In section 4.1, restored the order of the Authentication Header
+ and the ESP header, which were mistakenly swapped in the 00
+ version of this memo.
+
+ 01) In section 4.4, deleted the Strict/Loose Bit Map field and the
+ strict routing functionality from the Type 0 Routing header, and
+ removed the restriction on number of addresses that may be
+ carried in the Type 0 Routing header (was limited to 23
+ addresses, because of the size of the strict/loose bit map).
+
+ 01) In section 5, changed the minimum IPv6 MTU from 576 to 1280
+ octets, and added a recommendation that links with configurable
+ MTU (e.g., PPP links) be configured to have an MTU of at least
+ 1500 octets.
+
+ 01) In section 5, deleted the requirement that a node must not send
+ fragmented packets that reassemble to more than 1500 octets
+ without knowledge of the destination reassembly buffer size, and
+ replaced it with a recommendation that upper-layer protocols or
+ applications should not do that.
+
+ 01) Replaced reference to the IPv4 Path MTU Discovery spec (RFC-
+ 1191) with reference to the IPv6 Path MTU Discovery spec (RFC-
+ 1981), and deleted the Notes at the end of section 5 regarding
+ Path MTU Discovery, since those details are now covered by RFC-
+ 1981.
+
+ 01) In section 6, deleted specification of "opportunistic" flow
+ set-up, and removed all references to the 6-second maximum
+ lifetime for opportunistically established flow state.
+
+ 01) In section 7, deleted the provisional description of the
+ internal structure and semantics of the Traffic Class field, and
+ specified that such descriptions be provided in separate
+ documents.
+
+ --------------------------------------------------------
+
+ 00) In section 4, corrected the Code value to indicate "unrecognized
+ Next Header type encountered" in an ICMP Parameter Problem
+ message (changed from 2 to 1).
+
+ 00) In the description of the Payload Length field in section 3, and
+ of the Jumbo Payload Length field in section 4.3, made it
+ clearer that extension headers are included in the payload
+ length count.
+
+
+
+
+
+Deering & Hinden Standards Track [Page 37]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+ 00) In section 4.1, swapped the order of the Authentication header
+ and the ESP header. (NOTE: this was a mistake, and the change
+ was undone in version 01.)
+
+ 00) In section 4.2, made it clearer that options are identified by
+ the full 8-bit Option Type, not by the low-order 5 bits of an
+ Option Type. Also specified that the same Option Type numbering
+ space is used for both Hop-by-Hop Options and Destination
+ Options headers.
+
+ 00) In section 4.4, added a sentence requiring that nodes processing
+ a Routing header must send an ICMP Packet Too Big message in
+ response to a packet that is too big to fit in the next hop link
+ (rather than, say, performing fragmentation).
+
+ 00) Changed the name of the IPv6 Priority field to "Class", and
+ replaced the previous description of Priority in section 7 with
+ a description of the Class field. Also, excluded this field
+ from the set of fields that must remain the same for all packets
+ in the same flow, as specified in section 6.
+
+ 00) In the pseudo-header in section 8.1, changed the name of the
+ "Payload Length" field to "Upper-Layer Packet Length". Also
+ clarified that, in the case of protocols that carry their own
+ length info (like non-jumbogram UDP), it is the upper-layer-
+ derived length, not the IP-layer-derived length, that is used in
+ the pseudo-header.
+
+ 00) Added section 8.4, specifying that upper-layer protocols, when
+ responding to a received packet that carried a Routing header,
+ must not include the reverse of the Routing header in the
+ response packet(s) unless the received Routing header was
+ authenticated.
+
+ 00) Fixed some typos and grammatical errors.
+
+ 00) Authors' contact info updated.
+
+ --------------------------------------------------------
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 38]
+
+RFC 2460 IPv6 Specification December 1998
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (1998). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Deering & Hinden Standards Track [Page 39]
+
diff --git a/build-system/builder/src/test/resources/testData/packaging/text-files/wikipedia.html b/build-system/builder/src/test/resources/testData/packaging/text-files/wikipedia.html
new file mode 100644
index 0000000..2c3726f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/packaging/text-files/wikipedia.html
@@ -0,0 +1,2578 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr" class="client-nojs">
+<head>
+<meta charset="UTF-8" />
+<title>Wikipedia - Wikipedia, the free encyclopedia</title>
+<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>
+<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {
+mw.config.set({"wgCanonicalNamespace":"","wgCanonicalSpecialPageName":false,"wgNamespaceNumber":0,"wgPageName":"Wikipedia","wgTitle":"Wikipedia","wgCurRevisionId":693133248,"wgRevisionId":693133248,"wgArticleId":5043734,"wgIsArticle":true,"wgIsRedirect":false,"wgAction":"view","wgUserName":null,"wgUserGroups":["*"],"wgCategories":["Pages containing links to subscription-only content","All articles with dead external links","Articles with dead external links from October 2015","CS1 German [...]
+mw.user.tokens.set({"editToken":"+\\","patrolToken":"+\\","watchToken":"+\\"}); /* @nomin */ ;
+
+});mw.loader.load(["mw.MediaWikiPlayer.loader","mw.PopUpMediaTransform","mw.TMHGalleryHook.js","mediawiki.page.startup","mediawiki.legacy.wikibits","ext.centralauth.centralautologin","ext.gadget.WatchlistBase","ext.gadget.WatchlistGreenIndicators","mmv.head","ext.visualEditor.desktopArticleTarget.init","ext.uls.init","ext.uls.interface","ext.quicksurveys.init","ext.centralNotice.bannerController","skins.vector.js"]);
+} );</script>
+<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=ext.cite.styles%7Cext.gadget.DRN-wizard%2CReferenceTooltips%2CWatchlistBase%2CWatchlistGreenIndicators%2Ccharinsert%2Cfeatured-articles-links%2CrefToolbar%2Cswitcher%2Cteahouse%7Cext.tmh.thumbnail.styles%7Cext.uls.nojs%7Cext.visualEditor.desktopArticleTarget.noscript%7Cext.wikimediaBadges%7Cmediawiki.legacy.commonPrint%2Cshared%7Cmediawiki.raggett%2CsectionAnchor%7Cmediawiki.skinning.interface%7Cskins.vector.sty [...]
+<meta name="ResourceLoaderDynamicStyles" content="" />
+<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=site&only=styles&skin=vector" />
+<style>a:lang(ar),a:lang(kk-arab),a:lang(mzn),a:lang(ps),a:lang(ur){text-decoration:none}</style>
+<script async="" src="/w/load.php?debug=false&lang=en&modules=startup&only=scripts&skin=vector"></script>
+<meta name="generator" content="MediaWiki 1.27.0-wmf.7" />
+<link rel="alternate" href="android-app://org.wikipedia/http/en.m.wikipedia.org/wiki/Wikipedia" />
+<link rel="apple-touch-icon" href="/static/apple-touch/wikipedia.png" />
+<link rel="shortcut icon" href="/static/favicon/wikipedia.ico" />
+<link rel="search" type="application/opensearchdescription+xml" href="/w/opensearch_desc.php" title="Wikipedia (en)" />
+<link rel="EditURI" type="application/rsd+xml" href="//en.wikipedia.org/w/api.php?action=rsd" />
+<link rel="copyright" href="//creativecommons.org/licenses/by-sa/3.0/" />
+<link rel="alternate" type="application/atom+xml" title="Wikipedia Atom feed" href="/w/index.php?title=Special:RecentChanges&feed=atom" />
+<link rel="canonical" href="https://en.wikipedia.org/wiki/Wikipedia" />
+<link rel="dns-prefetch" href="//meta.wikimedia.org" />
+<!--[if lt IE 7]><style type="text/css">body{behavior:url("/w/static/1.27.0-wmf.7/skins/Vector/csshover.min.htc")}</style><![endif]-->
+</head>
+<body class="mediawiki ltr sitedir-ltr ns-0 ns-subject page-Wikipedia skin-vector action-view">
+ <div id="mw-page-base" class="noprint"></div>
+ <div id="mw-head-base" class="noprint"></div>
+ <div id="content" class="mw-body" role="main">
+ <a id="top"></a>
+
+ <div id="siteNotice"><!-- CentralNotice --></div>
+ <div class="mw-indicators">
+<div id="mw-indicator-pp-default" class="mw-indicator"><a href="/wiki/Wikipedia:Protection_policy#semi" title="This article is semi-protected due to vandalism"><img alt="Page semi-protected" src="//upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Padlock-silver.svg/20px-Padlock-silver.svg.png" width="20" height="20" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Padlock-silver.svg/30px-Padlock-silver.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Padlock- [...]
+</div>
+ <h1 id="firstHeading" class="firstHeading" lang="en">Wikipedia</h1>
+ <div id="bodyContent" class="mw-body-content">
+ <div id="siteSub">From Wikipedia, the free encyclopedia</div>
+ <div id="contentSub"></div>
+ <div id="jump-to-nav" class="mw-jump">
+ Jump to: <a href="#mw-head">navigation</a>, <a href="#p-search">search</a>
+ </div>
+ <div id="mw-content-text" lang="en" dir="ltr" class="mw-content-ltr"><div class="hatnote">This article is about the Internet encyclopedia. For other uses, see <a href="/wiki/Wikipedia_(disambiguation)" title="Wikipedia (disambiguation)" class="mw-disambig">Wikipedia (disambiguation)</a>.</div>
+<div class="hatnote"><span class="plainlinks selfreference">For Wikipedia's non-encyclopedic visitor introduction, see <a href="/wiki/Wikipedia:About" title="Wikipedia:About">Wikipedia:About</a>.</span></div>
+<div class="hatnote">For the Wikipedia home page, see <a href="/wiki/Wikipedia:Main_Page" title="Wikipedia:Main Page" class="mw-redirect">Wikipedia:Main Page</a>.</div>
+<table class="infobox" style="width:22em">
+<caption>Wikipedia</caption>
+<tr>
+<td colspan="2" style="text-align:center"><a href="/wiki/File:Wikipedia-logo-v2.svg" class="image"><img alt="A white sphere made of large jigsaw pieces, with letters from several alphabets shown on the pieces" src="//upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/150px-Wikipedia-logo-v2.svg.png" width="150" height="137" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/225px-Wikipedia-logo-v2.svg.png 1.5x, //upload.wikimedia.org/wi [...]
+<a href="/wiki/File:Wikipedia_wordmark.svg" class="image" title="Wikipedia wordmark"><img alt="Wikipedia wordmark" src="//upload.wikimedia.org/wikipedia/commons/thumb/b/bb/Wikipedia_wordmark.svg/150px-Wikipedia_wordmark.svg.png" width="150" height="26" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/b/bb/Wikipedia_wordmark.svg/225px-Wikipedia_wordmark.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/b/bb/Wikipedia_wordmark.svg/300px-Wikipedia_wordmark.svg.png 2x" data- [...]
+<div>The <a href="/wiki/Logo_of_Wikipedia" title="Logo of Wikipedia" class="mw-redirect">logo of Wikipedia</a>, a globe featuring <a href="/wiki/Glyph" title="Glyph">glyphs</a> from several <a href="/wiki/Writing_system" title="Writing system">writing systems</a>, for the letter <a href="/wiki/W" title="W">W</a> or sounds "wi", "wo" or "wa"</div>
+</td>
+</tr>
+<tr>
+<td colspan="2" style="text-align:center">
+<div class="NavFrame collapsed" style="border:none;">
+<div class="NavHead" style="background:transparent;text-align:left;background:gainsboro;text-align:center;">Screenshot </div>
+<div class="NavContent" style="background:transparent;text-align:left;text-align:center;"><a href="/wiki/File:Wikipedia_Main_Page.png" class="image"><img alt="Main page of the English Wikipedia" src="//upload.wikimedia.org/wikipedia/commons/thumb/8/87/Wikipedia_Main_Page.png/300px-Wikipedia_Main_Page.png" width="300" height="502" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/8/87/Wikipedia_Main_Page.png/450px-Wikipedia_Main_Page.png 1.5x, //upload.wikimedia.o [...]
+<div><a href="/wiki/Main_Page" title="Main Page">Main Page</a> of the English Wikipedia</div>
+</div>
+</div>
+</td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;"><a href="/wiki/Uniform_resource_locator" title="Uniform resource locator" class="mw-redirect">Web address</a></th>
+<td><span class="url"><a class="external text" href="https://www.wikipedia.org">wikipedia<wbr />.org</a></span></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;"><a href="/wiki/Slogan" title="Slogan">Slogan</a></th>
+<td>The free encyclopedia that anyone can edit</td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Commercial?</th>
+<td>No</td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">
+<div style="padding:0.1em 0;line-height:1.2em;">Type of site</div>
+</th>
+<td><a href="/wiki/Internet_encyclopedia" title="Internet encyclopedia" class="mw-redirect">Internet encyclopedia</a></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Registration</th>
+<td>Optional<sup id="cite_ref-1" class="reference"><a href="#cite_note-1"><span>[</span>notes 1<span>]</span></a></sup></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Available in</th>
+<td>288 editions<sup id="cite_ref-2" class="reference"><a href="#cite_note-2"><span>[</span>1<span>]</span></a></sup></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;"><a href="/wiki/Registered_user" title="Registered user">Users</a></th>
+<td><a href="/wiki/Special:Statistics" title="Special:Statistics">127,505</a> active editors<sup id="cite_ref-3" class="reference"><a href="#cite_note-3"><span>[</span>notes 2<span>]</span></a></sup> and <a href="/wiki/Special:Statistics" title="Special:Statistics">26,886,656</a> registered editors</td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">
+<div style="padding:0.1em 0;line-height:1.2em;">Content license</div>
+</th>
+<td><span class="nowrap"><a href="/wiki/Creative_Commons_licenses" title="Creative Commons licenses" class="mw-redirect">CC Attribution / Share-Alike</a> 3.0</span><br />
+<small>Most text is also dual-licensed under <a href="/wiki/GFDL" title="GFDL" class="mw-redirect">GFDL</a>; media licensing varies</small></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Written in</th>
+<td><a href="/wiki/Linux" title="Linux">L</a> <a href="/wiki/Apache_HTTP_Server" title="Apache HTTP Server">A</a> <a href="/wiki/MySQL" title="MySQL">M</a> <a href="/wiki/PHP" title="PHP">P</a> platform<sup id="cite_ref-roadchap_4-0" class="reference"><a href="#cite_note-roadchap-4"><span>[</span>2<span>]</span></a></sup></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Owner</th>
+<td><a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Created by</th>
+<td><a href="/wiki/Jimmy_Wales" title="Jimmy Wales">Jimmy Wales</a>, <a href="/wiki/Larry_Sanger" title="Larry Sanger">Larry Sanger</a><sup id="cite_ref-Sidener_5-0" class="reference"><a href="#cite_note-Sidener-5"><span>[</span>3<span>]</span></a></sup></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Launched</th>
+<td>January 15, 2001<span class="noprint">; 14 years ago</span><span style="display:none"> (<span class="bday dtstart published updated">2001-01-15</span>)</span></td>
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">
+<div style="padding:0.1em 0;line-height:1.2em;"><a href="/wiki/Alexa_Internet" title="Alexa Internet">Alexa</a> rank</div>
+</th>
+<td><span title="Decrease"><img alt="Decrease" src="//upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Decrease2.svg/11px-Decrease2.svg.png" width="11" height="11" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Decrease2.svg/17px-Decrease2.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Decrease2.svg/22px-Decrease2.svg.png 2x" data-file-width="300" data-file-height="300" /></span> 7<sup id="cite_ref-Alexa_siteinfo_6-0" class="reference"><a href="#cite_note [...]
+</tr>
+<tr>
+<th scope="row" style="padding-right:0.65em;">Current status</th>
+<td>Active</td>
+</tr>
+</table>
+<p><b>Wikipedia</b> (<span class="nowrap"><span class="noexcerpt"><a href="//upload.wikimedia.org/wikipedia/commons/c/c5/En-uk-Wikipedia.ogg" title="Listen"><img alt="Listen" src="//upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Speakerlink-new.svg/11px-Speakerlink-new.svg.png" width="11" height="11" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Speakerlink-new.svg/17px-Speakerlink-new.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Speakerlink-new.svg/ [...]
+<p><a href="/wiki/Jimmy_Wales" title="Jimmy Wales">Jimmy Wales</a> and <a href="/wiki/Larry_Sanger" title="Larry Sanger">Larry Sanger</a> launched Wikipedia on January 15, 2001. Sanger<sup id="cite_ref-MiliardWho_11-0" class="reference"><a href="#cite_note-MiliardWho-11"><span>[</span>9<span>]</span></a></sup> coined <a href="//en.wiktionary.org/wiki/Wikipedia" class="extiw" title="wikt:Wikipedia">its name</a>,<sup id="cite_ref-J_Sidener_12-0" class="reference"><a href="#cite_note-J_Side [...]
+<p>Supporters of Wikipedia cite a 2005 survey of Wikipedia published in <i><a href="/wiki/Nature_(journal)" title="Nature (journal)">Nature</a></i> based on a comparison of 42 science articles with <i><a href="/wiki/Encyclop%C3%A6dia_Britannica" title="Encyclopædia Britannica">Encyclopædia Britannica</a></i>, which found that Wikipedia's level of accuracy approached <i>Encyclopedia Britannica</i> 's.<sup id="cite_ref-GilesJ2005Internet_17-0" class="reference"><a href="#cite_note-GilesJ [...]
+<div class="toclimit-3">
+<div id="toc" class="toc">
+<div id="toctitle">
+<h2>Contents</h2>
+</div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#History"><span class="tocnumber">1</span> <span class="toctext">History</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Openness"><span class="tocnumber">2</span> <span class="toctext">Openness</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Restrictions"><span class="tocnumber">2.1</span> <span class="toctext">Restrictions</span></a></li>
+<li class="toclevel-2 tocsection-4"><a href="#Review_of_changes"><span class="tocnumber">2.2</span> <span class="toctext">Review of changes</span></a></li>
+<li class="toclevel-2 tocsection-5"><a href="#Vandalism"><span class="tocnumber">2.3</span> <span class="toctext">Vandalism</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-6"><a href="#Policies_and_laws"><span class="tocnumber">3</span> <span class="toctext"><span>Policies and laws</span></span></a>
+<ul>
+<li class="toclevel-2 tocsection-7"><a href="#Content_policies_and_guidelines"><span class="tocnumber">3.1</span> <span class="toctext">Content policies and guidelines</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-8"><a href="#Governance"><span class="tocnumber">4</span> <span class="toctext">Governance</span></a>
+<ul>
+<li class="toclevel-2 tocsection-9"><a href="#Administrators"><span class="tocnumber">4.1</span> <span class="toctext">Administrators</span></a></li>
+<li class="toclevel-2 tocsection-10"><a href="#Dispute_resolution"><span class="tocnumber">4.2</span> <span class="toctext">Dispute resolution</span></a>
+<ul>
+<li class="toclevel-3 tocsection-11"><a href="#Arbitration_Committee"><span class="tocnumber">4.2.1</span> <span class="toctext">Arbitration Committee</span></a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-12"><a href="#Community"><span class="tocnumber">5</span> <span class="toctext">Community</span></a>
+<ul>
+<li class="toclevel-2 tocsection-13"><a href="#Diversity"><span class="tocnumber">5.1</span> <span class="toctext">Diversity</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-14"><a href="#Language_editions"><span class="tocnumber">6</span> <span class="toctext">Language editions</span></a></li>
+<li class="toclevel-1 tocsection-15"><a href="#Critical_reception"><span class="tocnumber">7</span> <span class="toctext">Critical reception</span></a>
+<ul>
+<li class="toclevel-2 tocsection-16"><a href="#Accuracy_of_content"><span class="tocnumber">7.1</span> <span class="toctext">Accuracy of content</span></a>
+<ul>
+<li class="toclevel-3 tocsection-17"><a href="#Medical_information"><span class="tocnumber">7.1.1</span> <span class="toctext">Medical information</span></a></li>
+</ul>
+</li>
+<li class="toclevel-2 tocsection-18"><a href="#Quality_of_writing"><span class="tocnumber">7.2</span> <span class="toctext">Quality of writing</span></a></li>
+<li class="toclevel-2 tocsection-19"><a href="#Coverage_of_topics_and_systemic_bias"><span class="tocnumber">7.3</span> <span class="toctext">Coverage of topics and systemic bias</span></a>
+<ul>
+<li class="toclevel-3 tocsection-20"><a href="#Coverage_of_topics_and_selection_bias"><span class="tocnumber">7.3.1</span> <span class="toctext">Coverage of topics and selection bias</span></a></li>
+<li class="toclevel-3 tocsection-21"><a href="#Systemic_bias"><span class="tocnumber">7.3.2</span> <span class="toctext">Systemic bias</span></a></li>
+</ul>
+</li>
+<li class="toclevel-2 tocsection-22"><a href="#Explicit_content"><span class="tocnumber">7.4</span> <span class="toctext">Explicit content</span></a></li>
+<li class="toclevel-2 tocsection-23"><a href="#Privacy"><span class="tocnumber">7.5</span> <span class="toctext">Privacy</span></a></li>
+<li class="toclevel-2 tocsection-24"><a href="#Sexism"><span class="tocnumber">7.6</span> <span class="toctext">Sexism</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-25"><a href="#Operation"><span class="tocnumber">8</span> <span class="toctext">Operation</span></a>
+<ul>
+<li class="toclevel-2 tocsection-26"><a href="#Wikimedia_Foundation_and_the_Wikimedia_chapters"><span class="tocnumber">8.1</span> <span class="toctext">Wikimedia Foundation and the Wikimedia chapters</span></a></li>
+<li class="toclevel-2 tocsection-27"><a href="#Software_operations_and_support"><span class="tocnumber">8.2</span> <span class="toctext">Software operations and support</span></a></li>
+<li class="toclevel-2 tocsection-28"><a href="#Automated_editing"><span class="tocnumber">8.3</span> <span class="toctext">Automated editing</span></a></li>
+<li class="toclevel-2 tocsection-29"><a href="#Wikiprojects.2C_and_assessments_of_articles.27_importance_and_quality"><span class="tocnumber">8.4</span> <span class="toctext">Wikiprojects, and assessments of articles' importance and quality</span></a></li>
+<li class="toclevel-2 tocsection-30"><a href="#Hardware_operations_and_support"><span class="tocnumber">8.5</span> <span class="toctext">Hardware operations and support</span></a></li>
+<li class="toclevel-2 tocsection-31"><a href="#Internal_research_and_operational_development"><span class="tocnumber">8.6</span> <span class="toctext">Internal research and operational development</span></a></li>
+<li class="toclevel-2 tocsection-32"><a href="#Internal_news_publications"><span class="tocnumber">8.7</span> <span class="toctext">Internal news publications</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-33"><a href="#Access_to_content"><span class="tocnumber">9</span> <span class="toctext">Access to content</span></a>
+<ul>
+<li class="toclevel-2 tocsection-34"><a href="#Content_licensing"><span class="tocnumber">9.1</span> <span class="toctext">Content licensing</span></a></li>
+<li class="toclevel-2 tocsection-35"><a href="#Methods_of_access"><span class="tocnumber">9.2</span> <span class="toctext">Methods of access</span></a>
+<ul>
+<li class="toclevel-3 tocsection-36"><a href="#Mobile_access"><span class="tocnumber">9.2.1</span> <span class="toctext">Mobile access</span></a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-37"><a href="#Impact"><span class="tocnumber">10</span> <span class="toctext">Impact</span></a>
+<ul>
+<li class="toclevel-2 tocsection-38"><a href="#Readership"><span class="tocnumber">10.1</span> <span class="toctext">Readership</span></a></li>
+<li class="toclevel-2 tocsection-39"><a href="#Cultural_significance"><span class="tocnumber">10.2</span> <span class="toctext">Cultural significance</span></a>
+<ul>
+<li class="toclevel-3 tocsection-40"><a href="#Awards"><span class="tocnumber">10.2.1</span> <span class="toctext">Awards</span></a></li>
+<li class="toclevel-3 tocsection-41"><a href="#Satire"><span class="tocnumber">10.2.2</span> <span class="toctext">Satire</span></a></li>
+</ul>
+</li>
+<li class="toclevel-2 tocsection-42"><a href="#Sister_projects_.E2.80.93_Wikimedia"><span class="tocnumber">10.3</span> <span class="toctext">Sister projects – Wikimedia</span></a></li>
+<li class="toclevel-2 tocsection-43"><a href="#Publishing"><span class="tocnumber">10.4</span> <span class="toctext">Publishing</span></a></li>
+<li class="toclevel-2 tocsection-44"><a href="#Scientific_use"><span class="tocnumber">10.5</span> <span class="toctext">Scientific use</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-45"><a href="#Related_projects"><span class="tocnumber">11</span> <span class="toctext">Related projects</span></a></li>
+<li class="toclevel-1 tocsection-46"><a href="#See_also"><span class="tocnumber">12</span> <span class="toctext">See also</span></a></li>
+<li class="toclevel-1 tocsection-47"><a href="#References"><span class="tocnumber">13</span> <span class="toctext">References</span></a>
+<ul>
+<li class="toclevel-2 tocsection-48"><a href="#Notes"><span class="tocnumber">13.1</span> <span class="toctext">Notes</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-49"><a href="#Further_reading"><span class="tocnumber">14</span> <span class="toctext">Further reading</span></a>
+<ul>
+<li class="toclevel-2 tocsection-50"><a href="#Academic_studies"><span class="tocnumber">14.1</span> <span class="toctext">Academic studies</span></a></li>
+<li class="toclevel-2 tocsection-51"><a href="#Books"><span class="tocnumber">14.2</span> <span class="toctext">Books</span></a></li>
+<li class="toclevel-2 tocsection-52"><a href="#Book_reviews_and_other_articles"><span class="tocnumber">14.3</span> <span class="toctext">Book reviews and other articles</span></a>
+<ul>
+<li class="toclevel-3 tocsection-53"><a href="#Learning_resources"><span class="tocnumber">14.3.1</span> <span class="toctext">Learning resources</span></a></li>
+<li class="toclevel-3 tocsection-54"><a href="#Other_media_coverage"><span class="tocnumber">14.3.2</span> <span class="toctext">Other media coverage</span></a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-55"><a href="#External_links"><span class="tocnumber">15</span> <span class="toctext">External links</span></a></li>
+</ul>
+</div>
+</div>
+<h2><span class="mw-headline" id="History">History</span></h2>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/History_of_Wikipedia" title="History of Wikipedia">History of Wikipedia</a></div>
+<div class="thumb tmulti tright">
+<div class="thumbinner" style="width:248px;max-width:248px">
+<div class="tsingle" style="float:left;margin:1px;width:122px;max-width:122px">
+<div class="thumbimage"><a href="/wiki/File:Jimmy_Wales_September_2015.jpg" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/6/65/Jimmy_Wales_September_2015.jpg/120px-Jimmy_Wales_September_2015.jpg" width="120" height="170" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/6/65/Jimmy_Wales_September_2015.jpg/180px-Jimmy_Wales_September_2015.jpg 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/6/65/Jimmy_Wales_September_2015.jpg/240px-Jimmy_Wales_Sept [...]
+</div>
+<div class="tsingle" style="float:left;margin:1px;width:122px;max-width:122px">
+<div class="thumbimage"><a href="/wiki/File:L_Sanger.jpg" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/a/a5/L_Sanger.jpg/120px-L_Sanger.jpg" width="120" height="148" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/a/a5/L_Sanger.jpg/180px-L_Sanger.jpg 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/a/a5/L_Sanger.jpg/240px-L_Sanger.jpg 2x" data-file-width="1209" data-file-height="1490" /></a></div>
+</div>
+<div style="clear:left"></div>
+<div class="thumbcaption" style="clear:left;text-align:left;background-color:transparent">Jimmy Wales and Larry Sanger</div>
+</div>
+</div>
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:Nupedia_logo_and_wordmark.png" class="image"><img alt="Logo reading "Nupedia.com the free encyclopedia" in blue with large initial "N"." src="//upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Nupedia_logo_and_wordmark.png/220px-Nupedia_logo_and_wordmark.png" width="220" height="51" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Nupedia_logo_and_wordmark.png/330px-Nupedia [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Nupedia_logo_and_wordmark.png" class="internal" title="Enlarge"></a></div>
+Wikipedia originally developed from another encyclopedia project, <a href="/wiki/Nupedia" title="Nupedia">Nupedia</a></div>
+</div>
+</div>
+<p>Other collaborative online encyclopedias were attempted before Wikipedia but none were so successful.<sup id="cite_ref-20" class="reference"><a href="#cite_note-20"><span>[</span>17<span>]</span></a></sup></p>
+<p>Wikipedia began as a complementary project for <a href="/wiki/Nupedia" title="Nupedia">Nupedia</a>, a free online <a href="/wiki/English_language" title="English language">English-language</a> encyclopedia project whose articles were written by experts and reviewed under a formal process. Nupedia was founded on March 9, 2000, under the ownership of <a href="/wiki/Bomis" title="Bomis">Bomis</a>, a <a href="/wiki/Web_portal" title="Web portal">web portal</a> company. Its main figures we [...]
+<table class="infobox" style="width:22em;width: 210px; float: right; clear: right; margin:0 0 1.0em 1.0em">
+<caption>External audio</caption>
+<tr>
+<td colspan="2" style="text-align:center;text-align: left"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Nuvola_apps_arts.svg/16px-Nuvola_apps_arts.svg.png" width="16" height="16" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Nuvola_apps_arts.svg/24px-Nuvola_apps_arts.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Nuvola_apps_arts.svg/32px-Nuvola_apps_arts.svg.png 2x" data-file-width="128" data-file-height="128" /> <a rel="nofollow" [...]
+</tr>
+</table>
+<p>Wikipedia was formally launched on January 15, 2001, as a single English-language edition at www.wikipedia.com,<sup id="cite_ref-WikipediaHome_28-0" class="reference"><a href="#cite_note-WikipediaHome-28"><span>[</span>25<span>]</span></a></sup> and announced by Sanger on the Nupedia mailing list.<sup id="cite_ref-SangerMemoir_24-1" class="reference"><a href="#cite_note-SangerMemoir-24"><span>[</span>21<span>]</span></a></sup> Wikipedia's policy of "neutral point-of-view"<sup id="cite [...]
+<p>Wikipedia gained early contributors from Nupedia, <a href="/wiki/Slashdot" title="Slashdot">Slashdot</a> postings, and <a href="/wiki/Web_search_engine" title="Web search engine">web search engine</a> indexing. By August 8, 2001, Wikipedia had over 8,000 articles.<sup id="cite_ref-Wikipedia_August_08.2C_2001_31-0" class="reference"><a href="#cite_note-Wikipedia_August_08.2C_2001-31"><span>[</span>28<span>]</span></a></sup> On September 25, 2001, Wikipedia had over 13,000 articles.<sup [...]
+<p>Citing fears of commercial advertising and lack of control in Wikipedia, users of the <a href="/wiki/Spanish_Wikipedia" title="Spanish Wikipedia">Spanish Wikipedia</a> <a href="/wiki/Fork_(software_development)" title="Fork (software development)">forked</a> from Wikipedia to create the <i><a href="/wiki/Enciclopedia_Libre_Universal_en_Espa%C3%B1ol" title="Enciclopedia Libre Universal en Español">Enciclopedia Libre</a></i> in February 2002.<sup id="cite_ref-EL_fears_and_start_1_35-0" [...]
+<p>Though the English Wikipedia reached three million articles in August 2009, the growth of the edition, in terms of the numbers of articles and of contributors, appears to have peaked around early 2007.<sup id="cite_ref-guardian_WP_user_peak_1_37-0" class="reference"><a href="#cite_note-guardian_WP_user_peak_1-37"><span>[</span>34<span>]</span></a></sup> Around 1,800 articles were added daily to the encyclopedia in 2006; by 2013 that average was roughly 800.<sup id="cite_ref-WP_growth_ [...]
+<p><span id="Decline_in_participation_since_2009"></span> In November 2009, a researcher at the <a href="/wiki/Rey_Juan_Carlos_University" title="Rey Juan Carlos University" class="mw-redirect">Rey Juan Carlos University</a> in <a href="/wiki/Madrid" title="Madrid">Madrid</a> (<a href="/wiki/Spain" title="Spain">Spain</a>) found that the English Wikipedia had lost 49,000 editors during the first three months of 2009; in comparison, the project lost only 4,900 editors during the same peri [...]
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:History_Wikipedia_English_SOPA_2012_Blackout2.jpg" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/a/a1/History_Wikipedia_English_SOPA_2012_Blackout2.jpg/220px-History_Wikipedia_English_SOPA_2012_Blackout2.jpg" width="220" height="107" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/a/a1/History_Wikipedia_English_SOPA_2012_Blackout2.jpg/330px-History_Wikipedia_English_ [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:History_Wikipedia_English_SOPA_2012_Blackout2.jpg" class="internal" title="Enlarge"></a></div>
+Wikipedia blackout protest against <a href="/wiki/Stop_Online_Piracy_Act" title="Stop Online Piracy Act">SOPA</a> on January 18, 2012</div>
+</div>
+</div>
+<div class="thumb tright">
+<div class="thumbinner" style="width:252px;">
+<div id="mwe_player_0" class="PopUpMediaTransform" style="width:250px;" videopayload="<div class="mediaContainer" style="width:854px"><video id="mwe_player_1" style="width:854px;height:480px" poster="//upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Wikipedia_Edit_2014.webm/854px--Wikipedia_Edit_2014.webm.jpg" controls="" preload="none" autoplay="" class="kskin" data-durationhint="17 [...]
+You can <a href="//upload.wikimedia.org/wikipedia/commons/4/4d/Wikipedia_Edit_2014.webm">download the clip</a> or <a href="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:TimedMediaHandler/Client_download">download a player</a> to play the clip in your browser.</video></div>"><img alt="File:Wikipedia Edit 2014.webm" style="width:250px;height:141px" src="//upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Wikipedia_Edit_2 [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikipedia_Edit_2014.webm" class="internal" title="Enlarge"></a></div>
+A promotional video of the Wikimedia Foundation that encourages viewers to edit Wikipedia, mostly reviewing 2014 via Wikipedia content</div>
+</div>
+</div>
+<p>In January 2007, Wikipedia entered for the first time the top-ten list of the most popular websites in the United States, according to <a href="/wiki/ComScore" title="ComScore">comScore</a> Networks. With 42.9 million unique visitors, Wikipedia was ranked number 9, surpassing the <i><a href="/wiki/New_York_Times" title="New York Times" class="mw-redirect">New York Times</a></i> (#10) and <a href="/wiki/Apple_Inc." title="Apple Inc.">Apple</a> (#11). This marked a significant increase [...]
+<p>On January 18, 2012, the English Wikipedia participated in a series of coordinated protests against two proposed laws in the United States Congress—the <a href="/wiki/Stop_Online_Piracy_Act" title="Stop Online Piracy Act">Stop Online Piracy Act</a> (SOPA) and the <a href="/wiki/PROTECT_IP_Act" title="PROTECT IP Act">PROTECT IP Act</a> (PIPA)—by <a href="/wiki/2012_Wikipedia_blackout" title="2012 Wikipedia blackout" class="mw-redirect">blacking out its pages for 24 hours</a>.<sup id="c [...]
+<p>Loveland and Reagle argue that, in process, Wikipedia follows a long tradition of historical encyclopedias that accumulated improvements piecemeal through "<a href="/wiki/Stigmergy" title="Stigmergy">stigmergic</a> accumulation".<sup id="cite_ref-sagepub_WP_and_encyclopedic_production_1_57-0" class="reference"><a href="#cite_note-sagepub_WP_and_encyclopedic_production_1-57"><span>[</span>54<span>]</span></a></sup><sup id="cite_ref-theatlantic_WP_actually_a_reversion_1_58-0" class="ref [...]
+<p>On January 20, 2014, Subodh Varma reporting for <i>The Economic Times</i> indicated that not only had Wikipedia growth flattened but that it has "lost nearly 10 per cent of its page-views last year. That's a decline of about 2 billion between December 2012 and December 2013. Its most popular versions are leading the slide: page-views of the English Wikipedia declined by 12 per cent, those of German version slid by 17 per cent and the Japanese version lost 9 per cent."<sup id="cite_ref [...]
+<table style="background:transparent;margin-top:0.5em;float:left">
+<tr>
+<td>
+<table style="width:350px;float:left;border-collapse:collapse;margin:3px">
+<tr>
+<td style="height:240px;border:1px solid #CCCCCC;background-color:#F8F8F8;padding:0px;text-align:center">
+<div class="center">
+<div class="floatnone"><a href="/wiki/File:EnwikipediaGom.PNG" class="image" title="Number of articles in the English Wikipedia (in blue)"><img alt="Graph of number of articles in the English Wikipedia showing steady growth" src="//upload.wikimedia.org/wikipedia/commons/thumb/5/57/EnwikipediaGom.PNG/309px-EnwikipediaGom.PNG" width="309" height="220" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/5/57/EnwikipediaGom.PNG/463px-EnwikipediaGom.PNG 1.5x, //upload.w [...]
+</div>
+</td>
+</tr>
+<tr style="vertical-align:top">
+<td style="display:block;font-size:1em;padding:0px">
+<div class="gallerytext" style="width:337px;line-height:1.3em;padding:2px 6px 1px 6px;margin:0px;border:none;border-width:0px">Number of articles in the English Wikipedia (in blue) </div>
+</td>
+</tr>
+</table>
+<table style="width:350px;float:left;border-collapse:collapse;margin:3px">
+<tr>
+<td style="height:240px;border:1px solid #CCCCCC;background-color:#F8F8F8;padding:0px;text-align:center">
+<div class="center">
+<div class="floatnone"><a href="/wiki/File:EnwikipediagrowthGom.PNG" class="image" title="Growth of the number of articles in the English Wikipedia (in blue)"><img alt="Growth of the number of articles in the English Wikipedia showing a max around 2007" src="//upload.wikimedia.org/wikipedia/commons/thumb/e/ea/EnwikipediagrowthGom.PNG/330px-EnwikipediagrowthGom.PNG" width="330" height="207" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/e/ea/EnwikipediagrowthGo [...]
+</div>
+</td>
+</tr>
+<tr style="vertical-align:top">
+<td style="display:block;font-size:1em;padding:0px">
+<div class="gallerytext" style="width:337px;line-height:1.3em;padding:2px 6px 1px 6px;margin:0px;border:none;border-width:0px">Growth of the number of articles in the English Wikipedia (in blue) </div>
+</td>
+</tr>
+</table>
+<table style="width:350px;float:left;border-collapse:collapse;margin:3px">
+<tr>
+<td style="height:240px;border:1px solid #CCCCCC;background-color:#F8F8F8;padding:0px;text-align:center">
+<div class="center">
+<div class="floatnone"><a href="/wiki/File:Time_Between_Edits_Graph_Jul05-Present.png" class="image" title="Number of days between every 10,000,000th edit from 2005 to 2012"><img alt="Graph showing the number of days between every 10,000,000th edit (ca. 50 days), from 2005 to 2011" src="//upload.wikimedia.org/wikipedia/commons/thumb/5/51/Time_Between_Edits_Graph_Jul05-Present.png/330px-Time_Between_Edits_Graph_Jul05-Present.png" width="330" height="203" class="thumbborder" srcset="//uplo [...]
+</div>
+</td>
+</tr>
+<tr style="vertical-align:top">
+<td style="display:block;font-size:1em;padding:0px">
+<div class="gallerytext" style="width:337px;line-height:1.3em;padding:2px 6px 1px 6px;margin:0px;border:none;border-width:0px">Number of days between every 10,000,000th edit from 2005 to 2012 </div>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+<div style="clear:both;"></div>
+<h2><span class="mw-headline" id="Openness">Openness</span></h2>
+<div class="thumb tleft">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:History_Comparison_Example_(Vector).png" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/9/96/History_Comparison_Example_%28Vector%29.png/220px-History_Comparison_Example_%28Vector%29.png" width="220" height="138" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/9/96/History_Comparison_Example_%28Vector%29.png/330px-History_Comparison_Example_%28Vector%29.png 1.5x, //up [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:History_Comparison_Example_(Vector).png" class="internal" title="Enlarge"></a></div>
+Differences between versions of an article are highlighted as shown.</div>
+</div>
+</div>
+<p>Unlike traditional encyclopedias, Wikipedia follows the <a href="/wiki/Procrastination" title="Procrastination">procrastination</a> principle<sup id="cite_ref-60" class="reference"><a href="#cite_note-60"><span>[</span>notes 4<span>]</span></a></sup> regarding the security of its content.<sup id="cite_ref-61" class="reference"><a href="#cite_note-61"><span>[</span>57<span>]</span></a></sup> It started almost entirely open—anyone could create articles, and any Wikipedia article could b [...]
+<h3><span class="mw-headline" id="Restrictions">Restrictions</span></h3>
+<p>Over time, the English Wikipedia and some other Wikipedias gradually have restricted modifications. For example, in the English Wikipedia and some other language editions, only registered users may create a new article.<sup id="cite_ref-62" class="reference"><a href="#cite_note-62"><span>[</span>58<span>]</span></a></sup> On the English Wikipedia and some others, some particularly controversial, sensitive and/or vandalism-prone pages are now "<a href="/wiki/Wikipedia:Protection_policy [...]
+<p>In certain cases, all editors are allowed to submit modifications, but review is required for some editors, depending on certain conditions. For example, the <a href="/wiki/German_Wikipedia" title="German Wikipedia">German Wikipedia</a> maintains "stable versions" of articles,<sup id="cite_ref-WP_some_sites_stable_versions_1_66-0" class="reference"><a href="#cite_note-WP_some_sites_stable_versions_1-66"><span>[</span>62<span>]</span></a></sup> which have passed certain reviews. Follow [...]
+<div class="thumb tleft">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:Wikipedia_editing_interface.png" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Wikipedia_editing_interface.png/220px-Wikipedia_editing_interface.png" width="220" height="105" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Wikipedia_editing_interface.png/330px-Wikipedia_editing_interface.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Wikipedi [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikipedia_editing_interface.png" class="internal" title="Enlarge"></a></div>
+The editing interface of Wikipedia</div>
+</div>
+</div>
+<h3><span class="mw-headline" id="Review_of_changes">Review of changes</span></h3>
+<p>Although changes are not systematically reviewed, the software that powers Wikipedia provides certain tools allowing anyone to review changes made by others. The "History" page of each article links to each revision.<sup id="cite_ref-69" class="reference"><a href="#cite_note-69"><span>[</span>notes 5<span>]</span></a></sup><sup id="cite_ref-Torsten_Kleinz_70-0" class="reference"><a href="#cite_note-Torsten_Kleinz-70"><span>[</span>65<span>]</span></a></sup> On most articles, anyone ca [...]
+<p>In 2003, economics PhD student Andrea Ciffolilli argued that the low <a href="/wiki/Transaction_cost" title="Transaction cost">transaction costs</a> of participating in a <a href="/wiki/Wiki" title="Wiki">wiki</a> create a catalyst for collaborative development, and that features such as allowing easy access to past versions of a page favor "creative construction" over "creative destruction".<sup id="cite_ref-FMonday_collaborative_effort_1_72-0" class="reference"><a href="#cite_note-F [...]
+<h3><span class="mw-headline" id="Vandalism">Vandalism</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Vandalism_on_Wikipedia" title="Vandalism on Wikipedia">Vandalism on Wikipedia</a></div>
+<p>Any edit that changes content in a way that deliberately compromises the integrity of Wikipedia is considered vandalism. The most common and obvious types of vandalism include insertion of obscenities and crude humor. Vandalism can also include advertising language and other types of <a href="/wiki/Wikipedia:Spam" title="Wikipedia:Spam">spam</a>.<sup id="cite_ref-upenn_link_spamming_1_73-0" class="reference"><a href="#cite_note-upenn_link_spamming_1-73"><span>[</span>68<span>]</span>< [...]
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:John_Seigenthaler_Sr._speaking.jpg" class="image"><img alt="White-haired elderly gentleman in suit and tie speaks at a podium." src="//upload.wikimedia.org/wikipedia/commons/thumb/2/2f/John_Seigenthaler_Sr._speaking.jpg/220px-John_Seigenthaler_Sr._speaking.jpg" width="220" height="165" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/2/2f/John_Seigenthaler_Sr._speaking.jpg/330px-John_Seigenthaler_Sr [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:John_Seigenthaler_Sr._speaking.jpg" class="internal" title="Enlarge"></a></div>
+American journalist <a href="/wiki/John_Seigenthaler" title="John Seigenthaler">John Seigenthaler</a> (1927–2014), subject of the <a href="/wiki/Seigenthaler_incident" title="Seigenthaler incident" class="mw-redirect">Seigenthaler incident</a></div>
+</div>
+</div>
+<p>Obvious vandalism is generally easy to remove from wiki articles; the median time to detect and fix vandalism is a few minutes.<sup id="cite_ref-MIT_IBM_study_75-0" class="reference"><a href="#cite_note-MIT_IBM_study-75"><span>[</span>70<span>]</span></a></sup><sup id="cite_ref-CreatingDestroyingAndRestoringValue_76-0" class="reference"><a href="#cite_note-CreatingDestroyingAndRestoringValue-76"><span>[</span>71<span>]</span></a></sup> However, some vandalism takes much longer to repa [...]
+<p>In the <a href="/wiki/Wikipedia_Seigenthaler_biography_incident" title="Wikipedia Seigenthaler biography incident">Wikipedia Seigenthaler biography incident</a>, an anonymous editor introduced false information into the biography of American political figure <a href="/wiki/John_Seigenthaler" title="John Seigenthaler">John Seigenthaler</a> in May 2005. Seigenthaler was falsely presented as a suspect in the assassination of John F. Kennedy.<sup id="cite_ref-Seigenthaler_77-1" class="ref [...]
+<h2><span class="mw-headline" id="Policies_and_laws"><span id="Rules_and_laws_governing_content">Policies and laws</span><span id="Rules_and_laws_governing_content_and_editor_behavior"></span></span></h2>
+<p><span id="Censorship"></span></p>
+<dl>
+<dd><i>See also: <a href="/wiki/Wikipedia:Five_pillars" title="Wikipedia:Five pillars">Wikipedia:Five Pillars</a></i></dd>
+</dl>
+<table class="infobox" style="width:22em;width: 210px; float: right; clear: right; margin:0 0 1.0em 1.0em">
+<caption>External video</caption>
+<tr>
+<td colspan="2" style="text-align:center"><a href="/wiki/File:Jimbo_at_Fosdem_cropped.jpg" class="image"><img alt="Jimbo at Fosdem cropped.jpg" src="//upload.wikimedia.org/wikipedia/commons/thumb/4/47/Jimbo_at_Fosdem_cropped.jpg/210px-Jimbo_at_Fosdem_cropped.jpg" width="210" height="244" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/4/47/Jimbo_at_Fosdem_cropped.jpg/315px-Jimbo_at_Fosdem_cropped.jpg 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/4/47/Jimbo_at_Fosdem_cropped [...]
+</tr>
+<tr>
+<td colspan="2" style="text-align:center;text-align: left"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Nuvola_apps_kaboodle.svg/16px-Nuvola_apps_kaboodle.svg.png" width="16" height="16" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Nuvola_apps_kaboodle.svg/24px-Nuvola_apps_kaboodle.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Nuvola_apps_kaboodle.svg/32px-Nuvola_apps_kaboodle.svg.png 2x" data-file-width="128" data-file-height="1 [...]
+</tr>
+</table>
+<p>Content in Wikipedia is subject to the laws (in particular, <a href="/wiki/Copyright" title="Copyright">copyright</a> laws) of the United States and of the U.S. state of <a href="/wiki/Virginia" title="Virginia">Virginia</a>, where the majority of Wikipedia's servers are located. Beyond legal matters, the editorial principles of Wikipedia are embodied in the <a href="/wiki/Wikipedia:Five_pillars" title="Wikipedia:Five pillars">"five pillars"</a> and in numerous <a href="/wiki/Wikipedi [...]
+<h3><span class="mw-headline" id="Content_policies_and_guidelines">Content policies and guidelines<span id="Content_policies"></span></span></h3>
+<div class="hatnote relarticle mainarticle">Main pages: <a href="/wiki/Wikipedia:Content_policies" title="Wikipedia:Content policies" class="mw-redirect">Wikipedia:Content policies</a> and <a href="/wiki/Wikipedia:Content_guidelines" title="Wikipedia:Content guidelines" class="mw-redirect">Wikipedia:Content guidelines</a></div>
+<p>According to the rules on the English Wikipedia, each entry in Wikipedia must be about a topic that is <a href="//en.wiktionary.org/wiki/encyclopedic" class="extiw" title="wikt:encyclopedic">encyclopedic</a> and <a href="/wiki/Wikipedia:Wikipedia_is_not_a_dictionary" title="Wikipedia:Wikipedia is not a dictionary">is not a dictionary entry</a> or dictionary-like.<sup id="cite_ref-WP_content_policy_1_82-0" class="reference"><a href="#cite_note-WP_content_policy_1-82"><span>[</span>77<s [...]
+<h2><span class="mw-headline" id="Governance">Governance</span></h2>
+<p>Wikipedia's initial <a href="/wiki/Anarchy" title="Anarchy">anarchy</a> integrated <a href="/wiki/Democracy" title="Democracy">democratic</a> and hierarchical elements over time.<sup id="cite_ref-89" class="reference"><a href="#cite_note-89"><span>[</span>84<span>]</span></a></sup><sup id="cite_ref-90" class="reference"><a href="#cite_note-90"><span>[</span>85<span>]</span></a></sup> An article is not considered to be owned by its creator or any other editor and is not vetted by any r [...]
+<h3><span class="mw-headline" id="Administrators">Administrators</span></h3>
+<p>Editors in good standing in the community can run for one of many levels of volunteer stewardship: this begins with "<a href="/wiki/Administrators_(Wikipedia)" title="Administrators (Wikipedia)" class="mw-redirect">administrator</a>",<sup id="cite_ref-93" class="reference"><a href="#cite_note-93"><span>[</span>88<span>]</span></a></sup><sup id="cite_ref-David_Mehegan_94-0" class="reference"><a href="#cite_note-David_Mehegan-94"><span>[</span>89<span>]</span></a></sup> privileged users [...]
+<p>Fewer editors become administrators than in years past, in part because the process of vetting potential Wikipedia administrators has become more rigorous.<sup id="cite_ref-97" class="reference"><a href="#cite_note-97"><span>[</span>92<span>]</span></a></sup></p>
+<p><i><a href="/wiki/Wikipedia:Bureaucrats" title="Wikipedia:Bureaucrats">Bureaucrats</a></i> name new administrators, solely upon the recommendations from the community.</p>
+<h3><span class="mw-headline" id="Dispute_resolution">Dispute resolution</span></h3>
+<p>Wikipedians may dispute, for example by repeatedly making opposite changes to an article.<sup id="cite_ref-98" class="reference"><a href="#cite_note-98"><span>[</span>93<span>]</span></a></sup><sup id="cite_ref-99" class="reference"><a href="#cite_note-99"><span>[</span>94<span>]</span></a></sup><sup id="cite_ref-NBC_WP_editorial_warzone_1_100-0" class="reference"><a href="#cite_note-NBC_WP_editorial_warzone_1-100"><span>[</span>95<span>]</span></a></sup> Over time, Wikipedia has deve [...]
+<h4><span class="mw-headline" id="Arbitration_Committee">Arbitration Committee</span></h4>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Arbitration_Committee" title="Arbitration Committee">Arbitration Committee</a></div>
+<p>The Arbitration Committee presides over the ultimate dispute resolution process. Although disputes usually arise from a disagreement between two opposing views on how an article should read, the Arbitration Committee explicitly refuses to directly rule on the specific view that should be adopted. Statistical analyses suggest that the committee ignores the content of disputes and rather focuses on the way disputes are conducted,<sup id="cite_ref-emory_disputes_handled_1_101-0" class="r [...]
+<h2><span class="mw-headline" id="Community">Community</span></h2>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Wikipedia_community" title="Wikipedia community">Wikipedia community</a></div>
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;">
+<div id="mwe_player_2" class="PopUpMediaTransform" style="width:220px;" videopayload="<div class="mediaContainer" style="width:628px"><video id="mwe_player_3" style="width:628px;height:478px" poster="//upload.wikimedia.org/wikipedia/commons/thumb/6/61/Wikimania_-_the_Wikimentary.webm/627px--Wikimania_-_the_Wikimentary.webm.jpg" controls="" preload="none" autoplay="" class="kskin" data-durat [...]
+You can <a href="//upload.wikimedia.org/wikipedia/commons/6/61/Wikimania_-_the_Wikimentary.webm">download the clip</a> or <a href="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:TimedMediaHandler/Client_download">download a player</a> to play the clip in your browser.</video></div>"><img alt="File:Wikimania - the Wikimentary.webm" style="width:220px;height:168px" src="//upload.wikimedia.org/wikipedia/commons/thumb/6/61/ [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikimania_-_the_Wikimentary.webm" class="internal" title="Enlarge"></a></div>
+Video of <a href="/wiki/Wikimania#2005" title="Wikimania">Wikimania 2005</a> – an annual conference for users of Wikipedia and other projects operated by the <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a>, was held in <a href="/wiki/Frankfurt_am_Main" title="Frankfurt am Main" class="mw-redirect">Frankfurt am Main</a>, <a href="/wiki/Germany" title="Germany">Germany</a> from August 4 to 8</div>
+</div>
+</div>
+<p>Each article and each user of Wikipedia has an associated "Talk" page. These form the primary communication channel for editors to discuss, coordinate and debate.<sup id="cite_ref-103" class="reference"><a href="#cite_note-103"><span>[</span>98<span>]</span></a></sup></p>
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;">
+<div id="mwe_player_4" class="PopUpMediaTransform" style="width:220px;" videopayload="<div class="mediaContainer" style="width:716px"><video id="mwe_player_5" style="width:716px;height:480px" poster="//upload.wikimedia.org/wikipedia/commons/thumb/2/27/Editing_Hoxne_Hoard_at_the_British_Museum.ogv/716px--Editing_Hoxne_Hoard_at_the_British_Museum.ogv.jpg" controls="" preload="none" autoplay="" class=&q [...]
+You can <a href="//upload.wikimedia.org/wikipedia/commons/2/27/Editing_Hoxne_Hoard_at_the_British_Museum.ogv">download the clip</a> or <a href="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:TimedMediaHandler/Client_download">download a player</a> to play the clip in your browser.</video></div>"><img alt="File:Editing Hoxne Hoard at the British Museum.ogv" style="width:220px;height:148px" src="//upload.wikimedia.org/wik [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Editing_Hoxne_Hoard_at_the_British_Museum.ogv" class="internal" title="Enlarge"></a></div>
+Wikipedians and <a href="/wiki/British_Museum" title="British Museum">British Museum</a> curators collaborate on the article <a href="/wiki/Hoxne_Hoard" title="Hoxne Hoard">Hoxne Hoard</a> in June 2010</div>
+</div>
+</div>
+<p>Wikipedia's community has been described as <a href="/wiki/Cult" title="Cult">cult</a>-like,<sup id="cite_ref-104" class="reference"><a href="#cite_note-104"><span>[</span>99<span>]</span></a></sup> although not always with entirely negative connotations.<sup id="cite_ref-105" class="reference"><a href="#cite_note-105"><span>[</span>100<span>]</span></a></sup> The project's preference for cohesiveness, even if it requires compromise that includes disregard of <a href="/wiki/Credential [...]
+<p>Wikipedians sometimes award one another <a href="/wiki/Wikipedia:Barnstars" title="Wikipedia:Barnstars">virtual barnstars</a> for good work. These personalized tokens of appreciation reveal a wide range of valued work extending far beyond simple editing to include social support, administrative actions, and types of articulation work.<sup id="cite_ref-107" class="reference"><a href="#cite_note-107"><span>[</span>102<span>]</span></a></sup></p>
+<p>Wikipedia does not require that its editors and contributors provide identification.<sup id="cite_ref-user_identification_108-0" class="reference"><a href="#cite_note-user_identification-108"><span>[</span>103<span>]</span></a></sup> As Wikipedia grew, "Who writes Wikipedia?" became one of the questions frequently asked on the project.<sup id="cite_ref-109" class="reference"><a href="#cite_note-109"><span>[</span>104<span>]</span></a></sup> Jimmy Wales once argued that only "a communi [...]
+<p><span id="Decline_in_participation_since_2007"></span> The English Wikipedia has <a href="/wiki/Special:Statistics" title="Special:Statistics">5,022,963</a> articles, <a href="/wiki/Special:ActiveUsers" title="Special:ActiveUsers">26,886,656</a> registered editors, and <a href="/wiki/Special:ActiveUsers" title="Special:ActiveUsers">127,505</a> active editors. An editor is considered active if they make one or more edits in said month.</p>
+<p>Editors who fail to comply with Wikipedia cultural rituals, such as signing talk pages, may implicitly signal that they are Wikipedia outsiders, increasing the odds that Wikipedia insiders may target or discount their contributions. Becoming a Wikipedia insider involves non-trivial costs: the contributor is expected to learn Wikipedia-specific technological codes, submit to a sometimes convoluted dispute resolution process, and learn a "baffling culture rich with in-jokes and insider [...]
+<p>A 2007 study by researchers from <a href="/wiki/Dartmouth_College" title="Dartmouth College">Dartmouth College</a> found that "anonymous and infrequent contributors to Wikipedia [...] are as reliable a source of knowledge as those contributors who register with the site".<sup id="cite_ref-sciam_good_samaritans_1_115-0" class="reference"><a href="#cite_note-sciam_good_samaritans_1-115"><span>[</span>110<span>]</span></a></sup> Jimmy Wales stated in 2009 that "(I)t turns out over 50% of [...]
+<p>A 2008 study found that Wikipedians were less agreeable, open, and conscientious than others.<sup id="cite_ref-liebertonline_view_on_WP_users_1_116-0" class="reference"><a href="#cite_note-liebertonline_view_on_WP_users_1-116"><span>[</span>111<span>]</span></a></sup><sup id="cite_ref-newscientist_view_on_WP_users_1_117-0" class="reference"><a href="#cite_note-newscientist_view_on_WP_users_1-117"><span>[</span>112<span>]</span></a></sup> According to a 2009 study, there is "evidence o [...]
+<h3><span class="mw-headline" id="Diversity">Diversity</span></h3>
+<div class="thumb tright">
+<div class="thumbinner" style="width:302px;"><a href="/wiki/File:WMFstratplanSurvey1.png" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/e/e7/WMFstratplanSurvey1.png/300px-WMFstratplanSurvey1.png" width="300" height="248" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/e/e7/WMFstratplanSurvey1.png/450px-WMFstratplanSurvey1.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/e/e7/WMFstratplanSurvey1.png/600px-WMFstratplanSurvey [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:WMFstratplanSurvey1.png" class="internal" title="Enlarge"></a></div>
+Wikipedia editor demographics (April 2009)</div>
+</div>
+</div>
+<p>One study found that the contributor base to Wikipedia "was barely 13% women; the average age of a contributor was in the mid-20s".<sup id="cite_ref-119" class="reference"><a href="#cite_note-119"><span>[</span>114<span>]</span></a></sup> A 2011 study by researchers from the <a href="/wiki/University_of_Minnesota" title="University of Minnesota">University of Minnesota</a> found that females comprised 16.1% of the 38,497 editors who started editing Wikipedia during 2009.<sup id="cite_ [...]
+<p>In response, various universities have hosted <a href="/wiki/Edit-a-thon" title="Edit-a-thon">edit-a-thons</a> to encourage more women to participate in the Wikipedia community. In fall 2013, 15 colleges and universities, including Yale, Brown, and Pennsylvania State, offered college credit for students to "write feminist thinking" about technology into Wikipedia.<sup id="cite_ref-124" class="reference"><a href="#cite_note-124"><span>[</span>119<span>]</span></a></sup></p>
+<p>In August 2014, Wikipedia co-founder <a href="/wiki/Jimmy_Wales" title="Jimmy Wales">Jimmy Wales</a> announced in a BBC interview the <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a>'s plans for "doubling down" on the issue of <a href="/wiki/Gender_bias_on_Wikipedia" title="Gender bias on Wikipedia">gender bias on Wikipedia</a>. Wales agreed that Sue Gardner's goal of 25% women enrollment by 2015 had not been met. Wales said the foundation wou [...]
+<h2><span class="mw-headline" id="Language_editions">Language editions</span></h2>
+<p>There are currently 288 <a href="/wiki/List_of_Wikipedias" title="List of Wikipedias">language editions of Wikipedia</a> (also called <i>language versions</i>, or simply <i>Wikipedias</i>). Twelve of these have over one million articles each (<a href="/wiki/English_Wikipedia" title="English Wikipedia">English</a>, <a href="/wiki/Swedish_Wikipedia" title="Swedish Wikipedia">Swedish</a>, <a href="/wiki/Dutch_Wikipedia" title="Dutch Wikipedia">Dutch</a>, <a href="/wiki/German_Wikipedia" [...]
+<div class="thumb tright">
+<div class="thumbinner" style="width:202px">
+<div style="background-color:white;margin:auto;position:relative;width:200px;height:200px;overflow:hidden">
+<p><br /></p>
+<p><br /></p>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:100px; border-width:9.4108313318515px 99.556196460308px 0 0; border-top-color:#333333"></div>
+<div style="position:absolute;line-height:0;border-style:solid;left:0;top:0;border-width:0 200px 100px 0;border-color:#333333"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:0; border-width:0 880.42099845209px 100px 0; border-right-color:#33CC99"></div>
+<div style="position:absolute;line-height:0;border-style:solid;right:0;top:0;border-width:0 100px 100px 0;border-color:#33CC99"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:0; border-width:0 301.31325811305px 100px 0; border-right-color:#CC79A7"></div>
+<div style="position:absolute;line-height:0;border-style:solid;right:0;top:0;border-width:0 100px 100px 0;border-color:#CC79A7"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:0; border-width:0 169.0907655785px 100px 0; border-right-color:#D55E00"></div>
+<div style="position:absolute;line-height:0;border-style:solid;right:0;top:0;border-width:0 100px 100px 0;border-color:#D55E00"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:0; border-width:0 107.83906208398px 100px 0; border-right-color:#0072B2"></div>
+<div style="position:absolute;line-height:0;border-style:solid;right:0;top:0;border-width:0 100px 100px 0;border-color:#0072B2"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:0; border-width:0 65.238931786305px 100px 0; border-right-color:#F0E442"></div>
+<div style="position:absolute;line-height:0;border-style:solid;right:0;top:0;border-width:0 100px 100px 0;border-color:#F0E442"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:0; border-width:0 29.735293003841px 100px 0; border-right-color:#009E73"></div>
+<div style="position:absolute;line-height:0;border-style:solid;right:0;top:0;border-width:0 100px 100px 0;border-color:#009E73"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:0.03158107166999px; border-width:0 0 99.96841892833px 2.5130095443334px; border-bottom-color:#56B4E9"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:5.9119231045774px; border-width:0 0 94.088076895423px 33.873792024529px; border-bottom-color:#E69F00"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:24.574861926389px; border-width:0 0 75.425138073611px 65.658575575296px; border-bottom-color:#666666"></div>
+<div style="position:absolute;left:0;top:0"><img alt="Circle frame.svg" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/200px-Circle_frame.svg.png" width="200" height="200" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/300px-Circle_frame.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/400px-Circle_frame.svg.png 2x" data-file-width="200" data-file-height="200" /></div>
+</div>
+<div class="thumbcaption">
+<p><b>Distribution of the 37,006,309 articles in different language editions (as of 3 December 2015)</b><sup id="cite_ref-meta.wikimedia_130-0" class="reference"><a href="#cite_note-meta.wikimedia-130"><span>[</span>125<span>]</span></a></sup></p>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #666666; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/English_Wikipedia" title="English Wikipedia">English</a> (13.6%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #E69F00; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/Swedish_Wikipedia" title="Swedish Wikipedia">Swedish</a> (5.9%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #56B4E9; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/German_Wikipedia" title="German Wikipedia">German</a> (5.1%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #009E73; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/Dutch_Wikipedia" title="Dutch Wikipedia">Dutch</a> (5%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #F0E442; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/French_Wikipedia" title="French Wikipedia">French</a> (4.6%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #0072B2; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/Cebuano_Wikipedia" title="Cebuano Wikipedia">Cebuano</a> (3.9%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #D55E00; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/Russian_Wikipedia" title="Russian Wikipedia">Russian</a> (3.4%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #CC79A7; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/Waray-Waray_Wikipedia" title="Waray-Waray Wikipedia" class="mw-redirect">Waray-Waray</a> (3.4%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #33CC99; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/Italian_Wikipedia" title="Italian Wikipedia">Italian</a> (3.3%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color: #333333; color:black; font-size:100%; text-align:center;"> </span> <a href="/wiki/Spanish_Wikipedia" title="Spanish Wikipedia">Spanish</a> (3.3%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:white; color:black; font-size:100%; text-align:center;"> </span> Other (48.5%)</div>
+</div>
+</div>
+</div>
+<table width="300px">
+<caption><b>Logarithmic graph of the 20 largest language editions of Wikipedia</b><br />
+(as of 3 December 2015)<sup id="cite_ref-131" class="reference"><a href="#cite_note-131"><span>[</span>126<span>]</span></a></sup><br />
+(millions of articles)</caption>
+<tr style="text-align:center;border-style:solid; border-color:black; border-width=1px;">
+<td colspan="2" style="border-right:solid black 1px;">0.1</td>
+<td colspan="2" style="border-right:solid black 1px;">0.3</td>
+<td colspan="2" style="border-right:solid black 1px;">1</td>
+<td colspan="2" style="border-right:solid black 1px;">3</td>
+</tr>
+<tr>
+<td width="12.5%" style="border-right:solid black 1px;"></td>
+<td width="12.5%" style="border-right:solid black 1px;"></td>
+<td width="12.5%" style="border-right:solid black 1px;"></td>
+<td width="12.5%" style="border-right:solid black 1px;"></td>
+<td width="12.5%" style="border-right:solid black 1px;"></td>
+<td width="12.5%" style="border-right:solid black 1px;"></td>
+<td width="12.5%" style="border-right:solid black 1px;"></td>
+<td width="12.5%" style="border-right:solid black 1px;"><br /></td>
+</tr>
+<tr>
+<td colspan="8">
+<table style="text-align:left;">
+<tr>
+<td>
+<div style="background-color:rgb(215,72,88); width:570.83158562343px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/English_Wikipedia" title="English Wikipedia">English</a> 5,022,963</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(125,47,203); width:462.16119815682px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Swedish_Wikipedia" title="Swedish Wikipedia">Swedish</a> 2,181,350</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(203,47,125); width:442.90898432951px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/German_Wikipedia" title="German Wikipedia">German</a> 1,881,704</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(88,72,215); width:440.32498885628px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Dutch_Wikipedia" title="Dutch Wikipedia">Dutch</a> 1,844,752</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(178,35,162); width:429.79042128354px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/French_Wikipedia" title="French Wikipedia">French</a> 1,701,464</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(58,106,211); width:409.59996653804px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Cebuano_Wikipedia" title="Cebuano Wikipedia">Cebuano</a> 1,457,207</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(144,39,192); width:391.8758967856px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Russian_Wikipedia" title="Russian Wikipedia">Russian</a> 1,271,865</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(39,144,192); width:390.59031915387px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Waray-Waray_Wikipedia" title="Waray-Waray Wikipedia" class="mw-redirect">Waray-Waray</a> 1,259,377</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(106,58,211); width:388.45099730576px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Italian_Wikipedia" title="Italian Wikipedia">Italian</a> 1,238,867</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(35,178,162); width:386.08311629904px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Spanish_Wikipedia" title="Spanish Wikipedia">Spanish</a> 1,216,555</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(72,88,215); width:378.37171825697px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Polish_Wikipedia" title="Polish Wikipedia">Polish</a> 1,146,640</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(47,203,125); width:377.76342292007px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Vietnamese_Wikipedia" title="Vietnamese Wikipedia">Vietnamese</a> 1,141,299</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(47,125,203); width:359.74666125433px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Japanese_Wikipedia" title="Japanese Wikipedia">Japanese</a> 993,902</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(72,215,88); width:346.24980811889px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Portuguese_Wikipedia" title="Portuguese Wikipedia">Portuguese</a> 896,095</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(35,162,178); width:339.39164662988px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Chinese_Wikipedia" title="Chinese Wikipedia">Chinese</a> 850,146</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(106,211,58); width:294.83978870085px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Ukrainian_Wikipedia" title="Ukrainian Wikipedia">Ukrainian</a> 603,931</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(39,192,144); width:266.79639441381px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Catalan_Wikipedia" title="Catalan Wikipedia">Catalan</a> 486,978</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(144,192,39); width:263.75996954551px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Persian_Wikipedia" title="Persian Wikipedia">Persian</a> 475,760</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(58,211,106); width:250.25799987965px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Serbo-Croatian_Wikipedia" title="Serbo-Croatian Wikipedia">Serbo-Croatian</a> 428,925</div>
+</td>
+</tr>
+<tr>
+<td>
+<div style="background-color:rgb(178,162,35); width:249.3114085819px; height:20px; padding-left:1em; text-align:center;"><a href="/wiki/Norwegian_Wikipedia" title="Norwegian Wikipedia">Norwegian</a> 425,820</div>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+<p><b>The unit for the numbers in bars is articles.</b> Since Wikipedia is based on the <a href="/wiki/World_Wide_Web" title="World Wide Web">Web</a> and therefore worldwide, contributors to the same language edition may use different dialects or may come from different countries (as is the case for the <a href="/wiki/English_Wikipedia" title="English Wikipedia">English edition</a>). These differences may lead to some conflicts over <a href="/wiki/American_and_British_English_spelling_di [...]
+<p>Though the various language editions are held to global policies such as "neutral point of view", they diverge on some points of policy and practice, most notably on whether images that are not <a href="/wiki/Free_content" title="Free content">licensed freely</a> may be used under a claim of <a href="/wiki/Fair_use" title="Fair use">fair use</a>.<sup id="cite_ref-WP_meta_fair_use_1_134-0" class="reference"><a href="#cite_note-WP_meta_fair_use_1-134"><span>[</span>129<span>]</span></a> [...]
+<p>Jimmy Wales has described Wikipedia as "an effort to create and distribute a free encyclopedia of the highest possible quality to every single person on the planet in their own language".<sup id="cite_ref-WP_Wales_free_multi-lingual_encyclopedia_137-0" class="reference"><a href="#cite_note-WP_Wales_free_multi-lingual_encyclopedia-137"><span>[</span>132<span>]</span></a></sup> Though each language edition functions more or less independently, some efforts are made to supervise them all [...]
+<div class="thumb tleft">
+<div class="thumbinner" style="width:502px;"><a href="/wiki/File:User_-_demography.svg" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/2/20/User_-_demography.svg/500px-User_-_demography.svg.png" width="500" height="300" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/2/20/User_-_demography.svg/750px-User_-_demography.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/2/20/User_-_demography.svg/1000px-User_-_demography.svg [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:User_-_demography.svg" class="internal" title="Enlarge"></a></div>
+Estimation of contributions shares from different regions in the world to different Wikipedia editions</div>
+</div>
+</div>
+<p>Translated articles represent only a small portion of articles in most editions, in part because fully automated translation of articles is disallowed.<sup id="cite_ref-WP_auto-translations_rules_1_141-0" class="reference"><a href="#cite_note-WP_auto-translations_rules_1-141"><span>[</span>136<span>]</span></a></sup> Articles available in more than one language may offer "<a href="/wiki/Interwiki_links" title="Interwiki links">interwiki links</a>", which link to the counterpart articl [...]
+<p>A study published by <a href="/wiki/PLOS_ONE" title="PLOS ONE">PLOS ONE</a> in 2012 also estimated the share of contributions to different editions of Wikipedia from different regions of the world. It reported that the proportion of the edits made from <a href="/wiki/North_America" title="North America">North America</a> was 51% for the <a href="/wiki/English_Wikipedia" title="English Wikipedia">English Wikipedia</a>, and 25% for the <a href="/wiki/Simple_English_Wikipedia" title="Sim [...]
+<p>On March 1, 2014, <i>The Economist</i> in an article titled "The Future of Wikipedia" cited a trend analysis concerning data published by Wikimedia stating that: "The number of editors for the English-language version has fallen by a third in seven years."<sup id="cite_ref-economist1_144-0" class="reference"><a href="#cite_note-economist1-144"><span>[</span>139<span>]</span></a></sup> The attrition rate for active editors in English Wikipedia was cited by <i>The Economist</i> as subst [...]
+<h2><span class="mw-headline" id="Critical_reception">Critical reception</span></h2>
+<div class="hatnote">See also: <a href="/wiki/Academic_studies_about_Wikipedia" title="Academic studies about Wikipedia">Academic studies about Wikipedia</a> and <a href="/wiki/Criticism_of_Wikipedia" title="Criticism of Wikipedia">Criticism of Wikipedia</a></div>
+<p>Several Wikipedians have <a href="/wiki/Criticism_of_Wikipedia#Excessive_regulation" title="Criticism of Wikipedia">criticized Wikipedia's large and growing regulation</a>, which includes over 50 policies and nearly 150,000 words as of 2014<sup class="plainlinks noprint asof-tag update" style="display:none;"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Wikipedia&action=edit">[update]</a></sup>.<sup id="cite_ref-bureaucracy_146-0" class="reference"><a href="# [...]
+<p>Critics have stated that Wikipedia exhibits <a href="/wiki/Systemic_bias" title="Systemic bias">systemic bias</a>. Columnist and journalist <a href="/wiki/Edwin_Black" title="Edwin Black">Edwin Black</a> criticizes Wikipedia for being a mixture of "truth, half truth, and some falsehoods".<sup id="cite_ref-EdwinBlack_18-1" class="reference"><a href="#cite_note-EdwinBlack-18"><span>[</span>15<span>]</span></a></sup> Articles in <i><a href="/wiki/The_Chronicle_of_Higher_Education" title= [...]
+<p>Journalists <a href="/wiki/Oliver_Kamm" title="Oliver Kamm">Oliver Kamm</a> and <a href="/wiki/Edwin_Black" title="Edwin Black">Edwin Black</a> noted how articles are dominated by the loudest and most persistent voices, usually by a group with an "ax to grind" on the topic.<sup id="cite_ref-EdwinBlack_18-2" class="reference"><a href="#cite_note-EdwinBlack-18"><span>[</span>15<span>]</span></a></sup><sup id="cite_ref-okw_151-0" class="reference"><a href="#cite_note-okw-151"><span>[</sp [...]
+<p>In 2006, the <i>Wikipedia Watch</i> criticism website listed dozens of examples of <a href="/wiki/Plagiarism" title="Plagiarism">plagiarism</a> in the English Wikipedia.<sup id="cite_ref-wwplagiarism_152-0" class="reference"><a href="#cite_note-wwplagiarism-152"><span>[</span>147<span>]</span></a></sup></p>
+<h3><span class="mw-headline" id="Accuracy_of_content">Accuracy of content</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Reliability_of_Wikipedia" title="Reliability of Wikipedia">Reliability of Wikipedia</a></div>
+<p>Articles for traditional encyclopedias such as <i><a href="/wiki/Encyclop%C3%A6dia_Britannica" title="Encyclopædia Britannica">Encyclopædia Britannica</a></i> are carefully and deliberately written by experts, lending such encyclopedias a reputation for accuracy.<sup id="cite_ref-153" class="reference"><a href="#cite_note-153"><span>[</span>148<span>]</span></a></sup> Conversely, Wikipedia is often cited for factual inaccuracies and misrepresentations. However, a peer review in 2005 o [...]
+<p>As a consequence of the open structure, Wikipedia "makes no guarantee of validity" of its content, since no one is ultimately responsible for any claims appearing in it.<sup id="cite_ref-WP_general_disclaimer_1_159-0" class="reference"><a href="#cite_note-WP_general_disclaimer_1-159"><span>[</span>154<span>]</span></a></sup> Concerns have been raised by <i>PC World</i> in 2009 regarding the lack of <a href="/wiki/Accountability" title="Accountability">accountability</a> that results f [...]
+<p>Economist <a href="/wiki/Tyler_Cowen" title="Tyler Cowen">Tyler Cowen</a> wrote: "If I had to guess whether Wikipedia or the median refereed journal article on economics was more likely to be true, after a not so long think I would opt for Wikipedia." He comments that some traditional sources of non-fiction suffer from systemic biases and novel results, in his opinion, are over-reported in journal articles and relevant information is omitted from news reports. However, he also caution [...]
+<p>Critics argue that Wikipedia's open nature and a lack of proper sources for most of the information makes it unreliable.<sup id="cite_ref-TNY_reliability_issues_1_163-0" class="reference"><a href="#cite_note-TNY_reliability_issues_1-163"><span>[</span>158<span>]</span></a></sup> Some commentators suggest that Wikipedia may be reliable, but that the reliability of any given article is not clear.<sup id="cite_ref-AcademiaAndWikipedia_164-0" class="reference"><a href="#cite_note-Academia [...]
+<table class="infobox" style="width:22em;width: 210px; float: right; clear: right; margin:0 0 1.0em 1.0em">
+<caption>External video</caption>
+<tr>
+<td colspan="2" style="text-align:center;text-align: left"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Nuvola_apps_kaboodle.svg/16px-Nuvola_apps_kaboodle.svg.png" width="16" height="16" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Nuvola_apps_kaboodle.svg/24px-Nuvola_apps_kaboodle.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Nuvola_apps_kaboodle.svg/32px-Nuvola_apps_kaboodle.svg.png 2x" data-file-width="128" data-file-height="1 [...]
+</tr>
+</table>
+<p>Wikipedia's open structure inherently makes it an easy target for Internet <a href="/wiki/Troll_(Internet)" title="Troll (Internet)" class="mw-redirect">trolls</a>, <a href="/wiki/Spam_(electronic)" title="Spam (electronic)" class="mw-redirect">spammers</a>, and various forms of paid advocacy seen as counterproductive to the maintenance of a neutral and verifiable online encyclopedia.<sup id="cite_ref-Torsten_Kleinz_70-1" class="reference"><a href="#cite_note-Torsten_Kleinz-70"><span> [...]
+<p>Most university <a href="/wiki/Lecturer" title="Lecturer">lecturers</a> discourage students from citing any encyclopedia in <a href="/wiki/Academia" title="Academia">academic work</a>, preferring <a href="/wiki/Primary_source" title="Primary source">primary sources</a>;<sup id="cite_ref-WideWorldOfWikipedia_174-0" class="reference"><a href="#cite_note-WideWorldOfWikipedia-174"><span>[</span>169<span>]</span></a></sup> some specifically prohibit Wikipedia citations.<sup id="cite_ref-in [...]
+<p>In February 2007, an article in <i><a href="/wiki/The_Harvard_Crimson" title="The Harvard Crimson">The Harvard Crimson</a></i> newspaper reported that a few of the professors at <a href="/wiki/Harvard_University" title="Harvard University">Harvard University</a> include Wikipedia in their <a href="/wiki/Syllabus" title="Syllabus">syllabi</a>, but that there is a split in their perception of using Wikipedia.<sup id="cite_ref-thecrimson_wiki_debate_179-0" class="reference"><a href="#cit [...]
+<p>A Harvard law textbook, <i>Legal Research in a Nutshell</i> (2011), cites Wikipedia as a "general source" that "can be a real boon" in "coming up to speed in the law governing a situation" and, "while not authoritative, can provide basic facts as well as leads to more in-depth resources".<sup id="cite_ref-Nutshell_in-depth_resources_181-0" class="reference"><a href="#cite_note-Nutshell_in-depth_resources-181"><span>[</span>176<span>]</span></a></sup></p>
+<h4><span class="mw-headline" id="Medical_information">Medical information</span></h4>
+<p><span id="Medical_information"></span></p>
+<div class="hatnote">See also: <a href="/wiki/Health_information_on_Wikipedia" title="Health information on Wikipedia">Health information on Wikipedia</a></div>
+<p>On March 5, 2014, Julie Beck writing for <i>The Atlantic</i> magazine in an article titled "Doctors' #1 Source for Healthcare Information: Wikipedia", stated that "Fifty percent of physicians look up conditions on the (Wikipedia) site, and some are editing articles themselves to improve the quality of available information."<sup id="cite_ref-Julie_Beck_2014_182-0" class="reference"><a href="#cite_note-Julie_Beck_2014-182"><span>[</span>177<span>]</span></a></sup> Beck continued to det [...]
+<h3><span class="mw-headline" id="Quality_of_writing">Quality of writing</span></h3>
+<p>Because contributors usually rewrite small portions of an entry rather than making full-length revisions, high- and low-quality content may be intermingled within an entry. <a href="/wiki/Roy_Rosenzweig" title="Roy Rosenzweig">Roy Rosenzweig</a>, a history professor, stated that <i>American National Biography Online</i> outperformed Wikipedia in terms of its "clear and engaging prose", which, he said, was an important aspect of good historical writing.<sup id="cite_ref-Rosenzweig_184- [...]
+<p>Other critics have made similar charges that, even if Wikipedia articles are factually accurate, they are often written in a poor, almost unreadable style. Frequent Wikipedia critic Andrew Orlowski commented: "Even when a Wikipedia entry is 100 per cent factually correct, and those facts have been carefully chosen, it all too often reads as if it has been translated from one language to another then into to a third, passing an illiterate translator at each stage."<sup id="cite_ref-the [...]
+<h3><span class="mw-headline" id="Coverage_of_topics_and_systemic_bias">Coverage of topics and systemic bias</span></h3>
+<div class="hatnote">See also: <a href="/wiki/Notability_in_the_English_Wikipedia" title="Notability in the English Wikipedia">Notability in the English Wikipedia</a></div>
+<p>Wikipedia seeks to create a summary of all <a href="/wiki/Human" title="Human">human</a> knowledge in the form of an online encyclopedia, with each topic covered encyclopedically in one article. Since it has <a href="/wiki/Terabyte" title="Terabyte">terabytes</a> of disk space, it can have far more topics than can be covered by any printed encyclopedia.<sup id="cite_ref-WP_advantages_over_trad_media_1_188-0" class="reference"><a href="#cite_note-WP_advantages_over_trad_media_1-188"><s [...]
+<div class="thumb tright">
+<div class="thumbinner" style="width:502px;"><a href="/wiki/File:Wikipedia_content_by_subject.png" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/5/54/Wikipedia_content_by_subject.png/500px-Wikipedia_content_by_subject.png" width="500" height="322" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/5/54/Wikipedia_content_by_subject.png 1.5x, //upload.wikimedia.org/wikipedia/commons/5/54/Wikipedia_content_by_subject.png 2x" data-file-width=" [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikipedia_content_by_subject.png" class="internal" title="Enlarge"></a></div>
+Pie chart of Wikipedia content by subject as of January 2008<sup id="cite_ref-Kittur2009_194-0" class="reference"><a href="#cite_note-Kittur2009-194"><span>[</span>189<span>]</span></a></sup></div>
+</div>
+</div>
+<p>A 2008 study conducted by researchers at Carnegie Mellon University and Palo Alto Research Center gave a distribution of topics as well as growth (from July 2006 to January 2008) in each field:<sup id="cite_ref-Kittur2009_194-1" class="reference"><a href="#cite_note-Kittur2009-194"><span>[</span>189<span>]</span></a></sup></p>
+<div class="refbegin columns references-column-width" style="-moz-column-width: 30em; -webkit-column-width: 30em; column-width: 30em;">
+<ul>
+<li>Culture and the arts: 30% (210%)</li>
+<li>Biographies and persons: 15% (97%)</li>
+<li>Geography and places: 14% (52%)</li>
+<li>Society and social sciences: 12% (83%)</li>
+<li>History and events: 11% (143%)</li>
+<li>Natural and physical sciences: 9% (213%)</li>
+<li>Technology and the applied sciences: 4% (−6%)</li>
+<li>Religions and belief systems: 2% (38%)</li>
+<li>Health: 2% (42%)</li>
+<li>Mathematics and logic: 1% (146%)</li>
+<li>Thought and philosophy: 1% (160%)</li>
+</ul>
+</div>
+<p>These numbers refer only to the quantity of articles: it is possible for one topic to contain a large number of short articles and another to contain a small number of large ones. Through its "<a href="/wiki/Wikipedia:Wikipedia_Loves_Libraries" title="Wikipedia:Wikipedia Loves Libraries">Wikipedia Loves Libraries</a>" program, Wikipedia has partnered with major public libraries such as the <a href="/wiki/New_York_Public_Library_for_the_Performing_Arts" title="New York Public Library f [...]
+<p>A 2011 study conducted by researchers at the <a href="/wiki/University_of_Minnesota" title="University of Minnesota">University of Minnesota</a> indicated that male and female editors focus on different coverage topics. There was a greater concentration of females in the People and Arts category, while males focus more on Geography and Science.<sup id="cite_ref-196" class="reference"><a href="#cite_note-196"><span>[</span>191<span>]</span></a></sup></p>
+<h4><span class="mw-headline" id="Coverage_of_topics_and_selection_bias">Coverage of topics and selection bias</span></h4>
+<p>Research conducted by the Oxford Internet Institute has shown that the geographic distribution of article topics is highly uneven. <a href="/wiki/Africa" title="Africa">Africa</a> is most underrepresented.<sup id="cite_ref-zerogeography_places_coverage_197-0" class="reference"><a href="#cite_note-zerogeography_places_coverage-197"><span>[</span>192<span>]</span></a></sup></p>
+<p>A "selection bias" may arise when more words per article are devoted to one public figure than a rival public figure. Editors may dispute suspected biases and discuss controversial articles, sometimes at great length.</p>
+<h4><span class="mw-headline" id="Systemic_bias">Systemic bias</span></h4>
+<p>When multiple editors contribute to one topic or set of topics, there may arise a <a href="/wiki/Systemic_bias" title="Systemic bias">systemic bias</a>, such as non-opposite definitions for apparent antonyms. In 2011, Wales noted that the unevenness of coverage is a reflection of the demography of the editors, which predominantly consists of young males with high education levels in the developed world (cf. previously).<sup id="cite_ref-wiki-women_47-1" class="reference"><a href="#cit [...]
+<p><a href="/wiki/Bias_in_Wikipedia" title="Bias in Wikipedia" class="mw-redirect">Systemic bias on Wikipedia</a> may follow that of culture generally, for example favouring certain ethnicities or majority religions.<sup id="cite_ref-Quilter_198-0" class="reference"><a href="#cite_note-Quilter-198"><span>[</span>193<span>]</span></a></sup> It may more specifically follow the biases of <a href="/wiki/Internet_culture" title="Internet culture" class="mw-redirect">Internet culture</a>, incl [...]
+<p>Taha Yasseri of the <a href="/wiki/University_of_Oxford" title="University of Oxford">University of Oxford</a>, in 2013, studied the statistical trends of systemic bias at Wikipedia introduced by editing conflicts and their resolution.<sup id="cite_ref-199" class="reference"><a href="#cite_note-199"><span>[</span>194<span>]</span></a></sup><sup id="cite_ref-autogenerated3_200-0" class="reference"><a href="#cite_note-autogenerated3-200"><span>[</span>195<span>]</span></a></sup> His res [...]
+<h3><span class="mw-headline" id="Explicit_content">Explicit content</span></h3>
+<div class="hatnote">Main category: <a href="/wiki/Category:Wikipedia_objectionable_content" title="Category:Wikipedia objectionable content">Wikipedia objectionable content</a></div>
+<div class="hatnote">See also: <a href="/wiki/Internet_Watch_Foundation_and_Wikipedia" title="Internet Watch Foundation and Wikipedia">Internet Watch Foundation and Wikipedia</a> and <a href="/wiki/Reporting_of_child_pornography_images_on_Wikimedia_Commons" title="Reporting of child pornography images on Wikimedia Commons">Reporting of child pornography images on Wikimedia Commons</a></div>
+<table class="rquote floatright" role="presentation" style="border-collapse:collapse; border-style:none; float:right; margin:0.5em 0.75em; width:33%;">
+<tr style="text-align:left; vertical-align:top;">
+<td style="color:#b2b7f2; font-size:3.3em; font-family:'Times New Roman',serif; font-weight:bold; padding:4px 2px 2px; width:0.5em; line-height: 1em;">“</td>
+<td style="padding:0 10px;">Problem? What problem? So, you didn't know that Wikipedia has a porn problem?</td>
+<td style="color:#b2b7f2; font-size:3.3em; font-family:'Times New Roman',serif; font-weight:bold; padding:4px 2px 2px; text-align:right; vertical-align:bottom; width:0.5em; line-height: 1em;">”</td>
+</tr>
+<tr>
+<td class="rquotecite" colspan="3" style="padding-top:10px;">
+<div style="font-size:smaller; line-height:1em; text-align:right"><cite>— Larry Sanger, <sup id="cite_ref-autogenerated4_201-0" class="reference"><a href="#cite_note-autogenerated4-201"><span>[</span>196<span>]</span></a></sup></cite></div>
+</td>
+</tr>
+</table>
+<p>Wikipedia has been criticized for allowing information of graphic content. Articles depicting arguably objectionable content (such as <i><a href="/wiki/Feces" title="Feces">Feces</a></i>, <i><a href="/wiki/Cadaver" title="Cadaver">Cadaver</a></i>, <i><a href="/wiki/Human_penis" title="Human penis">Human penis</a></i>, <i><a href="/wiki/Vulva" title="Vulva">Vulva</a></i>, and <i><a href="/wiki/Nudity" title="Nudity">Nudity</a></i>) contain graphic pictures and detailed information easi [...]
+<p>The site also includes <a href="/wiki/Sexual_content" title="Sexual content">sexual content</a> such as images and videos of <a href="/wiki/Masturbation" title="Masturbation">masturbation</a> and <a href="/wiki/Ejaculation" title="Ejaculation">ejaculation</a>, <a href="/wiki/Child_nudity" title="Child nudity" class="mw-redirect">photographs of nude children</a>, <a href="/wiki/Zoophilia" title="Zoophilia">illustrations of zoophilia</a>, and photos from <a href="/wiki/Hardcore_pornogra [...]
+<p>The Wikipedia article about <i><a href="/wiki/Virgin_Killer" title="Virgin Killer">Virgin Killer</a>—</i>a 1976 album from <a href="/wiki/Music_of_Germany" title="Music of Germany">German</a> <a href="/wiki/Heavy_metal_music" title="Heavy metal music">heavy metal</a> <a href="/wiki/Rock_band" title="Rock band" class="mw-redirect">band</a> <a href="/wiki/Scorpions_(band)" title="Scorpions (band)">Scorpions</a>—features a picture of the album's original cover, which depicts a naked <a h [...]
+<p>In April 2010, Sanger wrote a letter to the Federal Bureau of Investigation, outlining his concerns that two categories of images on <a href="/wiki/Wikimedia_Commons" title="Wikimedia Commons">Wikimedia Commons</a> contained child pornography, and were in violation of <a href="/wiki/United_States_obscenity_law" title="United States obscenity law">US federal obscenity law</a>.<sup id="cite_ref-Inquirer_child_abuse_allegations_204-0" class="reference"><a href="#cite_note-Inquirer_child_ [...]
+<h3><span class="mw-headline" id="Privacy">Privacy</span></h3>
+<p>One <a href="/wiki/Privacy" title="Privacy">privacy</a> concern in the case of Wikipedia is the right of a private citizen to remain a "private citizen" rather than a "<a href="/wiki/Public_figure" title="Public figure">public figure</a>" in the eyes of the law.<sup id="cite_ref-210" class="reference"><a href="#cite_note-210"><span>[</span>205<span>]</span></a></sup><sup id="cite_ref-211" class="reference"><a href="#cite_note-211"><span>[</span>notes 6<span>]</span></a></sup> It is a [...]
+<p>In January 2006, a German court ordered the <a href="/wiki/German_Wikipedia" title="German Wikipedia">German Wikipedia</a> shut down within Germany because it stated the full name of <a href="/wiki/Tron_(hacker)" title="Tron (hacker)">Boris Floricic</a>, aka "Tron", a deceased hacker. On February 9, 2006, the injunction against Wikimedia Deutschland was overturned, with the court rejecting the notion that Tron's right to privacy or that of his parents was being violated.<sup id="cite_ [...]
+<p>Wikipedia has a "<span id="Volunteer_Response_Team">Volunteer Response Team</span>" that uses the <a href="/wiki/OTRS" title="OTRS">OTRS</a> system to handle queries without having to reveal the identities of the involved parties. This is used, for example, in confirming the permission for using individual images and other media in the project.<sup id="cite_ref-213" class="reference"><a href="#cite_note-213"><span>[</span>207<span>]</span></a></sup></p>
+<h3><span class="mw-headline" id="Sexism">Sexism</span></h3>
+<p>Wikipedia has been described as harboring a battleground culture of <a href="/wiki/Sexism" title="Sexism">sexism</a> and <a href="/wiki/Harassment" title="Harassment">harassment</a>.<sup id="cite_ref-214" class="reference"><a href="#cite_note-214"><span>[</span>208<span>]</span></a></sup><sup id="cite_ref-215" class="reference"><a href="#cite_note-215"><span>[</span>209<span>]</span></a></sup></p>
+<h2><span class="mw-headline" id="Operation">Operation</span></h2>
+<p>A group of Wikipedia editors may form a <a href="/wiki/Wikipedia:WikiProject" title="Wikipedia:WikiProject">WikiProject</a> to focus their work on a specific topic area, using its associated discussion page to coordinate changes across multiple articles.<sup id="cite_ref-216" class="reference"><a href="#cite_note-216"><span>[</span>210<span>]</span></a></sup></p>
+<h3><span class="mw-headline" id="Wikimedia_Foundation_and_the_Wikimedia_chapters">Wikimedia Foundation and the Wikimedia chapters</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a></div>
+<div class="thumb tright">
+<div class="thumbinner" style="width:172px;"><a href="/wiki/File:Wikimedia_Foundation_RGB_logo_with_text.svg" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/c/c4/Wikimedia_Foundation_RGB_logo_with_text.svg/170px-Wikimedia_Foundation_RGB_logo_with_text.svg.png" width="170" height="170" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/c/c4/Wikimedia_Foundation_RGB_logo_with_text.svg/255px-Wikimedia_Foundation_RGB_logo_with_text.svg.pn [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikimedia_Foundation_RGB_logo_with_text.svg" class="internal" title="Enlarge"></a></div>
+<a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a> logo</div>
+</div>
+</div>
+<p>Wikipedia is hosted and funded by the <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a>, a non-profit organization which also operates Wikipedia-related projects such as <a href="/wiki/Wiktionary" title="Wiktionary">Wiktionary</a> and <a href="/wiki/Wikibooks" title="Wikibooks">Wikibooks</a>. The foundation relies on public contributions and grants to fund its mission.<sup id="cite_ref-financialstatements_217-0" class="reference"><a href="#cite [...]
+<p>In May 2014, Wikimedia Foundation named <a href="/wiki/Lila_Tretikov" title="Lila Tretikov">Lila Tretikov</a> as its new executive director, taking over for Sue Gardner.<sup id="cite_ref-219" class="reference"><a href="#cite_note-219"><span>[</span>213<span>]</span></a></sup> The <i>Wall Street Journal</i> reported on May 1, 2014 that Tretikov's information technology background from her years at University of California offers Wikipedia an opportunity to develop in more concentrated [...]
+<h3><span class="mw-headline" id="Software_operations_and_support">Software operations and support</span></h3>
+<div class="hatnote">See also: <a href="/wiki/MediaWiki" title="MediaWiki">MediaWiki</a></div>
+<p>The operation of Wikipedia depends on <a href="/wiki/MediaWiki" title="MediaWiki">MediaWiki</a>, a custom-made, <a href="/wiki/Free_software" title="Free software">free</a> and <a href="/wiki/Open_source_software" title="Open source software" class="mw-redirect">open source</a> <a href="/wiki/Wiki_software" title="Wiki software">wiki software</a> platform written in <a href="/wiki/PHP" title="PHP">PHP</a> and built upon the <a href="/wiki/MySQL" title="MySQL">MySQL</a> database system [...]
+<p>Several MediaWiki extensions are installed<sup id="cite_ref-WP_extensions_installed_223-0" class="reference"><a href="#cite_note-WP_extensions_installed-223"><span>[</span>217<span>]</span></a></sup> to extend the functionality of the MediaWiki software.</p>
+<p>In April 2005, a <a href="/wiki/Lucene" title="Lucene">Lucene</a> extension<sup id="cite_ref-224" class="reference"><a href="#cite_note-224"><span>[</span>218<span>]</span></a></sup><sup id="cite_ref-225" class="reference"><a href="#cite_note-225"><span>[</span>219<span>]</span></a></sup> was added to MediaWiki's built-in search and Wikipedia switched from <a href="/wiki/MySQL" title="MySQL">MySQL</a> to Lucene for searching. The site currently uses Lucene Search 2.1,<sup id="cite_ref [...]
+<p>In July 2013, after extensive beta testing, a WYSIWYG (What You See Is What You Get) extension, <a href="/wiki/VisualEditor" title="VisualEditor">VisualEditor</a>, was opened to public use.<sup id="cite_ref-thenextwebve_228-0" class="reference"><a href="#cite_note-thenextwebve-228"><span>[</span>222<span>]</span></a></sup><sup id="cite_ref-229" class="reference"><a href="#cite_note-229"><span>[</span>223<span>]</span></a></sup><sup id="cite_ref-TheEconomistVE_230-0" class="reference"> [...]
+<h3><span class="mw-headline" id="Automated_editing">Automated editing</span></h3>
+<p>Computer programs called <a href="/wiki/Internet_bot" title="Internet bot">bots</a> have been used widely to perform simple and repetitive tasks, such as correcting common misspellings and stylistic issues, or to start articles such as geography entries in a standard format from statistical data.<sup id="cite_ref-233" class="reference"><a href="#cite_note-233"><span>[</span>227<span>]</span></a></sup><sup id="cite_ref-meetbots_234-0" class="reference"><a href="#cite_note-meetbots-234" [...]
+<p>According to <a href="/wiki/Andrew_Lih" title="Andrew Lih">Andrew Lih</a>, the current expansion of Wikipedia to millions of articles would be difficult to envision without the use of such bots.<sup id="cite_ref-240" class="reference"><a href="#cite_note-240"><span>[</span>234<span>]</span></a></sup></p>
+<h3><span class="mw-headline" id="Wikiprojects.2C_and_assessments_of_articles.27_importance_and_quality">Wikiprojects, and assessments of articles' importance and quality</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/WikiProject" title="WikiProject">WikiProject</a></div>
+<p>A "<a href="/wiki/WikiProject" title="WikiProject">WikiProject</a>" is a <a href="/wiki/Social_group" title="Social group">group</a> of contributors who want to work together as a <a href="/wiki/Team" title="Team">team</a> to improve Wikipedia. These groups often focus on a specific topic area (for example, <a href="/wiki/Women%27s_history" title="Women's history">women's history</a>), a specific location or a specific kind of task (for example, checking newly created pages). The <a h [...]
+<p>In 2007, in preparation for producing a print version, the English Wikipedia introduced an assessment scale of the quality of articles.<sup id="cite_ref-WP_1.0_editorial_team_1_242-0" class="reference"><a href="#cite_note-WP_1.0_editorial_team_1-242"><span>[</span>236<span>]</span></a></sup> Articles are rated by Wikiprojects. The range of quality classes begins with "Stub" (very short pages), followed by "Start", "C" and "B" (in increasing order of quality). Community peer review is [...]
+<p>The articles can also be rated as per "importance" as judged by a Wikiproject. Currently, there are 5 importance categories: "low", "mid", "high", "top", and "???" for unclassified/unsure level. For a particular article, different Wikiprojects may assign different importance levels.</p>
+<p>The <a href="/wiki/Wikipedia:Version_1.0_Editorial_Team" title="Wikipedia:Version 1.0 Editorial Team">Wikipedia Version 1.0 Editorial Team</a> has developed a table (shown below) that displays data of all rated articles by quality and importance, on the English Wikipedia. <b>If an article or list receives different ratings by two or more Wikiprojects, then the highest rating is used in the table, pie-charts, and bar-chart.</b> The software regularly auto-updates the data.</p>
+<p>Researcher Giacomo Poderi found that articles tend to reach featured status via the intensive work of a few editors.<sup id="cite_ref-Poderi_Giacomo_feat_articles_1_245-0" class="reference"><a href="#cite_note-Poderi_Giacomo_feat_articles_1-245"><span>[</span>239<span>]</span></a></sup> A 2010 study found unevenness in quality among featured articles and concluded that the community process is ineffective in assessing the quality of articles.<sup id="cite_ref-FMonday_WP_quality_contro [...]
+<div class="thumb tright">
+<div class="thumbinner" style="width:202px">
+<div style="background-color:white;margin:auto;position:relative;width:200px;height:200px;overflow:hidden">
+<p><br /></p>
+<p><br /></p>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:0; top:0; border-width:0 200px 200px 0; border-color:black"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:100px; border-width:100px 0 0 137.82021170681px; border-left-color:purple"></div>
+<div style="position:absolute;line-height:0;border-style:solid;left:0;top:0;border-width:0 200px 100px 0;border-color:purple"></div>
+<div style="position:absolute;line-height:0;border-style:solid;left:0;top:0;border-width:0 100px 200px 0;border-color:purple"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:100px; border-width:100px 0 0 88.273589137601px; border-left-color:red"></div>
+<div style="position:absolute;line-height:0;border-style:solid;left:0;top:0;border-width:0 200px 100px 0;border-color:red"></div>
+<div style="position:absolute;line-height:0;border-style:solid;left:0;top:0;border-width:0 100px 200px 0;border-color:red"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:0; border-width:0 50.399772021864px 100px 0; border-right-color:orange"></div>
+<div style="position:absolute;line-height:0;border-style:solid;right:0;top:0;border-width:0 100px 100px 0;border-color:orange"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:59.135092526365px; border-width:0 0 40.864907473635px 91.26915874035px; border-bottom-color:yellow"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:83.054728989056px; border-width:0 0 16.945271010944px 98.553831941562px; border-bottom-color:lightgreen"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:95.854286152471px; border-width:0 0 4.1457138475285px 99.91402832783px; border-bottom-color:darkgreen"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:98.869050755034px; border-width:0 0 1.1309492449658px 99.993604564519px; border-bottom-color:lightblue"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:99.057536156686px; border-width:0 0 0.9424638433144px 99.995558710895px; border-bottom-color:indigo"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:99.308855118778px; border-width:0 0 0.69114488122233px 99.997611565243px; border-bottom-color:violet"></div>
+<div style="position:absolute;left:0;top:0"><img alt="Circle frame.svg" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/200px-Circle_frame.svg.png" width="200" height="200" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/300px-Circle_frame.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/400px-Circle_frame.svg.png 2x" data-file-width="200" data-file-height="200" /></div>
+</div>
+<div class="thumbcaption">
+<p><b>Quality-wise distribution of over 4.8 million articles and lists on the English Wikipedia, as of 3 April 2015<sup class="plainlinks noprint asof-tag update" style="display:none;"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Wikipedia&action=edit">[update]</a></sup></b><sup id="cite_ref-en.wikipedia_247-0" class="reference"><a href="#cite_note-en.wikipedia-247"><span>[</span>241<span>]</span></a></sup></p>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:violet; color:black; font-size:100%; text-align:center;"> </span> Featured articles (0.11%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:indigo; color:black; font-size:100%; text-align:center;"> </span> Featured lists (0.04%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:lightblue; color:black; font-size:100%; text-align:center;"> </span> A class (0.03%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:darkgreen; color:black; font-size:100%; text-align:center;"> </span> Good articles (0.48%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:lightgreen; color:black; font-size:100%; text-align:center;"> </span> B class (2.05%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:yellow; color:black; font-size:100%; text-align:center;"> </span> C class (3.99%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:orange; color:black; font-size:100%; text-align:center;"> </span> Start class (25.73%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:red; color:black; font-size:100%; text-align:center;"> </span> Stub class (54.08%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:purple; color:black; font-size:100%; text-align:center;"> </span> Lists (3.50%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:black; color:black; font-size:100%; text-align:center;"> </span> Unassessed (9.99%)</div>
+</div>
+</div>
+</div>
+<div style="float: right; padding-left: 7 px;">
+<div class="thumb tright">
+<div class="thumbinner" style="width:202px">
+<div style="background-color:white;margin:auto;position:relative;width:200px;height:200px;overflow:hidden">
+<p><br /></p>
+<p><br /></p>
+<p><br /></p>
+<p><br /></p>
+<p><br /></p>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:0; top:0; border-width:0 200px 200px 0; border-color:fuchsia"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;right:100px; top:100px; border-width:83.684075955104px 54.744638381675px 0 0; border-top-color:yellow"></div>
+<div style="position:absolute;line-height:0;border-style:solid;left:0;top:0;border-width:0 200px 100px 0;border-color:yellow"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:14.215028120458px; border-width:0 0 85.784971879542px 51.390063238199px; border-bottom-color:blue"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:74.401430588733px; border-width:0 0 25.598569411267px 96.668056999696px; border-bottom-color:green"></div>
+<div class="transborder" style="position:absolute;width:100px;line-height:0;left:100px; top:94.285416252053px; border-width:0 0 5.7145837479469px 99.836584139221px; border-bottom-color:red"></div>
+<div style="position:absolute;left:0;top:0"><img alt="Circle frame.svg" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/200px-Circle_frame.svg.png" width="200" height="200" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/300px-Circle_frame.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/18/Circle_frame.svg/400px-Circle_frame.svg.png 2x" data-file-width="200" data-file-height="200" /></div>
+</div>
+<div class="thumbcaption">
+<p><b>Importance-wise distribution of over 4.8 million articles and lists on the English Wikipedia, as of 5 April 2015<sup class="plainlinks noprint asof-tag update" style="display:none;"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Wikipedia&action=edit">[update]</a></sup></b><sup id="cite_ref-en.wikipedia_247-1" class="reference"><a href="#cite_note-en.wikipedia-247"><span>[</span>241<span>]</span></a></sup></p>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:red; color:black; font-size:100%; text-align:center;"> </span> Top (0.91%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:green; color:black; font-size:100%; text-align:center;"> </span> High (3.21%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:blue; color:black; font-size:100%; text-align:center;"> </span> Medium (12.29%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:yellow; color:black; font-size:100%; text-align:center;"> </span> Low (49.37%)</div>
+<div class="legend"><span class="legend-color" style="display:inline-block; width:1.5em; height:1.5em; margin:1px 0; border:1px solid black; background-color:fuchsia; color:black; font-size:100%; text-align:center;"> </span> ??? (34.22%)</div>
+</div>
+</div>
+</div>
+</div>
+<table class="ratingstable wikitable plainlinks" style="text-align: right; margin-left: auto; margin-right: auto;">
+<tr>
+<th colspan="7" class="ratingstabletitle">All rated articles by quality and importance</th>
+</tr>
+<tr>
+<th rowspan="2" style="vertical-align: bottom"><b>Quality</b></th>
+<th colspan="6"><b>Importance</b></th>
+</tr>
+<tr>
+<th class="import-top" style="text-align:center; background: #ff00ff;"><b><a href="/wiki/Category:Top-importance_articles" title="Category:Top-importance articles">Top</a></b></th>
+<th class="import-high" style="text-align:center; background: #ff88ff;"><b><a href="/wiki/Category:High-importance_articles" title="Category:High-importance articles">High</a></b></th>
+<th class="import-mid" style="text-align:center; background: #ffbbff;"><b><a href="/wiki/Category:Mid-importance_articles" title="Category:Mid-importance articles">Mid</a></b></th>
+<th class="import-low" style="text-align:center; background: #ffddff;"><b><a href="/wiki/Category:Low-importance_articles" title="Category:Low-importance articles">Low</a></b></th>
+<th class="assess-" style="background: transparent; text-align: center;"><b><a href="/wiki/Category:Unassessed-Class_articles" title="Category:Unassessed-Class articles">???</a></b></th>
+<th style="text-align: center;"><b>Total</b></th>
+</tr>
+<tr>
+<td class="assess-fa" style="background: #6699ff; text-align: center;"><span style="" title="Featured Article"><a href="/wiki/File:Featured_article_star.svg" class="image" title="Featured article"><img alt="Featured article" src="//upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Featured_article_star.svg/16px-Featured_article_star.svg.png" width="16" height="16" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Featured_article_star.svg/24px-Featured_article_star.svg.png 1.5x [...]
+<td>1,113</td>
+<td>1,717</td>
+<td>1,619</td>
+<td>969</td>
+<td>181</td>
+<td><b>5,599</b></td>
+</tr>
+<tr>
+<td class="assess-fl" style="background: #6699ff; text-align: center;"><span style="" title="Featured List"><a href="/wiki/File:Featured_article_star.svg" class="image" title="Featured list"><img alt="Featured list" src="//upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Featured_article_star.svg/16px-Featured_article_star.svg.png" width="16" height="16" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Featured_article_star.svg/24px-Featured_article_star.svg.png 1.5x, //uploa [...]
+<td>141</td>
+<td>541</td>
+<td>633</td>
+<td>579</td>
+<td>112</td>
+<td><b>2,006</b></td>
+</tr>
+<tr>
+<td class="assess-a" style="background: #66ffff; text-align:center;"><span style="" title="A-Class Article"><a href="/wiki/File:Symbol_a_class.svg" class="image" title="A-Class article"><img alt="A-Class article" src="//upload.wikimedia.org/wikipedia/commons/thumb/2/25/Symbol_a_class.svg/16px-Symbol_a_class.svg.png" width="16" height="16" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/2/25/Symbol_a_class.svg/24px-Symbol_a_class.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commo [...]
+<td>196</td>
+<td>392</td>
+<td>549</td>
+<td>327</td>
+<td>71</td>
+<td><b>1,535</b></td>
+</tr>
+<tr>
+<td class="assess-ga" style="background: #66ff66; text-align: center;"><span style="" title="Good Article"><img alt="" src="//upload.wikimedia.org/wikipedia/en/thumb/9/94/Symbol_support_vote.svg/16px-Symbol_support_vote.svg.png" title="Good article" width="16" height="16" srcset="//upload.wikimedia.org/wikipedia/en/thumb/9/94/Symbol_support_vote.svg/24px-Symbol_support_vote.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/9/94/Symbol_support_vote.svg/32px-Symbol_support_vote.svg.p [...]
+<td>1,893</td>
+<td>4,348</td>
+<td>8,532</td>
+<td>8,749</td>
+<td>1,608</td>
+<td><b>25,130</b></td>
+</tr>
+<tr>
+<td class="assess-b" style="background: #b2ff66; text-align: center;"><b><a href="/wiki/Category:B-Class_articles" title="Category:B-Class articles">B</a></b></td>
+<td>11,448</td>
+<td>21,809</td>
+<td>32,962</td>
+<td>25,400</td>
+<td>13,325</td>
+<td><b>104,944</b></td>
+</tr>
+<tr>
+<td class="assess-c" style="background: #ffff66; text-align: center;"><b><a href="/wiki/Category:C-Class_articles" title="Category:C-Class articles">C</a></b></td>
+<td>9,285</td>
+<td>26,836</td>
+<td>59,408</td>
+<td>77,837</td>
+<td>39,413</td>
+<td><b>212,779</b></td>
+</tr>
+<tr>
+<td class="assess-start" style="background: #ffaa66; text-align: center;"><b><a href="/wiki/Category:Start-Class_articles" title="Category:Start-Class articles">Start</a></b></td>
+<td>16,463</td>
+<td>71,419</td>
+<td>286,418</td>
+<td>698,360</td>
+<td>270,485</td>
+<td><b>1,343,145</b></td>
+</tr>
+<tr>
+<td class="assess-stub" style="background: #ff6666; text-align: center;"><b><a href="/wiki/Category:Stub-Class_articles" title="Category:Stub-Class articles">Stub</a></b></td>
+<td>4,220</td>
+<td>29,632</td>
+<td>212,076</td>
+<td>1,708,662</td>
+<td>816,889</td>
+<td><b>2,771,479</b></td>
+</tr>
+<tr>
+<td class="assess-list" style="background: #aa88ff; text-align: center;"><b><a href="/wiki/Category:List-Class_articles" title="Category:List-Class articles">List</a></b></td>
+<td>2,787</td>
+<td>10,239</td>
+<td>30,160</td>
+<td>80,926</td>
+<td>58,084</td>
+<td><b>182,196</b></td>
+</tr>
+<tr>
+<td><b>Assessed</b></td>
+<td>47,546</td>
+<td>166,933</td>
+<td>632,357</td>
+<td>2,601,809</td>
+<td>1,200,168</td>
+<td><b>4,648,813</b></td>
+</tr>
+<tr>
+<td class="assess-unassessed" style="background: transparent; text-align:center;"><b><a href="/wiki/Category:Unassessed-Class_articles" title="Category:Unassessed-Class articles">Unassessed</a></b></td>
+<td>116</td>
+<td>351</td>
+<td>1,621</td>
+<td>17,418</td>
+<td>483,846</td>
+<td><b>503,352</b></td>
+</tr>
+<tr>
+<td style="text-align: center;"><b>Total</b></td>
+<td><b>47,662</b></td>
+<td><b>167,284</b></td>
+<td><b>633,978</b></td>
+<td><b>2,619,227</b></td>
+<td><b>1,684,014</b></td>
+<td><b>5,152,165</b></td>
+</tr>
+</table>
+<div style="max-width:800px;">
+<div style="position:relative;min-height:700px;min-width:800px;max-width:800px;">
+<div style="float:right;position:relative;min-height:620px;min-width:700px;max-width:700px;border-left:1px black solid;border-bottom:1px black solid;">
+<div style="position:absolute;left:51px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:violet;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured articles: 1,054"></div>
+<div style="position:absolute;left:191px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:violet;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured articles: 1,651"></div>
+<div style="position:absolute;left:331px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:violet;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured articles: 1,542"></div>
+<div style="position:absolute;left:471px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:violet;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured articles: 897"></div>
+<div style="position:absolute;left:611px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:violet;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured articles: 169"></div>
+<div style="position:absolute;left:51px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:indigo;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured lists: 135"></div>
+<div style="position:absolute;left:191px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:indigo;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured lists: 518"></div>
+<div style="position:absolute;left:331px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:indigo;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured lists: 614"></div>
+<div style="position:absolute;left:471px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:indigo;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured lists: 563"></div>
+<div style="position:absolute;left:611px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:indigo;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Featured lists: 122"></div>
+<div style="position:absolute;left:51px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:lightblue;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="A-class articles: 189"></div>
+<div style="position:absolute;left:191px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:lightblue;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="A-class articles: 363"></div>
+<div style="position:absolute;left:331px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:lightblue;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="A-class articles: 534"></div>
+<div style="position:absolute;left:471px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:lightblue;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="A-class articles: 294"></div>
+<div style="position:absolute;left:611px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:lightblue;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="A-class articles: 69"></div>
+<div style="position:absolute;left:51px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:darkgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Good articles: 1,757"></div>
+<div style="position:absolute;left:191px;top:619px;height:1px;min-width:38px;max-width:38px;background-color:darkgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Good articles: 4,109"></div>
+<div style="position:absolute;left:331px;top:618px;height:2px;min-width:38px;max-width:38px;background-color:darkgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Good articles: 8,016"></div>
+<div style="position:absolute;left:471px;top:618px;height:2px;min-width:38px;max-width:38px;background-color:darkgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Good articles: 7,927"></div>
+<div style="position:absolute;left:611px;top:620px;height:0px;min-width:38px;max-width:38px;background-color:darkgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Good articles: 1,562"></div>
+<div style="position:absolute;left:51px;top:618px;height:2px;min-width:38px;max-width:38px;background-color:lightgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="B-class articles: 10,883"></div>
+<div style="position:absolute;left:191px;top:615px;height:4px;min-width:38px;max-width:38px;background-color:lightgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="B-class articles: 20,924"></div>
+<div style="position:absolute;left:331px;top:611px;height:7px;min-width:38px;max-width:38px;background-color:lightgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="B-class articles: 31,625"></div>
+<div style="position:absolute;left:471px;top:613px;height:5px;min-width:38px;max-width:38px;background-color:lightgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="B-class articles: 23,722"></div>
+<div style="position:absolute;left:611px;top:617px;height:3px;min-width:38px;max-width:38px;background-color:lightgreen;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="B-class articles: 12,919"></div>
+<div style="position:absolute;left:51px;top:616px;height:2px;min-width:38px;max-width:38px;background-color:yellow;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="C-class articles: 8,574"></div>
+<div style="position:absolute;left:191px;top:610px;height:5px;min-width:38px;max-width:38px;background-color:yellow;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="C-class articles: 24,667"></div>
+<div style="position:absolute;left:331px;top:601px;height:11px;min-width:38px;max-width:38px;background-color:yellow;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="C-class articles: 54,790"></div>
+<div style="position:absolute;left:471px;top:600px;height:14px;min-width:38px;max-width:38px;background-color:yellow;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="C-class articles: 68,867"></div>
+<div style="position:absolute;left:611px;top:609px;height:8px;min-width:38px;max-width:38px;background-color:yellow;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="C-class articles: 37,261"></div>
+<div style="position:absolute;left:51px;top:613px;height:3px;min-width:38px;max-width:38px;background-color:orange;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Start-class articles: 15,548"></div>
+<div style="position:absolute;left:191px;top:596px;height:14px;min-width:38px;max-width:38px;background-color:orange;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Start-class articles: 67,227"></div>
+<div style="position:absolute;left:331px;top:544px;height:56px;min-width:38px;max-width:38px;background-color:orange;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Start-class articles: 272,795"></div>
+<div style="position:absolute;left:471px;top:467px;height:132px;min-width:38px;max-width:38px;background-color:orange;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Start-class articles: 640,683"></div>
+<div style="position:absolute;left:611px;top:557px;height:53px;min-width:38px;max-width:38px;background-color:orange;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Start-class articles: 258,114"></div>
+<div style="position:absolute;left:51px;top:612px;height:1px;min-width:38px;max-width:38px;background-color:red;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Stub articles: 3,902"></div>
+<div style="position:absolute;left:191px;top:590px;height:6px;min-width:38px;max-width:38px;background-color:red;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Stub articles: 27,452"></div>
+<div style="position:absolute;left:331px;top:502px;height:42px;min-width:38px;max-width:38px;background-color:red;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Stub articles: 201,827"></div>
+<div style="position:absolute;left:471px;top:143px;height:324px;min-width:38px;max-width:38px;background-color:red;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Stub articles: 1,568,976"></div>
+<div style="position:absolute;left:611px;top:383px;height:173px;min-width:38px;max-width:38px;background-color:red;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Stub articles: 838,139"></div>
+<div style="position:absolute;left:51px;top:611px;height:1px;min-width:38px;max-width:38px;background-color:purple;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Lists: 2,531"></div>
+<div style="position:absolute;left:191px;top:588px;height:2px;min-width:38px;max-width:38px;background-color:purple;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Lists: 9,435"></div>
+<div style="position:absolute;left:331px;top:496px;height:6px;min-width:38px;max-width:38px;background-color:purple;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Lists: 27,511"></div>
+<div style="position:absolute;left:471px;top:128px;height:15px;min-width:38px;max-width:38px;background-color:purple;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Lists: 73,543"></div>
+<div style="position:absolute;left:611px;top:371px;height:12px;min-width:38px;max-width:38px;background-color:purple;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Lists: 57,349"></div>
+<div style="position:absolute;left:51px;top:611px;height:0px;min-width:38px;max-width:38px;background-color:black;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Unassessed articles and lists: 103"></div>
+<div style="position:absolute;left:191px;top:588px;height:0px;min-width:38px;max-width:38px;background-color:black;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Unassessed articles and lists: 320"></div>
+<div style="position:absolute;left:331px;top:497px;height:0px;min-width:38px;max-width:38px;background-color:black;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Unassessed articles and lists: 1,647"></div>
+<div style="position:absolute;left:471px;top:124px;height:4px;min-width:38px;max-width:38px;background-color:black;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Unassessed articles and lists: 18,791"></div>
+<div style="position:absolute;left:611px;top:274px;height:97px;min-width:38px;max-width:38px;background-color:black;box-shadow:2px -1px 4px 0 silver;overflow:hidden;" title="Unassessed articles and lists: 471,002"></div>
+</div>
+<div style="position:absolute;height:620px;min-width:100px;max-width:100px;">
+<div style="position:absolute;height=20px;text-align:right;vertical-align:middle;width:90px;top:507px;padding:0 2px">500,000</div>
+<div style="position:absolute;height=1px;min-width:5px;top:517px;left:96px;border:1px solid black;"></div>
+<div style="position:absolute;height=20px;text-align:right;vertical-align:middle;width:90px;top:403px;padding:0 2px">1,000,000</div>
+<div style="position:absolute;height=1px;min-width:5px;top:413px;left:96px;border:1px solid black;"></div>
+<div style="position:absolute;height=20px;text-align:right;vertical-align:middle;width:90px;top:300px;padding:0 2px">1,500,000</div>
+<div style="position:absolute;height=1px;min-width:5px;top:310px;left:96px;border:1px solid black;"></div>
+<div style="position:absolute;height=20px;text-align:right;vertical-align:middle;width:90px;top:197px;padding:0 2px">2,000,000</div>
+<div style="position:absolute;height=1px;min-width:5px;top:207px;left:96px;border:1px solid black;"></div>
+<div style="position:absolute;height=20px;text-align:right;vertical-align:middle;width:90px;top:93px;padding:0 2px">2,500,000</div>
+<div style="position:absolute;height=1px;min-width:5px;top:103px;left:96px;border:1px solid black;"></div>
+<div style="position:absolute;height=20px;text-align:right;vertical-align:middle;width:90px;top:-10px;padding:0 2px">3,000,000</div>
+<div style="position:absolute;height=1px;min-width:5px;top:0px;left:96px;border:1px solid black;"></div>
+</div>
+<div style="position:absolute;top:620px;left:100px;width:700px;">
+<div style="position:absolute;left:5px;top:10px;min-width:130px;max-width:130px;text-align:center;veritical-align:top;">Top</div>
+<div style="position:absolute;left:70px;height:10px;width:1px;border-left:1px solid black;"></div>
+<div style="position:absolute;left:145px;top:10px;min-width:130px;max-width:130px;text-align:center;veritical-align:top;">High</div>
+<div style="position:absolute;left:210px;height:10px;width:1px;border-left:1px solid black;"></div>
+<div style="position:absolute;left:285px;top:10px;min-width:130px;max-width:130px;text-align:center;veritical-align:top;">Medium</div>
+<div style="position:absolute;left:350px;height:10px;width:1px;border-left:1px solid black;"></div>
+<div style="position:absolute;left:425px;top:10px;min-width:130px;max-width:130px;text-align:center;veritical-align:top;">Low</div>
+<div style="position:absolute;left:490px;height:10px;width:1px;border-left:1px solid black;"></div>
+<div style="position:absolute;left:565px;top:10px;min-width:130px;max-width:130px;text-align:center;veritical-align:top;">???</div>
+<div style="position:absolute;left:630px;height:10px;width:1px;border-left:1px solid black;"></div>
+</div>
+</div>
+<div>
+<ul style="width:100%;list-style:none;-webkit-column-width:12em;-moz-column-width:12em;column-width:12em;">
+<li><span style="padding:0 1em;background-color:violet;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> Featured articles</li>
+<li><span style="padding:0 1em;background-color:indigo;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> Featured lists</li>
+<li><span style="padding:0 1em;background-color:lightblue;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> A-class articles</li>
+<li><span style="padding:0 1em;background-color:darkgreen;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> Good articles</li>
+<li><span style="padding:0 1em;background-color:lightgreen;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> B-class articles</li>
+<li><span style="padding:0 1em;background-color:yellow;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> C-class articles</li>
+<li><span style="padding:0 1em;background-color:orange;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> Start-class articles</li>
+<li><span style="padding:0 1em;background-color:red;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> Stub articles</li>
+<li><span style="padding:0 1em;background-color:purple;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> Lists</li>
+<li><span style="padding:0 1em;background-color:black;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"> </span> Unassessed articles and lists</li>
+</ul>
+</div>
+</div>
+<p><i>[Note: The table above (prepared by the <a href="/wiki/Wikipedia:Version_1.0_Editorial_Team" title="Wikipedia:Version 1.0 Editorial Team">Wikipedia Version 1.0 Editorial Team</a>) is automatically updated daily by <a href="/wiki/User:WP_1.0_bot" title="User:WP 1.0 bot">User:WP 1.0 bot</a>, but the bar-chart and the two pie-charts are not auto-updated. In them, new data has to be entered by a Wikipedia editor.]</i></p>
+<h3><span class="mw-headline" id="Hardware_operations_and_support">Hardware operations and support</span></h3>
+<div class="hatnote">See also: <a href="/wiki/Wikimedia_Foundation#Hardware" title="Wikimedia Foundation">Wikimedia Foundation § Hardware</a></div>
+<p>Wikipedia receives between 25,000 and 60,000 page requests per second, depending on time of day.<sup id="cite_ref-WP_tools_requests_per_day_248-0" class="reference"><a href="#cite_note-WP_tools_requests_per_day-248"><span>[</span>242<span>]</span></a></sup> As of 2008<sup class="plainlinks noprint asof-tag update" style="display:none;"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Wikipedia&action=edit">[update]</a></sup> page requests are first passed to a f [...]
+<p>Wikipedia currently runs on dedicated <a href="/wiki/Cluster_(computing)" title="Cluster (computing)" class="mw-redirect">clusters</a> of <a href="/wiki/Linux" title="Linux">Linux</a> servers (mainly <a href="/wiki/Ubuntu_(operating_system)" title="Ubuntu (operating system)">Ubuntu</a>).<sup id="cite_ref-CW_WP_simplifies_infrastructure_251-0" class="reference"><a href="#cite_note-CW_WP_simplifies_infrastructure-251"><span>[</span>245<span>]</span></a></sup><sup id="cite_ref-ars_tech_U [...]
+<div class="center">
+<div class="thumb tnone">
+<div class="thumbinner" style="width:702px;"><a href="/wiki/File:Wikimedia-servers-2010-12-28.svg" class="image"><img alt="Diagram showing flow of data between Wikipedia's servers. Twenty database servers talk to hundreds of Apache servers in the backend; the Apache servers talk to fifty squids in the frontend." src="//upload.wikimedia.org/wikipedia/commons/thumb/d/d8/Wikimedia-servers-2010-12-28.svg/700px-Wikimedia-servers-2010-12-28.svg.png" width="700" height="753" class="thumbimage" [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikimedia-servers-2010-12-28.svg" class="internal" title="Enlarge"></a></div>
+Overview of system architecture, December 2010. See <a href="//meta.wikimedia.org/wiki/Server_layout_diagrams" class="extiw" title="meta:Server layout diagrams">server layout diagrams on Meta-Wiki</a></div>
+</div>
+</div>
+</div>
+<h3><span class="mw-headline" id="Internal_research_and_operational_development">Internal research and operational development</span></h3>
+<p>In accordance with growing amounts of incoming donations exceeding seven digits in 2013 as recently reported,<sup id="cite_ref-256" class="reference"><a href="#cite_note-256"><span>[</span>250<span>]</span></a></sup> the Foundation has reached a threshold of assets which qualify its consideration under the principles of <a href="/wiki/Industrial_organization" title="Industrial organization">industrial organization</a> economics to indicate the need for the re-investment of donations i [...]
+<p>According to the Michael <a href="/wiki/Porter_five_forces_analysis" title="Porter five forces analysis">Porter five forces analysis</a> framework for industry analysis, Wikipedia and its parent institution Wikimedia are known as "first movers" and "radical innovators" in the services provided and supported by an open-source, on-line encyclopedia.<sup id="cite_ref-Porter.2C_M.E._1985_260-0" class="reference"><a href="#cite_note-Porter.2C_M.E._1985-260"><span>[</span>254<span>]</span>< [...]
+<h3><span class="mw-headline" id="Internal_news_publications">Internal news publications</span></h3>
+<p>Community-produced news publications include the <a href="/wiki/English_Wikipedia" title="English Wikipedia">English Wikipedia</a>'s <a href="/wiki/Wikipedia:Signpost" title="Wikipedia:Signpost" class="mw-redirect"><i>The Signpost</i></a>, founded in 2005 by Michael Snow, an attorney, Wikipedia administrator and former chair of the <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a> board of trustees.<sup id="cite_ref-263" class="reference"><a hr [...]
+<h2><span class="mw-headline" id="Access_to_content">Access to content</span></h2>
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:Imagina_un_mundo_donde_cada_persona_tenga_acceso_a_todo_el_conocimiento_humano..JPG" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/9/91/Imagina_un_mundo_donde_cada_persona_tenga_acceso_a_todo_el_conocimiento_humano..JPG/220px-Imagina_un_mundo_donde_cada_persona_tenga_acceso_a_todo_el_conocimiento_humano..JPG" width="220" height="154" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Imagina_un_mundo_donde_cada_persona_tenga_acceso_a_todo_el_conocimiento_humano..JPG" class="internal" title="Enlarge"></a></div>
+A world where every person has access to all human knowledge.</div>
+</div>
+</div>
+<h3><span class="mw-headline" id="Content_licensing">Content licensing</span></h3>
+<p>When the project was started in 2001, all text in Wikipedia was covered by the <a href="/wiki/GNU_Free_Documentation_License" title="GNU Free Documentation License">GNU Free Documentation License</a> (GFDL), a <a href="/wiki/Copyleft" title="Copyleft">copyleft</a> license permitting the redistribution, creation of derivative works, and commercial use of content while authors retain copyright of their work.<sup id="cite_ref-WP_copyright_and_commerciality_1_264-0" class="reference"><a h [...]
+<p>The handling of media files (e.g. image files) varies across language editions. Some language editions, such as the English Wikipedia, include non-free image files under <a href="/wiki/Fair_use" title="Fair use">fair use</a> doctrine, while the others have opted not to, in part because of the lack of fair use doctrines in their home countries (e.g. in <a href="/wiki/Copyright_law_of_Japan" title="Copyright law of Japan">Japanese copyright law</a>). Media files covered by <a href="/wik [...]
+<p>The Wikimedia Foundation is not a licensor of content, but merely a hosting service for the contributors (and licensors) of the Wikipedia. This position has been successfully defended in court.<sup id="cite_ref-reuters_French_defamation_case_271-0" class="reference"><a href="#cite_note-reuters_French_defamation_case-271"><span>[</span>265<span>]</span></a></sup><sup id="cite_ref-ars_tech_WP_dumb_suing_case_272-0" class="reference"><a href="#cite_note-ars_tech_WP_dumb_suing_case-272">< [...]
+<h3><span class="mw-headline" id="Methods_of_access"><span id="Reusing_Wikipedia.27s_content"></span>Methods of access</span></h3>
+<p>Because Wikipedia content is distributed under an open license, anyone can reuse or re-distribute it at no charge. The content of Wikipedia has been published in many forms, both online and offline, outside of the Wikipedia website.</p>
+<ul>
+<li><b>Websites</b> – Thousands of "<a href="/wiki/Mirror_website" title="Mirror website" class="mw-redirect">mirror sites</a>" exist that republish content from Wikipedia: two prominent ones, that also include content from other reference sources, are <a href="/wiki/Reference.com" title="Reference.com">Reference.com</a> and <a href="/wiki/Answers.com" title="Answers.com">Answers.com</a>. Another example is <a href="/wiki/Wapedia" title="Wapedia">Wapedia</a>, which began to display Wikip [...]
+<li><b>Mobile apps</b> – A variety of mobile apps provide access to Wikipedia on <a href="/wiki/Hand-held_device" title="Hand-held device" class="mw-redirect">hand-held devices</a>, including both <a href="/wiki/Android_(operating_system)" title="Android (operating system)">Android</a> and <a href="/wiki/IOS" title="IOS">iOS</a> devices (see <a href="/wiki/Wikipedia_App" title="Wikipedia App" class="mw-redirect">Wikipedia apps</a>). (See also <a href="#Mobile_access">Mobile access</a>.)</li>
+<li><b>Search engines</b> – Some <a href="/wiki/Web_search_engine" title="Web search engine">web search engines</a> make special use of Wikipedia content when displaying search results: examples include <a href="/wiki/Bing" title="Bing">Bing</a> (via technology gained from <a href="/wiki/Powerset_(company)" title="Powerset (company)">Powerset</a>)<sup id="cite_ref-bing_WP_research_and_referencing_273-0" class="reference"><a href="#cite_note-bing_WP_research_and_referencing-273"><span>[</ [...]
+<li><b>Compact discs, DVDs</b> – Collections of Wikipedia articles have been published on <a href="/wiki/Optical_disc" title="Optical disc">optical discs</a>. An English version, <a href="/wiki/Wikipedia_CD_Selection" title="Wikipedia CD Selection" class="mw-redirect">2006 Wikipedia CD Selection</a>, contained about 2,000 articles.<sup id="cite_ref-wikipediaondvd_authorized_1_274-0" class="reference"><a href="#cite_note-wikipediaondvd_authorized_1-274"><span>[</span>268<span>]</span></a> [...]
+<li><b>Books</b> – There are efforts to put a select subset of Wikipedia's articles into printed book form.<sup id="cite_ref-WP_into_books_1_280-0" class="reference"><a href="#cite_note-WP_into_books_1-280"><span>[</span>274<span>]</span></a></sup><sup id="cite_ref-WP_schools_selection_1_281-0" class="reference"><a href="#cite_note-WP_schools_selection_1-281"><span>[</span>275<span>]</span></a></sup> Since 2009, tens of thousands of <a href="/wiki/Print_on_demand" title="Print on demand" [...]
+<li><b>Semantic Web</b> – The website <a href="/wiki/DBpedia" title="DBpedia">DBpedia</a>, begun in 2007, extracts data from the infoboxes and category declarations of the English-language Wikipedia. Wikimedia has created the <a href="/wiki/Wikidata" title="Wikidata">Wikidata</a> project with a similar objective of storing the basic facts from each page of Wikipedia and the other WMF wikis and make it available in a queriable <a href="/wiki/Semantic_Web" title="Semantic Web">semantic</a> [...]
+</ul>
+<p>Obtaining the full contents of Wikipedia for reuse presents challenges, since direct cloning via a <a href="/wiki/Web_crawler" title="Web crawler">web crawler</a> is discouraged.<sup id="cite_ref-WP_DB_usage_policy_1_283-0" class="reference"><a href="#cite_note-WP_DB_usage_policy_1-283"><span>[</span>277<span>]</span></a></sup> Wikipedia publishes <a href="/wiki/Wikipedia:Database_download" title="Wikipedia:Database download">"dumps"</a> of its contents, but these are text-only; as of [...]
+<p>Several languages of Wikipedia also maintain a <a href="/wiki/Wikipedia:REFDESK" title="Wikipedia:REFDESK" class="mw-redirect">reference desk</a>, where volunteers answer questions from the general public. According to a study by Pnina Shachaf in the <a href="/wiki/Journal_of_Documentation" title="Journal of Documentation">Journal of Documentation</a>, the quality of the Wikipedia reference desk is comparable to a standard <a href="/wiki/Library_reference_desk" title="Library referenc [...]
+<h4><span class="mw-headline" id="Mobile_access">Mobile access<span id="Wikipedia_mobile_access"></span><span id="Wikipedia_mobile"></span></span></h4>
+<div class="hatnote">See also: <a href="/wiki/Help:Mobile_access" title="Help:Mobile access">Help:Mobile access</a></div>
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:Android_2.2_GT-I5800.png" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/d/d4/Android_2.2_GT-I5800.png/220px-Android_2.2_GT-I5800.png" width="220" height="367" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/d/d4/Android_2.2_GT-I5800.png 1.5x, //upload.wikimedia.org/wikipedia/commons/d/d4/Android_2.2_GT-I5800.png 2x" data-file-width="240" data-file-height="400" /></a>
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Android_2.2_GT-I5800.png" class="internal" title="Enlarge"></a></div>
+The mobile version of the English Wikipedia's main page</div>
+</div>
+</div>
+<p>Wikipedia's original medium was for users to read and edit content using any standard <a href="/wiki/Web_browser" title="Web browser">web browser</a> through a fixed <a href="/wiki/Internet_access" title="Internet access">Internet connection</a>. Although Wikipedia content has been accessible through the <a href="/wiki/Mobile_web" title="Mobile web" class="mw-redirect">mobile web</a> since July 2013, <i>The New York Times</i> on February 9, 2014 quoted Erik Moller, deputy director of [...]
+<p><i>Bloomberg BusinessWeek</i> reported in July 2014 that Google's Android mobile apps have dominated the largest share of global smartphone shipments for 2013 with 78.6% of market share over their next closest competitor in iOS with 15.2% of the market.<sup id="cite_ref-286" class="reference"><a href="#cite_note-286"><span>[</span>280<span>]</span></a></sup> At the time of the Tretikov appointment and her posted web interview with Sue Gardner in May 2014, Wikimedia representatives mad [...]
+<p>Access to Wikipedia from mobile phones was possible as early as 2004, through the <a href="/wiki/Wireless_Application_Protocol" title="Wireless Application Protocol">Wireless Application Protocol</a> (WAP), via the <a href="/wiki/Wapedia" title="Wapedia">Wapedia</a> service. In June 2007 Wikipedia launched <a class="external text" href="http://en.mobile.wikipedia.org/">en.mobile.wikipedia.org</a>, an official website for wireless devices. In 2009 a newer mobile service was officially [...]
+<p><a href="/wiki/Wikipedia_Zero" title="Wikipedia Zero">Wikipedia Zero</a> is an initiative of the Wikimedia Foundation to expand the reach of the encyclopedia to the developing countries.<sup id="cite_ref-292" class="reference"><a href="#cite_note-292"><span>[</span>286<span>]</span></a></sup></p>
+<p><a href="/wiki/Andrew_Lih" title="Andrew Lih">Andrew Lih</a> and <a href="/wiki/Andrew_Brown_(writer)" title="Andrew Brown (writer)">Andrew Brown</a> both maintain editing Wikipedia with <a href="/wiki/Smart_phone" title="Smart phone" class="mw-redirect">smart phones</a> is difficult and this discourages new potential contributors. Several years running the number of Wikipedia editors has been falling and Tom Simonite of <i><a href="/wiki/MIT_Technology_Review" title="MIT Technology R [...]
+<h2><span class="mw-headline" id="Impact">Impact</span></h2>
+<h3><span class="mw-headline" id="Readership">Readership</span></h3>
+<p>Wikipedia is extremely popular. In February 2014, <i>The New York Times</i> reported that Wikipedia is ranked fifth globally among all websites, stating "With 18 billion page views and nearly 500 million unique visitors a month [...] Wikipedia trails just Yahoo, Facebook, Microsoft and Google, the largest with 1.2 billion unique visitors."<sup id="cite_ref-small_screen_16-5" class="reference"><a href="#cite_note-small_screen-16"><span>[</span>13<span>]</span></a></sup></p>
+<p>In addition to <a href="/wiki/Logistic_function" title="Logistic function">logistic growth</a> in the number of its articles,<sup id="cite_ref-modelling_296-0" class="reference"><a href="#cite_note-modelling-296"><span>[</span>290<span>]</span></a></sup> Wikipedia has steadily gained status as a general reference website since its inception in 2001.<sup id="cite_ref-comscore_297-0" class="reference"><a href="#cite_note-comscore-297"><span>[</span>291<span>]</span></a></sup> About 50% [...]
+<p>According to "Wikipedia Readership Survey 2011", the average age of Wikipedia readers is 36, with a rough parity between genders. Almost half of Wikipedia readers visit the site more than five times a month, and a similar number of readers specifically look for Wikipedia in search engine results. About 47% of Wikipedia readers do not realize that Wikipedia is a non-profit organization.<sup id="cite_ref-303" class="reference"><a href="#cite_note-303"><span>[</span>297<span>]</span></a> [...]
+<h3><span class="mw-headline" id="Cultural_significance">Cultural significance</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Wikipedia_in_culture" title="Wikipedia in culture">Wikipedia in culture</a></div>
+<p>Wikipedia's content has also been used in academic studies, books, conferences, and court cases.<sup id="cite_ref-Wikipedia_in_media_304-0" class="reference"><a href="#cite_note-Wikipedia_in_media-304"><span>[</span>298<span>]</span></a></sup><sup id="cite_ref-Bourgeois_305-0" class="reference"><a href="#cite_note-Bourgeois-305"><span>[</span>299<span>]</span></a></sup><sup id="cite_ref-ssrn.com_Wikipedian_Justice_1_306-0" class="reference"><a href="#cite_note-ssrn.com_Wikipedian_Just [...]
+<p>Wikipedia has also been used as a source in journalism,<sup id="cite_ref-ajr.org_WP_in_the_newsroom_312-0" class="reference"><a href="#cite_note-ajr.org_WP_in_the_newsroom-312"><span>[</span>306<span>]</span></a></sup><sup id="cite_ref-twsY23_313-0" class="reference"><a href="#cite_note-twsY23-313"><span>[</span>307<span>]</span></a></sup> often without attribution, and several reporters have been dismissed for plagiarizing from Wikipedia.<sup id="cite_ref-shizuoka_plagiarized_WP_1_31 [...]
+<p>In 2006, <a href="/wiki/Time_(magazine)" title="Time (magazine)"><i>Time</i> magazine</a> recognized Wikipedia's participation (along with <a href="/wiki/YouTube" title="YouTube">YouTube</a>, <a href="/wiki/Reddit" title="Reddit">Reddit</a>, <a href="/wiki/MySpace" title="MySpace" class="mw-redirect">MySpace</a>, and <a href="/wiki/Facebook" title="Facebook">Facebook</a><sup id="cite_ref-Time2006_317-0" class="reference"><a href="#cite_note-Time2006-317"><span>[</span>311<span>]</span [...]
+<p>In July 2007 Wikipedia was the focus of a 30-minute documentary on <a href="/wiki/BBC_Radio_4" title="BBC Radio 4">BBC Radio 4</a><sup id="cite_ref-318" class="reference"><a href="#cite_note-318"><span>[</span>312<span>]</span></a></sup> which argued that, with increased usage and awareness, the number of references to Wikipedia in popular culture is such that the word is one of a select band of 21st-century nouns that are so familiar (<a href="/wiki/Google" title="Google">Google</a>, [...]
+<p>On September 28, 2007, <a href="/wiki/Italy" title="Italy">Italian</a> politician <a href="/wiki/Franco_Grillini" title="Franco Grillini">Franco Grillini</a> raised a parliamentary question with the minister of cultural resources and activities about the necessity of <a href="/wiki/Freedom_of_panorama" title="Freedom of panorama">freedom of panorama</a>. He said that the lack of such freedom forced Wikipedia, "the seventh most consulted website", to forbid all images of modern Italian [...]
+<div class="thumb tright">
+<div class="thumbinner" style="width:262px;">
+<div id="mwe_player_6" class="PopUpMediaTransform" style="width:260px;" videopayload="<div class="mediaContainer" style="width:854px"><video id="mwe_player_7" style="width:854px;height:480px" poster="//upload.wikimedia.org/wikipedia/commons/thumb/3/33/Wikipedia%2C_an_introduction_-_Erasmus_Prize_2015.webm/854px--Wikipedia%2C_an_introduction_-_Erasmus_Prize_2015.webm.jpg" controls="" preload="none" autoplay=&qu [...]
+You can <a href="//upload.wikimedia.org/wikipedia/commons/3/33/Wikipedia%2C_an_introduction_-_Erasmus_Prize_2015.webm">download the clip</a> or <a href="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:TimedMediaHandler/Client_download">download a player</a> to play the clip in your browser.</video></div>"><img alt="File:Wikipedia, an introduction - Erasmus Prize 2015.webm" style="width:260px;height:146px" src="//upload.w [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikipedia,_an_introduction_-_Erasmus_Prize_2015.webm" class="internal" title="Enlarge"></a></div>
+Wikipedia, an introduction - Erasmus Prize 2015</div>
+</div>
+</div>
+<div class="thumb tright">
+<div class="thumbinner" style="width:262px;"><a href="/wiki/File:Quadriga-verleihung-rr-02.jpg" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Quadriga-verleihung-rr-02.jpg/260px-Quadriga-verleihung-rr-02.jpg" width="260" height="209" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Quadriga-verleihung-rr-02.jpg/390px-Quadriga-verleihung-rr-02.jpg 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Quadriga-verleihun [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Quadriga-verleihung-rr-02.jpg" class="internal" title="Enlarge"></a></div>
+<a href="/wiki/Jimmy_Wales" title="Jimmy Wales">Jimmy Wales</a> receiving the <a href="/wiki/Quadriga_(award)" title="Quadriga (award)">Quadriga</a> <i>A Mission of Enlightenment</i> award</div>
+</div>
+</div>
+<p>On September 16, 2007, <i><a href="/wiki/The_Washington_Post" title="The Washington Post">The Washington Post</a></i> reported that Wikipedia had become a focal point in the <a href="/wiki/United_States_presidential_election,_2008" title="United States presidential election, 2008">2008 US election campaign</a>, saying: "Type a candidate's name into Google, and among the first results is a Wikipedia page, making those entries arguably as important as any ad in defining a candidate. Alr [...]
+<p>Active participation also has an impact. Law students have been assigned to write Wikipedia articles as an exercise in clear and succinct writing for an uninitiated audience.<sup id="cite_ref-LER_students_write_for_WP_1_322-0" class="reference"><a href="#cite_note-LER_students_write_for_WP_1-322"><span>[</span>316<span>]</span></a></sup></p>
+<h4><span class="mw-headline" id="Awards">Awards</span></h4>
+<div class="thumb tright">
+<div class="thumbinner" style="width:262px;"><a href="/wiki/File:Wikipedia_team_visiting_to_Parliament_of_Oviedo_Spain_2015.JPG" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Wikipedia_team_visiting_to_Parliament_of_Oviedo_Spain_2015.JPG/260px-Wikipedia_team_visiting_to_Parliament_of_Oviedo_Spain_2015.JPG" width="260" height="173" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Wikipedia_team_visiting_to_Parliament_of_Ov [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Wikipedia_team_visiting_to_Parliament_of_Oviedo_Spain_2015.JPG" class="internal" title="Enlarge"></a></div>
+Wikipedia team visiting to Parliament of Asturias.</div>
+</div>
+</div>
+<div class="thumb tright">
+<div class="thumbinner" style="width:262px;"><a href="/wiki/File:Spain_wikipedians_photo.JPG" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/9/97/Spain_wikipedians_photo.JPG/260px-Spain_wikipedians_photo.JPG" width="260" height="173" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/9/97/Spain_wikipedians_photo.JPG/390px-Spain_wikipedians_photo.JPG 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/9/97/Spain_wikipedians_photo.JPG/ [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Spain_wikipedians_photo.JPG" class="internal" title="Enlarge"></a></div>
+Wikipedians meeting after the Asturias awards ceremony.</div>
+</div>
+</div>
+<p>Wikipedia won two major awards in May 2004.<sup id="cite_ref-WP_awards_for_WP_1_323-0" class="reference"><a href="#cite_note-WP_awards_for_WP_1-323"><span>[</span>317<span>]</span></a></sup> The first was a Golden Nica for Digital Communities of the annual <a href="/wiki/Prix_Ars_Electronica" title="Prix Ars Electronica">Prix Ars Electronica</a> contest; this came with a €10,000 (£6,588; $12,700) grant and an invitation to present at the PAE Cyberarts Festival in <a href="/wiki/Austri [...]
+<p>In 2007, readers of brandchannel.com voted Wikipedia as the fourth-highest brand ranking, receiving 15% of the votes in answer to the question "Which brand had the most impact on our lives in 2006?"<sup id="cite_ref-brandchannel.com_awards_1_325-0" class="reference"><a href="#cite_note-brandchannel.com_awards_1-325"><span>[</span>319<span>]</span></a></sup></p>
+<p>In September 2008, Wikipedia received <a href="/wiki/Quadriga_(award)" title="Quadriga (award)">Quadriga</a> <i>A Mission of Enlightenment</i> award of Werkstatt Deutschland along with <a href="/wiki/Boris_Tadi%C4%87" title="Boris Tadić">Boris Tadić</a>, <a href="/wiki/Eckart_H%C3%B6fling" title="Eckart Höfling">Eckart Höfling</a>, and <a href="/wiki/Peter_Gabriel" title="Peter Gabriel">Peter Gabriel</a>. The award was presented to Wales by <a href="/wiki/David_Weinberger" title="Davi [...]
+<p>In 2015, Wikipedia was awarded both the annual <a href="/wiki/Erasmus_Prize" title="Erasmus Prize">Erasmus Prize</a>, which recognizes exceptional contributions to culture, society or social sciences,<sup id="cite_ref-EP2015_327-0" class="reference"><a href="#cite_note-EP2015-327"><span>[</span>321<span>]</span></a></sup> and the <a href="/wiki/Spain" title="Spain">Spanish</a> <a href="/wiki/Princess_of_Asturias_Award" title="Princess of Asturias Award" class="mw-redirect">Princess of [...]
+<h4><span class="mw-headline" id="Satire">Satire</span></h4>
+<div class="hatnote">See also: <a href="/wiki/Category:Parodies_of_Wikipedia" title="Category:Parodies of Wikipedia">Category:Parodies of Wikipedia</a>.</div>
+<p>Many parodies target Wikipedia's openness and susceptibility to inserted inaccuracies, with characters vandalizing or modifying the online encyclopedia project's articles.</p>
+<p>Comedian <a href="/wiki/Stephen_Colbert" title="Stephen Colbert">Stephen Colbert</a> has parodied or referenced Wikipedia on numerous episodes of his show <i><a href="/wiki/The_Colbert_Report" title="The Colbert Report">The Colbert Report</a></i> and coined the related term <i><a href="/wiki/Wikiality" title="Wikiality" class="mw-redirect">wikiality</a></i>, meaning "together we can create a reality that we all agree on—the reality we just agreed on".<sup id="cite_ref-wikiality_173-1" [...]
+<p>In an episode of the television comedy <a href="/wiki/The_Office_(U.S._TV_series)" title="The Office (U.S. TV series)"><i>The Office</i> U.S.</a>, which aired in April 2007, an incompetent office manager (<a href="/wiki/Michael_Scott_(The_Office)" title="Michael Scott (The Office)">Michael Scott</a>) is shown relying on a hypothetical Wikipedia article for information on <a href="/wiki/Negotiation" title="Negotiation">negotiation</a> tactics in order to assist him in negotiating lesse [...]
+<p>"<a href="/wiki/My_Number_One_Doctor" title="My Number One Doctor" class="mw-redirect">My Number One Doctor</a>", a 2007 episode of the television show <i><a href="/wiki/Scrubs_(TV_series)" title="Scrubs (TV series)">Scrubs</a></i>, played on the perception that Wikipedia is an unreliable reference tool with a scene in which <a href="/wiki/Perry_Cox" title="Perry Cox">Dr. Perry Cox</a> reacts to a patient who says that a Wikipedia article indicates that the <a href="/wiki/Raw_food_die [...]
+<p>In 2008, the comedic website <i><a href="/wiki/CollegeHumor" title="CollegeHumor">CollegeHumor</a></i> produced a video sketch named "Professor Wikipedia", in which the fictitious Professor Wikipedia instructs a class with a medley of unverifiable and occasionally absurd statements.<sup id="cite_ref-collegehumor.com_WP_funny_1_335-0" class="reference"><a href="#cite_note-collegehumor.com_WP_funny_1-335"><span>[</span>329<span>]</span></a></sup></p>
+<p>The <i><a href="/wiki/Dilbert" title="Dilbert">Dilbert</a></i> comic strip from May 8, 2009, features a character supporting an improbable claim by saying "Give me ten minutes and then check Wikipedia."<sup id="cite_ref-dilbert_WP_funny_1_336-0" class="reference"><a href="#cite_note-dilbert_WP_funny_1-336"><span>[</span>330<span>]</span></a></sup></p>
+<p>In July 2009, <a href="/wiki/BBC_Radio_4" title="BBC Radio 4">BBC Radio 4</a> broadcast a comedy series called <i><a href="/wiki/Bigipedia" title="Bigipedia">Bigipedia</a></i>, which was set on a website which was a parody of Wikipedia. Some of the sketches were directly inspired by Wikipedia and its articles.<sup id="cite_ref-comedy.org.uk_WP_funny_1_337-0" class="reference"><a href="#cite_note-comedy.org.uk_WP_funny_1-337"><span>[</span>331<span>]</span></a></sup></p>
+<p>In 2010, comedian Daniel Tosh encouraged viewers of his show, <i><a href="/wiki/Tosh.0" title="Tosh.0">Tosh.0</a></i>, to visit the show's Wikipedia article and edit it at will. On a later episode, he commented on the edits to the article, most of them offensive, which had been made by the audience and had prompted the article to be locked from editing.<sup id="cite_ref-tosh_CC_WP_funny_1_338-0" class="reference"><a href="#cite_note-tosh_CC_WP_funny_1-338"><span>[</span>332<span>]</sp [...]
+<p>On August 23, 2013, the <i><a href="/wiki/The_New_Yorker" title="The New Yorker">New Yorker</a></i> <a href="/wiki/Website" title="Website">website</a> published a cartoon with this caption: "Dammit, <a href="/wiki/Bradley_Manning" title="Bradley Manning" class="mw-redirect">Manning,</a> have you considered the pronoun war that this is going to start on your Wikipedia page?"<sup id="cite_ref-340" class="reference"><a href="#cite_note-340"><span>[</span>334<span>]</span></a></sup></p>
+<h3><span class="mw-headline" id="Sister_projects_.E2.80.93_Wikimedia">Sister projects – Wikimedia</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Wikimedia_project" title="Wikimedia project">Wikimedia project</a></div>
+<p>Wikipedia has also spawned several sister projects, which are also wikis run by the <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a>. These other <a href="/wiki/Wikimedia_projects" title="Wikimedia projects" class="mw-redirect">Wikimedia projects</a> include <a href="/wiki/Wiktionary" title="Wiktionary">Wiktionary</a>, a dictionary project launched in December 2002,<sup id="cite_ref-WM_dictionary_1_341-0" class="reference"><a href="#cite_note- [...]
+<h3><span class="mw-headline" id="Publishing">Publishing</span></h3>
+<div class="thumb tright">
+<div class="thumbinner" style="width:222px;"><a href="/wiki/File:WikiMedia_DC_2013_Annual_Meeting_08.JPG" class="image"><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/2/28/WikiMedia_DC_2013_Annual_Meeting_08.JPG/220px-WikiMedia_DC_2013_Annual_Meeting_08.JPG" width="220" height="146" class="thumbimage" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/2/28/WikiMedia_DC_2013_Annual_Meeting_08.JPG/330px-WikiMedia_DC_2013_Annual_Meeting_08.JPG 1.5x, //upload.wikimedia.o [...]
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:WikiMedia_DC_2013_Annual_Meeting_08.JPG" class="internal" title="Enlarge"></a></div>
+A group of Wikimedians of the <a rel="nofollow" class="external text" href="http://wikimediadc.org/wiki/Home">Wikimedia DC chapter</a> at the 2013 DC Wikimedia annual meeting standing in front of the Encyclopædia Britannica (back left) at the US National Archives</div>
+</div>
+</div>
+<p>The most obvious economic effect of Wikipedia has been the death of commercial encyclopedias, especially the printed versions, e.g. <i><a href="/wiki/Encyclopaedia_Britannica" title="Encyclopaedia Britannica" class="mw-redirect">Encyclopaedia Britannica</a>,</i> which were unable to compete with a product that is essentially free.<sup id="cite_ref-343" class="reference"><a href="#cite_note-343"><span>[</span>337<span>]</span></a></sup><sup id="cite_ref-344" class="reference"><a href=" [...]
+<p>There is also an ongoing debate about the influence of Wikipedia on the biography publishing business. "The worry is that, if you can get all that information from Wikipedia, what's left for biography?" said <a href="/wiki/Kathryn_Hughes" title="Kathryn Hughes">Kathryn Hughes</a>, professor of life writing at UEA and author of <i>The Short Life and Long Times of Mrs Beeton</i> and <i>George Eliot: the Last Victorian</i>.<sup id="cite_ref-348" class="reference"><a href="#cite_note-348" [...]
+<h3><span class="mw-headline" id="Scientific_use">Scientific use</span></h3>
+<p>In <a href="/wiki/Computational_linguistics" title="Computational linguistics">computational linguistics</a>, <a href="/wiki/Information_retrieval" title="Information retrieval">information retrieval</a> and <a href="/wiki/Natural_language_processing" title="Natural language processing">natural language processing</a>, Wikipedia has seen widespread use as a <a href="/wiki/Text_corpus" title="Text corpus">corpus</a> for linguistic research. In particular, it commonly serves as a target [...]
+<h2><span class="mw-headline" id="Related_projects">Related projects</span></h2>
+<p>A number of interactive multimedia encyclopedias incorporating entries written by the public existed long before Wikipedia was founded. The first of these was the 1986 <a href="/wiki/BBC_Domesday_Project" title="BBC Domesday Project">BBC Domesday Project</a>, which included text (entered on <a href="/wiki/BBC_Micro" title="BBC Micro">BBC Micro</a> computers) and photographs from over 1 million contributors in the UK, and covered the geography, art, and culture of the UK. This was [...]
+<p>One of the most successful early online encyclopedias incorporating entries by the public was <a href="/wiki/H2g2" title="H2g2">h2g2</a>, which was created by <a href="/wiki/Douglas_Adams" title="Douglas Adams">Douglas Adams</a>. The h2g2 encyclopedia is relatively light-hearted, focusing on articles which are both witty and informative. <a href="/wiki/Everything2" title="Everything2">Everything2</a> was created in 1998. All of these projects had similarities with Wikipedia, but were [...]
+<p><a href="/wiki/GNE_(encyclopedia)" title="GNE (encyclopedia)">GNE</a>, an encyclopedia which was not a wiki, also created in January 2001, co-existed with Nupedia and Wikipedia early in its history; however, it has been retired.<sup id="cite_ref-stallman1999_21-1" class="reference"><a href="#cite_note-stallman1999-21"><span>[</span>18<span>]</span></a></sup></p>
+<p>Other websites centered on collaborative <a href="/wiki/Knowledge_base" title="Knowledge base">knowledge base</a> development have drawn inspiration from Wikipedia. Some, such as <a href="/wiki/Susning.nu" title="Susning.nu">Susning.nu</a>, <a href="/wiki/Enciclopedia_Libre_Universal_en_Espa%C3%B1ol" title="Enciclopedia Libre Universal en Español">Enciclopedia Libre</a>, <a href="/wiki/Hudong" title="Hudong" class="mw-redirect">Hudong</a>, and <a href="/wiki/Baidu_Baike" title="Baidu [...]
+<h2><span class="mw-headline" id="See_also">See also</span></h2>
+<div class="noprint portal tright" style="border:solid #aaa 1px;margin:0.5em 0 0.5em 1em">
+<table style="background:#f9f9f9;font-size:85%;line-height:110%;max-width:175px">
+<tr style="vertical-align:middle">
+<td style="text-align:center"><a href="/wiki/File:Crystal_Clear_app_browser.png" class="image"><img alt="Portal icon" src="//upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Crystal_Clear_app_browser.png/28px-Crystal_Clear_app_browser.png" width="28" height="28" class="noviewer" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Crystal_Clear_app_browser.png/42px-Crystal_Clear_app_browser.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Crystal_Clear_app_browser.pn [...]
+<td style="padding:0 0.2em;vertical-align:middle;font-style:italic;font-weight:bold"><a href="/wiki/Portal:Internet" title="Portal:Internet">Internet portal</a></td>
+</tr>
+</table>
+</div>
+<div class="div-col columns column-count column-count-2" style="-moz-column-count: 2; -webkit-column-count: 2; column-count: 2;">
+<ul>
+<li><a href="/wiki/Outline_of_Wikipedia" title="Outline of Wikipedia">Outline of Wikipedia</a> – guide to the subject of <i>Wikipedia</i> presented as a <a href="/wiki/Tree_structure" title="Tree structure">tree structured</a> list of its subtopics; for an outline of the contents of <i>Wikipedia</i>, see <a href="/wiki/Portal:Contents/Outlines" title="Portal:Contents/Outlines">Portal:Contents/Outlines</a></li>
+<li><a href="/wiki/Conflict-of-interest_editing_on_Wikipedia" title="Conflict-of-interest editing on Wikipedia">Conflict-of-interest editing on Wikipedia</a></li>
+<li><a href="/wiki/Democratization_of_knowledge" title="Democratization of knowledge">Democratization of knowledge</a></li>
+<li><a href="/wiki/Interpedia" title="Interpedia">Interpedia</a>, an early proposal for a collaborative <a href="/wiki/Internet" title="Internet">Internet</a> encyclopedia</li>
+<li><a href="/wiki/List_of_Internet_encyclopedias" title="List of Internet encyclopedias" class="mw-redirect">List of Internet encyclopedias</a></li>
+<li><a href="/wiki/Network_effect" title="Network effect">Network effect</a></li>
+<li><a href="/wiki/Print_Wikipedia" title="Print Wikipedia">Print Wikipedia</a> art project to visualize how big Wikipedia is. In cooperation with Wikimedia foundation.</li>
+<li><a href="/wiki/QRpedia" title="QRpedia">QRpedia</a> – multilingual, mobile interface to Wikipedia</li>
+<li><a href="/wiki/Wikipedia_Review" title="Wikipedia Review">Wikipedia Review</a></li>
+</ul>
+</div>
+<p><b>Special searches</b></p>
+<ul>
+<li><span class="selfreference"><a href="/wiki/Special:Search/intitle:Wikipedia" title="Special:Search/intitle:Wikipedia">All pages with titles containing "Wikipedia"</a></span></li>
+<li><span class="selfreference"><a href="/wiki/Special:PrefixIndex/Wikipedia" title="Special:PrefixIndex/Wikipedia">All pages beginning with "Wikipedia"</a></span></li>
+</ul>
+<h2><span class="mw-headline" id="References">References</span></h2>
+<div class="reflist columns references-column-width" style="-moz-column-width: 30em; -webkit-column-width: 30em; column-width: 30em; list-style-type: decimal;">
+<ol class="references">
+<li id="cite_note-2"><span class="mw-cite-backlink"><b><a href="#cite_ref-2">^</a></b></span> <span class="reference-text"><cite class="citation news">Kiss, Jemima; Gibbs, Samuel (August 6, 2014). <a rel="nofollow" class="external text" href="http://www.theguardian.com/technology/2014/aug/06/wikipedia-lila-tretikov-glasnost-freedom-of-information">"Wikipedia boss Lila Tretikov: 'Glasnost taught me much about freedom of information"</a>. <i>The Guardian</i><span class="reference-accessdat [...]
+<li id="cite_note-roadchap-4"><span class="mw-cite-backlink"><b><a href="#cite_ref-roadchap_4-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Roger Chapman. <a rel="nofollow" class="external text" href="http://rogchap.com/2011/09/06/top-40-website-programming-languages/">"Top 40 Website Programming Languages"</a>. <i>roadchap.com</i><span class="reference-accessdate">. Retrieved <span class="nowrap">September 6,</span> 2011</span>.</cite><span title="ctx_ver=Z [...]
+<li id="cite_note-Sidener-5"><span class="mw-cite-backlink"><b><a href="#cite_ref-Sidener_5-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Jonathan Sidener. <a rel="nofollow" class="external text" href="http://www.signonsandiego.com/uniontrib/20041206/news_mz1b6encyclo.html">"Everyone's Encyclopedia"</a>. <i><a href="/wiki/U-T_San_Diego" title="U-T San Diego" class="mw-redirect">U-T San Diego</a></i><span class="reference-accessdate">. Retrieved <span class= [...]
+<li id="cite_note-Alexa_siteinfo-6"><span class="mw-cite-backlink">^ <a href="#cite_ref-Alexa_siteinfo_6-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Alexa_siteinfo_6-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-Alexa_siteinfo_6-2"><sup><i><b>c</b></i></sup></a> <a href="#cite_ref-Alexa_siteinfo_6-3"><sup><i><b>d</b></i></sup></a></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.alexa.com/siteinfo/wiki [...]
+<li id="cite_note-7"><span class="mw-cite-backlink"><b><a href="#cite_ref-7">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.theverge.com/2015/9/4/9260981/jimmy-wales-wikipedia-china">"Wikipedia founder defends decision to encrypt the site in China"</a>. <i>The Verge</i><span class="reference-accessdate">. Retrieved <span class="nowrap">September 19,</span> 2015</span>.</cite><span title="ctx_ver=Z39.88-200 [...]
+<li id="cite_note-Tancer-8"><span class="mw-cite-backlink"><b><a href="#cite_ref-Tancer_8-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Bill Tancer (May 1, 2007). <a rel="nofollow" class="external text" href="http://www.time.com/time/business/article/0,8599,1595184,00.html">"Look Who's Using Wikipedia"</a>. <i><a href="/wiki/Time_(magazine)" title="Time (magazine)">Time</a></i><span class="reference-accessdate">. Retrieved <span class="nowrap">December 1,</ [...]
+<li id="cite_note-Woodson-9"><span class="mw-cite-backlink"><b><a href="#cite_ref-Woodson_9-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Alex Woodson (July 8, 2007). <a rel="nofollow" class="external text" href="http://www.reuters.com/article/internetNews/idUSN0819429120070708">"Wikipedia remains go-to site for online news"</a>. Reuters<span class="reference-accessdate">. Retrieved <span class="nowrap">December 16,</span> 2007</span>. <q>Online encyclopedi [...]
+<li id="cite_note-10"><span class="mw-cite-backlink"><b><a href="#cite_ref-10">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.comscore.com/Insights/Press_Releases/2012/9/comScore_Media_Metrix_Ranks_Top_50_US_Web_Properties_for_August_201">"comScore MMX Ranks Top 50 US Web Properties for August 2012"</a>. comScore. September 12, 2012<span class="reference-accessdate">. Retrieved <span class="nowrap">Februar [...]
+<li id="cite_note-MiliardWho-11"><span class="mw-cite-backlink"><b><a href="#cite_ref-MiliardWho_11-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Mike Miliard (March 1, 2008). <a rel="nofollow" class="external text" href="http://www.cityweekly.net/utah/article-5129-feature-wikipediots-who-are-these-devoted-even-obsessive-contributors-to-wikipedia.html">"Wikipediots: Who Are These Devoted, Even Obsessive Contributors to Wikipedia?"</a>. <i><a href="/wiki/Sal [...]
+<li id="cite_note-J_Sidener-12"><span class="mw-cite-backlink"><b><a href="#cite_ref-J_Sidener_12-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Sidener, Jonathan (October 9, 2006). <a rel="nofollow" class="external text" href="http://www.signonsandiego.com/news/tech/personaltech/20061009-9999-mz1b9wikiped.html">"Wikipedia family feud rooted in San Diego"</a>. <a href="/wiki/The_San_Diego_Union-Tribune" title="The San Diego Union-Tribune" class="mw-redirect" [...]
+<li id="cite_note-13"><span class="mw-cite-backlink"><b><a href="#cite_ref-13">^</a></b></span> <span class="reference-text">"Wiki" in the Hawaiian Dictionary, revised and enlarged edition, University of Hawaii Press, 1986</span></li>
+<li id="cite_note-CBS-15"><span class="mw-cite-backlink"><b><a href="#cite_ref-CBS_15-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.cbsnews.com/news/wikipedia-jimmy-wales-morley-safer-60-minutes/">"Wikipedia cofounder Jimmy Wales on 60 Minutes"</a>. <a href="/wiki/CBS_News" title="CBS News">CBS News</a><span class="reference-accessdate">. Retrieved <span class="nowrap">April 6,</span> 2015</span>.</cit [...]
+<li id="cite_note-small_screen-16"><span class="mw-cite-backlink">^ <a href="#cite_ref-small_screen_16-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-small_screen_16-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-small_screen_16-2"><sup><i><b>c</b></i></sup></a> <a href="#cite_ref-small_screen_16-3"><sup><i><b>d</b></i></sup></a> <a href="#cite_ref-small_screen_16-4"><sup><i><b>e</b></i></sup></a> <a href="#cite_ref-small_screen_16-5"><sup><i><b>f</b></i></sup></a></span> <sp [...]
+<li id="cite_note-GilesJ2005Internet-17"><span class="mw-cite-backlink">^ <a href="#cite_ref-GilesJ2005Internet_17-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-GilesJ2005Internet_17-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation journal"><a href="/wiki/Jim_Giles_(reporter)" title="Jim Giles (reporter)">Jim Giles</a> (December 2005). <a rel="nofollow" class="external text" href="http://www.nature.com/nature/journal/v438/n7070/full/43890 [...]
+<ul>
+<li><cite class="citation news"><a rel="nofollow" class="external text" href="http://news.bbc.co.uk/2/hi/technology/4530930.stm">"Wikipedia survives research test"</a>. BBC News. December 15, 2005.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=Wikipedia+survives+research+test&rft.date=December+15%2C+2005&rft.genre=article&rft_id=http%3A%2F%2Fnews.bbc.co.uk%2F2%2Fhi%2Ftechnology%2F4530930.stm&rft.pub=BBC+News& [...]
+</ul>
+</li>
+<li id="cite_note-EdwinBlack-18"><span class="mw-cite-backlink">^ <a href="#cite_ref-EdwinBlack_18-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-EdwinBlack_18-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-EdwinBlack_18-2"><sup><i><b>c</b></i></sup></a></span> <span class="reference-text"><a href="/wiki/Edwin_Black" title="Edwin Black">Black, Edwin</a> (April 19, 2010) <a rel="nofollow" class="external text" href="http://historynewsnetwork.org/article/125437">Wikipedia—The D [...]
+<li id="cite_note-Petrilli-19"><span class="mw-cite-backlink">^ <a href="#cite_ref-Petrilli_19-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Petrilli_19-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text">J. Petrilli , Michael (SPRING 2008/Vol.8, No.2) <a rel="nofollow" class="external text" href="http://educationnext.org/wikipedia-or-wickedpedia/">Wikipedia or Wickedpedia?</a>, <a href="/wiki/Education_Next" title="Education Next" class="mw-redirect">Education N [...]
+<li id="cite_note-20"><span class="mw-cite-backlink"><b><a href="#cite_ref-20">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.niemanlab.org/2011/10/the-contribution-conundrum-why-did-wikipedia-succeed-while-other-encyclopedias-failed/">"The contribution conundrum: Why did Wikipedia succeed while other encyclopedias failed?"</a>. <i>Nieman Lab</i>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asi [...]
+<li id="cite_note-stallman1999-21"><span class="mw-cite-backlink">^ <a href="#cite_ref-stallman1999_21-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-stallman1999_21-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation web"><a href="/wiki/Richard_Stallman" title="Richard Stallman">Richard M. Stallman</a> (June 20, 2007). <a rel="nofollow" class="external text" href="https://www.gnu.org/encyclopedia/encyclopedia.html">"The Free Encyclopedia Pro [...]
+<li id="cite_note-autogenerated1-22"><span class="mw-cite-backlink"><b><a href="#cite_ref-autogenerated1_22-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Jonathan Sidener (December 6, 2004). <a rel="nofollow" class="external text" href="http://www.signonsandiego.com/uniontrib/20041206/news_mz1b6encyclo.html">"Everyone's Encyclopedia"</a>. <i><a href="/wiki/U-T_San_Diego" title="U-T San Diego" class="mw-redirect">U-T San Diego</a></i><span class="reference-a [...]
+<li id="cite_note-Meyers-23"><span class="mw-cite-backlink"><b><a href="#cite_ref-Meyers_23-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Meyers, Peter (September 20, 2001). <a rel="nofollow" class="external text" href="http://query.nytimes.com/gst/fullpage.html?res=9800E5D6123BF933A1575AC0A9679C8B63&n=Top%2fReference%2fTimes%20Topics%2fSubjects%2fC%2fComputer%20Software">"Fact-Driven? Collegial? This Site Wants You"</a>. <i>The New York Times</i><span [...]
+<li id="cite_note-SangerMemoir-24"><span class="mw-cite-backlink">^ <a href="#cite_ref-SangerMemoir_24-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-SangerMemoir_24-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-SangerMemoir_24-2"><sup><i><b>c</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Sanger, Larry (April 18, 2005). <a rel="nofollow" class="external text" href="http://features.slashdot.org/features/05/04/18/164213.shtml">"The Early His [...]
+<li id="cite_note-Sanger-25"><span class="mw-cite-backlink"><b><a href="#cite_ref-Sanger_25-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Sanger, Larry (January 17, 2001). <a rel="nofollow" class="external text" href="https://web.archive.org/web/20010506042824/http://www.nupedia.com/pipermail/nupedia-l/2001-January/000684.html">"Wikipedia Is Up!"</a>. Archived from <a rel="nofollow" class="external text" href="http://www.nupedia.com/pipermail/nupedia-l/2001 [...]
+<li id="cite_note-WM_foundation_of_WP_1-26"><span class="mw-cite-backlink"><b><a href="#cite_ref-WM_foundation_of_WP_1_26-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://lists.wikimedia.org/pipermail/wikipedia-l/2001-October/000671.html">"Wikipedia-l: LinkBacks?"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">February 20,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3As [...]
+<li id="cite_note-nupedia_feeder_from_WP_1-27"><span class="mw-cite-backlink"><b><a href="#cite_ref-nupedia_feeder_from_WP_1_27-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Sanger, Larry (January 10, 2001). <a rel="nofollow" class="external text" href="https://web.archive.org/web/20030414014355/http://www.nupedia.com/pipermail/nupedia-l/2001-January/000676.html">"Let's Make a Wiki"</a>. Internet Archive. Archived from <a rel="nofollow" class="external text [...]
+<li id="cite_note-WikipediaHome-28"><span class="mw-cite-backlink"><b><a href="#cite_ref-WikipediaHome_28-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="https://web.archive.org/web/20010331173908/http://www.wikipedia.com/">"Wikipedia: HomePage"</a>. Archived from <a rel="nofollow" class="external text" href="http://www.wikipedia.com/">the original</a> on March 31, 2001<span class="reference-accessdate">. Retrieved [...]
+<li id="cite_note-NPOV-29"><span class="mw-cite-backlink"><b><a href="#cite_ref-NPOV_29-0">^</a></b></span> <span class="reference-text">"<a class="external text" href="https://en.wikipedia.org/w/index.php?title=Wikipedia:Neutral">point of view&oldid=102236018 Wikipedia:Neutral point of view</a>, Wikipedia (January 21, 2007).</span></li>
+<li id="cite_note-Seth-Finkelstein-30"><span class="mw-cite-backlink"><b><a href="#cite_ref-Seth-Finkelstein_30-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Finkelstein, Seth (September 25, 2008). <a rel="nofollow" class="external text" href="http://www.guardian.co.uk/technology/2008/sep/25/wikipedia.internet">"Read me first: Wikipedia isn't about human potential, whatever Wales says"</a>. London: <i><a href="/wiki/The_Guardian" title="The Guardian">The Gu [...]
+<li id="cite_note-Wikipedia_August_08.2C_2001-31"><span class="mw-cite-backlink"><b><a href="#cite_ref-Wikipedia_August_08.2C_2001_31-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://web.archive.bibalex.org/web/20010808121638/http://www.wikipedia.org/">"Wikipedia, August 8, 2001"</a>. Web.archive.bibalex.org. August 8, 2001<span class="reference-accessdate">. Retrieved <span class="nowrap">March 3,</span> 201 [...]
+<li id="cite_note-Wikipedia_September_25.2C_2001-32"><span class="mw-cite-backlink"><b><a href="#cite_ref-Wikipedia_September_25.2C_2001_32-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://web.archive.bibalex.org/web/20011010233257/www.wikipedia.com/">"Wikipedia, September 25, 2001"</a>. Web.archive.bibalex.org<span class="reference-accessdate">. Retrieved <span class="nowrap">March 3,</span> 2014</span>.</ci [...]
+<li id="cite_note-WP_early_language_stats_1-33"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_early_language_stats_1_33-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Wikipedia:Multilingual_statistics "Multilingual statistics"]. <i>Wikipedia</i>. March 30, 2005<span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedi [...]
+<li id="cite_note-EB_encyclopedia-34"><span class="mw-cite-backlink"><b><a href="#cite_ref-EB_encyclopedia_34-0">^</a></b></span> <span class="reference-text"><cite class="citation encyclopaedia">"Encyclopedias and Dictionaries". <i>Encyclopædia Britannica</i> <b>18</b> (15th ed.). 2007. pp. 257–286.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=Encyclopedias+and+Dictionaries&rft.btitle=Encyclop%C3%A6dia+Britannica& [...]
+<li id="cite_note-EL_fears_and_start_1-35"><span class="mw-cite-backlink"><b><a href="#cite_ref-EL_fears_and_start_1_35-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://osdir.com/ml/science.linguistics.wikipedia.international/2003-03/msg00008.html">"[long] Enciclopedia Libre: msg#00008"</a>. <i>Osdir</i><span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite> [...]
+<li id="cite_note-Shirky-36"><span class="mw-cite-backlink"><b><a href="#cite_ref-Shirky_36-0">^</a></b></span> <span class="reference-text"><cite class="citation book"><a href="/wiki/Clay_Shirky" title="Clay Shirky">Clay Shirky</a> (February 28, 2008). <a rel="nofollow" class="external text" href="http://www.amazon.com/gp/reader/1594201536/ref=sib_dp_srch_pop?v=search-inside&keywords=spanish&go.x=0&go.y=0&go=Go%21"><i>Here Comes Everybody: The Power of Organizing Without [...]
+<li id="cite_note-guardian_WP_user_peak_1-37"><span class="mw-cite-backlink"><b><a href="#cite_ref-guardian_WP_user_peak_1_37-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Bobbie Johnson (August 12, 2009). <a rel="nofollow" class="external text" href="http://www.guardian.co.uk/technology/2009/aug/12/wikipedia-deletionist-inclusionist">"Wikipedia approaches its limits"</a>. <i>The Guardian</i> (London)<span class="reference-accessdate">. Retrieved <span clas [...]
+<li id="cite_note-WP_growth_modelling_1-38"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_growth_modelling_1_38-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Modelling_Wikipedia_extended_growth" title="Wikipedia:Modelling Wikipedia extended growth">Wikipedia:Modelling_Wikipedia_extended_growth</a></span></li>
+<li id="cite_note-wikisym_slowing_growth_1-39"><span class="mw-cite-backlink"><b><a href="#cite_ref-wikisym_slowing_growth_1_39-0">^</a></b></span> <span class="reference-text"><cite class="citation conference"><a rel="nofollow" class="external text" href="http://www.wikisym.org/ws2009/procfiles/p108-suh.pdf"><i>The Singularity is Not Near: Slowing Growth of Wikipedia</i></a> <span style="font-size:85%;">(PDF)</span>. The International Symposium on Wikis. Orlando, Florida. 2009.</cite><s [...]
+<li id="cite_note-bostonreview_the_end_of_WP_1-40"><span class="mw-cite-backlink"><b><a href="#cite_ref-bostonreview_the_end_of_WP_1_40-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Evgeny Morozov (November–December 2009). <a rel="nofollow" class="external text" href="http://www.bostonreview.net/books-ideas/edit-page-wikipedia-evgeny-morozov">"Edit This Page; Is it the end of Wikipedia"</a>. <i>Boston Review</i>.</cite><span title="ctx_ver=Z39.88-2004&r [...]
+<li id="cite_note-41"><span class="mw-cite-backlink"><b><a href="#cite_ref-41">^</a></b></span> <span class="reference-text"><cite class="citation news">Cohen, Noam (March 28, 2009). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2009/03/29/weekinreview/29cohen.html">"Wikipedia – Exploring Fact City"</a>. <i>The New York Times</i><span class="reference-accessdate">. Retrieved <span class="nowrap">April 19,</span> 2011</span>.</cite><span title="ctx_ver=Z39.88-2004&a [...]
+<li id="cite_note-stanford_WP_lack_of_future_growth_1-42"><span class="mw-cite-backlink"><b><a href="#cite_ref-stanford_WP_lack_of_future_growth_1_42-0">^</a></b></span> <span class="reference-text">Austin Gibbons, David Vetrano, Susan Biancani (2012). <a rel="nofollow" class="external text" href="http://snap.stanford.edu/class/cs341-2012/reports/09-GibbonsVetranoBiancaniCS341.pdf">Wikipedia: Nowhere to grow</a> <a href="/wiki/Open_access" title="open access publication - free to read">< [...]
+<li id="cite_note-guardian_editors_leaving_1-43"><span class="mw-cite-backlink"><b><a href="#cite_ref-guardian_editors_leaving_1_43-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Jenny Kleeman (November 26, 2009). <a rel="nofollow" class="external text" href="http://www.guardian.co.uk/technology/2009/nov/26/wikipedia-losing-disgruntled-editors">"Wikipedia falling victim to a war of words"</a>. <i>The Guardian</i> (London)<span class="reference-accessdate">. [...]
+<li id="cite_note-44"><span class="mw-cite-backlink"><b><a href="#cite_ref-44">^</a></b></span> <span class="reference-text"><cite class="citation journal"><a rel="nofollow" class="external text" href="https://web.archive.org/web/20120403172516/http://libresoft.es/publications/thesis-jfelipe">"Wikipedia: A quantitative analysis"</a>. Archived from <a rel="nofollow" class="external text" href="http://libresoft.es/publications/thesis-jfelipe">the original</a> <span style="font-size:85%;">( [...]
+<li id="cite_note-WSJ_WP_losing_editors_1-45"><span class="mw-cite-backlink"><b><a href="#cite_ref-WSJ_WP_losing_editors_1_45-0">^</a></b></span> <span class="reference-text">Volunteers Log Off as Wikipedia Ages, The Wall Street Journal, November 27, 2009.</span></li>
+<li id="cite_note-telegraph_Wales_WP_not_losing_editors_1-46"><span class="mw-cite-backlink"><b><a href="#cite_ref-telegraph_Wales_WP_not_losing_editors_1_46-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Barnett, Emma (November 26, 2009). <a rel="nofollow" class="external text" href="http://www.telegraph.co.uk/technology/wikipedia/6660646/Wikipedias-Jimmy-Wales-denies-site-is-losing-thousands-of-volunteer-editors.html">"Wikipedia's Jimmy Wales denies site i [...]
+<li id="cite_note-wiki-women-47"><span class="mw-cite-backlink">^ <a href="#cite_ref-wiki-women_47-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-wiki-women_47-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Kevin Rawlinson (August 8, 2011). <a rel="nofollow" class="external text" href="http://www.independent.co.uk/life-style/gadgets-and-tech/news/wikipedia-seeks-women-to-balance-its-geeky-editors-2333605.html">"Wikipedia seeks wome [...]
+<li id="cite_note-Simonite-2013-48"><span class="mw-cite-backlink">^ <a href="#cite_ref-Simonite-2013_48-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Simonite-2013_48-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation journal">Simonite, Tom (October 22, 2013). <a rel="nofollow" class="external text" href="http://www.technologyreview.com/featuredstory/520446/the-decline-of-wikipedia/">"The Decline of Wikipedia"</a>. <i><a href="/wiki/MIT_Te [...]
+<li id="cite_note-49"><span class="mw-cite-backlink"><b><a href="#cite_ref-49">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.theatlantic.com/technology/archive/2012/07/3-charts-that-show-how-wikipedia-is-running-out-of-admins/259829">"3 Charts That Show How Wikipedia Is Running Out of Admins"</a>. <i>The Atlantic</i>. July 16, 2012.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipe [...]
+<li id="cite_note-50"><span class="mw-cite-backlink"><b><a href="#cite_ref-50">^</a></b></span> <span class="reference-text">Ward, Katherine. <i>New York</i> Magazine, issue of November 25, 2013, p. 18.</span></li>
+<li id="cite_note-51"><span class="mw-cite-backlink"><b><a href="#cite_ref-51">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.pcworld.com/article/129135/wikipedia_breaks_into_us_top_10_sites.html">"Wikipedia Breaks Into US Top 10 Sites"</a>. PCWorld. February 17, 2007.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Wikipedia+Breaks+Into+US+Top+10+ [...]
+<li id="cite_note-Alexa_top-52"><span class="mw-cite-backlink"><b><a href="#cite_ref-Alexa_top_52-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.alexa.com/siteinfo/wikipedia.org">"Wikipedia.org Site Overview"</a>. <i>alexa.com</i>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Wikipedia.org+Site+Overview&rft.genre=book&rft_id=http%3A%2 [...]
+<li id="cite_note-53"><span class="mw-cite-backlink"><b><a href="#cite_ref-53">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://stats.wikimedia.org/wikimedia/squids/SquidReportPageViewsPerCountryOverview.htm">"Wikimedia Traffic Analysis Report – Wikipedia Page Views Per Country"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">March 8,</span> 2015</span>.</cite><span title="ctx_ver=Z [...]
+<li id="cite_note-LA_Times_Jan_19-54"><span class="mw-cite-backlink"><b><a href="#cite_ref-LA_Times_Jan_19_54-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Netburn, Deborah (January 19, 2012). <a rel="nofollow" class="external text" href="http://latimesblogs.latimes.com/technology/2012/01/wikipedia-sopa-blackout-congressional-representatives.html">"Wikipedia: SOPA protest led 8 million to look up reps in Congress"</a>. <i>Los Angeles Times</i><span class="r [...]
+<li id="cite_note-BBC_WP_blackout_protest_1-55"><span class="mw-cite-backlink"><b><a href="#cite_ref-BBC_WP_blackout_protest_1_55-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.bbc.co.uk/news/technology-16590585">"Wikipedia joins blackout protest at US anti-piracy moves"</a>. BBC News. January 18, 2012<span class="reference-accessdate">. Retrieved <span class="nowrap">January 19,</span> 2012</span>.</c [...]
+<li id="cite_note-56"><span class="mw-cite-backlink"><b><a href="#cite_ref-56">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://wikimediafoundation.org/wiki/SOPA/Blackoutpage">"SOPA/Blackoutpage"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">January 19,</span> 2012</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=SO [...]
+<li id="cite_note-sagepub_WP_and_encyclopedic_production_1-57"><span class="mw-cite-backlink"><b><a href="#cite_ref-sagepub_WP_and_encyclopedic_production_1_57-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Jeff Loveland and Joseph Reagle (January 15, 2013). <a rel="nofollow" class="external text" href="http://nms.sagepub.com/content/early/2013/01/13/1461444812470428.full">"Wikipedia and encyclopedic production. New Media & Society. Sage Journals"</a> [...]
+<li id="cite_note-theatlantic_WP_actually_a_reversion_1-58"><span class="mw-cite-backlink"><b><a href="#cite_ref-theatlantic_WP_actually_a_reversion_1_58-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Rebecca J. Rosen (Jan 30, 2013). <a rel="nofollow" class="external text" href="http://www.theatlantic.com/technology/archive/2013/01/what-if-the-great-wikipedia-revolution-was-actually-a-reversion/272697">"What If the Great Wikipedia 'Revolution' Was Actually a [...]
+<li id="cite_note-economictimes.indiatimes.com-59"><span class="mw-cite-backlink">^ <a href="#cite_ref-economictimes.indiatimes.com_59-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-economictimes.indiatimes.com_59-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-economictimes.indiatimes.com_59-2"><sup><i><b>c</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Varma, Subodh (January 20, 2014). <a rel="nofollow" class="external text" href="http://ec [...]
+<li id="cite_note-61"><span class="mw-cite-backlink"><b><a href="#cite_ref-61">^</a></b></span> <span class="reference-text"><cite class="citation book"><a href="/wiki/Jonathan_Zittrain" title="Jonathan Zittrain">Zittrain, Jonathan</a> (2008). <a rel="nofollow" class="external text" href="http://yupnet.org/zittrain/archives/16"><i>The Future of the Internet and How to Stop It – Chapter 6: The Lessons of Wikipedia</i></a>. Yale University Press. <a href="/wiki/International_Standard_ [...]
+<li id="cite_note-62"><span class="mw-cite-backlink"><b><a href="#cite_ref-62">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Tutorial/Registration" title="Wikipedia:Tutorial/Registration">Registration notes</a></span></li>
+<li id="cite_note-WP_protection_policy_1-63"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_protection_policy_1_63-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Protection_policy" title="Wikipedia:Protection policy">Protection Policy</a></span></li>
+<li id="cite_note-64"><span class="mw-cite-backlink"><b><a href="#cite_ref-64">^</a></b></span> <span class="reference-text"><a href="/wiki/Help:Semi-protection" title="Help:Semi-protection" class="mw-redirect">English Wikipedia's semi-protection policy</a></span></li>
+<li id="cite_note-65"><span class="mw-cite-backlink"><b><a href="#cite_ref-65">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Full_protection" title="Wikipedia:Full protection" class="mw-redirect">English Wikipedia's full protection policy</a></span></li>
+<li id="cite_note-WP_some_sites_stable_versions_1-66"><span class="mw-cite-backlink">^ <a href="#cite_ref-WP_some_sites_stable_versions_1_66-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-WP_some_sites_stable_versions_1_66-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation mailinglist">Birken, P. (December 14, 2008). <a class="external text" href="http://lists.wikimedia.org/pipermail/wikide-l/2008-December/021594.html">"Bericht Gesichtete Ve [...]
+<li id="cite_note-BInsider_pending_changes_intro_1-67"><span class="mw-cite-backlink"><b><a href="#cite_ref-BInsider_pending_changes_intro_1_67-0">^</a></b></span> <span class="reference-text"><cite class="citation news">William Henderson (December 10, 2012). <a rel="nofollow" class="external text" href="http://www.businessinsider.com/pending-changes-safeguard-on-wikipedia-2012-12">"Wikipedia Has Figured Out A New Way To Stop Vandals In Their Tracks"</a>. <i>Business Insider</i>.</cite>< [...]
+<li id="cite_note-68"><span class="mw-cite-backlink"><b><a href="#cite_ref-68">^</a></b></span> <span class="reference-text"><cite class="citation web">Frewin, Jonathan (June 15, 2010). <a rel="nofollow" class="external text" href="http://www.bbc.com/news/10312095">"Wikipedia unlocks divisive pages for editing"</a>. BBC News<span class="reference-accessdate">. Retrieved <span class="nowrap">August 21,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.w [...]
+<li id="cite_note-Torsten_Kleinz-70"><span class="mw-cite-backlink">^ <a href="#cite_ref-Torsten_Kleinz_70-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Torsten_Kleinz_70-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Kleinz, Torsten (February 2005). <a rel="nofollow" class="external text" href="http://w3.linux-magazine.com/issue/51/Wikipedia_Encyclopedia.pdf">"World of Knowledge"</a> <span style="font-size:85%;">(PDF)</span>. <i> [...]
+<li id="cite_note-71"><span class="mw-cite-backlink"><b><a href="#cite_ref-71">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:New_pages_patrol" title="Wikipedia:New pages patrol">Wikipedia:New pages patrol</a></span></li>
+<li id="cite_note-FMonday_collaborative_effort_1-72"><span class="mw-cite-backlink"><b><a href="#cite_ref-FMonday_collaborative_effort_1_72-0">^</a></b></span> <span class="reference-text">Andrea Ciffolilli, <a rel="nofollow" class="external text" href="http://firstmonday.org/article/view/1108/1028">"Phantom authority, self-selective recruitment and retention of members in virtual communities: The case of Wikipedia"</a>, <i><a href="/wiki/First_Monday_(journal)" title="First Monday (jour [...]
+<li id="cite_note-upenn_link_spamming_1-73"><span class="mw-cite-backlink"><b><a href="#cite_ref-upenn_link_spamming_1_73-0">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://repository.upenn.edu/cgi/viewcontent.cgi?article=1508&context=cis_papers">Link spamming Wikipedia for profit</a> (2011)</span></li>
+<li id="cite_note-WP_vandalism_manipulation_1-74"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_vandalism_manipulation_1_74-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Vandalism" title="Wikipedia:Vandalism">Vandalism</a>. <i>Wikipedia</i>. Retrieved November 6, 2012.</span></li>
+<li id="cite_note-MIT_IBM_study-75"><span class="mw-cite-backlink"><b><a href="#cite_ref-MIT_IBM_study_75-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Fernanda B. Viégas, Martin Wattenberg, and Kushal Dave (2004). <a rel="nofollow" class="external text" href="http://alumni.media.mit.edu/~fviegas/papers/history_flow.pdf">"Studying Cooperation and Conflict between Authors with History Flow Visualizations"</a> <span style="font-size:85%;">(PDF)</span>. <i> [...]
+<li id="cite_note-CreatingDestroyingAndRestoringValue-76"><span class="mw-cite-backlink"><b><a href="#cite_ref-CreatingDestroyingAndRestoringValue_76-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Reid Priedhorsky, Jilin Chen, Shyong (Tony) K. Lam, Katherine Panciera, Loren Terveen, and John Riedl (GroupLens Research, Department of Computer Science and Engineering, <a href="/wiki/University_of_Minnesota" title="University of Minnesota">University of Minne [...]
+<li id="cite_note-Seigenthaler-77"><span class="mw-cite-backlink">^ <a href="#cite_ref-Seigenthaler_77-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Seigenthaler_77-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-Seigenthaler_77-2"><sup><i><b>c</b></i></sup></a> <a href="#cite_ref-Seigenthaler_77-3"><sup><i><b>d</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Seigenthaler, John (November 29, 2005). <a rel="nofollow" class="external text" href [...]
+<li id="cite_note-book_The_World_is_Flat_1-78"><span class="mw-cite-backlink"><b><a href="#cite_ref-book_The_World_is_Flat_1_78-0">^</a></b></span> <span class="reference-text"><cite class="citation book">Friedman, Thomas L. (2007). <i>The World is Flat</i>. <a href="/wiki/Farrar,_Straus_%26_Giroux" title="Farrar, Straus & Giroux" class="mw-redirect">Farrar, Straus & Giroux</a>. p. 124. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Num [...]
+<li id="cite_note-79"><span class="mw-cite-backlink"><b><a href="#cite_ref-79">^</a></b></span> <span class="reference-text"><cite class="citation web">Buchanan, Brian J. (November 17, 2006). <a rel="nofollow" class="external text" href="https://web.archive.org/web/20121221140311/http://archive.firstamendmentcenter.org/news.aspx?id=17798">"Founder shares cautionary tale of libel in cyberspace"</a>. archive.firstamendmentcenter.org. Archived from <a rel="nofollow" class="external text" hr [...]
+<li id="cite_note-80"><span class="mw-cite-backlink"><b><a href="#cite_ref-80">^</a></b></span> <span class="reference-text"><cite class="citation news">Helm, Burt (December 13, 2005). <a rel="nofollow" class="external text" href="http://www.businessweek.com/stories/2005-12-13/wikipedia-a-work-in-progress">"Wikipedia: "A Work in Progress<span style="padding-right:0.2em;">"</span>"</a>. <i><a href="/wiki/BusinessWeek" title="BusinessWeek" class="mw-redirect">BusinessWeek</a></i><span clas [...]
+<li id="cite_note-pcworld_who.27s_behind_WP-81"><span class="mw-cite-backlink"><b><a href="#cite_ref-pcworld_who.27s_behind_WP_81-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.pcworld.idg.com.au/index.php/id;1866322157;fp;2;fpid;2">"Who's behind Wikipedia?"</a>. <i>PC World</i>. February 6, 2008<span class="reference-accessdate">. Retrieved <span class="nowrap">February 7,</span> 2008</span>.</cite><sp [...]
+<li id="cite_note-WP_content_policy_1-82"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_content_policy_1_82-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:What_Wikipedia_is_not" title="Wikipedia:What Wikipedia is not">What Wikipedia is not</a>. Retrieved April 1, 2010. "Wikipedia is not a dictionary, usage, or jargon guide."</span></li>
+<li id="cite_note-WP_notability_guide_1-83"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_notability_guide_1_83-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Notability" title="Wikipedia:Notability">Notability</a>. Retrieved February 13, 2008. "A topic is presumed to be notable if it has received significant coverage in reliable secondary sources that are independent of the subject."</span></li>
+<li id="cite_note-NOR-84"><span class="mw-cite-backlink"><b><a href="#cite_ref-NOR_84-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:No_original_research" title="Wikipedia:No original research">No original research</a>. February 13, 2008. "Wikipedia does not publish original thought."</span></li>
+<li id="cite_note-WP_Verifiability_policy_1-85"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_Verifiability_policy_1_85-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Verifiability" title="Wikipedia:Verifiability">Verifiability</a>. February 13, 2008. "Material challenged or likely to be challenged, and all quotations, must be attributed to a reliable, published source."</span></li>
+<li id="cite_note-IHT_WP_valid_info_wrong_removable_1-86"><span class="mw-cite-backlink"><b><a href="#cite_ref-IHT_WP_valid_info_wrong_removable_1_86-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Cohen, Noam (August 9, 2011). <a rel="nofollow" class="external text" href="http://news-business.vlex.com/vid/inclusive-mission-is-that-goes-far-425135170">"For inclusive mission, Wikipedia is told that written word goes only so far"</a>. <i><a href="/wiki/Internat [...]
+<li id="cite_note-autogenerated2-87"><span class="mw-cite-backlink"><b><a href="#cite_ref-autogenerated2_87-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Neutral_point_of_view" title="Wikipedia:Neutral point of view">Neutral point of view</a>. February 13, 2008. "All Wikipedia articles and other encyclopedic content must be written from a neutral point of view, representing significant views fairly, proportionately and without bias."</span></li>
+<li id="cite_note-alternet_WP_unethical_editing_destroy.27s_credibility_1-88"><span class="mw-cite-backlink"><b><a href="#cite_ref-alternet_WP_unethical_editing_destroy.27s_credibility_1_88-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Eric Haas (October 26, 2007). <a rel="nofollow" class="external text" href="http://www.alternet.org/story/61365/?page=entire">"Will Unethical Editing Destroy Wikipedia's Credibility?"</a>. AlterNet<span class="reference-access [...]
+<li id="cite_note-89"><span class="mw-cite-backlink"><b><a href="#cite_ref-89">^</a></b></span> <span class="reference-text"><cite class="citation web">Sanger, Larry (April 18, 2005). <a rel="nofollow" class="external text" href="http://features.slashdot.org/story/05/04/18/164213/the-early-history-of-nupedia-and-wikipedia-a-memoir">"The Early History of Nupedia and Wikipedia: A Memoir"</a>. <i>Slashdot</i>. Dice.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia [...]
+<li id="cite_note-90"><span class="mw-cite-backlink"><b><a href="#cite_ref-90">^</a></b></span> <span class="reference-text"><cite class="citation web">Kostakis, Vasilis (March 2010). <a rel="nofollow" class="external text" href="http://firstmonday.org/ojs/index.php/fm/article/view/2613/2479">"Identifying and understanding the problems of Wikipedia's peer governance: The case of inclusionists versus deletionists"</a>. <i>First Monday</i>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id [...]
+<li id="cite_note-91"><span class="mw-cite-backlink"><b><a href="#cite_ref-91">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Ownership_of_articles" title="Wikipedia:Ownership of articles" class="mw-redirect">Ownership of articles</a></span></li>
+<li id="cite_note-92"><span class="mw-cite-backlink"><b><a href="#cite_ref-92">^</a></b></span> <span class="reference-text">Avoiding Tragedy in the Wiki-Commons, by Andrew George, 12 Va. J.L. & Tech. 8 (2007)</span></li>
+<li id="cite_note-93"><span class="mw-cite-backlink"><b><a href="#cite_ref-93">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Administrators" title="Wikipedia:Administrators">Wikipedia:Administrators</a></span></li>
+<li id="cite_note-David_Mehegan-94"><span class="mw-cite-backlink"><b><a href="#cite_ref-David_Mehegan_94-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Mehegan, David (February 13, 2006). <a rel="nofollow" class="external text" href="http://www.boston.com/business/technology/articles/2006/02/13/many_contributors_common_cause">"Many contributors, common cause"</a>. <i>Boston Globe</i><span class="reference-accessdate">. Retrieved <span class="nowrap">March 2 [...]
+<li id="cite_note-95"><span class="mw-cite-backlink"><b><a href="#cite_ref-95">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://en.wikipedia.org/wiki/Wikipedia:Administrators#Administrator_conduct">"Wikipedia:Administrators"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">July 12,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btit [...]
+<li id="cite_note-96"><span class="mw-cite-backlink"><b><a href="#cite_ref-96">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://en.wikipedia.org/wiki/Wikipedia:RfA_Review/Reflect">"Wikipedia:RfA_Review/Reflect"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">September 24,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Wikipe [...]
+<li id="cite_note-97"><span class="mw-cite-backlink"><b><a href="#cite_ref-97">^</a></b></span> <span class="reference-text"><cite class="citation web">Meyer, Robinson (July 16, 2012). <a rel="nofollow" class="external text" href="http://www.theatlantic.com/technology/archive/2012/07/3-charts-that-show-how-wikipedia-is-running-out-of-admins/259829">"3 Charts That Show How Wikipedia Is Running Out of Admins"</a>. <i><a href="/wiki/The_Atlantic" title="The Atlantic">The Atlantic</a></i><sp [...]
+<li id="cite_note-98"><span class="mw-cite-backlink"><b><a href="#cite_ref-98">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Edit_warring" title="Wikipedia:Edit warring">"edit war"</a></span></li>
+<li id="cite_note-99"><span class="mw-cite-backlink"><b><a href="#cite_ref-99">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Dispute_Resolution" title="Wikipedia:Dispute Resolution" class="mw-redirect">Dispute Resolution</a></span></li>
+<li id="cite_note-NBC_WP_editorial_warzone_1-100"><span class="mw-cite-backlink"><b><a href="#cite_ref-NBC_WP_editorial_warzone_1_100-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Coldewey, Devin (June 21, 2012). <a rel="nofollow" class="external text" href="http://sys03-public.nbcnews.com/technology/wikipedia-editorial-warzone-says-study-838793">"Wikipedia is editorial warzone, says study"</a>. <i>Technology</i>. <a href="/wiki/NBC_News" title="NBC News">NB [...]
+<li id="cite_note-emory_disputes_handled_1-101"><span class="mw-cite-backlink"><b><a href="#cite_ref-emory_disputes_handled_1_101-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Hoffman, David A., Mehra, Salil K. (2009). <a rel="nofollow" class="external text" href="http://papers.ssrn.com/sol3/papers.cfm?abstract_id=1354424">"Wikitruth through Wikiorder"</a> <span style="font-size:85%;">(PDF)</span>. <i>Emory Law Journal</i> (<a href="/wiki/Emory_Universit [...]
+<li id="cite_note-102"><span class="mw-cite-backlink"><b><a href="#cite_ref-102">^</a></b></span> <span class="reference-text"><cite class="citation journal">Hoffman, David A., Mehra, Salil K. (2009). <a rel="nofollow" class="external text" href="http://papers.ssrn.com/sol3/papers.cfm?abstract_id=1354424">"Wikitruth through Wikiorder"</a> <span style="font-size:85%;">(PDF)</span>. <i><a href="/wiki/Emory_Law_Journal" title="Emory Law Journal" class="mw-redirect">Emory Law Journal</a></i> [...]
+<li id="cite_note-103"><span class="mw-cite-backlink"><b><a href="#cite_ref-103">^</a></b></span> <span class="reference-text"><cite class="citation journal"><a href="/wiki/Fernanda_B._Vi%C3%A9gas" title="Fernanda B. Viégas" class="mw-redirect">Fernanda B. Viégas</a>; <a href="/wiki/Martin_M._Wattenberg" title="Martin M. Wattenberg">Martin M. Wattenberg</a>; Jesse Kriss; Frank van Ham (January 3, 2007). <a rel="nofollow" class="external text" href="http://www.research.ibm.com/visual/pape [...]
+<li id="cite_note-104"><span class="mw-cite-backlink"><b><a href="#cite_ref-104">^</a></b></span> <span class="reference-text"><cite class="citation news">Arthur, Charles (December 15, 2005). <a rel="nofollow" class="external text" href="http://www.guardian.co.uk/technology/2005/dec/15/wikipedia.web20">"Log on and join in, but beware the web cults"</a>. <i><a href="/wiki/The_Guardian" title="The Guardian">The Guardian</a></i> (London)<span class="reference-accessdate">. Retrieved <span c [...]
+<li id="cite_note-105"><span class="mw-cite-backlink"><b><a href="#cite_ref-105">^</a></b></span> <span class="reference-text"><cite class="citation news">Lu Stout, Kristie (August 4, 2003). <a rel="nofollow" class="external text" href="http://www.cnn.com/2003/TECH/internet/08/03/wikipedia/index.html">"Wikipedia: The know-it-all Web site"</a>. CNN<span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004& [...]
+<li id="cite_note-106"><span class="mw-cite-backlink"><b><a href="#cite_ref-106">^</a></b></span> <span class="reference-text"><cite class="citation web"><a href="/wiki/Larry_Sanger" title="Larry Sanger">Larry Sanger</a> (December 31, 2004). <a rel="nofollow" class="external text" href="http://www.kuro5hin.org/story/2004/12/30/142458/25">"Why Wikipedia Must Jettison Its Anti-Elitism"</a>. <i><a href="/wiki/Kuro5hin.org" title="Kuro5hin.org" class="mw-redirect">Kuro5hin</a>, Op–Ed</i>. <q [...]
+<li id="cite_note-107"><span class="mw-cite-backlink"><b><a href="#cite_ref-107">^</a></b></span> <span class="reference-text"><cite class="citation journal">T. Kriplean, I. Beschastnikh, et al. (2008). <a rel="nofollow" class="external text" href="http://portal.acm.org/citation.cfm?id=1460563.1460573">"Articulations of wikiwork: uncovering valued work in Wikipedia through barnstars"</a>. Proceedings of the ACM. p. 47. <a href="/wiki/Digital_object_identifier" title="Digital object [...]
+<li id="cite_note-user_identification-108"><span class="mw-cite-backlink"><b><a href="#cite_ref-user_identification_108-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Jean Goodwin (2009). <a rel="nofollow" class="external text" href="http://www.public.iastate.edu/~goodwin/pubs/goodwinwikipedia.pdf">"The Authority of Wikipedia"</a> <span style="font-size:85%;">(PDF)</span><span class="reference-accessdate">. Retrieved <span class="nowrap">January 31,</span> 20 [...]
+<li id="cite_note-109"><span class="mw-cite-backlink"><b><a href="#cite_ref-109">^</a></b></span> <span class="reference-text"><cite class="citation web">Kittur, Aniket. <a rel="nofollow" class="external text" href="http://citeseerx.ist.psu.edu/viewdoc/summary;jsessionid=5F2472BC3443736B94200AFDCECAC3C8?doi=10.1.1.212.8218">"Power of the Few vs. Wisdom of the Crowd: Wikipedia and the Rise of the Bourgeoisie"</a> <span style="font-size:85%;">(PDF)</span>. <a href="/wiki/Viktoria_Institute [...]
+<li id="cite_note-blodget-110"><span class="mw-cite-backlink">^ <a href="#cite_ref-blodget_110-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-blodget_110-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-blodget_110-2"><sup><i><b>c</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Blodget, Henry (January 3, 2009). <a rel="nofollow" class="external text" href="http://www.businessinsider.com/2009/1/who-the-hell-writes-wikipedia-anyway">"Who The Hell [...]
+<li id="cite_note-111"><span class="mw-cite-backlink"><b><a href="#cite_ref-111">^</a></b></span> <span class="reference-text"><cite class="citation news">Wilson, Chris (February 22, 2008). <a rel="nofollow" class="external text" href="http://www.slate.com/id/2184487">"The Wisdom of the Chaperones"</a>. <i><a href="/wiki/Slate_(magazine)" title="Slate (magazine)">Slate</a></i><span class="reference-accessdate">. Retrieved <span class="nowrap">August 13,</span> 2014</span>.</cite><span ti [...]
+<li id="cite_note-112"><span class="mw-cite-backlink"><b><a href="#cite_ref-112">^</a></b></span> <span class="reference-text"><cite class="citation web">Swartz, Aaron (September 4, 2006). <a rel="nofollow" class="external text" href="http://www.aaronsw.com/weblog/whowriteswikipedia">"Raw Thought: Who Writes Wikipedia?"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">February 23,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen. [...]
+<li id="cite_note-labor_squeeze_on_WP_1-113"><span class="mw-cite-backlink"><b><a href="#cite_ref-labor_squeeze_on_WP_1_113-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Goldman, Eric. "Wikipedia's Labor Squeeze and its Consequences" <b>8</b>. Journal on Telecommunications and High Technology Law.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=Wikipedia%27s+Labor+Squeeze+and+its+Consequences& [...]
+<li id="cite_note-legal_edu_and_WP_1-114"><span class="mw-cite-backlink"><b><a href="#cite_ref-legal_edu_and_WP_1_114-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Noveck, Beth Simone. "Wikipedia and the Future of Legal Education" <b>57</b>. Journal of Legal Education.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=Wikipedia+and+the+Future+of+Legal+Education&rft.au=Noveck%2C+Beth+Simone& [...]
+<li id="cite_note-sciam_good_samaritans_1-115"><span class="mw-cite-backlink"><b><a href="#cite_ref-sciam_good_samaritans_1_115-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.sciam.com/article.cfm?id=good-samaritans-are-on-the-money">"Wikipedia "Good Samaritans" Are on the Money"</a>. <i>Scientific American</i>. October 19, 2007<span class="reference-accessdate">. Retrieved <span class="nowrap">Decembe [...]
+<li id="cite_note-liebertonline_view_on_WP_users_1-116"><span class="mw-cite-backlink"><b><a href="#cite_ref-liebertonline_view_on_WP_users_1_116-0">^</a></b></span> <span class="reference-text">Yair Amichai–Hamburger, Naama Lamdan, Rinat Madiel, Tsahi Hayat, <a rel="nofollow" class="external text" href="http://www.liebertonline.com/doi/abs/10.1089/cpb.2007.0225">Personality Characteristics of Wikipedia Members</a>, <i>CyberPsychology & Behavior</i>, December 1, 2008, 11 (6): 679–681 [...]
+<li id="cite_note-newscientist_view_on_WP_users_1-117"><span class="mw-cite-backlink"><b><a href="#cite_ref-newscientist_view_on_WP_users_1_117-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.newscientist.com/article/mg20126883.900-wikipedians-are-closed-and-disagreeable.html">"Wikipedians are 'closed' and 'disagreeable<span style="padding-right:0.2em;">'</span>"</a>. <i>New Scientist</i><span class="ref [...]
+<li id="cite_note-newscientist_WP_boom_to_bust_1-118"><span class="mw-cite-backlink"><b><a href="#cite_ref-newscientist_WP_boom_to_bust_1_118-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Giles, Jim (August 4, 2009). <a rel="nofollow" class="external text" href="http://www.newscientist.com/article/dn17554-after-the-boom-is-wikipedia-heading-for-bust.html">"After the boom, is Wikipedia heading for bust?"</a>. <i>New Scientist</i>.</cite><span title="ctx_ver=Z [...]
+<li id="cite_note-119"><span class="mw-cite-backlink"><b><a href="#cite_ref-119">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.nytimes.com/roomfordebate/2011/02/02/where-are-the-women-in-wikipedia">"Where Are the Women in Wikipedia? - Room for Debate"</a>. NYTimes.com. February 2, 2011<span class="reference-accessdate">. Retrieved <span class="nowrap">June 14,</span> 2014</span>.</cite><span title="ctx_v [...]
+<li id="cite_note-120"><span class="mw-cite-backlink"><b><a href="#cite_ref-120">^</a></b></span> <span class="reference-text"><cite class="citation journal">Lam, Shyong; Anuradha Uduwage; Zhenhua Dong; Shilad Sen; David R. Musicant; Loren Terveen; John Riedl (October 3–5, 2011). <a rel="nofollow" class="external text" href="http://files.grouplens.org/papers/wp-gender-wikisym2011.pdf">"WP:Clubhouse? An Exploration of Wikipedia's Gender Imbalance"</a> <span style="font-size:85%;">(PDF)</s [...]
+<li id="cite_note-121"><span class="mw-cite-backlink"><b><a href="#cite_ref-121">^</a></b></span> <span class="reference-text"><cite class="citation news">Cohen, Noam. <a rel="nofollow" class="external text" href="http://www.nytimes.com/2011/01/31/business/media/31link.html?_r=0">"Define Gender Gap? Look Up Wikipedia's Contributor List"</a>. <i>The New York Times</i> (The New York Times Company)<span class="reference-accessdate">. Retrieved <span class="nowrap">October 28,</span> 2013</s [...]
+<li id="cite_note-NYT_WP_contributors_gender_1-122"><span class="mw-cite-backlink"><b><a href="#cite_ref-NYT_WP_contributors_gender_1_122-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Chom, Noam (January 31, 2011). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2011/01/31/business/media/31link.html?scp=1&sq=wikipedia%20gender&st=cse">"Define Gender Gap? Look Up Wikipedia's Contributor List"</a>. <i>The New York Times</i>. p. [...]
+<li id="cite_note-NYT_WP_male_domination_1-123"><span class="mw-cite-backlink"><b><a href="#cite_ref-NYT_WP_male_domination_1_123-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Basch, Linda (February 6, 2011). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2011/02/06/opinion/l06wiki.html">"Male-Dominated Web Site Seeking Female Experts"</a> <span style="font-size:85%;">(Letters to the Editor)</span>. <i>The New York Times</i>. p. W [...]
+<li id="cite_note-124"><span class="mw-cite-backlink"><b><a href="#cite_ref-124">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.cbc.ca/news/canada/toronto/ocad-to-storm-wikipedia-this-fall-1.1412807">"OCAD to 'Storm Wikipedia' this fall"</a>. <i>CBC News</i>. August 27, 2013<span class="reference-accessdate">. Retrieved <span class="nowrap">August 21,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88- [...]
+<li id="cite_note-BBC-125"><span class="mw-cite-backlink"><b><a href="#cite_ref-BBC_125-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.bbc.com/news/business-28701772">"Wikipedia 'completely failed' to fix gender imbalance"</a>. <i>BBC News</i><span class="reference-accessdate">. Retrieved <span class="nowrap">September 9,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2 [...]
+<li id="cite_note-CommonKnowledge-126"><span class="mw-cite-backlink"><b><a href="#cite_ref-CommonKnowledge_126-0">^</a></b></span> <span class="reference-text"><cite class="citation book">Jemielniak, Dariusz (2014). <a rel="nofollow" class="external text" href="https://books.google.com/books/about/Common_Knowledge.html?id=-Iw5AwAAQBAJ"><i>Common Knowledge? An Ethnography of Wikipedia</i></a>. Stanford University. <a href="/wiki/International_Standard_Book_Number" title="International St [...]
+<li id="cite_note-ListOfWikipedias-127"><span class="mw-cite-backlink"><b><a href="#cite_ref-ListOfWikipedias_127-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Special:Statistics "Statistics"]. <a href="/wiki/English_Wikipedia" title="English Wikipedia">English Wikipedia</a><span class="reference-accessdate">. Retrieved <span class="nowrap">June 21,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikiped [...]
+<li id="cite_note-WP_list_of_WPs_1-128"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_list_of_WPs_1_128-0">^</a></b></span> <span class="reference-text"><a href="//meta.wikimedia.org/wiki/List_of_Wikipedias" class="extiw" title="meta:List of Wikipedias">List of Wikipedias</a></span></li>
+<li id="cite_note-WP_list_of_WPs_by_article_1-129"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_list_of_WPs_by_article_1_129-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/List_of_Wikipedias#All_Wikipedias_ordered_by_number_of_articles">"Wikipedia:List of Wikipedias"</a>. English Wikipedia<span class="reference-accessdate">. Retrieved <span class="nowrap">December 3,</span> 2015</span>.< [...]
+<li id="cite_note-meta.wikimedia-130"><span class="mw-cite-backlink"><b><a href="#cite_ref-meta.wikimedia_130-0">^</a></b></span> <span class="reference-text"><a href="//meta.wikimedia.org/wiki/List_of_Wikipedias#1.2B_articles" class="extiw" title="m:List of Wikipedias">List of Wikipedias – Meta</a></span></li>
+<li id="cite_note-131"><span class="mw-cite-backlink"><b><a href="#cite_ref-131">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://meta.wikimedia.org/wiki/List_of_Wikipedias#All_Wikipedias_ordered_by_number_of_articles">"List of Wikipedias"</a>. <i>Wikimedia Meta-Wiki</i><span class="reference-accessdate">. Retrieved <span class="nowrap">3 December</span> 2015</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asi [...]
+<li id="cite_note-WP_spelling_MOS_1-132"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_spelling_MOS_1_132-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Wikipedia:Spelling "Spelling"]. <i>Manual of Style</i>. Wikipedia<span class="reference-accessdate">. Retrieved <span class="nowrap">May 19,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Spelling&rft.genre=book& [...]
+<li id="cite_note-WP_countering_bias_1-133"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_countering_bias_1_133-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Wikipedia:WikiProject_Countering_systemic_bias "Countering systemic bias"]<span class="reference-accessdate">. Retrieved <span class="nowrap">May 19,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Countering+syste [...]
+<li id="cite_note-WP_meta_fair_use_1-134"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_meta_fair_use_1_134-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/Fair_use">"Fair use"</a>. Meta-Wiki<span class="reference-accessdate">. Retrieved <span class="nowrap">July 14,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btit [...]
+<li id="cite_note-WP_meta_WP_images_1-135"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_meta_WP_images_1_135-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/Images_on_Wikipedia">"Images on Wikipedia"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">July 14,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia [...]
+<li id="cite_note-IBM_visual_WP_1-136"><span class="mw-cite-backlink"><b><a href="#cite_ref-IBM_visual_WP_1_136-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Fernanda B. Viégas (January 3, 2007). <a rel="nofollow" class="external text" href="http://www.research.ibm.com/visual/papers/viegas_hicss_visual_wikipedia.pdf">"The Visual Side of Wikipedia"</a> <span style="font-size:85%;">(PDF)</span>. Visual Communication Lab, IBM Research<span class="reference- [...]
+<li id="cite_note-WP_Wales_free_multi-lingual_encyclopedia-137"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_Wales_free_multi-lingual_encyclopedia_137-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Jimmy_Wales" title="Jimmy Wales">Jimmy Wales</a>, <a href="https://lists.wikimedia.org/pipermail/wikipedia-l/2005-March/020469.html" class="extiw" title="mailarchive:wikipedia-l/2005-March/020469.html">"Wikipedia is an encyclopedia"</a>, March 8, 2005, <Wikipedia [...]
+<li id="cite_note-WP_metawiki_maintenance_1-138"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_metawiki_maintenance_1_138-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org">"Meta-Wiki"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">March 24,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia [...]
+<li id="cite_note-WP_meta_stats_1-139"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_meta_stats_1_139-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/Statistics">"Meta-Wiki Statistics"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">March 24,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWi [...]
+<li id="cite_note-WP_meta_articles_on_all_sites_1-140"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_meta_articles_on_all_sites_1_140-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/List_of_articles_every_Wikipedia_should_have">"List of articles every Wikipedia should have"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">March 24,</span> 2008</s [...]
+<li id="cite_note-WP_auto-translations_rules_1-141"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_auto-translations_rules_1_141-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Wikipedia:Translations "Wikipedia: Translation"]. <i>English Wikipedia</i><span class="reference-accessdate">. Retrieved <span class="nowrap">February 3,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.bti [...]
+<li id="cite_note-142"><span class="mw-cite-backlink"><b><a href="#cite_ref-142">^</a></b></span> <span class="reference-text"><cite class="citation web">Taha Yasseri, Robert Sumi, <a href="/wiki/J%C3%A1nos_Kert%C3%A9sz" title="János Kertész">János Kertész</a> (January 17, 2012). <a rel="nofollow" class="external text" href="http://www.plosone.org/article/info%3Adoi%2F10.1371%2Fjournal.pone.0030091">"Circadian Patterns of Wikipedia Editorial Activity: A Demographic Analysis"</a>. <a href [...]
+<li id="cite_note-WP_global_south_demographic_increase_plan_1-143"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_global_south_demographic_increase_plan_1_143-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://upload.wikimedia.org/wikipedia/foundation/3/37/2011-12_Wikimedia_Foundation_Plan_FINAL_FOR_WEBSITE_.pdf">"Wikimedia Foundation 2011–12 Annual Plan"</a> <span style="font-size:85%;">(PDF)</span>. Wikimedia Found [...]
+<li id="cite_note-economist1-144"><span class="mw-cite-backlink">^ <a href="#cite_ref-economist1_144-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-economist1_144-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-economist1_144-2"><sup><i><b>c</b></i></sup></a></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.economist.com/news/international/21597959-popular-online-encyclopedia-must-work-out-what-next-wikipe [...]
+<li id="cite_note-145"><span class="mw-cite-backlink"><b><a href="#cite_ref-145">^</a></b></span> <span class="reference-text">Andrew Lih. <i>Wikipedia</i>. Alternative edit policies at Wikipedia in other languages.</span></li>
+<li id="cite_note-bureaucracy-146"><span class="mw-cite-backlink"><b><a href="#cite_ref-bureaucracy_146-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Jemielniak, Dariusz (June 22, 2014). <a rel="nofollow" class="external text" href="http://www.slate.com/articles/technology/future_tense/2014/06/wikipedia_s_bureaucracy_problem_and_how_to_fix_it.html">"The Unbearable Bureaucracy of Wikipedia"</a>. <a href="/wiki/Slate_(magazine)" title="Slate (magazine)">Slate [...]
+<li id="cite_note-147"><span class="mw-cite-backlink"><b><a href="#cite_ref-147">^</a></b></span> <span class="reference-text">D. Jemielniak, <i>Common Knowledge</i>, Stanford University Press, 2014.</span></li>
+<li id="cite_note-148"><span class="mw-cite-backlink"><b><a href="#cite_ref-148">^</a></b></span> <span class="reference-text">Messer-Kruse, Timothy (February 12, 2012) <a rel="nofollow" class="external text" href="http://chronicle.com/article/The-Undue-Weight-of-Truth-on/130704/">The 'Undue Weight' of Truth on Wikipedia</a> <i><a href="/wiki/The_Chronicle_of_Higher_Education" title="The Chronicle of Higher Education">The Chronicle of Higher Education</a></i> Retrieved March 27, 2014</sp [...]
+<li id="cite_note-149"><span class="mw-cite-backlink"><b><a href="#cite_ref-149">^</a></b></span> <span class="reference-text">Colón-Aguirre, Monica &Fleming-May, Rachel A. (October 11, 2012) <a rel="nofollow" class="external text" href="http://faculty.washington.edu/jwj/lis521/colon%20wikipedia.pdf">“You Just Type in What You Are Looking For”: Undergraduates' Use of Library Resources vs. Wikipedia</a> (page 392) <i><a href="/wiki/The_Journal_of_Academic_Librarianship" title="The Jou [...]
+<li id="cite_note-150"><span class="mw-cite-backlink"><b><a href="#cite_ref-150">^</a></b></span> <span class="reference-text">Bowling Green News (February 27, 2012) <a rel="nofollow" class="external text" href="http://www.bgsu.edu/news/2012/02/wikipedia-experience-sparks-national-debate.html">Wikipedia experience sparks national debate</a> <a href="/wiki/Bowling_Green_State_University" title="Bowling Green State University">Bowling Green State University</a> Retrieved March 27, 2014</sp [...]
+<li id="cite_note-okw-151"><span class="mw-cite-backlink"><b><a href="#cite_ref-okw_151-0">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://web.archive.org/web/20110814104256/http://www.timesonline.co.uk/tol/comment/columnists/guest_contributors/article2267665.ece">Wisdom? More like dumbness of the crowds | Oliver Kamm – Times Online (archive version 2011-08-14)</a> (<a rel="nofollow" class="external text" href="http://oliverkamm.typepad. [...]
+<li id="cite_note-wwplagiarism-152"><span class="mw-cite-backlink"><b><a href="#cite_ref-wwplagiarism_152-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.webcitation.org/5lXiLbptk">"Plagiarism by Wikipedia editors"</a>. Wikipedia Watch. October 27, 2006. Archived from <a rel="nofollow" class="external text" href="http://www.wikipedia-watch.org/psamples.html">the original</a> on November 25, 2009.</cite>< [...]
+<li id="cite_note-153"><span class="mw-cite-backlink"><b><a href="#cite_ref-153">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://archive.wired.com/culture/lifestyle/news/2005/12/69844">"Wikipedia, Britannica: A Toss-Up"</a>. <i>Wired</i>. Associated Press. December 15, 2005<span class="reference-accessdate">. Retrieved <span class="nowrap">August 8,</span> 2015</span>.</cite><span title="ctx_ver=Z39.88-2004&am [...]
+<li id="cite_note-154"><span class="mw-cite-backlink"><b><a href="#cite_ref-154">^</a></b></span> <span class="reference-text">Reagle, pp. 165-166.</span></li>
+<li id="cite_note-corporate.britannica.com-155"><span class="mw-cite-backlink"><b><a href="#cite_ref-corporate.britannica.com_155-0">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://corporate.britannica.com/britannica_nature_response.pdf">Fatally Flawed: Refuting the recent study on encyclopedic accuracy by the journal Nature</a>, Encyclopædia Britannica, March 2006</span></li>
+<li id="cite_note-nature.com_britannica_response_1-156"><span class="mw-cite-backlink"><b><a href="#cite_ref-nature.com_britannica_response_1_156-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.nature.com/press_releases/Britannica_response.pdf?item">"Encyclopaedia Britannica and Nature: a response"</a> <span style="font-size:85%;">(PDF)</span><span class="reference-accessdate">. Retrieved <span class="no [...]
+<li id="cite_note-nature.com-157"><span class="mw-cite-backlink"><b><a href="#cite_ref-nature.com_157-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.nature.com/nature/britannica/index.html">"Nature's responses to Encyclopaedia Britannica"</a>. <i>Nature</i>. March 30, 2006<span class="reference-accessdate">. Retrieved <span class="nowrap">March 19,</span> 2012</span>.</cite><span title="ctx_ver=Z39.88-2 [...]
+<li id="cite_note-158"><span class="mw-cite-backlink"><b><a href="#cite_ref-158">^</a></b></span> <span class="reference-text">See author acknowledged comments in response to the citation of the <i>Nature</i> study, at <i>PLoS One</i>, 2014, "Citation of fundamentally flawed <i>Nature</i> quality 'study' ", In response to T. Yasseri et al. (2012) Dynamics of Conflicts in Wikipedia, Published June 20, 2012, DOI 10.1371/journal.pone.0038869, see <a rel="nofollow" class="external autonumber [...]
+<li id="cite_note-WP_general_disclaimer_1-159"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_general_disclaimer_1_159-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Wikipedia:General_disclaimer "Wikipedia:General disclaimer"]. English Wikipedia<span class="reference-accessdate">. Retrieved <span class="nowrap">April 22,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Wik [...]
+<li id="cite_note-WikipediaWatch-160"><span class="mw-cite-backlink"><b><a href="#cite_ref-WikipediaWatch_160-0">^</a></b></span> <span class="reference-text">Public Information Research, Wikipedia Watch</span></li>
+<li id="cite_note-pcworld_WP_blunders_1-161"><span class="mw-cite-backlink"><b><a href="#cite_ref-pcworld_WP_blunders_1_161-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Raphel, JR. <a rel="nofollow" class="external text" href="http://www.pcworld.com/article/170874/the_15_biggest_wikipedia_blunders.html">"The 15 Biggest Wikipedia Blunders"</a>. <i><a href="/wiki/PC_World_(magazine)" title="PC World (magazine)" class="mw-redirect">PC World</a></i><span class= [...]
+<li id="cite_note-tnr_experts_vigilant_in_correcting_WP_1-162"><span class="mw-cite-backlink"><b><a href="#cite_ref-tnr_experts_vigilant_in_correcting_WP_1_162-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Cowen, Tyler (March 14, 2008). <a rel="nofollow" class="external text" href="https://web.archive.org/web/20080318103017/http://www.tnr.com/story.html?id=82eb5d70-13bd-4086-9ec0-cb0e9e8411b3">"Cooked Books"</a>. <i>The New Republic</i>. Archived from <a rel [...]
+<li id="cite_note-TNY_reliability_issues_1-163"><span class="mw-cite-backlink"><b><a href="#cite_ref-TNY_reliability_issues_1_163-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a href="/wiki/Stacy_Schiff" title="Stacy Schiff">Stacy Schiff</a> (July 31, 2006). "Know It All". <i><a href="/wiki/The_New_Yorker" title="The New Yorker">The New Yorker</a></i>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitl [...]
+<li id="cite_note-AcademiaAndWikipedia-164"><span class="mw-cite-backlink"><b><a href="#cite_ref-AcademiaAndWikipedia_164-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Danah Boyd (January 4, 2005). <a rel="nofollow" class="external text" href="http://many.corante.com/archives/2005/01/04/academia_and_wikipedia.php">"Academia and Wikipedia"</a>. <i>Many 2 Many: A Group <a href="/wiki/Blog" title="Blog">Weblog</a> on Social Software</i>. Corante<span class="ref [...]
+<li id="cite_note-McHenry_2004-165"><span class="mw-cite-backlink"><b><a href="#cite_ref-McHenry_2004_165-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Robert_McHenry" title="Robert McHenry">Robert McHenry</a>, <a rel="nofollow" class="external text" href="http://www.techcentralstation.com/111504A.html">"The Faith-Based Encyclopedia"</a><sup class="noprint Inline-Template"><span style="white-space: nowrap;">[<i><a href="/wiki/Wikipedia:Link_rot" title="Wikipedia:Link ro [...]
+<li id="cite_note-dw-166"><span class="mw-cite-backlink"><b><a href="#cite_ref-dw_166-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.dw.de/inside-wikipedia-attack-of-the-pr-industry/av-17745881">"Inside Wikipedia - Attack of the PR Industry"</a>. <a href="/wiki/Deutsche_Welle" title="Deutsche Welle">Deutsche Welle</a>. June 30, 2014<span class="reference-accessdate">. Retrieved <span class="nowrap">July [...]
+<li id="cite_note-citizendium_WP_trolling_issues_1-167"><span class="mw-cite-backlink"><b><a href="#cite_ref-citizendium_WP_trolling_issues_1_167-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="https://web.archive.org/web/20061011230402/http://www.citizendium.org/essay.html">"Toward a New Compendium of Knowledge (longer version)"</a>. <i>Citizendium</i>. Archived from <a rel="nofollow" class="external text" href="h [...]
+<li id="cite_note-ReferenceA-168"><span class="mw-cite-backlink">^ <a href="#cite_ref-ReferenceA_168-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-ReferenceA_168-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text">June 16, 2014, "Wikipedia Strengthens Rules Against Undisclosed Editing", By Jeff Elder, <i>The Wall Street Journal</i>.</span></li>
+<li id="cite_note-DeathByWikipedia-169"><span class="mw-cite-backlink"><b><a href="#cite_ref-DeathByWikipedia_169-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Ahrens, Frank (July 9, 2006). <a rel="nofollow" class="external text" href="http://www.washingtonpost.com/wp-dyn/content/article/2006/07/08/AR2006070800135.html">"Death by Wikipedia: The Kenneth Lay Chronicles"</a>. <i>The Washington Post</i><span class="reference-accessdate">. Retrieved <span class= [...]
+<li id="cite_note-cnet_politicians_and_WP_1-170"><span class="mw-cite-backlink"><b><a href="#cite_ref-cnet_politicians_and_WP_1_170-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Kane, Margaret (January 30, 2006). <a rel="nofollow" class="external text" href="http://news.cnet.com/8301-10784_3-6032713-7.html">"Politicians notice Wikipedia"</a>. CNET<span class="reference-accessdate">. Retrieved <span class="nowrap">January 28,</span> 2007</span>.</cite><span t [...]
+<li id="cite_note-msnbc_MS_cash_for_WP_edits_1-171"><span class="mw-cite-backlink"><b><a href="#cite_ref-msnbc_MS_cash_for_WP_edits_1_171-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a href="/wiki/Brian_Bergstein" title="Brian Bergstein">Bergstein, Brian</a> (January 23, 2007). <a rel="nofollow" class="external text" href="http://www.msnbc.msn.com/id/16775981">"Microsoft offers cash for Wikipedia edit"</a>. MSNBC<span class="reference-accessdate">. Retriev [...]
+<li id="cite_note-Seeing_Corporate_Fingerprints-172"><span class="mw-cite-backlink"><b><a href="#cite_ref-Seeing_Corporate_Fingerprints_172-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Hafner, Katie (August 19, 2007). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2007/08/19/technology/19wikipedia.html">"Lifting Corporate Fingerprints From the Editing of Wikipedia"</a>. <i>The New York Times</i>. p. 1<span class="reference-access [...]
+<li id="cite_note-wikiality-173"><span class="mw-cite-backlink">^ <a href="#cite_ref-wikiality_173-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-wikiality_173-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Stephen Colbert (July 30, 2006). <a rel="nofollow" class="external text" href="http://www.cc.com/video-clips/z1aahs/the-colbert-report-the-word---wikiality">"Wikiality"</a><span class="reference-accessdate">. Retrieved <span cla [...]
+<li id="cite_note-WideWorldOfWikipedia-174"><span class="mw-cite-backlink"><b><a href="#cite_ref-WideWorldOfWikipedia_174-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.emorywheel.com/detail.php?n=17902">"Wide World of Wikipedia"</a>. The Emory Wheel. April 21, 2006<span class="reference-accessdate">. Retrieved <span class="nowrap">October 17,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&a [...]
+<li id="cite_note-insidehighered_against_WP_1-175"><span class="mw-cite-backlink"><b><a href="#cite_ref-insidehighered_against_WP_1_175-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Waters, N. L. (2007). "Why you can't cite Wikipedia in my class". <i>Communications of the ACM</i> <b>50</b> (9): 15. <a href="/wiki/Digital_object_identifier" title="Digital object identifier">doi</a>:<a rel="nofollow" class="external text" href="//dx.doi.org/10.1145%2F12846 [...]
+<li id="cite_note-insidehighered_wiki_no_cite-176"><span class="mw-cite-backlink"><b><a href="#cite_ref-insidehighered_wiki_no_cite_176-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Jaschik, Scott (January 26, 2007). <a rel="nofollow" class="external text" href="http://www.insidehighered.com/news/2007/01/26/wiki">"A Stand Against Wikipedia"</a>. Inside Higher Ed<span class="reference-accessdate">. Retrieved <span class="nowrap">January 27,</span> 2007</span [...]
+<li id="cite_note-AWorkInProgress-177"><span class="mw-cite-backlink"><b><a href="#cite_ref-AWorkInProgress_177-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Helm, Burt (December 14, 2005). <a rel="nofollow" class="external text" href="http://www.businessweek.com/technology/content/dec2005/tc20051214_441708.htm">"Wikipedia: "A Work in Progress<span style="padding-right:0.2em;">"</span>"</a>. <i>Bloomberg BusinessWeek</i><span class="reference-accessdate">. [...]
+<li id="cite_note-Jimmy_Wales_don.27t_cite_WP_1-178"><span class="mw-cite-backlink"><b><a href="#cite_ref-Jimmy_Wales_don.27t_cite_WP_1_178-0">^</a></b></span> <span class="reference-text">"Jimmy Wales", <i>Biography Resource Center Online</i>. (Gale, 2006.)</span></li>
+<li id="cite_note-thecrimson_wiki_debate-179"><span class="mw-cite-backlink"><b><a href="#cite_ref-thecrimson_wiki_debate_179-0">^</a></b></span> <span class="reference-text">Child, Maxwell L., <a rel="nofollow" class="external text" href="http://www.thecrimson.com/article.aspx?ref=517305">"Professors Split on Wiki Debate"</a>, <i>The Harvard Crimson</i>, Monday, February 26, 2007.</span></li>
+<li id="cite_note-stothart-180"><span class="mw-cite-backlink"><b><a href="#cite_ref-stothart_180-0">^</a></b></span> <span class="reference-text">Chloe Stothart, <a rel="nofollow" class="external text" href="http://www.timeshighereducation.co.uk/story.asp?sectioncode=26&storycode=209408">Web threatens learning ethos</a>, <i>The Times Higher Education Supplement</i>, 2007, 1799 (June 22), page 2</span></li>
+<li id="cite_note-Nutshell_in-depth_resources-181"><span class="mw-cite-backlink"><b><a href="#cite_ref-Nutshell_in-depth_resources_181-0">^</a></b></span> <span class="reference-text"><cite class="citation book">Cohen, Morris; Olson, Kent (2010). <i>Legal Research in a Nutshell</i> (10th ed.). St. Paul, Minnesota, USA: Thomson Reuters. pp. 32–34. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSo [...]
+<li id="cite_note-Julie_Beck_2014-182"><span class="mw-cite-backlink">^ <a href="#cite_ref-Julie_Beck_2014_182-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Julie_Beck_2014_182-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text">Julie Beck. "Doctors' #1 Source for Healthcare Information: Wikipedia". <i>The Atlantic</i>, March 5, 2014.</span></li>
+<li id="cite_note-theatlantic.com-183"><span class="mw-cite-backlink">^ <a href="#cite_ref-theatlantic.com_183-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-theatlantic.com_183-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation web">Green, Emma (May 7, 2014). <a rel="nofollow" class="external text" href="http://www.theatlantic.com/health/archive/2014/05/can-wikipedia-ever-be-a-definitive-medical-text/361822/">"Can Wikipedia Ever Be a Defini [...]
+<li id="cite_note-Rosenzweig-184"><span class="mw-cite-backlink">^ <a href="#cite_ref-Rosenzweig_184-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Rosenzweig_184-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation journal">Roy Rosenzweig (June 2006). <a rel="nofollow" class="external text" href="http://chnm.gmu.edu/essays-on-history-new-media/essays/?essayid=42">"Can History be Open Source? Wikipedia and the Future of the Past"</a>. <i>The J [...]
+<li id="cite_note-theregister_Wales_WP_founder_on_quality_1-185"><span class="mw-cite-backlink"><b><a href="#cite_ref-theregister_Wales_WP_founder_on_quality_1_185-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Andrew Orlowski (October 18, 2005). <a rel="nofollow" class="external text" href="http://www.theregister.co.uk/2005/10/18/wikipedia_quality_problem/page2.html">"Wikipedia founder admits to serious quality problems"</a>. <i>The Register</i><span class=" [...]
+<li id="cite_note-upi_accuracy_1-186"><span class="mw-cite-backlink"><b><a href="#cite_ref-upi_accuracy_1_186-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.sciencedaily.com/releases/2010/06/100601114641.htm">"Cancer information on Wikipedia is accurate, but not very readable, study finds"</a>. <i>Science Daily</i>. June 2, 2010<span class="reference-accessdate">. Retrieved <span class="nowrap">Decembe [...]
+<li id="cite_note-economist_incomplete_info-187"><span class="mw-cite-backlink"><b><a href="#cite_ref-economist_incomplete_info_187-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.economist.com/node/8820422?story_id=8820422">"Fact or fiction? Wikipedia's variety of contributors is not only a strength"</a>. <i>The Economist</i>. March 10, 2007<span class="reference-accessdate">. Retrieved <span class="no [...]
+<li id="cite_note-WP_advantages_over_trad_media_1-188"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_advantages_over_trad_media_1_188-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:PAPER" title="Wikipedia:PAPER" class="mw-redirect">Wikipedia:PAPER</a></span></li>
+<li id="cite_note-Economist_disagreements_not_uncommon-189"><span class="mw-cite-backlink"><b><a href="#cite_ref-Economist_disagreements_not_uncommon_189-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.economist.com/printedition/displaystory.cfm?story_id=10789354">"The battle for Wikipedia's soul"</a>. <i>The Economist</i>. March 6, 2008<span class="reference-accessdate">. Retrieved <span class="nowrap" [...]
+<li id="cite_note-telegraph_WP_torn_apart_1-190"><span class="mw-cite-backlink"><b><a href="#cite_ref-telegraph_WP_torn_apart_1_190-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Douglas, Ian (November 10, 2007). <a rel="nofollow" class="external text" href="http://www.telegraph.co.uk/technology/3354752/Wikipedia-an-online-encyclopedia-torn-apart.html">"Wikipedia: an online encyclopedia torn apart"</a>. <i>The Daily Telegraph</i> (London)<span class="referen [...]
+<li id="cite_note-Taylor-191"><span class="mw-cite-backlink"><b><a href="#cite_ref-Taylor_191-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Sophie Taylor (April 5, 2008). <a rel="nofollow" class="external text" href="http://in.reuters.com/article/technologyNews/idINIndia-32865420080405">"China allows access to English Wikipedia"</a>. Reuters<span class="reference-accessdate">. Retrieved <span class="nowrap">July 29,</span> 2008</span>.</cite><span title="ct [...]
+<li id="cite_note-washington_post_state_censorship_1-192"><span class="mw-cite-backlink"><b><a href="#cite_ref-washington_post_state_censorship_1_192-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Bruilliard, Karin (May 21, 2010). <a rel="nofollow" class="external text" href="http://www.washingtonpost.com/wp-dyn/content/article/2010/05/20/AR2010052005073.html">"Pakistan blocks YouTube a day after shutdown of Facebook over Muhammad issue"</a>. <i>The Washingt [...]
+<li id="cite_note-BBC_child_image_censored_1-193"><span class="mw-cite-backlink"><b><a href="#cite_ref-BBC_child_image_censored_1_193-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://news.bbc.co.uk/1/hi/uk/7770456.stm">"Wikipedia child image censored"</a>. BBC News. December 8, 2008<span class="reference-accessdate">. Retrieved <span class="nowrap">December 8,</span> 2008</span>.</cite><span title="ctx_ver=Z [...]
+<li id="cite_note-Kittur2009-194"><span class="mw-cite-backlink">^ <a href="#cite_ref-Kittur2009_194-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Kittur2009_194-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text">Kittur, A., Chi, E. H., and Suh, B. 2009. <a rel="nofollow" class="external text" href="http://www-users.cs.umn.edu/~echi/papers/2009-CHI2009/p1509.pdf">What's in Wikipedia? Mapping Topics and Conflict Using Socially Annotated Category Structure</a>. In [...]
+<li id="cite_note-NYT_subjects_and_articles-195"><span class="mw-cite-backlink"><b><a href="#cite_ref-NYT_subjects_and_articles_195-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Petrusich, Amanda (October 20, 2011). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2011/10/21/theater/editing-wikipedia-at-the-new-york-public-library-for-the-performing-arts.html">"Wikipedia's Deep Dive Into a Library Collection"</a>. <i>The New York Times</ [...]
+<li id="cite_note-196"><span class="mw-cite-backlink"><b><a href="#cite_ref-196">^</a></b></span> <span class="reference-text"><cite class="citation journal">Lam, Shyong; Anuradha Uduwage; Zhenhua Dong; Shilad Sen; David R. Musicant; Loren Terveen; John Riedl (October 3–5, 2011). <a rel="nofollow" class="external text" href="http://files.grouplens.org/papers/wp-gender-wikisym2011.pdf">"WP: Clubhouse? An Exploration of Wikipedia's Gender Imblance"</a> <span style="font-size:85%;">(PDF)</s [...]
+<li id="cite_note-zerogeography_places_coverage-197"><span class="mw-cite-backlink"><b><a href="#cite_ref-zerogeography_places_coverage_197-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://zerogeography.blogspot.com/2009/11/mapping-geographies-of-wikipedia.html">"Mapping the Geographies of Wikipedia Content"</a>. <i>Mark Graham Oxford Internet Institute</i>. ZeroGeography<span class="reference-accessdate">. R [...]
+<li id="cite_note-Quilter-198"><span class="mw-cite-backlink">^ <a href="#cite_ref-Quilter_198-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Quilter_198-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation web">Quilter, Laura (October 24, 2012). <a rel="nofollow" class="external text" href="http://works.bepress.com/cgi/viewcontent.cgi?article=1035&context=laura_quilter">"Systemic Bias in Wikipedia: What It Looks Like, and How to Deal with [...]
+<li id="cite_note-199"><span class="mw-cite-backlink"><b><a href="#cite_ref-199">^</a></b></span> <span class="reference-text">"Edit Wars Reveal the 10 Most Controversial Topics on Wikipedia", MIT Technology Review, July 17, 2013.</span></li>
+<li id="cite_note-autogenerated3-200"><span class="mw-cite-backlink">^ <a href="#cite_ref-autogenerated3_200-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-autogenerated3_200-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-autogenerated3_200-2"><sup><i><b>c</b></i></sup></a></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2269392">"The Most Controversial Topics in Wi [...]
+<li id="cite_note-autogenerated4-201"><span class="mw-cite-backlink"><b><a href="#cite_ref-autogenerated4_201-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Sanger, Larry. <a rel="nofollow" class="external text" href="http://larrysanger.org/2012/05/what-should-we-do-about-wikipedias-porn-problem">"What should we do about Wikipedia's porn problem?"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">July 26,</span> 2012</span>.</cite><span t [...]
+<li id="cite_note-Register_ISP_censorship-202"><span class="mw-cite-backlink"><b><a href="#cite_ref-Register_ISP_censorship_202-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Metz, Cade (December 7, 2008). <a rel="nofollow" class="external text" href="http://www.theregister.co.uk/2008/12/07/brit_isps_censor_wikipedia">"Brit ISPs censor Wikipedia over 'child porn' album cover"</a>. <i><a href="/wiki/The_Register" title="The Register">The Register</a></i><span [...]
+<li id="cite_note-WP_free_speech_debate-203"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_free_speech_debate_203-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Raphael, JR (December 10, 2008). <a rel="nofollow" class="external text" href="http://www.washingtonpost.com/wp-dyn/content/article/2008/12/08/AR2008120803188.html">"Wikipedia Censorship Sparks Free Speech Debate"</a>. <i>The Washington Post</i><span class="reference-accessdate">. Retrieved [...]
+<li id="cite_note-Inquirer_child_abuse_allegations-204"><span class="mw-cite-backlink"><b><a href="#cite_ref-Inquirer_child_abuse_allegations_204-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Farrell, Nick (April 29, 2010). <a rel="nofollow" class="external text" href="http://www.theinquirer.net/inquirer/news/1603521/wikipedia-denies-child-abuse-allegations">"Wikipedia denies child abuse allegations: Co-founder grassed the outfit to the FBI"</a>. <i>The Inq [...]
+<li id="cite_note-The_Register-April-205"><span class="mw-cite-backlink">^ <a href="#cite_ref-The_Register-April_205-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-The_Register-April_205-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Metz, Cade (April 9, 2010). <a rel="nofollow" class="external text" href="http://www.theregister.co.uk/2010/04/09/sanger_reports_wikimedia_to_the_fbi/">"Wikifounder reports Wikiparent to FBI over 'chil [...]
+<li id="cite_note-TET_child_porn_accusations-206"><span class="mw-cite-backlink"><b><a href="#cite_ref-TET_child_porn_accusations_206-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://economictimes.indiatimes.com/infotech/internet/Wikipedia-blasts-co-founders-accusations-of-child-porn-on-website/articleshow/5871943.cms">"Wikipedia blasts co-founder's accusations of child porn on website"</a>. <i>The Economic [...]
+<li id="cite_note-AFP-207"><span class="mw-cite-backlink">^ <a href="#cite_ref-AFP_207-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-AFP_207-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.google.com/hostednews/afp/article/ALeqM5iPnPNqEkWafeVXnPIWfaS2wN6XSQ">"Wikipedia blasts talk of child porn at website"</a>. <span lang="fr">Agence France-Presse</span>. April 28, 2010<span [...]
+<li id="cite_note-BBC_News_Wales_cedes_rights-208"><span class="mw-cite-backlink"><b><a href="#cite_ref-BBC_News_Wales_cedes_rights_208-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://news.bbc.co.uk/2/hi/technology/10104946.stm">"Wikimedia pornography row deepens as Wales cedes rights"</a>. BBC News. May 10, 2010<span class="reference-accessdate">. Retrieved <span class="nowrap">May 19,</span> 2010</span>.< [...]
+<li id="cite_note-XBIZ-209"><span class="mw-cite-backlink"><b><a href="#cite_ref-XBIZ_209-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Gray, Lila (September 17, 2013). <a rel="nofollow" class="external text" href="http://newswire.xbiz.com/view.php?id=169017">"Wikipedia Gives Porn a Break"</a>. <i>XBIZ.com</i><span class="reference-accessdate">. Retrieved <span class="nowrap">November 10,</span> 2013</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id [...]
+<li id="cite_note-210"><span class="mw-cite-backlink"><b><a href="#cite_ref-210">^</a></b></span> <span class="reference-text">Andrew McStay, 2014, <a rel="nofollow" class="external text" href="http://www.amazon.co.uk/Privacy-Philosophy-Affective-Protocol-Formations/dp/143311898X">Privacy and Philosophy: New Media and Affective Protocol</a>, New York Peter Lang.</span></li>
+<li id="cite_note-heise_Tron_public_issue_1-212"><span class="mw-cite-backlink"><b><a href="#cite_ref-heise_Tron_public_issue_1_212-0">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://www.heise.de/newsticker/meldung/Gericht-weist-einstweilige-Verfuegung-gegen-Wikimedia-Deutschland-ab-Update-173587.html">Heise – <span lang="de">Gericht weist einstweilige Verfügung gegen Wikimedia Deutschland ab</span>[Update</a>, by Torsten Kleinz, Februar [...]
+<li id="cite_note-213"><span class="mw-cite-backlink"><b><a href="#cite_ref-213">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.otrs.com/en/">"IT Service Management Software"</a>. OTRS.com<span class="reference-accessdate">. Retrieved <span class="nowrap">June 9,</span> 2012</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=IT+Service+Managem [...]
+<li id="cite_note-214"><span class="mw-cite-backlink"><b><a href="#cite_ref-214">^</a></b></span> <span class="reference-text"><cite class="citation web">Paling, Emma. <a rel="nofollow" class="external text" href="http://www.theatlantic.com/technology/archive/2015/10/how-wikipedia-is-hostile-to-women/411619/">"Wikipedia's Hostility to Women"</a>. <i>The Atlantic</i><span class="reference-accessdate">. Retrieved <span class="nowrap">October 24,</span> 2015</span>.</cite><span title="ctx_v [...]
+<li id="cite_note-215"><span class="mw-cite-backlink"><b><a href="#cite_ref-215">^</a></b></span> <span class="reference-text"><cite class="citation web">Auerbach, David. <a rel="nofollow" class="external text" href="http://www.slate.com/articles/technology/bitwise/2014/12/wikipedia_editing_disputes_the_crowdsourced_encyclopedia_has_become_a_rancorous.html">"Encyclopedia Frown"</a>. <i>Slate</i><span class="reference-accessdate">. Retrieved <span class="nowrap">October 24,</span> 2015</s [...]
+<li id="cite_note-216"><span class="mw-cite-backlink"><b><a href="#cite_ref-216">^</a></b></span> <span class="reference-text"><cite class="citation book">Ayers, Phoebe (2008). <i>How Wikipedia Works</i>. San Francisco: No Starch Press. p. 213. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/1-59327-176-X" title="Special:BookSources/1-59327-176-X">1-59327-176-X</a>.</cite><span title="ctx_ [...]
+<li id="cite_note-financialstatements-217"><span class="mw-cite-backlink"><b><a href="#cite_ref-financialstatements_217-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://upload.wikimedia.org/wikipedia/foundation/a/ac/FINAL_10_11From_KPMG.pdf">"Wikimedia Foundation – Financial Statements – June 30, 2011 and 2010"</a> <span style="font-size:85%;">(PDF)</span>. Wikimedia Foundation.</cite><span title="ctx_ver=Z39.88-2004&rf [...]
+<li id="cite_note-218"><span class="mw-cite-backlink"><b><a href="#cite_ref-218">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://upload.wikimedia.org/wikipedia/foundation/5/5c/Form_990_-_FY_12-13_-_Public.pdf">"Wikimedia Foundation IRS Form 990"</a> <span style="font-size:85%;">(PDF)</span><span class="reference-accessdate">. Retrieved <span class="nowrap">October 14,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-2004 [...]
+<li id="cite_note-219"><span class="mw-cite-backlink"><b><a href="#cite_ref-219">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://wikimediafoundation.org/wiki/Press_releases/WMF_announces_new_ED_Lila_Tretikov">"Press releases/WMF announces new ED Lila Tretikov"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">June 14,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-2004&rf [...]
+<li id="cite_note-Jeff_Elder_2014-220"><span class="mw-cite-backlink">^ <a href="#cite_ref-Jeff_Elder_2014_220-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Jeff_Elder_2014_220-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text">Jeff Elder, <i>The Wall Street Journal</i>, May 1, 2014, "Wikipedia's New Chief: From Soviet Union to World's Sixth-Largest Site".</span></li>
+<li id="cite_note-nytimes.com-221"><span class="mw-cite-backlink">^ <a href="#cite_ref-nytimes.com_221-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-nytimes.com_221-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Naom Cohen (May 1, 2014). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2014/05/02/business/media/open-source-software-specialist-selected-as-executive-director-of-wikipedia.html?_r=0">"Media: Open-S [...]
+<li id="cite_note-nedworks_database_system-222"><span class="mw-cite-backlink"><b><a href="#cite_ref-nedworks_database_system_222-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Mark Bergman. <a rel="nofollow" class="external text" href="http://www.nedworks.org/~mark/presentations/san/Wikimedia%20architecture.pdf">"Wikimedia Architecture"</a> <span style="font-size:85%;">(PDF)</span>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span cla [...]
+<li id="cite_note-WP_extensions_installed-223"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_extensions_installed_223-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://en.wikipedia.org/wiki/Special:Version">"Version: Installed extensions"</a>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Version%3A+Installed+extensions&rft.genre=book&rft_id=http [...]
+<li id="cite_note-224"><span class="mw-cite-backlink"><b><a href="#cite_ref-224">^</a></b></span> <span class="reference-text"><cite class="citation web">Michael Snow. <a class="external text" href="https://en.wikipedia.org/wiki/Wikipedia:Wikipedia_Signpost/2005-04-18/Lucene_search">"Lucene search: Internal search function returns to service"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">February 26,</span> 2009</span>.</cite><span title="ct [...]
+<li id="cite_note-225"><span class="mw-cite-backlink"><b><a href="#cite_ref-225">^</a></b></span> <span class="reference-text"><cite class="citation web">Brion Vibber. <a class="external text" href="http://lists.wikimedia.org/pipermail/wikitech-l/2005-April/016297.html">"[Wikitech-l] Lucene search"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">February 26,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipe [...]
+<li id="cite_note-226"><span class="mw-cite-backlink"><b><a href="#cite_ref-226">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://www.mediawiki.org/wiki/Extension:Lucene-search">"Extension:Lucene-search"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">August 31,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.bt [...]
+<li id="cite_note-227"><span class="mw-cite-backlink"><b><a href="#cite_ref-227">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://svn.wikimedia.org/svnroot/mediawiki/branches/lucene-search-2.1/lib">"mediawiki – Revision 55688: /branches/lucene-search-2.1/lib"</a>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">August 31,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&am [...]
+<li id="cite_note-thenextwebve-228"><span class="mw-cite-backlink"><b><a href="#cite_ref-thenextwebve_228-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Emil Protalinski (July 2, 2013). <a rel="nofollow" class="external text" href="http://thenextweb.com/insider/2013/07/02/wikimedia-rolls-out-its-wysiwyg-visual-editor-for-logged-in-users-accessing-wikipedia-articles-in-english/">"Wikimedia rolls out WYSIWYG visual editor for logged-in users accessing Wikipedia [...]
+<li id="cite_note-229"><span class="mw-cite-backlink"><b><a href="#cite_ref-229">^</a></b></span> <span class="reference-text"><cite class="citation web">Curtis, Sophie (July 23, 2013). <a rel="nofollow" class="external text" href="http://www.telegraph.co.uk/technology/wikipedia/10196578/Wikipedia-introduces-new-features-to-entice-editors.html">"Wikipedia introduces new features to entice editors"</a>. The Daily Telegraph<span class="reference-accessdate">. Retrieved <span class="nowrap" [...]
+<li id="cite_note-TheEconomistVE-230"><span class="mw-cite-backlink"><b><a href="#cite_ref-TheEconomistVE_230-0">^</a></b></span> <span class="reference-text"><cite class="citation news">L.M. (December 13, 2011). <a rel="nofollow" class="external text" href="http://www.economist.com/blogs/babbage/2011/12/changes-wikipedia">"Changes at Wikipedia: Seeing things"</a>. <i><a href="/wiki/The_Economist" title="The Economist">The Economist</a></i><span class="reference-accessdate">. Retrieved < [...]
+<li id="cite_note-softpedia-best-231"><span class="mw-cite-backlink"><b><a href="#cite_ref-softpedia-best_231-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Lucian Parfeni (July 2, 2013). <a rel="nofollow" class="external text" href="http://news.softpedia.com/news/Wikipedia-s-New-VisualEditor-Is-the-Best-Update-in-Years-and-You-Can-Make-It-Better-365072.shtml">"Wikipedia's New VisualEditor Is the Best Update in Years and You Can Make It Better"</a>. <i><a hre [...]
+<li id="cite_note-Orlowski.2C_Andrew-232"><span class="mw-cite-backlink">^ <a href="#cite_ref-Orlowski.2C_Andrew_232-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Orlowski.2C_Andrew_232-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation web">Orlowski, Andrew (August 1, 2013). <a rel="nofollow" class="external text" href="http://www.theregister.co.uk/2013/08/01/wikipedians_reject_wysiwyg_editor/">"Wikipedians say no to Jimmy's 'buggy' WYSIWY [...]
+<li id="cite_note-233"><span class="mw-cite-backlink"><b><a href="#cite_ref-233">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Bots" title="Wikipedia:Bots">Wikipedia Bot Information</a></span></li>
+<li id="cite_note-meetbots-234"><span class="mw-cite-backlink">^ <a href="#cite_ref-meetbots_234-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-meetbots_234-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><cite class="citation news">Daniel Nasaw (July 24, 2012). <a rel="nofollow" class="external text" href="http://www.bbc.co.uk/news/magazine-18892510">"Meet the 'bots' that edit Wikipedia"</a>. BBC News.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3A [...]
+<li id="cite_note-235"><span class="mw-cite-backlink"><b><a href="#cite_ref-235">^</a></b></span> <span class="reference-text"><cite class="citation news">Halliday, Josh; Arthur, Charles (July 26, 2012). <a rel="nofollow" class="external text" href="http://www.guardian.co.uk/technology/blog/2012/jul/26/boot-up-wikipedia-apple">"Boot up: The Wikipedia vandalism police, Apple analysts, and more"</a>. <i><a href="/wiki/The_Guardian" title="The Guardian">The Guardian</a></i><span class="refe [...]
+<li id="cite_note-236"><span class="mw-cite-backlink"><b><a href="#cite_ref-236">^</a></b></span> <span class="reference-text"><cite class="citation news">Jervell, Ellen Emmerentze (July 13, 2014). <a rel="nofollow" class="external text" href="http://online.wsj.com/articles/for-this-author-10-000-wikipedia-articles-is-a-good-days-work-1405305001">"For This Author, 10,000 Wikipedia Articles Is a Good Day's Work"</a>. The Wall Street Journal<span class="reference-accessdate">. Retrieved <s [...]
+<li id="cite_note-237"><span class="mw-cite-backlink"><b><a href="#cite_ref-237">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://en.wikipedia.org/wiki/Wikipedia:Wikipedia_Signpost/2009-03-23/Abuse_Filter">"Wikipedia signpost: Abuse Filter is enabled"</a>. English Wikipedia. March 23, 2009<span class="reference-accessdate">. Retrieved <span class="nowrap">July 13,</span> 2010</span>.</cite><span title="ctx_ver=Z39.88-2004& [...]
+<li id="cite_note-238"><span class="mw-cite-backlink"><b><a href="#cite_ref-238">^</a></b></span> <span class="reference-text">Aljazeera, July 21, 2014, "MH17 Wikipedia entry edited from Russian Government IP Address". <a rel="nofollow" class="external autonumber" href="http://stream.aljazeera.com/story/201407211855-0023944">[2]</a></span></li>
+<li id="cite_note-239"><span class="mw-cite-backlink"><b><a href="#cite_ref-239">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Bot_policy" title="Wikipedia:Bot policy">Wikipedia's policy on bots</a></span></li>
+<li id="cite_note-240"><span class="mw-cite-backlink"><b><a href="#cite_ref-240">^</a></b></span> <span class="reference-text">Andrew Lih (2009). <i><a href="/wiki/The_Wikipedia_Revolution" title="The Wikipedia Revolution">The Wikipedia Revolution</a></i>, chapter <i>Then came the Bots</i>, pp. 99-106.</span></li>
+<li id="cite_note-241"><span class="mw-cite-backlink"><b><a href="#cite_ref-241">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://en.wikipedia.org/wiki/Wikipedia:WikiProject">"Wikipedia: Wikiprojects"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">March 16,</span> 2015</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Wikipedia%3A+Wikipro [...]
+<li id="cite_note-WP_1.0_editorial_team_1-242"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_1.0_editorial_team_1_242-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://en.wikipedia.org/wiki/Wikipedia:Version_1.0_Editorial_Team/Assessment">"Wikipedia:Version 1.0 Editorial Team/Assessment"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">October 28,</span> 2007</span>.</cite><span title="ctx_ve [...]
+<li id="cite_note-FMonday_feat_article_patterns_1-243"><span class="mw-cite-backlink"><b><a href="#cite_ref-FMonday_feat_article_patterns_1_243-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://firstmonday.org/htbin/cgiwrap/bin/ojs/index.php/fm/article/view/2365/2182">"Comparing featured article groups and revision patterns correlations in Wikipedia"</a>. <a href="/wiki/First_Monday_(journal)" title="First Mon [...]
+<li id="cite_note-IBM_feat_articles_hidden_pattern_1-244"><span class="mw-cite-backlink"><b><a href="#cite_ref-IBM_feat_articles_hidden_pattern_1_244-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Fernanda B. Viégas, Martin Wattenberg, and Matthew M. McKeon (July 22, 2007). <a rel="nofollow" class="external text" href="http://www.research.ibm.com/visual/papers/hidden_order_wikipedia.pdf">"The Hidden Order of Wikipedia"</a> <span style="font-size:85%;">(PD [...]
+<li id="cite_note-Poderi_Giacomo_feat_articles_1-245"><span class="mw-cite-backlink"><b><a href="#cite_ref-Poderi_Giacomo_feat_articles_1_245-0">^</a></b></span> <span class="reference-text">Poderi, Giacomo, <i>Wikipedia and the Featured Articles: How a Technological System Can Produce Best Quality Articles</i>, Master thesis, <a href="/wiki/University_of_Maastricht" title="University of Maastricht" class="mw-redirect">University of Maastricht</a>, October 2008.</span></li>
+<li id="cite_note-FMonday_WP_quality_control_1-246"><span class="mw-cite-backlink"><b><a href="#cite_ref-FMonday_WP_quality_control_1_246-0">^</a></b></span> <span class="reference-text"><cite class="citation news">David Lindsey. <a rel="nofollow" class="external text" href="http://firstmonday.org/htbin/cgiwrap/bin/ojs/index.php/fm/article/viewArticle/2721/2482">"Evaluating quality control of Wikipedia's featured articles"</a>. First Monday.</cite><span title="ctx_ver=Z39.88-2004&rfr [...]
+<li id="cite_note-en.wikipedia-247"><span class="mw-cite-backlink">^ <a href="#cite_ref-en.wikipedia_247-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-en.wikipedia_247-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><a href="/wiki/Wikipedia:Version_1.0_Editorial_Team/Statistics" title="Wikipedia:Version 1.0 Editorial Team/Statistics">Wikipedia:Version 1.0 Editorial Team/Statistics – Wikipedia, the free encyclopedia</a></span></li>
+<li id="cite_note-WP_tools_requests_per_day-248"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_tools_requests_per_day_248-0">^</a></b></span> <span class="reference-text"><a href="//toolserver.org/~leon/stats/reqstats/reqstats-monthly.png" class="extiw" title="tools:~leon/stats/reqstats/reqstats-monthly.png">"Monthly request statistics"</a>, Wikimedia. Retrieved October 31, 2008.<sup class="noprint Inline-Template"><span style="white-space: nowrap;">[<i><a href="/wiki/Wikipedia [...]
+<li id="cite_note-site_internals_configuration-249"><span class="mw-cite-backlink"><b><a href="#cite_ref-site_internals_configuration_249-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Domas Mituzas. <a rel="nofollow" class="external text" href="http://domasmituzas.files.wordpress.com/2011/09/mysqluc2007-wikipedia-workbook.pdf">"Wikipedia: Site internals, configuration, code examples and management issues"</a> <span style="font-size:85%;">(PDF)</span>. MySQL [...]
+<li id="cite_note-globule_access_trace-250"><span class="mw-cite-backlink"><b><a href="#cite_ref-globule_access_trace_250-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Guido Urdaneta, Guillaume Pierre and Maarten van Steen. <a rel="nofollow" class="external text" href="http://www.globule.org/publi/WWADH_comnet2009.html">"Wikipedia Workload Analysis for Decentralized Hosting"</a>. Elsevier Computer Networks 53 (11), pp. 1830–1845, June 2009.</cite><span title [...]
+<li id="cite_note-CW_WP_simplifies_infrastructure-251"><span class="mw-cite-backlink"><b><a href="#cite_ref-CW_WP_simplifies_infrastructure_251-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Weiss, Todd R. (October 9, 2008). <a rel="nofollow" class="external text" href="http://www.computerworld.com/s/article/9116787/Wikipedia_simplifies_IT_infrastructure_by_moving_to_one_Linux_vendor?taxonomyId=154&pageNumber=1&taxonomyName=Servers%20and%20Data%20Cen [...]
+<li id="cite_note-ars_tech_Ubuntu_server_infra-252"><span class="mw-cite-backlink"><b><a href="#cite_ref-ars_tech_Ubuntu_server_infra_252-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Paul, Ryan (October 9, 2008). <a rel="nofollow" class="external text" href="http://arstechnica.com/open-source/news/2008/10/wikipedia-adopts-ubuntu-for-its-server-infrastructure.ars">"Wikipedia adopts Ubuntu for its server infrastructure"</a>. Ars Technica<span class="referenc [...]
+<li id="cite_note-servers-253"><span class="mw-cite-backlink"><b><a href="#cite_ref-servers_253-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://wikitech.wikimedia.org/view/Server_roles">"Server roles at wikitech.wikimedia.org"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">December 8,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia& [...]
+<li id="cite_note-254"><span class="mw-cite-backlink"><b><a href="#cite_ref-254">^</a></b></span> <span class="reference-text"><cite class="citation web">Guillaume Palmier. <a class="external text" href="https://blog.wikimedia.org/2013/01/19/wikimedia-sites-move-to-primary-data-center-in-ashburn-virginia/">"Wikimedia sites to move to primary data center in Ashburn, Virginia"</a>. WMF.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.au=G [...]
+<li id="cite_note-255"><span class="mw-cite-backlink"><b><a href="#cite_ref-255">^</a></b></span> <span class="reference-text"><cite class="citation web">Jason Verge. <a rel="nofollow" class="external text" href="http://www.datacenterknowledge.com/archives/2013/01/14/its-official-equinix-ashburn-is-wikimedias-home/">"It's Official: Ashburn is Wikipedia's New Home"</a>. Data Center Knowledge.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&r [...]
+<li id="cite_note-256"><span class="mw-cite-backlink"><b><a href="#cite_ref-256">^</a></b></span> <span class="reference-text">Simonite, T. (2013). MIT <i>Technology Review</i>.</span></li>
+<li id="cite_note-autogenerated5-257"><span class="mw-cite-backlink"><b><a href="#cite_ref-autogenerated5_257-0">^</a></b></span> <span class="reference-text">Frederic M. Scherer and David Ross, [1970] 1990. <i>Industrial Market Structure and Economic Performance</i>, 3rd ed. Houghton-Mifflin. <a rel="nofollow" class="external text" href="http://papers.ssrn.com/sol3/papers.cfm?abstract_id=1496716">Description</a> and 1st ed. review <a rel="nofollow" class="external text" href="http://www [...]
+ • <a href="/wiki/Google_Scholar" title="Google Scholar">Google Scholar</a> search of <a rel="nofollow" class="external text" href="http://scholar.google.com/scholar?q=Frederic+M.+Scherer&hl=en&btnG=Search&as_sdt=1%2C47&as_sdtp=on">Frederic M. Scherer</a>.</span></li>
+<li id="cite_note-258"><span class="mw-cite-backlink"><b><a href="#cite_ref-258">^</a></b></span> <span class="reference-text">Simonite, T. (2013) MIT <i>Technology Review</i>.</span></li>
+<li id="cite_note-Patents.2C_Citations_pp_89-153-259"><span class="mw-cite-backlink">^ <a href="#cite_ref-Patents.2C_Citations_pp_89-153_259-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Patents.2C_Citations_pp_89-153_259-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text"><i>Patents, Citations, and Innovations</i>, by Adam B. Jaffe, Manuel Trajtenberg, pp 89-153.</span></li>
+<li id="cite_note-Porter.2C_M.E._1985-260"><span class="mw-cite-backlink">^ <a href="#cite_ref-Porter.2C_M.E._1985_260-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Porter.2C_M.E._1985_260-1"><sup><i><b>b</b></i></sup></a> <a href="#cite_ref-Porter.2C_M.E._1985_260-2"><sup><i><b>c</b></i></sup></a></span> <span class="reference-text">Porter, M.E. (1985) Competitive Advantage, Free Press, New York, 1985.</span></li>
+<li id="cite_note-Porter.2C_M.E._1980-261"><span class="mw-cite-backlink">^ <a href="#cite_ref-Porter.2C_M.E._1980_261-0"><sup><i><b>a</b></i></sup></a> <a href="#cite_ref-Porter.2C_M.E._1980_261-1"><sup><i><b>b</b></i></sup></a></span> <span class="reference-text">Porter, M.E. (1980) Competitive Strategy, Free Press, New York, 1980.</span></li>
+<li id="cite_note-262"><span class="mw-cite-backlink"><b><a href="#cite_ref-262">^</a></b></span> <span class="reference-text">Markides, Constantinos (2005). Fast Second, Wiley&Sons Inc., San Francisco, 2005</span></li>
+<li id="cite_note-263"><span class="mw-cite-backlink"><b><a href="#cite_ref-263">^</a></b></span> <span class="reference-text"><cite class="citation news">Cohen, Noam (March 5, 2007). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2007/03/05/technology/05wikipedia.html?pagewanted=2&_r=1">"A Contributor to Wikipedia Has His Fictional Side"</a>. <i>The New York Times</i><span class="reference-accessdate">. Retrieved <span class="nowrap">October 18,</span> 2008</sp [...]
+<li id="cite_note-WP_copyright_and_commerciality_1-264"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_copyright_and_commerciality_1_264-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Copyrights" title="Wikipedia:Copyrights">Wikipedia:Copyrights</a></span></li>
+<li id="cite_note-WPF_switch_to_CC-265"><span class="mw-cite-backlink"><b><a href="#cite_ref-WPF_switch_to_CC_265-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Walter Vermeir (2007). <a class="external text" href="http://wikimediafoundation.org/wiki/Resolution:License_update">"Resolution:License update"</a>. Wikizine<span class="reference-accessdate">. Retrieved <span class="nowrap">December 4,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&r [...]
+<li id="cite_note-voteresult-266"><span class="mw-cite-backlink"><b><a href="#cite_ref-voteresult_266-0">^</a></b></span> <span class="reference-text"><a href="//meta.wikimedia.org/wiki/Licensing_update/Result" class="extiw" title="meta:Licensing update/Result">Wikimedia</a></span></li>
+<li id="cite_note-MW_licensing_QA-267"><span class="mw-cite-backlink"><b><a href="#cite_ref-MW_licensing_QA_267-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/Licensing_update/Questions_and_Answers">"Licensing update/Questions and Answers"</a>. <i>Wikimedia Meta</i>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">February 15,</span> 2009</span>.</cite><span [...]
+<li id="cite_note-MW_licensing_timeline_1-268"><span class="mw-cite-backlink"><b><a href="#cite_ref-MW_licensing_timeline_1_268-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/Licensing_update/Timeline">"Licensing_update/Timeline"</a>. <i>Wikimedia Meta</i>. Wikimedia Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">April 5,</span> 2009</span>.</cite><span title="ctx_ver [...]
+<li id="cite_note-WP_blog_license_migration-269"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_blog_license_migration_269-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://blog.wikimedia.org/2009/05/21/wikimedia-community-approves-license-migration">"Wikimedia community approves license migration"</a>. <i>Wikimedia Foundation</i><span class="reference-accessdate">. Retrieved <span class="nowrap">May 21,</span> 2009 [...]
+<li id="cite_note-NYT_photos_on_WP-270"><span class="mw-cite-backlink"><b><a href="#cite_ref-NYT_photos_on_WP_270-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Cohen, Noam (July 19, 2009). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2009/07/20/arts/20funny.html">"Wikipedia May Be a Font of Facts, but It's a Desert for Photos"</a>. New York Times<span class="reference-accessdate">. Retrieved <span class="nowrap">March 9,</span> 2013< [...]
+<li id="cite_note-reuters_French_defamation_case-271"><span class="mw-cite-backlink"><b><a href="#cite_ref-reuters_French_defamation_case_271-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.reuters.com/article/internetNews/idUSL0280486220071102?feedType=RSS&feedName=internetNews">"Wikipedia cleared in French defamation case"</a>. Reuters. November 2, 2007<span class="reference-accessdate">. Retrieve [...]
+<li id="cite_note-ars_tech_WP_dumb_suing_case-272"><span class="mw-cite-backlink"><b><a href="#cite_ref-ars_tech_WP_dumb_suing_case_272-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Anderson, Nate (May 2, 2008). <a rel="nofollow" class="external text" href="http://arstechnica.com/news.ars/post/20080502-dumb-idea-suing-wikipedia-for-calling-you-dumb.html">"Dumb idea: suing Wikipedia for calling you "dumb<span style="padding-right:0.2em;">"</span>"</a>. Ars Te [...]
+<li id="cite_note-bing_WP_research_and_referencing-273"><span class="mw-cite-backlink"><b><a href="#cite_ref-bing_WP_research_and_referencing_273-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.bing.com/community/site_blogs/b/search/archive/2009/07/27/researching-with-bing-reference.aspxResearching">"With Bing Reference"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">September 9,< [...]
+<li id="cite_note-wikipediaondvd_authorized_1-274"><span class="mw-cite-backlink"><b><a href="#cite_ref-wikipediaondvd_authorized_1_274-0">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://www.wikipediaondvd.com/">"Wikipedia on DVD"</a>. Linterweb. Retrieved June 1, 2007. "Linterweb is authorized to make a commercial use of the Wikipedia trademark restricted to the selling of the Encyclopedia CDs and DVDs".</span></li>
+<li id="cite_note-wikipediaondvd_commercially_available_1-275"><span class="mw-cite-backlink"><b><a href="#cite_ref-wikipediaondvd_commercially_available_1_275-0">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://www.wikipediaondvd.com/site.php?temp=buy">"Wikipedia 0.5 Available on a CD-ROM"</a>. <i>Wikipedia on DVD</i>. Linterweb. "The DVD or CD-ROM version 0.5 was commercially available for purchase." Retrieved June 1, 2007.</span></li>
+<li id="cite_note-WM_polish_WP_on_dvd-276"><span class="mw-cite-backlink"><b><a href="#cite_ref-WM_polish_WP_on_dvd_276-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/Polska_Wikipedia_na_DVD_%28z_Helionem%29/en">"Polish Wikipedia on DVD"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asi [...]
+<li id="cite_note-WP_german_on_dvd_1-277"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_german_on_dvd_1_277-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="https://de.wikipedia.org/wiki/Wikipedia:DVD">"Wikipedia:DVD"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.bt [...]
+<li id="cite_note-python.org_CDPedia_Argentina_1-278"><span class="mw-cite-backlink"><b><a href="#cite_ref-python.org_CDPedia_Argentina_1_278-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://python.org.ar/pyar/Proyectos/CDPedia">"CDPedia (Python Argentina)"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">July 7,</span> 2011</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info% [...]
+<li id="cite_note-WP_CD_selection_1-279"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_CD_selection_1_279-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Wikipedia_CD_Selection" title="Wikipedia:Wikipedia CD Selection">Wikipedia CD Selection</a>. Retrieved September 8, 2009.</span></li>
+<li id="cite_note-WP_into_books_1-280"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_into_books_1_280-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.webcitation.org/5jeCgQjpj">"Wikipedia turned into book"</a>. <i>The Daily Telegraph</i> (London: Telegraph Media Group). June 16, 2009. Archived from <a rel="nofollow" class="external text" href="http://www.telegraph.co.uk/news/newstopics/howabou [...]
+<li id="cite_note-WP_schools_selection_1-281"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_schools_selection_1_281-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://schools-wikipedia.org">"Wikipedia Selection for Schools"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">July 14,</span> 2012</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org% [...]
+<li id="cite_note-FAZ-282"><span class="mw-cite-backlink"><b><a href="#cite_ref-FAZ_282-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Thiel, Thomas (September 27, 2010). <a rel="nofollow" class="external text" href="http://www.faz.net/s/RubCF3AEB154CE64960822FA5429A182360/Doc~E7A20980B9C0D46E99A9F60BC09506343~ATpl~Ecommon~Scontent.html">"Wikipedia und Amazon: Der Marketplace soll es richten"</a>. <i>Frankfurter Allgemeine Zeitung</i> (in German). <a href="/w [...]
+<li id="cite_note-WP_DB_usage_policy_1-283"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_DB_usage_policy_1_283-0">^</a></b></span> <span class="reference-text"><a href="/wiki/Wikipedia:Database_download" title="Wikipedia:Database download">Wikipedia policies</a> on data download</span></li>
+<li id="cite_note-WP_image_data_dumps_1-284"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_image_data_dumps_1_284-0">^</a></b></span> <span class="reference-text"><a href="//meta.wikimedia.org/wiki/Data_dumps#Downloading_Images" class="extiw" title="meta:Data dumps">Data dumps: Downloading Images</a>, <a href="/wiki/Wikimedia_Meta-Wiki" title="Wikimedia Meta-Wiki" class="mw-redirect">Wikimedia Meta-Wiki</a></span></li>
+<li id="cite_note-slis_WP_reference_desk_1-285"><span class="mw-cite-backlink"><b><a href="#cite_ref-slis_WP_reference_desk_1_285-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.slis.indiana.edu/news/story.php?story_id=2064">"Wikipedia Reference Desk"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">September 9,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=i [...]
+<li id="cite_note-286"><span class="mw-cite-backlink"><b><a href="#cite_ref-286">^</a></b></span> <span class="reference-text">Brad Stone, "How Google's Android chief, Sundar Pichai, became the most powerful man in mobile", June 30 – July 6, 2014, <i>Bloomberg BusinessWeek</i>, pp. 47-51.</span></li>
+<li id="cite_note-287"><span class="mw-cite-backlink"><b><a href="#cite_ref-287">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="https://play.google.com/store/apps/details?id=org.wikipedia&hl=en">"Wikipedia - Android Apps on Google Play"</a>. <i>Play.Google.com</i><span class="reference-accessdate">. Retrieved <span class="nowrap">August 21,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=inf [...]
+<li id="cite_note-288"><span class="mw-cite-backlink"><b><a href="#cite_ref-288">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="https://itunes.apple.com/us/app/wikipedia-mobile/id324715238?mt=8">"Wikipedia Mobile on the App Store on iTunes"</a>. <i>iTunes.Apple.com</i>. August 4, 2014<span class="reference-accessdate">. Retrieved <span class="nowrap">August 21,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-20 [...]
+<li id="cite_note-WM_mobile_added_1-289"><span class="mw-cite-backlink"><b><a href="#cite_ref-WM_mobile_added_1_289-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://techblog.wikimedia.org/2009/06/wikimedia-mobile-launch">"Wikimedia Mobile is Officially Launched"</a>. <i>Wikimedia Technical Blog</i>. June 30, 2009<span class="reference-accessdate">. Retrieved <span class="nowrap">July 22,</span> 2009</span>.</cite><span titl [...]
+<li id="cite_note-androgeoid.com_LPOI_WP_1-290"><span class="mw-cite-backlink"><b><a href="#cite_ref-androgeoid.com_LPOI_WP_1_290-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://androgeoid.com/2011/04/local-points-of-interest-in-wikipedia">"Local Points Of Interest In Wikipedia"</a>. May 15, 2011<span class="reference-accessdate">. Retrieved <span class="nowrap">May 15,</span> 2011</span>.</cite><span title= [...]
+<li id="cite_note-ilounge_iphone_gems_WP-291"><span class="mw-cite-backlink"><b><a href="#cite_ref-ilounge_iphone_gems_WP_291-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.ilounge.com/index.php/articles/comments/15802">"iPhone Gems: Wikipedia Apps"</a>. November 30, 2008<span class="reference-accessdate">. Retrieved <span class="nowrap">July 22,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-200 [...]
+<li id="cite_note-292"><span class="mw-cite-backlink"><b><a href="#cite_ref-292">^</a></b></span> <span class="reference-text"><cite class="citation web">Ellis, Justin (January 17, 2013). <a rel="nofollow" class="external text" href="http://www.niemanlab.org/2013/01/wikipedia-plans-to-expand-mobile-access-around-the-globe-with-new-funding">"Wikipedia plans to expand mobile access around the globe with new funding"</a>. <i>NiemanLab</i>. Nieman Journalism Lab<span class="reference-accessd [...]
+<li id="cite_note-293"><span class="mw-cite-backlink"><b><a href="#cite_ref-293">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.technologyreview.com/featuredstory/520446/the-decline-of-wikipedia/">"The Decline of Wikipedia"</a>. <i>MIT Technology Review</i>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=The+Decline+of+Wikipedia&rft.genre=book [...]
+<li id="cite_note-294"><span class="mw-cite-backlink"><b><a href="#cite_ref-294">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://www.nytimes.com/2015/06/21/opinion/can-wikipedia-survive.html?_r=0">Can Wikipedia Survive?</a></span></li>
+<li id="cite_note-295"><span class="mw-cite-backlink"><b><a href="#cite_ref-295">^</a></b></span> <span class="reference-text"><cite class="citation web">Andrew Brown. <a rel="nofollow" class="external text" href="http://www.theguardian.com/commentisfree/2015/jun/25/wikipedia-editors-dying-breed-mobile-smartphone-technology-online-encyclopedia">"Wikipedia editors are a dying breed. The reason? Mobile"</a>. <i>the Guardian</i>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid% [...]
+<li id="cite_note-modelling-296"><span class="mw-cite-backlink"><b><a href="#cite_ref-modelling_296-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Wikipedia:Modelling_Wikipedia%27s_growth "Wikipedia:Modelling Wikipedia's growth"]<span class="reference-accessdate">. Retrieved <span class="nowrap">December 22,</span> 2007</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Wikipedia%3AModelling+Wik [...]
+<li id="cite_note-comscore-297"><span class="mw-cite-backlink"><b><a href="#cite_ref-comscore_297-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="https://web.archive.org/web/20080730011713/http://www.comscore.com/press/release.asp?press=849">"694 Million People Currently Use the Internet Worldwide According To comScore Networks"</a>. comScore. May 4, 2006. Archived from <a rel="nofollow" class="external text" href= [...]
+<li id="cite_note-hitwisegoogle-298"><span class="mw-cite-backlink"><b><a href="#cite_ref-hitwisegoogle_298-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://weblogs.hitwise.com/leeann-prescott/2007/02/wikipedia_traffic_sources.html">"Google Traffic To Wikipedia up 166% Year over Year"</a>. Hitwise. February 16, 2007<span class="reference-accessdate">. Retrieved <span class="nowrap">December 22,</span> 2007</s [...]
+<li id="cite_note-hitwiseAcademic-299"><span class="mw-cite-backlink"><b><a href="#cite_ref-hitwiseAcademic_299-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://weblogs.hitwise.com/leeann-prescott/2006/10/wikipedia_and_academic_researc.html">"Wikipedia and Academic Research"</a>. Hitwise. October 17, 2006<span class="reference-accessdate">. Retrieved <span class="nowrap">February 6,</span> 2008</span>.</cite> [...]
+<li id="cite_note-365M-300"><span class="mw-cite-backlink"><b><a href="#cite_ref-365M_300-0">^</a></b></span> <span class="reference-text"><cite class="citation web">West, Stuart. <a class="external text" href="http://upload.wikimedia.org/wikipedia/meta/3/3a/TED2010%2C_Stuart_West_full_presentation_updated_with_January_data.pdf">"Wikipedia's Evolving Impact: slideshow presentation at TED2010"</a> <span style="font-size:85%;">(PDF)</span><span class="reference-accessdate">. Retrieved <spa [...]
+<li id="cite_note-Wikipedia_users-301"><span class="mw-cite-backlink"><b><a href="#cite_ref-Wikipedia_users_301-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Rainie, Lee; Bill Tancer (December 15, 2007). <a rel="nofollow" class="external text" href="https://web.archive.org/web/20080306031354/http://www.pewinternet.org/pdfs/PIP_Wikipedia07.pdf">"Wikipedia users"</a> <span style="font-size:85%;">(PDF)</span>. <i>Pew Internet & American Life Project</i>. Pe [...]
+<li id="cite_note-302"><span class="mw-cite-backlink"><b><a href="#cite_ref-302">^</a></b></span> <span class="reference-text"><cite class="citation web">SAI (October 7, 2011). <a rel="nofollow" class="external text" href="http://www.businessinsider.com/2011-digital-100#7-wikimedia-foundation-wikipedia-7">"The World's Most Valuable Startups"</a>. Business Insider<span class="reference-accessdate">. Retrieved <span class="nowrap">June 14,</span> 2014</span>.</cite><span title="ctx_ver=Z39 [...]
+<li id="cite_note-303"><span class="mw-cite-backlink"><b><a href="#cite_ref-303">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/wiki/Research:Wikipedia_Readership_Survey_2011/Results">"Research:Wikipedia Readership Survey 2011/Results – Meta"</a>. Wikimedia. February 6, 2012<span class="reference-accessdate">. Retrieved <span class="nowrap">April 16,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-20 [...]
+<li id="cite_note-Wikipedia_in_media-304"><span class="mw-cite-backlink"><b><a href="#cite_ref-Wikipedia_in_media_304-0">^</a></b></span> <span class="reference-text"><cite class="citation web">[Wikipedia:Wikipedia_in_the_media "Wikipedia:Wikipedia in the media"]. <i>Wikipedia</i><span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Wik [...]
+<li id="cite_note-Bourgeois-305"><span class="mw-cite-backlink"><b><a href="#cite_ref-Bourgeois_305-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.ca11.uscourts.gov/opinions/ops/200216886.pdf">"Bourgeois <i>et al.</i> v. Peters <i>et al.</i>"</a> <span style="font-size:85%;">(PDF)</span><span class="reference-accessdate">. Retrieved <span class="nowrap">February 6,</span> 2007</span>.</cite><span title= [...]
+<li id="cite_note-ssrn.com_Wikipedian_Justice_1-306"><span class="mw-cite-backlink"><b><a href="#cite_ref-ssrn.com_Wikipedian_Justice_1_306-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://papers.ssrn.com/sol3/Delivery.cfm/SSRN_ID1346311_code835394.pdf?abstractid=1346311">"Wikipedian Justice"</a> <span style="font-size:85%;">(PDF)</span><span class="reference-accessdate">. Retrieved <span class="nowrap">June [...]
+<li id="cite_note-parl.gc.ca_same-sex_marriage-307"><span class="mw-cite-backlink"><b><a href="#cite_ref-parl.gc.ca_same-sex_marriage_307-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.parl.gc.ca/LegisInfo/BillDetails.aspx?billId=1585203&View=10">"LEGISinfo – House Government Bill C-38 (38–1)"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">September 9,</span> 2014</span>.</ci [...]
+<li id="cite_note-WP_court_source-308"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_court_source_308-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Arias, Martha L. (January 29, 2007). <a rel="nofollow" class="external text" href="http://www.ibls.com/internet_law_news_portal_view.aspx?s=latestnews&id=1668">"Wikipedia: The Free Online Encyclopedia and its Use as Court Source"</a>. <i>Internet Business Law Services</i><span class="reference-a [...]
+<li id="cite_note-Courts_turn_to_Wikipedia-309"><span class="mw-cite-backlink"><b><a href="#cite_ref-Courts_turn_to_Wikipedia_309-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Cohen, Noam (January 29, 2007). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2007/01/29/technology/29wikipedia.html">"Courts Turn to Wikipedia, but Selectively"</a>. <i>The New York Times</i><span class="reference-accessdate">. Retrieved <span class="nowrap">De [...]
+<li id="cite_note-US_Intelligence-310"><span class="mw-cite-backlink"><b><a href="#cite_ref-US_Intelligence_310-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Aftergood, Steven (March 21, 2007). <a rel="nofollow" class="external text" href="https://fas.org/blog/secrecy/2007/03/the_wikipedia_factor_in_us_int.html">"The Wikipedia Factor in US Intelligence"</a>. Federation of American Scientists Project on Government Secrecy<span class="reference-accessdate">. R [...]
+<li id="cite_note-Declan-311"><span class="mw-cite-backlink"><b><a href="#cite_ref-Declan_311-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Butler, Declan (December 16, 2008). "Publish in Wikipedia or perish". <i>Nature News</i>. <a href="/wiki/Digital_object_identifier" title="Digital object identifier">doi</a>:<a rel="nofollow" class="external text" href="//dx.doi.org/10.1038%2Fnews.2008.1312">10.1038/news.2008.1312</a>.</cite><span title="ctx_ver=Z39. [...]
+<li id="cite_note-ajr.org_WP_in_the_newsroom-312"><span class="mw-cite-backlink"><b><a href="#cite_ref-ajr.org_WP_in_the_newsroom_312-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Shaw, Donna (February–March 2008). <a rel="nofollow" class="external text" href="http://www.ajr.org/Article.asp?id=4461">"Wikipedia in the Newsroom"</a>. <i>American Journalism Review</i><span class="reference-accessdate">. Retrieved <span class="nowrap">February 11,</span> 2008</ [...]
+<li id="cite_note-twsY23-313"><span class="mw-cite-backlink"><b><a href="#cite_ref-twsY23_313-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Lexington (September 24, 2011). <a rel="nofollow" class="external text" href="http://www.economist.com/node/21530100">"Classlessness in America: The uses and abuses of an enduring myth"</a>. <i>The Economist</i><span class="reference-accessdate">. Retrieved <span class="nowrap">September 27,</span> 2011</span>. <q>Socia [...]
+<li id="cite_note-shizuoka_plagiarized_WP_1-314"><span class="mw-cite-backlink"><b><a href="#cite_ref-shizuoka_plagiarized_WP_1_314-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.japannewsreview.com/society/chubu/20070705page_id=364">"Shizuoka newspaper plagiarized Wikipedia article"</a>. <i>Japan News Review</i>. July 5, 2007.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia [...]
+<li id="cite_note-WA_Express-News_staffer_resigns-315"><span class="mw-cite-backlink"><b><a href="#cite_ref-WA_Express-News_staffer_resigns_315-0">^</a></b></span> <span class="reference-text"><a rel="nofollow" class="external text" href="https://web.archive.org/web/20071015045010/http://www.mysanantonio.com/news/metro/stories/MYSA010307.02A.richter.132c153.html">"Express-News staffer resigns after plagiarism in column is discovered"</a> at the <a href="/wiki/Wayback_Machine" title="Wayb [...]
+<li id="cite_note-starbulletin.com_Inquiry_prompts_dismissal-316"><span class="mw-cite-backlink"><b><a href="#cite_ref-starbulletin.com_Inquiry_prompts_dismissal_316-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Frank Bridgewater. <a rel="nofollow" class="external text" href="http://archives.starbulletin.com/2006/01/13/news/story03.html">"Inquiry prompts reporter's dismissal"</a>. <i>Honolulu Star-Bulletin</i><span class="reference-accessdate">. Retrieved <s [...]
+<li id="cite_note-Time2006-317"><span class="mw-cite-backlink"><b><a href="#cite_ref-Time2006_317-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Grossman, Lev (December 13, 2006). <a rel="nofollow" class="external text" href="http://content.time.com/time/magazine/article/0,9171,1570810,00.html">"Time's Person of the Year: You"</a>. <i>Time</i> (Time)<span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><sp [...]
+<li id="cite_note-318"><span class="mw-cite-backlink"><b><a href="#cite_ref-318">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.bbc.co.uk/programmes/b007tc6x">"Radio 4 documentary, BBC"</a>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.btitle=Radio+4+documentary%2C+BBC&rft.genre=book&rft_id=http%3A%2F%2Fwww.bbc.co.uk%2Fprogrammes%2Fb007tc6x& [...]
+<li id="cite_note-319"><span class="mw-cite-backlink"><b><a href="#cite_ref-319">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.grillini.it/show.php?4885">"Comunicato stampa. On. Franco Grillini. Wikipedia. Interrogazione a Rutelli. Con "diritto di panorama" promuovere arte e architettura contemporanea italiana. Rivedere con urgenza legge copyright" [Press release. Honorable Franco Grillini. Wikipedia. Int [...]
+<li id="cite_note-WP.com_WP_election_usage-320"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP.com_WP_election_usage_320-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Jose Antonio Vargas (September 17, 2007). <a rel="nofollow" class="external text" href="http://www.washingtonpost.com/wp-dyn/content/article/2007/09/16/AR2007091601699_pf.html">"On Wikipedia, Debating 2008 Hopefuls' Every Facet"</a>. <i>The Washington Post</i><span class="reference-ac [...]
+<li id="cite_note-321"><span class="mw-cite-backlink"><b><a href="#cite_ref-321">^</a></b></span> <span class="reference-text"><cite class="citation news">Jennifer Ablan (October 22, 2007). <a rel="nofollow" class="external text" href="http://www.reuters.com/article/domesticNews/idUSN2232893820071022?sp=true">"Wikipedia page the latest status symbol"</a>. Reuters<span class="reference-accessdate">. Retrieved <span class="nowrap">October 24,</span> 2007</span>.</cite><span title="ctx_ver= [...]
+<li id="cite_note-LER_students_write_for_WP_1-322"><span class="mw-cite-backlink"><b><a href="#cite_ref-LER_students_write_for_WP_1_322-0">^</a></b></span> <span class="reference-text"><cite class="citation journal">Witzleb, Normann (2009). "Engaging with the World: Students of Comparative Law Write for Wikipedia" <b>19</b> (1 and 2). Legal Education Review. pp. 83–98.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=Engaging [...]
+<li id="cite_note-WP_awards_for_WP_1-323"><span class="mw-cite-backlink"><b><a href="#cite_ref-WP_awards_for_WP_1_323-0">^</a></b></span> <span class="reference-text"><a href="//meta.wikimedia.org/wiki/Trophy_box" class="extiw" title="m:Trophy box">"Trophy box"</a>, <a href="/wiki/Wikipedia:Meta" title="Wikipedia:Meta">Meta-Wiki</a> (March 28, 2005).</span></li>
+<li id="cite_note-webbyawards_WP_awards_1-324"><span class="mw-cite-backlink"><b><a href="#cite_ref-webbyawards_WP_awards_1_324-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="https://web.archive.org/web/20110722174246/http://www.webbyawards.com/webbys/winners-2004.php">"Webby Awards 2004"</a>. The International Academy of Digital Arts and Sciences. 2004. Archived from <a rel="nofollow" class="external text" href=" [...]
+<li id="cite_note-brandchannel.com_awards_1-325"><span class="mw-cite-backlink"><b><a href="#cite_ref-brandchannel.com_awards_1_325-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Zumpano, Anthony (January 29, 2007). <a rel="nofollow" class="external text" href="http://www.brandchannel.com/features_effect.asp?pf_id=352">"Similar Search Results: Google Wins"</a>. Interbrand<span class="reference-accessdate">. Retrieved <span class="nowrap">January 28,</span> 2 [...]
+<li id="cite_note-loomarea.com_WP_award_1-326"><span class="mw-cite-backlink"><b><a href="#cite_ref-loomarea.com_WP_award_1_326-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://loomarea.com/die_quadriga/e/index.php?title=Award_2008">"Die Quadriga – Award 2008"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004& [...]
+<li id="cite_note-EP2015-327"><span class="mw-cite-backlink"><b><a href="#cite_ref-EP2015_327-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.erasmusprijs.org/?lang=en&page=Erasmusprijs">"Erasmus Prize - Praemium Erasmianum"</a>. Praemium Erasmianum Foundation<span class="reference-accessdate">. Retrieved <span class="nowrap">January 15,</span> 2015</span>.</cite><span title="ctx_ver=Z39.88-2004& [...]
+<li id="cite_note-328"><span class="mw-cite-backlink"><b><a href="#cite_ref-328">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.fpa.es/es/premios-princesa-de-asturias/premiados/2015-wikipedia.html?especifica=0&idCategoria=0&anio=2015&especifica=0">"Premio Princesa de Asturias de Cooperación Internacional 2015"</a>. Fundación Princesa de Asturias<span class="reference-accessdate">. Retrieved <sp [...]
+<li id="cite_note-329"><span class="mw-cite-backlink"><b><a href="#cite_ref-329">^</a></b></span> <span class="reference-text"><cite class="citation web">. La Nueva España <a rel="nofollow" class="external free" href="http://www.lne.es/sociedad-cultura/2015/10/22/fundadores-wikipedia-destacan-version-asturiano/1830529.html">http://www.lne.es/sociedad-cultura/2015/10/22/fundadores-wikipedia-destacan-version-asturiano/1830529.html</a><span class="reference-accessdate">. Retrieved <span cla [...]
+<li id="cite_note-onion_WP_750_years_1-330"><span class="mw-cite-backlink"><b><a href="#cite_ref-onion_WP_750_years_1_330-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.theonion.com/articles/wikipedia-celebrates-750-years-of-american-indepen,2007/">"Wikipedia Celebrates 750 Years Of American Independence"</a>. <i><a href="/wiki/The_Onion" title="The Onion">The Onion</a></i>. July 26, 2006<span class="re [...]
+<li id="cite_note-331"><span class="mw-cite-backlink"><b><a href="#cite_ref-331">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.theonion.com/articles/la-law-wikipedia-page-viewed-874-times-today,18521/">"<span style="padding-left:0.2em;">'</span>L.A. Law' Wikipedia Page Viewed 874 Times Today"</a>. <i><a href="/wiki/The_Onion" title="The Onion">The Onion</a></i>. November 24, 2010.</cite><span title="ctx_v [...]
+<li id="cite_note-332"><span class="mw-cite-backlink"><b><a href="#cite_ref-332">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.officetally.com/the-office-the-negotiation">"The Office: The Negotiation, 3.19"</a>. April 5, 2007<span class="reference-accessdate">. Retrieved <span class="nowrap">December 27,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3A [...]
+<li id="cite_note-333"><span class="mw-cite-backlink"><b><a href="#cite_ref-333">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://usatoday30.usatoday.com/tech/webguide/internetlife/2007-04-12-office-wikipedia_N.htm">"<span style="padding-left:0.2em;">'</span>Office' fans, inspired by Michael Scott, flock to edit Wikipedia"</a>. USA Today. April 12, 2007<span class="reference-accessdate">. Retrieved <span class=" [...]
+<li id="cite_note-Bakken_one_doctor_1-334"><span class="mw-cite-backlink"><b><a href="#cite_ref-Bakken_one_doctor_1_334-0">^</a></b></span> <span class="reference-text">Bakken, Janae. "<a href="/wiki/My_Number_One_Doctor" title="My Number One Doctor" class="mw-redirect">My Number One Doctor</a>"; <i><a href="/wiki/Scrubs_(TV_series)" title="Scrubs (TV series)">Scrubs</a></i>; <a href="/wiki/American_Broadcasting_Company" title="American Broadcasting Company">ABC</a>; December 6, 2007.</s [...]
+<li id="cite_note-collegehumor.com_WP_funny_1-335"><span class="mw-cite-backlink"><b><a href="#cite_ref-collegehumor.com_WP_funny_1_335-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.collegehumor.com/video/3581424/professor-wikipedia">"Professor Wikipedia – CollegeHumor Video"</a>. CollegeHumor. November 17, 2009<span class="reference-accessdate">. Retrieved <span class="nowrap">April 19,</span> 2011</s [...]
+<li id="cite_note-dilbert_WP_funny_1-336"><span class="mw-cite-backlink"><b><a href="#cite_ref-dilbert_WP_funny_1_336-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://dilbert.com/strips/comic/2009-05-08">"Dilbert comic strip for 05/08/2009 from the official Dilbert comic strips archive"</a>. Universal Uclick. May 8, 2009<span class="reference-accessdate">. Retrieved <span class="nowrap">March 10,</span> 2013< [...]
+<li id="cite_note-comedy.org.uk_WP_funny_1-337"><span class="mw-cite-backlink"><b><a href="#cite_ref-comedy.org.uk_WP_funny_1_337-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.comedy.org.uk/guide/radio/bigipedia/interview/">"Interview With Nick Doody and Matt Kirshen"</a>. <a href="/wiki/British_Comedy_Guide" title="British Comedy Guide">British Comedy Guide</a><span class="reference-accessdate">. Retr [...]
+<li id="cite_note-tosh_CC_WP_funny_1-338"><span class="mw-cite-backlink"><b><a href="#cite_ref-tosh_CC_WP_funny_1_338-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://tosh.comedycentral.com/blog/2010/02/03/your-wikipedia-entries">"<i>Your Wikipedia Entries</i>"</a>. <i>Tosh.0</i>. February 3, 2010<span class="reference-accessdate">. Retrieved <span class="nowrap">September 9,</span> 2014</span>.</cite><span t [...]
+<li id="cite_note-tosh_CC_WP_funny_2-339"><span class="mw-cite-backlink"><b><a href="#cite_ref-tosh_CC_WP_funny_2_339-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://tosh.comedycentral.com/video-clips/wikipedia-updates">"<i>Wikipedia Updates</i>"</a>. <i>Tosh.0</i>. February 3, 2010<span class="reference-accessdate">. Retrieved <span class="nowrap">September 9,</span> 2014</span>.</cite><span title="ctx_ver= [...]
+<li id="cite_note-340"><span class="mw-cite-backlink"><b><a href="#cite_ref-340">^</a></b></span> <span class="reference-text"><cite class="citation web"><a href="/wiki/Emily_Flake" title="Emily Flake">Emily Flake</a> (August 23, 2013). <a rel="nofollow" class="external text" href="http://www.condenaststore.com/-sp/Dammit-Manning-have-you-considered-the-pronoun-war-that-this-is-going-t-Cartoon-Prints_i9813981_.htm">"Manning/Wikipedia cartoon"</a><span class="reference-accessdate">. Retri [...]
+<li id="cite_note-WM_dictionary_1-341"><span class="mw-cite-backlink"><b><a href="#cite_ref-WM_dictionary_1_341-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a class="external text" href="http://meta.wikimedia.org/w/index.php?title=Wikimedia_News&diff=prev&oldid=4133">"Announcement of Wiktionary's creation"</a>. meta.wikimedia.org<span class="reference-accessdate">. Retrieved <span class="nowrap">July 14,</span> 2012</span>.</cite><span title="ctx_v [...]
+<li id="cite_note-OurProjects-342"><span class="mw-cite-backlink"><b><a href="#cite_ref-OurProjects_342-0">^</a></b></span> <span class="reference-text"><a href="//wikimediafoundation.org/wiki/Our_projects" class="extiw" title="foundation:Our projects">"Our projects"</a>, <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a>. Retrieved January 24, 2007.</span></li>
+<li id="cite_note-343"><span class="mw-cite-backlink"><b><a href="#cite_ref-343">^</a></b></span> <span class="reference-text"><cite class="citation web">Bosman, Julie. <a rel="nofollow" class="external text" href="http://mediadecoder.blogs.nytimes.com//2012/03/13/after-244-years-encyclopaedia-britannica-stops-the-presses/">"After 244 Years, Encyclopaedia Britannica Stops the Presses"</a>. <i>The New York Times</i><span class="reference-accessdate">. Retrieved <span class="nowrap">Januar [...]
+<li id="cite_note-344"><span class="mw-cite-backlink"><b><a href="#cite_ref-344">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.gizmocrazed.com/2012/03/encyclopedia-britannica-dies-at-the-hands-of-wikipedia-infographic/">"<i>Encyclopedia Britannica Dies At The Hands Of Wikipedia</i>, Gizmocrazed.com (with <i>statista</i> infographic from NYTimes.com)"</a>. Gizmocrazed.com. March 20, 2012<span class="refere [...]
+<li id="cite_note-FT_impact_on_traditional_media-345"><span class="mw-cite-backlink"><b><a href="#cite_ref-FT_impact_on_traditional_media_345-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a href="/wiki/Christopher_Caldwell" title="Christopher Caldwell">Christopher Caldwell</a> (June 14, 2013). <a rel="nofollow" class="external text" href="http://www.ft.com/cms/s/0/ae22314a-d383-11e2-b3ff-00144feab7de.html">"A chapter in the Enlightenment closes"</a>. <a hre [...]
+<li id="cite_note-RType_WP_traditional_media_impact_1-346"><span class="mw-cite-backlink"><b><a href="#cite_ref-RType_WP_traditional_media_impact_1_346-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.roughtype.com/archives/2005/10/the_amorality_o.php">"The amorality of Web 2.0"</a>. <i>Rough Type</i>. October 3, 2005<span class="reference-accessdate">. Retrieved <span class="nowrap">July 15,</span> 2006< [...]
+<li id="cite_note-nature.com_crowds_wisdom-347"><span class="mw-cite-backlink"><b><a href="#cite_ref-nature.com_crowds_wisdom_347-0">^</a></b></span> <span class="reference-text"><cite class="citation web"><a rel="nofollow" class="external text" href="http://www.nature.com/nature/peerreview/debate/nature04992.html">"Technical solutions: Wisdom of the crowds"</a>. <i>Nature</i><span class="reference-accessdate">. Retrieved <span class="nowrap">October 10,</span> 2006</span>.</cite><span t [...]
+<li id="cite_note-348"><span class="mw-cite-backlink"><b><a href="#cite_ref-348">^</a></b></span> <span class="reference-text"><cite class="citation web">Alison Flood. <a rel="nofollow" class="external text" href="http://www.guardian.co.uk/books/booksblog/2013/feb/07/traditional-biography-shakespeare-breakfast">"Alison Flood: <i>Should traditional biography be buried alongside Shakespeare's breakfast?</i>"</a>. The Guardian<span class="reference-accessdate">. Retrieved <span class="nowra [...]
+<li id="cite_note-wikify-349"><span class="mw-cite-backlink"><b><a href="#cite_ref-wikify_349-0">^</a></b></span> <span class="reference-text">Rada Mihalcea and Andras Csomai (2007). <a rel="nofollow" class="external text" href="http://www.cse.unt.edu/~tarau/teaching/NLP/papers/Mihalcea-2007-Wikify-Linking_Documents_to_Encyclopedic.pdf">Wikify! Linking Documents to Encyclopedic Knowledge</a>. Proc. CIKM.</span></li>
+<li id="cite_note-milne_witten_WP_usage_1-350"><span class="mw-cite-backlink"><b><a href="#cite_ref-milne_witten_WP_usage_1_350-0">^</a></b></span> <span class="reference-text">David Milne and Ian H. Witten (2008). Learning to link with Wikipedia. Proc. CIKM.</span></li>
+<li id="cite_note-discovering_missing_WP_links_1-351"><span class="mw-cite-backlink"><b><a href="#cite_ref-discovering_missing_WP_links_1_351-0">^</a></b></span> <span class="reference-text">Sisay Fissaha Adafre and [Maarten de Rijke] (2005). <a rel="nofollow" class="external text" href="http://staff.science.uva.nl/~mdr/Publications/Files/linkkdd2005.pdf">Discovering missing links in Wikipedia</a>. Proc. LinkKDD.</span></li>
+<li id="cite_note-Domesday_Project-352"><span class="mw-cite-backlink"><b><a href="#cite_ref-Domesday_Project_352-0">^</a></b></span> <span class="reference-text"><cite class="citation web">Heart Internet. <a rel="nofollow" class="external text" href="http://www.domesday1986.com/">"Website discussing the emulator of the Domesday Project User Interface"</a><span class="reference-accessdate">. Retrieved <span class="nowrap">September 9,</span> 2014</span>.</cite><span title="ctx_ver=Z39.88 [...]
+<li id="cite_note-Orlowski18-353"><span class="mw-cite-backlink"><b><a href="#cite_ref-Orlowski18_353-0">^</a></b></span> <span class="reference-text"><cite class="citation news"><a href="/wiki/Andrew_Orlowski" title="Andrew Orlowski">Orlowski, Andrew</a> (September 18, 2006). <a rel="nofollow" class="external text" href="http://www.theregister.co.uk/2006/09/18/sanger_forks_wikipedia">"Wikipedia founder forks Wikipedia, More experts, less fiddling?"</a>. <i>The Register</i><span class="r [...]
+<li id="cite_note-JayLyman-354"><span class="mw-cite-backlink"><b><a href="#cite_ref-JayLyman_354-0">^</a></b></span> <span class="reference-text"><cite class="citation news">Lyman, Jay (September 20, 2006). <a rel="nofollow" class="external text" href="http://www.crmbuyer.com/story/53137.html">"Wikipedia Co-Founder Planning New Expert-Authored Site"</a>. LinuxInsider<span class="reference-accessdate">. Retrieved <span class="nowrap">June 27,</span> 2007</span>.</cite><span title="ctx_ve [...]
+</ol>
+</div>
+<h3><span class="mw-headline" id="Notes">Notes</span></h3>
+<div class="reflist" style="list-style-type: decimal;">
+<ol class="references">
+<li id="cite_note-1"><span class="mw-cite-backlink"><b><a href="#cite_ref-1">^</a></b></span> <span class="reference-text">Registration is required for certain tasks such as editing <a href="/wiki/Protected_page" title="Protected page" class="mw-redirect">protected pages</a>, creating pages in the English Wikipedia, and uploading files.</span></li>
+<li id="cite_note-3"><span class="mw-cite-backlink"><b><a href="#cite_ref-3">^</a></b></span> <span class="reference-text">For an editor to be considered <a href="/wiki/Special:ActiveUsers" title="Special:ActiveUsers">active</a> in a given month, one or more edits have had to be made in said month.</span></li>
+<li id="cite_note-14"><span class="mw-cite-backlink"><b><a href="#cite_ref-14">^</a></b></span> <span class="reference-text">Wikis are a type of website. The word "wiki" itself is from the <a href="/wiki/Hawaiian_language" title="Hawaiian language">Hawaiian</a> <a href="//en.wiktionary.org/wiki/wiki#Hawaiian" class="extiw" title="wikt:wiki">word for "quick"</a>.<sup id="cite_ref-13" class="reference"><a href="#cite_note-13"><span>[</span>11<span>]</span></a></sup></span></li>
+<li id="cite_note-60"><span class="mw-cite-backlink"><b><a href="#cite_ref-60">^</a></b></span> <span class="reference-text">The procrastination principle consists in waiting for an issue to cause enough problems before taking measures to solve it.<sup class="noprint Inline-Template Template-Fact" style="white-space:nowrap;">[<i><a href="/wiki/Wikipedia:Citation_needed" title="Wikipedia:Citation needed"><span title="This claim needs references to reliable sources. (August 2015)">citation [...]
+<li id="cite_note-69"><span class="mw-cite-backlink"><b><a href="#cite_ref-69">^</a></b></span> <span class="reference-text">Revisions with libelous content, criminal threats, or copyright infringements <a href="/wiki/Wikipedia:Suppression" title="Wikipedia:Suppression" class="mw-redirect">may be removed completely</a>.</span></li>
+<li id="cite_note-211"><span class="mw-cite-backlink"><b><a href="#cite_ref-211">^</a></b></span> <span class="reference-text">See <a rel="nofollow" class="external text" href="http://texaspress.com/index.php/publications/law-media/731-law-a-the-media-in-texas--libel-cases">"Libel"</a><sup class="noprint Inline-Template"><span style="white-space: nowrap;">[<i><a href="/wiki/Wikipedia:Link_rot" title="Wikipedia:Link rot"><span title=" since October 2015">dead link</span></a></i>]</sp [...]
+</ol>
+</div>
+<h2><span class="mw-headline" id="Further_reading">Further reading</span></h2>
+<h3><span class="mw-headline" id="Academic_studies">Academic studies</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/Academic_studies_about_Wikipedia" title="Academic studies about Wikipedia">Academic studies about Wikipedia</a></div>
+<div class="refbegin columns references-column-width" style="-moz-column-width: 30em; -webkit-column-width: 30em; column-width: 30em;">
+<ul>
+<li>Leitch, Thomas. <i>Wikipedia U: Knowledge, authority, and a liberal education in the digital age</i> (2014)</li>
+<li>Jensen, Richard. "Military History on the Electronic Frontier: Wikipedia Fights the War of 1812", <i>The Journal of Military History</i> 76#4 (October 2012): 523–556; <a rel="nofollow" class="external text" href="http://www.americanhistoryprojects.com/downloads/JMH1812.PDF">online version</a>.</li>
+<li><cite class="citation journal">Yasseri, Taha; Robert Sumi; János Kertész (2012). Szolnoki, Attila, ed. <a rel="nofollow" class="external text" href="http://www.plosone.org/article/info%3Adoi%2F10.1371%2Fjournal.pone.0030091">"Circadian Patterns of Wikipedia Editorial Activity: A Demographic Analysis"</a>. <i>PLoS ONE</i> <b>7</b> (1): e30091. <a href="/wiki/ArXiv" title="ArXiv">arXiv</a>:<a rel="nofollow" class="external text" href="//arxiv.org/abs/1109.1746">1109.1746</a>. <a href=" [...]
+<li><cite class="citation journal">Goldman, Eric (2010). <a rel="nofollow" class="external text" href="http://papers.ssrn.com/sol3/papers.cfm?abstract_id=1458162##">"Wikipedia's Labor Squeeze and its Consequences"</a>. <i>Journal of Telecommunications and High Technology Law</i> <b>8</b>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=Wikipedia%27s+Labor+Squeeze+and+its+Consequences&rft.aufirst=Eric&rft.aulast=Goldman&am [...]
+<li><cite class="citation journal">Nielsen, Finn (August 2007). <a rel="nofollow" class="external text" href="http://www.firstmonday.org/issues/issue12_8/nielsen/index.html">"Scientific Citations in Wikipedia"</a>. <i><a href="/wiki/First_Monday_(journal)" title="First Monday (journal)">First Monday</a></i> <b>12</b> (8). <a href="/wiki/Digital_object_identifier" title="Digital object identifier">doi</a>:<a rel="nofollow" class="external text" href="//dx.doi.org/10.5210%2Ffm.v12i8.1997"> [...]
+<li><cite class="citation journal">Pfeil, Ulrike; Panayiotis Zaphiris; Chee Siang Ang (2006). <a rel="nofollow" class="external text" href="http://jcmc.indiana.edu./vol12/issue1/pfeil.html">"Cultural Differences in Collaborative Authoring of Wikipedia"</a>. <i>Journal of Computer-Mediated Communication</i> <b>12</b> (1): 88. <a href="/wiki/Digital_object_identifier" title="Digital object identifier">doi</a>:<a rel="nofollow" class="external text" href="//dx.doi.org/10.1111%2Fj.1083-6101. [...]
+<li>Priedhorsky, Reid, Jilin Chen, Shyong (Tony) K. Lam, Katherine Panciera, <a href="/wiki/Loren_Terveen" title="Loren Terveen">Loren Terveen</a>, and <a href="/wiki/John_Riedl" title="John Riedl" class="mw-redirect">John Riedl</a>. <a rel="nofollow" class="external text" href="http://portal.acm.org/citation.cfm?doid=1316624.1316663">"Creating, Destroying, and Restoring Value in Wikipedia"</a>. Proc. GROUP 2007; <a href="/wiki/Digital_object_identifier" title="Digital object identifier" [...]
+<li><cite class="citation conference">Reagle, Joseph (2007). <a rel="nofollow" class="external text" href="http://reagle.org/joseph/2007/10/Wikipedia-Authorial-Leadership.pdf"><i>Do as I Do: Authorial Leadership in Wikipedia</i></a> <span style="font-size:85%;">(PDF)</span>. <i>WikiSym '07: Proceedings of the 2007 International Symposium on Wikis</i> (Montreal, Canada: ACM)<span class="reference-accessdate">. Retrieved <span class="nowrap">December 26,</span> 2008</span>.</cite><span tit [...]
+<li><a href="/wiki/Roy_Rosenzweig" title="Roy Rosenzweig">Rosenzweig, Roy</a>. <a rel="nofollow" class="external text" href="http://chnm.gmu.edu/resources/essays/d/42">Can History be Open Source? Wikipedia and the Future of the Past</a><sup class="noprint Inline-Template"><span style="white-space: nowrap;">[<i><a href="/wiki/Wikipedia:Link_rot" title="Wikipedia:Link rot"><span title=" since October 2015">dead link</span></a></i>]</span></sup>. (Originally published in <i><a href="/w [...]
+<li><cite class="citation journal">Wilkinson, Dennis M.; Bernardo A. Huberman (April 2007). <a rel="nofollow" class="external text" href="http://www.firstmonday.org/issues/issue12_4/wilkinson/index.html">"Assessing the Value of Cooperation in Wikipedia"</a>. <i>First Monday</i> <b>12</b> (4). <a href="/wiki/Digital_object_identifier" title="Digital object identifier">doi</a>:<a rel="nofollow" class="external text" href="//dx.doi.org/10.5210%2Ffm.v12i4.1763">10.5210/fm.v12i4.1763</a><span [...]
+<li><cite class="citation journal">Aaron Halfaker, R. Stuart Geiger, Jonathan T. Morgan, John Riedl (2012). <a rel="nofollow" class="external text" href="http://abs.sagepub.com/content/57/5/664">"The Rise and Decline of an Open Collaboration Community"</a>. <i>American Behavioral Scientist</i> <b>57</b> (5): 664. <a href="/wiki/Digital_object_identifier" title="Digital object identifier">doi</a>:<a rel="nofollow" class="external text" href="//dx.doi.org/10.1177%2F0002764212469365">10.117 [...]
+</ul>
+</div>
+<h3><span class="mw-headline" id="Books">Books</span></h3>
+<div class="hatnote relarticle mainarticle">Main article: <a href="/wiki/List_of_books_about_Wikipedia" title="List of books about Wikipedia" class="mw-redirect">List of books about Wikipedia</a></div>
+<div class="refbegin columns references-column-width" style="-moz-column-width: 30em; -webkit-column-width: 30em; column-width: 30em;">
+<ul>
+<li><cite class="citation book">Ayers, Phoebe; Matthews, Charles; Yates, Ben (September 2008). <i><a href="/wiki/How_Wikipedia_Works" title="How Wikipedia Works">How Wikipedia Works</a>: And How You Can Be a Part of It</i>. San Francisco: No Starch Press. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/978-1-59327-176-3" title="Special:BookSources/978-1-59327-176-3">978-1-59327-176-3</a>.</cite [...]
+<li><cite class="citation book">Broughton, John (2008). <i><a href="/wiki/Wikipedia_%E2%80%93_The_Missing_Manual" title="Wikipedia – The Missing Manual">Wikipedia – The Missing Manual</a></i>. O'Reilly Media. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/0-596-51516-2" title="Special:BookSources/0-596-51516-2">0-596-51516-2</a>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fe [...]
+<li><cite class="citation book">Broughton, John (2008). <i>Wikipedia Reader's Guide</i>. Sebastopol: Pogue Press. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/0-596-52174-X" title="Special:BookSources/0-596-52174-X">0-596-52174-X</a>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.aufirst=John&rft.aulast=Broughton&rft.btitle=Wikipe [...]
+<li><cite class="citation book"><a href="/wiki/Andrew_Dalby" title="Andrew Dalby">Dalby, Andrew</a> (2009). <i><a href="/wiki/The_World_and_Wikipedia" title="The World and Wikipedia">The World and Wikipedia</a>: How We are Editing Reality</i>. Siduri. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/978-0-9562052-0-9" title="Special:BookSources/978-0-9562052-0-9">978-0-9562052-0-9</a>.</cite><sp [...]
+<li><cite class="citation book"><a href="/wiki/Andrew_Keen" title="Andrew Keen">Keen, Andrew</a> (2007). <i><a href="/wiki/The_Cult_of_the_Amateur" title="The Cult of the Amateur">The Cult of the Amateur</a></i>. Doubleday/Currency. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/978-0-385-52080-5" title="Special:BookSources/978-0-385-52080-5">978-0-385-52080-5</a>.</cite><span title="ctx_ver=Z [...]
+<ul>
+<li>Listen to:
+<ul>
+<li><cite class="citation web">Keen, Andrew (June 16, 2007). <a rel="nofollow" class="external text" href="http://www.npr.org/templates/story/story.php?storyId=11131872">"Does the Internet Undermine Culture?"</a>. National Public Radio, USA.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.aufirst=Andrew&rft.aulast=Keen&rft.btitle=Does+the+Internet+Undermine+Culture%3F&rft.date=June+16%2C+2007&rft.genre=book&rft_id=ht [...]
+</ul>
+</li>
+</ul>
+</li>
+<li><cite class="citation book"><a href="/wiki/Andrew_Lih" title="Andrew Lih">Lih, Andrew</a> (2009). <i><a href="/wiki/The_Wikipedia_Revolution:_How_a_Bunch_of_Nobodies_Created_the_World%27s_Greatest_Encyclopedia" title="The Wikipedia Revolution: How a Bunch of Nobodies Created the World's Greatest Encyclopedia" class="mw-redirect">The Wikipedia Revolution: How a Bunch of Nobodies Created the World's Greatest Encyclopedia</a></i>. New York: Hyperion. <a href="/wiki/International_Standar [...]
+<li><cite class="citation book">O'Sullivan, Dan (September 24, 2009). <a rel="nofollow" class="external text" href="https://books.google.com/books?id=htu8A-m_Y4EC"><i>Wikipedia: a new community of practice?</i></a>. Ashgate Publishing. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/978-0-7546-7433-7" title="Special:BookSources/978-0-7546-7433-7">978-0-7546-7433-7</a>.</cite><span title="ctx_ve [...]
+<li><a href="/wiki/Sheizaf_Rafaeli" title="Sheizaf Rafaeli">Sheizaf Rafaeli</a> & Yaron Ariel (2008). "Online motivational factors: Incentives for participation and contribution in Wikipedia." In <cite class="citation book">Barak, A. <i>Psychological aspects of cyberspace: Theory, research, applications</i>. Cambridge, UK: <a href="/wiki/Cambridge_University_Press" title="Cambridge University Press">Cambridge University Press</a>. pp. 243–267.</cite><span title="ctx_ver=Z39.88-2 [...]
+<li><cite class="citation book">Reagle, Joseph Michael Jr. (2010). <a rel="nofollow" class="external text" href="http://reagle.org/joseph/2010/gfc"><i>Good Faith Collaboration: The Culture of Wikipedia</i></a>. Cambridge, Massachusetts, USA: the MIT Press. <a href="/wiki/International_Standard_Book_Number" title="International Standard Book Number">ISBN</a> <a href="/wiki/Special:BookSources/978-0-262-01447-2" title="Special:BookSources/978-0-262-01447-2">978-0-262-01447-2</a><span [...]
+</ul>
+</div>
+<h3><span class="mw-headline" id="Book_reviews_and_other_articles">Book reviews and other articles</span></h3>
+<div class="refbegin" style="">
+<ul>
+<li><a href="/wiki/Nicholson_Baker" title="Nicholson Baker">Baker, Nicholson</a>. <a rel="nofollow" class="external text" href="http://www.nybooks.com/articles/21131">"The Charms of Wikipedia"</a>. <i><a href="/wiki/The_New_York_Review_of_Books" title="The New York Review of Books">The New York Review of Books</a></i>, March 20, 2008. Retrieved December 17, 2008. (Book rev. of <i>The Missing Manual</i>, by John Broughton, as listed previously.)</li>
+<li><a href="/wiki/L._Gordon_Crovitz" title="L. Gordon Crovitz">Crovitz, L. Gordon</a>. <a rel="nofollow" class="external text" href="http://online.wsj.com/article/SB123897399273491031.html">"Wikipedia's Old-Fashioned Revolution: The online encyclopedia is fast becoming the best."</a> (Originally published in <a href="/wiki/The_Wall_Street_Journal" title="The Wall Street Journal"><i>Wall Street Journal</i></a> online – April 6, 2009.)</li>
+<li><a href="/wiki/Virginia_Postrel" title="Virginia Postrel">Postrel, Virginia</a>, <a rel="nofollow" class="external text" href="http://www.psmag.com/books-and-culture/killed-wikipedia-93777">"Who Killed Wikipedia? : A hardened corps of volunteer editors is the only force protecting Wikipedia. They might also be killing it"</a>, <i><a href="/wiki/Pacific_Standard" title="Pacific Standard">Pacific Standard</a></i> magazine, November/December 2014 issue.</li>
+</ul>
+</div>
+<h4><span class="mw-headline" id="Learning_resources">Learning resources</span></h4>
+<div class="refbegin" style="">
+<ul>
+<li><a href="//en.wikiversity.org/wiki/wikipedia#Learning_resources" class="extiw" title="v:wikipedia">Wikiversity list of learning resources</a>. (Includes related courses, <a href="/wiki/Web_conferencing" title="Web conferencing">Web-based seminars</a>, slides, lecture notes, text books, quizzes, glossaries, etc.)</li>
+<li><a rel="nofollow" class="external text" href="http://www.cbc.ca/ideas/episodes/2014/01/15/the-great-book-of-knowledge-part-1/">The Great Book of Knowledge, Part 1: A Wiki is a Kind of Bus</a>, <a href="/wiki/Ideas_(radio_show)" title="Ideas (radio show)">Ideas, with Paul Kennedy</a>, <a href="/wiki/CBC_Radio_One" title="CBC Radio One">CBC Radio One</a>, originally broadcast January 15, 2014. Webpage includes a link to the archived audio program (also <a rel="nofollow" class="external [...]
+</ul>
+</div>
+<h4><span class="mw-headline" id="Other_media_coverage">Other media coverage</span></h4>
+<div class="hatnote">See also: <a href="/wiki/List_of_films_about_Wikipedia" title="List of films about Wikipedia">List of films about Wikipedia</a></div>
+<div class="refbegin" style="">
+<ul>
+<li><a rel="nofollow" class="external text" href="http://www.wired.com/politics/onlinerights/news/2007/08/wiki_tracker?currentPage=all">"See Who's Editing Wikipedia – Diebold, the CIA, a Campaign", <i>WIRED</i>, August 14, 2007.</a><sup class="noprint Inline-Template"><span style="white-space: nowrap;">[<i><a href="/wiki/Wikipedia:Link_rot" title="Wikipedia:Link rot"><span title=" since October 2015">dead link</span></a></i>]</span></sup></li>
+<li><cite class="citation news">Balke, Jeff (March 2008). <a rel="nofollow" class="external text" href="http://blogs.chron.com/brokenrecord/2008/03/for_music_fans_wikipedia_myspa.html">"For Music Fans: Wikipedia; MySpace"</a>. <i><a href="/wiki/Houston_Chronicle" title="Houston Chronicle">Houston Chronicle</a></i>. Broken Record (blog)<span class="reference-accessdate">. Retrieved <span class="nowrap">December 17,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info [...]
+<li><cite class="citation news">Dee, Jonathan (July 1, 2007). <a rel="nofollow" class="external text" href="http://www.nytimes.com/2007/07/01/magazine/01WIKIPEDIA-t.html">"All the News That's Fit to Print Out"</a>. <i>The New York Times Magazine</i><span class="reference-accessdate">. Retrieved <span class="nowrap">February 22,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=All+the+News+That%27s+Fit+to+Print+ [...]
+<li><cite class="citation news">Giles, Jim (September 20, 2007). <a rel="nofollow" class="external text" href="http://www.newscientist.com/article/mg19526226.200">"Wikipedia 2.0 – Now with Added Trust"</a>. <i><a href="/wiki/New_Scientist" title="New Scientist">New Scientist</a></i><span class="reference-accessdate">. Retrieved <span class="nowrap">January 14,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atit [...]
+<li><cite class="citation news">Miliard, Mike (December 2, 2007). <a rel="nofollow" class="external text" href="http://thephoenix.com/Boston/Life/52864-Wikipedia-rules">"Wikipedia Rules"</a>. <i><a href="/wiki/The_Phoenix_(newspaper)" title="The Phoenix (newspaper)">The Phoenix</a></i><span class="reference-accessdate">. Retrieved <span class="nowrap">February 22,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitl [...]
+<li><cite class="citation news"><a href="/wiki/Marshall_Poe" title="Marshall Poe">Poe, Marshall</a> (September 1, 2006). <a rel="nofollow" class="external text" href="http://www.theatlantic.com/doc/200609/wikipedia">"The Hive"</a>. <i><a href="/wiki/The_Atlantic" title="The Atlantic">The Atlantic Monthly</a></i><span class="reference-accessdate">. Retrieved <span class="nowrap">March 22,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3 [...]
+<li><cite class="citation news">Rosenwald, Michael S. (October 23, 2009). <a rel="nofollow" class="external text" href="http://www.washingtonpost.com/wp-dyn/content/article/2009/10/22/AR2009102204715.html?hpid=topnews">"Gatekeeper of D.C.'s entry: Road to city's Wikipedia page goes through a DuPont Circle bedroom"</a>. <i>The Washington Post</i><span class="reference-accessdate">. Retrieved <span class="nowrap">October 22,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rf [...]
+<li><cite class="citation news">Runciman, David (May 28, 2009). <a rel="nofollow" class="external text" href="http://www.lrb.co.uk/v31/n10/runc01_.html">"Like Boiling a Frog"</a>. <i>London Review of Books</i><span class="reference-accessdate">. Retrieved <span class="nowrap">June 3,</span> 2009</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=Like+Boiling+a+Frog&rft.aufirst=David&rft.aulast=Runciman&rft.date=M [...]
+<li><cite class="citation news">Taylor, Chris (May 29, 2005). <a rel="nofollow" class="external text" href="http://www.time.com/time/magazine/article/0,9171,1066904-1,00.html">"It's a Wiki, Wiki World"</a>. <i><a href="/wiki/Time_(magazine)" title="Time (magazine)">Time</a></i><span class="reference-accessdate">. Retrieved <span class="nowrap">February 22,</span> 2008</span>.</cite><span title="ctx_ver=Z39.88-2004&rfr_id=info%3Asid%2Fen.wikipedia.org%3AWikipedia&rft.atitle=It%27s [...]
+<li><cite class="citation news"><a rel="nofollow" class="external text" href="http://www.economist.com/science/tq/displaystory.cfm?story_id=11484062">"Technological Quarterly: Brain Scan: The Free-knowledge Fundamentalist"</a>. <i><a href="/wiki/The_Economist" title="The Economist">The Economist</a> Web and <a href="/wiki/Magazine" title="Magazine">Print</a></i>. June 5, 2008<span class="reference-accessdate">. Retrieved <span class="nowrap">June 5,</span> 2008</span>. <q>Jimmy Wales cha [...]
+<li><a rel="nofollow" class="external text" href="http://www.independent.co.uk/life-style/gadgets-and-tech/features/is-wikipedia-cracking-up-1543527.html">"Is Wikipedia Cracking Up?" <i>The Independent</i>, February 3, 2009.</a></li>
+<li><a rel="nofollow" class="external text" href="http://www.bbc.co.uk/news/technology-24613608">"Wikipedia probe into paid-for 'sockpuppet' entries", BBC News', October 21, 2013.</a></li>
+<li><a rel="nofollow" class="external text" href="http://www.technologyreview.com/featuredstory/520446/the-decline-of-wikipedia/">"The Decline of Wikipedia"</a>, <i>MIT Technology Review</i>, October 22, 2013</li>
+<li><a rel="nofollow" class="external text" href="http://www.capitalnewyork.com/article/city-hall/2015/03/8563947/edits-wikipedia-pages-bell-garner-diallo-traced-1-police-plaza">Edits to Wikipedia pages on Bell, Garner, Diallo traced to 1 Police Plaza</a> (March 2015), <i><a href="/wiki/Media_in_New_York%27s_Capital_District" title="Media in New York's Capital District">Capital</a></i></li>
+</ul>
+</div>
+<h2><span class="mw-headline" id="External_links">External links</span></h2>
+<table class="metadata plainlinks mbox-small" style="padding:0.25em 0.5em 0.5em 0.75em;border:1px solid #aaa;background:#f9f9f9;">
+<tr>
+<td colspan="2" style="padding-bottom:0.75em;border-bottom:1px solid #aaa;text-align:center;">
+<div style="clear:both;">Find more about<br />
+<b>Wikipedia</b><br />
+at Wikipedia's <a href="/wiki/Wikipedia:Wikimedia_sister_projects" title="Wikipedia:Wikimedia sister projects">sister projects</a></div>
+</td>
+</tr>
+<tr style="height:25px;">
+<td style="padding-top:0.75em;"><a href="//en.wiktionary.org/wiki/Special:Search/Wikipedia" title="Search Wiktionary"><img alt="Search Wiktionary" src="//upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Wiktionary-logo-en.svg/23px-Wiktionary-logo-en.svg.png" width="23" height="25" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Wiktionary-logo-en.svg/35px-Wiktionary-logo-en.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Wiktionary-logo-en.svg/46px-Wiktiona [...]
+<td style="padding-top:0.75em;"><a href="//en.wiktionary.org/wiki/Special:Search/Wikipedia" class="extiw" title="wikt:Special:Search/Wikipedia">Definitions</a> from Wiktionary</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//commons.wikimedia.org/wiki/Special:Search/Wikipedia" title="Search Commons"><img alt="Search Commons" src="//upload.wikimedia.org/wikipedia/en/thumb/4/4a/Commons-logo.svg/18px-Commons-logo.svg.png" width="18" height="25" srcset="//upload.wikimedia.org/wikipedia/en/thumb/4/4a/Commons-logo.svg/28px-Commons-logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/4/4a/Commons-logo.svg/37px-Commons-logo.svg.png 2x" data-file-width="1024" data-file-height="1376" /></a></td>
+<td><a href="//commons.wikimedia.org/wiki/Special:Search/Wikipedia" class="extiw" title="c:Special:Search/Wikipedia">Media</a> from Commons</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//en.wikinews.org/wiki/Special:Search/Wikipedia" title="Search Wikinews"><img alt="Search Wikinews" src="//upload.wikimedia.org/wikipedia/commons/thumb/2/24/Wikinews-logo.svg/25px-Wikinews-logo.svg.png" width="25" height="14" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/2/24/Wikinews-logo.svg/38px-Wikinews-logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/2/24/Wikinews-logo.svg/50px-Wikinews-logo.svg.png 2x" data-file-width="759" data-file-height="4 [...]
+<td><a href="//en.wikinews.org/wiki/Special:Search/Wikipedia" class="extiw" title="n:Special:Search/Wikipedia">News stories</a> from Wikinews</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//en.wikiquote.org/wiki/Special:Search/Wikipedia" title="Search Wikiquote"><img alt="Search Wikiquote" src="//upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Wikiquote-logo.svg/21px-Wikiquote-logo.svg.png" width="21" height="25" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Wikiquote-logo.svg/32px-Wikiquote-logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Wikiquote-logo.svg/42px-Wikiquote-logo.svg.png 2x" data-file-width="300" data-file- [...]
+<td><a href="//en.wikiquote.org/wiki/Special:Search/Wikipedia" class="extiw" title="q:Special:Search/Wikipedia">Quotations</a> from Wikiquote</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//en.wikisource.org/wiki/Category:Wikipedia" title="Search Wikisource"><img alt="Search Wikisource" src="//upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Wikisource-logo.svg/24px-Wikisource-logo.svg.png" width="24" height="25" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Wikisource-logo.svg/36px-Wikisource-logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Wikisource-logo.svg/48px-Wikisource-logo.svg.png 2x" data-file-width="410" data-fi [...]
+<td><a href="//en.wikisource.org/wiki/Category:Wikipedia" class="extiw" title="s:Category:Wikipedia">Source texts</a> from Wikisource</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//en.wikibooks.org/wiki/Special:Search/Wikipedia" title="Search Wikibooks"><img alt="Search Wikibooks" src="//upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Wikibooks-logo.svg/25px-Wikibooks-logo.svg.png" width="25" height="25" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Wikibooks-logo.svg/38px-Wikibooks-logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Wikibooks-logo.svg/50px-Wikibooks-logo.svg.png 2x" data-file-width="300" data-file- [...]
+<td><a href="//en.wikibooks.org/wiki/Special:Search/Wikipedia" class="extiw" title="b:Special:Search/Wikipedia">Textbooks</a> from Wikibooks</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//en.wikivoyage.org/wiki/Wikivoyage:Cooperating_with_Wikipedia" title="Search Wikivoyage"><img alt="Search Wikivoyage" src="//upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Wikivoyage-Logo-v3-icon.svg/25px-Wikivoyage-Logo-v3-icon.svg.png" width="25" height="25" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Wikivoyage-Logo-v3-icon.svg/38px-Wikivoyage-Logo-v3-icon.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Wikivoyage-Logo-v3-icon.svg/50p [...]
+<td><a href="//en.wikivoyage.org/wiki/Wikivoyage:Cooperating_with_Wikipedia" class="extiw" title="voy:Wikivoyage:Cooperating with Wikipedia">Travel guide</a> from Wikivoyage</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//en.wikiversity.org/wiki/Special:Search/Wikipedia" title="Search Wikiversity"><img alt="Search Wikiversity" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Wikiversity-logo-en.svg/25px-Wikiversity-logo-en.svg.png" width="25" height="23" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Wikiversity-logo-en.svg/38px-Wikiversity-logo-en.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Wikiversity-logo-en.svg/50px-Wikiversity-logo-en.svg.png [...]
+<td><a href="//en.wikiversity.org/wiki/Special:Search/Wikipedia" class="extiw" title="v:Special:Search/Wikipedia">Learning resources</a> from Wikiversity</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//www.wikidata.org/wiki/Q52" title="Search Wikidata"><img alt="Search Wikidata" src="//upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Wikidata-logo.svg/25px-Wikidata-logo.svg.png" width="25" height="14" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Wikidata-logo.svg/38px-Wikidata-logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Wikidata-logo.svg/50px-Wikidata-logo.svg.png 2x" data-file-width="1050" data-file-height="590" /></a></td>
+<td><a href="//www.wikidata.org/wiki/Q52" class="extiw" title="d:Q52">Data</a> from Wikidata</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//meta.wikimedia.org/wiki/Wikipedia" title="Search Meta-Wiki"><img alt="Search Meta-Wiki" src="//upload.wikimedia.org/wikipedia/commons/thumb/7/75/Wikimedia_Community_Logo.svg/25px-Wikimedia_Community_Logo.svg.png" width="25" height="25" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/7/75/Wikimedia_Community_Logo.svg/38px-Wikimedia_Community_Logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/7/75/Wikimedia_Community_Logo.svg/50px-Wikimedia_Community_Lo [...]
+<td><a href="//meta.wikimedia.org/wiki/Wikipedia" class="extiw" title="m:Wikipedia">Discussion</a> from Meta-Wiki</td>
+</tr>
+<tr style="height:25px;">
+<td><a href="//www.mediawiki.org/wiki/Wikipedia" title="Search MediaWiki"><img alt="Search MediaWiki" src="//upload.wikimedia.org/wikipedia/commons/thumb/b/bb/MediaWiki-notext.svg/25px-MediaWiki-notext.svg.png" width="25" height="19" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/b/bb/MediaWiki-notext.svg/38px-MediaWiki-notext.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/b/bb/MediaWiki-notext.svg/50px-MediaWiki-notext.svg.png 2x" data-file-width="685" data-file-he [...]
+<td><a href="//www.mediawiki.org/wiki/Wikipedia" class="extiw" title="mw:Wikipedia">Documentation on Wikipedia</a> from MediaWiki</td>
+</tr>
+</table>
+<ul>
+<li><span class="official website"><span class="url"><a class="external text" href="https://www.wikipedia.org">Official website</a></span></span> (<span class="url"><a class="external text" href="https://en.m.wikipedia.org">Mobile</a></span>) – multilingual portal (contains links to all language editions)
+<ul>
+<li><a rel="nofollow" class="external text" href="https://twitter.com/Wikipedia">Wikipedia</a> on <a href="/wiki/Twitter" title="Twitter">Twitter</a></li>
+<li><a rel="nofollow" class="external text" href="https://www.facebook.com/Wikipedia">Wikipedia</a> on <a href="/wiki/Facebook" title="Facebook">Facebook</a></li>
+</ul>
+</li>
+<li><a rel="nofollow" class="external text" href="https://www.dmoz.org/Computers/Open_Source/Open_Content/Encyclopedias/Wikipedia">Wikipedia</a> at <a href="/wiki/DMOZ" title="DMOZ">DMOZ</a></li>
+<li><a href="//toolserver.org/~johang/wikitrends/english-most-visited-today.html" class="extiw" title="tools:~johang/wikitrends/english-most-visited-today.html">Wikitrends: Wikipedia articles most visited today</a></li>
+<li><a rel="nofollow" class="external text" href="http://www.theguardian.com/technology/wikipedia">Wikipedia</a> collected news and commentary at <i><a href="/wiki/The_Guardian" title="The Guardian">The Guardian</a></i></li>
+<li><a rel="nofollow" class="external text" href="http://topics.nytimes.com/top/news/business/companies/wikipedia/index.html">Wikipedia</a> topic page at <i><a href="/wiki/The_New_York_Times" title="The New York Times">The New York Times</a></i></li>
+<li><a rel="nofollow" class="external text" href="http://www.ted.com/index.php/talks/jimmy_wales_on_the_birth_of_wikipedia.html">Video of TED talk by Jimmy Wales on the birth of Wikipedia</a></li>
+<li><a rel="nofollow" class="external text" href="http://www.econtalk.org/archives/2009/03/wales_on_wikipe.html">Audio of interview with Jimmy Wales about Wikipedia in general</a> on the <a href="/wiki/EconTalk" title="EconTalk">EconTalk</a> podcast</li>
+<li><a rel="nofollow" class="external text" href="http://www.stanford.edu/class/ee380/Abstracts/020116.html">Wikipedia and why it matters</a> – Larry Sanger's 2002 talk at <a href="/wiki/Stanford_University" title="Stanford University">Stanford University</a>; <a rel="nofollow" class="external text" href="http://stanford-online.stanford.edu/courses/ee380/020116-ee380-100.asx">video archive</a> and <a href="//meta.wikimedia.org/wiki/Wikipedia_and_why_it_matters" class="extiw" title=" [...]
+<li><a rel="nofollow" class="external text" href="https://www.youtube.com/watch?v=cqOHbihYbhE"><span class="plainlinks">"Intelligence in Wikipedia" Google TechTalk</span></a> on <a href="/wiki/YouTube" title="YouTube">YouTube</a>, describing an intelligence project utilizing Wikipedia, and how Wikipedia articles could be auto-generated from web content</li>
+<li><a rel="nofollow" class="external text" href="http://www.infoq.com/presentations/vibber-community-perf-opt"><i>Community Performance Optimization: Making Your People Run as Smoothly as Your Site</i></a>, talk presented at <a href="/wiki/OOPSLA" title="OOPSLA">OOPSLA</a> 2009 by the <a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a>'s senior <a href="/wiki/Software" title="Software">software</a> architect and former <a href="/wiki/Chief_technolo [...]
+<li><a rel="nofollow" class="external text" href="http://wikipapers.referata.com/">WikiPapers</a> – compilation of conference papers, journal articles, theses, books, datasets and tools about Wikipedia and wikis</li>
+<li><a rel="nofollow" class="external text" href="http://wikirank.di.unimi.it/">Open Wikipedia Ranking</a></li>
+</ul>
+<table class="navbox" style="border-spacing:0">
+<tr>
+<td style="padding:2px">
+<table class="nowraplinks collapsible autocollapse navbox-inner" style="border-spacing:0;background:transparent;color:inherit">
+<tr>
+<th scope="col" class="navbox-title" colspan="2">
+<div class="plainlinks hlist navbar mini">
+<ul>
+<li class="nv-view"><a href="/wiki/Template:Wikipedia" title="Template:Wikipedia"><abbr title="View this template" style=";;background:none transparent;border:none;">v</abbr></a></li>
+<li class="nv-talk"><a href="/wiki/Template_talk:Wikipedia" title="Template talk:Wikipedia"><abbr title="Discuss this template" style=";;background:none transparent;border:none;">t</abbr></a></li>
+<li class="nv-edit"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Template:Wikipedia&action=edit"><abbr title="Edit this template" style=";;background:none transparent;border:none;">e</abbr></a></li>
+</ul>
+</div>
+<div style="font-size:114%"><strong class="selflink">Wikipedia</strong></div>
+</th>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group"><a href="/wiki/Outline_of_Wikipedia" title="Outline of Wikipedia">Overview</a></th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Censorship_of_Wikipedia" title="Censorship of Wikipedia">Censorship of</a></li>
+<li><a href="/wiki/Conflict-of-interest_editing_on_Wikipedia" title="Conflict-of-interest editing on Wikipedia">Conflict-of-interest editing</a></li>
+<li><a href="/wiki/Criticism_of_Wikipedia" title="Criticism of Wikipedia">Criticism</a></li>
+<li><a href="/wiki/Deletionism_and_inclusionism_in_Wikipedia" title="Deletionism and inclusionism in Wikipedia">Deletionism and inclusionism</a></li>
+<li><a href="/wiki/Gender_bias_on_Wikipedia" title="Gender bias on Wikipedia">Gender bias</a></li>
+<li><a href="/wiki/Racial_bias_on_Wikipedia" title="Racial bias on Wikipedia">Racial bias</a></li>
+<li><a href="/wiki/MediaWiki" title="MediaWiki">MediaWiki</a></li>
+<li><a href="/wiki/Notability_in_the_English_Wikipedia" title="Notability in the English Wikipedia">Notability</a></li>
+<li><a href="/wiki/Reliability_of_Wikipedia" title="Reliability of Wikipedia">Reliability</a></li>
+<li><a href="/wiki/Vandalism_on_Wikipedia" title="Vandalism on Wikipedia">Vandalism</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group"><a href="/wiki/Wikipedia_community" title="Wikipedia community">Community</a></th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Administrators_(Wikipedia)" title="Administrators (Wikipedia)" class="mw-redirect">Administrators</a></li>
+<li><a href="/wiki/Arbitration_Committee" title="Arbitration Committee">Arbitration Committee</a></li>
+<li><a href="/wiki/Edit-a-thon" title="Edit-a-thon">Edit-a-thon</a></li>
+<li><a href="/wiki/Wikipedian_in_residence" title="Wikipedian in residence">Wikipedian in residence</a></li>
+<li><a href="/wiki/Wikimania" title="Wikimania">Wikimania</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">People</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Florence_Devouard" title="Florence Devouard">Florence Devouard</a></li>
+<li><a href="/wiki/Magnus_Manske" title="Magnus Manske">Magnus Manske</a></li>
+<li><a href="/wiki/Erik_M%C3%B6ller" title="Erik Möller">Erik Möller</a></li>
+<li><a href="/wiki/Larry_Sanger" title="Larry Sanger">Larry Sanger</a></li>
+<li><a href="/wiki/Lila_Tretikov" title="Lila Tretikov">Lila Tretikov</a></li>
+<li><a href="/wiki/Jimmy_Wales" title="Jimmy Wales">Jimmy Wales</a></li>
+<li><a href="/wiki/Wikipedia_community" title="Wikipedia community">Wikipedians</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group"><a href="/wiki/History_of_Wikipedia" title="History of Wikipedia">History</a></th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Bomis" title="Bomis">Bomis</a></li>
+<li><a href="/wiki/Wikipedia_logo" title="Wikipedia logo">Logo</a></li>
+<li><a href="/wiki/List_of_Wikipedia_controversies" title="List of Wikipedia controversies">Controversies</a>
+<ul>
+<li><a href="/wiki/U.S._Congressional_staff_edits_to_Wikipedia" title="U.S. Congressional staff edits to Wikipedia" class="mw-redirect">U.S. Congressional staff edits</a></li>
+<li><a href="/wiki/Essjay_controversy" title="Essjay controversy">Essjay controversy</a></li>
+<li><a href="/wiki/Wikipedia_Seigenthaler_biography_incident" title="Wikipedia Seigenthaler biography incident">Seigenthaler biography incident</a></li>
+<li><a href="/wiki/Henryk_Batuta_hoax" title="Henryk Batuta hoax">Henryk Batuta hoax</a></li>
+<li><a href="/wiki/Bicholim_conflict" title="Bicholim conflict">Bicholim Conflict hoax</a></li>
+</ul>
+</li>
+<li><a href="/wiki/Internet_Watch_Foundation_and_Wikipedia" title="Internet Watch Foundation and Wikipedia">Internet Watch Foundation</a></li>
+<li><a href="/wiki/Church_of_Scientology_editing_on_Wikipedia" title="Church of Scientology editing on Wikipedia">Scientology</a></li>
+<li><a href="/wiki/Italian_Wikipedia#2011_mass_blanking_protest" title="Italian Wikipedia">Italian Wikipedia blackout</a></li>
+<li><a href="/wiki/Protests_against_SOPA_and_PIPA#Wikimedia_community" title="Protests against SOPA and PIPA">English Wikipedia blackout</a></li>
+<li><a href="/wiki/Hillsborough_Wikipedia_posts" title="Hillsborough Wikipedia posts">Hillsborough Wikipedia posts</a></li>
+<li><a href="/wiki/MyWikiBiz" title="MyWikiBiz">MyWikiBiz</a></li>
+<li><a href="/wiki/Lsjbot" title="Lsjbot">Lsjbot</a></li>
+<li><a href="/wiki/VisualEditor" title="VisualEditor">VisualEditor</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Recognition<br />
+and honors</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Quadriga_(award)" title="Quadriga (award)">2008 Quadriga award</a></li>
+<li><a href="/wiki/Wikipedia_Monument" title="Wikipedia Monument">Wikipedia Monument</a></li>
+<li><a href="/wiki/Erasmus_Prize" title="Erasmus Prize">2015 Erasmus Prize</a></li>
+<li><a href="/wiki/Princess_of_Asturias_Awards" title="Princess of Asturias Awards">2015 Princess of Asturias Award</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">References<br />
+and analysis</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Wikipedia_in_culture" title="Wikipedia in culture">Cultural</a></li>
+<li><a href="/wiki/Bibliography_of_Wikipedia" title="Bibliography of Wikipedia">Books</a></li>
+<li><a href="/wiki/List_of_films_about_Wikipedia" title="List of films about Wikipedia">Films</a></li>
+<li><a href="/wiki/Academic_studies_about_Wikipedia" title="Academic studies about Wikipedia">Academic studies</a></li>
+<li><a href="/wiki/WikiScanner" title="WikiScanner">WikiScanner</a></li>
+<li><a href="/wiki/Wikipedia_Review" title="Wikipedia Review">Wikipedia Review</a></li>
+<li><a href="/wiki/Wikipediocracy" title="Wikipediocracy">Wikipediocracy</a></li>
+<li><a href="/wiki/Wiki-Watch" title="Wiki-Watch">Wiki-Watch</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Mobile access</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/List_of_Wikipedia_mobile_applications" title="List of Wikipedia mobile applications">Apps</a></li>
+<li><a href="/wiki/QRpedia" title="QRpedia">QRpedia</a></li>
+<li><a href="/wiki/Wapedia" title="Wapedia">Wapedia</a></li>
+<li><a href="/wiki/WikiNodes" title="WikiNodes">WikiNodes</a></li>
+<li><a href="/wiki/Wikipedia_Zero" title="Wikipedia Zero">Wikipedia Zero</a></li>
+<li><a href="/wiki/Wikiwand" title="Wikiwand">Wikiwand</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Content use</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Books_LLC" title="Books LLC">Books LLC</a></li>
+<li><a href="/wiki/DBpedia" title="DBpedia">DBpedia</a></li>
+<li><a href="/wiki/Deletionpedia" title="Deletionpedia">Deletionpedia</a></li>
+<li><a href="/wiki/Kiwix" title="Kiwix">Kiwix</a></li>
+<li><a href="/wiki/WikiReader" title="WikiReader">WikiReader</a></li>
+<li><a href="/wiki/Health_information_on_Wikipedia" title="Health information on Wikipedia">Health information</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Similar projects</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Interpedia" title="Interpedia">Interpedia</a></li>
+<li><a href="/wiki/Nupedia" title="Nupedia">Nupedia</a></li>
+<li><a href="/wiki/Citizendium" title="Citizendium">Citizendium</a></li>
+<li><a href="/wiki/Enciclopedia_Libre_Universal_en_Espa%C3%B1ol" title="Enciclopedia Libre Universal en Español">Enciclopedia Libre Universal en Español</a></li>
+<li><a href="/wiki/Veropedia" title="Veropedia">Veropedia</a></li>
+<li><a href="/wiki/List_of_Internet_encyclopedias" title="List of Internet encyclopedias" class="mw-redirect">List of online encyclopedias</a></li>
+<li><a href="/wiki/List_of_wikis" title="List of wikis">List of wikis</a></li>
+</ul>
+</div>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+<table class="navbox" style="border-spacing:0">
+<tr>
+<td style="padding:2px">
+<table class="nowraplinks hlist collapsible autocollapse navbox-inner" style="border-spacing:0;background:transparent;color:inherit">
+<tr>
+<th scope="col" class="navbox-title" colspan="2">
+<div class="plainlinks hlist navbar mini">
+<ul>
+<li class="nv-view"><a href="/wiki/Template:Wikimedia_Foundation" title="Template:Wikimedia Foundation"><abbr title="View this template" style=";;background:none transparent;border:none;">v</abbr></a></li>
+<li class="nv-talk"><a href="/wiki/Template_talk:Wikimedia_Foundation" title="Template talk:Wikimedia Foundation"><abbr title="Discuss this template" style=";;background:none transparent;border:none;">t</abbr></a></li>
+<li class="nv-edit"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Template:Wikimedia_Foundation&action=edit"><abbr title="Edit this template" style=";;background:none transparent;border:none;">e</abbr></a></li>
+</ul>
+</div>
+<div style="font-size:114%"><a href="/wiki/Wikimedia_Foundation" title="Wikimedia Foundation">Wikimedia Foundation</a></div>
+</th>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group"><a href="/wiki/Board_of_Trustees" title="Board of Trustees" class="mw-redirect">Board of Trustees</a></th>
+<td class="navbox-list navbox-odd" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><span class="flagicon"><a href="/wiki/Argentina" title="Argentina"><img alt="Argentina" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Flag_of_Argentina.svg/23px-Flag_of_Argentina.svg.png" width="23" height="14" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Flag_of_Argentina.svg/35px-Flag_of_Argentina.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Flag_of_Argentina.svg/46px-Flag_of_Argentina.svg.png 2x" data-file-width="8 [...]
+<li><span class="flagicon"><a href="/wiki/Germany" title="Germany"><img alt="Germany" src="//upload.wikimedia.org/wikipedia/en/thumb/b/ba/Flag_of_Germany.svg/23px-Flag_of_Germany.svg.png" width="23" height="14" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/en/thumb/b/ba/Flag_of_Germany.svg/35px-Flag_of_Germany.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/b/ba/Flag_of_Germany.svg/46px-Flag_of_Germany.svg.png 2x" data-file-width="1000" data-file-height="600" /></a [...]
+</ul>
+<ul>
+<li><span class="flagicon"><a href="/wiki/Italy" title="Italy"><img alt="Italy" src="//upload.wikimedia.org/wikipedia/en/thumb/0/03/Flag_of_Italy.svg/23px-Flag_of_Italy.svg.png" width="23" height="15" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/en/thumb/0/03/Flag_of_Italy.svg/35px-Flag_of_Italy.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/0/03/Flag_of_Italy.svg/45px-Flag_of_Italy.svg.png 2x" data-file-width="1500" data-file-height="1000" /></a></span> <a href= [...]
+<li><span class="flagicon"><a href="/wiki/Canada" title="Canada"><img alt="Canada" src="//upload.wikimedia.org/wikipedia/en/thumb/c/cf/Flag_of_Canada.svg/23px-Flag_of_Canada.svg.png" width="23" height="12" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/en/thumb/c/cf/Flag_of_Canada.svg/35px-Flag_of_Canada.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/c/cf/Flag_of_Canada.svg/46px-Flag_of_Canada.svg.png 2x" data-file-width="1000" data-file-height="500" /></a></span> [...]
+<li><span class="flagicon"><a href="/wiki/Poland" title="Poland"><img alt="Poland" src="//upload.wikimedia.org/wikipedia/en/thumb/1/12/Flag_of_Poland.svg/23px-Flag_of_Poland.svg.png" width="23" height="14" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/en/thumb/1/12/Flag_of_Poland.svg/35px-Flag_of_Poland.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/1/12/Flag_of_Poland.svg/46px-Flag_of_Poland.svg.png 2x" data-file-width="1280" data-file-height="800" /></a></span> [...]
+<li><span class="flagicon"><a href="/wiki/United_States" title="United States"><img alt="United States" src="//upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/23px-Flag_of_the_United_States.svg.png" width="23" height="12" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/35px-Flag_of_the_United_States.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/46px-Flag_of_the [...]
+<li><span class="flagicon"><a href="/wiki/Croatia" title="Croatia"><img alt="Croatia" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Flag_of_Croatia.svg/23px-Flag_of_Croatia.svg.png" width="23" height="12" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Flag_of_Croatia.svg/35px-Flag_of_Croatia.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Flag_of_Croatia.svg/46px-Flag_of_Croatia.svg.png 2x" data-file-width="1200" data-file-hei [...]
+<li><span class="flagicon"><a href="/wiki/Netherlands" title="Netherlands"><img alt="Netherlands" src="//upload.wikimedia.org/wikipedia/commons/thumb/2/20/Flag_of_the_Netherlands.svg/23px-Flag_of_the_Netherlands.svg.png" width="23" height="15" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/2/20/Flag_of_the_Netherlands.svg/35px-Flag_of_the_Netherlands.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/2/20/Flag_of_the_Netherlands.svg/45px-Flag_of_the_ [...]
+<li><span class="flagicon"><a href="/wiki/United_States" title="United States"><img alt="United States" src="//upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/23px-Flag_of_the_United_States.svg.png" width="23" height="12" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/35px-Flag_of_the_United_States.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/46px-Flag_of_the [...]
+<li><span class="flagicon"><a href="/wiki/United_States" title="United States"><img alt="United States" src="//upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/23px-Flag_of_the_United_States.svg.png" width="23" height="12" class="thumbborder" srcset="//upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/35px-Flag_of_the_United_States.svg.png 1.5x, //upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/46px-Flag_of_the [...]
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group"><a href="/wiki/Wikimedia_project" title="Wikimedia project">Content projects</a></th>
+<td class="navbox-list navbox-even" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><strong class="selflink">Wikipedia</strong></li>
+<li><a href="/wiki/Wikimedia_project#List" title="Wikimedia project">Meta-Wiki</a></li>
+<li><a href="/wiki/MediaWiki" title="MediaWiki">MediaWiki</a></li>
+<li><a href="/wiki/Wiktionary" title="Wiktionary">Wiktionary</a></li>
+<li><a href="/wiki/Wikiquote" title="Wikiquote">Wikiquote</a></li>
+<li><a href="/wiki/Wikibooks" title="Wikibooks">Wikibooks</a></li>
+<li><a href="/wiki/Wikisource" title="Wikisource">Wikisource</a></li>
+<li><a href="/wiki/Wikimedia_Commons" title="Wikimedia Commons">Wikimedia Commons</a></li>
+<li><a href="/wiki/Wikispecies" title="Wikispecies">Wikispecies</a></li>
+<li><a href="/wiki/Wikinews" title="Wikinews">Wikinews</a></li>
+<li><a href="/wiki/Wikimedia_project#List" title="Wikimedia project">Wikimedia Incubator</a></li>
+<li><a href="/wiki/Wikiversity" title="Wikiversity">Wikiversity</a></li>
+<li><a href="/wiki/Wikidata" title="Wikidata">Wikidata</a></li>
+<li><a href="/wiki/Wikivoyage" title="Wikivoyage">Wikivoyage</a><sup>1</sup></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Other</th>
+<td class="navbox-list navbox-odd" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/List_of_Wikipedias" title="List of Wikipedias">List of Wikipedias</a></li>
+<li><a href="/wiki/List_of_Wikimedia_chapters" title="List of Wikimedia chapters">List of Wikimedia chapters</a></li>
+<li><a href="/wiki/Wikimania" title="Wikimania">Wikimania</a></li>
+<li><a href="/wiki/Wikimedia_project#Software_projects_and_other_backstage_projects" title="Wikimedia project">Software and backstage</a></li>
+<li><i><a href="/wiki/Wikimedia_Foundation_v._NSA" title="Wikimedia Foundation v. NSA">Wikimedia v. NSA</a></i></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<td class="navbox-abovebelow" colspan="2">
+<div><sup>1</sup> <a href="/wiki/Fork_(software_development)" title="Fork (software development)">Forked</a> from <a href="/wiki/Wikitravel" title="Wikitravel">Wikitravel</a> in 2006; joined the Wikimedia Foundation in 2012. Wikitravel founded in 2003.</div>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+<table class="navbox" style="border-spacing:0">
+<tr>
+<td style="padding:2px">
+<table class="nowraplinks collapsible autocollapse navbox-inner" style="border-spacing:0;background:transparent;color:inherit">
+<tr>
+<th scope="col" class="navbox-title" colspan="2">
+<div class="plainlinks hlist navbar mini">
+<ul>
+<li class="nv-view"><a href="/wiki/Template:Wikipedias" title="Template:Wikipedias"><abbr title="View this template" style=";;background:none transparent;border:none;">v</abbr></a></li>
+<li class="nv-talk"><a href="/wiki/Template_talk:Wikipedias" title="Template talk:Wikipedias"><abbr title="Discuss this template" style=";;background:none transparent;border:none;">t</abbr></a></li>
+<li class="nv-edit"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Template:Wikipedias&action=edit"><abbr title="Edit this template" style=";;background:none transparent;border:none;">e</abbr></a></li>
+</ul>
+</div>
+<div style="font-size:114%"><a href="/wiki/List_of_Wikipedias" title="List of Wikipedias">List of Wikipedias</a> by article count</div>
+</th>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">5,000,000+</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><b><a href="/wiki/English_Wikipedia" title="English Wikipedia">English</a> (<a href="/wiki/Main_Page" title="Main Page">en</a>)</b></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">2,000,000+</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Swedish_Wikipedia" title="Swedish Wikipedia">Swedish</a> (<a href="//sv.wikipedia.org/wiki/" class="extiw" title="sv:">sv</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">1,000,000+</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/German_Wikipedia" title="German Wikipedia">German</a> (<a href="//de.wikipedia.org/wiki/" class="extiw" title="de:">de</a>)</li>
+<li><a href="/wiki/Dutch_Wikipedia" title="Dutch Wikipedia">Dutch</a> (<a href="//nl.wikipedia.org/wiki/" class="extiw" title="nl:">nl</a>)</li>
+<li><a href="/wiki/French_Wikipedia" title="French Wikipedia">French</a> (<a href="//fr.wikipedia.org/wiki/" class="extiw" title="fr:">fr</a>)</li>
+<li><a href="/wiki/Cebuano_Wikipedia" title="Cebuano Wikipedia">Cebuano</a> (<a href="//ceb.wikipedia.org/wiki/" class="extiw" title="ceb:">ceb</a>)</li>
+<li><a href="/wiki/Russian_Wikipedia" title="Russian Wikipedia">Russian</a> (<a href="//ru.wikipedia.org/wiki/" class="extiw" title="ru:">ru</a>)</li>
+<li><a href="/wiki/Waray_Wikipedia" title="Waray Wikipedia">Waray</a> (<a href="//war.wikipedia.org/wiki/" class="extiw" title="war:">war</a>)</li>
+<li><a href="/wiki/Italian_Wikipedia" title="Italian Wikipedia">Italian</a> (<a href="//it.wikipedia.org/wiki/" class="extiw" title="it:">it</a>)</li>
+<li><a href="/wiki/Spanish_Wikipedia" title="Spanish Wikipedia">Spanish</a> (<a href="//es.wikipedia.org/wiki/" class="extiw" title="es:">es</a>)</li>
+<li><a href="/wiki/Polish_Wikipedia" title="Polish Wikipedia">Polish</a> (<a href="//pl.wikipedia.org/wiki/" class="extiw" title="pl:">pl</a>)</li>
+<li><a href="/wiki/Vietnamese_Wikipedia" title="Vietnamese Wikipedia">Vietnamese</a> (<a href="//vi.wikipedia.org/wiki/" class="extiw" title="vi:">vi</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">500,000+</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Japanese_Wikipedia" title="Japanese Wikipedia">Japanese</a> (<a href="//ja.wikipedia.org/wiki/" class="extiw" title="ja:">ja</a>)</li>
+<li><a href="/wiki/Portuguese_Wikipedia" title="Portuguese Wikipedia">Portuguese</a> (<a href="//pt.wikipedia.org/wiki/" class="extiw" title="pt:">pt</a>)</li>
+<li><a href="/wiki/Chinese_Wikipedia" title="Chinese Wikipedia">Chinese</a> (<a href="//zh.wikipedia.org/wiki/" class="extiw" title="zh:">zh</a>)</li>
+<li><a href="/wiki/Ukrainian_Wikipedia" title="Ukrainian Wikipedia">Ukrainian</a> (<a href="//uk.wikipedia.org/wiki/" class="extiw" title="uk:">uk</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">200,000+</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Catalan_Wikipedia" title="Catalan Wikipedia">Catalan</a> (<a href="//ca.wikipedia.org/wiki/" class="extiw" title="ca:">ca</a>)</li>
+<li><a href="/wiki/Persian_Wikipedia" title="Persian Wikipedia">Persian</a> (<a href="//fa.wikipedia.org/wiki/" class="extiw" title="fa:">fa</a>)</li>
+<li><a href="/wiki/Serbo-Croatian_Wikipedia" title="Serbo-Croatian Wikipedia">Serbo-Croatian</a> (<a href="//sh.wikipedia.org/wiki/" class="extiw" title="sh:">sh</a>)</li>
+<li><a href="/wiki/Norwegian_Wikipedia" title="Norwegian Wikipedia">Norwegian</a> (<a href="/wiki/Bokm%C3%A5l" title="Bokmål">Bokmål</a>) (<a href="//no.wikipedia.org/wiki/" class="extiw" title="no:">no</a>)</li>
+<li><a href="/wiki/Arabic_Wikipedia" title="Arabic Wikipedia">Arabic</a> (<a href="//ar.wikipedia.org/wiki/" class="extiw" title="ar:">ar</a>)</li>
+<li><a href="/wiki/Finnish_Wikipedia" title="Finnish Wikipedia">Finnish</a> (<a href="//fi.wikipedia.org/wiki/" class="extiw" title="fi:">fi</a>)</li>
+<li><a href="/wiki/Hungarian_Wikipedia" title="Hungarian Wikipedia">Hungarian</a> (<a href="//hu.wikipedia.org/wiki/" class="extiw" title="hu:">hu</a>)</li>
+<li><a href="/wiki/Indonesian_Wikipedia" title="Indonesian Wikipedia">Indonesian</a> (<a href="//id.wikipedia.org/wiki/" class="extiw" title="id:">id</a>)</li>
+<li><a href="/wiki/Romanian_Wikipedia" title="Romanian Wikipedia">Romanian</a> (<a href="//ro.wikipedia.org/wiki/" class="extiw" title="ro:">ro</a>)</li>
+<li><a href="/wiki/Czech_Wikipedia" title="Czech Wikipedia">Czech</a> (<a href="//cs.wikipedia.org/wiki/" class="extiw" title="cs:">cs</a>)</li>
+<li><a href="/wiki/Korean_Wikipedia" title="Korean Wikipedia">Korean</a> (<a href="//ko.wikipedia.org/wiki/" class="extiw" title="ko:">ko</a>)</li>
+<li><a href="/wiki/Serbian_Wikipedia" title="Serbian Wikipedia">Serbian</a> (<a href="//sr.wikipedia.org/wiki/" class="extiw" title="sr:">sr</a>)</li>
+<li><a href="/wiki/Malay_Wikipedia" title="Malay Wikipedia">Malay</a> (<a href="//ms.wikipedia.org/wiki/" class="extiw" title="ms:">ms</a>)</li>
+<li><a href="/wiki/Turkish_Wikipedia" title="Turkish Wikipedia">Turkish</a> (<a href="//tr.wikipedia.org/wiki/" class="extiw" title="tr:">tr</a>)</li>
+<li><a href="/wiki/Esperanto_Wikipedia" title="Esperanto Wikipedia">Esperanto</a> (<a href="//eo.wikipedia.org/wiki/" class="extiw" title="eo:">eo</a>)</li>
+<li><a href="/wiki/Minangkabau_Wikipedia" title="Minangkabau Wikipedia" class="mw-redirect">Minangkabau</a> (<a href="//min.wikipedia.org/wiki/" class="extiw" title="min:">min</a>)</li>
+<li><a href="/wiki/Basque_Wikipedia" title="Basque Wikipedia">Basque</a> (<a href="//eu.wikipedia.org/wiki/" class="extiw" title="eu:">eu</a>)</li>
+<li><a href="/wiki/Kazakh_Wikipedia" title="Kazakh Wikipedia">Kazakh</a> (<a href="//kk.wikipedia.org/wiki/" class="extiw" title="kk:">kk</a>)</li>
+<li><a href="/wiki/Danish_Wikipedia" title="Danish Wikipedia">Danish</a> (<a href="//da.wikipedia.org/wiki/" class="extiw" title="da:">da</a>)</li>
+<li><a href="/wiki/Bulgarian_Wikipedia" title="Bulgarian Wikipedia">Bulgarian</a> (<a href="//bg.wikipedia.org/wiki/" class="extiw" title="bg:">bg</a>)</li>
+<li><a href="/wiki/Slovak_Wikipedia" title="Slovak Wikipedia">Slovak</a> (<a href="//sk.wikipedia.org/wiki/" class="extiw" title="sk:">sk</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">100,000+</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Armenian_Wikipedia" title="Armenian Wikipedia">Armenian</a> (<a href="//hy.wikipedia.org/wiki/" class="extiw" title="hy:">hy</a>)</li>
+<li><a href="/wiki/Hebrew_Wikipedia" title="Hebrew Wikipedia">Hebrew</a> (<a href="//he.wikipedia.org/wiki/" class="extiw" title="he:">he</a>)</li>
+<li><a href="/wiki/Lithuanian_Wikipedia" title="Lithuanian Wikipedia">Lithuanian</a> (<a href="//lt.wikipedia.org/wiki/" class="extiw" title="lt:">lt</a>)</li>
+<li><a href="/wiki/Croatian_Wikipedia" title="Croatian Wikipedia">Croatian</a> (<a href="//hr.wikipedia.org/wiki/" class="extiw" title="hr:">hr</a>)</li>
+<li><a href="/wiki/Slovene_Wikipedia" title="Slovene Wikipedia">Slovene</a> (<a href="//sl.wikipedia.org/wiki/" class="extiw" title="sl:">sl</a>)</li>
+<li><a href="/wiki/Estonian_Wikipedia" title="Estonian Wikipedia">Estonian</a> (<a href="//et.wikipedia.org/wiki/" class="extiw" title="et:">et</a>)</li>
+<li><a href="/wiki/Uzbek_Wikipedia" title="Uzbek Wikipedia">Uzbek</a> (<a href="//uz.wikipedia.org/wiki/" class="extiw" title="uz:">uz</a>)</li>
+<li><a href="/wiki/Galician_Wikipedia" title="Galician Wikipedia">Galician</a> (<a href="//gl.wikipedia.org/wiki/" class="extiw" title="gl:">gl</a>)</li>
+<li><a href="/wiki/Norwegian_Wikipedia" title="Norwegian Wikipedia">Norwegian</a> (<a href="/wiki/Nynorsk" title="Nynorsk">Nynorsk</a>) (<a href="//nn.wikipedia.org/wiki/" class="extiw" title="nn:">nn</a>)</li>
+<li><a href="/wiki/Latin_Wikipedia" title="Latin Wikipedia">Latin</a> (<a href="//la.wikipedia.org/wiki/" class="extiw" title="la:">la</a>)</li>
+<li><a href="/wiki/Volapuk_Wikipedia" title="Volapuk Wikipedia" class="mw-redirect">Volapük</a> (<a href="//vo.wikipedia.org/wiki/" class="extiw" title="vo:">vo</a>)</li>
+<li><a href="/wiki/Simple_English_Wikipedia" title="Simple English Wikipedia">Simple English</a> (<a href="//simple.wikipedia.org/wiki/" class="extiw" title="simple:">simple</a>)</li>
+<li><a href="/wiki/Chechen_Wikipedia" title="Chechen Wikipedia">Chechen</a> (<a href="//ce.wikipedia.org/wiki/" class="extiw" title="ce:">ce</a>)</li>
+<li><a href="/wiki/Greek_Wikipedia" title="Greek Wikipedia">Greek</a> (<a href="//el.wikipedia.org/wiki/" class="extiw" title="el:">el</a>)</li>
+<li><a href="/wiki/Belarusian_Wikipedia" title="Belarusian Wikipedia">Belarusian</a> (<a href="//be.wikipedia.org/wiki/" class="extiw" title="be:">be</a>)</li>
+<li><a href="/wiki/Georgian_Wikipedia" title="Georgian Wikipedia">Georgian</a> (<a href="//ka.wikipedia.org/wiki/" class="extiw" title="ka:">ka</a>)</li>
+<li><a href="/wiki/Azerbaijani_Wikipedia" title="Azerbaijani Wikipedia">Azerbaijani</a> (<a href="//az.wikipedia.org/wiki/" class="extiw" title="az:">az</a>)</li>
+<li><a href="/wiki/Hindi_Wikipedia" title="Hindi Wikipedia">Hindi</a> (<a href="//hi.wikipedia.org/wiki/" class="extiw" title="hi:">hi</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">50,000+</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Thai_Wikipedia" title="Thai Wikipedia">Thai</a> (<a href="//th.wikipedia.org/wiki/" class="extiw" title="th:">th</a>)</li>
+<li><a href="/wiki/Urdu_Wikipedia" title="Urdu Wikipedia">Urdu</a> (<a href="//ur.wikipedia.org/wiki/" class="extiw" title="ur:">ur</a>)</li>
+<li><a href="/wiki/Min_Nan_Wikipedia" title="Min Nan Wikipedia">Min Nan</a> (<a href="//zh-min-nan.wikipedia.org/wiki/" class="extiw" title="zh-min-nan:">zh-min-nan</a>)</li>
+<li><a href="/wiki/Occitan_Wikipedia" title="Occitan Wikipedia">Occitan</a> (<a href="//oc.wikipedia.org/wiki/" class="extiw" title="oc:">oc</a>)</li>
+<li><a href="/wiki/Macedonian_Wikipedia" title="Macedonian Wikipedia">Macedonian</a> (<a href="//mk.wikipedia.org/wiki/" class="extiw" title="mk:">mk</a>)</li>
+<li><a href="/wiki/Tamil_Wikipedia" title="Tamil Wikipedia">Tamil</a> (<a href="//ta.wikipedia.org/wiki/" class="extiw" title="ta:">ta</a>)</li>
+<li><a href="/wiki/Nepal_Bhasa_Wikipedia" title="Nepal Bhasa Wikipedia">Nepal Bhasa</a> (<a href="//new.wikipedia.org/wiki/" class="extiw" title="new:">new</a>)</li>
+<li><a href="/wiki/Welsh_Wikipedia" title="Welsh Wikipedia">Welsh</a> (<a href="//cy.wikipedia.org/wiki/" class="extiw" title="cy:">cy</a>)</li>
+<li><a href="/wiki/Bosnian_Wikipedia" title="Bosnian Wikipedia">Bosnian</a> (<a href="//bs.wikipedia.org/wiki/" class="extiw" title="bs:">bs</a>)</li>
+<li><a href="/wiki/Latvian_Wikipedia" title="Latvian Wikipedia">Latvian</a> (<a href="//lv.wikipedia.org/wiki/" class="extiw" title="lv:">lv</a>)</li>
+<li><a href="/wiki/Tagalog_Wikipedia" title="Tagalog Wikipedia">Tagalog</a> (<a href="//tl.wikipedia.org/wiki/" class="extiw" title="tl:">tl</a>)</li>
+<li><a href="/wiki/Telugu_Wikipedia" title="Telugu Wikipedia">Telugu</a> (<a href="//te.wikipedia.org/wiki/" class="extiw" title="te:">te</a>)</li>
+<li><a href="/wiki/Belarusian_Wikipedia" title="Belarusian Wikipedia">Belarusian</a> (<a href="//be-x-old.wikipedia.org/wiki/" class="extiw" title="be-tarask:">be-tarask</a>)</li>
+<li><a href="/wiki/Breton_Wikipedia" title="Breton Wikipedia">Breton</a> (<a href="//br.wikipedia.org/wiki/" class="extiw" title="br:">br</a>)</li>
+<li><a href="/wiki/Albanian_Wikipedia" title="Albanian Wikipedia">Albanian</a> (<a href="//sq.wikipedia.org/wiki/" class="extiw" title="sq:">sq</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">20,000+</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Javanese_Wikipedia" title="Javanese Wikipedia">Javanese</a> (<a href="//jv.wikipedia.org/wiki/" class="extiw" title="jv:">jv</a>)</li>
+<li><a href="/wiki/Asturian_Wikipedia" title="Asturian Wikipedia">Asturian</a> (<a href="//ast.wikipedia.org/wiki/" class="extiw" title="ast:">ast</a>)</li>
+<li><a href="/wiki/Marathi_Wikipedia" title="Marathi Wikipedia">Marathi</a> (<a href="//mr.wikipedia.org/wiki/" class="extiw" title="mr:">mr</a>)</li>
+<li><a href="/wiki/Cantonese_Wikipedia" title="Cantonese Wikipedia">Cantonese</a> (<a href="//zh-yue.wikipedia.org/wiki/" class="extiw" title="zh-yue:">zh-yue</a>)</li>
+<li><a href="/wiki/Malayalam_Wikipedia" title="Malayalam Wikipedia">Malayalam</a> (<a href="//ml.wikipedia.org/wiki/" class="extiw" title="ml:">ml</a>)</li>
+<li><a href="/wiki/Bengali_Wikipedia" title="Bengali Wikipedia">Bengali</a> (<a href="//bn.wikipedia.org/wiki/" class="extiw" title="bn:">bn</a>)</li>
+<li><a href="/wiki/Afrikaans_Wikipedia" title="Afrikaans Wikipedia">Afrikaans</a> (<a href="//af.wikipedia.org/wiki/" class="extiw" title="af:">af</a>)</li>
+<li><a href="/wiki/Irish_language_Wikipedia" title="Irish language Wikipedia">Irish</a> (<a href="//ga.wikipedia.org/wiki/" class="extiw" title="ga:">ga</a>)</li>
+<li><a href="/wiki/Scots_Wikipedia" title="Scots Wikipedia">Scots</a> (<a href="//sco.wikipedia.org/wiki/" class="extiw" title="sco:">sco</a>)</li>
+<li><a href="/wiki/Chuvash_Wikipedia" title="Chuvash Wikipedia">Chuvash</a> (<a href="//cv.wikipedia.org/wiki/" class="extiw" title="cv:">cv</a>)</li>
+<li><a href="/wiki/West_Frisian_Wikipedia" title="West Frisian Wikipedia">West Frisian</a> (<a href="//fy.wikipedia.org/wiki/" class="extiw" title="fy:">fy</a>)</li>
+<li><a href="/wiki/Burmese_Wikipedia" title="Burmese Wikipedia">Burmese</a> (<a href="//my.wikipedia.org/wiki/" class="extiw" title="my:">my</a>)</li>
+<li><a href="/wiki/Swahili_Wikipedia" title="Swahili Wikipedia">Swahili</a> (<a href="//sw.wikipedia.org/wiki/" class="extiw" title="sw:">sw</a>)</li>
+<li><a href="/wiki/Yoruba_Wikipedia" title="Yoruba Wikipedia">Yoruba</a> (<a href="//yo.wikipedia.org/wiki/" class="extiw" title="yo:">yo</a>)</li>
+<li><a href="/wiki/Aragonese_Wikipedia" title="Aragonese Wikipedia">Aragonese</a> (<a href="//an.wikipedia.org/wiki/" class="extiw" title="an:">an</a>)</li>
+<li><a href="/wiki/Sicilian_Wikipedia" title="Sicilian Wikipedia">Sicilian</a> (<a href="//scn.wikipedia.org/wiki/" class="extiw" title="scn:">scn</a>)</li>
+<li><a href="/wiki/Bishnupriya_Manipuri_Wikipedia" title="Bishnupriya Manipuri Wikipedia">Bishnupriya Manipuri</a> (<a href="//bpy.wikipedia.org/wiki/" class="extiw" title="bpy:">bpy</a>)</li>
+<li><a href="/wiki/Kurdish_Wikipedia" title="Kurdish Wikipedia" class="mw-redirect">Kurdish</a> (<a href="//ku.wikipedia.org/wiki/" class="extiw" title="ku:">ku</a>)</li>
+<li><a href="/wiki/Alemannic_Wikipedia" title="Alemannic Wikipedia">Alemannic</a> (<a href="//als.wikipedia.org/wiki/" class="extiw" title="als:">als</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">10,000+</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Punjabi_Wikipedia" title="Punjabi Wikipedia">Punjabi</a> (<a href="//pa.wikipedia.org/wiki/" class="extiw" title="pa:">pa</a> and <a href="//pnb.wikipedia.org/wiki/" class="extiw" title="pnb:">pnb</a>)</li>
+<li><a href="/wiki/Sundanese_Wikipedia" title="Sundanese Wikipedia">Sundanese</a> (<a href="//su.wikipedia.org/wiki/" class="extiw" title="su:">su</a>)</li>
+<li><a href="/wiki/Kannada_Wikipedia" title="Kannada Wikipedia">Kannada</a> (<a href="//kn.wikipedia.org/wiki/" class="extiw" title="kn:">kn</a>)</li>
+<li><a href="/wiki/Mongolian_Wikipedia" title="Mongolian Wikipedia">Mongolian</a> (<a href="//mn.wikipedia.org/wiki/" class="extiw" title="mn:">mn</a>)</li>
+<li><a href="/wiki/Egyptian_Arabic_Wikipedia" title="Egyptian Arabic Wikipedia">Egyptian Arabic</a> (<a href="//arz.wikipedia.org/wiki/" class="extiw" title="arz:">arz</a>)</li>
+<li><a href="/wiki/Yiddish_Wikipedia" title="Yiddish Wikipedia">Yiddish</a> (<a href="//yi.wikipedia.org/wiki/" class="extiw" title="yi:">yi</a>)</li>
+<li><a href="/wiki/Ossetian_Wikipedia" title="Ossetian Wikipedia">Ossetian</a> (<a href="//os.wikipedia.org/wiki/" class="extiw" title="os:">os</a>)</li>
+<li><a href="/wiki/Oriya_Wikipedia" title="Oriya Wikipedia" class="mw-redirect">Oriya</a> (<a href="//or.wikipedia.org/wiki/" class="extiw" title="or:">or</a>)</li>
+<li><a href="/wiki/Sanskrit_Wikipedia" title="Sanskrit Wikipedia">Sanskrit</a> (<a href="//sa.wikipedia.org/wiki/" class="extiw" title="sa:">sa</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">1,000+</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Tarantino_language" title="Tarantino language" class="mw-redirect">Tarantino</a> (<a href="//roa-tara.wikipedia.org/wiki/" class="extiw" title="roa-tara:">roa-tara</a>)</li>
+<li><a href="/wiki/South_Azerbaijani_Wikipedia" title="South Azerbaijani Wikipedia">South Azerbaijani</a> (<a href="//azb.wikipedia.org/wiki/" class="extiw" title="azb:">azb</a>)</li>
+<li><a href="/wiki/Northern_Sami_Wikipedia" title="Northern Sami Wikipedia">Northern Sami</a> (<a href="//se.wikipedia.org/wiki/" class="extiw" title="se:">se</a>)</li>
+<li><a href="/wiki/Dutch_Low_Saxon_Wikipedia" title="Dutch Low Saxon Wikipedia">Dutch Low Saxon</a> (<a href="//nds-nl.wikipedia.org/wiki/" class="extiw" title="nds-nl:">nds-nl</a>)</li>
+<li><a href="/wiki/Sindhi_Wikipedia" title="Sindhi Wikipedia">Sindhi</a> (<a href="//sd.wikipedia.org/wiki/" class="extiw" title="sd:">sd</a>)</li>
+<li><a href="/wiki/Silesian_Wikipedia" title="Silesian Wikipedia">Silesian</a> (<a href="//szl.wikipedia.org/wiki/" class="extiw" title="szl:">szl</a>)</li>
+<li><a href="/wiki/Assamese_Wikipedia" title="Assamese Wikipedia">Assamese</a> (<a href="//as.wikipedia.org/wiki/" class="extiw" title="as:">as</a>)</li>
+<li><a href="/wiki/Ripuarian_Wikipedia" title="Ripuarian Wikipedia">Ripuarian</a> (<a href="//ksh.wikipedia.org/wiki/" class="extiw" title="ksh:">ksh</a>)</li>
+<li><a href="/wiki/Northern_Sotho_Wikipedia" title="Northern Sotho Wikipedia">Northern Sotho</a> (<a href="//nso.wikipedia.org/wiki/" class="extiw" title="nso:">nso</a>)</li>
+<li><a href="/wiki/Wolof_Wikipedia" title="Wolof Wikipedia">Wolof</a> (<a href="//wo.wikipedia.org/wiki/" class="extiw" title="wo:">wo</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">100+</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Zulu_Wikipedia" title="Zulu Wikipedia">Zulu</a> (<a href="//zu.wikipedia.org/wiki/" class="extiw" title="zu:">zu</a>)</li>
+<li><a href="/wiki/Xhosa_Wikipedia" title="Xhosa Wikipedia">Xhosa</a> (<a href="//xh.wikipedia.org/wiki/" class="extiw" title="xh:">xh</a>)</li>
+<li><a href="/wiki/Moldovan_Wikipedia" title="Moldovan Wikipedia" class="mw-redirect">Moldovan</a> (<a href="//mo.wikipedia.org/wiki/" class="extiw" title="mo:">mo</a>)</li>
+<li><a href="/wiki/Bambara_Wikipedia" title="Bambara Wikipedia">Bambara</a> (<a href="//bm.wikipedia.org/wiki/" class="extiw" title="bm:">bm</a>)</li>
+<li><a href="/wiki/Tsonga_Wikipedia" title="Tsonga Wikipedia">Tsonga</a> (<a href="//ts.wikipedia.org/wiki/" class="extiw" title="ts:">ts</a>)</li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<td class="navbox-abovebelow" colspan="2">
+<div><a href="//meta.wikimedia.org/wiki/List_of_Wikipedias" class="extiw" title="meta:List of Wikipedias">Full list of Wikipedias</a></div>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+<table class="navbox" style="border-spacing:0">
+<tr>
+<td style="padding:2px">
+<table class="nowraplinks collapsible autocollapse navbox-inner" style="border-spacing:0;background:transparent;color:inherit">
+<tr>
+<th scope="col" class="navbox-title" colspan="2">
+<div class="plainlinks hlist navbar mini">
+<ul>
+<li class="nv-view"><a href="/wiki/Template:Wiki_topics" title="Template:Wiki topics"><abbr title="View this template" style=";;background:none transparent;border:none;">v</abbr></a></li>
+<li class="nv-talk"><a href="/wiki/Template_talk:Wiki_topics" title="Template talk:Wiki topics"><abbr title="Discuss this template" style=";;background:none transparent;border:none;">t</abbr></a></li>
+<li class="nv-edit"><a class="external text" href="//en.wikipedia.org/w/index.php?title=Template:Wiki_topics&action=edit"><abbr title="Edit this template" style=";;background:none transparent;border:none;">e</abbr></a></li>
+</ul>
+</div>
+<div style="font-size:114%"><a href="/wiki/Wiki" title="Wiki">Wikis</a></div>
+</th>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Types</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Personal_wiki" title="Personal wiki">Personal</a></li>
+<li><a href="/wiki/List_of_medical_wikis" title="List of medical wikis">Medical</a></li>
+<li><a href="/wiki/Semantic_wiki" title="Semantic wiki">Semantic</a></li>
+<li><a href="/wiki/Wiki_hosting_service" title="Wiki hosting service">Wiki farm</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Components</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Wiki_software" title="Wiki software">Software</a></li>
+<li><a href="/wiki/Wiki_markup" title="Wiki markup">Markup</a></li>
+<li><a href="/wiki/Interwiki_links" title="Interwiki links">Interwiki links</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Lists</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/List_of_wikis" title="List of wikis">Wikis</a></li>
+<li><a href="/wiki/List_of_wiki_software" title="List of wiki software">Software</a></li>
+<li><a href="/wiki/List_of_Wiki_markups" title="List of Wiki markups" class="mw-redirect">Markups and parsers</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">Comparisons</th>
+<td class="navbox-list navbox-even hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/Comparison_of_wiki_software" title="Comparison of wiki software">Software</a></li>
+<li><a href="/wiki/Comparison_of_wiki_hosting_services" title="Comparison of wiki hosting services">Wiki farms</a></li>
+</ul>
+</div>
+</td>
+</tr>
+<tr style="height:2px">
+<td colspan="2"></td>
+</tr>
+<tr>
+<th scope="row" class="navbox-group">See also</th>
+<td class="navbox-list navbox-odd hlist" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a href="/wiki/History_of_wikis" title="History of wikis">History of wikis</a></li>
+<li><a href="/wiki/Creole_(markup)" title="Creole (markup)">Creole</a></li>
+</ul>
+</div>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+<table class="navbox" style="border-spacing:0">
+<tr>
+<td style="padding:2px">
+<table class="nowraplinks hlist navbox-inner" style="border-spacing:0;background:transparent;color:inherit">
+<tr>
+<th scope="row" class="navbox-group"><a href="/wiki/Help:Authority_control" title="Help:Authority control">Authority control</a></th>
+<td class="navbox-list navbox-odd" style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px">
+<div style="padding:0em 0.25em">
+<ul>
+<li><a rel="nofollow" class="external text" href="//www.worldcat.org/identities/lccn-no2008-072801">WorldCat</a></li>
+<li><a href="/wiki/Virtual_International_Authority_File" title="Virtual International Authority File">VIAF</a>: <span class="uid"><a rel="nofollow" class="external text" href="https://viaf.org/viaf/195846295">195846295</a></span></li>
+<li><a href="/wiki/Library_of_Congress_Control_Number" title="Library of Congress Control Number">LCCN</a>: <span class="uid"><a rel="nofollow" class="external text" href="http://id.loc.gov/authorities/names/no2008072801">no2008072801</a></span></li>
+<li><a href="/wiki/Integrated_Authority_File" title="Integrated Authority File">GND</a>: <span class="uid"><a rel="nofollow" class="external text" href="http://d-nb.info/gnd/7545251-0">7545251-0</a></span></li>
+<li><a href="/wiki/Syst%C3%A8me_universitaire_de_documentation" title="Système universitaire de documentation">SUDOC</a>: <span class="uid"><a rel="nofollow" class="external text" href="http://www.idref.fr/11109383X">11109383X</a></span></li>
+<li><a href="/wiki/Biblioth%C3%A8que_nationale_de_France" title="Bibliothèque nationale de France">BNF</a>: <span class="uid"><a rel="nofollow" class="external text" href="http://catalogue.bnf.fr/ark:/12148/cb150837752">cb150837752</a> <a rel="nofollow" class="external text" href="http://data.bnf.fr/ark:/12148/cb150837752">(data)</a></span></li>
+<li><a href="/wiki/National_Library_of_the_Czech_Republic" title="National Library of the Czech Republic">NKC</a>: <span class="uid"><a rel="nofollow" class="external text" href="http://aleph.nkp.cz/F/?func=find-c&local_base=aut&ccl_term=ica=kn20090528031&CON_LNG=ENG">kn20090528031</a></span></li>
+</ul>
+</div>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+
+
+<!--
+NewPP limit report
+Parsed by mw1011
+Cached time: 20151203140256
+Cache expiry: 3600
+Dynamic content: true
+CPU time usage: 6.145 seconds
+Real time usage: 6.753 seconds
+Preprocessor visited node count: 41897/1000000
+Preprocessor generated node count: 0/1500000
+Post‐expand include size: 701412/2097152 bytes
+Template argument size: 32096/2097152 bytes
+Highest expansion depth: 23/40
+Expensive parser function count: 23/500
+Lua time usage: 2.587/10.000 seconds
+Lua memory usage: 20.73 MB/50 MB
+Number of Wikibase entities loaded: 1-->
+
+<!--
+Transclusion expansion time report (%,ms,calls,template)
+100.00% 5865.571 1 - -total
+ 45.99% 2697.389 2 - Template:Reflist
+ 17.07% 1001.454 151 - Template:Cite_web
+ 11.39% 668.029 96 - Template:Cite_news
+ 8.36% 490.518 36 - Template:Wikipedia_rank_by_size/WP
+ 8.17% 478.995 1 - User:WP_1.0_bot/Tables/OverallArticles
+ 6.61% 387.504 1 - User:WP_1.0_bot/WikiWork
+ 6.54% 383.329 1 - User:WP_1.0_bot/WikiWork/ta
+ 6.47% 379.730 1 - User:WP_1.0_bot/WikiWork/ta/pri
+ 6.42% 376.821 1 - User:WP_1.0_bot/WikiWork/ww
+-->
+
+<!-- Saved in parser cache with key enwiki:pcache:idhash:5043734-0!*!0!!en!4!* and timestamp 20151203140250 and revision id 693133248
+ -->
+<noscript><img src="//en.wikipedia.org/wiki/Special:CentralAutoLogin/start?type=1x1" alt="" title="" width="1" height="1" style="border: none; position: absolute;" /></noscript></div> <div class="printfooter">
+ Retrieved from "<a dir="ltr" href="https://en.wikipedia.org/w/index.php?title=Wikipedia&oldid=693133248">https://en.wikipedia.org/w/index.php?title=Wikipedia&oldid=693133248</a>" </div>
+ <div id='catlinks' class='catlinks'><div id="mw-normal-catlinks" class="mw-normal-catlinks"><a href="/wiki/Help:Category" title="Help:Category">Categories</a>: <ul><li><a href="/wiki/Category:Wikipedia" title="Category:Wikipedia">Wikipedia</a></li><li><a href="/wiki/Category:Collaborative_projects" title="Category:Collaborative projects">Collaborative projects</a></li><li><a href="/wiki/Category:Creative_Commons-licensed_websites" title="Category:Creative Commons-licensed websites">C [...]
+ </div>
+ </div>
+ <div id="mw-navigation">
+ <h2>Navigation menu</h2>
+
+ <div id="mw-head">
+ <div id="p-personal" role="navigation" class="" aria-labelledby="p-personal-label">
+ <h3 id="p-personal-label">Personal tools</h3>
+ <ul>
+ <li id="pt-createaccount"><a href="/w/index.php?title=Special:UserLogin&returnto=Wikipedia&type=signup" title="You are encouraged to create an account and log in; however, it is not mandatory">Create account</a></li><li id="pt-login"><a href="/w/index.php?title=Special:UserLogin&returnto=Wikipedia" title="You're encouraged to log in; however, it's not mandatory. [o]" accesskey="o">Log in</a></li> </ul>
+ </div>
+ <div id="left-navigation">
+ <div id="p-namespaces" role="navigation" class="vectorTabs" aria-labelledby="p-namespaces-label">
+ <h3 id="p-namespaces-label">Namespaces</h3>
+ <ul>
+ <li id="ca-nstab-main" class="selected"><span><a href="/wiki/Wikipedia" title="View the content page [c]" accesskey="c">Article</a></span></li>
+ <li id="ca-talk"><span><a href="/wiki/Talk:Wikipedia" title="Discussion about the content page [t]" accesskey="t" rel="discussion">Talk</a></span></li>
+ </ul>
+ </div>
+ <div id="p-variants" role="navigation" class="vectorMenu emptyPortlet" aria-labelledby="p-variants-label">
+ <h3 id="p-variants-label">
+ <span>Variants</span><a href="#"></a>
+ </h3>
+
+ <div class="menu">
+ <ul>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div id="right-navigation">
+ <div id="p-views" role="navigation" class="vectorTabs" aria-labelledby="p-views-label">
+ <h3 id="p-views-label">Views</h3>
+ <ul>
+ <li id="ca-view" class="selected"><span><a href="/wiki/Wikipedia" >Read</a></span></li>
+ <li id="ca-viewsource"><span><a href="/w/index.php?title=Wikipedia&action=edit" title="This page is protected.
You can view its source [e]" accesskey="e">View source</a></span></li>
+ <li id="ca-history" class="collapsible"><span><a href="/w/index.php?title=Wikipedia&action=history" title="Past revisions of this page [h]" accesskey="h">View history</a></span></li>
+ </ul>
+ </div>
+ <div id="p-cactions" role="navigation" class="vectorMenu emptyPortlet" aria-labelledby="p-cactions-label">
+ <h3 id="p-cactions-label"><span>More</span><a href="#"></a></h3>
+
+ <div class="menu">
+ <ul>
+ </ul>
+ </div>
+ </div>
+ <div id="p-search" role="search">
+ <h3>
+ <label for="searchInput">Search</label>
+ </h3>
+
+ <form action="/w/index.php" id="searchform">
+ <div id="simpleSearch">
+ <input type="search" name="search" placeholder="Search" title="Search Wikipedia [f]" accesskey="f" id="searchInput" /><input type="hidden" value="Special:Search" name="title" /><input type="submit" name="fulltext" value="Search" title="Search Wikipedia for this text" id="mw-searchButton" class="searchButton mw-fallbackSearchButton" /><input type="submit" name="go" value="Go" title="Go to a page with this exact name if it exists" id="searchButton" class="searchButton" /> </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ <div id="mw-panel">
+ <div id="p-logo" role="banner"><a class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"></a></div>
+ <div class="portal" role="navigation" id='p-navigation' aria-labelledby='p-navigation-label'>
+ <h3 id='p-navigation-label'>Navigation</h3>
+
+ <div class="body">
+ <ul>
+ <li id="n-mainpage-description"><a href="/wiki/Main_Page" title="Visit the main page [z]" accesskey="z">Main page</a></li><li id="n-contents"><a href="/wiki/Portal:Contents" title="Guides to browsing Wikipedia">Contents</a></li><li id="n-featuredcontent"><a href="/wiki/Portal:Featured_content" title="Featured content – the best of Wikipedia">Featured content</a></li><li id="n-currentevents"><a href="/wiki/Portal:Current_events" title="Find background information on current events"> [...]
+ </div>
+ </div>
+ <div class="portal" role="navigation" id='p-interaction' aria-labelledby='p-interaction-label'>
+ <h3 id='p-interaction-label'>Interaction</h3>
+
+ <div class="body">
+ <ul>
+ <li id="n-help"><a href="/wiki/Help:Contents" title="Guidance on how to use and edit Wikipedia">Help</a></li><li id="n-aboutsite"><a href="/wiki/Wikipedia:About" title="Find out about Wikipedia">About Wikipedia</a></li><li id="n-portal"><a href="/wiki/Wikipedia:Community_portal" title="About the project, what you can do, where to find things">Community portal</a></li><li id="n-recentchanges"><a href="/wiki/Special:RecentChanges" title="A list of recent changes in the wiki [r]" acce [...]
+ </div>
+ </div>
+ <div class="portal" role="navigation" id='p-tb' aria-labelledby='p-tb-label'>
+ <h3 id='p-tb-label'>Tools</h3>
+
+ <div class="body">
+ <ul>
+ <li id="t-whatlinkshere"><a href="/wiki/Special:WhatLinksHere/Wikipedia" title="List of all English Wikipedia pages containing links to this page [j]" accesskey="j">What links here</a></li><li id="t-recentchangeslinked"><a href="/wiki/Special:RecentChangesLinked/Wikipedia" title="Recent changes in pages linked from this page [k]" accesskey="k">Related changes</a></li><li id="t-upload"><a href="/wiki/Wikipedia:File_Upload_Wizard" title="Upload files [u]" accesskey="u">Upload file</a [...]
+ </div>
+ </div>
+ <div class="portal" role="navigation" id='p-coll-print_export' aria-labelledby='p-coll-print_export-label'>
+ <h3 id='p-coll-print_export-label'>Print/export</h3>
+
+ <div class="body">
+ <ul>
+ <li id="coll-create_a_book"><a href="/w/index.php?title=Special:Book&bookcmd=book_creator&referer=Wikipedia">Create a book</a></li><li id="coll-download-as-rdf2latex"><a href="/w/index.php?title=Special:Book&bookcmd=render_article&arttitle=Wikipedia&returnto=Wikipedia&oldid=693133248&writer=rdf2latex">Download as PDF</a></li><li id="t-print"><a href="/w/index.php?title=Wikipedia&printable=yes" title="Printable version of this page [p]" accesskey="p"> [...]
+ </div>
+ </div>
+ <div class="portal" role="navigation" id='p-lang' aria-labelledby='p-lang-label'>
+ <h3 id='p-lang-label'>Languages</h3>
+
+ <div class="body">
+ <ul>
+ <li class="interlanguage-link interwiki-ace"><a href="//ace.wikipedia.org/wiki/Wikip%C3%A8dia" title="Wikipèdia – Achinese" lang="ace" hreflang="ace">Acèh</a></li><li class="interlanguage-link interwiki-af"><a href="//af.wikipedia.org/wiki/Wikipedia" title="Wikipedia – Afrikaans" lang="af" hreflang="af">Afrikaans</a></li><li class="interlanguage-link interwiki-ak"><a href="//ak.wikipedia.org/wiki/Wikipedia" title="Wikipedia – Akan" lang="ak" hreflang="ak">Akan</a></li><li class="in [...]
+ <div class='after-portlet after-portlet-lang'><span class="wb-langlinks-edit wb-langlinks-link"><a href="//www.wikidata.org/wiki/Q52#sitelinks-wikipedia" title="Edit interlanguage links" class="wbc-editpage">Edit links</a></span></div> </div>
+ </div>
+ </div>
+ </div>
+ <div id="footer" role="contentinfo">
+ <ul id="footer-info">
+ <li id="footer-info-lastmod"> This page was last modified on 30 November 2015, at 17:09.</li>
+ <li id="footer-info-copyright">Text is available under the <a rel="license" href="//en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">Creative Commons Attribution-ShareAlike License</a><a rel="license" href="//creativecommons.org/licenses/by-sa/3.0/" style="display:none;"></a>;
+additional terms may apply. By using this site, you agree to the <a href="//wikimediafoundation.org/wiki/Terms_of_Use">Terms of Use</a> and <a href="//wikimediafoundation.org/wiki/Privacy_policy">Privacy Policy</a>. Wikipedia® is a registered trademark of the <a href="//www.wikimediafoundation.org/">Wikimedia Foundation, Inc.</a>, a non-profit organization.</li>
+ </ul>
+ <ul id="footer-places">
+ <li id="footer-places-privacy"><a href="//wikimediafoundation.org/wiki/Privacy_policy" title="wmf:Privacy policy">Privacy policy</a></li>
+ <li id="footer-places-about"><a href="/wiki/Wikipedia:About" title="Wikipedia:About">About Wikipedia</a></li>
+ <li id="footer-places-disclaimer"><a href="/wiki/Wikipedia:General_disclaimer" title="Wikipedia:General disclaimer">Disclaimers</a></li>
+ <li id="footer-places-contact"><a href="//en.wikipedia.org/wiki/Wikipedia:Contact_us">Contact Wikipedia</a></li>
+ <li id="footer-places-developers"><a href="https://www.mediawiki.org/wiki/Special:MyLanguage/How_to_contribute">Developers</a></li>
+ <li id="footer-places-mobileview"><a href="//en.m.wikipedia.org/w/index.php?title=Wikipedia&mobileaction=toggle_view_mobile" class="noprint stopMobileRedirectToggle">Mobile view</a></li>
+ </ul>
+ <ul id="footer-icons" class="noprint">
+ <li id="footer-copyrightico">
+ <a href="//wikimediafoundation.org/"><img src="/static/images/wikimedia-button.png" srcset="/static/images/wikimedia-button-1.5x.png 1.5x, /static/images/wikimedia-button-2x.png 2x" width="88" height="31" alt="Wikimedia Foundation"/></a> </li>
+ <li id="footer-poweredbyico">
+ <a href="//www.mediawiki.org/"><img src="/static/1.27.0-wmf.7/resources/assets/poweredby_mediawiki_88x31.png" alt="Powered by MediaWiki" srcset="/static/1.27.0-wmf.7/resources/assets/poweredby_mediawiki_132x47.png 1.5x, /static/1.27.0-wmf.7/resources/assets/poweredby_mediawiki_176x62.png 2x" width="88" height="31" /></a> </li>
+ </ul>
+ <div style="clear:both"></div>
+ </div>
+ <script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {
+mw.loader.state({"ext.globalCssJs.site":"ready","ext.globalCssJs.user":"ready","user":"ready","user.groups":"ready"});mw.loader.load(["ext.cite.a11y","mediawiki.toc","mediawiki.action.view.postEdit","site","mediawiki.user","mediawiki.hidpi","mediawiki.page.ready","mediawiki.searchSuggest","ext.eventLogging.subscriber","ext.wikimediaEvents","ext.navigationTiming","ext.gadget.teahouse","ext.gadget.ReferenceTooltips","ext.gadget.DRN-wizard","ext.gadget.charinsert","ext.gadget.refToolbar","e [...]
+} );</script><script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {
+mw.config.set({"wgBackendResponseTime":207,"wgHostname":"mw1042"}); /* @nomin */
+} );</script>
+ </body>
+</html>
diff --git a/base/build-system/builder/src/test/resources/testData/png/blue_patch.9.png b/build-system/builder/src/test/resources/testData/png/blue_patch.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/blue_patch.9.png
rename to build-system/builder/src/test/resources/testData/png/blue_patch.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/crunched.png b/build-system/builder/src/test/resources/testData/png/crunched.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/crunched.png
rename to build-system/builder/src/test/resources/testData/png/crunched.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/grayscale.png b/build-system/builder/src/test/resources/testData/png/grayscale.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/grayscale.png
rename to build-system/builder/src/test/resources/testData/png/grayscale.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/grayscale.png.crunched b/build-system/builder/src/test/resources/testData/png/grayscale.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/grayscale.png.crunched
rename to build-system/builder/src/test/resources/testData/png/grayscale.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/icon.png b/build-system/builder/src/test/resources/testData/png/icon.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/icon.png
rename to build-system/builder/src/test/resources/testData/png/icon.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/missing_patch.9.png b/build-system/builder/src/test/resources/testData/png/missing_patch.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/missing_patch.9.png
rename to build-system/builder/src/test/resources/testData/png/missing_patch.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_dark_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_inverse_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_solid_light_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_dark_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_bottom_transparent_light_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_share_pack_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_dark_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_light_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_solid_shadow_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_dark_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_inverse_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_solid_light_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_dark_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_stacked_transparent_light_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_dark_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ab_transparent_light_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/activity_title_bar.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_default_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_cab_done_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_check_label_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_disable_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_normal_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_normal_disable_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_small_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_default_transparent_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_disabled_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_dropdown_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_erase_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_global_search_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_normal_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_group_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_off_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_normal_on_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_off_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_dark_pressed_on_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_off.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_normal_on.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_off.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_fulltrans_pressed_on.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_normal_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_light_pressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_off.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_normal_on.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_off.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_pressed_on.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_off.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_normal_on.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_off.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_pressed_on.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_keyboard_key_trans_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_disabled_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_media_player_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_radio_label_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_search_dialog_voice_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_star_label_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_normal_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_off_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_normal_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_toggle_on_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_disabled_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_down_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_disabled_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/btn_zoom_up_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_bottom_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cab_background_top_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_bg.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/cling_button_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_bottom.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_left.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/code_lock_top.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/contact_header_bg.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dark_header.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/day_picker_week_view_dayline_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_bottom_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_divider_horizontal_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_full_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_middle_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dialog_top_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_bright_opaque.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dark_opaque.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_dim_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_horizontal_textfield.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_strong_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_bright_opaque.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_dark_opaque.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/divider_vertical_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_normal_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/dropdown_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/edit_query_background_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_focus_yellow.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_background_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/editbox_dropdown_background_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_close_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_maximized.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_ic_minimized.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/expander_open_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_left_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_label_right_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_default_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/fastscroll_track_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/frame_gallery_thumb_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_selected_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/gallery_unselected_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_focus.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/grid_selector_background_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/highlight_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/ic_notification_overlay.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_rectangle.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/icon_highlight_square.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_gray.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_green.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_red.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_confirm_yellow.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_left_end_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_gray.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_green.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_red.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_confirm_yellow.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/jog_tab_bar_right_end_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_accessory_bg_landscape.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_key_feedback_more_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/keyboard_popup_panel_trans_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/light_header.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_activated_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_divider_horizontal_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_focused_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_longpressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_divider_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_section_header_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selected_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_activated_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_default_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_disabled_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focus.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_focused_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_longpress_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_pressed_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_background_selected_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_multiselect_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/list_selector_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/magnified_region_frame.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_background_fill_parent_width.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_dropdown_panel_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_hardkey_panel_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_popup_panel_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_separator.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menu_submenu_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_focus.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/menuitem_background_solid_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_focus.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_press.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/minitab_lt_unselected_press.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_low_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/notification_bg_normal_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_disabled_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_down_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_input_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_selection_divider.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_disabled_focused.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/numberpicker_up_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_bg_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_focus_blue.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/panel_picture_frame_bg_pressed_blue.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/password_field_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/password_keyboard_background_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/picture_frame.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_bright.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_bottom_medium.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_bright.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_center_medium.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_bright.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_full_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_above_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_inline_error_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_bright.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/popup_top_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_bg_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_primary_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/progress_secondary_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_left_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowdown_right_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_left_right_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickactions_arrowup_right_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_focused_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_normal_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/quickcontact_badge_overlay_pressed_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/recent_dialog_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_accelerated_anim2.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_horizontal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrollbar_handle_vertical.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_primary_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_secondary_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/scrubber_track_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_dropdown_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_plate.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/search_plate_global.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/settings_header_raw.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_default_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_disabled_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_focused_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_ab_pressed_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_default_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_disabled_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_down.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_dropdown_background_up.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_focused_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_press.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_dark_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_pressed_holo_light_am.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/spinner_select.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_header_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_app_background_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_focus.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_normal.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/status_bar_item_background_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/statusbar_background.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_bg_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_activated_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/switch_thumb_pressed_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_bottom_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_left.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_focus_bar_right.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_left.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_press_bar_right.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_pressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_left_v4.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_bar_right_v4.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_focused_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_pressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_selected_v4.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_focused_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_pressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/tab_unselected_v4.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_paste_window.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_side_paste_window.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/text_edit_suggestions_window.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_activated_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_activated_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_default_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_bg_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_default_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_disabled_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_longpress_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_activated_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_default_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_disabled_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_multiline_focused_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_pressed_holo.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_default_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_default.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_empty_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_pressed.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_default_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_right_selected_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_dark.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_search_selected_holo_light.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/textfield_selected.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_medium.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_portrait.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_shadow.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/title_bar_tall.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/toast_frame.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/transportcontrol_bg.9.png.crunched.aapt
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png b/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png
rename to build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched b/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched
rename to build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched
diff --git a/base/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched.aapt b/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched.aapt
similarity index 100%
rename from base/build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched.aapt
rename to build-system/builder/src/test/resources/testData/png/ninepatch/zoom_plate.9.png.crunched.aapt
diff --git a/build-system/changelog.txt b/build-system/changelog.txt
new file mode 100644
index 0000000..f39b57d
--- /dev/null
+++ b/build-system/changelog.txt
@@ -0,0 +1,681 @@
+1.6
+
+- Added support for using UN M.49 area codes in resource directories allowing locale definitions such as es-r419 alongside es-rES.
+- Warnings from libpng about unrecognized sRGB profiles are now reported with info level.
+
+1.4.0-beta1
+
+- Instead of processing java resources during the packaging of the APK,
+ moved this upfront before the obfuscation tasks. This will allow
+ the obfuscation tasks to have a chance to adapt the java resources
+ following packages obfuscation
+- made java resources extraction from libraries incremental tasks.
+- Fixed issue with using jni code in experimental library plugin.
+- Allow platform version to be set separately from compileSdkVersion in experimental plugin.
+- Prevent a consumer of a library removing a resource from that library, which would lead to a runtime NoSuchFieldError.
+- Allow a comma-separated list of serials in ANDROID_SERIAL when installing or running tests
+- Fix installation failure on L+ devices when the APK name contains a space.
+- Fix various issues related to AAPT error output.
+- Vector drawable support for generating PNGs at build time.
+- PNGs are generated for every vector drawable found in a resource directory that does not specify an API version (or specifies a version lower than 21).
+ This only happens if minSdk is below 21.
+- Densities to use can be set using the new "generatedDensities" property in defaultConfig or per-flavor.
+- Multiple modules (e.g. app and lib) now share the same mockable android.jar (for unit testing) which is generated only once. Delete $rootDir/build to regenerate it.
+
+1.3.1
+
+- fixed issue when ZipAlign task would not consume previous' task output when it the file name is customized.
+- fixed packaging of Renderscript with NDK
+- Keep the createDebugCoverageReport task name.
+- Fix customized archiveBaseName handling : see http://b.android.com/182016
+- Fix for http://b.android.com/182433
+
+1.3.0
+- By default, "LICENSE" and "LICENSE.txt" are excluded when creating an APK.
+ This can be changed from the DSL:
+
+ android {
+ packagingOptions.excludes = []
+ }
+
+- New sourceSets task for inspecting the set of all available source sets.
+- Unit tests recognize multi-flavor and per-variant source folders (e.g.
+ testDemoDebug). Android tests recognized multi-flavor source folders.
+- Unit testing improvements
+ * Run javac on main and test sources, even if useJack is true.
+ * Correctly recognize per-build-type dependencies.
+- It's now possible to specify instrumentation test runner arguments in
+ build.gradle (in defaultConfig or per flavor):
+
+ android {
+ defaultConfig {
+ testInstrumentationRunnerArguments size: "medium"
+ }
+
+ productFlavors {
+ foo {
+ testInstrumentationRunnerArguments foo: "bar"
+ }
+ }
+ }
+
+ or from the command line:
+
+ ./gradlew cC \
+ -Pandroid.testInstrumentationRunnerArguments.size=medium \
+ -Pandroid.testInstrumentationRunnerArguments.class=TestA,TestB
+
+- Arbitrary additional AAPT parameters can be set in build.gradle:
+ android {
+ aaptOptions {
+ additionalParameters "--custom_option", "value"
+ }
+ }
+- Resource names are validated before they are merged.
+- When building aar, do not provide automatic @{applicationId} placeholder
+ in manifest merger. Use a different placeholder like @{libApplicationId}
+ and provide a value for it if applicationIds should be baked in the library.
+- Introduce support for incremental compilation support with Jill and Jack. Change is purely
+ internal and does not require DSL change nor can it be disabled.
+
+1.2.0
+- Unit testing improvements
+ * Fixed task dependencies for library projects, so test classes should now
+ be up-to-date when running tests.
+ * Java-style resources are now put on the class path when running unit tests
+ through Gradle.
+ * Unit test configurations (e.g. testCompile) can now depend on AAR
+ artifacts.
+ * Fixes to mockable-android.jar: correct handling of enums, stripping the
+ final modifier of public instance fields.
+ * DSL: new code block for configuring the test tasks:
+ android {
+ testOptions {
+ unitTests.all {
+ jvmArgs '-XX:MaxPermSize=256m' // Or any other gradle option.
+ }
+ }
+ }
+
+ * Variants API: unit-testing variants are now exposed in the API and can be
+ manipulated (e.g. by calling addJavaSourceFoldersToModel).
+ android {
+ unitTestVariants.all { ... }
+ applicationVariants.all { v -> v.unitTestVariant }
+ }
+- Test-only ProGuard files. When running instrumentation tests (i.e. connectedCheck) against
+ a minified variant, the test APK needs to be processed by ProGuard to rename references to code
+ in the main APK. Flags for this ProGuard run (mostly for silencing warnings) can now be specified
+ like this:
+ android {
+ testBuildType = "minified"
+ buildTypes {
+ minified.initWith(buildTypes.debug)
+ minified {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard-rules.pro"
+ testProguardFile "test-proguard-rules.pro"
+ }
+ }
+ }
+
+1.1.0
+- Unit testing support. Unit testing code is run on the local JVM, against a
+ special version of android.jar that is compatible with popular mocking
+ frameworks (e.g. Mockito).
+ * New tasks: test, testDebug/testRelease, testMyFlavorDebug (when using flavors).
+ * New source folders recognized as unit tests:
+ src/test/java, src/testDebug/java, src/testMyFlavor/java etc.
+ * New configurations for adding test-only dependencies, e.g.
+ testCompile 'junit:junit:4.11'
+ testMyFlavorCompile 'some:library:1.0'
+ * New option, android.testOptions.unitTests.returnDefaultValues to control
+ the behaviour of the "mockable" android.jar.
+- Task names that used to contain 'Test', e.g. 'assembleDebugTest' now use
+ 'AndroidTest', e.g. 'assembleDebugAndroidTest'. This is to distinguish them
+ from the unit test tasks, e.g. 'assembleDebugUnitTest'.
+- ProGuard configuration files are no longer applied to the test APK. If
+ minification is enabled, the test APK will be processed by ProGuard only to apply
+ the mapping file generated when minifying the main APK.
+- Fixes and changes to the dependency management:
+ * Properly handle 'provided' and 'package' scopes to do what they should be doing.
+ * 'provided' and 'package' cannot be used with Android Libraries, and will generate an error
+ * sync tested and test dependency trees:
+ - if the same version of an artifact is present in both, it'll get skipped in the test app.
+ - if the version is different it'll generate a build error. Gradle provides mechanism to resolve this.
+- Made queue based cruncher the default png cruncher which should bring significant performance
+ improvement when crunching multiple png files.
+ To turn it off :
+ android {
+ aaptOptions {
+ useNewCruncher false
+ }
+ }
+- Improved DSL reference. See http://developer.android.com/tools/building/plugin-for-gradle.html
+
+1.0.0
+- Final 1.0.0 version
+
+1.0.0-rc2
+- Enhanced manifest merger logging by specifying library coordinates.
+- Allow manifest placeholder to be of any type as long as toString() is implemented.
+- Fixed issue where a library with a low targetSdk would add permissions due to a declared permission in a different manifest.
+- Better fix for issue where embedding a micro app could add new permissions to the main app manifest.
+- Added check for conflict between density splits and resConfig property.
+- test applications are now not using multi-dexing, unless they test a library project.
+- Fixed lint issues 80872, 80834, 60416, 80837
+
+1.0.0-rc1
+- Fixed issue in resources shrinking
+- Fixed issue in publishNonDefault
+- Install task on 21+ devices now does a reinstall again.
+- Density split using aapt 21+ now use --preferred-density allowing for missing density version of some bitmaps.
+- hasProperty() will now work again on read-only wrapper returned by the variant API.
+- Setting applicationId(Suffix) in a Library project will now properly fail.
+- Fixed issue where embedding a micro app could add new permissions to the main app manifest.
+
+0.14.3
+- Variant Specific BuildConfigField/resValue
+ applicationVariants.all { variant ->
+ variant.buildConfigField "int", "VALUE", "1"
+ variant.resValue "string", "name", "value"
+ }
+- Variant (and multi-flavor) specific dependency configuration
+ multi-flavor is all the flavors without the build Type. Only exists for 2+ dimensions of Flavors.
+ Current limitation: Requires defining the configuration manually first:
+ configurations {
+ fooDebugCompile
+ }
+
+ android {
+ productFlavors {
+ foo { ... }
+ }
+ }
+
+ dependencies {
+ fooDebugCompile '...'
+ }
+
+- BuildType/Flavor/Variant configuration for embedding wear app (<name>WearApp)
+- Upgrade to Proguard 5.1
+- Almost 1.0: removed deprecated properties/methods
+ * BuildConfig.PACKAGE_NAME (use new field name)
+ * android.flavorGroups (use new property names)
+ * ProductFlavor.packageName/flavorGroup/testPackageName/renderscriptSupportMode (use new property name)
+ * BuildType.runProguard/packageNameSuffix/jniDebugBuild/renderscriptDebugBuild/zipAlign (use the new property name)
+ * Variant.packageApplication/zipAlign/createZipAlignTask/outputFile/processResources/processManifest (use the variant output)
+
+0.14.2
+- Fix versionNameSuffix support
+- Fix BuildType.initWith to copy shrinkResources flag
+- setup default proguard rule file if none are provided (SDK/tools/proguard/proguard-android.txt)
+- BuildType.pseudoLocalesEnabled flag to include fake locales in apk.
+
+
+0.14.1
+- Fix coverage support.
+- Fix resource shrinking for style references
+- Exclude embedded Wear micro-app from resource shrinking.
+
+0.14.0
+- Proguard and code coverage can now work together
+- Support for pulling coverage data from Android 5.0 devices
+- Env var ANDROID_SERIAL (if present) restrict installation/execution of tests to device matching the serial number
+- Multi-Dex support.
+ * Requires Build-Tools 21.1.0, and Support repository rev 8.
+ * multiDexEnabled = true on defaultConfig, ProductFlavor, or BuildType
+ * Works for minSdkVersion 21+ (native) or <21 (legacy mode, with automatic dependency on com.android.support:multidex:1.0.0)
+ * See multidex samples.
+- Support for automatic removal of unused resources
+ * Off by default for now, enable by setting shrinkResources to true in your
+ release build types. Requires minifyEnabled as well.
+- DSL/API changes:
+ * Renamed a few properties to make things more consistent.
+ - BuildType.runProguard -> minifyEnabled
+ - BuildType.zipAlign -> zipAlignEnabled
+ - BuildType.jniDebugBuild -> jniDebuggable
+ - BuildType.renderscriptDebug -> renderscriptDebuggable
+ - ProductFlavor.renderscriptSupportMode -> renderscriptSupportModeEnabled
+ - ProductFlavor.renderscriptNdkMode -> renderscriptNdkModeEnabled
+ * BuildType/ProductFlavor/SigningConfig queried through the variant and variantFilter API are now read-only.
+ - These objects have always been global and changing them would have side effects in other variants
+ - Merged flavor is still per-variant and can me modified
+ * Variant / VariantOutput API change
+ - Getting the value of the density or ABI filter is done with
+ output.getFilter(com.android.build.OutputFile.DENSITY)
+ output.getFilter(com.android.build.OutputFile.ABI)
+ - See densitySplit sample
+
+0.13.3
+- Added support for selectively allowing dependencies on libraries with incompatible uses-sdk
+- Fixed race condition in lint's resource folder cache which could trigger a build failure
+
+0.13.2
+- Fixed issue in manifest merger that could put wrong uses-sdk node in the manifest.
+
+0.13.1
+- Added ability to merge Instrumentation element from test
+- Fix uninstallAll task
+- Fix issue where bad configuration could lead to no outputs on variants which would prevent evaluation of the project.
+- connectedCheck will now fail if no tests are found.
+
+0.13.0 (2014/09/18)
+- Requires Gradle 2.1
+- It is now possible to provide a manifest for test apps (src/androidTest/AndroidManifest.xml)
+- AndroidManifest files in Library project can now include placeholders. If they cannot be resolved
+ in the libraries, they'll be resolved in the consuming project.
+- AndroidManifest placeholder can now be setup on Product Flavors and Build Types.
+- Variant.getMappingFile() API now allow querying for the proguard mapping file.
+- New Split mechanism for Density and ABI driven multi-apk.
+- Bug fixes:
+ * Fix issue where consumer proguard file (from aars) are ignored on first build
+ * Fixed aar output names so that variants do not overwrite each other
+ * Properly merge declare-styleable to contain all attrs.
+ * Fix whitespace issue in resource strings during resource merge.
+
+0.12.2 (2014/07/16)
+- Fix packaging of wear application
+- Fix issue with ${applicationId} placeholder when build.gradle doesn't customize it.
+- Custom Java code generation steps now part of the source generation steps (fix IDE integration).
+- Move unzipped aar back in each project as a temporary fix for a possible race condition.
+
+0.12.1 (2014/07/01)
+- Fix merging of the package attribute in the manifest.
+
+0.12.0 (2014/06/25)
+- New IDE Model, requires Studio Beta 0.8
+- Fixes in the manifest mergers.
+
+0.11.1:
+- Fix issue with artifact depending on android.jar artifact on MavenCentral.
+- Fix issue with missing custom namespace declaration in generated manifest.
+- Fix issue with validation of permission group in manifest merger.
+
+0.11.0:
+- Updated IDE model, requires Studio 0.6
+- New Manifest merger is now the default merger.
+ - lots of fixes
+ - added ability to add custom placeholders for merger.
+
+- Replaced the various DSL properties used to define the "package
+ name" with an "application ID", to decouple the persistent ID of the
+ application from the implementation package used to contain for
+ example the R and BuildConfig classes.
+ packageName => applicationId
+ packageNameSuffix => applicationIdSuffix
+ testPackageName => testApplicationId
+ testedPackageName => testedApplicationId
+- min/targetSdkVersion on ProductFlavor is now a ApiVersion which contains both an integer and a string.
+- DSL impact: cannot use setter: flavor {minSdkVersion = 9}, must use method: flavor { minSdkVersion 9}, due to a groovy limitation preventing overloaded setters.
+
+- Moved files and folders around in the buildDir for better IDE integration.
+- Generated APK can now be published. Same configuration as libraries with defaultPublishConfig and publishNonDefault flags.
+
+
+0.10.2:
+
+- More fixes on the Manifest merger, including better handling of minSdkVersion.
+- More lint fixes.
+- Fixed incremental dex support (still needs to be enabled)
+
+0.10.1:
+
+- fixed some issues with the new manifest merger. Please keep sending us feedback.
+- fixed issue with uninstall task.
+- lots of lint fixes and new checks. For instance you can use lint to enforce resource prefix in your library.
+
+0.10.0:
+- New manifest merger
+- test code coverage support with Jacoco
+- Pre-dex cache (in rootProject/build). Shared across modules and variants
+- Exploded aar are extracted in a single location (under rootProject/build) to share across all modules using it.
+- Upgraded to Proguard 4.11. Fixed incremental issues.
+- Fixed incremental issues with aidl files.
+
+0.9.2:
+- Aapt-based PNG processor is now default again while we investigate some issues with the old one.
+- flavorGroups have been renamed flavorDimensions and the DSL has been updated. The old DSL is still available until 1.0 at which time it'll be removed.
+
+0.9.1:
+- It's now possible to include a file when there's a conflict during packaging:
+ android.packagingOptions {
+ pickFirst 'META-INF/foo.txt'
+ }
+- New PNG processor.
+ * Should be much faster when processing many files
+ * Fix issue where crunched png are bigger than original file
+ * To revert to the old cruncher: android.aaptOptions.useAaptPngCruncher = true
+- The plugin now enforces that all library dependencies have a unique package name.
+ To disable this you can use android.enforceUniquePackageName = false
+ WARNING: The ability to disable enforcement will disappear in 1.0
+- Fixes:
+ * Generated POM files now have the proper dependencies even if the pom object is manipulated in build.gradle
+ * libraryVariant API now gives access to the list of flavors.
+ * fixed issue where changes to the manifests of libraries didn't trigger a new manifest merge.
+ * BuildConfig.VERSION_NAME is always generated even if the value is not set in the current variant.
+ * BuildConfig is now package in the library. This requires that all your libraries have a unique package name.
+ If you are disabling enforcement of package name, then you should disable packaging of BuildConfig with:
+ android.packageBuildConfig = false
+ WARNING: the ability to disable packaging will disappear in 1.0
+
+0.9.0:
+- Compatible with Gradle 1.10 and 1.11
+- BREAKING CHANGES:
+ * DSL for Library Projects is now the same as for app projects, meaning you can create more Build Types, as well as ProductFlavors.
+ * instrumentTest (both default folders and DSL objects) renamed androidTest
+
+- In preparation for a final variant publishing mechanism, flavors in Libraries can be published alongside the default configuration.
+ The default publishing configuration is configured with
+ android.defaultPublishConfig
+ Default Value is "release", but can be changed to be the name of any variant.
+ To enable publication all the variants, use:
+ android.publishNonDefault = true
+ To use from another project:
+ compile project(path: ':project', configuration: 'flavor1Debug')
+ See 'FlavoredLib' sample.
+ Note that this does not really solve the issue with library being published with 'release' mode always. This is because you would have to manually
+ specify which variant you want to reference in each of the configuration of the app project. A better mechanism will come later.
+- Ability to skip some variants. Create a closure to control which variants should be created.
+ android.variantFilter { variant ->
+ ...
+ }
+
+ The object passed to the closure implements the following methods:
+ public void setIgnore(boolean ignore);
+ @NonNull
+ public ProductFlavor getDefaultConfig();
+ @NonNull
+ public BuildType getBuildType();
+ @NonNull
+ public List<ProductFlavor> getFlavors();
+ To skip a variant, call setIgnore(false)
+- Library dependency scopes are now 'provided', 'compile', 'publish'.
+ The 'publish' and 'apk' configurations don't extend 'compile' anymore but the composite configurations are still properly setup.
+- Fix issue where variant specific source folders where not used for java compilation.
+- Fix for some Renderscript support mode compatibility issues. Requires Build Tools 19.0.3
+- Lots of misc fixes.
+
+0.8.3:
+
+- Fix Studio integration regression.
+
+0.8.2:
+- Fix incremental issue with build config fields and generated res values.
+
+0.8.1:
+- Added the ability to create resource values through the DSL.
+ You can now use 'resValue <type>,<name>,<value>' on build types and product flavors
+ the same way you can use buildConfigField.
+- Fixed package renaming in activity-alias:targetActivity
+- Variant API improvements:
+ * packageName returns the variant's package name
+ * versionCode returns the (app/test) variant's versionCode
+ * versionName returns the (app/test/) variant's versionName. Can return null.
+
+0.8.0
+
+- Support for Gradle 1.10
+- Requires Build-Tools 19.0.0+
+- Fixed issue 64302: Add renderscript support mode jar to the dependencies in the IDE model.
+- Fixed issue 64094: buildConfigField can now replace previous values inside the same type/flavors.
+- Add support for NDK prebuilts in library projects.
+- Parallelize pre-dexing to speed up clean builds.
+- Incremental dexing re-enabled (though it'll be automatically disabled in some builds for some cases that dx doesn't support yet.)
+- Added 'provided' dependency scope for compile only (not packaged) dependencies.
+ Additional scope per buildtype and flavors are also available (debugProvided, myFlavorProvided,etc...)
+- Fix NDK on windows.
+- Variant API improvements:
+ * getPreBuild() returns the prebuild task for the variant
+ * getSourceSets() returns the sorted sourcesets for the task, from lower to higher priority
+ * createZipAlignTask(String taskName, File inputFile, File outputFile)
+ This creates and return a new zipalign task. Useful if you have a custom plugin providing custom signing of APKs.
+ This also makes the assemble task depend on the new zipalign task, and wires variant.getOutputFile() to return the result of the zipalign task.
+ * project.android.registerJavaArtifact() now receives a Configuration object to pass the dependencies to the IDE. See artifactApi sample.
+- New "lintVital" task, run automatically as part of assembling release variants, which checks only fatal-severity issues
+- Replace Java parser in lint with ECJ; much faster and fixes bug where lint could hang on certain source constructs
+- Lint HTML report now writes links to source files and images as URLs relative to the report location
+
+0.7.3
+
+- Rebuild 0.7.2 to work with Java6
+
+0.7.2
+
+- Fix issue with Proguard.
+- Add packagingOptions support in Library projects.
+- Solve issue with local jar when testing library projects.
+- Fix bug with variant.addJavaSourceFoldersToModel
+- Add jniLibs folder to source sets for prebuilt .so files.
+- Lint fixes:
+ * fix RTL detector
+ * fix HTML report to have valid HTML
+
+0.7.1
+
+- DSL to exclude some files coming from jar dependencies
+ android {
+ packagingOptions {
+ exclude 'META-INF/LICENSE.txt'
+ }
+ }
+
+
+0.7.0
+- Requires Gradle 1.9
+- You can now have a variant specific source folder if you have flavors.
+ Only for app (not library or test). Name is src/flavorDebug/... or src/flavor1Flavor2Debug/
+ (note the camelcase naming, with lower case for first letter).
+ Its components (res, manifest, etc...) have higher priority than components from build type
+ or flavors.
+ There is also a "flavor combination" source folder available when more than one
+ flavor dimension is used.
+ For instance src/flavor1Flavor2/
+ Note that this is for all combinations of *all* dimensions.
+- Build config improvements and DSL changes.
+ The previous DSL proprety:
+ buildConfigLine "<value>"
+ has changed to
+ buildConfigField "<type>", "<name>", "<value>"
+ You can only add a single field at a time.
+ This allows override a field (see 'basic' sample)
+ Also, BuildConfig now automatically contains constants for
+ PACKAGE_NAME, VERSION_CODE, VERSION_NAME, BUILD_TYPE, FLAVOR as well as FLAVOR_<group> if there are several flavor dimensions.
+- Switch to ProGuard 4.10
+ - Added ability to test proguarded (obfuscated) apps.
+- New option on product Flavor (and defaultConfig) allow filtering of resources through the -c option of aapt
+ You can pass single or multiple values through the DSL. All values from the default config and flavors get combined and passed to aapt.
+ The DSL is
+ resConfig "en"
+ or
+ resConfigs "nodpi","hdpi"
+
+- Jar files are now pre-dexed for faster dexing.
+ Incremental dexing is disabled by default as it can lead to increased dex file size.
+- First pass at NDK integration. See the samples.
+- API to add new generated source folders:
+ variant.addJavaSourceFoldersToModel(sourceFolder1, sourceFolders2,...)
+ This adds the source folder to the model (for IDE support).
+ Another API:
+ variant.registerJavaGeneratingTask(task, sourceFolder1, sourceFolders2,...)
+ This automatically adds the dependency on the task, sets up the JavaCompile task inputs and propagates
+ the folders to the model for IDE integration.
+ See sample 'genFolderApi'
+- API to add extra artifacts on variants. This will allow to register Java or Android artifacts, for instance
+ for alternative test artifacts.
+ See sample 'artifactApi' for the API (sample is not meant to be used, it's for testing).
+- Revamped lint integration. Lint is now run as part of the check task, and will analyze all variants and then
+ merge the results and create a report which lists which variants each error applies to (unless an error
+ applies to all variants). You can also run lint on a specific variant, e.g. lintDebug or lintFreeRelease.
+ Lint will no longer report errors in AAR libraries. This version of the plugin also picks up some new lint
+ checks.
+ A new DSL allows configuration of lint from build.gradle. This is read and used in Studio
+- Fixed issue with parentActivityName when handling different package name in the manifest merger.
+- Allow files inside META-INF/ from jars to be packaged in the APK.
+- Disabled incremental dx mode as it can lead to broken dex files.
+
+0.6.3
+- Fixed ClassNotFoundException:MergingException introduced in 0.6.2
+
+0.6.2
+- Lint now picks up the SDK home from sdk.dir in local.properties
+- Error message shown when using an unsupported version of Gradle now explains how to update the Gradle wrapper
+- Merged resource files no longer place their source markers into the R file as comments
+- Project path can contain '--' (two dashes)
+- Internal changes to improve integration with Android Studio
+
+0.6.1
+
+- Fixed issues with lint task found in 0.6.0
+
+0.6.0
+
+- Enabled support for Gradle 1.8
+- Gradle 1.8 is now the minimum supported version
+- Default encoding for compiling Java code is UTF-8
+- Users can now specify the encoding to use to compile Java code
+- Fixed Gradle 1.8-specific bugs
+ - Importing projects with missing dependencies was broken
+ - Compiling projects with AIDL files was broken
+
+0.5.7
+
+- Proguard support for libraries.
+ Note the current DSL property 'proguardFiles' for library now sets the proguard rule file used when proguarding the library code.
+ The new property 'consumerProguardFiles' is used to package a rule file inside an aar.
+- Improved IDE support, including loading project with broken dependencies and anchor task to generate Java code
+- New hook tasks: preBuild and prebuild<VariantName>
+- First lint integration. This is a work in progress and therefore the lint task is not added to the check task.
+- Enable compatibility with 1.8
+
+0.5.6
+
+- Enabled support for 1.7
+
+0.5.5
+
+- Fix issue preventing to use Build Tools 18.0.1
+- access to the variants container don't force creating the task.
+ This means android.[application|Library|Test]Variants will be empty
+ during the evaluation phase. To use it, use .all instead of .each
+- Only package a library's own resources in its aar.
+- Fix incremental issues in the resource merger.
+- Misc bug fixes.
+
+0.5.4
+
+- Fixed incremental compilation issue with declare-styleable
+
+0.5.3
+
+- Fixed a crashing bug in PrepareDependenciesTask
+
+0.5.2
+
+- Better error reporting for cmd line tools, especially
+ if run in parallel in spawned threads
+- Fixed an issue due to windows path in merged resource files.
+
+0.5.1
+
+- Fixed issue in the dependency checker.
+
+0.5.0:
+
+- IDE Model is changed and is not compatible with earlier version! A new IDE
+ will required.
+- Fixed IDE model to contain the output file even if it's customized
+ through the DSL. Also fixed the DSL to get/set the output file on the
+ variant object so that it's not necessary to use variant.packageApplication
+ or variant.zipAlign
+- Fixed dependency resolution so that we resolved the combination of (default config,
+ build types, flavor(s)) together instead of separately.
+- Fixed dependency for tests of library project to properly include all the dependencies
+ of the library itself.
+- Fixed case where two dependencies have the same leaf name.
+- Fixed issue where proguard rules file cannot be applied on flavors.
+
+0.4.3:
+
+- Enabled crunching for all png files, not just .9.png
+- Fixed dealing with non resource files in res/ and assets/
+- Fixed crash when doing incremental aidl compilation due to broken method name (ah the joy of Groovy...)
+- Cleaned older R classes when the app package name has changed.
+
+0.4.2
+
+* Fixed incremental support for resource merging.
+* Fixed issue where all pngs would be processed in parallel with no limit
+ on the number of thread used, leading to failure to run aapt.
+* Fixed ignoreAsset support in aaptOptions
+* Added more logging on failure to merge manifests.
+* Added flavor names to the TestServer API.
+
+0.4.1:
+
+* Renamed 'package' scope to 'apk'
+ - variants are 'debugApk', 'releaseApk', 'flavor1Apk', etc...
+ - Now properly supported at build to allow package-only dependencies.
+* Only Jar dependencies can be package-only. Library projects must be added to the compile scope.
+* Fixed [application|library|test]Variants API (always returned empty on 0.4)
+* Fixed issue in Proguard where it would complain about duplicate Manifests.
+
+0.4
+
+* System requirements:
+ - Gradle 1.6+
+ - Android Build Tools 16.0.2+
+* Rename deviceCheck into connectedDevice
+* API for 3rd party Device Providers and Test Servers to run and deploy tests. API is @Beta
+* Support for ProGuard 4.9
+ - enable with BuildType.runProguard
+ - add proguard config files with BuiltType.proguardFile or ProductFlavor.proguardFile
+ - default proguard files accessible through android.getDefaultProguardFile(name) with name
+ being 'proguard-android.txt' or 'proguard-android-optimize.txt'
+* Implements Gradle 1.6 custom model for IDE Tooling support
+* Fixes:
+ - Fix support for subfolders in assets/
+ - Fix cases where Android Libraries have local Jars dependencies
+ - Fix renaming of package through DSL to ensure resources are compiled in the new namespace
+ - Fix DSL to add getSourceSets on the "android" extension.
+ - DSL to query variants has changed to applicationVariants and libraryVariants (depending on the plugin)
+ Also both plugin have testVariants (tests are not included in the default collection).
+
+0.3
+
+* System requirements:
+ - Gradle 1.3+ (tested on 1.3/1.4)
+ - Android Platform Tools 16.0.2+
+* New Features:
+ - Renderscript support.
+ - Support for multi resource folders. See 'multires' sample.
+ * PNG crunch is now done incrementally and in parallel.
+ - Support for multi asset folders.
+ - Support for asset folders in Library Projects.
+ - Support for versionName suffix provided by the BuildType.
+ - Testing
+ * Default sourceset for tests now src/instrumentTest (instrumentTest<Name> for flavors)
+ * Instrumentation tests now:
+ - started from "deviceCheck" instead of "check"
+ - run on all connected devices in parallel.
+ - break the build if any test fails.
+ - generate an HTML report for each flavor/project, but also aggregated.
+ * New plugin 'android-reporting' to aggregate android test results across projects. See 'flavorlib' sample.
+ - Improved DSL:
+ * replaced android.target with android.compileSdkVersion to make it less confusing with targetSdkVersion
+ * signing information now a SigningConfig object reusable across BuildType and ProductFlavor
+ * ability to relocate a full sourceSet. See 'migrated' sample.
+ * API to manipulate Build Variants.
+* Fixes:
+ - Default Java compile target set to 1.6.
+ - Fix generation of R classes in case libraries share same package name as the app project.
+
+0.2
+
+* Fixed support for windows.
+* Added support for customized sourceset. (http://tools.android.com/tech-docs/new-build-system/using-the-new-build-system#TOC-Working-with-and-Customizing-SourceSets)
+* Added support for dependency per configuration.
+* Fixed support for dependency on local jar files.
+* New samples "migrated" and "flavorlib"
+
+0.1: initial release
diff --git a/base/build-system/docs/README b/build-system/docs/README
similarity index 100%
rename from base/build-system/docs/README
rename to build-system/docs/README
diff --git a/build-system/docs/build.gradle b/build-system/docs/build.gradle
new file mode 100755
index 0000000..0f66a25
--- /dev/null
+++ b/build-system/docs/build.gradle
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * 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.
+ */
+
+import org.apache.tools.ant.filters.ReplaceTokens
+import org.gradle.build.docs.AssembleSamplesDocTask
+import org.gradle.build.docs.Docbook2Xhtml
+import org.gradle.build.docs.UserGuideTransformTask
+import org.gradle.build.docs.dsl.docbook.AssembleDslDocTask
+import org.gradle.build.docs.dsl.source.ExtractDslMetaDataTask
+import org.gradle.build.docs.dsl.source.GenerateDefaultImportsTask
+import org.gradle.build.docs.releasenotes.*
+import org.gradle.build.docs.releasenotes.checks.*
+
+evaluationDependsOn(':base:gradle')
+evaluationDependsOn(':base:gradle-core')
+evaluationDependsOn(':base:builder')
+
+apply plugin: 'groovy'
+apply plugin: 'base'
+apply plugin: 'pegdown'
+apply plugin: 'jsoup'
+apply plugin: 'javascript-base'
+
+def generatedResourcesDir = file("$buildDir/generated-resources/main")
+
+version = rootProject.buildVersion
+
+repositories {
+ maven { url 'https://repo.gradle.org/gradle/libs' }
+
+ javaScript.googleApis()
+
+ ivy {
+ name "Google Fonts"
+ url "http://themes.googleusercontent.com/static/fonts/"
+ layout 'pattern', {
+ artifact '[organisation]/v[revision](/[classifier])(.[ext])'
+ ivy '[organisation]/v[revision]/ivy(.[ext])'
+ }
+ }
+
+ maven { url 'https://repo.gradle.org/gradle/gradle-build-internal' }
+}
+
+configurations {
+ groovydocGroovy {}
+ userGuideStyleSheets
+ userGuideTask
+ fonts
+}
+
+dependencies {
+ userGuideTask 'xalan:xalan:2.7.1', 'org.codehaus.groovy:groovy-all:2.3.6'
+ userGuideTask module('xhtmlrenderer:xhtmlrenderer:R8rc1') {
+ dependency 'itext:itext:2.0.8 at jar'
+ }
+ userGuideTask 'xslthl:xslthl:2.0.1 at jar'
+
+ userGuideStyleSheets 'docbook:docbook-xsl:1.75.2 at zip'
+
+ fonts \
+ "lato:regular:6:v0SdcGFAl2aezM9Vq_aFTQ at ttf",
+ "lato:regular-italic:6:LqowQDslGv4DmUBAfWa2Vw at ttf",
+ "lato:bold:6:DvlFBScY1r-FMtZSYIYoYw at ttf",
+ "lato:bold-italic:6:HkF_qI1x_noxlxhrhMQYEKCWcynf_cDxXwCLxiixG1c at ttf",
+ "roboto:regular:14:zN7GBFwfMP4uA6AR0HCoLQ at ttf",
+ "ubuntumono:regular:3:ViZhet7Ak-LRXZMXzuAfkZ0EAVxt0G0biEntp43Qt6E at ttf",
+ "ubuntumono:regular-italic:3:KAKuHXAHZOeECOWAHsRKA-LrC4Du4e_yfTJ8Ol60xk0 at ttf",
+ "ubuntumono:bold:3:ceqTZGKHipo8pJj4molytp_TkvowlIOtbR7ePgFOpF4 at ttf",
+ "ubuntumono:bold-italic:3:n_d8tv_JOIiYyMXR4eaV9WsGzsqhEorxQDpu60nfWEc at ttf"
+
+ groovydocGroovy 'org.codehaus.groovy:groovy-all:2.3.6'
+
+ testCompile 'org.codehaus.groovy:groovy-all:2.3.6'
+ testCompile "org.pegdown:pegdown:1.1.0"
+ testCompile 'org.jsoup:jsoup:1.6.3'
+ testCompile "org.gebish:geb-spock:0.9.3"
+ testCompile 'org.seleniumhq.selenium:selenium-htmlunit-driver:2.42.2'
+ testCompile project(":base:gradle"), project(":base:gradle-core"), project(":base:builder")
+}
+
+ext {
+ srcDocsDir = file('src/fromGradle/docs')
+ userguideSrcDir = new File(srcDocsDir, 'userguide')
+ dslSrcDir = new File(srcDocsDir, 'dsl')
+ docsDir = file("$buildDir/docs")
+ userguideDir = new File(docsDir, 'userguide')
+ distDocsDir = new File(buildDir, 'distDocs')
+ docbookSrc = new File(project.buildDir, 'src')
+}
+
+ext.outputs = [:]
+outputs.distDocs = files(distDocsDir) {
+ builtBy 'distDocs'
+}
+outputs.docs = files(docsDir) {
+ builtBy 'javadocAll', 'groovydocAll', 'userguide', 'dslHtml', 'releaseNotes'
+}
+
+tasks.withType(Docbook2Xhtml) {
+ dependsOn userguideStyleSheets
+ classpath = configurations.userGuideTask
+ stylesheetsDir = userguideStyleSheets.destinationDir
+}
+tasks.withType(UserGuideTransformTask) {
+ dependsOn dslDocbook
+ linksFile = dslDocbook.linksFile
+ websiteUrl = 'http://www.gradle.org'
+
+ if (name in ["pdfUserguideDocbook", "userguideFragmentSrc"]) {
+ // These will only be valid for releases, but that's ok
+ javadocUrl = "http://www.gradle.org/docs/${->version}/javadoc"
+ groovydocUrl = "http://www.gradle.org/docs/${->version}/groovydoc"
+ dsldocUrl = "http://www.gradle.org/docs/${->version}/dsl"
+ } else {
+ javadocUrl = '../javadoc'
+ groovydocUrl = '../groovydoc'
+ dsldocUrl = '../dsl'
+ }
+}
+tasks.withType(AssembleDslDocTask) {
+ classDocbookDir = dslSrcDir
+}
+
+task configureCss << {
+ def images = fileTree(dir: "$srcDocsDir/css/images", include: "*.*").files.collectEntries {
+ [it.name, it.bytes.encodeBase64().toString()]
+ }
+
+ def fonts = configurations.fonts.resolvedConfiguration.resolvedArtifacts.collectEntries {
+ def id = it.moduleVersion.id
+ ["${id.group}-${id.name}".toString(), it.file.bytes.encodeBase64().toString()]
+ }
+
+ ext.tokens = images + fonts
+ css.inputs.property 'tokens', tokens
+ css.filter ReplaceTokens, tokens: tokens
+}
+
+task css(type: Sync, dependsOn: configureCss) {
+ into "$buildDir/css"
+ from "$srcDocsDir/css"
+ include "*.css"
+}
+
+ext.cssFiles = fileTree(css.destinationDir) {
+ builtBy css
+}
+
+task userguideStyleSheets(type: Copy) {
+ File stylesheetsDir = new File(srcDocsDir, 'stylesheets')
+ into new File(buildDir, 'stylesheets')
+ from(stylesheetsDir) {
+ include '*.xsl'
+ }
+ from(cssFiles)
+ from({ zipTree(configurations.userGuideStyleSheets.singleFile) }) {
+ // Remove the prefix
+ eachFile { fcd -> fcd.path = fcd.path.replaceFirst('^docbook-xsl-[0-9\\.]+/', '') }
+ }
+}
+
+task samplesDocbook(type: AssembleSamplesDocTask) {
+ include '**/readme.xml'
+ destFile = new File(docbookSrc, 'samplesList.xml')
+}
+
+task samplesDocs(type: Docbook2Xhtml) {
+ source samplesDocbook
+ stylesheetName = 'standaloneHtml.xsl'
+}
+
+task dslMetaData(type: ExtractDslMetaDataTask) {
+ source { groovydocAll.source }
+ destFile = new File(docbookSrc, 'dsl-meta-data.bin')
+}
+
+task dslDocbook(type: AssembleDslDocTask, dependsOn: [dslMetaData]) {
+ inputs.files fileTree(dir: dslSrcDir, includes: ['*.xml'])
+ sourceFile = new File(dslSrcDir, 'dsl.xml')
+ classMetaDataFile = dslMetaData.destFile
+
+ pluginsMetaDataFile = new File(dslSrcDir, 'plugins.xml')
+ destFile = new File(docbookSrc, 'dsl.xml')
+ linksFile = new File(docbookSrc, 'api-links.bin')
+}
+
+task dslStandaloneDocbook(type: UserGuideTransformTask, dependsOn: [dslDocbook]) {
+ sourceFile = dslDocbook.destFile
+ snippetsDir = buildDir
+ destFile = new File(docbookSrc, 'dsl-standalone.xml')
+ dsldocUrl = '.'
+}
+
+task defaultImports(type: GenerateDefaultImportsTask, dependsOn: dslMetaData) {
+ metaDataFile = dslMetaData.destFile
+ destFile = new File(generatedResourcesDir, "default-imports.txt")
+ // These are part of the API, but not the DSL
+ excludePackage 'org.gradle.tooling.**'
+ excludePackage 'org.gradle.testfixtures.**'
+
+ // Tweak the imports due to some inconsistencies introduced before we automated the default-imports generation
+ excludePackage 'org.gradle.plugins.ide.eclipse.model'
+ excludePackage 'org.gradle.plugins.ide.idea.model'
+ excludePackage 'org.gradle.api.tasks.testing.logging'
+ extraPackage 'org.gradle.util'
+
+ // TODO - rename some incubating types to remove collisions and then remove these exclusions
+ excludePackage 'org.gradle.plugins.binaries.model'
+ excludePackage 'org.gradle.ide.cdt.model'
+}
+
+task dslHtml(type: Docbook2Xhtml) {
+ source dslStandaloneDocbook
+ destDir = new File(docsDir, 'dsl')
+ stylesheetName = 'dslHtml.xsl'
+ resources = cssFiles + fileTree(dslSrcDir) {
+ include '*.js'
+ }
+}
+
+task dslHtmlZip(type: Zip) {
+ dependsOn dslHtml
+ from dslHtml.destDir
+ destinationDir docsDir
+ baseName 'gradle-dsl'
+}
+
+def javaApiUrl = "http://docs.oracle.com/javase/1.6.0/docs/api/"
+def groovyApiUrl = "http://groovy.codehaus.org/gapi/"
+def publicGroovyProjects = [
+ project(":base:gradle"),
+ project(":base:gradle-core"),
+ project(":base:builder")
+]
+
+task javadocAll(type: Javadoc) {
+// ext.stylesheetFile = file("src/docs/css/javadoc.css")
+// inputs.file stylesheetFile
+
+ group = 'documentation'
+ options.encoding = 'utf-8'
+ options.docEncoding = 'utf-8'
+ options.charSet = 'utf-8'
+ if (JavaVersion.current().isJava8Compatible()) {
+ options.addStringOption 'Xdoclint:none', '-quiet'
+ }
+// options.addStringOption "stylesheetfile", stylesheetFile.absolutePath
+ source publicGroovyProjects.collect {project -> project.sourceSets.main.allJava }
+ destinationDir = new File(docsDir, 'javadoc')
+ classpath = files(publicGroovyProjects.collect {project -> [project.sourceSets.main.compileClasspath, project.sourceSets.main.output] })
+// include 'org/gradle/*'
+// include 'org/gradle/api/**'
+ include 'com/android/build/gradle/**'
+ include 'com/android/build/gradle/internal/dsl/**'
+ include 'com/android/builder/**'
+// exclude '**/internal/**'
+
+ exclude 'com/android/build/gradle/shrinker/parser/ProguardLexer*'
+ options.links(javaApiUrl, groovyApiUrl, "http://maven.apache.org/ref/2.2.1/maven-core/apidocs",
+ "http://maven.apache.org/ref/2.2.1/maven-model/apidocs")
+ doFirst {
+ title = "Android plugin API $version"
+ }
+}
+
+task configureGroovydocAll {
+ doFirst {
+ project.configure(groovydocAll) {
+ [javaApiUrl, groovyApiUrl].each {
+ link(it, *(new URL("$it/package-list").text.tokenize("\n")))
+ }
+ docTitle = "Gradle API $version"
+ windowTitle = "Gradle API $version"
+ footer = "Gradle API $version"
+ }
+ }
+}
+
+task groovydocAll(type: Groovydoc, dependsOn: configureGroovydocAll) {
+ group = 'documentation'
+ source publicGroovyProjects.collect {project ->
+ def main = project.sourceSets.main
+ try {
+ main.groovy + main.java
+ } catch (MissingPropertyException e) {
+ main.java
+ }
+ }
+ destinationDir = new File(docsDir, 'groovydoc')
+
+ // Groovydoc runs static initializers, and at least ProjectBuilder's initializers depend on runtime classes
+ // http://jira.codehaus.org/browse/GROOVY-7096
+ classpath = files(publicGroovyProjects.collect {project -> [project.sourceSets.main.runtimeClasspath, project.sourceSets.main.output] })
+
+ includes = javadocAll.includes
+ excludes = javadocAll.excludes
+ doFirst {
+ windowTitle = "Gradle API $version"
+ docTitle = windowTitle
+ }
+ groovyClasspath = configurations.groovydocGroovy
+ doLast {
+ def index = new File(destinationDir, "index.html")
+ index.text = index.text.replace("{todo.title}", windowTitle) // workaround groovydoc bug
+ }
+}
+
+task docsAll {
+ dependsOn groovydocAll, dslHtml
+ description = 'Generates all documentation'
+ group = 'documentation'
+}
+
+// Make sure all the references are valid and up-to-date. Hopefully this will mark the build
+// red in Jenkins if they get out of sync.
+check.dependsOn dslHtml
diff --git a/base/build-system/docs/src/fromGradle/docs/css/base.css b/build-system/docs/src/fromGradle/docs/css/base.css
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/base.css
rename to build-system/docs/src/fromGradle/docs/css/base.css
diff --git a/base/build-system/docs/src/fromGradle/docs/css/docs.css b/build-system/docs/src/fromGradle/docs/css/docs.css
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/docs.css
rename to build-system/docs/src/fromGradle/docs/css/docs.css
diff --git a/base/build-system/docs/src/fromGradle/docs/css/dsl.css b/build-system/docs/src/fromGradle/docs/css/dsl.css
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/dsl.css
rename to build-system/docs/src/fromGradle/docs/css/dsl.css
diff --git a/base/build-system/docs/src/fromGradle/docs/css/images/gradle-logo_25o.gif b/build-system/docs/src/fromGradle/docs/css/images/gradle-logo_25o.gif
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/images/gradle-logo_25o.gif
rename to build-system/docs/src/fromGradle/docs/css/images/gradle-logo_25o.gif
diff --git a/base/build-system/docs/src/fromGradle/docs/css/images/studio_logo_background.png b/build-system/docs/src/fromGradle/docs/css/images/studio_logo_background.png
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/images/studio_logo_background.png
rename to build-system/docs/src/fromGradle/docs/css/images/studio_logo_background.png
diff --git a/base/build-system/docs/src/fromGradle/docs/css/javadoc.css b/build-system/docs/src/fromGradle/docs/css/javadoc.css
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/javadoc.css
rename to build-system/docs/src/fromGradle/docs/css/javadoc.css
diff --git a/base/build-system/docs/src/fromGradle/docs/css/print.css b/build-system/docs/src/fromGradle/docs/css/print.css
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/print.css
rename to build-system/docs/src/fromGradle/docs/css/print.css
diff --git a/base/build-system/docs/src/fromGradle/docs/css/release-notes.css b/build-system/docs/src/fromGradle/docs/css/release-notes.css
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/release-notes.css
rename to build-system/docs/src/fromGradle/docs/css/release-notes.css
diff --git a/base/build-system/docs/src/fromGradle/docs/css/userguide.css b/build-system/docs/src/fromGradle/docs/css/userguide.css
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/css/userguide.css
rename to build-system/docs/src/fromGradle/docs/css/userguide.css
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.AppExtension.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.AppExtension.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.AppExtension.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.AppExtension.xml
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.BaseExtension.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.BaseExtension.xml
new file mode 100644
index 0000000..52a1ab3
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.BaseExtension.xml
@@ -0,0 +1,61 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>aaptOptions</td></tr>
+ <tr><td>adbOptions</td></tr>
+ <tr><td>buildToolsVersion</td></tr>
+ <tr><td>buildTypes</td></tr>
+ <tr><td>compileOptions</td></tr>
+ <tr><td>compileSdkVersion</td></tr>
+ <tr><td>compileSdkVersion</td></tr>
+ <tr><td>defaultConfig</td></tr>
+ <tr><td>defaultPublishConfig</td></tr>
+ <tr><td>dexOptions</td></tr>
+ <tr><td>flavorDimensionList</td></tr>
+ <tr><td>generatePureSplits</td></tr>
+ <tr><td>jacoco</td></tr>
+ <tr><td>dataBinding</td></tr>
+ <tr><td>lintOptions</td></tr>
+ <tr><td>ndkDirectory</td></tr>
+ <tr><td>packagingOptions</td></tr>
+ <tr><td>productFlavors</td></tr>
+ <tr><td>publishNonDefault</td></tr>
+ <tr><td>resourcePrefix</td></tr>
+ <tr><td>sdkDirectory</td></tr>
+ <tr><td>signingConfigs</td></tr>
+ <tr><td>sourceSets</td></tr>
+ <tr><td>splits</td></tr>
+ <tr><td>testOptions</td></tr>
+ <tr><td>variantFilter</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>aaptOptions</td></tr>
+ <tr><td>adbOptions</td></tr>
+ <tr><td>buildTypes</td></tr>
+ <tr><td>compileOptions</td></tr>
+ <tr><td>dataBinding</td></tr>
+ <tr><td>defaultConfig</td></tr>
+ <tr><td>dexOptions</td></tr>
+ <tr><td>flavorDimensions</td></tr>
+ <tr><td>jacoco</td></tr>
+ <tr><td>lintOptions</td></tr>
+ <tr><td>packagingOptions</td></tr>
+ <tr><td>productFlavors</td></tr>
+ <tr><td>signingConfigs</td></tr>
+ <tr><td>sourceSets</td></tr>
+ <tr><td>splits</td></tr>
+ <tr><td>testOptions</td></tr>
+ <tr><td>useLibrary</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.LibraryExtension.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.LibraryExtension.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.LibraryExtension.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.LibraryExtension.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestExtension.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestExtension.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestExtension.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestExtension.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestedExtension.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestedExtension.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestedExtension.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.TestedExtension.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceDirectorySet.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceDirectorySet.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceDirectorySet.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceDirectorySet.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceFile.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceFile.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceFile.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceFile.xml
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceSet.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceSet.xml
new file mode 100644
index 0000000..8a09a35
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.AndroidSourceSet.xml
@@ -0,0 +1,33 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>aidl</td></tr>
+ <tr><td>assets</td></tr>
+ <tr><td>compileConfigurationName</td></tr>
+ <tr><td>java</td></tr>
+ <tr><td>jni</td></tr>
+ <tr><td>jniLibs</td></tr>
+ <tr><td>manifest</td></tr>
+ <tr><td>manifest</td></tr>
+ <tr><td>name</td></tr>
+ <tr><td>packageConfigurationName</td></tr>
+ <tr><td>providedConfigurationName</td></tr>
+ <tr><td>renderscript</td></tr>
+ <tr><td>res</td></tr>
+ <tr><td>resources</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>setRoot</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.VariantFilter.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.VariantFilter.xml
new file mode 100644
index 0000000..8aa6659
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.api.VariantFilter.xml
@@ -0,0 +1,22 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>defaultConfig</td></tr>
+ <tr><td>buildType</td></tr>
+ <tr><td>flavors</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>setIgnore</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.CompileOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.CompileOptions.xml
new file mode 100644
index 0000000..523cdb8
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.CompileOptions.xml
@@ -0,0 +1,21 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>encoding</td></tr>
+ <tr><td>sourceCompatibility</td></tr>
+ <tr><td>targetCompatibility</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.coverage.JacocoExtension.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.coverage.JacocoOptions.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.coverage.JacocoExtension.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.coverage.JacocoOptions.xml
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AaptOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AaptOptions.xml
new file mode 100644
index 0000000..3676012
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AaptOptions.xml
@@ -0,0 +1,26 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>additionalParameters</td></tr>
+ <tr><td>cruncherEnabled</td></tr>
+ <tr><td>failOnMissingConfigEntry</td></tr>
+ <tr><td>ignoreAssets</td></tr>
+ <tr><td>ignoreAssetsPattern</td></tr>
+ <tr><td>noCompress</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>noCompress</td></tr>
+ <tr><td>additionalParameters</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AbiSplitOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AbiSplitOptions.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AbiSplitOptions.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AbiSplitOptions.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AdbOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AdbOptions.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AdbOptions.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.AdbOptions.xml
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.BuildType.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.BuildType.xml
new file mode 100644
index 0000000..ef3fc60
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.BuildType.xml
@@ -0,0 +1,44 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>applicationIdSuffix</td></tr>
+ <tr><td>debuggable</td></tr>
+ <tr><td>embedMicroApp</td></tr>
+ <tr><td>jniDebuggable</td></tr>
+ <tr><td>minifyEnabled</td></tr>
+ <tr><td>multiDexEnabled</td></tr>
+ <tr><td>name</td></tr>
+ <tr><td>pseudoLocalesEnabled</td></tr>
+ <tr><td>renderscriptDebuggable</td></tr>
+ <tr><td>renderscriptOptimLevel</td></tr>
+ <tr><td>shrinkResources</td></tr>
+ <tr><td>signingConfig</td></tr>
+ <tr><td>testCoverageEnabled</td></tr>
+ <tr><td>useJack</td></tr>
+ <tr><td>versionNameSuffix</td></tr>
+ <tr><td>zipAlignEnabled</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>buildConfigField</td></tr>
+ <tr><td>resValue</td></tr>
+ <tr><td>proguardFile</td></tr>
+ <tr><td>proguardFiles</td></tr>
+ <tr><td>resValue</td></tr>
+ <tr><td>setProguardFiles</td></tr>
+ <tr><td>shrinkResources</td></tr>
+ <tr><td>useJack</td></tr>
+ <tr><td>consumerProguardFile</td></tr>
+ <tr><td>consumerProguardFiles</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DataBindingOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DataBindingOptions.xml
new file mode 100644
index 0000000..b54f315
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DataBindingOptions.xml
@@ -0,0 +1,37 @@
+<!--
+ ~ 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.
+ -->
+
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>version</td></tr>
+ <tr><td>enabled</td></tr>
+ <tr><td>addDefaultAdapters</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DensitySplitOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DensitySplitOptions.xml
new file mode 100644
index 0000000..a846a26
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DensitySplitOptions.xml
@@ -0,0 +1,20 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>compatibleScreens</td></tr>
+ <tr><td>auto</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DexOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DexOptions.xml
new file mode 100644
index 0000000..e3353c8
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.DexOptions.xml
@@ -0,0 +1,23 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>incremental</td></tr>
+ <tr><td>javaMaxHeapSize</td></tr>
+ <tr><td>jumboMode</td></tr>
+ <tr><td>preDexLibraries</td></tr>
+ <tr><td>threadCount</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LanguageSplitOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LanguageSplitOptions.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LanguageSplitOptions.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LanguageSplitOptions.xml
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LintOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LintOptions.xml
new file mode 100644
index 0000000..e76fea2
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.LintOptions.xml
@@ -0,0 +1,46 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>abortOnError</td></tr>
+ <tr><td>absolutePaths</td></tr>
+ <tr><td>check</td></tr>
+ <tr><td>checkAllWarnings</td></tr>
+ <tr><td>checkReleaseBuilds</td></tr>
+ <tr><td>disable</td></tr>
+ <tr><td>enable</td></tr>
+ <tr><td>explainIssues</td></tr>
+ <tr><td>htmlOutput</td></tr>
+ <tr><td>htmlReport</td></tr>
+ <tr><td>ignoreWarnings</td></tr>
+ <tr><td>lintConfig</td></tr>
+ <tr><td>noLines</td></tr>
+ <tr><td>quiet</td></tr>
+ <tr><td>severityOverrides</td></tr>
+ <tr><td>showAll</td></tr>
+ <tr><td>textOutput</td></tr>
+ <tr><td>textReport</td></tr>
+ <tr><td>warningsAsErrors</td></tr>
+ <tr><td>xmlOutput</td></tr>
+ <tr><td>xmlReport</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>check</td></tr>
+ <tr><td>enable</td></tr>
+ <tr><td>disable</td></tr>
+ <tr><td>ignore</td></tr>
+ <tr><td>error</td></tr>
+ <tr><td>fatal</td></tr>
+ <tr><td>warning</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.PackagingOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.PackagingOptions.xml
new file mode 100644
index 0000000..eb48160
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.PackagingOptions.xml
@@ -0,0 +1,24 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>excludes</td></tr>
+ <tr><td>merges</td></tr>
+ <tr><td>pickFirsts</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>exclude</td></tr>
+ <tr><td>merge</td></tr>
+ <tr><td>pickFirst</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.ProductFlavor.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.ProductFlavor.xml
new file mode 100644
index 0000000..8fd58ce
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.ProductFlavor.xml
@@ -0,0 +1,52 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>applicationId</td></tr>
+ <tr><td>dimension</td></tr>
+ <tr><td>flavorDimension</td></tr>
+ <tr><td>generatedDensities</td></tr>
+ <tr><td>multiDexEnabled</td></tr>
+ <tr><td>signingConfig</td></tr>
+ <tr><td>testApplicationId</td></tr>
+ <tr><td>testFunctionalTest</td></tr>
+ <tr><td>testHandleProfiling</td></tr>
+ <tr><td>testInstrumentationRunner</td></tr>
+ <tr><td>testInstrumentationRunnerArguments</td></tr>
+ <tr><td>useJack</td></tr>
+ <tr><td>versionCode</td></tr>
+ <tr><td>versionName</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>buildConfigField</td></tr>
+ <tr><td>consumerProguardFile</td></tr>
+ <tr><td>consumerProguardFiles</td></tr>
+ <tr><td>maxSdkVersion</td></tr>
+ <tr><td>maxSdkVersion</td></tr>
+ <tr><td>minSdkVersion</td></tr>
+ <tr><td>proguardFile</td></tr>
+ <tr><td>proguardFiles</td></tr>
+ <tr><td>resConfig</td></tr>
+ <tr><td>resConfigs</td></tr>
+ <tr><td>resValue</td></tr>
+ <tr><td>setConsumerProguardFiles</td></tr>
+ <tr><td>setProguardFiles</td></tr>
+ <tr><td>setTestProguardFiles</td></tr>
+ <tr><td>targetSdkVersion</td></tr>
+ <tr><td>testInstrumentationRunnerArgument</td></tr>
+ <tr><td>testInstrumentationRunnerArguments</td></tr>
+ <tr><td>testProguardFile</td></tr>
+ <tr><td>testProguardFiles</td></tr>
+ <tr><td>useJack</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SigningConfig.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SigningConfig.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SigningConfig.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SigningConfig.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SplitOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SplitOptions.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SplitOptions.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.SplitOptions.xml
diff --git a/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.Splits.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.Splits.xml
new file mode 100644
index 0000000..fc62242
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.Splits.xml
@@ -0,0 +1,27 @@
+<section>
+ <section>
+ <title>Properties</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>density</td></tr>
+ <tr><td>densityFilters</td></tr>
+ <tr><td>abi</td></tr>
+ <tr><td>abiFilters</td></tr>
+ <tr><td>language</td></tr>
+ <tr><td>languageFilters</td></tr>
+ </table>
+ </section>
+ <section>
+ <title>Methods</title>
+ <table>
+ <thead>
+ <tr><td>Name</td></tr>
+ </thead>
+ <tr><td>abi</td></tr>
+ <tr><td>density</td></tr>
+ <tr><td>language</td></tr>
+ </table>
+ </section>
+</section>
\ No newline at end of file
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.build.gradle.internal.dsl.TestOptions.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultBuildType.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultBuildType.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultBuildType.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultBuildType.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultProductFlavor.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultProductFlavor.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultProductFlavor.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.builder.core.DefaultProductFlavor.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.internal.BaseConfigImpl.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.internal.BaseConfigImpl.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.internal.BaseConfigImpl.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.builder.internal.BaseConfigImpl.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.signing.DefaultSigningConfig.xml b/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.signing.DefaultSigningConfig.xml
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/com.android.builder.signing.DefaultSigningConfig.xml
rename to build-system/docs/src/fromGradle/docs/dsl/com.android.builder.signing.DefaultSigningConfig.xml
diff --git a/build-system/docs/src/fromGradle/docs/dsl/dsl.xml b/build-system/docs/src/fromGradle/docs/dsl/dsl.xml
new file mode 100644
index 0000000..62107cc
--- /dev/null
+++ b/build-system/docs/src/fromGradle/docs/dsl/dsl.xml
@@ -0,0 +1,57 @@
+<book id="dsl">
+ <bookinfo>
+ <title>Android Plugin DSL Reference</title>
+ </bookinfo>
+
+ <section>
+ <title>Introduction</title>
+ <para>This is the DSL reference for Android Gradle Plugin.</para>
+ <para>Start reading by finding the right extension type for the plugin you are using, e.g. <code>AppExtension</code></para>
+ </section>
+
+ <!--
+ -
+ - 1. Adding new types:
+ - There are 2 ways to include a new types to this guide:
+ - * Types referenced by a property are automatically included, if there is a corresponding ${typename}.xml in the DSL source directory.
+ - * Types listed in one of the following tables are included. There must be a corresponding ${typename}.xml in the DSL source directory.
+ -
+ - 2. Adding new sections:
+ - The section title should end with 'types' (see AssembleDslDocTask.mergeContent)
+ -->
+
+ <section>
+ <title>Extension types</title>
+ <para>Listed below are the Gradle extension types used by respective plugins:</para>
+ <table>
+ <title>Extension types</title>
+ <tr><td>com.android.build.gradle.AppExtension</td></tr>
+ <tr><td>com.android.build.gradle.LibraryExtension</td></tr>
+ <tr><td>com.android.build.gradle.TestExtension</td></tr>
+ </table>
+ </section>
+
+ <section>
+ <title>Configuration blocks</title>
+ <para>Listed below are the configuration blocks available within <code>android</code></para>
+ <table>
+ <title>Configuration blocks</title>
+ <tr><td>defaultConfig</td></tr>
+ <tr><td>aaptOptions</td></tr>
+ <tr><td>adbOptions</td></tr>
+ <tr><td>buildTypes</td></tr>
+ <tr><td>compileOptions</td></tr>
+ <tr><td>dataBinding</td></tr>
+ <tr><td>dexOptions</td></tr>
+ <tr><td>jacoco</td></tr>
+ <tr><td>lintOptions</td></tr>
+ <tr><td>packagingOptions</td></tr>
+ <tr><td>productFlavors</td></tr>
+ <tr><td>signingConfigs</td></tr>
+ <tr><td>sourceSets</td></tr>
+ <tr><td>splits</td></tr>
+ <tr><td>testOptions</td></tr>
+ </table>
+ </section>
+
+</book>
diff --git a/base/build-system/docs/src/fromGradle/docs/dsl/plugins.xml b/build-system/docs/src/fromGradle/docs/dsl/plugins.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/dsl/plugins.xml
rename to build-system/docs/src/fromGradle/docs/dsl/plugins.xml
diff --git a/base/build-system/docs/src/fromGradle/docs/stylesheets/dslHtml.xsl b/build-system/docs/src/fromGradle/docs/stylesheets/dslHtml.xsl
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/stylesheets/dslHtml.xsl
rename to build-system/docs/src/fromGradle/docs/stylesheets/dslHtml.xsl
diff --git a/base/build-system/docs/src/fromGradle/docs/stylesheets/standaloneHtml.xsl b/build-system/docs/src/fromGradle/docs/stylesheets/standaloneHtml.xsl
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/stylesheets/standaloneHtml.xsl
rename to build-system/docs/src/fromGradle/docs/stylesheets/standaloneHtml.xsl
diff --git a/base/build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtml.xsl b/build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtml.xsl
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtml.xsl
rename to build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtml.xsl
diff --git a/base/build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtmlCommon.xsl b/build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtmlCommon.xsl
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtmlCommon.xsl
rename to build-system/docs/src/fromGradle/docs/stylesheets/userGuideHtmlCommon.xsl
diff --git a/base/build-system/docs/src/fromGradle/docs/stylesheets/userGuidePdf.xsl b/build-system/docs/src/fromGradle/docs/stylesheets/userGuidePdf.xsl
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/stylesheets/userGuidePdf.xsl
rename to build-system/docs/src/fromGradle/docs/stylesheets/userGuidePdf.xsl
diff --git a/base/build-system/docs/src/fromGradle/docs/stylesheets/userGuideSingleHtml.xsl b/build-system/docs/src/fromGradle/docs/stylesheets/userGuideSingleHtml.xsl
similarity index 100%
rename from base/build-system/docs/src/fromGradle/docs/stylesheets/userGuideSingleHtml.xsl
rename to build-system/docs/src/fromGradle/docs/stylesheets/userGuideSingleHtml.xsl
diff --git a/base/build-system/google-services/build.gradle b/build-system/google-services/build.gradle
similarity index 100%
rename from base/build-system/google-services/build.gradle
rename to build-system/google-services/build.gradle
diff --git a/base/build-system/google-services/google-services.iml b/build-system/google-services/google-services.iml
similarity index 100%
rename from base/build-system/google-services/google-services.iml
rename to build-system/google-services/google-services.iml
diff --git a/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesPlugin.groovy b/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesPlugin.groovy
new file mode 100644
index 0000000..9fb855c
--- /dev/null
+++ b/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesPlugin.groovy
@@ -0,0 +1,176 @@
+/*
+ * 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.google.gms.googleservices
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.GradleException
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class GoogleServicesPlugin implements Plugin<Project> {
+
+ public final static String JSON_FILE_NAME = 'google-services.json'
+
+ public final static String MODULE_GROUP = "com.google.android.gms"
+ public final static String MODULE_NAME = "play-services-measurement"
+ public final static String MODULE_VERSION = "8.3.0"
+ public final static String MINIMUM_VERSION = "8.1.0"
+
+ private static String targetVersion;
+
+ @Override
+ void apply(Project project) {
+ if (project.plugins.hasPlugin("android") ||
+ project.plugins.hasPlugin("com.android.application")) {
+ // this is a bit fragile but since this is internal usage this is ok
+ // (another plugin could declare itself to be 'android')
+ addDependency(project)
+ setupPlugin(project, false)
+ return
+ }
+ if (project.plugins.hasPlugin("android-library") ||
+ project.plugins.hasPlugin("com.android.library")) {
+ // this is a bit fragile but since this is internal usage this is ok
+ // (another plugin could declare itself to be 'android-library')
+ addDependency(project)
+ setupPlugin(project, true)
+ return
+ }
+ // If the google-service plugin is applied before any android plugin.
+ // We should warn that google service plugin should be applied at
+ // the bottom of build file.
+ showWarningForPluginLocation(project)
+
+ // Setup google-services plugin after android plugin is applied.
+ project.plugins.withId("android", {
+ setupPlugin(project, false)
+ })
+ project.plugins.withId("android-library", {
+ setupPlugin(project, true)
+ })
+
+ // Add dependencies after the build file is evaluate and hopefully it
+ // can be execute before android plugin process the dependencies.
+ project.afterEvaluate({
+ addDependency(project)
+ })
+ }
+
+ private void showWarningForPluginLocation(Project project) {
+ project.getLogger().warn(
+ "please apply google-services plugin at the bottom of the build file.")
+ }
+
+ private boolean checkMinimumVersion() {
+ String[] subTargetVersions = targetVersion.split("\\.")
+ String[] subMinimumVersions = MINIMUM_VERSION.split("\\.")
+ for (int i = 0; i < subTargetVersions.length && i < subMinimumVersions.length; i++) {
+ Integer subTargetVersion = Integer.valueOf(subTargetVersions[i])
+ Integer subMinimumVersion = Integer.valueOf(subMinimumVersions[i])
+ if (subTargetVersion > subMinimumVersion) {
+ return true;
+ } else if (subTargetVersion < subMinimumVersion) {
+ return false;
+ }
+ }
+ return subTargetVersions.length >= subMinimumVersions.length;
+ }
+
+ private void addDependency(Project project) {
+ targetVersion = findTargetVersion(project).split("-")[0]
+ if (checkMinimumVersion()) {
+ // If the target version is not lower than the minimum version.
+ project.dependencies.add('compile', MODULE_GROUP + ':' + MODULE_NAME + ':' + targetVersion)
+ } else {
+ throw new GradleException("Version: " + targetVersion + " is lower than the minimum version (" +
+ MINIMUM_VERSION + ") required for google-services plugin.")
+ }
+ }
+
+ private String findTargetVersion(Project project) {
+ def configurations = project.getConfigurations()
+ if (configurations == null) return
+ for (def configuration : configurations) {
+ if (configuration == null) continue
+ def dependencies = configuration.getDependencies()
+ if (dependencies == null) continue
+ for (def dependency : dependencies) {
+ if (dependency == null) continue
+ if (dependency.getGroup() == MODULE_GROUP) {
+ // Use the first version found in the dependencies.
+ return dependency.getVersion()
+ }
+ }
+ }
+
+ // If none of the version for Google play services is found, default
+ // version is used and a warning that google-services plugin should be
+ // applied at the bottom of the build file.
+ project.getLogger().warn("google-services plugin could not detect any version for " +
+ MODULE_GROUP + ", default version: " + MODULE_VERSION + " will be used.")
+ showWarningForPluginLocation(project)
+ // If no version is found, use the default one for the plugin.
+ return MODULE_VERSION
+ }
+
+ private void setupPlugin(Project project, boolean isLibrary) {
+ if (isLibrary) {
+ project.android.libraryVariants.all { variant ->
+ handleVariant(project, variant)
+ }
+ } else {
+ project.android.applicationVariants.all { variant ->
+ handleVariant(project, variant)
+ }
+ }
+ }
+
+ private static void handleVariant(Project project, def variant) {
+ File quickstartFile = project.file(JSON_FILE_NAME)
+
+ String variantName = "$variant.dirName";
+ String[] variantTokens = variantName.split('/')
+ // If flavor is found.
+ if (variantTokens.length == 2) {
+ String flavorName = variantTokens[0]
+ // check google-services.json at flavor source folder.
+ // If file exists, it will be used instead of the one at root.
+ File flavorFile = project.file('src/' + flavorName + '/' + JSON_FILE_NAME)
+ if (flavorFile.isFile()) {
+ quickstartFile = flavorFile
+ }
+ }
+
+ File outputDir = project.file("$project.buildDir/generated/res/google-services/$variant.dirName")
+
+ GoogleServicesTask task = project.tasks.create("process${variant.name.capitalize()}GoogleServices", GoogleServicesTask)
+
+ task.quickstartFile = quickstartFile
+ task.intermediateDir = outputDir
+ task.packageName = variant.applicationId
+ task.moduleGroup = MODULE_GROUP;
+ // Use the target version for the task.
+ task.moduleVersion = targetVersion;
+
+ variant.registerResGeneratingTask(task, outputDir)
+ }
+}
diff --git a/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesTask.java b/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesTask.java
new file mode 100644
index 0000000..3bc6e6c
--- /dev/null
+++ b/build-system/google-services/src/main/groovy/com/google/gms/googleservices/GoogleServicesTask.java
@@ -0,0 +1,397 @@
+/*
+ * 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.google.gms.googleservices;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.io.Files;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ */
+public class GoogleServicesTask extends DefaultTask {
+
+ private static final String STATUS_DISABLED = "1";
+ private static final String STATUS_ENABLED = "2";
+
+ /**
+ * The input is not technically optional but we want to control the error message.
+ * Without @Optional, Gradle will complain itself the file is missing.
+ */
+ @InputFile @Optional
+ public File quickstartFile;
+
+ @OutputDirectory
+ public File intermediateDir;
+
+ @Input
+ public String packageName;
+
+ @Input
+ public String moduleGroup;
+
+ @Input
+ public String moduleVersion;
+
+ @TaskAction
+ public void action() throws IOException {
+ checkVersionConflict();
+ if (!quickstartFile.isFile()) {
+ throw new GradleException("File " + quickstartFile.getName() + " is missing from module root folder." +
+ " The Google Services Plugin cannot function without it.");
+ }
+
+ // delete content of outputdir.
+ deleteFolder(intermediateDir);
+ if (!intermediateDir.mkdirs()) {
+ throw new GradleException("Failed to create folder: " + intermediateDir);
+ }
+
+ JsonElement root = new JsonParser().parse(Files.newReader(quickstartFile, Charsets.UTF_8));
+
+ if (!root.isJsonObject()) {
+ throw new GradleException("Malformed root json");
+ }
+
+ JsonObject rootObject = root.getAsJsonObject();
+
+ Map<String, String> resValues = new TreeMap<String, String>();
+ Map<String, Map<String, String>> resAttributes = new TreeMap<String, Map<String, String>>();
+
+ handleProjectNumber(rootObject, resValues);
+
+ JsonObject clientObject = getClientForPackageName(rootObject);
+
+ if (clientObject != null) {
+ handleAnalytics(clientObject, resValues);
+ handleAdsService(clientObject, resValues);
+ handleMapsService(clientObject, resValues);
+ handleGoogleAppId(clientObject, resValues);
+ } else {
+ throw new GradleException("No matching client found for package name '" + packageName + "'");
+ }
+
+ // write the values file.
+ File values = new File(intermediateDir, "values");
+ if (!values.exists() && !values.mkdirs()) {
+ throw new GradleException("Failed to create folder: " + values);
+ }
+
+ Files.write(getValuesContent(resValues, resAttributes), new File(values, "values.xml"), Charsets.UTF_8);
+ }
+
+ /**
+ * Check if there is any conflict between Play-Services Version
+ */
+ private void checkVersionConflict() {
+ Project project = getProject();
+ ConfigurationContainer configurations = project.getConfigurations();
+ if (configurations == null) {
+ return;
+ }
+ boolean hasConflict = false;
+ for (Configuration configuration : configurations) {
+ if (configuration == null) {
+ continue;
+ }
+ DependencySet dependencies = configuration.getDependencies();
+ if (dependencies == null) {
+ continue;
+ }
+ for (Dependency dependency : dependencies) {
+ if (dependency == null || dependency.getGroup() == null || dependency.getVersion() == null) {
+ continue;
+ }
+ if (dependency.getGroup().equals(moduleGroup)
+ && !dependency.getVersion().equals(moduleVersion)) {
+ hasConflict = true;
+ project.getLogger().warn("Found " + dependency.getGroup() + ":" +
+ dependency.getName() + ":" + dependency.getVersion() + ", but version " +
+ moduleVersion + " is needed for the google-services plugin.");
+ }
+ }
+ }
+ if (hasConflict) {
+ throw new GradleException("Please fix the version conflict either by updating the version " +
+ "of the google-services plugin (information about the latest version is available at " +
+ "https://bintray.com/android/android-tools/com.google.gms.google-services/) or updating " +
+ "the version of " + moduleGroup + " to " + moduleVersion + ".");
+ }
+ }
+
+ /**
+ * Handle project_info/project_number for @string/gcm_defaultSenderId, and fill the res map with the read value.
+ * @param rootObject the root Json object.
+ * @throws IOException
+ */
+ private void handleProjectNumber(JsonObject rootObject, Map<String, String> resValues)
+ throws IOException {
+ JsonObject projectInfo = rootObject.getAsJsonObject("project_info");
+ if (projectInfo == null) {
+ throw new GradleException("Missing project_info object");
+ }
+
+ JsonPrimitive projectNumber = projectInfo.getAsJsonPrimitive("project_number");
+ if (projectNumber == null) {
+ throw new GradleException("Missing project_info/project_number object");
+ }
+
+ resValues.put("gcm_defaultSenderId", projectNumber.getAsString());
+ }
+
+ /**
+ * Handle a client object for analytics (@xml/global_tracker)
+ * @param clientObject the client Json object.
+ * @throws IOException
+ */
+ private void handleAnalytics(JsonObject clientObject, Map<String, String> resValues)
+ throws IOException {
+ JsonObject analyticsService = getServiceByName(clientObject, "analytics_service");
+ if (analyticsService == null) return;
+
+ JsonObject analyticsProp = analyticsService.getAsJsonObject("analytics_property");
+ if (analyticsProp == null) return;
+
+ JsonPrimitive trackingId = analyticsProp.getAsJsonPrimitive("tracking_id");
+ if (trackingId == null) return;
+
+ resValues.put("ga_trackingId", trackingId.getAsString());
+
+ File xml = new File(intermediateDir, "xml");
+ if (!xml.exists() && !xml.mkdirs()) {
+ throw new GradleException("Failed to create folder: " + xml);
+ }
+
+ Files.write(getGlobalTrackerContent(
+ trackingId.getAsString()),
+ new File(xml, "global_tracker.xml"),
+ Charsets.UTF_8);
+ }
+
+ /**
+ * Handle a client object for analytics (@xml/global_tracker)
+ * @param clientObject the client Json object.
+ * @throws IOException
+ */
+ private void handleAdsService(JsonObject clientObject, Map<String, String> resValues)
+ throws IOException {
+ JsonObject adsService = getServiceByName(clientObject, "ads_service");
+ if (adsService == null) return;
+
+ findStringByName(adsService, "test_banner_ad_unit_id", resValues);
+ findStringByName(adsService, "test_interstitial_ad_unit_id", resValues);
+ }
+
+ /**
+ * Handle a client object for maps (@string/google_maps_key).
+ * @param clientObject the client Json object.
+ * @throws IOException
+ */
+ private void handleMapsService(JsonObject clientObject, Map<String, String> resValues)
+ throws IOException {
+ JsonObject mapsService = getServiceByName(clientObject, "maps_service");
+ if (mapsService == null) return;
+
+ JsonArray array = clientObject.getAsJsonArray("api_key");
+ if (array != null) {
+ final int count = array.size();
+ for (int i = 0 ; i < count ; i++) {
+ JsonElement apiKeyElement = array.get(i);
+ if (apiKeyElement == null || !apiKeyElement.isJsonObject()) {
+ continue;
+ }
+ JsonObject apiKeyObject = apiKeyElement.getAsJsonObject();
+ JsonPrimitive currentKey = apiKeyObject.getAsJsonPrimitive("current_key");
+ if (currentKey == null) {
+ continue;
+ }
+ resValues.put("google_maps_key", currentKey.getAsString());
+ return;
+ }
+ }
+ throw new GradleException("Missing api_key/current_key object");
+ }
+
+ private static void findStringByName(JsonObject jsonObject, String stringName,
+ Map<String, String> resValues) {
+ JsonPrimitive id = jsonObject.getAsJsonPrimitive(stringName);
+ if (id != null) {
+ resValues.put(stringName, id.getAsString());
+ }
+ }
+
+ /**
+ * find an item in the "client" array that match the package name of the app
+ * @param jsonObject the root json object.
+ * @return a JsonObject representing the client entry or null if no match is found.
+ */
+ private JsonObject getClientForPackageName(JsonObject jsonObject) {
+ JsonArray array = jsonObject.getAsJsonArray("client");
+ if (array != null) {
+ final int count = array.size();
+ for (int i = 0 ; i < count ; i++) {
+ JsonElement clientElement = array.get(i);
+ if (clientElement == null || !clientElement.isJsonObject()) {
+ continue;
+ }
+
+ JsonObject clientObject = clientElement.getAsJsonObject();
+
+ JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
+ if (clientInfo == null) continue;
+
+ JsonObject androidClientInfo = clientInfo.getAsJsonObject("android_client_info");
+ if (androidClientInfo == null) continue;
+
+ JsonPrimitive clientPackageName = androidClientInfo.getAsJsonPrimitive("package_name");
+ if (clientPackageName == null) continue;
+
+ if (packageName.equals(clientPackageName.getAsString())) {
+ return clientObject;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Handle a client object for Google App Id.
+ */
+ private void handleGoogleAppId(JsonObject clientObject, Map<String, String> resValues)
+ throws IOException {
+ JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
+ if (clientInfo == null) {
+ // Should not happen
+ throw new GradleException("Client does not have client info");
+ }
+
+ JsonPrimitive googleAppId = clientInfo.getAsJsonPrimitive("mobilesdk_app_id");
+ if (googleAppId == null) return;
+
+ String googleAppIdStr = googleAppId.getAsString();
+ if (Strings.isNullOrEmpty(googleAppIdStr)) return;
+
+ resValues.put("google_app_id", googleAppIdStr);
+ }
+
+ /**
+ * Finds a service by name in the client object. Returns null if the service is not found
+ * or if the service is disabled.
+ *
+ * @param clientObject the json object that represents the client.
+ * @param serviceName the service name
+ * @return the service if found.
+ */
+ private JsonObject getServiceByName(JsonObject clientObject, String serviceName) {
+ JsonObject services = clientObject.getAsJsonObject("services");
+ if (services == null) return null;
+
+ JsonObject service = services.getAsJsonObject(serviceName);
+ if (service == null) return null;
+
+ JsonPrimitive status = service.getAsJsonPrimitive("status");
+ if (status == null) return null;
+
+ String statusStr = status.getAsString();
+
+ if (STATUS_DISABLED.equals(statusStr)) return null;
+ if (!STATUS_ENABLED.equals(statusStr)) {
+ getLogger().warn(String.format("Status with value '%1$s' for service '%2$s' is unknown",
+ statusStr,
+ serviceName));
+ return null;
+ }
+
+ return service;
+ }
+
+
+ private static String getGlobalTrackerContent(String ga_trackingId) {
+ return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<resources>\n" +
+ " <string name=\"ga_trackingId\" translatable=\"false\">" + ga_trackingId + "</string>\n" +
+ "</resources>\n";
+ }
+
+ private static String getValuesContent(Map<String, String> values,
+ Map<String, Map<String, String>> attributes) {
+ StringBuilder sb = new StringBuilder(256);
+
+ sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<resources>\n");
+
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ String name = entry.getKey();
+ sb.append(" <string name=\"").append(name).append("\" translatable=\"false\"");
+ if (attributes.containsKey(name)) {
+ for (Map.Entry<String, String> attr : attributes.get(name).entrySet()) {
+ sb.append(" ").append(attr.getKey()).append("=\"")
+ .append(attr.getValue()).append("\"");
+ }
+ }
+ sb.append(">").append(entry.getValue()).append("</string>\n");
+ }
+
+ sb.append("</resources>\n");
+
+ return sb.toString();
+ }
+
+ private static void deleteFolder(final File folder) {
+ if (!folder.exists()) {
+ return;
+ }
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (final File file : files) {
+ if (file.isDirectory()) {
+ deleteFolder(file);
+ } else {
+ if (!file.delete()) {
+ throw new GradleException("Failed to delete: " + file);
+ }
+ }
+ }
+ }
+ if (!folder.delete()) {
+ throw new GradleException("Failed to delete: " + folder);
+ }
+ }
+}
diff --git a/base/build-system/google-services/src/main/resources/META-INF/gradle-plugins/com.google.gms.google-services.properties b/build-system/google-services/src/main/resources/META-INF/gradle-plugins/com.google.gms.google-services.properties
similarity index 100%
rename from base/build-system/google-services/src/main/resources/META-INF/gradle-plugins/com.google.gms.google-services.properties
rename to build-system/google-services/src/main/resources/META-INF/gradle-plugins/com.google.gms.google-services.properties
diff --git a/build-system/gradle-api/build.gradle b/build-system/gradle-api/build.gradle
new file mode 100644
index 0000000..7b4982a
--- /dev/null
+++ b/build-system/gradle-api/build.gradle
@@ -0,0 +1,49 @@
+apply plugin: 'java'
+apply plugin: 'jacoco'
+apply plugin: 'clone-artifacts'
+
+configurations {
+ provided
+}
+
+dependencies {
+ compile project(':base:annotations')
+ compile 'com.google.guava:guava:17.0'
+
+ provided gradleApi()
+
+ testCompile 'junit:junit:4.12'
+}
+
+sourceSets {
+ main {
+ compileClasspath += configurations.provided
+ }
+}
+
+javadoc {
+ classpath += configurations.provided
+ options.links("http://docs.oracle.com/javase/7/docs/api/", "https://docs.gradle.org/current/javadoc/")
+
+ if (JavaVersion.current().isJava8Compatible()) {
+ options.addStringOption 'Xdoclint:none', '-quiet'
+ }
+}
+
+task javadocZip(type: Zip) {
+ dependsOn javadoc
+ from javadoc.destinationDir
+ baseName 'javadoc'
+}
+
+group = 'com.android.tools.build'
+archivesBaseName = 'gradle-api'
+version = rootProject.ext.buildVersion
+
+project.ext.pomName = 'Android Gradle API'
+project.ext.pomDesc = 'APIs to customize Android Gradle Builds'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
+
diff --git a/build-system/gradle-api/gradle-api.iml b/build-system/gradle-api/gradle-api.iml
new file mode 100644
index 0000000..7f43972
--- /dev/null
+++ b/build-system/gradle-api/gradle-api.iml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" exported="" name="guava-tools" level="project" />
+ <orderEntry type="module" module-name="android-annotations" exported="" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-core-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-model-core-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ </component>
+</module>
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/Context.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Context.java
new file mode 100644
index 0000000..a672e5c
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Context.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.api.transform;
+
+import com.google.common.annotations.Beta;
+
+import org.gradle.api.logging.LoggingManager;
+
+import java.io.File;
+
+/**
+ * Context for the transform.
+ *
+ * This gives access to a limited amount of context when the transform is run.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public interface Context {
+
+ /**
+ * Returns the LoggingManager which can be used to control the logging level and standard
+ * output/error capture for this task.
+ *
+ * By default, System.out is redirected to the Gradle logging system at the QUIET log level,
+ * and System.err is redirected at the ERROR log level.
+ *
+ * @return the LoggingManager. Never returns null.
+ */
+ LoggingManager getLogging();
+
+ /**
+ * Returns a directory which this task can use to write temporary files to.
+ *
+ * Each task instance is provided with a separate temporary directory. There are
+ * no guarantees that the contents of this directory will be kept beyond the execution
+ * of the task.
+ *
+ * @return The directory. Never returns null. The directory will already exist.
+ */
+ File getTemporaryDir();
+
+ /**
+ * Returns the path of the task, which is a fully qualified name for the task.
+ *
+ * The path of a task is the path of its <code>Project</code> plus the name of the task,
+ * separated by <code>:</code>.
+ *
+ * @return the path of the task, which is equal to the path of the project plus the name of the task.
+ */
+ String getPath();
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/DirectoryInput.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/DirectoryInput.java
new file mode 100644
index 0000000..3b34fd8
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/DirectoryInput.java
@@ -0,0 +1,64 @@
+/*
+ * 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.build.api.transform;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * A {@link QualifiedContent} of type directory.
+ * <p/>
+ * This means the {@link #getFile()} is the root directory containing the content.
+ * <p/>
+ * This also contains incremental data if the transform is in incremental mode through
+ * {@link #getChangedFiles()}.
+ * <p/>
+ * For a transform to run in incremental mode:
+ * <ul>
+ * <li>{@link Transform#isIncremental()} must return <code>true</code></li>
+ * <li>The parameter <var>isIncremental</var> of
+ * {@link Transform#transform(Context, Collection, Collection, TransformOutputProvider, boolean)}
+ * must be <code>true</code>.</li>
+ * </ul>
+ *
+ * If the transform is not in incremental mode, {@link #getChangedFiles()} will not contain any
+ * information (it will <strong>not</strong> contain the list of all the files with state
+ * {@link Status#NOTCHANGED}.)
+ *
+ * <p/>
+ * When a root level directory is removed, and incremental mode is on, the instance will receive
+ * a {@link DirectoryInput} instance for the removed folder, but {@link QualifiedContent#getFile()}
+ * will return a directory that does not exist. In this case, the transform should prcess this
+ * as a removed input.
+ *
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public interface DirectoryInput extends QualifiedContent {
+
+ /**
+ * Returns the changed files. This is only valid if the transform is in incremental mode.
+ */
+ @NonNull
+ Map<File, Status> getChangedFiles();
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/Format.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Format.java
new file mode 100644
index 0000000..9f7b071
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Format.java
@@ -0,0 +1,41 @@
+/*
+ * 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.build.api.transform;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * The format in which content is stored.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public enum Format {
+
+ /**
+ * The content is a jar.
+ */
+ JAR,
+ /**
+ * The content is a directory.
+ * <p/>
+ * This means that in the case of java class files, the files should be in directories
+ * matching their package names, directly under the root directory.
+ */
+ DIRECTORY
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/JarInput.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/JarInput.java
new file mode 100644
index 0000000..3810e15
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/JarInput.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.api.transform;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+import java.util.Collection;
+
+/**
+ * A {@link QualifiedContent} of type jar.
+ * <p/>
+ * This means the {@link #getFile()} is the jar file containing the content.
+ * <p/>
+ * This also contains the incremental state of the jar file, if the transform is in incremental
+ * mode through {@link #getStatus()}.
+ * <p/>
+ * For a transform to run in incremental mode:
+ * <ul>
+ * <li>{@link Transform#isIncremental()} must return <code>true</code></li>
+ * <li>The parameter <var>isIncremental</var> of
+ * {@link Transform#transform(Context, Collection, Collection, TransformOutputProvider, boolean)}
+ * must be <code>true</code>.</li>
+ * </ul>
+ *
+ * If the transform is not in incremental mode, {@link #getStatus()} always returns
+ * {@link Status#NOTCHANGED}.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public interface JarInput extends QualifiedContent {
+
+ @NonNull
+ Status getStatus();
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/QualifiedContent.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/QualifiedContent.java
new file mode 100644
index 0000000..0e9cdf1
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/QualifiedContent.java
@@ -0,0 +1,155 @@
+/*
+ * 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.build.api.transform;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Represent content qualified with one or more {@link ContentType} and one or more {@link Scope}.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public interface QualifiedContent {
+
+ /**
+ * A content type that is requested through the transform API.
+ */
+ interface ContentType {
+
+ /**
+ * Content type name, readable by humans.
+ * @return the string content type name
+ */
+ String name();
+
+ /**
+ * A unique value for a content type.
+ */
+ int getValue();
+ }
+
+ /**
+ * The type of of the content.
+ */
+ enum DefaultContentType implements ContentType {
+ /**
+ * The content is compiled Java code. This can be in a Jar file or in a folder. If
+ * in a folder, it is expected to in sub-folders matching package names.
+ */
+ CLASSES(0x01),
+
+ /**
+ * The content is standard Java resources.
+ */
+ RESOURCES(0x02);
+
+ private final int value;
+
+ DefaultContentType(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public int getValue() {
+ return value;
+ }
+ }
+
+ /**
+ * The scope of the content.
+ *
+ * <p/>
+ * This indicates what the content represents, so that Transforms can apply to only part(s)
+ * of the classes or resources that the build manipulates.
+ */
+ enum Scope {
+ /** Only the project content */
+ PROJECT(0x01),
+ /** Only the project's local dependencies (local jars) */
+ PROJECT_LOCAL_DEPS(0x02),
+ /** Only the sub-projects. */
+ SUB_PROJECTS(0x04),
+ /** Only the sub-projects's local dependencies (local jars). */
+ SUB_PROJECTS_LOCAL_DEPS(0x08),
+ /** Only the external libraries */
+ EXTERNAL_LIBRARIES(0x10),
+ /** Code that is being tested by the current variant, including dependencies */
+ TESTED_CODE(0x20),
+ /** Local or remote dependencies that are provided-only */
+ PROVIDED_ONLY(0x40);
+
+ private final int value;
+
+ Scope(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ /**
+ * Returns the name of the content. Can be used to differentiate different content using
+ * the same scope.
+ *
+ * This is not reliably usable at every stage of the transformations, but can be used for
+ * logging for instance.
+ *
+ * @return the name
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns he location of the content.
+ *
+ * @return the content location.
+ */
+ @NonNull
+ File getFile();
+
+ /**
+ * Returns the type of content that the stream represents.
+ * <p/>
+ * Even though this may return only {@link DefaultContentType#RESOURCES} or
+ * {@link DefaultContentType#CLASSES}, the actual content (the folder or the jar) may
+ * contain files representing other content types. This is because the transform mechanism
+ * avoids duplicating files around to remove unwanted types for performance.
+ * <p/>
+ * For each input, transforms should always take care to read and process only the files
+ * associated with the types returned by this method.
+ *
+ * @return a set of one or more types, never null nor empty.
+ */
+ @NonNull
+ Set<ContentType> getContentTypes();
+
+ /**
+ * Returns the scope of the content.
+ *
+ * @return a set of one or more scopes, never null nor empty.
+ */
+ @NonNull
+ Set<Scope> getScopes();
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/SecondaryFile.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/SecondaryFile.java
new file mode 100644
index 0000000..6a23e95
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/SecondaryFile.java
@@ -0,0 +1,57 @@
+/*
+ * 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.build.api.transform;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * A secondary input file for a {@link Transform}.
+ *
+ * A secondary input is part of the transform inputs and can be decorated to indicate if a change
+ * to the input would trigger a non incremental
+ * {@link Transform#transform(Context, Collection, Collection, Collection, TransformOutputProvider, boolean)}
+ * call
+ */
+public class SecondaryFile {
+
+ private final boolean supportsIncrementalBuild;
+ private final File secondaryInputFile;
+
+ public SecondaryFile(File secondaryInputFile, boolean supportsIncrementalBuild) {
+ this.supportsIncrementalBuild = supportsIncrementalBuild;
+ this.secondaryInputFile = secondaryInputFile;
+ }
+
+ /**
+ * Returns true if this secondary input changes can be handled by the receiving {@link Transform}
+ * incrementally. If false, a change to the file returned by {@link #getFile()} will trigger
+ * a non incremental build.
+ * @return true when the input file changes can be handled incrementally, false otherwise.
+ */
+ public boolean supportsIncrementalBuild() {
+ return supportsIncrementalBuild;
+ }
+
+ /**
+ * Returns the file handle for this secondary input to a Transform.
+ * @return a file handle.
+ */
+ public File getFile() {
+ return secondaryInputFile;
+ }
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/SecondaryInput.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/SecondaryInput.java
new file mode 100644
index 0000000..ba6f5d5
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/SecondaryInput.java
@@ -0,0 +1,35 @@
+/*
+ * 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.build.api.transform;
+
+/**
+ * Represents a change event for a {@link SecondaryFile} transform input.
+ */
+public interface SecondaryInput {
+
+ /**
+ * The change event subject.
+ * @return an instance of {@link SecondaryFile} that represent a transform input file.
+ */
+ SecondaryFile getSecondaryInput();
+
+ /**
+ * The change status.
+ * @return the change status.
+ */
+ Status getStatus();
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/Status.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Status.java
new file mode 100644
index 0000000..0b31549
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Status.java
@@ -0,0 +1,45 @@
+/*
+ * 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.build.api.transform;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * The file changed status for incremental execution.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public enum Status {
+ /**
+ * The file was not changed since the last build.
+ */
+ NOTCHANGED,
+ /**
+ * The file was added since the last build.
+ */
+ ADDED,
+ /**
+ * The file was modified since the last build.
+ */
+ CHANGED,
+ /**
+ * The file was removed since the last build.
+ */
+ REMOVED
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/Transform.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Transform.java
new file mode 100644
index 0000000..03de7fd
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/Transform.java
@@ -0,0 +1,313 @@
+/*
+ * 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.build.api.transform;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Transform that processes intermediary build artifacts.
+ * <p>
+ * For each added transform, a new task is created. The action of adding a transform takes
+ * care of handling dependencies between the tasks. This is done based on what the transform
+ * processes. The output of the transform becomes consumable by other transforms and these
+ * tasks get automatically linked together.
+ * <p/>
+ * The Transform indicates what it applies to (content, scope) and what it generates (content).
+ * <p/>
+ * A transform receives input as a collection {@link TransformInput}, which is composed of
+ * {@link JarInput}s and {@link DirectoryInput}s.
+ * Both provide information about the {@link Scope}s and {@link ContentType}s associated with their
+ * particular content.
+ *<p/>
+ * The output is handled by {@link TransformOutputProvider} which allows creating new self-contained
+ * content, each associated with their own Scopes and Content Types.
+ * The content handled by TransformInput/Output is managed by the transform system, and their
+ * location is not configurable.
+ * <p/>
+ * It is best practice to write into as many outputs as Jar/Folder Inputs have been received by the
+ * transform. Combining all the inputs into a single output prevents downstream transform from
+ * processing limited scopes.
+ * <p/>
+ * While it's possible to differentiate different Content Types by file extension, it's not possible
+ * to do so for Scopes. Therefore if a transform request a Scope but the only available Output
+ * contains more than the requested Scope, the build will fail.<br>
+ * If a transform request a single content type but the only available content includes more than
+ * the requested type, the input file/folder will contain all the files of all the types, but
+ * the transform should only read, process and output the type(s) it requested.
+ *
+ * <p/>
+ * Additionally, a transform can indicate secondary inputs/outputs. These are not handled by
+ * upstream or downstream transforms, and are not restricted by type handled by transform. They can
+ * be anything.
+ * It's up to each transform to manage where these files are, and to make sure that these files
+ * are generated before the transform is called. This is done through additional parameters
+ * when register the transform.
+ * <p/>These secondary inputs/outputs allow a transform to read but not process any content. This
+ * can be achieved by having {@link #getScopes()} return an empty list and use
+ * {@link #getReferencedScopes()} to indicate what to read instead.
+ *
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+ at SuppressWarnings("MethodMayBeStatic")
+public abstract class Transform {
+
+ /**
+ * Returns the unique name of the transform.
+ *
+ * <p/>
+ * This is associated with the type of work that the transform does. It does not have to be
+ * unique per variant.
+ */
+ @NonNull
+ public abstract String getName();
+
+ /**
+ * Returns the type(s) of data that is consumed by the Transform. This may be more than
+ * one type.
+ *
+ * <strong>This must be of type {@link QualifiedContent.DefaultContentType}</strong>
+ */
+ @NonNull
+ public abstract Set<ContentType> getInputTypes();
+
+ /**
+ * Returns the type(s) of data that is generated by the Transform. This may be more than
+ * one type.
+ *
+ * <p/>
+ * The default implementation returns {@link #getInputTypes()}.
+ * <p/>
+ * <strong>This must be of type {@link QualifiedContent.DefaultContentType}</strong>
+ */
+ @NonNull
+ public Set<ContentType> getOutputTypes() {
+ return getInputTypes();
+ }
+
+ /**
+ * Returns the scope(s) of the Transform. This indicates which scopes the transform consumes.
+ */
+ @NonNull
+ public abstract Set<Scope> getScopes();
+
+ /**
+ * Returns the referenced scope(s) for the Transform. These scopes are not consumed by
+ * the Transform. They are provided as inputs, but are still available as inputs for
+ * other Transforms to consume.
+ *
+ * <p/>
+ * The default implementation returns an empty Set.
+ */
+ @NonNull
+ public Set<Scope> getReferencedScopes() {
+ return ImmutableSet.of();
+ }
+
+ /**
+ * Returns a list of additional file(s) that this Transform needs to run. Preferably, use
+ * {@link #getSecondaryFiles()} API which allow eah secondary file to indicate if changes
+ * can be handled incrementally or not. This API will treat all additional file change as
+ * a non incremental event.
+ *
+ * <p/>
+ * Changes to files returned in this list will trigger a new execution of the Transform
+ * even if the qualified-content inputs haven't been touched.
+ * <p/>
+ * Any changes to these files will trigger a non incremental execution.
+ *
+ * <p/>
+ * The default implementation returns an empty collection.
+ * @deprecated
+ */
+ @Deprecated
+ @NonNull
+ public Collection<File> getSecondaryFileInputs() {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns a list of additional file(s) that this Transform needs to run.
+ *
+ * <p/>
+ * Changes to files returned in this list will trigger a new execution of the Transform
+ * even if the qualified-content inputs haven't been touched.
+ * <p/>
+ * Each secondary input has the ability to be declared as necessitating a non incremental
+ * execution in case of change. This Transform can therefore declare which secondary file
+ * changes it supports in incremental mode.
+ *
+ * <p/>
+ * The default implementation returns an empty collection.
+ *
+ * @return
+ */
+ @NonNull
+ public Collection<SecondaryFile> getSecondaryFiles() {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns a list of additional (out of streams) file(s) that this Transform creates.
+ *
+ * <p/>
+ * These File instances can only represent files, not directories. For directories, use
+ * {@link #getSecondaryDirectoryOutputs()}
+ *
+ * <p/>
+ * Changes to files returned in this list will trigger a new execution of the Transform
+ * even if the qualified-content inputs haven't been touched.
+ * <p/>
+ * Changes to these output files force a non incremental execution.
+ *
+ * <p/>
+ * The default implementation returns an empty collection.
+ */
+ @NonNull
+ public Collection<File> getSecondaryFileOutputs() {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns a list of additional (out of streams) directory(ies) that this Transform creates.
+ *
+ * <p/>
+ * These File instances can only represent directories. For files, use
+ * {@link #getSecondaryFileOutputs()}
+ *
+ * <p/>
+ * Changes to directories returned in this list will trigger a new execution of the Transform
+ * even if the qualified-content inputs haven't been touched.
+ * <p/>
+ * Changes to these output directories force a non incremental execution.
+ *
+ * <p/>
+ * The default implementation returns an empty collection.
+ */
+ @NonNull
+ public Collection<File> getSecondaryDirectoryOutputs() {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns a map of non-file input parameters using a unique identifier as the map key.
+ *
+ * <p/>
+ * Changes to values returned in this map will trigger a new execution of the Transform
+ * even if the content inputs haven't been touched.
+ * <p/>
+ * Changes to these values force a non incremental execution.
+ *
+ * <p/>
+ * The default implementation returns an empty Map.
+ */
+ @NonNull
+ public Map<String, Object> getParameterInputs() {
+ return ImmutableMap.of();
+ }
+
+ /**
+ * Returns whether the Transform can perform incremental work.
+ *
+ * <p/>
+ * If it does, then the TransformInput may contain a list of changed/removed/added files, unless
+ * something else triggers a non incremental run.
+ */
+ public abstract boolean isIncremental();
+
+
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public void transform(
+ @NonNull Context context,
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedInputs,
+ @Nullable TransformOutputProvider outputProvider,
+ boolean isIncremental) throws IOException, TransformException, InterruptedException {
+ }
+
+ /**
+ * Executes the Transform.
+ *
+ * <p/>
+ * The inputs are packaged as an instance of {@link TransformInvocation}
+ * <ul>
+ * <li>The <var>inputs</var> collection of {@link TransformInput}. These are the inputs
+ * that are consumed by this Transform. A transformed version of these inputs must
+ * be written into the output. What is received is controlled through
+ * {@link #getInputTypes()}, and {@link #getScopes()}.</li>
+ * <li>The <var>referencedInputs</var> collection of {@link TransformInput}. This is
+ * for reference only and should be not be transformed. What is received is controlled
+ * through {@link #getReferencedScopes()}.</li>
+ * </ul>
+ *
+ * A transform that does not want to consume anything but instead just wants to see the content
+ * of some inputs should return an empty set in {@link #getScopes()}, and what it wants to
+ * see in {@link #getReferencedScopes()}.
+ *
+ * <p/>
+ * Even though a transform's {@link Transform#isIncremental()} returns true, this method may
+ * be receive <code>false</code> in <var>isIncremental</var>. This can be due to
+ * <ul>
+ * <li>a change in secondary files ({@link #getSecondaryFiles()},
+ * {@link #getSecondaryFileOutputs()}, {@link #getSecondaryDirectoryOutputs()})</li>
+ * <li>a change to a non file input ({@link #getParameterInputs()})</li>
+ * <li>an unexpected change to the output files/directories. This should not happen unless
+ * tasks are improperly configured and clobber each other's output.</li>
+ * <li>a file deletion that the transform mechanism could not match to a previous input.
+ * This should not happen in most case, except in some cases where dependencies have
+ * changed.</li>
+ * </ul>
+ * In such an event, when <var>isIncremental</var> is false, the inputs will not have any
+ * incremental change information:
+ * <ul>
+ * <li>{@link JarInput#getStatus()} will return {@link Status#NOTCHANGED} even though
+ * the file may be added/changed.</li>
+ * <li>{@link DirectoryInput#getChangedFiles()} will return an empty map even though
+ * some files may be added/changed.</li>
+ * </ul>
+ *
+ * @param transformInvocation the invocation object containing the transform inputs.
+ * @throws IOException if an IO error occurs.
+ * @throws InterruptedException
+ * @throws TransformException Generic exception encapsulating the cause.
+ */
+ public void transform(@NonNull TransformInvocation transformInvocation)
+ throws TransformException, InterruptedException, IOException {
+ transform(transformInvocation.getContext(), transformInvocation.getInputs(),
+ transformInvocation.getReferencedInputs(),
+ transformInvocation.getOutputProvider(),
+ transformInvocation.isIncremental());
+ }
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformException.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformException.java
new file mode 100644
index 0000000..9863166
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.build.api.transform;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * An exception during the execution of a Transform.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public class TransformException extends Exception {
+
+ public TransformException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public TransformException(String s) {
+ super(s);
+ }
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformInput.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformInput.java
new file mode 100644
index 0000000..3f1ddff
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformInput.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.api.transform;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+import java.util.Collection;
+
+/**
+ * The input to a Transform.
+ * <p/>
+ * It is mostly composed of a list of {@link JarInput} and a list of {@link DirectoryInput}.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public interface TransformInput {
+
+ /**
+ * Returns a collection of {@link JarInput}.
+ */
+ @NonNull
+ Collection<JarInput> getJarInputs();
+
+ /**
+ * Returns a collection of {@link DirectoryInput}.
+ */
+ @NonNull
+ Collection<DirectoryInput> getDirectoryInputs();
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformInvocation.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformInvocation.java
new file mode 100644
index 0000000..57c774b
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformInvocation.java
@@ -0,0 +1,69 @@
+/*
+ * 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.build.api.transform;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * An invocation object used to pass of pertinent information for a
+ * {@link Transform#transform(TransformInvocation)} call.
+ */
+public interface TransformInvocation {
+
+ /**
+ * Returns the context in which the transform is run.
+ * @return the context in which the transform is run.
+ */
+ @NonNull
+ Context getContext();
+
+ /**
+ * Returns the inputs/outputs of the transform.
+ * @return the inputs/outputs of the transform.
+ */
+ @NonNull
+ Collection<TransformInput> getInputs();
+
+ /**
+ * Returns the referenced-only inputs which are not consumed by this transformation.
+ * @return the referenced-only inputs.
+ */
+ @NonNull Collection<TransformInput> getReferencedInputs();
+ /**
+ * Returns the list of secondary file changes since last. Only secondary files that this
+ * transform can handle incrementally will be part of this change set.
+ * @return the list of changes impacting a {@link SecondaryInput}
+ */
+ @NonNull Collection<SecondaryInput> getSecondaryInputs();
+
+ /**
+ * Returns the output provider allowing to create content.
+ * @return he output provider allowing to create content.
+ */
+ @Nullable
+ TransformOutputProvider getOutputProvider();
+
+
+ /**
+ * Indicates whether the transform execution is incremental.
+ * @return true for an incremental invocation, false otherwise.
+ */
+ boolean isIncremental();
+}
diff --git a/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformOutputProvider.java b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformOutputProvider.java
new file mode 100644
index 0000000..3b558d7
--- /dev/null
+++ b/build-system/gradle-api/src/main/java/com/android/build/api/transform/TransformOutputProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.build.api.transform;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * The output of a transform.
+ * <p/>
+ * There is no direct access to a location to write. Instead, Transforms can ask to get the
+ * location for given scopes, content-types and a format.
+ * <p/>
+ * <strong>This API is non final and is subject to change. We are looking for feedback, and will
+ * attempt to stabilize it in the 1.6 time frame.</strong>
+ */
+ at Beta
+public interface TransformOutputProvider {
+
+ /**
+ * Delete all content. This is useful when running in non-incremental mode
+ * @throws IOException
+ */
+ void deleteAll() throws IOException;
+
+ /**
+ * Returns the location of content for a given set of Scopes, Content Types, and Format.
+ * <p/>
+ * If the format is {@link Format#DIRECTORY} then the result is the file location of the
+ * directory.<br>
+ * If the format is {@link Format#JAR} then the result is a file representing the jar to create.
+ * <p/>
+ * Non of the directories or files are created by querying this method, and there is
+ * no checks regarding the existence of content in this location.
+ * <p/>
+ * In case of incremental processing of removed files, it is safe to query the method to get
+ * the location of the files to removed.
+ *
+ * @param name a unique name for the content. For a given set of scopes/types/format it must
+ * be unique.
+ * @param types the content types associated with this content.
+ * @param scopes the scopes associated with this content.
+ * @param format the format of the content.
+ * @return the location of the content.
+ */
+ @NonNull
+ File getContentLocation(@NonNull String name,
+ @NonNull Set<QualifiedContent.ContentType> types,
+ @NonNull Set<QualifiedContent.Scope> scopes,
+ @NonNull Format format);
+}
diff --git a/base/build-system/gradle-core/MODULE_LICENSE_APACHE2 b/build-system/gradle-core/MODULE_LICENSE_APACHE2
similarity index 100%
rename from base/build-system/gradle-core/MODULE_LICENSE_APACHE2
rename to build-system/gradle-core/MODULE_LICENSE_APACHE2
diff --git a/base/build-system/gradle-core/NOTICE b/build-system/gradle-core/NOTICE
similarity index 100%
rename from base/build-system/gradle-core/NOTICE
rename to build-system/gradle-core/NOTICE
diff --git a/base/build-system/gradle-core/README b/build-system/gradle-core/README
similarity index 100%
rename from base/build-system/gradle-core/README
rename to build-system/gradle-core/README
diff --git a/build-system/gradle-core/build.gradle b/build-system/gradle-core/build.gradle
new file mode 100644
index 0000000..5f331ad
--- /dev/null
+++ b/build-system/gradle-core/build.gradle
@@ -0,0 +1,202 @@
+apply plugin: 'groovy'
+apply plugin: 'jacoco'
+apply plugin: 'clone-artifacts'
+apply plugin: 'antlr'
+
+configurations {
+ provided
+ includeInJar
+}
+
+// Incremental update test support
+File classesDir = new File(project.buildDir, "classes/incremental-test")
+File baseClasses = new File(classesDir, "base")
+File baseInstrumentedClasses = new File(classesDir, "baseInstrumented")
+
+sourceSets {
+ main {
+ groovy.srcDirs = ['src/main/groovy', 'src/fromGradle/groovy']
+ resources.srcDirs = ['src/main/resources', 'src/fromGradle/resources']
+ compileClasspath += configurations.provided
+ }
+
+ test {
+ compileClasspath += files(baseClasses)
+ runtimeClasspath += files(baseInstrumentedClasses)
+ }
+}
+
+ext.proguardVersion = "5.2.1"
+
+dependencies {
+ compile project(':base:builder')
+ compile project(':base:lint')
+ // TODO: Switch to remote dependency once the empty deprecated transform-api is published
+ compile project(':base:transform-api')
+ compile project(':base:gradle-api')
+ compile project(':dataBinding:compilerCommon')
+ compile 'org.ow2.asm:asm:5.0.3'
+ compile 'org.ow2.asm:asm-commons:5.0.3'
+ compile "net.sf.proguard:proguard-gradle:${project.ext.proguardVersion}"
+ compile "org.jacoco:org.jacoco.core:0.7.4.201502262128"
+ testCompile project(':base:instant-run:instant-run-annotations')
+ testCompile project(':base:instant-run:instant-run-runtime')
+
+
+ // Add gradleApi to classpath for compilation, but use provided configuration so that groovy is
+ // not exposed as a runtime dependency.
+ provided gradleApi()
+ testCompile gradleApi()
+
+ includeInJar project(':base:instant-run:instant-run-server')
+
+ antlr 'org.antlr:antlr:3.5.2'
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'com.google.truth:truth:0.28'
+ testCompile 'org.mockito:mockito-all:1.9.5'
+ testCompile 'com.google.guava:guava:17.0'
+ testCompile project(':base:project-test-lib')
+ testCompile project(':base:testutils')
+}
+
+jar {
+ into('instant-run') {
+ from configurations.includeInJar
+ }
+}
+
+tasks.compileJava.dependsOn ":setupGradleInIde"
+
+group = 'com.android.tools.build'
+archivesBaseName = 'gradle-core'
+version = rootProject.ext.buildVersion
+
+project.ext.pomName = 'Core Library for Android Gradle Plug-in'
+project.ext.pomDesc = 'Core library to build Android Gradle plugin.'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+
+test {
+ environment("CUSTOM_REPO", rootProject.file("../out/repo"))
+
+ testLogging {
+ events "failed"
+ }
+
+ maxParallelForks = Runtime.runtime.availableProcessors() / 2
+}
+
+groovydoc {
+ exclude "**/internal/**"
+ includePrivate false
+
+ docTitle "Gradle Plugin for Android"
+ header ""
+ footer "Copyright (C) 2012 The Android Open Source Project"
+ overview ""
+
+ groovyClasspath = configurations.provided
+}
+
+task javadocJar(type: Jar, dependsOn:groovydoc) {
+ classifier 'javadoc'
+ from groovydoc.destinationDir
+}
+
+
+
+// Only package JavaDoc if using --init-script=buildSrc/base/release.gradle
+if (project.has("release")) {
+ artifacts {
+ archives javadocJar
+ }
+}
+
+// Incremental update test support
+String androidJar = System.env.ANDROID_HOME + '/platforms/android-15/android.jar'
+
+Task compileIncrementalTestBaseClasses = tasks.create(
+ name: "compileIncrementalTestBaseClasses",
+ type: org.gradle.api.tasks.compile.JavaCompile) {
+ source = new File(project.getProjectDir(), "src/test/incremental-test-classes/base")
+ classpath = sourceSets.test.compileClasspath
+ destinationDir = baseClasses
+ sourceCompatibility '1.6'
+ targetCompatibility '1.6'
+ options.bootClasspath = androidJar
+}
+
+compileTestJava.dependsOn compileIncrementalTestBaseClasses
+
+
+Task instrumentIncrementalTestBaseClasses = tasks.create(
+ name: "instrumentIncrementalTestBaseClasses",
+ type: org.gradle.api.tasks.JavaExec) {
+ main = 'com.android.build.gradle.internal.incremental.IncrementalSupportVisitor'
+// jvmArgs = [ '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005']
+ classpath = sourceSets.main.runtimeClasspath + sourceSets.test.compileClasspath
+ args baseClasses, baseInstrumentedClasses, androidJar
+}
+
+instrumentIncrementalTestBaseClasses.dependsOn compileIncrementalTestBaseClasses
+
+// Compile other changesets. They have the default changset on their classpath.
+Task instrumentIncrementalTestPatches = tasks.create("instrumentIncrementalTestPatches")
+
+compileTestJava.dependsOn(
+ instrumentIncrementalTestPatches,
+ instrumentIncrementalTestBaseClasses)
+
+File incrementalTestOutDir = new File(classesDir, "patches")
+File instrumentedIncrementalTestOutDir = new File(classesDir, "instrumentedPatches")
+
+for (File f: new File(project.projectDir, "src/test/incremental-test-classes/patches").listFiles()) {
+ File incrementalPatchClassFileDir = new File(incrementalTestOutDir, f.getName())
+ Task compilePatch = tasks.create(
+ name: "compileIncrementalTestPatch${f.getName().capitalize()}",
+ type: org.gradle.api.tasks.compile.JavaCompile) {
+ source = project.files(f)
+ classpath = sourceSets.test.compileClasspath
+ destinationDir = incrementalPatchClassFileDir
+ sourceCompatibility '1.6'
+ targetCompatibility '1.6'
+ options.bootClasspath = androidJar
+ }
+
+ compilePatch.dependsOn compileIncrementalTestBaseClasses
+
+ Task instrumentPatch = tasks.create(
+ name: "instrumentIncrementalTestPatch${f.getName().capitalize()}",
+ type: org.gradle.api.tasks.JavaExec) {
+ main = 'com.android.build.gradle.internal.incremental.IncrementalChangeVisitor'
+// jvmArgs = [ '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005']
+ classpath = sourceSets.main.runtimeClasspath + sourceSets.test.compileClasspath
+ args incrementalPatchClassFileDir, new File(instrumentedIncrementalTestOutDir, f.getName()),
+ androidJar + File.pathSeparator + baseClasses
+ }
+
+ instrumentPatch.dependsOn(compilePatch)
+
+ instrumentIncrementalTestPatches.dependsOn instrumentPatch
+}
+
+Task jarIncrementalTests = tasks.create(name: "jarIncrementalTests", type: Jar) {
+ from sourceSets.test,outputs
+ into 'build/incrementalTests.jar'
+ include "com/android/build/gradle/internal/incremental/**"
+ exclude "com/android/build/gradle/internal/incremental/fixture/**"
+}
+
+configurations {
+ incrementalTestClasses
+}
+
+artifacts {
+ incrementalTestClasses jarIncrementalTests
+}
+
+generateGrammarSource {
+ outputDirectory = new File(outputDirectory,'/com/android/build/gradle/shrinker/parser')
+}
diff --git a/build-system/gradle-core/gradle-core.iml b/build-system/gradle-core/gradle-core.iml
new file mode 100644
index 0000000..a7e4a85
--- /dev/null
+++ b/build-system/gradle-core/gradle-core.iml
@@ -0,0 +1,1009 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$/../../../../out/build/base/gradle-core/build/generated-src/antlr/main">
+ <sourceFolder url="file://$MODULE_DIR$/../../../../out/build/base/gradle-core/build/generated-src/antlr/main" isTestSource="false" />
+ </content>
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/groovy" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/groovy" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/incremental-test-classes/base" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
+ <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/resources" type="java-resource" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/.gradle" />
+ </content>
+ <orderEntry type="library" name="asm-tools" level="project" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" exported="" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" exported="" name="proguard-gradle" level="project" />
+ <orderEntry type="module" module-name="db-compilerCommon-base" exported="" />
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/jacoco/org.jacoco.core/0.7.4.201502262128/org.jacoco.core-0.7.4.201502262128.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/jacoco/org.jacoco.core/0.7.4.201502262128/org.jacoco.core-0.7.4.201502262128-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-core-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-model-core-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-model-groovy-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/asm-all-5.0.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ant-1.9.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-collections-3.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-io-1.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-lang-2.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <ANNOTATIONS>
+ <root url="file://$MODULE_DIR$/../../.idea/codeContracts" />
+ </ANNOTATIONS>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/guava-jdk5-17.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jcip-annotations-1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jul-to-slf4j-1.7.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jarjar-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/javax.inject-1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/slf4j-api-1.7.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/log4j-over-slf4j-1.7.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jcl-over-slf4j-1.7.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/ant-launcher-1.9.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-collections-3.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-io-1.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/commons-lang-2.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-docs-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-base-services-groovy-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-base-services-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-resources-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-cli-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-native-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jna-3.2.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jansi-1.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-osx-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-osx-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-linux-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-linux-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-windows-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/native-platform-windows-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-messaging-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/kryo-2.20.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/reflectasm-1.07-shaded.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/minlog-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/objenesis-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/nekohtml-1.9.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xbean-reflect-3.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xml-apis-1.3.04.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/xercesImpl-2.9.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-tooling-api-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-plugins-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/junit-4.12.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/testng-6.3.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/commons-cli-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/bsh-2.0b4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/snakeyaml-1.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/hamcrest-core-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-code-quality-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-jetty-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-util-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/servlet-api-2.5-20081211.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-plus-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jsp-2.1-6.1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-annotations-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/geronimo-annotation_1.0_spec-1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jetty-naming-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jsp-api-2.1-6.1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-antlr-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-wrapper-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-osgi-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-maven-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/pmaven-common-0.8-20100325.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/pmaven-groovy-0.8-20100325.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ide-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-announce-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-scala-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-sonar-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/sonar-batch-bootstrapper-2.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-signing-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ear-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-javascript-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/rhino-1.7R3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gson-2.2.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/simple-4.1.21.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-build-comparison-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-diagnostics-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-reporting-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/jatl-0.2.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-publish-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-ivy-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-jacoco-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-build-init-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-jvm-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="builder" exported="" />
+ <orderEntry type="module" module-name="lint-cli" exported="" />
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/junit/junit/4.12/junit-4.12-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="library" name="groovy" level="project" />
+ <orderEntry type="module" module-name="project-test-lib" scope="TEST" />
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-java-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-language-native-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-platform-jvm-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-platform-base-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/gradle-dependency-management-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="sdk-common-base" exported="" />
+ <orderEntry type="module" module-name="manifest-merger-base" exported="" />
+ <orderEntry type="module" module-name="gradle-api" exported="" />
+ <orderEntry type="module" module-name="lint-api-base" />
+ <orderEntry type="module" module-name="lint-checks-base" />
+ <orderEntry type="library" scope="TEST" name="jacoco" level="project" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="library" scope="TEST" name="truth" level="project" />
+ <orderEntry type="library" name="ecj" level="project" />
+ <orderEntry type="module" module-name="instant-run-annotations" exported="" scope="TEST" />
+ <orderEntry type="module" module-name="instant-run-runtime" exported="" scope="TEST" />
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/dom4j-1.6.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-launcher-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-open-api-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/gradle-ui-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/jaxen-1.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/gradle-dist-link/lib/plugins/ivy-2.2.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="PROVIDED">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../data-binding/maven-repo/com/android/databinding/compilerCommon/1.0-rc5/compilerCommon-1.0-rc5.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="library" name="antlr" level="project" />
+ <orderEntry type="library" name="slf4j-api" level="project" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/gradle/gradle-tooling-api/2.1/gradle-tooling-api-2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/org/gradle/gradle-tooling-api/2.1/gradle-tooling-api-2.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ </component>
+</module>
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/AllTestResults.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/AllTestResults.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/AllTestResults.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/AllTestResults.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassPageRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassPageRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassPageRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassPageRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassTestResults.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassTestResults.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassTestResults.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassTestResults.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CodePanelRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CodePanelRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CodePanelRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CodePanelRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CompositeTestResults.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CompositeTestResults.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CompositeTestResults.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CompositeTestResults.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/DeviceTestResults.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/DeviceTestResults.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/DeviceTestResults.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/DeviceTestResults.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ErroringAction.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ErroringAction.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ErroringAction.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ErroringAction.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/HtmlReportRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/HtmlReportRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/HtmlReportRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/HtmlReportRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/OverviewPageRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/OverviewPageRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/OverviewPageRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/OverviewPageRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackagePageRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackagePageRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackagePageRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackagePageRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackageTestResults.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackageTestResults.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackageTestResults.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackageTestResults.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PageRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PageRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PageRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PageRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleHtmlWriter.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleHtmlWriter.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleHtmlWriter.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleHtmlWriter.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleMarkupWriter.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleMarkupWriter.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleMarkupWriter.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/SimpleMarkupWriter.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabbedPageRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabbedPageRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabbedPageRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabbedPageRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabsRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabsRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabsRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TabsRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestReport.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestReport.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestReport.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestReport.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResult.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResult.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResult.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResult.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResultModel.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResultModel.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResultModel.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResultModel.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TextReportRenderer.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TextReportRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TextReportRenderer.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TextReportRenderer.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/VariantTestResults.java b/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/VariantTestResults.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/VariantTestResults.java
rename to build-system/gradle-core/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/VariantTestResults.java
diff --git a/base/build-system/gradle-core/src/fromGradle/groovy/org/gradle/api/tasks/ParallelizableTask.java b/build-system/gradle-core/src/fromGradle/groovy/org/gradle/api/tasks/ParallelizableTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/groovy/org/gradle/api/tasks/ParallelizableTask.java
rename to build-system/gradle-core/src/fromGradle/groovy/org/gradle/api/tasks/ParallelizableTask.java
diff --git a/base/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/base-style.css b/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/base-style.css
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/base-style.css
rename to build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/base-style.css
diff --git a/base/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/report.js b/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/report.js
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/report.js
rename to build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/report.js
diff --git a/base/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/style.css b/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/style.css
similarity index 100%
rename from base/build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/style.css
rename to build-system/gradle-core/src/fromGradle/resources/com/android/build/gradle/internal/test/report/style.css
diff --git a/build-system/gradle-core/src/main/antlr/Proguard.g b/build-system/gradle-core/src/main/antlr/Proguard.g
new file mode 100644
index 0000000..ebed00c
--- /dev/null
+++ b/build-system/gradle-core/src/main/antlr/Proguard.g
@@ -0,0 +1,285 @@
+grammar Proguard;
+
+options{
+ k = 3;
+}
+
+tokens {
+ NEGATOR = '!';
+}
+
+ at header {
+package com.android.build.gradle.shrinker.parser;
+import static org.objectweb.asm.Opcodes.*;
+}
+
+ at lexer::header {
+package com.android.build.gradle.shrinker.parser;
+}
+
+ at members {
+ @Override
+ public void emitErrorMessage(String msg) {
+ throw new RuntimeException(msg);
+ }
+}
+
+ at lexer::members {
+ @Override
+ public void emitErrorMessage(String msg) {
+ throw new RuntimeException(msg);
+ }
+}
+
+prog [Flags flags, String baseDirectory]
+ :
+ (
+ ('-basedirectory' baseDir=NAME {baseDirectory=$baseDir.text;})
+ | ('-include'|'@') proguardFile=NAME {GrammarActions.include($proguardFile.text, baseDirectory, $flags);}
+ | ('-keepclassmembers' keepModifier=keepOptionModifier? classSpec=classSpecification {GrammarActions.addKeepClassMembers($flags, $classSpec.classSpec, $keepModifier.modifier);})
+ | ('-keepclasseswithmembers' keepModifier=keepOptionModifier? classSpec=classSpecification {GrammarActions.addKeepClassesWithMembers($flags, $classSpec.classSpec, $keepModifier.modifier);})
+ | ('-keep' keepModifier=keepOptionModifier? classSpec=classSpecification {GrammarActions.addKeepClassSpecification($flags, $classSpec.classSpec, $keepModifier.modifier);})
+ | (igFlag=ignoredFlag {GrammarActions.ignoredFlag($igFlag.text, true);})
+ | (nopFlag=noOpFlag {GrammarActions.ignoredFlag($nopFlag.text, false);})
+ | (unFlag=unsupportedFlag {GrammarActions.unsupportedFlag($unFlag.text);})
+ | ('-dontwarn' {FilterSpecification class_filter = new FilterSpecification();} filter[class_filter] {GrammarActions.dontWarn($flags, class_filter);})
+ | ('-ignorewarnings' {GrammarActions.ignoreWarnings($flags);})
+ )*
+ EOF
+ ;
+ catch [RecognitionException e] {
+ throw e;
+ }
+
+private noOpFlag
+ :
+ ( '-verbose'
+ | ('-dontnote' {FilterSpecification class_filter = new FilterSpecification();} filter[class_filter])
+ // These flags are used in the default SDK proguard rules, so there's no point warning about them:
+ | '-dontusemixedcaseclassnames'
+ | '-dontskipnonpubliclibraryclasses'
+ | '-dontskipnonpubliclibraryclassmembers'
+ | '-skipnonpubliclibraryclasses'
+ // Similar flags as above:
+ | '-keepparameternames'
+ | ('-keepnames' classSpec=classSpecification )
+ | ('-keepclassmembernames' classSpec=classSpecification )
+ | ('-keepclasseswithmembernames' classSpec=classSpecification )
+ | ('-keepattributes' {FilterSpecification attribute_filter = new FilterSpecification();} filter[attribute_filter] )
+ | ('-keeppackagenames' {FilterSpecification package_filter = new FilterSpecification();} filter[package_filter] )
+ | ('-dontshrink' )
+ | ('-dontoptimize' )
+ | ('-dontpreverify' )
+ | ('-dontobfuscate' )
+ )
+ ;
+
+private ignoredFlag
+ :
+ ( ('-optimizations' {FilterSpecification optimization_filter = new FilterSpecification();} filter[optimization_filter])
+ | '-useuniqueclassmembernames'
+ | '-allowaccessmodification'
+ | ('-optimizationpasses' NAME) //n
+ | ('-assumenosideeffects' classSpecification)
+ | '-mergeinterfacesaggressively'
+ | '-overloadaggressively'
+ | ('-renamesourcefileattribute' sourceFile=NAME?)
+ | ('-adaptclassstrings' {FilterSpecification filter = new FilterSpecification();} filter[filter])
+ | ('-applymapping' mapping=NAME )
+ | '-obfuscationdictionary' obfuscationDictionary=NAME
+ | '-classobfuscationdictionary' classObfuscationDictionary=NAME
+ | '-packageobfuscationdictionary' packageObfuscationDictionary=NAME
+ | ('-repackageclasses' ('\'' newPackage=NAME? '\'')? )
+ | ('-flattenpackagehierarchy' ('\'' newPackage=NAME? '\'')? )
+ | ('-adaptresourcefilenames' {FilterSpecification file_filter = new FilterSpecification();} filter[file_filter] )
+ | ('-adaptresourcefilecontents' {FilterSpecification file_filter = new FilterSpecification();} filter[file_filter] )
+ )
+ ;
+
+private unsupportedFlag
+ :
+ ( '-injars' inJars=classpath
+ | '-outjars' outJars=classpath
+ | '-libraryjars' libraryJars=classpath
+ | ('-target' NAME) //version
+ | '-forceprocessing'
+ | ('-printusage' NAME) //[filename]
+ | ('-whyareyoukeeping' classSpecification)
+ | '-microedition'
+ | ('-printconfiguration' NAME?) //[filename]
+ | ('-dump' NAME?) //[filename]
+ | '-printmapping' outputMapping=NAME?
+ | ('-printseeds' seedOutputFile=NAME? )
+ | ('-keepdirectories' {FilterSpecification directory_filter = new FilterSpecification();} filter[directory_filter])
+ )
+ ;
+
+private classpath
+ : NAME ((':'|';') classpath)?
+ ;
+
+private filter [FilterSpecification filter]
+ :
+ nonEmptytFilter[filter]
+ | {GrammarActions.filter($filter, false, "**");}
+ ;
+
+
+private nonEmptytFilter [FilterSpecification filter]
+ at init {
+ boolean negator = false;
+}
+ :
+ ((NEGATOR {negator=true;})? NAME {GrammarActions.filter($filter, negator, $NAME.text);} (',' nonEmptytFilter[filter])?)
+ ;
+
+private classSpecification returns [ClassSpecification classSpec]
+ at init{
+ ModifierSpecification modifier = new ModifierSpecification();
+ boolean hasNameNegator = false;
+}
+ :
+ (annotation)?
+ cType=classModifierAndType[modifier]
+ (NEGATOR {hasNameNegator = true;})? NAME {classSpec = GrammarActions.classSpec($NAME.text, hasNameNegator, cType, $annotation.annotSpec, modifier);}
+ (inheritanceSpec=inheritance {classSpec.setInheritance(inheritanceSpec);})?
+ members[classSpec]?
+ ;
+
+private classModifierAndType[ModifierSpecification modifier] returns [ClassTypeSpecification cType]
+ at init{
+ boolean hasNegator = false;
+}
+ :
+ (NEGATOR {hasNegator = true;})?
+ (
+ 'public' {GrammarActions.addModifier(modifier, ACC_PUBLIC, hasNegator);} cmat=classModifierAndType[modifier] {cType = $cmat.cType;}
+ | 'abstract' {GrammarActions.addModifier(modifier, ACC_ABSTRACT, hasNegator);} cmat=classModifierAndType[modifier] {cType = $cmat.cType;}
+ | 'final' {GrammarActions.addModifier(modifier, ACC_FINAL, hasNegator);} cmat=classModifierAndType[modifier] {cType = $cmat.cType;}
+ | classType {cType=GrammarActions.classType($classType.type, hasNegator); }
+ )
+ ;
+
+private classType returns [int type]
+ at init {
+ $type = 0;
+}
+ :
+ ('@' {$type |= ACC_ANNOTATION;})?
+ ('interface' {$type |= ACC_INTERFACE;}
+ | 'enum' {$type |= ACC_ENUM;}
+ | 'class'
+ )
+ ;
+
+private members [ClassSpecification classSpec]
+ :
+ '{'
+ member[classSpec]*
+ '}'
+ ;
+
+private member [ClassSpecification classSpec]
+ :
+ annotation? modifiers
+ (
+ (typeSig=type)? name=(NAME|'<init>') (signature=arguments {GrammarActions.method(classSpec, $annotation.annotSpec, typeSig, $name.text, signature, $modifiers.modifiers);}
+ | {GrammarActions.fieldOrAnyMember(classSpec, $annotation.annotSpec, typeSig, $name.text, $modifiers.modifiers);})
+ | '<methods>' {GrammarActions.method(classSpec, $annotation.annotSpec,
+ GrammarActions.getSignature("***", 0), "*", "("+ GrammarActions.getSignature("...", 0) + ")",
+ $modifiers.modifiers);}
+ | '<fields>' {GrammarActions.field(classSpec, $annotation.annotSpec, null, "*", $modifiers.modifiers);}
+ ) ';'
+ ;
+
+private annotation returns [AnnotationSpecification annotSpec]
+ at init{
+ boolean hasNameNegator = false;
+}
+ : '@' (NEGATOR {hasNameNegator = true;})? NAME {$annotSpec = GrammarActions.annotation($NAME.text, hasNameNegator);};
+
+private modifiers returns [ModifierSpecification modifiers]
+ at init{
+ modifiers = new ModifierSpecification();
+}
+ :
+ modifier[modifiers]*
+ ;
+
+private modifier [ModifierSpecification modifiers]
+ at init{
+ boolean hasNegator = false;
+}
+ :
+ (NEGATOR {hasNegator = true;})?
+ (
+ 'public' {modifiers.addModifier(ACC_PUBLIC, hasNegator);}
+ | 'private' {modifiers.addModifier(ACC_PRIVATE, hasNegator);}
+ | 'protected' {modifiers.addModifier(ACC_PROTECTED, hasNegator);}
+ | 'static' {modifiers.addModifier(ACC_STATIC, hasNegator);}
+ | 'synchronized' {modifiers.addModifier(ACC_SYNCHRONIZED, hasNegator);}
+ | 'volatile' {modifiers.addModifier(ACC_VOLATILE, hasNegator);}
+ | 'native' {modifiers.addModifier(ACC_NATIVE, hasNegator);}
+ | 'abstract' {modifiers.addModifier(ACC_ABSTRACT, hasNegator);}
+ | 'strictfp' {modifiers.addModifier(ACC_STRICT, hasNegator);}
+ | 'final' {modifiers.addModifier(ACC_FINAL, hasNegator);}
+ | 'transient' {modifiers.addModifier(ACC_TRANSIENT, hasNegator);}
+ | 'synthetic' {modifiers.addModifier(ACC_SYNTHETIC, hasNegator);}
+ | 'bridge' {modifiers.addModifier(ACC_BRIDGE, hasNegator);}
+ | 'varargs' {modifiers.addModifier(ACC_VARARGS, hasNegator);}
+ )
+ ;
+
+private inheritance returns [InheritanceSpecification inheritanceSpec]
+ at init{
+ boolean hasNameNegator = false;
+}
+ :
+ ('extends' | 'implements')
+ annotation? (NEGATOR {hasNameNegator = true;})? NAME {inheritanceSpec = GrammarActions.createInheritance($NAME.text, hasNameNegator, $annotation.annotSpec);};
+
+private arguments returns [String signature]
+ :
+ '(' {signature = "(";}
+ (
+ (
+ parameterSig=type {signature += parameterSig;}
+ (',' parameterSig=type {signature += parameterSig;})*
+ )?
+ )
+ ')' {signature += ")";}
+ ;
+
+private type returns [String signature]
+ at init {
+ int dim = 0;
+}
+ :
+ (
+ typeName='%' {String sig = $typeName.text; signature = GrammarActions.getSignature(sig == null ? "" : sig, 0);}
+ |
+ (typeName=NAME ('[]' {dim++;})* {String sig = $typeName.text; signature = GrammarActions.getSignature(sig == null ? "" : sig, dim);})
+ )
+ ;
+
+private keepOptionModifier returns [KeepModifier modifier]
+ : ','
+ ('allowshrinking' {modifier = KeepModifier.ALLOW_SHRINKING;}
+ | 'allowoptimization' // Optimizations not supported
+ | 'allowobfuscation' {modifier = KeepModifier.ALLOW_OBFUSCATION;})
+ ;
+
+private NAME : ('a'..'z'|'A'..'Z'|'_'|'0'..'9'|'?'|'$'|'.'|'*'|'/'|'\\'|'-'|'<'|'>')+ ;
+
+LINE_COMMENT
+ : '#' ~( '\r' | '\n' )* {$channel=HIDDEN;}
+ ;
+
+private WS : ( ' '
+ | '\t'
+ | '\r'
+ | '\n'
+ ) {$channel=HIDDEN;}
+ ;
+
+
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidConfig.java
new file mode 100644
index 0000000..735af18
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidConfig.java
@@ -0,0 +1,147 @@
+/*
+ * 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.build.gradle;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.api.VariantFilter;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.build.gradle.internal.coverage.JacocoOptions;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.dsl.AdbOptions;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.dsl.LintOptions;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.dsl.Splits;
+import com.android.build.gradle.internal.dsl.TestOptions;
+import com.android.build.api.transform.Transform;
+import com.android.builder.core.LibraryRequest;
+import com.android.builder.model.DataBindingOptions;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.builder.testing.api.TestServer;
+import com.android.repository.Revision;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * User configuration settings for all android plugins.
+ */
+public interface AndroidConfig {
+
+ /** Build tool version */
+ String getBuildToolsVersion();
+
+ /** Compile SDK version */
+ String getCompileSdkVersion();
+
+ /** Build tool revisions */
+ Revision getBuildToolsRevision();
+
+ /** Name of the variant to publish */
+ String getDefaultPublishConfig();
+
+ /** Whether to also publish non-default variants */
+ boolean getPublishNonDefault();
+
+ /** Filter to determine which variants to build */
+ Action<VariantFilter> getVariantFilter();
+
+ /** Adb options */
+ AdbOptions getAdbOptions();
+
+ /** A prefix to be used when creating new resources. Used by Studio */
+ String getResourcePrefix();
+
+ /** List of flavor dimensions */
+ List<String> getFlavorDimensionList();
+
+ /** Whether to generate pure splits or multi apk */
+ boolean getGeneratePureSplits();
+
+ @Deprecated
+ boolean getEnforceUniquePackageName();
+
+ /** Default config, shared by all flavors. */
+ CoreProductFlavor getDefaultConfig();
+
+ /** Options for aapt, tool for packaging resources. */
+ AaptOptions getAaptOptions();
+
+ /** Compile options */
+ CompileOptions getCompileOptions();
+
+ /** Dex options. */
+ DexOptions getDexOptions();
+
+ /** JaCoCo options. */
+ JacocoOptions getJacoco();
+
+ /** Lint options. */
+ LintOptions getLintOptions();
+
+ /** Packaging options. */
+ PackagingOptions getPackagingOptions();
+
+ /** APK splits */
+ Splits getSplits();
+
+ /** Options for running tests. */
+ TestOptions getTestOptions();
+
+ /** List of device providers */
+ @NonNull
+ List<DeviceProvider> getDeviceProviders();
+
+ /** List of remote CI servers */
+ @NonNull
+ List<TestServer> getTestServers();
+
+ @NonNull
+ List<Transform> getTransforms();
+ @NonNull
+ List<List<Object>> getTransformsDependencies();
+
+ /** All product flavors used by this project. */
+ Collection<? extends CoreProductFlavor> getProductFlavors();
+
+ /** Build types used by this project. */
+ Collection<? extends CoreBuildType> getBuildTypes();
+
+ /** Signing configs used by this project. */
+ Collection<? extends SigningConfig> getSigningConfigs();
+
+ /** Source sets for all variants */
+ NamedDomainObjectContainer<AndroidSourceSet> getSourceSets();
+
+ /** Whether to package build config class file */
+ Boolean getPackageBuildConfig();
+
+ /** Aidl files to package in the aar. */
+ Collection<String> getAidlPackageWhiteList();
+
+ Collection<LibraryRequest> getLibraryRequests();
+
+ /** Data Binding options */
+ DataBindingOptions getDataBinding();
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java
new file mode 100644
index 0000000..78a04b9
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/AndroidGradleOptions.java
@@ -0,0 +1,269 @@
+/*
+ * 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.build.gradle;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.sdklib.AndroidVersion;
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Project;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * Determines if various options, triggered from the command line or environment, are set.
+ */
+public class AndroidGradleOptions {
+
+ private static final String PROPERTY_TEST_RUNNER_ARGS =
+ "android.testInstrumentationRunnerArguments.";
+
+ private static final String PROPERTY_THREAD_POOL_SIZE = "android.threadPoolSize";
+ private static final String PROPERTY_THREAD_POOL_SIZE_OLD = "com.android.build.threadPoolSize";
+
+ public static final String USE_DEPRECATED_NDK = "android.useDeprecatedNdk";
+
+ private static final String PROPERTY_DISABLE_RESOURCE_VALIDATION =
+ "android.disableResourceValidation";
+
+ // TODO: Drop the "com." prefix, for consistency.
+ private static final String PROPERTY_BENCHMARK_NAME = "com.android.benchmark.name";
+ private static final String PROPERTY_BENCHMARK_MODE = "com.android.benchmark.mode";
+
+ public static final String PROPERTY_INCREMENTAL_JAVA_COMPILE =
+ "android.incrementalJavaCompile";
+
+ private static final String PROPERTY_USE_OLD_PACKAGING = "android.useOldPackaging";
+
+ @NonNull
+ public static Map<String, String> getExtraInstrumentationTestRunnerArgs(@NonNull Project project) {
+ Map<String, String> argsMap = Maps.newHashMap();
+ for (Map.Entry<String, ?> entry : project.getProperties().entrySet()) {
+ if (entry.getKey().startsWith(PROPERTY_TEST_RUNNER_ARGS)) {
+ String argName = entry.getKey().substring(PROPERTY_TEST_RUNNER_ARGS.length());
+ String argValue = entry.getValue().toString();
+
+ argsMap.put(argName, argValue);
+ }
+ }
+
+ return argsMap;
+ }
+
+ @Nullable
+ public static String getBenchmarkName(@NonNull Project project) {
+ return getString(project, PROPERTY_BENCHMARK_NAME);
+ }
+
+ @Nullable
+ public static String getBenchmarkMode(@NonNull Project project) {
+ return getString(project, PROPERTY_BENCHMARK_MODE);
+ }
+
+ public static boolean invokedFromIde(@NonNull Project project) {
+ return getBoolean(project, AndroidProject.PROPERTY_INVOKED_FROM_IDE);
+ }
+
+ public static boolean buildModelOnly(@NonNull Project project) {
+ return getBoolean(project, AndroidProject.PROPERTY_BUILD_MODEL_ONLY);
+ }
+
+ public static boolean buildModelOnlyAdvanced(@NonNull Project project) {
+ return getBoolean(project, AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED);
+ }
+
+ public static boolean useOldPackaging(@NonNull Project project) {
+ return getBoolean(project, PROPERTY_USE_OLD_PACKAGING, true);
+ }
+
+ @Nullable
+ public static String getApkLocation(@NonNull Project project) {
+ return getString(project, AndroidProject.PROPERTY_APK_LOCATION);
+ }
+
+ @Nullable
+ public static String getBuildTargetDensity(@NonNull Project project) {
+ return getString(project, AndroidProject.PROPERTY_BUILD_DENSITY);
+ }
+
+ @Nullable
+ public static String getBuildTargetAbi(@NonNull Project project) {
+ return getString(project, AndroidProject.PROPERTY_BUILD_ABI);
+ }
+
+ /**
+ * Returns the {@link AndroidVersion} for the target device.
+ *
+ * @param project the project being built
+ * @return a {@link AndroidVersion} for the targeted device, following the
+ * {@link AndroidProject#PROPERTY_BUILD_API} value passed by Android Studio.
+ */
+ @NonNull
+ public static AndroidVersion getTargetApiLevel(@NonNull Project project) {
+ String apiVersion = getString(project, AndroidProject.PROPERTY_BUILD_API);
+ AndroidVersion version = AndroidVersion.DEFAULT;
+ if (apiVersion != null) {
+ try {
+ version = new AndroidVersion(apiVersion);
+ } catch (AndroidVersion.AndroidVersionException e) {
+ project.getLogger().warn("Wrong build target version passed ", e);
+ }
+ }
+ return version;
+ }
+
+
+ @Nullable
+ public static String getColdswapMode(@NonNull Project project) {
+ return getString(project, AndroidProject.PROPERTY_SIGNING_COLDSWAP_MODE);
+ }
+
+ public static boolean useDeprecatedNdk(@NonNull Project project) {
+ return getBoolean(project, USE_DEPRECATED_NDK);
+ }
+
+ @Nullable
+ public static Integer getThreadPoolSize(@NonNull Project project) {
+ Integer size = getInteger(project, PROPERTY_THREAD_POOL_SIZE);
+ if (size == null) {
+ size = getInteger(project, PROPERTY_THREAD_POOL_SIZE_OLD);
+ }
+
+ return size;
+ }
+
+ @Nullable
+ public static SigningOptions getSigningOptions(@NonNull Project project) {
+ String signingStoreFile =
+ getString(project, AndroidProject.PROPERTY_SIGNING_STORE_FILE);
+ String signingStorePassword =
+ getString(project, AndroidProject.PROPERTY_SIGNING_STORE_PASSWORD);
+ String signingKeyAlias =
+ getString(project, AndroidProject.PROPERTY_SIGNING_KEY_ALIAS);
+ String signingKeyPassword =
+ getString(project, AndroidProject.PROPERTY_SIGNING_KEY_PASSWORD);
+
+ if (signingStoreFile != null
+ && signingStorePassword != null
+ && signingKeyAlias != null
+ && signingKeyPassword != null) {
+ String signingStoreType =
+ getString(project, AndroidProject.PROPERTY_SIGNING_STORE_TYPE);
+
+ return new SigningOptions(
+ signingStoreFile,
+ signingStorePassword,
+ signingKeyAlias,
+ signingKeyPassword,
+ signingStoreType);
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public static EnumSet<OptionalCompilationStep> getOptionalCompilationSteps(
+ @NonNull Project project) {
+
+ String values = getString(project, AndroidProject.OPTIONAL_COMPILATION_STEPS);
+ if (values != null) {
+ List<OptionalCompilationStep> optionalCompilationSteps =
+ new ArrayList<OptionalCompilationStep>();
+ StringTokenizer st = new StringTokenizer(values, ",");
+ while(st.hasMoreElements()) {
+ optionalCompilationSteps.add(OptionalCompilationStep.valueOf(st.nextToken()));
+ }
+ return EnumSet.copyOf(optionalCompilationSteps);
+ }
+ return EnumSet.noneOf(OptionalCompilationStep.class);
+ }
+
+ public static boolean isResourceValidationEnabled(@NonNull Project project) {
+ return !getBoolean(project, PROPERTY_DISABLE_RESOURCE_VALIDATION);
+ }
+
+ @Nullable
+ private static String getString(@NonNull Project project, String propertyName) {
+ return (String) project.getProperties().get(propertyName);
+ }
+
+ @Nullable
+ private static Integer getInteger(@NonNull Project project, String propertyName) {
+ if (project.hasProperty(propertyName)) {
+ try {
+ return Integer.parseInt(project.getProperties().get(propertyName).toString());
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("Property " + propertyName + " needs to be an integer.");
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean getBoolean(
+ @NonNull Project project,
+ @NonNull String propertyName) {
+ return getBoolean(project, propertyName, false /*defaultValue*/);
+ }
+
+ private static boolean getBoolean(
+ @NonNull Project project,
+ @NonNull String propertyName,
+ boolean defaultValue) {
+ if (project.hasProperty(propertyName)) {
+ Object value = project.getProperties().get(propertyName);
+ if (value instanceof String) {
+ return Boolean.parseBoolean((String) value);
+ } else if (value instanceof Boolean) {
+ return ((Boolean) value);
+ }
+ }
+
+ return defaultValue;
+ }
+
+ public static boolean isJavaCompileIncrementalPropertySet(@NonNull Project project) {
+ return project.hasProperty(PROPERTY_INCREMENTAL_JAVA_COMPILE);
+ }
+
+ public static class SigningOptions {
+ @NonNull public final String storeFile;
+ @NonNull public final String storePassword;
+ @NonNull public final String keyAlias;
+ @NonNull public final String keyPassword;
+ @Nullable public final String storeType;
+
+ SigningOptions(
+ @NonNull String storeFile,
+ @NonNull String storePassword,
+ @NonNull String keyAlias,
+ @NonNull String keyPassword,
+ @Nullable String storeType) {
+ this.storeFile = storeFile;
+ this.storeType = storeType;
+ this.storePassword = storePassword;
+ this.keyAlias = keyAlias;
+ this.keyPassword = keyPassword;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/OptionalCompilationStep.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/OptionalCompilationStep.java
new file mode 100644
index 0000000..7f4f932
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/OptionalCompilationStep.java
@@ -0,0 +1,42 @@
+/*
+ * 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.build.gradle;
+
+/**
+ * enum describing possible optional compilation steps. This can be used to turn on java byte code
+ * manipulation in order to support instant reloading, or profiling, or anything related to
+ * transforming java compiler .class files before they are processed into .dex files.
+ */
+public enum OptionalCompilationStep {
+
+ /**
+ * presence will turn on the InstantRun feature.
+ */
+ INSTANT_DEV,
+ /**
+ * presence will disable all tasks before javac.
+ */
+ LOCAL_JAVA_ONLY,
+ /**
+ * presence will disable all tasks before resource merger.
+ */
+ LOCAL_RES_ONLY,
+ /**
+ * presence will force production of all the necessary artifacts to do an application restart.
+ */
+ RESTART_ONLY
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.java
new file mode 100644
index 0000000..090df8a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/ReportingPlugin.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle;
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS;
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS;
+import static com.android.builder.core.BuilderConstants.FD_REPORTS;
+
+import com.android.build.gradle.internal.dsl.TestOptions;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.tasks.AndroidReportTask;
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
+import com.android.build.gradle.internal.test.report.ReportType;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.api.tasks.TaskCollection;
+import org.gradle.execution.TaskGraphExecuter;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import groovy.lang.Closure;
+
+/**
+ * Gradle plugin class for 'reporting' projects.
+ *
+ * This is mostly used to aggregate reports from subprojects.
+ *
+ */
+class ReportingPlugin implements org.gradle.api.Plugin<Project> {
+
+ private TestOptions extension;
+
+ @Override
+ public void apply(final Project project) {
+ // make sure this project depends on the evaluation of all sub projects so that
+ // it's evaluated last.
+ project.evaluationDependsOnChildren();
+
+ extension = project.getExtensions().create("android", TestOptions.class);
+
+ final AndroidReportTask mergeReportsTask = project.getTasks().create("mergeAndroidReports",
+ AndroidReportTask.class);
+ mergeReportsTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ mergeReportsTask.setDescription("Merges all the Android test reports from the sub "
+ + "projects.");
+ mergeReportsTask.setReportType(ReportType.MULTI_PROJECT);
+
+ ConventionMappingHelper.map(mergeReportsTask, "resultsDir", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ String resultsDir = extension.getResultsDir();
+ if (resultsDir == null) {
+ return new File(project.getBuildDir(), FD_ANDROID_RESULTS);
+ } else {
+ return project.file(resultsDir);
+ }
+ }
+ });
+
+ ConventionMappingHelper.map(mergeReportsTask, "reportsDir", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ String reportsDir = extension.getReportDir();
+ if (reportsDir == null) {
+ return new File(new File(project.getBuildDir(), FD_REPORTS), FD_ANDROID_TESTS);
+ } else {
+ return project.file(reportsDir);
+ }
+ }
+ });
+
+ // gather the subprojects
+ project.afterEvaluate(new Action<Project>() {
+ @Override
+ public void execute(Project prj) {
+ for (Project p : prj.getSubprojects()) {
+ TaskCollection<AndroidReportTask> tasks = p.getTasks().withType(
+ AndroidReportTask.class);
+ for (AndroidReportTask task : tasks) {
+ mergeReportsTask.addTask(task);
+ }
+
+ TaskCollection<DeviceProviderInstrumentTestTask> tasks2 =
+ p.getTasks().withType(DeviceProviderInstrumentTestTask.class);
+ for (DeviceProviderInstrumentTestTask task : tasks2) {
+ mergeReportsTask.addTask(task);
+ }
+ }
+ }
+ });
+
+ // If gradle is launched with --continue, we want to run all tests and generate an
+ // aggregate report (to help with the fact that we may have several build variants).
+ // To do that, the "mergeAndroidReports" task (which does the aggregation) must always
+ // run even if one of its dependent task (all the testFlavor tasks) fails, so we make
+ // them ignore their error.
+ // We cannot do that always: in case the test task is not going to run, we do want the
+ // individual testFlavor tasks to fail.
+ if (mergeReportsTask != null
+ && project.getGradle().getStartParameter().isContinueOnFailure()) {
+ project.getGradle().getTaskGraph().whenReady(new Closure(this) {
+ void doCall(TaskGraphExecuter taskGraph) {
+ if (taskGraph.hasTask(mergeReportsTask)) {
+ mergeReportsTask.setWillRun();
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestAndroidConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestAndroidConfig.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestAndroidConfig.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestAndroidConfig.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestedAndroidConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestedAndroidConfig.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestedAndroidConfig.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/TestedAndroidConfig.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java
new file mode 100644
index 0000000..afd2afa
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.util.PatternFilterable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An AndroidSourceDirectorySet represents a lit of directory input for an Android project.
+ */
+public interface AndroidSourceDirectorySet extends PatternFilterable {
+
+ /**
+ * A concise name for the source directory (typically used to identify it in a collection).
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Adds the given source directory to this set.
+ *
+ * @param srcDir The source directory. This is evaluated as for
+ * {@link org.gradle.api.Project#file(Object)}
+ * @return this
+ */
+ @NonNull
+ AndroidSourceDirectorySet srcDir(Object srcDir);
+
+ /**
+ * Adds the given source directories to this set.
+ *
+ * @param srcDirs The source directories. These are evaluated as for
+ * {@link org.gradle.api.Project#files(Object...)}
+ * @return this
+ */
+ @NonNull
+ AndroidSourceDirectorySet srcDirs(Object... srcDirs);
+
+ /**
+ * Sets the source directories for this set.
+ *
+ * @param srcDirs The source directories. These are evaluated as for
+ * {@link org.gradle.api.Project#files(Object...)}
+ * @return this
+ */
+ @NonNull
+ AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs);
+
+ /**
+ * Returns the list of source files as a {@link org.gradle.api.file.FileTree}
+ *
+ * @return a non null {@link FileTree} for all the source files in this set.
+ */
+ @NonNull
+ FileTree getSourceFiles();
+
+ /**
+ * Returns the filter used to select the source from the source directories.
+ *
+ * @return a non null {@link org.gradle.api.tasks.util.PatternFilterable}
+ */
+ @NonNull
+ PatternFilterable getFilter();
+
+ /**
+ * Returns the source folders as a list of {@link org.gradle.api.file.ConfigurableFileTree}
+ *
+ * <p>This is used as the input to the java compile to enable incremental compilation.
+ *
+ * @return a non null list of {@link ConfigurableFileTree}s, one per source dir in this set.
+ */
+ @NonNull
+ List<ConfigurableFileTree> getSourceDirectoryTrees();
+
+ /**
+ * Returns the resolved directories.
+ *
+ * <p>Setter can be called with a collection of {@link Object}s, just like
+ * Gradle's {@code project.file(...)}.
+ *
+ * @return a non null set of File objects.
+ */
+ @NonNull
+ Set<File> getSrcDirs();
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceFile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceFile.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceFile.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceFile.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceSet.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceSet.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceSet.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/AndroidSourceSet.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkOutputFile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkOutputFile.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkOutputFile.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkOutputFile.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariant.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariant.java
new file mode 100644
index 0000000..40a91bc
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariant.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SigningConfig;
+
+import org.gradle.api.DefaultTask;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * A Build variant and all its public data.
+ */
+public interface ApkVariant extends BaseVariant {
+
+ /**
+ * Return the app versionCode. Even the value is not found, then 1 is returned as this
+ * is the implicit value that the platform would use.
+ *
+ * If not output define its own variant override then this is used for all outputs.
+ */
+ int getVersionCode();
+
+ /**
+ * Return the app versionName or null if none found.
+ */
+ @Nullable
+ String getVersionName();
+
+ /**
+ * Returns the {@link SigningConfig} for this build variant,
+ * if one has been specified.
+ */
+ @Nullable
+ SigningConfig getSigningConfig();
+
+ /**
+ * Returns true if this variant has the information it needs to create a signed APK.
+ */
+ boolean isSigningReady();
+
+ /**
+ * Returns the Dex task.
+ *
+ * This method will actually throw an exception with a clear message.
+ *
+ * @deprecated With the new transform mechanism, there is no direct access to the task anymore.
+ */
+ @Deprecated
+ @Nullable
+ Object getDex();
+
+ /**
+ * Returns the list of jar files that are on the compile classpath. This does not include
+ * the runtime.
+ */
+ @NonNull
+ Collection<File> getCompileLibraries();
+
+ /**
+ * Returns the list of jar files that are packaged in the APK.
+ */
+ @NonNull
+ Collection<File> getApkLibraries();
+
+ /**
+ * Returns the install task for the variant.
+ */
+ @Nullable
+ DefaultTask getInstall();
+
+ /**
+ * Returns the uninstallation task.
+ *
+ * For non-library project this is always true even if the APK is not created because
+ * signing isn't setup.
+ */
+ @Nullable
+ DefaultTask getUninstall();
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariantOutput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariantOutput.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariantOutput.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApkVariantOutput.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApplicationVariant.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApplicationVariant.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApplicationVariant.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/ApplicationVariant.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariant.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariant.java
new file mode 100644
index 0000000..d79fe74
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariant.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.MergeSourceSetFolders;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.NdkCompile;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SourceProvider;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.AbstractCopyTask;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * A Build variant and all its public data. This is the base class for items common to apps,
+ * test apps, and libraries
+ */
+public interface BaseVariant {
+
+ /**
+ * Returns the name of the variant. Guaranteed to be unique.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns a description for the build variant.
+ */
+ @NonNull
+ String getDescription();
+
+ /**
+ * Returns a subfolder name for the variant. Guaranteed to be unique.
+ *
+ * This is usually a mix of build type and flavor(s) (if applicable).
+ * For instance this could be:
+ * "debug"
+ * "debug/myflavor"
+ * "release/Flavor1Flavor2"
+ */
+ @NonNull
+ String getDirName();
+
+ /**
+ * Returns the base name for the output of the variant. Guaranteed to be unique.
+ */
+ @NonNull
+ String getBaseName();
+
+ /**
+ * Returns the flavor name of the variant. This is a concatenation of all the
+ * applied flavors
+ * @return the name of the flavors, or an empty string if there is not flavors.
+ */
+ @NonNull
+ String getFlavorName();
+
+ /**
+ * Returns the variant outputs. There should always be at least one output.
+ * @return a non-null list of variants.
+ */
+ @NonNull
+ List<BaseVariantOutput> getOutputs();
+
+ /**
+ * Returns the {@link com.android.builder.core.DefaultBuildType} for this build variant.
+ */
+ @NonNull
+ BuildType getBuildType();
+
+ /**
+ * Returns a {@link com.android.builder.core.DefaultProductFlavor} that represents the merging
+ * of the default config and the flavors of this build variant.
+ */
+ @NonNull
+ ProductFlavor getMergedFlavor();
+
+ /**
+ * Returns the list of {@link com.android.builder.core.DefaultProductFlavor} for this build variant.
+ *
+ * This is always non-null but could be empty.
+ */
+ @NonNull
+ List<ProductFlavor> getProductFlavors();
+
+ /**
+ * Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier
+ * items are meant to be overridden by later items.
+ *
+ * @return a list of source provider
+ */
+ @NonNull
+ List<SourceProvider> getSourceSets();
+
+ /**
+ * Returns the applicationId of the variant.
+ */
+ @NonNull
+ String getApplicationId();
+
+ /**
+ * Returns the pre-build anchor task
+ */
+ @NonNull
+ Task getPreBuild();
+
+ /**
+ * Returns the check manifest task.
+ */
+ @NonNull
+ Task getCheckManifest();
+
+ /**
+ * Returns the AIDL compilation task.
+ */
+ @NonNull
+ AidlCompile getAidlCompile();
+
+ /**
+ * Returns the Renderscript compilation task.
+ */
+ @NonNull
+ RenderscriptCompile getRenderscriptCompile();
+
+ /**
+ * Returns the resource merging task.
+ */
+ @Nullable
+ MergeResources getMergeResources();
+
+ /**
+ * Returns the asset merging task.
+ */
+ @Nullable
+ MergeSourceSetFolders getMergeAssets();
+
+ /**
+ * Returns the BuildConfig generation task.
+ */
+ @Nullable
+ GenerateBuildConfig getGenerateBuildConfig();
+
+ /**
+ * Returns the Java Compilation task if javac was configured to compile the source files.
+ * @deprecated prefer {@link #getJavaCompiler} which always return the java compiler task
+ * irrespective of which tool chain (javac or jack) used.
+ */
+ @Nullable
+ @Deprecated
+ JavaCompile getJavaCompile() throws IllegalStateException;
+
+ /**
+ * Returns the Java Compiler task which can be either javac or jack depending on the project
+ * configuration.
+ */
+ @NonNull
+ AbstractCompile getJavaCompiler();
+
+ /**
+ * Returns the NDK Compilation task.
+ */
+ @NonNull
+ NdkCompile getNdkCompile();
+
+ /**
+ * Returns the obfuscation task. This can be null if obfuscation is not enabled.
+ */
+ @Nullable
+ Task getObfuscation();
+
+ /**
+ * Returns the obfuscation mapping file. This can be null if obfuscation is not enabled.
+ */
+ @Nullable
+ File getMappingFile();
+
+ /**
+ * Returns the Java resource processing task.
+ */
+ @NonNull
+ AbstractCopyTask getProcessJavaResources();
+
+ /**
+ * Returns the assemble task for all this variant's output
+ */
+ @Nullable
+ Task getAssemble();
+
+ /**
+ * Adds new Java source folders to the model.
+ *
+ * These source folders will not be used for the default build
+ * system, but will be passed along the default Java source folders
+ * to whoever queries the model.
+ *
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void addJavaSourceFoldersToModel(@NonNull File... sourceFolders);
+
+ /**
+ * Adds new Java source folders to the model.
+ *
+ * These source folders will not be used for the default build
+ * system, but will be passed along the default Java source folders
+ * to whoever queries the model.
+ *
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void addJavaSourceFoldersToModel(@NonNull Collection<File> sourceFolders);
+
+ /**
+ * Adds to the variant a task that generates Java source code.
+ *
+ * This will make the generate[Variant]Sources task depend on this task and add the
+ * new source folders as compilation inputs.
+ *
+ * The new source folders are also added to the model.
+ *
+ * @param task the task
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
+
+ /**
+ * Adds to the variant a task that generates Java source code.
+ *
+ * This will make the generate[Variant]Sources task depend on this task and add the
+ * new source folders as compilation inputs.
+ *
+ * The new source folders are also added to the model.
+ *
+ * @param task the task
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> sourceFolders);
+
+ /**
+ * Adds to the variant a task that generates Resources.
+ *
+ * This will make the generate[Variant]Resources task depend on this task and add the
+ * new Resource folders as Resource merge inputs.
+ *
+ * The Resource folders are also added to the model.
+ *
+ * @param task the task
+ * @param resFolders the folders where the generated resources are.
+ */
+ void registerResGeneratingTask(@NonNull Task task, @NonNull File... resFolders);
+
+ /**
+ * Adds to the variant a task that generates Resources.
+ *
+ * This will make the generate[Variant]Resources task depend on this task and add the
+ * new Resource folders as Resource merge inputs.
+ *
+ * The Resource folders are also added to the model.
+ *
+ * @param task the task
+ * @param resFolders the folders where the generated resources are.
+ */
+ void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> resFolders);
+
+ /**
+ * Adds a variant-specific BuildConfig field.
+ * @param type the type of the field
+ * @param name the name of the field
+ * @param value the value of the field
+ */
+ void buildConfigField(@NonNull String type, @NonNull String name, @NonNull String value);
+
+ /**
+ * Adds a variant-specific res value.
+ * @param type the type of the field
+ * @param name the name of the field
+ * @param value the value of the field
+ */
+ void resValue(@NonNull String type, @NonNull String name, @NonNull String value);
+
+ /**
+ * If true, variant outputs will be considered signed. Only set if you manually set the outputs
+ * to point to signed files built by other tasks.
+ */
+ void setOutputsAreSigned(boolean isSigned);
+
+ /**
+ * @see #setOutputsAreSigned(boolean)
+ */
+ boolean getOutputsAreSigned();
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariantOutput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariantOutput.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariantOutput.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/BaseVariantOutput.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariant.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariant.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariant.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariant.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariantOutput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariantOutput.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariantOutput.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/LibraryVariantOutput.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/TestVariant.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/TestVariant.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/TestVariant.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/TestVariant.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/UnitTestVariant.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/UnitTestVariant.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/UnitTestVariant.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/UnitTestVariant.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/VariantFilter.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/VariantFilter.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/VariantFilter.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/api/VariantFilter.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/AndroidAsciiReportRenderer.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/AndroidAsciiReportRenderer.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/AndroidAsciiReportRenderer.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/AndroidAsciiReportRenderer.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ApplicationTaskManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ApplicationTaskManager.java
new file mode 100644
index 0000000..e293f79
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ApplicationTaskManager.java
@@ -0,0 +1,345 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.incremental.InstantRunWrapperTask;
+import com.android.build.gradle.internal.incremental.InstantRunPatchingPolicy;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.pipeline.TransformStream;
+import com.android.build.gradle.internal.scope.AndroidTask;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.transforms.InstantRunSplitApkBuilder;
+import com.android.build.gradle.internal.variant.ApplicationVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * TaskManager for creating tasks in an Android application project.
+ */
+public class ApplicationTaskManager extends TaskManager {
+
+ public ApplicationTaskManager(
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ super(project, androidBuilder, dataBindingBuilder, extension, sdkHandler,dependencyManager,
+ toolingRegistry);
+ }
+
+ @Override
+ public void createTasksForVariantData(
+ @NonNull final TaskFactory tasks,
+ @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ assert variantData instanceof ApplicationVariantData;
+
+ final VariantScope variantScope = variantData.getScope();
+
+ createAnchorTasks(tasks, variantScope);
+ createCheckManifestTask(tasks, variantScope);
+
+ handleMicroApp(tasks, variantScope);
+
+ // Create all current streams (dependencies mostly at this point)
+ createDependencyStreams(variantScope);
+
+ // Add a task to process the manifest(s)
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createMergeAppManifestsTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to create the res values
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createGenerateResValuesTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to compile renderscript files.
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createRenderscriptTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to merge the resource folders
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createMergeResourcesTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to merge the asset folders
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createMergeAssetsTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to create the BuildConfig class
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createBuildConfigTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ // Add a task to process the Android Resources and generate source files
+ createApkProcessResTask(tasks, variantScope);
+
+ // Add a task to process the java resources
+ createProcessJavaResTasks(tasks, variantScope);
+ return null;
+ }
+ });
+
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_AIDL_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createAidlTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add NDK tasks
+ if (!isComponentModelPlugin) {
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_NDK_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createNdkTasks(variantScope);
+ return null;
+ }
+ });
+ } else {
+ if (variantData.compileTask != null) {
+ variantData.compileTask.dependsOn(getNdkBuildable(variantData));
+ } else {
+ variantScope.getCompileTask().dependsOn(tasks, getNdkBuildable(variantData));
+ }
+ }
+ variantScope.setNdkBuildable(getNdkBuildable(variantData));
+
+ // Add a task to merge the jni libs folders
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_JNILIBS_FOLDERS_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createMergeJniLibFoldersTasks(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a compile task
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_COMPILE_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ AndroidTask<? extends JavaCompile> javacTask =
+ createJavacTask(tasks, variantScope);
+
+ if (variantData.getVariantConfiguration().getUseJack()) {
+ createJackTask(tasks, variantScope);
+ } else {
+ setJavaCompilerTask(javacTask, tasks, variantScope);
+ createJarTasks(tasks, variantScope);
+ createPostCompilationTasks(tasks, variantScope);
+ }
+ return null;
+ }
+ });
+
+ // Add data binding tasks if enabled
+ if (extension.getDataBinding().isEnabled()) {
+ createDataBindingTasks(tasks, variantScope);
+ }
+
+ if (variantData.getSplitHandlingPolicy().equals(
+ BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY)) {
+ if (getExtension().getBuildToolsRevision().getMajor() < 21) {
+ throw new RuntimeException("Pure splits can only be used with buildtools 21 and later");
+ }
+
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_SPLIT_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createSplitTasks(tasks, variantScope);
+ return null;
+ }
+ });
+ }
+
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_PACKAGING_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ @Nullable
+ AndroidTask<InstantRunWrapperTask> fullBuildInfoGeneratorTask
+ = createInstantRunPackagingTasks(tasks, variantScope);
+ createPackagingTask(tasks, variantScope, true /*publishApk*/,
+ fullBuildInfoGeneratorTask);
+ return null;
+ }
+ });
+
+ // create the lint tasks.
+ ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_LINT_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createLintTasks(tasks, variantScope);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Create tasks related to creating pure split APKs containing sharded dex files.
+ */
+ @Nullable
+ protected AndroidTask<InstantRunWrapperTask> createInstantRunPackagingTasks(
+ @NonNull TaskFactory tasks, @NonNull VariantScope variantScope) {
+
+ if (getIncrementalMode(variantScope.getVariantConfiguration()) == IncrementalMode.NONE) {
+ return null;
+ }
+
+ // create a buildInfoGeneratorTask that will only be invoked if a assembleVARIANT is called.
+ AndroidTask<InstantRunWrapperTask> fullBuildInfoGeneratorTask = getAndroidTasks()
+ .create(tasks, new InstantRunWrapperTask.ConfigAction(
+ variantScope, InstantRunWrapperTask.TaskType.FULL, getLogger()));
+
+ InstantRunPatchingPolicy patchingPolicy =
+ variantScope.getInstantRunBuildContext().getPatchingPolicy();
+
+ if (patchingPolicy == InstantRunPatchingPolicy.MULTI_APK) {
+
+ AndroidTask<InstantRunSplitApkBuilder> splitApk =
+ getAndroidTasks().create(tasks,
+ new InstantRunSplitApkBuilder.ConfigAction(
+ variantScope));
+
+ TransformManager transformManager = variantScope.getTransformManager();
+ for (TransformStream stream : transformManager.getStreams(
+ PackageApplication.sDexFilter)) {
+ // TODO Optimize to avoid creating too many actions
+ splitApk.dependsOn(tasks, stream.getDependencies());
+ }
+ variantScope.getVariantData().assembleVariantTask.dependsOn(
+ splitApk.get(tasks));
+
+ // if the assembleVariant task run, make sure it also runs the task to generate
+ // the build-info.xml.
+ variantScope.getVariantData().assembleVariantTask.dependsOn(
+ fullBuildInfoGeneratorTask.get(tasks));
+
+ // make sure the split APK task is run before we generate the build-info.xml
+ variantScope.getInstantRunAnchorTask().dependsOn(tasks, splitApk);
+ variantScope.getInstantRunIncrementalTask().dependsOn(tasks, splitApk);
+ }
+ return fullBuildInfoGeneratorTask;
+ }
+
+ @NonNull
+ @Override
+ protected Set<Scope> getResMergingScopes(@NonNull VariantScope variantScope) {
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+
+ /**
+ * Configure variantData to generate embedded wear application.
+ */
+ private void handleMicroApp(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ if (variantData.getVariantConfiguration().getBuildType().isEmbedMicroApp()) {
+ // get all possible configurations for the variant. We'll take the highest priority
+ // of them that have a file.
+ List<String> wearConfigNames = variantData.getWearConfigNames();
+
+ for (String configName : wearConfigNames) {
+ Configuration config = project.getConfigurations().findByName(
+ configName);
+ // this shouldn't happen, but better safe.
+ if (config == null) {
+ continue;
+ }
+
+ Set<File> file = config.getFiles();
+
+ int count = file.size();
+ if (count == 1) {
+ createGenerateMicroApkDataTask(tasks, scope, config);
+ // found one, bail out.
+ return;
+ } else if (count > 1) {
+ throw new RuntimeException(String.format(
+ "Configuration '%s' resolves to more than one apk.", configName));
+ }
+ }
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BadPluginException.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BadPluginException.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BadPluginException.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BadPluginException.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/CompileOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/CompileOptions.java
new file mode 100644
index 0000000..181b948
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/CompileOptions.java
@@ -0,0 +1,148 @@
+/*
+ * 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 excodess or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.gradle.tasks.factory.JavaCompileConfigAction;
+import com.google.common.base.Charsets;
+
+import org.gradle.api.JavaVersion;
+
+import java.util.Locale;
+
+/**
+ * Java compilation options.
+ */
+public class CompileOptions {
+ private static final String VERSION_PREFIX = "VERSION_";
+
+ @Nullable
+ private JavaVersion sourceCompatibility;
+
+ @Nullable
+ private JavaVersion targetCompatibility;
+
+ @NonNull
+ private String encoding = Charsets.UTF_8.name();
+
+ /** The default value is chosen in {@link JavaCompileConfigAction}. */
+ private Boolean incremental = null;
+
+ /** @see #setDefaultJavaVersion(JavaVersion) */
+ @NonNull
+ @VisibleForTesting
+ JavaVersion defaultJavaVersion = JavaVersion.VERSION_1_6;
+
+ /** @see #getSourceCompatibility() */
+ public void setSourceCompatibility(@NonNull Object sourceCompatibility) {
+ this.sourceCompatibility = convert(sourceCompatibility);
+ }
+
+ /**
+ * Language level of the java source code.
+ *
+ * <p>Similar to what <a href="http://www.gradle.org/docs/current/userguide/java_plugin.html">
+ * Gradle Java plugin</a> uses. Formats supported are:
+ * <ul>
+ * <li><code>"1.6"</code></li>
+ * <li><code>1.6</code></li>
+ * <li><code>JavaVersion.Version_1_6</code></li>
+ * <li><code>"Version_1_6"</code></li>
+ * </ul>
+ */
+ @NonNull
+ public JavaVersion getSourceCompatibility() {
+ return sourceCompatibility != null ? sourceCompatibility : defaultJavaVersion;
+ }
+
+ /** @see #getTargetCompatibility() */
+ public void setTargetCompatibility(@NonNull Object targetCompatibility) {
+ this.targetCompatibility = convert(targetCompatibility);
+ }
+
+ /**
+ * Version of the generated Java bytecode.
+ *
+ * <p>Similar to what <a href="http://www.gradle.org/docs/current/userguide/java_plugin.html">
+ * Gradle Java plugin</a> uses. Formats supported are:
+ * <ul>
+ * <li><code>"1.6"</code></li>
+ * <li><code>1.6</code></li>
+ * <li><code>JavaVersion.Version_1_6</code></li>
+ * <li><code>"Version_1_6"</code></li>
+ * </ul>
+ */
+ @NonNull
+ public JavaVersion getTargetCompatibility() {
+ return targetCompatibility != null ? targetCompatibility : defaultJavaVersion;
+ }
+
+ /** @see #getEncoding() */
+ public void setEncoding(@NonNull String encoding) {
+ this.encoding = checkNotNull(encoding);
+ }
+
+ /**
+ * Java source files encoding.
+ */
+ @NonNull
+ public String getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Default java version, based on the target SDK. Set by the plugin, not meant to be used in
+ * build files by users.
+ */
+ public void setDefaultJavaVersion(@NonNull JavaVersion defaultJavaVersion) {
+ this.defaultJavaVersion = checkNotNull(defaultJavaVersion);
+ }
+
+ /**
+ * Whether java compilation should use Gradle's new incremental model.
+ *
+ * <p>This may cause issues in projects that rely on annotation processing etc.
+ */
+ public Boolean getIncremental() {
+ return incremental;
+ }
+
+ /** @see #getIncremental() */
+ public void setIncremental(boolean incremental) {
+ this.incremental = incremental;
+ }
+
+ /**
+ * Converts all possible supported way of specifying a Java version to a {@link JavaVersion}.
+ * @param version the user provided java version.
+ */
+ @NonNull
+ private static JavaVersion convert(@NonNull Object version) {
+ // for backward version reasons, we support setting strings like 'Version_1_6'
+ if (version instanceof String) {
+ final String versionString = (String) version;
+ if (versionString.toUpperCase(Locale.ENGLISH).startsWith(VERSION_PREFIX)) {
+ version = versionString.substring(VERSION_PREFIX.length()).replace('_', '.');
+ }
+ }
+ return JavaVersion.toVersion(version);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java
new file mode 100644
index 0000000..e3711c4
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationDependencies.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.model.JavaLibraryImpl;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Implementation of {@link com.android.builder.model.Dependencies} over a Gradle
+ * Configuration object. This is used to lazily query the list of files from the config object.
+ */
+public class ConfigurationDependencies implements Dependencies {
+
+ @NonNull
+ private final Configuration configuration;
+
+ public ConfigurationDependencies(@NonNull Configuration configuration) {
+
+ this.configuration = configuration;
+ }
+
+ @NonNull
+ @Override
+ public Collection<AndroidLibrary> getLibraries() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Collection<JavaLibrary> getJavaLibraries() {
+ Set<File> files = configuration.getFiles();
+ if (files.isEmpty()) {
+ return Collections.emptySet();
+ }
+ Set<JavaLibrary> javaLibraries = Sets.newHashSet();
+ for (File file : files) {
+ javaLibraries.add(new JavaLibraryImpl(file, false, null, null));
+ }
+ return javaLibraries;
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getProjects() {
+ return Collections.emptyList();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProvider.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProvider.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProvider.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProvider.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/DependencyManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/DependencyManager.java
new file mode 100644
index 0000000..06c626c
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/DependencyManager.java
@@ -0,0 +1,1152 @@
+/*
+ * 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.build.gradle.internal;
+
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.EXT_ANDROID_PACKAGE;
+import static com.android.SdkConstants.EXT_JAR;
+import static com.android.builder.core.BuilderConstants.EXT_LIB_ARCHIVE;
+import static com.android.builder.core.ErrorReporter.EvaluationMode.STANDARD;
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dependency.JarInfo;
+import com.android.build.gradle.internal.dependency.LibInfo;
+import com.android.build.gradle.internal.dependency.LibraryDependencyImpl;
+import com.android.build.gradle.internal.dependency.ManifestDependencyImpl;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.model.MavenCoordinatesImpl;
+import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
+import com.android.build.gradle.internal.tasks.PrepareLibraryTask;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.dependency.DependencyContainer;
+import com.android.builder.dependency.JarDependency;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.SyncIssue;
+import com.android.utils.ILogger;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.CircularReferenceException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.UnknownProjectException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.SelfResolvingDependency;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A manager to resolve configuration dependencies.
+ */
+public class DependencyManager {
+ protected static final boolean DEBUG_DEPENDENCY = false;
+
+ private Project project;
+ private ExtraModelInfo extraModelInfo;
+ private ILogger logger;
+
+ final Map<LibraryDependencyImpl, PrepareLibraryTask> prepareTaskMap = Maps.newHashMap();
+
+ public DependencyManager(Project project, ExtraModelInfo extraModelInfo) {
+ this.project = project;
+ this.extraModelInfo = extraModelInfo;
+ logger = new LoggerWrapper(Logging.getLogger(DependencyManager.class));
+ }
+
+ /**
+ * Returns the list of packaged local jars.
+ */
+ public static List<File> getPackagedLocalJarFileList(DependencyContainer dependencyContainer) {
+ List<JarDependency> jarDependencyList = dependencyContainer.getLocalDependencies();
+ Set<File> files = Sets.newHashSetWithExpectedSize(jarDependencyList.size());
+ for (JarDependency jarDependency : jarDependencyList) {
+ if (jarDependency.isPackaged()) {
+ files.add(jarDependency.getJarFile());
+ }
+ }
+
+ return Lists.newArrayList(files);
+ }
+
+ public void addDependencyToPrepareTask(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
+ @NonNull PrepareDependenciesTask prepareDependenciesTask,
+ @NonNull LibraryDependencyImpl lib) {
+ PrepareLibraryTask prepareLibTask = prepareTaskMap.get(lib.getNonTransitiveRepresentation());
+ if (prepareLibTask != null) {
+ prepareDependenciesTask.dependsOn(prepareLibTask);
+ prepareLibTask.dependsOn(variantData.preBuildTask);
+ }
+
+ for (LibraryDependency childLib : lib.getDependencies()) {
+ addDependencyToPrepareTask(
+ variantData,
+ prepareDependenciesTask,
+ (LibraryDependencyImpl) childLib);
+ }
+ }
+
+ public void resolveDependencies(
+ @NonNull VariantDependencies variantDeps,
+ @Nullable VariantDependencies testedVariantDeps,
+ @Nullable String testedProjectPath) {
+ Multimap<LibraryDependency, VariantDependencies> reverseMap = ArrayListMultimap.create();
+
+ resolveDependencyForConfig(variantDeps, testedVariantDeps, testedProjectPath, reverseMap);
+ processLibraries(variantDeps.getLibraries(), reverseMap);
+ }
+
+ private void processLibraries(
+ @NonNull Collection<LibraryDependencyImpl> libraries,
+ @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
+ for (LibraryDependencyImpl lib : libraries) {
+ setupPrepareLibraryTask(lib, reverseMap);
+ //noinspection unchecked
+ processLibraries(
+ (Collection<LibraryDependencyImpl>) (List<?>) lib.getDependencies(),
+ reverseMap);
+ }
+ }
+
+ private void setupPrepareLibraryTask(
+ @NonNull LibraryDependencyImpl libDependency,
+ @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
+ Task task = maybeCreatePrepareLibraryTask(libDependency, project);
+
+ // Use the reverse map to find all the configurations that included this android
+ // library so that we can make sure they are built.
+ // TODO fix, this is not optimum as we bring in more dependencies than we should.
+ Collection<VariantDependencies> configDepList = reverseMap.get(libDependency);
+ if (configDepList != null && !configDepList.isEmpty()) {
+ for (VariantDependencies configDependencies: configDepList) {
+ task.dependsOn(configDependencies.getCompileConfiguration().getBuildDependencies());
+ }
+ }
+
+ // check if this library is created by a parent (this is based on the
+ // output file.
+ // TODO Fix this as it's fragile
+ /*
+ This is a somewhat better way but it doesn't work in some project with
+ weird setups...
+ Project parentProject = DependenciesImpl.getProject(library.getBundle(), projects)
+ if (parentProject != null) {
+ String configName = library.getProjectVariant()
+ if (configName == null) {
+ configName = "default"
+ }
+
+ prepareLibraryTask.dependsOn parentProject.getPath() + ":assemble${configName.capitalize()}"
+ }
+*/
+
+ }
+
+ /**
+ * Handles the library and returns a task to "prepare" the library (ie unarchive it). The task
+ * will be reused for all projects using the same library.
+ *
+ * @param library the library.
+ * @param project the project
+ * @return the prepare task.
+ */
+ private PrepareLibraryTask maybeCreatePrepareLibraryTask(
+ @NonNull LibraryDependencyImpl library,
+ @NonNull Project project) {
+
+ // create proper key for the map. library here contains all the dependencies which
+ // are not relevant for the task (since the task only extract the aar which does not
+ // include the dependencies.
+ // However there is a possible case of a rewritten dependencies (with resolution strategy)
+ // where the aar here could have different dependencies, in which case we would still
+ // need the same task.
+ // So we extract a LibraryBundle (no dependencies) from the LibraryDependencyImpl to
+ // make the map key that doesn't take into account the dependencies.
+ LibraryDependencyImpl key = library.getNonTransitiveRepresentation();
+
+ PrepareLibraryTask prepareLibraryTask = prepareTaskMap.get(key);
+
+ if (prepareLibraryTask == null) {
+ String bundleName = GUtil.toCamelCase(library.getName().replaceAll("\\:", " "));
+
+ prepareLibraryTask = project.getTasks().create(
+ "prepare" + bundleName + "Library", PrepareLibraryTask.class);
+
+ prepareLibraryTask.setDescription("Prepare " + library.getName());
+ prepareLibraryTask.setBundle(library.getBundle());
+ prepareLibraryTask.setExplodedDir(library.getBundleFolder());
+ prepareLibraryTask.setVariantName("");
+
+ prepareTaskMap.put(key, prepareLibraryTask);
+ }
+
+ return prepareLibraryTask;
+ }
+
+ private void resolveDependencyForConfig(
+ @NonNull VariantDependencies variantDeps,
+ @Nullable VariantDependencies testedVariantDeps,
+ @Nullable String testedProjectPath,
+ @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
+
+ Configuration compileClasspath = variantDeps.getCompileConfiguration();
+ Configuration packageClasspath = variantDeps.getPackageConfiguration();
+
+ // TODO - shouldn't need to do this - fix this in Gradle
+ ensureConfigured(compileClasspath);
+ ensureConfigured(packageClasspath);
+
+ if (DEBUG_DEPENDENCY) {
+ System.out.println(">>>>>>>>>>");
+ System.out.println(
+ project.getName() + ":" +
+ compileClasspath.getName() + "/" +
+ packageClasspath.getName());
+ }
+
+ Set<String> currentUnresolvedDependencies = Sets.newHashSet();
+
+ // TODO - defer downloading until required -- This is hard to do as we need the info to build the variant config.
+ Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts = Maps.newHashMap();
+ collectArtifacts(compileClasspath, artifacts);
+ collectArtifacts(packageClasspath, artifacts);
+
+ // --- Handle the external/module dependencies ---
+ // keep a map of modules already processed so that we don't go through sections of the
+ // graph that have been seen elsewhere.
+ Map<ModuleVersionIdentifier, List<LibInfo>> foundLibraries = Maps.newHashMap();
+ Map<ModuleVersionIdentifier, List<JarInfo>> foundJars = Maps.newHashMap();
+
+ // first get the compile dependencies. Note that in both case the libraries and the
+ // jars are a graph. The list only contains the first level of dependencies, and
+ // they themselves contain transitive dependencies (libraries can contain both, jars only
+ // contains jars)
+ List<LibInfo> compiledAndroidLibraries = Lists.newArrayList();
+ List<JarInfo> compiledJars = Lists.newArrayList();
+
+ Set<? extends DependencyResult> dependencyResultSet = compileClasspath.getIncoming()
+ .getResolutionResult().getRoot().getDependencies();
+
+ for (DependencyResult dependencyResult : dependencyResultSet) {
+ if (dependencyResult instanceof ResolvedDependencyResult) {
+ addDependency(
+ ((ResolvedDependencyResult) dependencyResult).getSelected(),
+ variantDeps,
+ compiledAndroidLibraries,
+ compiledJars,
+ foundLibraries,
+ foundJars,
+ artifacts,
+ reverseMap,
+ currentUnresolvedDependencies,
+ testedProjectPath,
+ Collections.<String>emptyList(),
+ 0);
+ } else if (dependencyResult instanceof UnresolvedDependencyResult) {
+ ComponentSelector attempted = ((UnresolvedDependencyResult) dependencyResult).getAttempted();
+ if (attempted != null) {
+ currentUnresolvedDependencies.add(attempted.toString());
+ }
+ }
+ }
+
+ // then the packaged ones.
+ List<LibInfo> packagedAndroidLibraries = Lists.newArrayList();
+ List<JarInfo> packagedJars = Lists.newArrayList();
+
+ dependencyResultSet = packageClasspath.getIncoming()
+ .getResolutionResult().getRoot().getDependencies();
+
+ for (DependencyResult dependencyResult : dependencyResultSet) {
+ if (dependencyResult instanceof ResolvedDependencyResult) {
+ addDependency(
+ ((ResolvedDependencyResult) dependencyResult).getSelected(),
+ variantDeps,
+ packagedAndroidLibraries,
+ packagedJars,
+ foundLibraries,
+ foundJars,
+ artifacts,
+ reverseMap,
+ currentUnresolvedDependencies,
+ testedProjectPath,
+ Collections.<String>emptyList(),
+ 0);
+ } else if (dependencyResult instanceof UnresolvedDependencyResult) {
+ ComponentSelector attempted = ((UnresolvedDependencyResult) dependencyResult)
+ .getAttempted();
+ if (attempted != null) {
+ currentUnresolvedDependencies.add(attempted.toString());
+ }
+ }
+ }
+
+ // now look through both results.
+ // 1. Handle the compile and package list of Libraries.
+ // For Libraries:
+ // Only library projects can support provided aar.
+ // However, package(publish)-only are still not supported (they don't make sense).
+ // For now, provided only dependencies will be kept normally in the compile-graph.
+ // However we'll want to not include them in the resource merging.
+ // For Applications:
+ // All Android libraries must be in both lists.
+ // ---
+ // Since we reuse the same instance of LibInfo for identical modules
+ // we can simply run through each list and look for libs that are in only one.
+ // While the list of library is actually a graph, it's fine to look only at the
+ // top level ones since the transitive ones are in the same scope as the direct libraries.
+ List<LibInfo> copyOfPackagedLibs = Lists.newArrayList(packagedAndroidLibraries);
+ boolean isLibrary = extraModelInfo.isLibrary();
+
+ for (LibInfo lib : compiledAndroidLibraries) {
+ if (!copyOfPackagedLibs.contains(lib)) {
+ if (isLibrary || lib.isOptional()) {
+ lib.setIsOptional(true);
+ } else {
+ //noinspection ConstantConditions
+ variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
+ lib.getResolvedCoordinates().toString(),
+ SyncIssue.TYPE_NON_JAR_PROVIDED_DEP,
+ String.format(
+ "Project %s: provided dependencies can only be jars. %s is an Android Library.",
+ project.getName(), lib.getResolvedCoordinates())));
+ }
+ } else {
+ copyOfPackagedLibs.remove(lib);
+ }
+ }
+ // at this stage copyOfPackagedLibs should be empty, if not, error.
+ for (LibInfo lib : copyOfPackagedLibs) {
+ //noinspection ConstantConditions
+ variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
+ lib.getResolvedCoordinates().toString(),
+ SyncIssue.TYPE_NON_JAR_PACKAGE_DEP,
+ String.format(
+ "Project %s: apk dependencies can only be jars. %s is an Android Library.",
+ project.getName(), lib.getResolvedCoordinates())));
+ }
+
+ // 2. merge jar dependencies with a single list where items have packaged/compiled properties.
+ // since we reuse the same instance of a JarInfo for identical modules, we can use an
+ // Identity set (ie both compiledJars and packagedJars will contain the same instance
+ // if it's both compiled and packaged)
+ Set<JarInfo> jarInfoSet = Sets.newIdentityHashSet();
+ Set<LibInfo> libInfoSet = Sets.newIdentityHashSet();
+
+ // go through the graphs of dependencies (jars and libs) and gather all the transitive
+ // jar dependencies.
+ // At the same this we set the compiled/packaged properties.
+ gatherJarDependencies(jarInfoSet, compiledJars, true /*compiled*/, false /*packaged*/);
+ gatherJarDependencies(jarInfoSet, packagedJars, false /*compiled*/, true /*packaged*/);
+ // at this step, we know that libraries have been checked and libraries can only
+ // be in both compiled and packaged scope.
+ gatherJarDependenciesFromLibraries(jarInfoSet, compiledAndroidLibraries);
+
+ gatherAndroidDependencies(libInfoSet, compiledAndroidLibraries);
+
+ // the final list of JarDependency, created from the list of JarInfo.
+ List<JarDependency> jars = Lists.newArrayListWithCapacity(jarInfoSet.size());
+ Set<LibInfo> librariesToKeep = Sets.newHashSetWithExpectedSize(compiledAndroidLibraries.size());
+
+ // if this is a test dependencies (ie tested dependencies is non null), override
+ // packaged attributes for jars that are already in the tested dependencies in order to
+ // not package them twice (since the VM loads the classes of both APKs in the same
+ // classpath and refuses to load the same class twice)
+ if (testedVariantDeps != null) {
+ List<JarDependency> jarDependencies = testedVariantDeps.getJarDependencies();
+
+ // gather the tested dependencies
+ Map<String, String> testedJarDeps = Maps.newHashMapWithExpectedSize(jarDependencies.size());
+
+ for (JarDependency jar : jarDependencies) {
+ if (jar.isPackaged()) {
+ MavenCoordinates coordinates = jar.getResolvedCoordinates();
+ checkState(coordinates != null);
+ testedJarDeps.put(
+ computeVersionLessCoordinateKey(coordinates),
+ coordinates.getVersion());
+ }
+ }
+
+ // now go through all the test dependencies and check we don't have the same thing.
+ // Skip the ones that are already in the tested variant, and convert the rest
+ // to the final immutable instance
+ for (JarInfo jar : jarInfoSet) {
+ if (jar.isPackaged()) {
+ MavenCoordinates coordinates = jar.getResolvedCoordinates();
+
+ String testedVersion = testedJarDeps.get(
+ computeVersionLessCoordinateKey(coordinates));
+ if (testedVersion != null) {
+ skipTestDependency(variantDeps, coordinates, testedVersion);
+ } else {
+ // new artifact, convert it.
+ jars.add(jar.createJarDependency());
+ }
+ }
+ }
+
+ List<? extends LibraryDependency> androidDependencies =
+ testedVariantDeps.getAndroidDependencies();
+
+ Map<String, String> testedAndroidDeps = Maps.newHashMapWithExpectedSize(jarDependencies.size());
+ for (LibraryDependency androidDependency : androidDependencies) {
+ MavenCoordinates coordinates = androidDependency.getResolvedCoordinates();
+ checkState(coordinates != null);
+
+ testedAndroidDeps.put(
+ computeVersionLessCoordinateKey(coordinates),
+ coordinates.getVersion());
+
+ }
+
+
+ for (LibInfo androidLibrary : libInfoSet) {
+ MavenCoordinates coordinates = androidLibrary.getResolvedCoordinates();
+ checkState(coordinates != null);
+
+ String testedVersion =
+ testedAndroidDeps.get(computeVersionLessCoordinateKey(coordinates));
+
+ if (testedVersion != null) {
+ skipTestDependency(variantDeps, coordinates, testedVersion);
+ } else {
+ librariesToKeep.add(androidLibrary);
+ }
+ }
+ } else {
+ // just convert all of them to JarDependency
+ for (JarInfo jarInfo : jarInfoSet) {
+ jars.add(jarInfo.createJarDependency());
+ }
+ librariesToKeep.addAll(libInfoSet);
+ }
+
+ // --- Handle the local jar dependencies ---
+
+ // also need to process local jar files, as they are not processed by the
+ // resolvedConfiguration result. This only includes the local jar files for this project.
+ Set<File> localCompiledJars = Sets.newHashSet();
+ for (Dependency dependency : compileClasspath.getAllDependencies()) {
+ if (dependency instanceof SelfResolvingDependency &&
+ !(dependency instanceof ProjectDependency)) {
+ Set<File> files = ((SelfResolvingDependency) dependency).resolve();
+ for (File f : files) {
+ if (DEBUG_DEPENDENCY) {
+ System.out.println("LOCAL compile: " + f.getName());
+ }
+ // only accept local jar, no other types.
+ if (!f.getName().toLowerCase(Locale.getDefault()).endsWith(DOT_JAR)) {
+ variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
+ f.getAbsolutePath(),
+ SyncIssue.TYPE_NON_JAR_LOCAL_DEP,
+ String.format(
+ "Project %s: Only Jar-type local dependencies are supported. Cannot handle: %s",
+ project.getName(), f.getAbsolutePath())));
+ } else {
+ localCompiledJars.add(f);
+ }
+ }
+ }
+ }
+
+ Set<File> localPackagedJars = Sets.newHashSet();
+ for (Dependency dependency : packageClasspath.getAllDependencies()) {
+ if (dependency instanceof SelfResolvingDependency &&
+ !(dependency instanceof ProjectDependency)) {
+ Set<File> files = ((SelfResolvingDependency) dependency).resolve();
+ for (File f : files) {
+ if (DEBUG_DEPENDENCY) {
+ System.out.println("LOCAL package: " + f.getName());
+ }
+ // only accept local jar, no other types.
+ if (!f.getName().toLowerCase(Locale.getDefault()).endsWith(DOT_JAR)) {
+ variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
+ f.getAbsolutePath(),
+ SyncIssue.TYPE_NON_JAR_LOCAL_DEP,
+ String.format(
+ "Project %s: Only Jar-type local dependencies are supported. Cannot handle: %s",
+ project.getName(), f.getAbsolutePath())));
+ } else {
+ localPackagedJars.add(f);
+ }
+ }
+ }
+ }
+
+ // loop through both the compiled and packaged jar to compute the list
+ // of jars that are: compile-only, package-only, or both.
+ Map<File, JarDependency> localJars = Maps.newHashMap();
+ for (File file : localCompiledJars) {
+ localJars.put(file, new JarDependency(
+ file,
+ true /*compiled*/,
+ localPackagedJars.contains(file) /*packaged*/,
+ null /*resolvedCoordinates*/,
+ null /*projectPath*/));
+ }
+
+ for (File file : localPackagedJars) {
+ if (!localCompiledJars.contains(file)) {
+ localJars.put(file, new JarDependency(
+ file,
+ false /*compiled*/,
+ true /*packaged*/,
+ null /*resolvedCoordinates*/,
+ null /*projectPath*/));
+ }
+ }
+
+ if (extraModelInfo.getMode() != STANDARD &&
+ compileClasspath.getResolvedConfiguration().hasError()) {
+ for (String dependency : currentUnresolvedDependencies) {
+ extraModelInfo.handleSyncError(
+ dependency,
+ SyncIssue.TYPE_UNRESOLVED_DEPENDENCY,
+ String.format(
+ "Unable to resolve dependency '%s'",
+ dependency));
+ }
+ }
+
+ // convert the LibInfo in LibraryDependencyImpl and update the reverseMap
+ // with the converted keys
+ List<LibraryDependencyImpl> libList = convertLibraryInfoIntoDependency(
+ compiledAndroidLibraries, librariesToKeep, reverseMap);
+
+ if (DEBUG_DEPENDENCY) {
+ for (LibraryDependency lib : libList) {
+ System.out.println("LIB: " + lib);
+ }
+ for (JarDependency jar : jars) {
+ System.out.println("JAR: " + jar);
+ }
+ for (JarDependency jar : localJars.values()) {
+ System.out.println("LOCAL-JAR: " + jar);
+ }
+ }
+
+ variantDeps.addLibraries(libList);
+ variantDeps.addJars(jars);
+ variantDeps.addLocalJars(localJars.values());
+
+ configureBuild(variantDeps);
+
+ if (DEBUG_DEPENDENCY) {
+ System.out.println(project.getName() + ":" + compileClasspath.getName() + "/" +packageClasspath.getName());
+ System.out.println("<<<<<<<<<<");
+ }
+
+ }
+
+ private void skipTestDependency(@NonNull VariantDependencies variantDeps,
+ MavenCoordinates coordinates, String testedVersion) {
+ // same artifact, skip packaging of the dependency in the test app,
+ // whether the version is a match or not.
+
+ // if the dependency is present in both tested and test artifact,
+ // verify that they are the same version
+ if (!testedVersion.equals(coordinates.getVersion())) {
+ String artifactInfo = coordinates.getGroupId() + ":" + coordinates.getArtifactId();
+ variantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
+ artifactInfo,
+ SyncIssue.TYPE_MISMATCH_DEP,
+ String.format(
+ "Conflict with dependency '%s'. Resolved versions for"
+ + " app (%s) and test app (%s) differ. See"
+ + " http://g.co/androidstudio/app-test-app-conflict"
+ + " for details.",
+ artifactInfo,
+ testedVersion,
+ coordinates.getVersion())));
+
+ } else {
+ logger.info(String.format(
+ "Removed '%s' from packaging of %s: Already in tested package.",
+ coordinates,
+ variantDeps.getName()));
+ }
+ }
+
+ private static List<LibraryDependencyImpl> convertLibraryInfoIntoDependency(
+ @NonNull List<LibInfo> libInfos,
+ Set<LibInfo> librariesToKeep,
+ @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap) {
+ List<LibraryDependencyImpl> list = Lists.newArrayListWithCapacity(libInfos.size());
+
+ // since the LibInfos is a graph and the previous "foundLibraries" map ensure we reuse
+ // instance where applicable, we'll create a map to keep track of what we have already
+ // converted.
+ Map<LibInfo, LibraryDependencyImpl> convertedMap = Maps.newIdentityHashMap();
+
+ for (LibInfo libInfo : libInfos) {
+ if (librariesToKeep.contains(libInfo)) {
+ list.add(convertLibInfo(libInfo, librariesToKeep, reverseMap, convertedMap));
+ }
+ }
+
+ return list;
+ }
+
+ private static LibraryDependencyImpl convertLibInfo(
+ @NonNull LibInfo libInfo,
+ Set<LibInfo> librariesToKeep,
+ @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap,
+ @NonNull Map<LibInfo, LibraryDependencyImpl> convertedMap) {
+ LibraryDependencyImpl convertedLib = convertedMap.get(libInfo);
+ if (convertedLib == null) {
+ // first, convert the children.
+ @SuppressWarnings("unchecked")
+ List<LibInfo> children = (List<LibInfo>) (List<?>) libInfo.getDependencies();
+ List<LibraryDependency> convertedChildren = Lists.newArrayListWithCapacity(children.size());
+
+ for (LibInfo child : children) {
+ if (librariesToKeep.contains(child)) {
+ convertedChildren.add(convertLibInfo(child, librariesToKeep, reverseMap, convertedMap));
+ }
+ }
+
+ // now convert the libInfo
+ convertedLib = new LibraryDependencyImpl(
+ libInfo.getBundle(),
+ libInfo.getFolder(),
+ convertedChildren,
+ libInfo.getName(),
+ libInfo.getProjectVariant(),
+ libInfo.getProject(),
+ libInfo.getRequestedCoordinates(),
+ libInfo.getResolvedCoordinates(),
+ libInfo.isOptional());
+
+ // add it to the map
+ convertedMap.put(libInfo, convertedLib);
+
+ // and update the reversemap
+ // get the items associated with the libInfo. Put in a fresh list as the returned
+ // collection is backed by the content of the map.
+ Collection<VariantDependencies> values = Lists.newArrayList(reverseMap.get(libInfo));
+ reverseMap.removeAll(libInfo);
+ reverseMap.putAll(convertedLib, values);
+ }
+
+ return convertedLib;
+ }
+
+ private static void gatherJarDependencies(
+ Set<JarInfo> outJarInfos,
+ Collection<JarInfo> inJarInfos,
+ boolean compiled,
+ boolean packaged) {
+ for (JarInfo jarInfo : inJarInfos) {
+ outJarInfos.add(jarInfo);
+
+ if (compiled) {
+ jarInfo.setCompiled(true);
+ }
+ if (packaged) {
+ jarInfo.setPackaged(true);
+ }
+
+ gatherJarDependencies(outJarInfos, jarInfo.getDependencies(), compiled, packaged);
+ }
+ }
+
+ private static void gatherAndroidDependencies(
+ Set<LibInfo> outLibInfos,
+ Collection<LibInfo> inLibInfos) {
+ for (LibInfo libInfo : inLibInfos) {
+ outLibInfos.add(libInfo);
+ gatherAndroidDependencies(outLibInfos, libInfo.getLibInfoDependencies());
+ }
+ }
+
+ private static void gatherJarDependenciesFromLibraries(
+ Set<JarInfo> outJarInfos,
+ Collection<LibInfo> inLibraryDependencies) {
+ for (LibInfo libInfo : inLibraryDependencies) {
+ gatherJarDependencies(outJarInfos, libInfo.getJarDependencies(),
+ true, !libInfo.isOptional());
+
+ gatherJarDependenciesFromLibraries(
+ outJarInfos,
+ libInfo.getLibInfoDependencies());
+ }
+ }
+
+ private void ensureConfigured(Configuration config) {
+ for (Dependency dependency : config.getAllDependencies()) {
+ if (dependency instanceof ProjectDependency) {
+ ProjectDependency projectDependency = (ProjectDependency) dependency;
+ project.evaluationDependsOn(projectDependency.getDependencyProject().getPath());
+ try {
+ ensureConfigured(projectDependency.getProjectConfiguration());
+ } catch (Throwable e) {
+ throw new UnknownProjectException(String.format(
+ "Cannot evaluate module %s : %s",
+ projectDependency.getName(), e.getMessage()),
+ e);
+ }
+ }
+ }
+ }
+
+ private void collectArtifacts(
+ Configuration configuration,
+ Map<ModuleVersionIdentifier,
+ List<ResolvedArtifact>> artifacts) {
+
+ Set<ResolvedArtifact> allArtifacts;
+ if (extraModelInfo.getMode() != STANDARD) {
+ allArtifacts = configuration.getResolvedConfiguration().getLenientConfiguration().getArtifacts(
+ Specs.satisfyAll());
+ } else {
+ allArtifacts = configuration.getResolvedConfiguration().getResolvedArtifacts();
+ }
+
+ for (ResolvedArtifact artifact : allArtifacts) {
+ ModuleVersionIdentifier id = artifact.getModuleVersion().getId();
+ List<ResolvedArtifact> moduleArtifacts = artifacts.get(id);
+
+ if (moduleArtifacts == null) {
+ moduleArtifacts = Lists.newArrayList();
+ artifacts.put(id, moduleArtifacts);
+ }
+
+ if (!moduleArtifacts.contains(artifact)) {
+ moduleArtifacts.add(artifact);
+ }
+ }
+ }
+
+ private static void printIndent(int indent, @NonNull String message) {
+ for (int i = 0 ; i < indent ; i++) {
+ System.out.print("\t");
+ }
+
+ System.out.println(message);
+ }
+
+ private void addDependency(
+ @NonNull ResolvedComponentResult resolvedComponentResult,
+ @NonNull VariantDependencies configDependencies,
+ @NonNull Collection<LibInfo> outLibraries,
+ @NonNull List<JarInfo> outJars,
+ @NonNull Map<ModuleVersionIdentifier, List<LibInfo>> alreadyFoundLibraries,
+ @NonNull Map<ModuleVersionIdentifier, List<JarInfo>> alreadyFoundJars,
+ @NonNull Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
+ @NonNull Multimap<LibraryDependency, VariantDependencies> reverseMap,
+ @NonNull Set<String> currentUnresolvedDependencies,
+ @Nullable String testedProjectPath,
+ @NonNull List<String> projectChain,
+ int indent) {
+
+ ModuleVersionIdentifier moduleVersion = resolvedComponentResult.getModuleVersion();
+ if (configDependencies.getChecker().excluded(moduleVersion)) {
+ return;
+ }
+
+ if (moduleVersion.getName().equals("support-annotations") &&
+ moduleVersion.getGroup().equals("com.android.support")) {
+ configDependencies.setAnnotationsPresent(true);
+ }
+
+ List<LibInfo> libsForThisModule = alreadyFoundLibraries.get(moduleVersion);
+ List<JarInfo> jarsForThisModule = alreadyFoundJars.get(moduleVersion);
+
+ if (libsForThisModule != null) {
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "FOUND LIB: " + moduleVersion.getName());
+ }
+ outLibraries.addAll(libsForThisModule);
+
+ for (LibInfo lib : libsForThisModule) {
+ reverseMap.put(lib, configDependencies);
+ }
+
+ } else if (jarsForThisModule != null) {
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "FOUND JAR: " + moduleVersion.getName());
+ }
+ outJars.addAll(jarsForThisModule);
+ }
+ else {
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "NOT FOUND: " + moduleVersion.getName());
+ }
+ // new module! Might be a jar or a library
+
+ // get the nested components first.
+ List<LibInfo> nestedLibraries = Lists.newArrayList();
+ List<JarInfo> nestedJars = Lists.newArrayList();
+
+ Set<? extends DependencyResult> dependencies = resolvedComponentResult.getDependencies();
+ for (DependencyResult dependencyResult : dependencies) {
+ if (dependencyResult instanceof ResolvedDependencyResult) {
+ ResolvedComponentResult selected =
+ ((ResolvedDependencyResult) dependencyResult).getSelected();
+
+ List<String> newProjectChain = projectChain;
+
+ ComponentIdentifier identifier = selected.getId();
+ if (identifier instanceof ProjectComponentIdentifier) {
+ String projectPath =
+ ((ProjectComponentIdentifier) identifier).getProjectPath();
+
+ int index = projectChain.indexOf(projectPath);
+ if (index != -1) {
+ projectChain.add(projectPath);
+ String path = Joiner
+ .on(" -> ")
+ .join(projectChain.subList(index, projectChain.size()));
+
+ throw new CircularReferenceException(
+ "Circular reference between projects: " + path);
+ }
+
+ newProjectChain = Lists.newArrayList();
+ newProjectChain.addAll(projectChain);
+ newProjectChain.add(projectPath);
+ }
+
+ addDependency(
+ selected,
+ configDependencies,
+ nestedLibraries,
+ nestedJars,
+ alreadyFoundLibraries,
+ alreadyFoundJars,
+ artifacts,
+ reverseMap,
+ currentUnresolvedDependencies,
+ testedProjectPath,
+ newProjectChain,
+ indent + 1);
+ } else if (dependencyResult instanceof UnresolvedDependencyResult) {
+ ComponentSelector attempted = ((UnresolvedDependencyResult) dependencyResult).getAttempted();
+ if (attempted != null) {
+ currentUnresolvedDependencies.add(attempted.toString());
+ }
+ }
+ }
+
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "BACK2: " + moduleVersion.getName());
+ printIndent(indent, "NESTED LIBS: " + nestedLibraries.size());
+ printIndent(indent, "NESTED JARS: " + nestedJars.size());
+ }
+
+ // now loop on all the artifact for this modules.
+ List<ResolvedArtifact> moduleArtifacts = artifacts.get(moduleVersion);
+
+ ComponentIdentifier id = resolvedComponentResult.getId();
+ String gradlePath = (id instanceof ProjectComponentIdentifier) ?
+ ((ProjectComponentIdentifier) id).getProjectPath() : null;
+
+ if (moduleArtifacts != null) {
+ for (ResolvedArtifact artifact : moduleArtifacts) {
+ if (EXT_LIB_ARCHIVE.equals(artifact.getExtension())) {
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "TYPE: AAR");
+ }
+ if (libsForThisModule == null) {
+ libsForThisModule = Lists.newArrayList();
+ alreadyFoundLibraries.put(moduleVersion, libsForThisModule);
+ }
+
+ String path = computeArtifactPath(moduleVersion, artifact);
+ String name = computeArtifactName(moduleVersion, artifact);
+
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "NAME: " + name);
+ printIndent(indent, "PATH: " + path);
+ }
+
+ //def explodedDir = project.file("$project.rootProject.buildDir/${FD_INTERMEDIATES}/exploded-aar/$path")
+ File explodedDir = project.file(project.getBuildDir() + "/" + FD_INTERMEDIATES + "/exploded-aar/" + path);
+ @SuppressWarnings("unchecked")
+ LibInfo libInfo = new LibInfo(
+ artifact.getFile(),
+ explodedDir,
+ (List<LibraryDependency>) (List<?>) nestedLibraries,
+ nestedJars,
+ name,
+ artifact.getClassifier(),
+ gradlePath,
+ null /*requestedCoordinates*/,
+ new MavenCoordinatesImpl(artifact));
+
+ libsForThisModule.add(libInfo);
+ outLibraries.add(libInfo);
+ reverseMap.put(libInfo, configDependencies);
+
+ } else if (EXT_JAR.equals(artifact.getExtension())) {
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "TYPE: JAR");
+ }
+ // check this jar does not have a dependency on an library, as this would not work.
+ if (!nestedLibraries.isEmpty()) {
+ if (testedProjectPath != null && testedProjectPath.equals(gradlePath)) {
+ // TODO: make sure this is a direct dependency and not a transitive one.
+ // add nested libs as optional somehow...
+ for (LibInfo lib : nestedLibraries) {
+ lib.setIsOptional(true);
+ }
+ outLibraries.addAll(nestedLibraries);
+
+ } else {
+ configDependencies.getChecker()
+ .addSyncIssue(extraModelInfo.handleSyncError(
+ new MavenCoordinatesImpl(artifact).toString(),
+ SyncIssue.TYPE_JAR_DEPEND_ON_AAR,
+ String.format(
+ "Module '%s' depends on one or more Android Libraries but is a jar",
+ moduleVersion)));
+ }
+ }
+
+ if (jarsForThisModule == null) {
+ jarsForThisModule = Lists.newArrayList();
+ alreadyFoundJars.put(moduleVersion, jarsForThisModule);
+ }
+
+ JarInfo jarInfo = new JarInfo(
+ artifact.getFile(),
+ new MavenCoordinatesImpl(artifact),
+ gradlePath,
+ nestedJars);
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "JAR-INFO: " + jarInfo.toString());
+ }
+
+ jarsForThisModule.add(jarInfo);
+ outJars.add(jarInfo);
+
+ } else if (EXT_ANDROID_PACKAGE.equals(artifact.getExtension())) {
+ String name = computeArtifactName(moduleVersion, artifact);
+
+ configDependencies.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
+ name,
+ SyncIssue.TYPE_DEPENDENCY_IS_APK,
+ String.format(
+ "Dependency %s on project %s resolves to an APK archive " +
+ "which is not supported as a compilation dependency. File: %s",
+ name, project.getName(), artifact.getFile())));
+ } else if ("apklib".equals(artifact.getExtension())) {
+ String name = computeArtifactName(moduleVersion, artifact);
+
+ configDependencies.getChecker().addSyncIssue(extraModelInfo.handleSyncError(
+ name,
+ SyncIssue.TYPE_DEPENDENCY_IS_APKLIB,
+ String.format(
+ "Packaging for dependency %s is 'apklib' and is not supported. " +
+ "Only 'aar' libraries are supported.", name)));
+ } else {
+ String name = computeArtifactName(moduleVersion, artifact);
+
+ logger.warning(String.format(
+ "Unrecognized dependency: '%s' (type: '%s', extension: '%s')",
+ name, artifact.getType(), artifact.getExtension()));
+ }
+ }
+ }
+
+ if (DEBUG_DEPENDENCY) {
+ printIndent(indent, "DONE: " + moduleVersion.getName());
+ }
+ }
+ }
+
+ @NonNull
+ private String computeArtifactPath(
+ @NonNull ModuleVersionIdentifier moduleVersion,
+ @NonNull ResolvedArtifact artifact) {
+ StringBuilder pathBuilder = new StringBuilder();
+
+ pathBuilder.append(normalize(logger, moduleVersion, moduleVersion.getGroup()))
+ .append('/')
+ .append(normalize(logger, moduleVersion, moduleVersion.getName()))
+ .append('/')
+ .append(normalize(logger, moduleVersion,
+ moduleVersion.getVersion()));
+
+ if (artifact.getClassifier() != null && !artifact.getClassifier().isEmpty()) {
+ pathBuilder.append('/').append(normalize(logger, moduleVersion,
+ artifact.getClassifier()));
+ }
+
+ return pathBuilder.toString();
+ }
+
+ @NonNull
+ private static String computeArtifactName(
+ @NonNull ModuleVersionIdentifier moduleVersion,
+ @NonNull ResolvedArtifact artifact) {
+ StringBuilder nameBuilder = new StringBuilder();
+
+ nameBuilder.append(moduleVersion.getGroup())
+ .append(':')
+ .append(moduleVersion.getName())
+ .append(':')
+ .append(moduleVersion.getVersion());
+
+ if (artifact.getClassifier() != null && !artifact.getClassifier().isEmpty()) {
+ nameBuilder.append(':').append(artifact.getClassifier());
+ }
+
+ return nameBuilder.toString();
+ }
+
+ /**
+ * Normalize a path to remove all illegal characters for all supported operating systems.
+ * {@see http://en.wikipedia.org/wiki/Filename#Comparison%5Fof%5Ffile%5Fname%5Flimitations}
+ *
+ * @param id the module coordinates that generated this path
+ * @param path the proposed path name
+ * @return the normalized path name
+ */
+ static String normalize(ILogger logger, ModuleVersionIdentifier id, String path) {
+ if (path == null || path.isEmpty()) {
+ logger.info(String.format(
+ "When unzipping library '%s:%s:%s, either group, name or version is empty",
+ id.getGroup(), id.getName(), id.getVersion()));
+ return path;
+ }
+ // list of illegal characters
+ String normalizedPath = path.replaceAll("[%<>:\"/?*\\\\]", "@");
+ if (normalizedPath == null || normalizedPath.isEmpty()) {
+ // if the path normalization failed, return the original path.
+ logger.info(String.format(
+ "When unzipping library '%s:%s:%s, the normalized '%s' is empty",
+ id.getGroup(), id.getName(), id.getVersion(), path));
+ return path;
+ }
+ try {
+ int pathPointer = normalizedPath.length() - 1;
+ // do not end your path with either a dot or a space.
+ String suffix = "";
+ while (pathPointer >= 0 && (normalizedPath.charAt(pathPointer) == '.'
+ || normalizedPath.charAt(pathPointer) == ' ')) {
+ pathPointer--;
+ suffix += "@";
+ }
+ if (pathPointer < 0) {
+ throw new RuntimeException(String.format(
+ "When unzipping library '%s:%s:%s, " +
+ "the path '%s' cannot be transformed into a valid directory name",
+ id.getGroup(), id.getName(), id.getVersion(), path));
+ }
+ return normalizedPath.substring(0, pathPointer + 1) + suffix;
+ } catch (Exception e) {
+ logger.error(e, String.format(
+ "When unzipping library '%s:%s:%s', " +
+ "Path normalization failed for input %s",
+ id.getGroup(), id.getName(), id.getVersion(), path));
+ return path;
+ }
+ }
+
+ private void configureBuild(VariantDependencies configurationDependencies) {
+ addDependsOnTaskInOtherProjects(
+ project.getTasks().getByName(JavaBasePlugin.BUILD_NEEDED_TASK_NAME), true,
+ JavaBasePlugin.BUILD_NEEDED_TASK_NAME, "compile");
+ addDependsOnTaskInOtherProjects(
+ project.getTasks().getByName(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME), false,
+ JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, "compile");
+ }
+
+ @NonNull
+ public static List<ManifestDependencyImpl> getManifestDependencies(
+ List<LibraryDependency> libraries) {
+
+ List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size());
+
+ for (LibraryDependency lib : libraries) {
+ // get the dependencies
+ List<ManifestDependencyImpl> children = getManifestDependencies(lib.getDependencies());
+ list.add(new ManifestDependencyImpl(lib.getName(), lib.getManifest(), children));
+ }
+
+ return list;
+ }
+
+ /**
+ * Adds a dependency on tasks with the specified name in other projects. The other projects
+ * are determined from project lib dependencies using the specified configuration name.
+ * These may be projects this project depends on or projects that depend on this project
+ * based on the useDependOn argument.
+ *
+ * @param task Task to add dependencies to
+ * @param useDependedOn if true, add tasks from projects this project depends on, otherwise
+ * use projects that depend on this one.
+ * @param otherProjectTaskName name of task in other projects
+ * @param configurationName name of configuration to use to find the other projects
+ */
+ private static void addDependsOnTaskInOtherProjects(final Task task, boolean useDependedOn,
+ String otherProjectTaskName,
+ String configurationName) {
+ Project project = task.getProject();
+ final Configuration configuration = project.getConfigurations().getByName(
+ configurationName);
+ task.dependsOn(configuration.getTaskDependencyFromProjectDependency(
+ useDependedOn, otherProjectTaskName));
+ }
+
+ /**
+ * Compute a version-less key representing the given coordinates.
+ * @param coordinates the coordinate
+ * @return the key.
+ */
+ @NonNull
+ private static String computeVersionLessCoordinateKey(@NonNull MavenCoordinates coordinates) {
+ StringBuilder sb = new StringBuilder(coordinates.getGroupId());
+ sb.append(':').append(coordinates.getArtifactId());
+ if (coordinates.getClassifier() != null) {
+ sb.append(':').append(coordinates.getClassifier());
+ }
+ return sb.toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExecutionConfigurationUtil.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExecutionConfigurationUtil.java
new file mode 100644
index 0000000..68f2fa1
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExecutionConfigurationUtil.java
@@ -0,0 +1,35 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.ide.common.internal.ExecutorSingleton;
+
+import org.gradle.api.Project;
+
+public class ExecutionConfigurationUtil {
+
+
+ public static void setThreadPoolSize(Project project) {
+ Integer size = AndroidGradleOptions.getThreadPoolSize(project);
+ if (size == null) {
+ return;
+ }
+
+ ExecutorSingleton.setThreadPoolSize(size);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExtraModelInfo.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExtraModelInfo.java
new file mode 100644
index 0000000..7efb69b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ExtraModelInfo.java
@@ -0,0 +1,366 @@
+/*
+ * 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.build.gradle.internal;
+
+import static com.android.ide.common.blame.parser.JsonEncodedGradleMessageParser.STDOUT_ERROR_TAG;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.model.ArtifactMetaDataImpl;
+import com.android.build.gradle.internal.model.JavaArtifactImpl;
+import com.android.build.gradle.internal.model.SyncIssueImpl;
+import com.android.build.gradle.internal.model.SyncIssueKey;
+import com.android.build.gradle.internal.variant.DefaultSourceProviderContainer;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.ArtifactMetaData;
+import com.android.builder.model.JavaArtifact;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.builder.model.SyncIssue;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.MessageJsonSerializer;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.utils.SdkUtils;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * For storing additional model information.
+ */
+public class ExtraModelInfo extends ErrorReporter {
+
+ @NonNull
+ private final Project project;
+ private final boolean isLibrary;
+
+ @NonNull
+ private final ErrorFormatMode errorFormatMode;
+
+ private final Map<SyncIssueKey, SyncIssue> syncIssues = Maps.newHashMap();
+
+ private final Map<String, ArtifactMetaData> extraArtifactMap = Maps.newHashMap();
+ private final ListMultimap<String, AndroidArtifact> extraAndroidArtifacts = ArrayListMultimap.create();
+ private final ListMultimap<String, JavaArtifact> extraJavaArtifacts = ArrayListMultimap.create();
+
+ private final ListMultimap<String, SourceProviderContainer> extraBuildTypeSourceProviders = ArrayListMultimap.create();
+ private final ListMultimap<String, SourceProviderContainer> extraProductFlavorSourceProviders = ArrayListMultimap.create();
+ private final ListMultimap<String, SourceProviderContainer> extraMultiFlavorSourceProviders = ArrayListMultimap.create();
+
+ @Nullable
+ private final Gson mGson;
+
+ public ExtraModelInfo(@NonNull Project project, boolean isLibrary) {
+ super(computeModelQueryMode(project));
+ this.project = project;
+ this.isLibrary = isLibrary;
+ errorFormatMode = computeErrorFormatMode(project);
+ if (errorFormatMode == ErrorFormatMode.MACHINE_PARSABLE) {
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ MessageJsonSerializer.registerTypeAdapters(gsonBuilder);
+ mGson = gsonBuilder.create();
+ } else {
+ mGson = null;
+ }
+ }
+
+ public boolean isLibrary() {
+ return isLibrary;
+ }
+
+ public Map<SyncIssueKey, SyncIssue> getSyncIssues() {
+ return syncIssues;
+ }
+
+ @Override
+ @NonNull
+ protected SyncIssue handleSyncIssue(
+ @Nullable String data,
+ int type,
+ int severity,
+ @NonNull String msg) {
+ SyncIssue issue;
+ switch (getMode()) {
+ case STANDARD:
+ if (severity != SyncIssue.SEVERITY_WARNING && !isDependencyIssue(type)) {
+ throw new GradleException(msg);
+ }
+ // if it's a dependency issue we don't throw right away. we'll
+ // throw during build instead.
+ // but we do log.
+ project.getLogger().warn("WARNING: " + msg);
+ issue = new SyncIssueImpl(type, severity, data, msg);
+ break;
+ case IDE_LEGACY:
+ // compat mode for the only issue supported before the addition of SyncIssue
+ // in the model.
+ if (severity != SyncIssue.SEVERITY_WARNING
+ && type != SyncIssue.TYPE_UNRESOLVED_DEPENDENCY) {
+ throw new GradleException(msg);
+ }
+ // intended fall-through
+ case IDE:
+ // new IDE, able to support SyncIssue.
+ issue = new SyncIssueImpl(type, severity, data, msg);
+ syncIssues.put(SyncIssueKey.from(issue), issue);
+ break;
+ default:
+ throw new RuntimeException("Unknown SyncIssue type");
+ }
+
+ return issue;
+ }
+
+ private static boolean isDependencyIssue(int type) {
+ switch (type) {
+ case SyncIssue.TYPE_UNRESOLVED_DEPENDENCY:
+ case SyncIssue.TYPE_DEPENDENCY_IS_APK:
+ case SyncIssue.TYPE_DEPENDENCY_IS_APKLIB:
+ case SyncIssue.TYPE_NON_JAR_LOCAL_DEP:
+ case SyncIssue.TYPE_NON_JAR_PACKAGE_DEP:
+ case SyncIssue.TYPE_NON_JAR_PROVIDED_DEP:
+ case SyncIssue.TYPE_JAR_DEPEND_ON_AAR:
+ case SyncIssue.TYPE_MISMATCH_DEP:
+ return true;
+ }
+
+ return false;
+
+ }
+
+ @Override
+ public void receiveMessage(@NonNull Message message) {
+ switch (message.getKind()) {
+ case ERROR:
+ if (errorFormatMode == ErrorFormatMode.MACHINE_PARSABLE) {
+ project.getLogger().error(machineReadableMessage(message));
+ } else {
+ project.getLogger().error(humanReadableMessage(message));
+ }
+ break;
+ case WARNING:
+ if (errorFormatMode == ErrorFormatMode.MACHINE_PARSABLE) {
+ project.getLogger().warn(machineReadableMessage(message));
+ } else {
+ project.getLogger().warn(humanReadableMessage(message));
+ }
+ break;
+ case INFO:
+ project.getLogger().info(humanReadableMessage(message));
+ break;
+ case STATISTICS:
+ project.getLogger().trace(humanReadableMessage(message));
+ break;
+ case UNKNOWN:
+ project.getLogger().debug(humanReadableMessage(message));
+ break;
+ case SIMPLE:
+ project.getLogger().info(humanReadableMessage(message));
+ break;
+ }
+ }
+
+ private static String humanReadableMessage(@NonNull Message message) {
+ StringBuilder errorStringBuilder = new StringBuilder();
+ List<SourceFilePosition> positions = message.getSourceFilePositions();
+ if (positions.size() != 1 ||
+ !SourceFilePosition.UNKNOWN.equals(Iterables.getOnlyElement(positions))) {
+ errorStringBuilder.append(Joiner.on(' ').join(positions));
+ }
+ if (errorStringBuilder.length() > 0) {
+ errorStringBuilder.append(": ");
+ }
+ if (message.getToolName().isPresent()) {
+ errorStringBuilder.append(message.getToolName().get()).append(": ");
+ }
+ errorStringBuilder.append(message.getText());
+
+ String rawMessage = message.getRawMessage();
+ if (!message.getText().equals(message.getRawMessage())) {
+ String separator = SdkUtils.getLineSeparator();
+ errorStringBuilder.append("\n ")
+ .append(rawMessage.replace(separator, separator + " "));
+ }
+ return errorStringBuilder.toString();
+ }
+
+ /**
+ * Only call if errorFormatMode == {@link ErrorFormatMode#MACHINE_PARSABLE}
+ */
+ private String machineReadableMessage(@NonNull Message message) {
+ Preconditions.checkNotNull(mGson);
+ return STDOUT_ERROR_TAG + mGson.toJson(message);
+ }
+
+ public Collection<ArtifactMetaData> getExtraArtifacts() {
+ return extraArtifactMap.values();
+ }
+
+ public Collection<AndroidArtifact> getExtraAndroidArtifacts(@NonNull String variantName) {
+ return extraAndroidArtifacts.get(variantName);
+ }
+
+ public Collection<JavaArtifact> getExtraJavaArtifacts(@NonNull String variantName) {
+ return extraJavaArtifacts.get(variantName);
+ }
+
+ public Collection<SourceProviderContainer> getExtraFlavorSourceProviders(
+ @NonNull String flavorName) {
+ return extraProductFlavorSourceProviders.get(flavorName);
+ }
+
+ public Collection<SourceProviderContainer> getExtraBuildTypeSourceProviders(
+ @NonNull String buildTypeName) {
+ return extraBuildTypeSourceProviders.get(buildTypeName);
+ }
+
+ public void registerArtifactType(@NonNull String name,
+ boolean isTest,
+ int artifactType) {
+
+ if (extraArtifactMap.get(name) != null) {
+ throw new IllegalArgumentException(
+ String.format("Artifact with name %1$s already registered.", name));
+ }
+
+ extraArtifactMap.put(name, new ArtifactMetaDataImpl(name, isTest, artifactType));
+ }
+
+ public void registerBuildTypeSourceProvider(@NonNull String name,
+ @NonNull CoreBuildType buildType,
+ @NonNull SourceProvider sourceProvider) {
+ if (extraArtifactMap.get(name) == null) {
+ throw new IllegalArgumentException(String.format(
+ "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
+ name));
+ }
+
+ extraBuildTypeSourceProviders.put(buildType.getName(),
+ new DefaultSourceProviderContainer(name, sourceProvider));
+
+ }
+
+ public void registerProductFlavorSourceProvider(@NonNull String name,
+ @NonNull CoreProductFlavor productFlavor,
+ @NonNull SourceProvider sourceProvider) {
+ if (extraArtifactMap.get(name) == null) {
+ throw new IllegalArgumentException(String.format(
+ "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
+ name));
+ }
+
+ extraProductFlavorSourceProviders.put(productFlavor.getName(),
+ new DefaultSourceProviderContainer(name, sourceProvider));
+
+ }
+
+ public void registerMultiFlavorSourceProvider(@NonNull String name,
+ @NonNull String flavorName,
+ @NonNull SourceProvider sourceProvider) {
+ if (extraArtifactMap.get(name) == null) {
+ throw new IllegalArgumentException(String.format(
+ "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
+ name));
+ }
+
+ extraMultiFlavorSourceProviders.put(flavorName,
+ new DefaultSourceProviderContainer(name, sourceProvider));
+ }
+
+ public void registerJavaArtifact(
+ @NonNull String name,
+ @NonNull BaseVariant variant,
+ @NonNull String assembleTaskName,
+ @NonNull String javaCompileTaskName,
+ @NonNull Collection<File> generatedSourceFolders,
+ @NonNull Iterable<String> ideSetupTaskNames,
+ @NonNull Configuration configuration,
+ @NonNull File classesFolder,
+ @NonNull File javaResourcesFolder,
+ @Nullable SourceProvider sourceProvider) {
+ ArtifactMetaData artifactMetaData = extraArtifactMap.get(name);
+ if (artifactMetaData == null) {
+ throw new IllegalArgumentException(String.format(
+ "Artifact with name %1$s is not yet registered. Use registerArtifactType()",
+ name));
+ }
+ if (artifactMetaData.getType() != ArtifactMetaData.TYPE_JAVA) {
+ throw new IllegalArgumentException(
+ String.format("Artifact with name %1$s is not of type JAVA", name));
+ }
+
+ JavaArtifact artifact = new JavaArtifactImpl(
+ name, assembleTaskName, javaCompileTaskName,
+ ideSetupTaskNames, generatedSourceFolders, classesFolder, javaResourcesFolder, null,
+ new ConfigurationDependencies(configuration), sourceProvider, null);
+
+ extraJavaArtifacts.put(variant.getName(), artifact);
+ }
+
+ /**
+ * Returns whether we are just trying to build a model for the IDE instead of building. This
+ * means we will attempt to resolve dependencies even if some are broken/unsupported to avoid
+ * failing the import in the IDE.
+ */
+ private static EvaluationMode computeModelQueryMode(@NonNull Project project) {
+ if (AndroidGradleOptions.buildModelOnlyAdvanced(project)) {
+ return EvaluationMode.IDE;
+ }
+
+ if (AndroidGradleOptions.buildModelOnly(project)) {
+ return EvaluationMode.IDE_LEGACY;
+ }
+
+ return EvaluationMode.STANDARD;
+ }
+
+ private static ErrorFormatMode computeErrorFormatMode(@NonNull Project project) {
+ if (AndroidGradleOptions.invokedFromIde(project)) {
+ return ErrorFormatMode.MACHINE_PARSABLE;
+ } else {
+ return ErrorFormatMode.HUMAN_READABLE;
+ }
+ }
+
+ @NonNull
+ public ErrorFormatMode getErrorFormatMode() {
+ return errorFormatMode;
+ }
+
+ public enum ErrorFormatMode {
+ MACHINE_PARSABLE, HUMAN_READABLE
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryCache.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryCache.java
new file mode 100644
index 0000000..1d31bd1
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryCache.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import static com.android.SdkConstants.FD_JARS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.GuardedBy;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.RelativePath;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import groovy.lang.Closure;
+
+/**
+ * Cache to library prepareTask.
+ *
+ * Each project creates its own version of LibraryDependencyImpl, but they all represent the
+ * same library. This creates a single task that will unarchive the aar so that this is done only
+ * once even for multi-module projects where 2+ modules depend on the same library.
+ *
+ * The prepareTask is created in the root project always.
+ */
+public class LibraryCache {
+
+ @NonNull
+ private static final LibraryCache sCache = new LibraryCache();
+
+ @NonNull
+ public static LibraryCache getCache() {
+ return sCache;
+ }
+
+ public synchronized void unload() {
+ bundleLatches.clear();
+ }
+
+ @GuardedBy("this")
+ private final Map<String, CountDownLatch> bundleLatches = Maps.newHashMap();
+
+ public void unzipLibrary(
+ @NonNull String taskName,
+ @NonNull Project project,
+ @NonNull File bundle,
+ @NonNull File folderOut) throws IOException {
+
+ // only synchronize access to the latch so that unzipping 2+ different
+ // libraries in parallel will work.
+ boolean newItem = false;
+ CountDownLatch latch;
+ synchronized (this) {
+ String path = bundle.getCanonicalPath();
+ latch = bundleLatches.get(path);
+ if (latch == null) {
+ latch = new CountDownLatch(1);
+ bundleLatches.put(path, latch);
+ newItem = true;
+ }
+ }
+
+ if (newItem) {
+ try {
+ project.getLogger().debug("$taskName: ERASE ${folderOut.getPath()}");
+
+ unzipAar(bundle, folderOut, project);
+
+ project.getLogger().debug(
+ "$taskName: UNZIP ${bundle.getPath()} -> ${folderOut.getPath()}");
+ } finally {
+ latch.countDown();
+ }
+ } else {
+ while (true) {
+ try {
+ latch.await();
+ break;
+ } catch (InterruptedException e) {
+ // Cycle again.
+ }
+ }
+ }
+ }
+
+ public static void unzipAar(final File bundle, final File folderOut, final Project project) {
+ for (File f : Files.fileTreeTraverser().postOrderTraversal(folderOut)) {
+ f.delete();
+ }
+
+ folderOut.mkdirs();
+
+ project.copy(new Closure(LibraryCache.class) {
+ public Object doCall(CopySpec cs) {
+ cs.from(project.zipTree(bundle));
+ cs.into(folderOut);
+ cs.filesMatching("**/*.jar", new Action<FileCopyDetails>() {
+ @Override
+ public void execute(FileCopyDetails details) {
+ /*
+ * For each jar, check where it is. /classes.jar, /lint.jar and jars in
+ * /libs are moved inside the FD_JARS directory. Jars inside /assets or
+ * /res/raw are kept where they were. All other jars are ignored and a
+ * warning is issued.
+ */
+ String path = details.getRelativePath().getPathString();
+ if (path.equals("classes.jar") || path.equals("lint.jar")
+ || path.startsWith("libs/")) {
+ details.setRelativePath(new RelativePath(false, FD_JARS).plus(
+ details.getRelativePath()));
+ } else if (!path.startsWith("res/raw/*") && !path.startsWith("assets/*")) {
+ project.getLogger().warn("Jar found at unexpected path (" + path
+ + ") in " + bundle + " and will be ignored. Jars should be "
+ + "placed inside 'jars' folder to be merged into dex. Jars "
+ + "that are in assets/ or res/raw/ will be copied as-is.");
+ }
+ }
+ });
+
+ return cs;
+ }
+ });
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryTaskManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryTaskManager.java
new file mode 100644
index 0000000..d30e776
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LibraryTaskManager.java
@@ -0,0 +1,608 @@
+/*
+ * 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.build.gradle.internal;
+
+import static com.android.SdkConstants.FD_JNI;
+import static com.android.SdkConstants.FD_RENDERSCRIPT;
+import static com.android.SdkConstants.FN_ANNOTATIONS_ZIP;
+import static com.android.SdkConstants.FN_CLASSES_JAR;
+import static com.android.SdkConstants.LIBS_FOLDER;
+import static com.android.builder.dependency.LibraryBundle.FN_PROGUARD_TXT;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.pipeline.TransformTask;
+import com.android.build.gradle.internal.scope.AndroidTask;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.LibraryJarTransform;
+import com.android.build.gradle.internal.tasks.LibraryJniLibsTransform;
+import com.android.build.gradle.internal.tasks.MergeFileTask;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.internal.variant.LibVariantOutputData;
+import com.android.build.gradle.internal.variant.LibraryVariantData;
+import com.android.build.gradle.internal.variant.VariantHelper;
+import com.android.build.gradle.tasks.ExtractAnnotations;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.dependency.LibraryBundle;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.dependency.ManifestDependency;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+import com.android.utils.FileUtils;
+import com.android.utils.StringHelper;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.plugins.BasePlugin;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.bundling.Zip;
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.gradle.tooling.BuildException;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * TaskManager for creating tasks in an Android library project.
+ */
+public class LibraryTaskManager extends TaskManager {
+
+ private static final String ANNOTATIONS = "annotations";
+
+ private Task assembleDefault;
+
+ public LibraryTaskManager (
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ super(project, androidBuilder, dataBindingBuilder, extension, sdkHandler,dependencyManager, toolingRegistry);
+ }
+
+ @Override
+ public void createTasksForVariantData(
+ @NonNull final TaskFactory tasks,
+ @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ final LibraryVariantData libVariantData = (LibraryVariantData) variantData;
+ final GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
+ final CoreBuildType buildType = variantConfig.getBuildType();
+
+ final VariantScope variantScope = variantData.getScope();
+ GlobalScope globalScope = variantScope.getGlobalScope();
+
+ final File intermediatesDir = globalScope.getIntermediatesDir();
+ final Collection<String> variantDirectorySegments = variantConfig.getDirectorySegments();
+ final File variantBundleDir = FileUtils.join(
+ intermediatesDir,
+ StringHelper.toStrings(DIR_BUNDLES, variantDirectorySegments));
+
+ createAnchorTasks(tasks, variantScope);
+
+ // Create all current streams (dependencies mostly at this point)
+ createDependencyStreams(variantScope);
+
+ createCheckManifestTask(tasks, variantScope);
+
+ // Add a task to create the res values
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createGenerateResValuesTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to process the manifest(s)
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createMergeLibManifestsTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to compile renderscript files.
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createRenderscriptTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ AndroidTask<MergeResources> packageRes = ThreadRecorder.get().record(
+ ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
+ new Recorder.Block<AndroidTask<MergeResources>>() {
+ @Override
+ public AndroidTask<MergeResources> call() throws Exception {
+ // Create a merge task to only merge the resources from this library and not
+ // the dependencies. This is what gets packaged in the aar.
+ AndroidTask<MergeResources> mergeResourceTask =
+ basicCreateMergeResourcesTask(
+ tasks,
+ variantScope,
+ "package",
+ FileUtils.join(variantBundleDir, "res"),
+ false /*includeDependencies*/,
+ false /*process9Patch*/);
+
+ if (variantData.getVariantDependency().hasNonOptionalLibraries()) {
+ // Add a task to merge the resource folders, including the libraries, in order to
+ // generate the R.txt file with all the symbols, including the ones from
+ // the dependencies.
+ createMergeResourcesTask(tasks, variantScope);
+ }
+
+ mergeResourceTask.configure(tasks,
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ MergeResources mergeResourcesTask = (MergeResources) task;
+ mergeResourcesTask.setPublicFile(FileUtils.join(
+ variantBundleDir,
+ SdkConstants.FN_PUBLIC_TXT
+ ));
+ }
+ });
+
+ return mergeResourceTask;
+ }
+ });
+
+ // Add a task to merge the assets folders
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ createMergeAssetsTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a task to create the BuildConfig class
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createBuildConfigTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ // Add a task to generate resource source files, directing the location
+ // of the r.txt file to be directly in the bundle.
+ createProcessResTask(tasks, variantScope, variantBundleDir,
+ false /*generateResourcePackage*/);
+
+ // process java resources
+ createProcessJavaResTasks(tasks, variantScope);
+ return null;
+ }
+ });
+
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_AIDL_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createAidlTask(tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add a compile task
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_COMPILE_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ AndroidTask<? extends JavaCompile> javacTask =
+ createJavacTask(tasks, variantScope);
+ TaskManager.setJavaCompilerTask(javacTask, tasks, variantScope);
+ return null;
+ }
+ });
+
+ // Add data binding tasks if enabled
+ if (extension.getDataBinding().isEnabled()) {
+ createDataBindingTasks(tasks, variantScope);
+ }
+
+ // Add dependencies on NDK tasks if NDK plugin is applied.
+ if (!isComponentModelPlugin) {
+ // Add NDK tasks
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_NDK_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createNdkTasks(variantScope);
+ return null;
+ }
+ });
+ }
+ variantScope.setNdkBuildable(getNdkBuildable(variantData));
+
+ // merge jni libs.
+ createMergeJniLibFoldersTasks(tasks, variantScope);
+
+ Sync packageRenderscript = ThreadRecorder.get().record(
+ ExecutionType.LIB_TASK_MANAGER_CREATE_PACKAGING_TASK,
+ new Recorder.Block<Sync>() {
+ @Override
+ public Sync call() throws Exception {
+ // package the renderscript header files files into the bundle folder
+ Sync packageRenderscript = project.getTasks().create(
+ variantScope.getTaskName("package", "Renderscript"), Sync.class);
+ // package from 3 sources. the order is important to make sure the override works well.
+ packageRenderscript.from(variantConfig.getRenderscriptSourceList())
+ .include("**/*.rsh");
+ packageRenderscript.into(new File(variantBundleDir, FD_RENDERSCRIPT));
+ return packageRenderscript;
+ }
+ });
+
+ // merge consumer proguard files from different build types and flavors
+ MergeFileTask mergeProGuardFileTask = ThreadRecorder.get().record(
+ ExecutionType.LIB_TASK_MANAGER_CREATE_MERGE_PROGUARD_FILE_TASK,
+ new Recorder.Block<MergeFileTask>() {
+ @Override
+ public MergeFileTask call() throws Exception {
+ MergeFileTask mergeProGuardFileTask = project.getTasks().create(
+ variantScope.getTaskName("merge", "ProguardFiles"),
+ MergeFileTask.class);
+ mergeProGuardFileTask.setVariantName(variantConfig.getFullName());
+ mergeProGuardFileTask.setInputFiles(
+ project.files(variantConfig.getConsumerProguardFiles())
+ .getFiles());
+ mergeProGuardFileTask.setOutputFile(
+ new File(variantBundleDir, FN_PROGUARD_TXT));
+ return mergeProGuardFileTask;
+ }
+
+ });
+
+ // copy lint.jar into the bundle folder
+ Copy lintCopy = project.getTasks().create(
+ variantScope.getTaskName("copy", "Lint"), Copy.class);
+ lintCopy.dependsOn(LINT_COMPILE);
+ lintCopy.from(new File(
+ globalScope.getIntermediatesDir(),
+ "lint/lint.jar"));
+ lintCopy.into(variantBundleDir);
+
+ final Zip bundle = project.getTasks().create(variantScope.getTaskName("bundle"), Zip.class);
+ if (variantData.getVariantDependency().isAnnotationsPresent()) {
+ libVariantData.generateAnnotationsTask =
+ createExtractAnnotations(project, variantData);
+ }
+ if (libVariantData.generateAnnotationsTask != null) {
+ bundle.dependsOn(libVariantData.generateAnnotationsTask);
+ }
+
+ final boolean instrumented = variantConfig.getBuildType().isTestCoverageEnabled();
+
+ ThreadRecorder.get().record(
+ ExecutionType.LIB_TASK_MANAGER_CREATE_POST_COMPILATION_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ TransformManager transformManager = variantScope.getTransformManager();
+
+ // ----- Code Coverage first -----
+ if (instrumented) {
+ createJacocoTransform(tasks, variantScope);
+ }
+
+ // ----- External Transforms -----
+ // apply all the external transforms.
+ List<Transform> customTransforms = extension.getTransforms();
+ List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();
+
+ for (int i = 0, count = customTransforms.size() ; i < count ; i++) {
+ Transform transform = customTransforms.get(i);
+
+ // Check the transform only applies to supported scopes for libraries:
+ // We cannot transform scopes that are not packaged in the library
+ // itself.
+ Sets.SetView<Scope> difference = Sets.difference(transform.getScopes(),
+ TransformManager.SCOPE_FULL_LIBRARY);
+ if (!difference.isEmpty()) {
+ String scopes = difference.toString();
+ androidBuilder.getErrorReporter().handleSyncError(
+ "",
+ SyncIssue.TYPE_GENERIC,
+ String.format("Transforms with scopes '%s' cannot be applied to library projects.",
+ scopes));
+ }
+
+ AndroidTask<TransformTask> task = transformManager
+ .addTransform(tasks, variantScope, transform);
+ if (task != null) {
+ List<Object> deps = customTransformsDependencies.get(i);
+ if (!deps.isEmpty()) {
+ task.dependsOn(tasks, deps);
+ }
+
+ // if the task is a no-op then we make assemble task depend on it.
+ if (transform.getScopes().isEmpty()) {
+ variantData.assembleVariantTask.dependsOn(tasks, task);
+ }
+ }
+ }
+
+ // ----- Minify next -----
+ if (buildType.isMinifyEnabled()) {
+ createMinifyTransform(tasks, variantScope, false);
+ }
+
+ // now add a transform that will take all the class/res and package them
+ // into the main and secondary jar files.
+ // This transform technically does not use its transform output, but that's
+ // ok. We use the transform mechanism to get incremental data from
+ // the streams.
+
+ String packageName = variantConfig.getPackageFromManifest();
+ if (packageName == null) {
+ throw new BuildException("Failed to read manifest", null);
+ }
+
+ LibraryJarTransform transform = new LibraryJarTransform(
+ new File(variantBundleDir, FN_CLASSES_JAR),
+ new File(variantBundleDir, LIBS_FOLDER),
+ packageName,
+ getExtension().getPackageBuildConfig());
+ excludeDataBindingClassesIfNecessary(variantScope, transform);
+
+ AndroidTask<TransformTask> jarPackagingTask = transformManager
+ .addTransform(tasks, variantScope, transform);
+ bundle.dependsOn(jarPackagingTask.getName());
+
+ // now add a transform that will take all the native libs and package
+ // them into the libs folder of the bundle.
+ LibraryJniLibsTransform jniTransform = new LibraryJniLibsTransform(
+ new File(variantBundleDir, FD_JNI));
+ AndroidTask<TransformTask> jniPackagingTask = transformManager
+ .addTransform(tasks, variantScope, jniTransform);
+ bundle.dependsOn(jniPackagingTask.getName());
+
+ return null;
+ }
+ });
+
+ bundle.dependsOn(packageRes.getName(), packageRenderscript, lintCopy,
+ mergeProGuardFileTask);
+ bundle.dependsOn(variantScope.getNdkBuildable());
+
+ bundle.setDescription("Assembles a bundle containing the library in " +
+ variantConfig.getFullName() + ".");
+ bundle.setDestinationDir(
+ new File(globalScope.getOutputsDir(), BuilderConstants.EXT_LIB_ARCHIVE));
+ bundle.setArchiveName(globalScope.getProjectBaseName()
+ + "-" + variantConfig.getBaseName()
+ + "." + BuilderConstants.EXT_LIB_ARCHIVE);
+ bundle.setExtension(BuilderConstants.EXT_LIB_ARCHIVE);
+ bundle.from(variantBundleDir);
+ bundle.from(FileUtils.join(intermediatesDir,
+ StringHelper.toStrings(ANNOTATIONS, variantDirectorySegments)));
+
+ // get the single output for now, though that may always be the case for a library.
+ LibVariantOutputData variantOutputData = libVariantData.getOutputs().get(0);
+ variantOutputData.packageLibTask = bundle;
+
+ variantData.assembleVariantTask.dependsOn(bundle);
+ variantOutputData.assembleTask = variantData.assembleVariantTask;
+
+ if (getExtension().getDefaultPublishConfig().equals(variantConfig.getFullName())) {
+ VariantHelper.setupDefaultConfig(project,
+ variantData.getVariantDependency().getPackageConfiguration());
+
+ // add the artifact that will be published
+ project.getArtifacts().add("default", bundle);
+
+ getAssembleDefault().dependsOn(variantData.assembleVariantTask);
+ }
+
+ // also publish the artifact with its full config name
+ if (getExtension().getPublishNonDefault()) {
+ project.getArtifacts().add(
+ variantData.getVariantDependency().getPublishConfiguration().getName(), bundle);
+ bundle.setClassifier(
+ variantData.getVariantDependency().getPublishConfiguration().getName());
+ }
+
+ // configure the variant to be testable.
+ variantConfig.setOutput(new LibraryBundle(
+ bundle.getArchivePath(),
+ variantBundleDir,
+ variantData.getName(),
+ project.getPath()) {
+ @Override
+ @Nullable
+ public String getProjectVariant() {
+ return variantData.getName();
+ }
+
+ @NonNull
+ @Override
+ public List<LibraryDependency> getDependencies() {
+ return variantConfig.getDirectLibraries();
+ }
+
+ @NonNull
+ @Override
+ public List<? extends AndroidLibrary> getLibraryDependencies() {
+ return variantConfig.getDirectLibraries();
+ }
+
+ @NonNull
+ @Override
+ public List<? extends ManifestDependency> getManifestDependencies() {
+ return variantConfig.getDirectLibraries();
+ }
+
+ @Override
+ @Nullable
+ public MavenCoordinates getRequestedCoordinates() {
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public MavenCoordinates getResolvedCoordinates() {
+ return null;
+ }
+
+ @Override
+ @NonNull
+ protected File getJarsRootFolder() {
+ return getFolder();
+ }
+
+ @Override
+ public boolean isOptional() {
+ return false;
+ }
+ });
+
+ ThreadRecorder.get().record(ExecutionType.LIB_TASK_MANAGER_CREATE_LINT_TASK,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createLintTasks(tasks, variantScope);
+ return null;
+ }
+ });
+ }
+
+ private void excludeDataBindingClassesIfNecessary(final VariantScope variantScope,
+ LibraryJarTransform transform) {
+ if (!extension.getDataBinding().isEnabled()) {
+ return;
+ }
+ transform.addExcludeListProvider(
+ new LibraryJarTransform.ExcludeListProvider() {
+ @Nullable
+ @Override
+ public List<String> getExcludeList() {
+ final File excludeFile = variantScope.getVariantData().getType()
+ .isExportDataBindingClassList() ? variantScope
+ .getGeneratedClassListOutputFileForDataBinding() : null;
+ return dataBindingBuilder.getJarExcludeList(
+ variantScope.getVariantData().getLayoutXmlProcessor(), excludeFile
+ );
+ }
+ });
+ }
+
+ @NonNull
+ @Override
+ protected Set<Scope> getResMergingScopes(@NonNull VariantScope variantScope) {
+ if (variantScope.getTestedVariantData() != null) {
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+ return TransformManager.SCOPE_FULL_LIBRARY;
+ }
+
+ public ExtractAnnotations createExtractAnnotations(
+ final Project project,
+ final BaseVariantData variantData) {
+ final GradleVariantConfiguration config = variantData.getVariantConfiguration();
+
+ final ExtractAnnotations task = project.getTasks().create(
+ variantData.getScope().getTaskName("extract", "Annotations"),
+ ExtractAnnotations.class);
+ task.setDescription(
+ "Extracts Android annotations for the " + variantData.getVariantConfiguration()
+ .getFullName()
+ + " variant into the archive file");
+ task.setGroup(BasePlugin.BUILD_GROUP);
+ task.variant = variantData;
+ task.setDestinationDir(new File(
+ variantData.getScope().getGlobalScope().getIntermediatesDir(),
+ ANNOTATIONS + "/" + config.getDirName()));
+ task.output = new File(task.getDestinationDir(), FN_ANNOTATIONS_ZIP);
+ task.classDir = new File(variantData.getScope().getGlobalScope().getIntermediatesDir(),
+ "classes/" + variantData.getVariantConfiguration().getDirName());
+ task.setSource(variantData.getJavaSources());
+ task.encoding = getExtension().getCompileOptions().getEncoding();
+ task.setSourceCompatibility(
+ getExtension().getCompileOptions().getSourceCompatibility().toString());
+ ConventionMappingHelper.map(task, "classpath", new Callable<ConfigurableFileCollection>() {
+ @Override
+ public ConfigurableFileCollection call() throws Exception {
+ return project.files(androidBuilder.getCompileClasspath(config));
+ }
+ });
+ task.dependsOn(variantData.getScope().getJavacTask().getName());
+
+ // Setup the boot classpath just before the task actually runs since this will
+ // force the sdk to be parsed. (Same as in compileTask)
+ task.doFirst(new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ if (task instanceof ExtractAnnotations) {
+ ExtractAnnotations extractAnnotations = (ExtractAnnotations) task;
+ extractAnnotations.bootClasspath = androidBuilder.getBootClasspathAsStrings(false);
+ }
+ }
+ });
+
+ return task;
+ }
+
+
+ private Task getAssembleDefault() {
+ if (assembleDefault == null) {
+ assembleDefault = project.getTasks().findByName("assembleDefault");
+ }
+ return assembleDefault;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
new file mode 100644
index 0000000..40c026a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+import static java.io.File.separator;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
+import com.android.sdklib.BuildToolInfo;
+import com.android.tools.lint.LintCliClient;
+import com.android.tools.lint.LintCliFlags;
+import com.android.tools.lint.Warning;
+import com.android.tools.lint.checks.UnusedResourceDetector;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class LintGradleClient extends LintCliClient {
+ private final AndroidProject mModelProject;
+
+ /**
+ * Variant to run the client on.
+ */
+ @NonNull private Variant mVariant;
+
+ private final org.gradle.api.Project mGradleProject;
+ private List<File> mCustomRules = Lists.newArrayList();
+ private File mSdkHome;
+ private final BuildToolInfo mBuildToolInfo;
+
+ public LintGradleClient(
+ @NonNull IssueRegistry registry,
+ @NonNull LintCliFlags flags,
+ @NonNull org.gradle.api.Project gradleProject,
+ @NonNull AndroidProject modelProject,
+ @Nullable File sdkHome,
+ @NonNull Variant variant,
+ @Nullable BuildToolInfo buildToolInfo) {
+ super(flags, CLIENT_GRADLE);
+ mGradleProject = gradleProject;
+ mModelProject = modelProject;
+ mSdkHome = sdkHome;
+ mRegistry = registry;
+ mBuildToolInfo = buildToolInfo;
+ mVariant = variant;
+ }
+
+ public void setCustomRules(List<File> customRules) {
+ mCustomRules = customRules;
+ }
+
+ @NonNull
+ @Override
+ public List<File> findRuleJars(@NonNull Project project) {
+ return mCustomRules;
+ }
+
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ // Should not be called by lint since we supply an explicit set of projects
+ // to the LintRequest
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public File getSdkHome() {
+ if (mSdkHome != null) {
+ return mSdkHome;
+ }
+ return super.getSdkHome();
+ }
+
+ @Override
+ @Nullable
+ public File getCacheDir(boolean create) {
+ File dir = new File(mGradleProject.getRootProject().getBuildDir(),
+ FD_INTERMEDIATES + separator + "lint-cache"); //$NON-NLS-1$
+ if (dir.exists() || create && dir.mkdirs()) {
+ return dir;
+ }
+
+ return super.getCacheDir(create);
+ }
+
+ @Override
+ @NonNull
+ protected LintRequest createLintRequest(@NonNull List<File> files) {
+ LintRequest lintRequest = new LintRequest(this, files);
+ if (mVariant == null) {
+ lintRequest.setProjects(Lists.<Project>newArrayList());
+ } else {
+ Pair<LintGradleProject,List<File>> result = LintGradleProject.create(
+ this, mModelProject, mVariant, mGradleProject);
+ lintRequest.setProjects(Collections.<Project>singletonList(result.getFirst()));
+ setCustomRules(result.getSecond());
+ }
+
+ return lintRequest;
+ }
+
+ /** Run lint with the given registry and return the resulting warnings */
+ @NonNull
+ public List<Warning> run(@NonNull IssueRegistry registry) throws IOException {
+ run(registry, Collections.<File>emptyList());
+ return mWarnings;
+ }
+
+ /**
+ * Given a list of results from separate variants, merge them into a single
+ * list of warnings, and mark their
+ * @param warningMap a map from variant to corresponding warnings
+ * @param project the project model
+ * @return a merged list of issues
+ */
+ @NonNull
+ public static List<Warning> merge(
+ @NonNull Map<Variant,List<Warning>> warningMap,
+ @NonNull AndroidProject project) {
+ // Easy merge?
+ if (warningMap.size() == 1) {
+ return warningMap.values().iterator().next();
+ }
+ int maxCount = 0;
+ for (List<Warning> warnings : warningMap.values()) {
+ int size = warnings.size();
+ maxCount = Math.max(size, maxCount);
+ }
+ if (maxCount == 0) {
+ return Collections.emptyList();
+ }
+
+ int totalVariantCount = project.getVariants().size();
+
+ List<Warning> merged = Lists.newArrayListWithExpectedSize(2 * maxCount);
+
+ // Map fro issue to message to line number to file name to canonical warning
+ Map<Issue,Map<String, Map<Integer, Map<String, Warning>>>> map =
+ Maps.newHashMapWithExpectedSize(2 * maxCount);
+
+ for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
+ Variant variant = entry.getKey();
+ List<Warning> warnings = entry.getValue();
+ for (Warning warning : warnings) {
+ Map<String,Map<Integer,Map<String,Warning>>> messageMap = map.get(warning.issue);
+ if (messageMap == null) {
+ messageMap = Maps.newHashMap();
+ map.put(warning.issue, messageMap);
+ }
+ Map<Integer, Map<String, Warning>> lineMap = messageMap.get(warning.message);
+ if (lineMap == null) {
+ lineMap = Maps.newHashMap();
+ messageMap.put(warning.message, lineMap);
+ }
+ Map<String, Warning> fileMap = lineMap.get(warning.line);
+ if (fileMap == null) {
+ fileMap = Maps.newHashMap();
+ lineMap.put(warning.line, fileMap);
+ }
+ String fileName = warning.file != null ? warning.file.getName() : "<unknown>";
+ Warning canonical = fileMap.get(fileName);
+ if (canonical == null) {
+ canonical = warning;
+ fileMap.put(fileName, canonical);
+ canonical.variants = Sets.newHashSet();
+ canonical.gradleProject = project;
+ merged.add(canonical);
+ }
+ canonical.variants.add(variant);
+ }
+ }
+
+ // Clear out variants on any nodes that define all
+ for (Warning warning : merged) {
+ if (warning.variants != null && warning.variants.size() == totalVariantCount) {
+ // If this error is present in all variants, just clear it out
+ warning.variants = null;
+ }
+
+ }
+
+ Collections.sort(merged);
+ return merged;
+ }
+
+ @Override
+ protected void addProgressPrinter() {
+ // No progress printing from the Gradle lint task; gradle tasks
+ // do not really do that, even for long-running jobs.
+ }
+
+ @Nullable
+ @Override
+ public BuildToolInfo getBuildTools(@NonNull Project project) {
+ return mBuildToolInfo;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
new file mode 100644
index 0000000..a121772
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
@@ -0,0 +1,726 @@
+package com.android.build.gradle.internal;
+
+import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT;
+import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
+import static java.io.File.separatorChar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.builder.model.Variant;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Project;
+import com.android.utils.Pair;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An implementation of Lint's {@link Project} class wrapping a Gradle model (project or
+ * library)
+ */
+public class LintGradleProject extends Project {
+ protected AndroidVersion mMinSdkVersion;
+ protected AndroidVersion mTargetSdkVersion;
+
+ private LintGradleProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull File manifest) {
+ super(client, dir, referenceDir);
+ mGradleProject = true;
+ mMergeManifests = true;
+ mDirectLibraries = Lists.newArrayList();
+ readManifest(manifest);
+ }
+
+ /**
+ * Creates a {@link com.android.build.gradle.internal.LintGradleProject} from
+ * the given {@link com.android.builder.model.AndroidProject} definition for
+ * a given {@link com.android.builder.model.Variant}, and returns it along with
+ * a set of lint custom rule jars applicable for the given model project.
+ *
+ * @param client the client
+ * @param project the model project
+ * @param variant the variant
+ * @param gradleProject the gradle project
+ * @return a pair of new project and list of custom rule jars
+ */
+ @NonNull
+ public static Pair<LintGradleProject, List<File>> create(
+ @NonNull LintGradleClient client,
+ @NonNull AndroidProject project,
+ @NonNull Variant variant,
+ @NonNull org.gradle.api.Project gradleProject) {
+ File dir = gradleProject.getProjectDir();
+ AppGradleProject lintProject = new AppGradleProject(client, dir,
+ dir, project, variant);
+
+ List<File> customRules = Lists.newArrayList();
+ File appLintJar = new File(gradleProject.getBuildDir(),
+ "lint" + separatorChar + "lint.jar");
+ if (appLintJar.exists()) {
+ customRules.add(appLintJar);
+ }
+
+ Set<AndroidLibrary> libraries = Sets.newHashSet();
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+ for (AndroidLibrary library : dependencies.getLibraries()) {
+ lintProject.addDirectLibrary(createLibrary(client, library, libraries, customRules));
+ }
+
+ return Pair.<LintGradleProject,List<File>>of(lintProject, customRules);
+ }
+
+ @Override
+ protected void initialize() {
+ // Deliberately not calling super; that code is for ADT compatibility
+ }
+
+ protected void readManifest(File manifest) {
+ if (manifest.exists()) {
+ try {
+ String xml = Files.toString(manifest, Charsets.UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ if (document != null) {
+ readManifest(document);
+ }
+ } catch (IOException e) {
+ mClient.log(e, "Could not read manifest %1$s", manifest);
+ }
+ }
+ }
+
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ protected static boolean dependsOn(@NonNull Dependencies dependencies,
+ @NonNull String artifact) {
+ for (AndroidLibrary library : dependencies.getLibraries()) {
+ if (dependsOn(library, artifact)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static boolean dependsOn(@NonNull AndroidLibrary library, @NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (library.getJarFile().getName().startsWith("support-v4-")) {
+ return true;
+ }
+
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ File bundle = library.getBundle();
+ if (bundle.getName().startsWith("appcompat-v7-")) {
+ return true;
+ }
+ }
+
+ for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+ if (dependsOn(dependency, artifact)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void addDirectLibrary(@NonNull Project project) {
+ mDirectLibraries.add(project);
+ }
+
+ @NonNull
+ private static LibraryProject createLibrary(@NonNull LintGradleClient client,
+ @NonNull AndroidLibrary library,
+ @NonNull Set<AndroidLibrary> seen, List<File> customRules) {
+ seen.add(library);
+ File dir = library.getFolder();
+ LibraryProject project = new LibraryProject(client, dir, dir, library);
+
+ File ruleJar = library.getLintJar();
+ if (ruleJar.exists()) {
+ customRules.add(ruleJar);
+ }
+
+ for (AndroidLibrary dependent : library.getLibraryDependencies()) {
+ if (!seen.contains(dependent)) {
+ project.addDirectLibrary(createLibrary(client, dependent, seen, customRules));
+ }
+ }
+
+ return project;
+ }
+
+ private static class AppGradleProject extends LintGradleProject {
+ private AndroidProject mProject;
+ private Variant mVariant;
+ private List<SourceProvider> mProviders;
+ private List<SourceProvider> mTestProviders;
+
+ private AppGradleProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull AndroidProject project,
+ @NonNull Variant variant) {
+ //TODO FIXME: handle multi-apk
+ super(client, dir, referenceDir,
+ variant.getMainArtifact().getOutputs().iterator().next().getGeneratedManifest());
+
+ mProject = project;
+ mVariant = variant;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return mProject.isLibrary();
+ }
+
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ return mProject;
+ }
+
+ @Override
+ public Variant getCurrentVariant() {
+ return mVariant;
+ }
+
+ private List<SourceProvider> getSourceProviders() {
+ if (mProviders == null) {
+ List<SourceProvider> providers = Lists.newArrayList();
+ AndroidArtifact mainArtifact = mVariant.getMainArtifact();
+
+ providers.add(mProject.getDefaultConfig().getSourceProvider());
+
+ for (String flavorName : mVariant.getProductFlavors()) {
+ for (ProductFlavorContainer flavor : mProject.getProductFlavors()) {
+ if (flavorName.equals(flavor.getProductFlavor().getName())) {
+ providers.add(flavor.getSourceProvider());
+ break;
+ }
+ }
+ }
+
+ SourceProvider multiProvider = mainArtifact.getMultiFlavorSourceProvider();
+ if (multiProvider != null) {
+ providers.add(multiProvider);
+ }
+
+ String buildTypeName = mVariant.getBuildType();
+ for (BuildTypeContainer buildType : mProject.getBuildTypes()) {
+ if (buildTypeName.equals(buildType.getBuildType().getName())) {
+ providers.add(buildType.getSourceProvider());
+ break;
+ }
+ }
+
+ SourceProvider variantProvider = mainArtifact.getVariantSourceProvider();
+ if (variantProvider != null) {
+ providers.add(variantProvider);
+ }
+
+ mProviders = providers;
+ }
+
+ return mProviders;
+ }
+
+ private List<SourceProvider> getTestSourceProviders() {
+ if (mTestProviders == null) {
+ List<SourceProvider> providers = Lists.newArrayList();
+
+ ProductFlavorContainer defaultConfig = mProject.getDefaultConfig();
+ for (SourceProviderContainer extra : defaultConfig.getExtraSourceProviders()) {
+ String artifactName = extra.getArtifactName();
+ if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
+ providers.add(extra.getSourceProvider());
+ }
+ }
+
+ for (String flavorName : mVariant.getProductFlavors()) {
+ for (ProductFlavorContainer flavor : mProject.getProductFlavors()) {
+ if (flavorName.equals(flavor.getProductFlavor().getName())) {
+ for (SourceProviderContainer extra : flavor.getExtraSourceProviders()) {
+ String artifactName = extra.getArtifactName();
+ if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
+ providers.add(extra.getSourceProvider());
+ }
+ }
+ }
+ }
+ }
+
+ String buildTypeName = mVariant.getBuildType();
+ for (BuildTypeContainer buildType : mProject.getBuildTypes()) {
+ if (buildTypeName.equals(buildType.getBuildType().getName())) {
+ for (SourceProviderContainer extra : buildType.getExtraSourceProviders()) {
+ String artifactName = extra.getArtifactName();
+ if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
+ providers.add(extra.getSourceProvider());
+ }
+ }
+ }
+ }
+
+ mTestProviders = providers;
+ }
+
+ return mTestProviders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ mManifestFiles = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ File manifestFile = provider.getManifestFile();
+ if (manifestFile.exists()) { // model returns path whether or not it exists
+ mManifestFiles.add(manifestFile);
+ }
+ }
+ }
+
+ return mManifestFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getProguardFiles() {
+ if (mProguardFiles == null) {
+ ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
+ mProguardFiles = Lists.newArrayList();
+ for (File file : flavor.getProguardFiles()) {
+ if (file.exists()) {
+ mProguardFiles.add(file);
+ }
+ }
+ try {
+ for (File file : flavor.getConsumerProguardFiles()) {
+ if (file.exists()) {
+ mProguardFiles.add(file);
+ }
+ }
+ } catch (Throwable t) {
+ // On some models, this threw
+ // org.gradle.tooling.model.UnsupportedMethodException:
+ // Unsupported method: BaseConfig.getConsumerProguardFiles().
+ // Playing it safe for a while.
+ }
+ }
+
+ return mProguardFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getResourceFolders() {
+ if (mResourceFolders == null) {
+ mResourceFolders = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ Collection<File> resDirs = provider.getResDirectories();
+ for (File res : resDirs) {
+ if (res.exists()) { // model returns path whether or not it exists
+ mResourceFolders.add(res);
+ }
+ }
+ }
+
+ for (File file : mVariant.getMainArtifact().getGeneratedResourceFolders()) {
+ if (file.exists()) {
+ mResourceFolders.add(file);
+ }
+ }
+ }
+
+ return mResourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getAssetFolders() {
+ if (mAssetFolders == null) {
+ mAssetFolders = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ Collection<File> dirs = provider.getAssetsDirectories();
+ for (File dir : dirs) {
+ if (dir.exists()) { // model returns path whether or not it exists
+ mAssetFolders.add(dir);
+ }
+ }
+ }
+ }
+
+ return mAssetFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaSourceFolders() {
+ if (mJavaSourceFolders == null) {
+ mJavaSourceFolders = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ Collection<File> srcDirs = provider.getJavaDirectories();
+ for (File srcDir : srcDirs) {
+ if (srcDir.exists()) { // model returns path whether or not it exists
+ mJavaSourceFolders.add(srcDir);
+ }
+ }
+ }
+
+ for (File file : mVariant.getMainArtifact().getGeneratedSourceFolders()) {
+ if (file.exists()) {
+ mJavaSourceFolders.add(file);
+ }
+ }
+ }
+
+ return mJavaSourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getTestSourceFolders() {
+ if (mTestSourceFolders == null) {
+ mTestSourceFolders = Lists.newArrayList();
+ for (SourceProvider provider : getTestSourceProviders()) {
+ Collection<File> srcDirs = provider.getJavaDirectories();
+ for (File srcDir : srcDirs) {
+ if (srcDir.exists()) { // model returns path whether or not it exists
+ mTestSourceFolders.add(srcDir);
+ }
+ }
+ }
+ }
+
+ return mTestSourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaClassFolders() {
+ if (mJavaClassFolders == null) {
+ mJavaClassFolders = new ArrayList<File>(1);
+ File outputClassFolder = mVariant.getMainArtifact().getClassesFolder();
+ if (outputClassFolder.exists()) {
+ mJavaClassFolders.add(outputClassFolder);
+ }
+ }
+
+ return mJavaClassFolders;
+ }
+
+ private static boolean sProvidedAvailable = true;
+
+ @NonNull
+ @Override
+ public List<File> getJavaLibraries(boolean includeProvided) {
+ if (includeProvided) {
+ if (mJavaLibraries == null) {
+ Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
+ Collection<JavaLibrary> libs = dependencies.getJavaLibraries();
+ mJavaLibraries = Lists.newArrayListWithExpectedSize(libs.size());
+ for (JavaLibrary lib : libs) {
+ File jar = lib.getJarFile();
+ if (jar.exists()) {
+ mJavaLibraries.add(jar);
+ }
+ }
+ }
+ return mJavaLibraries;
+ } else {
+ // Skip provided libraries?
+ if (mNonProvidedJavaLibraries == null) {
+ Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
+ Collection<JavaLibrary> libs = dependencies.getJavaLibraries();
+ mNonProvidedJavaLibraries = Lists.newArrayListWithExpectedSize(libs.size());
+ for (JavaLibrary lib : libs) {
+ File jar = lib.getJarFile();
+ if (jar.exists()) {
+ if (sProvidedAvailable) {
+ // Method added in 1.4-rc1; gracefully handle running with
+ // older plugins
+ try {
+ if (lib.isProvided()) {
+ continue;
+ }
+ } catch (Throwable t) {
+ //noinspection AssignmentToStaticFieldFromInstanceMethod
+ sProvidedAvailable = false; // don't try again
+ }
+ }
+
+ mNonProvidedJavaLibraries.add(jar);
+ }
+ }
+ }
+ return mNonProvidedJavaLibraries;
+ }
+ }
+
+ @Nullable
+ @Override
+ public String getPackage() {
+ // For now, lint only needs the manifest package; not the potentially variant specific
+ // package. As part of the Gradle work on the Lint API we should make two separate
+ // package lookup methods -- one for the manifest package, one for the build package
+ if (mPackage == null) { // only used as a fallback in case manifest somehow is null
+ String packageName = mProject.getDefaultConfig().getProductFlavor().getApplicationId();
+ if (packageName != null) {
+ return packageName;
+ }
+ }
+
+ return mPackage; // from manifest
+ }
+
+ @Override
+ @NonNull
+ public AndroidVersion getMinSdkVersion() {
+ if (mMinSdkVersion == null) {
+ ApiVersion minSdk = mVariant.getMergedFlavor().getMinSdkVersion();
+ if (minSdk == null) {
+ ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
+ minSdk = flavor.getMinSdkVersion();
+ }
+ if (minSdk != null) {
+ mMinSdkVersion = LintUtils.convertVersion(minSdk, mClient.getTargets());
+ } else {
+ mMinSdkVersion = super.getMinSdkVersion(); // from manifest
+ }
+ }
+
+ return mMinSdkVersion;
+ }
+
+ @Override
+ @NonNull
+ public AndroidVersion getTargetSdkVersion() {
+ if (mTargetSdkVersion == null) {
+ ApiVersion targetSdk = mVariant.getMergedFlavor().getTargetSdkVersion();
+ if (targetSdk == null) {
+ ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
+ targetSdk = flavor.getTargetSdkVersion();
+ }
+ if (targetSdk != null) {
+ mTargetSdkVersion = LintUtils.convertVersion(targetSdk, mClient.getTargets());
+ } else {
+ mTargetSdkVersion = super.getTargetSdkVersion(); // from manifest
+ }
+ }
+
+ return mTargetSdkVersion;
+ }
+
+ @Override
+ public int getBuildSdk() {
+ String compileTarget = mProject.getCompileTarget();
+ AndroidVersion version = AndroidTargetHash.getPlatformVersion(compileTarget);
+ if (version != null) {
+ return version.getApiLevel();
+ }
+
+ return super.getBuildSdk();
+ }
+
+ @Nullable
+ @Override
+ public Boolean dependsOn(@NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (mSupportLib == null) {
+ Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
+ mSupportLib = dependsOn(dependencies, artifact);
+ }
+ return mSupportLib;
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ if (mAppCompat == null) {
+ Dependencies dependencies = mVariant.getMainArtifact().getDependencies();
+ mAppCompat = dependsOn(dependencies, artifact);
+ }
+ return mAppCompat;
+ } else {
+ return super.dependsOn(artifact);
+ }
+ }
+ }
+
+ private static class LibraryProject extends LintGradleProject {
+ private AndroidLibrary mLibrary;
+
+ private LibraryProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull AndroidLibrary library) {
+ super(client, dir, referenceDir, library.getManifest());
+ mLibrary = library;
+
+ // TODO: Make sure we don't use this project for any source library projects!
+ mReportIssues = false;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return true;
+ }
+
+ @Override
+ public AndroidLibrary getGradleLibraryModel() {
+ return mLibrary;
+ }
+
+ @Override
+ public Variant getCurrentVariant() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ File manifest = mLibrary.getManifest();
+ if (manifest.exists()) {
+ mManifestFiles = Collections.singletonList(manifest);
+ } else {
+ mManifestFiles = Collections.emptyList();
+ }
+ }
+
+ return mManifestFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getProguardFiles() {
+ if (mProguardFiles == null) {
+ File proguardRules = mLibrary.getProguardRules();
+ if (proguardRules.exists()) {
+ mProguardFiles = Collections.singletonList(proguardRules);
+ } else {
+ mProguardFiles = Collections.emptyList();
+ }
+ }
+
+ return mProguardFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getResourceFolders() {
+ if (mResourceFolders == null) {
+ File folder = mLibrary.getResFolder();
+ if (folder.exists()) {
+ mResourceFolders = Collections.singletonList(folder);
+ } else {
+ mResourceFolders = Collections.emptyList();
+ }
+ }
+
+ return mResourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getAssetFolders() {
+ if (mAssetFolders == null) {
+ File folder = mLibrary.getAssetsFolder();
+ if (folder.exists()) {
+ mAssetFolders = Collections.singletonList(folder);
+ } else {
+ mAssetFolders = Collections.emptyList();
+ }
+ }
+
+ return mAssetFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaSourceFolders() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getTestSourceFolders() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaClassFolders() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaLibraries(boolean includeProvided) {
+ if (!includeProvided && mLibrary.isOptional()) {
+ return Collections.emptyList();
+ }
+
+ if (mJavaLibraries == null) {
+ mJavaLibraries = Lists.newArrayList();
+ File jarFile = mLibrary.getJarFile();
+ if (jarFile.exists()) {
+ mJavaLibraries.add(jarFile);
+ }
+
+ for (File local : mLibrary.getLocalJars()) {
+ if (local.exists()) {
+ mJavaLibraries.add(local);
+ }
+ }
+ }
+
+ return mJavaLibraries;
+ }
+
+ @Nullable
+ @Override
+ public Boolean dependsOn(@NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (mSupportLib == null) {
+ mSupportLib = dependsOn(mLibrary, artifact);
+ }
+ return mSupportLib;
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ if (mAppCompat == null) {
+ mAppCompat = dependsOn(mLibrary, artifact);
+ }
+ return mAppCompat;
+ } else {
+ return super.dependsOn(artifact);
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.java
new file mode 100644
index 0000000..03fea5e
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.res2.MergingException;
+import com.android.utils.ILogger;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+/**
+ * Implementation of Android's {@link ILogger} over Gradle's {@link Logger}.
+ */
+public class LoggerWrapper implements ILogger {
+
+ private final Logger logger;
+
+ private final LogLevel infoLogLevel;
+
+ public static LoggerWrapper getLogger(Class<?> klass) {
+ return new LoggerWrapper(Logging.getLogger(klass));
+ }
+
+ public LoggerWrapper(@NonNull Logger logger) {
+ this(logger, LogLevel.INFO);
+ }
+
+ /**
+ * Allow {@link ILogger} info() level messages to be remapped to e.g. {@link
+ * LogLevel}.LIFECYCLE} rather than INFO
+ *
+ * This is useful for installs and uninstalls, so install details can be shown without the user
+ * having to use the noisy INFO log level, while not flagging informational log output as
+ * warnings or errors.
+ */
+ public LoggerWrapper(@NonNull Logger logger, @NonNull LogLevel infoLogLevel) {
+ this.logger = logger;
+ this.infoLogLevel = infoLogLevel;
+ }
+
+ @Override
+ public void error(Throwable throwable, String s, Object... objects) {
+ if (throwable instanceof MergingException) {
+ // MergingExceptions have a known cause: they aren't internal errors, they
+ // are errors in the user's code, so a full exception is not helpful (and
+ // these exceptions should include a pointer to the user's error right in
+ // the message).
+ //
+ // Furthermore, these exceptions are already caught by the MergeResources
+ // and MergeAsset tasks, so don't duplicate the output
+ return;
+ }
+
+ if (!logger.isEnabled(LogLevel.ERROR)) {
+ return;
+ }
+
+ if (objects != null && objects.length > 0) {
+ s = String.format(s, objects);
+ }
+
+ if (throwable == null) {
+ logger.log(LogLevel.ERROR, s);
+
+ } else {
+ logger.log(LogLevel.ERROR, s, throwable);
+ }
+ }
+
+ @Override
+ public void warning(@NonNull String s, Object... objects) {
+ if (!logger.isEnabled(LogLevel.WARN)) {
+ return;
+ }
+ if (objects == null || objects.length == 0) {
+ logger.log(LogLevel.WARN, s);
+ } else {
+ logger.log(LogLevel.WARN, String.format(s, objects));
+ }
+ }
+
+ @Override
+ public void info(@NonNull String s, Object... objects) {
+ if (!logger.isEnabled(infoLogLevel)) {
+ return;
+ }
+ if (objects == null || objects.length == 0) {
+ logger.log(infoLogLevel, s);
+ } else {
+ logger.log(infoLogLevel, String.format(s, objects));
+ }
+ }
+
+ @Override
+ public void verbose(@NonNull String s, Object... objects) {
+ if (!logger.isEnabled(LogLevel.DEBUG)) {
+ return;
+ }
+ if (objects == null || objects.length == 0) {
+ logger.log(LogLevel.DEBUG, s);
+
+ } else {
+ logger.log(LogLevel.DEBUG, String.format(s, objects));
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggingUtil.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggingUtil.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggingUtil.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/LoggingUtil.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/NdkHandler.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/NdkHandler.java
new file mode 100644
index 0000000..6eacfb3
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/NdkHandler.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.core.Toolchain;
+import com.android.repository.Revision;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.utils.FileUtils;
+import com.android.utils.Pair;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Closeables;
+
+import org.gradle.api.InvalidUserDataException;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Handles NDK related information.
+ */
+public class NdkHandler {
+
+ @Nullable
+ private String platformVersion;
+ private boolean resolvedSdkVersion;
+ private final Toolchain toolchain;
+ private final String toolchainVersion;
+ private final File ndkDirectory;
+
+ private Map<Pair<Toolchain, Abi>, Revision> defaultToolchainVersions = Maps.newHashMap();
+
+
+ public NdkHandler(
+ @NonNull File projectDir,
+ @Nullable String platformVersion,
+ @NonNull String toolchainName,
+ @NonNull String toolchainVersion) {
+ if (platformVersion != null) {
+ setPlatformVersion(platformVersion);
+ }
+ this.toolchain = Toolchain.getByName(toolchainName);
+ this.toolchainVersion = toolchainVersion;
+ ndkDirectory = findNdkDirectory(projectDir);
+ }
+
+ @Nullable
+ private String getPlatformVersion() {
+ if (!resolvedSdkVersion) {
+ resolveCompileSdkVersion();
+ }
+ return platformVersion;
+ }
+
+ /**
+ * Retrieve the newest supported version if it is not the specified version is not supported.
+ *
+ * An older NDK may not support the specified compiledSdkVersion. In that case, determine what
+ * is the newest supported version and modifycompileSdkVersion.
+ */
+ private void resolveCompileSdkVersion() {
+ if (platformVersion == null) {
+ return;
+ }
+ File platformFolder = new File(ndkDirectory, "/platforms/" + platformVersion);
+ if (!platformFolder.exists()) {
+ int targetVersion;
+ try {
+ targetVersion = Integer.parseInt(platformVersion.substring("android-".length()));
+ } catch (NumberFormatException ignore) {
+ // If the targetVerison is not a number, most likely it is a preview version.
+ // In that case, assume we are using the highest available version.
+ targetVersion = Integer.MAX_VALUE;
+ }
+
+ File[] platformFolders = new File(ndkDirectory, "/platforms/").listFiles(
+ new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isDirectory();
+ }
+ });
+ int highestVersion = 0;
+ for(File platform :platformFolders) {
+ if (platform.getName().startsWith("android-")) {
+ try {
+ int version = Integer.parseInt(
+ platform.getName().substring("android-".length()));
+ if (version > highestVersion && version < targetVersion) {
+ highestVersion = version;
+ platformVersion = "android-" + version;
+ }
+ } catch(NumberFormatException ignore) {
+ }
+ }
+ }
+ }
+ resolvedSdkVersion = true;
+ }
+
+ public void setPlatformVersion(@NonNull String platformVersion) {
+ // Ensure compileSdkVersion is in platform hash string format (e.g. "android-21").
+ AndroidVersion androidVersion = AndroidTargetHash.getVersionFromHash(platformVersion);
+ if (androidVersion == null) {
+ this.platformVersion = null;
+ } else {
+ this.platformVersion = AndroidTargetHash.getPlatformHashString(androidVersion);
+ }
+ resolvedSdkVersion = false;
+ }
+
+ public Toolchain getToolchain() {
+ return toolchain;
+ }
+
+ public String getToolchainVersion() {
+ return toolchainVersion;
+ }
+
+ /**
+ * Determine the location of the NDK directory.
+ *
+ * The NDK directory can be set in the local.properties file or using the ANDROID_NDK_HOME
+ * environment variable.
+ */
+ private static File findNdkDirectory(File projectDir) {
+ File localProperties = new File(projectDir, FN_LOCAL_PROPERTIES);
+
+ if (localProperties.isFile()) {
+
+ Properties properties = new Properties();
+ InputStreamReader reader = null;
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ FileInputStream fis = new FileInputStream(localProperties);
+ reader = new InputStreamReader(fis, Charsets.UTF_8);
+ properties.load(reader);
+ } catch (FileNotFoundException ignored) {
+ // ignore since we check up front and we don't want to fail on it anyway
+ // in case there's an env var.
+ } catch (IOException e) {
+ throw new RuntimeException(String.format("Unable to read %1$s.", localProperties), e);
+ } finally {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+
+ String ndkDirProp = properties.getProperty("ndk.dir");
+ if (ndkDirProp != null) {
+ return new File(ndkDirProp);
+ }
+
+ } else {
+ String envVar = System.getenv("ANDROID_NDK_HOME");
+ if (envVar != null) {
+ return new File(envVar);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the directory of the NDK.
+ */
+ @Nullable
+ public File getNdkDirectory() {
+ return ndkDirectory;
+ }
+
+ /**
+ * Return true if NDK directory is configured.
+ */
+ public boolean isNdkDirConfigured() {
+ return ndkDirectory != null;
+ }
+
+ private static String getToolchainPrefix(Toolchain toolchain, Abi abi) {
+ if (toolchain == Toolchain.GCC) {
+ return abi.getGccToolchainPrefix();
+ } else {
+ return "llvm";
+ }
+ }
+
+ /**
+ * Return the directory containing the toolchain.
+ *
+ * @param toolchain toolchain to use.
+ * @param toolchainVersion toolchain version to use.
+ * @param abi target ABI of the toolchaina
+ * @return a directory that contains the executables.
+ */
+ private File getToolchainPath(
+ Toolchain toolchain,
+ String toolchainVersion,
+ Abi abi) {
+ String version = toolchainVersion.isEmpty()
+ ? getDefaultToolchainVersion(toolchain, abi).toString()
+ : toolchainVersion;
+
+ File prebuiltFolder = new File(
+ ndkDirectory,
+ "toolchains/" + getToolchainPrefix(toolchain, abi) + "-" + version + "/prebuilt");
+
+ String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
+ String hostOs;
+ if (osName.contains("windows")) {
+ hostOs = "windows";
+ } else if (osName.contains("mac")) {
+ hostOs = "darwin";
+ } else {
+ hostOs = "linux";
+ }
+
+ // There should only be one directory in the prebuilt folder. If there are more than one
+ // attempt to determine the right one based on the operating system.
+ File[] toolchainPaths = prebuiltFolder.listFiles(
+ new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isDirectory();
+ }
+ });
+
+ if (toolchainPaths == null) {
+ throw new InvalidUserDataException("Unable to find toolchain: " + prebuiltFolder);
+ }
+ if (toolchainPaths.length == 1) {
+ return toolchainPaths[0];
+ }
+
+ // Use 64-bit toolchain if available.
+ File toolchainPath = new File(prebuiltFolder, hostOs + "-x86_64");
+ if (toolchainPath.isDirectory()) {
+ return toolchainPath;
+ }
+
+ // Fallback to 32-bit if we can't find the 64-bit toolchain.
+ String osString = (osName.equals("windows")) ? hostOs : hostOs + "-x86";
+ toolchainPath = new File(prebuiltFolder, osString);
+ if (toolchainPath.isDirectory()) {
+ return toolchainPath;
+ } else {
+ throw new InvalidUserDataException("Unable to find toolchain prebuilt folder in: "
+ + prebuiltFolder);
+ }
+ }
+
+ /**
+ * Returns the sysroot directory for the toolchain.
+ */
+ public String getSysroot(Abi abi) {
+ if (getPlatformVersion() == null) {
+ return "";
+ } else {
+ return ndkDirectory + "/platforms/" + getPlatformVersion() + "/arch-"
+ + abi.getArchitecture();
+ }
+ }
+
+ /**
+ * Return true if compiledSdkVersion supports 64 bits ABI.
+ */
+ public boolean supports64Bits() {
+ if (getPlatformVersion() == null) {
+ return false;
+ }
+ String targetString = getPlatformVersion().replace("android-", "");
+ try {
+ return Integer.parseInt(targetString) >= 20;
+ } catch (NumberFormatException ignored) {
+ // "android-L" supports 64-bits.
+ return true;
+ }
+ }
+
+ /**
+ * Return the default version of the specified toolchain for a target abi.
+ *
+ * The default version is the highest version found in the NDK for the specified toolchain and
+ * ABI. The result is cached for performance.
+ */
+ private Revision getDefaultToolchainVersion(Toolchain toolchain, final Abi abi) {
+ Revision defaultVersion = defaultToolchainVersions.get(Pair.of(toolchain, abi));
+ if (defaultVersion != null) {
+ return defaultVersion;
+ }
+
+ final String toolchainPrefix = getToolchainPrefix(toolchain, abi);
+ File toolchains = new File(ndkDirectory, "toolchains");
+ File[] toolchainsForAbi = toolchains.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.startsWith(toolchainPrefix);
+ }
+ });
+ if (toolchainsForAbi == null || toolchainsForAbi.length == 0) {
+ throw new RuntimeException(
+ "No toolchains found in the NDK toolchains folder for ABI with prefix: "
+ + toolchainPrefix);
+ }
+
+ // Once we have a list of toolchains, we look the highest version
+ Revision bestRevision = null;
+ for (File toolchainFolder : toolchainsForAbi) {
+ String folderName = toolchainFolder.getName();
+ String version = folderName.substring(toolchainPrefix.length() + 1);
+ try {
+ Revision revision = Revision.parseRevision(version);
+ if (bestRevision == null || revision.compareTo(bestRevision) > 0) {
+ bestRevision = revision;
+ }
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ defaultToolchainVersions.put(Pair.of(toolchain, abi), bestRevision);
+ if (bestRevision == null) {
+ throw new RuntimeException("Unable to find a valid toolchain in " + toolchains);
+ }
+ return bestRevision;
+ }
+
+ /**
+ * Return the version of gcc that will be used by the NDK.
+ *
+ * Gcc is used by clang for linking. It also contains gnu-libstdc++.
+ *
+ * If the gcc toolchain is used, then it's simply the toolchain version requested by the user.
+ * If clang is used, then it depends the target abi.
+ */
+ public String getGccToolchainVersion(Abi abi) {
+ return (toolchain == Toolchain.GCC && !toolchainVersion.isEmpty())
+ ? toolchainVersion
+ : getDefaultToolchainVersion(Toolchain.GCC, abi).toString();
+ }
+
+ /**
+ * Return the folder containing gcc that will be used by the NDK.
+ */
+ public File getDefaultGccToolchainPath(Abi abi) {
+ return getToolchainPath(Toolchain.GCC, getGccToolchainVersion(abi), abi);
+ }
+
+ /**
+ * Returns a list of all ABI.
+ */
+ public static Collection<Abi> getAbiList() {
+ return ImmutableList.copyOf(Abi.values());
+ }
+
+ /**
+ * Returns a list of 32-bits ABI.
+ */
+ public static Collection<Abi> getAbiList32() {
+ ImmutableList.Builder<Abi> builder = ImmutableList.builder();
+ for (Abi abi : Abi.values()) {
+ if (!abi.supports64Bits()) {
+ builder.add(abi);
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns a list of supported ABI.
+ */
+ public Collection<Abi> getSupportedAbis() {
+ return supports64Bits() ? getAbiList() : getAbiList32();
+ }
+
+ /**
+ * Return the executable for compiling C code.
+ */
+ public File getCCompiler(Abi abi) {
+ String compiler = toolchain == Toolchain.CLANG ? "clang" : abi.getGccExecutablePrefix() + "-gcc";
+ return new File(getToolchainPath(toolchain, toolchainVersion, abi), "bin/" + compiler);
+ }
+
+ /**
+ * Return the executable for compiling C++ code.
+ */
+ public File getCppCompiler(Abi abi) {
+ String compiler = toolchain == Toolchain.CLANG ? "clang++" : abi.getGccExecutablePrefix() + "-g++";
+ return new File(getToolchainPath(toolchain, toolchainVersion, abi), "bin/" + compiler);
+ }
+
+ /**
+ * Return the executable for removing debug symbols from a shared object.
+ */
+ public File getStripCommand(Abi abi) {
+ return FileUtils.join(
+ getDefaultGccToolchainPath(abi),
+ "bin",
+ abi.getGccExecutablePrefix() + "-strip");
+ }
+
+ /**
+ * Return a list of include directories for an STl.
+ */
+ public List<File> getStlIncludes(
+ @Nullable String stlName,
+ @Nullable String stlVersion,
+ @NonNull Abi abi) {
+ File stlBaseDir = new File(ndkDirectory, "sources/cxx-stl/");
+ if (stlName == null || stlName.isEmpty()) {
+ stlName = "system";
+ } else if (stlName.contains("_")) {
+ stlName = stlName.substring(0, stlName.indexOf('_'));
+ }
+
+ List<File> includeDirs = Lists.newArrayList();
+ if (stlName.equals("system")) {
+ includeDirs.add(new File(stlBaseDir, "system/include"));
+ } else if (stlName.equals("stlport")) {
+ includeDirs.add(new File(stlBaseDir, "stlport/stlport"));
+ includeDirs.add(new File(stlBaseDir, "gabi++/include"));
+ } else if (stlName.equals("gnustl")) {
+ String version = stlVersion != null ? stlVersion : getGccToolchainVersion(abi) ;
+ includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + version + "/include"));
+ includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + version + "/libs/"
+ + abi.getName() + "/include"));
+ includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + version + "/include/backward"));
+ } else if (stlName.equals("gabi++")) {
+ includeDirs.add(new File(stlBaseDir, "gabi++/include"));
+ } else if (stlName.equals("c++")) {
+ includeDirs.add(new File(stlBaseDir, "llvm-libc++/libcxx/include"));
+ includeDirs.add(new File(stlBaseDir, "gabi++/include"));
+ includeDirs.add(new File(stlBaseDir, "../android/support/include"));
+ }
+
+ return includeDirs;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorCombo.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorCombo.java
new file mode 100644
index 0000000..aa7d2ad
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorCombo.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.DimensionAware;
+import com.android.builder.model.ProductFlavor;
+import com.android.utils.StringHelper;
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Named;
+
+import java.util.List;
+
+/**
+ * A combination of product flavors for a variant, each belonging to a different flavor dimension.
+ */
+public class ProductFlavorCombo<T extends DimensionAware & Named> {
+ private String name;
+
+ @NonNull
+ private final List<T> flavorList;
+
+ /**
+ * Create a ProductFlavorCombo.
+ * @param flavors Lists of ProductFlavor.
+ */
+ public ProductFlavorCombo(@NonNull T... flavors) {
+ flavorList = ImmutableList.copyOf(flavors);
+ }
+
+ public ProductFlavorCombo(@NonNull Iterable<T> flavors) {
+ flavorList = ImmutableList.copyOf(flavors);
+ }
+
+ @NonNull
+ public String getName() {
+ if (name == null) {
+ boolean first = true;
+ StringBuilder sb = new StringBuilder();
+ for (T flavor : flavorList) {
+ if (first) {
+ sb.append(flavor.getName());
+ first = false;
+ } else {
+ sb.append(StringHelper.capitalize(flavor.getName()));
+ }
+ }
+ name = sb.toString();
+ }
+ return name;
+ }
+
+ @NonNull
+ public List<T> getFlavorList() {
+ return flavorList;
+ }
+
+ /**
+ * Return the name of the combined list of flavors.
+ */
+ @NonNull
+ public static String getFlavorComboName(List<? extends Named> flavorList) {
+ Iterable<String> flavorNames = Iterables.transform(
+ flavorList,
+ new Function<Named, String>() {
+ @Override
+ public String apply(Named namedObject) {
+ return namedObject.getName();
+ }
+ });
+ return StringHelper.combineAsCamelCase(flavorNames);
+ }
+
+ /**
+ * Creates a list containing all combinations of ProductFlavors of the given dimensions.
+ * @param flavorDimensions The dimensions each product flavor can belong to.
+ * @param productFlavors An iterable of all ProductFlavors in the project..
+ * @return A list of ProductFlavorCombo representing all combinations of ProductFlavors.
+ */
+ @NonNull
+ public static <S extends DimensionAware & Named> List<ProductFlavorCombo<S>> createCombinations(
+ @Nullable List<String> flavorDimensions,
+ @NonNull Iterable<S> productFlavors) {
+
+ List <ProductFlavorCombo<S>> result = Lists.newArrayList();
+ if (flavorDimensions == null || flavorDimensions.isEmpty()) {
+ for (S flavor : productFlavors) {
+ result.add(new ProductFlavorCombo<S>(ImmutableList.of(flavor)));
+ }
+ } else {
+ // need to group the flavor per dimension.
+ // First a map of dimension -> list(ProductFlavor)
+ ArrayListMultimap<String, S> map = ArrayListMultimap.create();
+ for (S flavor : productFlavors) {
+ String flavorDimension = flavor.getDimension();
+
+ if (flavorDimension == null) {
+ throw new RuntimeException(String.format(
+ "Flavor '%1$s' has no flavor dimension.", flavor.getName()));
+ }
+ if (!flavorDimensions.contains(flavorDimension)) {
+ throw new RuntimeException(String.format(
+ "Flavor '%1$s' has unknown dimension '%2$s'.",
+ flavor.getName(), flavor.getDimension()));
+ }
+
+ map.put(flavorDimension, flavor);
+ }
+
+ createProductFlavorCombinations(result,
+ Lists.<S>newArrayListWithCapacity(flavorDimensions.size()),
+ 0, flavorDimensions, map);
+ }
+ return result;
+ }
+
+ /**
+ * Remove all null reference from an array and create an ImmutableList it.
+ */
+ private static ImmutableList<ProductFlavor> filterNullFromArray(ProductFlavor[] flavors) {
+ ImmutableList.Builder<ProductFlavor> builder = ImmutableList.builder();
+ for (ProductFlavor flavor : flavors) {
+ if (flavor != null) {
+ builder.add(flavor);
+ }
+ }
+ return builder.build();
+ }
+
+ private static <S extends DimensionAware & Named> void createProductFlavorCombinations(
+ List<ProductFlavorCombo<S>> flavorGroups,
+ List<S> group,
+ int index,
+ List<String> flavorDimensionList,
+ ListMultimap<String, S> map) {
+ if (index == flavorDimensionList.size()) {
+ flavorGroups.add(new ProductFlavorCombo<S>(Iterables.filter(group, Predicates.notNull())));
+ return;
+ }
+
+ // fill the array at the current index.
+ // get the dimension name that matches the index we are filling.
+ String dimension = flavorDimensionList.get(index);
+
+ // from our map, get all the possible flavors in that dimension.
+ List<S> flavorList = map.get(dimension);
+
+ // loop on all the flavors to add them to the current index and recursively fill the next
+ // indices.
+ if (flavorList.isEmpty()) {
+ throw new RuntimeException(String.format(
+ "No flavor is associated with flavor dimension '%1$s'.", dimension));
+ } else {
+ for (S flavor : flavorList) {
+ group.add(flavor);
+ createProductFlavorCombinations(
+ flavorGroups, group, index + 1, flavorDimensionList, map);
+ group.remove(group.size() - 1);
+ }
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java
new file mode 100644
index 0000000..11248b8
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SdkHandler.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.LibraryRequest;
+import com.android.builder.sdk.DefaultSdkLoader;
+import com.android.builder.sdk.PlatformLoader;
+import com.android.builder.sdk.SdkInfo;
+import com.android.builder.sdk.SdkLoader;
+import com.android.builder.sdk.TargetInfo;
+import com.android.repository.Revision;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.io.Closeables;
+
+import org.gradle.api.Project;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collection;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handles the all things SDK for the Gradle plugin. There is one instance per project, around
+ * a singleton {@link com.android.builder.sdk.SdkLoader}.
+ */
+public class SdkHandler {
+
+ // Used for injecting SDK location in tests.
+ public static File sTestSdkFolder;
+
+ private static final Object LOCK_FOR_SDK_HANDLER = new Object();
+ @GuardedBy("LOCK_FOR_SDK_HANDLER")
+ private static SdkLoader sSdkLoader;
+
+ @NonNull
+ private final ILogger logger;
+
+ private SdkLoader sdkLoader;
+ private File sdkFolder;
+ private File ndkFolder;
+ private boolean isRegularSdk = true;
+
+ public static void setTestSdkFolder(File testSdkFolder) {
+ sTestSdkFolder = testSdkFolder;
+ }
+
+ /**
+ * Returns true if we should use a cached SDK, false if we should force the re-parsing of the
+ * SDK components.
+ */
+ public static boolean useCachedSdk(Project project) {
+ // only used cached version of the sdk when in instant run mode but not
+ // syncing.
+ return AndroidGradleOptions.getOptionalCompilationSteps(project).contains(
+ OptionalCompilationStep.INSTANT_DEV)
+ && !AndroidGradleOptions.buildModelOnlyAdvanced(project);
+ }
+
+ public SdkHandler(@NonNull Project project,
+ @NonNull ILogger logger) {
+ this.logger = logger;
+ findLocation(project);
+ }
+
+ public SdkInfo getSdkInfo() {
+ SdkLoader sdkLoader = getSdkLoader();
+ return sdkLoader.getSdkInfo(logger);
+ }
+
+ public void initTarget(
+ @NonNull String targetHash,
+ @NonNull Revision buildToolRevision,
+ @NonNull Collection<LibraryRequest> usedLibraries,
+ @NonNull AndroidBuilder androidBuilder,
+ boolean useCachedVersion) {
+ Preconditions.checkNotNull(targetHash, "android.compileSdkVersion is missing!");
+ Preconditions.checkNotNull(buildToolRevision, "android.buildToolsVersion is missing!");
+
+ synchronized (LOCK_FOR_SDK_HANDLER) {
+ if (useCachedVersion) {
+ if (sSdkLoader == null) {
+ logger.info("Parsing the Sdk");
+ sSdkLoader = getSdkLoader();
+ } else {
+ logger.info("Reusing the SdkLoader");
+ }
+ } else {
+ logger.info("Parsing the SDK, no caching allowed");
+ sSdkLoader = getSdkLoader();
+ }
+ sdkLoader = sSdkLoader;
+ }
+
+
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ SdkInfo sdkInfo = sdkLoader.getSdkInfo(logger);
+ TargetInfo targetInfo = sdkLoader.getTargetInfo(targetHash, buildToolRevision, logger);
+
+ androidBuilder.setTargetInfo(sdkInfo, targetInfo, usedLibraries);
+ logger.verbose("SDK initialized in %1$d ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ }
+
+ @Nullable
+ public File getSdkFolder() {
+ return sdkFolder;
+ }
+
+ @Nullable
+ public File getAndCheckSdkFolder() {
+ if (sdkFolder == null) {
+ throw new RuntimeException(
+ "SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.");
+ }
+
+ return sdkFolder;
+ }
+
+ public synchronized SdkLoader getSdkLoader() {
+ if (sdkLoader == null) {
+ if (isRegularSdk) {
+ getAndCheckSdkFolder();
+
+ // check if the SDK folder actually exist.
+ // For internal test we provide a fake SDK location through
+ // setTestSdkFolder in order to have an SDK, even though we don't use it
+ // so in this case we ignore the check.
+ if (sTestSdkFolder == null && !sdkFolder.isDirectory()) {
+ throw new RuntimeException(String.format(
+ "The SDK directory '%1$s' does not exist.", sdkFolder));
+ }
+
+ sdkLoader = DefaultSdkLoader.getLoader(sdkFolder);
+ } else {
+ sdkLoader = PlatformLoader.getLoader(sdkFolder);
+ }
+ }
+
+ return sdkLoader;
+ }
+
+ public synchronized void unload() {
+ if (sdkLoader != null) {
+ if (isRegularSdk) {
+ DefaultSdkLoader.unload();
+ } else {
+ PlatformLoader.unload();
+ }
+
+ sdkLoader = null;
+ }
+ }
+
+ @Nullable
+ public File getNdkFolder() {
+ return ndkFolder;
+ }
+
+ private void findSdkLocation(@NonNull Properties properties, @NonNull File rootDir) {
+ String sdkDirProp = properties.getProperty("sdk.dir");
+ if (sdkDirProp != null) {
+ sdkFolder = new File(sdkDirProp);
+ if (!sdkFolder.isAbsolute()) {
+ sdkFolder = new File(rootDir, sdkDirProp);
+ }
+ return;
+ }
+
+ sdkDirProp = properties.getProperty("android.dir");
+ if (sdkDirProp != null) {
+ sdkFolder = new File(rootDir, sdkDirProp);
+ isRegularSdk = false;
+ return;
+ }
+
+ String envVar = System.getenv("ANDROID_HOME");
+ if (envVar != null) {
+ sdkFolder = new File(envVar);
+ return;
+ }
+
+ String property = System.getProperty("android.home");
+ if (property != null) {
+ sdkFolder = new File(property);
+ }
+ }
+
+ private void findNdkLocation(@NonNull Properties properties) {
+ String ndkDirProp = properties.getProperty("ndk.dir");
+ if (ndkDirProp != null) {
+ ndkFolder = new File(ndkDirProp);
+ return;
+ }
+
+ String envVar = System.getenv("ANDROID_NDK_HOME");
+ if (envVar != null) {
+ ndkFolder = new File(envVar);
+ }
+ }
+
+ private void findLocation(@NonNull Project project) {
+ if (sTestSdkFolder != null) {
+ sdkFolder = sTestSdkFolder;
+ return;
+ }
+
+ File rootDir = project.getRootDir();
+ File localProperties = new File(rootDir, FN_LOCAL_PROPERTIES);
+ Properties properties = new Properties();
+
+ if (localProperties.isFile()) {
+ InputStreamReader reader = null;
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ FileInputStream fis = new FileInputStream(localProperties);
+ reader = new InputStreamReader(fis, Charsets.UTF_8);
+ properties.load(reader);
+ } catch (FileNotFoundException ignored) {
+ // ignore since we check up front and we don't want to fail on it anyway
+ // in case there's an env var.
+ } catch (IOException e) {
+ throw new RuntimeException(
+ String.format("Unable to read %1$s.", localProperties.getAbsolutePath()),
+ e);
+ } finally {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+
+ findSdkLocation(properties, rootDir);
+ findNdkLocation(properties);
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SourceSetSourceProviderWrapper.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SourceSetSourceProviderWrapper.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SourceSetSourceProviderWrapper.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/SourceSetSourceProviderWrapper.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskContainerAdaptor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskContainerAdaptor.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskContainerAdaptor.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskContainerAdaptor.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskFactory.java
new file mode 100644
index 0000000..bfff5be
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskFactory.java
@@ -0,0 +1,65 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.annotations.Nullable;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+
+/**
+ * Interface for a container that can create Task.
+ */
+public interface TaskFactory {
+ /**
+ * Returns true if this collection contains an item with the given name.
+ */
+ boolean containsKey(String name);
+
+ /**
+ * Creates a task with the given name.
+ */
+ void create(String name);
+
+ /**
+ * Creates a task and initialize it with the given configAction.
+ */
+ void create(String name, Action<? super Task> configAction);
+
+ /**
+ * Creates a task with the given name and type.
+ */
+ <S extends Task> void create(String name, Class<S> type);
+
+ /**
+ * Creates a task the given name and type, and initialize it with the given configAction.
+ */
+ <S extends Task> void create(String name, Class<S> type, Action<? super S> configAction);
+
+ /**
+ * Applies the given configAction to the task with given name.
+ */
+ void named(String name, Action<? super Task> configAction);
+
+ /**
+ * Returns the {@link Task} named name from the current set of defined tasks.
+ * @param name the name of the requested {@link Task}
+ * @return the {@link Task} instance or null if not found.
+ */
+ @Nullable
+ Task named(String name);
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java
new file mode 100644
index 0000000..6c29df1
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TaskManager.java
@@ -0,0 +1,3128 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import static com.android.build.OutputFile.DENSITY;
+import static com.android.builder.core.BuilderConstants.CONNECTED;
+import static com.android.builder.core.BuilderConstants.DEVICE;
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS;
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS;
+import static com.android.builder.core.BuilderConstants.FD_FLAVORS_ALL;
+import static com.android.builder.core.VariantType.ANDROID_TEST;
+import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Verify.verifyNotNull;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.coverage.JacocoPlugin;
+import com.android.build.gradle.internal.coverage.JacocoReportTask;
+import com.android.build.gradle.internal.dependency.LibraryDependencyImpl;
+import com.android.build.gradle.internal.dependency.ManifestDependencyImpl;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.dsl.AbiSplitOptions;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.incremental.BuildInfoLoaderTask;
+import com.android.build.gradle.internal.incremental.InstantRunAnchorTask;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunPatchingPolicy;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.build.gradle.internal.incremental.InstantRunWrapperTask;
+import com.android.build.gradle.internal.pipeline.ExtendedContentType;
+import com.android.build.gradle.internal.pipeline.OriginalStream;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.pipeline.TransformStream;
+import com.android.build.gradle.internal.pipeline.TransformTask;
+import com.android.build.gradle.internal.publishing.ApkPublishArtifact;
+import com.android.build.gradle.internal.publishing.MappingPublishArtifact;
+import com.android.build.gradle.internal.publishing.MetadataPublishArtifact;
+import com.android.build.gradle.internal.scope.AndroidTask;
+import com.android.build.gradle.internal.scope.AndroidTaskRegistry;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.AndroidReportTask;
+import com.android.build.gradle.internal.tasks.CheckManifest;
+import com.android.build.gradle.internal.tasks.DependencyReportTask;
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.internal.tasks.GenerateApkDataTask;
+import com.android.build.gradle.internal.tasks.InstallVariantTask;
+import com.android.build.gradle.internal.tasks.MockableAndroidJarTask;
+import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
+import com.android.build.gradle.internal.tasks.SigningReportTask;
+import com.android.build.gradle.internal.tasks.SourceSetsTask;
+import com.android.build.gradle.internal.tasks.TestServerTask;
+import com.android.build.gradle.internal.tasks.UninstallTask;
+import com.android.build.gradle.internal.tasks.databinding.DataBindingExportBuildInfoTask;
+import com.android.build.gradle.internal.tasks.databinding.DataBindingProcessLayoutsTask;
+import com.android.build.gradle.internal.tasks.multidex.CreateManifestKeepList;
+import com.android.build.gradle.internal.test.TestDataImpl;
+import com.android.build.gradle.internal.test.report.ReportType;
+import com.android.build.gradle.internal.transforms.DexTransform;
+import com.android.build.gradle.internal.transforms.ExtractJarsTransform;
+import com.android.build.gradle.internal.transforms.InstantRunBuildType;
+import com.android.build.gradle.internal.transforms.InstantRunDex;
+import com.android.build.gradle.internal.transforms.InstantRunSlicer;
+import com.android.build.gradle.internal.transforms.InstantRunTransform;
+import com.android.build.gradle.internal.transforms.InstantRunVerifierTransform;
+import com.android.build.gradle.internal.transforms.JacocoTransform;
+import com.android.build.gradle.internal.transforms.JarMergingTransform;
+import com.android.build.gradle.internal.transforms.NoChangesVerifierTransform;
+import com.android.build.gradle.internal.transforms.MergeJavaResourcesTransform;
+import com.android.build.gradle.internal.transforms.MultiDexTransform;
+import com.android.build.gradle.internal.transforms.NewShrinkerTransform;
+import com.android.build.gradle.internal.transforms.ProGuardTransform;
+import com.android.build.gradle.internal.transforms.ProguardConfigurable;
+import com.android.build.gradle.internal.transforms.ShrinkResourcesTransform;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+import com.android.build.gradle.internal.variant.ApplicationVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.internal.variant.LibraryVariantData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.AndroidJarTask;
+import com.android.build.gradle.tasks.ColdswapArtifactsKickerTask;
+import com.android.build.gradle.tasks.CompatibleScreensManifest;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.GenerateResValues;
+import com.android.build.gradle.tasks.GenerateSplitAbiRes;
+import com.android.build.gradle.tasks.JackTask;
+import com.android.build.gradle.tasks.JillTask;
+import com.android.build.gradle.tasks.Lint;
+import com.android.build.gradle.tasks.MergeManifests;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.MergeSourceSetFolders;
+import com.android.build.gradle.tasks.NdkCompile;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.build.gradle.tasks.PackageSplitAbi;
+import com.android.build.gradle.tasks.PackageSplitRes;
+import com.android.build.gradle.tasks.PrePackageApplication;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.ProcessManifest;
+import com.android.build.gradle.tasks.ProcessTestManifest;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.build.gradle.tasks.SplitZipAlign;
+import com.android.build.gradle.tasks.ZipAlign;
+import com.android.build.gradle.tasks.factory.JavaCompileConfigAction;
+import com.android.build.gradle.tasks.factory.ProcessJavaResConfigAction;
+import com.android.build.gradle.tasks.factory.UnitTestConfigAction;
+import com.android.build.gradle.tasks.fd.FastDeployRuntimeExtractorTask;
+import com.android.build.gradle.tasks.fd.GenerateInstantRunAppInfoTask;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.core.VariantType;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.internal.testing.SimpleTestCallable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.DataBindingOptions;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.sdk.SdkLoader;
+import com.android.builder.sdk.TargetInfo;
+import com.android.builder.testing.ConnectedDeviceProvider;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.builder.testing.api.TestServer;
+import com.android.manifmerger.ManifestMerger2;
+import com.android.sdklib.AndroidVersion;
+import com.android.utils.StringHelper;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.plugins.BasePlugin;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import groovy.lang.Closure;
+
+/**
+ * Manages tasks creation.
+ */
+public abstract class TaskManager {
+
+ public static final String FILE_JACOCO_AGENT = "jacocoagent.jar";
+
+ public static final String DEFAULT_PROGUARD_CONFIG_FILE = "proguard-android.txt";
+
+ public static final String DIR_BUNDLES = "bundles";
+
+ public static final String INSTALL_GROUP = "Install";
+
+ public static final String BUILD_GROUP = BasePlugin.BUILD_GROUP;
+
+ public static final String ANDROID_GROUP = "Android";
+
+ protected Project project;
+
+ protected AndroidBuilder androidBuilder;
+
+ protected DataBindingBuilder dataBindingBuilder;
+
+ private DependencyManager dependencyManager;
+
+ protected SdkHandler sdkHandler;
+
+ protected AndroidConfig extension;
+
+ protected ToolingModelBuilderRegistry toolingRegistry;
+
+ private final GlobalScope globalScope;
+
+ private final AndroidTaskRegistry androidTasks = new AndroidTaskRegistry();
+
+ private Logger logger;
+
+ protected boolean isComponentModelPlugin = false;
+
+ // Task names
+ // TODO: Convert to AndroidTask.
+ private static final String MAIN_PREBUILD = "preBuild";
+
+ private static final String UNINSTALL_ALL = "uninstallAll";
+
+ private static final String DEVICE_CHECK = "deviceCheck";
+
+ protected static final String CONNECTED_CHECK = "connectedCheck";
+
+ private static final String ASSEMBLE_ANDROID_TEST = "assembleAndroidTest";
+
+ private static final String SOURCE_SETS = "sourceSets";
+
+ private static final String LINT = "lint";
+
+ protected static final String LINT_COMPILE = "compileLint";
+
+ // Tasks
+ private Copy jacocoAgentTask;
+
+ public AndroidTask<MockableAndroidJarTask> createMockableJar;
+
+ public TaskManager(
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ this.project = project;
+ this.androidBuilder = androidBuilder;
+ this.dataBindingBuilder = dataBindingBuilder;
+ this.sdkHandler = sdkHandler;
+ this.extension = extension;
+ this.toolingRegistry = toolingRegistry;
+ this.dependencyManager = dependencyManager;
+ logger = Logging.getLogger(this.getClass());
+
+ globalScope = new GlobalScope(
+ project,
+ androidBuilder,
+ extension,
+ sdkHandler,
+ toolingRegistry);
+ }
+
+ private boolean isVerbose() {
+ return project.getLogger().isEnabled(LogLevel.INFO);
+ }
+
+ private boolean isDebugLog() {
+ return project.getLogger().isEnabled(LogLevel.DEBUG);
+ }
+
+ public DataBindingBuilder getDataBindingBuilder() {
+ return dataBindingBuilder;
+ }
+
+ /**
+ * Creates the tasks for a given BaseVariantData.
+ */
+ public abstract void createTasksForVariantData(
+ @NonNull TaskFactory tasks,
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData);
+
+ public GlobalScope getGlobalScope() {
+ return globalScope;
+ }
+
+ /**
+ * Returns a collection of buildables that creates native object.
+ *
+ * A buildable is considered to be any object that can be used as the argument to
+ * Task.dependsOn. This could be a Task or a BuildableModelElement (e.g. BinarySpec).
+ */
+ protected Collection<Object> getNdkBuildable(BaseVariantData variantData) {
+ return Collections.<Object>singleton(variantData.ndkCompileTask);
+ }
+
+ /**
+ * Override to configure NDK data in the scope.
+ */
+ public void configureScopeForNdk(@NonNull VariantScope scope) {
+ final BaseVariantData variantData = scope.getVariantData();
+ scope.setNdkSoFolder(Collections.singleton(new File(
+ scope.getGlobalScope().getIntermediatesDir(),
+ "ndk/" + variantData.getVariantConfiguration().getDirName() + "/lib")));
+ File objFolder = new File(scope.getGlobalScope().getIntermediatesDir(),
+ "ndk/" + variantData.getVariantConfiguration().getDirName() + "/obj");
+ scope.setNdkObjFolder(objFolder);
+ for (Abi abi : NdkHandler.getAbiList()) {
+ scope.addNdkDebuggableLibraryFolders(abi,
+ new File(objFolder, "local/" + abi.getName()));
+ }
+
+ }
+
+ protected AndroidConfig getExtension() {
+ return extension;
+ }
+
+ public void resolveDependencies(
+ @NonNull VariantDependencies variantDeps,
+ @Nullable VariantDependencies testedVariantDeps,
+ @Nullable String testedProjectPath) {
+ dependencyManager.resolveDependencies(variantDeps, testedVariantDeps, testedProjectPath);
+ }
+
+ /**
+ * Create tasks before the evaluation (on plugin apply). This is useful for tasks that
+ * could be referenced by custom build logic.
+ */
+ public void createTasksBeforeEvaluate(@NonNull TaskFactory tasks) {
+ tasks.create(UNINSTALL_ALL, new Action<Task>() {
+ @Override
+ public void execute(Task uninstallAllTask) {
+ uninstallAllTask.setDescription("Uninstall all applications.");
+ uninstallAllTask.setGroup(INSTALL_GROUP);
+ }
+ });
+
+ tasks.create(DEVICE_CHECK, new Action<Task>() {
+ @Override
+ public void execute(Task deviceCheckTask) {
+ deviceCheckTask.setDescription(
+ "Runs all device checks using Device Providers and Test Servers.");
+ deviceCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ }
+ });
+
+ tasks.create(CONNECTED_CHECK, new Action<Task>() {
+ @Override
+ public void execute(Task connectedCheckTask) {
+ connectedCheckTask.setDescription(
+ "Runs all device checks on currently connected devices.");
+ connectedCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ }
+ });
+
+ tasks.create(MAIN_PREBUILD);
+
+ tasks.create(SOURCE_SETS, SourceSetsTask.class, new Action<SourceSetsTask>() {
+ @Override
+ public void execute(SourceSetsTask sourceSetsTask) {
+ sourceSetsTask.setConfig(extension);
+ sourceSetsTask.setDescription(
+ "Prints out all the source sets defined in this project.");
+ sourceSetsTask.setGroup(ANDROID_GROUP);
+ }
+ });
+
+ tasks.create(ASSEMBLE_ANDROID_TEST, new Action<Task>() {
+ @Override
+ public void execute(Task assembleAndroidTestTask) {
+ assembleAndroidTestTask.setGroup(BasePlugin.BUILD_GROUP);
+ assembleAndroidTestTask.setDescription("Assembles all the Test applications.");
+ }
+ });
+
+ tasks.create(LINT, Lint.class, new Action<Lint>() {
+ @Override
+ public void execute(Lint lintTask) {
+ lintTask.setDescription("Runs lint on all variants.");
+ lintTask.setVariantName("");
+ lintTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ lintTask.setLintOptions(getExtension().getLintOptions());
+ lintTask.setSdkHome(sdkHandler.getSdkFolder());
+ lintTask.setToolingRegistry(toolingRegistry);
+ lintTask.setAndroidBuilder(androidBuilder);
+ }
+ });
+ tasks.named(JavaBasePlugin.CHECK_TASK_NAME, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(LINT);
+ }
+ });
+ createLintCompileTask(tasks);
+ }
+
+ public void createMockableJarTask(TaskFactory tasks) {
+ createMockableJar = androidTasks.create(tasks, new MockableAndroidJarTask.ConfigAction(globalScope));
+ }
+
+ protected void createDependencyStreams(@NonNull final VariantScope variantScope) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = variantScope.getVariantData();
+ final GradleVariantConfiguration config = variantData.getVariantConfiguration();
+
+ TransformManager transformManager = variantScope.getTransformManager();
+
+ // content that can be found in a jar:
+ Set<ContentType> fullJar = ImmutableSet.<ContentType>of(
+ DefaultContentType.CLASSES, DefaultContentType.RESOURCES, ExtendedContentType.NATIVE_LIBS);
+
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(fullJar)
+ .addScope(Scope.PROJECT_LOCAL_DEPS)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return config.getLocalPackagedJars();
+ }
+ })
+ .build());
+
+ IncrementalMode incrementalMode = getIncrementalMode(config);
+ boolean skipDependency = incrementalMode == IncrementalMode.LOCAL_JAVA_ONLY ||
+ incrementalMode == IncrementalMode.LOCAL_RES_ONLY;
+ ImmutableList<Object> dependencies = skipDependency ?
+ ImmutableList.of() :
+ ImmutableList.of(variantData.prepareDependenciesTask,
+ variantData.getVariantDependency().getPackageConfiguration()
+ .getBuildDependencies());
+
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_JARS)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ Set<File> files = config.getExternalPackagedJars();
+ Set<File> additions = variantScope.getGlobalScope().getAndroidBuilder()
+ .getAdditionalPackagedJars(config);
+
+ if (!additions.isEmpty()) {
+ ImmutableSet.Builder<File> builder = ImmutableSet.builder();
+ return builder.addAll(files).addAll(additions).build();
+ }
+
+ return files;
+ }
+ })
+ .setDependencies(dependencies)
+ .build());
+
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_NATIVE_LIBS)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ Set<File> files = config.getExternalPackagedJniJars();
+ Set<File> additions = variantScope.getGlobalScope().getAndroidBuilder()
+ .getAdditionalPackagedJars(config);
+
+ if (!additions.isEmpty()) {
+ ImmutableSet.Builder<File> builder = ImmutableSet.builder();
+ return builder.addAll(files).addAll(additions).build();
+ }
+
+ return files;
+ }
+ })
+ .setFolders(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return config.getExternalAarJniLibFolders();
+ }
+ })
+ .setDependencies(dependencies)
+ .build());
+
+ // for the sub modules, only the main jar has resources.
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_JARS)
+ .addScope(Scope.SUB_PROJECTS)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return config.getSubProjectPackagedJars();
+ }
+
+ })
+ .setDependencies(dependencies)
+ .build());
+
+ // the local deps don't have resources (been merged into the main jar)
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_CLASS)
+ .addScope(Scope.SUB_PROJECTS_LOCAL_DEPS)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return config.getSubProjectLocalPackagedJars();
+ }
+
+ })
+ .setDependencies(dependencies)
+ .build());
+
+ // and the native libs of the libraries are in a separate folder.
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_NATIVE_LIBS)
+ .addScope(Scope.SUB_PROJECTS)
+ .setFolders(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return config.getSubProjectJniLibFolders();
+ }
+ })
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return config.getSubProjectPackagedJniJars();
+ }
+ })
+ .setDependencies(dependencies)
+ .build());
+
+ // provided only scopes.
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(fullJar)
+ .addScope(Scope.PROVIDED_ONLY)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return config.getProvidedOnlyJars();
+ }
+
+ })
+ .build());
+
+ if (variantScope.getTestedVariantData() != null) {
+ final BaseVariantData testedVariantData = variantScope.getTestedVariantData();
+
+ VariantScope testedVariantScope = testedVariantData.getScope();
+
+ // create two streams of different types.
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScope(Scope.TESTED_CODE)
+ .setFolders(
+ Suppliers.ofInstance(
+ (Collection<File>) ImmutableList.of(
+ testedVariantScope.getJavaOutputDir())))
+ .setDependency(testedVariantScope.getJavacTask().getName())
+ .build());
+
+ transformManager.addStream(OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScope(Scope.TESTED_CODE)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return variantScope.getGlobalScope().getAndroidBuilder()
+ .getAllPackagedJars(
+ testedVariantData.getVariantConfiguration());
+ }
+ })
+ .setDependency(ImmutableList.of(
+ testedVariantData.prepareDependenciesTask,
+ testedVariantData.getVariantDependency().getPackageConfiguration()
+ .getBuildDependencies()))
+ .build());
+ }
+
+ handleJacocoDependencies(variantScope);
+ }
+
+ public void createMergeAppManifestsTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope variantScope) {
+
+ ApplicationVariantData appVariantData =
+ (ApplicationVariantData) variantScope.getVariantData();
+ Set<String> screenSizes = appVariantData.getCompatibleScreens();
+
+ // loop on all outputs. The only difference will be the name of the task, and location
+ // of the generated manifest
+ for (final BaseVariantOutputData vod : appVariantData.getOutputs()) {
+ VariantOutputScope scope = vod.getScope();
+
+ AndroidTask<CompatibleScreensManifest> csmTask = null;
+ if (vod.getMainOutputFile().getFilter(DENSITY) != null) {
+ csmTask = androidTasks.create(tasks,
+ new CompatibleScreensManifest.ConfigAction(scope, screenSizes));
+ scope.setCompatibleScreensManifestTask(csmTask);
+ }
+
+ List<ManifestMerger2.Invoker.Feature> optionalFeatures = getIncrementalMode(
+ variantScope.getVariantConfiguration()) != IncrementalMode.NONE
+ ? ImmutableList.of(ManifestMerger2.Invoker.Feature.INSTANT_RUN_REPLACEMENT)
+ : ImmutableList.<ManifestMerger2.Invoker.Feature>of();
+
+ scope.setManifestProcessorTask(androidTasks.create(tasks,
+ new MergeManifests.ConfigAction(scope, optionalFeatures)));
+
+ if (csmTask != null) {
+ scope.getManifestProcessorTask().dependsOn(tasks, csmTask);
+ }
+ }
+ }
+
+ public void createMergeLibManifestsTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+
+ AndroidTask<ProcessManifest> processManifest = androidTasks.create(tasks,
+ new ProcessManifest.ConfigAction(scope));
+
+ processManifest.dependsOn(tasks, scope.getVariantData().prepareDependenciesTask);
+
+ BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
+ variantOutputData.getScope().setManifestProcessorTask(processManifest);
+ }
+
+ protected void createProcessTestManifestTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+
+ AndroidTask<ProcessTestManifest> processTestManifestTask = androidTasks.create(tasks,
+ new ProcessTestManifest.ConfigAction(scope));
+
+ processTestManifestTask.dependsOn(tasks, scope.getVariantData().prepareDependenciesTask);
+
+ BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
+ variantOutputData.getScope().setManifestProcessorTask(processTestManifestTask);
+ }
+
+ public void createRenderscriptTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+ scope.setRenderscriptCompileTask(
+ androidTasks.create(tasks, new RenderscriptCompile.ConfigAction(scope)));
+
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ GradleVariantConfiguration config = variantData.getVariantConfiguration();
+
+ if (config.getRenderscriptSupportModeEnabled() && config.getRenderscriptTarget() >= 21) {
+ androidBuilder.getErrorReporter().handleSyncError(
+ "",
+ SyncIssue.TYPE_GENERIC,
+ "Renderscript support mode is not currently supported with renderscript target 21+");
+ }
+
+ // get single output for now.
+ BaseVariantOutputData variantOutputData = variantData.getOutputs().get(0);
+
+ scope.getRenderscriptCompileTask().dependsOn(tasks, variantData.prepareDependenciesTask);
+ if (config.getType().isForTesting()) {
+ scope.getRenderscriptCompileTask().dependsOn(tasks,
+ variantOutputData.getScope().getManifestProcessorTask());
+ } else {
+ scope.getRenderscriptCompileTask().dependsOn(tasks, scope.getCheckManifestTask());
+ }
+
+ scope.getResourceGenTask().dependsOn(tasks, scope.getRenderscriptCompileTask());
+ // only put this dependency if rs will generate Java code
+ if (!config.getRenderscriptNdkModeEnabled()) {
+ scope.getSourceGenTask().dependsOn(tasks, scope.getRenderscriptCompileTask());
+ }
+
+ }
+
+ public AndroidTask<MergeResources> createMergeResourcesTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+ return basicCreateMergeResourcesTask(
+ tasks,
+ scope,
+ "merge",
+ null /*outputLocation*/,
+ true /*includeDependencies*/,
+ true /*process9patch*/);
+ }
+
+ public AndroidTask<MergeResources> basicCreateMergeResourcesTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope,
+ @NonNull String taskNamePrefix,
+ @Nullable File outputLocation,
+ final boolean includeDependencies,
+ final boolean process9Patch) {
+ AndroidTask<MergeResources> mergeResourcesTask = androidTasks.create(tasks,
+ new MergeResources.ConfigAction(
+ scope,
+ taskNamePrefix,
+ outputLocation,
+ includeDependencies,
+ process9Patch));
+
+ if (getIncrementalMode(scope.getVariantConfiguration()) != IncrementalMode.LOCAL_RES_ONLY) {
+ mergeResourcesTask.dependsOn(tasks,
+ scope.getVariantData().prepareDependenciesTask,
+ scope.getResourceGenTask());
+ }
+ scope.setMergeResourcesTask(mergeResourcesTask);
+ scope.setResourceOutputDir(
+ Objects.firstNonNull(outputLocation, scope.getDefaultMergeResourcesOutputDir()));
+ scope.setMergeResourceOutputDir(outputLocation);
+ return scope.getMergeResourcesTask();
+ }
+
+ public void createMergeAssetsTask(TaskFactory tasks, VariantScope scope) {
+ AndroidTask<MergeSourceSetFolders> mergeAssetsTask = androidTasks.create(tasks,
+ new MergeSourceSetFolders.MergeAssetConfigAction(scope));
+ mergeAssetsTask.dependsOn(tasks,
+ scope.getVariantData().prepareDependenciesTask,
+ scope.getAssetGenTask());
+ scope.setMergeAssetsTask(mergeAssetsTask);
+ }
+
+ public void createMergeJniLibFoldersTasks(
+ @NonNull TaskFactory tasks,
+ @NonNull final VariantScope variantScope) {
+ // merge the source folders together using the proper priority.
+ AndroidTask<MergeSourceSetFolders> mergeJniLibFoldersTask = androidTasks.create(tasks,
+ new MergeSourceSetFolders.MergeJniLibFoldersConfigAction(variantScope));
+ mergeJniLibFoldersTask.dependsOn(tasks,
+ variantScope.getVariantData().prepareDependenciesTask,
+ variantScope.getAssetGenTask());
+ variantScope.setMergeJniLibFoldersTask(mergeJniLibFoldersTask);
+
+ // create the stream generated from this task
+ variantScope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentType(ExtendedContentType.NATIVE_LIBS)
+ .addScope(Scope.PROJECT)
+ .setFolder(variantScope.getMergeNativeLibsOutputDir())
+ .setDependency(mergeJniLibFoldersTask.getName())
+ .build());
+
+
+ // create a stream that contains the content of the local NDK build
+ variantScope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentType(ExtendedContentType.NATIVE_LIBS)
+ .addScope(Scope.PROJECT)
+ .setFolders(Suppliers.ofInstance(variantScope.getNdkSoFolder()))
+ .setDependency(getNdkBuildable(variantScope.getVariantData()))
+ .build());
+
+ // create a stream containing the content of the renderscript compilation output
+ // if support mode is enabled.
+ if (variantScope.getVariantConfiguration().getRenderscriptSupportModeEnabled()) {
+ variantScope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentType(ExtendedContentType.NATIVE_LIBS)
+ .addScope(Scope.PROJECT)
+ .setFolders(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ ImmutableList.Builder<File> builder = ImmutableList.builder();
+
+ if (variantScope.getRenderscriptLibOutputDir().isDirectory()) {
+ builder.add(variantScope.getRenderscriptLibOutputDir());
+ }
+
+ File rsLibs = variantScope.getGlobalScope().getAndroidBuilder()
+ .getSupportNativeLibFolder();
+ if (rsLibs != null && rsLibs.isDirectory()) {
+ builder.add(rsLibs);
+ }
+
+ return builder.build();
+ }
+ })
+ .setDependency(variantScope.getRenderscriptCompileTask().getName())
+ .build());
+ }
+
+ // compute the scopes that need to be merged.
+ Set<Scope> mergeScopes = getResMergingScopes(variantScope);
+ // Create the merge transform
+ MergeJavaResourcesTransform mergeTransform = new MergeJavaResourcesTransform(
+ variantScope.getGlobalScope().getExtension().getPackagingOptions(),
+ mergeScopes, ExtendedContentType.NATIVE_LIBS, "mergeJniLibs");
+ variantScope.getTransformManager().addTransform(tasks, variantScope, mergeTransform);
+ }
+
+ public void createBuildConfigTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
+ AndroidTask<GenerateBuildConfig> generateBuildConfigTask =
+ androidTasks.create(tasks, new GenerateBuildConfig.ConfigAction(scope));
+ scope.setGenerateBuildConfigTask(generateBuildConfigTask);
+ scope.getSourceGenTask().dependsOn(tasks, generateBuildConfigTask.getName());
+ if (scope.getVariantConfiguration().getType().isForTesting()) {
+ // in case of a test project, the manifest is generated so we need to depend
+ // on its creation.
+
+ // For test apps there should be a single output, so we get it.
+ BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
+
+ generateBuildConfigTask.dependsOn(
+ tasks, variantOutputData.getScope().getManifestProcessorTask());
+ } else {
+ generateBuildConfigTask.dependsOn(tasks, scope.getCheckManifestTask());
+ }
+ }
+
+ public void createGenerateResValuesTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+ AndroidTask<GenerateResValues> generateResValuesTask = androidTasks.create(
+ tasks, new GenerateResValues.ConfigAction(scope));
+ scope.getResourceGenTask().dependsOn(tasks, generateResValuesTask);
+ }
+
+ public void createApkProcessResTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+ createProcessResTask(
+ tasks,
+ scope,
+ new File(globalScope.getIntermediatesDir(),
+ "symbols/" + scope.getVariantData().getVariantConfiguration().getDirName()),
+ true);
+ }
+
+ public void createProcessResTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope,
+ @Nullable File symbolLocation,
+ boolean generateResourcePackage) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+
+ variantData.calculateFilters(scope.getGlobalScope().getExtension().getSplits());
+
+ // loop on all outputs. The only difference will be the name of the task, and location
+ // of the generated data.
+ for (BaseVariantOutputData vod : variantData.getOutputs()) {
+ final VariantOutputScope variantOutputScope = vod.getScope();
+
+ variantOutputScope.setProcessResourcesTask(androidTasks.create(tasks,
+ new ProcessAndroidResources.ConfigAction(variantOutputScope, symbolLocation,
+ generateResourcePackage)));
+
+ // always depend on merge res,
+ variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
+ scope.getMergeResourcesTask());
+
+ if (getIncrementalMode(scope.getVariantConfiguration()) != IncrementalMode.LOCAL_RES_ONLY) {
+ variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
+ variantOutputScope.getManifestProcessorTask(),
+ scope.getMergeAssetsTask());
+ }
+
+ if (vod.getMainOutputFile().getFilter(DENSITY) == null) {
+ scope.setGenerateRClassTask(variantOutputScope.getProcessResourcesTask());
+ scope.getSourceGenTask().optionalDependsOn(
+ tasks,
+ variantOutputScope.getProcessResourcesTask());
+ }
+
+ }
+ }
+
+ /**
+ * Creates the split resources packages task if necessary. AAPT will produce split packages for
+ * all --split provided parameters. These split packages should be signed and moved unchanged to
+ * the APK build output directory.
+ */
+ @NonNull
+ public AndroidTask<PackageSplitRes> createSplitResourcesTasks(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+
+ checkState(variantData.getSplitHandlingPolicy().equals(
+ BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY),
+ "Can only create split resources tasks for pure splits.");
+
+ List<? extends BaseVariantOutputData> outputs = variantData.getOutputs();
+ final BaseVariantOutputData variantOutputData = outputs.get(0);
+ if (outputs.size() != 1) {
+ throw new RuntimeException(
+ "In release 21 and later, there can be only one main APK, " +
+ "found " + outputs.size());
+ }
+
+ VariantOutputScope variantOutputScope = variantOutputData.getScope();
+ AndroidTask<PackageSplitRes> packageSplitRes =
+ androidTasks.create(tasks, new PackageSplitRes.ConfigAction(scope));
+ packageSplitRes.dependsOn(tasks,
+ variantOutputScope.getProcessResourcesTask().getName());
+ return packageSplitRes;
+ }
+
+ @Nullable
+ public AndroidTask<PackageSplitAbi> createSplitAbiTasks(
+ @NonNull TaskFactory tasks,
+ @NonNull final VariantScope scope) {
+ ApplicationVariantData variantData = (ApplicationVariantData) scope.getVariantData();
+
+ checkState(variantData.getSplitHandlingPolicy().equals(
+ BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY),
+ "split ABI tasks are only compatible with pure splits.");
+
+ Set<String> filters = AbiSplitOptions.getAbiFilters(
+ getExtension().getSplits().getAbiFilters());
+ if (filters.isEmpty()) {
+ return null;
+ }
+
+ List<ApkVariantOutputData> outputs = variantData.getOutputs();
+ if (outputs.size() != 1) {
+ throw new RuntimeException(
+ "In release 21 and later, there can be only one main APK, " +
+ "found " + outputs.size());
+ }
+
+ BaseVariantOutputData variantOutputData = outputs.get(0);
+
+ // first create the split APK resources.
+ AndroidTask<GenerateSplitAbiRes> generateSplitAbiRes =
+ androidTasks.create(tasks, new GenerateSplitAbiRes.ConfigAction(scope));
+ generateSplitAbiRes.dependsOn(tasks,
+ variantOutputData.getScope().getProcessResourcesTask().getName());
+
+ // then package those resources with the appropriate JNI libraries.
+ AndroidTask<PackageSplitAbi> packageSplitAbiTask =
+ androidTasks.create(tasks, new PackageSplitAbi.ConfigAction(scope));
+
+ packageSplitAbiTask.dependsOn(tasks, generateSplitAbiRes);
+ packageSplitAbiTask.dependsOn(tasks, scope.getNdkBuildable());
+
+ // set up dependency on the jni merger.
+ for (TransformStream stream : scope.getTransformManager().getStreams(
+ PackageApplication.sNativeLibsFilter)) {
+ packageSplitAbiTask.dependsOn(tasks, stream.getDependencies());
+ }
+ return packageSplitAbiTask;
+ }
+
+ public void createSplitTasks(@NonNull TaskFactory tasks, @NonNull final VariantScope scope) {
+ AndroidTask<PackageSplitRes> packageSplitResourcesTask =
+ createSplitResourcesTasks(tasks, scope);
+ final AndroidTask<PackageSplitAbi> packageSplitAbiTask = createSplitAbiTasks(tasks, scope);
+
+ AndroidTask<SplitZipAlign> zipAlign =
+ androidTasks.create(tasks, new SplitZipAlign.ConfigAction(scope));
+ //noinspection VariableNotUsedInsideIf - only need to check if task exist.
+ if (packageSplitAbiTask != null) {
+ zipAlign.configure(tasks, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ ((SplitZipAlign)task).getAbiInputFiles().addAll(
+ scope.getPackageSplitAbiOutputFiles());
+ }
+ });
+ }
+ zipAlign.dependsOn(tasks, packageSplitResourcesTask);
+ zipAlign.optionalDependsOn(tasks, packageSplitAbiTask);
+
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ VariantOutputScope outputScope = variantData.getOutputs().get(0).getScope();
+ outputScope.setSplitZipAlignTask(zipAlign);
+ }
+
+ /**
+ * Returns the scopes for which the java resources should be merged.
+ * @param variantScope the scope of the variant being processed.
+ * @return the list of scopes for which to merge the java resources.
+ */
+ @NonNull
+ protected abstract Set<Scope> getResMergingScopes(@NonNull VariantScope variantScope);
+
+ /**
+ * Creates the java resources processing tasks.
+ *
+ * The java processing will happen in two steps:
+ * <ul>
+ * <li>{@link Sync} task configured with {@link ProcessJavaResConfigAction} will sync all source
+ * folders into a single folder identified by
+ * {@link VariantScope#getSourceFoldersJavaResDestinationDir()}</li>
+ * <li>{@link MergeJavaResourcesTransform} will take the output of this merge plus the
+ * dependencies and will create a single merge with the {@link PackagingOptions} settings
+ * applied.</li>
+ * </ul>
+ *
+ * @param tasks tasks factory to create tasks.
+ * @param variantScope the variant scope we are operating under.
+ */
+ public void createProcessJavaResTasks(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope variantScope) {
+ TransformManager transformManager = variantScope.getTransformManager();
+
+ // now copy the source folders java resources into the temporary location, mainly to
+ // maintain the PluginDsl COPY semantics.
+ AndroidTask<Sync> processJavaResourcesTask =
+ androidTasks.create(tasks, new ProcessJavaResConfigAction(variantScope));
+ variantScope.setProcessJavaResourcesTask(processJavaResourcesTask);
+
+ // create the stream generated from this task
+ variantScope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentType(DefaultContentType.RESOURCES)
+ .addScope(Scope.PROJECT)
+ .setFolder(variantScope.getSourceFoldersJavaResDestinationDir())
+ .setDependency(processJavaResourcesTask.getName())
+ .build());
+
+ // compute the scopes that need to be merged.
+ Set<Scope> mergeScopes = getResMergingScopes(variantScope);
+
+ // Create the merge transform
+ MergeJavaResourcesTransform mergeTransform = new MergeJavaResourcesTransform(
+ variantScope.getGlobalScope().getExtension().getPackagingOptions(),
+ mergeScopes, DefaultContentType.RESOURCES, "mergeJavaRes");
+
+ variantScope.setMergeJavaResourcesTask(
+ transformManager.addTransform(tasks, variantScope, mergeTransform));
+ }
+
+ public void createAidlTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
+ scope.setAidlCompileTask(androidTasks.create(tasks, new AidlCompile.ConfigAction(scope)));
+ scope.getSourceGenTask().dependsOn(tasks, scope.getAidlCompileTask());
+ scope.getAidlCompileTask().dependsOn(tasks, scope.getVariantData().prepareDependenciesTask);
+ }
+
+ /**
+ * Creates the task for creating *.class files using javac. These tasks are created regardless
+ * of whether Jack is used or not, but assemble will not depend on them if it is. They are
+ * always used when running unit tests.
+ */
+ public AndroidTask<? extends JavaCompile> createJavacTask(
+ @NonNull final TaskFactory tasks,
+ @NonNull final VariantScope scope) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ final AndroidTask<? extends JavaCompile> javacTask = androidTasks.create(tasks,
+ new JavaCompileConfigAction(scope));
+ scope.setJavacTask(javacTask);
+
+ setupCompileTaskDependencies(tasks, scope, variantData, javacTask);
+
+ // create the output stream from this task
+ scope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(scope.getJavaOutputDir())
+ .setDependency(javacTask.getName()).build());
+
+ // Create jar task for uses by external modules.
+ if (variantData.getVariantDependency().getClassesConfiguration() != null) {
+ tasks.create(scope.getTaskName("package", "JarArtifact"), Jar.class, new Action<Jar>() {
+ @Override
+ public void execute(Jar jar) {
+ variantData.classesJarTask = jar;
+ jar.dependsOn(javacTask.getName());
+
+ // add the class files (whether they are instrumented or not.
+ jar.from(scope.getJavaOutputDir());
+
+ jar.setDestinationDir(new File(
+ scope.getGlobalScope().getIntermediatesDir(),
+ "classes-jar/" +
+ variantData.getVariantConfiguration().getDirName()));
+ jar.setArchiveName("classes.jar");
+ }
+ });
+ }
+
+ return javacTask;
+ }
+
+ private void setupCompileTaskDependencies(@NonNull TaskFactory tasks,
+ @NonNull VariantScope scope,
+ BaseVariantData<? extends BaseVariantOutputData> variantData,
+ AndroidTask<?> compileTask) {
+ IncrementalMode incrementalMode = getIncrementalMode(scope.getVariantConfiguration());
+
+ if (incrementalMode == IncrementalMode.LOCAL_RES_ONLY) {
+ // in this case only depend on the R class. We want to ignore the other
+ // source generating classes like RS, aidl, etc...
+ compileTask.optionalDependsOn(tasks, scope.getGenerateRClassTask());
+ } else if (incrementalMode != IncrementalMode.LOCAL_JAVA_ONLY) {
+ compileTask.optionalDependsOn(tasks, scope.getSourceGenTask());
+ compileTask.dependsOn(tasks, scope.getVariantData().prepareDependenciesTask);
+ // TODO - dependency information for the compile classpath is being lost.
+ // Add a temporary approximation
+ compileTask.dependsOn(tasks,
+ scope.getVariantData().getVariantDependency().getCompileConfiguration()
+ .getBuildDependencies());
+ }
+
+ if (variantData.getType().isForTesting()) {
+ BaseVariantData testedVariantData =
+ (BaseVariantData) ((TestVariantData) variantData).getTestedVariantData();
+ final JavaCompile testedJavacTask = testedVariantData.javacTask;
+ compileTask.dependsOn(tasks,
+ testedJavacTask != null ? testedJavacTask :
+ testedVariantData.getScope().getJavacTask());
+ }
+ }
+
+ /**
+ * Makes the given task the one used by top-level "compile" task.
+ */
+ public static void setJavaCompilerTask(
+ @NonNull AndroidTask<? extends AbstractCompile> javaCompilerTask,
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+ scope.getCompileTask().dependsOn(tasks, javaCompilerTask);
+ scope.setJavaCompilerTask(javaCompilerTask);
+
+ // TODO: Get rid of it once we stop keeping tasks in variant data.
+ //noinspection VariableNotUsedInsideIf
+ if (scope.getVariantData().javacTask != null) {
+ // This is not the experimental plugin, let's update variant data, so Variants API
+ // keeps working.
+ scope.getVariantData().javaCompilerTask = (AbstractCompile) tasks.named(javaCompilerTask.getName());
+ }
+ }
+
+ public void createGenerateMicroApkDataTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope,
+ @NonNull Configuration config) {
+ AndroidTask<GenerateApkDataTask> generateMicroApkTask = androidTasks.create(tasks,
+ new GenerateApkDataTask.ConfigAction(scope, config));
+ generateMicroApkTask.dependsOn(tasks, config);
+
+ // the merge res task will need to run after this one.
+ scope.getResourceGenTask().dependsOn(tasks, generateMicroApkTask);
+ }
+
+ public void createNdkTasks(@NonNull VariantScope scope) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ NdkCompile ndkCompile = project.getTasks().create(
+ scope.getTaskName("compile", "Ndk"),
+ NdkCompile.class);
+
+ ndkCompile.dependsOn(variantData.preBuildTask);
+
+ ndkCompile.setAndroidBuilder(androidBuilder);
+ ndkCompile.setVariantName(variantData.getName());
+ ndkCompile.setNdkDirectory(sdkHandler.getNdkFolder());
+ ndkCompile.setIsForTesting(variantData.getType().isForTesting());
+ variantData.ndkCompileTask = ndkCompile;
+ variantData.compileTask.dependsOn(variantData.ndkCompileTask);
+
+ final GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
+
+ if (Boolean.TRUE.equals(variantConfig.getMergedFlavor().getRenderscriptNdkModeEnabled())) {
+ ndkCompile.setNdkRenderScriptMode(true);
+ ndkCompile.dependsOn(variantData.renderscriptCompileTask);
+ } else {
+ ndkCompile.setNdkRenderScriptMode(false);
+ }
+
+ ConventionMappingHelper.map(ndkCompile, "sourceFolders", new Callable<List<File>>() {
+ @Override
+ public List<File> call() {
+ List<File> sourceList = variantConfig.getJniSourceList();
+ if (Boolean.TRUE.equals(
+ variantConfig.getMergedFlavor().getRenderscriptNdkModeEnabled())) {
+ sourceList.add(variantData.renderscriptCompileTask.getSourceOutputDir());
+ }
+
+ return sourceList;
+ }
+ });
+
+ ndkCompile.setGeneratedMakefile(new File(scope.getGlobalScope().getIntermediatesDir(),
+ "ndk/" + variantData.getVariantConfiguration().getDirName() + "/Android.mk"));
+
+ ConventionMappingHelper.map(ndkCompile, "ndkConfig", new Callable<CoreNdkOptions>() {
+ @Override
+ public CoreNdkOptions call() {
+ return variantConfig.getNdkConfig();
+ }
+ });
+
+ ConventionMappingHelper.map(ndkCompile, "debuggable", new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return variantConfig.getBuildType().isJniDebuggable();
+ }
+ });
+
+ ndkCompile.setObjFolder(new File(scope.getGlobalScope().getIntermediatesDir(),
+ "ndk/" + variantData.getVariantConfiguration().getDirName() + "/obj"));
+
+ Collection<File> ndkSoFolder = scope.getNdkSoFolder();
+ if (ndkSoFolder != null && !ndkSoFolder.isEmpty()) {
+ ndkCompile.setSoFolder(ndkSoFolder.iterator().next());
+ }
+ }
+
+ /**
+ * Creates the tasks to build unit tests.
+ */
+ public void createUnitTestVariantTasks(
+ @NonNull TaskFactory tasks,
+ @NonNull TestVariantData variantData) {
+ VariantScope variantScope = variantData.getScope();
+ BaseVariantData testedVariantData = variantScope.getTestedVariantData();
+ checkState(testedVariantData != null);
+
+ createPreBuildTasks(tasks, variantScope);
+
+ // Create all current streams (dependencies mostly at this point)
+ createDependencyStreams(variantScope);
+
+ createProcessJavaResTasks(tasks, variantScope);
+ createCompileAnchorTask(tasks, variantScope);
+
+ // :app:compileDebugUnitTestSources should be enough for running tests from AS, so add an
+ // explicit dependency on resource copying tasks.
+ variantScope.getCompileTask().dependsOn(
+ tasks,
+ variantScope.getProcessJavaResourcesTask(),
+ testedVariantData.getScope().getProcessJavaResourcesTask());
+
+ AndroidTask<? extends JavaCompile> javacTask = createJavacTask(tasks, variantScope);
+ setJavaCompilerTask(javacTask, tasks, variantScope);
+ createRunUnitTestTask(tasks, variantScope);
+
+ variantData.assembleVariantTask.dependsOn(createMockableJar.getName());
+ // This hides the assemble unit test task from the task list.
+ variantData.assembleVariantTask.setGroup(null);
+ }
+
+ /**
+ * Creates the tasks to build android tests.
+ */
+ public void createAndroidTestVariantTasks(@NonNull TaskFactory tasks,
+ @NonNull TestVariantData variantData) {
+ VariantScope variantScope = variantData.getScope();
+
+ // get single output for now (though this may always be the case for tests).
+ final BaseVariantOutputData variantOutputData = variantData.getOutputs().get(0);
+
+ final BaseVariantData<BaseVariantOutputData> baseTestedVariantData =
+ (BaseVariantData<BaseVariantOutputData>) variantData.getTestedVariantData();
+ final BaseVariantOutputData testedVariantOutputData =
+ baseTestedVariantData.getOutputs().get(0);
+
+ createAnchorTasks(tasks, variantScope);
+
+ // Create all current streams (dependencies mostly at this point)
+ createDependencyStreams(variantScope);
+
+ // Add a task to process the manifest
+ createProcessTestManifestTask(tasks, variantScope);
+
+ // Add a task to create the res values
+ createGenerateResValuesTask(tasks, variantScope);
+
+ // Add a task to compile renderscript files.
+ createRenderscriptTask(tasks, variantScope);
+
+ // Add a task to merge the resource folders
+ createMergeResourcesTask(tasks, variantScope);
+
+ // Add a task to merge the assets folders
+ createMergeAssetsTask(tasks, variantScope);
+
+ if (variantData.getTestedVariantData().getVariantConfiguration().getType().equals(
+ VariantType.LIBRARY)) {
+ // in this case the tested library must be fully built before test can be built!
+ if (testedVariantOutputData.assembleTask != null) {
+ variantOutputData.getScope().getManifestProcessorTask().dependsOn(
+ tasks, testedVariantOutputData.assembleTask);
+ variantScope.getMergeResourcesTask().dependsOn(
+ tasks, testedVariantOutputData.assembleTask);
+ }
+ }
+
+ // Add a task to create the BuildConfig class
+ createBuildConfigTask(tasks, variantScope);
+
+ // Add a task to generate resource source files
+ createApkProcessResTask(tasks, variantScope);
+
+ // process java resources
+ createProcessJavaResTasks(tasks, variantScope);
+
+ createAidlTask(tasks, variantScope);
+
+ // Add NDK tasks
+ if (!isComponentModelPlugin) {
+ createNdkTasks(variantScope);
+ }
+ variantScope.setNdkBuildable(getNdkBuildable(variantData));
+
+ // add tasks to merge jni libs.
+ createMergeJniLibFoldersTasks(tasks, variantScope);
+
+ // Add a task to compile the test application
+ if (variantData.getVariantConfiguration().getUseJack()) {
+ createJackTask(tasks, variantScope);
+ } else {
+ AndroidTask<? extends JavaCompile> javacTask = createJavacTask(tasks, variantScope);
+ setJavaCompilerTask(javacTask, tasks, variantScope);
+ createPostCompilationTasks(tasks, variantScope);
+ }
+
+ // Add data binding tasks if enabled
+ if (extension.getDataBinding().isEnabled()) {
+ createDataBindingTasks(tasks, variantScope);
+ }
+
+ createPackagingTask(tasks, variantScope, false /*publishApk*/,
+ null /* buildInfoGeneratorTask */);
+
+ tasks.named(ASSEMBLE_ANDROID_TEST, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(variantOutputData.assembleTask);
+ }
+ });
+
+ createConnectedTestForVariant(tasks, variantScope);
+ }
+
+
+ protected enum IncrementalMode {
+ NONE, FULL, LOCAL_JAVA_ONLY, LOCAL_RES_ONLY;
+ }
+
+ /**
+ * Returns the incremental mode for this variant.
+ * @param config the variant's configuration
+ * @return the {@link IncrementalMode} for this variant.
+ */
+ protected IncrementalMode getIncrementalMode(@NonNull GradleVariantConfiguration config) {
+ if (config.isInstantRunSupported()
+ && targetDeviceSupportsInstantRun(config, project)
+ && globalScope.isActive(OptionalCompilationStep.INSTANT_DEV)) {
+ if (isComponentModelPlugin) {
+ return IncrementalMode.FULL;
+ }
+
+ // while both LOCAL_RES and LOCAL_JAVA could be active, LOCAL_RES is higher priority.
+ if (globalScope.isActive(OptionalCompilationStep.LOCAL_RES_ONLY)) {
+ return IncrementalMode.LOCAL_RES_ONLY;
+ }
+
+ if (globalScope.isActive(OptionalCompilationStep.LOCAL_JAVA_ONLY)) {
+ return IncrementalMode.LOCAL_JAVA_ONLY;
+ }
+
+ return IncrementalMode.FULL;
+ }
+
+ return IncrementalMode.NONE;
+ }
+
+ private static boolean targetDeviceSupportsInstantRun(
+ @NonNull GradleVariantConfiguration config,
+ @NonNull Project project) {
+ if (config.isLegacyMultiDexMode()) {
+ // We don't support legacy multi-dex on Dalvik.
+ return AndroidGradleOptions.getTargetApiLevel(project)
+ .compareTo(AndroidVersion.ART_RUNTIME) >= 0;
+ }
+
+ return true;
+ }
+
+ // TODO - should compile src/lint/java from src/lint/java and jar it into build/lint/lint.jar
+ private void createLintCompileTask(TaskFactory tasks) {
+
+ // TODO: move doFirst into dedicated task class.
+ tasks.create(LINT_COMPILE, Task.class,
+ new Action<Task>() {
+ @Override
+ public void execute(Task lintCompile) {
+ final File outputDir =
+ new File(getGlobalScope().getIntermediatesDir(), "lint");
+
+ lintCompile.doFirst(new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ // create the directory for lint output if it does not exist.
+ if (!outputDir.exists()) {
+ boolean mkdirs = outputDir.mkdirs();
+ if (!mkdirs) {
+ throw new GradleException(
+ "Unable to create lint output directory.");
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Is the given variant relevant for lint?
+ */
+ private static boolean isLintVariant(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> baseVariantData) {
+ // Only create lint targets for variants like debug and release, not debugTest
+ VariantConfiguration config = baseVariantData.getVariantConfiguration();
+ return !config.getType().isForTesting();
+ }
+
+ /**
+ * Add tasks for running lint on individual variants. We've already added a
+ * lint task earlier which runs on all variants.
+ */
+ public void createLintTasks(TaskFactory tasks, final VariantScope scope) {
+ final BaseVariantData<? extends BaseVariantOutputData> baseVariantData =
+ scope.getVariantData();
+ if (!isLintVariant(baseVariantData)) {
+ return;
+ }
+
+ // wire the main lint task dependency.
+ tasks.named(LINT, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(LINT_COMPILE);
+ it.dependsOn(scope.getJavacTask().getName());
+ }
+ });
+
+ AndroidTask<Lint> variantLintCheck = androidTasks.create(
+ tasks, new Lint.ConfigAction(scope));
+ variantLintCheck.dependsOn(tasks, LINT_COMPILE, scope.getJavacTask());
+ }
+
+ private void createLintVitalTask(@NonNull ApkVariantData variantData) {
+ checkState(getExtension().getLintOptions().isCheckReleaseBuilds());
+ // TODO: re-enable with Jack when possible
+ if (!variantData.getVariantConfiguration().getBuildType().isDebuggable() &&
+ !variantData.getVariantConfiguration().getUseJack()) {
+ String variantName = variantData.getVariantConfiguration().getFullName();
+ String capitalizedVariantName = StringHelper.capitalize(variantName);
+ String taskName = "lintVital" + capitalizedVariantName;
+ final Lint lintReleaseCheck = project.getTasks().create(taskName, Lint.class);
+ lintReleaseCheck.setAndroidBuilder(androidBuilder);
+ // TODO: Make this task depend on lintCompile too (resolve initialization order first)
+ optionalDependsOn(lintReleaseCheck, variantData.javacTask);
+ lintReleaseCheck.setLintOptions(getExtension().getLintOptions());
+ lintReleaseCheck.setSdkHome(
+ checkNotNull(sdkHandler.getSdkFolder(), "SDK not set up."));
+ lintReleaseCheck.setVariantName(variantName);
+ lintReleaseCheck.setToolingRegistry(toolingRegistry);
+ lintReleaseCheck.setFatalOnly(true);
+ lintReleaseCheck.setDescription(
+ "Runs lint on just the fatal issues in the " + capitalizedVariantName
+ + " build.");
+
+ variantData.assembleVariantTask.dependsOn(lintReleaseCheck);
+
+ // If lint is being run, we do not need to run lint vital.
+ // TODO: Find a better way to do this.
+ project.getGradle().getTaskGraph().whenReady(new Closure<Void>(this, this) {
+ public void doCall(TaskExecutionGraph taskGraph) {
+ if (taskGraph.hasTask(LINT)) {
+ lintReleaseCheck.setEnabled(false);
+ }
+ }
+ });
+ }
+ }
+
+ private void createRunUnitTestTask(
+ @NonNull TaskFactory tasks,
+ @NonNull final VariantScope variantScope) {
+ final AndroidTask<Test> runtTestsTask =
+ androidTasks.create(tasks, new UnitTestConfigAction(variantScope));
+ runtTestsTask.dependsOn(tasks, variantScope.getVariantData().assembleVariantTask);
+
+ tasks.named(JavaPlugin.TEST_TASK_NAME, new Action<Task>() {
+ @Override
+ public void execute(Task test) {
+ test.dependsOn(runtTestsTask.getName());
+ }
+ });
+ }
+
+ public void createTopLevelTestTasks(final TaskFactory tasks, boolean hasFlavors) {
+ createMockableJarTask(tasks);
+
+ final List<String> reportTasks = Lists.newArrayListWithExpectedSize(2);
+
+ List<DeviceProvider> providers = getExtension().getDeviceProviders();
+
+ final String connectedRootName = CONNECTED + ANDROID_TEST.getSuffix();
+ final String defaultReportsDir = getGlobalScope().getReportsDir().getAbsolutePath()
+ + "/" + FD_ANDROID_TESTS;
+ final String defaultResultsDir = getGlobalScope().getOutputsDir().getAbsolutePath()
+ + "/" + FD_ANDROID_RESULTS;
+
+ // If more than one flavor, create a report aggregator task and make this the parent
+ // task for all new connected tasks. Otherwise, create a top level connectedAndroidTest
+ // DefaultTask.
+ if (hasFlavors) {
+ tasks.create(connectedRootName, AndroidReportTask.class,
+ new Action<AndroidReportTask>() {
+ @Override
+ public void execute(AndroidReportTask mainConnectedTask) {
+ mainConnectedTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ mainConnectedTask.setDescription("Installs and runs instrumentation "
+ + "tests for all flavors on connected devices.");
+ mainConnectedTask.setReportType(ReportType.MULTI_FLAVOR);
+
+ ConventionMappingHelper.map(mainConnectedTask, "resultsDir",
+ new Callable<File>() {
+ @Override
+ public File call() {
+ final String dir =
+ extension.getTestOptions().getResultsDir();
+ String rootLocation = dir != null && !dir.isEmpty()
+ ? dir : defaultResultsDir;
+ return project.file(rootLocation + "/connected/"
+ + FD_FLAVORS_ALL);
+ }
+ });
+ ConventionMappingHelper.map(mainConnectedTask, "reportsDir",
+ new Callable<File>() {
+ @Override
+ public File call() {
+ final String dir =
+ extension.getTestOptions().getReportDir();
+ String rootLocation = dir != null && !dir.isEmpty()
+ ? dir : defaultReportsDir;
+ return project.file(rootLocation + "/connected/"
+ + FD_FLAVORS_ALL);
+ }
+ });
+
+ }
+
+ });
+ reportTasks.add(connectedRootName);
+ } else {
+ tasks.create(connectedRootName, new Action<Task>() {
+ @Override
+ public void execute(Task connectedTask) {
+ connectedTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ connectedTask.setDescription(
+ "Installs and runs instrumentation tests for all flavors on connected devices.");
+ }
+
+ });
+ }
+
+ tasks.named(CONNECTED_CHECK, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(connectedRootName);
+ }
+
+ });
+
+ final String mainProviderTaskName = DEVICE + ANDROID_TEST.getSuffix();
+ // if more than one provider tasks, either because of several flavors, or because of
+ // more than one providers, then create an aggregate report tasks for all of them.
+ if (providers.size() > 1 || hasFlavors) {
+ tasks.create(mainProviderTaskName, AndroidReportTask.class,
+ new Action<AndroidReportTask>() {
+ @Override
+ public void execute(AndroidReportTask mainProviderTask) {
+ mainProviderTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ mainProviderTask.setDescription(
+ "Installs and runs instrumentation tests using all Device Providers.");
+ mainProviderTask.setReportType(ReportType.MULTI_FLAVOR);
+
+ ConventionMappingHelper.map(mainProviderTask, "resultsDir",
+ new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final String dir =
+ extension.getTestOptions().getResultsDir();
+ String rootLocation = dir != null && !dir.isEmpty()
+ ? dir : defaultResultsDir;
+
+ return project.file(rootLocation + "/devices/"
+ + FD_FLAVORS_ALL);
+ }
+ });
+ ConventionMappingHelper.map(mainProviderTask, "reportsDir",
+ new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final String dir =
+ extension.getTestOptions().getReportDir();
+ String rootLocation = dir != null && !dir.isEmpty()
+ ? dir : defaultReportsDir;
+ return project.file(rootLocation + "/devices/"
+ + FD_FLAVORS_ALL);
+ }
+ });
+ }
+
+ });
+ reportTasks.add(mainProviderTaskName);
+ } else {
+ tasks.create(mainProviderTaskName, new Action<Task>() {
+ @Override
+ public void execute(Task providerTask) {
+ providerTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ providerTask.setDescription(
+ "Installs and runs instrumentation tests using all Device Providers.");
+ }
+ });
+ }
+
+ tasks.named(DEVICE_CHECK, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(mainProviderTaskName);
+ }
+ });
+
+ // Create top level unit test tasks.
+ tasks.create(JavaPlugin.TEST_TASK_NAME, new Action<Task>() {
+ @Override
+ public void execute(Task unitTestTask) {
+ unitTestTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ unitTestTask.setDescription("Run unit tests for all variants.");
+ }
+
+ });
+ tasks.named(JavaBasePlugin.CHECK_TASK_NAME,
+ new Action<Task>() {
+ @Override
+ public void execute(Task check) {
+ check.dependsOn(JavaPlugin.TEST_TASK_NAME);
+ }
+ });
+
+ // If gradle is launched with --continue, we want to run all tests and generate an
+ // aggregate report (to help with the fact that we may have several build variants, or
+ // or several device providers).
+ // To do that, the report tasks must run even if one of their dependent tasks (flavor
+ // or specific provider tasks) fails, when --continue is used, and the report task is
+ // meant to run (== is in the task graph).
+ // To do this, we make the children tasks ignore their errors (ie they won't fail and
+ // stop the build).
+ if (!reportTasks.isEmpty() && project.getGradle().getStartParameter()
+ .isContinueOnFailure()) {
+ project.getGradle().getTaskGraph().whenReady(new Closure<Void>(this, this) {
+ public void doCall(TaskExecutionGraph taskGraph) {
+ for (String reportTask : reportTasks) {
+ if (taskGraph.hasTask(reportTask)) {
+ tasks.named(reportTask, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ ((AndroidReportTask) task).setWillRun();
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+ }
+
+ protected void createConnectedTestForVariant(
+ @NonNull TaskFactory tasks,
+ @NonNull final VariantScope variantScope) {
+ final BaseVariantData<? extends BaseVariantOutputData> baseVariantData =
+ variantScope.getTestedVariantData();
+ final TestVariantData testVariantData = (TestVariantData) variantScope.getVariantData();
+
+ // get single output for now
+ final BaseVariantOutputData variantOutputData = baseVariantData.getOutputs().get(0);
+ final BaseVariantOutputData testVariantOutputData = testVariantData.getOutputs().get(0);
+
+ String connectedRootName = CONNECTED + ANDROID_TEST.getSuffix();
+
+ TestDataImpl testData = new TestDataImpl(testVariantData);
+ testData.setExtraInstrumentationTestRunnerArgs(
+ AndroidGradleOptions.getExtraInstrumentationTestRunnerArgs(project));
+
+ // create the check tasks for this test
+ // first the connected one.
+ ImmutableList<Task> artifactsTasks = ImmutableList.of(
+ testVariantData.getOutputs().get(0).assembleTask,
+ baseVariantData.assembleVariantTask);
+
+ final AndroidTask<DeviceProviderInstrumentTestTask> connectedTask = androidTasks.create(
+ tasks,
+ new DeviceProviderInstrumentTestTask.ConfigAction(
+ testVariantData.getScope(),
+ new ConnectedDeviceProvider(sdkHandler.getSdkInfo().getAdb(),
+ globalScope.getExtension().getAdbOptions().getTimeOutInMs(),
+ new LoggerWrapper(logger)), testData));
+
+ connectedTask.dependsOn(tasks, artifactsTasks);
+
+ tasks.named(connectedRootName, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(connectedTask.getName());
+ }
+ });
+
+ if (baseVariantData.getVariantConfiguration().getBuildType().isTestCoverageEnabled()
+ && !baseVariantData.getVariantConfiguration().getUseJack()) {
+ final AndroidTask<JacocoReportTask> reportTask = androidTasks.create(
+ tasks,
+ variantScope.getTaskName("create", "CoverageReport"),
+ JacocoReportTask.class,
+ new Action<JacocoReportTask>() {
+ @Override
+ public void execute(JacocoReportTask reportTask) {
+ reportTask.setDescription("Creates JaCoCo test coverage report from "
+ + "data gathered on the device.");
+
+ reportTask.setReportName(
+ baseVariantData.getVariantConfiguration().getFullName());
+
+ ConventionMappingHelper.map(reportTask, "jacocoClasspath",
+ new Callable<FileCollection>() {
+ @Override
+ public FileCollection call() throws Exception {
+ return project.getConfigurations().getAt(
+ JacocoPlugin.ANT_CONFIGURATION_NAME);
+ }
+ });
+ ConventionMappingHelper
+ .map(reportTask, "coverageFile", new Callable<File>() {
+ @Override
+ public File call() {
+ return new File(
+ ((TestVariantData) testVariantData.getScope()
+ .getVariantData()).connectedTestTask
+ .getCoverageDir(),
+ SimpleTestCallable.FILE_COVERAGE_EC);
+ }
+ });
+ ConventionMappingHelper
+ .map(reportTask, "classDir", new Callable<File>() {
+ @Override
+ public File call() {
+ return baseVariantData.javacTask.getDestinationDir();
+ }
+ });
+ ConventionMappingHelper
+ .map(reportTask, "sourceDir", new Callable<List<File>>() {
+ @Override
+ public List<File> call() {
+ return baseVariantData
+ .getJavaSourceFoldersForCoverage();
+ }
+ });
+
+ ConventionMappingHelper
+ .map(reportTask, "reportDir", new Callable<File>() {
+ @Override
+ public File call() {
+ return new File(
+ variantScope.getGlobalScope().getReportsDir(),
+ "/coverage/" + baseVariantData
+ .getVariantConfiguration()
+ .getDirName());
+ }
+ });
+
+ reportTask.dependsOn(connectedTask.getName());
+ }
+ });
+
+ variantScope.setCoverageReportTask(reportTask);
+ baseVariantData.getScope().getCoverageReportTask().dependsOn(tasks, reportTask);
+
+ tasks.named(connectedRootName, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(reportTask.getName());
+ }
+ });
+ }
+
+ String mainProviderTaskName = DEVICE + ANDROID_TEST.getSuffix();
+
+ List<DeviceProvider> providers = getExtension().getDeviceProviders();
+
+ boolean hasFlavors = baseVariantData.getVariantConfiguration().hasFlavors();
+
+ // now the providers.
+ for (DeviceProvider deviceProvider : providers) {
+
+ final AndroidTask<DeviceProviderInstrumentTestTask> providerTask = androidTasks
+ .create(tasks, new DeviceProviderInstrumentTestTask.ConfigAction(
+ testVariantData.getScope(), deviceProvider, testData));
+
+ tasks.named(mainProviderTaskName, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(providerTask.getName());
+ }
+ });
+ providerTask.dependsOn(tasks, artifactsTasks);
+ }
+
+ // now the test servers
+ List<TestServer> servers = getExtension().getTestServers();
+ for (TestServer testServer : servers) {
+ final TestServerTask serverTask = project.getTasks().create(
+ hasFlavors ?
+ baseVariantData.getScope().getTaskName(testServer.getName() + "Upload")
+ :
+ testServer.getName() + ("Upload"),
+ TestServerTask.class);
+
+ serverTask.setDescription(
+ "Uploads APKs for Build \'" + baseVariantData.getVariantConfiguration()
+ .getFullName() + "\' to Test Server \'" +
+ StringHelper.capitalize(testServer.getName()) + "\'.");
+ serverTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ serverTask.setVariantName(
+ baseVariantData.getScope().getVariantConfiguration().getFullName());
+ serverTask.dependsOn(testVariantOutputData.assembleTask,
+ variantOutputData.assembleTask);
+
+ serverTask.setTestServer(testServer);
+
+ ConventionMappingHelper.map(serverTask, "testApk", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return testVariantOutputData.getOutputFile();
+ }
+ });
+ if (!(baseVariantData instanceof LibraryVariantData)) {
+ ConventionMappingHelper.map(serverTask, "testedApk", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return variantOutputData.getOutputFile();
+ }
+ });
+ }
+
+ ConventionMappingHelper.map(serverTask, "variantName", new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return baseVariantData.getVariantConfiguration().getFullName();
+ }
+ });
+
+ tasks.named(DEVICE_CHECK, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(serverTask);
+ }
+ });
+
+ if (!testServer.isConfigured()) {
+ serverTask.setEnabled(false);
+ }
+ }
+ }
+
+ public void createJarTasks(@NonNull TaskFactory tasks, @NonNull final VariantScope scope) {
+ final BaseVariantData variantData = scope.getVariantData();
+
+ final GradleVariantConfiguration config = variantData.getVariantConfiguration();
+ tasks.create(
+ scope.getTaskName("jar", "Classes"),
+ AndroidJarTask.class,
+ new Action<AndroidJarTask>() {
+ @Override
+ public void execute(AndroidJarTask jarTask) {
+ jarTask.setArchiveName("classes.jar");
+ jarTask.setDestinationDir(new File(
+ scope.getGlobalScope().getIntermediatesDir(),
+ "packaged/" + config.getDirName() + "/"));
+ jarTask.from(scope.getJavaOutputDir());
+ jarTask.dependsOn(scope.getJavacTask().getName());
+ variantData.binaryFileProviderTask = jarTask;
+ }
+
+ });
+ }
+
+ /**
+ * Creates the post-compilation tasks for the given Variant.
+ *
+ * These tasks create the dex file from the .class files, plus optional intermediary steps like
+ * proguard and jacoco
+ *
+ */
+ public void createPostCompilationTasks(@NonNull TaskFactory tasks,
+ @NonNull final VariantScope variantScope) {
+
+ checkNotNull(variantScope.getJavacTask());
+
+ final ApkVariantData variantData = (ApkVariantData) variantScope.getVariantData();
+ final GradleVariantConfiguration config = variantData.getVariantConfiguration();
+
+ TransformManager transformManager = variantScope.getTransformManager();
+
+ // ---- Code Coverage first -----
+ boolean isTestCoverageEnabled = config.getBuildType().isTestCoverageEnabled() &&
+ !config.getType().isForTesting() &&
+ getIncrementalMode(variantScope.getVariantConfiguration()) == IncrementalMode.NONE;
+ if (isTestCoverageEnabled) {
+ createJacocoTransform(tasks, variantScope);
+ }
+
+ boolean isMinifyEnabled = config.isMinifyEnabled();
+ boolean isMultiDexEnabled = config.isMultiDexEnabled();
+ // Switch to native multidex if possible when using instant run.
+ boolean isLegacyMultiDexMode = config.isLegacyMultiDexMode() &&
+ (getIncrementalMode(variantScope.getVariantConfiguration()) == IncrementalMode.NONE
+ || variantScope.getInstantRunBuildContext().getPatchingPolicy() ==
+ InstantRunPatchingPolicy.PRE_LOLLIPOP);
+
+ AndroidConfig extension = variantScope.getGlobalScope().getExtension();
+
+ // ----- External Transforms -----
+ // apply all the external transforms.
+ List<Transform> customTransforms = extension.getTransforms();
+ List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();
+
+ for (int i = 0, count = customTransforms.size() ; i < count ; i++) {
+ Transform transform = customTransforms.get(i);
+ AndroidTask<TransformTask> task = transformManager
+ .addTransform(tasks, variantScope, transform);
+ // task could be null if the transform is invalid.
+ if (task != null) {
+ List<Object> deps = customTransformsDependencies.get(i);
+ if (!deps.isEmpty()) {
+ task.dependsOn(tasks, deps);
+ }
+
+ // if the task is a no-op then we make assemble task depend on it.
+ if (transform.getScopes().isEmpty()) {
+ variantData.assembleVariantTask.dependsOn(tasks, task);
+ }
+
+ }
+ }
+
+ // ----- Minify next -----
+
+ if (isMinifyEnabled) {
+ boolean outputToJarFile = isMultiDexEnabled && isLegacyMultiDexMode;
+ createMinifyTransform(tasks, variantScope, outputToJarFile);
+ }
+
+ // ----- 10x support
+
+ AndroidTask<InstantRunWrapperTask> incrementalBuildWrapperTask = null;
+ if (getIncrementalMode(variantScope.getVariantConfiguration()) != IncrementalMode.NONE) {
+
+ variantScope.getInstantRunBuildContext().setInstantRunMode(true);
+
+ // we are creating two anchor tasks
+ // 1. allActionAnchorTask to anchor tasks that should be executed whether a full build or
+ // incremental build is invoked.
+ // 2. incrementalAnchorTask to anchor tasks that should only be executed when an
+ // incremental build is requested.
+ // the incrementalAnchorTask will therefore depend on the allActionAnchorTask.
+
+ AndroidTask<InstantRunAnchorTask> allActionsAnchorTask =
+ createInstantRunAllActionsTasks(tasks, variantScope);
+ variantScope.setInstantRunAnchorTask(allActionsAnchorTask);
+ incrementalBuildWrapperTask = createInstantRunIncrementalTasks(tasks, variantScope);
+ variantScope.setInstantRunIncrementalTask(incrementalBuildWrapperTask);
+ incrementalBuildWrapperTask.dependsOn(tasks, allActionsAnchorTask);
+
+ // when dealing with platforms that can handle multi dexes natively, automatically
+ // turn on multi dexing so shards are packaged as individual dex files.
+ if (InstantRunPatchingPolicy.PRE_LOLLIPOP !=
+ variantScope.getInstantRunBuildContext().getPatchingPolicy()) {
+ isMultiDexEnabled = true;
+ // force pre-dexing to be true as we rely on individual slices to be packaged
+ // separately.
+ extension.getDexOptions().setPreDexLibraries(true);
+ }
+
+ extension.getDexOptions().setJumboMode(true);
+ }
+ // ----- Multi-Dex support
+
+ AndroidTask<TransformTask> multiDexClassListTask = null;
+ // non Library test are running as native multi-dex
+ if (isMultiDexEnabled && isLegacyMultiDexMode) {
+ if (!variantData.getVariantConfiguration().getBuildType().isUseProguard()) {
+ throw new IllegalStateException(
+ "Build-in class shrinker and multidex are not supported yet.");
+ }
+
+ // ----------
+ // create a transform to jar the inputs into a single jar.
+ if (!isMinifyEnabled) {
+ // merge the classes only, no need to package the resources since they are
+ // not used during the computation.
+ JarMergingTransform jarMergingTransform = new JarMergingTransform(
+ TransformManager.SCOPE_FULL_PROJECT);
+ transformManager.addTransform(tasks, variantScope, jarMergingTransform);
+ }
+
+ // ----------
+ // Create a task to collect the list of manifest entry points which are
+ // needed in the primary dex
+ AndroidTask<CreateManifestKeepList> manifestKeepListTask = androidTasks.create(tasks,
+ new CreateManifestKeepList.ConfigAction(variantScope));
+ manifestKeepListTask.dependsOn(tasks,
+ variantData.getOutputs().get(0).getScope().getManifestProcessorTask());
+
+ // ---------
+ // create the transform that's going to take the code and the proguard keep list
+ // from above and compute the main class list.
+ MultiDexTransform multiDexTransform = new MultiDexTransform(
+ variantScope.getManifestKeepListFile(),
+ variantScope,
+ null);
+ multiDexClassListTask = transformManager.addTransform(
+ tasks, variantScope, multiDexTransform);
+ multiDexClassListTask.dependsOn(tasks, manifestKeepListTask);
+ }
+ // create dex transform
+ DexTransform dexTransform = new DexTransform(
+ extension.getDexOptions(),
+ config.getBuildType().isDebuggable(),
+ isMultiDexEnabled,
+ isMultiDexEnabled && isLegacyMultiDexMode ? variantScope.getMainDexListFile() : null,
+ variantScope.getPreDexOutputDir(),
+ variantScope.getGlobalScope().getAndroidBuilder(),
+ getLogger(),
+ variantScope.getInstantRunBuildContext());
+ AndroidTask<TransformTask> dexTask = transformManager.addTransform(
+ tasks, variantScope, dexTransform);
+ // need to manually make dex task depend on MultiDexTransform since there's no stream
+ // consumption making this automatic
+ dexTask.optionalDependsOn(tasks, multiDexClassListTask);
+
+ // if we are in instant-run mode and the patching policy is relying on mult-dex shards,
+ // we should run the dexing as part of the incremental build.
+ if (incrementalBuildWrapperTask != null &&
+ InstantRunPatchingPolicy.PRE_LOLLIPOP !=
+ variantScope.getInstantRunBuildContext().getPatchingPolicy()) {
+ incrementalBuildWrapperTask.dependsOn(tasks, dexTask);
+ }
+ }
+
+ /**
+ * Create InstantRun related tasks that should be ran right after the java compilation task.
+ */
+ @NonNull
+ protected AndroidTask<InstantRunAnchorTask> createInstantRunAllActionsTasks(
+ @NonNull TaskFactory tasks, @NonNull VariantScope variantScope) {
+
+ AndroidTask<InstantRunAnchorTask> allActionAnchorTask = getAndroidTasks().create(tasks,
+ new InstantRunAnchorTask.ConfigAction(variantScope, "Tasks"));
+
+ AndroidTask<BuildInfoLoaderTask> buildInfoLoaderTask = getAndroidTasks().create(tasks,
+ new BuildInfoLoaderTask.ConfigAction(variantScope, getLogger()));
+
+ TransformManager transformManager = variantScope.getTransformManager();
+
+ ExtractJarsTransform extractJarsTransform = new ExtractJarsTransform(
+ ImmutableSet.<QualifiedContent.ContentType>of(
+ QualifiedContent.DefaultContentType.CLASSES),
+ ImmutableSet.of(Scope.SUB_PROJECTS));
+ AndroidTask<TransformTask> extractJarsTask = transformManager
+ .addTransform(tasks, variantScope, extractJarsTransform);
+
+ // always run the verifier first, since if it detects incompatible changes, we
+ // should skip bytecode enhancements of the changed classes.
+ InstantRunVerifierTransform verifierTransform = new InstantRunVerifierTransform(variantScope);
+ AndroidTask<TransformTask> verifierTask = variantScope.getTransformManager()
+ .addTransform(tasks, variantScope, verifierTransform);
+ verifierTask.dependsOn(tasks, extractJarsTask);
+ variantScope.setInstantRunVerifierTask(verifierTask);
+
+ NoChangesVerifierTransform jniLibsVerifierTransform = new NoChangesVerifierTransform(
+ variantScope,
+ ImmutableSet.<ContentType>of(DefaultContentType.RESOURCES, ExtendedContentType.NATIVE_LIBS),
+ getResMergingScopes(variantScope), InstantRunVerifierStatus.JAVA_RESOURCES_CHANGED,
+ true /* abortBuild */);
+ AndroidTask<TransformTask> jniLibsVerifierTask =
+ variantScope.getTransformManager().addTransform(
+ tasks,
+ variantScope,
+ jniLibsVerifierTransform);
+ jniLibsVerifierTask.dependsOn(tasks, verifierTask);
+
+ NoChangesVerifierTransform dependenciesVerifierTransform =
+ new NoChangesVerifierTransform(
+ variantScope,
+ ImmutableSet.<ContentType>of(DefaultContentType.CLASSES),
+ Sets.immutableEnumSet(
+ Scope.PROJECT_LOCAL_DEPS,
+ Scope.SUB_PROJECTS_LOCAL_DEPS,
+ Scope.EXTERNAL_LIBRARIES),
+ InstantRunVerifierStatus.DEPENDENCY_CHANGED,
+ false /* abortBuild */);
+ AndroidTask<TransformTask> dependenciesVerifierTask =
+ variantScope.getTransformManager().addTransform(
+ tasks,
+ variantScope,
+ dependenciesVerifierTransform);
+ dependenciesVerifierTask.dependsOn(tasks, verifierTask);
+
+
+ InstantRunTransform instantRunTransform = new InstantRunTransform(variantScope);
+ AndroidTask<TransformTask> instantRunTask = transformManager
+ .addTransform(tasks, variantScope, instantRunTransform);
+ instantRunTask.dependsOn(tasks, buildInfoLoaderTask, verifierTask, jniLibsVerifierTask,
+ dependenciesVerifierTask);
+
+ AndroidTask<FastDeployRuntimeExtractorTask> extractorTask = getAndroidTasks().create(
+ tasks, new FastDeployRuntimeExtractorTask.ConfigAction(variantScope));
+
+ // also add a new stream for the extractor task output.
+ variantScope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_CLASS)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setJar(variantScope.getIncrementalRuntimeSupportJar())
+ .setDependency(extractorTask.get(tasks))
+ .build());
+
+ AndroidTask<GenerateInstantRunAppInfoTask> generateAppInfoAndroidTask = getAndroidTasks()
+ .create(tasks, new GenerateInstantRunAppInfoTask.ConfigAction(variantScope));
+
+ // create the AppInfo.class for this variant.
+ GenerateInstantRunAppInfoTask generateInstantRunAppInfoTask
+ = generateAppInfoAndroidTask.get(tasks);
+
+ // make the task that generates the AppInfo dependent on the first merge manifest task
+ // so we can get its output file.
+ VariantOutputScope outputScope =
+ variantScope.getVariantData().getOutputs().get(0).getScope();
+ generateAppInfoAndroidTask.dependsOn(tasks, outputScope.getManifestProcessorTask());
+
+ // also add a new stream for the injector task output.
+ variantScope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_CLASS)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setJar(generateInstantRunAppInfoTask.getOutputFile())
+ .setDependency(generateInstantRunAppInfoTask)
+ .build());
+
+ allActionAnchorTask.dependsOn(tasks, instantRunTask);
+
+ DexOptions dexOptions = variantScope.getGlobalScope().getExtension().getDexOptions();
+
+ // we always produce the reload.dex irrespective of the targeted version,
+ // and if we are not in incremental mode, we need to still need to clean our output state.
+ InstantRunDex reloadDexTransform = new InstantRunDex(
+ variantScope,
+ InstantRunBuildType.RELOAD,
+ androidBuilder,
+ dexOptions,
+ getLogger(),
+ ImmutableSet.<ContentType>of(
+ ExtendedContentType.CLASSES_ENHANCED));
+
+ AndroidTask<TransformTask> reloadDexing = variantScope.getTransformManager()
+ .addTransform(tasks, variantScope, reloadDexTransform);
+
+ allActionAnchorTask.dependsOn(tasks, reloadDexing);
+
+ return allActionAnchorTask;
+ }
+
+ /**
+ * Creates all InstantRun related transforms after compilation.
+ */
+ @NonNull
+ public AndroidTask<InstantRunWrapperTask> createInstantRunIncrementalTasks(
+ @NonNull TaskFactory tasks, @NonNull final VariantScope scope) {
+
+ // we are creating two anchor tasks
+ // 1. allActionAnchorTask to anchor tasks that should be executed whether a full build or
+ // incremental build is invoked.
+ // 2. incrementalAnchorTask to anchor tasks that should only be executed when an
+ // incremental build is requested.
+ // the incrementalAnchorTask will therefore depend on the allActionAnchorTask.
+ AndroidTask<InstantRunAnchorTask> incrementalAnchorTask = androidTasks.create(tasks,
+ new InstantRunAnchorTask.ConfigAction(scope));
+
+ // create the incremental version of the build-info.xml, another task will take care
+ // of generating the build-info.xml when a full build is invoked.
+ AndroidTask<InstantRunWrapperTask> incrementalWrapperTask = getAndroidTasks().create(tasks,
+ new InstantRunWrapperTask.ConfigAction(
+ scope, InstantRunWrapperTask.TaskType.INCREMENTAL, getLogger()));
+
+ // this will force build-info.xml to be generated only when the external task is directly
+ // invoked by the IDE.
+ incrementalAnchorTask.dependsOn(tasks, incrementalWrapperTask);
+
+ scope.getInstantRunBuildContext().setApiLevel(
+ AndroidGradleOptions.getTargetApiLevel(project),
+ AndroidGradleOptions.getColdswapMode(project),
+ AndroidGradleOptions.getBuildTargetAbi(project));
+ scope.getInstantRunBuildContext().setDensity(
+ AndroidGradleOptions.getBuildTargetDensity(project));
+ InstantRunPatchingPolicy patchingPolicy =
+ scope.getInstantRunBuildContext().getPatchingPolicy();
+
+ DexOptions dexOptions = scope.getGlobalScope().getExtension().getDexOptions();
+
+ // let's create the coldswap kicker task. It is necessary as sometimes the IDE will
+ // request an assembleDebug to get the latest coldswap bits yet without any user changes.
+ // so we need to manually kick the tasks that accumulated changes during reload.dex
+ // iterations so they produce the artifacts.
+ AndroidTask<ColdswapArtifactsKickerTask> coldswapKickerTask = getAndroidTasks().create(
+ tasks, new ColdswapArtifactsKickerTask.ConfigAction("coldswapKicker", scope));
+
+ // this kicker task is dependent on the verifier and associated tasks result.
+ coldswapKickerTask.dependsOn(tasks, scope.getInstantRunVerifierTask());
+
+ if (patchingPolicy == InstantRunPatchingPolicy.PRE_LOLLIPOP) {
+ // for Dalvik, we generate a restart.dex.
+ InstantRunDex classesTwoTransform = new InstantRunDex(
+ scope,
+ InstantRunBuildType.RESTART,
+ androidBuilder,
+ dexOptions,
+ getLogger(),
+ ImmutableSet.<ContentType>of(
+ DefaultContentType.CLASSES));
+ AndroidTask<TransformTask> classesTwoDexing = scope.getTransformManager()
+ .addTransform(tasks, scope, classesTwoTransform);
+ // restart task depends on the kicker result
+ classesTwoDexing.dependsOn(tasks, coldswapKickerTask);
+ incrementalWrapperTask.dependsOn(tasks, classesTwoDexing);
+ } else {
+ // if we are at API 21 or above, we generate multi-dexes.
+ // this transform and all its dependencies will also run in full build mode as
+ // it is automatically enrolled by the transform manager.
+ InstantRunSlicer slicer = new InstantRunSlicer(getLogger(), scope);
+ AndroidTask<TransformTask> slicing = scope.getTransformManager()
+ .addTransform(tasks, scope, slicer);
+
+ // slicing should only happen if we need to produce the restart dexes.
+ slicing.dependsOn(tasks, coldswapKickerTask);
+
+ incrementalWrapperTask.dependsOn(tasks, slicing);
+
+ }
+
+ return incrementalWrapperTask;
+ }
+
+ protected void handleJacocoDependencies(@NonNull VariantScope variantScope) {
+ GradleVariantConfiguration config = variantScope.getVariantConfiguration();
+ // we add the jacoco jar if coverage is enabled, but we don't add it
+ // for test apps as it's already part of the tested app.
+ // For library project, since we cannot use the local jars of the library,
+ // we add it as well.
+ boolean isTestCoverageEnabled = config.getBuildType().isTestCoverageEnabled() &&
+ getIncrementalMode(variantScope.getVariantConfiguration()) == IncrementalMode.NONE &&
+ (!config.getType().isForTesting() ||
+ (config.getTestedConfig() != null &&
+ config.getTestedConfig().getType() == VariantType.LIBRARY));
+ if (isTestCoverageEnabled) {
+ Copy agentTask = getJacocoAgentTask();
+
+ // also add a new stream for the jacoco agent Jar
+ variantScope.getTransformManager().addStream(OriginalStream.builder()
+ .addContentTypes(TransformManager.CONTENT_JARS)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setJar(new File(agentTask.getDestinationDir(), FILE_JACOCO_AGENT))
+ .setDependency(agentTask)
+ .build());
+ }
+ }
+
+ public void createJacocoTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull final VariantScope variantScope) {
+
+ AndroidTask<?> task = variantScope.getTransformManager().addTransform(taskFactory,
+ variantScope, new JacocoTransform(project.getConfigurations()));
+
+ Copy agentTask = getJacocoAgentTask();
+ task.dependsOn(taskFactory, agentTask);
+ }
+
+ public void createJackTask(
+ @NonNull TaskFactory tasks,
+ @NonNull VariantScope scope) {
+
+ // ----- Create Jill tasks -----
+ final AndroidTask<JillTask> jillRuntimeTask = androidTasks.create(tasks,
+ new JillTask.RuntimeTaskConfigAction(scope));
+
+ final AndroidTask<JillTask> jillPackagedTask = androidTasks.create(tasks,
+ new JillTask.PackagedConfigAction(scope));
+
+ jillPackagedTask.dependsOn(tasks,
+ scope.getVariantData().getVariantDependency().getPackageConfiguration()
+ .getBuildDependencies());
+
+ // ----- Create Jack Task -----
+ AndroidTask<JackTask> jackTask = androidTasks.create(tasks,
+ new JackTask.ConfigAction(scope, isVerbose(), isDebugLog()));
+
+ // Jack is compiling and also providing the binary and mapping files.
+ setJavaCompilerTask(jackTask, tasks, scope);
+ jackTask.optionalDependsOn(tasks, scope.getMergeJavaResourcesTask());
+ jackTask.dependsOn(tasks,
+ scope.getVariantData().sourceGenTask,
+ jillRuntimeTask,
+ jillPackagedTask,
+ // TODO - dependency information for the compile classpath is being lost.
+ // Add a temporary approximation
+ scope.getVariantData().getVariantDependency().getCompileConfiguration()
+ .getBuildDependencies());
+
+ }
+
+ protected void createDataBindingTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
+ if (scope.getVariantConfiguration().getUseJack()) {
+ androidBuilder.getErrorReporter().handleSyncError(
+ scope.getVariantConfiguration().getFullName(),
+ SyncIssue.TYPE_JACK_IS_NOT_SUPPORTED,
+ "Data Binding does not support Jack builds yet"
+ );
+ }
+
+ dataBindingBuilder.setDebugLogEnabled(getLogger().isDebugEnabled());
+ AndroidTask<DataBindingProcessLayoutsTask> processLayoutsTask = androidTasks
+ .create(tasks, new DataBindingProcessLayoutsTask.ConfigAction(scope));
+ scope.setDataBindingProcessLayoutsTask(processLayoutsTask);
+
+ scope.getGenerateRClassTask().dependsOn(tasks, processLayoutsTask);
+ processLayoutsTask.dependsOn(tasks, scope.getMergeResourcesTask());
+
+ AndroidTask<DataBindingExportBuildInfoTask> exportBuildInfo = androidTasks
+ .create(tasks, new DataBindingExportBuildInfoTask.ConfigAction(scope,
+ dataBindingBuilder.getPrintMachineReadableOutput()));
+ scope.setDataBindingExportInfoTask(exportBuildInfo);
+
+ exportBuildInfo.dependsOn(tasks, processLayoutsTask);
+ AndroidTask<? extends AbstractCompile> javaCompilerTask = scope.getJavaCompilerTask();
+ if (javaCompilerTask != null) {
+ javaCompilerTask.dependsOn(tasks, exportBuildInfo);
+ }
+
+ setupCompileTaskDependencies(tasks, scope, scope.getVariantData(), exportBuildInfo);
+ }
+
+ /**
+ * Creates the final packaging task, and optionally the zipalign task (if the variant is signed)
+ *
+ * @param publishApk if true the generated APK gets published.
+ * @param fullBuildInfoGeneratorTask task that generates the build-info.xml for full build.
+ */
+ public void createPackagingTask(@NonNull TaskFactory tasks,
+ @NonNull VariantScope variantScope,
+ boolean publishApk,
+ @Nullable AndroidTask<InstantRunWrapperTask> fullBuildInfoGeneratorTask) {
+
+ final ApkVariantData variantData = (ApkVariantData) variantScope.getVariantData();
+ GradleVariantConfiguration config = variantData.getVariantConfiguration();
+
+ boolean signedApk = variantData.isSigned();
+ final File apkLocation = new File(variantScope.getGlobalScope().getApkLocation());
+
+ boolean multiOutput = variantData.getOutputs().size() > 1;
+
+ GradleVariantConfiguration variantConfiguration = variantScope.getVariantConfiguration();
+ /**
+ * PrePackaging step class that will look if the packaging of the main APK split is
+ * necessary when running in InstantRun mode. In InstantRun mode targeting an api 23 or
+ * above device, resources are packaged in the main split APK. However when a warm swap is
+ * possible, it is not necessary to produce immediately the new main SPLIT since the runtime
+ * use the resources.ap_ file directly. However, as soon as an incompatible change forcing a
+ * cold swap is triggered, the main APK must be rebuilt (even if the resources were changed
+ * in a previous build).
+ */
+ AndroidTask<PrePackageApplication> prePackageApp = androidTasks.create(tasks,
+ new PrePackageApplication.ConfigAction("prePackageMarkerFor", variantScope));
+ IncrementalMode incrementalMode = getIncrementalMode(variantConfiguration);
+ if (incrementalMode != IncrementalMode.NONE) {
+ prePackageApp.dependsOn(tasks, variantScope.getInstantRunAnchorTask());
+ }
+
+ // loop on all outputs. The only difference will be the name of the task, and location
+ // of the generated data.
+ for (final ApkVariantOutputData variantOutputData : variantData.getOutputs()) {
+ VariantOutputScope variantOutputScope = variantOutputData.getScope();
+
+ final String outputName = variantOutputData.getFullName();
+ InstantRunPatchingPolicy instantRunPatchingPolicy =
+ variantScope.getInstantRunBuildContext().getPatchingPolicy();
+
+ // when building for instant run, never puts the user's code in the APK directly.
+ PackageApplication.DexPackagingPolicy dexPackagingPolicy =
+ incrementalMode == IncrementalMode.NONE
+ || variantScope.getInstantRunBuildContext().getPatchingPolicy()
+ == InstantRunPatchingPolicy.PRE_LOLLIPOP
+ ? PackageApplication.DexPackagingPolicy.STANDARD
+ : PackageApplication.DexPackagingPolicy.INSTANT_RUN;
+
+ AndroidTask<PackageApplication> packageApp = androidTasks.create(tasks,
+ new PackageApplication.ConfigAction(variantOutputScope, dexPackagingPolicy));
+
+ packageApp.dependsOn(tasks, prePackageApp, variantOutputScope.getProcessResourcesTask());
+
+ packageApp.optionalDependsOn(
+ tasks,
+ variantOutputScope.getShrinkResourcesTask(),
+ // TODO: When Jack is converted, add activeDexTask to VariantScope.
+ variantOutputScope.getVariantScope().getJavaCompilerTask(),
+ // TODO: Remove when Jack is converted to AndroidTask.
+ variantData.javaCompilerTask,
+ variantOutputData.packageSplitResourcesTask,
+ variantOutputData.packageSplitAbiTask);
+
+ TransformManager transformManager = variantScope.getTransformManager();
+
+ for (TransformStream stream : transformManager
+ .getStreams(PackageApplication.sDexFilter)) {
+ // TODO Optimize to avoid creating too many actions
+ packageApp.dependsOn(tasks, stream.getDependencies());
+ }
+
+ for (TransformStream stream : transformManager.getStreams(
+ PackageApplication.sResFilter)) {
+ // TODO Optimize to avoid creating too many actions
+ packageApp.dependsOn(tasks, stream.getDependencies());
+ }
+ for (TransformStream stream : transformManager.getStreams(
+ PackageApplication.sNativeLibsFilter)) {
+ // TODO Optimize to avoid creating too many actions
+ packageApp.dependsOn(tasks, stream.getDependencies());
+ }
+
+ AndroidTask appTask = packageApp;
+
+ if (signedApk) {
+ if (variantData.getZipAlignEnabled()) {
+ AndroidTask<ZipAlign> zipAlignTask = androidTasks.create(
+ tasks, new ZipAlign.ConfigAction(variantOutputScope));
+ zipAlignTask.dependsOn(tasks, packageApp);
+ if (variantOutputScope.getSplitZipAlignTask() != null) {
+ zipAlignTask.dependsOn(tasks, variantOutputScope.getSplitZipAlignTask());
+ }
+
+ appTask = zipAlignTask;
+ }
+
+ }
+
+ checkState(variantData.assembleVariantTask != null);
+ if (fullBuildInfoGeneratorTask != null) {
+ fullBuildInfoGeneratorTask.dependsOn(tasks, appTask);
+ variantData.assembleVariantTask.dependsOn(fullBuildInfoGeneratorTask.getName());
+ }
+
+ // when dealing with 23 and above, we should make sure the packaging task is running
+ // as part of the incremental build in case resources have changed and need to be
+ // repackaged in the main APK.
+ if (dexPackagingPolicy == PackageApplication.DexPackagingPolicy.INSTANT_RUN
+ && instantRunPatchingPolicy == InstantRunPatchingPolicy.MULTI_APK) {
+ variantScope.getInstantRunIncrementalTask().dependsOn(tasks, appTask);
+ }
+
+ // Add an assemble task
+ if (multiOutput) {
+ // create a task for this output
+ variantOutputData.assembleTask = createAssembleTask(variantOutputData);
+
+ // variant assemble task depends on each output assemble task.
+ variantData.assembleVariantTask.dependsOn(variantOutputData.assembleTask);
+ } else {
+ // single output
+ variantOutputData.assembleTask = variantData.assembleVariantTask;
+ }
+
+ if (!signedApk && variantOutputData.packageSplitResourcesTask != null) {
+ // in case we are not signing the resulting APKs and we have some pure splits
+ // we should manually copy them from the intermediate location to the final
+ // apk location unmodified.
+ final String appTaskName = appTask.getName();
+ AndroidTask<Copy> copySplitTask = androidTasks.create(
+ tasks,
+ variantOutputScope.getTaskName("copySplit"),
+ Copy.class,
+ new Action<Copy>() {
+ @Override
+ public void execute(Copy copyTask) {
+ copyTask.setDestinationDir(apkLocation);
+ copyTask.from(variantOutputData.packageSplitResourcesTask.getOutputDirectory());
+ variantOutputData.assembleTask.dependsOn(copyTask);
+ copyTask.mustRunAfter(appTaskName);
+ }
+ });
+ variantOutputData.assembleTask.dependsOn(copySplitTask.getName());
+ }
+ variantOutputData.assembleTask.dependsOn(appTask.getName());
+
+ if (publishApk) {
+ final String projectBaseName = globalScope.getProjectBaseName();
+
+ // if this variant is the default publish config or we also should publish non
+ // defaults, proceed with declaring our artifacts.
+ if (getExtension().getDefaultPublishConfig().equals(outputName)) {
+ appTask.configure(tasks, new Action<Task>() {
+ @Override
+ public void execute(Task packageTask) {
+ project.getArtifacts().add("default",
+ new ApkPublishArtifact(projectBaseName,
+ null,
+ (FileSupplier) packageTask));
+ }
+
+ });
+
+ for (FileSupplier outputFileProvider :
+ variantOutputData.getSplitOutputFileSuppliers()) {
+ project.getArtifacts().add("default",
+ new ApkPublishArtifact(projectBaseName, null, outputFileProvider));
+ }
+
+ try {
+ if (variantOutputData.getMetadataFile() != null) {
+ project.getArtifacts().add("default-metadata",
+ new MetadataPublishArtifact(projectBaseName, null,
+ variantOutputData.getMetadataFile()));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (variantData.getMappingFileProvider() != null) {
+ project.getArtifacts().add("default-mapping",
+ new MappingPublishArtifact(projectBaseName, null,
+ variantData.getMappingFileProvider()));
+ }
+ }
+
+ if (getExtension().getPublishNonDefault()) {
+ appTask.configure(tasks, new Action<Task>() {
+ @Override
+ public void execute(Task packageTask) {
+ project.getArtifacts().add(
+ variantData.getVariantDependency().getPublishConfiguration().getName(),
+ new ApkPublishArtifact(
+ projectBaseName,
+ null,
+ (FileSupplier) packageTask));
+ }
+
+ });
+
+ for (FileSupplier outputFileProvider :
+ variantOutputData.getSplitOutputFileSuppliers()) {
+ project.getArtifacts().add(
+ variantData.getVariantDependency().getPublishConfiguration().getName(),
+ new ApkPublishArtifact(
+ projectBaseName,
+ null,
+ outputFileProvider));
+ }
+
+ try {
+ if (variantOutputData.getMetadataFile() != null) {
+ project.getArtifacts().add(
+ variantData.getVariantDependency().getMetadataConfiguration().getName(),
+ new MetadataPublishArtifact(
+ projectBaseName,
+ null,
+ variantOutputData.getMetadataFile()));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (variantData.getMappingFileProvider() != null) {
+ project.getArtifacts().add(
+ variantData.getVariantDependency().getMappingConfiguration().getName(),
+ new MappingPublishArtifact(
+ projectBaseName,
+ null,
+ variantData.getMappingFileProvider()));
+ }
+
+ if (variantData.classesJarTask != null) {
+ project.getArtifacts().add(
+ variantData.getVariantDependency().getClassesConfiguration().getName(),
+ variantData.classesJarTask);
+ }
+ }
+ }
+ }
+
+
+ // create install task for the variant Data. This will deal with finding the
+ // right output if there are more than one.
+ // Add a task to install the application package
+ if (signedApk) {
+ AndroidTask<InstallVariantTask> installTask = androidTasks.create(
+ tasks, new InstallVariantTask.ConfigAction(variantScope));
+ installTask.dependsOn(tasks, variantData.assembleVariantTask);
+ }
+
+ if (getExtension().getLintOptions().isCheckReleaseBuilds()
+ && incrementalMode == IncrementalMode.NONE) {
+ createLintVitalTask(variantData);
+ }
+
+ // add an uninstall task
+ final AndroidTask<UninstallTask> uninstallTask = androidTasks.create(
+ tasks, new UninstallTask.ConfigAction(variantScope));
+
+ tasks.named(UNINSTALL_ALL, new Action<Task>() {
+ @Override
+ public void execute(Task it) {
+ it.dependsOn(uninstallTask.getName());
+ }
+ });
+ }
+
+ public Task createAssembleTask(@NonNull final BaseVariantOutputData variantOutputData) {
+ Task assembleTask =
+ project.getTasks().create(variantOutputData.getScope().getTaskName("assemble"));
+ return assembleTask;
+ }
+
+ public Task createAssembleTask(TaskFactory tasks,
+ @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ Task assembleTask =
+ project.getTasks().create(variantData.getScope().getTaskName("assemble"));
+ return assembleTask;
+ }
+
+ public Copy getJacocoAgentTask() {
+ if (jacocoAgentTask == null) {
+ jacocoAgentTask = project.getTasks().create("unzipJacocoAgent", Copy.class);
+ jacocoAgentTask.from(new Callable<List<FileTree>>() {
+ @Override
+ public List<FileTree> call() throws Exception {
+ return Lists.newArrayList(Iterables.transform(
+ project.getConfigurations().getByName(
+ JacocoPlugin.AGENT_CONFIGURATION_NAME),
+ new Function<Object, FileTree>() {
+ @Override
+ public FileTree apply(@Nullable Object it) {
+ return project.zipTree(it);
+ }
+ }));
+ }
+ });
+ jacocoAgentTask.include(FILE_JACOCO_AGENT);
+ jacocoAgentTask.into(new File(getGlobalScope().getIntermediatesDir(), "jacoco"));
+ }
+
+ return jacocoAgentTask;
+ }
+
+ /**
+ * creates a zip align. This does not use convention mapping, and is meant to let other plugin
+ * create zip align tasks.
+ *
+ * @param name the name of the task
+ * @param buildContext the InstantRun build context
+ * @param inputFile the input file
+ * @param outputFile the output file
+ * @return the task
+ */
+ @NonNull
+ public ZipAlign createZipAlignTask(
+ @NonNull String name,
+ @NonNull InstantRunBuildContext buildContext,
+ @NonNull File inputFile,
+ @NonNull File outputFile) {
+ // Add a task to zip align application package
+ ZipAlign zipAlignTask = project.getTasks().create(name, ZipAlign.class);
+
+ zipAlignTask.setInputFile(inputFile);
+ zipAlignTask.setOutputFile(outputFile);
+ zipAlignTask.setInstantRunBuildContext(buildContext);
+ ConventionMappingHelper.map(zipAlignTask, "zipAlignExe", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final TargetInfo info = androidBuilder.getTargetInfo();
+ if (info != null) {
+ String path = info.getBuildTools().getPath(ZIP_ALIGN);
+ if (path != null) {
+ return new File(path);
+ }
+ }
+
+ return null;
+ }
+ });
+
+ return zipAlignTask;
+ }
+
+ protected void createMinifyTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull final VariantScope variantScope,
+ boolean createJarFile) {
+ doCreateMinifyTransform(taskFactory,
+ variantScope,
+ null /*mappingConfiguration*/, // No mapping in non-test modules.
+ createJarFile);
+ }
+
+ /**
+ * Actually creates the minify transform, using the given mapping configuration. The mapping is
+ * only used by test-only modules.
+ */
+ protected final void doCreateMinifyTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull final VariantScope variantScope,
+ @Nullable Configuration mappingConfiguration,
+ boolean createJarFile) {
+ if (variantScope.getVariantData().getVariantConfiguration().getBuildType().isUseProguard()) {
+ if (getIncrementalMode(variantScope.getVariantConfiguration()) != IncrementalMode.NONE) {
+ logger.warn("Instant Run: Proguard is not compatible with instant run. "
+ + "It has been disabled for {}",
+ variantScope.getVariantConfiguration().getFullName());
+ return;
+ }
+ createProguardTransform(taskFactory, variantScope, mappingConfiguration, createJarFile);
+ createShrinkResourcesTransform(taskFactory, variantScope);
+ } else {
+ // Since the built-in class shrinker does not obfuscate, there's no point running
+ // it on the test APK (it also doesn't have a -dontshrink mode).
+ if (variantScope.getTestedVariantData() == null) {
+ createNewShrinkerTransform(variantScope, taskFactory);
+ createShrinkResourcesTransform(taskFactory, variantScope);
+ }
+ }
+ }
+
+ private void createNewShrinkerTransform(VariantScope scope, TaskFactory taskFactory) {
+ NewShrinkerTransform transform = new NewShrinkerTransform(scope);
+ addProguardConfigFiles(transform, scope.getVariantData());
+
+ if (scope.getVariantConfiguration().isTestCoverageEnabled()) {
+ addJacocoShrinkerFlags(transform);
+ }
+
+ if (getIncrementalMode(scope.getVariantConfiguration()) != IncrementalMode.NONE) {
+ //TODO: This is currently overly broad, as finding the actual application class
+ // requires manually parsing the manifest (See CreateManifestKeepList)
+ transform.keep("class ** extends android.app.Application {*;}");
+ transform.keep("class com.android.tools.fd.** {*;}");
+ }
+
+ scope.getTransformManager().addTransform(taskFactory, scope, transform);
+ }
+
+ private void createProguardTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull VariantScope variantScope,
+ @Nullable Configuration mappingConfiguration,
+ boolean createJarFile) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = variantScope
+ .getVariantData();
+ final GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
+ final BaseVariantData testedVariantData = variantScope.getTestedVariantData();
+
+ ProGuardTransform transform = new ProGuardTransform(variantScope, createJarFile);
+
+ if (testedVariantData != null) {
+ // Don't remove any code in tested app.
+ transform.dontshrink();
+ transform.dontoptimize();
+
+ // We can't call dontobfuscate, since that would make ProGuard ignore the mapping file.
+ transform.keep("class * {*;}");
+ transform.keep("interface * {*;}");
+ transform.keep("enum * {*;}");
+ transform.keepattributes();
+
+ // All -dontwarn rules for test dependencies should go in here:
+ transform.setConfigurationFiles(
+ Suppliers.ofInstance(
+ (Collection<File>)testedVariantData.getVariantConfiguration().getTestProguardFiles()));
+
+ // register the mapping file which may or may not exists (only exist if obfuscation)
+ // is enabled.
+ transform.applyTestedMapping(testedVariantData.getMappingFile());
+
+ } else {
+ if (variantConfig.isTestCoverageEnabled()) {
+ addJacocoShrinkerFlags(transform);
+ }
+
+ addProguardConfigFiles(transform, variantData);
+
+ if (mappingConfiguration != null) {
+ transform.applyTestedMapping(mappingConfiguration);
+ }
+ }
+
+ AndroidTask<?> task = variantScope.getTransformManager().addTransform(taskFactory,
+ variantScope, transform, new TransformTask.ConfigActionCallback<ProGuardTransform>() {
+ @Override
+ public void callback(
+ @NonNull final ProGuardTransform transform,
+ @NonNull final TransformTask task) {
+ variantData.mappingFileProviderTask = new FileSupplier() {
+ @NonNull
+ @Override
+ public Task getTask() {
+ return task;
+ }
+
+ @Override
+ public File get() {
+ return transform.getMappingFile();
+ }
+ };
+ }
+ });
+
+ if (mappingConfiguration != null) {
+ verifyNotNull(task);
+ task.dependsOn(taskFactory, mappingConfiguration);
+ }
+ }
+
+ private void createShrinkResourcesTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull VariantScope scope) {
+ if (!scope.getVariantConfiguration().getBuildType().isShrinkResources()) {
+ return;
+ }
+
+ if (getIncrementalMode(scope.getVariantConfiguration()) != IncrementalMode.NONE) {
+ logger.warn("Instant Run: Resource shrinker automatically disabled for {}",
+ scope.getVariantConfiguration().getFullName());
+ // Disabled for instant run, as shrinking is automatically disabled.
+ return;
+ }
+
+ if (!scope.getVariantConfiguration().getBuildType().isUseProguard()) {
+ throw new IllegalArgumentException(
+ "Build-in class shrinker and resource shrinking are not supported yet.");
+ }
+
+ // if resources are shrink, insert a no-op transform per variant output
+ // to transform the res package into a stripped res package
+ for (final BaseVariantOutputData variantOutputData : scope.getVariantData().getOutputs()) {
+ VariantOutputScope variantOutputScope = variantOutputData.getScope();
+
+ ShrinkResourcesTransform shrinkResTransform = new ShrinkResourcesTransform(
+ variantOutputData,
+ variantOutputScope.getProcessResourcePackageOutputFile(),
+ variantOutputScope.getCompressedResourceFile(),
+ androidBuilder,
+ logger);
+ AndroidTask<TransformTask> shrinkTask = scope.getTransformManager()
+ .addTransform(taskFactory, variantOutputScope, shrinkResTransform);
+ // need to record this task since the package task will not depend
+ // on it through the transform manager.
+ variantOutputScope.setShrinkResourcesTask(shrinkTask);
+ }
+ }
+
+ private static void addJacocoShrinkerFlags(ProguardConfigurable transform) {
+ // when collecting coverage, don't remove the JaCoCo runtime
+ transform.keep("class com.vladium.** {*;}");
+ transform.keep("class org.jacoco.** {*;}");
+ transform.keep("interface org.jacoco.** {*;}");
+ transform.dontwarn("org.jacoco.**");
+ }
+
+ private void addProguardConfigFiles(
+ ProguardConfigurable transform,
+ final BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ final GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
+ transform.setConfigurationFiles(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ Set<File> proguardFiles = variantConfig.getProguardFiles(
+ true,
+ Collections.singletonList(getDefaultProguardFile(
+ TaskManager.DEFAULT_PROGUARD_CONFIG_FILE)));
+
+ // use the first output when looking for the proguard rule output of
+ // the aapt task. The different outputs are not different in a way that
+ // makes this rule file different per output.
+ BaseVariantOutputData outputData = variantData.getOutputs().get(0);
+ proguardFiles.add(outputData.processResourcesTask.getProguardOutputFile());
+ return proguardFiles;
+ }
+ });
+ }
+
+ public void createReportTasks(
+ List<BaseVariantData<? extends BaseVariantOutputData>> variantDataList) {
+ DependencyReportTask dependencyReportTask =
+ project.getTasks().create("androidDependencies", DependencyReportTask.class);
+ dependencyReportTask.setDescription("Displays the Android dependencies of the project.");
+ dependencyReportTask.setVariants(variantDataList);
+ dependencyReportTask.setGroup(ANDROID_GROUP);
+
+ SigningReportTask signingReportTask =
+ project.getTasks().create("signingReport", SigningReportTask.class);
+ signingReportTask.setDescription("Displays the signing info for each variant.");
+ signingReportTask.setVariants(variantDataList);
+ signingReportTask.setGroup(ANDROID_GROUP);
+ }
+
+ public void createAnchorTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
+ createPreBuildTasks(tasks, scope);
+
+ // also create sourceGenTask
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ scope.setSourceGenTask(androidTasks.create(tasks,
+ scope.getTaskName("generate", "Sources"),
+ Task.class,
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ variantData.sourceGenTask = task;
+ }
+ }));
+ // and resGenTask
+ scope.setResourceGenTask(androidTasks.create(tasks,
+ scope.getTaskName("generate", "Resources"),
+ Task.class,
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ variantData.resourceGenTask = task;
+ }
+
+ }));
+
+ scope.setAssetGenTask(androidTasks.create(tasks,
+ scope.getTaskName("generate", "Assets"),
+ Task.class,
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ variantData.assetGenTask = task;
+ }
+ }));
+
+ if (!variantData.getType().isForTesting()
+ && variantData.getVariantConfiguration().getBuildType().isTestCoverageEnabled()) {
+ scope.setCoverageReportTask(androidTasks.create(tasks,
+ scope.getTaskName("create", "CoverageReport"),
+ Task.class,
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ task.setDescription(String.format(
+ "Creates test coverage reports for the %s variant.",
+ variantData.getName()));
+ }
+ }));
+ }
+
+ // and compile task
+ createCompileAnchorTask(tasks, scope);
+ }
+
+ private void createPreBuildTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ variantData.preBuildTask = project.getTasks().create(scope.getTaskName("pre", "Build"));
+ variantData.preBuildTask.dependsOn(MAIN_PREBUILD);
+
+ PrepareDependenciesTask prepareDependenciesTask = project.getTasks().create(
+ scope.getTaskName("prepare", "Dependencies"), PrepareDependenciesTask.class);
+
+ variantData.prepareDependenciesTask = prepareDependenciesTask;
+ prepareDependenciesTask.dependsOn(variantData.preBuildTask);
+
+ prepareDependenciesTask.setAndroidBuilder(androidBuilder);
+ prepareDependenciesTask.setVariantName(scope.getVariantConfiguration().getFullName());
+ prepareDependenciesTask.setVariant(variantData);
+
+ // for all libraries required by the configurations of this variant, make this task
+ // depend on all the tasks preparing these libraries.
+ VariantDependencies configurationDependencies = variantData.getVariantDependency();
+ prepareDependenciesTask.addChecker(configurationDependencies.getChecker());
+
+ for (LibraryDependencyImpl lib : configurationDependencies.getLibraries()) {
+ dependencyManager.addDependencyToPrepareTask(variantData, prepareDependenciesTask, lib);
+ }
+ }
+
+ private void createCompileAnchorTask(@NonNull TaskFactory tasks, @NonNull final VariantScope scope) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ scope.setCompileTask(androidTasks.create(tasks, new TaskConfigAction<Task>() {
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("compile", "Sources");
+ }
+
+ @NonNull
+ @Override
+ public Class<Task> getType() {
+ return Task.class;
+ }
+
+ @Override
+ public void execute(@NonNull Task task) {
+ variantData.compileTask = task;
+ variantData.compileTask.setGroup(BUILD_GROUP);
+ }
+ }));
+ variantData.assembleVariantTask.dependsOn(scope.getCompileTask().getName());
+ }
+
+ public void createCheckManifestTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ final String name = variantData.getVariantConfiguration().getFullName();
+ scope.setCheckManifestTask(androidTasks.create(tasks,
+ scope.getTaskName("check", "Manifest"),
+ CheckManifest.class,
+ new Action<CheckManifest>() {
+ @Override
+ public void execute(CheckManifest checkManifestTask) {
+ variantData.checkManifestTask = checkManifestTask;
+ checkManifestTask.setVariantName(name);
+ ConventionMappingHelper.map(checkManifestTask, "manifest",
+ new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return variantData.getVariantConfiguration()
+ .getDefaultSourceSet().getManifestFile();
+ }
+ });
+ }
+
+ }));
+ scope.getCheckManifestTask().dependsOn(tasks, variantData.preBuildTask);
+ variantData.prepareDependenciesTask.dependsOn(scope.getCheckManifestTask().getName());
+ }
+
+ public static void optionalDependsOn(@NonNull Task main, Task... dependencies) {
+ for (Task dependency : dependencies) {
+ if (dependency != null) {
+ main.dependsOn(dependency);
+ }
+
+ }
+
+ }
+
+ public static void optionalDependsOn(@NonNull Task main, @NonNull List<?> dependencies) {
+ for (Object dependency : dependencies) {
+ if (dependency != null) {
+ main.dependsOn(dependency);
+ }
+
+ }
+
+ }
+
+ @NonNull
+ private static List<ManifestDependencyImpl> getManifestDependencies(
+ List<LibraryDependency> libraries) {
+
+ List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size());
+
+ for (LibraryDependency lib : libraries) {
+ // get the dependencies
+ List<ManifestDependencyImpl> children = getManifestDependencies(lib.getDependencies());
+ list.add(new ManifestDependencyImpl(lib.getName(), lib.getManifest(), children));
+ }
+
+ return list;
+ }
+
+ @NonNull
+ protected Logger getLogger() {
+ return logger;
+ }
+
+ @NonNull
+ public AndroidTaskRegistry getAndroidTasks() {
+ return androidTasks;
+ }
+
+ private File getDefaultProguardFile(String name) {
+ File sdkDir = sdkHandler.getAndCheckSdkFolder();
+ return new File(sdkDir,
+ SdkConstants.FD_TOOLS + File.separatorChar + SdkConstants.FD_PROGUARD
+ + File.separatorChar + name);
+ }
+
+ public void addDataBindingDependenciesIfNecessary(DataBindingOptions options) {
+ if (!options.isEnabled()) {
+ return;
+ }
+ String version = Objects.firstNonNull(options.getVersion(),
+ dataBindingBuilder.getCompilerVersion());
+ project.getDependencies().add("compile", SdkConstants.DATA_BINDING_LIB_ARTIFACT + ":"
+ + dataBindingBuilder.getLibraryVersion(version));
+ project.getDependencies().add("compile", SdkConstants.DATA_BINDING_BASELIB_ARTIFACT + ":"
+ + dataBindingBuilder.getBaseLibraryVersion(version));
+ project.getDependencies().add("provided",
+ SdkConstants.DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT + ":" +
+ version);
+ if (options.getAddDefaultAdapters()) {
+ project.getDependencies()
+ .add("compile", SdkConstants.DATA_BINDING_ADAPTER_LIB_ARTIFACT + ":" +
+ dataBindingBuilder.getBaseAdaptersVersion(version));
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TestApplicationTaskManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TestApplicationTaskManager.java
new file mode 100644
index 0000000..98e0594
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/TestApplicationTaskManager.java
@@ -0,0 +1,144 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.TestAndroidConfig;
+import com.android.build.gradle.internal.scope.AndroidTask;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
+import com.android.build.gradle.internal.test.TestApplicationTestData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.core.VariantType;
+import com.android.builder.testing.ConnectedDeviceProvider;
+import com.android.builder.testing.TestData;
+import com.google.common.collect.ImmutableMap;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+import android.databinding.tool.DataBindingBuilder;
+/**
+ * TaskManager for standalone test application that lives in a separate module from the tested
+ * application.
+ */
+public class TestApplicationTaskManager extends ApplicationTaskManager {
+
+
+ public TestApplicationTaskManager(Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ super(project, androidBuilder, dataBindingBuilder, extension, sdkHandler, dependencyManager,
+ toolingRegistry);
+ }
+
+ @Override
+ public void createTasksForVariantData(@NonNull TaskFactory tasks,
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
+
+ super.createTasksForVariantData(tasks, variantData);
+
+ // create a new configuration with the target application coordinates.
+ // This is for the tested APK.
+ final Configuration testTarget = project.getConfigurations().create("testTarget");
+
+ DependencyHandler dependencyHandler = project.getDependencies();
+ TestAndroidConfig testExtension = (TestAndroidConfig) extension;
+ dependencyHandler.add("testTarget",
+ dependencyHandler.project(
+ ImmutableMap.of(
+ "path", testExtension.getTargetProjectPath(),
+ "configuration", testExtension.getTargetVariant())));
+
+ // and create the configuration for the project's metadata.
+ final Configuration testTargetMetadata = project.getConfigurations().create("testTargetMetadata");
+
+ dependencyHandler.add("testTargetMetadata", dependencyHandler.project(
+ ImmutableMap.of(
+ "path", testExtension.getTargetProjectPath(),
+ "configuration", testExtension.getTargetVariant() + "-metadata"
+ )));
+
+ TestData testData = new TestApplicationTestData(
+ variantData, testTarget, testTargetMetadata, androidBuilder);
+
+ // create the test connected check task.
+ AndroidTask<DeviceProviderInstrumentTestTask> instrumentTestTask =
+ getAndroidTasks().create(
+ tasks,
+ new DeviceProviderInstrumentTestTask.ConfigAction(
+ variantData.getScope(),
+ new ConnectedDeviceProvider(
+ sdkHandler.getSdkInfo().getAdb(),
+ getGlobalScope().getExtension().getAdbOptions().getTimeOutInMs(),
+ new LoggerWrapper(getLogger())),
+ testData) {
+ @NonNull
+ @Override
+ public String getName() {
+ return super.getName() + VariantType.ANDROID_TEST.getSuffix();
+ }
+ });
+
+ // make the test application connectedCheck depends on the configuration added above so
+ // we can retrieve its artifacts
+
+ instrumentTestTask.dependsOn(tasks,
+ testTarget,
+ testTargetMetadata,
+ variantData.assembleVariantTask);
+
+ Task connectedAndroidTest = tasks.named(BuilderConstants.CONNECTED + VariantType.ANDROID_TEST.getSuffix());
+ if (connectedAndroidTest != null) {
+ connectedAndroidTest.dependsOn(instrumentTestTask.getName());
+ }
+ }
+
+ @Override
+ protected void createMinifyTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull VariantScope variantScope,
+ boolean createJarFile) {
+
+ DependencyHandler dependencyHandler = project.getDependencies();
+ TestAndroidConfig testExtension = (TestAndroidConfig) extension;
+ Configuration testTargetMapping = project.getConfigurations().create("testTargetMapping");
+
+ dependencyHandler.add("testTargetMapping", dependencyHandler.project(
+ ImmutableMap.of(
+ "path", testExtension.getTargetProjectPath(),
+ "configuration", testExtension.getTargetVariant() + "-mapping"
+ )));
+
+ if (testTargetMapping.getFiles().isEmpty()
+ || variantScope.getVariantConfiguration().getProvidedOnlyJars().isEmpty()) {
+ return;
+ }
+
+ doCreateMinifyTransform(taskFactory, variantScope, testTargetMapping, false);
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantDimensionData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantDimensionData.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantDimensionData.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantDimensionData.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantManager.java
new file mode 100644
index 0000000..fff8b94
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantManager.java
@@ -0,0 +1,846 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import static com.android.builder.core.BuilderConstants.LINT;
+import static com.android.builder.core.VariantType.ANDROID_TEST;
+import static com.android.builder.core.VariantType.LIBRARY;
+import static com.android.builder.core.VariantType.UNIT_TEST;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.TestAndroidConfig;
+import com.android.build.gradle.TestedAndroidConfig;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
+import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
+import com.android.build.gradle.internal.api.VariantFilter;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.profile.SpanRecorders;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.build.gradle.internal.variant.TestedVariantData;
+import com.android.build.gradle.internal.variant.VariantFactory;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.VariantType;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+import com.android.utils.StringHelper;
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class to create, manage variants.
+ */
+public class VariantManager implements VariantModel {
+
+ private static final String MULTIDEX_VERSION = "1.0.1";
+
+ protected static final String COM_ANDROID_SUPPORT_MULTIDEX =
+ "com.android.support:multidex:" + MULTIDEX_VERSION;
+ protected static final String COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION =
+ "com.android.support:multidex-instrumentation:" + MULTIDEX_VERSION;
+
+ @NonNull
+ private final Project project;
+ @NonNull
+ private final AndroidBuilder androidBuilder;
+ @NonNull
+ private final AndroidConfig extension;
+ @NonNull
+ private final VariantFactory variantFactory;
+ @NonNull
+ private final TaskManager taskManager;
+ @NonNull
+ private final Instantiator instantiator;
+ @NonNull
+ private ProductFlavorData<CoreProductFlavor> defaultConfigData;
+ @NonNull
+ private final Map<String, BuildTypeData> buildTypes = Maps.newHashMap();
+ @NonNull
+ private final Map<String, ProductFlavorData<CoreProductFlavor>> productFlavors = Maps.newHashMap();
+ @NonNull
+ private final Map<String, SigningConfig> signingConfigs = Maps.newHashMap();
+
+ @NonNull
+ private final ReadOnlyObjectProvider readOnlyObjectProvider = new ReadOnlyObjectProvider();
+ @NonNull
+ private final VariantFilter variantFilter = new VariantFilter(readOnlyObjectProvider);
+
+ @NonNull
+ private final List<BaseVariantData<? extends BaseVariantOutputData>> variantDataList = Lists.newArrayList();
+ @Nullable
+ private SigningConfig signingOverride;
+
+ public VariantManager(
+ @NonNull Project project,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull AndroidConfig extension,
+ @NonNull VariantFactory variantFactory,
+ @NonNull TaskManager taskManager,
+ @NonNull Instantiator instantiator) {
+ this.extension = extension;
+ this.androidBuilder = androidBuilder;
+ this.project = project;
+ this.variantFactory = variantFactory;
+ this.taskManager = taskManager;
+ this.instantiator = instantiator;
+
+ DefaultAndroidSourceSet mainSourceSet =
+ (DefaultAndroidSourceSet) extension.getSourceSets().getByName(extension.getDefaultConfig().getName());
+
+ DefaultAndroidSourceSet androidTestSourceSet = null;
+ DefaultAndroidSourceSet unitTestSourceSet = null;
+ if (variantFactory.hasTestScope()) {
+ androidTestSourceSet =
+ (DefaultAndroidSourceSet) extension.getSourceSets()
+ .getByName(ANDROID_TEST.getPrefix());
+ unitTestSourceSet =
+ (DefaultAndroidSourceSet) extension.getSourceSets()
+ .getByName(UNIT_TEST.getPrefix());
+ }
+
+ defaultConfigData = new ProductFlavorData<CoreProductFlavor>(
+ extension.getDefaultConfig(), mainSourceSet,
+ androidTestSourceSet, unitTestSourceSet, project);
+ signingOverride = createSigningOverride();
+ }
+
+ @NonNull
+ @Override
+ public ProductFlavorData<CoreProductFlavor> getDefaultConfig() {
+ return defaultConfigData;
+ }
+
+ @Override
+ @NonNull
+ public Map<String, BuildTypeData> getBuildTypes() {
+ return buildTypes;
+ }
+
+ @Override
+ @NonNull
+ public Map<String, ProductFlavorData<CoreProductFlavor>> getProductFlavors() {
+ return productFlavors;
+ }
+
+ @Override
+ @NonNull
+ public Map<String, SigningConfig> getSigningConfigs() {
+ return signingConfigs;
+ }
+
+ public void addSigningConfig(@NonNull SigningConfig signingConfig) {
+ signingConfigs.put(signingConfig.getName(), signingConfig);
+ }
+
+ /**
+ * Adds new BuildType, creating a BuildTypeData, and the associated source set,
+ * and adding it to the map.
+ * @param buildType the build type.
+ */
+ public void addBuildType(@NonNull CoreBuildType buildType) {
+ String name = buildType.getName();
+ checkName(name, "BuildType");
+
+ if (productFlavors.containsKey(name)) {
+ throw new RuntimeException("BuildType names cannot collide with ProductFlavor names");
+ }
+
+ DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSets().maybeCreate(name);
+
+ DefaultAndroidSourceSet unitTestSourceSet = null;
+ if (variantFactory.hasTestScope()) {
+ unitTestSourceSet = (DefaultAndroidSourceSet) extension
+ .getSourceSets().maybeCreate(
+ computeSourceSetName(buildType.getName(), UNIT_TEST));
+ }
+
+ BuildTypeData buildTypeData = new BuildTypeData(
+ buildType, project, mainSourceSet, unitTestSourceSet);
+
+ buildTypes.put(name, buildTypeData);
+ }
+
+ /**
+ * Adds a new ProductFlavor, creating a ProductFlavorData and associated source sets,
+ * and adding it to the map.
+ *
+ * @param productFlavor the product flavor
+ */
+ public void addProductFlavor(@NonNull CoreProductFlavor productFlavor) {
+ String name = productFlavor.getName();
+ checkName(name, "ProductFlavor");
+
+ if (buildTypes.containsKey(name)) {
+ throw new RuntimeException("ProductFlavor names cannot collide with BuildType names");
+ }
+
+ DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSets().maybeCreate(
+ productFlavor.getName());
+
+ DefaultAndroidSourceSet androidTestSourceSet = null;
+ DefaultAndroidSourceSet unitTestSourceSet = null;
+ if (variantFactory.hasTestScope()) {
+ androidTestSourceSet = (DefaultAndroidSourceSet) extension
+ .getSourceSets().maybeCreate(
+ computeSourceSetName(productFlavor.getName(), ANDROID_TEST));
+ unitTestSourceSet = (DefaultAndroidSourceSet) extension
+ .getSourceSets().maybeCreate(
+ computeSourceSetName(productFlavor.getName(), UNIT_TEST));
+ }
+
+ ProductFlavorData<CoreProductFlavor> productFlavorData =
+ new ProductFlavorData<CoreProductFlavor>(
+ productFlavor,
+ mainSourceSet,
+ androidTestSourceSet,
+ unitTestSourceSet,
+ project);
+
+ productFlavors.put(productFlavor.getName(), productFlavorData);
+ }
+
+ /**
+ * Return a list of all created VariantData.
+ */
+ @NonNull
+ public List<BaseVariantData<? extends BaseVariantOutputData>> getVariantDataList() {
+ return variantDataList;
+ }
+
+ /**
+ * Variant/Task creation entry point.
+ *
+ * Not used by gradle-experimental.
+ */
+ public void createAndroidTasks() {
+ variantFactory.validateModel(this);
+ variantFactory.preVariantWork(project);
+
+ final TaskFactory tasks = new TaskContainerAdaptor(project.getTasks());
+ if (variantDataList.isEmpty()) {
+ ThreadRecorder.get().record(ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ populateVariantDataList();
+ return null;
+ }
+ });
+ }
+
+ // Create top level test tasks.
+ ThreadRecorder.get().record(ExecutionType.VARIANT_MANAGER_CREATE_TESTS_TASKS,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ taskManager.createTopLevelTestTasks(tasks, !productFlavors.isEmpty());
+ return null;
+ }
+ });
+
+ for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {
+
+ SpanRecorders.record(project, ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createTasksForVariantData(tasks, variantData);
+ return null;
+ }
+ },
+ new Recorder.Property(SpanRecorders.VARIANT, variantData.getName()));
+ }
+
+ taskManager.createReportTasks(variantDataList);
+ }
+
+ /**
+ * Create assemble task for VariantData.
+ */
+ private void createAssembleTaskForVariantData(
+ TaskFactory tasks,
+ final BaseVariantData<?> variantData) {
+ if (variantData.getType().isForTesting()) {
+ variantData.assembleVariantTask = taskManager.createAssembleTask(tasks, variantData);
+ } else {
+ BuildTypeData buildTypeData =
+ buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName());
+
+ if (productFlavors.isEmpty()) {
+ // Reuse assemble task for build type if there is no product flavor.
+ variantData.assembleVariantTask = buildTypeData.getAssembleTask();
+ } else {
+ variantData.assembleVariantTask = taskManager.createAssembleTask(tasks, variantData);
+
+ // setup the task dependencies
+ // build type
+ buildTypeData.getAssembleTask().dependsOn(variantData.assembleVariantTask);
+
+ // each flavor
+ GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
+ for (CoreProductFlavor flavor : variantConfig.getProductFlavors()) {
+ productFlavors.get(flavor.getName()).getAssembleTask()
+ .dependsOn(variantData.assembleVariantTask);
+ }
+
+ // assembleTask for this flavor(dimension), created on demand if needed.
+ if (variantConfig.getProductFlavors().size() > 1) {
+ final String name = StringHelper.capitalize(variantConfig.getFlavorName());
+ final String variantAssembleTaskName = "assemble" + name;
+ if (!tasks.containsKey(variantAssembleTaskName)) {
+ tasks.create(variantAssembleTaskName, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.setDescription(
+ "Assembles all builds for flavor combination: " + name);
+ task.setGroup("Build");
+ task.dependsOn(variantData.assembleVariantTask);
+
+ }
+ });
+ }
+ tasks.named("assemble", new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.dependsOn(variantAssembleTaskName);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Create tasks for the specified variantData.
+ */
+ public void createTasksForVariantData(
+ final TaskFactory tasks,
+ final BaseVariantData<? extends BaseVariantOutputData> variantData) {
+
+ // Add dependency of assemble task on assemble build type task.
+ tasks.named("assemble", new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ BuildTypeData buildTypeData = buildTypes.get(
+ variantData.getVariantConfiguration().getBuildType().getName());
+ task.dependsOn(buildTypeData.getAssembleTask());
+ }
+ });
+
+
+ VariantType variantType = variantData.getType();
+
+ createAssembleTaskForVariantData(tasks, variantData);
+ if (variantType.isForTesting()) {
+ final GradleVariantConfiguration testVariantConfig = variantData.getVariantConfiguration();
+ final BaseVariantData testedVariantData = (BaseVariantData) ((TestVariantData) variantData)
+ .getTestedVariantData();
+
+ // Add the container of dependencies, the order of the libraries is important.
+ // In descending order: build type (only for unit test), flavors, defaultConfig.
+ List<ConfigurationProvider> testVariantProviders = Lists.newArrayListWithExpectedSize(
+ 2 + testVariantConfig.getProductFlavors().size());
+
+ ConfigurationProvider buildTypeConfigurationProvider =
+ buildTypes.get(testVariantConfig.getBuildType().getName())
+ .getTestConfigurationProvider(variantType);
+ if (buildTypeConfigurationProvider != null) {
+ testVariantProviders.add(buildTypeConfigurationProvider);
+ }
+
+ for (CoreProductFlavor productFlavor : testVariantConfig.getProductFlavors()) {
+ ProductFlavorData<CoreProductFlavor> data =
+ productFlavors.get(productFlavor.getName());
+ testVariantProviders.add(data.getTestConfigurationProvider(variantType));
+ }
+
+ // now add the default config
+ testVariantProviders.add(defaultConfigData.getTestConfigurationProvider(variantType));
+
+ assert(testVariantConfig.getTestedConfig() != null);
+ VariantDependencies parentVariant = null;
+ if (testVariantConfig.getTestedConfig().getType() == VariantType.LIBRARY) {
+ parentVariant = testedVariantData.getVariantDependency();
+ }
+
+ // If the variant being tested is a library variant, VariantDependencies must be
+ // computed after the tasks for the tested variant is created. Therefore, the
+ // VariantDependencies is computed here instead of when the VariantData was created.
+ final VariantDependencies variantDep = VariantDependencies.compute(
+ project, testVariantConfig.getFullName(),
+ false /*publishVariant*/,
+ variantType,
+ parentVariant,
+ testVariantProviders.toArray(
+ new ConfigurationProvider[testVariantProviders.size()]));
+ variantData.setVariantDependency(variantDep);
+
+ if (variantType == VariantType.ANDROID_TEST &&
+ testVariantConfig.isMultiDexEnabled() &&
+ testVariantConfig.isLegacyMultiDexMode()) {
+ project.getDependencies().add(
+ variantDep.getCompileConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION);
+ project.getDependencies().add(
+ variantDep.getPackageConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION);
+ }
+
+ SpanRecorders.record(project, ExecutionType.RESOLVE_DEPENDENCIES,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ taskManager.resolveDependencies(variantDep,
+ testVariantConfig.getTestedConfig().getType() == VariantType.LIBRARY
+ ? null
+ : testedVariantData.getVariantDependency(),
+ null /*testedProjectPath*/);
+ return null;
+ }
+ },
+ new Recorder.Property(SpanRecorders.VARIANT, testVariantConfig.getFullName()));
+ testVariantConfig.setDependencies(variantDep);
+ switch (variantType) {
+ case ANDROID_TEST:
+ taskManager.createAndroidTestVariantTasks(tasks, (TestVariantData) variantData);
+ break;
+ case UNIT_TEST:
+ taskManager.createUnitTestVariantTasks(tasks, (TestVariantData) variantData);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown test type " + variantType);
+ }
+ } else {
+ taskManager.createTasksForVariantData(tasks, variantData);
+ }
+ }
+
+ /**
+ * Create all variants.
+ */
+ public void populateVariantDataList() {
+ if (productFlavors.isEmpty()) {
+ createVariantDataForProductFlavors(Collections.<ProductFlavor>emptyList());
+ } else {
+ List<String> flavorDimensionList = extension.getFlavorDimensionList();
+
+ // Create iterable to get GradleProductFlavor from ProductFlavorData.
+ Iterable<CoreProductFlavor> flavorDsl =
+ Iterables.transform(
+ productFlavors.values(),
+ new Function<ProductFlavorData<CoreProductFlavor>, CoreProductFlavor>() {
+ @Override
+ public CoreProductFlavor apply(
+ ProductFlavorData<CoreProductFlavor> data) {
+ return data.getProductFlavor();
+ }
+ });
+
+ // Get a list of all combinations of product flavors.
+ List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =
+ ProductFlavorCombo.createCombinations(
+ flavorDimensionList,
+ flavorDsl);
+
+ for (ProductFlavorCombo<CoreProductFlavor> flavorCombo : flavorComboList) {
+ //noinspection unchecked
+ createVariantDataForProductFlavors(
+ (List<ProductFlavor>) (List) flavorCombo.getFlavorList());
+ }
+ }
+ }
+
+ /**
+ * Create a VariantData for a specific combination of BuildType and ProductFlavor list.
+ */
+ public BaseVariantData<? extends BaseVariantOutputData> createVariantData(
+ @NonNull com.android.builder.model.BuildType buildType,
+ @NonNull List<? extends ProductFlavor> productFlavorList) {
+ BuildTypeData buildTypeData = buildTypes.get(buildType.getName());
+
+ GradleVariantConfiguration variantConfig = new GradleVariantConfiguration(
+ defaultConfigData.getProductFlavor(),
+ defaultConfigData.getSourceSet(),
+ buildTypeData.getBuildType(),
+ buildTypeData.getSourceSet(),
+ variantFactory.getVariantConfigurationType(),
+ signingOverride);
+
+ if (variantConfig.getType() == LIBRARY && variantConfig.getUseJack()) {
+ project.getLogger().warn(
+ "{}, {}: Jack compiler is not supported in library projects, falling back to javac.",
+ project.getPath(),
+ variantConfig.getFullName());
+ }
+
+ // sourceSetContainer in case we are creating variant specific sourceSets.
+ NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer = extension
+ .getSourceSets();
+
+ // We must first add the flavors to the variant config, in order to get the proper
+ // variant-specific and multi-flavor name as we add/create the variant providers later.
+ for (ProductFlavor productFlavor : productFlavorList) {
+ ProductFlavorData<CoreProductFlavor> data = productFlavors.get(
+ productFlavor.getName());
+
+ String dimensionName = productFlavor.getDimension();
+ if (dimensionName == null) {
+ dimensionName = "";
+ }
+
+ variantConfig.addProductFlavor(
+ data.getProductFlavor(),
+ data.getSourceSet(),
+ dimensionName);
+ }
+
+ createCompoundSourceSets(productFlavorList, variantConfig, sourceSetsContainer);
+
+ // Add the container of dependencies.
+ // The order of the libraries is important, in descending order:
+ // variant-specific, build type, multi-flavor, flavor1, flavor2, ..., defaultConfig.
+ // variant-specific if the full combo of flavors+build type. Does not exist if no flavors.
+ // multi-flavor is the combination of all flavor dimensions. Does not exist if <2 dimension.
+ final List<ConfigurationProvider> variantProviders =
+ Lists.newArrayListWithExpectedSize(productFlavorList.size() + 4);
+
+ // 1. add the variant-specific if applicable.
+ if (!productFlavorList.isEmpty()) {
+ variantProviders.add(
+ new ConfigurationProviderImpl(
+ project,
+ (DefaultAndroidSourceSet) variantConfig.getVariantSourceProvider()));
+ }
+
+ // 2. the build type.
+ variantProviders.add(buildTypeData.getMainProvider());
+
+ // 3. the multi-flavor combination
+ if (productFlavorList.size() > 1) {
+ variantProviders.add(
+ new ConfigurationProviderImpl(
+ project,
+ (DefaultAndroidSourceSet) variantConfig.getMultiFlavorSourceProvider()));
+ }
+
+ // 4. the flavors.
+ for (ProductFlavor productFlavor : productFlavorList) {
+ variantProviders.add(productFlavors.get(productFlavor.getName()).getMainProvider());
+ }
+
+ // 5. The defaultConfig
+ variantProviders.add(defaultConfigData.getMainProvider());
+
+ // Done. Create the variant and get its internal storage object.
+ BaseVariantData<?> variantData =
+ variantFactory.createVariantData(variantConfig, taskManager);
+
+ final VariantDependencies variantDep = VariantDependencies.compute(
+ project, variantConfig.getFullName(),
+ isVariantPublished(),
+ variantData.getType(),
+ null,
+ variantProviders.toArray(new ConfigurationProvider[variantProviders.size()]));
+ variantData.setVariantDependency(variantDep);
+
+ if (variantConfig.isMultiDexEnabled() && variantConfig.isLegacyMultiDexMode()) {
+ project.getDependencies().add(
+ variantDep.getCompileConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
+ project.getDependencies().add(
+ variantDep.getPackageConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
+ }
+
+ final String testedProjectPath = extension instanceof TestAndroidConfig ?
+ ((TestAndroidConfig) extension).getTargetProjectPath() :
+ null;
+
+ SpanRecorders.record(project, ExecutionType.RESOLVE_DEPENDENCIES,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() {
+ taskManager.resolveDependencies(
+ variantDep,
+ null /*testedVariantDeps*/,
+ testedProjectPath);
+ return null;
+ }
+ }, new Recorder.Property(SpanRecorders.VARIANT, variantConfig.getFullName()));
+
+ variantConfig.setDependencies(variantDep);
+
+ return variantData;
+ }
+
+ private static void createCompoundSourceSets(
+ @NonNull List<? extends ProductFlavor> productFlavorList,
+ GradleVariantConfiguration variantConfig,
+ NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer) {
+ if (!productFlavorList.isEmpty() && !variantConfig.getType().isSingleBuildType()) {
+ DefaultAndroidSourceSet variantSourceSet =
+ (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
+ computeSourceSetName(
+ variantConfig.getFullName(),
+ variantConfig.getType()));
+ variantConfig.setVariantSourceProvider(variantSourceSet);
+ }
+
+ if (productFlavorList.size() > 1) {
+ DefaultAndroidSourceSet multiFlavorSourceSet =
+ (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
+ computeSourceSetName(
+ variantConfig.getFlavorName(),
+ variantConfig.getType()));
+ variantConfig.setMultiFlavorSourceProvider(multiFlavorSourceSet);
+ }
+ }
+
+ /**
+ * Turns a string into a valid source set name for the given {@link VariantType}, e.g.
+ * "fooBarUnitTest" becomes "testFooBar".
+ */
+ @NonNull
+ private static String computeSourceSetName(
+ @NonNull String name,
+ @NonNull VariantType variantType) {
+ if (name.endsWith(variantType.getSuffix())) {
+ name = name.substring(0, name.length() - variantType.getSuffix().length());
+ }
+
+ if (!variantType.getPrefix().isEmpty()) {
+ name = variantType.getPrefix() + StringHelper.capitalize(name);
+ }
+
+ return name;
+ }
+
+ /**
+ * Create a TestVariantData for the specified testedVariantData.
+ */
+ public TestVariantData createTestVariantData(
+ BaseVariantData testedVariantData,
+ VariantType type) {
+ CoreProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
+ CoreBuildType buildType = testedVariantData.getVariantConfiguration().getBuildType();
+ BuildTypeData buildTypeData = buildTypes.get(buildType.getName());
+
+ GradleVariantConfiguration testedConfig = testedVariantData.getVariantConfiguration();
+ List<? extends CoreProductFlavor> productFlavorList = testedConfig.getProductFlavors();
+
+ // handle test variant
+ // need a suppress warning because ProductFlavor.getTestSourceSet(type) is annotated
+ // to return @Nullable and the constructor is @NonNull on this parameter,
+ // but it's never the case on defaultConfigData
+ // The constructor does a runtime check on the instances so we should be safe.
+ @SuppressWarnings("ConstantConditions")
+ GradleVariantConfiguration testVariantConfig = new GradleVariantConfiguration(
+ testedVariantData.getVariantConfiguration(),
+ defaultConfig,
+ defaultConfigData.getTestSourceSet(type),
+ buildType,
+ buildTypeData.getTestSourceSet(type),
+ type,
+ signingOverride);
+
+
+ for (CoreProductFlavor productFlavor : productFlavorList) {
+ ProductFlavorData<CoreProductFlavor> data = productFlavors
+ .get(productFlavor.getName());
+
+ String dimensionName = productFlavor.getDimension();
+ if (dimensionName == null) {
+ dimensionName = "";
+ }
+ // same supress warning here.
+ //noinspection ConstantConditions
+ testVariantConfig.addProductFlavor(
+ data.getProductFlavor(),
+ data.getTestSourceSet(type),
+ dimensionName);
+ }
+
+ createCompoundSourceSets(
+ productFlavorList,
+ testVariantConfig,
+ extension.getSourceSets());
+
+ // create the internal storage for this variant.
+ TestVariantData testVariantData = new TestVariantData(
+ extension, taskManager,
+ testVariantConfig, (TestedVariantData) testedVariantData,
+ androidBuilder.getErrorReporter());
+ // link the testVariant to the tested variant in the other direction
+ ((TestedVariantData) testedVariantData).setTestVariantData(testVariantData, type);
+
+ return testVariantData;
+ }
+
+ /**
+ * Creates VariantData for a specified list of product flavor.
+ *
+ * This will create VariantData for all build types of the given flavors.
+ *
+ * @param productFlavorList the flavor(s) to build.
+ */
+ private void createVariantDataForProductFlavors(
+ @NonNull List<ProductFlavor> productFlavorList) {
+
+ BuildTypeData testBuildTypeData = null;
+ if (extension instanceof TestedAndroidConfig) {
+ TestedAndroidConfig testedExtension = (TestedAndroidConfig) extension;
+
+ testBuildTypeData = buildTypes.get(testedExtension.getTestBuildType());
+ if (testBuildTypeData == null) {
+ throw new RuntimeException(String.format(
+ "Test Build Type '%1$s' does not exist.", testedExtension.getTestBuildType()));
+ }
+ }
+
+ BaseVariantData variantForAndroidTest = null;
+
+ CoreProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
+
+ Action<com.android.build.gradle.api.VariantFilter> variantFilterAction =
+ extension.getVariantFilter();
+
+ for (BuildTypeData buildTypeData : buildTypes.values()) {
+ boolean ignore = false;
+ if (variantFilterAction != null) {
+ variantFilter.reset(defaultConfig, buildTypeData.getBuildType(), productFlavorList);
+ variantFilterAction.execute(variantFilter);
+ ignore = variantFilter.isIgnore();
+ }
+
+ if (!ignore) {
+ BaseVariantData<?> variantData = createVariantData(
+ buildTypeData.getBuildType(),
+ productFlavorList);
+ variantDataList.add(variantData);
+
+ GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
+ ThreadRecorder.get().record(
+ ExecutionType.VARIANT_CONFIG,
+ Recorder.EmptyBlock,
+ new Recorder.Property(
+ "project",
+ project.getName()),
+ new Recorder.Property(
+ "variant",
+ variantData.getName()),
+ new Recorder.Property(
+ "use_jack",
+ Boolean.toString(variantConfig.getUseJack())),
+ new Recorder.Property(
+ "use_minify",
+ Boolean.toString(variantConfig.isMinifyEnabled())),
+ new Recorder.Property(
+ "use_multi_dex",
+ Boolean.toString(variantConfig.isMultiDexEnabled())),
+ new Recorder.Property(
+ "multi_dex_legacy",
+ Boolean.toString(variantConfig.isLegacyMultiDexMode())));
+
+
+ if (variantFactory.hasTestScope()) {
+ TestVariantData unitTestVariantData = createTestVariantData(
+ variantData,
+ UNIT_TEST);
+ variantDataList.add(unitTestVariantData);
+
+ if (buildTypeData == testBuildTypeData) {
+ if (variantConfig.isMinifyEnabled() && variantConfig.getUseJack()) {
+ throw new RuntimeException(
+ "Cannot test obfuscated variants when compiling with jack.");
+ }
+ variantForAndroidTest = variantData;
+ }
+ }
+ }
+ }
+
+ if (variantForAndroidTest != null) {
+ TestVariantData androidTestVariantData = createTestVariantData(
+ variantForAndroidTest,
+ ANDROID_TEST);
+ variantDataList.add(androidTestVariantData);
+ }
+ }
+
+ private boolean isVariantPublished() {
+ return extension.getPublishNonDefault();
+ }
+
+ private static void checkName(@NonNull String name, @NonNull String displayName) {
+ checkPrefix(name, displayName, ANDROID_TEST.getPrefix());
+ checkPrefix(name, displayName, UNIT_TEST.getPrefix());
+
+ if (LINT.equals(name)) {
+ throw new RuntimeException(String.format(
+ "%1$s names cannot be %2$s", displayName, LINT));
+ }
+ }
+
+ private static void checkPrefix(String name, String displayName, String prefix) {
+ if (name.startsWith(prefix)) {
+ throw new RuntimeException(String.format(
+ "%1$s names cannot start with '%2$s'", displayName, prefix));
+ }
+ }
+
+ private SigningConfig createSigningOverride() {
+ AndroidGradleOptions.SigningOptions signingOptions =
+ AndroidGradleOptions.getSigningOptions(project);
+ if (signingOptions != null) {
+ com.android.build.gradle.internal.dsl.SigningConfig signingConfigDsl =
+ new com.android.build.gradle.internal.dsl.SigningConfig("externalOverride");
+
+ signingConfigDsl.setStoreFile(new File(signingOptions.storeFile));
+ signingConfigDsl.setStorePassword(signingOptions.storePassword);
+ signingConfigDsl.setKeyAlias(signingOptions.keyAlias);
+ signingConfigDsl.setKeyPassword(signingOptions.keyPassword);
+
+ if (signingOptions.storeType != null) {
+ signingConfigDsl.setStoreType(signingOptions.storeType);
+ }
+
+ return signingConfigDsl;
+ }
+ return null;
+ }
+
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantModel.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantModel.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantModel.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/VariantModel.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/annotations/ApkFile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/annotations/ApkFile.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/annotations/ApkFile.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/annotations/ApkFile.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java
new file mode 100644
index 0000000..2c5d5b6
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantImpl.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.api.ApkVariant;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.model.SigningConfig;
+
+import org.gradle.api.DefaultTask;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Implementation of the apk-generating variant.
+ *
+ * This is a wrapper around the internal data model, in order to control what is accessible
+ * through the external API.
+ */
+public abstract class ApkVariantImpl extends BaseVariantImpl implements ApkVariant {
+
+ protected ApkVariantImpl(@NonNull AndroidBuilder androidBuilder,
+ @NonNull ReadOnlyObjectProvider immutableObjectProvider) {
+ super(androidBuilder, immutableObjectProvider);
+ }
+
+ @NonNull
+ protected abstract ApkVariantData getApkVariantData();
+
+ @Override
+ @Nullable
+ public String getVersionName() {
+ return getApkVariantData().getVariantConfiguration().getVersionName();
+ }
+
+ @Override
+ public int getVersionCode() {
+ return getApkVariantData().getVariantConfiguration().getVersionCode();
+ }
+
+ @Nullable
+ @Override
+ public Object getDex() {
+ throw new RuntimeException("Access to the dex task is now impossible, starting with 1.4.0\n"
+ + "1.4.0 introduces a new Transform API allowing manipulation of the .class files.\n"
+ + "See more information: http://tools.android.com/tech-docs/new-build-system/transform-api");
+ }
+
+ @Override
+ public DefaultTask getUninstall() {
+ return getApkVariantData().uninstallTask;
+ }
+
+ @Override
+ public SigningConfig getSigningConfig() {
+ return readOnlyObjectProvider.getSigningConfig(
+ getApkVariantData().getVariantConfiguration().getSigningConfig());
+ }
+
+ @Override
+ public boolean isSigningReady() {
+ return getApkVariantData().isSigned();
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getCompileLibraries() {
+ return androidBuilder.getCompileClasspath(
+ getVariantData().getVariantConfiguration());
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getApkLibraries() {
+ return androidBuilder.getAllPackagedJars(getVariantData().getVariantConfiguration());
+ }
+
+ @Override
+ public DefaultTask getInstall() {
+ return getApkVariantData().installTask;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantOutputImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantOutputImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantOutputImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApkVariantOutputImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApplicationVariantImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApplicationVariantImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApplicationVariantImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ApplicationVariantImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java
new file mode 100644
index 0000000..1738ff8
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.api.BaseVariantOutput;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.MergeSourceSetFolders;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.NdkCompile;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SourceProvider;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Base class for variants.
+ *
+ * This is a wrapper around the internal data model, in order to control what is accessible
+ * through the external API.
+ */
+abstract class BaseVariantImpl implements BaseVariant {
+
+ @NonNull
+ protected AndroidBuilder androidBuilder;
+
+ @NonNull
+ protected ReadOnlyObjectProvider readOnlyObjectProvider;
+
+ protected List<BaseVariantOutput> outputs = Lists.newArrayListWithExpectedSize(1);
+
+ BaseVariantImpl(
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
+ this.androidBuilder = androidBuilder;
+ this.readOnlyObjectProvider = readOnlyObjectProvider;
+ }
+
+ @NonNull
+ protected abstract BaseVariantData<?> getVariantData();
+
+ public void addOutputs(@NonNull List<BaseVariantOutput> outputs) {
+ this.outputs.addAll(outputs);
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return getVariantData().getVariantConfiguration().getFullName();
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ return getVariantData().getDescription();
+ }
+
+ @Override
+ @NonNull
+ public String getDirName() {
+ return getVariantData().getVariantConfiguration().getDirName();
+ }
+
+ @Override
+ @NonNull
+ public String getBaseName() {
+ return getVariantData().getVariantConfiguration().getBaseName();
+ }
+
+ @NonNull
+ @Override
+ public String getFlavorName() {
+ return getVariantData().getVariantConfiguration().getFlavorName();
+ }
+
+ @NonNull
+ @Override
+ public List<BaseVariantOutput> getOutputs() {
+ return outputs;
+ }
+
+ @Override
+ @NonNull
+ public BuildType getBuildType() {
+ return readOnlyObjectProvider.getBuildType(
+ getVariantData().getVariantConfiguration().getBuildType());
+ }
+
+ @Override
+ @NonNull
+ public List<ProductFlavor> getProductFlavors() {
+ return new ImmutableFlavorList(
+ getVariantData().getVariantConfiguration().getProductFlavors(),
+ readOnlyObjectProvider);
+ }
+
+ @Override
+ @NonNull
+ public ProductFlavor getMergedFlavor() {
+ return getVariantData().getVariantConfiguration().getMergedFlavor();
+ }
+
+ @NonNull
+ @Override
+ public List<SourceProvider> getSourceSets() {
+ return getVariantData().getVariantConfiguration().getSortedSourceProviders();
+ }
+
+ @Override
+ @NonNull
+ public String getApplicationId() {
+ return getVariantData().getApplicationId();
+ }
+
+ @Override
+ @NonNull
+ public Task getPreBuild() {
+ return getVariantData().preBuildTask;
+ }
+
+ @Override
+ @NonNull
+ public Task getCheckManifest() {
+ return getVariantData().checkManifestTask;
+ }
+
+ @Override
+ @NonNull
+ public AidlCompile getAidlCompile() {
+ return getVariantData().aidlCompileTask;
+ }
+
+ @Override
+ @NonNull
+ public RenderscriptCompile getRenderscriptCompile() {
+ return getVariantData().renderscriptCompileTask;
+ }
+
+ @Override
+ public MergeResources getMergeResources() {
+ return getVariantData().mergeResourcesTask;
+ }
+
+ @Override
+ public MergeSourceSetFolders getMergeAssets() {
+ return getVariantData().mergeAssetsTask;
+ }
+
+ @Override
+ public GenerateBuildConfig getGenerateBuildConfig() {
+ return getVariantData().generateBuildConfigTask;
+ }
+
+ @Override
+ @Nullable
+ public JavaCompile getJavaCompile() {
+ return getVariantData().javacTask;
+ }
+
+ @NonNull
+ @Override
+ public AbstractCompile getJavaCompiler() {
+ return getVariantData().javaCompilerTask;
+ }
+
+ @NonNull
+ @Override
+ public NdkCompile getNdkCompile() {
+ return getVariantData().ndkCompileTask;
+ }
+
+ @Nullable
+ @Override
+ public Task getObfuscation() {
+ return getVariantData().obfuscationTask;
+ }
+
+ @Nullable
+ @Override
+ public File getMappingFile() {
+ return getVariantData().getMappingFile();
+ }
+
+ @Override
+ @NonNull
+ public Sync getProcessJavaResources() {
+ return getVariantData().processJavaResourcesTask;
+ }
+
+ @Override
+ @Nullable
+ public Task getAssemble() {
+ return getVariantData().assembleVariantTask;
+ }
+
+ @Override
+ public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
+ getVariantData().addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+
+ @Override
+ public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
+ getVariantData().addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+
+ @Override
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders) {
+ getVariantData().registerJavaGeneratingTask(task, sourceFolders);
+ }
+
+ @Override
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> sourceFolders) {
+ getVariantData().registerJavaGeneratingTask(task, sourceFolders);
+ }
+
+ @Override
+ public void registerResGeneratingTask(@NonNull Task task, @NonNull File... generatedResFolders) {
+ getVariantData().registerResGeneratingTask(task, generatedResFolders);
+ }
+
+ @Override
+ public void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedResFolders) {
+ getVariantData().registerResGeneratingTask(task, generatedResFolders);
+ }
+
+ @Override
+ public void buildConfigField(@NonNull String type, @NonNull String name,
+ @NonNull String value) {
+ getVariantData().getVariantConfiguration().addBuildConfigField(type, name, value);
+ }
+
+ @Override
+ public void resValue(@NonNull String type, @NonNull String name, @NonNull String value) {
+ getVariantData().getVariantConfiguration().addResValue(type, name, value);
+ }
+
+ @Override
+ public void setOutputsAreSigned(boolean isSigned) {
+ getVariantData().outputsAreSigned = isSigned;
+ }
+
+ @Override
+ public boolean getOutputsAreSigned() {
+ return getVariantData().outputsAreSigned;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantOutputImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantOutputImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantOutputImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantOutputImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java
new file mode 100644
index 0000000..9c448af
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceDirectorySet;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Project;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import groovy.lang.Closure;
+
+/**
+ * Default implementation of the AndroidSourceDirectorySet.
+ */
+public class DefaultAndroidSourceDirectorySet implements AndroidSourceDirectorySet {
+
+ private final String name;
+ private final Project project;
+ private List<Object> source = Lists.newArrayList();
+ private final PatternSet filter = new PatternSet();
+
+ public DefaultAndroidSourceDirectorySet(@NonNull String name,
+ @NonNull Project project) {
+ this.name = name;
+ this.project = project;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet srcDir(Object srcDir) {
+ source.add(srcDir);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet srcDirs(Object... srcDirs) {
+ Collections.addAll(source, srcDirs);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs) {
+ source.clear();
+ for (Object srcDir : srcDirs) {
+ source.add(srcDir);
+ }
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public FileTree getSourceFiles() {
+ FileTree src = null;
+ Set<File> sources = getSrcDirs();
+ if (!sources.isEmpty()) {
+ src = project.files(new ArrayList<Object>(sources)).getAsFileTree().matching(filter);
+ }
+ return src == null ? project.files().getAsFileTree() : src;
+ }
+
+ @Override
+ @NonNull
+ public List<ConfigurableFileTree> getSourceDirectoryTrees() {
+ List<ConfigurableFileTree> directoryTrees = Lists.newArrayListWithExpectedSize(source.size());
+ for (Object sourceDir: source) {
+ directoryTrees.add(project.fileTree(ImmutableMap.of(
+ "dir", sourceDir,
+ "includes", getIncludes(),
+ "excludes", getExcludes())));
+ }
+ return directoryTrees;
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getSrcDirs() {
+ return project.files(source.toArray()).getFiles();
+ }
+
+ @Override
+ @NonNull
+ public PatternFilterable getFilter() {
+ return filter;
+ }
+
+
+ @Override
+ @NonNull
+ public String toString() {
+ return source.toString();
+ }
+
+ @Override
+ public Set<String> getIncludes() {
+ return filter.getIncludes();
+ }
+
+ @Override
+ public Set<String> getExcludes() {
+ return filter.getExcludes();
+ }
+
+ @Override
+ public PatternFilterable setIncludes(Iterable<String> includes) {
+ filter.setIncludes(includes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable setExcludes(Iterable<String> excludes) {
+ filter.setExcludes(excludes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(String... includes) {
+ filter.include(includes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(Iterable<String> includes) {
+ filter.include(includes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(Spec<FileTreeElement> includeSpec) {
+ filter.include(includeSpec);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable include(Closure includeSpec) {
+ filter.include(includeSpec);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(Iterable<String> excludes) {
+ filter.exclude(excludes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(String... excludes) {
+ filter.exclude(excludes);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(Spec<FileTreeElement> excludeSpec) {
+ filter.exclude(excludeSpec);
+ return this;
+ }
+
+ @Override
+ public PatternFilterable exclude(Closure excludeSpec) {
+ filter.exclude(excludeSpec);
+ return this;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceFile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceFile.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceFile.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceFile.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceSet.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceSet.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceSet.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceSet.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ImmutableFlavorList.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ImmutableFlavorList.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ImmutableFlavorList.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ImmutableFlavorList.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantOutputImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantOutputImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantOutputImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantOutputImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBaseConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBaseConfig.java
new file mode 100644
index 0000000..742e86a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBaseConfig.java
@@ -0,0 +1,167 @@
+/*
+ * 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.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.BaseConfig;
+import com.android.builder.model.ClassField;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+
+import groovy.lang.GroovyObject;
+import groovy.lang.GroovyObjectSupport;
+import groovy.lang.MissingPropertyException;
+
+/**
+ * Read-only version of the BaseConfig wrapping another BaseConfig.
+ *
+ * <p>In the variant API, it is important that the objects returned by the variants are read-only.
+ *
+ * <p>However, even though the API is defined to use the base interfaces as return type (which all
+ * contain only getters), the dynamics of Groovy makes it easy to actually use the setters of the
+ * implementation classes.
+ *
+ * <p>This wrapper ensures that the returned instance is actually just a strict implementation of the
+ * base interface and is read-only.
+ */
+public abstract class ReadOnlyBaseConfig extends GroovyObjectSupport implements BaseConfig {
+
+ @NonNull
+ private BaseConfig baseConfig;
+
+ protected ReadOnlyBaseConfig(@NonNull BaseConfig baseConfig) {
+ this.baseConfig = baseConfig;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return baseConfig.getName();
+ }
+
+ @Nullable
+ @Override
+ public String getApplicationIdSuffix() {
+ return baseConfig.getApplicationIdSuffix();
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getBuildConfigFields() {
+ // TODO: cache immutable map?
+ return ImmutableMap.copyOf(baseConfig.getBuildConfigFields());
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getResValues() {
+ return ImmutableMap.copyOf(baseConfig.getResValues());
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getProguardFiles() {
+ return ImmutableList.copyOf(baseConfig.getProguardFiles());
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getConsumerProguardFiles() {
+ return ImmutableList.copyOf(baseConfig.getConsumerProguardFiles());
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getTestProguardFiles() {
+ return ImmutableList.copyOf(baseConfig.getTestProguardFiles());
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Object> getManifestPlaceholders() {
+ return ImmutableMap.copyOf(baseConfig.getManifestPlaceholders());
+ }
+
+ @Nullable
+ @Override
+ public Boolean getMultiDexEnabled() {
+ return baseConfig.getMultiDexEnabled();
+ }
+
+ @Nullable
+ @Override
+ public File getMultiDexKeepFile() {
+ return baseConfig.getMultiDexKeepFile();
+ }
+
+ @Nullable
+ @Override
+ public File getMultiDexKeepProguard() {
+ return baseConfig.getMultiDexKeepProguard();
+ }
+
+ /**
+ * Some build scripts add dynamic properties to flavors declaration (and others) and expect to
+ * retrieve such properties values through this model. Delegate any property we don't know about
+ * to the {@see BaseConfig} groovy object which hopefully will know about the dynamic property.
+ *
+ * @param name the property name
+ * @return the property value if exists or an exception will be thrown.
+ */
+ @SuppressWarnings("unused") // This is part of the Groovy method dispatch convention.
+ public Object propertyMissing(final String name) {
+ try {
+ return ((GroovyObject) baseConfig).getProperty(name);
+ } catch (MissingPropertyException e) {
+ // do not leak implementation types, replace the delegate with ourselves in the message
+ throw new MissingPropertyException("Could not find " + name + " on " + this);
+ }
+
+ }
+
+ /**
+ * Do not authorize setting dynamic properties values and provide a meaningful error message.
+ */
+ @SuppressWarnings("unused") // This is part of the Groovy method dispatch convention.
+ public void propertyMissing(String name, Object value) {
+ throw new RuntimeException(
+ String.format(
+ "Cannot set property %s on read-only %s.",
+ name,
+ baseConfig.getClass().getName()));
+ }
+
+ // This is part of the Groovy/Gradle method dispatch convention.
+ @SuppressWarnings({"unused"})
+ public boolean hasProperty(String name) {
+ if (DefaultGroovyMethods.hasProperty(this, name) != null) {
+ return true;
+ } else {
+ GroovyObject groovyObject = (GroovyObject) this.baseConfig;
+ // Object _Decorated by Gradle implement hasProperty, so the "usual" Groovy conventions
+ // don't apply.
+ return (Boolean) groovyObject.invokeMethod("hasProperty", name);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBuildType.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBuildType.java
new file mode 100644
index 0000000..6d4b647
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyBuildType.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.SigningConfig;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Read-only version of the BuildType wrapping another BuildType.
+ *
+ * In the variant API, it is important that the objects returned by the variants
+ * are read-only.
+ *
+ * However, even though the API is defined to use the base interfaces as return
+ * type (which all contain only getters), the dynamics of Groovy makes it easy to
+ * actually use the setters of the implementation classes.
+ *
+ * This wrapper ensures that the returned instance is actually just a strict implementation
+ * of the base interface and is read-only.
+ */
+public class ReadOnlyBuildType extends ReadOnlyBaseConfig implements BuildType {
+
+ @NonNull
+ private final BuildType buildType;
+
+ @NonNull
+ private final ReadOnlyObjectProvider readOnlyObjectProvider;
+
+ public ReadOnlyBuildType(
+ @NonNull BuildType buildType,
+ @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
+ super(buildType);
+ this.buildType = buildType;
+ this.readOnlyObjectProvider = readOnlyObjectProvider;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return buildType.isDebuggable();
+ }
+
+ @Override
+ public boolean isTestCoverageEnabled() {
+ return buildType.isTestCoverageEnabled();
+ }
+
+ @Override
+ public boolean isJniDebuggable() {
+ return buildType.isJniDebuggable();
+ }
+
+ @Override
+ public boolean isPseudoLocalesEnabled() {
+ return buildType.isPseudoLocalesEnabled();
+ }
+
+ @Override
+ public boolean isRenderscriptDebuggable() {
+ return buildType.isRenderscriptDebuggable();
+ }
+
+ @Override
+ public int getRenderscriptOptimLevel() {
+ return buildType.getRenderscriptOptimLevel();
+ }
+
+ @Nullable
+ @Override
+ public String getVersionNameSuffix() {
+ return buildType.getVersionNameSuffix();
+ }
+
+ @Override
+ public boolean isMinifyEnabled() {
+ return buildType.isMinifyEnabled();
+ }
+
+ @Override
+ public boolean isZipAlignEnabled() {
+ return buildType.isZipAlignEnabled();
+ }
+
+ @Override
+ public boolean isEmbedMicroApp() {
+ return buildType.isEmbedMicroApp();
+ }
+
+ @Nullable
+ @Override
+ public SigningConfig getSigningConfig() {
+ return readOnlyObjectProvider.getSigningConfig(buildType.getSigningConfig());
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJarJarRuleFiles() {
+ return buildType.getJarJarRuleFiles();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyObjectProvider.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyObjectProvider.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyObjectProvider.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyObjectProvider.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyProductFlavor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyProductFlavor.java
new file mode 100644
index 0000000..4442e66
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyProductFlavor.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.VectorDrawablesOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * read-only version of the ProductFlavor wrapping another ProductFlavor.
+ *
+ * In the variant API, it is important that the objects returned by the variants
+ * are read-only.
+ *
+ * However, even though the API is defined to use the base interfaces as return
+ * type (which all contain only getters), the dynamics of Groovy makes it easy to
+ * actually use the setters of the implementation classes.
+ *
+ * This wrapper ensures that the returned instance is actually just a strict implementation
+ * of the base interface and is read-only.
+ */
+public class ReadOnlyProductFlavor extends ReadOnlyBaseConfig implements ProductFlavor {
+
+ @NonNull
+ /*package*/ final ProductFlavor productFlavor;
+
+ @NonNull
+ private final ReadOnlyObjectProvider readOnlyObjectProvider;
+
+ ReadOnlyProductFlavor(
+ @NonNull ProductFlavor productFlavor,
+ @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
+ super(productFlavor);
+ this.productFlavor = productFlavor;
+ this.readOnlyObjectProvider = readOnlyObjectProvider;
+ }
+
+ @Nullable
+ @Override
+ public String getApplicationId() {
+ return productFlavor.getApplicationId();
+ }
+
+ @Nullable
+ @Override
+ public Integer getVersionCode() {
+ return productFlavor.getVersionCode();
+ }
+
+ @Nullable
+ @Override
+ public String getVersionName() {
+ return productFlavor.getVersionName();
+ }
+
+ @Nullable
+ @Override
+ public ApiVersion getMinSdkVersion() {
+ return productFlavor.getMinSdkVersion();
+ }
+
+ @Nullable
+ @Override
+ public ApiVersion getTargetSdkVersion() {
+ return productFlavor.getTargetSdkVersion();
+ }
+
+ @Nullable
+ @Override
+ public Integer getMaxSdkVersion() {
+ return productFlavor.getMaxSdkVersion();
+ }
+
+ @Nullable
+ @Override
+ public Integer getRenderscriptTargetApi() {
+ return productFlavor.getRenderscriptTargetApi();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getRenderscriptSupportModeEnabled() {
+ return productFlavor.getRenderscriptSupportModeEnabled();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getRenderscriptNdkModeEnabled() {
+ return productFlavor.getRenderscriptNdkModeEnabled();
+ }
+
+ @Nullable
+ @Override
+ public String getTestApplicationId() {
+ return productFlavor.getTestApplicationId();
+ }
+
+ @Nullable
+ @Override
+ public String getTestInstrumentationRunner() {
+ return productFlavor.getTestInstrumentationRunner();
+ }
+
+ @NonNull
+ @Override
+ public Map<String, String> getTestInstrumentationRunnerArguments() {
+ return ImmutableMap.copyOf(productFlavor.getTestInstrumentationRunnerArguments());
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestHandleProfiling() {
+ return productFlavor.getTestHandleProfiling();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestFunctionalTest() {
+ return productFlavor.getTestFunctionalTest();
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getResourceConfigurations() {
+ return ImmutableList.copyOf(productFlavor.getResourceConfigurations());
+ }
+
+ @Nullable
+ @Override
+ public SigningConfig getSigningConfig() {
+ return readOnlyObjectProvider.getSigningConfig(productFlavor.getSigningConfig());
+ }
+
+ @NonNull
+ @Override
+ public VectorDrawablesOptions getVectorDrawables() {
+ return new ReadOnlyVectorDrawablesOptions(productFlavor.getVectorDrawables());
+ }
+
+ @Nullable
+ @Override
+ public String getDimension() {
+ return productFlavor.getDimension();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJarJarRuleFiles() {
+ return ImmutableList.copyOf(productFlavor.getJarJarRuleFiles());
+ }
+
+ @Nullable
+ @Deprecated
+ public String getFlavorDimension() {
+ return productFlavor.getDimension();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlySigningConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlySigningConfig.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlySigningConfig.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlySigningConfig.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyVectorDrawablesOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyVectorDrawablesOptions.java
new file mode 100644
index 0000000..4333941
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/ReadOnlyVectorDrawablesOptions.java
@@ -0,0 +1,51 @@
+/*
+ * 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.build.gradle.internal.api;
+
+import com.android.annotations.Nullable;
+import com.android.builder.model.VectorDrawablesOptions;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Set;
+
+/**
+ * Read-only wrapper around another (@link VectorDrawablesOptions}.
+ */
+public class ReadOnlyVectorDrawablesOptions implements VectorDrawablesOptions {
+
+ private final VectorDrawablesOptions mOptions;
+
+ public ReadOnlyVectorDrawablesOptions(VectorDrawablesOptions options) {
+ mOptions = options;
+ }
+
+ @Nullable
+ @Override
+ public Set<String> getGeneratedDensities() {
+ if (mOptions.getGeneratedDensities() == null) {
+ return null;
+ } else {
+ return ImmutableSet.copyOf(mOptions.getGeneratedDensities());
+ }
+ }
+
+ @Nullable
+ @Override
+ public Boolean getUseSupportLibrary() {
+ return mOptions.getUseSupportLibrary();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestVariantImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestVariantImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestVariantImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestVariantImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestedVariant.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestedVariant.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestedVariant.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/TestedVariant.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/UnitTestVariantImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/UnitTestVariantImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/UnitTestVariantImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/UnitTestVariantImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/VariantFilter.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/VariantFilter.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/VariantFilter.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/api/VariantFilter.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Abi.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Abi.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Abi.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Abi.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/GradleVariantConfiguration.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/GradleVariantConfiguration.java
new file mode 100644
index 0000000..1fff8b0
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/GradleVariantConfiguration.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.core;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.core.VariantType;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.SourceProvider;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Version of {@link com.android.builder.core.VariantConfiguration} that uses the specific
+ * types used in the Gradle plugins.
+ *
+ * It also adds support for Ndk support that is not ready to go in the builder library.
+ */
+public class GradleVariantConfiguration extends VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> {
+
+ @Nullable
+ private Boolean enableInstantRunOverride = null;
+ private final MergedNdkConfig mMergedNdkConfig = new MergedNdkConfig();
+
+ /**
+ * Creates a {@link GradleVariantConfiguration} for a normal (non-test) variant.
+ */
+ public GradleVariantConfiguration(
+ @NonNull CoreProductFlavor defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull CoreBuildType buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull VariantType type,
+ @Nullable SigningConfig signingConfigOverride) {
+ super(defaultConfig, defaultSourceProvider, buildType, buildTypeSourceProvider, type,
+ signingConfigOverride);
+ computeNdkConfig();
+ }
+
+ /**
+ * Creates a {@link GradleVariantConfiguration} for a testing variant.
+ */
+ public GradleVariantConfiguration(
+ @Nullable VariantConfiguration testedConfig,
+ @NonNull CoreProductFlavor defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull CoreBuildType buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull VariantType type,
+ @Nullable SigningConfig signingConfigOverride) {
+ super(defaultConfig, defaultSourceProvider, buildType, buildTypeSourceProvider, type,
+ testedConfig, signingConfigOverride);
+ computeNdkConfig();
+ }
+
+ @NonNull
+ @Override
+ public VariantConfiguration addProductFlavor(
+ @NonNull CoreProductFlavor productFlavor,
+ @NonNull SourceProvider sourceProvider,
+ @NonNull String dimensionName) {
+ checkNotNull(productFlavor);
+ checkNotNull(sourceProvider);
+ checkNotNull(dimensionName);
+ super.addProductFlavor(productFlavor, sourceProvider, dimensionName);
+ computeNdkConfig();
+ return this;
+ }
+
+ @NonNull
+ public CoreNdkOptions getNdkConfig() {
+ return mMergedNdkConfig;
+ }
+
+ /**
+ * Returns the ABI filters associated with the artifact, or null if there are no filters.
+ *
+ * If the list contains values, then the artifact only contains these ABIs and excludes
+ * others.
+ */
+ @Nullable
+ public Set<String> getSupportedAbis() {
+ return mMergedNdkConfig.getAbiFilters();
+ }
+
+ /**
+ * Returns whether the configuration has minification enabled.
+ */
+ public boolean isMinifyEnabled() {
+ VariantType type = getType();
+ // if type == test then getTestedConfig always returns non-null
+ //noinspection ConstantConditions
+ return getBuildType().isMinifyEnabled() &&
+ (!type.isForTesting() || (getTestedConfig().getType() != VariantType.LIBRARY));
+ }
+
+ public boolean getUseJack() {
+ Boolean value = getBuildType().getUseJack();
+ if (value != null) {
+ return value;
+ }
+
+ // cant use merge flavor as useJack is not a prop on the base class.
+ for (CoreProductFlavor productFlavor : getProductFlavors()) {
+ value = productFlavor.getUseJack();
+ if (value != null) {
+ return value;
+ }
+ }
+
+ value = getDefaultConfig().getUseJack();
+ if (value != null) {
+ return value;
+ }
+
+ return false;
+ }
+
+ private void computeNdkConfig() {
+ mMergedNdkConfig.reset();
+
+ if (getDefaultConfig().getNdkConfig() != null) {
+ mMergedNdkConfig.append(getDefaultConfig().getNdkConfig());
+ }
+
+ final List<CoreProductFlavor> flavors = getProductFlavors();
+ for (int i = flavors.size() - 1 ; i >= 0 ; i--) {
+ CoreNdkOptions ndkConfig = flavors.get(i).getNdkConfig();
+ if (ndkConfig != null) {
+ mMergedNdkConfig.append(ndkConfig);
+ }
+ }
+
+ if (getBuildType().getNdkConfig() != null && !getType().isForTesting()) {
+ mMergedNdkConfig.append(getBuildType().getNdkConfig());
+ }
+ }
+
+ public boolean isInstantRunSupported() {
+ if (enableInstantRunOverride != null) {
+ return enableInstantRunOverride;
+ }
+ return getBuildType().isDebuggable()
+ && !getType().isForTesting()
+ && !getUseJack();
+ }
+
+ public void setEnableInstantRunOverride(@Nullable Boolean enableInstantRunOverride) {
+ this.enableInstantRunOverride = enableInstantRunOverride;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/MergedNdkConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/MergedNdkConfig.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/MergedNdkConfig.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/MergedNdkConfig.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Toolchain.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Toolchain.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Toolchain.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/core/Toolchain.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoOptions.java
new file mode 100644
index 0000000..17a3bd1
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoOptions.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.coverage;
+
+import org.gradle.api.logging.Logging;
+
+/**
+ * DSL object for configuring JaCoCo settings.
+ */
+public class JacocoOptions {
+
+ @SuppressWarnings("MethodMayBeStatic")
+ @Deprecated
+ public void setVersion(String version) {
+ Logging.getLogger(JacocoOptions.class).warn(""
+ + "It is no longer possible to set the Jacoco version in the "
+ + "jacoco {} block.\n"
+ + "To update the version of Jacoco without updating the android plugin,\n"
+ + "add a buildscript dependency on a newer version, for example: "
+ + "buildscript{"
+ + " dependencies {\n"
+ + " classpath\"org.jacoco:org.jacoco.core:0.7.4.201502262128\""
+ + " }"
+ + "}");
+ }
+
+
+ /**
+ * This will not affect the JaCoCo version used.
+ *
+ * @deprecated Use a build script dependency instead.
+ */
+ @SuppressWarnings("MethodMayBeStatic")
+ @Deprecated
+ public String getVersion() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.java
new file mode 100644
index 0000000..6098ca9
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoPlugin.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.coverage;
+import com.android.annotations.Nullable;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ResolvableDependencies;
+import org.gradle.api.artifacts.ResolvedArtifact;
+
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * Jacoco plugin. This is very similar to the built-in support for Jacoco but we dup it in order
+ * to control it as we need our own offline instrumentation.
+ *
+ * This may disappear if we can ever reuse the built-in support.
+ *
+ */
+public class JacocoPlugin implements Plugin<Project> {
+ public static final String ANT_CONFIGURATION_NAME = "androidJacocoAnt";
+ public static final String AGENT_CONFIGURATION_NAME = "androidJacocoAgent";
+
+ private Project project;
+
+ @Override
+ public void apply(Project project) {
+ this.project = project;
+ String jacocoVersion = getJacocoVersion();
+ addJacocoConfigurations();
+ configureAgentDependencies(jacocoVersion);
+ configureTaskClasspathDefaults(jacocoVersion);
+ }
+
+ /**
+ * Creates the configurations used by plugin.
+ */
+ private void addJacocoConfigurations() {
+ this.project.getConfigurations().create(AGENT_CONFIGURATION_NAME,
+ new Action<Configuration>() {
+ @Override
+ public void execute(Configuration files) {
+ files.setVisible(false);
+ files.setTransitive(true);
+ files.setDescription("The Jacoco agent to use to get coverage data.");
+ }
+ });
+ this.project.getConfigurations().create(ANT_CONFIGURATION_NAME,
+ new Action<Configuration>() {
+ @Override
+ public void execute(Configuration files) {
+ files.setVisible(false);
+ files.setTransitive(true);
+ files.setDescription(
+ "The Jacoco ant tasks to use to get execute Gradle tasks.");
+ }
+ });
+ }
+
+ @Nullable
+ private String getJacocoVersion() {
+ Project candidateProject = project;
+ boolean shouldFailWithException = false;
+
+ while (candidateProject != null) {
+ Set<ResolvedArtifact> resolvedArtifacts =
+ candidateProject.getBuildscript().getConfigurations().getByName("classpath")
+ .getResolvedConfiguration().getResolvedArtifacts();
+ for (ResolvedArtifact artifact : resolvedArtifacts) {
+ ModuleVersionIdentifier moduleVersion = artifact.getModuleVersion().getId();
+ if ("org.jacoco.core".equals(moduleVersion.getName())) {
+ return moduleVersion.getVersion();
+ }
+ }
+ if (!resolvedArtifacts.isEmpty()) {
+ // not in the DSL test case, where nothing will have been resolved.
+ shouldFailWithException = true;
+ }
+
+ candidateProject = candidateProject.getParent();
+ }
+
+ if (shouldFailWithException) {
+ throw new IllegalStateException(
+ "Could not find project build script dependency on org.jacoco.core");
+ }
+
+ project.getLogger().error(
+ "No resolved dependencies found when searching for the jacoco version.");
+ return null;
+
+ }
+
+ /**
+ * Configures the agent dependencies using the 'jacocoAnt' configuration.
+ * Uses the version declared as a build script dependency if no other versions are specified.
+ */
+ private void configureAgentDependencies(final String jacocoVersion) {
+ final Configuration config = project.getConfigurations().getByName(AGENT_CONFIGURATION_NAME);
+ config.getIncoming().beforeResolve(new Action<ResolvableDependencies>() {
+ @Override
+ public void execute(ResolvableDependencies resolvableDependencies) {
+ if (config.getDependencies().isEmpty()) {
+ config.getDependencies().add(project.getDependencies().create(
+ "org.jacoco:org.jacoco.agent:" + jacocoVersion));
+ }
+ }
+ });
+ }
+
+ /**
+ * Configures the classpath for Jacoco tasks using the 'jacocoAnt' configuration.
+ * Uses the version declared as a build script dependency if no other versions are specified.
+ */
+ private void configureTaskClasspathDefaults(final String jacocoVersion) {
+ final Configuration config = project.getConfigurations().getByName(ANT_CONFIGURATION_NAME);
+ config.getIncoming().beforeResolve(new Action<ResolvableDependencies>() {
+ @Override
+ public void execute(ResolvableDependencies resolvableDependencies) {
+ if (config.getDependencies().isEmpty()) {
+ config.getDependencies().add(project.getDependencies().create(
+ "org.jacoco:org.jacoco.ant:" + jacocoVersion));
+ }
+ }
+ });
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoReportTask.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoReportTask.groovy
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoReportTask.groovy
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/coverage/JacocoReportTask.groovy
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/DependencyChecker.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/DependencyChecker.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/DependencyChecker.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/DependencyChecker.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/JarInfo.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/JarInfo.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/JarInfo.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/JarInfo.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibInfo.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibInfo.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibInfo.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibInfo.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibraryDependencyImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibraryDependencyImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibraryDependencyImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/LibraryDependencyImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/ManifestDependencyImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/ManifestDependencyImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/ManifestDependencyImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/ManifestDependencyImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/SymbolFileProviderImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/SymbolFileProviderImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/SymbolFileProviderImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/SymbolFileProviderImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.java
new file mode 100644
index 0000000..e3e6869
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dependency;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.ConfigurationProvider;
+import com.android.builder.core.VariantType;
+import com.android.builder.dependency.DependencyContainer;
+import com.android.builder.dependency.JarDependency;
+import com.android.builder.dependency.LibraryDependency;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Object that represents the dependencies of a "config", in the sense of defaultConfigs, build
+ * type and flavors.
+ *
+ * <p>The dependencies are expressed as composite Gradle configuration objects that extends
+ * all the configuration objects of the "configs".</p>
+ *
+ * <p>It optionally contains the dependencies for a test config for the given config.</p>
+ */
+public class VariantDependencies implements DependencyContainer {
+
+ private String name;
+
+ @NonNull
+ private Configuration compileConfiguration;
+ @NonNull
+ private Configuration packageConfiguration;
+ @NonNull
+ private Configuration publishConfiguration;
+
+ @Nullable
+ private Configuration mappingConfiguration;
+ @Nullable
+ private Configuration classesConfiguration;
+ @Nullable
+ private Configuration metadataConfiguration;
+
+ @NonNull
+ private List<LibraryDependencyImpl> libraries = Lists.newArrayList();
+ @NonNull
+ private List<JarDependency> jars = Lists.newArrayList();
+ @NonNull
+ private List<JarDependency> localJars = Lists.newArrayList();
+
+ /**
+ * Whether we have a direct dependency on com.android.support:support-annotations; this
+ * is used to drive whether we extract annotations when building libraries for example
+ */
+ private boolean annotationsPresent;
+
+ @NonNull
+ private DependencyChecker checker;
+
+ public static VariantDependencies compute(
+ @NonNull Project project,
+ @NonNull String name,
+ boolean publishVariant,
+ @NonNull VariantType variantType,
+ @Nullable VariantDependencies parentVariant,
+ @NonNull ConfigurationProvider... providers) {
+ Set<Configuration> compileConfigs = Sets.newHashSetWithExpectedSize(providers.length * 2);
+ Set<Configuration> apkConfigs = Sets.newHashSetWithExpectedSize(providers.length);
+
+ for (ConfigurationProvider provider : providers) {
+ if (provider != null) {
+ compileConfigs.add(provider.getCompileConfiguration());
+ if (provider.getProvidedConfiguration() != null) {
+ compileConfigs.add(provider.getProvidedConfiguration());
+ }
+
+ apkConfigs.add(provider.getCompileConfiguration());
+ apkConfigs.add(provider.getPackageConfiguration());
+ }
+ }
+
+ if (parentVariant != null) {
+ compileConfigs.add(parentVariant.getCompileConfiguration());
+ apkConfigs.add(parentVariant.getPackageConfiguration());
+ }
+
+ Configuration compile = project.getConfigurations().maybeCreate("_" + name + "Compile");
+ compile.setVisible(false);
+ compile.setDescription("## Internal use, do not manually configure ##");
+ compile.setExtendsFrom(compileConfigs);
+
+ Configuration apk = project.getConfigurations().maybeCreate(
+ variantType == VariantType.LIBRARY
+ ? "_" + name + "Publish"
+ : "_" + name + "Apk");
+
+ apk.setVisible(false);
+ apk.setDescription("## Internal use, do not manually configure ##");
+ apk.setExtendsFrom(apkConfigs);
+
+ Configuration publish = null, mapping = null, classes = null, metadata = null;
+ if (publishVariant) {
+ publish = project.getConfigurations().maybeCreate(name);
+ publish.setDescription("Published Configuration for Variant " + name);
+ // if the variant is not a library, then the publishing configuration should
+ // not extend from the apkConfigs. It's mostly there to access the artifact from
+ // another project but it shouldn't bring any dependencies with it.
+ if (variantType == VariantType.LIBRARY) {
+ publish.setExtendsFrom(apkConfigs);
+ }
+
+ // create configuration for -metadata.
+ metadata = project.getConfigurations().create(name + "-metadata");
+ metadata.setDescription("Published APKs metadata for Variant " + name);
+
+ // create configuration for -mapping and -classes.
+ mapping = project.getConfigurations().maybeCreate(name + "-mapping");
+ mapping.setDescription("Published mapping configuration for Variant " + name);
+
+ classes = project.getConfigurations().maybeCreate(name + "-classes");
+ classes.setDescription("Published classes configuration for Variant " + name);
+ // because we need the transitive dependencies for the classes, extend the compile config.
+ classes.setExtendsFrom(compileConfigs);
+ }
+
+ return new VariantDependencies(
+ name,
+ compile,
+ apk,
+ publish,
+ mapping,
+ classes,
+ metadata,
+ variantType != VariantType.UNIT_TEST);
+ }
+
+ private VariantDependencies(@NonNull String name,
+ @NonNull Configuration compileConfiguration,
+ @NonNull Configuration packageConfiguration,
+ @Nullable Configuration publishConfiguration,
+ @Nullable Configuration mappingConfiguration,
+ @Nullable Configuration classesConfiguration,
+ @Nullable Configuration metadataConfiguration,
+ boolean skipClassesInAndroid) {
+ this.name = name;
+ this.compileConfiguration = compileConfiguration;
+ this.packageConfiguration = packageConfiguration;
+ this.publishConfiguration = publishConfiguration;
+ this.mappingConfiguration = mappingConfiguration;
+ this.classesConfiguration = classesConfiguration;
+ this.metadataConfiguration = metadataConfiguration;
+ this.checker = new DependencyChecker(this, skipClassesInAndroid);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @NonNull
+ public Configuration getCompileConfiguration() {
+ return compileConfiguration;
+ }
+
+ @NonNull
+ public Configuration getPackageConfiguration() {
+ return packageConfiguration;
+ }
+
+ @Nullable
+ public Configuration getPublishConfiguration() {
+ return publishConfiguration;
+ }
+
+ @Nullable
+ public Configuration getMappingConfiguration() {
+ return mappingConfiguration;
+ }
+
+ @Nullable
+ public Configuration getClassesConfiguration() {
+ return classesConfiguration;
+ }
+
+ @Nullable
+ public Configuration getMetadataConfiguration() {
+ return metadataConfiguration;
+ }
+
+ public void addLibraries(@NonNull List<LibraryDependencyImpl> list) {
+ libraries.addAll(list);
+ }
+
+ public void addJars(@NonNull Collection<JarDependency> list) {
+ jars.addAll(list);
+ }
+
+ public void addLocalJars(@NonNull Collection<JarDependency> list) {
+ localJars.addAll(list);
+ }
+
+ @NonNull
+ public List<LibraryDependencyImpl> getLibraries() {
+ return libraries;
+ }
+
+ @NonNull
+ @Override
+ public List<? extends LibraryDependency> getAndroidDependencies() {
+ return libraries;
+ }
+
+ @NonNull
+ @Override
+ public List<JarDependency> getJarDependencies() {
+ return jars;
+ }
+
+ @NonNull
+ @Override
+ public List<JarDependency> getLocalDependencies() {
+ return localJars;
+ }
+
+ @NonNull
+ public DependencyChecker getChecker() {
+ return checker;
+ }
+
+ public void setAnnotationsPresent(boolean annotationsPresent) {
+ this.annotationsPresent = annotationsPresent;
+ }
+
+ public boolean isAnnotationsPresent() {
+ return annotationsPresent;
+ }
+
+ public boolean hasNonOptionalLibraries() {
+ for (LibraryDependency libraryDependency : libraries) {
+ if (!libraryDependency.isOptional()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", name)
+ .toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptions.java
new file mode 100644
index 0000000..5b0283b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptions.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * DSL object for configuring aapt options.
+ */
+public class AaptOptions implements com.android.builder.model.AaptOptions {
+
+ @Nullable
+ private String ignoreAssetsPattern;
+
+ @Nullable
+ private List<String> noCompressList;
+
+ private boolean useNewCruncher = true;
+
+ private boolean cruncherEnabled = true;
+
+ private boolean failOnMissingConfigEntry = false;
+
+ @Nullable
+ private List<String> additionalParameters;
+
+ public void setIgnoreAssetsPattern(@Nullable String ignoreAssetsPattern) {
+ this.ignoreAssetsPattern = ignoreAssetsPattern;
+ }
+
+ public void setIgnoreAssets(@Nullable String ignoreAssetsPattern) {
+ setIgnoreAssetsPattern(ignoreAssetsPattern);
+ }
+
+ /**
+ * Pattern describing assets to be ignore.
+ *
+ * <p>See <code>aapt --help</code>
+ */
+ @Override
+ @Optional
+ @Input
+ public String getIgnoreAssets() {
+ return ignoreAssetsPattern;
+ }
+
+ /**
+ * Pattern describing assets to be ignore.
+ *
+ * <p>See <code>aapt --help</code>
+ */
+ public String getIgnoreAssetsPattern() {
+ return ignoreAssetsPattern;
+ }
+
+ public void setNoCompress(String noCompress) {
+ noCompressList = Collections.singletonList(noCompress);
+ }
+
+ public void setNoCompress(String... noCompress) {
+ noCompressList = Arrays.asList(noCompress);
+ }
+
+ /**
+ * Extensions of files that will not be stored compressed in the APK.
+ *
+ * <p>Equivalent of the -0 flag. See <code>aapt --help</code>
+ */
+ @Override
+ @Optional
+ @Input
+ public Collection<String> getNoCompress() {
+ return noCompressList;
+ }
+
+ public void useNewCruncher(boolean value) {
+ useNewCruncher = value;
+ }
+
+ public void setUseNewCruncher(boolean value) {
+ useNewCruncher = value;
+ }
+
+ /**
+ * Enables or disables PNG crunching.
+ */
+ public void setCruncherEnabled(boolean value) {
+ cruncherEnabled = value;
+ }
+
+ /**
+ * Returns true if the PNGs should be crunched, false otherwise.
+ */
+ @Input
+ public boolean getCruncherEnabled() {
+ return cruncherEnabled;
+ }
+
+ /**
+ * Whether to use the new cruncher.
+ */
+ @Input
+ public boolean getUseNewCruncher() {
+ return useNewCruncher;
+ }
+
+ public void failOnMissingConfigEntry(boolean value) {
+ failOnMissingConfigEntry = value;
+ }
+
+ public void setFailOnMissingConfigEntry(boolean value) {
+ failOnMissingConfigEntry = value;
+ }
+
+ /**
+ * Forces aapt to return an error if it fails to find an entry for a configuration.
+ *
+ * <p>See <code>aapt --help</code>
+ */
+ @Override
+ @Input
+ public boolean getFailOnMissingConfigEntry() {
+ return failOnMissingConfigEntry;
+ }
+
+ // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+ /**
+ * Sets extensions of files that will not be stored compressed in the APK.
+ *
+ * <p>Equivalent of the -0 flag. See <code>aapt --help</code>
+ */
+ public void noCompress(String noCompress) {
+ noCompressList = Collections.singletonList(noCompress);
+ }
+
+ /**
+ * Sets extensions of files that will not be stored compressed in the APK.
+ *
+ * <p>Equivalent of the -0 flag. See <code>aapt --help</code>
+ */
+ public void noCompress(String... noCompress) {
+ noCompressList = Arrays.asList(noCompress);
+ }
+
+ /**
+ * Adds additional parameters to be passed to {@code aapt}.
+ */
+ public void additionalParameters(@NonNull String param) {
+ additionalParameters = Collections.singletonList(param);
+ }
+
+ /**
+ * Adds additional parameters to be passed to {@code aapt}.
+ */
+ public void additionalParameters(String... params) {
+ additionalParameters = Arrays.asList(params);
+ }
+
+ public void setAdditionalParameters(@Nullable List<String> parameters) {
+ additionalParameters = parameters;
+ }
+
+ /**
+ * Returns the list of additional parameters to pass to {@code appt}.
+ */
+ @Nullable
+ @Override
+ @Optional
+ @Input
+ public List<String> getAdditionalParameters() {
+ return additionalParameters;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AbiSplitOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AbiSplitOptions.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AbiSplitOptions.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AbiSplitOptions.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AdbOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AdbOptions.java
new file mode 100644
index 0000000..0a1125f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AdbOptions.java
@@ -0,0 +1,72 @@
+/*
+ * 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.build.gradle.internal.dsl;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Options for the adb tool.
+ */
+public class AdbOptions implements com.android.builder.model.AdbOptions {
+
+ int timeOutInMs;
+
+ List<String> installOptions;
+
+ /**
+ * Returns the time out used for all adb operations.
+ */
+ @Override
+ public int getTimeOutInMs() {
+ return timeOutInMs;
+ }
+
+ public void setTimeOutInMs(int timeOutInMs) {
+ this.timeOutInMs = timeOutInMs;
+ }
+
+ public void timeOutInMs(int timeOutInMs) {
+ setTimeOutInMs(timeOutInMs);
+ }
+
+ /**
+ * Returns the list of APK installation options.
+ */
+ @Override
+ public Collection<String> getInstallOptions() {
+ return installOptions;
+ }
+
+ public void setInstallOptions(String option) {
+ installOptions = ImmutableList.of(option);
+ }
+
+ public void setInstallOptions(String... options) {
+ installOptions = ImmutableList.copyOf(options);
+ }
+
+ public void installOptions(String option) {
+ installOptions = ImmutableList.of(option);
+ }
+
+ public void installOptions(String... options) {
+ installOptions = ImmutableList.copyOf(options);
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AndroidSourceSetFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AndroidSourceSetFactory.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AndroidSourceSetFactory.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/AndroidSourceSetFactory.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildType.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildType.java
new file mode 100644
index 0000000..0389048
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildType.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.core.DefaultBuildType;
+import com.android.builder.model.BaseConfig;
+import com.android.builder.model.ClassField;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.Serializable;
+
+/**
+ * DSL object to configure build types.
+ */
+public class BuildType extends DefaultBuildType implements CoreBuildType, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final Project project;
+
+ @NonNull
+ private final Logger logger;
+
+ @Nullable
+ private final NdkOptions ndkConfig;
+
+ @Nullable
+ private Boolean useJack;
+
+ /** Opt-in for now until we've validated it in the field. */
+ private boolean shrinkResources = false;
+
+ /** Opt-in for now until we've validated the new shrinker in the field. */
+ private boolean useProguard = true;
+
+ public BuildType(@NonNull String name,
+ @NonNull Project project,
+ @NonNull Instantiator instantiator,
+ @NonNull Logger logger) {
+ super(name);
+ this.project = project;
+ this.logger = logger;
+ ndkConfig = instantiator.newInstance(NdkOptions.class);
+ }
+
+ @VisibleForTesting
+ BuildType(@NonNull String name,
+ @NonNull Project project,
+ @NonNull Logger logger) {
+ super(name);
+ this.project = project;
+ this.logger = logger;
+ ndkConfig = null;
+ }
+
+ @Override
+ @Nullable
+ public CoreNdkOptions getNdkConfig() {
+ return ndkConfig;
+ }
+
+ /**
+ * Initialize the DSL object. Not meant to be used from the build scripts.
+ */
+ public void init(SigningConfig debugSigningConfig) {
+ if (BuilderConstants.DEBUG.equals(getName())) {
+ setDebuggable(true);
+ setEmbedMicroApp(false);
+
+ assert debugSigningConfig != null;
+ setSigningConfig(debugSigningConfig);
+ } else if (BuilderConstants.RELEASE.equals(getName())) {
+ // no config needed for now.
+ }
+ }
+
+ /** The signing configuration. */
+ @Override
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ return (SigningConfig) super.getSigningConfig();
+ }
+
+ @Override
+ protected void _initWith(@NonNull BaseConfig that) {
+ super._initWith(that);
+ BuildType thatBuildType = (BuildType) that;
+ shrinkResources = thatBuildType.isShrinkResources();
+ useJack = thatBuildType.getUseJack();
+ }
+
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (useJack != null ? useJack.hashCode() : 0);
+ result = 31 * result + (shrinkResources ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof BuildType)) return false;
+ if (!super.equals(o)) return false;
+ BuildType other = (BuildType) o;
+ if (useJack != other.getUseJack()) return false;
+ if (shrinkResources != other.isShrinkResources()) return false;
+
+ return true;
+ }
+
+ // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+ /**
+ * Adds a new field to the generated BuildConfig class.
+ *
+ * <p>The field is generated as: {@code <type> <name> = <value>;}
+ *
+ * <p>This means each of these must have valid Java content. If the type is a String, then the
+ * value should include quotes.
+ *
+ * @param type the type of the field
+ * @param name the name of the field
+ * @param value the value of the field
+ */
+ public void buildConfigField(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ ClassField alreadyPresent = getBuildConfigFields().get(name);
+ if (alreadyPresent != null) {
+ logger.info("BuildType({}): buildConfigField '{}' value is being replaced: {} -> {}",
+ getName(), name, alreadyPresent.getValue(), value);
+ }
+ addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
+ }
+
+ /**
+ * Adds a new generated resource.
+ *
+ * <p>This is equivalent to specifying a resource in res/values.
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/resources/available-resources.html">Resource Types</a>.
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource
+ * @param value the value of the resource
+ */
+ public void resValue(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ ClassField alreadyPresent = getResValues().get(name);
+ if (alreadyPresent != null) {
+ logger.info("BuildType({}): resValue '{}' value is being replaced: {} -> {}",
+ getName(), name, alreadyPresent.getValue(), value);
+ }
+ addResValue(AndroidBuilder.createClassField(type, name, value));
+ }
+
+ /**
+ * Adds a new ProGuard configuration file.
+ *
+ * <p><code>proguardFile getDefaultProguardFile('proguard-android.txt')</code></p>
+ *
+ * <p>There are 2 default rules files
+ * <ul>
+ * <li>proguard-android.txt
+ * <li>proguard-android-optimize.txt
+ * </ul>
+ * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code>
+ * will return the full path to the files. They are identical except for enabling optimizations.
+ */
+ @NonNull
+ public BuildType proguardFile(@NonNull Object proguardFile) {
+ getProguardFiles().add(project.file(proguardFile));
+ return this;
+ }
+
+ /**
+ * Adds new ProGuard configuration files.
+ *
+ * <p>There are 2 default rules files
+ * <ul>
+ * <li>proguard-android.txt
+ * <li>proguard-android-optimize.txt
+ * </ul>
+ * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
+ * full path to the files. They are identical except for enabling optimizations.
+ */
+ @NonNull
+ public BuildType proguardFiles(@NonNull Object... proguardFiles) {
+ getProguardFiles().addAll(project.files(proguardFiles).getFiles());
+ return this;
+ }
+
+ /**
+ * Sets the ProGuard configuration files.
+ *
+ * <p>There are 2 default rules files
+ * <ul>
+ * <li>proguard-android.txt
+ * <li>proguard-android-optimize.txt
+ * </ul>
+ * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
+ * full path to the files. They are identical except for enabling optimizations.
+ */
+ @NonNull
+ public BuildType setProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
+ getProguardFiles().clear();
+ for (Object proguardFile : proguardFileIterable) {
+ getProguardFiles().add(project.file(proguardFile));
+ }
+ return this;
+ }
+
+ /**
+ * Adds a proguard rule file to be used when processing test code.
+ *
+ * <p>Test code needs to be processed to apply the same obfuscation as was done to main code.
+ */
+ @NonNull
+ public BuildType testProguardFile(@NonNull Object proguardFile) {
+ getTestProguardFiles().add(project.file(proguardFile));
+ return this;
+ }
+
+ /**
+ * Adds proguard rule files to be used when processing test code.
+ *
+ * <p>Test code needs to be processed to apply the same obfuscation as was done to main code.
+ */
+ @NonNull
+ public BuildType testProguardFiles(@NonNull Object... proguardFiles) {
+ getTestProguardFiles().addAll(project.files(proguardFiles).getFiles());
+ return this;
+ }
+
+ /**
+ * Specifies proguard rule files to be used when processing test code.
+ *
+ * <p>Test code needs to be processed to apply the same obfuscation as was done to main code.
+ */
+ public void setTestProguardFiles(@NonNull Iterable<?> files) {
+ getTestProguardFiles().clear();
+ for (Object proguardFile : files) {
+ getTestProguardFiles().add(project.file(proguardFile));
+ }
+ }
+
+ /**
+ * Adds proguard rule files to be included in the published AAR.
+ *
+ * <p>This proguard rule file will then be used by any application project that consume the AAR
+ * (if proguard is enabled).
+ *
+ * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
+ *
+ * <p>This is only valid for Library project. This is ignored in Application project.
+ */
+ @NonNull
+ public BuildType consumerProguardFiles(@NonNull Object... proguardFiles) {
+ getConsumerProguardFiles().addAll(project.files(proguardFiles).getFiles());
+ return this;
+ }
+
+ /**
+ * Adds a proguard rule file to be included in the published AAR.
+ *
+ * <p>This proguard rule file will then be used by any application project that consume the AAR
+ * (if proguard is enabled).
+ *
+ * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
+ *
+ * <p>This is only valid for Library project. This is ignored in Application project.
+ */
+ public void consumerProguardFile(@NonNull Object proguardFile) {
+ getConsumerProguardFiles().add(project.file(proguardFile));
+ }
+
+ /**
+ * Specifies a proguard rule file to be included in the published AAR.
+ *
+ * This proguard rule file will then be used by any application project that consume the AAR
+ * (if proguard is enabled).
+ *
+ * This allows AAR to specify shrinking or obfuscation exclude rules.
+ *
+ * This is only valid for Library project. This is ignored in Application project.
+ */
+ @NonNull
+ public BuildType setConsumerProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
+ getConsumerProguardFiles().clear();
+ for (Object proguardFile : proguardFileIterable) {
+ getConsumerProguardFiles().add(project.file(proguardFile));
+ }
+ return this;
+ }
+
+ public void ndk(@NonNull Action<NdkOptions> action) {
+ action.execute(ndkConfig);
+ }
+
+ /**
+ * Whether the experimental Jack toolchain should be used.
+ */
+ @Override
+ @Nullable
+ public Boolean getUseJack() {
+ return useJack;
+ }
+
+ /**
+ * Whether the experimental Jack toolchain should be used.
+ */
+ public void setUseJack(@Nullable Boolean useJack) {
+ this.useJack = useJack;
+ }
+
+ /**
+ * Whether the experimental Jack toolchain should be used.
+ */
+ public void useJack(@Nullable Boolean useJack) {
+ setUseJack(useJack);
+ }
+
+ /**
+ * Whether shrinking of unused resources is enabled.
+ *
+ * Default is false;
+ */
+ @Override
+ public boolean isShrinkResources() {
+ return shrinkResources;
+ }
+
+ @Override
+ public boolean isUseProguard() {
+ return useProguard;
+ }
+
+ public void setUseProguard(boolean useProguard) {
+ this.useProguard = useProguard;
+ }
+
+ public void useProguard(boolean useProguard) {
+ setUseProguard(useProguard);
+ }
+
+ public void setShrinkResources(boolean shrinkResources) {
+ this.shrinkResources = shrinkResources;
+ }
+
+ /**
+ * Whether shrinking of unused resources is enabled.
+ *
+ * Default is false;
+ */
+ public void shrinkResources(boolean flag) {
+ this.shrinkResources = flag;
+ }
+
+ public void jarJarRuleFile(@NonNull Object file) {
+ getJarJarRuleFiles().add(project.file(file));
+ }
+
+ public void jarJarRuleFiles(@NonNull Object... files) {
+ getJarJarRuleFiles().clear();
+ for (Object file : files) {
+ getJarJarRuleFiles().add(project.file(file));
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeFactory.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeFactory.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeFactory.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreBuildType.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreBuildType.java
new file mode 100644
index 0000000..69c54e2
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreBuildType.java
@@ -0,0 +1,36 @@
+/*
+ * 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.build.gradle.internal.dsl;
+
+import com.android.annotations.Nullable;
+import com.android.builder.model.BuildType;
+
+/**
+ * A build type with addition properties for building with Gradle plugin.
+ */
+public interface CoreBuildType extends BuildType {
+
+ @Nullable
+ CoreNdkOptions getNdkConfig();
+
+ @Nullable
+ Boolean getUseJack();
+
+ boolean isShrinkResources();
+
+ boolean isUseProguard();
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreNdkOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreNdkOptions.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreNdkOptions.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreNdkOptions.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreProductFlavor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreProductFlavor.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreProductFlavor.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreProductFlavor.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreSigningConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreSigningConfig.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreSigningConfig.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/CoreSigningConfig.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DataBindingOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DataBindingOptions.java
new file mode 100644
index 0000000..0ca4390
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DataBindingOptions.java
@@ -0,0 +1,62 @@
+/*
+ * 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.build.gradle.internal.dsl;
+
+/**
+ * DSL object for configuring databinding options.
+ */
+public class DataBindingOptions implements com.android.builder.model.DataBindingOptions {
+ private String version;
+ private boolean enabled = false;
+ private boolean addDefaultAdapters = true;
+
+ /**
+ * The version of data binding to use.
+ */
+ @Override
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ /**
+ * Whether to enable data binding.
+ */
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Whether to add the default data binding adapters.
+ */
+ @Override
+ public boolean getAddDefaultAdapters() {
+ return addDefaultAdapters;
+ }
+
+ public void setAddDefaultAdapters(boolean addDefaultAdapters) {
+ this.addDefaultAdapters = addDefaultAdapters;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptions.java
new file mode 100644
index 0000000..a290b69
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptions.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.resources.Density;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * DSL object for configuring per-density splits options.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
+ */
+public class DensitySplitOptions extends SplitOptions {
+
+ private boolean strict = true;
+ private boolean auto = false;
+ private Set<String> compatibleScreens;
+
+ @Override
+ protected Set<String> getDefaultValues() {
+ Set<Density> values = Density.getRecommendedValuesForDevice();
+ Set<String> fullList = Sets.newHashSetWithExpectedSize(values.size());
+ for (Density value : values) {
+ fullList.add(value.getResourceValue());
+ }
+
+ return fullList;
+ }
+
+ @Override
+ protected ImmutableSet<String> getAllowedValues() {
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+
+ for (Density value : Density.values()) {
+ if (value != Density.NODPI && value != Density.ANYDPI) {
+ builder.add(value.getResourceValue());
+ }
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * TODO: Document.
+ */
+ public boolean isStrict() {
+ return strict;
+ }
+
+ public void setStrict(boolean strict) {
+ this.strict = strict;
+ }
+
+ public void setCompatibleScreens(@NonNull List<String> sizes) {
+ compatibleScreens = Sets.newHashSet(sizes);
+ }
+
+ /**
+ * Adds a new compatible screen.
+ *
+ * <p>See {@link #getCompatibleScreens()}.
+ */
+ public void compatibleScreens(@NonNull String... sizes) {
+ if (compatibleScreens == null) {
+ compatibleScreens = Sets.newHashSet(sizes);
+ return;
+ }
+
+ compatibleScreens.addAll(Arrays.asList(sizes));
+ }
+
+ /**
+ * A list of compatible screens.
+ *
+ * <p>This will inject a matching <code><compatible-screens><screen ...></code>
+ * node in the manifest. This is optional.
+ */
+ @NonNull
+ public Set<String> getCompatibleScreens() {
+ if (compatibleScreens == null) {
+ return Collections.emptySet();
+ }
+ return compatibleScreens;
+ }
+
+ /**
+ * Sets whether the build system should determine the splits based on the "language-*" folders
+ * in the resources.
+ *
+ * <p>If the auto mode is set to true, the include list will be ignored.
+ *
+ * @param auto true to automatically set the splits list based on the folders presence, false
+ * to use the include list.
+ */
+ public void setAuto(boolean auto) {
+ this.auto = auto;
+ }
+
+ /**
+ * Whether the build system should determine the splits based on the "language-*" folders
+ * in the resources.
+ *
+ * <p>If the auto mode is set to true, the include list will be ignored.
+ */
+ public boolean isAuto() {
+ return auto;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptions.java
new file mode 100644
index 0000000..98d9f1b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptions.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.Nullable;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+
+/**
+ * DSL object for configuring dx options.
+ */
+public class DexOptions implements com.android.builder.core.DexOptions {
+
+ private boolean isIncrementalFlag = false;
+
+ private boolean isPreDexLibrariesFlag = true;
+
+ private boolean isJumboModeFlag = false;
+
+ private Boolean isDexInProcess = null;
+
+ private Integer threadCount = null;
+
+ private String javaMaxHeapSize;
+
+ private Integer maxProcessCount = null;
+
+ public void setIncremental(boolean isIncremental) {
+ // TODO: Print out a warning, that this is ignored.
+ isIncrementalFlag = isIncremental;
+ }
+
+ /**
+ * Whether to enable the incremental mode for dx. This has many limitations and may not
+ * work. Use carefully.
+ */
+ @Override
+ @Input
+ public boolean getIncremental() {
+ return isIncrementalFlag;
+ }
+
+ public void setPreDexLibraries(boolean flag) {
+ isPreDexLibrariesFlag = flag;
+ }
+
+ /**
+ * Whether to pre-dex libraries. This can improve incremental builds, but clean builds may
+ * be slower.
+ */
+ @Override
+ @Input
+ public boolean getPreDexLibraries() {
+ return isPreDexLibrariesFlag;
+ }
+
+ public void setJumboMode(boolean flag) {
+ isJumboModeFlag = flag;
+ }
+
+ /**
+ * Enable jumbo mode in dx (--force-jumbo).
+ */
+ @Override
+ @Input
+ public boolean getJumboMode() {
+ return isJumboModeFlag;
+ }
+
+ public void setJavaMaxHeapSize(String theJavaMaxHeapSize) {
+ if (theJavaMaxHeapSize.matches("\\d+[kKmMgGtT]?")) {
+ javaMaxHeapSize = theJavaMaxHeapSize;
+ } else {
+ throw new IllegalArgumentException(
+ "Invalid max heap size DexOption. See `man java` for valid -Xmx arguments.");
+ }
+ }
+
+ /**
+ * Sets the -JXmx* value when calling dx. Format should follow the 1024M pattern.
+ */
+ @Override
+ @Optional @Input
+ @Nullable
+ public String getJavaMaxHeapSize() {
+ return javaMaxHeapSize;
+ }
+
+ public void setThreadCount(int threadCount) {
+ this.threadCount = threadCount;
+ }
+
+ /**
+ * Number of threads to use when running dx. Defaults to 4.
+ */
+ @Override
+ @Nullable
+ public Integer getThreadCount() {
+ return threadCount;
+ }
+
+
+ /**
+ * Returns the maximum number of concurrent processes that can be used to dex. Defaults to 2.
+ *
+ * <p>Be aware that the number of concurrent process times the memory requirement represent the
+ * minimum amount of memory that will be used by the dx processes:
+ * {@code Total Memory = getMaxProcessCount() * getJavaMaxHeapSize()}
+ *
+ * To avoid trashing, keep these two settings appropriate for your configuration.
+ * @return the max number of concurrent dx processes.
+ */
+ @Nullable
+ @Override
+ public Integer getMaxProcessCount() {
+ return maxProcessCount;
+ }
+
+ public void setMaxProcessCount(int maxProcessCount) {
+ this.maxProcessCount = maxProcessCount;
+ }
+
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LanguageSplitOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LanguageSplitOptions.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LanguageSplitOptions.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LanguageSplitOptions.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptions.java
new file mode 100644
index 0000000..982b5d8
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptions.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+import static com.android.tools.lint.detector.api.Severity.ERROR;
+import static com.android.tools.lint.detector.api.Severity.FATAL;
+import static com.android.tools.lint.detector.api.Severity.IGNORE;
+import static com.android.tools.lint.detector.api.Severity.INFORMATIONAL;
+import static com.android.tools.lint.detector.api.Severity.WARNING;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.HtmlReporter;
+import com.android.tools.lint.LintCliClient;
+import com.android.tools.lint.LintCliFlags;
+import com.android.tools.lint.TextReporter;
+import com.android.tools.lint.XmlReporter;
+import com.android.tools.lint.checks.BuiltinIssueRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * DSL object for configuring lint options.
+ */
+public class LintOptions implements com.android.builder.model.LintOptions, Serializable {
+ public static final String STDOUT = "stdout";
+ public static final String STDERR = "stderr";
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private Set<String> disable = Sets.newHashSet();
+ @NonNull
+ private Set<String> enable = Sets.newHashSet();
+ @Nullable
+ private Set<String> check = Sets.newHashSet();
+ private boolean abortOnError = true;
+ private boolean absolutePaths = true;
+ private boolean noLines;
+ private boolean quiet;
+ private boolean checkAllWarnings;
+ private boolean ignoreWarnings;
+ private boolean warningsAsErrors;
+ private boolean showAll;
+ private boolean checkReleaseBuilds = true;
+ private boolean explainIssues = true;
+ @Nullable
+ private File lintConfig;
+ private boolean textReport;
+ @Nullable
+ private File textOutput;
+ private boolean htmlReport = true;
+ @Nullable
+ private File htmlOutput;
+ private boolean xmlReport = true;
+ @Nullable
+ private File xmlOutput;
+
+ private Map<String,Severity> severities = Maps.newHashMap();
+
+ public LintOptions() {
+ }
+
+ public LintOptions(
+ @NonNull Set<String> disable,
+ @NonNull Set<String> enable,
+ @Nullable Set<String> check,
+ @Nullable File lintConfig,
+ boolean textReport,
+ @Nullable File textOutput,
+ boolean htmlReport,
+ @Nullable File htmlOutput,
+ boolean xmlReport,
+ @Nullable File xmlOutput,
+ boolean abortOnError,
+ boolean absolutePaths,
+ boolean noLines,
+ boolean quiet,
+ boolean checkAllWarnings,
+ boolean ignoreWarnings,
+ boolean warningsAsErrors,
+ boolean showAll,
+ boolean explainIssues,
+ boolean checkReleaseBuilds,
+ @Nullable Map<String,Integer> severityOverrides) {
+ this.disable = disable;
+ this.enable = enable;
+ this.check = check;
+ this.lintConfig = lintConfig;
+ this.textReport = textReport;
+ this.textOutput = textOutput;
+ this.htmlReport = htmlReport;
+ this.htmlOutput = htmlOutput;
+ this.xmlReport = xmlReport;
+ this.xmlOutput = xmlOutput;
+ this.abortOnError = abortOnError;
+ this.absolutePaths = absolutePaths;
+ this.noLines = noLines;
+ this.quiet = quiet;
+ this.checkAllWarnings = checkAllWarnings;
+ this.ignoreWarnings = ignoreWarnings;
+ this.warningsAsErrors = warningsAsErrors;
+ this.showAll = showAll;
+ this.explainIssues = explainIssues;
+ this.checkReleaseBuilds = checkReleaseBuilds;
+
+ if (severityOverrides != null) {
+ for (Map.Entry<String,Integer> entry : severityOverrides.entrySet()) {
+ severities.put(entry.getKey(), convert(entry.getValue()));
+ }
+ }
+ }
+
+ @NonNull
+ public static com.android.builder.model.LintOptions create(@NonNull com.android.builder.model.LintOptions source) {
+ return new LintOptions(
+ source.getDisable(),
+ source.getEnable(),
+ source.getCheck(),
+ source.getLintConfig(),
+ source.getTextReport(),
+ source.getTextOutput(),
+ source.getHtmlReport(),
+ source.getHtmlOutput(),
+ source.getXmlReport(),
+ source.getXmlOutput(),
+ source.isAbortOnError(),
+ source.isAbsolutePaths(),
+ source.isNoLines(),
+ source.isQuiet(),
+ source.isCheckAllWarnings(),
+ source.isIgnoreWarnings(),
+ source.isWarningsAsErrors(),
+ source.isShowAll(),
+ source.isExplainIssues(),
+ source.isCheckReleaseBuilds(),
+ source.getSeverityOverrides()
+ );
+ }
+
+ /**
+ * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
+ */
+ @Override
+ @NonNull
+ @Input
+ public Set<String> getDisable() {
+ return disable;
+ }
+
+ /**
+ * Sets the set of issue id's to suppress. Callers are allowed to modify this collection.
+ * Note that these ids add to rather than replace the given set of ids.
+ */
+ public void setDisable(@Nullable Set<String> ids) {
+ disable.addAll(ids);
+ }
+
+ /**
+ * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
+ * To enable a given issue, add the issue ID to the returned set.
+ */
+ @Override
+ @NonNull
+ @Input
+ public Set<String> getEnable() {
+ return enable;
+ }
+
+ /**
+ * Sets the set of issue id's to enable. Callers are allowed to modify this collection.
+ * Note that these ids add to rather than replace the given set of ids.
+ */
+ public void setEnable(@Nullable Set<String> ids) {
+ enable.addAll(ids);
+ }
+
+ /**
+ * Returns the exact set of issues to check, or null to run the issues that are enabled
+ * by default plus any issues enabled via {@link #getEnable} and without issues disabled
+ * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
+ */
+ @Override
+ @Nullable
+ @Optional
+ @Input
+ public Set<String> getCheck() {
+ return check;
+ }
+
+ /**
+ * Sets the <b>exact</b> set of issues to check.
+ * @param ids the set of issue id's to check
+ */
+ public void setCheck(@NonNull Set<String> ids) {
+ check.addAll(ids);
+ }
+
+ /** Whether lint should set the exit code of the process if errors are found */
+ @Override
+ @Input
+ public boolean isAbortOnError() {
+ return abortOnError;
+ }
+
+ /** Sets whether lint should set the exit code of the process if errors are found */
+ public void setAbortOnError(boolean abortOnError) {
+ this.abortOnError = abortOnError;
+ }
+
+ /**
+ * Whether lint should display full paths in the error output. By default the paths
+ * are relative to the path lint was invoked from.
+ */
+ @Override
+ @Input
+ public boolean isAbsolutePaths() {
+ return absolutePaths;
+ }
+
+ /**
+ * Sets whether lint should display full paths in the error output. By default the paths
+ * are relative to the path lint was invoked from.
+ */
+ public void setAbsolutePaths(boolean absolutePaths) {
+ this.absolutePaths = absolutePaths;
+ }
+
+ /**
+ * Whether lint should include the source lines in the output where errors occurred
+ * (true by default)
+ */
+ @Override
+ @Input
+ public boolean isNoLines() {
+ return this.noLines;
+ }
+
+ /**
+ * Sets whether lint should include the source lines in the output where errors occurred
+ * (true by default)
+ */
+ public void setNoLines(boolean noLines) {
+ this.noLines = noLines;
+ }
+
+ /**
+ * Returns whether lint should be quiet (for example, not write informational messages
+ * such as paths to report files written)
+ */
+ @Override
+ @Input
+ public boolean isQuiet() {
+ return quiet;
+ }
+
+ /**
+ * Sets whether lint should be quiet (for example, not write informational messages
+ * such as paths to report files written)
+ */
+ public void setQuiet(boolean quiet) {
+ this.quiet = quiet;
+ }
+
+ /** Returns whether lint should check all warnings, including those off by default */
+ @Override
+ @Input
+ public boolean isCheckAllWarnings() {
+ return checkAllWarnings;
+ }
+
+ /** Sets whether lint should check all warnings, including those off by default */
+ public void setCheckAllWarnings(boolean warnAll) {
+ this.checkAllWarnings = warnAll;
+ }
+
+ /** Returns whether lint will only check for errors (ignoring warnings) */
+ @Override
+ @Input
+ public boolean isIgnoreWarnings() {
+ return ignoreWarnings;
+ }
+
+ /** Sets whether lint will only check for errors (ignoring warnings) */
+ public void setIgnoreWarnings(boolean noWarnings) {
+ this.ignoreWarnings = noWarnings;
+ }
+
+ /** Returns whether lint should treat all warnings as errors */
+ @Override
+ @Input
+ public boolean isWarningsAsErrors() {
+ return warningsAsErrors;
+ }
+
+ /** Sets whether lint should treat all warnings as errors */
+ public void setWarningsAsErrors(boolean allErrors) {
+ this.warningsAsErrors = allErrors;
+ }
+
+ /** Returns whether lint should include explanations for issue errors. (Note that
+ * HTML and XML reports intentionally do this unconditionally, ignoring this setting.) */
+ @Override
+ @Input
+ public boolean isExplainIssues() {
+ return explainIssues;
+ }
+
+ public void setExplainIssues(boolean explainIssues) {
+ this.explainIssues = explainIssues;
+ }
+
+ /**
+ * Returns whether lint should include all output (e.g. include all alternate
+ * locations, not truncating long messages, etc.)
+ */
+ @Override
+ @Input
+ public boolean isShowAll() {
+ return showAll;
+ }
+
+ /**
+ * Sets whether lint should include all output (e.g. include all alternate
+ * locations, not truncating long messages, etc.)
+ */
+ public void setShowAll(boolean showAll) {
+ this.showAll = showAll;
+ }
+
+ /**
+ * Returns whether lint should check for fatal errors during release builds. Default is true.
+ * If issues with severity "fatal" are found, the release build is aborted.
+ */
+ @Override
+ @Input
+ public boolean isCheckReleaseBuilds() {
+ return checkReleaseBuilds;
+ }
+
+ public void setCheckReleaseBuilds(boolean checkReleaseBuilds) {
+ this.checkReleaseBuilds = checkReleaseBuilds;
+ }
+
+ /**
+ * Returns the default configuration file to use as a fallback
+ */
+ @Override
+ @Optional @InputFile
+ public File getLintConfig() {
+ return lintConfig;
+ }
+
+ /** Whether we should write an text report. Default false. The location can be
+ * controlled by {@link #getTextOutput()}. */
+ @Override
+ @Input
+ public boolean getTextReport() {
+ return textReport;
+ }
+
+ public void setTextReport(boolean textReport) {
+ this.textReport = textReport;
+ }
+
+ public void setHtmlReport(boolean htmlReport) {
+ this.htmlReport = htmlReport;
+ }
+
+ public void setHtmlOutput(@NonNull File htmlOutput) {
+ this.htmlOutput = htmlOutput;
+ }
+
+ public void setXmlReport(boolean xmlReport) {
+ this.xmlReport = xmlReport;
+ }
+
+ public void setXmlOutput(@NonNull File xmlOutput) {
+ this.xmlOutput = xmlOutput;
+ }
+
+ /**
+ * The optional path to where a text report should be written. The special value
+ * "stdout" can be used to point to standard output.
+ */
+ @Override
+ @Nullable
+ @Optional
+ @Input
+ public File getTextOutput() {
+ return textOutput;
+ }
+
+ /** Whether we should write an HTML report. Default true. The location can be
+ * controlled by {@link #getHtmlOutput()}. */
+ @Override
+ @Input
+ public boolean getHtmlReport() {
+ return htmlReport;
+ }
+
+ /** The optional path to where an HTML report should be written */
+ @Override
+ @Nullable
+ @Optional
+ @OutputFile
+ public File getHtmlOutput() {
+ return htmlOutput;
+ }
+
+ /** Whether we should write an XML report. Default true. The location can be
+ * controlled by {@link #getXmlOutput()}. */
+ @Override
+ @Input
+ public boolean getXmlReport() {
+ return xmlReport;
+ }
+
+ /** The optional path to where an XML report should be written */
+ @Override
+ @Nullable
+ @Optional
+ @OutputFile
+ public File getXmlOutput() {
+ return xmlOutput;
+ }
+
+ /**
+ * Sets the default config file to use as a fallback. This corresponds to a {@code lint.xml}
+ * file with severities etc to use when a project does not have more specific information.
+ */
+ public void setLintConfig(@NonNull File lintConfig) {
+ this.lintConfig = lintConfig;
+ }
+
+ public void syncTo(
+ @NonNull LintCliClient client,
+ @NonNull LintCliFlags flags,
+ @Nullable String variantName,
+ @Nullable org.gradle.api.Project project,
+ boolean report) {
+ if (disable != null) {
+ flags.getSuppressedIds().addAll(disable);
+ }
+ if (enable != null) {
+ flags.getEnabledIds().addAll(enable);
+ }
+ if (check != null && !check.isEmpty()) {
+ flags.setExactCheckedIds(check);
+ }
+ flags.setSetExitCode(this.abortOnError);
+ flags.setFullPath(absolutePaths);
+ flags.setShowSourceLines(!noLines);
+ flags.setQuiet(quiet);
+ flags.setCheckAllWarnings(checkAllWarnings);
+ flags.setIgnoreWarnings(ignoreWarnings);
+ flags.setWarningsAsErrors(warningsAsErrors);
+ flags.setShowEverything(showAll);
+ flags.setDefaultConfiguration(lintConfig);
+ flags.setSeverityOverrides(severities);
+ flags.setExplainIssues(explainIssues);
+
+ if (report || flags.isFatalOnly() && this.abortOnError) {
+ if (textReport || flags.isFatalOnly()) {
+ File output = textOutput;
+ if (output == null) {
+ output = new File(flags.isFatalOnly() ? STDERR: STDOUT);
+ } else if (!output.isAbsolute() && !isStdOut(output) && !isStdErr(output)) {
+ output = project.file(output.getPath());
+ }
+ output = validateOutputFile(output);
+
+ Writer writer;
+ File file = null;
+ boolean closeWriter;
+ if (isStdOut(output)) {
+ writer = new PrintWriter(System.out, true);
+ closeWriter = false;
+ } else if (isStdErr(output)) {
+ writer = new PrintWriter(System.err, true);
+ closeWriter = false;
+ } else {
+ file = output;
+ try {
+ writer = new BufferedWriter(new FileWriter(output));
+ } catch (IOException e) {
+ throw new org.gradle.api.GradleException("Text invalid argument.", e);
+ }
+ closeWriter = true;
+ }
+ flags.getReporters().add(new TextReporter(client, flags, file, writer,
+ closeWriter));
+ }
+ if (htmlReport) {
+ File output = htmlOutput;
+ if (output == null || flags.isFatalOnly()) {
+ output = createOutputPath(project, variantName, ".html", flags.isFatalOnly());
+ } else if (!output.isAbsolute()) {
+ output = project.file(output.getPath());
+ }
+ output = validateOutputFile(output);
+ try {
+ flags.getReporters().add(new HtmlReporter(client, output));
+ } catch (IOException e) {
+ throw new GradleException("HTML invalid argument.", e);
+ }
+ }
+ if (xmlReport) {
+ File output = xmlOutput;
+ if (output == null || flags.isFatalOnly()) {
+ output = createOutputPath(project, variantName, DOT_XML, flags.isFatalOnly());
+ } else if (!output.isAbsolute()) {
+ output = project.file(output.getPath());
+ }
+ output = validateOutputFile(output);
+ try {
+ flags.getReporters().add(new XmlReporter(client, output));
+ } catch (IOException e) {
+ throw new org.gradle.api.GradleException("XML invalid argument.", e);
+ }
+ }
+ }
+ }
+
+ private static boolean isStdOut(@NonNull File output) {
+ return STDOUT.equals(output.getPath());
+ }
+
+ private static boolean isStdErr(@NonNull File output) {
+ return STDERR.equals(output.getPath());
+ }
+
+ @NonNull
+ private static File validateOutputFile(@NonNull File output) {
+ if (isStdOut(output) || isStdErr(output)) {
+ return output;
+ }
+
+ File parent = output.getParentFile();
+ if (!parent.exists()) {
+ parent.mkdirs();
+ }
+
+ output = output.getAbsoluteFile();
+ if (output.exists()) {
+ boolean delete = output.delete();
+ if (!delete) {
+ throw new org.gradle.api.GradleException("Could not delete old " + output);
+ }
+ }
+ if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
+ throw new org.gradle.api.GradleException("Cannot write output file " + output);
+ }
+
+ return output;
+ }
+
+ private static File createOutputPath(
+ @NonNull org.gradle.api.Project project,
+ @Nullable String variantName,
+ @NonNull String extension,
+ boolean fatalOnly) {
+ StringBuilder base = new StringBuilder();
+ base.append(FD_OUTPUTS);
+ base.append(File.separator);
+ base.append("lint-results");
+ if (!Strings.isNullOrEmpty(variantName)) {
+ base.append("-");
+ base.append(variantName);
+ }
+ if (fatalOnly) {
+ base.append("-fatal");
+ }
+ base.append(extension);
+ return new File(project.getBuildDir(), base.toString());
+ }
+
+ /**
+ * An optional map of severity overrides. The map maps from issue id's to the corresponding
+ * severity to use, which must be "fatal", "error", "warning", or "ignore".
+ *
+ * @return a map of severity overrides, or null. The severities are one of the constants
+ * {@link #SEVERITY_FATAL}, {@link #SEVERITY_ERROR}, {@link #SEVERITY_WARNING},
+ * {@link #SEVERITY_INFORMATIONAL}, {@link #SEVERITY_IGNORE}
+ */
+ @Override
+ @Nullable
+ public Map<String, Integer> getSeverityOverrides() {
+ if (severities == null || severities.isEmpty()) {
+ return null;
+ }
+
+ Map<String, Integer> map =
+ Maps.newHashMapWithExpectedSize(severities.size());
+ for (Map.Entry<String,Severity> entry : severities.entrySet()) {
+ map.put(entry.getKey(), convert(entry.getValue()));
+ }
+
+ return map;
+ }
+
+ // -- DSL Methods.
+
+ /**
+ * Adds the id to the set of issues to check.
+ */
+ public void check(String id) {
+ check.add(id);
+ }
+
+ /**
+ * Adds the ids to the set of issues to check.
+ */
+ public void check(String... ids) {
+ for (String id : ids) {
+ check(id);
+ }
+ }
+
+ /**
+ * Adds the id to the set of issues to enable.
+ */
+ public void enable(String id) {
+ enable.add(id);
+ Issue issue = new BuiltinIssueRegistry().getIssue(id);
+ severities.put(id, issue != null ? issue.getDefaultSeverity() : WARNING);
+ }
+
+ /**
+ * Adds the ids to the set of issues to enable.
+ */
+ public void enable(String... ids) {
+ for (String id : ids) {
+ enable(id);
+ }
+ }
+
+ /**
+ * Adds the id to the set of issues to enable.
+ */
+ public void disable(String id) {
+ disable.add(id);
+ severities.put(id, IGNORE);
+ }
+
+ /**
+ * Adds the ids to the set of issues to enable.
+ */
+ public void disable(String... ids) {
+ for (String id : ids) {
+ disable(id);
+ }
+ }
+
+ // For textOutput 'stdout' or 'stderr' (normally a file)
+ public void textOutput(String textOutput) {
+ this.textOutput = new File(textOutput);
+ }
+
+ // For textOutput file()
+ public void textOutput(File textOutput) {
+ this.textOutput = textOutput;
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void fatal(String id) {
+ severities.put(id, FATAL);
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void fatal(String... ids) {
+ for (String id : ids) {
+ fatal(id);
+ }
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void error(String id) {
+ severities.put(id, ERROR);
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void error(String... ids) {
+ for (String id : ids) {
+ error(id);
+ }
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void warning(String id) {
+ severities.put(id, WARNING);
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void warning(String... ids) {
+ for (String id : ids) {
+ warning(id);
+ }
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void ignore(String id) {
+ severities.put(id, IGNORE);
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void ignore(String... ids) {
+ for (String id : ids) {
+ ignore(id);
+ }
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void informational(String id) {
+ severities.put(id, INFORMATIONAL);
+ }
+
+ /**
+ * Adds a severity override for the given issues.
+ */
+ public void informational(String... ids) {
+ for (String id : ids) {
+ informational(id);
+ }
+ }
+
+ // Without these qualifiers, Groovy compilation will fail with "Apparent variable
+ // 'SEVERITY_FATAL' was found in a static scope but doesn't refer to a local variable,
+ // static field or class"
+ //@SuppressWarnings("UnnecessaryQualifiedReference")
+ private static int convert(Severity s) {
+ switch (s) {
+ case FATAL:
+ return com.android.builder.model.LintOptions.SEVERITY_FATAL;
+ case ERROR:
+ return com.android.builder.model.LintOptions.SEVERITY_ERROR;
+ case WARNING:
+ return com.android.builder.model.LintOptions.SEVERITY_WARNING;
+ case INFORMATIONAL:
+ return com.android.builder.model.LintOptions.SEVERITY_INFORMATIONAL;
+ case IGNORE:
+ default:
+ return com.android.builder.model.LintOptions.SEVERITY_IGNORE;
+ }
+ }
+
+ //@SuppressWarnings("UnnecessaryQualifiedReference")
+ private static Severity convert(int s) {
+ switch (s) {
+ case com.android.builder.model.LintOptions.SEVERITY_FATAL:
+ return FATAL;
+ case com.android.builder.model.LintOptions.SEVERITY_ERROR:
+ return ERROR;
+ case com.android.builder.model.LintOptions.SEVERITY_WARNING:
+ return WARNING;
+ case com.android.builder.model.LintOptions.SEVERITY_INFORMATIONAL:
+ return INFORMATIONAL;
+ case com.android.builder.model.LintOptions.SEVERITY_IGNORE:
+ default:
+ return IGNORE;
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/NdkOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/NdkOptions.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/NdkOptions.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/NdkOptions.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptions.java
new file mode 100644
index 0000000..01bb53d
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/PackagingOptions.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.tasks.Input;
+
+import java.util.Set;
+
+/**
+ * DSL objects for configuring APK packaging options.
+ */
+public class PackagingOptions implements com.android.builder.model.PackagingOptions {
+
+ private Set<String> excludes = Sets.newHashSet(
+ "META-INF/LICENSE",
+ "META-INF/LICENSE.txt",
+ "META-INF/NOTICE",
+ "META-INF/NOTICE.txt",
+ "NOTICE",
+ "NOTICE.txt",
+ "LICENSE.txt",
+ "LICENSE");
+ private Set<String> pickFirsts = Sets.newHashSet();
+ private Set<String> merges = Sets.newHashSet();
+
+ /**
+ * Returns the list of excluded paths.
+ *
+ * <p>Contains "LICENSE.txt" and "LICENSE" by default, since they often cause
+ * packaging conflicts.
+ */
+ @Override
+ @NonNull
+ @Input
+ public Set<String> getExcludes() {
+ return Sets.newHashSet(excludes);
+ }
+
+ public void setExcludes(Set<String> excludes) {
+ this.excludes = Sets.newHashSet(excludes);
+ pickFirsts.removeAll(excludes);
+ merges.removeAll(excludes);
+ }
+
+ /**
+ * Adds an excluded paths.
+ * @param path the path, as packaged in the APK
+ */
+ public void exclude(String path) {
+ excludes.add(path);
+ merges.remove(path);
+ pickFirsts.remove(path);
+ }
+
+ /**
+ * Returns the list of paths where the first occurrence is packaged in the APK.
+ */
+ @Override
+ @NonNull
+ @Input
+ public Set<String> getPickFirsts() {
+ return Sets.newHashSet(pickFirsts);
+ }
+
+ /**
+ * Adds a firstPick path. First pick paths do get packaged in the APK, but only the first
+ * occurrence gets packaged.
+ * @param path the path to add.
+ */
+ public void pickFirst(String path) {
+ pickFirsts.add(path);
+ merges.remove(path);
+ excludes.remove(path);
+ }
+
+ public void setPickFirsts(Set<String> pickFirsts) {
+ this.pickFirsts = Sets.newHashSet(pickFirsts);
+ excludes.removeAll(pickFirsts);
+ merges.removeAll(pickFirsts);
+ }
+
+ /**
+ * Returns the list of paths where all occurrences are concatenated and packaged in the APK.
+ */
+ @Override
+ @NonNull
+ @Input
+ public Set<String> getMerges() {
+ return Sets.newHashSet(merges);
+ }
+
+ public void setMerges(Set<String> merges) {
+ this.merges = Sets.newHashSet(merges);
+ excludes.removeAll(merges);
+ pickFirsts.removeAll(merges);
+ }
+
+ /**
+ * Adds a merge path.
+ * @param path the path, as packaged in the APK
+ */
+ public void merge(String path) {
+ merges.add(path);
+ excludes.remove(path);
+ pickFirsts.remove(path);
+ }
+
+ @NonNull
+ public Action getAction(String archivePath) {
+ if (pickFirsts.contains(archivePath)) {
+ return PackagingOptions.Action.PICK_FIRST;
+ }
+ if (merges.contains(archivePath)) {
+ return PackagingOptions.Action.MERGE;
+ }
+ if (excludes.contains(archivePath)) {
+ return PackagingOptions.Action.EXCLUDE;
+ }
+
+ return Action.NONE;
+ }
+
+ /**
+ * User's setting for a particular archive entry. This is expressed in the build.gradle
+ * DSL and used by this filter to determine file merging behaviors.
+ */
+ public enum Action {
+ /**
+ * no action was described for archive entry.
+ */
+ NONE,
+ /**
+ * merge all archive entries with the same archive path.
+ */
+ MERGE,
+ /**
+ * pick to first archive entry with that archive path (not stable).
+ */
+ PICK_FIRST,
+ /**
+ * exclude all archive entries with that archive path.
+ */
+ EXCLUDE
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavor.java
new file mode 100644
index 0000000..7b80954
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavor.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import static com.android.build.gradle.AndroidGradleOptions.USE_DEPRECATED_NDK;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.core.DefaultApiVersion;
+import com.android.builder.core.DefaultProductFlavor;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.VectorDrawablesOptions;
+import com.android.builder.model.SyncIssue;
+import com.google.common.base.Strings;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * DSL object used to configure product flavors.
+ */
+public class ProductFlavor extends DefaultProductFlavor implements CoreProductFlavor {
+
+ @NonNull
+ protected final Project project;
+
+ @NonNull
+ protected final Logger logger;
+
+ @NonNull
+ private final NdkOptions ndkConfig;
+
+ @NonNull
+ private final ErrorReporter errorReporter;
+
+ @Nullable
+ private Boolean useJack;
+
+ public ProductFlavor(
+ @NonNull String name,
+ @NonNull Project project,
+ @NonNull Instantiator instantiator,
+ @NonNull Logger logger,
+ @NonNull ErrorReporter errorReporter) {
+ super(name);
+ this.project = project;
+ this.logger = logger;
+ this.errorReporter = errorReporter;
+ ndkConfig = instantiator.newInstance(NdkOptions.class);
+ }
+
+ @Override
+ @Nullable
+ public CoreNdkOptions getNdkConfig() {
+ return ndkConfig;
+ }
+
+ public void setMinSdkVersion(int minSdkVersion) {
+ setMinSdkVersion(new DefaultApiVersion(minSdkVersion));
+ }
+
+ /**
+ * Sets minimum SDK version.
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
+ * uses-sdk element documentation</a>.
+ */
+ public void minSdkVersion(int minSdkVersion) {
+ setMinSdkVersion(minSdkVersion);
+ }
+
+ public void setMinSdkVersion(@Nullable String minSdkVersion) {
+ setMinSdkVersion(getApiVersion(minSdkVersion));
+ }
+
+ /**
+ * Sets minimum SDK version.
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
+ * uses-sdk element documentation</a>.
+ */
+ public void minSdkVersion(@Nullable String minSdkVersion) {
+ setMinSdkVersion(minSdkVersion);
+ }
+
+ @NonNull
+ public com.android.builder.model.ProductFlavor setTargetSdkVersion(int targetSdkVersion) {
+ setTargetSdkVersion(new DefaultApiVersion(targetSdkVersion));
+ return this;
+ }
+
+ /**
+ * Sets the target SDK version to the given value.
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
+ * uses-sdk element documentation</a>.
+ */
+ public void targetSdkVersion(int targetSdkVersion) {
+ setTargetSdkVersion(targetSdkVersion);
+ }
+
+ public void setTargetSdkVersion(@Nullable String targetSdkVersion) {
+ setTargetSdkVersion(getApiVersion(targetSdkVersion));
+ }
+
+ /**
+ * Sets the target SDK version to the given value.
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
+ * uses-sdk element documentation</a>.
+ */
+ public void targetSdkVersion(@Nullable String targetSdkVersion) {
+ setTargetSdkVersion(targetSdkVersion);
+ }
+
+ /**
+ * Sets the maximum SDK version to the given value.
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
+ * uses-sdk element documentation</a>.
+ */
+ public void maxSdkVersion(int targetSdkVersion) {
+ setMaxSdkVersion(targetSdkVersion);
+ }
+
+ @Nullable
+ private static ApiVersion getApiVersion(@Nullable String value) {
+ if (!Strings.isNullOrEmpty(value)) {
+ if (Character.isDigit(value.charAt(0))) {
+ try {
+ int apiLevel = Integer.valueOf(value);
+ return new DefaultApiVersion(apiLevel);
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("'" + value + "' is not a valid API level. ", e);
+ }
+ }
+
+ return new DefaultApiVersion(value);
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds a custom argument to the test instrumentation runner, e.g:
+ *
+ * <p><pre>testInstrumentationRunnerArgument "size", "medium"</pre>
+ *
+ * <p>Test runner arguments can also be specified from the command line:
+ *
+ * <p><pre>
+ * ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.size=medium
+ * ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.foo=bar
+ * </pre>
+ */
+ public void testInstrumentationRunnerArgument(@NonNull String key, @NonNull String value) {
+ getTestInstrumentationRunnerArguments().put(key, value);
+ }
+
+ /**
+ * Adds custom arguments to the test instrumentation runner, e.g:
+ *
+ * <p><pre>testInstrumentationRunnerArguments(size: "medium", foo: "bar")</pre>
+ *
+ * <p>Test runner arguments can also be specified from the command line:
+ *
+ * <p><pre>
+ * ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.size=medium
+ * ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.foo=bar
+ * </pre>
+ */
+ public void testInstrumentationRunnerArguments(@NonNull Map<String, String> args) {
+ getTestInstrumentationRunnerArguments().putAll(args);
+ }
+
+ /**
+ * Signing config used by this product flavor.
+ */
+ @Override
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ return (SigningConfig) super.getSigningConfig();
+ }
+
+// -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+ /**
+ * Adds a new field to the generated BuildConfig class.
+ *
+ * <p>The field is generated as: {@code <type> <name> = <value>;}
+ *
+ * <p>This means each of these must have valid Java content. If the type is a String, then the
+ * value should include quotes.
+ *
+ * @param type the type of the field
+ * @param name the name of the field
+ * @param value the value of the field
+ */
+ public void buildConfigField(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ ClassField alreadyPresent = getBuildConfigFields().get(name);
+ if (alreadyPresent != null) {
+ String flavorName = getName();
+ if (BuilderConstants.MAIN.equals(flavorName)) {
+ logger.info(
+ "DefaultConfig: buildConfigField '{}' value is being replaced: {} -> {}",
+ name, alreadyPresent.getValue(), value);
+ } else {
+ logger.info(
+ "ProductFlavor({}): buildConfigField '{}' "
+ + "value is being replaced: {} -> {}",
+ flavorName, name, alreadyPresent.getValue(), value);
+ }
+ }
+ addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
+ }
+
+ /**
+ * Adds a new generated resource.
+ *
+ * <p>This is equivalent to specifying a resource in res/values.
+ *
+ * <p>See <a href="http://developer.android.com/guide/topics/resources/available-resources.html">Resource Types</a>.
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource
+ * @param value the value of the resource
+ */
+ public void resValue(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ ClassField alreadyPresent = getResValues().get(name);
+ if (alreadyPresent != null) {
+ String flavorName = getName();
+ if (BuilderConstants.MAIN.equals(flavorName)) {
+ logger.info(
+ "DefaultConfig: resValue '{}' value is being replaced: {} -> {}",
+ name, alreadyPresent.getValue(), value);
+ } else {
+ logger.info(
+ "ProductFlavor({}): resValue '{}' value is being replaced: {} -> {}",
+ flavorName, name, alreadyPresent.getValue(), value);
+ }
+ }
+ addResValue(AndroidBuilder.createClassField(type, name, value));
+ }
+
+ /**
+ * Adds a new ProGuard configuration file.
+ *
+ * <p><code>proguardFile getDefaultProguardFile('proguard-android.txt')</code></p>
+ *
+ * <p>There are 2 default rules files
+ * <ul>
+ * <li>proguard-android.txt
+ * <li>proguard-android-optimize.txt
+ * </ul>
+ * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
+ * full path to the files. They are identical except for enabling optimizations.
+ */
+ public void proguardFile(@NonNull Object proguardFile) {
+ getProguardFiles().add(project.file(proguardFile));
+ }
+
+ /**
+ * Adds new ProGuard configuration files.
+ *
+ * <p>There are 2 default rules files
+ * <ul>
+ * <li>proguard-android.txt
+ * <li>proguard-android-optimize.txt
+ * </ul>
+ * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
+ * full path to the files. They are identical except for enabling optimizations.
+ */
+ public void proguardFiles(@NonNull Object... proguardFiles) {
+ getProguardFiles().addAll(project.files(proguardFiles).getFiles());
+ }
+
+ /**
+ * Sets the ProGuard configuration files.
+ *
+ * <p>There are 2 default rules files
+ * <ul>
+ * <li>proguard-android.txt
+ * <li>proguard-android-optimize.txt
+ * </ul>
+ * <p>They are located in the SDK. Using <code>getDefaultProguardFile(String filename)</code> will return the
+ * full path to the files. They are identical except for enabling optimizations.
+ */
+ public void setProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
+ getProguardFiles().clear();
+ for (Object proguardFile : proguardFileIterable) {
+ getProguardFiles().add(project.file(proguardFile));
+ }
+ }
+
+ /**
+ * Adds a proguard rule file to be used when processing test code.
+ *
+ * <p>Test code needs to be processed to apply the same obfuscation as was done to main code.
+ */
+ public void testProguardFile(@NonNull Object proguardFile) {
+ getTestProguardFiles().add(project.file(proguardFile));
+ }
+
+ /**
+ * Adds proguard rule files to be used when processing test code.
+ *
+ * <p>Test code needs to be processed to apply the same obfuscation as was done to main code.
+ */
+ public void testProguardFiles(@NonNull Object... proguardFiles) {
+ getTestProguardFiles().addAll(project.files(proguardFiles).getFiles());
+ }
+
+ /**
+ * Specifies proguard rule files to be used when processing test code.
+ *
+ * <p>Test code needs to be processed to apply the same obfuscation as was done to main code.
+ */
+ public void setTestProguardFiles(@NonNull Iterable<?> files) {
+ getTestProguardFiles().clear();
+ for (Object proguardFile : files) {
+ getTestProguardFiles().add(project.file(proguardFile));
+ }
+ }
+
+ /**
+ * Adds a proguard rule file to be included in the published AAR.
+ *
+ * <p>This proguard rule file will then be used by any application project that consume the AAR
+ * (if proguard is enabled).
+ *
+ * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
+ *
+ * <p>This is only valid for Library project. This is ignored in Application project.
+ */
+ public void consumerProguardFile(@NonNull Object proguardFile) {
+ getConsumerProguardFiles().add(project.file(proguardFile));
+ }
+
+ /**
+ * Adds proguard rule files to be included in the published AAR.
+ *
+ * <p>This proguard rule file will then be used by any application project that consume the AAR
+ * (if proguard is enabled).
+ *
+ * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
+ *
+ * <p>This is only valid for Library project. This is ignored in Application project.
+ */
+ public void consumerProguardFiles(@NonNull Object... proguardFiles) {
+ getConsumerProguardFiles().addAll(project.files(proguardFiles).getFiles());
+ }
+
+ /**
+ * Specifies a proguard rule file to be included in the published AAR.
+ *
+ * <p>This proguard rule file will then be used by any application project that consume the AAR
+ * (if proguard is enabled).
+ *
+ * <p>This allows AAR to specify shrinking or obfuscation exclude rules.
+ *
+ * <p>This is only valid for Library project. This is ignored in Application project.
+ */
+ public void setConsumerProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
+ getConsumerProguardFiles().clear();
+ for (Object proguardFile : proguardFileIterable) {
+ getConsumerProguardFiles().add(project.file(proguardFile));
+ }
+ }
+
+ public void ndk(Action<NdkOptions> action) {
+ action.execute(ndkConfig);
+ if (!project.hasProperty(USE_DEPRECATED_NDK)) {
+ throw new RuntimeException(
+ "Error: NDK integration is deprecated in the current plugin. Consider trying " +
+ "the new experimental plugin. For details, see " +
+ "http://tools.android.com/tech-docs/new-build-system/gradle-experimental. " +
+ "Set \"" + USE_DEPRECATED_NDK + "=true\" in gradle.properties to " +
+ "continue using the current NDK integration.");
+ }
+ }
+
+ /**
+ * Adds a resource configuration filter.
+ *
+ * <p>If a qualifier value is passed, then all other resources using a qualifier of the same type
+ * but of different value will be ignored from the final packaging of the APK.
+ *
+ * <p>For instance, specifying 'hdpi', will ignore all resources using mdpi, xhdpi, etc...
+ */
+ public void resConfig(@NonNull String config) {
+ addResourceConfiguration(config);
+ }
+
+ /**
+ * Adds several resource configuration filters.
+ *
+ * <p>If a qualifier value is passed, then all other resources using a qualifier of the same type
+ * but of different value will be ignored from the final packaging of the APK.
+ *
+ * <p>For instance, specifying 'hdpi', will ignore all resources using mdpi, xhdpi, etc...
+ */
+ public void resConfigs(@NonNull String... config) {
+ addResourceConfigurations(config);
+ }
+
+ /**
+ * Adds several resource configuration filters.
+ *
+ * <p>If a qualifier value is passed, then all other resources using a qualifier of the same type
+ * but of different value will be ignored from the final packaging of the APK.
+ *
+ * <p>For instance, specifying 'hdpi', will ignore all resources using mdpi, xhdpi, etc...
+ */
+ public void resConfigs(@NonNull Collection<String> config) {
+ addResourceConfigurations(config);
+ }
+
+ /**
+ * Whether the experimental Jack toolchain should be used.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/jackandjill">Jack and Jill</a>
+ */
+ @Override
+ @Nullable
+ public Boolean getUseJack() {
+ return useJack;
+ }
+
+ /**
+ * Whether the experimental Jack toolchain should be used.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/jackandjill">Jack and Jill</a>
+ */
+ public void setUseJack(Boolean useJack) {
+ this.useJack = useJack;
+ }
+
+ /**
+ * Whether the experimental Jack toolchain should be used.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/jackandjill">Jack and Jill</a>
+ */
+ public void useJack(Boolean useJack) {
+ setUseJack(useJack);
+ }
+
+ @Deprecated
+ public void setFlavorDimension(String dimension) {
+ errorReporter.handleSyncWarning(null, SyncIssue.TYPE_GENERIC,
+ "'flavorDimension' will be removed in a future version of Android Gradle Plugin, " +
+ "it has been replaced by 'dimension'.");
+ setDimension(dimension);
+ }
+
+ /**
+ * Name of the dimension this product flavor belongs to. Has been replaced by
+ * <code>dimension</code>
+ */
+ @Deprecated
+ public String getFlavorDimension() {
+ errorReporter.handleSyncWarning(null, SyncIssue.TYPE_GENERIC,
+ "'flavorDimension' will be removed in a future version of Android Gradle Plugin, " +
+ "it has been replaced by 'dimension'.");
+ return getDimension();
+ }
+
+ public void jarJarRuleFile(Object file) {
+ getJarJarRuleFiles().add(project.file(file));
+ }
+
+ public void jarJarRuleFiles(Object ...files) {
+ getJarJarRuleFiles().clear();
+ for (Object file : files) {
+ getJarJarRuleFiles().add(project.file(file));
+ }
+ }
+
+ /**
+ * Deprecated equivalent of {@code vectorDrawablesOptions.generatedDensities}.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ @Nullable
+ public Set<String> getGeneratedDensities() {
+ return getVectorDrawables().getGeneratedDensities();
+ }
+
+ @Deprecated
+ public void setGeneratedDensities(@Nullable Iterable<String> densities) {
+ getVectorDrawables().setGeneratedDensities(densities);
+ }
+
+ /**
+ * Configures {@link VectorDrawablesOptions}.
+ */
+ public void vectorDrawables(Action<VectorDrawablesOptions> action) {
+ action.execute(getVectorDrawables());
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfig.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfig.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfig.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfig.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SplitOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SplitOptions.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SplitOptions.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/SplitOptions.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/Splits.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/Splits.java
new file mode 100644
index 0000000..03b4bc6
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/Splits.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.api.Action;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.Set;
+
+/**
+ * DSL object for configuring APK Splits options.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
+ */
+public class Splits {
+
+ private final DensitySplitOptions density;
+ private final AbiSplitOptions abi;
+ private final LanguageSplitOptions language;
+
+ public Splits(@NonNull Instantiator instantiator) {
+ density = instantiator.newInstance(DensitySplitOptions.class);
+ abi = instantiator.newInstance(AbiSplitOptions.class);
+ language = instantiator.newInstance(LanguageSplitOptions.class);
+ }
+
+ /**
+ * Density settings.
+ */
+ public DensitySplitOptions getDensity() {
+ return density;
+ }
+
+ /**
+ * Configures density split settings.
+ */
+ public void density(Action<DensitySplitOptions> action) {
+ action.execute(density);
+ }
+
+ /**
+ * ABI settings.
+ */
+ public AbiSplitOptions getAbi() {
+ return abi;
+ }
+
+ /**
+ * Configures ABI split settings.
+ */
+ public void abi(Action<AbiSplitOptions> action) {
+ action.execute(abi);
+ }
+
+ /**
+ * Language settings.
+ */
+ public LanguageSplitOptions getLanguage() {
+ return language;
+ }
+
+ /**
+ * Configures the language split settings.
+ */
+ public void language(Action<LanguageSplitOptions> action) {
+ action.execute(language);
+ }
+
+ /**
+ * Returns the list of Density filters used for multi-apk.
+ *
+ * <p>null value is allowed, indicating the need to generate an apk with all densities.
+ *
+ * @return a set of filters.
+ */
+ @NonNull
+ public Set<String> getDensityFilters() {
+ return density.getApplicableFilters();
+ }
+
+ /**
+ * Returns the list of ABI filters used for multi-apk.
+ *
+ * <p>null value is allowed, indicating the need to generate an apk with all abis.
+ *
+ * @return a set of filters.
+ */
+ @NonNull
+ public Set<String> getAbiFilters() {
+ return abi.getApplicableFilters();
+ }
+
+ /**
+ * Returns the list of language filters used for multi-apk.
+ *
+ * <p>null value is allowed, indicating the need to generate an apk with all languages.
+ *
+ * @return a set of language filters.
+ */
+ @NonNull
+ public Set<String> getLanguageFilters() {
+ return language.getApplicationFilters();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/TestOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/TestOptions.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/TestOptions.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/dsl/TestOptions.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/BuildInfoLoaderTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/BuildInfoLoaderTask.java
new file mode 100644
index 0000000..9f6a800
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/BuildInfoLoaderTask.java
@@ -0,0 +1,155 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.android.utils.FileUtils;
+import com.google.common.io.Files;
+
+import org.apache.log4j.Level;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+
+/**
+ * Task responsible for loading past iteration build-info.xml file and backup necessary files for
+ * disconnected devices to be able to "catch up" to latest bits.
+ */
+public class BuildInfoLoaderTask extends BaseTask {
+
+ @OutputDirectory
+ File pastBuildsFolder;
+
+ @Input
+ String buildId;
+
+ @InputFile
+ @Optional
+ @NonNull
+ File buildInfoFile;
+
+
+ @InputFile
+ @Optional
+ @NonNull
+ File tmpBuildInfoFile;
+
+ Logger logger;
+ InstantRunBuildContext instantRunBuildContext;
+
+ @TaskAction
+ public void executeAction() {
+ // saves the build information xml file.
+ try {
+ // load the persisted state, this will give us previous build-ids in case we need them.
+ if (buildInfoFile.exists()) {
+ instantRunBuildContext.loadFromXmlFile(buildInfoFile);
+ }
+ // check for the presence of a temporary buildInfoFile and if it exists, merge its
+ // artifacts into the current build.
+ if (tmpBuildInfoFile.exists()) {
+ instantRunBuildContext.mergeFromFile(tmpBuildInfoFile);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("Exception while loading build-info.xml : %s", e.getMessage()));
+ }
+ try {
+ // move last iteration artifacts to our back up folder.
+ InstantRunBuildContext.Build lastBuild = instantRunBuildContext.getLastBuild();
+ if (lastBuild == null) {
+ return;
+ }
+
+ // create a new backup folder with the old build-id as the name.
+ File backupFolder = new File(pastBuildsFolder, String.valueOf(lastBuild.getBuildId()));
+ FileUtils.mkdirs(backupFolder);
+ for (InstantRunBuildContext.Artifact artifact : lastBuild.getArtifacts()) {
+ if (!artifact.isAccumulative()) {
+ File oldLocation = artifact.getLocation();
+ // last iteration could have been a cold swap.
+ if (!oldLocation.isFile()) {
+ return;
+ }
+ File newLocation = new File(backupFolder, oldLocation.getName());
+ if (logger.isEnabled(LogLevel.DEBUG)) {
+ logger.debug(String.format("File moved from %1$s to %2$s",
+ oldLocation.getPath(), newLocation.getPath()));
+ }
+ Files.copy(oldLocation, newLocation);
+ // update the location in the model so it is saved with the build-info.xml
+ artifact.setLocation(newLocation);
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("Exception while doing past iteration backup : %s",
+ e.getMessage()));
+ }
+ }
+
+ public static class ConfigAction implements TaskConfigAction<BuildInfoLoaderTask> {
+
+ private final String taskName;
+
+ private final VariantScope variantScope;
+
+ private final Logger logger;
+
+ public ConfigAction(@NonNull VariantScope scope, at NonNull Logger logger) {
+ this.taskName = scope.getTaskName("buildInfo", "Loader");
+ this.variantScope = scope;
+ this.logger = logger;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return taskName;
+ }
+
+ @NonNull
+ @Override
+ public Class<BuildInfoLoaderTask> getType() {
+ return BuildInfoLoaderTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull BuildInfoLoaderTask task) {
+ task.setDescription("InstantRun task to load and backup previous iterations artifacts");
+ task.setVariantName(variantScope.getVariantConfiguration().getFullName());
+ variantScope.getInstantRunBuildContext().setTmpBuildInfo(
+ InstantRunWrapperTask.ConfigAction.getTmpBuildInfoFile(variantScope));
+ task.buildInfoFile = InstantRunWrapperTask.ConfigAction.getBuildInfoFile(variantScope);
+ task.tmpBuildInfoFile =
+ InstantRunWrapperTask.ConfigAction.getTmpBuildInfoFile(variantScope);
+ task.pastBuildsFolder = variantScope.getInstantRunPastIterationsFolder();
+ task.instantRunBuildContext = variantScope.getInstantRunBuildContext();
+ task.logger = logger;
+ task.buildId = String.valueOf(task.instantRunBuildContext.getBuildId());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ByteCodeUtils.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ByteCodeUtils.java
new file mode 100644
index 0000000..a4ab8ae
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ByteCodeUtils.java
@@ -0,0 +1,54 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+
+/**
+ * Bytecode generation utilities to work around some ASM / Dex issues.
+ */
+public class ByteCodeUtils {
+
+ private static final Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
+ private static final Method SHORT_VALUE = Method.getMethod("short shortValue()");
+ private static final Method BYTE_VALUE = Method.getMethod("byte byteValue()");
+
+ /**
+ * Generates unboxing bytecode for the passed type. An {@link Object} is expected to be on the
+ * stack when these bytecodes are inserted.
+ *
+ * ASM takes a short cut when dealing with short/byte types and convert them into int rather
+ * than short/byte types. This is not an issue on the jvm nor Android's ART but it is an issue
+ * on Dalvik.
+ *
+ * @param mv the {@link GeneratorAdapter} generating a method implementation.
+ * @param type the expected un-boxed type.
+ */
+ public static void unbox(GeneratorAdapter mv, Type type) {
+ if (type.equals(Type.SHORT_TYPE)) {
+ mv.checkCast(NUMBER_TYPE);
+ mv.invokeVirtual(NUMBER_TYPE, SHORT_VALUE);
+ } else if (type.equals(Type.BYTE_TYPE)) {
+ mv.checkCast(NUMBER_TYPE);
+ mv.invokeVirtual(NUMBER_TYPE, BYTE_VALUE);
+ } else {
+ mv.unbox(type);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ColdswapMode.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ColdswapMode.java
new file mode 100644
index 0000000..19d8d17
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ColdswapMode.java
@@ -0,0 +1,40 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+/**
+ * Defines the coldswap modes as specified by the IDE.
+ */
+public enum ColdswapMode {
+
+ /**
+ * Use multi APKs (pure splits) for Lollipop and above.
+ */
+ MULTIAPK,
+ /**
+ * Use native multi dex for Lollipop and above.
+ */
+ MULTIDEX,
+ /**
+ * User native multi dex for Lollipop and multi apk for Marshmallow.
+ */
+ AUTO,
+ /**
+ * User has not expressed any choice, use the current default
+ */
+ DEFAULT
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ConstructorArgsRedirection.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ConstructorArgsRedirection.java
new file mode 100644
index 0000000..a3c98a3
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ConstructorArgsRedirection.java
@@ -0,0 +1,174 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.tree.LabelNode;
+
+import java.util.List;
+
+/**
+ * A specialized redirection that handles redirecting the part that redirects the
+ * argument construction for the super()/this() call in a constructor.
+ * <p/>
+ * Note that the generated bytecode does not have a direct translation to code, but as an
+ * example, for a constructor of the form:
+ * <code>
+ * <init>(int x) {
+ * super(x = 1, expr2() ? 3 : 7)
+ * doSomething(x)
+ * }
+ * </code>
+ * <p/>
+ * it becomes:
+ * <code>
+ * <init>(int x) {
+ * Change change = $change; // Move to a variable to avoid multithreading issues.
+ * int a, b; // These variables are not needed in bytecode but are needed for the example.
+ * if (change != null) {
+ * Object[] locals = new Object[2];
+ * locals[0] = locals; // So the unboxed receiver can update this array
+ * locals[1] = x;
+ * Object[] constructorArguments = change.access$dispatch("init$args", locals);
+ * x = locals[1];
+ * this(constructorArguments, null);
+ * } else {
+ * a = x = 1;
+ * b = expr2() ? 3 : 7;
+ * super(a, b);
+ * }
+ * if (change != null) {
+ * Object[] locals = new Object[2];
+ * locals[0] = this;
+ * locals[1] = x;
+ * change.access$dispatch("init$body", locals);
+ * return;
+ * }
+ * doSomething(x);
+ * }
+ * </code>
+ *
+ * @see ConstructorDelegationDetector for the generation of init$args and init$body.
+ */
+public class ConstructorArgsRedirection extends Redirection {
+
+ @NonNull
+ private final String thisClassName;
+
+ @NonNull
+ private final Type[] types;
+
+ @NonNull
+ private final LabelNode end;
+
+ private int locals;
+
+ // The signature of the dynamically dispatching 'this' constructor. The final parameters is
+ // to disambiguate from other constructors that might preexist on the class.
+ static final String DISPATCHING_THIS_SIGNATURE =
+ "([Ljava/lang/Object;L" + IncrementalVisitor.INSTANT_RELOAD_EXCEPTION + ";)V";
+
+ /**
+ * @param thisClassName name of the class that this constructor is in.
+ * @param name the name to redirect to.
+ * @param end the label where the redirection should end (before the super()/this() call).
+ * @param types the types of the arguments on the super()/this() call.
+ */
+ ConstructorArgsRedirection(LabelNode label, String thisClassName, String name, @NonNull LabelNode end, @NonNull Type[] types) {
+ super(label, name);
+ this.thisClassName = thisClassName;
+ this.types = types;
+ this.end = end;
+
+ locals = -1;
+ }
+
+ @Override
+ protected void createLocals(GeneratorAdapter mv, List<Type> args) {
+ super.createLocals(mv, args);
+
+ // Override the locals creation to keep a reference to it. We keep a reference to this
+ // array because we use it to receive the values of the local variables after the
+ // redirection is done.
+ locals = mv.newLocal(Type.getType("[Ljava/lang/Object;"));
+ mv.dup();
+ mv.storeLocal(locals);
+ }
+
+ @Override
+ protected void redirectLocal(GeneratorAdapter mv, int stackIndex, Type arg) {
+ // If the stack index is 0, we do not send the local variable 0 (this) as it
+ // cannot escape the constructor. Instead, we use this argument position to send
+ // a reference to the locals array where the redirected method will return their
+ // values.
+ if (stackIndex == 0) {
+ mv.loadLocal(locals);
+ } else {
+ super.redirectLocal(mv, stackIndex, arg);
+ }
+ }
+
+ @Override
+ protected void restore(GeneratorAdapter mv, List<Type> args) {
+ // At this point, init$args has been called and the result Object is on the stack.
+ // The value of that Object is Object[] with exactly n + 1 elements.
+ // The first element is a string with the qualified name of the constructor to call.
+ // The remaining elements are the constructtor arguments.
+
+ // Create a new local that holds the result of init$args call.
+ mv.visitTypeInsn(Opcodes.CHECKCAST, "[Ljava/lang/Object;");
+ int constructorArgs = mv.newLocal(Type.getType("[Ljava/lang/Object;"));
+ mv.storeLocal(constructorArgs);
+
+ // Reinstate local values
+ mv.loadLocal(locals);
+ int stackIndex = 0;
+ for (int arrayIndex = 0; arrayIndex < args.size(); arrayIndex++) {
+ Type arg = args.get(arrayIndex);
+ // Do not restore "this"
+ if (arrayIndex > 0) {
+ // duplicates the array
+ mv.dup();
+ // index in the array of objects to restore the boxed parameter.
+ mv.push(arrayIndex);
+ // get it from the array
+ mv.arrayLoad(Type.getType(Object.class));
+ // unbox the argument
+ ByteCodeUtils.unbox(mv, arg);
+ // restore the argument
+ mv.visitVarInsn(arg.getOpcode(Opcodes.ISTORE), stackIndex);
+ }
+ // stack index must progress according to the parameter type we just processed.
+ stackIndex += arg.getSize();
+ }
+ // pops the array
+ mv.pop();
+
+ // Push a null for the marker parameter.
+ mv.loadLocal(constructorArgs);
+ mv.visitInsn(Opcodes.ACONST_NULL);
+
+ // Invoke the constructor
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, thisClassName, "<init>", DISPATCHING_THIS_SIGNATURE, false);
+
+ mv.goTo(end.getLabel());
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ConstructorDelegationDetector.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ConstructorDelegationDetector.java
new file mode 100644
index 0000000..6823573
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/ConstructorDelegationDetector.java
@@ -0,0 +1,343 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LineNumberNode;
+import org.objectweb.asm.tree.LocalVariableNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+import org.objectweb.asm.tree.VarInsnNode;
+import org.objectweb.asm.tree.analysis.Analyzer;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.objectweb.asm.tree.analysis.BasicInterpreter;
+import org.objectweb.asm.tree.analysis.BasicValue;
+import org.objectweb.asm.tree.analysis.Frame;
+import org.objectweb.asm.tree.analysis.Value;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utilities to detect and manipulate constructor methods.
+ *
+ * A constructor of a non static inner class usually has the form:
+ *
+ * ALOAD_0 // push this to the stack
+ * ... // Code to set up $this
+ * ALOAD_0 // push this to the stack
+ * ... // Code to set up the arguments (aka "args") for the delegation
+ * ... // via super() or this(). Note that here we can have INVOKESPECIALS
+ * ... // for all the new calls here.
+ * INVOKESPECIAL <init> // super() or this() call
+ * ... // the "body" of the constructor goes here.
+ *
+ * This class has the utilities to detect which instruction is the right INVOKESPECIAL call before
+ * the "body".
+ */
+public class ConstructorDelegationDetector {
+
+ /**
+ * A specialized value used to track the first local variable (this) on the
+ * constructor.
+ */
+ public static class LocalValue extends BasicValue {
+ public LocalValue(Type type) {
+ super(type);
+ }
+
+ @Override
+ public String toString() {
+ return "*";
+ }
+ }
+
+ /**
+ * A deconstructed constructor, split up in the parts mentioned above.
+ */
+ static class Constructor {
+
+ /**
+ * The last LOAD_0 instruction of the original code, before the call to the delegated
+ * constructor.
+ */
+ public final VarInsnNode loadThis;
+
+ /**
+ * Line number of LOAD_0. Used to set the line number in the generated constructor call
+ * so that a break point may be set at this(...) or super(...)
+ */
+ public final int lineForLoad;
+
+ /**
+ * The "args" part of the constructor. Described above.
+ */
+ public final MethodNode args;
+
+ /**
+ * The INVOKESPECIAL instruction of the original code that calls the delegation.
+ */
+ public final MethodInsnNode delegation;
+
+ /**
+ * A copy of the body of the constructor.
+ */
+ public final MethodNode body;
+
+ Constructor(VarInsnNode loadThis, int lineForLoad, MethodNode args, MethodInsnNode delegation, MethodNode body) {
+ this.loadThis = loadThis;
+ this.lineForLoad = lineForLoad;
+ this.args = args;
+ this.delegation = delegation;
+ this.body = body;
+ }
+ }
+
+ /**
+ * Deconstruct a constructor into its components and adds the necessary code to link the components
+ * later. The generated bytecode does not correspond exactly to this code, but in essence, for
+ * a constructor of this form:
+ * <p/>
+ * <code>
+ * <init>(int x) {
+ * super(x = 1, expr2() ? 3 : 7)
+ * doSomething(x)
+ * }
+ * </code>
+ * <p/>
+ * it creates the two parts:
+ * <code>
+ * Object[] init$args(Object[] locals, int x) {
+ * Object[] args = new Object[2];
+ * args[0] = (x = 1)
+ * args[1] = expr2() ? 3 : 7;
+ * locals[0] = x;
+ * return new Object[] {"myclass.<init>(I;I;)V", args};
+ * }
+ *
+ * void init$body(int x) {
+ * doSomething(x);
+ * }
+ * </code>
+ *
+ * @param owner the owning class.
+ * @param method the constructor method.
+ */
+ @NonNull
+ public static Constructor deconstruct(@NonNull String owner, @NonNull MethodNode method) {
+ // Basic interpreter uses BasicValue.REFERENCE_VALUE for all object types. However
+ // we need to distinguish one in particular. The value of the local variable 0, ie. the
+ // uninitialized this. By doing it this way we ensure that whenever there is a ALOAD_0
+ // a LocalValue instance will be on the stack.
+ BasicInterpreter interpreter = new BasicInterpreter() {
+ boolean done = false;
+ @Override
+ // newValue is called first to initialize the frame values of all the local variables
+ // we intercept the first one to create our own special value.
+ public BasicValue newValue(Type type) {
+ if (type == null) {
+ return BasicValue.UNINITIALIZED_VALUE;
+ } else if (type.getSort() == Type.VOID) {
+ return null;
+ } else {
+ // If this is the first value created (i.e. the first local variable)
+ // we use a special marker.
+ BasicValue ret = done ? super.newValue(type) : new LocalValue(type);
+ done = true;
+ return ret;
+ }
+ }
+ };
+
+ Analyzer analyzer = new Analyzer(interpreter);
+ AbstractInsnNode[] instructions = method.instructions.toArray();
+ try {
+ Frame[] frames = analyzer.analyze(owner, method);
+ if (frames.length != instructions.length) {
+ // Should never happen.
+ throw new IllegalStateException(
+ "The number of frames is not equals to the number of instructions");
+ }
+ VarInsnNode lastThis = null;
+ int stackAtThis = -1;
+ boolean poppedThis = false;
+ // Records the most recent line number encountered. For javac, there should always be
+ // a line number node before the call of interest to this(...) or super(...). For robustness,
+ // -1 is recorded as a sentinel to indicate this assumption didn't hold. Upstream consumers
+ // should check for -1 and recover in a reasonable way (for example, don't set the line
+ // number in generated code).
+ int recentLine = -1;
+ for (int i = 0; i < instructions.length; i++) {
+ AbstractInsnNode insn = instructions[i];
+ Frame frame = frames[i];
+ if (frame.getStackSize() < stackAtThis) {
+ poppedThis = true;
+ }
+ if (insn instanceof MethodInsnNode) {
+ // TODO: Do we need to check that the stack is empty after this super call?
+ MethodInsnNode methodhInsn = (MethodInsnNode) insn;
+ Type[] types = Type.getArgumentTypes(methodhInsn.desc);
+ Value value = frame.getStack(frame.getStackSize() - types.length - 1);
+ if (value instanceof LocalValue && methodhInsn.name.equals("<init>")) {
+ if (poppedThis) {
+ throw new IllegalStateException("Unexpected constructor structure.");
+ }
+ return split(owner, method, lastThis, methodhInsn, recentLine);
+ }
+ } else if (insn instanceof VarInsnNode) {
+ VarInsnNode var = (VarInsnNode) insn;
+ if (var.var == 0) {
+ lastThis = var;
+ stackAtThis = frame.getStackSize();
+ poppedThis = false;
+ }
+ } else if (insn instanceof LineNumberNode) {
+ // Record the most recent line number encountered so that call to this(...)
+ // or super(...) has line number information. Ultimately used to emit a line
+ // number in the generated code.
+ LineNumberNode lineNumberNode = (LineNumberNode) insn;
+ recentLine = lineNumberNode.line;
+ }
+ }
+ throw new IllegalStateException("Unexpected constructor structure.");
+ } catch (AnalyzerException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Splits the constructor in two methods, the "set up" and the "body" parts (see above).
+ */
+ @NonNull
+ private static Constructor split(@NonNull String owner, @NonNull MethodNode method, @NonNull VarInsnNode loadThis, @NonNull MethodInsnNode delegation, int loadThisLine ) {
+ String[] exceptions = ((List<String>)method.exceptions).toArray(new String[method.exceptions.size()]);
+ String newDesc = method.desc.replaceAll("\\((.*)\\)V", "([Ljava/lang/Object;$1)Ljava/lang/Object;");
+
+ MethodNode initArgs = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "init$args", newDesc, null, exceptions);
+ AbstractInsnNode insn = loadThis.getNext();
+ while (insn != delegation) {
+ insn.accept(initArgs);
+ insn = insn.getNext();
+ }
+ LabelNode labelBefore = new LabelNode();
+ labelBefore.accept(initArgs);
+
+ GeneratorAdapter mv = new GeneratorAdapter(initArgs, initArgs.access, initArgs.name, initArgs.desc);
+ // Copy the arguments back to the argument array
+ // The init_args part cannot access the "this" object and can have side effects on the
+ // local variables. Because of this we use the first argument (which we want to keep
+ // so all the other arguments remain unchanged) as a reference to the array where to
+ // return the values of the modified local variables.
+ Type[] types = Type.getArgumentTypes(initArgs.desc);
+ int stack = 1; // Skip the first one which is a reference to the local array.
+ for (int i = 1; i < types.length; i++) {
+ Type type = types[i];
+ // This is not this, but the array of local arguments final values.
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.push(i);
+ mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), stack);
+ mv.box(type);
+ mv.arrayStore(Type.getType(Object.class));
+ stack += type.getSize();
+ }
+ // Create the args array with the values to send to the delegated constructor
+ Type[] returnTypes = Type.getArgumentTypes(delegation.desc);
+ // The extra element for the qualified name of the constructor.
+ mv.push(returnTypes.length + 1);
+ mv.newArray(Type.getType(Object.class));
+ int args = mv.newLocal(Type.getType("[Ljava/lang/Object;"));
+ mv.storeLocal(args);
+ for (int i = returnTypes.length - 1; i >= 0; i--) {
+ Type type = returnTypes[i];
+ mv.loadLocal(args);
+ mv.swap(type, Type.getType(Object.class));
+ mv.push(i + 1);
+ mv.swap(type, Type.INT_TYPE);
+ mv.box(type);
+ mv.arrayStore(Type.getType(Object.class));
+ }
+
+ // Store the qualified name of the constructor in the first element of the array.
+ mv.loadLocal(args);
+ mv.push(0);
+ mv.push(delegation.owner + "." + delegation.desc); // Name of the constructor to be called.
+ mv.arrayStore(Type.getType(Object.class));
+
+ mv.loadLocal(args);
+ mv.returnValue();
+
+ newDesc = method.desc.replace("(", "(L" + owner + ";");
+ MethodNode body = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
+ "init$body", newDesc, null, exceptions);
+ LabelNode labelAfter = new LabelNode();
+ labelAfter.accept(body);
+ Set<LabelNode> bodyLabels = new HashSet<LabelNode>();
+
+ insn = delegation.getNext();
+ while (insn != null) {
+ if (insn instanceof LabelNode) {
+ bodyLabels.add((LabelNode) insn);
+ }
+ insn.accept(body);
+ insn = insn.getNext();
+ }
+
+ // manually transfer the exception table from the existing constructor to the new
+ // "init$body" method. The labels were transferred just above so we can reuse them.
+
+ //noinspection unchecked
+ for (TryCatchBlockNode tryCatch : (List<TryCatchBlockNode>) method.tryCatchBlocks) {
+ tryCatch.accept(body);
+ }
+
+ //noinspection unchecked
+ for (LocalVariableNode variable : (List<LocalVariableNode>) method.localVariables) {
+ boolean startsInBody = bodyLabels.contains(variable.start);
+ boolean endsInBody = bodyLabels.contains(variable.end);
+ if (!startsInBody && !endsInBody) {
+ if (variable.index != 0) { // '#0' on init$args is not 'this'
+ variable.accept(initArgs);
+ }
+ } else if (startsInBody && endsInBody) {
+ variable.accept(body);
+ } else if (!startsInBody && endsInBody) {
+ // The variable spans from the args to the end of the method, create two:
+ if (variable.index != 0) { // '#0' on init$args is not 'this'
+ LocalVariableNode var0 = new LocalVariableNode(variable.name,
+ variable.desc, variable.signature,
+ variable.start, labelBefore, variable.index);
+ var0.accept(initArgs);
+ }
+ LocalVariableNode var1 = new LocalVariableNode(variable.name,
+ variable.desc, variable.signature,
+ labelAfter, variable.end, variable.index);
+ var1.accept(body);
+ } else {
+ throw new IllegalStateException("Local variable starts after it ends.");
+ }
+ }
+
+ return new Constructor(loadThis, loadThisLine, initArgs, delegation, body);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalChangeVisitor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalChangeVisitor.java
new file mode 100644
index 0000000..ffaf0c3
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalChangeVisitor.java
@@ -0,0 +1,1073 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+/**
+ * Visitor for classes that have been changed since the initial push.
+ *
+ * This will generate a new class which name is the original class name + Support. This class will
+ * have a static method for each method found in the updated class.
+ *
+ * The static method will be invoked from the generated access$dispatch method
+ * following a delegation request issued by the original method implementation (through the bytecode
+ * injection done in {@link IncrementalSupportVisitor}.
+ *
+ * So far the static method implementation do not require any change since the "this" parameter
+ * is passed as the first parameter and is available in register 0.
+ */
+public class IncrementalChangeVisitor extends IncrementalVisitor {
+
+ public static final VisitorBuilder VISITOR_BUILDER = new IncrementalVisitor.VisitorBuilder() {
+ @NonNull
+ @Override
+ public IncrementalVisitor build(@NonNull ClassNode classNode,
+ @NonNull List<ClassNode> parentNodes,
+ @NonNull ClassVisitor classVisitor) {
+ return new IncrementalChangeVisitor(classNode, parentNodes, classVisitor);
+ }
+
+ @NonNull
+ @Override
+ public String getMangledRelativeClassFilePath(@NonNull String path) {
+ // Remove .class (length 6) and replace with $override.class
+ return path.substring(0, path.length() - 6) + OVERRIDE_SUFFIX + ".class";
+ }
+
+ @NonNull
+ @Override
+ public OutputType getOutputType() {
+ return OutputType.OVERRIDE;
+ }
+ };
+
+ // todo : find a better way to specify logging and append to a log file.
+ private static final boolean DEBUG = false;
+
+ @VisibleForTesting
+ public static final String OVERRIDE_SUFFIX = "$override";
+
+ private static final String METHOD_MANGLE_PREFIX = "static$";
+
+ private MachineState state = MachineState.NORMAL;
+ private boolean instantRunDisabled = false;
+
+ // Description prefix used to add fake "this" as the first argument to each instance method
+ // when converted to a static method.
+ private String instanceToStaticDescPrefix;
+
+ // List of constructors we encountered and deconstructed.
+ List<MethodNode> addedMethods = new ArrayList<MethodNode>();
+
+ private enum MachineState {
+ NORMAL, AFTER_NEW
+ }
+
+ public IncrementalChangeVisitor(
+ @NonNull ClassNode classNode,
+ @NonNull List<ClassNode> parentNodes,
+ @NonNull ClassVisitor classVisitor) {
+ super(classNode, parentNodes, classVisitor);
+ }
+
+ /**
+ * Turns this class into an override class that can be loaded by our custom class loader:
+ *<ul>
+ * <li>Make the class name be OriginalName$override</li>
+ * <li>Ensure the class derives from java.lang.Object, no other inheritance</li></li>
+ * <li>Ensure the class has a public parameterless constructor that is a noop.</li></li>
+ *</ul>
+ */
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER,
+ name + OVERRIDE_SUFFIX, signature, "java/lang/Object",
+ new String[]{CHANGE_TYPE.getInternalName()});
+
+ if (DEBUG) {
+ System.out.println(">>>>>>>> Processing " + name + "<<<<<<<<<<<<<");
+ }
+
+ visitedClassName = name;
+ visitedSuperName = superName;
+ instanceToStaticDescPrefix = "(L" + visitedClassName + ";";
+
+ // Create empty constructor
+ MethodVisitor mv = super
+ .visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V",
+ false);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ super.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC,
+ "$obsolete", "Z", null, null);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (DISABLE_ANNOTATION_TYPE.getDescriptor().equals(desc)) {
+ instantRunDisabled = true;
+ }
+ return super.visitAnnotation(desc, visible);
+ }
+
+ /**
+ * Generates new delegates for all 'patchable' methods in the visited class. Delegates
+ * are static methods that do the same thing the visited code does, but from outside the class.
+ * For instance methods, the instance is passed as the first argument. Note that:
+ * <ul>
+ * <li>We ignore the class constructor as we don't support it right now</li>
+ * <li>We skip abstract methods.</li>
+ * <li>For constructors split the method body into super arguments and the rest of
+ * the method body, see {@link ConstructorDelegationDetector}</li>
+ * </ul>
+ */
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+
+ if (instantRunDisabled || !isAccessCompatibleWithInstantRun(access)) {
+ // Nothing to generate.
+ return null;
+ }
+ if (name.equals("<clinit>")) {
+ // we skip the class init as it can reset static fields which we don't support right now
+ return null;
+ }
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ String newDesc = computeOverrideMethodDesc(desc, isStatic);
+
+ if (DEBUG) {
+ System.out.println(">>> Visiting method " + visitedClassName + ":" + name + ":" + desc);
+ if (exceptions != null) {
+ for (String exception : exceptions) {
+ System.out.println("> Exception thrown : " + exception);
+ }
+ }
+ }
+ if (DEBUG) {
+ System.out.println("New Desc is " + newDesc + ":" + isStatic);
+ }
+
+ // Do not carry on any access flags from the original method. For example synchronized
+ // on the original method would translate into a static synchronized method here.
+ access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
+ MethodNode method = getMethodByNameInClass(name, desc, classNode);
+ if (name.equals("<init>")) {
+ ConstructorDelegationDetector.Constructor constructor =
+ ConstructorDelegationDetector.deconstruct(visitedClassName, method);
+
+ MethodVisitor original = super.visitMethod(access, constructor.args.name, constructor.args.desc, constructor.args.signature, exceptions);
+ ISVisitor mv = new ISVisitor(original, access, constructor.args.name, constructor.args.desc, isStatic, true /* isConstructor */);
+ constructor.args.accept(mv);
+
+ original = super.visitMethod(access, constructor.body.name, constructor.body.desc, constructor.body.signature, exceptions);
+ mv = new ISVisitor(original, access, constructor.body.name, newDesc, isStatic, true /* isConstructor */);
+ constructor.body.accept(mv);
+
+ // Remember our created methods so we can generated the access$dispatch for them.
+ addedMethods.add(constructor.args);
+ addedMethods.add(constructor.body);
+ return null;
+ } else {
+ String newName = isStatic ? computeOverrideMethodName(name, desc) : name;
+ MethodVisitor original = super.visitMethod(access, newName, newDesc, signature, exceptions);
+ return new ISVisitor(original, access, newName, newDesc, isStatic, false /* isConstructor */);
+ }
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ // do not add any of the original class fields in the $override class, they would never
+ // be used and confuse the debugger.
+ return null;
+ }
+
+ public class ISVisitor extends GeneratorAdapter {
+
+ private final boolean isStatic;
+ private final boolean isConstructor;
+
+ /**
+ * Instrument a method.
+ * @param mv the parent method visitor.
+ * @param access the method access flags.
+ * @param name method name.
+ * @param desc method signature.
+ * @param isStatic true if the instrumented method was originally a static method.
+ * @param isConstructor true if the instrumented code was originally a constructor body.
+ */
+ public ISVisitor(
+ MethodVisitor mv,
+ int access,
+ String name,
+ String desc,
+ boolean isStatic,
+ boolean isConstructor) {
+ super(Opcodes.ASM5, mv, access, name, desc);
+ this.isStatic = isStatic;
+ this.isConstructor = isConstructor;
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (DEBUG) {
+ System.out.println(
+ "Visit field access : " + owner + ":" + name + ":" + desc + ":" + isStatic);
+ }
+ AccessRight accessRight;
+ if (!owner.equals(visitedClassName)) {
+ if (DEBUG) {
+ System.out.println(owner + ":" + name + " field access");
+ }
+ // we are accessing another object field, and at this point the visitor is not smart
+ // enough to know if has seen this class before or not so we must assume the field
+ // is *not* accessible from the $override class which lives in a different
+ // hierarchy and package.
+ // However, since we made all package-private and protected fields public, and it
+ // cannot be private since the visitedClassName is not the "owner", we can safely
+ // assume it's public.
+ accessRight = AccessRight.PUBLIC;
+ } else {
+ // check the field access bits.
+ FieldNode fieldNode = getFieldByName(name);
+ if (fieldNode == null) {
+ // If this is an inherited field, we might not have had access to the parent
+ // bytecode. In such a case, treat it as private.
+ accessRight = AccessRight.PACKAGE_PRIVATE;
+ } else {
+ accessRight = AccessRight.fromNodeAccess(fieldNode.access);
+ }
+ }
+
+ boolean handled = false;
+ switch(opcode) {
+ case Opcodes.PUTSTATIC:
+ case Opcodes.GETSTATIC:
+ handled = visitStaticFieldAccess(opcode, owner, name, desc, accessRight);
+ break;
+ case Opcodes.PUTFIELD:
+ case Opcodes.GETFIELD:
+ handled = visitFieldAccess(opcode, owner, name, desc, accessRight);
+ break;
+ default:
+ System.out.println("Unhandled field opcode " + opcode);
+ }
+ if (!handled) {
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ /**
+ * Visits an instance field access. The field could be of the visited class or it could be
+ * an accessible field from the class being visited (unless it's private).
+ * <p/>
+ * For private instance fields, the access instruction is rewritten to calls to reflection
+ * to access the fields value:
+ * <p/>
+ * Pseudo code for Get:
+ * <code>
+ * value = $instance.fieldName;
+ * </code>
+ * becomes:
+ * <code>
+ * value = (unbox)$package/AndroidInstantRuntime.getPrivateField($instance, $fieldName);
+ * </code>
+ * <p/>
+ * Pseudo code for Set:
+ * <code>
+ * $instance.fieldName = value;
+ * </code>
+ * becomes:
+ * <code>
+ * $package/AndroidInstantRuntime.setPrivateField($instance, value, $fieldName);
+ * </code>
+ *
+ *
+ * @param opcode the field access opcode, can only be {@link Opcodes#PUTFIELD} or
+ * {@link Opcodes#GETFIELD}
+ * @param owner the field declaring class
+ * @param name the field name
+ * @param desc the field type
+ * @param accessRight the {@link AccessRight} for the field.
+ * @return true if the field access was handled or false otherwise.
+ */
+ private boolean visitFieldAccess(
+ int opcode, String owner, String name, String desc, AccessRight accessRight) {
+
+ // if the accessed field is anything but public, we must go through reflection.
+ boolean useReflection = accessRight != AccessRight.PUBLIC;
+
+ // if the accessed field is accessed from within a constructor, it might be a public
+ // final field that cannot be set by anything but the original constructor unless
+ // we use reflection.
+ if (!useReflection) {
+ useReflection = isConstructor && (owner.equals(visitedClassName));
+ }
+
+ if (useReflection) {
+ // we should make this more efficient, have a per field access type method
+ // for getting and setting field values.
+ switch (opcode) {
+ case Opcodes.GETFIELD:
+ if (DEBUG) {
+ System.out.println("Get field");
+ }
+ // push declaring class
+ visitLdcInsn(Type.getType("L" + owner + ";"));
+
+ // the instance of the owner class we are getting the field value from
+ // is on top of the stack. It could be "this"
+ push(name);
+
+ // Stack : <receiver>
+ // <field_declaring_class>
+ // <field_name>
+ invokeStatic(RUNTIME_TYPE,
+ Method.getMethod("Object getPrivateField(Object, Class, String)"));
+ // Stack : <field_value>
+ ByteCodeUtils.unbox(this, Type.getType(desc));
+ break;
+ case Opcodes.PUTFIELD:
+ if (DEBUG) {
+ System.out.println("Set field");
+ }
+ // the instance of the owner class we are getting the field value from
+ // is second on the stack. It could be "this"
+ // top of the stack is the new value we are trying to set, box it.
+ box(Type.getType(desc));
+
+ // push declaring class
+ visitLdcInsn(Type.getType("L" + owner + ";"));
+ // push the field name.
+ push(name);
+ // Stack : <receiver>
+ // <boxed_field_value>
+ // <field_declaring_class>
+ // <field_name>
+ invokeStatic(RUNTIME_TYPE,
+ Method.getMethod(
+ "void setPrivateField(Object, Object, Class, String)"));
+ break;
+ default:
+ throw new RuntimeException(
+ "VisitFieldAccess called with wrong opcode " + opcode);
+ }
+ return true;
+ }
+ // if this is a public field, no need to change anything we can access it from the
+ // $override class.
+ return false;
+ }
+
+ /**
+ * Static field access visit.
+ * So far we do not support class initializer "clinit" that would reset the static field
+ * value in the class newer versions. Think about the case, where a static initializer
+ * resets a static field value, we don't know if the current field value was set through
+ * the initial class initializer or some code path, should we change the field value to the
+ * new one ?
+ *
+ * For private static fields, the access instruction is rewritten to calls to reflection
+ * to access the fields value:
+ * <p/>
+ * Pseudo code for Get:
+ * <code>
+ * value = $type.fieldName;
+ * </code>
+ * becomes:
+ * <code>
+ * value = (unbox)$package/AndroidInstantRuntime.getStaticPrivateField(
+ * $type.class, $fieldName);
+ * </code>
+ * <p/>
+ * Pseudo code for Set:
+ * <code>
+ * $type.fieldName = value;
+ * </code>
+ * becomes:
+ * <code>
+ * $package/AndroidInstantRuntime.setStaticPrivateField(value, $type.class $fieldName);
+ * </code>
+ *
+ * @param opcode the field access opcode, can only be {@link Opcodes#PUTSTATIC} or
+ * {@link Opcodes#GETSTATIC}
+ * @param name the field name
+ * @param desc the field type
+ * @param accessRight the {@link AccessRight} for the field.
+ * @return true if the field access was handled or false
+ */
+ private boolean visitStaticFieldAccess(
+ int opcode, String owner, String name, String desc, AccessRight accessRight) {
+
+ if (accessRight != AccessRight.PUBLIC) {
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ if (DEBUG) {
+ System.out.println("Get static field " + name);
+ }
+ // nothing of interest is on the stack.
+ visitLdcInsn(Type.getType("L" + owner + ";"));
+ push(name);
+ // Stack : <target_class>
+ // <field_name>
+ invokeStatic(RUNTIME_TYPE,
+ Method.getMethod("Object getStaticPrivateField(Class, String)"));
+ // Stack : <field_value>
+ ByteCodeUtils.unbox(this, Type.getType(desc));
+ return true;
+ case Opcodes.PUTSTATIC:
+ if (DEBUG) {
+ System.out.println("Set static field " + name);
+ }
+ // the new field value is on top of the stack.
+ // box it into an Object.
+ box(Type.getType(desc));
+ visitLdcInsn(Type.getType("L" + owner + ";"));
+ push(name);
+ // Stack : <boxed_field_value>
+ // <target_class>
+ // <field_name>
+ invokeStatic(RUNTIME_TYPE,
+ Method.getMethod(
+ "void setStaticPrivateField(Object, Class, String)"));
+ return true;
+ default:
+ throw new RuntimeException(
+ "VisitStaticFieldAccess called with wrong opcode " + opcode);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
+
+ if (DEBUG) {
+ System.out.println("Generic Method dispatch : " + opcode +
+ ":" + owner + ":" + name + ":" + desc + ":" + itf + ":" + isStatic);
+ }
+ boolean opcodeHandled = false;
+ if (opcode == Opcodes.INVOKESPECIAL) {
+ opcodeHandled = handleSpecialOpcode(owner, name, desc, itf);
+ } else if (opcode == Opcodes.INVOKEVIRTUAL) {
+ opcodeHandled = handleVirtualOpcode(owner, name, desc, itf);
+ } else if (opcode == Opcodes.INVOKESTATIC) {
+ opcodeHandled = handleStaticOpcode(owner, name, desc, itf);
+ }
+ if (DEBUG) {
+ System.out.println("Opcode handled ? " + opcodeHandled);
+ }
+ if (!opcodeHandled) {
+ mv.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ if (DEBUG) {
+ System.out.println("Done with generic method dispatch");
+ }
+ }
+
+ /**
+ * Rewrites INVOKESPECIAL method calls:
+ * <ul>
+ * <li>calls to constructors are handled specially (see below)</li>
+ * <li>calls to super methods are rewritten to call the 'access$super' trampoline we
+ * injected into the original code</li>
+ * <li>calls to methods in this class are rewritten to call the mathcin $override class
+ * static method</li>
+ * </ul>
+ */
+ private boolean handleSpecialOpcode(String owner, String name, String desc,
+ boolean itf) {
+ if (name.equals("<init>")) {
+ return handleConstructor(owner, name, desc);
+ }
+ if (owner.equals(visitedClassName)) {
+ if (DEBUG) {
+ System.out.println(
+ "Private Method : " + name + ":" + desc + ":" + itf + ":" + isStatic);
+ }
+ // private method dispatch, just invoke the $override class static method.
+ String newDesc = computeOverrideMethodDesc(desc, false /*isStatic*/);
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, owner + "$override", name, newDesc, itf);
+ return true;
+ } else {
+ if (DEBUG) {
+ System.out.println(
+ "Super Method : " + name + ":" + desc + ":" + itf + ":" + isStatic);
+ }
+ int arr = boxParametersToNewLocalArray(Type.getArgumentTypes(desc));
+ push(name + "." + desc);
+ loadLocal(arr);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, visitedClassName, "access$super",
+ instanceToStaticDescPrefix
+ + "Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ handleReturnType(desc);
+
+ return true;
+ }
+ }
+
+ /**
+ * Rewrites INVOKEVIRTUAL method calls.
+ * <p/>
+ * Virtual calls to protected methods are rewritten according to the following pseudo code:
+ * before:
+ * <code>
+ * $value = $instance.protectedVirtual(arg1, arg2);
+ * </code>
+ * after:
+ * <code>
+ * $value = (unbox)$package/AndroidInstantRuntime.invokeProtectedMethod($instance,
+ * new object[] {arg1, arg2}, new Class[] { String.class, Integer.class },
+ * "protectedVirtual");
+ * </code>
+ */
+ private boolean handleVirtualOpcode(String owner, String name, String desc, boolean itf) {
+
+ if (DEBUG) {
+ System.out.println(
+ "Virtual Method : " + name + ":" + desc + ":" + itf + ":" + isStatic);
+
+ }
+ AccessRight accessRight = getMethodAccessRight(owner, name, desc);
+ if (accessRight == AccessRight.PUBLIC) {
+ return false;
+ }
+
+ // for anything else, private, protected and package private, we must go through
+ // reflection.
+ // Stack : <receiver>
+ // <param_1>
+ // <param_2>
+ // ...
+ // <param_n>
+ pushMethodRedirectArgumentsOnStack(name, desc);
+
+ // Stack : <receiver>
+ // <array of parameter_values>
+ // <array of parameter_types>
+ // <method_name>
+ invokeStatic(RUNTIME_TYPE, Method.getMethod(
+ "Object invokeProtectedMethod(Object, Object[], Class[], String)"));
+ // Stack : <return value or null if no return value>
+ handleReturnType(desc);
+ return true;
+ }
+
+ /**
+ * Rewrites INVOKESTATIC method calls.
+ * <p/>
+ * Static calls to non-public methods are rewritten according to the following pseudo code:
+ * before:
+ * <code>
+ * $value = $type.protectedStatic(arg1, arg2);
+ * </code>
+ * after:
+ * <code>
+ * $value = (unbox)$package/AndroidInstantRuntime.invokeProtectedStaticMethod(
+ * new object[] {arg1, arg2}, new Class[] { String.class, Integer.class },
+ * "protectedStatic", $type.class);
+ * </code>
+ */
+ private boolean handleStaticOpcode(String owner, String name, String desc, boolean itf) {
+
+ if (DEBUG) {
+ System.out.println(
+ "Static Method : " + name + ":" + desc + ":" + itf + ":" + isStatic);
+
+ }
+ AccessRight accessRight = getMethodAccessRight(owner, name, desc);
+ if (accessRight == AccessRight.PUBLIC) {
+ return false;
+ }
+
+ // for anything else, private, protected and package private, we must go through
+ // reflection.
+
+ // stack: <param_1>
+ // <param_2>
+ // ...
+ // <param_n>
+ pushMethodRedirectArgumentsOnStack(name, desc);
+
+ // push the class implementing the original static method
+ visitLdcInsn(Type.getType("L" + owner + ";"));
+
+ // stack: <boxed method parameter>
+ // <target parameter types>
+ // <target method name>
+ // <target class name>
+ invokeStatic(RUNTIME_TYPE, Method.getMethod(
+ "Object invokeProtectedStaticMethod(Object[], Class[], String, Class)"));
+ // stack : method return value or null if the method was VOID.
+ handleReturnType(desc);
+ return true;
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String s) {
+ if (opcode == Opcodes.NEW) {
+ // state can only normal or dup_after new
+ if (state == MachineState.AFTER_NEW) {
+ throw new RuntimeException("Panic, two NEW opcode without a DUP");
+ }
+
+ if (isInSamePackage(s)) {
+ // this is a new allocation in the same package, this could be protected or
+ // package private class, we must go through reflection, otherwise not.
+ // set our state so we swallow the next DUP we encounter.
+ state = MachineState.AFTER_NEW;
+
+ // swallow the NEW, we will also swallow the DUP associated with the new
+ return;
+ }
+ }
+ super.visitTypeInsn(opcode, s);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ // check the last object allocation we encountered, if this is in the same package
+ // we need to go through reflection and should therefore remove the DUP, otherwise
+ // we leave it.
+ if (opcode == Opcodes.DUP && state == MachineState.AFTER_NEW) {
+
+ state = MachineState.NORMAL;
+ return;
+ }
+ super.visitInsn(opcode);
+ }
+
+ /**
+ * For calls to constructors in the same package, calls are rewritten to use reflection
+ * to create the instance (see above, the NEW & DUP instructions are also removed) using
+ * the following pseudo code.
+ * <p/>
+ * before:
+ * <code>
+ * $value = new $type(arg1, arg2);
+ * </code>
+ * after:
+ * <code>
+ * $value = ($type)$package/AndroidInstantRuntime.newForClass(new Object[] {arg1, arg2 },
+ * new Class[]{ String.class, Integer.class }, $type.class);
+ * </code>
+ *
+ */
+ private boolean handleConstructor(String owner, String name, String desc) {
+
+ if (isInSamePackage(owner)) {
+
+ Type expectedType = Type.getType("L" + owner + ";");
+ pushMethodRedirectArgumentsOnStack(name, desc);
+
+ // pop the name, we don't need it.
+ pop();
+ visitLdcInsn(expectedType);
+
+ invokeStatic(RUNTIME_TYPE, Method.getMethod(
+ "Object newForClass(Object[], Class[], Class)"));
+
+ checkCast(expectedType);
+ ByteCodeUtils.unbox(this, expectedType);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature, Label start,
+ Label end, int index) {
+ // Even if we call the first argument of the static redirection "this", JDI has a
+ // specific API to retrieve "thisObject" from the current stack frame, which totally
+ // ignores and bypasses this variable declaration. We will not show the renamed
+ // variable to the user and will redirect in Studio to be the real this object.
+ // We use a name unlikely to be used, but different than "this".
+ if ("this".equals(name)) {
+ name = "$this";
+ }
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (DEBUG) {
+ System.out.println("Method visit end");
+ }
+ }
+
+ /**
+ * Returns the actual method access right or a best guess if we don't have access to the
+ * method definition.
+ * @param owner the method owner class
+ * @param name the method name
+ * @param desc the method signature
+ * @return the {@link AccessRight} for that method.
+ */
+ private AccessRight getMethodAccessRight(String owner, String name, String desc) {
+ AccessRight accessRight;
+ if (owner.equals(visitedClassName)) {
+ MethodNode methodByName = getMethodByName(name, desc);
+ if (methodByName == null) {
+ // we did not find the method invoked on ourselves, which mean that it really
+ // is a parent class method invocation and we just don't have access to it.
+ // the most restrictive access right in that case is protected.
+ return AccessRight.PROTECTED;
+ }
+ accessRight = AccessRight.fromNodeAccess(methodByName.access);
+ } else {
+ // we are accessing another class method, and since we make all protected and
+ // package-private methods public, we can safely assume it is public.
+ accessRight = AccessRight.PUBLIC;
+ }
+ return accessRight;
+ }
+
+ /**
+ * Push arguments necessary to invoke one of the method redirect function :
+ * <ul>{@link GenericInstantRuntime#invokeProtectedMethod(Object, Object[], Class[], String)}</ul>
+ * <ul>{@link GenericInstantRuntime#invokeProtectedStaticMethod(Object[], Class[], String, Class)}</ul>
+ *
+ * This function will only push on the stack the three common arguments :
+ * Object[] the boxed parameter values
+ * Class[] the parameter types
+ * String the original method name
+ *
+ * Stack before :
+ * <param1>
+ * <param2>
+ * ...
+ * <paramN>
+ * Stack After :
+ * <array of parameters>
+ * <array of parameter types>
+ * <method name>
+ * @param name the original method name.
+ * @param desc the original method signature.
+ */
+ private void pushMethodRedirectArgumentsOnStack(String name, String desc) {
+ Type[] parameterTypes = Type.getArgumentTypes(desc);
+
+ // stack : <parameters values>
+ int parameters = boxParametersToNewLocalArray(parameterTypes);
+ // push the parameter values as a Object[] on the stack.
+ loadLocal(parameters);
+
+ // push the parameter types as a Class[] on the stack
+ pushParameterTypesOnStack(parameterTypes);
+
+ push(name);
+ }
+
+ /**
+ * Creates an array of {@link Class} objects with the same size of the array of the passed
+ * parameter types. For each parameter type, stores its {@link Class} object into the
+ * result array. For intrinsic types which are not present in the class constant pool, just
+ * push the actual {@link Type} object on the stack and let ASM do the rest. For non
+ * intrinsic type use a {@link MethodVisitor#visitLdcInsn(Object)} to ensure the
+ * referenced class's presence in this class constant pool.
+ *
+ * Stack Before : nothing of interest
+ * Stack After : <array of {@link Class}>
+ *
+ * @param parameterTypes a method list of parameters.
+ */
+ private void pushParameterTypesOnStack(Type[] parameterTypes) {
+ push(parameterTypes.length);
+ newArray(Type.getType(Class.class));
+
+ for (int i = 0; i < parameterTypes.length; i++) {
+ dup();
+ push(i);
+ switch(parameterTypes[i].getSort()) {
+ case Type.OBJECT:
+ case Type.ARRAY:
+ visitLdcInsn(parameterTypes[i]);
+ break;
+ case Type.BOOLEAN:
+ case Type.CHAR:
+ case Type.BYTE:
+ case Type.SHORT:
+ case Type.INT:
+ case Type.LONG:
+ case Type.FLOAT:
+ case Type.DOUBLE:
+ push(parameterTypes[i]);
+ break;
+ default:
+ throw new RuntimeException(
+ "Unexpected parameter type " + parameterTypes[i]);
+
+ }
+ arrayStore(Type.getType(Class.class));
+ }
+ }
+
+ /**
+ * Handle method return logic.
+ * @param desc the method signature
+ */
+ private void handleReturnType(String desc) {
+ Type ret = Type.getReturnType(desc);
+ if (ret.getSort() == Type.VOID) {
+ pop();
+ } else {
+ ByteCodeUtils.unbox(this, ret);
+ }
+ }
+
+ private int boxParametersToNewLocalArray(Type[] parameterTypes) {
+ int parameters = newLocal(Type.getType("[Ljava/lang.Object;"));
+ push(parameterTypes.length);
+ newArray(Type.getType(Object.class));
+ storeLocal(parameters);
+
+ for (int i = parameterTypes.length - 1; i >= 0; i--) {
+ loadLocal(parameters);
+ swap(parameterTypes[i], Type.getType(Object.class));
+ push(i);
+ swap(parameterTypes[i], Type.INT_TYPE);
+ box(parameterTypes[i]);
+ arrayStore(Type.getType(Object.class));
+ }
+ return parameters;
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ addDispatchMethod();
+ }
+
+ /**
+ * To each class, add the dispatch method called by the original code that acts as a trampoline to
+ * invoke the changed methods.
+ * <p/>
+ * Pseudo code:
+ * <code>
+ * Object access$dispatch(String name, object[] args) {
+ * if (name.equals(
+ * "firstMethod.(L$type;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;")) {
+ * return firstMethod(($type)arg[0], (String)arg[1], arg[2]);
+ * }
+ * if (name.equals("secondMethod.(L$type;Ljava/lang/String;I;)V")) {
+ * secondMethod(($type)arg[0], (String)arg[1], (int)arg[2]);
+ * return;
+ * }
+ * ...
+ * StringBuilder $local1 = new StringBuilder();
+ * $local1.append("Method not found ");
+ * $local1.append(name);
+ * $local1.append(" in " + visitedClassName +
+ * "$dispatch implementation, restart the application");
+ * throw new $package/InstantReloadException($local1.toString());
+ * }
+ * </code>
+ */
+ private void addDispatchMethod() {
+ int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_VARARGS;
+ Method m = new Method("access$dispatch", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
+ MethodVisitor visitor = super.visitMethod(access,
+ m.getName(),
+ m.getDescriptor(),
+ null, null);
+
+ final GeneratorAdapter mv = new GeneratorAdapter(access, m, visitor);
+
+ if (TRACING_ENABLED) {
+ mv.push("Redirecting ");
+ mv.loadArg(0);
+ trace(mv, 2);
+ }
+
+ List<MethodNode> allMethods = new ArrayList<MethodNode>();
+
+ // if we are disabled, do not generate any dispatch, the method will throw an exception
+ // if invoked which should never happen.
+ if (!instantRunDisabled) {
+ //noinspection unchecked
+ allMethods.addAll(classNode.methods);
+ allMethods.addAll(addedMethods);
+ }
+
+ final Map<String, MethodNode> methods = new HashMap<String, MethodNode>();
+ for (MethodNode methodNode : allMethods) {
+ if (methodNode.name.equals("<clinit>") || methodNode.name.equals("<init>")) {
+ continue;
+ }
+ if (!isAccessCompatibleWithInstantRun(methodNode.access)) {
+ continue;
+ }
+ methods.put(methodNode.name + "." + methodNode.desc, methodNode);
+ }
+
+ new StringSwitch() {
+ @Override
+ void visitString() {
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ }
+
+ @Override
+ void visitCase(String methodName) {
+ MethodNode methodNode = methods.get(methodName);
+ String name = methodNode.name;
+ boolean isStatic = (methodNode.access & Opcodes.ACC_STATIC) != 0;
+ String newDesc =
+ computeOverrideMethodDesc(methodNode.desc, isStatic);
+
+ if (TRACING_ENABLED) {
+ trace(mv, "M: " + name + " P:" + newDesc);
+ }
+ Type[] args = Type.getArgumentTypes(newDesc);
+ int argc = 0;
+ for (Type t : args) {
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.push(argc);
+ mv.visitInsn(Opcodes.AALOAD);
+ ByteCodeUtils.unbox(mv, t);
+ argc++;
+ }
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, visitedClassName + "$override",
+ isStatic ? computeOverrideMethodName(name, methodNode.desc) : name,
+ newDesc, false);
+ Type ret = Type.getReturnType(methodNode.desc);
+ if (ret.getSort() == Type.VOID) {
+ mv.visitInsn(Opcodes.ACONST_NULL);
+ } else {
+ mv.box(ret);
+ }
+ mv.visitInsn(Opcodes.ARETURN);
+ }
+
+ @Override
+ void visitDefault() {
+ writeMissingMessageWithHash(mv, visitedClassName);
+ }
+ }.visit(mv, methods.keySet());
+
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ super.visitEnd();
+ }
+
+ /**
+ * Command line invocation entry point. Expects 2 parameters, first is the source directory
+ * with .class files as produced by the Java compiler, second is the output directory where to
+ * store the bytecode enhanced version.
+ * @param args the command line arguments.
+ * @throws IOException if some files cannot be read or written.
+ */
+ public static void main(String[] args) throws IOException {
+ IncrementalVisitor.main(args, VISITOR_BUILDER);
+ }
+
+ /**
+ * Returns true if the passed class name is in the same package as the visited class.
+ *
+ * @param type The type name of the other object, either a "com/var/Object" or a "[Type" one.
+ * @return true if className and visited class are in the same java package.
+ */
+ private boolean isInSamePackage(@NonNull String type) {
+ if (type.charAt(0) == '[') {
+ return false;
+ }
+ return getPackage(visitedClassName).equals(getPackage(type));
+ }
+
+ /**
+ * @return the package of the given / separated class name.
+ */
+ private String getPackage(@NonNull String className) {
+ int i = className.lastIndexOf('/');
+ return i == -1 ? className : className.substring(0, i);
+ }
+
+ /**
+ * Returns true if the passed class name is an ancestor of the visited class.
+ *
+ * @param className a / separated class name
+ * @return true if it is an ancestor, false otherwise.
+ */
+ private boolean isAnAncestor(@NonNull String className) {
+ for (ClassNode parentNode : parentNodes) {
+ if (parentNode.name.equals(className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Instance methods, when converted to static methods need to have the subject object as
+ * the first parameter. If the method is static, it is unchanged.
+ */
+ @NonNull
+ private String computeOverrideMethodDesc(@NonNull String desc, boolean isStatic) {
+ if (isStatic) {
+ return desc;
+ } else {
+ return instanceToStaticDescPrefix + desc.substring(1);
+ }
+ }
+
+ /**
+ * Prevent method name collisions.
+ *
+ * A static method that takes an instance of this class as the first argument might clash with
+ * a rewritten instance method, and this rewrites all methods like that. This is an
+ * over-approximation of the necessary renames, but it has the advantage of neither adding
+ * additional state nor requiring lookups.
+ */
+ @NonNull
+ private String computeOverrideMethodName(@NonNull String name, @NonNull String desc) {
+ if (desc.startsWith(instanceToStaticDescPrefix) && !name.equals("init$body")) {
+ return METHOD_MANGLE_PREFIX + name;
+ }
+ return name;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalSupportVisitor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalSupportVisitor.java
new file mode 100644
index 0000000..5abbe01
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalSupportVisitor.java
@@ -0,0 +1,614 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.utils.AsmUtils;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LineNumberNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Visitor for classes that will eventually be replaceable at runtime.
+ *
+ * Since classes cannot be replaced in an existing class loader, we use a delegation model to
+ * redirect any method implementation to the AndroidInstantRuntime.
+ *
+ * This redirection happens only when a new class implementation is available. A new version
+ * will register itself in a static synthetic field called $change. Each method will be enhanced
+ * with a piece of code to check if a new version is available by looking at the $change field
+ * and redirect if necessary.
+ *
+ * Redirection will be achieved by calling a
+ * {@link IncrementalChange#access$dispatch(String, Object...)} method.
+ */
+public class IncrementalSupportVisitor extends IncrementalVisitor {
+
+ private boolean disableRedirectionForClass = false;
+
+ private static final class VisitorBuilder implements IncrementalVisitor.VisitorBuilder {
+
+ private VisitorBuilder() {
+ }
+
+ @NonNull
+ @Override
+ public IncrementalVisitor build(
+ @NonNull ClassNode classNode,
+ @NonNull List<ClassNode> parentNodes,
+ @NonNull ClassVisitor classVisitor) {
+ return new IncrementalSupportVisitor(classNode, parentNodes, classVisitor);
+ }
+
+ @Override
+ @NonNull
+ public String getMangledRelativeClassFilePath(@NonNull String originalClassFilePath) {
+ return originalClassFilePath;
+ }
+
+ @NonNull
+ @Override
+ public OutputType getOutputType() {
+ return OutputType.INSTRUMENT;
+ }
+ }
+
+ public static final IncrementalVisitor.VisitorBuilder VISITOR_BUILDER =
+ new VisitorBuilder();
+
+ public IncrementalSupportVisitor(
+ @NonNull ClassNode classNode,
+ @NonNull List<ClassNode> parentNodes,
+ @NonNull ClassVisitor classVisitor) {
+ super(classNode, parentNodes, classVisitor);
+ }
+
+ /**
+ * Ensures that the class contains a $change field used for referencing the
+ * IncrementalChange dispatcher.
+ * <p/>
+ * Also updates package_private visiblity to public so we can call into this class from
+ * outside the package.
+ */
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ visitedClassName = name;
+ visitedSuperName = superName;
+
+ super.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+ | Opcodes.ACC_VOLATILE | Opcodes.ACC_SYNTHETIC,
+ "$change", getRuntimeTypeName(CHANGE_TYPE), null, null);
+ access = transformClassAccessForInstantRun(access);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (desc.equals(DISABLE_ANNOTATION_TYPE.getDescriptor())) {
+ disableRedirectionForClass = true;
+ }
+ return super.visitAnnotation(desc, visible);
+ }
+
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+
+ access = transformAccessForInstantRun(access);
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ /**
+ * Insert Constructor specific logic({@link ConstructorArgsRedirection} and
+ * {@link ConstructorDelegationDetector}) for constructor redirecting or
+ * normal method redirecting ({@link MethodRedirection}) for other methods.
+ */
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+
+ access = transformAccessForInstantRun(access);
+
+ MethodVisitor defaultVisitor = super.visitMethod(access, name, desc, signature, exceptions);
+ MethodNode method = getMethodByNameInClass(name, desc, classNode);
+ // does the method use blacklisted APIs.
+ boolean hasIncompatibleChange = InstantRunMethodVerifier.verifyMethod(method)
+ != InstantRunVerifierStatus.COMPATIBLE;
+
+ if (hasIncompatibleChange || disableRedirectionForClass
+ || !isAccessCompatibleWithInstantRun(access)
+ || name.equals(AsmUtils.CLASS_INITIALIZER)) {
+ return defaultVisitor;
+ } else {
+ ISMethodVisitor mv = new ISMethodVisitor(defaultVisitor, access, name, desc);
+ if (name.equals(AsmUtils.CONSTRUCTOR)) {
+
+ ConstructorDelegationDetector.Constructor constructor =
+ ConstructorDelegationDetector.deconstruct(visitedClassName, method);
+ LabelNode start = new LabelNode();
+ LabelNode after = new LabelNode();
+ method.instructions.insert(constructor.loadThis, start);
+ if (constructor.lineForLoad != -1) {
+ // Record the line number from the start of LOAD_0 for uninitialized 'this'.
+ // This allows a breakpoint to be set at the line with this(...) or super(...)
+ // call in the constructor.
+ method.instructions.insert(constructor.loadThis,
+ new LineNumberNode(constructor.lineForLoad, start));
+ }
+ method.instructions.insert(constructor.delegation, after);
+ mv.addRedirection(
+ new ConstructorArgsRedirection(
+ start,
+ visitedClassName,
+ constructor.args.name + "." + constructor.args.desc,
+ after,
+ Type.getArgumentTypes(constructor.delegation.desc)));
+
+ mv.addRedirection(new MethodRedirection(after, constructor.body.name + "."
+ + constructor.body.desc, Type.getReturnType(desc)));
+ } else {
+ mv.addRedirection(new MethodRedirection(
+ new LabelNode(mv.getStartLabel()),
+ name + "." + desc,
+ Type.getReturnType(desc)));
+ }
+ method.accept(mv);
+ return null;
+ }
+ }
+
+ /**
+ * If a class is package private, make it public so instrumented code living in a different
+ * class loader can instantiate them.
+ *
+ * @param access the original class/method/field access.
+ * @return the new access or the same one depending on the original access rights.
+ */
+ private static int transformClassAccessForInstantRun(int access) {
+ AccessRight accessRight = AccessRight.fromNodeAccess(access);
+ return accessRight == AccessRight.PACKAGE_PRIVATE ? access | Opcodes.ACC_PUBLIC : access;
+ }
+
+ /**
+ * If a method/field is not private, make it public. This is to workaround the fact
+ * <ul>Our restart.dex files are loaded with a different class loader than the main dex file
+ * class loader on restart. so we need methods/fields to be public</ul>
+ * <ul>Our reload.dex are loaded from a different class loader as well but methods/fields
+ * are accessed through reflection, yet you need class visibility.</ul>
+ *
+ * remember that in Java, protected methods or fields can be acessed by classes in the same
+ * package :
+ * {@see https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html}
+ *
+ * @param access the original class/method/field access.
+ * @return the new access or the same one depending on the original access rights.
+ */
+ private static int transformAccessForInstantRun(int access) {
+ AccessRight accessRight = AccessRight.fromNodeAccess(access);
+ if (accessRight != AccessRight.PRIVATE) {
+ access &= ~Opcodes.ACC_PROTECTED;
+ access &= ~Opcodes.ACC_PRIVATE;
+ return access | Opcodes.ACC_PUBLIC;
+ }
+ return access;
+ }
+
+ private class ISMethodVisitor extends GeneratorAdapter {
+
+ private boolean disableRedirection = false;
+ private int change;
+ private final List<Type> args;
+ private final List<Redirection> redirections;
+ private final Map<Label, Redirection> resolvedRedirections;
+ private final Label start;
+
+ public ISMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
+ super(Opcodes.ASM5, mv, access, name, desc);
+ this.change = -1;
+ this.redirections = new ArrayList<Redirection>();
+ this.resolvedRedirections = new HashMap<Label, Redirection>();
+ this.args = new ArrayList<Type>(Arrays.asList(Type.getArgumentTypes(desc)));
+ this.start = new Label();
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ // if this is not a static, we add a fictional first parameter what will contain the
+ // "this" reference which can be loaded with ILOAD_0 bytecode.
+ if (!isStatic) {
+ args.add(0, Type.getType(Object.class));
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (desc.equals(DISABLE_ANNOTATION_TYPE.getDescriptor())) {
+ disableRedirection = true;
+ }
+ return super.visitAnnotation(desc, visible);
+ }
+
+ /**
+ * inserts a new local '$change' in each method that contains a reference to the type's
+ * IncrementalChange dispatcher, this is done to avoid threading issues.
+ * <p/>
+ * Pseudo code:
+ * <code>
+ * $package/IncrementalChange $local1 = $className$.$change;
+ * </code>
+ */
+ @Override
+ public void visitCode() {
+ if (!disableRedirection) {
+ // Labels cannot be used directly as they are volatile between different visits,
+ // so we must use LabelNode and resolve before visiting for better performance.
+ for (Redirection redirection : redirections) {
+ resolvedRedirections.put(redirection.getPosition().getLabel(), redirection);
+ }
+
+ super.visitLabel(start);
+ change = newLocal(CHANGE_TYPE);
+ visitFieldInsn(Opcodes.GETSTATIC, visitedClassName, "$change",
+ getRuntimeTypeName(CHANGE_TYPE));
+ storeLocal(change);
+
+ redirectAt(start);
+ }
+ super.visitCode();
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ super.visitLabel(label);
+ redirectAt(label);
+ }
+
+ private void redirectAt(Label label) {
+ if (disableRedirection) return;
+ Redirection redirection = resolvedRedirections.get(label);
+ if (redirection != null) {
+ // A special line number to mark this area of code.
+ super.visitLineNumber(0, label);
+ redirection.redirect(this, change, args);
+ }
+ }
+
+ public void addRedirection(@NonNull Redirection redirection) {
+ redirections.add(redirection);
+ }
+
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature, Label start,
+ Label end, int index) {
+ // In dex format, the argument names are separated from the local variable names. It
+ // seems to be needed to declare the local argument variables from the beginning of
+ // the methods for dex to pick that up. By inserting code before the first label we
+ // break that. In Java this is fine, and the debugger shows the right thing. However
+ // if we don't readjust the local variables, we just don't see the arguments.
+ if (!disableRedirection && index < args.size()) {
+ start = this.start;
+ }
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+
+ public Label getStartLabel() {
+ return start;
+ }
+ }
+
+ /**
+ * Decorated {@link MethodNode} that maintains a reference to the class declaring the method.
+ */
+ private static class MethodReference {
+ final MethodNode method;
+ final ClassNode owner;
+
+ private MethodReference(MethodNode method, ClassNode owner) {
+ this.method = method;
+ this.owner = owner;
+ }
+ }
+ /***
+ * Inserts a trampoline to this class so that the updated methods can make calls to super
+ * class methods.
+ * <p/>
+ * Pseudo code for this trampoline:
+ * <code>
+ * Object access$super($classType instance, String name, object[] args) {
+ * switch(name) {
+ * case "firstMethod.(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;":
+ * return super~instance.firstMethod((String)arg[0], arg[1]);
+ * case "secondMethod.(Ljava/lang/String;I)V":
+ * return super~instance.firstMethod((String)arg[0], arg[1]);
+ *
+ * default:
+ * StringBuilder $local1 = new StringBuilder();
+ * $local1.append("Method not found ");
+ * $local1.append(name);
+ * $local1.append(" in " $classType $super implementation");
+ * throw new $package/InstantReloadException($local1.toString());
+ * }
+ * </code>
+ */
+ private void createAccessSuper() {
+ int access = Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC
+ | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_VARARGS;
+ Method m = new Method("access$super", "(L" + visitedClassName
+ + ";Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
+ MethodVisitor visitor = super.visitMethod(access,
+ m.getName(),
+ m.getDescriptor(),
+ null, null);
+
+ final GeneratorAdapter mv = new GeneratorAdapter(access, m, visitor);
+
+ // Gather all methods from itself and its superclasses to generate a giant access$super
+ // implementation.
+ // This will work fine as long as we don't support adding methods to a class.
+ final Map<String, MethodReference> uniqueMethods =
+ new HashMap<String, MethodReference>();
+ if (parentNodes.isEmpty()) {
+ // if we cannot determine the parents for this class, let's blindly add all the
+ // method of the current class as a gateway to a possible parent version.
+ addAllNewMethods(uniqueMethods, classNode);
+ } else {
+ // otherwise, use the parent list.
+ for (ClassNode parentNode : parentNodes) {
+ addAllNewMethods(uniqueMethods, parentNode);
+ }
+ }
+
+ new StringSwitch() {
+ @Override
+ void visitString() {
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ }
+
+ @Override
+ void visitCase(String methodName) {
+ MethodReference methodRef = uniqueMethods.get(methodName);
+
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+
+ Type[] args = Type.getArgumentTypes(methodRef.method.desc);
+ int argc = 0;
+ for (Type t : args) {
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.push(argc);
+ mv.visitInsn(Opcodes.AALOAD);
+ ByteCodeUtils.unbox(mv, t);
+ argc++;
+ }
+
+ if (TRACING_ENABLED) {
+ trace(mv, "super selected ", methodRef.owner.name,
+ methodRef.method.name, methodRef.method.desc);
+ }
+ // Call super on the other object, yup this works cos we are on the right place to
+ // call from.
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
+ methodRef.owner.name,
+ methodRef.method.name,
+ methodRef.method.desc, false);
+
+ Type ret = Type.getReturnType(methodRef.method.desc);
+ if (ret.getSort() == Type.VOID) {
+ mv.visitInsn(Opcodes.ACONST_NULL);
+ } else {
+ mv.box(ret);
+ }
+ mv.visitInsn(Opcodes.ARETURN);
+ }
+
+ @Override
+ void visitDefault() {
+ writeMissingMessageWithHash(mv, visitedClassName);
+ }
+ }.visit(mv, uniqueMethods.keySet());
+
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ /***
+ * Inserts a trampoline to this class so that the updated methods can make calls to
+ * constructors.
+ *
+ * <p/>
+ * Pseudo code for this trampoline:
+ * <code>
+ * ClassName(Object[] args, Marker unused) {
+ * String name = (String) args[0];
+ * if (name.equals(
+ * "java/lang/ClassName.(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;")) {
+ * this((String)arg[1], arg[2]);
+ * return
+ * }
+ * if (name.equals("SuperClassName.(Ljava/lang/String;I)V")) {
+ * super((String)arg[1], (int)arg[2]);
+ * return;
+ * }
+ * ...
+ * StringBuilder $local1 = new StringBuilder();
+ * $local1.append("Method not found ");
+ * $local1.append(name);
+ * $local1.append(" in " $classType $super implementation");
+ * throw new $package/InstantReloadException($local1.toString());
+ * }
+ * </code>
+ */
+ private void createDispatchingThis() {
+ // Gather all methods from itself and its superclasses to generate a giant constructor
+ // implementation.
+ // This will work fine as long as we don't support adding constructors to classes.
+ final Map<String, MethodNode> uniqueMethods = new HashMap<String, MethodNode>();
+
+ addAllNewConstructors(uniqueMethods, classNode, true /*keepPrivateConstructors*/);
+ for (ClassNode parentNode : parentNodes) {
+ addAllNewConstructors(uniqueMethods, parentNode, false /*keepPrivateConstructors*/);
+ }
+
+ int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC;
+
+ Method m = new Method(AsmUtils.CONSTRUCTOR,
+ ConstructorArgsRedirection.DISPATCHING_THIS_SIGNATURE);
+ MethodVisitor visitor = super.visitMethod(0, m.getName(), m.getDescriptor(), null, null);
+ final GeneratorAdapter mv = new GeneratorAdapter(access, m, visitor);
+
+ mv.visitCode();
+ // Mark this code as redirection code
+ Label label = new Label();
+ mv.visitLineNumber(0, label);
+
+ // Get and store the constructor canonical name.
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.push(0);
+ mv.visitInsn(Opcodes.AALOAD);
+ mv.unbox(Type.getType("Ljava/lang/String;"));
+ final int constructorCanonicalName = mv.newLocal(Type.getType("Ljava/lang/String;"));
+ mv.storeLocal(constructorCanonicalName);
+
+ new StringSwitch() {
+
+ @Override
+ void visitString() {
+ mv.loadLocal(constructorCanonicalName);
+ }
+
+ @Override
+ void visitCase(String canonicalName) {
+ MethodNode methodNode = uniqueMethods.get(canonicalName);
+ String owner = canonicalName.split("\\.")[0];
+
+ // Parse method arguments and
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ Type[] args = Type.getArgumentTypes(methodNode.desc);
+ int argc = 0;
+ for (Type t : args) {
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.push(argc + 1);
+ mv.visitInsn(Opcodes.AALOAD);
+ ByteCodeUtils.unbox(mv, t);
+ argc++;
+ }
+
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, AsmUtils.CONSTRUCTOR,
+ methodNode.desc, false);
+
+ mv.visitInsn(Opcodes.RETURN);
+ }
+
+ @Override
+ void visitDefault() {
+ writeMissingMessageWithHash(mv, visitedClassName);
+ }
+ }.visit(mv,uniqueMethods.keySet());
+
+ mv.visitMaxs(1, 3);
+ mv.visitEnd();
+ }
+
+ @Override
+ public void visitEnd() {
+ createAccessSuper();
+ createDispatchingThis();
+ super.visitEnd();
+ }
+
+ /**
+ * Add all unseen methods from the passed ClassNode's methods. {@see ClassNode#methods}
+ * @param methods the methods already encountered in the ClassNode hierarchy
+ * @param classNode the class to save all new methods from.
+ */
+ private static void addAllNewMethods(Map<String, MethodReference> methods, ClassNode classNode) {
+ //noinspection unchecked
+ for (MethodNode method : (List<MethodNode>) classNode.methods) {
+ if (method.name.equals(AsmUtils.CONSTRUCTOR) || method.name.equals("<clinit>")) {
+ continue;
+ }
+ String name = method.name + "." + method.desc;
+ if (isAccessCompatibleWithInstantRun(method.access)
+ && !methods.containsKey(name) &&
+ (method.access & Opcodes.ACC_STATIC) == 0 &&
+ (method.access & Opcodes.ACC_PRIVATE) == 0) {
+ methods.put(name, new MethodReference(method, classNode));
+ }
+ }
+ }
+
+ /**
+ * Add all constructors from the passed ClassNode's methods. {@see ClassNode#methods}
+ * @param methods the constructors already encountered in the ClassNode hierarchy
+ * @param classNode the class to save all new methods from.
+ * @param keepPrivateConstructors whether to keep the private constructors.
+ */
+ private void addAllNewConstructors(Map<String, MethodNode> methods, ClassNode classNode,
+ boolean keepPrivateConstructors) {
+ //noinspection unchecked
+ for (MethodNode method : (List<MethodNode>) classNode.methods) {
+ if (!method.name.equals(AsmUtils.CONSTRUCTOR)) {
+ continue;
+ }
+
+ if (!isAccessCompatibleWithInstantRun(method.access)) {
+ continue;
+ }
+
+ if (!keepPrivateConstructors && (method.access & Opcodes.ACC_PRIVATE) != 0) {
+ continue;
+ }
+ if (!classNode.name.equals(visitedClassName)
+ && !classNode.name.equals(visitedSuperName)) {
+ continue;
+ }
+ String key = classNode.name + "." + method.desc;
+ if (methods.containsKey(key)) {
+ continue;
+ }
+ methods.put(key, method);
+ }
+ }
+
+ /**
+ * Command line invocation entry point. Expects 2 parameters, first is the source directory
+ * with .class files as produced by the Java compiler, second is the output directory where to
+ * store the bytecode enhanced version.
+ * @param args the command line arguments.
+ * @throws IOException if some files cannot be read or written.
+ */
+ public static void main(String[] args) throws IOException {
+ IncrementalVisitor.main(args, VISITOR_BUILDER);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalVisitor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalVisitor.java
new file mode 100644
index 0000000..6ff8f73
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/IncrementalVisitor.java
@@ -0,0 +1,474 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class IncrementalVisitor extends ClassVisitor {
+
+ private static final ILogger LOG = LoggerWrapper.getLogger(IncrementalVisitor.class);
+
+ /**
+ * Defines the output type from this visitor.
+ */
+ public enum OutputType {
+ /**
+ * provide instrumented classes that can be hot swapped at runtime with an override class.
+ */
+ INSTRUMENT,
+ /**
+ * provide override classes that be be used to hot swap an instrumented class.
+ */
+ OVERRIDE
+ }
+
+ public static final String PACKAGE = "com/android/tools/fd/runtime";
+ public static final String ABSTRACT_PATCHES_LOADER_IMPL =
+ PACKAGE + "/AbstractPatchesLoaderImpl";
+ public static final String APP_PATCHES_LOADER_IMPL = PACKAGE + "/AppPatchesLoaderImpl";
+ protected static final Type INSTANT_RELOAD_EXCEPTION =
+ Type.getType(PACKAGE + "/InstantReloadException");
+ protected static final Type RUNTIME_TYPE = Type.getType("L" + PACKAGE + "/AndroidInstantRuntime;");
+ public static final Type DISABLE_ANNOTATION_TYPE =
+ Type.getType("Lcom/android/tools/ir/api/DisableInstantRun;");
+
+ protected static final boolean TRACING_ENABLED = Boolean.getBoolean("FDR_TRACING");
+
+ public static final Type CHANGE_TYPE = Type.getType("L" + PACKAGE + "/IncrementalChange;");
+
+ protected String visitedClassName;
+ protected String visitedSuperName;
+ @NonNull
+ protected final ClassNode classNode;
+ @NonNull
+ protected final List<ClassNode> parentNodes;
+
+ /**
+ * Enumeration describing a method of field access rights.
+ */
+ protected enum AccessRight {
+ PRIVATE, PACKAGE_PRIVATE, PROTECTED, PUBLIC;
+
+ @NonNull
+ static AccessRight fromNodeAccess(int nodeAccess) {
+ if ((nodeAccess & Opcodes.ACC_PRIVATE) != 0) return PRIVATE;
+ if ((nodeAccess & Opcodes.ACC_PROTECTED) != 0) return PROTECTED;
+ if ((nodeAccess & Opcodes.ACC_PUBLIC) != 0) return PUBLIC;
+ return PACKAGE_PRIVATE;
+ }
+ }
+
+ public IncrementalVisitor(
+ @NonNull ClassNode classNode,
+ @NonNull List<ClassNode> parentNodes,
+ @NonNull ClassVisitor classVisitor) {
+ super(Opcodes.ASM5, classVisitor);
+ this.classNode = classNode;
+ this.parentNodes = parentNodes;
+ LOG.info("%s: Visiting %s", getClass().getSimpleName(), classNode.name);
+ }
+
+ @NonNull
+ protected static String getRuntimeTypeName(@NonNull Type type) {
+ return "L" + type.getInternalName() + ";";
+ }
+
+ @Nullable
+ FieldNode getFieldByName(@NonNull String fieldName) {
+ FieldNode fieldNode = getFieldByNameInClass(fieldName, classNode);
+ Iterator<ClassNode> iterator = parentNodes.iterator();
+ while(fieldNode == null && iterator.hasNext()) {
+ ClassNode parentNode = iterator.next();
+ fieldNode = getFieldByNameInClass(fieldName, parentNode);
+ }
+ return fieldNode;
+ }
+
+ @Nullable
+ protected static FieldNode getFieldByNameInClass(
+ @NonNull String fieldName, @NonNull ClassNode classNode) {
+ //noinspection unchecked ASM api.
+ List<FieldNode> fields = classNode.fields;
+ for (FieldNode field: fields) {
+ if (field.name.equals(fieldName)) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ protected MethodNode getMethodByName(String methodName, String desc) {
+ MethodNode methodNode = getMethodByNameInClass(methodName, desc, classNode);
+ Iterator<ClassNode> iterator = parentNodes.iterator();
+ while(methodNode == null && iterator.hasNext()) {
+ ClassNode parentNode = iterator.next();
+ methodNode = getMethodByNameInClass(methodName, desc, parentNode);
+ }
+ return methodNode;
+ }
+
+ @Nullable
+ protected static MethodNode getMethodByNameInClass(String methodName, String desc, ClassNode classNode) {
+ //noinspection unchecked ASM API
+ List<MethodNode> methods = classNode.methods;
+ for (MethodNode method : methods) {
+ if (method.name.equals(methodName) && method.desc.equals(desc)) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ protected static void trace(@NonNull GeneratorAdapter mv, @Nullable String s) {
+ mv.push(s);
+ mv.invokeStatic(Type.getType(PACKAGE + ".AndroidInstantRuntime"),
+ Method.getMethod("void trace(String)"));
+ }
+
+ protected static void trace(@NonNull GeneratorAdapter mv, @Nullable String s1,
+ @Nullable String s2) {
+ mv.push(s1);
+ mv.push(s2);
+ mv.invokeStatic(Type.getType(PACKAGE + ".AndroidInstantRuntime"),
+ Method.getMethod("void trace(String, String)"));
+ }
+
+ protected static void trace(@NonNull GeneratorAdapter mv, @Nullable String s1,
+ @Nullable String s2, @Nullable String s3) {
+ mv.push(s1);
+ mv.push(s2);
+ mv.push(s3);
+ mv.invokeStatic(Type.getType(PACKAGE + ".AndroidInstantRuntime"),
+ Method.getMethod("void trace(String, String, String)"));
+ }
+
+ protected static void trace(@NonNull GeneratorAdapter mv, @Nullable String s1,
+ @Nullable String s2, @Nullable String s3, @Nullable String s4) {
+ mv.push(s1);
+ mv.push(s2);
+ mv.push(s3);
+ mv.push(s4);
+ mv.invokeStatic(Type.getType(PACKAGE + ".AndroidInstantRuntime"),
+ Method.getMethod("void trace(String, String, String, String)"));
+ }
+
+ protected static void trace(@NonNull GeneratorAdapter mv, int argsNumber) {
+ StringBuilder methodSignature = new StringBuilder("void trace(String");
+ for (int i=0 ; i < argsNumber-1; i++) {
+ methodSignature.append(", String");
+ }
+ methodSignature.append(")");
+ mv.invokeStatic(Type.getType(PACKAGE + ".AndroidInstantRuntime"),
+ Method.getMethod(methodSignature.toString()));
+ }
+
+ /**
+ * Simple Builder interface for common methods between all byte code visitors.
+ */
+ public interface VisitorBuilder {
+ @NonNull
+ IncrementalVisitor build(@NonNull ClassNode classNode,
+ @NonNull List<ClassNode> parentNodes, @NonNull ClassVisitor classVisitor);
+
+ @NonNull
+ String getMangledRelativeClassFilePath(@NonNull String originalClassFilePath);
+
+ @NonNull
+ OutputType getOutputType();
+ }
+
+ protected static void main(
+ @NonNull String[] args,
+ @NonNull VisitorBuilder visitorBuilder) throws IOException {
+
+ if (args.length != 3) {
+ throw new IllegalArgumentException("Needs to be given an input and output directory "
+ + "and a classpath");
+ }
+
+ File srcLocation = new File(args[0]);
+ File baseInstrumentedCompileOutputFolder = new File(args[1]);
+ FileUtils.emptyFolder(baseInstrumentedCompileOutputFolder);
+
+ Iterable<String> classPathStrings = Splitter.on(File.pathSeparatorChar).split(args[2]);
+ List<URL> classPath = Lists.newArrayList();
+ for (String classPathString : classPathStrings) {
+ File path = new File(classPathString);
+ if (!path.exists()) {
+ throw new IllegalArgumentException(
+ String.format("Invalid class path element %s", classPathString));
+ }
+ classPath.add(path.toURI().toURL());
+ }
+ classPath.add(srcLocation.toURI().toURL());
+ URL[] classPathArray = Iterables.toArray(classPath, URL.class);
+
+ ClassLoader classesToInstrumentLoader = new URLClassLoader(classPathArray, null) {
+ @Override
+ public URL getResource(String name) {
+ // Never delegate to bootstrap classes.
+ return findResource(name);
+ }
+ };
+
+ ClassLoader originalThreadContextClassLoader = Thread.currentThread()
+ .getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(classesToInstrumentLoader);
+ instrumentClasses(srcLocation,
+ baseInstrumentedCompileOutputFolder, visitorBuilder);
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalThreadContextClassLoader);
+ }
+ }
+
+ private static void instrumentClasses(
+ @NonNull File rootLocation,
+ @NonNull File outLocation,
+ @NonNull VisitorBuilder visitorBuilder) throws IOException {
+
+ Iterable<File> files =
+ Files.fileTreeTraverser().preOrderTraversal(rootLocation).filter(Files.isFile());
+
+ for (File inputFile : files) {
+ instrumentClass(rootLocation, inputFile, outLocation, visitorBuilder);
+ }
+ }
+
+ /**
+ * Defines when a method access flags are compatible with InstantRun technology.
+ *
+ * - If the method is a bridge method, we do not enable it for instantReload.
+ * it is most likely only calling a twin method (same name, same parameters).
+ * - if the method is abstract, we don't add a redirection.
+ *
+ * @param access the method access flags
+ * @return true if the method should be InstantRun enabled, false otherwise.
+ */
+ protected static boolean isAccessCompatibleWithInstantRun(int access) {
+ return ((access & Opcodes.ACC_ABSTRACT) == 0) && ((access & Opcodes.ACC_BRIDGE) == 0);
+ }
+
+ @Nullable
+ public static File instrumentClass(
+ @NonNull File inputRootDirectory,
+ @NonNull File inputFile,
+ @NonNull File outputDirectory,
+ @NonNull VisitorBuilder visitorBuilder) throws IOException {
+
+ byte[] classBytes;
+ String path = FileUtils.relativePath(inputFile, inputRootDirectory);
+ if (!inputFile.getPath().endsWith(SdkConstants.DOT_CLASS)) {
+ File outputFile = new File(outputDirectory, path);
+ Files.createParentDirs(outputFile);
+ Files.copy(inputFile, outputFile);
+ return outputFile;
+ }
+ classBytes = Files.toByteArray(inputFile);
+ ClassReader classReader = new ClassReader(classBytes);
+ // override the getCommonSuperClass to use the thread context class loader instead of
+ // the system classloader. This is useful as ASM needs to load classes from the project
+ // which the system classloader does not have visibility upon.
+ // TODO: investigate if there is not a simpler way than overriding.
+ ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) {
+ @Override
+ protected String getCommonSuperClass(final String type1, final String type2) {
+ Class<?> c, d;
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ c = Class.forName(type1.replace('/', '.'), false, classLoader);
+ d = Class.forName(type2.replace('/', '.'), false, classLoader);
+ } catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ if (c.isAssignableFrom(d)) {
+ return type1;
+ }
+ if (d.isAssignableFrom(c)) {
+ return type2;
+ }
+ if (c.isInterface() || d.isInterface()) {
+ return "java/lang/Object";
+ } else {
+ do {
+ c = c.getSuperclass();
+ } while (!c.isAssignableFrom(d));
+ return c.getName().replace('.', '/');
+ }
+ }
+ };
+
+ ClassNode classNode = new ClassNode();
+ classReader.accept(classNode, ClassReader.EXPAND_FRAMES);
+
+ // when dealing with interface, we just copy the inputFile over without any changes unless
+ // this is a package private interface.
+ AccessRight accessRight = AccessRight.fromNodeAccess(classNode.access);
+ File outputFile = new File(outputDirectory, path);
+ if ((classNode.access & Opcodes.ACC_INTERFACE) != 0) {
+ if (visitorBuilder.getOutputType() == OutputType.INSTRUMENT) {
+ // don't change the name of interfaces.
+ Files.createParentDirs(outputFile);
+ if (accessRight == AccessRight.PACKAGE_PRIVATE) {
+ classNode.access = classNode.access | Opcodes.ACC_PUBLIC;
+ classNode.accept(classWriter);
+ Files.write(classWriter.toByteArray(), outputFile);
+ } else {
+ // just copy the input file over, no change.
+ Files.write(classBytes, outputFile);
+ }
+ return outputFile;
+ } else {
+ return null;
+ }
+ }
+
+ if (isPackageInstantRunDisabled(inputFile, classNode)) {
+ if (visitorBuilder.getOutputType() == OutputType.INSTRUMENT) {
+ Files.createParentDirs(outputFile);
+ Files.write(classBytes, outputFile);
+ return outputFile;
+ } else {
+ return null;
+ }
+ }
+
+ List<ClassNode> parentsNodes = parseParents(inputFile, classNode);
+ outputFile = new File(outputDirectory, visitorBuilder.getMangledRelativeClassFilePath(path));
+ Files.createParentDirs(outputFile);
+ IncrementalVisitor visitor = visitorBuilder.build(classNode, parentsNodes, classWriter);
+ classNode.accept(visitor);
+
+ Files.write(classWriter.toByteArray(), outputFile);
+ return outputFile;
+ }
+
+ @NonNull
+ private static File getBinaryFolder(@NonNull File inputFile, @NonNull ClassNode classNode) {
+ return new File(inputFile.getAbsolutePath().substring(0,
+ inputFile.getAbsolutePath().length() - (classNode.name.length() + ".class".length())));
+ }
+
+ @NonNull
+ private static List<ClassNode> parseParents(
+ @NonNull File inputFile, @NonNull ClassNode classNode) throws IOException {
+ File binaryFolder = getBinaryFolder(inputFile, classNode);
+ List<ClassNode> parentNodes = new ArrayList<ClassNode>();
+ String currentParentName = classNode.superName;
+
+ while (currentParentName != null) {
+ File parentFile = new File(binaryFolder, currentParentName + ".class");
+ if (parentFile.exists()) {
+ LOG.info("Parsing %s.", parentFile);
+ InputStream parentFileClassReader = new BufferedInputStream(new FileInputStream(parentFile));
+ ClassReader parentClassReader = new ClassReader(parentFileClassReader);
+ ClassNode parentNode = new ClassNode();
+ parentClassReader.accept(parentNode, ClassReader.EXPAND_FRAMES);
+ parentNodes.add(parentNode);
+ currentParentName = parentNode.superName;
+ } else {
+ // May need method information from outside of the current project. Thread local class reader
+ // should be the one
+ try {
+ ClassReader parentClassReader = new ClassReader(
+ Thread.currentThread().getContextClassLoader().getResourceAsStream(
+ currentParentName + ".class"));
+ ClassNode parentNode = new ClassNode();
+ parentClassReader.accept(parentNode, ClassReader.EXPAND_FRAMES);
+ parentNodes.add(parentNode);
+ currentParentName = parentNode.superName;
+ } catch (IOException e) {
+ // Could not locate parent class. This is as far as we can go locating parents.
+ LOG.error(null,
+ "IncrementalVisitor parseParents could not locate %1$s "
+ + "which is an ancestor of project class %2$s.\n",
+ currentParentName, classNode.name);
+ currentParentName = null;
+ }
+ }
+ }
+ return parentNodes;
+ }
+
+ @Nullable
+ private static ClassNode parsePackageInfo(
+ @NonNull File inputFile, @NonNull ClassNode classNode) throws IOException {
+
+ File packageFolder = inputFile.getParentFile();
+ File packageInfoClass = new File(packageFolder, "package-info.class");
+ if (packageInfoClass.exists()) {
+ InputStream reader = new BufferedInputStream(new FileInputStream(packageInfoClass));
+ ClassReader classReader = new ClassReader(reader);
+ ClassNode packageInfo = new ClassNode();
+ classReader.accept(packageInfo, ClassReader.EXPAND_FRAMES);
+ return packageInfo;
+ }
+ return null;
+ }
+
+ private static boolean isPackageInstantRunDisabled(
+ @NonNull File inputFile, @NonNull ClassNode classNode) throws IOException {
+
+ ClassNode packageInfoClass = parsePackageInfo(inputFile, classNode);
+ if (packageInfoClass != null) {
+ //noinspection unchecked
+ List<AnnotationNode> annotations = packageInfoClass.invisibleAnnotations;
+ if (annotations == null) {
+ return false;
+ }
+ for (AnnotationNode annotation : annotations) {
+ if (annotation.desc.equals(DISABLE_ANNOTATION_TYPE.getDescriptor())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunAnchorTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunAnchorTask.java
new file mode 100644
index 0000000..abde6bc
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunAnchorTask.java
@@ -0,0 +1,79 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import org.gradle.api.tasks.TaskAction;
+
+
+/**
+ * Simple task used as an anchor task for all instant run related tasks. An anchor task can be used
+ * to conveniently set dependencies.
+ */
+public class InstantRunAnchorTask extends BaseTask {
+
+ @TaskAction
+ public void executeAction() {
+ }
+
+ public static class ConfigAction implements TaskConfigAction<InstantRunAnchorTask> {
+
+ /**
+ * Task name for Instant Run incremental build external anchor task.
+ */
+ public static String getName(VariantScope scope) {
+ return scope.getTaskName("incremental", "SupportDex");
+ }
+
+ private final String taskName;
+ private final VariantScope variantScope;
+
+ public ConfigAction(VariantScope scope) {
+ this.taskName = ConfigAction.getName(scope);
+ this.variantScope = scope;
+ }
+
+ /**
+ * Creates a new anchor task with a dedicated prefix.
+ */
+ public ConfigAction(VariantScope scope, String prefix) {
+ this.taskName = scope.getTaskName("incremental", prefix);
+ this.variantScope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return taskName;
+ }
+
+ @NonNull
+ @Override
+ public Class<InstantRunAnchorTask> getType() {
+ return InstantRunAnchorTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull InstantRunAnchorTask task) {
+ task.setDescription("InstantRun task to build incremental artifacts");
+ task.setVariantName(variantScope.getVariantConfiguration().getFullName());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunBuildContext.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunBuildContext.java
new file mode 100644
index 0000000..b9d2381
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunBuildContext.java
@@ -0,0 +1,729 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.sdklib.AndroidVersion;
+import com.android.utils.XmlUtils;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Context object for all InstantRun related information.
+ */
+public class InstantRunBuildContext {
+
+ static final String TAG_INSTANT_RUN = "instant-run";
+ static final String TAG_BUILD = "build";
+ static final String TAG_ARTIFACT = "artifact";
+ static final String TAG_TASK = "task";
+ static final String ATTR_NAME = "name";
+ static final String ATTR_DURATION = "duration";
+ static final String ATTR_TIMESTAMP = "timestamp";
+ static final String ATTR_VERIFIER = "verifier";
+ static final String ATTR_TYPE = "type";
+ static final String ATTR_LOCATION = "location";
+ static final String ATTR_API_LEVEL = "api-level";
+ static final String ATTR_DENSITY = "density";
+ static final String ATTR_FORMAT = "format";
+ static final String ATTR_ABI = "abi";
+
+ // Keep roughly in sync with InstantRunBuildInfo#isCompatibleFormat:
+ //
+ // (These aren't directly aliased in case in the future we want to for
+ // example have the client understand a range of versions. E.g. Gradle
+ // may bump this version to force older IDE's to not attempt instant run
+ // with this metadata, but a newer IDE could decide to work both with this
+ // new Gradle version and the older version. Whenever we bump this version
+ // we should cross check the logic and decide how to handle the isCompatible()
+ // method.)
+ static final String CURRENT_FORMAT = "7";
+
+ public enum TaskType {
+ JAVAC,
+ INSTANT_RUN_DEX,
+ INSTANT_RUN_TRANSFORM,
+ VERIFIER
+ }
+
+ /**
+ * Enumeration of the possible file types produced by an instant run enabled build.
+ */
+ public enum FileType {
+ /**
+ * Main APK file for 19, and 21 platforms when using the {@link ColdswapMode#MULTIDEX} mode.
+ */
+ MAIN,
+ /**
+ * Main APK file when application is using the {@link ColdswapMode#MULTIAPK} mode.
+ */
+ SPLIT_MAIN,
+ /**
+ * Reload dex file that can be used to patch application live.
+ */
+ RELOAD_DEX,
+ /**
+ * Restart.dex file that can be used for Dalvik to restart applications with minimum set of
+ * changes delivered.
+ */
+ RESTART_DEX,
+ /**
+ * Shard dex file that can be used to replace originally installed multi-dex shard.
+ */
+ DEX,
+ /**
+ * Pure split (code only) that can be installed individually on M+ devices.
+ */
+ SPLIT,
+ /**
+ * Resources: res.ap_ file
+ */
+ RESOURCES,
+ }
+
+ /**
+ * A Build represents the result of an InstantRun enabled build invocation.
+ * It will contain all the artifacts it produced as well as the unique timestamp for the build
+ * and the result of the InstantRun verification process.
+ */
+ public static class Build {
+ private final long buildId;
+ private Optional<InstantRunVerifierStatus> verifierStatus;
+ private final List<Artifact> artifacts = new ArrayList<Artifact>();
+
+ public Build(long buildId, @NonNull Optional<InstantRunVerifierStatus> verifierStatus) {
+ this.buildId = buildId;
+ this.verifierStatus = verifierStatus;
+ }
+
+ @Nullable
+ public Artifact getArtifactForType(@NonNull FileType fileType) {
+ for (Artifact artifact : artifacts) {
+ if (artifact.fileType == fileType) {
+ return artifact;
+ }
+ }
+ return null;
+ }
+
+ private boolean hasCodeArtifact() {
+ for (Artifact artifact : artifacts) {
+ FileType type = artifact.getType();
+ if (type == FileType.DEX || type == FileType.SPLIT
+ || type == FileType.MAIN || type == FileType.RESTART_DEX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Element toXml(@NonNull Document document) {
+ Element build = document.createElement(TAG_BUILD);
+ toXml(document, build);
+ return build;
+ }
+
+ private void toXml(@NonNull Document document, @NonNull Element element) {
+ element.setAttribute(ATTR_TIMESTAMP, String.valueOf(buildId));
+ if (verifierStatus.isPresent()) {
+ element.setAttribute(ATTR_VERIFIER, verifierStatus.get().name());
+ }
+ for (Artifact artifact : artifacts) {
+ element.appendChild(artifact.toXml(document));
+ }
+ }
+
+ @NonNull
+ public static Build fromXml(@NonNull Node buildNode) {
+ NamedNodeMap attributes = buildNode.getAttributes();
+ Node verifierAttribute = attributes.getNamedItem(ATTR_VERIFIER);
+ Build build = new Build(
+ Long.parseLong(attributes.getNamedItem(ATTR_TIMESTAMP).getNodeValue()),
+ verifierAttribute != null
+ ? Optional.of(InstantRunVerifierStatus.valueOf(
+ verifierAttribute.getNodeValue()))
+ : Optional.<InstantRunVerifierStatus>absent());
+ NodeList childNodes = buildNode.getChildNodes();
+ for (int i=0; i< childNodes.getLength(); i++) {
+ Node artifactNode = childNodes.item(i);
+ if (artifactNode.getNodeName().equals(TAG_ARTIFACT)) {
+ Artifact artifact = Artifact.fromXml(artifactNode);
+ build.artifacts.add(artifact);
+ }
+ }
+ return build;
+ }
+
+ public long getBuildId() {
+ return buildId;
+ }
+
+ @NonNull
+ public List<Artifact> getArtifacts() {
+ return artifacts;
+ }
+
+ @NonNull
+ public Optional<InstantRunVerifierStatus> getVerifierStatus() {
+ return verifierStatus;
+ }
+ }
+
+ /**
+ * A build artifact defined by its type and location.
+ */
+ public static class Artifact {
+ private final FileType fileType;
+ private File location;
+
+ public Artifact(@NonNull FileType fileType, @NonNull File location) {
+ this.fileType = fileType;
+ this.location = location;
+ }
+
+ @NonNull
+ public Node toXml(@NonNull Document document) {
+ Element artifact = document.createElement(TAG_ARTIFACT);
+ artifact.setAttribute(ATTR_TYPE, fileType.name());
+ artifact.setAttribute(ATTR_LOCATION,
+ XmlUtils.toXmlAttributeValue(location.getAbsolutePath()));
+ return artifact;
+ }
+
+ @NonNull
+ public static Artifact fromXml(@NonNull Node artifactNode) {
+ NamedNodeMap attributes = artifactNode.getAttributes();
+ return new Artifact(
+ FileType.valueOf(attributes.getNamedItem(ATTR_TYPE).getNodeValue()),
+ new File(attributes.getNamedItem(ATTR_LOCATION).getNodeValue()));
+ }
+
+ @NonNull
+ public File getLocation() {
+ return location;
+ }
+
+ /**
+ * Returns true if the file accumulates all the changes since it was initially built and
+ * deployed on the device.
+ */
+ public boolean isAccumulative() {
+ return fileType == FileType.DEX || fileType == FileType.SPLIT ||
+ fileType == FileType.MAIN || fileType == FileType.RESOURCES;
+ }
+
+ public void setLocation(@NonNull File location) {
+ this.location = location;
+ }
+
+ @NonNull
+ public FileType getType() {
+ return fileType;
+ }
+ }
+
+ private final long[] taskStartTime = new long[TaskType.values().length];
+ private final long[] taskDurationInMs = new long[TaskType.values().length];
+ private InstantRunPatchingPolicy patchingPolicy;
+ private AndroidVersion apiLevel = AndroidVersion.DEFAULT;
+ private String density = null;
+ private String abi = null;
+ private final Build currentBuild = new Build(
+ System.nanoTime(), Optional.<InstantRunVerifierStatus>absent());
+ private final TreeMap<Long, Build> previousBuilds = new TreeMap<Long, Build>();
+ private File tmpBuildInfo = null;
+ private boolean isInstantRunMode = false;
+ private volatile boolean isAborted = false;
+
+ public void setInstantRunMode(boolean instantRunMode) {
+ isInstantRunMode = instantRunMode;
+ }
+
+ public boolean isInInstantRunMode() {
+ return isInstantRunMode;
+ }
+
+ public void setTmpBuildInfo(File tmpBuildInfo) {
+ this.tmpBuildInfo = tmpBuildInfo;
+ }
+
+ /**
+ * Get the unique build id for this build invocation.
+ * @return a unique build id.
+ */
+ public long getBuildId() {
+ return currentBuild.buildId;
+ }
+
+ public void startRecording(@NonNull TaskType taskType) {
+ taskStartTime[taskType.ordinal()] = System.currentTimeMillis();
+ }
+
+ public long stopRecording(@NonNull TaskType taskType) {
+ long duration = System.currentTimeMillis() - taskStartTime[taskType.ordinal()];
+ taskDurationInMs[taskType.ordinal()] = duration;
+ return duration;
+ }
+
+ public void setVerifierResult(@NonNull InstantRunVerifierStatus verifierStatus) {
+ if (!currentBuild.verifierStatus.isPresent() ||
+ currentBuild.getVerifierStatus().get() == InstantRunVerifierStatus.COMPATIBLE) {
+ currentBuild.verifierStatus = Optional.of(verifierStatus);
+ }
+ }
+
+ /**
+ * Returns true if the verifier did not find any incompatible changes for InstantRun or was not
+ * run due to no code changes.
+ * @return true to use hot swapping, false otherwise.
+ */
+ public boolean hasPassedVerification() {
+ return !currentBuild.verifierStatus.isPresent()
+ || currentBuild.verifierStatus.get() == InstantRunVerifierStatus.COMPATIBLE;
+ }
+
+ public void setApiLevel(@NonNull AndroidVersion apiLevel,
+ @Nullable String coldswapMode,
+ @Nullable String targetAbi) {
+ this.apiLevel = apiLevel;
+ // cache the patching policy.
+ this.patchingPolicy = InstantRunPatchingPolicy.getPatchingPolicy(
+ apiLevel, coldswapMode, targetAbi);
+ this.abi = targetAbi;
+ }
+
+ public AndroidVersion getApiLevel() {
+ return apiLevel;
+ }
+
+ @Nullable
+ public String getDensity() {
+ return density;
+ }
+
+ public void setDensity(@Nullable String density) {
+ this.density = density;
+ }
+
+ @Nullable
+ public InstantRunPatchingPolicy getPatchingPolicy() {
+ return patchingPolicy;
+ }
+
+ public synchronized void addChangedFile(@NonNull FileType fileType, @NonNull File file)
+ throws IOException {
+ if (patchingPolicy == null) {
+ return;
+ }
+ // make sure we don't add the same artifacts twice.
+ for (Artifact artifact : currentBuild.artifacts) {
+ if (artifact.getType() == fileType
+ && artifact.getLocation().getAbsolutePath().equals(file.getAbsolutePath())) {
+ return;
+ }
+ }
+
+ // validate the patching policy and the received file type to record the file or not.
+ // RELOAD and MAIN are always record.
+ if (fileType != FileType.RELOAD_DEX && fileType != FileType.MAIN &&
+ fileType != FileType.RESOURCES) {
+ switch (patchingPolicy) {
+ case PRE_LOLLIPOP:
+ if (fileType != FileType.RESTART_DEX) {
+ return;
+ }
+ break;
+ case MULTI_DEX:
+ if (fileType != FileType.DEX) {
+ return;
+ }
+ break;
+ case MULTI_APK:
+ if (fileType != FileType.SPLIT) {
+ return;
+ }
+ // let's work around the Lollipop and Marshmallow issue that cannot deal with
+ // just changing a split APK, we forcefully add again the MAIN apk so it is
+ // automatically enrolled.
+ Artifact splitMain = currentBuild.getArtifactForType(FileType.SPLIT_MAIN);
+ if (splitMain == null) {
+ splitMain = getPastBuildsArtifactForType(FileType.SPLIT_MAIN);
+ if (splitMain != null) {
+ currentBuild.artifacts.add(splitMain);
+ }
+ }
+ }
+ }
+ if (fileType == FileType.MAIN) {
+ // in case of MAIN, we need to disambiguate whether this is a SPLIT_MAIN or just a
+ // MAIN. this is useful for the IDE so it knows which deployment method to use.
+ if (patchingPolicy == InstantRunPatchingPolicy.MULTI_APK) {
+ fileType = FileType.SPLIT_MAIN;
+ }
+
+ // because of signing/aligning, we can be notified several times of the main APK
+ // construction, last one wins.
+ Artifact previousArtifact = currentBuild.getArtifactForType(fileType);
+ if (previousArtifact != null) {
+ currentBuild.artifacts.remove(previousArtifact);
+ }
+
+ // also if we are in LOLLIPOP, the DEX files are packaged in the original main APK, so
+ // we can remove individual files.
+ if (patchingPolicy == InstantRunPatchingPolicy.MULTI_DEX) {
+ currentBuild.artifacts.clear();
+ }
+
+ // since the main APK is produced, no need to keep the RESOURCES record around.
+ Artifact resourcesApFile = currentBuild.getArtifactForType(FileType.RESOURCES);
+ if (resourcesApFile != null) {
+ currentBuild.artifacts.remove(resourcesApFile);
+ }
+ }
+ currentBuild.artifacts.add(new Artifact(fileType, file));
+ // save the temporary build info file in case the build fails later on.
+ try {
+ writeTmpBuildInfo();
+ } catch (ParserConfigurationException e) {
+ throw new IOException(e);
+ }
+ }
+
+ public void abort() {
+ isAborted = true;
+ }
+
+ @Nullable
+ public Build getLastBuild() {
+ return previousBuilds.isEmpty() ? null : previousBuilds.lastEntry().getValue();
+ }
+
+ @Nullable
+ public Artifact getPastBuildsArtifactForType(@NonNull FileType fileType) {
+ for (Build build : previousBuilds.values()) {
+ Artifact artifact = build.getArtifactForType(fileType);
+ if (artifact != null) {
+ return artifact;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ Collection<Build> getPreviousBuilds() {
+ return previousBuilds.values();
+ }
+
+ /**
+ * Remove all unwanted changes :
+ * - All reload.dex changes older than the last cold swap.
+ * - Empty changes (unless it's the last one).
+ */
+ private void purge() {
+ boolean foundColdRestart = false;
+ Set<String> splitFilesAlreadyFound = new HashSet<String>();
+ // the oldest build is by definition the full build.
+ Long initialFullBuild = previousBuilds.firstKey();
+ // iterate from the most recent to the oldest build, which reflect the most up to date
+ // natural order of builds.
+ for (Long aBuildId : new ArrayList<Long>(previousBuilds.descendingKeySet())) {
+ Build previousBuild = previousBuilds.get(aBuildId);
+ // initial builds are never purged in any way.
+ if (previousBuild.buildId == initialFullBuild) {
+ continue;
+ }
+ if (previousBuild.verifierStatus.isPresent()) {
+ if (previousBuild.verifierStatus.get() == InstantRunVerifierStatus.COMPATIBLE) {
+ if (foundColdRestart) {
+ previousBuilds.remove(aBuildId);
+ continue;
+ }
+ } else {
+ foundColdRestart = true;
+ }
+ } else {
+ // no verifier status is indicative of a full build or no code change.
+ // If this is a full build, treat it as a cold restart.
+ foundColdRestart = previousBuild.hasCodeArtifact();
+ }
+ // when a coldswap build was found, remove all RESOURCES entries for previous builds
+ // as the resource is redelivered as part of the main split.
+ if (foundColdRestart
+ && patchingPolicy == InstantRunPatchingPolicy.MULTI_APK) {
+ Artifact resourceApArtifact = previousBuild.getArtifactForType(FileType.RESOURCES);
+ if (resourceApArtifact != null) {
+ previousBuild.artifacts.remove(resourceApArtifact);
+ }
+ }
+
+ // remove all DEX, SPLIT and Resources files from older built artifacts if we have
+ // already seen a newer version, we only need to most recent one.
+ for (Artifact artifact : new ArrayList<Artifact>(previousBuild.artifacts)) {
+ if (artifact.isAccumulative()) {
+ // we don't remove artifacts from the first build.
+ if (splitFilesAlreadyFound.contains(artifact.getLocation().getAbsolutePath())) {
+ previousBuild.artifacts.remove(artifact);
+ } else {
+ splitFilesAlreadyFound.add(artifact.getLocation().getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ // bunch of builds can be empty, either because we did nothing or all its artifact got
+ // rebuilt in a more recent iteration, in such a case, remove it.
+ for (Long aBuildId : new ArrayList<Long>(previousBuilds.descendingKeySet())) {
+ Build aBuild = previousBuilds.get(aBuildId);
+ // if the build artifacts are empty and it's not the current build.
+ if (aBuild.artifacts.isEmpty() && aBuild.buildId != currentBuild.buildId) {
+ previousBuilds.remove(aBuildId);
+ }
+ }
+ }
+
+ /**
+ * Load previous iteration build-info.xml. The only information we really care about is the
+ * list of previous builds so we can provide the list of artifacts to the IDE to catch up
+ * a disconnected device.
+ * @param persistedState the persisted xml file.
+ */
+ public void loadFromXmlFile(@NonNull File persistedState)
+ throws IOException, ParserConfigurationException, SAXException {
+ if (!persistedState.exists()) {
+ return;
+ }
+ loadFromDocument(XmlUtils.parseUtfXmlFile(persistedState, false));
+ }
+
+ /**
+ * {@see loadFromXlFile} but using a String
+ */
+ public void loadFromXml(@NonNull String persistedState)
+ throws IOException, SAXException, ParserConfigurationException {
+ loadFromDocument(XmlUtils.parseDocument(persistedState, false));
+ }
+
+ private void loadFromDocument(@NonNull Document document) {
+ Element instantRun = document.getDocumentElement();
+ Build lastBuild = Build.fromXml(instantRun);
+ previousBuilds.put(lastBuild.buildId, lastBuild);
+ NodeList buildNodes = instantRun.getChildNodes();
+ for (int i=0; i<buildNodes.getLength();i++) {
+ Node buildNode = buildNodes.item(i);
+ if (buildNode.getNodeName().equals(TAG_BUILD)) {
+ Build build = Build.fromXml(buildNode);
+ previousBuilds.put(build.buildId, build);
+ }
+ }
+ }
+
+ /**
+ * Merges the artifacts of a temporary build info into this build's artifacts. If this build
+ * finishes the build-info.xml will contain the artifacts produced by this iteration as well
+ * as the artifacts produced in a previous iteration and saved into the temporary build info.
+ * @param tmpBuildInfoFile a past build build-info.xml
+ * @throws IOException
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ */
+
+ public void mergeFromFile(@NonNull File tmpBuildInfoFile)
+ throws IOException, SAXException, ParserConfigurationException {
+ if (!tmpBuildInfoFile.exists()) {
+ return;
+ }
+ mergeFrom(XmlUtils.parseUtfXmlFile(tmpBuildInfoFile, false));
+ }
+
+ /**
+ * Merges the artifacts of a temporary build info into this build's artifacts. If this build
+ * finishes the build-info.xml will contain the artifacts produced by this iteration as well
+ * as the artifacts produced in a previous iteration and saved into the temporary build info.
+ * @param tmpBuildInfo a past build build-info.xml as a String
+ * @throws IOException
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ */
+
+ public void mergeFrom(@NonNull String tmpBuildInfo)
+ throws IOException, SAXException, ParserConfigurationException {
+
+ mergeFrom(XmlUtils.parseDocument(tmpBuildInfo, false));
+ }
+
+ private void mergeFrom(@NonNull Document document) {
+ Element instantRun = document.getDocumentElement();
+ Build lastBuild = Build.fromXml(instantRun);
+ currentBuild.artifacts.addAll(lastBuild.artifacts);
+ }
+
+ /**
+ * Close all activities related to InstantRun.
+ */
+ public void close(PersistenceMode persistenceMode) {
+ if (isAborted) {
+ // check if the failure is a BINARY_MANIFEST_CHANGE and we are in full build mode.
+ if (!(currentBuild.getVerifierStatus().isPresent()
+ && currentBuild.getVerifierStatus().get()
+ == InstantRunVerifierStatus.BINARY_MANIFEST_FILE_CHANGE
+ && persistenceMode == PersistenceMode.FULL_BUILD)) {
+ currentBuild.artifacts.clear();
+ }
+ }
+
+ // add the current build to the list of builds to be persisted.
+ previousBuilds.put(currentBuild.buildId, currentBuild);
+
+ // purge unwanted past iterations.
+ purge();
+ }
+
+ public void close() {
+ close(PersistenceMode.FULL_BUILD);
+ }
+
+ /**
+ * Define the pesistence mode for this context (which results in the build-info.xml).
+ */
+ public enum PersistenceMode {
+ /**
+ * Persist this build as a final full build (and do not include any previous builds).
+ */
+ FULL_BUILD,
+ /**
+ * Persist this build as a final incremental build and include all previous builds
+ */
+ INCREMENTAL_BUILD,
+ /**
+ * Persist this build as a temporary build (that may still execute or failed to complete)
+ */
+ TEMP_BUILD
+ }
+ /**
+ * Serialize this context into an xml format.
+ * @return the xml persisted information as a {@link String}
+ * @throws ParserConfigurationException
+ */
+ @NonNull
+ public String toXml() throws ParserConfigurationException {
+ return toXml(PersistenceMode.INCREMENTAL_BUILD);
+ }
+
+ /**
+ * Serialize this context into an xml format.
+ * @param persistenceMode desired {@link PersistenceMode}
+ * @return the xml persisted information as a {@link String}
+ * @throws ParserConfigurationException
+ */
+ @NonNull
+ public String toXml(PersistenceMode persistenceMode) throws ParserConfigurationException {
+
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ toXml(document, persistenceMode);
+ return XmlPrettyPrinter.prettyPrint(document, true);
+ }
+
+ private Element toXml(Document document, PersistenceMode persistenceMode) {
+ Element instantRun = document.createElement(TAG_INSTANT_RUN);
+ document.appendChild(instantRun);
+
+ for (TaskType taskType : TaskType.values()) {
+ Element taskTypeNode = document.createElement(TAG_TASK);
+ taskTypeNode.setAttribute(ATTR_NAME,
+ CaseFormat.UPPER_UNDERSCORE.converterTo(
+ CaseFormat.LOWER_HYPHEN).convert(taskType.name()));
+ taskTypeNode.setAttribute(ATTR_DURATION,
+ String.valueOf(taskDurationInMs[taskType.ordinal()]));
+ instantRun.appendChild(taskTypeNode);
+ }
+
+ // if we are doing a full APK build which may be incremental, we do not need to worry
+ // about what the incremental change might be since we produced the APK.
+ if (persistenceMode == PersistenceMode.FULL_BUILD) {
+ currentBuild.verifierStatus = Optional.absent();
+ }
+
+ currentBuild.toXml(document, instantRun);
+ instantRun.setAttribute(ATTR_API_LEVEL, String.valueOf(apiLevel.getApiLevel()));
+ if (density != null) {
+ instantRun.setAttribute(ATTR_DENSITY, density);
+ }
+ if (abi != null) {
+ instantRun.setAttribute(ATTR_ABI, abi);
+ }
+ instantRun.setAttribute(ATTR_FORMAT, CURRENT_FORMAT);
+
+ switch(persistenceMode) {
+ case FULL_BUILD:
+ // only include the last build.
+ if (!previousBuilds.isEmpty()) {
+ instantRun.appendChild(previousBuilds.lastEntry().getValue().toXml(document));
+ }
+ break;
+ case INCREMENTAL_BUILD:
+ for (Build build : previousBuilds.values()) {
+ instantRun.appendChild(build.toXml(document));
+ }
+ break;
+ case TEMP_BUILD:
+ break;
+ default :
+ throw new RuntimeException("PersistenceMode not handled" + persistenceMode);
+ }
+ return instantRun;
+ }
+
+ /**
+ * Writes a temporary build-info.xml to persist the produced artifacts in case the build
+ * fails before we have a chance to write the final build-info.xml
+ * @throws ParserConfigurationException
+ * @throws IOException
+ */
+ private void writeTmpBuildInfo() throws ParserConfigurationException, IOException {
+
+ if (tmpBuildInfo == null) {
+ return;
+ }
+ Files.createParentDirs(tmpBuildInfo);
+ Files.write(toXml(PersistenceMode.TEMP_BUILD), tmpBuildInfo, Charsets.UTF_8);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunMethodVerifier.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunMethodVerifier.java
new file mode 100644
index 0000000..ff31a85
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunMethodVerifier.java
@@ -0,0 +1,112 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMultimap;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.Method;
+import org.objectweb.asm.tree.MethodNode;
+
+/**
+ * Verifies that a method implementation is compatible with the InstantRun current capabilities.
+ */
+public class InstantRunMethodVerifier {
+
+ /**
+ * Verifies a method implementation against the blacklisted list of APIs.
+ * @param method the method to verify
+ * @return a {@link InstantRunVerifierStatus} instance or null if the method is not making any
+ * blacklisted calls.
+ */
+ @NonNull
+ public static InstantRunVerifierStatus verifyMethod(MethodNode method) {
+
+ VerifierMethodVisitor mv = new VerifierMethodVisitor(method);
+ method.accept(mv);
+ return mv.incompatibleChange.or(InstantRunVerifierStatus.COMPATIBLE);
+ }
+
+ /**
+ * {@link MethodVisitor} implementation that checks methods invocation from this method against
+ * a list of blacklisted methods that is not compatible with the current InstantRun class
+ * reloading capability.
+ */
+ public static class VerifierMethodVisitor extends MethodNode {
+
+ Optional<InstantRunVerifierStatus> incompatibleChange = Optional.absent();
+
+ public VerifierMethodVisitor(MethodNode method) {
+ super(Opcodes.ASM5, method.access, method.name, method.desc, method.signature,
+ (String[]) method.exceptions.toArray(new String[method.exceptions.size()]));
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
+
+ Type receiver = Type.getType(owner);
+ if (!incompatibleChange.isPresent()) {
+ if (opcode == Opcodes.INVOKEVIRTUAL && blackListedMethods.containsKey(receiver)) {
+ for (Method method : blackListedMethods.get(receiver)) {
+ if (method.getName().equals(name) && method.getDescriptor().equals(desc)) {
+ incompatibleChange = Optional.of(InstantRunVerifierStatus.REFLECTION_USED);
+ }
+ }
+ }
+ }
+
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+
+ // List of all black listed methods.
+ // All these methods are java.lang.reflect classes and associated : since the new version of the
+ // class is loaded in a different class loader, the classes are in a different package and
+ // package private methods would need a setAccessble(true) to work correctly. Eventually, we
+ // could transform all reflection calls to automatically insert these setAccessible calls but
+ // at this point, we just don't enable InstantRun on those.
+ private static final ImmutableMultimap<Type, Method> blackListedMethods =
+ ImmutableMultimap.<Type, Method>builder()
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("Object get(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("boolean getBoolean(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("byte getByte(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("char getChar(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("double getDouble(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("float getFloat(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("int getInt(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("long getLong(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("short getShort(Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void set(Object, Object)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setBoolean(Object, boolean)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setByte(Object, byte)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setChar(Object, char)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setDouble(Object, double)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setFloat(Object, float)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setInt(Object, int)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setLong(Object, long)"))
+ .put(Type.getType("java/lang/reflect/Field"), Method.getMethod("void setShort(Object, short)"))
+ .put(Type.getType("java/lang/reflect/Constructor"), Method.getMethod("Object newInstance(Object[])"))
+ .put(Type.getType("java/lang/Class"), Method.getMethod("Object newInstance()"))
+ .put(Type.getType("java/lang/reflect/Method"), Method.getMethod("Object invoke(Object, Object[])"))
+ .build();
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunPatchingPolicy.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunPatchingPolicy.java
new file mode 100644
index 0000000..7f8f74b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunPatchingPolicy.java
@@ -0,0 +1,101 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.builder.model.AndroidProject;
+import com.android.sdklib.AndroidVersion;
+import com.google.common.base.Strings;
+
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
+
+import java.util.Locale;
+
+/**
+ * Patching policy for delivering incremental code changes and triggering a cold start (application
+ * restart).
+ */
+public enum InstantRunPatchingPolicy {
+
+ /**
+ * For Dalvik, a patch dex file will be generated with the incremental changes from the last
+ * non incremental build or the last build that contained changes identified by the verifier as
+ * incompatible.
+ */
+ PRE_LOLLIPOP,
+
+ /**
+ * For Lollipop and above, the application will be split in shards of dex files upon initial
+ * build and packaged as a native multi dex application. Each incremental changes will trigger
+ * rebuilding the affected shard dex files. Such dex files will be pushed on the device using
+ * the embedded micro-server and installed by it.
+ */
+ MULTI_DEX,
+
+ /**
+ * For Lollipop and above, each shard dex file described above will be packaged in a single pure
+ * split APK that will be pushed and installed on the device using adb install-multiple
+ * commands.
+ */
+ MULTI_APK;
+
+ /**
+ * Returns the patching policy following the {@link AndroidProject#PROPERTY_BUILD_API} value
+ * passed by Android Studio.
+ * @param version the {@link AndroidVersion}
+ * @param coldswapMode desired coldswap mode optionally provided.
+ * @param targetArchitecture the targeted architecture.
+ * @return a {@link InstantRunPatchingPolicy} instance.
+ */
+ @NonNull
+ public static InstantRunPatchingPolicy getPatchingPolicy(
+ @NonNull AndroidVersion version,
+ @Nullable String coldswapMode,
+ @Nullable String targetArchitecture) {
+
+ if (version.compareTo(AndroidVersion.ART_RUNTIME) < 0) {
+ return PRE_LOLLIPOP;
+ } else {
+ // whe dealing with Lollipop and above, by default, we use MULTI_DEX.
+ InstantRunPatchingPolicy defaultModeForArchitecture = MULTI_DEX;
+
+ if (Strings.isNullOrEmpty(coldswapMode)) {
+ return defaultModeForArchitecture;
+ }
+ // coldswap mode was provided, it trumps everything
+ ColdswapMode coldswap = ColdswapMode.valueOf(coldswapMode.toUpperCase(Locale.US));
+ switch(coldswap) {
+ case MULTIAPK: return MULTI_APK;
+ case MULTIDEX: return MULTI_DEX;
+ case AUTO:
+ if (version.getApiLevel() < 23) {
+ return MULTI_DEX;
+ } else {
+ return MULTI_APK;
+ }
+ case DEFAULT:
+ return MULTI_DEX;
+ default:
+ throw new RuntimeException("Cold-swap case not handled " + coldswap);
+ }
+ }
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunVerifier.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunVerifier.java
new file mode 100644
index 0000000..d4375cd
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunVerifier.java
@@ -0,0 +1,484 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.COMPATIBLE;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.FIELD_ADDED;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.FIELD_REMOVED;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.FIELD_TYPE_CHANGE;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.IMPLEMENTED_INTERFACES_CHANGE;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.INSTANT_RUN_DISABLED;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.METHOD_ADDED;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.METHOD_ANNOTATION_CHANGE;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.METHOD_DELETED;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.PARENT_CLASS_CHANGED;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.REFLECTION_USED;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.R_CLASS_CHANGE;
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.STATIC_INITIALIZER_CHANGE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.utils.AsmUtils;
+import com.google.common.base.Objects;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceMethodVisitor;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Instant Run Verifier responsible for checking that a class change (between two developers
+ * iteration) can be safely hot swapped on the device or not.
+ *
+ * ThreadSafe
+ */
+public class InstantRunVerifier {
+
+ private static final Comparator<MethodNode> METHOD_COMPARATOR = new MethodNodeComparator();
+
+ @VisibleForTesting
+ static final Comparator<AnnotationNode> ANNOTATION_COMPARATOR =
+ new AnnotationNodeComparator();
+ private static final Comparator<Object> OBJECT_COMPARATOR = new Comparator<Object>() {
+ @Override
+ public boolean areEqual(Object first, Object second) {
+ return Objects.equal(first, second);
+ }
+ };
+
+ public interface ClassBytesProvider {
+ byte[] load() throws IOException;
+ }
+
+ public static class ClassBytesFileProvider implements ClassBytesProvider {
+
+ private final File file;
+ public ClassBytesFileProvider(File file) {
+ this.file = file;
+ }
+
+ @Override
+ public byte[] load() throws IOException {
+ return Files.toByteArray(file);
+ }
+
+ @VisibleForTesting
+ public File getFile() {
+ return file;
+ }
+ }
+
+ public static class ClassBytesJarEntryProvider implements ClassBytesProvider {
+
+ private final JarFile jarFile;
+ private final JarEntry jarEntry;
+
+ public ClassBytesJarEntryProvider(JarFile jarFile, JarEntry jarEntry) {
+ this.jarFile = jarFile;
+ this.jarEntry = jarEntry;
+ }
+
+ @Override
+ public byte[] load() throws IOException {
+ InputStream is = jarFile.getInputStream(jarEntry);
+ try {
+ ByteStreams.toByteArray(is);
+ } finally {
+ Closeables.close(is, false /* swallowIOException */);
+ }
+
+ return new byte[0];
+ }
+ }
+
+ /**
+ * describe the difference between two collections of the same elements.
+ */
+ @VisibleForTesting
+ enum Diff {
+ /**
+ * no change, the collections are equals
+ */
+ NONE,
+ /**
+ * an element was added to the first collection.
+ */
+ ADDITION,
+ /**
+ * an element was removed from the first collection.
+ */
+ REMOVAL,
+ /**
+ * an element was changed.
+ */
+ CHANGE
+ }
+
+ private InstantRunVerifier() {
+ }
+
+
+ public static InstantRunVerifierStatus run(File original, File updated) throws IOException {
+ return run(new ClassBytesFileProvider(original), new ClassBytesFileProvider(updated));
+ }
+
+ // ASM API not generified.
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public static InstantRunVerifierStatus run(ClassBytesProvider original, ClassBytesProvider updated)
+ throws IOException {
+
+ ClassNode originalClass = loadClass(original);
+ ClassNode updatedClass = loadClass(updated);
+
+ if (!originalClass.superName.equals(updatedClass.superName)) {
+ return PARENT_CLASS_CHANGED;
+ }
+
+ if (diffList(originalClass.interfaces, updatedClass.interfaces,
+ OBJECT_COMPARATOR) != Diff.NONE) {
+ return IMPLEMENTED_INTERFACES_CHANGE;
+ }
+
+ if (diffList(originalClass.visibleAnnotations, updatedClass.visibleAnnotations,
+ ANNOTATION_COMPARATOR) != Diff.NONE) {
+ return CLASS_ANNOTATION_CHANGE;
+ }
+
+ // check if the class is InstantRunDisabled.
+ List<AnnotationNode> invisibleAnnotations = originalClass.invisibleAnnotations;
+ if (invisibleAnnotations!=null) {
+ for (AnnotationNode annotationNode : invisibleAnnotations) {
+
+ if (annotationNode.desc.equals(
+ IncrementalVisitor.DISABLE_ANNOTATION_TYPE.getDescriptor())) {
+ // potentially, we could try to see if anything has really changed between
+ // the two classes but the fact that we got an updated class means so far that
+ // we have a new version and should restart.
+ return INSTANT_RUN_DISABLED;
+ }
+ }
+ }
+
+ InstantRunVerifierStatus fieldChange = verifyFields(originalClass, updatedClass);
+ if (fieldChange != COMPATIBLE) {
+ return fieldChange;
+ }
+
+ return verifyMethods(originalClass, updatedClass);
+ }
+
+ @NonNull
+ private static InstantRunVerifierStatus verifyFields(
+ @NonNull ClassNode originalClass,
+ @NonNull ClassNode updatedClass) {
+
+ //noinspection unchecked
+ Diff diff = diffList(originalClass.fields, updatedClass.fields, new Comparator<FieldNode>()
+ {
+
+ @Override
+ public boolean areEqual(@Nullable FieldNode first, @Nullable FieldNode second) {
+ if ((first == null) && (second == null)) {
+ return true;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ return first.name.equals(second.name)
+ && first.desc.equals(second.desc)
+ && first.access == second.access
+ && Objects.equal(first.value, second.value);
+ }
+ });
+
+ if (diff != Diff.NONE) {
+ // Detect R$something classes, and report changes in them separately.
+ String name = originalClass.name;
+ int index = name.lastIndexOf('/');
+ if (index != -1 &&
+ name.startsWith("R$", index + 1) &&
+ (originalClass.access & Opcodes.ACC_PUBLIC) != 0 &&
+ (originalClass.access & Opcodes.ACC_FINAL) != 0 &&
+ originalClass.outerClass == null &&
+ originalClass.interfaces.isEmpty() &&
+ originalClass.superName.equals("java/lang/Object") &&
+ name.length() > 3 && Character.isLowerCase(name.charAt(2))) {
+ return R_CLASS_CHANGE;
+ }
+ }
+
+ switch (diff) {
+ case NONE:
+ return COMPATIBLE;
+ case ADDITION:
+ return FIELD_ADDED;
+ case REMOVAL:
+ return FIELD_REMOVED;
+ case CHANGE:
+ return FIELD_TYPE_CHANGE;
+ default:
+ throw new RuntimeException("Unhandled action : " + diff);
+ }
+ }
+
+ @NonNull
+ private static InstantRunVerifierStatus verifyMethods(
+ @NonNull ClassNode originalClass, @NonNull ClassNode updatedClass) {
+
+ @SuppressWarnings("unchecked") // ASM API.
+ List<MethodNode> nonVisitedMethodsOnUpdatedClass =
+ new ArrayList<MethodNode>(updatedClass.methods);
+
+ //noinspection unchecked
+ for(MethodNode methodNode : (List<MethodNode>) originalClass.methods) {
+
+ MethodNode updatedMethod = findMethod(updatedClass, methodNode.name, methodNode.desc);
+ if (updatedMethod == null) {
+ // although it's probably ok if a method got deleted since nobody should be calling
+ // it anymore BUT the application might be using reflection to get the list of
+ // methods and would still see the deleted methods. To be prudent, restart.
+ // However, if the class initializer got removed, it's always fine.
+ return methodNode.name.equals(AsmUtils.CLASS_INITIALIZER)
+ ? COMPATIBLE
+ : METHOD_DELETED;
+ }
+
+ // remove the method from the visited ones on the updated class.
+ nonVisitedMethodsOnUpdatedClass.remove(updatedMethod);
+
+ InstantRunVerifierStatus change = methodNode.name.equals(AsmUtils.CLASS_INITIALIZER)
+ ? visitClassInitializer(methodNode, updatedMethod)
+ : verifyMethod(methodNode, updatedMethod);
+
+ if (change != COMPATIBLE) {
+ return change;
+ }
+ }
+
+ if (!nonVisitedMethodsOnUpdatedClass.isEmpty()) {
+ return METHOD_ADDED;
+ }
+ return COMPATIBLE;
+ }
+
+ @NonNull
+ private static InstantRunVerifierStatus visitClassInitializer(MethodNode originalClassInitializer,
+ MethodNode updateClassInitializer) {
+
+ return METHOD_COMPARATOR.areEqual(originalClassInitializer, updateClassInitializer)
+ ? COMPATIBLE
+ : STATIC_INITIALIZER_CHANGE;
+ }
+
+ @SuppressWarnings("unchecked") // ASM API
+ @NonNull
+ private static InstantRunVerifierStatus verifyMethod(
+ MethodNode methodNode,
+ MethodNode updatedMethod) {
+
+ // check for annotations changes
+ if (diffList(methodNode.visibleAnnotations, updatedMethod.visibleAnnotations,
+ new AnnotationNodeComparator()) != Diff.NONE) {
+ return METHOD_ANNOTATION_CHANGE;
+ }
+
+ // the method exist in both classes, check if the original method was disabled for
+ // instantRun or contained calls to blacklisted APIs. If either of these conditions
+ // is true, and the method implementation has changed, a restart is needed.
+ boolean disabledMethod = false;
+ List<AnnotationNode> invisibleAnnotations = methodNode.invisibleAnnotations;
+ if (invisibleAnnotations != null) {
+ for (AnnotationNode originalMethodAnnotation : invisibleAnnotations) {
+ if (originalMethodAnnotation.desc.equals(
+ IncrementalVisitor.DISABLE_ANNOTATION_TYPE.getDescriptor())) {
+ disabledMethod = true;
+ }
+ }
+ }
+
+ boolean usingBlackListedAPIs =
+ InstantRunMethodVerifier.verifyMethod(updatedMethod) != COMPATIBLE;
+
+ // either disabled or using blacklisted APIs, let it through only if the method
+ // implementation is unchanged.
+ if ((disabledMethod || usingBlackListedAPIs) &&
+ !METHOD_COMPARATOR.areEqual(methodNode, updatedMethod)) {
+
+ return disabledMethod
+ ? INSTANT_RUN_DISABLED
+ : REFLECTION_USED;
+
+ }
+ return COMPATIBLE;
+ }
+
+ @Nullable
+ private static MethodNode findMethod(@NonNull ClassNode classNode,
+ @NonNull String name,
+ @Nullable String desc) {
+
+ //noinspection unchecked
+ for (MethodNode methodNode : (List<MethodNode>) classNode.methods) {
+
+ if (methodNode.name.equals(name) &&
+ ((desc == null && methodNode.desc == null) || (methodNode.desc.equals(desc)))) {
+ return methodNode;
+ }
+ }
+ return null;
+ }
+
+ private interface Comparator<T> {
+ boolean areEqual(@Nullable T first, @Nullable T second);
+ }
+
+ private static class MethodNodeComparator implements Comparator<MethodNode> {
+
+ @Override
+ public boolean areEqual(@Nullable MethodNode first, @Nullable MethodNode second) {
+ if (first==null && second==null) {
+ return true;
+ }
+ if (first==null || second==null) {
+ return false;
+ }
+ if (!first.name.equals(second.name) || !first.desc.equals(second.desc)) {
+ return false;
+ }
+ VerifierTextifier firstMethodTextifier = new VerifierTextifier();
+ VerifierTextifier secondMethodTextifier = new VerifierTextifier();
+ first.accept(new TraceMethodVisitor(firstMethodTextifier));
+ second.accept(new TraceMethodVisitor(secondMethodTextifier));
+
+ StringWriter firstText = new StringWriter();
+ StringWriter secondText = new StringWriter();
+ firstMethodTextifier.print(new PrintWriter(firstText));
+ secondMethodTextifier.print(new PrintWriter(secondText));
+
+ return firstText.toString().equals(secondText.toString());
+ }
+ }
+
+ /**
+ * Subclass of {@link Textifier} that will pretty print method bytecodes but will swallow the
+ * line numbers notification as it is not pertinent for the InstantRun hot swapping.
+ */
+ private static class VerifierTextifier extends Textifier {
+
+ protected VerifierTextifier() {
+ super(Opcodes.ASM5);
+ }
+
+ @Override
+ public void visitLineNumber(int i, Label label) {
+ // don't care about line numbers
+ }
+ }
+
+ public static class AnnotationNodeComparator implements Comparator<AnnotationNode> {
+
+ @Override
+ public boolean areEqual(@Nullable AnnotationNode first, @Nullable AnnotationNode second) {
+ // probably deep compare for values...
+ //noinspection unchecked
+ return (first == null && second == null) || (first != null && second != null)
+ && (OBJECT_COMPARATOR.areEqual(first.desc, second.desc) &&
+ diffList(first.values, second.values, OBJECT_COMPARATOR) == Diff.NONE);
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull
+ static <T> Diff diffList(
+ @Nullable List<T> one,
+ @Nullable List<T> two,
+ @NonNull Comparator<T> comparator) {
+
+ if (one == null && two == null) {
+ return Diff.NONE;
+ }
+ if (one == null) {
+ return Diff.ADDITION;
+ }
+ if (two == null) {
+ return Diff.REMOVAL;
+ }
+ List<T> copyOfOne = new ArrayList<T>(one);
+ List<T> copyOfTwo = new ArrayList<T>(two);
+
+ for (T elementOfTwo : two) {
+ T commonElement = getElementOf(copyOfOne, elementOfTwo, comparator);
+ if (commonElement != null) {
+ copyOfOne.remove(commonElement);
+ }
+ }
+
+ for (T elementOfOne : one) {
+ T commonElement = getElementOf(copyOfTwo, elementOfOne, comparator);
+ if (commonElement != null) {
+ copyOfTwo.remove(commonElement);
+ }
+ }
+ if ((!copyOfOne.isEmpty()) && (copyOfOne.size() == copyOfTwo.size())) {
+ return Diff.CHANGE;
+ }
+ if (!copyOfOne.isEmpty()) {
+ return Diff.REMOVAL;
+ }
+ return copyOfTwo.isEmpty() ? Diff.NONE : Diff.ADDITION;
+ }
+
+ @Nullable
+ public static <T> T getElementOf(List<T> list, T element, Comparator<T> comparator) {
+ for (T elementOfList : list) {
+ if (comparator.areEqual(elementOfList, element)) {
+ return elementOfList;
+ }
+ }
+ return null;
+ }
+
+ static ClassNode loadClass(ClassBytesProvider classFile) throws IOException {
+ byte[] classBytes = classFile.load();
+ ClassReader classReader = new ClassReader(classBytes);
+
+ org.objectweb.asm.tree.ClassNode classNode = new org.objectweb.asm.tree.ClassNode();
+ classReader.accept(classNode, ClassReader.EXPAND_FRAMES);
+ return classNode;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunVerifierStatus.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunVerifierStatus.java
new file mode 100644
index 0000000..87d0a6d
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunVerifierStatus.java
@@ -0,0 +1,74 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+/**
+ * Changes to a class that cannot be hot swapped with the current InstantRun runtime
+ */
+public enum InstantRunVerifierStatus {
+
+ // changes are compatible with current InstantRun features.
+ COMPATIBLE,
+
+ // the verifier did not run successfully.
+ NOT_RUN,
+
+ // InstantRun disabled on element like a method, class or package.
+ INSTANT_RUN_DISABLED,
+
+ // Any inability to run the verifier on a file will be tagged as such
+ INSTANT_RUN_FAILURE,
+
+ // A new class was added.
+ CLASS_ADDED,
+
+ // changes in the hierarchy
+ PARENT_CLASS_CHANGED,
+ IMPLEMENTED_INTERFACES_CHANGE,
+
+ // class related changes.
+ CLASS_ANNOTATION_CHANGE,
+ STATIC_INITIALIZER_CHANGE,
+
+ // changes in constructors,
+ CONSTRUCTOR_SIGNATURE_CHANGE,
+
+ // changes in method
+ METHOD_SIGNATURE_CHANGE,
+ METHOD_ANNOTATION_CHANGE,
+ METHOD_DELETED,
+ METHOD_ADDED,
+
+ // changes in fields.
+ FIELD_ADDED,
+ FIELD_REMOVED,
+ // change of field type or kind (static | instance)
+ FIELD_TYPE_CHANGE,
+
+ R_CLASS_CHANGE,
+
+ // reflection use
+ REFLECTION_USED,
+
+ JAVA_RESOURCES_CHANGED,
+
+ DEPENDENCY_CHANGED,
+
+ // the binary manifest file changed, probably due to references to resources which ID changed
+ // since last build.
+ BINARY_MANIFEST_FILE_CHANGE
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunWrapperTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunWrapperTask.java
new file mode 100644
index 0000000..4254b5a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/InstantRunWrapperTask.java
@@ -0,0 +1,162 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.android.build.gradle.internal.transforms.InstantRunBuildType;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.util.Locale;
+
+/**
+ * InstantRun related tasks wrapping code, this task is added twice to the task trees, once
+ * for the full build (assembleVariant), once for the incremental build. Only one of these two
+ * task will execute from the IDE.
+ *
+ * Task responsibility :
+ * <ul>generate the build-info.xml on each gradle invocation with InstantRun enabled.</ul>
+ * <ul>delete incremental change files when doing a full build.</ul>
+ */
+public class InstantRunWrapperTask extends BaseTask {
+
+ @OutputFile
+ File buildInfoFile;
+
+ @Input
+ String buildId;
+
+ TaskType taskType;
+
+ File incrementChangesFile;
+ File tmpBuildInfoFile;
+
+ Logger logger;
+
+ InstantRunBuildContext instantRunBuildContext;
+
+ @TaskAction
+ public void executeAction() {
+
+ InstantRunBuildContext.PersistenceMode persistenceMode = taskType == TaskType.FULL
+ ? InstantRunBuildContext.PersistenceMode.FULL_BUILD
+ : InstantRunBuildContext.PersistenceMode.INCREMENTAL_BUILD;
+
+ // done with the instant run context.
+ instantRunBuildContext.close(persistenceMode);
+
+ // saves the build information xml file.
+ try {
+ // only write past builds in incremental mode.
+ String xml = instantRunBuildContext.toXml(persistenceMode);
+ if (logger.isEnabled(LogLevel.DEBUG)) {
+ logger.debug("build-id $1$l, build-info.xml : %2$s",
+ instantRunBuildContext.getBuildId(), xml);
+ }
+ Files.createParentDirs(buildInfoFile);
+ Files.write(xml, buildInfoFile, Charsets.UTF_8);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("Exception while saving build-info.xml : %s", e.getMessage()));
+ }
+
+ // if this is a full build, delete the incremental files recorders.
+ if (taskType == TaskType.FULL && incrementChangesFile.exists()) {
+ if (!incrementChangesFile.delete()) {
+ logger.warn(String.format("Cannot delete %1$s", incrementChangesFile));
+ }
+ }
+
+ // since we closed and produce the build-info.xml, delete any temporary one.
+ if (tmpBuildInfoFile.exists()) {
+ if (!tmpBuildInfoFile.delete()) {
+ logger.warn(String.format("Cannot delete %1$s", tmpBuildInfoFile));
+ }
+ }
+ }
+
+ public enum TaskType {
+ INCREMENTAL,
+ FULL
+ }
+
+ public static class ConfigAction implements TaskConfigAction<InstantRunWrapperTask> {
+
+ public static File getBuildInfoFile(VariantScope scope) {
+ return new File(scope.getRestartDexOutputFolder(), "build-info.xml");
+ }
+
+ public static File getTmpBuildInfoFile(VariantScope scope) {
+ return new File(scope.getRestartDexOutputFolder(), "tmp-build-info.xml");
+ }
+
+
+ private final String taskName;
+ private final TaskType taskType;
+ private final File incrementalChangesFile;
+ private final VariantScope variantScope;
+ private final Logger logger;
+
+ public ConfigAction(@NonNull VariantScope scope,
+ @NonNull TaskType taskType,
+ @NonNull Logger logger) {
+ this.taskName = scope.getTaskName(taskType.name().toLowerCase(Locale.getDefault()),
+ "BuildInfoGenerator");
+ this.variantScope = scope;
+ this.logger = logger;
+ this.taskType = taskType;
+ this.incrementalChangesFile =
+ InstantRunBuildType.RESTART.getIncrementalChangesFile(variantScope);
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return taskName;
+ }
+
+ @NonNull
+ @Override
+ public Class<InstantRunWrapperTask> getType() {
+ return InstantRunWrapperTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull InstantRunWrapperTask task) {
+ task.setDescription("InstantRun task to build incremental artifacts");
+ task.setVariantName(variantScope.getVariantConfiguration().getFullName());
+ task.buildInfoFile = getBuildInfoFile(variantScope);
+ task.tmpBuildInfoFile = getTmpBuildInfoFile(variantScope);
+ task.instantRunBuildContext = variantScope.getInstantRunBuildContext();
+ task.logger = logger;
+ task.taskType = taskType;
+ task.incrementChangesFile = incrementalChangesFile;
+ task.buildId = String.valueOf(task.instantRunBuildContext.getBuildId());
+ }
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/MethodRedirection.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/MethodRedirection.java
new file mode 100644
index 0000000..c2ef5fa
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/MethodRedirection.java
@@ -0,0 +1,66 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.tree.LabelNode;
+
+import java.util.List;
+
+public class MethodRedirection extends Redirection {
+
+ public final Type type;
+
+ MethodRedirection(LabelNode label, String name, Type type) {
+ super(label, name);
+ this.type = type;
+ }
+
+ /**
+ * For methods, restore creates a return from the dispatch call, to exit the method
+ * once the new implementation has been executed. for void methods, this is an empty return
+ * and for all other methods, it returns the value from the dispatch call.
+ * <p/>
+ * Note that the generated bytecode does not have a direct translation to code, but as an
+ * example, this restore implementation in combination with the base class generates the
+ * following for methods with avoid return type:
+ * <code>
+ * if ($change != null) {
+ * $change.access$dispatch($name, new object[] { arg0, ... argsN })
+ * return;
+ * }
+ * $originalMethodBody
+ *</code>
+ * and the following for methods with a non-void return type:
+ * <code>
+ * if ($change != null) {
+ * return $change.access$dispatch($name, new object[] { arg0, ... argsN })
+ * }
+ * $originalMethodBody
+ *</code>
+ */
+ @Override
+ protected void restore(GeneratorAdapter mv, List<Type> args) {
+ if (type == Type.VOID_TYPE) {
+ mv.pop();
+ } else {
+ ByteCodeUtils.unbox(mv, type);
+ }
+ mv.returnValue();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/Redirection.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/Redirection.java
new file mode 100644
index 0000000..3302858
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/Redirection.java
@@ -0,0 +1,132 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.android.annotations.NonNull;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+import org.objectweb.asm.tree.LabelNode;
+
+import java.util.List;
+
+/**
+ * A redirection is the part of an instrumented method that calls out to a different implementation.
+ */
+public abstract class Redirection {
+ /**
+ * The name of the method we redirect to.
+ */
+ @NonNull
+ private final String name;
+
+ /**
+ * The position where this redirection should happen.
+ */
+ @NonNull
+ private final LabelNode label;
+
+
+ Redirection(@NonNull LabelNode label, @NonNull String name) {
+ this.name = name;
+ this.label = label;
+ }
+
+ /**
+ * Adds the instructions to do a generic redirection.
+ * <p/>
+ * Note that the generated bytecode does not have a direct translation to code, but as an
+ * example, the following code block gets inserted.
+ * <code>
+ * if ($change != null) {
+ * $change.access$dispatch($name, new object[] { arg0, ... argsN })
+ * $anyCodeInsertedbyRestore
+ * }
+ * $originalMethodBody
+ *</code>
+ * @param mv the method visitor to add the instructions to.
+ * @param change the local variable containing the alternate implementation.
+ * @param args the type of the local variable that need to be forwarded.
+ */
+ void redirect(GeneratorAdapter mv, int change, List<Type> args) {
+ // code to check if a new implementation of the current class is available.
+ Label l0 = new Label();
+ mv.loadLocal(change);
+ mv.visitJumpInsn(Opcodes.IFNULL, l0);
+ mv.loadLocal(change);
+ mv.push(name);
+
+ // create an array of objects capable of containing all the parameters and optionally the "this"
+ createLocals(mv, args);
+
+ // we need to maintain the stack index when loading parameters from, as for long and double
+ // values, it uses 2 stack elements, all others use only 1 stack element.
+ int stackIndex = 0;
+ for (int arrayIndex = 0; arrayIndex < args.size(); arrayIndex++) {
+ Type arg = args.get(arrayIndex);
+ // duplicate the array of objects reference, it will be used to store the value in.
+ mv.dup();
+ // index in the array of objects to store the boxed parameter.
+ mv.push(arrayIndex);
+ // Pushes the appropriate local variable on the stack
+ redirectLocal(mv, stackIndex, arg);
+ // potentially box up intrinsic types.
+ mv.box(arg);
+ mv.arrayStore(Type.getType(Object.class));
+ // stack index must progress according to the parameter type we just processed.
+ stackIndex += arg.getSize();
+ }
+
+ // now invoke the generic dispatch method.
+ mv.invokeInterface(IncrementalVisitor.CHANGE_TYPE,Method.getMethod("Object access$dispatch(String, Object[])"));
+
+ // Restore the state after the redirection
+ restore(mv, args);
+ // jump label for classes without any new implementation, just invoke the original
+ // method implementation.
+ mv.visitLabel(l0);
+ }
+
+ /**
+ * Creates and pushes to the stack the array to hold all the parameters to redirect, and
+ * optionally this.
+ */
+ protected void createLocals(GeneratorAdapter mv, List<Type> args) {
+ mv.push(args.size());
+ mv.newArray(Type.getType(Object.class));
+ }
+
+ /**
+ * After the redirection is called, this methods handles restoring the state given
+ * the return values of the redirection.
+ */
+ protected abstract void restore(GeneratorAdapter mv, List<Type> args);
+
+ /**
+ * Pushes in the stack the value that should be redirected for the given local.
+ */
+ protected void redirectLocal(GeneratorAdapter mv, int local, Type arg) {
+ mv.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), local);
+ }
+
+ public LabelNode getPosition() {
+ return label;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/StringSwitch.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/StringSwitch.java
new file mode 100644
index 0000000..ed87028
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/incremental/StringSwitch.java
@@ -0,0 +1,241 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Ordering;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Class for generating an efficient string switch in ByteCode.
+ *
+ * Current implementation looks like:
+ *
+ * switch(s.hashCode()) {
+ * case 192: visitCase(s);
+ * case 312: visitCase(s);
+ * case 1024:
+ * if (s.equals("collided_method1")) {
+ * visit(s);
+ * } else if (s.equals("collided_method2")) {
+ * visit(s);
+ * }
+ * visitDefault();
+ * default:
+ * visitDefault();
+ * }
+ *
+ * In the most common case of no hash collisions, only the hashCode level if switching is needed.
+ */
+abstract class StringSwitch {
+ static Function<String, Integer> hashMethod = new Function<String, Integer>() {
+ @Override
+ public Integer apply(String input) {
+ return input.hashCode();
+ }
+ };
+
+ // Set this to a small positive number to force has hashcode collisions to exercise the
+ // length and character checks.
+ private static final Integer FORCE_HASH_COLLISION_MODULUS = null;
+
+ // Figure out some types and methods ahead of time.
+ private static final Type OBJECT_TYPE = Type.getType(java.lang.Object.class);
+ private static final Type STRING_TYPE = Type.getType(java.lang.String.class);
+ private static final Type INSTANT_RELOAD_EXCEPTION_TYPE =
+ Type.getType(IncrementalVisitor.PACKAGE + "/InstantReloadException");
+
+ // Methods overridden by caller to implement the switch behavior
+
+ // Caller-implemented behavior should push one string onto the stack. This is the string to be
+ // matched.
+ abstract void visitString();
+
+ // Caller-implemented behavior for when a string is matched. This method should return a value
+ // appropriate for the method that it exists in or throw an exception.
+ abstract void visitCase(String string);
+
+ // Caller-implemented behavior to handle the default case of the switch. The default is an
+ // error case so this method should throw an exception.
+ abstract void visitDefault();
+
+ // Override this method to provide a different hash generation method. You'll also need
+ // to change hashMethod in this class to correspond
+ void visitHashMethod(GeneratorAdapter mv) {
+ mv.invokeVirtual(STRING_TYPE, Method.getMethod("int hashCode()"));
+ }
+
+ /**
+ * Emit code for a string if-else block.
+ *
+ * if (s.equals("collided_method1")) {
+ * visit(s);
+ * } else if (s.equals("collided_method2")) {
+ * visit(s);
+ * }
+ *
+ * In the most common case of just one string, this degenerates to:
+ *
+ * visit(s)
+ *
+ */
+ private void visitx(GeneratorAdapter mv, List<String> strings) {
+ if (strings.size() == 1) {
+ visitCase(strings.get(0));
+ return;
+ }
+ for (int i = 0; i < strings.size(); ++i) {
+ String string = strings.get(i);
+ Label label = new Label();
+ visitString();
+ mv.visitLdcInsn(string);
+ mv.invokeVirtual(STRING_TYPE, Method.getMethod("boolean equals(Object)"));
+ mv.visitJumpInsn(Opcodes.IFEQ, label);
+ visitCase(string);
+ mv.visitLabel(label);
+ }
+
+ visitDefault();
+ }
+
+ /**
+ * Emit code for a string switch for the given string classifier.
+ *
+ * switch(s.hashCode()) {
+ * case 192: visitCase(s);
+ * case 312: visitCase(s);
+ * case 1024:
+ * if (s.equals("collided_method1")) {
+ * visit(s);
+ * } else if (s.equals("collided_method2")) {
+ * visit(s);
+ * }
+ * visitDefault();
+ * default:
+ * visitDefault();
+ * }
+ *
+ **/
+ private void visitClassifier(GeneratorAdapter mv, Set<String> strings) {
+ visitString();
+ visitHashMethod(mv);
+
+ // Group strings by hash code.
+ Multimap<Integer, String> buckets = Multimaps.index(strings, hashMethod);
+ List<Map.Entry<Integer, Collection<String>>> sorted = Ordering.natural()
+ .onResultOf(new Function<Map.Entry<Integer, Collection<String>>, Integer>() {
+ @Override
+ public Integer apply(Map.Entry<Integer, Collection<String>> entry) {
+ return entry.getKey();
+ }
+ }).immutableSortedCopy(buckets.asMap().entrySet());
+
+ int sortedHashes[] = new int[sorted.size()];
+ List<String> sortedCases[] = new List[sorted.size()];
+ int index = 0;
+ for (Map.Entry<Integer, Collection<String>> entry : sorted) {
+ sortedHashes[index] = entry.getKey();
+ sortedCases[index] = Lists.newCopyOnWriteArrayList(entry.getValue());
+ index++;
+ }
+
+ // Label for each hash and for default.
+ Label labels[] = new Label[sorted.size()];
+ Label defaultLabel = new Label();
+ for (int i = 0; i < sorted.size(); ++i) {
+ labels[i] = new Label();
+ }
+
+ // Create a switch that dispatches to each label from the hash code of
+ mv.visitLookupSwitchInsn(defaultLabel, sortedHashes, labels);
+
+ // Create the cases.
+ for (int i = 0; i < sorted.size(); ++i) {
+ mv.visitLabel(labels[i]);
+ visitx(mv, sortedCases[i]);
+ }
+ mv.visitLabel(defaultLabel);
+ visitDefault();
+ }
+
+ /**
+ * Generates a standard error exception with message similar to:
+ *
+ * String switch could not find 'equals.(Ljava/lang/Object;)Z' with hashcode 0
+ * in com/example/basic/GrandChild
+ *
+ * @param mv The generator adaptor used to emit the lookup switch code.
+ * @param visitedClassName The abstract string trie structure.
+ */
+ void writeMissingMessageWithHash(GeneratorAdapter mv, String visitedClassName) {
+ mv.newInstance(INSTANT_RELOAD_EXCEPTION_TYPE);
+ mv.dup();
+ mv.push("String switch could not find '%s' with hashcode %s in %s");
+ mv.push(3);
+ mv.newArray(OBJECT_TYPE);
+ mv.dup();
+ mv.push(0);
+ visitString();
+ mv.arrayStore(OBJECT_TYPE);
+ mv.dup();
+ mv.push(1);
+ visitString();
+ visitHashMethod(mv);
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ "java/lang/Integer",
+ "valueOf",
+ "(I)Ljava/lang/Integer;", false);
+ mv.arrayStore(OBJECT_TYPE);
+ mv.dup();
+ mv.push(2);
+ mv.push(visitedClassName);
+ mv.arrayStore(OBJECT_TYPE);
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ "java/lang/String",
+ "format",
+ "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false);
+ mv.invokeConstructor(INSTANT_RELOAD_EXCEPTION_TYPE,
+ Method.getMethod("void <init> (String)"));
+ mv.throwException();
+ }
+
+ /**
+ * Main entry point for creation of string switch
+ *
+ * @param mv The generator adaptor used to emit the lookup switch code.
+ * @param strings The closed set of strings to generate a switch for.
+ */
+ void visit(GeneratorAdapter mv, Set<String> strings) {
+ visitClassifier(mv, strings);
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AaptOptionsImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AaptOptionsImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AaptOptionsImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AaptOptionsImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java
new file mode 100644
index 0000000..04befec
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidArtifactOutput;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.InstantRun;
+import com.android.builder.model.NativeLibrary;
+import com.android.builder.model.SourceProvider;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of AndroidArtifact that is serializable
+ */
+public class AndroidArtifactImpl extends BaseArtifactImpl implements AndroidArtifact, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final Collection<AndroidArtifactOutput> outputs;
+ private final boolean isSigned;
+ @Nullable
+ private final String signingConfigName;
+ @NonNull
+ private final String applicationId;
+ @NonNull
+ private final String sourceGenTaskName;
+
+ @NonNull
+ private final List<File> generatedResourceFolders;
+ @Nullable
+ private final Set<String> abiFilters;
+ @NonNull
+ private final Collection<NativeLibrary> nativeLibraries;
+ @NonNull
+ private final Map<String, ClassField> buildConfigFields;
+ @NonNull
+ private final Map<String, ClassField> resValues;
+ @NonNull
+ private final InstantRun instantRun;
+
+ AndroidArtifactImpl(
+ @NonNull String name,
+ @NonNull Collection<AndroidArtifactOutput> outputs,
+ @NonNull String assembleTaskName,
+ boolean isSigned,
+ @Nullable String signingConfigName,
+ @NonNull String applicationId,
+ @NonNull String sourceGenTaskName,
+ @NonNull String compileTaskName,
+ @NonNull List<File> generatedSourceFolders,
+ @NonNull List<File> generatedResourceFolders,
+ @NonNull File classesFolder,
+ @NonNull File javaResourcesFolder,
+ @NonNull Dependencies dependencies,
+ @Nullable SourceProvider variantSourceProvider,
+ @Nullable SourceProvider multiFlavorSourceProviders,
+ @Nullable Set<String> abiFilters,
+ @NonNull Collection<NativeLibrary> nativeLibraries,
+ @NonNull Map<String,ClassField> buildConfigFields,
+ @NonNull Map<String,ClassField> resValues,
+ @NonNull InstantRun instantRun) {
+ super(name, assembleTaskName, compileTaskName,
+ classesFolder, javaResourcesFolder,
+ dependencies, variantSourceProvider, multiFlavorSourceProviders,
+ generatedSourceFolders);
+
+ this.outputs = outputs;
+ this.isSigned = isSigned;
+ this.signingConfigName = signingConfigName;
+ this.applicationId = applicationId;
+ this.sourceGenTaskName = sourceGenTaskName;
+ this.generatedResourceFolders = generatedResourceFolders;
+ this.abiFilters = abiFilters;
+ this.nativeLibraries = nativeLibraries;
+ this.buildConfigFields = buildConfigFields;
+ this.resValues = resValues;
+ this.instantRun = instantRun;
+ }
+
+ @NonNull
+ @Override
+ public Collection<AndroidArtifactOutput> getOutputs() {
+ return outputs;
+ }
+
+ @Override
+ public boolean isSigned() {
+ return isSigned;
+ }
+
+ @Nullable
+ @Override
+ public String getSigningConfigName() {
+ return signingConfigName;
+ }
+
+ @NonNull
+ @Override
+ public String getApplicationId() {
+ return applicationId;
+ }
+
+ @NonNull
+ @Override
+ public String getSourceGenTaskName() {
+ return sourceGenTaskName;
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getIdeSetupTaskNames() {
+ return Sets.newHashSet(getSourceGenTaskName());
+ }
+
+ @NonNull
+ @Override
+ public List<File> getGeneratedResourceFolders() {
+ return generatedResourceFolders;
+ }
+
+ @Nullable
+ @Override
+ public Set<String> getAbiFilters() {
+ return abiFilters;
+ }
+
+ @NonNull
+ @Override
+ public Collection<NativeLibrary> getNativeLibraries() {
+ return nativeLibraries;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getBuildConfigFields() {
+ return buildConfigFields;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getResValues() {
+ return resValues;
+ }
+
+ @NonNull
+ @Override
+ public InstantRun getInstantRun() {
+ return instantRun;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactOutputImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactOutputImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactOutputImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactOutputImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidLibraryImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidLibraryImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidLibraryImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/AndroidLibraryImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ApiVersionImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ApiVersionImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ApiVersionImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ApiVersionImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ArtifactMetaDataImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ArtifactMetaDataImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ArtifactMetaDataImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ArtifactMetaDataImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseArtifactImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseArtifactImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseArtifactImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseArtifactImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseConfigImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseConfigImpl.java
new file mode 100644
index 0000000..7e7dcf7
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BaseConfigImpl.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.BaseConfig;
+import com.android.builder.model.ClassField;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An implementation of BaseConfig specifically for sending as part of the Android model
+ * through the Gradle tooling API.
+ */
+abstract class BaseConfigImpl implements BaseConfig, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Nullable
+ private String mApplicationIdSuffix;
+ @NonNull
+ private final Map<String, Object> mManifestPlaceholders;
+ @NonNull
+ private final Map<String, ClassField> mBuildConfigFields;
+ @NonNull
+ private final Map<String, ClassField> mResValues;
+ @Nullable
+ private Boolean mMultiDexEnabled;
+ @Nullable
+ private File mMultiDexKeepFile;
+ @Nullable
+ private File mMultiDexKeepProguard;
+ @Nullable
+ private List<File> mJarJarRuleFiles;
+
+ protected BaseConfigImpl(@NonNull BaseConfig baseConfig) {
+ mApplicationIdSuffix = baseConfig.getApplicationIdSuffix();
+ mManifestPlaceholders = ImmutableMap.copyOf(baseConfig.getManifestPlaceholders());
+ mBuildConfigFields = ImmutableMap.copyOf(baseConfig.getBuildConfigFields());
+ mResValues = ImmutableMap.copyOf(baseConfig.getResValues());
+ mMultiDexEnabled = baseConfig.getMultiDexEnabled();
+ mMultiDexKeepFile = baseConfig.getMultiDexKeepFile();
+ mMultiDexKeepProguard = baseConfig.getMultiDexKeepProguard();
+ mJarJarRuleFiles = ImmutableList.copyOf(baseConfig.getJarJarRuleFiles());
+ }
+
+ @Nullable
+ @Override
+ public String getApplicationIdSuffix() {
+ return mApplicationIdSuffix;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getBuildConfigFields() {
+ return mBuildConfigFields;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getResValues() {
+ return mResValues;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getProguardFiles() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getConsumerProguardFiles() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getTestProguardFiles() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ @NonNull
+ public Map<String, Object> getManifestPlaceholders() {
+ return mManifestPlaceholders;
+ }
+
+ @Override
+ @Nullable
+ public Boolean getMultiDexEnabled() {
+ return mMultiDexEnabled;
+ }
+
+ @Nullable
+ @Override
+ public File getMultiDexKeepFile() {
+ return mMultiDexKeepFile;
+ }
+
+ @Nullable
+ @Override
+ public File getMultiDexKeepProguard() {
+ return mMultiDexKeepProguard;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJarJarRuleFiles() {
+ return mJarJarRuleFiles;
+ }
+
+ @Override
+ public String toString() {
+ return "BaseConfigImpl{" +
+ "applicationIdSuffix='" + mApplicationIdSuffix + '\'' +
+ ", mManifestPlaceholders=" + mManifestPlaceholders +
+ ", mBuildConfigFields=" + mBuildConfigFields +
+ ", mResValues=" + mResValues +
+ ", mMultiDexEnabled=" + mMultiDexEnabled +
+ ", mJarJarRuleFiles=" + mJarJarRuleFiles +
+ '}';
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeContainerImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeContainerImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeContainerImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeContainerImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java
new file mode 100644
index 0000000..5321068
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.SigningConfig;
+
+import java.io.Serializable;
+
+/**
+ * Implementation of BuildType that is serializable. Objects used in the DSL cannot be
+ * serialized.
+ */
+class BuildTypeImpl extends BaseConfigImpl implements BuildType, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private boolean debuggable;
+ private boolean testCoverageEnabled;
+ private boolean jniDebuggable;
+ private boolean pseudoLocalesEnabled;
+ private boolean renderscriptDebuggable;
+ private int renderscriptOptimLevel;
+ private String versionNameSuffix;
+ private boolean minifyEnabled;
+ private boolean zipAlignEnabled;
+ private boolean embedMicroApp;
+
+ @NonNull
+ static BuildTypeImpl cloneBuildType(@NonNull BuildType buildType) {
+ BuildTypeImpl clonedBuildType = new BuildTypeImpl(buildType);
+
+ clonedBuildType.name = buildType.getName();
+ clonedBuildType.debuggable = buildType.isDebuggable();
+ clonedBuildType.testCoverageEnabled = buildType.isTestCoverageEnabled();
+ clonedBuildType.jniDebuggable = buildType.isJniDebuggable();
+ clonedBuildType.renderscriptDebuggable = buildType.isRenderscriptDebuggable();
+ clonedBuildType.renderscriptOptimLevel = buildType.getRenderscriptOptimLevel();
+ clonedBuildType.versionNameSuffix = buildType.getVersionNameSuffix();
+ clonedBuildType.minifyEnabled = buildType.isMinifyEnabled();
+ clonedBuildType.zipAlignEnabled = buildType.isZipAlignEnabled();
+ clonedBuildType.embedMicroApp = buildType.isEmbedMicroApp();
+ clonedBuildType.pseudoLocalesEnabled = buildType.isPseudoLocalesEnabled();
+
+ return clonedBuildType;
+ }
+
+ private BuildTypeImpl(@NonNull BuildType buildType) {
+ super(buildType);
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return debuggable;
+ }
+
+ @Override
+ public boolean isTestCoverageEnabled() {
+ return testCoverageEnabled;
+ }
+
+ @Override
+ public boolean isJniDebuggable() {
+ return jniDebuggable;
+ }
+
+ @Override
+ public boolean isRenderscriptDebuggable() {
+ return renderscriptDebuggable;
+ }
+
+ @Override
+ public boolean isPseudoLocalesEnabled() {
+ return pseudoLocalesEnabled;
+ }
+
+ @Override
+ public int getRenderscriptOptimLevel() {
+ return renderscriptOptimLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getVersionNameSuffix() {
+ return versionNameSuffix;
+ }
+
+ @Override
+ public boolean isMinifyEnabled() {
+ return minifyEnabled;
+ }
+
+ @Override
+ public boolean isZipAlignEnabled() {
+ return zipAlignEnabled;
+ }
+
+ @Override
+ public boolean isEmbedMicroApp() {
+ return embedMicroApp;
+ }
+
+ @Nullable
+ @Override
+ public SigningConfig getSigningConfig() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "BuildTypeImpl{" +
+ "name='" + name + '\'' +
+ ", debuggable=" + debuggable +
+ ", testCoverageEnabled=" + testCoverageEnabled +
+ ", jniDebuggable=" + jniDebuggable +
+ ", renderscriptDebuggable=" + renderscriptDebuggable +
+ ", renderscriptOptimLevel=" + renderscriptOptimLevel +
+ ", versionNameSuffix='" + versionNameSuffix + '\'' +
+ ", minifyEnabled=" + minifyEnabled +
+ ", zipAlignEnabled=" + zipAlignEnabled +
+ ", embedMicroApp=" + embedMicroApp +
+ "} " + super.toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
new file mode 100644
index 0000000..d0a2c23
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.builder.model.AaptOptions;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ArtifactMetaData;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.JavaCompileOptions;
+import com.android.builder.model.LintOptions;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.model.NativeToolchain;
+import com.android.builder.model.Variant;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * Implementation of the AndroidProject model object.
+ */
+class DefaultAndroidProject implements AndroidProject, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String modelVersion;
+ @NonNull
+ private final String name;
+ @NonNull
+ private final String compileTarget;
+ @NonNull
+ private final Collection<String> bootClasspath;
+ @NonNull
+ private final Collection<File> frameworkSource;
+ @NonNull
+ private final Collection<SigningConfig> signingConfigs;
+ @NonNull
+ private final AaptOptions aaptOptions;
+ @NonNull
+ private final Collection<ArtifactMetaData> extraArtifacts;
+ @NonNull
+ private final Collection<String> unresolvedDependencies;
+ @NonNull
+ private final Collection<SyncIssue> syncIssues;
+
+ private final int generation;
+
+ @NonNull
+ private final JavaCompileOptions javaCompileOptions;
+ @NonNull
+ private final LintOptions lintOptions;
+ @NonNull
+ private final File buildFolder;
+ @NonNull
+ private final String buildToolsVersion;
+ @Nullable
+ private final String resourcePrefix;
+ @NonNull
+ private final Collection<NativeToolchain> nativeToolchains;
+ private final boolean isLibrary;
+ private final int apiVersion;
+
+ private final Collection<BuildTypeContainer> buildTypes = Lists.newArrayList();
+ private final Collection<ProductFlavorContainer> productFlavors = Lists.newArrayList();
+ private final Collection<Variant> variants = Lists.newArrayList();
+
+ private ProductFlavorContainer defaultConfig;
+
+ @NonNull
+ private final Collection<String> flavorDimensions;
+
+ DefaultAndroidProject(
+ @NonNull String modelVersion,
+ @NonNull String name,
+ @NonNull Collection<String> flavorDimensions,
+ @NonNull String compileTarget,
+ @NonNull Collection<String> bootClasspath,
+ @NonNull Collection<File> frameworkSource,
+ @NonNull Collection<SigningConfig> signingConfigs,
+ @NonNull AaptOptions aaptOptions,
+ @NonNull Collection<ArtifactMetaData> extraArtifacts,
+ @NonNull Collection<String> unresolvedDependencies,
+ @NonNull Collection<SyncIssue> syncIssues,
+ @NonNull CompileOptions compileOptions,
+ @NonNull LintOptions lintOptions,
+ @NonNull File buildFolder,
+ @Nullable String resourcePrefix,
+ @NonNull Collection<NativeToolchain> nativeToolchains,
+ @NonNull String buildToolsVersion,
+ boolean isLibrary,
+ int apiVersion,
+ int generation) {
+ this.modelVersion = modelVersion;
+ this.name = name;
+ this.flavorDimensions = flavorDimensions;
+ this.compileTarget = compileTarget;
+ this.bootClasspath = bootClasspath;
+ this.frameworkSource = frameworkSource;
+ this.signingConfigs = signingConfigs;
+ this.aaptOptions = aaptOptions;
+ this.extraArtifacts = extraArtifacts;
+ this.unresolvedDependencies = unresolvedDependencies;
+ this.syncIssues = syncIssues;
+ javaCompileOptions = new DefaultJavaCompileOptions(compileOptions);
+ this.lintOptions = lintOptions;
+ this.buildFolder = buildFolder;
+ this.resourcePrefix = resourcePrefix;
+ this.isLibrary = isLibrary;
+ this.apiVersion = apiVersion;
+ this.generation = generation;
+ this.nativeToolchains = nativeToolchains;
+ this.buildToolsVersion = buildToolsVersion;
+ }
+
+ @NonNull
+ DefaultAndroidProject setDefaultConfig(@NonNull ProductFlavorContainer defaultConfigContainer) {
+ defaultConfig = defaultConfigContainer;
+ return this;
+ }
+
+ @NonNull
+ DefaultAndroidProject addBuildType(@NonNull BuildTypeContainer buildTypeContainer) {
+ buildTypes.add(buildTypeContainer);
+ return this;
+ }
+
+ @NonNull
+ DefaultAndroidProject addProductFlavors(
+ @NonNull ProductFlavorContainer productFlavorContainer) {
+ productFlavors.add(productFlavorContainer);
+ return this;
+ }
+
+ @NonNull
+ DefaultAndroidProject addVariant(@NonNull VariantImpl variant) {
+ variants.add(variant);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getModelVersion() {
+ return modelVersion;
+ }
+
+ @Override
+ public int getApiVersion() {
+ return apiVersion;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public ProductFlavorContainer getDefaultConfig() {
+ return defaultConfig;
+ }
+
+ @Override
+ @NonNull
+ public Collection<BuildTypeContainer> getBuildTypes() {
+ return buildTypes;
+ }
+
+ @Override
+ @NonNull
+ public Collection<ProductFlavorContainer> getProductFlavors() {
+ return productFlavors;
+ }
+
+ @Override
+ @NonNull
+ public Collection<Variant> getVariants() {
+ return variants;
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getFlavorDimensions() {
+ return flavorDimensions;
+ }
+
+ @NonNull
+ @Override
+ public Collection<ArtifactMetaData> getExtraArtifacts() {
+ return extraArtifacts;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return isLibrary;
+ }
+
+ @Override
+ @NonNull
+ public String getCompileTarget() {
+ return compileTarget;
+ }
+
+ @Override
+ @NonNull
+ public Collection<String> getBootClasspath() {
+ return bootClasspath;
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getFrameworkSources() {
+ return frameworkSource;
+ }
+
+ @Override
+ @NonNull
+ public Collection<SigningConfig> getSigningConfigs() {
+ return signingConfigs;
+ }
+
+ @Override
+ @NonNull
+ public AaptOptions getAaptOptions() {
+ return aaptOptions;
+ }
+
+ @Override
+ @NonNull
+ public LintOptions getLintOptions() {
+ return lintOptions;
+ }
+
+ @Override
+ @NonNull
+ public Collection<String> getUnresolvedDependencies() {
+ return unresolvedDependencies;
+ }
+
+ @NonNull
+ @Override
+ public Collection<SyncIssue> getSyncIssues() {
+ return syncIssues;
+ }
+
+ @Override
+ @NonNull
+ public JavaCompileOptions getJavaCompileOptions() {
+ return javaCompileOptions;
+ }
+
+ @Override
+ @NonNull
+ public File getBuildFolder() {
+ return buildFolder;
+ }
+
+ @Override
+ @Nullable
+ public String getResourcePrefix() {
+ return resourcePrefix;
+ }
+
+ @NonNull
+ @Override
+ public Collection<NativeToolchain> getNativeToolchains() {
+ return nativeToolchains;
+ }
+
+ @NonNull
+ @Override
+ public String getBuildToolsVersion() {
+ return buildToolsVersion;
+ }
+
+ @Override
+ public int getPluginGeneration() {
+ return generation;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultJavaCompileOptions.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultJavaCompileOptions.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultJavaCompileOptions.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DefaultJavaCompileOptions.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java
new file mode 100644
index 0000000..96f2d48
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FD_JARS;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dependency.LibraryDependencyImpl;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.dependency.JarDependency;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.android.ide.common.caching.CreatingCache;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ */
+public class DependenciesImpl implements Dependencies, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private static final CreatingCache<LibraryDependency, AndroidLibrary> sCache
+ = new CreatingCache<LibraryDependency, AndroidLibrary>(
+ new CreatingCache.ValueFactory<LibraryDependency, AndroidLibrary>() {
+ @Override
+ @NonNull
+ public AndroidLibrary create(@NonNull LibraryDependency key) {
+ return convertAndroidLibrary(key);
+ }
+ });
+
+ @NonNull
+ private final List<AndroidLibrary> libraries;
+ @NonNull
+ private final List<JavaLibrary> javaLibraries;
+ @NonNull
+ private final List<String> projects;
+
+ public static void clearCaches() {
+ sCache.clear();
+ }
+
+ @NonNull
+ static DependenciesImpl cloneDependenciesForJavaArtifacts(@NonNull Dependencies dependencies) {
+ List<AndroidLibrary> libraries = Collections.emptyList();
+ List<JavaLibrary> javaLibraries = Lists.newArrayList(dependencies.getJavaLibraries());
+ List<String> projects = Collections.emptyList();
+
+ return new DependenciesImpl(libraries, javaLibraries, projects);
+ }
+
+ @NonNull
+ static DependenciesImpl cloneDependencies(
+ @NonNull BaseVariantData variantData,
+ @NonNull AndroidBuilder androidBuilder) {
+ VariantDependencies variantDependencies = variantData.getVariantDependency();
+
+ List<AndroidLibrary> libraries;
+ List<JavaLibrary> javaLibraries;
+ List<String> projects;
+
+ List<LibraryDependencyImpl> libs = variantDependencies.getLibraries();
+ libraries = Lists.newArrayListWithCapacity(libs.size());
+ for (LibraryDependencyImpl libImpl : libs) {
+ AndroidLibrary clonedLib = sCache.get(libImpl);
+ if (clonedLib != null) {
+ libraries.add(clonedLib);
+ }
+ }
+
+ List<JarDependency> jarDeps = variantDependencies.getJarDependencies();
+ List<JarDependency> localDeps = variantDependencies.getLocalDependencies();
+
+ javaLibraries = Lists.newArrayListWithExpectedSize(jarDeps.size() + localDeps.size());
+ projects = Lists.newArrayList();
+
+ for (JarDependency jarDep : jarDeps) {
+ // don't include package-only dependencies
+ if (jarDep.isCompiled()) {
+ boolean customArtifact = jarDep.getResolvedCoordinates() != null &&
+ jarDep.getResolvedCoordinates().getClassifier() != null;
+
+ File jarFile = jarDep.getJarFile();
+ if (!customArtifact && jarDep.getProjectPath() != null) {
+ projects.add(jarDep.getProjectPath());
+ } else {
+ javaLibraries.add(
+ new JavaLibraryImpl(
+ jarFile,
+ !jarDep.isPackaged(),
+ null,
+ jarDep.getResolvedCoordinates()));
+ }
+ }
+ }
+
+ for (JarDependency jarDep : localDeps) {
+ // don't include package-only dependencies
+ if (jarDep.isCompiled()) {
+ javaLibraries.add(
+ new JavaLibraryImpl(
+ jarDep.getJarFile(),
+ !jarDep.isPackaged(),
+ null,
+ jarDep.getResolvedCoordinates()));
+ }
+ }
+
+ GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
+
+ if (variantConfig.getRenderscriptSupportModeEnabled()) {
+ File supportJar = androidBuilder.getRenderScriptSupportJar();
+ if (supportJar != null) {
+ javaLibraries.add(new JavaLibraryImpl(supportJar, false, null, null));
+ }
+ }
+
+ return new DependenciesImpl(libraries, javaLibraries, projects);
+ }
+
+ public DependenciesImpl(@NonNull Set<JavaLibrary> javaLibraries) {
+ this.javaLibraries = Lists.newArrayList(javaLibraries);
+ this.libraries = Collections.emptyList();
+ this.projects = Collections.emptyList();
+ }
+
+ private DependenciesImpl(@NonNull List<AndroidLibrary> libraries,
+ @NonNull List<JavaLibrary> javaLibraries,
+ @NonNull List<String> projects) {
+ this.libraries = libraries;
+ this.javaLibraries = javaLibraries;
+ this.projects = projects;
+ }
+
+ @NonNull
+ @Override
+ public Collection<AndroidLibrary> getLibraries() {
+ return libraries;
+ }
+
+ @NonNull
+ @Override
+ public Collection<JavaLibrary> getJavaLibraries() {
+ return javaLibraries;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getProjects() {
+ return projects;
+ }
+
+ @NonNull
+ private static AndroidLibrary convertAndroidLibrary(
+ @NonNull LibraryDependency libraryDependency) {
+ List<LibraryDependency> deps = libraryDependency.getDependencies();
+ List<AndroidLibrary> clonedDeps = Lists.newArrayListWithCapacity(deps.size());
+ for (LibraryDependency child : deps) {
+ AndroidLibrary clonedLib = sCache.get(child);
+ if (clonedLib != null) {
+ clonedDeps.add(clonedLib);
+ }
+ }
+
+ // compute local jar even if the bundle isn't exploded.
+ Collection<File> localJarOverride = findLocalJar(libraryDependency);
+
+ return new AndroidLibraryImpl(
+ libraryDependency,
+ clonedDeps,
+ localJarOverride,
+ libraryDependency.getProject(),
+ libraryDependency.getProjectVariant(),
+ libraryDependency.getRequestedCoordinates(),
+ libraryDependency.getResolvedCoordinates());
+ }
+
+ /**
+ * Finds the local jar for an aar.
+ *
+ * Since the model can be queried before the aar are exploded, we attempt to get them
+ * from inside the aar.
+ *
+ * @param library the library.
+ * @return its local jars.
+ */
+ @NonNull
+ private static Collection<File> findLocalJar(LibraryDependency library) {
+ // if the library is exploded, just use the normal method.
+ File explodedFolder = library.getFolder();
+ if (explodedFolder.isDirectory()) {
+ return library.getLocalJars();
+ }
+
+ // if the aar file is present, search inside it for jar files under libs/
+ File aarFile = library.getBundle();
+ if (aarFile.isFile()) {
+ List<File> jarList = Lists.newArrayList();
+
+ File jarsFolder = new File(explodedFolder, FD_JARS);
+
+ ZipFile zipFile = null;
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ zipFile = new ZipFile(aarFile);
+
+ for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
+ ZipEntry zipEntry = e.nextElement();
+ String name = zipEntry.getName();
+ if (name.startsWith("libs/") && name.endsWith(DOT_JAR)) {
+ jarList.add(new File(jarsFolder, name.replace('/', File.separatorChar)));
+ }
+ }
+
+ return jarList;
+ } catch (FileNotFoundException ignored) {
+ // should not happen since we check ahead of time
+ } catch (IOException e) {
+ // we'll return an empty list below
+ } finally {
+ if (zipFile != null) {
+ try {
+ zipFile.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ return Collections.emptyList();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/FilterDataImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/FilterDataImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/FilterDataImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/FilterDataImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/InstantRunImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/InstantRunImpl.java
new file mode 100644
index 0000000..1435d58
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/InstantRunImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.InstantRun;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Implementation of the {@link InstantRun} model
+ */
+public class InstantRunImpl implements InstantRun, Serializable {
+
+ @NonNull private final String incrementalBuildTaskName;
+ @NonNull private final File infoFile;
+ private final boolean isSupported;
+
+ public InstantRunImpl(
+ @NonNull String incrementalBuildTaskName,
+ @NonNull File infoFile, boolean isSupported) {
+ this.incrementalBuildTaskName = incrementalBuildTaskName;
+ this.infoFile = infoFile;
+ this.isSupported = isSupported;
+ }
+
+ @NonNull
+ @Override
+ public String getIncrementalAssembleTaskName() {
+ return incrementalBuildTaskName;
+ }
+
+ @NonNull
+ @Override
+ public File getInfoFile() {
+ return infoFile;
+ }
+
+ @Override
+ public boolean isSupportedByArtifact() {
+ return isSupported;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java
new file mode 100644
index 0000000..a4e5910
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaArtifact;
+import com.android.builder.model.SourceProvider;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Implementation of JavaArtifact that is serializable
+ */
+public class JavaArtifactImpl extends BaseArtifactImpl implements JavaArtifact, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final Set<String> ideSetupTaskNames;
+
+ @Nullable
+ private final File mockablePlatformJar;
+
+ public static JavaArtifactImpl clone(@NonNull JavaArtifact javaArtifact) {
+ SourceProvider variantSP = javaArtifact.getVariantSourceProvider();
+ SourceProvider flavorsSP = javaArtifact.getMultiFlavorSourceProvider();
+
+ return new JavaArtifactImpl(
+ javaArtifact.getName(),
+ javaArtifact.getAssembleTaskName(),
+ javaArtifact.getCompileTaskName(),
+ javaArtifact.getIdeSetupTaskNames(),
+ javaArtifact.getGeneratedSourceFolders(),
+ javaArtifact.getClassesFolder(),
+ javaArtifact.getJavaResourcesFolder(),
+ javaArtifact.getMockablePlatformJar(),
+ DependenciesImpl.cloneDependenciesForJavaArtifacts(javaArtifact.getDependencies()),
+ variantSP != null ? SourceProviderImpl.cloneProvider(variantSP) : null,
+ flavorsSP != null ? SourceProviderImpl.cloneProvider(flavorsSP) : null);
+ }
+
+ public JavaArtifactImpl(@NonNull String name,
+ @NonNull String assembleTaskName,
+ @NonNull String compileTaskName,
+ @NonNull Iterable<String> ideSetupTaskNames,
+ @NonNull Collection<File> generatedSourceFolders,
+ @NonNull File classesFolder,
+ @NonNull File javaResourcesFolder,
+ @Nullable File mockablePlatformJar,
+ @NonNull Dependencies dependencies,
+ @Nullable SourceProvider variantSourceProvider,
+ @Nullable SourceProvider multiFlavorSourceProviders) {
+ super(name, assembleTaskName, compileTaskName,
+ classesFolder, javaResourcesFolder, dependencies,
+ variantSourceProvider, multiFlavorSourceProviders, generatedSourceFolders);
+ this.ideSetupTaskNames = Sets.newHashSet(ideSetupTaskNames);
+ this.mockablePlatformJar = mockablePlatformJar;
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getIdeSetupTaskNames() {
+ return ideSetupTaskNames;
+ }
+
+ @Override
+ @Nullable
+ public File getMockablePlatformJar() {
+ return mockablePlatformJar;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java
new file mode 100644
index 0000000..097f20a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/JavaLibraryImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.JavaLibrary;
+import com.android.builder.model.MavenCoordinates;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class JavaLibraryImpl extends LibraryImpl implements JavaLibrary, Serializable {
+ private final File jarFile;
+ private final boolean provided;
+
+ public JavaLibraryImpl(
+ @NonNull File jarFile,
+ @NonNull boolean provided,
+ @Nullable MavenCoordinates requestedCoordinates,
+ @Nullable MavenCoordinates resolvedCoordinates) {
+ super(requestedCoordinates, resolvedCoordinates);
+ this.jarFile = jarFile;
+ this.provided = provided;
+ }
+
+ @NonNull
+ @Override
+ public File getJarFile() {
+ return jarFile;
+ }
+
+ @Override
+ public boolean isProvided() {
+ return provided;
+ }
+
+ @NonNull
+ @Override
+ public List<? extends JavaLibrary> getDependencies() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("JavaLibraryImpl{");
+ sb.append("jarFile=").append(jarFile);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/LibraryImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/LibraryImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/LibraryImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/LibraryImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/MavenCoordinatesImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/MavenCoordinatesImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/MavenCoordinatesImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/MavenCoordinatesImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.java
new file mode 100644
index 0000000..8ed6e6c
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.java
@@ -0,0 +1,610 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import static com.android.builder.model.AndroidProject.ARTIFACT_MAIN;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.OutputFile;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.api.ApkOutputFile;
+import com.android.build.gradle.internal.BuildTypeData;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.ProductFlavorData;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.VariantManager;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.incremental.InstantRunAnchorTask;
+import com.android.build.gradle.internal.incremental.InstantRunWrapperTask;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.build.gradle.internal.variant.TestedVariantData;
+import com.android.builder.Version;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.VariantType;
+import com.android.builder.model.AaptOptions;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidArtifactOutput;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ArtifactMetaData;
+import com.android.builder.model.JavaArtifact;
+import com.android.builder.model.LintOptions;
+import com.android.builder.model.NativeLibrary;
+import com.android.builder.model.NativeToolchain;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.builder.model.SyncIssue;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.Project;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Builder for the custom Android model.
+ */
+public class ModelBuilder implements ToolingModelBuilder {
+
+ @NonNull
+ private final AndroidBuilder androidBuilder;
+ @NonNull
+ private final AndroidConfig config;
+ @NonNull
+ private final ExtraModelInfo extraModelInfo;
+ @NonNull
+ private final VariantManager variantManager;
+ @NonNull
+ private final TaskManager taskManager;
+ @NonNull
+ private final NdkHandler ndkHandler;
+ @NonNull
+ private Map<Abi, NativeToolchain> toolchains;
+ @NonNull
+ private NativeLibraryFactory nativeLibFactory;
+ private final boolean isLibrary;
+ private final int generation;
+
+ public ModelBuilder(
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull VariantManager variantManager,
+ @NonNull TaskManager taskManager,
+ @NonNull AndroidConfig config,
+ @NonNull ExtraModelInfo extraModelInfo,
+ @NonNull NdkHandler ndkHandler,
+ @NonNull NativeLibraryFactory nativeLibraryFactory,
+ boolean isLibrary,
+ int generation) {
+ this.androidBuilder = androidBuilder;
+ this.config = config;
+ this.extraModelInfo = extraModelInfo;
+ this.variantManager = variantManager;
+ this.taskManager = taskManager;
+ this.ndkHandler = ndkHandler;
+ this.nativeLibFactory = nativeLibraryFactory;
+ this.isLibrary = isLibrary;
+ this.generation = generation;
+ }
+
+ public static void clearCaches() {
+ DependenciesImpl.clearCaches();
+ }
+
+ @Override
+ public boolean canBuild(String modelName) {
+ // The default name for a model is the name of the Java interface.
+ return modelName.equals(AndroidProject.class.getName());
+ }
+
+ @Override
+ public Object buildAll(String modelName, Project project) {
+ Collection<? extends SigningConfig> signingConfigs = config.getSigningConfigs();
+
+ // Get the boot classpath. This will ensure the target is configured.
+ List<String> bootClasspath = androidBuilder.getBootClasspathAsStrings(false);
+
+ List<File> frameworkSource = Collections.emptyList();
+
+ // List of extra artifacts, with all test variants added.
+ List<ArtifactMetaData> artifactMetaDataList = Lists.newArrayList(
+ extraModelInfo.getExtraArtifacts());
+
+ for (VariantType variantType : VariantType.getTestingTypes()) {
+ artifactMetaDataList.add(new ArtifactMetaDataImpl(
+ variantType.getArtifactName(),
+ true /*isTest*/,
+ variantType.getArtifactType()));
+ }
+
+ LintOptions lintOptions = com.android.build.gradle.internal.dsl.LintOptions.create(
+ config.getLintOptions());
+
+ AaptOptions aaptOptions = AaptOptionsImpl.create(config.getAaptOptions());
+
+ List<SyncIssue> syncIssues = Lists.newArrayList(extraModelInfo.getSyncIssues().values());
+
+ List<String> flavorDimensionList = (config.getFlavorDimensionList() != null ?
+ config.getFlavorDimensionList() : Lists.<String>newArrayList());
+
+ toolchains = createNativeToolchainModelMap(ndkHandler);
+
+ DefaultAndroidProject androidProject = new DefaultAndroidProject(
+ Version.ANDROID_GRADLE_PLUGIN_VERSION,
+ project.getName(),
+ flavorDimensionList,
+ androidBuilder.getTarget() != null ? androidBuilder.getTarget().hashString() : "",
+ bootClasspath,
+ frameworkSource,
+ cloneSigningConfigs(config.getSigningConfigs()),
+ aaptOptions,
+ artifactMetaDataList,
+ findUnresolvedDependencies(syncIssues),
+ syncIssues,
+ config.getCompileOptions(),
+ lintOptions,
+ project.getBuildDir(),
+ config.getResourcePrefix(),
+ ImmutableList.copyOf(toolchains.values()),
+ config.getBuildToolsVersion(),
+ isLibrary,
+ Version.BUILDER_MODEL_API_VERSION,
+ generation);
+
+ androidProject.setDefaultConfig(ProductFlavorContainerImpl.createProductFlavorContainer(
+ variantManager.getDefaultConfig(),
+ extraModelInfo.getExtraFlavorSourceProviders(
+ variantManager.getDefaultConfig().getProductFlavor().getName())));
+
+ for (BuildTypeData btData : variantManager.getBuildTypes().values()) {
+ androidProject.addBuildType(BuildTypeContainerImpl.create(
+ btData,
+ extraModelInfo.getExtraBuildTypeSourceProviders(btData.getBuildType().getName())));
+ }
+ for (ProductFlavorData pfData : variantManager.getProductFlavors().values()) {
+ androidProject.addProductFlavors(ProductFlavorContainerImpl.createProductFlavorContainer(
+ pfData,
+ extraModelInfo.getExtraFlavorSourceProviders(pfData.getProductFlavor().getName())));
+ }
+
+ for (BaseVariantData<? extends BaseVariantOutputData> variantData : variantManager.getVariantDataList()) {
+ if (!variantData.getType().isForTesting()) {
+ androidProject.addVariant(createVariant(variantData));
+ }
+ }
+
+ return androidProject;
+ }
+
+ /**
+ * Create a map of ABI to NativeToolchain
+ */
+ public static Map<Abi, NativeToolchain> createNativeToolchainModelMap(
+ @NonNull NdkHandler ndkHandler) {
+ if (!ndkHandler.isNdkDirConfigured()) {
+ return ImmutableMap.of();
+ }
+
+ Map<Abi, NativeToolchain> toolchains = Maps.newHashMap();
+
+ for (Abi abi : ndkHandler.getSupportedAbis()) {
+ toolchains.put(
+ abi,
+ new NativeToolchainImpl(
+ ndkHandler.getToolchain().getName() + "-" + abi.getName(),
+ ndkHandler.getCCompiler(abi),
+ ndkHandler.getCppCompiler(abi)));
+ }
+ return toolchains;
+ }
+
+ @NonNull
+ private VariantImpl createVariant(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ AndroidArtifact mainArtifact = createAndroidArtifact(ARTIFACT_MAIN, variantData);
+
+ GradleVariantConfiguration variantConfiguration = variantData.getVariantConfiguration();
+
+ String variantName = variantConfiguration.getFullName();
+
+ List<AndroidArtifact> extraAndroidArtifacts = Lists.newArrayList(
+ extraModelInfo.getExtraAndroidArtifacts(variantName));
+ // Make sure all extra artifacts are serializable.
+ Collection<JavaArtifact> extraJavaArtifacts = extraModelInfo.getExtraJavaArtifacts(
+ variantName);
+ List<JavaArtifact> clonedExtraJavaArtifacts = Lists.newArrayListWithCapacity(
+ extraJavaArtifacts.size());
+ for (JavaArtifact javaArtifact : extraJavaArtifacts) {
+ clonedExtraJavaArtifacts.add(JavaArtifactImpl.clone(javaArtifact));
+ }
+
+ if (variantData instanceof TestedVariantData) {
+ for (VariantType variantType : VariantType.getTestingTypes()) {
+ TestVariantData testVariantData = ((TestedVariantData) variantData).getTestVariantData(variantType);
+ if (testVariantData != null) {
+ VariantType type = testVariantData.getType();
+ if (type != null) {
+ switch (type) {
+ case ANDROID_TEST:
+ extraAndroidArtifacts.add(createAndroidArtifact(
+ variantType.getArtifactName(),
+ testVariantData));
+ break;
+ case UNIT_TEST:
+ clonedExtraJavaArtifacts.add(createUnitTestsJavaArtifact(
+ variantType,
+ testVariantData));
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported test variant type ${variantType}.");
+ }
+ }
+ }
+ }
+ }
+
+ // if the target is a codename, override the model value.
+ ApiVersion sdkVersionOverride = null;
+
+ // we know the getTargetInfo won't return null here.
+ @SuppressWarnings("ConstantConditions")
+ IAndroidTarget androidTarget = androidBuilder.getTargetInfo().getTarget();
+
+ AndroidVersion version = androidTarget.getVersion();
+ if (version.getCodename() != null) {
+ sdkVersionOverride = ApiVersionImpl.clone(version);
+ }
+
+ return new VariantImpl(
+ variantName,
+ variantConfiguration.getBaseName(),
+ variantConfiguration.getBuildType().getName(),
+ getProductFlavorNames(variantData),
+ ProductFlavorImpl.cloneFlavor(
+ variantConfiguration.getMergedFlavor(),
+ sdkVersionOverride,
+ sdkVersionOverride),
+ mainArtifact,
+ extraAndroidArtifacts,
+ clonedExtraJavaArtifacts);
+ }
+
+ private JavaArtifactImpl createUnitTestsJavaArtifact(
+ @NonNull VariantType variantType,
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ SourceProviders sourceProviders = determineSourceProviders(variantData);
+ DependenciesImpl dependencies = DependenciesImpl.cloneDependencies(variantData,
+ androidBuilder);
+
+ List<File> extraGeneratedSourceFolders = variantData.getExtraGeneratedSourceFolders();
+ return new JavaArtifactImpl(
+ variantType.getArtifactName(),
+ variantData.assembleVariantTask.getName(),
+ variantData.getScope().getCompileTask().getName(),
+ Sets.newHashSet(variantData.prepareDependenciesTask.getName(),
+ taskManager.createMockableJar.getName()),
+ extraGeneratedSourceFolders != null ? extraGeneratedSourceFolders : Collections.<File>emptyList(),
+ (variantData.javacTask != null) ?
+ variantData.javacTask.getDestinationDir() :
+ variantData.getScope().getJavaOutputDir(),
+ variantData.getJavaResourcesForUnitTesting(),
+ taskManager.getGlobalScope().getMockableAndroidJarFile(),
+ dependencies,
+ sourceProviders.variantSourceProvider,
+ sourceProviders.multiFlavorSourceProvider);
+ }
+
+ /**
+ * Create a NativeLibrary for each ABI.
+ */
+ private Collection<NativeLibrary> createNativeLibraries(
+ @NonNull Collection<Abi> abis,
+ @NonNull VariantScope scope) {
+ Collection<NativeLibrary> nativeLibraries = Lists.newArrayListWithCapacity(abis.size());
+ for (Abi abi : abis) {
+ NativeToolchain toolchain = toolchains.get(abi);
+ if (toolchain == null) {
+ continue;
+ }
+ Optional<NativeLibrary> lib = nativeLibFactory.create(scope, toolchain.getName(), abi);
+ if (lib.isPresent()) {
+ nativeLibraries.add(lib.get());
+ }
+ }
+ return nativeLibraries;
+ }
+
+ private AndroidArtifact createAndroidArtifact(
+ @NonNull String name,
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ VariantScope scope = variantData.getScope();
+ GradleVariantConfiguration variantConfiguration = variantData.getVariantConfiguration();
+
+ SigningConfig signingConfig = variantConfiguration.getSigningConfig();
+ String signingConfigName = null;
+ if (signingConfig != null) {
+ signingConfigName = signingConfig.getName();
+ }
+
+ SourceProviders sourceProviders = determineSourceProviders(variantData);
+
+ // get the outputs
+ List<? extends BaseVariantOutputData> variantOutputs = variantData.getOutputs();
+ List<AndroidArtifactOutput> outputs = Lists.newArrayListWithCapacity(variantOutputs.size());
+
+ CoreNdkOptions ndkConfig = variantData.getVariantConfiguration().getNdkConfig();
+ Collection<NativeLibrary> nativeLibraries = ImmutableList.of();
+ if (ndkHandler.getNdkDirectory() != null) {
+ if (config.getSplits().getAbi().isEnable()) {
+ nativeLibraries = createNativeLibraries(
+ config.getSplits().getAbi().isUniversalApk()
+ ? ndkHandler.getSupportedAbis()
+ : createAbiList(config.getSplits().getAbiFilters()),
+ scope);
+ } else {
+ if (ndkConfig.getAbiFilters() == null || ndkConfig.getAbiFilters().isEmpty()) {
+ nativeLibraries = createNativeLibraries(
+ ndkHandler.getSupportedAbis(),
+ scope);
+ } else {
+ nativeLibraries = createNativeLibraries(
+ createAbiList(ndkConfig.getAbiFilters()),
+ scope);
+ }
+ }
+ }
+
+ for (BaseVariantOutputData variantOutputData : variantOutputs) {
+ int intVersionCode;
+ if (variantOutputData instanceof ApkVariantOutputData) {
+ intVersionCode = variantOutputData.getVersionCode();
+ } else {
+ Integer versionCode = variantConfiguration.getMergedFlavor().getVersionCode();
+ intVersionCode = versionCode != null ? versionCode : 1;
+ }
+
+ ImmutableCollection.Builder<OutputFile> outputFiles = ImmutableList.builder();
+
+ // add the main APK
+ outputFiles.add(new OutputFileImpl(
+ variantOutputData.getMainOutputFile().getFilters(),
+ variantOutputData.getMainOutputFile().getType().name(),
+ variantOutputData.getOutputFile()));
+
+ for (ApkOutputFile splitApk : variantOutputData.getOutputs()) {
+ if (splitApk.getType() == OutputFile.OutputType.SPLIT) {
+ outputFiles.add(new OutputFileImpl(
+ splitApk.getFilters(), OutputFile.SPLIT, splitApk.getOutputFile()));
+ }
+ }
+
+ // add the main APK.
+ outputs.add(new AndroidArtifactOutputImpl(
+ outputFiles.build(),
+ "assemble" + variantOutputData.getFullName(),
+ variantOutputData.getScope().getManifestOutputFile(),
+ intVersionCode));
+ }
+
+ InstantRunImpl instantRun = new InstantRunImpl(
+ InstantRunAnchorTask.ConfigAction.getName(scope),
+ // todo : move this to a shared location.
+ InstantRunWrapperTask.ConfigAction.getBuildInfoFile(scope),
+ variantConfiguration.isInstantRunSupported());
+
+ return new AndroidArtifactImpl(
+ name,
+ outputs,
+ variantData.assembleVariantTask == null ? scope.getTaskName("assemble") : variantData.assembleVariantTask.getName(),
+ variantConfiguration.isSigningReady() || variantData.outputsAreSigned,
+ signingConfigName,
+ variantConfiguration.getApplicationId(),
+ // TODO: Need to determine the tasks' name when the tasks may not be created
+ // in component plugin.
+ scope.getSourceGenTask() == null ? scope.getTaskName("generate", "Sources") : scope.getSourceGenTask().getName(),
+ scope.getCompileTask() == null ? scope.getTaskName("compile", "Sources") : scope.getCompileTask().getName(),
+ getGeneratedSourceFolders(variantData),
+ getGeneratedResourceFolders(variantData),
+ (variantData.javacTask != null) ?
+ variantData.javacTask.getDestinationDir() :
+ scope.getJavaOutputDir(),
+ scope.getVariantData().getJavaResourcesForUnitTesting(),
+ DependenciesImpl.cloneDependencies(variantData, androidBuilder),
+ sourceProviders.variantSourceProvider,
+ sourceProviders.multiFlavorSourceProvider,
+ variantConfiguration.getSupportedAbis(),
+ nativeLibraries,
+ variantConfiguration.getMergedBuildConfigFields(),
+ variantConfiguration.getMergedResValues(),
+ instantRun);
+ }
+
+ private static Collection<Abi> createAbiList(Collection<String> abiNames) {
+ ImmutableList.Builder<Abi> builder = ImmutableList.builder();
+ for (String abiName : abiNames) {
+ Abi abi = Abi.getByName(abiName);
+ if (abi != null) {
+ builder.add(abi);
+ }
+ }
+ return builder.build();
+ }
+
+ private static SourceProviders determineSourceProviders(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ SourceProvider variantSourceProvider =
+ variantData.getVariantConfiguration().getVariantSourceProvider();
+ SourceProvider multiFlavorSourceProvider =
+ variantData.getVariantConfiguration().getMultiFlavorSourceProvider();
+
+ return new SourceProviders(
+ variantSourceProvider != null ?
+ SourceProviderImpl.cloneProvider(variantSourceProvider) :
+ null,
+ multiFlavorSourceProvider != null ?
+ SourceProviderImpl.cloneProvider(multiFlavorSourceProvider) :
+ null);
+ }
+
+ @NonNull
+ private static List<String> getProductFlavorNames(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ List<CoreProductFlavor> productFlavors = variantData.getVariantConfiguration()
+ .getProductFlavors();
+
+ List<String> flavorNames = Lists.newArrayListWithCapacity(productFlavors.size());
+
+ for (ProductFlavor flavor : productFlavors) {
+ flavorNames.add(flavor.getName());
+ }
+
+ return flavorNames;
+ }
+
+ @NonNull
+ private static List<File> getGeneratedSourceFolders(
+ @Nullable BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ if (variantData == null) {
+ return Collections.emptyList();
+ }
+
+ List<File> extraFolders = variantData.getExtraGeneratedSourceFolders();
+
+ List<File> folders;
+ if (extraFolders != null) {
+ folders = Lists.newArrayListWithExpectedSize(5 + extraFolders.size());
+ folders.addAll(extraFolders);
+ } else {
+ folders = Lists.newArrayListWithExpectedSize(5);
+ }
+
+ VariantScope scope = variantData.getScope();
+
+ // The R class is only generated by the first output.
+ folders.add(scope.getRClassSourceOutputDir());
+
+ folders.add(scope.getAidlSourceOutputDir());
+ folders.add(scope.getBuildConfigSourceOutputDir());
+ Boolean ndkMode = variantData.getVariantConfiguration().getMergedFlavor().getRenderscriptNdkModeEnabled();
+ if (ndkMode == null || !ndkMode) {
+ folders.add(scope.getRenderscriptSourceOutputDir());
+ }
+
+ return folders;
+ }
+
+ @NonNull
+ private static List<File> getGeneratedResourceFolders(
+ @Nullable BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ if (variantData == null) {
+ return Collections.emptyList();
+ }
+
+ List<File> result;
+
+ List<File> extraFolders = variantData.getExtraGeneratedResFolders();
+ if (extraFolders != null && !extraFolders.isEmpty()) {
+ result = Lists.newArrayListWithCapacity(extraFolders.size() + 2);
+
+ result.addAll(extraFolders);
+ } else {
+ result = Lists.newArrayListWithCapacity(2);
+ }
+
+ VariantScope scope = variantData.getScope();
+
+ result.add(scope.getRenderscriptResOutputDir());
+ result.add(scope.getGeneratedResOutputDir());
+
+ return result;
+ }
+
+ @NonNull
+ private static Collection<SigningConfig> cloneSigningConfigs(
+ @NonNull Collection<? extends SigningConfig> signingConfigs) {
+ Collection<SigningConfig> results = Lists.newArrayListWithCapacity(signingConfigs.size());
+
+ for (SigningConfig signingConfig : signingConfigs) {
+ results.add(SigningConfigImpl.createSigningConfig(signingConfig));
+ }
+
+ return results;
+ }
+
+ @Nullable
+ private static SourceProviderContainer getSourceProviderContainer(
+ @NonNull Collection<SourceProviderContainer> items,
+ @NonNull String name) {
+ for (SourceProviderContainer item : items) {
+ if (name.equals(item.getArtifactName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ private static class SourceProviders {
+ protected SourceProviderImpl variantSourceProvider;
+ protected SourceProviderImpl multiFlavorSourceProvider;
+
+ public SourceProviders(
+ SourceProviderImpl variantSourceProvider,
+ SourceProviderImpl multiFlavorSourceProvider) {
+ this.variantSourceProvider = variantSourceProvider;
+ this.multiFlavorSourceProvider = multiFlavorSourceProvider;
+ }
+ }
+
+ /**
+ * Return the unresolved dependencies in SyncIssues
+ */
+ private static Collection<String> findUnresolvedDependencies(
+ @NonNull Collection<SyncIssue> syncIssues) {
+ List<String> unresolvedDependencies = Lists.newArrayList();
+
+ for (SyncIssue issue : syncIssues) {
+ if (issue.getType() == SyncIssue.TYPE_UNRESOLVED_DEPENDENCY) {
+ unresolvedDependencies.add(issue.getData());
+ }
+ }
+ return unresolvedDependencies;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeAndroidProjectImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeAndroidProjectImpl.java
new file mode 100644
index 0000000..a447270
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeAndroidProjectImpl.java
@@ -0,0 +1,118 @@
+/*
+ * 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.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.NativeAndroidProject;
+import com.android.builder.model.NativeArtifact;
+import com.android.builder.model.NativeSettings;
+import com.android.builder.model.NativeToolchain;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Implementation of {@link NativeAndroidProject}.
+ */
+public class NativeAndroidProjectImpl implements NativeAndroidProject, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private int apiVersion;
+ @NonNull
+ private String modelVersion;
+ @NonNull
+ private String name;
+ @NonNull
+ private Collection<File> buildFiles;
+ @NonNull
+ private Collection<NativeArtifact> artifacts;
+ @NonNull
+ private Collection<NativeToolchain> toolChains;
+ @NonNull
+ private Collection<NativeSettings> settings;
+ @NonNull
+ private Map<String, String> fileExtensions;
+
+ public NativeAndroidProjectImpl(
+ @NonNull String modelVersion,
+ @NonNull String name,
+ @NonNull Collection<File> buildFiles,
+ @NonNull Collection<NativeArtifact> artifacts,
+ @NonNull Collection<NativeToolchain> toolChains,
+ @NonNull Collection<NativeSettings> settings,
+ @NonNull Map<String, String> fileExtensions,
+ int apiVersion) {
+ this.modelVersion = modelVersion;
+ this.name = name;
+ this.buildFiles = buildFiles;
+ this.artifacts = artifacts;
+ this.toolChains = toolChains;
+ this.settings = settings;
+ this.fileExtensions = fileExtensions;
+ this.apiVersion = apiVersion;
+ }
+
+ @Override
+ public int getApiVersion() {
+ return apiVersion;
+ }
+
+ @Override
+ @NonNull
+ public String getModelVersion() {
+ return modelVersion;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getBuildFiles() {
+ return buildFiles;
+ }
+
+ @Override
+ @NonNull
+ public Collection<NativeArtifact> getArtifacts() {
+ return artifacts;
+ }
+
+ @Override
+ @NonNull
+ public Collection<NativeToolchain> getToolChains() {
+ return toolChains;
+ }
+
+ @Override
+ @NonNull
+ public Collection<NativeSettings> getSettings() {
+ return settings;
+ }
+
+ @Override
+ @NonNull
+ public Map<String, String> getFileExtensions() {
+ return fileExtensions;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeArtifactImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeArtifactImpl.java
new file mode 100644
index 0000000..49145a4
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeArtifactImpl.java
@@ -0,0 +1,107 @@
+/*
+ * 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.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.NativeArtifact;
+import com.android.builder.model.NativeFile;
+import com.android.builder.model.NativeFolder;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * Implementation of {@link NativeArtifact}.
+ */
+public class NativeArtifactImpl implements NativeArtifact, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String name;
+ @NonNull
+ private final String toolChain;
+ @NonNull
+ private final String groupName;
+ @NonNull
+ private final Collection<NativeFolder> sourceFolders;
+ @NonNull
+ private final Collection<NativeFile> sourceFiles;
+ @NonNull
+ private final Collection<File> exportedHeaders;
+ @NonNull
+ private final File getOutputFile;
+
+ public NativeArtifactImpl(
+ @NonNull String name,
+ @NonNull String toolChain,
+ @NonNull String groupName,
+ @NonNull Collection<NativeFolder> sourceFolders,
+ @NonNull Collection<NativeFile> sourceFiles,
+ @NonNull Collection<File> exportedHeaders,
+ @NonNull File getOutputFile) {
+ this.name = name;
+ this.toolChain = toolChain;
+ this.groupName = groupName;
+ this.sourceFolders = sourceFolders;
+ this.sourceFiles = sourceFiles;
+ this.exportedHeaders = exportedHeaders;
+ this.getOutputFile = getOutputFile;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public String getToolChain() {
+ return toolChain;
+ }
+
+ @Override
+ @NonNull
+ public String getGroupName() {
+ return groupName;
+ }
+
+ @Override
+ @NonNull
+ public Collection<NativeFolder> getSourceFolders() {
+ return sourceFolders;
+ }
+
+ @Override
+ @NonNull
+ public Collection<NativeFile> getSourceFiles() {
+ return sourceFiles;
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getExportedHeaders() {
+ return exportedHeaders;
+ }
+
+ @Override
+ @NonNull
+ public File getOutputFile() {
+ return getOutputFile;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeFileImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeFileImpl.java
new file mode 100644
index 0000000..d56d98f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeFileImpl.java
@@ -0,0 +1,65 @@
+/*
+ * 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.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.NativeFile;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Implementation of {@link NativeFile}.
+ */
+public class NativeFileImpl implements NativeFile, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final File filePath;
+ @NonNull
+ private final String settingsName;
+ @Nullable
+ private final File workingDirectory;
+
+ public NativeFileImpl(
+ @NonNull File filePath,
+ @NonNull String settingsName,
+ @Nullable File workingDirectory) {
+ this.filePath = filePath;
+ this.settingsName = settingsName;
+ this.workingDirectory = workingDirectory;
+ }
+
+ @Override
+ @NonNull
+ public File getFilePath() {
+ return filePath;
+ }
+
+ @Override
+ @NonNull
+ public String getSettingsName() {
+ return settingsName;
+ }
+
+ @Override
+ @Nullable
+ public File getWorkingDirectory() {
+ return workingDirectory;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeFolderImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeFolderImpl.java
new file mode 100644
index 0000000..9ebe2a6
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeFolderImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.NativeFolder;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Implementation of {@link NativeFolder}
+ */
+public class NativeFolderImpl implements NativeFolder, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final File folderPath;
+ @NonNull
+ private final Map<String, String> perLanguageSettings;
+ @Nullable
+ private final File workingDirectory;
+
+
+ public NativeFolderImpl(
+ @NonNull File folderPath,
+ @NonNull Map<String, String> perLanguageSettings,
+ @Nullable File workingDirectory) {
+ this.folderPath = folderPath;
+ this.perLanguageSettings = perLanguageSettings;
+ this.workingDirectory = workingDirectory;
+ }
+
+ @Override
+ @NonNull
+ public File getFolderPath() {
+ return folderPath;
+ }
+
+ @Override
+ @NonNull
+ public Map<String, String> getPerLanguageSettings() {
+ return perLanguageSettings;
+ }
+
+ @Override
+ @Nullable
+ public File getWorkingDirectory() {
+ return workingDirectory;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryFactory.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryFactory.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryFactory.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeLibraryImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeSettingsImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeSettingsImpl.java
new file mode 100644
index 0000000..5fd6f70
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeSettingsImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.NativeSettings;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Implementation of {@link NativeSettings}.
+ */
+public class NativeSettingsImpl implements NativeSettings, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ String name;
+ @NonNull
+ List<String> compilerFlags;
+
+ public NativeSettingsImpl(@NonNull String name, @NonNull List<String> compilerFlags) {
+ this.name = name;
+ this.compilerFlags = compilerFlags;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public List<String> getCompilerFlags() {
+ return compilerFlags;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeToolchainImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeToolchainImpl.java
new file mode 100644
index 0000000..5bcaf62
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/NativeToolchainImpl.java
@@ -0,0 +1,73 @@
+/*
+ * 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.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.NativeToolchain;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Implementation of NativeToolchain that is serializable.
+ */
+public class NativeToolchainImpl implements NativeToolchain, Serializable {
+
+ @NonNull
+ String name;
+ @Nullable
+ File cCompilerExecutable;
+ @Nullable
+ File cppCompilerExecutable;
+
+ public NativeToolchainImpl(
+ @NonNull String name,
+ @Nullable File cCompilerExecutable,
+ @Nullable File cppCompilerExecutable) {
+ this.name = name;
+ this.cCompilerExecutable = cCompilerExecutable;
+ this.cppCompilerExecutable = cppCompilerExecutable;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Nullable
+ @Override
+ public File getCCompilerExecutable() {
+ return cCompilerExecutable;
+ }
+
+ @Nullable
+ @Override
+ public File getCppCompilerExecutable() {
+ return cppCompilerExecutable;
+ }
+
+ @Override
+ public String toString() {
+ return "ToolchainImpl{" +
+ "name='" + name + '\'' +
+ ", cCompilerExecutable=" + cCompilerExecutable +
+ ", cppCompilerExecutable=" + cppCompilerExecutable +
+ '}';
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/OutputFileImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/OutputFileImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/OutputFileImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/OutputFileImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorContainerImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorContainerImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorContainerImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorContainerImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java
new file mode 100644
index 0000000..1c5332d
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.core.DefaultVectorDrawablesOptions;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.VectorDrawablesOptions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of ProductFlavor that is serializable. Objects used in the DSL cannot be
+ * serialized.
+ **/
+class ProductFlavorImpl extends BaseConfigImpl implements ProductFlavor, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name = null;
+ private String mDimension = null;
+ private ApiVersion mMinSdkVersion = null;
+ private ApiVersion mTargetSdkVersion = null;
+ private Integer mMaxSdkVersion = null;
+ private Integer mRenderscriptTargetApi = null;
+ private Boolean mRenderscriptSupportMode = null;
+ private Boolean mRenderscriptNdkMode = null;
+ private Integer mVersionCode = null;
+ private String mVersionName = null;
+ private String mApplicationId = null;
+ private String mTestApplicationId = null;
+ private String mTestInstrumentationRunner = null;
+ private Map<String, String> mTestInstrumentationRunnerArguments = Maps.newHashMap();
+ private Boolean mTestHandleProfiling = null;
+ private Boolean mTestFunctionalTest = null;
+ private Set<String> mResourceConfigurations = null;
+ private DefaultVectorDrawablesOptions mVectorDrawablesOptions = null;
+
+ @NonNull
+ static ProductFlavorImpl cloneFlavor(
+ @NonNull ProductFlavor productFlavor,
+ @Nullable ApiVersion minSdkVersionOverride,
+ @Nullable ApiVersion targetSdkVersionOverride) {
+ ProductFlavorImpl clonedFlavor = new ProductFlavorImpl(productFlavor);
+ clonedFlavor.name = productFlavor.getName();
+ clonedFlavor.mDimension = productFlavor.getDimension();
+ clonedFlavor.mMinSdkVersion = minSdkVersionOverride != null
+ ? minSdkVersionOverride
+ : ApiVersionImpl.clone(productFlavor.getMinSdkVersion());
+ clonedFlavor.mTargetSdkVersion = targetSdkVersionOverride != null
+ ? targetSdkVersionOverride
+ : ApiVersionImpl.clone(productFlavor.getTargetSdkVersion());
+ clonedFlavor.mMaxSdkVersion = targetSdkVersionOverride != null
+ ? null /* we remove the maxSdkVersion when dealing with a preview release */
+ : productFlavor.getMaxSdkVersion();
+ clonedFlavor.mRenderscriptTargetApi = productFlavor.getRenderscriptTargetApi();
+ clonedFlavor.mRenderscriptSupportMode = productFlavor.getRenderscriptSupportModeEnabled();
+ clonedFlavor.mRenderscriptNdkMode = productFlavor.getRenderscriptNdkModeEnabled();
+
+ clonedFlavor.mVersionCode = productFlavor.getVersionCode();
+ clonedFlavor.mVersionName = productFlavor.getVersionName();
+
+ clonedFlavor.mApplicationId = productFlavor.getApplicationId();
+
+ clonedFlavor.mTestApplicationId = productFlavor.getTestApplicationId();
+ clonedFlavor.mTestInstrumentationRunner = productFlavor.getTestInstrumentationRunner();
+ clonedFlavor.mTestHandleProfiling = productFlavor.getTestHandleProfiling();
+ clonedFlavor.mTestFunctionalTest = productFlavor.getTestFunctionalTest();
+
+ clonedFlavor.mResourceConfigurations = ImmutableSet.copyOf(
+ productFlavor.getResourceConfigurations());
+
+ clonedFlavor.mTestInstrumentationRunnerArguments = Maps.newHashMap(
+ productFlavor.getTestInstrumentationRunnerArguments());
+
+ clonedFlavor.mVectorDrawablesOptions =
+ DefaultVectorDrawablesOptions.copyOf(productFlavor.getVectorDrawables());
+
+ return clonedFlavor;
+ }
+
+ private ProductFlavorImpl(@NonNull ProductFlavor productFlavor) {
+ super(productFlavor);
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @Nullable
+ public String getApplicationId() {
+ return mApplicationId;
+ }
+
+ @Override
+ @Nullable
+ public Integer getVersionCode() {
+ return mVersionCode;
+ }
+
+ @Override
+ @Nullable
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ @Override
+ @Nullable
+ public ApiVersion getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ @Override
+ @Nullable
+ public ApiVersion getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @Override
+ @Nullable
+ public Integer getMaxSdkVersion() { return mMaxSdkVersion; }
+
+ @Override
+ @Nullable
+ public Integer getRenderscriptTargetApi() {
+ return mRenderscriptTargetApi;
+ }
+
+ @Override
+ @Nullable
+ public Boolean getRenderscriptSupportModeEnabled() {
+ return mRenderscriptSupportMode;
+ }
+
+ @Override
+ @Nullable
+ public Boolean getRenderscriptNdkModeEnabled() {
+ return mRenderscriptNdkMode;
+ }
+
+ @Nullable
+ @Override
+ public String getTestApplicationId() {
+ return mTestApplicationId;
+ }
+
+ @Nullable
+ @Override
+ public String getTestInstrumentationRunner() {
+ return mTestInstrumentationRunner;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, String> getTestInstrumentationRunnerArguments() {
+ return mTestInstrumentationRunnerArguments;
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestHandleProfiling() {
+ return mTestHandleProfiling;
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestFunctionalTest() {
+ return mTestFunctionalTest;
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getResourceConfigurations() {
+ return mResourceConfigurations;
+ }
+
+ @Nullable
+ @Override
+ public SigningConfig getSigningConfig() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public VectorDrawablesOptions getVectorDrawables() {
+ return mVectorDrawablesOptions;
+ }
+
+ @Nullable
+ @Override
+ public String getDimension() {
+ return mDimension;
+ }
+
+ @Override
+ public String toString() {
+ return "ProductFlavorImpl{" +
+ "name='" + name + '\'' +
+ ", mDimension='" + mDimension + '\'' +
+ ", mMinSdkVersion=" + mMinSdkVersion +
+ ", mTargetSdkVersion=" + mTargetSdkVersion +
+ ", mMaxSdkVersion=" + mMaxSdkVersion +
+ ", mRenderscriptTargetApi=" + mRenderscriptTargetApi +
+ ", mRenderscriptSupportMode=" + mRenderscriptSupportMode +
+ ", mRenderscriptNdkMode=" + mRenderscriptNdkMode +
+ ", mVersionCode=" + mVersionCode +
+ ", mVersionName='" + mVersionName + '\'' +
+ ", mApplicationId='" + mApplicationId + '\'' +
+ ", mTestApplicationId='" + mTestApplicationId + '\'' +
+ ", mTestInstrumentationRunner='" + mTestInstrumentationRunner + '\'' +
+ ", mTestHandleProfiling=" + mTestHandleProfiling +
+ ", mTestFunctionalTest=" + mTestFunctionalTest +
+ ", mResourceConfigurations=" + mResourceConfigurations +
+ ", mVectorDrawablesOptions=" + mVectorDrawablesOptions +
+ "} " + super.toString();
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SigningConfigImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SigningConfigImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SigningConfigImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SigningConfigImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderContainerImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderContainerImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderContainerImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderContainerImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueImpl.java
new file mode 100644
index 0000000..c7c4fd9
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueImpl.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SyncIssue;
+
+import java.io.Serializable;
+
+/**
+ * An implementation of BaseConfig specifically for sending as part of the Android model
+ * through the Gradle tooling API.
+ */
+public class SyncIssueImpl implements SyncIssue, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final int type;
+ private final int severity;
+ @Nullable
+ private final String data;
+ private final String message;
+
+ public SyncIssueImpl(int type, int severity, @Nullable String data, @NonNull String message) {
+ this.type = type;
+ this.severity = severity;
+ this.data = data;
+ this.message = message;
+ }
+
+ @Override
+ public int getSeverity() {
+ return severity;
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ @Nullable
+ @Override
+ public String getData() {
+ return data;
+ }
+
+ @NonNull
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return "SyncIssueImpl{" +
+ "type=" + type +
+ ", severity=" + severity +
+ ", data='" + data + '\'' +
+ ", message='" + message + '\'' +
+ '}';
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueKey.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueKey.java
new file mode 100644
index 0000000..fec9bf5
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/SyncIssueKey.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SyncIssue;
+import com.google.common.base.Objects;
+
+/**
+ * Creates a key from a SyncIssue to use in a map.
+ */
+public class SyncIssueKey {
+
+ private final int type;
+
+ @Nullable
+ private final String data;
+
+ public static SyncIssueKey from(@NonNull SyncIssue syncIssue) {
+ return new SyncIssueKey(syncIssue.getType(), syncIssue.getData());
+ }
+
+ private SyncIssueKey(int type, @Nullable String data) {
+ this.type = type;
+ this.data = data;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ SyncIssueKey that = (SyncIssueKey) o;
+
+ return type == that.type && Objects.equal(this.data, that.data);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(type, data);
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/VariantImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/VariantImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/VariantImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/model/VariantImpl.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ExtendedContentType.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ExtendedContentType.java
new file mode 100644
index 0000000..99a2d97
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ExtendedContentType.java
@@ -0,0 +1,78 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.google.common.collect.ImmutableSet;
+
+
+import java.util.Set;
+
+/**
+ * Content types private to the Android Plugin.
+ */
+public enum ExtendedContentType implements ContentType {
+
+ /**
+ * The content is dex files.
+ */
+ DEX(0x1000),
+
+ /**
+ * Content is a native library.
+ */
+ NATIVE_LIBS(0x2000),
+
+ /**
+ * Classes that have been instrumented to be patch already loaded classes.
+ */
+ CLASSES_ENHANCED(0x4000);
+
+ private final int value;
+
+ ExtendedContentType(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public int getValue() {
+ return value;
+ }
+
+
+ /**
+ * Returns all {@link DefaultContentType} and {@link ExtendedContentType} content types.
+ * @return a set of all known {@link ContentType}
+ */
+ public static Set<ContentType> getAllContentTypes() {
+ return allContentTypes;
+ }
+
+ private static final Set<ContentType> allContentTypes;
+
+ static {
+ ImmutableSet.Builder<ContentType> builder = ImmutableSet.builder();
+ for (DefaultContentType contentType : DefaultContentType.values()) {
+ builder.add(contentType);
+ }
+ for (ExtendedContentType extendedContentType : ExtendedContentType.values()) {
+ builder.add(extendedContentType);
+ }
+ allContentTypes = builder.build();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/FilterableStreamCollection.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/FilterableStreamCollection.java
new file mode 100644
index 0000000..5c11950
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/FilterableStreamCollection.java
@@ -0,0 +1,82 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.TransformInput;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A collection of {@link TransformStream} that can be queried.
+ */
+public abstract class FilterableStreamCollection {
+
+ public interface StreamFilter {
+ boolean accept(
+ @NonNull Set<QualifiedContent.ContentType> types,
+ @NonNull Set<QualifiedContent.Scope> scopes);
+ }
+
+ @NonNull
+ abstract Collection<TransformStream> getStreams();
+
+ @NonNull
+ public ImmutableList<TransformStream> getStreams(@NonNull StreamFilter streamFilter) {
+ ImmutableList.Builder<TransformStream> streamsByType = ImmutableList.builder();
+ for (TransformStream s : getStreams()) {
+ if (streamFilter.accept(s.getContentTypes(), s.getScopes())) {
+ streamsByType.add(s);
+ }
+ }
+
+ return streamsByType.build();
+ }
+
+ @NonNull
+ public Map<File, Format> getPipelineOutput(
+ @NonNull StreamFilter streamFilter) {
+ ImmutableList<TransformStream> streams = getStreams(streamFilter);
+ if (streams.isEmpty()) {
+ return ImmutableMap.of();
+ }
+
+ ImmutableMap.Builder<File, Format> builder = ImmutableMap.builder();
+ for (TransformStream stream : streams) {
+ // get the input for it
+ TransformInput input = stream.asNonIncrementalInput();
+
+ for (JarInput jarInput : input.getJarInputs()) {
+ builder.put(jarInput.getFile(), Format.JAR);
+ }
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ builder.put(directoryInput.getFile(), Format.DIRECTORY);
+ }
+ }
+
+ return builder.build();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableDirectoryInput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableDirectoryInput.java
new file mode 100644
index 0000000..22d062a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableDirectoryInput.java
@@ -0,0 +1,75 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Status;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Immutable version of {@link DirectoryInput}.
+ */
+ at Immutable
+class ImmutableDirectoryInput extends QualifiedContentImpl implements DirectoryInput {
+
+ @NonNull
+ private final Map<File, Status> changedFiles;
+
+ ImmutableDirectoryInput(
+ @NonNull String name,
+ @NonNull File file,
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes) {
+ super(name, file, contentTypes, scopes);
+ this.changedFiles = ImmutableMap.of();
+ }
+
+ protected ImmutableDirectoryInput(
+ @NonNull String name,
+ @NonNull File file,
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes,
+ @NonNull Map<File, Status> changedFiles) {
+ super(name, file, contentTypes, scopes);
+ this.changedFiles = ImmutableMap.copyOf(changedFiles);
+ }
+
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return changedFiles;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", getName())
+ .add("file", getFile())
+ .add("contentTypes", Joiner.on(',').join(getContentTypes()))
+ .add("scopes", Joiner.on(',').join(getScopes()))
+ .add("changedFiles", changedFiles)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableJarInput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableJarInput.java
new file mode 100644
index 0000000..39130c2
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableJarInput.java
@@ -0,0 +1,72 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Immutable version of {@link JarInput}.
+ */
+ at Immutable
+class ImmutableJarInput extends QualifiedContentImpl implements JarInput {
+
+ @NonNull
+ private final Status status;
+
+ ImmutableJarInput(
+ @NonNull String name,
+ @NonNull File file,
+ @NonNull Status status,
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes) {
+ super(name, file, contentTypes, scopes);
+ this.status = status;
+ }
+
+ ImmutableJarInput(
+ @NonNull QualifiedContent qualifiedContent,
+ @NonNull Status status) {
+ super(qualifiedContent);
+ this.status = status;
+ }
+
+ @NonNull
+ @Override
+ public Status getStatus() {
+ return status;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", getName())
+ .add("file", getFile())
+ .add("contentTypes", Joiner.on(',').join(getContentTypes()))
+ .add("scopes", Joiner.on(',').join(getScopes()))
+ .add("status", status)
+ .toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableTransformInput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableTransformInput.java
new file mode 100644
index 0000000..fced479
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/ImmutableTransformInput.java
@@ -0,0 +1,70 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.TransformInput;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Immutable version of {@link TransformInput}.
+ */
+class ImmutableTransformInput implements TransformInput {
+
+ private File optionalRootLocation;
+ @NonNull
+ private final Collection<JarInput> jarInputs;
+ @NonNull
+ private final Collection<DirectoryInput> directoryInputs;
+
+ ImmutableTransformInput(
+ @NonNull Collection<JarInput> jarInputs,
+ @NonNull Collection<DirectoryInput> directoryInputs,
+ @Nullable File optionalRootLocation) {
+ this.jarInputs = ImmutableList.copyOf(jarInputs);
+ this.directoryInputs = ImmutableList.copyOf(directoryInputs);
+ this.optionalRootLocation = optionalRootLocation;
+ }
+
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return jarInputs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return directoryInputs;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("rootLocation", optionalRootLocation)
+ .add("jarInputs", jarInputs)
+ .add("folderInputs", directoryInputs)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IncrementalTransformInput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IncrementalTransformInput.java
new file mode 100644
index 0000000..9822cc2
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IncrementalTransformInput.java
@@ -0,0 +1,171 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Version of Transform Input to incrementally build the {@link JarInput} and {@link DirectoryInput}
+ * that contains incremental file change information.
+ */
+abstract class IncrementalTransformInput {
+
+ /**
+ * List of JarInputs for which we haven't found a match with a changed file.
+ */
+ @NonNull
+ private final Map<File, QualifiedContent> jarInputs = Maps.newHashMap();
+
+ /**
+ * List of folder inputs used to process changed files.
+ */
+ @NonNull
+ private final List<MutableDirectoryInput> folderInputs = Lists.newArrayList();
+
+ /**
+ * List of JarInputs that are already matched to a changed (jar) file.
+ */
+ private final List<JarInput> convertedJarInputs = Lists.newArrayList();
+
+ protected IncrementalTransformInput() {
+ }
+
+ /**
+ * Process a changed file against the known list of jar files
+ *
+ * If the file matches a jar, creates an internal ImmutableJarInput and return true.
+ * @param file the changed (jar?) file
+ * @param status the file status
+ * @return true if the file is a match, false otherwise.
+ */
+ boolean checkForJar(@NonNull File file, @NonNull Status status) {
+ if (jarInputs.containsKey(file)) {
+ QualifiedContent jarContent = jarInputs.get(file);
+ addImmutableJar(new ImmutableJarInput(jarContent, status));
+ jarInputs.remove(file);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Process a changed file against the known list of folders
+ *
+ * If the file is contains within a known folder, records it and return true
+ * @param file the changed file
+ * @param fileSegments the changed file path segments for faster checks
+ * @param status the file status
+ * @return true if the file is a match, false otherwise.
+ */
+ boolean checkForFolder(
+ @NonNull File file,
+ @NonNull List<String> fileSegments,
+ @NonNull Status status) {
+ for (MutableDirectoryInput folderInput : folderInputs) {
+ if (folderInput.processForChangedFile(file, fileSegments, status)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Process a removed file to see if it belonged to a folder of this input.
+ *
+ * @param file the removed file
+ * @param fileSegments the removed file path segments for faster checks.
+ * @return true if the file was part of this input.
+ */
+ boolean checkRemovedFolderFile(@NonNull File file, @NonNull List<String> fileSegments) {
+ // first check for removed files in existing folders.
+ for (MutableDirectoryInput folderInput : folderInputs) {
+ if (folderInput.processForChangedFile(file, fileSegments, Status.REMOVED)) {
+ return true;
+ }
+ }
+
+ // if we don't find anything, see if we figure out scopes/types of the removed file (and
+ // its root folder.
+ return checkRemovedFolder(file, fileSegments);
+ }
+
+ /**
+ * Process a removed file to see if it was a jar belonging to this input.
+ *
+ * @param file the removed file
+ * @param fileSegments the removed file path segments for faster checks.
+ * @return true if the file was part of this input.
+ */
+ abstract boolean checkRemovedJarFile(@NonNull File file, @NonNull List<String> fileSegments);
+
+ /**
+ * Process a removed file to see if it belonged to a removed folder of this input.
+ *
+ * @param file the removed file
+ * @param fileSegments the removed file path segments for faster checks.
+ * @return true if the file was part of this input.
+ */
+ protected abstract boolean checkRemovedFolder(
+ @NonNull File file,
+ @NonNull List<String> fileSegments);
+
+
+ void addJarInput(@NonNull QualifiedContent jarInput) {
+ jarInputs.put(jarInput.getFile(), jarInput);
+ }
+
+ protected void addImmutableJar(@NonNull ImmutableJarInput jarInput) {
+ convertedJarInputs.add(jarInput);
+ }
+
+ void addFolderInput(@NonNull MutableDirectoryInput folderInput) {
+ folderInputs.add(folderInput);
+ }
+
+ @NonNull
+ ImmutableTransformInput asImmutable() {
+ // create a list with all the touched jars + the untouched ones.
+ List<JarInput> immutableJarInputs = Lists.newArrayListWithCapacity(
+ jarInputs.size() + convertedJarInputs.size());
+ immutableJarInputs.addAll(convertedJarInputs);
+ // add untouched jars.
+ for (QualifiedContent jarContent : jarInputs.values()) {
+ immutableJarInputs.add(new ImmutableJarInput(jarContent, Status.NOTCHANGED));
+ }
+
+ // now create the list for the folder inputs.
+ List<DirectoryInput> immutableDirectoryInputs = Lists.newArrayListWithCapacity(
+ folderInputs.size());
+ for (MutableDirectoryInput folderInput : folderInputs) {
+ immutableDirectoryInputs.add(folderInput.asImmutable());
+ }
+
+ return new ImmutableTransformInput(immutableJarInputs, immutableDirectoryInputs, null);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IntermediateFolderUtils.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IntermediateFolderUtils.java
new file mode 100644
index 0000000..cfebfde
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IntermediateFolderUtils.java
@@ -0,0 +1,518 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import static com.android.SdkConstants.DOT_JAR;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformInput;
+import com.android.utils.FileUtils;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper to handle the folder structure in the output of transforms.
+ */
+public class IntermediateFolderUtils {
+
+ public static final String FOLDERS = "folders";
+ public static final String JARS = "jars";
+
+ /**
+ * Returns the location of content for a given set of Scopes, Content Types, and format.
+ *
+ * If the format is {@link Format#DIRECTORY} then the result is the file location of the folder.
+ * If the format is {@link Format#JAR} then the result is a file representing the jar to create.
+ *
+ * @param rootLocation the root location from which to create the content.
+ * @param name a unique name for the content. For a given set of scopes/types/format it must
+ * be unique.
+ * @param types the content types associated with this content.
+ * @param scopes the scopes associated with this content.
+ * @param format the format of the content.
+ * @return the location of the content.
+ */
+ @NonNull
+ public static File getContentLocation(
+ @NonNull File rootLocation,
+ @NonNull String name,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes,
+ @NonNull Format format) {
+ // runtime check these since it's (indirectly) called by 3rd party transforms.
+ checkNotNull(name);
+ checkNotNull(types);
+ checkNotNull(scopes);
+ checkNotNull(format);
+ checkState(!name.isEmpty());
+ checkState(!types.isEmpty());
+ checkState(!scopes.isEmpty());
+
+ switch (format) {
+ case DIRECTORY: {
+ File location = FileUtils.join(rootLocation,
+ FOLDERS,
+ typesToString(types),
+ scopesToString(scopes),
+ name);
+ return location;
+ }
+ case JAR: {
+ File location = FileUtils.join(rootLocation,
+ JARS,
+ typesToString(types),
+ scopesToString(scopes),
+ name + DOT_JAR);
+ return location;
+ }
+ default:
+ throw new RuntimeException("Unexpected Format: " + format);
+ }
+ }
+
+ @NonNull
+ public static TransformInput computeNonIncrementalInputFromFolder(
+ @NonNull File folder,
+ @NonNull Set<ContentType> requiredTypes,
+ @NonNull Set<Scope> requiredScopes) {
+ final List<JarInput> jarInputs = Lists.newArrayList();
+ final List<DirectoryInput> directoryInputs = Lists.newArrayList();
+
+ File jarsFolder = new File(folder, JARS);
+ if (jarsFolder.isDirectory()) {
+ parseTypeLevelFolders(
+ jarsFolder,
+ requiredTypes,
+ requiredScopes,
+ new InputGenerator() {
+ @Override
+ public boolean accept(@NonNull File file) {
+ return file.isFile() && file.getName().endsWith(DOT_JAR);
+ }
+
+ @Override
+ public void generate(
+ @NonNull File file,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes) {
+ jarInputs.add(new ImmutableJarInput(
+ file.getName().substring(0,
+ file.getName().length() - DOT_JAR.length()),
+ file,
+ Status.NOTCHANGED,
+ types,
+ scopes));
+
+ }
+ });
+ }
+
+ File foldersFolder = new File(folder, FOLDERS);
+ if (foldersFolder.isDirectory()) {
+ parseTypeLevelFolders(
+ foldersFolder,
+ requiredTypes,
+ requiredScopes,
+ new InputGenerator() {
+ @Override
+ public boolean accept(@NonNull File file) {
+ return file.isDirectory();
+ }
+
+ @Override
+ public void generate(@NonNull File file, @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes) {
+ directoryInputs.add(new ImmutableDirectoryInput(
+ file.getName(),
+ file,
+ types,
+ scopes));
+ }
+ });
+ }
+
+ return new ImmutableTransformInput(jarInputs, directoryInputs, folder);
+ }
+
+ static class IntermediateTransformInput extends IncrementalTransformInput {
+
+ @NonNull
+ private final File inputRoot;
+ private List<String> rootLocationSegments = null;
+
+ IntermediateTransformInput(@NonNull File inputRoot) {
+ this.inputRoot = inputRoot;
+ }
+
+ @Override
+ protected boolean checkRemovedFolder(
+ @NonNull File file,
+ @NonNull List<String> fileSegments) {
+ if (!checkRootSegments(fileSegments)) {
+ return false;
+ }
+
+ // there must be at least 5 additional segments (4 to the root of the folder and 1 for
+ // the file inside.
+ if (fileSegments.size() <= rootLocationSegments.size() + 4) {
+ return false;
+ }
+
+ // now check that the segments after the root are what we expect.
+ int index = rootLocationSegments.size();
+ if (!fileSegments.get(index++).equals(FOLDERS)) {
+ return false;
+ }
+
+ // get the types.
+ Set<ContentType> types = stringToTypes(fileSegments.get(index++));
+ if (types == null) {
+ return false;
+ }
+
+ // get the scopes.
+ Set<Scope> scopes = stringToScopes(fileSegments.get(index++));
+ if (scopes == null) {
+ return false;
+ }
+
+ String name = fileSegments.get(index);
+
+ // create the folder input. a mutable one so that it can be directly used
+ // for other removed files from the same folder.
+ // The root location of this folder is fileSegments, up to rootLocation + 4 (as the
+ // rest is the changed file.
+ File root = new File(FileUtils.join(
+ fileSegments.subList(0, rootLocationSegments.size() + 4)));
+ MutableDirectoryInput folder = new MutableDirectoryInput(name, root, types, scopes);
+ // add this file to it.
+ folder.addChangedFile(file, Status.REMOVED);
+
+ // add it to the list.
+ addFolderInput(folder);
+
+ return true;
+ }
+
+ @Override
+ boolean checkRemovedJarFile(@NonNull File file, @NonNull List<String> fileSegments) {
+ if (!checkRootSegments(fileSegments)) {
+ return false;
+ }
+
+ // there must be only 4 additional segments.
+ if (fileSegments.size() != rootLocationSegments.size() + 4) {
+ return false;
+ }
+
+ // last segment must end in .jar
+ if (!file.getPath().endsWith(DOT_JAR)) {
+ return false;
+ }
+
+ // now check that the segments after the root are what we expect.
+ int index = rootLocationSegments.size();
+ if (!fileSegments.get(index++).equals(JARS)) {
+ return false;
+ }
+
+ // get the types.
+ Set<ContentType> types = stringToTypes(fileSegments.get(index++));
+ if (types == null) {
+ return false;
+ }
+
+ // get the scopes.
+ Set<Scope> scopes = stringToScopes(fileSegments.get(index++));
+ if (scopes == null) {
+ return false;
+ }
+
+ String name = fileSegments.get(index);
+
+ // create the jar input
+ addImmutableJar(new ImmutableJarInput(name, file, Status.REMOVED, types, scopes));
+
+ return true;
+ }
+
+ private boolean checkRootSegments(@NonNull List<String> fileSegments) {
+ if (rootLocationSegments == null) {
+ rootLocationSegments = Lists.newArrayList(
+ Splitter.on(File.separatorChar).split(inputRoot.getAbsolutePath()));
+ }
+
+ if (fileSegments.size() <= rootLocationSegments.size()) {
+ return false;
+ }
+
+ // compare segments going backward as the leafs are more likely to be different.
+ // We can ignore the segments of the file that are beyond the segments for the folder.
+ for (int i = rootLocationSegments.size() - 1 ; i >= 0 ; i--) {
+ if (!rootLocationSegments.get(i).equals(fileSegments.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ }
+
+ @NonNull
+ public static IncrementalTransformInput computeIncrementalInputFromFolder(
+ @NonNull File rootLocation,
+ @NonNull Set<ContentType> requiredTypes,
+ @NonNull Set<Scope> requiredScopes) {
+ final IncrementalTransformInput input = new IntermediateTransformInput(rootLocation);
+
+ File jarsFolder = new File(rootLocation, JARS);
+ if (jarsFolder.isDirectory()) {
+ parseTypeLevelFolders(
+ jarsFolder,
+ requiredTypes,
+ requiredScopes,
+ new InputGenerator() {
+ @Override
+ public boolean accept(@NonNull File file) {
+ return file.isFile() && file.getName().endsWith(DOT_JAR);
+ }
+
+ @Override
+ public void generate(
+ @NonNull File file,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes) {
+ input.addJarInput(new QualifiedContentImpl(
+ file.getName().substring(0,
+ file.getName().length() - DOT_JAR.length()),
+ file,
+ types,
+ scopes) {
+ });
+
+ }
+ });
+ }
+
+ File foldersFolder = new File(rootLocation, FOLDERS);
+ if (foldersFolder.isDirectory()) {
+ parseTypeLevelFolders(
+ foldersFolder,
+ requiredTypes,
+ requiredScopes,
+ new InputGenerator() {
+ @Override
+ public boolean accept(@NonNull File file) {
+ return file.isDirectory();
+ }
+
+ @Override
+ public void generate(@NonNull File file, @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes) {
+ input.addFolderInput(new MutableDirectoryInput(
+ file.getName(),
+ file,
+ types,
+ scopes));
+ }
+ });
+ }
+
+ return input;
+ }
+
+ private interface InputGenerator {
+ boolean accept(@NonNull File file);
+ void generate(
+ @NonNull File file,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes);
+ }
+
+ private static void parseTypeLevelFolders(
+ @NonNull File rootFolder,
+ @NonNull Set<ContentType> requiredTypes,
+ @NonNull Set<Scope> requiredScopes,
+ @NonNull InputGenerator generator) {
+ File[] files = rootFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isDirectory();
+ }
+ });
+
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ Set<ContentType> types = stringToTypes(file.getName());
+ if (types != null) {
+ // check these are types we care about and only pass down types we care about.
+ // In this case we can safely return the content with a limited type,
+ // as file extension allows for differentiation.
+ Set<ContentType> limitedTypes = Sets.intersection(requiredTypes, types);
+ if (!limitedTypes.isEmpty()) {
+ parseScopeLevelFolders(file, limitedTypes, requiredScopes, generator);
+ }
+ }
+ }
+ }
+ }
+
+ private static void parseScopeLevelFolders(
+ @NonNull File rootFolder,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> requiredScopes,
+ @NonNull InputGenerator generator) {
+ File[] files = rootFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isDirectory();
+ }
+ });
+
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ Set<Scope> scopes = stringToScopes(file.getName());
+ if (scopes != null) {
+ // we need up to the requiredScopes, but no more.
+ // content that only contains unwanted Scope can be safely dropped, however
+ // content that is both in and out of Scope will trigger a runtime error.
+ // check these are the scope we want, and only pass down scopes we care about.
+ Set<Scope> limitedScopes = Sets.newHashSetWithExpectedSize(requiredScopes.size());
+ boolean foundUnwanted = false;
+ for (Scope scope : scopes) {
+ if (requiredScopes.contains(scope)) {
+ limitedScopes.add(scope);
+ } else {
+ foundUnwanted = true;
+ }
+ }
+ if (!limitedScopes.isEmpty()) {
+ if (foundUnwanted) {
+ throw new RuntimeException("error");
+ }
+ parseContentLevelFolders(file, types, Sets.immutableEnumSet(limitedScopes),
+ generator);
+ }
+ }
+ }
+ }
+ }
+
+ private static void parseContentLevelFolders(
+ @NonNull File rootFolder,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes,
+ @NonNull final InputGenerator generator) {
+
+ File[] files = rootFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return generator.accept(file);
+ }
+ });
+
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ generator.generate(file, types, scopes);
+ }
+ }
+ }
+
+ @Nullable
+ private static Set<ContentType> stringToTypes(String folderName) {
+ int value;
+ try {
+ value = Integer.parseInt(folderName, 16);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+
+ ImmutableSet.Builder<ContentType> typesBuilder = ImmutableSet.builder();
+
+ for (ContentType type : ExtendedContentType.getAllContentTypes()) {
+ if ((type.getValue() & value) != 0) {
+ typesBuilder.add(type);
+ }
+ }
+
+ Set<ContentType> types = typesBuilder.build();
+ if (types.isEmpty()) {
+ return null;
+ }
+
+ return types;
+ }
+
+ private static String typesToString(@NonNull Set<ContentType> types) {
+ int value = 0;
+ for (ContentType type : types) {
+ value += type.getValue();
+ }
+
+ return String.format("%x", value);
+ }
+
+ @Nullable
+ private static Set<Scope> stringToScopes(String folderName) {
+ int value;
+ try {
+ value = Integer.parseInt(folderName, 16);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+
+ Set<Scope> scopes = Sets.newHashSet();
+
+ for (Scope scope : Scope.values()) {
+ if ((scope.getValue() & value) != 0) {
+ scopes.add(scope);
+ }
+ }
+
+ if (scopes.isEmpty()) {
+ return null;
+ }
+
+ return Sets.immutableEnumSet(scopes);
+ }
+
+ private static String scopesToString(@NonNull Set<Scope> scopes) {
+ int value = 0;
+ for (Scope scope : scopes) {
+ value += scope.getValue();
+ }
+
+ return String.format("%x", value);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IntermediateStream.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IntermediateStream.java
new file mode 100644
index 0000000..bb20c17
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/IntermediateStream.java
@@ -0,0 +1,167 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Version of TransformStream handling outputs of transforms.
+ */
+ at Immutable
+class IntermediateStream extends TransformStream {
+
+ private final Supplier<File> rootLocation;
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ static final class Builder {
+ private Set<ContentType> contentTypes = Sets.newHashSet();
+ private Set<Scope> scopes = Sets.newHashSet();
+ private Supplier<File> rootLocation;
+ private List<? extends Object> dependencies;
+
+ public IntermediateStream build() {
+ Preconditions.checkNotNull(rootLocation);
+ Preconditions.checkState(!contentTypes.isEmpty());
+ Preconditions.checkState(!scopes.isEmpty());
+ return new IntermediateStream(
+ ImmutableSet.copyOf(contentTypes),
+ Sets.immutableEnumSet(scopes),
+ rootLocation,
+ dependencies != null ? dependencies : ImmutableList.of());
+ }
+
+ Builder addContentTypes(@NonNull Set<ContentType> types) {
+ this.contentTypes.addAll(types);
+ return this;
+ }
+
+ Builder addContentTypes(@NonNull ContentType... types) {
+ this.contentTypes.addAll(Arrays.asList(types));
+ return this;
+ }
+
+ Builder addScopes(@NonNull Set<Scope> scopes) {
+ this.scopes.addAll(scopes);
+ return this;
+ }
+
+ Builder addScopes(@NonNull Scope... scopes) {
+ this.scopes.addAll(Arrays.asList(scopes));
+ return this;
+ }
+
+ Builder setRootLocation(@NonNull final File rootLocation) {
+ this.rootLocation = Suppliers.ofInstance(rootLocation);
+ return this;
+ }
+
+ Builder setDependency(@NonNull Object dependency) {
+ this.dependencies = ImmutableList.of(dependency);
+ return this;
+ }
+ }
+
+ private IntermediateStream(
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes,
+ @NonNull Supplier<File> rootLocation,
+ @NonNull List<? extends Object> dependencies) {
+ super(contentTypes, scopes, dependencies);
+ this.rootLocation = rootLocation;
+ }
+
+ /**
+ * Returns the files that make up the streams. The callable allows for resolving this lazily.
+ */
+ @NonNull
+ Supplier<File> getRootLocation() {
+ return rootLocation;
+ }
+
+ /**
+ * Returns a new view of this content as a {@link TransformOutputProvider}.
+ */
+ @NonNull
+ TransformOutputProvider asOutput() {
+ return new TransformOutputProviderImpl(rootLocation.get());
+ }
+
+ @NonNull
+ @Override
+ List<File> getInputFiles() {
+ return ImmutableList.of(rootLocation.get());
+ }
+
+ @NonNull
+ @Override
+ TransformInput asNonIncrementalInput() {
+ return IntermediateFolderUtils.computeNonIncrementalInputFromFolder(
+ rootLocation.get(),
+ getContentTypes(),
+ getScopes());
+ }
+
+ @NonNull
+ @Override
+ IncrementalTransformInput asIncrementalInput() {
+ return IntermediateFolderUtils.computeIncrementalInputFromFolder(
+ rootLocation.get(),
+ getContentTypes(),
+ getScopes());
+ }
+
+ @Override
+ TransformStream makeRestrictedCopy(
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes) {
+ return new IntermediateStream(
+ types,
+ scopes,
+ rootLocation,
+ getDependencies());
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("rootLocation", rootLocation.get())
+ .add("scopes", getScopes())
+ .add("contentTypes", getContentTypes())
+ .add("dependencies", getDependencies())
+ .toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/MutableDirectoryInput.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/MutableDirectoryInput.java
new file mode 100644
index 0000000..41c8993
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/MutableDirectoryInput.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Status;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Mutable DirectoryInput implementation, allowing changes to the list of changed files.
+ *
+ * This is used to build instanceof of {@link ImmutableDirectoryInput} in steps.
+ */
+class MutableDirectoryInput extends QualifiedContentImpl {
+
+ @NonNull
+ private final Map<File, Status> changedFiles = Maps.newHashMap();
+
+ private List<String> rootLocationSegments = null;
+
+ MutableDirectoryInput(
+ @NonNull String name,
+ @NonNull File file,
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes) {
+ super(name, file, contentTypes, scopes);
+ }
+
+ @NonNull
+ DirectoryInput asImmutable() {
+ return new ImmutableDirectoryInput(
+ getName(), getFile(), getContentTypes(), getScopes(), changedFiles);
+ }
+
+ /**
+ * Process a changed file.
+ *
+ * If the file belongs to this folder then it is added to this folder's changed file map, and
+ * the method returns true;
+ *
+ * @param file the changed file
+ * @param fileSegments the changed file path segments for faster checks
+ * @param status the status of the changed file.
+ * @return true if the file belongs to this folder.
+ */
+ boolean processForChangedFile(
+ @NonNull File file,
+ @NonNull List<String> fileSegments,
+ @NonNull Status status) {
+
+ if (rootLocationSegments == null) {
+ rootLocationSegments = Lists.newArrayList(
+ Splitter.on(File.separatorChar).split(getFile().getAbsolutePath()));
+ }
+
+ if (fileSegments.size() <= rootLocationSegments.size()) {
+ return false;
+ }
+
+ // compare segments going backward as the leafs are more likely to be different.
+ // We can ignore the segments of the file that are beyond the segments for the folder.
+ for (int i = rootLocationSegments.size() - 1 ; i >= 0 ; i--) {
+ if (!rootLocationSegments.get(i).equals(fileSegments.get(i))) {
+ return false;
+ }
+ }
+
+ // ok the file is part of the folder, add it to the changed list.
+ addChangedFile(file, status);
+ return true;
+ }
+
+ void addChangedFile(@NonNull File file, @NonNull Status status) {
+ changedFiles.put(file, status);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/OriginalStream.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/OriginalStream.java
new file mode 100644
index 0000000..4b61ed0
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/OriginalStream.java
@@ -0,0 +1,277 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.concurrency.Immutable;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformInput;
+import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.hash.Hashing;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Version of TransformStream handling input that is not generated by transforms.
+ */
+ at Immutable
+public class OriginalStream extends TransformStream {
+
+ private static Supplier<Collection<File>> EMPTY_SUPPLIER = new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return ImmutableList.of();
+ }
+ };
+
+ private final Supplier<Collection<File>> jarFiles;
+ private final Supplier<Collection<File>> folders;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private Set<ContentType> contentTypes = Sets.newHashSet();
+ private Scope scope;
+ private Supplier<Collection<File>> jarFiles;
+ private Supplier<Collection<File>> folders;
+ private List<? extends Object> dependencies;
+
+ public OriginalStream build() {
+ checkNotNull(scope);
+ checkState(!contentTypes.isEmpty());
+
+ return new OriginalStream(
+ ImmutableSet.copyOf(contentTypes),
+ scope,
+ jarFiles != null ? jarFiles : EMPTY_SUPPLIER,
+ folders != null ? folders : EMPTY_SUPPLIER,
+ dependencies != null ? dependencies : ImmutableList.of());
+ }
+
+ public Builder addContentTypes(@NonNull Set<ContentType> types) {
+ this.contentTypes.addAll(types);
+ return this;
+ }
+
+ public Builder addContentTypes(@NonNull ContentType... types) {
+ this.contentTypes.addAll(Arrays.asList(types));
+ return this;
+ }
+
+ public Builder addContentType(@NonNull ContentType type) {
+ this.contentTypes.add(type);
+ return this;
+ }
+
+ public Builder addScope(@NonNull Scope scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public Builder setJar(@NonNull final File jarFile) {
+ this.jarFiles = Suppliers.ofInstance((Collection<File>) ImmutableList.of(jarFile));
+ return this;
+ }
+
+ public Builder setJars(@NonNull Supplier<Collection<File>> jarSupplier) {
+ this.jarFiles = jarSupplier;
+ return this;
+ }
+
+ public Builder setFolder(@NonNull final File folder) {
+ this.folders = Suppliers.ofInstance((Collection<File>) ImmutableList.of(folder));
+ return this;
+ }
+
+ public Builder setFolders(@NonNull Supplier<Collection<File>> folderSupplier) {
+ this.folders = folderSupplier;
+ return this;
+ }
+
+ public Builder setDependencies(@NonNull List<? extends Object> dependencies) {
+ this.dependencies = ImmutableList.copyOf(dependencies);
+ return this;
+ }
+
+ public Builder setDependency(@NonNull Object dependency) {
+ this.dependencies = ImmutableList.of(dependency);
+ return this;
+ }
+ }
+
+ private OriginalStream(
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Scope scope,
+ @NonNull Supplier<Collection<File>> jarFiles,
+ @NonNull Supplier<Collection<File>> folders,
+ @NonNull List<? extends Object> dependencies) {
+ super(contentTypes, Sets.immutableEnumSet(scope), dependencies);
+ this.jarFiles = jarFiles;
+ this.folders = folders;
+ }
+
+ @NonNull
+ @Override
+ List<File> getInputFiles() {
+ Collection<File> list1 = jarFiles.get();
+ Collection<File> list2 = folders.get();
+
+ List<File> inputFiles = Lists.newArrayListWithCapacity(list1.size() + list2.size());
+ inputFiles.addAll(list1);
+ inputFiles.addAll(list2);
+ return inputFiles;
+ }
+
+ private static class OriginalTransformInput extends IncrementalTransformInput {
+
+ @Override
+ protected boolean checkRemovedFolder(@NonNull File file,
+ @NonNull List<String> fileSegments) {
+ // we can never detect if a random file was removed from this input.
+ return false;
+ }
+
+ @Override
+ boolean checkRemovedJarFile(@NonNull File file,
+ @NonNull List<String> fileSegments) {
+ // we can never detect if a jar was removed from this input.
+ return false;
+ }
+ }
+
+ @NonNull
+ @Override
+ TransformInput asNonIncrementalInput() {
+ List<JarInput> jarInputs = Lists.newArrayList();
+ List<DirectoryInput> directoryInputs = Lists.newArrayList();
+
+ Set<ContentType> contentTypes = getContentTypes();
+ Set<Scope> scopes = getScopes();
+
+ for (File file : jarFiles.get()) {
+ jarInputs.add(new ImmutableJarInput(
+ getUniqueInputName(file),
+ file,
+ Status.NOTCHANGED,
+ contentTypes,
+ scopes));
+ }
+
+ for (File file : folders.get()) {
+ directoryInputs.add(new ImmutableDirectoryInput(
+ getUniqueInputName(file),
+ file,
+ contentTypes,
+ scopes));
+ }
+
+ return new ImmutableTransformInput(jarInputs, directoryInputs, null);
+ }
+
+ @NonNull
+ @Override
+ IncrementalTransformInput asIncrementalInput() {
+ IncrementalTransformInput input = new OriginalTransformInput();
+
+ Set<ContentType> contentTypes = getContentTypes();
+ Set<Scope> scopes = getScopes();
+
+ for (File file : jarFiles.get()) {
+ input.addJarInput(new QualifiedContentImpl(
+ getUniqueInputName(file),
+ file,
+ contentTypes,
+ scopes));
+ }
+
+ for (File file : folders.get()) {
+ input.addFolderInput(new MutableDirectoryInput(
+ getUniqueInputName(file),
+ file,
+ contentTypes,
+ scopes));
+ }
+
+ return input;
+ }
+
+ @NonNull
+ private static String getUniqueInputName(@NonNull File file) {
+ return Hashing.sha1().hashString(file.getPath(), Charsets.UTF_16LE).toString();
+ }
+
+ @Override
+ TransformStream makeRestrictedCopy(
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes) {
+ if (!scopes.equals(getScopes())) {
+ // since the content itself (jars and folders) don't have they own notion of scopes
+ // we cannot do a restricted stream. However, since this stream is always created
+ // with a single stream, this shouldn't happen.
+ throw new UnsupportedOperationException("Cannot do a scope-restricted OriginalStream");
+ }
+ return new OriginalStream(
+ types,
+ Iterables.getOnlyElement(scopes),
+ jarFiles,
+ folders,
+ getDependencies());
+ }
+
+ @VisibleForTesting
+ Supplier<Collection<File>> getJarFiles() {
+ return jarFiles;
+ }
+
+ @VisibleForTesting
+ Supplier<Collection<File>> getFolders() {
+ return folders;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("jarFiles", jarFiles.get())
+ .add("folders", folders.get())
+ .add("scopes", getScopes())
+ .add("contentTypes", getContentTypes())
+ .add("dependencies", getDependencies())
+ .toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/QualifiedContentImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/QualifiedContentImpl.java
new file mode 100644
index 0000000..2749244
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/QualifiedContentImpl.java
@@ -0,0 +1,94 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.android.build.api.transform.QualifiedContent;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Basic implementation of {@link QualifiedContent}.
+ */
+ at Immutable
+class QualifiedContentImpl implements QualifiedContent {
+
+ @NonNull
+ private final String name;
+ @NonNull
+ private final File file;
+ @NonNull
+ private final Set<ContentType> contentTypes;
+ @NonNull
+ private final Set<Scope> scopes;
+
+ protected QualifiedContentImpl(
+ @NonNull String name,
+ @NonNull File file,
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes) {
+ this.name = name;
+ this.file = file;
+ this.contentTypes = ImmutableSet.copyOf(contentTypes);
+ this.scopes = ImmutableSet.copyOf(scopes);
+ }
+
+ protected QualifiedContentImpl(@NonNull QualifiedContent qualifiedContent) {
+ this.name = qualifiedContent.getName();
+ this.file = qualifiedContent.getFile();
+ this.contentTypes = qualifiedContent.getContentTypes();
+ this.scopes = qualifiedContent.getScopes();
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public File getFile() {
+ return file;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getContentTypes() {
+ return contentTypes;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return scopes;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", name)
+ .add("file", file)
+ .add("contentTypes", contentTypes)
+ .add("scopes", scopes)
+ .toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/StreamBasedTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/StreamBasedTask.java
new file mode 100644
index 0000000..926154b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/StreamBasedTask.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A base task with stream fields that properly use Gradle's input/output annotations to
+ * return the stream's content as input/output.
+ */
+public class StreamBasedTask extends BaseTask {
+
+ protected Collection<TransformStream> consumedInputStreams;
+ protected Collection<TransformStream> referencedInputStreams;
+ protected IntermediateStream outputStream;
+
+ @NonNull
+ @InputFiles
+ public List<File> getStreamInputs() {
+ List<File> inputs = Lists.newArrayList();
+ for (TransformStream s : consumedInputStreams) {
+ inputs.addAll(s.getInputFiles());
+ }
+
+ for (TransformStream s : referencedInputStreams) {
+ inputs.addAll(s.getInputFiles());
+ }
+
+ return inputs;
+ }
+
+ @Nullable
+ @Optional
+ @OutputDirectory
+ public File getStreamOutputFolder() {
+ if (outputStream != null) {
+ return outputStream.getRootLocation().get();
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformInvocationBuilder.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformInvocationBuilder.java
new file mode 100644
index 0000000..4461c37
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformInvocationBuilder.java
@@ -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.
+ */
+
+package com.android.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+
+/**
+ * A simple holder for all {@link TransformInvocation} relevant information, provider a Builder
+ * style interface to build up.
+ */
+public class TransformInvocationBuilder {
+
+ Context context;
+ Collection<TransformInput> inputs = ImmutableList.of();
+ Collection<TransformInput> referencedInputs = ImmutableList.of();
+ Collection<SecondaryInput> secondaryInputs = ImmutableList.of();
+ TransformOutputProvider transformOutputProvider;
+ boolean isIncremental = false;
+
+ public TransformInvocationBuilder(@NonNull Context context) {
+ this.context = context;
+ }
+
+ public TransformInvocationBuilder addInputs(Collection<TransformInput> inputs) {
+ this.inputs = ImmutableList.copyOf(inputs);
+ return this;
+ }
+
+ public TransformInvocationBuilder addReferencedInputs(Collection<TransformInput> referencedInputs) {
+ this.referencedInputs = ImmutableList.copyOf(referencedInputs);
+ return this;
+ }
+
+ public TransformInvocationBuilder addSecondaryInputs(Collection<SecondaryInput> secondaryInputs) {
+ this.secondaryInputs = ImmutableList.copyOf(secondaryInputs);
+ return this;
+ }
+
+ public TransformInvocationBuilder addOutputProvider(
+ @Nullable TransformOutputProvider transformOutputProvider) {
+ this.transformOutputProvider = transformOutputProvider;
+ return this;
+ }
+
+ public TransformInvocationBuilder setIncrementalMode(boolean isIncremental) {
+ this.isIncremental = isIncremental;
+ return this;
+ }
+
+ public TransformInvocation build() {
+ return new TransformInvocation() {
+ @NonNull
+ @Override
+ public Context getContext() {
+ return context;
+ }
+
+ @NonNull
+ @Override
+ public Collection<TransformInput> getInputs() {
+ return inputs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<TransformInput> getReferencedInputs() {
+ return referencedInputs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<SecondaryInput> getSecondaryInputs() {
+ return secondaryInputs;
+ }
+
+ @Nullable
+ @Override
+ public TransformOutputProvider getOutputProvider() {
+ return transformOutputProvider;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return isIncremental;
+ }
+
+ };
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformManager.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformManager.java
new file mode 100644
index 0000000..134f972
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformManager.java
@@ -0,0 +1,426 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import static com.android.build.api.transform.QualifiedContent.DefaultContentType.CLASSES;
+import static com.android.build.api.transform.QualifiedContent.DefaultContentType.RESOURCES;
+import static com.android.utils.StringHelper.capitalize;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.TaskFactory;
+import com.android.build.gradle.internal.scope.AndroidTask;
+import com.android.build.gradle.internal.scope.AndroidTaskRegistry;
+import com.android.build.gradle.internal.scope.BaseScope;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+import com.android.utils.FileUtils;
+import com.android.utils.StringHelper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Manages the transforms for a variant.
+ *
+ * Th actual execution is handled by Gradle through the tasks.
+ * Instead it's a mean to more easily configure a series of transforms that consume each other's
+ * inputs when several of these transform are optional.
+ */
+public class TransformManager extends FilterableStreamCollection {
+
+ private static final boolean DEBUG = true;
+
+ private static final String FD_TRANSFORMS = "transforms";
+
+ public static final Set<Scope> EMPTY_SCOPES = ImmutableSet.of();
+
+ public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.<ContentType>of(CLASSES);
+ public static final Set<ContentType> CONTENT_JARS = ImmutableSet.<ContentType>of(CLASSES, RESOURCES);
+ public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.<ContentType>of(RESOURCES);
+ public static final Set<ContentType> CONTENT_NATIVE_LIBS = ImmutableSet.<ContentType>of(
+ ExtendedContentType.NATIVE_LIBS);
+ public static final Set<ContentType> CONTENT_DEX = ImmutableSet.<ContentType>of(
+ ExtendedContentType.DEX);
+ public static final Set<Scope> SCOPE_FULL_PROJECT = Sets.immutableEnumSet(
+ Scope.PROJECT,
+ Scope.PROJECT_LOCAL_DEPS,
+ Scope.SUB_PROJECTS,
+ Scope.SUB_PROJECTS_LOCAL_DEPS,
+ Scope.EXTERNAL_LIBRARIES);
+ public static final Set<Scope> SCOPE_FULL_LIBRARY = Sets.immutableEnumSet(
+ Scope.PROJECT,
+ Scope.PROJECT_LOCAL_DEPS);
+
+ @NonNull
+ private final AndroidTaskRegistry taskRegistry;
+ @NonNull
+ private final ErrorReporter errorReporter;
+ @NonNull
+ private final Logger logger;
+
+ /**
+ * These are the streams that are available for new Transforms to consume.
+ *
+ * Once a new transform is added, the streams that it consumes are removed from this list,
+ * and the streams it produces are put instead.
+ *
+ * When all the transforms have been added, the remaining streams should be consumed by
+ * standard Tasks somehow.
+ *
+ * @see #getStreams(StreamFilter)
+ */
+ @NonNull
+ private final List<TransformStream> streams = Lists.newArrayList();
+ @NonNull
+ private final List<Transform> transforms = Lists.newArrayList();
+
+ public TransformManager(
+ @NonNull AndroidTaskRegistry taskRegistry,
+ @NonNull ErrorReporter errorReporter) {
+ this.taskRegistry = taskRegistry;
+ this.errorReporter = errorReporter;
+ this.logger = Logging.getLogger(TransformManager.class);
+
+ }
+
+ @NonNull
+ public AndroidTaskRegistry getTaskRegistry() {
+ return taskRegistry;
+ }
+
+ public void addStream(@NonNull TransformStream stream) {
+ streams.add(stream);
+ }
+
+ public <T extends Transform> AndroidTask<TransformTask> addTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull BaseScope variantScope,
+ @NonNull T transform) {
+ return addTransform(taskFactory, variantScope, transform, null /*callback*/);
+ }
+
+ /**
+ * Adds a Transform.
+ *
+ * This makes the current transform consumes whatever Streams are currently available and
+ * creates new ones for the transform output.
+ *
+ * This also creates a {@link TransformTask} to run the transform and wire it up with the
+ * dependencies of the consumed streams.
+ *
+ * @param taskFactory the task factory
+ * @param scope the current scope
+ * @param transform the transform to add
+ * @param callback a callback that is run when the task is actually configured
+ * @param <T> the type of the transform
+ * @return the AndroidTask for the given transform task or null if it cannot be created.
+ */
+ @Nullable
+ public <T extends Transform> AndroidTask<TransformTask> addTransform(
+ @NonNull TaskFactory taskFactory,
+ @NonNull BaseScope scope,
+ @NonNull T transform,
+ @Nullable TransformTask.ConfigActionCallback<T> callback) {
+
+ if (!validateTransform(transform)) {
+ // validate either throws an exception, or records the problem during sync
+ // so it's safe to just return null here.
+ return null;
+ }
+
+ List<TransformStream> inputStreams = Lists.newArrayList();
+ String taskName = scope.getTaskName(getTaskNamePrefix(transform));
+
+ // get referenced-only streams
+ List<TransformStream> referencedStreams = grabReferencedStreams(transform);
+
+ // find input streams, and compute output streams for the transform.
+ IntermediateStream outputStream = findTransformStreams(
+ transform,
+ scope,
+ inputStreams,
+ taskName,
+ scope.getGlobalScope().getBuildDir());
+
+ if (inputStreams.isEmpty() && referencedStreams.isEmpty()) {
+ // didn't find any match. Means there is a broken order somewhere in the streams.
+ errorReporter.handleSyncError(
+ null,
+ SyncIssue.TYPE_GENERIC,
+ String.format(
+ "Unable to add Transform '%s' on variant '%s': requested streams not available: %s+%s / %s",
+ transform.getName(), scope.getVariantConfiguration().getFullName(),
+ transform.getScopes(), transform.getReferencedScopes(),
+ transform.getInputTypes()));
+ return null;
+ }
+
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && logger.isEnabled(LogLevel.DEBUG)) {
+ logger.debug(
+ "ADDED TRANSFORM(" + scope.getVariantConfiguration().getFullName()
+ + "):");
+ logger.debug("\tName: " + transform.getName());
+ logger.debug("\tTask: " + taskName);
+ for (TransformStream sd : inputStreams) {
+ logger.debug("\tInputStream: " + sd);
+ }
+ for (TransformStream sd : referencedStreams) {
+ logger.debug("\tRef'edStream: " + sd);
+ }
+ if (outputStream != null) {
+ logger.debug("\tOutputStream: " + outputStream);
+ }
+ }
+
+ transforms.add(transform);
+
+ // create the task...
+ AndroidTask<TransformTask> task = taskRegistry.create(
+ taskFactory,
+ new TransformTask.ConfigAction<T>(
+ scope.getVariantConfiguration().getFullName(),
+ taskName,
+ transform,
+ inputStreams,
+ referencedStreams,
+ outputStream,
+ callback));
+
+ for (TransformStream s : inputStreams) {
+ task.dependsOn(taskFactory, s.getDependencies());
+ }
+ for (TransformStream s : referencedStreams) {
+ task.dependsOn(taskFactory, s.getDependencies());
+ }
+
+ return task;
+ }
+
+ @Override
+ @NonNull
+ public List<TransformStream> getStreams() {
+ return streams;
+ }
+
+ @NonNull
+ private static String getTaskNamePrefix(@NonNull Transform transform) {
+ StringBuilder sb = new StringBuilder(100);
+ sb.append("transform");
+
+ Iterator<ContentType> iterator = transform.getInputTypes().iterator();
+ // there's always at least one
+ sb.append(capitalize(iterator.next().name().toLowerCase(Locale.getDefault())));
+ while (iterator.hasNext()) {
+ sb.append("And").append(capitalize(
+ iterator.next().name().toLowerCase(Locale.getDefault())));
+ }
+
+ sb.append("With").append(capitalize(transform.getName())).append("For");
+
+ return sb.toString();
+ }
+
+ /**
+ * Finds the stream the transform consumes, and return them.
+ *
+ * This also removes them from the instance list. They will be replaced with the output
+ * stream(s) from the transform.
+ *
+ * This returns an optional output stream.
+ *
+ * @param transform the transform.
+ * @param scope the scope the transform is applied to.
+ * @param inputStreams the out list of input streams for the transform.
+ * @param taskName the name of the task that will run the transform
+ * @param buildDir the build dir of the project.
+ * @return the output stream if any.
+ */
+ @Nullable
+ private IntermediateStream findTransformStreams(
+ @NonNull Transform transform,
+ @NonNull BaseScope scope,
+ @NonNull List<TransformStream> inputStreams,
+ @NonNull String taskName,
+ @NonNull File buildDir) {
+
+ Set<Scope> requestedScopes = transform.getScopes();
+ if (requestedScopes.isEmpty()) {
+ // this is a no-op transform.
+ return null;
+ }
+
+ Set<ContentType> requestedTypes = transform.getInputTypes();
+
+ // list to hold the list of unused streams in the manager after everything is done.
+ // they'll be put back in the streams collection, along with the new outputs.
+ List<TransformStream> oldStreams = Lists.newArrayListWithExpectedSize(streams.size());
+
+ for (TransformStream stream : streams) {
+ // streams may contain more than we need. In this case, we'll make a copy of the stream
+ // with the remaining types/scopes. It'll be up to the TransformTask to make
+ // sure that the content of the stream is usable (for instance when a stream
+ // may contain two scopes, these scopes could be combined or not, impacting consumption)
+ Set<ContentType> availableTypes = stream.getContentTypes();
+ Set<Scope> availableScopes = stream.getScopes();
+
+ Set<ContentType> commonTypes = Sets.intersection(requestedTypes,
+ availableTypes);
+ Set<Scope> commonScopes = Sets.intersection(requestedScopes, availableScopes);
+ if (!commonTypes.isEmpty() && !commonScopes.isEmpty()) {
+
+ // check if we need to make another stream from this one with less scopes/types.
+ if (!commonScopes.equals(availableScopes) || !commonTypes.equals(availableTypes)) {
+ // first the stream that gets consumed. It consumes only the common types/scopes
+ inputStreams.add(stream.makeRestrictedCopy(commonTypes, commonScopes));
+
+ // now we'll have a second stream, that's left for consumption later on.
+ // compute remaining scopes/types.
+ Sets.SetView<ContentType> remainingTypes = Sets.difference(availableTypes, commonTypes);
+ Sets.SetView<Scope> remainingScopes = Sets.difference(availableScopes, commonScopes);
+
+ oldStreams.add(stream.makeRestrictedCopy(
+ remainingTypes.isEmpty() ? availableTypes : remainingTypes.immutableCopy(),
+ remainingScopes.isEmpty() ? availableScopes : remainingScopes.immutableCopy()));
+ } else {
+ // stream is an exact match (or at least subset) for the request,
+ // so we add it as it.
+ inputStreams.add(stream);
+ }
+ } else {
+ // stream is not used, keep it around.
+ oldStreams.add(stream);
+ }
+ }
+
+ // create the output stream.
+ // create single combined output stream for all types and scopes
+ Set<ContentType> outputTypes = transform.getOutputTypes();
+
+ File outRootFolder = FileUtils.join(buildDir, StringHelper.toStrings(
+ AndroidProject.FD_INTERMEDIATES,
+ FD_TRANSFORMS,
+ transform.getName(),
+ scope.getDirectorySegments()));
+
+ // update the list of available streams.
+ streams.clear();
+ streams.addAll(oldStreams);
+
+ // create the output
+ IntermediateStream outputStream = IntermediateStream.builder()
+ .addContentTypes(outputTypes)
+ .addScopes(requestedScopes)
+ .setRootLocation(outRootFolder)
+ .setDependency(taskName)
+ .build();
+ // and add it to the list of available streams for next transforms.
+ streams.add(outputStream);
+
+ return outputStream;
+ }
+
+ @NonNull
+ private List<TransformStream> grabReferencedStreams(@NonNull Transform transform) {
+ Set<Scope> requestedScopes = transform.getReferencedScopes();
+ if (requestedScopes.isEmpty()) {
+ return ImmutableList.of();
+ }
+
+ List<TransformStream> streamMatches = Lists.newArrayListWithExpectedSize(streams.size());
+
+ Set<ContentType> requestedTypes = transform.getInputTypes();
+ for (TransformStream stream : streams) {
+ // streams may contain more than we need. In this case, we'll provide the whole
+ // stream as-is since it's not actually consumed.
+ // It'll be up to the TransformTask to make sure that the content of the stream is
+ // usable (for instance when a stream
+ // may contain two scopes, these scopes could be combined or not, impacting consumption)
+ Set<ContentType> availableTypes = stream.getContentTypes();
+ Set<Scope> availableScopes = stream.getScopes();
+
+ Set<ContentType> commonTypes = Sets.intersection(requestedTypes,
+ availableTypes);
+ Set<Scope> commonScopes = Sets.intersection(requestedScopes, availableScopes);
+
+ if (!commonTypes.isEmpty() && !commonScopes.isEmpty()) {
+ streamMatches.add(stream);
+ }
+ }
+
+ return streamMatches;
+ }
+
+ private boolean validateTransform(@NonNull Transform transform) {
+ // check the content type are of the right Type.
+ if (!checkContentTypes(transform.getInputTypes(), transform)
+ || !checkContentTypes(transform.getOutputTypes(), transform)) {
+ return false;
+ }
+
+ // check some scopes are not consumed.
+ Set<Scope> scopes = transform.getScopes();
+ if (scopes.contains(Scope.PROVIDED_ONLY)) {
+ errorReporter.handleSyncError(null, SyncIssue.TYPE_GENERIC,
+ String.format("PROVIDED_ONLY scope cannot be consumed by Transform '%1$s'",
+ transform.getName()));
+ return false;
+ }
+ if (scopes.contains(Scope.TESTED_CODE)) {
+ errorReporter.handleSyncError(null, SyncIssue.TYPE_GENERIC,
+ String.format("TESTED_CODE scope cannot be consumed by Transform '%1$s'",
+ transform.getName()));
+ return false;
+
+ }
+
+
+ return true;
+ }
+
+ private boolean checkContentTypes(
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Transform transform) {
+ for (ContentType contentType : contentTypes) {
+ if (!(contentType instanceof QualifiedContent.DefaultContentType
+ || contentType instanceof ExtendedContentType)) {
+ errorReporter.handleSyncError(null, SyncIssue.TYPE_GENERIC,
+ String.format("Custom content types (%1$s) are not supported in transforms (%2$s)",
+ contentType.getClass().getName(), transform.getName()));
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformOutputProviderImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformOutputProviderImpl.java
new file mode 100644
index 0000000..70c9763
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformOutputProviderImpl.java
@@ -0,0 +1,55 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.utils.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Implementation of {@link TransformOutputProvider} passed to the transforms.
+ */
+class TransformOutputProviderImpl implements TransformOutputProvider {
+
+ @NonNull
+ private final File rootLocation;
+
+ TransformOutputProviderImpl(@NonNull File rootLocation) {
+ this.rootLocation = rootLocation;
+ }
+
+ @Override
+ public void deleteAll() throws IOException {
+ FileUtils.emptyFolder(rootLocation);
+ }
+
+ @NonNull
+ @Override
+ public File getContentLocation(
+ @NonNull String name,
+ @NonNull Set<QualifiedContent.ContentType> types,
+ @NonNull Set<QualifiedContent.Scope> scopes,
+ @NonNull Format format) {
+ return IntermediateFolderUtils.getContentLocation(rootLocation, name, types, scopes, format);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformStream.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformStream.java
new file mode 100644
index 0000000..9b0644d
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformStream.java
@@ -0,0 +1,118 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Representation of a stream for internal usage of the {@link TransformManager} to wire up
+ * the different Transforms.
+ *
+ * Transforms read from and write into TransformStreams, via a custom view of them:
+ * {@link TransformInput}, and {@link TransformOutputProvider}.
+ *
+ * This contains information about the content via {@link QualifiedContent}, dependencies, and the
+ * actual file information.
+ *
+ * The dependencies is what triggers the creation of the files and any Transform (task) consuming
+ * the files must be made to depend on these objects.
+ */
+ at Immutable
+public abstract class TransformStream {
+
+ @NonNull
+ private final Set<ContentType> contentTypes;
+ @NonNull
+ private final Set<Scope> scopes;
+ @NonNull
+ private final List<? extends Object> dependencies;
+
+ protected TransformStream(
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes,
+ @NonNull List<? extends Object> dependencies) {
+ this.contentTypes = contentTypes;
+ this.scopes = scopes;
+ this.dependencies = dependencies;
+ }
+
+ /**
+ * Returns the type of content that the stream represents.
+ *
+ * <p/>
+ * It's never null nor empty, but can contain several types.
+ */
+ @NonNull
+ public Set<ContentType> getContentTypes() {
+ return contentTypes;
+ }
+
+ /**
+ * Returns the scope of the stream.
+ *
+ * <p/>
+ * It's never null nor empty, but can contain several scopes.
+ */
+ @NonNull
+ public Set<Scope> getScopes() {
+ return scopes;
+ }
+
+ /**
+ * Returns the dependency objects that generate the stream files
+ */
+ @NonNull
+ public List<? extends Object> getDependencies() {
+ return dependencies;
+ }
+
+ @NonNull
+ abstract List<File> getInputFiles();
+
+ /**
+ * Returns the transform input for this stream.
+ *
+ * All the {@link JarInput} and {@link DirectoryInput} will be in non-incremental mode.
+ *
+ * @return the transform input.
+ */
+ @NonNull
+ abstract TransformInput asNonIncrementalInput();
+
+ /**
+ * Returns a list of QualifiedContent for the jars and one for the folders.
+ *
+ */
+ @NonNull
+ abstract IncrementalTransformInput asIncrementalInput();
+
+ abstract TransformStream makeRestrictedCopy(
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes);
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformTask.java
new file mode 100644
index 0000000..174762f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/pipeline/TransformTask.java
@@ -0,0 +1,477 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.SecondaryFile;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+import com.android.ide.common.util.ReferenceHolder;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectories;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A task running a transform.
+ */
+ at ParallelizableTask
+public class TransformTask extends StreamBasedTask implements Context {
+
+ private Transform transform;
+ Collection<SecondaryFile> secondaryFiles = null;
+
+ public Transform getTransform() {
+ return transform;
+ }
+
+ @InputFiles
+ public Collection<File> getOtherFileInputs() {
+
+ ImmutableList.Builder<File> otherFileInputs = ImmutableList.builder();
+ otherFileInputs.addAll(Iterables.transform(transform.getSecondaryFiles(),
+ new Function<SecondaryFile, File>() {
+ @Override
+ public File apply(SecondaryFile input) {
+ return input.getFile();
+ }
+ }));
+ otherFileInputs.addAll(transform.getSecondaryFileInputs());
+ return otherFileInputs.build();
+ }
+
+ @OutputFiles
+ public Collection<File> getOtherFileOutputs() {
+ return transform.getSecondaryFileOutputs();
+ }
+
+ @OutputDirectories
+ public Collection<File> getOtherFolderOutputs() {
+ return transform.getSecondaryDirectoryOutputs();
+ }
+
+ @Input
+ Map<String, Object> getOtherInputs() {
+ return transform.getParameterInputs();
+ }
+
+ @TaskAction
+ void transform(final IncrementalTaskInputs incrementalTaskInputs)
+ throws IOException, TransformException, InterruptedException {
+
+ final ReferenceHolder<List<TransformInput>> consumedInputs = ReferenceHolder.empty();
+ final ReferenceHolder<List<TransformInput>> referencedInputs = ReferenceHolder.empty();
+ final ReferenceHolder<Boolean> isIncremental = ReferenceHolder.empty();
+ final ReferenceHolder<Collection<SecondaryInput>> changedSecondaryInputs =
+ ReferenceHolder.empty();
+
+ ThreadRecorder.get().record(ExecutionType.TASK_TRANSFORM_PREPARATION,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+
+ isIncremental.setValue( transform.isIncremental() &&
+ incrementalTaskInputs.isIncremental());
+
+ Map<File, Status> changedMap = Maps.newHashMap();
+ Set<File> removedFiles = Sets.newHashSet();
+ if (isIncremental.getValue()) {
+ // gather the changed files first.
+ gatherChangedFiles(incrementalTaskInputs, changedMap, removedFiles);
+
+ // and check against secondary files, which disables
+ // incremental mode.
+ isIncremental.setValue(checkSecondaryFiles(changedMap, removedFiles));
+ }
+
+ if (isIncremental.getValue()) {
+ // ok create temporary incremental data
+ List<IncrementalTransformInput> incInputs
+ = createIncrementalInputs(consumedInputStreams);
+ List<IncrementalTransformInput> incReferencedInputs
+ = createIncrementalInputs(referencedInputStreams);
+
+ // then compare to changed list and create final Inputs
+ if (isIncremental.setValue(updateIncrementalInputsWithChangedFiles(
+ incInputs,
+ incReferencedInputs,
+ changedMap,
+ removedFiles))) {
+ consumedInputs.setValue(convertToImmutable(incInputs));
+ referencedInputs.setValue(convertToImmutable(incReferencedInputs));
+ }
+ }
+
+ // at this point if we do not have incremental mode, got with
+ // default TransformInput with no inc data.
+ if (!isIncremental.getValue()) {
+ consumedInputs.setValue(
+ computeNonIncTransformInput(consumedInputStreams));
+ referencedInputs.setValue(
+ computeNonIncTransformInput(referencedInputStreams));
+ changedSecondaryInputs.setValue(
+ ImmutableList.<SecondaryInput>of());
+ } else {
+ // gather all secondary input changes.
+ changedSecondaryInputs.setValue(
+ gatherSecondaryInputChanges(changedMap, removedFiles));
+ }
+
+ return null;
+ }
+ },
+ new Recorder.Property("project", getProject().getName()),
+ new Recorder.Property("transform", transform.getName()),
+ new Recorder.Property("incremental", Boolean.toString(transform.isIncremental())));
+
+ ThreadRecorder.get().record(ExecutionType.TASK_TRANSFORM,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+
+ transform.transform(new TransformInvocationBuilder(TransformTask.this)
+ .addInputs(consumedInputs.getValue())
+ .addReferencedInputs(referencedInputs.getValue())
+ .addSecondaryInputs(changedSecondaryInputs.getValue())
+ .addOutputProvider(outputStream != null
+ ? outputStream.asOutput()
+ : null)
+ .setIncrementalMode(isIncremental.getValue())
+ .build());
+ return null;
+ }
+ },
+ new Recorder.Property("project", getProject().getName()),
+ new Recorder.Property("transform", transform.getName()),
+ new Recorder.Property("incremental", Boolean.toString(transform.isIncremental())));
+ }
+
+ private Collection<SecondaryInput> gatherSecondaryInputChanges(
+ Map<File, Status> changedMap, Set<File> removedFiles) {
+
+ ImmutableList.Builder<SecondaryInput> builder = ImmutableList.builder();
+ for (final SecondaryFile secondaryFile : getAllSecondaryInputs()) {
+ final File file = secondaryFile.getFile();
+ final Status status = changedMap.containsKey(file)
+ ? changedMap.get(file)
+ : removedFiles.contains(file)
+ ? Status.REMOVED
+ : Status.NOTCHANGED;
+
+ builder.add(new SecondaryInput() {
+ @Override
+ public SecondaryFile getSecondaryInput() {
+ return secondaryFile;
+ }
+
+ @Override
+ public Status getStatus() {
+ return status;
+ }
+ });
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns a list of non incremental TransformInput.
+ * @param streams the streams.
+ * @return a list of non-incremental TransformInput matching the content of the streams.
+ */
+ @NonNull
+ private static List<TransformInput> computeNonIncTransformInput(
+ @NonNull Collection<TransformStream> streams) {
+ List<TransformInput> inputs = Lists.newArrayListWithCapacity(streams.size());
+ for (TransformStream stream : streams) {
+ inputs.add(stream.asNonIncrementalInput());
+ }
+
+ return inputs;
+ }
+
+ /**
+ * Returns a list of IncrementalTransformInput for all the inputs.
+ */
+ @NonNull
+ private static List<IncrementalTransformInput> createIncrementalInputs(
+ @NonNull Collection<TransformStream> streams) {
+ List<IncrementalTransformInput> list = Lists.newArrayListWithCapacity(streams.size());
+
+ for (TransformStream stream : streams) {
+ list.add(stream.asIncrementalInput());
+ }
+
+ return list;
+ }
+
+ private synchronized Collection<SecondaryFile> getAllSecondaryInputs() {
+ if (secondaryFiles == null) {
+ ImmutableList.Builder<SecondaryFile> builder = ImmutableList.builder();
+ builder.addAll(transform.getSecondaryFiles());
+ builder.addAll(Collections2.transform(transform.getSecondaryFileInputs(),
+ new Function<File, SecondaryFile>() {
+ @Override
+ public SecondaryFile apply(File input) {
+ return new SecondaryFile(input, false /* supportsIncrementalChanges */);
+ }
+ }));
+ secondaryFiles = builder.build();
+ }
+ return secondaryFiles;
+ }
+
+ private static void gatherChangedFiles(
+ @NonNull IncrementalTaskInputs incrementalTaskInputs,
+ @NonNull final Map<File, Status> changedFileMap,
+ @NonNull final Set<File> removedFiles) {
+ incrementalTaskInputs.outOfDate(new org.gradle.api.Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ if (inputFileDetails.isAdded()) {
+ changedFileMap.put(inputFileDetails.getFile(), Status.ADDED);
+ } else if (inputFileDetails.isModified()) {
+ changedFileMap.put(inputFileDetails.getFile(), Status.CHANGED);
+ }
+ }
+ });
+
+ incrementalTaskInputs.removed(new org.gradle.api.Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ removedFiles.add(inputFileDetails.getFile());
+ }
+ });
+ }
+
+ private boolean checkSecondaryFiles(
+ @NonNull Map<File, Status> changedMap,
+ @NonNull Set<File> removedFiles) {
+
+ for (SecondaryFile secondaryFile : getAllSecondaryInputs()) {
+ File file = secondaryFile.getFile();
+ if ((changedMap.containsKey(file) || removedFiles.contains(file))
+ && !secondaryFile.supportsIncrementalBuild()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isSecondaryFile(File file) {
+ for (SecondaryFile secondaryFile : getAllSecondaryInputs()) {
+ if (secondaryFile.getFile().equals(file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean updateIncrementalInputsWithChangedFiles(
+ @NonNull List<IncrementalTransformInput> consumedInputs,
+ @NonNull List<IncrementalTransformInput> referencedInputs,
+ @NonNull Map<File, Status> changedFilesMap,
+ @NonNull Set<File> removedFiles) {
+
+ // we're going to concat both list multiple times, and the Iterators API ultimately put
+ // all the iterators to concat in a list. So let's reuse a list.
+ List<Iterator<IncrementalTransformInput>> iterators = Lists.newArrayListWithCapacity(2);
+
+ Splitter splitter = Splitter.on(File.separatorChar);
+
+ // start with the removed files as they carry the risk of removing incremental mode.
+ // If we detect such a case, we stop immediately.
+ for (File removedFile : removedFiles) {
+ List<String> removedFileSegments = Lists.newArrayList(
+ splitter.split(removedFile.getAbsolutePath()));
+
+ Iterator<IncrementalTransformInput> iterator = getConcatIterator(consumedInputs,
+ referencedInputs, iterators);
+
+ boolean found = false;
+ while (iterator.hasNext()) {
+ IncrementalTransformInput next = iterator.next();
+ if (next.checkRemovedJarFile(removedFile, removedFileSegments) ||
+ next.checkRemovedFolderFile(removedFile, removedFileSegments)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found && !isSecondaryFile(removedFile)) {
+ // this deleted file breaks incremental because we cannot figure out where it's
+ // coming from and what types/scopes is associated with it.
+ return false;
+ }
+ }
+
+ // now handle the added/changed files.
+
+ for (Map.Entry<File, Status> entry : changedFilesMap.entrySet()) {
+ File changedFile = entry.getKey();
+ Status changedStatus = entry.getValue();
+
+ // first go through the jars first as it's a faster check.
+ Iterator<IncrementalTransformInput> iterator = getConcatIterator(consumedInputs,
+ referencedInputs, iterators);
+ boolean found = false;
+ while (iterator.hasNext()) {
+ if (iterator.next().checkForJar(changedFile, changedStatus)) {
+ // we can skip to the next changed file.
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ continue;
+ }
+
+ // now go through the folders. First get a segment list for the path.
+ iterator = getConcatIterator(consumedInputs,
+ referencedInputs, iterators);
+ List<String> changedSegments = Lists.newArrayList(
+ splitter.split(changedFile.getAbsolutePath()));
+
+ while (iterator.hasNext()) {
+ if (iterator.next().checkForFolder(changedFile, changedSegments, changedStatus)) {
+ // we can skip to the next changed file.
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @NonNull
+ private static Iterator<IncrementalTransformInput> getConcatIterator(
+ @NonNull List<IncrementalTransformInput> consumedInputs,
+ @NonNull List<IncrementalTransformInput> referencedInputs,
+ List<Iterator<IncrementalTransformInput>> iterators) {
+ iterators.clear();
+ iterators.add(consumedInputs.iterator());
+ iterators.add(referencedInputs.iterator());
+ return Iterators.concat(iterators.iterator());
+ }
+
+ @NonNull
+ private static List<TransformInput> convertToImmutable(
+ @NonNull List<IncrementalTransformInput> inputs) {
+ List<TransformInput> immutableInputs = Lists.newArrayListWithCapacity(inputs.size());
+ for (IncrementalTransformInput input : inputs) {
+ immutableInputs.add(input.asImmutable());
+ }
+
+ return immutableInputs;
+ }
+
+ public interface ConfigActionCallback<T extends Transform> {
+ void callback(@NonNull T transform, @NonNull TransformTask task);
+ }
+
+ public static class ConfigAction<T extends Transform> implements TaskConfigAction<TransformTask> {
+
+ @NonNull
+ private final String variantName;
+ @NonNull
+ private final String taskName;
+ @NonNull
+ private final T transform;
+ @NonNull
+ private Collection<TransformStream> consumedInputStreams;
+ @NonNull
+ private Collection<TransformStream> referencedInputStreams;
+ @Nullable
+ private IntermediateStream outputStream;
+ @Nullable
+ private final ConfigActionCallback<T> configActionCallback;
+
+ ConfigAction(
+ @NonNull String variantName,
+ @NonNull String taskName,
+ @NonNull T transform,
+ @NonNull Collection<TransformStream> consumedInputStreams,
+ @NonNull Collection<TransformStream> referencedInputStreams,
+ @Nullable IntermediateStream outputStream,
+ @Nullable ConfigActionCallback<T> configActionCallback) {
+ this.variantName = variantName;
+ this.taskName = taskName;
+ this.transform = transform;
+ this.consumedInputStreams = consumedInputStreams;
+ this.referencedInputStreams = referencedInputStreams;
+ this.outputStream = outputStream;
+ this.configActionCallback = configActionCallback;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return taskName;
+ }
+
+ @NonNull
+ @Override
+ public Class<TransformTask> getType() {
+ return TransformTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull TransformTask task) {
+ task.transform = transform;
+ task.consumedInputStreams = consumedInputStreams;
+ task.referencedInputStreams = referencedInputStreams;
+ task.outputStream = outputStream;
+ task.setVariantName(variantName);
+
+ if (configActionCallback != null) {
+ configActionCallback.callback(transform, task);
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleJavaProcessExecutor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleJavaProcessExecutor.java
new file mode 100644
index 0000000..63be219
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleJavaProcessExecutor.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.process;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.process.JavaProcessExecutor;
+import com.android.ide.common.process.JavaProcessInfo;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutput;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.ide.common.process.ProcessResult;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.process.ExecResult;
+import org.gradle.process.JavaExecSpec;
+
+import java.io.File;
+
+/**
+ * Implementation of JavaProcessExecutor that uses Gradle's mechanism to execute external java
+ * processes.
+ */
+public class GradleJavaProcessExecutor implements JavaProcessExecutor {
+
+ @NonNull
+ private final Project project;
+
+ public GradleJavaProcessExecutor(@NonNull Project project) {
+ this.project = project;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult execute(
+ @NonNull JavaProcessInfo javaProcessInfo,
+ @NonNull ProcessOutputHandler processOutputHandler) {
+ ProcessOutput output = processOutputHandler.createOutput();
+
+ ExecResult result = project.javaexec(new ExecAction(javaProcessInfo, output));
+
+ try {
+ processOutputHandler.handleOutput(output);
+ } catch (ProcessException e) {
+ return new OutputHandlerFailedGradleProcessResult(e);
+ }
+
+ return new GradleProcessResult(result);
+ }
+
+ private static class ExecAction implements Action<JavaExecSpec> {
+
+ @NonNull
+ private final JavaProcessInfo javaProcessInfo;
+
+ @NonNull
+ private final ProcessOutput processOutput;
+
+ private ExecAction(@NonNull JavaProcessInfo javaProcessInfo,
+ @NonNull ProcessOutput processOutput) {
+ this.javaProcessInfo = javaProcessInfo;
+ this.processOutput = processOutput;
+ }
+
+ @Override
+ public void execute(JavaExecSpec javaExecSpec) {
+ javaExecSpec.classpath(new File(javaProcessInfo.getClasspath()));
+ javaExecSpec.setMain(javaProcessInfo.getMainClass());
+ javaExecSpec.args(javaProcessInfo.getArgs());
+ javaExecSpec.jvmArgs(javaProcessInfo.getJvmArgs());
+ javaExecSpec.environment(javaProcessInfo.getEnvironment());
+ javaExecSpec.setStandardOutput(processOutput.getStandardOutput());
+ javaExecSpec.setErrorOutput(processOutput.getErrorOutput());
+
+ // we run by default in headless mode, so the forked JVM doesn't steal focus.
+ javaExecSpec.systemProperty("java.awt.headless", "true");
+
+ // we want the caller to be able to do its own thing.
+ javaExecSpec.setIgnoreExitValue(true);
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessExecutor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessExecutor.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessExecutor.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessExecutor.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessResult.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessResult.java
new file mode 100644
index 0000000..d0d7417
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/GradleProcessResult.java
@@ -0,0 +1,64 @@
+/*
+ * 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.build.gradle.internal.process;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessResult;
+
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.ExecException;
+
+/**
+ */
+class GradleProcessResult implements ProcessResult {
+
+ @NonNull
+ private final ExecResult result;
+
+ GradleProcessResult(@NonNull ExecResult result) {
+ this.result = result;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult assertNormalExitValue() throws ProcessException {
+ try {
+ result.assertNormalExitValue();
+ } catch (ExecException e) {
+ throw new ProcessException(e);
+ }
+
+ return this;
+ }
+
+ @Override
+ public int getExitValue() {
+ return result.getExitValue();
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult rethrowFailure() throws ProcessException {
+ try {
+ result.rethrowFailure();
+ } catch (ExecException e) {
+ throw new ProcessException(e);
+ }
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/OutputHandlerFailedGradleProcessResult.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/OutputHandlerFailedGradleProcessResult.java
new file mode 100644
index 0000000..cd5dbaf
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/process/OutputHandlerFailedGradleProcessResult.java
@@ -0,0 +1,47 @@
+/*
+ * 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.build.gradle.internal.process;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessResult;
+
+public class OutputHandlerFailedGradleProcessResult implements ProcessResult {
+ @NonNull
+ private final ProcessException failure;
+
+ OutputHandlerFailedGradleProcessResult(@NonNull ProcessException failure) {
+ this.failure = failure;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult assertNormalExitValue() throws ProcessException {
+ throw failure;
+ }
+
+ @Override
+ public int getExitValue() {
+ return -1;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult rethrowFailure() throws ProcessException {
+ throw failure;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/RecordingBuildListener.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/RecordingBuildListener.java
new file mode 100644
index 0000000..f090f69
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/RecordingBuildListener.java
@@ -0,0 +1,107 @@
+
+/*
+ * 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.build.gradle.internal.profile;
+
+import com.android.build.gradle.internal.tasks.DefaultAndroidTask;
+import com.android.builder.profile.ExecutionRecord;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.Recorder;
+import com.google.common.base.CaseFormat;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.tasks.TaskState;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implementation of the {@link TaskExecutionListener} that records the execution span of
+ * tasks execution and records such spans using the {@link Recorder} facilities.
+ */
+public class RecordingBuildListener implements TaskExecutionListener {
+
+ private static final class TaskRecord {
+
+ private final long recordId;
+ private final long startTime;
+
+ TaskRecord(long recordId, long startTime) {
+ this.startTime = startTime;
+ this.recordId = recordId;
+ }
+ }
+
+ private final Recorder mRecorder;
+
+ public RecordingBuildListener(Recorder recorder) {
+ mRecorder = recorder;
+ }
+
+ // map of outstanding tasks executing, keyed by their name.
+ final Map<String, TaskRecord> taskRecords = new ConcurrentHashMap<String, TaskRecord>();
+
+ @Override
+ public void beforeExecute(Task task) {
+ taskRecords.put(task.getName(), new TaskRecord(
+ mRecorder.allocationRecordId(), System.currentTimeMillis()));
+ }
+
+ @Override
+ public void afterExecute(Task task, TaskState taskState) {
+
+ // find the right ExecutionType.
+ String taskImpl = task.getClass().getSimpleName();
+ if (taskImpl.endsWith("_Decorated")) {
+ taskImpl = taskImpl.substring(0, taskImpl.length() - "_Decorated".length());
+ }
+ String potentialExecutionTypeName = "TASK_" +
+ CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, taskImpl);
+ ExecutionType executionType;
+ try {
+ executionType = ExecutionType.valueOf(potentialExecutionTypeName);
+ } catch (IllegalArgumentException ignored) {
+ executionType = ExecutionType.GENERIC_TASK_EXECUTION;
+ }
+
+ List<Recorder.Property> properties = new ArrayList<Recorder.Property>();
+ properties.add(new Recorder.Property("project", task.getProject().getName()));
+ properties.add(new Recorder.Property("task", task.getName()));
+
+ if (task instanceof DefaultAndroidTask) {
+ String variantName = ((DefaultAndroidTask) task).getVariantName();
+ if (variantName == null) {
+ throw new IllegalStateException("Task with type " + task.getClass().getName() +
+ " does not include a variantName");
+ }
+ if (!variantName.isEmpty()) {
+ properties.add(new Recorder.Property("variant", variantName));
+ }
+ }
+
+ TaskRecord taskRecord = taskRecords.get(task.getName());
+ mRecorder.closeRecord(new ExecutionRecord(
+ taskRecord.recordId,
+ 0 /* parentId */,
+ taskRecord.startTime,
+ System.currentTimeMillis() - taskRecord.startTime,
+ executionType,
+ properties));
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/SpanRecorders.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/SpanRecorders.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/SpanRecorders.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/profile/SpanRecorders.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/ApkPublishArtifact.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/ApkPublishArtifact.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/ApkPublishArtifact.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/ApkPublishArtifact.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/BasePublishArtifact.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/BasePublishArtifact.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/BasePublishArtifact.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/BasePublishArtifact.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistence.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistence.java
new file mode 100644
index 0000000..2d84353
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistence.java
@@ -0,0 +1,73 @@
+/*
+ * 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.build.gradle.internal.publishing;
+
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.internal.tasks.SplitFileSupplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Persistence utility to save metadata about split files generated by the build. This metadata
+ * will be the split type, the split identifier and the resulting split APK file name.
+ */
+public class FilterDataPersistence {
+
+ public static class Record {
+ public final String filterType;
+ public final String filterIdentifier;
+ public final String splitFileName;
+
+ private Record(String filterType, String filterIdentifier, String splitFileName) {
+ this.filterType = filterType;
+ this.filterIdentifier = filterIdentifier;
+ this.splitFileName = splitFileName;
+ }
+ }
+
+ public void persist(List<? extends FileSupplier> fileSuppliers, Writer writer) throws IOException {
+ Gson gson = new Gson();
+ ImmutableList.Builder<Record> records = ImmutableList.builder();
+ for (FileSupplier fileSupplier : fileSuppliers) {
+ if (fileSupplier instanceof SplitFileSupplier) {
+ records.add(new Record(
+ ((SplitFileSupplier) fileSupplier).getFilterData().getFilterType(),
+ ((SplitFileSupplier) fileSupplier).getFilterData().getIdentifier(),
+ fileSupplier.get().getName()));
+ }
+ }
+ String recordsAsString = gson.toJson(records.build());
+ try {
+ writer.append(recordsAsString);
+ } finally {
+ writer.close();
+ }
+ }
+
+ public List<Record> load(Reader reader) throws IOException {
+ Gson gson = new Gson();
+ Type recordType = new TypeToken<List<Record>>() {}.getType();
+ return gson.fromJson(reader, recordType);
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MappingPublishArtifact.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MappingPublishArtifact.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MappingPublishArtifact.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MappingPublishArtifact.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MetadataPublishArtifact.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MetadataPublishArtifact.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MetadataPublishArtifact.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/publishing/MetadataPublishArtifact.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTask.java
new file mode 100644
index 0000000..0199407
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTask.java
@@ -0,0 +1,172 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.TaskFactory;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handle for a {@link Task} that may not yet been created.
+ * For tasks created using ModelMap<Task>, they are usually not actually created until
+ * Gradle decides those tasks needs to be executed. This class contains information about those
+ * tasks and allow dependencies to be specified.
+ */
+public class AndroidTask<T extends Task> {
+ @NonNull
+ private String name;
+ @NonNull
+ private final Class<T> taskType;
+ @NonNull
+ private final List<AndroidTask<? extends Task>> upstreamTasks;
+ @NonNull
+ private final List<AndroidTask<? extends Task>> downstreamTasks;
+
+ public AndroidTask(@NonNull String name, @NonNull Class<T> taskType) {
+ this.name = name;
+ this.taskType = taskType;
+ upstreamTasks = new ArrayList<AndroidTask<? extends Task>>();
+ downstreamTasks = new ArrayList<AndroidTask<? extends Task>>();
+ }
+
+ /**
+ * The name of Task this represents.
+ */
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The type of Task this represents.
+ */
+ @NonNull
+ public Class<T> getTaskType() {
+ return taskType;
+ }
+
+ /**
+ * Return all the AndroidTask this depends on.
+ */
+ @NonNull
+ public List<AndroidTask<? extends Task>> getUpstreamTasks() {
+ return upstreamTasks;
+ }
+
+ /**
+ * Return all the AndroidTask that depends on this.
+ */
+ @NonNull
+ public List<AndroidTask<? extends Task>> getDownstreamTasks() {
+ return downstreamTasks;
+ }
+
+ /**
+ * Add dependency on another AndroidTask.
+ * @param taskFactory TaskFactory used to configure the task for dependencies.
+ * @param other The task that this depends on.
+ */
+ public void dependsOn(final TaskFactory taskFactory, final AndroidTask<?> other) {
+ taskFactory.named(name, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.dependsOn(other.name);
+ }
+ });
+ upstreamTasks.add(other);
+ other.addDependent(this);
+ }
+
+ /**
+ * Add dependency on objects.
+ * This method adds dependencies on any objects accepted by {@link Task#dependsOn} and is
+ * needed for compatibility until all tasks are trasitioned to AndroidTask.
+ * @param taskFactory TaskFactory used to configure the task for dependencies.
+ * @param dependencies Objects accepted by {@link Task#dependsOn}.
+ */
+ public void dependsOn(final TaskFactory taskFactory, final Object... dependencies) {
+ taskFactory.named(name, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ for (Object dependency : dependencies) {
+ if (dependency instanceof AndroidTask) {
+ task.dependsOn(((AndroidTask) dependency).getName());
+ } else {
+ task.dependsOn(dependency);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Add dependency on other objects if the object is not null.
+ * This method adds dependencies on any objects accepted by {@link Task#dependsOn} and is
+ * needed for compatibility until all tasks are trasitioned to AndroidTask.
+ * @param taskFactory TaskFactory used to configure the task for dependencies.
+ * @param dependencies Objects accepted by {@link Task#dependsOn}.
+ */
+ public void optionalDependsOn(final TaskFactory taskFactory, final Object... dependencies) {
+ for (Object dependency : dependencies) {
+ if (dependency != null) {
+ if (dependency instanceof AndroidTask) {
+ dependsOn(taskFactory, ((AndroidTask) dependency).getName());
+ } else {
+ dependsOn(taskFactory, dependency);
+ }
+ }
+ }
+ }
+
+
+ public void optionalDependsOn(final TaskFactory taskFactory, @NonNull List<?> dependencies) {
+ for (Object dependency : dependencies) {
+ if (dependency != null) {
+ dependsOn(taskFactory, dependency);
+ }
+ }
+ }
+
+ private void addDependent(AndroidTask<? extends Task> tAndroidTask) {
+ downstreamTasks.add(tAndroidTask);
+ }
+
+ /**
+ * Add a configuration action for this task.
+ * @param taskFactory TaskFactory used to configure the task.
+ * @param configAction An Action to be executed.
+ */
+ public void configure(TaskFactory taskFactory, Action<? super Task> configAction) {
+ taskFactory.named(name, configAction);
+ }
+
+ /**
+ * Potentially instantiates and return the task. Should only be called once the task is
+ * configured.
+ * @param taskFactory the factory for tasks
+ * @return the task instance.
+ */
+ @SuppressWarnings("unchecked")
+ public T get(TaskFactory taskFactory) {
+ return (T) taskFactory.named(name);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTaskRegistry.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTaskRegistry.java
new file mode 100644
index 0000000..d9260c5
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/AndroidTaskRegistry.java
@@ -0,0 +1,83 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.TaskFactory;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.internal.ClosureBackedAction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import groovy.lang.Closure;
+
+/**
+ * Registry for creating and storing AndroidTask.
+ */
+public class AndroidTaskRegistry {
+
+ @NonNull
+ private final Map<String, AndroidTask> tasks = new HashMap<String, AndroidTask>();
+
+ public synchronized < T extends Task> AndroidTask<T> create(
+ @NonNull TaskFactory taskFactory,
+ @NonNull String taskName,
+ @NonNull Class<T> taskClass,
+ Action<T> configAction) {
+
+ taskFactory.create(taskName, taskClass, configAction);
+ final AndroidTask<T> newTask = new AndroidTask<T>(taskName, taskClass);
+ tasks.put(taskName, newTask);
+
+ return newTask;
+ }
+
+ public synchronized AndroidTask<Task> create(
+ TaskFactory taskFactory,
+ String taskName,
+ Closure configAction) {
+
+ taskFactory.create(taskName, DefaultTask.class, new ClosureBackedAction<Task>(configAction));
+ final AndroidTask<Task> newTask = new AndroidTask<Task>(taskName, Task.class);
+ tasks.put(taskName, newTask);
+
+ return newTask;
+ }
+
+ public synchronized <T extends Task> AndroidTask<T> create(
+ TaskFactory taskFactory,
+ String taskName,
+ Class<T> taskClass,
+ Closure configAction) {
+
+ taskFactory.create(taskName, taskClass, new ClosureBackedAction<T>(configAction));
+ final AndroidTask<T> newTask = new AndroidTask<T>(taskName, taskClass);
+ tasks.put(taskName, newTask);
+
+ return newTask;
+ }
+
+ public <T extends Task> AndroidTask<T> create(
+ TaskFactory taskFactory,
+ TaskConfigAction<T> configAction) {
+ return create(taskFactory, configAction.getName(), configAction.getType(), configAction);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/BaseScope.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/BaseScope.java
new file mode 100644
index 0000000..9a283e8
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/BaseScope.java
@@ -0,0 +1,60 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+
+import java.util.Collection;
+
+/**
+ * Base Scope for Variants and Variant Outputs
+ */
+public interface BaseScope {
+
+ @NonNull
+ GlobalScope getGlobalScope();
+
+ @NonNull
+ GradleVariantConfiguration getVariantConfiguration();
+
+ @NonNull
+ String getTaskName(@NonNull String prefix);
+
+ @NonNull
+ String getTaskName(@NonNull String prefix, @NonNull String suffix);
+
+ /**
+ * Returns a unique directory name (can include multiple folders) for the variant,
+ * based on build type, flavor and test.
+ *
+ * <p>This always uses forward slashes ('/') as separator on all platform.
+ *
+ * @return the directory name for the variant
+ */
+ @NonNull
+ String getDirName();
+
+ /**
+ * Returns a unique directory name (can include multiple folders) for the variant,
+ * based on build type, flavor and test.
+ *
+ * @return the directory name for the variant
+ */
+ @NonNull
+ Collection<String> getDirectorySegments();
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/ConventionMappingHelper.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/ConventionMappingHelper.java
new file mode 100644
index 0000000..9ecf853
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/ConventionMappingHelper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.ConventionTask;
+
+import java.util.concurrent.Callable;
+
+import groovy.lang.GroovyObject;
+
+/**
+ * Helper class to dynamically access conventionMapping of a task.
+ */
+public final class ConventionMappingHelper {
+ private ConventionMappingHelper() {}
+
+ public static void map(@NonNull Task task, @NonNull String key, @NonNull Callable<?> value) {
+ if (task instanceof ConventionTask) {
+ ((ConventionTask) task).getConventionMapping().map(key, value);
+ } else if (task instanceof GroovyObject) {
+ ConventionMapping conventionMapping =
+ (ConventionMapping) ((GroovyObject) task).getProperty("conventionMapping");
+ conventionMapping.map(key, value);
+ } else {
+ throw new IllegalArgumentException(
+ "Don't know how to apply convention mapping to task of type " + task.getClass().getName());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/GlobalScope.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/GlobalScope.java
new file mode 100644
index 0000000..cc7c355
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/GlobalScope.java
@@ -0,0 +1,216 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import static com.android.builder.core.BuilderConstants.FD_REPORTS;
+import static com.android.builder.model.AndroidProject.FD_GENERATED;
+import static com.android.builder.model.AndroidProject.FD_LOGS;
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.model.AndroidProject;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.tasks.options.Option;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import java.io.File;
+import java.util.EnumSet;
+
+/**
+ * A scope containing data for the Android plugin.
+ */
+public class GlobalScope {
+
+ @NonNull
+ private Project project;
+
+ @NonNull
+ private AndroidBuilder androidBuilder;
+
+ @NonNull
+ private AndroidConfig extension;
+
+ @NonNull
+ private SdkHandler sdkHandler;
+
+ @NonNull
+ private ToolingModelBuilderRegistry toolingRegistry;
+
+ @NonNull
+ private final File intermediatesDir;
+
+ @NonNull
+ private final File generatedDir;
+
+ @NonNull
+ private final File reportsDir;
+
+ @NonNull
+ private final File outputsDir;
+
+ @Nullable
+ private File mockableAndroidJarFile;
+
+ @NonNull
+ private final EnumSet<OptionalCompilationStep> optionalCompilationSteps;
+
+ public GlobalScope(
+ @NonNull Project project,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull AndroidConfig extension,
+ @NonNull SdkHandler sdkHandler,
+ @NonNull ToolingModelBuilderRegistry toolingRegistry) {
+ this.project = project;
+ this.androidBuilder = androidBuilder;
+ this.extension = extension;
+ this.sdkHandler = sdkHandler;
+ this.toolingRegistry = toolingRegistry;
+ intermediatesDir = new File(getBuildDir(), FD_INTERMEDIATES);
+ generatedDir = new File(getBuildDir(), FD_GENERATED);
+ reportsDir = new File(getBuildDir(), FD_REPORTS);
+ outputsDir = new File(getBuildDir(), FD_OUTPUTS);
+ optionalCompilationSteps = AndroidGradleOptions.getOptionalCompilationSteps(project);
+ }
+
+ @NonNull
+ public Project getProject() {
+ return project;
+ }
+
+ @NonNull
+ public AndroidConfig getExtension() {
+ return extension;
+ }
+
+ @NonNull
+ public AndroidBuilder getAndroidBuilder() {
+ return androidBuilder;
+ }
+
+ @NonNull
+ public String getProjectBaseName() {
+ return (String) project.property("archivesBaseName");
+ }
+
+ @NonNull
+ public SdkHandler getSdkHandler() {
+ return sdkHandler;
+ }
+
+ @NonNull
+ public ToolingModelBuilderRegistry getToolingRegistry() {
+ return toolingRegistry;
+ }
+
+ @NonNull
+ public File getBuildDir() {
+ return project.getBuildDir();
+ }
+
+ @NonNull
+ public File getIntermediatesDir() {
+ return intermediatesDir;
+ }
+
+ @NonNull
+ public File getGeneratedDir() {
+ return generatedDir;
+ }
+
+ @NonNull
+ public File getReportsDir() {
+ return reportsDir;
+ }
+
+ public File getTestResultsFolder() {
+ return new File(getBuildDir(), "test-results");
+ }
+
+ public File getTestReportFolder() {
+ return new File(getBuildDir(), "reports/tests");
+ }
+
+ @NonNull
+ public File getMockableAndroidJarFile() {
+
+ if (mockableAndroidJarFile == null) {
+ // Since the file ends up in $rootProject.buildDir, it will survive clean
+ // operations - projects generated by AS don't have a top-level clean task that
+ // would delete the top-level build directory. This means that the name has to
+ // encode all the necessary information, otherwise the task will be UP-TO-DATE
+ // even if the file should be regenerated. That's why we put the SDK version and
+ // "default-values" in there, so if one project uses the returnDefaultValues flag,
+ // it will just generate a new file and not change the semantics for other
+ // sub-projects. There's an implicit "v1" there as well, if we ever change the
+ // generator logic, the names will have to be changed.
+ String fileExt;
+ if (getExtension().getTestOptions().getUnitTests().isReturnDefaultValues()) {
+ fileExt = ".default-values.jar";
+ } else {
+ fileExt = ".jar";
+ }
+ File outDir = new File(
+ getProject().getRootProject().getBuildDir(),
+ AndroidProject.FD_GENERATED);
+
+ CharMatcher safeCharacters =
+ CharMatcher.JAVA_LETTER_OR_DIGIT.or(CharMatcher.anyOf("-."));
+ String sdkName =
+ safeCharacters.negate().replaceFrom(
+ getExtension().getCompileSdkVersion(), '-');
+ mockableAndroidJarFile = new File(outDir, "mockable-" + sdkName + fileExt);
+ }
+ return mockableAndroidJarFile;
+ }
+
+ @NonNull
+ public File getOutputsDir() {
+ return outputsDir;
+ }
+
+ @NonNull
+ public String getDefaultApkLocation() {
+ return getBuildDir() + "/" + FD_OUTPUTS + "/apk";
+ }
+
+ @NonNull
+ public String getApkLocation() {
+ return Objects.firstNonNull(
+ AndroidGradleOptions.getApkLocation(project),
+ getDefaultApkLocation());
+ }
+
+ public boolean isActive(OptionalCompilationStep step) {
+ return optionalCompilationSteps.contains(step);
+ }
+
+ @NonNull
+ public String getArchivesBaseName() {
+ return (String)getProject().getProperties().get("archivesBaseName");
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/TaskConfigAction.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/TaskConfigAction.java
new file mode 100644
index 0000000..b70d8e3
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/TaskConfigAction.java
@@ -0,0 +1,45 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.api.Action;
+
+/**
+ * Interface of Task configuration Actions.
+ */
+public interface TaskConfigAction<T> extends Action<T> {
+
+ /**
+ * Return the name of the task to be configured.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Return the class type of the task to be configured.
+ */
+ @NonNull
+ Class<T> getType();
+
+ /**
+ * Configure the given newly-created task object.
+ */
+ @Override
+ void execute(@NonNull T task);
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantOutputScope.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantOutputScope.java
new file mode 100644
index 0000000..67cadf9
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantOutputScope.java
@@ -0,0 +1,220 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.tasks.CompatibleScreensManifest;
+import com.android.build.gradle.tasks.ManifestProcessorTask;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.SplitZipAlign;
+import com.android.utils.StringHelper;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A scope containing data for a specific variant.
+ */
+public class VariantOutputScope implements BaseScope {
+
+ @NonNull
+ private VariantScope variantScope;
+ @NonNull
+ private BaseVariantOutputData variantOutputData;
+
+ // Tasks
+ private AndroidTask<CompatibleScreensManifest> compatibleScreensManifestTask;
+
+ private AndroidTask<? extends ManifestProcessorTask> manifestProcessorTask;
+
+ private AndroidTask<ProcessAndroidResources> processResourcesTask;
+
+ private AndroidTask<?> shrinkResourcesTask;
+
+ private AndroidTask<SplitZipAlign> splitZipAlignTask;
+
+ public VariantOutputScope(
+ @NonNull VariantScope variantScope,
+ @NonNull BaseVariantOutputData variantOutputData) {
+ this.variantScope = variantScope;
+ this.variantOutputData = variantOutputData;
+ }
+
+ @Override
+ @NonNull
+ public GlobalScope getGlobalScope() {
+ return variantScope.getGlobalScope();
+ }
+
+ @NonNull
+ public VariantScope getVariantScope() {
+ return variantScope;
+ }
+
+ @NonNull
+ public BaseVariantOutputData getVariantOutputData() {
+ return variantOutputData;
+ }
+
+ @NonNull
+ @Override
+ public GradleVariantConfiguration getVariantConfiguration() {
+ return variantScope.getVariantConfiguration();
+ }
+
+ @NonNull
+ @Override
+ public String getDirName() {
+ // this is here as a safety net in the Transform manager which handles either VariantScope
+ // or VariantOutputScope. Should this ever be called we'll need to compute this properly.
+ throw new UnsupportedOperationException("dir name per output scope not yet supported");
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getDirectorySegments() {
+ // this is here as a safety net in the Transform manager which handles either VariantScope
+ // or VariantOutputScope. Should this ever be called we'll need to compute this properly.
+ throw new UnsupportedOperationException("dir name per output scope not yet supported");
+ }
+
+ @Override
+ @NonNull
+ public String getTaskName(@NonNull String prefix) {
+ return getTaskName(prefix, "");
+ }
+
+ @Override
+ @NonNull
+ public String getTaskName(@NonNull String prefix, @NonNull String suffix) {
+ return prefix + StringHelper.capitalize(getVariantOutputData().getFullName()) + suffix;
+ }
+
+ @NonNull
+ public File getPackageApk() {
+ ApkVariantData apkVariantData = (ApkVariantData) variantScope.getVariantData();
+
+ boolean signedApk = apkVariantData.isSigned();
+ String apkName = signedApk ?
+ getGlobalScope().getProjectBaseName() + "-" + variantOutputData.getBaseName() + "-unaligned.apk" :
+ getGlobalScope().getProjectBaseName() + "-" + variantOutputData.getBaseName() + "-unsigned.apk";
+
+ // if this is the final task then the location is
+ // the potentially overridden one.
+ if (!signedApk || !apkVariantData.getZipAlignEnabled()) {
+ return getGlobalScope().getProject().file(
+ getGlobalScope().getApkLocation() + "/" + apkName);
+ } else {
+ // otherwise default one.
+ return getGlobalScope().getProject().file(getGlobalScope().getDefaultApkLocation() + "/" + apkName);
+ }
+ }
+
+ @NonNull
+ public File getCompressedResourceFile() {
+ return new File(getGlobalScope().getIntermediatesDir(), "/res/" +
+ "resources-" + variantOutputData.getBaseName() + "-stripped.ap_");
+ }
+
+ @NonNull
+ public File getCompatibleScreensManifestFile() {
+ return new File(getGlobalScope().getIntermediatesDir(),
+ "/manifests/density/" + variantOutputData.getDirName() + "/AndroidManifest.xml");
+
+ }
+
+ @NonNull
+ public File getManifestOutputFile() {
+ switch(variantScope.getVariantConfiguration().getType()) {
+ case DEFAULT:
+ return new File(getGlobalScope().getIntermediatesDir(),
+ "/manifests/full/" + variantOutputData.getDirName()
+ + "/AndroidManifest.xml");
+ case LIBRARY:
+ return new File(getGlobalScope().getIntermediatesDir(),
+ TaskManager.DIR_BUNDLES + "/"
+ + getVariantScope().getVariantConfiguration().getDirName()
+ + "/AndroidManifest.xml");
+ case ANDROID_TEST:
+ return new File(getGlobalScope().getIntermediatesDir(),
+ "manifest/" + variantScope.getVariantConfiguration().getDirName()
+ + "/AndroidManifest.xml");
+ default:
+ throw new RuntimeException(
+ "getManifestOutputFile called for an unexpected variant.");
+ }
+ }
+
+ @NonNull
+ public File getProcessResourcePackageOutputFile() {
+ return new File(getGlobalScope().getIntermediatesDir(),
+ "res/resources-" + variantOutputData.getBaseName() + ".ap_");
+ }
+
+ // Tasks
+ @Nullable
+ public AndroidTask<CompatibleScreensManifest> getCompatibleScreensManifestTask() {
+ return compatibleScreensManifestTask;
+ }
+
+ public void setCompatibleScreensManifestTask(
+ @Nullable AndroidTask<CompatibleScreensManifest> compatibleScreensManifestTask) {
+ this.compatibleScreensManifestTask = compatibleScreensManifestTask;
+ }
+
+ public AndroidTask<? extends ManifestProcessorTask> getManifestProcessorTask() {
+ return manifestProcessorTask;
+ }
+
+ public void setManifestProcessorTask(
+ AndroidTask<? extends ManifestProcessorTask> manifestProcessorTask) {
+ this.manifestProcessorTask = manifestProcessorTask;
+ }
+
+ public AndroidTask<ProcessAndroidResources> getProcessResourcesTask() {
+ return processResourcesTask;
+ }
+
+ public void setProcessResourcesTask(
+ AndroidTask<ProcessAndroidResources> processResourcesTask) {
+ this.processResourcesTask = processResourcesTask;
+ }
+
+ public AndroidTask<?> getShrinkResourcesTask() {
+ return shrinkResourcesTask;
+ }
+
+ public void setShrinkResourcesTask(
+ AndroidTask<?> shrinkResourcesTask) {
+ this.shrinkResourcesTask = shrinkResourcesTask;
+ }
+
+ public AndroidTask<SplitZipAlign> getSplitZipAlignTask() {
+ return splitZipAlignTask;
+ }
+
+ public void setSplitZipAlignTask(
+ AndroidTask<SplitZipAlign> splitZipAlignTask) {
+ this.splitZipAlignTask = splitZipAlignTask;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScope.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScope.java
new file mode 100644
index 0000000..dea16a9
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScope.java
@@ -0,0 +1,382 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.incremental.InstantRunAnchorTask;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunWrapperTask;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.pipeline.TransformTask;
+import com.android.build.gradle.internal.tasks.CheckManifest;
+import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
+import com.android.build.gradle.internal.tasks.databinding.DataBindingExportBuildInfoTask;
+import com.android.build.gradle.internal.tasks.databinding.DataBindingProcessLayoutsTask;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.GenerateResValues;
+import com.android.build.gradle.tasks.JackTask;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.MergeSourceSetFolders;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A scope containing data for a specific variant.
+ */
+public interface VariantScope extends BaseScope {
+
+ @NonNull
+ BaseVariantData<? extends BaseVariantOutputData> getVariantData();
+
+ @NonNull
+ TransformManager getTransformManager();
+
+ @Nullable
+ Collection<Object> getNdkBuildable();
+
+ void setNdkBuildable(@NonNull Collection<Object> ndkBuildable);
+
+ @Nullable
+ Collection<File> getNdkSoFolder();
+
+ void setNdkSoFolder(@NonNull Collection<File> ndkSoFolder);
+
+ @Nullable
+ File getNdkObjFolder();
+
+ void setNdkObjFolder(@NonNull File ndkObjFolder);
+
+ @Nullable
+ File getNdkDebuggableLibraryFolders(@NonNull Abi abi);
+
+ void addNdkDebuggableLibraryFolders(@NonNull Abi abi, @NonNull File searchPath);
+
+ @NonNull
+ File getDexOutputFolder();
+
+ @Nullable
+ BaseVariantData getTestedVariantData();
+
+ @NonNull
+ File getReloadDexOutputFolder();
+
+ @NonNull
+ File getRestartDexOutputFolder();
+
+ @NonNull
+ File getInstantRunSplitApkOutputFolder();
+
+ @NonNull
+ File getInstantRunPastIterationsFolder();
+
+ @NonNull
+ FileCollection getJavaClasspath();
+
+ @NonNull
+ File getJavaOutputDir();
+
+ @NonNull
+ File getInstantRunSupportDir();
+
+ @NonNull
+ File getInstantRunSliceSupportDir();
+
+ @NonNull
+ File getIncrementalRuntimeSupportJar();
+
+ @NonNull
+ File getIncrementalApplicationSupportDir();
+
+ @NonNull
+ File getIncrementalVerifierDir();
+
+ @NonNull
+ Iterable<File> getJavaOuptuts();
+
+ @NonNull
+ File getJavaDependencyCache();
+
+ @NonNull
+ File getPreDexOutputDir();
+
+ @NonNull
+ File getProguardOutputFile();
+
+ @NonNull
+ File getProguardComponentsJarFile();
+
+ @NonNull
+ File getJarMergingOutputFile();
+
+ @NonNull
+ File getManifestKeepListFile();
+
+ @NonNull
+ File getMainDexListFile();
+
+ @NonNull
+ File getRenderscriptSourceOutputDir();
+
+ @NonNull
+ File getRenderscriptLibOutputDir();
+
+ @NonNull
+ File getSymbolLocation();
+
+ @NonNull
+ File getFinalResourcesDir();
+
+ void setResourceOutputDir(@NonNull File resourceOutputDir);
+
+ @NonNull
+ File getDefaultMergeResourcesOutputDir();
+
+ @NonNull
+ File getMergeResourcesOutputDir();
+
+ void setMergeResourceOutputDir(@Nullable File mergeResourceOutputDir);
+
+ @NonNull
+ File getResourceBlameLogDir();
+
+ @NonNull
+ File getMergeAssetsOutputDir();
+
+ @NonNull
+ File getMergeNativeLibsOutputDir();
+
+ @NonNull
+ File getBuildConfigSourceOutputDir();
+
+ @NonNull
+ File getGeneratedResOutputDir();
+
+ @NonNull
+ File getGeneratedPngsOutputDir();
+
+ @NonNull
+ File getRenderscriptResOutputDir();
+
+ @NonNull
+ File getPackagedJarsJavaResDestinationDir();
+
+ @NonNull
+ File getSourceFoldersJavaResDestinationDir();
+
+ @NonNull
+ File getJavaResourcesDestinationDir();
+
+ @NonNull
+ File getRClassSourceOutputDir();
+
+ @NonNull
+ File getAidlSourceOutputDir();
+
+ @NonNull
+ File getPackagedAidlDir();
+
+ /**
+ * Returns a place to store incremental build data. The {@code name} argument has to be unique
+ * per task, ideally generated with {@link TaskConfigAction#getName()}.
+ */
+ @NonNull
+ File getIncrementalDir(String name);
+
+ @NonNull
+ File getJillPackagedLibrariesDir();
+
+ @NonNull
+ File getJillRuntimeLibrariesDir();
+
+ @NonNull
+ File getJackDestinationDir();
+
+ @NonNull
+ File getJackClassesZip();
+
+ @NonNull
+ File getClassOutputForDataBinding();
+
+ @NonNull
+ File getLayoutInfoOutputForDataBinding();
+
+ @NonNull
+ File getLayoutFolderOutputForDataBinding();
+
+ @NonNull
+ File getGeneratedClassListOutputFileForDataBinding();
+
+ @NonNull
+ File getProguardOutputFolder();
+
+ @NonNull
+ File getProcessAndroidResourcesProguardOutputFile();
+
+ File getMappingFile();
+
+ @NonNull
+ File getGenerateSplitAbiResOutputDirectory();
+
+ @NonNull
+ File getSplitOutputDirectory();
+
+ @NonNull
+ List<File> getSplitAbiResOutputFiles();
+
+ @NonNull
+ List<File> getPackageSplitAbiOutputFiles();
+
+ @NonNull
+ File getAaptFriendlyManifestOutputFile();
+
+ @NonNull
+ File getInstantRunManifestOutputFile();
+
+ @NonNull
+ File getManifestReportFile();
+
+ AndroidTask<Task> getPreBuildTask();
+
+ void setPreBuildTask(AndroidTask<Task> preBuildTask);
+
+ AndroidTask<PrepareDependenciesTask> getPrepareDependenciesTask();
+
+ void setPrepareDependenciesTask(AndroidTask<PrepareDependenciesTask> prepareDependenciesTask);
+
+ AndroidTask<ProcessAndroidResources> getGenerateRClassTask();
+
+ void setGenerateRClassTask(AndroidTask<ProcessAndroidResources> generateRClassTask);
+
+ AndroidTask<Task> getSourceGenTask();
+
+ void setSourceGenTask(AndroidTask<Task> sourceGenTask);
+
+ AndroidTask<Task> getResourceGenTask();
+
+ void setResourceGenTask(AndroidTask<Task> resourceGenTask);
+
+ AndroidTask<Task> getAssetGenTask();
+
+ void setAssetGenTask(AndroidTask<Task> assetGenTask);
+
+ AndroidTask<CheckManifest> getCheckManifestTask();
+
+ void setCheckManifestTask(AndroidTask<CheckManifest> checkManifestTask);
+
+ AndroidTask<RenderscriptCompile> getRenderscriptCompileTask();
+
+ void setRenderscriptCompileTask(AndroidTask<RenderscriptCompile> renderscriptCompileTask);
+
+ AndroidTask<AidlCompile> getAidlCompileTask();
+
+ void setAidlCompileTask(AndroidTask<AidlCompile> aidlCompileTask);
+
+ @Nullable
+ AndroidTask<MergeResources> getMergeResourcesTask();
+
+ void setMergeResourcesTask(@Nullable AndroidTask<MergeResources> mergeResourcesTask);
+
+ @Nullable
+ AndroidTask<MergeSourceSetFolders> getMergeAssetsTask();
+
+ void setMergeAssetsTask(@Nullable AndroidTask<MergeSourceSetFolders> mergeAssetsTask);
+
+ @Nullable
+ AndroidTask<MergeSourceSetFolders> getMergeJniLibFoldersTask();
+
+ void setMergeJniLibFoldersTask(@Nullable AndroidTask<MergeSourceSetFolders> mergeJniLibsTask);
+
+ AndroidTask<GenerateBuildConfig> getGenerateBuildConfigTask();
+
+ void setGenerateBuildConfigTask(AndroidTask<GenerateBuildConfig> generateBuildConfigTask);
+
+ AndroidTask<GenerateResValues> getGenerateResValuesTask();
+
+ void setGenerateResValuesTask(AndroidTask<GenerateResValues> generateResValuesTask);
+
+ @Nullable
+ AndroidTask<DataBindingExportBuildInfoTask> getDataBindingExportInfoTask();
+
+ void setDataBindingExportInfoTask(
+ @Nullable AndroidTask<DataBindingExportBuildInfoTask> dataBindingExportInfoTask);
+
+ @Nullable
+ AndroidTask<DataBindingProcessLayoutsTask> getDataBindingProcessLayoutsTask();
+
+ void setDataBindingProcessLayoutsTask(
+ @Nullable AndroidTask<DataBindingProcessLayoutsTask> dataBindingProcessLayoutsTask);
+
+ AndroidTask<Sync> getProcessJavaResourcesTask();
+
+ void setProcessJavaResourcesTask(AndroidTask<Sync> processJavaResourcesTask);
+
+ void setMergeJavaResourcesTask(AndroidTask<TransformTask> mergeJavaResourcesTask);
+
+ AndroidTask<TransformTask> getMergeJavaResourcesTask();
+
+ @Nullable
+ AndroidTask<? extends AbstractCompile> getJavaCompilerTask();
+
+ @Nullable
+ AndroidTask<JackTask> getJackTask();
+
+ void setJackTask(@Nullable AndroidTask<JackTask> jackTask);
+
+ @Nullable
+ AndroidTask<? extends JavaCompile> getJavacTask();
+
+ void setJavacTask(@Nullable AndroidTask<? extends JavaCompile> javacTask);
+
+ void setJavaCompilerTask(@NonNull AndroidTask<? extends AbstractCompile> javaCompileTask);
+
+ AndroidTask<Task> getCompileTask();
+
+ void setCompileTask(AndroidTask<Task> compileTask);
+
+ AndroidTask<?> getCoverageReportTask();
+
+ void setCoverageReportTask(AndroidTask<?> coverageReportTask);
+
+ @NonNull
+ InstantRunBuildContext getInstantRunBuildContext();
+
+ @NonNull
+ AndroidTask<InstantRunAnchorTask> getInstantRunAnchorTask();
+ void setInstantRunAnchorTask(@NonNull AndroidTask<InstantRunAnchorTask> instantRunTask);
+
+ @NonNull
+ AndroidTask<InstantRunWrapperTask> getInstantRunIncrementalTask();
+ void setInstantRunIncrementalTask(@NonNull AndroidTask<InstantRunWrapperTask> instantRunTask);
+
+ AndroidTask<TransformTask> getInstantRunVerifierTask();
+ void setInstantRunVerifierTask(AndroidTask<TransformTask> verifierTask);
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScopeImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScopeImpl.java
new file mode 100644
index 0000000..51c1f5f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/scope/VariantScopeImpl.java
@@ -0,0 +1,1040 @@
+/*
+ * 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.build.gradle.internal.scope;
+
+import static com.android.build.gradle.internal.TaskManager.DIR_BUNDLES;
+import static com.android.builder.model.AndroidProject.FD_GENERATED;
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.coverage.JacocoReportTask;
+import com.android.build.gradle.internal.incremental.InstantRunAnchorTask;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.dsl.AbiSplitOptions;
+import com.android.build.gradle.internal.incremental.InstantRunWrapperTask;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.pipeline.TransformTask;
+import com.android.build.gradle.internal.tasks.CheckManifest;
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
+import com.android.build.gradle.internal.tasks.databinding.DataBindingExportBuildInfoTask;
+import com.android.build.gradle.internal.tasks.databinding.DataBindingProcessLayoutsTask;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.internal.variant.LibraryVariantData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.BinaryFileProviderTask;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.GenerateResValues;
+import com.android.build.gradle.tasks.JackTask;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.MergeSourceSetFolders;
+import com.android.build.gradle.tasks.NdkCompile;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.builder.core.VariantType;
+import com.android.utils.FileUtils;
+import com.android.utils.StringHelper;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A scope containing data for a specific variant.
+ */
+public class VariantScopeImpl implements VariantScope {
+
+ @NonNull
+ private GlobalScope globalScope;
+ @NonNull
+ private BaseVariantData<? extends BaseVariantOutputData> variantData;
+ @NonNull
+ private TransformManager transformManager;
+ @Nullable
+ private Collection<Object> ndkBuildable;
+ @Nullable
+ private Collection<File> ndkSoFolder;
+ @Nullable
+ private File ndkObjFolder;
+ @NonNull
+ private Map<Abi, File> ndkDebuggableLibraryFolders = Maps.newHashMap();
+
+ @Nullable
+ private File mergeResourceOutputDir;
+
+ // Tasks
+ private AndroidTask<Task> preBuildTask;
+ private AndroidTask<PrepareDependenciesTask> prepareDependenciesTask;
+ private AndroidTask<ProcessAndroidResources> generateRClassTask;
+
+ private AndroidTask<Task> sourceGenTask;
+ private AndroidTask<Task> resourceGenTask;
+ private AndroidTask<Task> assetGenTask;
+ private AndroidTask<CheckManifest> checkManifestTask;
+
+ private AndroidTask<RenderscriptCompile> renderscriptCompileTask;
+ private AndroidTask<AidlCompile> aidlCompileTask;
+ @Nullable
+ private AndroidTask<MergeResources> mergeResourcesTask;
+ @Nullable
+ private AndroidTask<MergeSourceSetFolders> mergeAssetsTask;
+ private AndroidTask<GenerateBuildConfig> generateBuildConfigTask;
+ private AndroidTask<GenerateResValues> generateResValuesTask;
+
+ private AndroidTask<Sync> processJavaResourcesTask;
+ private AndroidTask<TransformTask> mergeJavaResourcesTask;
+
+ private AndroidTask<MergeSourceSetFolders> mergeJniLibsFolderTask;
+
+ private AndroidTask<NdkCompile> ndkCompileTask;
+
+ @Nullable
+ private AndroidTask<DataBindingExportBuildInfoTask> dataBindingExportInfoTask;
+ @Nullable
+ private AndroidTask<DataBindingProcessLayoutsTask> dataBindingProcessLayoutsTask;
+
+ /** @see BaseVariantData#javaCompilerTask */
+ @Nullable
+ private AndroidTask<? extends AbstractCompile> javaCompilerTask;
+ @Nullable
+ private AndroidTask<? extends JavaCompile> javacTask;
+ @Nullable
+ private AndroidTask<JackTask> jackTask;
+
+ // empty anchor compile task to set all compilations tasks as dependents.
+ private AndroidTask<Task> compileTask;
+
+ /**
+ * This is an instance of {@link JacocoReportTask} in android test variants, an umbrella
+ * {@link Task} in app and lib variants and null in unit test variants.
+ */
+ private AndroidTask<?> coverageReportTask;
+
+ private FileSupplier mappingFileProviderTask;
+ private AndroidTask<BinaryFileProviderTask> binayFileProviderTask;
+
+ private File resourceOutputDir;
+
+
+ public VariantScopeImpl(
+ @NonNull GlobalScope globalScope,
+ @NonNull TransformManager transformManager,
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ this.globalScope = globalScope;
+ this.transformManager = transformManager;
+ this.variantData = variantData;
+ }
+
+ @Override
+ @NonNull
+ public GlobalScope getGlobalScope() {
+ return globalScope;
+ }
+
+ @Override
+ @NonNull
+ public BaseVariantData<? extends BaseVariantOutputData> getVariantData() {
+ return variantData;
+ }
+
+ @Override
+ @NonNull
+ public GradleVariantConfiguration getVariantConfiguration() {
+ return variantData.getVariantConfiguration();
+ }
+
+ @NonNull
+ @Override
+ public String getDirName() {
+ return variantData.getVariantConfiguration().getDirName();
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getDirectorySegments() {
+ return variantData.getVariantConfiguration().getDirectorySegments();
+ }
+
+ @NonNull
+ @Override
+ public TransformManager getTransformManager() {
+ return transformManager;
+ }
+
+ @Override
+ @NonNull
+ public String getTaskName(@NonNull String prefix) {
+ return getTaskName(prefix, "");
+ }
+
+ @Override
+ @NonNull
+ public String getTaskName(@NonNull String prefix, @NonNull String suffix) {
+ return prefix + StringHelper.capitalize(getVariantConfiguration().getFullName()) + suffix;
+ }
+
+ @Override
+ @Nullable
+ public Collection<Object> getNdkBuildable() {
+ return ndkBuildable;
+ }
+
+ @Override
+ public void setNdkBuildable(@NonNull Collection<Object> ndkBuildable) {
+ this.ndkBuildable = ndkBuildable;
+ }
+
+ @Nullable
+ @Override
+ public AndroidTask<DataBindingExportBuildInfoTask> getDataBindingExportInfoTask() {
+ return dataBindingExportInfoTask;
+ }
+
+ @Override
+ public void setDataBindingExportInfoTask(
+ @Nullable AndroidTask<DataBindingExportBuildInfoTask> dataBindingExportInfoTask) {
+ this.dataBindingExportInfoTask = dataBindingExportInfoTask;
+ }
+
+ @Nullable
+ @Override
+ public AndroidTask<DataBindingProcessLayoutsTask> getDataBindingProcessLayoutsTask() {
+ return dataBindingProcessLayoutsTask;
+ }
+
+ @Override
+ public void setDataBindingProcessLayoutsTask(
+ @Nullable AndroidTask<DataBindingProcessLayoutsTask> dataBindingProcessLayoutsTask) {
+ this.dataBindingProcessLayoutsTask = dataBindingProcessLayoutsTask;
+ }
+
+ @Override
+ @Nullable
+ public Collection<File> getNdkSoFolder() {
+ return ndkSoFolder;
+ }
+
+ @Override
+ public void setNdkSoFolder(@NonNull Collection<File> ndkSoFolder) {
+ this.ndkSoFolder = ndkSoFolder;
+ }
+
+ @Override
+ @Nullable
+ public File getNdkObjFolder() {
+ return ndkObjFolder;
+ }
+
+ @Override
+ public void setNdkObjFolder(@NonNull File ndkObjFolder) {
+ this.ndkObjFolder = ndkObjFolder;
+ }
+
+ /**
+ * Return the folder containing the shared object with debugging symbol for the specified ABI.
+ */
+ @Override
+ @Nullable
+ public File getNdkDebuggableLibraryFolders(@NonNull Abi abi) {
+ return ndkDebuggableLibraryFolders.get(abi);
+ }
+
+ @Override
+ public void addNdkDebuggableLibraryFolders(@NonNull Abi abi, @NonNull File searchPath) {
+ this.ndkDebuggableLibraryFolders.put(abi, searchPath);
+ }
+
+ @Override
+ @Nullable
+ public BaseVariantData getTestedVariantData() {
+ return variantData instanceof TestVariantData ?
+ (BaseVariantData) ((TestVariantData) variantData).getTestedVariantData() :
+ null;
+ }
+
+ @NonNull
+ @Override
+ public File getDexOutputFolder() {
+ return new File(globalScope.getIntermediatesDir(), "/dex/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getReloadDexOutputFolder() {
+ return new File(globalScope.getIntermediatesDir(), "/reload-dex/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getRestartDexOutputFolder() {
+ return new File(globalScope.getIntermediatesDir(), "/restart-dex/" + getVariantConfiguration().getDirName());
+ }
+
+ @NonNull
+ @Override
+ public File getInstantRunSplitApkOutputFolder() {
+ return new File(globalScope.getIntermediatesDir(), "/split-apk/" + getVariantConfiguration().getDirName());
+ }
+
+ @NonNull
+ @Override
+ public File getInstantRunPastIterationsFolder() {
+ return new File(globalScope.getIntermediatesDir(), "/builds/" + getVariantConfiguration().getDirName());
+ }
+
+ // Precomputed file paths.
+
+ @Override
+ @NonNull
+ public FileCollection getJavaClasspath() {
+ return getGlobalScope().getProject().files(
+ getGlobalScope().getAndroidBuilder().getCompileClasspath(
+ getVariantData().getVariantConfiguration()));
+ }
+
+ @Override
+ @NonNull
+ public File getJavaOutputDir() {
+ return new File(globalScope.getIntermediatesDir(), "/classes/" +
+ variantData.getVariantConfiguration().getDirName());
+ }
+
+ @NonNull
+ @Override
+ public File getInstantRunSupportDir() {
+ return new File(globalScope.getIntermediatesDir(), "/instant-run-support/" +
+ variantData.getVariantConfiguration().getDirName());
+ }
+
+ @NonNull
+ @Override
+ public File getInstantRunSliceSupportDir() {
+ return new File(globalScope.getIntermediatesDir(), "/instant-run-slices/" +
+ variantData.getVariantConfiguration().getDirName());
+ }
+
+ @NonNull
+ @Override
+ public File getIncrementalRuntimeSupportJar() {
+ return new File(globalScope.getIntermediatesDir(), "/incremental-runtime-classes/" +
+ variantData.getVariantConfiguration().getDirName() + "/instant-run.jar");
+ }
+
+ @Override
+ @NonNull
+ public File getIncrementalApplicationSupportDir() {
+ return new File(globalScope.getIntermediatesDir(), "/incremental-classes/" +
+ variantData.getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getIncrementalVerifierDir() {
+ return new File(globalScope.getIntermediatesDir(), "/incremental-verifier/" +
+ variantData.getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public Iterable<File> getJavaOuptuts() {
+ return Iterables.concat(
+ getJavaClasspath(),
+ ImmutableList.of(
+ getJavaOutputDir(),
+ getJavaDependencyCache()));
+ }
+
+ @Override
+ @NonNull
+ public File getJavaDependencyCache() {
+ return new File(globalScope.getIntermediatesDir(), "/dependency-cache/" +
+ variantData.getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getPreDexOutputDir() {
+ return new File(globalScope.getIntermediatesDir(), "/pre-dexed/" +
+ getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getProguardOutputFile() {
+ return (variantData instanceof LibraryVariantData) ?
+ new File(globalScope.getIntermediatesDir(),
+ DIR_BUNDLES + "/" + getVariantConfiguration().getDirName()
+ + "/classes.jar") :
+ new File(globalScope.getIntermediatesDir(),
+ "/classes-proguard/" + getVariantConfiguration().getDirName()
+ + "/classes.jar");
+ }
+
+ @Override
+ @NonNull
+ public File getProguardComponentsJarFile() {
+ return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
+ + "/componentClasses.jar");
+ }
+
+ @Override
+ @NonNull
+ public File getJarMergingOutputFile() {
+ return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
+ + "/allclasses.jar");
+ }
+
+ @Override
+ @NonNull
+ public File getManifestKeepListFile() {
+ return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
+ + "/manifest_keep.txt");
+ }
+
+ @Override
+ @NonNull
+ public File getMainDexListFile() {
+ return new File(globalScope.getIntermediatesDir(), "multi-dex/" + getVariantConfiguration().getDirName()
+ + "/maindexlist.txt");
+ }
+
+ @Override
+ @NonNull
+ public File getRenderscriptSourceOutputDir() {
+ return new File(globalScope.getGeneratedDir(),
+ "source/rs/" + variantData.getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getRenderscriptLibOutputDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "rs/" + variantData.getVariantConfiguration().getDirName() + "/lib");
+ }
+
+ @Override
+ @NonNull
+ public File getSymbolLocation() {
+ return new File(globalScope.getIntermediatesDir() + "/symbols/" +
+ variantData.getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getFinalResourcesDir() {
+ return Objects.firstNonNull(resourceOutputDir, getDefaultMergeResourcesOutputDir());
+ }
+
+ @Override
+ public void setResourceOutputDir(@NonNull File resourceOutputDir) {
+ this.resourceOutputDir = resourceOutputDir;
+ }
+
+ @Override
+ @NonNull
+ public File getDefaultMergeResourcesOutputDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "/res/merged/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getMergeResourcesOutputDir() {
+ if (mergeResourceOutputDir == null) {
+ return getDefaultMergeResourcesOutputDir();
+ }
+ return mergeResourceOutputDir;
+ }
+
+ @Override
+ public void setMergeResourceOutputDir(@Nullable File mergeResourceOutputDir) {
+ this.mergeResourceOutputDir = mergeResourceOutputDir;
+ }
+
+ @NonNull
+ @Override
+ public File getResourceBlameLogDir() {
+ return FileUtils.join(
+ globalScope.getIntermediatesDir(),
+ StringHelper.toStrings(
+ "blame", "res", getDirectorySegments()));
+ }
+
+ @Override
+ @NonNull
+ public File getMergeAssetsOutputDir() {
+ return getVariantConfiguration().getType() == VariantType.LIBRARY ?
+ new File(globalScope.getIntermediatesDir(),
+ DIR_BUNDLES + "/" + getVariantConfiguration().getDirName() +
+ "/assets") :
+ new File(globalScope.getIntermediatesDir(),
+ "/assets/" + getVariantConfiguration().getDirName());
+ }
+
+ @NonNull
+ @Override
+ public File getMergeNativeLibsOutputDir() {
+ return FileUtils.join(globalScope.getIntermediatesDir(),
+ "/jniLibs/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getBuildConfigSourceOutputDir() {
+ return new File(globalScope.getBuildDir() + "/" + FD_GENERATED + "/source/buildConfig/"
+ + variantData.getVariantConfiguration().getDirName());
+ }
+
+ @NonNull
+ private File getGeneratedResourcesDir(String name) {
+ return FileUtils.join(
+ globalScope.getGeneratedDir(),
+ StringHelper.toStrings(
+ "res",
+ name,
+ getDirectorySegments()));
+ }
+
+ @Override
+ @NonNull
+ public File getGeneratedResOutputDir() {
+ return getGeneratedResourcesDir("resValues");
+ }
+
+ @Override
+ @NonNull
+ public File getGeneratedPngsOutputDir() {
+ return getGeneratedResourcesDir("pngs");
+ }
+
+ @Override
+ @NonNull
+ public File getRenderscriptResOutputDir() {
+ return getGeneratedResourcesDir("rs");
+ }
+
+ @Override
+ @NonNull
+ public File getPackagedJarsJavaResDestinationDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "packagedJarsJavaResources/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getSourceFoldersJavaResDestinationDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "sourceFolderJavaResources/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getJavaResourcesDestinationDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "javaResources/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getRClassSourceOutputDir() {
+ return new File(globalScope.getGeneratedDir(),
+ "source/r/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getAidlSourceOutputDir() {
+ return new File(globalScope.getGeneratedDir(),
+ "source/aidl/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getIncrementalDir(String name) {
+ return FileUtils.join(
+ globalScope.getIntermediatesDir(),
+ "incremental",
+ name);
+ }
+
+ @Override
+ @NonNull
+ public File getPackagedAidlDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ DIR_BUNDLES + "/" + getVariantConfiguration().getDirName() + "/aidl");
+ }
+
+ @Override
+ @NonNull
+ public File getJillPackagedLibrariesDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "jill/" + getVariantConfiguration().getDirName() + "/packaged");
+ }
+
+ @Override
+ @NonNull
+ public File getJillRuntimeLibrariesDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "jill/" + getVariantConfiguration().getDirName() + "/runtime");
+ }
+
+ @Override
+ @NonNull
+ public File getJackDestinationDir() {
+ return new File(globalScope.getIntermediatesDir(),
+ "dex/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getJackClassesZip() {
+ return new File(globalScope.getIntermediatesDir(),
+ "packaged/" + getVariantConfiguration().getDirName() + "/classes.zip");
+ }
+
+ @Override
+ @NonNull
+ public File getClassOutputForDataBinding() {
+ return new File(globalScope.getGeneratedDir(),
+ "source/dataBinding/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getLayoutInfoOutputForDataBinding() {
+ return new File(globalScope.getIntermediatesDir() + "/data-binding-info/" +
+ getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getLayoutFolderOutputForDataBinding() {
+ return new File(globalScope.getIntermediatesDir() + "/data-binding-layout-out/" +
+ getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getGeneratedClassListOutputFileForDataBinding() {
+ return new File(getLayoutInfoOutputForDataBinding(), "_generated.txt");
+ }
+
+ @Override
+ @NonNull
+ public File getProguardOutputFolder() {
+ return new File(globalScope.getBuildDir(), "/" + FD_OUTPUTS + "/mapping/" +
+ getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getProcessAndroidResourcesProguardOutputFile() {
+ return new File(globalScope.getIntermediatesDir(),
+ "/proguard-rules/" + getVariantConfiguration().getDirName() + "/aapt_rules.txt");
+ }
+
+ @Override
+ public File getMappingFile() {
+ return new File(globalScope.getOutputsDir(),
+ "/mapping/" + getVariantConfiguration().getDirName() + "/mapping.txt");
+ }
+
+ @Override
+ @NonNull
+ public File getGenerateSplitAbiResOutputDirectory() {
+ return new File(globalScope.getIntermediatesDir(),
+ "abi/" + getVariantConfiguration().getDirName());
+ }
+
+ @Override
+ @NonNull
+ public File getSplitOutputDirectory() {
+ return new File(globalScope.getIntermediatesDir(),
+ "splits/" + getVariantConfiguration().getDirName());
+ }
+
+
+ @Override
+ @NonNull
+ public List<File> getSplitAbiResOutputFiles() {
+ Set<String> filters = AbiSplitOptions.getAbiFilters(
+ globalScope.getExtension().getSplits().getAbiFilters());
+ List<File> outputFiles = new ArrayList<File>();
+ for (String split : filters) {
+ outputFiles.add(getOutputFileForSplit(split));
+ }
+ return outputFiles;
+ }
+
+ private File getOutputFileForSplit(final String split) {
+ return new File(getGenerateSplitAbiResOutputDirectory(),
+ "resources-" + getVariantConfiguration().getBaseName() + "-" + split + ".ap_");
+ }
+
+ @Override
+ @NonNull
+ public List<File> getPackageSplitAbiOutputFiles() {
+ ImmutableList.Builder<File> builder = ImmutableList.builder();
+ for (String split : globalScope.getExtension().getSplits().getAbiFilters()) {
+ String apkName = getApkName(split);
+ builder.add(new File(getSplitOutputDirectory(), apkName));
+ }
+ return builder.build();
+ }
+
+ private String getApkName(final String split) {
+ String archivesBaseName = globalScope.getArchivesBaseName();
+ String apkName =
+ archivesBaseName + "-" + getVariantConfiguration().getBaseName() + "_" + split;
+ return apkName
+ + (getVariantConfiguration().getSigningConfig() == null
+ ? "-unsigned.apk"
+ : "-unaligned.apk");
+ }
+
+ @NonNull
+ @Override
+ public File getAaptFriendlyManifestOutputFile() {
+ return FileUtils.join(globalScope.getIntermediatesDir(), DIR_BUNDLES,
+ getVariantConfiguration().getDirName(), "aapt", "AndroidManifest.xml");
+ }
+
+ @NonNull
+ @Override
+ public File getInstantRunManifestOutputFile() {
+ return FileUtils.join(globalScope.getIntermediatesDir(), DIR_BUNDLES,
+ getVariantConfiguration().getDirName(), "instant-run", "AndroidManifest.xml");
+ }
+
+ @NonNull
+ @Override
+ public File getManifestReportFile() {
+ return FileUtils.join(getGlobalScope().getOutputsDir(),
+ "logs", "manifest-merger-" + variantData.getVariantConfiguration().getBaseName()
+ + "-report.txt");
+ }
+
+ // Tasks getters/setters.
+
+ @Override
+ public AndroidTask<Task> getPreBuildTask() {
+ return preBuildTask;
+ }
+
+ @Override
+ public void setPreBuildTask(
+ AndroidTask<Task> preBuildTask) {
+ this.preBuildTask = preBuildTask;
+ }
+
+ @Override
+ public AndroidTask<PrepareDependenciesTask> getPrepareDependenciesTask() {
+ return prepareDependenciesTask;
+ }
+
+ @Override
+ public void setPrepareDependenciesTask(
+ AndroidTask<PrepareDependenciesTask> prepareDependenciesTask) {
+ this.prepareDependenciesTask = prepareDependenciesTask;
+ }
+
+ @Override
+ public AndroidTask<ProcessAndroidResources> getGenerateRClassTask() {
+ return generateRClassTask;
+ }
+
+ @Override
+ public void setGenerateRClassTask(
+ AndroidTask<ProcessAndroidResources> generateRClassTask) {
+ this.generateRClassTask = generateRClassTask;
+ }
+
+ @Override
+ public AndroidTask<Task> getSourceGenTask() {
+ return sourceGenTask;
+ }
+
+ @Override
+ public void setSourceGenTask(
+ AndroidTask<Task> sourceGenTask) {
+ this.sourceGenTask = sourceGenTask;
+ }
+
+ @Override
+ public AndroidTask<Task> getResourceGenTask() {
+ return resourceGenTask;
+ }
+
+ @Override
+ public void setResourceGenTask(
+ AndroidTask<Task> resourceGenTask) {
+ this.resourceGenTask = resourceGenTask;
+ }
+
+ @Override
+ public AndroidTask<Task> getAssetGenTask() {
+ return assetGenTask;
+ }
+
+ @Override
+ public void setAssetGenTask(
+ AndroidTask<Task> assetGenTask) {
+ this.assetGenTask = assetGenTask;
+ }
+
+ @Override
+ public AndroidTask<CheckManifest> getCheckManifestTask() {
+ return checkManifestTask;
+ }
+
+ @Override
+ public void setCheckManifestTask(
+ AndroidTask<CheckManifest> checkManifestTask) {
+ this.checkManifestTask = checkManifestTask;
+ }
+
+ @Override
+ public AndroidTask<RenderscriptCompile> getRenderscriptCompileTask() {
+ return renderscriptCompileTask;
+ }
+
+ @Override
+ public void setRenderscriptCompileTask(
+ AndroidTask<RenderscriptCompile> renderscriptCompileTask) {
+ this.renderscriptCompileTask = renderscriptCompileTask;
+ }
+
+ @Override
+ public AndroidTask<AidlCompile> getAidlCompileTask() {
+ return aidlCompileTask;
+ }
+
+ @Override
+ public void setAidlCompileTask(
+ AndroidTask<AidlCompile> aidlCompileTask) {
+ this.aidlCompileTask = aidlCompileTask;
+ }
+
+ @Override
+ @Nullable
+ public AndroidTask<MergeResources> getMergeResourcesTask() {
+ return mergeResourcesTask;
+ }
+
+ @Override
+ public void setMergeResourcesTask(
+ @Nullable AndroidTask<MergeResources> mergeResourcesTask) {
+ this.mergeResourcesTask = mergeResourcesTask;
+ }
+
+ @Override
+ @Nullable
+ public AndroidTask<MergeSourceSetFolders> getMergeAssetsTask() {
+ return mergeAssetsTask;
+ }
+
+ @Override
+ public void setMergeAssetsTask(
+ @Nullable AndroidTask<MergeSourceSetFolders> mergeAssetsTask) {
+ this.mergeAssetsTask = mergeAssetsTask;
+ }
+
+ @Nullable
+ @Override
+ public AndroidTask<MergeSourceSetFolders> getMergeJniLibFoldersTask() {
+ return mergeJniLibsFolderTask;
+ }
+
+ @Override
+ public void setMergeJniLibFoldersTask(
+ @Nullable AndroidTask<MergeSourceSetFolders> mergeJniLibsFolderTask) {
+ this.mergeJniLibsFolderTask = mergeJniLibsFolderTask;
+ }
+
+ @Override
+ public AndroidTask<GenerateBuildConfig> getGenerateBuildConfigTask() {
+ return generateBuildConfigTask;
+ }
+
+ @Override
+ public void setGenerateBuildConfigTask(
+ AndroidTask<GenerateBuildConfig> generateBuildConfigTask) {
+ this.generateBuildConfigTask = generateBuildConfigTask;
+ }
+
+ @Override
+ public AndroidTask<GenerateResValues> getGenerateResValuesTask() {
+ return generateResValuesTask;
+ }
+
+ @Override
+ public void setGenerateResValuesTask(
+ AndroidTask<GenerateResValues> generateResValuesTask) {
+ this.generateResValuesTask = generateResValuesTask;
+ }
+
+ @Override
+ public AndroidTask<Sync> getProcessJavaResourcesTask() {
+ return processJavaResourcesTask;
+ }
+
+ @Override
+ public void setProcessJavaResourcesTask(
+ AndroidTask<Sync> processJavaResourcesTask) {
+ this.processJavaResourcesTask = processJavaResourcesTask;
+ }
+
+ @Override
+ public void setMergeJavaResourcesTask(
+ AndroidTask<TransformTask> mergeJavaResourcesTask) {
+ this.mergeJavaResourcesTask = mergeJavaResourcesTask;
+ }
+
+ /**
+ * Returns the task extracting java resources from libraries and merging those with java
+ * resources coming from the variant's source folders.
+ * @return the task merging resources.
+ */
+ @Override
+ public AndroidTask<TransformTask> getMergeJavaResourcesTask() {
+ return mergeJavaResourcesTask;
+ }
+
+ @Override
+ @Nullable
+ public AndroidTask<? extends AbstractCompile> getJavaCompilerTask() {
+ return javaCompilerTask;
+ }
+
+ @Override
+ @Nullable
+ public AndroidTask<JackTask> getJackTask() {
+ return jackTask;
+ }
+
+ @Override
+ public void setJackTask(
+ @Nullable AndroidTask<JackTask> jackTask) {
+ this.jackTask = jackTask;
+ }
+
+ @Override
+ @Nullable
+ public AndroidTask<? extends JavaCompile> getJavacTask() {
+ return javacTask;
+ }
+
+ @Override
+ public void setJavacTask(
+ @Nullable AndroidTask<? extends JavaCompile> javacTask) {
+ this.javacTask = javacTask;
+ }
+
+ @Override
+ public void setJavaCompilerTask(
+ @NonNull AndroidTask<? extends AbstractCompile> javaCompileTask) {
+ this.javaCompilerTask = javaCompileTask;
+ }
+
+ @Override
+ public AndroidTask<Task> getCompileTask() {
+ return compileTask;
+ }
+
+ @Override
+ public void setCompileTask(
+ AndroidTask<Task> compileTask) {
+ this.compileTask = compileTask;
+ }
+
+ @Override
+ public AndroidTask<?> getCoverageReportTask() {
+ return coverageReportTask;
+ }
+
+ @Override
+ public void setCoverageReportTask(AndroidTask<?> coverageReportTask) {
+ this.coverageReportTask = coverageReportTask;
+ }
+
+ @NonNull
+ InstantRunBuildContext instantRunBuildContext = new InstantRunBuildContext();
+
+ @Override
+ @NonNull
+ public InstantRunBuildContext getInstantRunBuildContext() {
+ return instantRunBuildContext;
+ }
+
+ private AndroidTask<InstantRunWrapperTask> instantRunWrapperTask;
+
+ @NonNull
+ @Override
+ public AndroidTask<InstantRunWrapperTask> getInstantRunIncrementalTask() {
+ return instantRunWrapperTask;
+ }
+
+ @Override
+ public void setInstantRunIncrementalTask(
+ @NonNull AndroidTask<InstantRunWrapperTask> instantRunWrapperTask) {
+ this.instantRunWrapperTask = instantRunWrapperTask;
+ }
+
+ private AndroidTask<InstantRunAnchorTask> instantRunAllActions;
+
+ @NonNull
+ @Override
+ public AndroidTask<InstantRunAnchorTask> getInstantRunAnchorTask() {
+ return instantRunAllActions;
+ }
+
+ @Override
+ public void setInstantRunAnchorTask(
+ @NonNull AndroidTask<InstantRunAnchorTask> instantAllActionsTask) {
+ this.instantRunAllActions = instantAllActionsTask;
+ }
+
+ private AndroidTask<TransformTask> instantRunVerifierTask;
+
+ @Override
+ public AndroidTask<TransformTask> getInstantRunVerifierTask() {
+ return instantRunVerifierTask;
+ }
+
+ @Override
+ public void setInstantRunVerifierTask(AndroidTask<TransformTask> verifierTask) {
+ instantRunVerifierTask = verifierTask;
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AbstractAndroidCompile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AbstractAndroidCompile.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AbstractAndroidCompile.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AbstractAndroidCompile.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java
new file mode 100644
index 0000000..77fb823
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import static com.android.utils.FileUtils.copy;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.test.report.ReportType;
+import com.android.build.gradle.internal.test.report.TestReport;
+import com.android.utils.FileUtils;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.logging.ConsoleRenderer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Task doing test report aggregation.
+ */
+ at ParallelizableTask
+public class AndroidReportTask extends DefaultTask implements AndroidTestTask {
+
+ private final List<AndroidTestTask> subTasks = Lists.newArrayList();
+
+ private ReportType reportType;
+
+ private boolean ignoreFailures;
+
+ private boolean testFailed;
+
+ private File reportsDir;
+
+ private File resultsDir;
+
+
+ @OutputDirectory
+ public File getReportsDir() {
+ return reportsDir;
+ }
+
+ public void setReportsDir(@NonNull File reportsDir) {
+ this.reportsDir = reportsDir;
+ }
+
+ @Override
+ @OutputDirectory
+ public File getResultsDir() {
+ return resultsDir;
+ }
+
+ public void setResultsDir(@NonNull File resultsDir) {
+ this.resultsDir = resultsDir;
+ }
+
+ @Override
+ public boolean getTestFailed() {
+ return testFailed;
+ }
+
+ @Override
+ public boolean getIgnoreFailures() {
+ return ignoreFailures;
+ }
+
+ @Override
+ public void setIgnoreFailures(boolean ignoreFailures) {
+ this.ignoreFailures = ignoreFailures;
+ }
+
+ public ReportType getReportType() {
+ return reportType;
+ }
+
+ public void setReportType(ReportType reportType) {
+ this.reportType = reportType;
+ }
+
+ public void addTask(AndroidTestTask task) {
+ subTasks.add(task);
+ this.dependsOn(task);
+ }
+
+ @InputFiles
+ public List<File> getResultInputs() {
+ List<File> list = Lists.newArrayList();
+
+ for (AndroidTestTask task : subTasks) {
+ list.add(task.getResultsDir());
+ }
+
+ return list;
+ }
+
+ /**
+ * Sets that this current task will run and therefore needs to tell its children
+ * class to not stop on failures.
+ */
+ public void setWillRun() {
+ for (AndroidTestTask task : subTasks) {
+ task.setIgnoreFailures(true);
+ }
+ }
+
+ @TaskAction
+ public void createReport() throws IOException {
+ File resultsOutDir = getResultsDir();
+ File reportOutDir = getReportsDir();
+
+ // empty the folders
+ FileUtils.emptyFolder(resultsOutDir);
+ FileUtils.emptyFolder(reportOutDir);
+
+ // do the copy.
+ copyResults(resultsOutDir);
+
+ // create the report.
+ TestReport report = new TestReport(reportType, resultsOutDir, reportOutDir);
+ report.generateReport();
+
+ // fail if any of the tasks failed.
+ for (AndroidTestTask task : subTasks) {
+ if (task.getTestFailed()) {
+ testFailed = true;
+ String reportUrl = new ConsoleRenderer().asClickableFileUrl(
+ new File(reportOutDir, "index.html"));
+ String message = "There were failing tests. See the report at: " + reportUrl;
+
+ if (getIgnoreFailures()) {
+ getLogger().warn(message);
+ } else {
+ throw new GradleException(message);
+ }
+
+ break;
+ }
+ }
+ }
+
+ private void copyResults(File reportOutDir) throws IOException {
+ List<File> inputs = getResultInputs();
+
+ for (File input : inputs) {
+ File[] children = input.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ copy(child, reportOutDir);
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidTestTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidTestTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidTestTask.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidTestTask.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/BaseTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/BaseTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/BaseTask.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/BaseTask.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.java
new file mode 100644
index 0000000..4cf1b75
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/CheckManifest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+
+/**
+ * Class that checks the presence of the manifest.
+ */
+ at ParallelizableTask
+public class CheckManifest extends DefaultAndroidTask {
+
+ private File manifest;
+
+ private String variantName;
+
+ @InputFile
+ public File getManifest() {
+ return manifest;
+ }
+
+ public void setManifest(@NonNull File manifest) {
+ this.manifest = manifest;
+ }
+
+ public String getVariantName() {
+ return variantName;
+ }
+
+ public void setVariantName(@NonNull String variantName) {
+ this.variantName = variantName;
+ }
+
+ @TaskAction
+ void check() {
+ // use getter to resolve convention mapping
+ File f = getManifest();
+ if (!f.isFile()) {
+ throw new IllegalArgumentException(String.format(
+ "Main Manifest missing for variant %s. Expected path: ",
+ getVariantName(), getManifest().getAbsolutePath()));
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DefaultAndroidTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DefaultAndroidTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DefaultAndroidTask.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DefaultAndroidTask.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyReportTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyReportTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyReportTask.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyReportTask.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.java
new file mode 100644
index 0000000..080830d
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks;
+
+import static com.android.builder.core.BuilderConstants.CONNECTED;
+import static com.android.builder.core.BuilderConstants.DEVICE;
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS;
+import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS;
+import static com.android.builder.core.BuilderConstants.FD_FLAVORS;
+import static com.android.builder.core.BuilderConstants.FD_REPORTS;
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.test.report.ReportType;
+import com.android.build.gradle.internal.test.report.TestReport;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.builder.internal.testing.SimpleTestCallable;
+import com.android.builder.sdk.SdkInfo;
+import com.android.builder.sdk.TargetInfo;
+import com.android.builder.testing.ConnectedDeviceProvider;
+import com.android.builder.testing.SimpleTestRunner;
+import com.android.builder.testing.TestData;
+import com.android.builder.testing.TestRunner;
+import com.android.builder.testing.api.DeviceException;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.builder.testing.api.TestException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.utils.FileUtils;
+import com.android.utils.StringHelper;
+import com.google.common.collect.ImmutableList;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Nullable;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.logging.ConsoleRenderer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+/**
+ * Run instrumentation tests for a given variant
+ */
+public class DeviceProviderInstrumentTestTask extends BaseTask implements AndroidTestTask {
+
+ private File reportsDir;
+ private File resultsDir;
+ private File coverageDir;
+
+ private String flavorName;
+
+ @Nullable
+ private Collection<String> installOptions;
+
+ private DeviceProvider deviceProvider;
+ private TestData testData;
+
+ private File adbExec;
+ @Nullable
+ private File splitSelectExec;
+ private ProcessExecutor processExecutor;
+
+ private boolean ignoreFailures;
+ private boolean testFailed;
+
+ @TaskAction
+ protected void runTests() throws DeviceException, IOException, InterruptedException,
+ TestRunner.NoAuthorizedDeviceFoundException, TestException {
+
+ File resultsOutDir = getResultsDir();
+ FileUtils.emptyFolder(resultsOutDir);
+
+ File coverageOutDir = getCoverageDir();
+ FileUtils.emptyFolder(coverageOutDir);
+
+ boolean success = false;
+ // If there are tests to run, and the test runner returns with no results, we fail (since
+ // this is most likely a problem with the device setup). If no, the task will succeed.
+ if (!testsFound()) {
+ getLogger().info("No tests found, nothing to do.");
+ // If we don't create the coverage file, createXxxCoverageReport task will fail.
+ File emptyCoverageFile = new File(coverageOutDir, SimpleTestCallable.FILE_COVERAGE_EC);
+ emptyCoverageFile.createNewFile();
+ success = true;
+ } else {
+ File testApk = testData.getTestApk();
+ String flavor = getFlavorName();
+ TestRunner testRunner = new SimpleTestRunner(
+ getSplitSelectExec(),
+ getProcessExecutor());
+ deviceProvider.init();
+
+ Collection<String> extraArgs = installOptions == null || installOptions.isEmpty()
+ ? ImmutableList.<String>of() : installOptions;
+ try {
+ success = testRunner.runTests(getProject().getName(), flavor,
+ testApk,
+ testData,
+ deviceProvider.getDevices(),
+ deviceProvider.getMaxThreads(),
+ deviceProvider.getTimeoutInMs(),
+ extraArgs,
+ resultsOutDir,
+ coverageOutDir,
+ getILogger());
+ } finally {
+ deviceProvider.terminate();
+ }
+
+ }
+
+ // run the report from the results.
+ File reportOutDir = getReportsDir();
+ FileUtils.emptyFolder(reportOutDir);
+
+ TestReport report = new TestReport(ReportType.SINGLE_FLAVOR, resultsOutDir, reportOutDir);
+ report.generateReport();
+
+ if (!success) {
+ testFailed = true;
+ String reportUrl = new ConsoleRenderer().asClickableFileUrl(
+ new File(reportOutDir, "index.html"));
+ String message = "There were failing tests. See the report at: " + reportUrl;
+ if (getIgnoreFailures()) {
+ getLogger().warn(message);
+ return;
+
+ } else {
+ throw new GradleException(message);
+ }
+ }
+
+ testFailed = false;
+ }
+
+ /**
+ * Determines if there are any tests to run.
+ *
+ * @return true if there are some tests to run, false otherwise
+ */
+ private boolean testsFound() {
+ // For now we check if there are any test sources. We could inspect the test classes and
+ // apply JUnit logic to see if there's something to run, but that would not catch the case
+ // where user makes a typo in a test name or forgets to inherit from a JUnit class
+ return !getProject().files(testData.getTestDirectories()).getAsFileTree().isEmpty();
+ }
+
+ public File getReportsDir() {
+ return reportsDir;
+ }
+
+ public void setReportsDir(File reportsDir) {
+ this.reportsDir = reportsDir;
+ }
+
+ @Override
+ public File getResultsDir() {
+ return resultsDir;
+ }
+
+ public void setResultsDir(File resultsDir) {
+ this.resultsDir = resultsDir;
+ }
+
+ public File getCoverageDir() {
+ return coverageDir;
+ }
+
+ public void setCoverageDir(File coverageDir) {
+ this.coverageDir = coverageDir;
+ }
+
+ public String getFlavorName() {
+ return flavorName;
+ }
+
+ public void setFlavorName(String flavorName) {
+ this.flavorName = flavorName;
+ }
+
+ public Collection<String> getInstallOptions() {
+ return installOptions;
+ }
+
+ public void setInstallOptions(Collection<String> installOptions) {
+ this.installOptions = installOptions;
+ }
+
+ public DeviceProvider getDeviceProvider() {
+ return deviceProvider;
+ }
+
+ public void setDeviceProvider(DeviceProvider deviceProvider) {
+ this.deviceProvider = deviceProvider;
+ }
+
+ public TestData getTestData() {
+ return testData;
+ }
+
+ public void setTestData(TestData testData) {
+ this.testData = testData;
+ }
+
+ public File getAdbExec() {
+ return adbExec;
+ }
+
+ public void setAdbExec(File adbExec) {
+ this.adbExec = adbExec;
+ }
+
+ public File getSplitSelectExec() {
+ return splitSelectExec;
+ }
+
+ public void setSplitSelectExec(File splitSelectExec) {
+ this.splitSelectExec = splitSelectExec;
+ }
+
+ public ProcessExecutor getProcessExecutor() {
+ return processExecutor;
+ }
+
+ public void setProcessExecutor(ProcessExecutor processExecutor) {
+ this.processExecutor = processExecutor;
+ }
+
+ @Override
+ public boolean getIgnoreFailures() {
+ return ignoreFailures;
+ }
+
+ @Override
+ public void setIgnoreFailures(boolean ignoreFailures) {
+ this.ignoreFailures = ignoreFailures;
+ }
+
+ @Override
+ public boolean getTestFailed() {
+ return testFailed;
+ }
+
+
+ public static class ConfigAction implements TaskConfigAction<DeviceProviderInstrumentTestTask> {
+
+ @NonNull
+ private final VariantScope scope;
+ @NonNull
+ private final DeviceProvider deviceProvider;
+ @NonNull
+ private final TestData testData;
+
+ public ConfigAction(
+ @NonNull VariantScope scope,
+ @NonNull DeviceProvider deviceProvider,
+ @NonNull TestData testData) {
+ this.scope = scope;
+ this.deviceProvider = deviceProvider;
+ this.testData = testData;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName(deviceProvider.getName());
+ }
+
+ @NonNull
+ @Override
+ public Class<DeviceProviderInstrumentTestTask> getType() {
+ return DeviceProviderInstrumentTestTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull DeviceProviderInstrumentTestTask task) {
+ final boolean connected = deviceProvider instanceof ConnectedDeviceProvider;
+ String variantName = scope.getTestedVariantData() != null ?
+ scope.getTestedVariantData().getName() : scope.getVariantData().getName();
+ if (connected) {
+ task.setDescription("Installs and runs the tests for " + variantName +
+ " on connected devices.");
+ } else {
+ task.setDescription("Installs and runs the tests for " + variantName +
+ " using provider: " + StringHelper.capitalize(deviceProvider.getName()));
+
+ }
+ task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ task.setVariantName(variantName);
+ task.setTestData(testData);
+ task.setFlavorName(testData.getFlavorName());
+ task.setDeviceProvider(deviceProvider);
+ task.setInstallOptions(scope.getGlobalScope().getExtension().getAdbOptions().getInstallOptions());
+ task.setProcessExecutor(scope.getGlobalScope().getAndroidBuilder().getProcessExecutor());
+
+ String flavorFolder = testData.getFlavorName();
+ if (!flavorFolder.isEmpty()) {
+ flavorFolder = FD_FLAVORS + "/" + flavorFolder;
+ }
+ String providerFolder = connected ? CONNECTED : DEVICE + "/" + deviceProvider.getName();
+ final String subFolder = "/" + providerFolder + "/" + flavorFolder;
+
+ ConventionMappingHelper.map(task, "adbExec", new Callable<File>() {
+ @Override
+ public File call() {
+ final SdkInfo info = scope.getGlobalScope().getSdkHandler()
+ .getSdkInfo();
+ return (info == null ? null : info.getAdb());
+ }
+ });
+ ConventionMappingHelper.map(task, "splitSelectExec", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final TargetInfo info = scope.getGlobalScope().getAndroidBuilder().getTargetInfo();
+ String path = info == null ? null : info.getBuildTools().getPath(SPLIT_SELECT);
+ if (path != null) {
+ File splitSelectExe = new File(path);
+ return splitSelectExe.exists() ? splitSelectExe : null;
+ } else {
+ return null;
+ }
+ }
+ });
+
+ ConventionMappingHelper.map(task, "resultsDir", new Callable<File>() {
+ @Override
+ public File call() {
+ String rootLocation = scope.getGlobalScope().getExtension().getTestOptions().getResultsDir();
+ if (rootLocation == null) {
+ rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
+ FD_OUTPUTS + "/" + FD_ANDROID_RESULTS;
+ }
+ return scope.getGlobalScope().getProject().file(rootLocation + subFolder);
+ }
+ });
+
+ ConventionMappingHelper.map(task, "reportsDir", new Callable<File>() {
+ @Override
+ public File call() {
+ String rootLocation = scope.getGlobalScope().getExtension().getTestOptions().getReportDir();
+ if (rootLocation == null) {
+ rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
+ FD_REPORTS + "/" + FD_ANDROID_TESTS;
+ }
+ return scope.getGlobalScope().getProject().file(rootLocation + subFolder);
+ }
+ });
+
+ String rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
+ FD_OUTPUTS + "/code-coverage";
+ task.setCoverageDir(scope.getGlobalScope().getProject().file(rootLocation + subFolder));
+
+ if (scope.getVariantData() instanceof TestVariantData) {
+ TestVariantData testVariantData = (TestVariantData) scope.getVariantData();
+ if (connected) {
+ testVariantData.connectedTestTask = task;
+ } else {
+ testVariantData.providerTestTaskList.add(task);
+ }
+ }
+
+ task.setEnabled(deviceProvider.isConfigured());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ExtractJavaResourcesTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ExtractJavaResourcesTask.java
new file mode 100644
index 0000000..59c5b94
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ExtractJavaResourcesTask.java
@@ -0,0 +1,263 @@
+/*
+ * 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.build.gradle.internal.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.android.utils.FileUtils;
+import com.google.common.io.ByteStreams;
+
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Extract all packaged jar files java resources into a directory. Each jar file will be extracted
+ * in a jar specific folder, and only java resources are extracted.
+ */
+ at ParallelizableTask
+public class ExtractJavaResourcesTask extends DefaultAndroidTask {
+
+ // the fact we use a SET is not right, we should have an ordered list of jars...
+ // VariantConfiguration.getPackaged|ProvidedJars should use List<>
+ @InputFiles
+ public Set<File> jarInputFiles;
+
+ @OutputDirectory
+ public File outputDir;
+
+ @InputFiles
+ public Set<File> getJarInputFiles() {
+ return jarInputFiles;
+ }
+
+ @TaskAction
+ public void extractJavaResources(final IncrementalTaskInputs incrementalTaskInputs) {
+
+ incrementalTaskInputs.outOfDate(new org.gradle.api.Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ File inputJar = inputFileDetails.getFile();
+ String folderName = inputJar.getName() +
+ inputJar.getPath().hashCode();
+
+ File outputFolder = new File(outputDir, folderName);
+ if (outputFolder.exists()) {
+ try {
+ FileUtils.deleteFolder(outputFolder);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ if (!outputFolder.mkdirs()) {
+ throw new RuntimeException(
+ "Cannot create folder to extract java resources in for "
+ + inputJar.getAbsolutePath());
+ }
+
+ // create the jar file visitor that will check for out-dated resources.
+
+ JarFile jarFile = null;
+ try {
+ jarFile = new JarFile(inputJar);
+ Enumeration<JarEntry> entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+ if (!jarEntry.isDirectory()) {
+ processJarEntry(jarFile, jarEntry, outputFolder);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+ }
+ });
+
+ incrementalTaskInputs.removed(new org.gradle.api.Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ File deletedJar = inputFileDetails.getFile();
+ String folderName = deletedJar.getName() +
+ deletedJar.getPath().hashCode();
+ File outputFolder = new File(outputDir, folderName);
+ if (outputFolder.exists()) {
+ try {
+ FileUtils.deleteFolder(outputFolder);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * process one jar entry in an input jar file and optionally stores the entry in the output
+ * folder.
+ * @param jarFile the input jar file
+ * @param jarEntry the jar entry in the jarFile to process
+ * @param outputDir the output folder to use to copy/merge the entry in.
+ * @throws IOException
+ */
+ private static void processJarEntry(JarFile jarFile, JarEntry jarEntry, File outputDir) throws IOException {
+ File outputFile = new File(outputDir, jarEntry.getName());
+ Action action = getAction(jarEntry.getName());
+ if (action == Action.COPY) {
+ if (!outputFile.getParentFile().exists() &&
+ !outputFile.getParentFile().mkdirs()) {
+ throw new RuntimeException("Cannot create directory " + outputFile.getParent());
+ }
+ if (!outputFile.exists() || outputFile.lastModified()
+ < jarEntry.getTime()) {
+ InputStream inputStream = null;
+ OutputStream outputStream = null;
+ try {
+ inputStream = jarFile.getInputStream(jarEntry);
+ if (inputStream != null) {
+ outputStream = new BufferedOutputStream(
+ new FileOutputStream(outputFile));
+ ByteStreams.copy(inputStream, outputStream);
+ outputStream.flush();
+ } else {
+ throw new RuntimeException("Cannot copy " + jarEntry.getName());
+ }
+ } finally {
+ try {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ } finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Define all possible actions for a Jar file entry.
+ */
+ enum Action {
+ /**
+ * Copy the file to the output destination.
+ */
+ COPY,
+ /**
+ * Ignore the file.
+ */
+ IGNORE
+ }
+
+ /**
+ * Provides an {@link Action} for the archive entry.
+ * @param archivePath the archive entry path in the archive.
+ * @return the action to implement.
+ */
+ @NonNull
+ public static Action getAction(@NonNull String archivePath) {
+ // Manifest files are never merged.
+ if (JarFile.MANIFEST_NAME.equals(archivePath)) {
+ return Action.IGNORE;
+ }
+
+ // split the path into segments.
+ String[] segments = archivePath.split("/");
+
+ // empty path? skip to next entry.
+ if (segments.length == 0) {
+ return Action.IGNORE;
+ }
+
+ // Check each folders to make sure they should be included.
+ // Folders like CVS, .svn, etc.. should already have been excluded from the
+ // jar file, but we need to exclude some other folder (like /META-INF) so
+ // we check anyway.
+ for (int i = 0 ; i < segments.length - 1; i++) {
+ if (!PackagingUtils.checkFolderForPackaging(segments[i])) {
+ return Action.IGNORE;
+ }
+ }
+
+ // get the file name from the path
+ String fileName = segments[segments.length-1];
+
+ return PackagingUtils.checkFileForPackaging(fileName)
+ ? Action.COPY
+ : Action.IGNORE;
+ }
+
+ public static class Config implements TaskConfigAction<ExtractJavaResourcesTask> {
+
+ private final VariantScope scope;
+
+ public Config(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ public String getName() {
+ return scope.getTaskName("extract", "PackagedLibrariesJavaResources");
+ }
+
+ @Override
+ public Class<ExtractJavaResourcesTask> getType() {
+ return ExtractJavaResourcesTask.class;
+ }
+
+ @Override
+ public void execute(ExtractJavaResourcesTask extractJavaResourcesTask) {
+ ConventionMappingHelper.map(extractJavaResourcesTask, "jarInputFiles",
+ new Callable<Set<File>>() {
+
+ @Override
+ public Set<File> call() throws Exception {
+ return scope.getVariantConfiguration().getAllPackagedJars();
+ }
+ });
+ extractJavaResourcesTask.outputDir = scope.getPackagedJarsJavaResDestinationDir();
+ extractJavaResourcesTask.setVariantName(scope.getVariantConfiguration().getFullName());
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/FileSupplier.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/FileSupplier.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/FileSupplier.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/FileSupplier.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/FilteredJarCopyTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/FilteredJarCopyTask.java
new file mode 100644
index 0000000..55b578c
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/FilteredJarCopyTask.java
@@ -0,0 +1,134 @@
+/*
+ * 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.build.gradle.internal.tasks;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.Closer;
+import com.google.common.io.Files;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Simple task copying a Jar file from one location to another location, while filtering
+ * out a set of entries.
+ *
+ */
+public class FilteredJarCopyTask extends SingleFileCopyTask {
+
+ private List<String> excludes;
+
+ @Input
+ public List<String> getExcludes() {
+ return excludes;
+ }
+
+ public void setExcludes(@NonNull List<String> excludes) {
+ this.excludes = ImmutableList.copyOf(excludes);
+ }
+
+ @TaskAction
+ @Override
+ public void copy() throws IOException {
+ if (excludes.isEmpty()) {
+ super.copy();
+ }
+
+ // create Pattern Objects.
+ List<Pattern> patterns = Lists.newArrayListWithCapacity(excludes.size());
+ for (String exclude : excludes) {
+ patterns.add(Pattern.compile(exclude));
+ }
+
+ Closer closer = Closer.create();
+ byte[] buffer = new byte[4096];
+
+ try {
+ FileOutputStream fos = closer.register(new FileOutputStream(outputFile));
+ BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos));
+ ZipOutputStream zos = closer.register(new ZipOutputStream(bos));
+
+ FileInputStream fis = closer.register(new FileInputStream(inputFile));
+ BufferedInputStream bis = closer.register(new BufferedInputStream(fis));
+ ZipInputStream zis = closer.register(new ZipInputStream(bis));
+
+ // loop on the entries of the intermediary package and put them in the final package.
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ String name = entry.getName();
+
+ if (!checkEntry(patterns, name)) {
+ continue;
+ }
+
+ JarEntry newEntry;
+
+ // Preserve the STORED method of the input entry.
+ if (entry.getMethod() == JarEntry.STORED) {
+ newEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ newEntry = new JarEntry(name);
+ }
+
+ // add the entry to the jar archive
+ zos.putNextEntry(newEntry);
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ int count;
+ while ((count = zis.read(buffer)) != -1) {
+ zos.write(buffer, 0, count);
+ }
+
+ zos.closeEntry();
+ zis.closeEntry();
+ }
+ } finally {
+ closer.close();
+ }
+ }
+
+ private static boolean checkEntry(
+ @NonNull List<Pattern> patterns,
+ @NonNull String name) {
+
+ for (Pattern pattern : patterns) {
+ if (pattern.matcher(name).matches()) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.java
new file mode 100644
index 0000000..7009882
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/GenerateApkDataTask.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import static com.android.SdkConstants.DOT_ANDROID_PACKAGE;
+import static com.android.SdkConstants.FD_RES_RAW;
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
+import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+import com.android.builder.core.AndroidBuilder;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.process.ProcessException;
+import com.android.utils.FileUtils;
+import com.google.common.io.Files;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+/**
+ * Task to generate micro app data res file.
+ */
+ at ParallelizableTask
+public class GenerateApkDataTask extends BaseTask {
+
+ private File apkFile;
+
+ private File resOutputDir;
+
+ private File manifestFile;
+
+ private String mainPkgName;
+
+ private int minSdkVersion;
+
+ private int targetSdkVersion;
+
+ @Input
+ String getBuildToolsVersion() {
+ return getBuildTools().getRevision().toString();
+ }
+
+ @TaskAction
+ void generate() throws IOException, ProcessException, LoggedErrorException,
+ InterruptedException {
+ // always empty output dir.
+ File outDir = getResOutputDir();
+ FileUtils.emptyFolder(outDir);
+
+ File apk = getApkFile();
+ // copy the file into the destination, by sanitizing the name first.
+ File rawDir = new File(outDir, FD_RES_RAW);
+ rawDir.mkdirs();
+
+ File to = new File(rawDir, ANDROID_WEAR_MICRO_APK + DOT_ANDROID_PACKAGE);
+ Files.copy(apk, to);
+
+ // now create the matching XML and the manifest entry.
+ AndroidBuilder builder = getBuilder();
+
+ builder.generateApkData(apk, outDir, getMainPkgName(), ANDROID_WEAR_MICRO_APK);
+ AndroidBuilder.generateApkDataEntryInManifest(getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getManifestFile());
+ }
+
+ @OutputDirectory
+ public File getResOutputDir() {
+ return resOutputDir;
+ }
+
+ public void setResOutputDir(File resOutputDir) {
+ this.resOutputDir = resOutputDir;
+ }
+
+ @InputFile
+ public File getApkFile() {
+ return apkFile;
+ }
+
+ public void setApkFile(File apkFile) {
+ this.apkFile = apkFile;
+ }
+
+ @Input
+ public String getMainPkgName() {
+ return mainPkgName;
+ }
+
+ public void setMainPkgName(String mainPkgName) {
+ this.mainPkgName = mainPkgName;
+ }
+
+ @Input
+ public int getMinSdkVersion() {
+ return minSdkVersion;
+ }
+
+ public void setMinSdkVersion(int minSdkVersion) {
+ this.minSdkVersion = minSdkVersion;
+ }
+
+ @Input
+ public int getTargetSdkVersion() {
+ return targetSdkVersion;
+ }
+
+ public void setTargetSdkVersion(int targetSdkVersion) {
+ this.targetSdkVersion = targetSdkVersion;
+ }
+
+ @OutputFile
+ public File getManifestFile() {
+ return manifestFile;
+ }
+
+ public void setManifestFile(File manifestFile) {
+ this.manifestFile = manifestFile;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<GenerateApkDataTask> {
+
+ @NonNull
+ VariantScope scope;
+
+ @NonNull
+ Configuration config;
+
+ public ConfigAction(@NonNull VariantScope scope, @NonNull Configuration config) {
+ this.scope = scope;
+ this.config = config;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return scope.getTaskName("handle", "MicroApk");
+ }
+
+ @Override
+ @NonNull
+ public Class<GenerateApkDataTask> getType() {
+ return GenerateApkDataTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull GenerateApkDataTask task) {
+ final ApkVariantData variantData = (ApkVariantData) scope.getVariantData();
+ variantData.generateApkDataTask = task;
+
+ task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ task.setVariantName(scope.getVariantConfiguration().getFullName());
+ ConventionMappingHelper.map(task, "resOutputDir", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return new File(
+ scope.getGlobalScope().getGeneratedDir(),
+ "/res/microapk/"
+ + variantData.getVariantConfiguration().getDirName());
+ }
+ });
+ ConventionMappingHelper.map(task, "apkFile", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ // only care about the first one. There shouldn't be more anyway.
+ return config.getFiles().iterator().next();
+ }
+ });
+ ConventionMappingHelper.map(task, "manifestFile", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return new File(
+ scope.getGlobalScope().getGeneratedDir(),
+ "/manifests/microapk/"
+ + scope.getVariantData().getVariantConfiguration().getDirName()
+ + "/" + FN_ANDROID_MANIFEST_XML);
+ }
+ });
+ ConventionMappingHelper.map(task, "mainPkgName", new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return variantData.getVariantConfiguration().getApplicationId();
+ }
+ });
+
+ ConventionMappingHelper.map(task, "minSdkVersion", new Callable<Integer>() {
+ @Override
+ public Integer call() throws Exception {
+ return variantData.getVariantConfiguration().getMinSdkVersion().getApiLevel();
+ }
+ });
+
+ ConventionMappingHelper.map(task, "targetSdkVersion", new Callable<Integer>() {
+ @Override
+ public Integer call() throws Exception {
+ return variantData.getVariantConfiguration().getTargetSdkVersion().getApiLevel();
+ }
+ });
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java
new file mode 100644
index 0000000..9f4c06d
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks;
+import com.android.ide.common.res2.FileStatus;
+import com.android.ide.common.res2.SourceSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public abstract class IncrementalTask extends BaseTask {
+
+ public static final String MARKER_NAME = "build_was_incremental";
+
+ private File incrementalFolder;
+
+ public void setIncrementalFolder(File incrementalFolder) {
+ this.incrementalFolder = incrementalFolder;
+ }
+
+ @OutputDirectory @Optional
+ public File getIncrementalFolder() {
+ return incrementalFolder;
+ }
+
+ /**
+ * Whether this task can support incremental update.
+ *
+ * @return whether this task can support incremental update.
+ */
+ protected boolean isIncremental() {
+ return false;
+ }
+
+ /**
+ * Actual task action. This is called when a full run is needed, which is always the case if
+ * {@link #isIncremental()} returns false.
+ *
+ */
+ protected abstract void doFullTaskAction() throws IOException;
+
+ /**
+ * Optional incremental task action.
+ * Only used if {@link #isIncremental()} returns true.
+ *
+ * @param changedInputs the changed input files.
+ */
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
+ // do nothing.
+ }
+
+ /**
+ * Actual entry point for the action.
+ * Calls out to the doTaskAction as needed.
+ */
+ @TaskAction
+ void taskAction(IncrementalTaskInputs inputs) throws IOException {
+ if (!isIncremental()) {
+ doFullTaskAction();
+ return;
+ }
+
+ if (!inputs.isIncremental()) {
+ getProject().getLogger().info("Unable do incremental execution: full task run");
+ doFullTaskAction();
+ return;
+ }
+
+ final Map<File, FileStatus> changedInputs = Maps.newHashMap();
+ inputs.outOfDate(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails change) {
+ changedInputs.put(change.getFile(), change.isAdded() ? FileStatus.NEW : FileStatus.CHANGED);
+ }
+ });
+
+ inputs.removed(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails change) {
+
+ changedInputs.put(change.getFile(), FileStatus.REMOVED);
+ }
+ });
+
+ doIncrementalTaskAction(changedInputs);
+ }
+
+ public static List<File> flattenSourceSets(List<? extends SourceSet> resourceSets) {
+ List<File> list = Lists.newArrayList();
+
+ for (SourceSet sourceSet : resourceSets) {
+ list.addAll(sourceSet.getSourceFiles());
+ }
+
+ return list;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/InstallVariantTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/InstallVariantTask.java
new file mode 100644
index 0000000..4c36656
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/InstallVariantTask.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks;
+
+import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.internal.InstallUtils;
+import com.android.builder.sdk.SdkInfo;
+import com.android.builder.sdk.TargetInfo;
+import com.android.builder.testing.ConnectedDeviceProvider;
+import com.android.builder.testing.api.DeviceConfigProviderImpl;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.ide.common.build.SplitOutputMatcher;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Task installing an app variant. It looks at connected device and install the best matching
+ * variant output on each device.
+ */
+ at ParallelizableTask
+public class InstallVariantTask extends BaseTask {
+
+ private File adbExe;
+
+ private File splitSelectExe;
+
+ private ProcessExecutor processExecutor;
+
+ private String projectName;
+
+ private int timeOutInMs = 0;
+
+ private Collection<String> installOptions;
+
+ private BaseVariantData<? extends BaseVariantOutputData> variantData;
+
+ public InstallVariantTask() {
+ this.getOutputs().upToDateWhen(new Spec<Task>() {
+ @Override
+ public boolean isSatisfiedBy(Task task) {
+ getLogger().debug("Install task is always run.");
+ return false;
+ }
+ });
+ }
+
+ @TaskAction
+ public void install() throws DeviceException, ProcessException, InterruptedException {
+ final ILogger iLogger = new LoggerWrapper(getLogger(), LogLevel.LIFECYCLE);
+ DeviceProvider deviceProvider = new ConnectedDeviceProvider(getAdbExe(),
+ getTimeOutInMs(),
+ iLogger);
+ deviceProvider.init();
+
+ VariantConfiguration variantConfig = variantData.getVariantConfiguration();
+ String variantName = variantConfig.getFullName();
+
+ int successfulInstallCount = 0;
+ List<? extends DeviceConnector> devices = deviceProvider.getDevices();
+ for (final DeviceConnector device : devices) {
+ if (InstallUtils.checkDeviceApiLevel(
+ device, variantConfig.getMinSdkVersion(), iLogger, projectName, variantName)) {
+ // When InstallUtils.checkDeviceApiLevel returns false, it logs the reason.
+ final List<File> apkFiles = SplitOutputMatcher.computeBestOutput(processExecutor,
+ getSplitSelectExe(),
+ new DeviceConfigProviderImpl(device),
+ variantData.getOutputs(),
+ variantData.getVariantConfiguration().getSupportedAbis());
+
+ if (apkFiles.isEmpty()) {
+ getLogger().lifecycle(
+ "Skipping device '{}' for '{}:{}': Could not find build of variant " +
+ "which supports density {} and an ABI in {}",
+ device.getName(), projectName, variantName,
+ device.getDensity(), Joiner.on(", ").join(device.getAbis()));
+ } else {
+ getLogger().lifecycle(
+ "Installing APK '{}' on '{}' for {}:{}",
+ FileUtils.getNamesAsCommaSeparatedList(apkFiles),
+ device.getName(),
+ projectName,
+ variantName);
+
+ final Collection<String> extraArgs =
+ Objects.firstNonNull(installOptions, ImmutableList.<String>of());
+
+ if (apkFiles.size() > 1 || device.getApiLevel() >= 21) {
+ device.installPackages(apkFiles, extraArgs,
+ getTimeOutInMs(), getILogger());
+ successfulInstallCount++;
+ } else {
+ device.installPackage(apkFiles.get(0), extraArgs,
+ getTimeOutInMs(),
+ getILogger());
+ successfulInstallCount++;
+ }
+ }
+ }
+ }
+
+ if (successfulInstallCount == 0) {
+ throw new GradleException("Failed to install on any devices.");
+ } else {
+ getLogger().quiet("Installed on {} {}.",
+ successfulInstallCount,
+ successfulInstallCount==1 ? "device" : "devices");
+ }
+ }
+
+ @InputFile
+ public File getAdbExe() {
+ return adbExe;
+ }
+
+ public void setAdbExe(File adbExe) {
+ this.adbExe = adbExe;
+ }
+
+ @InputFile
+ @Optional
+ public File getSplitSelectExe() {
+ return splitSelectExe;
+ }
+
+ public void setSplitSelectExe(File splitSelectExe) {
+ this.splitSelectExe = splitSelectExe;
+ }
+
+ public ProcessExecutor getProcessExecutor() {
+ return processExecutor;
+ }
+
+ public void setProcessExecutor(ProcessExecutor processExecutor) {
+ this.processExecutor = processExecutor;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public void setProjectName(String projectName) {
+ this.projectName = projectName;
+ }
+
+ @Input
+ public int getTimeOutInMs() {
+ return timeOutInMs;
+ }
+
+ public void setTimeOutInMs(int timeOutInMs) {
+ this.timeOutInMs = timeOutInMs;
+ }
+
+ @Input
+ @Optional
+ public Collection<String> getInstallOptions() {
+ return installOptions;
+ }
+
+ public void setInstallOptions(Collection<String> installOptions) {
+ this.installOptions = installOptions;
+ }
+
+ public BaseVariantData<? extends BaseVariantOutputData> getVariantData() {
+ return variantData;
+ }
+
+ public void setVariantData(
+ BaseVariantData<? extends BaseVariantOutputData> variantData) {
+ this.variantData = variantData;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<InstallVariantTask> {
+
+ private final VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("install");
+ }
+
+ @NonNull
+ @Override
+ public Class<InstallVariantTask> getType() {
+ return InstallVariantTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull InstallVariantTask installTask) {
+ installTask.setDescription(
+ "Installs the " + scope.getVariantData().getDescription() + ".");
+ installTask.setVariantName(scope.getVariantConfiguration().getFullName());
+ installTask.setGroup(TaskManager.INSTALL_GROUP);
+ installTask.setProjectName(scope.getGlobalScope().getProject().getName());
+ installTask.setVariantData(scope.getVariantData());
+ installTask.setTimeOutInMs(
+ scope.getGlobalScope().getExtension().getAdbOptions().getTimeOutInMs());
+ installTask.setInstallOptions(
+ scope.getGlobalScope().getExtension().getAdbOptions().getInstallOptions());
+ installTask.setProcessExecutor(
+ scope.getGlobalScope().getAndroidBuilder().getProcessExecutor());
+ ConventionMappingHelper.map(installTask, "adbExe", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final SdkInfo info = scope.getGlobalScope().getSdkHandler().getSdkInfo();
+ return (info == null ? null : info.getAdb());
+ }
+ });
+ ConventionMappingHelper.map(installTask, "splitSelectExe", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final TargetInfo info =
+ scope.getGlobalScope().getAndroidBuilder().getTargetInfo();
+ String path = info == null ? null : info.getBuildTools().getPath(SPLIT_SELECT);
+ if (path != null) {
+ File splitSelectExe = new File(path);
+ return splitSelectExe.exists() ? splitSelectExe : null;
+ } else {
+ return null;
+ }
+ }
+ });
+ ((ApkVariantData) scope.getVariantData()).installTask = installTask;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/LibraryJarTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/LibraryJarTransform.java
new file mode 100644
index 0000000..b72164b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/LibraryJarTransform.java
@@ -0,0 +1,370 @@
+/*
+ * 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.build.gradle.internal.tasks;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.transforms.JarMerger;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Closer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * A Transforms that takes the project/project local streams for CLASSES and RESOURCES,
+ * and processes and combines them, and put them in the bundle folder.
+ *
+ * Regarding Streams, this is a no-op transform as it does not write any output to any stream. It
+ * uses secondary outputs to write directly into the bundle folder.
+ */
+public class LibraryJarTransform extends Transform {
+
+ @NonNull
+ private final File mainClassLocation;
+ @NonNull
+ private final File localJarsLocation;
+ @NonNull
+ private final String packagePath;
+ private final boolean packageBuildConfig;
+
+ @Nullable
+ private List<ExcludeListProvider> excludeListProviders;
+
+ public LibraryJarTransform(
+ @NonNull File mainClassLocation,
+ @NonNull File localJarsLocation,
+ @NonNull String packageName,
+ boolean packageBuildConfig) {
+ this.mainClassLocation = mainClassLocation;
+ this.localJarsLocation = localJarsLocation;
+ this.packagePath = packageName.replace(".", "/");
+ this.packageBuildConfig = packageBuildConfig;
+ }
+
+ public void addExcludeListProvider(ExcludeListProvider provider) {
+ if (excludeListProviders == null) {
+ excludeListProviders = Lists.newArrayList();
+ }
+ excludeListProviders.add(provider);
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "syncLibJars";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return TransformManager.CONTENT_JARS;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return TransformManager.EMPTY_SCOPES;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ return TransformManager.SCOPE_FULL_LIBRARY;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ // TODO make incremental
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileOutputs() {
+ return ImmutableList.of(mainClassLocation);
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryDirectoryOutputs() {
+ return ImmutableList.of(localJarsLocation);
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+ List<String> excludes = Lists.newArrayListWithExpectedSize(5);
+
+ // these must be regexp to match the zip entries
+ excludes.add(".*/R.class$");
+ excludes.add(".*/R\\$(.*).class$");
+ excludes.add(packagePath + "/Manifest.class$");
+ excludes.add(packagePath + "/Manifest\\$(.*).class$");
+ if (!packageBuildConfig) {
+ excludes.add(packagePath + "/BuildConfig.class$");
+ }
+ if (excludeListProviders != null) {
+ for (ExcludeListProvider provider : excludeListProviders) {
+ List<String> list = provider.getExcludeList();
+ if (list != null) {
+ excludes.addAll(list);
+ }
+ }
+ }
+
+ // create Pattern Objects.
+ List<Pattern> patterns = Lists.newArrayListWithCapacity(excludes.size());
+ for (String exclude : excludes) {
+ patterns.add(Pattern.compile(exclude));
+ }
+
+ // first look for what inputs we have. There shouldn't be that many inputs so it should
+ // be quick and it'll allow us to minimize jar merging if we don't have to.
+ List<QualifiedContent> mainScope = Lists.newArrayList();
+ List<QualifiedContent> locaJlJarScope = Lists.newArrayList();
+
+ for (TransformInput input : invocation.getReferencedInputs()) {
+ for (QualifiedContent qualifiedContent : Iterables.concat(
+ input.getJarInputs(), input.getDirectoryInputs())) {
+ if (qualifiedContent.getScopes().contains(Scope.PROJECT)) {
+ // even if the scope contains both project + local jar, we treat this as main
+ // scope.
+ mainScope.add(qualifiedContent);
+ } else {
+ locaJlJarScope.add(qualifiedContent);
+ }
+ }
+ }
+
+ // process main scope.
+ if (mainScope.isEmpty()) {
+ throw new RuntimeException("Empty Main scope for " + getName());
+ }
+
+ if (mainScope.size() == 1) {
+ QualifiedContent content = mainScope.get(0);
+ if (content instanceof JarInput) {
+ copyJarWithContentFilter(content.getFile(), mainClassLocation, patterns);
+ } else {
+ jarFolderToRootLocation(content.getFile(), patterns);
+ }
+ } else {
+ mergeToRootLocation(mainScope, patterns);
+ }
+
+ // process local scope
+ processLocalJars(locaJlJarScope);
+ }
+
+ private void mergeToRootLocation(
+ @NonNull List<QualifiedContent> qualifiedContentList,
+ @NonNull final List<Pattern> excludes)
+ throws IOException {
+ JarMerger jarMerger = new JarMerger(mainClassLocation);
+ jarMerger.setFilter(new IZipEntryFilter() {
+ @Override
+ public boolean checkEntry(String archivePath)
+ throws ZipAbortException {
+ return LibraryJarTransform.checkEntry(excludes, archivePath);
+ }
+ });
+
+ for (QualifiedContent content : qualifiedContentList) {
+ System.out.println(content);
+ if (content instanceof JarInput) {
+ jarMerger.addJar(content.getFile());
+ } else {
+ jarMerger.addFolder(content.getFile());
+ }
+ }
+
+ jarMerger.close();
+ }
+
+ private void processLocalJars(@NonNull List<QualifiedContent> qualifiedContentList)
+ throws IOException {
+
+ // first copy the jars (almost) as is, and remove them from the list.
+ // then we'll make a single jars that contains all the folders.
+ // Note that we do need to remove the resources from the jars since they have been merged
+ // somewhere else.
+ // TODO: maybe do the folders separately to handle incremental?
+
+ IZipEntryFilter classOnlyFilter = new IZipEntryFilter() {
+ @Override
+ public boolean checkEntry(String archivePath)
+ throws ZipAbortException {
+ return archivePath.endsWith(SdkConstants.DOT_CLASS);
+ }
+ };
+
+ Iterator<QualifiedContent> iterator = qualifiedContentList.iterator();
+
+ while (iterator.hasNext()) {
+ QualifiedContent content = iterator.next();
+ if (content instanceof JarInput) {
+ // we need to copy the jars but only take the class files as the resources have
+ // been merged into the main jar.
+ copyJarWithContentFilter(
+ content.getFile(),
+ new File(localJarsLocation, content.getFile().getName()),
+ classOnlyFilter);
+ iterator.remove();
+ }
+ }
+
+ // now handle the folders.
+ if (!qualifiedContentList.isEmpty()) {
+ JarMerger jarMerger = new JarMerger(new File(localJarsLocation, "otherclasses.jar"));
+ jarMerger.setFilter(classOnlyFilter);
+ for (QualifiedContent content : qualifiedContentList) {
+ jarMerger.addFolder(content.getFile());
+ }
+ jarMerger.close();
+ }
+ }
+
+ private void jarFolderToRootLocation(@NonNull File file, @NonNull final List<Pattern> excludes)
+ throws IOException {
+ JarMerger jarMerger = new JarMerger(mainClassLocation);
+ jarMerger.setFilter(new IZipEntryFilter() {
+ @Override
+ public boolean checkEntry(String archivePath)
+ throws ZipAbortException {
+ return LibraryJarTransform.checkEntry(excludes, archivePath);
+ }
+ });
+ jarMerger.addFolder(file);
+ jarMerger.close();
+ }
+
+ public static void copyJarWithContentFilter(
+ @NonNull File from,
+ @NonNull File to,
+ @NonNull final List<Pattern> excludes) throws IOException {
+ copyJarWithContentFilter(from, to, new IZipEntryFilter() {
+ @Override
+ public boolean checkEntry(String archivePath)
+ throws ZipAbortException {
+ return LibraryJarTransform.checkEntry(excludes, archivePath);
+ }
+ });
+ }
+
+ public static void copyJarWithContentFilter(
+ @NonNull File from,
+ @NonNull File to,
+ @Nullable IZipEntryFilter filter) throws IOException {
+ Closer closer = Closer.create();
+ byte[] buffer = new byte[4096];
+
+ try {
+ FileOutputStream fos = closer.register(new FileOutputStream(to));
+ BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos));
+ ZipOutputStream zos = closer.register(new ZipOutputStream(bos));
+
+ FileInputStream fis = closer.register(new FileInputStream(from));
+ BufferedInputStream bis = closer.register(new BufferedInputStream(fis));
+ ZipInputStream zis = closer.register(new ZipInputStream(bis));
+
+ // loop on the entries of the intermediary package and put them in the final package.
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ String name = entry.getName();
+
+ if (filter != null && !filter.checkEntry(name)) {
+ continue;
+ }
+
+ JarEntry newEntry;
+
+ // Preserve the STORED method of the input entry.
+ if (entry.getMethod() == JarEntry.STORED) {
+ newEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ newEntry = new JarEntry(name);
+ }
+
+ // add the entry to the jar archive
+ zos.putNextEntry(newEntry);
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ int count;
+ while ((count = zis.read(buffer)) != -1) {
+ zos.write(buffer, 0, count);
+ }
+
+ zos.closeEntry();
+ zis.closeEntry();
+ }
+ } catch (IZipEntryFilter.ZipAbortException e) {
+ throw new IOException(e);
+ } finally {
+ closer.close();
+ }
+ }
+
+ private static boolean checkEntry(
+ @NonNull List<Pattern> patterns,
+ @NonNull String name) {
+ for (Pattern pattern : patterns) {
+ if (pattern.matcher(name).matches()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Convenient way to attach exclude list providers that can provide their list at the end of
+ * the build.
+ */
+ public interface ExcludeListProvider {
+ @Nullable List<String> getExcludeList();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/LibraryJniLibsTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/LibraryJniLibsTransform.java
new file mode 100644
index 0000000..9096d30
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/LibraryJniLibsTransform.java
@@ -0,0 +1,195 @@
+/*
+ * 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.build.gradle.internal.tasks;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.utils.FileUtils;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * A Transforms that takes the project/project local streams for native libs and processes and
+ * combines them, and put them in the bundle folder under jni/
+ *
+ * Regarding Streams, this is a no-op transform as it does not write any output to any stream. It
+ * uses secondary outputs to write directly into the bundle folder.
+ */
+public class LibraryJniLibsTransform extends Transform {
+
+ @NonNull
+ private final File jniLibsFolder;
+
+ private final Pattern pattern = Pattern.compile("lib/[^/]+/[^/]+\\.so");
+
+
+ public LibraryJniLibsTransform(
+ @NonNull File jniLibsFolder) {
+ this.jniLibsFolder = jniLibsFolder;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "syncJniLibs";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return TransformManager.CONTENT_NATIVE_LIBS;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return TransformManager.EMPTY_SCOPES;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ return TransformManager.SCOPE_FULL_LIBRARY;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ // TODO make incremental
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryDirectoryOutputs() {
+ return ImmutableList.of(jniLibsFolder);
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+
+ FileUtils.emptyFolder(jniLibsFolder);
+
+ for (TransformInput input : invocation.getReferencedInputs()) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ copyFromJar(jarInput.getFile());
+ }
+
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ copyFromFolder(directoryInput.getFile());
+ }
+ }
+ }
+
+ private void copyFromFolder(@NonNull File rootDirectory) throws IOException {
+ copyFromFolder(rootDirectory, Lists.<String>newArrayListWithCapacity(3));
+ }
+
+ private void copyFromFolder(@NonNull File from, @NonNull List<String> pathSegments)
+ throws IOException {
+ File[] children = from.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+ return file.isDirectory() || name.endsWith(SdkConstants.DOT_NATIVE_LIBS);
+ }
+ });
+
+ if (children != null) {
+ for (File child : children) {
+ pathSegments.add(child.getName());
+ if (child.isDirectory()) {
+ copyFromFolder(child, pathSegments);
+ } else if (child.isFile()) {
+ if (pattern.matcher(Joiner.on('/').join(pathSegments)).matches()) {
+ // copy the file. However we do want to skip the first segment ('lib') here
+ // since the 'jni' folder is representing the same concept.
+ File to = FileUtils.join(jniLibsFolder, pathSegments.subList(1, 3));
+ FileUtils.mkdirs(to.getParentFile());
+ Files.copy(child, to);
+ }
+ }
+
+ pathSegments.remove(pathSegments.size() - 1);
+ }
+ }
+ }
+
+ private void copyFromJar(@NonNull File jarFile) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ZipFile zipFile = new ZipFile(jarFile);
+ try {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+
+ String entryPath = entry.getName();
+
+ if (!pattern.matcher(entryPath).matches()) {
+ continue;
+ }
+
+ // read the content.
+ buffer.reset();
+ ByteStreams.copy(zipFile.getInputStream(entry), buffer);
+
+ // get the output file and write to it.
+ Files.write(buffer.toByteArray(), computeFile(jniLibsFolder, entryPath));
+ }
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ /**
+ * computes a file path from a root folder and a zip archive path.
+ * @param rootFolder the root folder
+ * @param path the archive path
+ * @return the File
+ */
+ private static File computeFile(@NonNull File rootFolder, @NonNull String path) {
+ path = FileUtils.toSystemDependentPath(path);
+ return new File(rootFolder, path);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.java
new file mode 100644
index 0000000..17f66ea
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Task to merge files. This appends all the files together into an output file.
+ */
+ at ParallelizableTask
+public class MergeFileTask extends DefaultAndroidTask {
+
+ private Set<File> mInputFiles;
+
+ private File mOutputFile;
+
+ @TaskAction
+ public void mergeFiles() throws IOException {
+
+ Set<File> files = getInputFiles();
+ File output = getOutputFile();
+
+ if (files.size() == 1) {
+ Files.copy(files.iterator().next(), output);
+ return;
+ }
+
+ // first delete the current file
+ output.delete();
+
+ // no input? done.
+ if (files.isEmpty()) {
+ return;
+ }
+
+ // otherwise put the all the files together
+ for (File file : files) {
+ String content = Files.toString(file, Charsets.UTF_8);
+ Files.append(content, output, Charsets.UTF_8);
+ Files.append("\n", output, Charsets.UTF_8);
+ }
+ }
+
+ @InputFiles
+ public Set<File> getInputFiles() {
+ return mInputFiles;
+ }
+
+ public void setInputFiles(Set<File> inputFiles) {
+ mInputFiles = inputFiles;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return mOutputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ mOutputFile = outputFile;
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MockableAndroidJarTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MockableAndroidJarTask.java
new file mode 100644
index 0000000..71a3da8
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/MockableAndroidJarTask.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.testing.MockableJarGenerator;
+import com.android.sdklib.IAndroidTarget;
+import com.google.common.base.CharMatcher;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+/**
+ * Task for generating a mockable android.jar
+ */
+ at ParallelizableTask
+public class MockableAndroidJarTask extends DefaultTask {
+
+ private File mAndroidJar;
+
+ private File mOutputFile;
+
+ /**
+ * Whether the generated jar should return default values from all methods or throw exceptions.
+ */
+ private boolean mReturnDefaultValues;
+
+ @TaskAction
+ public void createMockableJar() throws IOException {
+ File outputFile = getOutputFile();
+ if (outputFile.exists()) {
+ // Modules share the mockable jar, all the "inputs" are reflected in the filename,
+ // e.g. mockable-android-22.default-values.jar. If we ever change the generator logic,
+ // it will be reflected in the name as well.
+ //
+ // This is not how Gradle understands tasks with overlapping outputs - it will run
+ // all instances of this task, because the output was not created by this instance. We
+ // need to return here manually because of that behavior.
+ return;
+ }
+ MockableJarGenerator generator = new MockableJarGenerator(getReturnDefaultValues());
+ getLogger().info(String.format("Creating %s from %s.", outputFile.getAbsolutePath(),
+ getAndroidJar().getAbsolutePath()));
+ generator.createMockableJar(getAndroidJar(), outputFile);
+ }
+
+ @Input
+ public boolean getReturnDefaultValues() {
+ return mReturnDefaultValues;
+ }
+
+ public void setReturnDefaultValues(boolean returnDefaultValues) {
+ mReturnDefaultValues = returnDefaultValues;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return mOutputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ mOutputFile = outputFile;
+ }
+
+ @InputFile
+ public File getAndroidJar() {
+ return mAndroidJar;
+ }
+
+ public void setAndroidJar(File androidJar) {
+ mAndroidJar = androidJar;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<MockableAndroidJarTask> {
+
+ final GlobalScope scope;
+
+ public ConfigAction(GlobalScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ public String getName() {
+ return "mockableAndroidJar";
+ }
+
+ @Override
+ public Class<MockableAndroidJarTask> getType() {
+ return MockableAndroidJarTask.class;
+ }
+
+ @Override
+ public void execute(final MockableAndroidJarTask task) {
+ task.setGroup(TaskManager.BUILD_GROUP);
+ task.setDescription(
+ "Creates a version of android.jar that's suitable for unit tests.");
+ task.setReturnDefaultValues(
+ scope.getExtension().getTestOptions().getUnitTests().isReturnDefaultValues());
+
+ ConventionMappingHelper.map(task, "androidJar", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ checkNotNull(scope.getAndroidBuilder().getTarget(), "ensureTargetSetup not called");
+ return new File(
+ scope.getAndroidBuilder().getTarget().getPath(IAndroidTarget.ANDROID_JAR));
+ }
+ });
+
+ task.setOutputFile(scope.getMockableAndroidJarFile());
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/NdkTask.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/NdkTask.groovy
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/NdkTask.groovy
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/NdkTask.groovy
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.java
new file mode 100644
index 0000000..9623b3e
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks;
+
+import com.android.build.gradle.internal.dependency.DependencyChecker;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.SyncIssue;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.utils.StringHelper;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.util.List;
+import java.util.Map;
+
+ at ParallelizableTask
+public class PrepareDependenciesTask extends BaseTask {
+
+ private BaseVariantData variant;
+ private final List<DependencyChecker> checkers = Lists.newArrayList();
+
+ @TaskAction
+ protected void prepare() {
+ ApiVersion minSdkVersion = variant.getVariantConfiguration().getMinSdkVersion();
+ int minSdk = 1;
+ if (minSdkVersion.getCodename() != null) {
+ minSdk = SdkVersionInfo.getApiByBuildCode(minSdkVersion.getCodename(), true);
+ } else {
+ minSdk = minSdkVersion.getApiLevel();
+ }
+
+ boolean foundError = false;
+
+ for (DependencyChecker checker : checkers) {
+ for (Map.Entry<ModuleVersionIdentifier, Integer> entry :
+ checker.getLegacyApiLevels().entrySet()) {
+ ModuleVersionIdentifier mavenVersion = entry.getKey();
+ int api = entry.getValue();
+ if (api > minSdk) {
+ foundError = true;
+ String configurationName = checker.getConfigurationDependencies().getName();
+ getLogger().error(
+ "Variant {} has a dependency on version {} of the legacy {} Maven " +
+ "artifact, which corresponds to API level {}. This is not " +
+ "compatible with min SDK of this module, which is {}. " +
+ "Please use the 'gradle dependencies' task to debug your " +
+ "dependencies graph.",
+ StringHelper.capitalize(configurationName),
+ mavenVersion.getVersion(),
+ mavenVersion.getGroup(),
+ api,
+ minSdk);
+ }
+ }
+
+ for (SyncIssue syncIssue : checker.getSyncIssues()) {
+ foundError = true;
+ getLogger().error(syncIssue.getMessage());
+ }
+ }
+
+ if (foundError) {
+ throw new GradleException("Dependency Error. See console for details.");
+ }
+
+ }
+
+ public void addChecker(DependencyChecker checker) {
+ checkers.add(checker);
+ }
+
+ public BaseVariantData getVariant() {
+ return variant;
+ }
+
+ public void setVariant(BaseVariantData variant) {
+ this.variant = variant;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.java
new file mode 100644
index 0000000..2dcdbdd
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks;
+
+import com.android.build.gradle.internal.LibraryCache;
+import com.android.utils.FileUtils;
+import com.google.common.io.Files;
+
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+ at ParallelizableTask
+public class PrepareLibraryTask extends DefaultAndroidTask {
+ private File bundle;
+ private File explodedDir;
+
+ @TaskAction
+ public void prepare() {
+ //LibraryCache.getCache().unzipLibrary(this.name, project, getBundle(), getExplodedDir())
+ LibraryCache.unzipAar(getBundle(), getExplodedDir(), getProject());
+ // verify the we have a classes.jar, if we don't just create an empty one.
+ File classesJar = new File(new File(getExplodedDir(), "jars"), "classes.jar");
+ if (classesJar.exists()) {
+ return;
+ }
+ try {
+ Files.createParentDirs(classesJar);
+ JarOutputStream jarOutputStream = new JarOutputStream(
+ new BufferedOutputStream(new FileOutputStream(classesJar)), new Manifest());
+ jarOutputStream.close();
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot create missing classes.jar", e);
+ }
+
+ }
+
+ @InputFile
+ public File getBundle() {
+ return bundle;
+ }
+
+ @OutputDirectory
+ public File getExplodedDir() {
+ return explodedDir;
+ }
+
+ public void setBundle(File bundle) {
+ this.bundle = bundle;
+ }
+
+ public void setExplodedDir(File explodedDir) {
+ this.explodedDir = explodedDir;
+ }
+
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SigningReportTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SigningReportTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SigningReportTask.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SigningReportTask.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SingleFileCopyTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SingleFileCopyTask.java
new file mode 100644
index 0000000..3ed0246
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SingleFileCopyTask.java
@@ -0,0 +1,72 @@
+/*
+ * 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.build.gradle.internal.tasks;
+
+import com.android.annotations.NonNull;
+import com.google.common.io.Files;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Simple task to do a single file copy.
+ *
+ * Using the built-in Copy task to copy a single file is dangerous if multiple files
+ * go in the same folder. This is because Copy uses a folder destination rather than a file
+ * destination, and if 2+ Copy task write in the same folder, it'll disable task-level
+ * parallelism.
+ * Also, when a task output (in this case the folder) is clobbered by another task (which would
+ * simply write in the same folder), on the next run the tasks will run again in a non
+ * incremental way which is bad.
+ *
+ * So this task is very simple file to file copy which does not have all these issues, and allow
+ * to copy multiple files into the same folder.
+ *
+ */
+public class SingleFileCopyTask extends DefaultTask {
+
+ protected File inputFile;
+ protected File outputFile;
+
+ @InputFile
+ public File getInputFile() {
+ return inputFile;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ public void setInputFile(@NonNull File inputFile) {
+ this.inputFile = inputFile;
+ }
+
+ public void setOutputFile(@NonNull File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ @TaskAction
+ public void copy() throws IOException {
+ Files.copy(inputFile, outputFile);
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SourceSetsTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SourceSetsTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SourceSetsTask.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SourceSetsTask.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SplitFileSupplier.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SplitFileSupplier.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SplitFileSupplier.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/SplitFileSupplier.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.java
new file mode 100644
index 0000000..97c758e
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.builder.testing.api.TestServer;
+import com.google.common.base.Preconditions;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+
+/**
+ * Task sending APKs out to a {@link TestServer}
+ */
+ at ParallelizableTask
+public class TestServerTask extends DefaultAndroidTask {
+
+ private File testApk;
+
+ File testedApk;
+
+ TestServer testServer;
+
+ @TaskAction
+ public void sendToServer() {
+ testServer.uploadApks(getVariantName(), getTestApk(), getTestedApk());
+ }
+
+ @InputFile
+ public File getTestApk() {
+ return testApk;
+ }
+
+ public void setTestApk(File testApk) {
+ this.testApk = testApk;
+ }
+
+ @InputFile @Optional
+ public File getTestedApk() {
+ return testedApk;
+ }
+
+ public void setTestedApk(File testedApk) {
+ this.testedApk = testedApk;
+ }
+
+ @NonNull
+ @Override
+ @Input
+ public String getVariantName() {
+ return Preconditions.checkNotNull(super.getVariantName(),
+ "Test server task must have a variant name.");
+ }
+
+ public TestServer getTestServer() {
+ return testServer;
+ }
+
+ public void setTestServer(TestServer testServer) {
+ this.testServer = testServer;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.java
new file mode 100644
index 0000000..eada46e
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.builder.sdk.SdkInfo;
+import com.android.builder.testing.ConnectedDeviceProvider;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.utils.ILogger;
+import com.android.utils.StringHelper;
+
+import org.gradle.api.Task;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+ at ParallelizableTask
+public class UninstallTask extends BaseTask {
+
+ private BaseVariantData variant;
+
+ private int mTimeOutInMs = 0;
+
+ public UninstallTask() {
+ this.getOutputs().upToDateWhen(new Spec<Task>() {
+ @Override
+ public boolean isSatisfiedBy(Task task) {
+ getLogger().debug("Uninstall task is always run.");
+ return false;
+ }
+ });
+ }
+
+ @TaskAction
+ public void uninstall() throws DeviceException {
+ final Logger logger = getLogger();
+ final String applicationId = variant.getApplicationId();
+
+ logger.info("Uninstalling app: {}", applicationId);
+
+ final ILogger lifecycleLogger = new LoggerWrapper(getLogger(), LogLevel.LIFECYCLE);
+ final DeviceProvider deviceProvider =
+ new ConnectedDeviceProvider(getAdbExe(), getTimeOutInMs(), lifecycleLogger);
+
+ deviceProvider.init();
+ final List<? extends DeviceConnector> devices = deviceProvider.getDevices();
+
+ for (DeviceConnector device : devices) {
+ device.uninstallPackage(applicationId, getTimeOutInMs(), getILogger());
+ logger.lifecycle(
+ "Uninstalling {} (from {}:{}) from device '{}' ({}).",
+ applicationId, getProject().getName(),
+ variant.getVariantConfiguration().getFullName(),
+ device.getName(), device.getSerialNumber());
+ }
+
+ int n = devices.size();
+ logger.quiet("Uninstalled {} from {} device{}.",
+ applicationId, n, n == 1 ? "" : "s");
+
+ }
+
+
+ @InputFile
+ public File getAdbExe() {
+ SdkInfo sdkInfo = getBuilder().getSdkInfo();
+ if (sdkInfo == null) {
+ return null;
+ }
+ return sdkInfo.getAdb();
+ }
+
+ public BaseVariantData getVariant() {
+ return variant;
+ }
+
+ public void setVariant(BaseVariantData variant) {
+ this.variant = variant;
+ }
+
+ @Input
+ public int getTimeOutInMs() {
+ return mTimeOutInMs;
+ }
+
+ public void setTimeOutInMs(int timeoutInMs) {
+ mTimeOutInMs = timeoutInMs;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<UninstallTask> {
+
+ private final VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ String prefix = "uninstall";
+
+ return prefix + StringHelper.capitalize(scope.getVariantConfiguration().getFullName());
+ }
+
+ @NonNull
+ @Override
+ public Class<UninstallTask> getType() {
+ return UninstallTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull UninstallTask uninstallTask) {
+
+ uninstallTask.setDescription(
+ "Uninstalls the " + scope.getVariantData().getDescription() + ".");
+ uninstallTask.setGroup(TaskManager.INSTALL_GROUP);
+ uninstallTask.setVariant(scope.getVariantData());
+ uninstallTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ uninstallTask.setVariantName(scope.getVariantConfiguration().getFullName());
+ uninstallTask.setTimeOutInMs(
+ scope.getGlobalScope().getExtension().getAdbOptions().getTimeOutInMs());
+
+ ConventionMappingHelper.map(uninstallTask, "adbExe", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final SdkInfo info = scope.getGlobalScope().getSdkHandler().getSdkInfo();
+ return (info == null ? null : info.getAdb());
+ }
+ });
+
+ ((ApkVariantData) scope.getVariantData()).uninstallTask = uninstallTask;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.java
new file mode 100644
index 0000000..f517d4f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.builder.model.SigningConfig;
+import com.android.ide.common.signing.KeystoreHelper;
+import com.android.ide.common.signing.KeytoolException;
+import com.android.prefs.AndroidLocation;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.tooling.BuildException;
+
+import java.io.File;
+
+/**
+ * A validate task that creates the debug keystore if it's missing. It only creates it if it's in
+ * the default debug keystore location.
+ *
+ * It's linked to a given SigningConfig
+ */
+ at ParallelizableTask
+public class ValidateSigningTask extends BaseTask {
+
+ private SigningConfig signingConfig;
+
+ public void setSigningConfig(SigningConfig signingConfig) {
+ this.signingConfig = signingConfig;
+ }
+
+ public SigningConfig getSigningConfig() {
+ return signingConfig;
+ }
+
+ /**
+ * Annotated getter for task input.
+ *
+ * This is an Input and not an InputFile because the file might not exist. This is not actually
+ * used by the task, this is only for Gradle to check inputs.
+ *
+ * @return the path of the keystore.
+ */
+ @Input
+ @Optional
+ @SuppressWarnings("unused")
+ public String getStoreLocation() {
+ File f = signingConfig.getStoreFile();
+ if (f != null) {
+ return f.getAbsolutePath();
+ }
+ return null;
+ }
+
+ @TaskAction
+ public void validate() throws AndroidLocation.AndroidLocationException, KeytoolException {
+ File storeFile = signingConfig.getStoreFile();
+ if (storeFile == null) {
+ throw new IllegalArgumentException(
+ "Keystore file not set for signing config " + signingConfig.getName());
+ }
+ if (!storeFile.exists()) {
+ if (KeystoreHelper.defaultDebugKeystoreLocation().equals(storeFile.getAbsolutePath())) {
+ checkState(signingConfig.isSigningReady(), "Debug signing config not ready.");
+ File storeDirectory = storeFile.getParentFile();
+
+ if (!storeDirectory.canWrite()) {
+ String message = "Unable to create debug keystore in \""
+ + storeDirectory.getAbsolutePath() + "\" because it is not writable.";
+
+ throw new BuildException(message, null);
+ }
+
+ getLogger().info(
+ "Creating default debug keystore at {}",
+ storeFile.getAbsolutePath());
+
+ //noinspection ConstantConditions - isSigningReady() called above
+ if (!KeystoreHelper.createDebugStore(
+ signingConfig.getStoreType(), signingConfig.getStoreFile(),
+ signingConfig.getStorePassword(), signingConfig.getKeyPassword(),
+ signingConfig.getKeyAlias(), getILogger())) {
+ throw new BuildException("Unable to recreate missing debug keystore.", null);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ String.format(
+ "Keystore file %s not found for signing config '%s'.",
+ storeFile.getAbsolutePath(),
+ signingConfig.getName()));
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/databinding/DataBindingExportBuildInfoTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/databinding/DataBindingExportBuildInfoTask.java
new file mode 100644
index 0000000..44f153a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/databinding/DataBindingExportBuildInfoTask.java
@@ -0,0 +1,195 @@
+/*
+ * 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.build.gradle.internal.tasks.databinding;
+
+import com.android.SdkConstants;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import android.databinding.tool.LayoutXmlProcessor;
+import android.databinding.tool.processing.Scope;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * This task creates a class which includes the build environment information, which is needed for
+ * the annotation processor.
+ */
+public class DataBindingExportBuildInfoTask extends DefaultTask {
+
+ private LayoutXmlProcessor xmlProcessor;
+
+ private File sdkDir;
+
+ private File xmlOutFolder;
+
+ private File exportClassListTo;
+
+ private boolean printMachineReadableErrors;
+
+ private File dataBindingClassOutput;
+
+ @TaskAction
+ public void exportInfo(IncrementalTaskInputs inputs) {
+ xmlProcessor.writeInfoClass(getSdkDir(), getXmlOutFolder(), getExportClassListTo(),
+ getLogger().isDebugEnabled(), isPrintMachineReadableErrors());
+ Scope.assertNoError();
+ }
+
+ public LayoutXmlProcessor getXmlProcessor() {
+ return xmlProcessor;
+ }
+
+ public void setXmlProcessor(LayoutXmlProcessor xmlProcessor) {
+ this.xmlProcessor = xmlProcessor;
+ }
+
+ @InputFiles
+ public FileCollection getCompilerClasspath() {
+ return null;
+ }
+
+ @InputFiles
+ public Iterable<ConfigurableFileTree> getCompilerSources() {
+ return null;
+ }
+
+ @Input
+ public File getSdkDir() {
+ return sdkDir;
+ }
+
+ public void setSdkDir(File sdkDir) {
+ this.sdkDir = sdkDir;
+ }
+
+ @InputDirectory // output of the process layouts task
+ public File getXmlOutFolder() {
+ return xmlOutFolder;
+ }
+
+ public void setXmlOutFolder(File xmlOutFolder) {
+ this.xmlOutFolder = xmlOutFolder;
+ }
+
+ @Input
+ @Optional
+ public File getExportClassListTo() {
+ return exportClassListTo;
+ }
+
+ public void setExportClassListTo(File exportClassListTo) {
+ this.exportClassListTo = exportClassListTo;
+ }
+
+ @Input
+ public boolean isPrintMachineReadableErrors() {
+ return printMachineReadableErrors;
+ }
+
+ public void setPrintMachineReadableErrors(boolean printMachineReadableErrors) {
+ this.printMachineReadableErrors = printMachineReadableErrors;
+ }
+
+ @OutputDirectory
+ public File getOutput() {
+ return dataBindingClassOutput;
+ }
+
+ public void setDataBindingClassOutput(File dataBindingClassOutput) {
+ this.dataBindingClassOutput = dataBindingClassOutput;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<DataBindingExportBuildInfoTask> {
+
+ private final VariantScope variantScope;
+
+ private final boolean printMachineReadableErrors;
+
+ public ConfigAction(VariantScope variantScope, boolean printMachineReadableErrors) {
+ this.variantScope = variantScope;
+ this.printMachineReadableErrors = printMachineReadableErrors;
+ }
+
+ @Override
+ public String getName() {
+ return variantScope.getTaskName("dataBindingExportBuildInfo");
+ }
+
+ @Override
+ public Class<DataBindingExportBuildInfoTask> getType() {
+ return DataBindingExportBuildInfoTask.class;
+ }
+
+ @Override
+ public void execute(DataBindingExportBuildInfoTask task) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData = variantScope
+ .getVariantData();
+ task.setXmlProcessor(variantData.getLayoutXmlProcessor());
+ task.setSdkDir(variantScope.getGlobalScope().getSdkHandler().getSdkFolder());
+ task.setXmlOutFolder(variantScope.getLayoutInfoOutputForDataBinding());
+
+ ConventionMappingHelper.map(task, "compilerClasspath", new Callable<FileCollection>() {
+ @Override
+ public FileCollection call() {
+ return variantScope.getJavaClasspath();
+ }
+ });
+ ConventionMappingHelper
+ .map(task, "compilerSources", new Callable<Iterable<ConfigurableFileTree>>() {
+ @Override
+ public Iterable<ConfigurableFileTree> call() throws Exception {
+ return Iterables.filter(variantData.getJavaSources(),
+ new Predicate<ConfigurableFileTree>() {
+ @Override
+ public boolean apply(ConfigurableFileTree input) {
+ File dataBindingOut = variantScope
+ .getClassOutputForDataBinding();
+ return !dataBindingOut.equals(input.getDir());
+ }
+ });
+ }
+ });
+
+ task.setExportClassListTo(variantData.getType().isExportDataBindingClassList() ?
+ variantScope.getGeneratedClassListOutputFileForDataBinding() : null);
+ task.setPrintMachineReadableErrors(printMachineReadableErrors);
+ task.setDataBindingClassOutput(variantScope.getClassOutputForDataBinding());
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/databinding/DataBindingProcessLayoutsTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/databinding/DataBindingProcessLayoutsTask.java
new file mode 100644
index 0000000..fb4bd18
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/databinding/DataBindingProcessLayoutsTask.java
@@ -0,0 +1,166 @@
+/*
+ * 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.build.gradle.internal.tasks.databinding;
+
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+
+import android.databinding.tool.LayoutXmlProcessor;
+import android.databinding.tool.processing.Scope;
+import android.databinding.tool.util.L;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.bind.JAXBException;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPathExpressionException;
+
+/**
+ * Parses xml files and generates metadata. Will be removed when aapt supports binding tags.
+ */
+public class DataBindingProcessLayoutsTask extends DefaultTask {
+
+ private LayoutXmlProcessor xmlProcessor;
+
+ private File sdkDir;
+
+ private int minSdk;
+
+ private File layoutInputFolder;
+
+ private File layoutOutputFolder;
+
+ private File xmlInfoOutFolder;
+
+ @InputDirectory
+ public File getLayoutInputFolder() {
+ return layoutInputFolder;
+ }
+
+ public void setLayoutInputFolder(File layoutInputFolder) {
+ this.layoutInputFolder = layoutInputFolder;
+ }
+
+ @OutputDirectory
+ public File getLayoutOutputFolder() {
+ return layoutOutputFolder;
+ }
+
+ public void setLayoutOutputFolder(File layoutOutputFolder) {
+ this.layoutOutputFolder = layoutOutputFolder;
+ }
+
+ @OutputDirectory
+ public File getXmlInfoOutFolder() {
+ return xmlInfoOutFolder;
+ }
+
+ @TaskAction
+ public void processResources(IncrementalTaskInputs incrementalTaskInputs)
+ throws ParserConfigurationException, SAXException, XPathExpressionException,
+ IOException, JAXBException {
+ final LayoutXmlProcessor.ResourceInput resourceInput =
+ new LayoutXmlProcessor.ResourceInput(incrementalTaskInputs.isIncremental(),
+ getLayoutInputFolder(), getLayoutOutputFolder());
+ if (incrementalTaskInputs.isIncremental()) {
+ incrementalTaskInputs.outOfDate(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ if (inputFileDetails.isAdded()) {
+ resourceInput.added(inputFileDetails.getFile());
+ } else if (inputFileDetails.isModified()) {
+ resourceInput.changed(inputFileDetails.getFile());
+ }
+ }
+ });
+ incrementalTaskInputs.removed(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ resourceInput.removed(inputFileDetails.getFile());
+ }
+ });
+ }
+ xmlProcessor.processResources(resourceInput);
+ Scope.assertNoError();
+ xmlProcessor.writeLayoutInfoFiles(getXmlInfoOutFolder());
+ Scope.assertNoError();
+ }
+
+ public void setXmlProcessor(LayoutXmlProcessor xmlProcessor) {
+ this.xmlProcessor = xmlProcessor;
+ }
+
+ public File getSdkDir() {
+ return sdkDir;
+ }
+
+ public void setSdkDir(File sdkDir) {
+ this.sdkDir = sdkDir;
+ }
+
+ public int getMinSdk() {
+ return minSdk;
+ }
+
+ public void setMinSdk(int minSdk) {
+ this.minSdk = minSdk;
+ }
+
+ public void setXmlInfoOutFolder(File xmlInfoOutFolder) {
+ this.xmlInfoOutFolder = xmlInfoOutFolder;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<DataBindingProcessLayoutsTask> {
+
+ private final VariantScope variantScope;
+
+ public ConfigAction(VariantScope variantScope) {
+ this.variantScope = variantScope;
+ }
+
+ @Override
+ public String getName() {
+ return variantScope.getTaskName("dataBindingProcessLayouts");
+ }
+
+ @Override
+ public Class<DataBindingProcessLayoutsTask> getType() {
+ return DataBindingProcessLayoutsTask.class;
+ }
+
+ @Override
+ public void execute(DataBindingProcessLayoutsTask task) {
+ task.setXmlProcessor(variantScope.getVariantData().getLayoutXmlProcessor());
+ task.setSdkDir(variantScope.getGlobalScope().getSdkHandler().getSdkFolder());
+ task.setMinSdk(variantScope.getVariantConfiguration().getMinSdkVersion().getApiLevel());
+ task.setLayoutInputFolder(variantScope.getMergeResourcesOutputDir());
+ task.setLayoutOutputFolder(variantScope.getLayoutFolderOutputForDataBinding());
+ task.setXmlInfoOutFolder(variantScope.getLayoutInfoOutputForDataBinding());
+ variantScope.setResourceOutputDir(variantScope.getLayoutFolderOutputForDataBinding());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.java
new file mode 100644
index 0000000..15c5467
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/tasks/multidex/CreateManifestKeepList.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks.multidex;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.DefaultAndroidTask;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+ at ParallelizableTask
+public class CreateManifestKeepList extends DefaultAndroidTask {
+
+ private File manifest;
+
+ private File outputFile;
+
+ private File proguardFile;
+
+ @InputFile
+ public File getManifest() {
+ return manifest;
+ }
+
+ public void setManifest(File manifest) {
+ this.manifest = manifest;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ @InputFile @Optional
+ public File getProguardFile() {
+ return proguardFile;
+ }
+
+ public void setProguardFile(File proguardFile) {
+ this.proguardFile = proguardFile;
+ }
+
+ @TaskAction
+ public void generateKeepListFromManifest()
+ throws ParserConfigurationException, SAXException, IOException {
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+
+ Writer out = new BufferedWriter(new FileWriter(getOutputFile()));
+ try {
+ parser.parse(getManifest(), new ManifestHandler(out));
+
+ // add a couple of rules that cannot be easily parsed from the manifest.
+ out.write("-keep public class * extends android.app.backup.BackupAgent {\n"
+ + " <init>();\n"
+ + "}\n"
+ + "-keep public class * extends java.lang.annotation.Annotation {\n"
+ + " *;\n"
+ + "}\n"
+ + "-keep class com.android.tools.fd.** {\n"
+ + " *;\n"
+ + "}\n"
+ + "-dontnote com.android.tools.fd.**,"
+ + "android.support.multidex.MultiDexExtractor\n");
+
+ if (proguardFile != null) {
+ out.write(Files.toString(proguardFile, Charsets.UTF_8));
+ }
+ } finally {
+ out.close();
+ }
+ }
+
+ private static final String DEFAULT_KEEP_SPEC = "{ <init>(); }";
+
+ private static final Map<String, String> KEEP_SPECS = ImmutableMap.<String, String>builder()
+ .put("application", "{\n"
+ + " <init>();\n"
+ + " void attachBaseContext(android.content.Context);\n"
+ + "}")
+ .put("activity", DEFAULT_KEEP_SPEC)
+ .put("service", DEFAULT_KEEP_SPEC)
+ .put("receiver", DEFAULT_KEEP_SPEC)
+ .put("provider", DEFAULT_KEEP_SPEC)
+ .put("instrumentation", DEFAULT_KEEP_SPEC).build();
+
+ private static class ManifestHandler extends DefaultHandler {
+ private Writer out;
+
+ ManifestHandler(Writer out) {
+ this.out = out;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attr) {
+ // IJ thinks the qualified reference to KEEP_SPECS is not needed but Groovy needs
+ // it at runtime?!?
+ //noinspection UnnecessaryQualifiedReference
+ String keepSpec = CreateManifestKeepList.KEEP_SPECS.get(qName);
+ if (!Strings.isNullOrEmpty(keepSpec)) {
+ keepClass(attr.getValue("android:name"), keepSpec, out);
+ // Also keep the original application class when using instant-run.
+ keepClass(attr.getValue("name"), keepSpec, out);
+ }
+ }
+ }
+
+ private static void keepClass(
+ @Nullable String className,
+ @NonNull String keepSpec,
+ @NonNull Writer out) {
+ if (className != null) {
+ try {
+ out.write("-keep class ");
+ out.write(className);
+ out.write(" ");
+ out.write(keepSpec);
+ out.write("\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public static class ConfigAction implements TaskConfigAction<CreateManifestKeepList> {
+
+ private VariantScope scope;
+
+ public ConfigAction(@NonNull VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("collect", "MultiDexComponents");
+ }
+
+ @NonNull
+ @Override
+ public Class<CreateManifestKeepList> getType() {
+ return CreateManifestKeepList.class;
+ }
+
+ @Override
+ public void execute(@NonNull CreateManifestKeepList manifestKeepListTask) {
+ manifestKeepListTask.setVariantName(scope.getVariantConfiguration().getFullName());
+
+ // since all the output have the same manifest, besides the versionCode,
+ // we can take any of the output and use that.
+ final BaseVariantOutputData output = scope.getVariantData().getOutputs().get(0);
+ ConventionMappingHelper.map(manifestKeepListTask, "manifest", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return output.getScope().getManifestOutputFile();
+ }
+ });
+
+ manifestKeepListTask.proguardFile = scope.getVariantConfiguration().getMultiDexKeepProguard();
+ manifestKeepListTask.outputFile = scope.getManifestKeepListFile();
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/AbstractTestDataImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/AbstractTestDataImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/AbstractTestDataImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/AbstractTestDataImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestApplicationTestData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestApplicationTestData.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestApplicationTestData.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestApplicationTestData.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestDataImpl.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestDataImpl.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestDataImpl.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/TestDataImpl.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/report/ReportType.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/report/ReportType.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/report/ReportType.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/test/report/ReportType.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/BaseProguardAction.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/BaseProguardAction.java
new file mode 100644
index 0000000..33f438f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/BaseProguardAction.java
@@ -0,0 +1,170 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import proguard.ClassPath;
+import proguard.ClassPathEntry;
+import proguard.ClassSpecification;
+import proguard.Configuration;
+import proguard.ConfigurationParser;
+import proguard.KeepClassSpecification;
+import proguard.ParseException;
+import proguard.ProGuard;
+import proguard.classfile.util.ClassUtil;
+import proguard.util.ListUtil;
+
+public abstract class BaseProguardAction extends ProguardConfigurable {
+
+ protected static final List<String> JAR_FILTER = ImmutableList.of("!META-INF/MANIFEST.MF");
+
+ protected final Configuration configuration = new Configuration();
+
+ public BaseProguardAction() {
+ configuration.useMixedCaseClassNames = false;
+ configuration.programJars = new ClassPath();
+ configuration.libraryJars = new ClassPath();
+ }
+
+ public void runProguard() throws IOException {
+ new ProGuard(configuration).execute();
+ }
+
+ @Override
+ public void keep(@NonNull String keep) {
+ if (configuration.keep == null) {
+ configuration.keep = Lists.newArrayList();
+ }
+
+ ClassSpecification classSpecification;
+ try {
+ ConfigurationParser parser = new ConfigurationParser(new String[]{keep}, null);
+ classSpecification = parser.parseClassSpecificationArguments();
+ } catch (IOException e) {
+ // No IO happens when parsing in-memory strings.
+ throw new AssertionError(e);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+
+ //noinspection unchecked
+ configuration.keep.add(new KeepClassSpecification(
+ true /*markClasses*/,
+ false /*markConditionally*/,
+ false /*includedescriptorclasses */,
+ false /*allowshrinking*/,
+ false /*allowoptimization*/,
+ false /*allowobfuscation*/,
+ classSpecification));
+ }
+
+ public void dontshrink() {
+ configuration.shrink = false;
+ }
+
+ public void dontobfuscate() {
+ configuration.obfuscate = false;
+ }
+
+ public void dontoptimize() {
+ configuration.optimize = false;
+ }
+
+ public void dontpreverify() {
+ configuration.preverify = false;
+ }
+
+ public void keepattributes() {
+ configuration.keepAttributes = Lists.newArrayListWithExpectedSize(0);
+ }
+
+ @Override
+ public void dontwarn(@NonNull String dontwarn) {
+ if (configuration.warn == null) {
+ configuration.warn = Lists.newArrayList();
+ }
+
+ dontwarn = ClassUtil.internalClassName(dontwarn);
+
+ //noinspection unchecked
+ configuration.warn.addAll(ListUtil.commaSeparatedList(dontwarn));
+ }
+
+ public void dontwarn() {
+ configuration.warn = Lists.newArrayList("**");
+ }
+
+ public void dontnote() {
+ configuration.note = Lists.newArrayList("**");
+ }
+
+ public void forceprocessing() {
+ configuration.lastModified = Long.MAX_VALUE;
+ }
+
+ protected void applyMapping(@NonNull File testedMappingFile) {
+ configuration.applyMapping = testedMappingFile;
+ }
+
+ public void applyConfigurationFile(@NonNull File file) throws IOException, ParseException {
+ ConfigurationParser parser =
+ new ConfigurationParser(file, System.getProperties());
+ try {
+ parser.parse(configuration);
+ } finally {
+ parser.close();
+ }
+ }
+
+ public void printconfiguration(@NonNull File file) {
+ configuration.printConfiguration = file;
+ }
+
+ protected void inJar(@NonNull File jarFile) {
+ inputJar(configuration.programJars, jarFile, null);
+ }
+
+ protected void outJar(@NonNull File file) {
+ ClassPathEntry classPathEntry = new ClassPathEntry(file, true /*output*/);
+ configuration.programJars.add(classPathEntry);
+ }
+
+ protected void libraryJar(@NonNull File jarFile) {
+ inputJar(configuration.libraryJars, jarFile, null);
+ }
+
+ protected static void inputJar(
+ @NonNull ClassPath classPath,
+ @NonNull File file,
+ @Nullable List<String> filter) {
+ ClassPathEntry classPathEntry = new ClassPathEntry(file, false /*output*/);
+
+ if (filter != null) {
+ classPathEntry.setFilter(filter);
+ }
+
+ classPath.add(classPathEntry);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ChangeRecords.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ChangeRecords.java
new file mode 100644
index 0000000..8e9d9ee
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ChangeRecords.java
@@ -0,0 +1,156 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformException;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ * Book keeper for all recorded changes to files of interest.
+ */
+public class ChangeRecords {
+
+ // changes records, indexed by file path, and value is the last change made on the file.
+ Map<String, Status> records = new HashMap<String, Status>();
+
+ public synchronized void add(@NonNull Status status, @NonNull String filePath) {
+ records.put(filePath, status);
+ }
+
+ /**
+ * Add all changes from another {@link ChangeRecords} instance. The passed instance records
+ * will be added if there is not already a record with the same file path present in this
+ * instance.
+ * @param changeRecords another set of file changes records.
+ */
+ public synchronized void addAll(@NonNull ChangeRecords changeRecords) {
+ for (Map.Entry<String, Status> changeRecord : changeRecords.records.entrySet()) {
+ if (!records.containsKey(changeRecord.getKey())) {
+ records.put(changeRecord.getKey(), changeRecord.getValue());
+ }
+ }
+ }
+
+ /**
+ * Returns the last change recorded for a file or null of no change was ever recorded.
+ */
+ @Nullable
+ synchronized Status getChangeFor(String filePath) {
+ return records.get(filePath);
+ }
+
+ /**
+ * Writes the current set of file changes to the passed file.
+ * @param file file to write changes to.
+ * @throws IOException if the file cannot be opened or wrote.
+ */
+ void write(File file) throws IOException {
+ Files.createParentDirs(file);
+ FileWriter fileWriter = new FileWriter(file);
+ try {
+ for (Map.Entry<String, Status> record : records.entrySet()) {
+ fileWriter.write(String.format("%s,%s", record.getValue(), record.getKey()));
+ fileWriter.write("\n");
+ }
+ } finally {
+ fileWriter.close();
+ }
+ }
+
+ /**
+ * Calculates the list of files which change record has the passed status.
+ * @return a possibly empty list of files path.
+ */
+ @NonNull
+ synchronized Set<String> getFilesForStatus(Status status) {
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ for (String s : records.keySet()) {
+ if (getChangeFor(s) == status) {
+ builder.add(s);
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Interface to process individual change records.
+ */
+ public interface RecordHandler {
+
+ /**
+ * Process an individual file change record.
+ *
+ * @param filePath the file path that changed
+ * @param status the change.
+ */
+ void handle(String filePath, Status status) throws IOException, TransformException;
+ }
+
+ /**
+ * Process the incremental file records individually with the passed {@link RecordHandler}.
+ * @param incrementalFile a file containing change records.
+ * @param handler the record handler.
+ */
+ public static void process(@NonNull File incrementalFile, @NonNull RecordHandler handler)
+ throws IOException, TransformException {
+ if (!incrementalFile.isFile()) {
+ return;
+ }
+ Map<String, Status> changeRecords = load(incrementalFile).records;
+ // delete the incremental changes file to reset the list of changes
+ FileUtils.delete(incrementalFile);
+ if (changeRecords.isEmpty()) {
+ return;
+ }
+
+ for (Map.Entry<String, Status> changeRecord : changeRecords.entrySet()) {
+ handler.handle(changeRecord.getKey(), changeRecord.getValue());
+ }
+ }
+
+ /**
+ * Load change records from a persisted file.
+ */
+ @NonNull
+ static ChangeRecords load(File file) throws IOException {
+ ChangeRecords changeRecords = new ChangeRecords();
+ List<String> rawRecords = Files.readLines(file, Charsets.UTF_8);
+ for (String rawRecord : rawRecords) {
+ StringTokenizer st = new StringTokenizer(rawRecord, ",");
+ if (st.countTokens() != 2) {
+ throw new IOException("Invalid incremental change record : " + rawRecord);
+ }
+ changeRecords.add(Status.valueOf(st.nextToken()), st.nextToken());
+ }
+ return changeRecords;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/DexTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/DexTransform.java
new file mode 100644
index 0000000..05482fe
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/DexTransform.java
@@ -0,0 +1,557 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.utils.FileUtils.mkdirs;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext.FileType;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.DexOptions;
+import com.android.builder.sdk.TargetInfo;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.ParsingProcessOutputHandler;
+import com.android.ide.common.blame.parser.DexParser;
+import com.android.ide.common.blame.parser.ToolOutputParser;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.sdklib.BuildToolInfo;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.Logger;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Dexing as a transform.
+ *
+ * This consumes all the available classes streams and creates a dex file (or more in the case of
+ * multi-dex)
+ *
+ * This handles pre-dexing as well. If there are more than one stream, then only streams with
+ * changed files will be re-dexed before a single merge phase is done at the end.
+ * If there is a single input, then there's only a single dx phase.
+ */
+public class DexTransform extends Transform {
+
+ @NonNull
+ private final DexOptions dexOptions;
+
+ private final boolean debugMode;
+ private final boolean multiDex;
+
+ @NonNull
+ private final File intermediateFolder;
+ @Nullable
+ private final File mainDexListFile;
+
+ @NonNull
+ private final AndroidBuilder androidBuilder;
+ @NonNull
+ private final ILogger logger;
+
+ private final InstantRunBuildContext instantRunBuildContext;
+
+ public DexTransform(
+ @NonNull DexOptions dexOptions,
+ boolean debugMode,
+ boolean multiDex,
+ @Nullable File mainDexListFile,
+ @NonNull File intermediateFolder,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull Logger logger,
+ @NonNull InstantRunBuildContext instantRunBuildContext) {
+ this.dexOptions = dexOptions;
+ this.debugMode = debugMode;
+ this.multiDex = multiDex;
+ this.mainDexListFile = mainDexListFile;
+ this.intermediateFolder = intermediateFolder;
+ this.androidBuilder = androidBuilder;
+ this.logger = new LoggerWrapper(logger);
+ this.instantRunBuildContext = instantRunBuildContext;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "dex";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getOutputTypes() {
+ return TransformManager.CONTENT_DEX;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileInputs() {
+ if (mainDexListFile != null) {
+ return ImmutableList.of(mainDexListFile);
+ }
+
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryDirectoryOutputs() {
+ // we use the intermediate folder only if
+ // - there's per-scope dexing
+ // - there's no native multi-dex
+ if (dexOptions.getPreDexLibraries() && !(multiDex && mainDexListFile == null)) {
+ return ImmutableList.of(intermediateFolder);
+ }
+
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Object> getParameterInputs() {
+ try {
+ Map<String, Object> params = Maps.newHashMapWithExpectedSize(4);
+
+ params.put("debugMode", debugMode);
+ params.put("predex", dexOptions.getPreDexLibraries());
+ params.put("incremental", dexOptions.getIncremental());
+ params.put("jumbo", dexOptions.getJumboMode());
+ params.put("multidex", multiDex);
+ params.put("multidex-legacy", multiDex && mainDexListFile != null);
+
+ TargetInfo targetInfo = androidBuilder.getTargetInfo();
+ Preconditions.checkState(targetInfo != null,
+ "androidBuilder.targetInfo required for task '%s'.", getName());
+ BuildToolInfo buildTools = targetInfo.getBuildTools();
+ params.put("build-tools", buildTools.getRevision().toString());
+
+ return params;
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(TransformInvocation transformInvocation)
+ throws TransformException, IOException, InterruptedException {
+ TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
+ boolean isIncremental = transformInvocation.isIncremental();
+ checkNotNull(outputProvider, "Missing output object for transform " + getName());
+
+ // Gather a full list of all inputs.
+ List<JarInput> jarInputs = Lists.newArrayList();
+ List<DirectoryInput> directoryInputs = Lists.newArrayList();
+ for (TransformInput input : transformInvocation.getInputs()) {
+ jarInputs.addAll(input.getJarInputs());
+ directoryInputs.addAll(input.getDirectoryInputs());
+ }
+
+ ProcessOutputHandler outputHandler = new ParsingProcessOutputHandler(
+ new ToolOutputParser(new DexParser(), Message.Kind.ERROR, logger),
+ new ToolOutputParser(new DexParser(), logger),
+ androidBuilder.getErrorReporter());
+
+ if (!isIncremental) {
+ outputProvider.deleteAll();
+ }
+ try {
+ // if only one scope or no per-scope dexing, just do a single pass that
+ // runs dx on everything.
+ if ((jarInputs.size() + directoryInputs.size()) == 1 || !dexOptions.getPreDexLibraries()) {
+ File outputDir = outputProvider.getContentLocation("main",
+ getOutputTypes(), getScopes(),
+ Format.DIRECTORY);
+ FileUtils.mkdirs(outputDir);
+
+ // first delete the output folder where the final dex file(s) will be.
+ FileUtils.emptyFolder(outputDir);
+
+ // gather the inputs. This mode is always non incremental, so just
+ // gather the top level folders/jars
+ final List<File> inputFiles = Lists.newArrayList();
+ for (JarInput jarInput : jarInputs) {
+ inputFiles.add(jarInput.getFile());
+ }
+
+ for (DirectoryInput directoryInput : directoryInputs) {
+ inputFiles.add(directoryInput.getFile());
+ }
+
+ androidBuilder.convertByteCode(
+ inputFiles,
+ outputDir,
+ multiDex,
+ mainDexListFile,
+ dexOptions,
+ null,
+ false,
+ true,
+ outputHandler,
+ false /* instantRunMode */);
+
+ for (File file : Files.fileTreeTraverser().breadthFirstTraversal(outputDir)) {
+ if (file.isFile()) {
+ instantRunBuildContext.addChangedFile(FileType.DEX, file);
+ }
+ }
+ } else {
+ // Figure out if we need to do a dx merge.
+ // The ony case we don't need it is in native multi-dex mode when doing debug
+ // builds. This saves build time at the expense of too many dex files which is fine.
+ // FIXME dx cannot receive dex files to merge inside a folder. They have to be in a jar. Need to fix in dx.
+ boolean needMerge = !multiDex || mainDexListFile != null;// || !debugMode;
+
+ // where we write the pre-dex depends on whether we do the merge after.
+ // If needMerge changed from one build to another, we'll be in non incremental
+ // mode, so we don't have to deal with changing folder in incremental mode.
+ File perStreamDexFolder = null;
+ if (needMerge) {
+ perStreamDexFolder = intermediateFolder;
+
+ if (!isIncremental) {
+ FileUtils.deleteFolder(perStreamDexFolder);
+ }
+ }
+
+ // dex all the different streams separately, then merge later (maybe)
+ // hash to detect duplicate jars (due to isse with library and tests)
+ final Set<String> hashs = Sets.newHashSet();
+ // input files to output file map
+ final Map<File, File> inputFiles = Maps.newHashMap();
+ // stuff to delete. Might be folders.
+ final List<File> deletedFiles = Lists.newArrayList();
+
+ // first gather the different inputs to be dexed separately.
+ for (DirectoryInput directoryInput : directoryInputs) {
+ File rootFolder = directoryInput.getFile();
+ // The incremental mode only detect file level changes.
+ // It does not handle removed root folders. However the transform
+ // task will add the TransformInput right after it's removed so that it
+ // can be detected by the transform.
+ if (!rootFolder.exists()) {
+ // if the root folder is gone we need to remove the previous
+ // output
+ File preDexedFile = getPreDexFile(outputProvider, needMerge, perStreamDexFolder,
+ directoryInput);
+ if (preDexedFile.exists()) {
+ deletedFiles.add(preDexedFile);
+ }
+ } else if (!isIncremental || !directoryInput.getChangedFiles().isEmpty()) {
+ // add the folder for re-dexing only if we're not in incremental
+ // mode or if it contains changed files.
+ File preDexFile = getPreDexFile(outputProvider, needMerge, perStreamDexFolder,
+ directoryInput);
+ inputFiles.put(rootFolder, preDexFile);
+ }
+ }
+
+ for (JarInput jarInput : jarInputs) {
+ switch (jarInput.getStatus()) {
+ case NOTCHANGED:
+ if (isIncremental) {
+ break;
+ }
+ // intended fall-through
+ case CHANGED:
+ case ADDED: {
+ File preDexFile = getPreDexFile(outputProvider, needMerge, perStreamDexFolder,
+ jarInput);
+ inputFiles.put(jarInput.getFile(), preDexFile);
+ break;
+ }
+ case REMOVED: {
+ File preDexedFile = getPreDexFile(outputProvider, needMerge, perStreamDexFolder,
+ jarInput);
+ if (preDexedFile.exists()) {
+ deletedFiles.add(preDexedFile);
+ }
+ break;
+ }
+ }
+ }
+
+ WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
+
+ for (Map.Entry<File, File> entry : inputFiles.entrySet()) {
+ Callable<Void> action = new PreDexTask(
+ entry.getKey(),
+ entry.getValue(),
+ hashs,
+ outputHandler);
+ executor.execute(action);
+ }
+
+ for (final File file : deletedFiles) {
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ FileUtils.deleteFolder(file);
+ return null;
+ }
+ });
+ }
+
+ executor.waitForTasksWithQuickFail(false);
+
+ if (needMerge) {
+ File outputDir = outputProvider.getContentLocation("main",
+ TransformManager.CONTENT_DEX, getScopes(),
+ Format.DIRECTORY);
+ FileUtils.mkdirs(outputDir);
+
+ // first delete the output folder where the final dex file(s) will be.
+ FileUtils.emptyFolder(outputDir);
+ mkdirs(outputDir);
+
+ // find the inputs of the dex merge.
+ // they are the content of the intermediate folder.
+ List<File> outputs = null;
+ if (!multiDex) {
+ // content of the folder is jar files.
+ File[] files = intermediateFolder.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+ return name.endsWith(SdkConstants.DOT_JAR);
+ }
+ });
+ if (files != null) {
+ outputs = Arrays.asList(files);
+ }
+ } else {
+ File[] directories = intermediateFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isDirectory();
+ }
+ });
+ if (directories != null) {
+ outputs = Arrays.asList(directories);
+ }
+ }
+
+ if (outputs == null) {
+ throw new RuntimeException("No dex files to merge!");
+ }
+
+ androidBuilder.convertByteCode(
+ outputs,
+ outputDir,
+ multiDex,
+ mainDexListFile,
+ dexOptions,
+ null,
+ false,
+ true,
+ outputHandler,
+ false /* instantRunMode */);
+ }
+ }
+ } catch (LoggedErrorException e) {
+ throw new TransformException(e);
+ } catch (ProcessException e) {
+ throw new TransformException(e);
+ } catch (Exception e) {
+ throw new TransformException(e);
+ }
+ }
+
+ private final class PreDexTask implements Callable<Void> {
+ @NonNull
+ private final File from;
+ @NonNull
+ private final File to;
+ @NonNull
+ private final Set<String> hashs;
+ @NonNull
+ private final ProcessOutputHandler mOutputHandler;
+
+ private PreDexTask(
+ @NonNull File from,
+ @NonNull File to,
+ @NonNull Set<String> hashs,
+ @NonNull ProcessOutputHandler outputHandler) {
+ this.from = from;
+ this.to = to;
+ this.hashs = hashs;
+ this.mOutputHandler = outputHandler;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ // TODO remove once we can properly add a library as a dependency of its test.
+ String hash = getFileHash(from);
+
+ synchronized (hashs) {
+ if (hashs.contains(hash)) {
+ return null;
+ }
+
+ hashs.add(hash);
+ }
+
+ if (to.isDirectory()) {
+ FileUtils.emptyFolder(to);
+ } else if (to.isFile()) {
+ FileUtils.delete(to);
+ } else {
+ if (multiDex) {
+ mkdirs(to);
+ } else {
+ mkdirs(to.getParentFile());
+ }
+ }
+
+ androidBuilder.preDexLibrary(
+ from, to, multiDex, dexOptions, mOutputHandler);
+
+ for (File file : Files.fileTreeTraverser().breadthFirstTraversal(to)) {
+ if (file.isFile()) {
+ instantRunBuildContext.addChangedFile(FileType.DEX, file);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Returns the hash of a file.
+ *
+ * If the file is a folder, it's a hash of its path. If the file is a file, then
+ * it's a hash of the file itself.
+ *
+ * @param file the file to hash
+ */
+ @NonNull
+ private static String getFileHash(@NonNull File file) throws IOException {
+ HashCode hashCode;
+ HashFunction hashFunction = Hashing.sha1();
+ if (file.isDirectory()) {
+ hashCode = hashFunction.hashString(file.getPath(), Charsets.UTF_16LE);
+ } else {
+ hashCode = Files.hash(file, hashFunction);
+ }
+
+ return hashCode.toString();
+ }
+
+
+ @NonNull
+ private File getPreDexFile(
+ @NonNull TransformOutputProvider output,
+ boolean needMerge,
+ @Nullable File outFolder,
+ @NonNull QualifiedContent qualifiedContent) {
+ if (needMerge) {
+ checkNotNull(outFolder);
+ return new File(outFolder, getFilename(qualifiedContent.getFile()));
+ } else {
+ return getOutputLocation(output, qualifiedContent, qualifiedContent.getFile());
+ }
+ }
+
+ @NonNull
+ private File getOutputLocation(
+ @NonNull TransformOutputProvider output,
+ @NonNull QualifiedContent qualifiedContent,
+ @NonNull File file) {
+ // In InstantRun mode, all files are guaranteed to have a unique name due to the slicer
+ // transform. adding sha1 to the name can lead to cleaning issues in device, it's much
+ // easier if the slices always have the same names, irrespective of the current vairiant,
+ // last version wins.
+ String name = instantRunBuildContext.isInInstantRunMode()
+ && (qualifiedContent.getScopes().contains(Scope.PROJECT)
+ || qualifiedContent.getScopes().contains(Scope.SUB_PROJECTS))
+ ? file.getName() : getFilename(file);
+ File contentLocation = output.getContentLocation(name,
+ TransformManager.CONTENT_DEX, qualifiedContent.getScopes(),
+ multiDex ? Format.DIRECTORY : Format.JAR);
+ if (multiDex) {
+ FileUtils.mkdirs(contentLocation);
+ } else {
+ FileUtils.mkdirs(contentLocation.getParentFile());
+ }
+ return contentLocation;
+ }
+
+ @NonNull
+ private String getFilename(@NonNull File inputFile) {
+ // If multidex is enabled, this name will be used for a folder and classes*.dex files will
+ // inside of it.
+ String suffix = multiDex ? "" : SdkConstants.DOT_JAR;
+
+ return FileUtils.getDirectoryNameForJar(inputFile) + suffix;
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ExtractJarsTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ExtractJarsTransform.java
new file mode 100644
index 0000000..8ec9ac6
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ExtractJarsTransform.java
@@ -0,0 +1,295 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.utils.FileUtils.delete;
+import static com.android.utils.FileUtils.emptyFolder;
+import static com.android.utils.FileUtils.mkdirs;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.android.utils.FileUtils;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Transform to extract jars.
+ *
+ */
+public class ExtractJarsTransform extends Transform {
+
+ @NonNull
+ private final Set<ContentType> contentTypes;
+ @NonNull
+ private final Set<Scope> scopes;
+
+ public ExtractJarsTransform(
+ @NonNull Set<ContentType> contentTypes,
+ @NonNull Set<Scope> scopes) {
+ this.contentTypes = contentTypes;
+ this.scopes = scopes;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "extractJars";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return contentTypes;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return scopes;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(TransformInvocation transformInvocation)
+ throws IOException, TransformException, InterruptedException {
+ TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
+ boolean isIncremental = transformInvocation.isIncremental();
+ checkNotNull(outputProvider, "Missing output object for transform " + getName());
+
+ // as_input transform and no referenced scopes, all the inputs will in InputOutputStreams.
+ final boolean extractCode = contentTypes.contains(DefaultContentType.CLASSES);
+
+ Logger logger = Logging.getLogger(ExtractJarsTransform.class);
+
+ if (!isIncremental) {
+ outputProvider.deleteAll();
+ }
+
+ try {
+ WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
+
+ for (TransformInput input : transformInvocation.getInputs()) {
+ for (DirectoryInput dirInput : input.getDirectoryInputs()) {
+ File dirOutput = outputProvider.getContentLocation(dirInput.getName()
+ + "-" + dirInput.getFile().getAbsolutePath().hashCode(),
+ dirInput.getContentTypes(),
+ dirInput.getScopes(),
+ Format.DIRECTORY);
+ org.apache.commons.io.FileUtils.copyDirectory(dirInput.getFile(), dirOutput);
+ }
+
+ for (JarInput jarInput : input.getJarInputs()) {
+ final File jarFile = jarInput.getFile();
+
+ // create an output folder for this jar, keeping its type and scopes.
+ final File outJarFolder = outputProvider.getContentLocation(
+ jarFile.getName() + "-" + jarFile.getPath().hashCode(),
+ jarInput.getContentTypes(),
+ jarInput.getScopes(),
+ Format.DIRECTORY);
+ FileUtils.mkdirs(outJarFolder);
+
+ if (!isIncremental) {
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ extractJar(outJarFolder, jarFile, extractCode);
+ return null;
+ }
+ });
+ } else {
+ switch (jarInput.getStatus()) {
+ case CHANGED:
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ emptyFolder(outJarFolder);
+ extractJar(outJarFolder, jarFile, extractCode);
+ return null;
+ }
+ });
+ break;
+ case ADDED:
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ extractJar(outJarFolder, jarFile, extractCode);
+ return null;
+ }
+ });
+ break;
+ case REMOVED:
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ delete(outJarFolder);
+ return null;
+ }
+ });
+ break;
+ }
+ }
+ }
+ }
+
+ executor.waitForTasksWithQuickFail(true);
+
+ } catch (InterruptedException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new TransformException(e);
+ }
+ }
+
+ private static void extractJar(
+ @NonNull File outJarFolder,
+ @NonNull File jarFile,
+ boolean extractCode) throws IOException {
+ mkdirs(outJarFolder);
+
+ Closer closer = Closer.create();
+ try {
+ FileInputStream fis = closer.register(new FileInputStream(jarFile));
+ ZipInputStream zis = closer.register(new ZipInputStream(fis));
+ // loop on the entries of the intermediary package and put them in the final package.
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ try {
+ String name = entry.getName();
+
+ // do not take directories
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ Action action = getAction(name, extractCode);
+ if (action == Action.COPY) {
+ File outputFile = new File(outJarFolder,
+ name.replace('/', File.separatorChar));
+ mkdirs(outputFile.getParentFile());
+
+ Closer closer2 = Closer.create();
+ try {
+ java.io.OutputStream outputStream = closer2.register(
+ new BufferedOutputStream(new FileOutputStream(outputFile)));
+ ByteStreams.copy(zis, outputStream);
+ outputStream.flush();
+ } finally {
+ closer2.close();
+ }
+ }
+ } finally {
+ zis.closeEntry();
+ }
+ }
+
+ } finally {
+ closer.close();
+ }
+ }
+
+ /**
+ * Define all possible actions for a Jar file entry.
+ */
+ enum Action {
+ /**
+ * Copy the file to the output destination.
+ */
+ COPY,
+ /**
+ * Ignore the file.
+ */
+ IGNORE
+ }
+
+ /**
+ * Provides an {@link Action} for the archive entry.
+ * @param archivePath the archive entry path in the archive.
+ * @param extractCode whether to extractCode
+ * @return the action to implement.
+ */
+ @NonNull
+ public static Action getAction(@NonNull String archivePath, boolean extractCode) {
+ // Manifest files are never merged.
+ if (JarFile.MANIFEST_NAME.equals(archivePath)) {
+ return Action.IGNORE;
+ }
+
+ // split the path into segments.
+ String[] segments = archivePath.split("/");
+
+ // empty path? skip to next entry.
+ if (segments.length == 0) {
+ return Action.IGNORE;
+ }
+
+ // Check each folders to make sure they should be included.
+ // Folders like CVS, .svn, etc.. should already have been excluded from the
+ // jar file, but we need to exclude some other folder (like /META-INF) so
+ // we check anyway.
+ for (int i = 0 ; i < segments.length - 1; i++) {
+ if (!PackagingUtils.checkFolderForPackaging(segments[i])) {
+ return Action.IGNORE;
+ }
+ }
+
+ // get the file name from the path
+ String fileName = segments[segments.length-1];
+
+ return PackagingUtils.checkFileForPackaging(fileName, extractCode)
+ ? Action.COPY
+ : Action.IGNORE;
+ }
+
+ @NonNull
+ private static File getFolder(
+ @NonNull File outFolder,
+ @NonNull File jarFile) {
+ return new File(outFolder, jarFile.getName() + "-" + jarFile.getPath().hashCode());
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/FileFilter.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/FileFilter.java
new file mode 100644
index 0000000..92e8b2b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/FileFilter.java
@@ -0,0 +1,338 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.utils.FileUtils.deleteIfExists;
+import static com.android.utils.FileUtils.mkdirs;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.builder.signing.SignedJarBuilder;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Defines a file filter contract which will use {@link PackagingOptions} to take appropriate
+ * action.
+ */
+ at VisibleForTesting
+public class FileFilter implements SignedJarBuilder.IZipEntryFilter {
+
+ public static class SubStream {
+ private final File folder;
+ private final String name;
+
+ SubStream(File folder, String name) {
+ this.folder = folder;
+ this.name = name;
+ }
+
+ public File getFolder() {
+ return folder;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @Nullable
+ private final PackagingOptions packagingOptions;
+ @NonNull
+ private final List<SubStream> expandedFolders;
+
+ public FileFilter(
+ @NonNull List<SubStream> expandedFolders,
+ @Nullable PackagingOptions packagingOptions) {
+ this.expandedFolders = ImmutableList.copyOf(expandedFolders);
+ this.packagingOptions = packagingOptions;
+ }
+
+ /**
+ * Implementation of the {@link SignedJarBuilder.IZipEntryFilter} contract which only
+ * cares about copying or ignoring files since merging is handled differently.
+ * @param archivePath the archive file path of the entry
+ * @return true if the archive entry satisfies the filter, false otherwise.
+ * @throws ZipAbortException
+ */
+ @Override
+ public boolean checkEntry(@NonNull String archivePath)
+ throws ZipAbortException {
+ PackagingOptions.Action action = getPackagingAction(archivePath);
+ switch(action) {
+ case EXCLUDE:
+ return false;
+ case PICK_FIRST:
+ List<File> allFiles = getAllFiles(archivePath);
+ return allFiles.isEmpty();
+ case MERGE:
+ case NONE:
+ return true;
+ default:
+ throw new RuntimeException("Unhandled action " + action);
+ }
+ }
+
+ /**
+ * Notification of an incremental file changed since last successful run of the task.
+ *
+ * Usually, we just copy the changed file into the merged folder. However, if the user
+ * specified {@link PackagingOptions.Action#PICK_FIRST}, the file will only be copied if it the
+ * first pick. Also, if the user specified {@link PackagingOptions.Action#MERGE}, all the files
+ * with the same entry archive path will be re-merged.
+ *
+ * @param outputDir merged resources folder.
+ * @param changedFile changed file located in a temporary expansion folder
+ * @throws IOException
+ */
+ void handleChanged(@NonNull File outputDir, @NonNull File changedFile)
+ throws IOException {
+ String archivePath = getArchivePath(changedFile);
+ PackagingOptions.Action action = getPackagingAction(archivePath);
+ switch (action) {
+ case EXCLUDE:
+ return;
+ case MERGE:
+ // one of the merged file has changed, re-merge all of them.
+ mergeAll(outputDir, archivePath);
+ return;
+ case PICK_FIRST:
+ copy(changedFile, outputDir, archivePath);
+ return;
+ case NONE:
+ copy(changedFile, outputDir, archivePath);
+ }
+ }
+
+ /**
+ * Notification of a file removal.
+ *
+ * file was removed, we need to check that it was not a pickFirst item (since we
+ * may now need to pick another one) or a merged item since we would need to re-merge
+ * all remaining items.
+ *
+ * @param outputDir expected merged output directory.
+ * @param removedFilePath removed file path from the temporary resources folders.
+ * @throws IOException
+ */
+ public void handleRemoved(@NonNull File outputDir, @NonNull String removedFilePath)
+ throws IOException {
+
+
+ // first delete the output file, it will be eventually replaced.
+ File outFile = new File(outputDir, removedFilePath);
+ if (outFile.exists()) {
+ if (!outFile.delete()) {
+ throw new IOException("Cannot delete " + outFile.getAbsolutePath());
+ }
+ }
+ PackagingOptions.Action itemAction = getPackagingAction(removedFilePath);
+
+ switch(itemAction) {
+ case NONE:
+ case PICK_FIRST:
+ // this was a picked up item, make sure we copy the first still available
+ com.google.common.base.Optional<File> firstPick = getFirstPick(removedFilePath);
+ if (firstPick.isPresent()) {
+ copy(firstPick.get(), outputDir, removedFilePath);
+ }
+ return;
+ case MERGE:
+ // re-merge all
+ mergeAll(outputDir, removedFilePath);
+ return;
+ case EXCLUDE:
+ // do nothing
+ return;
+ default:
+ throw new RuntimeException("Unhandled package option"
+ + itemAction);
+
+ }
+ }
+
+ private static void copy(@NonNull File inputFile,
+ @NonNull File outputDir,
+ @NonNull String archivePath) throws IOException {
+
+ File outputFile = new File(outputDir, archivePath);
+ mkdirs(outputFile.getParentFile());
+ Files.copy(inputFile, outputFile);
+ }
+
+ private void mergeAll(@NonNull File outputDir, @NonNull String archivePath)
+ throws IOException {
+
+ File outputFile = new File(outputDir, archivePath);
+ deleteIfExists(outputFile);
+ mkdirs(outputFile.getParentFile());
+ List<File> allFiles = getAllFiles(archivePath);
+ if (!allFiles.isEmpty()) {
+ OutputStream os = null;
+ try {
+ os = new BufferedOutputStream(new FileOutputStream(outputFile));
+ // take each file in order and merge them.
+ for (File file : allFiles) {
+ Files.copy(file, os);
+ }
+ } finally {
+ if (os != null) {
+ os.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the first file from the temporary expansion folders that satisfy the archive path.
+ * @param archivePath the entry archive path.
+ * @return the first file reference of {@link com.google.common.base.Optional#absent()} if
+ * none exist in any temporary expansion folders.
+ */
+ @NonNull
+ private com.google.common.base.Optional<File> getFirstPick(
+ @NonNull final String archivePath) {
+
+ return com.google.common.base.Optional.fromNullable(
+ forEachExpansionFolder(new FolderAction() {
+ @Nullable
+ @Override
+ public File on(File folder) {
+ File expandedFile = new File(folder, archivePath);
+ if (expandedFile.exists()) {
+ return expandedFile;
+ }
+ return null;
+ }
+ }));
+ }
+
+ /**
+ * Returns all files from temporary expansion folders with the same archive path.
+ * @param archivePath the entry archive path.
+ * @return a list possibly empty of {@link File}s that satisfy the archive path.
+ */
+ @NonNull
+ private List<File> getAllFiles(@NonNull final String archivePath) {
+ final ImmutableList.Builder<File> matchingFiles = ImmutableList.builder();
+ forEachExpansionFolder(new FolderAction() {
+ @Nullable
+ @Override
+ public File on(File folder) {
+ File expandedFile = new File(folder, archivePath);
+ if (expandedFile.exists()) {
+ matchingFiles.add(expandedFile);
+ }
+ return null;
+ }
+ });
+ return matchingFiles.build();
+ }
+
+ /**
+ * An action on a folder.
+ */
+ private interface FolderAction {
+
+ /**
+ * Perform an action on a folder and stop the processing if something is returned
+ * @param folder the folder to perform the action on.
+ * @return a file to stop processing or null to continue to the next expansion folder
+ * if any.
+ */
+ @Nullable
+ File on(File folder);
+ }
+
+ /**
+ * Perform the passed action on each expansion folder.
+ * @param action the action to perform on each folder.
+ * @return a file if any action returned a value, or null if none returned a value.
+ */
+ @Nullable
+ private File forEachExpansionFolder(@NonNull FolderAction action) {
+ for (SubStream subStream : expandedFolders) {
+ if (subStream.getFolder().isDirectory()) {
+ File value = action.on(subStream.getFolder());
+ if (value != null) {
+ return value;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the expansion folder for an expanded file. This represents the location
+ * where the packaged jar our source directories java resources were extracted into.
+ * @param expandedFile the java resource file.
+ * @return the expansion folder used to extract the java resource into.
+ */
+ @NonNull
+ private File getExpansionFolder(@NonNull final File expandedFile) {
+ File expansionFolder = forEachExpansionFolder(new FolderAction() {
+ @Nullable
+ @Override
+ public File on(File folder) {
+ return expandedFile.getAbsolutePath().startsWith(folder.getAbsolutePath())
+ ? folder : null;
+ }
+ });
+ if (expansionFolder == null) {
+ throw new RuntimeException("Cannot determine expansion folder for " + expandedFile
+ + " with folders " + Joiner.on(",").join(expandedFolders));
+ }
+ return expansionFolder;
+ }
+
+ /**
+ * Determines the archive entry path relative to its expansion folder. The archive entry
+ * path is the path that was used to save the entry in the original .jar file that got
+ * expanded in the expansion folder.
+ * @param expandedFile the expanded file to find the relative archive entry from.
+ * @return the expanded file relative path to its expansion folder.
+ */
+ @NonNull
+ private String getArchivePath(@NonNull File expandedFile) {
+ File expansionFolder = getExpansionFolder(expandedFile);
+ return expandedFile.getAbsolutePath()
+ .substring(expansionFolder.getAbsolutePath().length() + 1);
+ }
+
+ /**
+ * Determine the user's intention for a particular archive entry.
+ * @param archivePath the archive entry
+ * @return a {@link PackagingOptions.Action} as provided by the user in the build.gradle
+ */
+ @NonNull
+ private PackagingOptions.Action getPackagingAction(@NonNull String archivePath) {
+ if (packagingOptions != null) {
+ return packagingOptions.getAction(archivePath);
+ }
+ return PackagingOptions.Action.NONE;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunBuildType.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunBuildType.java
new file mode 100644
index 0000000..c73b5fb
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunBuildType.java
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.scope.VariantScope;
+
+import java.io.File;
+
+/**
+ * Expected dex file use.
+ */
+public enum InstantRunBuildType {
+ /**
+ * dex file will contain files that can be used to reload classes in a running application.
+ */
+ RELOAD {
+ @NonNull
+ @Override
+ File getOutputFolder(VariantScope variantScope) {
+ return variantScope.getReloadDexOutputFolder();
+ }
+ },
+ /**
+ * dex file will contain the delta files (from the last incremental build) that can be used
+ * to restart the application
+ */
+ RESTART {
+ @NonNull
+ @Override
+ File getOutputFolder(VariantScope variantScope) {
+ return variantScope.getRestartDexOutputFolder();
+ }
+ };
+
+ @NonNull
+ abstract File getOutputFolder(VariantScope variantScope);
+
+ @NonNull
+ public File getIncrementalChangesFile(VariantScope variantScope) {
+ return new File(variantScope.getInstantRunSupportDir(),
+ name().toLowerCase() + "-changes.txt");
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunDex.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunDex.java
new file mode 100644
index 0000000..55952b1
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunDex.java
@@ -0,0 +1,312 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.api.transform.SecondaryFile;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.tasks.ColdswapArtifactsKickerTask;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.DexOptions;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessException;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.base.CaseFormat;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.Logger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Non incremental transform that looks for a file named "incrementalChanges.txt" in its input
+ * and will use the file's content to get a list of .class files to dex in order to produce an
+ * incremental dex file that can contains the delta changes from the last invocation.
+ */
+public class InstantRunDex extends Transform {
+
+ @NonNull
+ private final AndroidBuilder androidBuilder;
+
+ @NonNull
+ private final DexOptions dexOptions;
+
+ @NonNull
+ private final ILogger logger;
+
+ @NonNull
+ private final Set<QualifiedContent.ContentType> inputTypes;
+
+ @NonNull
+ private final InstantRunBuildType buildType;
+
+ @NonNull
+ private final VariantScope variantScope;
+
+ @NonNull
+ private final InstantRunBuildContext instantRunBuildContext;
+
+ public InstantRunDex(
+ @NonNull VariantScope variantScope,
+ @NonNull InstantRunBuildType buildType,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull DexOptions dexOptions,
+ @NonNull Logger logger,
+ @NonNull Set<QualifiedContent.ContentType> inputTypes) {
+ this.variantScope = variantScope;
+ this.buildType = buildType;
+ this.androidBuilder = androidBuilder;
+ this.dexOptions = dexOptions;
+ this.logger = new LoggerWrapper(logger);
+ this.inputTypes = inputTypes;
+ this.instantRunBuildContext = variantScope.getInstantRunBuildContext();
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+
+ File outputFolder = buildType.getOutputFolder(variantScope);
+
+ boolean changesAreCompatible =
+ variantScope.getInstantRunBuildContext().hasPassedVerification();
+ boolean restartDexRequested =
+ variantScope.getGlobalScope().isActive(OptionalCompilationStep.RESTART_ONLY);
+
+ switch(buildType) {
+ case RELOAD:
+ if (!changesAreCompatible || restartDexRequested) {
+ FileUtils.emptyFolder(outputFolder);
+ return;
+ }
+ break;
+ case RESTART:
+ if (changesAreCompatible && !restartDexRequested) {
+ // do nothing, let the incrementalChanges.txt accumulate all changes until
+ // we are asked to produce a restart.dex or the verifier flagged the changes.
+ if (outputFolder.exists()) {
+ for (File file : outputFolder.listFiles()) {
+ if (!file.getName().equals("build-info.xml") &&
+ !file.delete()) {
+ logger.warning("Cannot delete " + file);
+ }
+ }
+ }
+ return;
+ } else {
+ logger.warning("InstantRun incompatible change detected for API 20 or below,"
+ + " full APK rebuild necessary, aborting");
+
+ // abort the build.
+ instantRunBuildContext.abort();
+
+ File incremental = buildType.getIncrementalChangesFile(variantScope);
+ if (incremental.exists()) {
+ incremental.delete();
+ }
+ return;
+ }
+ default:
+ throw new RuntimeException("Unhandled build type " + buildType);
+ }
+
+ // create a tmp jar file.
+ File classesJar = new File(outputFolder, "classes.jar");
+ if (classesJar.exists()) {
+ classesJar.delete();
+ }
+ Files.createParentDirs(classesJar);
+ final JarClassesBuilder jarClassesBuilder = getJarClassBuilder(classesJar);
+
+ try {
+
+ for (TransformInput input : invocation.getReferencedInputs()) {
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ if (!directoryInput.getContentTypes().containsAll(inputTypes)) {
+ continue;
+ }
+ final File folder = directoryInput.getFile();
+ File incremental = buildType.getIncrementalChangesFile(variantScope);
+ if (!incremental.exists()) {
+ // done
+ continue;
+ }
+ ChangeRecords.process(incremental,
+ new ChangeRecords.RecordHandler() {
+ @Override
+ public void handle(String filePath, Status status)
+ throws IOException {
+ // todo: check that file to process belongs to folder.
+ jarClassesBuilder.add(folder, new File(filePath));
+ }
+ });
+ }
+ }
+ } finally {
+ jarClassesBuilder.close();
+ }
+
+ // if no files were added, clean up and return.
+ if (jarClassesBuilder.isEmpty()) {
+ FileUtils.emptyFolder(outputFolder);
+ if (!classesJar.delete()) {
+ logger.warning("Cannot delete tmp file : " + classesJar.getAbsolutePath());
+ }
+ return;
+ }
+ final ImmutableList.Builder<File> inputFiles = ImmutableList.builder();
+ inputFiles.add(classesJar);
+
+ try {
+ variantScope.getInstantRunBuildContext().startRecording(
+ InstantRunBuildContext.TaskType.INSTANT_RUN_DEX);
+ convertByteCode(inputFiles.build(), outputFolder);
+ variantScope.getInstantRunBuildContext().addChangedFile(
+ buildType == InstantRunBuildType.RELOAD
+ ? InstantRunBuildContext.FileType.RELOAD_DEX
+ : InstantRunBuildContext.FileType.RESTART_DEX,
+ new File(outputFolder, "classes.dex"));
+ } catch (ProcessException e) {
+ throw new TransformException(e);
+ } finally {
+ variantScope.getInstantRunBuildContext().stopRecording(
+ InstantRunBuildContext.TaskType.INSTANT_RUN_DEX);
+ }
+ }
+
+ @VisibleForTesting
+ static class JarClassesBuilder {
+ final File outputFile;
+ JarOutputStream jarOutputStream;
+ boolean empty = true;
+
+ JarClassesBuilder(File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ void add(File inputDir, File file) throws IOException {
+ if (jarOutputStream == null) {
+ jarOutputStream = new JarOutputStream(new FileOutputStream(outputFile));
+ }
+ empty = false;
+ copyFileInJar(inputDir, file, jarOutputStream);
+ }
+
+ void close() throws IOException {
+ if (jarOutputStream != null) {
+ jarOutputStream.close();
+ }
+ }
+
+ boolean isEmpty() {
+ return empty;
+ }
+ }
+
+ @VisibleForTesting
+ protected void convertByteCode(List<File> inputFiles, File outputFolder)
+ throws InterruptedException, ProcessException, IOException {
+ androidBuilder.convertByteCode(inputFiles,
+ outputFolder,
+ false /* multiDexEnabled */,
+ null /*getMainDexListFile */,
+ dexOptions,
+ ImmutableList.<String>of() /* getAdditionalParameters */,
+ false /* incremental */,
+ true /* optimize */,
+ new LoggedProcessOutputHandler(logger),
+ true);
+ }
+
+ @VisibleForTesting
+ protected JarClassesBuilder getJarClassBuilder(File outputFile) {
+ return new JarClassesBuilder(outputFile);
+ }
+
+ private static void copyFileInJar(File inputDir, File inputFile, JarOutputStream jarOutputStream)
+ throws IOException {
+
+ String entryName = inputFile.getPath().substring(inputDir.getPath().length() + 1);
+ JarEntry jarEntry = new JarEntry(entryName);
+ jarOutputStream.putNextEntry(jarEntry);
+ Files.copy(inputFile, jarOutputStream);
+ jarOutputStream.closeEntry();
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "instant+" + CaseFormat.UPPER_UNDERSCORE.to(
+ CaseFormat.LOWER_CAMEL, buildType.name()) + "Dex";
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.ContentType> getInputTypes() {
+ return inputTypes;
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.Scope> getScopes() {
+ return ImmutableSet.of();
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.Scope> getReferencedScopes() {
+ return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT);
+ }
+
+ @NonNull
+ @Override
+ public Collection<SecondaryFile> getSecondaryFiles() {
+ return buildType == InstantRunBuildType.RESTART
+ ? ImmutableList.of(new SecondaryFile(
+ ColdswapArtifactsKickerTask.ConfigAction.getMarkerFile(variantScope),
+ true /* supportsIncrementalChange */))
+ : ImmutableList.<SecondaryFile>of();
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunSlicer.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunSlicer.java
new file mode 100644
index 0000000..712be8d
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunSlicer.java
@@ -0,0 +1,560 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.V1_6;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.SecondaryFile;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.tasks.ColdswapArtifactsKickerTask;
+import com.android.build.gradle.tasks.MarkerFile;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.Logger;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Transform that slices the project's classes into approximately 10 slices for
+ * {@link Scope#PROJECT} and {@link Scope#SUB_PROJECTS}</ul>
+ *
+ * Dependencies are not processed by the Slicer but will be dex'ed separately.
+ *
+ *
+ */
+public class InstantRunSlicer extends Transform {
+
+ public static final String MAIN_SLICE_NAME = "main_slice";
+
+ @VisibleForTesting
+ static final String PACKAGE_FOR_GUARD_CLASSS = "com/android/tools/fd/dummy";
+
+ // since we use the last digit of the FQCN hashcode() as the bucket, 10 is the appropriate
+ // number of slices.
+ public static final int NUMBER_OF_SLICES_FOR_PROJECT_CLASSES = 10;
+
+ private final ILogger logger;
+
+ @NonNull
+ private final VariantScope variantScope;
+
+ public InstantRunSlicer(@NonNull Logger logger, @NonNull VariantScope variantScope) {
+ this.logger = new LoggerWrapper(logger);
+ this.variantScope = variantScope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "instantRunSlicer";
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.ContentType> getOutputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return EnumSet.of(Scope.PROJECT, Scope.SUB_PROJECTS);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(@NonNull TransformInvocation transformInvocation)
+ throws IOException, TransformException, InterruptedException {
+
+ TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
+ boolean isIncremental = transformInvocation.isIncremental();
+ Collection<TransformInput> inputs = transformInvocation.getInputs();
+ if (outputProvider == null) {
+ logger.error(null /* throwable */, "null TransformOutputProvider for InstantRunSlicer");
+ return;
+ }
+
+ // if we are blocked, do not proceed with execution.
+ if (MarkerFile.Command.BLOCK == MarkerFile.readMarkerFile(
+ ColdswapArtifactsKickerTask.ConfigAction.getMarkerFile(variantScope))) {
+ return;
+ }
+ Slices slices = new Slices();
+
+ if (isIncremental) {
+ sliceIncrementally(inputs, outputProvider, slices);
+ } else {
+ slice(inputs, outputProvider, slices);
+ combineAllJars(inputs, outputProvider);
+ }
+ }
+
+ /**
+ * Combine all {@link Scope#PROJECT} and {@link Scope#SUB_PROJECTS} inputs into slices, ignore
+ * all other inputs.
+ *
+ * @param inputs the transform's input
+ * @param outputProvider the transform's output provider to create streams
+ * @throws IOException if the files cannot be copied
+ * @throws TransformException never thrown
+ * @throws InterruptedException never thrown.
+ */
+ private void slice(@NonNull Collection<TransformInput> inputs,
+ @NonNull TransformOutputProvider outputProvider,
+ @NonNull Slices slices)
+ throws IOException, TransformException, InterruptedException {
+
+ File dependenciesLocation =
+ getDependenciesSliceOutputFolder(outputProvider, Format.DIRECTORY);
+
+ // first path, gather all input files, organize per package.
+ for (TransformInput input : inputs) {
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+
+ File inputDir = directoryInput.getFile();
+ FluentIterable<File> files = Files.fileTreeTraverser()
+ .breadthFirstTraversal(directoryInput.getFile());
+
+ for (File file : files) {
+ if (file.isDirectory()) {
+ continue;
+ }
+ String packagePath = FileUtils
+ .relativePossiblyNonExistingPath(file.getParentFile(), inputDir);
+
+ if (directoryInput.getScopes().contains(Scope.PROJECT) ||
+ directoryInput.getScopes().contains(Scope.SUB_PROJECTS)) {
+
+ slices.addElement(packagePath, file);
+ } else {
+ // dump in a single directory that may end up producing several .dex in case
+ // we are over 65K limit.
+ File outputFile = new File(dependenciesLocation,
+ new File(packagePath, file.getName()).getPath());
+ Files.createParentDirs(outputFile);
+ Files.copy(file, outputFile);
+ }
+ }
+ }
+ }
+
+ // now produces the output streams for each slice.
+ slices.writeTo(outputProvider);
+ }
+
+ /**
+ *
+ * WARNING : This code is no longer active as the transform scope if only PROJECT and
+ * SUB_PROJECTS but keeping it around in case it becomes useful again with a split APK solution.
+ *
+ * Combine all input jars with a different scope than {@link Scope#PROJECT} and
+ * {@link Scope#SUB_PROJECTS}
+ * @param inputs the transform's input
+ * @param outputProvider the transform's output provider to create streams
+ * @throws IOException if jar files cannot be created successfully
+ */
+ private void combineAllJars(@NonNull Collection<TransformInput> inputs,
+ @NonNull TransformOutputProvider outputProvider) throws IOException {
+
+ List<JarInput> jarFilesToProcess = new ArrayList<JarInput>();
+ for (TransformInput input : inputs) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ File jarFile = jarInput.getFile();
+ // handle separately instant-run runtime and the generated AppInfo so it is
+ // directly packaged in the APK and can be loaded successfully by Android runtime.
+ // This is obviously not very clean but until the Transform API can provide a way
+ // to attach some metadata to streams.
+ if (jarFile.getName().equals("instant-run.jar")) {
+ // this gets packaged in the main slice.
+ File mainSliceOutput = getMainSliceOutputFolder(outputProvider, null);
+ Files.copy(jarFile, mainSliceOutput);
+ } else if (jarFile.getAbsolutePath().contains("incremental-classes")) {
+ File mainSliceOutput = getMainSliceOutputFolder(outputProvider, "b");
+ Files.copy(jarFile, mainSliceOutput);
+ } else {
+ // otherwise, all other dependencies will be combined right below.
+ if (jarInput.getFile().exists()) {
+ jarFilesToProcess.add(jarInput);
+ }
+ }
+ }
+ }
+ if (jarFilesToProcess.isEmpty()) {
+ return;
+ }
+
+ // all the jar files will be combined into a single jar will all other scopes.
+ File dependenciesJar = getDependenciesSliceOutputFolder(outputProvider, Format.JAR);
+ Set<String> entries = new HashSet<String>();
+
+ Files.createParentDirs(dependenciesJar);
+ JarOutputStream jarOutputStream = new JarOutputStream(
+ new BufferedOutputStream(new FileOutputStream(dependenciesJar)));
+ try {
+ for (JarInput jarInput : jarFilesToProcess) {
+ mergeJarInto(jarInput, entries, jarOutputStream);
+ }
+ } finally {
+ jarOutputStream.close();
+ }
+ }
+
+ private void mergeJarInto(@NonNull JarInput jarInput,
+ @NonNull Set<String> entries, @NonNull JarOutputStream dependenciesJar)
+ throws IOException {
+
+ JarFile jarFile = new JarFile(jarInput.getFile());
+ try {
+ Enumeration<JarEntry> jarEntries = jarFile.entries();
+ while (jarEntries.hasMoreElements()) {
+ JarEntry jarEntry = jarEntries.nextElement();
+ if (jarEntry.isDirectory()) {
+ continue;
+ }
+ if (entries.contains(jarEntry.getName())) {
+ logger.verbose(
+ String.format("Entry %1$s is duplicated, ignore the one from %2$s",
+ jarEntry.getName(), jarInput.getName()));
+ } else {
+ dependenciesJar.putNextEntry(new JarEntry(jarEntry.getName()));
+ InputStream inputStream = jarFile.getInputStream(jarEntry);
+ try {
+ ByteStreams.copy(inputStream, dependenciesJar);
+ } finally {
+ inputStream.close();
+ }
+ dependenciesJar.closeEntry();
+ entries.add(jarEntry.getName());
+ }
+ }
+ } finally {
+ jarFile.close();
+ }
+ }
+
+ private void sliceIncrementally(@NonNull Collection<TransformInput> inputs,
+ @NonNull TransformOutputProvider outputProvider,
+ @NonNull Slices slices)
+ throws IOException, TransformException, InterruptedException {
+
+ processChangesSinceLastRestart(inputs, outputProvider, slices);
+
+ // in any case, we always process jar input changes incrementally.
+ for (TransformInput input : inputs) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ if (jarInput.getStatus() != Status.NOTCHANGED) {
+ // one jar has changed, was removed or added, re-merge all of our jars.
+ combineAllJars(inputs, outputProvider);
+ return;
+ }
+ }
+ }
+ logger.info("No jar merging necessary, all input jars unchanged");
+ }
+
+ @Nullable
+ private DirectoryInput getInputFor(@NonNull Collection<TransformInput> inputs, File file) {
+ for (TransformInput input : inputs) {
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ if (file.getAbsolutePath().startsWith(directoryInput.getFile().getAbsolutePath())) {
+ return directoryInput;
+ }
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Collection<SecondaryFile> getSecondaryFiles() {
+ return ImmutableList.of(new SecondaryFile(
+ ColdswapArtifactsKickerTask.ConfigAction.getMarkerFile(variantScope),
+ true /* supportsIncrementalChange */));
+ }
+
+ private void processChangesSinceLastRestart(
+ @NonNull final Collection<TransformInput> inputs,
+ @NonNull final TransformOutputProvider outputProvider,
+ @NonNull final Slices slices)
+ throws TransformException, InterruptedException, IOException {
+
+ File incrementalChangesFile = InstantRunBuildType.RESTART
+ .getIncrementalChangesFile(variantScope);
+
+ // process all files
+ ChangeRecords.process(incrementalChangesFile,
+ new ChangeRecords.RecordHandler() {
+ @Override
+ public void handle(String filePath, Status status)
+ throws IOException, TransformException {
+ // get the output stream for this file.
+ File fileToProcess = new File(filePath);
+ DirectoryInput directoryInput = getInputFor(inputs, fileToProcess);
+ if (directoryInput == null) {
+ logger.info("Cannot find input directory for " + filePath);
+ return;
+ }
+ File sliceOutputLocation = getOutputStreamForFile(
+ outputProvider, directoryInput, fileToProcess, slices);
+
+ // add the buildID timestamp to the slice out directory so we force the
+ // dex task to rerun, even if no .class files appear to have changed. This
+ // can happen when doing a lot of hot swapping with changes undoing
+ // themselves resulting in a state that was equal to the last restart state.
+ // In theory, it would not require to rebuild but it will confuse Android
+ // Studio is there is nothing to push so just be safe and rebuild.
+ Files.write(
+ String.valueOf(
+ variantScope.getInstantRunBuildContext().getBuildId()),
+ new File(sliceOutputLocation, "buildId.txt"), Charsets.UTF_8);
+
+ String relativePath = FileUtils.relativePossiblyNonExistingPath(
+ fileToProcess, directoryInput.getFile());
+
+ File outputFile = new File(sliceOutputLocation, relativePath);
+ switch(status) {
+ case ADDED:
+ case CHANGED:
+ Files.createParentDirs(outputFile);
+ Files.copy(fileToProcess, outputFile);
+ break;
+ case REMOVED:
+ if (!outputFile.delete()) {
+ throw new TransformException(
+ String.format("Cannot delete file %1$s",
+ outputFile.getAbsolutePath()));
+ }
+ break;
+ default:
+ throw new TransformException("Unhandled status " + status);
+
+ }
+ }
+ });
+ }
+
+ private static File getOutputStreamForFile(
+ @NonNull TransformOutputProvider transformOutputProvider,
+ @NonNull DirectoryInput input,
+ @NonNull File file,
+ @NonNull Slices slices) {
+
+ String relativePackagePath = FileUtils.relativePossiblyNonExistingPath(file.getParentFile(),
+ input.getFile());
+ if (input.getScopes().contains(Scope.PROJECT)
+ || input.getScopes().contains(Scope.SUB_PROJECTS)) {
+
+ Slice slice = slices.getSliceFor(new Slice.SlicedElement(relativePackagePath, file));
+ return transformOutputProvider.getContentLocation(slice.name,
+ TransformManager.CONTENT_CLASS,
+ Sets.immutableEnumSet(Scope.PROJECT, Scope.SUB_PROJECTS),
+ Format.DIRECTORY);
+ } else {
+ return getDependenciesSliceOutputFolder(transformOutputProvider, Format.DIRECTORY);
+ }
+ }
+
+ @NonNull
+ private static File getMainSliceOutputFolder(
+ @NonNull TransformOutputProvider outputProvider,
+ @Nullable String suffix) throws IOException {
+ File outputFolder = outputProvider
+ .getContentLocation(
+ MAIN_SLICE_NAME + Strings.nullToEmpty(suffix),
+ TransformManager.CONTENT_CLASS,
+ Sets.immutableEnumSet(Scope.PROJECT, Scope.SUB_PROJECTS), Format.JAR);
+ Files.createParentDirs(outputFolder);
+ return outputFolder;
+ }
+
+ @NonNull
+ private static File getDependenciesSliceOutputFolder(
+ @NonNull TransformOutputProvider outputProvider, @NonNull Format format) {
+ String name = format == Format.DIRECTORY ? "dep-classes" : "dependencies";
+ return outputProvider
+ .getContentLocation(name, TransformManager.CONTENT_CLASS,
+ Sets.immutableEnumSet(Scope.EXTERNAL_LIBRARIES,
+ Scope.SUB_PROJECTS_LOCAL_DEPS,
+ Scope.PROJECT_LOCAL_DEPS), format);
+ }
+
+ private static class Slices {
+ @NonNull
+ private final List<Slice> slices = new ArrayList<Slice>();
+
+ private Slices() {
+ for (int i=0; i<NUMBER_OF_SLICES_FOR_PROJECT_CLASSES; i++) {
+ Slice newSlice = new Slice("slice_" + i, i);
+ slices.add(newSlice);
+ }
+ }
+
+ private void addElement(@NonNull String packagePath, @NonNull File file) {
+ Slice.SlicedElement slicedElement = new Slice.SlicedElement(packagePath, file);
+ Slice slice = getSliceFor(slicedElement);
+ slice.add(slicedElement);
+ }
+
+ private void writeTo(@NonNull TransformOutputProvider outputProvider) throws IOException {
+ for (Slice slice : slices) {
+ slice.writeTo(outputProvider);
+ }
+ }
+
+ private Slice getSliceFor(Slice.SlicedElement slicedElement) {
+ return slices.get(slicedElement.getHashBucket());
+ }
+ }
+
+ private static class Slice {
+
+ private static class SlicedElement {
+ @NonNull
+ private final String packagePath;
+ @NonNull
+ private final File slicedFile;
+
+ private SlicedElement(@NonNull String packagePath, @NonNull File slicedFile) {
+ this.packagePath = packagePath;
+ this.slicedFile = slicedFile;
+ }
+
+ /**
+ * Returns the bucket number in which this {@link SlicedElement} belongs.
+ * @return an integer between 0 and {@link #NUMBER_OF_SLICES_FOR_PROJECT_CLASSES}
+ * exclusive that will be used to bucket this item.
+ */
+ public int getHashBucket() {
+ String hashTarget = Strings.isNullOrEmpty(packagePath)
+ ? slicedFile.getName()
+ : packagePath;
+ return Math.abs(hashTarget.hashCode() % NUMBER_OF_SLICES_FOR_PROJECT_CLASSES);
+ }
+
+ @Override
+ public String toString() {
+ return packagePath + slicedFile.getName();
+ }
+ }
+
+ @NonNull
+ private final String name;
+ private final int hashBucket;
+ private final List<SlicedElement> slicedElements;
+
+ private Slice(@NonNull String name, int hashBucket) {
+ this.name = name;
+ this.hashBucket = hashBucket;
+ slicedElements = new ArrayList<SlicedElement>();
+ }
+
+ private void add(@NonNull SlicedElement slicedElement) {
+ if (hashBucket != slicedElement.getHashBucket()) {
+ throw new RuntimeException("Wrong bucket for " + slicedElement);
+ }
+ slicedElements.add(slicedElement);
+ }
+
+ private void writeTo(@NonNull TransformOutputProvider outputProvider) throws IOException {
+
+ File sliceOutputLocation = outputProvider.getContentLocation(name,
+ TransformManager.CONTENT_CLASS,
+ Sets.immutableEnumSet(Scope.PROJECT, Scope.SUB_PROJECTS),
+ Format.DIRECTORY);
+
+ // always write our dummy guard class, nobody will ever delete this file which mean
+ // the slice will continue existing even it there is no other .class file in it.
+ createGuardClass(name, sliceOutputLocation);
+
+ // now copy all the files into its new location.
+ for (Slice.SlicedElement slicedElement : slicedElements) {
+ File outputFile = new File(sliceOutputLocation, new File(slicedElement.packagePath,
+ slicedElement.slicedFile.getName()).getPath());
+ Files.createParentDirs(outputFile);
+ Files.copy(slicedElement.slicedFile, outputFile);
+ }
+ }
+ }
+
+ private static void createGuardClass(@NonNull String name, @NonNull File outputDir)
+ throws IOException {
+
+ ClassWriter cw = new ClassWriter(0);
+
+ File packageDir = new File(outputDir, PACKAGE_FOR_GUARD_CLASSS);
+ File outputFile = new File(packageDir, name + ".class");
+ Files.createParentDirs(outputFile);
+
+ // use package separator below which is always /
+ String appInfoOwner = PACKAGE_FOR_GUARD_CLASSS + '/' + name;
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, appInfoOwner, null, "java/lang/Object", null);
+ cw.visitEnd();
+
+ Files.write(cw.toByteArray(), outputFile);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunSplitApkBuilder.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunSplitApkBuilder.java
new file mode 100644
index 0000000..e40c65b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunSplitApkBuilder.java
@@ -0,0 +1,335 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext.FileType;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.builder.core.AaptPackageProcessBuilder;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.packaging.PackagerException;
+import com.android.builder.sdk.TargetInfo;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.ide.common.signing.KeytoolException;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Tasks to generate M+ style pure splits APKs with dex files.
+ */
+public class InstantRunSplitApkBuilder extends BaseTask {
+
+ private Set<File> dexFolders;
+ private File outputDirectory;
+ private SigningConfig signingConf;
+ private String applicationId;
+ private InstantRunBuildContext instantRunBuildContext;
+ private File zipAlignExe;
+ private AaptOptions aaptOptions;
+ private File supportDir;
+
+ private ApkVariantOutputData variantOutputData;
+
+ @Input
+ public String getApplicationId() {
+ return applicationId;
+ }
+
+ public void setApplicationId(String applicationId) {
+ this.applicationId = applicationId;
+ }
+
+ @Input
+ public int getVersionCode() {
+ return variantOutputData.getVersionCode();
+ }
+
+ @Input
+ @Optional
+ public String getVersionName() {
+ return variantOutputData.getVersionName();
+ }
+
+
+ @InputFiles
+ public Set<File> getDexFolders() {
+ return dexFolders;
+ }
+
+ @OutputDirectory
+ public File getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ @Input
+ public File getZipAlignExe() {
+ return zipAlignExe;
+ }
+
+ @TaskAction
+ public void run(IncrementalTaskInputs inputs)
+ throws IOException, DuplicateFileException, KeytoolException, PackagerException,
+ ProcessException, InterruptedException {
+ if (inputs.isIncremental()) {
+
+ inputs.outOfDate(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ try {
+ // we generate APKs for all slices but the main slice which will get
+ // packaged in the main APK.
+ if (!inputFileDetails.getFile().getName().contains(
+ InstantRunSlicer.MAIN_SLICE_NAME)) {
+ generateSplitApk(new DexFile(
+ inputFileDetails.getFile(),
+ inputFileDetails.getFile().getParentFile().getName()));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+
+ inputs.removed(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ DexFile dexFile = new DexFile(
+ inputFileDetails.getFile(),
+ inputFileDetails.getFile().getParentFile().getName());
+
+ String outputFileName = dexFile.encodeName() + "_unaligned.apk";
+ new File(getOutputDirectory(), outputFileName).delete();
+ outputFileName = dexFile.encodeName() + ".apk";
+ new File(getOutputDirectory(), outputFileName).delete();
+ }
+ });
+ } else {
+ List<DexFile> allFiles = new ArrayList<DexFile>();
+ for (File dexFolder : getDexFolders()) {
+ if (dexFolder.isDirectory()) {
+ File[] files = dexFolder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ allFiles.add(new DexFile(file, dexFolder.getName()));
+ }
+ }
+ }
+ }
+ for (DexFile file : allFiles) {
+ // generate a split APK for each.
+ if (!file.dexFile.getParentFile().getName().contains(
+ InstantRunSlicer.MAIN_SLICE_NAME)) {
+ generateSplitApk(file);
+ }
+ }
+ }
+ }
+
+ private void generateSplitApk(DexFile file)
+ throws IOException, DuplicateFileException, KeytoolException, PackagerException,
+ InterruptedException, ProcessException {
+
+ final File outputLocation = new File(getOutputDirectory(), file.encodeName()
+ + "_unaligned.apk");
+ Files.createParentDirs(outputLocation);
+ File resPackageFile = generateSplitApkManifest(file.encodeName());
+ getBuilder().packageCodeSplitApk(resPackageFile.getAbsolutePath(),
+ file.dexFile, signingConf, outputLocation.getAbsolutePath());
+ // zip align it.
+ final File alignedOutput = new File(getOutputDirectory(), file.encodeName() + ".apk");
+ ProcessInfoBuilder processInfoBuilder = new ProcessInfoBuilder();
+ processInfoBuilder.setExecutable(getZipAlignExe());
+ processInfoBuilder.addArgs("-f", "4");
+ processInfoBuilder.addArgs(outputLocation.getAbsolutePath());
+ processInfoBuilder.addArgs(alignedOutput.getAbsolutePath());
+
+ getBuilder().executeProcess(processInfoBuilder.createProcess(),
+ new LoggedProcessOutputHandler(getILogger()));
+
+ instantRunBuildContext.addChangedFile(FileType.SPLIT, alignedOutput);
+ resPackageFile.delete();
+ }
+
+ // todo, move this to a sub task, as it is reusable between invocations.
+ private File generateSplitApkManifest(String uniqueName)
+ throws IOException, ProcessException, InterruptedException {
+
+ String versionNameToUse = getVersionName();
+ if (versionNameToUse == null) {
+ versionNameToUse = String.valueOf(getVersionCode());
+ }
+
+ File sliceSupportDir = new File(supportDir, uniqueName);
+ sliceSupportDir.mkdirs();
+ File androidManifest = new File(sliceSupportDir, "AndroidManifest.xml");
+ OutputStreamWriter fileWriter = new OutputStreamWriter(
+ new FileOutputStream(androidManifest), "UTF-8");
+ try {
+ fileWriter.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"" + getApplicationId() + "\"\n"
+ + " android:versionCode=\"" + getVersionCode() + "\"\n"
+ + " android:versionName=\"" + versionNameToUse + "\"\n"
+ + " split=\"lib_" + uniqueName + "\">\n"
+ //+ " <uses-sdk android:minSdkVersion=\"21\"/>\n" + "</manifest>\n");
+ + "</manifest>\n");
+ fileWriter.flush();
+ } finally {
+ fileWriter.close();
+ }
+
+ File resFilePackageFile = new File(supportDir, "resources_ap");
+
+ AaptPackageProcessBuilder aaptPackageCommandBuilder =
+ new AaptPackageProcessBuilder(androidManifest, getAaptOptions())
+ .setDebuggable(true)
+ .setResPackageOutput(resFilePackageFile.getAbsolutePath());
+
+ getBuilder().processResources(
+ aaptPackageCommandBuilder,
+ false /* enforceUniquePackageName */,
+ new LoggedProcessOutputHandler(getILogger()));
+
+ return resFilePackageFile;
+ }
+
+ public AaptOptions getAaptOptions() {
+ return aaptOptions;
+ }
+
+ private static class DexFile {
+ private final File dexFile;
+ private final String dexFolderName;
+
+ private DexFile(@NonNull File dexFile, @NonNull String dexFolderName) {
+ this.dexFile = dexFile;
+ this.dexFolderName = dexFolderName;
+ }
+
+ private String encodeName() {
+ return dexFolderName.replace('-', '_');
+ }
+ }
+
+ public static class ConfigAction implements TaskConfigAction<InstantRunSplitApkBuilder> {
+
+ private final VariantScope variantScope;
+
+ public ConfigAction(@NonNull VariantScope variantScope) {
+ this.variantScope = variantScope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return variantScope.getTaskName("instantRun", "PureSplitBuilder");
+ }
+
+ @NonNull
+ @Override
+ public Class<InstantRunSplitApkBuilder> getType() {
+ return InstantRunSplitApkBuilder.class;
+ }
+
+ @Override
+ public void execute(@NonNull InstantRunSplitApkBuilder task) {
+
+ final GradleVariantConfiguration config = variantScope.getVariantConfiguration();
+
+ task.outputDirectory = variantScope.getInstantRunSplitApkOutputFolder();
+ task.signingConf = config.getSigningConfig();
+ task.setApplicationId(config.getApplicationId());
+ task.variantOutputData =
+ (ApkVariantOutputData) variantScope.getVariantData().getOutputs().get(0);
+ task.setVariantName(
+ variantScope.getVariantConfiguration().getFullName());
+ task.setAndroidBuilder(variantScope.getGlobalScope().getAndroidBuilder());
+ task.instantRunBuildContext = variantScope.getInstantRunBuildContext();
+ task.supportDir = variantScope.getInstantRunSliceSupportDir();
+
+ ConventionMappingHelper.map(task, "zipAlignExe", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final TargetInfo info =
+ variantScope.getGlobalScope().getAndroidBuilder().getTargetInfo();
+ if (info == null) {
+ return null;
+ }
+ String path = info.getBuildTools().getPath(ZIP_ALIGN);
+ if (path == null) {
+ return null;
+ }
+ return new File(path);
+ }
+ });
+
+ ConventionMappingHelper
+ .map(task, "dexFolders", new Callable<Set<File>>() {
+ @Override
+ public Set<File> call() {
+ if (config.getUseJack()) {
+ throw new IllegalStateException(
+ "InstantRun does not support Jack compiler yet.");
+ }
+
+ return variantScope.getTransformManager()
+ .getPipelineOutput(PackageApplication.sDexFilter).keySet();
+ }
+ });
+ ConventionMappingHelper.map(task, "aaptOptions",
+ new Callable<AaptOptions>() {
+ @Override
+ public AaptOptions call() throws Exception {
+ return variantScope.getGlobalScope().getExtension().getAaptOptions();
+ }
+ });
+
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunTransform.java
new file mode 100644
index 0000000..f1d66f6
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunTransform.java
@@ -0,0 +1,507 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import static com.android.build.api.transform.QualifiedContent.Scope;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.incremental.IncrementalChangeVisitor;
+import com.android.build.gradle.internal.incremental.IncrementalSupportVisitor;
+import com.android.build.gradle.internal.incremental.IncrementalVisitor;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.pipeline.ExtendedContentType;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.ide.common.util.UrlClassLoaderUtil;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.Logging;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of the {@link Transform} to run the byte code enhancement logic on compiled
+ * classes in order to support runtime hot swapping.
+ */
+public class InstantRunTransform extends Transform {
+
+ protected static final ILogger LOGGER =
+ new LoggerWrapper(Logging.getLogger(InstantRunTransform.class));
+ private final ChangeRecords generatedClasses2Files = new ChangeRecords();
+ private final ChangeRecords generatedClasses3Files = new ChangeRecords();
+ private final ImmutableList.Builder<String> generatedClasses3Names = ImmutableList.builder();
+ private final VariantScope variantScope;
+
+ public InstantRunTransform(VariantScope variantScope) {
+ this.variantScope = variantScope;
+ }
+
+ enum RecordingPolicy {RECORD, DO_NOT_RECORD}
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "instantRun";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getOutputTypes() {
+ return ImmutableSet.<ContentType>of(
+ DefaultContentType.CLASSES,
+ ExtendedContentType.CLASSES_ENHANCED);
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.Scope> getScopes() {
+ return Sets.immutableEnumSet(Scope.PROJECT, Scope.SUB_PROJECTS);
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ return Sets.immutableEnumSet(Scope.EXTERNAL_LIBRARIES,
+ Scope.PROJECT_LOCAL_DEPS,
+ Scope.SUB_PROJECTS_LOCAL_DEPS,
+ Scope.PROVIDED_ONLY);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(@NonNull TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+
+ TransformOutputProvider outputProvider = invocation.getOutputProvider();
+ if (outputProvider == null) {
+ throw new IllegalStateException("InstantRunTransform called with null output");
+ }
+
+ // first get all referenced input to construct a class loader capable of loading those
+ // classes. This is useful for ASM as it needs to load classes
+ List<URL> referencedInputUrls = getAllClassesLocations(
+ invocation.getInputs(), invocation.getReferencedInputs());
+
+ // This class loader could be optimized a bit, first we could create a parent class loader
+ // with the android.jar only that could be stored in the GlobalScope for reuse. This
+ // class loader could also be store in the VariantScope for potential reuse if some
+ // other transform need to load project's classes.
+ URLClassLoader urlClassLoader = new URLClassLoader(
+ referencedInputUrls.toArray(new URL[referencedInputUrls.size()]), null) {
+ @Override
+ public URL getResource(String name) {
+ // Never delegate to bootstrap classes.
+ return findResource(name);
+ }
+ };
+
+ ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ variantScope.getInstantRunBuildContext().startRecording(
+ InstantRunBuildContext.TaskType.INSTANT_RUN_TRANSFORM);
+ Thread.currentThread().setContextClassLoader(urlClassLoader);
+
+ File classesTwoOutput = outputProvider.getContentLocation("main",
+ TransformManager.CONTENT_CLASS, getScopes(), Format.DIRECTORY);
+
+ File classesThreeOutput = outputProvider.getContentLocation("enhanced",
+ ImmutableSet.<ContentType>of(ExtendedContentType.CLASSES_ENHANCED),
+ getScopes(), Format.DIRECTORY);
+
+ for (TransformInput input : invocation.getInputs()) {
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ File inputDir = directoryInput.getFile();
+ if (invocation.isIncremental()) {
+ for (Map.Entry<File, Status> fileEntry : directoryInput.getChangedFiles()
+ .entrySet()) {
+
+ File inputFile = fileEntry.getKey();
+ if (!inputFile.getName().endsWith(SdkConstants.DOT_CLASS))
+ continue;
+ switch (fileEntry.getValue()) {
+ case ADDED:
+ // a new file was added, we only generate the classes.2 format
+ transformToClasses2Format(
+ inputDir,
+ inputFile,
+ classesTwoOutput,
+ Status.ADDED,
+ RecordingPolicy.RECORD);
+ break;
+ case REMOVED:
+ // remove the classes.2 and classes.3 files.
+ File outputFile = deleteOutputFile(
+ IncrementalSupportVisitor.VISITOR_BUILDER,
+ inputDir, inputFile, classesTwoOutput);
+ if (outputFile != null) {
+ generatedClasses2Files.add(
+ Status.REMOVED, outputFile.getAbsolutePath());
+ }
+ deleteOutputFile(IncrementalChangeVisitor.VISITOR_BUILDER,
+ inputDir, inputFile, classesThreeOutput);
+ break;
+ case CHANGED:
+ // an existing file was changed, we regenerate the classes.2
+ // and classes.3 files as they are both needed to support
+ // restart and reload.
+ transformToClasses2Format(
+ inputDir,
+ inputFile,
+ classesTwoOutput,
+ Status.CHANGED,
+ RecordingPolicy.RECORD);
+ transformToClasses3Format(
+ inputDir,
+ inputFile,
+ classesThreeOutput);
+ break;
+ case NOTCHANGED:
+ break;
+ default:
+ throw new IllegalStateException("Unhandled file status "
+ + fileEntry.getValue());
+ }
+ }
+ } else {
+ // non incremental mode, we need to traverse the TransformInput#getFiles()
+ // folder
+ for (File file : Files.fileTreeTraverser().breadthFirstTraversal(inputDir)) {
+ if (file.isDirectory()) {
+ continue;
+ }
+
+ try {
+ // do not record the changes, everything should be packaged in the
+ // main APK.
+ transformToClasses2Format(
+ inputDir,
+ file,
+ classesTwoOutput,
+ Status.ADDED,
+ RecordingPolicy.DO_NOT_RECORD);
+ } catch (IOException e) {
+ throw new RuntimeException("Exception while preparing "
+ + file.getAbsolutePath());
+ }
+ }
+ File incremental =
+ InstantRunBuildType.RESTART.getIncrementalChangesFile(variantScope);
+ if (incremental.exists() && !incremental.delete()) {
+ LOGGER.warning("Cannot delete " + incremental);
+ }
+ }
+
+ }
+ }
+
+ wrapUpOutputs(classesTwoOutput, classesThreeOutput);
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ UrlClassLoaderUtil.attemptToClose(urlClassLoader);
+ variantScope.getInstantRunBuildContext().stopRecording(
+ InstantRunBuildContext.TaskType.INSTANT_RUN_TRANSFORM);
+ }
+ }
+
+ protected void wrapUpOutputs(File classes2Folder, File classes3Folder)
+ throws IOException {
+
+ // generate the patch file and add to the list of files to process next.
+ ImmutableList<String> generatedClassNames = generatedClasses3Names.build();
+ if (!generatedClassNames.isEmpty()) {
+ File patchFile = writePatchFileContents(generatedClassNames, classes3Folder);
+ generatedClasses3Files.add(Status.CHANGED, patchFile.getAbsolutePath());
+ }
+
+ // read the previous iterations output and append it to this iteration changes.
+ File incremental = InstantRunBuildType.RESTART.getIncrementalChangesFile(variantScope);
+ if (incremental.exists()) {
+ ChangeRecords previousIterationChanges = ChangeRecords.load(incremental);
+ merge(generatedClasses2Files, previousIterationChanges);
+ }
+
+ generatedClasses2Files.write(
+ InstantRunBuildType.RESTART.getIncrementalChangesFile(variantScope));
+
+ generatedClasses3Files.write(
+ InstantRunBuildType.RELOAD.getIncrementalChangesFile(variantScope));
+ }
+
+ /**
+ * Merges a past iteration set of change records into this iteration following simple merging
+ * rules :
+ * - if the file has been deleted during this iteration, do not merge past records.
+ * - if the file had been deleted previously but re-added in this iteration, remove the
+ * delete event.
+ * @param changeRecords this iteration change records
+ * @param pastIterationRecords past iteration change records.
+ */
+ @VisibleForTesting
+ static void merge(@NonNull ChangeRecords changeRecords,
+ @NonNull ChangeRecords pastIterationRecords) {
+
+ Set<String> deletedFiles = changeRecords.getFilesForStatus(Status.REMOVED);
+ for (Map.Entry<String, Status> record : pastIterationRecords.records.entrySet()) {
+ // if a previous iteration removed a class, only keep that remove event if it
+ // was not re-added during this iteration.
+ if (record.getValue() == Status.REMOVED) {
+ if (changeRecords.getChangeFor(record.getKey()) == null) {
+ changeRecords.add(record.getValue(), record.getKey());
+ }
+ } else {
+ // do not keep previous iteration ADD/CHANGED records for files that got deleted
+ // during this iteration.
+ if (!deletedFiles.contains(record.getKey())) {
+ changeRecords.add(record.getValue(), record.getKey());
+ }
+ }
+ }
+
+ // remove all the previous iteration REMOVED records if the class is re-added.
+ }
+
+ /**
+ * Calculate a list of {@link URL} that represent all the directories containing classes
+ * either directly belonging to this project or referencing it.
+ *
+ * @param inputs the project's inputs
+ * @param referencedInputs the project's referenced inputs
+ * @return a {@link List} or {@link URL} for all the locations.
+ * @throws MalformedURLException if once the locatio
+ */
+ @NonNull
+ private List<URL> getAllClassesLocations(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedInputs) throws MalformedURLException {
+
+ List<URL> referencedInputUrls = new ArrayList<URL>();
+
+ // add the bootstrap classpath for jars like android.jar
+ for (File file : variantScope.getGlobalScope().getAndroidBuilder().getBootClasspath(
+ true /* includeOptionalLibraries */)) {
+ referencedInputUrls.add(file.toURI().toURL());
+ }
+
+ // now add the project dependencies.
+ for (TransformInput referencedInput : referencedInputs) {
+ addAllClassLocations(referencedInput, referencedInputUrls);
+ }
+
+ // and finally add input folders.
+ for (TransformInput input : inputs) {
+ addAllClassLocations(input, referencedInputUrls);
+ }
+ return referencedInputUrls;
+ }
+
+ private static void addAllClassLocations(TransformInput transformInput, List<URL> into)
+ throws MalformedURLException {
+
+ for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
+ into.add(directoryInput.getFile().toURI().toURL());
+ }
+ for (JarInput jarInput : transformInput.getJarInputs()) {
+ into.add(jarInput.getFile().toURI().toURL());
+ }
+ }
+
+ /**
+ * Transform a single file into a format supporting class hot swap.
+ *
+ * @param inputDir the input directory containing the input file.
+ * @param inputFile the input file within the input directory to transform.
+ * @param outputDir the output directory where to place the transformed file.
+ * @param change the nature of the change that triggered the transformation.
+ * @param recordingPolicy whether or not, we should record the event.
+ * @throws IOException if the transformation failed.
+ */
+ protected void transformToClasses2Format(
+ @NonNull final File inputDir,
+ @NonNull final File inputFile,
+ @NonNull final File outputDir,
+ @NonNull final Status change,
+ @NonNull final RecordingPolicy recordingPolicy)
+ throws IOException {
+ if (inputFile.getPath().endsWith(SdkConstants.DOT_CLASS)) {
+ File outputFile = IncrementalVisitor.instrumentClass(
+ inputDir, inputFile, outputDir, IncrementalSupportVisitor.VISITOR_BUILDER);
+
+ if (outputFile != null && recordingPolicy == RecordingPolicy.RECORD) {
+ generatedClasses2Files.add(change, outputFile.getAbsolutePath());
+ }
+ }
+ }
+
+ @Nullable
+ private static File deleteOutputFile(
+ @NonNull IncrementalVisitor.VisitorBuilder visitorBuilder,
+ @NonNull File inputDir, @NonNull File inputFile, @NonNull File outputDir) {
+ String inputPath = FileUtils.relativePossiblyNonExistingPath(inputFile, inputDir);
+ String outputPath =
+ visitorBuilder.getMangledRelativeClassFilePath(inputPath);
+ File outputFile = new File(outputDir, outputPath);
+ if (outputFile.exists()) {
+ try {
+ FileUtils.delete(outputFile);
+ } catch (IOException e) {
+ // it's not a big deal if the file cannot be deleted, hopefully
+ // no code is still referencing it, yet we should notify.
+ LOGGER.warning("Cannot delete %1$s file.\nCause: %2$s",
+ outputFile, Throwables.getStackTraceAsString(e));
+ }
+ return outputFile;
+ }
+ return null;
+ }
+
+ /**
+ * Transform a single file into a {@link ExtendedContentType#CLASSES_ENHANCED} format
+ *
+ * @param inputDir the input directory containing the input file.
+ * @param inputFile the input file within the input directory to transform.
+ * @param outputDir the output directory where to place the transformed file.
+ * @throws IOException if the transformation failed.
+ */
+ protected void transformToClasses3Format(File inputDir, File inputFile, File outputDir)
+ throws IOException {
+
+ File outputFile = IncrementalVisitor.instrumentClass(
+ inputDir, inputFile, outputDir, IncrementalChangeVisitor.VISITOR_BUILDER);
+
+ // if the visitor returned null, that means the class not be hot swapped or more likely
+ // that it was disabled for InstantRun, we don't add it to our collection of generated
+ // classes and it will not be part of the Patch class that apply changes.
+ if (outputFile == null) {
+ return;
+ }
+ generatedClasses3Names.add(
+ inputFile.getAbsolutePath().substring(
+ inputDir.getAbsolutePath().length() + 1,
+ inputFile.getAbsolutePath().length() - ".class".length())
+ .replace(File.separatorChar, '.'));
+ generatedClasses3Files.add(Status.CHANGED, outputFile.getAbsolutePath());
+ }
+
+ /**
+ * Use asm to generate a concrete subclass of the AppPathLoaderImpl class.
+ * It only implements one method :
+ * String[] getPatchedClasses();
+ *
+ * The method is supposed to return the list of classes that were patched in this iteration.
+ * This will be used by the InstantRun runtime to load all patched classes and register them
+ * as overrides on the original classes.2 class files.
+ *
+ * @param patchFileContents list of patched class names.
+ * @param outputDir output directory where to generate the .class file in.
+ * @return the generated .class files
+ */
+ private static File writePatchFileContents(
+ ImmutableList<String> patchFileContents, File outputDir) {
+
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER,
+ IncrementalVisitor.APP_PATCHES_LOADER_IMPL, null,
+ IncrementalVisitor.ABSTRACT_PATCHES_LOADER_IMPL, null);
+
+ {
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
+ IncrementalVisitor.ABSTRACT_PATCHES_LOADER_IMPL,
+ "<init>", "()V", false);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC,
+ "getPatchedClasses", "()[Ljava/lang/String;", null, null);
+ mv.visitCode();
+ mv.visitIntInsn(Opcodes.BIPUSH, patchFileContents.size());
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
+ for (int index=0; index < patchFileContents.size(); index++) {
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitIntInsn(Opcodes.BIPUSH, index);
+ mv.visitLdcInsn(patchFileContents.get(index));
+ mv.visitInsn(Opcodes.AASTORE);
+ }
+ mv.visitInsn(Opcodes.ARETURN);
+ mv.visitMaxs(4, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ byte[] classBytes = cw.toByteArray();
+ File outputFile = new File(outputDir, IncrementalVisitor.APP_PATCHES_LOADER_IMPL + ".class");
+ try {
+ Files.createParentDirs(outputFile);
+ Files.write(classBytes, outputFile);
+ // add the files to the list of files to be processed by subsequent tasks.
+ return outputFile;
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunVerifierTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunVerifierTransform.java
new file mode 100644
index 0000000..0f79d53
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/InstantRunVerifierTransform.java
@@ -0,0 +1,396 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunVerifier;
+import com.android.build.gradle.internal.incremental.InstantRunVerifier.ClassBytesJarEntryProvider;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.Logging;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * No-op transform that verifies that changes between 2 versions of the same class are supported
+ * by the InstantRun implementation.
+ *
+ * To verify class changes, this transform will save all .class files in a private directory
+ * {@see VariantScope#getIncrementalVerifierDir}.
+ *
+ * When new classes are compiled, this transform will receive an incremental notification and will
+ * compare the new versions to the ones saved in the private directory. The result of this
+ * verification process will be encapsulated in an instance of {@link VerificationResult} and stored
+ * in the VariantScope.
+ */
+public class InstantRunVerifierTransform extends Transform {
+
+ protected static final ILogger LOGGER =
+ new LoggerWrapper(Logging.getLogger(InstantRunVerifierTransform.class));
+
+ private final VariantScope variantScope;
+ private final File outputDir;
+
+ /**
+ * Object that encapsulates the result of the verification process.
+ */
+ public static class VerificationResult {
+
+ @Nullable
+ private final InstantRunVerifierStatus changes;
+
+ @VisibleForTesting
+ VerificationResult(@Nullable InstantRunVerifierStatus changes) {
+ this.changes = changes;
+ }
+
+ /**
+ * Returns true if the verification process has determined that the new class' version is
+ * compatible with hot swap technology, false otherwise.
+ * @return true if the new class can be hot swapped, false otherwise.
+ */
+ public boolean isCompatible() {
+ return changes == null;
+ }
+ }
+
+ public InstantRunVerifierTransform(VariantScope variantScope) {
+ this.variantScope = variantScope;
+ this.outputDir = variantScope.getIncrementalVerifierDir();
+ }
+
+ @Override
+ public void transform(@NonNull TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+
+ if (invocation.getReferencedInputs().isEmpty()) {
+ throw new RuntimeException("Empty list of referenced inputs");
+ }
+ try {
+ variantScope.getInstantRunBuildContext().startRecording(
+ InstantRunBuildContext.TaskType.VERIFIER);
+ doTransform(invocation.getReferencedInputs(), invocation.isIncremental());
+ } finally {
+ variantScope.getInstantRunBuildContext().stopRecording(
+ InstantRunBuildContext.TaskType.VERIFIER);
+ }
+ }
+
+ public void doTransform(@NonNull Collection<TransformInput> inputs, boolean isIncremental)
+ throws IOException, TransformException, InterruptedException {
+
+ if (!isIncremental && outputDir.exists()) {
+ FileUtils.emptyFolder(outputDir);
+ } else {
+ FileUtils.mkdirs(outputDir);
+ }
+
+ InstantRunVerifierStatus resultSoFar = InstantRunVerifierStatus.COMPATIBLE;
+ for (TransformInput transformInput : inputs) {
+ resultSoFar = processFolderInputs(resultSoFar, isIncremental, transformInput);
+ resultSoFar = processJarInputs(resultSoFar, transformInput);
+ }
+
+ // if we are being asked to produce the RESTART artifacts, there is no need to set the
+ // verifier result, however the transform needed to run to backup the .class files.
+ if (!variantScope.getGlobalScope().isActive(OptionalCompilationStep.RESTART_ONLY)) {
+ variantScope.getInstantRunBuildContext().setVerifierResult(resultSoFar);
+ }
+ }
+
+ @NonNull
+ private InstantRunVerifierStatus processFolderInputs(
+ @NonNull InstantRunVerifierStatus verificationResult,
+ boolean isIncremental,
+ @NonNull TransformInput transformInput) throws IOException {
+
+ for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
+
+ File inputDir = directoryInput.getFile();
+
+ if (!isIncremental) {
+ // non incremental mode, we need to traverse the folder
+ for (File file : Files.fileTreeTraverser().breadthFirstTraversal(inputDir)) {
+
+ if (file.isDirectory()) {
+ continue;
+ }
+ copyFile(file, getOutputFile(inputDir, file, outputDir));
+ }
+ continue;
+ }
+ for (Map.Entry<File, Status> changedFile :
+ directoryInput.getChangedFiles().entrySet()) {
+
+ File inputFile = changedFile.getKey();
+ if (inputFile.isDirectory()) {
+ continue;
+ }
+ File lastIterationFile = getOutputFile(inputDir, inputFile, outputDir);
+ switch(changedFile.getValue()) {
+ case REMOVED:
+ // remove the backup file.
+ if (lastIterationFile.exists() && !lastIterationFile.delete()) {
+ // it's not a big deal if the file cannot be deleted, hopefully
+ // no code is still referencing it, yet we should notify.
+ LOGGER.warning("Cannot delete %1$s file", lastIterationFile);
+ }
+ break;
+ case ADDED:
+ // new file, save it for next iteration.
+ copyFile(inputFile, lastIterationFile);
+ verificationResult = InstantRunVerifierStatus.CLASS_ADDED;
+ break;
+ case CHANGED:
+ // a new version of the class has been compiled, we should compare
+ // it with the one saved during the last iteration on the file, but only
+ // if we have not failed any verification so far.
+ if (verificationResult == InstantRunVerifierStatus.COMPATIBLE) {
+ if (lastIterationFile.exists()) {
+ verificationResult = runVerifier(inputFile.getName(),
+ new InstantRunVerifier.ClassBytesFileProvider(
+ lastIterationFile),
+ new InstantRunVerifier.ClassBytesFileProvider(inputFile));
+ LOGGER.verbose("%1$s : verifier result : %2$s",
+ inputFile.getName(), verificationResult);
+ } else {
+ verificationResult = InstantRunVerifierStatus.INSTANT_RUN_FAILURE;
+ LOGGER.verbose("Changed file %1$s not found in verifier backup",
+ inputFile.getAbsolutePath());
+ }
+ }
+
+ // always copy the new file over to our private backup directory for the
+ // next iteration verification.
+ copyFile(inputFile, lastIterationFile);
+ break;
+ case NOTCHANGED:
+ break;
+ default:
+ throw new IllegalArgumentException("Unhandled DirectoryInput status "
+ + changedFile.getValue());
+ }
+
+ }
+ }
+ return verificationResult;
+ }
+
+ @NonNull
+ private InstantRunVerifierStatus processJarInputs(
+ @NonNull InstantRunVerifierStatus resultSoFar,
+ @NonNull TransformInput transformInput) throws IOException {
+
+ // can jarInput have colliding names ?
+ for (JarInput jarInput : transformInput.getJarInputs()) {
+ File backupJar = new File(outputDir, jarInput.getName());
+ switch(jarInput.getStatus()) {
+ case REMOVED:
+ if (backupJar.exists() && !backupJar.delete()) {
+ // it's not a big deal if the file cannot be deleted, hopefully
+ // no code is still referencing it, yet we should notify
+ LOGGER.warning("Cannot delete %1$s file", backupJar);
+ }
+ break;
+ case ADDED:
+ copyFile(jarInput.getFile(), backupJar);
+ break;
+ case CHANGED:
+ // get a Map of the back up jar entries indexed by name.
+ if (resultSoFar != InstantRunVerifierStatus.COMPATIBLE) {
+ JarFile backupJarFile = new JarFile(backupJar);
+ try {
+ JarFile jarFile = new JarFile(jarInput.getFile());
+ try {
+ resultSoFar = processChangedJar(backupJarFile, jarFile);
+ } finally {
+ jarFile.close();
+ }
+ } finally {
+ backupJarFile.close();
+ }
+ }
+ copyFile(jarInput.getFile(), backupJar);
+ break;
+ case NOTCHANGED:
+ break;
+ default:
+ throw new IllegalArgumentException("Unhandled JarInput status "
+ + jarInput.getStatus());
+ }
+ }
+ return resultSoFar;
+ }
+
+ @NonNull
+ private InstantRunVerifierStatus processChangedJar(JarFile backupJar, JarFile newJar)
+ throws IOException {
+
+ Map<String, JarEntry> backupEntries = new HashMap<String, JarEntry>();
+ Enumeration<JarEntry> backupJarEntries = backupJar.entries();
+ while (backupJarEntries.hasMoreElements()) {
+ JarEntry jarEntry = backupJarEntries.nextElement();
+ backupEntries.put(jarEntry.getName(), jarEntry);
+ }
+ // go through the jar file, entry by entry.
+ Enumeration<JarEntry> jarEntries = newJar.entries();
+ while (jarEntries.hasMoreElements()) {
+ JarEntry jarEntry = jarEntries.nextElement();
+ if (jarEntry.getName().endsWith(".class")) {
+ JarEntry backupEntry = backupEntries.get(jarEntry.getName());
+ if (backupEntry != null) {
+ InstantRunVerifierStatus verificationResult =
+ runVerifier(
+ newJar.getName() + ":" + jarEntry.getName(),
+ new ClassBytesJarEntryProvider(backupJar, backupEntry),
+ new ClassBytesJarEntryProvider(newJar, jarEntry));
+ if (verificationResult != InstantRunVerifierStatus.COMPATIBLE) {
+ return verificationResult;
+ }
+ }
+
+ }
+ }
+ return InstantRunVerifierStatus.COMPATIBLE;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ protected InstantRunVerifierStatus runVerifier(String name,
+ @NonNull final InstantRunVerifier.ClassBytesProvider originalClass ,
+ @NonNull final InstantRunVerifier.ClassBytesProvider updatedClass) throws IOException {
+ if (!name.endsWith(SdkConstants.DOT_CLASS)) {
+ return InstantRunVerifierStatus.COMPATIBLE;
+ }
+ InstantRunVerifierStatus status = ThreadRecorder.get().record(
+ ExecutionType.TASK_FILE_VERIFICATION,
+ new Recorder.Block<InstantRunVerifierStatus>() {
+ @Override
+ @NonNull
+ public InstantRunVerifierStatus call() throws Exception {
+ return InstantRunVerifier.run(originalClass, updatedClass);
+ }
+ }, new Recorder.Property("target", name)
+ );
+ if (status == null) {
+ LOGGER.warning("No verifier result provided for %1$s", name);
+ return InstantRunVerifierStatus.NOT_RUN;
+ }
+ return status;
+ }
+
+ @VisibleForTesting
+ protected void copyFile(File inputFile, File outputFile) {
+ try {
+ Files.createParentDirs(outputFile);
+ Files.copy(inputFile, outputFile);
+ } catch(IOException e) {
+ LOGGER.error(e, "Cannot copy $1$s to back up folder, build will continue but "
+ + "next time this file is modified will result in a cold swap.",
+ inputFile.getAbsolutePath());
+ }
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "instantRunVerifier";
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.ContentType> getOutputTypes() {
+ return TransformManager.CONTENT_CLASS; }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.Scope> getScopes() {
+ return ImmutableSet.of();
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.Scope> getReferencedScopes() {
+ return Sets.immutableEnumSet(
+ QualifiedContent.Scope.PROJECT, QualifiedContent.Scope.SUB_PROJECTS);
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryDirectoryOutputs() {
+ return ImmutableList.of(outputDir);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ /**
+ * Return the expected output {@link File} for an input file located in the transform input
+ * directory. The output file will have a similar relative path than the input file (to the
+ * input dir) inside the output directory.
+ *
+ * @param inputDir the input directory containing the input file
+ * @param inputFile the input file within the input directory
+ * @param outputDir the output directory
+ * @return the output file within the output directory with the right relative path.
+ */
+ protected static File getOutputFile(File inputDir, File inputFile, File outputDir) {
+ String relativePath = FileUtils.relativePossiblyNonExistingPath(inputFile, inputDir);
+ return new File(outputDir, relativePath);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JacocoTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JacocoTransform.java
new file mode 100644
index 0000000..5805e53
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JacocoTransform.java
@@ -0,0 +1,185 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.coverage.JacocoPlugin;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.utils.FileUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.jacoco.core.instr.Instrumenter;
+import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Jacoco Transform
+ */
+public class JacocoTransform extends Transform {
+
+ @NonNull
+ private final Supplier<Collection<File>> jacocoClasspath;
+
+ public JacocoTransform(@NonNull final ConfigurationContainer configurations) {
+ this.jacocoClasspath = Suppliers.memoize(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ return configurations.getByName(JacocoPlugin.AGENT_CONFIGURATION_NAME).getFiles();
+ }
+ });
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "jacoco";
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.Scope> getScopes() {
+ // only run on the project classes
+ return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT);
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileInputs() {
+ return jacocoClasspath.get();
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+
+ checkNotNull(invocation.getOutputProvider(),
+ "Missing output object for transform " + getName());
+ File outputDir = invocation.getOutputProvider().getContentLocation(
+ "main", getOutputTypes(), getScopes(), Format.DIRECTORY);
+ FileUtils.mkdirs(outputDir);
+
+ TransformInput input = Iterables.getOnlyElement(invocation.getInputs());
+ // we don't want jar inputs.
+ Preconditions.checkState(input.getJarInputs().isEmpty());
+ DirectoryInput directoryInput = Iterables.getOnlyElement(input.getDirectoryInputs());
+ File inputDir = directoryInput.getFile();
+
+ Instrumenter instrumenter = new Instrumenter(new OfflineInstrumentationAccessGenerator());
+ if (invocation.isIncremental()) {
+ instrumentFilesIncremental(instrumenter, inputDir, outputDir, directoryInput.getChangedFiles());
+ } else {
+ instrumentFilesFullRun(instrumenter, inputDir, outputDir);
+ }
+ }
+
+ private static void instrumentFilesIncremental(
+ @NonNull Instrumenter instrumenter,
+ @NonNull File inputDir,
+ @NonNull File outputDir,
+ @NonNull Map<File, Status> changedFiles) throws IOException {
+ for (Map.Entry<File, Status> changedInput : changedFiles.entrySet()) {
+ File inputFile = changedInput.getKey();
+ if (!inputFile.getName().endsWith(SdkConstants.DOT_CLASS)) {
+ continue;
+ }
+
+ File outputFile = new File(outputDir,
+ FileUtils.relativePossiblyNonExistingPath(inputFile, inputDir));
+ switch (changedInput.getValue()) {
+ case REMOVED:
+ FileUtils.delete(outputFile);
+ break;
+ case ADDED:
+ // fall through
+ case CHANGED:
+ instrumentFile(instrumenter, inputFile, outputFile);
+ }
+ }
+ }
+
+ private static void instrumentFilesFullRun(
+ @NonNull Instrumenter instrumenter,
+ @NonNull File inputDir,
+ @NonNull File outputDir) throws IOException {
+ FileUtils.emptyFolder(outputDir);
+ Iterable<File> files = FileUtils.getAllFiles(inputDir);
+ for (File inputFile : files) {
+ if (!inputFile.getName().endsWith(SdkConstants.DOT_CLASS)) {
+ continue;
+ }
+
+ File outputFile = new File(outputDir, FileUtils.relativePath(inputFile, inputDir));
+ instrumentFile(instrumenter, inputFile, outputFile);
+ }
+ }
+
+ private static void instrumentFile(
+ @NonNull Instrumenter instrumenter,
+ @NonNull File inputFile,
+ @NonNull File outputFile) throws IOException {
+ InputStream inputStream = null;
+ try {
+ inputStream = Files.asByteSource(inputFile).openBufferedStream();
+ Files.createParentDirs(outputFile);
+ byte[] instrumented = instrumenter.instrument(
+ inputStream,
+ inputFile.toString());
+ Files.write(instrumented, outputFile);
+ } finally {
+ Closeables.closeQuietly(inputStream);
+ }
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JarMerger.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JarMerger.java
new file mode 100644
index 0000000..30dbb03
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JarMerger.java
@@ -0,0 +1,193 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.io.Closer;
+
+import org.gradle.api.logging.Logging;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Jar Merger class.
+ */
+public class JarMerger {
+
+ private final byte[] buffer = new byte[8192];
+
+ @NonNull
+ private final ILogger logger = new LoggerWrapper(Logging.getLogger(JarMerger.class));
+
+ @NonNull
+ private final File jarFile;
+ private Closer closer;
+ private JarOutputStream jarOutputStream;
+
+ private SignedJarBuilder.IZipEntryFilter filter;
+
+ public JarMerger(@NonNull File jarFile) throws IOException {
+ this.jarFile = jarFile;
+ }
+
+ private void init() throws IOException {
+ if (closer == null) {
+ FileUtils.mkdirs(jarFile.getParentFile());
+
+ closer = Closer.create();
+
+ FileOutputStream fos = closer.register(new FileOutputStream(jarFile));
+ jarOutputStream = closer.register(new JarOutputStream(fos));
+ }
+ }
+
+ /**
+ * Sets a list of regex to exclude from the jar.
+ */
+ public void setFilter(@NonNull SignedJarBuilder.IZipEntryFilter filter) {
+ this.filter = filter;
+ }
+
+ public void addFolder(@NonNull File folder) throws IOException {
+ init();
+ try {
+ addFolder(folder, "");
+ } catch (SignedJarBuilder.IZipEntryFilter.ZipAbortException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private void addFolder(@NonNull File folder, @NonNull String path)
+ throws IOException, SignedJarBuilder.IZipEntryFilter.ZipAbortException {
+ logger.verbose("addFolder(%1$s, %2$s)", folder, path);
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile()) {
+ String entryPath = path + file.getName();
+ if (filter == null || filter.checkEntry(entryPath)) {
+ logger.verbose("addFolder(%1$s, %2$s): entry %3$s", folder, path, entryPath);
+ // new entry
+ jarOutputStream.putNextEntry(new JarEntry(entryPath));
+
+ // put the file content
+ Closer localCloser = Closer.create();
+ try {
+ FileInputStream fis = localCloser.register(new FileInputStream(file));
+ int count;
+ while ((count = fis.read(buffer)) != -1) {
+ jarOutputStream.write(buffer, 0, count);
+ }
+ } finally {
+ localCloser.close();
+ }
+
+ // close the entry
+ jarOutputStream.closeEntry();
+ }
+ } else if (file.isDirectory()) {
+ addFolder(file, path + file.getName() + "/");
+ }
+ }
+ }
+ }
+
+ public void addJar(@NonNull File file) throws IOException {
+ addJar(file, false);
+ }
+
+ public void addJar(@NonNull File file, boolean removeEntryTimestamp) throws IOException {
+ logger.verbose("addJar(%1$s)", file);
+ init();
+
+ Closer localCloser = Closer.create();
+ try {
+ FileInputStream fis = localCloser.register(new FileInputStream(file));
+ ZipInputStream zis = localCloser.register(new ZipInputStream(fis));
+
+ // loop on the entries of the jar file package and put them in the final jar
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // do not take directories or anything inside a potential META-INF folder.
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ String name = entry.getName();
+ if (filter != null && !filter.checkEntry(name)) {
+ continue;
+ }
+
+ JarEntry newEntry;
+
+ // Preserve the STORED method of the input entry.
+ if (entry.getMethod() == JarEntry.STORED) {
+ newEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ newEntry = new JarEntry(name);
+ }
+ if (removeEntryTimestamp) {
+ newEntry.setTime(0);
+ }
+
+ // add the entry to the jar archive
+ logger.verbose("addJar(%1$s): entry %2$s", file, name);
+ jarOutputStream.putNextEntry(newEntry);
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ int count;
+ while ((count = zis.read(buffer)) != -1) {
+ jarOutputStream.write(buffer, 0, count);
+ }
+
+ // close the entries for this file
+ jarOutputStream.closeEntry();
+ zis.closeEntry();
+ }
+ } catch (SignedJarBuilder.IZipEntryFilter.ZipAbortException e) {
+ throw new IOException(e);
+ } finally {
+ localCloser.close();
+ }
+ }
+
+ public void addEntry(@NonNull String path, @NonNull byte[] bytes) throws IOException {
+ init();
+
+ jarOutputStream.putNextEntry(new JarEntry(path));
+ jarOutputStream.write(bytes);
+ jarOutputStream.closeEntry();
+ }
+
+ public void close() throws IOException {
+ if (closer != null) {
+ closer.close();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JarMergingTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JarMergingTransform.java
new file mode 100644
index 0000000..b15b25a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/JarMergingTransform.java
@@ -0,0 +1,122 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.utils.FileUtils.deleteIfExists;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * A transform that merges all the incoming inputs (folders and jars) into a single jar in a
+ * single combined output.
+ *
+ * This only packages the class files. It ignores other files.
+ */
+public class JarMergingTransform extends Transform {
+
+ @NonNull
+ private final ImmutableSet<Scope> scopes;
+
+ public JarMergingTransform(@NonNull Set<Scope> scopes) {
+ this.scopes = ImmutableSet.copyOf(scopes);
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "jarMerging";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return scopes;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation) throws TransformException, IOException {
+ TransformOutputProvider outputProvider = invocation.getOutputProvider();
+ checkNotNull(outputProvider, "Missing output object for transform " + getName());
+
+ // all the output will be the same since the transform type is COMBINED.
+ // and format is SINGLE_JAR so output is a jar
+ File jarFile = outputProvider.getContentLocation("combined", getOutputTypes(), getScopes(),
+ Format.JAR);
+ FileUtils.mkdirs(jarFile.getParentFile());
+ deleteIfExists(jarFile);
+
+ JarMerger jarMerger = new JarMerger(jarFile);
+
+ try {
+ jarMerger.setFilter(new SignedJarBuilder.IZipEntryFilter() {
+ @Override
+ public boolean checkEntry(String archivePath)
+ throws ZipAbortException {
+ return archivePath.endsWith(SdkConstants.DOT_CLASS);
+ }
+ });
+
+ for (TransformInput input : invocation.getInputs()) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ jarMerger.addJar(jarInput.getFile());
+ }
+
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ jarMerger.addFolder(directoryInput.getFile());
+ }
+ }
+ } catch (FileNotFoundException e) {
+ throw new TransformException(e);
+ } catch (IOException e) {
+ throw new TransformException(e);
+ } finally {
+ jarMerger.close();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/MergeJavaResourcesTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/MergeJavaResourcesTransform.java
new file mode 100644
index 0000000..e76b3d5
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/MergeJavaResourcesTransform.java
@@ -0,0 +1,555 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.SdkConstants.FD_APK_NATIVE_LIBS;
+import static com.android.utils.FileUtils.mkdirs;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.pipeline.ExtendedContentType;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Transform to merge all the Java resources.
+ *
+ * Based on the value of {@link #getInputTypes()} this will either process native libraries
+ * or java resources. While native libraries inside jars are technically java resources, they
+ * must be handled separately.
+ */
+public class MergeJavaResourcesTransform extends Transform {
+
+ private interface FileValidator {
+ boolean validateJarPath(@NonNull String path);
+ boolean validateFolderPath(@NonNull String path);
+ @NonNull
+ String folderPathToKey(@NonNull String path);
+ @NonNull
+ String keyToFolderPath(@NonNull String path);
+ }
+
+ @NonNull
+ private final PackagingOptions packagingOptions;
+
+ @NonNull
+ private final String name;
+
+ @NonNull
+ private final Set<Scope> mergeScopes;
+ @NonNull
+ private final Set<ContentType> mergedType;
+ @NonNull
+ private final FileValidator validator;
+
+ public MergeJavaResourcesTransform(
+ @NonNull PackagingOptions packagingOptions,
+ @NonNull Set<Scope> mergeScopes,
+ @NonNull ContentType mergedType,
+ @NonNull String name) {
+ this.packagingOptions = packagingOptions;
+ this.name = name;
+ this.mergeScopes = Sets.immutableEnumSet(mergeScopes);
+ this.mergedType = ImmutableSet.of(mergedType);
+
+
+ if (mergedType == QualifiedContent.DefaultContentType.RESOURCES) {
+ validator = new FileValidator() {
+ @Override
+ public boolean validateJarPath(@NonNull String path) {
+ return !path.endsWith(SdkConstants.DOT_CLASS) &&
+ !path.endsWith(SdkConstants.DOT_NATIVE_LIBS);
+ }
+
+ @Override
+ public boolean validateFolderPath(@NonNull String path) {
+ return !path.endsWith(SdkConstants.DOT_CLASS) &&
+ !path.endsWith(SdkConstants.DOT_NATIVE_LIBS);
+
+ }
+
+ @NonNull
+ @Override
+ public String folderPathToKey(@NonNull String path) {
+ return path;
+ }
+
+ @NonNull
+ @Override
+ public String keyToFolderPath(@NonNull String path) {
+ return path;
+ }
+ };
+
+ } else if (mergedType == ExtendedContentType.NATIVE_LIBS) {
+ validator = new NativeLibValidator();
+
+ } else {
+ throw new UnsupportedOperationException(
+ "mergedType param must be RESOURCES or NATIVE_LIBS");
+ }
+ }
+
+ private static final class NativeLibValidator implements FileValidator {
+ private final Pattern jarAbiPattern = Pattern.compile("lib/([^/]+)/[^/]+");
+ private final Pattern folderAbiPattern = Pattern.compile("([^/]+)/[^/]+");
+ private final Pattern filenamePattern = Pattern.compile(".*\\.so");
+
+ @Override
+ public boolean validateJarPath(@NonNull String path) {
+ // extract abi from path, checking the general path structure (lib/<abi>/<filename>)
+ Matcher m = jarAbiPattern.matcher(path);
+
+ // if the ABI is accepted, check the 3rd segment
+ if (m.matches()) {
+ // remove the beginning of the path (lib/<abi>/)
+ String filename = path.substring(5 + m.group(1).length());
+ // and check the filename
+ return filenamePattern.matcher(filename).matches() ||
+ SdkConstants.FN_GDBSERVER.equals(filename) ||
+ SdkConstants.FN_GDB_SETUP.equals(filename);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean validateFolderPath(@NonNull String path) {
+ // extract abi from path, checking the general path structure (<abi>/<filename>)
+ Matcher m = folderAbiPattern.matcher(path);
+
+ // if the ABI is accepted, check the 3rd segment
+ if (m.matches()) {
+ // remove the beginning of the path (<abi>/)
+ String filename = path.substring(1 + m.group(1).length());
+ // and check the filename
+ return filenamePattern.matcher(filename).matches() ||
+ SdkConstants.FN_GDBSERVER.equals(filename) ||
+ SdkConstants.FN_GDB_SETUP.equals(filename);
+ }
+
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public String folderPathToKey(@NonNull String path) {
+ return FD_APK_NATIVE_LIBS + "/" + path;
+ }
+
+ @NonNull
+ @Override
+ public String keyToFolderPath(@NonNull String path) {
+ return path.substring(FD_APK_NATIVE_LIBS.length() + 1);
+ }
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return mergedType;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return mergeScopes;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Object> getParameterInputs() {
+ // TODO the inputs that controls the merge.
+ return ImmutableMap.of();
+ }
+
+ @Override
+ public boolean isIncremental() {
+ // FIXME
+ return false;
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation) throws IOException, TransformException {
+
+ TransformOutputProvider outputProvider = invocation.getOutputProvider();
+ checkNotNull(outputProvider, "Missing output object for transform " + getName());
+
+ // folder to copy the files that were originally in folders.
+ File outFolder = null;
+ // jar to copy the files that came from jars. We want copy files from jar into a jar to
+ // avoid case sensitivity issue on a case insensitive file system.
+ File outJar = null;
+
+ if (!invocation.isIncremental()) {
+ outputProvider.deleteAll();
+
+ // gather all the inputs.
+ ListMultimap<String, QualifiedContent> sourceFileList = ArrayListMultimap.create();
+ for (TransformInput input : invocation.getInputs()) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ gatherListFromJar(jarInput, sourceFileList);
+ }
+
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ gatherListFromFolder(directoryInput, sourceFileList);
+ }
+ }
+
+ // at this point we have what we need, write the output.
+
+ // we're recording all the files that must be merged.
+ // this is a map of (archive path -> source folder/jar)
+ ListMultimap<String, File> mergedFiles = ArrayListMultimap.create();
+
+ // we're also going to record for each jar which files comes from it.
+ ListMultimap<File, String> jarSources = ArrayListMultimap.create();
+
+ for (String key : sourceFileList.keySet()) {
+ PackagingOptions.Action packagingAction = packagingOptions.getAction(key);
+
+ // first thing we do is check if it's excluded.
+ if (packagingAction == PackagingOptions.Action.EXCLUDE) {
+ // skip, no need to do anything else.
+ continue;
+ }
+
+ List<QualifiedContent> contentSourceList = sourceFileList.get(key);
+
+ // if there is only one content or if one of the source is PROJECT then it wins.
+ // This is similar behavior as the other merger (assets, res, manifest).
+ QualifiedContent selectedContent = findUniqueOrProjectContent(contentSourceList);
+
+ // otherwise search for a selection
+ if (selectedContent == null) {
+ if (packagingAction == PackagingOptions.Action.PICK_FIRST) {
+ // if pickFirst then just pick the first one.
+ selectedContent = contentSourceList.get(0);
+ } else if (packagingAction == PackagingOptions.Action.MERGE) {
+ // if it's selected for merging, we need to record this for later where
+ // we'll merge all the files we've found.
+ for (QualifiedContent content : contentSourceList) {
+ mergedFiles.put(key, content.getFile());
+ }
+ } else {
+ // finally if it's not excluded, then this is an error.
+ // collect the sources.
+ List<File> sources = Lists
+ .newArrayListWithCapacity(contentSourceList.size());
+ for (QualifiedContent content : contentSourceList) {
+ sources.add(content.getFile());
+ }
+ throw new TransformException(new DuplicateFileException(key, sources));
+ }
+ }
+
+ // if a file was selected, write it here.
+ if (selectedContent != null) {
+ if (selectedContent instanceof JarInput) {
+ // or just record it for now if it's coming from a jar.
+ // This will allow to open these source jars just once to copy
+ // all their content out.
+ jarSources.put(selectedContent.getFile(), key);
+ } else {
+ if (outFolder == null) {
+ outFolder = outputProvider.getContentLocation(
+ "main",
+ getOutputTypes(), getScopes(),
+ Format.DIRECTORY);
+ mkdirs(outFolder);
+ }
+ copyFromFolder(selectedContent.getFile(), outFolder, key);
+ }
+ }
+ }
+
+ // now copy all the non-merged files into the jar.
+ JarMerger jarMerger = null;
+ if (!jarSources.isEmpty()) {
+ outJar = outputProvider.getContentLocation(
+ "main", getOutputTypes(), getScopes(), Format.JAR);
+ mkdirs(outJar.getParentFile());
+ jarMerger = copyIntoJar(jarSources, outJar);
+ }
+
+ // then handle the merged files.
+ if (!mergedFiles.isEmpty()) {
+ for (String key : mergedFiles.keySet()) {
+ List<File> sourceFiles = mergedFiles.get(key);
+
+ // first check if we have a jar source
+ boolean hasJarSource = false;
+ for (File sourceFile : sourceFiles) {
+ if (sourceFile.isDirectory()) {
+ hasJarSource = true;
+ break;
+ }
+ }
+
+ // merge the content into a ByteArrayOutputStream.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ for (File sourceFile : sourceFiles) {
+ if (sourceFile.isDirectory()) {
+ File actualFile = computeFile(sourceFile, validator.keyToFolderPath(key));
+ baos.write(Files.toByteArray(actualFile));
+ } else {
+ ZipFile zipFile = new ZipFile(sourceFile);
+ try {
+ ByteStreams.copy(
+ zipFile.getInputStream(zipFile.getEntry(key)), baos);
+ } finally {
+ zipFile.close();
+ }
+ }
+ }
+
+ if (hasJarSource) {
+ // if we haven't written into the outjar, create it.
+ if (outJar == null) {
+ outJar = outputProvider.getContentLocation(
+ "main", getOutputTypes(), getScopes(), Format.JAR);
+ mkdirs(outJar.getParentFile());
+ jarMerger = new JarMerger(outJar);
+ }
+
+ jarMerger.addEntry(key, baos.toByteArray());
+ } else {
+ if (outFolder == null) {
+ outFolder = outputProvider.getContentLocation(
+ "main",
+ getOutputTypes(), getScopes(),
+ Format.DIRECTORY);
+ mkdirs(outFolder);
+ }
+
+ Files.write(baos.toByteArray(), computeFile(outFolder, key));
+ }
+ }
+ }
+
+ if (jarMerger != null) {
+ jarMerger.close();
+ }
+ }
+ }
+
+ @Nullable
+ private static QualifiedContent findUniqueOrProjectContent(
+ @NonNull List<QualifiedContent> contentSourceList) {
+ if (contentSourceList.size() == 1) {
+ return contentSourceList.get(0);
+ }
+
+ for (QualifiedContent content : contentSourceList) {
+ if (content.getScopes().contains(Scope.PROJECT)) {
+ return content;
+ }
+ }
+
+ return null;
+ }
+
+ private void copyFromFolder(
+ @NonNull File fromFolder,
+ @NonNull File toFolder,
+ @NonNull String path)
+ throws IOException {
+ File from = computeFile(fromFolder, validator.keyToFolderPath(path));
+ File to = computeFile(toFolder, path);
+ mkdirs(to.getParentFile());
+ Files.copy(from, to);
+ }
+
+ /**
+ * computes a file path from a root folder and a zip archive path.
+ * @param rootFolder the root folder
+ * @param path the archive path
+ * @return the File
+ */
+ private static File computeFile(@NonNull File rootFolder, @NonNull String path) {
+ path = path.replace('/', File.separatorChar);
+ return new File(rootFolder, path);
+ }
+
+ private static class JarFilter implements SignedJarBuilder.IZipEntryFilter {
+ private final Set<String> allowedPath = Sets.newHashSet();
+
+ void resetList(@NonNull List<String> paths) {
+ allowedPath.clear();
+ allowedPath.addAll(paths);
+ }
+
+ @Override
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ return allowedPath.contains(archivePath);
+ }
+ }
+
+ private static JarMerger copyIntoJar(@NonNull ListMultimap<File, String> jarSources,
+ @NonNull File outJar)
+ throws IOException {
+ JarMerger jarMerger = new JarMerger(outJar);
+
+ JarFilter filter = new JarFilter();
+ jarMerger.setFilter(filter);
+
+ for (File jarFile : jarSources.keySet()) {
+ // reset filter to allow the expected list of files for that particular jar file.
+ filter.resetList(jarSources.get(jarFile));
+
+ // copy the jar file
+ jarMerger.addJar(jarFile, true);
+ }
+
+ return jarMerger;
+ }
+
+ private void gatherListFromJar(
+ @NonNull JarInput jarInput,
+ @NonNull ListMultimap<String, QualifiedContent> content) throws IOException {
+
+ ZipFile zipFile = new ZipFile(jarInput.getFile());
+ try {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+
+ String path = entry.getName();
+ if (skipEntry(entry, path)) {
+ continue;
+ }
+
+ content.put(path, jarInput);
+ }
+
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ private boolean skipEntry(
+ @NonNull ZipEntry entry,
+ @NonNull String path) {
+ if (entry.isDirectory() ||
+ JarFile.MANIFEST_NAME.equals(path) ||
+ !validator.validateJarPath(path)) {
+ return true;
+ }
+
+ // split the path into segments.
+ String[] segments = path.split("/");
+
+ // empty path? skip to next entry.
+ if (segments.length == 0) {
+ return true;
+ }
+
+ // Check each folders to make sure they should be included.
+ // Folders like CVS, .svn, etc.. should already have been excluded from the
+ // jar file, but we need to exclude some other folder (like /META-INF) so
+ // we check anyway.
+ for (int i = 0 ; i < segments.length - 1; i++) {
+ if (!PackagingUtils.checkFolderForPackaging(segments[i])) {
+ return true;
+ }
+ }
+
+ return !PackagingUtils.checkFileForPackaging(segments[segments.length-1],
+ false /*allowClassFiles*/);
+ }
+
+ private void gatherListFromFolder(
+ @NonNull DirectoryInput directoryInput,
+ @NonNull ListMultimap<String, QualifiedContent> content) {
+ gatherListFromFolder(directoryInput.getFile(), "", directoryInput, content);
+ }
+
+ private void gatherListFromFolder(
+ @NonNull File file,
+ @NonNull String path,
+ @NonNull DirectoryInput directoryInput,
+ @NonNull ListMultimap<String, QualifiedContent> content) {
+ File[] children = file.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+
+ return file.isDirectory() || !name.endsWith(SdkConstants.DOT_CLASS);
+ }
+ });
+
+ if (children != null) {
+ for (File child : children) {
+ String newPath = path.isEmpty() ? child.getName() : path + '/' + child.getName();
+ if (child.isDirectory()) {
+ gatherListFromFolder(
+ child,
+ newPath,
+ directoryInput,
+ content);
+ } else if (child.isFile() && validator.validateFolderPath(newPath)) {
+ content.put(validator.folderPathToKey(newPath), directoryInput);
+ }
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/MultiDexTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/MultiDexTransform.java
new file mode 100644
index 0000000..d02d056
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/MultiDexTransform.java
@@ -0,0 +1,247 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import proguard.ParseException;
+
+/**
+ * Transform for multi-dex.
+ *
+ * This does not actually consume anything, rather it only reads streams and extract information
+ * from them.
+ */
+public class MultiDexTransform extends BaseProguardAction {
+
+ @NonNull
+ private final File manifestKeepListFile;
+ @NonNull
+ private final VariantScope variantScope;
+ @Nullable
+ private final File includeInMainDexJarFile;
+
+ @NonNull
+ private final File configFileOut;
+ @NonNull
+ private final File mainDexListFile;
+
+ public MultiDexTransform(
+ @NonNull File manifestKeepListFile,
+ @NonNull VariantScope variantScope,
+ @Nullable File includeInMainDexJarFile) {
+ this.manifestKeepListFile = manifestKeepListFile;
+ this.variantScope = variantScope;
+ this.includeInMainDexJarFile = includeInMainDexJarFile;
+ configFileOut = new File(variantScope.getGlobalScope().getBuildDir() + "/" + FD_INTERMEDIATES
+ + "/multi-dex/" + variantScope.getVariantConfiguration().getDirName()
+ + "/components.flags");
+ mainDexListFile = variantScope.getMainDexListFile();
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "multidexlist";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return ImmutableSet.<ContentType>of(QualifiedContent.DefaultContentType.CLASSES);
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getOutputTypes() {
+ return ImmutableSet.of();
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return TransformManager.EMPTY_SCOPES;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileInputs() {
+ if (includeInMainDexJarFile != null) {
+ return ImmutableList.of(includeInMainDexJarFile);
+ }
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileOutputs() {
+ return Lists.newArrayList(mainDexListFile, configFileOut);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+
+ try {
+ File input = verifyInputs(invocation.getReferencedInputs());
+ shrinkWithProguard(input);
+ computeList(input);
+ } catch (ParseException e) {
+ throw new TransformException(e);
+ } catch (ProcessException e) {
+ throw new TransformException(e);
+ }
+ }
+
+ private static File verifyInputs(@NonNull Collection<TransformInput> inputs) {
+ // Collect the inputs. There should be only one.
+ List<File> inputFiles = Lists.newArrayList();
+
+ for (TransformInput transformInput : inputs) {
+ for (JarInput jarInput : transformInput.getJarInputs()) {
+ inputFiles.add(jarInput.getFile());
+ }
+
+ for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
+ inputFiles.add(directoryInput.getFile());
+ }
+ }
+
+ return Iterables.getOnlyElement(inputFiles);
+ }
+
+ private void shrinkWithProguard(@NonNull File input) throws IOException, ParseException {
+ dontobfuscate();
+ dontoptimize();
+ dontpreverify();
+ dontwarn();
+ dontnote();
+ forceprocessing();
+ applyConfigurationFile(manifestKeepListFile);
+
+ // handle inputs
+ libraryJar(findShrinkedAndroidJar());
+ inJar(input);
+
+ // outputs.
+ outJar(variantScope.getProguardComponentsJarFile());
+ printconfiguration(configFileOut);
+
+ // run proguard
+ runProguard();
+ }
+
+ @NonNull
+ private File findShrinkedAndroidJar() {
+ Preconditions.checkNotNull(
+ variantScope.getGlobalScope().getAndroidBuilder().getTargetInfo());
+ File shrinkedAndroid = new File(
+ variantScope.getGlobalScope().getAndroidBuilder().getTargetInfo()
+ .getBuildTools()
+ .getLocation(),
+ "lib" + File.separatorChar + "shrinkedAndroid.jar");
+
+ if (!shrinkedAndroid.isFile()) {
+ shrinkedAndroid = new File(
+ variantScope.getGlobalScope().getAndroidBuilder().getTargetInfo()
+ .getBuildTools().getLocation(),
+ "multidex" + File.separatorChar + "shrinkedAndroid.jar");
+ }
+ return shrinkedAndroid;
+ }
+
+ private void computeList(File _allClassesJarFile) throws ProcessException, IOException {
+ // manifest components plus immediate dependencies must be in the main dex.
+ Set<String> mainDexClasses = callDx(
+ _allClassesJarFile,
+ variantScope.getProguardComponentsJarFile());
+
+ // add additional classes specified via a jar file.
+ if (includeInMainDexJarFile != null) {
+ // proguard shrinking is overly aggressive when it comes to removing
+ // interface classes: even if an interface is implemented by a concrete
+ // class, if no code actually references the interface class directly
+ // (i.e., code always references the concrete class), proguard will
+ // remove the interface class when shrinking. This is problematic,
+ // as the runtime verifier still needs the interface class to be
+ // present, or the concrete class won't be valid. Use a
+ // ClassReferenceListBuilder here (only) to pull in these missing
+ // interface classes. Note that doing so brings in other unnecessary
+ // stuff, too; next time we're low on main dex space, revisit this!
+ mainDexClasses.addAll(callDx(_allClassesJarFile, includeInMainDexJarFile));
+ }
+/*
+ TODO: figure this out this wasn't plugged-in in the previous version.
+ if (manifestKeepListFile != null) {
+ Set<String> mainDexList = new HashSet<String>(
+ Files.readLines(manifestKeepListFile, Charsets.UTF_8));
+ mainDexClasses.addAll(mainDexList);
+ }*/
+
+ String fileContent = Joiner.on(System.getProperty("line.separator")).join(mainDexClasses);
+
+ Files.write(fileContent, mainDexListFile, Charsets.UTF_8);
+
+ }
+
+ private Set<String> callDx(File allClassesJarFile, File jarOfRoots) throws ProcessException {
+ return variantScope.getGlobalScope().getAndroidBuilder().createMainDexList(
+ allClassesJarFile, jarOfRoots);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/NewShrinkerTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/NewShrinkerTransform.java
new file mode 100644
index 0000000..2d44960
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/NewShrinkerTransform.java
@@ -0,0 +1,292 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.build.gradle.shrinker.AbstractShrinker.logTime;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.shrinker.AbstractShrinker.CounterSet;
+import com.android.build.gradle.shrinker.FullRunShrinker;
+import com.android.build.gradle.shrinker.IncrementalShrinker;
+import com.android.build.gradle.shrinker.JavaSerializationShrinkerGraph;
+import com.android.build.gradle.shrinker.KeepRules;
+import com.android.build.gradle.shrinker.ProguardConfig;
+import com.android.build.gradle.shrinker.ProguardFlagsKeepRules;
+import com.android.build.gradle.shrinker.ShrinkerLogger;
+import com.android.builder.core.VariantType;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.gradle.tooling.BuildException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Transform that performs shrinking - only reachable methods in reachable class files are copied
+ * into the output folders (one per stream).
+ */
+public class NewShrinkerTransform extends ProguardConfigurable {
+
+ private static final Logger logger = LoggerFactory.getLogger(NewShrinkerTransform.class);
+ private static final String NAME = "newClassShrinker";
+
+ private final VariantType variantType;
+ private final Set<File> platformJars;
+ private final File incrementalDir;
+ private final List<String> dontwarnLines;
+ private final List<String> keepLines;
+
+ public NewShrinkerTransform(@NonNull VariantScope scope) {
+ this.platformJars = ImmutableSet.copyOf(
+ scope.getGlobalScope().getAndroidBuilder().getBootClasspath(true));
+ this.variantType = scope.getVariantData().getType();
+ this.incrementalDir = scope.getIncrementalDir(scope.getTaskName(NAME));
+ this.dontwarnLines = Lists.newArrayList();
+ this.keepLines = Lists.newArrayList();
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @NonNull
+ @Override
+ public Set<QualifiedContent.ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ if (variantType == VariantType.LIBRARY) {
+ return Sets.immutableEnumSet(Scope.PROJECT, Scope.PROJECT_LOCAL_DEPS);
+ }
+
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ Set<Scope> set = Sets.newLinkedHashSetWithExpectedSize(5);
+ if (variantType == VariantType.LIBRARY) {
+ set.add(Scope.SUB_PROJECTS);
+ set.add(Scope.SUB_PROJECTS_LOCAL_DEPS);
+ set.add(Scope.EXTERNAL_LIBRARIES);
+ }
+
+ if (variantType.isForTesting()) {
+ set.add(Scope.TESTED_CODE);
+ }
+
+ set.add(Scope.PROVIDED_ONLY);
+
+ return Sets.immutableEnumSet(set);
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileInputs() {
+ return getAllConfigurationFiles();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryDirectoryOutputs() {
+ return ImmutableList.of(incrementalDir);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+ TransformOutputProvider output = invocation.getOutputProvider();
+ Collection<TransformInput> referencedInputs = invocation.getReferencedInputs();
+
+ checkNotNull(output, "Missing output object for transform " + getName());
+
+ if (isIncrementalRun(invocation.isIncremental(), referencedInputs)) {
+ incrementalRun(invocation.getInputs(), referencedInputs, output);
+ } else {
+ fullRun(invocation.getInputs(), referencedInputs, output);
+ }
+ }
+
+ private void fullRun(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedInputs,
+ @NonNull TransformOutputProvider output) throws IOException {
+ ProguardConfig config = getConfig();
+
+ ShrinkerLogger shrinkerLogger =
+ new ShrinkerLogger(config.getFlags().getDontWarnSpecs(), logger);
+
+ FullRunShrinker<String> shrinker =
+ new FullRunShrinker<String>(
+ new WaitableExecutor<Void>(),
+ JavaSerializationShrinkerGraph.empty(incrementalDir),
+ platformJars,
+ shrinkerLogger);
+
+ // Only save state if incremental mode is enabled.
+ boolean saveState = this.isIncremental();
+
+ shrinker.run(
+ inputs,
+ referencedInputs,
+ output,
+ ImmutableMap.<CounterSet, KeepRules>of(
+ CounterSet.SHRINK,
+ new ProguardFlagsKeepRules(config.getFlags(), shrinkerLogger)),
+ saveState);
+
+ checkForWarnings(config, shrinkerLogger);
+ }
+
+ private static void checkForWarnings(
+ @NonNull ProguardConfig config,
+ @NonNull ShrinkerLogger shrinkerLogger) {
+ if (shrinkerLogger.getWarningsCount() > 0 && !config.getFlags().isIgnoreWarnings()) {
+ throw new BuildException(
+ "Warnings found during shrinking, please use -dontwarn or -ignorewarnings to suppress them.",
+ null);
+ }
+ }
+
+ @NonNull
+ private ProguardConfig getConfig() throws IOException {
+ ProguardConfig config = new ProguardConfig();
+
+ for (File configFile : getAllConfigurationFiles()) {
+ config.parse(configFile);
+ }
+
+ config.parse(getAdditionalConfigString());
+ return config;
+ }
+
+ @NonNull
+ private String getAdditionalConfigString() {
+ StringBuilder sb = new StringBuilder();
+
+ for (String keepLine : keepLines) {
+ sb.append("-keep ");
+ sb.append(keepLine);
+ sb.append("\n");
+ }
+
+ for (String dontWarn : dontwarnLines) {
+ sb.append("-dontwarn ");
+ sb.append(dontWarn);
+ sb.append("\n");
+ }
+
+ return sb.toString();
+ }
+
+ private void incrementalRun(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedInputs,
+ @NonNull TransformOutputProvider output) throws IOException {
+ try {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ JavaSerializationShrinkerGraph graph =
+ JavaSerializationShrinkerGraph.readFromDir(
+ incrementalDir,
+ this.getClass().getClassLoader());
+ logTime("loading state", stopwatch);
+
+ ProguardConfig config = getConfig();
+
+ ShrinkerLogger shrinkerLogger =
+ new ShrinkerLogger(config.getFlags().getDontWarnSpecs(), logger);
+
+ IncrementalShrinker<String> shrinker =
+ new IncrementalShrinker<String>(new WaitableExecutor<Void>(), graph, shrinkerLogger);
+ shrinker.incrementalRun(inputs, output);
+ checkForWarnings(config, shrinkerLogger);
+ } catch (IncrementalShrinker.IncrementalRunImpossibleException e) {
+ logger.warn("Incremental shrinker run impossible: " + e.getMessage());
+ // Log the full stack trace at INFO level for debugging.
+ logger.info("Incremental shrinker run impossible: " + e.getMessage(), e);
+ fullRun(inputs, referencedInputs, output);
+ }
+ }
+
+ private static boolean isIncrementalRun(
+ boolean isIncremental,
+ @NonNull Collection<TransformInput> referencedInputs) {
+ if (!isIncremental) {
+ return false;
+ }
+
+ for (TransformInput referencedInput : referencedInputs) {
+ for (JarInput jarInput : referencedInput.getJarInputs()) {
+ if (jarInput.getStatus() != Status.NOTCHANGED) {
+ return false;
+ }
+ }
+
+ for (DirectoryInput directoryInput : referencedInput.getDirectoryInputs()) {
+ if (!directoryInput.getChangedFiles().isEmpty()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void keep(@NonNull String keep) {
+ this.keepLines.add(keep);
+ }
+
+ @Override
+ public void dontwarn(@NonNull String dontwarn) {
+ this.dontwarnLines.add(dontwarn);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/NoChangesVerifierTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/NoChangesVerifierTransform.java
new file mode 100644
index 0000000..76be38e
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/NoChangesVerifierTransform.java
@@ -0,0 +1,123 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * No-op transform that verifies if any Java resources changed.
+ */
+public class NoChangesVerifierTransform extends Transform {
+
+ @NonNull
+ private final VariantScope variantScope;
+ @NonNull
+ private final Set<ContentType> inputTypes;
+ @NonNull
+ private final Set<Scope> mergeScopes;
+ @NonNull
+ private final InstantRunVerifierStatus failureStatus;
+ private final boolean abortBuild;
+
+ public NoChangesVerifierTransform(
+ @NonNull VariantScope variantScope,
+ @NonNull Set<ContentType> inputTypes,
+ @NonNull Set<Scope> mergeScopes,
+ @NonNull InstantRunVerifierStatus failureStatus,
+ boolean abortBuild) {
+ this.variantScope = variantScope;
+ this.inputTypes = inputTypes;
+ this.mergeScopes = mergeScopes;
+ this.failureStatus = failureStatus;
+ this.abortBuild = abortBuild;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "javaResourcesVerifier";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return inputTypes;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return ImmutableSet.of();
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ return mergeScopes;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return true;
+ }
+
+ @Override
+ public void transform(@NonNull TransformInvocation transformInvocation)
+ throws TransformException, InterruptedException, IOException {
+ // This task will not be invoked on the initial assemble build. For subsequent instant run
+ // build, we want to fail the verifier if any Java resource changed. (Native libraries are
+ // treated as Java resources in the plugin)
+ if (hasChangedInputs(transformInvocation.getReferencedInputs())) {
+ variantScope.getInstantRunBuildContext().setVerifierResult(failureStatus);
+ if (abortBuild) {
+ variantScope.getInstantRunBuildContext().abort();
+ }
+ }
+ }
+
+ private static boolean hasChangedInputs(Collection<TransformInput> inputs) {
+ for (TransformInput input : inputs) {
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ if (!directoryInput.getChangedFiles().isEmpty()) {
+ return true;
+ }
+ }
+ for (JarInput jarInput : input.getJarInputs()) {
+ if (jarInput.getStatus() != Status.NOTCHANGED) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ProGuardTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ProGuardTransform.java
new file mode 100644
index 0000000..2c9ce45
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ProGuardTransform.java
@@ -0,0 +1,365 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+import static com.android.utils.FileUtils.mkdirs;
+import static com.android.utils.FileUtils.renameTo;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.LibraryVariantData;
+import com.android.build.gradle.tasks.SimpleWorkQueue;
+import com.android.builder.tasks.Job;
+import com.android.builder.tasks.JobContext;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import proguard.ClassPath;
+
+/**
+ * ProGuard support as a transform
+ */
+public class ProGuardTransform extends BaseProguardAction {
+
+ private final VariantScope variantScope;
+ private final boolean asJar;
+
+ private final boolean isLibrary;
+ private final boolean isTest;
+
+ private final File proguardOut;
+
+ private final File printMapping;
+ private final File dump;
+ private final File printSeeds;
+ private final File printUsage;
+ private final ImmutableList<File> secondaryFileOutputs;
+
+ private File testedMappingFile = null;
+ private org.gradle.api.artifacts.Configuration testMappingConfiguration = null;
+
+ public ProGuardTransform(
+ @NonNull VariantScope variantScope,
+ boolean asJar) {
+ this.variantScope = variantScope;
+
+ // TODO: Allow asJar to be true, once we make sure input jars have unique file names.
+ // There cannot be duplicate classes.jar inputs for example. This confuses ProGuard in
+ // "directory output" mode.
+ this.asJar = true;
+
+ isLibrary = variantScope.getVariantData() instanceof LibraryVariantData;
+ isTest = variantScope.getTestedVariantData() != null;
+
+ GlobalScope globalScope = variantScope.getGlobalScope();
+ proguardOut = new File(Joiner.on(File.separatorChar).join(
+ String.valueOf(globalScope.getBuildDir()),
+ FD_OUTPUTS,
+ "mapping",
+ variantScope.getVariantConfiguration().getDirName()));
+
+ printMapping = new File(proguardOut, "mapping.txt");
+ dump = new File(proguardOut, "dump.txt");
+ printSeeds = new File(proguardOut, "seeds.txt");
+ printUsage = new File(proguardOut, "usage.txt");
+ secondaryFileOutputs = ImmutableList.of(printMapping, dump, printSeeds, printUsage);
+ }
+
+ @Nullable
+ public File getMappingFile() {
+ return printMapping;
+ }
+
+ public void applyTestedMapping(@Nullable File testedMappingFile) {
+ this.testedMappingFile = testedMappingFile;
+ }
+
+ public void applyTestedMapping(
+ @Nullable org.gradle.api.artifacts.Configuration testMappingConfiguration) {
+ this.testMappingConfiguration = testMappingConfiguration;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "proguard";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return TransformManager.CONTENT_JARS;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ if (isLibrary) {
+ return Sets.immutableEnumSet(Scope.PROJECT, Scope.PROJECT_LOCAL_DEPS);
+ }
+
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ Set<Scope> set = Sets.newLinkedHashSetWithExpectedSize(5);
+ if (isLibrary) {
+ set.add(Scope.SUB_PROJECTS);
+ set.add(Scope.SUB_PROJECTS_LOCAL_DEPS);
+ set.add(Scope.EXTERNAL_LIBRARIES);
+ }
+
+ if (isTest) {
+ set.add(Scope.TESTED_CODE);
+ }
+
+ set.add(Scope.PROVIDED_ONLY);
+
+ return Sets.immutableEnumSet(set);
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileInputs() {
+ final List<File> files = Lists.newArrayList();
+
+ // the mapping file.
+ File testedMappingFile = computeMappingFile();
+ if (testedMappingFile != null) {
+ files.add(testedMappingFile);
+ }
+
+ // the config files
+ files.addAll(getAllConfigurationFiles());
+
+ return files;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileOutputs() {
+ return secondaryFileOutputs;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void transform(final TransformInvocation invocation) throws TransformException {
+ // only run one minification at a time (across projects)
+ final Job<Void> job = new Job<Void>(getName(),
+ new com.android.builder.tasks.Task<Void>() {
+ @Override
+ public void run(@NonNull Job<Void> job,
+ @NonNull JobContext<Void> context) throws IOException {
+ doMinification(
+ invocation.getInputs(),
+ invocation.getReferencedInputs(),
+ invocation.getOutputProvider());
+ }
+ });
+ try {
+ SimpleWorkQueue.push(job);
+
+ // wait for the task completion.
+ if (!job.awaitRethrowExceptions()) {
+ throw new RuntimeException("Job failed, see logs for details");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void doMinification(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedInputs,
+ @Nullable TransformOutputProvider output) throws IOException {
+ checkNotNull(output, "Missing output object for transform " + getName());
+ Set<ContentType> outputTypes = getOutputTypes();
+ Set<Scope> scopes = getScopes();
+ File outFile = output.getContentLocation("main", outputTypes, scopes,
+ asJar ? Format.JAR : Format.DIRECTORY);
+ if (asJar) {
+ mkdirs(outFile.getParentFile());
+ } else {
+ mkdirs(outFile);
+ }
+
+ try {
+ GlobalScope globalScope = variantScope.getGlobalScope();
+
+ if (isLibrary) {
+ keep("class **.R");
+ keep("class **.R$*");
+ }
+
+ // set the mapping file if there is one.
+ File testedMappingFile = computeMappingFile();
+ if (testedMappingFile != null) {
+ applyMapping(testedMappingFile);
+ }
+
+ // --- InJars / LibraryJars ---
+ addInputsToConfiguration(inputs, false);
+ addInputsToConfiguration(referencedInputs, true);
+
+ // libraryJars: the runtime jars, with all optional libraries.
+ for (File runtimeJar : globalScope.getAndroidBuilder().getBootClasspath(true)) {
+ libraryJar(runtimeJar);
+ }
+
+ // --- Out files ---
+ outJar(outFile);
+
+ // proguard doesn't verify that the seed/mapping/usage folders exist and will fail
+ // if they don't so create them.
+ mkdirs(proguardOut);
+
+ for (File configFile : getAllConfigurationFiles()) {
+ applyConfigurationFile(configFile);
+ }
+
+ configuration.printMapping = printMapping;
+ configuration.dump = dump;
+ configuration.printSeeds = printSeeds;
+ configuration.printUsage = printUsage;
+
+ forceprocessing();
+ runProguard();
+
+ if (!asJar) {
+ // if the output of proguard is a folder (rather than a single jar), the
+ // dependencies will be written as jar in the same folder output.
+ // So we move it to their normal location as new jar outputs.
+ File[] jars = outFile.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String name) {
+ return name.endsWith(DOT_JAR);
+ }
+ });
+ if (jars != null) {
+ for (File jarFile : jars) {
+ String jarFileName = jarFile.getName();
+ File to = output.getContentLocation(
+ jarFileName.substring(0, jarFileName.length() - DOT_JAR.length()),
+ outputTypes, scopes, Format.JAR);
+ mkdirs(to.getParentFile());
+ renameTo(jarFile, to);
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ if (e instanceof IOException) {
+ throw (IOException) e;
+ }
+
+ throw new IOException(e);
+ }
+ }
+
+ private void addInputsToConfiguration(
+ @NonNull Collection<TransformInput> inputs,
+ boolean referencedOnly) {
+ ClassPath classPath;
+ List<String> baseFilter;
+
+ if (referencedOnly) {
+ classPath = configuration.libraryJars;
+ baseFilter = JAR_FILTER;
+ } else {
+ classPath = configuration.programJars;
+ baseFilter = null;
+ }
+
+ for (TransformInput transformInput : inputs) {
+ for (JarInput jarInput : transformInput.getJarInputs()) {
+ handleQualifiedContent(classPath, jarInput, baseFilter);
+ }
+
+ for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
+ handleQualifiedContent(classPath, directoryInput, baseFilter);
+ }
+ }
+ }
+
+ private static void handleQualifiedContent(
+ @NonNull ClassPath classPath,
+ @NonNull QualifiedContent content,
+ @Nullable List<String> baseFilter) {
+ List<String> filter = baseFilter;
+
+ if (!content.getContentTypes().contains(DefaultContentType.CLASSES)) {
+ // if the content is not meant to contain classes, we ignore them
+ // in case they are present.
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ if (filter != null) {
+ builder.addAll(filter);
+ }
+ builder.add("!**/*.class");
+ filter = builder.build();
+ } else if (!content.getContentTypes().contains(DefaultContentType.RESOURCES)) {
+ // if the content is not meant to contain resources, we ignore them
+ // in case they are present (by accepting only classes.)
+ filter = ImmutableList.of("**/*.class");
+ }
+
+ inputJar(classPath, content.getFile(), filter);
+ }
+
+ @Nullable
+ private File computeMappingFile() {
+ if (testedMappingFile != null && testedMappingFile.isFile()) {
+ return testedMappingFile;
+ } else if (testMappingConfiguration != null && testMappingConfiguration.getSingleFile().isFile()) {
+ return testMappingConfiguration.getSingleFile();
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ProguardConfigurable.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ProguardConfigurable.java
new file mode 100644
index 0000000..c2407eb
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ProguardConfigurable.java
@@ -0,0 +1,50 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.Transform;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Base class for transforms that consume ProGuard configuration files.
+ */
+public abstract class ProguardConfigurable extends Transform {
+ protected final List<Supplier<Collection<File>>> configurationFiles =
+ Lists.newArrayListWithExpectedSize(3);
+
+ public void setConfigurationFiles(Supplier<Collection<File>> configFiles) {
+ configurationFiles.add(configFiles);
+ }
+
+ protected List<File> getAllConfigurationFiles() {
+ List<File> files = Lists.newArrayList();
+ for (Supplier<Collection<File>> supplier : configurationFiles) {
+ files.addAll(supplier.get());
+ }
+ return files;
+ }
+
+ public abstract void keep(@NonNull String keep);
+
+ public abstract void dontwarn(@NonNull String dontwarn);
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ShrinkResourcesTransform.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ShrinkResourcesTransform.java
new file mode 100644
index 0000000..55265e4
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/transforms/ShrinkResourcesTransform.java
@@ -0,0 +1,296 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.ResourceUsageAnalyzer;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.builder.core.AaptPackageProcessBuilder;
+import com.android.builder.core.AndroidBuilder;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Implementation of Resource Shrinking as a transform.
+ *
+ * Since this transform only reads the data from the stream but does not output anything
+ * back into the stream, it is a no-op transform, asking only for referenced scopes, and not
+ * "consumed" scopes.
+ * <p>
+ * To run the tests specifically related to resource shrinking:
+ * <pre>
+ * ./gradlew :base:int:test -Dtest.single=ShrinkResourcesTest
+ * </pre>
+ */
+public class ShrinkResourcesTransform extends Transform {
+
+ /** Whether we've already warned about how to turn off shrinking. Used to avoid
+ * repeating the same multi-line message for every repeated abi split. */
+ private static boolean ourWarned = true; // Logging disabled until shrinking is on by default.
+
+ /**
+ * Associated variant data that the strip task will be run against. Used to locate
+ * not only locations the task needs (e.g. for resources and generated R classes)
+ * but also to obtain the resource merging task, since we will run it a second time
+ * here to generate a new .ap_ file with fewer resources
+ */
+ @NonNull
+ private final BaseVariantOutputData variantOutputData;
+ @NonNull
+ private final File uncompressedResources;
+ @NonNull
+ private final File compressedResources;
+
+ @NonNull
+ private final AndroidBuilder androidBuilder;
+ @NonNull
+ private final Logger logger;
+
+ @NonNull
+ private final ImmutableList<File> secondaryInputs;
+
+ private final File sourceDir;
+ private final File resourceDir;
+ private final File mergedManifest;
+ private final File mappingFile;
+
+ public ShrinkResourcesTransform(
+ @NonNull BaseVariantOutputData variantOutputData,
+ @NonNull File uncompressedResources,
+ @NonNull File compressedResources,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull Logger logger) {
+ this.variantOutputData = variantOutputData;
+ this.uncompressedResources = uncompressedResources;
+ this.compressedResources = compressedResources;
+ this.androidBuilder = androidBuilder;
+ this.logger = logger;
+
+ BaseVariantData<?> variantData = variantOutputData.variantData;
+ ProcessAndroidResources processResourcesTask = variantData.generateRClassTask;
+ sourceDir = processResourcesTask.getSourceOutputDir();
+ resourceDir = variantData.getScope().getFinalResourcesDir();
+ mergedManifest = variantOutputData.manifestProcessorTask.getManifestOutputFile();
+ mappingFile = variantData.getMappingFile();
+
+ if (mappingFile != null) {
+ secondaryInputs = ImmutableList.of(
+ uncompressedResources,
+ sourceDir,
+ resourceDir,
+ mergedManifest,
+ mappingFile);
+ } else {
+ secondaryInputs = ImmutableList.of(
+ uncompressedResources,
+ sourceDir,
+ resourceDir,
+ mergedManifest);
+ }
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "shrinkRes";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getOutputTypes() {
+ return ImmutableSet.of();
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return TransformManager.EMPTY_SCOPES;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileInputs() {
+ return secondaryInputs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getSecondaryFileOutputs() {
+ return ImmutableList.of(compressedResources);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+
+ // there should be only one input since this transform is always applied after
+ // proguard.
+ TransformInput input = Iterables.getOnlyElement(invocation.getReferencedInputs());
+ File minifiedOutJar = Iterables.getOnlyElement(input.getJarInputs()).getFile();
+
+ BaseVariantData<?> variantData = variantOutputData.variantData;
+ ProcessAndroidResources processResourcesTask = variantData.generateRClassTask;
+
+ File reportFile = null;
+ if (mappingFile != null) {
+ File logDir = mappingFile.getParentFile();
+ if (logDir != null) {
+ reportFile = new File(logDir, "resources.txt");
+ }
+ }
+
+ // Analyze resources and usages and strip out unused
+ ResourceUsageAnalyzer analyzer = new ResourceUsageAnalyzer(
+ sourceDir,
+ minifiedOutJar,
+ mergedManifest,
+ mappingFile,
+ resourceDir,
+ reportFile);
+ try {
+ analyzer.setVerbose(logger.isEnabled(LogLevel.INFO));
+ analyzer.setDebug(logger.isEnabled(LogLevel.DEBUG));
+ analyzer.analyze();
+
+ if (ResourceUsageAnalyzer.TWO_PASS_AAPT) {
+ // This is currently not working; we need support from aapt to be able
+ // to assign a stable set of resources that it should use.
+ File destination = new File(resourceDir.getParentFile(), resourceDir.getName() + "-stripped");
+ analyzer.removeUnused(destination);
+
+ File sourceOutputs = new File(sourceDir.getParentFile(),
+ sourceDir.getName() + "-stripped");
+ FileUtils.mkdirs(sourceOutputs);
+
+ // We don't need to emit R files again, but we can do this here such that
+ // we can *verify* that the R classes generated in the second aapt pass
+ // matches those we saw the first time around.
+ //String sourceOutputPath = sourceOutputs?.getAbsolutePath();
+ String sourceOutputPath = null;
+
+ // Repackage the resources:
+ AaptPackageProcessBuilder aaptPackageCommandBuilder =
+ new AaptPackageProcessBuilder(
+ mergedManifest,
+ processResourcesTask.getAaptOptions())
+ .setAssetsFolder(processResourcesTask.getAssetsDir())
+ .setResFolder(destination)
+ .setLibraries(processResourcesTask.getLibraries())
+ .setPackageForR(processResourcesTask.getPackageForR())
+ .setSourceOutputDir(sourceOutputPath)
+ .setResPackageOutput(compressedResources.getAbsolutePath())
+ .setType(processResourcesTask.getType())
+ .setDebuggable(processResourcesTask.getDebuggable())
+ .setResourceConfigs(processResourcesTask.getResourceConfigs())
+ .setSplits(processResourcesTask.getSplits());
+
+ androidBuilder.processResources(
+ aaptPackageCommandBuilder,
+ processResourcesTask.getEnforceUniquePackageName(),
+ new LoggedProcessOutputHandler(androidBuilder.getLogger())
+ );
+ } else {
+ // Just rewrite the .ap_ file to strip out the res/ files for unused resources
+ analyzer.rewriteResourceZip(uncompressedResources, compressedResources);
+ }
+
+ // Dump some stats
+ int unused = analyzer.getUnusedResourceCount();
+ if (unused > 0) {
+ StringBuilder sb = new StringBuilder(200);
+ sb.append("Removed unused resources");
+
+ // This is a bit misleading until we can strip out all resource types:
+ //int total = analyzer.getTotalResourceCount()
+ //sb.append("(" + unused + "/" + total + ")")
+
+ long before = uncompressedResources.length();
+ long after = compressedResources.length();
+ long percent = (int) ((before - after) * 100 / before);
+ sb.append(": Binary resource data reduced from ").
+ append(toKbString(before)).
+ append("KB to ").
+ append(toKbString(after)).
+ append("KB: Removed ").append(percent).append("%");
+ if (!ourWarned) {
+ ourWarned = true;
+ sb.append(
+ "\nNote: If necessary, you can disable resource shrinking by adding\n" +
+ "android {\n" +
+ " buildTypes {\n" +
+ " " + variantData.getVariantConfiguration().getBuildType().getName() + " {\n" +
+ " shrinkResources false\n" +
+ " }\n" +
+ " }\n" +
+ "}");
+ }
+
+ System.out.println(sb.toString());
+ }
+ } catch (Exception e) {
+ System.out.println("Failed to shrink resources: " + e.toString() + "; ignoring");
+ logger.quiet("Failed to shrink resources: ignoring", e);
+ } finally {
+ analyzer.dispose();
+ }
+ }
+
+ private static String toKbString(long size) {
+ return Integer.toString((int)size/1024);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java
new file mode 100644
index 0000000..9a21827
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.builder.core.ErrorReporter;
+
+import org.gradle.api.DefaultTask;
+
+import java.util.Collection;
+
+/**
+ * Base data about a variant that generates an APK file.
+ */
+public abstract class ApkVariantData extends BaseVariantData<ApkVariantOutputData> {
+
+ public DefaultTask installTask;
+ public DefaultTask uninstallTask;
+
+ protected ApkVariantData(
+ @NonNull AndroidConfig androidConfig,
+ @NonNull TaskManager taskManager,
+ @NonNull GradleVariantConfiguration config,
+ @NonNull ErrorReporter errorReporter) {
+ super(androidConfig, taskManager, config, errorReporter);
+ }
+
+ @Override
+ @NonNull
+ protected ApkVariantOutputData doCreateOutput(
+ OutputFile.OutputType outputType,
+ Collection<FilterData> filters) {
+ return new ApkVariantOutputData(outputType, filters, this, taskManager);
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ if (getVariantConfiguration().hasFlavors()) {
+ return String.format("%s%s build",
+ getCapitalizedBuildTypeName(),
+ getCapitalizedFlavorName());
+ } else {
+ return String.format("%s build", getCapitalizedBuildTypeName());
+ }
+ }
+
+ public boolean isSigned() {
+ return getVariantConfiguration().isSigningReady();
+ }
+
+ public boolean getZipAlignEnabled() {
+ return getVariantConfiguration().getBuildType().isZipAlignEnabled();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantOutputData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantOutputData.java
new file mode 100644
index 0000000..26cd0a6
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantOutputData.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.api.ApkOutputFile;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.build.gradle.tasks.SplitZipAlign;
+import com.android.build.gradle.tasks.ZipAlign;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+
+import org.gradle.api.Task;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Base output data for a variant that generates an APK file.
+ */
+public class ApkVariantOutputData extends BaseVariantOutputData {
+
+ public PackageApplication packageApplicationTask;
+ public ZipAlign zipAlignTask;
+ public SplitZipAlign splitZipAlign;
+
+ private TaskManager taskManager;
+ private int versionCodeOverride = -1;
+ private String versionNameOverride = null;
+
+ public ApkVariantOutputData(
+ @NonNull OutputFile.OutputType outputType,
+ @NonNull Collection<FilterData> filters,
+ @NonNull BaseVariantData variantData,
+ @NonNull TaskManager taskManager) {
+ super(outputType, filters, variantData);
+ this.taskManager = taskManager;
+ }
+
+ @Override
+ public void setOutputFile(@NonNull File file) {
+ if (zipAlignTask != null) {
+ zipAlignTask.setOutputFile(file);
+ } else {
+ packageApplicationTask.setOutputFile(file);
+ }
+ }
+
+ @Nullable
+ @Override
+ public File getOutputFile() {
+ if (zipAlignTask != null) {
+ return zipAlignTask.getOutputFile();
+ }
+
+ return packageApplicationTask == null ? null : packageApplicationTask.getOutputFile();
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<ApkOutputFile> getOutputs() {
+ ImmutableList.Builder<ApkOutputFile> outputs = ImmutableList.builder();
+ outputs.add(getMainOutputFile());
+ if (splitZipAlign != null) {
+ outputs.addAll(splitZipAlign.getOutputSplitFiles());
+ } else {
+ if (packageSplitResourcesTask != null) {
+ outputs.addAll(packageSplitResourcesTask.getOutputSplitFiles());
+ }
+ }
+ return outputs.build();
+ }
+
+ @NonNull
+ public ZipAlign createZipAlignTask(@NonNull String taskName, @NonNull File inputFile,
+ @NonNull File outputFile) {
+ //noinspection VariableNotUsedInsideIf
+ if (zipAlignTask != null) {
+ throw new RuntimeException(String.format(
+ "ZipAlign task for variant '%s' already exists.", variantData.getName()));
+ }
+
+ InstantRunBuildContext instantRunBuildContext = getScope().getVariantScope()
+ .getInstantRunBuildContext();
+ zipAlignTask = taskManager.createZipAlignTask(
+ taskName, instantRunBuildContext, inputFile, outputFile);
+
+ // setup dependencies
+ assembleTask.dependsOn(zipAlignTask);
+
+ return zipAlignTask;
+ }
+
+ @Override
+ public int getVersionCode() {
+ if (versionCodeOverride > 0) {
+ return versionCodeOverride;
+ }
+
+ return variantData.getVariantConfiguration().getVersionCode();
+ }
+
+ @NonNull
+ @Override
+ public File getSplitFolder() {
+ return getOutputFile().getParentFile();
+ }
+
+ public String getVersionName() {
+ if (versionNameOverride != null) {
+ return versionNameOverride;
+ }
+
+ return variantData.getVariantConfiguration().getVersionName();
+ }
+
+ public void setVersionCodeOverride(int versionCodeOverride) {
+ this.versionCodeOverride = versionCodeOverride;
+ }
+
+ public int getVersionCodeOverride() {
+ return versionCodeOverride;
+ }
+
+ public void setVersionNameOverride(String versionNameOverride) {
+ this.versionNameOverride = versionNameOverride;
+ }
+
+ public String getVersionNameOverride() {
+ return versionNameOverride;
+ }
+
+ /**
+ * Returns the list of {@link Supplier} for this variant. Some variant can produce more
+ * than one file when dealing with pure splits.
+ * @return the complete list of tasks producing an APK for this variant.
+ */
+ public List<FileSupplier> getSplitOutputFileSuppliers() {
+ ImmutableList.Builder<FileSupplier> tasks = ImmutableList.builder();
+ if (splitZipAlign != null || packageSplitResourcesTask != null) {
+ tasks.addAll(splitZipAlign == null ? packageSplitResourcesTask.getOutputFileSuppliers()
+ : splitZipAlign.getOutputFileSuppliers());
+ }
+ // ABI splits zip are aligned together with the other densities in the splitZipAlign task
+ // so only add the ABI splits from the package task if there was no splitZipAlign task.
+ if (packageSplitAbiTask != null && splitZipAlign == null) {
+ tasks.addAll(packageSplitAbiTask.getOutputFileSuppliers());
+ }
+ return tasks.build();
+ }
+
+ @Nullable
+ public FileSupplier getMetadataFile() throws IOException {
+
+ if (splitZipAlign == null) {
+ return null;
+ }
+ return new FileSupplier() {
+ @NonNull
+ @Override
+ public Task getTask() {
+ return splitZipAlign;
+ }
+
+ @Override
+ public File get() {
+ return splitZipAlign.getApkMetadataFile();
+ }
+ };
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java
new file mode 100644
index 0000000..fee8a57
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.core.VariantType;
+import com.google.common.collect.Maps;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Data about a variant that produce an application APK
+ */
+public class ApplicationVariantData extends ApkVariantData implements TestedVariantData {
+ private final Map<VariantType, TestVariantData> testVariants;
+ private Set<String> compatibleScreens = null;
+
+ public ApplicationVariantData(
+ @NonNull AndroidConfig androidConfig,
+ @NonNull GradleVariantConfiguration config,
+ @NonNull TaskManager taskManager,
+ @NonNull ErrorReporter errorReporter) {
+ super(androidConfig, taskManager, config, errorReporter);
+ testVariants = Maps.newEnumMap(VariantType.class);
+ }
+
+
+ public void setCompatibleScreens(Set<String> compatibleScreens) {
+ this.compatibleScreens = compatibleScreens;
+ }
+
+ @NonNull
+ public Set<String> getCompatibleScreens() {
+ if (compatibleScreens == null) {
+ return Collections.emptySet();
+ }
+
+ return compatibleScreens;
+ }
+
+ @Override
+ public void setTestVariantData(
+ @NonNull TestVariantData testVariantData,
+ @NonNull VariantType type) {
+ testVariants.put(type, testVariantData);
+ }
+
+ @Nullable
+ @Override
+ public TestVariantData getTestVariantData(@NonNull VariantType type) {
+ return testVariants.get(type);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java
new file mode 100644
index 0000000..4eedd86
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantFactory.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.variant;
+
+import static com.android.build.OutputFile.NO_FILTER;
+import static com.android.builder.core.BuilderConstants.DEBUG;
+import static com.android.builder.core.BuilderConstants.RELEASE;
+
+import com.android.annotations.NonNull;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.api.ApplicationVariant;
+import com.android.build.gradle.api.BaseVariantOutput;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.VariantModel;
+import com.android.build.gradle.internal.api.ApkVariantImpl;
+import com.android.build.gradle.internal.api.ApkVariantOutputImpl;
+import com.android.build.gradle.internal.api.ApplicationVariantImpl;
+import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.build.gradle.internal.model.FilterDataImpl;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.VariantType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An implementation of VariantFactory for a project that generates APKs.
+ *
+ * This can be an app project, or a test-only project, though the default
+ * behavior is app.
+ */
+public class ApplicationVariantFactory implements VariantFactory {
+
+ Instantiator instantiator;
+ @NonNull
+ protected final AndroidConfig extension;
+ @NonNull
+ private final AndroidBuilder androidBuilder;
+
+ public ApplicationVariantFactory(
+ @NonNull Instantiator instantiator,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull AndroidConfig extension) {
+ this.instantiator = instantiator;
+ this.androidBuilder = androidBuilder;
+ this.extension = extension;
+ }
+
+ @Override
+ @NonNull
+ public BaseVariantData createVariantData(
+ @NonNull GradleVariantConfiguration variantConfiguration,
+ @NonNull TaskManager taskManager) {
+ ApplicationVariantData variant =
+ new ApplicationVariantData(extension, variantConfiguration, taskManager,
+ androidBuilder.getErrorReporter());
+
+ variant.calculateFilters(extension.getSplits());
+
+ Set<String> densities = variant.getFilters(OutputFile.FilterType.DENSITY);
+ Set<String> abis = variant.getFilters(OutputFile.FilterType.ABI);
+
+ if (!densities.isEmpty()) {
+ variant.setCompatibleScreens(extension.getSplits().getDensity()
+ .getCompatibleScreens());
+ }
+
+ // create its outputs
+ if (variant.getSplitHandlingPolicy() ==
+ BaseVariantData.SplitHandlingPolicy.PRE_21_POLICY) {
+
+ // Always dd an entry with no filter for universal and add it FIRST,
+ // since code assume that the first variant output will be the universal one.
+ List<String> orderedDensities = new ArrayList<String>();
+ orderedDensities.add(NO_FILTER);
+ orderedDensities.addAll(densities);
+
+ List<String> orderedAbis = new ArrayList<String>();
+ // if the abi list is empty or we must generate a universal apk, add a NO_FILTER
+ if (abis.isEmpty() || (extension.getSplits().getAbi().isEnable() &&
+ extension.getSplits().getAbi().isUniversalApk())) {
+ orderedAbis.add(NO_FILTER);
+ }
+ orderedAbis.addAll(abis);
+
+ // create its outputs
+ for (String density : orderedDensities) {
+ for (String abi : orderedAbis) {
+ ImmutableList.Builder<FilterData> builder = ImmutableList.builder();
+ if (density != null) {
+ builder.add(FilterDataImpl.build(OutputFile.DENSITY, density));
+ }
+ if (abi != null) {
+ builder.add(FilterDataImpl.build(OutputFile.ABI, abi));
+ }
+ variant.createOutput(
+ OutputFile.OutputType.FULL_SPLIT,
+ builder.build());
+ }
+ }
+ } else {
+ variant.createOutput(OutputFile.OutputType.MAIN,
+ Collections.<FilterData>emptyList());
+ }
+
+ return variant;
+ }
+
+ @Override
+ @NonNull
+ public ApplicationVariant createVariantApi(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
+ @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
+ // create the base variant object.
+ ApplicationVariantImpl variant = instantiator.newInstance(
+ ApplicationVariantImpl.class,
+ variantData,
+ androidBuilder,
+ readOnlyObjectProvider);
+
+ // now create the output objects
+ createApkOutputApiObjects(instantiator, variantData, variant);
+
+ return variant;
+ }
+
+ public static void createApkOutputApiObjects(
+ @NonNull Instantiator instantiator,
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
+ @NonNull ApkVariantImpl variant) {
+ List<? extends BaseVariantOutputData> outputList = variantData.getOutputs();
+ List<BaseVariantOutput> apiOutputList = Lists.newArrayListWithCapacity(outputList.size());
+
+ for (BaseVariantOutputData variantOutputData : outputList) {
+ ApkVariantOutputData apkOutput = (ApkVariantOutputData) variantOutputData;
+
+ ApkVariantOutputImpl output = instantiator.newInstance(
+ ApkVariantOutputImpl.class, apkOutput);
+
+ apiOutputList.add(output);
+ }
+
+ variant.addOutputs(apiOutputList);
+ }
+
+ @NonNull
+ @Override
+ public VariantType getVariantConfigurationType() {
+ return VariantType.DEFAULT;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return false;
+ }
+
+ @Override
+ public boolean hasTestScope() {
+ return true;
+ }
+
+ @Override
+ public void validateModel(@NonNull VariantModel model){
+ // No additional checks for ApplicationVariantFactory, so just return.
+ }
+
+ @Override
+ public void preVariantWork(Project project) {
+ // nothing to be done here.
+ }
+
+ @Override
+ public void createDefaultComponents(
+ @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
+ // must create signing config first so that build type 'debug' can be initialized
+ // with the debug signing config.
+ signingConfigs.create(DEBUG);
+ buildTypes.create(DEBUG);
+ buildTypes.create(RELEASE);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java
new file mode 100644
index 0000000..c3f1253
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.dsl.Splits;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.scope.VariantScopeImpl;
+import com.android.build.gradle.internal.tasks.CheckManifest;
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.internal.tasks.GenerateApkDataTask;
+import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.BinaryFileProviderTask;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.GenerateResValues;
+import com.android.build.gradle.tasks.JackTask;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.MergeSourceSetFolders;
+import com.android.build.gradle.tasks.NdkCompile;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.core.VariantType;
+import com.android.builder.model.SourceProvider;
+import com.android.ide.common.blame.MergingLog;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.res2.ResourceSet;
+import com.android.utils.StringHelper;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.DirectoryTree;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.gradle.api.tasks.util.PatternSet;
+
+import android.databinding.tool.LayoutXmlProcessor;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Base data about a variant.
+ */
+public abstract class BaseVariantData<T extends BaseVariantOutputData> {
+
+ public enum SplitHandlingPolicy {
+ /**
+ * Any release before L will create fake splits where each split will be the entire
+ * application with the split specific resources.
+ */
+ PRE_21_POLICY,
+
+ /**
+ * Android L and after, the splits are pure splits where splits only contain resources
+ * specific to the split characteristics.
+ */
+ RELEASE_21_AND_AFTER_POLICY
+ }
+
+
+ @NonNull
+ protected final AndroidConfig androidConfig;
+ @NonNull
+ protected final TaskManager taskManager;
+ @NonNull
+ private final GradleVariantConfiguration variantConfiguration;
+
+ private VariantDependencies variantDependency;
+
+ // Needed for ModelBuilder. Should be removed once VariantScope can replace BaseVariantData.
+ @NonNull
+ private final VariantScope scope;
+
+ public Task preBuildTask;
+ public PrepareDependenciesTask prepareDependenciesTask;
+ public ProcessAndroidResources generateRClassTask;
+
+ public Task sourceGenTask;
+ public Task resourceGenTask;
+ public Task assetGenTask;
+ public CheckManifest checkManifestTask;
+
+ public RenderscriptCompile renderscriptCompileTask;
+ public AidlCompile aidlCompileTask;
+ public MergeResources mergeResourcesTask;
+ public MergeSourceSetFolders mergeAssetsTask;
+ public GenerateBuildConfig generateBuildConfigTask;
+ public GenerateResValues generateResValuesTask;
+ public Copy copyApkTask;
+ public GenerateApkDataTask generateApkDataTask;
+
+ public Sync processJavaResourcesTask;
+ public NdkCompile ndkCompileTask;
+
+ /** Can be JavaCompile or JackTask depending on user's settings. */
+ public AbstractCompile javaCompilerTask;
+ public JavaCompile javacTask;
+ public JackTask jackTask;
+ public Jar classesJarTask;
+ // empty anchor compile task to set all compilations tasks as dependents.
+ public Task compileTask;
+
+ public FileSupplier mappingFileProviderTask;
+ public BinaryFileProviderTask binaryFileProviderTask;
+
+ // TODO : why is Jack not registered as the obfuscationTask ???
+ public Task obfuscationTask;
+
+ // Task to assemble the variant and all its output.
+ public Task assembleVariantTask;
+
+ private List<ConfigurableFileTree> javaSources;
+
+ private List<File> extraGeneratedSourceFolders;
+ private List<File> extraGeneratedResFolders;
+
+ private final List<T> outputs = Lists.newArrayListWithExpectedSize(4);
+
+ private Set<String> densityFilters;
+ private Set<String> languageFilters;
+ private Set<String> abiFilters;
+
+ @Nullable
+ private LayoutXmlProcessor layoutXmlProcessor;
+
+ /**
+ * If true, variant outputs will be considered signed. Only set if you manually set the outputs
+ * to point to signed files built by other tasks.
+ */
+ public boolean outputsAreSigned = false;
+
+ private SplitHandlingPolicy mSplitHandlingPolicy;
+
+
+ public BaseVariantData(
+ @NonNull AndroidConfig androidConfig,
+ @NonNull TaskManager taskManager,
+ @NonNull GradleVariantConfiguration variantConfiguration,
+ @NonNull ErrorReporter errorReporter) {
+ this.androidConfig = androidConfig;
+ this.variantConfiguration = variantConfiguration;
+ this.taskManager = taskManager;
+
+ // eventually, this will require a more open ended comparison.
+ mSplitHandlingPolicy =
+ androidConfig.getGeneratePureSplits()
+ && variantConfiguration.getMinSdkVersion().getApiLevel() >= 21
+ ? SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY
+ : SplitHandlingPolicy.PRE_21_POLICY;
+
+ // warn the user in case we are forced to ignore the generatePureSplits flag.
+ if (androidConfig.getGeneratePureSplits()
+ && mSplitHandlingPolicy != SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY) {
+ Logging.getLogger(BaseVariantData.class).warn(
+ String.format("Variant %s, MinSdkVersion %s is too low (<21) "
+ + "to support pure splits, reverting to full APKs",
+ variantConfiguration.getFullName(),
+ variantConfiguration.getMinSdkVersion().getApiLevel()));
+ }
+ scope = new VariantScopeImpl(
+ taskManager.getGlobalScope(),
+ new TransformManager(taskManager.getAndroidTasks(), errorReporter),
+ this);
+ taskManager.configureScopeForNdk(scope);
+ }
+
+ @NonNull
+ public LayoutXmlProcessor getLayoutXmlProcessor() {
+ if (layoutXmlProcessor == null) {
+ File resourceBlameLogDir = getScope().getResourceBlameLogDir();
+ final MergingLog mergingLog = new MergingLog(resourceBlameLogDir);
+ layoutXmlProcessor = new LayoutXmlProcessor(
+ getVariantConfiguration().getOriginalApplicationId(),
+ taskManager.getDataBindingBuilder()
+ .createJavaFileWriter(scope.getClassOutputForDataBinding()),
+ getVariantConfiguration().getMinSdkVersion().getApiLevel(),
+ getType() == VariantType.LIBRARY,
+ new LayoutXmlProcessor.OriginalFileLookup() {
+
+ @Override
+ public File getOriginalFileFor(File file) {
+ SourceFile input = new SourceFile(file);
+ SourceFile original = mergingLog.find(input);
+ // merged log api returns the file back if original cannot be found.
+ // it is not what we want so we alter the response.
+ return original == input ? null : original.getSourceFile();
+ }
+ }
+ );
+ }
+ return layoutXmlProcessor;
+ }
+
+ public SplitHandlingPolicy getSplitHandlingPolicy() {
+ return mSplitHandlingPolicy;
+ }
+
+ @NonNull
+ protected abstract T doCreateOutput(
+ OutputFile.OutputType outputType,
+ Collection<FilterData> filters);
+
+ @NonNull
+ public T createOutput(OutputFile.OutputType outputType,
+ Collection<FilterData> filters) {
+ T data = doCreateOutput(outputType, filters);
+
+ // if it's the first time we add an output, mark previous output as part of a multi-output
+ // setup.
+ if (outputs.size() == 1) {
+ outputs.get(0).setMultiOutput(true);
+ data.setMultiOutput(true);
+ } else if (outputs.size() > 1) {
+ data.setMultiOutput(true);
+ }
+
+ outputs.add(data);
+ return data;
+ }
+
+ @NonNull
+ public List<T> getOutputs() {
+ return outputs;
+ }
+
+ @NonNull
+ public GradleVariantConfiguration getVariantConfiguration() {
+ return variantConfiguration;
+ }
+
+ public void setVariantDependency(@NonNull VariantDependencies variantDependency) {
+ this.variantDependency = variantDependency;
+ }
+
+ @NonNull
+ public VariantDependencies getVariantDependency() {
+ return variantDependency;
+ }
+
+ @NonNull
+ public abstract String getDescription();
+
+ @NonNull
+ public String getApplicationId() {
+ return variantConfiguration.getApplicationId();
+ }
+
+ @NonNull
+ protected String getCapitalizedBuildTypeName() {
+ return StringHelper.capitalize(variantConfiguration.getBuildType().getName());
+ }
+
+ @NonNull
+ protected String getCapitalizedFlavorName() {
+ return StringHelper.capitalize(variantConfiguration.getFlavorName());
+ }
+
+ public VariantType getType() {
+ return variantConfiguration.getType();
+ }
+
+ @NonNull
+ public String getName() {
+ return variantConfiguration.getFullName();
+ }
+
+ @Nullable
+ public List<File> getExtraGeneratedSourceFolders() {
+ return extraGeneratedSourceFolders;
+ }
+
+ @Nullable
+ public List<File> getExtraGeneratedResFolders() {
+ return extraGeneratedResFolders;
+ }
+
+ public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
+ if (extraGeneratedSourceFolders == null) {
+ extraGeneratedSourceFolders = Lists.newArrayList();
+ }
+
+ Collections.addAll(extraGeneratedSourceFolders, generatedSourceFolders);
+ }
+
+ public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
+ if (extraGeneratedSourceFolders == null) {
+ extraGeneratedSourceFolders = Lists.newArrayList();
+ }
+
+ extraGeneratedSourceFolders.addAll(generatedSourceFolders);
+ }
+
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... generatedSourceFolders) {
+ sourceGenTask.dependsOn(task);
+
+ for (File f : generatedSourceFolders) {
+ javacTask.source(f);
+
+ // Jack task is not always created.
+ if (jackTask != null) {
+ jackTask.source(f);
+ }
+ }
+
+ addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedSourceFolders) {
+ sourceGenTask.dependsOn(task);
+
+ for (File f : generatedSourceFolders) {
+ javacTask.source(f);
+
+ // Jack task is not always created.
+ if (jackTask != null) {
+ jackTask.source(f);
+ }
+ }
+
+ addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+
+ public void registerResGeneratingTask(@NonNull Task task, @NonNull File... generatedResFolders) {
+ // no need add the folders anywhere, the convention mapping closure for the MergeResources
+ // action will pick them up from here
+ resourceGenTask.dependsOn(task);
+
+ if (extraGeneratedResFolders == null) {
+ extraGeneratedResFolders = Lists.newArrayList();
+ }
+
+ Collections.addAll(extraGeneratedResFolders, generatedResFolders);
+ }
+
+ public void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedResFolders) {
+ // no need add the folders anywhere, the convention mapping closure for the MergeResources
+ // action will pick them up from here
+ resourceGenTask.dependsOn(task);
+
+ if (extraGeneratedResFolders == null) {
+ extraGeneratedResFolders = Lists.newArrayList();
+ }
+
+ extraGeneratedResFolders.addAll(generatedResFolders);
+ }
+
+ /**
+ * Calculates the filters for this variant. The filters can either be manually specified by
+ * the user within the build.gradle or can be automatically discovered using the variant
+ * specific folders.
+ *
+ * This method must be called before {@link #getFilters(OutputFile.FilterType)}.
+ *
+ * @param splits the splits configuration from the build.gradle.
+ */
+ public void calculateFilters(Splits splits) {
+
+ List<ResourceSet> resourceSets = variantConfiguration
+ .getResourceSets(getGeneratedResFolders(), false, false /*validate*/);
+ densityFilters = getFilters(resourceSets, DiscoverableFilterType.DENSITY, splits);
+ languageFilters = getFilters(resourceSets, DiscoverableFilterType.LANGUAGE, splits);
+ abiFilters = getFilters(resourceSets, DiscoverableFilterType.ABI, splits);
+ }
+
+ /**
+ * Returns the filters values (as manually specified or automatically discovered) for a
+ * particular {@link com.android.build.OutputFile.FilterType}
+ * @param filterType the type of filter in question
+ * @return a possibly empty set of filter values.
+ * @throws IllegalStateException if {@link #calculateFilters(Splits)} has not been called prior
+ * to invoking this method.
+ */
+ @NonNull
+ public Set<String> getFilters(OutputFile.FilterType filterType) {
+ if (densityFilters == null || languageFilters == null || abiFilters == null) {
+ throw new IllegalStateException("calculateFilters method not called");
+ }
+ switch(filterType) {
+ case DENSITY:
+ return densityFilters;
+ case LANGUAGE:
+ return languageFilters;
+ case ABI:
+ return abiFilters;
+ default:
+ throw new RuntimeException("Unhandled filter type");
+ }
+ }
+
+ /**
+ * Returns the list of generated res folders for this variant.
+ */
+ private List<File> getGeneratedResFolders() {
+ List<File> generatedResFolders = Lists.newArrayList(
+ scope.getRenderscriptResOutputDir(),
+ scope.getGeneratedResOutputDir());
+ if (extraGeneratedResFolders != null) {
+ generatedResFolders.addAll(extraGeneratedResFolders);
+ }
+ if (generateApkDataTask != null &&
+ getVariantConfiguration().getBuildType().isEmbedMicroApp()) {
+ generatedResFolders.add(generateApkDataTask.getResOutputDir());
+ }
+ return generatedResFolders;
+ }
+
+ @NonNull
+ public List<String> discoverListOfResourceConfigs() {
+ List<String> resFoldersOnDisk = new ArrayList<String>();
+ List<ResourceSet> resourceSets = variantConfiguration.getResourceSets(
+ getGeneratedResFolders(), false /* no libraries resources */, false /*validate*/);
+ resFoldersOnDisk.addAll(getAllFilters(
+ resourceSets,
+ DiscoverableFilterType.LANGUAGE.folderPrefix,
+ DiscoverableFilterType.DENSITY.folderPrefix));
+ return resFoldersOnDisk;
+ }
+
+ @NonNull
+ public List<String> discoverListOfResourceConfigsNotDensities() {
+ List<String> resFoldersOnDisk = new ArrayList<String>();
+ List<ResourceSet> resourceSets = variantConfiguration.getResourceSets(
+ getGeneratedResFolders(), false /* no libraries resources */, false /*validate*/);
+ resFoldersOnDisk.addAll(getAllFilters(
+ resourceSets,
+ DiscoverableFilterType.LANGUAGE.folderPrefix));
+ return resFoldersOnDisk;
+ }
+
+ /**
+ * Defines the discoverability attributes of filters.
+ */
+ private enum DiscoverableFilterType {
+
+ DENSITY("drawable-") {
+ @NonNull
+ @Override
+ Collection<String> getConfiguredFilters(@NonNull Splits splits) {
+ return splits.getDensityFilters();
+ }
+
+ @Override
+ boolean isAuto(@NonNull Splits splits) {
+ return splits.getDensity().isAuto();
+ }
+
+ }, LANGUAGE("values-") {
+ @NonNull
+ @Override
+ Collection<String> getConfiguredFilters(@NonNull Splits splits) {
+ return splits.getLanguageFilters();
+ }
+
+ @Override
+ boolean isAuto(@NonNull Splits splits) {
+ return splits.getLanguage().isAuto();
+ }
+ }, ABI("") {
+ @NonNull
+ @Override
+ Collection<String> getConfiguredFilters(@NonNull Splits splits) {
+ return splits.getAbiFilters();
+ }
+
+ @Override
+ boolean isAuto(@NonNull Splits splits) {
+ // so far, we never auto-discover abi filters.
+ return false;
+ }
+ };
+
+ /**
+ * Sets the folder prefix that filter specific resources must start with.
+ */
+ private String folderPrefix;
+
+ DiscoverableFilterType(String folderPrefix) {
+ this.folderPrefix = folderPrefix;
+ }
+
+ /**
+ * Returns the applicable filters configured in the build.gradle for this filter type.
+ * @param splits the build.gradle splits configuration
+ * @return a list of filters.
+ */
+ @NonNull
+ abstract Collection<String> getConfiguredFilters(@NonNull Splits splits);
+
+ /**
+ * Returns true if the user wants the build system to auto discover the splits for this
+ * split type.
+ * @param splits the build.gradle splits configuration.
+ * @return true to use auto-discovery, false to use the build.gradle configuration.
+ */
+ abstract boolean isAuto(@NonNull Splits splits);
+ }
+
+ /**
+ * Gets the list of filter values for a filter type either from the user specified build.gradle
+ * settings or through a discovery mechanism using folders names.
+ * @param resourceSets the list of source folders to discover from.
+ * @param filterType the filter type
+ * @param splits the variant's configuration for splits.
+ * @return a possibly empty list of filter value for this filter type.
+ */
+ @NonNull
+ private static Set<String> getFilters(
+ @NonNull List<ResourceSet> resourceSets,
+ @NonNull DiscoverableFilterType filterType,
+ @NonNull Splits splits) {
+
+ Set<String> filtersList = new HashSet<String>();
+ if (filterType.isAuto(splits)) {
+ filtersList.addAll(getAllFilters(resourceSets, filterType.folderPrefix));
+ } else {
+ filtersList.addAll(filterType.getConfiguredFilters(splits));
+ }
+ return filtersList;
+ }
+
+ /**
+ * Discover all sub-folders of all the {@link ResourceSet#getSourceFiles()} which names are
+ * starting with one of the provided prefixes.
+ * @param resourceSets the list of sources {@link ResourceSet}
+ * @param prefixes the list of prefixes to look for folders.
+ * @return a possibly empty list of folders.
+ */
+ @NonNull
+ private static List<String> getAllFilters(List<ResourceSet> resourceSets, String... prefixes) {
+ List<String> providedResFolders = new ArrayList<String>();
+ for (ResourceSet resourceSet : resourceSets) {
+ for (File resFolder : resourceSet.getSourceFiles()) {
+ File[] subResFolders = resFolder.listFiles();
+ if (subResFolders != null) {
+ for (File subResFolder : subResFolders) {
+ for (String prefix : prefixes) {
+ if (subResFolder.getName().startsWith(prefix)) {
+ providedResFolders
+ .add(subResFolder.getName().substring(prefix.length()));
+ }
+ }
+ }
+ }
+ }
+ }
+ return providedResFolders;
+ }
+
+
+ /**
+ * Computes the Java sources to use for compilation.
+ *
+ * Every entry is a ConfigurableFileTree instance to enable incremental java compilation.
+ */
+ @NonNull
+ public List<ConfigurableFileTree> getJavaSources() {
+ if (javaSources == null) {
+ Project project = scope.getGlobalScope().getProject();
+ // Build the list of source folders.
+ ImmutableList.Builder<ConfigurableFileTree> sourceSets = ImmutableList.builder();
+
+ // First the actual source folders.
+ List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
+ for (SourceProvider provider : providers) {
+ sourceSets.addAll(((AndroidSourceSet) provider).getJava().getSourceDirectoryTrees());
+ }
+
+ // then all the generated src folders.
+ if (getScope().getGenerateRClassTask() != null) {
+ sourceSets.add(project.fileTree(getScope().getRClassSourceOutputDir()));
+ }
+
+ // for the other, there's no duplicate so no issue.
+ if (getScope().getGenerateBuildConfigTask() != null) {
+ sourceSets.add(project.fileTree(scope.getBuildConfigSourceOutputDir()));
+ }
+
+ if (getScope().getAidlCompileTask() != null) {
+ sourceSets.add(project.fileTree(scope.getAidlSourceOutputDir()));
+ }
+
+ if (scope.getGlobalScope().getExtension().getDataBinding().isEnabled()) {
+ sourceSets.add(project.fileTree(scope.getClassOutputForDataBinding()));
+ }
+
+ if (!variantConfiguration.getRenderscriptNdkModeEnabled()
+ && getScope().getRenderscriptCompileTask() != null) {
+ sourceSets.add(project.fileTree(scope.getRenderscriptSourceOutputDir()));
+ }
+
+ javaSources = sourceSets.build();
+ }
+
+ return javaSources;
+ }
+
+ /**
+ * Returns the Java folders needed for code coverage report.
+ *
+ * This includes all the source folders except for the ones containing R and buildConfig.
+ */
+ @NonNull
+ public List<File> getJavaSourceFoldersForCoverage() {
+ // Build the list of source folders.
+ List<File> sourceFolders = Lists.newArrayList();
+
+ // First the actual source folders.
+ List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
+ for (SourceProvider provider : providers) {
+ for (File sourceFolder : provider.getJavaDirectories()) {
+ if (sourceFolder.isDirectory()) {
+ sourceFolders.add(sourceFolder);
+ }
+ }
+ }
+
+ File sourceFolder;
+ // then all the generated src folders, except the ones for the R/Manifest and
+ // BuildConfig classes.
+ sourceFolder = aidlCompileTask.getSourceOutputDir();
+ if (sourceFolder.isDirectory()) {
+ sourceFolders.add(sourceFolder);
+ }
+
+ if (!variantConfiguration.getRenderscriptNdkModeEnabled()) {
+ sourceFolder = renderscriptCompileTask.getSourceOutputDir();
+ if (sourceFolder.isDirectory()) {
+ sourceFolders.add(sourceFolder);
+ }
+ }
+
+ return sourceFolders;
+ }
+
+ /**
+ * Returns a list of configuration name for wear connection, from highest to lowest priority.
+ * @return list of config.
+ */
+ @NonNull
+ public List<String> getWearConfigNames() {
+ List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
+
+ // this is the wrong order, so let's reverse it as we gather the names.
+ final int count = providers.size();
+ List<String> names = Lists.newArrayListWithCapacity(count);
+ for (int i = count - 1 ; i >= 0; i--) {
+ DefaultAndroidSourceSet sourceSet = (DefaultAndroidSourceSet) providers.get(i);
+
+ names.add(sourceSet.getWearAppConfigurationName());
+ }
+
+ return names;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .addValue(variantConfiguration.getFullName())
+ .toString();
+ }
+
+ @Nullable
+ public FileSupplier getMappingFileProvider() {
+ return mappingFileProviderTask;
+ }
+
+ @Nullable
+ public File getMappingFile() {
+ return mappingFileProviderTask != null ? mappingFileProviderTask.get() : null;
+ }
+
+ @NonNull
+ public VariantScope getScope() {
+ return scope;
+ }
+
+ @NonNull
+ public File getJavaResourcesForUnitTesting() {
+ if (processJavaResourcesTask != null) {
+ return processJavaResourcesTask.getOutputs().getFiles().getSingleFile();
+ } else {
+ return getScope().getSourceFoldersJavaResDestinationDir();
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantOutputData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantOutputData.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantOutputData.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantOutputData.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/DefaultSourceProviderContainer.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/DefaultSourceProviderContainer.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/DefaultSourceProviderContainer.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/DefaultSourceProviderContainer.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibVariantOutputData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibVariantOutputData.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibVariantOutputData.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibVariantOutputData.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java
new file mode 100644
index 0000000..31a280a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.tasks.ExtractAnnotations;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.core.VariantType;
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Task;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Data about a variant that produce a Library bundle (.aar)
+ */
+public class LibraryVariantData extends BaseVariantData<LibVariantOutputData> implements TestedVariantData {
+
+ private final Map<VariantType, TestVariantData> testVariants;
+
+ @Nullable
+ public ExtractAnnotations generateAnnotationsTask = null;
+
+ public LibraryVariantData(
+ @NonNull AndroidConfig androidConfig,
+ @NonNull TaskManager taskManager,
+ @NonNull GradleVariantConfiguration config,
+ @NonNull ErrorReporter errorReporter) {
+ super(androidConfig, taskManager, config, errorReporter);
+ testVariants = Maps.newEnumMap(VariantType.class);
+
+ // create default output
+ createOutput(OutputFile.OutputType.MAIN,
+ Collections.<FilterData>emptyList());
+ }
+
+ @NonNull
+ @Override
+ protected LibVariantOutputData doCreateOutput(
+ OutputFile.OutputType splitOutput,
+ Collection<FilterData> filters) {
+ return new LibVariantOutputData(splitOutput, filters, this);
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ if (getVariantConfiguration().hasFlavors()) {
+ return String.format("%s build for flavor %s",
+ getCapitalizedBuildTypeName(),
+ getCapitalizedFlavorName());
+ } else {
+ return String.format("%s build", getCapitalizedBuildTypeName());
+ }
+ }
+
+ @Nullable
+ @Override
+ public TestVariantData getTestVariantData(@NonNull VariantType type) {
+ return testVariants.get(type);
+ }
+
+ @Override
+ public void setTestVariantData(
+ @NonNull TestVariantData testVariantData,
+ VariantType type) {
+ testVariants.put(type, testVariantData);
+ }
+
+ // Overridden to add source folders to a generateAnnotationsTask, if it exists.
+ @Override
+ public void registerJavaGeneratingTask(
+ @NonNull Task task, @NonNull File... generatedSourceFolders) {
+ super.registerJavaGeneratingTask(task, generatedSourceFolders);
+ if (generateAnnotationsTask != null) {
+ for (File f : generatedSourceFolders) {
+ generateAnnotationsTask.source(f);
+ }
+ }
+ }
+
+ // Overridden to add source folders to a generateAnnotationsTask, if it exists.
+ @Override
+ public void registerJavaGeneratingTask(
+ @NonNull Task task, @NonNull Collection<File> generatedSourceFolders) {
+ super.registerJavaGeneratingTask(task, generatedSourceFolders);
+ if (generateAnnotationsTask != null) {
+ for (File f : generatedSourceFolders) {
+ generateAnnotationsTask.source(f);
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.java
new file mode 100644
index 0000000..0d547a7
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantFactory.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.variant;
+
+import static com.android.builder.core.BuilderConstants.DEBUG;
+import static com.android.builder.core.BuilderConstants.RELEASE;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.api.BaseVariantOutput;
+import com.android.build.gradle.api.LibraryVariant;
+import com.android.build.gradle.internal.BuildTypeData;
+import com.android.build.gradle.internal.ProductFlavorData;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.VariantModel;
+import com.android.build.gradle.internal.api.LibraryVariantImpl;
+import com.android.build.gradle.internal.api.LibraryVariantOutputImpl;
+import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.VariantType;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.List;
+
+public class LibraryVariantFactory implements VariantFactory {
+
+ @NonNull
+ private Instantiator instantiator;
+ @NonNull
+ private final AndroidConfig extension;
+ @NonNull
+ private final AndroidBuilder androidBuilder;
+
+ public LibraryVariantFactory(
+ @NonNull Instantiator instantiator,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull AndroidConfig extension) {
+ this.instantiator = instantiator;
+ this.androidBuilder = androidBuilder;
+ this.extension = extension;
+ }
+
+ @Override
+ @NonNull
+ public BaseVariantData createVariantData(
+ @NonNull GradleVariantConfiguration variantConfiguration,
+ @NonNull TaskManager taskManager) {
+ return new LibraryVariantData(extension, taskManager, variantConfiguration,
+ androidBuilder.getErrorReporter());
+ }
+
+ @Override
+ @NonNull
+ public LibraryVariant createVariantApi(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
+ @NonNull ReadOnlyObjectProvider readOnlyObjectProvider) {
+ LibraryVariantImpl variant = instantiator.newInstance(
+ LibraryVariantImpl.class, variantData, androidBuilder, readOnlyObjectProvider);
+
+ // now create the output objects
+ List<? extends BaseVariantOutputData> outputList = variantData.getOutputs();
+ List<BaseVariantOutput> apiOutputList = Lists.newArrayListWithCapacity(outputList.size());
+
+ for (BaseVariantOutputData variantOutputData : outputList) {
+ LibVariantOutputData libOutput = (LibVariantOutputData) variantOutputData;
+
+ LibraryVariantOutputImpl output = instantiator.newInstance(
+ LibraryVariantOutputImpl.class, libOutput);
+
+ apiOutputList.add(output);
+ }
+
+ variant.addOutputs(apiOutputList);
+
+ return variant;
+ }
+
+ @NonNull
+ @Override
+ public VariantType getVariantConfigurationType() {
+ return VariantType.LIBRARY;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return true;
+ }
+
+ @Override
+ public boolean hasTestScope() {
+ return true;
+ }
+
+ /***
+ * Prevent customization of applicationId or applicationIdSuffix.
+ */
+ @Override
+ public void validateModel(@NonNull VariantModel model) {
+ if (model.getDefaultConfig().getProductFlavor().getApplicationId() != null) {
+ throw new GradleException("Library projects cannot set applicationId. " +
+ "applicationId is set to '" +
+ model.getDefaultConfig().getProductFlavor().getApplicationId() +
+ "' in default config.");
+ }
+
+ if (model.getDefaultConfig().getProductFlavor().getApplicationIdSuffix() != null) {
+ throw new GradleException("Library projects cannot set applicationIdSuffix. " +
+ "applicationIdSuffix is set to '" +
+ model.getDefaultConfig().getProductFlavor().getApplicationIdSuffix() +
+ "' in default config.");
+ }
+
+ for (BuildTypeData buildType : model.getBuildTypes().values()) {
+ if (buildType.getBuildType().getApplicationIdSuffix() != null) {
+ throw new GradleException("Library projects cannot set applicationIdSuffix. " +
+ "applicationIdSuffix is set to '" +
+ buildType.getBuildType().getApplicationIdSuffix() +
+ "' in build type '" + buildType.getBuildType().getName() + "'.");
+ }
+ }
+ for (ProductFlavorData productFlavor : model.getProductFlavors().values()) {
+ if (productFlavor.getProductFlavor().getApplicationId() != null) {
+ throw new GradleException("Library projects cannot set applicationId. " +
+ "applicationId is set to '" +
+ productFlavor.getProductFlavor().getApplicationId() + "' in flavor '" +
+ productFlavor.getProductFlavor().getName() + "'.");
+ }
+
+ if (productFlavor.getProductFlavor().getApplicationIdSuffix() != null) {
+ throw new GradleException("Library projects cannot set applicationIdSuffix. " +
+ "applicationIdSuffix is set to '" +
+ productFlavor.getProductFlavor().getApplicationIdSuffix() +
+ "' in flavor '" + productFlavor.getProductFlavor().getName() + "'.");
+ }
+ }
+
+ }
+
+ @Override
+ public void preVariantWork(Project project) {
+ // nothing to be done here.
+ }
+
+ @Override
+ public void createDefaultComponents(
+ @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
+ // must create signing config first so that build type 'debug' can be initialized
+ // with the debug signing config.
+ signingConfigs.create(DEBUG);
+ buildTypes.create(DEBUG);
+ buildTypes.create(RELEASE);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java
new file mode 100644
index 0000000..80f4edb
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
+import com.android.builder.core.ErrorReporter;
+import com.google.common.collect.Lists;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Data about a variant that produce a test APK
+ */
+public class TestVariantData extends ApkVariantData {
+
+ public DeviceProviderInstrumentTestTask connectedTestTask;
+ public final List<DeviceProviderInstrumentTestTask> providerTestTaskList = Lists.newArrayList();
+ @NonNull
+ private final TestedVariantData testedVariantData;
+
+ public TestVariantData(
+ @NonNull AndroidConfig androidConfig,
+ @NonNull TaskManager taskManager,
+ @NonNull GradleVariantConfiguration config,
+ @NonNull TestedVariantData testedVariantData,
+ @NonNull ErrorReporter errorReporter) {
+ super(androidConfig, taskManager, config, errorReporter);
+ this.testedVariantData = testedVariantData;
+
+ // create default output
+ createOutput(OutputFile.OutputType.MAIN,
+ Collections.<FilterData>emptyList());
+ }
+
+ @NonNull
+ public TestedVariantData getTestedVariantData() {
+ return testedVariantData;
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ String prefix;
+ switch (getType()) {
+ case ANDROID_TEST:
+ prefix = "android (on device) tests";
+ break;
+ case UNIT_TEST:
+ prefix = "unit tests";
+ break;
+ default:
+ throw new IllegalStateException("Unknown test variant type.");
+ }
+ if (getVariantConfiguration().hasFlavors()) {
+ return String.format("%s for the %s%s build", prefix,
+ getCapitalizedFlavorName(), getCapitalizedBuildTypeName());
+ } else {
+ return String.format("%s for the %s build", prefix,
+ getCapitalizedBuildTypeName());
+ }
+ }
+
+ @Override
+ public boolean getZipAlignEnabled() {
+ return false;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantFactory.java
new file mode 100644
index 0000000..87a5eed
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantFactory.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.TestAndroidConfig;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.BuilderConstants;
+import com.google.common.collect.ImmutableMap;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * Customization of ApplcationVariantFactory for test-only projects.
+ */
+public class TestVariantFactory extends ApplicationVariantFactory {
+
+ public TestVariantFactory(
+ @NonNull Instantiator instantiator,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull AndroidConfig extension) {
+ super(instantiator, androidBuilder, extension);
+ }
+
+ @Override
+ public boolean hasTestScope() {
+ return false;
+ }
+
+ @Override
+ public void preVariantWork(final Project project) {
+ final TestAndroidConfig testExtension = (TestAndroidConfig) extension;
+
+ String path = testExtension.getTargetProjectPath();
+ if (path == null) {
+ throw new GradleException(
+ "targetProjectPath cannot be null in test project " + project.getName());
+ }
+
+ if (testExtension.getTargetVariant() == null) {
+ throw new GradleException(
+ "targetVariant cannot be null in test project " + project.getName());
+ }
+
+ // add the code of the tested app to the provided scope.
+ DependencyHandler handler = project.getDependencies();
+ handler.add("provided", handler.project(ImmutableMap.of(
+ "path", path,
+ "configuration", testExtension.getTargetVariant() + "-classes"
+ )));
+ }
+
+ @Override
+ public void createDefaultComponents(
+ @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
+ // don't call super as we don't want the default app version.
+ // must create signing config first so that build type 'debug' can be initialized
+ // with the debug signing config.
+ signingConfigs.create(BuilderConstants.DEBUG);
+ buildTypes.create(BuilderConstants.DEBUG);
+ }
+
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestedVariantData.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestedVariantData.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestedVariantData.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/TestedVariantData.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantFactory.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantFactory.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantFactory.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantFactory.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantHelper.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantHelper.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantHelper.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/internal/variant/VariantHelper.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.java
new file mode 100644
index 0000000..1bc3995
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.IncrementalTask;
+import com.android.builder.compiling.DependencyFileProcessor;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.core.VariantType;
+import com.android.builder.internal.incremental.DependencyData;
+import com.android.builder.internal.incremental.DependencyDataStore;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.ide.common.res2.FileStatus;
+import com.android.utils.FileUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * Task to compile aidl files. Supports incremental update.
+ */
+ at ParallelizableTask
+public class AidlCompile extends IncrementalTask {
+
+ private static final String DEPENDENCY_STORE = "dependency.store";
+ private static final PatternSet PATTERN_SET = new PatternSet().include("**/*.aidl");
+
+ // ----- PUBLIC TASK API -----
+ private File sourceOutputDir;
+ @Nullable
+ private File packagedDir;
+ @Nullable
+ private Collection<String> packageWhitelist;
+
+ // ----- PRIVATE TASK API -----
+ @Input
+ String getBuildToolsVersion() {
+ return getBuildTools().getRevision().toString();
+ }
+ private List<File> sourceDirs;
+ private List<File> importDirs;
+
+ @InputFiles
+ FileTree getSourceFiles() {
+ FileTree src = null;
+ List<File> sources = getSourceDirs();
+ if (!sources.isEmpty()) {
+ src = getProject().files(sources).getAsFileTree().matching(PATTERN_SET);
+ }
+ return src == null ? getProject().files().getAsFileTree() : src;
+ }
+
+ private static class DepFileProcessor implements DependencyFileProcessor {
+
+ @GuardedBy("this")
+ List<DependencyData> dependencyDataList = Lists.newArrayList();
+
+ List<DependencyData> getDependencyDataList() {
+ return dependencyDataList;
+ }
+
+ @Override
+ public DependencyData processFile(@NonNull File dependencyFile) throws IOException {
+ DependencyData data = DependencyData.parseDependencyFile(dependencyFile);
+ if (data != null) {
+ synchronized (this) {
+ dependencyDataList.add(data);
+ }
+ }
+
+ return data;
+ }
+ }
+
+ @Override
+ protected boolean isIncremental() {
+ // TODO fix once dep file parsing is resolved.
+ return false;
+ }
+
+ /**
+ * Action methods to compile all the files.
+ *
+ * The method receives a {@link DependencyFileProcessor} to be used by the
+ * {@link com.android.builder.internal.compiler.SourceSearcher.SourceFileProcessor} during
+ * the compilation.
+ *
+ * @param dependencyFileProcessor a DependencyFileProcessor
+ */
+ private void compileAllFiles(DependencyFileProcessor dependencyFileProcessor)
+ throws InterruptedException, ProcessException, LoggedErrorException, IOException {
+ getBuilder().compileAllAidlFiles(
+ getSourceDirs(),
+ getSourceOutputDir(),
+ getPackagedDir(),
+ getPackageWhitelist(),
+ getImportDirs(),
+ dependencyFileProcessor,
+ new LoggedProcessOutputHandler(getILogger()));
+ }
+
+ /**
+ * Returns the import folders.
+ */
+ @NonNull
+ private List<File> getImportFolders() {
+ List<File> fullImportDir = Lists.newArrayList();
+ fullImportDir.addAll(getImportDirs());
+ fullImportDir.addAll(getSourceDirs());
+
+ return fullImportDir;
+ }
+
+ /**
+ * Compiles a single file.
+ * @param sourceFolder the file to compile.
+ * @param file the file to compile.
+ * @param importFolders the import folders.
+ * @param dependencyFileProcessor a DependencyFileProcessor
+ */
+ private void compileSingleFile(
+ @NonNull File sourceFolder,
+ @NonNull File file,
+ @Nullable List<File> importFolders,
+ @NonNull DependencyFileProcessor dependencyFileProcessor,
+ @NonNull ProcessOutputHandler processOutputHandler)
+ throws InterruptedException, ProcessException, LoggedErrorException, IOException {
+ getBuilder().compileAidlFile(
+ sourceFolder,
+ file,
+ getSourceOutputDir(),
+ getPackagedDir(),
+ getPackageWhitelist(),
+ Preconditions.checkNotNull(importFolders),
+ dependencyFileProcessor,
+ processOutputHandler);
+ }
+
+ @Override
+ protected void doFullTaskAction() throws IOException {
+ // this is full run, clean the previous output
+ File destinationDir = getSourceOutputDir();
+ File parcelableDir = getPackagedDir();
+ FileUtils.emptyFolder(destinationDir);
+ if (parcelableDir != null) {
+ FileUtils.emptyFolder(parcelableDir);
+ }
+
+
+ DepFileProcessor processor = new DepFileProcessor();
+
+ try {
+ compileAllFiles(processor);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ List<DependencyData> dataList = processor.getDependencyDataList();
+
+ DependencyDataStore store = new DependencyDataStore();
+ store.addData(dataList);
+
+ try {
+ store.saveTo(new File(getIncrementalFolder(), DEPENDENCY_STORE));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
+ File incrementalData = new File(getIncrementalFolder(), DEPENDENCY_STORE);
+ DependencyDataStore store = new DependencyDataStore();
+ Multimap<String, DependencyData> inputMap;
+ try {
+ inputMap = store.loadFrom(incrementalData);
+ } catch (Exception ignored) {
+ FileUtils.delete(incrementalData);
+ getProject().getLogger().info(
+ "Failed to read dependency store: full task run!");
+ doFullTaskAction();
+ return;
+ }
+
+ final List<File> importFolders = getImportFolders();
+ final DepFileProcessor processor = new DepFileProcessor();
+ final ProcessOutputHandler processOutputHandler =
+ new LoggedProcessOutputHandler(getILogger());
+
+ // use an executor to parallelize the compilation of multiple files.
+ WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
+
+ Map<String,DependencyData> mainFileMap = store.getMainFileMap();
+
+ for (final Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
+ FileStatus status = entry.getValue();
+
+ switch (status) {
+ case NEW:
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ File file = entry.getKey();
+ compileSingleFile(getSourceFolder(file), file, importFolders,
+ processor, processOutputHandler);
+ return null;
+ }
+ });
+ break;
+ case CHANGED:
+ Collection<DependencyData> impactedData =
+ inputMap.get(entry.getKey().getAbsolutePath());
+ if (impactedData != null) {
+ for (final DependencyData data: impactedData) {
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ File file = new File(data.getMainFile());
+ compileSingleFile(getSourceFolder(file), file,
+ importFolders, processor, processOutputHandler);
+ return null;
+ }
+ });
+ }
+ }
+ break;
+ case REMOVED:
+ final DependencyData data2 = mainFileMap.get(entry.getKey().getAbsolutePath());
+ if (data2 != null) {
+ executor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ cleanUpOutputFrom(data2);
+ return null;
+ }
+ });
+ store.remove(data2);
+ }
+ break;
+ }
+ }
+
+ try {
+ executor.waitForTasksWithQuickFail(true /*cancelRemaining*/);
+ } catch (Throwable t) {
+ FileUtils.delete(incrementalData);
+ throw new RuntimeException(t);
+ }
+
+ // get all the update data for the recompiled objects
+ store.updateAll(processor.getDependencyDataList());
+
+ try {
+ store.saveTo(incrementalData);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private File getSourceFolder(@NonNull File file) {
+ File parentDir = file;
+ while ((parentDir = parentDir.getParentFile()) != null) {
+ for (File folder : getSourceDirs()) {
+ if (parentDir.equals(folder)) {
+ return folder;
+ }
+ }
+ }
+
+ throw new IllegalArgumentException(String.format("File '%s' is not in a source dir", file));
+ }
+
+ private static void cleanUpOutputFrom(@NonNull DependencyData dependencyData)
+ throws IOException {
+ for (String output : dependencyData.getOutputFiles()) {
+ FileUtils.delete(new File(output));
+ }
+ for (String output : dependencyData.getSecondaryOutputFiles()) {
+ FileUtils.delete(new File(output));
+ }
+ }
+
+ @OutputDirectory
+ public File getSourceOutputDir() {
+ return sourceOutputDir;
+ }
+
+ public void setSourceOutputDir(File sourceOutputDir) {
+ this.sourceOutputDir = sourceOutputDir;
+ }
+
+ @OutputDirectory @Optional @Nullable
+ public File getPackagedDir() {
+ return packagedDir;
+ }
+
+ public void setPackagedDir(@Nullable File packagedDir) {
+ this.packagedDir = packagedDir;
+ }
+
+ @Input @Optional @Nullable
+ public Collection<String> getPackageWhitelist() {
+ return packageWhitelist;
+ }
+
+ public void setPackageWhitelist(@Nullable Collection<String> packageWhitelist) {
+ this.packageWhitelist = packageWhitelist;
+ }
+
+ public List<File> getSourceDirs() {
+ return sourceDirs;
+ }
+
+ public void setSourceDirs(List<File> sourceDirs) {
+ this.sourceDirs = sourceDirs;
+ }
+
+ @InputFiles
+ public List<File> getImportDirs() {
+ return importDirs;
+ }
+
+ public void setImportDirs(List<File> importDirs) {
+ this.importDirs = importDirs;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<AidlCompile> {
+
+ @NonNull
+ VariantScope scope;
+
+ public ConfigAction(@NonNull VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return scope.getTaskName("compile", "Aidl");
+ }
+
+ @Override
+ @NonNull
+ public Class<AidlCompile> getType() {
+ return AidlCompile.class;
+ }
+
+ @Override
+ public void execute(AidlCompile compileTask) {
+ final VariantConfiguration<?,?,?> variantConfiguration = scope.getVariantConfiguration();
+
+ scope.getVariantData().aidlCompileTask = compileTask;
+
+ compileTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ compileTask.setVariantName(scope.getVariantConfiguration().getFullName());
+ compileTask.setIncrementalFolder(scope.getIncrementalDir(getName()));
+
+ ConventionMappingHelper.map(compileTask, "sourceDirs", new Callable<List<File>>() {
+ @Override
+ public List<File> call() throws Exception {
+ return variantConfiguration.getAidlSourceList();
+ }
+ });
+ ConventionMappingHelper.map(compileTask, "importDirs", new Callable<List<File>>() {
+ @Override
+ public List<File> call() throws Exception {
+ return variantConfiguration.getAidlImports();
+ }
+ });
+
+ compileTask.setSourceOutputDir(scope.getAidlSourceOutputDir());
+
+ if (variantConfiguration.getType() == VariantType.LIBRARY) {
+ compileTask.setPackagedDir(scope.getPackagedAidlDir());
+ compileTask.setPackageWhitelist(
+ scope.getGlobalScope().getExtension().getAidlPackageWhiteList());
+ }
+ }
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidJarTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidJarTask.java
new file mode 100644
index 0000000..f89dff7
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/AndroidJarTask.java
@@ -0,0 +1,35 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.bundling.Jar;
+
+/**
+ * Decorated {@link Jar} task with android specific behaviors.
+ */
+ at ParallelizableTask
+public class AndroidJarTask extends Jar implements BinaryFileProviderTask {
+
+ @Override
+ @NonNull
+ public Artifact getArtifact() {
+ return new Artifact(BinaryArtifactType.JAR, getArchivePath());
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/BinaryFileProviderTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/BinaryFileProviderTask.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/BinaryFileProviderTask.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/BinaryFileProviderTask.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ColdswapArtifactsKickerTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ColdswapArtifactsKickerTask.java
new file mode 100644
index 0000000..6b07872
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ColdswapArtifactsKickerTask.java
@@ -0,0 +1,71 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.scope.VariantScope;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Kicker task to force execution of the InstantRun slicer and dexer even if the files have not
+ * changed (since we delay and batch restart artifacts creation).
+ */
+public class ColdswapArtifactsKickerTask extends KickerTask {
+
+ @Override
+ protected void doFullTaskAction() throws IOException {
+ // if the restart flag is set, we should allow for downstream tasks to execute.
+ boolean restartDexRequested =
+ variantScope.getGlobalScope().isActive(OptionalCompilationStep.RESTART_ONLY);
+ boolean changesAreCompatible =
+ variantScope.getInstantRunBuildContext().hasPassedVerification();
+
+ // so if the restart artifacts are requested or the changes are incompatible
+ // we should always run, otherwise it can wait.
+ MarkerFile.createMarkerFile(getMarkerFile(),
+ restartDexRequested || !changesAreCompatible
+ ? MarkerFile.Command.RUN
+ : MarkerFile.Command.BLOCK);
+ }
+
+ public static class ConfigAction extends KickerTask.ConfigAction<ColdswapArtifactsKickerTask> {
+
+ public ConfigAction(@NonNull String name, @NonNull VariantScope scope) {
+ super(name, scope);
+ }
+
+ public static File getMarkerFile(VariantScope scope) {
+ return new File(scope.getInstantRunSupportDir(), "coldswap.marker");
+ }
+
+ @NonNull
+ @Override
+ public Class<ColdswapArtifactsKickerTask> getType() {
+ return ColdswapArtifactsKickerTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull ColdswapArtifactsKickerTask task) {
+ super.execute(task);
+ task.markerFile = getMarkerFile(scope);
+ }
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/CompatibleScreensManifest.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/CompatibleScreensManifest.groovy
new file mode 100644
index 0000000..b01dcf3
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/CompatibleScreensManifest.groovy
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.scope.ConventionMappingHelper
+import com.android.build.gradle.internal.scope.TaskConfigAction
+import com.android.build.gradle.internal.scope.VariantOutputScope
+import com.android.build.gradle.internal.tasks.DefaultAndroidTask
+import com.android.resources.Density
+import com.google.common.base.Charsets
+import com.google.common.io.Files
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Task to generate a manifest snippet that just contains a compatible-screens
+ * node with the given density and the given list of screen sizes.
+ */
+ at ParallelizableTask
+class CompatibleScreensManifest extends DefaultAndroidTask {
+
+ @Input
+ String screenDensity
+
+ @Input
+ Set<String> screenSizes
+
+ @OutputFile
+ File manifestFile
+
+ @TaskAction
+ void generate() {
+ StringBuilder content = new StringBuilder(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " package=\"\">\n" +
+ "\n" +
+ " <compatible-screens>\n")
+
+ String density = getScreenDensity()
+
+ // convert unsupported values to numbers.
+ density = convert(density, Density.XXHIGH, Density.XXXHIGH);
+
+ for (String size : getScreenSizes()) {
+ content.append(
+ " <screen android:screenSize=\"$size\" android:screenDensity=\"$density\" />\n")
+ }
+
+ content.append(
+ " </compatible-screens>\n" +
+ "</manifest>")
+
+ Files.write(content.toString(), getManifestFile(), Charsets.UTF_8);
+ }
+
+ private static String convert(@NonNull String density, @NonNull Density... densitiesToConvert) {
+ for (Density densityToConvert : densitiesToConvert) {
+ if (densityToConvert.getResourceValue().equals(density)) {
+ return Integer.toString(densityToConvert.dpiValue);
+ }
+ }
+
+ return density;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<CompatibleScreensManifest> {
+
+ @NonNull
+ VariantOutputScope scope
+ @NonNull
+ Set<String> screenSizes
+
+ ConfigAction(
+ @NonNull VariantOutputScope scope,
+ @NonNull Set<String> screenSizes) {
+ this.scope = scope
+ this.screenSizes = screenSizes
+ }
+
+ @NonNull
+ @Override
+ String getName() {
+ return scope.getTaskName("create", "CompatibleScreenManifest")
+ }
+
+ @NonNull
+ @Override
+ Class<CompatibleScreensManifest> getType() {
+ return CompatibleScreensManifest.class
+ }
+
+ @Override
+ void execute(@NonNull CompatibleScreensManifest csmTask) {
+ csmTask.setVariantName(scope.getVariantScope().getVariantConfiguration().getFullName())
+
+ csmTask.screenDensity = scope.variantOutputData.getMainOutputFile().getFilter(
+ com.android.build.OutputFile.DENSITY)
+ csmTask.screenSizes = screenSizes
+
+ ConventionMappingHelper.map(csmTask, "manifestFile") {
+ scope.getCompatibleScreensManifestFile()
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy
new file mode 100644
index 0000000..1533b91
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ExtractAnnotations.groovy
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.tasks.AbstractAndroidCompile
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.tasks.annotations.ApiDatabase
+import com.android.build.gradle.tasks.annotations.Extractor
+import com.android.tools.lint.EcjParser
+import com.google.common.collect.Lists
+import org.eclipse.jdt.core.compiler.IProblem
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions
+import org.eclipse.jdt.internal.compiler.util.Util
+import org.gradle.api.file.EmptyFileVisitor
+import org.gradle.api.file.FileVisitDetails
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.TaskAction
+import org.gradle.tooling.BuildException
+
+import static com.android.SdkConstants.DOT_JAVA
+import static com.android.SdkConstants.UTF_8
+
+/**
+ * Task which extracts annotations from the source files, and writes them to one of
+ * two possible destinations:
+ * <ul>
+ * <li> A "external annotations" file (pointed to by {@link ExtractAnnotations#output})
+ * which records the annotations in a zipped XML format for use by the IDE and by
+ * lint to associate the (source retention) annotations back with the compiled code</li>
+ * <li> For any {@code Keep} annotated elements, a Proguard keep file (pointed to by
+ * {@link ExtractAnnotations#proguard}, which lists APIs (classes, methods and fields)
+ * that should not be removed even if no references in code are found to those APIs.</li>
+ * <p>
+ * We typically only extract external annotations when building libraries; ProGuard annotations
+ * are extracted when building libraries (to record in the AAR), <b>or</b> when building an
+ * app module where ProGuarding is enabled.
+ * </ul>
+ */
+ at ParallelizableTask
+class ExtractAnnotations extends AbstractAndroidCompile {
+ public BaseVariantData variant
+
+ /** Boot classpath: typically android.jar */
+ @Input
+ public List<String> bootClasspath
+
+ /** The output .zip file to write the annotations database to, if any */
+ @Optional
+ @OutputFile
+ public File output
+
+ /** The output proguard file to write any @Keep rules into, if any */
+ @Optional
+ @OutputFile
+ public File proguard
+
+ /**
+ * An optional pointer to an API file to filter the annotations by (any annotations
+ * not found in the API file are considered hidden/not exposed.) This is in the same
+ * format as the api-versions.xml file found in the SDK.
+ */
+ @Optional
+ @InputFile
+ public File apiFilter
+
+ /**
+ * A list of existing annotation zip files (or dirs) to merge in. This can be used to merge in
+ * a hardcoded set of annotations that are not present in the source code, such as
+ * {@code @Contract} annotations we'd like to record without actually having a dependency
+ * on the IDEA annotations library.
+ */
+ @Optional
+ @InputFile
+ public List<File> mergeJars
+
+ /**
+ * The encoding to use when reading source files. The output file will ignore this and
+ * will always be a UTF-8 encoded .xml file inside the annotations zip file.
+ */
+ @Optional
+ @Input
+ public String encoding
+
+ /**
+ * Location of class files. If set, any non-public typedef source retention annotations
+ * will be removed prior to .jar packaging.
+ */
+ @Optional
+ @InputFile
+ public File classDir
+
+ /** Whether we allow extraction even in the presence of symbol resolution errors */
+ @InputFile
+ public boolean allowErrors = true
+
+ @Override
+ @TaskAction
+ protected void compile() {
+ if (!hasAndroidAnnotations()) {
+ return
+ }
+
+ if (encoding == null) {
+ encoding = UTF_8
+ }
+
+ def result = parseSources()
+ def parsedUnits = result.getCompilationUnits()
+
+ try {
+ if (!allowErrors) {
+ for (CompilationUnitDeclaration unit : parsedUnits) {
+ // so maybe I don't need my map!!
+ def problems = unit.compilationResult().allProblems
+ for (IProblem problem : problems) {
+ if (problem.error) {
+ println "Not extracting annotations (compilation problems encountered)";
+ println "Error: " + problem.getOriginatingFileName() + ":" +
+ problem.getSourceLineNumber() + ": " + problem.getMessage()
+ // TODO: Consider whether we abort the build at this point!
+ return
+ }
+ }
+ }
+ }
+
+ // API definition file
+ ApiDatabase database = null;
+ if (apiFilter != null && apiFilter.exists()) {
+ try {
+ database = new ApiDatabase(apiFilter);
+ } catch (IOException e) {
+ throw new BuildException("Could not open API database " + apiFilter, e)
+ }
+ }
+
+ def displayInfo = project.logger.isEnabled(LogLevel.INFO)
+ def includeClassRetentionAnnotations = false
+ def sortAnnotations = false
+
+ Extractor extractor = new Extractor(database, classDir, displayInfo,
+ includeClassRetentionAnnotations, sortAnnotations);
+ extractor.extractFromProjectSource(parsedUnits)
+ if (mergeJars != null) {
+ for (File jar : mergeJars) {
+ extractor.mergeExisting(jar);
+ }
+ }
+ extractor.export(output, proguard)
+ extractor.removeTypedefClasses();
+ } finally {
+ result.dispose()
+ }
+ }
+
+ @Input
+ public boolean hasAndroidAnnotations() {
+ return variant.variantDependency.annotationsPresent
+ }
+
+ @NonNull
+ private EcjParser.EcjResult parseSources() {
+ List<ICompilationUnit> sourceUnits = Lists.newArrayListWithExpectedSize(100);
+
+ source.visit(new EmptyFileVisitor() {
+ @Override
+ void visitFile(FileVisitDetails fileVisitDetails) {
+ def file = fileVisitDetails.file;
+ def path = file.getPath()
+ if (path.endsWith(DOT_JAVA) && file.isFile()) {
+ char[] contents = Util.getFileCharContent(file, encoding);
+ ICompilationUnit unit = new CompilationUnit(contents, path, encoding);
+ sourceUnits.add(unit);
+ }
+ }
+ })
+
+ List<String> jars = Lists.newArrayList();
+ if (bootClasspath != null) {
+ jars.addAll(bootClasspath)
+ }
+ if (classpath != null) {
+ for (File jar : classpath) {
+ jars.add(jar.getPath());
+ }
+ }
+
+ CompilerOptions options = EcjParser.createCompilerOptions();
+ options.docCommentSupport = true; // So I can find @hide
+
+ // Note: We can *not* set options.ignoreMethodBodies=true because it disables
+ // type attribution!
+
+ def level = getLanguageLevel(sourceCompatibility)
+ options.sourceLevel = level
+ options.complianceLevel = options.sourceLevel
+ // We don't generate code, but just in case the parser consults this flag
+ // and makes sure that it's not greater than the source level:
+ options.targetJDK = options.sourceLevel
+ options.originalComplianceLevel = options.sourceLevel;
+ options.originalSourceLevel = options.sourceLevel;
+ options.inlineJsrBytecode = true; // >= 1.5
+
+ return EcjParser.parse(options, sourceUnits, jars, null);
+ }
+
+ private static long getLanguageLevel(String version) {
+ if ("1.6".equals(version)) {
+ return EcjParser.getLanguageLevel(1, 6);
+ } else if ("1.7".equals(version)) {
+ return EcjParser.getLanguageLevel(1, 7);
+ } else if ("1.5") {
+ return EcjParser.getLanguageLevel(1, 5);
+ } else {
+ return EcjParser.getLanguageLevel(1, 7);
+ }
+ }
+
+ private def addSources(List<ICompilationUnit> sourceUnits, File file) {
+ if (file.isDirectory()) {
+ def files = file.listFiles();
+ if (files != null) {
+ for (File sub : files) {
+ addSources(sourceUnits, sub);
+ }
+ }
+ } else if (file.getPath().endsWith(DOT_JAVA) && file.isFile()) {
+ char[] contents = Util.getFileCharContent(file, encoding);
+ ICompilationUnit unit = new CompilationUnit(contents, file.getPath(), encoding);
+ sourceUnits.add(unit);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy
new file mode 100644
index 0000000..6d07628
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.scope.ConventionMappingHelper
+import com.android.build.gradle.internal.scope.TaskConfigAction
+import com.android.build.gradle.internal.scope.VariantScope
+import com.android.build.gradle.internal.tasks.BaseTask
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.BaseVariantOutputData
+import com.android.builder.compiling.BuildConfigGenerator
+import com.android.builder.core.VariantConfiguration
+import com.android.builder.model.ClassField
+import com.android.utils.FileUtils
+import com.google.common.base.Strings
+import com.google.common.collect.Lists
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.TaskAction
+
+ at ParallelizableTask
+public class GenerateBuildConfig extends BaseTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File sourceOutputDir
+
+ // ----- PRIVATE TASK API -----
+
+ @Input
+ String buildConfigPackageName
+
+ @Input
+ String appPackageName
+
+ @Input
+ boolean debuggable
+
+ @Input
+ String flavorName
+
+ @Input
+ List<String> flavorNamesWithDimensionNames
+
+ @Input
+ String buildTypeName
+
+ @Input
+ @Optional
+ String versionName
+
+ @Input
+ int versionCode
+
+ List<Object> items;
+
+ @Input
+ List<String> getItemValues() {
+ List<Object> resolvedItems = getItems()
+ List<String> list = Lists.newArrayListWithCapacity(resolvedItems.size() * 3)
+
+ for (Object object : resolvedItems) {
+ if (object instanceof String) {
+ list.add((String) object)
+ } else if (object instanceof ClassField) {
+ ClassField field = (ClassField) object
+ list.add(field.type)
+ list.add(field.name)
+ list.add(field.value)
+ }
+ }
+
+ return list
+ }
+
+ @TaskAction
+ void generate() throws IOException {
+ // must clear the folder in case the packagename changed, otherwise,
+ // there'll be two classes.
+ File destinationDir = getSourceOutputDir()
+ FileUtils.emptyFolder(destinationDir)
+
+ BuildConfigGenerator generator = new BuildConfigGenerator(
+ getSourceOutputDir(),
+ getBuildConfigPackageName());
+
+ // Hack (see IDEA-100046): We want to avoid reporting "condition is always true"
+ // from the data flow inspection, so use a non-constant value. However, that defeats
+ // the purpose of this flag (when not in debug mode, if (BuildConfig.DEBUG && ...) will
+ // be completely removed by the compiler), so as a hack we do it only for the case
+ // where debug is true, which is the most likely scenario while the user is looking
+ // at source code.
+ //map.put(PH_DEBUG, Boolean.toString(mDebug));
+ generator.addField("boolean", "DEBUG",
+ getDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
+ .addField("String", "APPLICATION_ID", "\"${getAppPackageName()}\"")
+ .addField("String", "BUILD_TYPE", "\"${getBuildTypeName()}\"")
+ .addField("String", "FLAVOR", "\"${getFlavorName()}\"")
+ .addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
+ .addField("String", "VERSION_NAME", "\"${Strings.nullToEmpty(getVersionName())}\"")
+ .addItems(getItems())
+
+ List<String> flavors = getFlavorNamesWithDimensionNames()
+ int count = flavors.size()
+ if (count > 1) {
+ for (int i = 0; i < count; i += 2) {
+ generator.
+ addField("String", "FLAVOR_${flavors.get(i + 1)}", "\"${flavors.get(i)}\"")
+ }
+ }
+
+ generator.generate()
+ }
+
+ // ----- Config Action -----
+
+ public static class ConfigAction implements TaskConfigAction<GenerateBuildConfig> {
+
+ @NonNull
+ VariantScope scope
+
+ ConfigAction(@NonNull VariantScope scope) {
+ this.scope = scope
+ }
+
+ @Override
+ @NonNull
+ String getName() {
+ return scope.getTaskName("generate", "BuildConfig");
+ }
+
+ @Override
+ @NonNull
+ Class<GenerateBuildConfig> getType() {
+ return GenerateBuildConfig
+ }
+
+
+ @Override
+ void execute(@NonNull GenerateBuildConfig generateBuildConfigTask) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
+
+ variantData.generateBuildConfigTask = generateBuildConfigTask
+
+ VariantConfiguration variantConfiguration = variantData.variantConfiguration
+
+ generateBuildConfigTask.androidBuilder = scope.globalScope.androidBuilder
+ generateBuildConfigTask.setVariantName(scope.getVariantConfiguration().getFullName())
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "buildConfigPackageName") {
+ variantConfiguration.originalApplicationId
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "appPackageName") {
+ variantConfiguration.applicationId
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "versionName") {
+ variantConfiguration.versionName
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "versionCode") {
+ variantConfiguration.versionCode
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "debuggable") {
+ variantConfiguration.buildType.isDebuggable()
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "buildTypeName") {
+ variantConfiguration.buildType.name
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "flavorName") {
+ variantConfiguration.flavorName
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "flavorNamesWithDimensionNames") {
+ variantConfiguration.flavorNamesWithDimensionNames
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "items") {
+ variantConfiguration.buildConfigItems
+ }
+
+ ConventionMappingHelper.map(generateBuildConfigTask, "sourceOutputDir") {
+ scope.getBuildConfigSourceOutputDir()
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy
new file mode 100644
index 0000000..e5582b5
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateResValues.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.scope.ConventionMappingHelper
+import com.android.build.gradle.internal.scope.TaskConfigAction
+import com.android.build.gradle.internal.scope.VariantScope
+import com.android.build.gradle.internal.tasks.BaseTask
+import com.android.builder.compiling.ResValueGenerator
+import com.android.builder.core.VariantConfiguration
+import com.android.builder.model.ClassField
+import com.google.common.collect.Lists
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.TaskAction
+
+ at ParallelizableTask
+public class GenerateResValues extends BaseTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File resOutputDir
+
+ // ----- PRIVATE TASK API -----
+
+ List<Object> items
+
+ @Input
+ List<String> getItemValues() {
+ List<Object> resolvedItems = getItems()
+ List<String> list = Lists.newArrayListWithCapacity(resolvedItems.size() * 3)
+
+ for (Object object : resolvedItems) {
+ if (object instanceof String) {
+ list.add((String) object)
+ } else if (object instanceof ClassField) {
+ ClassField field = (ClassField) object
+ list.add(field.type)
+ list.add(field.name)
+ list.add(field.value)
+ }
+ }
+
+ return list
+ }
+
+ @TaskAction
+ void generate() {
+ File folder = getResOutputDir()
+ List<Object> resolvedItems = getItems()
+
+ if (resolvedItems.isEmpty()) {
+ folder.deleteDir()
+ } else {
+ ResValueGenerator generator = new ResValueGenerator(folder)
+ generator.addItems(getItems())
+
+ generator.generate()
+ }
+ }
+
+
+ public static class ConfigAction implements TaskConfigAction<GenerateResValues> {
+
+ @NonNull
+ VariantScope scope
+
+ ConfigAction(@NonNull VariantScope scope) {
+ this.scope = scope
+ }
+
+ @NonNull
+ @Override
+ String getName() {
+ return scope.getTaskName("generate", "ResValues");
+ }
+
+ @NonNull
+ @Override
+ Class getType() {
+ return GenerateResValues.class
+ }
+
+ @Override
+ void execute(@NonNull GenerateResValues generateResValuesTask) {
+ scope.variantData.generateResValuesTask = generateResValuesTask
+
+ VariantConfiguration variantConfiguration = scope.variantData.variantConfiguration
+
+ generateResValuesTask.androidBuilder = scope.globalScope.androidBuilder
+ generateResValuesTask.setVariantName(variantConfiguration.getFullName())
+
+ ConventionMappingHelper.map(generateResValuesTask, "items") {
+ variantConfiguration.resValues
+ }
+
+ ConventionMappingHelper.map(generateResValuesTask, "resOutputDir") {
+ new File(scope.globalScope.generatedDir,
+ "res/resValues/${scope.variantData.variantConfiguration.dirName}")
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateSplitAbiRes.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateSplitAbiRes.java
new file mode 100644
index 0000000..f6f0b5f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GenerateSplitAbiRes.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.dsl.AbiSplitOptions;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+import com.android.builder.core.AaptPackageProcessBuilder;
+import com.android.builder.core.VariantConfiguration;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.Iterables;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Generates all metadata (like AndroidManifest.xml) necessary for a ABI dimension split APK.
+ */
+ at ParallelizableTask
+public class GenerateSplitAbiRes extends BaseTask {
+
+ private String applicationId;
+
+ private String outputBaseName;
+
+ private Set<String> splits;
+
+ private File outputDirectory;
+
+ private boolean debuggable;
+
+ private AaptOptions aaptOptions;
+
+ private ApkVariantOutputData variantOutputData;
+
+ @SuppressWarnings("unused") // Synthetic task output
+ @OutputFiles
+ public List<File> getOutputFiles() {
+ List<File> outputFiles = new ArrayList<File>();
+ for (String split : getSplits()) {
+ outputFiles.add(getOutputFileForSplit(split));
+ }
+
+ return outputFiles;
+ }
+
+ @TaskAction
+ protected void doFullTaskAction() throws IOException, InterruptedException, ProcessException {
+
+ for (String split : getSplits()) {
+ String resPackageFileName = getOutputFileForSplit(split).getAbsolutePath();
+
+ File tmpDirectory = new File(getOutputDirectory(), getOutputBaseName());
+ tmpDirectory.mkdirs();
+
+ File tmpFile = new File(tmpDirectory, "AndroidManifest.xml");
+
+ String versionNameToUse = getVersionName();
+ if (versionNameToUse == null) {
+ versionNameToUse = String.valueOf(getVersionCode());
+ }
+
+ OutputStreamWriter fileWriter = new OutputStreamWriter(new FileOutputStream(tmpFile), "UTF-8");
+ try {
+ // Split name can only contains 0-9, a-z, A-Z, '.' and '_'. Replace all other
+ // characters with underscore.
+ String splitName = CharMatcher.inRange('0', '9')
+ .or(CharMatcher.inRange('A', 'Z'))
+ .or(CharMatcher.inRange('a', 'z'))
+ .or(CharMatcher.is('_'))
+ .or(CharMatcher.is('.'))
+ .negate()
+ .replaceFrom(split + "_" + getOutputBaseName(), '_');
+ fileWriter.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"" + getApplicationId() + "\"\n"
+ + " android:versionCode=\"" + getVersionCode() + "\"\n"
+ + " android:versionName=\"" + versionNameToUse + "\"\n"
+ + " split=\"lib_" + splitName + "\">\n"
+ + " <uses-sdk android:minSdkVersion=\"21\"/>\n" + "</manifest> ");
+ fileWriter.flush();
+ } finally {
+ fileWriter.close();
+ }
+
+ AaptPackageProcessBuilder aaptPackageCommandBuilder =
+ new AaptPackageProcessBuilder(tmpFile, getAaptOptions())
+ .setDebuggable(isDebuggable())
+ .setResPackageOutput(resPackageFileName);
+
+ getBuilder().processResources(
+ aaptPackageCommandBuilder,
+ false /* enforceUniquePackageName */,
+ new LoggedProcessOutputHandler(getILogger()));
+ }
+ }
+
+ private File getOutputFileForSplit(final String split) {
+ return new File(getOutputDirectory(),
+ "resources-" + getOutputBaseName() + "-" + split + ".ap_");
+ }
+
+ @Input
+ public String getApplicationId() {
+ return applicationId;
+ }
+
+ public void setApplicationId(String applicationId) {
+ this.applicationId = applicationId;
+ }
+
+ @Input
+ public int getVersionCode() {
+ return variantOutputData.getVersionCode();
+ }
+
+ @Input
+ @Optional
+ public String getVersionName() {
+ return variantOutputData.getVersionName();
+ }
+
+ @Input
+ public String getOutputBaseName() {
+ return outputBaseName;
+ }
+
+ public void setOutputBaseName(String outputBaseName) {
+ this.outputBaseName = outputBaseName;
+ }
+
+ @Input
+ public Set<String> getSplits() {
+ return splits;
+ }
+
+ public void setSplits(Set<String> splits) {
+ this.splits = splits;
+ }
+
+ public File getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ public void setOutputDirectory(File outputDirectory) {
+ this.outputDirectory = outputDirectory;
+ }
+
+ @Input
+ public boolean isDebuggable() {
+ return debuggable;
+ }
+
+ public void setDebuggable(boolean debuggable) {
+ this.debuggable = debuggable;
+ }
+
+ @Nested
+ public AaptOptions getAaptOptions() {
+ return aaptOptions;
+ }
+
+ public void setAaptOptions(AaptOptions aaptOptions) {
+ this.aaptOptions = aaptOptions;
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements TaskConfigAction<GenerateSplitAbiRes> {
+
+ private VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return scope.getTaskName("generate", "SplitAbiRes");
+ }
+
+ @Override
+ @NonNull
+ public Class<GenerateSplitAbiRes> getType() {
+ return GenerateSplitAbiRes.class;
+ }
+
+ @Override
+ public void execute(@NonNull GenerateSplitAbiRes generateSplitAbiRes) {
+ final VariantConfiguration config = scope.getVariantConfiguration();
+ Set<String> filters = AbiSplitOptions.getAbiFilters(
+ scope.getGlobalScope().getExtension().getSplits().getAbiFilters());
+
+ generateSplitAbiRes.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ generateSplitAbiRes.setVariantName(config.getFullName());
+
+ generateSplitAbiRes.setOutputDirectory(scope.getGenerateSplitAbiResOutputDirectory());
+ generateSplitAbiRes.setSplits(filters);
+ generateSplitAbiRes.setOutputBaseName(config.getBaseName());
+ generateSplitAbiRes.setApplicationId(config.getApplicationId());
+ generateSplitAbiRes.variantOutputData =
+ (ApkVariantOutputData) scope.getVariantData().getOutputs().get(0);
+ ConventionMappingHelper.map(generateSplitAbiRes, "debuggable", new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return config.getBuildType().isDebuggable();
+ }
+ });
+ ConventionMappingHelper.map(generateSplitAbiRes, "aaptOptions",
+ new Callable<AaptOptions>() {
+ @Override
+ public AaptOptions call() throws Exception {
+ return scope.getGlobalScope().getExtension().getAaptOptions();
+ }
+ });
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GroovyGradleDetector.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GroovyGradleDetector.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GroovyGradleDetector.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/GroovyGradleDetector.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/InvokeManifestMerger.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/InvokeManifestMerger.groovy
new file mode 100644
index 0000000..78b0621
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/InvokeManifestMerger.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+
+import com.android.build.gradle.internal.LoggerWrapper
+import com.android.build.gradle.internal.tasks.DefaultAndroidTask
+import com.android.manifmerger.ManifestMerger2
+import com.android.manifmerger.MergingReport
+import com.android.utils.ILogger
+import com.google.common.base.Supplier
+import org.apache.tools.ant.BuildException
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Simple task to invoke the new Manifest Merger without any injection, features, system properties
+ * or overlay manifests
+ */
+ at ParallelizableTask
+class InvokeManifestMerger extends DefaultAndroidTask implements Supplier<File> {
+
+ @InputFile
+ File mainManifestFile;
+
+ @InputFiles
+ List<File> secondaryManifestFiles
+
+ @OutputFile
+ File outputFile
+
+ @TaskAction
+ protected void doFullTaskAction() {
+ ILogger iLogger = new LoggerWrapper(getLogger());
+ ManifestMerger2.Invoker mergerInvoker = ManifestMerger2.
+ newMerger(getMainManifestFile(), iLogger, ManifestMerger2.MergeType.APPLICATION)
+ mergerInvoker.addLibraryManifests(secondaryManifestFiles.toArray(new File[secondaryManifestFiles.size()]))
+ MergingReport mergingReport = mergerInvoker.merge();
+ if (mergingReport.result.isError()) {
+ getLogger().error(mergingReport.reportString);
+ mergingReport.log(iLogger);
+ throw new BuildException(mergingReport.reportString);
+ }
+ FileWriter fileWriter = null;
+ try {
+ fileWriter = new FileWriter(getOutputFile())
+ fileWriter.append(mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED))
+ } finally {
+ if (fileWriter != null) {
+ fileWriter.close()
+ }
+ }
+ }
+
+ @Override
+ File get() {
+ return getOutputFile()
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JackTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JackTask.java
new file mode 100644
index 0000000..51295ca
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JackTask.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.AbstractAndroidCompile;
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.internal.variant.ApplicationVariantData;
+import com.android.build.gradle.tasks.factory.AbstractCompilesUtil;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.tasks.Job;
+import com.android.builder.tasks.JobContext;
+import com.android.builder.tasks.Task;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessException;
+import com.android.sdklib.BuildToolInfo;
+import com.android.repository.Revision;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.gradle.api.Project;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Jack task.
+ */
+ at ParallelizableTask
+public class JackTask extends AbstractAndroidCompile
+ implements FileSupplier, BinaryFileProviderTask {
+
+ public static final Revision JACK_MIN_REV = new Revision(21, 1, 0);
+
+ private AndroidBuilder androidBuilder;
+
+ private boolean isVerbose;
+ private boolean isDebugLog;
+
+ private Collection<File> packagedLibraries;
+ private Collection<File> proguardFiles;
+ private Collection<File> jarJarRuleFiles;
+
+ private boolean debug;
+
+ private File tempFolder;
+ private File jackFile;
+ private File javaResourcesFolder;
+
+ private File mappingFile;
+
+ private boolean multiDexEnabled;
+
+ private int minSdkVersion;
+
+ private String javaMaxHeapSize;
+
+ private File incrementalDir;
+
+ @Override
+ @TaskAction
+ public void compile() {
+ final Job<Void> job = new Job<Void>(getName(), new Task<Void>() {
+ @Override
+ public void run(@NonNull Job<Void> job, @NonNull JobContext<Void> context)
+ throws IOException {
+ try {
+ JackTask.this.doMinification();
+ } catch (ProcessException e) {
+ throw new IOException(e);
+ }
+ }
+
+ });
+ try {
+ SimpleWorkQueue.push(job);
+
+ // wait for the task completion.
+ job.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private void doMinification() throws ProcessException, IOException {
+
+ if (System.getenv("USE_JACK_API") != null) {
+ androidBuilder.convertByteCodeUsingJackApis(
+ getDestinationDir(),
+ getJackFile(),
+ getClasspath().getFiles(),
+ getPackagedLibraries(),
+ getSource().getFiles(),
+ getProguardFiles(),
+ getMappingFile(),
+ getJarJarRuleFiles(),
+ getIncrementalDir(),
+ getJavaResourcesFolder(),
+ isMultiDexEnabled(),
+ getMinSdkVersion());
+ } else {
+ // no incremental support through command line so far.
+ androidBuilder.convertByteCodeWithJack(
+ getDestinationDir(),
+ getJackFile(),
+ computeBootClasspath(),
+ getPackagedLibraries(),
+ computeEcjOptionFile(),
+ getProguardFiles(),
+ getMappingFile(),
+ getJarJarRuleFiles(),
+ isMultiDexEnabled(),
+ getMinSdkVersion(),
+ isDebugLog,
+ getJavaMaxHeapSize(),
+ new LoggedProcessOutputHandler(androidBuilder.getLogger()));
+ }
+
+ }
+
+ private File computeEcjOptionFile() throws IOException {
+ File folder = getTempFolder();
+ //noinspection ResultOfMethodCallIgnored
+ folder.mkdirs();
+ File file = new File(folder, "ecj-options.txt");
+
+ StringBuilder sb = new StringBuilder();
+
+ for (File sourceFile : getSource().getFiles()) {
+ sb.append(sourceFile.getAbsolutePath()).append("\n");
+ }
+
+ //noinspection ResultOfMethodCallIgnored
+ file.getParentFile().mkdirs();
+
+ Files.write(sb.toString(), file, Charsets.UTF_8);
+
+ return file;
+ }
+
+ private String computeBootClasspath() {
+ return Joiner.on(':').join(
+ Iterables.transform(getClasspath().getFiles(), GET_ABSOLUTE_PATH));
+ }
+
+ private static final Function<File, String> GET_ABSOLUTE_PATH = new Function<File, String>() {
+ @Override
+ public String apply(File file) {
+ return file.getAbsolutePath();
+ }
+ };
+
+
+ @InputFile
+ public File getJackExe() {
+ return new File(
+ androidBuilder.getTargetInfo().getBuildTools().getPath(BuildToolInfo.PathId.JACK));
+ }
+
+ public AndroidBuilder getAndroidBuilder() {
+ return androidBuilder;
+ }
+
+ public void setAndroidBuilder(AndroidBuilder androidBuilder) {
+ this.androidBuilder = androidBuilder;
+ }
+
+ public boolean getIsVerbose() {
+ return isVerbose;
+ }
+
+ public void setIsVerbose(boolean isVerbose) {
+ this.isVerbose = isVerbose;
+ }
+
+ public boolean getIsDebugLog() {
+ return isDebugLog;
+ }
+
+ public void setIsDebugLog(boolean isDebugLog) {
+ this.isDebugLog = isDebugLog;
+ }
+
+ @InputFiles
+ public Collection<File> getPackagedLibraries() {
+ return packagedLibraries;
+ }
+
+ public void setPackagedLibraries(Collection<File> packagedLibraries) {
+ this.packagedLibraries = packagedLibraries;
+ }
+
+ @InputFiles
+ @Optional
+ public Collection<File> getProguardFiles() {
+ return proguardFiles;
+ }
+
+ public void setProguardFiles(Collection<File> proguardFiles) {
+ this.proguardFiles = proguardFiles;
+ }
+
+ @InputFiles
+ @Optional
+ public Collection<File> getJarJarRuleFiles() {
+ return jarJarRuleFiles;
+ }
+
+ public void setJarJarRuleFiles(Collection<File> jarJarRuleFiles) {
+ this.jarJarRuleFiles = jarJarRuleFiles;
+ }
+
+ @Input
+ public boolean getDebug() {
+ return debug;
+ }
+
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ public File getTempFolder() {
+ return tempFolder;
+ }
+
+ public void setTempFolder(File tempFolder) {
+ this.tempFolder = tempFolder;
+ }
+
+ @OutputFile
+ public File getJackFile() {
+ return jackFile;
+ }
+
+ public void setJackFile(File jackFile) {
+ this.jackFile = jackFile;
+ }
+
+ @OutputFile
+ @Optional
+ public File getMappingFile() {
+ return mappingFile;
+ }
+
+ public void setMappingFile(File mappingFile) {
+ this.mappingFile = mappingFile;
+ }
+
+ @Input
+ public boolean isMultiDexEnabled() {
+ return multiDexEnabled;
+ }
+
+ public void setMultiDexEnabled(boolean multiDexEnabled) {
+ this.multiDexEnabled = multiDexEnabled;
+ }
+
+ @Input
+ public int getMinSdkVersion() {
+ return minSdkVersion;
+ }
+
+ public void setMinSdkVersion(int minSdkVersion) {
+ this.minSdkVersion = minSdkVersion;
+ }
+
+ @Input
+ @Optional
+ public String getJavaMaxHeapSize() {
+ return javaMaxHeapSize;
+ }
+
+ public void setJavaMaxHeapSize(String javaMaxHeapSize) {
+ this.javaMaxHeapSize = javaMaxHeapSize;
+ }
+
+ @Input
+ @Optional
+ public File getIncrementalDir() {
+ return incrementalDir;
+ }
+
+ public void setIncrementalDir(File incrementalDir) {
+ this.incrementalDir = incrementalDir;
+ }
+
+ @Input
+ @Optional
+ public File getJavaResourcesFolder() {
+ return javaResourcesFolder;
+ }
+
+ public void setJavaResourcesFolder(File javaResourcesFolder) {
+ this.javaResourcesFolder = javaResourcesFolder;
+ }
+
+ @Override
+ @NonNull
+ public BinaryFileProviderTask.Artifact getArtifact() {
+ return new BinaryFileProviderTask.Artifact(
+ BinaryFileProviderTask.BinaryArtifactType.JACK,
+ getJackFile());
+ }
+
+ // ----- FileSupplierTask ----
+ @NonNull
+ @Override
+ public org.gradle.api.Task getTask() {
+ return this;
+ }
+
+ @Override
+ public File get() {
+ return getMappingFile();
+ }
+
+ public static class ConfigAction implements TaskConfigAction<JackTask> {
+
+ private final VariantScope scope;
+ private final boolean isVerbose;
+ private final boolean isDebugLog;
+
+ public ConfigAction(VariantScope scope, boolean isVerbose, boolean isDebugLog) {
+ this.scope = scope;
+ this.isVerbose = isVerbose;
+ this.isDebugLog = isDebugLog;
+ }
+
+ @Override
+ public String getName() {
+ return scope.getTaskName("compile", "JavaWithJack");
+ }
+
+ @Override
+ public Class<JackTask> getType() {
+ return JackTask.class;
+ }
+
+ @Override
+ public void execute(JackTask jackTask) {
+ jackTask.setIsVerbose(isVerbose);
+ jackTask.setIsDebugLog(isDebugLog);
+
+ GlobalScope globalScope = scope.getGlobalScope();
+
+ jackTask.androidBuilder = globalScope.getAndroidBuilder();
+ jackTask.setJavaMaxHeapSize(
+ globalScope.getExtension().getDexOptions().getJavaMaxHeapSize());
+
+ jackTask.setSource(scope.getVariantData().getJavaSources());
+
+ final GradleVariantConfiguration config = scope.getVariantData().getVariantConfiguration();
+ jackTask.setMultiDexEnabled(config.isMultiDexEnabled());
+ jackTask.setMinSdkVersion(config.getMinSdkVersion().getApiLevel());
+ jackTask.incrementalDir = scope.getIncrementalDir(getName());
+
+ // if the tested variant is an app, add its classpath. For the libraries,
+ // it's done automatically since the classpath includes the library output as a normal
+ // dependency.
+ if (scope.getTestedVariantData() instanceof ApplicationVariantData) {
+ ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() {
+ @Override
+ public FileCollection call() throws Exception {
+ Project project = scope.getGlobalScope().getProject();
+ return project.fileTree(scope.getJillRuntimeLibrariesDir()).plus(
+ project.fileTree(
+ scope.getTestedVariantData().getScope()
+ .getJillRuntimeLibrariesDir())).plus(
+ project.fileTree(
+ scope.getTestedVariantData().getScope().getJackClassesZip()
+ ));
+ }
+ });
+ } else {
+ ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() {
+ @Override
+ public FileCollection call() throws Exception {
+ return scope.getGlobalScope().getProject().fileTree(
+ scope.getJillRuntimeLibrariesDir());
+ }
+ });
+ }
+
+ ConventionMappingHelper.map(jackTask, "packagedLibraries", new Callable<Collection<File>>() {
+ @Override
+ public Collection<File> call() throws Exception {
+ return scope.getGlobalScope().getProject()
+ .fileTree(scope.getJillPackagedLibrariesDir()).getFiles();
+ }
+ });
+
+ jackTask.setDestinationDir(scope.getJackDestinationDir());
+ jackTask.setJackFile(scope.getJackClassesZip());
+ jackTask.setTempFolder(new File(scope.getGlobalScope().getIntermediatesDir(),
+ "/tmp/jack/" + scope.getVariantConfiguration().getDirName()));
+
+ jackTask.setJavaResourcesFolder(scope.getJavaResourcesDestinationDir());
+
+ if (config.isMinifyEnabled()) {
+ ConventionMappingHelper.map(jackTask, "proguardFiles", new Callable<Collection<File>>() {
+ @Override
+ public Collection<File> call() throws Exception {
+ // since all the output use the same resources, we can use the first output
+ // to query for a proguard file.
+ File sdkDir = scope.getGlobalScope().getSdkHandler().getAndCheckSdkFolder();
+ File defaultProguardFile = new File(sdkDir,
+ SdkConstants.FD_TOOLS + File.separatorChar
+ + SdkConstants.FD_PROGUARD + File.separatorChar
+ + TaskManager.DEFAULT_PROGUARD_CONFIG_FILE);
+
+ Set<File> proguardFiles = config.getProguardFiles(true /*includeLibs*/,
+ ImmutableList.of(defaultProguardFile));
+ File proguardResFile = scope.getProcessAndroidResourcesProguardOutputFile();
+ proguardFiles.add(proguardResFile);
+ // for tested app, we only care about their aapt config since the base
+ // configs are the same files anyway.
+ if (scope.getTestedVariantData() != null) {
+ proguardResFile = scope.getTestedVariantData().getScope()
+ .getProcessAndroidResourcesProguardOutputFile();
+ proguardFiles.add(proguardResFile);
+ }
+
+ return proguardFiles;
+ }
+ });
+
+ jackTask.mappingFile = new File(scope.getProguardOutputFolder(), "mapping.txt");
+ }
+
+
+ ConventionMappingHelper.map(jackTask, "jarJarRuleFiles", new Callable<List<File>>() {
+ @Override
+ public List<File> call() throws Exception {
+ List<File> jarJarRuleFiles = Lists.newArrayListWithCapacity(
+ config.getJarJarRuleFiles().size());
+ Project project = scope.getGlobalScope().getProject();
+ for (File file: config.getJarJarRuleFiles()) {
+ jarJarRuleFiles.add(project.file(file));
+ }
+ return jarJarRuleFiles;
+ }
+ });
+
+ AbstractCompilesUtil.configureLanguageLevel(
+ jackTask,
+ scope.getGlobalScope().getExtension().getCompileOptions(),
+ scope.getGlobalScope().getExtension().getCompileSdkVersion()
+ );
+
+ scope.getVariantData().jackTask = jackTask;
+ scope.getVariantData().javaCompilerTask = jackTask;
+ scope.getVariantData().mappingFileProviderTask = jackTask;
+ scope.getVariantData().binaryFileProviderTask = jackTask;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JillTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JillTask.java
new file mode 100644
index 0000000..cc4c99a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/JillTask.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.android.builder.core.AndroidBuilder;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.repository.Revision;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+ at ParallelizableTask
+public class JillTask extends BaseTask {
+
+ private Collection<File> inputLibs;
+
+ private File outputFolder;
+
+ private DexOptions dexOptions;
+
+ @TaskAction
+ public void taskAction(IncrementalTaskInputs taskInputs)
+ throws LoggedErrorException, InterruptedException, IOException {
+ Revision revision = getBuilder().getTargetInfo().getBuildTools().getRevision();
+ if (revision.compareTo(JackTask.JACK_MIN_REV) < 0) {
+ throw new RuntimeException(
+ "Jack requires Build Tools " + JackTask.JACK_MIN_REV.toString()
+ + " or later");
+ }
+
+ final File outFolder = getOutputFolder();
+
+ // if we are not in incremental mode, then outOfDate will contain
+ // all th files, but first we need to delete the previous output
+ if (!taskInputs.isIncremental()) {
+ FileUtils.emptyFolder(outFolder);
+ }
+
+ final Set<String> hashs = Sets.newHashSet();
+ final WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
+ final List<File> inputFileDetails = Lists.newArrayList();
+
+ final AndroidBuilder builder = getBuilder();
+
+ taskInputs.outOfDate(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails change) {
+ inputFileDetails.add(change.getFile());
+ }
+ });
+
+ for (final File file : inputFileDetails) {
+ Callable<Void> action = new JillCallable(this, file, hashs, outFolder, builder);
+ executor.execute(action);
+ }
+
+ taskInputs.removed(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails change) {
+ File jackFile = getJackFileName(outFolder, ((InputFileDetails) change).getFile());
+ //noinspection ResultOfMethodCallIgnored
+ jackFile.delete();
+ }
+ });
+
+ executor.waitForTasksWithQuickFail(false);
+ }
+
+ @Input
+ public String getBuildToolsVersion() {
+ return getBuildTools().getRevision().toString();
+ }
+
+ @InputFiles
+ public Collection<File> getInputLibs() {
+ return inputLibs;
+ }
+
+ public void setInputLibs(Collection<File> inputLibs) {
+ this.inputLibs = inputLibs;
+ }
+
+ @OutputDirectory
+ public File getOutputFolder() {
+ return outputFolder;
+ }
+
+ public void setOutputFolder(File outputFolder) {
+ this.outputFolder = outputFolder;
+ }
+
+ @Nested
+ public DexOptions getDexOptions() {
+ return dexOptions;
+ }
+
+ public void setDexOptions(DexOptions dexOptions) {
+ this.dexOptions = dexOptions;
+ }
+
+ private final class JillCallable implements Callable<Void> {
+
+ @NonNull
+ private final File fileToProcess;
+
+ @NonNull
+ private final Set<String> hashs;
+
+ @NonNull
+ private final com.android.builder.core.DexOptions options = getDexOptions();
+
+ @NonNull
+ private final File outFolder;
+
+ @NonNull
+ private final AndroidBuilder builder;
+
+ private JillCallable(JillTask enclosing, @NonNull File file, @NonNull Set<String> hashs,
+ @NonNull File outFolder, @NonNull AndroidBuilder builder) {
+ this.fileToProcess = file;
+ this.hashs = hashs;
+ this.outFolder = outFolder;
+ this.builder = builder;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ // TODO remove once we can properly add a library as a dependency of its test.
+ String hash = getFileHash(fileToProcess);
+
+ synchronized (hashs) {
+ if (hashs.contains(hash)) {
+ return null;
+ }
+
+ hashs.add(hash);
+ }
+
+ //noinspection GroovyAssignabilityCheck
+ File jackFile = getJackFileName(outFolder, fileToProcess);
+ //noinspection GroovyAssignabilityCheck
+ builder.convertLibraryToJack(fileToProcess, jackFile, options,
+ new LoggedProcessOutputHandler(builder.getLogger()));
+
+ return null;
+ }
+
+ @NonNull
+ public final File getOutFolder() {
+ return outFolder;
+ }
+ }
+
+ /**
+ * Returns the hash of a file.
+ *
+ * @param file the file to hash
+ */
+ private static String getFileHash(@NonNull File file) throws IOException {
+ HashCode hashCode = Files.hash(file, Hashing.sha1());
+ return hashCode.toString();
+ }
+
+ /**
+ * Returns a unique File for the converted library, even if there are 2 libraries with the same
+ * file names (but different paths)
+ *
+ * @param outFolder the output folder.
+ * @param inputFile the library
+ */
+ @NonNull
+ public static File getJackFileName(@NonNull File outFolder, @NonNull File inputFile) {
+ // get the filename
+ String name = inputFile.getName();
+ // remove the extension
+ int pos = name.lastIndexOf('.');
+ if (pos != -1) {
+ name = name.substring(0, pos);
+ }
+
+ // add a hash of the original file path.
+ String input = inputFile.getAbsolutePath();
+ HashFunction hashFunction = Hashing.sha1();
+ HashCode hashCode = hashFunction.hashString(input, Charsets.UTF_16LE);
+
+ return new File(outFolder, name + "-" + hashCode.toString() + SdkConstants.DOT_JAR);
+ }
+
+ public static class RuntimeTaskConfigAction implements TaskConfigAction<JillTask> {
+
+ private final VariantScope variantScope;
+
+ // TODO: If task can be shared between variants, change to GlobalScope.
+ public RuntimeTaskConfigAction(VariantScope scope) {
+ this.variantScope = scope;
+ }
+
+ @Override
+ public String getName() {
+ return variantScope.getTaskName("jill", "RuntimeLibraries");
+ }
+
+ @Override
+ public Class<JillTask> getType() {
+ return JillTask.class;
+ }
+
+ @Override
+ public void execute(JillTask jillTask) {
+ final GlobalScope globalScope = variantScope.getGlobalScope();
+ final AndroidBuilder androidBuilder = globalScope.getAndroidBuilder();
+
+ jillTask.setAndroidBuilder(androidBuilder);
+ jillTask.setVariantName(variantScope.getVariantConfiguration().getFullName());
+ jillTask.setDexOptions(globalScope.getExtension().getDexOptions());
+
+ ConventionMappingHelper.map(jillTask, "inputLibs", new Callable<List<File>>() {
+ @Override
+ public List<File> call() throws Exception {
+ return androidBuilder.getBootClasspath(false);
+ }
+ });
+
+ jillTask.setOutputFolder(variantScope.getJillRuntimeLibrariesDir());
+ }
+ }
+
+ public static class PackagedConfigAction implements TaskConfigAction<JillTask> {
+
+ private final VariantScope variantScope;
+
+ public PackagedConfigAction(VariantScope scope) {
+ this.variantScope = scope;
+ }
+
+ @Override
+ public String getName() {
+ return variantScope.getTaskName("jill", "PackagedLibraries");
+ }
+
+ @Override
+ public Class<JillTask> getType() {
+ return JillTask.class;
+ }
+
+ @Override
+ public void execute(JillTask jillTask) {
+ final GlobalScope globalScope = variantScope.getGlobalScope();
+ final AndroidBuilder androidBuilder = globalScope.getAndroidBuilder();
+
+ jillTask.setAndroidBuilder(androidBuilder);
+ jillTask.setVariantName(variantScope.getVariantConfiguration().getFullName());
+ jillTask.setDexOptions(globalScope.getExtension().getDexOptions());
+
+ ConventionMappingHelper.map(jillTask, "inputLibs", new Callable<Set<File>>() {
+ @Override
+ public Set<File> call() throws Exception {
+ return androidBuilder.getAllPackagedJars(variantScope.getVariantConfiguration());
+ }
+ });
+
+ jillTask.setOutputFolder(variantScope.getJillPackagedLibrariesDir());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/KickerTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/KickerTask.java
new file mode 100644
index 0000000..574036a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/KickerTask.java
@@ -0,0 +1,86 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunPatchingPolicy;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.IncrementalTask;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * Generic kicker task that will run at each iteration and possibly block or manually kick any
+ * dependent task.
+ */
+public abstract class KickerTask extends IncrementalTask {
+
+ long buildId;
+
+ @Input
+ public long getBuildId() {
+ return buildId;
+ }
+
+ File markerFile;
+
+ @OutputFile
+ @Optional
+ public File getMarkerFile() {
+ return markerFile;
+ }
+ InstantRunBuildContext instantRunContext;
+ VariantScope variantScope;
+
+ public abstract static class ConfigAction<T extends KickerTask>
+ implements TaskConfigAction<T> {
+
+ @NonNull
+ protected final VariantScope scope;
+
+ @NonNull
+ protected final String name;
+
+ public ConfigAction(@NonNull String name, @NonNull VariantScope scope) {
+ this.scope = scope;
+ this.name = name;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return scope.getTaskName(name);
+ }
+
+ @Override
+ public void execute(@NonNull T task) {
+ task.setVariantName(scope.getVariantConfiguration().getFullName());
+ task.buildId = scope.getInstantRunBuildContext().getBuildId();
+ task.variantScope = scope;
+ task.instantRunContext = scope.getInstantRunBuildContext();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
new file mode 100644
index 0000000..0f59cbf
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.internal.LintGradleClient
+import com.android.build.gradle.internal.dsl.LintOptions
+import com.android.build.gradle.internal.scope.TaskConfigAction
+import com.android.build.gradle.internal.scope.VariantScope
+import com.android.build.gradle.internal.tasks.BaseTask
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.android.tools.lint.LintCliFlags
+import com.android.tools.lint.Reporter
+import com.android.tools.lint.Warning
+import com.android.tools.lint.checks.BuiltinIssueRegistry
+import com.android.tools.lint.checks.GradleDetector
+import com.android.tools.lint.checks.UnusedResourceDetector
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.Severity
+import com.google.common.collect.Maps
+import com.google.common.collect.Sets
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.TaskAction
+import org.gradle.tooling.provider.model.ToolingModelBuilder
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+
+ at ParallelizableTask
+public class Lint extends BaseTask {
+ @NonNull private LintOptions mLintOptions
+ @Nullable private File mSdkHome
+ private boolean mFatalOnly
+ private ToolingModelBuilderRegistry mToolingRegistry
+
+ public void setLintOptions(@NonNull LintOptions lintOptions) {
+ mLintOptions = lintOptions
+ }
+
+ public void setSdkHome(@NonNull File sdkHome) {
+ mSdkHome = sdkHome
+ }
+
+ void setToolingRegistry(ToolingModelBuilderRegistry toolingRegistry) {
+ mToolingRegistry = toolingRegistry
+ }
+
+ public void setFatalOnly(boolean fatalOnly) {
+ mFatalOnly = fatalOnly
+ }
+
+ @SuppressWarnings("GroovyUnusedDeclaration")
+ @TaskAction
+ public void lint() {
+ def modelProject = createAndroidProject(project)
+ if (getVariantName() != null && !getVariantName().isEmpty()) {
+ for (Variant variant : modelProject.getVariants()) {
+ if (variant.getName().equals(getVariantName())) {
+ lintSingleVariant(modelProject, variant);
+ }
+ }
+ } else {
+ lintAllVariants(modelProject);
+ }
+ }
+
+ /**
+ * Runs lint individually on all the variants, and then compares the results
+ * across variants and reports these
+ */
+ public void lintAllVariants(@NonNull AndroidProject modelProject) {
+ // In the Gradle integration we iterate over each variant, and
+ // attribute unused resources to each variant, so don't make
+ // each variant run go and inspect the inactive variant sources
+ UnusedResourceDetector.sIncludeInactiveReferences = false;
+
+ Map<Variant,List<Warning>> warningMap = Maps.newHashMap()
+ for (Variant variant : modelProject.getVariants()) {
+ try {
+ List<Warning> warnings = runLint(modelProject, variant, false)
+ warningMap.put(variant, warnings)
+ } catch (IOException e) {
+ throw new GradleException("Invalid arguments.", e)
+ }
+ }
+
+ // Compute error matrix
+ def quiet = mLintOptions.quiet
+
+
+ for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
+ def variant = entry.getKey()
+ def warnings = entry.getValue()
+ if (!mFatalOnly && !quiet) {
+ println "Ran lint on variant " + variant.getName() + ": " + warnings.size() +
+ " issues found"
+ }
+ }
+
+ List<Warning> mergedWarnings = LintGradleClient.merge(warningMap, modelProject)
+ int errorCount = 0
+ int warningCount = 0
+ for (Warning warning : mergedWarnings) {
+ if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) {
+ errorCount++
+ } else if (warning.severity == Severity.WARNING) {
+ warningCount++
+ }
+ }
+
+ /*
+ * We pick the first variant to generate the full report and don't generate if we don't
+ * have any variants.
+ */
+ if (modelProject.getVariants().size() > 0) {
+ Set<Variant> allVariants = Sets.newTreeSet(new Comparator<Variant>() {
+ @Override
+ int compare(Variant v1, Variant v2) {
+ v1.getName().compareTo(v2.getName());
+ }
+ });
+
+ allVariants.addAll(modelProject.getVariants());
+ Variant variant = allVariants.iterator().next();
+
+ IssueRegistry registry = new BuiltinIssueRegistry()
+ LintCliFlags flags = new LintCliFlags()
+ LintGradleClient client = new LintGradleClient(registry, flags, project, modelProject,
+ mSdkHome, variant, getBuildTools())
+ syncOptions(mLintOptions, client, flags, variant, project, true, mFatalOnly)
+
+ for (Reporter reporter : flags.getReporters()) {
+ reporter.write(errorCount, warningCount, mergedWarnings)
+ }
+
+ if (flags.isSetExitCode() && errorCount > 0) {
+ abort()
+ }
+ }
+ }
+
+ private void abort() {
+ def message;
+ if (mFatalOnly) {
+ message = "" +
+ "Lint found fatal errors while assembling a release target.\n" +
+ "\n" +
+ "To proceed, either fix the issues identified by lint, or modify your build script as follows:\n" +
+ "...\n" +
+ "android {\n" +
+ " lintOptions {\n" +
+ " checkReleaseBuilds false\n" +
+ " // Or, if you prefer, you can continue to check for errors in release builds,\n" +
+ " // but continue the build even when errors are found:\n" +
+ " abortOnError false\n" +
+ " }\n" +
+ "}\n" +
+ "..."
+ ""
+ } else {
+ message = "" +
+ "Lint found errors in the project; aborting build.\n" +
+ "\n" +
+ "Fix the issues identified by lint, or add the following to your build script to proceed with errors:\n" +
+ "...\n" +
+ "android {\n" +
+ " lintOptions {\n" +
+ " abortOnError false\n" +
+ " }\n" +
+ "}\n" +
+ "..."
+ }
+ throw new GradleException(message);
+ }
+
+ /**
+ * Runs lint on a single specified variant
+ */
+ public void lintSingleVariant(@NonNull AndroidProject modelProject, @NonNull Variant variant) {
+ runLint(modelProject, variant, true)
+ }
+
+ /** Runs lint on the given variant and returns the set of warnings */
+ private List<Warning> runLint(
+ @NonNull AndroidProject modelProject,
+ @NonNull Variant variant,
+ boolean report) {
+ IssueRegistry registry = createIssueRegistry()
+ LintCliFlags flags = new LintCliFlags()
+ LintGradleClient client = new LintGradleClient(registry, flags, project, modelProject,
+ mSdkHome, variant, getBuildTools())
+ if (mFatalOnly) {
+ if (!mLintOptions.isCheckReleaseBuilds()) {
+ return Collections.emptyList()
+ }
+ flags.setFatalOnly(true)
+ }
+ syncOptions(mLintOptions, client, flags, variant, project, report, mFatalOnly)
+ if (!report || mFatalOnly) {
+ flags.setQuiet(true)
+ }
+
+ List<Warning> warnings;
+ try {
+ warnings = client.run(registry)
+ } catch (IOException e) {
+ throw new GradleException("Invalid arguments.", e)
+ }
+
+ if (report && client.haveErrors() && flags.isSetExitCode()) {
+ abort()
+ }
+
+ return warnings;
+ }
+
+ private static syncOptions(
+ @NonNull LintOptions options,
+ @NonNull LintGradleClient client,
+ @NonNull LintCliFlags flags,
+ @NonNull Variant variant,
+ @NonNull Project project,
+ boolean report,
+ boolean fatalOnly) {
+ options.syncTo(client, flags, variant.getName(), project, report)
+
+ if (fatalOnly || flags.quiet) {
+ for (Reporter reporter : flags.getReporters()) {
+ reporter.setDisplayEmpty(false)
+ }
+ }
+ }
+
+ private AndroidProject createAndroidProject(@NonNull Project gradleProject) {
+ String modelName = AndroidProject.class.getName()
+ ToolingModelBuilder modelBuilder = mToolingRegistry.getBuilder(modelName)
+ assert modelBuilder != null
+ return (AndroidProject) modelBuilder.buildAll(modelName, gradleProject)
+ }
+
+ private static BuiltinIssueRegistry createIssueRegistry() {
+ return new LintGradleIssueRegistry()
+ }
+
+ // Issue registry when Lint is run inside Gradle: we replace the Gradle
+ // detector with a local implementation which directly references Groovy
+ // for parsing. In Studio on the other hand, the implementation is replaced
+ // by a PSI-based check. (This is necessary for now since we don't have a
+ // tool-agnostic API for the Groovy AST and we don't want to add a 6.3MB dependency
+ // on Groovy itself quite yet.
+ public static class LintGradleIssueRegistry extends BuiltinIssueRegistry {
+ private boolean mInitialized;
+
+ public LintGradleIssueRegistry() {
+ }
+
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ List<Issue> issues = super.getIssues();
+ if (!mInitialized) {
+ mInitialized = true;
+ for (Issue issue : issues) {
+ if (issue.getImplementation().getDetectorClass() == GradleDetector.class) {
+ issue.setImplementation(GroovyGradleDetector.IMPLEMENTATION);
+ }
+ }
+ }
+
+ return issues;
+ }
+ }
+
+ public static class ConfigAction implements TaskConfigAction<Lint> {
+
+ @NonNull
+ VariantScope scope
+
+ ConfigAction(@NonNull VariantScope scope) {
+ this.scope = scope
+ }
+
+ @Override
+ @NonNull
+ String getName() {
+ return scope.getTaskName("lint")
+ }
+
+ @Override
+ @NonNull
+ Class<Lint> getType() {
+ return Lint
+ }
+
+ @Override
+ void execute(@NonNull Lint lint) {
+ lint.setLintOptions(scope.globalScope.getExtension().lintOptions)
+ lint.setSdkHome(scope.globalScope.sdkHandler.getSdkFolder())
+ lint.androidBuilder = scope.globalScope.androidBuilder
+ lint.setVariantName(scope.variantConfiguration.fullName)
+ lint.setToolingRegistry(scope.globalScope.toolingRegistry)
+ lint.description = "Runs lint on the " + scope.variantConfiguration.fullName.capitalize() + " build."
+ lint.group = JavaBasePlugin.VERIFICATION_GROUP
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.java
new file mode 100644
index 0000000..d962044
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ManifestProcessorTask.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks;
+
+import com.android.build.gradle.internal.tasks.IncrementalTask;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Ordering;
+
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * A task that processes the manifest
+ */
+public abstract class ManifestProcessorTask extends IncrementalTask {
+
+ private File manifestOutputFile;
+
+ private File aaptFriendlyManifestOutputFile;
+
+ private File instantRunManifestOutputFile;
+
+ /**
+ * The processed Manifest.
+ */
+ @OutputFile
+ public File getManifestOutputFile() {
+ return manifestOutputFile;
+ }
+
+ public void setManifestOutputFile(File manifestOutputFile) {
+ this.manifestOutputFile = manifestOutputFile;
+ }
+
+ @OutputFile
+ @Optional
+ public File getInstantRunManifestOutputFile() {
+ return instantRunManifestOutputFile;
+ }
+
+ public void setInstantRunManifestOutputFile(File instantRunManifestOutputFile) {
+ this.instantRunManifestOutputFile = instantRunManifestOutputFile;
+ }
+
+ /**
+ * The aapt friendly processed Manifest. In case we are processing a library manifest, some
+ * placeholders may not have been resolved (and will be when the library is merged into the
+ * importing application). However, such placeholders keys are not friendly to aapt which
+ * flags some illegal characters. Such characters are replaced/encoded in this version.
+ */
+ @OutputFile
+ @Optional
+ public File getAaptFriendlyManifestOutputFile() {
+ return aaptFriendlyManifestOutputFile;
+ }
+
+ public void setAaptFriendlyManifestOutputFile(File aaptFriendlyManifestOutputFile) {
+ this.aaptFriendlyManifestOutputFile = aaptFriendlyManifestOutputFile;
+ }
+
+
+ /**
+ * Serialize a map key+value pairs into a comma separated list. Map elements are sorted to
+ * ensure stability between instances.
+ *
+ * @param mapToSerialize the map to serialize.
+ */
+ protected static String serializeMap(Map<String, Object> mapToSerialize) {
+ final Joiner keyValueJoiner = Joiner.on(":");
+ // transform the map on a list of key:value items, sort it and concatenate it.
+ return Joiner.on(",").join(
+ Ordering.natural().sortedCopy(Iterables.transform(
+ mapToSerialize.entrySet(),
+ new Function<Map.Entry<String, Object>, String>() {
+ @Override
+ public String apply(final Map.Entry<String, Object> input) {
+ return keyValueJoiner.join(input.getKey(), input.getValue());
+ }
+ })));
+ }
+
+ /**
+ * Returns the manifest processing output file. if an aapt friendly version was requested,
+ * return that otherwise return the actual output of the manifest merger tool directly.
+ */
+ public File getOutputFile() {
+ File aaptFriendlyManifest = getAaptFriendlyManifestOutputFile();
+ return aaptFriendlyManifest != null ? aaptFriendlyManifest : getManifestOutputFile();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MarkerFile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MarkerFile.java
new file mode 100644
index 0000000..dc57cc9
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MarkerFile.java
@@ -0,0 +1,51 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.utils.FileUtils;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.logging.Logger;
+
+/**
+ * Marker file that can be used by tasks to communicate. The marker file will contain a command.
+ */
+public class MarkerFile {
+
+ public enum Command {
+ RUN,
+ BLOCK
+ }
+
+ public static void createMarkerFile(@NonNull File markerFile, @NonNull Command command)
+ throws IOException {
+ if (markerFile.exists()) {
+ markerFile.delete();
+ }
+ FileUtils.createFile(markerFile, command.name());
+ }
+
+ @NonNull
+ public static Command readMarkerFile(@NonNull File markerFile) throws IOException {
+ String line = Files.readFirstLine(markerFile, Charset.defaultCharset());
+ return Command.valueOf(line);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.java
new file mode 100644
index 0000000..3096a37
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeManifests.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.dependency.ManifestDependencyImpl;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.model.ApiVersion;
+import com.android.manifmerger.ManifestMerger2;
+import com.android.manifmerger.ManifestMerger2.Invoker.Feature;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * A task that processes the manifest
+ */
+ at ParallelizableTask
+public class MergeManifests extends ManifestProcessorTask {
+
+ private String minSdkVersion;
+ private String targetSdkVersion;
+ private Integer maxSdkVersion;
+ private File reportFile;
+ private VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor>
+ variantConfiguration;
+ private ApkVariantOutputData variantOutputData;
+ private List<ManifestDependencyImpl> libraries;
+ private List<Feature> optionalFeatures;
+
+ @Override
+ protected void doFullTaskAction() {
+ getBuilder().mergeManifests(
+ getMainManifest(),
+ getManifestOverlays(),
+ getLibraries(),
+ getPackageOverride(),
+ getVersionCode(),
+ getVersionName(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getMaxSdkVersion(),
+ getManifestOutputFile().getAbsolutePath(),
+ // no aapt friendly merged manifest file necessary for applications.
+ null /* aaptFriendlyManifestOutputFile */,
+ getInstantRunManifestOutputFile().getAbsolutePath(),
+ ManifestMerger2.MergeType.APPLICATION,
+ variantConfiguration.getManifestPlaceholders(),
+ getOptionalFeatures(),
+ getReportFile());
+ }
+
+ @InputFile
+ public File getMainManifest() {
+ return variantConfiguration.getMainManifest();
+ }
+
+ @InputFiles
+ public List<File> getManifestOverlays() {
+ return variantConfiguration.getManifestOverlays();
+ }
+
+ @Input
+ @Optional
+ public String getPackageOverride() {
+ return variantConfiguration.getIdOverride();
+ }
+
+ @Input
+ public int getVersionCode() {
+ if (variantOutputData != null) {
+ return variantOutputData.getVersionCode();
+ }
+ return variantConfiguration.getVersionCode();
+ }
+
+ @Input
+ @Optional
+ public String getVersionName() {
+ if (variantOutputData != null) {
+ return variantOutputData.getVersionName();
+ }
+ return variantConfiguration.getVersionName();
+ }
+
+ /**
+ * Returns a serialized version of our map of key value pairs for placeholder substitution.
+ *
+ * This serialized form is only used by gradle to compare past and present tasks to determine
+ * whether a task need to be re-run or not.
+ */
+ @SuppressWarnings("unused")
+ @Input
+ @Optional
+ public String getManifestPlaceholders() {
+ return serializeMap(variantConfiguration.getManifestPlaceholders());
+ }
+
+ /**
+ * A synthetic input to allow gradle up-to-date checks to work.
+ *
+ * Since List<ManifestDependencyImpl> can't be used directly, as @Nested doesn't work on lists,
+ * this method gathers and returns the underlying manifest files.
+ */
+ @SuppressWarnings("unused")
+ @InputFiles
+ List<File> getLibraryManifests() {
+ List<ManifestDependencyImpl> libs = getLibraries();
+ if (libs == null || libs.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<File> files = Lists.newArrayListWithCapacity(libs.size() * 2);
+ for (ManifestDependencyImpl mdi : libs) {
+ files.addAll(mdi.getAllManifests());
+ }
+
+ return files;
+ }
+
+
+ @Input
+ @Optional
+ public String getMinSdkVersion() {
+ return minSdkVersion;
+ }
+
+ public void setMinSdkVersion(String minSdkVersion) {
+ this.minSdkVersion = minSdkVersion;
+ }
+
+ @Input
+ @Optional
+ public String getTargetSdkVersion() {
+ return targetSdkVersion;
+ }
+
+ public void setTargetSdkVersion(String targetSdkVersion) {
+ this.targetSdkVersion = targetSdkVersion;
+ }
+
+ @Input
+ @Optional
+ public Integer getMaxSdkVersion() {
+ return maxSdkVersion;
+ }
+
+ public void setMaxSdkVersion(Integer maxSdkVersion) {
+ this.maxSdkVersion = maxSdkVersion;
+ }
+
+ @OutputFile
+ @Optional
+ public File getReportFile() {
+ return reportFile;
+ }
+
+ public void setReportFile(File reportFile) {
+ this.reportFile = reportFile;
+ }
+
+ @Input
+ public List<Feature> getOptionalFeatures() {
+ return optionalFeatures;
+ }
+
+ public VariantConfiguration getVariantConfiguration() {
+ return variantConfiguration;
+ }
+
+ public void setVariantConfiguration(
+ VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> variantConfiguration) {
+ this.variantConfiguration = variantConfiguration;
+ }
+
+ public ApkVariantOutputData getVariantOutputData() {
+ return variantOutputData;
+ }
+
+ public void setVariantOutputData(ApkVariantOutputData variantOutputData) {
+ this.variantOutputData = variantOutputData;
+ }
+
+ public List<ManifestDependencyImpl> getLibraries() {
+ return libraries;
+ }
+
+ public void setLibraries(List<ManifestDependencyImpl> libraries) {
+ this.libraries = libraries;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<MergeManifests> {
+
+ private final VariantOutputScope scope;
+ private final List<Feature> optionalFeatures;
+
+ public ConfigAction(VariantOutputScope scope, List<Feature> optionalFeatures) {
+ this.scope = scope;
+ this.optionalFeatures = optionalFeatures;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("process", "Manifest");
+ }
+
+ @NonNull
+ @Override
+ public Class<MergeManifests> getType() {
+ return MergeManifests.class;
+ }
+
+ @Override
+ public void execute(@NonNull MergeManifests processManifestTask) {
+ BaseVariantOutputData variantOutputData = scope.getVariantOutputData();
+
+ final BaseVariantData<? extends BaseVariantOutputData> variantData =
+ scope.getVariantScope().getVariantData();
+ final VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> config =
+ variantData.getVariantConfiguration();
+
+ variantOutputData.manifestProcessorTask = processManifestTask;
+
+ processManifestTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ processManifestTask.setVariantName(config.getFullName());
+
+ processManifestTask.dependsOn(variantData.prepareDependenciesTask);
+ if (variantData.generateApkDataTask != null) {
+ processManifestTask.dependsOn(variantData.generateApkDataTask);
+ }
+ if (scope.getCompatibleScreensManifestTask() != null) {
+ processManifestTask.dependsOn(scope.getCompatibleScreensManifestTask().getName());
+ }
+
+ processManifestTask.setVariantConfiguration(config);
+ if (variantOutputData instanceof ApkVariantOutputData) {
+ processManifestTask.variantOutputData =
+ (ApkVariantOutputData) variantOutputData;
+ }
+
+ ConventionMappingHelper.map(processManifestTask, "libraries",
+ new Callable<List<ManifestDependencyImpl>>() {
+ @Override
+ public List<ManifestDependencyImpl> call() throws Exception {
+ List<ManifestDependencyImpl> manifests =
+ getManifestDependencies(config.getDirectLibraries());
+
+ if (variantData.generateApkDataTask != null &&
+ variantData.getVariantConfiguration().getBuildType().
+ isEmbedMicroApp()) {
+ manifests.add(new ManifestDependencyImpl(
+ variantData.generateApkDataTask.getManifestFile(),
+ Collections.<ManifestDependencyImpl>emptyList()));
+ }
+
+ if (scope.getCompatibleScreensManifestTask() != null) {
+ manifests.add(new ManifestDependencyImpl(
+ scope.getCompatibleScreensManifestFile(),
+ Collections.<ManifestDependencyImpl>emptyList()));
+ }
+
+ return manifests;
+ }
+ });
+
+ ConventionMappingHelper.map(processManifestTask, "minSdkVersion",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (scope.getGlobalScope().getAndroidBuilder().isPreviewTarget()) {
+ return scope.getGlobalScope().getAndroidBuilder()
+ .getTargetCodename();
+ }
+
+ ApiVersion minSdk = config.getMergedFlavor().getMinSdkVersion();
+ return minSdk == null ? null : minSdk.getApiString();
+ }
+ });
+
+ ConventionMappingHelper.map(processManifestTask, "targetSdkVersion",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (scope.getGlobalScope().getAndroidBuilder().isPreviewTarget()) {
+ return scope.getGlobalScope().getAndroidBuilder()
+ .getTargetCodename();
+ }
+ ApiVersion targetSdk = config.getMergedFlavor().getTargetSdkVersion();
+ return targetSdk == null ? null : targetSdk.getApiString();
+ }
+ });
+
+ ConventionMappingHelper.map(processManifestTask, "maxSdkVersion",
+ new Callable<Integer>() {
+ @Override
+ public Integer call() throws Exception {
+ if (scope.getGlobalScope().getAndroidBuilder().isPreviewTarget()) {
+ return null;
+ }
+ return config.getMergedFlavor().getMaxSdkVersion();
+ }
+ });
+
+ processManifestTask.setManifestOutputFile(scope.getManifestOutputFile());
+ processManifestTask.setInstantRunManifestOutputFile(
+ scope.getVariantScope().getInstantRunManifestOutputFile());
+
+ processManifestTask.setReportFile(scope.getVariantScope().getManifestReportFile());
+ processManifestTask.optionalFeatures = optionalFeatures;
+
+ }
+
+ @NonNull
+ private static List<ManifestDependencyImpl> getManifestDependencies(
+ List<LibraryDependency> libraries) {
+
+ List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size());
+
+ for (LibraryDependency lib : libraries) {
+ if (!lib.isOptional()) {
+ // get the dependencies
+ List<ManifestDependencyImpl> children =
+ getManifestDependencies(lib.getDependencies());
+ list.add(new ManifestDependencyImpl(lib.getName(), lib.getManifest(), children));
+ }
+ }
+
+ return list;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java
new file mode 100644
index 0000000..7f2e61a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeResources.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.IncrementalTask;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.model.VectorDrawablesOptions;
+import com.android.builder.png.QueuedCruncher;
+import com.android.builder.png.VectorDrawableRenderer;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.res2.FileStatus;
+import com.android.ide.common.res2.FileValidity;
+import com.android.ide.common.res2.GeneratedResourceSet;
+import com.android.ide.common.res2.MergedResourceWriter;
+import com.android.ide.common.res2.MergingException;
+import com.android.ide.common.res2.NoOpResourcePreprocessor;
+import com.android.ide.common.res2.ResourceMerger;
+import com.android.ide.common.res2.ResourcePreprocessor;
+import com.android.ide.common.res2.ResourceSet;
+import com.android.resources.Density;
+import com.android.sdklib.BuildToolInfo;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.regex.Pattern;
+
+ at ParallelizableTask
+public class MergeResources extends IncrementalTask {
+
+ private static final List<Pattern> IGNORED_WARNINGS = Lists.newArrayList(
+ Pattern.compile("Not recognizing known sRGB profile that has been edited"));
+
+ // ----- PUBLIC TASK API -----
+
+ /**
+ * Directory to write the merged resources to
+ */
+ private File outputDir;
+
+ private File generatedPngsOutputDir;
+
+ // ----- PRIVATE TASK API -----
+
+ /**
+ * Optional file to write any publicly imported resource types and names to
+ */
+ private File publicFile;
+
+ private boolean process9Patch;
+
+ private boolean crunchPng;
+
+ private boolean useNewCruncher;
+
+ private boolean validateEnabled;
+
+ private File blameLogFolder;
+ // actual inputs
+ private List<ResourceSet> inputResourceSets;
+
+ private final FileValidity<ResourceSet> fileValidity = new FileValidity<ResourceSet>();
+
+ private boolean disableVectorDrawables;
+
+ private Collection<String> generatedDensities;
+
+ private int minSdk;
+
+ @InputFiles
+ @SuppressWarnings("unused") // Fake input to detect changes. Not actually used by the task.
+ public Iterable<File> getRawInputFolders() {
+ return flattenSourceSets(getInputResourceSets());
+ }
+
+ @Input
+ public String getBuildToolsVersion() {
+ return getBuildTools().getRevision().toString();
+ }
+
+ @Override
+ protected boolean isIncremental() {
+ return true;
+ }
+
+ private PngCruncher getCruncher() {
+ if (getUseNewCruncher()) {
+ // At this point ensureTargetSetup() has been called, so no NPE below.
+ // noinspection ConstantConditions
+ BuildToolInfo buildTools = getBuilder().getTargetInfo().getBuildTools();
+ if (buildTools.getRevision().getMajor() >= 22) {
+ return QueuedCruncher.Builder.INSTANCE.newCruncher(
+ buildTools.getPath(BuildToolInfo.PathId.AAPT),
+ getFilteringLogger());
+ }
+ getLogger().info("New PNG cruncher will be enabled with build tools 22 and above.");
+ }
+
+ return getBuilder().getAaptCruncher(new LoggedProcessOutputHandler(getFilteringLogger()));
+ }
+
+ /**
+ * Returns an {@link ILogger} that degrades certain warnings to INFO level.
+ */
+ @NonNull
+ private ILogger getFilteringLogger() {
+ return new FilteringLogger(getILogger());
+ }
+
+ @Override
+ protected void doFullTaskAction() throws IOException {
+ ResourcePreprocessor preprocessor = getPreprocessor();
+
+ // this is full run, clean the previous output
+ File destinationDir = getOutputDir();
+ FileUtils.emptyFolder(destinationDir);
+
+ List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
+
+ // create a new merger and populate it with the sets.
+ ResourceMerger merger = new ResourceMerger(minSdk);
+
+ try {
+ for (ResourceSet resourceSet : resourceSets) {
+ resourceSet.loadFromFiles(getILogger());
+ merger.addDataSet(resourceSet);
+ }
+
+ // get the merged set and write it down.
+ MergedResourceWriter writer = new MergedResourceWriter(
+ destinationDir,
+ getCruncher(),
+ getCrunchPng(),
+ getProcess9Patch(),
+ getPublicFile(),
+ getBlameLogFolder(),
+ preprocessor);
+
+ merger.mergeData(writer, false /*doCleanUp*/);
+
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer);
+ } catch (MergingException e) {
+ System.out.println(e.getMessage());
+ merger.cleanBlob(getIncrementalFolder());
+ throw new ResourceException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
+ ResourcePreprocessor preprocessor = getPreprocessor();
+
+ // create a merger and load the known state.
+ ResourceMerger merger = new ResourceMerger(minSdk);
+ try {
+ if (!merger.loadFromBlob(getIncrementalFolder(), true /*incrementalState*/)) {
+ doFullTaskAction();
+ return;
+ }
+
+ for (ResourceSet resourceSet : merger.getDataSets()) {
+ resourceSet.setPreprocessor(preprocessor);
+ }
+
+ List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
+
+ // compare the known state to the current sets to detect incompatibility.
+ // This is in case there's a change that's too hard to do incrementally. In this case
+ // we'll simply revert to full build.
+ if (!merger.checkValidUpdate(resourceSets)) {
+ getLogger().info("Changed Resource sets: full task run!");
+ doFullTaskAction();
+ return;
+ }
+
+ // The incremental process is the following:
+ // Loop on all the changed files, find which ResourceSet it belongs to, then ask
+ // the resource set to update itself with the new file.
+ for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
+ File changedFile = entry.getKey();
+
+ merger.findDataSetContaining(changedFile, fileValidity);
+ if (fileValidity.getStatus() == FileValidity.FileStatus.UNKNOWN_FILE) {
+ doFullTaskAction();
+ return;
+ } else if (fileValidity.getStatus() == FileValidity.FileStatus.VALID_FILE) {
+ if (!fileValidity.getDataSet().updateWith(
+ fileValidity.getSourceFile(), changedFile, entry.getValue(),
+ getILogger())) {
+ getLogger().info(
+ String.format("Failed to process %s event! Full task run",
+ entry.getValue()));
+ doFullTaskAction();
+ return;
+ }
+ }
+ }
+
+ MergedResourceWriter writer = new MergedResourceWriter(
+ getOutputDir(),
+ getCruncher(),
+ getCrunchPng(),
+ getProcess9Patch(),
+ getPublicFile(),
+ getBlameLogFolder(),
+ preprocessor);
+ merger.mergeData(writer, false /*doCleanUp*/);
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer);
+ } catch (MergingException e) {
+ merger.cleanBlob(getIncrementalFolder());
+ throw new ResourceException(e.getMessage(), e);
+ } finally {
+ // some clean up after the task to help multi variant/module builds.
+ fileValidity.clear();
+ }
+ }
+
+ @NonNull
+ private ResourcePreprocessor getPreprocessor() {
+ // Only one pre-processor for now. The code will need slight changes when we add more.
+ Collection<String> generatedDensitiesNames = getGeneratedDensities();
+
+ if (isDisableVectorDrawables()) {
+ // If the user doesn't want any PNGs, leave the XML file alone as well.
+ return new NoOpResourcePreprocessor();
+ }
+
+ Collection<Density> densities = Lists.newArrayList();
+ for (String density : generatedDensitiesNames) {
+ densities.add(Density.getEnum(density));
+ }
+
+ return new VectorDrawableRenderer(
+ getMinSdk(),
+ getGeneratedPngsOutputDir(),
+ densities,
+ getILogger());
+ }
+
+ @NonNull
+ private List<ResourceSet> getConfiguredResourceSets(ResourcePreprocessor preprocessor) {
+ List<ResourceSet> resourceSets = Lists.newArrayList(getInputResourceSets());
+ List<ResourceSet> generatedSets = Lists.newArrayListWithCapacity(resourceSets.size());
+
+ for (ResourceSet resourceSet : resourceSets) {
+ resourceSet.setPreprocessor(preprocessor);
+ ResourceSet generatedSet = new GeneratedResourceSet(resourceSet);
+ resourceSet.setGeneratedSet(generatedSet);
+ generatedSets.add(generatedSet);
+ }
+
+ // Put all generated sets at the start of the list.
+ resourceSets.addAll(0, generatedSets);
+ return resourceSets;
+ }
+
+ public List<ResourceSet> getInputResourceSets() {
+ return inputResourceSets;
+ }
+
+ @SuppressWarnings("unused") // Property set with convention mapping.
+ public void setInputResourceSets(
+ List<ResourceSet> inputResourceSets) {
+ this.inputResourceSets = inputResourceSets;
+ }
+
+ public boolean getUseNewCruncher() {
+ return useNewCruncher;
+ }
+
+ public void setUseNewCruncher(boolean useNewCruncher) {
+ this.useNewCruncher = useNewCruncher;
+ }
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ public boolean getCrunchPng() {
+ return crunchPng;
+ }
+
+ public void setCrunchPng(boolean crunchPng) {
+ this.crunchPng = crunchPng;
+ }
+
+ public boolean getProcess9Patch() {
+ return process9Patch;
+ }
+
+ public void setProcess9Patch(boolean process9Patch) {
+ this.process9Patch = process9Patch;
+ }
+
+ @Optional
+ @OutputFile
+ public File getPublicFile() {
+ return publicFile;
+ }
+
+ public void setPublicFile(File publicFile) {
+ this.publicFile = publicFile;
+ }
+
+ // Synthetic input: the validation flag is set on the resource sets in ConfigAction.execute.
+ @SuppressWarnings("unused")
+ @Input
+ public boolean isValidateEnabled() {
+ return validateEnabled;
+ }
+
+ public void setValidateEnabled(boolean validateEnabled) {
+ this.validateEnabled = validateEnabled;
+ }
+
+ @OutputDirectory
+ @Optional
+ public File getBlameLogFolder() {
+ return blameLogFolder;
+ }
+
+ public void setBlameLogFolder(File blameLogFolder) {
+ this.blameLogFolder = blameLogFolder;
+ }
+
+ public File getGeneratedPngsOutputDir() {
+ return generatedPngsOutputDir;
+ }
+
+ public void setGeneratedPngsOutputDir(File generatedPngsOutputDir) {
+ this.generatedPngsOutputDir = generatedPngsOutputDir;
+ }
+
+ @Input
+ public Collection<String> getGeneratedDensities() {
+ return generatedDensities;
+ }
+
+ @Input
+ public int getMinSdk() {
+ return minSdk;
+ }
+
+ public void setMinSdk(int minSdk) {
+ this.minSdk = minSdk;
+ }
+
+ public void setGeneratedDensities(Collection<String> generatedDensities) {
+ this.generatedDensities = generatedDensities;
+ }
+
+ @Input
+ public boolean isDisableVectorDrawables() {
+ return disableVectorDrawables;
+ }
+
+ public void setDisableVectorDrawables(boolean disableVectorDrawables) {
+ this.disableVectorDrawables = disableVectorDrawables;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<MergeResources> {
+
+ @NonNull
+ private final VariantScope scope;
+
+ @NonNull
+ private final String taskNamePrefix;
+
+ @Nullable
+ private final File outputLocation;
+
+ private final boolean includeDependencies;
+
+ private final boolean process9Patch;
+
+ public ConfigAction(
+ @NonNull VariantScope scope,
+ @NonNull String taskNamePrefix,
+ @Nullable File outputLocation,
+ boolean includeDependencies,
+ boolean process9Patch) {
+ this.scope = scope;
+ this.taskNamePrefix = taskNamePrefix;
+ this.outputLocation = outputLocation;
+ this.includeDependencies = includeDependencies;
+ this.process9Patch = process9Patch;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName(taskNamePrefix, "Resources");
+ }
+
+ @NonNull
+ @Override
+ public Class<MergeResources> getType() {
+ return MergeResources.class;
+ }
+
+ @Override
+ public void execute(@NonNull MergeResources mergeResourcesTask) {
+ final BaseVariantData<? extends BaseVariantOutputData> variantData =
+ scope.getVariantData();
+ final AndroidConfig extension = scope.getGlobalScope().getExtension();
+
+ mergeResourcesTask.setMinSdk(
+ variantData.getVariantConfiguration().getMinSdkVersion().getApiLevel());
+
+ mergeResourcesTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ mergeResourcesTask.setVariantName(scope.getVariantConfiguration().getFullName());
+ mergeResourcesTask.setIncrementalFolder(scope.getIncrementalDir(getName()));
+
+ // Libraries use this task twice, once for compilation (with dependencies),
+ // where blame is useful, and once for packaging where it is not.
+ if (includeDependencies) {
+ mergeResourcesTask.setBlameLogFolder(scope.getResourceBlameLogDir());
+ }
+ mergeResourcesTask.setProcess9Patch(process9Patch);
+ mergeResourcesTask.setCrunchPng(extension.getAaptOptions().getCruncherEnabled());
+
+ VectorDrawablesOptions vectorDrawablesOptions = variantData
+ .getVariantConfiguration()
+ .getMergedFlavor()
+ .getVectorDrawables();
+
+ Set<String> generatedDensities = vectorDrawablesOptions.getGeneratedDensities();
+
+ mergeResourcesTask.setGeneratedDensities(
+ Objects.firstNonNull(generatedDensities, Collections.<String>emptySet()));
+
+ mergeResourcesTask.setDisableVectorDrawables(
+ vectorDrawablesOptions.getUseSupportLibrary()
+ || mergeResourcesTask.getGeneratedDensities().isEmpty());
+
+ mergeResourcesTask.setUseNewCruncher(extension.getAaptOptions().getUseNewCruncher());
+
+ final boolean validateEnabled = AndroidGradleOptions.isResourceValidationEnabled(
+ scope.getGlobalScope().getProject());
+
+ mergeResourcesTask.setValidateEnabled(validateEnabled);
+
+ ConventionMappingHelper.map(mergeResourcesTask, "inputResourceSets",
+ new Callable<List<ResourceSet>>() {
+ @Override
+ public List<ResourceSet> call() throws Exception {
+ List<File> generatedResFolders = Lists.newArrayList(
+ scope.getRenderscriptResOutputDir(),
+ scope.getGeneratedResOutputDir());
+ if (variantData.getExtraGeneratedResFolders() != null) {
+ generatedResFolders.addAll(
+ variantData.getExtraGeneratedResFolders());
+ }
+ if (variantData.generateApkDataTask != null &&
+ variantData.getVariantConfiguration().getBuildType()
+ .isEmbedMicroApp()) {
+ generatedResFolders.add(
+ variantData.generateApkDataTask.getResOutputDir());
+ }
+
+ return variantData.getVariantConfiguration().getResourceSets(
+ generatedResFolders, includeDependencies, validateEnabled);
+ }
+ });
+
+ mergeResourcesTask.setOutputDir(
+ outputLocation != null
+ ? outputLocation
+ : scope.getDefaultMergeResourcesOutputDir());
+
+ mergeResourcesTask.setGeneratedPngsOutputDir(scope.getGeneratedPngsOutputDir());
+
+ variantData.mergeResourcesTask = mergeResourcesTask;
+ }
+ }
+
+ private static class FilteringLogger implements ILogger {
+
+ private final ILogger mDelegate;
+
+ FilteringLogger(ILogger delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) {
+ if (msgFormat != null && isIgnored(msgFormat, args)) {
+ mDelegate.info(Strings.nullToEmpty(msgFormat), args);
+ } else {
+ mDelegate.error(t, msgFormat, args);
+ }
+ }
+
+ @Override
+ public void warning(@NonNull String msgFormat, Object... args) {
+ if (isIgnored(msgFormat, args)) {
+ mDelegate.info(msgFormat, args);
+ } else {
+ mDelegate.warning(msgFormat, args);
+ }
+ }
+
+ @Override
+ public void info(@NonNull String msgFormat, Object... args) {
+ mDelegate.info(msgFormat, args);
+ }
+
+ @Override
+ public void verbose(@NonNull String msgFormat, Object... args) {
+ mDelegate.verbose(msgFormat, args);
+ }
+
+ private boolean isIgnored(String msgFormat, Object... args) {
+ String message = String.format(msgFormat, args);
+ for (Pattern pattern : IGNORED_WARNINGS) {
+ if (pattern.matcher(message).find()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeSourceSetFolders.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeSourceSetFolders.groovy
new file mode 100644
index 0000000..3c694af
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/MergeSourceSetFolders.groovy
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.scope.ConventionMappingHelper
+import com.android.build.gradle.internal.scope.TaskConfigAction
+import com.android.build.gradle.internal.scope.VariantScope
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.BaseVariantOutputData
+import com.android.builder.core.VariantConfiguration
+import com.android.builder.core.VariantType
+import com.android.ide.common.res2.AssetMerger
+import com.android.ide.common.res2.AssetSet
+import com.android.ide.common.res2.FileStatus
+import com.android.ide.common.res2.FileValidity
+import com.android.ide.common.res2.MergedAssetWriter
+import com.android.ide.common.res2.MergingException
+import com.android.utils.FileUtils
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.ParallelizableTask
+
+ at ParallelizableTask
+public class MergeSourceSetFolders extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File outputDir
+
+ // ----- PRIVATE TASK API -----
+
+ // fake input to detect changes. Not actually used by the task
+ @InputFiles
+ Iterable<File> getRawInputFolders() {
+ return flattenSourceSets(getInputDirectorySets())
+ }
+
+ // actual inputs
+ List<AssetSet> inputDirectorySets
+
+ private final FileValidity<AssetSet> fileValidity = new FileValidity<AssetSet>();
+
+ @Override
+ protected boolean isIncremental() {
+ return true
+ }
+
+ @Override
+ protected void doFullTaskAction() {
+ // this is full run, clean the previous output
+ File destinationDir = getOutputDir()
+ FileUtils.emptyFolder(destinationDir)
+
+ List<AssetSet> assetSets = getInputDirectorySets()
+
+ // create a new merger and populate it with the sets.
+ AssetMerger merger = new AssetMerger()
+
+ try {
+ for (AssetSet assetSet : assetSets) {
+ // set needs to be loaded.
+ assetSet.loadFromFiles(getILogger())
+ merger.addDataSet(assetSet)
+ }
+
+ // get the merged set and write it down.
+ MergedAssetWriter writer = new MergedAssetWriter(destinationDir)
+
+ merger.mergeData(writer, false /*doCleanUp*/)
+
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer)
+ } catch (MergingException e) {
+ println e.getMessage()
+ merger.cleanBlob(getIncrementalFolder())
+ throw new ResourceException(e.getMessage(), e)
+ }
+ }
+
+ @Override
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
+ // create a merger and load the known state.
+ AssetMerger merger = new AssetMerger()
+ try {
+ if (!merger.loadFromBlob(getIncrementalFolder(), true /*incrementalState*/)) {
+ doFullTaskAction()
+ return
+ }
+
+ // compare the known state to the current sets to detect incompatibility.
+ // This is in case there's a change that's too hard to do incrementally. In this case
+ // we'll simply revert to full build.
+ List<AssetSet> assetSets = getInputDirectorySets()
+
+ if (!merger.checkValidUpdate(assetSets)) {
+ project.logger.info("Changed Asset sets: full task run!")
+ doFullTaskAction()
+ return
+ }
+
+ // The incremental process is the following:
+ // Loop on all the changed files, find which ResourceSet it belongs to, then ask
+ // the resource set to update itself with the new file.
+ for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
+ File changedFile = entry.getKey()
+
+ // Ignore directories.
+ if (changedFile.isDirectory()) {
+ continue;
+ }
+
+ merger.findDataSetContaining(changedFile, fileValidity)
+ if (fileValidity.status == FileValidity.FileStatus.UNKNOWN_FILE) {
+ doFullTaskAction()
+ return
+ } else if (fileValidity.status == FileValidity.FileStatus.VALID_FILE) {
+ if (!fileValidity.dataSet.updateWith(
+ fileValidity.sourceFile, changedFile, entry.getValue(), getILogger())) {
+ project.logger.info(
+ String.format("Failed to process %s event! Full task run",
+ entry.getValue()))
+ doFullTaskAction()
+ return
+ }
+ }
+ }
+
+ MergedAssetWriter writer = new MergedAssetWriter(getOutputDir())
+
+ merger.mergeData(writer, false /*doCleanUp*/)
+
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer)
+ } catch (MergingException e) {
+ println e.getMessage()
+ merger.cleanBlob(getIncrementalFolder())
+ throw new ResourceException(e.getMessage(), e)
+ } finally {
+ // some clean up after the task to help multi variant/module builds.
+ fileValidity.clear();
+ }
+ }
+
+ protected abstract static class ConfigAction implements TaskConfigAction<MergeSourceSetFolders> {
+
+ @NonNull
+ protected VariantScope scope
+
+ protected ConfigAction(@NonNull VariantScope scope) {
+ this.scope = scope
+ }
+
+ @NonNull
+ @Override
+ Class<MergeSourceSetFolders> getType() {
+ return MergeSourceSetFolders
+ }
+
+ @Override
+ void execute(@NonNull MergeSourceSetFolders mergeAssetsTask) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+
+ mergeAssetsTask.androidBuilder = scope.globalScope.androidBuilder
+ mergeAssetsTask.setVariantName(variantConfig.getFullName())
+ mergeAssetsTask.incrementalFolder = scope.getIncrementalDir(getName())
+ }
+ }
+
+ public static class MergeAssetConfigAction extends ConfigAction {
+
+ MergeAssetConfigAction(@NonNull VariantScope scope) {
+ super(scope);
+ }
+
+ @NonNull
+ @Override
+ String getName() {
+ return scope.getTaskName("merge", "Assets");
+ }
+
+ @Override
+ void execute(@NonNull MergeSourceSetFolders mergeAssetsTask) {
+ super.execute(mergeAssetsTask);
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+
+ variantData.mergeAssetsTask = mergeAssetsTask
+
+ boolean includeDependencies = variantConfig.type != VariantType.LIBRARY
+ ConventionMappingHelper.map(mergeAssetsTask, "inputDirectorySets") {
+ def generatedAssets = []
+ if (variantData.copyApkTask != null) {
+ generatedAssets.add(variantData.copyApkTask.destinationDir)
+ }
+ variantConfig.getAssetSets(generatedAssets, includeDependencies)
+ }
+ ConventionMappingHelper.map(mergeAssetsTask, "outputDir") {
+ scope.getMergeAssetsOutputDir()
+ }
+ }
+ }
+
+ public static class MergeJniLibFoldersConfigAction extends ConfigAction {
+
+ MergeJniLibFoldersConfigAction(@NonNull VariantScope scope) {
+ super(scope);
+ }
+
+ @NonNull
+ @Override
+ String getName() {
+ return scope.getTaskName("merge", "JniLibFolders");
+ }
+
+ @Override
+ void execute(@NonNull MergeSourceSetFolders mergeAssetsTask) {
+ super.execute(mergeAssetsTask);
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+
+ ConventionMappingHelper.map(mergeAssetsTask, "inputDirectorySets") {
+ variantConfig.getJniLibsSets()
+ }
+
+ ConventionMappingHelper.map(mergeAssetsTask, "outputDir") {
+ scope.getMergeNativeLibsOutputDir()
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy
new file mode 100644
index 0000000..90a3c86
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+import com.android.annotations.NonNull
+import com.android.build.gradle.AndroidGradleOptions
+import com.android.build.gradle.internal.dsl.CoreNdkOptions
+import com.android.build.gradle.internal.tasks.NdkTask
+import com.android.ide.common.process.LoggedProcessOutputHandler
+import com.android.ide.common.process.ProcessInfoBuilder
+import com.android.ide.common.process.ProcessOutputHandler
+import com.android.sdklib.IAndroidTarget
+import com.android.utils.FileUtils
+import com.google.common.base.Charsets
+import com.google.common.base.Joiner
+import com.google.common.collect.Lists
+import com.google.common.io.Files
+import org.gradle.api.GradleException
+import org.gradle.api.file.FileTree
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.SkipWhenEmpty
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs
+import org.gradle.api.tasks.util.PatternSet
+
+import static com.android.SdkConstants.CURRENT_PLATFORM
+import static com.android.SdkConstants.PLATFORM_WINDOWS
+import static com.android.build.gradle.AndroidGradleOptions.USE_DEPRECATED_NDK
+
+ at ParallelizableTask
+class NdkCompile extends NdkTask {
+
+ List<File> sourceFolders
+
+ @OutputFile
+ File generatedMakefile
+
+ @Input
+ boolean debuggable
+
+ @OutputDirectory
+ File soFolder
+
+ @OutputDirectory
+ File objFolder
+
+ @Optional
+ @Input
+ File ndkDirectory
+
+ @Input
+ boolean ndkRenderScriptMode
+
+ @Input
+ boolean ndkCygwinMode
+
+ @Input
+ boolean isForTesting
+
+ @SkipWhenEmpty
+ @InputFiles
+ FileTree getSource() {
+ FileTree src = null
+ List<File> sources = getSourceFolders()
+ if (!sources.isEmpty()) {
+ src = getProject().files(new ArrayList<Object>(sources)).getAsFileTree()
+ }
+ return src == null ? getProject().files().getAsFileTree() : src
+ }
+
+ @TaskAction
+ void taskAction(IncrementalTaskInputs inputs) {
+ if (!AndroidGradleOptions.useDeprecatedNdk(project)) {
+ // Normally, we would catch the user when they try to configure the NDK, but NDK do
+ // not need to be configured by default. Throw this exception during task execution in
+ // case we miss it.
+ throw new RuntimeException(
+ "Error: NDK integration is deprecated in the current plugin. Consider trying " +
+ "the new experimental plugin. For details, see " +
+ "http://tools.android.com/tech-docs/new-build-system/gradle-experimental. " +
+ "Set \"$USE_DEPRECATED_NDK=true\" in gradle.properties to " +
+ "continue using the current NDK integration.");
+ }
+
+
+ if (isNdkOptionUnset()) {
+ logger.warn("Warning: Native C/C++ source code is found, but it seems that NDK " +
+ "option is not configured. Note that if you have an Android.mk, it is not " +
+ "used for compilation. The recommended workaround is to remove the default " +
+ "jni source code directory by adding: \n " +
+ "android {\n" +
+ " sourceSets {\n" +
+ " main {\n" +
+ " jni.srcDirs = []\n" +
+ " }\n" +
+ " }\n" +
+ "}\n" +
+ "to build.gradle, manually compile the code with ndk-build, " +
+ "and then place the resulting shared object in src/main/jniLibs.");
+ }
+
+ FileTree sourceFileTree = getSource()
+ Set<File> sourceFiles = sourceFileTree.matching(new PatternSet().exclude("**/*.h")).files
+ File makefile = getGeneratedMakefile()
+
+ if (sourceFiles.isEmpty()) {
+ makefile.delete()
+ FileUtils.emptyFolder(getSoFolder())
+ FileUtils.emptyFolder(getObjFolder())
+ return
+ }
+
+ if (ndkDirectory == null || !ndkDirectory.isDirectory()) {
+ throw new GradleException(
+ "NDK not configured.\n" +
+ "Download the NDK from http://developer.android.com/tools/sdk/ndk/." +
+ "Then add ndk.dir=path/to/ndk in local.properties.\n" +
+ "(On Windows, make sure you escape backslashes, e.g. C:\\\\ndk rather than C:\\ndk)");
+ }
+
+ boolean generateMakefile = false
+
+ if (!inputs.isIncremental()) {
+ project.logger.info("Unable do incremental execution: full task run")
+ generateMakefile = true
+ FileUtils.emptyFolder(getSoFolder())
+ FileUtils.emptyFolder(getObjFolder())
+ } else {
+ // look for added or removed files *only*
+
+ //noinspection GroovyAssignabilityCheck
+ inputs.outOfDate { change ->
+ if (change.isAdded()) {
+ generateMakefile = true
+ }
+ }
+
+ //noinspection GroovyAssignabilityCheck
+ inputs.removed { change ->
+ generateMakefile = true
+ }
+ }
+
+ if (generateMakefile) {
+ writeMakefile(sourceFiles, makefile)
+ }
+
+ // now build
+ runNdkBuild(ndkDirectory, makefile)
+ }
+
+ private void writeMakefile(@NonNull Set<File> sourceFiles, @NonNull File makefile) {
+ CoreNdkOptions ndk = getNdkConfig()
+
+ StringBuilder sb = new StringBuilder()
+
+ sb.append(
+ 'LOCAL_PATH := $(call my-dir)\n' +
+ 'include \$(CLEAR_VARS)\n\n')
+
+ String moduleName = ndk.moduleName != null ? ndk.moduleName : project.name
+ if (isForTesting) {
+ moduleName = moduleName + "_test"
+ }
+
+ sb.append('LOCAL_MODULE := ').append(moduleName).append('\n')
+
+ if (ndk.cFlags != null) {
+ sb.append('LOCAL_CFLAGS := ').append(ndk.cFlags).append('\n')
+ }
+
+ // To support debugging from Android Studio.
+ sb.append("LOCAL_LDFLAGS := -Wl,--build-id\n")
+
+ List<String> fullLdlibs = Lists.newArrayList()
+ if (ndk.ldLibs != null) {
+ fullLdlibs.addAll(ndk.ldLibs)
+ }
+ if (getNdkRenderScriptMode()) {
+ fullLdlibs.add("dl")
+ fullLdlibs.add("log")
+ fullLdlibs.add("jnigraphics")
+ fullLdlibs.add("RScpp_static")
+ }
+
+ if (!fullLdlibs.isEmpty()) {
+ sb.append('LOCAL_LDLIBS := \\\n')
+ for (String lib : fullLdlibs) {
+ sb.append('\t-l') .append(lib).append(' \\\n')
+ }
+ sb.append('\n')
+ }
+
+ sb.append('LOCAL_SRC_FILES := \\\n')
+ for (File sourceFile : sourceFiles) {
+ sb.append('\t').append(sourceFile.absolutePath).append(' \\\n')
+ }
+ sb.append('\n')
+
+ for (File sourceFolder : getSourceFolders()) {
+ sb.append("LOCAL_C_INCLUDES += ${sourceFolder.absolutePath}\n")
+ }
+
+ if (getNdkRenderScriptMode()) {
+ sb.append('LOCAL_LDFLAGS += -L$(call host-path,$(TARGET_C_INCLUDES)/../lib/rs)\n')
+
+ sb.append('LOCAL_C_INCLUDES += $(TARGET_C_INCLUDES)/rs/cpp\n')
+ sb.append('LOCAL_C_INCLUDES += $(TARGET_C_INCLUDES)/rs\n')
+ sb.append('LOCAL_C_INCLUDES += $(TARGET_OBJS)/$(LOCAL_MODULE)\n')
+ }
+
+ sb.append(
+ '\ninclude \$(BUILD_SHARED_LIBRARY)\n')
+
+ Files.write(sb.toString(), makefile, Charsets.UTF_8)
+ }
+
+ private void runNdkBuild(@NonNull File ndkLocation, @NonNull File makefile) {
+ CoreNdkOptions ndk = getNdkConfig()
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder()
+
+ String exe = ndkLocation.absolutePath + File.separator + "ndk-build"
+ if (CURRENT_PLATFORM == PLATFORM_WINDOWS && !ndkCygwinMode) {
+ exe += ".cmd"
+ }
+ builder.setExecutable(exe)
+
+ builder.addArgs(
+ "NDK_PROJECT_PATH=null",
+ "APP_BUILD_SCRIPT=" + makefile.absolutePath)
+
+ // target
+ IAndroidTarget target = getBuilder().getTarget()
+ if (!target.isPlatform()) {
+ target = target.parent
+ }
+ builder.addArgs("APP_PLATFORM=" + target.hashString())
+
+ // temp out
+ builder.addArgs("NDK_OUT=" + getObjFolder().absolutePath)
+
+ // libs out
+ builder.addArgs("NDK_LIBS_OUT=" + getSoFolder().absolutePath)
+
+ // debug builds
+ if (getDebuggable()) {
+ builder.addArgs("NDK_DEBUG=1")
+ }
+
+ if (ndk.getStl() != null) {
+ builder.addArgs("APP_STL=" + ndk.getStl())
+ }
+
+ Set<String> abiFilters = ndk.abiFilters
+ if (abiFilters != null && !abiFilters.isEmpty()) {
+ if (abiFilters.size() == 1) {
+ builder.addArgs("APP_ABI=" + abiFilters.iterator().next())
+ } else {
+ Joiner joiner = Joiner.on(',').skipNulls()
+ builder.addArgs("APP_ABI=" + joiner.join(abiFilters.iterator()))
+ }
+ } else {
+ builder.addArgs("APP_ABI=all")
+ }
+
+ if (ndk.getJobs() != null) {
+ builder.addArgs("-j" + ndk.getJobs());
+ }
+
+ ProcessOutputHandler handler = new LoggedProcessOutputHandler(getBuilder().getLogger());
+ getBuilder().executeProcess(builder.createProcess(), handler)
+ .rethrowFailure().assertNormalExitValue()
+ }
+
+ private boolean isNdkOptionUnset() {
+ // If none of the NDK options are set, then it is likely that NDK is not configured.
+ return (getModuleName() == null &&
+ getcFlags() == null &&
+ getLdLibs() == null &&
+ getAbiFilters() == null &&
+ getStl() == null);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.java
new file mode 100644
index 0000000..cb833db
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.java
@@ -0,0 +1,547 @@
+package com.android.build.gradle.tasks;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.internal.annotations.ApkFile;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dsl.AbiSplitOptions;
+import com.android.build.gradle.internal.dsl.CoreSigningConfig;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.pipeline.ExtendedContentType;
+import com.android.build.gradle.internal.pipeline.FilterableStreamCollection;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.internal.tasks.IncrementalTask;
+import com.android.build.gradle.internal.tasks.ValidateSigningTask;
+import com.android.build.gradle.internal.transforms.InstantRunSlicer;
+import com.android.build.gradle.internal.variant.ApkVariantData;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.utils.StringHelper;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+import org.gradle.api.Task;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.tooling.BuildException;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+ at ParallelizableTask
+public class PackageApplication extends IncrementalTask implements FileSupplier {
+
+ public enum DexPackagingPolicy {
+ /**
+ * Standard Dex packaging policy, all dex files will be packaged at the root of the APK.
+ */
+ STANDARD,
+
+ /**
+ * InstantRun specific Dex packaging policy, all dex files with a name containing
+ * {@link InstantRunSlicer#MAIN_SLICE_NAME} will be packaged at the root of the APK while
+ * all other dex files will be packaged in a instant-run.zip itself packaged at the root
+ * of the APK.
+ */
+ INSTANT_RUN
+ }
+
+ public static final String INSTANT_RUN_PACKAGES_PREFIX = "instant-run";
+
+ private boolean useOldPackaging;
+
+ public static final FilterableStreamCollection.StreamFilter sDexFilter =
+ new TransformManager.StreamFilter() {
+ @Override
+ public boolean accept(@NonNull Set<ContentType> types, @NonNull Set<Scope> scopes) {
+ return types.contains(ExtendedContentType.DEX);
+ }
+ };
+
+ public static final FilterableStreamCollection.StreamFilter sResFilter =
+ new TransformManager.StreamFilter() {
+ @Override
+ public boolean accept(@NonNull Set<ContentType> types, @NonNull Set<Scope> scopes) {
+ return types.contains(QualifiedContent.DefaultContentType.RESOURCES) &&
+ !scopes.contains(Scope.PROVIDED_ONLY) &&
+ !scopes.contains(Scope.TESTED_CODE);
+ }
+ };
+
+ public static final FilterableStreamCollection.StreamFilter sNativeLibsFilter =
+ new TransformManager.StreamFilter() {
+ @Override
+ public boolean accept(@NonNull Set<ContentType> types, @NonNull Set<Scope> scopes) {
+ return types.contains(ExtendedContentType.NATIVE_LIBS) &&
+ !scopes.contains(Scope.PROVIDED_ONLY) &&
+ !scopes.contains(Scope.TESTED_CODE);
+ }
+ };
+
+ // ----- PUBLIC TASK API -----
+
+ @InputFile
+ public File getResourceFile() {
+ return resourceFile;
+ }
+
+ public void setResourceFile(File resourceFile) {
+ this.resourceFile = resourceFile;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ @Input
+ public Set<String> getAbiFilters() {
+ return abiFilters;
+ }
+
+ public void setAbiFilters(Set<String> abiFilters) {
+ this.abiFilters = abiFilters;
+ }
+
+ // ----- PRIVATE TASK API -----
+
+ @InputFiles
+ @Optional
+ public Collection<File> getJavaResourceFiles() {
+ return javaResourceFiles;
+ }
+ @InputFiles
+ @Optional
+ public Collection<File> getJniFolders() {
+ return jniFolders;
+ }
+
+
+ private File resourceFile;
+
+ private Set<File> dexFolders;
+ @InputFiles
+ public Set<File> getDexFolders() {
+ return dexFolders;
+ }
+
+ /** list of folders and/or jars that contain the merged java resources. */
+ private Set<File> javaResourceFiles;
+ private Set<File> jniFolders;
+
+ @ApkFile
+ private File outputFile;
+
+ private Set<String> abiFilters;
+
+ private boolean jniDebugBuild;
+
+ private CoreSigningConfig signingConfig;
+
+ private PackagingOptions packagingOptions;
+
+ private ApiVersion minSdkVersion;
+
+ private InstantRunBuildContext instantRunContext;
+
+ private File instantRunSupportDir;
+
+ @Input
+ public boolean getJniDebugBuild() {
+ return jniDebugBuild;
+ }
+
+ public boolean isJniDebugBuild() {
+ return jniDebugBuild;
+ }
+
+ public void setJniDebugBuild(boolean jniDebugBuild) {
+ this.jniDebugBuild = jniDebugBuild;
+ }
+
+ @Nested
+ @Optional
+ public CoreSigningConfig getSigningConfig() {
+ return signingConfig;
+ }
+
+ public void setSigningConfig(CoreSigningConfig signingConfig) {
+ this.signingConfig = signingConfig;
+ }
+
+ @Nested
+ public PackagingOptions getPackagingOptions() {
+ return packagingOptions;
+ }
+
+ public void setPackagingOptions(PackagingOptions packagingOptions) {
+ this.packagingOptions = packagingOptions;
+ }
+
+ @Input
+ public int getMinSdkVersion() {
+ return this.minSdkVersion.getApiLevel();
+ }
+
+ public void setMinSdkVersion(ApiVersion version) {
+ this.minSdkVersion = version;
+ }
+
+ @InputFile
+ public File getMarkerFile() {
+ return markerFile;
+ }
+
+ private File markerFile;
+
+ DexPackagingPolicy dexPackagingPolicy;
+
+ @Input
+ String getDexPackagingPolicy() {
+ return dexPackagingPolicy.toString();
+ }
+
+ @Override
+ protected void doFullTaskAction() {
+
+ // if the blocker file is there, do not run.
+ if (getMarkerFile().exists()) {
+ try {
+ if (MarkerFile.readMarkerFile(getMarkerFile()) == MarkerFile.Command.BLOCK) {
+ return;
+ }
+ } catch (IOException e) {
+ getLogger().warn("Cannot read marker file, proceed with execution", e);
+ }
+ }
+
+ try {
+
+ ImmutableSet.Builder<File> dexFoldersForApk = ImmutableSet.builder();
+ ImmutableList.Builder<File> javaResourcesForApk = ImmutableList.builder();
+
+ Collection<File> javaResourceFiles = getJavaResourceFiles();
+ if (javaResourceFiles != null) {
+ javaResourcesForApk.addAll(javaResourceFiles);
+ }
+ switch(dexPackagingPolicy) {
+ case INSTANT_RUN:
+ File zippedDexes = zipDexesForInstantRun(getDexFolders(), dexFoldersForApk);
+ javaResourcesForApk.add(zippedDexes);
+ break;
+ case STANDARD:
+ dexFoldersForApk.addAll(getDexFolders());
+ break;
+ default:
+ throw new RuntimeException(
+ "Unhandled DexPackagingPolicy : " + getDexPackagingPolicy());
+ }
+
+ getBuilder().packageApk(
+ getResourceFile().getAbsolutePath(),
+ dexFoldersForApk.build(),
+ javaResourcesForApk.build(),
+ getJniFolders(),
+ getAbiFilters(),
+ getJniDebugBuild(),
+ getSigningConfig(),
+ getOutputFile().getAbsolutePath(),
+ getMinSdkVersion());
+ } catch (DuplicateFileException e) {
+ Logger logger = getLogger();
+ logger.error("Error: duplicate files during packaging of APK " + getOutputFile()
+ .getAbsolutePath());
+ logger.error("\tPath in archive: " + e.getArchivePath());
+ int index = 1;
+ for (File file : e.getSourceFiles()) {
+ logger.error("\tOrigin " + (index++) + ": " + file);
+ }
+ logger.error("You can ignore those files in your build.gradle:");
+ logger.error("\tandroid {");
+ logger.error("\t packagingOptions {");
+ logger.error("\t exclude \'" + e.getArchivePath() + "\'");
+ logger.error("\t }");
+ logger.error("\t}");
+ throw new BuildException(e.getMessage(), e);
+ } catch (Exception e) {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ Throwable rootCause = Throwables.getRootCause(e);
+ if (rootCause instanceof NoSuchAlgorithmException) {
+ throw new BuildException(
+ rootCause.getMessage() + ": try using a newer JVM to build your application.",
+ rootCause);
+ }
+ throw new BuildException(e.getMessage(), e);
+ }
+ // mark this APK production, this will eventually be saved when instant-run is enabled.
+ // this might get overriden if the apk is signed/aligned.
+ try {
+ instantRunContext.addChangedFile(InstantRunBuildContext.FileType.MAIN,
+ getOutputFile());
+ } catch (IOException e) {
+ throw new BuildException(e.getMessage(), e);
+ }
+ }
+
+ private File zipDexesForInstantRun(Iterable<File> dexFolders,
+ ImmutableSet.Builder<File> dexFoldersForApk)
+ throws IOException {
+
+ File tmpZipFile = new File(instantRunSupportDir, "classes.zip");
+ Files.createParentDirs(tmpZipFile);
+ ZipOutputStream zipFile = new ZipOutputStream(
+ new BufferedOutputStream(new FileOutputStream(tmpZipFile)));
+ // no need to compress a zip, the APK itself gets compressed.
+ zipFile.setLevel(0);
+
+ try {
+ for (File dexFolder : dexFolders) {
+ if (dexFolder.getName().contains(INSTANT_RUN_PACKAGES_PREFIX)) {
+ dexFoldersForApk.add(dexFolder);
+ } else {
+ for (File file : Files.fileTreeTraverser().breadthFirstTraversal(dexFolder)) {
+ if (file.isFile() && file.getName().endsWith(SdkConstants.DOT_DEX)) {
+ // There are several pieces of code in the runtime library which depends on
+ // this exact pattern, so it should not be changed without thorough testing
+ // (it's basically part of the contract).
+ String entryName = file.getParentFile().getName() + "-" + file.getName();
+ zipFile.putNextEntry(new ZipEntry(entryName));
+ try {
+ Files.copy(file, zipFile);
+ } finally {
+ zipFile.closeEntry();
+ }
+ }
+
+ }
+ }
+ }
+ } finally {
+ zipFile.close();
+ }
+
+ // now package that zip file as a zip since this is what the packager is expecting !
+ File finalResourceFile = new File(instantRunSupportDir, "resources.zip");
+ zipFile = new ZipOutputStream(new BufferedOutputStream(
+ new FileOutputStream(finalResourceFile)));
+ try {
+ zipFile.putNextEntry(new ZipEntry("instant-run.zip"));
+ try {
+ Files.copy(tmpZipFile, zipFile);
+ } finally {
+ zipFile.closeEntry();
+ }
+ } finally {
+ zipFile.close();
+ }
+
+ return finalResourceFile;
+ }
+
+ // ----- FileSupplierTask -----
+
+ @Override
+ public File get() {
+ return getOutputFile();
+ }
+
+ @NonNull
+ @Override
+ public Task getTask() {
+ return this;
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements TaskConfigAction<PackageApplication> {
+
+ private final VariantOutputScope scope;
+ private final DexPackagingPolicy dexPackagingPolicy;
+
+ public ConfigAction(VariantOutputScope scope, DexPackagingPolicy dexPackagingPolicy) {
+ this.scope = scope;
+ this.dexPackagingPolicy = dexPackagingPolicy;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("package");
+ }
+
+ @NonNull
+ @Override
+ public Class<PackageApplication> getType() {
+ return PackageApplication.class;
+ }
+
+ @Override
+ public void execute(@NonNull final PackageApplication packageApp) {
+ final VariantScope variantScope = scope.getVariantScope();
+ final ApkVariantData variantData = (ApkVariantData) variantScope.getVariantData();
+ final ApkVariantOutputData variantOutputData = (ApkVariantOutputData) scope
+ .getVariantOutputData();
+ final GradleVariantConfiguration config = variantScope.getVariantConfiguration();
+
+ variantOutputData.packageApplicationTask = packageApp;
+ packageApp.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ packageApp.setVariantName(
+ variantScope.getVariantConfiguration().getFullName());
+ packageApp.setMinSdkVersion(config.getMinSdkVersion());
+ packageApp.instantRunContext = variantScope.getInstantRunBuildContext();
+ packageApp.dexPackagingPolicy = dexPackagingPolicy;
+ packageApp.instantRunSupportDir = variantScope.getInstantRunSupportDir();
+
+ if (config.isMinifyEnabled()
+ && config.getBuildType().isShrinkResources()
+ && !config.getUseJack()) {
+ ConventionMappingHelper.map(packageApp, "resourceFile", new Callable<File>() {
+ @Override
+ public File call() {
+ return scope.getCompressedResourceFile();
+ }
+ });
+ } else {
+ ConventionMappingHelper.map(packageApp, "resourceFile", new Callable<File>() {
+ @Override
+ public File call() {
+ return variantOutputData.processResourcesTask.getPackageOutputFile();
+ }
+ });
+ }
+
+ ConventionMappingHelper.map(packageApp, "dexFolders", new Callable<Set<File>>() {
+ @Override
+ public Set<File> call() {
+ if (config.getUseJack()) {
+ return ImmutableSet.of(variantScope.getJackDestinationDir());
+ }
+ return variantScope.getTransformManager()
+ .getPipelineOutput(sDexFilter).keySet();
+ }
+ });
+
+ ConventionMappingHelper.map(packageApp, "javaResourceFiles", new Callable<Set<File>>() {
+ @Override
+ public Set<File> call() throws Exception {
+ return variantScope.getTransformManager().getPipelineOutput(
+ sResFilter).keySet();
+ }
+ });
+
+ ConventionMappingHelper.map(packageApp, "jniFolders", new Callable<Set<File>>() {
+ @Override
+ public Set<File> call() {
+ if (variantData.getSplitHandlingPolicy() ==
+ BaseVariantData.SplitHandlingPolicy.PRE_21_POLICY) {
+ return variantScope.getTransformManager().getPipelineOutput(
+ sNativeLibsFilter).keySet();
+ }
+
+ Set<String> filters = AbiSplitOptions.getAbiFilters(
+ scope.getGlobalScope().getExtension().getSplits().getAbiFilters());
+ return filters.isEmpty() ? variantScope.getTransformManager().getPipelineOutput(
+ sNativeLibsFilter).keySet() : Collections.<File>emptySet();
+ }
+ });
+
+ ConventionMappingHelper.map(packageApp, "abiFilters", new Callable<Set<String>>() {
+ @Override
+ public Set<String> call() throws Exception {
+ if (variantOutputData.getMainOutputFile().getFilter(com.android.build.OutputFile.ABI) != null) {
+ return ImmutableSet.of(
+ variantOutputData.getMainOutputFile()
+ .getFilter(com.android.build.OutputFile.ABI));
+ }
+ Set<String> supportedAbis = config.getSupportedAbis();
+ if (supportedAbis != null) {
+ return supportedAbis;
+ }
+
+ return ImmutableSet.of();
+ }
+ });
+ ConventionMappingHelper.map(packageApp, "jniDebugBuild", new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return config.getBuildType().isJniDebuggable();
+ }
+ });
+
+ CoreSigningConfig sc = (CoreSigningConfig) config.getSigningConfig();
+ packageApp.setSigningConfig(sc);
+ if (sc != null) {
+ String validateSigningTaskName = "validate" + StringHelper.capitalize(sc.getName()) + "Signing";
+ ValidateSigningTask validateSigningTask =
+ (ValidateSigningTask) scope.getGlobalScope().getProject().getTasks().findByName(validateSigningTaskName);
+ if (validateSigningTask == null) {
+ validateSigningTask =
+ scope.getGlobalScope().getProject().getTasks().create(
+ "validate" + StringHelper.capitalize(sc.getName()) + "Signing",
+ ValidateSigningTask.class);
+ validateSigningTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ validateSigningTask.setVariantName(
+ variantScope.getVariantConfiguration().getFullName());
+ validateSigningTask.setSigningConfig(sc);
+ }
+
+ packageApp.dependsOn(validateSigningTask);
+ }
+
+ ConventionMappingHelper.map(packageApp, "packagingOptions", new Callable<PackagingOptions>() {
+ @Override
+ public PackagingOptions call() throws Exception {
+ return scope.getGlobalScope().getExtension().getPackagingOptions();
+ }
+ });
+
+ ConventionMappingHelper.map(packageApp, "outputFile", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return scope.getPackageApk();
+ }
+ });
+
+ packageApp.markerFile =
+ PrePackageApplication.ConfigAction.getMarkerFile(variantScope);
+ packageApp.useOldPackaging = AndroidGradleOptions.useOldPackaging(
+ variantScope.getGlobalScope().getProject());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitAbi.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitAbi.java
new file mode 100644
index 0000000..e806fce
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitAbi.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.api.ApkOutputFile;
+import com.android.build.gradle.internal.dsl.AbiSplitOptions;
+import com.android.build.gradle.internal.model.FilterDataImpl;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.packaging.PackagerException;
+import com.android.builder.packaging.SigningException;
+import com.android.ide.common.signing.KeytoolException;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Callables;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Package a abi dimension specific split APK
+ */
+ at ParallelizableTask
+public class PackageSplitAbi extends SplitRelatedTask {
+
+ private ImmutableList<ApkOutputFile> outputFiles;
+
+ private Collection<File> inputFiles;
+
+ private File outputDirectory;
+
+ private Set<String> splits;
+
+ private String outputBaseName;
+
+ private boolean jniDebuggable;
+
+ private SigningConfig signingConfig;
+
+ private Collection<File> jniFolders;
+
+ private ApiVersion minSdkVersion;
+
+ @OutputFiles
+ public List<File> getOutputFiles() {
+ ImmutableList.Builder<File> builder = ImmutableList.builder();
+ for (String split : splits) {
+ String apkName = getApkName(split);
+ builder.add(new File(outputDirectory, apkName));
+ }
+ return builder.build();
+ }
+
+ @Override
+ @Nullable
+ public File getApkMetadataFile() {
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public synchronized ImmutableList<ApkOutputFile> getOutputSplitFiles() {
+
+ if (outputFiles == null) {
+ ImmutableList.Builder<ApkOutputFile> builder = ImmutableList.builder();
+ for (String split : splits) {
+ String apkName = getApkName(split);
+ ApkOutputFile apkOutput = new ApkOutputFile(
+ OutputFile.OutputType.SPLIT,
+ ImmutableList.of(FilterDataImpl.build(OutputFile.ABI, apkName)),
+ Callables.returning(new File(outputDirectory, apkName)));
+ builder.add(apkOutput);
+ }
+
+ outputFiles = builder.build();
+ }
+ return outputFiles;
+ }
+
+ private boolean isAbiSplit(String fileName) {
+ for (String abi : getSplits()) {
+ if (fileName.contains(abi)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @TaskAction
+ protected void doFullTaskAction() throws FileNotFoundException, SigningException,
+ KeytoolException, DuplicateFileException, PackagerException {
+
+ // resources- and .ap_ should be shared in a setting somewhere. see BasePlugin:1206
+ final Pattern pattern = Pattern.compile("resources-" + getOutputBaseName() + "-(.*).ap_");
+ List<String> unprocessedSplits = Lists.newArrayList(splits);
+ for (File file : inputFiles) {
+ Matcher matcher = pattern.matcher(file.getName());
+ if (matcher.matches() && isAbiSplit(file.getName())) {
+ String apkName = getApkName(matcher.group(1));
+
+ File outFile = new File(getOutputDirectory(), apkName);
+ getBuilder().packageApk(
+ file.getAbsolutePath(),
+ ImmutableSet.<File>of(), /* dexFolders */
+ ImmutableList.<File>of(), /* getJavaResourceDir */
+ getJniFolders(),
+ ImmutableSet.of(matcher.group(1)),
+ isJniDebuggable(),
+ getSigningConfig(),
+ outFile.getAbsolutePath(),
+ getMinSdkVersion());
+ unprocessedSplits.remove(matcher.group(1));
+ }
+ }
+ if (!unprocessedSplits.isEmpty()) {
+ String message = "Could not find resource package for "
+ + Joiner.on(',').join(unprocessedSplits);
+ getLogger().error(message);
+ throw new IllegalStateException(message);
+ }
+ }
+
+ @Override
+ public List<FilterData> getSplitsData() {
+ ImmutableList.Builder<FilterData> filterDataBuilder = ImmutableList.builder();
+ SplitRelatedTask.addAllFilterData(filterDataBuilder, splits, OutputFile.FilterType.ABI);
+ return filterDataBuilder.build();
+ }
+
+ private String getApkName(final String split) {
+ String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
+ String apkName = archivesBaseName + "-" + getOutputBaseName() + "_" + split;
+ return apkName + (getSigningConfig() == null ? "-unsigned.apk" : "-unaligned.apk");
+ }
+
+ public void setOutputFiles(ImmutableList<ApkOutputFile> outputFiles) {
+ this.outputFiles = outputFiles;
+ }
+
+ @InputFiles
+ public Collection<File> getInputFiles() {
+ return inputFiles;
+ }
+
+ public void setInputFiles(Collection<File> inputFiles) {
+ this.inputFiles = inputFiles;
+ }
+
+ public File getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ public void setOutputDirectory(File outputDirectory) {
+ this.outputDirectory = outputDirectory;
+ }
+
+ @Input
+ public Set<String> getSplits() {
+ return splits;
+ }
+
+ public void setSplits(Set<String> splits) {
+ this.splits = splits;
+ }
+
+ @Input
+ public String getOutputBaseName() {
+ return outputBaseName;
+ }
+
+ public void setOutputBaseName(String outputBaseName) {
+ this.outputBaseName = outputBaseName;
+ }
+
+ @Input
+ public boolean isJniDebuggable() {
+ return jniDebuggable;
+ }
+
+ public void setJniDebuggable(boolean jniDebuggable) {
+ this.jniDebuggable = jniDebuggable;
+ }
+
+ @Nested
+ @Optional
+ public SigningConfig getSigningConfig() {
+ return signingConfig;
+ }
+
+ public void setSigningConfig(SigningConfig signingConfig) {
+ this.signingConfig = signingConfig;
+ }
+
+ @Input
+ public Collection<File> getJniFolders() {
+ return jniFolders;
+ }
+
+ public void setJniFolders(Collection<File> jniFolders) {
+ this.jniFolders = jniFolders;
+ }
+
+ public void setMinSdkVersion(ApiVersion version) {
+ this.minSdkVersion = version;
+ }
+
+ @Input
+ public int getMinSdkVersion() {
+ return minSdkVersion.getApiLevel();
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements TaskConfigAction<PackageSplitAbi> {
+
+ private VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return scope.getTaskName("package", "SplitAbi");
+ }
+
+ @Override
+ @NonNull
+ public Class<PackageSplitAbi> getType() {
+ return PackageSplitAbi.class;
+ }
+
+ @Override
+ public void execute(@NonNull PackageSplitAbi packageSplitAbiTask) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ List<? extends BaseVariantOutputData> outputs = variantData.getOutputs();
+ final BaseVariantOutputData variantOutputData = outputs.get(0);
+ final VariantConfiguration config = scope.getVariantConfiguration();
+
+ variantOutputData.packageSplitAbiTask = packageSplitAbiTask;
+
+ Set<String> filters = AbiSplitOptions.getAbiFilters(
+ scope.getGlobalScope().getExtension().getSplits().getAbiFilters());
+ packageSplitAbiTask.setInputFiles(scope.getSplitAbiResOutputFiles());
+ packageSplitAbiTask.setSplits(filters);
+ packageSplitAbiTask.setOutputBaseName(config.getBaseName());
+ packageSplitAbiTask.setSigningConfig(config.getSigningConfig());
+ packageSplitAbiTask.setOutputDirectory(scope.getSplitOutputDirectory());
+ packageSplitAbiTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ packageSplitAbiTask.setVariantName(config.getFullName());
+ packageSplitAbiTask.setMinSdkVersion(config.getMinSdkVersion());
+
+ ConventionMappingHelper.map(packageSplitAbiTask, "jniFolders",
+ new Callable<Set<File>>() {
+ @Override
+ public Set<File> call() throws Exception {
+ return scope.getTransformManager().getPipelineOutput(
+ PackageApplication.sNativeLibsFilter).keySet();
+ }
+ });
+
+ ConventionMappingHelper.map(packageSplitAbiTask, "jniDebuggable",
+ new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return config.getBuildType().isJniDebuggable();
+ }
+ });
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitRes.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitRes.java
new file mode 100644
index 0000000..fa5e157
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PackageSplitRes.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.gradle.api.ApkOutputFile;
+import com.android.build.gradle.internal.model.FilterDataImpl;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.packaging.SigningException;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.ide.common.signing.KeytoolException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Callables;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Package each split resources into a specific signed apk file.
+ */
+ at ParallelizableTask
+public class PackageSplitRes extends SplitRelatedTask {
+
+ private Set<String> densitySplits;
+
+ private Set<String> languageSplits;
+
+ private String outputBaseName;
+
+ private SigningConfig signingConfig;
+
+ /**
+ * This directories are not officially input/output to the task as they are shared among tasks.
+ * To be parallelizable, we must only define our I/O in terms of files...
+ */
+ private File inputDirectory;
+
+ private File outputDirectory;
+
+ @InputFiles
+ public List<File> getInputFiles() {
+ final ImmutableList.Builder<File> builder = ImmutableList.builder();
+ forEachInputFile(new SplitFileHandler() {
+ @Override
+ public void execute(String split, File file) {
+ builder.add(file);
+ }
+ });
+ return builder.build();
+ }
+
+ @OutputFiles
+ public List<File> getOutputFiles() {
+ ImmutableList.Builder<File> builder = ImmutableList.builder();
+ for (ApkOutputFile apk : getOutputSplitFiles()) {
+ builder.add(apk.getOutputFile());
+ }
+ return builder.build();
+ }
+
+ @Override
+ public File getApkMetadataFile() {
+ return null;
+ }
+
+ /**
+ * Calculates the list of output files, coming from the list of input files, mangling the output
+ * file name.
+ */
+ @Override
+ public List<ApkOutputFile> getOutputSplitFiles() {
+ final ImmutableList.Builder<ApkOutputFile> builder = ImmutableList.builder();
+ forEachInputFile(new SplitFileHandler() {
+ @Override
+ public void execute(String split, File file) {
+ // find the split identification, if null, the split is not requested any longer.
+ FilterData filterData = null;
+ for (String density : densitySplits) {
+ if (split.startsWith(density)) {
+ filterData = FilterDataImpl.build(
+ OutputFile.FilterType.DENSITY.toString(), density);
+ }
+
+ }
+
+ if (languageSplits.contains(unMangleSplitName(split))) {
+ filterData = FilterDataImpl.build(
+ OutputFile.FilterType.LANGUAGE.toString(), unMangleSplitName(split));
+ }
+ if (filterData != null) {
+ builder.add(new ApkOutputFile(
+ OutputFile.OutputType.SPLIT,
+ ImmutableList.of(filterData),
+ Callables.returning(
+ new File(outputDirectory, getOutputFileNameForSplit(split)))));
+ }
+
+ }
+ });
+ return builder.build();
+ }
+
+ @TaskAction
+ protected void doFullTaskAction() {
+ forEachInputFile(
+ new SplitFileHandler() {
+ @Override
+ public void execute(String split, File file) {
+ File outFile = new File(outputDirectory, getOutputFileNameForSplit(split));
+ try {
+ getBuilder().signApk(file, signingConfig, outFile);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (KeytoolException e) {
+ throw new RuntimeException(e);
+ } catch (SigningException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ } catch (SignedJarBuilder.IZipEntryFilter.ZipAbortException e) {
+ throw new RuntimeException(e);
+ } catch (com.android.builder.signing.SigningException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ private interface SplitFileHandler {
+ void execute(String split, File file);
+ }
+
+ /**
+ * Runs the handler for each task input file, providing the split identifier (possibly with a
+ * suffix generated by aapt) and the input file handle.
+ */
+ private void forEachInputFile(SplitFileHandler handler) {
+ Pattern resourcePattern = Pattern.compile("resources-" + outputBaseName + ".ap__(.*)");
+
+ // make a copy of the expected densities and languages filters.
+ List<String> densitiesCopy = Lists.newArrayList(densitySplits);
+ List<String> languagesCopy = Lists.newArrayList(languageSplits);
+
+ // resources- and .ap_ should be shared in a setting somewhere. see BasePlugin:1206
+ File[] fileLists = inputDirectory.listFiles();
+ if (fileLists != null) {
+ for (File file : fileLists) {
+ Matcher match = resourcePattern.matcher(file.getName());
+ // each time we match, we remove the associated filter from our copies.
+ if (match.matches() && !match.group(1).isEmpty()
+ && isValidSplit(densitiesCopy, languagesCopy, match.group(1))) {
+ handler.execute(match.group(1), file);
+ }
+ }
+ }
+ // manually invoke the handler for filters we did not find associated files, apply best
+ // guess on the actual file names.
+ for (String density : densitiesCopy) {
+ handler.execute(density,
+ new File(inputDirectory, "resources-" + outputBaseName + ".ap__" + density));
+ }
+ for (String language : languagesCopy) {
+ handler.execute(language,
+ new File(inputDirectory, "resources-" + outputBaseName + ".ap__" + language));
+
+ }
+ }
+
+ /**
+ * Returns true if the passed split identifier is a valid identifier (valid mean it is a
+ * requested split for this task). A density split identifier can be suffixed with characters
+ * added by aapt.
+ */
+ private static boolean isValidSplit(
+ List<String> densities,
+ List<String> languages,
+ @NonNull String splitWithOptionalSuffix) {
+ for (String density : densities) {
+ if (splitWithOptionalSuffix.startsWith(density)) {
+ densities.remove(density);
+ return true;
+ }
+ }
+ String mangledName = unMangleSplitName(splitWithOptionalSuffix);
+ if (languages.contains(mangledName)) {
+ languages.remove(mangledName);
+ return true;
+ }
+ return false;
+ }
+
+ public String getOutputFileNameForSplit(final String split) {
+ String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
+ String apkName = archivesBaseName + "-" + outputBaseName + "_" + split;
+ return apkName + (signingConfig == null ? "-unsigned.apk" : "-unaligned.apk");
+ }
+
+ @Override
+ public List<FilterData> getSplitsData() {
+ ImmutableList.Builder<FilterData> filterDataBuilder = ImmutableList.builder();
+ addAllFilterData(filterDataBuilder, densitySplits, OutputFile.FilterType.DENSITY);
+ addAllFilterData(filterDataBuilder, languageSplits, OutputFile.FilterType.LANGUAGE);
+ return filterDataBuilder.build();
+ }
+
+ /**
+ * Un-mangle a split name as created by the aapt tool to retrieve a split name as configured in
+ * the project's build.gradle.
+ *
+ * when dealing with several split language in a single split, each language (+ optional region)
+ * will be seperated by an underscore.
+ *
+ * note that there is currently an aapt bug, remove the 'r' in the region so for instance,
+ * fr-rCA becomes fr-CA, temporarily put it back until it is fixed.
+ *
+ * @param splitWithOptionalSuffix the mangled split name.
+ */
+ public static String unMangleSplitName(String splitWithOptionalSuffix) {
+ String mangledName = splitWithOptionalSuffix.replaceAll("_", ",");
+ return mangledName.contains("-r") ? mangledName : mangledName.replace("-", "-r");
+ }
+
+ @Input
+ public Set<String> getDensitySplits() {
+ return densitySplits;
+ }
+
+ public void setDensitySplits(Set<String> densitySplits) {
+ this.densitySplits = densitySplits;
+ }
+
+ @Input
+ public Set<String> getLanguageSplits() {
+ return languageSplits;
+ }
+
+ public void setLanguageSplits(Set<String> languageSplits) {
+ this.languageSplits = languageSplits;
+ }
+
+ @Input
+ public String getOutputBaseName() {
+ return outputBaseName;
+ }
+
+ public void setOutputBaseName(String outputBaseName) {
+ this.outputBaseName = outputBaseName;
+ }
+
+ @Nested
+ @Optional
+ public SigningConfig getSigningConfig() {
+ return signingConfig;
+ }
+
+ public void setSigningConfig(SigningConfig signingConfig) {
+ this.signingConfig = signingConfig;
+ }
+
+ public File getInputDirectory() {
+ return inputDirectory;
+ }
+
+ public void setInputDirectory(File inputDirectory) {
+ this.inputDirectory = inputDirectory;
+ }
+
+ public File getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ public void setOutputDirectory(File outputDirectory) {
+ this.outputDirectory = outputDirectory;
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements TaskConfigAction<PackageSplitRes> {
+
+ private VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return scope.getTaskName("package", "SplitResources");
+ }
+
+ @Override
+ @NonNull
+ public Class<PackageSplitRes> getType() {
+ return PackageSplitRes.class;
+ }
+
+ @Override
+ public void execute(@NonNull PackageSplitRes packageSplitResourcesTask) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ List<? extends BaseVariantOutputData> outputs = variantData.getOutputs();
+
+ final VariantConfiguration config = variantData.getVariantConfiguration();
+ Set<String> densityFilters = variantData.getFilters(OutputFile.FilterType.DENSITY);
+ Set<String> languageFilters = variantData.getFilters(OutputFile.FilterType.LANGUAGE);
+
+ final BaseVariantOutputData variantOutputData = outputs.get(0);
+ variantOutputData.packageSplitResourcesTask = packageSplitResourcesTask;
+ VariantOutputScope variantOutputScope = variantOutputData.getScope();
+ packageSplitResourcesTask.setInputDirectory(
+ variantOutputScope.getProcessResourcePackageOutputFile().getParentFile());
+ packageSplitResourcesTask.setDensitySplits(densityFilters);
+ packageSplitResourcesTask.setLanguageSplits(languageFilters);
+ packageSplitResourcesTask.setOutputBaseName(config.getBaseName());
+ packageSplitResourcesTask.setSigningConfig(config.getSigningConfig());
+ packageSplitResourcesTask.setOutputDirectory(scope.getSplitOutputDirectory());
+ packageSplitResourcesTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ packageSplitResourcesTask.setVariantName(config.getFullName());
+ packageSplitResourcesTask.dependsOn(
+ variantOutputScope.getProcessResourcesTask().getName());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PrePackageApplication.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PrePackageApplication.java
new file mode 100644
index 0000000..15663e1
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/PrePackageApplication.java
@@ -0,0 +1,87 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunPatchingPolicy;
+import com.android.build.gradle.internal.scope.VariantScope;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * PrePackaging step class that will look if the packaging of the main APK split is necessary
+ * when running in InstantRun mode. In InstantRun mode targeting an api 23 or above device,
+ * resources are packaged in the main split APK. However when a warm swap is possible, it is
+ * not necessary to produce immediately the new main SPLIT since the runtime use directly the
+ * resources.ap_ file. However, as soon as an incompatible change forcing a cold swap is
+ * triggered, the main APK must be rebuilt (even if the resources were changed in a previous
+ * build).
+ */
+public class PrePackageApplication extends KickerTask {
+
+ @Override
+ protected void doFullTaskAction() throws IOException {
+
+ // when instantRun is disabled or not targeting 23 and above, we must run the packageApp
+ // task.
+ if (InstantRunPatchingPolicy.MULTI_APK != instantRunContext.getPatchingPolicy()
+ || variantScope.getGlobalScope().isActive(OptionalCompilationStep.RESTART_ONLY)) {
+ MarkerFile.createMarkerFile(getMarkerFile(), MarkerFile.Command.RUN);
+ return;
+ }
+
+ if (instantRunContext.hasPassedVerification()) {
+ MarkerFile.createMarkerFile(getMarkerFile(), MarkerFile.Command.BLOCK);
+ } else {
+ // now the main apk is only necessary if we produced a RESOURCES file in a previous
+ // build, let's check it out.
+ if (instantRunContext.getPastBuildsArtifactForType(
+ InstantRunBuildContext.FileType.RESOURCES) == null) {
+ MarkerFile.createMarkerFile(getMarkerFile(), MarkerFile.Command.BLOCK);
+ } else {
+ MarkerFile.createMarkerFile(getMarkerFile(), MarkerFile.Command.RUN);
+ }
+ }
+ }
+
+ public static class ConfigAction extends KickerTask.ConfigAction<PrePackageApplication> {
+
+ public ConfigAction(@NonNull String name, @NonNull VariantScope scope) {
+ super(name, scope);
+ }
+
+ @NonNull
+ public static File getMarkerFile(@NonNull VariantScope scope) {
+ return new File(scope.getInstantRunSupportDir(), "package.marker");
+ }
+
+ @NonNull
+ @Override
+ public Class<PrePackageApplication> getType() {
+ return PrePackageApplication.class;
+ }
+
+ @Override
+ public void execute(@NonNull PrePackageApplication task) {
+ super.execute(task);
+ task.markerFile = getMarkerFile(scope);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.java
new file mode 100644
index 0000000..0cabb9c
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.java
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.internal.LoggingUtil;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.dependency.SymbolFileProviderImpl;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.build.gradle.internal.incremental.InstantRunWrapperTask;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.tasks.IncrementalTask;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.AaptPackageProcessBuilder;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.VariantType;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.ide.common.blame.MergingLog;
+import com.android.ide.common.blame.MergingLogRewriter;
+import com.android.ide.common.blame.ParsingProcessOutputHandler;
+import com.android.ide.common.blame.parser.ToolOutputParser;
+import com.android.ide.common.blame.parser.aapt.AaptOutputParser;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutputHandler;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+ at ParallelizableTask
+public class ProcessAndroidResources extends IncrementalTask {
+
+ private File manifestFile;
+
+ private File instantRunManifestFile;
+
+ private File resDir;
+
+ private File assetsDir;
+
+ private File sourceOutputDir;
+
+ private File textSymbolOutputDir;
+
+ private File packageOutputFile;
+
+ private File proguardOutputFile;
+
+ private Collection<String> resourceConfigs;
+
+ private String preferredDensity;
+
+ private List<SymbolFileProviderImpl> libraries;
+
+ private String packageForR;
+
+ private Collection<String> splits;
+
+ private boolean enforceUniquePackageName;
+
+ private VariantType type;
+
+ private boolean debuggable;
+
+ private boolean pseudoLocalesEnabled;
+
+ private AaptOptions aaptOptions;
+
+ private File mergeBlameLogFolder;
+
+ private InstantRunBuildContext instantRunBuildContext;
+
+ private File instantRunSupportDir;
+
+ private File buildInfoFile;
+
+ @Override
+ protected void doFullTaskAction() throws IOException {
+ // we have to clean the source folder output in case the package name changed.
+ File srcOut = getSourceOutputDir();
+ if (srcOut != null) {
+ FileUtils.emptyFolder(srcOut);
+ }
+
+ @Nullable
+ File resOutBaseNameFile = getPackageOutputFile();
+
+ // If are in instant run mode and we have an instant run enabled manifest
+ File instantRunManifest = getInstantRunManifestFile();
+ File manifestFileToPackage = instantRunBuildContext.isInInstantRunMode() &&
+ instantRunManifest != null && instantRunManifest.exists()
+ ? instantRunManifest
+ : getManifestFile();
+
+ AaptPackageProcessBuilder aaptPackageCommandBuilder =
+ new AaptPackageProcessBuilder(manifestFileToPackage, getAaptOptions())
+ .setAssetsFolder(getAssetsDir())
+ .setResFolder(getResDir())
+ .setLibraries(getLibraries())
+ .setPackageForR(getPackageForR())
+ .setSourceOutputDir(absolutePath(srcOut))
+ .setSymbolOutputDir(absolutePath(getTextSymbolOutputDir()))
+ .setResPackageOutput(absolutePath(resOutBaseNameFile))
+ .setProguardOutput(absolutePath(getProguardOutputFile()))
+ .setType(getType())
+ .setDebuggable(getDebuggable())
+ .setPseudoLocalesEnabled(getPseudoLocalesEnabled())
+ .setResourceConfigs(getResourceConfigs())
+ .setSplits(getSplits())
+ .setPreferredDensity(getPreferredDensity());
+
+ @NonNull
+ AndroidBuilder builder = getBuilder();
+
+ MergingLog mergingLog = new MergingLog(getMergeBlameLogFolder());
+
+ ProcessOutputHandler processOutputHandler = new ParsingProcessOutputHandler(
+ new ToolOutputParser(new AaptOutputParser(), getILogger()),
+ new MergingLogRewriter(mergingLog, builder.getErrorReporter()));
+ try {
+ builder.processResources(
+ aaptPackageCommandBuilder,
+ getEnforceUniquePackageName(),
+ processOutputHandler);
+ if (resOutBaseNameFile != null) {
+ if (instantRunBuildContext.isInInstantRunMode()) {
+
+ instantRunBuildContext.addChangedFile(
+ InstantRunBuildContext.FileType.RESOURCES, resOutBaseNameFile);
+
+ // get the new manifest file CRC
+ JarFile jarFile = new JarFile(resOutBaseNameFile);
+ String currentIterationCRC = null;
+ try {
+ ZipEntry entry = jarFile.getEntry(SdkConstants.ANDROID_MANIFEST_XML);
+ if (entry != null) {
+ currentIterationCRC = String.valueOf(entry.getCrc());
+ }
+ } finally {
+ jarFile.close();
+ }
+
+ // check the manifest file binary format.
+ File crcFile = new File(instantRunSupportDir, "manifest.crc");
+ if (crcFile.exists() && currentIterationCRC != null) {
+ // compare its content with the new binary file crc.
+ String previousIterationCRC = Files.readFirstLine(crcFile, Charsets.UTF_8);
+ if (!currentIterationCRC.equals(previousIterationCRC)) {
+ instantRunBuildContext.abort();
+ instantRunBuildContext.setVerifierResult(
+ InstantRunVerifierStatus.BINARY_MANIFEST_FILE_CHANGE);
+ }
+ }
+
+ // write the new manifest file CRC.
+ Files.createParentDirs(crcFile);
+ Files.write(currentIterationCRC, crcFile, Charsets.UTF_8);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ProcessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean isSplitPackage(File file, File resBaseName) {
+ if (file.getName().startsWith(resBaseName.getName())) {
+ for (String split : splits) {
+ if (file.getName().contains(split)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private static String absolutePath(@Nullable File file) {
+ return file == null ? null : file.getAbsolutePath();
+ }
+
+ public static class ConfigAction implements TaskConfigAction<ProcessAndroidResources> {
+
+ private VariantOutputScope scope;
+ private File symbolLocation;
+ private boolean generateResourcePackage;
+
+ public ConfigAction(
+ VariantOutputScope scope, File symbolLocation, boolean generateResourcePackage) {
+ this.scope = scope;
+ this.symbolLocation = symbolLocation;
+ this.generateResourcePackage = generateResourcePackage;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("process", "Resources");
+ }
+
+ @NonNull
+ @Override
+ public Class<ProcessAndroidResources> getType() {
+ return ProcessAndroidResources.class;
+ }
+
+ @Override
+ public void execute(@NonNull ProcessAndroidResources processResources) {
+ final BaseVariantOutputData variantOutputData = scope.getVariantOutputData();
+ final BaseVariantData<? extends BaseVariantOutputData> variantData =
+ scope.getVariantScope().getVariantData();
+ variantOutputData.processResourcesTask = processResources;
+ final GradleVariantConfiguration config = variantData.getVariantConfiguration();
+
+ processResources.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ processResources.setVariantName(config.getFullName());
+
+ if (variantData.getSplitHandlingPolicy() ==
+ BaseVariantData.SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY) {
+ Set<String> allFilters = new HashSet<String>();
+ allFilters.addAll(
+ variantData.getFilters(com.android.build.OutputFile.FilterType.DENSITY));
+ allFilters.addAll(
+ variantData.getFilters(com.android.build.OutputFile.FilterType.LANGUAGE));
+ processResources.splits = allFilters;
+ }
+
+ // only generate code if the density filter is null, and if we haven't generated
+ // it yet (if you have abi + density splits, then several abi output will have no
+ // densityFilter)
+ if (variantOutputData.getMainOutputFile()
+ .getFilter(com.android.build.OutputFile.DENSITY) == null
+ && variantData.generateRClassTask == null) {
+ variantData.generateRClassTask = processResources;
+ processResources.enforceUniquePackageName = scope.getGlobalScope().getExtension()
+ .getEnforceUniquePackageName();
+
+ ConventionMappingHelper.map(processResources, "libraries",
+ new Callable<List<SymbolFileProviderImpl>>() {
+ @Override
+ public List<SymbolFileProviderImpl> call() throws Exception {
+ return getTextSymbolDependencies(config.getAllLibraries());
+ }
+ });
+ ConventionMappingHelper.map(processResources, "packageForR",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return config.getOriginalApplicationId();
+ }
+ });
+
+ // TODO: unify with generateBuilderConfig, compileAidl, and library packaging somehow?
+ processResources
+ .setSourceOutputDir(scope.getVariantScope().getRClassSourceOutputDir());
+ processResources.setTextSymbolOutputDir(symbolLocation);
+
+ if (config.getBuildType().isMinifyEnabled()) {
+ if (config.getBuildType().isShrinkResources() && config.getUseJack()) {
+ LoggingUtil.displayWarning(Logging.getLogger(getClass()),
+ scope.getGlobalScope().getProject(),
+ "shrinkResources does not yet work with useJack=true");
+ }
+ processResources.setProguardOutputFile(
+ scope.getVariantScope().getProcessAndroidResourcesProguardOutputFile());
+
+ } else if (config.getBuildType().isShrinkResources()) {
+ LoggingUtil.displayWarning(Logging.getLogger(getClass()),
+ scope.getGlobalScope().getProject(),
+ "To shrink resources you must also enable ProGuard");
+ }
+ }
+
+ ConventionMappingHelper.map(processResources, "manifestFile", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return variantOutputData.manifestProcessorTask.getOutputFile();
+ }
+ });
+
+ ConventionMappingHelper.map(processResources, "instantRunManifestFile",
+ new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return variantOutputData.manifestProcessorTask.getInstantRunManifestOutputFile();
+ }
+ });
+
+ ConventionMappingHelper.map(processResources, "resDir", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return scope.getVariantScope().getFinalResourcesDir();
+ }
+ });
+
+ ConventionMappingHelper.map(processResources, "assetsDir", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return variantData.mergeAssetsTask.getOutputDir();
+ }
+ });
+
+ if (generateResourcePackage) {
+ processResources.setPackageOutputFile(scope.getProcessResourcePackageOutputFile());
+ }
+
+ processResources.setType(config.getType());
+ processResources.setDebuggable(config.getBuildType().isDebuggable());
+ processResources.setAaptOptions(scope.getGlobalScope().getExtension().getAaptOptions());
+ processResources
+ .setPseudoLocalesEnabled(config.getBuildType().isPseudoLocalesEnabled());
+
+ ConventionMappingHelper.map(processResources, "resourceConfigs",
+ new Callable<Collection<String>>() {
+ @Override
+ public Collection<String> call() throws Exception {
+ Collection<String> resConfigs =
+ config.getMergedFlavor().getResourceConfigurations();
+ if (resConfigs.size() == 1 &&
+ Iterators.getOnlyElement(resConfigs.iterator())
+ .equals("auto")) {
+ if (scope.getGlobalScope().getAndroidBuilder().getTargetInfo()
+ .getBuildTools().getRevision().getMajor() >= 21) {
+ return variantData.discoverListOfResourceConfigsNotDensities();
+ } else {
+ return variantData.discoverListOfResourceConfigs();
+ }
+ }
+ return config.getMergedFlavor().getResourceConfigurations();
+ }
+ });
+
+ ConventionMappingHelper.map(processResources, "preferredDensity",
+ new Callable<String>() {
+ @Override
+ @Nullable
+ public String call() throws Exception {
+ String variantFilter = variantOutputData.getMainOutputFile()
+ .getFilter(com.android.build.OutputFile.DENSITY);
+ if (variantFilter != null) {
+ return variantFilter;
+ }
+ return AndroidGradleOptions.getBuildTargetDensity(
+ scope.getGlobalScope().getProject());
+ }
+ });
+
+
+ processResources.setMergeBlameLogFolder(
+ scope.getVariantScope().getResourceBlameLogDir());
+
+ processResources.instantRunSupportDir =
+ scope.getVariantScope().getInstantRunSupportDir();
+
+ processResources.instantRunBuildContext =
+ scope.getVariantScope().getInstantRunBuildContext();
+ processResources.buildInfoFile =
+ InstantRunWrapperTask.ConfigAction.getTmpBuildInfoFile(scope.getVariantScope());
+ }
+
+ @NonNull
+ private static List<SymbolFileProviderImpl> getTextSymbolDependencies(
+ List<LibraryDependency> libraries) {
+
+ List<SymbolFileProviderImpl> list = Lists.newArrayListWithCapacity(libraries.size());
+
+ for (LibraryDependency lib : libraries) {
+ list.add(new SymbolFileProviderImpl(lib));
+ }
+
+ return list;
+ }
+ }
+
+ @InputFile
+ public File getManifestFile() {
+ return manifestFile;
+ }
+
+ public void setManifestFile(File manifestFile) {
+ this.manifestFile = manifestFile;
+ }
+
+ // not an input, it's optional and should never changes independently of the main manifest file.
+ public File getInstantRunManifestFile() {
+ return instantRunManifestFile;
+ }
+
+ public void setInstantRunManifestFile(File manifestFile) {
+ this.instantRunManifestFile = manifestFile;
+ }
+
+ @NonNull
+ @InputDirectory
+ public File getResDir() {
+ return resDir;
+ }
+
+ public void setResDir(@NonNull File resDir) {
+ this.resDir = resDir;
+ }
+
+ @InputDirectory
+ @Optional
+ @Nullable
+ public File getAssetsDir() {
+ return assetsDir;
+ }
+
+ public void setAssetsDir(File assetsDir) {
+ this.assetsDir = assetsDir;
+ }
+
+ @OutputDirectory
+ @Optional
+ @Nullable
+ public File getSourceOutputDir() {
+ return sourceOutputDir;
+ }
+
+ public void setSourceOutputDir(File sourceOutputDir) {
+ this.sourceOutputDir = sourceOutputDir;
+ }
+
+ @OutputDirectory
+ @Optional
+ @Nullable
+ public File getTextSymbolOutputDir() {
+ return textSymbolOutputDir;
+ }
+
+ public void setTextSymbolOutputDir(File textSymbolOutputDir) {
+ this.textSymbolOutputDir = textSymbolOutputDir;
+ }
+
+ @OutputFile
+ @Optional
+ @Nullable
+ public File getPackageOutputFile() {
+ return packageOutputFile;
+ }
+
+ public void setPackageOutputFile(File packageOutputFile) {
+ this.packageOutputFile = packageOutputFile;
+ }
+
+ @OutputFile
+ @Optional
+ @Nullable
+ public File getProguardOutputFile() {
+ return proguardOutputFile;
+ }
+
+ public void setProguardOutputFile(File proguardOutputFile) {
+ this.proguardOutputFile = proguardOutputFile;
+ }
+
+ @Input
+ public Collection<String> getResourceConfigs() {
+ return resourceConfigs;
+ }
+
+ public void setResourceConfigs(Collection<String> resourceConfigs) {
+ this.resourceConfigs = resourceConfigs;
+ }
+
+ @Input
+ @Optional
+ @Nullable
+ public String getPreferredDensity() {
+ return preferredDensity;
+ }
+
+ public void setPreferredDensity(String preferredDensity) {
+ this.preferredDensity = preferredDensity;
+ }
+
+ @Input
+ String getBuildToolsVersion() {
+ return getBuildTools().getRevision().toString();
+ }
+
+ @Nested
+ @Optional
+ @Nullable
+ public List<SymbolFileProviderImpl> getLibraries() {
+ return libraries;
+ }
+
+ public void setLibraries(
+ List<SymbolFileProviderImpl> libraries) {
+ this.libraries = libraries;
+ }
+
+ @Input
+ @Optional
+ @Nullable
+ public String getPackageForR() {
+ return packageForR;
+ }
+
+ public void setPackageForR(String packageForR) {
+ this.packageForR = packageForR;
+ }
+
+ @Input
+ @Optional
+ @Nullable
+ public Collection<String> getSplits() {
+ return splits;
+ }
+
+ public void setSplits(Collection<String> splits) {
+ this.splits = splits;
+ }
+
+ @Input
+ public boolean getEnforceUniquePackageName() {
+ return enforceUniquePackageName;
+ }
+
+ public void setEnforceUniquePackageName(boolean enforceUniquePackageName) {
+ this.enforceUniquePackageName = enforceUniquePackageName;
+ }
+
+ /** Does not change between incremental builds, so does not need to be @Input. */
+ public VariantType getType() {
+ return type;
+ }
+
+ public void setType(VariantType type) {
+ this.type = type;
+ }
+
+ @Input
+ public boolean getDebuggable() {
+ return debuggable;
+ }
+
+ public void setDebuggable(boolean debuggable) {
+ this.debuggable = debuggable;
+ }
+
+ @Input
+ public boolean getPseudoLocalesEnabled() {
+ return pseudoLocalesEnabled;
+ }
+
+ public void setPseudoLocalesEnabled(boolean pseudoLocalesEnabled) {
+ this.pseudoLocalesEnabled = pseudoLocalesEnabled;
+ }
+
+ @Nested
+ public AaptOptions getAaptOptions() {
+ return aaptOptions;
+ }
+
+ public void setAaptOptions(AaptOptions aaptOptions) {
+ this.aaptOptions = aaptOptions;
+ }
+
+ @Input
+ public File getMergeBlameLogFolder() {
+ return mergeBlameLogFolder;
+ }
+
+ public void setMergeBlameLogFolder(File mergeBlameLogFolder) {
+ this.mergeBlameLogFolder = mergeBlameLogFolder;
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.java
new file mode 100644
index 0000000..a42a817
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.dependency.ManifestDependency;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.ProductFlavor;
+import com.android.manifmerger.ManifestMerger2;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.ParallelizableTask;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * a Task that only merge a single manifest with its overlays.
+ */
+ at ParallelizableTask
+public class ProcessManifest extends ManifestProcessorTask {
+
+ private String minSdkVersion;
+
+ private String targetSdkVersion;
+
+ private Integer maxSdkVersion;
+
+ private VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor>
+ variantConfiguration;
+
+ private File reportFile;
+
+ @Override
+ protected void doFullTaskAction() {
+ File aaptManifestFile = getAaptFriendlyManifestOutputFile();
+ String aaptFriendlyManifestOutputFilePath =
+ aaptManifestFile == null ? null : aaptManifestFile.getAbsolutePath();
+ getBuilder().mergeManifests(
+ getMainManifest(),
+ getManifestOverlays(),
+ Collections.<ManifestDependency>emptyList(),
+ getPackageOverride(),
+ getVersionCode(),
+ getVersionName(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getMaxSdkVersion(),
+ getManifestOutputFile().getAbsolutePath(),
+ aaptFriendlyManifestOutputFilePath,
+ null /* outInstantRunManifestLocation */,
+ ManifestMerger2.MergeType.LIBRARY,
+ variantConfiguration.getManifestPlaceholders(),
+ Collections.<ManifestMerger2.Invoker.Feature>emptyList(),
+ getReportFile());
+ }
+
+ @Input
+ @Optional
+ public String getMinSdkVersion() {
+ return minSdkVersion;
+ }
+
+ public void setMinSdkVersion(String minSdkVersion) {
+ this.minSdkVersion = minSdkVersion;
+ }
+
+ @Input
+ @Optional
+ public String getTargetSdkVersion() {
+ return targetSdkVersion;
+ }
+
+ public void setTargetSdkVersion(String targetSdkVersion) {
+ this.targetSdkVersion = targetSdkVersion;
+ }
+
+ @Input
+ @Optional
+ public Integer getMaxSdkVersion() {
+ return maxSdkVersion;
+ }
+
+ public void setMaxSdkVersion(Integer maxSdkVersion) {
+ this.maxSdkVersion = maxSdkVersion;
+ }
+
+ public VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> getVariantConfiguration() {
+ return variantConfiguration;
+ }
+
+ public void setVariantConfiguration(
+ VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> variantConfiguration) {
+ this.variantConfiguration = variantConfiguration;
+ }
+
+ @Input
+ @Optional
+ public File getReportFile() {
+ return reportFile;
+ }
+
+ public void setReportFile(File reportFile) {
+ this.reportFile = reportFile;
+ }
+
+ @InputFile
+ public File getMainManifest() {
+ return variantConfiguration.getMainManifest();
+ }
+
+ @Input
+ @Optional
+ public String getPackageOverride() {
+ return variantConfiguration.getApplicationId();
+ }
+
+ @Input
+ public int getVersionCode() {
+ return variantConfiguration.getVersionCode();
+ }
+
+ @Input
+ @Optional
+ public String getVersionName() {
+ return variantConfiguration.getVersionName();
+ }
+
+ @InputFiles
+ public List<File> getManifestOverlays() {
+ return variantConfiguration.getManifestOverlays();
+ }
+
+ /**
+ * Returns a serialized version of our map of key value pairs for placeholder substitution.
+ *
+ * This serialized form is only used by gradle to compare past and present tasks to determine
+ * whether a task need to be re-run or not.
+ */
+ @SuppressWarnings("unused")
+ @Input
+ @Optional
+ public String getManifestPlaceholders() {
+ return serializeMap(variantConfiguration.getManifestPlaceholders());
+ }
+
+ public static class ConfigAction implements TaskConfigAction<ProcessManifest> {
+
+ private VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("process", "Manifest");
+ }
+
+ @NonNull
+ @Override
+ public Class<ProcessManifest> getType() {
+ return ProcessManifest.class;
+ }
+
+ @Override
+ public void execute(@NonNull ProcessManifest processManifest) {
+ VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> config =
+ scope.getVariantConfiguration();
+ final AndroidBuilder androidBuilder = scope.getGlobalScope().getAndroidBuilder();
+
+ // get single output for now.
+ BaseVariantOutputData variantOutputData = scope.getVariantData().getOutputs().get(0);
+
+ variantOutputData.manifestProcessorTask = processManifest;
+ processManifest.setAndroidBuilder(androidBuilder);
+ processManifest.setVariantName(config.getFullName());
+
+ processManifest.variantConfiguration = config;
+
+ final ProductFlavor mergedFlavor = config.getMergedFlavor();
+
+ ConventionMappingHelper.map(processManifest, "minSdkVersion", new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename();
+ }
+ ApiVersion minSdkVersion = mergedFlavor.getMinSdkVersion();
+ if (minSdkVersion == null) {
+ return null;
+ }
+ return minSdkVersion.getApiString();
+ }
+ });
+
+ ConventionMappingHelper.map(processManifest, "targetSdkVersion",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (androidBuilder.isPreviewTarget()) {
+ return androidBuilder.getTargetCodename();
+ }
+ ApiVersion targetSdkVersion = mergedFlavor.getTargetSdkVersion();
+ if (targetSdkVersion == null) {
+ return null;
+ }
+ return targetSdkVersion.getApiString();
+ }
+ });
+
+ ConventionMappingHelper.map(processManifest, "maxSdkVersion", new Callable<Integer>() {
+ @Override
+ public Integer call() throws Exception {
+ if (androidBuilder.isPreviewTarget()) {
+ return null;
+ } else {
+ return mergedFlavor.getMaxSdkVersion();
+ }
+ }
+ });
+
+ processManifest.setManifestOutputFile(
+ variantOutputData.getScope().getManifestOutputFile());
+
+ processManifest.setAaptFriendlyManifestOutputFile(
+ scope.getAaptFriendlyManifestOutputFile());
+
+ }
+ }
+}
diff --git a/base/build-system/gradle-experimental/MODULE_LICENSE_APACHE2 b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy
similarity index 100%
rename from base/build-system/gradle-experimental/MODULE_LICENSE_APACHE2
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.java
new file mode 100644
index 0000000..7bb960a
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.DependencyManager;
+import com.android.build.gradle.internal.dependency.ManifestDependencyImpl;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.VariantConfiguration;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.ParallelizableTask;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * A task that processes the manifest
+ */
+ at ParallelizableTask
+public class ProcessTestManifest extends ManifestProcessorTask {
+
+ private File testManifestFile;
+ private File tmpDir;
+ private String testApplicationId;
+ private String minSdkVersion;
+ private String targetSdkVersion;
+ private String testedApplicationId;
+ private String instrumentationRunner;
+ private Boolean handleProfiling;
+ private Boolean functionalTest;
+ private Map<String, Object> placeholdersValues;
+ private List<ManifestDependencyImpl> libraries;
+
+ @Override
+ protected void doFullTaskAction() throws IOException {
+ getBuilder().processTestManifest(
+ getTestApplicationId(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getTestedApplicationId(),
+ getInstrumentationRunner(),
+ getHandleProfiling(),
+ getFunctionalTest(),
+ getTestManifestFile(),
+ getLibraries(),
+ getPlaceholdersValues(),
+ getManifestOutputFile(),
+ getTmpDir());
+ }
+
+ @InputFile
+ @Optional
+ public File getTestManifestFile() {
+ return testManifestFile;
+ }
+
+ public void setTestManifestFile(File testManifestFile) {
+ this.testManifestFile = testManifestFile;
+ }
+
+ public File getTmpDir() {
+ return tmpDir;
+ }
+
+ public void setTmpDir(File tmpDir) {
+ this.tmpDir = tmpDir;
+ }
+
+ @Input
+ public String getTestApplicationId() {
+ return testApplicationId;
+ }
+
+ public void setTestApplicationId(String testApplicationId) {
+ this.testApplicationId = testApplicationId;
+ }
+
+ @Input
+ public String getMinSdkVersion() {
+ return minSdkVersion;
+ }
+
+ public void setMinSdkVersion(String minSdkVersion) {
+ this.minSdkVersion = minSdkVersion;
+ }
+
+ @Input
+ public String getTargetSdkVersion() {
+ return targetSdkVersion;
+ }
+
+ public void setTargetSdkVersion(String targetSdkVersion) {
+ this.targetSdkVersion = targetSdkVersion;
+ }
+
+ @Input
+ public String getTestedApplicationId() {
+ return testedApplicationId;
+ }
+
+ public void setTestedApplicationId(String testedApplicationId) {
+ this.testedApplicationId = testedApplicationId;
+ }
+
+ @Input
+ public String getInstrumentationRunner() {
+ return instrumentationRunner;
+ }
+
+ public void setInstrumentationRunner(String instrumentationRunner) {
+ this.instrumentationRunner = instrumentationRunner;
+ }
+
+ @Input
+ public Boolean getHandleProfiling() {
+ return handleProfiling;
+ }
+
+ public void setHandleProfiling(Boolean handleProfiling) {
+ this.handleProfiling = handleProfiling;
+ }
+
+ @Input
+ public Boolean getFunctionalTest() {
+ return functionalTest;
+ }
+
+ public void setFunctionalTest(Boolean functionalTest) {
+ this.functionalTest = functionalTest;
+ }
+
+ @Input
+ public Map<String, Object> getPlaceholdersValues() {
+ return placeholdersValues;
+ }
+
+ public void setPlaceholdersValues(
+ Map<String, Object> placeholdersValues) {
+ this.placeholdersValues = placeholdersValues;
+ }
+
+ public List<ManifestDependencyImpl> getLibraries() {
+ return libraries;
+ }
+
+ public void setLibraries(
+ List<ManifestDependencyImpl> libraries) {
+ this.libraries = libraries;
+ }
+
+ /**
+ * A synthetic input to allow gradle up-to-date checks to work.
+ *
+ * Since List<ManifestDependencyImpl> can't be used directly, as @Nested doesn't work on lists,
+ * this method gathers and returns the underlying manifest files.
+ */
+ @SuppressWarnings("unused")
+ @InputFiles
+ public List<File> getLibraryManifests() {
+ List<ManifestDependencyImpl> libs = getLibraries();
+ if (libs == null || libs.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<File> files = Lists.newArrayListWithCapacity(libs.size() * 2);
+ for (ManifestDependencyImpl mdi : libs) {
+ files.addAll(mdi.getAllManifests());
+ }
+
+ return files;
+ }
+
+ public static class ConfigAction implements TaskConfigAction<ProcessTestManifest> {
+
+ private VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("process", "Manifest");
+ }
+
+ @NonNull
+ @Override
+ public Class<ProcessTestManifest> getType() {
+ return ProcessTestManifest.class;
+ }
+
+ @Override
+ public void execute(final ProcessTestManifest processTestManifestTask) {
+
+ final VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> config =
+ scope.getVariantConfiguration();
+
+ processTestManifestTask.setTestManifestFile(config.getMainManifest());
+
+ processTestManifestTask.setTmpDir(
+ new File(scope.getGlobalScope().getIntermediatesDir(), "manifest/tmp"));
+
+ // get single output for now.
+ final BaseVariantOutputData variantOutputData =
+ scope.getVariantData().getOutputs().get(0);
+
+ variantOutputData.manifestProcessorTask = processTestManifestTask;
+
+ processTestManifestTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
+ processTestManifestTask.setVariantName(config.getFullName());
+
+ processTestManifestTask.setTestApplicationId(config.getApplicationId());
+ ConventionMappingHelper.map(processTestManifestTask, "minSdkVersion",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (scope.getGlobalScope().getAndroidBuilder().isPreviewTarget()) {
+ return scope.getGlobalScope().getAndroidBuilder()
+ .getTargetCodename();
+ }
+ return config.getMinSdkVersion().getApiString();
+ }
+ });
+
+ ConventionMappingHelper.map(processTestManifestTask, "targetSdkVersion",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (scope.getGlobalScope().getAndroidBuilder().isPreviewTarget()) {
+ return scope.getGlobalScope().getAndroidBuilder()
+ .getTargetCodename();
+ }
+
+ return config.getTargetSdkVersion().getApiString();
+ }
+ });
+ ConventionMappingHelper.map(processTestManifestTask, "testedApplicationId",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return config.getTestedApplicationId();
+ }
+ });
+ ConventionMappingHelper.map(processTestManifestTask, "instrumentationRunner",
+ new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return config.getInstrumentationRunner();
+ }
+ });
+
+ ConventionMappingHelper.map(processTestManifestTask, "handleProfiling",
+ new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return config.getHandleProfiling();
+ }
+ });
+ ConventionMappingHelper.map(processTestManifestTask, "functionalTest",
+ new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return config.getFunctionalTest();
+ }
+ });
+
+ ConventionMappingHelper.map(processTestManifestTask, "libraries",
+ new Callable<List<ManifestDependencyImpl>>() {
+ @Override
+ public List<ManifestDependencyImpl> call() throws Exception {
+ return DependencyManager.getManifestDependencies(
+ config.getDirectLibraries());
+ }
+ });
+
+ processTestManifestTask.setManifestOutputFile(
+ variantOutputData.getScope().getManifestOutputFile());
+
+ ConventionMappingHelper.map(processTestManifestTask, "placeholdersValues",
+ new Callable<Map<String, Object>>() {
+ @Override
+ public Map<String, Object> call() throws Exception {
+ return config.getManifestPlaceholders();
+ }
+ });
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy
new file mode 100644
index 0000000..fb4d77c
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.core.GradleVariantConfiguration
+import com.android.build.gradle.internal.scope.ConventionMappingHelper
+import com.android.build.gradle.internal.scope.TaskConfigAction
+import com.android.build.gradle.internal.scope.VariantScope
+import com.android.build.gradle.internal.tasks.NdkTask
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.BaseVariantOutputData
+import com.android.ide.common.process.LoggedProcessOutputHandler
+import com.android.utils.FileUtils
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.ParallelizableTask
+import org.gradle.api.tasks.TaskAction
+
+import static com.android.builder.model.AndroidProject.FD_GENERATED
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
+/**
+ * Task to compile Renderscript files. Supports incremental update.
+ */
+ at ParallelizableTask
+public class RenderscriptCompile extends NdkTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File sourceOutputDir
+
+ @OutputDirectory
+ File resOutputDir
+
+ @OutputDirectory
+ File objOutputDir
+
+ @OutputDirectory
+ File libOutputDir
+
+
+ // ----- PRIVATE TASK API -----
+ @Input
+ String getBuildToolsVersion() {
+ getBuildTools().getRevision()
+ }
+
+ @InputFiles
+ List<File> sourceDirs
+
+ @InputFiles
+ List<File> importDirs
+
+ @Input
+ Integer targetApi
+
+ @Input
+ boolean supportMode
+
+ @Input
+ int optimLevel
+
+ @Input
+ boolean debugBuild
+
+ @Input
+ boolean ndkMode
+
+ @TaskAction
+ void taskAction() throws IOException {
+ // this is full run (always), clean the previous outputs
+ File sourceDestDir = getSourceOutputDir()
+ FileUtils.emptyFolder(sourceDestDir)
+
+ File resDestDir = getResOutputDir()
+ FileUtils.emptyFolder(resDestDir)
+
+ File objDestDir = getObjOutputDir()
+ FileUtils.emptyFolder(objDestDir)
+
+ File libDestDir = getLibOutputDir()
+ FileUtils.emptyFolder(libDestDir)
+
+ // get the import folders. If the .rsh files are not directly under the import folders,
+ // we need to get the leaf folders, as this is what llvm-rs-cc expects.
+ List<File> importFolders = getBuilder().getLeafFolders("rsh",
+ getImportDirs(), getSourceDirs())
+
+ getBuilder().compileAllRenderscriptFiles(
+ getSourceDirs(),
+ importFolders,
+ sourceDestDir,
+ resDestDir,
+ objDestDir,
+ libDestDir,
+ getTargetApi(),
+ getDebugBuild(),
+ getOptimLevel(),
+ getNdkMode(),
+ getSupportMode(),
+ getNdkConfig()?.abiFilters,
+ new LoggedProcessOutputHandler(getILogger()))
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements TaskConfigAction<RenderscriptCompile> {
+
+ @NonNull
+ VariantScope scope
+
+ ConfigAction(VariantScope scope) {
+ this.scope = scope
+ }
+
+ @Override
+ String getName() {
+ return scope.getTaskName("compile", "Renderscript");
+ }
+
+ @Override
+ Class<RenderscriptCompile> getType() {
+ return RenderscriptCompile
+ }
+
+ @Override
+ void execute(RenderscriptCompile renderscriptTask) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
+ GradleVariantConfiguration config = variantData.variantConfiguration
+
+ variantData.renderscriptCompileTask = renderscriptTask
+ boolean ndkMode = config.renderscriptNdkModeEnabled
+ renderscriptTask.androidBuilder = scope.globalScope.androidBuilder
+ renderscriptTask.setVariantName(config.getFullName())
+
+ ConventionMappingHelper.map(renderscriptTask, "targetApi") {
+ config.getRenderscriptTarget()
+ }
+
+ renderscriptTask.supportMode = config.renderscriptSupportModeEnabled
+ renderscriptTask.ndkMode = ndkMode
+ renderscriptTask.debugBuild = config.buildType.renderscriptDebuggable
+ renderscriptTask.optimLevel = config.buildType.renderscriptOptimLevel
+
+ ConventionMappingHelper.map(renderscriptTask, "sourceDirs") { config.renderscriptSourceList }
+ ConventionMappingHelper.map(renderscriptTask, "importDirs") { config.renderscriptImports }
+
+ ConventionMappingHelper.map(renderscriptTask, "sourceOutputDir") {
+ new File(
+ "$scope.globalScope.buildDir/${FD_GENERATED}/source/rs/${variantData.variantConfiguration.dirName}")
+ }
+ ConventionMappingHelper.map(renderscriptTask, "resOutputDir") {
+ scope.getRenderscriptResOutputDir()
+ }
+ ConventionMappingHelper.map(renderscriptTask, "objOutputDir") {
+ new File(
+ "$scope.globalScope.buildDir/${FD_INTERMEDIATES}/rs/${variantData.variantConfiguration.dirName}/obj")
+ }
+ ConventionMappingHelper.map(renderscriptTask, "libOutputDir") {
+ scope.getRenderscriptLibOutputDir()
+ }
+ ConventionMappingHelper.map(renderscriptTask, "ndkConfig") { config.ndkConfig }
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceException.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceException.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceException.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceException.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzer.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzer.java
new file mode 100644
index 0000000..21b310f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzer.java
@@ -0,0 +1,1648 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.DOT_9PNG;
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FD_RES_VALUES;
+import static com.android.SdkConstants.TAG_RESOURCES;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static com.google.common.base.Charsets.UTF_8;
+import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
+import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.checks.ResourceUsageModel;
+import com.android.tools.lint.checks.ResourceUsageModel.Resource;
+import com.android.tools.lint.checks.StringFormatDetector;
+import com.android.utils.AsmUtils;
+import com.android.utils.Pair;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Class responsible for searching through a Gradle built tree (after resource merging,
+ * compilation and ProGuarding has been completed, but before final .apk assembly), which
+ * figures out which resources if any are unused, and removes them.
+ * <p>
+ * It does this by examining
+ * <ul>
+ * <li>The merged manifest, to find root resource references (such as drawables
+ * used for activity icons)</li>
+ * <li>The merged R class (to find the actual integer constants assigned to resources)</li>
+ * <li>The ProGuard log files (to find the mapping from original symbol names to
+ * short names)</li>*
+ * <li>The merged resources (to find which resources reference other resources, e.g.
+ * drawable state lists including other drawables, or layouts including other
+ * layouts, or styles referencing other drawables, or menus items including action
+ * layouts, etc.)</li>
+ * <li>The ProGuard output classes (to find resource references in code that are
+ * actually reachable)</li>
+ * </ul>
+ * From all this, it builds up a reference graph, and based on the root references (e.g.
+ * from the manifest and from the remaining code) it computes which resources are actually
+ * reachable in the app, and anything that is not reachable is then marked for deletion.
+ * <p>
+ * A resource is referenced in code if either the field R.type.name is referenced (which
+ * is the case for non-final resource references, e.g. in libraries), or if the corresponding
+ * int value is referenced (for final resource values). We check this by looking at the
+ * ProGuard output classes with an ASM visitor. One complication is that code can also
+ * call {@code Resources#getIdentifier(String,String,String)} where they can pass in the names
+ * of resources to look up. To handle this scenario, we use the ClassVisitor to see if
+ * there are any calls to the specific {@code Resources#getIdentifier} method. If not,
+ * great, the usage analysis is completely accurate. If we <b>do</b> find one, we check
+ * <b>all</b> the string constants found anywhere in the app, and look to see if any look
+ * relevant. For example, if we find the string "string/foo" or "my.pkg:string/foo", we
+ * will then mark the string resource named foo (if any) as potentially used. Similarly,
+ * if we find just "foo" or "/foo", we will mark <b>all</b> resources named "foo" as
+ * potentially used. However, if the string is "bar/foo" or " foo " these strings are
+ * ignored. This means we can potentially miss resources usages where the resource name
+ * is completed computed (e.g. by concatenating individual characters or taking substrings
+ * of strings that do not look like resource names), but that seems extremely unlikely
+ * to be a real-world scenario.
+ * <p>
+ * For now, for reasons detailed in the code, this only applies to file-based resources
+ * like layouts, menus and drawables, not value-based resources like strings and dimensions.
+ */
+public class ResourceUsageAnalyzer {
+ private static final String ANDROID_RES = "android_res/";
+
+ /**
+ * Whether we should create small/empty dummy files instead of actually
+ * removing file resources. This is to work around crashes on some devices
+ * where the device is traversing resources. See http://b.android.com/79325 for more.
+ */
+ public static final boolean REPLACE_DELETED_WITH_EMPTY = true;
+
+ /**
+ Whether we support running aapt twice, to regenerate the resources.arsc file
+ such that we can strip out value resources as well. We don't do this yet, for
+ reasons detailed in the ShrinkResources task
+
+ We have two options:
+ (1) Copy the resource files over to a new destination directory, filtering out
+ removed file resources and rewriting value resource files by stripping out
+ the declarations for removed value resources. We then re-run aapt on this
+ new destination directory.
+
+ The problem with this approach is that when we re-run aapt it will assign new
+ id's to all the resources, so we have to create dummy placeholders for all the
+ removed resources. (The alternative would be to then run compilation one more
+ time -- regenerating classes.jar, regenerating .dex) -- this would really slow
+ down builds.)
+
+ A cleaner solution than this is to get aapt to support using a predefined set
+ of id's. It can emit R.txt symbol files now; if we can get it to read R.txt
+ and use those numbers in its assignment, we can solve this cleanly. This request
+ is tracked in https://code.google.com/p/android/issues/detail?id=70869
+
+ (2) Just rewrite the .ap_ file directly. It's just a .zip file which contains
+ (a) binary files for bitmaps and XML file resources such as layouts and menus
+ (b) a binary file, resources.arsc, containing all the values.
+ The resources.arsc format is opaque to us. However, MOST of the resource bulk
+ comes from the bitmap and other resource files.
+
+ So here we don't even need to run aapt a second time; we simply rewrite the
+ .ap_ zip file directly, filtering out res/ files we know to be unused.
+
+ Approach #2 gives us most of the space savings without the risk of #1 (running aapt
+ a second time introduces the possibility of aapt compilation errors if we haven't
+ been careful enough to insert resource aliases for all necessary items (such as
+ inline @+id declarations), or if we haven't carefully not created aliases for items
+ already defined in other value files as aliases, and perhaps most importantly,
+ introduces risk that aapt will pick a different resource order anyway, which we can
+ only guard against by doing a full compilation over again.
+
+ Therefore, for now the below code uses #2, but since we can solve #1 with support
+ from aapt), we're preserving all the code to rewrite resource files since that will
+ give additional space savings, particularly for apps with a lot of strings or a lot
+ of translations.
+ */
+ @SuppressWarnings("SpellCheckingInspection") // arsc
+ public static final boolean TWO_PASS_AAPT = false;
+
+ /** Special marker regexp which does not match a resource name */
+ static final String NO_MATCH = "-nomatch-";
+
+ private final File mResourceClassDir;
+ private final File mProguardMapping;
+ private final File mClasses;
+ private final File mMergedManifest;
+ private final File mMergedResourceDir;
+
+ private final File mReportFile;
+ private final StringWriter mDebugOutput;
+ private final PrintWriter mDebugPrinter;
+
+ private boolean mVerbose;
+ private boolean mDebug;
+ private boolean mDryRun;
+
+ /** The computed set of unused resources */
+ private List<Resource> mUnused;
+
+ /**
+ * Map from resource class owners (VM format class) to corresponding resource entries.
+ * This lets us map back from code references (obfuscated class and possibly obfuscated field
+ * reference) back to the corresponding resource type and name.
+ */
+ private Map<String, Pair<ResourceType, Map<String, String>>> mResourceObfuscation =
+ Maps.newHashMapWithExpectedSize(30);
+
+ /** Obfuscated name of android/support/v7/widget/SuggestionsAdapter.java */
+ private String mSuggestionsAdapter;
+
+ /** Obfuscated name of android/support/v7/internal/widget/ResourcesWrapper.java */
+ private String mResourcesWrapper;
+
+ public ResourceUsageAnalyzer(
+ @NonNull File rDir,
+ @NonNull File classes,
+ @NonNull File manifest,
+ @Nullable File mapping,
+ @NonNull File resources,
+ @Nullable File reportFile) {
+ mResourceClassDir = rDir;
+ mProguardMapping = mapping;
+ mClasses = classes;
+ mMergedManifest = manifest;
+ mMergedResourceDir = resources;
+
+ mReportFile = reportFile;
+ if (reportFile != null || mDebug) {
+ mDebugOutput = new StringWriter(8*1024);
+ mDebugPrinter = new PrintWriter(mDebugOutput);
+ } else {
+ mDebugOutput = null;
+ mDebugPrinter = null;
+ }
+ }
+
+ public void dispose() {
+ if (mDebugOutput != null) {
+ String output = mDebugOutput.toString();
+
+ if (mDebug) {
+ System.out.println(output);
+ }
+
+ if (mReportFile != null) {
+ File dir = mReportFile.getParentFile();
+ if (dir != null) {
+ if ((dir.exists() || dir.mkdir()) && dir.canWrite()) {
+ try {
+ Files.write(output, mReportFile, Charsets.UTF_8);
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void analyze() throws IOException, ParserConfigurationException, SAXException {
+ gatherResourceValues(mResourceClassDir);
+ recordMapping(mProguardMapping);
+ recordClassUsages(mClasses);
+ recordManifestUsages(mMergedManifest);
+ recordResources(mMergedResourceDir);
+ keepPossiblyReferencedResources();
+ dumpReferences();
+ mModel.processToolsAttributes();
+ mUnused = mModel.findUnused();
+ }
+
+ public boolean isDryRun() {
+ return mDryRun;
+ }
+
+ public void setDryRun(boolean dryRun) {
+ mDryRun = dryRun;
+ }
+
+ public boolean isVerbose() {
+ return mVerbose;
+ }
+
+ public void setVerbose(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+
+ public boolean isDebug() {
+ return mDebug;
+ }
+
+ public void setDebug(boolean verbose) {
+ mDebug = verbose;
+ }
+
+ // A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY
+ public static final byte[] TINY_PNG = new byte[] {
+ (byte)-119, (byte) 80, (byte) 78, (byte) 71, (byte) 13, (byte) 10,
+ (byte) 26, (byte) 10, (byte) 0, (byte) 0, (byte) 0, (byte) 13,
+ (byte) 73, (byte) 72, (byte) 68, (byte) 82, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 1, (byte) 0, (byte) 0, (byte) 0, (byte) 1,
+ (byte) 8, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 58,
+ (byte) 126, (byte)-101, (byte) 85, (byte) 0, (byte) 0, (byte) 0,
+ (byte) 10, (byte) 73, (byte) 68, (byte) 65, (byte) 84, (byte) 120,
+ (byte) -38, (byte) 99, (byte) 96, (byte) 0, (byte) 0, (byte) 0,
+ (byte) 2, (byte) 0, (byte) 1, (byte) -27, (byte) 39, (byte) -34,
+ (byte) -4, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 73,
+ (byte) 69, (byte) 78, (byte) 68, (byte) -82, (byte) 66, (byte) 96,
+ (byte)-126
+ };
+
+ public static final long TINY_PNG_CRC = 0x88b2a3b0L;
+
+ // A 3x3 pixel PNG of type BufferedImage.TYPE_INT_ARGB with 9-patch markers
+ public static final byte[] TINY_9PNG = new byte[] {
+ (byte)-119, (byte) 80, (byte) 78, (byte) 71, (byte) 13, (byte) 10,
+ (byte) 26, (byte) 10, (byte) 0, (byte) 0, (byte) 0, (byte) 13,
+ (byte) 73, (byte) 72, (byte) 68, (byte) 82, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 3, (byte) 0, (byte) 0, (byte) 0, (byte) 3,
+ (byte) 8, (byte) 6, (byte) 0, (byte) 0, (byte) 0, (byte) 86,
+ (byte) 40, (byte) -75, (byte) -65, (byte) 0, (byte) 0, (byte) 0,
+ (byte) 20, (byte) 73, (byte) 68, (byte) 65, (byte) 84, (byte) 120,
+ (byte) -38, (byte) 99, (byte) 96, (byte)-128, (byte)-128, (byte) -1,
+ (byte) 12, (byte) 48, (byte) 6, (byte) 8, (byte) -96, (byte) 8,
+ (byte)-128, (byte) 8, (byte) 0, (byte)-107, (byte)-111, (byte) 7,
+ (byte) -7, (byte) -64, (byte) -82, (byte) 8, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 0, (byte) 73, (byte) 69, (byte) 78,
+ (byte) 68, (byte) -82, (byte) 66, (byte) 96, (byte)-126
+ };
+
+ public static final long TINY_9PNG_CRC = 0x1148f987L;
+
+ // The XML document <x/> as binary-packed with AAPT
+ public static final byte[] TINY_XML = new byte[] {
+ (byte) 3, (byte) 0, (byte) 8, (byte) 0, (byte) 104, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 1, (byte) 0, (byte) 28, (byte) 0,
+ (byte) 36, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 1, (byte) 0, (byte) 0, (byte) 32, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 1,
+ (byte) 120, (byte) 0, (byte) 2, (byte) 1, (byte) 16, (byte) 0,
+ (byte) 36, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0,
+ (byte) 0, (byte) 0, (byte) -1, (byte) -1, (byte) -1, (byte) -1,
+ (byte) -1, (byte) -1, (byte) -1, (byte) -1, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 20, (byte) 0, (byte) 20, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 0, (byte) 3, (byte) 1, (byte) 16, (byte) 0,
+ (byte) 24, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0,
+ (byte) 0, (byte) 0, (byte) -1, (byte) -1, (byte) -1, (byte) -1,
+ (byte) -1, (byte) -1, (byte) -1, (byte) -1, (byte) 0, (byte) 0,
+ (byte) 0, (byte) 0
+ };
+
+ public static final long TINY_XML_CRC = 0xd7e65643L;
+
+ /**
+ * "Removes" resources from an .ap_ file by writing it out while filtering out
+ * unused resources. This won't touch the values XML data (resources.arsc) but
+ * will remove the individual file-based resources, which is where most of
+ * the data is anyway (usually in drawable bitmaps)
+ *
+ * @param source the .ap_ file created by aapt
+ * @param dest a new .ap_ file with unused file-based resources removed
+ */
+ public void rewriteResourceZip(@NonNull File source, @NonNull File dest)
+ throws IOException {
+ if (dest.exists()) {
+ boolean deleted = dest.delete();
+ if (!deleted) {
+ throw new IOException("Could not delete " + dest);
+ }
+ }
+
+ JarInputStream zis = null;
+ try {
+ FileInputStream fis = new FileInputStream(source);
+ try {
+ FileOutputStream fos = new FileOutputStream(dest);
+ zis = new JarInputStream(fis);
+ JarOutputStream zos = new JarOutputStream(fos);
+ try {
+ // Rather than using Deflater.DEFAULT_COMPRESSION we use 9 here,
+ // since that seems to match the compressed sizes we observe in source
+ // .ap_ files encountered by the resource shrinker:
+ zos.setLevel(9);
+
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+ boolean directory = entry.isDirectory();
+ Resource resource = getResourceByJarPath(name);
+ if (resource == null || resource.isReachable()) {
+ // We can't just compress all files; files that are not
+ // compressed in the source .ap_ file must be left uncompressed
+ // here, since for example RAW files need to remain uncompressed in
+ // the APK such that they can be mmap'ed at runtime.
+ // Preserve the STORED method of the input entry.
+ JarEntry outEntry;
+ if (entry.getMethod() == JarEntry.STORED) {
+ outEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ outEntry = new JarEntry(name);
+ if (entry.getTime() != -1L) {
+ outEntry.setTime(entry.getTime());
+ }
+ }
+
+ zos.putNextEntry(outEntry);
+
+ if (!directory) {
+ byte[] bytes = ByteStreams.toByteArray(zis);
+ if (bytes != null) {
+ zos.write(bytes);
+ }
+ }
+
+ zos.closeEntry();
+ } else //noinspection PointlessBooleanExpression
+ if (REPLACE_DELETED_WITH_EMPTY && !directory
+ // Canonical name for resource file that only contains keep rules
+ && !name.equals("res/raw/keep.xml")) {
+ // Create a new entry so that the compressed len is recomputed.
+ byte[] bytes;
+ long crc;
+ if (name.endsWith(DOT_9PNG)) {
+ bytes = TINY_9PNG;
+ crc = TINY_9PNG_CRC;
+ } else if (name.endsWith(DOT_PNG)) {
+ bytes = TINY_PNG;
+ crc = TINY_PNG_CRC;
+ } else if (name.endsWith(DOT_XML)) {
+ bytes = TINY_XML;
+ crc = TINY_XML_CRC;
+ } else {
+ bytes = new byte[0];
+ crc = 0L;
+ }
+ JarEntry outEntry = new JarEntry(name);
+ if (entry.getTime() != -1L) {
+ outEntry.setTime(entry.getTime());
+ }
+ if (entry.getMethod() == JarEntry.STORED) {
+ outEntry.setMethod(JarEntry.STORED);
+ outEntry.setSize(bytes.length);
+ outEntry.setCrc(crc);
+ }
+ zos.putNextEntry(outEntry);
+ zos.write(bytes);
+ zos.closeEntry();
+
+ if (isVerbose() || mDebugPrinter != null) {
+ String message = "Skipped unused resource " + name + ": " + entry
+ .getSize()
+ + " bytes (replaced with small dummy file of size "
+ + bytes.length + " bytes)";
+ if (isVerbose()) {
+ System.out.println(message);
+ }
+ if (mDebugPrinter != null) {
+ mDebugPrinter.println(message);
+ }
+ }
+ } else if (isVerbose() || mDebugPrinter != null) {
+ String message = "Skipped unused resource " + name + ": "
+ + entry.getSize() + " bytes";
+ if (isVerbose()) {
+ System.out.println(message);
+ }
+ if (mDebugPrinter != null) {
+ mDebugPrinter.println(message);
+ }
+ }
+ entry = zis.getNextEntry();
+ }
+ zos.flush();
+ } finally {
+ Closeables.close(zos, false);
+ }
+ } finally {
+ Closeables.close(fis, true);
+ }
+ } finally {
+ Closeables.close(zis, false);
+ }
+
+ // If net negative, copy original back. This is unusual, but can happen
+ // in some circumstances, such as the one described in
+ // https://plus.google.com/+SaidTahsinDane/posts/X9sTSwoVUhB
+ // "Removed unused resources: Binary resource data reduced from 588KB to 595KB: Removed -1%"
+ // Guard against that, and worst case, just use the original.
+ long before = source.length();
+ long after = dest.length();
+ if (after > before) {
+ String message = "Resource shrinking did not work (grew from " + before + " to "
+ + after + "); using original instead";
+ if (isVerbose()) {
+ System.out.println(message);
+ }
+ if (mDebugPrinter != null) {
+ mDebugPrinter.println(message);
+ }
+
+ Files.copy(source, dest);
+ }
+ }
+
+ /**
+ * Remove resources (already identified by {@link #analyze()}).
+ *
+ * This task will copy all remaining used resources over from the full resource
+ * directory to a new reduced resource directory. However, it can't just
+ * delete the resources, because it has no way to tell aapt to continue to use
+ * the same id's for the resources. When we re-run aapt on the stripped resource
+ * directory, it will assign new id's to some of the resources (to fill the gaps)
+ * which means the resource id's no longer match the constants compiled into the
+ * dex files, and as a result, the app crashes at runtime.
+ * <p>
+ * Therefore, it needs to preserve all id's by actually keeping all the resource
+ * names. It can still save a lot of space by making these resources tiny; e.g.
+ * all strings are set to empty, all styles, arrays and plurals are set to not contain
+ * any children, and most importantly, all file based resources like bitmaps and
+ * layouts are replaced by simple resource aliases which just point to @null.
+ *
+ * @param destination directory to copy resources into; if null, delete resources in place
+ * @throws IOException
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ */
+ public void removeUnused(@Nullable File destination) throws IOException,
+ ParserConfigurationException, SAXException {
+ if (TWO_PASS_AAPT) {
+ assert mUnused != null; // should always call analyze() first
+
+ int resourceCount = mUnused.size()
+ * 4; // *4: account for some resource folder repetition
+ boolean inPlace = destination == null;
+ Set<File> skip = inPlace ? null : Sets.<File>newHashSetWithExpectedSize(resourceCount);
+ Set<File> rewrite = Sets.newHashSetWithExpectedSize(resourceCount);
+ for (Resource resource : mUnused) {
+ if (resource.declarations != null) {
+ for (File file : resource.declarations) {
+ String folder = file.getParentFile().getName();
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folder);
+ if (folderType != null && folderType != ResourceFolderType.VALUES) {
+ if (isVerbose()) {
+ System.out.println("Deleted unused resource " + file);
+ }
+ if (inPlace) {
+ if (!isDryRun()) {
+ boolean delete = file.delete();
+ if (!delete) {
+ System.err.println("Could not delete " + file);
+ }
+ }
+ } else {
+ assert skip != null;
+ skip.add(file);
+ }
+ } else {
+ // Can't delete values immediately; there can be many resources
+ // in this file, so we have to process them all
+ rewrite.add(file);
+ }
+ }
+ }
+ }
+
+ // Special case the base values.xml folder
+ File values = new File(mMergedResourceDir,
+ FD_RES_VALUES + File.separatorChar + "values.xml");
+ boolean valuesExists = values.exists();
+ if (valuesExists) {
+ rewrite.add(values);
+ }
+
+ Map<File, String> rewritten = Maps.newHashMapWithExpectedSize(rewrite.size());
+
+ // Delete value resources: Must rewrite the XML files
+ for (File file : rewrite) {
+ String xml = Files.toString(file, UTF_8);
+ Document document = XmlUtils.parseDocument(xml, true);
+ Element root = document.getDocumentElement();
+ if (root != null && TAG_RESOURCES.equals(root.getTagName())) {
+ List<String> removed = Lists.newArrayList();
+ stripUnused(root, removed);
+ if (isVerbose()) {
+ System.out.println("Removed " + removed.size() +
+ " unused resources from " + file + ":\n " +
+ Joiner.on(", ").join(removed));
+ }
+
+ String formatted = XmlPrettyPrinter.prettyPrint(document, xml.endsWith("\n"));
+ rewritten.put(file, formatted);
+ }
+ }
+
+ if (isDryRun()) {
+ return;
+ }
+
+ if (valuesExists) {
+ String xml = rewritten.get(values);
+ if (xml == null) {
+ xml = Files.toString(values, UTF_8);
+ }
+ Document document = XmlUtils.parseDocument(xml, true);
+
+ assert false;
+ /* This doesn't work; we don't need this when we have stable aapt id's anyway
+ Element root = document.getDocumentElement();
+ for (Resource resource : mModel.getAllResources()) {
+ if (resource.type == ResourceType.ID && !resource.hasDefault) {
+ Element item = document.createElement(TAG_ITEM);
+ item.setAttribute(ATTR_TYPE, resource.type.getName());
+ item.setAttribute(ATTR_NAME, resource.name);
+ root.appendChild(item);
+ } else if (!resource.reachable
+ && !resource.hasDefault
+ && resource.type != ResourceType.DECLARE_STYLEABLE
+ && resource.type != ResourceType.STYLE
+ && resource.type != ResourceType.PLURALS
+ && resource.type != ResourceType.ARRAY
+ && resource.isRelevantType()) {
+ Element item = document.createElement(TAG_ITEM);
+ item.setAttribute(ATTR_TYPE, resource.type.getName());
+ item.setAttribute(ATTR_NAME, resource.name);
+ root.appendChild(item);
+ String s = "@null";
+ item.appendChild(document.createTextNode(s));
+ }
+ }
+ */
+
+ String formatted = XmlPrettyPrinter.prettyPrint(document, xml.endsWith("\n"));
+ rewritten.put(values, formatted);
+ }
+
+ if (inPlace) {
+ for (Map.Entry<File, String> entry : rewritten.entrySet()) {
+ File file = entry.getKey();
+ String formatted = entry.getValue();
+ Files.write(formatted, file, UTF_8);
+ }
+ } else {
+ filteredCopy(mMergedResourceDir, destination, skip, rewritten);
+ }
+ } else {
+ assert false;
+ }
+ }
+
+ /**
+ * Copies one resource directory tree into another; skipping some files, replacing
+ * the contents of some, and passing everything else through unmodified
+ */
+ private static void filteredCopy(File source, File destination, Set<File> skip,
+ Map<File, String> replace) throws IOException {
+ if (TWO_PASS_AAPT) {
+ if (source.isDirectory()) {
+ File[] children = source.listFiles();
+ if (children != null) {
+ if (!destination.exists()) {
+ boolean success = destination.mkdirs();
+ if (!success) {
+ throw new IOException("Could not create " + destination);
+ }
+ }
+ for (File child : children) {
+ filteredCopy(child, new File(destination, child.getName()), skip, replace);
+ }
+ }
+ } else if (!skip.contains(source) && source.isFile()) {
+ String contents = replace.get(source);
+ if (contents != null) {
+ Files.write(contents, destination, UTF_8);
+ } else {
+ Files.copy(source, destination);
+ }
+ }
+ } else {
+ assert false;
+ }
+ }
+
+ private void stripUnused(Element element, List<String> removed) {
+ if (TWO_PASS_AAPT) {
+ ResourceType type = ResourceUsageModel.getResourceType(element);
+ if (type == ResourceType.ATTR) {
+ // Not yet properly handled
+ return;
+ }
+
+ Resource resource = mModel.getResource(element);
+ if (resource != null) {
+ if (resource.type == ResourceType.DECLARE_STYLEABLE ||
+ resource.type == ResourceType.ATTR) {
+ // Don't strip children of declare-styleable; we're not correctly
+ // tracking field references of the R_styleable_attr fields yet
+ return;
+ }
+
+ if (!resource.isReachable() &&
+ (resource.type == ResourceType.STYLE ||
+ resource.type == ResourceType.PLURALS ||
+ resource.type == ResourceType.ARRAY)) {
+ NodeList children = element.getChildNodes();
+ for (int i = children.getLength() - 1; i >= 0; i--) {
+ Node child = children.item(i);
+ element.removeChild(child);
+ }
+ return;
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = children.getLength() - 1; i >= 0; i--) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ stripUnused((Element) child, removed);
+ }
+ }
+
+ if (resource != null && !resource.isReachable()) {
+ if (mVerbose) {
+ removed.add(resource.getUrl());
+ }
+ // for themes etc where .'s have been replaced by _'s
+ String name = element.getAttribute(ATTR_NAME);
+ if (name.isEmpty()) {
+ name = resource.name;
+ }
+ Node nextSibling = element.getNextSibling();
+ Node parent = element.getParentNode();
+ NodeList oldChildren = element.getChildNodes();
+ parent.removeChild(element);
+ Document document = element.getOwnerDocument();
+ element = document.createElement("item");
+ for (int i = 0; i < oldChildren.getLength(); i++) {
+ element.appendChild(oldChildren.item(i));
+ }
+
+ element.setAttribute(ATTR_NAME, name);
+ element.setAttribute(ATTR_TYPE, resource.type.getName());
+ String text = null;
+ switch (resource.type) {
+ case BOOL:
+ text = "true";
+ break;
+ case DIMEN:
+ text = "0dp";
+ break;
+ case INTEGER:
+ text = "0";
+ break;
+ }
+ element.setTextContent(text);
+ parent.insertBefore(element, nextSibling);
+ }
+ } else {
+ assert false;
+ }
+ }
+
+ @Nullable
+ private Resource getResourceByJarPath(String path) {
+ // Jars use forward slash paths, not File.separator
+ if (path.startsWith("res/")) {
+ int folderStart = 4; // "res/".length
+ int folderEnd = path.indexOf('/', folderStart);
+ if (folderEnd != -1) {
+ String folderName = path.substring(folderStart, folderEnd);
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
+ if (folderType != null) {
+ int nameStart = folderEnd + 1;
+ int nameEnd = path.indexOf('.', nameStart);
+ if (nameEnd == -1) {
+ nameEnd = path.length();
+ }
+ if (nameEnd != -1) {
+ String name = path.substring(nameStart, nameEnd);
+ List<ResourceType> types =
+ FolderTypeRelationship.getRelatedResourceTypes(folderType);
+ for (ResourceType type : types) {
+ if (type != ResourceType.ID) {
+ Resource resource = mModel.getResource(type, name);
+ if (resource != null) {
+ return resource;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void dumpReferences() {
+ if (mDebugPrinter != null) {
+ mDebugPrinter.print(mModel.dumpReferences());
+ }
+ }
+
+ private void keepPossiblyReferencedResources() {
+ if ((!mFoundGetIdentifier && !mFoundWebContent) || mStrings == null) {
+ // No calls to android.content.res.Resources#getIdentifier; no need
+ // to worry about string references to resources
+ return;
+ }
+
+ if (!mModel.isSafeMode()) {
+ // User specifically asked for us not to guess resources to keep; they will
+ // explicitly mark them as kept if necessary instead
+ return;
+ }
+
+ if (mDebugPrinter != null) {
+ List<String> strings = new ArrayList<String>(mStrings);
+ Collections.sort(strings);
+ mDebugPrinter.println("android.content.res.Resources#getIdentifier present: "
+ + mFoundGetIdentifier);
+ mDebugPrinter.println("Web content present: " + mFoundWebContent);
+ mDebugPrinter.println("Referenced Strings:");
+ for (String s : strings) {
+ s = s.trim().replace("\n", "\\n");
+ if (s.length() > 40) {
+ s = s.substring(0, 37) + "...";
+ } else if (s.isEmpty()) {
+ continue;
+ }
+ mDebugPrinter.println(" " + s);
+ }
+ }
+
+ int shortest = Integer.MAX_VALUE;
+ Set<String> names = Sets.newHashSetWithExpectedSize(50);
+ for (Resource resource : mModel.getResources()) {
+ String name = resource.name;
+ names.add(name);
+ int length = name.length();
+ if (length < shortest) {
+ shortest = length;
+ }
+ }
+
+ for (String string : mStrings) {
+ if (string.length() < shortest) {
+ continue;
+ }
+
+ // Check whether the string looks relevant
+ // We consider four types of strings:
+ // (1) simple resource names, e.g. "foo" from @layout/foo
+ // These might be the parameter to a getIdentifier() call, or could
+ // be composed into a fully qualified resource name for the getIdentifier()
+ // method. We match these for *all* resource types.
+ // (2) Relative source names, e.g. layout/foo, from @layout/foo
+ // These might be composed into a fully qualified resource name for
+ // getIdentifier().
+ // (3) Fully qualified resource names of the form package:type/name.
+ // (4) If mFoundWebContent is true, look for android_res/ URL strings as well
+
+ if (mFoundWebContent) {
+ Resource resource = mModel.getResourceFromFilePath(string);
+ if (resource != null) {
+ ResourceUsageModel.markReachable(resource);
+ continue;
+ } else {
+ int start = 0;
+ int slash = string.lastIndexOf('/');
+ if (slash != -1) {
+ start = slash + 1;
+ }
+ int dot = string.indexOf('.', start);
+ String name = string.substring(start, dot != -1 ? dot : string.length());
+ if (names.contains(name)) {
+ for (Map<String, Resource> map : mModel.getResourceMaps()) {
+ resource = map.get(name);
+ if (mDebug && resource != null) {
+ mDebugPrinter.println("Marking " + resource + " used because it "
+ + "matches string pool constant " + string);
+ }
+ ResourceUsageModel.markReachable(resource);
+ }
+ }
+ }
+ }
+
+ // Look for normal getIdentifier resource URLs
+ int n = string.length();
+ boolean justName = true;
+ boolean formatting = false;
+ boolean haveSlash = false;
+ for (int i = 0; i < n; i++) {
+ char c = string.charAt(i);
+ if (c == '/') {
+ haveSlash = true;
+ justName = false;
+ } else if (c == '.' || c == ':' || c == '%') {
+ justName = false;
+ if (c == '%') {
+ formatting = true;
+ }
+ } else if (!Character.isJavaIdentifierPart(c)) {
+ // This shouldn't happen; we've filtered out these strings in
+ // the {@link #referencedString} method
+ assert false : string;
+ break;
+ }
+ }
+
+ String name;
+ if (justName) {
+ // Check name (below)
+ name = string;
+
+ // Check for a simple prefix match, e.g. as in
+ // getResources().getIdentifier("ic_video_codec_" + codecName, "drawable", ...)
+ for (Resource resource : mModel.getResources()) {
+ if (resource.name.startsWith(name)) {
+ if (mDebugPrinter != null) {
+ mDebugPrinter.println("Marking " + resource + " used because its "
+ + "prefix matches string pool constant " + string);
+ }
+ ResourceUsageModel.markReachable(resource);
+ }
+ }
+ } else if (!haveSlash) {
+ if (formatting) {
+ // Possibly a formatting string, e.g.
+ // String name = String.format("my_prefix_%1d", index);
+ // int res = getContext().getResources().getIdentifier(name, "drawable", ...)
+
+ try {
+ Pattern pattern = Pattern.compile(convertFormatStringToRegexp(string));
+ for (Resource resource : mModel.getResources()) {
+ if (pattern.matcher(resource.name).matches()) {
+ if (mDebugPrinter != null) {
+ mDebugPrinter.println("Marking " + resource + " used because "
+ + "it format-string matches string pool constant "
+ + string);
+ }
+ ResourceUsageModel.markReachable(resource);
+ }
+ }
+ } catch (PatternSyntaxException ignored) {
+ // Might not have been a formatting string after all!
+ }
+ }
+
+ // If we have more than just a symbol name, we expect to also see a slash
+ //noinspection UnnecessaryContinue
+ continue;
+ } else {
+ // Try to pick out the resource name pieces; if we can find the
+ // resource type unambiguously; if not, just match on names
+ int slash = string.indexOf('/');
+ assert slash != -1; // checked with haveSlash above
+ name = string.substring(slash + 1);
+ if (name.isEmpty() || !names.contains(name)) {
+ continue;
+ }
+ // See if have a known specific resource type
+ if (slash > 0) {
+ int colon = string.indexOf(':');
+ String typeName = string.substring(colon != -1 ? colon + 1 : 0, slash);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type == null) {
+ continue;
+ }
+ Resource resource = mModel.getResource(type, name);
+ if (mDebug && resource != null) {
+ mDebugPrinter.println("Marking " + resource + " used because it "
+ + "matches string pool constant " + string);
+ }
+ ResourceUsageModel.markReachable(resource);
+ continue;
+ }
+
+ // fall through and check the name
+ }
+
+ if (names.contains(name)) {
+ for (Map<String, Resource> map : mModel.getResourceMaps()) {
+ Resource resource = map.get(name);
+ if (mDebug && resource != null) {
+ mDebugPrinter.println("Marking " + resource + " used because it "
+ + "matches string pool constant " + string);
+ }
+ ResourceUsageModel.markReachable(resource);
+ }
+ } else if (Character.isDigit(name.charAt(0))) {
+ // Just a number? There are cases where it calls getIdentifier by
+ // a String number; see for example SuggestionsAdapter in the support
+ // library which reports supporting a string like "2130837524" and
+ // "android.resource://com.android.alarmclock/2130837524".
+ try {
+ int id = Integer.parseInt(name);
+ if (id != 0) {
+ ResourceUsageModel.markReachable(mModel.getResource(id));
+ }
+ } catch (NumberFormatException e) {
+ // pass
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static String convertFormatStringToRegexp(String formatString) {
+ StringBuilder regexp = new StringBuilder();
+ int from = 0;
+ boolean hasEscapedLetters = false;
+ Matcher matcher = StringFormatDetector.FORMAT.matcher(formatString);
+ int length = formatString.length();
+ while (matcher.find(from)) {
+ int start = matcher.start();
+ int end = matcher.end();
+ if (start == 0 && end == length) {
+ // Don't match if the entire string literal starts with % and ends with
+ // the a formatting character, such as just "%d": this just matches absolutely
+ // everything and is unlikely to be used in a resource lookup
+ return NO_MATCH;
+ }
+ if (start > from) {
+ hasEscapedLetters |= appendEscapedPattern(formatString, regexp, from, start);
+ }
+ // If the wildcard follows a previous wildcard, just skip it
+ // (e.g. don't convert %s%s into .*.*; .* is enough.
+ int regexLength = regexp.length();
+ if (regexLength < 2
+ || regexp.charAt(regexLength - 1) != '*'
+ || regexp.charAt(regexLength - 2) != '.') {
+ regexp.append(".*");
+ }
+ from = end;
+ }
+
+ if (from < length) {
+ hasEscapedLetters |= appendEscapedPattern(formatString, regexp, from, length);
+ }
+
+ if (!hasEscapedLetters) {
+ // If the regexp contains *only* formatting characters, e.g. "%.0f%d", or
+ // if it contains only formatting characters and punctuation, e.g. "%s_%d",
+ // don't treat this as a possible resource name pattern string: it is unlikely
+ // to be intended for actual resource names, and has the side effect of matching
+ // most names.
+ return NO_MATCH;
+ }
+
+ return regexp.toString();
+ }
+
+ /**
+ * Appends the characters in the range [from,to> from formatString as escaped
+ * regexp characters into the given string builder. Returns true if there were
+ * any letters in the appended text.
+ */
+ private static boolean appendEscapedPattern(@NonNull String formatString,
+ @NonNull StringBuilder regexp, int from, int to) {
+ regexp.append(Pattern.quote(formatString.substring(from, to)));
+
+ for (int i = from; i < to; i++) {
+ if (Character.isLetter(formatString.charAt(i))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void recordResources(File resDir)
+ throws IOException, SAXException, ParserConfigurationException {
+ File[] resourceFolders = resDir.listFiles();
+ if (resourceFolders != null) {
+ for (File folder : resourceFolders) {
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folder.getName());
+ if (folderType != null) {
+ recordResources(folderType, folder);
+ }
+ }
+ }
+ }
+
+ private void recordResources(@NonNull ResourceFolderType folderType, File folder)
+ throws ParserConfigurationException, SAXException, IOException {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ String path = file.getPath();
+ mModel.file = file;
+ try {
+ boolean isXml = endsWithIgnoreCase(path, DOT_XML);
+ if (isXml) {
+ String xml = Files.toString(file, UTF_8);
+ Document document = XmlUtils.parseDocument(xml, true);
+ mModel.visitXmlDocument(file, folderType, document);
+ } else {
+ mModel.visitBinaryResource(folderType, file);
+ }
+ } finally {
+ mModel.file = null;
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void recordMapping(@Nullable File mapping) throws IOException {
+ if (mapping == null || !mapping.exists()) {
+ return;
+ }
+ final String ARROW = " -> ";
+ final String RESOURCE = ".R$";
+ Map<String, String> nameMap = null;
+ for (String line : Files.readLines(mapping, UTF_8)) {
+ if (line.startsWith(" ") || line.startsWith("\t")) {
+ if (nameMap != null) {
+ // We're processing the members of a resource class: record names into the map
+ int n = line.length();
+ int i = 0;
+ for (; i < n; i++) {
+ if (!Character.isWhitespace(line.charAt(i))) {
+ break;
+ }
+ }
+ if (i < n && line.startsWith("int", i)) { // int or int[]
+ int start = line.indexOf(' ', i + 3) + 1;
+ int arrow = line.indexOf(ARROW);
+ if (start > 0 && arrow != -1) {
+ int end = line.indexOf(' ', start + 1);
+ if (end != -1) {
+ String oldName = line.substring(start, end);
+ String newName = line.substring(arrow + ARROW.length()).trim();
+ if (!newName.equals(oldName)) {
+ nameMap.put(newName, oldName);
+ }
+ }
+ }
+ }
+ }
+ continue;
+ } else {
+ nameMap = null;
+ }
+ int index = line.indexOf(RESOURCE);
+ if (index == -1) {
+ // Record obfuscated names of a few known appcompat usages of
+ // Resources#getIdentifier that are unlikely to be used for general
+ // resource name reflection
+ if (line.startsWith("android.support.v7.widget.SuggestionsAdapter ")) {
+ mSuggestionsAdapter = line.substring(line.indexOf(ARROW) + ARROW.length(),
+ line.indexOf(':') != -1 ? line.indexOf(':') : line.length())
+ .trim().replace('.','/') + DOT_CLASS;
+ } else if (line.startsWith("android.support.v7.internal.widget.ResourcesWrapper ")
+ || line.startsWith("android.support.v7.widget.ResourcesWrapper ")
+ || (mResourcesWrapper == null // Recently wrapper moved
+ && line.startsWith("android.support.v7.widget.TintContextWrapper$TintResources "))) {
+ mResourcesWrapper = line.substring(line.indexOf(ARROW) + ARROW.length(),
+ line.indexOf(':') != -1 ? line.indexOf(':') : line.length())
+ .trim().replace('.','/') + DOT_CLASS;
+ }
+ continue;
+ }
+ int arrow = line.indexOf(ARROW, index + 3);
+ if (arrow == -1) {
+ continue;
+ }
+ String typeName = line.substring(index + RESOURCE.length(), arrow);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type == null) {
+ continue;
+ }
+ int end = line.indexOf(':', arrow + ARROW.length());
+ if (end == -1) {
+ end = line.length();
+ }
+ String target = line.substring(arrow + ARROW.length(), end).trim();
+ String ownerName = AsmUtils.toInternalName(target);
+
+ nameMap = Maps.newHashMap();
+ Pair<ResourceType, Map<String, String>> pair = Pair.of(type, nameMap);
+ mResourceObfuscation.put(ownerName, pair);
+ // For fast lookup in isResourceClass
+ mResourceObfuscation.put(ownerName + DOT_CLASS, pair);
+ }
+ }
+
+ private void recordManifestUsages(File manifest)
+ throws IOException, ParserConfigurationException, SAXException {
+ String xml = Files.toString(manifest, UTF_8);
+ Document document = XmlUtils.parseDocument(xml, true);
+ mModel.visitXmlDocument(manifest, null, document);
+ }
+
+ private Set<String> mStrings;
+ private boolean mFoundGetIdentifier;
+ private boolean mFoundWebContent;
+
+ private void referencedString(@NonNull String string) {
+ // See if the string is at all eligible; ignore strings that aren't
+ // identifiers (has java identifier chars and nothing but .:/), or are empty or too long
+ // We also allow "%", used for formatting strings.
+ if (string.isEmpty() || string.length() > 80) {
+ return;
+ }
+ boolean haveIdentifierChar = false;
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+ boolean identifierChar = Character.isJavaIdentifierPart(c);
+ if (!identifierChar && c != '.' && c != ':' && c != '/' && c != '%') {
+ // .:/ are for the fully qualified resource names, or for resource URLs or
+ // relative file names
+ return;
+ } else if (identifierChar) {
+ haveIdentifierChar = true;
+ }
+ }
+ if (!haveIdentifierChar) {
+ return;
+ }
+
+ if (mStrings == null) {
+ mStrings = Sets.newHashSetWithExpectedSize(300);
+ }
+ mStrings.add(string);
+
+ if (!mFoundWebContent && string.contains(ANDROID_RES)) {
+ mFoundWebContent = true;
+ }
+ }
+
+ private void recordClassUsages(File file) throws IOException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ recordClassUsages(child);
+ }
+ }
+ } else if (file.isFile()) {
+ if (file.getPath().endsWith(DOT_CLASS)) {
+ byte[] bytes = Files.toByteArray(file);
+ recordClassUsages(file, file.getName(), bytes);
+ } else if (file.getPath().endsWith(DOT_JAR)) {
+ ZipInputStream zis = null;
+ try {
+ FileInputStream fis = new FileInputStream(file);
+ try {
+ zis = new ZipInputStream(fis);
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+ if (name.endsWith(DOT_CLASS) &&
+ // Skip resource type classes like R$drawable; they will
+ // reference the integer id's we're looking for, but these aren't
+ // actual usages we need to track; if somebody references the
+ // field elsewhere, we'll catch that
+ !isResourceClass(name)) {
+ byte[] bytes = ByteStreams.toByteArray(zis);
+ if (bytes != null) {
+ recordClassUsages(file, name, bytes);
+ }
+ }
+
+ entry = zis.getNextEntry();
+ }
+ } finally {
+ Closeables.close(fis, true);
+ }
+ } finally {
+ Closeables.close(zis, true);
+ }
+ }
+ }
+ }
+
+ private void recordClassUsages(File file, String name, byte[] bytes) {
+ ClassReader classReader = new ClassReader(bytes);
+ classReader.accept(new UsageVisitor(file, name), SKIP_DEBUG | SKIP_FRAMES);
+ }
+
+ /** Returns whether the given class file name points to an aapt-generated compiled R class */
+ @VisibleForTesting
+ boolean isResourceClass(@NonNull String name) {
+ if (mResourceObfuscation.containsKey(name)) {
+ return true;
+ }
+ assert name.endsWith(DOT_CLASS) : name;
+ int index = name.lastIndexOf('/');
+ if (index != -1 && name.startsWith("R$", index + 1)) {
+ String typeName = name.substring(index + 3, name.length() - DOT_CLASS.length());
+ return ResourceType.getEnum(typeName) != null;
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ Resource getResourceFromCode(@NonNull String owner, @NonNull String name) {
+ Pair<ResourceType, Map<String, String>> pair = mResourceObfuscation.get(owner);
+ if (pair != null) {
+ ResourceType type = pair.getFirst();
+ Map<String, String> nameMap = pair.getSecond();
+ String renamedField = nameMap.get(name);
+ if (renamedField != null) {
+ name = renamedField;
+ }
+ return mModel.getResource(type, name);
+ }
+ return null;
+ }
+
+ private void gatherResourceValues(File file) throws IOException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ gatherResourceValues(child);
+ }
+ }
+ } else if (file.isFile() && file.getName().equals(SdkConstants.FN_RESOURCE_CLASS)) {
+ parseResourceClass(file);
+ }
+ }
+
+ // TODO: Use Lombok/ECJ here
+ private void parseResourceClass(File file) throws IOException {
+ String s = Files.toString(file, UTF_8);
+ // Simple parser which handles only aapt's special R output
+ String pkg = null;
+ int index = s.indexOf("package ");
+ if (index != -1) {
+ int end = s.indexOf(';', index);
+ pkg = s.substring(index + "package ".length(), end).trim().replace('.', '/');
+ }
+ index = 0;
+ int length = s.length();
+ String classDeclaration = "public static final class ";
+ while (true) {
+ index = s.indexOf(classDeclaration, index);
+ if (index == -1) {
+ break;
+ }
+ int start = index + classDeclaration.length();
+ int end = s.indexOf(' ', start);
+ if (end == -1) {
+ break;
+ }
+ String typeName = s.substring(start, end);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type == null) {
+ break;
+ }
+
+ if (pkg != null) {
+ String owner = pkg + "/R$" + type.getName();
+ Pair<ResourceType, Map<String, String>> pair = mResourceObfuscation.get(owner);
+ if (pair == null) {
+ Map<String, String> nameMap = Maps.newHashMap();
+ pair = Pair.of(type, nameMap);
+ }
+ mResourceObfuscation.put(owner, pair);
+ }
+
+ index = end;
+
+ // Find next declaration
+ for (; index < length - 1; index++) {
+ char c = s.charAt(index);
+ if (Character.isWhitespace(c)) {
+ //noinspection UnnecessaryContinue
+ continue;
+ }
+
+ if (c == '/') {
+ char next = s.charAt(index + 1);
+ if (next == '*') {
+ // Scan forward to comment end
+ end = index + 2;
+ while (end < length -2) {
+ c = s.charAt(end);
+ if (c == '*' && s.charAt(end + 1) == '/') {
+ end++;
+ break;
+ } else {
+ end++;
+ }
+ }
+ index = end;
+ } else if (next == '/') {
+ // Scan forward to next newline
+ assert false : s.substring(index - 1, index + 50); // we don't put line comments in R files
+ } else {
+ assert false : s.substring(index - 1, index + 50); // unexpected division
+ }
+ } else if (c == 'p' && s.startsWith("public ", index)) {
+ if (type == ResourceType.STYLEABLE) {
+ start = s.indexOf(" int", index);
+ if (s.startsWith(" int[] ", start)) {
+ start += " int[] ".length();
+ end = s.indexOf('=', start);
+ assert end != -1;
+ String styleable = s.substring(start, end).trim();
+ mModel.addResource(ResourceType.DECLARE_STYLEABLE, styleable, null);
+ mModel.addResource(ResourceType.STYLEABLE, styleable, null);
+ // TODO: Read in all the action bar ints!
+ // For now, we're simply treating all R.attr fields as used
+ index = s.indexOf(';', index);
+ if (index == -1) {
+ break;
+ }
+ } else if (s.startsWith(" int ", start)) {
+ // Read these fields in and correlate with the attr R's. Actually
+ // we don't need this for anything; the local attributes are
+ // found by the R attr thing. I just need to record the class
+ // (style).
+ // public static final int ActionBar_background = 10;
+ // ignore - jump to end
+ index = s.indexOf(';', index);
+ if (index == -1) {
+ break;
+ }
+ // For now, we're simply treating all R.attr fields as used
+ }
+ } else {
+ start = s.indexOf(" int ", index);
+ if (start != -1) {
+ start += " int ".length();
+ // e.g. abc_fade_in=0x7f040000;
+ end = s.indexOf('=', start);
+ assert end != -1;
+ String name = s.substring(start, end).trim();
+ start = end + 1;
+ end = s.indexOf(';', start);
+ assert end != -1;
+ String value = s.substring(start, end).trim();
+ mModel.addResource(type, name, value);
+ }
+ }
+ } else if (c == '}') {
+ // Done with resource class
+ break;
+ }
+ }
+ }
+ }
+
+ public int getUnusedResourceCount() {
+ return mUnused.size();
+ }
+
+ @VisibleForTesting
+ ResourceUsageModel getModel() {
+ return mModel;
+ }
+
+ /**
+ * Class visitor responsible for looking for resource references in code.
+ * It looks for R.type.name references (as well as inlined constants for these,
+ * in the case of non-library code), as well as looking both for Resources#getIdentifier
+ * calls and recording string literals, used to handle dynamic lookup of resources.
+ */
+ private class UsageVisitor extends ClassVisitor {
+ private final File mJarFile;
+ private final String mCurrentClass;
+
+ public UsageVisitor(File jarFile, String name) {
+ super(Opcodes.ASM5);
+ mJarFile = jarFile;
+ mCurrentClass = name;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, final String name,
+ String desc, String signature, String[] exceptions) {
+ return new MethodVisitor(Opcodes.ASM5) {
+ @Override
+ public void visitLdcInsn(Object cst) {
+ handleCodeConstant(cst, "ldc");
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (opcode == Opcodes.GETSTATIC) {
+ Resource resource = getResourceFromCode(owner, name);
+ if (resource != null) {
+ ResourceUsageModel.markReachable(resource);
+ }
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc, boolean itf) {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ if (owner.equals("android/content/res/Resources")
+ && name.equals("getIdentifier")
+ && desc.equals(
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I")) {
+
+ if (mCurrentClass.equals(mResourcesWrapper) ||
+ mCurrentClass.equals(mSuggestionsAdapter)) {
+ // "benign" usages: don't trigger reflection mode just because
+ // the user has included appcompat
+ return;
+ }
+
+ mFoundGetIdentifier = true;
+ // TODO: Check previous instruction and see if we can find a literal
+ // String; if so, we can more accurately dispatch the resource here
+ // rather than having to check the whole string pool!
+ }
+ if (owner.equals("android/webkit/WebView") && name.startsWith("load")) {
+ mFoundWebContent = true;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new AnnotationUsageVisitor();
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return new AnnotationUsageVisitor();
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ return new AnnotationUsageVisitor();
+ }
+ };
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return new AnnotationUsageVisitor();
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ handleCodeConstant(value, "field");
+ return new FieldVisitor(Opcodes.ASM5) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return new AnnotationUsageVisitor();
+ }
+ };
+ }
+
+ private class AnnotationUsageVisitor extends AnnotationVisitor {
+ public AnnotationUsageVisitor() {
+ super(Opcodes.ASM5);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ return new AnnotationUsageVisitor();
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return new AnnotationUsageVisitor();
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ handleCodeConstant(value, "annotation");
+ super.visit(name, value);
+ }
+ }
+
+ /** Invoked when an ASM visitor encounters a constant: record corresponding reference */
+ private void handleCodeConstant(@Nullable Object cst, @NonNull String context) {
+ if (cst instanceof Integer) {
+ Integer value = (Integer) cst;
+ Resource resource = mModel.getResource(value);
+ if (ResourceUsageModel.markReachable(resource) && mDebug) {
+ mDebugPrinter.println("Marking " + resource + " reachable: referenced from " +
+ context + " in " + mJarFile + ":" + mCurrentClass);
+ }
+ } else if (cst instanceof int[]) {
+ int[] values = (int[]) cst;
+ for (int value : values) {
+ Resource resource = mModel.getResource(value);
+ if (ResourceUsageModel.markReachable(resource) && mDebug) {
+ mDebugPrinter.println("Marking " + resource + " reachable: referenced from " +
+ context + " in " + mJarFile + ":" + mCurrentClass);
+ }
+ }
+ } else if (cst instanceof String) {
+ String string = (String) cst;
+ referencedString(string);
+ }
+ }
+ }
+
+ private final ResourceShrinkerUsageModel mModel =
+ new ResourceShrinkerUsageModel();
+
+ private class ResourceShrinkerUsageModel extends ResourceUsageModel {
+ public File file;
+
+ @NonNull
+ @Override
+ protected List<Resource> findRoots(@NonNull List<Resource> resources) {
+ List<Resource> roots = super.findRoots(resources);
+ if (mDebugPrinter != null) {
+ mDebugPrinter.println("\nThe root reachable resources are:\n" +
+ Joiner.on(",\n ").join(roots));
+ }
+
+ return roots;
+ }
+
+ @Override
+ protected Resource declareResource(ResourceType type, String name, Node node) {
+ Resource resource = super.declareResource(type, name, node);
+ resource.addLocation(file);
+ return resource;
+ }
+
+ @Override
+ protected void referencedString(@NonNull String string) {
+ ResourceUsageAnalyzer.this.referencedString(string);
+ mFoundWebContent = true;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SimpleWorkQueue.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SimpleWorkQueue.java
new file mode 100644
index 0000000..7dd5b68
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SimpleWorkQueue.java
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.builder.tasks.Job;
+import com.android.builder.tasks.JobContext;
+import com.android.builder.tasks.QueueThreadContextAdapter;
+import com.android.builder.tasks.WorkQueue;
+import com.android.utils.StdLogger;
+
+/**
+ * Common utilities to use a simple shared instance of {@link WorkQueue}.
+ * The context for job will be empty, and it is the responsibility of the
+ * {@link com.android.builder.tasks.WorkQueue.QueueTask} to have enough context to run.
+ */
+public class SimpleWorkQueue {
+
+ /**
+ * Simple {@link WorkQueue} context implementation that simply runs the proguard job.
+ */
+ private static class EmptyThreadContext extends
+ QueueThreadContextAdapter<Void> {
+
+ @Override
+ public void runTask(@NonNull Job<Void> job) throws Exception {
+ job.runTask(new JobContext<Void>(null /* payload */));
+ job.finished();
+ }
+ }
+
+ /**
+ * singleton work queue for all proguard invocations.
+ */
+ private static final WorkQueue<Void> WORK_QUEUE =
+ new WorkQueue<Void>(
+ new StdLogger(StdLogger.Level.VERBOSE),
+ new EmptyThreadContext(), "Tasks limiter", 4);
+
+
+ public static void push(Job<Void> job) throws InterruptedException {
+ WORK_QUEUE.push(job);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitRelatedTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitRelatedTask.java
new file mode 100644
index 0000000..fb29368
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitRelatedTask.java
@@ -0,0 +1,144 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.build.OutputFile.FilterType;
+import com.android.build.gradle.api.ApkOutputFile;
+import com.android.build.gradle.internal.model.FilterDataImpl;
+import com.android.build.gradle.internal.publishing.FilterDataPersistence;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.android.build.gradle.internal.tasks.SplitFileSupplier;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import org.gradle.api.Task;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Common code for all split related tasks
+ */
+public abstract class SplitRelatedTask extends BaseTask {
+
+ @Nullable
+ public abstract File getApkMetadataFile();
+
+ /**
+ * Calculates the list of output files, coming from the list of input files, mangling the output
+ * file name.
+ */
+ public abstract List<ApkOutputFile> getOutputSplitFiles();
+
+ /**
+ * Returns the list of split information for this task. Each split is a unique combination of
+ * filter type and identifier.
+ */
+ public abstract List<FilterData> getSplitsData();
+
+ /**
+ * Returns a list of {@link Supplier<File>} for each split APK file
+ */
+ public List<SplitFileSupplier> getOutputFileSuppliers() {
+ ImmutableList.Builder<SplitFileSupplier> suppliers = ImmutableList.builder();
+ for (final FilterData filterData : getSplitsData()) {
+
+ ApkOutputFile outputFile =
+ Iterables.find(getOutputSplitFiles(), new Predicate<ApkOutputFile>() {
+ @Override
+ public boolean apply(ApkOutputFile apkOutputFile) {
+ return filterData.getIdentifier().equals(
+ apkOutputFile.getFilter(filterData.getFilterType()));
+ }
+ });
+
+ if (outputFile != null) {
+ // make final references to not confused the groovy runtime...
+ final File file = outputFile.getOutputFile();
+ final FilterData data = filterData;
+
+ suppliers.add(new SplitFileSupplier() {
+
+ @Override
+ public File get() {
+ return file;
+ }
+
+ @NonNull
+ @Override
+ public Task getTask() {
+ return SplitRelatedTask.this;
+ }
+
+ @NonNull
+ @Override
+ public FilterData getFilterData() {
+ return data;
+ }
+ });
+ }
+ }
+ return suppliers.build();
+ }
+
+ /**
+ * Saves the APK metadata to the configured file.
+ */
+ protected void saveApkMetadataFile() throws IOException {
+
+ File metadataFile = getApkMetadataFile();
+ if (metadataFile == null) {
+ return;
+ }
+ FileWriter fileWriter = null;
+ try {
+ metadataFile.getParentFile().mkdirs();
+ fileWriter = new FileWriter(metadataFile);
+ FilterDataPersistence persistence = new FilterDataPersistence();
+ persistence.persist(getOutputFileSuppliers(), fileWriter);
+
+ } finally {
+ if (fileWriter != null) {
+ fileWriter.close();
+ }
+ }
+ }
+
+ /**
+ * Creates a new FilterData for each identifiers for a particular {@link FilterType} and store
+ * it in the to builder.
+ * @param to the builder to store the new FilterData instances in.
+ * @param identifiers the list of filter identifiers
+ * @param filterType the filter type.
+ */
+ protected static void addAllFilterData(ImmutableList.Builder<FilterData> to,
+ Collection<String> identifiers,
+ OutputFile.FilterType filterType) {
+ for (String identifier : identifiers) {
+ to.add(FilterDataImpl.build(filterType.toString(), identifier));
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitZipAlign.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitZipAlign.java
new file mode 100644
index 0000000..d2e9818
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/SplitZipAlign.java
@@ -0,0 +1,391 @@
+/*
+ * 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.build.gradle.tasks;
+
+import static com.android.build.OutputFile.FilterType.ABI;
+import static com.android.build.OutputFile.FilterType.DENSITY;
+import static com.android.build.OutputFile.FilterType.LANGUAGE;
+import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile.FilterType;
+import com.android.build.OutputFile.OutputType;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.api.ApkOutputFile;
+import com.android.build.gradle.internal.model.FilterDataImpl;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.core.VariantConfiguration;
+import com.android.builder.sdk.TargetInfo;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.Callables;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.process.ExecSpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Task to zip align all the splits
+ */
+ at ParallelizableTask
+public class SplitZipAlign extends SplitRelatedTask {
+
+ private List<File> densityOrLanguageInputFiles = new ArrayList<File>();
+
+ private List<File> abiInputFiles = new ArrayList<File>();
+
+ private String outputBaseName;
+
+ private Set<String> densityFilters;
+
+ private Set<String> abiFilters;
+
+ private Set<String> languageFilters;
+
+ private File outputDirectory;
+
+ private File zipAlignExe;
+
+ private boolean useOldPackaging;
+
+ @Nullable
+ private File apkMetadataFile;
+
+ @InputFiles
+ public List<File> getDensityOrLanguageInputFiles() {
+ return densityOrLanguageInputFiles;
+ }
+
+ @InputFiles
+ public List<File> getAbiInputFiles() {
+ return abiInputFiles;
+ }
+
+ @Input
+ public String getOutputBaseName() {
+ return outputBaseName;
+ }
+
+ public void setOutputBaseName(String outputBaseName) {
+ this.outputBaseName = outputBaseName;
+ }
+
+ @Input
+ public Set<String> getDensityFilters() {
+ return densityFilters;
+ }
+
+ public void setDensityFilters(Set<String> densityFilters) {
+ this.densityFilters = densityFilters;
+ }
+
+ @Input
+ public Set<String> getAbiFilters() {
+ return abiFilters;
+ }
+
+ public void setAbiFilters(Set<String> abiFilters) {
+ this.abiFilters = abiFilters;
+ }
+
+ @Input
+ public Set<String> getLanguageFilters() {
+ return languageFilters;
+ }
+
+ public void setLanguageFilters(Set<String> languageFilters) {
+ this.languageFilters = languageFilters;
+ }
+
+ public File getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ public void setOutputDirectory(File outputDirectory) {
+ this.outputDirectory = outputDirectory;
+ }
+
+ @InputFile
+ public File getZipAlignExe() {
+ return zipAlignExe;
+ }
+
+ public void setZipAlignExe(File zipAlignExe) {
+ this.zipAlignExe = zipAlignExe;
+ }
+
+ @Override
+ @OutputFile
+ @Nullable
+ public File getApkMetadataFile() {
+ return apkMetadataFile;
+ }
+
+ public void setApkMetadataFile(@Nullable File apkMetadataFile) {
+ this.apkMetadataFile = apkMetadataFile;
+ }
+
+ @OutputFiles
+ public List<File> getOutputFiles() {
+ ImmutableList.Builder<File> builder = ImmutableList.builder();
+ for (ApkOutputFile outputFile : getOutputSplitFiles()) {
+ builder.add(outputFile.getOutputFile());
+ }
+ return builder.build();
+ }
+
+ @NonNull
+ public List<File> getInputFiles() {
+ return ImmutableList.copyOf(
+ Iterables.concat(getDensityOrLanguageInputFiles(), getAbiInputFiles()));
+ }
+
+ @Override
+ @NonNull
+ public synchronized ImmutableList<ApkOutputFile> getOutputSplitFiles() {
+ final String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
+
+ final ImmutableList.Builder<ApkOutputFile> outputFiles = ImmutableList.builder();
+ InputProcessor addingLogic = new InputProcessor() {
+ @Override
+ public void process(String split, File file) {
+ outputFiles.add(new ApkOutputFile(
+ OutputType.SPLIT,
+ ImmutableList.of(
+ FilterDataImpl.build(
+ getFilterType(split).toString(),
+ getFilter(split))),
+ Callables.returning(
+ new File(
+ outputDirectory,
+ archivesBaseName + "-" + outputBaseName + "_" + split
+ + ".apk"))));
+
+ }
+ };
+
+ forEachUnalignedInput(addingLogic);
+ forEachUnsignedInput(addingLogic);
+ return outputFiles.build();
+ }
+
+ public FilterType getFilterType(String filter) {
+ String languageName = PackageSplitRes.unMangleSplitName(filter);
+ if (languageFilters.contains(languageName)) {
+ return FilterType.LANGUAGE;
+ }
+
+ if (abiFilters.contains(filter)) {
+ return FilterType.ABI;
+ }
+
+ return FilterType.DENSITY;
+ }
+
+ public String getFilter(String filterWithPossibleSuffix) {
+ FilterType type = getFilterType(filterWithPossibleSuffix);
+ if (type == FilterType.DENSITY) {
+ for (String density : densityFilters) {
+ if (filterWithPossibleSuffix.startsWith(density)) {
+ return density;
+ }
+ }
+ }
+
+ if (type == FilterType.LANGUAGE) {
+ return PackageSplitRes.unMangleSplitName(filterWithPossibleSuffix);
+ }
+ return filterWithPossibleSuffix;
+ }
+
+ /**
+ * Returns true if the passed string is one of the filter we must process potentially followed
+ * by a prefix (some density filters get V4, V16, etc... appended).
+ */
+ public boolean isFilter(String potentialFilterWithSuffix) {
+ for (String density : densityFilters) {
+ if (potentialFilterWithSuffix.startsWith(density)) {
+ return true;
+ }
+ }
+
+ return abiFilters.contains(potentialFilterWithSuffix)
+ || languageFilters.contains(
+ PackageSplitRes.unMangleSplitName(potentialFilterWithSuffix));
+
+ }
+
+ private interface InputProcessor {
+ void process(String split, File file);
+ }
+
+ private void forEachUnalignedInput(InputProcessor processor) {
+ String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
+ Pattern unalignedPattern = Pattern.compile(
+ archivesBaseName + "-" + outputBaseName + "_(.*)-unaligned.apk");
+
+ for (File file : getInputFiles()) {
+ Matcher unaligned = unalignedPattern.matcher(file.getName());
+ if (unaligned.matches() && isFilter(unaligned.group(1))) {
+ processor.process(unaligned.group(1), file);
+ }
+ }
+ }
+
+ private void forEachUnsignedInput(InputProcessor processor) {
+ String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
+ Pattern unsignedPattern = Pattern.compile(
+ archivesBaseName + "-" + outputBaseName + "_(.*)-unsigned.apk");
+
+ for (File file : getInputFiles()) {
+ Matcher unsigned = unsignedPattern.matcher(file.getName());
+ if (unsigned.matches() && isFilter(unsigned.group(1))) {
+ processor.process(unsigned.group(1), file);
+ }
+
+ }
+
+ }
+
+ @TaskAction
+ public void splitZipAlign() throws IOException {
+ final String archivesBaseName = (String)getProject().getProperties().get("archivesBaseName");
+
+ InputProcessor zipAlignIt = new InputProcessor() {
+ @Override
+ public void process(final String split, final File file) {
+ final File out = new File(getOutputDirectory(),
+ archivesBaseName + "-" + outputBaseName + "_" + split + ".apk");
+ getProject().exec(new Action<ExecSpec>() {
+ @Override
+ public void execute(ExecSpec execSpec) {
+ execSpec.setExecutable(getZipAlignExe());
+ execSpec.args("-f", "4");
+ execSpec.args(file.getAbsolutePath());
+ execSpec.args(out);
+ }
+ });
+ }
+ };
+ forEachUnalignedInput(zipAlignIt);
+ forEachUnsignedInput(zipAlignIt);
+ saveApkMetadataFile();
+ }
+
+ @Override
+ public List<FilterData> getSplitsData() {
+ ImmutableList.Builder<FilterData> filterDataBuilder = ImmutableList.builder();
+ SplitRelatedTask.addAllFilterData(filterDataBuilder, densityFilters, FilterType.DENSITY);
+ SplitRelatedTask.addAllFilterData(filterDataBuilder, languageFilters, FilterType.LANGUAGE);
+ SplitRelatedTask.addAllFilterData(filterDataBuilder, abiFilters, FilterType.ABI);
+ return filterDataBuilder.build();
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements TaskConfigAction<SplitZipAlign> {
+
+ private VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return scope.getTaskName("zipAlign", "SplitPackages");
+ }
+
+ @Override
+ @NonNull
+ public Class<SplitZipAlign> getType() {
+ return SplitZipAlign.class;
+ }
+
+ @Override
+ public void execute(@NonNull SplitZipAlign zipAlign) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ List<? extends BaseVariantOutputData> outputs = variantData.getOutputs();
+ final BaseVariantOutputData variantOutputData = outputs.get(0);
+
+ final VariantConfiguration config = scope.getVariantConfiguration();
+ Set<String> densityFilters = variantData.getFilters(DENSITY);
+ Set<String> abiFilters = variantData.getFilters(ABI);
+ Set<String> languageFilters = variantData.getFilters(LANGUAGE);
+
+ zipAlign.setVariantName(config.getFullName());
+ ConventionMappingHelper.map(zipAlign, "zipAlignExe", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ final TargetInfo info =
+ scope.getGlobalScope().getAndroidBuilder().getTargetInfo();
+ if (info == null) {
+ return null;
+ }
+ String path = info.getBuildTools().getPath(ZIP_ALIGN);
+ if (path == null) {
+ return null;
+ }
+ return new File(path);
+ }
+ });
+
+ zipAlign.setOutputDirectory(new File(scope.getGlobalScope().getBuildDir(), "outputs/apk"));
+ ConventionMappingHelper.map(zipAlign, "densityOrLanguageInputFiles",
+ new Callable<List<File>>() {
+ @Override
+ public List<File> call() {
+ return variantOutputData.packageSplitResourcesTask.getOutputFiles();
+ }
+ });
+ zipAlign.setOutputBaseName(config.getBaseName());
+ zipAlign.setAbiFilters(abiFilters);
+ zipAlign.setLanguageFilters(languageFilters);
+ zipAlign.setDensityFilters(densityFilters);
+ File metadataDirectory = new File(zipAlign.getOutputDirectory().getParentFile(),
+ "metadata");
+ zipAlign.setApkMetadataFile(new File(metadataDirectory, config.getFullName() + ".mtd"));
+ ((ApkVariantOutputData) variantOutputData).splitZipAlign = zipAlign;
+
+ zipAlign.useOldPackaging = AndroidGradleOptions.useOldPackaging(
+ scope.getGlobalScope().getProject());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/TestModuleProGuardTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/TestModuleProGuardTask.java
new file mode 100644
index 0000000..cc805d9
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/TestModuleProGuardTask.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.builder.core.VariantConfiguration;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.IOException;
+
+import proguard.ParseException;
+import proguard.gradle.ProGuardTask;
+
+/**
+ * Specialization of the {@link ProGuardTask} that can use {@link Configuration} objects to retrieve
+ * input files like the tested application classes and the tested application mapping file.
+ */
+ at ParallelizableTask
+public class TestModuleProGuardTask extends ProGuardTask {
+ private Logger logger;
+ private Configuration mappingConfiguration;
+ private VariantConfiguration variantConfiguration;
+
+
+ /**
+ * Sets the {@link Configuration} to later retrieve the tested application mapping file
+ */
+ public void setMappingConfiguration(Configuration configuration) {
+ this.mappingConfiguration = configuration;
+ dependsOn(configuration);
+ }
+
+ /**
+ * Sets the {@link Configuration} to later retrieve the test application classes jar file.
+ */
+ public void setClassesConfiguration(Configuration configuration) {
+ dependsOn(configuration);
+ }
+
+
+ public void setVariantConfiguration(
+ VariantConfiguration variantConfiguration) {
+ this.variantConfiguration = variantConfiguration;
+ }
+
+ public void setLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ @TaskAction
+ public void proguard() throws ParseException, IOException {
+ if (logger.isEnabled(LogLevel.INFO)) {
+ logger.info("test module mapping file " + mappingConfiguration.getSingleFile());
+ for (Object file : variantConfiguration.getAllPackagedJars()) {
+ logger.info("test module proguard input " + file);
+
+ }
+ for (Object file : variantConfiguration.getProvidedOnlyJars()) {
+ logger.info("test module proguard library " + file);
+ }
+ }
+
+ if (mappingConfiguration.getSingleFile().isFile()) {
+ applymapping(mappingConfiguration.getSingleFile());
+ }
+ super.proguard();
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.java
new file mode 100644
index 0000000..b4f0b88
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.java
@@ -0,0 +1,168 @@
+package com.android.build.gradle.tasks;
+
+import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.internal.annotations.ApkFile;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantOutputScope;
+import com.android.build.gradle.internal.tasks.FileSupplier;
+import com.android.build.gradle.internal.variant.ApkVariantOutputData;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.ParallelizableTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.process.ExecSpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+ at ParallelizableTask
+public class ZipAlign extends DefaultTask implements FileSupplier {
+
+ private boolean useOldPackaging;
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ @InputFile
+ public File getInputFile() {
+ return inputFile;
+ }
+
+ public void setInputFile(File inputFile) {
+ this.inputFile = inputFile;
+ }
+
+ // ----- PRIVATE TASK API -----
+
+ private File outputFile;
+ @ApkFile
+ private File inputFile;
+ @ApkFile
+ private File zipAlignExe;
+
+ private InstantRunBuildContext instantRunBuildContext;
+
+ public void setInstantRunBuildContext(InstantRunBuildContext instantRunBuildContext) {
+ this.instantRunBuildContext = instantRunBuildContext;
+ }
+
+ @InputFile
+ public File getZipAlignExe() {
+ return zipAlignExe;
+ }
+
+ public void setZipAlignExe(File zipAlignExe) {
+ this.zipAlignExe = zipAlignExe;
+ }
+
+ @TaskAction
+ public void zipAlign() {
+ getProject().exec(new Action<ExecSpec>() {
+ @Override
+ public void execute(ExecSpec execSpec) {
+ execSpec.executable(getZipAlignExe());
+ execSpec.args("-f", "4");
+ execSpec.args(getInputFile());
+ execSpec.args(getOutputFile());
+ }
+ });
+ // mark this APK production, this will eventually be saved when instant-run is enabled.
+ try {
+ instantRunBuildContext.addChangedFile(InstantRunBuildContext.FileType.MAIN,
+ getOutputFile());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // ----- FileSupplierTask -----
+
+ @Override
+ public File get() {
+ return getOutputFile();
+ }
+
+ @NonNull
+ @Override
+ public Task getTask() {
+ return this;
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements TaskConfigAction<ZipAlign> {
+
+ private final VariantOutputScope scope;
+
+ @Override
+ public String getName() {
+ return scope.getTaskName("zipalign");
+ }
+
+ @Override
+ public Class<ZipAlign> getType() {
+ return ZipAlign.class;
+ }
+
+ public ConfigAction(VariantOutputScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ public void execute(ZipAlign zipAlign) {
+ ((ApkVariantOutputData) scope.getVariantOutputData()).zipAlignTask = zipAlign;
+ ConventionMappingHelper.map(zipAlign, "inputFile", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ // wire to the output of the package task.
+ PackageApplication packageApplicationTask = ((ApkVariantOutputData) scope
+ .getVariantOutputData()).packageApplicationTask;
+ return packageApplicationTask == null
+ ? scope.getPackageApk()
+ : packageApplicationTask.getOutputFile();
+ }
+ });
+ ConventionMappingHelper.map(zipAlign, "outputFile", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ return scope.getGlobalScope().getProject().file(
+ scope.getGlobalScope().getApkLocation() + "/" +
+ scope.getGlobalScope().getProjectBaseName() + "-" +
+ scope.getVariantOutputData().getBaseName() + ".apk");
+ }
+ });
+ ConventionMappingHelper.map(zipAlign, "zipAlignExe", new Callable<File>() {
+ @Override
+ public File call() throws Exception {
+ String path = scope.getGlobalScope().getAndroidBuilder().getTargetInfo()
+ .getBuildTools().getPath(ZIP_ALIGN);
+ if (path != null) {
+ return new File(path);
+ }
+ return null;
+ }
+ });
+ zipAlign.instantRunBuildContext = scope.getVariantScope().getInstantRunBuildContext();
+ zipAlign.useOldPackaging = AndroidGradleOptions.useOldPackaging(
+ scope.getGlobalScope().getProject());
+ }
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ApiDatabase.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ApiDatabase.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ApiDatabase.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ApiDatabase.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java
new file mode 100644
index 0000000..a4601ba
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriver.java
@@ -0,0 +1,391 @@
+/*
+ * 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.build.gradle.tasks.annotations;
+
+import static com.android.SdkConstants.DOT_JAVA;
+import static java.io.File.pathSeparator;
+import static java.io.File.pathSeparatorChar;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.EcjParser;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.util.Util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * The extract annotations driver is a command line interface to extracting annotations
+ * from a source tree. It's similar to the gradle
+ * {@link com.android.build.gradle.tasks.ExtractAnnotations} task,
+ * but usable from the command line and outside Gradle, for example
+ * to extract annotations from the Android framework itself (which is not built with
+ * Gradle). It also allows other options only interesting for extracting
+ * platform annotations, such as filtering all APIs and constants through an
+ * API white-list (such that we for example can pull annotations from the master
+ * branch which has the latest metadata, but only expose APIs that are actually in
+ * a released platform), as well as translating android.annotation annotations into
+ * android.support.annotations.
+ */
+public class ExtractAnnotationsDriver {
+ public static void main(String[] args) {
+ new ExtractAnnotationsDriver().run(args);
+ }
+
+ private static void usage(PrintStream output) {
+ output.println("Usage: " + ExtractAnnotationsDriver.class.getSimpleName() + " <flags>");
+ output.println(" --sources <paths> : Source directories to extract annotations from. ");
+ output.println(" Separate paths with " + pathSeparator + ", and you can use @ ");
+ output.println(" as a filename prefix to have the filenames fed from a file");
+ output.println("--classpath <paths> : Directories and .jar files to resolve symbols from");
+ output.println("--output <zip path> : The .zip file to write the extracted annotations to, if any");
+ output.println("--proguard <path> : The proguard.cfg file to write the keep rules to, if any");
+ output.println();
+ output.println("Optional flags:");
+ output.println("--merge-zips <paths> : Existing external annotation files to merge in");
+ output.println("--quiet : Don't print summary information");
+ output.println("--rmtypedefs <folder> : Remove typedef classes found in the given folder");
+ output.println("--allow-missing-types : Don't fail even if some types can't be resolved");
+ output.println("--allow-errors : Don't fail even if there are some compiler errors");
+ output.println("--encoding <encoding> : Encoding (defaults to utf-8)");
+ output.println("--language-level <level> : Java source language level, typically 1.6 (default) or 1.7");
+ output.println("--api-filter <api.txt> : A framework API definition to restrict included APIs to");
+ output.println("--hide-filtered : If filtering out non-APIs, supply this flag to hide listing matches");
+ output.println("--skip-class-retention : Don't extract annotations that have class retention");
+ System.exit(-1);
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ public void run(@NonNull String[] args) {
+ List<String> classpath = Lists.newArrayList();
+ List<File> sources = Lists.newArrayList();
+ List<File> mergePaths = Lists.newArrayList();
+ List<File> apiFilters = null;
+ File rmTypeDefs = null;
+ boolean verbose = true;
+ boolean allowMissingTypes = false;
+ boolean allowErrors = false;
+ boolean listFiltered = true;
+ boolean skipClassRetention = false;
+
+ String encoding = Charsets.UTF_8.name();
+ File output = null;
+ File proguard = null;
+ long languageLevel = EcjParser.getLanguageLevel(1, 7);
+ if (args.length == 1 && "--help".equals(args[0])) {
+ usage(System.out);
+ }
+ if (args.length < 2) {
+ usage(System.err);
+ }
+ for (int i = 0, n = args.length; i < n; i++) {
+ String flag = args[i];
+
+ if (flag.equals("--quiet")) {
+ verbose = false;
+ continue;
+ } else if (flag.equals("--allow-missing-types")) {
+ allowMissingTypes = true;
+ continue;
+ } else if (flag.equals("--allow-errors")) {
+ allowErrors = true;
+ continue;
+ } else if (flag.equals("--hide-filtered")) {
+ listFiltered = false;
+ continue;
+ } else if (flag.equals("--skip-class-retention")) {
+ skipClassRetention = true;
+ continue;
+ }
+ if (i == n - 1) {
+ usage(System.err);
+ }
+ String value = args[i + 1];
+ i++;
+
+ if (flag.equals("--sources")) {
+ sources = getFiles(value);
+ } else if (flag.equals("--classpath")) {
+ classpath = getPaths(value);
+ } else if (flag.equals("--merge-zips")) {
+ mergePaths = getFiles(value);
+ } else if (flag.equals("--output")) {
+ output = new File(value);
+ if (output.exists()) {
+ if (output.isDirectory()) {
+ abort(output + " is a directory");
+ }
+ boolean deleted = output.delete();
+ if (!deleted) {
+ abort("Could not delete previous version of " + output);
+ }
+ } else if (output.getParentFile() != null && !output.getParentFile().exists()) {
+ abort(output.getParentFile() + " does not exist");
+ }
+ } else if (flag.equals("--proguard")) {
+ proguard = new File(value);
+ if (proguard.exists()) {
+ if (proguard.isDirectory()) {
+ abort(proguard + " is a directory");
+ }
+ boolean deleted = proguard.delete();
+ if (!deleted) {
+ abort("Could not delete previous version of " + proguard);
+ }
+ } else if (proguard.getParentFile() != null && !proguard.getParentFile().exists()) {
+ abort(proguard.getParentFile() + " does not exist");
+ }
+ } else if (flag.equals("--encoding")) {
+ encoding = value;
+ } else if (flag.equals("--api-filter")) {
+ if (apiFilters == null) {
+ apiFilters = Lists.newArrayList();
+ }
+ for (String path : Splitter.on(",").omitEmptyStrings().split(value)) {
+ File apiFilter = new File(path);
+ if (!apiFilter.isFile()) {
+ String message = apiFilter + " does not exist or is not a file";
+ abort(message);
+ }
+ apiFilters.add(apiFilter);
+ }
+ } else if (flag.equals("--language-level")) {
+ if ("1.6".equals(value)) {
+ languageLevel = EcjParser.getLanguageLevel(1, 6);
+ } else if ("1.7".equals(value)) {
+ languageLevel = EcjParser.getLanguageLevel(1, 7);
+ } else {
+ abort("Unsupported language level " + value);
+ }
+ } else if (flag.equals("--rmtypedefs")) {
+ rmTypeDefs = new File(value);
+ if (!rmTypeDefs.isDirectory()) {
+ abort(rmTypeDefs + " is not a directory");
+ }
+ } else {
+ System.err.println("Unknown flag " + flag + ": Use --help for usage information");
+ }
+ }
+
+ if (sources.isEmpty()) {
+ abort("Must specify at least one source path");
+ }
+ if (classpath.isEmpty()) {
+ abort("Must specify classpath pointing to at least android.jar or the framework");
+ }
+ if (output == null && proguard == null) {
+ abort("Must specify output path with --output or a proguard path with --proguard");
+ }
+
+ // API definition files
+ ApiDatabase database = null;
+ if (apiFilters != null && !apiFilters.isEmpty()) {
+ try {
+ List<String> lines = Lists.newArrayList();
+ for (File file : apiFilters) {
+ lines.addAll(Files.readLines(file, Charsets.UTF_8));
+ }
+ database = new ApiDatabase(lines);
+ } catch (IOException e) {
+ abort("Could not open API database " + apiFilters + ": " + e.getLocalizedMessage());
+ }
+ }
+
+ Extractor extractor = new Extractor(database, rmTypeDefs, verbose, !skipClassRetention,
+ true);
+ extractor.setListIgnored(listFiltered);
+
+ EcjParser.EcjResult result = null;
+ try {
+ result = parseSources(sources, classpath, encoding, languageLevel);
+ Collection<CompilationUnitDeclaration> units = result.getCompilationUnits();
+
+ boolean abort = false;
+ int errorCount = 0;
+ for (CompilationUnitDeclaration unit : units) {
+ // so maybe I don't need my map!!
+ IProblem[] problems = unit.compilationResult().getAllProblems();
+ if (problems != null) {
+ for (IProblem problem : problems) {
+ if (problem.isError()) {
+ errorCount++;
+ String message = problem.getMessage();
+ if (allowMissingTypes) {
+ if (message.contains("cannot be resolved")) {
+ continue;
+ }
+ }
+
+ System.out.println("Error: " +
+ new String(problem.getOriginatingFileName()) + ":" +
+ problem.getSourceLineNumber() + ": " + message);
+ abort = !allowErrors;
+ }
+ }
+ }
+ }
+ if (errorCount > 0) {
+ System.err.println("Found " + errorCount + " errors");
+ }
+ if (abort) {
+ abort("Not extracting annotations (compilation problems encountered)");
+ }
+
+ extractor.extractFromProjectSource(units);
+
+ if (mergePaths != null) {
+ for (File jar : mergePaths) {
+ extractor.mergeExisting(jar);
+ }
+ }
+
+ extractor.export(output, proguard);
+
+ // Remove typedefs?
+ //noinspection VariableNotUsedInsideIf
+ if (rmTypeDefs != null) {
+ extractor.removeTypedefClasses();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (result != null) {
+ result.dispose();
+ }
+ }
+ }
+
+ private static void abort(@NonNull String message) {
+ System.err.println(message);
+ System.exit(-1);
+ }
+
+ private static List<File> getFiles(String value) {
+ List<File> files = Lists.newArrayList();
+ Splitter splitter = Splitter.on(pathSeparatorChar).omitEmptyStrings().trimResults();
+ for (String path : splitter.split(value)) {
+ if (path.startsWith("@")) {
+ // Special syntax for providing files in a list
+ File sourcePath = new File(path.substring(1));
+ if (!sourcePath.exists()) {
+ abort(sourcePath + " does not exist");
+ }
+ try {
+ for (String line : Files.readLines(sourcePath, Charsets.UTF_8)) {
+ line = line.trim();
+ if (!line.isEmpty()) {
+ File file = new File(line);
+ if (!file.exists()) {
+ System.err.println("Warning: Could not find file " + line +
+ " listed in " + sourcePath);
+ }
+ files.add(file);
+ }
+ }
+ continue;
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.exit(-1);
+ }
+ }
+ File file = new File(path);
+ if (!file.exists()) {
+ abort(file + " does not exist");
+ }
+ files.add(file);
+ }
+
+ return files;
+ }
+
+ private static List<String> getPaths(String value) {
+ List<File> files = getFiles(value);
+ List<String> paths = Lists.newArrayListWithExpectedSize(files.size());
+ for (File file : files) {
+ paths.add(file.getPath());
+ }
+ return paths;
+ }
+
+ private static void addJavaSources(List<File> list, File file) {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ addJavaSources(list, child);
+ }
+ }
+ } else {
+ if (file.isFile() && file.getName().endsWith(DOT_JAVA)) {
+ list.add(file);
+ }
+ }
+ }
+
+ private static List<File> gatherJavaSources(List<File> sourcePath) {
+ List<File> sources = Lists.newArrayList();
+ for (File file : sourcePath) {
+ addJavaSources(sources, file);
+ }
+ return sources;
+ }
+
+ @NonNull
+ private static EcjParser.EcjResult parseSources(
+ @NonNull List<File> sourcePaths,
+ @NonNull List<String> classpath,
+ @NonNull String encoding,
+ long languageLevel)
+ throws IOException {
+ List<ICompilationUnit> sourceUnits = Lists.newArrayListWithExpectedSize(100);
+
+ for (File source : gatherJavaSources(sourcePaths)) {
+ char[] contents = Util.getFileCharContent(source, encoding);
+ ICompilationUnit unit = new CompilationUnit(contents, source.getPath(), encoding);
+ sourceUnits.add(unit);
+ }
+
+ CompilerOptions options = EcjParser.createCompilerOptions();
+ options.docCommentSupport = true; // So I can find @hide
+
+ // Note: We can *not* set options.ignoreMethodBodies=true because it disables
+ // type attribution!
+
+ options.sourceLevel = languageLevel;
+ options.complianceLevel = options.sourceLevel;
+ // We don't generate code, but just in case the parser consults this flag
+ // and makes sure that it's not greater than the source level:
+ options.targetJDK = options.sourceLevel;
+ options.originalComplianceLevel = options.sourceLevel;
+ options.originalSourceLevel = options.sourceLevel;
+ options.inlineJsrBytecode = true; // >= 1.5
+
+ return EcjParser.parse(options, sourceUnits, classpath, null);
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
new file mode 100644
index 0000000..aac587e
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks.annotations;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.eclipse.jdt.internal.compiler.ASTVisitor;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Javadoc;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/** Gathers information about typedefs (@IntDef and @StringDef */
+public class TypedefCollector extends ASTVisitor {
+ private Map<String,List<Annotation>> mMap = Maps.newHashMap();
+
+ private final boolean mRequireHide;
+ private final boolean mRequireSourceRetention;
+ private CompilationUnitDeclaration mCurrentUnit;
+ private List<String> mTypedefClasses = Lists.newArrayList();
+
+ public TypedefCollector(
+ @NonNull Collection<CompilationUnitDeclaration> units,
+ boolean requireHide,
+ boolean requireSourceRetention) {
+ mRequireHide = requireHide;
+ mRequireSourceRetention = requireSourceRetention;
+
+ for (CompilationUnitDeclaration unit : units) {
+ mCurrentUnit = unit;
+ unit.traverse(this, unit.scope);
+ mCurrentUnit = null;
+ }
+ }
+
+ public List<String> getNonPublicTypedefClasses() {
+ return mTypedefClasses;
+ }
+
+ public Map<String,List<Annotation>> getTypedefs() {
+ return mMap;
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
+ return recordTypedefs(memberTypeDeclaration);
+
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
+ return recordTypedefs(typeDeclaration);
+ }
+
+ private boolean recordTypedefs(TypeDeclaration declaration) {
+ SourceTypeBinding binding = declaration.binding;
+ if (binding == null) {
+ return false;
+ }
+ Annotation[] annotations = declaration.annotations;
+ if (annotations != null) {
+ if (declaration.binding.isAnnotationType()) {
+ for (Annotation annotation : annotations) {
+ String typeName = Extractor.getFqn(annotation);
+ if (typeName == null) {
+ continue;
+ }
+
+ if (Extractor.isNestedAnnotation(typeName)) {
+ String fqn = new String(binding.readableName());
+
+ List<Annotation> list = mMap.get(fqn);
+ if (list == null) {
+ list = new ArrayList<Annotation>(2);
+ mMap.put(fqn, list);
+ }
+ list.add(annotation);
+
+ if (mRequireHide) {
+ Javadoc javadoc = declaration.javadoc;
+ if (javadoc != null) {
+ StringBuffer stringBuffer = new StringBuffer(200);
+ javadoc.print(0, stringBuffer);
+ String documentation = stringBuffer.toString();
+ if (!documentation.contains("@hide")) {
+ Extractor.warning(getFileName()
+ + ": The typedef annotation " + fqn
+ + " should specify @hide in a doc comment");
+ }
+ }
+ }
+ if (mRequireSourceRetention
+ && !Extractor.hasSourceRetention(annotations)) {
+ Extractor.warning(getFileName()
+ + ": The typedef annotation " + fqn
+ + " should have @Retention(RetentionPolicy.SOURCE)");
+ }
+ if (declaration.binding != null
+ && (declaration.modifiers & ClassFileConstants.AccPublic) == 0) {
+ StringBuilder sb = new StringBuilder(100);
+ for (char c : declaration.binding.qualifiedPackageName()) {
+ if (c == '.') {
+ sb.append('/');
+ } else {
+ sb.append(c);
+ }
+ }
+ sb.append('/');
+ for (char c : declaration.binding.qualifiedSourceName()) {
+ if (c == '.') {
+ sb.append('$');
+ } else {
+ sb.append(c);
+ }
+ }
+ mTypedefClasses.add(sb.toString());
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ private String getFileName() {
+ return new String(mCurrentUnit.getFileName());
+ }
+}
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefRemover.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefRemover.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefRemover.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefRemover.java
diff --git a/base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/AbstractCompilesUtil.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/AbstractCompilesUtil.java
similarity index 100%
rename from base/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/AbstractCompilesUtil.java
rename to build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/AbstractCompilesUtil.java
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/AndroidJavaCompile.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/AndroidJavaCompile.java
new file mode 100644
index 0000000..b85480b
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/AndroidJavaCompile.java
@@ -0,0 +1,42 @@
+/*
+ * 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.build.gradle.tasks.factory;
+
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+
+/**
+ * Specialization of the JavaCompile task to record execution time.
+ */
+public class AndroidJavaCompile extends JavaCompile {
+
+ InstantRunBuildContext mBuildContext;
+
+ @Override
+ protected void compile(IncrementalTaskInputs inputs) {
+ getLogger().info(
+ "Compiling with source level {} and target level {}.",
+ getSourceCompatibility(),
+ getTargetCompatibility());
+
+ mBuildContext.startRecording(InstantRunBuildContext.TaskType.JAVAC);
+ super.compile(inputs);
+ mBuildContext.stopRecording(InstantRunBuildContext.TaskType.JAVAC);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/JavaCompileConfigAction.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/JavaCompileConfigAction.java
new file mode 100644
index 0000000..72341b3
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/JavaCompileConfigAction.java
@@ -0,0 +1,157 @@
+package com.android.build.gradle.tasks.factory;
+
+import static com.android.builder.core.VariantType.LIBRARY;
+import static com.android.builder.core.VariantType.UNIT_TEST;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.model.SyncIssue;
+import com.android.utils.ILogger;
+import com.google.common.base.Joiner;
+
+import org.gradle.api.Project;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.FileCollection;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * Configuration Action for a JavaCompile task.
+ */
+public class JavaCompileConfigAction implements TaskConfigAction<AndroidJavaCompile> {
+ private static final ILogger LOG = LoggerWrapper.getLogger(JavaCompileConfigAction.class);
+
+ private VariantScope scope;
+
+ public JavaCompileConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("compile", "JavaWithJavac");
+ }
+
+ @NonNull
+ @Override
+ public Class<AndroidJavaCompile> getType() {
+ return AndroidJavaCompile.class;
+ }
+
+ @Override
+ public void execute(@NonNull final AndroidJavaCompile javacTask) {
+ final BaseVariantData testedVariantData = scope.getTestedVariantData();
+ scope.getVariantData().javacTask = javacTask;
+ javacTask.mBuildContext = scope.getInstantRunBuildContext();
+
+ // We can't just pass the collection directly, as the instanceof check in the incremental
+ // compile doesn't work recursively currently, so every ConfigurableFileTree needs to be
+ // directly in the source array.
+ for (ConfigurableFileTree fileTree: scope.getVariantData().getJavaSources()) {
+ javacTask.source(fileTree);
+ }
+
+ ConventionMappingHelper.map(javacTask, "classpath", new Callable<FileCollection>() {
+ @Override
+ public FileCollection call() {
+ FileCollection classpath = scope.getJavaClasspath();
+ Project project = scope.getGlobalScope().getProject();
+
+ if (testedVariantData != null) {
+ // For libraries, the classpath from androidBuilder includes the library
+ // output (bundle/classes.jar) as a normal dependency. In unit tests we
+ // don't want to package the jar at every run, so we use the *.class
+ // files instead.
+ if (!testedVariantData.getType().equals(LIBRARY)
+ || scope.getVariantData().getType().equals(UNIT_TEST)) {
+ classpath = classpath.plus(project.files(
+ testedVariantData.getScope().getJavaClasspath(),
+ testedVariantData.getScope().getJavaOutputDir(),
+ testedVariantData.getScope().getJavaDependencyCache()));
+ }
+
+ if (scope.getVariantData().getType().equals(UNIT_TEST)
+ && testedVariantData.getType().equals(LIBRARY)) {
+ // The bundled classes.jar may exist, but it's probably old. Don't
+ // use it, we already have the *.class files in the classpath.
+ LibraryDependency libraryDependency =
+ testedVariantData.getVariantConfiguration().getOutput();
+ if (libraryDependency != null) {
+ File jarFile = libraryDependency.getJarFile();
+ classpath = classpath.minus(project.files(jarFile));
+ }
+ }
+ }
+
+ return classpath;
+ }
+ });
+
+ javacTask.setDestinationDir(scope.getJavaOutputDir());
+
+ javacTask.setDependencyCacheDir(scope.getJavaDependencyCache());
+
+ CompileOptions compileOptions = scope.getGlobalScope().getExtension().getCompileOptions();
+
+ AbstractCompilesUtil.configureLanguageLevel(
+ javacTask,
+ compileOptions,
+ scope.getGlobalScope().getExtension().getCompileSdkVersion()
+ );
+
+ javacTask.getOptions().setEncoding(compileOptions.getEncoding());
+
+ javacTask.getOptions().setBootClasspath(
+ Joiner.on(File.pathSeparator).join(
+ scope.getGlobalScope().getAndroidBuilder().getBootClasspathAsStrings(false)));
+
+ GlobalScope globalScope = scope.getGlobalScope();
+ Project project = globalScope.getProject();
+
+ boolean incremental;
+
+ if (compileOptions.getIncremental() != null) {
+ incremental = compileOptions.getIncremental();
+ } else {
+ //if (globalScope.getExtension().getDataBinding().isEnabled()
+ // || project.getPlugins().hasPlugin("com.neenbedankt.android-apt")
+ // || project.getPlugins().hasPlugin("me.tatarka.retrolambda")) {
+ // incremental = false;
+ //} else {
+ // For now, default to false, irrespective of Instant Run.
+ incremental = false;
+ //}
+ }
+
+ if (AndroidGradleOptions.isJavaCompileIncrementalPropertySet(project)) {
+ scope.getGlobalScope().getAndroidBuilder().getErrorReporter().handleSyncError(
+ null,
+ SyncIssue.TYPE_GENERIC,
+ String.format(
+ "The %s property has been replaced by a DSL property. Please add the "
+ + "following to your build.gradle instead:\n"
+ + "android {\n"
+ + " compileOptions.incremental = false\n"
+ + "}",
+ AndroidGradleOptions.PROPERTY_INCREMENTAL_JAVA_COMPILE));
+ }
+
+ if (incremental) {
+ LOG.info("Using incremental javac compilation.");
+ } else {
+ LOG.info("Not using incremental javac compilation.");
+ }
+ javacTask.getOptions().setIncremental(incremental);
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProcessJavaResConfigAction.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProcessJavaResConfigAction.java
new file mode 100644
index 0000000..c4afe8f
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/ProcessJavaResConfigAction.java
@@ -0,0 +1,94 @@
+package com.android.build.gradle.tasks.factory;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.builder.model.SourceProvider;
+import com.android.utils.FileUtils;
+import com.google.common.util.concurrent.Callables;
+
+import org.gradle.api.tasks.Sync;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Configuration Action for a process*JavaRes tasks.
+ */
+public class ProcessJavaResConfigAction implements TaskConfigAction<Sync> {
+ private VariantScope scope;
+
+ public ProcessJavaResConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName("process", "JavaRes");
+ }
+
+ @NonNull
+ @Override
+ public Class<Sync> getType() {
+ return Sync.class;
+ }
+
+ @Override
+ public void execute(@NonNull Sync processResources) {
+ scope.getVariantData().processJavaResourcesTask = processResources;
+ GradleVariantConfiguration variantConfiguration = scope.getVariantConfiguration();
+
+ AndroidSourceSet defaultSourceSet =
+ (AndroidSourceSet) variantConfiguration.getDefaultSourceSet();
+
+ processResources.from(defaultSourceSet.getResources().getSourceFiles());
+
+ if (!variantConfiguration.getType().isSingleBuildType()) {
+ AndroidSourceSet buildTypeSourceSet =
+ (AndroidSourceSet) variantConfiguration.getBuildTypeSourceSet();
+ checkState(buildTypeSourceSet != null); // checked isSingleBuildType() above.
+
+ processResources.from(buildTypeSourceSet.getResources().getSourceFiles());
+ }
+
+ if (variantConfiguration.hasFlavors()) {
+ List<SourceProvider> flavorSourceProviders =
+ variantConfiguration.getFlavorSourceProviders();
+
+ for (SourceProvider flavorSourceProvider : flavorSourceProviders) {
+ AndroidSourceSet flavorSourceSet = (AndroidSourceSet) flavorSourceProvider;
+ processResources.from(flavorSourceSet.getResources().getSourceFiles());
+ }
+
+ AndroidSourceSet multiFlavorSourceSet =
+ (AndroidSourceSet) variantConfiguration.getMultiFlavorSourceProvider();
+ if (multiFlavorSourceSet != null) {
+ processResources.from(multiFlavorSourceSet.getResources().getSourceFiles());
+ }
+ }
+
+ AndroidSourceSet variantSourceSet =
+ (AndroidSourceSet) variantConfiguration.getVariantSourceProvider();
+ if (variantSourceSet != null) {
+ processResources.from(variantSourceSet.getResources().getSourceFiles());
+ }
+
+ if (processResources.getInputs().getFiles().getFiles().isEmpty()) {
+ try {
+ FileUtils.deleteFolder(scope.getSourceFoldersJavaResDestinationDir());
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot delete merged source resource folder", e);
+ }
+ }
+ ConventionMappingHelper.map(
+ processResources,
+ "destinationDir",
+ Callables.returning(scope.getSourceFoldersJavaResDestinationDir()));
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/UnitTestConfigAction.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/UnitTestConfigAction.java
new file mode 100644
index 0000000..53f6f41
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/factory/UnitTestConfigAction.java
@@ -0,0 +1,163 @@
+/*
+ * 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.build.gradle.tasks.factory;
+
+import static com.android.SdkConstants.FN_FRAMEWORK_LIBRARY;
+import static com.android.builder.core.VariantType.UNIT_TEST;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.api.reporting.ConfigurableReport;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.api.tasks.testing.TestTaskReports;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Configuration Action for a JavaCompile task.
+ */
+public class UnitTestConfigAction implements TaskConfigAction<Test> {
+
+ final VariantScope scope;
+
+ public UnitTestConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return scope.getTaskName(UNIT_TEST.getPrefix());
+ }
+
+ @NonNull
+ @Override
+ public Class<Test> getType() {
+ return Test.class;
+ }
+
+ @Override
+ public void execute(@NonNull Test runTestsTask) {
+ final TestVariantData variantData = (TestVariantData)scope.getVariantData();
+ final BaseVariantData testedVariantData =
+ (BaseVariantData) variantData.getTestedVariantData();
+
+ // we run by default in headless mode, so the forked JVM doesn't steal focus.
+ runTestsTask.systemProperty("java.awt.headless", "true");
+
+ runTestsTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+ runTestsTask.setDescription(
+ "Run unit tests for the "
+ + testedVariantData.getVariantConfiguration().getFullName()
+ + " build.");
+
+ fixTestTaskSources(runTestsTask);
+
+ runTestsTask.setTestClassesDir(scope.getJavaOutputDir());
+
+ ConventionMappingHelper.map(runTestsTask, "classpath",
+ new Callable<ConfigurableFileCollection>() {
+ @Override
+ public ConfigurableFileCollection call() throws Exception {
+ Iterable<File> filteredBootClasspath = Iterables.filter(
+ scope.getGlobalScope().getAndroidBuilder().getBootClasspath(false),
+ new Predicate<File>() {
+ @Override
+ public boolean apply(@Nullable File file) {
+ Preconditions.checkState(file != null);
+ return !FN_FRAMEWORK_LIBRARY.equals(file.getName());
+ }
+ });
+
+ List<Object> classpaths = Lists.newArrayList();
+
+ // Get classpath values from tasks if the tasks are already created.
+ final AbstractCompile testCompileTask = variantData.javacTask;
+ if (testCompileTask != null) {
+ classpaths.add(testCompileTask.getClasspath());
+ classpaths.add(testCompileTask.getOutputs().getFiles());
+ } else {
+ classpaths.add(testedVariantData.getScope().getJavaOuptuts());
+ classpaths.add(scope.getJavaOuptuts());
+ }
+
+ classpaths.add(variantData.getJavaResourcesForUnitTesting());
+ classpaths.add(testedVariantData.getJavaResourcesForUnitTesting());
+ classpaths.add(filteredBootClasspath);
+
+ // Mockable JAR is last, to make sure you can shadow the classes with
+ // dependencies.
+ classpaths.add(scope.getGlobalScope().getMockableAndroidJarFile());
+
+ return scope.getGlobalScope().getProject().files(classpaths);
+ }
+ });
+
+ // Put the variant name in the report path, so that different testing tasks don't
+ // overwrite each other's reports.
+ // For component model plugin, the report tasks are not yet configured. We get a hardcoded
+ // value matching Gradle's default. This will eventually be replaced with the new Java
+ // plugin.
+ TestTaskReports testTaskReports = runTestsTask.getReports();
+ ConfigurableReport xmlReport = testTaskReports.getJunitXml();
+ xmlReport.setDestination(
+ xmlReport.getDestination() != null
+ ? new File(xmlReport.getDestination(), testedVariantData.getName())
+ : new File(scope.getGlobalScope().getTestResultsFolder(),
+ testedVariantData.getName()));
+
+ ConfigurableReport htmlReport = testTaskReports.getHtml();
+ htmlReport.setDestination(
+ htmlReport.getDestination() != null
+ ? new File(htmlReport.getDestination(), testedVariantData.getName())
+ : new File(scope.getGlobalScope().getTestReportFolder(),
+ testedVariantData.getName()));
+
+ scope.getGlobalScope().getExtension().getTestOptions().getUnitTests().applyConfiguration(
+ runTestsTask);
+
+ }
+
+ private static void fixTestTaskSources(@NonNull Test testTask) {
+ // We are running in afterEvaluate, so the JavaBasePlugin has already added a
+ // callback to add test classes to the list of source files of the newly created task.
+ // The problem is that we haven't configured the test classes yet (JavaBasePlugin
+ // assumes all Test tasks are fully configured at this point), so we have to remove the
+ // "directory null" entry from source files and add the right value.
+ //
+ // This is an ugly hack, since we assume sourceFiles is an instance of
+ // DefaultConfigurableFileCollection.
+ ((DefaultConfigurableFileCollection) testTask.getInputs().getSourceFiles()).getFrom().clear();
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/fd/FastDeployRuntimeExtractorTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/fd/FastDeployRuntimeExtractorTask.java
new file mode 100644
index 0000000..bc58842
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/fd/FastDeployRuntimeExtractorTask.java
@@ -0,0 +1,140 @@
+/*
+ * 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.build.gradle.tasks.fd;
+
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.DefaultAndroidTask;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+/**
+ * Task to extract the FastDeploy runtime from the gradle-core jar file into a folder to be picked
+ * up for co-packaging in the resulting application APK.
+ */
+public class FastDeployRuntimeExtractorTask extends DefaultAndroidTask {
+
+ private File outputFile;
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ public void setOutputFile(File file) {
+ this.outputFile = file;
+ }
+
+
+ // we could just extract the instant-runtime jar and place it as a stream once we
+ // don't have to deal with AppInfo replacement.
+ @TaskAction
+ public void extract() throws IOException {
+ URL fdrJar = FastDeployRuntimeExtractorTask.class
+ .getResource("/instant-run/instant-run-server.jar");
+ if (fdrJar == null) {
+ throw new RuntimeException("Couldn't find Instant-Run runtime library");
+ }
+ URLConnection urlConnection = fdrJar.openConnection();
+ urlConnection.setUseCaches(false);
+ Files.createParentDirs(getOutputFile());
+ JarOutputStream jarOutputStream = new JarOutputStream(
+ new BufferedOutputStream(new FileOutputStream(getOutputFile())));
+
+ InputStream inputStream = urlConnection.getInputStream();
+ try {
+ JarInputStream jarInputStream =
+ new JarInputStream(inputStream);
+ try {
+ ZipEntry entry = jarInputStream.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+ // don't extract metadata or classes supposed to be replaced by generated ones.
+ if (isValidForPackaging(name)) {
+ jarOutputStream.putNextEntry(new ZipEntry(entry.getName()));
+ ByteStreams.copy(jarInputStream, jarOutputStream);
+ jarOutputStream.closeEntry();
+ }
+ entry = jarInputStream.getNextEntry();
+ }
+ } finally {
+ jarInputStream.close();
+ jarOutputStream.close();
+ }
+ } finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+
+ }
+ }
+
+ /**
+ * Returns true if the fast deploy runtime jar entry should be packaged in the user's APK.
+ *
+ * @param name the fast deploy runtime jar entry name.
+ * @return true to package it, false otherwise.
+ */
+ private static boolean isValidForPackaging(String name) {
+ // don't extract metadata or classes supposed to be replaced by generated ones.
+ return !name.startsWith("META-INF") && !name.endsWith("AppInfo.class");
+ }
+
+ public static class ConfigAction implements TaskConfigAction<FastDeployRuntimeExtractorTask> {
+
+ private VariantScope scope;
+
+ public ConfigAction(VariantScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ public String getName() {
+ return scope.getTaskName("fastDeploy", "Extractor");
+ }
+
+ @Override
+ public Class<FastDeployRuntimeExtractorTask> getType() {
+ return FastDeployRuntimeExtractorTask.class;
+ }
+
+ @Override
+ public void execute(FastDeployRuntimeExtractorTask fastDeployRuntimeExtractorTask) {
+ fastDeployRuntimeExtractorTask.setVariantName(
+ scope.getVariantConfiguration().getFullName());
+ fastDeployRuntimeExtractorTask.setOutputFile(
+ scope.getIncrementalRuntimeSupportJar());
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/fd/GenerateInstantRunAppInfoTask.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/fd/GenerateInstantRunAppInfoTask.java
new file mode 100644
index 0000000..dcec992
--- /dev/null
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/fd/GenerateInstantRunAppInfoTask.java
@@ -0,0 +1,274 @@
+/*
+ * 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.build.gradle.tasks.fd;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PACKAGE;
+import static com.android.SdkConstants.TAG_APPLICATION;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.ICONST_1;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.LCONST_0;
+import static org.objectweb.asm.Opcodes.PUTSTATIC;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.V1_6;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.incremental.InstantRunPatchingPolicy;
+import com.android.build.gradle.internal.scope.ConventionMappingHelper;
+import com.android.build.gradle.internal.scope.TaskConfigAction;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.tasks.BaseTask;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.android.utils.XmlUtils;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.tooling.BuildException;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Reads the merged manifest file and creates an AppInfo class listing the applicationId and
+ * application classes (if any).
+ */
+public class GenerateInstantRunAppInfoTask extends BaseTask {
+
+ private File outputFile;
+ private File mergedManifest;
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ @InputFile
+ public File getMergedManifest() {
+ return mergedManifest;
+ }
+
+ @Input
+ boolean isUsingMultiApks() {
+ return usingMultiApks;
+ }
+
+ boolean usingMultiApks;
+
+ @TaskAction
+ public void generateInfoTask() throws IOException {
+ // Grab the application id and application class stashes away in the Android
+ // manifest (not in the Android namespace) and generate an AppInfo class.
+ // (Earlier, I did all the processing here - read the manifest, rewrite it by
+ // generating the AppInfo class and rewriting the manifest on the fly to have
+ // the new bootstrapping application, but we
+ // (1) need for this task to be done after manifest merging, and
+ // (2) need for it to be done before packaging
+ // but when combined with the current task dependencies (e.g. compilation
+ // depending on resource merging, such that R classes exist) this led to
+ // circular task dependencies. So for now, this is split into two parts:
+ // In manifest merging we stash away and replace the application id/class, and
+ // here in a packaging task we inject runtime libraries.
+ if (getMergedManifest().exists()) {
+ try {
+ Document document = XmlUtils.parseUtfXmlFile(getMergedManifest(), true);
+ Element root = document.getDocumentElement();
+ if (root != null) {
+ String applicationId = root.getAttribute(ATTR_PACKAGE);
+ String applicationClass = null;
+ NodeList children = root.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE &&
+ node.getNodeName().equals(TAG_APPLICATION)) {
+ String applicationClass1 = null;
+
+ Element element = (Element) node;
+ if (element.hasAttribute(ATTR_NAME)) {
+ String name = element.getAttribute(ATTR_NAME);
+ assert !name.startsWith(".") : name;
+ if (!name.isEmpty()) {
+ applicationClass1 = name;
+ }
+ }
+
+ applicationClass = applicationClass1;
+ break;
+ }
+ }
+
+ if (!applicationId.isEmpty()) {
+ File buildDir = getProject().getBuildDir();
+ long token = PackagingUtils.computeApplicationHash(buildDir);
+
+ // Must be *after* extractLibrary() to replace dummy version
+ writeAppInfoClass(applicationId, applicationClass, token);
+ }
+ }
+ } catch (ParserConfigurationException e) {
+ throw new BuildException("Failed to inject bootstrapping application", e);
+ } catch (IOException e) {
+ throw new BuildException("Failed to inject bootstrapping application", e);
+ } catch (SAXException e) {
+ throw new BuildException("Failed to inject bootstrapping application", e);
+ }
+ }
+ }
+
+ void writeAppInfoClass(
+ @NonNull String applicationId,
+ @Nullable String applicationClass,
+ long token)
+ throws IOException {
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+
+ String appInfoOwner = "com/android/tools/fd/runtime/AppInfo";
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, appInfoOwner, null, "java/lang/Object", null);
+
+ fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "applicationId", "Ljava/lang/String;", null, null);
+ fv.visitEnd();
+ fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "applicationClass", "Ljava/lang/String;", null, null);
+ fv.visitEnd();
+ fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "token", "J", null, null);
+ fv.visitEnd();
+ fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "usingApkSplits", "Z", null, null);
+ fv.visitEnd();
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "L" + appInfoOwner + ";", null, l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ mv.visitCode();
+ mv.visitLdcInsn(applicationId);
+ mv.visitFieldInsn(PUTSTATIC, appInfoOwner, "applicationId", "Ljava/lang/String;");
+ if (applicationClass != null) {
+ mv.visitLdcInsn(applicationClass);
+ } else {
+ mv.visitInsn(ACONST_NULL);
+ }
+ mv.visitFieldInsn(PUTSTATIC, appInfoOwner, "applicationClass", "Ljava/lang/String;");
+ if (token != 0L) {
+ mv.visitLdcInsn(token);
+ } else {
+ mv.visitInsn(LCONST_0);
+ }
+ mv.visitFieldInsn(PUTSTATIC, appInfoOwner, "token", "J");
+ if (isUsingMultiApks()) {
+ mv.visitInsn(ICONST_1);
+ } else {
+ mv.visitInsn(ICONST_0);
+ }
+ mv.visitFieldInsn(PUTSTATIC, appInfoOwner, "usingApkSplits", "Z");
+
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ cw.visitEnd();
+
+ byte[] bytes = cw.toByteArray();
+
+ JarOutputStream outputStream = new JarOutputStream(new BufferedOutputStream(
+ new FileOutputStream(getOutputFile())));
+ try {
+ outputStream.putNextEntry(new ZipEntry("com/android/tools/fd/runtime/AppInfo.class"));
+ outputStream.write(bytes);
+ outputStream.closeEntry();
+ } finally {
+ outputStream.close();
+ }
+ }
+
+ public static class ConfigAction implements TaskConfigAction<GenerateInstantRunAppInfoTask> {
+ private final VariantScope variantScope;
+
+ public ConfigAction(VariantScope variantScope) {
+ this.variantScope = variantScope;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return variantScope.getTaskName("generate", "InstantRunAppInfo");
+ }
+
+ @NonNull
+ @Override
+ public Class<GenerateInstantRunAppInfoTask> getType() {
+ return GenerateInstantRunAppInfoTask.class;
+ }
+
+ @Override
+ public void execute(@NonNull GenerateInstantRunAppInfoTask task) {
+ task.setVariantName(variantScope.getVariantConfiguration().getFullName());
+ task.outputFile =
+ new File(variantScope.getIncrementalApplicationSupportDir(),
+ PackageApplication.INSTANT_RUN_PACKAGES_PREFIX + "-bootstrap.jar");
+
+ BaseVariantOutputData variantOutput = variantScope.getVariantData()
+ .getOutputs().get(0);
+
+ task.mergedManifest =
+ variantOutput.getScope().getVariantScope().getInstantRunManifestOutputFile();
+ ConventionMappingHelper.map(task, "usingMultiApks",
+ new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return variantScope.getInstantRunBuildContext().getPatchingPolicy()
+ == InstantRunPatchingPolicy.MULTI_APK;
+ }
+ });
+
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/AbstractShrinker.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/AbstractShrinker.java
new file mode 100644
index 0000000..84676c8
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/AbstractShrinker.java
@@ -0,0 +1,382 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.utils.FileUtils;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Common code for both types of shrinker runs, {@link FullRunShrinker} and
+ * {@link IncrementalShrinker}.
+ */
+public abstract class AbstractShrinker<T> {
+
+ /**
+ * Specifies whether the shrinker should assume certain package names are specific to the SDK,
+ * to trim the graph as early as possible.
+ */
+ private static final boolean IGNORE_PACKAGE_NAME =
+ Boolean.getBoolean("android.newShrinker.ignorePackageName");
+
+ protected final WaitableExecutor<Void> mExecutor;
+
+ protected final ShrinkerGraph<T> mGraph;
+
+ protected final ShrinkerLogger mShrinkerLogger;
+
+ protected AbstractShrinker(
+ ShrinkerGraph<T> graph, WaitableExecutor<Void> executor, ShrinkerLogger shrinkerLogger) {
+ mGraph = graph;
+ mExecutor = executor;
+ mShrinkerLogger = shrinkerLogger;
+ }
+
+ /**
+ * Checks if a given class name starts with a package name that we assume to be an SDK class.
+ *
+ * <p>This way we can make the check cheaper in the common case and also filter out a lot of
+ * unnecessary edges from the graph early on, where we don't yet know which class is which.
+ */
+ static boolean isSdkPackage(@NonNull String className) {
+ if (IGNORE_PACKAGE_NAME) {
+ return false;
+ } else {
+ return className.startsWith("java/")
+ || (className.startsWith("android/")
+ // Match android/support and android/preview/support, possibly others.
+ && !className.contains("/support/"));
+ }
+ }
+
+ /**
+ * Tries to determine the output class file, for rewriting the given class file.
+ *
+ * <p>This will return {@link Optional#absent()} if the class is not part of the program to
+ * shrink (e.g. comes from a platform JAR).
+ */
+ @NonNull
+ protected Optional<File> chooseOutputFile(
+ @NonNull T klass,
+ @NonNull File classFile,
+ @NonNull Iterable<TransformInput> inputs,
+ @NonNull TransformOutputProvider output) {
+ String classFilePath = classFile.getAbsolutePath();
+
+ for (TransformInput input : inputs) {
+ Iterable<QualifiedContent> directoriesAndJars =
+ Iterables.concat(input.getDirectoryInputs(), input.getJarInputs());
+
+ for (QualifiedContent directoryOrJar : directoriesAndJars) {
+ File file = directoryOrJar.getFile();
+ if (classFilePath.startsWith(file.getAbsolutePath())) {
+ File outputDir = output.getContentLocation(
+ FileUtils.getDirectoryNameForJar(file),
+ directoryOrJar.getContentTypes(),
+ directoryOrJar.getScopes(),
+ Format.DIRECTORY);
+
+ return Optional.of(new File(outputDir, mGraph.getClassName(klass) + ".class"));
+ }
+ }
+ }
+
+ return Optional.absent();
+ }
+
+ /**
+ * Determines all directories where class files can be found in the given
+ * {@link TransformInput}.
+ */
+ @NonNull
+ protected static Collection<File> getAllDirectories(@NonNull TransformInput input) {
+ List<File> files = Lists.newArrayList();
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ files.add(directoryInput.getFile());
+ }
+
+ return files;
+ }
+
+ /**
+ * Determines all directories where class files can be found in the given
+ * {@link TransformInput}.
+ */
+ @NonNull
+ protected static Collection<File> getAllJars(@NonNull TransformInput input) {
+ List<File> files = Lists.newArrayList();
+ for (JarInput jarInput : input.getJarInputs()) {
+ files.add(jarInput.getFile());
+ }
+
+ return files;
+ }
+
+ /**
+ * Increments the counter on the given graph node. If the node just became reachable, keeps on
+ * walking the graph to find newly reachable nodes.
+ *
+ * @param member node to increment
+ * @param dependencyType type of counter to increment
+ * @param counterSet set of counters to work on
+ */
+ protected void incrementCounter(
+ @NonNull T member,
+ @NonNull DependencyType dependencyType,
+ @NonNull CounterSet counterSet) {
+ if (mGraph.incrementAndCheck(member, dependencyType, counterSet)) {
+ for (Dependency<T> dependency : mGraph.getDependencies(member)) {
+ incrementCounter(dependency.target, dependency.type, counterSet);
+ }
+ }
+ }
+
+ /**
+ * Finds existing methods or fields (graph nodes) which encountered opcodes refer to. Updates
+ * the graph with additional edges accordingly.
+ */
+ protected void resolveReferences(
+ @NonNull Iterable<PostProcessingData.UnresolvedReference<T>> unresolvedReferences) {
+ for (final PostProcessingData.UnresolvedReference<T> unresolvedReference : unresolvedReferences) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() {
+ T target = unresolvedReference.target;
+ T source = unresolvedReference.method;
+ T startClass = mGraph.getClassForMember(target);
+
+ if (unresolvedReference.invokespecial) {
+ // With invokespecial we disregard the class in target and start walking up
+ // the type hierarchy, starting from the superclass of the caller.
+ T sourceClass = mGraph.getClassForMember(source);
+ try {
+ startClass = mGraph.getSuperclass(sourceClass);
+ checkState(startClass != null);
+ } catch (ClassLookupException e) {
+ mShrinkerLogger.invalidClassReference(
+ mGraph.getClassName(sourceClass),
+ e.getClassName());
+ }
+ }
+
+ if (!mGraph.isClassKnown(startClass)) {
+ mShrinkerLogger.invalidClassReference(
+ mGraph.getMemberName(source),
+ mGraph.getClassName(startClass));
+
+ return null;
+ }
+
+ TypeHierarchyTraverser<T> traverser =
+ TypeHierarchyTraverser.superclassesAndInterfaces(mGraph, mShrinkerLogger);
+
+ for (T currentClass : traverser.preOrderTraversal(startClass)) {
+ T matchingMethod = mGraph.findMatchingMethod(
+ currentClass,
+ target);
+ if (matchingMethod != null) {
+ if (isProgramClass(mGraph.getClassForMember(matchingMethod))) {
+ mGraph.addDependency(
+ source,
+ currentClass,
+ unresolvedReference.dependencyType);
+ mGraph.addDependency(
+ source,
+ matchingMethod,
+ unresolvedReference.dependencyType);
+ }
+ return null;
+ }
+ }
+
+ mShrinkerLogger.invalidMemberReference(
+ mGraph.getMemberName(source),
+ mGraph.getMemberName(target));
+
+ return null;
+ }
+ });
+ }
+ }
+
+ protected boolean isProgramClass(T klass) {
+ return !mGraph.isLibraryClass(klass);
+ }
+
+ /**
+ * Rewrites the given class (read from file) to only include used methods and fields. Returns
+ * the new class bytecode as {@code byte[]}.
+ */
+ @NonNull
+ protected static byte[] rewrite(
+ @NonNull String className,
+ @NonNull File classFile,
+ @NonNull Set<String> membersToKeep,
+ @NonNull Predicate<String> keepInterface) throws IOException {
+ byte[] bytes;
+ if (Files.getFileExtension(classFile.getName()).equals("class")) {
+ bytes = Files.toByteArray(classFile);
+ } else {
+ JarFile jarFile = new JarFile(classFile);
+ try {
+ JarEntry jarEntry = jarFile.getJarEntry(className + ".class");
+ bytes = ByteStreams.toByteArray(jarFile.getInputStream(jarEntry));
+ } finally {
+ jarFile.close();
+ }
+ }
+
+ ClassReader classReader = new ClassReader(bytes);
+ // Don't pass the reader as an argument to the writer. This forces the writer to recompute
+ // the constant pool, which we want, since it can contain unused entries that end up in the
+ // dex file.
+ ClassWriter classWriter = new ClassWriter(0);
+ ClassVisitor filter = new FilterMembersVisitor(membersToKeep, keepInterface, classWriter);
+ classReader.accept(filter, 0);
+ return classWriter.toByteArray();
+ }
+
+ /**
+ * Walks the entire graph, starting from the roots, and increments counters for reachable nodes.
+ */
+ protected void setCounters(@NonNull final CounterSet counterSet) {
+ Map<T, DependencyType> roots = mGraph.getRoots(counterSet);
+ for (final Map.Entry<T, DependencyType> toIncrementEntry : roots.entrySet()) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ incrementCounter(
+ toIncrementEntry.getKey(),
+ toIncrementEntry.getValue(),
+ counterSet);
+ return null;
+ }
+ });
+ }
+ waitForAllTasks();
+ }
+
+ /**
+ * Writes updates class files to the outputs.
+ */
+ protected void updateClassFiles(
+ @NonNull Iterable<T> classesToWrite,
+ @NonNull Iterable<File> classFilesToDelete,
+ @NonNull Iterable<TransformInput> inputs,
+ @NonNull TransformOutputProvider output) throws IOException {
+ for (final T klass : classesToWrite) {
+ final File sourceFile = mGraph.getSourceFile(klass);
+ checkState(sourceFile != null, "Program class has no source file.");
+
+ final Optional<File> outputFile = chooseOutputFile(klass, sourceFile, inputs, output);
+ if (!outputFile.isPresent()) {
+ // The class is from code we don't control.
+ continue;
+ }
+ Files.createParentDirs(outputFile.get());
+
+ final Predicate<String> keepInterfacePredicate = new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ T iface = mGraph.getClassReference(input);
+
+ return !isProgramClass(iface)
+ || mGraph.isReachable(iface, CounterSet.SHRINK);
+ }
+ };
+
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ byte[] newBytes = rewrite(
+ mGraph.getClassName(klass),
+ sourceFile,
+ mGraph.getReachableMembersLocalNames(klass, CounterSet.SHRINK),
+ keepInterfacePredicate);
+
+ Files.write(newBytes, outputFile.get());
+ return null;
+ }
+ });
+ }
+
+ for (File classFile : classFilesToDelete) {
+ FileUtils.delete(classFile);
+ }
+
+ waitForAllTasks();
+ }
+
+ protected void waitForAllTasks() {
+ try {
+ mExecutor.waitForTasksWithQuickFail(true);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (LoggedErrorException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Set of counters, for keeping different sets of reachable nodes for different purposes.
+ */
+ public enum CounterSet {
+ /** Counters for removing dead code. */
+ SHRINK,
+
+ /** Counters for finding classes that have to be in the main classes.dex file. */
+ LEGACY_MULTIDEX
+ }
+
+ public static void logTime(String section, Stopwatch stopwatch) {
+ if (System.getProperty("android.newShrinker.profile") != null) {
+ System.out.println(section + ": " + stopwatch);
+ stopwatch.reset();
+ stopwatch.start();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ClassLookupException.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ClassLookupException.java
new file mode 100644
index 0000000..c0eadef
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ClassLookupException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.build.gradle.shrinker;
+
+/**
+ * Checked exception thrown by all graph operations that may fail due to invalid class being
+ * referenced.
+ */
+public class ClassLookupException extends Exception {
+
+ private final String mClassName;
+
+ public ClassLookupException(String className) {
+ this.mClassName = className;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Invalid class reference: " + mClassName;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ClassStructureVisitor.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ClassStructureVisitor.java
new file mode 100644
index 0000000..1b7e617
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ClassStructureVisitor.java
@@ -0,0 +1,93 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.File;
+
+/**
+ * {@link ClassVisitor} that adds visited classes, methods and fields to a {@link ShrinkerGraph}.
+ */
+public class ClassStructureVisitor<T> extends ClassVisitor {
+
+ @Nullable
+ private final File mClassFile;
+ private final ShrinkerGraph<T> mGraph;
+
+ private T mClass;
+
+ public ClassStructureVisitor(
+ @NonNull ShrinkerGraph<T> graph,
+ @Nullable File classFile,
+ @Nullable ClassVisitor cv) {
+ super(Opcodes.ASM5, cv);
+ mClassFile = classFile;
+ mGraph = graph;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ mClass = mGraph.addClass(name, superName, interfaces, access, mClassFile);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ final T method = mGraph.addMember(mClass, name, desc, access);
+
+ MethodVisitor superVisitor = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM5, superVisitor) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ mGraph.addAnnotation(method, Type.getType(desc).getInternalName());
+ return super.visitAnnotation(desc, visible);
+ }
+ };
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ final T field = mGraph.addMember(mClass, name, desc, access);
+
+ FieldVisitor superVisitor = super.visitField(access, name, desc, signature, value);
+ return new FieldVisitor(Opcodes.ASM5, superVisitor) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ mGraph.addAnnotation(field, Type.getType(desc).getInternalName());
+ return super.visitAnnotation(desc, visible);
+ }
+ };
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ mGraph.addAnnotation(mClass, Type.getType(desc).getInternalName());
+ return super.visitAnnotation(desc, visible);
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/Dependency.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/Dependency.java
new file mode 100644
index 0000000..d705568
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/Dependency.java
@@ -0,0 +1,62 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Objects;
+
+import java.io.Serializable;
+
+/**
+ * Edge in the shrinker graph.
+ */
+public final class Dependency<T> implements Serializable {
+ @NonNull
+ final T target;
+
+ @NonNull
+ final DependencyType type;
+
+ public Dependency(@NonNull T target, @NonNull DependencyType type) {
+ this.target = checkNotNull(target);
+ this.type = checkNotNull(type);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof Dependency) {
+ Dependency<?> that = (Dependency<?>) object;
+ return Objects.equal(target, that.target) && type == that.type;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(target, type);
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("target", target)
+ .add("type", type)
+ .toString();
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyFinderVisitor.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyFinderVisitor.java
new file mode 100644
index 0000000..4a4643e
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyFinderVisitor.java
@@ -0,0 +1,678 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static com.android.build.gradle.shrinker.AbstractShrinker.isSdkPackage;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.shrinker.PostProcessingData.UnresolvedReference;
+import com.android.utils.AsmUtils;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * {@link ClassVisitor} that finds all dependencies that should be added to the shrinker graph.
+ *
+ * <p>Subclasses should implement the {@link #handleDependency(Object, Object, DependencyType)}
+ * method.
+ */
+abstract class DependencyFinderVisitor<T> extends ClassVisitor {
+ private final ShrinkerGraph<T> mGraph;
+ private String mClassName;
+ private boolean mIsAnnotation;
+ private T mKlass;
+
+ DependencyFinderVisitor(
+ ShrinkerGraph<T> graph,
+ ClassVisitor cv) {
+ super(Opcodes.ASM5, cv);
+ mGraph = graph;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ if (interfaces == null) {
+ interfaces = new String[0];
+ }
+
+ mKlass = mGraph.getClassReference(name);
+ if (superName != null && !isSdkPackage(superName)) {
+ // Don't create graph edges for obvious things.
+ handleDependency(
+ mKlass,
+ mGraph.getClassReference(superName),
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+
+ if (interfaces.length > 0) {
+ handleInterfaceInheritance(mKlass);
+
+ if (!Objects.equal(superName, "java/lang/Object")) {
+ // It's possible the superclass is implementing a method from the interface, we may
+ // need to add more edges to the graph to represent this.
+ handleMultipleInheritance(mKlass);
+ }
+ }
+
+ mClassName = name;
+ mIsAnnotation = (access & Opcodes.ACC_ANNOTATION) != 0;
+
+ if (signature != null) {
+ handleClassSignature(mKlass, signature);
+ }
+
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ T method = mGraph.getMemberReference(mClassName, name, desc);
+
+ if ((access & Opcodes.ACC_STATIC) == 0 && !name.equals(AsmUtils.CONSTRUCTOR)) {
+ handleVirtualMethod(method);
+ }
+
+ Type methodType = Type.getMethodType(desc);
+ handleDeclarationType(method, methodType.getReturnType());
+ for (Type argType : methodType.getArgumentTypes()) {
+ handleDeclarationType(method, argType);
+ }
+
+ if (name.equals(AsmUtils.CLASS_INITIALIZER)) {
+ handleDependency(mKlass, method, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+
+ if (mIsAnnotation) {
+ // TODO: Strip unused annotation classes members.
+ handleDependency(mKlass, method, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+
+ if (signature != null) {
+ handleClassSignature(method, signature);
+ }
+
+ return new DependencyFinderMethodVisitor(
+ method,
+ super.visitMethod(access, name, desc, signature, exceptions));
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ T field = mGraph.getMemberReference(mClassName, name, desc);
+ Type fieldType = Type.getType(desc);
+ handleDeclarationType(field, fieldType);
+
+ if (signature != null) {
+ SignatureReader reader = new SignatureReader(signature);
+ SignatureVisitor visitor = new DependencyFinderSignatureVisitor(field);
+ reader.acceptType(visitor);
+ }
+
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (!visible) {
+ return super.visitAnnotation(desc, false);
+ } else {
+ Type type = Type.getType(desc);
+ handleDeclarationType(mKlass, type);
+ return new DependencyFinderAnnotationVisitor(
+ type.getInternalName(),
+ mKlass,
+ super.visitAnnotation(desc, true));
+ }
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ if (mClassName.equals(name) && outerName != null) {
+ // I'm the inner class, keep the outer class, as ProGuard does.
+ handleDependency(
+ mKlass,
+ mGraph.getClassReference(outerName),
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ private void handleDeclarationType(T member, Type type) {
+ String className = getClassName(type);
+ if (className != null && !isSdkPackage(className)) {
+ T classReference = mGraph.getClassReference(className);
+ handleDependency(member, classReference, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+ }
+
+ private void handleClassSignature(T source, String signature) {
+ SignatureReader reader = new SignatureReader(signature);
+ SignatureVisitor visitor = new DependencyFinderSignatureVisitor(source);
+ reader.accept(visitor);
+ }
+
+ @Nullable
+ private static String getClassName(String desc) {
+ return getClassName(Type.getType(desc));
+ }
+
+ @Nullable
+ private static String getClassName(Type type) {
+ switch (type.getSort()) {
+ case Type.VOID:
+ case Type.METHOD:
+ case Type.SHORT:
+ case Type.INT:
+ case Type.LONG:
+ case Type.FLOAT:
+ case Type.BOOLEAN:
+ case Type.BYTE:
+ case Type.DOUBLE:
+ case Type.CHAR:
+ return null;
+ case Type.ARRAY:
+ return getClassName(type.getElementType());
+ case Type.OBJECT:
+ return type.getInternalName();
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ protected abstract void handleDependency(T source, T target, DependencyType type);
+ protected abstract void handleMultipleInheritance(T klass);
+ protected abstract void handleVirtualMethod(T method);
+ protected abstract void handleInterfaceInheritance(T klass);
+ protected abstract void handleUnresolvedReference(UnresolvedReference<T> reference);
+
+ private class DependencyFinderMethodVisitor extends MethodVisitor {
+
+ private final T mMethod;
+
+ /*
+ * We want to detect calls to AtomicFieldUpdaters to figure out dependencies in this
+ * common case that uses reflection. We detect the method call and, if both instructions
+ * that preceed it are two LDCs. In that case the first one should be a type and the
+ * second one should be a field name.
+ */
+ private final Deque<Object> mLastLdcs;
+
+ DependencyFinderMethodVisitor(T method, MethodVisitor mv) {
+ super(Opcodes.ASM5, mv);
+ this.mMethod = method;
+ mLastLdcs = new ArrayDeque<Object>();
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (!visible) {
+ return super.visitAnnotation(desc, false);
+ } else {
+ Type type = Type.getType(desc);
+ handleDeclarationType(mMethod, type);
+ return new DependencyFinderAnnotationVisitor(
+ type.getInternalName(),
+ mMethod,
+ super.visitAnnotation(desc, true));
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new DependencyFinderAnnotationVisitor(null, mMethod, super.visitAnnotationDefault());
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ String className = getClassName(Type.getObjectType(type));
+ if (className != null && !isSdkPackage(className)) {
+ T classReference = mGraph.getClassReference(className);
+ handleDependency(mMethod, classReference, DependencyType.REQUIRED_CODE_REFERENCE);
+ }
+
+ mLastLdcs.clear();
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (!isSdkPackage(owner)) {
+ handleDependency(
+ mMethod,
+ mGraph.getClassReference(owner),
+ DependencyType.REQUIRED_CODE_REFERENCE);
+ T target = mGraph.getMemberReference(owner, name, desc);
+ handleUnresolvedReference(
+ new UnresolvedReference<T>(
+ mMethod,
+ target,
+ opcode == Opcodes.INVOKESPECIAL));
+ }
+
+ mLastLdcs.clear();
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ Type type = Type.getObjectType(((Type) cst).getInternalName());
+ String className = getClassName(type);
+ if (className != null && !isSdkPackage(className)) {
+ T classReference = mGraph.getClassReference(className);
+ handleDependency(
+ mMethod,
+ classReference,
+ DependencyType.REQUIRED_CODE_REFERENCE);
+ }
+ }
+
+ mLastLdcs.push(cst);
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String desc, boolean itf) {
+ // This can be the case when calling "clone" on arrays, which is done in Enum classes.
+ // Just ignore it, we know arrays not declared in the program, so there's no point in
+ // creating the dependency.
+ Type type = Type.getType(owner);
+ if (type.getSort() != Type.ARRAY && !isSdkPackage(owner)) {
+ handleDependency(
+ mMethod,
+ mGraph.getClassReference(owner),
+ DependencyType.REQUIRED_CODE_REFERENCE);
+
+ T target = mGraph.getMemberReference(owner, name, desc);
+
+ if (opcode == Opcodes.INVOKESPECIAL
+ && (name.equals(AsmUtils.CONSTRUCTOR) || owner.equals(mClassName))) {
+ // The "invokenonvirtual" semantics of invokespecial, for calling constructors
+ // and private methods.
+ handleDependency(mMethod, target, DependencyType.REQUIRED_CODE_REFERENCE);
+ } else {
+ // In all other cases we have to go through resolution stage (including fields,
+ // static methods etc).
+ handleUnresolvedReference(
+ new UnresolvedReference<T>(
+ mMethod,
+ target,
+ opcode == Opcodes.INVOKESPECIAL));
+ }
+ }
+
+ ReflectionMethod reflectionMethod =
+ ReflectionMethod.findBySignature(new Signature(owner, name, desc));
+
+ if (reflectionMethod != null) {
+ Deque<Object> stackCopy = new ArrayDeque<Object>(mLastLdcs);
+ T target = reflectionMethod.getMember(mGraph, stackCopy);
+ if (target != null) {
+ if (reflectionMethod == ReflectionMethod.CLASS_FOR_NAME) {
+ // 'target' is a class, create a direct dependency.
+ handleDependency(
+ mMethod,
+ target,
+ DependencyType.REQUIRED_CODE_REFERENCE_REFLECTION);
+ } else {
+ // Resolve the exact dependency.
+ handleUnresolvedReference(
+ new UnresolvedReference<T>(
+ mMethod,
+ target,
+ false,
+ DependencyType.REQUIRED_CODE_REFERENCE_REFLECTION));
+ }
+ }
+ }
+
+ mLastLdcs.clear();
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ String className = getClassName(desc);
+ if (className != null && !isSdkPackage(className)) {
+ handleDependency(
+ mMethod,
+ mGraph.getClassReference(className),
+ DependencyType.REQUIRED_CODE_REFERENCE);
+ }
+
+ mLastLdcs.clear();
+ super.visitMultiANewArrayInsn(desc, dims);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (type != null && !isSdkPackage(type)) {
+ handleDependency(
+ mMethod,
+ mGraph.getClassReference(type),
+ DependencyType.REQUIRED_CODE_REFERENCE);
+ }
+
+ mLastLdcs.clear();
+ super.visitTryCatchBlock(start, end, handler, type);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ mLastLdcs.clear();
+ super.visitInsn(opcode);
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ mLastLdcs.clear();
+ super.visitIntInsn(opcode, operand);
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ mLastLdcs.clear();
+ super.visitVarInsn(opcode, var);
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
+ Object... bsmArgs) {
+ mLastLdcs.clear();
+ super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ mLastLdcs.clear();
+ super.visitIincInsn(var, increment);
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ mLastLdcs.clear();
+ super.visitJumpInsn(opcode, label);
+ }
+ }
+
+ private class DependencyFinderAnnotationVisitor extends AnnotationVisitor {
+ private final String mAnnotationName;
+ private final T mSource;
+
+ DependencyFinderAnnotationVisitor(String annotationName, T source, AnnotationVisitor av) {
+ super(Opcodes.ASM5, av);
+ mAnnotationName = annotationName;
+ mSource = source;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (value instanceof Type) {
+ handleDeclarationType(mSource, (Type) value);
+ }
+ super.visit(name, value);
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ String internalName = getClassName(desc);
+ if (internalName != null) {
+ handleDependency(
+ mSource,
+ mGraph.getClassReference(internalName),
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ handleDependency(
+ mSource,
+ mGraph.getMemberReference(internalName, value, desc),
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+
+ super.visitEnum(name, desc, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ String internalName = getClassName(desc);
+ if (internalName != null) {
+ handleDependency(
+ mSource,
+ mGraph.getClassReference(internalName),
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+ return new DependencyFinderAnnotationVisitor(
+ mAnnotationName,
+ mSource,
+ super.visitAnnotation(name, desc));
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return new DependencyFinderAnnotationVisitor(
+ mAnnotationName,
+ mSource,
+ super.visitArray(name));
+ }
+ }
+
+ private class DependencyFinderSignatureVisitor extends SignatureVisitor {
+
+ private final T mSource;
+
+ DependencyFinderSignatureVisitor(T source) {
+ super(Opcodes.ASM5);
+ mSource = source;
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ if (!isSdkPackage(name)) {
+ handleDependency(
+ mSource,
+ mGraph.getClassReference(name),
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+ super.visitClassType(name);
+ }
+ }
+
+ /** A method signature, tuple of owner, name and descriptor. */
+ private static class Signature {
+ @NonNull private final String owner;
+ @NonNull private final String name;
+ @NonNull private final String desc;
+
+ Signature(@NonNull String owner, @NonNull String name, @NonNull String desc) {
+ this.owner = owner;
+ this.name = name;
+ this.desc = desc;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Signature method = (Signature) o;
+ return Objects.equal(owner, method.owner)
+ && Objects.equal(name, method.name)
+ && Objects.equal(desc, method.desc);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(owner, name, desc);
+ }
+ }
+
+ /**
+ * Represents reflection APIs that we recognize and understand.
+ */
+ private enum ReflectionMethod {
+ CLASS_FOR_NAME("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;") {
+ @Override
+ public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
+ if (!(stack.peek() instanceof String)) {
+ return null;
+ }
+
+ return graph.getClassReference(AsmUtils.toInternalName((String) stack.pop()));
+ }
+ },
+ ATOMIC_INTEGER_FIELD_UPDATER(
+ "java/util/concurrent/atomic/AtomicIntegerFieldUpdater",
+ "newUpdater",
+ "(Ljava/lang/Class;Ljava/lang/String;)"
+ + "Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;") {
+ @Override
+ public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
+ return primitiveFieldUpdater(graph, stack, "I");
+ }
+ },
+ ATOMIC_LONG_FIELD_UPDATER(
+ "java/util/concurrent/atomic/AtomicLongFieldUpdater",
+ "newUpdater",
+ "(Ljava/lang/Class;Ljava/lang/String;)"
+ + "Ljava/util/concurrent/atomic/AtomicLongFieldUpdater;") {
+ @Override
+ public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
+ return primitiveFieldUpdater(graph, stack, "J");
+ }
+ },
+ ATOMIC_REFERENCE_FIELD_UPDATER(
+ "java/util/concurrent/atomic/AtomicReferenceFieldUpdater",
+ "newUpdater",
+ "(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)"
+ + "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;") {
+ @Override
+ public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
+ if (!(stack.peek() instanceof String)) {
+ return null;
+ }
+ String fieldName = (String) stack.pop();
+
+ if (!(stack.peek() instanceof Type)) {
+ return null;
+ }
+ Type fieldType = (Type) stack.pop();
+
+ if (!(stack.peek() instanceof Type)) {
+ return null;
+ }
+ Type klass = (Type) stack.pop();
+
+ return graph.getMemberReference(
+ klass.getInternalName(),
+ fieldName,
+ fieldType.getDescriptor());
+ }
+ },
+ ;
+ private static final ImmutableMap<Signature, ReflectionMethod> BY_SIGNATURE;
+
+ static {
+ // Store all known reflection methods, indexed by "full" signature.
+ ImmutableMap.Builder<Signature, ReflectionMethod> builder = ImmutableMap.builder();
+ for (ReflectionMethod reflectionMethod : ReflectionMethod.values()) {
+ builder.put(reflectionMethod.getSignature(), reflectionMethod);
+ }
+ BY_SIGNATURE = builder.build();
+ }
+
+ /**
+ * Finds an instance of {@link ReflectionMethod} for the given {@link Signature}.
+ *
+ * @param signature signature to check
+ * @return {@link ReflectionMethod} with the given signature, if supported, null otherwise
+ */
+ @Nullable
+ public static ReflectionMethod findBySignature(Signature signature) {
+ return BY_SIGNATURE.get(signature);
+ }
+
+ @NonNull private Signature mSignature;
+
+ ReflectionMethod(@NonNull String owner, @NonNull String name, @NonNull String desc) {
+ mSignature = new Signature(owner, name, desc);
+ }
+
+ @NonNull
+ public Signature getSignature() {
+ return mSignature;
+ }
+
+ /**
+ * Returns the referenced class or member, that the given reflection method indirectly
+ * references.
+ *
+ * <p>{@link DependencyFinderVisitor} keeps track of consecutive {@code ldc} instructions
+ * and passes the loaded values to this method as the {@code stack} parameter.
+ *
+ * @param graph {@link ShrinkerGraph} in use
+ * @param stack read-only copy of the constant values that have been pushed on the stack
+ * just before invoking the method in question
+ * @return target of the "indirect" reference, or null if it cannot be determined from the
+ * constant arguments
+ */
+ @Nullable
+ public abstract <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack);
+
+ /**
+ * Common code for handling {@code AtomicIntegerFieldUpdater} and
+ * {@code AtomicLongFieldUpdater}.
+ *
+ * @see #getMember(ShrinkerGraph, Deque)
+ */
+ private static <T> T primitiveFieldUpdater(
+ @NonNull ShrinkerGraph<T> graph,
+ @NonNull Deque<Object> stack,
+ @NonNull String desc) {
+ if (!(stack.peek() instanceof String)) {
+ return null;
+ }
+ String fieldName = (String) stack.pop();
+
+ if (!(stack.peek() instanceof Type)) {
+ return null;
+ }
+ Type type = (Type) stack.pop();
+
+ return graph.getMemberReference(type.getInternalName(), fieldName, desc);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyRemoverVisitor.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyRemoverVisitor.java
new file mode 100644
index 0000000..435f3de
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyRemoverVisitor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.shrinker;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A {@link ClassVisitor} that removes dependencies created by code references of the given class.
+ * They will be re-added by another run of the {@link DependencyFinderVisitor}.
+ */
+public class DependencyRemoverVisitor<T> extends ClassVisitor {
+
+ private final ShrinkerGraph<T> mGraph;
+
+ private String mClassName;
+
+ public DependencyRemoverVisitor(ShrinkerGraph<T> graph, ClassVisitor cv) {
+ super(Opcodes.ASM5, cv);
+ mGraph = graph;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ mClassName = name;
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ mGraph.removeAllCodeDependencies(mGraph.getMemberReference(mClassName, name, desc));
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyType.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyType.java
new file mode 100644
index 0000000..8c558c4
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/DependencyType.java
@@ -0,0 +1,76 @@
+/*
+ * 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.build.gradle.shrinker;
+
+/**
+ * Type of the dependency (edge) between two nodes in the shrinker graph.
+ *
+ * <p>A node is considered "reachable" if:
+ * <ul>
+ * <li>it can be reached with a required edge, or
+ * <li>at least one {@link #IF_CLASS_KEPT} and at least one {@link #CLASS_IS_KEPT} edge, or
+ * <li>at least one {@link #SUPERINTERFACE_KEPT} and at least one {@link #INTERFACE_IMPLEMENTED}
+ * edge.
+ * </ul>
+ *
+ * <p>The second case models the situation where a method implements an interface method. The only
+ * case when it should be kept is if the interface method is called somewhere (1) AND the containing
+ * class is used in some way (2). Otherwise the super-method can be removed from both the interface
+ * and the class (not 1) or the whole implementation class can be removed (not 2).
+ */
+public enum DependencyType {
+ /** Target is referenced from an opcode. */
+ REQUIRED_CODE_REFERENCE,
+
+ /**
+ * Target is referenced by name in reflection code. Same as above, but doesn't trigger
+ * warnings.
+ */
+ REQUIRED_CODE_REFERENCE_REFLECTION,
+
+ /** Target type is referenced in class declaration. */
+ REQUIRED_CLASS_STRUCTURE,
+
+ /**
+ * Target member should be kept, assuming its owner class is kept.
+ * @see #CLASS_IS_KEPT
+ */
+ IF_CLASS_KEPT,
+
+ /**
+ * Target member's owner class is kept.
+ * @see #IF_CLASS_KEPT
+ */
+ CLASS_IS_KEPT,
+
+ /**
+ * Superinterface of the target interface is kept. If the target is implemented by some class,
+ * it should be kept as well.
+ *
+ * @see #INTERFACE_IMPLEMENTED
+ */
+ SUPERINTERFACE_KEPT,
+
+ /**
+ * Target interface is implemented (directly or indirectly) by a kept class, so it may need to
+ * be kept if a superinterface is kept as well.
+ *
+ * @see #SUPERINTERFACE_KEPT
+ */
+ INTERFACE_IMPLEMENTED,
+ ;
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/FilterMembersVisitor.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/FilterMembersVisitor.java
new file mode 100644
index 0000000..ab11e19
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/FilterMembersVisitor.java
@@ -0,0 +1,85 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@link ClassVisitor} that skips class members which are not reachable. It also filters the list
+ * of implemented interfaces.
+ */
+public class FilterMembersVisitor extends ClassVisitor {
+ private final Set<String> mMembers;
+ private final Predicate<String> mClassKeptPredicate;
+
+ public FilterMembersVisitor(Set<String> members, Predicate<String> classKeptPredicate, ClassVisitor cv) {
+ super(Opcodes.ASM5, cv);
+ mMembers = members;
+ mClassKeptPredicate = classKeptPredicate;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ List<String> interfacesToKeep = Lists.newArrayList();
+ for (String iface : interfaces) {
+ if (mClassKeptPredicate.apply(iface)) {
+ interfacesToKeep.add(iface);
+ }
+ }
+
+ super.visit(version, access, name, signature, superName, Iterables.toArray(interfacesToKeep, String.class));
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mMembers.contains(name + ":" + desc)) {
+ return super.visitField(access, name, desc, signature, value);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ if (mMembers.contains(name + ":" + desc)) {
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // Remove constant pool references to removed classes.
+ if (mClassKeptPredicate.apply(name)) {
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/FullRunShrinker.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/FullRunShrinker.java
new file mode 100644
index 0000000..9ae023d
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/FullRunShrinker.java
@@ -0,0 +1,508 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static com.android.utils.FileUtils.getAllFiles;
+import static com.android.utils.FileUtils.withExtension;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.TreeTraverser;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Code shrinker. It analyzes the input classes and the SDK jar and outputs minified classes. Uses
+ * the given implementation of {@link ShrinkerGraph} to keep state and persist it for later
+ * incremental runs.
+ */
+public class FullRunShrinker<T> extends AbstractShrinker<T> {
+
+ /** Suffix for "fake methods", inserted to forward dependencies between unrelated classes. */
+ static final String SHRINKER_FAKE_MARKER = "$shrinker_fake";
+
+ private final Set<File> mPlatformJars;
+
+ public FullRunShrinker(
+ WaitableExecutor<Void> executor,
+ ShrinkerGraph<T> graph,
+ Set<File> platformJars,
+ ShrinkerLogger shrinkerLogger) {
+ super(graph, executor, shrinkerLogger);
+ mPlatformJars = platformJars;
+ }
+
+ /**
+ * Performs the full shrinking run. This clears previous incremental state, creates a new
+ * {@link ShrinkerGraph} and fills it with data read from the platform JARs as well as input
+ * classes. Then we find "entry points" that match {@code -keep} rules from the config file,
+ * and walk the graph, setting the counters and finding reachable classes and members. In the
+ * last step we rewrite all reachable class files to only contain kept class members and put
+ * them in the matching output directories.
+ */
+ public void run(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedClasses,
+ @NonNull TransformOutputProvider output,
+ @NonNull ImmutableMap<CounterSet, KeepRules> keepRules,
+ boolean saveState) throws IOException {
+ output.deleteAll();
+
+ buildGraph(inputs, referencedClasses);
+
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ setCounters(keepRules);
+ logTime("Set counters", stopwatch);
+ writeOutput(inputs, output);
+ logTime("Write output", stopwatch);
+
+ if (saveState) {
+ mGraph.saveState();
+ logTime("Saving state", stopwatch);
+ }
+ }
+
+ /**
+ * Populates the graph with all nodes (classes, members) and edges (dependencies, references),
+ * so that it's ready to be traversed in search of reachable ndoes.
+ */
+ private void buildGraph(
+ @NonNull Iterable<TransformInput> programInputs,
+ @NonNull Iterable<TransformInput> libraryInputs) throws IOException {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ final PostProcessingData<T> postProcessingData = new PostProcessingData<T>();
+
+ readPlatformJars();
+
+ for (TransformInput input : libraryInputs) {
+ for (File directory : getAllDirectories(input)) {
+ for (final File classFile : getClassFiles(directory)) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ processLibraryClass(Files.toByteArray(classFile));
+ return null;
+ }
+ });
+ }
+ }
+
+ for (final File jarFile : getAllJars(input)) {
+ processJarFile(jarFile, new ByteCodeConsumer() {
+ @Override
+ public void process(byte[] bytes) throws IOException {
+ processLibraryClass(bytes);
+ }
+ });
+ }
+ }
+
+ for (TransformInput input : programInputs) {
+ for (File directory : getAllDirectories(input)) {
+ for (final File classFile : getClassFiles(directory)) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ processProgramClassFile(
+ Files.toByteArray(classFile),
+ classFile,
+ postProcessingData);
+ return null;
+ }
+ });
+ }
+ }
+
+ for (final File jarFile : getAllJars(input)) {
+ processJarFile(jarFile, new ByteCodeConsumer() {
+ @Override
+ public void process(byte[] bytes) throws IOException {
+ processProgramClassFile(
+ bytes,
+ jarFile,
+ postProcessingData);
+ }
+ });
+ }
+ }
+ waitForAllTasks();
+ logTime("Read input", stopwatch);
+
+ handleOverrides(postProcessingData.getVirtualMethods());
+ handleMultipleInheritance(postProcessingData.getMultipleInheritance());
+ handleInterfaceInheritance(postProcessingData.getInterfaceInheritance());
+ resolveReferences(postProcessingData.getUnresolvedReferences());
+ waitForAllTasks();
+ logTime("Finish graph", stopwatch);
+
+ mGraph.checkDependencies(mShrinkerLogger);
+ }
+
+ private void handleInterfaceInheritance(@NonNull Set<T> interfaceInheritance) {
+ for (final T klass : interfaceInheritance) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ TreeTraverser<T> interfaceTraverser =
+ TypeHierarchyTraverser.interfaces(mGraph, mShrinkerLogger);
+
+ if ((mGraph.getClassModifiers(klass) & Opcodes.ACC_INTERFACE) != 0) {
+
+ // The "children" name is unfortunate: in the type hierarchy tree traverser,
+ // these are the interfaces that klass (which is an interface itself)
+ // extends (directly).
+ Iterable<T> superinterfaces = interfaceTraverser.children(klass);
+
+ for (T superinterface : superinterfaces) {
+ if (!mGraph.isLibraryClass(superinterface)) {
+ // Add the arrow going "down", from the superinterface to this one.
+ mGraph.addDependency(
+ superinterface,
+ klass,
+ DependencyType.SUPERINTERFACE_KEPT);
+ } else {
+ // The superinterface is part of the SDK, so it's always kept. As
+ // long as there's any class that implements this interface, it
+ // needs to be kept.
+ mGraph.incrementAndCheck(
+ klass,
+ DependencyType.SUPERINTERFACE_KEPT,
+ CounterSet.SHRINK);
+ }
+ }
+ }
+
+ for (T iface : interfaceTraverser.preOrderTraversal(klass)) {
+ if (!mGraph.isLibraryClass(iface)) {
+ mGraph.addDependency(
+ klass,
+ iface,
+ DependencyType.INTERFACE_IMPLEMENTED);
+ }
+ }
+
+ return null;
+ }
+ });
+ }
+ }
+
+ @NonNull
+ private static FluentIterable<File> getClassFiles(@NonNull File dir) {
+ return getAllFiles(dir).filter(withExtension("class"));
+ }
+
+ /**
+ * Updates the graph to handle a case when a class inherits an interface method implementation
+ * from a super class which does not implement the given interface.
+ *
+ * <p>We handle it by inserting fake nodes into the graph, equivalent to just calling super()
+ * to invoke the inherited implementation. This way an "invokeinterface" opcode can cause the
+ * fake method to be kept, which in turn causes the real method to be kept, even though on the
+ * surface it has nothing to do with the interface.
+ */
+ private void handleMultipleInheritance(@NonNull Set<T> multipleInheritance) {
+ for (final T klass : multipleInheritance) {
+ mExecutor.execute(new Callable<Void>() {
+ Set<T> methods = mGraph.getMethods(klass);
+
+ @Override
+ public Void call() throws Exception {
+ if (!isProgramClass(mGraph.getSuperclass(klass))) {
+ // All the superclass methods are kept anyway.
+ return null;
+ }
+
+ Iterable<T> interfaces = TypeHierarchyTraverser
+ .interfaces(mGraph, mShrinkerLogger)
+ .preOrderTraversal(klass);
+ for (T iface : interfaces) {
+ for (T method : mGraph.getMethods(iface)) {
+ handleMethod(method);
+ }
+ }
+ return null;
+ }
+
+ private void handleMethod(T method) {
+ if (this.methods.contains(method)) {
+ // We implement this interface method directly in the class, which is the
+ // common case. Nothing left to do.
+ return;
+ }
+
+ // Otherwise, look in the superclasses for the implementation.
+
+ FluentIterable<T> superclasses = TypeHierarchyTraverser
+ .superclasses(mGraph, mShrinkerLogger)
+ .preOrderTraversal(klass);
+ for (T current : superclasses) {
+ if (!isProgramClass(current)) {
+ // We will not remove the method anyway.
+ return;
+ }
+
+ T matchingMethod = mGraph.findMatchingMethod(current, method);
+ if (matchingMethod != null) {
+ String name = mGraph.getMethodNameAndDesc(method);
+ String desc = name.substring(name.indexOf(':') + 1);
+ name = name.substring(0, name.indexOf(':'));
+ name = name + SHRINKER_FAKE_MARKER;
+ T fakeMethod = mGraph.addMember(
+ klass, name, desc,
+ mGraph.getMemberModifiers(method));
+
+ // Simulate a super call.
+ mGraph.addDependency(fakeMethod, matchingMethod,
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+
+ if (!isProgramClass(mGraph.getClassForMember(method))) {
+ mGraph.addDependency(klass, fakeMethod,
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ } else {
+ mGraph.addDependency(klass, fakeMethod,
+ DependencyType.CLASS_IS_KEPT);
+ mGraph.addDependency(method, fakeMethod,
+ DependencyType.IF_CLASS_KEPT);
+ }
+
+ return;
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Updates the graph to add edges which model how overridden methods should be handled.
+ *
+ * <p>A method overriding another one (from a class or interface), is kept if it's invoked
+ * directly (naturally) or if the class is kept for whatever reason and the overridden method
+ * is also invoked - we don't know if the call site for the overridden method actually operates
+ * on objects of the subclass.
+ */
+ private void handleOverrides(@NonNull Set<T> virtualMethods) {
+ for (final T method : virtualMethods) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ String methodNameAndDesc = mGraph.getMethodNameAndDesc(method);
+ if (isJavaLangObjectMethod(methodNameAndDesc)) {
+ // If we override an SDK method, it just has to be there at runtime
+ // (if the class itself is kept).
+ mGraph.addDependency(
+ mGraph.getClassForMember(method),
+ method,
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ return null;
+ }
+
+ FluentIterable<T> superTypes =
+ TypeHierarchyTraverser.superclassesAndInterfaces(mGraph, mShrinkerLogger)
+ .preOrderTraversal(mGraph.getClassForMember(method));
+
+ for (T klass : superTypes) {
+ if (mGraph.getClassName(klass).equals("java/lang/Object")) {
+ continue;
+ }
+
+ T superMethod = mGraph.findMatchingMethod(klass, method);
+ if (superMethod != null && !superMethod.equals(method)) {
+ if (!isProgramClass(mGraph.getClassForMember(superMethod))) {
+ // If we override an SDK method, it just has to be there at runtime
+ // (if the class itself is kept).
+ mGraph.addDependency(
+ mGraph.getClassForMember(method),
+ method,
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ return null;
+ } else {
+ // If we override a program method, there's a chance this method is
+ // never called and we will get rid of it. Set up the dependencies
+ // appropriately.
+ mGraph.addDependency(
+ mGraph.getClassForMember(method),
+ method,
+ DependencyType.CLASS_IS_KEPT);
+ mGraph.addDependency(
+ superMethod,
+ method,
+ DependencyType.IF_CLASS_KEPT);
+ }
+ }
+ }
+ return null;
+ }
+ });
+ }
+ }
+
+ private static boolean isJavaLangObjectMethod(@NonNull String nameAndDesc) {
+ return nameAndDesc.equals("hashCode:()I")
+ || (nameAndDesc.equals("equals:(Ljava/lang/Object;)Z")
+ || (nameAndDesc.equals("toString:()Ljava/lang/String;")));
+ }
+
+ /**
+ * Updates the graph with nodes from a library (read-only) class. There's no point creating
+ * edges, since library classes cannot references program classes and we don't shrink library
+ * code.
+ */
+ private void processLibraryClass(@NonNull byte[] source) throws IOException {
+ ClassReader classReader = new ClassReader(source);
+ classReader.accept(
+ new ClassStructureVisitor<T>(mGraph, null, null),
+ ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
+ }
+
+ /**
+ * Updates the graph with nodes and edges based on the given class file.
+ */
+ private void processProgramClassFile(
+ byte[] bytes,
+ @NonNull File classFile,
+ @NonNull final PostProcessingData<T> postProcessingData) throws IOException {
+ ClassNode classNode = new ClassNode(Opcodes.ASM5);
+ ClassVisitor depsFinder =
+ new DependencyFinderVisitor<T>(mGraph, classNode) {
+ @Override
+ protected void handleDependency(T source, T target, DependencyType type) {
+ mGraph.addDependency(source, target, type);
+ }
+
+ @Override
+ protected void handleMultipleInheritance(T klass) {
+ postProcessingData.getMultipleInheritance().add(klass);
+ }
+
+ @Override
+ protected void handleVirtualMethod(T method) {
+ postProcessingData.getVirtualMethods().add(method);
+ }
+
+ @Override
+ protected void handleInterfaceInheritance(T klass) {
+ postProcessingData.getInterfaceInheritance().add(klass);
+ }
+
+ @Override
+ protected void handleUnresolvedReference(PostProcessingData.UnresolvedReference<T> reference) {
+ postProcessingData.getUnresolvedReferences().add(reference);
+ }
+ };
+ ClassVisitor structureVisitor =
+ new ClassStructureVisitor<T>(mGraph, classFile, depsFinder);
+ ClassReader classReader = new ClassReader(bytes);
+ classReader.accept(structureVisitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ }
+
+ private interface ByteCodeConsumer {
+ void process(byte[] bytes) throws IOException;
+ }
+
+ private void readPlatformJars() throws IOException {
+ for (File platformJar : mPlatformJars) {
+ processJarFile(platformJar, new ByteCodeConsumer() {
+ @Override
+ public void process(byte[] bytes) throws IOException {
+ processLibraryClass(bytes);
+ }
+ });
+ }
+ }
+
+ private void processJarFile(File platformJar, final ByteCodeConsumer consumer)
+ throws IOException {
+ JarFile jarFile = new JarFile(platformJar);
+ try {
+ for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
+ JarEntry entry = entries.nextElement();
+ if (!entry.getName().endsWith(".class")) {
+ continue;
+ }
+ final InputStream inputStream = jarFile.getInputStream(entry);
+ try {
+ final byte[] bytes = ByteStreams.toByteArray(inputStream);
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ consumer.process(bytes);
+ return null;
+ }
+ });
+ } finally {
+ inputStream.close();
+ }
+ }
+ } finally {
+ jarFile.close();
+ }
+ }
+
+ /**
+ * Sets the roots (i.e. entry points) of the graph and marks all nodes reachable from them.
+ */
+ private void setCounters(@NonNull ImmutableMap<CounterSet, KeepRules> allKeepRules) {
+ final CounterSet counterSet = CounterSet.SHRINK;
+ final KeepRules keepRules = allKeepRules.get(counterSet);
+
+ for (final T klass : mGraph.getAllProgramClasses()) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ mGraph.addRoots(keepRules.getSymbolsToKeep(klass, mGraph), counterSet);
+ return null;
+ }
+ });
+ }
+ waitForAllTasks();
+
+ setCounters(counterSet);
+ }
+
+ private void writeOutput(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull TransformOutputProvider output) throws IOException {
+ updateClassFiles(
+ mGraph.getReachableClasses(CounterSet.SHRINK),
+ Collections.<File>emptyList(),
+ inputs,
+ output);
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/IncrementalRunVisitor.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/IncrementalRunVisitor.java
new file mode 100644
index 0000000..ef77463
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/IncrementalRunVisitor.java
@@ -0,0 +1,281 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Visitor for handling modified classes in an incremental run.
+ */
+class IncrementalRunVisitor<T> extends DependencyFinderVisitor<T> {
+
+ private final ShrinkerGraph<T> mGraph;
+
+ private final Collection<T> mClassesToWrite;
+
+ private final Collection<PostProcessingData.UnresolvedReference<T>> mUnresolvedReferences;
+
+ private String mClassName;
+
+ private Set<T> mMethods;
+
+ private Set<T> mFields;
+
+ private Set<String> mAnnotations;
+
+ public IncrementalRunVisitor(
+ @NonNull ShrinkerGraph<T> graph,
+ @NonNull Collection<T> classesToWrite,
+ @NonNull Collection<PostProcessingData.UnresolvedReference<T>> unresolvedReferences) {
+ super(graph, null);
+ mGraph = graph;
+ mClassesToWrite = classesToWrite;
+ mUnresolvedReferences = unresolvedReferences;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ T klass = mGraph.getClassReference(name);
+ mClassName = name;
+
+ checkSuperclass(klass, superName);
+ checkInterfaces(klass, interfaces);
+ checkModifiers(klass, access);
+
+ mMethods = mGraph.getMethods(klass);
+ mFields = mGraph.getFields(klass);
+ mAnnotations = Sets.newHashSet(mGraph.getAnnotations(klass));
+ mClassesToWrite.add(klass);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ private void checkModifiers(T klass, int modifiers) {
+ int oldModifiers = mGraph.getClassModifiers(klass);
+ if (oldModifiers != modifiers) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "%s modifiers changed.",
+ mClassName));
+ }
+ }
+
+ private void checkInterfaces(T klass, String[] interfaceNames) {
+ try {
+ Set<String> oldNames = ImmutableSet.copyOf(interfaceNames);
+ T[] interfaces = mGraph.getInterfaces(klass);
+ Set<String> newNames = Sets.newHashSet();
+ for (T iface : interfaces) {
+ newNames.add(mGraph.getClassName(iface));
+ }
+
+ if (!oldNames.equals(newNames)) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "%s interfaces changed.",
+ mClassName));
+ }
+ } catch (ClassLookupException e) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format("Can't find info for class %s.", mClassName));
+ }
+ }
+
+ private void checkSuperclass(T klass, String superName) {
+ try {
+ T superclass = mGraph.getSuperclass(klass);
+ Verify.verifyNotNull(superclass);
+ if (!mGraph.getClassName(superclass).equals(superName)) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "%s superclass changed.",
+ mClassName));
+ }
+ } catch (ClassLookupException e) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format("Can't find info for class %s.", mClassName));
+ }
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, final String name, String desc, String signature,
+ Object value) {
+ T field = mGraph.getMemberReference(mClassName, name, desc);
+ if (!mFields.remove(field)) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Field %s.%s:%s added.",
+ mClassName,
+ name,
+ desc));
+ }
+
+ if (mGraph.getMemberModifiers(field) != access) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Field %s.%s:%s modifiers changed.",
+ mClassName,
+ name,
+ desc));
+ }
+
+ final Set<String> memberAnnotations = Sets.newHashSet(mGraph.getAnnotations(field));
+ FieldVisitor superVisitor = super.visitField(access, name, desc, signature, value);
+ return new FieldVisitor(Opcodes.ASM5, superVisitor) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ checkForAddedAnnotation(desc, memberAnnotations, mClassName + "." + name);
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public void visitEnd() {
+ checkForRemovedAnnotation(memberAnnotations, mClassName + "." + name);
+ super.visitEnd();
+ }
+ };
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, final String name, String desc, String signature,
+ String[] exceptions) {
+ final T method = mGraph.getMemberReference(mClassName, name, desc);
+
+ if (!mMethods.remove(method)) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Method %s.%s:%s added.",
+ mClassName,
+ name,
+ desc));
+ }
+
+ if (mGraph.getMemberModifiers(method) != access) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Method %s.%s:%s modifiers changed.",
+ mClassName,
+ name,
+ desc));
+ }
+
+ final Set<String> memberAnnotations = Sets.newHashSet(mGraph.getAnnotations(method));
+ MethodVisitor superVisitor = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM5, superVisitor) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ checkForAddedAnnotation(desc, memberAnnotations, mClassName + "." + name);
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public void visitEnd() {
+ checkForRemovedAnnotation(memberAnnotations, mClassName + "." + name);
+ super.visitEnd();
+ }
+ };
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ checkForAddedAnnotation(desc, mAnnotations, mClassName);
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ protected void handleDependency(T source, T target, DependencyType type) {
+ if (type == DependencyType.REQUIRED_CODE_REFERENCE
+ || type == DependencyType.REQUIRED_CODE_REFERENCE_REFLECTION) {
+ mGraph.addDependency(source, target, type);
+ }
+ }
+
+ @Override
+ protected void handleMultipleInheritance(T klass) {}
+
+ @Override
+ protected void handleVirtualMethod(T method) {}
+
+ @Override
+ protected void handleInterfaceInheritance(T klass) {}
+
+ @Override
+ protected void handleUnresolvedReference(PostProcessingData.UnresolvedReference<T> reference) {
+ mUnresolvedReferences.add(reference);
+ }
+
+ @Override
+ public void visitEnd() {
+ T field = Iterables.getFirst(mFields, null);
+ if (field != null) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Field %s.%s:%s removed.",
+ mClassName,
+ mGraph.getFieldName(field),
+ mGraph.getFieldDesc(field)));
+ }
+
+ for (T method : mMethods) {
+ if (mGraph.getMemberName(method).contains(FullRunShrinker.SHRINKER_FAKE_MARKER)) {
+ continue;
+ }
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Method %s.%s removed.",
+ mClassName,
+ mGraph.getMethodNameAndDesc(method)));
+ }
+
+ checkForRemovedAnnotation(mAnnotations, mClassName);
+ }
+
+ private static void checkForAddedAnnotation(String desc, Set<String> annotations, String target) {
+ String name = Type.getType(desc).getInternalName();
+ if (!annotations.remove(name)) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Annotation %s on %s added.",
+ name,
+ target));
+ }
+ }
+
+ private static void checkForRemovedAnnotation(Set<String> annotations, String target) {
+ String annotation = Iterables.getFirst(annotations, null);
+ if (annotation != null) {
+ throw new IncrementalShrinker.IncrementalRunImpossibleException(
+ String.format(
+ "Annotation %s on %s removed.",
+ annotation,
+ target));
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/IncrementalShrinker.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/IncrementalShrinker.java
new file mode 100644
index 0000000..24541fe
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/IncrementalShrinker.java
@@ -0,0 +1,285 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.google.common.base.Optional;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.objectweb.asm.ClassReader;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Code for incremental shrinking.
+ */
+public class IncrementalShrinker<T> extends AbstractShrinker<T> {
+
+ /**
+ * Exception thrown when the incremental shrinker detects incompatible changes and requests
+ * a full run instead.
+ */
+ public static class IncrementalRunImpossibleException extends RuntimeException {
+ IncrementalRunImpossibleException(String message) {
+ super(message);
+ }
+
+ IncrementalRunImpossibleException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ public IncrementalShrinker(
+ WaitableExecutor<Void> executor,
+ ShrinkerGraph<T> graph,
+ ShrinkerLogger shrinkerLogger) {
+ super(graph, executor, shrinkerLogger);
+ }
+
+ /**
+ * Perform incremental shrinking, in the supported cases (where only code in pre-existing
+ * methods has been modified).
+ *
+ * <p>The general idea is this: for every method in modified classes, remove all outgoing
+ * "code reference" edges, add them again based on the current code and then set the counters
+ * again (traverse the graph) using the new set of edges.
+ *
+ * <p>The counters are re-calculated every time from scratch (starting from known entry points
+ * from the config file) to avoid cycles being left in the output.
+ *
+ * @throws IncrementalRunImpossibleException If incremental shrinking is impossible and a full
+ * run should be done instead.
+ */
+ public void incrementalRun(
+ @NonNull Iterable<TransformInput> inputs,
+ @NonNull TransformOutputProvider output)
+ throws IOException, IncrementalRunImpossibleException {
+ final Set<T> classesToWrite = Sets.newConcurrentHashSet();
+ final Set<File> classFilesToDelete = Sets.newConcurrentHashSet();
+ final Set<PostProcessingData.UnresolvedReference<T>> unresolvedReferences = Sets.newConcurrentHashSet();
+
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ SetMultimap<T, String> oldState = resetState();
+ logTime("resetState()", stopwatch);
+
+ processInputs(inputs, classesToWrite, unresolvedReferences);
+ logTime("processInputs", stopwatch);
+
+ finishGraph(unresolvedReferences);
+ logTime("finish graph", stopwatch);
+
+ setCounters(CounterSet.SHRINK);
+ logTime("set counters", stopwatch);
+
+ chooseClassesToWrite(inputs, output, classesToWrite, classFilesToDelete, oldState);
+ logTime("choose classes", stopwatch);
+
+ updateClassFiles(classesToWrite, classFilesToDelete, inputs, output);
+ logTime("update class files", stopwatch);
+
+ mGraph.saveState();
+ logTime("save state", stopwatch);
+ }
+
+ /**
+ * Decides which classes need to be updated on disk and which need to be deleted. It puts
+ * appropriate entries in the lists passed as arguments.
+ */
+ private void chooseClassesToWrite(
+ @NonNull Iterable<TransformInput> inputs,
+ @NonNull TransformOutputProvider output,
+ @NonNull Collection<T> classesToWrite,
+ @NonNull Collection<File> classFilesToDelete,
+ @NonNull SetMultimap<T, String> oldState) {
+ for (T klass : mGraph.getReachableClasses(CounterSet.SHRINK)) {
+ if (!oldState.containsKey(klass)) {
+ classesToWrite.add(klass);
+ } else {
+ Set<String> newMembers = mGraph.getReachableMembersLocalNames(klass, CounterSet.SHRINK);
+ Set<String> oldMembers = oldState.get(klass);
+
+ // Reverse of the trick above, where we store one artificial member for empty
+ // classes.
+ if (oldMembers.size() == 1) {
+ oldMembers.remove(mGraph.getClassName(klass));
+ }
+
+ if (!newMembers.equals(oldMembers)) {
+ classesToWrite.add(klass);
+ }
+ }
+
+ oldState.removeAll(klass);
+ }
+
+ // All keys that remained in oldState should be deleted.
+ for (T klass : oldState.keySet()) {
+ File sourceFile = mGraph.getSourceFile(klass);
+ checkState(sourceFile != null, "One of the inputs has no source file.");
+
+ Optional<File> outputFile = chooseOutputFile(klass, sourceFile, inputs, output);
+ if (!outputFile.isPresent()) {
+ throw new IllegalStateException(
+ "Can't determine path of " + mGraph.getClassName(klass));
+ }
+ classFilesToDelete.add(outputFile.get());
+ }
+ }
+
+ /**
+ * Saves all reachable classes and members in a {@link SetMultimap} and clears all counters, so
+ * that the graph can be traversed again, using the new edges.
+ *
+ * <p>Returns a multimap that contains names of all reachable members for every reachable class.
+ */
+ @NonNull
+ private SetMultimap<T, String> resetState() {
+ SetMultimap<T, String> oldState = HashMultimap.create();
+
+ for (T klass : mGraph.getReachableClasses(CounterSet.SHRINK)) {
+ Set<String> reachableMembers = mGraph.getReachableMembersLocalNames(klass, CounterSet.SHRINK);
+ for (String member : reachableMembers) {
+ oldState.put(klass, member);
+ }
+
+ // Make sure the key is in the map.
+ if (reachableMembers.isEmpty()) {
+ oldState.put(klass, mGraph.getClassName(klass));
+ }
+ }
+
+ mGraph.clearCounters(mExecutor);
+ waitForAllTasks();
+ return oldState;
+ }
+
+ private void finishGraph(@NonNull Iterable<PostProcessingData.UnresolvedReference<T>> unresolvedReferences) {
+ resolveReferences(unresolvedReferences);
+ waitForAllTasks();
+ }
+
+ private void processInputs(
+ @NonNull Iterable<TransformInput> inputs,
+ @NonNull final Collection<T> classesToWrite,
+ @NonNull final Collection<PostProcessingData.UnresolvedReference<T>> unresolvedReferences)
+ throws IncrementalRunImpossibleException {
+ for (final TransformInput input : inputs) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ switch (jarInput.getStatus()) {
+ case ADDED:
+ case REMOVED:
+ case CHANGED:
+ //noinspection StringToUpperCaseOrToLowerCaseWithoutLocale
+ throw new IncrementalRunImpossibleException(
+ String.format(
+ "Input jar %s has been %s.",
+ jarInput.getFile(),
+ jarInput.getStatus().name().toLowerCase()));
+ case NOTCHANGED:
+ break;
+ }
+ }
+
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ for (final Map.Entry<File, Status> changedFile : directoryInput.getChangedFiles().entrySet()) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ switch (changedFile.getValue()) {
+ case ADDED:
+ throw new IncrementalRunImpossibleException(
+ String.format(
+ "File %s added.", changedFile.getKey()));
+ case REMOVED:
+ throw new IncrementalRunImpossibleException(
+ String.format(
+ "File %s removed.", changedFile.getKey()));
+ case CHANGED:
+ processChangedClassFile(
+ changedFile.getKey(),
+ unresolvedReferences,
+ classesToWrite);
+ break;
+ }
+ return null;
+ }
+ });
+ }
+ }
+ }
+ waitForAllTasks();
+ }
+
+ /**
+ * Handles a changed class file by removing old code references (graph edges) and adding
+ * up-to-date edges, according to the current state of the class.
+ *
+ * <p>This only works on {@link DependencyType#REQUIRED_CODE_REFERENCE} edges, which are only
+ * ever created from method containing the opcode to target member. The first pass is equivalent
+ * to removing all code from the method, the second to adding "current" opcodes to it.
+ *
+ * @throws IncrementalRunImpossibleException If current members of the class are not the same as
+ * they used to be. This means that edges of other types need to be updated, and we don't
+ * handle this incrementally. It also means that -keep rules would need to be re-applied,
+ * which is something we also don't do incrementally.
+ */
+ private void processChangedClassFile(
+ @NonNull File file,
+ @NonNull final Collection<PostProcessingData.UnresolvedReference<T>> unresolvedReferences,
+ @NonNull final Collection<T> classesToWrite)
+ throws IOException, IncrementalRunImpossibleException {
+ ClassReader classReader = new ClassReader(Files.toByteArray(file));
+
+ IncrementalRunVisitor<T> visitor =
+ new IncrementalRunVisitor<T>(mGraph, classesToWrite, unresolvedReferences);
+
+ DependencyRemoverVisitor<T> remover = new DependencyRemoverVisitor<T>(mGraph, visitor);
+
+ classReader.accept(remover, 0);
+ }
+
+ @Override
+ protected void waitForAllTasks() {
+ try {
+ super.waitForAllTasks();
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof IncrementalRunImpossibleException) {
+ throw (IncrementalRunImpossibleException) e.getCause();
+ } else {
+ throw e;
+ }
+ }
+ }
+
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/JavaSerializationShrinkerGraph.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/JavaSerializationShrinkerGraph.java
new file mode 100644
index 0000000..39c8004
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/JavaSerializationShrinkerGraph.java
@@ -0,0 +1,624 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static com.android.build.gradle.shrinker.AbstractShrinker.isSdkPackage;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.shrinker.AbstractShrinker.CounterSet;
+import com.android.build.gradle.shrinker.IncrementalShrinker.IncrementalRunImpossibleException;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.utils.AsmUtils;
+import com.android.utils.FileUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InvalidClassException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Simple {@link ShrinkerGraph} implementation that uses strings, maps and Java serialization.
+ */
+public class JavaSerializationShrinkerGraph implements ShrinkerGraph<String> {
+ private final SetMultimap<String, String> mAnnotations;
+
+ private final ConcurrentMap<String, ClassInfo> mClasses;
+
+ private final SetMultimap<String, Dependency<String>> mDependencies;
+
+ private final SetMultimap<String, String> mMembers;
+
+ private final ConcurrentMap<String, Integer> mModifiers;
+
+ private final Counters mMultidexCounters;
+
+ private final Counters mShrinkCounters;
+
+ private final File mStateDir;
+
+
+ private JavaSerializationShrinkerGraph(File stateDir) {
+ mStateDir = checkNotNull(stateDir);
+ mShrinkCounters = new Counters(
+ Maps.<String, DependencyType>newConcurrentMap(),
+ ImmutableMap.<String, Counter>of());
+ mMultidexCounters = new Counters(
+ Maps.<String, DependencyType>newConcurrentMap(),
+ ImmutableMap.<String, Counter>of());
+ mMembers = Multimaps.synchronizedSetMultimap(HashMultimap.<String, String>create());
+ mAnnotations = Multimaps.synchronizedSetMultimap(HashMultimap.<String, String>create());
+ mClasses = Maps.newConcurrentMap();
+ mModifiers = Maps.newConcurrentMap();
+ mDependencies =
+ Multimaps.synchronizedSetMultimap(HashMultimap.<String, Dependency<String>>create());
+ }
+
+ private JavaSerializationShrinkerGraph(
+ File stateDir,
+ SetMultimap<String, String> annotations,
+ ConcurrentMap<String, ClassInfo> classes,
+ SetMultimap<String, Dependency<String>> dependencies,
+ SetMultimap<String, String> members,
+ ConcurrentMap<String, Integer> modifiers,
+ ConcurrentMap<String, DependencyType> multidexRoots,
+ Map<String, Counter> multidexCounters,
+ ConcurrentMap<String, DependencyType> shrinkRoots,
+ Map<String, Counter> shrinkCounters) {
+ mStateDir = stateDir;
+ mAnnotations = annotations;
+ mClasses = classes;
+ mDependencies = dependencies;
+ mMembers = members;
+ mModifiers = modifiers;
+ mMultidexCounters = new Counters(multidexRoots, multidexCounters);
+ mShrinkCounters = new Counters(shrinkRoots, shrinkCounters);
+ }
+
+ public static JavaSerializationShrinkerGraph empty(File stateDir) {
+ return new JavaSerializationShrinkerGraph(stateDir);
+ }
+
+ /**
+ * Constructs a graph by deserializing saved state.
+ *
+ * @param dir directory where the state was saved
+ * @param classLoader class loader used to resolve class names
+ * @throws IOException
+ */
+ @SuppressWarnings("unchecked") // readObject() returns an Object, we need to cast it.
+ public static JavaSerializationShrinkerGraph readFromDir(
+ @NonNull File dir,
+ @NonNull final ClassLoader classLoader) throws IOException {
+ File stateFile = getStateFile(dir);
+
+ // For some reason, when invoked from Gradle on a complex project, sometimes shrinker
+ // classes cannot be found. This seems to fix the problem.
+ ObjectInputStream stream =
+ new ObjectInputStream(new BufferedInputStream(new FileInputStream(stateFile))) {
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc)
+ throws IOException, ClassNotFoundException {
+ return Class.forName(desc.getName(), false, classLoader);
+ }
+ };
+ try {
+ return new JavaSerializationShrinkerGraph(
+ dir,
+ (SetMultimap) stream.readObject(),
+ (ConcurrentMap) stream.readObject(),
+ (SetMultimap) stream.readObject(),
+ (SetMultimap) stream.readObject(),
+ (ConcurrentMap) stream.readObject(),
+ (ConcurrentMap) stream.readObject(),
+ (Map) stream.readObject(),
+ (ConcurrentMap) stream.readObject(),
+ (Map) stream.readObject());
+ } catch (ClassNotFoundException e) {
+ throw new IncrementalRunImpossibleException("Failed to load incremental state.", e);
+ } catch (InvalidClassException e) {
+ throw new IncrementalRunImpossibleException("Failed to load incremental state.", e);
+ } finally {
+ stream.close();
+ }
+ }
+
+ @NonNull
+ @Override
+ public String addMember(@NonNull String owner, @NonNull String name, @NonNull String desc, int modifiers) {
+ String fullName = getFullMethodName(owner, name, desc);
+ mMembers.put(owner, fullName);
+ mModifiers.put(fullName, modifiers);
+ return fullName;
+ }
+
+ @NonNull
+ @Override
+ public String getMemberReference(@NonNull String className, @NonNull String memberName, @NonNull String desc) {
+ return getFullMethodName(className, memberName, desc);
+ }
+
+ @Override
+ public void addDependency(@NonNull String source, @NonNull String target, @NonNull DependencyType type) {
+ Dependency<String> dep = new Dependency<String>(target, type);
+ mDependencies.put(source, dep);
+ }
+
+ @NonNull
+ @Override
+ public Set<Dependency<String>> getDependencies(@NonNull String member) {
+ return Sets.newHashSet(mDependencies.get(member));
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getMethods(@NonNull String klass) {
+ HashSet<String> members = Sets.newHashSet(mMembers.get(klass));
+ for (Iterator<String> iterator = members.iterator(); iterator.hasNext(); ) {
+ String member = iterator.next();
+ if (!isMethod(member)) {
+ iterator.remove();
+ }
+ }
+ return members;
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getFields(@NonNull String klass) {
+ HashSet<String> members = Sets.newHashSet(mMembers.get(klass));
+ for (Iterator<String> iterator = members.iterator(); iterator.hasNext(); ) {
+ String member = iterator.next();
+ if (isMethod(member)) {
+ iterator.remove();
+ }
+ }
+ return members;
+ }
+
+ @Override
+ public boolean incrementAndCheck(@NonNull String memberOrClass, @NonNull DependencyType type, @NonNull CounterSet counterSet) {
+ try {
+ return getCounters(counterSet).mReferenceCounters.get(memberOrClass).incrementAndCheck(type);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void saveState() throws IOException {
+ File stateFile = getStateFile(mStateDir);
+ FileUtils.deleteIfExists(stateFile);
+ Files.createParentDirs(stateFile);
+
+ ObjectOutputStream stream =
+ new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(stateFile)));
+ try {
+ stream.writeObject(mAnnotations);
+ stream.writeObject(mClasses);
+ stream.writeObject(mDependencies);
+ stream.writeObject(mMembers);
+ stream.writeObject(mModifiers);
+ stream.writeObject(mMultidexCounters.mRoots);
+ stream.writeObject(ImmutableMap.copyOf(mMultidexCounters.mReferenceCounters.asMap()));
+ stream.writeObject(mShrinkCounters.mRoots);
+ stream.writeObject(ImmutableMap.copyOf(mShrinkCounters.mReferenceCounters.asMap()));
+ } finally {
+ stream.close();
+ }
+ }
+
+ @Override
+ public boolean isReachable(@NonNull String klass, @NonNull CounterSet counterSet) {
+ try {
+ return getCounters(counterSet).mReferenceCounters.get(klass).isReachable();
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void removeAllCodeDependencies(@NonNull String source) {
+ Set<Dependency<String>> dependencies = mDependencies.get(source);
+ for (Iterator<Dependency<String>> iterator = dependencies.iterator(); iterator.hasNext(); ) {
+ Dependency<String> dependency = iterator.next();
+ if (dependency.type == DependencyType.REQUIRED_CODE_REFERENCE
+ || dependency.type == DependencyType.REQUIRED_CODE_REFERENCE_REFLECTION) {
+ iterator.remove();
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public String getSuperclass(@NonNull String klass) throws ClassLookupException {
+ ClassInfo classInfo = mClasses.get(klass);
+ if (classInfo == null) {
+ throw new ClassLookupException(klass);
+ }
+ String superclass = classInfo.superclass;
+
+ if (superclass != null && !mClasses.containsKey(superclass)) {
+ throw new ClassLookupException(superclass);
+ }
+
+ return superclass;
+ }
+
+ @Nullable
+ @Override
+ public String findMatchingMethod(@NonNull String klass, @NonNull String method) {
+ // Common case:
+ if (mMembers.containsEntry(klass, method)) {
+ return method;
+ }
+
+ String methodToLookFor = klass + "." + getMemberId(method);
+ if (mMembers.containsEntry(klass, methodToLookFor)) {
+ return methodToLookFor;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isLibraryClass(@NonNull String klass) {
+ if (isSdkPackage(klass)) {
+ return true;
+ }
+
+ ClassInfo classInfo = mClasses.get(klass);
+ return classInfo == null || classInfo.isLibraryClass();
+ }
+
+ @NonNull
+ @Override
+ public String[] getInterfaces(String klass) throws ClassLookupException {
+ ClassInfo classInfo = mClasses.get(klass);
+ if (classInfo == null) {
+ throw new ClassLookupException(klass);
+ }
+
+ if (classInfo.interfaces == null) {
+ return new String[0];
+ } else {
+ return classInfo.interfaces;
+ }
+ }
+
+ @Override
+ public void checkDependencies(ShrinkerLogger shrinkerLogger) {
+ Map<String, Dependency<String>> invalidDeps = Maps.newHashMap();
+
+ for (Map.Entry<String, Dependency<String>> entry : mDependencies.entries()) {
+ String source = entry.getKey();
+ Dependency<String> dep = entry.getValue();
+ String target = dep.target;
+ if (!target.contains(".")) {
+ if (!mClasses.containsKey(target)) {
+ // We don't warn about by-name references in strings.
+ if (dep.type != DependencyType.REQUIRED_CODE_REFERENCE_REFLECTION) {
+ shrinkerLogger.invalidClassReference(source, target);
+ invalidDeps.put(source, entry.getValue());
+ }
+ }
+ } else {
+ if (!mMembers.containsEntry(getClassForMember(target), target)) {
+ shrinkerLogger.invalidMemberReference(source, target);
+ invalidDeps.put(source, entry.getValue());
+ }
+ }
+ }
+
+ for (Map.Entry<String, Dependency<String>> entry : invalidDeps.entrySet()) {
+ mDependencies.remove(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getReachableClasses(@NonNull CounterSet counterSet) {
+ Set<String> classesToKeep = Sets.newHashSet();
+ for (Map.Entry<String, ClassInfo> entry : mClasses.entrySet()) {
+ if (entry.getValue().isLibraryClass()) {
+ // Skip lib
+ continue;
+ }
+ if (isReachable(entry.getKey(), counterSet)) {
+ classesToKeep.add(entry.getKey());
+ }
+ }
+
+ return classesToKeep;
+ }
+
+ @Override
+ public File getSourceFile(@NonNull String klass) {
+ return mClasses.get(klass).classFile;
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getReachableMembersLocalNames(@NonNull String klass, @NonNull CounterSet counterSet) {
+ Set<String> memberIds = Sets.newHashSet();
+ for (String member : mMembers.get(klass)) {
+ if (isReachable(member, counterSet)) {
+ String memberId = getMemberId(member);
+ memberIds.add(memberId);
+ }
+ }
+
+ return memberIds;
+ }
+
+ @NonNull
+ @Override
+ public String getClassForMember(@NonNull String member) {
+ return AsmUtils.getClassName(member);
+ }
+
+ @NonNull
+ @Override
+ public String getClassReference(@NonNull String className) {
+ checkNotNull(className);
+ return className;
+ }
+
+ @NonNull
+ @Override
+ public String addClass(
+ @NonNull String name,
+ @Nullable String superName,
+ @Nullable String[] interfaces,
+ int modifiers,
+ @Nullable File classFile) {
+ //noinspection unchecked - ASM API
+ ClassInfo classInfo = new ClassInfo(classFile, superName, interfaces);
+ mClasses.put(name, classInfo);
+ mModifiers.put(name, modifiers);
+ return name;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<String> getAllProgramClasses() {
+ List<String> classes = Lists.newArrayList();
+ for (Map.Entry<String, ClassInfo> entry : mClasses.entrySet()) {
+ boolean isProgramClass = entry.getValue().classFile != null;
+ if (isProgramClass) {
+ classes.add(entry.getKey());
+ }
+ }
+
+ return classes;
+ }
+
+ @NonNull
+ @Override
+ public String getClassName(@NonNull String klass) {
+ return klass;
+ }
+
+ @NonNull
+ @Override
+ public String getMethodNameAndDesc(@NonNull String method) {
+ return method.substring(method.indexOf('.') + 1);
+ }
+
+ @NonNull
+ @Override
+ public String getFieldName(@NonNull String field) {
+ return field.substring(field.indexOf('.') + 1, field.indexOf(':'));
+ }
+
+ @NonNull
+ @Override
+ public String getFieldDesc(@NonNull String field) {
+ return field.substring(field.indexOf(':') + 1);
+ }
+
+ @Override
+ public int getClassModifiers(@NonNull String klass) {
+ return mModifiers.get(klass);
+ }
+
+ @Override
+ public int getMemberModifiers(@NonNull String member) {
+ return mModifiers.get(member);
+ }
+
+ @Override
+ public void addAnnotation(@NonNull String classOrMember, @NonNull String annotationName) {
+ Preconditions.checkArgument(!annotationName.endsWith(";"));
+ mAnnotations.put(classOrMember, annotationName);
+ }
+
+ @NonNull
+ @Override
+ public Iterable<String> getAnnotations(@NonNull String classOrMember) {
+ return mAnnotations.get(classOrMember);
+ }
+
+ @Override
+ public void addRoots(@NonNull Map<String, DependencyType> symbolsToKeep, @NonNull CounterSet counterSet) {
+ getCounters(counterSet).mRoots.putAll(symbolsToKeep);
+ }
+
+ @NonNull
+ @Override
+ public Map<String, DependencyType> getRoots(@NonNull CounterSet counterSet) {
+ return ImmutableMap.copyOf(getCounters(counterSet).mRoots);
+ }
+
+ @Override
+ public void clearCounters(@NonNull WaitableExecutor<Void> executor) {
+ getCounters(CounterSet.SHRINK).mReferenceCounters.invalidateAll();
+ getCounters(CounterSet.LEGACY_MULTIDEX).mReferenceCounters.invalidateAll();
+ }
+
+ @Override
+ public String getMemberName(@NonNull String member) {
+ return member;
+ }
+
+ @Override
+ public boolean isClassKnown(@NonNull String klass) {
+ return mClasses.containsKey(klass);
+ }
+
+ private Counters getCounters(CounterSet counterSet) {
+ if (counterSet == CounterSet.SHRINK) {
+ return mShrinkCounters;
+ } else {
+ return mMultidexCounters;
+ }
+ }
+
+ @NonNull
+ private static String getFullMethodName(String className, String methodName, String typeDesc) {
+ return className + "." + methodName + ":" + typeDesc;
+ }
+
+ @NonNull
+ private static String getMemberId(String member) {
+ return member.substring(member.indexOf('.') + 1);
+ }
+
+ @NonNull
+ private static File getStateFile(File dir) {
+ return new File(dir, "shrinker.bin");
+ }
+
+ private static boolean isMethod(String member) {
+ return member.contains("(");
+ }
+
+ private static final class ClassInfo implements Serializable {
+ @Nullable
+ final File classFile;
+
+ @Nullable
+ final String superclass;
+
+ @Nullable
+ final String[] interfaces;
+
+ private ClassInfo(
+ @Nullable File classFile,
+ @Nullable String superclass,
+ @Nullable String[] interfaces) {
+ this.classFile = classFile;
+ this.superclass = superclass;
+ this.interfaces = interfaces;
+ }
+
+ boolean isLibraryClass() {
+ return classFile == null;
+ }
+ }
+
+ private static final class Counters implements Serializable {
+
+ private final LoadingCache<String, Counter> mReferenceCounters;
+ private final ConcurrentMap<String, DependencyType> mRoots;
+
+ public Counters(
+ ConcurrentMap<String, DependencyType> roots,
+ Map<String, Counter> counters) {
+ mRoots = roots;
+
+ mReferenceCounters = CacheBuilder.newBuilder()
+ // TODO: set concurrency level?
+ .build(new CacheLoader<String, Counter>() {
+ @Override
+ public Counter load(@NonNull String unused) throws Exception {
+ return new Counter();
+ }
+ });
+
+ mReferenceCounters.putAll(counters);
+ }
+ }
+
+ private static final class Counter implements Serializable {
+ int required = 0;
+ int ifClassKept = 0;
+ int classIsKept = 0;
+ int superInterfaceKept = 0;
+ int interfaceImplemented = 0;
+
+ synchronized boolean incrementAndCheck(DependencyType type) {
+ boolean before = isReachable();
+ switch (type) {
+ case REQUIRED_CLASS_STRUCTURE:
+ case REQUIRED_CODE_REFERENCE:
+ case REQUIRED_CODE_REFERENCE_REFLECTION:
+ required++;
+ break;
+ case IF_CLASS_KEPT:
+ ifClassKept++;
+ break;
+ case CLASS_IS_KEPT:
+ classIsKept++;
+ break;
+ case SUPERINTERFACE_KEPT:
+ superInterfaceKept++;
+ break;
+ case INTERFACE_IMPLEMENTED:
+ interfaceImplemented++;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown dependency type.");
+ }
+ boolean after = isReachable();
+ return before != after;
+ }
+
+ synchronized boolean isReachable() {
+ return required > 0
+ || (ifClassKept > 0 && classIsKept > 0)
+ || (superInterfaceKept > 0 && interfaceImplemented > 0);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/KeepRules.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/KeepRules.java
new file mode 100644
index 0000000..1adf367
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/KeepRules.java
@@ -0,0 +1,32 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import java.util.Map;
+
+/**
+ * Represents the -keep* flags from a ProGuard config file.
+ */
+public interface KeepRules {
+
+ /**
+ * Given a program class and the whole {@link ShrinkerGraph}, decides which symbols should be
+ * kept in the output. The result can contain methods and fields from {@code klass} as well as
+ * {@code klass} itself (in case of -keep rules).
+ */
+ <T> Map<T, DependencyType> getSymbolsToKeep(T klass, ShrinkerGraph<T> graph);
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/PostProcessingData.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/PostProcessingData.java
new file mode 100644
index 0000000..2dcb86e
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/PostProcessingData.java
@@ -0,0 +1,94 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Objects;
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Data object that records all "tasks" that need to be done, once all the graph nodes are in place.
+ *
+ * <p>Many edges can only be added to the graph, once the whole class structure is known. That's why
+ * we record nodes that need "additional attention" for later, when reading input classes.
+ */
+class PostProcessingData<T> {
+ @NonNull
+ private final Set<T> virtualMethods = Sets.newConcurrentHashSet();
+ @NonNull
+ private final Set<T> multipleInheritance = Sets.newConcurrentHashSet();
+ @NonNull
+ private final Set<T> interfaceInheritance = Sets.newConcurrentHashSet();
+ @NonNull
+ private final Set<UnresolvedReference<T>> unresolvedReferences = Sets.newConcurrentHashSet();
+
+ @NonNull
+ Set<T> getVirtualMethods() {
+ return virtualMethods;
+ }
+
+ @NonNull
+ Set<T> getMultipleInheritance() {
+ return multipleInheritance;
+ }
+
+ @NonNull
+ Set<T> getInterfaceInheritance() {
+ return interfaceInheritance;
+ }
+
+ @NonNull
+ Set<UnresolvedReference<T>> getUnresolvedReferences() {
+ return unresolvedReferences;
+ }
+
+ static class UnresolvedReference<T> {
+ @NonNull final T method;
+ @NonNull final T target;
+ final boolean invokespecial;
+ @NonNull final DependencyType dependencyType;
+
+ UnresolvedReference(@NonNull T method, @NonNull T target, boolean invokespecial) {
+ this.method = method;
+ this.target = target;
+ this.invokespecial = invokespecial;
+ this.dependencyType = DependencyType.REQUIRED_CODE_REFERENCE;
+ }
+
+ public UnresolvedReference(
+ @NonNull T method,
+ @NonNull T target,
+ boolean invokespecial,
+ @NonNull DependencyType dependencyType) {
+ this.method = method;
+ this.target = target;
+ this.dependencyType = dependencyType;
+ this.invokespecial = invokespecial;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("method", method)
+ .add("target", target)
+ .add("invokespecial", invokespecial)
+ .toString();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ProguardConfig.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ProguardConfig.java
new file mode 100644
index 0000000..eb68b6e
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ProguardConfig.java
@@ -0,0 +1,55 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.shrinker.parser.Flags;
+import com.android.build.gradle.shrinker.parser.GrammarActions;
+
+import org.antlr.runtime.RecognitionException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Stub of a real parser. Only checks the most simple rules produced by AAPT.
+ */
+public class ProguardConfig {
+
+ private final Flags mFlags = new Flags();
+
+ public void parse(@NonNull File configFile) throws IOException {
+ try {
+ GrammarActions.parse(configFile, mFlags);
+ } catch (RecognitionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void parse(@NonNull String input) throws IOException {
+ try {
+ GrammarActions.parse(input, mFlags);
+ } catch (RecognitionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public Flags getFlags() {
+ return mFlags;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ProguardFlagsKeepRules.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ProguardFlagsKeepRules.java
new file mode 100644
index 0000000..e992e67
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ProguardFlagsKeepRules.java
@@ -0,0 +1,222 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.shrinker.parser.MethodSpecification;
+import com.android.build.gradle.shrinker.parser.AnnotationSpecification;
+import com.android.build.gradle.shrinker.parser.ClassSpecification;
+import com.android.build.gradle.shrinker.parser.FieldSpecification;
+import com.android.build.gradle.shrinker.parser.Flags;
+import com.android.build.gradle.shrinker.parser.InheritanceSpecification;
+import com.android.build.gradle.shrinker.parser.Matcher;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of {@link KeepRules} that uses {@link Flags} obtained from parsing a ProGuard
+ * config file.
+ */
+public class ProguardFlagsKeepRules implements KeepRules {
+
+ private final Flags mFlags;
+ private final ShrinkerLogger mShrinkerLogger;
+
+ public ProguardFlagsKeepRules(Flags flags, ShrinkerLogger shrinkerLogger) {
+ mFlags = flags;
+ mShrinkerLogger = shrinkerLogger;
+ }
+
+ @Override
+ public <T> Map<T, DependencyType> getSymbolsToKeep(T klass, ShrinkerGraph<T> graph) {
+ Map<T, DependencyType> result = Maps.newHashMap();
+
+ for (ClassSpecification spec : mFlags.getKeepClassSpecs()) {
+ if (matchesClass(klass, spec, graph)) {
+ result.put(klass, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ result.put(
+ graph.getMemberReference(graph.getClassName(klass), "<init>", "()V"),
+ DependencyType.REQUIRED_CLASS_STRUCTURE);
+ for (T member : findMatchingMembers(klass, spec, graph)) {
+ result.put(member, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+ }
+ }
+
+ for (ClassSpecification spec : mFlags.getKeepClassMembersSpecs()) {
+ if (matchesClass(klass, spec, graph)) {
+ for (T member : findMatchingMembers(klass, spec, graph)) {
+ result.put(member, DependencyType.IF_CLASS_KEPT);
+ graph.addDependency(klass, member, DependencyType.CLASS_IS_KEPT);
+ }
+ }
+ }
+
+ for (ClassSpecification spec : mFlags.getKeepClassesWithMembersSpecs()) {
+ if (matchesClass(klass, spec, graph)) {
+ for (T t : handleKeepClassesWithMembers(spec, klass, graph)) {
+ result.put(t, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private static <T> List<T> handleKeepClassesWithMembers(
+ ClassSpecification classSpec,
+ T klass,
+ ShrinkerGraph<T> graph) {
+ List<T> result = Lists.newArrayList();
+
+ for (MethodSpecification methodSpec : classSpec.getMethodSpecifications()) {
+ boolean found = false;
+ for (T method : graph.getMethods(klass)) {
+ if (matchesMethod(method, methodSpec, graph)) {
+ found = true;
+ result.add(method);
+ }
+ }
+
+ if (!found) {
+ return Collections.emptyList();
+ }
+ }
+
+ for (FieldSpecification fieldSpec : classSpec.getFieldSpecifications()) {
+ boolean found = false;
+ for (T method : graph.getMethods(klass)) {
+ if (matchesField(method, fieldSpec, graph)) {
+ found = true;
+ result.add(method);
+ }
+ }
+
+ if (!found) {
+ return Collections.emptyList();
+ }
+ }
+
+ // If we're here, then all member specs have matched something.
+ result.add(klass);
+ return result;
+ }
+
+ private static <T> List<T> findMatchingMembers(
+ T klass,
+ ClassSpecification spec,
+ ShrinkerGraph<T> graph) {
+ List<T> result = Lists.newArrayList();
+ for (T method : graph.getMethods(klass)) {
+ for (MethodSpecification methodSpec : spec.getMethodSpecifications()) {
+ if (matchesMethod(method, methodSpec, graph)) {
+ result.add(method);
+ }
+ }
+ }
+
+ for (T field : graph.getFields(klass)) {
+ for (FieldSpecification fieldSpecification : spec.getFieldSpecifications()) {
+ if (matchesField(field, fieldSpecification, graph)) {
+ result.add(field);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private static <T> boolean matchesField(
+ T field,
+ FieldSpecification spec,
+ ShrinkerGraph<T> graph) {
+ return matches(spec.getName(), graph.getFieldName(field))
+ && matches(spec.getModifier(), graph.getMemberModifiers(field))
+ && matches(spec.getTypeSignature(), graph.getFieldDesc(field))
+ && matchesAnnotations(field, spec.getAnnotations(), graph);
+ }
+
+ private static <T> boolean matchesMethod(
+ T method,
+ MethodSpecification spec,
+ ShrinkerGraph<T> graph) {
+ return matches(spec.getName(), graph.getMethodNameAndDesc(method))
+ && matches(spec.getModifiers(), graph.getMemberModifiers(method))
+ && matchesAnnotations(method, spec.getAnnotations(), graph);
+ }
+
+ private <T> boolean matchesClass(
+ T klass,
+ ClassSpecification spec,
+ ShrinkerGraph<T> graph) {
+ int classModifiers = graph.getClassModifiers(klass);
+ return matches(spec.getName(), graph.getClassName(klass))
+ && matches(spec.getClassType(), classModifiers)
+ && matches(spec.getModifier(), classModifiers)
+ && matchesAnnotations(klass, spec.getAnnotation(), graph)
+ && matchesInheritance(klass, spec.getInheritance(), graph);
+ }
+
+ private static <U> boolean matches(@Nullable Matcher<U> matcher, @NonNull U value) {
+ return matcher == null || matcher.matches(value);
+ }
+
+ private static <T> boolean matchesAnnotations(
+ @NonNull T classOrMember,
+ @Nullable AnnotationSpecification annotation,
+ @NonNull ShrinkerGraph<T> graph) {
+ if (annotation == null) {
+ return true;
+ }
+
+ for (String annotationName : graph.getAnnotations(classOrMember)) {
+ if (annotation.getName().matches(annotationName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private <T> boolean matchesInheritance(
+ @NonNull T klass,
+ @Nullable InheritanceSpecification spec,
+ @NonNull ShrinkerGraph<T> graph) {
+ if (spec == null) {
+ return true;
+ }
+
+ FluentIterable<T> superTypes =
+ TypeHierarchyTraverser.superclassesAndInterfaces(graph, mShrinkerLogger)
+ .preOrderTraversal(klass)
+ .skip(1); // Skip the class itself.
+
+ for (T superType : superTypes) {
+ String name = graph.getClassName(superType);
+ if (spec.getNameSpec().matches(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ShrinkerGraph.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ShrinkerGraph.java
new file mode 100644
index 0000000..faee0ec
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ShrinkerGraph.java
@@ -0,0 +1,143 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.shrinker.AbstractShrinker.CounterSet;
+import com.android.ide.common.internal.WaitableExecutor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * State the {@link FullRunShrinker} needs to keep during and between invocations.
+ *
+ * @param <T> Reference to a class member.
+ */
+public interface ShrinkerGraph<T> {
+
+ /**
+ * Returns the source file that this class was read from. Return null for library classes.
+ */
+ @Nullable
+ File getSourceFile(@NonNull T klass);
+
+ @NonNull
+ Set<T> getReachableClasses(@NonNull CounterSet counterSet);
+
+ /**
+ * Returns all the reachable members of the given class, in the name:desc format, without the
+ * class name at the front.
+ */
+ @NonNull
+ Set<String> getReachableMembersLocalNames(@NonNull T klass, @NonNull CounterSet counterSet);
+
+ @NonNull
+ Set<Dependency<T>> getDependencies(@NonNull T member);
+
+ @NonNull
+ Set<T> getMethods(@NonNull T klass);
+
+ @NonNull
+ Set<T> getFields(@NonNull T klass);
+
+ @NonNull
+ T addClass(
+ @NonNull String name,
+ @Nullable String superName,
+ @Nullable String[] interfaces,
+ int modifiers,
+ @Nullable File classFile);
+
+ @NonNull
+ T addMember(@NonNull T owner, @NonNull String name, @NonNull String desc, int modifiers);
+
+ @NonNull
+ T getClassForMember(@NonNull T member);
+
+ @NonNull
+ T getClassReference(@NonNull String className);
+
+ @NonNull
+ T getMemberReference(@NonNull String className, @NonNull String memberName, @NonNull String desc);
+
+ boolean incrementAndCheck(
+ @NonNull T memberOrClass,
+ @NonNull DependencyType dependencyType,
+ @NonNull CounterSet counterSet);
+
+ void addDependency(@NonNull T source, @NonNull T target, @NonNull DependencyType type);
+
+ void saveState() throws IOException;
+
+ boolean isReachable(@NonNull T klass, @NonNull CounterSet counterSet);
+
+ void removeAllCodeDependencies(@NonNull T source);
+
+ @Nullable
+ T getSuperclass(@NonNull T klass) throws ClassLookupException;
+
+ @Nullable
+ T findMatchingMethod(@NonNull T klass, @NonNull T method);
+
+ boolean isLibraryClass(@NonNull T klass);
+
+ @NonNull
+ T[] getInterfaces(T klass) throws ClassLookupException;
+
+ void checkDependencies(ShrinkerLogger shrinkerLogger);
+
+ @NonNull
+ Iterable<T> getAllProgramClasses();
+
+ @NonNull
+ String getClassName(@NonNull T klass);
+
+ @NonNull
+ String getMethodNameAndDesc(@NonNull T method);
+
+ @NonNull
+ String getFieldName(@NonNull T field);
+
+ @NonNull
+ String getFieldDesc(@NonNull T field);
+
+ int getClassModifiers(@NonNull T klass);
+
+ int getMemberModifiers(@NonNull T member);
+
+ void addAnnotation(@NonNull T classOrMember, @NonNull String annotationName);
+
+ @NonNull
+ Iterable<String> getAnnotations(@NonNull T classOrMember);
+
+ void addRoots(
+ @NonNull Map<T, DependencyType> symbolsToKeep,
+ @NonNull CounterSet counterSet);
+
+ @NonNull
+ Map<T,DependencyType> getRoots(@NonNull CounterSet counterSet);
+
+ void clearCounters(@NonNull WaitableExecutor<Void> executor);
+
+ String getMemberName(@NonNull T member);
+
+ boolean isClassKnown(@NonNull T klass);
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ShrinkerLogger.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ShrinkerLogger.java
new file mode 100644
index 0000000..35830d5
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/ShrinkerLogger.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.shrinker;
+
+import com.android.build.gradle.shrinker.parser.FilterSpecification;
+import com.android.utils.AsmUtils;
+import com.android.utils.Pair;
+import com.google.common.collect.Sets;
+
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Shrinker-specific logger that can be configured with -dontwarn flag.
+ */
+public class ShrinkerLogger {
+ private final List<FilterSpecification> mDontWarnSpecs;
+ private final Logger mLogger;
+ private final Set<Pair<String, String>> mWarningsEmitted;
+
+ public ShrinkerLogger(List<FilterSpecification> dontWarnSpecs, Logger logger) {
+ mDontWarnSpecs = dontWarnSpecs;
+ mLogger = logger;
+ mWarningsEmitted = Sets.newHashSet();
+ }
+
+ synchronized void invalidClassReference(String from, String to) {
+ if (from.contains(".")) {
+ from = AsmUtils.getClassName(from);
+ }
+
+ if (mWarningsEmitted.contains(Pair.of(from, to))) {
+ return;
+ }
+
+ for (FilterSpecification dontWarnSpec : mDontWarnSpecs) {
+ if (dontWarnSpec.matches(from) || dontWarnSpec.matches(to)) {
+ return;
+ }
+ }
+
+ mWarningsEmitted.add(Pair.of(from, to));
+ mLogger.warn("{} references unknown class: {}", from, to);
+ }
+
+ synchronized void invalidMemberReference(String from, String to) {
+ if (mWarningsEmitted.contains(Pair.of(from, to))) {
+ return;
+ }
+
+ String fromClassName;
+ if (from.contains(".")) {
+ fromClassName = AsmUtils.getClassName(from);
+ } else {
+ fromClassName = from;
+ }
+ String toClassName = AsmUtils.getClassName(to);
+ for (FilterSpecification dontWarnSpec : mDontWarnSpecs) {
+ if (dontWarnSpec.matches(fromClassName) || dontWarnSpec.matches(toClassName)) {
+ return;
+ }
+ }
+
+ mWarningsEmitted.add(Pair.of(from, to));
+ mLogger.warn("{} references unknown class member: {}", from, to);
+ }
+
+ public int getWarningsCount() {
+ return mWarningsEmitted.size();
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/TypeHierarchyTraverser.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/TypeHierarchyTraverser.java
new file mode 100644
index 0000000..bff530a
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/TypeHierarchyTraverser.java
@@ -0,0 +1,102 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+import com.google.common.collect.TreeTraverser;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link TreeTraverser} that finds all supertypes (both superclasses and interfaces) of types.
+ */
+public class TypeHierarchyTraverser<T> extends TreeTraverser<T> {
+
+ private final ShrinkerGraph<T> mGraph;
+
+ private final ShrinkerLogger mShrinkerLogger;
+
+ private final boolean mIncludeInterfaces;
+
+ private final boolean mIncludeSuperclasses;
+
+ private TypeHierarchyTraverser(
+ ShrinkerGraph<T> graph,
+ ShrinkerLogger shrinkerLogger,
+ boolean includeSuperclasses,
+ boolean includeInterfaces) {
+ mGraph = graph;
+ mShrinkerLogger = shrinkerLogger;
+ mIncludeSuperclasses = includeSuperclasses;
+ mIncludeInterfaces = includeInterfaces;
+ }
+
+ public static <T> TypeHierarchyTraverser<T> superclassesAndInterfaces(ShrinkerGraph<T> graph,
+ ShrinkerLogger shrinkerLogger) {
+ return new TypeHierarchyTraverser<T>(graph, shrinkerLogger, true, true);
+ }
+
+ public static <T> TypeHierarchyTraverser<T> superclasses(ShrinkerGraph<T> graph,
+ ShrinkerLogger shrinkerLogger) {
+ return new TypeHierarchyTraverser<T>(graph, shrinkerLogger, true, false);
+ }
+
+ public static <T> TypeHierarchyTraverser<T> interfaces(ShrinkerGraph<T> graph,
+ ShrinkerLogger shrinkerLogger) {
+ return new TypeHierarchyTraverser<T>(graph, shrinkerLogger, false, true);
+ }
+
+ @Override
+ public Iterable<T> children(@NonNull T klass) {
+ List<T> result = Lists.newArrayList();
+ if (mIncludeSuperclasses) {
+ try {
+ T superclass = mGraph.getSuperclass(klass);
+ if (superclass != null) {
+ if (!mGraph.isClassKnown(superclass)) {
+ throw new ClassLookupException(mGraph.getClassName(superclass));
+ }
+ result.add(superclass);
+ }
+ } catch (ClassLookupException e) {
+ mShrinkerLogger.invalidClassReference(mGraph.getClassName(klass), e.getClassName());
+ return Collections.emptyList();
+ }
+ }
+
+ if (mIncludeInterfaces) {
+ try {
+ T[] interfaces = mGraph.getInterfaces(klass);
+ for (T iface : interfaces) {
+ if (!mGraph.isClassKnown(iface)) {
+ mShrinkerLogger.invalidClassReference(
+ mGraph.getClassName(klass),
+ mGraph.getClassName(iface));
+ } else {
+ result.add(iface);
+ }
+ }
+ } catch (ClassLookupException e) {
+ mShrinkerLogger.invalidClassReference(mGraph.getClassName(klass), e.getClassName());
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/AnnotationSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/AnnotationSpecification.java
new file mode 100644
index 0000000..7d5c5c0
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/AnnotationSpecification.java
@@ -0,0 +1,33 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+/**
+ * Represents the annotations part of a ProGuard. class specification.
+ */
+public class AnnotationSpecification {
+
+ private final NameSpecification mName;
+
+ public AnnotationSpecification(NameSpecification name) {
+ mName = name;
+ }
+
+ public NameSpecification getName() {
+ return mName;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ClassSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ClassSpecification.java
new file mode 100644
index 0000000..4c8e1f5
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ClassSpecification.java
@@ -0,0 +1,106 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Represents a ProGuard class specification.
+ */
+public class ClassSpecification {
+
+ @NonNull private final NameSpecification mNameSpec;
+ @NonNull private final ClassTypeSpecification mClassType;
+ @Nullable private final AnnotationSpecification mAnnotation;
+ @Nullable private KeepModifier mKeepModifier;
+ @Nullable private ModifierSpecification mModifier;
+ @NonNull private List<FieldSpecification> mFieldSpecifications = Lists.newArrayList();
+ @NonNull private List<MethodSpecification> mMethodSpecifications = Lists.newArrayList();
+ @Nullable private InheritanceSpecification mInheritanceSpecification;
+
+ public ClassSpecification(
+ @NonNull NameSpecification nameSpec,
+ @NonNull ClassTypeSpecification classType,
+ @Nullable AnnotationSpecification annotation) {
+ mNameSpec = nameSpec;
+ mClassType = classType;
+ mAnnotation = annotation;
+ }
+
+ public void setKeepModifier(@Nullable KeepModifier keepModifier) {
+ mKeepModifier = keepModifier;
+ }
+
+ @Nullable
+ public KeepModifier getKeepModifier() {
+ return mKeepModifier;
+ }
+
+ public void setModifier(@Nullable ModifierSpecification modifier) {
+ mModifier = modifier;
+ }
+
+ @Nullable
+ public ModifierSpecification getModifier() {
+ return mModifier;
+ }
+
+ public void add(FieldSpecification fieldSpecification) {
+ mFieldSpecifications.add(fieldSpecification);
+ }
+
+ public void add(MethodSpecification methodSpecification) {
+ mMethodSpecifications.add(methodSpecification);
+ }
+
+ @NonNull
+ public List<MethodSpecification> getMethodSpecifications() {
+ return mMethodSpecifications;
+ }
+
+ public NameSpecification getName() {
+ return mNameSpec;
+ }
+
+ @NonNull
+ public ClassTypeSpecification getClassType() {
+ return mClassType;
+ }
+
+ @Nullable
+ public AnnotationSpecification getAnnotation() {
+ return mAnnotation;
+ }
+
+ @NonNull
+ public List<FieldSpecification> getFieldSpecifications() {
+ return mFieldSpecifications;
+ }
+
+ public void setInheritance(@Nullable InheritanceSpecification inheritanceSpecification) {
+ mInheritanceSpecification = inheritanceSpecification;
+ }
+
+ @Nullable
+ public InheritanceSpecification getInheritance() {
+ return mInheritanceSpecification;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ClassTypeSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ClassTypeSpecification.java
new file mode 100644
index 0000000..d78b2f7
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ClassTypeSpecification.java
@@ -0,0 +1,55 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.objectweb.asm.Opcodes.ACC_ANNOTATION;
+import static org.objectweb.asm.Opcodes.ACC_ENUM;
+import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
+
+/**
+ * Represents the "class type" part of a ProGuard class specification.
+ */
+public class ClassTypeSpecification extends MatcherWithNegator<Integer> {
+
+ private static final int CLASS_TYPE_FLAGS = ACC_INTERFACE | ACC_ENUM;
+
+ private final int mSpec;
+
+ public ClassTypeSpecification(int spec) {
+ checkState((spec & (CLASS_TYPE_FLAGS | ACC_ANNOTATION)) == spec);
+ mSpec = spec;
+ }
+
+ @Override
+ protected boolean matchesWithoutNegator(Integer toCheck) {
+ int modifiers = toCheck;
+
+ //noinspection SimplifiableIfStatement
+ if (((mSpec & ACC_ANNOTATION) != 0) && ((modifiers & ACC_ANNOTATION) == 0)) {
+ // Only look at the annotation bit if the keep rule mentioned annotations.
+ return false;
+ }
+
+ if ((mSpec & CLASS_TYPE_FLAGS) == 0) {
+ // "The class keyword refers to any interface or class."
+ return true;
+ }
+
+ return (modifiers & CLASS_TYPE_FLAGS) == (mSpec & CLASS_TYPE_FLAGS);
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/FieldSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/FieldSpecification.java
new file mode 100644
index 0000000..cacae49
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/FieldSpecification.java
@@ -0,0 +1,62 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * Represents field part of a ProGuard class specification.
+ */
+public class FieldSpecification {
+
+ @NonNull private final NameSpecification mName;
+ @NonNull private final ModifierSpecification mModifier;
+ @Nullable private final NameSpecification mTypeSignature;
+ @Nullable private final AnnotationSpecification mAnnotationType;
+
+ public FieldSpecification(
+ @NonNull NameSpecification name,
+ @NonNull ModifierSpecification modifier,
+ @Nullable NameSpecification typeSignature,
+ @Nullable AnnotationSpecification annotationType) {
+ mName = name;
+ mModifier = modifier;
+ mTypeSignature = typeSignature;
+ mAnnotationType = annotationType;
+ }
+
+ @NonNull
+ public NameSpecification getName() {
+ return mName;
+ }
+
+ @NonNull
+ public ModifierSpecification getModifier() {
+ return mModifier;
+ }
+
+ @Nullable
+ public NameSpecification getTypeSignature() {
+ return mTypeSignature;
+ }
+
+ @Nullable
+ public AnnotationSpecification getAnnotations() {
+ return mAnnotationType;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/FilterSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/FilterSpecification.java
new file mode 100644
index 0000000..338e1e4
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/FilterSpecification.java
@@ -0,0 +1,60 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import com.android.annotations.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a ProGuard filter specification.
+ */
+public class FilterSpecification implements Matcher<String> {
+
+ private static class FilterElement extends MatcherWithNegator<String> {
+ @NonNull
+ private final NameSpecification name;
+
+ public FilterElement(@NonNull NameSpecification name, boolean negator) {
+ this.name = name;
+ setNegator(negator);
+ }
+
+ @Override
+ protected boolean matchesWithoutNegator(@NonNull String t) {
+ return name.matches(t);
+ }
+ }
+
+ @NonNull
+ private final List<FilterElement> elements = new ArrayList<FilterElement>();
+
+ public void addElement(@NonNull NameSpecification name, boolean negator) {
+ elements.add(new FilterElement(name, negator));
+ }
+
+ @Override
+ public boolean matches(@NonNull String t) {
+ for (FilterElement element : elements) {
+ if (element.matches(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/Flags.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/Flags.java
new file mode 100644
index 0000000..56eebab
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/Flags.java
@@ -0,0 +1,90 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Class representing a ProGuard config file.
+ *
+ * <p>Mostly copied from Jack.
+ */
+public class Flags {
+
+ @NonNull
+ private final List<ClassSpecification> keepClassSpecs = Lists.newArrayList();
+
+ @NonNull
+ private final List<ClassSpecification> keepClassesWithMembersSpecs = Lists.newArrayList();
+
+ @NonNull
+ private final List<ClassSpecification> keepClassMembersSpecs = Lists.newArrayList();
+
+ @NonNull
+ private final List<FilterSpecification> dontWarnSpecs = Lists.newArrayList();
+
+ private boolean mIgnoreWarnings;
+
+ @NonNull
+ public List<ClassSpecification> getKeepClassSpecs() {
+ return keepClassSpecs;
+ }
+
+ @NonNull
+ public List<ClassSpecification> getKeepClassesWithMembersSpecs() {
+ return keepClassesWithMembersSpecs;
+ }
+
+ @NonNull
+ public List<ClassSpecification> getKeepClassMembersSpecs() {
+ return keepClassMembersSpecs;
+ }
+
+ public void addKeepClassSpecification(@NonNull ClassSpecification classSpecification) {
+ keepClassSpecs.add(classSpecification);
+ }
+
+ public void addKeepClassesWithMembers(@NonNull ClassSpecification classSpecification) {
+ keepClassesWithMembersSpecs.add(classSpecification);
+ }
+
+ public void addKeepClassMembers(@NonNull ClassSpecification classSpecification) {
+ keepClassMembersSpecs.add(classSpecification);
+ }
+
+ public void dontWarn(@NonNull FilterSpecification classSpec) {
+ dontWarnSpecs.add(classSpec);
+ }
+
+ @NonNull
+ public List<FilterSpecification> getDontWarnSpecs() {
+ return dontWarnSpecs;
+ }
+
+ public void setIgnoreWarnings(boolean ignoreWarnings) {
+ mIgnoreWarnings = ignoreWarnings;
+ }
+
+ public boolean isIgnoreWarnings() {
+ return mIgnoreWarnings;
+ }
+}
+
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/GrammarActions.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/GrammarActions.java
new file mode 100644
index 0000000..02c9605
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/GrammarActions.java
@@ -0,0 +1,351 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.antlr.runtime.ANTLRFileStream;
+import org.antlr.runtime.ANTLRStringStream;
+import org.antlr.runtime.CharStream;
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.RecognitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+/**
+ * Grammar actions for the ProGuard config files parser, forked from Jack.
+ */
+ at SuppressWarnings("unused") // These methods are called by the ANTLR-generated parser.
+public class GrammarActions {
+
+ private static Logger logger = LoggerFactory.getLogger(GrammarActions.class);
+
+ public static void parse(
+ @NonNull File proguardFile,
+ @NonNull Flags flags) throws RecognitionException {
+ ProguardParser parser = createParserFromFile(proguardFile);
+ parser.prog(flags, proguardFile.getParentFile().getPath());
+ }
+
+ public static void parse(
+ @NonNull String input,
+ @NonNull Flags flags) throws RecognitionException {
+ ProguardParser parser = createParserFromString(input);
+ parser.prog(flags, null);
+ }
+
+ static void include(
+ @NonNull String fileName,
+ @NonNull String baseDirectory,
+ @NonNull Flags flags) throws RecognitionException {
+ parse(getFileFromBaseDir(baseDirectory, fileName), flags);
+ }
+
+ static void dontWarn(@NonNull Flags flags, @NonNull FilterSpecification classSpec) {
+ flags.dontWarn(classSpec);
+ }
+
+ static void ignoreWarnings(@NonNull Flags flags) {
+ flags.setIgnoreWarnings(true);
+ }
+
+ static void addKeepClassMembers(
+ @NonNull Flags flags,
+ @NonNull ClassSpecification classSpecification,
+ @Nullable KeepModifier keepModifier) {
+ if (keepModifier == null) {
+ keepModifier = KeepModifier.NONE;
+ }
+ classSpecification.setKeepModifier(keepModifier);
+ flags.addKeepClassMembers(classSpecification);
+ }
+
+ static void addKeepClassSpecification(
+ @NonNull Flags flags,
+ @NonNull ClassSpecification classSpecification,
+ @Nullable KeepModifier keepModifier) {
+ if (keepModifier == null) {
+ keepModifier = KeepModifier.NONE;
+ }
+ classSpecification.setKeepModifier(keepModifier);
+ flags.addKeepClassSpecification(classSpecification);
+ }
+
+ static void addKeepClassesWithMembers(
+ @NonNull Flags flags,
+ @NonNull ClassSpecification classSpecification,
+ @Nullable KeepModifier keepModifier) {
+ if (keepModifier == null) {
+ keepModifier = KeepModifier.NONE;
+ }
+ classSpecification.setKeepModifier(keepModifier);
+ flags.addKeepClassesWithMembers(classSpecification);
+ }
+
+ static void addModifier(
+ @NonNull ModifierSpecification modSpec,
+ int modifier,
+ boolean hasNegator) {
+ modSpec.addModifier(modifier, hasNegator);
+ }
+
+ @NonNull
+ static AnnotationSpecification annotation(
+ @NonNull String annotationName,
+ boolean hasNameNegator) {
+ NameSpecification name = name(annotationName);
+ name.setNegator(hasNameNegator);
+ return new AnnotationSpecification(name);
+ }
+
+ @NonNull
+ static ClassSpecification classSpec(
+ @NonNull String name,
+ boolean hasNameNegator,
+ @NonNull ClassTypeSpecification classType,
+ @Nullable AnnotationSpecification annotation,
+ @NonNull ModifierSpecification modifier) {
+ NameSpecification nameSpec;
+ if (name.equals("*")) {
+ nameSpec = name("**");
+ } else {
+ nameSpec = name(name);
+ }
+ nameSpec.setNegator(hasNameNegator);
+ ClassSpecification classSpec = new ClassSpecification(nameSpec, classType, annotation);
+ classSpec.setModifier(modifier);
+ return classSpec;
+ }
+
+ @NonNull
+ static ClassTypeSpecification classType(int type, boolean hasNegator) {
+ ClassTypeSpecification classSpec = new ClassTypeSpecification(type);
+ classSpec.setNegator(hasNegator);
+ return classSpec;
+ }
+
+ @NonNull
+ static InheritanceSpecification createInheritance(
+ /*@NonNull*/ String className, boolean hasNameNegator,
+ @NonNull AnnotationSpecification annotationType) {
+ NameSpecification nameSpec = name(className);
+ nameSpec.setNegator(hasNameNegator);
+ return new InheritanceSpecification(nameSpec, annotationType);
+ }
+
+ static void field(
+ @NonNull ClassSpecification classSpec,
+ @Nullable AnnotationSpecification annotationType,
+ @Nullable String typeSignature,
+ @NonNull String name,
+ @NonNull ModifierSpecification modifier) {
+ NameSpecification typeSignatureSpec = null;
+ if (typeSignature != null) {
+ typeSignatureSpec = name(typeSignature);
+ } else {
+ checkState(name.equals("*"), "No type signature, but name is not <fields> or *.");
+ name = "*";
+ }
+ classSpec.add(new FieldSpecification(name(name), modifier, typeSignatureSpec, annotationType));
+ }
+
+ static void fieldOrAnyMember(@NonNull ClassSpecification classSpec,
+ @Nullable AnnotationSpecification annotationType, @Nullable String typeSig,
+ @NonNull String name, @NonNull ModifierSpecification modifier) {
+ if (typeSig == null) {
+ assert name.equals("*");
+ // This is the "any member" case, we have to handle methods as well.
+ method(classSpec,
+ annotationType,
+ getSignature("***", 0),
+ "*",
+ "(" + getSignature("...", 0) + ")",
+ modifier);
+ }
+ field(classSpec, annotationType, typeSig, name, modifier);
+ }
+
+ static void filter(
+ @NonNull FilterSpecification filter,
+ boolean negator,
+ @NonNull String filterName) {
+ filter.addElement(name(filterName), negator);
+ }
+
+ @NonNull
+ static String getSignature(@NonNull String name, int dim) {
+ StringBuilder sig = new StringBuilder();
+
+ for (int i = 0; i < dim; i++) {
+ sig.append("\\[");
+ }
+
+ // ... matches any number of arguments of any type
+ if (name.equals("...")) {
+ sig.append(".*");
+ } else if (name.equals("***")) {
+ // *** matches any type (primitive or non-primitive, array or non-array)
+ sig.append(".*");
+ } else if (name.equals("%")) {
+ // % matches any primitive type ("boolean", "int", etc, but not "void")
+ sig.append("(Z|B|C|S|I|F|D|L)");
+ } else if (name.equals("boolean")) {
+ sig.append("Z");
+ } else if (name.equals("byte")) {
+ sig.append("B");
+ } else if (name.equals("char")) {
+ sig.append("C");
+ } else if (name.equals("short")) {
+ sig.append("S");
+ } else if (name.equals("int")) {
+ sig.append("I");
+ } else if (name.equals("float")) {
+ sig.append("F");
+ } else if (name.equals("double")) {
+ sig.append("D");
+ } else if (name.equals("long")) {
+ sig.append("J");
+ } else if (name.equals("void")) {
+ sig.append("V");
+ } else {
+ sig.append("L").append(convertNameToPattern(name)).append(";");
+ }
+
+ return sig.toString();
+ }
+
+ static void method(
+ @NonNull ClassSpecification classSpec,
+ @Nullable AnnotationSpecification annotationType,
+ @Nullable String typeSig,
+ @NonNull String name,
+ @NonNull String signature,
+ @Nullable ModifierSpecification modifier) {
+ String fullName = "^" + convertNameToPattern(name);
+ fullName += ":";
+ fullName += signature.replace("(", "\\(").replace(")", "\\)");
+ if (typeSig != null) {
+ fullName += typeSig;
+ } else {
+ fullName += "V";
+ }
+ fullName += "$";
+ Pattern pattern = Pattern.compile(fullName);
+ classSpec.add(
+ new MethodSpecification(
+ new NameSpecification(pattern),
+ modifier,
+ annotationType));
+ }
+
+ @NonNull
+ static NameSpecification name(@NonNull String name) {
+ String transformedName = "^" +
+ convertNameToPattern(name) + "$";
+
+ Pattern pattern = Pattern.compile(transformedName);
+ return new NameSpecification(pattern);
+ }
+
+ static void unsupportedFlag(String flag) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Flag %s is not supported by the built-in shrinker.",
+ flag));
+ }
+
+ static void ignoredFlag(String flag, boolean printWarning) {
+ if (printWarning) {
+ logger.warn("Flag {} is ignored by the built-in shrinker.", flag);
+ }
+ }
+
+ @NonNull
+ private static String convertNameToPattern(@NonNull String name) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ switch (c) {
+ case '.':
+ sb.append('/');
+ break;
+ case '?':
+ // ? matches any single character in a name
+ // but not the package separator
+ sb.append("[^/]");
+ break;
+ case '*':
+ int j = i + 1;
+ if (j < name.length() && name.charAt(j) == '*') {
+ // ** matches any part of a name, possibly containing
+ // any number of package separators or directory separators
+ sb.append(".*");
+ i++;
+ } else {
+ // * matches any part of a name not containing
+ // the package separator or directory separator
+ sb.append("[^/]*");
+ }
+ break;
+ case '$':
+ sb.append("\\$");
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ @NonNull
+ private static ProguardParser createParserCommon(@NonNull CharStream stream) {
+ ProguardLexer lexer = new ProguardLexer(stream);
+ CommonTokenStream tokens = new CommonTokenStream(lexer);
+ return new ProguardParser(tokens);
+ }
+
+ @NonNull
+ private static ProguardParser createParserFromFile(@NonNull File file) {
+ try {
+ return createParserCommon(new ANTLRFileStream(file.getPath()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ private static ProguardParser createParserFromString(@NonNull String input) {
+ return createParserCommon(new ANTLRStringStream(input));
+ }
+
+ @NonNull
+ private static File getFileFromBaseDir(@NonNull String baseDir, @NonNull String path) {
+ File file = new File(path);
+ if (!file.isAbsolute()) {
+ file = new File(baseDir, path);
+ }
+ return file;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/InheritanceSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/InheritanceSpecification.java
new file mode 100644
index 0000000..b38fa26
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/InheritanceSpecification.java
@@ -0,0 +1,41 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+/**
+ * "extends" part of a ProGuard class specification.
+ */
+public class InheritanceSpecification {
+
+ private final NameSpecification mNameSpec;
+ private final AnnotationSpecification mAnnotationType;
+
+ public InheritanceSpecification(
+ NameSpecification nameSpec,
+ AnnotationSpecification annotationType) {
+ mNameSpec = nameSpec;
+ mAnnotationType = annotationType;
+ }
+
+ public NameSpecification getNameSpec() {
+ return mNameSpec;
+ }
+
+ public AnnotationSpecification getAnnotationType() {
+ return mAnnotationType;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/KeepModifier.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/KeepModifier.java
new file mode 100644
index 0000000..ed7e4cf
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/KeepModifier.java
@@ -0,0 +1,25 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+/**
+ * Modifier for keep rules.
+ */
+public enum KeepModifier {
+ ALLOW_SHRINKING, ALLOW_OBFUSCATION, NONE
+}
+
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/Matcher.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/Matcher.java
new file mode 100644
index 0000000..279d0fb
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/Matcher.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.shrinker.parser;
+
+/**
+ * Object that can match objects of a given type.
+ */
+public interface Matcher<T> {
+ boolean matches(T t);
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/MatcherWithNegator.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/MatcherWithNegator.java
new file mode 100644
index 0000000..6b55c0c
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/MatcherWithNegator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.shrinker.parser;
+
+/**
+ * {@link Matcher} that can be negated (e.g. by prepending "!" to a class name).
+ */
+public abstract class MatcherWithNegator<T> implements Matcher<T> {
+ private boolean negator = false;
+
+ @Override
+ public boolean matches(T t) {
+ boolean result = matchesWithoutNegator(t);
+ if (!negator) {
+ return result;
+ } else {
+ return !result;
+ }
+ }
+
+ protected abstract boolean matchesWithoutNegator(T t);
+
+ public void setNegator(boolean negator) {
+ this.negator = negator;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/MethodSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/MethodSpecification.java
new file mode 100644
index 0000000..8da07d3
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/MethodSpecification.java
@@ -0,0 +1,54 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * Method part of a ProGuard class specification.
+ */
+public class MethodSpecification {
+
+ @NonNull private final NameSpecification mNameSpecification;
+ @Nullable private final ModifierSpecification mModifiers;
+ @Nullable private final AnnotationSpecification mAnnotationType;
+
+ public MethodSpecification(
+ @NonNull NameSpecification nameSpecification,
+ @Nullable ModifierSpecification modifiers,
+ @Nullable AnnotationSpecification annotationType) {
+ mNameSpecification = nameSpecification;
+ mModifiers = modifiers;
+ mAnnotationType = annotationType;
+ }
+
+ @Nullable
+ public ModifierSpecification getModifiers() {
+ return mModifiers;
+ }
+
+ @Nullable
+ public AnnotationSpecification getAnnotations() {
+ return mAnnotationType;
+ }
+
+ @NonNull
+ public NameSpecification getName() {
+ return mNameSpecification;
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ModifierSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ModifierSpecification.java
new file mode 100644
index 0000000..718bd8a
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/ModifierSpecification.java
@@ -0,0 +1,82 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import com.android.annotations.NonNull;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Modifier part of a ProGuard class specification.
+ */
+public class ModifierSpecification implements Matcher<Integer> {
+ private static final int ACCESSIBILITY_FLAGS =
+ Opcodes.ACC_PUBLIC
+ | Opcodes.ACC_PROTECTED
+ | Opcodes.ACC_PRIVATE;
+
+ private int modifier = 0;
+
+ private int modifierWithNegator;
+
+ public void addModifier(int modifier, boolean hasNegator) {
+ if (hasNegator) {
+ this.modifierWithNegator |= modifier;
+ } else {
+ this.modifier |= modifier;
+ }
+ }
+
+ @Override
+ public boolean matches(@NonNull Integer t) {
+ // Combining multiple flags is allowed (e.g. public static).
+ // It means that both access flags have to be set (e.g. public and static),
+ // except when they are conflicting, in which case at least one of them has
+ // to be set (e.g. at least public or protected).
+ int toCompare = t;
+ int accessflags = toCompare & ACCESSIBILITY_FLAGS;
+ int accessflagsSpec = modifier & ACCESSIBILITY_FLAGS;
+ if (accessflagsSpec != 0) {
+ if ((accessflags | accessflagsSpec) != accessflagsSpec) {
+ return false;
+ }
+ // If the visibility is "package" but the specification isn't,
+ // the modifier doesn't match
+ if (accessflags == 0) {
+ return false;
+ }
+ }
+
+ int negatorAccessFlags = modifierWithNegator & ACCESSIBILITY_FLAGS;
+ if (negatorAccessFlags != 0) {
+ if ((accessflags & negatorAccessFlags) != 0) {
+ return false;
+ }
+ }
+
+ int otherFlags = toCompare & ~ACCESSIBILITY_FLAGS;
+ int otherFlagsSpec = modifier & ~ACCESSIBILITY_FLAGS;
+ if ((otherFlags & otherFlagsSpec) != otherFlagsSpec) {
+ return false;
+ }
+
+ int otherFlagsSpecWithNegator = modifierWithNegator & ~ACCESSIBILITY_FLAGS;
+
+ return otherFlagsSpecWithNegator == 0
+ || ((otherFlagsSpecWithNegator & ~otherFlags) == otherFlagsSpecWithNegator);
+ }
+}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/NameSpecification.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/NameSpecification.java
new file mode 100644
index 0000000..62b8213
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/shrinker/parser/NameSpecification.java
@@ -0,0 +1,35 @@
+/*
+ * 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.build.gradle.shrinker.parser;
+
+import java.util.regex.Pattern;
+
+/**
+ * Name part of a ProGuard class specification, used for matching all names (classes, methods etc.)
+ */
+public class NameSpecification extends MatcherWithNegator<String> {
+ private final Pattern mPattern;
+
+ public NameSpecification(Pattern pattern) {
+ mPattern = pattern;
+ }
+
+ @Override
+ protected boolean matchesWithoutNegator(String s) {
+ return mPattern.matcher(s).matches();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/NoPackage.java b/build-system/gradle-core/src/test/incremental-test-classes/base/NoPackage.java
new file mode 100644
index 0000000..e7de06a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/NoPackage.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+public class NoPackage {
+
+ private String value;
+
+ public NoPackage(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void call() {
+ NoPackage noPackage = new NoPackage("data");
+ noPackage.getValue();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessFields.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessFields.java
new file mode 100644
index 0000000..1f7de8a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessFields.java
@@ -0,0 +1,39 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class that have various field types with all possible access rights.
+ */
+public class AllAccessFields {
+
+ int packagePrivateInt = 3;
+ protected int protectedInt = 7;
+ public int publicInt = 9;
+
+ String packagePrivateString = "foo";
+ protected String protectedString = "foobar";
+ public String publicString = "blahblahblah";
+
+ int[] packagePrivateIntArray = { 1, 2, 3};
+ protected int[] protectedIntArray = {1, 2, 3, 4, 5};
+ public int[] publicIntArray = {1, 3, 5, 7};
+
+ String[] packagePrivateStringArray = { "foo" };
+ protected String[] protectedStringArray = { "foo", "bar" };
+ public String[] publicStringArray = { "blah", "blah", "blah" };
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessFieldsSubclass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessFieldsSubclass.java
new file mode 100644
index 0000000..b2add0d
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessFieldsSubclass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+public class AllAccessFieldsSubclass extends AllAccessFields {
+
+ public int getProtectedInt() {
+ return protectedInt;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessMethods.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessMethods.java
new file mode 100644
index 0000000..bab177c
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessMethods.java
@@ -0,0 +1,163 @@
+/*
+ * 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.example.basic;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Class with all types of access methods.
+ */
+public abstract class AllAccessMethods {
+
+ private String privateMethod(double a, String b, int c) {
+ return "private_method:" + a + b + c;
+ }
+
+ protected String protectedMethod(double a, String b, int c) {
+ return "protected_method:" + a + b + c;
+ }
+
+ String packagePrivateMethod(double a, String b, int c) {
+ return "package_private_method:" + a + b + c;
+ }
+
+ public String publicStringMethod(double a, String b, int c) {
+ return "public_method:" + a + b + c;
+ }
+
+ public int publicIntMethod(int a) {
+ return a*2;
+ }
+
+ public long publicLongMethod(long a) {
+ return a*2;
+ }
+
+ public char publicCharMethod(char a) {
+ return 'a';
+ }
+
+ public double publicDoubleMethod(double a) {
+ return a*2;
+ }
+
+ public float publicFloatMethod(float a) {
+ return a*2;
+ }
+
+ public boolean publicBooleanMethod(boolean a) {
+ return !a;
+ }
+
+ public void voidMethod() {
+ }
+
+ public int[] publicIntArrayMethod(int[] a) {
+ return new int[] {a[0], 2, 3};
+ }
+
+ public long[] publicLongArrayMethod(long[] a) {
+ return new long[] {a[0], 4, 5};
+ }
+
+ public boolean[] publicBooleanArrayMethod(boolean[] a) {
+ return new boolean[] {!a[0], true, false};
+ }
+
+ public char[] publicCharArrayMethod(char[] a) {
+ return new char[] {a[0], 'b', 'c'};
+ }
+
+
+ public double[] publicDoubleArrayMethod(double[] a) {
+ return new double[] {a[0], 6d, 7d};
+ }
+
+ public float[] publicFloatArrayMethod(float[] a) {
+ return new float[] {a[0], 8f, 9f};
+ }
+
+ public String[] publicStringArrayMethod(String[] strings) {
+ return new String[] {strings[0], "a", "b"};
+ }
+
+ private String privateMethodDispath(double a, String b, int c) {
+ return privateMethod(a, b, c);
+ }
+
+ protected String protectedMethodDispatch(double a, String b, int c) {
+ return protectedMethod(a, b, c);
+ }
+
+ String packagePrivateMethodDispatch(double a, String b, int c) {
+ return packagePrivateMethod(a, b, c);
+ }
+
+ public String publicMethodDispatch(double a, String b, int c) {
+ return publicStringMethod(a, b, c);
+ }
+
+ abstract public String abstractMethod(double a, String b, int c);
+
+ // methods on the super class to check the overriden methods are invoked property from the
+ // super class methods.
+ private String doNotOverridePrivateMethodDispath(double a, String b, int c) {
+ return privateMethod(a, b, c);
+ }
+
+ protected String doNotOverrideProtectedMethodDispatch(double a, String b, int c) {
+ return protectedMethod(a, b, c);
+ }
+
+ String doNotOverridePackagePrivateMethodDispatch(double a, String b, int c) {
+ return packagePrivateMethod(a, b, c);
+ }
+
+ public String doNotOverridePublicMethodDispatch(double a, String b, int c) {
+ return publicStringMethod(a, b, c);
+ }
+
+ public List<String> invokeAll(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(privateMethod(a, b, c));
+ builder.add(protectedMethod(a, b, c));
+ builder.add(packagePrivateMethod(a, b, c));
+ builder.add(publicStringMethod(a, b, c));
+ builder.add(abstractMethod(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeAllDispatches(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(privateMethodDispath(a, b, c));
+ builder.add(protectedMethodDispatch(a, b, c));
+ builder.add(packagePrivateMethodDispatch(a, b, c));
+ builder.add(publicMethodDispatch(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeAllDoNotOverrideDispatches(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(doNotOverridePrivateMethodDispath(a, b, c));
+ builder.add(doNotOverrideProtectedMethodDispatch(a, b, c));
+ builder.add(doNotOverridePackagePrivateMethodDispatch(a, b, c));
+ builder.add(doNotOverridePublicMethodDispatch(a, b, c));
+ return builder.build();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessStaticFields.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessStaticFields.java
new file mode 100644
index 0000000..a2a3231
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessStaticFields.java
@@ -0,0 +1,84 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class to test static and static final fields.
+ */
+public class AllAccessStaticFields {
+
+ private static int privateInt = 12;
+ protected static int protectedInt = 24;
+ static int packagePrivateInt = 36;
+ public static int publicInt = 48;
+
+ private static final int finalPrivateInt = 14;
+ protected static final int finalProtectedInt = 28;
+ static final int finalPackagePrivateInt = 42;
+ public static final int finalPublicInt = 56;
+
+ public static String staticAccessAllFields() {
+ System.out.println(Object.class);
+ return String.valueOf(privateInt) +
+ protectedInt +
+ packagePrivateInt +
+ publicInt;
+ }
+
+ public static void staticSetAllFields(
+ int privateInt, int protectedInt, int packagePrivateInt, int publicInt) {
+
+ AllAccessStaticFields.privateInt = privateInt;
+ AllAccessStaticFields.protectedInt = protectedInt;
+ AllAccessStaticFields.packagePrivateInt = packagePrivateInt;
+ AllAccessStaticFields.publicInt = publicInt;
+ }
+
+
+ @SuppressWarnings("MethodMayBeStatic")
+ public String accessAllFields() {
+ return String.valueOf(privateInt) +
+ protectedInt +
+ packagePrivateInt +
+ publicInt;
+ }
+
+ @SuppressWarnings("AccessStaticViaInstance")
+ public void setAllFields(
+ int privateInt, int protectedInt, int packagePrivateInt, int publicInt) {
+
+ this.privateInt = privateInt;
+ this.protectedInt = protectedInt;
+ this.packagePrivateInt = packagePrivateInt;
+ this.publicInt = publicInt;
+ }
+
+ public static String staticAccessAllFinalFields() {
+ return String.valueOf(finalPrivateInt) +
+ finalProtectedInt +
+ finalPackagePrivateInt +
+ finalPublicInt;
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ public String accessAllFinalFields() {
+ return String.valueOf(finalPrivateInt) +
+ finalProtectedInt +
+ finalPackagePrivateInt +
+ finalPublicInt;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessStaticMethods.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessStaticMethods.java
new file mode 100644
index 0000000..8c331d4
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllAccessStaticMethods.java
@@ -0,0 +1,71 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Provide static methods with all access types.
+ */
+public class AllAccessStaticMethods {
+
+ public static int getPublicStaticInt() {
+ return 1;
+ }
+
+ protected static int getProtectedStaticInt() {
+ return 3;
+ }
+
+ static int getPackagePrivateStaticInt() {
+ return 5;
+ }
+
+ public static String getPublicStaticString() {
+ return "public";
+ }
+
+ protected static String getProtectedStaticString() {
+ return "protected";
+ }
+
+ static String getPackagePrivateStaticString() {
+ return "package_private";
+ }
+
+ public static double[] getPublicStaticDoubles() {
+ return new double[]{1d, 4d};
+ }
+
+ protected static double[] getProtectedStaticDoubles() {
+ return new double[0];
+ }
+
+ static double[] getPackagePrivateStaticDoubles() {
+ return null;
+ }
+
+ public static String[] getPublicStaticStrings() {
+ return new String[] { "public", "string"};
+ }
+
+ protected static String[] getProtectedStaticStrings() {
+ return new String[0];
+ }
+
+ static String[] getPackagePrivateStrings() {
+ return new String[] { "package", "private", "string"};
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllTypesFields.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllTypesFields.java
new file mode 100644
index 0000000..1c6a694
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AllTypesFields.java
@@ -0,0 +1,293 @@
+/*
+ * 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.example.basic;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+public class AllTypesFields {
+
+ private boolean privateBooleanField;
+
+ private final boolean privateFinalBooleanField = false;
+
+ private final boolean privateFinalBooleanFieldCtorInit;
+
+
+ private int privateIntField;
+
+ private final int privateFinalIntField = 435;
+
+ private final int privateFinalIntFieldCtorInit;
+
+
+ private long privateLongField;
+
+ private final long privateFinalLongField = 435L;
+
+ private final long privateFinalLongFieldCtorInit;
+
+
+ private float privateFloatField;
+
+ private final float privateFinalFloatField = 435.35f;
+
+ private final float privateFinalFloatFieldCtorInit;
+
+
+ private double privateDoubleField;
+
+ private final double privateFinalDoubleField = 435.35d;
+
+ private final double privateFinalDoubleFieldCtorInit;
+
+
+ private String privateStringField;
+
+ private final String privateFinalStringField = "private_field";
+
+ private final String privateFinalStringFieldCtorInit;
+
+ public AllTypesFields() {
+ privateFinalBooleanFieldCtorInit = true;
+ privateFinalLongFieldCtorInit = 1243L;
+ privateFinalIntFieldCtorInit = 1243;
+ privateFinalStringFieldCtorInit = "private_field_ctor_init";
+ privateFinalFloatFieldCtorInit = 1243.43f;
+ privateFinalDoubleFieldCtorInit = 1234.43d;
+ }
+
+ // STRING methods.
+ public String getPrivateStringField() {
+ return privateStringField;
+ }
+
+ public void setPrivateStringField(String value) {
+ privateStringField = value;
+ }
+
+ public String getPrivateFinalStringField() {
+ return privateFinalStringField;
+ }
+
+ public String getPrivateFinalStringFieldCtorInit() {
+ return privateFinalStringFieldCtorInit;
+ }
+
+ public AllTypesFields chaining(String value) {
+ privateStringField = value;
+ return this;
+ }
+ // end or String methods.
+
+ // BOOLEAN methods.
+ public boolean getPrivateBooleanField() {
+ return privateBooleanField;
+ }
+
+ public void setPrivateBooleanField(boolean value) {
+ privateBooleanField = value;
+ }
+
+ public boolean getPrivateFinalBooleanField() {
+ return privateFinalBooleanField;
+ }
+
+ public boolean getPrivateFinalBooleanFieldCtorInit() {
+ return privateFinalBooleanFieldCtorInit;
+ }
+
+ public AllTypesFields chaining(boolean value) {
+ privateBooleanField = value;
+ return this;
+ }
+ // end of boolean methods.
+
+ // INT methods.
+ public int getPrivateIntField() {
+ return privateIntField;
+ }
+
+ public void setPrivateIntField(int value) {
+ privateIntField = value;
+ }
+
+ public int getPrivateFinalIntField() {
+ return privateFinalIntField;
+ }
+
+ public int getPrivateFinalIntFieldCtorInit() {
+ return privateFinalIntFieldCtorInit;
+ }
+
+ public AllTypesFields chaining(int value) {
+ privateIntField = value;
+ return this;
+ }
+ // end of int methods.
+
+ // LONG methods.
+ public long getPrivateLongField() {
+ return privateLongField;
+ }
+
+ public void setPrivateLongField(long value) {
+ privateLongField = value;
+ }
+
+ public long getPrivateFinalLongField() {
+ return privateFinalLongField;
+ }
+
+ public long getPrivateFinalLongFieldCtorInit() {
+ return privateFinalLongFieldCtorInit;
+ }
+
+ public AllTypesFields chaining(long value) {
+ privateLongField = value;
+ return this;
+ }
+ // end of long methods.
+
+ // DOUBLE methods.
+ public double getPrivateDoubleField() {
+ return privateDoubleField;
+ }
+
+ public void setPrivateDoubleField(double value) {
+ privateDoubleField = value;
+ }
+
+ public double getPrivateFinalDoubleField() {
+ return privateFinalDoubleField;
+ }
+
+ public double getPrivateFinalDoubleFieldCtorInit() {
+ return privateFinalDoubleFieldCtorInit;
+ }
+
+ public AllTypesFields chaining(double value) {
+ privateDoubleField = value;
+ return this;
+ }
+ // end of double methods.
+
+ // FLOAT methods.
+ public float getPrivateFloatField() {
+ return privateFloatField;
+ }
+
+ public void setPrivateFloatField(float value) {
+ privateFloatField = value;
+ }
+
+ public float getPrivateFinalFloatField() {
+ return privateFinalFloatField;
+ }
+
+ public float getPrivateFinalFloatFieldCtorInit() {
+ return privateFinalFloatFieldCtorInit;
+ }
+
+ public AllTypesFields chaining(float value) {
+ privateFloatField = value;
+ return this;
+ }
+ // end of double methods.
+
+ // private methods.
+ private boolean privateBooleanField(boolean value) {
+ return value & privateBooleanField;
+ }
+
+ private int privateIntField(int value) {
+ return privateIntField * value;
+ }
+
+ private long privateLongField(int value) {
+ return privateLongField * value;
+ }
+
+ private float privateFloatField(float value) {
+ return privateLongField * value;
+ }
+
+ private double privateDoubleField(double value) {
+ return privateLongField * value;
+ }
+
+ private String privateStringField(String value) {
+ return privateStringField + value;
+ }
+
+ // Random methods invoking private fields.
+ public List<String> getAllPrivateFields() {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(String.valueOf(privateBooleanField));
+ builder.add(String.valueOf(privateIntField));
+ builder.add(String.valueOf(privateFloatField));
+ builder.add(String.valueOf(privateDoubleField));
+ builder.add(String.valueOf(privateLongField));
+ builder.add(privateStringField);
+ return builder.build();
+ }
+
+ public List<String> getAllPrivateFinalFields() {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(String.valueOf(privateFinalBooleanField));
+ builder.add(String.valueOf(privateFinalIntField));
+ builder.add(String.valueOf(privateFinalFloatField));
+ builder.add(String.valueOf(privateFinalDoubleField));
+ builder.add(String.valueOf(privateFinalLongField));
+ builder.add(privateFinalStringField);
+ return builder.build();
+ }
+
+ public List<String> getAllPrivateFinalCtorInitializedFields() {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(String.valueOf(privateFinalBooleanFieldCtorInit));
+ builder.add(String.valueOf(privateFinalIntFieldCtorInit));
+ builder.add(String.valueOf(privateFinalFloatFieldCtorInit));
+ builder.add(String.valueOf(privateFinalDoubleFieldCtorInit));
+ builder.add(String.valueOf(privateFinalLongFieldCtorInit));
+ builder.add(privateFinalStringFieldCtorInit);
+ return builder.build();
+ }
+
+ public List<String> getAllPrivateMethods() {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(String.valueOf(privateBooleanField(false)));
+ builder.add(String.valueOf(privateIntField(2)));
+ builder.add(String.valueOf(privateFloatField(2f)));
+ builder.add(String.valueOf(privateDoubleField(2d)));
+ builder.add(String.valueOf(privateLongField(2)));
+ builder.add(privateStringField("_modified"));
+ return builder.build();
+ }
+
+ public void setAll(boolean booleanValue, int intValue, long longValue, float floatValue,
+ double doubleValue, String stringValue) {
+
+ privateBooleanField = booleanValue;
+ privateIntField = intValue;
+ privateLongField = longValue;
+ privateFloatField = floatValue;
+ privateDoubleField = doubleValue;
+ privateStringField = stringValue;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AnonymousClasses.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AnonymousClasses.java
new file mode 100644
index 0000000..715d0fc
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AnonymousClasses.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Static anonymous inner classes.
+ */
+public abstract class AnonymousClasses {
+
+ public static final AnonymousClasses FIRST = new AnonymousClasses() {
+
+ @Override
+ public String doSomething() {
+ return "first";
+ }
+ };
+
+ public static final AnonymousClasses SECOND = new AnonymousClasses() {
+ @Override
+ public String doSomething() {
+ return "second";
+ }
+ };
+
+ public abstract String doSomething();
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AnotherPackagePrivateClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AnotherPackagePrivateClass.java
new file mode 100644
index 0000000..8d51d30
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/AnotherPackagePrivateClass.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Some other package private class.
+ */
+class AnotherPackagePrivateClass<T> implements Provider<T> {
+
+ final T value;
+
+ public AnotherPackagePrivateClass(T value) {
+ this.value = value;
+ }
+
+ @Override
+ public T getValue() {
+ return value;
+ }
+
+ Builder builder() {
+ return new Builder();
+ }
+
+ class Builder {
+ T value;
+
+ Builder setValue(T value) {
+ this.value = value;
+ return builder();
+ }
+
+ AnotherPackagePrivateClass<T> build() {
+ return new AnotherPackagePrivateClass<T>(value);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ClashStaticMethod.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ClashStaticMethod.java
new file mode 100644
index 0000000..7f479e0
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ClashStaticMethod.java
@@ -0,0 +1,55 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Check that clashing method names and signatures are dealt with properly.
+ *
+ * In this case, without deduplication logic:
+ * public String build(String string2)
+ * -> public static String build(ClashStaticMethod this, String string2)
+ * and public static String build(ClashStaticMethod obj, String string2)
+ * remains the same.
+ *
+ *
+ */
+public class ClashStaticMethod {
+
+ private final String string1;
+
+ public ClashStaticMethod(String string1) {
+ this.string1 = string1;
+ }
+
+ // The two methods that truly crash.
+ public String append(String string2) {
+ return append(this, string2) + "_instance";
+ }
+
+ public static String append(ClashStaticMethod obj, String string2) {
+ return obj.string1 + string2;
+ }
+
+ // Some other methods which do not
+ public String append(int x) {
+ return append(this, x);
+ }
+
+ public static String append(ClashStaticMethod obj, long x) {
+ return append(obj, Long.toString(x));
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ClassWithFields.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ClassWithFields.java
new file mode 100644
index 0000000..5c787a1
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ClassWithFields.java
@@ -0,0 +1,29 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class with some fields to test field removal during $override transformation.
+ */
+public class ClassWithFields {
+
+ private final String stringField = "string";
+ private final int intField = 0;
+
+ private static final String staticStringField = "static";
+ private static final int staticIntField = 0;
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Constructors.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Constructors.java
new file mode 100644
index 0000000..7d8cf99
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Constructors.java
@@ -0,0 +1,224 @@
+/*
+ * 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.example.basic;
+
+import java.util.List;
+
+public class Constructors {
+
+ public String value;
+ public Constructors(String value) {
+ this.value = value;
+ }
+
+ public static class Base {
+
+ private final String baseFinal;
+
+ public Base(double a, String b, int c) {
+ baseFinal = "base:" + a + b + c;
+ }
+
+ protected Base() {
+ baseFinal = "base:";
+ }
+
+ public String getBaseFinal() {
+ return baseFinal;
+ }
+ }
+
+ abstract static class AbstractBase {
+ private final String baseFinal;
+
+ public AbstractBase(double a, String b, int c) {
+ baseFinal = "abstract_base:" + a + b + c;
+ }
+
+ protected AbstractBase() {
+ baseFinal = "abstract base:";
+ }
+
+ public String getBaseFinal() {
+ return baseFinal;
+ }
+ }
+
+ public static class SubOfAbstract extends AbstractBase {
+ private final String subFinal;
+
+ public String value;
+
+ public SubOfAbstract(int a, int b, int c, int d) {
+ super();
+ subFinal = ":sub_abstract";
+ value = "SubOfAbstract(int, int, int, int)";
+ }
+
+ public String getSubFinal() {
+ return subFinal;
+ }
+ }
+
+ public static class Sub extends Base {
+
+ private final String subFinal;
+
+ public String value;
+
+ public Sub(int a, int b, int c, int d) {
+ super();
+ subFinal = ":sub";
+ value = "Sub(int, int, int, int)";
+ }
+
+ public Sub(double a, String b, int c) {
+ super(a, callMeBefore(b), c);
+ subFinal = "sub:" + a + b + c;
+ value = "Sub(double, String, int)";
+ }
+
+ public Sub(long l, float f) {
+ this(f, callMeBefore(String.valueOf(l)), 0);
+ value = "Sub(long, float)";
+ }
+
+ public Sub(boolean b) {
+ this(b ? 1.0 : 0.0, b ? "true" : "false", b ? 1 : 0);
+ value = "Sub(boolean)";
+ }
+
+ public Sub(int x, int y, int z) {
+ this((x = 2) + 0.1, "" + x + y, z);
+ value = "Sub(" + x + ", " + y + ", " + z + ")";
+ }
+
+ public Sub(String string, boolean condition) {
+ super(1d, string, 2);
+ subFinal = "subFinal";
+ try {
+ Utility.doSomething(condition);
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException("iae " + e.getMessage());
+ }
+ }
+
+ public Sub(String string, String exceptionMessage, boolean condition) {
+ super(1d, string, 2);
+ subFinal = "subFinal";
+ try {
+ Utility.doSomething(false);
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException("iae " + e.getMessage());
+ }
+ try {
+ Utility.doSomething(condition);
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException(exceptionMessage + " " + e.getMessage());
+ }
+ }
+
+ public Sub(String string, boolean condition, String exceptionMessage) {
+ super(1d, string, 2);
+ subFinal = "subFinal";
+ try {
+ try {
+ Utility.doSomething(condition);
+ } catch (ArithmeticException e) {
+ throw new IllegalArgumentException("iae " + e.getMessage());
+ }
+ } catch(IllegalArgumentException e) {
+ throw new RuntimeException(exceptionMessage + " " + e.getMessage());
+ }
+ }
+
+ public Sub(List<String> params, boolean condition) {
+ super(1d, params.get(0), 2);
+ try {
+ Utility.doSomething(condition);
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException(params.get(1) + " " + e.getMessage());
+ } finally {
+ subFinal = "success";
+ }
+ }
+
+ public Sub(boolean condition, List<String> params) {
+ super(1d, params.get(0), 2);
+ try {
+ Utility.doSomething(false);
+ subFinal = "success";
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException(params.get(1) + " " + e.getMessage());
+ } finally {
+ if (condition) {
+ throw new RuntimeException(params.get(1));
+ }
+ }
+ }
+
+ public Sub() {
+ super(10, "foo", 3);
+ throw new IllegalArgumentException("pass me a string !");
+ }
+
+ public String getSubFinal() {
+ return subFinal;
+ }
+
+ public static String callMeBefore(String s){
+ return "[" + s + "]";
+ }
+ }
+
+ private static class Utility {
+ private static void doSomething(boolean raise) throws ArithmeticException {
+ if (raise) {
+ throw new ArithmeticException("overflow");
+ }
+ }
+ }
+
+ public class DupInvokeSpecialBase {
+ public DupInvokeSpecialBase(DupInvokeSpecialBase a) { }
+ }
+
+ public class DupInvokeSpecialSub extends DupInvokeSpecialBase {
+ public String value;
+ public DupInvokeSpecialSub() {
+ super(new DupInvokeSpecialBase(null));
+ value = "original";
+ }
+ }
+
+ public static class PrivateConstructor {
+
+ private String mString;
+
+ private PrivateConstructor(String string) {
+ mString = string;
+ }
+
+ public PrivateConstructor() {
+ this("Base");
+ }
+
+ public String getString() {
+ return mString;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ControlClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ControlClass.java
new file mode 100644
index 0000000..feb0998
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ControlClass.java
@@ -0,0 +1,27 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Simplistic InstantRun validation example to ensure proper test and runtime hook up.
+ */
+public class ControlClass {
+
+ public String getValue() {
+ return "hello";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/CovariantChild.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/CovariantChild.java
new file mode 100644
index 0000000..1c668d0
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/CovariantChild.java
@@ -0,0 +1,29 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Created by jedo on 10/13/15.
+ */
+public class CovariantChild extends CovariantParent {
+
+ // method with a more specific return type.
+ @Override
+ public String getValue() {
+ return "hello" + (String) super.getValue();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/CovariantParent.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/CovariantParent.java
new file mode 100644
index 0000000..c888a29
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/CovariantParent.java
@@ -0,0 +1,27 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class that demonstrate covariant return types
+ */
+public class CovariantParent {
+
+ public Object getValue() {
+ return "hello";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Enums.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Enums.java
new file mode 100644
index 0000000..57c822a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Enums.java
@@ -0,0 +1,42 @@
+/*
+ * 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.example.basic;
+
+public enum Enums {
+
+ VALUE_0("zero"),
+ VALUE_1("one") {
+ @Override
+ public String getValue() {
+ return "overriden:" + super.getValue() + otherMethod();
+ }
+
+ public String otherMethod() {
+ return ":other";
+ }
+ };
+
+ private String value;
+
+ Enums(String argument) {
+ value = argument;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Exceptions.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Exceptions.java
new file mode 100644
index 0000000..91d0802
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Exceptions.java
@@ -0,0 +1,95 @@
+/*
+ * 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.example.basic;
+
+public class Exceptions {
+ public static class MyException extends Exception {
+ public String message;
+ public MyException(String message) {
+ this.message = message;
+ }
+ }
+
+ public Exceptions() {
+ }
+
+ public void throwsNamed() throws MyException {
+ throw new MyException("original");
+ }
+
+ protected void protectedThrowsNamed() throws MyException {
+ throw new MyException("protected");
+ }
+
+ static void staticThrowsNamed() throws MyException {
+ throw new MyException("static");
+ }
+
+ Exceptions(String unused) throws MyException {
+ throw new MyException("ctr");
+ }
+
+ public String catchesNamed() {
+ String ret = "";
+ try {
+ ret += "before";
+ throwsNamed();
+ } catch (MyException e) {
+ ret += ":caught[" + e.message + "]";
+ } finally {
+ ret += ":finally";
+ }
+ return ret;
+ }
+
+ public String catchesHiddenNamed() {
+ String ret = "caught: ";
+ try {
+ protectedThrowsNamed();
+ } catch (MyException e) {
+ ret += "," + e.message;
+ }
+ try {
+ staticThrowsNamed();
+ } catch (MyException e) {
+ ret += "," + e.message;
+ }
+ try {
+ new Exceptions("");
+ } catch (MyException e) {
+ ret += "," + e.message;
+ }
+ return ret;
+ }
+
+ public void throwsRuntime() {
+ throw new RuntimeException("original");
+ }
+
+ public String catchesRuntime() {
+ String ret = "";
+ try {
+ ret += "before";
+ throwsRuntime();
+ } catch (RuntimeException e) {
+ ret += ":caught[" + e.getMessage() + "]";
+ } finally {
+ ret += ":finally";
+ }
+ return ret;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingChild.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingChild.java
new file mode 100644
index 0000000..68b73bc
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingChild.java
@@ -0,0 +1,58 @@
+/*
+ * 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.example.basic;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Child that redefines its parent private fields with the same name
+ */
+public class FieldOverridingChild extends FieldOverridingParent {
+
+ private static Double staticField = 10d;
+ private static List<String> staticCollectionField;
+
+ private Double field = 11d;
+ protected List<String> collectionField;
+
+ public FieldOverridingChild() {
+ collectionField = new ArrayList<String>();
+ collectionField.add("child");
+
+ staticCollectionField = new ArrayList<String>();
+ staticCollectionField.add("static child");
+ }
+
+ public Double field() {
+ return field;
+ }
+
+ public static Double staticField() {
+ return staticField;
+ }
+
+ @Override
+ public List<String> getCollection() {
+ return collectionField;
+ }
+
+ @Override
+ public List<String> getStaticCollection() {
+ return staticCollectionField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingGrandChild.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingGrandChild.java
new file mode 100644
index 0000000..d604b1a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingGrandChild.java
@@ -0,0 +1,29 @@
+/*
+ * 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.example.basic;
+
+import java.util.Collection;
+
+/**
+ * Class that access its parents fields that were redefining the grand-parent private fields.
+ */
+public class FieldOverridingGrandChild extends FieldOverridingChild {
+
+ public Collection<String> getParentCollection() {
+ return super.collectionField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingParent.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingParent.java
new file mode 100644
index 0000000..bb70210
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FieldOverridingParent.java
@@ -0,0 +1,56 @@
+/*
+ * 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.example.basic;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Class to test private field overriding.
+ */
+public class FieldOverridingParent {
+
+ private static String staticField = "static parent";
+ private static Collection<String> staticCollectionField;
+
+ private String field = "parent";
+ private Collection<String> collectionField;
+
+ public FieldOverridingParent() {
+ collectionField = new ArrayList<String>();
+ collectionField.add("parent");
+
+ staticCollectionField = new ArrayList<String>();
+ staticCollectionField.add("static parent");
+ }
+
+ public String getField() {
+ return field;
+ }
+
+ public static String getStaticField() {
+ return staticField;
+ }
+
+ public Collection<String> getCollection() {
+ return collectionField;
+ }
+
+ public Collection<String> getStaticCollection() {
+ return staticCollectionField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FinalFieldsInCtor.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FinalFieldsInCtor.java
new file mode 100644
index 0000000..589ca5e
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/FinalFieldsInCtor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Class that sets a final fields in ctor
+ */
+public class FinalFieldsInCtor {
+
+ public final String publicField;
+ protected final String protectedField;
+ private final String privateField;
+ final String packagePrivateField;
+
+ public FinalFieldsInCtor(String packagePrivateField, String privateField,
+ String protectedField, String publicField) {
+ this.packagePrivateField = packagePrivateField;
+ this.privateField = privateField;
+ this.protectedField = protectedField;
+ this.publicField = publicField;
+ }
+
+ public String getPublicField() {
+ return publicField;
+ }
+
+ public String getProtectedField() {
+ return protectedField;
+ }
+
+ public String getPrivateField() {
+ return privateField;
+ }
+
+ public String getPackagePrivateField() {
+ return packagePrivateField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/GrandChild.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/GrandChild.java
new file mode 100644
index 0000000..298cb86
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/GrandChild.java
@@ -0,0 +1,42 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Tests for accessing grand parents or above methods.
+ */
+public class GrandChild extends ParentInvocation {
+
+ public String someProtectedMethodInv() {
+ // invokevirtual
+ return doNotOverrideProtectedMethodDispatch(24d, "from grand child", 12)
+ // invokespecial
+ + super.doNotOverrideProtectedMethodDispatch(12d, "from grand child", 24);
+ }
+
+ public String somePublicMethodInv() {
+ // invokevirtual
+ return doNotOverridePublicMethodDispatch(24d, "from grand child", 12)
+ // invokespecial
+ + super.doNotOverridePublicMethodDispatch(12d, "from grand child", 24);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o);
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerClass.java
new file mode 100644
index 0000000..16a51a3
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerClass.java
@@ -0,0 +1,35 @@
+/*
+ * 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.example.basic;
+
+/**
+ * InnerClass with Builder pattern semantics.
+ */
+public class InnerClass {
+
+ private final String param;
+
+ public InnerClass(String param) {
+ this.param = param;
+ }
+
+ static class Builder {
+ String build() {
+ return new InnerClass("from builder").param;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerClassInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerClassInvoker.java
new file mode 100644
index 0000000..a85d04f
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerClassInvoker.java
@@ -0,0 +1,27 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Invoker for the InnerClass.Builder
+ */
+public class InnerClassInvoker {
+
+ public String invokeInnerClass() {
+ return new InnerClass.Builder().build();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerOuterInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerOuterInvoker.java
new file mode 100644
index 0000000..f34faaf
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerOuterInvoker.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Invoker for special inner and out classes relationships.
+ */
+public class InnerOuterInvoker {
+
+ public int innerClassSubclassingOuterClassIntValue() {
+ return new InnerSubclassingOuter.Innerclass(12, "desserts").getValue();
+ }
+
+ public String innerClassSubclassingOuterClassFieldValue() {
+ return new InnerSubclassingOuter.Innerclass(12, "desserts").getField();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerSubclassingOuter.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerSubclassingOuter.java
new file mode 100644
index 0000000..45a1605
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/InnerSubclassingOuter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Semi public inner class calling its parent (also its outer class !) private constructor.
+ */
+class InnerSubclassingOuter {
+
+ final int value;
+
+ private InnerSubclassingOuter(int value) {
+ this.value = value;
+ }
+
+ int getValue() {
+ return value;
+ }
+
+ static class Innerclass extends InnerSubclassingOuter {
+ final String field;
+ Innerclass(int value, String field) {
+ super(value);
+ this.field = field;
+ }
+
+ String getField() {
+ return field;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/MultipleMethodInvocations.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/MultipleMethodInvocations.java
new file mode 100644
index 0000000..3c36a96
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/MultipleMethodInvocations.java
@@ -0,0 +1,36 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class with methods delegating multiple times to other methods within the same class or delegate
+ * objects.
+ */
+public class MultipleMethodInvocations {
+
+ public String doSomething(String aString, int aValue, Object something) {
+ return aString + "-" + aValue + "-" + something.toString();
+ }
+
+ private String doSomethingElse() {
+ return "bar";
+ }
+
+ public String doAll() {
+ return doSomething("foo", 4, "bar") + doSomethingElse();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/NoPackageAccess.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/NoPackageAccess.java
new file mode 100644
index 0000000..56dd3a6
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/NoPackageAccess.java
@@ -0,0 +1,28 @@
+/*
+ * 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.example.basic;
+
+import java.util.Arrays;
+
+public class NoPackageAccess {
+
+ public String accessNativeArrayMethods() {
+ int[] array = new int[] { 1, 2, 3};
+
+ return Arrays.toString(array.clone()) + ": " + array.length;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateClass.java
new file mode 100644
index 0000000..1efc724
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateClass.java
@@ -0,0 +1,48 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Package Private class with PackagePrivate methods.
+ */
+class PackagePrivateClass implements Provider<String> {
+
+ private final String stringValue;
+ final String packagePrivateValue;
+ PackagePrivateClass(String param) {
+ this.stringValue = param;
+ packagePrivateValue = new StringBuilder().append(param).reverse().toString();
+ }
+
+ String getStringValue() {
+ return stringValue;
+ }
+
+ @Override
+ public String getValue() {
+ return stringValue;
+ }
+
+ PackagePrivateInterface getPackagePrivateInterface() {
+ return new PackagePrivateInterface() {
+ @Override
+ public String getValue() {
+ return "packagePrivateInterface";
+ }
+ };
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateFieldAccess.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateFieldAccess.java
new file mode 100644
index 0000000..81578a2
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateFieldAccess.java
@@ -0,0 +1,69 @@
+/*
+ * 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.example.basic;
+
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+
+/**
+ * Class that uses package private methods and fields from another object.
+ */
+public class PackagePrivateFieldAccess {
+
+ AllAccessFields allAccessFields = new AllAccessFields();
+
+ public String accessIntFields() {
+ return String.valueOf(allAccessFields.protectedInt
+ + allAccessFields.packagePrivateInt
+ + allAccessFields.publicInt);
+ }
+
+ public String accessStringFields() {
+ return String.valueOf(allAccessFields.protectedString
+ + allAccessFields.packagePrivateString
+ + allAccessFields.publicString);
+ }
+
+ public String accessArrayFields() {
+ ArrayList<String> values = new ArrayList<String>();
+ for (int i : allAccessFields.protectedIntArray) {
+ values.add(String.valueOf(i));
+ }
+ for (int i : allAccessFields.packagePrivateIntArray) {
+ values.add(String.valueOf(i));
+ }
+ for (int i : allAccessFields.publicIntArray) {
+ values.add(String.valueOf(i));
+ }
+ return Joiner.on(",").join(values);
+ }
+
+ public String accessArrayOfStringFields() {
+ ArrayList<String> values = new ArrayList<String>();
+ for (String s : allAccessFields.protectedStringArray) {
+ values.add(s);
+ }
+ for (String s : allAccessFields.packagePrivateStringArray) {
+ values.add(s);
+ }
+ for (String s : allAccessFields.publicStringArray) {
+ values.add(s);
+ }
+ return Joiner.on(",").join(values);
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateInterface.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateInterface.java
new file mode 100644
index 0000000..2a31b43
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateInterface.java
@@ -0,0 +1,25 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Package Private interface for testing.
+ */
+interface PackagePrivateInterface {
+
+ String getValue();
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateInvoker.java
new file mode 100644
index 0000000..ec527ff
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PackagePrivateInvoker.java
@@ -0,0 +1,43 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class that uses package private class and methods.
+ */
+public class PackagePrivateInvoker {
+
+ public String createPackagePrivateObject() {
+ PackagePrivateClass packagePrivateClass = new PackagePrivateClass("foo");
+ return packagePrivateClass.getStringValue();
+ }
+
+ public String invokeTernaryOperator(boolean select) {
+ Provider<String> provider = select
+ ? new PackagePrivateClass("package_private")
+ : new AnotherPackagePrivateClass<String>("another_package_private");
+ return provider.getValue();
+ }
+
+ public String ternaryOperatorInConstructorParams(boolean select) {
+ return new AnotherPackagePrivateClass<String>(select ? "true" : "false").getValue();
+ }
+
+ public String invokePackagePrivateInterface() {
+ return new PackagePrivateClass("random").getPackagePrivateInterface().getValue();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ParentInvocation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ParentInvocation.java
new file mode 100644
index 0000000..647d539
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ParentInvocation.java
@@ -0,0 +1,76 @@
+/*
+ * 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.example.basic;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Tests related to invoking parent methods from a subclass.
+ */
+public class ParentInvocation extends AllAccessMethods {
+
+ public String childMethod(double a, String b, int c) {
+ return "child_method:" + a + b + c;
+ }
+
+ @Override
+ protected String protectedMethod(double a, String b, int c) {
+ return super.protectedMethod(a, b, c) + "_child";
+ }
+
+ @Override
+ String packagePrivateMethod(double a, String b, int c) {
+ return super.packagePrivateMethod(a, b, c) + "_child";
+ }
+
+ @Override
+ public String publicStringMethod(double a, String b, int c) {
+ return super.publicStringMethod(a, b, c) + "_child";
+ }
+
+ @Override
+ public String abstractMethod(double a, String b, int c) {
+ return "abstract: " + a + b + c;
+ }
+
+ public List<String> invokeAllFromSubclass(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(packagePrivateMethod(a, b, c));
+ builder.add(protectedMethod(a, b, c));
+ builder.add(publicStringMethod(a, b, c));
+ builder.add(abstractMethod(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeAllParent(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(super.packagePrivateMethod(a, b, c));
+ builder.add(super.protectedMethod(a, b, c));
+ builder.add(super.publicStringMethod(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeDoNoOverrideMethodsDirectly(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(super.doNotOverridePackagePrivateMethodDispatch(a, b, c));
+ builder.add(super.doNotOverrideProtectedMethodDispatch(a, b, c));
+ builder.add(super.doNotOverridePublicMethodDispatch(a, b, c));
+ return builder.build();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Provider.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Provider.java
new file mode 100644
index 0000000..d77fba7
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/Provider.java
@@ -0,0 +1,25 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Simple provider of a typed value.
+ */
+public interface Provider<T> {
+
+ T getValue();
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PublicMethodInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PublicMethodInvoker.java
new file mode 100644
index 0000000..c4ab2d7
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/PublicMethodInvoker.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+import com.google.common.base.Joiner;
+import com.google.common.primitives.Booleans;
+import com.google.common.primitives.Chars;
+import com.google.common.primitives.Doubles;
+import com.google.common.primitives.Floats;
+import com.google.common.primitives.Ints;
+import com.google.common.primitives.Longs;
+
+/**
+ * Invoke public methods on some other objects.
+ */
+public class PublicMethodInvoker {
+
+ private final int myField;
+
+ public PublicMethodInvoker(int myField) {
+ this.myField = myField;
+ }
+
+ // add tests for all types of parameters and return types.
+ public String invokeAllPublicMethods(AllAccessMethods allAccessMethods) {
+ StringBuilder result = new StringBuilder();
+ result.append(allAccessMethods.publicStringMethod(1d, "invoker", 12));
+ result.append('-');
+ result.append(allAccessMethods.publicIntMethod(1));
+ result.append('-');
+ result.append(allAccessMethods.publicLongMethod(3));
+ result.append('-');
+ result.append(allAccessMethods.publicFloatMethod(12f));
+ result.append('-');
+ result.append(allAccessMethods.publicBooleanMethod(false));
+ result.append('-');
+ result.append(allAccessMethods.publicDoubleMethod(24.12d));
+ result.append('-');
+ result.append(allAccessMethods.publicCharMethod('c'));
+ result.append('-');
+ result.append(myField);
+ allAccessMethods.voidMethod();
+ return result.toString();
+ }
+
+ public String invokeAllPublicArrayMethods(AllAccessMethods allAccessMethods) {
+ StringBuilder result = new StringBuilder();
+ result.append(Joiner.on(',').join(
+ allAccessMethods.publicStringArrayMethod(new String[] {"invoker"})));
+ result.append('-');
+ result.append(Joiner.on(',').join(
+ Longs.asList(allAccessMethods.publicLongArrayMethod(new long[] {3}))));
+ result.append('-');
+ result.append(Joiner.on(',').join(
+ Ints.asList(allAccessMethods.publicIntArrayMethod(new int[] {5}))));
+ result.append('-');
+ result.append(Joiner.on(',').join(
+ Booleans.asList(allAccessMethods.publicBooleanArrayMethod(new boolean[] {false}))));
+ result.append('-');
+ result.append(Joiner.on(',').join(
+ Chars.asList(allAccessMethods.publicCharArrayMethod(new char[] {'a'}))));
+ result.append('-');
+ result.append(Joiner.on(',').join(
+ Floats.asList(allAccessMethods.publicFloatArrayMethod(new float[] {12f}))));
+ result.append('-');
+ result.append(Joiner.on(',').join(
+ Doubles.asList(allAccessMethods.publicDoubleArrayMethod(new double[] {56d}))));
+ result.append('-');
+ result.append(myField);
+ allAccessMethods.voidMethod();
+ return result.toString();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ReflectiveUser.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ReflectiveUser.java
new file mode 100644
index 0000000..340835c
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/ReflectiveUser.java
@@ -0,0 +1,55 @@
+/*
+ * 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.example.basic;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * reflective API user class.
+ */
+public class ReflectiveUser {
+
+ public String value;
+
+ public ReflectiveUser() {
+ }
+
+ public ReflectiveUser(String useReflection)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method hiddenGem = getClass().getDeclaredMethod("hiddenGem", String.class);
+ value = (String) hiddenGem.invoke(this, "stressed");
+ }
+
+ public boolean amIWhoIThinkIam() {
+ return getClass().equals(ReflectiveUser.class);
+ }
+
+ public String useReflectionOnPublicMethod()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method hiddenGem = getClass().getDeclaredMethod("hiddenGem", String.class);
+ return (String) hiddenGem.invoke(this, "stressed");
+ }
+
+ public String hiddenGem(String param) {
+ return new StringBuilder(param).reverse().toString();
+ }
+
+ public String noReflectionUse() {
+ return hiddenGem("stressed");
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/StaticMethodsInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/StaticMethodsInvoker.java
new file mode 100644
index 0000000..dff2fce
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/StaticMethodsInvoker.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+import com.google.common.base.Joiner;
+
+/**
+ * Invoker of static methods on itself or on others.
+ */
+public class StaticMethodsInvoker {
+
+ public int invokeAllStaticIntMethods() {
+ return AllAccessStaticMethods.getPackagePrivateStaticInt()
+ + AllAccessStaticMethods.getProtectedStaticInt()
+ + AllAccessStaticMethods.getPublicStaticInt();
+ }
+
+ public String invokeAllStaticStringMethods() {
+ return Joiner.on(',').join(AllAccessStaticMethods.getPackagePrivateStaticString(),
+ AllAccessStaticMethods.getProtectedStaticString(),
+ AllAccessStaticMethods.getPublicStaticString());
+ }
+
+ public double invokeAllStaticDoubleArrayMethods() {
+ double toReturn = addAll(AllAccessStaticMethods.getPackagePrivateStaticDoubles());
+ toReturn += addAll(AllAccessStaticMethods.getProtectedStaticDoubles());
+ toReturn += addAll(AllAccessStaticMethods.getPublicStaticDoubles());
+ return toReturn;
+ }
+
+ public String invokeAllStaticStringArrayMethods() {
+ return Joiner.on(':').join(
+ Joiner.on(',').join(AllAccessStaticMethods.getPackagePrivateStrings()),
+ Joiner.on(',').join(AllAccessStaticMethods.getProtectedStaticStrings()),
+ Joiner.on(",").join(AllAccessStaticMethods.getPublicStaticStrings()));
+ }
+
+ private double addAll(double[] doubles) {
+ double accumulator = 0;
+ if (doubles!=null) {
+ for (double aDouble : doubles) {
+ accumulator += aDouble;
+ }
+ }
+ return accumulator;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/SuperCall.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/SuperCall.java
new file mode 100644
index 0000000..343ea8b
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/example/basic/SuperCall.java
@@ -0,0 +1,35 @@
+/*
+ * 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.example.basic;
+
+public class SuperCall {
+
+ public static class Base {
+ public String mixedCalls() {
+ return "super:base";
+ }
+ }
+
+ public static class Sub extends Base {
+ @Override
+ public String mixedCalls() {
+ String a = super.mixedCalls();
+ Base base = new Base();
+ return a + base.mixedCalls();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/InstantRunDisabledClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/InstantRunDisabledClass.java
new file mode 100644
index 0000000..30e190b
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/InstantRunDisabledClass.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ir.disable;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Class that disabled the InstantRun feature on itself.
+ */
+ at DisableInstantRun
+public class InstantRunDisabledClass {
+
+ public String stringField;
+
+ public static String getStaticString() {
+ return "Original";
+ }
+
+ public InstantRunDisabledClass() {
+ stringField = "set in original ctor";
+ }
+
+ public String getString() {
+ return "original method";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/InstantRunDisabledMethod.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/InstantRunDisabledMethod.java
new file mode 100644
index 0000000..edea46e
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/InstantRunDisabledMethod.java
@@ -0,0 +1,68 @@
+/*
+ * 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.ir.disable;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Class that selectively disables some of its method
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+public class InstantRunDisabledMethod {
+
+ public static String alterableStaticMethod() {
+ return "alterable static original";
+ }
+
+ @DisableInstantRun
+ public static String nonAlterableStaticMethod() {
+ return "non alterable static original";
+ }
+
+ final String stringField;
+
+ public InstantRunDisabledMethod(String alterable) {
+ stringField = alterable;
+ }
+
+ public String getStringField() {
+ return stringField;
+ }
+
+ @DisableInstantRun
+ public InstantRunDisabledMethod() {
+ stringField = "non alterable ctor";
+ }
+
+ public final String finalAlterableMethod() {
+ return "final alterable original";
+ }
+
+ @DisableInstantRun
+ public final String finalNonAlterableMethod() {
+ return "final non alterable original";
+ }
+
+ public String alterableMethod() {
+ return "alterable original";
+ }
+
+ @DisableInstantRun
+ public String nonAlterableMethod() {
+ return "non alterable original";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/DisabledClassOne.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/DisabledClassOne.java
new file mode 100644
index 0000000..38bc9c6
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/DisabledClassOne.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ir.disable.all;
+
+/**
+ * a Class which is InstantRun disabled due to the annotated package.
+ */
+public class DisabledClassOne {
+
+ public String getValue() {
+ return "original";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/DisabledClassTwo.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/DisabledClassTwo.java
new file mode 100644
index 0000000..a010804
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/DisabledClassTwo.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ir.disable.all;
+
+/**
+ * a Class which is InstantRun disabled due to the annotated package.
+ */
+public class DisabledClassTwo {
+
+ public String getValue() {
+ return "original";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/package-info.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/package-info.java
new file mode 100644
index 0000000..a55ab53
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/ir/disable/all/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * All classes in this package are InstantRun disabled.
+ */
+ at DisableInstantRun
+package com.ir.disable.all;
+
+import com.android.tools.ir.api.DisableInstantRun;
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddClassAnnotation.java
new file mode 100644
index 0000000..a174684
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddClassAnnotation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that will test adding a class annotation is flagged by the verifier.
+ */
+public class AddClassAnnotation {
+
+ public void someMethod() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddInstanceField.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddInstanceField.java
new file mode 100644
index 0000000..dda9504
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddInstanceField.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test adding a field is flagged by the verifier.
+ */
+public class AddInstanceField {
+
+ void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddInterfaceImplementation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddInterfaceImplementation.java
new file mode 100644
index 0000000..792d83b
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddInterfaceImplementation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that test adding an interface to the implemented list of interfaces of a class is flagged
+ * by the verifier.
+ */
+public class AddInterfaceImplementation {
+
+ public void method(){}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddMethodAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddMethodAnnotation.java
new file mode 100644
index 0000000..872d496
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddMethodAnnotation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test adding a method annotation is flagged by verifier.
+ */
+public class AddMethodAnnotation {
+
+ public void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddNotRuntimeClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddNotRuntimeClassAnnotation.java
new file mode 100644
index 0000000..4f9efd9
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/AddNotRuntimeClassAnnotation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test adding an annotation not visible at runtime does not trigger a verifier flag.
+ */
+public class AddNotRuntimeClassAnnotation {
+
+ void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeFieldType.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeFieldType.java
new file mode 100644
index 0000000..b2888fb
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeFieldType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test changing a field type is flagged by the verifier.
+ */
+public class ChangeFieldType {
+
+ public String someField;
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeInstanceFieldToStatic.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeInstanceFieldToStatic.java
new file mode 100644
index 0000000..ec0bb1a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeInstanceFieldToStatic.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that will test changing an instance field into a static will be flagged by the verifier.
+ */
+public class ChangeInstanceFieldToStatic {
+
+ private String someField;
+
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeInstanceFieldVisibility.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeInstanceFieldVisibility.java
new file mode 100644
index 0000000..7d5488a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeInstanceFieldVisibility.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test change an instance field visibility is flagged by the verifier.
+ */
+public class ChangeInstanceFieldVisibility {
+
+ public String someField;
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeStaticFieldToInstance.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeStaticFieldToInstance.java
new file mode 100644
index 0000000..66046b3
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeStaticFieldToInstance.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that checks that changing a static field into an instance field is flagged by the verifier.
+ */
+public class ChangeStaticFieldToInstance {
+
+ public static double someDouble;
+
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeStaticFieldVisibility.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeStaticFieldVisibility.java
new file mode 100644
index 0000000..26e2c4c
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeStaticFieldVisibility.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test changing a static field visibility is flagged by the verifier.
+ */
+public class ChangeStaticFieldVisibility {
+
+ public static String someField;
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeSuperClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeSuperClass.java
new file mode 100644
index 0000000..6272102
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangeSuperClass.java
@@ -0,0 +1,28 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test changing the parent class triggers a verifier flag.
+ */
+public class ChangeSuperClass extends UnchangedClass {
+
+ @Override
+ public String getStringField() {
+ return "child" + super.getStringField();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer1.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer1.java
new file mode 100644
index 0000000..2672fc9
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer1.java
@@ -0,0 +1,29 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that changes its class initializer
+ */
+public class ChangedClassInitializer1 {
+
+ static final int VALUE;
+
+ static {
+ VALUE = 4;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer2.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer2.java
new file mode 100644
index 0000000..1e04c7e
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer2.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that changes its class initializer
+ */
+public class ChangedClassInitializer2 {
+
+ static int VALUE = 4;
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer3.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer3.java
new file mode 100644
index 0000000..4873917
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ChangedClassInitializer3.java
@@ -0,0 +1,33 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that changes its class initializer
+ */
+public class ChangedClassInitializer3 {
+
+ static int VALUE;
+
+ static void setStaticValue() throws RuntimeException {
+ VALUE = 4;
+ }
+
+ static {
+ setStaticValue();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledClassChanging.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledClassChanging.java
new file mode 100644
index 0000000..435d405
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledClassChanging.java
@@ -0,0 +1,35 @@
+/*
+ * 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.verifier.tests;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Verifier test that shows that an InstantRun disabled class changes are flagged by the
+ * verifier.
+ */
+ at DisableInstantRun
+public class DisabledClassChanging {
+
+ public String getString() {
+ return getFloat() + "bar";
+ }
+
+ public Float getFloat() {
+ return 12f;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledClassNotChanging.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledClassNotChanging.java
new file mode 100644
index 0000000..380d4d5
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledClassNotChanging.java
@@ -0,0 +1,35 @@
+/*
+ * 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.verifier.tests;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Verifier test that shows that an InstantRun disabled class not changing is not flagged by the
+ * verifier.
+ */
+ at DisableInstantRun
+public class DisabledClassNotChanging {
+
+ public String getString() {
+ return "foo";
+ }
+
+ public Float getFloat() {
+ return 12f;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledMethodChanging.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledMethodChanging.java
new file mode 100644
index 0000000..936c439
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/DisabledMethodChanging.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Classes with changing InstantRun methods should be flagged by the verifier.
+ */
+public class DisabledMethodChanging {
+
+ @DisableInstantRun
+ public String disabledMethod() {
+ return "foo";
+ }
+
+ public Float getFloat() {
+ return 12f;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/MethodAddedClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/MethodAddedClass.java
new file mode 100644
index 0000000..fea4464
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/MethodAddedClass.java
@@ -0,0 +1,27 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Base version which will simulate adding a method.
+ */
+public class MethodAddedClass {
+
+ public String getValue() {
+ return "value";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/MethodCollisionClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/MethodCollisionClass.java
new file mode 100644
index 0000000..6394c51
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/MethodCollisionClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that defines overloaded methods.
+ */
+public class MethodCollisionClass {
+
+ void method(int i) {}
+
+ void method(String s) {}
+
+ void method(Object o) {}
+
+ static void method(float f) {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/NewInstanceReflectionUser.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/NewInstanceReflectionUser.java
new file mode 100644
index 0000000..79d702c
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/NewInstanceReflectionUser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that use reflection APIs.
+ */
+public class NewInstanceReflectionUser {
+
+ public void method() {
+ try {
+ String.class.newInstance();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/R.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/R.java
new file mode 100644
index 0000000..3b3e716
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/R.java
@@ -0,0 +1,23 @@
+/*
+ * 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.verifier.tests;
+
+public final class R {
+ public static final class id {
+ public static final int text = 7000;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ReflectiveUserNotChanging.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ReflectiveUserNotChanging.java
new file mode 100644
index 0000000..8fb6202
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/ReflectiveUserNotChanging.java
@@ -0,0 +1,36 @@
+/*
+ * 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.verifier.tests;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Reflective API user class that will only change its authorized API.
+ */
+public class ReflectiveUserNotChanging {
+
+ public String useReflectionOnPublicMethod()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method hiddenGem = getClass().getDeclaredMethod("hiddenGem", String.class);
+ return (String) hiddenGem.invoke(this, "stressed");
+ }
+
+ public String hiddenGem(String param) {
+ return new StringBuilder(param).reverse().toString();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveClassAnnotation.java
new file mode 100644
index 0000000..03f9e83
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveClassAnnotation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that test removing a class annotation is flagged by the verifier.
+ */
+ at Deprecated
+public class RemoveClassAnnotation {
+
+ public void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveInterfaceImplementation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveInterfaceImplementation.java
new file mode 100644
index 0000000..ca4721a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveInterfaceImplementation.java
@@ -0,0 +1,27 @@
+/*
+ * 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.verifier.tests;
+
+import java.io.Serializable;
+
+/**
+ * Class that test that removing an implemented interface from a class is flagged by the verifier.
+ */
+public class RemoveInterfaceImplementation implements Serializable {
+
+ public void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveMethodAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveMethodAnnotation.java
new file mode 100644
index 0000000..8069ce7
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveMethodAnnotation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that test removing a method annotation is flagged by the verifier.
+ */
+public class RemoveMethodAnnotation {
+
+ @Deprecated
+ public void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveNotRuntimeClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveNotRuntimeClassAnnotation.java
new file mode 100644
index 0000000..4624f46
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/RemoveNotRuntimeClassAnnotation.java
@@ -0,0 +1,27 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test that removing a non runtime visible class annotation is not flagged by the
+ * verifier
+ */
+ at SuppressWarnings("any")
+public class RemoveNotRuntimeClassAnnotation {
+
+ void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/UnchangedClass.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/UnchangedClass.java
new file mode 100644
index 0000000..ec4c956
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/UnchangedClass.java
@@ -0,0 +1,29 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class which should always pass verification.
+ */
+public class UnchangedClass {
+
+ private String stringField;
+
+ public String getStringField() {
+ return stringField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/UnchangedClassInitializer1.java b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/UnchangedClassInitializer1.java
new file mode 100644
index 0000000..b5f0aaf
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/base/com/verifier/tests/UnchangedClassInitializer1.java
@@ -0,0 +1,33 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test that an unchanged class initializer is not flagged by the verifier.
+ */
+public class UnchangedClassInitializer1 {
+
+ static final int VALUE;
+
+ int someMethodToChangeStaticInitializerLineNumbers(int i) {
+ return i*2;
+ }
+
+ static {
+ VALUE = 40;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/NoPackage.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/NoPackage.java
new file mode 100644
index 0000000..e7de06a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/NoPackage.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+public class NoPackage {
+
+ private String value;
+
+ public NoPackage(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void call() {
+ NoPackage noPackage = new NoPackage("data");
+ noPackage.getValue();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/AllAccessMethods.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/AllAccessMethods.java
new file mode 100644
index 0000000..8094af9
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/AllAccessMethods.java
@@ -0,0 +1,117 @@
+/*
+ * 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.example.basic;
+
+import java.util.List;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Class with all types of access methods.
+ */
+public class AllAccessMethods {
+
+ private String privateMethod(double a, String b, int c) {
+ return "patched_private_method:" + a + b + c;
+ }
+
+ protected String protectedMethod(double a, String b, int c) {
+ return "patched_protected_method:" + a + b + c;
+ }
+
+ String packagePrivateMethod(double a, String b, int c) {
+ return "patched_package_private_method:" + a + b + c;
+ }
+
+ public String publicStringMethod(double a, String b, int c) {
+ return "patched_public_method:" + a + b + c;
+ }
+
+ private String privateMethodDispath(double a, String b, int c) {
+ return privateMethod(a, b, c);
+ }
+
+ protected String protectedMethodDispatch(double a, String b, int c) {
+ return protectedMethod(a, b, c);
+ }
+
+ String packagePrivateMethodDispatch(double a, String b, int c) {
+ return packagePrivateMethod(a, b, c);
+ }
+
+ public String publicMethodDispatch(double a, String b, int c) {
+ return publicStringMethod(a, b, c);
+ }
+
+ private String doNotOverridePrivateMethod(double a, String b, int c) {
+ return "patched_private_method:" + a + b + c;
+ }
+
+ protected String doNotOverrideProtectedMethod(double a, String b, int c) {
+ return "patched_protected_method:" + a + b + c;
+ }
+
+ String doNotOverridePackagePrivateMethod(double a, String b, int c) {
+ return "patched_package_private_method:" + a + b + c;
+ }
+
+ public String doNotOverridePublicMethod(double a, String b, int c) {
+ return "patched_public_method:" + a + b + c;
+ }
+
+ private String doNotOverridePrivateMethodDispath(double a, String b, int c) {
+ return privateMethod(a, b, c);
+ }
+
+ protected String doNotOverrideProtectedMethodDispatch(double a, String b, int c) {
+ return protectedMethod(a, b, c);
+ }
+
+ String doNotOverridePackagePrivateMethodDispatch(double a, String b, int c) {
+ return packagePrivateMethod(a, b, c);
+ }
+
+ public String doNotOverridePublicMethodDispatch(double a, String b, int c) {
+ return publicStringMethod(a, b, c);
+ }
+
+ public List<String> invokeAll(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(privateMethod(a, b, c));
+ builder.add(protectedMethod(a, b, c));
+ builder.add(packagePrivateMethod(a, b, c));
+ builder.add(publicStringMethod(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeAllDispatches(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(privateMethodDispath(a, b, c));
+ builder.add(protectedMethodDispatch(a, b, c));
+ builder.add(packagePrivateMethodDispatch(a, b, c));
+ builder.add(publicMethodDispatch(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeAllDoNotOverrideDispatches(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(doNotOverridePrivateMethodDispath(a, b, c));
+ builder.add(doNotOverrideProtectedMethodDispatch(a, b, c));
+ builder.add(doNotOverridePackagePrivateMethodDispatch(a, b, c));
+ builder.add(doNotOverridePublicMethodDispatch(a, b, c));
+ return builder.build();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/AnonymousClasses.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/AnonymousClasses.java
new file mode 100644
index 0000000..e6e919b
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/AnonymousClasses.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Anonymous inner classes
+ */
+public abstract class AnonymousClasses {
+
+ public final AnonymousClasses FIRST = new AnonymousClasses() {
+
+ @Override
+ public String doSomething() {
+ return "patched_first";
+ }
+ };
+
+ public final AnonymousClasses SECOND = new AnonymousClasses() {
+ @Override
+ public String doSomething() {
+ return "patched_second";
+ }
+ };
+
+ public abstract String doSomething();
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ClashStaticMethod.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ClashStaticMethod.java
new file mode 100644
index 0000000..32984c5
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ClashStaticMethod.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+public class ClashStaticMethod {
+
+ private final String string1;
+
+ public ClashStaticMethod(String string1) {
+ this.string1 = string1;
+ }
+
+ // The two methods that truly crash.
+ public String append(String string2) {
+ return append(this, string2) + "_instance_override";
+ }
+
+ private static String append(ClashStaticMethod obj, String string2) {
+ return string2 + obj.string1;
+ }
+
+ // Some other methods which do not
+ public String append(int x) {
+ return append(this, x);
+ }
+
+ public static String append(ClashStaticMethod obj, long x) {
+ return append(obj, Long.toString(x));
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ClassWithFields.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ClassWithFields.java
new file mode 100644
index 0000000..5c787a1
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ClassWithFields.java
@@ -0,0 +1,29 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class with some fields to test field removal during $override transformation.
+ */
+public class ClassWithFields {
+
+ private final String stringField = "string";
+ private final int intField = 0;
+
+ private static final String staticStringField = "static";
+ private static final int staticIntField = 0;
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Constructors.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Constructors.java
new file mode 100644
index 0000000..19427d8
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Constructors.java
@@ -0,0 +1,226 @@
+/*
+ * 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.example.basic;
+
+import java.util.List;
+
+public class Constructors {
+
+ public String value;
+ public Constructors(String value) {
+ this.value = value;
+ }
+
+ public static class Base {
+
+ private final String baseFinal;
+
+ public Base(double a, String b, int c) {
+ baseFinal = a + b + c + ":patched_base";
+ }
+
+ protected Base() {
+ baseFinal = "base:";
+ }
+
+ public String getBaseFinal() {
+ return baseFinal;
+ }
+ }
+
+ abstract static class AbstractBase {
+ private final String baseFinal;
+
+ public AbstractBase(double a, String b, int c) {
+ baseFinal = "patched_abstract_base:" + a + b + c;
+ }
+
+ protected AbstractBase() {
+ baseFinal = "patched_abstract base:";
+ }
+
+ public String getBaseFinal() {
+ return baseFinal;
+ }
+ }
+
+ public static class SubOfAbstract extends AbstractBase {
+ private final String subFinal;
+
+ public String value;
+
+ public SubOfAbstract(int a, int b, int c, int d) {
+ super(c, "" + b + c, c + d);
+ subFinal = ":patched_sub_abstract";
+ value = "patched_SubOfAbstract(int, int, int, int)";
+ }
+
+ public String getSubFinal() {
+ return subFinal;
+ }
+ }
+
+ public static class Sub extends Base {
+
+ private final String subFinal;
+
+ public String value;
+
+ public Sub(int a, int b, int c, int d) {
+ super(c = 3, "" + b + c, c + d);
+ subFinal = ":patched_sub";
+ value = "patched_Sub(int, int, int, int)" + c;
+ }
+
+ public Sub(double a, String b, int c) {
+ super(a, callMeBefore(b), c);
+ subFinal = a + b + c + ":patched_sub";
+ value = "patched_Sub(double, String, int)";
+ }
+
+ public Sub(long l, float f) {
+ this(f, callMeBefore(String.valueOf(l + 1) + "*"), 0);
+ value = "patched_Sub(long, float)";
+ }
+
+ public Sub(boolean b) {
+ this(b ? 1.0 : 0.0, b ? "true" : "false", b ? 1 : 0);
+ value = "Sub(boolean)";
+ }
+
+ public Sub(int x, int y, int z) {
+ this((x = 9) + 0.1, "" + x + y, z);
+ value = "Sub(" + x + ", " + y + ", " + z + ")";
+ }
+
+ public Sub(String string, boolean condition) {
+ super(10d, string, 20);
+ subFinal = "updated sub";
+ try {
+ Utility.doSomething(!condition);
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException("updated iae " + e.getMessage());
+ }
+ }
+
+ public Sub(String string, String exceptionMessage, boolean condition) {
+ super(11d, string, 20);
+ subFinal = "updated sub";
+ try {
+ Utility.doSomething(false);
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException("iae " + e.getMessage());
+ }
+ try {
+ Utility.doSomething(!condition);
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException(exceptionMessage + " " + e.getMessage());
+ }
+ }
+
+ public Sub(String string, boolean condition, String exceptionMessage) {
+ super(1d, string, 2);
+ subFinal = "subFinal";
+ try {
+ try {
+ Utility.doSomething(!condition);
+ } catch (ArithmeticException e) {
+ throw new IllegalArgumentException("updated iae " + e.getMessage());
+ }
+ } catch(IllegalArgumentException e) {
+ throw new RuntimeException(e.getMessage() + " " + exceptionMessage);
+ }
+ }
+
+ public Sub() {
+ super(100d, "updated sub", 30);
+ throw new IllegalArgumentException("pass me an updated string !");
+ }
+
+ public Sub(List<String> params, boolean condition) {
+ super(101d, params.get(0), 33);
+ try {
+ Utility.doSomething(!condition);
+ } catch(ArithmeticException e) {
+ throw new RuntimeException(e.getMessage() + " " + params.get(1));
+ } finally {
+ subFinal = "updated subFinal";
+ }
+ }
+
+ public Sub(boolean condition, List<String> params) {
+ super(1d, params.get(0), 2);
+ try {
+ Utility.doSomething(false);
+ subFinal = "updated success";
+ } catch(ArithmeticException e) {
+ throw new IllegalArgumentException(e.getMessage() + " updated " + params.get(1));
+ } finally {
+ if (condition) {
+ throw new RuntimeException("updated " + params.get(1));
+ }
+ }
+ }
+
+ public String getSubFinal() {
+ return subFinal;
+ }
+
+ public static String callMeBefore(String s){
+ return "(" + s + ")";
+ }
+ }
+
+
+ private static class Utility {
+ private static void doSomething(boolean raise) throws ArithmeticException {
+ if (raise) {
+ throw new ArithmeticException("underflow");
+ }
+ }
+ }
+
+ public class DupInvokeSpecialBase {
+ public DupInvokeSpecialBase(DupInvokeSpecialBase a) { }
+ }
+
+ public class DupInvokeSpecialSub extends DupInvokeSpecialBase {
+ public String value;
+ public DupInvokeSpecialSub() {
+ super(new DupInvokeSpecialBase(null));
+ value = "patched";
+ }
+ }
+
+ public static class PrivateConstructor {
+
+ private String mString;
+
+ private PrivateConstructor(String string) {
+ mString = string;
+ }
+
+ public PrivateConstructor() {
+ // Call to private constructor via dispatching this
+ this("Patched");
+ }
+
+ public String getString() {
+ return mString;
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ControlClass.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ControlClass.java
new file mode 100644
index 0000000..40b5d53
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/ControlClass.java
@@ -0,0 +1,27 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Simplistic InstantRun validation example to ensure proper test and runtime hook up.
+ */
+public class ControlClass {
+
+ public String getValue() {
+ return "modified";
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Enums.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Enums.java
new file mode 100644
index 0000000..05b2f80
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Enums.java
@@ -0,0 +1,42 @@
+/*
+ * 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.example.basic;
+
+public enum Enums {
+
+ VALUE_0("zero"),
+ VALUE_1("one") {
+ @Override
+ public String getValue() {
+ return "patched+overriden:" + super.getValue() + otherMethod();
+ }
+
+ public String otherMethod() {
+ return ":other+patched";
+ }
+ };
+
+ private String value;
+
+ Enums(String argument) {
+ value = argument;
+ }
+
+ public String getValue() {
+ return value + ":patched";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Exceptions.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Exceptions.java
new file mode 100644
index 0000000..201fc12
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/Exceptions.java
@@ -0,0 +1,96 @@
+/*
+ * 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.example.basic;
+
+public class Exceptions {
+ public static class MyException extends Exception {
+ public String message;
+ public MyException(String message) {
+ this.message = message;
+ }
+ }
+
+ public Exceptions() {
+ }
+
+ public void throwsNamed() throws MyException {
+ throw new MyException("patched");
+ }
+
+ protected void protectedThrowsNamed() throws MyException {
+ throw new MyException("protected_p");
+ }
+
+ static void staticThrowsNamed() throws MyException {
+ throw new MyException("static_p");
+ }
+
+ Exceptions(String unused) throws MyException {
+ throw new MyException("ctr_p");
+ }
+
+ public String catchesNamed() {
+ String ret = "";
+ try {
+ ret += "before_p";
+ throwsNamed();
+ } catch (MyException e) {
+ ret += ":caught_p[" + e.message + "]";
+ } finally {
+ ret += ":finally_p";
+ }
+ return ret;
+ }
+
+ public String catchesHiddenNamed() {
+ String ret = "caught_p: ";
+ try {
+ protectedThrowsNamed();
+ } catch (MyException e) {
+ ret += ";" + e.message;
+ }
+ try {
+ staticThrowsNamed();
+ } catch (MyException e) {
+ ret += ";" + e.message;
+ }
+ try {
+ new Exceptions("");
+ } catch (MyException e) {
+ ret += ";" + e.message;
+ }
+ return ret;
+ }
+
+
+ public void throwsRuntime() {
+ throw new RuntimeException("patched");
+ }
+
+ public String catchesRuntime() {
+ String ret = "";
+ try {
+ ret += "before_p";
+ throwsRuntime();
+ } catch (RuntimeException e) {
+ ret += ":caught_p[" + e.getMessage() + "]";
+ } finally {
+ ret += ":finally_p";
+ }
+ return ret;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/FinalFieldsInCtor.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/FinalFieldsInCtor.java
new file mode 100644
index 0000000..6d380ed
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/FinalFieldsInCtor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Class that sets a final fields in ctor
+ */
+public class FinalFieldsInCtor {
+
+ public final String publicField;
+ protected final String protectedField;
+ private final String privateField;
+ final String packagePrivateField;
+
+ public FinalFieldsInCtor(String packagePrivateField, String privateField,
+ String protectedField, String publicField) {
+ this.packagePrivateField = "modified " + packagePrivateField;
+ this.privateField = "modified " + privateField;
+ this.protectedField = "modified " + protectedField;
+ this.publicField = "modified " + publicField;
+ }
+
+ public String getPublicField() {
+ return "method " + publicField;
+ }
+
+ public String getProtectedField() {
+ return "method " + protectedField;
+ }
+
+ public String getPrivateField() {
+ return "method " + privateField;
+ }
+
+ public String getPackagePrivateField() {
+ return "method " + packagePrivateField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/InnerSubclassingOuter.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/InnerSubclassingOuter.java
new file mode 100644
index 0000000..b28c290
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/InnerSubclassingOuter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Semi public inner class calling its parent (also its outer class !) private constructor.
+ */
+class InnerSubclassingOuter {
+
+ final int value;
+
+ private InnerSubclassingOuter(int value) {
+ this.value = 2*value;
+ }
+
+ int getValue() {
+ return value;
+ }
+
+ static class Innerclass extends InnerSubclassingOuter {
+ final String field;
+ Innerclass(int value, String field) {
+ super(2*value);
+ this.field = new StringBuilder().append(field).reverse().toString();
+ }
+
+ String getField() {
+ return field;
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/MultipleMethodInvocations.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/MultipleMethodInvocations.java
new file mode 100644
index 0000000..0500138
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/MultipleMethodInvocations.java
@@ -0,0 +1,37 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Patched version.
+ * Class with methods delegating multiple times to other methods within the same class or delegate
+ * objects.
+ */
+public class MultipleMethodInvocations {
+
+ public String doSomething(String aString, int aValue, Object something) {
+ return aString + "-" + aValue + "-" + something.toString();
+ }
+
+ private String doSomethingElse() {
+ return "bar";
+ }
+
+ public String doAll() {
+ return doSomethingElse() + doSomething("foo", 5, "bar");
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/NoPackageAccess.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/NoPackageAccess.java
new file mode 100644
index 0000000..6c0e481
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/NoPackageAccess.java
@@ -0,0 +1,28 @@
+/*
+ * 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.example.basic;
+
+import java.util.Arrays;
+
+public class NoPackageAccess {
+
+ public String accessNativeArrayMethods() {
+ int[] array = new int[] { 4, 5, 6};
+
+ return Arrays.toString(array.clone()) + "[:] " + array.length;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/SuperCall.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/SuperCall.java
new file mode 100644
index 0000000..2cd27fc
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/example/basic/SuperCall.java
@@ -0,0 +1,35 @@
+/*
+ * 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.example.basic;
+
+public class SuperCall {
+
+ public static class Base {
+ public String mixedCalls() {
+ return "super:base:patched";
+ }
+ }
+
+ public static class Sub extends Base {
+ @Override
+ public String mixedCalls() {
+ String a = super.mixedCalls();
+ Base base = new Base();
+ return a + base.mixedCalls();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/InstantRunDisabledClass.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/InstantRunDisabledClass.java
new file mode 100644
index 0000000..4df517e
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/InstantRunDisabledClass.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ir.disable;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Class that disabled the InstantRun feature on itself.
+ */
+ at DisableInstantRun
+public class InstantRunDisabledClass {
+
+ private String stringField;
+
+ public static String getStaticString() {
+ return "Updated";
+ }
+
+ public InstantRunDisabledClass() {
+ stringField = "set in updated ctor";
+ }
+
+ public String getString() {
+ return "updated method";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/InstantRunDisabledMethod.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/InstantRunDisabledMethod.java
new file mode 100644
index 0000000..9ddafc0
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/InstantRunDisabledMethod.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ir.disable;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Class that selectively disables some of its method
+ */
+public class InstantRunDisabledMethod {
+
+ public static String alterableStaticMethod() {
+ return "alterable static updated";
+ }
+
+ @DisableInstantRun
+ public static String nonAlterableStaticMethod() {
+ return "non alterable static updated";
+ }
+
+ final String stringField;
+
+ public InstantRunDisabledMethod(String alterable) {
+ stringField = "modified " + alterable;
+ }
+
+ public String getStringField() {
+ return stringField;
+ }
+
+ @DisableInstantRun
+ public InstantRunDisabledMethod() {
+ stringField = "non alterable ctor updated";
+ }
+
+ public final String finalAlterableMethod() {
+ return "final alterable updated";
+ }
+
+ @DisableInstantRun
+ public final String finalNonAlterableMethod() {
+ return "final non alterable updated";
+ }
+
+ public String alterableMethod() {
+ return "alterable updated";
+ }
+
+ @DisableInstantRun
+ public String nonAlterableMethod() {
+ return "non alterable updated";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/DisabledClassOne.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/DisabledClassOne.java
new file mode 100644
index 0000000..5032243
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/DisabledClassOne.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ir.disable.all;
+
+/**
+ * a Class which is InstantRun disabled due to the annotated package.
+ */
+public class DisabledClassOne {
+
+ public String getValue() {
+ return "modified";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/DisabledClassTwo.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/DisabledClassTwo.java
new file mode 100644
index 0000000..4a4232c
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/DisabledClassTwo.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ir.disable.all;
+
+/**
+ * a Class which is InstantRun disabled due to the annotated package.
+ */
+public class DisabledClassTwo {
+
+ public String getValue() {
+ return "modified";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/package-info.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/package-info.java
new file mode 100644
index 0000000..a55ab53
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeBaseClass/com/ir/disable/all/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * All classes in this package are InstantRun disabled.
+ */
+ at DisableInstantRun
+package com.ir.disable.all;
+
+import com.android.tools.ir.api.DisableInstantRun;
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/AllAccessFieldsSubclass.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/AllAccessFieldsSubclass.java
new file mode 100644
index 0000000..c734a24
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/AllAccessFieldsSubclass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+public class AllAccessFieldsSubclass extends AllAccessFields {
+
+ public int getProtectedInt() {
+ return protectedInt + 42;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/AllAccessStaticFields.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/AllAccessStaticFields.java
new file mode 100644
index 0000000..8f2d9a2
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/AllAccessStaticFields.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Class to test static and static final fields.
+ */
+public class AllAccessStaticFields {
+
+ private static int privateInt = 13;
+ protected static int protectedInt = 26;
+ static int packagePrivateInt = 39;
+ public static int publicInt = 52;
+
+ private static final int finalPrivateInt = 15;
+ protected static final int finalProtectedInt = 30;
+ static final int finalPackagePrivateInt = 45;
+ public static final int finalPublicInt = 60;
+
+ // reverse order
+ public static String staticAccessAllFields() {
+ return String.valueOf(publicInt) +
+ packagePrivateInt +
+ protectedInt +
+ privateInt;
+ }
+
+ public static void staticSetAllFields(
+ int privateInt, int protectedInt, int packagePrivateInt, int publicInt) {
+
+ AllAccessStaticFields.privateInt = privateInt;
+ AllAccessStaticFields.protectedInt = protectedInt;
+ AllAccessStaticFields.packagePrivateInt = packagePrivateInt;
+ AllAccessStaticFields.publicInt = publicInt;
+ }
+
+
+ // don't reverse order, add one.
+ @SuppressWarnings("MethodMayBeStatic")
+ public String accessAllFields() {
+ return String.valueOf(publicInt + 1) +
+ (packagePrivateInt + 1) +
+ (protectedInt + 1) +
+ (privateInt +1);
+ }
+
+ @SuppressWarnings("AccessStaticViaInstance")
+ public void setAllFields(
+ int privateInt, int protectedInt, int packagePrivateInt, int publicInt) {
+
+ this.privateInt = 2*privateInt;
+ this.protectedInt = 2*protectedInt;
+ this.packagePrivateInt = 2*packagePrivateInt;
+ this.publicInt = 2*publicInt;
+ }
+
+ // reverse order.
+ public static String staticAccessAllFinalFields() {
+ return String.valueOf(finalPublicInt) +
+ finalPackagePrivateInt +
+ finalProtectedInt +
+ finalPrivateInt;
+ }
+
+ // no change.
+ @SuppressWarnings("MethodMayBeStatic")
+ public String accessAllFinalFields() {
+ return String.valueOf(finalPrivateInt) +
+ finalProtectedInt +
+ finalPackagePrivateInt +
+ finalPublicInt;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/CovariantChild.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/CovariantChild.java
new file mode 100644
index 0000000..8ab625d
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/CovariantChild.java
@@ -0,0 +1,29 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Created by jedo on 10/13/15.
+ */
+public class CovariantChild extends CovariantParent {
+
+ // method with a more specific return type.
+ @Override
+ String getValue() {
+ return "Modified child " + super.getValue();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/CovariantParent.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/CovariantParent.java
new file mode 100644
index 0000000..e951306
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/CovariantParent.java
@@ -0,0 +1,27 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Class that demonstrate covariant return types
+ */
+public class CovariantParent {
+
+ Object getValue() {
+ return "Modified parent";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingChild.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingChild.java
new file mode 100644
index 0000000..c8fc4a0
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingChild.java
@@ -0,0 +1,59 @@
+/*
+ * 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.example.basic;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Child that redefines its parent private fields with the same name
+ */
+public class FieldOverridingChild extends FieldOverridingParent {
+
+ private static Double staticField;
+ private static List<String> staticCollectionField;
+
+ private Double field = 12d;
+ protected List<String> collectionField;
+
+ public FieldOverridingChild() {
+ staticField = 13d;
+ collectionField = new ArrayList<String>();
+ collectionField.add("modified child");
+
+ staticCollectionField = new ArrayList<String>();
+ staticCollectionField.add("modified static child");
+ }
+
+ public Double field() {
+ return field;
+ }
+
+ public static Double staticField() {
+ return staticField;
+ }
+
+ @Override
+ public List<String> getCollection() {
+ return collectionField;
+ }
+
+ @Override
+ public List<String> getStaticCollection() {
+ return staticCollectionField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingGrandChild.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingGrandChild.java
new file mode 100644
index 0000000..d604b1a
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingGrandChild.java
@@ -0,0 +1,29 @@
+/*
+ * 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.example.basic;
+
+import java.util.Collection;
+
+/**
+ * Class that access its parents fields that were redefining the grand-parent private fields.
+ */
+public class FieldOverridingGrandChild extends FieldOverridingChild {
+
+ public Collection<String> getParentCollection() {
+ return super.collectionField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingParent.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingParent.java
new file mode 100644
index 0000000..2680d93
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/FieldOverridingParent.java
@@ -0,0 +1,56 @@
+/*
+ * 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.example.basic;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Class to test private field overriding.
+ */
+public class FieldOverridingParent {
+
+ private static String staticField = "modified static parent";
+ private static Collection<String> staticCollectionField;
+
+ private String field = "modified parent";
+ private Collection<String> collectionField;
+
+ public FieldOverridingParent() {
+ collectionField = new ArrayList<String>();
+ collectionField.add("modified parent");
+
+ staticCollectionField = new ArrayList<String>();
+ staticCollectionField.add("modified static parent");
+ }
+
+ public String getField() {
+ return "modified " + field;
+ }
+
+ public Collection<String> getCollection() {
+ return collectionField;
+ }
+
+ public static String getStaticField() {
+ return "modified " + staticField;
+ }
+
+ public Collection<String> getStaticCollection() {
+ return staticCollectionField;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/GrandChild.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/GrandChild.java
new file mode 100644
index 0000000..e24d1ff
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/GrandChild.java
@@ -0,0 +1,42 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Tests for accessing grand parents or above methods.
+ */
+public class GrandChild extends ParentInvocation {
+
+ public String someProtectedMethodInv() {
+ // invokevirtual
+ return doNotOverrideProtectedMethodDispatch(26d, "from grand child", 13)
+ // invokespecial
+ + super.doNotOverrideProtectedMethodDispatch(13d, "from grand child", 26);
+ }
+
+ public String somePublicMethodInv() {
+ // invokevirtual
+ return doNotOverridePublicMethodDispatch(26d, "from grand child", 12)
+ // invokespecial
+ + super.doNotOverridePublicMethodDispatch(12d, "from grand child", 26);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) && false;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/InnerClassInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/InnerClassInvoker.java
new file mode 100644
index 0000000..601d8b5
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/InnerClassInvoker.java
@@ -0,0 +1,27 @@
+/*
+ * 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.example.basic;
+
+/**
+ * Invoker for the InnerClass.Builder
+ */
+public class InnerClassInvoker {
+
+ public String invokeInnerClass() {
+ return new InnerClass.Builder().build() + " revisited";
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PackagePrivateFieldAccess.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PackagePrivateFieldAccess.java
new file mode 100644
index 0000000..7a032d8
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PackagePrivateFieldAccess.java
@@ -0,0 +1,73 @@
+/*
+ * 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.example.basic;
+
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+
+/**
+ * Class that uses package private methods and fields from another object.
+ */
+public class PackagePrivateFieldAccess {
+
+ AllAccessFields allAccessFields = new AllAccessFields();
+
+ // reverse public and protected order
+ public String accessIntFields() {
+ return String.valueOf(allAccessFields.publicInt
+ + allAccessFields.packagePrivateInt
+ + allAccessFields.protectedInt);
+ }
+
+ // reverse public and protected order
+ public String accessStringFields() {
+ return String.valueOf(allAccessFields.publicString
+ + allAccessFields.packagePrivateString
+ + allAccessFields.protectedString);
+ }
+
+ // reverse public and protected order
+ public String accessArrayFields() {
+ ArrayList<String> values = new ArrayList<String>();
+ for (int i : allAccessFields.publicIntArray) {
+ values.add(String.valueOf(i));
+ }
+ for (int i : allAccessFields.packagePrivateIntArray) {
+ values.add(String.valueOf(i));
+ }
+ for (int i : allAccessFields.protectedIntArray) {
+ values.add(String.valueOf(i));
+ }
+ return Joiner.on(",").join(values);
+ }
+
+ // reverse public and protected order
+ public String accessArrayOfStringFields() {
+ ArrayList<String> values = new ArrayList<String>();
+ for (String s : allAccessFields.publicStringArray) {
+ values.add(s);
+ }
+ for (String s : allAccessFields.packagePrivateStringArray) {
+ values.add(s);
+ }
+ for (String s : allAccessFields.protectedStringArray) {
+ values.add(s);
+ }
+ return Joiner.on(",").join(values);
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PackagePrivateInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PackagePrivateInvoker.java
new file mode 100644
index 0000000..36f03b4
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PackagePrivateInvoker.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+/**
+ * Class that uses package private methods and fields from another object.
+ */
+public class PackagePrivateInvoker {
+
+ public String createPackagePrivateObject() {
+ PackagePrivateClass packagePrivateClass = new PackagePrivateClass("foo");
+ return packagePrivateClass.getStringValue() + "bar";
+ }
+
+ public String invokeTernaryOperator(boolean select) {
+ Provider<String> provider = select
+ ? new PackagePrivateClass("patched_package_private")
+ : new AnotherPackagePrivateClass<String>("patched_another_package_private");
+ return provider.getValue();
+ }
+
+ public String ternaryOperatorInConstructorParams(boolean select) {
+ return new AnotherPackagePrivateClass<String>(!select ? "true" : "false").getValue();
+ }
+
+ public String invokePackagePrivateInterface() {
+ return "patched_"
+ + new PackagePrivateClass("random").getPackagePrivateInterface().getValue();
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/ParentInvocation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/ParentInvocation.java
new file mode 100644
index 0000000..bf32eb1
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/ParentInvocation.java
@@ -0,0 +1,77 @@
+/*
+ * 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.example.basic;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Tests related to invoking parent methods from a subclass.
+ */
+public class ParentInvocation extends AllAccessMethods {
+
+ public String childMethod(double a, String b, int c) {
+ return "patched_child_method:" + a + b + c;
+ }
+
+ @Override
+ protected String protectedMethod(double a, String b, int c) {
+ return super.protectedMethod(a, b, c) + "_child";
+ }
+
+ @Override
+ String packagePrivateMethod(double a, String b, int c) {
+ return super.packagePrivateMethod(a, b, c) + "_child";
+ }
+
+ @Override
+ public String publicStringMethod(double a, String b, int c) {
+ return super.publicStringMethod(a, b, c) + "_child";
+ }
+
+ @Override
+ public String abstractMethod(double a, String b, int c) {
+ return "abstract_patched: " + a + b + c;
+ }
+
+ public List<String> invokeAllFromSubclass(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(packagePrivateMethod(a, b, c));
+ builder.add(protectedMethod(a, b, c));
+ builder.add(publicStringMethod(a, b, c));
+ builder.add(abstractMethod(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeAllParent(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(super.packagePrivateMethod(a, b, c));
+ builder.add(super.protectedMethod(a, b, c));
+ builder.add(super.publicStringMethod(a, b, c));
+ return builder.build();
+ }
+
+ public List<String> invokeDoNoOverrideMethodsDirectly(double a, String b, int c) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ //TODO allow calling supers that don't exist on the child class.
+ //builder.add(super.doNotOverridePackagePrivateMethodDispatch(a, b, c));
+ //builder.add(super.doNotOverrideProtectedMethodDispatch(a, b, c));
+ //builder.add(super.doNotOverridePublicMethodDispatch(a, b, c));
+ return builder.build();
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PublicMethodInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PublicMethodInvoker.java
new file mode 100644
index 0000000..bc6fea7
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/PublicMethodInvoker.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+import com.google.common.base.Joiner;
+import com.google.common.primitives.Booleans;
+import com.google.common.primitives.Chars;
+import com.google.common.primitives.Doubles;
+import com.google.common.primitives.Floats;
+import com.google.common.primitives.Ints;
+import com.google.common.primitives.Longs;
+
+/**
+ * Invoke public methods on some other objects.
+ */
+public class PublicMethodInvoker {
+
+ private final int myField;
+
+ public PublicMethodInvoker(int myField) {
+ this.myField = 2*myField;
+ }
+
+ // add tests for all types of parameters and return types.
+ public String invokeAllPublicMethods(AllAccessMethods allAccessMethods) {
+ StringBuilder result = new StringBuilder();
+ result.append(allAccessMethods.publicStringMethod(1d, "invoker_reloaded", 12));
+ result.append('-');
+ result.append(allAccessMethods.publicIntMethod(1));
+ result.append('-');
+ result.append(allAccessMethods.publicLongMethod(3));
+ result.append('-');
+ result.append(allAccessMethods.publicFloatMethod(12f));
+ result.append('-');
+ result.append(allAccessMethods.publicBooleanMethod(false));
+ result.append('-');
+ result.append(allAccessMethods.publicDoubleMethod(24.12d));
+ result.append('-');
+ result.append(allAccessMethods.publicCharMethod('c'));
+ result.append('-');
+ result.append(myField);
+ allAccessMethods.voidMethod();
+ return result.toString();
+ }
+
+
+ public String invokeAllPublicArrayMethods(AllAccessMethods allAccessMethods) {
+ StringBuilder result = new StringBuilder();
+ result.append(Joiner.on(':').join(
+ allAccessMethods.publicStringArrayMethod(new String[] {"invoker"})));
+ result.append('-');
+ result.append(Joiner.on(':').join(
+ Ints.asList(allAccessMethods.publicIntArrayMethod(new int[] {5}))));
+ result.append('-');
+ result.append(Joiner.on(':').join(
+ Longs.asList(allAccessMethods.publicLongArrayMethod(new long[] {3}))));
+ result.append('-');
+ result.append(Joiner.on(':').join(
+ Chars.asList(allAccessMethods.publicCharArrayMethod(new char[] {'a'}))));
+ result.append('-');
+ result.append(Joiner.on(':').join(
+ Booleans.asList(allAccessMethods.publicBooleanArrayMethod(new boolean[] {false}))));
+ result.append('-');
+ result.append(Joiner.on(':').join(
+ Floats.asList(allAccessMethods.publicFloatArrayMethod(new float[] {12f}))));
+ result.append('-');
+ result.append(Joiner.on(':').join(
+ Doubles.asList(allAccessMethods.publicDoubleArrayMethod(new double[] {56d}))));
+ result.append('-');
+ result.append(myField);
+ allAccessMethods.voidMethod();
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/ReflectiveUser.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/ReflectiveUser.java
new file mode 100644
index 0000000..948513b
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/ReflectiveUser.java
@@ -0,0 +1,56 @@
+/*
+ * 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.example.basic;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * reflective API user.
+ */
+public class ReflectiveUser {
+
+ public String value;
+
+ public ReflectiveUser() {
+ }
+
+ public ReflectiveUser(String useReflection)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method hiddenGem = getClass().getDeclaredMethod("hiddenGem", String.class);
+ value = (String) hiddenGem.invoke(this, "desserts");
+ }
+
+ public boolean amIWhoIThinkIam() {
+ return getClass().equals(ReflectiveUser.class);
+ }
+
+ // this should never be invoked as the use of reflection is blacklisted.
+ public String useReflectionOnPublicMethod()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method hiddenGem = getClass().getDeclaredMethod("hiddenGem", String.class);
+ return (String) hiddenGem.invoke(this, "desserts");
+ }
+
+ public String hiddenGem(String param) {
+ return new StringBuilder(param).reverse().toString();
+ }
+
+ public String noReflectionUse() {
+ return hiddenGem("desserts");
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/StaticMethodsInvoker.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/StaticMethodsInvoker.java
new file mode 100644
index 0000000..a161e42
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/changeSubClass/com/example/basic/StaticMethodsInvoker.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.basic;
+
+import com.google.common.base.Joiner;
+
+/**
+ * Invoker of static methods on itself or on others.
+ */
+public class StaticMethodsInvoker {
+
+ public int invokeAllStaticIntMethods() {
+ return (AllAccessStaticMethods.getPackagePrivateStaticInt()
+ + AllAccessStaticMethods.getPublicStaticInt())
+ / AllAccessStaticMethods.getProtectedStaticInt();
+ }
+
+ public String invokeAllStaticStringMethods() {
+ return Joiner.on(',').join(AllAccessStaticMethods.getPublicStaticString(),
+ AllAccessStaticMethods.getProtectedStaticString(),
+ AllAccessStaticMethods.getPackagePrivateStaticString());
+ }
+
+ public double invokeAllStaticDoubleArrayMethods() {
+ double toReturn = addAll(AllAccessStaticMethods.getPackagePrivateStaticDoubles());
+ toReturn -= addAll(AllAccessStaticMethods.getProtectedStaticDoubles());
+ toReturn += addAll(AllAccessStaticMethods.getPublicStaticDoubles());
+ return toReturn;
+ }
+
+ public String invokeAllStaticStringArrayMethods() {
+ return Joiner.on(':').join(
+ Joiner.on(',').join(AllAccessStaticMethods.getProtectedStaticStrings()),
+ Joiner.on(',').join(AllAccessStaticMethods.getPackagePrivateStrings()),
+ Joiner.on(",").join(AllAccessStaticMethods.getPublicStaticStrings()));
+ }
+
+ private double addAll(double[] doubles) {
+ double accumulator = 0;
+ if (doubles!=null) {
+ for (double aDouble : doubles) {
+ accumulator += aDouble;
+ }
+ }
+ return accumulator;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/lineChangingVerifier/com/verifier/tests/UnchangedClassInitializer1.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/lineChangingVerifier/com/verifier/tests/UnchangedClassInitializer1.java
new file mode 100644
index 0000000..9a2fa9f
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/lineChangingVerifier/com/verifier/tests/UnchangedClassInitializer1.java
@@ -0,0 +1,37 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test that an unchanged class initializer is not flagged by the verifier.
+ */
+public class UnchangedClassInitializer1 {
+
+ static final int VALUE;
+
+ int someMethodToChangeStaticInitializerLineNumbers(int i) {
+ int k = 0;
+ for (int j=0; j<i; j++) {
+ k+=j;
+ }
+ return k;
+ }
+
+ static {
+ VALUE = 40;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/ChangeInstanceFieldToStatic.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/ChangeInstanceFieldToStatic.java
new file mode 100644
index 0000000..61d22ee
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/ChangeInstanceFieldToStatic.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that will test changing an instance field into a static will be flagged by the verifier.
+ */
+public class ChangeInstanceFieldToStatic {
+
+ private static String someField;
+
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddClassAnnotation.java
new file mode 100644
index 0000000..e2bb241
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddClassAnnotation.java
@@ -0,0 +1,28 @@
+/*
+ * 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.verifier.tests;
+
+import java.lang.Deprecated;
+
+/**
+ * Class that will test adding a class annotation is flagged by the verifier.
+ */
+ at Deprecated
+public class AddClassAnnotation {
+
+ public void someMethod() {}
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddInstanceField.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddInstanceField.java
new file mode 100644
index 0000000..50d0ff1
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddInstanceField.java
@@ -0,0 +1,27 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test adding a field is flagged by the verifier.
+ */
+public class AddInstanceField {
+
+ private int intField;
+
+ void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddInterfaceImplementation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddInterfaceImplementation.java
new file mode 100644
index 0000000..23b833f
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddInterfaceImplementation.java
@@ -0,0 +1,28 @@
+/*
+ * 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.verifier.tests;
+
+import java.io.Serializable;
+
+/**
+ * Class that test adding an interface to the implemented list of interfaces of a class is flagged
+ * by the verifier.
+ */
+public class AddInterfaceImplementation implements Serializable {
+
+ public void method(){}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddMethodAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddMethodAnnotation.java
new file mode 100644
index 0000000..aad68fe
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddMethodAnnotation.java
@@ -0,0 +1,28 @@
+/*
+ * 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.verifier.tests;
+
+import java.lang.Deprecated;
+
+/**
+ * Class that test adding a method annotation is flagged by verifier.
+ */
+public class AddMethodAnnotation {
+
+ @Deprecated
+ public void method() {}
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddNotRuntimeClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddNotRuntimeClassAnnotation.java
new file mode 100644
index 0000000..fe9e787
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/AddNotRuntimeClassAnnotation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that test adding an annotation not visible at runtime does not trigger a verifier flag.
+ */
+ at SuppressWarnings("any")
+public class AddNotRuntimeClassAnnotation {
+
+ void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeFieldType.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeFieldType.java
new file mode 100644
index 0000000..6d69407
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeFieldType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test changing a field type is flagged by the verifier.
+ */
+public class ChangeFieldType {
+
+ public Object someField;
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeInstanceFieldVisibility.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeInstanceFieldVisibility.java
new file mode 100644
index 0000000..eaba658
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeInstanceFieldVisibility.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test change an instance field visibility is flagged by the verifier.
+ */
+public class ChangeInstanceFieldVisibility {
+
+ private String someField;
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeStaticFieldToInstance.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeStaticFieldToInstance.java
new file mode 100644
index 0000000..61b9b25
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeStaticFieldToInstance.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+/**
+ * Class that checks that changing a static field into an instance field is flagged by the verifier.
+ */
+public class ChangeStaticFieldToInstance {
+
+ public double someDouble;
+
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeStaticFieldVisibility.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeStaticFieldVisibility.java
new file mode 100644
index 0000000..aa3fdc9
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeStaticFieldVisibility.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test changing a static field visibility is flagged by the verifier.
+ */
+public class ChangeStaticFieldVisibility {
+
+ private static String someField;
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeSuperClass.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeSuperClass.java
new file mode 100644
index 0000000..1cc6d9d
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangeSuperClass.java
@@ -0,0 +1,29 @@
+/*
+ * 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.verifier.tests;
+
+import java.util.HashSet;
+
+/**
+ * Patch version that changes the super class of this class.
+ */
+public class ChangeSuperClass extends HashSet<String> {
+
+ public String getStringField() {
+ return "orphan!";
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer1.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer1.java
new file mode 100644
index 0000000..c8806ef
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer1.java
@@ -0,0 +1,29 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that changes its class initializer
+ */
+public class ChangedClassInitializer1 {
+
+ static final int VALUE;
+
+ static {
+ VALUE = 5;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer2.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer2.java
new file mode 100644
index 0000000..3385434
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer2.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that changes its class initializer
+ */
+public class ChangedClassInitializer2 {
+
+ static int VALUE = 5;
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer3.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer3.java
new file mode 100644
index 0000000..129c03d
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ChangedClassInitializer3.java
@@ -0,0 +1,35 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that changes its class initializer
+ */
+public class ChangedClassInitializer3 {
+
+ static int VALUE;
+
+ static void setStaticValue() throws RuntimeException {
+ VALUE = 4;
+ }
+
+ static {
+ try {
+ setStaticValue();
+ } catch(Exception e) {}
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/DisabledClassChanging.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/DisabledClassChanging.java
new file mode 100644
index 0000000..42e6c36
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/DisabledClassChanging.java
@@ -0,0 +1,35 @@
+/*
+ * 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.verifier.tests;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Verifier test that shows that an InstantRun disabled class changes are flagged by the
+ * verifier.
+ */
+ at DisableInstantRun
+public class DisabledClassChanging {
+
+ public String getString() {
+ return "bar" + getFloat();
+ }
+
+ public Float getFloat() {
+ return 12f;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/DisabledMethodChanging.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/DisabledMethodChanging.java
new file mode 100644
index 0000000..457868f
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/DisabledMethodChanging.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.verifier.tests;
+
+import com.android.tools.ir.api.DisableInstantRun;
+
+/**
+ * Classes with changing InstantRun methods should be flagged by the verifier.
+ */
+public class DisabledMethodChanging {
+
+ @DisableInstantRun
+ public String disabledMethod() {
+ return "foo" + getFloat();
+ }
+
+ public Float getFloat() {
+ return 12f;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/MethodAddedClass.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/MethodAddedClass.java
new file mode 100644
index 0000000..86fadb5
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/MethodAddedClass.java
@@ -0,0 +1,33 @@
+/*
+ * 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.verifier.tests;
+
+import java.lang.RuntimeException;
+
+/**
+ * Patched version which simulates adding a method.
+ */
+public class MethodAddedClass {
+
+ public String getValue() {
+ return "value";
+ }
+
+ public void random() {
+ throw new RuntimeException("don't call !");
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/MethodCollisionClass.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/MethodCollisionClass.java
new file mode 100644
index 0000000..7469238
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/MethodCollisionClass.java
@@ -0,0 +1,29 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that defines overloaded methods.
+ */
+public class MethodCollisionClass {
+
+ void method(int i) {}
+
+ void method(Object o) {}
+
+ static void method(float f) {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/NewInstanceReflectionUser.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/NewInstanceReflectionUser.java
new file mode 100644
index 0000000..4c8fa74
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/NewInstanceReflectionUser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that use reflection APIs.
+ */
+public class NewInstanceReflectionUser {
+
+ public void method() {
+ try {
+ Float.class.newInstance();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/R.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/R.java
new file mode 100644
index 0000000..3114110
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/R.java
@@ -0,0 +1,24 @@
+/*
+ * 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.verifier.tests;
+
+public final class R {
+ public static final class id {
+ public static final int text = 7001;
+ public static final int blob = 7005;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ReflectiveUserNotChanging.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ReflectiveUserNotChanging.java
new file mode 100644
index 0000000..6ec7184
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/ReflectiveUserNotChanging.java
@@ -0,0 +1,36 @@
+/*
+ * 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.verifier.tests;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Reflective API user class that will only change its authorized API.
+ */
+public class ReflectiveUserNotChanging {
+
+ public String useReflectionOnPublicMethod()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method hiddenGem = getClass().getDeclaredMethod("hiddenGem", String.class);
+ return (String) hiddenGem.invoke(this, "stressed");
+ }
+
+ public String hiddenGem(String param) {
+ return param;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveClassAnnotation.java
new file mode 100644
index 0000000..6630be7
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveClassAnnotation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test removing a class annotation is flagged by the verifier.
+ */
+public class RemoveClassAnnotation {
+
+ public void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveInterfaceImplementation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveInterfaceImplementation.java
new file mode 100644
index 0000000..984cb45
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveInterfaceImplementation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test that removing an implemented interface from a class is flagged by the verifier.
+ */
+public class RemoveInterfaceImplementation {
+
+ public void method() {}
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveMethodAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveMethodAnnotation.java
new file mode 100644
index 0000000..fa5f94c
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveMethodAnnotation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test removing a method annotation is flagged by the verifier.
+ */
+public class RemoveMethodAnnotation {
+
+ public void method() {}
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveNotRuntimeClassAnnotation.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveNotRuntimeClassAnnotation.java
new file mode 100644
index 0000000..4624f46
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/RemoveNotRuntimeClassAnnotation.java
@@ -0,0 +1,27 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test that removing a non runtime visible class annotation is not flagged by the
+ * verifier
+ */
+ at SuppressWarnings("any")
+public class RemoveNotRuntimeClassAnnotation {
+
+ void method() {}
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/UnchangedClass.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/UnchangedClass.java
new file mode 100644
index 0000000..ec4c956
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/UnchangedClass.java
@@ -0,0 +1,29 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class which should always pass verification.
+ */
+public class UnchangedClass {
+
+ private String stringField;
+
+ public String getStringField() {
+ return stringField;
+ }
+}
diff --git a/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/UnchangedClassInitializer1.java b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/UnchangedClassInitializer1.java
new file mode 100644
index 0000000..820ffd3
--- /dev/null
+++ b/build-system/gradle-core/src/test/incremental-test-classes/patches/verifier/com/verifier/tests/UnchangedClassInitializer1.java
@@ -0,0 +1,33 @@
+/*
+ * 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.verifier.tests;
+
+/**
+ * Class that test that an unchanged class initializer is not flagged by the verifier.
+ */
+public class UnchangedClassInitializer1 {
+
+ static final int VALUE;
+
+ int someMethodToChangeStaticInitializerLineNumbers(int i) {
+ return i*2;
+ }
+
+ static {
+ VALUE = 40;
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/CompileOptionsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/CompileOptionsTest.java
new file mode 100644
index 0000000..03c321a
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/CompileOptionsTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.build.gradle.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import org.gradle.api.JavaVersion;
+import org.junit.Test;
+
+/**
+ * Tests for {@link CompileOptions}
+ */
+public class CompileOptionsTest {
+
+ @Test
+ public void sourceCompatibilityTest() {
+ CompileOptions options = new CompileOptions();
+
+ assertEquals(options.defaultJavaVersion, options.getSourceCompatibility());
+
+ options.setSourceCompatibility("1.6");
+ assertEquals(JavaVersion.VERSION_1_6, options.getSourceCompatibility());
+
+ options.setSourceCompatibility(1.6);
+ assertEquals(JavaVersion.VERSION_1_6, options.getSourceCompatibility());
+
+ options.setSourceCompatibility(JavaVersion.VERSION_1_7);
+ assertEquals(JavaVersion.VERSION_1_7, options.getSourceCompatibility());
+
+ options.setSourceCompatibility("Version_1_7");
+ assertEquals(JavaVersion.VERSION_1_7, options.getSourceCompatibility());
+
+ options.setSourceCompatibility("VERSION_1_7");
+ assertEquals(JavaVersion.VERSION_1_7, options.getSourceCompatibility());
+ }
+
+ @Test
+ public void targetCompatibilityTest() {
+ CompileOptions options = new CompileOptions();
+
+ assertEquals(options.defaultJavaVersion, options.getTargetCompatibility());
+
+ options.setTargetCompatibility("1.6");
+ assertEquals(JavaVersion.VERSION_1_6, options.getTargetCompatibility());
+
+ options.setTargetCompatibility(1.6);
+ assertEquals(JavaVersion.VERSION_1_6, options.getTargetCompatibility());
+
+ options.setTargetCompatibility(JavaVersion.VERSION_1_7);
+ assertEquals(JavaVersion.VERSION_1_7, options.getTargetCompatibility());
+
+ options.setTargetCompatibility("Version_1_7");
+ assertEquals(JavaVersion.VERSION_1_7, options.getTargetCompatibility());
+
+ options.setTargetCompatibility("VERSION_1_7");
+ assertEquals(JavaVersion.VERSION_1_7, options.getTargetCompatibility());
+ }
+}
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/DependencyManagerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/DependencyManagerTest.java
similarity index 100%
rename from base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/DependencyManagerTest.java
rename to build-system/gradle-core/src/test/java/com/android/build/gradle/internal/DependencyManagerTest.java
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/ProductFlavorComboTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/ProductFlavorComboTest.java
similarity index 100%
rename from base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/ProductFlavorComboTest.java
rename to build-system/gradle-core/src/test/java/com/android/build/gradle/internal/ProductFlavorComboTest.java
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/dsl/AbiSplitOptionsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/AbiSplitOptionsTest.java
similarity index 100%
rename from base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/dsl/AbiSplitOptionsTest.java
rename to build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/AbiSplitOptionsTest.java
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptionsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/DensitySplitOptionsTest.java
similarity index 100%
rename from base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/dsl/DensitySplitOptionsTest.java
rename to build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/DensitySplitOptionsTest.java
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/dsl/SigningConfigTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/SigningConfigTest.java
similarity index 100%
rename from base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/dsl/SigningConfigTest.java
rename to build-system/gradle-core/src/test/java/com/android/build/gradle/internal/dsl/SigningConfigTest.java
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/ByteCodeUtilsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/ByteCodeUtilsTest.java
new file mode 100644
index 0000000..080e2ac
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/ByteCodeUtilsTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+
+/**
+ * Tests for the {@link ByteCodeUtils} class.
+ */
+ at RunWith(MockitoJUnitRunner.class)
+public class ByteCodeUtilsTest {
+
+ @Mock
+ GeneratorAdapter generator;
+
+ @Test
+ public void testShortUnbox() {
+ ByteCodeUtils.unbox(generator, Type.SHORT_TYPE);
+ verify(generator, times(1)).checkCast(Type.getObjectType("java/lang/Number"));
+ verify(generator, times(1)).invokeVirtual(Type.getObjectType("java/lang/Number"),
+ Method.getMethod("short shortValue()"));
+ }
+
+ @Test
+ public void testByteUnbox() {
+ ByteCodeUtils.unbox(generator, Type.BYTE_TYPE);
+ verify(generator, times(1)).checkCast(Type.getObjectType("java/lang/Number"));
+ verify(generator, times(1)).invokeVirtual(Type.getObjectType("java/lang/Number"),
+ Method.getMethod("byte byteValue()"));
+ }
+
+ @Test
+ public void testIntUnbox() {
+ ByteCodeUtils.unbox(generator, Type.INT_TYPE);
+ verify(generator, times(1)).unbox(Type.INT_TYPE);
+ }
+
+ @Test
+ public void testObjectUnbox() {
+ ByteCodeUtils.unbox(generator, Type.getType(String.class));
+ verify(generator, times(1)).unbox(Type.getType(String.class));
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunBuildContextTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunBuildContextTest.java
new file mode 100644
index 0000000..70bbe8c
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunBuildContextTest.java
@@ -0,0 +1,546 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext.Build;
+import com.android.sdklib.AndroidVersion;
+import com.android.utils.FileUtils;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link InstantRunBuildContext}
+ */
+public class InstantRunBuildContextTest {
+
+ @Test
+ public void testTaskDurationRecording() throws ParserConfigurationException {
+ InstantRunBuildContext instantRunBuildContext = new InstantRunBuildContext();
+ instantRunBuildContext.startRecording(InstantRunBuildContext.TaskType.VERIFIER);
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ assertThat(instantRunBuildContext.stopRecording(InstantRunBuildContext.TaskType.VERIFIER))
+ .isAtLeast(1L);
+ assertThat(instantRunBuildContext.getBuildId()).isNotEqualTo(
+ new InstantRunBuildContext().getBuildId());
+ }
+
+ @Test
+ public void testPersistenceFromCleanState() throws ParserConfigurationException {
+ InstantRunBuildContext instantRunBuildContext = new InstantRunBuildContext();
+ String persistedState = instantRunBuildContext.toXml();
+ assertThat(persistedState).isNotEmpty();
+ assertThat(persistedState).contains(InstantRunBuildContext.ATTR_TIMESTAMP);
+ }
+
+ @Test
+ public void testFormatPresence() throws ParserConfigurationException {
+ InstantRunBuildContext instantRunBuildContext = new InstantRunBuildContext();
+ String persistedState = instantRunBuildContext.toXml();
+ assertThat(persistedState).isNotEmpty();
+ assertThat(persistedState).contains(InstantRunBuildContext.ATTR_FORMAT
+ + "=\"" + InstantRunBuildContext.CURRENT_FORMAT + "\"");
+ }
+
+ @Test
+ public void testDuplicateEntries() throws ParserConfigurationException, IOException {
+ InstantRunBuildContext context = new InstantRunBuildContext();
+ context.setApiLevel(new AndroidVersion(21, null /* codeName */),
+ ColdswapMode.MULTIDEX.name(), null /* targetArchitecture */);
+ context.addChangedFile(
+ InstantRunBuildContext.FileType.DEX, new File("/tmp/dependencies.dex"));
+ context.addChangedFile(
+ InstantRunBuildContext.FileType.DEX, new File("/tmp/dependencies.dex"));
+ context.close();
+ Build build = context.getPreviousBuilds().iterator().next();
+ assertThat(build.getArtifacts()).hasSize(1);
+ assertThat(build.getArtifacts().get(0).getType()).isEqualTo(
+ InstantRunBuildContext.FileType.DEX);
+ }
+
+ @Test
+ public void testLoadingFromCleanState()
+ throws ParserConfigurationException, SAXException, IOException {
+ InstantRunBuildContext instantRunBuildContext = new InstantRunBuildContext();
+ File file = new File("/path/to/non/existing/file");
+ instantRunBuildContext.loadFromXmlFile(file);
+ assertThat(instantRunBuildContext.getBuildId()).isAtLeast(1L);
+ }
+
+ @Test
+ public void testLoadingFromPreviousState()
+ throws IOException, ParserConfigurationException, SAXException {
+ File tmpFile = createMarkedBuildInfo();
+
+ InstantRunBuildContext newContext = new InstantRunBuildContext();
+ newContext.loadFromXmlFile(tmpFile);
+ String xml = newContext.toXml();
+ assertThat(xml).contains(InstantRunBuildContext.ATTR_TIMESTAMP);
+ }
+
+ @Test
+ public void testPersistingAndLoadingPastBuilds()
+ throws IOException, ParserConfigurationException, SAXException {
+ InstantRunBuildContext instantRunBuildContext = new InstantRunBuildContext();
+ File buildInfo = createBuildInfo(instantRunBuildContext);
+ instantRunBuildContext = new InstantRunBuildContext();
+ instantRunBuildContext.loadFromXmlFile(buildInfo);
+ assertThat(instantRunBuildContext.getPreviousBuilds()).hasSize(1);
+ saveBuildInfo(instantRunBuildContext, buildInfo);
+
+ instantRunBuildContext = new InstantRunBuildContext();
+ instantRunBuildContext.loadFromXmlFile(buildInfo);
+ assertThat(instantRunBuildContext.getPreviousBuilds()).hasSize(2);
+ }
+
+ @Test
+ public void testXmlFormat() throws ParserConfigurationException, IOException, SAXException {
+ InstantRunBuildContext first = new InstantRunBuildContext();
+ first.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ first.setDensity("xxxhdpi");
+ first.addChangedFile(InstantRunBuildContext.FileType.MAIN, new File("main.apk"));
+ first.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("split.apk"));
+ String buildInfo = first.toXml();
+
+ InstantRunBuildContext second = new InstantRunBuildContext();
+ second.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ second.setDensity("xhdpi");
+ second.loadFromXml(buildInfo);
+ second.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("other.apk"));
+ second.addChangedFile(InstantRunBuildContext.FileType.RELOAD_DEX, new File("reload.dex"));
+ buildInfo = second.toXml();
+
+ Document document = XmlUtils.parseDocument(buildInfo, false);
+ Element instantRun = (Element) document.getFirstChild();
+ assertThat(instantRun.getTagName()).isEqualTo("instant-run");
+ assertThat(instantRun.getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP)).isEqualTo(
+ String.valueOf(second.getBuildId()));
+ assertThat(instantRun.getAttribute(InstantRunBuildContext.ATTR_DENSITY)).isEqualTo("xhdpi");
+
+ // check the most recent build (called second) records :
+ List<Element> secondArtifacts = getElementsByName(instantRun,
+ InstantRunBuildContext.TAG_ARTIFACT);
+ assertThat(secondArtifacts).hasSize(3);
+ assertThat(secondArtifacts.get(0).getAttribute(InstantRunBuildContext.ATTR_TYPE))
+ .isEqualTo("SPLIT_MAIN");
+ assertThat(secondArtifacts.get(0).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .endsWith("main.apk");
+ assertThat(secondArtifacts.get(1).getAttribute(InstantRunBuildContext.ATTR_TYPE))
+ .isEqualTo("SPLIT");
+ assertThat(secondArtifacts.get(1).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .endsWith("other.apk");
+ assertThat(secondArtifacts.get(2).getAttribute(InstantRunBuildContext.ATTR_TYPE))
+ .isEqualTo("RELOAD_DEX");
+ assertThat(secondArtifacts.get(2).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .endsWith("reload.dex");
+
+ boolean foundFirst = false;
+ NodeList childNodes = instantRun.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node item = childNodes.item(i);
+ if (item.getNodeName().equals(InstantRunBuildContext.TAG_BUILD)) {
+ // there should be one build child with first build references.
+ foundFirst = true;
+ assertThat(((Element) item).getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP))
+ .isEqualTo(
+ String.valueOf(first.getBuildId()));
+ List<Element> firstArtifacts = getElementsByName(item,
+ InstantRunBuildContext.TAG_ARTIFACT);
+ assertThat(firstArtifacts).hasSize(2);
+ assertThat(firstArtifacts.get(0).getAttribute(InstantRunBuildContext.ATTR_TYPE))
+ .isEqualTo("SPLIT_MAIN");
+ assertThat(firstArtifacts.get(0).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .endsWith("main.apk");
+ assertThat(firstArtifacts.get(1).getAttribute(InstantRunBuildContext.ATTR_TYPE))
+ .isEqualTo("SPLIT");
+ assertThat(firstArtifacts.get(1).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .endsWith("split.apk");
+ }
+ }
+ assertThat(foundFirst).isTrue();
+ }
+
+ @Test
+ public void testArtifactsPersistence()
+ throws IOException, ParserConfigurationException, SAXException {
+ InstantRunBuildContext instantRunBuildContext = new InstantRunBuildContext();
+ instantRunBuildContext.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ instantRunBuildContext.addChangedFile(InstantRunBuildContext.FileType.MAIN,
+ new File("main.apk"));
+ instantRunBuildContext.addChangedFile(InstantRunBuildContext.FileType.SPLIT,
+ new File("split.apk"));
+ String buildInfo = instantRunBuildContext.toXml();
+
+ // check xml format, the IDE depends on it.
+ instantRunBuildContext = new InstantRunBuildContext();
+ instantRunBuildContext.loadFromXml(buildInfo);
+ assertThat(instantRunBuildContext.getPreviousBuilds()).hasSize(1);
+ Build build = instantRunBuildContext.getPreviousBuilds().iterator().next();
+
+ assertThat(build.getArtifacts()).hasSize(2);
+ assertThat(build.getArtifacts().get(0).getType()).isEqualTo(
+ InstantRunBuildContext.FileType.SPLIT_MAIN);
+ assertThat(build.getArtifacts().get(1).getType()).isEqualTo(
+ InstantRunBuildContext.FileType.SPLIT);
+ }
+
+ @Test
+ public void testOldReloadPurge()
+ throws ParserConfigurationException, IOException, SAXException {
+ InstantRunBuildContext initial = new InstantRunBuildContext();
+ initial.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ null /* coldswapMode */, null /* targetArchitecture */);
+ initial.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-0.apk"));
+ initial.close();
+ String buildInfo = initial.toXml();
+
+ InstantRunBuildContext first = new InstantRunBuildContext();
+ first.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ null /* coldswapMode */, null /* targetArchitecture */);
+ first.loadFromXml(buildInfo);
+ first.addChangedFile(InstantRunBuildContext.FileType.RELOAD_DEX,
+ new File("reload.dex"));
+ first.setVerifierResult(InstantRunVerifierStatus.COMPATIBLE);
+ first.close();
+ buildInfo = first.toXml();
+
+ InstantRunBuildContext second = new InstantRunBuildContext();
+ second.loadFromXml(buildInfo);
+ second.setApiLevel(new AndroidVersion(23, null),
+ null /* coldswapMode */, null /* targetArchitecture */);
+ second.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("split.apk"));
+ second.setVerifierResult(InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE);
+
+ second.close();
+ buildInfo = second.toXml();
+ Document document = XmlUtils.parseDocument(buildInfo, false /* namespaceAware */);
+
+ List<Element> builds = getElementsByName(document.getFirstChild(),
+ InstantRunBuildContext.TAG_BUILD);
+ // initial is never purged.
+ assertThat(builds).hasSize(2);
+ assertThat(builds.get(1).getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP)).isEqualTo(
+ String.valueOf(second.getBuildId()));
+ }
+
+ @Test
+ public void testMultipleReloadCollapse()
+ throws ParserConfigurationException, IOException, SAXException {
+ InstantRunBuildContext initial = new InstantRunBuildContext();
+ initial.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ initial.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-0.apk"));
+ initial.close();
+ String buildInfo = initial.toXml();
+
+ InstantRunBuildContext first = new InstantRunBuildContext();
+ first.loadFromXml(buildInfo);
+ first.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ first.addChangedFile(InstantRunBuildContext.FileType.RELOAD_DEX,
+ new File("reload.dex"));
+ first.setVerifierResult(InstantRunVerifierStatus.COMPATIBLE);
+ first.close();
+ buildInfo = first.toXml();
+
+ InstantRunBuildContext second = new InstantRunBuildContext();
+ second.loadFromXml(buildInfo);
+ second.setApiLevel(new AndroidVersion(23, null),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ second.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("split.apk"));
+ second.setVerifierResult(InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE);
+
+ second.close();
+ buildInfo = second.toXml();
+
+ InstantRunBuildContext third = new InstantRunBuildContext();
+ third.loadFromXml(buildInfo);
+ third.setApiLevel(new AndroidVersion(23, null),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ third.addChangedFile(InstantRunBuildContext.FileType.RESOURCES,
+ new File("resources-debug.ap_"));
+ third.addChangedFile(InstantRunBuildContext.FileType.RELOAD_DEX, new File("reload.dex"));
+ third.setVerifierResult(InstantRunVerifierStatus.COMPATIBLE);
+
+ third.close();
+ buildInfo = third.toXml();
+
+ InstantRunBuildContext fourth = new InstantRunBuildContext();
+ fourth.loadFromXml(buildInfo);
+ fourth.setApiLevel(new AndroidVersion(23, null),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ fourth.addChangedFile(InstantRunBuildContext.FileType.RESOURCES,
+ new File("resources-debug.ap_"));
+ fourth.setVerifierResult(InstantRunVerifierStatus.COMPATIBLE);
+ fourth.close();
+ buildInfo = fourth.toXml();
+
+ Document document = XmlUtils.parseDocument(buildInfo, false /* namespaceAware */);
+
+ List<Element> builds = getElementsByName(document.getFirstChild(),
+ InstantRunBuildContext.TAG_BUILD);
+ // first build should have been removed due to the coldswap presence.
+ assertThat(builds).hasSize(4);
+ assertThat(builds.get(1).getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP)).isEqualTo(
+ String.valueOf(second.getBuildId()));
+ assertThat(builds.get(2).getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP)).isEqualTo(
+ String.valueOf(third.getBuildId()));
+ assertThat(getElementsByName(builds.get(2), InstantRunBuildContext.TAG_ARTIFACT))
+ .named("Superseded resources.ap_ artifact should be removed.")
+ .hasSize(1);
+
+ }
+
+ @Test
+ public void testOverlappingAndEmptyChanges()
+ throws ParserConfigurationException, IOException, SAXException {
+ InstantRunBuildContext initial = new InstantRunBuildContext();
+ initial.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ initial.addChangedFile(InstantRunBuildContext.FileType.MAIN, new File("/tmp/main.apk"));
+ initial.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-0.apk"));
+ initial.close();
+ String buildInfo = initial.toXml();
+
+ InstantRunBuildContext first = new InstantRunBuildContext();
+ first.setApiLevel(new AndroidVersion(23, null /* codeName */),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ first.loadFromXml(buildInfo);
+ first.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-1.apk"));
+ first.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-2.apk"));
+ first.setVerifierResult(InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE);
+ first.close();
+ buildInfo = first.toXml();
+
+ InstantRunBuildContext second = new InstantRunBuildContext();
+ second.loadFromXml(buildInfo);
+ second.setApiLevel(new AndroidVersion(23, null),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ second.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-2.apk"));
+ second.setVerifierResult(InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE);
+ second.close();
+ buildInfo = second.toXml();
+
+ InstantRunBuildContext third = new InstantRunBuildContext();
+ third.loadFromXml(buildInfo);
+ third.setApiLevel(new AndroidVersion(23, null),
+ ColdswapMode.MULTIAPK.name(), null /* targetArchitecture */);
+ third.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-2.apk"));
+ third.addChangedFile(InstantRunBuildContext.FileType.SPLIT, new File("/tmp/split-3.apk"));
+ third.setVerifierResult(InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE);
+
+ third.close();
+ buildInfo = third.toXml();
+
+ Document document = XmlUtils.parseDocument(buildInfo, false /* namespaceAware */);
+
+ List<Element> builds = getElementsByName(document.getFirstChild(),
+ InstantRunBuildContext.TAG_BUILD);
+ // initial builds are never removed.
+ // first build should have only have split-main remaining.
+ assertThat(builds).hasSize(4);
+ assertThat(builds.get(1).getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP)).isEqualTo(
+ String.valueOf(first.getBuildId()));
+ List<Element> artifacts = getElementsByName(builds.get(1),
+ InstantRunBuildContext.TAG_ARTIFACT);
+ assertThat(artifacts).hasSize(2);
+ // split-2 changes on first build is overlapped by third change.
+ assertThat(artifacts.get(0).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .isEqualTo(new File("/tmp/main.apk").getAbsolutePath());
+ assertThat(artifacts.get(1).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .isEqualTo(new File("/tmp/split-1.apk").getAbsolutePath());
+
+ // second has not only split-main remaining.
+ assertThat(builds.get(2).getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP)).isEqualTo(
+ String.valueOf(second.getBuildId()));
+ artifacts = getElementsByName(builds.get(2), InstantRunBuildContext.TAG_ARTIFACT);
+ assertThat(artifacts).hasSize(1);
+ // split-2 changes on first build is overlapped by third change.
+ assertThat(artifacts.get(0).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .isEqualTo(new File("/tmp/main.apk").getAbsolutePath());
+
+ assertThat(builds.get(3).getAttribute(InstantRunBuildContext.ATTR_TIMESTAMP)).isEqualTo(
+ String.valueOf(third.getBuildId()));
+ artifacts = getElementsByName(builds.get(3), InstantRunBuildContext.TAG_ARTIFACT);
+ assertThat(artifacts).hasSize(3);
+ // split-2 changes on first build is overlapped by third change.
+ assertThat(artifacts.get(0).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .isEqualTo(new File("/tmp/main.apk").getAbsolutePath());
+ assertThat(artifacts.get(1).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .isEqualTo(new File("/tmp/split-2.apk").getAbsolutePath());
+ assertThat(artifacts.get(2).getAttribute(InstantRunBuildContext.ATTR_LOCATION))
+ .isEqualTo(new File("/tmp/split-3.apk").getAbsolutePath());
+ }
+
+ @Test
+ public void testTemporaryBuildProduction()
+ throws ParserConfigurationException, IOException, SAXException {
+ InstantRunBuildContext initial = new InstantRunBuildContext();
+ initial.setApiLevel(new AndroidVersion(21, null /* codeName */),
+ ColdswapMode.MULTIDEX.name(), null /* targetArchitecture */);
+ initial.addChangedFile(InstantRunBuildContext.FileType.DEX, new File("/tmp/split-1.apk"));
+ initial.addChangedFile(InstantRunBuildContext.FileType.DEX, new File("/tmp/split-2.apk"));
+ String buildInfo = initial.toXml();
+
+ InstantRunBuildContext first = new InstantRunBuildContext();
+ first.loadFromXml(buildInfo);
+ first.setApiLevel(new AndroidVersion(21, null /* codeName */),
+ null /* coldswapMode */, null /* targetArchitecture */);
+ first.addChangedFile(InstantRunBuildContext.FileType.RESOURCES, new File("/tmp/resources_ap"));
+ first.close();
+ String tmpBuildInfo = first.toXml(InstantRunBuildContext.PersistenceMode.TEMP_BUILD);
+
+ InstantRunBuildContext fixed = new InstantRunBuildContext();
+ fixed.loadFromXml(buildInfo);
+ fixed.mergeFrom(tmpBuildInfo);
+ fixed.setApiLevel(new AndroidVersion(21, null /* codeName */),
+ ColdswapMode.MULTIDEX.name(), null /* targetArchitecture */);
+ fixed.addChangedFile(InstantRunBuildContext.FileType.DEX, new File("/tmp/split-1.apk"));
+ fixed.close();
+ buildInfo = fixed.toXml();
+
+ // now check we only have 2 builds...
+ Document document = XmlUtils.parseDocument(buildInfo, false /* namespaceAware */);
+ List<Element> builds = getElementsByName(document.getFirstChild(),
+ InstantRunBuildContext.TAG_BUILD);
+ // initial builds are never removed.
+ // first build should have been removed due to the coldswap presence.
+ assertThat(builds).hasSize(2);
+ List<Element> artifacts = getElementsByName(builds.get(1),
+ InstantRunBuildContext.TAG_ARTIFACT);
+ assertThat(artifacts).hasSize(2);
+ }
+
+
+ @Test
+ public void testX86InjectedArchitecture() {
+
+ InstantRunBuildContext context = new InstantRunBuildContext();
+ context.setApiLevel(
+ new AndroidVersion(20, null /* codeName */), null /* coldswapMode */, "x86");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.PRE_LOLLIPOP);
+
+ context.setApiLevel(
+ new AndroidVersion(21, null /* codeName */), null /* coldswapMode */, "x86");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_DEX);
+
+ context.setApiLevel(
+ new AndroidVersion(23, null /* codeName */), null /* coldswapMode */, "x86");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_DEX);
+
+ context.setApiLevel(
+ new AndroidVersion(21, null /* codeName */), ColdswapMode.MULTIDEX.name(), "x86");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_DEX);
+
+ context.setApiLevel(
+ new AndroidVersion(23, null /* codeName */), ColdswapMode.MULTIDEX.name(), "x86");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_DEX);
+
+ context.setApiLevel(
+ new AndroidVersion(21, null /* codeName */), ColdswapMode.MULTIAPK.name(), "x86");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_APK);
+
+ context.setApiLevel(
+ new AndroidVersion(23, null /* codeName */), ColdswapMode.MULTIAPK.name(), "x86");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_APK);
+ }
+
+ private void testArmInjectedArchitecture() {
+ InstantRunBuildContext context = new InstantRunBuildContext();
+ context.setApiLevel(
+ new AndroidVersion(20, null /* codeName */), null /* coldswapMode */, "arm");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.PRE_LOLLIPOP);
+
+ context.setApiLevel(
+ new AndroidVersion(21, null /* codeName */), null /* coldswapMode */, "arm");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_APK);
+
+ context.setApiLevel(
+ new AndroidVersion(23, null /* codeName */), null /* coldswapMode */, "arm");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_APK);
+
+ context.setApiLevel(
+ new AndroidVersion(21, null /* codeName */), ColdswapMode.MULTIAPK.name(), "arm");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_APK);
+
+ context.setApiLevel(
+ new AndroidVersion(23, null /* codeName */), ColdswapMode.MULTIAPK.name(), "arm");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_APK);
+
+ context.setApiLevel(
+ new AndroidVersion(21, null /* codeName */), ColdswapMode.MULTIDEX.name(), "arm");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_DEX);
+
+ context.setApiLevel(
+ new AndroidVersion(23, null /* codeName */), ColdswapMode.MULTIDEX.name(), "arm");
+ assertThat(context.getPatchingPolicy()).isEqualTo(InstantRunPatchingPolicy.MULTI_DEX);
+ }
+
+ private static List<Element> getElementsByName(Node parent, String nodeName) {
+ ImmutableList.Builder<Element> builder = ImmutableList.builder();
+ NodeList childNodes = parent.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node item = childNodes.item(i);
+ if (item instanceof Element && item.getNodeName().equals(nodeName)) {
+ builder.add((Element) item);
+ }
+ }
+ return builder.build();
+ }
+
+ private static File createMarkedBuildInfo() throws IOException, ParserConfigurationException {
+ InstantRunBuildContext originalContext = new InstantRunBuildContext();
+ return createBuildInfo(originalContext);
+ }
+
+ private static File createBuildInfo(InstantRunBuildContext context)
+ throws IOException, ParserConfigurationException {
+ File tmpFile = File.createTempFile("InstantRunBuildContext", "tmp");
+ saveBuildInfo(context, tmpFile);
+ tmpFile.deleteOnExit();
+ return tmpFile;
+ }
+
+ private static void saveBuildInfo(InstantRunBuildContext context, File buildInfo)
+ throws IOException, ParserConfigurationException {
+ String xml = context.toXml();
+ Files.write(xml, buildInfo, Charsets.UTF_8);
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunMethodVerifierTarget.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunMethodVerifierTarget.java
new file mode 100644
index 0000000..0713241
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunMethodVerifierTarget.java
@@ -0,0 +1,156 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import com.example.basic.AllTypesFields;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Class that will have methods that use forbidden APIs.
+ */
+ at SuppressWarnings({"MethodMayBeStatic", "unused"})
+public class InstantRunMethodVerifierTarget {
+
+ public void classNewInstance() throws IllegalAccessException, InstantiationException {
+ Object.class.newInstance();
+ }
+
+ public void methodInvoke()
+ throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+ Method toString = Object.class.getMethod("toString");
+ toString.invoke(new Object());
+ }
+
+ public void contructorInvoke()
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
+ InstantiationException {
+ Constructor<String> constructor = String.class.getConstructor(String.class);
+ String foo = constructor.newInstance("Foo");
+ }
+
+ public void objectSetFåield() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateStringField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), "foo");
+ }
+
+ public void booleanSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateBooleanField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), true);
+ }
+
+ public void byteSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateByteField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), (byte) 0);
+ }
+
+ public void charSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateCharField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), 'c');
+ }
+
+ public void doubleSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateDoubleField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), 1d);
+ }
+
+ public void floatSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateFloatField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), 1f);
+ }
+
+ public void intSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateIntField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), 12);
+ }
+
+ public void longSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateLongField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), 12L);
+ }
+
+ public void shortSetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateShortField");
+ field.setAccessible(true);
+ field.set(new AllTypesFields(), (short) 12);
+ }
+
+ public String objectGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateStringField");
+ field.setAccessible(true);
+ return (String) field.get(new AllTypesFields());
+ }
+
+ public boolean booleanGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateBooleanField");
+ field.setAccessible(true);
+ return field.getBoolean(new AllTypesFields());
+ }
+
+ public byte byteGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateByteField");
+ field.setAccessible(true);
+ return field.getByte(new AllTypesFields());
+ }
+
+ public char charGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateCharField");
+ field.setAccessible(true);
+ return field.getChar(new AllTypesFields());
+ }
+
+ public double doubleGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateDoubleField");
+ field.setAccessible(true);
+ return field.getDouble(new AllTypesFields());
+ }
+
+ public float floatGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateFloatField");
+ field.setAccessible(true);
+ return field.getFloat(new AllTypesFields());
+ }
+
+ public int intGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateIntField");
+ field.setAccessible(true);
+ return field.getInt(new AllTypesFields());
+ }
+
+ public long longGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateLongField");
+ field.setAccessible(true);
+ return field.getLong(new AllTypesFields());
+ }
+
+ public short shortGetField() throws NoSuchFieldException, IllegalAccessException {
+ Field field = AllTypesFields.class.getField("privateShortField");
+ field.setAccessible(true);
+ return field.getShort(new AllTypesFields());
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunMethodVerifierTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunMethodVerifierTest.java
new file mode 100644
index 0000000..79d74a3
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunMethodVerifierTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.utils.AsmUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Tests for the InstantRunMethodVerifier class
+ */
+public class InstantRunMethodVerifierTest {
+
+ ClassNode targetClass;
+
+ @Before
+ public void setup() throws IOException {
+ ClassReader classReader = new ClassReader(InstantRunMethodVerifierTarget.class.getName());
+ targetClass = new ClassNode();
+ classReader.accept(targetClass, ClassReader.SKIP_FRAMES);
+ }
+
+ @Test
+ public void testMethods() {
+
+ //noinspection unchecked
+ for (MethodNode method : (List<MethodNode>) targetClass.methods) {
+ if (method.name.equals(AsmUtils.CONSTRUCTOR)) {
+ continue;
+ }
+ assertEquals("Failed when checking " + method.name, InstantRunVerifierStatus.REFLECTION_USED,
+ InstantRunMethodVerifier.verifyMethod(method));
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunVerifierTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunVerifierTest.java
new file mode 100644
index 0000000..a8196a5
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/InstantRunVerifierTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import static com.android.build.gradle.internal.incremental.InstantRunVerifierStatus.COMPATIBLE;
+import static org.junit.Assert.assertEquals;
+
+import com.android.build.gradle.internal.incremental.fixture.VerifierHarness;
+import com.google.common.collect.Lists;
+import com.verifier.tests.AddClassAnnotation;
+import com.verifier.tests.AddInstanceField;
+import com.verifier.tests.AddInterfaceImplementation;
+import com.verifier.tests.AddMethodAnnotation;
+import com.verifier.tests.AddNotRuntimeClassAnnotation;
+import com.verifier.tests.ChangeFieldType;
+import com.verifier.tests.ChangeInstanceFieldToStatic;
+import com.verifier.tests.ChangeInstanceFieldVisibility;
+import com.verifier.tests.ChangeStaticFieldToInstance;
+import com.verifier.tests.ChangeStaticFieldVisibility;
+import com.verifier.tests.ChangeSuperClass;
+import com.verifier.tests.ChangedClassInitializer1;
+import com.verifier.tests.ChangedClassInitializer2;
+import com.verifier.tests.ChangedClassInitializer3;
+import com.verifier.tests.DisabledClassChanging;
+import com.verifier.tests.DisabledClassNotChanging;
+import com.verifier.tests.DisabledMethodChanging;
+import com.verifier.tests.MethodAddedClass;
+import com.verifier.tests.MethodCollisionClass;
+import com.verifier.tests.NewInstanceReflectionUser;
+import com.verifier.tests.R;
+import com.verifier.tests.ReflectiveUserNotChanging;
+import com.verifier.tests.RemoveClassAnnotation;
+import com.verifier.tests.RemoveInterfaceImplementation;
+import com.verifier.tests.RemoveMethodAnnotation;
+import com.verifier.tests.RemoveNotRuntimeClassAnnotation;
+import com.verifier.tests.UnchangedClass;
+import com.verifier.tests.UnchangedClassInitializer1;
+
+import org.junit.Test;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AnnotationNode;
+
+import java.io.IOException;
+
+/**
+ * Tests for the {@link InstantRunVerifier}
+ */
+public class InstantRunVerifierTest {
+
+ VerifierHarness harness = new VerifierHarness(true);
+
+ @Test
+ public void testUnchangedClass() throws IOException {
+ InstantRunVerifierStatus changes = harness.verify(UnchangedClass.class, "verifier");
+ assertEquals(COMPATIBLE,changes);
+ }
+
+ @Test
+ public void testSuperClassChanged() throws IOException {
+ // not changing the super class name should be ok.
+ assertEquals(COMPATIBLE, harness.verify(ChangeSuperClass.class, null));
+ InstantRunVerifierStatus change = harness.verify(ChangeSuperClass.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.PARENT_CLASS_CHANGED, change);
+ }
+
+ @Test
+ public void testMethodAdded() throws IOException {
+ // not adding a method should be ok.
+ assertEquals(COMPATIBLE, harness.verify(MethodAddedClass.class, null));
+ InstantRunVerifierStatus changes = harness.verify(MethodAddedClass.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.METHOD_ADDED, changes);
+ }
+
+ @Test
+ public void testClassAnnotationAdded() throws IOException {
+ // not adding a class annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(AddClassAnnotation.class, null));
+ InstantRunVerifierStatus changes = harness.verify(AddClassAnnotation.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE, changes);
+ }
+
+ @Test
+ public void testClassAnnotationRemoved() throws IOException {
+ // not removing a class annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(RemoveClassAnnotation.class, null));
+ InstantRunVerifierStatus changes = harness.verify(RemoveClassAnnotation.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.CLASS_ANNOTATION_CHANGE, changes);
+ }
+
+ @Test
+ public void testNotRuntimeClassAnnotationAdded() throws IOException {
+ // not adding a non runtime visible class annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(AddNotRuntimeClassAnnotation.class, null));
+ // and adding it should still be fine.
+ assertEquals(COMPATIBLE, harness.verify(AddNotRuntimeClassAnnotation.class, "verifier"));
+ }
+
+ @Test
+ public void testNotRuntimeClassAnnotationRemoved() throws IOException {
+ // not removing a non runtime visible class annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(RemoveNotRuntimeClassAnnotation.class, null));
+ // and removing it should still be fine.
+ assertEquals(COMPATIBLE, harness.verify(RemoveNotRuntimeClassAnnotation.class, "verifier"));
+ }
+
+ @Test
+ public void testMethodAnnotationAdded() throws IOException {
+ // not adding a method annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(AddMethodAnnotation.class, null));
+ InstantRunVerifierStatus changes = harness.verify(AddMethodAnnotation.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.METHOD_ANNOTATION_CHANGE, changes);
+ }
+
+ @Test
+ public void testMethodAnnotationRemoved() throws IOException {
+ // not removing a class annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(RemoveMethodAnnotation.class, null));
+ InstantRunVerifierStatus changes = harness.verify(RemoveMethodAnnotation.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.METHOD_ANNOTATION_CHANGE, changes);
+ }
+
+ @Test
+ public void testInterfaceImplementationAdded() throws IOException {
+ // not removing a class annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(AddInterfaceImplementation.class, null));
+ InstantRunVerifierStatus changes = harness.verify(AddInterfaceImplementation.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.IMPLEMENTED_INTERFACES_CHANGE, changes);
+ }
+
+ @Test
+ public void testInterfaceImplementationRemoved() throws IOException {
+ // not removing a class annotation should be ok.
+ assertEquals(COMPATIBLE, harness.verify(RemoveInterfaceImplementation.class, null));
+ InstantRunVerifierStatus changes = harness.verify(RemoveInterfaceImplementation.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.IMPLEMENTED_INTERFACES_CHANGE, changes);
+ }
+
+ @Test
+ public void testMethodCollisionRemoved() throws IOException {
+ // not adding/removing overloaded methods should be ok.
+ assertEquals(COMPATIBLE, harness.verify(MethodCollisionClass.class, null));
+ InstantRunVerifierStatus changes = harness.verify(MethodCollisionClass.class, "verifier");
+ assertEquals(InstantRunVerifierStatus.METHOD_DELETED, changes);
+ }
+
+ @Test
+ public void testUnchangedClassInitializer() throws IOException {
+ assertEquals(COMPATIBLE, harness.verify(UnchangedClassInitializer1.class, null));
+ assertEquals(COMPATIBLE, harness.verify(UnchangedClassInitializer1.class, "verifier"));
+ assertEquals(
+ COMPATIBLE, harness.verify(UnchangedClassInitializer1.class, "lineChangingVerifier"));
+ }
+
+ @Test
+ public void testChangedClassInitializer() throws IOException {
+ assertEquals(InstantRunVerifierStatus.STATIC_INITIALIZER_CHANGE,
+ harness.verify(ChangedClassInitializer1.class, "verifier"));
+ assertEquals(InstantRunVerifierStatus.STATIC_INITIALIZER_CHANGE,
+ harness.verify(ChangedClassInitializer2.class, "verifier"));
+ assertEquals(InstantRunVerifierStatus.STATIC_INITIALIZER_CHANGE,
+ harness.verify(ChangedClassInitializer3.class, "verifier"));
+ }
+
+ @Test
+ public void testAddingInstanceField() throws IOException {
+ // not adding an instance field should be ok.
+ assertEquals(COMPATIBLE, harness.verify(AddInstanceField.class, null));
+ assertEquals(InstantRunVerifierStatus.FIELD_ADDED,
+ harness.verify(AddInstanceField.class, "verifier"));
+ }
+
+ @Test
+ public void testChangingAnInstanceFieldIntoStatic() throws IOException {
+ // not changing anything in an instance field should be ok.
+ assertEquals(COMPATIBLE, harness.verify(ChangeInstanceFieldToStatic.class, null));
+ assertEquals(InstantRunVerifierStatus.FIELD_TYPE_CHANGE,
+ harness.verify(ChangeInstanceFieldToStatic.class, "verifier"));
+ }
+
+ @Test
+ public void testChangingStaticFieldIntoAnInstance() throws IOException {
+ // not changing anything in a static field should be ok.
+ assertEquals(COMPATIBLE, harness.verify(ChangeStaticFieldToInstance.class, null));
+ assertEquals(InstantRunVerifierStatus.FIELD_TYPE_CHANGE,
+ harness.verify(ChangeStaticFieldToInstance.class, "verifier"));
+ }
+
+ @Test
+ public void testChangingFieldType() throws IOException {
+ // not changing a field type should be ok.
+ assertEquals(COMPATIBLE, harness.verify(ChangeFieldType.class, null));
+ assertEquals(InstantRunVerifierStatus.FIELD_TYPE_CHANGE,
+ harness.verify(ChangeFieldType.class, "verifier"));
+ }
+
+ @Test
+ public void testChangingInstanceFieldVisibility() throws IOException {
+ // not changing a field type should be ok.
+ assertEquals(COMPATIBLE, harness.verify(ChangeInstanceFieldVisibility.class, null));
+ assertEquals(InstantRunVerifierStatus.FIELD_TYPE_CHANGE, harness.verify(ChangeInstanceFieldVisibility.class, "verifier"));
+ }
+
+ @Test
+ public void testChangingStaticFieldVisibility() throws IOException {
+ // not changing a field type should be ok.
+ assertEquals(COMPATIBLE, harness.verify(ChangeStaticFieldVisibility.class, null));
+ assertEquals(InstantRunVerifierStatus.FIELD_TYPE_CHANGE, harness.verify(ChangeStaticFieldVisibility.class, "verifier"));
+ }
+
+ @Test
+ public void testClassNewInstanceReflectionUser() throws IOException {
+ // not changing a method implementation that uses reflection should be ok.
+ assertEquals(COMPATIBLE, harness.verify(NewInstanceReflectionUser.class, null));
+ assertEquals(InstantRunVerifierStatus.REFLECTION_USED,
+ harness.verify(NewInstanceReflectionUser.class, "verifier"));
+ }
+
+ @Test
+ public void testReflectiveUserNotChanging() throws IOException {
+ // not changing a method implementation that uses reflection should be ok.
+ assertEquals(COMPATIBLE,
+ harness.verify(ReflectiveUserNotChanging.class, null));
+ // changing other methods should be fine.
+ assertEquals(COMPATIBLE,
+ harness.verify(ReflectiveUserNotChanging.class, "verifier"));
+ }
+
+ @Test
+ public void testDisabledClassNotChanging() throws IOException {
+ // even though nothing changed, the verifier will flag it as a new version is available.
+ assertEquals(InstantRunVerifierStatus.INSTANT_RUN_DISABLED,
+ harness.verify(DisabledClassNotChanging.class, null));
+ }
+
+ @Test
+ public void testDisabledClassChanging() throws IOException {
+ // not changing a method implementation from a disabled class should be ok.
+ //assertEquals(InstantRunVerifierStatus.COMPATIBLE, harness.verify(DisabledClassChanging.class, null));
+ // changing a method implementation from a disabled class should be flagged.
+ assertEquals(InstantRunVerifierStatus.INSTANT_RUN_DISABLED,
+ harness.verify(DisabledClassChanging.class, "verifier"));
+ }
+
+ @Test
+ public void testDisabledMethodChanging() throws IOException {
+ // not changing a method implementation from a disabled class should be ok.
+ assertEquals(COMPATIBLE,
+ harness.verify(DisabledMethodChanging.class, null));
+ // changing a method implementation from a disabled class should be flagged.
+ assertEquals(InstantRunVerifierStatus.INSTANT_RUN_DISABLED,
+ harness.verify(DisabledMethodChanging.class, "verifier"));
+ }
+
+ @Test
+ public void testRClassSpecialCase() throws IOException {
+ assertEquals(COMPATIBLE, harness.verify(R.class, null));
+ assertEquals(InstantRunVerifierStatus.R_CLASS_CHANGE,
+ harness.verify(R.id.class, "verifier"));
+ }
+
+ @Test
+ public void testDiffListOnAnnotationNodes() throws Exception {
+ AnnotationNode original = new AnnotationNode("Ltest/SomeAnnotation;");
+ original.values = Lists.newArrayList("modules", Type.getObjectType("test/DaggerModule"));
+
+ AnnotationNode updated = new AnnotationNode("Ltest/SomeAnnotation;");
+ updated.values = Lists.newArrayList("modules", Type.getObjectType("test/DaggerModule"));
+
+ assertEquals(
+ InstantRunVerifier.Diff.NONE,
+ InstantRunVerifier.diffList(
+ Lists.newArrayList(original),
+ Lists.newArrayList(updated),
+ InstantRunVerifier.ANNOTATION_COMPARATOR));
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/OverrideClassFieldRemovalTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/OverrideClassFieldRemovalTest.java
new file mode 100644
index 0000000..61e465c
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/OverrideClassFieldRemovalTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.ClassWithFields;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test to ensure that all original class fields are removed in the $override class.
+ */
+public class OverrideClassFieldRemovalTest {
+
+ /**
+ * this is more complicated than it should be. Our tests have jacoco injected so our
+ * .class files contain some jacoco fields, so we make sure that all original fields
+ * have been removed from the class.
+ */
+ @Test
+ public void testFieldRemoval()
+ throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException,
+ InstantiationException {
+
+ harness.reset();
+
+ harness.applyPatch("changeBaseClass");
+ Class overrideClass = harness.loadPatchForClass("changeBaseClass", ClassWithFields.class);
+
+ for (Field field : overrideClass.getDeclaredFields()) {
+ assertTrue(field.getName().startsWith("$"));
+ }
+ }
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/StringSwitchTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/StringSwitchTest.java
new file mode 100644
index 0000000..3547e3e
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/StringSwitchTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.build.gradle.internal.incremental;
+
+import org.gradle.internal.reflect.NoSuchMethodException;
+import org.junit.Test;
+
+import org.objectweb.asm.*;
+import org.objectweb.asm.util.CheckClassAdapter;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.*;
+import org.objectweb.asm.tree.MethodNode;
+
+import static org.objectweb.asm.Opcodes.*;
+
+import com.android.utils.AsmUtils;
+
+import java.lang.reflect.*;
+import java.lang.reflect.Method;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * Tests for StringSwitch.
+ */
+public class StringSwitchTest {
+
+ public static String switchOn(String string) {
+ return string;
+ }
+
+ public static class TestClassDump implements Opcodes {
+
+ public static Class<?> loadClass(byte[] bytecode)
+ throws Exception {
+ ClassLoader scl = ClassLoader.getSystemClassLoader();
+ Object[] args = new Object[] {
+ null, bytecode, 0, bytecode.length
+ };
+ Method m = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
+ m.setAccessible(true);
+ return (Class<?>) m.invoke(scl, args);
+ }
+
+ public static Method dump (String className, String... strings) throws Exception {
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+
+ CheckClassAdapter cv = new CheckClassAdapter(cw);
+
+ cv.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null);
+
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "switchOn", "(Ljava/lang/String;)Ljava/lang/String;", null, null);
+ final GeneratorAdapter ga = new GeneratorAdapter(mv, ACC_PUBLIC + ACC_STATIC, "switchOn", "(Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitCode();
+ new StringSwitch() {
+ @Override
+ void visitString() {
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ }
+
+ @Override
+ void visitCase(String methodName) {
+ mv.visitLdcInsn(methodName);
+ mv.visitInsn(Opcodes.ARETURN);
+ }
+
+ @Override
+ void visitDefault() {
+ mv.visitLdcInsn("No Match Found");
+ mv.visitInsn(Opcodes.ARETURN);
+ }
+ }.visit(ga, new HashSet<String>(Arrays.asList(strings)));
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cv.visitEnd();
+
+ byte[] bytes = cw.toByteArray();
+ return loadClass(bytes).getMethod("switchOn", String.class);
+ }
+ }
+
+ void exercise(String className, String ...strings) throws Exception {
+ final Method m = TestClassDump.dump(className, strings);
+ for (String string : strings) {
+ try {
+ String result = (String) m.invoke(null, string);
+ if (!result.equals(string)) {
+ throw new RuntimeException(String.format("%s != %s", result, string));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Test public void largeSet() throws Exception {
+ exercise(
+ "LargeSet",
+ "com/example/basic/AllAccessFieldsSubclass.()V",
+ "com/example/basic/AllAccessFields.()V",
+ "com/example/basic/AllAccessMethods.()V",
+ "com/example/basic/AllAccessStaticFields.()V",
+ "com/example/basic/AllAccessStaticMethods.()V",
+ "com/example/basic/AllTypesFields.()V",
+ "com/example/basic/AnonymousClasses$1.()V",
+ "com/example/basic/AnonymousClasses$2.()V",
+ "com/example/basic/AnonymousClasses.()V",
+ "com/example/basic/AnotherPackagePrivateClass$Builder.(Lcom/example/basic/AnotherPackagePrivateClass;)V",
+ "com/example/basic/AnotherPackagePrivateClass.(Ljava/lang/Object;)V",
+ "com/example/basic/ClashStaticMethod.(Ljava/lang/String;)V",
+ "com/example/basic/Constructors$AbstractBase.(DLjava/lang/String;I)V",
+ "com/example/basic/Constructors$AbstractBase.()V",
+ "com/example/basic/Constructors$Base.(DLjava/lang/String;I)V",
+ "com/example/basic/Constructors$Base.()V",
+ "com/example/basic/Constructors$DupInvokeSpecialBase.(Lcom/example/basic/Constructors;Lcom/example/basic/Constructors$DupInvokeSpecialBase;)V",
+ "com/example/basic/Constructors$DupInvokeSpecialSub.(Lcom/example/basic/Constructors;)V",
+ "com/example/basic/Constructors.(Ljava/lang/String;)V",
+ "com/example/basic/Constructors$Sub.(DLjava/lang/String;I)V",
+ "com/example/basic/Constructors$Sub.(IIII)V",
+ "com/example/basic/Constructors$Sub.(III)V",
+ "com/example/basic/Constructors$Sub.(JF)V",
+ "com/example/basic/Constructors$Sub.(Ljava/lang/String;Ljava/lang/String;Z)V",
+ "com/example/basic/Constructors$Sub.(Ljava/lang/String;ZLjava/lang/String;)V",
+ "com/example/basic/Constructors$Sub.(Ljava/lang/String;Z)V",
+ "com/example/basic/Constructors$Sub.(Ljava/util/List;Z)V",
+ "com/example/basic/Constructors$SubOfAbstract.(IIII)V",
+ "com/example/basic/Constructors$Sub.()V",
+ "com/example/basic/Constructors$Sub.(ZLjava/util/List;)V",
+ "com/example/basic/Constructors$Sub.(Z)V",
+ "com/example/basic/Constructors$Utility.()V",
+ "com/example/basic/ControlClass.()V",
+ "com/example/basic/CovariantChild.()V",
+ "com/example/basic/CovariantParent.()V",
+ "com/example/basic/Enums$1.(Ljava/lang/String;ILjava/lang/String;)V",
+ "com/example/basic/Enums.(Ljava/lang/String;ILjava/lang/String;Lcom/example/basic/Enums$1;)V",
+ "com/example/basic/Enums.(Ljava/lang/String;ILjava/lang/String;)V",
+ "com/example/basic/Exceptions.(Ljava/lang/String;)V",
+ "com/example/basic/Exceptions$MyException.(Ljava/lang/String;)V",
+ "com/example/basic/Exceptions.()V",
+ "com/example/basic/FieldOverridingChild.()V",
+ "com/example/basic/FieldOverridingGrandChild.()V",
+ "com/example/basic/FieldOverridingParent.()V",
+ "com/example/basic/FinalFieldsInCtor.(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
+ "com/example/basic/GrandChild.()V",
+ "com/example/basic/InnerClass$Builder.()V",
+ "com/example/basic/InnerClassInvoker.()V",
+ "com/example/basic/InnerClass.(Ljava/lang/String;)V",
+ "com/example/basic/MultipleMethodInvocations.()V",
+ "com/example/basic/NoPackageAccess.()V",
+ "com/example/basic/PackagePrivateClass$1.(Lcom/example/basic/PackagePrivateClass;)V",
+ "com/example/basic/PackagePrivateClass.(Ljava/lang/String;)V",
+ "com/example/basic/PackagePrivateFieldAccess.()V",
+ "com/example/basic/PackagePrivateInvoker.()V",
+ "com/example/basic/ParentInvocation.()V",
+ "com/example/basic/PublicMethodInvoker.(I)V",
+ "com/example/basic/ReflectiveUser.()V",
+ "com/example/basic/StaticMethodsInvoker.()V",
+ "com/example/basic/SuperCall$Base.()V",
+ "com/example/basic/SuperCall$Sub.()V",
+ "com/example/basic/SuperCall.()V",
+ "com/ir/disable/InstantRunDisabledClass.()V",
+ "com/ir/disable/InstantRunDisabledMethod.(Ljava/lang/String;)V",
+ "com/ir/disable/InstantRunDisabledMethod.()V",
+ "com/verifier/tests/AddClassAnnotation.()V",
+ "com/verifier/tests/AddInstanceField.()V",
+ "com/verifier/tests/AddInterfaceImplementation.()V",
+ "com/verifier/tests/AddMethodAnnotation.()V",
+ "com/verifier/tests/AddNotRuntimeClassAnnotation.()V",
+ "com/verifier/tests/ChangedClassInitializer1.()V",
+ "com/verifier/tests/ChangedClassInitializer2.()V",
+ "com/verifier/tests/ChangedClassInitializer3.()V",
+ "com/verifier/tests/ChangeFieldType.()V",
+ "com/verifier/tests/ChangeInstanceFieldToStatic.()V",
+ "com/verifier/tests/ChangeInstanceFieldVisibility.()V",
+ "com/verifier/tests/ChangeStaticFieldToInstance.()V",
+ "com/verifier/tests/ChangeStaticFieldVisibility.()V",
+ "com/verifier/tests/ChangeSuperClass.()V",
+ "com/verifier/tests/MethodAddedClass.()V",
+ "com/verifier/tests/MethodCollisionClass.()V",
+ "com/verifier/tests/RemoveClassAnnotation.()V",
+ "com/verifier/tests/RemoveInterfaceImplementation.()V",
+ "com/verifier/tests/RemoveMethodAnnotation.()V",
+ "com/verifier/tests/RemoveNotRuntimeClassAnnotation.()V",
+ "com/verifier/tests/UnchangedClassInitializer1.()V",
+ "com/verifier/tests/UnchangedClass.()V",
+ "java/lang/Enum.(Ljava/lang/String;I)V",
+ "java/lang/Exception.(Ljava/lang/String;Ljava/lang/Throwable;)V",
+ "java/lang/Exception.(Ljava/lang/String;)V",
+ "java/lang/Exception.(Ljava/lang/Throwable;)V",
+ "java/lang/Exception.()V",
+ "java/lang/Object.()V",
+ "NoPackage.(Ljava/lang/String;)V"
+ );
+ }
+
+ @Test public void hashCollide() throws Exception {
+ exercise(
+ "HashCollide",
+ "FB", "Ea");
+ }
+
+}
+
+
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/fixture/ClassEnhancement.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/fixture/ClassEnhancement.java
new file mode 100644
index 0000000..4af8900
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/fixture/ClassEnhancement.java
@@ -0,0 +1,293 @@
+/*
+ * 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.build.gradle.internal.incremental.fixture;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.incremental.IncrementalChangeVisitor;
+import com.android.tools.fd.runtime.AndroidInstantRuntime;
+import com.android.utils.FileUtils;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Map;
+import java.util.logging.Logger;
+
+public class ClassEnhancement implements TestRule {
+
+ @NonNull
+ private final File mInstrumentedPatchesFolder;
+
+ @NonNull
+ private final File mBaseCompileOutputFolder;
+
+
+ private Map<String, File> mInstrumentedPatchFolders;
+
+ private Map<String, ClassLoader> mEnhancedClassLoaders;
+
+ private String currentPatchState = null;
+
+ private final boolean tracing;
+
+ public ClassEnhancement() {
+ this(true);
+ }
+
+ public ClassEnhancement(boolean tracing) {
+ this.tracing = tracing;
+ File classes = new File(ClassEnhancement.class.getResource("/").getFile()).getParentFile();
+ File incrementalTestClasses = new File(classes, "incremental-test");
+ mInstrumentedPatchesFolder = new File(incrementalTestClasses, "instrumentedPatches");
+ mBaseCompileOutputFolder = new File(incrementalTestClasses, "base");
+ }
+
+ public void reset()
+ throws ClassNotFoundException, InstantiationException, IllegalAccessException,
+ NoSuchFieldException {
+ applyPatch(null);
+ }
+
+ public void applyPatch(@Nullable String patch)
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+ // if requested level is null, always reset no matter what state we think we are in since
+ // we share the same class loader among all ClassEnhancement instances.
+ if (patch == null || !Objects.equal(patch, currentPatchState)) {
+
+ final File outputFolder =
+ patch == null ? mBaseCompileOutputFolder : mInstrumentedPatchFolders.get(patch);
+ Iterable<File> files =
+ Files.fileTreeTraverser()
+ .preOrderTraversal(outputFolder)
+ .filter(Files.isFile());
+ Iterable<String> classNames = Iterables.transform(files, new Function<File, String>() {
+ @Override
+ public String apply(File file) {
+ String relativePath = FileUtils.relativePath(file, outputFolder);
+ return relativePath.substring(0, relativePath.length() - 6 /*.class */)
+ .replace(File.separatorChar, '.');
+ }
+ });
+
+ for (String changedClassName : classNames) {
+ if (changedClassName.endsWith("$override")) {
+ changedClassName = changedClassName.substring(0, changedClassName.length() - 9);
+ }
+ patchClass(changedClassName, patch);
+ }
+ currentPatchState = patch;
+ }
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ AndroidInstantRuntime.setLogger(Logger.getLogger(description.getClassName()));
+ mInstrumentedPatchFolders = getCompileFolders(mInstrumentedPatchesFolder);
+
+ final URL[] classLoaderUrls = getClassLoaderUrls();
+ final ClassLoader mainClassLoader = this.getClass().getClassLoader();
+
+ mEnhancedClassLoaders = setUpEnhancedClassLoaders(
+ classLoaderUrls, mainClassLoader, mInstrumentedPatchFolders, tracing);
+
+ base.evaluate();
+
+ }
+ };
+ }
+
+ private static Map<String, File> getCompileFolders(File folder) {
+ File[] iterations = folder.listFiles();
+
+ if (iterations == null) {
+ throw new AssertionError("Resource base must be a directory.");
+ }
+ ImmutableMap.Builder<String, File> compileOutputFolders = ImmutableMap.builder();
+
+ for (File f : iterations) {
+ compileOutputFolders.put(f.getName(), f);
+ }
+
+ return compileOutputFolders.build();
+ }
+
+ private static Map<String, ClassLoader> setUpEnhancedClassLoaders(
+ final URL[] classLoaderUrls,
+ final ClassLoader mainClassLoader,
+ final Map<String, File> instrumentedPatches,
+ final boolean tracing) {
+ return Maps.transformValues(instrumentedPatches, new Function<File, ClassLoader>() {
+ @Override
+ public ClassLoader apply(File instrumentedPatchFolder) {
+ return new IncrementalChangeClassLoader(
+ classLoaderUrls, mainClassLoader, instrumentedPatchFolder, tracing);
+ }
+ });
+ }
+
+
+ private void patchClass(@NonNull String name, @Nullable String patch)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+
+ Class<?> originalEnhancedClass = getClass().getClassLoader().loadClass(name);
+ if (originalEnhancedClass.isInterface()) {
+ // we don't patch interfaces.
+ return;
+ }
+ Field newImplementationField = null;
+ try {
+ newImplementationField = originalEnhancedClass.getField("$change");
+ } catch (NoSuchFieldException e) {
+ // the original class does not contain the $change field which mean that InstantRun
+ // was disabled for it, we should ignore and not try to patch it.
+ return;
+ }
+ // class might not be accessible from there
+ newImplementationField.setAccessible(true);
+
+ if (patch == null) {
+ // Revert to original implementation.
+ newImplementationField.set(null, null);
+ return;
+ }
+
+ Object change = mEnhancedClassLoaders.get(patch)
+ .loadClass(name + "$override").newInstance();
+
+ newImplementationField.set(null, change);
+ }
+
+ private static URL[] getClassLoaderUrls() {
+ URL resource = ClassEnhancement.class.getClassLoader().getResource(
+ "com/android/tools/fd/runtime/AndroidInstantRuntime.class");
+
+ assertNotNull(resource);
+ String runtimeURL = resource.toString().substring(0, resource.toString().length() -
+ "com/android/tools/fd/runtime/AndroidInstantRuntime.class"
+ .length());
+
+ resource = ClassEnhancement.class.getClassLoader().getResource(
+ "org/objectweb/asm/Type.class");
+ Preconditions.checkNotNull(resource);
+ String asmURL = resource.toString().substring(0, resource.toString().length() -
+ "org/objectweb/asm/Type.class".length());
+
+ try {
+ return new URL[]{new URL(runtimeURL), new URL(asmURL)};
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class IncrementalChangeClassLoader extends URLClassLoader {
+
+ private final File mInstrumentedPatchFolder;
+ private final boolean tracing;
+
+ public IncrementalChangeClassLoader(
+ URL[] urls, ClassLoader parent, File instrumentedPatchFolder, boolean tracing) {
+ super(urls, parent);
+ this.tracing = tracing;
+ mInstrumentedPatchFolder = instrumentedPatchFolder;
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+
+ if (!name.endsWith("$override")) {
+ return super.findClass(name);
+ }
+
+ File compiledFile =
+ new File(mInstrumentedPatchFolder, name.replace(".", "/") + ".class");
+
+ if (!compiledFile.exists()) {
+ return super.findClass(name);
+ }
+
+ byte[] instrumentedClassBytes;
+ try {
+ instrumentedClassBytes = Files.toByteArray(compiledFile);
+ } catch (IOException e) {
+ throw new ClassNotFoundException(Throwables.getStackTraceAsString(e));
+ }
+
+ return defineClass(name, instrumentedClassBytes, 0, instrumentedClassBytes.length);
+ }
+ }
+
+ public Class loadPatchForClass(String patchName, Class originalClass)
+ throws ClassNotFoundException {
+ ClassLoader classLoader = mEnhancedClassLoaders.get(patchName);
+ if (classLoader != null) {
+ return classLoader.loadClass(originalClass.getName()
+ + IncrementalChangeVisitor.OVERRIDE_SUFFIX);
+ }
+ throw new IllegalArgumentException("Unknown patch name " + patchName);
+ }
+
+ @SuppressWarnings("unused") // Helpful for debugging.
+ public static String traceClass(byte[] bytes) {
+ ClassReader classReader = new ClassReader(bytes, 0, bytes.length);
+ StringWriter sw = new StringWriter();
+ TraceClassVisitor traceClassVisitor = new TraceClassVisitor(new PrintWriter(sw));
+ classReader.accept(traceClassVisitor, 0);
+ return sw.toString();
+ }
+
+ @SuppressWarnings("unused") // Helpful for debugging.
+ public static String traceClass(ClassLoader classLoader, String classNameAsResource) throws IOException {
+ InputStream inputStream = classLoader.getResourceAsStream(classNameAsResource);
+ assertNotNull(inputStream);
+ try {
+ ClassReader classReader = new ClassReader(inputStream);
+ StringWriter sw = new StringWriter();
+ TraceClassVisitor traceClassVisitor = new TraceClassVisitor(new PrintWriter(sw));
+ classReader.accept(traceClassVisitor, 0);
+ return sw.toString();
+ } finally {
+ inputStream.close();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/fixture/VerifierHarness.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/fixture/VerifierHarness.java
new file mode 100644
index 0000000..5329bfc
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/fixture/VerifierHarness.java
@@ -0,0 +1,55 @@
+/*
+ * 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.build.gradle.internal.incremental.fixture;
+
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.build.gradle.internal.incremental.InstantRunVerifier;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Facilities shared by all tests testing the {@link InstantRunVerifier}
+ */
+public class VerifierHarness {
+
+ private final boolean tracing;
+ private final File baseFolder;
+ private final File patchesFolder;
+
+ public VerifierHarness(boolean tracing) {
+ this.tracing = tracing;
+ File classes = new File(ClassEnhancement.class.getResource("/").getFile()).getParentFile();
+ File incrementalTestClasses = new File(classes, "incremental-test");
+ patchesFolder = new File(incrementalTestClasses, "patches");
+ baseFolder = new File(incrementalTestClasses, "base");
+ }
+
+ public InstantRunVerifierStatus verify(Class clazz, String patchLevel) throws IOException {
+ String fqcn = clazz.getName();
+ File originalFile = new File(baseFolder, fqcn.replace('.', File.separatorChar) + ".class");
+ File patchedFile;
+ if (patchLevel != null) {
+ File patchLevelFolder = new File(patchesFolder, patchLevel);
+ patchedFile = new File(patchLevelFolder,
+ fqcn.replace('.', File.separatorChar) + ".class");
+ } else {
+ patchedFile = originalFile;
+ }
+ return InstantRunVerifier.run(originalFile, patchedFile);
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessFieldsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessFieldsTest.java
new file mode 100644
index 0000000..b33e6d6
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessFieldsTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.AllAccessFieldsSubclass;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class AllAccessFieldsTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void accessParentProtected() throws Exception {
+ harness.reset();
+
+ AllAccessFieldsSubclass sub = new AllAccessFieldsSubclass();
+
+ assertWithMessage("AllAccessFieldsTest.accessParentProtected()")
+ .that(sub.getProtectedInt()).isEqualTo(7);
+
+ harness.applyPatch("changeSubClass");
+
+ assertWithMessage("AllAccessFieldsTest.accessParentProtected()")
+ .that(sub.getProtectedInt()).isEqualTo(49);
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessStaticFieldsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessStaticFieldsTest.java
new file mode 100644
index 0000000..1528d8b
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessStaticFieldsTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.AllAccessStaticFields;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Tests for static fields and final static fields accesses from byte code enhanced code.
+ */
+public class AllAccessStaticFieldsTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @SuppressWarnings("AccessStaticViaInstance")
+ @Test
+ public void changeClassWithStaticFields() throws Exception {
+
+ harness.reset();
+ AllAccessStaticFields allAccessStaticFields = new AllAccessStaticFields();
+
+ assertWithMessage("base: allAccessStaticFields.getAllFields()")
+ .that(allAccessStaticFields.accessAllFields()).isEqualTo("12243648");
+
+ assertWithMessage("base: allAccessStaticFields.getAllFields()")
+ .that(AllAccessStaticFields.staticAccessAllFields()).isEqualTo("12243648");
+
+ assertWithMessage("base: allAccessStaticFields.staticAccessAllFinalFields()")
+ .that(AllAccessStaticFields.staticAccessAllFinalFields()).isEqualTo("14284256");
+
+ assertWithMessage("base: allAccessStaticFields.getAllFields()")
+ .that(allAccessStaticFields.staticAccessAllFinalFields()).isEqualTo("14284256");
+
+ assertWithMessage("base: AllAccessStaticFields.publicInt")
+ .that(AllAccessStaticFields.publicInt == 12);
+
+ assertWithMessage("base: AllAccessStaticFields.")
+ .that(AllAccessStaticFields.finalPublicInt == 56);
+
+ assertWithMessage("base: allAccessStaticFields.publicInt")
+ .that(allAccessStaticFields.publicInt == 12);
+
+ harness.applyPatch("changeSubClass");
+
+ assertWithMessage("changeSubClass: allAccessStaticFields.getAllFields()")
+ .that(allAccessStaticFields.accessAllFields()).isEqualTo("49372513");
+
+ assertWithMessage("changeSubClass: allAccessStaticFields.staticAccessAllFields()")
+ .that(allAccessStaticFields.staticAccessAllFields()).isEqualTo("48362412");
+
+ // it will set static fields to these values times two.
+ allAccessStaticFields.setAllFields(11,22,33,44);
+
+ // one should be added to above values*2
+ assertWithMessage("changeSubClass: allAccessStaticFields.accessAllFields()")
+ .that(allAccessStaticFields.accessAllFields()).isEqualTo("89674523");
+
+ // should be above values multiplied by two only.
+ assertWithMessage("changeSubClass: AllAccessStaticFields.accessAllFields()")
+ .that(AllAccessStaticFields.staticAccessAllFields()).isEqualTo("88664422");
+
+ assertWithMessage("changeSubClass: allAccessStaticFields.staticAccessAllFinalFields()")
+ .that(allAccessStaticFields.staticAccessAllFinalFields()).isEqualTo("60453015");
+
+ assertWithMessage("changeSubClass: AllAccessStaticFields.publicInt")
+ .that(AllAccessStaticFields.publicInt == 22);
+
+ assertWithMessage("changeSubClass: AllAccessStaticFields.")
+ .that(AllAccessStaticFields.finalPublicInt == 60);
+
+ assertWithMessage("changeSubClass: allAccessStaticFields.publicInt")
+ .that(allAccessStaticFields.publicInt == 22);
+
+ AllAccessStaticFields.staticSetAllFields(10, 20, 30, 40);
+ assertWithMessage("changeSubClass: AllAccessStaticFields.accessAllFields()")
+ .that(AllAccessStaticFields.staticAccessAllFields()).isEqualTo("40302010");
+
+ // values should also be changed for the original class but with the original method impl.
+ harness.reset();
+ assertWithMessage("base: allAccessStaticFields.getAllFields()")
+ .that(allAccessStaticFields.accessAllFields()).isEqualTo("10203040");
+
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessStaticMethodsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessStaticMethodsTest.java
new file mode 100644
index 0000000..c2dec47
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllAccessStaticMethodsTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.StaticMethodsInvoker;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.logging.Logger;
+
+/**
+ * Tests that will invoke code that itself use all possible static methods access rights invocation
+ */
+public class AllAccessStaticMethodsTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void checkInitialByteCodeChanges() throws Exception {
+
+ harness.reset();
+
+ StaticMethodsInvoker invoker = new StaticMethodsInvoker();
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticIntMethods()")
+ .that(invoker.invokeAllStaticIntMethods()).isEqualTo(9);
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticStringMethods()")
+ .that(invoker.invokeAllStaticStringMethods()).isEqualTo(
+ "package_private,protected,public");
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticDoubleArrayMethods()")
+ .that(invoker.invokeAllStaticDoubleArrayMethods() == 5d);
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticStringArrayMethods()")
+ .that(invoker.invokeAllStaticStringArrayMethods()).isEqualTo(
+ "package,private,string::public,string");
+ }
+
+ @Test
+ public void checkByteCodeEnhancedVersion() throws Exception {
+
+ harness.applyPatch("changeSubClass");
+
+ StaticMethodsInvoker invoker = new StaticMethodsInvoker();
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticIntMethods()")
+ .that(invoker.invokeAllStaticIntMethods()).isEqualTo(2);
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticStringMethods()")
+ .that(invoker.invokeAllStaticStringMethods()).isEqualTo(
+ "public,protected,package_private");
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticDoubleArrayMethods()")
+ .that(invoker.invokeAllStaticDoubleArrayMethods() == 5d);
+
+ assertWithMessage("base: StaticMethodsInvoker.invokeAllStaticStringArrayMethods()")
+ .that(invoker.invokeAllStaticStringArrayMethods()).isEqualTo(
+ ":package,private,string:public,string");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllTypesFieldsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllTypesFieldsTest.java
new file mode 100644
index 0000000..2e4cbfe
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AllTypesFieldsTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.AllTypesFields;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Tests for private fields accesses.
+ */
+public class AllTypesFieldsTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement(false);
+
+ /**
+ * Checks that the initial bytecode changes did not prevent proper access to private fields
+ * and methods.
+ *
+ * @throws Exception when Byte code generation failed.
+ */
+ @Test
+ public void checkInitialByteCodeChanges() throws Exception {
+
+ harness.reset();
+ AllTypesFields allTypesFields = new AllTypesFields();
+
+ allTypesFields.setPrivateBooleanField(false);
+ assertFalse(allTypesFields.getPrivateBooleanField());
+
+ allTypesFields.setPrivateDoubleField(1354.43d);
+ assertEquals(1354.43d, allTypesFields.getPrivateDoubleField(), 0d);
+
+ // more to come...
+ }
+
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AnonymousClassesTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AnonymousClassesTest.java
new file mode 100644
index 0000000..66aede8
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/AnonymousClassesTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.AnonymousClasses;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test for static anonymous classes.
+ * TODO: non static...
+ */
+public class AnonymousClassesTest {
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement(false);
+
+ /**
+ * Checks that the initial bytecode changes did not prevent proper access to private fields
+ * and methods.
+ *
+ * @throws Exception when Byte code generation failed.
+ */
+ @Test
+ public void checkAnonymousClasses() throws Exception {
+
+ harness.reset();
+ assertEquals("first", AnonymousClasses.FIRST.doSomething());
+ assertEquals("second", AnonymousClasses.SECOND.doSomething());
+
+ harness.applyPatch("changeBaseClass");
+ assertEquals("patched_first", AnonymousClasses.FIRST.doSomething());
+ assertEquals("patched_second", AnonymousClasses.SECOND.doSomething());
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ClashingMethodNamesTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ClashingMethodNamesTest.java
new file mode 100644
index 0000000..c35f988
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ClashingMethodNamesTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.ClashStaticMethod;
+import com.google.common.truth.Expect;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+
+public class ClashingMethodNamesTest {
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement(false);
+
+ @Test
+ public void checkClashingStaticAndNonStaticMethods() throws Exception {
+ harness.reset();
+ ClashStaticMethod m = new ClashStaticMethod("A");
+ expect.withFailureMessage("base: new ClashStaticMethod('A').append('B')")
+ .that(m.append("B"))
+ .isEqualTo("AB_instance");
+
+ expect.withFailureMessage("base: new ClashStaticMethod('A').append('B')")
+ .that(ClashStaticMethod.append(m, "B"))
+ .isEqualTo("AB");
+
+ harness.applyPatch("changeBaseClass");
+ expect.withFailureMessage("changeBaseClass: new ClashStaticMethod('A').append('B')")
+ .that(m.append("B"))
+ .isEqualTo("BA_instance_override");
+ expect.withFailureMessage("changeBaseClass: new ClashStaticMethod('A').append('B')")
+ .that(ClashStaticMethod.append(m, "B"))
+ .isEqualTo("BA");
+
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ConstructorTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ConstructorTest.java
new file mode 100644
index 0000000..54d6932
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ConstructorTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.Constructors;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ConstructorTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void updateConstructor() throws Exception {
+ harness.reset();
+
+ Constructors.Sub sub = new Constructors.Sub(1.2, ".3.", 4);
+ assertEquals("base:1.2[.3.]4", sub.getBaseFinal());
+ assertEquals("sub:1.2.3.4", sub.getSubFinal());
+ assertEquals("Sub(double, String, int)", sub.value);
+
+ sub = new Constructors.Sub(1234L, 0.5f);
+ assertEquals("base:0.5[[1234]]0", sub.getBaseFinal());
+ assertEquals("sub:0.5[1234]0", sub.getSubFinal());
+ assertEquals("Sub(long, float)", sub.value);
+
+ sub = new Constructors.Sub(10, 20, 30);
+ assertEquals("base:2.1[220]30", sub.getBaseFinal());
+ assertEquals("sub:2.122030", sub.getSubFinal());
+ assertEquals("Sub(2, 20, 30)", sub.value);
+
+ Constructors outer = new Constructors("outer");
+ Constructors.DupInvokeSpecialSub dup = outer.new DupInvokeSpecialSub();
+ assertEquals("original", dup.value);
+ assertEquals("outer", outer.value);
+
+ harness.applyPatch("changeBaseClass");
+ sub = new Constructors.Sub(1.2, ".3.", 4);
+ assertEquals("1.2(.3.)4:patched_base", sub.getBaseFinal());
+ assertEquals("1.2.3.4:patched_sub", sub.getSubFinal());
+ assertEquals("patched_Sub(double, String, int)", sub.value);
+
+ sub = new Constructors.Sub(1234L, 0.5f);
+ assertEquals("0.5((1235*))0:patched_base", sub.getBaseFinal());
+ assertEquals("0.5(1235*)0:patched_sub", sub.getSubFinal());
+ assertEquals("patched_Sub(long, float)", sub.value);
+
+ sub = new Constructors.Sub(10, 20, 30);
+ assertEquals("9.1(920)30:patched_base", sub.getBaseFinal());
+ assertEquals("9.192030:patched_sub", sub.getSubFinal());
+ assertEquals("Sub(9, 20, 30)", sub.value);
+
+ outer = new Constructors("outer_patched");
+ dup = outer.new DupInvokeSpecialSub();
+ assertEquals("patched", dup.value);
+ assertEquals("outer_patched", outer.value);
+ }
+
+ @Test
+ public void updateConstructorSuperCall() throws Exception {
+ harness.reset();
+ Constructors.Sub sub = new Constructors.Sub(10, 20, 30, 40);
+ assertEquals("base:", sub.getBaseFinal());
+ assertEquals(":sub", sub.getSubFinal());
+ assertEquals("Sub(int, int, int, int)", sub.value);
+
+ harness.reset();
+ harness.applyPatch("changeBaseClass");
+ sub = new Constructors.Sub(10, 20, 30, 40);
+ assertEquals("3.020343:patched_base", sub.getBaseFinal());
+ assertEquals(":patched_sub", sub.getSubFinal());
+ assertEquals("patched_Sub(int, int, int, int)3", sub.value);
+ }
+
+ @Test
+ public void updateConstructorSuperCallInAbstractBase() throws Exception {
+ harness.reset();
+ Constructors.SubOfAbstract sub = new Constructors.SubOfAbstract(10, 20, 30, 40);
+ assertEquals("abstract base:", sub.getBaseFinal());
+ assertEquals(":sub_abstract", sub.getSubFinal());
+ assertEquals("SubOfAbstract(int, int, int, int)", sub.value);
+
+ harness.reset();
+ harness.applyPatch("changeBaseClass");
+ sub = new Constructors.SubOfAbstract(10, 20, 30, 40);
+ assertEquals("patched_abstract_base:30.0203070", sub.getBaseFinal());
+ assertEquals(":patched_sub_abstract", sub.getSubFinal());
+ assertEquals("patched_SubOfAbstract(int, int, int, int)", sub.value);
+ }
+
+ @Test
+ public void testExceptionsInConstructor()
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+
+ harness.reset();
+ try {
+ Constructors.Sub sub = new Constructors.Sub();
+ fail("iae expected");
+ } catch(IllegalArgumentException e) {
+ assertEquals("pass me a string !", e.getMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub("sub", true);
+ fail("iae expected");
+ } catch(IllegalArgumentException e) {
+ assertEquals("iae overflow", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub("sub", "expected", true);
+ fail("iae expected");
+ } catch(IllegalArgumentException e) {
+ assertEquals("expected overflow", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub("sub", true, "expected");
+ fail("RuntimeException expected");
+ } catch(RuntimeException e) {
+ assertEquals("expected iae overflow", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(ImmutableList.of("one", "two"), true);
+ fail("RuntimeException expected");
+ } catch(IllegalArgumentException e) {
+ assertEquals("two overflow", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(ImmutableList.of("one", "two"), false);
+ assertEquals("success", sub.getSubFinal());
+ } catch(IllegalArgumentException e) {
+ fail("unexpected " + e);
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(true, ImmutableList.of("one", "two"));
+ fail("RuntimeException expected");
+ } catch(RuntimeException e) {
+ assertEquals("two", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(false, ImmutableList.of("one", "two"));
+ assertEquals("success", sub.getSubFinal());
+ } catch(Exception e) {
+ fail("unexpected " + e);
+ }
+
+ Constructors.Sub orginalSub = new Constructors.Sub("sub", false);
+ assertEquals("base:1.0sub2", orginalSub.getBaseFinal());
+
+ harness.applyPatch("changeBaseClass");
+ try {
+ Constructors.Sub sub = new Constructors.Sub();
+ fail("iae expected");
+ } catch(IllegalArgumentException e) {
+ assertEquals("pass me an updated string !", e.getMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub("sub", false);
+ fail("iae expected");
+ } catch(IllegalArgumentException e) {
+ assertEquals("updated iae underflow", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub("sub", "expected", false);
+ fail("iae expected");
+ } catch(IllegalArgumentException e) {
+ assertEquals("expected underflow", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub("sub", false, "expected");
+ fail("RuntimeException expected");
+ } catch(RuntimeException e) {
+ assertEquals("updated iae underflow expected", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(ImmutableList.of("un", "deux"), false);
+ fail("RuntimeException expected");
+ } catch(RuntimeException e) {
+ assertEquals("underflow deux", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(ImmutableList.of("un", "deux"), true);
+ assertEquals("updated subFinal", sub.getSubFinal());
+ } catch(IllegalArgumentException e) {
+ fail("unexpected " + e);
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(true, ImmutableList.of("one", "two"));
+ fail("RuntimeException expected");
+ } catch(RuntimeException e) {
+ assertEquals("updated two", e.getLocalizedMessage());
+ }
+
+ try {
+ Constructors.Sub sub = new Constructors.Sub(false, ImmutableList.of("one", "two"));
+ assertEquals("updated success", sub.getSubFinal());
+ } catch(Exception e) {
+ fail("unexpected " + e);
+ }
+
+ Constructors.Sub sub = new Constructors.Sub("sub", true);
+ assertEquals("10.0sub20:patched_base", sub.getBaseFinal());
+ }
+
+ @Test
+ public void patchPublicConstructorWhichCallsAPrivateConstructor() throws Exception {
+ harness.reset();
+ Constructors.PrivateConstructor p = new Constructors.PrivateConstructor();
+ assertEquals("Public constructor calls private constructor.", "Base", p.getString());
+ harness.applyPatch("changeBaseClass");
+ p = new Constructors.PrivateConstructor();
+ assertEquals("Public constructor calls private constructor.", "Patched", p.getString());
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/CovariantTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/CovariantTest.java
new file mode 100644
index 0000000..c08ef7a
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/CovariantTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.CovariantChild;
+import com.example.basic.CovariantParent;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test for covariant return types.
+ */
+public class CovariantTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void invokeCovariantMethod() throws Exception {
+ harness.reset();
+
+ CovariantChild child = new CovariantChild();
+ assertEquals("hellohello", child.getValue());
+ CovariantParent parent = child;
+ assertTrue(parent.getValue() instanceof String);
+
+ harness.applyPatch("changeSubClass");
+ assertEquals("Modified child Modified parent", child.getValue());
+ assertTrue(parent.getValue() instanceof String);
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/EnumTests.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/EnumTests.java
new file mode 100644
index 0000000..0df3a62
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/EnumTests.java
@@ -0,0 +1,44 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.Enums;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class EnumTests {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void testEnums() throws Exception {
+ harness.reset();
+
+ assertEquals("zero", Enums.VALUE_0.getValue());
+ assertEquals("overriden:one:other", Enums.VALUE_1.getValue());
+
+ harness.applyPatch("changeBaseClass");
+
+ assertEquals("zero:patched", Enums.VALUE_0.getValue());
+ assertEquals("patched+overriden:one:patched:other+patched", Enums.VALUE_1.getValue());
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ExceptionsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ExceptionsTest.java
new file mode 100644
index 0000000..752e2dc
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ExceptionsTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.Exceptions;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class ExceptionsTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void testExceptions() throws Exception {
+ harness.reset();
+
+ Exceptions exceptions = new Exceptions();
+ try {
+ exceptions.throwsNamed();
+ fail();
+ } catch (Exceptions.MyException e) {
+ assertEquals("original", e.message);
+ }
+ assertEquals("before:caught[original]:finally", exceptions.catchesNamed());
+ assertEquals("caught: ,protected,static,ctr", exceptions.catchesHiddenNamed());
+
+ try {
+ exceptions.throwsRuntime();
+ fail();
+ } catch (RuntimeException e) {
+ assertEquals("original", e.getMessage());
+ }
+ assertEquals("before:caught[original]:finally", exceptions.catchesRuntime());
+
+ harness.applyPatch("changeBaseClass");
+
+ try {
+ exceptions.throwsNamed();
+ fail();
+ } catch (Exceptions.MyException e) {
+ assertEquals("patched", e.message);
+ }
+ assertEquals("before_p:caught_p[patched]:finally_p", exceptions.catchesNamed());
+ assertEquals("caught_p: ;protected_p;static_p;ctr_p", exceptions.catchesHiddenNamed());
+
+ try {
+ exceptions.throwsRuntime();
+ fail();
+ } catch (RuntimeException e) {
+ assertEquals("patched", e.getMessage());
+ }
+ assertEquals("before_p:caught_p[patched]:finally_p", exceptions.catchesRuntime());
+
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/FieldOverridingTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/FieldOverridingTest.java
new file mode 100644
index 0000000..a6e39f8
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/FieldOverridingTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.FieldOverridingChild;
+import com.example.basic.FieldOverridingGrandChild;
+import com.example.basic.FieldOverridingParent;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test private fields overriding classes.
+ */
+ at SuppressWarnings("AccessStaticViaInstance")
+public class FieldOverridingTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void getField() throws Exception {
+ harness.reset();
+
+ FieldOverridingParent parent = new FieldOverridingParent();
+ assertThat(parent.getField()).isEqualTo("parent");
+ assertThat(FieldOverridingParent.getStaticField()).isEqualTo("static parent");
+ assertThat(parent.getStaticField()).isEqualTo("static parent");
+ assertThat(parent.getStaticCollection()).contains("static parent");
+
+ FieldOverridingChild child = new FieldOverridingChild();
+ assertThat(child.getField()).isEqualTo("parent");
+ assertThat(child.field()).isWithin(0.1).of(11d);
+ assertThat(FieldOverridingChild.staticField()).isWithin(0.1).of(10d);
+ assertThat(child.staticField()).isWithin(0.1).of(10d);
+ assertThat(child.getStaticCollection()).contains("static child");
+ assertThat(child.getCollection()).contains("child");
+
+ FieldOverridingGrandChild fieldOverridingGrandChild = new FieldOverridingGrandChild();
+ assertThat(fieldOverridingGrandChild.getParentCollection()).contains("child");
+
+
+ harness.applyPatch("changeSubClass");
+
+ // use original instances.
+ assertThat(parent.getField()).isEqualTo("modified parent");
+ assertThat(child.getField()).isEqualTo("modified parent");
+ assertThat(FieldOverridingParent.getStaticField()).isEqualTo("modified static parent");
+ assertThat(parent.getStaticField()).isEqualTo("modified static parent");
+ assertThat(parent.getStaticCollection()).contains("static parent");
+
+ assertThat(child.field()).isWithin(0.1).of(11d);
+ assertThat(FieldOverridingChild.staticField()).isWithin(0.1).of(10d);
+ assertThat(child.staticField()).isWithin(0.1).of(10d);
+ assertThat(child.getStaticCollection()).contains("static child");
+ assertThat(child.getCollection()).contains("child");
+
+ assertThat(fieldOverridingGrandChild.getParentCollection()).contains("child");
+
+ // use new instances.
+ parent = new FieldOverridingParent();
+ child = new FieldOverridingChild();
+ assertThat(parent.getField()).isEqualTo("modified modified parent");
+ assertThat(child.getField()).isEqualTo("modified modified parent");
+ assertThat(FieldOverridingParent.getStaticField()).isEqualTo("modified static parent");
+ assertThat(parent.getStaticField()).isEqualTo("modified static parent");
+ assertThat(parent.getStaticCollection()).contains("modified static parent");
+
+ assertThat(child.field()).isWithin(0.1).of(12d);
+ assertThat(FieldOverridingChild.staticField()).isWithin(0.1).of(13d);
+ assertThat(child.staticField()).isWithin(0.1).of(13d);
+ assertThat(child.getStaticCollection()).contains("modified static child");
+ assertThat(child.getCollection()).contains("modified child");
+
+
+ fieldOverridingGrandChild = new FieldOverridingGrandChild();
+ assertThat(fieldOverridingGrandChild.getParentCollection()).contains("modified child");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/FinalFieldsInCtorTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/FinalFieldsInCtorTest.java
new file mode 100644
index 0000000..c4c5676
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/FinalFieldsInCtorTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.FinalFieldsInCtor;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test for classes that sets their final fields in their constructors.
+ */
+public class FinalFieldsInCtorTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void test() throws Exception {
+ harness.reset();
+
+ FinalFieldsInCtor target = new FinalFieldsInCtor("package", "private", "protected", "public");
+ assertThat(target.getPackagePrivateField()).isEqualTo("package");
+ assertThat(target.getPrivateField()).isEqualTo("private");
+ assertThat(target.getProtectedField()).isEqualTo("protected");
+ assertThat(target.getPublicField()).isEqualTo("public");
+
+ harness.applyPatch("changeBaseClass");
+ assertThat(target.getPackagePrivateField()).isEqualTo("method package");
+ assertThat(target.getPrivateField()).isEqualTo("method private");
+ assertThat(target.getProtectedField()).isEqualTo("method protected");
+ assertThat(target.getPublicField()).isEqualTo("method public");
+
+ target = new FinalFieldsInCtor("package", "private", "protected", "public");
+ assertThat(target.getPackagePrivateField()).isEqualTo("method modified package");
+ assertThat(target.getPrivateField()).isEqualTo("method modified private");
+ assertThat(target.getProtectedField()).isEqualTo("method modified protected");
+ assertThat(target.getPublicField()).isEqualTo("method modified public");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/GrandChildTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/GrandChildTest.java
new file mode 100644
index 0000000..1158685
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/GrandChildTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.GrandChild;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.logging.Logger;
+
+/**
+ * Tests when changing grand children of a hierarchy.
+ */
+public class GrandChildTest {
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void changeGrandChildImpl() throws Exception {
+
+ harness.reset();
+ GrandChild grandChild = new GrandChild();
+
+ assertWithMessage("base: grandChild:someProtectedMethodInv()")
+ .that(grandChild.someProtectedMethodInv()).isEqualTo(
+ "protected_method:24.0from grand child12_child"
+ +"protected_method:12.0from grand child24_child");
+
+ assertWithMessage("base: grandChild:somePublicMethodInv()")
+ .that(grandChild.somePublicMethodInv()).isEqualTo(
+ "public_method:24.0from grand child12_child"
+ +"public_method:12.0from grand child24_child");
+
+ assertWithMessage("base: grandChild:equals()")
+ .that(grandChild.equals(grandChild)).isTrue();
+
+ // change the super class of the parentInvocation instance and check that parent's methods
+ // are the new implementations.
+ harness.applyPatch("changeSubClass");
+
+ assertWithMessage("changeSub: grandChild:someProtectedMethodInv()")
+ .that(grandChild.someProtectedMethodInv()).isEqualTo(
+ "protected_method:26.0from grand child13_child"
+ + "protected_method:13.0from grand child26_child");
+
+ assertWithMessage("changeSub: grandChild:somePublicMethodInv()")
+ .that(grandChild.somePublicMethodInv()).isEqualTo(
+ "public_method:26.0from grand child12_child"
+ +"public_method:12.0from grand child26_child");
+
+ assertWithMessage("changeSub: grandChild:equals()")
+ .that(grandChild.equals(grandChild)).isFalse();
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InnerClassInvokerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InnerClassInvokerTest.java
new file mode 100644
index 0000000..aca830d
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InnerClassInvokerTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.InnerClassInvoker;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Invoker class of inner classes methods.
+ */
+public class InnerClassInvokerTest {
+
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+
+ @Test
+ public void innerClassInstantiateOuterClass()
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+
+ harness.reset();
+
+ InnerClassInvoker invoker = new InnerClassInvoker();
+ assertWithMessage("base: invoker.invokeInnerClass()")
+ .that(invoker.invokeInnerClass()).isEqualTo("from builder");
+
+ harness.applyPatch("changeSubClass");
+ assertWithMessage("changeSubClass: invoker.invokeInnerClass()")
+ .that(invoker.invokeInnerClass()).isEqualTo("from builder revisited");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InnerSubclassingOuterTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InnerSubclassingOuterTest.java
new file mode 100644
index 0000000..cf9bc22
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InnerSubclassingOuterTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.InnerOuterInvoker;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test for inner class subclassing outer class with private constructors.
+ */
+public class InnerSubclassingOuterTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+
+ @Test
+ public void innerClassInstantiateOuterClass()
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+
+ harness.reset();
+
+ InnerOuterInvoker invoker = new InnerOuterInvoker();
+ assertWithMessage("base: invoker.innerClassSubclassingOuterClassIntValue()")
+ .that(invoker.innerClassSubclassingOuterClassIntValue()).isEqualTo(12);
+
+ assertWithMessage("base: invoker.innerClassSubclassingOuterClassFieldValue()")
+ .that(invoker.innerClassSubclassingOuterClassFieldValue()).isEqualTo("desserts");
+
+ harness.applyPatch("changeBaseClass");
+ assertWithMessage("changeSubClass: invoker.innerClassSubclassingOuterClassIntValue()")
+ .that(invoker.innerClassSubclassingOuterClassIntValue()).isEqualTo(48);
+
+ assertWithMessage("base: invoker.innerClassSubclassingOuterClassFieldValue()")
+ .that(invoker.innerClassSubclassingOuterClassFieldValue()).isEqualTo("stressed");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InstantRunDisabledTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InstantRunDisabledTest.java
new file mode 100644
index 0000000..67edd96
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/InstantRunDisabledTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.ControlClass;
+import com.ir.disable.InstantRunDisabledClass;
+import com.ir.disable.InstantRunDisabledMethod;
+import com.ir.disable.all.DisabledClassOne;
+import com.ir.disable.all.DisabledClassTwo;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test class for InstantRun disabled package, classes and methods
+ */
+public class InstantRunDisabledTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void testDisabledPackage()
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+ harness.reset();
+
+ DisabledClassOne classOne = new DisabledClassOne();
+ DisabledClassTwo classTwo = new DisabledClassTwo();
+ assertThat(classOne.getValue()).isEqualTo("original");
+ assertThat(classTwo.getValue()).isEqualTo("original");
+
+ ControlClass controlClass = new ControlClass();
+ assertThat(controlClass.getValue()).isEqualTo("hello");
+
+ harness.applyPatch("changeBaseClass");
+ assertThat(classOne.getValue()).isEqualTo("original");
+ assertThat(classTwo.getValue()).isEqualTo("original");
+
+ classOne = new DisabledClassOne();
+ classTwo = new DisabledClassTwo();
+ assertThat(classOne.getValue()).isEqualTo("original");
+ assertThat(classTwo.getValue()).isEqualTo("original");
+
+ // control that runtime was properly setup.
+ assertThat(controlClass.getValue()).isEqualTo("modified");
+ }
+
+ @Test
+ public void testDisabledClass()
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+ harness.reset();
+
+ InstantRunDisabledClass disabledClass = new InstantRunDisabledClass();
+ assertThat(InstantRunDisabledClass.getStaticString()).isEqualTo("Original");
+ assertThat(disabledClass.stringField).isEqualTo("set in original ctor");
+ assertThat(disabledClass.getString()).isEqualTo("original method");
+
+ ControlClass controlClass = new ControlClass();
+ assertThat(controlClass.getValue()).isEqualTo("hello");
+
+ harness.applyPatch("changeBaseClass");
+ assertThat(InstantRunDisabledClass.getStaticString()).isEqualTo("Original");
+ assertThat(disabledClass.stringField).isEqualTo("set in original ctor");
+ assertThat(disabledClass.getString()).isEqualTo("original method");
+
+ disabledClass = new InstantRunDisabledClass();
+ assertThat(InstantRunDisabledClass.getStaticString()).isEqualTo("Original");
+ assertThat(disabledClass.stringField).isEqualTo("set in original ctor");
+ assertThat(disabledClass.getString()).isEqualTo("original method");
+ assertThat(disabledClass.getString()).isEqualTo("original method");
+
+ // control that runtime was properly setup.
+ assertThat(controlClass.getValue()).isEqualTo("modified");
+ }
+
+ @Test
+ public void testDisabledMethods()
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+ harness.reset();
+
+ InstantRunDisabledMethod methods = new InstantRunDisabledMethod();
+ assertThat(methods.getStringField()).isEqualTo("non alterable ctor");
+ assertThat(methods.alterableMethod()).isEqualTo("alterable original");
+ assertThat(methods.nonAlterableMethod()).isEqualTo("non alterable original");
+ assertThat(methods.finalAlterableMethod()).isEqualTo("final alterable original");
+ assertThat(methods.finalNonAlterableMethod()).isEqualTo("final non alterable original");
+ assertThat(InstantRunDisabledMethod.alterableStaticMethod()).isEqualTo(
+ "alterable static original");
+ assertThat(InstantRunDisabledMethod.nonAlterableStaticMethod()).isEqualTo(
+ "non alterable static original");
+
+ assertThat(new InstantRunDisabledMethod("hello").getStringField()).isEqualTo("hello");
+
+ ControlClass controlClass = new ControlClass();
+ assertThat(controlClass.getValue()).isEqualTo("hello");
+
+ harness.applyPatch("changeBaseClass");
+ assertThat(methods.alterableMethod()).isEqualTo("alterable updated");
+ assertThat(methods.nonAlterableMethod()).isEqualTo("non alterable original");
+ assertThat(methods.finalAlterableMethod()).isEqualTo("final alterable updated");
+ assertThat(methods.finalNonAlterableMethod()).isEqualTo("final non alterable original");
+ assertThat(InstantRunDisabledMethod.alterableStaticMethod()).isEqualTo(
+ "alterable static updated");
+ assertThat(InstantRunDisabledMethod.nonAlterableStaticMethod()).isEqualTo(
+ "non alterable static original");
+
+ methods = new InstantRunDisabledMethod();
+ assertThat(methods.getStringField()).isEqualTo("non alterable ctor");
+ assertThat(methods.alterableMethod()).isEqualTo("alterable updated");
+ assertThat(methods.nonAlterableMethod()).isEqualTo("non alterable original");
+ assertThat(methods.finalAlterableMethod()).isEqualTo("final alterable updated");
+ assertThat(methods.finalNonAlterableMethod()).isEqualTo("final non alterable original");
+ assertThat(InstantRunDisabledMethod.alterableStaticMethod()).isEqualTo(
+ "alterable static updated");
+ assertThat(InstantRunDisabledMethod.nonAlterableStaticMethod()).isEqualTo(
+ "non alterable static original");
+
+ assertThat(new InstantRunDisabledMethod("hello").getStringField()).isEqualTo("modified hello");
+
+ // control that runtime was properly setup.
+ assertThat(controlClass.getValue()).isEqualTo("modified");
+ }
+}
+
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/MultipleMethodInvocationsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/MultipleMethodInvocationsTest.java
new file mode 100644
index 0000000..98a4d0f
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/MultipleMethodInvocationsTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.MultipleMethodInvocations;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import java.util.logging.Logger;
+
+/**
+ * Tests that invoke method that delegates to other methods on "this" or other delegate objects.
+ */
+public class MultipleMethodInvocationsTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void changeMultipleMethodInvocations() throws Exception {
+
+ harness.reset();
+ MultipleMethodInvocations testTarget = new MultipleMethodInvocations();
+ assertEquals("foo-4-barbar", testTarget.doAll());
+
+ harness.applyPatch("changeBaseClass");
+ assertEquals("barfoo-5-bar", testTarget.doAll());
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/NoPackageTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/NoPackageTest.java
new file mode 100644
index 0000000..9d3dbaf
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/NoPackageTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.NoPackageAccess;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class NoPackageTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void noPackageObjects() throws Exception {
+ harness.reset();
+
+ NoPackageAccess noPackage = new NoPackageAccess();
+
+ assertWithMessage("base: NoPackageAccess")
+ .that(noPackage.accessNativeArrayMethods()).isEqualTo("[1, 2, 3]: 3");
+
+ harness.applyPatch("changeBaseClass");
+
+ assertWithMessage("changeBaseClass: NoPackageAccess")
+ .that(noPackage.accessNativeArrayMethods()).isEqualTo("[4, 5, 6][:] 3");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PackageAllTypesFieldsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PackageAllTypesFieldsTest.java
new file mode 100644
index 0000000..d3861a5
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PackageAllTypesFieldsTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.PackagePrivateFieldAccess;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Tests for accessing package-private, protected and public fields and methods.
+ */
+public class PackageAllTypesFieldsTest {
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement(true);
+
+ /**
+ * Checks that package private fields and methods are accessible from the bytecode enhanced
+ * version.
+ */
+ @Test
+ public void checkInitialByteCodeChanges() throws Exception {
+
+ harness.reset();
+ PackagePrivateFieldAccess packagePrivateAccess = new PackagePrivateFieldAccess();
+
+ assertWithMessage("base: packagePrivateAccess.accessAllFields()")
+ .that(packagePrivateAccess.accessIntFields()).isEqualTo("19");
+
+ assertWithMessage("base: packagePrivateAccess.accessStringFields()")
+ .that(packagePrivateAccess.accessStringFields()).isEqualTo(
+ "foobarfooblahblahblah");
+
+ assertWithMessage("base: packagePrivateAccess.accessArrayFields()")
+ .that(packagePrivateAccess.accessArrayFields()).isEqualTo(
+ "1,2,3,4,5,1,2,3,1,3,5,7");
+
+ assertWithMessage("base: packagePrivateAccess.accessArrayOfStringFields()")
+ .that(packagePrivateAccess.accessArrayOfStringFields()).isEqualTo(
+ "foo,bar,foo,blah,blah,blah");
+ }
+
+ @Test
+ public void checkByteCodeEnhancedCode() throws Exception {
+
+ harness.applyPatch("changeSubClass");
+ PackagePrivateFieldAccess packagePrivateAccess = new PackagePrivateFieldAccess();
+
+ assertWithMessage("changeSubClass: packagePrivateAccess.accessAllFields()")
+ .that(packagePrivateAccess.accessIntFields()).isEqualTo("19");
+
+ assertWithMessage("changeSubClass: packagePrivateAccess.accessStringFields()")
+ .that(packagePrivateAccess.accessStringFields()).isEqualTo(
+ "blahblahblahfoofoobar");
+
+ assertWithMessage("changeSubClass: packagePrivateAccess.accessArrayFields()")
+ .that(packagePrivateAccess.accessArrayFields()).isEqualTo(
+ "1,3,5,7,1,2,3,1,2,3,4,5");
+
+ assertWithMessage("changeSubClass: packagePrivateAccess.accessArrayOfStringFields()")
+ .that(packagePrivateAccess.accessArrayOfStringFields()).isEqualTo(
+ "blah,blah,blah,foo,foo,bar");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PackagePrivateClassTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PackagePrivateClassTest.java
new file mode 100644
index 0000000..53a43ac
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PackagePrivateClassTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertTrue;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.PackagePrivateInvoker;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.logging.Logger;
+
+/**
+ * Test for package private classes/methods and fields.
+ */
+public class PackagePrivateClassTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+
+ @Test
+ public void verifyBaseClassAccesses() throws Exception {
+ Class packagePrivateClass = PackagePrivateInvoker.class.getClassLoader()
+ .loadClass("com.example.basic.PackagePrivateClass");
+ assertThat(packagePrivateClass).isNotNull();
+ assertTrue(Modifier.isPublic(packagePrivateClass.getModifiers()));
+ for (Method method : packagePrivateClass.getDeclaredMethods()) {
+ assertWithMessage(method.getName() + " is package private")
+ .that(Modifier.isPublic(method.getModifiers())
+ || Modifier.isPrivate(method.getModifiers())
+ || Modifier.isProtected(method.getModifiers())).isTrue();
+ }
+ for (Field field : packagePrivateClass.getDeclaredFields()) {
+ assertWithMessage(field.getName() + " is package private")
+ .that(Modifier.isPublic(field.getModifiers())
+ || Modifier.isPrivate(field.getModifiers())
+ || Modifier.isProtected(field.getModifiers())).isTrue();
+ }
+ }
+
+ @Test
+ public void changeBaseClassTest() throws Exception {
+
+ harness.reset();
+ PackagePrivateInvoker packagePrivateInvoker = new PackagePrivateInvoker();
+
+ assertWithMessage("base: PackagePrivateInvoker:createPackagePrivateObject()")
+ .that(packagePrivateInvoker.createPackagePrivateObject()).isEqualTo("foo");
+
+ assertWithMessage("base: PackagePrivateInvoker:invokeTernaryOperator()")
+ .that(packagePrivateInvoker.invokeTernaryOperator(true))
+ .isEqualTo("package_private");
+
+ assertWithMessage("base: PackagePrivateInvoker:invokeTernaryOperator()")
+ .that(packagePrivateInvoker.invokeTernaryOperator(false))
+ .isEqualTo("another_package_private");
+
+ assertWithMessage("base: PackagePrivateInvoker:ternaryOperatorInConstructorParams()")
+ .that(packagePrivateInvoker.ternaryOperatorInConstructorParams(true))
+ .isEqualTo("true");
+
+ assertWithMessage("base: PackagePrivateInvoker:ternaryOperatorInConstructorParams()")
+ .that(packagePrivateInvoker.ternaryOperatorInConstructorParams(false))
+ .isEqualTo("false");
+
+ assertWithMessage("base: PackagePrivateInvoker:invokePackagePrivateInterface()")
+ .that(packagePrivateInvoker.invokePackagePrivateInterface())
+ .isEqualTo("packagePrivateInterface");
+
+
+ harness.applyPatch("changeSubClass");
+ assertWithMessage("changeSubClass: PackagePrivateInvoker:createPackagePrivateObject()")
+ .that(packagePrivateInvoker.createPackagePrivateObject()).isEqualTo("foobar");
+
+ assertWithMessage("changeSubClass: PackagePrivateInvoker:invokeTernaryOperator()")
+ .that(packagePrivateInvoker.invokeTernaryOperator(true))
+ .isEqualTo("patched_package_private");
+
+ assertWithMessage("changeSubClass: PackagePrivateInvoker:invokeTernaryOperator()")
+ .that(packagePrivateInvoker.invokeTernaryOperator(false))
+ .isEqualTo("patched_another_package_private");
+
+ assertWithMessage("changeSubClass: PackagePrivateInvoker:ternaryOperatorInConstructorParams()")
+ .that(packagePrivateInvoker.ternaryOperatorInConstructorParams(true))
+ .isEqualTo("false");
+
+ assertWithMessage("changeSubClass: PackagePrivateInvoker:ternaryOperatorInConstructorParams()")
+ .that(packagePrivateInvoker.ternaryOperatorInConstructorParams(false))
+ .isEqualTo("true");
+
+ assertWithMessage("changeSubClass: PackagePrivateInvoker:invokePackagePrivateInterface()")
+ .that(packagePrivateInvoker.invokePackagePrivateInterface())
+ .isEqualTo("patched_packagePrivateInterface");
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ParentInvocationTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ParentInvocationTest.java
new file mode 100644
index 0000000..31bedbc
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ParentInvocationTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.ParentInvocation;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.logging.Logger;
+
+public class ParentInvocationTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void changeBaseClassTest() throws Exception {
+
+ harness.reset();
+ ParentInvocation parentInvocation = new ParentInvocation();
+
+ assertWithMessage("base: ParentInvocation:childMethod()")
+ .that(parentInvocation.childMethod(1.2, ".3.", 4)).isEqualTo("child_method:1.2.3.4");
+
+ // change the super class of the parentInvocation instance and check that parent's methods
+ // are the new implementations.
+ harness.applyPatch("changeBaseClass");
+
+ assertWithMessage("base: ParentInvocation:childMethod()")
+ .that(parentInvocation.childMethod(1.2, ".3.", 4)).isEqualTo("child_method:1.2.3.4");
+
+ // Call methods defined on the base class
+ assertWithMessage("changeBaseClass: AllAccessMethods:invokeAll())")
+ .that(parentInvocation.invokeAll(1.2, ".3.", 4)).containsExactly(
+ "patched_private_method:1.2.3.4",
+ "patched_protected_method:1.2.3.4_child",
+ "patched_package_private_method:1.2.3.4_child",
+ "patched_public_method:1.2.3.4_child");
+
+ assertWithMessage("changeBaseClass: AllAccessMethods:invokeAllDispatches())")
+ .that(parentInvocation.invokeAllDispatches(1.2, ".3.", 4)).containsExactly(
+ "patched_private_method:1.2.3.4",
+ "patched_protected_method:1.2.3.4_child",
+ "patched_package_private_method:1.2.3.4_child",
+ "patched_public_method:1.2.3.4_child");
+
+ assertWithMessage("changeBaseClass: AllAccessMethods:invokeAllDoNotOverrideDispatches())")
+ .that(parentInvocation
+ .invokeAllDoNotOverrideDispatches(1.2, ".3.", 4)).containsExactly(
+ "patched_private_method:1.2.3.4",
+ "patched_protected_method:1.2.3.4_child",
+ "patched_package_private_method:1.2.3.4_child",
+ "patched_public_method:1.2.3.4_child");
+
+ // Call methods on the sub class
+ assertWithMessage("changeBaseClass: ParentInvocation:invokeAllParent())")
+ .that(parentInvocation.invokeAllParent(1.2, ".3.", 4)).containsExactly(
+ "patched_protected_method:1.2.3.4",
+ "patched_package_private_method:1.2.3.4",
+ "patched_public_method:1.2.3.4");
+
+ assertWithMessage("changeBaseClass: ParentInvocation:invokeAllFromSubclass())")
+ .that(parentInvocation.invokeAllFromSubclass(1.2, ".3.", 4)).containsExactly(
+ "patched_package_private_method:1.2.3.4_child",
+ "patched_protected_method:1.2.3.4_child",
+ "patched_public_method:1.2.3.4_child",
+ "abstract: 1.2.3.4");
+
+ assertWithMessage("changeBaseClass: ParentInvocation:invokeDoNoOverrideMethodsDirectly())")
+ .that(parentInvocation
+ .invokeDoNoOverrideMethodsDirectly(1.2, ".3.", 4)).containsExactly(
+ "patched_protected_method:1.2.3.4_child",
+ "patched_package_private_method:1.2.3.4_child",
+ "patched_public_method:1.2.3.4_child");
+
+ // Now change the sub class
+ harness.applyPatch("changeSubClass");
+
+ // Call methods on the sub class
+ assertWithMessage("changeBaseClass: ParentInvocation:invokeAllParent())")
+ .that(parentInvocation.invokeAllParent(1.2, ".3.", 4)).containsExactly(
+ "patched_protected_method:1.2.3.4",
+ "patched_package_private_method:1.2.3.4",
+ "patched_public_method:1.2.3.4");
+
+ assertWithMessage("changeBaseClass: ParentInvocation:invokeAllFromSubclass())")
+ .that(parentInvocation.invokeAllFromSubclass(1.2, ".3.", 4)).containsExactly(
+ "patched_package_private_method:1.2.3.4_child",
+ "patched_protected_method:1.2.3.4_child",
+ "patched_public_method:1.2.3.4_child",
+ "abstract_patched: 1.2.3.4");
+
+ assertWithMessage("base: ParentInvocation:childMethod()")
+ .that(parentInvocation.childMethod(1.2, ".3.", 4)).isEqualTo("patched_child_method:1.2.3.4");
+
+ //assertWithMessage("changeBaseClass: ParentInvocation:invokeDoNoOverrideMethodsDirectly())")
+ // .that(parentInvocation
+ // .invokeDoNoOverrideMethodsDirectly(1.2, ".3.", 4)).containsExactly(
+ // "patched_protected_method:1.2.3.4_child",
+ // "patched_package_private_method:1.2.3.4_child",
+ // "patched_public_method:1.2.3.4_child");
+ }
+}
+
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PublicMethodTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PublicMethodTest.java
new file mode 100644
index 0000000..47e61c3
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/PublicMethodTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.AllAccessMethods;
+import com.example.basic.PublicMethodInvoker;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.logging.Logger;
+
+/**
+ * Test to exercise public method invocations.
+ */
+public class PublicMethodTest {
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+
+ @Test
+ public void changeBaseClassTest() throws Exception {
+
+ harness.reset();
+ AllAccessMethods allAccessMethods = new AllAccessMethods() {
+ @Override
+ public String abstractMethod(double a, String b, int c) {
+ return "Abstract";
+ }
+ };
+ PublicMethodInvoker publicMethodInvoker = new PublicMethodInvoker(5);
+
+ assertWithMessage("base: PublicMethodInvoker:invokeAllPublicMethods()")
+ .that(publicMethodInvoker.invokeAllPublicMethods(allAccessMethods)).isEqualTo(
+ "public_method:1.0invoker12-2-6-24.0-true-48.24-a-5");
+
+ assertWithMessage("base: PublicMethodInvoker:invokeAllPublicArrayMethods()")
+ .that(publicMethodInvoker.invokeAllPublicArrayMethods(allAccessMethods)).isEqualTo(
+ "invoker,a,b-3,4,5-5,2,3-true,true,false-a,b,c-12.0,8.0,9.0-56.0,6.0,7.0-5");
+
+ harness.applyPatch("changeSubClass");
+ assertWithMessage("changeSubClass: PublicMethodInvoker:invokeAllPublicMethods()")
+ .that(publicMethodInvoker.invokeAllPublicMethods(allAccessMethods)).isEqualTo(
+ "public_method:1.0invoker_reloaded12-2-6-24.0-true-48.24-a-5");
+
+ assertWithMessage("changeSubClass: PublicMethodInvoker:invokeAllPublicArrayMethods()")
+ .that(publicMethodInvoker.invokeAllPublicArrayMethods(allAccessMethods)).isEqualTo(
+ "invoker:a:b-5:2:3-3:4:5-a:b:c-true:true:false-12.0:8.0:9.0-56.0:6.0:7.0-5");
+
+ publicMethodInvoker = new PublicMethodInvoker(7);
+ assertWithMessage("base: PublicMethodInvoker:invokeAllPublicMethods()")
+ .that(publicMethodInvoker.invokeAllPublicMethods(allAccessMethods)).isEqualTo(
+ "public_method:1.0invoker_reloaded12-2-6-24.0-true-48.24-a-14");
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ReflectiveUserTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ReflectiveUserTest.java
new file mode 100644
index 0000000..94b8c94
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/ReflectiveUserTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.ReflectiveUser;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class ReflectiveUserTest {
+
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @SuppressWarnings("AccessStaticViaInstance")
+ @Test
+ public void checkByteCodeChanges() throws Exception {
+
+ ReflectiveUser reflectiveUser = new ReflectiveUser();
+ harness.reset();
+
+ assertWithMessage("base: ReflectiveUser.reflectiveUser()")
+ .that(reflectiveUser.amIWhoIThinkIam()).isTrue();
+
+ assertWithMessage("base: ReflectiveUser.useReflectionOnPublicMethod")
+ .that(reflectiveUser.useReflectionOnPublicMethod()).isEqualTo("desserts");
+
+ assertWithMessage("base: ReflectiveUser.noReflectionUse")
+ .that(reflectiveUser.noReflectionUse()).isEqualTo("desserts");
+
+ ReflectiveUser reflectionInConstructor = new ReflectiveUser("unused");
+ assertWithMessage("base: ReflectiveUser.reflectionInConstructor")
+ .that(reflectionInConstructor.value).isEqualTo("desserts");
+
+ harness.applyPatch("changeSubClass");
+
+ // unchanged as the use of reflection is banned.
+ assertWithMessage("changeSubClass: ReflectiveUser.useReflectionOnPublicMethod")
+ .that(reflectiveUser.useReflectionOnPublicMethod()).isEqualTo("desserts");
+
+ assertWithMessage("changeSubClass: ReflectiveUser.noReflectionUse")
+ .that(reflectiveUser.noReflectionUse()).isEqualTo("stressed");
+
+ // unchanged as the use of reflection is banned.
+ reflectionInConstructor = new ReflectiveUser("unused");
+ assertWithMessage("base: ReflectiveUser.reflectionInConstructor")
+ .that(reflectionInConstructor.value).isEqualTo("desserts");
+ }
+
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/SuperCallTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/SuperCallTest.java
new file mode 100644
index 0000000..5bb22a8
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/incremental/hotswap/SuperCallTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.build.gradle.internal.incremental.hotswap;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.build.gradle.internal.incremental.fixture.ClassEnhancement;
+import com.example.basic.SuperCall;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class SuperCallTest {
+ @ClassRule
+ public static ClassEnhancement harness = new ClassEnhancement();
+
+ @Test
+ public void mixedSuperAndNew() throws Exception {
+ harness.reset();
+
+ SuperCall.Sub sub = new SuperCall.Sub();
+ assertEquals("super:basesuper:base", sub.mixedCalls());
+
+ harness.applyPatch("changeBaseClass");
+
+ assertEquals("super:base:patchedsuper:base:patched", sub.mixedCalls());
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/ExtendedContentTypeTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/ExtendedContentTypeTest.java
new file mode 100644
index 0000000..67d2b71
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/ExtendedContentTypeTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import static org.junit.Assert.fail;
+
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for the {@link ExtendedContentType} and {@link DefaultContentType}
+ */
+public class ExtendedContentTypeTest {
+
+ @Test
+ public void testValueUniqueness() {
+ Map<Integer, ContentType> allContentTypesValues = new HashMap<Integer, ContentType>();
+ for (ContentType contentType : ExtendedContentType.getAllContentTypes()) {
+ if (allContentTypesValues.containsKey(contentType.getValue())) {
+ fail("Content types " + contentType.name() + " and "
+ + allContentTypesValues.get(contentType.getValue()).name()
+ + " have the same value : " + contentType.getValue());
+ } else {
+ allContentTypesValues.put(contentType.getValue(), contentType);
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/IntermediateFolderUtilsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/IntermediateFolderUtilsTest.java
new file mode 100644
index 0000000..baa9036
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/IntermediateFolderUtilsTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.pipeline;
+
+import org.junit.Test;
+
+/**
+ * TODO
+ */
+public class IntermediateFolderUtilsTest {
+
+ @Test
+ public void testGetContentLocation() throws Exception {
+
+ }
+
+ @Test
+ public void testComputeNonIncrementalInputFromFolder() throws Exception {
+
+ }
+
+ @Test
+ public void testComputeIncrementalInputFromFolder() throws Exception {
+
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TaskTestUtils.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TaskTestUtils.java
new file mode 100644
index 0000000..d2f82c4
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TaskTestUtils.java
@@ -0,0 +1,303 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.TaskContainerAdaptor;
+import com.android.build.gradle.internal.TaskFactory;
+import com.android.build.gradle.internal.core.GradleVariantConfiguration;
+import com.android.build.gradle.internal.model.SyncIssueImpl;
+import com.android.build.gradle.internal.scope.AndroidTaskRegistry;
+import com.android.build.gradle.internal.scope.BaseScope;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.builder.core.ErrorReporter;
+import com.android.builder.model.SyncIssue;
+import com.android.ide.common.blame.Message;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.Project;
+import org.gradle.testfixtures.ProjectBuilder;
+import org.junit.Before;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Base class for Junit-4 based tests that need to manually instantiate tasks to test them.
+ *
+ * Right now this is limited to using the TransformManager but that could be refactored
+ * to allow for other tasks using the AndroidTaskRegistry directly.
+ */
+public class TaskTestUtils {
+ private static final String FOLDER_TEST_PROJECTS = "test-projects";
+
+ protected static final String TASK_NAME = "task name";
+
+ protected TaskFactory taskFactory;
+ protected BaseScope scope;
+ protected TransformManager transformManager;
+ protected FakeErrorReporter errorReporter;
+
+ static class FakeErrorReporter extends ErrorReporter {
+
+ private SyncIssue syncIssue = null;
+
+ protected FakeErrorReporter(@NonNull EvaluationMode mode) {
+ super(mode);
+ }
+
+ public SyncIssue getSyncIssue() {
+ return syncIssue;
+ }
+
+ @NonNull
+ @Override
+ public SyncIssue handleSyncIssue(
+ @Nullable String data, int type, int severity, @NonNull String msg) {
+ // always create a sync issue, no matter what the mode is. This can be used to validate
+ // what error is thrown anyway.
+ syncIssue = new SyncIssueImpl(type, severity, data, msg);
+ return syncIssue;
+ }
+
+ @Override
+ public void receiveMessage(@NonNull Message message) {
+ // do nothing
+ }
+ }
+
+ @Before
+ public void setUp() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(getRootDir(), FOLDER_TEST_PROJECTS + "/basic")).build();
+
+ scope = getScope();
+ errorReporter = new FakeErrorReporter(ErrorReporter.EvaluationMode.IDE);
+ transformManager = new TransformManager(new AndroidTaskRegistry(), errorReporter);
+ taskFactory = new TaskContainerAdaptor(project.getTasks());
+ }
+
+ protected StreamTester streamTester() {
+ return new StreamTester(null);
+ }
+
+ protected StreamTester streamTester(@NonNull final Collection<TransformStream> streams) {
+ return new StreamTester(new FilterableStreamCollection() {
+ @NonNull
+ @Override
+ Collection<TransformStream> getStreams() {
+ return streams;
+ }
+ });
+ }
+
+ /**
+ * Simple class to test that a stream is present in the list of available streams in the
+ * transform manager.
+ *
+ * Right now this expects to find ony a single stream based on the content types and/or scopes
+ * provided.
+ *
+ * Then it optionally test for additional values, if provided.
+ */
+ protected class StreamTester {
+ @NonNull
+ private final FilterableStreamCollection streamCollection;
+ @NonNull
+ private final Set<QualifiedContent.ContentType> contentTypes = Sets.newHashSet();
+ private final Set<QualifiedContent.Scope> scopes = Sets.newHashSet();
+ private List<Object> dependencies = Lists.newArrayList();
+
+ private List<File> jars = Lists.newArrayList();
+ private List<File> folders = Lists.newArrayList();
+ private File rootLocation = null;
+
+ private StreamTester(@Nullable FilterableStreamCollection streamCollection) {
+ if (streamCollection != null) {
+ this.streamCollection = streamCollection;
+ } else {
+ this.streamCollection = transformManager;
+ }
+ }
+
+ StreamTester withContentTypes(@NonNull QualifiedContent.ContentType... contentTypes) {
+ this.contentTypes.addAll(Arrays.asList(contentTypes));
+ return this;
+ }
+
+ StreamTester withScopes(@NonNull QualifiedContent.Scope... scopes) {
+ this.scopes.addAll(Arrays.asList(scopes));
+ return this;
+ }
+
+ StreamTester withJar(@NonNull File file) {
+ jars.add(file);
+ return this;
+ }
+
+ StreamTester withJars(@NonNull Collection<File> files) {
+ jars.addAll(files);
+ return this;
+ }
+
+ StreamTester withFolder(@NonNull File file) {
+ folders.add(file);
+ return this;
+ }
+
+ StreamTester withFolders(@NonNull Collection<File> files) {
+ folders.addAll(files);
+ return this;
+ }
+
+ StreamTester withRootLocation(@NonNull File file) {
+ rootLocation = file;
+ return this;
+ }
+
+
+ StreamTester withDependency(@NonNull Object dependency) {
+ this.dependencies.add(dependency);
+ return this;
+ }
+
+ StreamTester withDependencies(@NonNull Collection<? extends Object> dependencies) {
+ this.dependencies.addAll(dependencies);
+ return this;
+ }
+
+ TransformStream test() {
+ if (contentTypes.isEmpty() && scopes.isEmpty()) {
+ fail("content-type and scopes empty in StreamTester");
+ }
+
+ ImmutableList<TransformStream> streams = streamCollection
+ .getStreams(new TransformManager.StreamFilter() {
+ @Override
+ public boolean accept(@NonNull Set<QualifiedContent.ContentType> types,
+ @NonNull Set<QualifiedContent.Scope> scopes) {
+ return (StreamTester.this.contentTypes.isEmpty() ||
+ types.equals(contentTypes)) &&
+ (StreamTester.this.scopes.isEmpty() ||
+ StreamTester.this.scopes.equals(scopes));
+ }
+ });
+ assertThat(streams)
+ .named(String.format("Streams matching requested %s/%s", scopes, contentTypes))
+ .hasSize(1);
+ TransformStream stream = Iterables.getOnlyElement(streams);
+
+ assertThat(stream.getContentTypes()).containsExactlyElementsIn(contentTypes);
+
+ if (!dependencies.isEmpty()) {
+ assertThat(stream.getDependencies()).containsExactlyElementsIn(dependencies);
+ }
+
+ if (!jars.isEmpty()) {
+ if (!(stream instanceof OriginalStream)) {
+ throw new RuntimeException("StreamTest.withJar(s) used on Stream that is not an OriginalStream");
+ }
+
+ OriginalStream originalStream = (OriginalStream) stream;
+ assertThat(originalStream.getJarFiles().get()).containsExactlyElementsIn(jars);
+ }
+
+ if (!folders.isEmpty()) {
+ if (!(stream instanceof OriginalStream)) {
+ throw new RuntimeException("StreamTest.withFolder(s) used on Stream that is not an OriginalStream");
+ }
+
+ OriginalStream originalStream = (OriginalStream) stream;
+ assertThat(originalStream.getFolders().get()).containsExactlyElementsIn(folders);
+ }
+
+ if (rootLocation != null) {
+ if (!(stream instanceof IntermediateStream)) {
+ throw new RuntimeException("StreamTest.withRootLocation used on Stream that is not an IntermediateStream");
+ }
+
+ IntermediateStream originalStream = (IntermediateStream) stream;
+ assertThat(originalStream.getRootLocation().get()).isEqualTo(rootLocation);
+ }
+
+ return stream;
+ }
+ }
+
+ @NonNull
+ private static BaseScope getScope() {
+ GradleVariantConfiguration mockConfig = mock(GradleVariantConfiguration.class);
+ when(mockConfig.getDirName()).thenReturn("config dir name");
+
+ GlobalScope globalScope = mock(GlobalScope.class);
+ when(globalScope.getBuildDir()).thenReturn(new File("build dir"));
+
+ BaseScope scope = mock(BaseScope.class);
+ when(scope.getDirName()).thenReturn("config dir name");
+ when(scope.getVariantConfiguration()).thenReturn(mockConfig);
+ when(scope.getGlobalScope()).thenReturn(globalScope);
+ when(scope.getTaskName(Mockito.anyString())).thenReturn(TASK_NAME);
+ return scope;
+ }
+
+ /**
+ * Returns the root dir for the gradle plugin project
+ */
+ private File getRootDir() {
+ CodeSource source = getClass().getProtectionDomain().getCodeSource();
+ if (source != null) {
+ URL location = source.getLocation();
+ try {
+ File dir = new File(location.toURI());
+ assertTrue(dir.getPath(), dir.exists());
+
+ File f= dir.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
+ return new File(
+ f,
+ Joiner.on(File.separator).join(
+ "tools",
+ "base",
+ "build-system",
+ "integration-test"));
+ } catch (URISyntaxException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ fail("Fail to get the tools/build folder");
+ return null;
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TestTransform.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TestTransform.java
new file mode 100644
index 0000000..d4aedf3
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TestTransform.java
@@ -0,0 +1,246 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.SecondaryFile;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformInvocation;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.junit.Assert;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Implementation of Transforms for testing.
+ *
+ * This is not meant to be instantiated directly. Use
+ * {@link com.android.build.gradle.internal.pipeline.TestTransform.Builder}.
+ */
+public class TestTransform extends Transform {
+
+ // transform data
+ private final String name;
+ private final Set<ContentType> inputTypes;
+ private final Set<ContentType> outputTypes;
+ private final Set<Scope> scopes;
+ private final Set<Scope> referencedScopes;
+ private final boolean isIncremental;
+ private final List<File> secondaryFileInputs;
+ private final List<SecondaryFile> mSecondaryFiles;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return inputTypes;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getOutputTypes() {
+ return outputTypes;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return scopes;
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getReferencedScopes() {
+ return referencedScopes;
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getSecondaryFileInputs() {
+ return secondaryFileInputs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<SecondaryFile> getSecondaryFiles() {
+ return mSecondaryFiles;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return isIncremental;
+ }
+
+ @Override
+ public void transform(TransformInvocation invocation)
+ throws IOException, TransformException, InterruptedException {
+ this.invocation = invocation;
+ }
+
+ // --- data recorded during the fake execution
+ private TransformInvocation invocation;
+
+ public boolean isIncrementalInputs() {
+ return invocation.isIncremental();
+ }
+
+ public Collection<TransformInput> getInputs() {
+ return invocation.getInputs();
+ }
+
+ public Collection<TransformInput> getReferencedInputs() {
+ return invocation.getReferencedInputs();
+ }
+
+ public TransformOutputProvider getOutput() {
+ return invocation.getOutputProvider();
+ }
+
+ public Collection<SecondaryInput> getSecondaryInputs() {
+ return invocation.getSecondaryInputs();
+ }
+
+ private TestTransform(
+ @NonNull String name,
+ @NonNull Set<ContentType> inputTypes,
+ @NonNull Set<ContentType> outputTypes,
+ @NonNull Set<Scope> scopes,
+ @NonNull Set<Scope> refedScopes,
+ boolean isIncremental,
+ @NonNull List<File> secondaryFileInputs,
+ @NonNull List<SecondaryFile> secondaryInputs) {
+ this.name = name;
+ this.inputTypes = inputTypes;
+ this.outputTypes = outputTypes;
+ this.scopes = scopes;
+ this.referencedScopes = refedScopes;
+ this.isIncremental = isIncremental;
+ this.secondaryFileInputs = ImmutableList.copyOf(secondaryFileInputs);
+ this.mSecondaryFiles = secondaryInputs;
+ }
+
+ /**
+ * Builder for the transforms.
+ */
+ static final class Builder {
+
+ private String name;
+ private final Set<ContentType> inputTypes = new HashSet<ContentType>();
+ private Set<ContentType> outputTypes;
+ private final Set<Scope> scopes = EnumSet.noneOf(Scope.class);
+ private final Set<Scope> referencedScopes = EnumSet.noneOf(Scope.class);
+ private boolean isIncremental = false;
+ private final List<File> secondaryFileInputs = Lists.newArrayList();
+ private final List<SecondaryFile> mSecondaryFiles = Lists.newArrayList();
+
+ Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ Builder setInputTypes(@NonNull ContentType... types) {
+ inputTypes.addAll(Arrays.asList(types));
+ return this;
+ }
+
+ Builder setOutputTypes(@NonNull ContentType... types) {
+ if (outputTypes == null) {
+ outputTypes = new HashSet<ContentType>();
+ }
+ outputTypes.addAll(Arrays.asList(types));
+ return this;
+ }
+
+ Builder setOutputTypes(@NonNull Set<ContentType> types) {
+ outputTypes = ImmutableSet.copyOf(types);
+ return this;
+ }
+
+ Builder setScopes(@NonNull Scope... scopes) {
+ this.scopes.addAll(Arrays.asList(scopes));
+ return this;
+ }
+
+ Builder setReferencedScopes(@NonNull Scope... scopes) {
+ this.referencedScopes.addAll(Arrays.asList(scopes));
+ return this;
+ }
+
+ Builder setIncremental(boolean isIncremental) {
+ this.isIncremental = isIncremental;
+ return this;
+ }
+
+ Builder setSecondaryFile(@NonNull File file) {
+ secondaryFileInputs.add(file);
+ return this;
+ }
+
+ Builder setSecondaryInput(@NonNull SecondaryFile secondaryFile) {
+ mSecondaryFiles.add(secondaryFile);
+ return this;
+ }
+
+ @NonNull
+ TestTransform build() {
+ String name = this.name != null ? this.name : "transform name";
+ Assert.assertFalse(this.inputTypes.isEmpty());
+ Set<ContentType> inputTypes = ImmutableSet.copyOf(this.inputTypes);
+ Set<ContentType> outputTypes = this.outputTypes != null ?
+ ImmutableSet.copyOf(this.outputTypes) : inputTypes;
+ Set<Scope> scopes = Sets.immutableEnumSet(this.scopes);
+ Set<Scope> refedScopes = Sets.immutableEnumSet(this.referencedScopes);
+
+ return new TestTransform(
+ name,
+ inputTypes,
+ outputTypes,
+ scopes,
+ refedScopes,
+ isIncremental,
+ secondaryFileInputs,
+ mSecondaryFiles);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TransformManagerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TransformManagerTest.java
new file mode 100644
index 0000000..9b82784
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TransformManagerTest.java
@@ -0,0 +1,711 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.gradle.internal.scope.AndroidTask;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.builder.model.SyncIssue;
+import com.google.common.collect.Iterables;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.File;
+import java.util.List;
+
+public class TransformManagerTest extends TaskTestUtils {
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void simpleTransform() {
+ // create a stream and add it to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new stream
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // check the stream was consumed.
+ assertThat(streams).doesNotContain(projectClass);
+
+ // and a new one is up
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.PROJECT)
+ .withDependency(TASK_NAME)
+ .test();
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ assertThat(transformTask.consumedInputStreams).containsExactly(projectClass);
+ assertThat(transformTask.referencedInputStreams).isEmpty();
+ assertThat(transformTask.outputStream).isSameAs(Iterables.getOnlyElement(streams));
+ }
+
+ @Test
+ public void missingStreams() {
+ // create a stream and add it to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.RESOURCES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ transformManager.addTransform(taskFactory, scope, t);
+
+ SyncIssue syncIssue = errorReporter.getSyncIssue();
+ assertThat(syncIssue).isNotNull();
+ assertThat(syncIssue.getMessage()).isEqualTo(
+ "Unable to add Transform 'transform name' on variant 'null': requested streams not available: [PROJECT]+[] / [RESOURCES]");
+ assertThat(syncIssue.getType()).isEqualTo(SyncIssue.TYPE_GENERIC);
+ }
+
+ @Test
+ public void referencedScope() {
+ // create streams and add them to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ TransformStream libClasses = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setFolder(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(libClasses);
+
+ TransformStream modulesClasses = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.SUB_PROJECTS)
+ .setFolder(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(modulesClasses);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .setReferencedScopes(Scope.EXTERNAL_LIBRARIES, Scope.SUB_PROJECTS)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new stream
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(3);
+
+ // check the class stream was consumed.
+ assertThat(streams).doesNotContain(projectClass);
+ // check the referenced stream is still present
+ assertThat(streams).containsAllOf(libClasses, modulesClasses);
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ assertThat(transformTask.consumedInputStreams).containsExactly(projectClass);
+ assertThat(transformTask.referencedInputStreams).containsAllOf(libClasses, modulesClasses);
+ }
+
+ @Test
+ public void splitStreamByTypes() throws Exception {
+ // test the case where the input stream has more types than gets consumed,
+ // and we need to create a new stream with the unused types.
+ // (class+res) -[class]-> (class, transformed) + (res, untouched)
+
+ // create streams and add them to the pipeline
+ OriginalStream projectClassAndResources = OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
+ .addScope(Scope.PROJECT)
+ .setFolder(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClassAndResources);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new streams
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(2);
+
+ // check the class stream was consumed.
+ assertThat(streams).doesNotContain(projectClassAndResources);
+
+ // check we now have 2 streams, one for classes and one for resources.
+ // the one for resources should match projectClassAndResources for location and dependency.
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.PROJECT)
+ .withDependency(TASK_NAME)
+ .test();
+ streamTester()
+ .withContentTypes(DefaultContentType.RESOURCES)
+ .withScopes(Scope.PROJECT)
+ .withDependencies(projectClassAndResources.getDependencies())
+ .withFolders(projectClassAndResources.getFolders().get())
+ .test();
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ streamTester(transformTask.consumedInputStreams)
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.PROJECT)
+ .withDependencies(projectClassAndResources.getDependencies())
+ .withFolders(projectClassAndResources.getFolders().get())
+ .test();
+ }
+
+ @Test
+ public void splitReferencedStreamByTypes() {
+ // transform processes classes.
+ // There's a (class, res) stream in a scope that's referenced. This stream should not
+ // be split in two since it's not consumed.
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ TransformStream libClassAndResources = OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(libClassAndResources);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .setReferencedScopes(Scope.EXTERNAL_LIBRARIES)
+ .build();
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new streams
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(2);
+
+ // check the referenced stream is still present in the list (ie not consumed, nor split)
+ assertThat(streams).contains(libClassAndResources);
+ }
+
+ @Test
+ public void splitStreamByScopes() throws Exception {
+ // test the case where the input stream has more types than gets consumed,
+ // and we need to create a new stream with the unused types.
+ // (project+libs) -[project]-> (project, transformed) + (libs, untouched)
+
+ // create streams and add them to the pipeline
+ IntermediateStream projectAndLibsClasses = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT, Scope.EXTERNAL_LIBRARIES)
+ .setRootLocation(new File("folder"))
+ .setDependency("my dependency")
+ .build();
+
+ transformManager.addStream(projectAndLibsClasses);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new streams
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(2);
+
+ // check the class stream was consumed.
+ assertThat(streams).doesNotContain(projectAndLibsClasses);
+
+ // check we now have 2 streams, one for classes and one for resources.
+ // the one for resources should match projectClassAndResources for location and dependency.
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.PROJECT)
+ .withDependency(TASK_NAME)
+ .test();
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.EXTERNAL_LIBRARIES)
+ .withDependencies(projectAndLibsClasses.getDependencies())
+ .withRootLocation(projectAndLibsClasses.getRootLocation().get())
+ .test();
+
+ // we also check that the stream used by the transform only has the requested scopes.
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ streamTester(transformTask.consumedInputStreams)
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.PROJECT)
+ .withDependencies(projectAndLibsClasses.getDependencies())
+ .withRootLocation(projectAndLibsClasses.getRootLocation().get())
+ .test();
+ }
+
+ @Test
+ public void combinedScopes() throws Exception {
+ // create streams and add them to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ TransformStream libClasses = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.EXTERNAL_LIBRARIES)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(libClasses);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT, Scope.EXTERNAL_LIBRARIES)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new stream
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // check the class stream was consumed.
+ assertThat(streams).doesNotContain(projectClass);
+ assertThat(streams).doesNotContain(libClasses);
+
+ // check we now have 1 streams, containing both scopes.
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.PROJECT, Scope.EXTERNAL_LIBRARIES)
+ .withDependency(TASK_NAME)
+ .test();
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ assertThat(transformTask.consumedInputStreams).containsAllOf(projectClass, libClasses);
+ assertThat(transformTask.referencedInputStreams).isEmpty();
+ assertThat(transformTask.outputStream).isSameAs(Iterables.getOnlyElement(streams));
+ }
+
+ @Test
+ public void noOpTransform() throws Exception {
+ // create stream and add them to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setReferencedScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // check the class stream was not consumed.
+ assertThat(transformManager.getStreams()).containsExactly(projectClass);
+
+ // check the task contains no consumed streams
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ assertThat(transformTask.consumedInputStreams).isEmpty();
+ assertThat(transformTask.referencedInputStreams).containsExactly(projectClass);
+ assertThat(transformTask.outputStream).isNull();
+ }
+
+ @Test
+ public void combinedTypes() {
+ // create streams and add them to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ TransformStream libClasses = OriginalStream.builder()
+ .addContentType(DefaultContentType.RESOURCES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(libClasses);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new stream
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // check the class stream was consumed.
+ assertThat(streams).doesNotContain(projectClass);
+ assertThat(streams).doesNotContain(libClasses);
+
+ // check we now have 1 streams, containing both types.
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
+ .withScopes(Scope.PROJECT)
+ .withDependency(TASK_NAME)
+ .test();
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ assertThat(transformTask.consumedInputStreams).containsExactly(projectClass, libClasses);
+ assertThat(transformTask.referencedInputStreams).isEmpty();
+ assertThat(transformTask.outputStream).isSameAs(Iterables.getOnlyElement(streams));
+ }
+
+ @Test
+ public void forkInput() {
+ // test the case where the transform creates an additional stream.
+ // (class) -[class]-> (class) + (dex)
+
+ // create streams and add them to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setOutputTypes(DefaultContentType.CLASSES, ExtendedContentType.DEX)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new stream
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // check the class stream was consumed.
+ assertThat(streams).doesNotContain(projectClass);
+
+ // check we now have a DEX/RES stream.
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES, ExtendedContentType.DEX)
+ .withDependency(TASK_NAME)
+ .test();
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ assertThat(transformTask.consumedInputStreams).containsExactly(projectClass);
+ assertThat(transformTask.referencedInputStreams).isEmpty();
+ assertThat(transformTask.outputStream).isSameAs(Iterables.getOnlyElement(streams));
+ }
+
+ @Test
+ public void forkInputWithMultiScopes() {
+ // test the case where the transform creates an additional stream.
+ // (class) -[class]-> (class) + (dex)
+
+ // create streams and add them to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ TransformStream libClass = OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScope(Scope.SUB_PROJECTS)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(libClass);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setOutputTypes(DefaultContentType.CLASSES, ExtendedContentType.DEX)
+ .setScopes(Scope.PROJECT, Scope.SUB_PROJECTS)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new stream
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // check the class stream was consumed.
+ assertThat(streams).doesNotContain(projectClass);
+ assertThat(streams).doesNotContain(libClass);
+
+ // check we now have a single stream with CLASS/DEX and both scopes.
+ streamTester()
+ .withContentTypes(DefaultContentType.CLASSES, ExtendedContentType.DEX)
+ .withScopes(Scope.PROJECT, Scope.SUB_PROJECTS)
+ .withDependency(TASK_NAME)
+ .test();
+
+ // check the task contains the streams
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ assertThat(transformTask.consumedInputStreams).containsExactly(projectClass, libClass);
+ assertThat(transformTask.referencedInputStreams).isEmpty();
+ assertThat(transformTask.outputStream).isSameAs(Iterables.getOnlyElement(streams));
+ }
+
+ @Test
+ public void forkInputWithSplitStream() {
+ // test the case where the transform creates an additional stream, and the original
+ // stream has more than the requested type.
+ // (class+res) -[class]-> (res, untouched) + (class, transformed) +(dex, transformed)
+
+ // create streams and add them to the pipeline
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("my file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setOutputTypes(DefaultContentType.CLASSES, ExtendedContentType.DEX)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+
+ // get the new stream
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(2);
+
+ // check the multi-stream was consumed.
+ assertThat(streams).doesNotContain(projectClass);
+
+ // check we now have a DEX/CLASS stream.
+ TransformStream outStream = streamTester()
+ .withContentTypes(DefaultContentType.CLASSES, ExtendedContentType.DEX)
+ .withScopes(Scope.PROJECT)
+ .withDependency(TASK_NAME)
+ .test();
+ // and the remaining res stream, with the original dependency, and location
+ streamTester()
+ .withContentTypes(DefaultContentType.RESOURCES)
+ .withScopes(Scope.PROJECT)
+ .withDependency("my dependency")
+ .withJar(new File("my file"))
+ .test();
+
+ // check the task contains the stream
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+ streamTester(transformTask.consumedInputStreams)
+ .withContentTypes(DefaultContentType.CLASSES)
+ .withScopes(Scope.PROJECT)
+ .withDependency("my dependency")
+ .withJar(new File("my file"))
+ .test();
+ assertThat(transformTask.referencedInputStreams).isEmpty();
+ assertThat(transformTask.outputStream).isSameAs(outStream);
+ }
+
+ enum FakeContentType implements QualifiedContent.ContentType {
+ FOO;
+
+ @Override
+ public int getValue() {
+ return 0;
+ }
+ }
+
+ @Test
+ public void wrongInputType() {
+ Transform t = TestTransform.builder()
+ .setInputTypes(FakeContentType.FOO)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(taskFactory, scope, t);
+
+ assertThat(task).isNull();
+
+ SyncIssue syncIssue = errorReporter.getSyncIssue();
+ assertThat(syncIssue).isNotNull();
+ assertThat(syncIssue.getMessage()).isEqualTo(
+ "Custom content types "
+ + "(com.android.build.gradle.internal.pipeline.TransformManagerTest$FakeContentType)"
+ + " are not supported in transforms (transform name)");
+ assertThat(syncIssue.getType()).isEqualTo(SyncIssue.TYPE_GENERIC);
+ }
+
+ @Test
+ public void wrongOutputType() {
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setOutputTypes(FakeContentType.FOO)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(taskFactory, scope, t);
+
+ assertThat(task).isNull();
+
+ SyncIssue syncIssue = errorReporter.getSyncIssue();
+ assertThat(syncIssue).isNotNull();
+ assertThat(syncIssue.getMessage()).isEqualTo(
+ "Custom content types "
+ + "(com.android.build.gradle.internal.pipeline.TransformManagerTest$FakeContentType)"
+ + " are not supported in transforms (transform name)");
+ assertThat(syncIssue.getType()).isEqualTo(SyncIssue.TYPE_GENERIC);
+ }
+
+ @Test
+ public void consumedProvidedOnlyScope() {
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROVIDED_ONLY)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(taskFactory, scope, t);
+
+ assertThat(task).isNull();
+
+ SyncIssue syncIssue = errorReporter.getSyncIssue();
+ assertThat(syncIssue).isNotNull();
+ assertThat(syncIssue.getMessage()).isEqualTo(
+ "PROVIDED_ONLY scope cannot be consumed by Transform 'transform name'");
+ assertThat(syncIssue.getType()).isEqualTo(SyncIssue.TYPE_GENERIC);
+ }
+
+ @Test
+ public void consumedTestedScope() {
+ // add a new transform
+ Transform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.TESTED_CODE)
+ .build();
+
+ // add the transform
+ AndroidTask<TransformTask> task = transformManager.addTransform(taskFactory, scope, t);
+
+ assertThat(task).isNull();
+
+ SyncIssue syncIssue = errorReporter.getSyncIssue();
+ assertThat(syncIssue).isNotNull();
+ assertThat(syncIssue.getMessage()).isEqualTo(
+ "TESTED_CODE scope cannot be consumed by Transform 'transform name'");
+ assertThat(syncIssue.getType()).isEqualTo(SyncIssue.TYPE_GENERIC);
+ }
+
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TransformTaskTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TransformTaskTest.java
new file mode 100644
index 0000000..da29890
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/pipeline/TransformTaskTest.java
@@ -0,0 +1,1990 @@
+/*
+ * 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.build.gradle.internal.pipeline;
+
+import static com.android.utils.FileUtils.mkdirs;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.SecondaryFile;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.gradle.internal.scope.AndroidTask;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class TransformTaskTest extends TaskTestUtils {
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void nonIncWithJarInputInOriginalStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(QualifiedContent.DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("input file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(QualifiedContent.DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getReferencedInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).isEmpty();
+
+ JarInput singleJarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(singleJarInput.getFile()).isEqualTo(
+ Iterables.getOnlyElement(projectClass.getJarFiles().get()));
+ assertThat(singleJarInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleJarInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleJarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void nonIncWithJarInputInIntermediateStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(QualifiedContent.DefaultContentType.CLASSES.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File jarFile = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.JAR);
+ mkdirs(jarFile.getParentFile());
+ Files.write("foo", jarFile, Charsets.UTF_8);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(QualifiedContent.DefaultContentType.CLASSES.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getReferencedInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).isEmpty();
+
+ JarInput singleJarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(singleJarInput.getFile()).isEqualTo(jarFile);
+ assertThat(singleJarInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleJarInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleJarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void nonIncWithReferencedJarInputInOriginalStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(QualifiedContent.DefaultContentType.CLASSES.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(new File("input file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(QualifiedContent.DefaultContentType.CLASSES.CLASSES)
+ .setReferencedScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getReferencedInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).isEmpty();
+
+ JarInput singleJarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(singleJarInput.getFile()).isEqualTo(
+ Iterables.getOnlyElement(projectClass.getJarFiles().get()));
+ assertThat(singleJarInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleJarInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleJarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void nonIncWithReferencedJarInputInIntermediateStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(QualifiedContent.DefaultContentType.CLASSES.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File jarFile = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.JAR);
+ mkdirs(jarFile.getParentFile());
+ Files.write("foo", jarFile, Charsets.UTF_8);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(QualifiedContent.DefaultContentType.CLASSES.CLASSES)
+ .setReferencedScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getReferencedInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).isEmpty();
+
+ JarInput singleJarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(singleJarInput.getFile()).isEqualTo(jarFile);
+ assertThat(singleJarInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleJarInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleJarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void nonIncWithFolderInputInOriginalStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(new File("input file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getReferencedInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).isEmpty();
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(
+ Iterables.getOnlyElement(projectClass.getFolders().get()));
+ assertThat(singleDirectoryInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleDirectoryInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleDirectoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void nonIncWithFolderInputInIntermediateStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File outputFolder = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.DIRECTORY);
+ mkdirs(outputFolder);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getReferencedInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).isEmpty();
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(outputFolder);
+ assertThat(singleDirectoryInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleDirectoryInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleDirectoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void nonIncWithReferencedFolderInputInOriginalStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(new File("input file"))
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setReferencedScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getReferencedInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).isEmpty();
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(
+ Iterables.getOnlyElement(projectClass.getFolders().get()));
+ assertThat(singleDirectoryInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleDirectoryInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleDirectoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void nonIncWithReferencedFolderInputInIntermediateStream()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File outputFolder = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.DIRECTORY);
+ mkdirs(outputFolder);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setReferencedScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with a non-incremental build.
+ transformTask.transform(inputBuilder().build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncremental()).isFalse();
+ assertThat(t.getInputs()).isEmpty();
+
+ Collection<TransformInput> inputs = t.getReferencedInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).isEmpty();
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(outputFolder);
+ assertThat(singleDirectoryInput.getContentTypes()).containsExactlyElementsIn(
+ projectClass.getContentTypes());
+ assertThat(singleDirectoryInput.getScopes()).containsExactlyElementsIn(projectClass.getScopes());
+ assertThat(singleDirectoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void incTaskWithNonIncTransformWithJarInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File jarFile = new File("input file");
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(jarFile)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(jarFile)
+ .build());
+
+ // check that was passed to the transform. Should be non-incremental since the
+ // transform isn't.
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // and the jar input should be status NOTCHANGED
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+
+ JarInput singleJarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(singleJarInput.getFile()).isEqualTo(jarFile);
+ assertThat(singleJarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void incTaskWithNonIncTransformWithJarInputInIntermediateStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File jarFile = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.JAR);
+ mkdirs(jarFile.getParentFile());
+ Files.write("foo", jarFile, Charsets.UTF_8);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(jarFile)
+ .build());
+
+ // check that was passed to the transform. Should be non-incremental since the
+ // transform isn't.
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // and the jar input should be status NOTCHANGED
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+
+ JarInput singleJarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(singleJarInput.getFile()).isEqualTo(jarFile);
+ assertThat(singleJarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void incTaskWithNonIncTransformWithFolderInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = new File("input file");
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+
+ File addedFile = new File(rootFolder, "added");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedFile)
+ .build());
+
+ // check that was passed to the transform. Should be non-incremental since the
+ // transform isn't.
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // and the jar input should be status NOTCHANGED
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(rootFolder);
+ assertThat(singleDirectoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void incTaskWithNonIncTransformWithFolderInputInIntermediateStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File outputFolder = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.DIRECTORY);
+ mkdirs(outputFolder);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+
+ File addedFile = new File(rootFolder, "added");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedFile)
+ .build());
+
+ // check that was passed to the transform. Should be non-incremental since the
+ // transform isn't.
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // and the jar input should be status NOTCHANGED
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(outputFolder);
+ assertThat(singleDirectoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void incrementalJarInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ // Don't create deleted files. This is handled in a separate test.
+ final File addedFile = new File("jar file1");
+ final File changedFile = new File("jar file2");
+ final ImmutableMap<File, Status> jarMap = ImmutableMap.of(
+ addedFile, Status.ADDED,
+ changedFile, Status.CHANGED);
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJars(new Supplier<Collection<File>>() {
+ @Override
+ public Collection<File> get() {
+ // this should not contain the removed jar files.
+ return ImmutableList.of(addedFile, changedFile);
+ }
+ })
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedFile)
+ .modifiedFile(changedFile)
+ .build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ // and the jar input should be status ADDED
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(jarMap.size());
+
+ for (JarInput jarInput : jarInputs) {
+ File file = jarInput.getFile();
+ assertThat(file).isIn(jarMap.keySet());
+ assertThat(jarInput.getStatus()).isSameAs(jarMap.get(file));
+ }
+ }
+
+ @Test
+ public void incrementalJarInputInIntermediateStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File addedJar = output.getContentLocation("added", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.JAR);
+ mkdirs(addedJar.getParentFile());
+ Files.write("foo", addedJar, Charsets.UTF_8);
+ File changedJar = output.getContentLocation("changed", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.JAR);
+ mkdirs(changedJar.getParentFile());
+ Files.write("foo", changedJar, Charsets.UTF_8);
+ // no need to create a deleted jar. It's handled by a separate test.
+ final ImmutableMap<File, Status> jarMap = ImmutableMap.of(
+ addedJar, Status.ADDED,
+ changedJar, Status.CHANGED);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedJar)
+ .modifiedFile(changedJar)
+ .build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ // and the jar input should be status ADDED
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(jarMap.size());
+
+ for (JarInput jarInput : jarInputs) {
+ File file = jarInput.getFile();
+ assertThat(file).isIn(jarMap.keySet());
+ assertThat(jarInput.getStatus()).isSameAs(jarMap.get(file));
+ }
+ }
+
+ @Test
+ public void incrementalFolderInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ File addedFile = new File(rootFolder, "added");
+ File modifiedFile = new File(rootFolder, "modified");
+ File removedFile = new File(rootFolder, "removed");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedFile)
+ .modifiedFile(modifiedFile)
+ .removedFile(removedFile)
+ .build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ // don't test everything, the rest is tested in the tests above.
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(rootFolder);
+
+ Map<File, Status> changedFiles = singleDirectoryInput.getChangedFiles();
+ assertThat(changedFiles).hasSize(3);
+ assertThat(changedFiles).containsEntry(addedFile, Status.ADDED);
+ assertThat(changedFiles).containsEntry(modifiedFile, Status.CHANGED);
+ assertThat(changedFiles).containsEntry(removedFile, Status.REMOVED);
+ }
+
+ @Test
+ public void incrementalFolderInputInIntermediateStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File outputFolder = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.DIRECTORY);
+ mkdirs(outputFolder);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ File addedFile = new File(outputFolder, "added");
+ File modifiedFile = new File(outputFolder, "modified");
+ File removedFile = new File(outputFolder, "removed");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedFile)
+ .modifiedFile(modifiedFile)
+ .removedFile(removedFile)
+ .build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ // don't test everything, the rest is tested in the tests above.
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(outputFolder);
+
+ Map<File, Status> changedFiles = singleDirectoryInput.getChangedFiles();
+ assertThat(changedFiles).hasSize(3);
+ assertThat(changedFiles).containsEntry(addedFile, Status.ADDED);
+ assertThat(changedFiles).containsEntry(modifiedFile, Status.CHANGED);
+ assertThat(changedFiles).containsEntry(removedFile, Status.REMOVED);
+ }
+
+ @Test
+ public void deletedJarInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File jarFile = new File("jar file");
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(jarFile)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ File deletedJar = new File("deleted jar");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .removedFile(deletedJar)
+ .build());
+
+ // in this case we cannot know what types/scopes the missing jar is associated with
+ // so we expect non-incremental mode.
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // don't test everything, the rest is tested in the tests above.
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+ JarInput jarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(jarInput.getFile()).isEqualTo(jarFile);
+ assertThat(jarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void deletedJarInputInIntermediateStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File jarFile = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.JAR);
+ mkdirs(jarFile.getParentFile());
+ // have to write content to create the file.
+ Files.write("foo", jarFile, Charsets.UTF_8);
+ // for this one just get the location. It won't be created, but we know the location
+ // is correct.
+ File deletedJarFile = output.getContentLocation("deleted", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.JAR);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .removedFile(deletedJarFile)
+ .build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ // don't test everything, the rest is tested in the tests above.
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(2);
+
+ List<File> jarLocations = ImmutableList.of(jarFile, deletedJarFile);
+ for (JarInput jarInput : jarInputs) {
+ File file = jarInput.getFile();
+ assertThat(file).isIn(jarLocations);
+
+ if (file.equals(jarFile)) {
+ assertThat(jarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ } else {
+ assertThat(jarInput.getStatus()).isSameAs(Status.REMOVED);
+ }
+ }
+ }
+
+ @Test
+ public void deletedFolderInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+ OriginalStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ File deletedFolder = new File("deleted");
+ File removedFile = new File(deletedFolder, "removed");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .removedFile(removedFile)
+ .build());
+
+ // in this case we cannot know what types/scopes the missing file/folder is associated with
+ // so we expect non-incremental mode.
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // don't test everything, the rest is tested in the tests above.
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+ DirectoryInput directoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(directoryInput.getFile()).isEqualTo(rootFolder);
+ assertThat(directoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void deletedFolderInputInIntermediateStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream projectClass = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = projectClass.asOutput();
+ File outputFolder = output.getContentLocation("foo", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.DIRECTORY);
+ mkdirs(outputFolder);
+ // for this one just get the location. It won't be created, but we know the location
+ // is correct.
+ File deletedOutputFolder = output.getContentLocation("foo2", projectClass.getContentTypes(),
+ projectClass.getScopes(), Format.DIRECTORY);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ File removedFile = new File(deletedOutputFolder, "removed");
+ File removedFile2 = new File(deletedOutputFolder, "removed2");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .removedFile(removedFile)
+ .removedFile(removedFile2)
+ .build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ // don't test everything, the rest is tested in the tests above.
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(2);
+
+ List<File> folderLocations = ImmutableList.of(outputFolder, deletedOutputFolder);
+ for (DirectoryInput directoryInput : directoryInputs) {
+ File file = directoryInput.getFile();
+ assertThat(file).isIn(folderLocations);
+ Map<File, Status> changedFiles = directoryInput.getChangedFiles();
+
+ if (file.equals(outputFolder)) {
+ assertThat(changedFiles).isEmpty();
+ } else {
+ assertThat(changedFiles).hasSize(2);
+ assertThat(changedFiles).containsEntry(removedFile, Status.REMOVED);
+ assertThat(changedFiles).containsEntry(removedFile2, Status.REMOVED);
+ }
+ }
+ }
+
+ @Test
+ public void incrementalTestComplexOriginalStreamOnly()
+ throws TransformException, InterruptedException, IOException {
+ // test with multiple scopes, both with multiple streams, and consumed and referenced scopes.
+
+ File scope1Jar = new File("jar file1");
+ File scope3Jar = new File("jar file2");
+ File scope1RootFolder = new File("folder file1");
+ File scope3RootFolder = new File("folder file2");
+
+ OriginalStream scope1 = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(scope1Jar)
+ .setFolder(scope1RootFolder)
+ .setDependency("my dependency")
+ .build();
+
+ File scope2Root = Files.createTempDir();
+ scope2Root.deleteOnExit();
+ IntermediateStream scope2 = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT_LOCAL_DEPS)
+ .setRootLocation(scope2Root)
+ .setDependency("my dependency")
+ .build();
+
+ // use the output version of this stream to create some content.
+ // only these jars could be detected as deleted.
+ TransformOutputProvider output2 = scope2.asOutput();
+ File scope2RootFolder = output2.getContentLocation("foo", scope2.getContentTypes(),
+ scope2.getScopes(), Format.DIRECTORY);
+ mkdirs(scope2RootFolder);
+ // for this one just get the location. It won't be created, but we know the location
+ // is correct.
+ File scope2Jar = output2.getContentLocation("foo2", scope2.getContentTypes(),
+ scope2.getScopes(), Format.JAR);
+
+ OriginalStream scope3 = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.SUB_PROJECTS)
+ .setJar(scope3Jar)
+ .setFolder(scope3RootFolder)
+ .setDependency("my dependency")
+ .build();
+
+ File scope4Root = Files.createTempDir();
+ scope4Root.deleteOnExit();
+ IntermediateStream scope4 = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.EXTERNAL_LIBRARIES)
+ .setRootLocation(scope4Root)
+ .setDependency("my dependency")
+ .build();
+
+ // use the output version of this stream to create some content.
+ // only these jars could be detected as deleted.
+ TransformOutputProvider output4 = scope4.asOutput();
+ File scope4RootFolder = output4.getContentLocation("foo", scope4.getContentTypes(),
+ scope4.getScopes(), Format.DIRECTORY);
+ mkdirs(scope4RootFolder);
+ // for this one just get the location. It won't be created, but we know the location
+ // is correct.
+ File scope4Jar = output4.getContentLocation("foo2", scope4.getContentTypes(),
+ scope4.getScopes(), Format.JAR);
+
+
+ final ImmutableMap<File, Status> inputJarMap1 = ImmutableMap.of(
+ scope1Jar, Status.ADDED,
+ scope2Jar, Status.REMOVED);
+
+ final ImmutableMap<File, Status> inputJarMap2 = ImmutableMap.of(
+ scope3Jar, Status.ADDED,
+ scope4Jar, Status.REMOVED);
+
+ transformManager.addStream(scope1);
+ transformManager.addStream(scope2);
+ transformManager.addStream(scope3);
+ transformManager.addStream(scope4);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT, Scope.PROJECT_LOCAL_DEPS)
+ .setReferencedScopes(Scope.EXTERNAL_LIBRARIES, Scope.SUB_PROJECTS)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(3); // the new output and the 2 referenced ones.
+
+ // call the task with incremental data
+ File addedFile1 = new File(scope1RootFolder, "added");
+ File removedFile2 = new File(scope2RootFolder, "removed");
+ File addedFile3 = new File(scope3RootFolder, "added");
+ File removedFile4 = new File(scope4RootFolder, "removed");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(scope1Jar)
+ .removedFile(scope2Jar)
+ .addedFile(scope3Jar)
+ .removedFile(scope4Jar)
+ .addedFile(addedFile1)
+ .addedFile(addedFile3)
+ .removedFile(removedFile2)
+ .removedFile(removedFile4)
+ .build());
+
+ // check that was passed to the transform.
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(2);
+ // we don't care much about the separation of the 3 inputs, so we'll mix the jar
+ // and folder inputs in single lists.
+ List<JarInput> jarInputs = Lists.newArrayListWithCapacity(2);
+ List<DirectoryInput> directoryInputs = Lists.newArrayListWithCapacity(2);
+ for (TransformInput input : inputs) {
+ jarInputs.addAll(input.getJarInputs());
+ directoryInputs.addAll(input.getDirectoryInputs());
+ }
+
+ assertThat(jarInputs).hasSize(2);
+
+ for (JarInput jarInput : jarInputs) {
+ File file = jarInput.getFile();
+ assertThat(file).isIn(inputJarMap1.keySet());
+ assertThat(jarInput.getStatus()).isSameAs(inputJarMap1.get(file));
+ }
+
+ assertThat(directoryInputs).hasSize(2);
+
+ for (DirectoryInput directoryInput : directoryInputs) {
+ Map<File, Status> changedFiles = directoryInput.getChangedFiles();
+ assertThat(changedFiles).hasSize(1);
+
+ File file = directoryInput.getFile();
+ assertThat(file).isAnyOf(scope1RootFolder, scope2RootFolder);
+
+ if (file.equals(scope1RootFolder)) {
+ assertThat(changedFiles).containsEntry(addedFile1, Status.ADDED);
+ } else if (file.equals(scope2RootFolder)) {
+ assertThat(changedFiles).containsEntry(removedFile2, Status.REMOVED);
+ }
+ }
+
+ // now check on the referenced inputs.
+ Collection<TransformInput> referencedInputs = t.getReferencedInputs();
+ assertThat(referencedInputs).hasSize(2);
+ // we don't care much about the separation of the 3 inputs, so we'll mix the jar
+ // and folder inputs in single lists.
+ jarInputs = Lists.newArrayListWithCapacity(2);
+ directoryInputs = Lists.newArrayListWithCapacity(2);
+ for (TransformInput input : referencedInputs) {
+ jarInputs.addAll(input.getJarInputs());
+ directoryInputs.addAll(input.getDirectoryInputs());
+ }
+
+ assertThat(jarInputs).hasSize(2);
+
+ for (JarInput jarInput : jarInputs) {
+ File file = jarInput.getFile();
+ assertThat(file).isIn(inputJarMap2.keySet());
+ assertThat(jarInput.getStatus()).isSameAs(inputJarMap2.get(file));
+ }
+
+ assertThat(directoryInputs).hasSize(2);
+
+ for (DirectoryInput directoryInput : directoryInputs) {
+ Map<File, Status> changedFiles = directoryInput.getChangedFiles();
+ assertThat(changedFiles).hasSize(1);
+
+ File file = directoryInput.getFile();
+ assertThat(file).isAnyOf(scope3RootFolder, scope4RootFolder);
+
+ if (file.equals(scope3RootFolder)) {
+ assertThat(changedFiles).containsEntry(addedFile3, Status.ADDED);
+ } else if (file.equals(scope4RootFolder)) {
+ assertThat(changedFiles).containsEntry(removedFile4, Status.REMOVED);
+ }
+ }
+ }
+
+ @Test
+ public void secondaryFileAddedWithJarInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File jarFile = Files.createTempDir();
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(jarFile)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform, with a 2ndary file.
+ File secondaryFile = new File("secondary file");
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .setSecondaryFile(secondaryFile)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ // including a normal, in stream changed file
+ File addedFile = new File(jarFile, "added");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedFile)
+ .addedFile(secondaryFile)
+ .build());
+
+ // check that was passed to the transform. Incremental should be off due
+ // to secondary file
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // Also check that the regular inputs are not marked as anything special
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<JarInput> jarInputs = input.getJarInputs();
+ assertThat(jarInputs).isNotNull();
+ assertThat(jarInputs).hasSize(1);
+
+ JarInput singleJarInput = Iterables.getOnlyElement(jarInputs);
+ assertThat(singleJarInput.getFile()).isEqualTo(jarFile);
+ assertThat(singleJarInput.getStatus()).isSameAs(Status.NOTCHANGED);
+ }
+
+ @Test
+ public void secondaryFileAddedWithFolderInputInOriginalStream()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setFolder(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform, with a 2ndary file.
+ File secondaryFile = new File("secondary file");
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .setSecondaryFile(secondaryFile)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ // including a normal, in stream changed file
+ File addedFile = new File(rootFolder, "added");
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(addedFile)
+ .addedFile(secondaryFile)
+ .build());
+
+ // check that was passed to the transform. Incremental should be off due
+ // to secondary file
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // Also check that the regular inputs are not marked as anything special
+ Collection<TransformInput> inputs = t.getInputs();
+ assertThat(inputs).hasSize(1);
+
+ TransformInput input = Iterables.getOnlyElement(inputs);
+ Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
+ assertThat(directoryInputs).isNotNull();
+ assertThat(directoryInputs).hasSize(1);
+
+ DirectoryInput singleDirectoryInput = Iterables.getOnlyElement(directoryInputs);
+ assertThat(singleDirectoryInput.getFile()).isEqualTo(rootFolder);
+ assertThat(singleDirectoryInput.getChangedFiles()).isEmpty();
+ }
+
+ @Test
+ public void secondaryFileModified()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File jarFile = new File("jar file");
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(jarFile)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform, with a 2ndary file.
+ File secondaryFile = new File("secondary file");
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .setSecondaryFile(secondaryFile)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ // including a normal, in stream changed file
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(jarFile)
+ .modifiedFile(secondaryFile)
+ .build());
+
+ // check that was passed to the transform. Incremental should be off due
+ // to secondary file
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // checks on the inputs are done in the "secondary file added" tests
+ }
+
+ @Test
+ public void secondaryFileModifiedWithIncrementalCapabilities()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File jarFile = new File("jar file");
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(jarFile)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform, with a 2ndary file.
+ File secondaryFile = new File("secondary file");
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .setSecondaryInput(new SecondaryFile(secondaryFile, true /* supportsIncrementalBuild */))
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data including a normal, in stream changed file
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(jarFile)
+ .modifiedFile(secondaryFile)
+ .build());
+
+ // check that was passed to the transform. Incremental should be off due
+ // to secondary file
+ assertThat(t.isIncrementalInputs()).isTrue();
+
+ // assert that the secondary file change event was provided.
+ assertThat(t.getSecondaryInputs()).hasSize(1);
+ SecondaryInput change = Iterables.getOnlyElement(t.getSecondaryInputs());
+ assertThat(change.getStatus()).isEqualTo(Status.CHANGED);
+ assertThat(change.getSecondaryInput().getFile()).isEqualTo(secondaryFile);
+
+ // now delete the file.
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .removedFile(secondaryFile)
+ .build());
+
+ assertThat(t.isIncrementalInputs()).isTrue();
+ assertThat(t.getSecondaryInputs()).hasSize(1);
+ change = Iterables.getOnlyElement(t.getSecondaryInputs());
+ assertThat(change.getStatus()).isEqualTo(Status.REMOVED);
+ assertThat(change.getSecondaryInput().getFile()).isEqualTo(secondaryFile);
+
+ // and add it back..
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(secondaryFile)
+ .build());
+
+ assertThat(t.isIncrementalInputs()).isTrue();
+ assertThat(t.getSecondaryInputs()).hasSize(1);
+ change = Iterables.getOnlyElement(t.getSecondaryInputs());
+ assertThat(change.getStatus()).isEqualTo(Status.ADDED);
+ assertThat(change.getSecondaryInput().getFile()).isEqualTo(secondaryFile);
+ }
+
+ @Test
+ public void secondaryFileRemoved()
+ throws TransformException, InterruptedException, IOException {
+ // create a stream and add it to the pipeline
+ File jarFile = new File("jar file");
+ TransformStream projectClass = OriginalStream.builder()
+ .addContentType(DefaultContentType.CLASSES)
+ .addScope(Scope.PROJECT)
+ .setJar(jarFile)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(projectClass);
+
+ // create the transform, with a 2ndary file.
+ File secondaryFile = new File("secondary file");
+ TestTransform t = TestTransform.builder()
+ .setIncremental(true)
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .setSecondaryFile(secondaryFile)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(1);
+
+ // call the task with incremental data
+ // including a normal, in stream changed file
+ transformTask.transform(inputBuilder()
+ .incremental()
+ .addedFile(jarFile)
+ .removedFile(secondaryFile)
+ .build());
+
+ // check that was passed to the transform. Incremental should be off due
+ // to secondary file
+ assertThat(t.isIncrementalInputs()).isFalse();
+
+ // checks on the inputs are done in the "secondary file added" tests
+ }
+
+ @Test
+ public void streamWithTooManyScopes()
+ throws IOException, TransformException, InterruptedException {
+ // create a stream and add it to the pipeline
+ File rootFolder = Files.createTempDir();
+ rootFolder.deleteOnExit();
+
+ IntermediateStream stream = IntermediateStream.builder()
+ .addContentTypes(DefaultContentType.CLASSES)
+ .addScopes(Scope.PROJECT, Scope.EXTERNAL_LIBRARIES)
+ .setRootLocation(rootFolder)
+ .setDependency("my dependency")
+ .build();
+ transformManager.addStream(stream);
+
+ // use the output version of this stream to create some content.
+ TransformOutputProvider output = stream.asOutput();
+ File outputFolder = output.getContentLocation("foo", stream.getContentTypes(),
+ stream.getScopes(), Format.DIRECTORY);
+ mkdirs(outputFolder);
+
+ // create the transform
+ TestTransform t = TestTransform.builder()
+ .setInputTypes(DefaultContentType.CLASSES)
+ .setScopes(Scope.PROJECT)
+ .build();
+
+ // add the transform to the manager
+ AndroidTask<TransformTask> task = transformManager.addTransform(
+ taskFactory, scope, t);
+ // and get the real gradle task object
+ TransformTask transformTask = (TransformTask) taskFactory.named(task.getName());
+ assertThat(transformTask).isNotNull();
+
+ // get the current output Stream in the transform manager.
+ List<TransformStream> streams = transformManager.getStreams();
+ assertThat(streams).hasSize(2);
+
+ // expect an exception at runtime.
+ exception.expect(RuntimeException.class);
+ exception.expectMessage("error");
+ transformTask.transform(inputBuilder().build());
+ }
+
+ static InputFileBuilder fileBuilder() {
+ return new InputFileBuilder();
+ }
+
+ /**
+ * Builder to create a mock of InputFileDetails.
+ */
+ static class InputFileBuilder {
+ private boolean added = false;
+ private boolean modified = false;
+ private boolean removed = false;
+ private File file = null;
+
+ InputFileBuilder added() {
+ this.added = true;
+ return this;
+ }
+
+ InputFileBuilder modified() {
+ this.modified = true;
+ return this;
+ }
+
+ InputFileBuilder removed() {
+ this.removed = true;
+ return this;
+ }
+
+ InputFileBuilder setFile(File file) {
+ this.file = file;
+ return this;
+ }
+
+ InputFileDetails build() {
+ assertTrue(added ^ modified ^ removed);
+ assertNotNull(file);
+
+ return new InputFileDetails() {
+
+ @Override
+ public boolean isAdded() {
+ return added;
+ }
+
+ @Override
+ public boolean isModified() {
+ return modified;
+ }
+
+ @Override
+ public boolean isRemoved() {
+ return removed;
+ }
+
+ @Override
+ public File getFile() {
+ return file;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("file", getFile())
+ .add("added", isAdded())
+ .add("modified", isModified())
+ .add("removed", isRemoved())
+ .toString();
+ }
+ };
+ }
+ }
+
+ static InputBuilder inputBuilder() {
+ return new InputBuilder();
+ }
+
+ /**
+ * Builder to create a mock of IncrementalTaskInputs
+ */
+ static class InputBuilder {
+ private boolean incremental = false;
+ private final List<InputFileDetails> files = Lists.newArrayList();
+
+ InputBuilder incremental() {
+ this.incremental = true;
+ return this;
+ }
+
+ InputBuilder addedFile(@NonNull File file) {
+ files.add(fileBuilder().added().setFile(file).build());
+ return this;
+ }
+
+ InputBuilder modifiedFile(@NonNull File file) {
+ files.add(fileBuilder().modified().setFile(file).build());
+ return this;
+ }
+
+ InputBuilder removedFile(@NonNull File file) {
+ files.add(fileBuilder().removed().setFile(file).build());
+ return this;
+ }
+
+ IncrementalTaskInputs build() {
+ return new IncrementalTaskInputs() {
+
+ @Override
+ public boolean isIncremental() {
+ return incremental;
+ }
+
+ @Override
+ public void outOfDate(Action<? super InputFileDetails> action) {
+ for (InputFileDetails details : files) {
+ if (details.isAdded() || details.isModified()) {
+ action.execute(details);
+ }
+ }
+ }
+
+ @Override
+ public void removed(Action<? super InputFileDetails> action) {
+ for (InputFileDetails details : files) {
+ if (details.isRemoved()) {
+ action.execute(details);
+ }
+ }
+ }
+ };
+ }
+ }
+
+}
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistenceTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/publishing/FilterDataPersistenceTest.java
similarity index 100%
rename from base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/internal/publishing/FilterDataPersistenceTest.java
rename to build-system/gradle-core/src/test/java/com/android/build/gradle/internal/publishing/FilterDataPersistenceTest.java
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/ChangeRecordsTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/ChangeRecordsTest.java
new file mode 100644
index 0000000..ef50348
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/ChangeRecordsTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.build.api.transform.Status;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Tests for the {@link ChangeRecords} class.
+ */
+public class ChangeRecordsTest {
+
+ @Rule
+ public TemporaryFolder tmpFolder = new TemporaryFolder();
+
+ @Test
+ public void testNormalOverriding() {
+ String someFilePath = "path/to/some/file";
+ ChangeRecords changeRecords = new ChangeRecords();
+ changeRecords.add(Status.CHANGED, someFilePath);
+ // now override it.
+ changeRecords.add(Status.REMOVED, someFilePath);
+
+ assertThat(changeRecords.getChangeFor(someFilePath)).isEqualTo(Status.REMOVED);
+ }
+
+ @Test
+ public void testNoChange() {
+ ChangeRecords changeRecords = new ChangeRecords();
+ assertThat(changeRecords.getChangeFor("foo")).isNull();
+ }
+
+ @Test
+ public void testPersistence() throws IOException {
+ ChangeRecords changeRecords = new ChangeRecords();
+ changeRecords.add(Status.CHANGED, "changed/file");
+ changeRecords.add(Status.REMOVED, "removed/file");
+ changeRecords.add(Status.ADDED, "added/file");
+ File file = tmpFolder.newFile("ChangeRecordsTest.txt");
+ changeRecords.write(file);
+ List<String> records = Files.readLines(file, Charsets.UTF_8);
+ assertThat(records).containsExactlyElementsIn(
+ ImmutableList.of("CHANGED,changed/file",
+ "REMOVED,removed/file", "ADDED,added/file"));
+ }
+
+ @Test
+ public void testLoading() throws IOException {
+ File file = tmpFolder.newFile("ChangeRecordsTest.txt");
+ Files.write("CHANGED,/some/changed/file\n"
+ + "CHANGED,/another/changed/file\n"
+ + "REMOVED,/some/removed/file\n"
+ + "ADDED,/some/added/file\n", file, Charsets.UTF_8);
+ ChangeRecords changeRecords = ChangeRecords.load(file);
+ assertThat(changeRecords.getChangeFor("/some/changed/file")).isEqualTo(Status.CHANGED);
+ assertThat(changeRecords.getChangeFor("/another/changed/file")).isEqualTo(Status.CHANGED);
+ assertThat(changeRecords.getChangeFor("/some/removed/file")).isEqualTo(Status.REMOVED);
+ assertThat(changeRecords.getChangeFor("/some/added/file")).isEqualTo(Status.ADDED);
+ }
+
+ @Test
+ public void testMerging() {
+ String someFilePath = "path/to/some/file";
+ ChangeRecords changeRecords = new ChangeRecords();
+ changeRecords.add(Status.CHANGED, someFilePath);
+
+ // now create an older set of changes.
+ ChangeRecords olderChangeRecords = new ChangeRecords();
+ olderChangeRecords.add(Status.ADDED, someFilePath);
+ olderChangeRecords.add(Status.CHANGED, "some/other/file");
+ changeRecords.addAll(olderChangeRecords);
+
+ assertThat(changeRecords.getChangeFor("path/to/some/file")).isEqualTo(Status.CHANGED);
+ assertThat(changeRecords.getChangeFor("some/other/file")).isEqualTo(Status.CHANGED);
+ }
+
+ @Test
+ public void testGetFilesForStatusAPI() {
+ ChangeRecords changeRecords = new ChangeRecords();
+ changeRecords.add(Status.ADDED, "some/added/file1");
+ changeRecords.add(Status.ADDED, "some/added/file2");
+ changeRecords.add(Status.CHANGED, "some/other/file1");
+ changeRecords.add(Status.CHANGED, "some/other/file2");
+ changeRecords.add(Status.REMOVED, "some/removed/file1");
+ changeRecords.add(Status.REMOVED, "some/removed/file2");
+
+ assertThat(changeRecords.getFilesForStatus(Status.ADDED)).containsExactly(
+ "some/added/file1", "some/added/file2");
+ assertThat(changeRecords.getFilesForStatus(Status.CHANGED)).containsExactly(
+ "some/other/file1", "some/other/file2");
+ assertThat(changeRecords.getFilesForStatus(Status.REMOVED)).containsExactly(
+ "some/removed/file1", "some/removed/file2");
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/FileFilterTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/FileFilterTest.java
new file mode 100644
index 0000000..af3c212
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/FileFilterTest.java
@@ -0,0 +1,434 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+
+/**
+ * Tests for {@link FileFilter}
+ */
+public class FileFilterTest {
+
+ @Rule
+ public TemporaryFolder tmpFolder = new TemporaryFolder();
+
+ private PackagingOptions mPackagingOptions;
+ private FileFilter mFileFilter;
+ private File mMergedFolder;
+ private File mExpandedJar1, mExpandedJar2, mExpandedJar3;
+ private List<FileFilter.SubStream> mPackagedJarExpansionSubStreams;
+
+ /**
+ * Create temporary folders to simulate expanded folders with java resources embedded
+ */
+ @Before
+ public void prepareFolders() throws IOException {
+ mExpandedJar1 = tmpFolder.newFolder("jar1");
+ mExpandedJar2 = tmpFolder.newFolder("jar2");
+ mExpandedJar3 = tmpFolder.newFolder("jar3");
+
+ mPackagedJarExpansionSubStreams = ImmutableList.of(
+ new FileFilter.SubStream(mExpandedJar1, "sExpandedJar1"),
+ new FileFilter.SubStream(mExpandedJar2, "sExpandedJar2"),
+ new FileFilter.SubStream(mExpandedJar3, "sExpandedJar3")
+ );
+
+ mMergedFolder = tmpFolder.newFolder("merged");
+
+ assertMergedFilesCount(0);
+ }
+
+ @Before
+ public void createPackagingOptions() {
+ mPackagingOptions = new PackagingOptions();
+ }
+
+ private void assertMergedFilesCount(int i) {
+ File[] files = mMergedFolder.listFiles();
+ assertNotNull(files);
+ assertTrue(files.length == i);
+ }
+
+ @Test
+ public void testSimpleCopy() throws IOException {
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+
+ assertFalse(new File(mMergedFolder, "foo/text.properties").exists());
+ File changedFile = createFile(mExpandedJar1, "foo/text.properties");
+ mFileFilter.handleChanged(mMergedFolder, changedFile);
+ assertTrue(new File(mMergedFolder, "foo/text.properties").exists());
+ }
+
+ @Test
+ public void testSimpleExclusion() throws IOException {
+ setExcludes(ImmutableSet.of(FileUtils.join("foo", "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar1, "foo/text.properties"));
+ assertMergedFilesCount(0);
+ }
+
+ @Test
+ public void testExclusionFromMultipleFiles() throws IOException {
+ setExcludes(ImmutableSet.of(FileUtils.join("foo", "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar1, "foo/text.properties"));
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar2, "foo/text.properties"));
+ assertMergedFilesCount(0);
+ }
+
+ @Test
+ public void testMultipleExclusions() throws IOException {
+ setExcludes(ImmutableSet.of(
+ FileUtils.join("foo", "text.properties"),
+ FileUtils.join("bar", "other.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar1, "foo/text.properties"));
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar2, "bar/other.properties"));
+ assertMergedFilesCount(0);
+ }
+
+ @Test
+ public void textNonExclusion() throws IOException {
+ setExcludes(ImmutableSet.of(
+ FileUtils.join("foo", "text.properties"),
+ FileUtils.join("bar", "other.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar1, "foo/text.properties"));
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar2, "bar/other.properties"));
+ // this one should be copied over.
+ mFileFilter.handleChanged(mMergedFolder, createFile(mExpandedJar2, "bar/foo.properties"));
+ assertMergedFilesCount(1);
+ assertTrue(new File(mMergedFolder, "bar/foo.properties").exists());
+ }
+
+ @Test
+ public void testSingleMerge() throws IOException {
+ setMerges(ImmutableSet.of(
+ FileUtils.join("foo", "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ createFile(mExpandedJar1, "foo/text.properties", "one");
+ File secondFile = createFile(mExpandedJar2, "foo/text.properties", "two");
+
+ // one has changed...
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "two"));
+ }
+
+ @Test
+ public void testMultipleMerges() throws IOException {
+ setMerges(ImmutableSet.of(
+ FileUtils.join("foo" , "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ createFile(mExpandedJar1, "foo/text.properties", "one");
+ File secondFile = createFile(mExpandedJar2, "foo/text.properties", "two");
+ createFile(mExpandedJar3, "foo/text.properties", "three");
+
+ // one has changed...
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "two", "three"));
+ }
+
+ @Test
+ public void testMergeAddon() throws IOException {
+ setMerges(ImmutableSet.of(
+ FileUtils.join("foo", "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ createFile(mExpandedJar1, "foo/text.properties", "one");
+ File secondFile = createFile(mExpandedJar2, "foo/text.properties", "two");
+
+ // simulate one has changed to create initial version
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "two"));
+
+ // add a new one.
+ File thirdFile = createFile(mExpandedJar3, "foo/text.properties", "three");
+ mFileFilter.handleChanged(mMergedFolder, thirdFile);
+
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "two", "three"));
+ }
+
+ @Test
+ public void testMergeUpdate() throws IOException {
+ setMerges(ImmutableSet.of(
+ FileUtils.join("foo", "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ createFile(mExpandedJar1, "foo/text.properties", "one");
+ File secondFile = createFile(mExpandedJar2, "foo/text.properties", "two");
+ createFile(mExpandedJar3, "foo/text.properties", "three");
+
+ // simulate one has changed to create initial version
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "two", "three"));
+
+ // change one...
+ assertTrue(secondFile.delete());
+ secondFile = createFile(mExpandedJar2, "foo/text.properties", "deux");
+
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "deux", "three"));
+ }
+
+ @Test
+ public void testMergeRemoval() throws IOException {
+ setMerges(ImmutableSet.of(
+ FileUtils.join("foo", "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ createFile(mExpandedJar1, "foo/text.properties", "one");
+ File secondFile = createFile(mExpandedJar2, "foo/text.properties", "two");
+ createFile(mExpandedJar3, "foo/text.properties", "three");
+
+ // simulate one has changed to create initial version
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "two", "three"));
+
+ // remove one...
+ assertTrue(secondFile.delete());
+
+ mFileFilter.handleRemoved(mMergedFolder, FileUtils.join("foo", "text.properties"));
+
+ assertTrue(mergedFile.exists());
+ assertContentInAnyOrder(
+ Files.asCharSource(mergedFile, Charset.defaultCharset()).read()
+ , ImmutableList.of("one", "three"));
+ }
+
+ @Test
+ public void testPickFirst() throws IOException {
+ setPickFirsts(ImmutableSet.of("foo/text.properties"));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ // simulate the three elements were added.
+ mFileFilter.handleChanged(mMergedFolder,
+ createFile(mExpandedJar1, "foo/text.properties", "one"));
+ mFileFilter.handleChanged(mMergedFolder,
+ createFile(mExpandedJar2, "foo/text.properties", "two"));
+ mFileFilter.handleChanged(mMergedFolder,
+ createFile(mExpandedJar3, "foo/text.properties", "three"));
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
+ assertTrue(mergedContent.equals("one")
+ || mergedContent.equals("two")
+ || mergedContent.equals("three"));
+ }
+
+ @Test
+ public void testPickFirstUpdate() throws IOException {
+ setPickFirsts(ImmutableSet.of("foo/text.properties"));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ File firstFile = createFile(mExpandedJar1, "foo/text.properties", "one");
+ File secondFile = createFile(mExpandedJar2, "foo/text.properties", "two");
+ File thirdFile = createFile(mExpandedJar3, "foo/text.properties", "three");
+
+ // simulate the three elements were added.
+ mFileFilter.handleChanged(mMergedFolder, firstFile);
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+ mFileFilter.handleChanged(mMergedFolder, thirdFile);
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
+ if (mergedContent.equals("one")) {
+ assertTrue(firstFile.delete());
+ createFile(mExpandedJar1, "foo/text.properties", "un");
+ mFileFilter.handleChanged(mMergedFolder, firstFile);
+ assertEquals("un", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
+ }
+ if (mergedContent.equals("two")) {
+ assertTrue(thirdFile.delete());
+ createFile(mExpandedJar2, "foo/text.properties", "deux");
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+ assertEquals("deux", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
+ }
+ if (mergedContent.equals("three")) {
+ assertTrue(thirdFile.delete());
+ createFile(mExpandedJar3, "foo/text.properties", "trois");
+ mFileFilter.handleChanged(mMergedFolder, thirdFile);
+ assertEquals("trois", Files.asCharSource(mergedFile, Charset.defaultCharset()).read());
+ }
+ }
+
+ @Test
+ public void testPickFirstRemoval() throws IOException {
+ setPickFirsts(ImmutableSet.of(
+ FileUtils.join("foo", "text.properties")));
+ mFileFilter = new FileFilter(
+ mPackagedJarExpansionSubStreams,
+ mPackagingOptions);
+
+ File firstFile = createFile(mExpandedJar1, "foo/text.properties", "one");
+ File secondFile = createFile(mExpandedJar2, "foo/text.properties", "two");
+ File thirdFile = createFile(mExpandedJar3, "foo/text.properties", "three");
+
+ // simulate the three elements were added.
+ mFileFilter.handleChanged(mMergedFolder, firstFile);
+ mFileFilter.handleChanged(mMergedFolder, secondFile);
+ mFileFilter.handleChanged(mMergedFolder, thirdFile);
+
+ File mergedFile = new File(mMergedFolder, "foo/text.properties");
+ assertTrue(mergedFile.exists());
+ String mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
+ if (mergedContent.equals("one")) {
+ assertTrue(firstFile.delete());
+ mFileFilter.handleRemoved(mMergedFolder, "foo/text.properties");
+ mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
+ assertTrue(mergedContent.equals("two") || mergedContent.equals("three"));
+ }
+ if (mergedContent.equals("two")) {
+ assertTrue(thirdFile.delete());
+ mFileFilter.handleRemoved(mMergedFolder, "foo/text.properties");
+ mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
+ assertTrue(mergedContent.equals("one") || mergedContent.equals("three"));
+ }
+ if (mergedContent.equals("three")) {
+ assertTrue(thirdFile.delete());
+ mFileFilter.handleRemoved(mMergedFolder, "foo/text.properties");
+ mergedContent = Files.asCharSource(mergedFile, Charset.defaultCharset()).read();
+ assertTrue(mergedContent.equals("one") || mergedContent.equals("two"));
+ }
+ }
+
+ private static void assertContentInAnyOrder(String content, Iterable<String> subStrings) {
+ int length = 0;
+ for (String subString : subStrings) {
+ length += subString.length();
+ assertTrue(content.contains(subString));
+ }
+ assertEquals(length, content.length());
+ }
+
+ @NonNull private static File createFile(
+ @NonNull File parent,
+ @NonNull String archivePath) throws IOException {
+ return createFile(parent, archivePath.replace('/', File.separatorChar), null /* content */);
+ }
+
+ @NonNull private static File createFile(
+ @NonNull File parent,
+ @NonNull String archivePath,
+ @Nullable String content) throws IOException {
+
+ File newFile = new File(parent, archivePath);
+ if (!newFile.getParentFile().exists()) {
+ assertTrue(newFile.getParentFile().mkdirs());
+ }
+ String fileContent = content == null ? "test!" : content;
+ Files.append(fileContent, newFile, Charset.defaultCharset());
+ return newFile;
+ }
+
+ private void setPickFirsts(ImmutableSet<String> paths) {
+ mPackagingOptions.setPickFirsts(paths);
+ }
+
+ private void setMerges(ImmutableSet<String> paths) {
+ mPackagingOptions.setMerges(paths);
+ }
+
+ private void setExcludes(ImmutableSet<String> paths) {
+ mPackagingOptions.setExcludes(paths);
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunDexTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunDexTest.java
new file mode 100644
index 0000000..863e883
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunDexTest.java
@@ -0,0 +1,386 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.pipeline.TransformInvocationBuilder;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.builder.core.AndroidBuilder;
+import com.android.ide.common.process.ProcessException;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for the InstantRunDex transform.
+ */
+ at RunWith(MockitoJUnitRunner.class)
+public class InstantRunDexTest {
+
+ @Mock
+ VariantScope variantScope;
+
+ @Mock
+ GlobalScope globalScope;
+
+ @Mock
+ AndroidBuilder androidBuilder;
+
+ @Mock
+ TransformOutputProvider transformOutputProvider;
+
+ @Mock
+ InstantRunBuildContext instantRunBuildContext;
+
+ @Mock
+ DexOptions dexOptions;
+
+ @Mock
+ Context context;
+
+ @Mock
+ Logger logger;
+
+ @Mock
+ Project project;
+
+ @Mock
+ InstantRunDex.JarClassesBuilder jarClassesBuilder;
+
+ File directoryInput;
+ File incrementalChanges;
+ File changedFile;
+
+
+ @Before
+ public void setUp() throws IOException {
+
+ final File reloadOutputFolder = Files.createTempDir();
+ File oldDexFile = new File(reloadOutputFolder, "reload.dex");
+ assertTrue(oldDexFile.createNewFile());
+
+ final File restartOutputFolder = Files.createTempDir();
+ File oldRestartFile = new File(restartOutputFolder, "restart.dex");
+ assertTrue(oldRestartFile.createNewFile());
+
+ when(variantScope.getInstantRunBuildContext()).thenReturn(instantRunBuildContext);
+ when(variantScope.getRestartDexOutputFolder()).thenReturn(restartOutputFolder);
+ when(variantScope.getReloadDexOutputFolder()).thenReturn(reloadOutputFolder);
+ when(variantScope.getGlobalScope()).thenReturn(globalScope);
+ when(variantScope.getGlobalScope().getProject()).thenReturn(project);
+ when(project.getProperties()).then(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return ImmutableMap.of("android.injected.build.api", "23");
+ }
+ });
+ when(globalScope.isActive(OptionalCompilationStep.RESTART_ONLY))
+ .thenReturn(Boolean.FALSE);
+
+ File tmp = new File(System.getProperty("java.io.tmpdir"));
+ when(variantScope.getInstantRunSupportDir()).thenReturn(tmp);
+
+ directoryInput = new File(tmp, "directory");
+ changedFile = new File(directoryInput, "path/to/some/file");
+ Files.createParentDirs(changedFile);
+ Files.write("abcde", changedFile, Charsets.UTF_8);
+
+ incrementalChanges = InstantRunBuildType.RESTART.getIncrementalChangesFile(variantScope);
+ Files.write("CHANGED," + changedFile.getAbsolutePath(), incrementalChanges, Charsets.UTF_8);
+ }
+
+ @After
+ public void takeDown() throws IOException {
+ FileUtils.deleteFolder(directoryInput);
+ if (incrementalChanges.isFile()) {
+ assertThat(incrementalChanges.delete()).isTrue();
+ }
+ }
+
+ @Test
+ public void testVerifierFlaggedClass()
+ throws TransformException, InterruptedException, IOException {
+
+ when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.FALSE);
+
+ final List<File> convertedFiles = new ArrayList<File>();
+ InstantRunDex instantRunDex = getTestedDex(convertedFiles, InstantRunBuildType.RELOAD);
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput)))
+ .addOutputProvider(transformOutputProvider)
+ .build());
+
+ assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isEmpty();
+
+ convertedFiles.clear();
+ instantRunDex = getTestedDex(convertedFiles, InstantRunBuildType.RESTART);
+ when(jarClassesBuilder.isEmpty()).thenReturn(Boolean.FALSE);
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput)))
+ .addOutputProvider(transformOutputProvider)
+ .build());
+
+ assertThat(variantScope.getRestartDexOutputFolder().listFiles()).isNotEmpty();
+ assertThat(convertedFiles).hasSize(0);
+
+ // should have been deleted by the transform.
+ assertThat(incrementalChanges.isFile()).isFalse();
+ }
+
+ @Test
+ public void testVerifierPassedClassOnLollipopOrAbove()
+ throws TransformException, InterruptedException, IOException {
+ when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.TRUE);
+
+ List<File> convertedFiles = new ArrayList<File>();
+ InstantRunDex instantRunDex = getTestedDex(convertedFiles, InstantRunBuildType.RELOAD);
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput)))
+ .addOutputProvider(transformOutputProvider)
+ .build());
+
+ assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isNotEmpty();
+ verify(instantRunBuildContext).addChangedFile(
+ eq(InstantRunBuildContext.FileType.RELOAD_DEX),
+ any(File.class));
+
+ instantRunDex = new InstantRunDex(
+ variantScope,
+ InstantRunBuildType.RESTART,
+ androidBuilder,
+ dexOptions,
+ logger,
+ ImmutableSet.<QualifiedContent.ContentType>of());
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput)))
+ .addOutputProvider(transformOutputProvider)
+ .build());
+
+ assertThat(variantScope.getRestartDexOutputFolder().listFiles()).isEmpty();
+ verify(instantRunBuildContext, times(2)).hasPassedVerification();
+ verify(instantRunBuildContext, times(1)).addChangedFile(
+ any(InstantRunBuildContext.FileType.class), any(File.class));
+ }
+
+ @Test
+ public void testVerifierPassedClassOnDalvik()
+ throws TransformException, InterruptedException, IOException {
+ when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.TRUE);
+ when(project.getProperties()).then(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return ImmutableMap.of("android.injected.build.api", "15");
+ }
+ });
+
+ File incrementalChanges = InstantRunBuildType.RELOAD.getIncrementalChangesFile(
+ variantScope);
+ Files.write("CHANGED," + changedFile.getAbsolutePath(),
+ incrementalChanges, Charsets.UTF_8);
+
+ InstantRunDex instantRunDex = new InstantRunDex(
+ variantScope,
+ InstantRunBuildType.RELOAD,
+ androidBuilder,
+ dexOptions,
+ logger,
+ ImmutableSet.<QualifiedContent.ContentType>of());
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput)))
+ .addOutputProvider(transformOutputProvider)
+ .build());
+
+ assertThat(incrementalChanges.isFile()).isFalse();
+ assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isNotEmpty();
+
+ instantRunDex = new InstantRunDex(
+ variantScope,
+ InstantRunBuildType.RESTART,
+ androidBuilder,
+ dexOptions,
+ logger,
+ ImmutableSet.<QualifiedContent.ContentType>of());
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput)))
+ .addOutputProvider(transformOutputProvider)
+ .build());
+ // since the verifier passed, the restart.dex is not present.
+ assertThat(variantScope.getRestartDexOutputFolder().listFiles()).isEmpty();
+ }
+
+ @Test
+ public void testNoChanges() throws TransformException, InterruptedException, IOException {
+ when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.TRUE);
+ when(project.getProperties()).then(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return ImmutableMap.of("android.injected.build.api", "15");
+ }
+ });
+
+ InstantRunDex instantRunDex = new InstantRunDex(
+ variantScope,
+ InstantRunBuildType.RELOAD,
+ androidBuilder,
+ dexOptions,
+ logger,
+ ImmutableSet.<QualifiedContent.ContentType>of());
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addOutputProvider(transformOutputProvider)
+ .build());
+
+ assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isEmpty();
+
+ instantRunDex = new InstantRunDex(
+ variantScope,
+ InstantRunBuildType.RESTART,
+ androidBuilder,
+ dexOptions,
+ logger,
+ ImmutableSet.<QualifiedContent.ContentType>of());
+
+ instantRunDex.transform(new TransformInvocationBuilder(context)
+ .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput)))
+ .addOutputProvider(transformOutputProvider)
+ .build());
+
+ // since the verifier passed, the restart.dex is not present.
+ assertThat(variantScope.getRestartDexOutputFolder().listFiles()).isEmpty();
+ }
+
+ private InstantRunDex getTestedDex(final List<File> convertedFiles, InstantRunBuildType type) {
+ return new InstantRunDex(
+ variantScope,
+ type,
+ androidBuilder,
+ dexOptions,
+ logger,
+ ImmutableSet.<QualifiedContent.ContentType>of()) {
+
+ @Override
+ protected JarClassesBuilder getJarClassBuilder(File outputFile) {
+ return jarClassesBuilder;
+ }
+
+ @Override
+ protected void convertByteCode(List<File> inputFiles, File outputFolder)
+ throws InterruptedException, ProcessException, IOException {
+ convertedFiles.addAll(inputFiles);
+ }
+ };
+ }
+
+ private static TransformInput getTransformInput(
+ final File directoryInput) {
+ return new TransformInput() {
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(
+ new DirectoryInput() {
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return ImmutableMap.of();
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "test-input";
+ }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return directoryInput;
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getContentTypes() {
+ return ImmutableSet.<ContentType>of(DefaultContentType.CLASSES);
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return ImmutableSet.of(Scope.PROJECT);
+ }
+ }
+
+ );
+ }
+ };
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunSlicerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunSlicerTest.java
new file mode 100644
index 0000000..a2b59dc
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunSlicerTest.java
@@ -0,0 +1,431 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.pipeline.TransformInvocationBuilder;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.tasks.ColdswapArtifactsKickerTask;
+import com.android.build.gradle.tasks.MarkerFile;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+import org.gradle.api.logging.Logger;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test for the {@link InstantRunSlicer}
+ *
+ * The tests creates 20 files in an input folder that should be initially sliced into 10 slices
+ * according to their natural order.
+ *
+ * For instance, slice-0 will contain file1.class, file0.class, file10.class
+ */
+public class InstantRunSlicerTest {
+
+ @Mock
+ Context context;
+
+ @Mock
+ Logger logger;
+
+ @Mock
+ VariantScope variantScope;
+
+ @Mock
+ InstantRunBuildContext instantRunBuildContext;
+
+ @Rule
+ public TemporaryFolder instantRunSupportDir = new TemporaryFolder();
+ @Rule
+ public TemporaryFolder inputDir = new TemporaryFolder();
+ @Rule
+ public TemporaryFolder outputDir = new TemporaryFolder();
+ @Rule
+ public TemporaryFolder jarOutputDir = new TemporaryFolder();
+
+ File jarOutput;
+
+ @Before
+ public void beforeTest() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ when(variantScope.getInstantRunSupportDir()).thenReturn(instantRunSupportDir.getRoot());
+ when(variantScope.getRestartDexOutputFolder()).thenReturn(instantRunSupportDir.getRoot());
+ MarkerFile.createMarkerFile(
+ ColdswapArtifactsKickerTask.ConfigAction.getMarkerFile(variantScope),
+ MarkerFile.Command.RUN);
+ when(variantScope.getInstantRunBuildContext()).thenReturn(instantRunBuildContext);
+
+ jarOutput = new File(jarOutputDir.getRoot(), "output.jar");
+ }
+
+ @Test
+ public void testNonIncrementalModeOnlyDirs()
+ throws IOException, TransformException, InterruptedException {
+
+ InstantRunSlicer slicer = new InstantRunSlicer(logger, variantScope);
+
+ final Map<File, Status> changedFiles;
+ ImmutableMap.Builder<File, Status> builder = ImmutableMap.builder();
+ List<String> listOfFiles = new ArrayList<String>();
+ for (int i=0; i<21; i++) {
+ listOfFiles.add("file" + i + ".class");
+ builder.put(createFile(getInputDir(), "file" + i + ".class"), Status.ADDED);
+ }
+ changedFiles = builder.build();
+
+ slicer.transform(new TransformInvocationBuilder(context)
+ .addInputs(ImmutableList.of(getInput(getInputDir(), changedFiles)))
+ .addOutputProvider(getOutputProvider(getOutputDir(), jarOutput))
+ .build());
+
+ // assert the file was copied in the output directory.
+ File[] files = getOutputDir().listFiles();
+ assertNotNull(files);
+
+ assertThat(files.length).isEqualTo(InstantRunSlicer.NUMBER_OF_SLICES_FOR_PROJECT_CLASSES);
+ for (int i=0; i<InstantRunSlicer.NUMBER_OF_SLICES_FOR_PROJECT_CLASSES; i++) {
+ assertThat(files[0]).named("slice-" + i);
+ File slice = files[i];
+ assertThat(slice.isDirectory()).isTrue();
+ File[] sliceFiles = slice.listFiles();
+ assertNotNull(sliceFiles);
+ assertThat(sliceFiles.length).isAtLeast(1);
+ for (File sliceFile : sliceFiles) {
+ if (sliceFile.getName().startsWith("file")) {
+ assertThat(listOfFiles.remove(sliceFile.getName())).isTrue();
+ }
+ }
+ }
+ assertThat(listOfFiles).isEmpty();
+ }
+
+ @Test
+ public void testIncrementalModeOnlyDirs()
+ throws IOException, TransformException, InterruptedException {
+
+ InstantRunSlicer slicer = new InstantRunSlicer(logger, variantScope);
+
+ Map<File, Status> changedFiles;
+ ImmutableMap.Builder<File, Status> builder = ImmutableMap.builder();
+ for (int i=0; i<21; i++) {
+ builder.put(createFile(getInputDir(), "file" + i + ".class"), Status.CHANGED);
+ }
+ changedFiles = builder.build();
+
+ slicer.transform(new TransformInvocationBuilder(context)
+ .addInputs(ImmutableList.of(getInput(getInputDir(), changedFiles)))
+ .addOutputProvider(getOutputProvider(getOutputDir(), jarOutput))
+ .build());
+
+ // incrementally change a few slices.
+ File file7 = createFile(getInputDir(), "file7.class", "updated file7.class");
+ changedFiles = ImmutableMap.of();
+
+ File incrementalChanges = InstantRunBuildType.RESTART
+ .getIncrementalChangesFile(variantScope);
+ Files.write(
+ "CHANGED," + file7.getAbsolutePath() + "\n" +
+ "REMOVED," + new File(getInputDir(), "file14.class").getAbsolutePath(),
+ incrementalChanges, Charsets.UTF_8);
+
+ slicer.transform(new TransformInvocationBuilder(context)
+ .addInputs(ImmutableList.of(getInput(getInputDir(), changedFiles)))
+ .addOutputProvider(getOutputProvider(getOutputDir(), jarOutput))
+ .setIncrementalMode(true)
+ .build());
+
+ // assert the file was copied in the output directory.
+ File[] slices = getOutputDir().listFiles();
+ assertNotNull(slices);
+ assertThat(slices.length).isEqualTo(InstantRunSlicer.NUMBER_OF_SLICES_FOR_PROJECT_CLASSES);
+
+ // file7 should have been updated.
+ int bucket = Math.abs((file7.getName()).hashCode() % 10);
+ File file7Slice = getFileByName(slices, "slice_" + bucket);
+ assertNotNull(file7Slice);
+ File[] slice6Files = file7Slice.listFiles();
+ File updatedFile7 = getFileByName(slice6Files, "file7.class");
+ assertNotNull(updatedFile7);
+ assertThat(FileUtils.loadFileWithUnixLineSeparators(updatedFile7))
+ .isEqualTo("updated file7.class");
+
+ // file14 should have been removed
+ bucket = Math.abs(("file14.class").hashCode() % 10);
+ File file14Bucket = getFileByName(slices, "slice_" + bucket);
+ assertNotNull(file14Bucket);
+ File[] file14BucketFiles = file14Bucket.listFiles();
+ assertNotNull(file14BucketFiles);
+ assertNull(getFileByName(file14BucketFiles, "file14.class"));
+ }
+
+ @Test
+ public void testNonIncrementalRemovingClass()
+ throws IOException, TransformException, InterruptedException {
+ testRemovingClass(false /* isIncremental */);
+ }
+
+ @Test
+ public void testIncrementalRemovingClass()
+ throws IOException, TransformException, InterruptedException {
+ testRemovingClass(true /* isIncremental */);
+ }
+
+ private void testRemovingClass(boolean isIncremental)
+ throws IOException, TransformException, InterruptedException {
+ InstantRunSlicer slicer = new InstantRunSlicer(logger, variantScope);
+
+ String packagePath = "com/foo/bar";
+ File singleClassFile = createFile(new File(getInputDir(), packagePath), "file0.class");
+ Map<File, Status> changedFiles = ImmutableMap.of(singleClassFile, Status.ADDED);
+
+ slicer.transform(new TransformInvocationBuilder(context)
+ .addInputs(ImmutableList.of(getInput(getInputDir(), changedFiles)))
+ .addOutputProvider(getOutputProvider(getOutputDir(), jarOutput))
+ .build());
+
+ File[] outputSlices = getOutputDir().listFiles();
+ assertThat(outputSlices).isNotNull();
+ assertThat(outputSlices).hasLength(InstantRunSlicer.NUMBER_OF_SLICES_FOR_PROJECT_CLASSES);
+
+ Optional<Integer> sliceForFile =
+ findSliceForFile(getOutputDir(), getInputDir(), singleClassFile);
+ assertThat(sliceForFile).isPresent();
+ int slot = sliceForFile.get();
+
+ File outputSlice = outputSlices[slot];
+ String sliceName = outputSlice.getName();
+ File outputFile = new File(new File(outputSlice, packagePath), "file0.class");
+ assertThat(outputFile.exists()).isTrue();
+
+ // delete the input file.
+ assertThat(singleClassFile.delete()).isTrue();
+ changedFiles = ImmutableMap.of();
+
+ if (!isIncremental) {
+ // delete the output directory and create a new clean one.
+ FileUtils.deleteFolder(getOutputDir());
+ outputDir.create();
+ } else {
+ // create the incremental change file.
+ FileUtils.createFile(InstantRunBuildType.RESTART
+ .getIncrementalChangesFile(variantScope),
+ Status.REMOVED.toString() + "," + singleClassFile.getAbsolutePath());
+ }
+
+ slicer.transform(new TransformInvocationBuilder(context)
+ .addInputs(ImmutableList.of(getInput(getInputDir(), changedFiles)))
+ .addOutputProvider(getOutputProvider(getOutputDir(), jarOutput))
+ .setIncrementalMode(isIncremental)
+ .build());
+
+ outputSlices = getOutputDir().listFiles();
+ assertThat(outputSlices).isNotNull();
+ assertThat(outputSlices).hasLength(InstantRunSlicer.NUMBER_OF_SLICES_FOR_PROJECT_CLASSES);
+ outputSlice = new File(getOutputDir(), sliceName);
+ outputFile = new File(new File(outputSlice, packagePath), "file0.class");
+ // check that the file was removed but the slice remained.
+ assertThat(outputFile.exists()).isFalse();
+ }
+
+ @Test
+ public void testEmptyPackages() throws IOException, TransformException, InterruptedException {
+ InstantRunSlicer slicer = new InstantRunSlicer(logger, variantScope);
+
+ // call with an empty input directory.
+ slicer.transform(new TransformInvocationBuilder(context)
+ .addInputs(ImmutableList.of(
+ getInput(getInputDir(), ImmutableMap.<File, Status>of())))
+ .addOutputProvider(getOutputProvider(getOutputDir(), jarOutput))
+ .build());
+
+ File[] outputSlices = getOutputDir().listFiles();
+ assertThat(outputSlices).isNotNull();
+ assertThat(outputSlices).hasLength(InstantRunSlicer.NUMBER_OF_SLICES_FOR_PROJECT_CLASSES);
+
+ for (File slice : outputSlices) {
+ // ensure guard class presence.
+ File guardClassPackage = new File(slice,
+ InstantRunSlicer.PACKAGE_FOR_GUARD_CLASSS
+ + File.separator + slice.getName() + ".class");
+ assertThat(guardClassPackage.exists()).isTrue();
+ }
+ }
+
+ private static Optional<Integer> findSliceForFile(
+ @NonNull File outputDir, @NonNull File inputDir, File file) {
+
+ File[] slices = outputDir.listFiles();
+ if (slices == null) {
+ return Optional.absent();
+ }
+
+ String relativePath = FileUtils.relativePath(file, inputDir);
+ for (int i=0; i<slices.length; i++) {
+ File potentialMatch = new File(slices[i], relativePath);
+ if (potentialMatch.exists()) {
+ return Optional.of(i);
+ }
+ }
+ return Optional.absent();
+ }
+
+ @Nullable
+ private static File getFileByName(@Nullable File[] files, @NonNull String fileName) {
+ if (files == null) {
+ return null;
+ }
+ for (File file : files) {
+ if (file.getName().equals(fileName)) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ private static TransformInput getInput(final File inputDir, final Map<File, Status> changedFiles) {
+ return new TransformInput() {
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(
+ new DirectoryInputForTests() {
+ @NonNull
+ @Override
+ public File getFile() {
+ return inputDir;
+ }
+
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return changedFiles;
+ }
+ });
+ }
+ };
+ }
+
+ private static TransformOutputProvider getOutputProvider(final File outputDir, final File jarOutput) {
+ return new TransformOutputProvider() {
+
+ @Override
+ public void deleteAll() throws IOException {
+
+ }
+
+ @NonNull
+ @Override
+ public File getContentLocation(@NonNull String name,
+ @NonNull Set<QualifiedContent.ContentType> types,
+ @NonNull Set<QualifiedContent.Scope> scopes, @NonNull Format format) {
+ if (format == Format.DIRECTORY) {
+ return new File(outputDir, name);
+ }
+ assertTrue(format == Format.JAR);
+ return jarOutput;
+ }
+ };
+ }
+
+ private abstract static class DirectoryInputForTests implements DirectoryInput {
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "test";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getContentTypes() {
+ return ImmutableSet.<ContentType>of(DefaultContentType.CLASSES);
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return ImmutableSet.of(Scope.PROJECT);
+ }
+ }
+
+ private static File createFile(File directory, String name) throws IOException {
+ return createFile(directory, name, name);
+ }
+
+ private static File createFile(File directory, String name, String content) throws IOException {
+ File newFile = new File(directory, name);
+ Files.createParentDirs(newFile);
+ FileWriter writer = new FileWriter(newFile);
+ try {
+ writer.append(content);
+ } finally {
+ writer.close();
+ }
+ return newFile;
+ }
+
+ private File getOutputDir() {
+ return outputDir.getRoot();
+ }
+
+ private File getInputDir() {
+ return inputDir.getRoot();
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunTransformTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunTransformTest.java
new file mode 100644
index 0000000..8abea80
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunTransformTest.java
@@ -0,0 +1,372 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.pipeline.TransformInvocationBuilder;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.builder.core.AndroidBuilder;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for the {@link InstantRunTransform} class
+ */
+public class InstantRunTransformTest {
+
+ @Mock
+ Context context;
+
+ @Mock
+ VariantScope variantScope;
+
+ @Mock
+ GlobalScope globalScope;
+
+ @Mock
+ InstantRunBuildContext instantRunBuildContext;
+
+ @Before
+ public void setUpMock() {
+ MockitoAnnotations.initMocks(this);
+ AndroidBuilder mockBuilder = Mockito.mock(AndroidBuilder.class);
+ when(mockBuilder.getBootClasspath(true)).thenReturn(ImmutableList.<File>of());
+ when(globalScope.getAndroidBuilder()).thenReturn(mockBuilder);
+ when(variantScope.getGlobalScope()).thenReturn(globalScope);
+ when(variantScope.getInstantRunBuildContext()).thenReturn(instantRunBuildContext);
+ }
+
+ @Test
+ public void incrementalModeTest() throws TransformException, InterruptedException, IOException {
+
+ final ImmutableList.Builder<File> filesElectedForClasses2Transformation = ImmutableList.builder();
+ final ImmutableList.Builder<File> filesElectedForClasses3Transformation = ImmutableList.builder();
+
+ InstantRunTransform transform = new InstantRunTransform(variantScope) {
+ @Override
+ protected void transformToClasses2Format(
+ @NonNull File inputDir, @NonNull File inputFile, @NonNull File outputDir,
+ @NonNull Status status,
+ @NonNull RecordingPolicy recordingPolicy)
+ throws IOException {
+ filesElectedForClasses2Transformation.add(inputFile);
+ }
+
+ @Override
+ protected void transformToClasses3Format(File inputDir, File inputFile, File outputDir)
+ throws IOException {
+ filesElectedForClasses3Transformation.add(inputFile);
+ }
+
+ @Override
+ protected void wrapUpOutputs(File classes2Folder, File classes3Folder)
+ throws IOException {
+
+ }
+ };
+
+ ImmutableList.Builder<TransformInput> input = ImmutableList.builder();
+
+ input.add(new TransformInput() {
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() {
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return ImmutableMap.<File, Status>builder()
+ .put(new File("/tmp/foo/bar/Changed.class"), Status.CHANGED)
+ .put(new File("/tmp/foo/bar/Added.class"), Status.ADDED)
+ .build();
+ }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return new File("/tmp");
+ }
+ });
+ }
+
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+ });
+
+ TransformOutputProvider transformOutput = new TransformOutputProvider() {
+ @Override
+ public void deleteAll() throws IOException {
+
+ }
+
+ @NonNull
+ @Override
+ public File getContentLocation(@NonNull String name,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes, @NonNull Format format) {
+ assertThat(types).hasSize(1);
+ if (types.iterator().next().equals(QualifiedContent.DefaultContentType.CLASSES)) {
+ return new File("out");
+ } else {
+ return new File("out.3");
+ }
+ }
+ };
+ transform.transform(new TransformInvocationBuilder(context)
+ .addInputs(input.build())
+ .addOutputProvider(transformOutput)
+ .setIncrementalMode(true)
+ .build());
+
+ ImmutableList<File> processedFiles = filesElectedForClasses2Transformation.build();
+ assertEquals("Wrong number of files elected for classes 2 processing", 2, processedFiles.size());
+ assertEquals("Output File path",
+ FileUtils.toSystemDependentPath("/tmp/foo/bar/Changed.class"),
+ processedFiles.get(0).getPath());
+ assertEquals("Output File path",
+ FileUtils.toSystemDependentPath("/tmp/foo/bar/Added.class"),
+ processedFiles.get(1).getPath());
+ processedFiles = filesElectedForClasses3Transformation.build();
+ assertEquals("Wrong number of files elected for classes 3 processing", 1, processedFiles.size());
+ assertEquals("Output File path",
+ FileUtils.toSystemDependentPath("/tmp/foo/bar/Changed.class"),
+ processedFiles.get(0).getPath());
+ }
+
+ @Test
+ public void fileDeletionTest() throws IOException, TransformException, InterruptedException {
+
+ final File tmpFolder = Files.createTempDir();
+
+ final File inputFolder = new File(tmpFolder, "input");
+ FileUtils.mkdirs(inputFolder);
+
+ final File originalFile = createEmptyFile(inputFolder, "com/example/A.class");
+
+ final File outputFolder = new File(tmpFolder, "output");
+ final File outputFile = createEmptyFile(outputFolder, "com/example/A.class");
+
+ final File outputEnhancedFolder = new File(tmpFolder, "outputEnhanced");
+ final File outputEnhancedFile =
+ createEmptyFile(outputEnhancedFolder, "com/example/A$override.class");
+
+ assertTrue(outputFile.exists());
+ assertTrue(outputEnhancedFile.exists());
+
+ final ImmutableList.Builder<File> filesElectedForClasses2Transformation = ImmutableList.builder();
+ final ImmutableList.Builder<File> filesElectedForClasses3Transformation = ImmutableList.builder();
+
+ InstantRunTransform transform = new InstantRunTransform(variantScope) {
+ @Override
+ protected void transformToClasses2Format(
+ @NonNull File inputDir,
+ @NonNull File inputFile,
+ @NonNull File outputDir,
+ @NonNull Status status,
+ @NonNull RecordingPolicy recordingPolicy)
+ throws IOException {
+ filesElectedForClasses2Transformation.add(inputFile);
+ }
+
+ @Override
+ protected void transformToClasses3Format(File inputDir, File inputFile, File outputDir)
+ throws IOException {
+ filesElectedForClasses3Transformation.add(inputFile);
+ }
+
+ @Override
+ protected void wrapUpOutputs(File classes2Folder, File classes3Folder)
+ throws IOException {
+
+ }
+ };
+
+ ImmutableList.Builder<TransformInput> input = ImmutableList.builder();
+
+ input.add(new TransformInput() {
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() {
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return ImmutableMap.<File, Status>builder()
+ .put(originalFile, Status.REMOVED)
+ .build();
+ }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return inputFolder;
+ }
+ });
+ }
+
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+ });
+
+ TransformOutputProvider transformOutputProvider = new TransformOutputProvider() {
+ @Override
+ public void deleteAll() throws IOException {
+
+ }
+
+ @NonNull
+ @Override
+ public File getContentLocation(@NonNull String name,
+ @NonNull Set<ContentType> types,
+ @NonNull Set<Scope> scopes, @NonNull Format format) {
+ assertThat(types).hasSize(1);
+ if (types.iterator().next().equals(QualifiedContent.DefaultContentType.CLASSES)) {
+ return outputFolder;
+ } else {
+ return outputEnhancedFolder;
+ }
+ }
+ };
+ // delete the "deleted" file.
+ originalFile.delete();
+
+ transform.transform(new TransformInvocationBuilder(context)
+ .addOutputProvider(transformOutputProvider)
+ .addInputs(input.build())
+ .setIncrementalMode(true)
+ .build());
+
+ ImmutableList<File> processedFiles = filesElectedForClasses2Transformation.build();
+ assertEquals("Wrong number of files elected for processing", 0, processedFiles.size());
+
+ assertFalse("Incremental support class file should have been deleted.", outputFile.exists());
+ assertFalse("Enhanced class file should have been deleted.", outputEnhancedFile.exists());
+
+ FileUtils.deleteFolder(tmpFolder);
+ }
+
+ @Test
+ public void testChangeRecordsMerging() {
+ ChangeRecords pastIteration = new ChangeRecords();
+ pastIteration.add(Status.CHANGED, "file/to/be/deleted");
+ pastIteration.add(Status.CHANGED, "file/that/will/remain");
+
+ ChangeRecords newIteration = new ChangeRecords();
+ newIteration.add(Status.REMOVED, "file/to/be/deleted");
+ newIteration.add(Status.CHANGED, "new/file/changed");
+
+ InstantRunTransform.merge(newIteration, pastIteration);
+ assertThat(newIteration.records.size()).isEqualTo(3);
+ assertThat(newIteration.getFilesForStatus(Status.CHANGED)).containsExactly(
+ "file/that/will/remain", "new/file/changed");
+ assertThat(newIteration.getFilesForStatus(Status.REMOVED)).containsExactly(
+ "file/to/be/deleted");
+ }
+
+ @Test
+ public void testChangeRecordsRemovedAndReAdded() {
+ ChangeRecords pastIteration = new ChangeRecords();
+ pastIteration.add(Status.REMOVED, "file/to/be/deleted/and/added");
+ pastIteration.add(Status.CHANGED, "file/that/will/remain");
+
+
+ ChangeRecords newIteration = new ChangeRecords();
+ newIteration.add(Status.ADDED, "file/to/be/deleted/and/added");
+ newIteration.add(Status.CHANGED, "new/file/changed");
+
+ InstantRunTransform.merge(newIteration, pastIteration);
+ assertThat(newIteration.records.size()).isEqualTo(3);
+ assertThat(newIteration.getFilesForStatus(Status.CHANGED)).containsExactly(
+ "file/that/will/remain", "new/file/changed");
+ assertThat(newIteration.getFilesForStatus(Status.ADDED)).containsExactly(
+ "file/to/be/deleted/and/added");
+ }
+
+ private static File createEmptyFile(File folder, String path)
+ throws IOException {
+ File file = new File(folder, path);
+ Files.createParentDirs(file);
+ Files.touch(file);
+ return file;
+ }
+
+ private abstract static class DirectoryInputForTests implements DirectoryInput {
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "test";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getContentTypes() {
+ return ImmutableSet.<ContentType>of(DefaultContentType.CLASSES);
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return ImmutableSet.of(Scope.PROJECT);
+ }
+
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunVerifierTransformTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunVerifierTransformTest.java
new file mode 100644
index 0000000..1d36ffa
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/transforms/InstantRunVerifierTransformTest.java
@@ -0,0 +1,451 @@
+/*
+ * 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.build.gradle.internal.transforms;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.SecondaryInput;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunVerifier;
+import com.android.build.gradle.internal.pipeline.TransformInvocationBuilder;
+import com.android.build.gradle.internal.scope.GlobalScope;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for the {@link InstantRunVerifierTransform}
+ */
+ at RunWith(MockitoJUnitRunner.class)
+public class InstantRunVerifierTransformTest {
+
+ final Map<File, File> recordedVerification = new HashMap<File, File>();
+ final Map<File, File> recordedCopies = new HashMap<File, File>();
+ final File backupDir = Files.createTempDir();
+
+ @Mock
+ VariantScope variantScope;
+
+ @Mock
+ GlobalScope globalScope;
+
+ @Mock
+ Context context;
+
+ @Mock
+ TransformOutputProvider transformOutputProvider;
+
+ @Mock
+ InstantRunBuildContext instantRunBuildContext;
+
+ @Before
+ public void setUpMock() {
+ when(variantScope.getIncrementalVerifierDir()).thenReturn(backupDir);
+ when(variantScope.getInstantRunBuildContext()).thenReturn(instantRunBuildContext);
+ when(variantScope.getGlobalScope()).thenReturn(globalScope);
+ when(globalScope.isActive(OptionalCompilationStep.RESTART_ONLY)).thenReturn(false);
+ }
+
+ @Test
+ public void testNonIncrementalModel()
+ throws TransformException, InterruptedException, IOException {
+
+ InstantRunVerifierTransform transform = getTransform();
+ final File tmpDir = Files.createTempDir();
+
+ final File inputClass = new File(tmpDir, "com/foo/bar/InputFile.class");
+ Files.createParentDirs(inputClass);
+ assertTrue(inputClass.createNewFile());
+
+ ImmutableList<TransformInput> transformInputs =
+ ImmutableList.<TransformInput>of(new TransformInput() {
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() {
+
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return ImmutableMap.of(inputClass, Status.ADDED);
+ }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return tmpDir;
+ }
+ });
+ }
+ });
+
+ transform.transform(new TransformInvocationBuilder(context)
+ .addOutputProvider(transformOutputProvider)
+ .addReferencedInputs(transformInputs)
+ .build());
+
+ // clean up.
+ FileUtils.deleteFolder(tmpDir);
+
+ // input class should have been copied.
+ assertThat(recordedVerification).isEmpty();
+ assertThat(recordedCopies).hasSize(1);
+ assertThat(recordedCopies).containsEntry(inputClass,
+ new File(backupDir, "com/foo/bar/InputFile.class"));
+ }
+
+ @Test
+ public void testIncrementalMode_changedAndAdded() throws TransformException, IOException, InterruptedException {
+
+ InstantRunVerifierTransform transform = getTransform();
+ final File tmpDir = Files.createTempDir();
+
+ final File addedFile = new File(tmpDir, "com/foo/bar/NewInputFile.class");
+ Files.createParentDirs(addedFile);
+ assertTrue(addedFile.createNewFile());
+
+ final File changedFile = new File(tmpDir, "com/foo/bar/ChangedFile.class");
+ assertTrue(changedFile.createNewFile());
+ final File lastIterationChangedFile =
+ new File(backupDir, "com/foo/bar/ChangedFile.class");
+ Files.createParentDirs(lastIterationChangedFile);
+ assertTrue(lastIterationChangedFile.createNewFile());
+
+ ImmutableList<TransformInput> transformInputs =
+ ImmutableList.<TransformInput>of(new TransformInput() {
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() {
+
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return ImmutableMap.<File, Status>builder()
+ .put(addedFile, Status.ADDED)
+ .put(changedFile, Status.CHANGED)
+ .build(); }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return tmpDir;
+ }
+ });
+ }
+ });
+
+ transform.transform(new TransformInvocationBuilder(context)
+ .addOutputProvider(transformOutputProvider)
+ .addReferencedInputs(transformInputs)
+ .setIncrementalMode(true)
+ .build());
+
+ // clean up.
+ FileUtils.deleteFolder(tmpDir);
+
+ // changed class should have been verified
+ assertThat(recordedVerification).isEmpty();
+
+ // new classes should have been copied, and changed ones updated.
+ assertThat(recordedCopies).hasSize(2);
+ assertThat(recordedCopies).containsEntry(
+ changedFile, lastIterationChangedFile);
+ assertThat(recordedCopies).containsEntry(addedFile,
+ new File(backupDir, "com/foo/bar/NewInputFile.class"));
+ }
+
+ @Test
+ public void testIncrementalMode_changedAndDeleted() throws TransformException, IOException, InterruptedException {
+
+ InstantRunVerifierTransform transform = getTransform();
+ final File tmpDir = Files.createTempDir();
+
+ final File changedFile = new File(tmpDir, "com/foo/bar/ChangedFile.class");
+ Files.createParentDirs(changedFile);
+ assertTrue(changedFile.createNewFile());
+ final File lastIterationChangedFile =
+ new File(backupDir, "com/foo/bar/ChangedFile.class");
+ Files.createParentDirs(lastIterationChangedFile);
+ assertTrue(lastIterationChangedFile.createNewFile());
+
+ final File deletedFile = new File(tmpDir, "com/foo/bar/DeletedFile.class");
+
+ ImmutableList<TransformInput> transformInputs =
+ ImmutableList.<TransformInput>of(new TransformInput() {
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() {
+
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ return ImmutableMap.<File, Status>builder()
+ .put(changedFile, Status.CHANGED)
+ .put(deletedFile, Status.REMOVED)
+ .build(); }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return tmpDir;
+ }
+ });
+ }
+ });
+
+ transform.transform(new TransformInvocationBuilder(context)
+ .addOutputProvider(transformOutputProvider)
+ .addReferencedInputs(transformInputs)
+ .setIncrementalMode(true)
+ .build());
+
+ // clean up.
+ FileUtils.deleteFolder(tmpDir);
+
+ // changed class should have been verified
+ assertThat(recordedVerification).hasSize(1);
+ assertThat(recordedVerification).containsEntry(
+ lastIterationChangedFile, changedFile);
+
+ // new classes should have been copied, and changed ones updated.
+ assertThat(recordedCopies).hasSize(1);
+ assertThat(recordedCopies).containsEntry(
+ changedFile, lastIterationChangedFile);
+ }
+
+ @Test
+ public void testSeveralAddedFilesInIncrementalMode()
+ throws IOException, TransformException, InterruptedException {
+ InstantRunVerifierTransform transform = getTransform();
+ final File tmpDir = Files.createTempDir();
+
+ final File[] files = new File[5];
+ for (int i = 0; i < 5; i++) {
+ files[i] = new File(tmpDir, "com/foo/bar/NewInputFile-" + i + ".class");
+ Files.createParentDirs(files[i]);
+ assertTrue(files[i].createNewFile());
+ }
+
+ ImmutableList<TransformInput> transformInputs =
+ ImmutableList.<TransformInput>of(new TransformInput() {
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() {
+
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ ImmutableMap.Builder<File, Status> builder = ImmutableMap.builder();
+ for (int i=0; i<5; i++) {
+ builder.put(files[i], Status.ADDED);
+ }
+ return builder.build(); }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return tmpDir;
+ }
+ });
+ }
+ });
+
+ transform.transform(new TransformInvocationBuilder(context)
+ .addOutputProvider(transformOutputProvider)
+ .addReferencedInputs(transformInputs)
+ .setIncrementalMode(true)
+ .build());
+
+ // clean up.
+ FileUtils.deleteFolder(tmpDir);
+
+ // input class should have been copied.
+ assertThat(recordedCopies).hasSize(5);
+ for (int i=0; i<5; i++) {
+ assertThat(recordedCopies).containsEntry(files[i],
+ new File(backupDir, "com/foo/bar/" + files[i].getName()));
+ }
+ assertThat(recordedVerification).isEmpty();
+ }
+
+ @Test
+ public void testSeveralChangedFilesInIncrementalMode()
+ throws IOException, TransformException, InterruptedException {
+ InstantRunVerifierTransform transform = getTransform();
+ final File tmpDir = Files.createTempDir();
+
+ final File[] files = new File[5];
+ final File[] lastIterationFiles = new File[5];
+ for (int i = 0; i < 5; i++) {
+ files[i] = new File(tmpDir, "com/foo/bar/NewInputFile-" + i + ".class");
+ Files.createParentDirs(files[i]);
+ assertTrue(files[i].createNewFile());
+ lastIterationFiles[i] = new File(backupDir, "com/foo/bar/NewInputFile-" + i + ".class");
+ Files.createParentDirs(lastIterationFiles[i]);
+ assertTrue(lastIterationFiles[i].createNewFile());
+ }
+
+ ImmutableList<TransformInput> transformInputs =
+ ImmutableList.<TransformInput>of(new TransformInput() {
+ @NonNull
+ @Override
+ public Collection<JarInput> getJarInputs() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public Collection<DirectoryInput> getDirectoryInputs() {
+ return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() {
+
+ @NonNull
+ @Override
+ public Map<File, Status> getChangedFiles() {
+ ImmutableMap.Builder<File, Status> builder = ImmutableMap.builder();
+ for (int i=0; i<5; i++) {
+ builder.put(files[i], Status.CHANGED);
+ }
+ return builder.build(); }
+
+ @NonNull
+ @Override
+ public File getFile() {
+ return tmpDir;
+ }
+ });
+ }
+ });
+
+ transform.transform(new TransformInvocationBuilder(context)
+ .addOutputProvider(transformOutputProvider)
+ .addReferencedInputs(transformInputs)
+ .setIncrementalMode(true)
+ .build());
+
+ // clean up.
+ FileUtils.deleteFolder(tmpDir);
+
+ // input class should have been verified.
+ assertThat(recordedVerification).hasSize(5);
+ for (int i=0; i<5; i++) {
+ assertThat(recordedVerification).containsEntry(lastIterationFiles[i], files[i]);
+ }
+ // and updated...
+ assertThat(recordedCopies).hasSize(5);
+ for (int i=0; i<5; i++) {
+ assertThat(recordedCopies).containsEntry(files[i], lastIterationFiles[i]);
+ } }
+
+ private InstantRunVerifierTransform getTransform() {
+
+ return new InstantRunVerifierTransform(variantScope) {
+
+ @NonNull
+ @Override
+ protected InstantRunVerifierStatus runVerifier(String name,
+ @NonNull final InstantRunVerifier.ClassBytesProvider originalClass ,
+ @NonNull final InstantRunVerifier.ClassBytesProvider updatedClass) throws IOException {
+
+ recordedVerification.put(
+ ((InstantRunVerifier.ClassBytesFileProvider) originalClass).getFile(),
+ ((InstantRunVerifier.ClassBytesFileProvider) updatedClass).getFile());
+ return InstantRunVerifierStatus.COMPATIBLE;
+ }
+
+ @Override
+ protected void copyFile(File inputFile, File outputFile) {
+ recordedCopies.put(inputFile, outputFile);
+ }
+ };
+ }
+
+ private abstract static class DirectoryInputForTests implements DirectoryInput {
+
+ @NonNull
+ @Override
+ public String getName() {
+ return "test";
+ }
+
+ @NonNull
+ @Override
+ public Set<ContentType> getContentTypes() {
+ return ImmutableSet.<ContentType>of(DefaultContentType.CLASSES);
+ }
+
+ @NonNull
+ @Override
+ public Set<Scope> getScopes() {
+ return ImmutableSet.of(Scope.PROJECT);
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/profiling/RecordingBuildListenerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/profiling/RecordingBuildListenerTest.java
new file mode 100644
index 0000000..1acae4c
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/profiling/RecordingBuildListenerTest.java
@@ -0,0 +1,346 @@
+/*
+ * 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.build.gradle.profiling;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.profile.RecordingBuildListener;
+import com.android.builder.profile.AsyncRecorder;
+import com.android.builder.profile.ExecutionRecord;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.ProcessRecorder;
+import com.android.builder.profile.ProcessRecorderFactory;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+import com.android.utils.ILogger;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskState;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link RecordingBuildListener}
+ */
+public class RecordingBuildListenerTest {
+
+ @Mock
+ Task mTask;
+
+ @Mock
+ Task mSecondTask;
+
+ @Mock
+ TaskState mTaskState;
+
+ @Mock
+ Project mProject;
+
+ @Mock
+ ILogger logger;
+
+ private static final class TestRecorder implements Recorder {
+
+ final AtomicLong recordId = new AtomicLong(0);
+ final List<ExecutionRecord> records = new CopyOnWriteArrayList<ExecutionRecord>();
+
+ @Override
+ public <T> T record(@NonNull ExecutionType executionType, @NonNull Recorder.Block<T> block,
+ Recorder.Property... properties) {
+ throw new UnsupportedOperationException("record method was not supposed to be called.");
+ }
+
+ @Override
+ public <T> T record(@NonNull ExecutionType executionType, @NonNull Recorder.Block<T> block,
+ @NonNull List<Recorder.Property> properties) {
+ throw new UnsupportedOperationException("record method was not supposed to be called.");
+ }
+
+ @Override
+ public long allocationRecordId() {
+ return recordId.incrementAndGet();
+ }
+
+ @Override
+ public void closeRecord(ExecutionRecord record) {
+ records.add(record);
+ }
+ }
+
+ private static final class TestExecutionRecordWriter implements ProcessRecorder.ExecutionRecordWriter {
+
+ final List<ExecutionRecord> records = new CopyOnWriteArrayList<ExecutionRecord>();
+
+ @Override
+ public void write(@NonNull ExecutionRecord executionRecord) throws IOException {
+ records.add(executionRecord);
+ }
+
+ List<ExecutionRecord> getRecords() {
+ return records;
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mTask.getName()).thenReturn("taskName");
+ when(mTask.getProject()).thenReturn(mProject);
+ when(mProject.getName()).thenReturn("projectName");
+
+ when(mSecondTask.getName()).thenReturn("task2Name");
+ when(mSecondTask.getProject()).thenReturn(mProject);
+ when(mProject.getName()).thenReturn("projectName");
+ }
+
+ @Test
+ public void singleThreadInvocation() {
+ TestRecorder recorder = new TestRecorder();
+ RecordingBuildListener listener = new RecordingBuildListener(recorder);
+
+ listener.beforeExecute(mTask);
+ listener.afterExecute(mTask, mTaskState);
+ assertEquals(1, recorder.records.size());
+ ExecutionRecord record = recorder.records.get(0);
+ assertEquals(1, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "taskName");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+ }
+
+ @Test
+ public void singleThreadWithMultipleSpansInvocation() throws InterruptedException {
+
+ TestExecutionRecordWriter recordWriter = new TestExecutionRecordWriter();
+ ProcessRecorderFactory.initializeForTests(recordWriter);
+
+ RecordingBuildListener listener = new RecordingBuildListener(ThreadRecorder.get());
+
+ listener.beforeExecute(mTask);
+ ThreadRecorder.get().record(ExecutionType.SOME_RANDOM_PROCESSING,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ Logger.getAnonymousLogger().finest("useless block");
+ return null;
+ }
+ }); {
+ }
+ listener.afterExecute(mTask, mTaskState);
+ ProcessRecorderFactory.shutdown();
+
+ assertEquals(4, recordWriter.getRecords().size());
+ ExecutionRecord record = getRecordForId(recordWriter.getRecords(), 2);
+ assertEquals(2, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "taskName");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+
+ record = getRecordForId(recordWriter.getRecords(), 3);
+ assertNotNull(record);
+ assertEquals(3, record.id);
+ assertEquals(2, record.parentId);
+ assertEquals(0, record.attributes.size());
+ assertEquals(ExecutionType.SOME_RANDOM_PROCESSING, record.type);
+ }
+
+ @Test
+ public void simulateTasksUnorderedLifecycleEventsDelivery() throws InterruptedException {
+
+ TestExecutionRecordWriter recordWriter = new TestExecutionRecordWriter();
+ ProcessRecorderFactory.initializeForTests(recordWriter);
+
+ RecordingBuildListener listener = new RecordingBuildListener(AsyncRecorder.get());
+
+ listener.beforeExecute(mTask);
+ listener.beforeExecute(mSecondTask);
+ ThreadRecorder.get().record(ExecutionType.SOME_RANDOM_PROCESSING,
+ new Recorder.Block<Object>() {
+ @Override
+ public Object call() throws Exception {
+ logger.verbose("useless block");
+ return null;
+ }
+ });
+ listener.afterExecute(mTask, mTaskState);
+ listener.afterExecute(mSecondTask, mTaskState);
+
+ ProcessRecorderFactory.shutdown();
+
+ assertEquals(5, recordWriter.getRecords().size());
+ ExecutionRecord record = getRecordForId(recordWriter.getRecords(), 2);
+ assertEquals(2, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "taskName");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+
+ record = getRecordForId(recordWriter.getRecords(), 3);
+ assertEquals(3, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "task2Name");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+
+ record = getRecordForId(recordWriter.getRecords(), 4);
+ assertNotNull(record);
+ assertEquals(4, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(0, record.attributes.size());
+ assertEquals(ExecutionType.SOME_RANDOM_PROCESSING, record.type);
+ }
+
+ @Test
+ public void testIinitialAndFinalRecords() throws InterruptedException {
+
+ TestExecutionRecordWriter recordWriter = new TestExecutionRecordWriter();
+ ProcessRecorderFactory.initializeForTests(recordWriter);
+
+ ProcessRecorderFactory.shutdown();
+
+ assertEquals(2, recordWriter.getRecords().size());
+ for (ExecutionRecord record : recordWriter.getRecords()) {
+ System.out.println(record);
+ }
+ ExecutionRecord record = getRecordForId(recordWriter.getRecords(), 1);
+ assertEquals(1, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(6, record.attributes.size());
+ assertEquals(ExecutionType.INITIAL_METADATA, record.type);
+ ensurePropertyValue(record.attributes, "os_name", System.getProperty("os.name"));
+
+ record = getRecordForId(recordWriter.getRecords(), 2);
+ assertNotNull(record);
+ assertEquals(2, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(3, record.attributes.size());
+ assertEquals(ExecutionType.FINAL_METADATA, record.type);
+ }
+
+ @Test
+ public void multipleThreadsInvocation() {
+ TestRecorder recorder = new TestRecorder();
+ RecordingBuildListener listener = new RecordingBuildListener(recorder);
+ Task secondTask = Mockito.mock(Task.class);
+ when(secondTask.getName()).thenReturn("secondTaskName");
+ when(secondTask.getProject()).thenReturn(mProject);
+
+ // first thread start
+ listener.beforeExecute(mTask);
+
+ // now second threads start
+ listener.beforeExecute(secondTask);
+
+ // first thread finishes
+ listener.afterExecute(mTask, mTaskState);
+
+ // and second thread finishes
+ listener.afterExecute(secondTask, mTaskState);
+
+ assertEquals(2, recorder.records.size());
+ ExecutionRecord record = getRecordForId(recorder.records, 1);
+ assertEquals(1, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "taskName");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+
+ record = getRecordForId(recorder.records, 2);
+ assertEquals(2, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "secondTaskName");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+ }
+
+ @Test
+ public void multipleThreadsOrderInvocation() {
+ TestRecorder recorder = new TestRecorder();
+ RecordingBuildListener listener = new RecordingBuildListener(recorder);
+ Task secondTask = Mockito.mock(Task.class);
+ when(secondTask.getName()).thenReturn("secondTaskName");
+ when(secondTask.getProject()).thenReturn(mProject);
+
+ // first thread start
+ listener.beforeExecute(mTask);
+
+ // now second threads start
+ listener.beforeExecute(secondTask);
+
+ // second thread finishes
+ listener.afterExecute(secondTask, mTaskState);
+
+ // and first thread finishes
+ listener.afterExecute(mTask, mTaskState);
+
+ assertEquals(2, recorder.records.size());
+ ExecutionRecord record = getRecordForId(recorder.records, 1);
+ assertEquals(1, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "taskName");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+
+ record = getRecordForId(recorder.records, 2);
+ assertEquals(2, record.id);
+ assertEquals(0, record.parentId);
+ assertEquals(2, record.attributes.size());
+ ensurePropertyValue(record.attributes, "task", "secondTaskName");
+ ensurePropertyValue(record.attributes, "project", "projectName");
+ }
+
+ private static void ensurePropertyValue(
+ List<Recorder.Property> properties, String name, String value) {
+
+ for (Recorder.Property property : properties) {
+ if (property.getName().equals(name)){
+ assertEquals(value, property.getValue());
+ }
+ }
+ }
+
+ @Nullable
+ private static ExecutionRecord getRecordForId(List<ExecutionRecord> records, long recordId) {
+ for (ExecutionRecord record : records) {
+ if (record.id == recordId) {
+ return record;
+ }
+ }
+ return null;
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/AbstractShrinkerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/AbstractShrinkerTest.java
new file mode 100644
index 0000000..03e49c4
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/AbstractShrinkerTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.shrinker.parser.FilterSpecification;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.InnerClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Common code for testing shrinker runs.
+ */
+public abstract class AbstractShrinkerTest {
+
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
+
+ protected File mTestPackageDir;
+
+ protected File mOutDir;
+
+ protected Collection<TransformInput> mInputs;
+
+ protected File mIncrementalDir;
+
+ protected TransformOutputProvider mOutput;
+
+ protected DirectoryInput mDirectoryInput;
+
+ protected ShrinkerLogger mShrinkerLogger =
+ new ShrinkerLogger(
+ Collections.<FilterSpecification>emptyList(),
+ LoggerFactory.getLogger(getClass()));
+
+ protected int mExpectedWarnings;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestPackageDir = tmpDir.newFolder("app-classes", "test");
+ File classDir = new File(tmpDir.getRoot(), "app-classes");
+ mOutDir = tmpDir.newFolder("out");
+ mIncrementalDir = tmpDir.newFolder("incremental");
+
+ mDirectoryInput = mock(DirectoryInput.class);
+ when(mDirectoryInput.getFile()).thenReturn(classDir);
+ TransformInput transformInput = mock(TransformInput.class);
+ when(transformInput.getDirectoryInputs()).thenReturn(ImmutableList.of(mDirectoryInput));
+ mOutput = mock(TransformOutputProvider.class);
+ // we probably want a better mock that than so that we can return different dir depending
+ // on inputs.
+ when(mOutput.getContentLocation(
+ Mockito.anyString(),
+ Mockito.anySetOf(ContentType.class),
+ Mockito.anySetOf(Scope.class),
+ Mockito.any(Format.class))).thenReturn(mOutDir);
+
+ mInputs = ImmutableList.of(transformInput);
+ }
+
+ @Before
+ public void resetWarningsCounter() throws Exception {
+ mExpectedWarnings = 0;
+ }
+
+ @After
+ public void checkLogger() throws Exception {
+ assertEquals(mExpectedWarnings, mShrinkerLogger.getWarningsCount());
+ }
+
+ protected void assertClassSkipped(String className) {
+ assertFalse(
+ "Class " + className + " exists in output.",
+ getOutputClassFile(className).exists());
+ }
+
+ protected void assertImplements(String className, String interfaceName) throws IOException {
+ File classFile = getOutputClassFile(className);
+ assertThat(getInterfaceNames(classFile)).contains(interfaceName);
+ }
+
+ protected void assertDoesntImplement(String className, String interfaceName) throws IOException {
+ File classFile = getOutputClassFile(className);
+ assertThat(getInterfaceNames(classFile)).doesNotContain(interfaceName);
+ }
+
+ protected static Set<String> getInterfaceNames(File classFile) throws IOException {
+ ClassNode classNode = getClassNode(classFile);
+
+ if (classNode.interfaces == null) {
+ return ImmutableSet.of();
+ } else {
+ //noinspection unchecked
+ return ImmutableSet.copyOf(classNode.interfaces);
+ }
+ }
+
+ private static List<InnerClassNode> getInnerClasses(File classFile) throws IOException {
+ ClassNode classNode = getClassNode(classFile);
+ if (classNode.innerClasses == null) {
+ return ImmutableList.of();
+ } else {
+ //noinspection unchecked
+ return classNode.innerClasses;
+ }
+ }
+
+ protected void assertSingleInnerClassesEntry(String className, String outer, String inner)
+ throws IOException {
+ List<InnerClassNode> innerClasses = getInnerClasses(getOutputClassFile(className));
+ assertThat(innerClasses).hasSize(1);
+ assertThat(innerClasses.get(0).outerName).isEqualTo(outer);
+ assertThat(innerClasses.get(0).name).isEqualTo(inner);
+ }
+
+ protected void assertNoInnerClasses(String className) throws IOException {
+ List<InnerClassNode> innerClasses = getInnerClasses(getOutputClassFile(className));
+ assertThat(innerClasses).isEmpty();
+ }
+
+ protected void assertMembersLeft(String className, String... members) throws IOException {
+ File outFile = getOutputClassFile(className);
+
+ assertTrue(
+ String.format("Class %s does not exist in output.", className),
+ outFile.exists());
+
+ assertThat(getMembers(outFile)).containsExactlyElementsIn(Arrays.asList(members));
+ }
+
+ @NonNull
+ protected static Set<File> getPlatformJars() {
+ String androidHomePath = System.getenv(SdkConstants.ANDROID_HOME_ENV);
+
+ assertThat(androidHomePath).named("$ANDROID_HOME env variable").isNotNull();
+
+ File androidHome = new File(androidHomePath);
+ File androidJar = FileUtils.join(
+ androidHome,
+ SdkConstants.FD_PLATFORMS,
+ "android-" + SdkVersionInfo.HIGHEST_KNOWN_STABLE_API,
+ "android.jar");
+ assertTrue(androidJar.getAbsolutePath(), androidJar.exists());
+
+ return ImmutableSet.of(androidJar);
+ }
+
+ private static Set<String> getMembers(File classFile) throws IOException {
+ ClassNode classNode = getClassNode(classFile);
+
+ Set<String> members = Sets.newHashSet();
+ //noinspection unchecked - ASM API
+ for (MethodNode methodNode : (List<MethodNode>) classNode.methods) {
+ members.add(methodNode.name + ":" + methodNode.desc);
+ }
+
+ //noinspection unchecked - ASM API
+ for (FieldNode fieldNode : (List<FieldNode>) classNode.fields) {
+ members.add(fieldNode.name + ":" + fieldNode.desc);
+ }
+
+ return members;
+ }
+
+ @NonNull
+ private static ClassNode getClassNode(File classFile) throws IOException {
+ ClassReader classReader = new ClassReader(Files.toByteArray(classFile));
+ ClassNode classNode = new ClassNode(Opcodes.ASM5);
+ classReader.accept(
+ classNode,
+ ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ return classNode;
+ }
+
+ @NonNull
+ protected File getOutputClassFile(String className) {
+ return FileUtils.join(mOutDir, "test", className + ".class");
+ }
+
+ @NonNull
+ protected KeepRules parseKeepRules(String rules) throws IOException {
+ ProguardConfig config = new ProguardConfig();
+ config.parse(rules);
+ return new ProguardFlagsKeepRules(config.getFlags(), mShrinkerLogger);
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/FullRunShrinkerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/FullRunShrinkerTest.java
new file mode 100644
index 0000000..2d2c0ca
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/FullRunShrinkerTest.java
@@ -0,0 +1,1433 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.android.annotations.NonNull;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.gradle.shrinker.TestClasses.InnerClasses;
+import com.android.build.gradle.shrinker.TestClasses.Interfaces;
+import com.android.build.gradle.shrinker.TestClasses.Reflection;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+
+/**
+ * Tests for {@link FullRunShrinker}.
+ */
+public class FullRunShrinkerTest extends AbstractShrinkerTest {
+
+ private FullRunShrinker<String> mShrinker;
+
+ @Before
+ public void createShrinker() throws Exception {
+ mShrinker = new FullRunShrinker<String>(
+ new WaitableExecutor<Void>(),
+ buildGraph(),
+ getPlatformJars(),
+ mShrinkerLogger);
+ }
+
+ @NonNull
+ private ShrinkerGraph<String> buildGraph() throws IOException {
+ return JavaSerializationShrinkerGraph.empty(mIncrementalDir);
+ }
+
+ @Test
+ public void simple_oneClass() throws Exception {
+ // Given:
+ Files.write(TestClasses.SimpleScenario.aaa(), new File(mTestPackageDir, "Aaa.class"));
+
+ // When:
+ run("Aaa", "aaa:()V");
+
+ // Then:
+ assertMembersLeft("Aaa", "aaa:()V", "bbb:()V");
+ }
+
+ @Test
+ public void simple_threeClasses() throws Exception {
+ // Given:
+ Files.write(TestClasses.SimpleScenario.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(TestClasses.SimpleScenario.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(TestClasses.SimpleScenario.ccc(), new File(mTestPackageDir, "Ccc.class"));
+
+ // When:
+ run("Bbb", "bbb:(Ltest/Aaa;)V");
+
+ // Then:
+ assertMembersLeft("Aaa", "aaa:()V", "bbb:()V");
+ assertMembersLeft("Bbb", "bbb:(Ltest/Aaa;)V");
+ assertClassSkipped("Ccc");
+ }
+
+ @Test
+ public void virtualCalls_keepEntryPointsSuperclass() throws Exception {
+ // Given:
+ Files.write(TestClasses.VirtualCalls.abstractClass(), new File(mTestPackageDir, "AbstractClass.class"));
+ Files.write(TestClasses.VirtualCalls.impl(1), new File(mTestPackageDir, "Impl1.class"));
+
+ // When:
+ run("Impl1", "abstractMethod:()V");
+
+ // Then:
+ assertMembersLeft("Impl1", "abstractMethod:()V");
+ assertMembersLeft("AbstractClass");
+ }
+
+ @Test
+ public void virtualCalls_abstractType() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.VirtualCalls.main_abstractType(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.VirtualCalls.abstractClass(), new File(mTestPackageDir, "AbstractClass.class"));
+ Files.write(TestClasses.VirtualCalls.impl(1), new File(mTestPackageDir, "Impl1.class"));
+ Files.write(TestClasses.VirtualCalls.impl(2), new File(mTestPackageDir, "Impl2.class"));
+ Files.write(TestClasses.VirtualCalls.impl(3), new File(mTestPackageDir, "Impl3.class"));
+
+ // When:
+ run("Main", "main:([Ljava/lang/String;)V");
+
+ // Then:
+ assertMembersLeft("Main", "main:([Ljava/lang/String;)V");
+ assertClassSkipped("Impl3");
+ assertMembersLeft("AbstractClass", "abstractMethod:()V", "<init>:()V");
+ assertMembersLeft("Impl1", "abstractMethod:()V", "<init>:()V");
+ assertMembersLeft("Impl2", "abstractMethod:()V", "<init>:()V");
+ }
+
+ @Test
+ public void virtualCalls_concreteType() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.VirtualCalls.main_concreteType(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.VirtualCalls.abstractClass(), new File(mTestPackageDir, "AbstractClass.class"));
+ Files.write(TestClasses.VirtualCalls.impl(1), new File(mTestPackageDir, "Impl1.class"));
+ Files.write(TestClasses.VirtualCalls.impl(2), new File(mTestPackageDir, "Impl2.class"));
+ Files.write(TestClasses.VirtualCalls.impl(3), new File(mTestPackageDir, "Impl3.class"));
+
+ // When:
+ run("Main", "main:([Ljava/lang/String;)V");
+
+ // Then:
+ assertMembersLeft("Main", "main:([Ljava/lang/String;)V");
+ assertClassSkipped("Impl3");
+ assertMembersLeft("AbstractClass", "<init>:()V");
+ assertMembersLeft("Impl1", "abstractMethod:()V", "<init>:()V");
+ assertMembersLeft("Impl2", "<init>:()V");
+ }
+
+ @Test
+ public void virtualCalls_methodFromParent() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.VirtualCalls.main_parentChild(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.VirtualCalls.parent(), new File(mTestPackageDir, "Parent.class"));
+ Files.write(TestClasses.VirtualCalls.child(), new File(mTestPackageDir, "Child.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("Parent", "<init>:()V", "onlyInParent:()V");
+ assertMembersLeft("Child", "<init>:()V");
+ }
+
+ @Test
+ public void sdkTypes_methodsFromJavaClasses() throws Exception {
+ // Given:
+ Files.write(TestClasses.SdkTypes.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(
+ TestClasses.SdkTypes.myException(), new File(mTestPackageDir, "MyException.class"));
+
+ // When:
+ run("Main", "main:([Ljava/lang/String;)V");
+
+ // Then:
+ assertMembersLeft("Main", "main:([Ljava/lang/String;)V");
+ assertMembersLeft(
+ "MyException",
+ "<init>:()V",
+ "hashCode:()I",
+ "getMessage:()Ljava/lang/String;");
+ }
+
+ @Test
+ public void interfaces_sdkInterface_classUsed_abstractType() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(
+ "Main",
+ "buildMyCharSequence:()Ltest/MyCharSequence;",
+ "callCharSequence:(Ljava/lang/CharSequence;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "buildMyCharSequence:()Ltest/MyCharSequence;",
+ "callCharSequence:(Ljava/lang/CharSequence;)V");
+ assertMembersLeft(
+ "MyCharSequence",
+ "subSequence:(II)Ljava/lang/CharSequence;",
+ "charAt:(I)C",
+ "length:()I",
+ "<init>:()V");
+ assertClassSkipped("MyInterface");
+ assertClassSkipped("MyImpl");
+
+ assertImplements("MyCharSequence", "java/lang/CharSequence");
+ }
+
+ @Test
+ public void interfaces_sdkInterface_implementedIndirectly() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(
+ "Main",
+ "buildNamedRunnableImpl:()Ltest/NamedRunnableImpl;",
+ "callRunnable:(Ljava/lang/Runnable;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "buildNamedRunnableImpl:()Ltest/NamedRunnableImpl;",
+ "callRunnable:(Ljava/lang/Runnable;)V");
+ assertMembersLeft(
+ "NamedRunnableImpl",
+ "run:()V",
+ "<init>:()V");
+
+ assertMembersLeft("NamedRunnable");
+ assertImplements("NamedRunnableImpl", "test/NamedRunnable");
+ }
+
+ @Test
+ public void interfaces_sdkInterface_classUsed_concreteType() throws Exception {
+ // Given:
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(
+ "Main",
+ "buildMyCharSequence:()Ltest/MyCharSequence;",
+ "callMyCharSequence:(Ltest/MyCharSequence;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "buildMyCharSequence:()Ltest/MyCharSequence;",
+ "callMyCharSequence:(Ltest/MyCharSequence;)V");
+ assertMembersLeft(
+ "MyCharSequence",
+ "subSequence:(II)Ljava/lang/CharSequence;",
+ "charAt:(I)C",
+ "length:()I",
+ "<init>:()V");
+ assertClassSkipped("MyInterface");
+ assertClassSkipped("MyImpl");
+
+ assertImplements("MyCharSequence", "java/lang/CharSequence");
+ }
+
+ @Test
+ public void interfaces_implementationFromSuperclass() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(
+ "Main",
+ "useImplementationFromSuperclass:(Ltest/ImplementationFromSuperclass;)V",
+ "useMyInterface:(Ltest/MyInterface;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useImplementationFromSuperclass:(Ltest/ImplementationFromSuperclass;)V",
+ "useMyInterface:(Ltest/MyInterface;)V");
+ assertMembersLeft("ImplementationFromSuperclass");
+ assertMembersLeft(
+ "MyInterface",
+ "doSomething:(Ljava/lang/Object;)V");
+ assertClassSkipped("MyImpl");
+ assertClassSkipped("MyCharSequence");
+
+ // This is the tricky part: this method should be kept, because a subclass is using it to
+ // implement an interface.
+ assertMembersLeft(
+ "DoesSomething",
+ "doSomething:(Ljava/lang/Object;)V");
+
+ assertImplements("ImplementationFromSuperclass", "test/MyInterface");
+ }
+
+ @Test
+ public void interfaces_implementationFromSuperclass_interfaceInheritance() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.mySubInterface(), new File(mTestPackageDir, "MySubInterface.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass_subInterface(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(
+ "Main",
+ "useImplementationFromSuperclass:(Ltest/ImplementationFromSuperclass;)V",
+ "useMyInterface:(Ltest/MyInterface;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useImplementationFromSuperclass:(Ltest/ImplementationFromSuperclass;)V",
+ "useMyInterface:(Ltest/MyInterface;)V");
+ assertMembersLeft("ImplementationFromSuperclass");
+ assertMembersLeft(
+ "MyInterface",
+ "doSomething:(Ljava/lang/Object;)V");
+
+ // This is the tricky part: this method should be kept, because a subclass is using it to
+ // implement an interface.
+ assertMembersLeft(
+ "DoesSomething",
+ "doSomething:(Ljava/lang/Object;)V");
+
+ assertMembersLeft("MySubInterface");
+ assertClassSkipped("MyImpl");
+ assertClassSkipped("MyCharSequence");
+
+ assertImplements("ImplementationFromSuperclass", "test/MySubInterface");
+ }
+
+ @Test
+ public void interfaces_sdkInterface_classNotUsed() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(
+ "Main",
+ "callCharSequence:(Ljava/lang/CharSequence;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "callCharSequence:(Ljava/lang/CharSequence;)V");
+ assertClassSkipped("MyCharSequence");
+ assertClassSkipped("MyInterface");
+ assertClassSkipped("MyImpl");
+ }
+
+ @Test
+ public void interfaces_keepRules_interfaceOnInterface() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(parseKeepRules("-keep interface test/MyInterface"));
+
+ // Then:
+ assertMembersLeft("MyInterface");
+ }
+
+ @Test
+ public void interfaces_keepRules_interfaceOnClass() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(parseKeepRules("-keep interface test/Main"));
+
+ // Then:
+ assertClassSkipped("Main");
+ }
+
+ @Test
+ public void interfaces_keepRules_atInterfaceOnInterface() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(parseKeepRules("-keep @interface test/MyInterface"));
+
+ // Then:
+ assertClassSkipped("MyInterface");
+ }
+
+ @Test
+ public void interfaces_appInterface_abstractType() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run(
+ "Main",
+ "buildMyImpl:()Ltest/MyImpl;",
+ "useMyInterface:(Ltest/MyInterface;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "buildMyImpl:()Ltest/MyImpl;",
+ "useMyInterface:(Ltest/MyInterface;)V");
+ assertMembersLeft(
+ "MyInterface",
+ "doSomething:(Ljava/lang/Object;)V");
+ assertMembersLeft(
+ "MyImpl",
+ "<init>:()V",
+ "doSomething:(Ljava/lang/Object;)V");
+ assertClassSkipped("MyCharSequence");
+
+ assertImplements("MyImpl", "test/MyInterface");
+ }
+
+ @Test
+ public void interfaces_appInterface_concreteType() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run("Main", "useMyImpl_interfaceMethod:(Ltest/MyImpl;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useMyImpl_interfaceMethod:(Ltest/MyImpl;)V");
+ assertClassSkipped("MyInterface");
+ assertMembersLeft("MyImpl", "doSomething:(Ljava/lang/Object;)V");
+ assertClassSkipped("MyCharSequence");
+
+ assertDoesntImplement("MyImpl", "test/MyInterface");
+ }
+
+ @Test
+ public void interfaces_appInterface_interfaceNotUsed() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ run("Main", "useMyImpl_otherMethod:(Ltest/MyImpl;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useMyImpl_otherMethod:(Ltest/MyImpl;)V");
+ assertClassSkipped("MyInterface");
+ assertMembersLeft(
+ "MyImpl",
+ "someOtherMethod:()V");
+ assertClassSkipped("MyCharSequence");
+
+ assertDoesntImplement("MyImpl", "test/MyInterface");
+ }
+
+ @Test
+ public void fields() throws Exception {
+ // Given:
+ Files.write(TestClasses.Fields.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Fields.myFields(), new File(mTestPackageDir, "MyFields.class"));
+ Files.write(TestClasses.Fields.myFieldsSubclass(), new File(mTestPackageDir, "MyFieldsSubclass.class"));
+ Files.write(TestClasses.emptyClass("MyFieldType"), new File(mTestPackageDir, "MyFieldType.class"));
+
+ // When:
+ run("Main", "main:()I");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "main:()I");
+ assertMembersLeft(
+ "MyFields",
+ "<init>:()V",
+ "<clinit>:()V",
+ "readField:()I",
+ "f1:I",
+ "f2:I",
+ "f4:Ltest/MyFieldType;",
+ "sString:Ljava/lang/String;");
+ assertMembersLeft("MyFieldType");
+ }
+
+ @Test
+ public void fields_fromSuperclass() throws Exception {
+ // Given:
+ Files.write(TestClasses.Fields.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Fields.myFields(), new File(mTestPackageDir, "MyFields.class"));
+ Files.write(TestClasses.Fields.myFieldsSubclass(), new File(mTestPackageDir, "MyFieldsSubclass.class"));
+ Files.write(TestClasses.emptyClass("MyFieldType"), new File(mTestPackageDir, "MyFieldType.class"));
+
+ // When:
+ run("Main", "main_subclass:()I");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "main_subclass:()I");
+ assertMembersLeft(
+ "MyFields",
+ "<init>:()V",
+ "<clinit>:()V",
+ "f1:I",
+ "f2:I",
+ "sString:Ljava/lang/String;");
+ assertMembersLeft(
+ "MyFieldsSubclass",
+ "<init>:()V");
+ assertClassSkipped("MyFieldType");
+ }
+
+ @Test
+ public void overrides_methodNotUsed() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.MultipleOverriddenMethods.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceOne(), new File(mTestPackageDir, "InterfaceOne.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceTwo(), new File(mTestPackageDir, "InterfaceTwo.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.implementation(), new File(mTestPackageDir, "Implementation.class"));
+
+ // When:
+ run("Main", "buildImplementation:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "buildImplementation:()V");
+ assertClassSkipped("InterfaceOne");
+ assertClassSkipped("InterfaceTwo");
+ assertMembersLeft("Implementation", "<init>:()V");
+ }
+
+ @Test
+ public void overrides_classNotUsed() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.MultipleOverriddenMethods.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceOne(), new File(mTestPackageDir, "InterfaceOne.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceTwo(), new File(mTestPackageDir, "InterfaceTwo.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.implementation(), new File(mTestPackageDir, "Implementation.class"));
+
+ // When:
+ run(
+ "Main",
+ "useInterfaceOne:(Ltest/InterfaceOne;)V",
+ "useInterfaceTwo:(Ltest/InterfaceTwo;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useInterfaceOne:(Ltest/InterfaceOne;)V",
+ "useInterfaceTwo:(Ltest/InterfaceTwo;)V");
+ assertMembersLeft("InterfaceOne", "m:()V");
+ assertMembersLeft("InterfaceTwo", "m:()V");
+ assertClassSkipped("MyImplementation");
+ }
+
+ @Test
+ public void overrides_interfaceOneUsed_classUsed() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.MultipleOverriddenMethods.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceOne(), new File(mTestPackageDir, "InterfaceOne.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceTwo(), new File(mTestPackageDir, "InterfaceTwo.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.implementation(), new File(mTestPackageDir, "Implementation.class"));
+
+ // When:
+ run(
+ "Main",
+ "useInterfaceOne:(Ltest/InterfaceOne;)V",
+ "buildImplementation:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useInterfaceOne:(Ltest/InterfaceOne;)V",
+ "buildImplementation:()V");
+ assertMembersLeft("InterfaceOne", "m:()V");
+ assertClassSkipped("InterfaceTwo");
+ assertMembersLeft("Implementation", "<init>:()V", "m:()V");
+ }
+
+ @Test
+ public void overrides_interfaceTwoUsed_classUsed() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.MultipleOverriddenMethods.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceOne(), new File(mTestPackageDir, "InterfaceOne.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceTwo(), new File(mTestPackageDir, "InterfaceTwo.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.implementation(), new File(mTestPackageDir, "Implementation.class"));
+
+ // When:
+ run(
+ "Main",
+ "useInterfaceTwo:(Ltest/InterfaceTwo;)V",
+ "buildImplementation:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useInterfaceTwo:(Ltest/InterfaceTwo;)V",
+ "buildImplementation:()V");
+ assertMembersLeft("InterfaceTwo", "m:()V");
+ assertClassSkipped("InterfaceOne");
+ assertMembersLeft("Implementation", "<init>:()V", "m:()V");
+ }
+
+ @Test
+ public void overrides_twoInterfacesUsed_classUsed() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.MultipleOverriddenMethods.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceOne(), new File(mTestPackageDir, "InterfaceOne.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceTwo(), new File(mTestPackageDir, "InterfaceTwo.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.implementation(), new File(mTestPackageDir, "Implementation.class"));
+
+ // When:
+ run(
+ "Main",
+ "useInterfaceOne:(Ltest/InterfaceOne;)V",
+ "useInterfaceTwo:(Ltest/InterfaceTwo;)V",
+ "buildImplementation:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useInterfaceOne:(Ltest/InterfaceOne;)V",
+ "useInterfaceTwo:(Ltest/InterfaceTwo;)V",
+ "buildImplementation:()V");
+ assertMembersLeft("InterfaceOne", "m:()V");
+ assertMembersLeft("InterfaceTwo", "m:()V");
+ assertMembersLeft("Implementation", "<init>:()V", "m:()V");
+ }
+
+ @Test
+ public void overrides_noInterfacesUsed_classUsed() throws Exception {
+ // Given:
+ Files.write(
+ TestClasses.MultipleOverriddenMethods.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceOne(), new File(mTestPackageDir, "InterfaceOne.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.interfaceTwo(), new File(mTestPackageDir, "InterfaceTwo.class"));
+ Files.write(TestClasses.MultipleOverriddenMethods.implementation(), new File(mTestPackageDir, "Implementation.class"));
+
+ // When:
+ run(
+ "Main",
+ "useImplementation:(Ltest/Implementation;)V",
+ "buildImplementation:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useImplementation:(Ltest/Implementation;)V",
+ "buildImplementation:()V");
+ assertClassSkipped("InterfaceOne");
+ assertClassSkipped("InterfaceTwo");
+ assertMembersLeft("Implementation", "<init>:()V", "m:()V");
+ }
+
+ @Test
+ public void annotations_annotatedClass() throws Exception {
+ // Given:
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "main:()V");
+ assertMembersLeft("SomeClass");
+ assertMembersLeft("SomeOtherClass");
+ assertMembersLeft(
+ "MyAnnotation",
+ "nested:()[Ltest/Nested;",
+ "f:()I",
+ "klass:()Ljava/lang/Class;",
+ "myEnum:()Ltest/MyEnum;");
+ assertMembersLeft("Nested", "name:()Ljava/lang/String;");
+ }
+
+ @Test
+ public void annotations_annotatedMethod() throws Exception {
+ // Given:
+ Files.write(TestClasses.Annotations.main_annotatedMethod(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "main:()V");
+ assertMembersLeft("SomeClass");
+ assertMembersLeft("SomeOtherClass");
+ assertMembersLeft(
+ "MyAnnotation",
+ "nested:()[Ltest/Nested;",
+ "f:()I",
+ "klass:()Ljava/lang/Class;",
+ "myEnum:()Ltest/MyEnum;");
+ assertMembersLeft("Nested", "name:()Ljava/lang/String;");
+ }
+
+ @Test
+ public void annotations_annotationsStripped() throws Exception {
+ // Given:
+ Files.write(TestClasses.Annotations.main_annotatedMethod(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ // When:
+ run("Main", "notAnnotated:()V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "notAnnotated:()V");
+ assertClassSkipped("SomeClass");
+ assertClassSkipped("SomeOtherClass");
+ assertClassSkipped("MyAnnotation");
+ assertClassSkipped("Nested");
+ }
+
+ @Test
+ public void annotations_keepRules_class() throws Exception {
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep @test.MyAnnotation class **"));
+
+ assertMembersLeft("Main", "<init>:()V");
+ }
+
+ @Test
+ public void annotations_keepRules_atInterface() throws Exception {
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep @interface test.MyAnnotation"));
+
+ assertMembersLeft(
+ "MyAnnotation",
+ "nested:()[Ltest/Nested;",
+ "f:()I",
+ "klass:()Ljava/lang/Class;",
+ "myEnum:()Ltest/MyEnum;");
+ }
+
+ @Test
+ public void annotations_keepRules_interface() throws Exception {
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep interface test.MyAnnotation"));
+
+ assertMembersLeft(
+ "MyAnnotation",
+ "nested:()[Ltest/Nested;",
+ "f:()I",
+ "klass:()Ljava/lang/Class;",
+ "myEnum:()Ltest/MyEnum;");
+ }
+
+ @Test
+ public void annotations_keepRules_atClass() throws Exception {
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep @class test.MyAnnotation"));
+
+ assertMembersLeft(
+ "MyAnnotation",
+ "nested:()[Ltest/Nested;",
+ "f:()I",
+ "klass:()Ljava/lang/Class;",
+ "myEnum:()Ltest/MyEnum;");
+ }
+
+ @Test
+ public void annotations_keepRules_atEnum() throws Exception {
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep @enum test.MyAnnotation"));
+
+ assertClassSkipped("MyAnnotation");
+ }
+
+ @Test
+ public void annotations_keepRules_classRule() throws Exception {
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep class test.MyAnnotation"));
+
+ assertMembersLeft(
+ "MyAnnotation",
+ "nested:()[Ltest/Nested;",
+ "f:()I",
+ "klass:()Ljava/lang/Class;",
+ "myEnum:()Ltest/MyEnum;");
+ }
+
+ @Test
+ public void annotations_keepRules_wrongAtClass() throws Exception {
+ Files.write(TestClasses.Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep @class test.SomeClass"));
+
+ assertClassSkipped("SomeClass");
+ }
+
+ @Test
+ public void annotations_keepRules_method() throws Exception {
+ Files.write(
+ TestClasses.Annotations.main_annotatedMethod(),
+ new File(mTestPackageDir, "Main.class"));
+ Files.write(
+ TestClasses.Annotations.myAnnotation(),
+ new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(
+ TestClasses.emptyClass("SomeClass"),
+ new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(
+ TestClasses.emptyClass("SomeOtherClass"),
+ new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ run(parseKeepRules("-keep class ** { @test/MyAnnotation *(...);}"));
+
+ assertMembersLeft("Main", "<init>:()V", "main:()V");
+ }
+
+ @Test
+ public void signatures_classSignature() throws Exception {
+ // Given:
+ Files.write(TestClasses.Signatures.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Signatures.named(), new File(mTestPackageDir, "Named.class"));
+ Files.write(TestClasses.Signatures.namedMap(), new File(mTestPackageDir, "NamedMap.class"));
+ Files.write(TestClasses.Signatures.hasAge(), new File(mTestPackageDir, "HasAge.class"));
+
+ // When:
+ run("Main", "main:(Ltest/NamedMap;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "main:(Ltest/NamedMap;)V");
+ assertMembersLeft("NamedMap");
+ assertMembersLeft("Named");
+ assertClassSkipped("HasAge");
+ }
+
+ @Test
+ public void signatures_methodSignature() throws Exception {
+ // Given:
+ Files.write(TestClasses.Signatures.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Signatures.named(), new File(mTestPackageDir, "Named.class"));
+ Files.write(TestClasses.Signatures.namedMap(), new File(mTestPackageDir, "NamedMap.class"));
+ Files.write(TestClasses.Signatures.hasAge(), new File(mTestPackageDir, "HasAge.class"));
+
+ // When:
+ run("Main", "callMethod:(Ltest/NamedMap;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "callMethod:(Ltest/NamedMap;)V");
+ assertMembersLeft("NamedMap", "method:(Ljava/util/Collection;)V");
+ assertMembersLeft("Named");
+ assertMembersLeft("HasAge");
+ }
+
+ @Test
+ public void superCalls_directSuperclass() throws Exception {
+ // Given:
+ Files.write(TestClasses.SuperCalls.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(TestClasses.SuperCalls.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(TestClasses.SuperCalls.ccc(), new File(mTestPackageDir, "Ccc.class"));
+
+ // When:
+ run("Ccc", "callBbbMethod:()V");
+
+ // Then:
+ assertMembersLeft("Aaa");
+ assertMembersLeft("Bbb", "onlyInBbb:()V");
+ assertMembersLeft("Ccc", "callBbbMethod:()V");
+ }
+
+ @Test
+ public void superCalls_indirectSuperclass() throws Exception {
+ // Given:
+ Files.write(TestClasses.SuperCalls.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(TestClasses.SuperCalls.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(TestClasses.SuperCalls.ccc(), new File(mTestPackageDir, "Ccc.class"));
+
+ // When:
+ run("Ccc", "callAaaMethod:()V");
+
+ // Then:
+ assertMembersLeft("Aaa", "onlyInAaa:()V");
+ assertMembersLeft("Bbb");
+ assertMembersLeft("Ccc", "callAaaMethod:()V");
+ }
+
+ @Test
+ public void superCalls_both() throws Exception {
+ // Given:
+ Files.write(TestClasses.SuperCalls.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(TestClasses.SuperCalls.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(TestClasses.SuperCalls.ccc(), new File(mTestPackageDir, "Ccc.class"));
+
+ // When:
+ run("Ccc", "callOverriddenMethod:()V");
+
+ // Then:
+ assertMembersLeft("Aaa");
+ assertMembersLeft("Bbb", "overridden:()V");
+ assertMembersLeft("Ccc", "callOverriddenMethod:()V");
+ }
+
+ @Test
+ public void innerClasses_useOuter() throws Exception {
+ // Given:
+ Files.write(InnerClasses.main_useOuterClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(InnerClasses.outer(), new File(mTestPackageDir, "Outer.class"));
+ Files.write(InnerClasses.inner(), new File(mTestPackageDir, "Outer$Inner.class"));
+ Files.write(InnerClasses.staticInner(), new File(mTestPackageDir, "Outer$StaticInner.class"));
+ Files.write(InnerClasses.anonymous(), new File(mTestPackageDir, "Outer$1.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("Outer", "outerMethod:()V", "<init>:()V");
+ assertClassSkipped("Outer$Inner");
+ assertClassSkipped("Outer$StaticInner");
+ assertClassSkipped("Outer$1");
+
+ assertNoInnerClasses("Outer");
+ assertNoInnerClasses("Main");
+ }
+
+ @Test
+ public void innerClasses_useOuter_anonymous() throws Exception {
+ // Given:
+ Files.write(
+ InnerClasses.main_useOuterClass_makeAnonymousClass(),
+ new File(mTestPackageDir, "Main.class"));
+ Files.write(InnerClasses.outer(), new File(mTestPackageDir, "Outer.class"));
+ Files.write(InnerClasses.inner(), new File(mTestPackageDir, "Outer$Inner.class"));
+ Files.write(InnerClasses.staticInner(), new File(mTestPackageDir, "Outer$StaticInner.class"));
+ Files.write(InnerClasses.anonymous(), new File(mTestPackageDir, "Outer$1.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("Outer", "makeRunnable:()V", "<init>:()V");
+ assertMembersLeft("Outer$1",
+ "run:()V",
+ "<init>:(Ltest/Outer;)V",
+ "this$0:Ltest/Outer;");
+ assertClassSkipped("Outer$Inner");
+ assertClassSkipped("Outer$StaticInner");
+
+ assertSingleInnerClassesEntry("Outer", null, "test/Outer$1");
+ assertNoInnerClasses("Main");
+ }
+
+ @Test
+ public void innerClasses_useInner() throws Exception {
+ // Given:
+ Files.write(InnerClasses.main_useInnerClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(InnerClasses.outer(), new File(mTestPackageDir, "Outer.class"));
+ Files.write(InnerClasses.inner(), new File(mTestPackageDir, "Outer$Inner.class"));
+ Files.write(InnerClasses.staticInner(), new File(mTestPackageDir, "Outer$StaticInner.class"));
+ Files.write(InnerClasses.anonymous(), new File(mTestPackageDir, "Outer$1.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("Outer", "<init>:()V");
+ assertMembersLeft(
+ "Outer$Inner",
+ "innerMethod:()V",
+ "<init>:(Ltest/Outer;)V",
+ "this$0:Ltest/Outer;");
+ assertClassSkipped("Outer$StaticInner");
+ assertClassSkipped("Outer$1");
+
+ assertSingleInnerClassesEntry("Outer", "test/Outer", "test/Outer$Inner");
+ assertSingleInnerClassesEntry("Outer$Inner", "test/Outer", "test/Outer$Inner");
+ assertSingleInnerClassesEntry("Main", "test/Outer", "test/Outer$Inner");
+ }
+
+ @Test
+ public void innerClasses_useStaticInner() throws Exception {
+ // Given:
+ Files.write(InnerClasses.main_useStaticInnerClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(InnerClasses.outer(), new File(mTestPackageDir, "Outer.class"));
+ Files.write(InnerClasses.inner(), new File(mTestPackageDir, "Outer$Inner.class"));
+ Files.write(InnerClasses.staticInner(), new File(mTestPackageDir, "Outer$StaticInner.class"));
+ Files.write(InnerClasses.anonymous(), new File(mTestPackageDir, "Outer$1.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("Outer");
+ assertClassSkipped("Outer$Inner");
+ assertClassSkipped("Outer$1");
+ assertMembersLeft("Outer$StaticInner", "<init>:()V", "staticInnerMethod:()V");
+
+ assertSingleInnerClassesEntry("Outer", "test/Outer", "test/Outer$StaticInner");
+ assertSingleInnerClassesEntry("Main", "test/Outer", "test/Outer$StaticInner");
+ assertSingleInnerClassesEntry("Outer$StaticInner", "test/Outer", "test/Outer$StaticInner");
+ }
+
+ @Test
+ public void innerClasses_notUsed() throws Exception {
+ // Given:
+ Files.write(InnerClasses.main_empty(), new File(mTestPackageDir, "Main.class"));
+ Files.write(InnerClasses.outer(), new File(mTestPackageDir, "Outer.class"));
+ Files.write(InnerClasses.inner(), new File(mTestPackageDir, "Outer$Inner.class"));
+ Files.write(InnerClasses.staticInner(), new File(mTestPackageDir, "Outer$StaticInner.class"));
+ Files.write(InnerClasses.anonymous(), new File(mTestPackageDir, "Outer$1.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertClassSkipped("Outer");
+ assertClassSkipped("Outer$Inner");
+ assertClassSkipped("Outer$StaticInner");
+ assertClassSkipped("Outer$1");
+
+ assertNoInnerClasses("Main");
+ }
+
+ @Test
+ public void staticMethods() throws Exception {
+ // Given:
+ Files.write(TestClasses.StaticMembers.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.StaticMembers.utils(), new File(mTestPackageDir, "Utils.class"));
+
+ // When:
+ run("Main", "callStaticMethod:()Ljava/lang/Object;");
+
+ // Then:
+ assertMembersLeft("Main", "callStaticMethod:()Ljava/lang/Object;");
+ assertMembersLeft("Utils", "staticMethod:()Ljava/lang/Object;");
+ }
+
+ @Test
+ public void staticFields_uninitialized() throws Exception {
+ // Given:
+ Files.write(TestClasses.StaticMembers.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.StaticMembers.utils(), new File(mTestPackageDir, "Utils.class"));
+
+ // When:
+ run("Main", "getStaticField:()Ljava/lang/Object;");
+
+ // Then:
+ assertMembersLeft("Main", "getStaticField:()Ljava/lang/Object;");
+ assertMembersLeft("Utils", "staticField:Ljava/lang/Object;");
+ }
+
+ @Test
+ public void reflection_instanceOf() throws Exception {
+ // Given:
+ Files.write(Reflection.main_instanceOf(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("Foo"), new File(mTestPackageDir, "Foo.class"));
+
+ // When:
+ run("Main", "main:(Ljava/lang/Object;)Z");
+
+ // Then:
+ assertMembersLeft("Main", "main:(Ljava/lang/Object;)Z");
+ assertMembersLeft("Foo");
+ }
+
+ @Test
+ public void reflection_classForName() throws Exception {
+ // Given:
+ Files.write(
+ Reflection.main_classForName(),
+ new File(mTestPackageDir, "Main.class"));
+ Files.write(
+ Reflection.classWithFields(),
+ new File(mTestPackageDir, "ClassWithFields.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("ClassWithFields");
+ }
+
+ @Test
+ public void reflection_classForName_dynamic() throws Exception {
+ // Given:
+ Files.write(
+ Reflection.main_classForName_dynamic(),
+ new File(mTestPackageDir, "Main.class"));
+ Files.write(
+ Reflection.classWithFields(),
+ new File(mTestPackageDir, "ClassWithFields.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertClassSkipped("ClassWithFields");
+ }
+
+ @Test
+ public void reflection_atomicIntegerFieldUpdater() throws Exception {
+ // Given:
+ Files.write(
+ Reflection.main_atomicIntegerFieldUpdater(),
+ new File(mTestPackageDir, "Main.class"));
+ Files.write(
+ Reflection.classWithFields(),
+ new File(mTestPackageDir, "ClassWithFields.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("ClassWithFields", "intField:I");
+ }
+
+ @Test
+ public void reflection_atomicLongFieldUpdater() throws Exception {
+ // Given:
+ Files.write(
+ Reflection.main_atomicLongFieldUpdater(),
+ new File(mTestPackageDir, "Main.class"));
+ Files.write(
+ Reflection.classWithFields(),
+ new File(mTestPackageDir, "ClassWithFields.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("ClassWithFields", "longField:J");
+ }
+
+ @Test
+ public void reflection_atomicReferenceFieldUpdater() throws Exception {
+ // Given:
+ Files.write(
+ Reflection.main_atomicReferenceFieldUpdater(),
+ new File(mTestPackageDir, "Main.class"));
+ Files.write(
+ Reflection.classWithFields(),
+ new File(mTestPackageDir, "ClassWithFields.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("ClassWithFields", "stringField:Ljava/lang/String;");
+ }
+
+
+ @Test
+ public void reflection_classLiteral() throws Exception {
+ // Given:
+ Files.write(
+ Reflection.main_classLiteral(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("Foo"), new File(mTestPackageDir, "Foo.class"));
+
+ // When:
+ run("Main", "main:()Ljava/lang/Object;");
+
+ // Then:
+ assertMembersLeft("Main", "main:()Ljava/lang/Object;");
+ assertMembersLeft("Foo");
+ }
+
+ @Test
+ public void testTryCatch() throws Exception {
+ // Given:
+ Files.write(TestClasses.TryCatch.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.TryCatch.customException(), new File(mTestPackageDir, "CustomException.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V", "helper:()V");
+ assertMembersLeft("CustomException");
+ }
+
+ @Test
+ public void testTryFinally() throws Exception {
+ // Given:
+ Files.write(TestClasses.TryCatch.main_tryFinally(), new File(mTestPackageDir, "Main.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V", "helper:()V");
+ }
+
+ @Test
+ public void abstractClasses_callToInterfaceMethodInAbstractClass() throws Exception {
+ // Given:
+ Files.write(TestClasses.AbstractClasses.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(TestClasses.AbstractClasses.abstractImpl(), new File(mTestPackageDir, "AbstractImpl.class"));
+ Files.write(
+ TestClasses.AbstractClasses.realImpl(), new File(mTestPackageDir, "RealImpl.class"));
+
+ // When:
+ run("RealImpl", "main:()V");
+
+ // Then:
+ assertMembersLeft("MyInterface", "m:()V");
+ assertMembersLeft("RealImpl", "main:()V", "m:()V");
+ assertMembersLeft("AbstractImpl", "helper:()V");
+ }
+
+ @Test
+ public void primitives() throws Exception {
+ // Given:
+ Files.write(TestClasses.Primitives.main(), new File(mTestPackageDir, "Main.class"));
+
+ // When:
+ run("Main", "ldc:()Ljava/lang/Object;", "checkcast:(Ljava/lang/Object;)[I");
+
+ // Then:
+ assertMembersLeft("Main", "ldc:()Ljava/lang/Object;", "checkcast:(Ljava/lang/Object;)[I");
+ }
+
+ @Test
+ public void invalidReferences_sunMiscUnsafe() throws Exception {
+ // Given:
+ Files.write(TestClasses.InvalidReferences.main_sunMiscUnsafe(), new File(mTestPackageDir, "Main.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ mExpectedWarnings = 1;
+ }
+
+ @Test
+ public void invalidReferences_Instrumentation() throws Exception {
+ // Given:
+ Files.write(TestClasses.InvalidReferences.main_javaInstrumentation(), new File(mTestPackageDir, "Main.class"));
+
+ // When:
+ run("Main", "main:()V");
+
+ // Make sure we kept the method, even though we encountered unrecognized classes.
+ assertMembersLeft("Main", "main:()V", "transform:(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[B)[B");
+ assertImplements("Main", "java/lang/instrument/ClassFileTransformer");
+ mExpectedWarnings = 1;
+ }
+
+ private void run(String className, String... methods) throws IOException {
+ run(new TestKeepRules(className, methods));
+ }
+
+ private void run(KeepRules keepRules) throws IOException {
+ mShrinker.run(
+ mInputs,
+ Collections.<TransformInput>emptyList(),
+ mOutput,
+ ImmutableMap.of(
+ AbstractShrinker.CounterSet.SHRINK, keepRules),
+ false);
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/IncrementalShrinkerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/IncrementalShrinkerTest.java
new file mode 100644
index 0000000..7931e32
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/IncrementalShrinkerTest.java
@@ -0,0 +1,480 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import com.android.build.api.transform.Status;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.gradle.shrinker.AbstractShrinker.CounterSet;
+import com.android.build.gradle.shrinker.IncrementalShrinker.IncrementalRunImpossibleException;
+import com.android.build.gradle.shrinker.TestClasses.Annotations;
+import com.android.build.gradle.shrinker.TestClasses.Interfaces;
+import com.android.build.gradle.shrinker.TestClassesForIncremental.Cycle;
+import com.android.build.gradle.shrinker.TestClassesForIncremental.Simple;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Tests for {@link IncrementalShrinker}.
+ */
+public class IncrementalShrinkerTest extends AbstractShrinkerTest {
+
+ private FullRunShrinker<String> mFullRunShrinker;
+
+ @Rule
+ public ExpectedException mException = ExpectedException.none();
+
+ @Before
+ public void createShrinker() throws Exception {
+ mFullRunShrinker = new FullRunShrinker<String>(
+ new WaitableExecutor<Void>(),
+ JavaSerializationShrinkerGraph.empty(mIncrementalDir),
+ getPlatformJars(),
+ mShrinkerLogger);
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("NotUsed"), new File(mTestPackageDir, "NotUsed.class"));
+
+ fullRun("Main", "main:()V");
+
+ assertTrue(new File(mIncrementalDir, "shrinker.bin").exists());
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("Aaa", "<init>:()V", "m1:()V");
+ assertMembersLeft("Bbb", "<init>:()V");
+ assertClassSkipped("NotUsed");
+
+ long timestampBbb = getOutputClassFile("Bbb").lastModified();
+ long timestampMain = getOutputClassFile("Main").lastModified();
+
+ // Give file timestamps time to tick.
+ Thread.sleep(1000);
+
+ Files.write(Simple.main2(), new File(mTestPackageDir, "Main.class"));
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("Aaa", "<init>:()V", "m2:()V");
+ assertMembersLeft("Bbb", "<init>:()V");
+ assertClassSkipped("NotUsed");
+
+ assertTrue(timestampMain < getOutputClassFile("Main").lastModified());
+ assertEquals(timestampBbb, getOutputClassFile("Bbb").lastModified());
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_methodAdded() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("NotUsed"), new File(mTestPackageDir, "NotUsed.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.main_extraMethod(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Main.extraMain:()V added");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_methodRemoved() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main_extraMethod(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("NotUsed"), new File(mTestPackageDir, "NotUsed.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Main.extraMain:()V removed");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_fieldAdded() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("NotUsed"), new File(mTestPackageDir, "NotUsed.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.main_extraField(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Main.sString:Ljava/lang/String; added");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_fieldRemoved() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main_extraField(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("NotUsed"), new File(mTestPackageDir, "NotUsed.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Main.sString:Ljava/lang/String; removed");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_classAdded() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(TestClasses.emptyClass("NotUsed"), new File(mTestPackageDir, "NotUsed.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage(FileUtils.toSystemDependentPath("test/NotUsed.class") + " added");
+ incrementalRun(ImmutableMap.of(
+ "Main", Status.CHANGED,
+ "NotUsed", Status.ADDED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_classRemoved() throws Exception {
+ // Given:
+ File notUsedClass = new File(mTestPackageDir, "NotUsed.class");
+
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.emptyClass("NotUsed"), notUsedClass);
+
+ fullRun("Main", "main:()V");
+
+ FileUtils.delete(notUsedClass);
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage(
+ FileUtils.toSystemDependentPath("test/NotUsed.class") + " removed");
+ incrementalRun(ImmutableMap.of(
+ "Main", Status.CHANGED,
+ "NotUsed", Status.REMOVED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_superclassChanged() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.bbb_extendsAaa(), new File(mTestPackageDir, "Bbb.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Bbb superclass changed");
+ incrementalRun(ImmutableMap.of(
+ "Bbb", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_interfacesChanged() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.bbb_serializable(), new File(mTestPackageDir, "Bbb.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Bbb interfaces changed");
+ incrementalRun(ImmutableMap.of(
+ "Bbb", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_methodModifiersChanged() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.bbb_packagePrivateConstructor(), new File(mTestPackageDir, "Bbb.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Bbb.<init>:()V modifiers changed");
+ incrementalRun(ImmutableMap.of(
+ "Bbb", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_fieldModifiersChanged() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main_extraField(), new File(mTestPackageDir, "Main.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.main_extraField_private(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Main.sString:Ljava/lang/String; modifiers changed");
+ incrementalRun(ImmutableMap.of(
+ "Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_classModifiersChanged() throws Exception {
+ // Given:
+ Files.write(Simple.aaa(), new File(mTestPackageDir, "Aaa.class"));
+ Files.write(Simple.bbb(), new File(mTestPackageDir, "Bbb.class"));
+ Files.write(Simple.main1(), new File(mTestPackageDir, "Main.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Simple.bbb_packagePrivate(), new File(mTestPackageDir, "Bbb.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("test/Bbb modifiers changed");
+ incrementalRun(ImmutableMap.of(
+ "Bbb", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_classAnnotationAdded() throws Exception {
+ // Given:
+ Files.write(Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Annotations.main_noAnnotations(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("Annotation test/MyAnnotation on test/Main removed");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_classAnnotationRemoved() throws Exception {
+ // Given:
+ Files.write(Annotations.main_noAnnotations(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Annotations.main_annotatedClass(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("Annotation test/MyAnnotation on test/Main added");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_methodAnnotationAdded() throws Exception {
+ // Given:
+ Files.write(Annotations.main_noAnnotations(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Annotations.main_annotatedMethod(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("Annotation test/MyAnnotation on test/Main.main added");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void simple_testIncrementalUpdate_methodAnnotationRemoved() throws Exception {
+ // Given:
+ Files.write(Annotations.main_annotatedMethod(), new File(mTestPackageDir, "Main.class"));
+ Files.write(TestClasses.Annotations.myAnnotation(), new File(mTestPackageDir, "MyAnnotation.class"));
+ Files.write(TestClasses.Annotations.nested(), new File(mTestPackageDir, "Nested.class"));
+ Files.write(TestClasses.Annotations.myEnum(), new File(mTestPackageDir, "MyEnum.class"));
+ Files.write(TestClasses.emptyClass("SomeClass"), new File(mTestPackageDir, "SomeClass.class"));
+ Files.write(TestClasses.emptyClass("SomeOtherClass"), new File(mTestPackageDir, "SomeOtherClass.class"));
+
+ fullRun("Main", "main:()V");
+
+ Files.write(Annotations.main_noAnnotations(), new File(mTestPackageDir, "Main.class"));
+
+ mException.expect(IncrementalRunImpossibleException.class);
+ mException.expectMessage("Annotation test/MyAnnotation on test/Main.main removed");
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ }
+
+ @Test
+ public void cycle() throws Exception {
+ // Given:
+ Files.write(Cycle.main1(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Cycle.cycleOne(), new File(mTestPackageDir, "CycleOne.class"));
+ Files.write(Cycle.cycleTwo(), new File(mTestPackageDir, "CycleTwo.class"));
+ Files.write(TestClasses.emptyClass("NotUsed"), new File(mTestPackageDir, "NotUsed.class"));
+
+ fullRun("Main", "main:()V");
+
+ assertTrue(new File(mIncrementalDir, "shrinker.bin").exists());
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("CycleOne", "<init>:()V");
+ assertMembersLeft("CycleTwo", "<init>:()V");
+ assertClassSkipped("NotUsed");
+
+ byte[] mainBytes = Files.toByteArray(getOutputClassFile("Main"));
+
+ Files.write(Cycle.main2(), new File(mTestPackageDir, "Main.class"));
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+
+ // Then:
+ assertMembersLeft("Main", "main:()V");
+ assertClassSkipped("CycleOne");
+ assertClassSkipped("CycleTwo");
+ assertClassSkipped("NotUsed");
+
+ assertNotEquals(mainBytes, Files.toByteArray(getOutputClassFile("Main")));
+
+ Files.write(Cycle.main1(), new File(mTestPackageDir, "Main.class"));
+ incrementalRun(ImmutableMap.of("Main", Status.CHANGED));
+ assertMembersLeft("Main", "main:()V");
+ assertMembersLeft("CycleOne", "<init>:()V");
+ assertMembersLeft("CycleTwo", "<init>:()V");
+ assertClassSkipped("NotUsed");
+ }
+
+ @Test
+ public void interfaces_implementationFromSuperclass() throws Exception {
+ // Given:
+ Files.write(Interfaces.main(), new File(mTestPackageDir, "Main.class"));
+ Files.write(Interfaces.myInterface(), new File(mTestPackageDir, "MyInterface.class"));
+ Files.write(Interfaces.myCharSequence(), new File(mTestPackageDir, "MyCharSequence.class"));
+ Files.write(Interfaces.myImpl(), new File(mTestPackageDir, "MyImpl.class"));
+ Files.write(Interfaces.namedRunnable(), new File(mTestPackageDir, "NamedRunnable.class"));
+ Files.write(Interfaces.namedRunnableImpl(), new File(mTestPackageDir, "NamedRunnableImpl.class"));
+ Files.write(Interfaces.doesSomething(), new File(mTestPackageDir, "DoesSomething.class"));
+ Files.write(
+ Interfaces.implementationFromSuperclass(),
+ new File(mTestPackageDir, "ImplementationFromSuperclass.class"));
+
+ // When:
+ fullRun(
+ "Main",
+ "useImplementationFromSuperclass:(Ltest/ImplementationFromSuperclass;)V",
+ "useMyInterface:(Ltest/MyInterface;)V");
+
+ // Then:
+ assertMembersLeft(
+ "Main",
+ "useImplementationFromSuperclass:(Ltest/ImplementationFromSuperclass;)V",
+ "useMyInterface:(Ltest/MyInterface;)V");
+ assertMembersLeft("ImplementationFromSuperclass");
+ assertMembersLeft(
+ "MyInterface",
+ "doSomething:(Ljava/lang/Object;)V");
+ assertClassSkipped("MyImpl");
+ assertClassSkipped("MyCharSequence");
+
+ // This is the tricky part: this method should be kept, because a subclass is using it to
+ // implement an interface.
+ assertMembersLeft(
+ "DoesSomething",
+ "doSomething:(Ljava/lang/Object;)V");
+
+ assertImplements("ImplementationFromSuperclass", "test/MyInterface");
+
+ incrementalRun(ImmutableMap.of("ImplementationFromSuperclass", Status.CHANGED));
+ }
+
+ private void fullRun(String className, String... methods) throws IOException {
+ mFullRunShrinker.run(
+ mInputs,
+ Collections.<TransformInput>emptyList(),
+ mOutput,
+ ImmutableMap.<CounterSet, KeepRules>of(
+ CounterSet.SHRINK, new TestKeepRules(className, methods)),
+ true);
+ }
+
+ private void incrementalRun(Map<String, Status> changes) throws Exception {
+ IncrementalShrinker<String> incrementalShrinker = new IncrementalShrinker<String>(
+ new WaitableExecutor<Void>(),
+ JavaSerializationShrinkerGraph.readFromDir(
+ mIncrementalDir,
+ this.getClass().getClassLoader()),
+ mShrinkerLogger);
+
+ Map<File, Status> files = Maps.newHashMap();
+ for (Map.Entry<String, Status> entry : changes.entrySet()) {
+ files.put(
+ new File(mTestPackageDir, entry.getKey() + ".class"),
+ entry.getValue());
+ }
+
+ when(mDirectoryInput.getChangedFiles()).thenReturn(files);
+
+ incrementalShrinker.incrementalRun(mInputs, mOutput);
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestClasses.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestClasses.java
new file mode 100644
index 0000000..abee22c
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestClasses.java
@@ -0,0 +1,3487 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * Code for generating test classes for the {@link FullRunShrinker}. This were created using the ASM
+ * bytecode outliner plugin for IJ.
+ */
+ at SuppressWarnings("unused") // Outliner plugin generates some unused visitors.
+public class TestClasses implements Opcodes {
+
+ /**
+ * Simple scenario with two related classes and one unused class.
+ */
+ public static class SimpleScenario {
+ static byte[] aaa() {
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Aaa", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "aaa", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "bbb", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "bbb", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "ccc", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ static byte[] bbb() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Bbb", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "bbb", "(Ltest/Aaa;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "aaa", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ static byte[] ccc() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Ccc", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "ccc", "(Ltest/Aaa;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "aaa", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ /**
+ * Virtual calls.
+ */
+ public static class VirtualCalls {
+ public static byte[] abstractClass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER + ACC_ABSTRACT,
+ "test/AbstractClass", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "abstractMethod", "()V", null, null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] impl(int i) throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Impl" + i, null,
+ "test/AbstractClass", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/AbstractClass",
+ "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "abstractMethod", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_concreteType() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Impl2");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Impl2", "<init>", "()V",
+ false);
+ mv.visitInsn(POP);
+ mv.visitTypeInsn(NEW, "test/Impl1");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Impl1", "<init>", "()V",
+ false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Impl1",
+ "abstractMethod", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_abstractType() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Impl2");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Impl2", "<init>", "()V",
+ false);
+ mv.visitInsn(POP);
+ mv.visitTypeInsn(NEW, "test/Impl1");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Impl1", "<init>", "()V",
+ false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/AbstractClass",
+ "abstractMethod", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_parentChild() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ cw.visitSource("Main.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "main", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitTypeInsn(NEW, "test/Child");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Child", "<init>",
+ "()V", false);
+ mv.visitVarInsn(ASTORE, 1);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(25, l1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Child",
+ "onlyInParent", "()V", false);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLineNumber(26, l2);
+ mv.visitInsn(RETURN);
+ Label l3 = new Label();
+ mv.visitLabel(l3);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l3,
+ 0);
+ mv.visitLocalVariable("c", "Ltest/Child;", null, l1, l3,
+ 1);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] parent() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Parent", null,
+ "java/lang/Object", null);
+
+ cw.visitSource("Parent.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Parent;", null, l0,
+ l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "onlyInParent", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(25, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Parent;", null, l0,
+ l1, 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] child() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Child", null,
+ "test/Parent", null);
+
+ cw.visitSource("Child.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Parent", "<init>",
+ "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Child;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ /**
+ * SDK types.
+ */
+ public static class SdkTypes {
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/MyException");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyException", "<init>", "()V", false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ /**
+ * It inherits from an SDK class, overrides one method from Exception, one from Object (we
+ * may have special handling code for Object, so check both) and adds one unused method.
+ */
+ public static byte[] myException() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/MyException",
+ null, "java/lang/Exception", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Exception", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "getMessage", "()Ljava/lang/String;", null, null);
+ mv.visitCode();
+ mv.visitLdcInsn("custom message");
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
+ mv.visitCode();
+ mv.visitIntInsn(BIPUSH, 42);
+ mv.visitInsn(IRETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ /**
+ * Interfaces.
+ */
+ public static class Interfaces {
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "buildMyCharSequence",
+ "()Ltest/MyCharSequence;", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/MyCharSequence");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyCharSequence",
+ "<init>", "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "buildMyImpl",
+ "()Ltest/MyImpl;", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/MyImpl");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyImpl", "<init>",
+ "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "buildNamedRunnableImpl",
+ "()Ltest/NamedRunnableImpl;", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/NamedRunnableImpl");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/NamedRunnableImpl", "<init>",
+ "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "callCharSequence",
+ "(Ljava/lang/CharSequence;)V",
+ null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/CharSequence", "length",
+ "()I", true);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "callRunnable",
+ "(Ljava/lang/Runnable;)V",
+ null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/Runnable", "run",
+ "()V", true);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "callMyCharSequence",
+ "(Ltest/MyCharSequence;)V", null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/MyCharSequence",
+ "length", "()I", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "useMyInterface",
+ "(Ltest/MyInterface;)V",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitMethodInsn(INVOKEINTERFACE, "test/MyInterface",
+ "doSomething", "(Ljava/lang/Object;)V", true);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "useImplementationFromSuperclass",
+ "(Ltest/ImplementationFromSuperclass;)V",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "useMyImpl_interfaceMethod",
+ "(Ltest/MyImpl;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitLdcInsn("foo");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/MyImpl",
+ "doSomething", "(Ljava/lang/Object;)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "useMyImpl_otherMethod",
+ "(Ltest/MyImpl;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/MyImpl",
+ "someOtherMethod", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] myCharSequence() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/MyCharSequence",
+ null, "java/lang/Object", new String[]{"java/lang/CharSequence"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "length", "()I", null, null);
+ mv.visitCode();
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(IRETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "charAt", "(I)C", null, null);
+ mv.visitCode();
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(IRETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "subSequence", "(II)Ljava/lang/CharSequence;", null,
+ null);
+ mv.visitCode();
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 3);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ /**
+ * Program interface that extends an SDK interface.
+ */
+ public static byte[] namedRunnable() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/NamedRunnable", null, "java/lang/Object",
+ new String[]{"java/lang/Runnable"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getName", "()Ljava/lang/String;", null,
+ null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] namedRunnableImpl() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/NamedRunnableImpl",
+ null, "java/lang/Object",
+ new String[]{"test/NamedRunnable"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
+ mv.visitCode();
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] myInterface() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/MyInterface", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "doSomething", "(Ljava/lang/Object;)V",
+ "(TT;)V", null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+
+ public static byte[] mySubInterface() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/MySubInterface", null,
+ "java/lang/Object",
+ new String[]{"test/MyInterface"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "anotherMethod", "()V", null, null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] myImpl() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/MyImpl",
+ null, "java/lang/Object", new String[]{"test/MyInterface"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "doSomething", "(Ljava/lang/Object;)V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "someOtherMethod", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ /**
+ * This class happens to have a method matching the interface signature. It does not
+ * implement the interface.
+ */
+ public static byte[] doesSomething() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/DoesSomething",
+ null, "java/lang/Object", null);
+
+ cw.visitSource("DoesSomething.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/DoesSomething;", null,
+ l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "doSomething", "(Ljava/lang/Object;)V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(25, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/DoesSomething;", null,
+ l0, l1, 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ /**
+ * This class extends DoesSomething and implements MyInterface. Extending DoesSomething
+ * is enough to implement the interface.
+ */
+ public static byte[] implementationFromSuperclass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER,
+ "test/ImplementationFromSuperclass", null,
+ "test/DoesSomething",
+ new String[]{"test/MyInterface"});
+
+ cw.visitSource("ImplementationFromSuperclass.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/DoesSomething",
+ "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this",
+ "Ltest/ImplementationFromSuperclass;", null, l0,
+ l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ /**
+ * This class extends DoesSomething and implements MyInterface. Extending DoesSomething
+ * is enough to implement the interface.
+ */
+ public static byte[] implementationFromSuperclass_subInterface() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER,
+ "test/ImplementationFromSuperclass", null,
+ "test/DoesSomething",
+ new String[]{"test/MySubInterface"});
+
+ cw.visitSource("ImplementationFromSuperclass.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/DoesSomething",
+ "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this",
+ "Ltest/ImplementationFromSuperclass;", null, l0,
+ l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "anotherMethod", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(25, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/ImplementationFromSuperclass;", null,
+ l0, l1, 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ /**
+ * Fields.
+ */
+ public static class Fields {
+
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "()I", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/MyFields");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyFields",
+ "<init>", "()V", false);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitFieldInsn(GETSTATIC, "test/MyFields", "sString",
+ "Ljava/lang/String;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I", false);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, "test/MyFields", "f1", "I");
+ mv.visitInsn(IADD);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/MyFields",
+ "readField", "()I", false);
+ mv.visitInsn(IADD);
+ mv.visitInsn(IRETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main_subclass", "()I", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/MyFieldsSubclass");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyFieldsSubclass",
+ "<init>", "()V", false);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitFieldInsn(GETSTATIC, "test/MyFieldsSubclass", "sString",
+ "Ljava/lang/String;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I", false);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, "test/MyFieldsSubclass", "f1", "I");
+ mv.visitInsn(IADD);
+ mv.visitInsn(IRETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] myFields() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/MyFields", null,
+ "java/lang/Object", null);
+
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "sString", "Ljava/lang/String;", null,
+ null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PUBLIC, "f1", "I", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE, "f2", "I", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE, "f3", "I", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE, "f4", "Ltest/MyFieldType;",
+ null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitIntInsn(BIPUSH, 17);
+ mv.visitFieldInsn(PUTFIELD, "test/MyFields", "f1", "I");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitIntInsn(BIPUSH, 42);
+ mv.visitFieldInsn(PUTFIELD, "test/MyFields", "f2", "I");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "readField", "()I", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, "test/MyFields", "f4",
+ "Ltest/MyFieldType;");
+ Label l0 = new Label();
+ mv.visitJumpInsn(IFNULL, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, "test/MyFields", "f2", "I");
+ mv.visitInsn(IRETURN);
+ mv.visitLabel(l0);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(IRETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ mv.visitCode();
+ mv.visitLdcInsn("foo");
+ mv.visitFieldInsn(PUTSTATIC, "test/MyFields", "sString",
+ "Ljava/lang/String;");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] myFieldsSubclass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/MyFieldsSubclass", null,
+ "test/MyFields", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyFields", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ /**
+ * Method overrides multiples interfaces.
+ */
+ public static class MultipleOverriddenMethods {
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "useInterfaceOne",
+ "(Ltest/InterfaceOne;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEINTERFACE, "test/InterfaceOne",
+ "m", "()V", true);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "useInterfaceTwo",
+ "(Ltest/InterfaceTwo;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEINTERFACE, "test/InterfaceTwo",
+ "m", "()V", true);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "useImplementation",
+ "(Ltest/Implementation;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Implementation",
+ "m", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "buildImplementation", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Implementation");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Implementation",
+ "<init>", "()V", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] interfaceOne() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/InterfaceOne", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "m", "()V", null, null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] interfaceTwo() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/InterfaceTwo", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "m", "()V", null, null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] implementation() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Implementation",
+ null, "java/lang/Object",
+ new String[]{"test/InterfaceOne",
+ "test/InterfaceTwo"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ /**
+ * Annotations.
+ */
+ public static class Annotations {
+ public static byte[] myAnnotation() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ANNOTATION + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/MyAnnotation", null, "java/lang/Object",
+ new String[]{"java/lang/annotation/Annotation"});
+
+ {
+ av0 = cw.visitAnnotation("Ljava/lang/annotation/Target;", true);
+ {
+ AnnotationVisitor av1 = av0.visitArray("value");
+ av1.visitEnum(null, "Ljava/lang/annotation/ElementType;", "TYPE");
+ av1.visitEnum(null, "Ljava/lang/annotation/ElementType;", "METHOD");
+ av1.visitEnd();
+ }
+ av0.visitEnd();
+ }
+ {
+ av0 = cw.visitAnnotation("Ljava/lang/annotation/Retention;", true);
+ av0.visitEnum("value", "Ljava/lang/annotation/RetentionPolicy;", "RUNTIME");
+ av0.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "klass", "()Ljava/lang/Class;",
+ "()Ljava/lang/Class<*>;", null);
+ {
+ av0 = mv.visitAnnotationDefault();
+ av0.visit(null, Type.getType("Ltest/SomeClass;"));
+ av0.visitEnd();
+ }
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "myEnum",
+ "()Ltest/MyEnum;", null, null);
+ {
+ av0 = mv.visitAnnotationDefault();
+ av0.visitEnum(null, "Ltest/MyEnum;", "ONE");
+ av0.visitEnd();
+ }
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "f", "()I", null, null);
+ {
+ av0 = mv.visitAnnotationDefault();
+ av0.visit(null, 14);
+ av0.visitEnd();
+ }
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "nested",
+ "()[Ltest/Nested;", null, null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] myEnum() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER + ACC_ENUM,
+ "test/MyEnum",
+ "Ljava/lang/Enum<Ltest/MyEnum;>;", "java/lang/Enum",
+ null);
+
+ cw.visitSource("MyEnum.java", null);
+
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC + ACC_ENUM, "ONE",
+ "Ltest/MyEnum;", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC + ACC_ENUM, "TWO",
+ "Ltest/MyEnum;", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC + ACC_SYNTHETIC, "$VALUES",
+ "[Ltest/MyEnum;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "values",
+ "()[Ltest/MyEnum;", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitFieldInsn(GETSTATIC, "test/MyEnum", "$VALUES",
+ "[Ltest/MyEnum;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "[Ltest/MyEnum;",
+ "clone", "()Ljava/lang/Object;", false);
+ mv.visitTypeInsn(CHECKCAST, "[Ltest/MyEnum;");
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "valueOf",
+ "(Ljava/lang/String;)Ltest/MyEnum;", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitLdcInsn(Type.getType("Ltest/MyEnum;"));
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESTATIC, "java/lang/Enum", "valueOf",
+ "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", false);
+ mv.visitTypeInsn(CHECKCAST, "test/MyEnum");
+ mv.visitInsn(ARETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l1, 0);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", "()V", null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ILOAD, 2);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Enum", "<init>", "(Ljava/lang/String;I)V",
+ false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/MyEnum;", null, l0,
+ l1, 0);
+ mv.visitMaxs(3, 3);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(23, l0);
+ mv.visitTypeInsn(NEW, "test/MyEnum");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("ONE");
+ mv.visitInsn(ICONST_0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyEnum", "<init>",
+ "(Ljava/lang/String;I)V", false);
+ mv.visitFieldInsn(PUTSTATIC, "test/MyEnum", "ONE",
+ "Ltest/MyEnum;");
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(24, l1);
+ mv.visitTypeInsn(NEW, "test/MyEnum");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("TWO");
+ mv.visitInsn(ICONST_1);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/MyEnum", "<init>",
+ "(Ljava/lang/String;I)V", false);
+ mv.visitFieldInsn(PUTSTATIC, "test/MyEnum", "TWO",
+ "Ltest/MyEnum;");
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLineNumber(22, l2);
+ mv.visitInsn(ICONST_2);
+ mv.visitTypeInsn(ANEWARRAY, "test/MyEnum");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitFieldInsn(GETSTATIC, "test/MyEnum", "ONE",
+ "Ltest/MyEnum;");
+ mv.visitInsn(AASTORE);
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_1);
+ mv.visitFieldInsn(GETSTATIC, "test/MyEnum", "TWO",
+ "Ltest/MyEnum;");
+ mv.visitInsn(AASTORE);
+ mv.visitFieldInsn(PUTSTATIC, "test/MyEnum", "$VALUES",
+ "[Ltest/MyEnum;");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(4, 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_noAnnotations() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "notAnnotated", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_annotatedClass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ av0 = cw.visitAnnotation("Ltest/MyAnnotation;", true);
+ av0.visit("klass", Type.getType("Ltest/SomeOtherClass;"));
+ av0.visitEnum("myEnum", "Ltest/MyEnum;", "TWO");
+ {
+ AnnotationVisitor av1 = av0.visitArray("nested");
+ {
+ AnnotationVisitor av2 = av1
+ .visitAnnotation(null, "Ltest/Nested;");
+ av2.visit("name", "foo");
+ av2.visitEnd();
+ }
+ av1.visitEnd();
+ }
+ av0.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "notAnnotated", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_annotatedMethod() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "()V", null, null);
+ {
+ av0 = mv.visitAnnotation("Ltest/MyAnnotation;", true);
+ av0.visit("klass",
+ Type.getType("Ltest/SomeOtherClass;"));
+ av0.visitEnum("myEnum", "Ltest/MyEnum;", "TWO");
+ {
+ AnnotationVisitor av1 = av0.visitArray("nested");
+ {
+ AnnotationVisitor av2 = av1.visitAnnotation(null,
+ "Ltest/Nested;");
+ av2.visit("name", "foo");
+ av2.visitEnd();
+ }
+ av1.visitEnd();
+ }
+ av0.visitEnd();
+ }
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "notAnnotated", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] nested() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ANNOTATION + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/Nested", null, "java/lang/Object",
+ new String[]{"java/lang/annotation/Annotation"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "name", "()Ljava/lang/String;", null,
+ null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ /**
+ * Generic signatures.
+ */
+ public static class Signatures {
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",
+ "(Ltest/NamedMap;)V",
+ "(Ltest/NamedMap<*>;)V", null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "callMethod",
+ "(Ltest/NamedMap;)V",
+ "(Ltest/NamedMap<*>;)V", null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/NamedMap",
+ "method", "(Ljava/util/Collection;)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] namedMap() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/NamedMap",
+ "<T::Ljava/io/Serializable;:Ltest/Named;>Ljava/lang/Object;",
+ "java/lang/Object", null);
+
+ {
+ fv = cw.visitField(0, "instance", "Ljava/io/Serializable;", "TT;", null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "method", "(Ljava/util/Collection;)V",
+ "<I::Ltest/HasAge;>(Ljava/util/Collection<TI;>;)V",
+ null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] named() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/Named", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getName", "()Ljava/lang/String;", null,
+ null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion") // Mirrors interface name.
+ public static byte[] hasAge() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/HasAge", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getAge", "()I", null, null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+
+ /**
+ * invokespecial when making "normal" super calls.
+ */
+ public static class SuperCalls {
+ public static byte[] aaa() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Aaa", null,
+ "java/lang/Object", null);
+
+ cw.visitSource("Aaa.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Aaa;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "onlyInAaa", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(23, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Aaa;", null, l0, l1,
+ 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "overridden", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Aaa;", null, l0, l1,
+ 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] bbb() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Bbb", null,
+ "test/Aaa", null);
+
+ cw.visitSource("Bbb.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Aaa", "<init>",
+ "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Bbb;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "overridden", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(25, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Bbb;", null, l0, l1,
+ 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "onlyInBbb", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(27, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Bbb;", null, l0, l1,
+ 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] ccc() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Ccc", null,
+ "test/Bbb", null);
+
+ cw.visitSource("Ccc.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "<init>",
+ "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Ccc;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "callAaaMethod", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "onlyInAaa",
+ "()V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(25, l1);
+ mv.visitInsn(RETURN);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "Ltest/Ccc;", null, l0, l2,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "callBbbMethod", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(28, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "onlyInBbb",
+ "()V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(29, l1);
+ mv.visitInsn(RETURN);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "Ltest/Ccc;", null, l0, l2,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "callOverriddenMethod", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(32, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "overridden",
+ "()V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(33, l1);
+ mv.visitInsn(RETURN);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "Ltest/Ccc;", null, l0, l2,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class InnerClasses {
+ public static byte[] main_useOuterClass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Outer");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Outer",
+ "<init>", "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Outer",
+ "outerMethod", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_useOuterClass_makeAnonymousClass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Outer");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Outer",
+ "<init>", "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Outer",
+ "makeRunnable", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_useStaticInnerClass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ cw.visitInnerClass("test/Outer$StaticInner",
+ "test/Outer", "StaticInner",
+ ACC_PUBLIC + ACC_STATIC);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Outer$StaticInner");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL,
+ "test/Outer$StaticInner", "<init>", "()V",
+ false);
+ mv.visitMethodInsn(INVOKEVIRTUAL,
+ "test/Outer$StaticInner", "staticInnerMethod",
+ "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_useInnerClass() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ cw.visitInnerClass("test/Outer$Inner",
+ "test/Outer", "Inner", ACC_PUBLIC);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Outer$Inner");
+ mv.visitInsn(DUP);
+ mv.visitTypeInsn(NEW, "test/Outer");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Outer",
+ "<init>", "()V", false);
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;",
+ false);
+ mv.visitInsn(POP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Outer$Inner",
+ "<init>", "(Ltest/Outer;)V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Outer$Inner",
+ "innerMethod", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(4, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_empty() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] outer() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Outer", null,
+ "java/lang/Object", null);
+
+ cw.visitInnerClass("test/Outer$StaticInner",
+ "test/Outer", "StaticInner",
+ ACC_PUBLIC + ACC_STATIC);
+
+ cw.visitInnerClass("test/Outer$Inner",
+ "test/Outer", "Inner", ACC_PUBLIC);
+
+ cw.visitInnerClass("test/Outer$1", null, null, 0);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "makeRunnable", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Outer$1");
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Outer$1", "<init>", "(Ltest/Outer;)V", false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "outerMethod", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] inner() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Outer$Inner",
+ null, "java/lang/Object", null);
+
+ cw.visitInnerClass("test/Outer$Inner",
+ "test/Outer", "Inner", ACC_PUBLIC);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0",
+ "Ltest/Outer;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>",
+ "(Ltest/Outer;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "test/Outer$Inner",
+ "this$0", "Ltest/Outer;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "innerMethod", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] staticInner() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER,
+ "test/Outer$StaticInner", null,
+ "java/lang/Object", null);
+
+ cw.visitInnerClass("test/Outer$StaticInner",
+ "test/Outer", "StaticInner",
+ ACC_PUBLIC + ACC_STATIC);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "staticInnerMethod", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] anonymous() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_SUPER, "test/Outer$1", null,
+ "java/lang/Object", new String[] { "java/lang/Runnable" });
+
+ cw.visitOuterClass("test/Outer", "makeRunnable", "()V");
+
+ cw.visitInnerClass("test/Outer$1", null, null, 0);
+
+ {
+ fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "Ltest/Outer;", null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "<init>", "(Ltest/Outer;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, "test/Outer$1", "this$0", "Ltest/Outer;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("hello");
+ mv.visitMethodInsn(INVOKEVIRTUAL,
+ "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class StaticMembers {
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main",
+ null, "java/lang/Object", null);
+
+ cw.visitSource("Main.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null,
+ l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "callStaticMethod", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitMethodInsn(INVOKESTATIC, "test/Utils",
+ "staticMethod", "()Ljava/lang/Object;", false);
+ mv.visitInsn(ARETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null,
+ l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "getStaticField", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(28, l0);
+ mv.visitFieldInsn(GETSTATIC, "test/Utils",
+ "staticField", "Ljava/lang/Object;");
+ mv.visitInsn(ARETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null,
+ l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] utils() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Utils",
+ null, "java/lang/Object", null);
+
+ cw.visitSource("Utils.java", null);
+
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "staticField", "Ljava/lang/Object;", null,
+ null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Utils;", null,
+ l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "staticMethod", "()Ljava/lang/Object;",
+ null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitTypeInsn(NEW, "java/lang/Object");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class Reflection {
+
+ public static byte[] main_instanceOf() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ cw.visitSource("Main.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "(Ljava/lang/Object;)Z", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitTypeInsn(INSTANCEOF, "test/Foo");
+ mv.visitInsn(IRETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitLocalVariable("o", "Ljava/lang/Object;", null, l0, l1, 1);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_classLiteral() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ cw.visitSource("Main.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "main", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLdcInsn(Type.getType("Ltest/Foo;"));
+ mv.visitInsn(ARETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_classForName() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitLdcInsn("test.ClassWithFields");
+ mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_classForName_dynamic() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitLdcInsn("test.");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
+ mv.visitLdcInsn("ClassWithFields");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
+ "()Ljava/lang/String;", false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_atomicIntegerFieldUpdater() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitLdcInsn(
+ Type.getType("Ltest/ClassWithFields;"));
+ mv.visitLdcInsn("intField");
+ mv.visitMethodInsn(INVOKESTATIC,
+ "java/util/concurrent/atomic/AtomicIntegerFieldUpdater", "newUpdater",
+ "(Ljava/lang/Class;Ljava/lang/String;)"
+ + "Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;",
+ false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_atomicLongFieldUpdater() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitLdcInsn(
+ Type.getType("Ltest/ClassWithFields;"));
+ mv.visitLdcInsn("longField");
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/concurrent/atomic/AtomicLongFieldUpdater",
+ "newUpdater",
+ "(Ljava/lang/Class;Ljava/lang/String;)"
+ + "Ljava/util/concurrent/atomic/AtomicLongFieldUpdater;",
+ false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_atomicReferenceFieldUpdater() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitLdcInsn(
+ Type.getType("Ltest/ClassWithFields;"));
+ mv.visitLdcInsn(Type.getType("Ljava/lang/String;"));
+ mv.visitLdcInsn("stringField");
+ mv.visitMethodInsn(INVOKESTATIC,
+ "java/util/concurrent/atomic/AtomicReferenceFieldUpdater", "newUpdater",
+ "(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;",
+ false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_getField() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null,
+ new String[]{"java/lang/Exception"});
+ mv.visitCode();
+ mv.visitLdcInsn(
+ Type.getType("Ltest/ClassWithFields;"));
+ mv.visitLdcInsn("intField");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getField",
+ "(Ljava/lang/String;)Ljava/lang/reflect/Field;", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] classWithFields() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER,
+ "test/ClassWithFields", null, "java/lang/Object",
+ null);
+
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_VOLATILE, "intField", "I", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_VOLATILE, "longField", "J", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_VOLATILE, "stringField", "Ljava/lang/String;",
+ null, null);
+ fv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class TryCatch {
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main",
+ null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "main", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ Label l2 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l2,
+ "test/CustomException");
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Main",
+ "helper", "()V", false);
+ mv.visitLabel(l1);
+ Label l3 = new Label();
+ mv.visitJumpInsn(GOTO, l3);
+ mv.visitLabel(l2);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitInsn(RETURN);
+ mv.visitLabel(l3);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "helper", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] customException() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_SUPER, "test/CustomException", null,
+ "java/lang/RuntimeException", null);
+
+ {
+ mv = cw.visitMethod(0, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_tryFinally() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main",
+ null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "main", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ Label l2 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l2, null);
+ Label l3 = new Label();
+ mv.visitTryCatchBlock(l2, l3, l2, null);
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Main", "helper", "()V", false);
+ mv.visitLabel(l1);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Main", "helper", "()V", false);
+ Label l4 = new Label();
+ mv.visitJumpInsn(GOTO, l4);
+ mv.visitLabel(l2);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(l3);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Main", "helper", "()V", false);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(l4);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "helper", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class AbstractClasses {
+ public static byte[] myInterface() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
+ "test/MyInterface", null, "java/lang/Object", null);
+
+ cw.visitSource("MyInterface.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "m", "()V", null, null);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] abstractImpl() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER + ACC_ABSTRACT,
+ "test/AbstractImpl", null, "java/lang/Object",
+ new String[]{"test/MyInterface"});
+
+ cw.visitSource("AbstractImpl.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/AbstractImpl;", null,
+ l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "helper", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/AbstractImpl", "m",
+ "()V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(25, l1);
+ mv.visitInsn(RETURN);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "Ltest/AbstractImpl;", null,
+ l0, l2, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] realImpl() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/RealImpl", null,
+ "test/AbstractImpl", null);
+
+ cw.visitSource("RealImpl.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(19, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/AbstractImpl",
+ "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/RealImpl;", null, l0,
+ l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/RealImpl;", null, l0,
+ l1, 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(25, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/RealImpl",
+ "helper", "()V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(26, l1);
+ mv.visitInsn(RETURN);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "Ltest/RealImpl;", null, l0,
+ l2, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class Primitives {
+ public static byte[] main() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ cw.visitSource("Main.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "ldc", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitLdcInsn(Type.getType("[I"));
+ mv.visitInsn(ARETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "checkcast", "(Ljava/lang/Object;)[I", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(28, l0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitTypeInsn(CHECKCAST, "[I");
+ mv.visitTypeInsn(CHECKCAST, "[I");
+ mv.visitInsn(ARETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitLocalVariable("o", "Ljava/lang/Object;", null, l0, l1, 1);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class InvalidReferences {
+ public static byte[] main_sunMiscUnsafe() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ cw.visitSource("Main.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(24, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l1,
+ 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "main", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(26, l0);
+ mv.visitMethodInsn(INVOKESTATIC, "sun/misc/Unsafe", "getUnsafe", "()Lsun/misc/Unsafe;",
+ false);
+ mv.visitVarInsn(ASTORE, 1);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(27, l1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "sun/misc/Unsafe", "addressSize", "()I", false);
+ mv.visitInsn(POP);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLineNumber(28, l2);
+ mv.visitInsn(RETURN);
+ Label l3 = new Label();
+ mv.visitLabel(l3);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0, l3,
+ 0);
+ mv.visitLocalVariable("unsafe", "Lsun/misc/Unsafe;", null, l1, l3, 1);
+ mv.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_javaInstrumentation() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", new String[]{"java/lang/instrument/ClassFileTransformer"});
+
+ cw.visitSource("Main.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(26, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0,
+ l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "main", "()V", null,
+ new String[]{"java/lang/instrument/IllegalClassFormatException"});
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(29, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Main",
+ "transform",
+ "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[B)[B",
+ false);
+ mv.visitInsn(POP);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(30, l1);
+ mv.visitInsn(RETURN);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0,
+ l2, 0);
+ mv.visitMaxs(6, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "transform",
+ "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[B)[B",
+ "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class<*>;Ljava/security/ProtectionDomain;[B)[B",
+ new String[]{"java/lang/instrument/IllegalClassFormatException"});
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(36, l0);
+ mv.visitInsn(ICONST_0);
+ mv.visitIntInsn(NEWARRAY, T_BYTE);
+ mv.visitInsn(ARETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Ltest/Main;", null, l0,
+ l1, 0);
+ mv.visitLocalVariable("loader", "Ljava/lang/ClassLoader;", null, l0, l1, 1);
+ mv.visitLocalVariable("className", "Ljava/lang/String;", null, l0, l1, 2);
+ mv.visitLocalVariable("classBeingRedefined", "Ljava/lang/Class;",
+ "Ljava/lang/Class<*>;", l0, l1, 3);
+ mv.visitLocalVariable("protectionDomain", "Ljava/security/ProtectionDomain;", null, l0,
+ l1, 4);
+ mv.visitLocalVariable("classfileBuffer", "[B", null, l0, l1, 5);
+ mv.visitMaxs(1, 6);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static byte[] emptyClass(String name) throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/" + name, null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestClassesForIncremental.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestClassesForIncremental.java
new file mode 100644
index 0000000..135cabf
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestClassesForIncremental.java
@@ -0,0 +1,552 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * TODO: Document.
+ */
+public class TestClassesForIncremental implements Opcodes {
+
+ public static class Simple {
+ public static byte[] main1() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Bbb");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "<init>",
+ "()V", false);
+ mv.visitInsn(POP);
+ mv.visitTypeInsn(NEW, "test/Aaa");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Aaa", "<init>",
+ "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "m1", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main2() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Bbb");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "<init>",
+ "()V", false);
+ mv.visitInsn(POP);
+ mv.visitTypeInsn(NEW, "test/Aaa");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Aaa", "<init>",
+ "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "m2", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_extraMethod() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Bbb");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "<init>",
+ "()V", false);
+ mv.visitInsn(POP);
+ mv.visitTypeInsn(NEW, "test/Aaa");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Aaa", "<init>",
+ "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "m1", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "extraMain", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Aaa");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Aaa", "<init>",
+ "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "m1", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_extraField() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Bbb");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "<init>",
+ "()V", false);
+ mv.visitInsn(POP);
+ mv.visitTypeInsn(NEW, "test/Aaa");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Aaa", "<init>",
+ "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "m1", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "sString", "Ljava/lang/String;", null,
+ null);
+ fv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main_extraField_private() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/Bbb");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Bbb", "<init>",
+ "()V", false);
+ mv.visitInsn(POP);
+ mv.visitTypeInsn(NEW, "test/Aaa");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/Aaa", "<init>",
+ "()V", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "test/Aaa", "m1", "()V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, "sString", "Ljava/lang/String;", null,
+ null);
+ fv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] aaa() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Aaa", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "m1", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(0, "m2", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] bbb() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Bbb", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] bbb_packagePrivateConstructor() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Bbb", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(0, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] bbb_packagePrivate() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_SUPER, "test/Bbb", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] bbb_serializable() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Bbb", null,
+ "java/lang/Object", new String[] {"java/io/Serializable"});
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] bbb_extendsAaa() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Bbb", null,
+ "test/Aaa", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ public static class Cycle {
+ public static byte[] main1() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "test/CycleOne");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/CycleOne",
+ "<init>", "()V", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] main2() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Main", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "main", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] cycleOne() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/CycleOne", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitTypeInsn(NEW, "test/CycleTwo");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/CycleTwo",
+ "<init>", "()V", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ public static byte[] cycleTwo() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/CycleTwo", null,
+ "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitTypeInsn(NEW, "test/CycleOne");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "test/CycleOne",
+ "<init>", "()V", false);
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestKeepRules.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestKeepRules.java
new file mode 100644
index 0000000..47a03e1
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/shrinker/TestKeepRules.java
@@ -0,0 +1,55 @@
+/*
+ * 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.build.gradle.shrinker;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Simple {@link KeepRules} implementation for testing.
+ */
+class TestKeepRules implements KeepRules {
+ private final String mClassName;
+ private final Set<String> mMethodNames;
+
+ TestKeepRules(String className, String... methodNames) {
+ mClassName = className;
+ mMethodNames = ImmutableSet.copyOf(methodNames);
+ }
+
+ @Override
+ public <T> Map<T, DependencyType> getSymbolsToKeep(T klass, ShrinkerGraph<T> graph) {
+ Map<T, DependencyType> symbols = Maps.newHashMap();
+
+ if (graph.getClassName(klass).endsWith(mClassName)) {
+ for (T method : graph.getMethods(klass)) {
+ String name = graph.getMethodNameAndDesc(method);
+ for (String methodName : mMethodNames) {
+ if (name.equals(methodName)) {
+ symbols.put(method, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ symbols.put(klass, DependencyType.REQUIRED_CLASS_STRUCTURE);
+ }
+ }
+ }
+ }
+
+ return symbols;
+ }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/ResourceUsageAnalyzerTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/ResourceUsageAnalyzerTest.java
new file mode 100644
index 0000000..fe919a8
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/ResourceUsageAnalyzerTest.java
@@ -0,0 +1,1108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import static com.android.build.gradle.tasks.ResourceUsageAnalyzer.NO_MATCH;
+import static com.android.build.gradle.tasks.ResourceUsageAnalyzer.REPLACE_DELETED_WITH_EMPTY;
+import static com.android.build.gradle.tasks.ResourceUsageAnalyzer.convertFormatStringToRegexp;
+import static java.io.File.separatorChar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.checks.ResourceUsageModel.Resource;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/** TODO: Test Resources#getIdentifier() handling */
+ at SuppressWarnings("SpellCheckingInspection")
+public class ResourceUsageAnalyzerTest extends TestCase {
+
+ public void testObfuscatedInPlace() throws Exception {
+ check(true, true);
+ }
+
+ public void testObfuscatedCopy() throws Exception {
+ check(true, false);
+ }
+
+ public void testNoProGuardInPlace() throws Exception {
+ check(false, true);
+ }
+
+ public void testNoProGuardCopy() throws Exception {
+ check(false, false);
+ }
+
+ private static void check(boolean useProguard, boolean inPlace) throws Exception {
+ File dir = Files.createTempDir();
+
+ File mapping;
+ File classes;
+ if (useProguard) {
+ classes = createProguardedClasses(dir);
+ mapping = createMappingFile(dir);
+ } else {
+ classes = createUnproguardedClasses(dir);
+ mapping = null;
+ }
+ File rDir = createResourceClassFolder(dir);
+ File mergedManifest = createMergedManifest(dir);
+ File resources = createResourceFolder(dir);
+
+ ResourceUsageAnalyzer analyzer = new ResourceUsageAnalyzer(rDir, classes,
+ mergedManifest, mapping, resources, null);
+ analyzer.analyze();
+ checkState(analyzer);
+ assertEquals(""
+ + "@attr/myAttr1 : reachable=false\n"
+ + "@attr/myAttr2 : reachable=false\n"
+ + "@dimen/activity_horizontal_margin : reachable=true\n"
+ + "@dimen/activity_vertical_margin : reachable=true\n"
+ + "@drawable/ic_launcher : reachable=true\n"
+ + "@drawable/unused : reachable=false\n"
+ + "@id/action_settings : reachable=true\n"
+ + "@id/action_settings2 : reachable=false\n"
+ + "@layout/activity_main : reachable=true\n"
+ + " @dimen/activity_vertical_margin\n"
+ + " @dimen/activity_horizontal_margin\n"
+ + " @string/hello_world\n"
+ + " @style/MyStyle_Child\n"
+ + "@menu/main : reachable=true\n"
+ + " @string/action_settings\n"
+ + "@menu/menu2 : reachable=false\n"
+ + " @string/action_settings2\n"
+ + "@raw/android_wear_micro_apk : reachable=true\n"
+ + "@raw/index1 : reachable=false\n"
+ + " @raw/my_used_raw_drawable\n"
+ + "@raw/my_js : reachable=false\n"
+ + "@raw/my_used_raw_drawable : reachable=false\n"
+ + "@raw/styles2 : reachable=false\n"
+ + "@string/action_settings : reachable=true\n"
+ + "@string/action_settings2 : reachable=false\n"
+ + "@string/alias : reachable=false\n"
+ + " @string/app_name\n"
+ + "@string/app_name : reachable=true\n"
+ + "@string/hello_world : reachable=true\n"
+ + "@style/AppTheme : reachable=true\n"
+ + "@style/MyStyle : reachable=true\n"
+ + " @style/MyStyle_Child\n"
+ + "@style/MyStyle_Child : reachable=true\n"
+ + " @style/MyStyle\n"
+ + "@xml/android_wear_micro_apk : reachable=true\n"
+ + " @raw/android_wear_micro_apk\n",
+ analyzer.getModel().dumpResourceModel());
+
+ File unusedBitmap = new File(resources, "drawable-xxhdpi" + separatorChar + "unused.png");
+ assertTrue(unusedBitmap.exists());
+
+ if (ResourceUsageAnalyzer.TWO_PASS_AAPT) {
+ File destination = inPlace ? null : Files.createTempDir();
+
+ analyzer.setDryRun(true);
+ analyzer.removeUnused(destination);
+ assertTrue(unusedBitmap.exists());
+
+ analyzer.setDryRun(false);
+ analyzer.removeUnused(destination);
+
+ if (inPlace) {
+ assertFalse(unusedBitmap.exists());
+ } else {
+ assertTrue(unusedBitmap.exists());
+ assertTrue(new File(destination, "values" + separatorChar + "values.xml").exists());
+ assertFalse(new File(destination, "drawable-xxhdpi" + separatorChar +
+ "unused.png").exists());
+ }
+ File values = new File(inPlace ? resources : destination,
+ "values" + separatorChar + "values.xml");
+ assertTrue(values.exists());
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + "\n"
+ + " <attr name=\"myAttr1\" format=\"integer\" />\n"
+ + " <attr name=\"myAttr2\" format=\"boolean\" />\n"
+ + "\n"
+ + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
+ + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n"
+ + "\n"
+ + " <string name=\"action_settings\">Settings</string>\n"
+ + " <string name=\"app_name\">ShrinkUnitTest</string>\n"
+ + " <string name=\"hello_world\">Hello world!</string>\n"
+ + "\n"
+ + "</resources>",
+
+ Files.toString(values, Charsets.UTF_8));
+ if (destination != null) {
+ deleteDir(destination);
+ }
+ } else {
+ List<File> files = Lists.newArrayList();
+ addFiles(resources, files);
+ Collections.sort(files, new Comparator<File>() {
+
+ @Override
+ public int compare(File file, File file2) {
+ return file.getPath().compareTo(file2.getPath());
+ }
+ });
+
+ // Generate a .zip file from a directory
+ File uncompressedFile = File.createTempFile("uncompressed", ".ap_");
+ String prefix = resources.getPath() + File.separatorChar;
+ FileOutputStream fos = new FileOutputStream(uncompressedFile);
+ ZipOutputStream zos = new ZipOutputStream(fos);
+ for (File file : files) {
+ if (file.equals(resources)) {
+ continue;
+ }
+ assertTrue(file.getPath().startsWith(prefix));
+ String relative = "res/" + file.getPath().substring(prefix.length())
+ .replace(File.separatorChar, '/');
+ boolean isValuesFile = relative.equals("res/values/values.xml");
+ if (isValuesFile) {
+ relative = "resources.arsc";
+ }
+ ZipEntry ze = new ZipEntry(relative);
+ zos.putNextEntry(ze);
+ if (!file.isDirectory() && !isValuesFile) {
+ byte[] bytes = Files.toByteArray(file);
+ zos.write(bytes);
+ }
+ zos.closeEntry();
+ }
+ zos.close();
+ fos.close();
+
+ assertEquals(""
+ + "res/drawable-hdpi\n"
+ + "res/drawable-hdpi/ic_launcher.png\n"
+ + "res/drawable-mdpi\n"
+ + "res/drawable-mdpi/ic_launcher.png\n"
+ + "res/drawable-xxhdpi\n"
+ + "res/drawable-xxhdpi/ic_launcher.png\n"
+ + "res/drawable-xxhdpi/unused.png\n"
+ + "res/layout\n"
+ + "res/layout/activity_main.xml\n"
+ + "res/menu\n"
+ + "res/menu/main.xml\n"
+ + "res/menu/menu2.xml\n"
+ + "res/raw\n"
+ + "res/raw/android_wear_micro_apk.apk\n"
+ + "res/raw/index1.html\n"
+ + "res/raw/my_js.js\n"
+ + "res/raw/styles2.css\n"
+ + "res/values\n"
+ + "resources.arsc\n"
+ + "res/xml\n"
+ + "res/xml/android_wear_micro_apk.xml\n",
+ dumpZipContents(uncompressedFile));
+
+ System.out.println(uncompressedFile);
+
+
+ File compressedFile = File.createTempFile("compressed", ".ap_");
+
+ analyzer.rewriteResourceZip(uncompressedFile, compressedFile);
+
+ // Check contents
+ assertEquals(""
+ + "res/drawable-hdpi\n"
+ + "res/drawable-hdpi/ic_launcher.png\n"
+ + "res/drawable-mdpi\n"
+ + "res/drawable-mdpi/ic_launcher.png\n"
+ + "res/drawable-xxhdpi\n"
+ + "res/drawable-xxhdpi/ic_launcher.png\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/drawable-xxhdpi/unused.png\n" : "")
+ + "res/layout\n"
+ + "res/layout/activity_main.xml\n"
+ + "res/menu\n"
+ + "res/menu/main.xml\n"
+ + "res/menu/menu2.xml\n"
+ + "res/raw\n"
+ + "res/raw/android_wear_micro_apk.apk\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/raw/index1.html\n" : "")
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/raw/my_js.js\n" : "")
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/raw/styles2.css\n" : "")
+ + "res/values\n"
+ + "resources.arsc\n"
+ + "res/xml\n"
+ + "res/xml/android_wear_micro_apk.xml\n",
+ dumpZipContents(compressedFile));
+
+ if (REPLACE_DELETED_WITH_EMPTY) {
+ assertTrue(Arrays.equals(ResourceUsageAnalyzer.TINY_PNG,
+ getZipContents(compressedFile, "res/drawable-xxhdpi/unused.png")));
+ }
+
+ analyzer.dispose();
+
+ uncompressedFile.delete();
+ compressedFile.delete();
+ }
+
+ deleteDir(dir);
+ }
+
+ private static String dumpZipContents(File zipFile) throws IOException {
+ StringBuilder sb = new StringBuilder();
+
+ FileInputStream fis = new FileInputStream(zipFile);
+ try {
+ ZipInputStream zis = new ZipInputStream(fis);
+ try {
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ sb.append(entry.getName());
+ sb.append('\n');
+ entry = zis.getNextEntry();
+ }
+ } finally {
+ zis.close();
+ }
+ } finally {
+ fis.close();
+ }
+
+ return sb.toString();
+ }
+
+ @Nullable
+ private static byte[] getZipContents(File zipFile, String name) throws IOException {
+ FileInputStream fis = new FileInputStream(zipFile);
+ try {
+ ZipInputStream zis = new ZipInputStream(fis);
+ try {
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ if (name.equals(entry.getName())) {
+ return ByteStreams.toByteArray(zis);
+ }
+ entry = zis.getNextEntry();
+ }
+ } finally {
+ zis.close();
+ }
+ } finally {
+ fis.close();
+ }
+
+ return null;
+ }
+
+ private static void addFiles(File file, List<File> files) {
+ files.add(file);
+ if (file.isDirectory()) {
+ File[] list = file.listFiles();
+ if (list != null) {
+ for (File f : list) {
+ addFiles(f, files);
+ }
+ }
+ }
+ }
+
+ private static File createResourceFolder(File dir) throws IOException {
+ File resources = new File(dir, "app/build/res/all/release".replace('/', separatorChar));
+ //noinspection ResultOfMethodCallIgnored
+ resources.mkdirs();
+
+ createFile(resources, "drawable-hdpi/ic_launcher.png", new byte[0]);
+ createFile(resources, "drawable-mdpi/ic_launcher.png", new byte[0]);
+ createFile(resources, "drawable-xxhdpi/ic_launcher.png", new byte[0]);
+ createFile(resources, "drawable-xxhdpi/unused.png", new byte[0]);
+
+ createFile(resources, "layout/activity_main.xml", ""
+ + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"\n"
+ + " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n"
+ + " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n"
+ + " android:paddingTop=\"@dimen/activity_vertical_margin\"\n"
+ + " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n"
+ + " tools:context=\".MainActivity\">\n"
+ + "\n"
+ + " <TextView\n"
+ + " style=\"@style/MyStyle.Child\"\n"
+ + " android:text=\"@string/hello_world\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\" />\n"
+ + "\n"
+ + "</RelativeLayout>");
+
+ createFile(resources, "menu/main.xml", ""
+ + "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " tools:context=\".MainActivity\" >\n"
+ + " <item android:id=\"@+id/action_settings\"\n"
+ + " android:title=\"@string/action_settings\"\n"
+ + " android:orderInCategory=\"100\"\n"
+ + " android:showAsAction=\"never\" />\n"
+ + "</menu>");
+
+ createFile(resources, "menu/menu2.xml", ""
+ + "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " tools:context=\".MainActivity\" >\n"
+ + " <item android:id=\"@+id/action_settings2\"\n"
+ + " android:title=\"@string/action_settings2\"\n"
+ + " android:orderInCategory=\"100\"\n"
+ + " android:showAsAction=\"never\" />\n"
+ + "</menu>");
+
+ createFile(resources, "values/values.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + "\n"
+ + " <attr name=\"myAttr1\" format=\"integer\" />\n"
+ + " <attr name=\"myAttr2\" format=\"boolean\" />\n"
+ + "\n"
+ + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
+ + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n"
+ + "\n"
+ + " <string name=\"action_settings\">Settings</string>\n"
+ + " <string name=\"action_settings2\">Settings2</string>\n"
+ + " <string name=\"alias\"> @string/app_name </string>\n"
+ + " <string name=\"app_name\">ShrinkUnitTest</string>\n"
+ + " <string name=\"hello_world\">Hello world!</string>\n"
+ + "\n"
+ + " <style name=\"AppTheme\" parent=\"android:Theme.Holo\"></style>\n"
+ + "\n"
+ + " <style name=\"MyStyle\">\n"
+ + " <item name=\"myAttr1\">50</item>\n"
+ + " </style>\n"
+ + "\n"
+ + " <style name=\"MyStyle.Child\">\n"
+ + " <item name=\"myAttr2\">true</item>\n"
+ + " </style>\n"
+ + "\n"
+ + "</resources>");
+
+ createFile(resources, "raw/android_wear_micro_apk.apk",
+ "<binary data>");
+
+ createFile(resources, "xml/android_wear_micro_apk.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<wearableApp package=\"com.example.shrinkunittest.app\">\n"
+ + " <versionCode>1</versionCode>\n"
+ + " <versionName>1.0' platformBuildVersionName='5.0-1521886</versionName>\n"
+ + " <rawPathResId>android_wear_micro_apk</rawPathResId>\n"
+ + "</wearableApp>");
+
+ // RAW content for HTML/web
+ createFile(resources, "raw/index1.html", ""
+ // TODO: Test single quotes, attribute without quotes, spaces around = etc, prologue, xhtml
+ + "<!DOCTYPE html>\n"
+ + "<html>\n"
+ + "<!--\n"
+ + " Blah blah\n"
+ + "-->\n"
+ + "<head>\n"
+ + " <meta charset=\"utf-8\">\n"
+ + " <link href=\"http://fonts.googleapis.com/css?family=Alegreya:400italic,900italic|Alegreya+Sans:300\" rel=\"stylesheet\">\n"
+ + " <link href=\"http://yui.yahooapis.com/2.8.0r4/build/reset/reset-min.css\" rel=\"stylesheet\">\n"
+ + " <link href=\"static/landing.css\" rel=\"stylesheet\">\n"
+ + " <script src=\"http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js\"></script>\n"
+ + " <script src=\"static/modernizr.custom.14469.js\"></script>\n"
+ + " <meta name=\"viewport\" content=\"width=690\">\n"
+ + " <style type=\"text/css\">\n"
+ + "html, body {\n"
+ + " margin: 0;\n"
+ + " height: 100%;\n"
+ + " background-image: url(file:///android_res/raw/my_used_raw_drawable);\n"
+ + "}\n"
+ + "</style>"
+ + "</head>\n"
+ + "<body>\n"
+ + "\n"
+ + "<div id=\"container\">\n"
+ + "\n"
+ + " <div id=\"logo\"></div>\n"
+ + "\n"
+ + " <div id=\"text\">\n"
+ + " <p>\n"
+ + " More ignored text here\n"
+ + " </p>\n"
+ + " </div>\n"
+ + "\n"
+ + " <a id=\"playlink\" href=\"file/foo.png\"> </a>\n"
+ + "</div>\n"
+ + "<script>\n"
+ + "\n"
+ + "if (Modernizr.cssanimations &&\n"
+ + " Modernizr.svg &&\n"
+ + " Modernizr.csstransforms3d &&\n"
+ + " Modernizr.csstransitions) {\n"
+ + "\n"
+ + " // progressive enhancement\n"
+ + " $('#device-screen').css('display', 'block');\n"
+ + " $('#device-frame').css('background-image', 'url( 'drawable-mdpi/tilted.png')' );\n"
+ + " $('#opentarget').css('visibility', 'visible');\n"
+ + " $('body').addClass('withvignette');\n"
+ + "</script>\n"
+ + "\n"
+ + "</body>\n"
+ + "</html>");
+
+ createFile(resources, "raw/styles2.css", ""
+ + "/**\n"
+ + " * Copyright 2014 Google Inc.\n"
+ + " */\n"
+ + "\n"
+ + "html, body {\n"
+ + " margin: 0;\n"
+ + " height: 100%;\n"
+ + " -webkit-font-smoothing: antialiased;\n"
+ + "}\n"
+ + "#logo {\n"
+ + " position: absolute;\n"
+ + " left: 0;\n"
+ + " top: 60px;\n"
+ + " width: 250px;\n"
+ + " height: 102px;\n"
+ + " background-image: url(img2.png);\n"
+ + " background-repeat: no-repeat;\n"
+ + " background-size: contain;\n"
+ + " opacity: 0.7;\n"
+ + " z-index: 100;\n"
+ + "}\n"
+ + "device-frame {\n"
+ + " position: absolute;\n"
+ + " right: -70px;\n"
+ + " top: 0;\n"
+ + " width: 420px;\n"
+ + " height: 500px;\n"
+ + " background-image: url(tilted_fallback.jpg);\n"
+ + " background-size: cover;\n"
+ + " -webkit-user-select: none;\n"
+ + " -moz-user-select: none;\n"
+ + "}");
+
+ createFile(resources, "raw/my_js.js", ""
+ + "function $(id) {\n"
+ + " return document.getElementById(id);\n"
+ + "}\n"
+ + "\n"
+ + "/* Ignored block comment: \"ignore me\" */\n"
+ + "function show(id) {\n"
+ + " $(id).style.display = \"block\";\n"
+ + "}\n"
+ + "\n"
+ + "function hide(id) {\n"
+ + " $(id).style.display = \"none\";\n"
+ + "}\n"
+ + "// Line comment\n"
+ + "function onStatusBoxFocus(elt) {\n"
+ + " elt.value = '';\n"
+ + " elt.style.color = \"#000\";\n"
+ + " show('status_submit');\n"
+ + "}\n");
+
+ return resources;
+ }
+
+ private static File createMergedManifest(File dir) throws IOException {
+ return createFile(dir, "app/build/manifests/release/AndroidManifest.xml", ""
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" android:versionCode=\"1\" android:versionName=\"1.0\" package=\"com.example.shrinkunittest.app\">\n"
+ + " <uses-sdk android:minSdkVersion=\"20\" android:targetSdkVersion=\"19\"/>\n"
+ + "\n"
+ + " <application android:allowBackup=\"true\" android:icon=\"@drawable/ic_launcher\" android:label=\"@string/app_name\">\n"
+ + " <activity android:label=\"@string/app_name\" android:name=\"com.example.shrinkunittest.app.MainActivity\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + "\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data\n"
+ + " android:name=\"com.google.android.wearable.beta.app\"\n"
+ + " android:resource=\"@xml/android_wear_micro_apk\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>");
+ }
+
+ private static File createResourceClassFolder(File dir) throws IOException {
+ File rDir = new File(dir, "app/build/source/r/release".replace('/', separatorChar));
+ //noinspection ResultOfMethodCallIgnored
+ rDir.mkdirs();
+
+ createFile(rDir, "com/example/shrinkunittest/app/R.java", ""
+ + "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+ + " *\n"
+ + " * This class was automatically generated by the\n"
+ + " * aapt tool from the resource data it found. It\n"
+ + " * should not be modified by hand.\n"
+ + " */\n"
+ + "\n"
+ + "package com.example.shrinkunittest.app;\n"
+ + "\n"
+ + "public final class R {\n"
+ + " public static final class attr {\n"
+ + " /** <p>Must be an integer value, such as \"<code>100</code>\".\n"
+ + "<p>This may also be a reference to a resource (in the form\n"
+ + "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
+ + "theme attribute (in the form\n"
+ + "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
+ + "containing a value of this type.\n"
+ + " */\n"
+ + " public static final int myAttr1=0x7f010000;\n"
+ + " /** <p>Must be a boolean value, either \"<code>true</code>\" or \"<code>false</code>\".\n"
+ + "<p>This may also be a reference to a resource (in the form\n"
+ + "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
+ + "theme attribute (in the form\n"
+ + "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
+ + "containing a value of this type.\n"
+ + " */\n"
+ + " public static final int myAttr2=0x7f010001;\n"
+ + " }\n"
+ + " public static final class dimen {\n"
+ + " public static final int activity_horizontal_margin=0x7f040000;\n"
+ + " public static final int activity_vertical_margin=0x7f040001;\n"
+ + " }\n"
+ + " public static final class drawable {\n"
+ + " public static final int ic_launcher=0x7f020000;\n"
+ + " public static final int unused=0x7f020001;\n"
+ + " }\n"
+ + " public static final class id {\n"
+ + " public static final int action_settings=0x7f080000;\n"
+ + " public static final int action_settings2=0x7f080001;\n"
+ + " }\n"
+ + " public static final class layout {\n"
+ + " public static final int activity_main=0x7f030000;\n"
+ + " }\n"
+ + " public static final class menu {\n"
+ + " public static final int main=0x7f070000;\n"
+ + " }\n"
+ + " public static final class raw {\n"
+ + " public static final int android_wear_micro_apk=0x7f090000;\n"
+ + " public static final int index1=0x7f090001;\n"
+ + " public static final int styles2=0x7f090002;\n"
+ + " public static final int my_js=0x7f090003;\n"
+ + " public static final int my_used_raw_drawable=0x7f090004;\n"
+ + " }"
+ + " public static final class string {\n"
+ + " public static final int action_settings=0x7f050000;\n"
+ + " public static final int action_settings2=0x7f050004;\n"
+ + " public static final int alias=0x7f050001;\n"
+ + " public static final int app_name=0x7f050002;\n"
+ + " public static final int hello_world=0x7f050003;\n"
+ + " }\n"
+ + " public static final class style {\n"
+ + " public static final int AppTheme=0x7f060000;\n"
+ + " public static final int MyStyle=0x7f060001;\n"
+ + " public static final int MyStyle_Child=0x7f060002;\n"
+ + " }\n"
+ + " public static final class xml {\n"
+ + " public static final int android_wear_micro_apk=0x7f0a0000;\n"
+ + " }"
+ + "}");
+ return rDir;
+ }
+
+ private static File createProguardedClasses(File dir) throws IOException {
+ byte[] bytecode = new byte[] {
+ (byte)80, (byte)75, (byte)3, (byte)4, (byte)20, (byte)0, (byte)8, (byte)0,
+ (byte)8, (byte)0, (byte)12, (byte)88, (byte)-62, (byte)68, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)49, (byte)0, (byte)0, (byte)0, (byte)99, (byte)111,
+ (byte)109, (byte)47, (byte)101, (byte)120, (byte)97, (byte)109, (byte)112, (byte)108,
+ (byte)101, (byte)47, (byte)115, (byte)104, (byte)114, (byte)105, (byte)110, (byte)107,
+ (byte)117, (byte)110, (byte)105, (byte)116, (byte)116, (byte)101, (byte)115, (byte)116,
+ (byte)47, (byte)97, (byte)112, (byte)112, (byte)47, (byte)77, (byte)97, (byte)105,
+ (byte)110, (byte)65, (byte)99, (byte)116, (byte)105, (byte)118, (byte)105, (byte)116,
+ (byte)121, (byte)46, (byte)99, (byte)108, (byte)97, (byte)115, (byte)115, (byte)117,
+ (byte)-110, (byte)-53, (byte)78, (byte)-37, (byte)64, (byte)20, (byte)-122, (byte)-1,
+ (byte)-63, (byte)14, (byte)14, (byte)97, (byte)32, (byte)36, (byte)36, (byte)-31,
+ (byte)82, (byte)110, (byte)-31, (byte)-46, (byte)58, (byte)-15, (byte)-62, (byte)82,
+ (byte)-73, (byte)-127, (byte)74, (byte)-112, (byte)-107, (byte)-91, (byte)-94, (byte)46,
+ (byte)-112, (byte)88, (byte)-80, (byte)-77, (byte)-30, (byte)1, (byte)70, (byte)36,
+ (byte)-29, (byte)40, (byte)-98, (byte)-92, (byte)101, (byte)-59, (byte)-85, (byte)-16,
+ (byte)0, (byte)108, (byte)-70, (byte)73, (byte)-44, (byte)46, (byte)-6, (byte)0,
+ (byte)60, (byte)20, (byte)-22, (byte)-103, (byte)-60, (byte)80, (byte)110, (byte)-99,
+ (byte)-111, (byte)-50, (byte)57, (byte)-93, (byte)-1, (byte)59, (byte)23, (byte)-23,
+ (byte)-52, (byte)-3, (byte)-61, (byte)-17, (byte)63, (byte)0, (byte)62, (byte)-61,
+ (byte)-77, (byte)110, (byte)44, (byte)-64, (byte)-70, (byte)113, (byte)-116, (byte)-55,
+ (byte)2, (byte)14, (byte)-74, (byte)28, (byte)84, (byte)29, (byte)108, (byte)59,
+ (byte)-40, (byte)-55, (byte)-63, (byte)70, (byte)-34, (byte)-104, (byte)69, (byte)99,
+ (byte)74, (byte)57, (byte)100, (byte)80, (byte)-52, (byte)17, (byte)81, (byte)48,
+ (byte)-90, (byte)60, (byte)-117, (byte)105, (byte)44, (byte)112, (byte)108, (byte)96,
+ (byte)-103, (byte)99, (byte)23, (byte)21, (byte)-114, (byte)61, (byte)44, (byte)113,
+ (byte)124, (byte)-60, (byte)42, (byte)-57, (byte)39, (byte)124, (byte)-32, (byte)-88,
+ (byte)97, (byte)-99, (byte)-93, (byte)-114, (byte)21, (byte)6, (byte)-53, (byte)-83,
+ (byte)5, (byte)12, (byte)-21, (byte)110, (byte)-19, (byte)107, (byte)-88, (byte)-94,
+ (byte)94, (byte)44, (byte)35, (byte)127, (byte)32, (byte)-59, (byte)119, (byte)-1,
+ (byte)88, (byte)-88, (byte)126, (byte)-96, (byte)-50, (byte)-37, (byte)-95, (byte)22,
+ (byte)-67, (byte)-58, (byte)-104, (byte)58, (byte)101, (byte)-80, (byte)-35, (byte)-64,
+ (byte)-72, (byte)37, (byte)55, (byte)120, (byte)11, (byte)55, (byte)-116, (byte)82,
+ (byte)113, (byte)-97, (byte)-124, (byte)56, (byte)-15, (byte)-113, (byte)-6, (byte)42,
+ (byte)106, (byte)-117, (byte)-41, (byte)-62, (byte)-77, (byte)-116, (byte)51, (byte)-122,
+ (byte)-43, (byte)119, (byte)-124, (byte)64, (byte)-117, (byte)-50, (byte)88, (byte)-100,
+ (byte)-34, (byte)-105, (byte)74, (byte)-22, (byte)47, (byte)-44, (byte)-72, (byte)25,
+ (byte)71, (byte)-126, (byte)-95, (byte)-12, (byte)-120, (byte)-122, (byte)-35, (byte)-82,
+ (byte)127, (byte)-40, (byte)-46, (byte)114, (byte)32, (byte)-11, (byte)53, (byte)-61,
+ (byte)-54, (byte)127, (byte)39, (byte)103, (byte)40, (byte)-65, (byte)91, (byte)-99,
+ (byte)-63, (byte)107, (byte)-59, (byte)29, (byte)95, (byte)-4, (byte)8, (byte)59,
+ (byte)-35, (byte)-74, (byte)-16, (byte)-109, (byte)-53, (byte)-98, (byte)84, (byte)87,
+ (byte)125, (byte)-22, (byte)-91, (byte)69, (byte)-94, (byte)-57, (byte)-43, (byte)-113,
+ (byte)67, (byte)-87, (byte)-2, (byte)117, (byte)-104, (byte)-71, (byte)16, (byte)-38,
+ (byte)-28, (byte)5, (byte)17, (byte)67, (byte)-98, (byte)-30, (byte)-105, (byte)61,
+ (byte)28, (byte)57, (byte)9, (byte)25, (byte)-78, (byte)-79, (byte)106, (byte)-10,
+ (byte)-60, (byte)56, (byte)92, (byte)124, (byte)12, (byte)-65, (byte)117, (byte)-75,
+ (byte)-116, (byte)85, (byte)98, (byte)82, (byte)104, (byte)-100, (byte)88, (byte)-91,
+ (byte)111, (byte)83, (byte)-18, (byte)68, (byte)-76, (byte)69, (byte)75, (byte)11,
+ (byte)42, (byte)58, (byte)-97, (byte)8, (byte)-35, (byte)-116, (byte)-107, (byte)22,
+ (byte)74, (byte)-97, (byte)-46, (byte)-96, (byte)-88, (byte)-46, (byte)18, (byte)109,
+ (byte)-104, (byte)99, (byte)-125, (byte)-103, (byte)53, (byte)-110, (byte)-35, (byte)-92,
+ (byte)87, (byte)-127, (byte)60, (byte)35, (byte)-97, (byte)-87, (byte)-113, (byte)-112,
+ (byte)-3, (byte)-103, (byte)2, (byte)-76, (byte)-47, (byte)84, (byte)94, (byte)-58,
+ (byte)20, (byte)93, (byte)-128, (byte)-41, (byte)-67, (byte)17, (byte)102, (byte)-22,
+ (byte)69, (byte)54, (byte)-60, (byte)-36, (byte)-124, (byte)98, (byte)112, (byte)-79,
+ (byte)-10, (byte)68, (byte)89, (byte)41, (byte)53, (byte)4, (byte)47, (byte)78,
+ (byte)121, (byte)67, (byte)-52, (byte)-38, (byte)119, (byte)41, (byte)69, (byte)31,
+ (byte)35, (byte)-91, (byte)-86, (byte)-60, (byte)-48, (byte)-17, (byte)67, (byte)-39,
+ (byte)-5, (byte)-123, (byte)121, (byte)-122, (byte)-125, (byte)-75, (byte)-94, (byte)117,
+ (byte)-117, (byte)-116, (byte)125, (byte)103, (byte)74, (byte)-25, (byte)38, (byte)56,
+ (byte)-2, (byte)2, (byte)80, (byte)75, (byte)7, (byte)8, (byte)39, (byte)-48,
+ (byte)-69, (byte)-98, (byte)-101, (byte)1, (byte)0, (byte)0, (byte)-86, (byte)2,
+ (byte)0, (byte)0, (byte)80, (byte)75, (byte)1, (byte)2, (byte)20, (byte)0,
+ (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)12, (byte)88,
+ (byte)-62, (byte)68, (byte)39, (byte)-48, (byte)-69, (byte)-98, (byte)-101, (byte)1,
+ (byte)0, (byte)0, (byte)-86, (byte)2, (byte)0, (byte)0, (byte)49, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120, (byte)97, (byte)109,
+ (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104, (byte)114, (byte)105,
+ (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116, (byte)116, (byte)101,
+ (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112, (byte)47, (byte)77,
+ (byte)97, (byte)105, (byte)110, (byte)65, (byte)99, (byte)116, (byte)105, (byte)118,
+ (byte)105, (byte)116, (byte)121, (byte)46, (byte)99, (byte)108, (byte)97, (byte)115,
+ (byte)115, (byte)80, (byte)75, (byte)5, (byte)6, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)1, (byte)0, (byte)1, (byte)0, (byte)95, (byte)0, (byte)0,
+ (byte)0, (byte)-6, (byte)1, (byte)0, (byte)0, (byte)0, (byte)0
+ };
+ return createFile(dir, "app/build/classes-proguard/release/classes.jar", bytecode);
+ }
+
+ private static File createUnproguardedClasses(File dir) throws IOException {
+ byte [] bytecode = new byte[] {
+ (byte)80, (byte)75, (byte)3, (byte)4, (byte)20, (byte)0, (byte)8, (byte)0,
+ (byte)8, (byte)0, (byte)-11, (byte)-127, (byte)-61, (byte)68, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)9, (byte)0, (byte)4, (byte)0, (byte)77, (byte)69,
+ (byte)84, (byte)65, (byte)45, (byte)73, (byte)78, (byte)70, (byte)47, (byte)-2,
+ (byte)-54, (byte)0, (byte)0, (byte)3, (byte)0, (byte)80, (byte)75, (byte)7,
+ (byte)8, (byte)0, (byte)0, (byte)0, (byte)0, (byte)2, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)80, (byte)75, (byte)3,
+ (byte)4, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-11,
+ (byte)-127, (byte)-61, (byte)68, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)20,
+ (byte)0, (byte)0, (byte)0, (byte)77, (byte)69, (byte)84, (byte)65, (byte)45,
+ (byte)73, (byte)78, (byte)70, (byte)47, (byte)77, (byte)65, (byte)78, (byte)73,
+ (byte)70, (byte)69, (byte)83, (byte)84, (byte)46, (byte)77, (byte)70, (byte)-13,
+ (byte)77, (byte)-52, (byte)-53, (byte)76, (byte)75, (byte)45, (byte)46, (byte)-47,
+ (byte)13, (byte)75, (byte)45, (byte)42, (byte)-50, (byte)-52, (byte)-49, (byte)-77,
+ (byte)82, (byte)48, (byte)-44, (byte)51, (byte)-32, (byte)-27, (byte)114, (byte)46,
+ (byte)74, (byte)77, (byte)44, (byte)73, (byte)77, (byte)-47, (byte)117, (byte)-86,
+ (byte)4, (byte)9, (byte)-104, (byte)-23, (byte)25, (byte)-60, (byte)-101, (byte)-103,
+ (byte)42, (byte)104, (byte)56, (byte)22, (byte)20, (byte)-28, (byte)-92, (byte)42,
+ (byte)120, (byte)-26, (byte)37, (byte)-21, (byte)105, (byte)-14, (byte)114, (byte)-15,
+ (byte)114, (byte)1, (byte)0, (byte)80, (byte)75, (byte)7, (byte)8, (byte)127,
+ (byte)71, (byte)56, (byte)-57, (byte)60, (byte)0, (byte)0, (byte)0, (byte)60,
+ (byte)0, (byte)0, (byte)0, (byte)80, (byte)75, (byte)3, (byte)4, (byte)20,
+ (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-49, (byte)-127, (byte)-61,
+ (byte)68, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)49, (byte)0, (byte)0,
+ (byte)0, (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120, (byte)97,
+ (byte)109, (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104, (byte)114,
+ (byte)105, (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116, (byte)116,
+ (byte)101, (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112, (byte)47,
+ (byte)77, (byte)97, (byte)105, (byte)110, (byte)65, (byte)99, (byte)116, (byte)105,
+ (byte)118, (byte)105, (byte)116, (byte)121, (byte)46, (byte)99, (byte)108, (byte)97,
+ (byte)115, (byte)115, (byte)-107, (byte)-109, (byte)-51, (byte)82, (byte)19, (byte)65,
+ (byte)16, (byte)-57, (byte)-1, (byte)-109, (byte)4, (byte)-106, (byte)-124, (byte)13,
+ (byte)-127, (byte)64, (byte)-126, (byte)32, (byte)72, (byte)16, (byte)-112, (byte)124,
+ (byte)8, (byte)43, (byte)-8, (byte)45, (byte)-96, (byte)66, (byte)4, (byte)-115,
+ (byte)5, (byte)90, (byte)37, (byte)22, (byte)7, (byte)47, (byte)-44, (byte)-110,
+ (byte)-116, (byte)48, (byte)-78, (byte)-103, (byte)77, (byte)101, (byte)39, (byte)81,
+ (byte)-34, (byte)-58, (byte)7, (byte)-32, (byte)34, (byte)7, (byte)40, (byte)61,
+ (byte)-8, (byte)0, (byte)62, (byte)-109, (byte)101, (byte)-39, (byte)-77, (byte)89,
+ (byte)80, (byte)-85, (byte)66, (byte)-91, (byte)-36, (byte)-61, (byte)-12, (byte)108,
+ (byte)79, (byte)-9, (byte)111, (byte)122, (byte)-2, (byte)-45, (byte)-13, (byte)-29,
+ (byte)-41, (byte)-73, (byte)-17, (byte)0, (byte)22, (byte)-15, (byte)50, (byte)6,
+ (byte)19, (byte)51, (byte)122, (byte)-72, (byte)17, (byte)-59, (byte)44, (byte)-78,
+ (byte)49, (byte)-12, (byte)34, (byte)-89, (byte)-121, (byte)124, (byte)20, (byte)5,
+ (byte)-36, (byte)-116, (byte)97, (byte)14, (byte)-13, (byte)-67, (byte)-80, (byte)112,
+ (byte)43, (byte)-118, (byte)5, (byte)44, (byte)-22, (byte)-72, (byte)-37, (byte)6,
+ (byte)-18, (byte)24, (byte)-72, (byte)-53, (byte)-48, (byte)-67, (byte)44, (byte)-92,
+ (byte)80, (byte)-113, (byte)25, (byte)-62, (byte)-39, (byte)-36, (byte)14, (byte)67,
+ (byte)-92, (byte)-24, (byte)86, (byte)56, (byte)67, (byte)98, (byte)83, (byte)72,
+ (byte)-2, (byte)-86, (byte)81, (byte)-35, (byte)-29, (byte)-11, (byte)-73, (byte)-10,
+ (byte)-98, (byte)67, (byte)-98, (byte)-28, (byte)-90, (byte)91, (byte)-74, (byte)-99,
+ (byte)29, (byte)-69, (byte)46, (byte)-12, (byte)127, (byte)-32, (byte)-116, (byte)-88,
+ (byte)3, (byte)-31, (byte)49, (byte)-52, (byte)109, (byte)-106, (byte)-35, (byte)-86,
+ (byte)-59, (byte)63, (byte)-39, (byte)-43, (byte)-102, (byte)-61, (byte)45, (byte)-17,
+ (byte)-96, (byte)46, (byte)-28, (byte)97, (byte)-125, (byte)-88, (byte)-118, (byte)123,
+ (byte)-54, (byte)-78, (byte)107, (byte)53, (byte)107, (byte)-53, (byte)22, (byte)114,
+ (byte)-75, (byte)-84, (byte)68, (byte)83, (byte)-88, (byte)-93, (byte)37, (byte)-122,
+ (byte)30, (byte)87, (byte)22, (byte)-21, (byte)-36, (byte)86, (byte)68, (byte)72,
+ (byte)103, (byte)55, (byte)109, (byte)89, (byte)-87, (byte)-69, (byte)-94, (byte)98,
+ (byte)-71, (byte)-98, (byte)-75, (byte)-42, (byte)-112, (byte)21, (byte)-121, (byte)47,
+ (byte)-23, (byte)66, (byte)-110, (byte)-98, (byte)-35, (byte)-28, (byte)-107, (byte)-110,
+ (byte)-12, (byte)-108, (byte)45, (byte)-53, (byte)124, (byte)91, (byte)-7, (byte)-47,
+ (byte)-125, (byte)109, (byte)-126, (byte)-55, (byte)123, (byte)-114, (byte)123, (byte)93,
+ (byte)83, (byte)-62, (byte)-107, (byte)-34, (byte)22, (byte)-105, (byte)-115, (byte)127,
+ (byte)-56, (byte)77, (byte)-63, (byte)63, (byte)90, (byte)-38, (byte)-69, (byte)-108,
+ (byte)123, (byte)71, (byte)69, (byte)87, (byte)-3, (byte)-11, (byte)-63, (byte)54,
+ (byte)-53, (byte)12, (byte)41, (byte)87, (byte)6, (byte)-108, (byte)-110, (byte)-30,
+ (byte)-43, (byte)109, (byte)-18, (byte)-16, (byte)-78, (byte)-30, (byte)21, (byte)-122,
+ (byte)-47, (byte)54, (byte)52, (byte)29, (byte)-47, (byte)34, (byte)10, (byte)-102,
+ (byte)49, (byte)12, (byte)95, (byte)18, (byte)-62, (byte)16, (byte)18, (byte)-124,
+ (byte)96, (byte)37, (byte)-122, (byte)56, (byte)29, (byte)-92, (byte)124, (byte)-72,
+ (byte)101, (byte)-41, (byte)2, (byte)1, (byte)99, (byte)-37, (byte)110, (byte)-93,
+ (byte)94, (byte)-26, (byte)27, (byte)66, (byte)-1, (byte)12, (byte)-4, (byte)45,
+ (byte)-45, (byte)-4, (byte)7, (byte)-69, (byte)105, (byte)-101, (byte)-120, (byte)-93,
+ (byte)-49, (byte)-60, (byte)16, (byte)82, (byte)6, (byte)-18, (byte)-101, (byte)120,
+ (byte)-124, (byte)73, (byte)19, (byte)75, (byte)88, (byte)54, (byte)-79, (byte)-126,
+ (byte)-57, (byte)6, (byte)-98, (byte)-104, (byte)120, (byte)-118, (byte)73, (byte)3,
+ (byte)-85, (byte)38, (byte)-42, (byte)80, (byte)52, (byte)-16, (byte)-52, (byte)-60,
+ (byte)58, (byte)54, (byte)12, (byte)60, (byte)55, (byte)-15, (byte)66, (byte)71,
+ (byte)-114, (byte)97, (byte)-100, (byte)-95, (byte)-16, (byte)31, (byte)87, (byte)-61,
+ (byte)48, (byte)116, (byte)126, (byte)2, (byte)-67, (byte)116, (byte)-18, (byte)54,
+ (byte)64, (byte)-107, (byte)-49, (byte)118, (byte)-32, (byte)-68, (byte)-103, (byte)118,
+ (byte)-20, (byte)35, (byte)-73, (byte)-95, (byte)-88, (byte)-93, (byte)-50, (byte)39,
+ (byte)102, (byte)73, (byte)74, (byte)94, (byte)47, (byte)58, (byte)-74, (byte)-25,
+ (byte)113, (byte)-22, (byte)-110, (byte)-72, (byte)29, (byte)-16, (byte)118, (byte)-85,
+ (byte)-76, (byte)39, (byte)67, (byte)-97, (byte)-57, (byte)85, (byte)-47, (byte)-107,
+ (byte)-118, (byte)75, (byte)-75, (byte)67, (byte)122, (byte)-111, (byte)-116, (byte)-39,
+ (byte)-110, (byte)-66, (byte)-7, (byte)-60, (byte)62, (byte)87, (byte)-66, (byte)118,
+ (byte)-14, (byte)-67, (byte)67, (byte)-105, (byte)90, (byte)103, (byte)24, (byte)-49,
+ (byte)-26, (byte)-38, (byte)72, (byte)27, (byte)44, (byte)-109, (byte)-68, (byte)51,
+ (byte)29, (byte)107, (byte)107, (byte)93, (byte)121, (byte)-92, (byte)-75, (byte)-15,
+ (byte)-56, (byte)-91, (byte)44, (byte)6, (byte)67, (byte)-76, (byte)-90, (byte)116,
+ (byte)-101, (byte)-39, (byte)82, (byte)-69, (byte)6, (byte)-94, (byte)2, (byte)83,
+ (byte)109, (byte)-81, (byte)-103, (byte)33, (byte)74, (byte)-123, (byte)-21, (byte)89,
+ (byte)-87, (byte)-30, (byte)-65, (byte)38, (byte)18, (byte)109, (byte)-86, (byte)99,
+ (byte)97, (byte)-70, (byte)49, (byte)18, (byte)90, (byte)24, (byte)87, (byte)-18,
+ (byte)-110, (byte)30, (byte)74, (byte)-56, (byte)125, (byte)-110, (byte)42, (byte)-45,
+ (byte)41, (byte)15, (byte)-109, (byte)-12, (byte)-72, (byte)77, (byte)-24, (byte)47,
+ (byte)2, (byte)-90, (byte)-69, (byte)-124, (byte)-58, (byte)4, (byte)-3, (byte)89,
+ (byte)100, (byte)25, (byte)-39, (byte)-82, (byte)-4, (byte)25, (byte)-40, (byte)23,
+ (byte)-102, (byte)-124, (byte)-48, (byte)79, (byte)99, (byte)-73, (byte)-17, (byte)-116,
+ (byte)98, (byte)-128, (byte)70, (byte)-77, (byte)21, (byte)-128, (byte)36, (byte)6,
+ (byte)-3, (byte)116, (byte)-22, (byte)-82, (byte)32, (byte)-71, (byte)68, (byte)-47,
+ (byte)33, (byte)-78, (byte)-15, (byte)124, (byte)-31, (byte)12, (byte)-95, (byte)-4,
+ (byte)9, (byte)-62, (byte)-89, (byte)-120, (byte)-4, (byte)-127, (byte)-12, (byte)33,
+ (byte)-84, (byte)23, (byte)41, (byte)-75, (byte)-113, (byte)32, (byte)9, (byte)31,
+ (byte)-106, (byte)110, (byte)37, (byte)4, (byte)48, (byte)61, (byte)75, (byte)99,
+ (byte)-40, (byte)-81, (byte)-31, (byte)10, (byte)70, (byte)2, (byte)-20, (byte)58,
+ (byte)-27, (byte)-75, (byte)-80, (byte)-89, (byte)-24, (byte)58, (byte)65, (byte)119,
+ (byte)-31, (byte)20, (byte)70, (byte)-28, (byte)-8, (byte)2, (byte)27, (byte)-13,
+ (byte)23, (byte)83, (byte)116, (byte)-96, (byte)-12, (byte)37, (byte)-56, (byte)81,
+ (byte)92, (byte)-11, (byte)-111, (byte)-44, (byte)-48, (byte)1, (byte)-46, (byte)-95,
+ (byte)24, (byte)93, (byte)76, (byte)-70, (byte)-16, (byte)21, (byte)61, (byte)12,
+ (byte)43, (byte)99, (byte)39, (byte)-120, (byte)126, (byte)70, (byte)87, (byte)-28,
+ (byte)88, (byte)87, (byte)30, (byte)-45, (byte)-20, (byte)-80, (byte)-49, (byte)78,
+ (byte)-46, (byte)-7, (byte)-128, (byte)107, (byte)48, (byte)48, (byte)65, (byte)69,
+ (byte)103, (byte)-56, (byte)119, (byte)-35, (byte)-33, (byte)35, (byte)-45, (byte)-54,
+ (byte)-66, (byte)-40, (byte)35, (byte)77, (byte)49, (byte)19, (byte)-60, (byte)54,
+ (byte)-120, (byte)-98, (byte)33, (byte)113, (byte)67, (byte)20, (byte)-25, (byte)-85,
+ (byte)-10, (byte)19, (byte)-3, (byte)-12, (byte)124, (byte)49, (byte)-27, (byte)87,
+ (byte)59, (byte)-115, (byte)-121, (byte)100, (byte)71, (byte)41, (byte)119, (byte)22,
+ (byte)-9, (byte)-16, (byte)-128, (byte)14, (byte)88, (byte)32, (byte)59, (byte)74,
+ (byte)118, (byte)-127, (byte)108, (byte)6, (byte)35, (byte)-65, (byte)1, (byte)80,
+ (byte)75, (byte)7, (byte)8, (byte)-67, (byte)119, (byte)-82, (byte)40, (byte)-35,
+ (byte)2, (byte)0, (byte)0, (byte)-111, (byte)5, (byte)0, (byte)0, (byte)80,
+ (byte)75, (byte)3, (byte)4, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8,
+ (byte)0, (byte)-49, (byte)-127, (byte)-61, (byte)68, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)48, (byte)0, (byte)0, (byte)0, (byte)99, (byte)111, (byte)109,
+ (byte)47, (byte)101, (byte)120, (byte)97, (byte)109, (byte)112, (byte)108, (byte)101,
+ (byte)47, (byte)115, (byte)104, (byte)114, (byte)105, (byte)110, (byte)107, (byte)117,
+ (byte)110, (byte)105, (byte)116, (byte)116, (byte)101, (byte)115, (byte)116, (byte)47,
+ (byte)97, (byte)112, (byte)112, (byte)47, (byte)66, (byte)117, (byte)105, (byte)108,
+ (byte)100, (byte)67, (byte)111, (byte)110, (byte)102, (byte)105, (byte)103, (byte)46,
+ (byte)99, (byte)108, (byte)97, (byte)115, (byte)115, (byte)-115, (byte)81, (byte)-37,
+ (byte)110, (byte)-45, (byte)64, (byte)16, (byte)61, (byte)-45, (byte)92, (byte)-20,
+ (byte)4, (byte)-105, (byte)-74, (byte)-127, (byte)2, (byte)-31, (byte)82, (byte)40,
+ (byte)-41, (byte)36, (byte)20, (byte)-101, (byte)-10, (byte)-107, (byte)10, (byte)41,
+ (byte)23, (byte)-73, (byte)-118, (byte)72, (byte)-109, (byte)-86, (byte)105, (byte)45,
+ (byte)-47, (byte)-105, (byte)104, (byte)-29, (byte)44, (byte)-83, (byte)-117, (byte)99,
+ (byte)71, (byte)-66, (byte)32, (byte)126, (byte)11, (byte)-15, (byte)0, (byte)-22,
+ (byte)3, (byte)31, (byte)-64, (byte)71, (byte)33, (byte)-58, (byte)38, (byte)74,
+ (byte)-115, (byte)120, (byte)-31, (byte)97, (byte)103, (byte)-9, (byte)-52, (byte)-50,
+ (byte)57, (byte)51, (byte)123, (byte)-10, (byte)-25, (byte)-81, (byte)-53, (byte)31,
+ (byte)0, (byte)118, (byte)-96, (byte)-105, (byte)81, (byte)-60, (byte)35, (byte)21,
+ (byte)-101, (byte)101, (byte)60, (byte)-58, (byte)-109, (byte)18, (byte)10, (byte)120,
+ (byte)-86, (byte)-32, (byte)-103, (byte)-126, (byte)-25, (byte)-124, (byte)66, (byte)-57,
+ (byte)108, (byte)-99, (byte)-20, (byte)19, (byte)-24, (byte)-108, (byte)-96, (byte)29,
+ (byte)54, (byte)-37, (byte)-17, (byte)-102, (byte)-5, (byte)-26, (byte)-88, (byte)-33,
+ (byte)60, (byte)48, (byte)9, (byte)-107, (byte)-34, (byte)-123, (byte)-8, (byte)36,
+ (byte)12, (byte)87, (byte)120, (byte)103, (byte)-58, (byte)48, (byte)10, (byte)28,
+ (byte)-17, (byte)-20, (byte)13, (byte)97, (byte)-71, (byte)-19, (byte)123, (byte)97,
+ (byte)36, (byte)-68, (byte)-56, (byte)18, (byte)110, (byte)44, (byte)85, (byte)-68,
+ (byte)32, (byte)-108, (byte)91, (byte)39, (byte)-35, (byte)94, (byte)103, (byte)116,
+ (byte)-4, (byte)-2, (byte)-48, (byte)84, (byte)81, (byte)35, (byte)20, (byte)-9,
+ (byte)122, (byte)77, (byte)107, (byte)112, (byte)-92, (byte)-94, (byte)-50, (byte)-110,
+ (byte)-106, (byte)121, (byte)52, (byte)-20, (byte)14, (byte)-6, (byte)-93, (byte)-10,
+ (byte)-96, (byte)-61, (byte)-110, (byte)-44, (byte)-51, (byte)-15, (byte)64, (byte)-108,
+ (byte)-55, (byte)39, (byte)-83, (byte)84, (byte)52, (byte)-104, (byte)-75, (byte)-21,
+ (byte)120, (byte)78, (byte)-12, (byte)-106, (byte)-112, (byte)-85, (byte)-43, (byte)45,
+ (byte)66, (byte)-66, (byte)-19, (byte)79, (byte)36, (byte)97, (byte)-91, (byte)-25,
+ (byte)120, (byte)-78, (byte)31, (byte)79, (byte)-57, (byte)50, (byte)56, (byte)22,
+ (byte)99, (byte)87, (byte)38, (byte)83, (byte)-7, (byte)-74, (byte)112, (byte)45,
+ (byte)17, (byte)56, (byte)9, (byte)-98, (byte)39, (byte)-13, (byte)-47, (byte)-71,
+ (byte)19, (byte)18, (byte)-74, (byte)122, (byte)-74, (byte)63, (byte)53, (byte)-28,
+ (byte)103, (byte)49, (byte)-99, (byte)-71, (byte)-46, (byte)8, (byte)-49, (byte)121,
+ (byte)-26, (byte)-113, (byte)49, (byte)-85, (byte)70, (byte)50, (byte)-116, (byte)12,
+ (byte)49, (byte)-101, (byte)25, (byte)-83, (byte)-40, (byte)113, (byte)39, (byte)-4,
+ (byte)-126, (byte)15, (byte)78, (byte)-14, (byte)22, (byte)117, (byte)-41, (byte)118,
+ (byte)-25, (byte)77, (byte)-53, (byte)67, (byte)63, (byte)14, (byte)108, (byte)-71,
+ (byte)-25, (byte)36, (byte)106, (byte)-85, (byte)-103, (byte)50, (byte)61, (byte)-15,
+ (byte)64, (byte)-61, (byte)45, (byte)-36, (byte)78, (byte)-70, (byte)4, (byte)-79,
+ (byte)84, (byte)-16, (byte)82, (byte)-61, (byte)22, (byte)94, (byte)105, (byte)80,
+ (byte)-96, (byte)18, (byte)26, (byte)-1, (byte)-33, (byte)-111, (byte)-123, (byte)-81,
+ (byte)12, (byte)29, (byte)-116, (byte)47, (byte)-92, (byte)29, (byte)17, (byte)54,
+ (byte)-104, (byte)-81, (byte)-49, (byte)-7, (byte)-6, (byte)-33, (byte)124, (byte)-99,
+ (byte)-7, (byte)-4, (byte)65, (byte)19, (byte)57, (byte)-114, (byte)-103, (byte)11,
+ (byte)118, (byte)102, (byte)91, (byte)127, (byte)77, (byte)88, (byte)-69, (byte)18,
+ (byte)105, (byte)-7, (byte)-66, (byte)43, (byte)-123, (byte)-57, (byte)118, (byte)-50,
+ (byte)68, (byte)16, (byte)-54, (byte)5, (byte)92, (byte)-81, (byte)-3, (byte)-5,
+ (byte)117, (byte)-11, (byte)83, (byte)108, (byte)-13, (byte)-57, (byte)23, (byte)-39,
+ (byte)-1, (byte)34, (byte)-86, (byte)-55, (byte)-16, (byte)124, (byte)-86, (byte)-94,
+ (byte)-124, (byte)50, (byte)43, (byte)95, (byte)-29, (byte)-13, (byte)18, (byte)52,
+ (byte)-58, (byte)-53, (byte)25, (byte)124, (byte)-99, (byte)-15, (byte)74, (byte)6,
+ (byte)-81, (byte)50, (byte)94, (byte)67, (byte)101, (byte)-127, (byte)111, (byte)48,
+ (byte)-66, (byte)-103, (byte)-71, (byte)95, (byte)-25, (byte)69, (byte)-119, (byte)85,
+ (byte)28, (byte)-17, (byte)112, (byte)-58, (byte)-32, (byte)-99, (byte)7, (byte)71,
+ (byte)-95, (byte)-15, (byte)13, (byte)-12, (byte)37, (byte)45, (byte)-87, (byte)-90,
+ (byte)-19, (byte)41, (byte)-115, (byte)119, (byte)57, (byte)106, (byte)127, (byte)10,
+ (byte)112, (byte)15, (byte)-9, (byte)121, (byte)87, (byte)-15, (byte)96, (byte)65,
+ (byte)-34, (byte)76, (byte)111, (byte)-128, (byte)82, (byte)101, (byte)-23, (byte)59,
+ (byte)114, (byte)95, (byte)-111, (byte)79, (byte)4, (byte)40, (byte)35, (byte)-96,
+ (byte)112, (byte)-36, (byte)72, (byte)69, (byte)31, (byte)-2, (byte)6, (byte)80,
+ (byte)75, (byte)7, (byte)8, (byte)-81, (byte)-102, (byte)-119, (byte)99, (byte)-46,
+ (byte)1, (byte)0, (byte)0, (byte)-23, (byte)2, (byte)0, (byte)0, (byte)80,
+ (byte)75, (byte)1, (byte)2, (byte)20, (byte)0, (byte)20, (byte)0, (byte)8,
+ (byte)0, (byte)8, (byte)0, (byte)-11, (byte)-127, (byte)-61, (byte)68, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)2, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)9, (byte)0, (byte)4, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)77, (byte)69, (byte)84,
+ (byte)65, (byte)45, (byte)73, (byte)78, (byte)70, (byte)47, (byte)-2, (byte)-54,
+ (byte)0, (byte)0, (byte)80, (byte)75, (byte)1, (byte)2, (byte)20, (byte)0,
+ (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-11, (byte)-127,
+ (byte)-61, (byte)68, (byte)127, (byte)71, (byte)56, (byte)-57, (byte)60, (byte)0,
+ (byte)0, (byte)0, (byte)60, (byte)0, (byte)0, (byte)0, (byte)20, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)61, (byte)0, (byte)0, (byte)0,
+ (byte)77, (byte)69, (byte)84, (byte)65, (byte)45, (byte)73, (byte)78, (byte)70,
+ (byte)47, (byte)77, (byte)65, (byte)78, (byte)73, (byte)70, (byte)69, (byte)83,
+ (byte)84, (byte)46, (byte)77, (byte)70, (byte)80, (byte)75, (byte)1, (byte)2,
+ (byte)20, (byte)0, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0,
+ (byte)-49, (byte)-127, (byte)-61, (byte)68, (byte)-67, (byte)119, (byte)-82, (byte)40,
+ (byte)-35, (byte)2, (byte)0, (byte)0, (byte)-111, (byte)5, (byte)0, (byte)0,
+ (byte)49, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)-69, (byte)0,
+ (byte)0, (byte)0, (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120,
+ (byte)97, (byte)109, (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104,
+ (byte)114, (byte)105, (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116,
+ (byte)116, (byte)101, (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112,
+ (byte)47, (byte)77, (byte)97, (byte)105, (byte)110, (byte)65, (byte)99, (byte)116,
+ (byte)105, (byte)118, (byte)105, (byte)116, (byte)121, (byte)46, (byte)99, (byte)108,
+ (byte)97, (byte)115, (byte)115, (byte)80, (byte)75, (byte)1, (byte)2, (byte)20,
+ (byte)0, (byte)20, (byte)0, (byte)8, (byte)0, (byte)8, (byte)0, (byte)-49,
+ (byte)-127, (byte)-61, (byte)68, (byte)-81, (byte)-102, (byte)-119, (byte)99, (byte)-46,
+ (byte)1, (byte)0, (byte)0, (byte)-23, (byte)2, (byte)0, (byte)0, (byte)48,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)-9, (byte)3, (byte)0,
+ (byte)0, (byte)99, (byte)111, (byte)109, (byte)47, (byte)101, (byte)120, (byte)97,
+ (byte)109, (byte)112, (byte)108, (byte)101, (byte)47, (byte)115, (byte)104, (byte)114,
+ (byte)105, (byte)110, (byte)107, (byte)117, (byte)110, (byte)105, (byte)116, (byte)116,
+ (byte)101, (byte)115, (byte)116, (byte)47, (byte)97, (byte)112, (byte)112, (byte)47,
+ (byte)66, (byte)117, (byte)105, (byte)108, (byte)100, (byte)67, (byte)111, (byte)110,
+ (byte)102, (byte)105, (byte)103, (byte)46, (byte)99, (byte)108, (byte)97, (byte)115,
+ (byte)115, (byte)80, (byte)75, (byte)5, (byte)6, (byte)0, (byte)0, (byte)0,
+ (byte)0, (byte)4, (byte)0, (byte)4, (byte)0, (byte)58, (byte)1, (byte)0,
+ (byte)0, (byte)39, (byte)6, (byte)0, (byte)0, (byte)0, (byte)0,
+ };
+ return createFile(dir, "app/build/intermediates/classes/debug/classes.jar", bytecode);
+ }
+
+ private static File createMappingFile(File dir) throws IOException {
+ return createFile(dir, "app/build/proguard/release/mapping.txt", ""
+ + "com.example.shrinkunittest.app.MainActivity -> com.example.shrinkunittest.app.MainActivity:\n"
+ + " void onCreate(android.os.Bundle) -> onCreate\n"
+ + " boolean onCreateOptionsMenu(android.view.Menu) -> onCreateOptionsMenu\n"
+ + " boolean onOptionsItemSelected(android.view.MenuItem) -> onOptionsItemSelected\n"
+ + "com.foo.bar.R$layout -> com.foo.bar.t:\n"
+ + " int checkable_option_view_layout -> a\n"
+ + " int error_layout -> b\n"
+ + " int glyph_button_icon_only -> c\n"
+ + " int glyph_button_icon_with_text_below -> d\n"
+ + " int glyph_button_icon_with_text_right -> e\n"
+ + " int structure_status_view -> f\n"
+ + "android.support.annotation.FloatRange -> android.support.annotation.FloatRange:\n"
+ + " double from() -> from\n"
+ + " double to() -> to\n"
+ + " boolean fromInclusive() -> fromInclusive\n"
+ + " boolean toInclusive() -> toInclusive\n");
+ }
+
+ public void testFormatStringRegexp() {
+ assertEquals(NO_MATCH, convertFormatStringToRegexp(""));
+ assertEquals("\\Qfoo_\\E", convertFormatStringToRegexp("foo_"));
+ assertEquals("\\Qfoo\\E.*\\Q_\\E.*\\Qend\\E", convertFormatStringToRegexp("foo%s_%1$send"));
+ assertEquals("\\Qescape!.()\\E", convertFormatStringToRegexp("escape!.()"));
+
+ assertEquals(NO_MATCH, convertFormatStringToRegexp("%c%c%c%d"));
+ assertEquals(NO_MATCH, convertFormatStringToRegexp("%d%s"));
+ assertEquals(NO_MATCH, convertFormatStringToRegexp("%s%s"));
+ assertEquals(NO_MATCH, convertFormatStringToRegexp("%d_%d"));
+ assertEquals(NO_MATCH, convertFormatStringToRegexp("%s%s%s%s"));
+ assertEquals(NO_MATCH, convertFormatStringToRegexp("%s_%s_%s"));
+ assertEquals(NO_MATCH, convertFormatStringToRegexp("%.0f%s"));
+ assertEquals(".*\\Qabc\\E", convertFormatStringToRegexp("%sabc"));
+ assertEquals("\\Qa\\E.*", convertFormatStringToRegexp("a%d%s"));
+
+ assertTrue("foo_".matches(convertFormatStringToRegexp("foo_")));
+ assertTrue("fooA_BBend".matches(convertFormatStringToRegexp("foo%s_%1$send")));
+ assertFalse("A_BBend".matches(convertFormatStringToRegexp("foo%s_%1$send")));
+ }
+
+ /** Utility method to generate byte array literal dump (used by classesJarBytecode above) */
+ @SuppressWarnings("UnusedDeclaration") // Utility for future .class/.jar additions
+ public static void dumpBytes(File file) throws IOException {
+ byte[] bytes = Files.toByteArray(file);
+ int count = 0;
+ for (byte b : bytes) {
+ System.out.print("(byte)" + Byte.toString(b) + ", ");
+ count++;
+ if (count == 8) {
+ count = 0;
+ System.out.println();
+ }
+ }
+
+ System.out.println();
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ protected static void deleteDir(File root) {
+ if (root.exists()) {
+ File[] files = root.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteDir(file);
+ } else {
+ file.delete();
+ }
+ }
+ }
+ root.delete();
+ }
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @NonNull
+ private static File createFile(File dir, String relative) throws IOException {
+ File file = new File(dir, relative.replace('/', separatorChar));
+ file.getParentFile().mkdirs();
+ return file;
+ }
+
+
+ @NonNull
+ private static File createFile(File dir, String relative, String contents) throws IOException {
+ File file = createFile(dir, relative);
+ Files.write(contents, file, Charsets.UTF_8);
+ return file;
+ }
+
+
+ @NonNull
+ private static File createFile(File dir, String relative, byte[] contents) throws IOException {
+ File file = createFile(dir, relative);
+ Files.write(contents, file);
+ return file;
+ }
+
+ private static void checkState(ResourceUsageAnalyzer analyzer) {
+ List<Resource> resources = analyzer.getModel().getResources();
+ Collections.sort(resources, new Comparator<Resource>() {
+ @Override
+ public int compare(Resource resource1, Resource resource2) {
+ int delta = resource1.type.compareTo(resource2.type);
+ if (delta != 0) {
+ return delta;
+ }
+ return resource1.name.compareTo(resource2.name);
+ }
+ });
+
+ // Ensure unique
+ Resource prev = null;
+ for (Resource resource : resources) {
+ assertTrue(resource + " and " + prev, prev == null
+ || resource.type != prev.type
+ || !resource.name.equals(prev.name));
+ prev = resource;
+ }
+ }
+
+ public void testIsResourceClass() throws Exception {
+ File dummy = new File("dummy");
+ File mappingFile = createMappingFile(Files.createTempDir());
+ ResourceUsageAnalyzer analyzer = new ResourceUsageAnalyzer(dummy, dummy, dummy,
+ mappingFile, dummy, null);
+ analyzer.getModel().addDeclaredResource(ResourceType.LAYOUT, "structure_status_view", null, true);
+ analyzer.recordMapping(mappingFile);
+ assertTrue(analyzer.isResourceClass("android/support/v7/appcompat/R$attr.class"));
+ assertTrue(analyzer.isResourceClass("android/support/v7/appcompat/R$attr.class"));
+ assertTrue(analyzer.isResourceClass("android/support/v7/appcompat/R$bool.class"));
+ assertFalse(analyzer.isResourceClass("android/support/v7/appcompat/R.class"));
+ assertFalse(analyzer.isResourceClass("com/google/samples/apps/iosched/ui/BrowseSessionsActivity.class"));
+ assertFalse(analyzer.isResourceClass("android/support/annotation/FloatRange.class"));
+ assertTrue(analyzer.isResourceClass("com/foo/bar/t.class"));
+ assertTrue(analyzer.isResourceClass("com/foo/bar/t"));
+ Resource resource = analyzer.getResourceFromCode("com/foo/bar/t", "f");
+ assertNotNull(resource);
+ assertEquals("structure_status_view", resource.name);
+ assertEquals(ResourceType.LAYOUT, resource.type);
+ assertNull(analyzer.getResourceFromCode("android/support/annotation/FloatRange",
+ "fromInclusive"));
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java
similarity index 100%
rename from base/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java
rename to build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/fd/InjectBootstrapApplicationTaskTest.java b/build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/fd/InjectBootstrapApplicationTaskTest.java
new file mode 100644
index 0000000..32de5a1
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/tasks/fd/InjectBootstrapApplicationTaskTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.build.gradle.tasks.fd;
+
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.util.ASMifier;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class InjectBootstrapApplicationTaskTest {
+
+ @Test
+ public void test() {
+ // todo
+ }
+
+ private static String disassemble(File expected) throws IOException {
+ ClassReader reader = new ClassReader(new FileInputStream(expected));
+ StringWriter writer = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(writer);
+ try {
+ reader.accept(new TraceClassVisitor(printWriter), 0);
+ } finally {
+ printWriter.close();
+ }
+ return writer.toString();
+ }
+
+ private static String asmify(File expected) throws IOException {
+ ClassReader reader = new ClassReader(new FileInputStream(expected));
+ StringWriter writer = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(writer);
+ try {
+ reader.accept(new TraceClassVisitor(null, new ASMifier(), printWriter), 0);
+ } finally {
+ printWriter.close();
+ }
+ printWriter.close();
+ return writer.toString();
+ }
+
+}
diff --git a/base/build-system/gradle-core/src/test/resources/com/android/build/gradle/internal/tasks/multidex/mapping.txt b/build-system/gradle-core/src/test/resources/com/android/build/gradle/internal/tasks/multidex/mapping.txt
similarity index 100%
rename from base/build-system/gradle-core/src/test/resources/com/android/build/gradle/internal/tasks/multidex/mapping.txt
rename to build-system/gradle-core/src/test/resources/com/android/build/gradle/internal/tasks/multidex/mapping.txt
diff --git a/base/build-system/gradle/MODULE_LICENSE_APACHE2 b/build-system/gradle-experimental/MODULE_LICENSE_APACHE2
similarity index 100%
rename from base/build-system/gradle/MODULE_LICENSE_APACHE2
rename to build-system/gradle-experimental/MODULE_LICENSE_APACHE2
diff --git a/base/build-system/gradle-experimental/NOTICE b/build-system/gradle-experimental/NOTICE
similarity index 100%
rename from base/build-system/gradle-experimental/NOTICE
rename to build-system/gradle-experimental/NOTICE
diff --git a/build-system/gradle-experimental/build.gradle b/build-system/gradle-experimental/build.gradle
new file mode 100644
index 0000000..ecdd51b
--- /dev/null
+++ b/build-system/gradle-experimental/build.gradle
@@ -0,0 +1,73 @@
+apply plugin: 'groovy'
+apply plugin: 'clone-artifacts'
+apply plugin: 'idea'
+apply plugin: 'jacoco'
+
+// Extract gradle libraries to ensure gradle-core is compatible with older version.
+String gradleVersion = "2.10"
+File gradleBinary = file("$rootProject.projectDir/external/gradle/gradle-$gradleVersion-all.zip")
+File gradleLib = file("$rootProject.ext.androidHostOut/alternate-gradle/gradle-$gradleVersion/lib")
+
+task extractGradleLibs(type: Copy) {
+ from zipTree(gradleBinary)
+ into gradleLib.parentFile.parentFile
+}
+
+task setupGradleInIde {
+ dependsOn extractGradleLibs
+}
+
+dependencies {
+ compile fileTree(dir:gradleLib)
+ compile project(':base:gradle-core')
+
+ testCompile 'junit:junit:4.12'
+ testCompile project(':base:project-test-lib')
+}
+
+group = 'com.android.tools.build'
+archivesBaseName = 'gradle-experimental'
+version = rootProject.ext.experimentalVersion
+
+project.ext.pomName = 'Gradle Plug-in for Android Using Component Model'
+project.ext.pomDesc = 'Gradle plug-in to build Android applications.'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+
+jar.manifest.attributes("Plugin-Version": version)
+jar.manifest.attributes("Inception-Date":"${Calendar.getInstance().get(Calendar.YEAR)}:" +
+ "${Calendar.getInstance().get(Calendar.MONTH)}:${Calendar.getInstance().get(Calendar.DATE)}")
+
+
+test {
+ environment("CUSTOM_REPO", rootProject.file("../out/repo"))
+
+ testLogging {
+ events "failed"
+ }
+
+ maxParallelForks = Runtime.runtime.availableProcessors() / 2
+}
+
+groovydoc {
+ exclude "**/internal/**"
+ includePrivate false
+
+ docTitle "Gradle Plugin for Android"
+ header ""
+ footer "Copyright (C) 2012 The Android Open Source Project"
+ overview ""
+}
+
+task javadocJar(type: Jar, dependsOn:groovydoc) {
+ classifier 'javadoc'
+ from groovydoc.destinationDir
+}
+
+// Only package JavaDoc if using --init-script=buildSrc/base/release.gradle
+if (project.has("release")) {
+ artifacts {
+ archives javadocJar
+ }
+}
diff --git a/build-system/gradle-experimental/gradle-experimental.iml b/build-system/gradle-experimental/gradle-experimental.iml
new file mode 100644
index 0000000..b10ae88
--- /dev/null
+++ b/build-system/gradle-experimental/gradle-experimental.iml
@@ -0,0 +1,1249 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+ <excludeFolder url="file://$MODULE_DIR$/.gradle" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" exported="" name="proguard-gradle" level="project" />
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.11/proguard-gradle-4.11-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.11/proguard-base-4.11-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-core-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-model-core-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-model-groovy-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/asm-all-5.0.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/ant-1.9.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/commons-collections-3.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/commons-io-1.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/commons-lang-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/logback-core-1.0.13.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/logback-classic-1.0.13.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/guava-jdk5-17.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/jcip-annotations-1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/jul-to-slf4j-1.7.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/jarjar-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/javax.inject-1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/slf4j-api-1.7.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/log4j-over-slf4j-1.7.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/jcl-over-slf4j-1.7.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/ant-launcher-1.9.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/commons-collections-3.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/commons-io-1.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/commons-lang-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-docs-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-launcher-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-base-services-groovy-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-base-services-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-resources-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-cli-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-native-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-open-api-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-ui-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/jna-3.2.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/native-platform-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/jansi-1.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/native-platform-osx-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/native-platform-osx-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/native-platform-linux-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/native-platform-linux-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/native-platform-windows-amd64-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/native-platform-windows-i386-0.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-messaging-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/kryo-2.20.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/reflectasm-1.07-shaded.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/minlog-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/objenesis-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-settings-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-repository-metadata-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-plexus-container-default-1.5.5.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-aether-provider-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-wagon-provider-api-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-plexus-cipher-1.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-plexus-interpolation-1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-plexus-utils-2.0.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-plexus-classworlds-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-plugin-api-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-model-builder-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-plexus-sec-dispatcher-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-plexus-component-annotations-1.5.5.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-aether-connector-wagon-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-compat-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-wagon-http-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-aether-api-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-settings-builder-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-aether-spi-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-core-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-wagon-http-shared4-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-aether-util-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-artifact-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-maven-model-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jarjar-aether-impl-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/maven-ant-tasks-2.1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/nekohtml-1.9.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/xbean-reflect-3.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/xml-apis-1.3.04.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/xercesImpl-2.10.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-tooling-api-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-plugins-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/junit-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/testng-6.3.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/commons-cli-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/bsh-2.0b4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/snakeyaml-1.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/hamcrest-core-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-code-quality-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-jetty-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jetty-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jetty-util-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/servlet-api-2.5-20081211.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jetty-plus-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jsp-2.1-6.1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jetty-annotations-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/geronimo-annotation_1.0_spec-1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jetty-naming-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/core-3.1.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jsp-api-2.1-6.1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-antlr-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-dependency-management-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-ide-native-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-language-groovy-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-language-java-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-language-jvm-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-language-native-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-platform-base-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-platform-jvm-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-platform-native-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-plugin-development-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-plugin-use-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/gradle-wrapper-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-osgi-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/bndlib-2.1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-maven-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/pmaven-common-0.8-20100325.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/pmaven-groovy-0.8-20100325.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/plexus-component-annotations-1.5.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-ide-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-announce-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-scala-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-sonar-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/sonar-batch-bootstrapper-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-signing-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-ear-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-javascript-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/rhino-1.7R3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gson-2.2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/simple-4.1.21.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-build-comparison-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-diagnostics-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-reporting-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/jatl-0.2.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-publish-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-ivy-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-jacoco-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-build-init-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../out/alternate-gradle/gradle-2.10/lib/plugins/gradle-language-jvm-2.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="builder" exported="" />
+ <orderEntry type="module" module-name="lint-cli" exported="" />
+ <orderEntry type="library" name="groovy" level="project" />
+ <orderEntry type="module" module-name="gradle-core" />
+ <orderEntry type="module" module-name="sdk-common-base" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/AbstractNativeDependentSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/AbstractNativeDependentSourceSet.java
new file mode 100644
index 0000000..131d9a6
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/AbstractNativeDependentSourceSet.java
@@ -0,0 +1,43 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.build.gradle.internal.dependency.AndroidNativeDependencySpecContainer;
+import com.android.build.gradle.model.NativeDependentSourceSet;
+
+import org.gradle.api.Action;
+import org.gradle.language.base.sources.BaseLanguageSourceSet;
+
+/**
+ * LanguageSourceSet supporting native dependency on project with NDK component.
+ */
+public abstract class AbstractNativeDependentSourceSet extends BaseLanguageSourceSet
+ implements NativeDependentSourceSet {
+
+ private final AndroidNativeDependencySpecContainer dependencyContainer =
+ new DefaultAndroidNativeDependencySpecContainer();
+
+ @Override
+ public boolean getMayHaveSources() {
+ return super.getMayHaveSources() || !dependencyContainer.isEmpty();
+ }
+
+ @Override
+ public AndroidNativeDependencySpecContainer getDependencies() {
+ return dependencyContainer;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/AndroidConfigHelper.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/AndroidConfigHelper.java
new file mode 100644
index 0000000..e220da5
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/AndroidConfigHelper.java
@@ -0,0 +1,132 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.internal.coverage.JacocoOptions;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.dsl.AdbOptions;
+import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.dsl.LintOptions;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.dsl.Splits;
+import com.android.build.gradle.internal.dsl.TestOptions;
+import com.android.build.gradle.managed.AndroidConfig;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.core.LibraryRequest;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.builder.testing.api.TestServer;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * Utility functions for initializing an AndroidConfig.
+ */
+public class AndroidConfigHelper {
+ public static void configure(
+ @NonNull AndroidConfig model,
+ @NonNull Instantiator instantiator) {
+ model.setDefaultPublishConfig(BuilderConstants.RELEASE);
+ model.setPublishNonDefault(false);
+ model.setGeneratePureSplits(false);
+ model.setDeviceProviders(Lists.<DeviceProvider>newArrayList());
+ model.setTestServers(Lists.<TestServer>newArrayList());
+ model.setAaptOptions(instantiator.newInstance(AaptOptions.class));
+ model.setDexOptions(instantiator.newInstance(DexOptions.class));
+ model.setLintOptions(instantiator.newInstance(LintOptions.class));
+ model.setTestOptions(instantiator.newInstance(TestOptions.class));
+ model.setCompileOptions(instantiator.newInstance(CompileOptions.class));
+ model.setPackagingOptions(instantiator.newInstance(PackagingOptions.class));
+ model.setJacoco(instantiator.newInstance(JacocoOptions.class));
+ model.setAdbOptions(instantiator.newInstance(AdbOptions.class));
+ model.setSplits(instantiator.newInstance(Splits.class, instantiator));
+ model.setLibraryRequests(Lists.<LibraryRequest>newArrayList());
+ }
+
+
+ public static NamedDomainObjectContainer<AndroidSourceSet> createSourceSetsContainer(
+ @NonNull final Project project,
+ @NonNull Instantiator instantiator,
+ final boolean isLibrary) {
+ NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer = project.container(
+ AndroidSourceSet.class,
+ new AndroidSourceSetFactory(instantiator, project, isLibrary));
+
+ sourceSetsContainer.whenObjectAdded(new Action<AndroidSourceSet>() {
+ @Override
+ public void execute(AndroidSourceSet sourceSet) {
+ ConfigurationContainer configurations = project.getConfigurations();
+
+ createConfiguration(
+ configurations,
+ sourceSet.getCompileConfigurationName(),
+ "Classpath for compiling the ${sourceSet.name} sources.");
+
+ String packageConfigDescription;
+ if (isLibrary) {
+ packageConfigDescription
+ = "Classpath only used when publishing '${sourceSet.name}'.";
+ } else {
+ packageConfigDescription
+ = "Classpath packaged with the compiled '${sourceSet.name}' classes.";
+ }
+ createConfiguration(
+ configurations,
+ sourceSet.getPackageConfigurationName(),
+ packageConfigDescription);
+
+ createConfiguration(
+ configurations,
+ sourceSet.getProvidedConfigurationName(),
+ "Classpath for only compiling the ${sourceSet.name} sources.");
+
+ createConfiguration(
+ configurations,
+ sourceSet.getWearAppConfigurationName(),
+ "Link to a wear app to embed for object '${sourceSet.name}'.");
+
+ sourceSet.setRoot(String.format("src/%s", sourceSet.getName()));
+
+ }
+ });
+ return sourceSetsContainer;
+ }
+
+ private static void createConfiguration(
+ @NonNull ConfigurationContainer configurations,
+ @NonNull String configurationName,
+ @NonNull String configurationDescription) {
+ Configuration configuration = configurations.findByName(configurationName);
+ if (configuration == null) {
+ configuration = configurations.create(configurationName);
+ }
+
+ // Disable modification to configurations as this causes issues when accessed through the
+ // tooling-api. Check that it works with Studio's ImportProjectAction before re-enabling
+ // them.
+ //configuration.setVisible(false);
+ //configuration.setDescription(configurationDescription);
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/DefaultAndroidNativeDependencySpecContainer.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/DefaultAndroidNativeDependencySpecContainer.java
new file mode 100644
index 0000000..ac618cb
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/DefaultAndroidNativeDependencySpecContainer.java
@@ -0,0 +1,107 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.build.gradle.internal.dependency.AndroidNativeDependencySpec;
+import com.android.build.gradle.internal.dependency.AndroidNativeDependencySpecContainer;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Action;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Implementation of {@link AndroidNativeDependencySpecContainer}
+ */
+public class DefaultAndroidNativeDependencySpecContainer implements
+ AndroidNativeDependencySpecContainer {
+
+ private final List<AndroidNativeDependencySpec.Builder> builders =
+ new LinkedList<AndroidNativeDependencySpec.Builder>();
+
+ @Override
+ public AndroidNativeDependencySpec.Builder project(final String value) {
+ return doCreate(new Action<AndroidNativeDependencySpec.Builder>() {
+ @Override
+ public void execute(AndroidNativeDependencySpec.Builder builder) {
+ builder.project(value);
+ }
+ });
+ }
+
+ @Override
+ public AndroidNativeDependencySpec.Builder library(final String value) {
+ return doCreate(new Action<AndroidNativeDependencySpec.Builder>() {
+ @Override
+ public void execute(AndroidNativeDependencySpec.Builder builder) {
+ builder.library(value);
+ }
+ });
+ }
+
+ @Override
+ public AndroidNativeDependencySpec.Builder buildType(final String value) {
+ return doCreate(new Action<AndroidNativeDependencySpec.Builder>() {
+ @Override
+ public void execute(AndroidNativeDependencySpec.Builder builder) {
+ builder.buildType(value);
+ }
+ });
+ }
+
+ @Override
+ public AndroidNativeDependencySpec.Builder productFlavor(final String value) {
+ return doCreate(new Action<AndroidNativeDependencySpec.Builder>() {
+ @Override
+ public void execute(AndroidNativeDependencySpec.Builder builder) {
+ builder.productFlavor(value);
+ }
+ });
+ }
+
+ @Override
+ public Collection<AndroidNativeDependencySpec> getDependencies() {
+ if (builders.isEmpty()) {
+ return Collections.emptySet();
+ }
+ return ImmutableSet.copyOf(Lists.transform(builders,
+ new Function<AndroidNativeDependencySpec.Builder, AndroidNativeDependencySpec>() {
+ @Override
+ public AndroidNativeDependencySpec apply(AndroidNativeDependencySpec.Builder builder) {
+ return builder.build();
+ }
+ }));
+ }
+
+ private AndroidNativeDependencySpec.Builder doCreate(
+ Action<? super AndroidNativeDependencySpec.Builder> action) {
+ AndroidNativeDependencySpec.Builder builder = new AndroidNativeDependencySpec.Builder();
+ action.execute(builder);
+ builders.add(builder);
+ return builder;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return builders.isEmpty();
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/JniLibsLanguageTransform.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/JniLibsLanguageTransform.java
new file mode 100644
index 0000000..01f066a
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/JniLibsLanguageTransform.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.dependency.AndroidNativeDependencySpec;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolveResult;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolver;
+import com.android.build.gradle.internal.dependency.NativeLibraryArtifact;
+import com.android.build.gradle.model.AndroidBinary;
+import com.android.build.gradle.model.JniLibsSourceSet;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.build.gradle.tasks.StripDependenciesTask;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.SourceTransformTaskConfig;
+import org.gradle.language.base.internal.registry.LanguageTransform;
+import org.gradle.nativeplatform.NativeLibraryBinary;
+import org.gradle.nativeplatform.SharedLibraryBinary;
+import org.gradle.platform.base.BinarySpec;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * {@link LanguageTransform} for {@link JniLibsSourceSet}.
+ *
+ * The transform creates a task that strips or copy the dependencies into a folder that will be
+ * packaged into the APK.
+ */
+public class JniLibsLanguageTransform implements LanguageTransform<JniLibsSourceSet, SharedObjectFile> {
+
+ private NdkHandler ndkHandler;
+
+ public JniLibsLanguageTransform(NdkHandler ndkHandler) {
+ this.ndkHandler = ndkHandler;
+ }
+
+ @Override
+ public Class<JniLibsSourceSet> getSourceSetType() {
+ return JniLibsSourceSet.class;
+ }
+
+ @Override
+ public Class<SharedObjectFile> getOutputType() {
+ return SharedObjectFile.class;
+ }
+
+ @Override
+ public Map<String, Class<?>> getBinaryTools() {
+ return ImmutableMap.of();
+ }
+
+ @Override
+ public SourceTransformTaskConfig getTransformTask() {
+ return new TransformConfig();
+ }
+
+ @Override
+ public boolean applyToBinary(BinarySpec binary) {
+ return binary instanceof AndroidBinary;
+ }
+
+ private class TransformConfig implements SourceTransformTaskConfig {
+
+ @Override
+ public String getTaskPrefix() {
+ return "process";
+ }
+
+ @Override
+ public Class<? extends DefaultTask> getTaskType() {
+ return StripDependenciesTask.class;
+ }
+
+ @Override
+ public void configureTask(
+ Task task,
+ BinarySpec binarySpec,
+ LanguageSourceSet languageSourceSet,
+ ServiceRegistry serviceRegistry) {
+ AndroidBinaryInternal binary = (AndroidBinaryInternal) binarySpec;
+
+ String binaryBuildType = binary.getBuildType().getName();
+ String binaryProductFlavor =
+ ProductFlavorCombo.getFlavorComboName(binary.getProductFlavors());
+
+ JniLibsSourceSet sourceSet = (JniLibsSourceSet) languageSourceSet;
+
+ for (final AndroidNativeDependencySpec dependencySpec :
+ sourceSet.getDependencies().getDependencies()) {
+ dependencySpec.validate();
+ if (dependencySpec.getLinkage() != null) {
+ throw new InvalidUserDataException(
+ "Cannot specify linkage for native dependency for jniLibs.");
+ }
+ }
+ NativeDependencyResolveResult dependencies =
+ new NativeDependencyResolver(
+ serviceRegistry,
+ sourceSet.getDependencies(),
+ new AndroidNativeDependencySpec(
+ null,
+ null,
+ binaryBuildType,
+ binaryProductFlavor,
+ NativeDependencyLinkage.SHARED)).resolve();
+
+ Map<File, Abi> inputFiles = Maps.newHashMap();
+ StripDependenciesTask stripTask = (StripDependenciesTask) task;
+
+ for (NativeLibraryArtifact artifacts: dependencies.getNativeArtifacts()) {
+ final String abi = artifacts.getAbi();
+ assert abi != null;
+
+ if (binary.getMergedNdkConfig().getAbiFilters().contains(abi)) {
+ stripTask.dependsOn(artifacts.getBuiltBy());
+
+ // Debug libraries created from another subproject may have debug symbols,
+ // therefore, the library is stripped before packaging.
+ for (File output : artifacts.getLibraries()) {
+ if (output.getName().endsWith(".so")) {
+ inputFiles.put(output, Abi.getByName(abi));
+ }
+ }
+ }
+ }
+
+ Multimap<File, Abi> stripedFiles = ArrayListMultimap.create();
+ for (final NativeLibraryBinary nativeBinary : dependencies.getPrebuiltLibraries()) {
+ // For dependency on a library file, there is no way to know if it contains debug
+ // symbol, and NDK may not not be set. We may not have access to the strip tool,
+ // therefore, we assume the library do not have debug symbols and simply copy the
+ // file.
+ if (nativeBinary instanceof SharedLibraryBinary) {
+ File output = ((SharedLibraryBinary) nativeBinary).getSharedLibraryFile();
+ Abi abi = Abi.getByName(nativeBinary.getTargetPlatform().getName());
+ if (output != null && abi != null) {
+ stripedFiles.put(output, abi);
+ }
+ }
+ }
+
+ new StripDependenciesTask.ConfigAction(binary.getBuildType().getName(),
+ ProductFlavorCombo.getFlavorComboName(binary.getProductFlavors()),
+ inputFiles,
+ stripedFiles,
+ task.getProject().getBuildDir(),
+ ndkHandler).execute(stripTask);
+
+
+ binarySpec.builtBy(task);
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/LanguageRegistryUtils.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/LanguageRegistryUtils.java
new file mode 100644
index 0000000..26eb68e
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/LanguageRegistryUtils.java
@@ -0,0 +1,38 @@
+/*
+ * 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.build.gradle.internal;
+
+import org.gradle.language.base.internal.registry.LanguageRegistry;
+import org.gradle.language.base.internal.registry.LanguageRegistration;
+
+/**
+ * Utitities for handling {@link LanguageRegistry}.
+ */
+public class LanguageRegistryUtils {
+ public static LanguageRegistration find(LanguageRegistry languageRegistry, Class sourceSetClass) {
+ for (LanguageRegistration languageRegistration : languageRegistry) {
+ if (languageRegistration.getSourceSetType().equals(sourceSetClass)) {
+ return languageRegistration;
+ }
+ }
+ // should not happen
+ throw new RuntimeException(
+ "Failed to find language registration for " + sourceSetClass.getName());
+ }
+
+
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/NativeDependencyLinkage.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/NativeDependencyLinkage.java
new file mode 100644
index 0000000..97f388d
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/NativeDependencyLinkage.java
@@ -0,0 +1,39 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Enumeration for describing dependency linkage.
+ */
+public enum NativeDependencyLinkage {
+ SHARED("shared"),
+ STATIC("static");
+
+ @NonNull
+ private String name;
+
+ NativeDependencyLinkage(@NonNull String name) {
+ this.name = name;
+ }
+
+ @NonNull
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/NdkOptionsHelper.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/NdkOptionsHelper.java
new file mode 100644
index 0000000..90629a1
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/NdkOptionsHelper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.build.gradle.managed.NdkBuildType;
+import com.android.build.gradle.managed.NdkOptions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+/**
+ * Helper functions for configuring an NdkOptions.
+ */
+public class NdkOptionsHelper {
+
+ /**
+ * Merge one NdkOptions to another.
+ * @param base NdkOptions to merge to. base is mutated to contain the merged result.
+ * @param other NdkOptions to merge. Options in other has priority over base if both are set.
+ */
+ public static void merge(NdkOptions base, NdkOptions other) {
+ if (other.getModuleName() != null) {
+ base.setModuleName(other.getModuleName());
+ }
+
+ base.getAbiFilters().addAll(other.getAbiFilters());
+ base.getCFlags().addAll(other.getCFlags());
+ base.getCppFlags().addAll(other.getCppFlags());
+ base.getLdFlags().addAll(other.getLdFlags());
+ base.getLdLibs().addAll(other.getLdLibs());
+
+ if (other.getStl() != null) {
+ base.setStl(other.getStl());
+ }
+ if (other.getStlVersion() != null) {
+ base.setStlVersion(other.getStlVersion());
+ }
+ if (other.getRenderscriptNdkMode() != null) {
+ base.setRenderscriptNdkMode(other.getRenderscriptNdkMode());
+ }
+ }
+
+ /**
+ * Merge one NdkBuildType to another
+ * @param base NdkBuildType to merge to. base is mutated to contain the merged result.
+ * @param other NdkBuildType to merge. Options in other has priority over base if both are set.
+ */
+ public static void merge(NdkBuildType base, NdkBuildType other) {
+ merge(base, (NdkOptions) other);
+ if (other.getDebuggable() != null) {
+ base.setDebuggable(other.getDebuggable());
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/SharedObjectFile.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/SharedObjectFile.java
new file mode 100644
index 0000000..98f5620
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/SharedObjectFile.java
@@ -0,0 +1,25 @@
+/*
+ * 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.build.gradle.internal;
+
+import org.gradle.platform.base.TransformationFileType;
+
+/**
+ * A TransformationFileType representing a .so file.
+ */
+public class SharedObjectFile implements TransformationFileType {
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/AndroidNativeDependencySpec.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/AndroidNativeDependencySpec.java
new file mode 100644
index 0000000..3e11e12
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/AndroidNativeDependencySpec.java
@@ -0,0 +1,157 @@
+/*
+ * 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.build.gradle.internal.dependency;
+
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.NativeDependencyLinkage;
+
+import org.gradle.api.InvalidUserDataException;
+
+import java.io.File;
+import java.util.Locale;
+
+/**
+ * Specification for native dependencies.
+ *
+ * This class provide different types of native dependencies. Supported dependencies are:
+ *
+ * 1) Project dependency
+ * Project dependency can be specified by setting the projectPath to the target Gradle project.
+ * The target Gradle project must apply any of Gradle's component model plugin.
+ * buildType and productFlavor can be optionally set to specified a build type or product flavor
+ * respectively on the target project. If unset, the build type and product flavor of the current
+ * project will be use.
+ *
+ * 2) File dependency
+ * User can set dependency on a specific library file by setting the libraryPath. The abi must also
+ * be set to indicate what ABI the target file is for.
+ */
+public class AndroidNativeDependencySpec {
+
+ @Nullable
+ private final String projectPath;
+ @Nullable
+ private final String libraryPath;
+ @Nullable
+ private final String buildType;
+ @Nullable
+ private final String productFlavor;
+ @Nullable
+ private final NativeDependencyLinkage linkage;
+
+ public AndroidNativeDependencySpec(
+ @Nullable String projectPath,
+ @Nullable String libraryPath,
+ @Nullable String buildType,
+ @Nullable String productFlavor,
+ @Nullable NativeDependencyLinkage linkage) {
+ this.projectPath = projectPath;
+ this.libraryPath = libraryPath;
+ this.buildType = buildType;
+ this.productFlavor = productFlavor;
+ this.linkage = linkage;
+ }
+
+ @Nullable
+ public String getProjectPath() {
+ return projectPath;
+ }
+
+ @Nullable
+ public String getLibraryPath() {
+ return libraryPath;
+ }
+
+ @Nullable
+ public String getBuildType() {
+ return buildType;
+ }
+
+ @Nullable
+ public String getProductFlavor() {
+ return productFlavor;
+ }
+
+ @Nullable
+ public NativeDependencyLinkage getLinkage() {
+ return linkage;
+ }
+
+ public void validate() {
+ if (projectPath == null && libraryPath == null) {
+ throw new InvalidUserDataException(
+ "Native dependency must contain either project or library.");
+ }
+ if (projectPath != null && libraryPath != null) {
+ throw new InvalidUserDataException(
+ "Native dependency cannot contain both project and library");
+ }
+ }
+
+ public static class Builder {
+
+ @Nullable
+ private String projectPath;
+ @Nullable
+ private String libraryPath;
+ @Nullable
+ private String buildType;
+ @Nullable
+ private String productFlavor;
+ @Nullable
+ NativeDependencyLinkage linkage;
+
+ public Builder project(String value) {
+ projectPath = value;
+ return this;
+ }
+
+ public Builder library(String value) {
+ libraryPath = value;
+ return this;
+ }
+
+ public Builder buildType(String value) {
+ buildType = value;
+ return this;
+ }
+
+ public Builder productFlavor(String value) {
+ productFlavor = value;
+ return this;
+ }
+
+ public Builder linkage(String value) {
+ linkage = NativeDependencyLinkage.valueOf(value.toUpperCase(Locale.US));
+ return this;
+ }
+
+ public Builder linkage(NativeDependencyLinkage value) {
+ linkage = value;
+ return this;
+ }
+
+ public AndroidNativeDependencySpec build() {
+ return new AndroidNativeDependencySpec(
+ projectPath,
+ libraryPath,
+ buildType,
+ productFlavor,
+ linkage);
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/AndroidNativeDependencySpecContainer.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/AndroidNativeDependencySpecContainer.java
new file mode 100644
index 0000000..90aeace
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/AndroidNativeDependencySpecContainer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dependency;
+
+import org.gradle.model.internal.core.UnmanagedStruct;
+import org.gradle.platform.base.DependencySpecContainer;
+
+import java.util.Collection;
+
+/**
+ * Container for {@link AndroidNativeDependencySpec}.
+ *
+ * Code is based on {@link DependencySpecContainer}. It should be removed when Gradle support more
+ * flexibility in defining dependency specs.
+ */
+ at UnmanagedStruct
+public interface AndroidNativeDependencySpecContainer {
+
+ AndroidNativeDependencySpec.Builder project(final String value);
+
+ AndroidNativeDependencySpec.Builder library(final String value);
+
+ AndroidNativeDependencySpec.Builder buildType(final String value);
+
+ AndroidNativeDependencySpec.Builder productFlavor(final String value);
+
+ Collection<AndroidNativeDependencySpec> getDependencies();
+
+ boolean isEmpty();
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/ArtifactContainer.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/ArtifactContainer.java
new file mode 100644
index 0000000..aa0ec5f
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/ArtifactContainer.java
@@ -0,0 +1,28 @@
+/*
+ * 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.build.gradle.internal.dependency;
+
+import org.gradle.model.Managed;
+import org.gradle.model.ModelMap;
+
+/**
+ * Generic container for all artifacts.
+ */
+ at Managed
+public interface ArtifactContainer {
+ ModelMap<NativeLibraryArtifact> getNativeArtifacts();
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeDependencyResolveResult.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeDependencyResolveResult.java
new file mode 100644
index 0000000..43ec337
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeDependencyResolveResult.java
@@ -0,0 +1,48 @@
+/*
+ * 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.build.gradle.internal.dependency;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.gradle.nativeplatform.NativeLibraryBinary;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Result of resolving dependencies for a native project.
+ */
+public class NativeDependencyResolveResult {
+
+ @NonNull
+ private Collection<NativeLibraryArtifact> nativeArtifacts = Lists.newArrayList();
+
+ @NonNull
+ private Set<NativeLibraryBinary> prebuiltLibraries = Sets.newHashSet();
+
+ @NonNull
+ public Collection<NativeLibraryArtifact> getNativeArtifacts() {
+ return nativeArtifacts;
+ }
+
+ @NonNull
+ public Set<NativeLibraryBinary> getPrebuiltLibraries() {
+ return prebuiltLibraries;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeDependencyResolver.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeDependencyResolver.java
new file mode 100644
index 0000000..38f6c98
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeDependencyResolver.java
@@ -0,0 +1,257 @@
+/*
+ * 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.build.gradle.internal.dependency;
+
+import static com.android.build.gradle.model.ModelConstants.ARTIFACTS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.NativeDependencyLinkage;
+import com.android.utils.StringHelper;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.internal.resolve.ProjectModelResolver;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.language.base.internal.resolve.LibraryResolveException;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.nativeplatform.NativeLibraryBinary;
+import org.gradle.nativeplatform.PrebuiltLibraries;
+import org.gradle.nativeplatform.PrebuiltLibrary;
+import org.gradle.nativeplatform.Repositories;
+import org.gradle.nativeplatform.SharedLibraryBinary;
+import org.gradle.nativeplatform.StaticLibraryBinary;
+import org.gradle.nativeplatform.internal.prebuilt.PrebuiltLibraryResolveException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Resolver for determining native dependencies based on {@link AndroidNativeDependencySpec}
+ */
+public class NativeDependencyResolver {
+ @NonNull
+ private ServiceRegistry serviceRegistry;
+ @NonNull
+ private AndroidNativeDependencySpecContainer dependencyContainer;
+ @NonNull
+ private AndroidNativeDependencySpec defaultDependencySpec;
+
+ public NativeDependencyResolver(
+ @NonNull ServiceRegistry serviceRegistry,
+ @NonNull AndroidNativeDependencySpecContainer dependencyContainer,
+ @NonNull AndroidNativeDependencySpec defaultDependencySpec) {
+ this.serviceRegistry = serviceRegistry;
+ this.dependencyContainer = dependencyContainer;
+ this.defaultDependencySpec = defaultDependencySpec;
+ }
+
+ /**
+ * Determine native dependencies from all {@link AndroidNativeDependencySpec}.
+ */
+ @NonNull
+ public NativeDependencyResolveResult resolve() {
+ NativeDependencyResolveResult result = new NativeDependencyResolveResult();
+ for (final AndroidNativeDependencySpec dependency : dependencyContainer.getDependencies()) {
+ if (dependency.getProjectPath() != null) {
+ result.getNativeArtifacts().addAll(resolveForNativeBinaries(dependency));
+ } else if (dependency.getLibraryPath() != null) {
+ resolveForPrebuiltLibraries(result, dependency);
+ }
+ }
+
+ return result;
+ }
+
+ private void resolveForPrebuiltLibraries(
+ NativeDependencyResolveResult result,
+ AndroidNativeDependencySpec dependency) {
+ NativeDependencyLinkage linkage =
+ Objects.firstNonNull(dependency.getLinkage(), defaultDependencySpec.getLinkage());
+ for (NativeLibraryBinary binary : getBinaries(dependency.getLibraryPath())) {
+ if (linkage.equals(NativeDependencyLinkage.STATIC)) {
+ if (binary instanceof StaticLibraryBinary
+ && ((StaticLibraryBinary) binary).getStaticLibraryFile() != null) {
+ result.getPrebuiltLibraries().add(binary);
+ }
+ } else {
+ if (binary instanceof SharedLibraryBinary
+ && ((SharedLibraryBinary) binary).getSharedLibraryFile() != null) {
+ result.getPrebuiltLibraries().add(binary);
+ }
+ }
+ }
+ }
+
+ private Collection<NativeLibraryBinary> getBinaries(String library) {
+ ModelRegistry projectModel = serviceRegistry.get(ModelRegistry.class);
+ NamedDomainObjectSet<PrebuiltLibraries> repositories = projectModel.realize(
+ ModelPath.path("repositories"),
+ ModelType.of(Repositories.class)).withType(PrebuiltLibraries.class);
+ projectModel.realize(
+ ModelPath.path("repositories"),
+ ModelType.of(Repositories.class));
+ if (repositories.isEmpty()) {
+ throw new PrebuiltLibraryResolveException(
+ "Project does not have any prebuilt library repositories.");
+ }
+ PrebuiltLibrary prebuiltLibrary = getPrebuiltLibrary(repositories, library);
+
+ return prebuiltLibrary.getBinaries();
+ }
+
+ private static PrebuiltLibrary getPrebuiltLibrary(
+ NamedDomainObjectSet<PrebuiltLibraries> repositories, String libraryName) {
+ List<String> repositoryNames = new ArrayList<String>();
+ for (PrebuiltLibraries prebuiltLibraries : repositories) {
+ repositoryNames.add(prebuiltLibraries.getName());
+ PrebuiltLibrary prebuiltLibrary = prebuiltLibraries.resolveLibrary(libraryName);
+ if (prebuiltLibrary != null) {
+ return prebuiltLibrary;
+ }
+ }
+ throw new PrebuiltLibraryResolveException(
+ String.format(
+ "Prebuilt library with name '%s' not found in repositories '%s'.",
+ libraryName,
+ repositoryNames));
+ }
+
+ /**
+ * Find all NativeLibraryArtifact matching the dependency spec.
+ */
+ @NonNull
+ private Collection<NativeLibraryArtifact> resolveForNativeBinaries(
+ AndroidNativeDependencySpec dependency) {
+ String project = dependency.getProjectPath();
+
+ // Find ArtifactContainer model in project.
+ ProjectModelResolver projectModelResolver = serviceRegistry.get(ProjectModelResolver.class);
+ ModelRegistry projectModel = projectModelResolver.resolveProjectModel(project);
+ ArtifactContainer artifactContainer = projectModel.find(
+ ModelPath.path(ARTIFACTS),
+ ModelType.of(ArtifactContainer.class));
+ if (artifactContainer == null) {
+ throw new InvalidUserDataException(
+ "Project '" + project + "' does not export native artifacts");
+ }
+
+ // We filter the artifacts twice.
+ // The first time filters according to user's dependency spec. If there is any ambiguity,
+ // we filter again to using the supplied default values.
+
+ Collection<NativeLibraryArtifact> matches = filter(
+ artifactContainer.getNativeArtifacts().values(),
+ dependency.getBuildType(),
+ dependency.getProductFlavor(),
+ dependency.getLinkage());
+
+ String buildType = findUniqueBuildTypeCount(matches) > 1
+ ? defaultDependencySpec.getBuildType()
+ : null;
+ String productFlavor = findUniqueProductFlavorCount(matches) > 1
+ ? defaultDependencySpec.getProductFlavor()
+ : null;
+ NativeDependencyLinkage linkage = findUniqueLinkageCount(matches) > 1
+ ? defaultDependencySpec.getLinkage()
+ : null;
+
+ Collection<NativeLibraryArtifact> libraries =
+ filter(matches, buildType, productFlavor, linkage);
+
+ if (libraries.isEmpty()) {
+ throw new LibraryResolveException(
+ String.format(
+ "Unable to find Android binary with buildType '%s' and productFlavor "
+ + "'%s' in project '%s'",
+ buildType,
+ productFlavor,
+ project));
+ }
+ return libraries;
+ }
+
+
+ /**
+ * Find the number of unique build types.
+ */
+ private static int findUniqueBuildTypeCount(Iterable<NativeLibraryArtifact> artifacts) {
+ Set<String> unique = Sets.newHashSet();
+ for (NativeLibraryArtifact artifact : artifacts) {
+ unique.add(artifact.getBuildType() == null ? null : artifact.getBuildType());
+ }
+ return unique.size();
+ }
+
+ /**
+ * Find the number of unique product flavors.
+ */
+ private static int findUniqueProductFlavorCount(Iterable<NativeLibraryArtifact> artifacts) {
+ Set<List<String>> unique = Sets.newHashSet();
+ for (NativeLibraryArtifact artifact : artifacts) {
+ unique.add(artifact.getProductFlavors() == null ? null : artifact.getProductFlavors());
+ }
+ return unique.size();
+ }
+
+ /**
+ * Find the number of unique linkage.
+ */
+ private static int findUniqueLinkageCount(Iterable<NativeLibraryArtifact> artifacts) {
+ Set<NativeDependencyLinkage> unique = Sets.newHashSet();
+ for (NativeLibraryArtifact artifact : artifacts) {
+ unique.add(artifact.getLinkage() == null ? null : artifact.getLinkage());
+ }
+ return unique.size();
+ }
+
+ /**
+ * Filter artifacts with the specified build type, product flavor and linkage.
+ * Null implies we don't care about the value.
+ */
+ private static Collection<NativeLibraryArtifact> filter(
+ @NonNull Collection<NativeLibraryArtifact> artifacts,
+ @Nullable String buildType,
+ @Nullable String productFlavor,
+ @Nullable NativeDependencyLinkage linkage) {
+ if (buildType == null && productFlavor == null && linkage == null) {
+ // Just return if all filters are null for performance.
+ return artifacts;
+ }
+
+ ImmutableList.Builder<NativeLibraryArtifact> builder = ImmutableList.builder();
+ for (NativeLibraryArtifact artifact : artifacts) {
+ if ((buildType == null || buildType.equals(artifact.getBuildType()))
+ && (productFlavor == null
+ || productFlavor.equals(
+ StringHelper.combineAsCamelCase(artifact.getProductFlavors())))
+ && (linkage == null
+ || linkage.equals(artifact.getLinkage())
+ || artifact.getLinkage() == null)) {
+ builder.add(artifact);
+ }
+ }
+ return builder.build();
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeLibraryArtifact.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeLibraryArtifact.java
new file mode 100644
index 0000000..cd96ec8
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeLibraryArtifact.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dependency;
+
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.NativeDependencyLinkage;
+
+import org.gradle.api.Named;
+import org.gradle.model.Managed;
+import org.gradle.model.Unmanaged;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Container for all native library artifacts.
+ */
+ at Managed
+public interface NativeLibraryArtifact extends Named {
+
+ /**
+ * List of libraries in this artifact.
+ */
+ List<File> getLibraries();
+
+ /**
+ * Folders contain the source files used to generate the libraries.
+ */
+ List<File> getSrcFolders();
+
+ /**
+ * Folders container the public headers for the libraries.
+ */
+ Set<File> getExportedHeaderDirectories();
+
+ /**
+ * Build type of the artifact.
+ */
+ @Nullable
+ String getBuildType();
+ void setBuildType(String buildType);
+
+ /**
+ * Product flavors of the artifact.
+ */
+ List<String> getProductFlavors();
+
+ /**
+ * Variant name of the artifact.
+ */
+ @Nullable
+ String getVariantName();
+ void setVariantName(String variantName);
+
+ /**
+ * Target ABI of the artfiact.
+ */
+ @Nullable
+ String getAbi();
+ void setAbi(String abi);
+
+ /**
+ *
+ */
+ @Nullable
+ NativeDependencyLinkage getLinkage();
+ void setLinkage(NativeDependencyLinkage linkage);
+
+ @Unmanaged
+ List<Object> getBuiltBy();
+ void setBuiltBy(List<Object> binary);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeLibraryArtifactAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeLibraryArtifactAdaptor.java
new file mode 100644
index 0000000..3fc4be7
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/dependency/NativeLibraryArtifactAdaptor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.build.gradle.internal.dependency;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.ProductFlavorCombo;
+import com.android.utils.StringHelper;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.nativeplatform.BuildType;
+import org.gradle.nativeplatform.Flavor;
+import org.gradle.nativeplatform.NativeLibraryBinary;
+import org.gradle.nativeplatform.internal.DefaultBuildType;
+import org.gradle.nativeplatform.internal.DefaultFlavor;
+import org.gradle.nativeplatform.platform.NativePlatform;
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform;
+
+/**
+ * Adaptor for {@link NativeLibraryArtifact} to {@link NativeLibraryBinary}
+ *
+ * Used for adding artifact dependency to a NativeLibraryBinary.
+ */
+public class NativeLibraryArtifactAdaptor implements NativeLibraryBinary {
+ @NonNull
+ NativeLibraryArtifact artifact;
+
+ public NativeLibraryArtifactAdaptor(@NonNull NativeLibraryArtifact artifact) {
+ this.artifact = artifact;
+ }
+
+ @Override
+ public FileCollection getHeaderDirs() {
+ return new SimpleFileCollection(artifact.getExportedHeaderDirectories());
+ }
+
+ @Override
+ public FileCollection getLinkFiles() {
+ return new SimpleFileCollection(artifact.getLibraries());
+ }
+
+ @Override
+ public FileCollection getRuntimeFiles() {
+ return new SimpleFileCollection(artifact.getLibraries());
+ }
+
+ @Override
+ public Flavor getFlavor() {
+ return new DefaultFlavor(StringHelper.combineAsCamelCase(artifact.getProductFlavors()));
+ }
+
+ @Override
+ public NativePlatform getTargetPlatform() {
+ return new DefaultNativePlatform(artifact.getAbi());
+ }
+
+ @Override
+ public BuildType getBuildType() {
+ return new DefaultBuildType(artifact.getBuildType());
+ }
+
+ @Override
+ public String getDisplayName() {
+ return artifact.getName();
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/FileGsonTypeAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/FileGsonTypeAdaptor.java
new file mode 100644
index 0000000..2ab0629
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/FileGsonTypeAdaptor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.gson;
+
+import com.android.annotations.NonNull;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Gson Type Adaptor for File.
+ *
+ * Using Gradle {@link FileResolver} to resolve relative paths.
+ */
+public class FileGsonTypeAdaptor extends TypeAdapter<File> {
+ @NonNull
+ private final FileResolver fileResolver;
+
+ public FileGsonTypeAdaptor(@NonNull FileResolver fileResolver) {
+ this.fileResolver = fileResolver;
+ }
+
+ @Override
+ public void write(JsonWriter jsonWriter, File file) throws IOException {
+ jsonWriter.value(file.getPath());
+ }
+
+ @Override
+ public File read(JsonReader jsonReader) throws IOException {
+ String path = jsonReader.nextString();
+ return fileResolver.resolve(path);
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeBuildConfigValue.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeBuildConfigValue.java
new file mode 100644
index 0000000..697f9c0
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeBuildConfigValue.java
@@ -0,0 +1,83 @@
+/*
+ * 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.build.gradle.internal.gson;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.managed.NativeBuildConfig;
+import com.android.build.gradle.managed.NativeLibrary;
+import com.android.build.gradle.managed.NativeToolchain;
+
+import org.gradle.api.Action;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Value type for {@link NativeBuildConfig} to be used with Gson.
+ */
+public class NativeBuildConfigValue {
+
+ @Nullable
+ Collection<File> buildFiles;
+ @Nullable
+ Map<String, NativeLibraryValue> libraries;
+ @Nullable
+ Map<String, NativeToolchainValue> toolchains;
+ @Nullable
+ Collection<String> cFileExtensions;
+ @Nullable
+ Collection<String> cppFileExtensions;
+
+ /**
+ * Copies this NativeBuildConfigValue to another.
+ *
+ * If target already contains non-empty lists, values are appended.
+ */
+ public void copyTo(@NonNull NativeBuildConfig config) {
+ if (buildFiles != null) {
+ config.getBuildFiles().addAll(buildFiles);
+ }
+ if (libraries != null) {
+ for (final Map.Entry<String, NativeLibraryValue> entry : libraries.entrySet()) {
+ config.getLibraries().create(entry.getKey(), new Action<NativeLibrary>() {
+ @Override
+ public void execute(NativeLibrary nativeLibrary) {
+ entry.getValue().copyTo(nativeLibrary);
+ }
+ });
+ }
+ }
+ if (toolchains != null) {
+ for (final Map.Entry<String, NativeToolchainValue> entry : toolchains.entrySet()) {
+ config.getToolchains().create(entry.getKey(), new Action<NativeToolchain>() {
+ @Override
+ public void execute(NativeToolchain nativeToolchain) {
+ entry.getValue().copyTo(nativeToolchain);
+ }
+ });
+ }
+ }
+ if (cFileExtensions != null) {
+ config.getCFileExtensions().addAll(cFileExtensions);
+ }
+ if (cppFileExtensions != null) {
+ config.getCppFileExtensions().addAll(cppFileExtensions);
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeLibraryValue.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeLibraryValue.java
new file mode 100644
index 0000000..41f3591
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeLibraryValue.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.gson;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.managed.NativeLibrary;
+import com.android.build.gradle.managed.NativeSourceFile;
+import com.android.build.gradle.managed.NativeSourceFolder;
+
+import org.gradle.api.Action;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Value type of {@link NativeLibrary} to be used with Gson.
+ */
+public class NativeLibraryValue {
+ @Nullable
+ String executable;
+ @Nullable
+ Collection<String> args;
+ @Nullable
+ String toolchain;
+ @Nullable
+ String groupName;
+ @Nullable
+ Collection<NativeSourceFolderValue> folders;
+ @Nullable
+ Collection<NativeSourceFileValue> files;
+ @Nullable
+ Collection<File> exportedHeaders;
+ @Nullable
+ File output;
+
+ void copyTo(@NonNull NativeLibrary library) {
+ library.setExecutable(executable);
+ if (args != null) {
+ library.getArgs().clear();
+ library.getArgs().addAll(args);
+ }
+ library.setToolchain(toolchain);
+ if (folders != null) {
+ for (final NativeSourceFolderValue folder : folders) {
+ library.getFolders().create(new Action<NativeSourceFolder>() {
+ @Override
+ public void execute(NativeSourceFolder nativeSourceFolder) {
+ folder.copyTo(nativeSourceFolder);
+ }
+ });
+ }
+ }
+
+ library.setGroupName(groupName);
+
+ if (files != null) {
+ for (final NativeSourceFileValue folder : files) {
+ library.getFiles().create(new Action<NativeSourceFile>() {
+ @Override
+ public void execute(NativeSourceFile nativeSourceFolder) {
+ folder.copyTo(nativeSourceFolder);
+ }
+ });
+ }
+ }
+ if (exportedHeaders != null) {
+ library.getExportedHeaders().addAll(exportedHeaders);
+ }
+ library.setOutput(output);
+ }
+
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeSourceFileValue.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeSourceFileValue.java
new file mode 100644
index 0000000..1c1d11d
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeSourceFileValue.java
@@ -0,0 +1,45 @@
+/*
+ * 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.build.gradle.internal.gson;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.managed.NativeSourceFile;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Value type for {@link NativeSourceFile} to be used with Gson.
+ */
+public class NativeSourceFileValue {
+ @Nullable
+ File src;
+ @Nullable
+ List<String> flags;
+ @Nullable
+ File workingDirectory;
+
+ void copyTo(@NonNull NativeSourceFile file) {
+ file.setSrc(src);
+ if (flags != null) {
+ file.getFlags().clear();
+ file.getFlags().addAll(flags);
+ }
+ file.setWorkingDirectory(workingDirectory);
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeSourceFolderValue.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeSourceFolderValue.java
new file mode 100644
index 0000000..ec2dd2c
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeSourceFolderValue.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.gson;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.managed.NativeSourceFolder;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Value type for {@link NativeSourceFolder} to be used with Gson.
+ */
+public class NativeSourceFolderValue {
+ @Nullable
+ File src;
+ @Nullable
+ List<String> cFlags;
+ @Nullable
+ List<String> cppFlags;
+ @Nullable
+ File workingDirectory;
+
+ void copyTo(@NonNull NativeSourceFolder folder) {
+ folder.setSrc(src);
+ if (cFlags != null) {
+ folder.getCFlags().clear();
+ folder.getCFlags().addAll(cFlags);
+ }
+ if (cppFlags != null) {
+ folder.getCppFlags().clear();
+ folder.getCppFlags().addAll(cppFlags);
+ }
+ folder.setWorkingDirectory(workingDirectory);
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeToolchainValue.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeToolchainValue.java
new file mode 100644
index 0000000..ab9ce26
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/internal/gson/NativeToolchainValue.java
@@ -0,0 +1,41 @@
+/*
+ * 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.build.gradle.internal.gson;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.managed.NativeToolchain;
+
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+
+/**
+ * Value type for {@link NativeToolchain} to be used with Gson.
+ */
+public class NativeToolchainValue {
+ @Nullable
+ File cCompilerExecutable;
+ @Nullable
+ File cppCompilerExecutable;
+
+ public void copyTo(@NonNull NativeToolchain toolchain) {
+ toolchain.setCCompilerExecutable(cCompilerExecutable);
+ toolchain.setCppCompilerExecutable(cppCompilerExecutable);
+ }
+
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/AndroidConfig.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/AndroidConfig.java
new file mode 100644
index 0000000..b384d1a
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/AndroidConfig.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed;
+
+import com.android.build.gradle.api.VariantFilter;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.build.gradle.internal.coverage.JacocoOptions;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.dsl.AdbOptions;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.dsl.LintOptions;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.dsl.Splits;
+import com.android.build.gradle.internal.dsl.TestOptions;
+import com.android.builder.core.LibraryRequest;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.builder.testing.api.TestServer;
+import com.android.repository.Revision;
+
+import org.gradle.api.Action;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.model.Managed;
+import org.gradle.model.ModelMap;
+import org.gradle.model.Unmanaged;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Component model for all Android plugin.
+ */
+ at Managed
+public interface AndroidConfig {
+
+ /** Build tool version */
+ String getBuildToolsVersion();
+ void setBuildToolsVersion(String buildToolsVersion);
+
+ /** Compile SDK version */
+ String getCompileSdkVersion();
+ void setCompileSdkVersion(String compileSdkVersion);
+
+ /** Build tool revisions */
+ @Unmanaged
+ Revision getBuildToolsRevision();
+ void setBuildToolsRevision(Revision revision);
+
+ /** Default config, shared by all flavors. */
+ ProductFlavor getDefaultConfig();
+
+ /** NDK config for specific ABI. */
+ ModelMap<NdkAbiOptions> getAbis();
+
+ /** List of device providers */
+ @Unmanaged
+ Collection<DeviceProvider> getDeviceProviders();
+ void setDeviceProviders(Collection<DeviceProvider> providers);
+
+ /** List of remote CI servers */
+ @Unmanaged
+ Collection<TestServer> getTestServers();
+ void setTestServers(Collection<TestServer> providers);
+
+ /** Name of the variant to publish */
+ String getDefaultPublishConfig();
+ void setDefaultPublishConfig(String defaultPublishConfig);
+
+ /** Whether to also publish non-default variants */
+ Boolean getPublishNonDefault();
+ void setPublishNonDefault(Boolean publishNonDefault);
+
+ /** Filter to determine which variants to build */
+ @Unmanaged
+ Action<VariantFilter> getVariantFilter();
+ void setVariantFilter(Action<VariantFilter> filter);
+
+ /** A prefix to be used when creating new resources. Used by Studio */
+ String getResourcePrefix();
+ void setResourcePrefix(String resourcePrefix);
+
+ /** Whether to generate pure splits or multi apk */
+ Boolean getGeneratePureSplits();
+ void setGeneratePureSplits(Boolean generateSplits);
+
+ /** Build types used by this project. */
+ ModelMap<BuildType> getBuildTypes();
+
+ /** All product flavors used by this project. */
+ ModelMap<ProductFlavor> getProductFlavors();
+
+ /** Signing configs used by this project. */
+ ModelMap<SigningConfig> getSigningConfigs();
+
+ /** Android source sets. */
+ ModelMap<FunctionalSourceSet> getSources();
+
+ NdkConfig getNdk();
+
+ /** Adb options */
+ @Unmanaged
+ AdbOptions getAdbOptions();
+ void setAdbOptions(AdbOptions adbOptions);
+
+ /** Options for aapt, tool for packaging resources. */
+ @Unmanaged
+ AaptOptions getAaptOptions();
+ void setAaptOptions(AaptOptions aaptOptions);
+
+ /** Compile options */
+ @Unmanaged
+ CompileOptions getCompileOptions();
+ void setCompileOptions(CompileOptions compileOptions);
+
+ /** Dex options. */
+ @Unmanaged
+ DexOptions getDexOptions();
+ void setDexOptions(DexOptions dexOptions);
+
+ /** JaCoCo options. */
+ @Unmanaged
+ JacocoOptions getJacoco();
+ void setJacoco(JacocoOptions jacoco);
+
+ /** Lint options. */
+ @Unmanaged
+ LintOptions getLintOptions();
+ void setLintOptions(LintOptions lintOptions);
+
+ /** Packaging options. */
+ @Unmanaged
+ PackagingOptions getPackagingOptions();
+ void setPackagingOptions(PackagingOptions packagingOptions);
+
+ /** Options for running tests. */
+ @Unmanaged
+ TestOptions getTestOptions();
+ void setTestOptions(TestOptions testOptions);
+
+ /** APK splits */
+ @Unmanaged
+ Splits getSplits();
+ void setSplits(Splits splits);
+
+ @Unmanaged
+ Collection<LibraryRequest> getLibraryRequests();
+ void setLibraryRequests(Collection<LibraryRequest> libraryRequests);
+
+ Set<String> getAidlPackageWhitelist();
+
+ DataBindingOptions getDataBinding();
+}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ApiVersion.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ApiVersion.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/ApiVersion.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ApiVersion.java
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/BaseConfig.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/BaseConfig.java
new file mode 100644
index 0000000..9bd7817
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/BaseConfig.java
@@ -0,0 +1,126 @@
+/*
+ * 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.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+
+import org.gradle.api.Named;
+import org.gradle.model.Managed;
+import org.gradle.model.ModelSet;
+import org.gradle.model.Unmanaged;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Managed BaseConfig.
+ */
+ at Managed
+public interface BaseConfig extends Named {
+
+ /**
+ * Returns the application id suffix applied to this base config.
+ * To get the final application id, use {@link AndroidArtifact#getApplicationId()}.
+ *
+ * @return the application id
+ */
+ String getApplicationIdSuffix();
+ void setApplicationIdSuffix(String applicationIdSuffix);
+
+ /**
+ * Map of Build Config Fields where the key is the field name.
+ *
+ * @return a non-null map of class fields (possibly empty).
+ */
+ @NonNull
+ ModelSet<ClassField> getBuildConfigFields();
+
+ /**
+ * Map of generated res values where the key is the res name.
+ *
+ * @return a non-null map of class fields (possibly empty).
+ */
+ @NonNull
+ ModelSet<ClassField> getResValues();
+
+ /**
+ * Returns the collection of proguard rule files.
+ *
+ * <p>These files are only applied to the production code.
+ *
+ * @return a non-null collection of files.
+ * @see #getTestProguardFiles()
+ */
+ Set<File> getProguardFiles();
+
+ /**
+ * Returns the collection of proguard rule files for consumers of the library to use.
+ *
+ * @return a non-null collection of files.
+ */
+ Set<File> getConsumerProguardFiles();
+
+ /**
+ * Returns the collection of proguard rule files to use for the test APK.
+ *
+ * @return a non-null collection of files.
+ */
+ Set<File> getTestProguardFiles();
+
+ /**
+ * Returns the map of key value pairs for placeholder substitution in the android manifest file.
+ *
+ * This map will be used by the manifest merger.
+ * @return the map of key value pairs.
+ */
+ // TODO: Add the commented fields.
+ //Map<String, Object> getManifestPlaceholders();
+
+ /**
+ * Returns whether multi-dex is enabled.
+ *
+ * This can be null if the flag is not set, in which case the default value is used.
+ */
+ @Nullable
+ Boolean getMultiDexEnabled();
+ void setMultiDexEnabled(Boolean multiDexEnabled);
+
+ @Nullable
+ File getMultiDexKeepFile();
+ void setMultiDexKeepFile(File multiDexKeepFile);
+
+ @Nullable
+ File getMultiDexKeepProguard();
+ void setMultiDexKeepProguard(File multiDexKeepProguard);
+
+ /**
+ * Returns the optional jarjar rule files, or empty if jarjar should be skipped.
+ *
+ * <p>If more than one file is provided, the rule files will be merged in order with last one
+ * win in case of rule redefinition.
+ *
+ * <p>Can only be used with Jack toolchain.
+ *
+ * @return the optional jarjar rule file.
+ */
+ List<File> getJarJarRuleFiles();
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/BuildType.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/BuildType.java
new file mode 100644
index 0000000..8303bba
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/BuildType.java
@@ -0,0 +1,114 @@
+/*
+ * 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.build.gradle.managed;
+
+import org.gradle.model.Managed;
+
+/**
+ * A Managed build type.
+ */
+ at Managed
+public interface BuildType extends BaseConfig {
+
+ /**
+ * Returns whether the build type is configured to generate a debuggable apk.
+ *
+ * @return true if the apk is debuggable
+ */
+ Boolean getDebuggable();
+ void setDebuggable(Boolean isDebuggable);
+
+ /**
+ * Returns whether the build type is configured to be build with support for code coverage.
+ *
+ * @return true if code coverage is enabled.
+ */
+ Boolean getTestCoverageEnabled();
+ void setTestCoverageEnabled(Boolean isTestCoverageEnabled);
+
+ /**
+ * Returns whether the build type is configured to be build with support for pseudolocales.
+ *
+ * @return true if code coverage is enabled.
+ */
+ Boolean getPseudoLocalesEnabled();
+ void setPseudoLocalesEnabled(Boolean isPseudoLocalesEnabled);
+
+ /**
+ * Returns whether the build type is configured to generate an apk with debuggable
+ * renderscript code.
+ *
+ * @return true if the apk is debuggable
+ */
+ Boolean getRenderscriptDebuggable();
+ void setRenderscriptDebuggable(Boolean isRenderscriptDebuggable);
+
+ /**
+ * Returns the optimization level of the renderscript compilation.
+ *
+ * @return the optimization level.
+ */
+ Integer getRenderscriptOptimLevel();
+ void setRenderscriptOptimLevel(Integer renderscriptOptimLevel);
+
+ /**
+ * Returns the version name suffix.
+ *
+ * @return the version name suffix.
+ */
+ String getVersionNameSuffix();
+ void setVersionNameSuffix(String versionNameSuffix);
+
+ /**
+ * Returns whether minification is enabled for this build type.
+ *
+ * @return true if minification is enabled.
+ */
+ Boolean getMinifyEnabled();
+ void setMinifyEnabled(Boolean isMinifyEnabled);
+
+ /**
+ * Return whether zipalign is enabled for this build type.
+ *
+ * @return true if zipalign is enabled.
+ */
+ Boolean getZipAlignEnabled();
+ void setZipAlignEnabled(Boolean isZipAlignEnabled);
+
+ /**
+ * Returns whether the variant embeds the micro app.
+ */
+ Boolean getEmbedMicroApp();
+ void setEmbedMicroApp(Boolean isEmbedMicroApp);
+
+ /**
+ * Returns the associated signing config or null if none are set on the build type.
+ */
+ SigningConfig getSigningConfig();
+ void setSigningConfig(SigningConfig signingConfig);
+
+ Boolean getUseJack();
+ void setUseJack(Boolean useJack);
+
+ Boolean getShrinkResources();
+ void setShrinkResources(Boolean shrinkResources);
+
+ NdkBuildType getNdk();
+
+ Boolean getUseProguard();
+ void setUseProguard(Boolean useProguard);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ClassField.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ClassField.java
new file mode 100644
index 0000000..86c72ba
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ClassField.java
@@ -0,0 +1,43 @@
+/*
+ * 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.build.gradle.managed;
+
+import org.gradle.model.Managed;
+import org.gradle.model.Unmanaged;
+
+import java.util.Set;
+
+/**
+ * A Managed ClassField.
+ */
+ at Managed
+public interface ClassField {
+
+ String getType();
+ void setType(String type);
+
+ String getName();
+ void setName(String name);
+
+ String getValue();
+ void setValue(String value);
+
+ String getDocumentation();
+ void setDocumentation(String documentation);
+
+ Set<String> getAnnotations();
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/DataBindingOptions.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/DataBindingOptions.java
new file mode 100644
index 0000000..66267cd
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/DataBindingOptions.java
@@ -0,0 +1,41 @@
+/*
+ * 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.build.gradle.managed;
+
+import org.gradle.model.Managed;
+
+ at Managed
+public interface DataBindingOptions {
+
+ /**
+ * The version of data binding to use.
+ */
+ String getVersion();
+ void setVersion(String version);
+
+ /**
+ * Whether to enable data binding.
+ */
+ Boolean getEnabled();
+ void setEnabled(Boolean enabled);
+
+ /**
+ * Whether to add the default data binding adapters.
+ */
+ Boolean getAddDefaultAdapters();
+ void setAddDefaultAdapters(Boolean addDefaultAdapters);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/JsonConfigFile.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/JsonConfigFile.java
new file mode 100644
index 0000000..41b3dc2
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/JsonConfigFile.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed;
+
+import org.gradle.model.Managed;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Managed type for specifying a JSON configurations file.
+ */
+ at Managed
+public interface JsonConfigFile {
+
+ /**
+ * A JSON data file for configuring the ExternalNativeComponentPlugin.
+ */
+ File getConfig();
+ void setConfig(File file);
+
+ /**
+ * Command for generating a JSON data file.
+ *
+ * If a JSON data file is used to configure this plugin and the data file is generated from
+ * another program, the command to generate the data file can be specified such that it will be
+ * invoke by the generateBuildData task.
+ */
+ List<String> getCommand();
+
+ /**
+ * Command string for generating a JSON data file.
+ *
+ * Same as getCommand, but allows command to be specified as a single String.
+ */
+ String getCommandString();
+ void setCommandString(String command);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeBuildConfig.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeBuildConfig.java
new file mode 100644
index 0000000..5db0f80
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeBuildConfig.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.model.Managed;
+import org.gradle.model.ModelMap;
+import org.gradle.model.ModelSet;
+import org.gradle.model.Unmanaged;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Configuration model for a native project.
+ */
+ at Managed
+public interface NativeBuildConfig {
+
+ /**
+ * Collection of files that affects the build.
+ *
+ * These files may be monitored to determine if the JSON data file needs to be updated.
+ */
+ Set<File> getBuildFiles();
+
+ /**
+ * Configurations for each native artifact.
+ */
+ @NonNull
+ ModelMap<NativeLibrary> getLibraries();
+
+ /**
+ * NDK toolchains used for compilation.
+ */
+ @NonNull
+ ModelMap<NativeToolchain> getToolchains();
+
+ @NonNull
+ List<String> getCFileExtensions();
+
+ @NonNull
+ List<String> getCppFileExtensions();
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeLibrary.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeLibrary.java
new file mode 100644
index 0000000..0f07fed
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeLibrary.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.gradle.api.Named;
+import org.gradle.model.Managed;
+import org.gradle.model.ModelSet;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Configurations for a native library.
+ */
+ at Managed
+public interface NativeLibrary extends Named {
+
+ /**
+ * Executable for building the library.
+ */
+ @Nullable
+ String getExecutable();
+ void setExecutable(String executable);
+
+ /**
+ * Arguments to the executable for building the project.
+ */
+ @NonNull
+ List<String> getArgs();
+
+ @Nullable
+ String getGroupName();
+ void setGroupName(String groupName);
+
+ /**
+ * Target ABI.
+ */
+ @Nullable
+ String getAbi();
+ void setAbi(String abi);
+
+ /**
+ * Name of the toolchain.
+ */
+ @Nullable
+ String getToolchain();
+ void setToolchain(String toolchain);
+
+ /**
+ * Folders containing source files.
+ */
+ @NonNull
+ ModelSet<NativeSourceFolder> getFolders();
+
+ /**
+ * Source files.
+ */
+ @NonNull
+ ModelSet<NativeSourceFile> getFiles();
+
+ @NonNull
+ List<File> getExportedHeaders();
+
+ /**
+ * The output file.
+ */
+ @Nullable
+ File getOutput();
+ void setOutput(File output);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeSourceFile.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeSourceFile.java
new file mode 100644
index 0000000..75a3e3a
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeSourceFile.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.gradle.model.Managed;
+import org.gradle.model.Unmanaged;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A source file for a native library.
+ */
+ at Managed
+public interface NativeSourceFile {
+
+ /**
+ * The source file.
+ */
+ File getSrc();
+ void setSrc(File src);
+
+ /**
+ * List of compiler flags for this file.
+ */
+ @NonNull
+ List<String> getFlags();
+
+ /**
+ * The working directory for the compiler.
+ */
+ @Nullable
+ File getWorkingDirectory();
+ void setWorkingDirectory(File dir);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeSourceFolder.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeSourceFolder.java
new file mode 100644
index 0000000..20d5e5b
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeSourceFolder.java
@@ -0,0 +1,59 @@
+/*
+ * 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.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.gradle.model.Managed;
+import org.gradle.model.Unmanaged;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * A source file for a native library.
+ */
+ at Managed
+public interface NativeSourceFolder {
+
+ /**
+ * The source folder.
+ */
+ File getSrc();
+ void setSrc(File src);
+
+ /**
+ * List of compiler flags for all C files.
+ */
+ @NonNull
+ List<String> getCFlags();
+
+ /**
+ * List of compiler flags for all C++ files.
+ */
+ @NonNull
+ List<String> getCppFlags();
+
+ /**
+ * The working directory for the compiler.
+ */
+ @Nullable
+ File getWorkingDirectory();
+ void setWorkingDirectory(File dir);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeToolchain.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeToolchain.java
new file mode 100644
index 0000000..33610d3
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NativeToolchain.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.gradle.api.Named;
+import org.gradle.model.Managed;
+
+import java.io.File;
+
+/**
+ * A @Managed interface for {@link com.android.builder.model.NativeToolchain}.
+ */
+ at Managed
+public interface NativeToolchain extends com.android.builder.model.NativeToolchain, Named {
+ /**
+ * Returns the full path of the C compiler.
+ *
+ * @return the C compiler path.
+ */
+ @Override
+ @Nullable
+ File getCCompilerExecutable();
+ void setCCompilerExecutable(@Nullable File exe);
+
+ /**
+ * Returns the full path of the C++ compiler.
+ *
+ * @return the C++ compiler path.
+ */
+ @Override
+ @Nullable
+ File getCppCompilerExecutable();
+ void setCppCompilerExecutable(@Nullable File exe);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkAbiOptions.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkAbiOptions.java
new file mode 100644
index 0000000..8e75d8f
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkAbiOptions.java
@@ -0,0 +1,28 @@
+/*
+ * 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.build.gradle.managed;
+
+import org.gradle.api.Named;
+import org.gradle.model.Managed;
+
+/**
+ * DSL object for ABI specific NDK-related settings.
+ */
+ at Managed
+public interface NdkAbiOptions extends NdkOptions, Named {
+
+}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkBuildType.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkBuildType.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/NdkBuildType.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkBuildType.java
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkConfig.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkConfig.java
new file mode 100644
index 0000000..86aefc4
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkConfig.java
@@ -0,0 +1,35 @@
+package com.android.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.model.Managed;
+
+/**
+ * Root configuration model for android-ndk plugin.
+ */
+ at Managed
+public interface NdkConfig extends NdkBuildType {
+
+ /**
+ * Platform version of the NDK.
+ * The version used is the highest version available in the NDK that is lower or equal to the
+ * specified platform version. If not specified, the highest available version up to the
+ * compileSdkVersion is used.
+ */
+ String getPlatformVersion();
+ void setPlatformVersion(@NonNull String platformVersion);
+
+ /**
+ * The toolchain version.
+ * Support "gcc" or "clang" (default: "gcc").
+ */
+ String getToolchain();
+ void setToolchain(@NonNull String toolchain);
+
+ /**
+ * The toolchain version.
+ * Set as empty to use the default version for the toolchain.
+ */
+ String getToolchainVersion();
+ void setToolchainVersion(@NonNull String toolchainVersion);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkOptions.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkOptions.java
new file mode 100644
index 0000000..ea3b7b1
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/NdkOptions.java
@@ -0,0 +1,85 @@
+/*
+ * 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.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+
+import org.gradle.model.Managed;
+import org.gradle.model.Unmanaged;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * DSL object for variant specific NDK-related settings.
+ */
+ at Managed
+public interface NdkOptions {
+
+ /**
+ * The module name.
+ * The resulting shared object will be named "lib${getModuleName()}.so".
+ */
+ String getModuleName();
+ void setModuleName(@NonNull String moduleName);
+
+ /**
+ * The ABI Filters. Leave empty to include all supported ABI.
+ */
+ Set<String> getAbiFilters();
+
+ /**
+ * The C Flags
+ */
+ List<String> getCFlags();
+
+ /**
+ * The C++ Flags
+ */
+ List<String> getCppFlags();
+
+ /**
+ * The linker flags
+ */
+ List<String> getLdFlags();
+
+ /**
+ * The LD Libs
+ */
+ List<String> getLdLibs();
+
+ /**
+ * The STL.
+ *
+ * Supported values are:
+ * - system (default)
+ * - gabi++_static
+ * - gabi++_shared
+ * - stlport_static
+ * - stlport_shared
+ * - gnustl_static
+ * - gnustl_shared
+ */
+ String getStl();
+ void setStl(@NonNull String stl);
+
+ String getStlVersion();
+ void setStlVersion(String stlVersion);
+
+ Boolean getRenderscriptNdkMode();
+ void setRenderscriptNdkMode(Boolean renderscriptNdkMode);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ProductFlavor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ProductFlavor.java
new file mode 100644
index 0000000..bf3d75f
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/ProductFlavor.java
@@ -0,0 +1,194 @@
+/*
+ * 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.build.gradle.managed;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.DimensionAware;
+import com.android.builder.model.Variant;
+
+import org.gradle.model.Managed;
+
+import java.util.Set;
+
+/**
+ * A Managed product flavor.
+ *
+ * TODO: Convert Unmanaged Collection to Managed type when Gradle provides ModelSet for basic class.
+ */
+ at Managed
+public interface ProductFlavor extends DimensionAware, BaseConfig {
+
+ /**
+ * Returns the flavor dimension or null if not applicable.
+ */
+ @Override
+ @Nullable
+ String getDimension();
+ void setDimension(String dimension);
+
+ /**
+ * Returns the name of the product flavor. This is only the value set on this product flavor.
+ * To get the final application id name, use {@link AndroidArtifact#getApplicationId()}.
+ *
+ * @return the application id.
+ */
+ @Nullable
+ String getApplicationId();
+ void setApplicationId(String applicationId);
+
+ /**
+ * Returns the version code associated with this flavor or null if none have been set.
+ * This is only the value set on this product flavor, not necessarily the actual
+ * version code used.
+ *
+ * @return the version code, or null if not specified
+ */
+ @Nullable
+ Integer getVersionCode();
+ void setVersionCode(Integer versionCode);
+
+ /**
+ * Returns the version name. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getMergedFlavor()} as well as
+ * {@link BuildType#getVersionNameSuffix()}
+ *
+ * @return the version name.
+ */
+ @Nullable
+ String getVersionName();
+ void setVersionName(String versionName);
+
+ /**
+ * Returns the minSdkVersion. This is only the value set on this product flavor.
+ *
+ * @return the minSdkVersion, or null if not specified
+ */
+ @Nullable
+ ApiVersion getMinSdkVersion();
+
+ /**
+ * Returns the targetSdkVersion. This is only the value set on this product flavor.
+ *
+ * @return the targetSdkVersion, or null if not specified
+ */
+ @Nullable
+ ApiVersion getTargetSdkVersion();
+
+ /**
+ * Returns the maxSdkVersion. This is only the value set on this produce flavor.
+ *
+ * @return the maxSdkVersion, or null if not specified
+ */
+ @Nullable
+ Integer getMaxSdkVersion();
+ void setMaxSdkVersion(Integer maxSdkVersion);
+
+ /**
+ * Returns the renderscript target api. This is only the value set on this product flavor.
+ * TODO: make final renderscript target api available through the model
+ *
+ * @return the renderscript target api, or null if not specified
+ */
+ @Nullable
+ Integer getRenderscriptTargetApi();
+ void setRenderscriptTargetApi(Integer renderscriptTargetApi);
+
+ /**
+ * Returns whether the renderscript code should be compiled in support mode to
+ * make it compatible with older versions of Android.
+ *
+ * @return true if support mode is enabled, false if not, and null if not specified.
+ */
+ @Nullable
+ Boolean getRenderscriptSupportModeEnabled();
+ void setRenderscriptSupportModeEnabled(Boolean renderscriptSupportModeEnabled);
+
+ /**
+ * Returns whether the renderscript code should be compiled to generate C/C++ bindings.
+ * @return true for C/C++ generation, false for Java, null if not specified.
+ */
+ @Nullable
+ Boolean getRenderscriptNdkModeEnabled();
+ void setRenderscriptNdkModeEnabled(Boolean renderscriptNdkModeEnabled);
+
+ /**
+ * Returns the test application id. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getExtraAndroidArtifacts()} with
+ * {@link AndroidProject#ARTIFACT_ANDROID_TEST} and then
+ * {@link AndroidArtifact#getApplicationId()}
+ *
+ * @return the test package name.
+ */
+ @Nullable
+ String getTestApplicationId();
+ void setTestApplicationId(String testApplicationId);
+
+ /**
+ * Returns the test instrumentation runner. This is only the value set on this product flavor.
+ * TODO: make test instrumentation runner available through the model.
+ *
+ * @return the test package name.
+ */
+ @Nullable
+ String getTestInstrumentationRunner();
+ void setTestInstrumentationRunner(String testInstrumentationRunner);
+
+ /**
+ * Returns the handlingProfile value. This is only the value set on this product flavor.
+ *
+ * @return the handlingProfile value.
+ */
+ @Nullable
+ Boolean getTestHandleProfiling();
+ void setTestHandleProfiling(Boolean testHandleProfiling);
+
+ /**
+ * Returns the functionalTest value. This is only the value set on this product flavor.
+ *
+ * @return the functionalTest value.
+ */
+ @Nullable
+ Boolean getTestFunctionalTest();
+ void setTestFunctionalTest(Boolean testFunctionalTest);
+
+ /**
+ * Returns the resource configuration for this variant.
+ *
+ * This is the list of -c parameters for aapt.
+ *
+ * @return the resource configuration options.
+ */
+ @NonNull
+ Set<String> getResourceConfigurations();
+
+ /**
+ * Returns the associated signing config or null if none are set on the product flavor.
+ */
+ SigningConfig getSigningConfig();
+ void setSigningConfig(SigningConfig signingConfig);
+
+ Boolean getUseJack();
+ void setUseJack(Boolean useJack);
+
+ NdkOptions getNdk();
+
+ @NonNull
+ VectorDrawablesOptions getVectorDrawables();
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/SigningConfig.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/SigningConfig.java
new file mode 100644
index 0000000..54bee07
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/SigningConfig.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed;
+
+import org.gradle.api.Named;
+import org.gradle.model.Managed;
+
+import java.io.File;
+
+/**
+ * A Managed SigningConfig.
+ */
+ at Managed
+public interface SigningConfig extends Named {
+
+ File getStoreFile();
+ void setStoreFile(File storeFile);
+
+ String getStorePassword();
+ void setStorePassword(String storePassword);
+
+ String getKeyAlias();
+ void setKeyAlias(String keyAlias);
+
+ String getKeyPassword();
+ void setKeyPassword(String keyPassword);
+
+ String getStoreType();
+ void setStoreType(String storeType);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/VectorDrawablesOptions.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/VectorDrawablesOptions.java
new file mode 100644
index 0000000..79eb0d1
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/VectorDrawablesOptions.java
@@ -0,0 +1,36 @@
+/*
+ * 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.build.gradle.managed;
+
+import org.gradle.model.Managed;
+
+import java.util.Set;
+
+/**
+ * Managed VectorDrawablesOptions.
+ */
+ at Managed
+public interface VectorDrawablesOptions extends com.android.builder.model.VectorDrawablesOptions {
+
+ @Override
+ Boolean getUseSupportLibrary();
+ void setUseSupportLibrary(Boolean useSupportLibrary);
+
+ @Override
+ Set<String> getGeneratedDensities();
+ void setGeneratedDensities(Set<String> generatedDensities);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/AndroidConfigAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/AndroidConfigAdaptor.java
new file mode 100644
index 0000000..e8f0b0e
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/AndroidConfigAdaptor.java
@@ -0,0 +1,332 @@
+/*
+ * 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.build.gradle.managed.adaptor;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceDirectorySet;
+import com.android.build.gradle.api.AndroidSourceFile;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.api.VariantFilter;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.internal.coverage.JacocoOptions;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.dsl.AdbOptions;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.dsl.LintOptions;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.dsl.Splits;
+import com.android.build.gradle.internal.dsl.TestOptions;
+import com.android.build.gradle.managed.BuildType;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.build.gradle.managed.SigningConfig;
+import com.android.build.gradle.managed.AndroidConfig;
+import com.android.build.api.transform.Transform;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.core.LibraryRequest;
+import com.android.builder.model.DataBindingOptions;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.builder.testing.api.TestServer;
+import com.android.repository.Revision;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.model.ModelMap;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An adaptor to convert a managed.AndroidConfig to an model.AndroidConfig.
+ */
+public class AndroidConfigAdaptor implements com.android.build.gradle.AndroidConfig {
+
+ private final AndroidConfig model;
+ private NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer;
+
+ public AndroidConfigAdaptor(
+ AndroidConfig model,
+ NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer) {
+ this.model = model;
+ this.sourceSetsContainer = sourceSetsContainer;
+ applyProjectSourceSet();
+ }
+
+ @Override
+ public String getBuildToolsVersion() {
+ return model.getBuildToolsVersion();
+ }
+
+ @Override
+ public String getCompileSdkVersion() {
+ return model.getCompileSdkVersion();
+ }
+
+ @Override
+ public Revision getBuildToolsRevision() {
+ return model.getBuildToolsRevision();
+ }
+
+ @Override
+ public boolean getEnforceUniquePackageName() {
+ return false;
+ }
+
+ @Override
+ public CoreProductFlavor getDefaultConfig() {
+ return new ProductFlavorAdaptor(model.getDefaultConfig());
+ }
+
+ @Override
+ @NonNull
+ public List<DeviceProvider> getDeviceProviders() {
+ return ImmutableList.copyOf(model.getDeviceProviders());
+ }
+
+ @Override
+ @NonNull
+ public List<TestServer> getTestServers() {
+ return ImmutableList.copyOf(model.getTestServers());
+ }
+
+ @NonNull
+ @Override
+ public List<Transform> getTransforms() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public List<List<Object>> getTransformsDependencies() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public String getDefaultPublishConfig() {
+ return model.getDefaultPublishConfig();
+ }
+
+ @Override
+ public boolean getPublishNonDefault() {
+ return model.getPublishNonDefault();
+ }
+
+ @Override
+ public Action<VariantFilter> getVariantFilter() {
+ return model.getVariantFilter();
+ }
+
+ @Override
+ public String getResourcePrefix() {
+ return model.getResourcePrefix();
+ }
+
+ @Override
+ public List<String> getFlavorDimensionList() {
+ return null;
+ }
+
+ @Override
+ public boolean getGeneratePureSplits() {
+ return model.getGeneratePureSplits();
+ }
+
+ @Override
+ public Collection<CoreBuildType> getBuildTypes() {
+ return ImmutableList.copyOf(Iterables.transform(model.getBuildTypes().values(),
+ new Function<BuildType, CoreBuildType>() {
+ @Override
+ public CoreBuildType apply(BuildType buildType) {
+ return new BuildTypeAdaptor(buildType);
+ }
+ }));
+ }
+
+ @Override
+ public Collection<CoreProductFlavor> getProductFlavors() {
+ return ImmutableList.copyOf(Iterables.transform(model.getProductFlavors().values(),
+ new Function<ProductFlavor, CoreProductFlavor>() {
+ @Override
+ public CoreProductFlavor apply(ProductFlavor flavor) {
+ return new ProductFlavorAdaptor(flavor);
+ }
+ }));
+ }
+
+ @Override
+ public Collection<com.android.builder.model.SigningConfig> getSigningConfigs() {
+ return ImmutableList.copyOf(Iterables.transform(model.getSigningConfigs().values(),
+ new Function<SigningConfig, com.android.builder.model.SigningConfig>() {
+ @Override
+ public com.android.builder.model.SigningConfig apply(SigningConfig signingConfig) {
+ return new SigningConfigAdaptor(signingConfig);
+ }
+ }));
+ }
+
+ @Override
+ public NamedDomainObjectContainer<AndroidSourceSet> getSourceSets() {
+ return sourceSetsContainer;
+ }
+
+ @Override
+ public Boolean getPackageBuildConfig() {
+ return true;
+ }
+
+ public ModelMap<FunctionalSourceSet> getSources() {
+ return model.getSources();
+ }
+
+ public CoreNdkOptions getNdk() {
+ return new NdkOptionsAdaptor(model.getNdk());
+ }
+
+ @Override
+ public AdbOptions getAdbOptions() {
+ return model.getAdbOptions();
+ }
+
+ @Override
+ public AaptOptions getAaptOptions() {
+ return model.getAaptOptions();
+ }
+
+ @Override
+ public CompileOptions getCompileOptions() {
+ return model.getCompileOptions();
+ }
+
+ @Override
+ public DexOptions getDexOptions() {
+ return model.getDexOptions();
+ }
+
+ @Override
+ public JacocoOptions getJacoco() {
+ return model.getJacoco();
+ }
+
+ @Override
+ public LintOptions getLintOptions() {
+ return model.getLintOptions();
+ }
+
+ @Override
+ public PackagingOptions getPackagingOptions() {
+ return model.getPackagingOptions();
+ }
+
+
+ @Override
+ public TestOptions getTestOptions() {
+ return model.getTestOptions();
+ }
+
+ @Override
+ public Splits getSplits() {
+ return model.getSplits();
+ }
+
+ @Override
+ public DataBindingOptions getDataBinding() {
+ return new DataBindingOptionsAdapter(model.getDataBinding());
+ }
+
+ @Override
+ public Collection<LibraryRequest> getLibraryRequests() {
+ return model.getLibraryRequests();
+ }
+
+ @Override
+ public Collection<String> getAidlPackageWhiteList() {
+ return ImmutableSet.copyOf(model.getAidlPackageWhitelist());
+ }
+
+ private void applyProjectSourceSet() {
+ for (String name : getSources().keySet()) {
+ FunctionalSourceSet source = getSources().get(name);
+ AndroidSourceSet androidSource = name.equals(BuilderConstants.MAIN) ?
+ sourceSetsContainer.maybeCreate(getDefaultConfig().getName()) :
+ sourceSetsContainer.maybeCreate(name);
+
+ convertSourceFile(androidSource.getManifest(), source, "manifest");
+ convertSourceSet(androidSource.getResources(), source, "resource");
+ convertSourceSet(androidSource.getJava(), source, "java");
+ convertSourceSet(androidSource.getRes(), source, "res");
+ convertSourceSet(androidSource.getAssets(), source, "assets");
+ convertSourceSet(androidSource.getAidl(), source, "aidl");
+ convertSourceSet(androidSource.getRenderscript(), source, "renderscript");
+ convertSourceSet(androidSource.getJni(), source, "jni");
+ convertSourceSet(androidSource.getJniLibs(), source, "jniLibs");
+ }
+ }
+
+ /**
+ * Convert a FunctionalSourceSet to an AndroidSourceFile.
+ */
+ private static void convertSourceFile(
+ AndroidSourceFile androidFile,
+ FunctionalSourceSet source,
+ String sourceName) {
+ LanguageSourceSet languageSourceSet = source.get(sourceName);
+ if (languageSourceSet == null) {
+ return;
+ }
+ SourceDirectorySet dir = languageSourceSet.getSource();
+ if (dir == null) {
+ return;
+ }
+ // We use the first file in the file tree until Gradle has a way to specify one source file
+ // instead of an entire source set.
+ Set<File> files = dir.getAsFileTree().getFiles();
+ if (!files.isEmpty()) {
+ androidFile.srcFile(Iterables.getOnlyElement(files));
+ }
+ }
+
+ /**
+ * Convert a FunctionalSourceSet to an AndroidSourceDirectorySet.
+ */
+ private static void convertSourceSet(
+ AndroidSourceDirectorySet androidDir,
+ FunctionalSourceSet source,
+ String sourceName) {
+ LanguageSourceSet languageSourceSet = source.get(sourceName);
+ if (languageSourceSet == null) {
+ return;
+ }
+ SourceDirectorySet dir = languageSourceSet.getSource();
+ if (dir == null) {
+ return;
+ }
+ androidDir.setSrcDirs(dir.getSrcDirs());
+ androidDir.include(dir.getIncludes());
+ androidDir.exclude(dir.getExcludes());
+ }
+}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/ApiVersionAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/ApiVersionAdaptor.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/managed/adaptor/ApiVersionAdaptor.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/ApiVersionAdaptor.java
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/BaseConfigAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/BaseConfigAdaptor.java
new file mode 100644
index 0000000..4cf7e4c
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/BaseConfigAdaptor.java
@@ -0,0 +1,141 @@
+/*
+ * 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.build.gradle.managed.adaptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.ClassFieldImpl;
+import com.android.builder.model.BaseConfig;
+import com.android.builder.model.ClassField;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An adaptor to convert a managed.BaseConfig to a model.BaseConfig.
+ */
+public class BaseConfigAdaptor implements BaseConfig {
+
+ @NonNull
+ private final com.android.build.gradle.managed.BaseConfig baseConfig;
+
+ public BaseConfigAdaptor(@NonNull com.android.build.gradle.managed.BaseConfig baseConfig) {
+ this.baseConfig = baseConfig;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return baseConfig.getName();
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getBuildConfigFields() {
+ ImmutableMap.Builder<String, ClassField> builder = ImmutableMap.builder();
+ for (com.android.build.gradle.managed.ClassField cf : baseConfig.getBuildConfigFields()) {
+ builder.put(
+ cf.getName(),
+ new ClassFieldImpl(
+ cf.getType(),
+ cf.getName(),
+ cf.getValue(),
+ ImmutableSet.copyOf(cf.getAnnotations()),
+ Objects.firstNonNull(cf.getDocumentation(), "")));
+ }
+ return builder.build();
+ }
+
+ @NonNull
+ @Override
+ public Map<String, ClassField> getResValues() {
+ ImmutableMap.Builder<String, ClassField> builder = ImmutableMap.builder();
+ for (com.android.build.gradle.managed.ClassField cf : baseConfig.getResValues()) {
+ builder.put(
+ cf.getName(),
+ new ClassFieldImpl(
+ cf.getType(),
+ cf.getName(),
+ cf.getValue(),
+ Objects.firstNonNull(cf.getAnnotations(), ImmutableSet.<String>of()),
+ Objects.firstNonNull(cf.getDocumentation(), "")));
+ }
+ return builder.build();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getProguardFiles() {
+ return ImmutableList.copyOf(baseConfig.getProguardFiles());
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getConsumerProguardFiles() {
+ return ImmutableList.copyOf(baseConfig.getConsumerProguardFiles());
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getTestProguardFiles() {
+ return ImmutableList.copyOf(baseConfig.getTestProguardFiles());
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Object> getManifestPlaceholders() {
+ // TODO: To be implemented
+ return Maps.newHashMap();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getMultiDexEnabled() {
+ return baseConfig.getMultiDexEnabled();
+ }
+
+ @Nullable
+ @Override
+ public File getMultiDexKeepFile() {
+ return baseConfig.getMultiDexKeepFile();
+ }
+
+ @Nullable
+ @Override
+ public File getMultiDexKeepProguard() {
+ return baseConfig.getMultiDexKeepProguard();
+ }
+
+ @Nullable
+ @Override
+ public String getApplicationIdSuffix() {
+ return baseConfig.getApplicationIdSuffix();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJarJarRuleFiles() {
+ return ImmutableList.copyOf(baseConfig.getJarJarRuleFiles());
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/BuildTypeAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/BuildTypeAdaptor.java
new file mode 100644
index 0000000..c349d63
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/BuildTypeAdaptor.java
@@ -0,0 +1,115 @@
+/*
+ * 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.build.gradle.managed.adaptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dsl.CoreBuildType;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.managed.BuildType;
+import com.android.builder.model.SigningConfig;
+import com.google.common.base.Objects;
+
+/**
+ * An adaptor to convert a BuildType to a CoreBuildType.
+ */
+public class BuildTypeAdaptor extends BaseConfigAdaptor implements CoreBuildType {
+ @NonNull
+ private final BuildType buildType;
+
+ public BuildTypeAdaptor(@NonNull BuildType buildType) {
+ super(buildType);
+ this.buildType = buildType;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return buildType.getDebuggable();
+ }
+
+ @Override
+ public boolean isTestCoverageEnabled() {
+ return buildType.getTestCoverageEnabled();
+ }
+
+ @Override
+ public boolean isJniDebuggable() {
+ return Objects.firstNonNull(buildType.getNdk().getDebuggable(), false);
+ }
+
+ @Override
+ public boolean isPseudoLocalesEnabled() {
+ return buildType.getPseudoLocalesEnabled();
+ }
+
+ @Override
+ public boolean isRenderscriptDebuggable() {
+ return buildType.getRenderscriptDebuggable();
+ }
+
+ @Override
+ public int getRenderscriptOptimLevel() {
+ return buildType.getRenderscriptOptimLevel();
+ }
+
+ @Nullable
+ @Override
+ public String getVersionNameSuffix() {
+ return buildType.getVersionNameSuffix();
+ }
+
+ @Override
+ public boolean isMinifyEnabled() {
+ return buildType.getMinifyEnabled();
+ }
+
+ @Override
+ public boolean isZipAlignEnabled() {
+ return buildType.getZipAlignEnabled();
+ }
+
+ @Override
+ public boolean isEmbedMicroApp() {
+ return buildType.getEmbedMicroApp();
+ }
+
+ @Nullable
+ @Override
+ public SigningConfig getSigningConfig() {
+ return buildType.getSigningConfig() == null ? null : new SigningConfigAdaptor(buildType.getSigningConfig());
+ }
+
+ @Override
+ public CoreNdkOptions getNdkConfig() {
+ return new NdkOptionsAdaptor(buildType.getNdk());
+ }
+
+ @Override
+ public Boolean getUseJack() {
+ return buildType.getUseJack();
+ }
+
+ @Override
+ public boolean isShrinkResources() {
+ return buildType.getShrinkResources();
+ }
+
+ @Override
+ public boolean isUseProguard() {
+ return buildType.getUseProguard();
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/DataBindingOptionsAdapter.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/DataBindingOptionsAdapter.java
new file mode 100644
index 0000000..ce31dca
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/DataBindingOptionsAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.build.gradle.managed.adaptor;
+
+import com.android.build.gradle.managed.DataBindingOptions;
+
+public class DataBindingOptionsAdapter implements com.android.builder.model.DataBindingOptions {
+
+ private final DataBindingOptions dataBindingOptions;
+
+ public DataBindingOptionsAdapter(DataBindingOptions dataBindingOptions) {
+ this.dataBindingOptions = dataBindingOptions;
+ }
+
+ @Override
+ public String getVersion() {
+ return dataBindingOptions.getVersion();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return dataBindingOptions.getEnabled();
+ }
+
+ @Override
+ public boolean getAddDefaultAdapters() {
+ return dataBindingOptions.getAddDefaultAdapters();
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/NdkOptionsAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/NdkOptionsAdaptor.java
new file mode 100644
index 0000000..336e121
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/NdkOptionsAdaptor.java
@@ -0,0 +1,76 @@
+/*
+ * 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.build.gradle.managed.adaptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.managed.NdkOptions;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An adaptor to convert a NdkConfig to NdkConfig.
+ */
+public class NdkOptionsAdaptor implements CoreNdkOptions {
+
+ NdkOptions ndkOptions;
+
+ public NdkOptionsAdaptor(@NonNull NdkOptions ndkOptions) {
+ this.ndkOptions = ndkOptions;
+ }
+
+ @Nullable
+ @Override
+ public String getModuleName() {
+ return ndkOptions.getModuleName();
+ }
+
+ @Nullable
+ @Override
+ public String getcFlags() {
+ return Joiner.on(' ').join(ndkOptions.getCFlags());
+ }
+
+ @Nullable
+ @Override
+ public List<String> getLdLibs() {
+ return ImmutableList.copyOf(ndkOptions.getLdLibs());
+ }
+
+ @Nullable
+ @Override
+ public Set<String> getAbiFilters() {
+ return ImmutableSet.copyOf(ndkOptions.getAbiFilters());
+ }
+
+ @Nullable
+ @Override
+ public String getStl() {
+ return ndkOptions.getStl();
+ }
+
+ @Nullable
+ @Override
+ public Integer getJobs() {
+ return null;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/ProductFlavorAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/ProductFlavorAdaptor.java
new file mode 100644
index 0000000..2484eba
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/ProductFlavorAdaptor.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed.adaptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.internal.dsl.CoreProductFlavor;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.VectorDrawablesOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * An adaptor to convert a ProductFlavor to CoreProductFlavor.
+ */
+public class ProductFlavorAdaptor extends BaseConfigAdaptor implements CoreProductFlavor {
+
+ @NonNull
+ protected final ProductFlavor productFlavor;
+
+ public ProductFlavorAdaptor(@NonNull ProductFlavor productFlavor) {
+ super(productFlavor);
+ this.productFlavor = productFlavor;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return productFlavor.getName().equals("defaultConfig") ? BuilderConstants.MAIN : productFlavor.getName();
+ }
+
+ @Nullable
+ @Override
+ public String getDimension() {
+ return productFlavor.getDimension();
+ }
+
+ @Nullable
+ @Override
+ public String getApplicationId() {
+ return productFlavor.getApplicationId();
+ }
+
+ @Nullable
+ @Override
+ public Integer getVersionCode() {
+ return productFlavor.getVersionCode();
+ }
+
+ @Nullable
+ @Override
+ public String getVersionName() {
+ return productFlavor.getVersionName();
+ }
+
+ @Nullable
+ @Override
+ public ApiVersion getMinSdkVersion() {
+ return ApiVersionAdaptor.isEmpty(productFlavor.getMinSdkVersion()) ?
+ null :
+ new ApiVersionAdaptor(productFlavor.getMinSdkVersion());
+ }
+
+ @Nullable
+ @Override
+ public ApiVersion getTargetSdkVersion() {
+ return ApiVersionAdaptor.isEmpty(productFlavor.getTargetSdkVersion()) ?
+ null :
+ new ApiVersionAdaptor(productFlavor.getTargetSdkVersion());
+ }
+
+ @Nullable
+ @Override
+ public Integer getMaxSdkVersion() {
+ return productFlavor.getMaxSdkVersion();
+ }
+
+ @Nullable
+ @Override
+ public Integer getRenderscriptTargetApi() {
+ return productFlavor.getRenderscriptTargetApi();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getRenderscriptSupportModeEnabled() {
+ return productFlavor.getRenderscriptSupportModeEnabled();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getRenderscriptNdkModeEnabled() {
+ return productFlavor.getRenderscriptNdkModeEnabled();
+ }
+
+ @Nullable
+ @Override
+ public String getTestApplicationId() {
+ return productFlavor.getTestApplicationId();
+ }
+
+ @Nullable
+ @Override
+ public String getTestInstrumentationRunner() {
+ return productFlavor.getTestInstrumentationRunner();
+ }
+
+ @NonNull
+ @Override
+ public Map<String, String> getTestInstrumentationRunnerArguments() {
+ // TODO: To be implemented.
+ return Maps.newHashMap();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestHandleProfiling() {
+ return productFlavor.getTestHandleProfiling();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestFunctionalTest() {
+ return productFlavor.getTestFunctionalTest();
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getResourceConfigurations() {
+ return ImmutableList.copyOf(productFlavor.getResourceConfigurations());
+ }
+
+ @Nullable
+ @Override
+ public SigningConfig getSigningConfig() {
+ return productFlavor.getSigningConfig() == null ?
+ null :
+ new SigningConfigAdaptor(productFlavor.getSigningConfig());
+ }
+
+ @NonNull
+ @Override
+ public VectorDrawablesOptions getVectorDrawables() {
+ return productFlavor.getVectorDrawables();
+ }
+
+ @Override
+ public CoreNdkOptions getNdkConfig() {
+ return new NdkOptionsAdaptor(productFlavor.getNdk());
+ }
+
+ @Override
+ public Boolean getUseJack() {
+ return productFlavor.getUseJack();
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/SigningConfigAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/SigningConfigAdaptor.java
new file mode 100644
index 0000000..14459c9
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/SigningConfigAdaptor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.managed.adaptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dsl.CoreSigningConfig;
+import com.android.build.gradle.managed.SigningConfig;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+
+import java.io.File;
+
+/**
+ * An adaptor to convert a managed.SigningConfig to a model.SigningConfig.
+ */
+public class SigningConfigAdaptor implements CoreSigningConfig {
+
+ @NonNull
+ private final SigningConfig signingConfig;
+
+ public SigningConfigAdaptor(@NonNull SigningConfig signingConfig) {
+ this.signingConfig = signingConfig;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return signingConfig.getName();
+ }
+
+ @Nullable
+ @Override
+ @InputFile @Optional
+ public File getStoreFile() {
+ return signingConfig.getStoreFile();
+ }
+
+ @Nullable
+ @Override
+ @Input
+ public String getStorePassword() {
+ return signingConfig.getStorePassword();
+ }
+
+ @Nullable
+ @Override
+ @Input
+ public String getKeyAlias() {
+ return signingConfig.getKeyAlias();
+ }
+
+ @Nullable
+ @Override
+ public String getKeyPassword() {
+ return signingConfig.getKeyPassword();
+ }
+
+ @Nullable
+ @Override
+ @Input
+ public String getStoreType() {
+ return signingConfig.getStoreType();
+ }
+
+ @Override
+ public boolean isSigningReady() {
+ return signingConfig.getStoreFile() != null &&
+ signingConfig.getStorePassword() != null &&
+ signingConfig.getKeyAlias() != null &&
+ signingConfig.getKeyPassword() != null;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/VectorDrawablesOptionsAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/VectorDrawablesOptionsAdaptor.java
new file mode 100644
index 0000000..fc0f6c0
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/managed/adaptor/VectorDrawablesOptionsAdaptor.java
@@ -0,0 +1,49 @@
+/*
+ * 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.build.gradle.managed.adaptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.VectorDrawablesOptions;
+
+import java.util.Set;
+
+/**
+ * Adaptor from managed.VectorDrawablesOptions to model.VectorDrawablesOptions.
+ */
+public class VectorDrawablesOptionsAdaptor implements VectorDrawablesOptions {
+
+ @NonNull
+ private com.android.build.gradle.managed.VectorDrawablesOptions managedOptions;
+
+ public VectorDrawablesOptionsAdaptor(
+ @NonNull com.android.build.gradle.managed.VectorDrawablesOptions managedOptions) {
+ this.managedOptions = managedOptions;
+ }
+
+ @Nullable
+ @Override
+ public Set<String> getGeneratedDensities() {
+ return managedOptions.getGeneratedDensities();
+ }
+
+ @Nullable
+ @Override
+ public Boolean getUseSupportLibrary() {
+ return managedOptions.getUseSupportLibrary();
+ }
+}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidBinary.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidBinary.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidBinary.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidBinary.java
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentModelPlugin.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentModelPlugin.java
new file mode 100644
index 0000000..afa95d5
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentModelPlugin.java
@@ -0,0 +1,318 @@
+/*
+ * 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.build.gradle.model;
+
+import static com.android.builder.core.VariantType.ANDROID_TEST;
+import static com.android.builder.core.VariantType.UNIT_TEST;
+
+import com.android.build.gradle.internal.ProductFlavorCombo;
+import com.android.build.gradle.managed.AndroidConfig;
+import com.android.build.gradle.managed.BuildType;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.build.gradle.model.internal.DefaultAndroidBinary;
+import com.android.build.gradle.model.internal.AndroidComponentSpecInternal;
+import com.android.build.gradle.model.internal.DefaultAndroidComponentSpec;
+import com.android.build.gradle.model.internal.DefaultAndroidLanguageSourceSet;
+import com.android.build.gradle.model.internal.DefaultJniLibsSourceSet;
+import com.android.builder.core.BuilderConstants;
+import com.android.repository.Revision;
+import com.android.utils.StringHelper;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Ints;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.ProjectSourceSet;
+import org.gradle.language.base.internal.registry.LanguageRegistry;
+import org.gradle.language.base.plugins.ComponentModelBasePlugin;
+import org.gradle.model.Defaults;
+import org.gradle.model.Finalize;
+import org.gradle.model.Model;
+import org.gradle.model.ModelMap;
+import org.gradle.model.Mutate;
+import org.gradle.model.Path;
+import org.gradle.model.RuleSource;
+import org.gradle.platform.base.BinarySpec;
+import org.gradle.platform.base.BinaryType;
+import org.gradle.platform.base.BinaryTypeBuilder;
+import org.gradle.platform.base.ComponentBinaries;
+import org.gradle.platform.base.ComponentType;
+import org.gradle.platform.base.ComponentTypeBuilder;
+import org.gradle.platform.base.LanguageType;
+import org.gradle.platform.base.LanguageTypeBuilder;
+import org.gradle.tooling.BuildException;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Plugin to set up infrastructure for other android plugins.
+ */
+public class AndroidComponentModelPlugin implements Plugin<Project> {
+
+ /**
+ * The name of ComponentSpec created with android component model plugin.
+ */
+ public static final String COMPONENT_NAME = "android";
+
+ public static final String GRADLE_ACCEPTABLE_VERSION = "2.10";
+
+ private static final String GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY =
+ "com.android.build.gradle.overrideVersionCheck";
+
+ @Override
+ public void apply(Project project) {
+ checkGradleVersion(project);
+ project.getPlugins().apply(ComponentModelBasePlugin.class);
+ }
+
+ private static void checkGradleVersion(Project project) {
+ String gradleVersion = project.getGradle().getGradleVersion();
+ if (!gradleVersion.startsWith(GRADLE_ACCEPTABLE_VERSION)) {
+ boolean allowNonMatching = Boolean.getBoolean(GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
+ File file = new File("gradle" + File.separator + "wrapper" + File.separator +
+ "gradle-wrapper.properties");
+ String errorMessage = String.format(
+ "Gradle version %s is required. Current version is %s. " +
+ "If using the gradle wrapper, try editing the distributionUrl in %s " +
+ "to gradle-%s-all.zip",
+ GRADLE_ACCEPTABLE_VERSION, gradleVersion, file.getAbsolutePath(),
+ GRADLE_ACCEPTABLE_VERSION);
+ if (allowNonMatching) {
+ project.getLogger().warn(errorMessage);
+ project.getLogger().warn("As %s is set, continuing anyways.",
+ GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
+ } else {
+ throw new BuildException(errorMessage, null);
+ }
+ }
+ }
+
+ public static class Rules extends RuleSource {
+
+ @LanguageType
+ public static void registerAndroidLanguageSourceSet(
+ LanguageTypeBuilder<AndroidLanguageSourceSet> builder) {
+ builder.setLanguageName("android");
+ builder.defaultImplementation(DefaultAndroidLanguageSourceSet.class);
+ }
+
+ @LanguageType
+ public static void registerJniLibsSourceSet(LanguageTypeBuilder<JniLibsSourceSet> builder) {
+ builder.setLanguageName("jniLibs");
+ builder.defaultImplementation(DefaultJniLibsSourceSet.class);
+ }
+
+ /**
+ * Create "android" model block.
+ */
+ @Model("android")
+ public void android(AndroidConfig androidModel) {
+ }
+
+ @Finalize
+ public static void finalizeAndroidModel(AndroidConfig androidModel) {
+ if (androidModel.getBuildToolsRevision() == null
+ && androidModel.getBuildToolsVersion() != null) {
+ //The underlying Revision class has the maven artifact semantic,
+ // so 20 is not the same as 20.0. For the build tools revision this
+ // is not the desired behavior, so normalize e.g. to 20.0.0.
+ androidModel.setBuildToolsRevision(
+ Revision.parseRevision(
+ androidModel.getBuildToolsVersion(),
+ Revision.Precision.MICRO));
+ }
+
+ if (androidModel.getCompileSdkVersion() != null
+ && !androidModel.getCompileSdkVersion().startsWith("android-")
+ && Ints.tryParse(androidModel.getCompileSdkVersion()) != null) {
+ androidModel.setCompileSdkVersion("android-" + androidModel.getCompileSdkVersion());
+ }
+
+ }
+
+ @Defaults
+ public static void createDefaultBuildTypes(
+ @Path("android.buildTypes") ModelMap<BuildType> buildTypes) {
+ buildTypes.create(BuilderConstants.DEBUG, new Action<BuildType>() {
+ @Override
+ public void execute(BuildType buildType) {
+ buildType.setDebuggable(true);
+ buildType.setEmbedMicroApp(false);
+ }
+ });
+ buildTypes.create(BuilderConstants.RELEASE);
+ }
+
+ @Model
+ public static List<ProductFlavorCombo<ProductFlavor>> createProductFlavorCombo(
+ @Path("android.productFlavors") ModelMap<ProductFlavor> productFlavors) {
+ // TODO: Create custom product flavor container to manually configure flavor dimensions.
+ Set<String> flavorDimensionList = Sets.newHashSet();
+ for (ProductFlavor flavor : productFlavors.values()) {
+ if (flavor.getDimension() != null) {
+ flavorDimensionList.add(flavor.getDimension());
+ }
+ }
+
+ return ProductFlavorCombo.createCombinations(
+ Lists.newArrayList(flavorDimensionList),
+ productFlavors.values());
+ }
+
+ @ComponentType
+ public static void defineComponentType(ComponentTypeBuilder<AndroidComponentSpec> builder) {
+ builder.defaultImplementation(DefaultAndroidComponentSpec.class);
+ builder.internalView(AndroidComponentSpecInternal.class);
+ }
+
+ @Mutate
+ public static void createAndroidComponents(
+ ModelMap<AndroidComponentSpec> androidComponents) {
+ androidComponents.create(COMPONENT_NAME);
+ }
+
+ /**
+ * Create all source sets for each AndroidBinary.
+ */
+ @Mutate
+ public static void createVariantSourceSet(
+ @Path("android.sources") final ModelMap<FunctionalSourceSet> sources,
+ @Path("android.buildTypes") final ModelMap<BuildType> buildTypes,
+ @Path("android.productFlavors") ModelMap<ProductFlavor> flavors,
+ List<ProductFlavorCombo<ProductFlavor>> flavorGroups,
+ ProjectSourceSet projectSourceSet,
+ LanguageRegistry languageRegistry) {
+
+ // Create main source set.
+ sources.create("main");
+ sources.create(ANDROID_TEST.getPrefix());
+ sources.create(UNIT_TEST.getPrefix());
+
+ for (BuildType buildType : buildTypes.values()) {
+ sources.create(buildType.getName());
+
+ if (!flavors.isEmpty()) {
+ for (ProductFlavorCombo group : flavorGroups) {
+ if (!group.getFlavorList().isEmpty()) {
+ sources.create(
+ group.getName() + StringHelper.capitalize(buildType.getName()));
+ }
+ }
+ }
+ }
+ for (ProductFlavorCombo group: flavorGroups) {
+ sources.create(group.getName());
+ }
+ if (flavorGroups.size() != flavors.size()) {
+ // If flavorGroups and flavors are the same size, there is at most 1 flavor
+ // dimension. So we don't need to reconfigure the source sets for flavorGroups.
+ for (ProductFlavor flavor: flavors.values()) {
+ sources.create(flavor.getName());
+ }
+ }
+ sources.afterEach(new Action<FunctionalSourceSet>() {
+ @Override
+ public void execute(final FunctionalSourceSet functionalSourceSet) {
+ functionalSourceSet.afterEach(
+ new Action<LanguageSourceSet>() {
+ @Override
+ public void execute(LanguageSourceSet languageSourceSet) {
+ SourceDirectorySet source = languageSourceSet.getSource();
+ if (source.getSrcDirs().isEmpty()) {
+ source.srcDir("src/" + languageSourceSet.getParentName()
+ + "/" + languageSourceSet.getName());
+ }
+ }
+ });
+ }
+ });
+
+ }
+
+ @BinaryType
+ public static void defineBinaryType(BinaryTypeBuilder<AndroidBinary> builder) {
+ builder.defaultImplementation(DefaultAndroidBinary.class);
+ builder.internalView(AndroidBinaryInternal.class);
+ }
+
+ @ComponentBinaries
+ public static void createBinaries(
+ final ModelMap<AndroidBinary> binaries,
+ @Path("android") final AndroidConfig androidConfig,
+ @Path("android.buildTypes") final ModelMap<BuildType> buildTypes,
+ final List<ProductFlavorCombo<ProductFlavor>> flavorCombos,
+ @Path("android.sources") final ModelMap<FunctionalSourceSet> sources,
+ final AndroidComponentSpec spec) {
+ if (flavorCombos.isEmpty()) {
+ flavorCombos.add(new ProductFlavorCombo<ProductFlavor>());
+ }
+
+ for (final BuildType buildType : buildTypes.values()) {
+ for (final ProductFlavorCombo<ProductFlavor> flavorCombo : flavorCombos) {
+ binaries.create(getBinaryName(buildType, flavorCombo),
+ new Action<AndroidBinary>() {
+ @Override
+ public void execute(AndroidBinary androidBinary) {
+ AndroidBinaryInternal binary = (AndroidBinaryInternal) androidBinary;
+ binary.setBuildType(buildType);
+ binary.setProductFlavors(flavorCombo.getFlavorList());
+
+ sourceBinary(binary, sources, "main");
+ sourceBinary(binary, sources, buildType.getName());
+ for (ProductFlavor flavor : flavorCombo.getFlavorList()) {
+ sourceBinary(binary, sources, flavor.getName());
+ }
+ if (flavorCombo.getFlavorList().size() > 1) {
+ sourceBinary(binary, sources, flavorCombo.getName());
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Add sources to the specified binary.
+ */
+ private static void sourceBinary(
+ BinarySpec binary,
+ ModelMap<FunctionalSourceSet> projectSourceSet,
+ final String sourceSetName) {
+ FunctionalSourceSet sourceSet = projectSourceSet.get(sourceSetName);
+ if (sourceSet != null) {
+ binary.getInputs().addAll(sourceSet.values());
+ }
+ }
+
+ private static String getBinaryName(BuildType buildType, ProductFlavorCombo flavorCombo) {
+ if (flavorCombo.getFlavorList().isEmpty()) {
+ return buildType.getName();
+ } else {
+ return flavorCombo.getName() + StringHelper.capitalize(buildType.getName());
+ }
+
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentModelTestPlugin.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentModelTestPlugin.java
new file mode 100644
index 0000000..9a2fba7
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentModelTestPlugin.java
@@ -0,0 +1,80 @@
+/*
+ * 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.build.gradle.model;
+
+import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
+import static com.android.builder.core.VariantType.ANDROID_TEST;
+import static com.android.builder.core.VariantType.UNIT_TEST;
+
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.VariantManager;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.build.gradle.model.internal.AndroidComponentSpecInternal;
+import com.android.builder.core.BuilderConstants;
+import com.google.common.base.Preconditions;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.model.ModelMap;
+import org.gradle.model.Mutate;
+import org.gradle.model.RuleSource;
+import org.gradle.platform.base.BinaryContainer;
+
+/**
+ * Plugin for creating test tasks for AndroidBinary.
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+public class AndroidComponentModelTestPlugin extends RuleSource {
+
+ @Mutate
+ public void createConnectedTestTasks(
+ final ModelMap<Task> tasks,
+ ModelMap<AndroidBinaryInternal> binaries,
+ TaskManager taskManager,
+ ModelMap<AndroidComponentSpecInternal> specs) {
+ final VariantManager variantManager = specs.get(COMPONENT_NAME).getVariantManager();
+ for (AndroidBinaryInternal binary : binaries.values()) {
+ // Create test tasks.
+ BaseVariantData testedVariantData = binary.getVariantData();
+
+ TestVariantData unitTestVariantData = variantManager.createTestVariantData(
+ testedVariantData,
+ UNIT_TEST);
+ variantManager.getVariantDataList().add(unitTestVariantData);
+ variantManager.createTasksForVariantData(
+ new TaskModelMapAdaptor(tasks),
+ unitTestVariantData);
+
+ // TODO: compare against testBuildType instead of BuilderConstants.DEBUG.
+ if (!binary.getBuildType().getName().equals(BuilderConstants.DEBUG)) {
+ continue;
+ }
+
+ Preconditions.checkState(testedVariantData != null,
+ "Internal error: tested variant must be created before test variant.");
+
+ TestVariantData testVariantData =
+ variantManager.createTestVariantData(testedVariantData, ANDROID_TEST);
+ variantManager.getVariantDataList().add(testVariantData);
+ variantManager.createTasksForVariantData(
+ new TaskModelMapAdaptor(tasks),
+ testVariantData);
+ }
+ }
+}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentSpec.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentSpec.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/AndroidComponentSpec.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidComponentSpec.java
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidLanguageSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidLanguageSourceSet.java
new file mode 100644
index 0000000..0e49d6e
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AndroidLanguageSourceSet.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * LanguageSourceSet for Android's sources.
+ */
+public interface AndroidLanguageSourceSet extends LanguageSourceSet {
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AppComponentModelPlugin.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AppComponentModelPlugin.java
new file mode 100644
index 0000000..f523b43
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/AppComponentModelPlugin.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import static com.android.build.gradle.model.ModelConstants.IS_APPLICATION;
+import static com.android.build.gradle.model.ModelConstants.TASK_MANAGER;
+
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.DependencyManager;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.variant.ApplicationVariantFactory;
+import com.android.build.gradle.internal.variant.VariantFactory;
+import com.android.builder.core.AndroidBuilder;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.model.Model;
+import org.gradle.model.RuleSource;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+/**
+ * Gradle component model plugin class for 'application' projects.
+ */
+public class AppComponentModelPlugin implements Plugin<Project> {
+
+ @Override
+ public void apply(Project project) {
+ project.getPluginManager().apply(BaseComponentModelPlugin.class);
+ project.getPluginManager().apply(AndroidComponentModelTestPlugin.class);
+ }
+
+ public static class Rules extends RuleSource {
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ @Model(IS_APPLICATION)
+ public static Boolean isApplication() {
+ return true;
+ }
+
+ @Model(TASK_MANAGER)
+ public static TaskManager createTaskManager(
+ AndroidConfig androidExtension,
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ SdkHandler sdkHandler,
+ ExtraModelInfo extraModelInfo,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ DependencyManager dependencyManager = new DependencyManager(project, extraModelInfo);
+
+ return new ApplicationComponentTaskManager(
+ project,
+ androidBuilder,
+ dataBindingBuilder,
+ androidExtension,
+ sdkHandler,
+ dependencyManager,
+ toolingRegistry);
+ }
+
+ @Model
+ public static VariantFactory createVariantFactory(
+ ServiceRegistry serviceRegistry,
+ AndroidBuilder androidBuilder,
+ AndroidConfig extension) {
+ Instantiator instantiator = serviceRegistry.get(Instantiator.class);
+ return new ApplicationVariantFactory(instantiator, androidBuilder, extension);
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ApplicationComponentTaskManager.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ApplicationComponentTaskManager.java
new file mode 100644
index 0000000..e5f661c
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ApplicationComponentTaskManager.java
@@ -0,0 +1,64 @@
+/*
+ * 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.build.gradle.model;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.ApplicationTaskManager;
+import com.android.build.gradle.internal.DependencyManager;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.builder.core.AndroidBuilder;
+import com.google.common.collect.ImmutableList;
+
+import org.gradle.api.Project;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+import java.util.Collection;
+
+/**
+ * TaskManager for creating tasks in an Android application project with component model plugin.
+ */
+public class ApplicationComponentTaskManager extends ApplicationTaskManager {
+
+ public ApplicationComponentTaskManager (
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ super(project, androidBuilder, dataBindingBuilder, extension, sdkHandler, dependencyManager,
+ toolingRegistry);
+ isComponentModelPlugin = true;
+ }
+
+ @Override
+ protected Collection<Object> getNdkBuildable(BaseVariantData variantData) {
+ NdkComponentModelPlugin plugin = project.getPlugins().getPlugin(NdkComponentModelPlugin.class);
+ return ImmutableList.<Object>copyOf(plugin.getBinaries(variantData.getVariantConfiguration()));
+ }
+
+ @Override
+ public void configureScopeForNdk(@NonNull VariantScope scope) {
+ NdkComponentModelPlugin.configureScopeForNdk(scope);
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/BaseComponentModelPlugin.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/BaseComponentModelPlugin.java
new file mode 100644
index 0000000..160268d
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/BaseComponentModelPlugin.java
@@ -0,0 +1,660 @@
+/*
+ * 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.build.gradle.model;
+
+import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
+import static com.android.build.gradle.model.ModelConstants.ANDROID_BUILDER;
+import static com.android.build.gradle.model.ModelConstants.ANDROID_CONFIG_ADAPTOR;
+import static com.android.build.gradle.model.ModelConstants.EXTRA_MODEL_INFO;
+import static com.android.build.gradle.model.ModelConstants.JNILIBS_DEPENDENCIES;
+import static com.android.builder.core.BuilderConstants.DEBUG;
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.internal.AndroidConfigHelper;
+import com.android.build.gradle.internal.ExecutionConfigurationUtil;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.JniLibsLanguageTransform;
+import com.android.build.gradle.internal.LibraryCache;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.NativeDependencyLinkage;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.NdkOptionsHelper;
+import com.android.build.gradle.internal.ProductFlavorCombo;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.VariantManager;
+import com.android.build.gradle.internal.coverage.JacocoPlugin;
+import com.android.build.gradle.internal.dependency.AndroidNativeDependencySpec;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolveResult;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolver;
+import com.android.build.gradle.internal.pipeline.TransformTask;
+import com.android.build.gradle.internal.process.GradleJavaProcessExecutor;
+import com.android.build.gradle.internal.process.GradleProcessExecutor;
+import com.android.build.gradle.internal.profile.RecordingBuildListener;
+import com.android.build.gradle.internal.tasks.DependencyReportTask;
+import com.android.build.gradle.internal.tasks.SigningReportTask;
+import com.android.build.gradle.internal.transforms.DexTransform;
+import com.android.build.gradle.internal.variant.VariantFactory;
+import com.android.build.gradle.managed.AndroidConfig;
+import com.android.build.gradle.managed.BuildType;
+import com.android.build.gradle.managed.DataBindingOptions;
+import com.android.build.gradle.managed.NdkConfig;
+import com.android.build.gradle.managed.NdkOptions;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.build.gradle.managed.SigningConfig;
+import com.android.build.gradle.managed.VectorDrawablesOptions;
+import com.android.build.gradle.managed.adaptor.AndroidConfigAdaptor;
+import com.android.build.gradle.managed.adaptor.BuildTypeAdaptor;
+import com.android.build.gradle.managed.adaptor.DataBindingOptionsAdapter;
+import com.android.build.gradle.managed.adaptor.ProductFlavorAdaptor;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.build.gradle.model.internal.AndroidComponentSpecInternal;
+import com.android.build.gradle.tasks.JillTask;
+import com.android.builder.Version;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.internal.compiler.JackConversionCache;
+import com.android.builder.internal.compiler.PreDexCache;
+import com.android.builder.profile.ProcessRecorderFactory;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+import com.android.builder.sdk.TargetInfo;
+import com.android.builder.signing.DefaultSigningConfig;
+import com.android.ide.common.internal.ExecutorSingleton;
+import com.android.ide.common.signing.KeystoreHelper;
+import com.android.prefs.AndroidLocation;
+import com.android.resources.Density;
+import com.android.utils.ILogger;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.registry.LanguageTransformContainer;
+import org.gradle.model.Defaults;
+import org.gradle.model.Model;
+import org.gradle.model.ModelMap;
+import org.gradle.model.Mutate;
+import org.gradle.model.Path;
+import org.gradle.model.RuleSource;
+import org.gradle.model.internal.core.ModelReference;
+import org.gradle.model.internal.core.ModelRegistrations;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.platform.base.ComponentSpecContainer;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import groovy.lang.Closure;
+
+public class BaseComponentModelPlugin implements Plugin<Project> {
+
+ private ToolingModelBuilderRegistry toolingRegistry;
+
+ private ModelRegistry modelRegistry;
+
+ @Inject
+ protected BaseComponentModelPlugin(ToolingModelBuilderRegistry toolingRegistry,
+ ModelRegistry modelRegistry) {
+ this.toolingRegistry = toolingRegistry;
+ this.modelRegistry = modelRegistry;
+ }
+
+ /**
+ * Replace BasePlugin's apply method for component model.
+ */
+ @Override
+ public void apply(Project project) {
+ ExecutionConfigurationUtil.setThreadPoolSize(project);
+ try {
+ List<Recorder.Property> propertyList = Lists.newArrayList(
+ new Recorder.Property("plugin_version", Version.ANDROID_GRADLE_PLUGIN_VERSION),
+ new Recorder.Property("next_gen_plugin", "true"),
+ new Recorder.Property("gradle_version", project.getGradle().getGradleVersion())
+ );
+ String benchmarkName = AndroidGradleOptions.getBenchmarkName(project);
+ if (benchmarkName != null) {
+ propertyList.add(new Recorder.Property("benchmark_name", benchmarkName));
+ }
+ String benchmarkMode = AndroidGradleOptions.getBenchmarkMode(project);
+ if (benchmarkMode != null) {
+ propertyList.add(new Recorder.Property("benchmark_mode", benchmarkMode));
+ }
+
+ ProcessRecorderFactory.initialize(
+ new LoggerWrapper(project.getLogger()),
+ project.getRootProject()
+ .file("profiler" + System.currentTimeMillis() + ".json"),
+ propertyList);
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to initialize ProcessRecorderFactory");
+ }
+ project.getGradle().addListener(new RecordingBuildListener(ThreadRecorder.get()));
+
+ project.getPlugins().apply(AndroidComponentModelPlugin.class);
+ project.getPlugins().apply(JavaBasePlugin.class);
+ project.getPlugins().apply(JacocoPlugin.class);
+
+ // Create default configurations. ConfigurationContainer is not part of the component
+ // model, making it difficult to define proper rules to create configurations based on
+ // build types and product flavors. We just create the default configurations for now
+ // which should handle the majority of the use cases.
+ // Users can still use variant specific configurations, they just have to be manually
+ // created.
+ // TODO: Migrate to new dependency management if it's ready.
+ ConfigurationContainer configurations = project.getConfigurations();
+ createConfiguration(configurations, "compile", "Classpath for compiling the default sources.");
+ createConfiguration(configurations, "testCompile", "Classpath for compiling the test sources.");
+ createConfiguration(configurations, "androidTestCompile", "Classpath for compiling the androidTest sources.");
+ createConfiguration(configurations, "default-metadata", "Metadata for published APKs");
+ createConfiguration(configurations, "default-mapping", "Metadata for published APKs");
+
+ project.getPlugins().apply(NdkComponentModelPlugin.class);
+
+ // Remove this when our models no longer depends on Project.
+ modelRegistry.register(ModelRegistrations
+ .bridgedInstance(ModelReference.of("projectModel", Project.class), project)
+ .descriptor("Model of project.").build());
+
+ toolingRegistry.register(new ComponentModelBuilder(modelRegistry));
+
+ // Inserting the ToolingModelBuilderRegistry into the model so that it can be use to create
+ // TaskManager in child classes.
+ modelRegistry.register(ModelRegistrations.bridgedInstance(
+ ModelReference.of("toolingRegistry", ToolingModelBuilderRegistry.class),
+ toolingRegistry).descriptor("Tooling model builder model registry.").build());
+
+ // Create SDK handler. This has to be done in the 'apply' method to ensure it is executed.
+ SdkHandler sdkHandler = createSdkHandler(project);
+ modelRegistry.register(ModelRegistrations.bridgedInstance(
+ ModelReference.of("createSdkHandler", SdkHandler.class),
+ sdkHandler).descriptor("SDK handler.").build());
+ }
+
+ private SdkHandler createSdkHandler(final Project project) {
+ final ILogger logger = new LoggerWrapper(project.getLogger());
+ final SdkHandler sdkHandler = new SdkHandler(project, logger);
+
+ // call back on execution. This is called after the whole build is done (not
+ // after the current project is done).
+ // This is will be called for each (android) projects though, so this should support
+ // being called 2+ times.
+ project.getGradle().buildFinished(new Closure<Object>(this, this) {
+ public void doCall(Object it) {
+ ExecutorSingleton.shutdown();
+ sdkHandler.unload();
+ try {
+ PreDexCache.getCache().clear(project.getRootProject()
+ .file(String.valueOf(project.getRootProject().getBuildDir()) + "/"
+ + FD_INTERMEDIATES + "/dex-cache/cache.xml"), logger);
+ JackConversionCache.getCache().clear(project.getRootProject()
+ .file(String.valueOf(project.getRootProject().getBuildDir()) + "/"
+ + FD_INTERMEDIATES + "/jack-cache/cache.xml"), logger);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ LibraryCache.getCache().unload();
+ }
+ });
+
+ project.getGradle().getTaskGraph().whenReady(new Closure<Void>(this, this) {
+ public void doCall(TaskExecutionGraph taskGraph) {
+ for (Task task : taskGraph.getAllTasks()) {
+ if (task instanceof TransformTask) {
+ if (((TransformTask) task).getTransform() instanceof DexTransform) {
+ PreDexCache.getCache().load(project.getRootProject()
+ .file(String.valueOf(project.getRootProject().getBuildDir())
+ + "/" + FD_INTERMEDIATES + "/dex-cache/cache.xml"));
+ break;
+ }
+ } else if (task instanceof JillTask) {
+ JackConversionCache.getCache().load(project.getRootProject()
+ .file(String.valueOf(project.getRootProject().getBuildDir())
+ + "/" + FD_INTERMEDIATES + "/jack-cache/cache.xml"));
+ break;
+ }
+ }
+ }
+ });
+
+ // setup SDK repositories.
+ for (final File file : sdkHandler.getSdkLoader().getRepositories()) {
+ project.getRepositories().maven(new Action<MavenArtifactRepository>() {
+ @Override
+ public void execute(MavenArtifactRepository repo) {
+ repo.setUrl(file.toURI());
+
+ }
+ });
+ }
+ return sdkHandler;
+ }
+
+ private static void createConfiguration(@NonNull ConfigurationContainer configurations,
+ @NonNull String configurationName, @NonNull String configurationDescription) {
+ Configuration configuration = configurations.findByName(configurationName);
+ if (configuration == null) {
+ configuration = configurations.create(configurationName);
+ }
+
+ configuration.setVisible(false);
+ configuration.setDescription(configurationDescription);
+ }
+
+ public static class Rules extends RuleSource {
+
+ @Mutate
+ public static void registerLanguageTransform(
+ LanguageTransformContainer languages,
+ ServiceRegistry serviceRegistry,
+ NdkHandler ndkHandler) {
+ languages.add(new JniLibsLanguageTransform(ndkHandler));
+ }
+
+ @Defaults
+ public static void configureAndroidModel(
+ AndroidConfig androidModel,
+ ServiceRegistry serviceRegistry) {
+ Instantiator instantiator = serviceRegistry.get(Instantiator.class);
+ AndroidConfigHelper.configure(androidModel, instantiator);
+ }
+
+ @Defaults
+ public static void initSigningConfigs(
+ @Path("android.signingConfigs") ModelMap<SigningConfig> signingConfigs) {
+ signingConfigs.beforeEach(new Action<SigningConfig>() {
+ @Override
+ public void execute(SigningConfig signingConfig) {
+ signingConfig.setStoreType(KeyStore.getDefaultType());
+ }
+ });
+ signingConfigs.create(DEBUG, new Action<SigningConfig>() {
+ @Override
+ public void execute(SigningConfig signingConfig) {
+ try {
+ signingConfig.setStoreFile(
+ new File(KeystoreHelper.defaultDebugKeystoreLocation()));
+ signingConfig.setStorePassword(DefaultSigningConfig.DEFAULT_PASSWORD);
+ signingConfig.setKeyAlias(DefaultSigningConfig.DEFAULT_ALIAS);
+ signingConfig.setKeyPassword(DefaultSigningConfig.DEFAULT_PASSWORD);
+ } catch (AndroidLocation.AndroidLocationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ // com.android.build.gradle.AndroidConfig do not contain an NdkConfig. Copy it to the
+ // defaultConfig for now.
+ @Defaults
+ public static void copyNdkConfig(
+ @Path("android.defaultConfig.ndk") NdkOptions defaultNdkConfig,
+ @Path("android.ndk") NdkConfig pluginNdkConfig) {
+ NdkOptionsHelper.merge(defaultNdkConfig, pluginNdkConfig);
+ }
+
+ @Defaults
+ public static void configureDefaultDataBindingOptions(
+ @Path("android.dataBinding") DataBindingOptions dataBindingOptions) {
+ dataBindingOptions.setEnabled(false);
+ dataBindingOptions.setAddDefaultAdapters(true);
+ }
+
+ // TODO: Remove code duplicated from BasePlugin.
+ @Model(EXTRA_MODEL_INFO)
+ public static ExtraModelInfo createExtraModelInfo(
+ Project project,
+ @NonNull @Path("isApplication") Boolean isApplication) {
+ return new ExtraModelInfo(project, isApplication);
+ }
+
+ @Model
+ public static DataBindingBuilder createDataBindingBuilder() {
+ return new DataBindingBuilder();
+ }
+
+ @Model(ANDROID_BUILDER)
+ public static AndroidBuilder createAndroidBuilder(
+ Project project,
+ ExtraModelInfo extraModelInfo) {
+ String creator = "Android Gradle";
+ ILogger logger = new LoggerWrapper(project.getLogger());
+
+ return new AndroidBuilder(project.equals(project.getRootProject()) ? project.getName()
+ : project.getPath(), creator, new GradleProcessExecutor(project),
+ new GradleJavaProcessExecutor(project),
+ extraModelInfo, logger, project.getLogger().isEnabled(LogLevel.INFO));
+
+ }
+
+ @Defaults
+ public static void initDebugBuildTypes(
+ @Path("android.buildTypes") ModelMap<BuildType> buildTypes,
+ @Path("android.signingConfigs") final ModelMap<SigningConfig> signingConfigs) {
+ buildTypes.beforeEach(new Action<BuildType>() {
+ @Override
+ public void execute(BuildType buildType) {
+ initBuildType(buildType);
+ }
+ });
+
+ buildTypes.named(DEBUG, new Action<BuildType>() {
+ @Override
+ public void execute(BuildType buildType) {
+ buildType.setSigningConfig(signingConfigs.get(DEBUG));
+ }
+ });
+ }
+
+ private static void initBuildType(@NonNull BuildType buildType) {
+ buildType.setDebuggable(false);
+ buildType.setTestCoverageEnabled(false);
+ buildType.setPseudoLocalesEnabled(false);
+ buildType.setRenderscriptDebuggable(false);
+ buildType.setRenderscriptOptimLevel(3);
+ buildType.setMinifyEnabled(false);
+ buildType.setUseProguard(true);
+ buildType.setZipAlignEnabled(true);
+ buildType.setEmbedMicroApp(true);
+ buildType.setUseJack(false);
+ buildType.setShrinkResources(false);
+ }
+
+ @Defaults
+ public static void initDefaultConfigVectorDrawables(
+ @Path("android.defaultConfig.vectorDrawables") VectorDrawablesOptions vectorDrawablesOptions) {
+ vectorDrawablesOptions.setUseSupportLibrary(false);
+
+ Set<Density> densities = Density.getRecommendedValuesForDevice();
+ Set<String> strings = Sets.newHashSetWithExpectedSize(densities.size());
+ for (Density density : densities) {
+ strings.add(density.getResourceValue());
+ }
+
+ vectorDrawablesOptions.setGeneratedDensities(strings);
+ }
+
+ @Defaults
+ public static void addDefaultAndroidSourceSet(
+ @Path("android.sources") ModelMap<FunctionalSourceSet> sources) {
+ sources.all(new Action<FunctionalSourceSet>() {
+ @Override
+ public void execute(FunctionalSourceSet sourceSet) {
+ sourceSet.create("resources", AndroidLanguageSourceSet.class);
+ sourceSet.create("java", AndroidLanguageSourceSet.class);
+ sourceSet.create("manifest", AndroidLanguageSourceSet.class);
+ sourceSet.create("res", AndroidLanguageSourceSet.class);
+ sourceSet.create("assets", AndroidLanguageSourceSet.class);
+ sourceSet.create("aidl", AndroidLanguageSourceSet.class);
+ sourceSet.create("renderscript", AndroidLanguageSourceSet.class);
+ sourceSet.create("jniLibs", JniLibsSourceSet.class);
+
+ sourceSet.named("manifest", new Action<LanguageSourceSet>() {
+ @Override
+ public void execute(LanguageSourceSet manifest) {
+ manifest.getSource().setIncludes(
+ ImmutableList.of("AndroidManifest.xml"));
+ }
+ });
+ }
+ });
+ }
+
+ @Mutate
+ public void androidConfigImplicitDependencies(AndroidConfig androidConfig,
+ @Path("android.dataBinding") DataBindingOptions dataBindingOptions) {
+
+ }
+
+ @Model(ANDROID_CONFIG_ADAPTOR)
+ public static com.android.build.gradle.AndroidConfig createModelAdaptor(
+ ServiceRegistry serviceRegistry,
+ AndroidConfig androidExtension,
+ Project project,
+ @Path("isApplication") Boolean isApplication) {
+ Instantiator instantiator = serviceRegistry.get(Instantiator.class);
+ return new AndroidConfigAdaptor(androidExtension, AndroidConfigHelper
+ .createSourceSetsContainer(project, instantiator, !isApplication));
+ }
+
+ @Model(JNILIBS_DEPENDENCIES)
+ public static Multimap<String, NativeDependencyResolveResult> resolveJniLibsDependencies(
+ ModelMap<AndroidBinary> androidBinary,
+ @Path("android.sources") final ModelMap<FunctionalSourceSet> sources,
+ final ServiceRegistry serviceRegistry) {
+ Multimap<String, NativeDependencyResolveResult> dependencies =
+ ArrayListMultimap.create();
+ for (AndroidBinary binary : androidBinary.values()) {
+ Collection<JniLibsSourceSet> jniSources = binary.getInputs().withType(
+ JniLibsSourceSet.class);
+
+ for (JniLibsSourceSet sourceSet : jniSources) {
+ dependencies.put(
+ binary.getName(),
+ new NativeDependencyResolver(
+ serviceRegistry,
+ sourceSet.getDependencies(),
+ new AndroidNativeDependencySpec(
+ null,
+ null,
+ binary.getBuildType().getName(),
+ ProductFlavorCombo.getFlavorComboName(
+ binary.getProductFlavors()),
+ NativeDependencyLinkage.SHARED)).resolve());
+ }
+ }
+ return dependencies;
+ }
+
+ @Mutate
+ public static void createAndroidComponents(
+ ComponentSpecContainer androidSpecs,
+ ServiceRegistry serviceRegistry, AndroidConfig androidExtension,
+ com.android.build.gradle.AndroidConfig adaptedModel,
+ @Path("android.buildTypes") ModelMap<BuildType> buildTypes,
+ @Path("android.productFlavors") ModelMap<ProductFlavor> productFlavors,
+ @Path("android.signingConfigs") ModelMap<SigningConfig> signingConfigs,
+ VariantFactory variantFactory,
+ TaskManager taskManager,
+ Project project,
+ AndroidBuilder androidBuilder,
+ SdkHandler sdkHandler,
+ ExtraModelInfo extraModelInfo,
+ @Path("isApplication") Boolean isApplication) {
+ Instantiator instantiator = serviceRegistry.get(Instantiator.class);
+
+ // check if the target has been set.
+ TargetInfo targetInfo = androidBuilder.getTargetInfo();
+ if (targetInfo == null) {
+ sdkHandler.initTarget(androidExtension.getCompileSdkVersion(),
+ androidExtension.getBuildToolsRevision(),
+ androidExtension.getLibraryRequests(), androidBuilder,
+ SdkHandler.useCachedSdk(project));
+ }
+
+ VariantManager variantManager = new VariantManager(project, androidBuilder,
+ adaptedModel, variantFactory, taskManager, instantiator);
+
+ variantFactory.validateModel(variantManager);
+
+ for (BuildType buildType : buildTypes.values()) {
+ variantManager.addBuildType(new BuildTypeAdaptor(buildType));
+ }
+
+ for (ProductFlavor productFlavor : productFlavors.values()) {
+ variantManager.addProductFlavor(new ProductFlavorAdaptor(productFlavor));
+ }
+
+ AndroidComponentSpecInternal spec =
+ (AndroidComponentSpecInternal) androidSpecs.get(COMPONENT_NAME);
+ spec.setExtension(androidExtension);
+ spec.setVariantManager(variantManager);
+ }
+
+ @Mutate
+ public static void createVariantData(
+ ModelMap<AndroidBinaryInternal> binaries,
+ ModelMap<AndroidComponentSpec> specs,
+ TaskManager taskManager) {
+ final VariantManager variantManager =
+ ((AndroidComponentSpecInternal) specs.get(COMPONENT_NAME)).getVariantManager();
+ binaries.afterEach(new Action<AndroidBinaryInternal>() {
+ @Override
+ public void execute(AndroidBinaryInternal binary) {
+ List<ProductFlavorAdaptor> adaptedFlavors = Lists.newArrayList();
+ for (ProductFlavor flavor : binary.getProductFlavors()) {
+ adaptedFlavors.add(new ProductFlavorAdaptor(flavor));
+ }
+ binary.setVariantData(
+ variantManager.createVariantData(
+ new BuildTypeAdaptor(binary.getBuildType()),
+ adaptedFlavors));
+ binary.getVariantData().getVariantConfiguration().setEnableInstantRunOverride(false);
+ variantManager.getVariantDataList().add(binary.getVariantData());
+ }
+ });
+ }
+
+ @Mutate
+ public static void createLifeCycleTasks(ModelMap<Task> tasks, TaskManager taskManager) {
+ taskManager.createTasksBeforeEvaluate(new TaskModelMapAdaptor(tasks));
+ }
+
+ @Mutate
+ public void addDataBindingDependenciesIfNecessary(
+ TaskManager taskManager,
+ @Path("android.dataBinding") DataBindingOptions dataBindingOptions) {
+ taskManager.addDataBindingDependenciesIfNecessary(
+ new DataBindingOptionsAdapter(dataBindingOptions));
+ }
+
+ @Mutate
+ public static void createAndroidTasks(
+ ModelMap<Task> tasks,
+ ModelMap<AndroidComponentSpec> androidSpecs,
+ TaskManager taskManager,
+ SdkHandler sdkHandler,
+ Project project,
+ @Path("android.sources") ModelMap<FunctionalSourceSet> androidSources) {
+ // setup SDK repositories.
+ for (final File file : sdkHandler.getSdkLoader().getRepositories()) {
+ project.getRepositories().maven(new Action<MavenArtifactRepository>() {
+ @Override
+ public void execute(MavenArtifactRepository repo) {
+ repo.setUrl(file.toURI());
+ }
+ });
+ }
+ // TODO: determine how to provide functionalities of variant API objects.
+ }
+
+ // TODO: Use @BinaryTasks after figuring how to configure non-binary specific tasks.
+ @Mutate
+ public static void createBinaryTasks(
+ final ModelMap<Task> tasks,
+ ModelMap<AndroidBinaryInternal> binaries,
+ ModelMap<AndroidComponentSpec> specs,
+ TaskManager taskManager) {
+ final VariantManager variantManager =
+ ((AndroidComponentSpecInternal) specs.get(COMPONENT_NAME)).getVariantManager();
+ for (AndroidBinaryInternal binary : binaries) {
+ variantManager.createTasksForVariantData(
+ new TaskModelMapAdaptor(tasks),
+ binary.getVariantData());
+ }
+ }
+
+ /**
+ * Create tasks that must be created after other tasks for variants are created.
+ */
+ @Mutate
+ public static void createRemainingTasks(
+ ModelMap<Task> tasks,
+ TaskManager taskManager,
+ ModelMap<AndroidComponentSpec> spec) {
+ VariantManager variantManager =
+ ((AndroidComponentSpecInternal)spec.get(COMPONENT_NAME)).getVariantManager();
+
+ // create the test tasks.
+ taskManager.createTopLevelTestTasks(new TaskModelMapAdaptor(tasks),
+ !variantManager.getProductFlavors().isEmpty());
+ }
+
+ @Mutate
+ public static void createReportTasks(
+ ModelMap<Task> tasks,
+ ModelMap<AndroidComponentSpecInternal> specs) {
+ final VariantManager variantManager = specs.get(COMPONENT_NAME).getVariantManager();
+
+ tasks.create("androidDependencies", DependencyReportTask.class,
+ new Action<DependencyReportTask>() {
+ @Override
+ public void execute(DependencyReportTask dependencyReportTask) {
+ dependencyReportTask.setDescription(
+ "Displays the Android dependencies of the project");
+ dependencyReportTask.setVariants(variantManager.getVariantDataList());
+ dependencyReportTask.setGroup("Android");
+ }
+ });
+
+ tasks.create("signingReport", SigningReportTask.class,
+ new Action<SigningReportTask>() {
+ @Override
+ public void execute(SigningReportTask signingReportTask) {
+ signingReportTask
+ .setDescription("Displays the signing info for each variant");
+ signingReportTask.setVariants(variantManager.getVariantDataList());
+ signingReportTask.setGroup("Android");
+
+ }
+ });
+ }
+
+ @Mutate
+ public static void modifyAssembleTaskDescription(
+ @Path("tasks.assemble") Task assembleTask) {
+ assembleTask.setDescription(
+ "Assembles all variants of all applications and secondary packages.");
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ComponentModelBuilder.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ComponentModelBuilder.java
new file mode 100644
index 0000000..686e648
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ComponentModelBuilder.java
@@ -0,0 +1,142 @@
+/*
+ * 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.build.gradle.model;
+
+import static com.android.build.gradle.model.ModelConstants.ANDROID_BUILDER;
+import static com.android.build.gradle.model.ModelConstants.ANDROID_CONFIG_ADAPTOR;
+import static com.android.build.gradle.model.ModelConstants.BINARIES;
+import static com.android.build.gradle.model.ModelConstants.COMPONENTS;
+import static com.android.build.gradle.model.ModelConstants.EXTRA_MODEL_INFO;
+import static com.android.build.gradle.model.ModelConstants.IS_APPLICATION;
+import static com.android.build.gradle.model.ModelConstants.NDK_HANDLER;
+import static com.android.build.gradle.model.ModelConstants.TASK_MANAGER;
+
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.VariantManager;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolveResult;
+import com.android.build.gradle.internal.model.ModelBuilder;
+import com.android.build.gradle.managed.NdkAbiOptions;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.build.gradle.model.internal.AndroidComponentSpecInternal;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.model.AndroidProject;
+import com.google.common.collect.Multimap;
+
+import org.gradle.api.Project;
+import org.gradle.model.ModelMap;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.model.internal.type.ModelTypes;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+/**
+ * A ToolingModelBuilder for creating AndroidProject in the component model plugin.
+ *
+ * It retrieves models from ModelRegistry and uses ModelBuilder to create AndroidProject.
+ *
+ * This model builder uses Gradle's internal API as a public API for create tooling model from
+ * component model is not yet available.
+ */
+public class ComponentModelBuilder implements ToolingModelBuilder {
+
+ ModelBuilder modelBuilder;
+ ModelRegistry registry;
+
+
+ public ComponentModelBuilder(ModelRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public boolean canBuild(String modelName) {
+ return modelName.equals(AndroidProject.class.getName());
+ }
+
+ @Override
+ public Object buildAll(String modelName, Project project) {
+ if (modelBuilder == null) {
+ modelBuilder = createModelBuilder();
+ }
+ return modelBuilder.buildAll(modelName, project);
+ }
+
+ /**
+ * Return the ModelType for a parameterized Multimap.
+ */
+ private static <K, V> ModelType<Multimap<K, V>> multimapModelType(
+ Class<K> keyClass,
+ Class<V> valueClass) {
+ return new ModelType.Builder<Multimap<K, V>>() {}
+ .where(new ModelType.Parameter<K>() {}, ModelType.of(keyClass))
+ .where(new ModelType.Parameter<V>() {}, ModelType.of(valueClass))
+ .build();
+ }
+
+ private ModelBuilder createModelBuilder() {
+ AndroidBuilder androidBuilder = registry.realize(
+ new ModelPath(ANDROID_BUILDER),
+ ModelType.of(AndroidBuilder.class));
+ AndroidComponentSpecInternal componentSpec = registry.realize(
+ new ModelPath(COMPONENTS),
+ ModelTypes.modelMap(AndroidComponentSpecInternal.class))
+ .get(AndroidComponentModelPlugin.COMPONENT_NAME);
+ VariantManager variantManager = componentSpec.getVariantManager();
+ TaskManager taskManager = registry.realize(
+ new ModelPath(TASK_MANAGER),
+ ModelType.of(TaskManager.class));
+ AndroidConfig extension = registry.realize(
+ new ModelPath(ANDROID_CONFIG_ADAPTOR),
+ ModelType.of(AndroidConfig.class));
+ ExtraModelInfo extraModelInfo = registry.realize(
+ new ModelPath(EXTRA_MODEL_INFO),
+ ModelType.of(ExtraModelInfo.class));
+ Boolean isApplication = registry.realize(
+ new ModelPath(IS_APPLICATION),
+ ModelType.of(Boolean.class));
+ NdkHandler ndkHandler = registry.realize(
+ new ModelPath(NDK_HANDLER),
+ ModelType.of(NdkHandler.class));
+ ModelMap<AndroidBinaryInternal> binaries = registry.realize(
+ new ModelPath(BINARIES),
+ ModelTypes.modelMap(AndroidBinaryInternal.class));
+ ModelMap<NdkAbiOptions> abiOptions = registry.realize(
+ new ModelPath(ModelConstants.ABI_OPTIONS),
+ ModelTypes.modelMap(NdkAbiOptions.class));
+ Multimap<String, NativeDependencyResolveResult> nativeDependencies = registry.realize(
+ new ModelPath(ModelConstants.NATIVE_DEPENDENCIES),
+ multimapModelType(String.class, NativeDependencyResolveResult.class));
+ Multimap<String, NativeDependencyResolveResult> jniLibsDependencies = registry.realize(
+ new ModelPath(ModelConstants.JNILIBS_DEPENDENCIES),
+ multimapModelType(String.class, NativeDependencyResolveResult.class));
+
+ return new ModelBuilder(
+ androidBuilder,
+ variantManager,
+ taskManager,
+ extension,
+ extraModelInfo,
+ ndkHandler,
+ new ComponentNativeLibraryFactory(
+ binaries, ndkHandler, abiOptions, nativeDependencies, jniLibsDependencies),
+ !isApplication,
+ AndroidProject.GENERATION_COMPONENT);
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ComponentNativeLibraryFactory.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ComponentNativeLibraryFactory.java
new file mode 100644
index 0000000..09dbb65
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ComponentNativeLibraryFactory.java
@@ -0,0 +1,239 @@
+/*
+ * 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.build.gradle.model;
+
+import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolveResult;
+import com.android.build.gradle.internal.dependency.NativeLibraryArtifact;
+import com.android.build.gradle.internal.model.NativeLibraryFactory;
+import com.android.build.gradle.internal.model.NativeLibraryImpl;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.build.gradle.managed.NdkAbiOptions;
+import com.android.build.gradle.managed.NdkConfig;
+import com.android.build.gradle.managed.NdkOptions;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.builder.model.NativeLibrary;
+import com.android.utils.StringHelper;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.gradle.model.ModelMap;
+import org.gradle.nativeplatform.NativeLibraryBinary;
+import org.gradle.nativeplatform.NativeLibraryBinarySpec;
+import org.gradle.nativeplatform.SharedLibraryBinary;
+import org.gradle.nativeplatform.StaticLibraryBinary;
+import org.gradle.platform.base.BinaryContainer;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Implementation of NativeLibraryFactory from in the component model plugin.
+ *
+ * The library extract information directly from the binaries.
+ */
+public class ComponentNativeLibraryFactory implements NativeLibraryFactory {
+ @NonNull
+ ModelMap<AndroidBinaryInternal> binaries;
+ @NonNull
+ NdkHandler ndkHandler;
+ @NonNull
+ ModelMap<NdkAbiOptions> abiOptions;
+ @NonNull
+ Multimap<String, NativeDependencyResolveResult> nativeDependencies;
+ @NonNull
+ Multimap<String, NativeDependencyResolveResult> jniLibsDependencies;
+
+ public ComponentNativeLibraryFactory(
+ @NonNull ModelMap<AndroidBinaryInternal> binaries,
+ @NonNull NdkHandler ndkHandler,
+ @NonNull ModelMap<NdkAbiOptions> abiOptions,
+ @NonNull Multimap<String, NativeDependencyResolveResult> nativeDependencies,
+ @NonNull Multimap<String, NativeDependencyResolveResult> jniLibsDependencies) {
+ this.binaries = binaries;
+ this.ndkHandler = ndkHandler;
+ this.abiOptions = abiOptions;
+ this.nativeDependencies = nativeDependencies;
+ this.jniLibsDependencies = jniLibsDependencies;
+ }
+
+ @NonNull
+ @Override
+ public Optional<NativeLibrary> create(
+ @NonNull VariantScope scope,
+ @NonNull String toolchainName,
+ @NonNull final Abi abi) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+
+ AndroidBinaryInternal androidBinary =
+ binaries.get(COMPONENT_NAME + StringHelper.capitalize(variantData.getName()));
+
+ if (androidBinary == null) {
+ // Binaries are not created for test variants.
+ return Optional.absent();
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ Optional<NativeLibraryBinarySpec> nativeBinary =
+ Iterables.tryFind(androidBinary.getNativeBinaries(),
+ new Predicate<NativeLibraryBinarySpec>() {
+ @Override
+ public boolean apply(NativeLibraryBinarySpec binary) {
+ return binary.getTargetPlatform().getName().equals(abi.getName());
+ }
+ });
+
+ if (!nativeBinary.isPresent()) {
+ // We don't have native binaries, but the project may be dependent on other native
+ // projects. Create a NativeLibrary to supply the debuggable library directories.
+ return Optional.<NativeLibrary>of(new NativeLibraryImpl(
+ abi.getName(),
+ toolchainName,
+ abi.getName(),
+ Collections.<File>emptyList(), /*cIncludeDirs*/
+ Collections.<File>emptyList(), /*cppIncludeDirs*/
+ Collections.<File>emptyList(), /*cSystemIncludeDirs*/
+ Collections.<File>emptyList(), /*cppSystemIncludeDirs*/
+ Collections.<String>emptyList(), /*cDefines*/
+ Collections.<String>emptyList(), /*cppDefines*/
+ Collections.<String>emptyList(), /*cFlags*/
+ Collections.<String>emptyList(), /*cppFlags*/
+ findDebuggableLibraryDirectories(variantData, androidBinary, abi)));
+ }
+
+ NdkOptions targetOptions = abiOptions.get(abi.getName());
+ List<String> cFlags = nativeBinary.get().getcCompiler().getArgs();
+ List<String> cppFlags = nativeBinary.get().getCppCompiler().getArgs();
+ if (targetOptions != null) {
+ if (!targetOptions.getCFlags().isEmpty()) {
+ cFlags = ImmutableList.copyOf(Iterables.concat(cFlags, targetOptions.getCFlags()));
+ }
+ if (!targetOptions.getCppFlags().isEmpty()) {
+ cppFlags = ImmutableList.copyOf(
+ Iterables.concat(cppFlags, targetOptions.getCppFlags()));
+ }
+ }
+
+ List<File> debuggableLibDir = findDebuggableLibraryDirectories(variantData, androidBinary, abi);
+
+ NdkConfig ndkConfig = androidBinary.getMergedNdkConfig();
+ // The DSL currently do not support all options available in the model such as the
+ // include dirs and the defines. Therefore, just pass an empty collection for now.
+ return Optional.<NativeLibrary>of(new NativeLibraryImpl(
+ ndkConfig.getModuleName(),
+ toolchainName,
+ abi.getName(),
+ Collections.<File>emptyList(), /*cIncludeDirs*/
+ Collections.<File>emptyList(), /*cppIncludeDirs*/
+ Collections.<File>emptyList(), /*cSystemIncludeDirs*/
+ ndkHandler.getStlIncludes(ndkConfig.getStl(), ndkConfig.getStlVersion(), abi),
+ Collections.<String>emptyList(), /*cDefines*/
+ Collections.<String>emptyList(), /*cppDefines*/
+ cFlags,
+ cppFlags,
+ debuggableLibDir));
+ }
+
+ /**
+ * Find all directories containing library with debug symbol.
+ * Include libraries from dependencies.
+ */
+ private List<File> findDebuggableLibraryDirectories(
+ @NonNull BaseVariantData<? extends BaseVariantOutputData> variantData,
+ @NonNull AndroidBinaryInternal binary,
+ @NonNull Abi abi) {
+ // Create LinkedHashSet to remove duplicated while maintaining order.
+ Set<File> debuggableLibDir = Sets.newLinkedHashSet();
+
+ debuggableLibDir.add(variantData.getScope().getNdkDebuggableLibraryFolders(abi));
+ addNativeDebuggableLib(debuggableLibDir, binary, abi, nativeDependencies);
+ addJniLibsDebuggableLib(debuggableLibDir, binary, abi, jniLibsDependencies);
+
+ return ImmutableList.copyOf(debuggableLibDir);
+ }
+
+ private static void addNativeDebuggableLib(
+ @NonNull Collection<File> debuggableLibDir,
+ @NonNull AndroidBinaryInternal binary,
+ @NonNull final Abi abi,
+ @NonNull Multimap<String, NativeDependencyResolveResult> dependencyMap) {
+ NativeLibraryBinarySpec nativeBinary =
+ Iterables.find(
+ binary.getNativeBinaries(),
+ new Predicate<NativeLibraryBinarySpec>() {
+ @Override
+ public boolean apply(NativeLibraryBinarySpec nativeBinary) {
+ return nativeBinary.getTargetPlatform().getName().equals(abi.getName());
+ }
+ },
+ null);
+ if (nativeBinary != null) {
+ addDebuggableLib(
+ debuggableLibDir,
+ binary,
+ abi,
+ dependencyMap.get(nativeBinary.getName()));
+ }
+ }
+
+
+ private static void addJniLibsDebuggableLib(
+ @NonNull Collection<File> debuggableLibDir,
+ @NonNull AndroidBinary binary,
+ @NonNull Abi abi,
+ @NonNull Multimap<String, NativeDependencyResolveResult> dependencyMap) {
+ addDebuggableLib(debuggableLibDir, binary, abi, dependencyMap.get(binary.getName()));
+ }
+
+
+ private static void addDebuggableLib(
+ @NonNull Collection<File> debuggableLibDir,
+ @NonNull AndroidBinary binary,
+ @NonNull Abi abi,
+ @NonNull Iterable<NativeDependencyResolveResult> dependencies) {
+ for (NativeDependencyResolveResult dependency : dependencies) {
+ for (NativeLibraryArtifact artifact : dependency.getNativeArtifacts()) {
+ for (File lib : artifact.getLibraries()) {
+ debuggableLibDir.add(lib.getParentFile());
+ }
+ }
+ for (final NativeLibraryBinary nativeBinary : dependency.getPrebuiltLibraries()) {
+ if (nativeBinary.getTargetPlatform().getName().equals(abi.getName())) {
+ File output = nativeBinary instanceof SharedLibraryBinary
+ ? ((SharedLibraryBinary) nativeBinary).getSharedLibraryFile()
+ : ((StaticLibraryBinary) nativeBinary).getStaticLibraryFile();
+ if (output != null) {
+ debuggableLibDir.add(output.getParentFile());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ExternalNativeBinarySpec.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ExternalNativeBinarySpec.java
new file mode 100644
index 0000000..6de8674
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ExternalNativeBinarySpec.java
@@ -0,0 +1,29 @@
+/*
+ * 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.build.gradle.model;
+
+import com.android.build.gradle.managed.NativeLibrary;
+
+import org.gradle.platform.base.BinarySpec;
+
+/**
+ * Binary for ExternalNativeComponentModelPlugin.
+ */
+public interface ExternalNativeBinarySpec extends BinarySpec {
+ NativeLibrary getConfig();
+ void setConfig(NativeLibrary config);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ExternalNativeComponentSpec.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ExternalNativeComponentSpec.java
new file mode 100644
index 0000000..d7c2d02
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ExternalNativeComponentSpec.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import com.android.build.gradle.managed.NativeBuildConfig;
+
+import org.gradle.platform.base.ComponentSpec;
+
+/**
+ * Component for ExternalNativeComponentModelPlugin.
+ */
+public interface ExternalNativeComponentSpec extends ComponentSpec {
+ NativeBuildConfig getConfig();
+
+ void setConfig(NativeBuildConfig config);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/JniLibsSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/JniLibsSourceSet.java
new file mode 100644
index 0000000..924acd8
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/JniLibsSourceSet.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import com.android.build.gradle.internal.AbstractNativeDependentSourceSet;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.nativeplatform.HeaderExportingSourceSet;
+
+/**
+ * LanguageSourceSet for prebuilt JNI libraries.
+ */
+public interface JniLibsSourceSet extends NativeDependentSourceSet, LanguageSourceSet {
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/LibraryComponentModelPlugin.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/LibraryComponentModelPlugin.java
new file mode 100644
index 0000000..9a49bd0
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/LibraryComponentModelPlugin.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import static com.android.build.gradle.model.ModelConstants.IS_APPLICATION;
+import static com.android.build.gradle.model.ModelConstants.TASK_MANAGER;
+
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.DependencyManager;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.variant.LibraryVariantFactory;
+import com.android.build.gradle.internal.variant.VariantFactory;
+import com.android.builder.core.AndroidBuilder;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.model.Model;
+import org.gradle.model.RuleSource;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+/**
+ * Gradle component model plugin class for 'application' projects.
+ */
+public class LibraryComponentModelPlugin implements Plugin<Project> {
+ @Override
+ public void apply(Project project) {
+ project.getPluginManager().apply(BaseComponentModelPlugin.class);
+ project.getTasks().create("assembleDefault");
+ project.getPluginManager().apply(AndroidComponentModelTestPlugin.class);
+ }
+
+ public static class Rules extends RuleSource {
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ @Model(IS_APPLICATION)
+ public static Boolean isApplication() {
+ return false;
+ }
+
+ @Model(TASK_MANAGER)
+ public static TaskManager createTaskManager(
+ AndroidConfig androidExtension,
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ SdkHandler sdkHandler,
+ ExtraModelInfo extraModelInfo,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ DependencyManager dependencyManager = new DependencyManager(project, extraModelInfo);
+
+ return new LibraryComponentTaskManager(
+ project,
+ androidBuilder,
+ dataBindingBuilder,
+ androidExtension,
+ sdkHandler,
+ dependencyManager,
+ toolingRegistry);
+ }
+
+ @Model
+ public static VariantFactory createVariantFactory(
+ ServiceRegistry serviceRegistry,
+ AndroidBuilder androidBuilder,
+ AndroidConfig extension) {
+ Instantiator instantiator = serviceRegistry.get(Instantiator.class);
+ return new LibraryVariantFactory(instantiator, androidBuilder, extension);
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/LibraryComponentTaskManager.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/LibraryComponentTaskManager.java
new file mode 100644
index 0000000..637e311
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/LibraryComponentTaskManager.java
@@ -0,0 +1,64 @@
+/*
+ * 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.build.gradle.model;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidConfig;
+import com.android.build.gradle.internal.DependencyManager;
+import com.android.build.gradle.internal.LibraryTaskManager;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.builder.core.AndroidBuilder;
+import com.google.common.collect.ImmutableList;
+
+import org.gradle.api.Project;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+import java.util.Collection;
+
+/**
+ * TaskManager for creating tasks in an Android library project with component model plugin.
+ */
+public class LibraryComponentTaskManager extends LibraryTaskManager {
+
+ public LibraryComponentTaskManager(
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ super(project, androidBuilder, dataBindingBuilder, extension, sdkHandler, dependencyManager,
+ toolingRegistry);
+ isComponentModelPlugin = true;
+ }
+
+ @Override
+ protected Collection<Object> getNdkBuildable(BaseVariantData variantData) {
+ NdkComponentModelPlugin plugin = project.getPlugins().getPlugin(NdkComponentModelPlugin.class);
+ return ImmutableList.<Object>copyOf(plugin.getBinaries(variantData.getVariantConfiguration()));
+ }
+
+ @Override
+ public void configureScopeForNdk(@NonNull VariantScope scope) {
+ NdkComponentModelPlugin.configureScopeForNdk(scope);
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ModelConstants.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ModelConstants.java
new file mode 100644
index 0000000..1598335
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/ModelConstants.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+/**
+ * Component model path names.
+ */
+public class ModelConstants {
+
+ public static final String ANDROID_BUILDER = "androidBuilder";
+
+ public static final String ANDROID_CONFIG_ADAPTOR = "androidConfigAdaptor";
+
+ public static final String ARTIFACTS = "artifacts";
+
+ public static final String BINARIES = "binaries";
+
+ public static final String COMPONENTS = "components";
+
+ public static final String EXTERNAL_CONFIG_FILES = "nativeBuild";
+
+ public static final String EXTERNAL_BUILD_CONFIG = "nativeBuildConfig";
+
+ public static final String EXTRA_MODEL_INFO = "extraModelInfo";
+
+ public static final String IS_APPLICATION = "isApplication";
+
+ public static final String JNILIBS_DEPENDENCIES = "jniLibsDependencies";
+
+ public static final String NATIVE_DEPENDENCIES = "nativeDependencies";
+
+ public static final String NDK_HANDLER = "ndkHandler";
+
+ public static final String TASK_MANAGER = "taskManager";
+
+ public static final String ABI_OPTIONS = "android.abis";
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeComponentModelBuilder.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeComponentModelBuilder.java
new file mode 100644
index 0000000..037c185
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeComponentModelBuilder.java
@@ -0,0 +1,173 @@
+/*
+ * 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.build.gradle.model;
+
+import static com.android.build.gradle.model.ModelConstants.EXTERNAL_BUILD_CONFIG;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.model.NativeAndroidProjectImpl;
+import com.android.build.gradle.internal.model.NativeArtifactImpl;
+import com.android.build.gradle.internal.model.NativeFileImpl;
+import com.android.build.gradle.internal.model.NativeFolderImpl;
+import com.android.build.gradle.internal.model.NativeSettingsImpl;
+import com.android.build.gradle.internal.model.NativeToolchainImpl;
+import com.android.build.gradle.managed.NativeBuildConfig;
+import com.android.build.gradle.managed.NativeLibrary;
+import com.android.build.gradle.managed.NativeSourceFile;
+import com.android.build.gradle.managed.NativeSourceFolder;
+import com.android.builder.Version;
+import com.android.builder.model.NativeAndroidProject;
+import com.android.builder.model.NativeArtifact;
+import com.android.builder.model.NativeFolder;
+import com.android.builder.model.NativeSettings;
+import com.android.builder.model.NativeToolchain;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Project;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.tooling.provider.model.ToolingModelBuilder;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Builder for the {@link NativeAndroidProject} model.
+ */
+public class NativeComponentModelBuilder implements ToolingModelBuilder {
+
+ @NonNull
+ ModelRegistry registry;
+ @NonNull
+ Map<List<String>, NativeSettings> settingsMap = Maps.newHashMap();
+ int settingIndex = 0;
+ NativeBuildConfig config;
+
+ public NativeComponentModelBuilder(@NonNull ModelRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public boolean canBuild(String modelName) {
+ return modelName.equals(NativeAndroidProject.class.getName());
+ }
+
+ /**
+ * Initialize private members.
+ */
+ private void initialize() {
+ config = registry.realize(
+ new ModelPath(EXTERNAL_BUILD_CONFIG),
+ ModelType.of(NativeBuildConfig.class));
+ settingIndex = 0;
+ }
+
+ @Override
+ public Object buildAll(String modelName, Project project) {
+ initialize();
+
+ List<NativeArtifact> artifacts = createNativeArtifacts();
+ List<NativeToolchain> toolchains = createNativeToolchains();
+ Collection<NativeSettings> settings = ImmutableList.copyOf(settingsMap.values());
+ Map<String, String> extensions = createFileExtensionMap();
+ return new NativeAndroidProjectImpl(
+ Version.ANDROID_GRADLE_PLUGIN_VERSION,
+ project.getName(),
+ ImmutableList.copyOf(config.getBuildFiles()),
+ artifacts,
+ toolchains,
+ settings,
+ extensions,
+ Version.BUILDER_NATIVE_MODEL_API_VERSION);
+
+ }
+
+ private List<NativeArtifact> createNativeArtifacts() {
+ List<NativeArtifact> artifacts = Lists.newArrayList();
+
+ for (NativeLibrary lib : config.getLibraries()) {
+ List<NativeFolder> folders = Lists.newArrayList();
+ for (NativeSourceFolder src : lib.getFolders()) {
+ folders.add(
+ new NativeFolderImpl(
+ src.getSrc(),
+ ImmutableMap.of(
+ "c", getSettingsName(src.getCFlags()),
+ "c++", getSettingsName(src.getCppFlags())),
+ src.getWorkingDirectory()));
+ }
+ List<com.android.builder.model.NativeFile> files = Lists.newArrayList();
+ for (NativeSourceFile src : lib.getFiles()) {
+ files.add(new NativeFileImpl(
+ src.getSrc(),
+ getSettingsName(src.getFlags()),
+ src.getWorkingDirectory()));
+ }
+ NativeArtifact artifact = new NativeArtifactImpl(
+ lib.getName(),
+ lib.getToolchain(),
+ Objects.firstNonNull(lib.getGroupName(), ""),
+ folders,
+ files,
+ ImmutableList.copyOf(lib.getExportedHeaders()),
+ lib.getOutput());
+ artifacts.add(artifact);
+ }
+ return artifacts;
+ }
+
+ private String getSettingsName(List<String> flags) {
+ // Copy flags to ensure it is serializable.
+ List<String> flagsCopy = ImmutableList.copyOf(flags);
+ NativeSettings setting = settingsMap.get(flags);
+ if (setting == null) {
+ setting = new NativeSettingsImpl("setting" + settingIndex, flagsCopy);
+ settingsMap.put(flagsCopy, setting);
+ settingIndex++;
+ }
+ return setting.getName();
+ }
+
+ private List<NativeToolchain> createNativeToolchains() {
+ List<NativeToolchain> toolchains = Lists.newArrayList();
+ for (NativeToolchain toolchain : config.getToolchains().values()) {
+ toolchains.add(new NativeToolchainImpl(
+ toolchain.getName(),
+ toolchain.getCCompilerExecutable(),
+ toolchain.getCppCompilerExecutable()));
+ }
+ return toolchains;
+ }
+
+ private Map<String, String> createFileExtensionMap() {
+ Map<String, String> extensions = Maps.newHashMap();
+ for (String ext : config.getCFileExtensions()) {
+ extensions.put(ext, "c");
+ }
+ for (String ext : config.getCppFileExtensions()) {
+ extensions.put(ext, "c++");
+ }
+ return extensions;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeDependentSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeDependentSourceSet.java
new file mode 100644
index 0000000..5b90a16
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeDependentSourceSet.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import com.android.build.gradle.internal.dependency.AndroidNativeDependencySpecContainer;
+
+import org.gradle.api.Action;
+import org.gradle.language.base.LanguageSourceSet;
+
+/**
+ * Interface for {@link LanguageSourceSet} that supports native dependencies.
+ */
+public interface NativeDependentSourceSet extends LanguageSourceSet {
+ AndroidNativeDependencySpecContainer getDependencies();
+
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeSourceSet.java
new file mode 100644
index 0000000..8f13497
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NativeSourceSet.java
@@ -0,0 +1,27 @@
+/*
+ * 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.build.gradle.model;
+
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.nativeplatform.HeaderExportingSourceSet;
+
+/**
+ * LanguageSourceSet for native sources.
+ */
+public interface NativeSourceSet extends NativeDependentSourceSet, LanguageSourceSet,
+ HeaderExportingSourceSet {
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NdkComponentModelPlugin.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NdkComponentModelPlugin.java
new file mode 100644
index 0000000..1ab59f9
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NdkComponentModelPlugin.java
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME;
+import static com.android.build.gradle.model.ModelConstants.ARTIFACTS;
+import static com.android.build.gradle.model.ModelConstants.EXTERNAL_BUILD_CONFIG;
+import static com.android.build.gradle.model.ModelConstants.NATIVE_DEPENDENCIES;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.NativeDependencyLinkage;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.ProductFlavorCombo;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.dependency.ArtifactContainer;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolveResult;
+import com.android.build.gradle.internal.dependency.NativeLibraryArtifact;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.managed.BuildType;
+import com.android.build.gradle.managed.NativeBuildConfig;
+import com.android.build.gradle.managed.NativeLibrary;
+import com.android.build.gradle.managed.NativeSourceFolder;
+import com.android.build.gradle.managed.NativeToolchain;
+import com.android.build.gradle.managed.NdkAbiOptions;
+import com.android.build.gradle.managed.NdkConfig;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.build.gradle.model.internal.AndroidComponentSpecInternal;
+import com.android.build.gradle.model.internal.DefaultNativeSourceSet;
+import com.android.build.gradle.ndk.internal.NdkConfiguration;
+import com.android.build.gradle.ndk.internal.NdkExtensionConvention;
+import com.android.build.gradle.ndk.internal.NdkNamingScheme;
+import com.android.build.gradle.ndk.internal.StlNativeToolSpecification;
+import com.android.build.gradle.ndk.internal.ToolchainConfiguration;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.core.VariantConfiguration;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.Action;
+import org.gradle.api.BuildableModelElement;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.c.plugins.CPlugin;
+import org.gradle.language.cpp.plugins.CppPlugin;
+import org.gradle.language.nativeplatform.HeaderExportingSourceSet;
+import org.gradle.model.Defaults;
+import org.gradle.model.Finalize;
+import org.gradle.model.Model;
+import org.gradle.model.ModelMap;
+import org.gradle.model.Mutate;
+import org.gradle.model.Path;
+import org.gradle.model.RuleSource;
+import org.gradle.model.Validate;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.model.internal.type.ModelTypes;
+import org.gradle.nativeplatform.BuildTypeContainer;
+import org.gradle.nativeplatform.FlavorContainer;
+import org.gradle.nativeplatform.NativeBinarySpec;
+import org.gradle.nativeplatform.NativeDependencySet;
+import org.gradle.nativeplatform.NativeLibraryBinary;
+import org.gradle.nativeplatform.NativeLibraryBinarySpec;
+import org.gradle.nativeplatform.NativeLibrarySpec;
+import org.gradle.nativeplatform.SharedLibraryBinary;
+import org.gradle.nativeplatform.SharedLibraryBinarySpec;
+import org.gradle.nativeplatform.StaticLibraryBinarySpec;
+import org.gradle.nativeplatform.platform.NativePlatform;
+import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
+import org.gradle.platform.base.BinaryContainer;
+import org.gradle.platform.base.BinarySpec;
+import org.gradle.platform.base.ComponentSpecContainer;
+import org.gradle.platform.base.LanguageType;
+import org.gradle.platform.base.LanguageTypeBuilder;
+import org.gradle.platform.base.PlatformContainer;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+/**
+ * Plugin for Android NDK applications.
+ */
+public class NdkComponentModelPlugin implements Plugin<Project> {
+ @NonNull
+ private final ToolingModelBuilderRegistry toolingRegistry;
+ @NonNull
+ private final ModelRegistry modelRegistry;
+
+ @Inject
+ private NdkComponentModelPlugin(
+ @NonNull ToolingModelBuilderRegistry toolingRegistry,
+ @NonNull ModelRegistry modelRegistry) {
+ this.toolingRegistry = toolingRegistry;
+ this.modelRegistry = modelRegistry;
+ }
+
+ @Override
+ public void apply(Project project) {
+ project.getPluginManager().apply(AndroidComponentModelPlugin.class);
+ project.getPluginManager().apply(CPlugin.class);
+ project.getPluginManager().apply(CppPlugin.class);
+
+ toolingRegistry.register(new NativeComponentModelBuilder(modelRegistry));
+ }
+
+ public static class Rules extends RuleSource {
+
+ @LanguageType
+ public static void registerNativeSourceSet(LanguageTypeBuilder<NativeSourceSet> builder) {
+ builder.setLanguageName("jni");
+ builder.defaultImplementation(DefaultNativeSourceSet.class);
+ }
+
+
+ @Defaults
+ public static void initializeNdkConfig(@Path("android.ndk") NdkConfig ndk) {
+ ndk.setModuleName("");
+ ndk.setToolchain("");
+ ndk.setToolchainVersion("");
+ ndk.setStl("");
+ ndk.setRenderscriptNdkMode(false);
+ }
+
+ @Finalize
+ public static void setDefaultNdkExtensionValue(@Path("android.ndk") NdkConfig ndkConfig) {
+ NdkExtensionConvention.setExtensionDefault(ndkConfig);
+ }
+
+ @Validate
+ public static void checkNdkDir(
+ NdkHandler ndkHandler,
+ @Path("android.ndk") NdkConfig ndkConfig) {
+ if (!ndkConfig.getModuleName().isEmpty() && !ndkHandler.isNdkDirConfigured()) {
+ throw new InvalidUserDataException(
+ "NDK location not found. Define location with ndk.dir in the "
+ + "local.properties file or with an ANDROID_NDK_HOME environment "
+ + "variable.");
+ }
+ if (ndkHandler.isNdkDirConfigured()) {
+ //noinspection ConstantConditions - isNdkDirConfigured ensures getNdkDirectory is not null
+ if (!ndkHandler.getNdkDirectory().exists()) {
+ throw new InvalidUserDataException(
+ "Specified NDK location does not exists. Please ensure ndk.dir in "
+ + "local.properties file or ANDROID_NDK_HOME is configured "
+ + "correctly.");
+
+ }
+ }
+ }
+
+ @Defaults
+ public static void addDefaultNativeSourceSet(
+ @Path("android.sources") ModelMap<FunctionalSourceSet> sources) {
+ sources.beforeEach(new Action<FunctionalSourceSet>() {
+ @Override
+ public void execute(FunctionalSourceSet sourceSet) {
+ sourceSet.create("jni", NativeSourceSet.class, new Action<NativeSourceSet>() {
+ @Override
+ public void execute(NativeSourceSet nativeSourceSet) {
+ nativeSourceSet.getSource().srcDir("src/" + nativeSourceSet.getParentName() + "/" + "jni");
+ }
+ });
+ }
+ });
+ }
+
+ @Model(ModelConstants.NDK_HANDLER)
+ public static NdkHandler ndkHandler(
+ ProjectIdentifier projectId,
+ @Path("android.compileSdkVersion") String compileSdkVersion,
+ @Path("android.ndk") NdkConfig ndkConfig) {
+ while (projectId.getParentIdentifier() != null) {
+ projectId = projectId.getParentIdentifier();
+ }
+
+ return new NdkHandler(
+ projectId.getProjectDir(),
+ Objects.firstNonNull(ndkConfig.getPlatformVersion(), compileSdkVersion),
+ ndkConfig.getToolchain(),
+ ndkConfig.getToolchainVersion());
+ }
+
+ @Defaults
+ public static void initBuildTypeNdk(
+ @Path("android.buildTypes") ModelMap<BuildType> buildTypes) {
+ buildTypes.named(
+ BuilderConstants.DEBUG,
+ new Action<BuildType>() {
+ @Override
+ public void execute(BuildType buildType) {
+ if (buildType.getNdk().getDebuggable() == null) {
+ buildType.getNdk().setDebuggable(true);
+ }
+ }
+ });
+ }
+
+ @Mutate
+ public static void createAndroidPlatforms(PlatformContainer platforms,
+ NdkHandler ndkHandler) {
+ if (!ndkHandler.isNdkDirConfigured()) {
+ return;
+ }
+ // Create android platforms.
+ ToolchainConfiguration.configurePlatforms(platforms, ndkHandler);
+ }
+
+ @Validate
+ public static void validateAbi(@Path("android.abis") ModelMap<NdkAbiOptions> abiConfigs) {
+ abiConfigs.afterEach(new Action<NdkAbiOptions>() {
+ @Override
+ public void execute(NdkAbiOptions abiOptions) {
+ if (Abi.getByName(abiOptions.getName()) == null) {
+ throw new InvalidUserDataException("Target ABI '" + abiOptions.getName()
+ + "' is not supported.");
+ }
+ }
+ });
+ }
+
+ @Mutate
+ public static void createToolchains(
+ NativeToolChainRegistry toolchainRegistry,
+ @Path("android.abis") ModelMap<NdkAbiOptions> abis,
+ @Path("android.ndk") NdkConfig ndkConfig,
+ NdkHandler ndkHandler) {
+ if (!ndkHandler.isNdkDirConfigured()) {
+ return;
+ }
+ // Create toolchain for each ABI.
+ ToolchainConfiguration.configureToolchain(
+ toolchainRegistry,
+ ndkConfig.getToolchain(),
+ abis,
+ ndkHandler);
+ }
+
+ @Mutate
+ public static void createNativeBuildTypes(BuildTypeContainer nativeBuildTypes,
+ @Path("android.buildTypes") ModelMap<BuildType> androidBuildTypes) {
+ for (BuildType buildType : androidBuildTypes.values()) {
+ nativeBuildTypes.maybeCreate(buildType.getName());
+ }
+ }
+
+ @Mutate
+ public static void createNativeFlavors(
+ FlavorContainer nativeFlavors,
+ List<ProductFlavorCombo<ProductFlavor>> androidFlavorGroups) {
+ if (androidFlavorGroups.isEmpty()) {
+ // Create empty native flavor to override Gradle's default name.
+ nativeFlavors.maybeCreate("");
+ } else {
+ for (ProductFlavorCombo group : androidFlavorGroups) {
+ nativeFlavors.maybeCreate(group.getName());
+ }
+ }
+ }
+
+ @Mutate
+ public static void createNativeLibrary(
+ final ComponentSpecContainer specs,
+ @Path("android.ndk") final NdkConfig ndkConfig,
+ final NdkHandler ndkHandler,
+ @Path("android.sources") final ModelMap<FunctionalSourceSet> sources,
+ @Path("buildDir") final File buildDir,
+ final ServiceRegistry serviceRegistry) {
+ if (!ndkHandler.isNdkDirConfigured()) {
+ return;
+ }
+ if (!ndkConfig.getModuleName().isEmpty()) {
+ specs.create(
+ ndkConfig.getModuleName(),
+ NativeLibrarySpec.class,
+ new Action<NativeLibrarySpec>() {
+ @Override
+ public void execute(final NativeLibrarySpec nativeLib) {
+ ((AndroidComponentSpecInternal) specs.get(COMPONENT_NAME))
+ .setNativeLibrary(nativeLib);
+ NdkConfiguration.configureProperties(
+ nativeLib,
+ sources,
+ buildDir,
+ ndkHandler,
+ serviceRegistry);
+ nativeLib.getBinaries().all(new Action<BinarySpec>() {
+ @Override
+ public void execute(BinarySpec binarySpec) {
+ NdkConfiguration.configureNativeBinaryOutputFile(
+ (NativeLibraryBinarySpec)binarySpec,
+ buildDir,
+ ndkConfig.getModuleName());
+ }
+ });
+ }
+ });
+ AndroidComponentSpecInternal androidSpecs =
+ (AndroidComponentSpecInternal) specs.get(COMPONENT_NAME);
+ androidSpecs.setNativeLibrary(
+ (NativeLibrarySpec) specs.get(ndkConfig.getModuleName()));
+ }
+ }
+
+ /**
+ * Find the native dependency for each native binaries.
+ *
+ * TODO: Remove duplication of functionality with NdkConfiguration.configureProperties.
+ * We need to predict the NativeBinarySpec that will be produce instead of creating this map
+ * after the binaries are created.
+ */
+ @Model(NATIVE_DEPENDENCIES)
+ public static Multimap<String, NativeDependencyResolveResult> resolveNativeDependencies(
+ ModelMap<NativeLibraryBinarySpec> nativeBinaries,
+ @Path("android.sources") final ModelMap<FunctionalSourceSet> sources,
+ final ServiceRegistry serviceRegistry) {
+ Multimap<String, NativeDependencyResolveResult> dependencies =
+ ArrayListMultimap.create();
+ for (NativeLibraryBinarySpec nativeBinary : nativeBinaries.values()) {
+ Collection<NativeSourceSet> jniSources =
+ NdkConfiguration.findNativeSourceSets(nativeBinary, sources).values();
+
+ for (NativeSourceSet jniSource : jniSources) {
+ dependencies.put(
+ nativeBinary.getName(),
+ NdkConfiguration.resolveDependency(
+ serviceRegistry,
+ nativeBinary,
+ jniSource));
+ }
+ }
+ return dependencies;
+ }
+
+ @Mutate
+ public static void createAdditionalTasksForNatives(
+ final ModelMap<Task> tasks,
+ ModelMap<AndroidComponentSpec> specs,
+ @Path("android.ndk") final NdkConfig ndkConfig,
+ @Path(NATIVE_DEPENDENCIES) final Multimap<String, NativeDependencyResolveResult> dependencies,
+ final NdkHandler ndkHandler,
+ ModelMap<AndroidBinaryInternal> binaries,
+ @Path("buildDir") final File buildDir) {
+ if (!ndkHandler.isNdkDirConfigured()) {
+ return;
+ }
+ final AndroidComponentSpecInternal androidSpec =
+ (AndroidComponentSpecInternal) specs.get(COMPONENT_NAME);
+ if (androidSpec.getNativeLibrary() != null) {
+ for (AndroidBinaryInternal binary : binaries.values()) {
+ for (NativeBinarySpec nativeBinary : binary.getNativeBinaries()) {
+ NdkConfiguration.createTasks(
+ tasks,
+ nativeBinary,
+ buildDir,
+ binary.getMergedNdkConfig(),
+ ndkHandler,
+ dependencies);
+ }
+ }
+ }
+ }
+
+ @Mutate
+ public static void configureNativeBinary(
+ BinaryContainer binaries,
+ ComponentSpecContainer specs,
+ @Path("android.ndk") final NdkConfig ndkConfig,
+ final NdkHandler ndkHandler) {
+ final NativeLibrarySpec library = specs.withType(NativeLibrarySpec.class)
+ .get(ndkConfig.getModuleName());
+
+ final ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ for(Abi abi : NdkHandler.getAbiList()) {
+ builder.add(abi.getName());
+ }
+ final ImmutableSet<String> supportedAbis = builder.build();
+
+ binaries.withType(
+ AndroidBinaryInternal.class,
+ new Action<AndroidBinaryInternal>() {
+ @Override
+ public void execute(AndroidBinaryInternal binary) {
+ binary.computeMergedNdk(
+ ndkConfig,
+ binary.getProductFlavors(),
+ binary.getBuildType());
+ if (binary.getMergedNdkConfig().getAbiFilters().isEmpty()) {
+ binary.getMergedNdkConfig().getAbiFilters().addAll(supportedAbis);
+ }
+
+ if (library != null) {
+ Collection<NativeLibraryBinarySpec> nativeBinaries =
+ getNativeBinaries(
+ library,
+ binary.getBuildType(),
+ binary.getProductFlavors());
+ for (NativeLibraryBinarySpec nativeBin : nativeBinaries) {
+ if (binary.getMergedNdkConfig().getAbiFilters().contains(
+ nativeBin.getTargetPlatform().getName())) {
+ NdkConfiguration.configureBinary(
+ nativeBin,
+ binary.getMergedNdkConfig(),
+ ndkHandler);
+ binary.getNativeBinaries().add(nativeBin);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Create external build config to allow NativeComponentModelBuilder to create native model.
+ */
+ @Model(EXTERNAL_BUILD_CONFIG)
+ public static void createNativeBuildModel(
+ NativeBuildConfig config,
+ ModelMap<AndroidBinaryInternal> binaries,
+ final NdkHandler ndkHandler) {
+
+ config.getCFileExtensions().addAll(NdkConfiguration.C_FILE_EXTENSIONS);
+ config.getCppFileExtensions().addAll(NdkConfiguration.CPP_FILE_EXTENSIONS);
+
+ for (final AndroidBinaryInternal binary : binaries.values()) {
+ for (final NativeLibraryBinarySpec nativeBinary : binary.getNativeBinaries()) {
+ if (!(nativeBinary instanceof SharedLibraryBinarySpec)) {
+ continue;
+ }
+ final String abi = nativeBinary.getTargetPlatform().getName();
+ config.getLibraries().create(
+ binary.getName() + '-' + abi,
+ new CreateNativeLibraryAction(
+ binary,
+ (SharedLibraryBinarySpec) nativeBinary));
+ }
+ }
+ for (final Abi abi : ndkHandler.getSupportedAbis()) {
+ config.getToolchains().create("ndk-" + abi.getName(),
+ new Action<NativeToolchain>() {
+ @Override
+ public void execute(NativeToolchain nativeToolchain) {
+ nativeToolchain.setCCompilerExecutable(
+ ndkHandler.getCCompiler(abi));
+ nativeToolchain.setCppCompilerExecutable(
+ ndkHandler.getCppCompiler(abi));
+ }
+ });
+ }
+ }
+ private static class CreateNativeLibraryAction implements Action<NativeLibrary> {
+ @NonNull
+ private final AndroidBinaryInternal binary;
+ @NonNull
+ private final SharedLibraryBinarySpec nativeBinary;
+
+ public CreateNativeLibraryAction(
+ @NonNull AndroidBinaryInternal binary,
+ @NonNull SharedLibraryBinarySpec nativeBinary) {
+ this.binary = binary;
+ this.nativeBinary = nativeBinary;
+ }
+
+ @Override
+ public void execute(NativeLibrary nativeLibrary) {
+ final String abi = nativeBinary.getTargetPlatform().getName();
+
+ nativeLibrary.setOutput(nativeBinary.getSharedLibraryFile());
+ Set<File> srcFolders = Sets.newHashSet();
+ nativeLibrary.setGroupName(binary.getName());
+
+ final List<String> cFlags = nativeBinary.getcCompiler().getArgs();
+ final List<String> cppFlags = nativeBinary.getCppCompiler().getArgs();
+
+ for (LanguageSourceSet sourceSet : nativeBinary.getSources()) {
+ srcFolders.addAll(sourceSet.getSource().getSrcDirs());
+ if (sourceSet instanceof HeaderExportingSourceSet) {
+ Set<File> headerDirs =
+ ((HeaderExportingSourceSet) sourceSet).getExportedHeaders()
+ .getSrcDirs();
+ for (File headerDir : headerDirs) {
+ if (!nativeLibrary.getExportedHeaders().contains(headerDir)) {
+ nativeLibrary.getExportedHeaders().add(headerDir);
+
+ // Exported headers are not part of the binary's flag. Need to add
+ // it manually.
+ cFlags.add("-I" + headerDir);
+ cppFlags.add("-I" + headerDir);
+ }
+ }
+ }
+ }
+
+ for (NativeDependencySet dependency : nativeBinary.getLibs()) {
+ for (File includeDir : dependency.getIncludeRoots()) {
+ cFlags.add("-I" + includeDir);
+ cppFlags.add("-I" + includeDir);
+ }
+ }
+
+ for (final File srcFolder : srcFolders) {
+ nativeLibrary.getFolders().create(
+ new Action<NativeSourceFolder>() {
+ @Override
+ public void execute(NativeSourceFolder nativeSourceFolder) {
+ nativeSourceFolder.setSrc(srcFolder);
+ nativeSourceFolder.getCFlags().addAll(cFlags);
+ nativeSourceFolder.getCppFlags().addAll(cppFlags);
+ }
+ });
+ }
+ nativeLibrary.setToolchain("ndk-" + abi);
+ }
+ }
+
+ @Finalize
+ public static void attachNativeTasksToAndroidBinary(ModelMap<AndroidBinaryInternal> binaries) {
+ binaries.afterEach(new Action<AndroidBinaryInternal>() {
+ @Override
+ public void execute(AndroidBinaryInternal binary) {
+ for (NativeLibraryBinarySpec nativeBinary : binary.getNativeBinaries()) {
+ if (binary.getTargetAbi().isEmpty() || binary.getTargetAbi().contains(
+ nativeBinary.getTargetPlatform().getName())) {
+ binary.getBuildTask().dependsOn(
+ NdkNamingScheme.getNdkBuildTaskName(nativeBinary));
+ }
+ }
+ }
+ });
+ }
+
+ @Model(ARTIFACTS)
+ public static void createNativeLibraryArtifacts(
+ ArtifactContainer artifactContainer,
+ ModelMap<AndroidBinaryInternal> binaries,
+ @Path("android.sources") final ModelMap<FunctionalSourceSet> sources,
+ final ModelMap<Task> tasks,
+ @Path(NATIVE_DEPENDENCIES) final Multimap<String, NativeDependencyResolveResult> dependencies,
+ NdkHandler ndkHandler,
+ ProjectIdentifier project) {
+ for(final AndroidBinaryInternal binary : binaries.values()) {
+ for (final NativeLibraryBinarySpec nativeBinary : binary.getNativeBinaries()) {
+ final String linkage = nativeBinary instanceof SharedLibraryBinarySpec
+ ? "shared"
+ : "static";
+ String name = Joiner.on('-').join(
+ binary.getName(),
+ nativeBinary.getTargetPlatform().getName(),
+ linkage);
+
+ artifactContainer.getNativeArtifacts().create(
+ name,
+ new CreateNativeLibraryArtifactAction(
+ binary,
+ nativeBinary,
+ dependencies.get(nativeBinary.getName()),
+ ndkHandler,
+ project));
+ }
+ }
+ }
+
+ private static class CreateNativeLibraryArtifactAction
+ implements Action<NativeLibraryArtifact> {
+ private final AndroidBinaryInternal binary;
+ private final NativeLibraryBinarySpec nativeBinary;
+ private final Collection<NativeDependencyResolveResult> dependencies;
+ private final NdkHandler ndkHandler;
+ private final ProjectIdentifier projectId;
+
+ public CreateNativeLibraryArtifactAction(AndroidBinaryInternal binary,
+ NativeLibraryBinarySpec nativeBinary,
+ Collection<NativeDependencyResolveResult> dependencies,
+ NdkHandler ndkHandler,
+ ProjectIdentifier projectId) {
+ this.binary = binary;
+ this.nativeBinary = nativeBinary;
+ this.dependencies = dependencies;
+ this.ndkHandler = ndkHandler;
+ this.projectId = projectId;
+ }
+
+ @Override
+ public void execute(NativeLibraryArtifact artifact) {
+
+ final NativeDependencyLinkage linkage =
+ nativeBinary instanceof SharedLibraryBinarySpec
+ ? NativeDependencyLinkage.SHARED
+ : NativeDependencyLinkage.STATIC;
+ File output = nativeBinary instanceof SharedLibraryBinarySpec
+ ? ((SharedLibraryBinarySpec) nativeBinary).getSharedLibraryFile()
+ : ((StaticLibraryBinarySpec) nativeBinary).getStaticLibraryFile();
+
+ artifact.getLibraries().add(output);
+ artifact.setBuildType(binary.getBuildType().getName());
+ for (ProductFlavor flavor : binary.getProductFlavors()) {
+ artifact.getProductFlavors().add(flavor.getName());
+ }
+ artifact.setVariantName(binary.getName());
+ artifact.setAbi(nativeBinary.getTargetPlatform().getName());
+ artifact.setLinkage(linkage);
+
+ List<Object> builtBy = Lists.newArrayList();
+ builtBy.add(nativeBinary);
+ builtBy.add(projectId.getPath() + ":" + NdkNamingScheme.getNdkBuildTaskName(nativeBinary));
+ artifact.setBuiltBy(builtBy);
+ for (LanguageSourceSet sourceSet : nativeBinary.getSources()) {
+ if (sourceSet instanceof HeaderExportingSourceSet) {
+ HeaderExportingSourceSet source = (HeaderExportingSourceSet) sourceSet;
+ artifact.getExportedHeaderDirectories().addAll(
+ source.getExportedHeaders().getSrcDirs());
+ }
+ }
+ String stl = binary.getMergedNdkConfig().getStl();
+ if (stl.endsWith("_shared")) {
+ NativePlatform abi = nativeBinary.getTargetPlatform();
+ final StlNativeToolSpecification stlConfig = new StlNativeToolSpecification(
+ ndkHandler,
+ stl,
+ binary.getMergedNdkConfig().getStlVersion(),
+ abi);
+ artifact.getLibraries().add(stlConfig.getStlLib(abi.getName()));
+ }
+
+ // Include transitive dependencies.
+ // Dynamic objects from dependencies needs to be added to the library list.
+ Abi abi = Abi.getByName(nativeBinary.getTargetPlatform().getName());
+ assert abi != null;
+ for (NativeDependencyResolveResult dependency : dependencies) {
+ for (NativeLibraryBinary lib : dependency.getPrebuiltLibraries()) {
+ if (lib instanceof SharedLibraryBinary
+ && abi.getName().equals(lib.getTargetPlatform().getName())) {
+ artifact.getLibraries().add(((SharedLibraryBinary) lib).getSharedLibraryFile());
+ }
+ }
+ Collection<NativeLibraryArtifact> artifacts = dependency.getNativeArtifacts();
+ for (NativeLibraryArtifact dep : artifacts) {
+ if (abi.getName().equals(dep.getAbi())) {
+ Iterables.addAll(
+ artifact.getLibraries(),
+ Iterables.filter(dep.getLibraries(), SHARED_OBJECT_FILTER));
+ }
+ }
+ }
+ }
+ }
+
+ private static final Predicate<File> SHARED_OBJECT_FILTER =
+ new Predicate<File>() {
+ @Override
+ public boolean apply(File file) {
+ return file.getName().endsWith(".so");
+ }
+ };
+
+ /**
+ * Remove unintended tasks created by Gradle native plugin from task list.
+ *
+ * Gradle native plugins creates static library tasks automatically. This method removes
+ * them to avoid cluttering the task list.
+ */
+ @Mutate
+ public static void hideNativeTasks(
+ TaskContainer tasks,
+ ModelMap<NativeLibraryBinarySpec> binaries) {
+ // Gradle do not support a way to remove created tasks. The best workaround is to clear
+ // the group of the task and have another task depends on it. Therefore, we have to
+ // create a dummy task to depend on all the tasks that we do not want to show up on the
+ // task list. The dummy task dependsOn itself, effectively making it non-executable and
+ // invisible unless the --all option is use.
+ final Task nonExecutableTask = tasks.create("nonExecutableTask");
+ nonExecutableTask.dependsOn(nonExecutableTask);
+ nonExecutableTask.setDescription(
+ "Dummy task to hide other unwanted tasks in the task list.");
+
+ for (NativeLibraryBinarySpec binary : binaries.values()) {
+ Task buildTask = binary.getBuildTask();
+ nonExecutableTask.dependsOn(buildTask);
+ buildTask.setGroup(null);
+ }
+ }
+ }
+
+
+ public static void configureScopeForNdk(VariantScope scope) {
+ VariantConfiguration config = scope.getVariantConfiguration();
+ ImmutableSet.Builder<File> builder = ImmutableSet.builder();
+ for (Abi abi : NdkHandler.getAbiList()) {
+ scope.addNdkDebuggableLibraryFolders(
+ abi,
+ new File(
+ scope.getGlobalScope().getBuildDir(),
+ NdkNamingScheme.getDebugLibraryDirectoryName(
+ config.getBuildType().getName(),
+ config.getFlavorName(),
+ abi.getName())));
+
+ // Return the parent directory of the binaries' output.
+ // If output directory is "/path/to/lib/platformName". We want to return
+ // "/path/to/lib".
+ builder.add(new File(
+ scope.getGlobalScope().getBuildDir(),
+ NdkNamingScheme.getOutputDirectoryName(
+ config.getBuildType().getName(),
+ config.getFlavorName(),
+ abi.getName())).getParentFile());
+ }
+ scope.setNdkSoFolder(builder.build());
+ }
+
+
+ private static Collection<NativeLibraryBinarySpec> getNativeBinaries(
+ NativeLibrarySpec library,
+ final BuildType buildType,
+ final List<ProductFlavor> productFlavors) {
+ final ProductFlavorCombo<ProductFlavor> flavorGroup =
+ new ProductFlavorCombo<ProductFlavor>(productFlavors);
+ return ImmutableList.copyOf(Iterables.filter(
+ library.getBinaries().withType(NativeLibraryBinarySpec.class).values(),
+ new Predicate<NativeLibraryBinarySpec>() {
+ @Override
+ public boolean apply(NativeLibraryBinarySpec binary) {
+ return binary.getBuildType().getName().equals(buildType.getName())
+ && (binary.getFlavor().getName().equals(flavorGroup.getName())
+ || (productFlavors.isEmpty()
+ && binary.getFlavor().getName().equals("default")));
+ }
+ }));
+ }
+
+ /**
+ * Return library binaries for a VariantConfiguration.
+ */
+ @NonNull
+ public Collection<? extends BuildableModelElement> getBinaries(
+ @NonNull final VariantConfiguration variantConfig) {
+ if (variantConfig.getType().isForTesting()) {
+ // Do not return binaries for test variants as test source set is not supported at the
+ // moment.
+ return Collections.emptyList();
+ }
+
+ ModelMap<BinarySpec> binaries = modelRegistry.realize(
+ new ModelPath("binaries"),
+ ModelTypes.modelMap(ModelType.of(BinarySpec.class)));
+
+ List<AndroidBinary> matches = Lists.newArrayList();
+ for (BinarySpec binary : binaries.values()) {
+ if (binary instanceof AndroidBinary
+ && binary.getName().equals(variantConfig.getFullName())) {
+ matches.add((AndroidBinary)binary);
+ }
+ }
+ return matches;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NdkConfigImpl.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NdkConfigImpl.java
new file mode 100644
index 0000000..70ad3d6
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/NdkConfigImpl.java
@@ -0,0 +1,163 @@
+/*
+ * 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.build.gradle.model;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.managed.NdkConfig;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Implementation of NdkConfig.
+ * Used in AndroidBinary, which is currently not a Managed type.
+ */
+public class NdkConfigImpl implements NdkConfig {
+
+ String moduleName;
+
+ String platformVersion;
+
+ String toolchain;
+
+ String toolchainVersion;
+
+ Set<String> abiFilters = Sets.newHashSet();
+
+ List<String> cFlags = Lists.newArrayList();
+
+ List<String> cppFlags = Lists.newArrayList();
+
+ List<String> ldFlags = Lists.newArrayList();
+
+ List<String> ldLibs = Lists.newArrayList();
+
+ String stl;
+
+ String stlVersion;
+
+ Boolean isDebuggable;
+
+ Boolean renderscriptNdkMode;
+
+ @Override
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ @Override
+ public void setModuleName(@NonNull String moduleName) {
+ this.moduleName = moduleName;
+ }
+
+ @Override
+ public String getPlatformVersion() {
+ return platformVersion;
+ }
+
+ @Override
+ public void setPlatformVersion(@NonNull String platformVersion) {
+ this.platformVersion = platformVersion;
+ }
+
+ @Override
+ public String getToolchain() {
+ return toolchain;
+ }
+
+ @Override
+ public void setToolchain(@NonNull String toolchain) {
+ this.toolchain = toolchain;
+ }
+
+ @Override
+ public String getToolchainVersion() {
+ return toolchainVersion;
+ }
+
+ @Override
+ public void setToolchainVersion(@NonNull String toolchainVersion) {
+ this.toolchainVersion = toolchainVersion;
+ }
+
+ @Override
+ public Set<String> getAbiFilters() {
+ return abiFilters;
+ }
+
+ @Override
+ public List<String> getCFlags() {
+ return cFlags;
+ }
+
+ @Override
+ public List<String> getCppFlags() {
+ return cppFlags;
+ }
+
+ @Override
+ public List<String> getLdFlags() {
+ return ldFlags;
+ }
+
+ @Override
+ public List<String> getLdLibs() {
+ return ldLibs;
+ }
+
+ @Override
+ public String getStl() {
+ return stl;
+ }
+
+ @Override
+ public void setStl(@NonNull String stl) {
+ this.stl = stl;
+ }
+
+ @Override
+ public String getStlVersion() {
+ return this.stlVersion;
+ }
+
+ @Override
+ public void setStlVersion(String stlVersion) {
+ this.stlVersion = stlVersion;
+ }
+
+ @Override
+ public Boolean getDebuggable() {
+ return isDebuggable;
+ }
+
+ @Override
+ public void setDebuggable(Boolean isDebuggable) {
+ this.isDebuggable = isDebuggable;
+ }
+
+ @Override
+ public Boolean getRenderscriptNdkMode() {
+ return renderscriptNdkMode;
+ }
+
+ @Override
+ public void setRenderscriptNdkMode(Boolean renderscriptNdkMode) {
+ this.renderscriptNdkMode = renderscriptNdkMode;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/StandaloneNdkComponentModelPlugin.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/StandaloneNdkComponentModelPlugin.java
new file mode 100644
index 0000000..ea6a2cf
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/StandaloneNdkComponentModelPlugin.java
@@ -0,0 +1,209 @@
+/*
+ * 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.build.gradle.model;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.ProductFlavorCombo;
+import com.android.build.gradle.managed.AndroidConfig;
+import com.android.build.gradle.managed.BuildType;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.build.gradle.model.internal.AndroidBinaryInternal;
+import com.android.build.gradle.ndk.internal.NdkNamingScheme;
+import com.android.utils.StringHelper;
+
+import org.gradle.api.Action;
+import org.gradle.api.Named;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.plugins.BasePlugin;
+import org.gradle.api.tasks.Copy;
+import org.gradle.model.ModelMap;
+import org.gradle.model.Mutate;
+import org.gradle.model.Path;
+import org.gradle.model.RuleSource;
+import org.gradle.model.Validate;
+import org.gradle.nativeplatform.NativeBinarySpec;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Plugin for compiling native source code to create a shared object.
+ */
+public class StandaloneNdkComponentModelPlugin implements Plugin<Project> {
+
+ @Override
+ public void apply(Project project) {
+ project.getPluginManager().apply(NdkComponentModelPlugin.class);
+ }
+
+ public static class Rules extends RuleSource {
+
+ @Validate
+ public static void validateCompileSdkVersion(AndroidConfig androidConfig) {
+ checkState(
+ androidConfig.getCompileSdkVersion() != null &&
+ !androidConfig.getCompileSdkVersion().isEmpty(),
+ "compileSdkVersion is not specified.");
+ }
+
+ @Mutate
+ public static void copyOutputs(
+ final ModelMap<Task> tasks,
+ ModelMap<AndroidBinaryInternal> androidSpecs,
+ @Path("buildDir") final File buildDir) {
+ for (AndroidBinaryInternal androidBinary : androidSpecs.values()) {
+ for (final NativeBinarySpec nativeBinary : androidBinary.getNativeBinaries()) {
+ final String copyTaskName = NdkNamingScheme.getTaskName(nativeBinary, "copy", "Output");
+ tasks.create(
+ copyTaskName,
+ Copy.class,
+ new Action<Copy>() {
+ @Override
+ public void execute(Copy copy) {
+ copy.from(new File(buildDir,
+ NdkNamingScheme.getOutputDirectoryName(nativeBinary)));
+ copy.into(new File(buildDir,
+ NdkNamingScheme
+ .getStandaloneOutputDirectoryName(nativeBinary)));
+ copy.dependsOn(NdkNamingScheme.getNdkBuildTaskName(nativeBinary));
+ }
+ });
+ dependsOn(tasks, getAssembleTaskName(androidBinary), copyTaskName);
+ }
+ }
+ }
+
+ // Create assemble tasks for each variant, build types and product flavors.
+
+ // TODO: These should be factored out into AndroidComponentModelPlugin.
+ // This requires significant changes AndroidTaskRegistry in order for it to work with
+ // experimental plugin.
+ @Mutate
+ public static void createAssembleTasksForBuildTypes(
+ ModelMap<Task> tasks,
+ @Path("android.buildTypes") final ModelMap<BuildType> buildTypes) {
+ for (final Named buildType : buildTypes.values()) {
+ createAssembleTask(tasks, buildType);
+ dependsOn(tasks, "assemble", getAssembleTaskName(buildType));
+ }
+ }
+
+ @Mutate
+ public static void createAssembleTasksForProductFlavors(
+ ModelMap<Task> tasks,
+ @Path("android.productFlavors") ModelMap<ProductFlavor> flavors) {
+ if (!flavors.isEmpty()) {
+ for (final Named productFlavor : flavors.values()) {
+ createAssembleTask(tasks, productFlavor);
+ dependsOn(tasks, "assemble", getAssembleTaskName(productFlavor));
+ }
+ }
+ }
+
+ /**
+ * Create assemble tasks for each AndroidBinary and configure their dependencies
+ */
+ @Mutate
+ public static void createAssembleTasksForBinaries(
+ ModelMap<Task> tasks,
+ ModelMap<AndroidBinary> binaries) {
+ for(final AndroidBinary binary : binaries.values()) {
+ String binaryAssembleTaskName = getAssembleTaskName(binary);
+ if (!binary.getProductFlavors().isEmpty()) {
+ createAssembleTask(tasks, binary);
+ dependsOn(tasks, getAssembleTaskName(binary.getBuildType()),
+ binaryAssembleTaskName);
+ for (ProductFlavor flavor :binary.getProductFlavors()) {
+ dependsOn(tasks, getAssembleTaskName(flavor), binaryAssembleTaskName);
+ }
+
+ if (binary.getProductFlavors().size() > 1) {
+ createAssembleTaskForFlavorCombo(tasks, binary.getProductFlavors());
+ dependsOn(tasks, getAssembleTaskName(binary.getProductFlavors()), binaryAssembleTaskName);
+ }
+ }
+
+ tasks.named(binaryAssembleTaskName, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.dependsOn(binary);
+ }
+ });
+ }
+ }
+
+ private static void dependsOn(
+ @NonNull final ModelMap<Task> tasks,
+ @NonNull final String dependee,
+ @NonNull final String dependent) {
+ tasks.named(dependee, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.dependsOn(dependent);
+ }
+ });
+ }
+
+ @NonNull
+ private static String getAssembleTaskName(@NonNull Named dimension) {
+ return "assemble" + StringHelper.capitalize(dimension.getName());
+ }
+
+ @NonNull
+ private static String getAssembleTaskName(@NonNull List<? extends Named> dimensions) {
+ return "assemble"
+ + StringHelper.capitalize(ProductFlavorCombo.getFlavorComboName(dimensions));
+ }
+
+ private static void createAssembleTaskForFlavorCombo(
+ @NonNull ModelMap<Task> tasks,
+ @NonNull final List<? extends Named> dimensions) {
+ final String flavorCombo = ProductFlavorCombo.getFlavorComboName(dimensions);
+ String taskName = getAssembleTaskName(dimensions);
+ tasks.create(
+ taskName,
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.setDescription(
+ "Assembles all builds for flavor combination: " + flavorCombo);
+ task.setGroup(BasePlugin.BUILD_GROUP);
+ }
+ });
+ }
+
+ private static void createAssembleTask(
+ @NonNull ModelMap<Task> tasks,
+ @NonNull final Named dimension) {
+ String taskName = getAssembleTaskName(dimension);
+ tasks.create(
+ taskName,
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.setDescription(
+ "Assembles all " + dimension.getName() + " builds.");
+ task.setGroup(BasePlugin.BUILD_GROUP);
+ }
+ });
+ }
+ }
+}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/TaskModelMapAdaptor.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/TaskModelMapAdaptor.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/model/TaskModelMapAdaptor.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/TaskModelMapAdaptor.java
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/AndroidBinaryInternal.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/AndroidBinaryInternal.java
new file mode 100644
index 0000000..bc547a6
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/AndroidBinaryInternal.java
@@ -0,0 +1,53 @@
+/*
+ * 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.build.gradle.model.internal;
+
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.managed.BuildType;
+import com.android.build.gradle.managed.NdkConfig;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.build.gradle.model.AndroidBinary;
+
+import org.gradle.nativeplatform.NativeLibraryBinarySpec;
+
+import java.util.List;
+
+/**
+ * Internal interface for {@link AndroidBinary}
+ */
+public interface AndroidBinaryInternal extends AndroidBinary {
+
+ void setBuildType(BuildType buildType);
+
+ void setProductFlavors(List<ProductFlavor> productFlavors);
+
+ NdkConfig getMergedNdkConfig();
+
+ BaseVariantData getVariantData();
+
+ void setVariantData(BaseVariantData variantData);
+
+ List<NativeLibraryBinarySpec> getNativeBinaries();
+
+ List<String> getTargetAbi();
+
+ void computeMergedNdk(
+ NdkConfig ndkConfig,
+ List<com.android.build.gradle.managed.ProductFlavor> flavors,
+ com.android.build.gradle.managed.BuildType buildType);
+
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/AndroidComponentSpecInternal.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/AndroidComponentSpecInternal.java
new file mode 100644
index 0000000..fcf2339
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/AndroidComponentSpecInternal.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model.internal;
+
+import com.android.build.gradle.internal.VariantManager;
+import com.android.build.gradle.managed.AndroidConfig;
+import com.android.build.gradle.model.AndroidComponentSpec;
+import com.android.builder.model.SigningConfig;
+
+import org.gradle.nativeplatform.NativeLibrarySpec;
+
+/**
+ * Internal interface for {@link AndroidComponentSpec}
+ */
+public interface AndroidComponentSpecInternal extends AndroidComponentSpec {
+
+ void setExtension(AndroidConfig extension);
+
+ VariantManager getVariantManager();
+
+ void setVariantManager(VariantManager variantManager);
+
+ SigningConfig getSigningOverride();
+
+ void setSigningOverride(SigningConfig signingOverride);
+
+ NativeLibrarySpec getNativeLibrary();
+
+ void setNativeLibrary(NativeLibrarySpec nativeLibrary);
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidBinary.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidBinary.java
new file mode 100644
index 0000000..42a8c37
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidBinary.java
@@ -0,0 +1,117 @@
+/*
+ * 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.build.gradle.model.internal;
+
+import com.android.build.gradle.internal.NdkOptionsHelper;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.managed.BuildType;
+import com.android.build.gradle.managed.NdkConfig;
+import com.android.build.gradle.managed.NdkOptions;
+import com.android.build.gradle.managed.ProductFlavor;
+import com.android.build.gradle.model.NdkConfigImpl;
+import com.google.common.collect.Lists;
+
+import org.gradle.nativeplatform.NativeLibraryBinarySpec;
+import org.gradle.platform.base.binary.BaseBinarySpec;
+
+import java.util.List;
+
+/**
+ * Binary for Android.
+ */
+public class DefaultAndroidBinary extends BaseBinarySpec implements AndroidBinaryInternal {
+
+ private BuildType buildType;
+
+ private List<ProductFlavor> productFlavors;
+
+ private NdkConfig mergedNdkConfig = new NdkConfigImpl();
+
+ private BaseVariantData variantData;
+
+ private List<NativeLibraryBinarySpec> nativeBinaries = Lists.newArrayList();
+
+ private List<String> targetAbi = Lists.newArrayList();
+
+ @Override
+ public BuildType getBuildType() {
+ return buildType;
+ }
+
+ @Override
+ public void setBuildType(BuildType buildType) {
+ this.buildType = buildType;
+ }
+
+ @Override
+ public List<ProductFlavor> getProductFlavors() {
+ return productFlavors;
+ }
+
+ @Override
+ public void setProductFlavors(List<ProductFlavor> productFlavors) {
+ this.productFlavors = productFlavors;
+ }
+
+ @Override
+ public NdkConfig getMergedNdkConfig() {
+ return mergedNdkConfig;
+ }
+
+ @Override
+ public BaseVariantData getVariantData() {
+ return variantData;
+ }
+
+ @Override
+ public void setVariantData(BaseVariantData variantData) {
+ this.variantData = variantData;
+ }
+
+ @Override
+ public List<NativeLibraryBinarySpec> getNativeBinaries() {
+ return nativeBinaries;
+ }
+
+ @Override
+ public List<String> getTargetAbi() {
+ return targetAbi;
+ }
+
+ @Override
+ public void computeMergedNdk(
+ NdkConfig ndkConfig,
+ List<com.android.build.gradle.managed.ProductFlavor> flavors,
+ com.android.build.gradle.managed.BuildType buildType) {
+
+
+ if (ndkConfig != null) {
+ NdkOptionsHelper.merge(mergedNdkConfig, ndkConfig);
+ }
+
+ for (int i = flavors.size() - 1 ; i >= 0 ; i--) {
+ NdkOptions ndkOptions = flavors.get(i).getNdk();
+ if (ndkOptions != null) {
+ NdkOptionsHelper.merge(mergedNdkConfig, ndkOptions);
+ }
+ }
+
+ if (buildType.getNdk() != null) {
+ NdkOptionsHelper.merge(mergedNdkConfig, buildType.getNdk());
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidComponentSpec.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidComponentSpec.java
new file mode 100644
index 0000000..31d74df
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidComponentSpec.java
@@ -0,0 +1,79 @@
+/*
+ * 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.build.gradle.model.internal;
+
+import com.android.build.gradle.internal.VariantManager;
+import com.android.build.gradle.managed.AndroidConfig;
+import com.android.builder.model.SigningConfig;
+
+import org.gradle.nativeplatform.NativeLibrarySpec;
+import org.gradle.platform.base.component.BaseComponentSpec;
+
+/**
+ * Implementation for Android component spec.
+ */
+public class DefaultAndroidComponentSpec extends BaseComponentSpec
+ implements AndroidComponentSpecInternal{
+ AndroidConfig extension;
+
+ VariantManager variantManager;
+
+ SigningConfig signingOverride;
+
+ NativeLibrarySpec nativeLibrary;
+
+ @Override
+ public AndroidConfig getExtension() {
+ return extension;
+ }
+
+ @Override
+ public void setExtension(AndroidConfig extension) {
+ this.extension = extension;
+ }
+
+ @Override
+ public VariantManager getVariantManager() {
+
+ return variantManager;
+ }
+
+ @Override
+ public void setVariantManager(VariantManager variantManager) {
+ this.variantManager = variantManager;
+ }
+
+ @Override
+ public SigningConfig getSigningOverride() {
+ return signingOverride;
+ }
+
+ @Override
+ public void setSigningOverride(SigningConfig signingOverride) {
+ this.signingOverride = signingOverride;
+ }
+
+ @Override
+ public NativeLibrarySpec getNativeLibrary() {
+ return nativeLibrary;
+ }
+
+ @Override
+ public void setNativeLibrary(NativeLibrarySpec nativeLibrary) {
+ this.nativeLibrary = nativeLibrary;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidLanguageSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidLanguageSourceSet.java
new file mode 100644
index 0000000..d3ed6ea
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultAndroidLanguageSourceSet.java
@@ -0,0 +1,28 @@
+/*
+ * 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.build.gradle.model.internal;
+
+import com.android.build.gradle.model.AndroidLanguageSourceSet;
+
+import org.gradle.language.base.sources.BaseLanguageSourceSet;
+
+/**
+ * Implementation of {@link AndroidLanguageSourceSet}
+ */
+public class DefaultAndroidLanguageSourceSet extends BaseLanguageSourceSet
+ implements AndroidLanguageSourceSet {
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultExternalNativeBinarySpec.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultExternalNativeBinarySpec.java
new file mode 100644
index 0000000..a3d1e39
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultExternalNativeBinarySpec.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model.internal;
+
+import com.android.build.gradle.managed.NativeLibrary;
+import com.android.build.gradle.model.ExternalNativeBinarySpec;
+
+import org.gradle.platform.base.binary.BaseBinarySpec;
+
+/**
+ * Implementation of {@link ExternalNativeBinarySpec}
+ */
+public class DefaultExternalNativeBinarySpec extends BaseBinarySpec
+ implements ExternalNativeBinarySpec {
+ private NativeLibrary config;
+
+ @Override
+ public NativeLibrary getConfig() {
+ return config;
+ }
+
+ @Override
+ public void setConfig(NativeLibrary config) {
+ this.config = config;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultExternalNativeComponentSpec.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultExternalNativeComponentSpec.java
new file mode 100644
index 0000000..2de3628
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultExternalNativeComponentSpec.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model.internal;
+
+import com.android.build.gradle.managed.NativeBuildConfig;
+import com.android.build.gradle.model.ExternalNativeComponentSpec;
+
+import org.gradle.platform.base.component.BaseComponentSpec;
+
+/**
+ * Implementation for {@link ExternalNativeComponentSpec}
+ */
+public class DefaultExternalNativeComponentSpec extends BaseComponentSpec
+ implements ExternalNativeComponentSpec {
+ private NativeBuildConfig config;
+
+ @Override
+ public NativeBuildConfig getConfig() {
+ return config;
+ }
+
+ @Override
+ public void setConfig(NativeBuildConfig config) {
+ this.config = config;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultJniLibsSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultJniLibsSourceSet.java
new file mode 100644
index 0000000..97b655a
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultJniLibsSourceSet.java
@@ -0,0 +1,27 @@
+/*
+ * 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.build.gradle.model.internal;
+
+import com.android.build.gradle.internal.AbstractNativeDependentSourceSet;
+import com.android.build.gradle.model.JniLibsSourceSet;
+
+/**
+ * Implementation of {@link JniLibsSourceSet}
+ */
+public class DefaultJniLibsSourceSet extends AbstractNativeDependentSourceSet
+ implements JniLibsSourceSet {
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultNativeSourceSet.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultNativeSourceSet.java
new file mode 100644
index 0000000..c67bf58
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/internal/DefaultNativeSourceSet.java
@@ -0,0 +1,48 @@
+/*
+ * 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.build.gradle.model.internal;
+
+import com.android.build.gradle.internal.AbstractNativeDependentSourceSet;
+import com.android.build.gradle.model.NativeSourceSet;
+
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+
+/**
+ * Implementation of {@link NativeSourceSet}
+ */
+public class DefaultNativeSourceSet extends AbstractNativeDependentSourceSet
+ implements NativeSourceSet {
+
+ private final DefaultSourceDirectorySet exportedHeaders;
+ private final DefaultSourceDirectorySet implicitHeaders;
+
+ public DefaultNativeSourceSet() {
+ this.exportedHeaders = new DefaultSourceDirectorySet("exported headers", fileResolver);
+ this.implicitHeaders = new DefaultSourceDirectorySet("implicit headers", fileResolver);
+ }
+
+ @Override
+ public SourceDirectorySet getExportedHeaders() {
+ return exportedHeaders;
+ }
+
+ @Override
+ public SourceDirectorySet getImplicitHeaders() {
+ return implicitHeaders;
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/AbstractNativeToolSpecification.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/AbstractNativeToolSpecification.java
new file mode 100644
index 0000000..f32e922
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/AbstractNativeToolSpecification.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.ndk.internal;
+
+import org.gradle.nativeplatform.NativeBinarySpec;
+
+/**
+ * An abstract class for NativeToolSpecification.
+ */
+public abstract class AbstractNativeToolSpecification implements NativeToolSpecification {
+ /**
+ * Configure a native binary with this specification.
+ *
+ * @param binary The binary to be configured. It is assumed the 'c' and 'cpp' plugin is applied
+ * such that the binary contains the cCompiler and cppCompiler extensions.
+ */
+ @Override
+ public void apply(NativeBinarySpec binary) {
+ for (String arg : getCFlags()) {
+ binary.getcCompiler().args(arg);
+ }
+
+ for (String arg : getCppFlags()) {
+ binary.getCppCompiler().args(arg);
+ }
+
+ for (String arg : getLdFlags()) {
+ binary.getLinker().args(arg);
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/ClangNativeToolSpecification.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/ClangNativeToolSpecification.java
new file mode 100644
index 0000000..0fd744c
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/ClangNativeToolSpecification.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.ndk.internal;
+
+import com.android.SdkConstants;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.builder.core.BuilderConstants;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+
+import org.gradle.nativeplatform.BuildType;
+import org.gradle.nativeplatform.platform.NativePlatform;
+
+import java.util.Map;
+
+/**
+ * Flag configuration for Clang toolchain.
+ */
+public class ClangNativeToolSpecification extends AbstractNativeToolSpecification {
+
+ private NdkHandler ndkHandler;
+
+ private NativePlatform platform;
+
+ private boolean isDebugBuild;
+
+ private static final Map<String, String> TARGET_TRIPLE = ImmutableMap.<String, String>builder()
+ .put(SdkConstants.ABI_INTEL_ATOM, "i686-none-linux-android")
+ .put(SdkConstants.ABI_INTEL_ATOM64, "x86_64-none-linux-android")
+ .put(SdkConstants.ABI_ARMEABI, "armv5-none-linux-android")
+ .put(SdkConstants.ABI_ARMEABI_V7A, "armv7-none-linux-android")
+ .put(SdkConstants.ABI_ARM64_V8A, "aarch64-none-linux-android")
+ .put(SdkConstants.ABI_MIPS, "mipsel-none-linux-android")
+ .put(SdkConstants.ABI_MIPS64, "mips64el-none-linux-android")
+ .build();
+
+
+ private static final ListMultimap<String, String> RELEASE_CFLAGS =
+ ImmutableListMultimap.<String, String>builder()
+ .putAll(SdkConstants.ABI_ARMEABI, ImmutableList.of(
+ "-fpic",
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fstack-protector",
+ "-no-canonical-prefixes",
+ "-march=armv5te",
+ "-mtune=xscale",
+ "-msoft-float",
+ "-mthumb",
+ "-Os",
+ "-g",
+ "-DNDEBUG",
+ "-fomit-frame-pointer",
+ "-fstrict-aliasing"))
+ .putAll(SdkConstants.ABI_ARMEABI_V7A, ImmutableList.of(
+ "-fpic",
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fstack-protector",
+ "-no-canonical-prefixes",
+ "-march=armv7-a",
+ "-mfloat-abi=softfp",
+ "-mfpu=vfpv3-d16",
+ "-mthumb",
+ "-Os",
+ "-g",
+ "-DNDEBUG",
+ "-fomit-frame-pointer",
+ "-fstrict-aliasing"))
+ .putAll(SdkConstants.ABI_ARM64_V8A, ImmutableList.of(
+ "-fpic",
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fstack-protector",
+ "-no-canonical-prefixes",
+ "-O2",
+ "-g",
+ "-DNDEBUG",
+ "-fomit-frame-pointer",
+ "-fstrict-aliasing"))
+ .putAll(SdkConstants.ABI_INTEL_ATOM, ImmutableList.of(
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fstack-protector",
+ "-fPIC",
+ "-no-canonical-prefixes",
+ "-O2",
+ "-g",
+ "-DNDEBUG",
+ "-fomit-frame-pointer",
+ "-fstrict-aliasing"))
+ .putAll(SdkConstants.ABI_INTEL_ATOM64, ImmutableList.of(
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fstack-protector",
+ "-fPIC",
+ "-no-canonical-prefixes",
+ "-O2",
+ "-g",
+ "-DNDEBUG",
+ "-fomit-frame-pointer",
+ "-fstrict-aliasing"))
+ .putAll(SdkConstants.ABI_MIPS, ImmutableList.of(
+ "-fpic",
+ "-fno-strict-aliasing",
+ "-finline-functions",
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fmessage-length=0",
+ "-no-canonical-prefixes",
+ "-O2",
+ "-g",
+ "-DNDEBUG",
+ "-fomit-frame-pointer"))
+ .putAll(SdkConstants.ABI_MIPS64, ImmutableList.of(
+ "-fpic",
+ "-fno-strict-aliasing",
+ "-finline-functions",
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fmessage-length=0",
+ "-no-canonical-prefixes",
+ "-O2",
+ "-g",
+ "-DNDEBUG",
+ "-fomit-frame-pointer"))
+ .build();
+
+ private static final ListMultimap<String, String> DEBUG_CFLAGS =
+ ImmutableListMultimap.<String, String>builder()
+ .putAll(SdkConstants.ABI_ARMEABI, ImmutableList.of(
+ "-O0",
+ "-UNDEBUG",
+ "-marm",
+ "-fno-strict-aliasing",
+ "-fno-limit-debug-info"))
+ .putAll(SdkConstants.ABI_ARMEABI_V7A, ImmutableList.of(
+ "-O0",
+ "-UNDEBUG",
+ "-marm",
+ "-fno-strict-aliasing",
+ "-fno-limit-debug-info"))
+ .putAll(SdkConstants.ABI_ARM64_V8A, ImmutableList.of(
+ "-O0",
+ "-UNDEBUG",
+ "-fno-omit-frame-pointer",
+ "-fno-strict-aliasing",
+ "-fno-limit-debug-info"))
+ .putAll(SdkConstants.ABI_INTEL_ATOM, ImmutableList.of(
+ "-O0",
+ "-UNDEBUG",
+ "-fno-omit-frame-pointer",
+ "-fno-strict-aliasing",
+ "-fno-limit-debug-info"))
+ .putAll(SdkConstants.ABI_INTEL_ATOM64, ImmutableList.of(
+ "-O0",
+ "-UNDEBUG",
+ "-fno-omit-frame-pointer",
+ "-fno-strict-aliasing",
+ "-fno-limit-debug-info"))
+ .putAll(SdkConstants.ABI_MIPS, ImmutableList.of(
+ "-O0",
+ "-UNDEBUG",
+ "-fno-omit-frame-pointer",
+ "-fno-limit-debug-info"))
+ .putAll(SdkConstants.ABI_MIPS64, ImmutableList.of(
+ "-O0",
+ "-UNDEBUG",
+ "-fno-omit-frame-pointer",
+ "-fno-limit-debug-info"))
+ .build();
+
+ public ClangNativeToolSpecification(
+ NdkHandler ndkHandler,
+ NativePlatform platform,
+ boolean isDebugBuild) {
+ this.ndkHandler = ndkHandler;
+ this.isDebugBuild = isDebugBuild;
+ this.platform = platform;
+ }
+
+ @Override
+ public Iterable<String> getCFlags() {
+ return Iterables.concat(
+ getTargetFlags(),
+ RELEASE_CFLAGS.get(platform.getName()),
+ isDebugBuild ? DEBUG_CFLAGS.get(platform.getName()) : ImmutableList.<String>of());
+ }
+
+ @Override
+ public Iterable<String> getCppFlags() {
+ return getCFlags();
+ }
+
+ @Override
+ public Iterable<String> getLdFlags() {
+ return Iterables.concat(
+ getTargetFlags(),
+ platform.getName().equals(SdkConstants.ABI_ARMEABI_V7A)
+ ? ImmutableList.of("-Wl,--fix-cortex-a8")
+ : ImmutableList.<String>of());
+ }
+
+ private Iterable<String> getTargetFlags() {
+ return ImmutableList.of(
+ "-gcc-toolchain",
+ ndkHandler.getDefaultGccToolchainPath(Abi.getByName(platform.getName())).toString(),
+ "-target",
+ TARGET_TRIPLE.get(platform.getName()));
+ }
+}
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/DefaultNativeToolSpecification.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/DefaultNativeToolSpecification.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/DefaultNativeToolSpecification.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/DefaultNativeToolSpecification.java
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/GccNativeToolSpecification.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/GccNativeToolSpecification.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/GccNativeToolSpecification.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/GccNativeToolSpecification.java
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NativeToolSpecification.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NativeToolSpecification.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NativeToolSpecification.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NativeToolSpecification.java
diff --git a/base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NativeToolSpecificationFactory.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NativeToolSpecificationFactory.java
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/groovy/com/android/build/gradle/ndk/internal/NativeToolSpecificationFactory.java
rename to build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NativeToolSpecificationFactory.java
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkConfiguration.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkConfiguration.java
new file mode 100644
index 0000000..fb17c6c
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkConfiguration.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.ndk.internal;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.NativeDependencyLinkage;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.dependency.AndroidNativeDependencySpec;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolveResult;
+import com.android.build.gradle.internal.dependency.NativeDependencyResolver;
+import com.android.build.gradle.internal.dependency.NativeLibraryArtifact;
+import com.android.build.gradle.internal.dependency.NativeLibraryArtifactAdaptor;
+import com.android.build.gradle.managed.NdkConfig;
+import com.android.build.gradle.model.NativeSourceSet;
+import com.android.build.gradle.tasks.StripDebugSymbolTask;
+import com.android.utils.StringHelper;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.c.CSourceSet;
+import org.gradle.language.c.tasks.CCompile;
+import org.gradle.language.cpp.CppSourceSet;
+import org.gradle.language.cpp.tasks.CppCompile;
+import org.gradle.language.nativeplatform.DependentSourceSet;
+import org.gradle.model.ModelMap;
+import org.gradle.nativeplatform.NativeBinarySpec;
+import org.gradle.nativeplatform.NativeLibraryBinarySpec;
+import org.gradle.nativeplatform.NativeLibrarySpec;
+import org.gradle.nativeplatform.SharedLibraryBinarySpec;
+import org.gradle.nativeplatform.StaticLibraryBinarySpec;
+import org.gradle.nativeplatform.internal.resolve.DefaultNativeDependencySet;
+import org.gradle.platform.base.BinarySpec;
+import org.gradle.platform.base.internal.BinarySpecInternal;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Configure settings used by the native binaries.
+ */
+public class NdkConfiguration {
+ public static final Collection<String> C_FILE_EXTENSIONS = ImmutableList.of("c");
+ public static final Collection<String> CPP_FILE_EXTENSIONS = ImmutableList.of(
+ "C", "CPP", "c++", "cc", "cp", "cpp", "cxx");
+
+ public static void configureProperties(
+ NativeLibrarySpec library,
+ final ModelMap<FunctionalSourceSet> sources,
+ final File buildDir,
+ final NdkHandler ndkHandler,
+ final ServiceRegistry serviceRegistry) {
+ for (Abi abi : ndkHandler.getSupportedAbis()) {
+ library.targetPlatform(abi.getName());
+ }
+
+ // Setting each native binary to not buildable to prevent the native tasks to be
+ // automatically added to the "assemble" task.
+ library.getBinaries().beforeEach(
+ new Action<BinarySpec>() {
+ @Override
+ public void execute(BinarySpec binary) {
+ ((BinarySpecInternal) binary).setBuildable(false);
+ }
+ });
+
+ library.getBinaries().withType(
+ NativeLibraryBinarySpec.class,
+ new Action<NativeLibraryBinarySpec>() {
+ @Override
+ public void execute(final NativeLibraryBinarySpec binary) {
+ Map<String, NativeSourceSet> jniSources =
+ findNativeSourceSets(binary, sources);
+ for (Map.Entry<String, NativeSourceSet> entry : jniSources.entrySet()) {
+ addNativeSourceSets(binary, entry.getKey(), entry.getValue());
+ }
+
+ binary.getcCompiler().define("ANDROID");
+ binary.getCppCompiler().define("ANDROID");
+ binary.getcCompiler().define("ANDROID_NDK");
+ binary.getCppCompiler().define("ANDROID_NDK");
+
+ // Replace output directory of compile tasks.
+ binary.getTasks().withType(CCompile.class, new Action<CCompile>() {
+ @Override
+ public void execute(CCompile task) {
+ String sourceSetName = task.getObjectFileDir().getName();
+ task.setObjectFileDir(
+ NdkNamingScheme.getObjectFilesOutputDirectory(
+ binary,
+ buildDir,
+ sourceSetName));
+ }
+ });
+ binary.getTasks().withType(CppCompile.class, new Action<CppCompile>() {
+ @Override
+ public void execute(CppCompile task) {
+ String sourceSetName = task.getObjectFileDir().getName();
+ task.setObjectFileDir(
+ NdkNamingScheme.getObjectFilesOutputDirectory(
+ binary,
+ buildDir,
+ sourceSetName));
+ }
+ });
+
+ new DefaultNativeToolSpecification().apply(binary);
+
+ String sysroot = ndkHandler.getSysroot(
+ Abi.getByName(binary.getTargetPlatform().getName()));
+
+ binary.getcCompiler().args("--sysroot=" + sysroot);
+ binary.getCppCompiler().args("--sysroot=" + sysroot);
+ binary.getLinker().args("--sysroot=" + sysroot);
+ binary.getLinker().args("-Wl,--build-id");
+
+ for (NativeSourceSet jniSource : jniSources.values()) {
+ handleDependencies(
+ binary,
+ resolveDependency(serviceRegistry, binary, jniSource));
+ }
+ }
+ });
+ }
+
+
+ /**
+ * Configure output file of a native library binary.
+ */
+ public static void configureNativeBinaryOutputFile(
+ NativeLibraryBinarySpec binary,
+ final File buildDir,
+ final String moduleName) {
+ // Set output library filename.
+ if (binary instanceof SharedLibraryBinarySpec) {
+ ((SharedLibraryBinarySpec) binary).setSharedLibraryFile(
+ new File(
+ buildDir,
+ NdkNamingScheme.getDebugLibraryDirectoryName(binary)
+ + "/"
+ + NdkNamingScheme.getSharedLibraryFileName(
+ moduleName)));
+ ((SharedLibraryBinarySpec) binary).setSharedLibraryLinkFile(
+ new File(
+ buildDir,
+ NdkNamingScheme.getDebugLibraryDirectoryName(binary)
+ + "/"
+ + NdkNamingScheme.getSharedLibraryFileName(
+ moduleName)));
+ } else if (binary instanceof StaticLibraryBinarySpec) {
+ ((StaticLibraryBinarySpec) binary).setStaticLibraryFile(
+ new File(
+ buildDir,
+ NdkNamingScheme.getDebugLibraryDirectoryName(binary)
+ + "/"
+ + NdkNamingScheme.getStaticLibraryFileName(
+ moduleName)));
+ } else {
+ throw new AssertionError("Should be unreachable");
+ }
+ }
+
+ /**
+ * Configure native binary with variant specific options.
+ */
+ public static void configureBinary(
+ NativeLibraryBinarySpec binary,
+ final NdkConfig ndkConfig,
+ final NdkHandler ndkHandler) {
+ String sysroot = ndkHandler.getSysroot(
+ Abi.getByName(binary.getTargetPlatform().getName()));
+
+ if (ndkConfig.getRenderscriptNdkMode()) {
+ binary.getcCompiler().args("-I" + sysroot + "/usr/include/rs");
+ binary.getcCompiler().args("-I" + sysroot + "/usr/include/rs/cpp");
+ binary.getCppCompiler().args("-I" + sysroot + "/usr/include/rs");
+ binary.getCppCompiler().args("-I" + sysroot + "/usr/include/rs/cpp");
+ binary.getLinker().args("-L" + sysroot + "/usr/lib/rs");
+ }
+
+ // STL flags must be applied before user defined flags to resolve possible undefined symbols
+ // in the STL library.
+ StlNativeToolSpecification stlConfig = new StlNativeToolSpecification(
+ ndkHandler,
+ ndkConfig.getStl(),
+ ndkConfig.getStlVersion(),
+ binary.getTargetPlatform());
+ stlConfig.apply(binary);
+
+ NativeToolSpecificationFactory.create(
+ ndkHandler,
+ binary.getTargetPlatform(),
+ Objects.firstNonNull(ndkConfig.getDebuggable(), false)).apply(
+ binary);
+
+ // Add flags defined in NdkConfig
+ for (String flag : ndkConfig.getCFlags()) {
+ binary.getcCompiler().args(flag.trim());
+ }
+
+ for (String flag : ndkConfig.getCppFlags()) {
+ binary.getCppCompiler().args(flag.trim());
+ }
+
+ for (String flag : ndkConfig.getLdFlags()) {
+ binary.getLinker().args(flag.trim());
+ }
+
+ for (String ldLib : ndkConfig.getLdLibs()) {
+ binary.getLinker().args("-l" + ldLib.trim());
+ }
+ }
+
+
+ public static NativeDependencyResolveResult resolveDependency(
+ @NonNull ServiceRegistry serviceRegistry,
+ @NonNull NativeBinarySpec binary,
+ @NonNull NativeSourceSet jniSource) {
+ return new NativeDependencyResolver(
+ serviceRegistry,
+ jniSource.getDependencies(),
+ new AndroidNativeDependencySpec(
+ null,
+ null,
+ binary.getBuildType().getName(),
+ binary.getFlavor().getName(),
+ NativeDependencyLinkage.SHARED)).resolve();
+ }
+
+ private static void handleDependencies(
+ @NonNull NativeBinarySpec binary,
+ @NonNull NativeDependencyResolveResult dependency) {
+ for (final NativeLibraryArtifact artifacts: dependency.getNativeArtifacts()) {
+ final String abi = artifacts.getAbi();
+ if (binary.getTargetPlatform().getName().equals(abi)) {
+ binary.getTasks().all(
+ new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.dependsOn(artifacts.getBuiltBy());
+ }
+ });
+ binary.lib(new DefaultNativeDependencySet(new NativeLibraryArtifactAdaptor(artifacts)));
+ }
+ }
+ }
+
+ /**
+ * Find all JNI source sets that should be added the a native binary.
+ *
+ * @return a map from the name of the FunctionalSourceSet containing the JNI source set, to the
+ * JNI source set.
+ */
+ public static Map<String, NativeSourceSet> findNativeSourceSets(
+ NativeBinarySpec binary,
+ ModelMap<FunctionalSourceSet> projectSourceSet) {
+ Map<String, NativeSourceSet> sourceSetMap = Maps.newHashMap();
+ addSourceIfExist(sourceSetMap, projectSourceSet, "main");
+ addSourceIfExist(sourceSetMap, projectSourceSet, binary.getFlavor().getName());
+ addSourceIfExist(sourceSetMap, projectSourceSet, binary.getBuildType().getName());
+ addSourceIfExist(sourceSetMap, projectSourceSet,
+ binary.getFlavor().getName()
+ + StringHelper.capitalize(binary.getBuildType().getName()));
+ return sourceSetMap;
+ }
+
+ private static void addSourceIfExist(
+ @NonNull Map<String, NativeSourceSet> sourceSetMap,
+ @NonNull ModelMap<FunctionalSourceSet> projectSourceSet,
+ @NonNull String sourceSetName) {
+ FunctionalSourceSet sourceSet = projectSourceSet.get(sourceSetName);
+ if (sourceSet != null) {
+ sourceSetMap.put(sourceSetName, (NativeSourceSet) sourceSet.get("jni"));
+ }
+ }
+
+ /**
+ * Add the sourceSet with the specified name to the binary.
+ */
+ private static void addNativeSourceSets(
+ @NonNull BinarySpec binary,
+ @NonNull final String sourceSetName,
+ @NonNull final NativeSourceSet jni) {
+ // Hardcode the acceptable extension until we find a suitable DSL for user to modify.
+ binary.getSources().create(
+ sourceSetName + "C",
+ CSourceSet.class,
+ new Action<CSourceSet>() {
+ @Override
+ public void execute(CSourceSet source) {
+ source.getSource().setSrcDirs(jni.getSource().getSrcDirs());
+ addInclude(source.getSource(), C_FILE_EXTENSIONS);
+ source.getSource().exclude(jni.getSource().getExcludes());
+ source.getExportedHeaders().source(jni.getExportedHeaders());
+ configurePrebuiltDependency(source, jni);
+ }
+ });
+ binary.getSources().create(
+ sourceSetName + "Cpp",
+ CppSourceSet.class,
+ new Action<CppSourceSet>() {
+ @Override
+ public void execute(CppSourceSet source) {
+ source.getSource().setSrcDirs(jni.getSource().getSrcDirs());
+ addInclude(source.getSource(), CPP_FILE_EXTENSIONS);
+ source.getSource().exclude(jni.getSource().getExcludes());
+ source.getExportedHeaders().source(jni.getExportedHeaders());
+ configurePrebuiltDependency(source, jni);
+ }
+ });
+ }
+
+ private static void addInclude(
+ @NonNull SourceDirectorySet sourceDirSet,
+ @NonNull Iterable<String> fileExtensions) {
+ for (String ext : fileExtensions) {
+ sourceDirSet.include("**/*." + ext);
+ }
+ }
+
+ private static void configurePrebuiltDependency(
+ DependentSourceSet source,
+ NativeSourceSet jni) {
+ for(AndroidNativeDependencySpec dependencySpec :
+ jni.getDependencies().getDependencies()) {
+ if (dependencySpec.getLibraryPath() != null) {
+ ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+ builder.put("library", dependencySpec.getLibraryPath());
+ if (dependencySpec.getLinkage() != null) {
+ builder.put("linkage", dependencySpec.getLinkage().getName());
+ }
+ source.lib(builder.build());
+ }
+ }
+ }
+
+ public static void createTasks(
+ @NonNull ModelMap<Task> tasks,
+ @NonNull final NativeBinarySpec binary,
+ @NonNull final File buildDir,
+ @NonNull NdkConfig ndkConfig,
+ @NonNull NdkHandler ndkHandler,
+ @NonNull final Multimap<String, NativeDependencyResolveResult> dependencyMap) {
+ String compileNdkTaskName = NdkNamingScheme.getNdkBuildTaskName(binary);
+ tasks.create(compileNdkTaskName);
+
+ if (binary instanceof SharedLibraryBinarySpec) {
+ StlConfiguration.createStlCopyTask(
+ tasks,
+ binary,
+ buildDir,
+ ndkHandler,
+ ndkConfig.getStl(),
+ ndkConfig.getStlVersion(),
+ compileNdkTaskName);
+
+ createStripDebugTask(
+ tasks,
+ (SharedLibraryBinarySpec) binary,
+ dependencyMap,
+ buildDir,
+ ndkHandler,
+ compileNdkTaskName);
+ }
+ }
+
+ private static void createStripDebugTask(
+ ModelMap<Task> tasks,
+ final SharedLibraryBinarySpec binary,
+ @NonNull final Multimap<String, NativeDependencyResolveResult> dependencyMap,
+ final File buildDir,
+ final NdkHandler handler,
+ String buildTaskName) {
+
+ final String taskName = NdkNamingScheme.getTaskName(binary, "stripSymbols");
+
+ List<File> libs = Lists.newArrayList();
+ final Collection<NativeDependencyResolveResult> dependencies =
+ dependencyMap.get(binary.getName());
+ for (NativeDependencyResolveResult dependency : dependencies) {
+ Collection<NativeLibraryArtifact> artifacts = dependency.getNativeArtifacts();
+ for (NativeLibraryArtifact artifact : artifacts) {
+ if (binary.getTargetPlatform().getName().equals(artifact.getAbi())) {
+ for (File lib : artifact.getLibraries()) {
+ if (lib.getName().endsWith(".so")) {
+ libs.add(lib);
+ }
+ }
+ }
+ }
+ }
+ tasks.create(
+ taskName,
+ StripDebugSymbolTask.class,
+ new StripDebugSymbolTask.ConfigAction(
+ binary,
+ new File(buildDir, NdkNamingScheme.getDebugLibraryDirectoryName(binary)),
+ libs,
+ buildDir,
+ handler));
+ tasks.named(buildTaskName, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.dependsOn(taskName);
+ }
+ });
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkExtensionConvention.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkExtensionConvention.java
new file mode 100644
index 0000000..317a988
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkExtensionConvention.java
@@ -0,0 +1,43 @@
+package com.android.build.gradle.ndk.internal;
+
+import com.android.build.gradle.internal.core.Toolchain;
+import com.android.build.gradle.managed.NdkConfig;
+import com.google.common.primitives.Ints;
+
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * Action to setup default values for NdkExtension.
+ */
+public class NdkExtensionConvention {
+
+ public static final String DEFAULT_STL = "system";
+
+ /**
+ * Validate the NdkExtension and provide default values.
+ */
+ public static void setExtensionDefault(NdkConfig ndkConfig) {
+ if (ndkConfig.getToolchain().isEmpty()) {
+ ndkConfig.setToolchain(Toolchain.getDefault().getName());
+ } else {
+ if (!ndkConfig.getToolchain().equals("gcc") &&
+ !ndkConfig.getToolchain().equals("clang")) {
+ throw new InvalidUserDataException(String.format(
+ "Invalid toolchain '%s'. Supported toolchains are 'gcc' and 'clang'.",
+ ndkConfig.getToolchain()));
+ }
+ }
+
+ if (ndkConfig.getStl().isEmpty()) {
+ ndkConfig.setStl(DEFAULT_STL);
+ } else {
+ StlConfiguration.checkStl(ndkConfig.getStl());
+ }
+
+ if (ndkConfig.getPlatformVersion() != null
+ && !ndkConfig.getPlatformVersion().startsWith("android-")
+ && Ints.tryParse(ndkConfig.getPlatformVersion()) != null) {
+ ndkConfig.setPlatformVersion("android-" + ndkConfig.getPlatformVersion());
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkNamingScheme.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkNamingScheme.java
new file mode 100644
index 0000000..2407857
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/NdkNamingScheme.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.ndk.internal;
+
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
+import static com.android.utils.StringHelper.appendCamelCase;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.FileUtils;
+
+import org.gradle.nativeplatform.NativeBinarySpec;
+
+import java.io.File;
+
+/**
+ * Naming scheme for NdkPlugin's outputs.
+ */
+public class NdkNamingScheme {
+
+ @NonNull
+ public static File getObjectFilesOutputDirectory(
+ @NonNull NativeBinarySpec binary,
+ @NonNull File buildDir,
+ @NonNull String sourceSetName) {
+ return new File(
+ buildDir,
+ String.format(
+ "%s/objectFiles/%s/%s",
+ FD_INTERMEDIATES ,
+ binary.getName(),
+ sourceSetName));
+ }
+
+ @NonNull
+ public static String getTaskName(@NonNull NativeBinarySpec binary, @Nullable String verb) {
+ return getTaskName(binary, verb, null);
+ }
+
+ @NonNull
+ public static String getTaskName(
+ @NonNull NativeBinarySpec binary,
+ @Nullable String verb,
+ @Nullable String target) {
+ StringBuilder sb = new StringBuilder();
+ appendCamelCase(sb, verb);
+ appendCamelCase(sb, binary.getName());
+ appendCamelCase(sb, target);
+ return sb.toString();
+ }
+
+ @NonNull
+ public static String getNdkBuildTaskName(@NonNull NativeBinarySpec binary) {
+ return getTaskName(binary, "ndkBuild");
+ }
+
+ /**
+ * Return the name of the directory that will contain the final output of the native binary.
+ */
+ @NonNull
+ public static String getOutputDirectoryName(
+ @NonNull String buildType,
+ @NonNull String productFlavor,
+ @NonNull String abi) {
+ return FileUtils.join(
+ FD_INTERMEDIATES,
+ "binaries",
+ buildType,
+ productFlavor,
+ "lib",
+ abi);
+ }
+
+
+ @NonNull
+ public static String getStandaloneOutputDirectoryName(@NonNull NativeBinarySpec binary) {
+ return FileUtils.join(
+ FD_OUTPUTS,
+ "native",
+ binary.getBuildType().getName(),
+ binary.getFlavor().getName(),
+ "lib",
+ binary.getTargetPlatform().getName());
+ }
+
+ @NonNull
+ public static String getOutputDirectoryName(@NonNull NativeBinarySpec binary) {
+ return getOutputDirectoryName(
+ binary.getBuildType().getName(),
+ binary.getFlavor().getName(),
+ binary.getTargetPlatform().getName());
+ }
+
+ /**
+ * Return the name of the directory that will contain the native library with debug symbols.
+ */
+ @NonNull
+ public static String getDebugLibraryDirectoryName(
+ @NonNull String buildType,
+ @NonNull String productFlavor,
+ @NonNull String abi) {
+ return FileUtils.join(
+ FD_INTERMEDIATES,
+ "binaries",
+ buildType,
+ productFlavor,
+ "obj",
+ abi);
+ }
+
+ @NonNull
+ public static String getDebugLibraryDirectoryName(@NonNull NativeBinarySpec binary) {
+ return getDebugLibraryDirectoryName(
+ binary.getBuildType().getName(),
+ binary.getFlavor().getName(),
+ binary.getTargetPlatform().getName());
+ }
+
+ /**
+ * Return the name of the output shared library.
+ */
+ @NonNull
+ public static String getSharedLibraryFileName(@NonNull String moduleName) {
+ return "lib" + moduleName + ".so";
+ }
+
+ public static String getStaticLibraryFileName(String moduleName) {
+ return "lib" + moduleName + ".a";
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/StlConfiguration.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/StlConfiguration.java
new file mode 100644
index 0000000..be26657
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/StlConfiguration.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.ndk.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.NdkHandler;
+import com.google.common.collect.ImmutableList;
+
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Copy;
+import org.gradle.model.ModelMap;
+import org.gradle.nativeplatform.NativeBinarySpec;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Configuration to setup STL for NDK.
+ */
+public class StlConfiguration {
+
+ private static final List<String> VALID_STL = ImmutableList.of(
+ "system",
+ "stlport_static",
+ "stlport_shared",
+ "gnustl_static",
+ "gnustl_shared",
+ "gabi++_static",
+ "gabi++_shared",
+ "c++_static",
+ "c++_shared");
+
+ public static File getStlBaseDirectory(NdkHandler ndkHandler) {
+ return new File(ndkHandler.getNdkDirectory(), "sources/cxx-stl/");
+ }
+
+ public static void checkStl(String stl) {
+ if (!VALID_STL.contains(stl)) {
+ throw new InvalidUserDataException("Invalid STL: " + stl);
+ }
+ }
+
+ public static void createStlCopyTask(
+ @NonNull ModelMap<Task> tasks,
+ @NonNull final NativeBinarySpec binary,
+ @NonNull final File buildDir,
+ @NonNull NdkHandler ndkHandler,
+ @NonNull String stl,
+ @Nullable String stlVersion,
+ @NonNull String buildTaskName) {
+ if (stl.endsWith("_shared")) {
+ final StlNativeToolSpecification stlConfig = new StlNativeToolSpecification(
+ ndkHandler,
+ stl,
+ stlVersion,
+ binary.getTargetPlatform());
+
+ final String copyTaskName = NdkNamingScheme.getTaskName(binary, "copy", "StlSo");
+ tasks.create(copyTaskName, Copy.class, new Action<Copy>() {
+ @Override
+ public void execute(Copy copy) {
+ copy.from(stlConfig.getStlLib(binary.getTargetPlatform().getName()));
+ copy.into(new File(buildDir,
+ NdkNamingScheme.getDebugLibraryDirectoryName(binary)));
+
+ }
+ });
+ tasks.named(buildTaskName, new Action<Task>() {
+ @Override
+ public void execute(Task task) {
+ task.dependsOn(copyTaskName);
+ }
+ });
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/StlNativeToolSpecification.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/StlNativeToolSpecification.java
new file mode 100644
index 0000000..7af1946
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/StlNativeToolSpecification.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.ndk.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.core.Abi;
+import com.google.common.collect.Lists;
+
+import org.gradle.nativeplatform.platform.NativePlatform;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Compiler flags to configure STL.
+ */
+public class StlNativeToolSpecification extends AbstractNativeToolSpecification {
+ private NdkHandler ndkHandler;
+ private String stl;
+ private String stlName;
+ private String stlVersion;
+ private Boolean isStatic;
+ private NativePlatform platform;
+
+ public StlNativeToolSpecification(
+ @NonNull NdkHandler ndkHandler,
+ @NonNull String stl,
+ @Nullable String stlVersion,
+ @NonNull NativePlatform platform) {
+ this.ndkHandler = ndkHandler;
+ this.stl = stl;
+ this.stlVersion = stlVersion;
+ this.stlName = stl.equals("system") ? stl : stl.substring(0, stl.indexOf('_'));
+ this.isStatic = stl.endsWith("_static");
+ this.platform = platform;
+ }
+
+
+ @Override
+ public Iterable<String> getCFlags() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Iterable<String> getCppFlags() {
+
+ List<String> cppFlags = Lists.newArrayList();
+
+ if (stlName.equals("c++")) {
+ cppFlags.add("-std=c++11");
+ }
+
+ List<File> includeDirs = ndkHandler.getStlIncludes(
+ stlName,
+ stlVersion,
+ Abi.getByName(platform.getName()));
+ for (File dir : includeDirs) {
+ cppFlags.add("-I" + dir.toString());
+ }
+ return cppFlags;
+ }
+
+ @Override
+ public Iterable<String> getLdFlags() {
+ if (stl.equals("system")) {
+ return Collections.emptyList();
+ }
+ List<String> flags = Lists.newArrayList();
+ File stlLib = getStlLib(platform.getName());
+ // Add folder containing the STL library to ld library path so that user can easily append
+ // STL with the -l flag.
+ flags.add("-L" + stlLib.getParent());
+ flags.add("-l" + stlLib.getName().substring(3, stlLib.getName().lastIndexOf('.')));
+ return flags;
+ }
+
+ public File getStlLib(String abi) {
+ String stlLib;
+ if (stlName.equals("stlport")) {
+ stlLib = "stlport";
+ } else if (stlName.equals("gnustl")) {
+ String version = stlVersion != null
+ ? stlVersion
+ : ndkHandler.getGccToolchainVersion(Abi.getByName(abi));
+ stlLib = "gnu-libstdc++/" + version;
+ } else if (stlName.equals("gabi++")) {
+ stlLib = "gabi++";
+ } else if (stlName.equals("c++")) {
+ stlLib = "llvm-libc++";
+ } else {
+ throw new AssertionError(
+ "Unreachable. Either stl is invalid or stl is \"system\", " +
+ "in which case there is no library file and getStlLib should not be called.");
+ }
+ return new File(
+ StlConfiguration.getStlBaseDirectory(ndkHandler),
+ stlLib + "/libs/" + platform.getName() + "/lib" + stl + (isStatic ? ".a" : ".so"));
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/ToolchainConfiguration.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/ToolchainConfiguration.java
new file mode 100644
index 0000000..6002368
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/ndk/internal/ToolchainConfiguration.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.ndk.internal;
+
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.core.Toolchain;
+import com.android.build.gradle.managed.NdkAbiOptions;
+
+import org.gradle.api.Action;
+import org.gradle.model.ModelMap;
+import org.gradle.nativeplatform.platform.NativePlatform;
+import org.gradle.nativeplatform.toolchain.Clang;
+import org.gradle.nativeplatform.toolchain.Gcc;
+import org.gradle.nativeplatform.toolchain.GccCompatibleToolChain;
+import org.gradle.nativeplatform.toolchain.GccPlatformToolChain;
+import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
+import org.gradle.platform.base.PlatformContainer;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Action to configure toolchain for native binaries.
+ */
+public class ToolchainConfiguration {
+
+ public static void configurePlatforms(PlatformContainer platforms, NdkHandler ndkHandler) {
+ for (Abi abi : ndkHandler.getSupportedAbis()) {
+ NativePlatform platform = platforms.maybeCreate(abi.getName(), NativePlatform.class);
+
+ // All we care is the name of the platform. It doesn't matter what the
+ // architecture is, but it must be set to non-x86 so that it does not match
+ // the default supported platform.
+ platform.architecture("ppc");
+ platform.operatingSystem("linux");
+ }
+
+ }
+
+ /**
+ * Configure toolchain for a platform.
+ */
+ public static void configureToolchain(
+ NativeToolChainRegistry toolchainRegistry,
+ final String toolchainName,
+ final ModelMap<NdkAbiOptions> abiConfigs,
+ final NdkHandler ndkHandler) {
+ final Toolchain ndkToolchain = Toolchain.getByName(toolchainName);
+ toolchainRegistry.create("ndk-" + toolchainName,
+ (Class <? extends GccCompatibleToolChain>)
+ (toolchainName.equals("gcc") ? Gcc.class : Clang.class),
+ new Action<GccCompatibleToolChain>() {
+ @Override
+ public void execute(GccCompatibleToolChain toolchain) {
+ // Configure each platform.
+ for (final Abi abi : ndkHandler.getSupportedAbis()) {
+ toolchain.target(abi.getName(), new Action<GccPlatformToolChain>() {
+ @Override
+ public void execute(GccPlatformToolChain targetPlatform) {
+ if (Toolchain.GCC.equals(ndkToolchain)) {
+ String gccPrefix = abi.getGccExecutablePrefix();
+ targetPlatform.getcCompiler()
+ .setExecutable(gccPrefix + "-gcc");
+ targetPlatform.getCppCompiler()
+ .setExecutable(gccPrefix + "-g++");
+ targetPlatform.getLinker()
+ .setExecutable(gccPrefix + "-g++");
+ targetPlatform.getAssembler()
+ .setExecutable(gccPrefix + "-as");
+ targetPlatform.getStaticLibArchiver()
+ .setExecutable(gccPrefix + "-ar");
+ }
+
+ // By default, gradle will use -Xlinker to pass arguments to the linker.
+ // Removing it as it prevents -sysroot from being properly set.
+ targetPlatform.getLinker().withArguments(
+ new Action<List<String>>() {
+ @Override
+ public void execute(List<String> args) {
+ args.removeAll(Collections.singleton("-Xlinker"));
+ }
+ });
+
+ final NdkAbiOptions config = abiConfigs.get(abi.getName());
+
+ if (config != null) {
+ // Specify ABI specific flags.
+ targetPlatform.getcCompiler().withArguments(
+ new Action<List<String>>() {
+ @Override
+ public void execute(List<String> args) {
+ for (String flag : config.getCFlags()) {
+ args.add(flag);
+ }
+ }
+ });
+ targetPlatform.getCppCompiler().withArguments(
+ new Action<List<String>>() {
+ @Override
+ public void execute(List<String> args) {
+ for (String flag : config.getCppFlags()) {
+ args.add(flag);
+ }
+ }
+ });
+ targetPlatform.getLinker().withArguments(
+ new Action<List<String>>() {
+ @Override
+ public void execute(List<String> args) {
+ for (String flag : config.getLdFlags()) {
+ args.add(flag);
+ }
+ for (String lib : config.getLdLibs()) {
+ args.add("-l" + lib);
+ }
+ }
+ });
+ }
+ }
+ });
+ toolchain.path(ndkHandler.getCCompiler(abi).getParentFile());
+ }
+ }
+ });
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/tasks/StripDebugSymbolTask.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/tasks/StripDebugSymbolTask.java
new file mode 100644
index 0000000..f2c3dfe
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/tasks/StripDebugSymbolTask.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.process.GradleProcessExecutor;
+import com.android.build.gradle.ndk.internal.NdkNamingScheme;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.utils.FileUtils;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+import org.gradle.nativeplatform.NativeBinarySpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * Task to remove debug symbols from a native library.
+ */
+public class StripDebugSymbolTask extends DefaultTask {
+
+ private File stripExecutable;
+
+ private File inputFolder;
+
+ private Collection<File> inputFiles = Lists.newArrayList();
+
+ private File outputFolder;
+
+ // ----- PUBLIC API -----
+
+ /**
+ * Strip command found in the NDK.
+ */
+ @Input
+ public File getStripExecutable() {
+ return stripExecutable;
+ }
+
+ public void setStripExecutable(File stripExecutable) {
+ this.stripExecutable = stripExecutable;
+ }
+
+ /**
+ * Directory containing all the files to be stripped.
+ */
+ @SuppressWarnings("unused") // Used by incremental task action.
+ @InputDirectory
+ public File getInputFolder() {
+ return inputFolder;
+ }
+
+ public void setInputFolder(File inputFolder) {
+ this.inputFolder = inputFolder;
+ }
+
+ @SuppressWarnings("unused") // Used by incremental task action.
+ @InputFiles
+ public Collection<File> getInputFiles() {
+ return inputFiles;
+ }
+
+ @OutputDirectory
+ public File getOutputFolder() {
+ return outputFolder;
+ }
+
+ public void setOutputFolder(File outputFolder) {
+ this.outputFolder = outputFolder;
+ }
+
+ // ----- PRIVATE API -----
+
+ @TaskAction
+ void taskAction(IncrementalTaskInputs inputs) throws IOException {
+ inputs.outOfDate(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ File input = inputFileDetails.getFile();
+ File output = new File(getOutputFolder(), input.getName());
+ stripFile(input, output);
+ }
+ });
+ inputs.removed(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ File input = inputFileDetails.getFile();
+ File output = new File(getOutputFolder(), input.getName());
+ try {
+ FileUtils.deleteIfExists(output);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+
+ private void stripFile(File input, File output) {
+ if (!getOutputFolder().exists()) {
+ boolean result = getOutputFolder().mkdirs();
+ if (!result) {
+ throw new RuntimeException("Unabled to create directory '"
+ + getOutputFolder().toString() + "' for native binaries.");
+ }
+ }
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.setExecutable(getStripExecutable());
+ builder.addArgs("--strip-unneeded");
+ builder.addArgs("-o");
+ builder.addArgs(output.toString());
+ builder.addArgs(input.toString());
+ new GradleProcessExecutor(getProject()).execute(
+ builder.createProcess(),
+ new LoggedProcessOutputHandler(new LoggerWrapper(getLogger())));
+ }
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements Action<StripDebugSymbolTask> {
+ @NonNull
+ private final NativeBinarySpec binary;
+ @NonNull
+ private final File inputFolder;
+ @NonNull
+ private final Collection<File> inputFiles;
+ @NonNull
+ private final File buildDir;
+ @NonNull
+ private final NdkHandler handler;
+
+ public ConfigAction(
+ @NonNull NativeBinarySpec binary,
+ @NonNull File inputFolder,
+ @NonNull Collection<File> inputFiles,
+ @NonNull File buildDir,
+ @NonNull NdkHandler handler) {
+ this.binary = binary;
+ this.inputFolder = inputFolder;
+ this.inputFiles = inputFiles;
+ this.buildDir = buildDir;
+ this.handler = handler;
+ }
+
+ @Override
+ public void execute(@NonNull StripDebugSymbolTask task) {
+ task.setInputFolder(inputFolder);
+ task.getInputFiles().addAll(inputFiles);
+ task.setOutputFolder(new File(
+ buildDir,
+ NdkNamingScheme.getOutputDirectoryName(binary)));
+ task.setStripExecutable(handler.getStripCommand(
+ Abi.getByName(binary.getTargetPlatform().getName())));
+ task.dependsOn(binary);
+ }
+ }
+}
diff --git a/build-system/gradle-experimental/src/main/java/com/android/build/gradle/tasks/StripDependenciesTask.java b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/tasks/StripDependenciesTask.java
new file mode 100644
index 0000000..08b3cae
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/java/com/android/build/gradle/tasks/StripDependenciesTask.java
@@ -0,0 +1,224 @@
+/*
+ * 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.build.gradle.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.process.GradleProcessExecutor;
+import com.android.build.gradle.ndk.internal.NdkNamingScheme;
+import com.android.ide.common.process.LoggedProcessOutputHandler;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.io.Files;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
+import org.gradle.api.tasks.incremental.InputFileDetails;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Task to remove debug symbols from a native library depended on from jniLibs sourceSet.
+ *
+ * For files that already has the debug symbols stripped, simply copy the file.
+ */
+public class StripDependenciesTask extends DefaultTask {
+
+ private Map<Abi, File> stripExecutables = Maps.newHashMap();
+
+ private Map<File, Abi> inputFiles = Maps.newHashMap();
+
+ @NonNull
+ private Multimap<File, Abi> stripedFiles = ArrayListMultimap.create();
+
+ private File outputFolder;
+
+
+ // ----- PUBLIC API -----
+
+ /**
+ * Strip command found in the NDK.
+ */
+ @SuppressWarnings("unused") // Used for task input monitoring.
+ @Input
+ public Collection<File> getStripExecutables() {
+ return ImmutableList.copyOf(stripExecutables.values());
+ }
+
+ public void addStripExecutables(Map<Abi, File> stripCommands) {
+ this.stripExecutables.putAll(stripCommands);
+ }
+
+ @OutputDirectory
+ public File getOutputFolder() {
+ return outputFolder;
+ }
+
+ public void setOutputFolder(File outputFolder) {
+ this.outputFolder = outputFolder;
+ }
+
+ @SuppressWarnings("unused") // Used by incremental task action.
+ @InputFiles
+ Iterable<File> getInputFiles() {
+ return Iterables.concat(stripedFiles.keySet(), inputFiles.keySet());
+ }
+
+ public void addInputFiles(Map<File, Abi> files) {
+ inputFiles.putAll(files);
+ }
+
+ public void addStripedFiles(Multimap<File, Abi> files) {
+ stripedFiles.putAll(files);
+ }
+ // ----- PRIVATE API -----
+
+ @TaskAction
+ void taskAction(IncrementalTaskInputs inputs) throws IOException {
+ inputs.outOfDate(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ File input = inputFileDetails.getFile();
+ if (inputFiles.containsKey(input)) {
+ Abi abi = inputFiles.get(input);
+ File output = FileUtils.join(getOutputFolder(), abi.getName(), input.getName());
+ stripFile(input, output, abi);
+ } else {
+ for (Abi abi : stripedFiles.get(input)) {
+ File output = FileUtils.join(
+ getOutputFolder(),
+ abi.getName(),
+ input.getName());
+ try {
+ FileUtils.mkdirs(output.getParentFile());
+ Files.copy(input, output);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ });
+ inputs.removed(new Action<InputFileDetails>() {
+ @Override
+ public void execute(InputFileDetails inputFileDetails) {
+ File input = inputFileDetails.getFile();
+ if (inputFiles.containsKey(input)) {
+ removeFile(input, inputFiles.get(input));
+ } else {
+ for (Abi abi : stripedFiles.get(input)) {
+ removeFile(input, abi);
+ }
+ }
+ }
+ });
+ }
+
+ private void removeFile(File file, Abi abi) {
+ File output = FileUtils.join(getOutputFolder(), abi.getName(), file.getName());
+ try {
+ FileUtils.deleteIfExists(output);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void stripFile(File input, File output, Abi abi) {
+ File outputDir = output.getParentFile();
+ if (!output.getParentFile().exists()) {
+ boolean result = outputDir.mkdirs();
+ if (!result) {
+ throw new RuntimeException("Unabled to create directory '"
+ + outputDir.toString() + "' for native binaries.");
+ }
+ }
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.setExecutable(stripExecutables.get(abi));
+ builder.addArgs("--strip-unneeded");
+ builder.addArgs("-o");
+ builder.addArgs(output.toString());
+ builder.addArgs(input.toString());
+ new GradleProcessExecutor(getProject()).execute(
+ builder.createProcess(),
+ new LoggedProcessOutputHandler(new LoggerWrapper(getLogger())));
+ }
+
+
+ // ----- ConfigAction -----
+
+ public static class ConfigAction implements Action<StripDependenciesTask> {
+ @NonNull
+ private final String buildType;
+ @NonNull
+ private final String flavor;
+ @NonNull
+ private final Map<File, Abi> inputFiles;
+ @NonNull
+ private final Multimap<File, Abi> stripedFiles;
+ @NonNull
+ private final File buildDir;
+ @NonNull
+ private final NdkHandler handler;
+
+ public ConfigAction(
+ @NonNull String buildType,
+ @NonNull String flavor,
+ @NonNull Map<File, Abi> inputFiles,
+ @NonNull Multimap<File, Abi> stripedFiles,
+ @NonNull File buildDir,
+ @NonNull NdkHandler handler) {
+ this.buildType = buildType;
+ this.flavor = flavor;
+ this.buildDir = buildDir;
+ this.handler = handler;
+ this.inputFiles = inputFiles;
+ this.stripedFiles = stripedFiles;
+ }
+
+ @Override
+ public void execute(@NonNull StripDependenciesTask task) {
+ task.addInputFiles(inputFiles);
+ task.addStripedFiles(stripedFiles);
+ task.setOutputFolder(new File(
+ buildDir,
+ NdkNamingScheme.getOutputDirectoryName(buildType, flavor, "")));
+ Map<Abi, File> stripCommands = Maps.newHashMap();
+ if (handler.isNdkDirConfigured()) {
+ for(Abi abi : handler.getSupportedAbis()) {
+ stripCommands.put(abi, handler.getStripCommand(abi));
+ task.addStripExecutables(stripCommands);
+ }
+ }
+ }
+ }
+}
diff --git a/base/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.application.properties b/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.application.properties
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.application.properties
rename to build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.application.properties
diff --git a/base/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.library.properties b/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.library.properties
similarity index 100%
rename from base/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.library.properties
rename to build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.library.properties
diff --git a/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.native.properties b/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.native.properties
new file mode 100644
index 0000000..f2d4ca9
--- /dev/null
+++ b/build-system/gradle-experimental/src/main/resources/META-INF/gradle-plugins/com.android.model.native.properties
@@ -0,0 +1 @@
+implementation-class=com.android.build.gradle.model.StandaloneNdkComponentModelPlugin
diff --git a/base/device_validator/MODULE_LICENSE_APACHE2 b/build-system/gradle/MODULE_LICENSE_APACHE2
similarity index 100%
rename from base/device_validator/MODULE_LICENSE_APACHE2
rename to build-system/gradle/MODULE_LICENSE_APACHE2
diff --git a/base/build-system/gradle/NOTICE b/build-system/gradle/NOTICE
similarity index 100%
rename from base/build-system/gradle/NOTICE
rename to build-system/gradle/NOTICE
diff --git a/base/build-system/gradle/build.gradle b/build-system/gradle/build.gradle
similarity index 100%
rename from base/build-system/gradle/build.gradle
rename to build-system/gradle/build.gradle
diff --git a/base/build-system/gradle/gradle.iml b/build-system/gradle/gradle.iml
similarity index 100%
rename from base/build-system/gradle/gradle.iml
rename to build-system/gradle/gradle.iml
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.java
new file mode 100644
index 0000000..7a75832
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.java
@@ -0,0 +1,49 @@
+package com.android.build.gradle;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.ApplicationVariant;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.builder.core.AndroidBuilder;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * {@code android} extension for {@code com.android.application} projects.
+ */
+public class AppExtension extends TestedExtension {
+
+ private final DefaultDomainObjectSet<ApplicationVariant> applicationVariantList
+ = new DefaultDomainObjectSet<ApplicationVariant>(ApplicationVariant.class);
+
+ public AppExtension(@NonNull ProjectInternal project, @NonNull Instantiator instantiator,
+ @NonNull AndroidBuilder androidBuilder, @NonNull SdkHandler sdkHandler,
+ @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
+ @NonNull ExtraModelInfo extraModelInfo, boolean isLibrary) {
+ super(project, instantiator, androidBuilder, sdkHandler, buildTypes, productFlavors,
+ signingConfigs, extraModelInfo, isLibrary);
+ }
+
+ /**
+ * Returns the list of Application variants. Since the collections is built after evaluation, it
+ * should be used with Gradle's <code>all</code> iterator to process future items.
+ */
+ public DomainObjectSet<ApplicationVariant> getApplicationVariants() {
+ return applicationVariantList;
+ }
+
+ @Override
+ public void addVariant(BaseVariant variant) {
+ applicationVariantList.add((ApplicationVariant) variant);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
new file mode 100644
index 0000000..9119a89
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle
+
+import android.databinding.tool.DataBindingBuilder
+import com.android.build.gradle.internal.ApplicationTaskManager
+import com.android.build.gradle.internal.DependencyManager
+import com.android.build.gradle.internal.SdkHandler
+import com.android.build.gradle.internal.TaskManager
+import com.android.build.gradle.internal.variant.ApplicationVariantFactory
+import com.android.build.gradle.internal.variant.VariantFactory
+import com.android.builder.core.AndroidBuilder
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+
+import javax.inject.Inject
+
+/**
+ * Gradle plugin class for 'application' projects.
+ */
+class AppPlugin extends BasePlugin implements Plugin<Project> {
+ @Inject
+ public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
+ super(instantiator, registry)
+ }
+
+ @Override
+ protected Class<? extends BaseExtension> getExtensionClass() {
+ return AppExtension.class
+ }
+
+ @Override
+ protected TaskManager createTaskManager(
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ return new ApplicationTaskManager(
+ project,
+ androidBuilder,
+ dataBindingBuilder,
+ extension,
+ sdkHandler,
+ dependencyManager,
+ toolingRegistry)
+ }
+
+ @Override
+ void apply(Project project) {
+ super.apply(project)
+ }
+
+ @Override
+ protected VariantFactory createVariantFactory() {
+ return new ApplicationVariantFactory(instantiator, androidBuilder, extension)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.java
new file mode 100644
index 0000000..2a6d35d
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.api.VariantFilter;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.LoggingUtil;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.SourceSetSourceProviderWrapper;
+import com.android.build.gradle.internal.coverage.JacocoOptions;
+import com.android.build.gradle.internal.dsl.AaptOptions;
+import com.android.build.gradle.internal.dsl.AdbOptions;
+import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.DexOptions;
+import com.android.build.gradle.internal.dsl.DataBindingOptions;
+import com.android.build.gradle.internal.dsl.LintOptions;
+import com.android.build.gradle.internal.dsl.PackagingOptions;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.build.gradle.internal.dsl.Splits;
+import com.android.build.gradle.internal.dsl.TestOptions;
+import com.android.build.api.transform.Transform;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.core.LibraryRequest;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.sdk.TargetInfo;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.builder.testing.api.TestServer;
+import com.android.resources.Density;
+import com.android.repository.Revision;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Base 'android' extension for all android plugins.
+ *
+ * <p>This is never used directly. Instead,
+ *<ul>
+ * <li>Plugin <code>com.android.application</code> uses {@link AppExtension}</li>
+ * <li>Plugin <code>com.android.library</code> uses {@link LibraryExtension}</li>
+ * <li>Plugin <code>com.android.test</code> uses {@link TestExtension}</li>
+ * </ul>
+ */
+ at SuppressWarnings("UnnecessaryInheritDoc")
+public abstract class BaseExtension implements AndroidConfig {
+
+ private String target;
+ private Revision buildToolsRevision;
+ private List<LibraryRequest> libraryRequests = Lists.newArrayList();
+
+ /** Default config, shared by all flavors. */
+ final ProductFlavor defaultConfig;
+
+ /** Options for aapt, tool for packaging resources. */
+ final AaptOptions aaptOptions;
+
+ /** Lint options. */
+ final LintOptions lintOptions;
+
+ /** Dex options. */
+ final DexOptions dexOptions;
+
+ /** Options for running tests. */
+ final TestOptions testOptions;
+
+ /** Compile options */
+ final CompileOptions compileOptions;
+
+ /** Packaging options. */
+ final PackagingOptions packagingOptions;
+
+ /** JaCoCo options. */
+ final JacocoOptions jacoco;
+
+ /**
+ * APK splits options.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
+ */
+ final Splits splits;
+
+ /** All product flavors used by this project. */
+ final NamedDomainObjectContainer<ProductFlavor> productFlavors;
+
+ /** Build types used by this project. */
+ final NamedDomainObjectContainer<BuildType> buildTypes;
+
+ /** Signing configs used by this project. */
+ final NamedDomainObjectContainer<SigningConfig> signingConfigs;
+
+ private ExtraModelInfo extraModelInfo;
+
+ protected Project project;
+
+ /** Adb options */
+ final AdbOptions adbOptions;
+
+ /** A prefix to be used when creating new resources. Used by Studio */
+ String resourcePrefix;
+
+ List<String> flavorDimensionList;
+
+ private String defaultPublishConfig = "release";
+ private boolean publishNonDefault = false;
+
+ private Action<VariantFilter> variantFilter;
+
+ private final List<DeviceProvider> deviceProviderList = Lists.newArrayList();
+ private final List<TestServer> testServerList = Lists.newArrayList();
+ private final List<Transform> transforms = Lists.newArrayList();
+ // secondary dependencies for the custom transform.
+ private final List<List<Object>> transformDependencies = Lists.newArrayList();
+
+ private final AndroidBuilder androidBuilder;
+
+ private final SdkHandler sdkHandler;
+
+ protected Logger logger;
+
+ private boolean isWritable = true;
+
+ /** Data Binding options */
+ final DataBindingOptions dataBinding;
+
+ /**
+ * The source sets container.
+ */
+ final NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer;
+
+ BaseExtension(
+ @NonNull final ProjectInternal project,
+ @NonNull Instantiator instantiator,
+ @NonNull AndroidBuilder androidBuilder,
+ @NonNull SdkHandler sdkHandler,
+ @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
+ @NonNull ExtraModelInfo extraModelInfo,
+ final boolean isLibrary) {
+ this.androidBuilder = androidBuilder;
+ this.sdkHandler = sdkHandler;
+ this.buildTypes = buildTypes;
+ //noinspection unchecked
+ this.productFlavors = (NamedDomainObjectContainer) productFlavors;
+ this.signingConfigs = signingConfigs;
+ this.extraModelInfo = extraModelInfo;
+ this.project = project;
+
+ logger = Logging.getLogger(this.getClass());
+
+ defaultConfig = instantiator.newInstance(ProductFlavor.class, BuilderConstants.MAIN,
+ project, instantiator, project.getLogger(), extraModelInfo);
+
+ aaptOptions = instantiator.newInstance(AaptOptions.class);
+ dexOptions = instantiator.newInstance(DexOptions.class);
+ lintOptions = instantiator.newInstance(LintOptions.class);
+ testOptions = instantiator.newInstance(TestOptions.class);
+ compileOptions = instantiator.newInstance(CompileOptions.class);
+ packagingOptions = instantiator.newInstance(PackagingOptions.class);
+ jacoco = instantiator.newInstance(JacocoOptions.class);
+ adbOptions = instantiator.newInstance(AdbOptions.class);
+ splits = instantiator.newInstance(Splits.class, instantiator);
+ dataBinding = instantiator.newInstance(DataBindingOptions.class);
+
+ sourceSetsContainer = project.container(AndroidSourceSet.class,
+ new AndroidSourceSetFactory(instantiator, project, isLibrary));
+
+ sourceSetsContainer.whenObjectAdded(new Action<AndroidSourceSet>() {
+ @Override
+ public void execute(AndroidSourceSet sourceSet) {
+ ConfigurationContainer configurations = project.getConfigurations();
+
+ createConfiguration(
+ configurations,
+ sourceSet.getCompileConfigurationName(),
+ "Classpath for compiling the " + sourceSet.getName() + " sources.");
+
+ String packageConfigDescription;
+ if (isLibrary) {
+ packageConfigDescription
+ = "Classpath only used when publishing '" + sourceSet.getName() + "'.";
+ } else {
+ packageConfigDescription
+ = "Classpath packaged with the compiled '" + sourceSet.getName() + "' classes.";
+ }
+ createConfiguration(
+ configurations,
+ sourceSet.getPackageConfigurationName(),
+ packageConfigDescription);
+
+ createConfiguration(
+ configurations,
+ sourceSet.getProvidedConfigurationName(),
+ "Classpath for only compiling the " + sourceSet.getName() + " sources.");
+
+ createConfiguration(
+ configurations,
+ sourceSet.getWearAppConfigurationName(),
+ "Link to a wear app to embed for object '" + sourceSet.getName() + "'.");
+
+ sourceSet.setRoot(String.format("src/%s", sourceSet.getName()));
+ }
+ });
+
+ sourceSetsContainer.create(defaultConfig.getName());
+
+ setDefaultConfigValues();
+ }
+
+ private void setDefaultConfigValues() {
+ Set<Density> densities = Density.getRecommendedValuesForDevice();
+ Set<String> strings = Sets.newHashSetWithExpectedSize(densities.size());
+ for (Density density : densities) {
+ strings.add(density.getResourceValue());
+ }
+ defaultConfig.getVectorDrawables().setGeneratedDensities(strings);
+ defaultConfig.getVectorDrawables().setUseSupportLibrary(false);
+ }
+
+ /**
+ * Disallow further modification on the extension.
+ */
+ public void disableWrite() {
+ isWritable = false;
+ }
+
+ protected void checkWritability() {
+ if (!isWritable) {
+ throw new GradleException(
+ "Android tasks have already been created.\n" +
+ "This happens when calling android.applicationVariants,\n" +
+ "android.libraryVariants or android.testVariants.\n" +
+ "Once these methods are called, it is not possible to\n" +
+ "continue configuring the model.");
+ }
+ }
+
+ protected void createConfiguration(
+ @NonNull ConfigurationContainer configurations,
+ @NonNull String configurationName,
+ @NonNull String configurationDescription) {
+ logger.info("Creating configuration {}", configurationName);
+
+ Configuration configuration = configurations.findByName(configurationName);
+ if (configuration == null) {
+ configuration = configurations.create(configurationName);
+ }
+ configuration.setVisible(false);
+ configuration.setDescription(configurationDescription);
+ }
+
+ /**
+ * Sets the compile SDK version, based on full SDK version string, e.g.
+ * <code>android-21</code> for Lollipop.
+ */
+ public void compileSdkVersion(String version) {
+ checkWritability();
+ this.target = version;
+ }
+
+ /**
+ * Sets the compile SDK version, based on API level, e.g. 21 for Lollipop.
+ */
+ public void compileSdkVersion(int apiLevel) {
+ compileSdkVersion("android-" + apiLevel);
+ }
+
+ public void setCompileSdkVersion(int apiLevel) {
+ compileSdkVersion(apiLevel);
+ }
+
+ public void setCompileSdkVersion(String target) {
+ compileSdkVersion(target);
+ }
+
+ /**
+ * Request the use a of Library. The library is then added to the classpath.
+ * @param name the name of the library.
+ */
+ public void useLibrary(String name) {
+ useLibrary(name, true);
+ }
+
+ /**
+ * Request the use a of Library. The library is then added to the classpath.
+ * @param name the name of the library.
+ * @param required if using the library requires a manifest entry, the entry will
+ * indicate that the library is not required.
+ */
+ public void useLibrary(String name, boolean required) {
+ libraryRequests.add(new LibraryRequest(name, required));
+ }
+
+ public void buildToolsVersion(String version) {
+ checkWritability();
+ //The underlying Revision class has the maven artifact semantic,
+ // so 20 is not the same as 20.0. For the build tools revision this
+ // is not the desired behavior, so normalize e.g. to 20.0.0.
+ buildToolsRevision = Revision.parseRevision(version, Revision.Precision.MICRO);
+ }
+
+ /**
+ * <strong>Required.</strong> Version of the build tools to use.
+ *
+ * <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
+ * back may give a slightly different string.
+ */
+ @Override
+ public String getBuildToolsVersion() {
+ return buildToolsRevision.toString();
+ }
+
+ public void setBuildToolsVersion(String version) {
+ buildToolsVersion(version);
+ }
+
+ /**
+ * Configures build types.
+ */
+ public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
+ checkWritability();
+ action.execute(buildTypes);
+ }
+
+ /**
+ * Configures product flavors.
+ */
+ public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
+ checkWritability();
+ action.execute(productFlavors);
+ }
+
+ /**
+ * Configures signing configs.
+ */
+ public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
+ checkWritability();
+ action.execute(signingConfigs);
+ }
+
+ /**
+ * Specifies names of flavor dimensions.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Multi-flavor-variants">Multi-flavor variants</a>.
+ */
+ public void flavorDimensions(String... dimensions) {
+ checkWritability();
+ flavorDimensionList = Arrays.asList(dimensions);
+ }
+
+ /**
+ * Configures source sets.
+ *
+ * <p>Note that the Android plugin uses its own implementation of source sets,
+ * {@link AndroidSourceSet}.
+ */
+ public void sourceSets(Action<NamedDomainObjectContainer<AndroidSourceSet>> action) {
+ checkWritability();
+ action.execute(sourceSetsContainer);
+ }
+
+ /**
+ * All source sets. Note that the Android plugin uses its own implementation of
+ * source sets, {@link AndroidSourceSet}.
+ */
+ @Override
+ public NamedDomainObjectContainer<AndroidSourceSet> getSourceSets() {
+ return sourceSetsContainer;
+ }
+
+ /**
+ * The default configuration, inherited by all product flavors (if any are defined).
+ */
+ public void defaultConfig(Action<ProductFlavor> action) {
+ checkWritability();
+ action.execute(defaultConfig);
+ }
+
+ /**
+ * Configures aapt options.
+ */
+ public void aaptOptions(Action<AaptOptions> action) {
+ checkWritability();
+ action.execute(aaptOptions);
+ }
+
+ /**
+ * Configures dex options.
+ */
+ public void dexOptions(Action<DexOptions> action) {
+ checkWritability();
+ action.execute(dexOptions);
+ }
+
+ /**
+ * Configures lint options.
+ */
+ public void lintOptions(Action<LintOptions> action) {
+ checkWritability();
+ action.execute(lintOptions);
+ }
+
+ /** Configures test options. */
+ public void testOptions(Action<TestOptions> action) {
+ checkWritability();
+ action.execute(testOptions);
+ }
+
+ /**
+ * Configures compile options.
+ */
+ public void compileOptions(Action<CompileOptions> action) {
+ checkWritability();
+ action.execute(compileOptions);
+ }
+
+ /**
+ * Configures packaging options.
+ */
+ public void packagingOptions(Action<PackagingOptions> action) {
+ checkWritability();
+ action.execute(packagingOptions);
+ }
+
+ /**
+ * Configures JaCoCo options.
+ */
+ public void jacoco(Action<JacocoOptions> action) {
+ checkWritability();
+ action.execute(jacoco);
+ }
+
+ /**
+ * Configures adb options.
+ */
+ public void adbOptions(Action<AdbOptions> action) {
+ checkWritability();
+ action.execute(adbOptions);
+ }
+
+ /**
+ * Configures APK splits.
+ */
+ public void splits(Action<Splits> action) {
+ checkWritability();
+ action.execute(splits);
+ }
+
+ /**
+ * Configures data binding options.
+ */
+ public void dataBinding(Action<DataBindingOptions> action) {
+ checkWritability();
+ action.execute(dataBinding);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public DataBindingOptions getDataBinding() {
+ return dataBinding;
+ }
+
+ public void deviceProvider(DeviceProvider deviceProvider) {
+ checkWritability();
+ deviceProviderList.add(deviceProvider);
+ }
+
+ @Override
+ @NonNull
+ public List<DeviceProvider> getDeviceProviders() {
+ return deviceProviderList;
+ }
+
+ public void testServer(TestServer testServer) {
+ checkWritability();
+ testServerList.add(testServer);
+ }
+
+ @Override
+ @NonNull
+ public List<TestServer> getTestServers() {
+ return testServerList;
+ }
+
+ public void registerTransform(@NonNull Transform transform, Object... dependencies) {
+ transforms.add(transform);
+ transformDependencies.add(Arrays.asList(dependencies));
+ }
+
+ @Override
+ @NonNull
+ public List<Transform> getTransforms() {
+ return ImmutableList.copyOf(transforms);
+ }
+
+ @Override
+ @NonNull
+ public List<List<Object>> getTransformsDependencies() {
+ return ImmutableList.copyOf(transformDependencies);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Collection<ProductFlavor> getProductFlavors() {
+ return productFlavors;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Collection<BuildType> getBuildTypes() {
+ return buildTypes;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Collection<SigningConfig> getSigningConfigs() {
+ return signingConfigs;
+ }
+
+ public void defaultPublishConfig(String value) {
+ setDefaultPublishConfig(value);
+ }
+
+ public void publishNonDefault(boolean value) {
+ publishNonDefault = value;
+ }
+
+ /**
+ * Name of the configuration used to build the default artifact of this project.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
+ * Referencing a Library</a>
+ */
+ @Override
+ public String getDefaultPublishConfig() {
+ return defaultPublishConfig;
+ }
+
+ public void setDefaultPublishConfig(String value) {
+ defaultPublishConfig = value;
+ }
+
+ /**
+ * Whether to publish artifacts for all configurations, not just the default one.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
+ * Referencing a Library</a>
+ */
+ @Override
+ public boolean getPublishNonDefault() {
+ return publishNonDefault;
+ }
+
+ public void variantFilter(Action<VariantFilter> filter) {
+ setVariantFilter(filter);
+ }
+
+ public void setVariantFilter(Action<VariantFilter> filter) {
+ variantFilter = filter;
+ }
+
+ /**
+ * Callback to control which variants should be excluded.
+ *
+ * <p>The {@link Action} is passed a single object of type
+ * {@link com.android.build.gradle.api.VariantFilter}. It should set the
+ * {@link VariantFilter#setIgnore(boolean)} flag to filter out the given variant.
+ */
+ @Override
+ public Action<VariantFilter> getVariantFilter() {
+ return variantFilter;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AdbOptions getAdbOptions() {
+ return adbOptions;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getResourcePrefix() {
+ return resourcePrefix;
+ }
+
+ /**
+ * Returns the names of flavor dimensions.
+ *
+ * <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Multi-flavor-variants">Multi-flavor variants</a>.
+ */
+ @Override
+ public List<String> getFlavorDimensionList() {
+ return flavorDimensionList;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getGeneratePureSplits() {
+ return generatePureSplits;
+ }
+
+ public void resourcePrefix(String prefix) {
+ resourcePrefix = prefix;
+ }
+
+ public abstract void addVariant(BaseVariant variant);
+
+ public void registerArtifactType(@NonNull String name,
+ boolean isTest,
+ int artifactType) {
+ extraModelInfo.registerArtifactType(name, isTest, artifactType);
+ }
+
+ public void registerBuildTypeSourceProvider(
+ @NonNull String name,
+ @NonNull BuildType buildType,
+ @NonNull SourceProvider sourceProvider) {
+ extraModelInfo.registerBuildTypeSourceProvider(name, buildType, sourceProvider);
+ }
+
+ public void registerProductFlavorSourceProvider(
+ @NonNull String name,
+ @NonNull ProductFlavor productFlavor,
+ @NonNull SourceProvider sourceProvider) {
+ extraModelInfo.registerProductFlavorSourceProvider(name, productFlavor, sourceProvider);
+ }
+
+ public void registerJavaArtifact(
+ @NonNull String name,
+ @NonNull BaseVariant variant,
+ @NonNull String assembleTaskName,
+ @NonNull String javaCompileTaskName,
+ @NonNull Collection<File> generatedSourceFolders,
+ @NonNull Iterable<String> ideSetupTaskNames,
+ @NonNull Configuration configuration,
+ @NonNull File classesFolder,
+ @NonNull File javaResourceFolder,
+ @Nullable SourceProvider sourceProvider) {
+ extraModelInfo.registerJavaArtifact(name, variant, assembleTaskName,
+ javaCompileTaskName, generatedSourceFolders, ideSetupTaskNames,
+ configuration, classesFolder, javaResourceFolder, sourceProvider);
+ }
+
+ public void registerMultiFlavorSourceProvider(
+ @NonNull String name,
+ @NonNull String flavorName,
+ @NonNull SourceProvider sourceProvider) {
+ extraModelInfo.registerMultiFlavorSourceProvider(name, flavorName, sourceProvider);
+ }
+
+ @NonNull
+ public SourceProvider wrapJavaSourceSet(@NonNull SourceSet sourceSet) {
+ return new SourceSetSourceProviderWrapper(sourceSet);
+ }
+
+ /**
+ * <strong>Required.</strong> Compile SDK version.
+ *
+ * <p>Your code will be compiled against the android.jar from this API level. You should
+ * generally use the most up-to-date SDK version here. Use the Lint tool to make sure you don't
+ * use APIs not available in earlier platform version without checking.
+ *
+ * <p>Setter can be called with a string like "android-21" or a number.
+ *
+ * <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
+ * back may give a slightly different string.
+ */
+ @Override
+ public String getCompileSdkVersion() {
+ return target;
+ }
+
+ @Override
+ public Revision getBuildToolsRevision() {
+ return buildToolsRevision;
+ }
+
+ @Override
+ public Collection<LibraryRequest> getLibraryRequests() {
+ return libraryRequests;
+ }
+
+ /**
+ * Returns the SDK directory used.
+ */
+ public File getSdkDirectory() {
+ return sdkHandler.getSdkFolder();
+ }
+
+ /**
+ * ReturnS the NDK directory used.
+ */
+ public File getNdkDirectory() {
+ return sdkHandler.getNdkFolder();
+ }
+
+ public List<File> getBootClasspath() {
+ ensureTargetSetup();
+ return androidBuilder.getBootClasspath(false);
+ }
+
+ public File getAdbExe() {
+ return sdkHandler.getSdkInfo().getAdb();
+ }
+
+ public File getDefaultProguardFile(String name) {
+ File sdkDir = sdkHandler.getAndCheckSdkFolder();
+ return new File(sdkDir,
+ SdkConstants.FD_TOOLS + File.separatorChar
+ + SdkConstants.FD_PROGUARD + File.separatorChar
+ + name);
+ }
+
+ // ---------------
+ // TEMP for compatibility
+
+ // by default, we do not generate pure splits
+ boolean generatePureSplits = false;
+
+ public void generatePureSplits(boolean flag) {
+ setGeneratePureSplits(flag);
+ }
+
+ public void setGeneratePureSplits(boolean flag) {
+ if (flag) {
+ logger.warn("Pure splits are not supported by PlayStore yet.");
+ }
+ this.generatePureSplits = flag;
+ }
+
+ private boolean enforceUniquePackageName = true;
+
+ public void enforceUniquePackageName(boolean value) {
+ if (!value) {
+ LoggingUtil.displayDeprecationWarning(logger, project, "Support for libraries with same package name is deprecated and will be removed in a future release.");
+ }
+ enforceUniquePackageName = value;
+ }
+
+ public void setEnforceUniquePackageName(boolean value) {
+ enforceUniquePackageName(value);
+ }
+
+ @Override
+ public boolean getEnforceUniquePackageName() {
+ return enforceUniquePackageName;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ProductFlavor getDefaultConfig() {
+ return defaultConfig;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AaptOptions getAaptOptions() {
+ return aaptOptions;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CompileOptions getCompileOptions() {
+ return compileOptions;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public DexOptions getDexOptions() {
+ return dexOptions;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public JacocoOptions getJacoco() {
+ return jacoco;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public LintOptions getLintOptions() {
+ return lintOptions;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PackagingOptions getPackagingOptions() {
+ return packagingOptions;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Splits getSplits() {
+ return splits;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestOptions getTestOptions() {
+ return testOptions;
+ }
+
+ private void ensureTargetSetup() {
+ // check if the target has been set.
+ TargetInfo targetInfo = androidBuilder.getTargetInfo();
+ if (targetInfo == null) {
+ sdkHandler.initTarget(
+ getCompileSdkVersion(),
+ buildToolsRevision,
+ libraryRequests,
+ androidBuilder,
+ SdkHandler.useCachedSdk(project));
+ }
+ }
+
+ // For compatibility with LibraryExtension.
+ @Override
+ public Boolean getPackageBuildConfig() {
+ throw new GradleException("packageBuildConfig is not supported.");
+ }
+
+ @Override
+ public Collection<String> getAidlPackageWhiteList() {
+ throw new GradleException("aidlPackageWhiteList is not supported.");
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.java
new file mode 100644
index 0000000..1befd12
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.java
@@ -0,0 +1,741 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle;
+
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES;
+import static com.google.common.base.Preconditions.checkState;
+import static java.io.File.separator;
+
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.gradle.internal.ApiObjectFactory;
+import com.android.build.gradle.internal.BadPluginException;
+import com.android.build.gradle.internal.DependencyManager;
+import com.android.build.gradle.internal.ExecutionConfigurationUtil;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.LibraryCache;
+import com.android.build.gradle.internal.LoggerWrapper;
+import com.android.build.gradle.internal.NativeLibraryFactoryImpl;
+import com.android.build.gradle.internal.NdkHandler;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.TaskContainerAdaptor;
+import com.android.build.gradle.internal.TaskManager;
+import com.android.build.gradle.internal.VariantManager;
+import com.android.build.gradle.internal.coverage.JacocoPlugin;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.BuildTypeFactory;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.ProductFlavorFactory;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.build.gradle.internal.dsl.SigningConfigFactory;
+import com.android.build.gradle.internal.model.ModelBuilder;
+import com.android.build.gradle.internal.pipeline.TransformTask;
+import com.android.build.gradle.internal.process.GradleJavaProcessExecutor;
+import com.android.build.gradle.internal.process.GradleProcessExecutor;
+import com.android.build.gradle.internal.profile.RecordingBuildListener;
+import com.android.build.gradle.internal.transforms.DexTransform;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.VariantFactory;
+import com.android.build.gradle.tasks.JillTask;
+import com.android.builder.Version;
+import com.android.builder.core.AndroidBuilder;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.internal.compiler.JackConversionCache;
+import com.android.builder.internal.compiler.PreDexCache;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.profile.ExecutionType;
+import com.android.builder.profile.ProcessRecorderFactory;
+import com.android.builder.profile.Recorder;
+import com.android.builder.profile.ThreadRecorder;
+import com.android.builder.sdk.TargetInfo;
+import com.android.ide.common.internal.ExecutorSingleton;
+import com.android.utils.ILogger;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.tasks.StopExecutionException;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
+
+import android.databinding.tool.DataBindingBuilder;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.Manifest;
+import java.util.regex.Pattern;
+
+/**
+ * Base class for all Android plugins
+ */
+public abstract class BasePlugin {
+
+ private static final String GRADLE_MIN_VERSION = "2.10";
+ public static final Pattern GRADLE_ACCEPTABLE_VERSIONS = Pattern.compile("2\\.\\d{2,}.*");
+ private static final String GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY =
+ "com.android.build.gradle.overrideVersionCheck";
+ private static final String SKIP_PATH_CHECK_PROPERTY =
+ "com.android.build.gradle.overridePathCheck";
+ /** default retirement age in days since its inception date for RC or beta versions. */
+ private static final int DEFAULT_RETIREMENT_AGE_FOR_NON_RELEASE_IN_DAYS = 40;
+
+
+ protected BaseExtension extension;
+
+ protected VariantManager variantManager;
+
+ protected TaskManager taskManager;
+
+ protected Project project;
+
+ protected SdkHandler sdkHandler;
+
+ private NdkHandler ndkHandler;
+
+ protected AndroidBuilder androidBuilder;
+
+ protected DataBindingBuilder dataBindingBuilder;
+
+ protected Instantiator instantiator;
+
+ protected VariantFactory variantFactory;
+
+ private ToolingModelBuilderRegistry registry;
+
+ private JacocoPlugin jacocoPlugin;
+
+ private LoggerWrapper loggerWrapper;
+
+ private ExtraModelInfo extraModelInfo;
+
+ private String creator;
+
+ private boolean hasCreatedTasks = false;
+
+ protected BasePlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
+ this.instantiator = instantiator;
+ this.registry = registry;
+ creator = "Android Gradle " + Version.ANDROID_GRADLE_PLUGIN_VERSION;
+ verifyRetirementAge();
+
+ ModelBuilder.clearCaches();
+ }
+
+ /**
+ * Verify that this plugin execution is within its public time range.
+ */
+ private void verifyRetirementAge() {
+
+ Manifest manifest;
+ URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
+ try {
+ URL url = cl.findResource("META-INF/MANIFEST.MF");
+ manifest = new Manifest(url.openStream());
+ } catch (IOException ignore) {
+ return;
+ }
+
+ int retirementAgeInDays =
+ getRetirementAgeInDays(manifest.getMainAttributes().getValue("Plugin-Version"));
+
+ // if this plugin version will never be outdated, return.
+ if (retirementAgeInDays == -1) {
+ return;
+ }
+
+ String inceptionDateAttr = manifest.getMainAttributes().getValue("Inception-Date");
+ // when running in unit tests, etc... the manifest entries are absent.
+ if (inceptionDateAttr == null) {
+ return;
+ }
+ List<String> items = ImmutableList.copyOf(Splitter.on(':').split(inceptionDateAttr));
+ GregorianCalendar inceptionDate = new GregorianCalendar(Integer.parseInt(items.get(0)),
+ Integer.parseInt(items.get(1)), Integer.parseInt(items.get(2)));
+
+
+ Calendar now = GregorianCalendar.getInstance();
+ long nowTimestamp = now.getTimeInMillis();
+ long inceptionTimestamp = inceptionDate.getTimeInMillis();
+ long days = TimeUnit.DAYS.convert(nowTimestamp - inceptionTimestamp, TimeUnit.MILLISECONDS);
+ if (days > retirementAgeInDays) {
+ // this plugin is too old.
+ String dailyOverride = System.getenv("ANDROID_DAILY_OVERRIDE");
+ final MessageDigest crypt;
+ try {
+ crypt = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return;
+ }
+ crypt.reset();
+ // encode the day, not the current time.
+ try {
+ crypt.update(String.format("%1$s:%2$s:%3$s",
+ now.get(Calendar.YEAR),
+ now.get(Calendar.MONTH),
+ now.get(Calendar.DATE)).getBytes("utf8"));
+ } catch (UnsupportedEncodingException e) {
+ return;
+ }
+ String overrideValue = new BigInteger(1, crypt.digest()).toString(16);
+ if (dailyOverride == null) {
+ String message = "Plugin is too old, please update to a more recent version, or " +
+ "set ANDROID_DAILY_OVERRIDE environment variable to \"" + overrideValue + '"';
+ System.err.println(message);
+ throw new RuntimeException(message);
+ } else {
+ if (!dailyOverride.equals(overrideValue)) {
+ String message = "Plugin is too old and ANDROID_DAILY_OVERRIDE value is " +
+ "also outdated, please use new value :\"" + overrideValue + '"';
+ System.err.println(message);
+ throw new RuntimeException(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the retirement age for this plugin depending on its version string, or -1 if this
+ * plugin version will never become obsolete
+ * @param version the plugin full version, like 1.3.4-preview5 or 1.0.2 or 1.2.3-beta4
+ * @return the retirement age in days or -1 if no retirement
+ */
+ private static int getRetirementAgeInDays(@Nullable String version) {
+ if (version == null || version.contains("rc") || version.contains("beta")
+ || version.contains("alpha") || version.contains("preview")) {
+ return DEFAULT_RETIREMENT_AGE_FOR_NON_RELEASE_IN_DAYS;
+ }
+ return -1;
+ }
+
+ protected abstract Class<? extends BaseExtension> getExtensionClass();
+ protected abstract VariantFactory createVariantFactory();
+ protected abstract TaskManager createTaskManager(
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry);
+
+ /**
+ * Return whether this plugin creates Android library. Should be overridden if true.
+ */
+ protected boolean isLibrary() {
+ return false;
+ }
+
+ @VisibleForTesting
+ VariantManager getVariantManager() {
+ return variantManager;
+ }
+
+ protected ILogger getLogger() {
+ if (loggerWrapper == null) {
+ loggerWrapper = new LoggerWrapper(project.getLogger());
+ }
+
+ return loggerWrapper;
+ }
+
+
+ protected void apply(Project project) throws IOException {
+ this.project = project;
+
+ ExecutionConfigurationUtil.setThreadPoolSize(project);
+ checkPathForErrors();
+ checkModulesForErrors();
+
+ List<Recorder.Property> propertyList = Lists.newArrayList(
+ new Recorder.Property("plugin_version", Version.ANDROID_GRADLE_PLUGIN_VERSION),
+ new Recorder.Property("next_gen_plugin", "false"),
+ new Recorder.Property("gradle_version", project.getGradle().getGradleVersion())
+ );
+ String benchmarkName = AndroidGradleOptions.getBenchmarkName(project);
+ if (benchmarkName != null) {
+ propertyList.add(new Recorder.Property("benchmark_name", benchmarkName));
+ }
+ String benchmarkMode = AndroidGradleOptions.getBenchmarkMode(project);
+ if (benchmarkMode != null) {
+ propertyList.add(new Recorder.Property("benchmark_mode", benchmarkMode));
+ }
+
+ ProcessRecorderFactory.initialize(
+ getLogger(),
+ project.getRootProject().file("profiler" + System.currentTimeMillis() + ".json"),
+ propertyList);
+ project.getGradle().addListener(new RecordingBuildListener(ThreadRecorder.get()));
+
+
+ ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ configureProject();
+ return null;
+ }
+ }, new Recorder.Property("project", project.getName()));
+
+ ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createExtension();
+ return null;
+ }
+ }, new Recorder.Property("project", project.getName()));
+
+ ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createTasks();
+ return null;
+ }
+ }, new Recorder.Property("project", project.getName()));
+ }
+
+ protected void configureProject() {
+ extraModelInfo = new ExtraModelInfo(project, isLibrary());
+ checkGradleVersion();
+ sdkHandler = new SdkHandler(project, getLogger());
+ androidBuilder = new AndroidBuilder(
+ project == project.getRootProject() ? project.getName() : project.getPath(),
+ creator,
+ new GradleProcessExecutor(project),
+ new GradleJavaProcessExecutor(project),
+ extraModelInfo,
+ getLogger(),
+ isVerbose());
+ dataBindingBuilder = new DataBindingBuilder();
+ dataBindingBuilder.setPrintMachineReadableOutput(
+ extraModelInfo.getErrorFormatMode() ==
+ ExtraModelInfo.ErrorFormatMode.MACHINE_PARSABLE);
+ project.getPlugins().apply(JavaBasePlugin.class);
+
+ jacocoPlugin = project.getPlugins().apply(JacocoPlugin.class);
+
+ project.getTasks().getByName("assemble").setDescription(
+ "Assembles all variants of all applications and secondary packages.");
+
+ // call back on execution. This is called after the whole build is done (not
+ // after the current project is done).
+ // This is will be called for each (android) projects though, so this should support
+ // being called 2+ times.
+ project.getGradle().addBuildListener(new BuildListener() {
+ private final LibraryCache libraryCache = LibraryCache.getCache();
+
+ @Override
+ public void buildStarted(Gradle gradle) { }
+
+ @Override
+ public void settingsEvaluated(Settings settings) { }
+
+ @Override
+ public void projectsLoaded(Gradle gradle) { }
+
+ @Override
+ public void projectsEvaluated(Gradle gradle) { }
+
+ @Override
+ public void buildFinished(BuildResult buildResult) {
+ ExecutorSingleton.shutdown();
+ sdkHandler.unload();
+ ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
+ new Recorder.Block() {
+ @Override
+ public Void call() throws Exception {
+ PreDexCache.getCache().clear(
+ new File(project.getRootProject().getBuildDir(),
+ FD_INTERMEDIATES + "/dex-cache/cache.xml"),
+ getLogger());
+ JackConversionCache.getCache().clear(
+ new File(project.getRootProject().getBuildDir(),
+ FD_INTERMEDIATES + "/jack-cache/cache.xml"),
+ getLogger());
+ libraryCache.unload();
+ return null;
+ }
+ }, new Recorder.Property("project", project.getName()));
+
+ try {
+ ProcessRecorderFactory.shutdown();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ project.getGradle().getTaskGraph().addTaskExecutionGraphListener(
+ new TaskExecutionGraphListener() {
+ @Override
+ public void graphPopulated(TaskExecutionGraph taskGraph) {
+ for (Task task : taskGraph.getAllTasks()) {
+ if (task instanceof TransformTask) {
+ if (((TransformTask) task).getTransform() instanceof DexTransform) {
+ PreDexCache.getCache().load(
+ new File(project.getRootProject().getBuildDir(),
+ FD_INTERMEDIATES + "/dex-cache/cache.xml"));
+ break;
+ }
+ } else if (task instanceof JillTask) {
+ JackConversionCache.getCache().load(
+ new File(project.getRootProject().getBuildDir(),
+ FD_INTERMEDIATES + "/jack-cache/cache.xml"));
+ break;
+ }
+ }
+ }
+ });
+ }
+
+
+ private void createExtension() {
+ final NamedDomainObjectContainer<BuildType> buildTypeContainer = project.container(
+ BuildType.class,
+ new BuildTypeFactory(instantiator, project, project.getLogger()));
+ final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer = project.container(
+ ProductFlavor.class,
+ new ProductFlavorFactory(instantiator, project, project.getLogger(), extraModelInfo));
+ final NamedDomainObjectContainer<SigningConfig> signingConfigContainer = project.container(
+ SigningConfig.class,
+ new SigningConfigFactory(instantiator));
+
+ extension = project.getExtensions().create("android", getExtensionClass(),
+ project, instantiator, androidBuilder, sdkHandler,
+ buildTypeContainer, productFlavorContainer, signingConfigContainer,
+ extraModelInfo, isLibrary());
+
+ // create the default mapping configuration.
+ project.getConfigurations().create("default-mapping")
+ .setDescription("Configuration for default mapping artifacts.");
+ project.getConfigurations().create("default-metadata")
+ .setDescription("Metadata for the produced APKs.");
+
+ DependencyManager dependencyManager = new DependencyManager(project, extraModelInfo);
+ taskManager = createTaskManager(
+ project,
+ androidBuilder,
+ dataBindingBuilder,
+ extension,
+ sdkHandler,
+ dependencyManager,
+ registry);
+
+ variantFactory = createVariantFactory();
+ variantManager = new VariantManager(
+ project,
+ androidBuilder,
+ extension,
+ variantFactory,
+ taskManager,
+ instantiator);
+
+ ndkHandler = new NdkHandler(
+ project.getRootDir(),
+ null, /* compileSkdVersion, this will be set in afterEvaluate */
+ "gcc",
+ "" /*toolchainVersion*/);
+
+ // Register a builder for the custom tooling model
+ ModelBuilder modelBuilder = new ModelBuilder(
+ androidBuilder,
+ variantManager,
+ taskManager,
+ extension,
+ extraModelInfo,
+ ndkHandler,
+ new NativeLibraryFactoryImpl(ndkHandler),
+ isLibrary(),
+ AndroidProject.GENERATION_ORIGINAL);
+ registry.register(modelBuilder);
+
+ // map the whenObjectAdded callbacks on the containers.
+ signingConfigContainer.whenObjectAdded(new Action<SigningConfig>() {
+ @Override
+ public void execute(SigningConfig signingConfig) {
+ variantManager.addSigningConfig(signingConfig);
+ }
+ });
+
+
+ buildTypeContainer.whenObjectAdded(new Action<BuildType>() {
+ @Override
+ public void execute(BuildType buildType) {
+ SigningConfig signingConfig = signingConfigContainer.findByName(BuilderConstants.DEBUG);
+ buildType.init(signingConfig);
+ variantManager.addBuildType(buildType);
+ }
+ });
+
+ productFlavorContainer.whenObjectAdded(new Action<ProductFlavor>() {
+ @Override
+ public void execute(ProductFlavor productFlavor) {
+ variantManager.addProductFlavor(productFlavor);
+ }
+ });
+
+ // map whenObjectRemoved on the containers to throw an exception.
+ signingConfigContainer.whenObjectRemoved(
+ new UnsupportedAction("Removing signingConfigs is not supported."));
+ buildTypeContainer.whenObjectRemoved(
+ new UnsupportedAction("Removing build types is not supported."));
+ productFlavorContainer.whenObjectRemoved(
+ new UnsupportedAction("Removing product flavors is not supported."));
+
+ // create default Objects, signingConfig first as its used by the BuildTypes.
+ variantFactory.createDefaultComponents(
+ buildTypeContainer, productFlavorContainer, signingConfigContainer);
+ }
+
+ private static class UnsupportedAction implements Action<Object> {
+
+ private final String message;
+
+ UnsupportedAction(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public void execute(Object o) {
+ throw new UnsupportedOperationException(message);
+ }
+ }
+
+ private void createTasks() {
+ ThreadRecorder.get().record(ExecutionType.TASK_MANAGER_CREATE_TASKS,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ taskManager.createTasksBeforeEvaluate(
+ new TaskContainerAdaptor(project.getTasks()));
+ return null;
+ }
+ },
+ new Recorder.Property("project", project.getName()));
+
+ project.afterEvaluate(new Action<Project>() {
+ @Override
+ public void execute(Project project) {
+ ThreadRecorder.get().record(ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createAndroidTasks(false);
+ return null;
+ }
+ },
+ new Recorder.Property("project", project.getName()));
+ }
+ });
+ }
+
+ private void checkGradleVersion() {
+ if (!GRADLE_ACCEPTABLE_VERSIONS.matcher(project.getGradle().getGradleVersion()).matches()) {
+ boolean allowNonMatching = Boolean.getBoolean(GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
+ File file = new File("gradle" + separator + "wrapper" + separator +
+ "gradle-wrapper.properties");
+ String errorMessage = String.format(
+ "Gradle version %s is required. Current version is %s. " +
+ "If using the gradle wrapper, try editing the distributionUrl in %s " +
+ "to gradle-%s-all.zip",
+ GRADLE_MIN_VERSION, project.getGradle().getGradleVersion(), file.getAbsolutePath(),
+ GRADLE_MIN_VERSION);
+ if (allowNonMatching) {
+ getLogger().warning(errorMessage);
+ getLogger().warning("As %s is set, continuing anyways.",
+ GRADLE_VERSION_CHECK_OVERRIDE_PROPERTY);
+ } else {
+ extraModelInfo.handleSyncError(
+ GRADLE_MIN_VERSION, SyncIssue.TYPE_GRADLE_TOO_OLD, errorMessage);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ final void createAndroidTasks(boolean force) {
+ // Make sure unit tests set the required fields.
+ checkState(extension.getBuildToolsRevision() != null, "buildToolsVersion is not specified.");
+ checkState(extension.getCompileSdkVersion() != null, "compileSdkVersion is not specified.");
+
+ ndkHandler.setPlatformVersion(extension.getCompileSdkVersion());
+
+ // get current plugins and look for the default Java plugin.
+ if (project.getPlugins().hasPlugin(JavaPlugin.class)) {
+ throw new BadPluginException(
+ "The 'java' plugin has been applied, but it is not compatible with the Android plugins.");
+ }
+
+ ensureTargetSetup();
+
+ // don't do anything if the project was not initialized.
+ // Unless TEST_SDK_DIR is set in which case this is unit tests and we don't return.
+ // This is because project don't get evaluated in the unit test setup.
+ // See AppPluginDslTest
+ if (!force
+ && (!project.getState().getExecuted() || project.getState().getFailure()!= null)
+ && SdkHandler.sTestSdkFolder == null) {
+ return;
+ }
+
+ if (hasCreatedTasks) {
+ return;
+ }
+ hasCreatedTasks = true;
+
+ extension.disableWrite();
+
+ ThreadRecorder.get().record(
+ ExecutionType.GENERAL_CONFIG,
+ Recorder.EmptyBlock,
+ new Recorder.Property("build_tools_version",
+ extension.getBuildToolsRevision().toString()));
+
+ // setup SDK repositories.
+ for (final File file : sdkHandler.getSdkLoader().getRepositories()) {
+ project.getRepositories().maven(new Action<MavenArtifactRepository>() {
+ @Override
+ public void execute(MavenArtifactRepository mavenArtifactRepository) {
+ mavenArtifactRepository.setUrl(file.toURI());
+ }
+ });
+ }
+
+ taskManager.addDataBindingDependenciesIfNecessary(extension.getDataBinding());
+ ThreadRecorder.get().record(ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
+ new Recorder.Block<Void>() {
+ @Override
+ public Void call() throws Exception {
+ variantManager.createAndroidTasks();
+ ApiObjectFactory apiObjectFactory = new ApiObjectFactory(
+ androidBuilder, extension, variantFactory, instantiator);
+ for (BaseVariantData variantData : variantManager.getVariantDataList()) {
+ apiObjectFactory.create(variantData);
+ }
+ return null;
+ }
+ }, new Recorder.Property("project", project.getName()));
+ }
+
+ private boolean isVerbose() {
+ return project.getLogger().isEnabled(LogLevel.INFO);
+ }
+
+ private void ensureTargetSetup() {
+ // check if the target has been set.
+ TargetInfo targetInfo = androidBuilder.getTargetInfo();
+ if (targetInfo == null) {
+ if (extension.getCompileOptions() == null) {
+ throw new GradleException("Calling getBootClasspath before compileSdkVersion");
+ }
+
+ sdkHandler.initTarget(
+ extension.getCompileSdkVersion(),
+ extension.getBuildToolsRevision(),
+ extension.getLibraryRequests(),
+ androidBuilder,
+ SdkHandler.useCachedSdk(project));
+ }
+ }
+
+ /**
+ * Check the sub-projects structure :
+ * So far, checks that 2 modules do not have the same identification (group+name).
+ */
+ private void checkModulesForErrors() {
+ Project rootProject = project.getRootProject();
+ Map<String, Project> subProjectsById = new HashMap<String, Project>();
+ for (Project subProject : rootProject.getAllprojects()) {
+ String id = subProject.getGroup().toString() + ":" + subProject.getName();
+ if (subProjectsById.containsKey(id)) {
+ String message = String.format(
+ "Your project contains 2 or more modules with the same " +
+ "identification %1$s\n" +
+ "at \"%2$s\" and \"%3$s\".\n" +
+ "You must use different identification (either name or group) for " +
+ "each modules.",
+ id,
+ subProjectsById.get(id).getPath(),
+ subProject.getPath() );
+ throw new StopExecutionException(message);
+ } else {
+ subProjectsById.put(id, subProject);
+ }
+ }
+ }
+
+ private void checkPathForErrors() {
+ // See if the user disabled the check:
+ if (Boolean.getBoolean(SKIP_PATH_CHECK_PROPERTY)) {
+ return;
+ }
+
+ if (project.hasProperty(SKIP_PATH_CHECK_PROPERTY)
+ && project.property(SKIP_PATH_CHECK_PROPERTY) instanceof String
+ && Boolean.valueOf((String) project.property(SKIP_PATH_CHECK_PROPERTY))) {
+ return;
+ }
+
+ // See if we're on Windows:
+ if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
+ return;
+ }
+
+ // See if the path contains non-ASCII characters.
+ if (CharMatcher.ASCII.matchesAllOf(project.getRootDir().getAbsolutePath())) {
+ return;
+ }
+
+ String message = "Your project path contains non-ASCII characters. This will most likely " +
+ "cause the build to fail on Windows. Please move your project to a different " +
+ "directory. See http://b.android.com/95744 for details. " +
+ "This warning can be disabled by using the command line flag -D" +
+ SKIP_PATH_CHECK_PROPERTY + "=true, or adding the line " +
+ SKIP_PATH_CHECK_PROPERTY + "=true' to gradle.properties file " +
+ "in the project directory.";
+
+ throw new StopExecutionException(message);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.java
new file mode 100644
index 0000000..5ab24d1
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.java
@@ -0,0 +1,94 @@
+package com.android.build.gradle;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.api.LibraryVariant;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.LoggingUtil;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.builder.core.AndroidBuilder;
+import com.google.common.collect.Lists;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * {@code android} extension for {@code com.android.library} projects.
+ */
+public class LibraryExtension extends TestedExtension {
+
+ private final DefaultDomainObjectSet<LibraryVariant> libraryVariantList
+ = new DefaultDomainObjectSet<LibraryVariant>(LibraryVariant.class);
+
+ private boolean packageBuildConfig = true;
+
+ private Collection<String> aidlPackageWhiteList = null;
+
+ public LibraryExtension(@NonNull ProjectInternal project, @NonNull Instantiator instantiator,
+ @NonNull AndroidBuilder androidBuilder, @NonNull SdkHandler sdkHandler,
+ @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
+ @NonNull ExtraModelInfo extraModelInfo, boolean isLibrary) {
+ super(project, instantiator, androidBuilder, sdkHandler, buildTypes, productFlavors,
+ signingConfigs, extraModelInfo, isLibrary);
+ }
+
+ /**
+ * Returns the list of library variants. Since the collections is built after evaluation, it
+ * should be used with Gradle's <code>all</code> iterator to process future items.
+ */
+ public DefaultDomainObjectSet<LibraryVariant> getLibraryVariants() {
+ return libraryVariantList;
+ }
+
+ @Override
+ public void addVariant(BaseVariant variant) {
+ libraryVariantList.add((LibraryVariant) variant);
+ }
+
+ public void packageBuildConfig(boolean value) {
+ if (!value) {
+ LoggingUtil.displayDeprecationWarning(logger, project,
+ "Support for not packaging BuildConfig is deprecated.");
+ }
+
+ packageBuildConfig = value;
+ }
+
+ @Deprecated
+ public void setPackageBuildConfig(boolean value) {
+ // Remove when users stop requiring this setting.
+ packageBuildConfig(value);
+ }
+
+ @Override
+ public Boolean getPackageBuildConfig() {
+ return packageBuildConfig;
+ }
+
+ public void aidlPackageWhiteList(String ... aidlFqcns) {
+ if (aidlPackageWhiteList == null) {
+ aidlPackageWhiteList = Lists.newArrayList();
+ }
+ Collections.addAll(aidlPackageWhiteList, aidlFqcns);
+ }
+
+ public void setAidlPackageWhiteList(Collection<String> aidlPackageWhiteList) {
+ this.aidlPackageWhiteList = Lists.newArrayList(aidlPackageWhiteList);
+ }
+
+ @Override
+ public Collection<String> getAidlPackageWhiteList() {
+ return aidlPackageWhiteList;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
new file mode 100644
index 0000000..af5c703
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle
+
+import android.databinding.tool.DataBindingBuilder
+import com.android.build.gradle.internal.DependencyManager
+import com.android.build.gradle.internal.LibraryTaskManager
+import com.android.build.gradle.internal.SdkHandler
+import com.android.build.gradle.internal.TaskManager
+import com.android.build.gradle.internal.variant.LibraryVariantFactory
+import com.android.build.gradle.internal.variant.VariantFactory
+import com.android.builder.core.AndroidBuilder
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+
+import javax.inject.Inject
+
+/**
+ * Gradle plugin class for 'library' projects.
+ */
+public class LibraryPlugin extends BasePlugin implements Plugin<Project> {
+
+ /**
+ * Default assemble task for the default-published artifact. this is needed for
+ * the prepare task on the consuming project.
+ */
+ Task assembleDefault
+
+ @Inject
+ public LibraryPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
+ super(instantiator, registry)
+ }
+
+ @Override
+ public Class<? extends BaseExtension> getExtensionClass() {
+ return LibraryExtension.class
+ }
+
+ @Override
+ protected VariantFactory createVariantFactory() {
+ return new LibraryVariantFactory(
+ instantiator,
+ androidBuilder,
+ (LibraryExtension) extension);
+ }
+
+ @Override
+ protected boolean isLibrary() {
+ return true;
+ }
+
+ @Override
+ protected TaskManager createTaskManager(
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ return new LibraryTaskManager(
+ project,
+ androidBuilder,
+ dataBindingBuilder,
+ extension,
+ sdkHandler,
+ dependencyManager,
+ toolingRegistry)
+ }
+
+ @Override
+ void apply(Project project) {
+ super.apply(project)
+
+ assembleDefault = project.tasks.create("assembleDefault")
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/TestExtension.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/TestExtension.java
new file mode 100644
index 0000000..ca06bc6
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/TestExtension.java
@@ -0,0 +1,88 @@
+package com.android.build.gradle;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.ApplicationVariant;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.internal.ExtraModelInfo;
+import com.android.build.gradle.internal.SdkHandler;
+import com.android.build.gradle.internal.dsl.BuildType;
+import com.android.build.gradle.internal.dsl.ProductFlavor;
+import com.android.build.gradle.internal.dsl.SigningConfig;
+import com.android.builder.core.AndroidBuilder;
+
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * {@code android} extension for {@code com.android.test} projects.
+ */
+public class TestExtension extends BaseExtension implements TestAndroidConfig {
+
+ private final DefaultDomainObjectSet<ApplicationVariant> applicationVariantList
+ = new DefaultDomainObjectSet<ApplicationVariant>(ApplicationVariant.class);
+
+ private String targetProjectPath = null;
+
+ private String targetVariant = "debug";
+
+ public TestExtension(@NonNull ProjectInternal project, @NonNull Instantiator instantiator,
+ @NonNull AndroidBuilder androidBuilder, @NonNull SdkHandler sdkHandler,
+ @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
+ @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
+ @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
+ @NonNull ExtraModelInfo extraModelInfo, boolean isLibrary) {
+ super(project, instantiator, androidBuilder, sdkHandler, buildTypes, productFlavors,
+ signingConfigs, extraModelInfo, isLibrary);
+ }
+
+ /**
+ * Returns the list of Application variants. Since the collections is built after evaluation, it
+ * should be used with Gradle's <code>all</code> iterator to process future items.
+ */
+ public DefaultDomainObjectSet<ApplicationVariant> getApplicationVariants() {
+ return applicationVariantList;
+ }
+
+ @Override
+ public void addVariant(BaseVariant variant) {
+ applicationVariantList.add((ApplicationVariant) variant);
+ }
+
+ /**
+ * Returns the Gradle path of the project that this test project tests.
+ */
+ @Override
+ public String getTargetProjectPath() {
+ return targetProjectPath;
+ }
+
+ public void setTargetProjectPath(String targetProjectPath) {
+ checkWritability();
+ this.targetProjectPath = targetProjectPath;
+ }
+
+ public void targetProjectPath(String targetProjectPath) {
+ setTargetProjectPath(targetProjectPath);
+ }
+
+ /**
+ * Returns the variant of the tested project.
+ *
+ * Default is 'debug'
+ */
+ @Override
+ public String getTargetVariant() {
+ return targetVariant;
+ }
+
+ public void setTargetVariant(String targetVariant) {
+ checkWritability();
+ this.targetVariant = targetVariant;
+ }
+
+ public void targetVariant(String targetVariant) {
+ setTargetVariant(targetVariant);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/TestPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/TestPlugin.groovy
new file mode 100644
index 0000000..6db7c1f
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/TestPlugin.groovy
@@ -0,0 +1,76 @@
+/*
+ * 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.build.gradle
+
+import android.databinding.tool.DataBindingBuilder
+import com.android.build.gradle.internal.DependencyManager
+import com.android.build.gradle.internal.SdkHandler
+import com.android.build.gradle.internal.TaskManager
+import com.android.build.gradle.internal.TestApplicationTaskManager
+import com.android.build.gradle.internal.variant.TestVariantFactory
+import com.android.build.gradle.internal.variant.VariantFactory
+import com.android.builder.core.AndroidBuilder
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+
+import javax.inject.Inject
+
+/**
+ * Gradle plugin class for 'application' projects.
+ */
+class TestPlugin extends BasePlugin implements Plugin<Project> {
+ @Inject
+ public TestPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
+ super(instantiator, registry)
+ }
+
+ @Override
+ protected Class<? extends BaseExtension> getExtensionClass() {
+ return TestExtension.class
+ }
+
+ @Override
+ protected TaskManager createTaskManager(
+ Project project,
+ AndroidBuilder androidBuilder,
+ DataBindingBuilder dataBindingBuilder,
+ AndroidConfig extension,
+ SdkHandler sdkHandler,
+ DependencyManager dependencyManager,
+ ToolingModelBuilderRegistry toolingRegistry) {
+ return new TestApplicationTaskManager (
+ project,
+ androidBuilder,
+ dataBindingBuilder,
+ extension,
+ sdkHandler,
+ dependencyManager,
+ toolingRegistry)
+ }
+
+ @Override
+ void apply(Project project) {
+ super.apply(project)
+ }
+
+ @Override
+ protected VariantFactory createVariantFactory() {
+ return new TestVariantFactory(instantiator, androidBuilder, extension)
+ }
+}
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestedExtension.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/TestedExtension.java
similarity index 100%
rename from base/build-system/gradle/src/main/groovy/com/android/build/gradle/TestedExtension.java
rename to build-system/gradle/src/main/groovy/com/android/build/gradle/TestedExtension.java
diff --git a/base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ApiObjectFactory.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ApiObjectFactory.java
similarity index 100%
rename from base/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ApiObjectFactory.java
rename to build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ApiObjectFactory.java
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/NativeLibraryFactoryImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/NativeLibraryFactoryImpl.java
new file mode 100644
index 0000000..d12229c
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/NativeLibraryFactoryImpl.java
@@ -0,0 +1,82 @@
+/*
+ * 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.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.AndroidGradleOptions;
+import com.android.build.gradle.internal.core.Abi;
+import com.android.build.gradle.internal.dsl.CoreNdkOptions;
+import com.android.build.gradle.internal.model.NativeLibraryFactory;
+import com.android.build.gradle.internal.model.NativeLibraryImpl;
+import com.android.build.gradle.internal.scope.VariantScope;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantOutputData;
+import com.android.builder.model.NativeLibrary;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of NativeLibraryFactory for gradle plugin.
+ */
+public class NativeLibraryFactoryImpl implements NativeLibraryFactory {
+
+ @NonNull
+ final NdkHandler ndkHandler;
+
+ public NativeLibraryFactoryImpl(
+ @NonNull NdkHandler ndkHandler) {
+ this.ndkHandler = ndkHandler;
+ }
+
+ @NonNull
+ @Override
+ public Optional<NativeLibrary> create(
+ @NonNull VariantScope scope,
+ @NonNull String toolchainName, @NonNull Abi abi) {
+ BaseVariantData<? extends BaseVariantOutputData> variantData = scope.getVariantData();
+ if (!AndroidGradleOptions.useDeprecatedNdk(scope.getGlobalScope().getProject())) {
+ return Optional.absent();
+ }
+
+ CoreNdkOptions ndkConfig = variantData.getVariantConfiguration().getNdkConfig();
+
+ String sysrootFlag = "--sysroot=" + ndkHandler.getSysroot(abi);
+ List<String> cFlags = ndkConfig.getcFlags() == null
+ ? ImmutableList.of(sysrootFlag)
+ : ImmutableList.of(sysrootFlag, ndkConfig.getcFlags());
+
+ // The DSL currently do not support all options available in the model such as the
+ // include dirs and the defines. Therefore, just pass an empty collection for now.
+ return Optional.<NativeLibrary>of(new NativeLibraryImpl(
+ ndkConfig.getModuleName(),
+ toolchainName,
+ abi.getName(),
+ Collections.<File>emptyList(), /*cIncludeDirs*/
+ Collections.<File>emptyList(), /*cppIncludeDirs*/
+ Collections.<File>emptyList(), /*cSystemIncludeDirs*/
+ ndkHandler.getStlIncludes(ndkConfig.getStl(), null /*stlVersion*/, abi),
+ Collections.<String>emptyList(), /*cDefines*/
+ Collections.<String>emptyList(), /*cppDefines*/
+ cFlags,
+ cFlags, // TODO: NdkConfig should allow cppFlags to be set separately.
+ ImmutableList.of(scope.getNdkDebuggableLibraryFolders(abi))));
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorFactory.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorFactory.java
new file mode 100644
index 0000000..712a771
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.builder.core.ErrorReporter;
+
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * Factory to create ProductFlavor object using an {@link Instantiator} to add
+ * the DSL methods.
+ */
+public class ProductFlavorFactory implements NamedDomainObjectFactory<ProductFlavor> {
+
+ @NonNull
+ private final Instantiator instantiator;
+ @NonNull
+ private final Project project;
+ @NonNull
+ private final Logger logger;
+ @NonNull
+ private final ErrorReporter errorReporter;
+
+ public ProductFlavorFactory(
+ @NonNull Instantiator instantiator,
+ @NonNull Project project,
+ @NonNull Logger logger,
+ @NonNull ErrorReporter errorReporter) {
+ this.instantiator = instantiator;
+ this.project = project;
+ this.logger = logger;
+ this.errorReporter = errorReporter;
+ }
+
+ @Override
+ public ProductFlavor create(String name) {
+ return instantiator.newInstance(ProductFlavor.class,
+ name, project, instantiator, logger, errorReporter);
+ }
+}
diff --git a/base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-library.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-library.properties
similarity index 100%
rename from base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-library.properties
rename to build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-library.properties
diff --git a/base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-reporting.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-reporting.properties
similarity index 100%
rename from base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-reporting.properties
rename to build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-reporting.properties
diff --git a/base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android.properties
similarity index 100%
rename from base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android.properties
rename to build-system/gradle/src/main/resources/META-INF/gradle-plugins/android.properties
diff --git a/base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.application.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.application.properties
similarity index 100%
rename from base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.application.properties
rename to build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.application.properties
diff --git a/base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.library.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.library.properties
similarity index 100%
rename from base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.library.properties
rename to build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.library.properties
diff --git a/base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.test.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.test.properties
similarity index 100%
rename from base/build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.test.properties
rename to build-system/gradle/src/main/resources/META-INF/gradle-plugins/com.android.test.properties
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy
new file mode 100644
index 0000000..739ecf3
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle
+
+import com.android.SdkConstants
+import com.android.annotations.NonNull
+import com.android.build.gradle.api.ApkVariant
+import com.android.build.gradle.api.ApkVariantOutput
+import com.android.build.gradle.api.ApplicationVariant
+import com.android.build.gradle.api.BaseVariantOutput
+import com.android.build.gradle.api.TestVariant
+import com.android.build.gradle.internal.SdkHandler
+import com.android.build.gradle.internal.core.GradleVariantConfiguration
+import com.android.build.gradle.internal.tasks.MockableAndroidJarTask
+import com.android.build.gradle.internal.test.BaseTest
+import org.gradle.api.JavaVersion
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Ignore
+
+import static com.android.build.gradle.DslTestUtil.DEFAULT_VARIANTS
+import static com.android.build.gradle.DslTestUtil.countVariants
+/**
+ * Tests for the public DSL of the App plugin ("android")
+ */
+public class AppPluginDslTest extends BaseTest {
+
+ @Override
+ protected void setUp() throws Exception {
+ SdkHandler.testSdkFolder = new File(System.getenv("ANDROID_HOME"))
+ }
+
+ public void testBasic() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ plugin.createAndroidTasks(false)
+ assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
+
+ // we can now call this since the variants/tasks have been created
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(2, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("debug", "debugAndroidTest", variants, testVariants)
+ checkNonTestedVariant("release", variants)
+ }
+
+ /**
+ * Same as Basic but with a slightly different DSL.
+ */
+ public void testBasic2() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ plugin.createAndroidTasks(false)
+ assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
+
+ // we can now call this since the variants/tasks have been created
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(2, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("debug", "debugAndroidTest", variants, testVariants)
+ checkNonTestedVariant("release", variants)
+ }
+
+ public void testBasicWithStringTarget() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion "android-" + COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ plugin.createAndroidTasks(false)
+ assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
+
+ // we can now call this since the variants/tasks have been created
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(2, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("debug", "debugAndroidTest", variants, testVariants)
+ checkNonTestedVariant("release", variants)
+ }
+
+ public void testMultiRes() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/multires")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ sourceSets {
+ main {
+ res {
+ srcDirs 'src/main/res1', 'src/main/res2'
+ }
+ }
+ }
+ }
+
+ // nothing to be done here. If the DSL fails, it'll throw an exception
+ }
+
+ public void testBuildTypes() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+ testBuildType "staging"
+
+ buildTypes {
+ staging {
+ signingConfig signingConfigs.debug
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ plugin.createAndroidTasks(false)
+ assertEquals(
+ countVariants(appVariants: 3, unitTest: 3, androidTests: 1),
+ plugin.variantManager.variantDataList.size())
+
+ // we can now call this since the variants/tasks have been created
+
+ // does not include tests
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(3, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("staging", "stagingAndroidTest", variants, testVariants)
+
+ checkNonTestedVariant("debug", variants)
+ checkNonTestedVariant("release", variants)
+ }
+
+ public void testFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ productFlavors {
+ flavor1 {
+
+ }
+ flavor2 {
+
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ plugin.createAndroidTasks(false)
+ assertEquals(
+ countVariants(appVariants: 4, unitTest: 4, androidTests: 2),
+ plugin.variantManager.variantDataList.size())
+
+ // we can now call this since the variants/tasks have been created
+
+ // does not include tests
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(4, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(2, testVariants.size())
+
+ checkTestedVariant("flavor1Debug", "flavor1DebugAndroidTest", variants, testVariants)
+ checkTestedVariant("flavor2Debug", "flavor2DebugAndroidTest", variants, testVariants)
+
+ checkNonTestedVariant("flavor1Release", variants)
+ checkNonTestedVariant("flavor2Release", variants)
+ }
+
+ public void testMultiFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ flavorDimensions "dimension1", "dimension2"
+
+ productFlavors {
+ f1 {
+ dimension "dimension1"
+ }
+ f2 {
+ dimension "dimension1"
+ }
+
+ fa {
+ dimension "dimension2"
+ }
+ fb {
+ dimension "dimension2"
+ }
+ fc {
+ dimension "dimension2"
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ plugin.createAndroidTasks(false)
+ assertEquals(
+ countVariants(appVariants: 12, unitTest: 12, androidTests: 6),
+ plugin.variantManager.variantDataList.size())
+
+ // we can now call this since the variants/tasks have been created
+
+ // does not include tests
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(12, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(6, testVariants.size())
+
+ checkTestedVariant("f1FaDebug", "f1FaDebugAndroidTest", variants, testVariants)
+ checkTestedVariant("f1FbDebug", "f1FbDebugAndroidTest", variants, testVariants)
+ checkTestedVariant("f1FcDebug", "f1FcDebugAndroidTest", variants, testVariants)
+ checkTestedVariant("f2FaDebug", "f2FaDebugAndroidTest", variants, testVariants)
+ checkTestedVariant("f2FbDebug", "f2FbDebugAndroidTest", variants, testVariants)
+ checkTestedVariant("f2FcDebug", "f2FcDebugAndroidTest", variants, testVariants)
+
+ checkNonTestedVariant("f1FaRelease", variants)
+ checkNonTestedVariant("f1FbRelease", variants)
+ checkNonTestedVariant("f1FcRelease", variants)
+ checkNonTestedVariant("f2FaRelease", variants)
+ checkNonTestedVariant("f2FbRelease", variants)
+ checkNonTestedVariant("f2FcRelease", variants)
+ }
+
+ public void testSourceSetsApi() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+ }
+
+ // query the sourceSets, will throw if missing
+ println project.android.sourceSets.main.java.srcDirs
+ println project.android.sourceSets.main.resources.srcDirs
+ println project.android.sourceSets.main.manifest.srcFile
+ println project.android.sourceSets.main.res.srcDirs
+ println project.android.sourceSets.main.assets.srcDirs
+ }
+
+ public void testObfuscationMappingFile() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ plugin.createAndroidTasks(false)
+ assertEquals(DEFAULT_VARIANTS.size(), plugin.variantManager.variantDataList.size())
+
+ // we can now call this since the variants/tasks have been created
+
+ // does not include tests
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(2, variants.size())
+
+ for (ApplicationVariant variant : variants) {
+ if ("release".equals(variant.getBuildType().getName())) {
+ assertNotNull(variant.getMappingFile())
+ } else {
+ assertNull(variant.getMappingFile())
+ }
+ }
+ }
+
+ public void testProguardDsl() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ buildTypes {
+ release {
+ proguardFile 'file1.1'
+ proguardFiles 'file1.2', 'file1.3'
+ }
+
+ custom {
+ proguardFile 'file3.1'
+ proguardFiles 'file3.2', 'file3.3'
+ proguardFiles = ['file3.1']
+ }
+ }
+
+ productFlavors {
+ f1 {
+ proguardFile 'file2.1'
+ proguardFiles 'file2.2', 'file2.3'
+ }
+
+ f2 {
+
+ }
+
+ f3 {
+ proguardFile 'file4.1'
+ proguardFiles 'file4.2', 'file4.3'
+ proguardFiles = ['file4.1']
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ def variantsData = plugin.variantManager.variantDataList
+ Map<String, GradleVariantConfiguration> variantMap =
+ variantsData.collectEntries {[it.name, it.variantConfiguration]}
+
+ def expectedFiles = [
+ f1Release: ["file1.1", "file1.2", "file1.3", "file2.1", "file2.2", "file2.3"],
+ f1Debug: ["file2.1", "file2.2", "file2.3"],
+ f2Release: ["file1.1", "file1.2", "file1.3"],
+ f2Debug: [],
+ f2Custom: ["file3.1"],
+ f3Custom: ["file3.1", "file4.1"],
+ ]
+
+ expectedFiles.each { name, expected ->
+ def actual = variantMap[name].getProguardFiles(false, [])
+ assert (actual*.name as Set) == (expected as Set), name
+ }
+ }
+
+ public void testProguardDsl_initWith() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ buildTypes {
+ common {
+ testProguardFile 'file1.1'
+ }
+
+ custom1.initWith(buildTypes.common)
+ custom2.initWith(buildTypes.common)
+
+ custom1 {
+ testProguardFile 'file2.1'
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ def variantsData = plugin.variantManager.variantDataList
+ Map<String, GradleVariantConfiguration> variantMap =
+ variantsData.collectEntries {[it.name, it.variantConfiguration]}
+
+ def expectedFiles = [
+ common: ["file1.1"],
+ custom1: ["file1.1", "file2.1"],
+ custom2: ["file1.1"],
+ ]
+
+ expectedFiles.each { name, expected ->
+ Set<File> actual = variantMap[name].testProguardFiles
+ assert (actual*.name as Set) == (expected as Set), name
+ }
+ }
+
+ public void testSettingLanguageLevelFromCompileSdk() {
+ def testLanguageLevel = { version, expectedLanguageLevel, useJack ->
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+ project.android {
+ compileSdkVersion version
+ buildToolsVersion '20.0.0'
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ assertEquals(
+ "target compatibility for ${version}",
+ expectedLanguageLevel.toString(),
+ project.compileReleaseJavaWithJavac.targetCompatibility)
+ assertEquals(
+ "source compatibility for ${version}",
+ expectedLanguageLevel.toString(),
+ project.compileReleaseJavaWithJavac.sourceCompatibility)
+ }
+
+ for (useJack in [true, false]) {
+ def propName = 'java.specification.version'
+ String originalVersion = System.getProperty(propName)
+ try{
+ System.setProperty(propName, '1.7')
+ testLanguageLevel('android-15', JavaVersion.VERSION_1_6, useJack)
+ testLanguageLevel('android-21', JavaVersion.VERSION_1_7, useJack)
+ testLanguageLevel('android-21', JavaVersion.VERSION_1_7, useJack)
+ testLanguageLevel('Google Inc.:Google APIs:22', JavaVersion.VERSION_1_7, useJack)
+
+ System.setProperty(propName, '1.6')
+ testLanguageLevel(15, JavaVersion.VERSION_1_6, useJack)
+ testLanguageLevel(21, JavaVersion.VERSION_1_6, useJack)
+ testLanguageLevel('android-21', JavaVersion.VERSION_1_6, useJack)
+ testLanguageLevel('Google Inc.:Google APIs:22', JavaVersion.VERSION_1_6, useJack)
+ } finally {
+ System.setProperty(propName, originalVersion)
+ }
+ }
+ }
+
+ public void testSettingLanguageLevelFromCompileSdk_dontOverride() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_6
+ targetCompatibility JavaVersion.VERSION_1_6
+ }
+ }
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ assertEquals(
+ JavaVersion.VERSION_1_6.toString(),
+ project.compileReleaseJavaWithJavac.targetCompatibility)
+ assertEquals(
+ JavaVersion.VERSION_1_6.toString(),
+ project.compileReleaseJavaWithJavac.sourceCompatibility)
+ }
+
+ public void testMockableJarName() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion "Google Inc.:Google APIs:" + COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ def mockableJarFile = ((MockableAndroidJarTask) project.tasks.mockableAndroidJar).outputFile
+ if (SdkConstants.CURRENT_PLATFORM != SdkConstants.PLATFORM_WINDOWS) {
+ // windows path contain : to identify drives.
+ assertFalse(mockableJarFile.absolutePath.contains(":"))
+ }
+ assertEquals("mockable-Google-Inc.-Google-APIs-21.jar", mockableJarFile.name)
+ }
+
+ public void testEncoding() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ compileOptions {
+ encoding "foo"
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ assertEquals(
+ "foo",
+ project.compileReleaseJavaWithJavac.options.encoding)
+ }
+
+ public void testInstrumentationRunnerArguments_merging() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ defaultConfig {
+ testInstrumentationRunnerArguments(value: "default", size: "small")
+ }
+
+ productFlavors {
+ f1 {
+ }
+
+ f2 {
+ testInstrumentationRunnerArgument "value", "f2"
+ }
+
+ f3 {
+ testInstrumentationRunnerArguments["otherValue"] = "f3"
+ }
+
+ f4 {
+ testInstrumentationRunnerArguments(otherValue: "f4.1")
+ testInstrumentationRunnerArguments = [otherValue: "f4.2"]
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ def variantsData = plugin.variantManager.variantDataList
+ Map<String, GradleVariantConfiguration> variantMap =
+ variantsData.collectEntries {[it.name, it.variantConfiguration]}
+
+ def expectedArgs = [
+ f1Debug: [value: "default", size: "small"],
+ f2Debug: [value: "f2", size: "small"],
+ f3Debug: [value: "default", size: "small", otherValue: "f3"],
+ f4Debug: [value: "default", size: "small", otherValue: "f4.2"],
+ ]
+
+ expectedArgs.each { name, expected ->
+ assert expected == variantMap[name].instrumentationRunnerArguments
+ }
+ }
+
+ public void testGeneratedDensities() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '23.0.2'
+
+ productFlavors {
+ f1 {
+ }
+
+ f2 {
+ vectorDrawables {
+ generatedDensities = ["ldpi"]
+ generatedDensities += ["mdpi"]
+ }
+ }
+
+ f3 {
+ vectorDrawables {
+ generatedDensities = defaultConfig.generatedDensities - ["ldpi", "mdpi"]
+ }
+ }
+
+ f4.vectorDrawables.generatedDensities = []
+
+ oldSyntax {
+ generatedDensities = ["ldpi"]
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ assert project.mergeF1DebugResources.generatedDensities ==
+ ["ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"] as Set
+
+ assert project.mergeF2DebugResources.generatedDensities ==
+ ["ldpi", "mdpi"] as Set
+
+ assert project.mergeF3DebugResources.generatedDensities ==
+ ["hdpi", "xhdpi", "xxhdpi", "xxxhdpi"] as Set
+
+ assert project.mergeF4DebugResources.generatedDensities == [] as Set
+
+ assert project.mergeOldSyntaxDebugResources.generatedDensities == ["ldpi"] as Set
+ }
+
+ public void testUseSupportLibrary_default() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ assert project.mergeDebugResources.disableVectorDrawables == false
+ }
+
+ public void testUseSupportLibrary_flavors() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion '20.0.0'
+
+ productFlavors {
+ f1 {
+ }
+
+ f2 {
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ }
+
+ f3 {
+ vectorDrawables {
+ useSupportLibrary = false
+ }
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(false)
+
+ assert project.mergeF1DebugResources.disableVectorDrawables == false
+ assert project.mergeF2DebugResources.disableVectorDrawables == true
+ assert project.mergeF3DebugResources.disableVectorDrawables == false
+ }
+
+ private static void checkTestedVariant(@NonNull String variantName,
+ @NonNull String testedVariantName,
+ @NonNull Collection<ApplicationVariant> variants,
+ @NonNull Set<TestVariant> testVariants) {
+ ApplicationVariant variant = findNamedItem(variants, variantName, "variantData")
+ assertNotNull(variant.testVariant)
+ assertEquals(testedVariantName, variant.testVariant.name)
+ assertEquals(variant.testVariant, findNamedItemMaybe(testVariants, testedVariantName))
+ checkTasks(variant)
+ checkTasks(variant.testVariant)
+ }
+
+ private static void checkNonTestedVariant(@NonNull String variantName,
+ @NonNull Set<ApplicationVariant> variants) {
+ ApplicationVariant variant = findNamedItem(variants, variantName, "variantData")
+ assertNull(variant.testVariant)
+ checkTasks(variant)
+ }
+
+ private static void checkTasks(@NonNull ApkVariant variant) {
+ boolean isTestVariant = variant instanceof TestVariant;
+
+ assertNotNull(variant.aidlCompile)
+ assertNotNull(variant.mergeResources)
+ assertNotNull(variant.mergeAssets)
+ assertNotNull(variant.generateBuildConfig)
+ assertNotNull(variant.javaCompiler)
+ assertNotNull(variant.processJavaResources)
+ assertNotNull(variant.assemble)
+ assertNotNull(variant.uninstall)
+
+ assertFalse(variant.outputs.isEmpty())
+
+ for (BaseVariantOutput baseVariantOutput : variant.outputs) {
+ assertTrue(baseVariantOutput instanceof ApkVariantOutput)
+ ApkVariantOutput apkVariantOutput = (ApkVariantOutput) baseVariantOutput
+
+ assertNotNull(apkVariantOutput.processManifest)
+ assertNotNull(apkVariantOutput.processResources)
+ assertNotNull(apkVariantOutput.packageApplication)
+ }
+
+ if (variant.isSigningReady()) {
+ assertNotNull(variant.install)
+
+ for (BaseVariantOutput baseVariantOutput : variant.outputs) {
+ ApkVariantOutput apkVariantOutput = (ApkVariantOutput) baseVariantOutput
+
+ // tested variant are never zipAligned.
+ if (!isTestVariant && variant.buildType.zipAlignEnabled) {
+ assertNotNull(apkVariantOutput.zipAlign)
+ } else {
+ assertNull(apkVariantOutput.zipAlign)
+ }
+ }
+ } else {
+ assertNull(variant.install)
+ }
+
+ if (isTestVariant) {
+ TestVariant testVariant = variant as TestVariant
+ assertNotNull(testVariant.connectedInstrumentTest)
+ assertNotNull(testVariant.testedVariant)
+ }
+ }
+}
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy
new file mode 100644
index 0000000..869ea30
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle
+import com.android.build.gradle.internal.BadPluginException
+import com.android.build.gradle.internal.SdkHandler
+import com.android.build.gradle.internal.test.BaseTest
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.builder.core.BuilderConstants
+import com.android.builder.core.DefaultBuildType
+import com.android.builder.model.SigningConfig
+import com.android.ide.common.signing.KeystoreHelper
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+
+import static com.android.build.gradle.DslTestUtil.DEFAULT_VARIANTS
+import static com.android.build.gradle.DslTestUtil.countVariants
+/**
+ * Tests for the internal workings of the app plugin ("android")
+ */
+public class AppPluginInternalTest extends BaseTest {
+
+ @Override
+ protected void setUp() throws Exception {
+ SdkHandler.testSdkFolder = new File(System.getenv("ANDROID_HOME"))
+ }
+
+ public void testBasic() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+1
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(2, plugin.variantManager.buildTypes.size())
+ assertNotNull(plugin.variantManager.buildTypes.get(BuilderConstants.DEBUG))
+ assertNotNull(plugin.variantManager.buildTypes.get(BuilderConstants.RELEASE))
+ assertEquals(0, plugin.variantManager.productFlavors.size())
+
+
+ List<BaseVariantData> variants = plugin.variantManager.variantDataList
+ assertEquals(DEFAULT_VARIANTS.size(), variants.size()) // includes the test variant(s)
+
+ findNamedItem(variants, "debug", "variantData")
+ findNamedItem(variants, "release", "variantData")
+ findNamedItem(variants, "debugAndroidTest", "variantData")
+ }
+
+ public void testDefaultConfig() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+
+ signingConfigs {
+ fakeConfig {
+ storeFile project.file("aa")
+ storePassword "bb"
+ keyAlias "cc"
+ keyPassword "dd"
+ }
+ }
+
+ defaultConfig {
+ versionCode 1
+ versionName "2.0"
+ minSdkVersion 2
+ targetSdkVersion 3
+
+ signingConfig signingConfigs.fakeConfig
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(1, plugin.extension.defaultConfig.versionCode)
+ assertNotNull(plugin.extension.defaultConfig.minSdkVersion)
+ assertEquals(2, plugin.extension.defaultConfig.minSdkVersion.apiLevel)
+ assertNotNull(plugin.extension.defaultConfig.targetSdkVersion)
+ assertEquals(3, plugin.extension.defaultConfig.targetSdkVersion.apiLevel)
+ assertEquals("2.0", plugin.extension.defaultConfig.versionName)
+
+ assertEquals(new File(project.projectDir, "aa"),
+ plugin.extension.defaultConfig.signingConfig.storeFile)
+ assertEquals("bb", plugin.extension.defaultConfig.signingConfig.storePassword)
+ assertEquals("cc", plugin.extension.defaultConfig.signingConfig.keyAlias)
+ assertEquals("dd", plugin.extension.defaultConfig.signingConfig.keyPassword)
+ }
+
+ public void testBuildTypes() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+ testBuildType "staging"
+
+ buildTypes {
+ staging {
+ signingConfig signingConfigs.debug
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(3, plugin.variantManager.buildTypes.size())
+
+ List<BaseVariantData> variants = plugin.variantManager.variantDataList
+ assertEquals(countVariants(appVariants: 3, unitTests: 3, androidTests: 1), variants.size())
+
+ String[] variantNames = [
+ "debug", "release", "staging"]
+
+ for (String variantName : variantNames) {
+ findNamedItem(variants, variantName, "variantData")
+ }
+
+ BaseVariantData testVariant = findNamedItem(variants, "stagingAndroidTest", "variantData")
+ assertEquals("staging", testVariant.variantConfiguration.buildType.name)
+ }
+
+ public void testFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+
+ productFlavors {
+ flavor1 {
+
+ }
+ flavor2 {
+
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(2, plugin.variantManager.productFlavors.size())
+
+ List<BaseVariantData> variants = plugin.variantManager.variantDataList
+ assertEquals(countVariants(appVariants: 4, unitTests: 4, androidTests: 2), variants.size())
+
+ String[] variantNames = [
+ "flavor1Debug", "flavor1Release", "flavor1DebugAndroidTest",
+ "flavor2Debug", "flavor2Release", "flavor2DebugAndroidTest"]
+
+ for (String variantName : variantNames) {
+ findNamedItem(variants, variantName, "variantData")
+ }
+ }
+
+ public void testMultiFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+
+ flavorDimensions "dimension1", "dimension2"
+
+ productFlavors {
+ f1 {
+ dimension "dimension1"
+ }
+ f2 {
+ dimension "dimension1"
+ }
+
+ fa {
+ dimension "dimension2"
+ }
+ fb {
+ dimension "dimension2"
+ }
+ fc {
+ dimension "dimension2"
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(5, plugin.variantManager.productFlavors.size())
+
+ List<BaseVariantData> variants = plugin.variantManager.variantDataList
+ assertEquals(countVariants(appVariants: 12, unitTests: 12, androidTests: 6), variants.size())
+
+ String[] variantNames = [
+ "f1FaDebug",
+ "f1FbDebug",
+ "f1FcDebug",
+ "f2FaDebug",
+ "f2FbDebug",
+ "f2FcDebug",
+ "f1FaRelease",
+ "f1FbRelease",
+ "f1FcRelease",
+ "f2FaRelease",
+ "f2FbRelease",
+ "f2FcRelease",
+ "f1FaDebugAndroidTest",
+ "f1FbDebugAndroidTest",
+ "f1FcDebugAndroidTest",
+ "f2FaDebugAndroidTest",
+ "f2FbDebugAndroidTest",
+ "f2FcDebugAndroidTest"];
+
+ for (String variantName : variantNames) {
+ findNamedItem(variants, variantName, "variantData");
+ }
+ }
+
+ public void testSigningConfigs() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+
+ signingConfigs {
+ one {
+ storeFile project.file("a1")
+ storePassword "b1"
+ keyAlias "c1"
+ keyPassword "d1"
+ }
+ two {
+ storeFile project.file("a2")
+ storePassword "b2"
+ keyAlias "c2"
+ keyPassword "d2"
+ }
+ three {
+ storeFile project.file("a3")
+ storePassword "b3"
+ keyAlias "c3"
+ keyPassword "d3"
+ }
+ }
+
+ defaultConfig {
+ versionCode 1
+ versionName "2.0"
+ minSdkVersion 2
+ targetSdkVersion 3
+ }
+
+ buildTypes {
+ debug {
+ }
+ staging {
+ }
+ release {
+ signingConfig owner.signingConfigs.three
+ }
+ }
+
+ productFlavors {
+ flavor1 {
+ }
+ flavor2 {
+ signingConfig owner.signingConfigs.one
+ }
+ }
+
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ plugin.createAndroidTasks(true /*force*/)
+
+ List<BaseVariantData> variants = plugin.variantManager.variantDataList
+ assertEquals(countVariants(appVariants: 6, unitTests: 6, androidTests: 2), variants.size())
+
+ BaseVariantData variant
+ SigningConfig signingConfig
+
+ variant = findNamedItem(variants, "flavor1Debug", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeFile?.absolutePath)
+
+ variant = findNamedItem(variants, "flavor1Staging", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNull(signingConfig)
+
+ variant = findNamedItem(variants, "flavor1Release", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(new File(project.projectDir, "a3"), signingConfig.storeFile)
+
+ variant = findNamedItem(variants, "flavor2Debug", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeFile?.absolutePath)
+
+ variant = findNamedItem(variants, "flavor2Staging", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(new File(project.projectDir, "a1"), signingConfig.storeFile)
+
+ variant = findNamedItem(variants, "flavor2Release", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(new File(project.projectDir, "a3"), signingConfig.storeFile)
+ }
+
+ /**
+ * test that debug build type maps to the SigningConfig object as the signingConfig container
+ * @throws Exception
+ */
+ public void testDebugSigningConfig() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+
+ signingConfigs {
+ debug {
+ storePassword = "foo"
+ }
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ // check that the debug buildType has the updated debug signing config.
+ DefaultBuildType buildType = plugin.variantManager.buildTypes.get(BuilderConstants.DEBUG).buildType
+ SigningConfig signingConfig = buildType.signingConfig
+ assertEquals(plugin.variantManager.signingConfigs.get(BuilderConstants.DEBUG), signingConfig)
+ assertEquals("foo", signingConfig.storePassword)
+ }
+
+ public void testSigningConfigInitWith() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+
+ signingConfigs {
+ foo.initWith(owner.signingConfigs.debug)
+ }
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+
+ SigningConfig debugSC = plugin.variantManager.signingConfigs.get(BuilderConstants.DEBUG)
+ SigningConfig fooSC = plugin.variantManager.signingConfigs.get("foo")
+
+ assertNotNull(fooSC);
+
+ assertEquals(debugSC.getStoreFile(), fooSC.getStoreFile());
+ assertEquals(debugSC.getStorePassword(), fooSC.getStorePassword());
+ assertEquals(debugSC.getKeyAlias(), fooSC.getKeyAlias());
+ assertEquals(debugSC.getKeyPassword(), fooSC.getKeyPassword());
+ }
+
+ public void testPluginDetection() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "${FOLDER_TEST_PROJECTS}/basic")).build()
+
+ project.apply plugin: 'com.android.application'
+ project.apply plugin: 'java'
+
+ project.android {
+ compileSdkVersion COMPILE_SDK_VERSION
+ buildToolsVersion BUILD_TOOL_VERSION
+ }
+
+ AppPlugin plugin = project.plugins.getPlugin(AppPlugin)
+ Exception recordedException = null;
+ try {
+ plugin.createAndroidTasks(true /*force*/)
+ } catch (Exception e) {
+ recordedException = e;
+ }
+
+ assertNotNull(recordedException)
+ assertEquals(BadPluginException.class, recordedException.getClass())
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/DslTestUtil.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/DslTestUtil.groovy
similarity index 100%
rename from base/build-system/gradle/src/test/groovy/com/android/build/gradle/DslTestUtil.groovy
rename to build-system/gradle/src/test/groovy/com/android/build/gradle/DslTestUtil.groovy
diff --git a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy
similarity index 100%
rename from base/build-system/gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy
rename to build-system/gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy
diff --git a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/BuildTypeTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/BuildTypeTest.groovy
similarity index 100%
rename from base/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/BuildTypeTest.groovy
rename to build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/BuildTypeTest.groovy
diff --git a/base/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/test/BaseTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/test/BaseTest.groovy
similarity index 100%
rename from base/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/test/BaseTest.groovy
rename to build-system/gradle/src/test/groovy/com/android/build/gradle/internal/test/BaseTest.groovy
diff --git a/build-system/integration-test/.gitignore b/build-system/integration-test/.gitignore
new file mode 100644
index 0000000..4021be3
--- /dev/null
+++ b/build-system/integration-test/.gitignore
@@ -0,0 +1 @@
+userHome
diff --git a/build-system/integration-test/build.gradle b/build-system/integration-test/build.gradle
new file mode 100644
index 0000000..0e6c9e7
--- /dev/null
+++ b/build-system/integration-test/build.gradle
@@ -0,0 +1,176 @@
+apply plugin: 'groovy'
+apply plugin: 'jacoco'
+
+repositories {
+ maven { url = uri(rootProject.cloneArtifacts.repository) }
+}
+
+dependencies {
+ compile gradleApi()
+ compile localGroovy()
+ testCompile project(':base:builder-model')
+ testCompile project(':base:builder')
+ testCompile project(':base:sdk-common')
+ testCompile project(':base:lint')
+ testCompile project(':base:instant-run:instant-run-client')
+
+
+ testCompile 'com.google.truth:truth:0.28'
+ testCompile "org.mockito:mockito-core:1.9.5"
+ testCompile 'org.jacoco:org.jacoco.agent:0.7.4.201502262128'
+
+ // Add dependency on plugin code. Exclude transitive dependencies to avoid conflict due to
+ // Groovy versions.
+ testCompile(project(':base:gradle-core')) {
+ transitive = false
+ }
+ testCompile(project(':base:gradle')) {
+ transitive = false
+ }
+ testCompile(project(':base:gradle-experimental')) {
+ transitive = false
+ }
+}
+
+def testEnvironment = [
+ PROJECT_BUILD_DIR: project.buildDir,
+ CUSTOM_REPO: rootProject.file("../out/repo"),
+ CUSTOM_GRADLE: System.env.CUSTOM_GRADLE,
+ CUSTOM_EXPERIMENTAL_GRADLE: System.env.CUSTOM_EXPERIMENTAL_GRADLE,
+ ANDROID_HOME: System.env.ANDROID_HOME,
+ ANDROID_NDK_HOME: System.env.ANDROID_NDK_HOME,
+ CUSTOM_BUILDTOOLS: System.env.CUSTOM_BUILDTOOLS,
+ CUSTOM_JACK: System.env.CUSTOM_JACK,
+ DEBUG_INNER_TEST: System.env.DEBUG_INNER_TEST,
+ RECORD_SPANS: System.env.RECORD_SPANS,
+ INTEGRATION_TEST: "true",
+ DATA_BINDING_INTERNAL_REPO : rootProject.file("../tools/data-binding/internal-prebuilts"),
+ DATA_BINDING_REPO : rootProject.file("../tools/data-binding/maven-repo"),
+].findAll { it.value != null }
+
+// These tasks will not depend on publishLocal, so they will run integration
+// tests against whatever version of the plugin is in ../../../out/repo. This
+// allows us to run integration tests with different versions of Java, without
+// rebuilding the plugin.
+task testPrebuilts(type: Test)
+task connectedIntegrationTestPrebuilts(type: Test)
+
+configure([test, testPrebuilts]) {
+ description =
+ "Runs the project integration tests. This requires an SDK either from the Android " +
+ "source tree, under out/..., or an env var ANDROID_HOME."
+ systemProperties['jar.path'] = jar.archivePath
+ environment = testEnvironment
+
+ // Always run the task, when requested.
+ outputs.upToDateWhen { false }
+
+ forkEvery = 1
+ maxParallelForks = Runtime.runtime.availableProcessors() / 2
+
+ useJUnit {
+ if (System.properties['test.includeCategories'] != null) {
+ def categories = System.properties['test.includeCategories'].split(',')
+ String defaultPackage = "com.android.build.gradle.integration.common.category."
+ categories = categories.collect { it.charAt(0).isUpperCase() ? defaultPackage + it : it }
+ includeCategories categories as String[]
+ }
+ excludeCategories "com.android.build.gradle.integration.common.category.DeviceTests"
+ }
+ exclude "com/android/build/gradle/integration/performance/**"
+ exclude "com/android/build/gradle/integration/automatic/**"
+}
+
+task automaticTest(type: Test) {
+ include "com/android/build/gradle/integration/automatic/**"
+
+ systemProperties['junit.parallel.threads'] = Runtime.runtime.availableProcessors() / 2
+
+ // Always run the task, when requested.
+ outputs.upToDateWhen { false }
+ environment = testEnvironment
+}
+
+check.dependsOn automaticTest
+
+task connectedIntegrationTest(type: Test)
+
+configure([connectedIntegrationTest, connectedIntegrationTestPrebuilts]) {
+ testClassesDir = sourceSets.test.output.classesDir
+ classpath = sourceSets.test.runtimeClasspath
+
+ description =
+ "Runs the project integration tests with device tests. This requires an SDK either " +
+ "from the Android source tree, under out/..., or an env var ANDROID_HOME " +
+ "and a device."
+ group = "verification"
+ systemProperties['jar.path'] = jar.archivePath
+ // Add to, rather than replace the environment, so that TEST_CLASSPATH_DEPENDENCY,
+ // REMOTE_TEST_PROVIDER, ADDITIONAL_TEST_CUSTOM_REPO and any dependencies of the remote test
+ // provider are present in the test environment only for these tests.
+ environment testEnvironment
+
+ // Always run the task, when requested.
+ outputs.upToDateWhen { false }
+
+ forkEvery= 1
+ def count = Runtime.runtime.availableProcessors()
+ if (count > 8) {
+ count = 8
+ }
+ maxParallelForks = System.env.containsKey("REMOTE_TEST_PROVIDER") ? count : 1
+
+ useJUnit {
+ includeCategories "com.android.build.gradle.integration.common.category.DeviceTests"
+ }
+ exclude "com/android/build/gradle/integration/performance/**"
+ exclude "com/android/build/gradle/integration/automatic/**"
+}
+
+task performanceTest(type: Test) {
+ include "com/android/build/gradle/integration/performance/**"
+
+ testClassesDir = sourceSets.test.output.classesDir
+ classpath = sourceSets.test.runtimeClasspath
+
+ description =
+ "Runs the project performance tests. This requires an SDK either " +
+ "from the Android source tree, under out/..., or an env var ANDROID_HOME."
+ group = "verification"
+ systemProperties['jar.path'] = jar.archivePath
+ environment = testEnvironment
+
+ reports {
+ junitXml.destination "${project.buildDir}/perf-results"
+ }
+}
+
+task buildTestDependencies {
+ dependsOn ':base:gradle-core:instrumentIncrementalTestPatches',
+ ':base:gradle-core:instrumentIncrementalTestBaseClasses',
+ ':base:instant-run:instant-run-server:jar'
+}
+
+automaticTest.dependsOn ':publishLocal'
+test.dependsOn buildTestDependencies, ':publishLocal'
+testPrebuilts.dependsOn buildTestDependencies
+connectedIntegrationTest.dependsOn buildTestDependencies, ':publishLocal'
+connectedIntegrationTestPrebuilts.dependsOn buildTestDependencies
+performanceTest.dependsOn ':publishLocal'
+
+jacocoTestReport {
+ sourceSets project(':base:gradle-experimental').sourceSets.main
+ sourceSets project(':base:gradle').sourceSets.main
+ sourceSets project(':base:gradle-core').sourceSets.main
+ sourceSets project(':base:builder').sourceSets.main
+ sourceSets project(':base:builder-model').sourceSets.main
+ sourceSets project(':base:builder-test-api').sourceSets.main
+}
+
+// Due to memory constraints, apply jacoco only when jacocoTestReport is invoked. Make sure to
+// rerun tests when generating report jacoco.
+gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
+ if (taskGraph.hasTask(jacocoTestReport)) {
+ test.environment("ATTACH_JACOCO_AGENT", "yes")
+ }
+}
diff --git a/build-system/integration-test/integration-test.iml b/build-system/integration-test/integration-test.iml
new file mode 100644
index 0000000..e34f266
--- /dev/null
+++ b/build-system/integration-test/integration-test.iml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/test/groovy" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="groovy" level="project" />
+ <orderEntry type="module" module-name="builder" scope="TEST" />
+ <orderEntry type="module" module-name="gradle" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="jacoco" level="project" />
+ <orderEntry type="library" scope="TEST" name="truth" level="project" />
+ <orderEntry type="module" module-name="instant-run-client" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="module" module-name="instant-run-common" scope="TEST" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AaptOptionsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AaptOptionsTest.groovy
new file mode 100644
index 0000000..bee7c8e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AaptOptionsTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.utils.FileUtils.createFile
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.searchAndReplace
+/**
+ * General Model tests
+ */
+ at CompileStatic
+class AaptOptionsTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin('com.android.application'))
+ .create()
+
+ @Before
+ public void setUp() {
+ createFile(project.file("src/main/res/raw/ignored"), "ignored")
+ createFile(project.file("src/main/res/raw/kept"), "kept")
+ }
+
+ @Test
+ public void "test aaptOptions flags"() {
+ project.getBuildFile() << """
+android {
+ aaptOptions {
+ additionalParameters "--ignore-assets", "!ignored*"
+ }
+}
+"""
+ project.execute("clean", "assembleDebug")
+ File apk = project.getApk("debug")
+ assertThatApk(apk).containsFileWithContent("res/raw/kept", "kept")
+ assertThatApk(apk).doesNotContain("res/raw/ignored")
+
+ createFile(project.file("src/main/res/raw/ignored2"), "ignored2")
+ createFile(project.file("src/main/res/raw/kept2"), "kept2")
+
+ project.execute("assembleDebug")
+ assertThatApk(apk).containsFileWithContent("res/raw/kept2", "kept2")
+ assertThatApk(apk).doesNotContain("res/raw/ignored2")
+
+ searchAndReplace(
+ project.buildFile,
+ 'additionalParameters "--ignore-assets", "!ignored\\*"',
+ "")
+
+ project.execute("assembleDebug")
+ assertThatApk(apk).containsFileWithContent("res/raw/kept", "kept")
+ assertThatApk(apk).containsFileWithContent("res/raw/ignored", "ignored")
+ assertThatApk(apk).containsFileWithContent("res/raw/kept2", "kept2")
+ assertThatApk(apk).containsFileWithContent("res/raw/ignored2", "ignored2")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AbiPureSplits.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AbiPureSplits.groovy
new file mode 100644
index 0000000..8c89806
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AbiPureSplits.groovy
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.OutputFile
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.google.common.collect.ImmutableMap
+import com.google.common.collect.Sets
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+import static org.junit.Assert.fail
+
+/**
+ * Test drive for the abiPureSplits samples test.
+ */
+class AbiPureSplits {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("abiPureSplits")
+ .addGradleProperties("android.useDeprecatedNdk=true")
+ .create()
+
+ @BeforeClass
+ static void setup() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ }
+
+ @Test
+ public void "test abi pure splits"() throws Exception {
+ AndroidProject model = project.executeAndReturnModel("clean", "assembleDebug")
+
+ // build a set of expected outputs
+ Set<String> expected = Sets.newHashSetWithExpectedSize(5)
+ expected.add("mips")
+ expected.add("x86")
+ expected.add("armeabi-v7a")
+
+ List<? extends OutputFile> outputs = getOutputs(model);
+ assertEquals(4, outputs.size())
+ for (OutputFile outputFile : outputs) {
+ String filter = ModelHelper.getFilter(outputFile, OutputFile.ABI)
+ assertEquals(filter == null ? OutputFile.MAIN : OutputFile.SPLIT,
+ outputFile.getOutputType())
+
+ // with pure splits, all split have the same version code.
+ if (filter != null) {
+ assertTrue(expected.remove(filter))
+
+ if (outputFile.getFilterTypes().contains(OutputFile.ABI)) {
+ // if this is an ABI split, ensure the .so file presence (and only one)
+ assertThatZip(outputFile.getOutputFile()).entries("lib/.*")
+ .containsExactly("lib/" + filter + "/libhello-jni.so");
+ }
+
+ } else {
+ // main file should not have any lib/ entries.
+ assertThatZip(outputFile.getOutputFile()).entries("lib/.*").isEmpty()
+ // assert that our resources got packaged in the main file.
+ assertThatZip(outputFile.getOutputFile()).entries("res/.*").hasSize(5)
+ }
+ }
+
+ // this checks we didn't miss any expected output.
+ assertTrue(expected.isEmpty())
+ }
+
+ @Test
+ void "test adding an abi pure split"() throws Exception {
+ AndroidProject model = project.executeAndReturnModel("clean", "assembleDebug")
+
+ // get the last modified time of the initial APKs so we can make sure incremental build
+ // does not rebuild things unnecessarily.
+ Map<String, Long> lastModifiedTimePerAbi =
+ getApkModifiedTimePerAbi(getOutputs(model));
+
+ TemporaryProjectModification.doTest(project) {
+ it.replaceInFile("build.gradle", "include 'x86', 'armeabi-v7a', 'mips'",
+ "include 'x86', 'armeabi-v7a', 'mips', 'armeabi'")
+ AndroidProject incrementalModel = project.executeAndReturnModel("assembleDebug")
+
+ List<? extends OutputFile> outputs = getOutputs(incrementalModel);
+ for (OutputFile output : outputs) {
+ System.out.println("found " + output.getOutputFile().getAbsolutePath())
+ }
+ assertThat(outputs).hasSize(5);
+ boolean foundAddedAPK = false;
+ for (OutputFile output : outputs) {
+
+ String filter = ModelHelper.getFilter(output, OutputFile.ABI)
+
+ if (filter.equals("armeabi")) {
+ // found our added abi, done.
+ foundAddedAPK = true;
+ } else {
+ // check that the APK was not rebuilt.
+ assertNotNull("Cannot find initial APK for ABI : " + filter);
+ // uncomment once packageAbiRes is incremental.
+// assertTrue("APK should not have been rebuilt in incremental mode : " + filter,
+// lastModifiedTimePerAbi.get(filter).longValue()
+// == output.getOuputFile().lastModified())
+
+ }
+ }
+ if (!foundAddedAPK) {
+ fail("Could not find added ABI split : armeabi")
+ }
+ }
+ }
+
+ @Test
+ void "test deleting an abi pure split"() throws Exception {
+ AndroidProject model = project.executeAndReturnModel("clean", "assembleDebug")
+
+ // record the build time of each APK to ensure we don't rebuild those in incremental mode.
+ Map<String, Long> lastModifiedTimePerAbi =
+ getApkModifiedTimePerAbi(getOutputs(model))
+
+ TemporaryProjectModification.doTest(project) {
+ it.replaceInFile("build.gradle", "include 'x86', 'armeabi-v7a', 'mips'",
+ "include 'x86', 'armeabi-v7a'")
+ AndroidProject incrementalModel = project.executeAndReturnModel("assembleDebug")
+
+ List<? extends OutputFile> outputs = getOutputs(incrementalModel);
+ assertThat(outputs).hasSize(3);
+ for (OutputFile output : outputs) {
+
+ String filter = ModelHelper.getFilter(output, OutputFile.ABI)
+ if (filter.equals("mips")) {
+ fail("Found deleted ABI split : mips")
+ } else {
+ // check that the APK was not rebuilt.
+ assertNotNull("Cannot find initial APK for ABI : " + filter);
+ // uncomment once packageAbiRes is incremental.
+// assertTrue("APK should not have been rebuilt in incremental mode",
+// lastModifiedTimePerAbi.get(filter).longValue()
+// == output.getOuputFile().lastModified())
+ }
+ }
+ }
+ }
+
+ private List<? extends OutputFile> getOutputs(AndroidProject projectModel) {
+ // Load the custom model for the project
+ Collection<Variant> variants = projectModel.getVariants()
+ assertEquals("Variant Count", 2 , variants.size())
+
+ // get the main artifact of the debug artifact
+ Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
+ assertNotNull("debug Variant null-check", debugVariant)
+ AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
+ assertNotNull("Debug main info null-check", debugMainArtifact)
+
+ // get the outputs.
+ Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
+ assertNotNull(debugOutputs)
+
+ assertEquals(1, debugOutputs.size())
+ AndroidArtifactOutput output = debugOutputs.iterator().next()
+
+ // all splits have the same version.
+ assertEquals(123, output.getVersionCode())
+
+ return output.getOutputs();
+ }
+
+ @NonNull
+ private static Map<String, Long> getApkModifiedTimePerAbi(
+ Collection<? extends OutputFile> outputs) {
+ ImmutableMap.Builder<String, Long> builder = ImmutableMap.builder();
+ for (OutputFile output : outputs) {
+ String key = output.getOutputType() + ModelHelper.getFilter(output, OutputFile.ABI);
+ builder.put(key, output.getOutputFile().lastModified());
+ }
+ return builder.build();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidManifestInTestTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidManifestInTestTest.groovy
new file mode 100644
index 0000000..000f9af
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidManifestInTestTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ApkHelper
+import com.android.build.gradle.integration.common.utils.SdkHelper
+import com.android.ide.common.process.ProcessInfoBuilder
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static org.junit.Assert.fail
+/**
+ * Assemble tests for androidManifestInTest.
+ */
+ at CompileStatic
+class AndroidManifestInTestTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("androidManifestInTest")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebugAndroidTest")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void testUserProvidedTestAndroidManifest() throws Exception {
+ File testApk = project.getApk("debug", "androidTest", "unaligned")
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder()
+ builder.setExecutable(SdkHelper.getAapt())
+
+ builder.addArgs("l", "-a", testApk.getPath())
+
+ List<String> output = ApkHelper.runAndGetOutput(builder.createProcess())
+
+ System.out.println("Beginning dump");
+ boolean foundPermission = false;
+ boolean foundMetadata = false;
+ for (String line : output) {
+ if (line.contains("foo.permission-group.COST_MONEY")) {
+ foundPermission = true
+ }
+ if (line.contains("meta-data")) {
+ foundMetadata = true
+ }
+ }
+ if (!foundPermission) {
+ fail("Could not find user-specified permission group.")
+ }
+ if (!foundMetadata) {
+ fail("Could not find meta-data under instrumentation ")
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidTestResourcesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidTestResourcesTest.groovy
new file mode 100644
index 0000000..2ff91b1
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AndroidTestResourcesTest.groovy
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.builder.model.AndroidProject
+import com.google.common.base.Joiner
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.utils.FileUtils.createFile
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.searchAndReplace
+import static com.android.utils.FileUtils.delete
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertTrue
+/**
+ * Check resources in androidTest are available in the generated R.java.
+ */
+ at CompileStatic
+class AndroidTestResourcesTest {
+ private static AndroidTestApp testApp = HelloWorldApp.noBuildFile()
+ static {
+
+ testApp.addFile(new TestSourceFile(
+ "src/androidTest/res/layout",
+ "test_layout_1.xml",
+ testLayout(1)))
+
+ // This class exists to prevent the resource from being automatically removed,
+ // if we start filtering test resources by default.
+ testApp.addFile(new TestSourceFile("src/androidTest/java/com/example/helloworld",
+ "HelloWorldResourceTest.java", """\
+ package com.example.helloworld;
+ import android.test.ActivityInstrumentationTestCase2;
+ import android.test.suitebuilder.annotation.MediumTest;
+ import android.widget.TextView;
+
+ public class HelloWorldResourceTest extends
+ ActivityInstrumentationTestCase2<HelloWorld> {
+ private TextView mTextView;
+
+ public HelloWorldResourceTest() {
+ super("com.example.helloworld", HelloWorld.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final HelloWorld a = getActivity();
+ mTextView = (TextView) a.findViewById(
+ com.example.helloworld.test.R.id.test_layout_1_textview);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNull("Shouldn't find test_layout_1_textview.", mTextView);
+ }
+ }
+ """.stripIndent()))
+ }
+
+ private static String testLayout(int i) {
+ """\
+ <?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <TextView android:id="@+id/test_layout_${i}_textview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Hello, I am a TextView" />
+ </LinearLayout>
+ """.stripIndent()
+ }
+
+ @ClassRule
+ public static GradleTestProject appProject = GradleTestProject.builder()
+ .withName("application")
+ .fromTestApp(testApp)
+ .create()
+
+ @ClassRule
+ public static GradleTestProject libProject = GradleTestProject.builder()
+ .withName("library")
+ .fromTestApp(testApp)
+ .create()
+
+ /**
+ * Use the test app to create an application and a library project.
+ */
+ @BeforeClass
+ static void setUp() {
+ appProject.getBuildFile() << """
+ apply plugin: 'com.android.application'
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+ """.stripIndent()
+
+ libProject.getBuildFile() << """
+ apply plugin: 'com.android.library'
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+ """.stripIndent()
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ appProject = null
+ libProject = null
+ testApp = null
+ }
+
+ @Test
+ public void "check test layout file listed in test R.java when compiled as an application"() {
+ doTest(appProject)
+ }
+
+ @Test
+ public void "check test layout file listed in test R.java when compiled as a library"() {
+ doTest(libProject)
+ }
+
+ private static void doTest(GradleTestProject project) {
+ project.execute("assembleDebugAndroidTest")
+ assertTrue(checkLayoutInR(project, 1, 1))
+ assertFalse(checkLayoutInR(project, 2, 2))
+
+ createFile(
+ project.file("src/androidTest/res/layout/test_layout_2.xml"),
+ testLayout(2))
+
+ project.execute("assembleDebugAndroidTest")
+ assertTrue(checkLayoutInR(project, 1, 1))
+ assertTrue(checkLayoutInR(project, 2, 2))
+
+ searchAndReplace(
+ project.file("src/androidTest/res/layout/test_layout_2.xml"),
+ "test_layout_2_textview",
+ "test_layout_3_textview")
+
+ project.execute("assembleDebugAndroidTest")
+ assertTrue(checkLayoutInR(project, 1, 1))
+ assertFalse(checkLayoutInR(project, 2, 2))
+ assertTrue(checkLayoutInR(project, 2, 3))
+
+ delete(project.file("src/androidTest/res/layout/test_layout_2.xml"))
+
+ project.execute("assembleDebugAndroidTest")
+ assertTrue(checkLayoutInR(project, 1, 1))
+ assertFalse(checkLayoutInR(project, 2, 2))
+ assertFalse(checkLayoutInR(project, 2, 3))
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void "check test layout can be used in device tests"() {
+ appProject.executeConnectedCheck()
+ }
+
+
+ private static boolean checkLayoutInR(GradleTestProject fixture, int layout, int textView) {
+ def rFile = fixture.file(Joiner.on(File.separatorChar).join(
+ "build", AndroidProject.FD_GENERATED, "source", "r",
+ "androidTest", "debug", "com", "example", "helloworld", "test", "R.java"))
+ assertTrue("Should have generated R file", rFile.exists())
+ def rFileContents = rFile.getText("UTF-8")
+
+ return (rFileContents.contains("test_layout_${layout}")
+ && rFileContents.contains("test_layout_${textView}_textview"))
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdInLibsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdInLibsTest.groovy
new file mode 100644
index 0000000..4bf6b1b
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdInLibsTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ApkHelper
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import org.junit.Rule
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Tests for @{applicationId} placeholder presence in library manifest files.
+ * Such placeholders should be left intact until the library is merged into a consuming application
+ * with a known application Id.
+ */
+class ApplicationIdInLibsTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("applicationIdInLibsTest")
+ .create()
+
+ @Test
+ public void "test library placeholder substitution in final apk"() throws Exception {
+ Map<String, AndroidProject> models =
+ project.executeAndReturnMultiModel(
+ "clean",
+ "app:assembleDebug")
+ assertTrue(checkPermissionPresent(
+ models,
+ "'com.example.manifest_merger_example.flavor.permission.C2D_MESSAGE'"))
+
+ TestFileUtils.searchAndReplace(
+ project.file('app/build.gradle'),
+ "manifest_merger_example.flavor",
+ "manifest_merger_example.change")
+
+ models = project.executeAndReturnMultiModel(
+ "clean",
+ "app:assembleDebug")
+ assertFalse(checkPermissionPresent(
+ models,
+ "'com.example.manifest_merger_example.flavor.permission.C2D_MESSAGE'"))
+ assertTrue(checkPermissionPresent(
+ models,
+ "'com.example.manifest_merger_example.change.permission.C2D_MESSAGE'"))
+ }
+
+ private static boolean checkPermissionPresent(Map<String, AndroidProject> models, String permission) {
+ // Load the custom model for the project
+ Collection<Variant> variants = models.get(":app").getVariants()
+ assertEquals("Variant Count", 2, variants.size())
+
+ // get the main artifact of the debug artifact
+ Variant debugVariant = ModelHelper.getVariant(variants, "flavorDebug")
+ assertNotNull("debug Variant null-check", debugVariant)
+ AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
+ assertNotNull("Debug main info null-check", debugMainArtifact)
+
+ // get the outputs.
+ Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
+ assertNotNull(debugOutputs)
+
+ assertEquals(1, debugOutputs.size())
+ AndroidArtifactOutput output = debugOutputs.first()
+ assertEquals(1, output.getOutputs().size())
+
+ List<String> apkBadging =
+ ApkHelper.getApkBadging(output.getOutputs().first().getOutputFile());
+
+ for (String line : apkBadging) {
+ if (line.contains("uses-permission: name=" +
+ permission)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdTest.groovy
new file mode 100644
index 0000000..11271b0
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ApplicationIdTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.searchAndReplace
+/**
+ * Test setting applicationId and applicationIdSuffix.
+ */
+ at CompileStatic
+class ApplicationIdTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create();
+
+ @Before
+ public void setUp() {
+ project.buildFile << """
+apply plugin: "com.android.application"
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ applicationId "com.example.applicationidtest"
+ applicationIdSuffix "default"
+ }
+
+ buildTypes {
+ debug {
+ applicationIdSuffix ".debug"
+ }
+ }
+ productFlavors {
+ f1 {
+ applicationIdSuffix "f1"
+ }
+ }
+}
+"""
+ }
+
+ @Test
+ public void "check application id"() {
+ project.execute("assembleF1");
+ assertThatApk(project.getApk("f1", "debug"))
+ .hasPackageName("com.example.applicationidtest.default.f1.debug")
+ assertThatApk(project.getApk("f1", "release", "unsigned"))
+ .hasPackageName("com.example.applicationidtest.default.f1")
+
+ searchAndReplace(
+ project.buildFile,
+ 'applicationIdSuffix ".debug"',
+ 'applicationIdSuffix ".foo"')
+
+ project.execute("assembleF1");
+
+ assertThatApk(project.getApk("f1", "debug"))
+ .hasPackageName("com.example.applicationidtest.default.f1.foo")
+ assertThatApk(project.getApk("f1", "release", "unsigned"))
+ .hasPackageName("com.example.applicationidtest.default.f1")
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArchivesBaseNameTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArchivesBaseNameTest.groovy
new file mode 100644
index 0000000..0c91007
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArchivesBaseNameTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.runner.FilterableParameterized
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.searchAndReplace
+/**
+ * Ensures that archivesBaseName setting on android project is used when choosing the apk file
+ * names
+ */
+ at CompileStatic
+ at RunWith(FilterableParameterized)
+class ArchivesBaseNameTest {
+ private static final String OLD_NAME = "random_name"
+ private static final String NEW_NAME = "changed_name"
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object[]> data() {
+ return [
+ ["com.android.application", "apk"].toArray(),
+ ["com.android.library", "aar"].toArray(),
+ ]
+ }
+
+ @Rule
+ public GradleTestProject project
+
+ private String extension
+
+ public ArchivesBaseNameTest(String plugin, String extension) {
+ project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin(plugin))
+ .create()
+
+ this.extension = extension
+ }
+
+ @Test
+ void testArtifactName() {
+ checkApkName('project', extension)
+
+ project.buildFile << """
+ archivesBaseName = '$OLD_NAME'
+ """
+ checkApkName(OLD_NAME, extension)
+
+ searchAndReplace(project.buildFile, OLD_NAME, NEW_NAME)
+ checkApkName(NEW_NAME, extension)
+ }
+
+ private void checkApkName(String name, String extension) {
+ AndroidProject model = project.executeAndReturnModel("assembleDebug")
+ File outputFile = model.getVariants().find { it.name == "debug" }
+ .getMainArtifact()
+ .getOutputs().first()
+ .getMainOutputFile()
+ .getOutputFile()
+
+
+ assertThat(outputFile.getName()).isEqualTo("$name-debug.$extension".toString())
+ assertThat(outputFile).isFile()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArtifactApiTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArtifactApiTest.groovy
new file mode 100644
index 0000000..7585793
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ArtifactApiTest.groovy
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.ArtifactMetaData
+import com.android.builder.model.BuildTypeContainer
+import com.android.builder.model.Dependencies
+import com.android.builder.model.JavaArtifact
+import com.android.builder.model.ProductFlavorContainer
+import com.android.builder.model.SourceProvider
+import com.android.builder.model.SourceProviderContainer
+import com.android.builder.model.Variant
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
+import static com.google.common.truth.Truth.assertThat
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+/**
+ * Assemble tests for artifactApi.
+ */
+ at CompileStatic
+class ArtifactApiTest {
+ // Unit test variants produce an extra Java artifact.
+ private static final int DEFAULT_EXTRA_JAVA_ARTIFACTS = 1
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("artifactApi")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.getSingleModel()
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check metadata info in model"() {
+ // check the Artifact Meta Data
+ Collection<ArtifactMetaData> extraArtifacts = model.getExtraArtifacts()
+ assertNotNull("Extra artifact collection null-check", extraArtifacts)
+ assertThat(extraArtifacts).hasSize(DEFAULT_EXTRA_JAVA_ARTIFACTS + 2)
+
+ assertNotNull("instrument test metadata null-check",
+ ModelHelper.getArtifactMetaData(extraArtifacts, ARTIFACT_ANDROID_TEST))
+
+ // get the custom one.
+ ArtifactMetaData extraArtifactMetaData = ModelHelper.getArtifactMetaData(
+ extraArtifacts, "__test__")
+ assertNotNull("custom extra metadata null-check", extraArtifactMetaData)
+ assertFalse("custom extra meta data is Test check", extraArtifactMetaData.isTest())
+ assertEquals("custom extra meta data type check", ArtifactMetaData.TYPE_JAVA,
+ extraArtifactMetaData.getType())
+ }
+
+ @Test
+ void "check build types contain extra source provider artifact is in model"() {
+ // check the extra source provider on the build Types.
+ for (BuildTypeContainer btContainer : model.getBuildTypes()) {
+ String name = btContainer.getBuildType().getName()
+ Collection<SourceProviderContainer> extraSourceProviderContainers = btContainer.getExtraSourceProviders()
+ assertNotNull(
+ "Extra source provider containers for build type '" + name + "' null-check",
+ extraSourceProviderContainers)
+ assertEquals(
+ "Extra source provider containers for build type size '" + name + "' check",
+ DEFAULT_EXTRA_JAVA_ARTIFACTS + 1,
+ extraSourceProviderContainers.size())
+
+ SourceProviderContainer sourceProviderContainer = extraSourceProviderContainers.iterator().next()
+ assertNotNull(
+ "Extra artifact source provider for " + name + " null check",
+ sourceProviderContainer)
+
+ assertEquals(
+ "Extra artifact source provider for " + name + " name check",
+ "__test__",
+ sourceProviderContainer.getArtifactName())
+
+ assertEquals(
+ "Extra artifact source provider for " + name + " value check",
+ "buildType:" + name,
+ sourceProviderContainer.getSourceProvider().getManifestFile().getPath())
+ }
+ }
+
+ @Test
+ void "check product flavors contain extra source provider artifact is in model"() {
+ // check the extra source provider on the product flavors.
+ for (ProductFlavorContainer pfContainer : model.getProductFlavors()) {
+ String name = pfContainer.getProductFlavor().getName()
+ Collection<SourceProviderContainer> extraSourceProviderContainers = pfContainer.
+ getExtraSourceProviders()
+ assertNotNull(
+ "Extra source provider container for product flavor '" + name + "' null-check",
+ extraSourceProviderContainers)
+ assertEquals(
+ "Extra artifact source provider container for product flavor size '" + name +
+ "' check",
+ 3, // unit test, android test, extra provider from the API
+ extraSourceProviderContainers.size())
+
+ assertNotNull(
+ "Extra source provider container for product flavor '" + name +
+ "': instTest check",
+ ModelHelper.getSourceProviderContainer(extraSourceProviderContainers,
+ ARTIFACT_ANDROID_TEST))
+
+
+ SourceProviderContainer sourceProviderContainer = ModelHelper.
+ getSourceProviderContainer(
+ extraSourceProviderContainers, "__test__")
+ assertNotNull(
+ "Custom source provider container for " + name + " null check",
+ sourceProviderContainer)
+
+ assertEquals(
+ "Custom artifact source provider for " + name + " name check",
+ "__test__",
+ sourceProviderContainer.getArtifactName())
+
+ assertEquals(
+ "Extra artifact source provider for " + name + " value check",
+ "productFlavor:" + name,
+ sourceProviderContainer.getSourceProvider().getManifestFile().getPath())
+ }
+ }
+
+ @Test
+ void "check extra artifact is in variants"() {
+ for (Variant variant : model.getVariants()) {
+ String name = variant.getName()
+ Collection<JavaArtifact> javaArtifacts = variant.getExtraJavaArtifacts()
+ assertThat(javaArtifacts).hasSize(DEFAULT_EXTRA_JAVA_ARTIFACTS + 1)
+ JavaArtifact javaArtifact = javaArtifacts.find {it.name == "__test__"}
+ assertEquals("assemble:" + name, javaArtifact.getAssembleTaskName())
+ assertEquals("compile:" + name, javaArtifact.getCompileTaskName())
+ assertEquals(new File("classesFolder:" + name), javaArtifact.getClassesFolder())
+
+ SourceProvider variantSourceProvider = javaArtifact.getVariantSourceProvider()
+ assertNotNull(variantSourceProvider)
+ assertEquals("provider:" + name, variantSourceProvider.getManifestFile().getPath())
+
+ Dependencies deps = javaArtifact.getDependencies()
+ assertNotNull("java artifact deps null-check", deps)
+ assertFalse(deps.getJavaLibraries().isEmpty())
+ }
+ }
+
+ @Test
+ public void backwardsCompatible() throws Exception {
+ // ATTENTION Author and Reviewers - please make sure required changes to the build file
+ // are backwards compatible before updating this test.
+ assertThat(FileUtils.sha1(project.file("build.gradle")))
+ .isEqualTo("082c085e52219d8e0f8f29911cf89f05ccd05e6f")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoDensitySplitTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoDensitySplitTest.groovy
new file mode 100644
index 0000000..ad9dd4b
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoDensitySplitTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static org.junit.Assert.assertEquals
+/**
+ * MultiAPK test where densities are obtained automatically.
+ */
+ at CompileStatic
+class AutoDensitySplitTest {
+ static AndroidProject model
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("densitySplit")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.getBuildFile() << """android {
+ splits {
+ density {
+ enable true
+ auto true
+ compatibleScreens 'small', 'normal', 'large', 'xlarge'
+ }
+ }
+ }"""
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void testPackaging() {
+ for (Variant variant : model.getVariants()) {
+ AndroidArtifact mainArtifact = variant.getMainArtifact()
+ if (!variant.getBuildType().equalsIgnoreCase("Debug")) {
+ continue
+ }
+ assertEquals(5, mainArtifact.getOutputs().size())
+
+ File mdpiApk = project.getApk("mdpi", "debug")
+ assertThatZip(mdpiApk).contains("res/drawable-mdpi-v4/other.png")
+ }
+ }
+
+ @Test
+ void "check version code in apk"() {
+ File universalApk = project.getApk("universal", "debug")
+ assertThatApk(universalApk).hasVersionCode(112)
+ assertThatApk(universalApk).hasVersionName("version 112")
+
+ File mdpiApk = project.getApk("mdpi", "debug")
+ assertThatApk(mdpiApk).hasVersionCode(212)
+ assertThatApk(mdpiApk).hasVersionName("version 212")
+
+ File hdpiApk = project.getApk("hdpi", "debug")
+ assertThatApk(hdpiApk).hasVersionCode(312)
+ assertThatApk(hdpiApk).hasVersionName("version 312")
+
+ File xhdpiApk = project.getApk("xhdpi", "debug")
+ assertThatApk(xhdpiApk).hasVersionCode(412)
+ assertThatApk(xhdpiApk).hasVersionName("version 412")
+
+ File xxhdiApk = project.getApk("xxhdpi", "debug")
+ assertThatApk(xxhdiApk).hasVersionCode(512)
+ assertThatApk(xxhdiApk).hasVersionName("version 512")
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoPureSplits.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoPureSplits.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoPureSplits.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoPureSplits.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoResConfig.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoResConfig.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoResConfig.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/AutoResConfig.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicMultiFlavorTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicMultiFlavorTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicMultiFlavorTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicMultiFlavorTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest.groovy
new file mode 100644
index 0000000..9062e9a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest.groovy
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.category.SmokeTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.JavaCompileOptions
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.google.common.truth.Truth.assertThat
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertNull
+import static org.junit.Assert.assertTrue
+/**
+ * Assemble tests for basic.
+ */
+ at Category(SmokeTests.class)
+ at CompileStatic
+class BasicTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("basic")
+ .withoutNdk()
+ .create()
+
+ static public AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void report() {
+ project.execute("androidDependencies")
+ }
+
+ @Test
+ void basicModel() {
+ assertFalse("Library Project", model.isLibrary())
+ assertEquals("Compile Target", "android-23", model.getCompileTarget())
+ assertFalse("Non empty bootclasspath", model.getBootClasspath().isEmpty())
+
+ assertNotNull("aaptOptions not null", model.getAaptOptions())
+ assertEquals("aaptOptions noCompress", 1, model.getAaptOptions().getNoCompress().size())
+ assertTrue("aaptOptions noCompress", model.getAaptOptions().getNoCompress().contains("txt"))
+ assertEquals(
+ "aaptOptions ignoreAssetsPattern",
+ "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~",
+ model.getAaptOptions().getIgnoreAssets())
+ assertFalse(
+ "aaptOptions getFailOnMissingConfigEntry",
+ model.getAaptOptions().getFailOnMissingConfigEntry())
+
+ JavaCompileOptions javaCompileOptions = model.getJavaCompileOptions()
+ // since source and target compatibility are not explicitly set in the build.gradle,
+ // the default value should be the JDK version used to build against.
+ assertEquals(System.getProperty("java.specification.version"),
+ javaCompileOptions.getSourceCompatibility())
+ assertEquals(System.getProperty("java.specification.version"),
+ javaCompileOptions.getTargetCompatibility())
+ assertEquals("UTF-8", javaCompileOptions.getEncoding())
+ }
+
+ @Test
+ public void sourceProvidersModel() {
+ ModelHelper.testDefaultSourceSets(model, project.getTestDir())
+
+ // test the source provider for the artifacts
+ for (Variant variant : model.getVariants()) {
+ AndroidArtifact artifact = variant.getMainArtifact()
+ assertNull(artifact.getVariantSourceProvider())
+ assertNull(artifact.getMultiFlavorSourceProvider())
+ }
+ }
+
+ @Test
+ void "check debug and release output have different names"() {
+ ModelHelper.compareDebugAndReleaseOutput(model)
+ }
+
+ @Test
+ void "we don't fail on LICENSE.txt when packaging dependencies"() {
+ project.execute("assembleAndroidTest")
+ }
+
+ @Test
+ void generationInModel() {
+ AndroidProject model = project.getSingleModel()
+ assertThat(model.getPluginGeneration())
+ .named("Plugin Generation")
+ .isEqualTo(AndroidProject.GENERATION_ORIGINAL)
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void install() {
+ GradleTestProject.assumeLocalDevice();
+ project.execute("installDebug", "uninstallAll")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest2.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest2.groovy
new file mode 100644
index 0000000..711ed44
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BasicTest2.groovy
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.build.gradle.integration.common.utils.ProductFlavorHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidLibrary
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.BuildTypeContainer
+import com.android.builder.model.ClassField
+import com.android.builder.model.Dependencies
+import com.android.builder.model.JavaLibrary
+import com.android.builder.model.MavenCoordinates
+import com.android.builder.model.ProductFlavor
+import com.android.builder.model.Variant
+import com.google.common.collect.Maps
+import com.google.common.collect.Sets
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Assemble tests for basic that loads the model but doesn't build.
+ */
+ at CompileStatic
+class BasicTest2 {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("basic")
+ .create()
+
+ static public AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ public void "check variant details"() throws Exception {
+ Collection<Variant> variants = model.getVariants()
+ assertEquals("Variant Count", 2 , variants.size())
+
+ // debug variant
+ Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
+ assertNotNull("debug Variant null-check", debugVariant)
+ new ProductFlavorHelper(debugVariant.getMergedFlavor(), "Debug Merged Flavor")
+ .setVersionCode(12)
+ .setVersionName("2.0")
+ .setMinSdkVersion(16)
+ .setTargetSdkVersion(16)
+ .setTestInstrumentationRunner("android.test.InstrumentationTestRunner")
+ .setTestHandleProfiling(Boolean.FALSE)
+ .setTestFunctionalTest(null)
+ .test()
+
+ // debug variant, tested.
+ AndroidArtifact debugMainInfo = debugVariant.getMainArtifact()
+ assertNotNull("Debug main info null-check", debugMainInfo)
+ assertEquals("Debug package name", "com.android.tests.basic.debug",
+ debugMainInfo.getApplicationId())
+ assertTrue("Debug signed check", debugMainInfo.isSigned())
+ assertEquals("Debug sourceGenTask", "generateDebugSources", debugMainInfo.getSourceGenTaskName())
+ assertEquals("Debug compileTask", "compileDebugSources", debugMainInfo.getCompileTaskName())
+
+ Collection<AndroidArtifactOutput> debugMainOutputs = debugMainInfo.getOutputs()
+ assertNotNull("Debug main output null-check", debugMainOutputs)
+ assertEquals("Debug main output size", 1, debugMainOutputs.size())
+ AndroidArtifactOutput debugMainOutput = debugMainOutputs.iterator().next()
+ assertNotNull(debugMainOutput)
+ assertNotNull(debugMainOutput.getMainOutputFile())
+ assertNotNull(debugMainOutput.getAssembleTaskName())
+ assertNotNull(debugMainOutput.getGeneratedManifest())
+ assertEquals(12, debugMainOutput.getVersionCode())
+
+ // check debug dependencies
+ Dependencies debugDependencies = debugMainInfo.getDependencies()
+ assertNotNull(debugDependencies)
+ Collection<AndroidLibrary> debugLibraries = debugDependencies.getLibraries()
+ assertNotNull(debugLibraries)
+ assertEquals(1, debugLibraries.size())
+ assertTrue(debugDependencies.getProjects().isEmpty())
+
+ AndroidLibrary androidLibrary = debugLibraries.iterator().next()
+ assertNotNull(androidLibrary)
+ assertNotNull(androidLibrary.getBundle())
+ assertNotNull(androidLibrary.getFolder())
+ MavenCoordinates coord = androidLibrary.getResolvedCoordinates()
+ assertNotNull(coord)
+ assertEquals("com.google.android.gms:play-services:3.1.36",
+ coord.getGroupId() + ":" + coord.getArtifactId() + ":" + coord.getVersion())
+
+
+ Collection<JavaLibrary> javaLibraries = debugDependencies.getJavaLibraries()
+ assertNotNull(javaLibraries)
+ assertEquals(2, javaLibraries.size())
+
+ Set<String> javaLibs = Sets.newHashSet(
+ "com.android.support:support-v13:13.0.0",
+ "com.android.support:support-v4:13.0.0"
+ )
+
+ for (JavaLibrary javaLib : javaLibraries) {
+ coord = javaLib.getResolvedCoordinates()
+ assertNotNull(coord)
+ String lib = coord.getGroupId() + ":" + coord.getArtifactId() + ":" + coord.getVersion()
+ assertTrue(javaLibs.contains(lib))
+ javaLibs.remove(lib)
+ }
+
+ // this variant is tested.
+ Collection<AndroidArtifact> debugExtraAndroidArtifacts = debugVariant.getExtraAndroidArtifacts()
+ AndroidArtifact debugTestInfo = ModelHelper.getAndroidArtifact(debugExtraAndroidArtifacts,
+ ARTIFACT_ANDROID_TEST)
+ assertNotNull("Test info null-check", debugTestInfo)
+ assertEquals("Test package name", "com.android.tests.basic.debug.test",
+ debugTestInfo.getApplicationId())
+ assertTrue("Test signed check", debugTestInfo.isSigned())
+ assertEquals("Test sourceGenTask", "generateDebugAndroidTestSources", debugTestInfo.getSourceGenTaskName())
+ assertEquals("Test compileTask", "compileDebugAndroidTestSources", debugTestInfo.getCompileTaskName())
+
+ Collection<File> generatedResFolders = debugTestInfo.getGeneratedResourceFolders()
+ assertNotNull(generatedResFolders)
+ // size 2 = rs output + resValue output
+ assertEquals(2, generatedResFolders.size())
+
+ Collection<AndroidArtifactOutput> debugTestOutputs = debugTestInfo.getOutputs()
+ assertNotNull("Debug test output null-check", debugTestOutputs)
+ assertEquals("Debug test output size", 1, debugTestOutputs.size())
+ AndroidArtifactOutput debugTestOutput = debugTestOutputs.iterator().next()
+ assertNotNull(debugTestOutput)
+ assertNotNull(debugTestOutput.getMainOutputFile())
+ assertNotNull(debugTestOutput.getAssembleTaskName())
+ assertNotNull(debugTestOutput.getGeneratedManifest())
+
+ // test the resValues and buildConfigFields.
+ ProductFlavor defaultConfig = model.getDefaultConfig().getProductFlavor()
+ Map<String, ClassField> buildConfigFields = defaultConfig.getBuildConfigFields()
+ assertNotNull(buildConfigFields)
+ assertEquals(2, buildConfigFields.size())
+
+ assertEquals("true", buildConfigFields.get("DEFAULT").getValue())
+ assertEquals("\"foo2\"", buildConfigFields.get("FOO").getValue())
+
+ Map<String, ClassField> resValues = defaultConfig.getResValues()
+ assertNotNull(resValues)
+ assertEquals(1, resValues.size())
+
+ assertEquals("foo", resValues.get("foo").getValue())
+
+ // test on the debug build type.
+ Collection<BuildTypeContainer> buildTypes = model.getBuildTypes()
+ for (BuildTypeContainer buildTypeContainer : buildTypes) {
+ if (buildTypeContainer.getBuildType().getName().equals(DEBUG)) {
+ buildConfigFields = buildTypeContainer.getBuildType().getBuildConfigFields()
+ assertNotNull(buildConfigFields)
+ assertEquals(1, buildConfigFields.size())
+
+ assertEquals("\"bar\"", buildConfigFields.get("FOO").getValue())
+
+ resValues = buildTypeContainer.getBuildType().getResValues()
+ assertNotNull(resValues)
+ assertEquals(1, resValues.size())
+
+ assertEquals("foo2", resValues.get("foo").getValue())
+ }
+ }
+
+ // now test the merged flavor
+ ProductFlavor mergedFlavor = debugVariant.getMergedFlavor()
+
+ buildConfigFields = mergedFlavor.getBuildConfigFields()
+ assertNotNull(buildConfigFields)
+ assertEquals(2, buildConfigFields.size())
+
+ assertEquals("true", buildConfigFields.get("DEFAULT").getValue())
+ assertEquals("\"foo2\"", buildConfigFields.get("FOO").getValue())
+
+ resValues = mergedFlavor.getResValues()
+ assertNotNull(resValues)
+ assertEquals(1, resValues.size())
+
+ assertEquals("foo", resValues.get("foo").getValue())
+
+
+ // release variant, not tested.
+ Variant releaseVariant = ModelHelper.getVariant(variants, "release")
+ assertNotNull("release Variant null-check", releaseVariant)
+
+ AndroidArtifact relMainInfo = releaseVariant.getMainArtifact()
+ assertNotNull("Release main info null-check", relMainInfo)
+ assertEquals("Release package name", "com.android.tests.basic",
+ relMainInfo.getApplicationId())
+ assertFalse("Release signed check", relMainInfo.isSigned())
+ assertEquals("Release sourceGenTask", "generateReleaseSources", relMainInfo.getSourceGenTaskName())
+ assertEquals("Release javaCompileTask", "compileReleaseSources", relMainInfo.getCompileTaskName())
+
+ Collection<AndroidArtifactOutput> relMainOutputs = relMainInfo.getOutputs()
+ assertNotNull("Rel Main output null-check", relMainOutputs)
+ assertEquals("Rel Main output size", 1, relMainOutputs.size())
+ AndroidArtifactOutput relMainOutput = relMainOutputs.iterator().next()
+ assertNotNull(relMainOutput)
+ assertNotNull(relMainOutput.getMainOutputFile())
+ assertNotNull(relMainOutput.getAssembleTaskName())
+ assertNotNull(relMainOutput.getGeneratedManifest())
+ assertEquals(13, relMainOutput.getVersionCode())
+
+
+ Collection<AndroidArtifact> releaseExtraAndroidArtifacts = releaseVariant.getExtraAndroidArtifacts()
+ AndroidArtifact relTestInfo = ModelHelper.getAndroidArtifact(releaseExtraAndroidArtifacts, ARTIFACT_ANDROID_TEST)
+ assertNull("Release test info null-check", relTestInfo)
+
+ // check release dependencies
+ Dependencies releaseDependencies = relMainInfo.getDependencies()
+ assertNotNull(releaseDependencies)
+ Collection<AndroidLibrary> releaseLibraries = releaseDependencies.getLibraries()
+ assertNotNull(releaseLibraries)
+ assertEquals(3, releaseLibraries.size())
+
+ // map for each aar we expect to find and how many local jars they each have.
+ Map<String, Integer> aarLibs = Maps.newHashMapWithExpectedSize(3)
+ aarLibs.put("com.android.support:support-v13:21.0.0", 1)
+ aarLibs.put("com.android.support:support-v4:21.0.0", 1)
+ aarLibs.put("com.google.android.gms:play-services:3.1.36", 0)
+ for (AndroidLibrary androidLib : releaseLibraries) {
+ assertNotNull(androidLib.getBundle())
+ assertNotNull(androidLib.getFolder())
+ coord = androidLib.getResolvedCoordinates()
+ assertNotNull(coord)
+ String lib = coord.getGroupId() + ":" + coord.getArtifactId() + ":" + coord.getVersion()
+
+ Integer localJarCount = aarLibs.get(lib)
+ assertNotNull("Check presense of " + lib, localJarCount)
+ assertEquals("Check local jar count for " + lib,
+ localJarCount.intValue(), androidLib.getLocalJars().size())
+ System.out.println(">>" + androidLib.getLocalJars())
+ aarLibs.remove(lib)
+ }
+
+ assertTrue("check for missing libs", aarLibs.isEmpty())
+ }
+
+
+ @Test
+ @Category(DeviceTests.class)
+ void install() {
+ GradleTestProject.assumeLocalDevice();
+ project.execute("installDebug", "uninstallAll")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BootClasspathTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BootClasspathTest.groovy
new file mode 100644
index 0000000..b9f0156
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BootClasspathTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Test BaseExtension.getBootClasspath can be use before afterEvaluate.
+ */
+ at CompileStatic
+class BootClasspathTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+
+task checkBootClasspath {
+ assert android.getBootClasspath() != null
+}
+"""
+ }
+
+ @Test
+ public void "check getBootClasspath can be called"() {
+ project.execute("checkBootClasspath")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildConfigTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildConfigTest.groovy
new file mode 100644
index 0000000..5ac1820
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildConfigTest.groovy
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.ClassField
+import com.android.builder.model.Variant
+import com.google.common.base.Charsets
+import com.google.common.collect.Maps
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.Assert
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+
+/**
+ * Test for BuildConfig field declared in build type, flavors, and variant and how they
+ * override each other
+ */
+ at CompileStatic
+class BuildConfigTest {
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ private static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ project.getBuildFile() << """
+ apply plugin: 'com.android.application'
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ buildConfigField "int", "VALUE_DEFAULT", "1"
+ buildConfigField "int", "VALUE_DEBUG", "1"
+ buildConfigField "int", "VALUE_FLAVOR", "1"
+ buildConfigField "int", "VALUE_VARIANT", "1"
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField "int", "VALUE_DEBUG", "100"
+ buildConfigField "int", "VALUE_VARIANT", "100"
+ }
+ }
+
+ productFlavors {
+ flavor1 {
+ buildConfigField "int", "VALUE_DEBUG", "10"
+ buildConfigField "int", "VALUE_FLAVOR", "10"
+ buildConfigField "int", "VALUE_VARIANT", "10"
+ }
+ flavor2 {
+ buildConfigField "int", "VALUE_DEBUG", "20"
+ buildConfigField "int", "VALUE_FLAVOR", "20"
+ buildConfigField "int", "VALUE_VARIANT", "20"
+ }
+ }
+
+ applicationVariants.all { variant ->
+ if (variant.buildType.name == "debug") {
+ variant.buildConfigField "int", "VALUE_VARIANT", "1000"
+ }
+ }
+ }
+ """.stripIndent()
+
+ model = project.executeAndReturnModel(
+ 'clean',
+ 'generateFlavor1DebugBuildConfig',
+ 'generateFlavor1ReleaseBuildConfig',
+ 'generateFlavor2DebugBuildConfig',
+ 'generateFlavor2ReleaseBuildConfig')
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void builFlavor1Debug() {
+ String expected =
+"""/**
+ * Automatically generated file. DO NOT MODIFY
+ */
+package com.example.helloworld;
+
+public final class BuildConfig {
+ public static final boolean DEBUG = Boolean.parseBoolean("true");
+ public static final String APPLICATION_ID = "com.example.helloworld";
+ public static final String BUILD_TYPE = "debug";
+ public static final String FLAVOR = "flavor1";
+ public static final int VERSION_CODE = 1;
+ public static final String VERSION_NAME = "1.0";
+ // Fields from the variant
+ public static final int VALUE_VARIANT = 1000;
+ // Fields from build type: debug
+ public static final int VALUE_DEBUG = 100;
+ // Fields from product flavor: flavor1
+ public static final int VALUE_FLAVOR = 10;
+ // Fields from default config.
+ public static final int VALUE_DEFAULT = 1;
+}
+"""
+ doCheckBuildConfig(expected, 'flavor1/debug')
+ }
+
+ @Test
+ void modelFlavor1Debug() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '10')
+ map.put('VALUE_DEBUG', '100')
+ map.put('VALUE_VARIANT', '1000')
+ checkVariant(model.getVariants(), 'flavor1Debug', map)
+ }
+
+ @Test
+ void buildFlavor2Debug() {
+ String expected =
+"""/**
+ * Automatically generated file. DO NOT MODIFY
+ */
+package com.example.helloworld;
+
+public final class BuildConfig {
+ public static final boolean DEBUG = Boolean.parseBoolean("true");
+ public static final String APPLICATION_ID = "com.example.helloworld";
+ public static final String BUILD_TYPE = "debug";
+ public static final String FLAVOR = "flavor2";
+ public static final int VERSION_CODE = 1;
+ public static final String VERSION_NAME = "1.0";
+ // Fields from the variant
+ public static final int VALUE_VARIANT = 1000;
+ // Fields from build type: debug
+ public static final int VALUE_DEBUG = 100;
+ // Fields from product flavor: flavor2
+ public static final int VALUE_FLAVOR = 20;
+ // Fields from default config.
+ public static final int VALUE_DEFAULT = 1;
+}
+"""
+ doCheckBuildConfig(expected, 'flavor2/debug')
+ }
+
+ @Test
+ void modelFlavor2Debug() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '20')
+ map.put('VALUE_DEBUG', '100')
+ map.put('VALUE_VARIANT', '1000')
+ checkVariant(model.getVariants(), 'flavor2Debug', map)
+ }
+
+ @Test
+ void buildFlavor1Release() {
+ String expected =
+ """/**
+ * Automatically generated file. DO NOT MODIFY
+ */
+package com.example.helloworld;
+
+public final class BuildConfig {
+ public static final boolean DEBUG = false;
+ public static final String APPLICATION_ID = "com.example.helloworld";
+ public static final String BUILD_TYPE = "release";
+ public static final String FLAVOR = "flavor1";
+ public static final int VERSION_CODE = 1;
+ public static final String VERSION_NAME = "1.0";
+ // Fields from product flavor: flavor1
+ public static final int VALUE_DEBUG = 10;
+ public static final int VALUE_FLAVOR = 10;
+ public static final int VALUE_VARIANT = 10;
+ // Fields from default config.
+ public static final int VALUE_DEFAULT = 1;
+}
+"""
+ doCheckBuildConfig(expected, 'flavor1/release')
+ }
+
+ @Test
+ void modelFlavor1Release() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '10')
+ map.put('VALUE_DEBUG', '10')
+ map.put('VALUE_VARIANT', '10')
+ checkVariant(model.getVariants(), 'flavor1Release', map)
+ }
+
+ @Test
+ void buildFlavor2Release() {
+ String expected =
+ """/**
+ * Automatically generated file. DO NOT MODIFY
+ */
+package com.example.helloworld;
+
+public final class BuildConfig {
+ public static final boolean DEBUG = false;
+ public static final String APPLICATION_ID = "com.example.helloworld";
+ public static final String BUILD_TYPE = "release";
+ public static final String FLAVOR = "flavor2";
+ public static final int VERSION_CODE = 1;
+ public static final String VERSION_NAME = "1.0";
+ // Fields from product flavor: flavor2
+ public static final int VALUE_DEBUG = 20;
+ public static final int VALUE_FLAVOR = 20;
+ public static final int VALUE_VARIANT = 20;
+ // Fields from default config.
+ public static final int VALUE_DEFAULT = 1;
+}
+"""
+ doCheckBuildConfig(expected, 'flavor2/release')
+ }
+
+ @Test
+ void modelFlavor2Release() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '20')
+ map.put('VALUE_DEBUG', '20')
+ map.put('VALUE_VARIANT', '20')
+ checkVariant(model.getVariants(), 'flavor2Release', map)
+ }
+
+ private static void doCheckBuildConfig(@NonNull String expected, @NonNull String variantDir) {
+ checkBuildConfig(project, expected, variantDir)
+ }
+
+ static void checkBuildConfig(
+ @NonNull GradleTestProject project,
+ @NonNull String expected,
+ @NonNull String variantDir) {
+ File outputFile = new File(project.getTestDir(),
+ "build/generated/source/buildConfig/$variantDir/com/example/helloworld/BuildConfig.java")
+ Assert.assertTrue("Missing file: " + outputFile, outputFile.isFile())
+ assertEquals(expected, Files.asByteSource(outputFile).asCharSource(Charsets.UTF_8).read())
+ }
+
+ private static void checkVariant(
+ @NonNull Collection<Variant> variants,
+ @NonNull String variantName,
+ @Nullable Map<String, String> valueMap) {
+ Variant variant = ModelHelper.findVariantByName(variants, variantName)
+ assertNotNull("${variantName} variant null-check", variant)
+
+ AndroidArtifact artifact = variant.getMainArtifact()
+ assertNotNull("${variantName} main artifact null-check", artifact)
+
+ Map<String, ClassField> value = artifact.getBuildConfigFields()
+ assertNotNull(value)
+
+ // check the map against the expected one.
+ assertEquals(valueMap.keySet(), value.keySet())
+ for (String key : valueMap.keySet()) {
+ ClassField field = value.get(key)
+ assertNotNull("${variantName}: expected field ${key}", field)
+ assertEquals(
+ "${variantName}: check Value of ${key}",
+ valueMap.get(key),
+ field.getValue())
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildToolsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildToolsTest.groovy
new file mode 100644
index 0000000..be5ebe9
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/BuildToolsTest.groovy
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.builder.model.AndroidProject
+import com.google.common.collect.ImmutableList
+import com.google.common.collect.Sets
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.google.common.truth.Truth.assert_
+
+ at CompileStatic
+/**
+ * Tests to ensure that changing the build tools version in the build.gradle will trigger
+ * re-execution of some tasks even if no source file change was detected.
+ */
+class BuildToolsTest {
+
+ private static final Pattern UP_TO_DATE_PATTERN = ~/:(\S+)\s+UP-TO-DATE/
+
+ private static final Pattern INPUT_CHANGED_PATTERN =
+ ~/Value of input property '.*' has changed for task ':(\S+)'/
+
+ private static final String[] COMMON_TASKS = [
+ "compileDebugAidl", "compileDebugRenderscript",
+ "mergeDebugResources", "processDebugResources",
+ "compileReleaseAidl", "compileReleaseRenderscript",
+ "mergeReleaseResources", "processReleaseResources"
+ ]
+
+ private static final List<String> JAVAC_TASKS = ImmutableList.builder().add(COMMON_TASKS)
+ .add("transformClassesWithDexForDebug")
+ .add("transformClassesWithDexForRelease")
+ .build()
+ private static final List<String> JACK_TASKS = ImmutableList.builder().add(COMMON_TASKS)
+ .add("jillDebugRuntimeLibraries").add("jillDebugPackagedLibraries")
+ .add("jillReleaseRuntimeLibraries").add("jillReleasePackagedLibraries").build()
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .captureStdOut(true)
+ .create()
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+"""
+ }
+
+ @Test
+ public void nullBuild() {
+ project.execute("assemble")
+ project.stdout.reset()
+ project.execute("assemble")
+
+ Set<String> skippedTasks = getTasksMatching(UP_TO_DATE_PATTERN, project.stdout)
+ assert_().withFailureMessage("Expecting tasks to be UP-TO-DATE").that(skippedTasks)
+ .containsAllIn(GradleTestProject.USE_JACK ? JACK_TASKS : JAVAC_TASKS)
+ }
+
+ @Test
+ public void invalidateBuildTools() {
+ project.execute("assemble")
+ // Change our build tools version to 22.0.1 unless it is already the current version,
+ // in that case, downgrade to 21.1.2.
+ // The point is, change the build tools version from what it was when the "assemble" task
+ // was executed right before this comment.
+ String oldBuildToolsVersion = "22.0.1"
+ // Sanity check:
+ assertThat(oldBuildToolsVersion).isNotEqualTo(GradleTestProject.DEFAULT_BUILD_TOOL_VERSION)
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion '$oldBuildToolsVersion'
+}
+"""
+
+ project.stdout.reset()
+ project.execute("assemble")
+ Set<String> affectedTasks = getTasksMatching(INPUT_CHANGED_PATTERN, project.stdout)
+ assert_().withFailureMessage("Expecting tasks to be invalidated").that(affectedTasks)
+ .containsAllIn(GradleTestProject.USE_JACK ? JACK_TASKS : JAVAC_TASKS)
+ }
+
+ @Test
+ void buildToolsInModel() {
+ AndroidProject model = project.getSingleModel()
+ assertThat(model.getBuildToolsVersion())
+ .named("Build Tools Version")
+ .isEqualTo(GradleTestProject.DEFAULT_BUILD_TOOL_VERSION)
+ }
+
+ private static Set<String> getTasksMatching(Pattern pattern, ByteArrayOutputStream output) {
+ Set<String> result = Sets.newHashSet()
+ Matcher matcher = (output.toString("UTF-8") =~ pattern)
+ while (matcher.find()) {
+ result.add(matcher.group(1))
+ }
+ result
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedAbiDensityPureSplits.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedAbiDensityPureSplits.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedAbiDensityPureSplits.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedAbiDensityPureSplits.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityAndLanguageTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityAndLanguageTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityAndLanguageTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityAndLanguageTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityWithDisabledLanguageTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityWithDisabledLanguageTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityWithDisabledLanguageTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedDensityWithDisabledLanguageTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedLanguageWithDisabledDensityTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedLanguageWithDisabledDensityTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedLanguageWithDisabledDensityTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CombinedLanguageWithDisabledDensityTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CustomArtifactDepTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CustomArtifactDepTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CustomArtifactDepTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/CustomArtifactDepTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitInLTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitInLTest.groovy
new file mode 100644
index 0000000..25bbe11
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitInLTest.groovy
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.OutputFile
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.google.common.collect.ImmutableMap
+import com.google.common.collect.Sets
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+import static org.junit.Assert.fail
+
+/**
+ * Assemble tests for class densitySplitInL
+ .
+ */
+class DensitySplitInLTest {
+
+ static AndroidProject model
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("densitySplitInL")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check split outputs"() throws Exception {
+
+ // build a set of expected outputs
+ Set<String> expected = Sets.newHashSetWithExpectedSize(5)
+ expected.add(null)
+ expected.add("mdpi")
+ expected.add("hdpi")
+ expected.add("xhdpi")
+ expected.add("xxhdpi")
+
+ List<? extends OutputFile> outputs = getOutputs(model);
+ assertThat(outputs).hasSize(5)
+ for (OutputFile outputFile : outputs) {
+ String densityFilter = ModelHelper.getFilter(outputFile, OutputFile.DENSITY)
+ assertEquals(densityFilter == null ? OutputFile.MAIN : OutputFile.SPLIT,
+ outputFile.getOutputType())
+
+ expected.remove(densityFilter)
+ }
+
+ // this checks we didn't miss any expected output.
+ assertTrue(expected.isEmpty())
+ }
+
+ @Test
+ void "check adding a density incrementally"() throws Exception {
+ // get the last modified time of the initial APKs so we can make sure incremental build
+ // does not rebuild things unnecessarily.
+ Map<String, Long> lastModifiedTimePerDensity =
+ getApkModifiedTimePerDensity(getOutputs(model));
+
+ TemporaryProjectModification.doTest(project) {
+ it.replaceInFile("build.gradle", "exclude \"ldpi\", \"tvdpi\", \"xxxhdpi\"",
+ "exclude \"ldpi\", \"tvdpi\"")
+ AndroidProject incrementalModel = project.executeAndReturnModel("assembleDebug")
+
+ List<? extends OutputFile> outputs = getOutputs(incrementalModel);
+ assertThat(outputs).hasSize(6);
+ boolean foundAddedAPK = false;
+ for (OutputFile output : outputs) {
+
+ String filter = ModelHelper.getFilter(output, OutputFile.DENSITY)
+
+ if ("xxxhdpi".equals(filter)) {
+ // found our added density, done.
+ foundAddedAPK = true;
+ } else {
+ // check that the APK was not rebuilt.
+ String key = output.getOutputType() + filter;
+ Long initialApkModifiedTime = lastModifiedTimePerDensity.get(key);
+ assertNotNull(
+ "Cannot find initial APK for density : " + filter,
+ initialApkModifiedTime);
+ // uncomment once PackageSplitRes is made incremental.
+// assertTrue("APK should not have been rebuilt in incremental mode : " + filter,
+// initialApkModifiedTime.longValue()
+// == output.getOutputFile().lastModified());
+ }
+ }
+ if (!foundAddedAPK) {
+ fail("Did not find the xxxhdpi density pure split in the outputs")
+ }
+ }
+ }
+
+ @Test
+ void "check deleting a density incrementally"() throws Exception {
+
+ // get the last modified time of the initial APKs so we can make sure incremental build
+ // does not rebuild things unnecessarily.
+ Map<String, Long> lastModifiedTimePerDensity =
+ getApkModifiedTimePerDensity(getOutputs(model));
+
+ TemporaryProjectModification.doTest(project) {
+ it.replaceInFile("build.gradle", "exclude \"ldpi\", \"tvdpi\", \"xxxhdpi\"",
+ "exclude \"ldpi\", \"tvdpi\", \"xxxhdpi\", \"xxhdpi\"")
+ AndroidProject incrementalModel = project.executeAndReturnModel("assembleDebug")
+
+ List<? extends OutputFile> outputs = getOutputs(incrementalModel);
+ assertThat(outputs).hasSize(4);
+ for (OutputFile output : outputs) {
+ String filter = ModelHelper.getFilter(output, OutputFile.DENSITY);
+ if (filter == null) continue;
+ if ("xxhdpi".equals(filter)) {
+ fail("Found deleted xxhdpi pure split split in the outputs")
+ } else {
+ // check that the APK was not rebuilt.
+ String key = output.getOutputType() + filter;
+ Long initialApkModifiedTime = lastModifiedTimePerDensity.get(key);
+ assertNotNull("Cannot find initial APK for density : " + filter,
+ initialApkModifiedTime);
+ // uncomment once PackageSplitRes is made incremental.
+// assertTrue("APK should not have been rebuilt in incremental mode : " + filter,
+// initialApkModifiedTime.longValue()
+// == output.getOutputFile().lastModified());
+ }
+ }
+ }
+ }
+
+ private static Collection<? extends OutputFile> getOutputs(AndroidProject projectModel) {
+ Collection<Variant> variants = projectModel.getVariants()
+ assertEquals("Variant Count", 2 , variants.size())
+
+ // get the main artifact of the debug artifact
+ Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
+ assertNotNull("debug Variant null-check", debugVariant)
+ AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
+ assertNotNull("Debug main info null-check", debugMainArtifact)
+
+ // get the outputs.
+ Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
+ assertNotNull(debugOutputs)
+
+ assertEquals(1, debugOutputs.size())
+ AndroidArtifactOutput output = debugOutputs.iterator().next()
+
+ // with pure splits, all split have the same version code.
+ assertEquals(12, output.getVersionCode())
+
+ return output.getOutputs()
+ }
+
+ @NonNull
+ private static Map<String, Long> getApkModifiedTimePerDensity(
+ Collection<? extends OutputFile> outputs) {
+ ImmutableMap.Builder<String, Long> builder = ImmutableMap.builder();
+ for (OutputFile output : outputs) {
+ String key = output.getOutputType() + ModelHelper.getFilter(output, OutputFile.DENSITY);
+ builder.put(key, output.getOutputFile().lastModified());
+ }
+ return builder.build();
+ }
+
+ @Nullable
+ private OutputFile getAPK(List<? extends OutputFile> outputs, String abiFilter) {
+ for (OutputFile output : outputs) {
+ if (ModelHelper.getFilter(output, OutputFile.DENSITY).equals(abiFilter)) {
+ return output;
+ }
+ }
+ return null;
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitTest.groovy
new file mode 100644
index 0000000..0065772
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitTest.groovy
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.OutputFile
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.google.common.collect.Maps
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+/**
+ * Assemble tests for densitySplit.
+ */
+ at CompileStatic
+class DensitySplitTest {
+
+ static AndroidProject model
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("densitySplit")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void testPackaging() {
+ for (Variant variant : model.getVariants()) {
+ AndroidArtifact mainArtifact = variant.getMainArtifact()
+ if (!variant.getBuildType().equalsIgnoreCase("Debug")) {
+ continue
+ }
+ assertEquals(5, mainArtifact.getOutputs().size())
+
+ File mdpiApk = project.getApk("mdpi", "debug")
+ assertThatZip(mdpiApk).contains("res/drawable-mdpi-v4/other.png")
+ }
+ }
+
+ @Test
+ void "check version code in apk"() {
+ File universalApk = project.getApk("universal", "debug")
+ assertThatApk(universalApk).hasVersionCode(112)
+ assertThatApk(universalApk).hasVersionName("version 112")
+
+ File mdpiApk = project.getApk("mdpi", "debug")
+ assertThatApk(mdpiApk).hasVersionCode(212)
+ assertThatApk(mdpiApk).hasVersionName("version 212")
+
+ File hdpiApk = project.getApk("hdpi", "debug")
+ assertThatApk(hdpiApk).hasVersionCode(312)
+ assertThatApk(hdpiApk).hasVersionName("version 312")
+
+ File xhdpiApk = project.getApk("xhdpi", "debug")
+ assertThatApk(xhdpiApk).hasVersionCode(412)
+ assertThatApk(xhdpiApk).hasVersionName("version 412")
+
+ File xxhdiApk = project.getApk("xxhdpi", "debug")
+ assertThatApk(xxhdiApk).hasVersionCode(512)
+ assertThatApk(xxhdiApk).hasVersionName("version 512")
+ }
+
+ @Test
+ void "check version code in model"() {
+ Collection<Variant> variants = model.getVariants()
+ assertEquals("Variant Count", 2 , variants.size())
+
+ // get the main artifact of the debug artifact
+ Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
+ assertNotNull("debug Variant null-check", debugVariant)
+ AndroidArtifact debugMainArficat = debugVariant.getMainArtifact()
+ assertNotNull("Debug main info null-check", debugMainArficat)
+
+ // get the outputs.
+ Collection<AndroidArtifactOutput> debugOutputs = debugMainArficat.getOutputs()
+ assertNotNull(debugOutputs)
+ assertEquals(5, debugOutputs.size())
+
+ // build a map of expected outputs and their versionCode
+ Map<String, Integer> expected = Maps.newHashMapWithExpectedSize(5)
+ expected.put(null, 112)
+ expected.put("mdpi", 212)
+ expected.put("hdpi", 312)
+ expected.put("xhdpi", 412)
+ expected.put("xxhdpi", 512)
+
+ assertEquals(5, debugOutputs.size())
+ for (AndroidArtifactOutput output : debugOutputs) {
+ assertEquals(OutputFile.FULL_SPLIT, output.getMainOutputFile().getOutputType())
+ Collection<? extends OutputFile> outputFiles = output.getOutputs()
+ assertEquals(1, outputFiles.size())
+ assertNotNull(output.getMainOutputFile())
+
+ String densityFilter = ModelHelper.getFilter(output.getMainOutputFile(), OutputFile.DENSITY)
+ Integer value = expected.get(densityFilter)
+ // this checks we're not getting an unexpected output.
+ assertNotNull("Check Valid output: " + (densityFilter == null ? "universal"
+ : densityFilter),
+ value)
+
+ assertEquals(value.intValue(), output.getVersionCode())
+ expected.remove(densityFilter)
+ }
+
+ // this checks we didn't miss any expected output.
+ assertTrue(expected.isEmpty())
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitWithPublishNonDefaultTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitWithPublishNonDefaultTest.groovy
new file mode 100644
index 0000000..2a553a3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DensitySplitWithPublishNonDefaultTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+ at CompileStatic
+class DensitySplitWithPublishNonDefaultTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ publishNonDefault true
+
+ splits {
+ density {
+ enable true
+ exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
+ compatibleScreens 'small', 'normal', 'large', 'xlarge'
+ }
+ }
+}
+"""
+ // build the release for publication (though debug is published too)
+ project.execute("assembleRelease")
+ }
+
+ @Test
+ public void "build and publish"() {
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesTest.groovy
new file mode 100644
index 0000000..d3af563
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependenciesTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for dependencies.
+ */
+ at CompileStatic
+class DependenciesTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("dependencies")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCheckerTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCheckerTest.groovy
new file mode 100644
index 0000000..adcb7c1
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCheckerTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import org.gradle.tooling.BuildException
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.runners.Enclosed
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static org.junit.Assert.fail
+/**
+ * Assemble tests for dependencyChecker.
+ */
+ at CompileStatic
+ at RunWith(Enclosed)
+class DependencyCheckerTest {
+
+ @RunWith(JUnit4)
+ @CompileStatic
+ public static class HttpClient {
+ @Rule
+ public GradleTestProject httpClientProject = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application"))
+ .captureStdOut(true)
+ .create()
+
+ @Test
+ public void httpComponents() throws Exception {
+ httpClientProject.buildFile <<
+ "dependencies.compile 'org.apache.httpcomponents:httpclient:4.1.1'"
+
+ httpClientProject.execute("clean", "assembleDebug")
+ assertThat(httpClientProject.stdout.toString())
+ .contains("Dependency org.apache.httpcomponents:httpclient:4.1.1 is ignored")
+ }
+ }
+
+ @RunWith(JUnit4)
+ @CompileStatic
+ public static class ComGoogleAndroid {
+ @Rule
+ public GradleTestProject minSdkProject = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application"))
+ .captureStdOut(true)
+ .captureStdErr(true)
+ .create()
+
+ /**
+ * See {@link com.android.build.gradle.internal.tasks.PrepareDependenciesTask} for the
+ * expected output.
+ */
+ @Test
+ public void comGoogleAndroid() throws Exception {
+ minSdkProject.buildFile << """
+ // Lower than com.google.android:android:4.1.14
+ android.defaultConfig.minSdkVersion 14
+
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ compile 'com.google.android:android:4.1.1.4'
+ }
+ """
+
+ try {
+ minSdkProject.execute("clean", "assemble")
+ fail("should throw")
+ } catch (BuildException e) {
+ // expected.
+ }
+
+ String stdOut = minSdkProject.stderr.toString()
+ assertThat(stdOut).contains("corresponds to API level 15")
+ // Picked up from com.google.android
+ assertThat(stdOut).contains("which is 14") // Declared in Gradle.
+ assertThat(stdOut).contains("com.google.android")
+ }
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCyclesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCyclesTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCyclesTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DependencyCyclesTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DexLimitTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DexLimitTest.java
new file mode 100644
index 0000000..062e80b
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/DexLimitTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.build.gradle.integration.application;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification;
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class DexLimitTest {
+
+ private static final String CLASS_NAME = "com/example/B";
+ private static final String CLASS_FULL_TYPE = "L" + CLASS_NAME + ";";
+ private static final String CLASS_SRC_LOCATION = "src/main/java/" + CLASS_NAME + ".java";
+
+ private static final String CLASS_CONTENT = "\n"
+ + "package com.example;\n"
+ + "public class B { }";
+
+ private static final AndroidTestApp TEST_APP =
+ HelloWorldApp.forPlugin("com.android.application");
+
+ static {
+ StringBuilder classFileBuilder = new StringBuilder();
+ for (int i=0; i<65536/2; i++) {
+ classFileBuilder.append(" public void m").append(i).append("() {}\n");
+ }
+
+ String methods = classFileBuilder.toString();
+
+ String classFileA = "package com.example;\npublic class A {\n" + methods + "\n}";
+ String classFileB = "package com.example;\npublic class B {\n" + methods + "\n}";
+
+ TEST_APP.addFile(new TestSourceFile("src/main/java/com/example", "A.java", classFileA));
+ TEST_APP.addFile(new TestSourceFile("src/main/java/com/example", "B.java", classFileB));
+ }
+
+ @Rule
+ public final GradleTestProject mProject = GradleTestProject.builder()
+ .fromTestApp(TEST_APP).captureStdErr(true).withHeap("2G").create();
+
+ @Test
+ public void checkDexErrorMessage() throws Exception {
+ mProject.getStderr().reset();
+ mProject.executeExpectingFailure("assembleDebug");
+ assertThat(mProject.getStderr().toString()).contains(
+ "https://developer.android.com/tools/building/multidex.html");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExternalTestProjectTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExternalTestProjectTest.groovy
new file mode 100644
index 0000000..4ec97e6
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExternalTestProjectTest.groovy
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.SyncIssue
+import groovy.transform.CompileStatic
+import org.gradle.tooling.BuildException
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+import static org.junit.Assert.fail
+/**
+ * Check that a project can depend on a jar dependency published by another app project.
+ */
+ at CompileStatic
+class ExternalTestProjectTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder().captureStdErr(true).create()
+
+ private File app2BuildFile
+
+ @Before
+ public void setUp() {
+ File rootFile = project.getTestDir()
+ new File(rootFile, "settings.gradle") << """
+include ':app1'
+include ':app2'
+"""
+ // app1 module
+ File app1 = new File(rootFile, "app1")
+ HelloWorldApp.noBuildFile().write(app1, null)
+ new File(app1, "build.gradle") << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+
+task testJar(type: Jar, dependsOn: 'assembleRelease') {
+
+}
+
+configurations {
+ testLib
+}
+
+artifacts {
+ testLib testJar
+}
+
+"""
+ // app2 module
+ File app2 = new File(rootFile, "app2")
+ HelloWorldApp.noBuildFile().write(app2, null)
+ app2BuildFile = new File(app2, "build.gradle")
+ }
+
+ @Test
+ public void testExtraJarDependency() {
+ app2BuildFile << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+
+dependencies {
+ compile project(path: ':app1', configuration: 'testLib')
+}
+"""
+
+ project.execute('clean', 'app2:assembleDebug')
+ }
+
+ @Test
+ void testApkDependencyInBuild() {
+ app2BuildFile << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+
+dependencies {
+ compile project(path: ':app1')
+}
+"""
+ try {
+ project.execute('clean', 'app2:assembleDebug')
+ fail('Broken build file did not throw exception')
+ } catch (BuildException e) {
+ Throwable t = e
+ while (t.getCause() != null) {
+ t = t.getCause()
+ }
+
+ // looks like we can't actually test the instance t against GradleException
+ // due to it coming through the tooling API from a different class loader.
+ assertEquals("org.gradle.api.GradleException", t.getClass().canonicalName)
+ assertEquals("Dependency Error. See console for details.", t.getMessage())
+ }
+
+ // check there is a version of the error, after the task name:
+ ByteArrayOutputStream stderr = project.stderr
+ String log = stderr.toString()
+
+ assertTrue("stderr contains error", log.contains(
+ "Dependency project:app1:unspecified on project app2 resolves to an APK archive which is not supported as a compilation dependency. File:"))
+
+ }
+
+ @Test
+ void testApkDependencyInModel() {
+ app2BuildFile << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+
+dependencies {
+ compile project(path: ':app1')
+}
+"""
+
+ Map<String, AndroidProject> modelMap = project.getAllModelsIgnoringSyncIssues()
+
+ AndroidProject model = modelMap.get(':app2')
+ assertNotNull(model)
+
+ SyncIssue issue = assertThat(model).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_DEPENDENCY_IS_APK,
+ 'project:app1:unspecified')
+
+ String expectedMsg = "Dependency project:app1:unspecified on project app2 resolves to an APK archive which is not supported as a compilation dependency. File:"
+ assertThat(issue.getMessage()).startsWith(expectedMsg)
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExtractAnnotationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExtractAnnotationTest.groovy
new file mode 100644
index 0000000..d7bcf96
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ExtractAnnotationTest.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+/**
+ * Integration test for extracting annotations.
+ * <p>
+ * Tip: To execute just this test after modifying the annotations extraction code:
+ * <pre>
+ * $ cd tools
+ * $ ./gradlew :base:i:test -Dtest.single=ExtractAnnotationTest
+ * </pre>
+ */
+ at CompileStatic
+class ExtractAnnotationTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("extractAnnotations")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check extract annotation"() {
+ File debugFileOutput = project.file("build/$AndroidProject.FD_INTERMEDIATES/annotations/debug")
+ File classesJar = project.file("build/$AndroidProject.FD_INTERMEDIATES/bundles/debug/classes.jar")
+ File file = new File(debugFileOutput, "annotations.zip")
+
+ //noinspection SpellCheckingInspection
+ String expectedContent = (""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest int getVisibility()\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.VISIBLE, com.android.tests.extractannotations.ExtractTest.INVISIBLE, com.android.tests.extractannotations.ExtractTest.GONE, 5, 17, com.android.tests.extractannotations.Constants.CONSTANT_1}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest java.lang.String getStringMode(int)\">\n"
+ + " <annotation name=\"android.support.annotation.StringDef\">\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.STRING_1, com.android.tests.extractannotations.ExtractTest.STRING_2, "literalValue", "concatenated"}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest java.lang.String getStringMode(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.ExtractTest.VISIBLE, com.android.tests.extractannotations.ExtractTest.INVISIBLE, com.android.tests.extractannotations.ExtractTest.GONE, 5, 17, com.android.tests.extractannotations.Constants.CONSTANT_1}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void checkForeignTypeDef(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"flag\" val=\"true\" />\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_2}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void testMask(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"flag\" val=\"true\" />\n"
+ + " <val name=\"value\" val=\"{0, com.android.tests.extractannotations.Constants.FLAG_VALUE_1, com.android.tests.extractannotations.Constants.FLAG_VALUE_2}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.ExtractTest void testNonMask(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"flag\" val=\"false\" />\n"
+ + " <val name=\"value\" val=\"{0, com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_3}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"com.android.tests.extractannotations.TopLevelTypeDef\">\n"
+ + " <annotation name=\"android.support.annotation.IntDef\">\n"
+ + " <val name=\"flag\" val=\"true\" />\n"
+ + " <val name=\"value\" val=\"{com.android.tests.extractannotations.Constants.CONSTANT_1, com.android.tests.extractannotations.Constants.CONSTANT_2}\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + "</root>\n")
+
+ assertThatZip(file).containsFileWithContent(
+ "com/android/tests/extractannotations/annotations.xml", expectedContent)
+
+ // check the resulting .aar file to ensure annotations.zip inclusion.
+ assertThatZip(project.getAar("debug")).contains("annotations.zip")
+
+ // Check typedefs removals:
+
+ // public typedef: should be present
+ assertThatZip(classesJar).contains(
+ "com/android/tests/extractannotations/ExtractTest\$Visibility.class")
+
+ // private/protected typedefs: should have been removed
+ assertThatZip(classesJar).doesNotContain(
+ "com/android/tests/extractannotations/ExtractTest\$Mask.class")
+ assertThatZip(classesJar).doesNotContain(
+ "com/android/tests/extractannotations/ExtractTest\$NonMaskType.class")
+
+ // Make sure the NonMask symbol (from a private typedef) is completely gone from the
+ // outer class
+ assertThatZip(classesJar).containsFileWithoutContent(
+ "com/android/tests/extractannotations/ExtractTest.class",
+ "NonMaskType");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutBuildTypeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutBuildTypeTest.groovy
new file mode 100644
index 0000000..8aa5d8e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutBuildTypeTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+/**
+ * Assemble tests for filteredOutBuildType.
+ */
+ at CompileStatic
+class FilteredOutBuildTypeTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("filteredOutBuildType")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check filtered out variant isn't in model"() {
+ // Load the custom model for the project
+ assertEquals("Variant Count", 1, model.getVariants().size())
+ Variant variant = model.getVariants().iterator().next()
+ assertEquals("Variant name", "release", variant.getBuildType())
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutVariantsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutVariantsTest.groovy
new file mode 100644
index 0000000..f9fe0a6
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FilteredOutVariantsTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+
+/**
+ * Assemble tests for filteredOutVariants.
+ */
+ at CompileStatic
+class FilteredOutVariantsTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("filteredOutVariants")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check filtered out variant isn't in model"() {
+ Collection<Variant> variants = model.getVariants()
+ // check we have the right number of variants:
+ // arm/cupcake, arm/gingerbread, x86/gingerbread, mips/gingerbread
+ // all 4 in release and debug
+ assertEquals("Variant Count", 8, variants.size())
+
+ for (Variant variant : variants) {
+ List<String> flavors = variant.getProductFlavors()
+ assertFalse("check ignored x86/cupcake",
+ flavors.contains("x68") && flavors.contains("cupcake"))
+ assertFalse("check ignored mips/cupcake",
+ flavors.contains("mips") && flavors.contains("cupcake"))
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavoredTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavoredTest.groovy
new file mode 100644
index 0000000..47627ba
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavoredTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for flavored.
+ */
+ at CompileStatic
+class FlavoredTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("flavored")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavorsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavorsTest.groovy
new file mode 100644
index 0000000..9a83744
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/FlavorsTest.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.build.gradle.integration.common.utils.ProductFlavorHelper
+import com.android.build.gradle.integration.common.utils.SourceProviderHelper
+import com.android.build.gradle.integration.common.utils.VariantHelper
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.BuildTypeContainer
+import com.android.builder.model.ProductFlavor
+import com.android.builder.model.ProductFlavorContainer
+import com.android.builder.model.SourceProviderContainer
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.builder.core.VariantType.ANDROID_TEST
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static com.google.common.truth.Truth.assertThat;
+
+/**
+ * Assemble tests for flavors.
+ */
+ at CompileStatic
+class FlavorsTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("flavors")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check flavors show up in model"() throws Exception {
+ File projectDir = project.getTestDir()
+
+ assertFalse("Library Project", model.isLibrary())
+
+ assertThat(model.getFlavorDimensions()).containsExactly("group1", "group2")
+
+ ProductFlavorContainer defaultConfig = model.getDefaultConfig()
+
+ new SourceProviderHelper(model.getName(), projectDir,
+ "main", defaultConfig.getSourceProvider())
+ .test()
+
+ SourceProviderContainer testSourceProviderContainer = ModelHelper.getSourceProviderContainer(
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST)
+ assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer)
+
+ new SourceProviderHelper(model.getName(), projectDir,
+ ANDROID_TEST.prefix, testSourceProviderContainer.getSourceProvider())
+ .test()
+
+ Collection<BuildTypeContainer> buildTypes = model.getBuildTypes()
+ assertEquals("Build Type Count", 2, buildTypes.size())
+
+ Collection<Variant> variants = model.getVariants()
+ assertEquals("Variant Count", 8, variants.size())
+
+ Collection<ProductFlavorContainer> flavorContainers = model.getProductFlavors();
+ assertThat(flavorContainers).hasSize(4);
+ Map expected = [f1:"group1", f2: "group1", fa: "group2", fb: "group2"]
+ for (ProductFlavorContainer flavorContainer: flavorContainers) {
+ ProductFlavor flavor = flavorContainer.getProductFlavor();
+ assertEquals(expected.get(flavor.name), flavor.dimension)
+ }
+
+ Variant f1faDebugVariant = ModelHelper.getVariant(variants, "f1FaDebug")
+ assertNotNull("f1faDebug Variant null-check", f1faDebugVariant)
+ assertThat(f1faDebugVariant.getProductFlavors()).containsExactly("f1","fa")
+ new ProductFlavorHelper(f1faDebugVariant.getMergedFlavor(), "F1faDebug Merged Flavor")
+ .test()
+ new VariantHelper(f1faDebugVariant, projectDir, "flavors-f1-fa-debug.apk").test()
+ }
+
+ @Test
+ public void "compound source sets are in the model"() throws Exception {
+ for (variant in model.variants) {
+ assert variant.extraJavaArtifacts.every { it.multiFlavorSourceProvider != null }
+ assert variant.extraJavaArtifacts.every { it.variantSourceProvider != null }
+ assert variant.extraAndroidArtifacts.every { it.multiFlavorSourceProvider != null }
+ // No per-variant source providers for android tests.
+ assert variant.extraAndroidArtifacts.every { it.variantSourceProvider == null }
+ }
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApi2Test.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApi2Test.groovy
new file mode 100644
index 0000000..85dd185
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApi2Test.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.JavaArtifact
+import com.android.builder.model.Variant
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+/**
+ * Assemble tests for genFolderApi2.
+ */
+ at CompileStatic
+class GenFolderApi2Test {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("genFolderApi2")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.getSingleModel()
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check Java Folder in Model"() throws Exception {
+ File projectDir = project.testDir
+
+ File buildDir = new File(projectDir, "build")
+
+ for (Variant variant : model.variants) {
+
+ AndroidArtifact mainInfo = variant.mainArtifact
+ assertNotNull(
+ "Null-check on mainArtifactInfo for " + variant.displayName,
+ mainInfo)
+
+ // Get the generated source folders.
+ Collection<File> genSourceFolder = mainInfo.generatedSourceFolders
+
+ // We're looking for a custom folder.
+ String sourceFolderStart = new File(buildDir, "customCode").absolutePath + File.separatorChar
+ assertTrue("custom generated source folder check", genSourceFolder.any {
+ it.absolutePath.startsWith(sourceFolderStart)
+ })
+
+ // Unit testing artifact:
+ assertThat(variant.getExtraJavaArtifacts()).hasSize(1)
+ JavaArtifact unitTestArtifact = variant.extraJavaArtifacts.first()
+ def sortedFolders = unitTestArtifact.generatedSourceFolders.sort()
+ assertThat(sortedFolders).hasSize(2)
+ assertThat(sortedFolders[0].absolutePath).startsWith(sourceFolderStart)
+ assertThat(sortedFolders[0].absolutePath).endsWith("-1")
+ assertThat(sortedFolders[1].absolutePath).startsWith(sourceFolderStart)
+ assertThat(sortedFolders[1].absolutePath).endsWith("-2")
+ }
+ }
+
+ @Test
+ public void backwardsCompatible() throws Exception {
+ // ATTENTION Author and Reviewers - please make sure required changes to the build file
+ // are backwards compatible before updating this test.
+ assertThat(FileUtils.sha1(project.file("build.gradle")))
+ .isEqualTo("f7733d1da27157e1b4cf6db25d4cf70bf59e9280")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApiTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApiTest.groovy
new file mode 100644
index 0000000..e6f4588
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/GenFolderApiTest.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+/**
+ * Assemble tests for genFolderApi.
+ */
+ at CompileStatic
+class GenFolderApiTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("genFolderApi")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check the custom java generation task ran"() throws Exception {
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/custom/Foo;")
+ }
+
+ @Test
+ void "check the custom res generation task ran"() throws Exception {
+ assertThatZip(project.getApk("debug")).contains("res/xml/generated.xml")
+ }
+
+ @Test
+ void "check Java folder in Model"() throws Exception {
+ File projectDir = project.getTestDir()
+
+ File buildDir = new File(projectDir, "build")
+
+ for (Variant variant : model.getVariants()) {
+
+ AndroidArtifact mainInfo = variant.getMainArtifact()
+ assertNotNull(
+ "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
+ mainInfo)
+
+ // get the generated source folders.
+ Collection<File> genSourceFolder = mainInfo.getGeneratedSourceFolders()
+
+ // We're looking for a custom folder
+ String sourceFolderStart = new File(buildDir, "customCode").getAbsolutePath() + File.separatorChar
+ boolean found = false
+ for (File f : genSourceFolder) {
+ if (f.getAbsolutePath().startsWith(sourceFolderStart)) {
+ found = true
+ break
+ }
+ }
+
+ assertTrue("custom generated source folder check", found)
+ }
+ }
+
+ @Test
+ void "check Res Folder in Model"() throws Exception {
+ File projectDir = project.getTestDir()
+
+ File buildDir = new File(projectDir, "build")
+
+ for (Variant variant : model.getVariants()) {
+
+ AndroidArtifact mainInfo = variant.getMainArtifact()
+ assertNotNull(
+ "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
+ mainInfo)
+
+ // get the generated res folders.
+ Collection<File> genResFolder = mainInfo.getGeneratedResourceFolders()
+ String resFolderStart = new File(buildDir, "customRes").getAbsolutePath() + File.separatorChar
+ boolean found = false
+ for (File f : genResFolder) {
+ if (f.getAbsolutePath().startsWith(resFolderStart)) {
+ found = true
+ break
+ }
+ }
+
+ assertTrue("custom generated res folder check", found)
+ }
+ }
+
+ @Test
+ public void backwardsCompatible() throws Exception {
+ // ATTENTION Author and Reviewers - please make sure required changes to the build file
+ // are backwards compatible before updating this test.
+ assertThat(FileUtils.sha1(project.file("build.gradle")))
+ .isEqualTo("976b508cb69c150a810cd3b8b087376adaafa903")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InjectedDensityTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InjectedDensityTest.java
new file mode 100644
index 0000000..1f240a6
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InjectedDensityTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.build.gradle.integration.application;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp;
+import com.android.build.gradle.integration.common.truth.ApkSubject;
+import com.android.builder.model.AndroidProject;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+import com.google.common.truth.Expect;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Check that the built apk contains the correct resources.
+ *
+ * As specified by the build property injected by studio.
+ */
+public class InjectedDensityTest {
+
+ @ClassRule
+ public static GradleTestProject sProject =
+ GradleTestProject.builder()
+ .fromTestApp(new EmptyAndroidTestApp("com.example.app.densities")).create();
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @BeforeClass
+ public static void setup() throws IOException {
+
+ String buildScript = sProject.getGradleBuildscript() + "\n"
+ + "apply plugin: 'com.android.application'\n"
+ + "android {\n"
+ + " compileSdkVersion rootProject.latestCompileSdk\n"
+ + " buildToolsVersion = rootProject.buildToolsVersion\n"
+ + " defaultConfig {\n"
+ + " minSdkVersion 15"
+ + " }\n"
+ + " dependencies {\n"
+ + " compile 'com.android.support:appcompat-v7:21.0.3'\n"
+ + " }"
+ + "}";
+
+ Files.write(buildScript, sProject.getBuildFile(), Charsets.UTF_8);
+
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ sProject = null;
+ }
+
+ @Test
+ public void buildNormallyThenFiltered() throws IOException, ProcessException {
+ sProject.execute("clean");
+ checkFilteredBuild();
+ checkFullBuild();
+ }
+
+ private void checkFullBuild() throws IOException, ProcessException {
+ sProject.execute("assembleDebug");
+ ApkSubject debug = expect.about(ApkSubject.FACTORY).that(sProject.getApk("debug"));
+ debug.containsResource("drawable-xxxhdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.containsResource("drawable-xxhdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.containsResource("drawable-xhdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.containsResource("drawable-hdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.containsResource("drawable-mdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ }
+
+ private void checkFilteredBuild() throws IOException, ProcessException {
+ sProject.execute(
+ ImmutableList.of("-P" + AndroidProject.PROPERTY_BUILD_DENSITY + "=xxhdpi"),
+ "assembleDebug");
+ ApkSubject debug = expect.about(ApkSubject.FACTORY).that(sProject.getApk("debug"));
+ debug.doesNotContainResource("drawable-xxxhdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.containsResource("drawable-xxhdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.doesNotContainResource("drawable-xhdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.doesNotContainResource("drawable-hdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ debug.doesNotContainResource("drawable-mdpi-v4/abc_ic_clear_mtrl_alpha.png");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InstantUnitTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InstantUnitTest.java
new file mode 100644
index 0000000..b768066
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InstantUnitTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.build.gradle.integration.application;
+
+import com.android.build.gradle.integration.common.category.DeviceTests;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+public class InstantUnitTest {
+
+ @ClassRule
+ public static GradleTestProject sProject = GradleTestProject.builder()
+ .fromTestProject("instant-unit-tests")
+ .create();
+
+ @Test
+ public void checkInstantUnitTestsBuild() {
+ sProject.execute("clean", "assembleDebugAndroidTest");
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void runTestsOnDevice() {
+ sProject.execute("clean");
+ sProject.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InvalidResourceDirectoryTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InvalidResourceDirectoryTest.groovy
new file mode 100644
index 0000000..5071c13
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/InvalidResourceDirectoryTest.groovy
@@ -0,0 +1,81 @@
+/*
+ * 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.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.google.common.base.Throwables
+import groovy.transform.CompileStatic
+import org.gradle.tooling.BuildException
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+ at CompileStatic
+class InvalidResourceDirectoryTest {
+
+ public static AndroidTestApp app = HelloWorldApp.noBuildFile()
+
+ static {
+ app.addFile(new TestSourceFile(INVALID_LAYOUT_FOLDER, "main.xml",
+ """<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="hello invalid layout world!"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+"""));
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder().fromTestApp(app).create()
+
+ public static final String INVALID_LAYOUT_FOLDER = "src/main/res/layout-hdpi-land"
+
+
+ @BeforeClass
+ public static void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+"""
+ }
+
+ @Test
+ public void "check build failure on invalid resource directory"() {
+ try {
+ project.execute("assembleRelease");
+ } catch (BuildException e) {
+ Throwable rootCause = Throwables.getRootCause(e);
+ assert rootCause.message.contains(
+ new File(project.testDir, INVALID_LAYOUT_FOLDER).absolutePath)
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JackTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JackTest.groovy
new file mode 100644
index 0000000..c5fa51c
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JackTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.truth.TruthHelper
+import com.google.common.collect.ImmutableList
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+/**
+ * Test Jack integration.
+ */
+ at CompileStatic
+class JackTest {
+ private final static List<String> JACK_OPTIONS = ImmutableList.of(
+ "-PCUSTOM_JACK=1",
+ "-PCUSTOM_BUILDTOOLS=" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION)
+
+ @ClassRule
+ static public GradleTestProject basic = GradleTestProject.builder()
+ .withName("basic")
+ .fromTestProject("basic")
+ .create()
+
+ @ClassRule
+ static public GradleTestProject minify = GradleTestProject.builder()
+ .withName("minify")
+ .fromTestProject("minify")
+ .create()
+
+ @ClassRule
+ static public GradleTestProject multiDex = GradleTestProject.builder()
+ .withName("multiDex")
+ .fromTestProject("multiDex")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ GradleTestProject.assumeBuildToolsAtLeast(21, 1, 0)
+ basic.execute(JACK_OPTIONS, "clean", "assembleDebug")
+ minify.execute(JACK_OPTIONS, "clean", "assembleDebug")
+ multiDex.execute(JACK_OPTIONS, "clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ basic = null
+ minify = null
+ multiDex = null
+ }
+
+ @Test
+ void assembleDebug() {
+ // Empty test to ensure setup succeeds if DeviceTests are not run.
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void "basic connectedCheck"() {
+ basic.executeConnectedCheck(JACK_OPTIONS)
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void "multiDex connectedCheck"() {
+ multiDex.executeConnectedCheck(JACK_OPTIONS)
+ }
+
+ @Test
+ void "check classes.dex is packaged"() {
+ TruthHelper.assertThatApk(basic.getApk("debug")).contains("classes.dex");
+ }
+
+ @Test
+ void "minify unitTests with Javac"() {
+ minify.execute("testMinified")
+ }
+
+ @Test
+ void "minify unitTests with Jack"() {
+ minify.execute(JACK_OPTIONS, "clean", "testMinified")
+
+ // Make sure javac was run.
+ File classesDir = new File(minify.testDir, "/build/intermediates/classes/minified")
+ assert classesDir.exists()
+
+ // Make sure jack was not run.
+ File jillDir = new File(minify.testDir, "/build/intermediates/jill")
+ assert !jillDir.exists()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoOnlySubprojectBuildScriptDependency.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoOnlySubprojectBuildScriptDependency.java
new file mode 100644
index 0000000..cfca908
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoOnlySubprojectBuildScriptDependency.java
@@ -0,0 +1,39 @@
+/*
+ * 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.build.gradle.integration.application;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import org.junit.Rule;
+import org.junit.Test;
+
+
+/**
+ * Check Jacoco doesn't get broken with annotation processor that dumps .java files in the
+ * compiler out folder.
+ */
+public class JacocoOnlySubprojectBuildScriptDependency {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("jacocoOnlySubprojectBuildScriptDependency")
+ .withoutNdk()
+ .create();
+
+ @Test
+ public void build() {
+ project.execute("transformClassesWithJacocoForDebug");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoTransformTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoTransformTest.java
new file mode 100644
index 0000000..103de69
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoTransformTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.build.gradle.integration.application;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification;
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class JacocoTransformTest {
+
+ private static final String CLASS_NAME = "com/example/B";
+ private static final String CLASS_FULL_TYPE = "L" + CLASS_NAME + ";";
+ private static final String CLASS_SRC_LOCATION = "src/main/java/" + CLASS_NAME + ".java";
+
+ private static final String CLASS_CONTENT = "\n"
+ + "package com.example;\n"
+ + "public class B { }";
+
+ private static final AndroidTestApp TEST_APP =
+ HelloWorldApp.forPlugin("com.android.application");
+
+ @Rule
+ public final GradleTestProject mProject =
+ GradleTestProject.builder().fromTestApp(TEST_APP).create();
+
+ @Before
+ public void enableCodeCoverage() throws IOException {
+ Files.append("\nandroid.buildTypes.debug.testCoverageEnabled true",
+ mProject.getBuildFile(), Charsets.UTF_8);
+ }
+
+ @Test
+ public void addAndRemoveClass() throws Exception {
+
+ mProject.execute("assembleDebug");
+ assertThatApk(mProject.getApk("debug")).doesNotContainClass(CLASS_FULL_TYPE);
+
+ TemporaryProjectModification.doTest(mProject,
+ new TemporaryProjectModification.ModifiedProjectTest() {
+ @Override
+ public void runTest(TemporaryProjectModification modifiedProject)
+ throws Exception {
+ modifiedProject.addFile(CLASS_SRC_LOCATION, CLASS_CONTENT);
+ mProject.execute("assembleDebug");
+ assertThatApk(mProject.getApk("debug")).containsClass(CLASS_FULL_TYPE);
+ }
+ });
+ mProject.execute("assembleDebug");
+ assertThatApk(mProject.getApk("debug")).doesNotContainClass(CLASS_FULL_TYPE);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoWithButterKnifeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoWithButterKnifeTest.groovy
new file mode 100644
index 0000000..a8742fc
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JacocoWithButterKnifeTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Check Jacoco doesn't get broken with annotation processor that dumps .java files in the
+ * compiler out folder.
+ */
+ at CompileStatic
+class JacocoWithButterKnifeTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("jacocoWithButterKnife")
+ .withoutNdk()
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void build() {
+ project.execute("transformClassesWithJacocoForDebug")
+
+ File javaFile = FileUtils.join(project.getTestDir(),
+ "build",
+ "intermediates",
+ "classes",
+ "debug",
+ "com",
+ "test",
+ "jacoco",
+ "annotation",
+ "BindActivity\$\$ViewBinder.java")
+ assertThat(javaFile).exists();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JarJarTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JarJarTest.groovy
new file mode 100644
index 0000000..8b7ebbc
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/JarJarTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * 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.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.google.common.collect.Iterators;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static com.android.builder.core.BuilderConstants.DEBUG;
+import static org.junit.Assert.assertEquals;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidArtifactOutput;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Test for the jarjar integration.
+ */
+public class JarJarTest {
+
+ static AndroidProject model
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject(GradleTestProject.USE_JACK ? "jarjarWithJack" : "jarjarIntegration")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check repackaged gson library"() {
+ Collection<Variant> variants = model.getVariants()
+ assertEquals("Variant Count", 2, variants.size())
+
+ // get the main artifact of the debug artifact
+ Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
+ assertNotNull("debug Variant null-check", debugVariant)
+ AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
+ assertNotNull("Debug main info null-check", debugMainArtifact)
+
+ // get the outputs.
+ Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
+ assertNotNull(debugOutputs)
+ assertEquals(1, debugOutputs.size())
+
+ // make sure the Gson library has been renamed and the original one is not present.
+ File outputFile = Iterators.getOnlyElement(debugOutputs.iterator()).mainOutputFile.
+ getOutputFile()
+ assertThatApk(outputFile).containsClass("Lcom/google/repacked/gson/Gson;");
+ assertThatApk(outputFile).doesNotContainClass("Lcom/google/gson/Gson;")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/LintVitalTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/LintVitalTest.groovy
new file mode 100644
index 0000000..c5c64f9
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/LintVitalTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.google.common.base.Throwables
+import org.gradle.tooling.BuildException
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static org.junit.Assert.fail
+
+/**
+ * Checks if fatal lint errors stop the release build.
+ */
+class LintVitalTest {
+
+ public static final AndroidTestApp helloWorldApp = HelloWorldApp.noBuildFile()
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(helloWorldApp)
+ .create()
+
+ @Before
+ public void setUp() {
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+"""
+
+ def manifest = project.file("src/main/AndroidManifest.xml")
+ def manifestLines = manifest.readLines()
+
+ int packageLineNumber = manifestLines.findIndexOf { it =~ /package="/ }
+ manifestLines.add(packageLineNumber + 1, 'android:debuggable="true"')
+ manifest.write(manifestLines.join(System.getProperty("line.separator")))
+ }
+
+ @Test
+ public void "Fatal lint checks stop the build"() {
+ try {
+ project.execute("assembleRelease")
+ fail("Release build should fail with fatal lint errors.")
+ } catch (BuildException e) {
+ assert Throwables.getRootCause(e).message.contains("fatal errors")
+ }
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ManifestMergingTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ManifestMergingTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ManifestMergingTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ManifestMergingTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MaxSdkVersionTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MaxSdkVersionTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MaxSdkVersionTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MaxSdkVersionTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MessageRewriteTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MessageRewriteTest.groovy
new file mode 100644
index 0000000..08a43f1
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MessageRewriteTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.builder.model.AndroidProject
+import com.android.utils.FileUtils
+import com.android.utils.SdkUtils
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Tests the error message rewriting logic.
+ */
+ at CompileStatic
+class MessageRewriteTest {
+
+ private static List<String> INVOKED_FROM_IDE_ARGS =
+ Collections.singletonList("-P" + AndroidProject.PROPERTY_INVOKED_FROM_IDE + "=true")
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("flavored")
+ .withoutNdk()
+ .captureStdOut(true)
+ .captureStdErr(true)
+ .create()
+
+ @BeforeClass
+ public static void assemble() {
+ project.execute('assembleDebug')
+ }
+
+ @Test
+ public void "invalid layout file"() {
+ TemporaryProjectModification.doTest(project) {
+ it.replaceInFile("src/main/res/layout/main.xml", "</LinearLayout>", "");
+ project.getStderr().reset()
+ project.executeExpectingFailure(INVOKED_FROM_IDE_ARGS, 'assembleF1Debug')
+ String err = project.getStderr().toString()
+ assertThat(err).contains(SdkUtils.escapePropertyValue(FileUtils.join(
+ "src", "main", "res", "layout", "main.xml")))
+ }
+
+ project.execute('assembleDebug')
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MigratedTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MigratedTest.groovy
new file mode 100644
index 0000000..b9552f9
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MigratedTest.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.build.gradle.integration.common.utils.SourceProviderHelper
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.ProductFlavorContainer
+import com.android.builder.model.SourceProviderContainer
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.builder.core.VariantType.ANDROID_TEST
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+
+/**
+ * Assemble tests for migrated.
+ */
+ at CompileStatic
+class MigratedTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("migrated")
+ .create()
+
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check model reflect migrated source providers"() throws Exception {
+ File projectDir = project.getTestDir()
+
+ assertFalse("Library Project", model.isLibrary())
+
+ ProductFlavorContainer defaultConfig = model.getDefaultConfig()
+
+ new SourceProviderHelper(model.getName(), projectDir,
+ "main", defaultConfig.getSourceProvider())
+ .setJavaDir("src")
+ .setResourcesDir("src")
+ .setAidlDir("src")
+ .setRenderscriptDir("src")
+ .setResDir("res")
+ .setAssetsDir("assets")
+ .setManifestFile("AndroidManifest.xml")
+ .test()
+
+ SourceProviderContainer testSourceProviderContainer = ModelHelper.getSourceProviderContainer(
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_ANDROID_TEST)
+ assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer)
+
+ new SourceProviderHelper(model.getName(), projectDir,
+ ANDROID_TEST.prefix, testSourceProviderContainer.getSourceProvider())
+ .setJavaDir("tests/java")
+ .setResourcesDir("tests/resources")
+ .setAidlDir("tests/aidl")
+ .setJniDir("tests/jni")
+ .setRenderscriptDir("tests/rs")
+ .setResDir("tests/res")
+ .setAssetsDir("tests/assets")
+ .setManifestFile("tests/AndroidManifest.xml")
+ .test()
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyLibAndAppWithJavaResTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyLibAndAppWithJavaResTest.groovy
new file mode 100644
index 0000000..31e5895
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyLibAndAppWithJavaResTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.runner.FilterableParameterized
+import com.android.build.gradle.integration.common.truth.ApkSubject
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assume.assumeFalse
+/**
+ * Tests that ensure that java resources files accessed with a relative or absolute path are
+ * packaged correctly.
+ */
+ at CompileStatic
+ at RunWith(FilterableParameterized)
+public class MinifyLibAndAppWithJavaResTest {
+
+ @Parameterized.Parameters(name = "useProguard = {0}")
+ public static Collection<Object[]> data() {
+ return [
+ [true] as Object[],
+ [false] as Object[],
+ ]
+ }
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("minifyLibWithJavaRes")
+ .create()
+
+ @Parameterized.Parameter(0)
+ public boolean useProguard
+
+ @Before
+ void setUp() {
+ project.getSubproject("app").buildFile << "android.buildTypes.release.useProguard $useProguard"
+ }
+
+ @Test
+ void testDebugPackaging() {
+ project.execute(":app:assembleDebug")
+ File debugApk = project.getSubproject("app").getApk("debug")
+ assertNotNull(debugApk)
+ ApkSubject apkSubject = assertThatApk(debugApk);
+ // check that resources with relative path lookup code have a matching obfuscated package
+ // name.
+ apkSubject.contains("com/android/tests/util/resources.properties")
+ apkSubject.contains("com/android/tests/other/resources.properties")
+ // check that resources with absolute path lookup remain in the original package name.
+ apkSubject.contains("com/android/tests/util/another.properties")
+ apkSubject.contains("com/android/tests/other/some.xml")
+ apkSubject.contains("com/android/tests/other/another.properties")
+ }
+
+ @Test
+ void testReleasePackaging() {
+ assumeFalse("Ignore until Jack fixed proguard confusion", GradleTestProject.USE_JACK)
+
+ project.execute(":app:assembleRelease")
+ File releaseApk = project.getSubproject("app").getApk("release")
+ assertNotNull(releaseApk)
+ ApkSubject apkSubject = assertThatApk(releaseApk);
+ // check that resources with absolute path lookup remain in the original package name.
+ apkSubject.contains("com/android/tests/util/another.properties")
+ apkSubject.contains("com/android/tests/other/some.xml")
+ apkSubject.contains("com/android/tests/other/another.properties")
+
+ if (useProguard) {
+ // check that resources with relative path lookup code have a matching obfuscated package
+ // name.
+ apkSubject.contains("com/android/tests/b/resources.properties")
+ apkSubject.contains("com/android/tests/a/resources.properties")
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyTest.groovy
new file mode 100644
index 0000000..aa31c5b
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MinifyTest.groovy
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.annotations.NonNull
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.truth.TruthHelper
+import com.android.build.gradle.integration.common.truth.ZipFileSubject
+import com.android.builder.model.AndroidProject
+import com.google.common.collect.Sets
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldNode
+
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+
+import static com.google.common.truth.Truth.assertThat
+/**
+ * Assemble tests for minify.
+ */
+ at CompileStatic
+class MinifyTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("minify")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleMinified",
+ "assembleMinifiedAndroidTest", "jarDebugClasses")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+
+ @Test
+ void 'App APK is minified'() throws Exception {
+ File jarFile = project.file(
+ "build/" +
+ "$AndroidProject.FD_INTERMEDIATES/" +
+ "transforms/" +
+ "proguard/" +
+ "minified/" +
+ "jars/3/1f/main.jar")
+
+ Set<String> minifiedList = getZipEntries(jarFile);
+
+ // Ignore JaCoCo stuff.
+ minifiedList.removeAll { it =~ /org.jacoco/ }
+ minifiedList.removeAll(["about.html", "com/vladium/emma/rt/RT.class"])
+
+ assertThat(minifiedList).containsExactly(
+ "com/android/tests/basic/a.class", // Renamed StringProvider.
+ "com/android/tests/basic/Main.class",
+ "com/android/tests/basic/IndirectlyReferencedClass.class", // Kept by ProGuard rules.
+ // No entry for UnusedClass, it gets removed.
+ )
+ }
+
+ @Test
+ void 'Test APK is not minified, but mappings are applied'() throws Exception {
+ File jarFile = project.file(
+ "build/" +
+ "$AndroidProject.FD_INTERMEDIATES/" +
+ "transforms/" +
+ "proguard/" +
+ "androidTest/" +
+ "minified/" +
+ "jars/3/1f/main.jar")
+ Set<String> minifiedList = getZipEntries(jarFile)
+
+ def testClassFiles = minifiedList.findAll { !it.startsWith("org/hamcrest") }
+
+ assertThat(testClassFiles).containsExactly(
+ "com/android/tests/basic/MainTest.class",
+ "com/android/tests/basic/UnusedTestClass.class",
+ "com/android/tests/basic/UsedTestClass.class",
+ "com/android/tests/basic/test/BuildConfig.class",
+ )
+
+ checkClassFile(jarFile)
+ }
+
+ @NonNull
+ private static Set<String> getZipEntries(@NonNull File file) {
+ Set<String> entries = Sets.newHashSet();
+ ZipFile zipFile = new ZipFile(file);
+ try {
+ Enumeration<? extends ZipEntry> zipFileEntries = zipFile.entries();
+ while (zipFileEntries.hasMoreElements()) {
+ entries.add(zipFileEntries.nextElement().getName());
+ }
+ } finally {
+ zipFile.close();
+ }
+
+ return entries;
+ }
+
+ @Test
+ void 'Test classes.jar is present for non Jack enabled variants'() throws Exception {
+ ZipFileSubject classes = TruthHelper.assertThatZip(project.file(
+ "build/$AndroidProject.FD_INTERMEDIATES/packaged/debug/classes.jar"))
+
+ classes.contains("com/android/tests/basic/Main.class")
+ classes.doesNotContain("com/android/tests/basic/MainTest.class")
+ }
+
+ @CompileDynamic
+ static def checkClassFile(@NonNull File jarFile) {
+ ZipFile zipFile = new ZipFile(jarFile);
+ try {
+ ZipEntry entry = zipFile.getEntry("com/android/tests/basic/MainTest.class")
+ assertThat(entry).named("MainTest.class entry").isNotNull()
+ def classReader = new ClassReader(zipFile.getInputStream(entry))
+ def mainTestClassNode = new ClassNode(Opcodes.ASM5)
+ classReader.accept(mainTestClassNode, 0)
+
+ // Make sure bytecode got rewritten to point to renamed classes.
+ FieldNode stringProviderField = mainTestClassNode.fields.find { it.name == "stringProvider" }
+ assert Type.getType(stringProviderField.desc).className == "com.android.tests.basic.a"
+
+ } finally {
+ zipFile.close();
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ModelTest.groovy
new file mode 100644
index 0000000..4cb1ecd
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ModelTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.SyncIssue
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+/**
+ * General Model tests
+ */
+ at CompileStatic
+class ModelTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+"""
+ }
+
+ @Test
+ public void unresolvedDependencies() {
+ project.getBuildFile() << """
+dependencies {
+ compile 'foo:bar:1.2.3'
+}
+"""
+ AndroidProject model = project.getSingleModelIgnoringSyncIssues()
+ assertThat(model).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_UNRESOLVED_DEPENDENCY,
+ 'foo:bar:1.2.3')
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiDexTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiDexTest.groovy
new file mode 100644
index 0000000..dfe476a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiDexTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.runner.FilterableParameterized
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
+/**
+ * Assemble tests for multiDex.
+ */
+ at CompileStatic
+class MultiDexTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("multiDex")
+ .withHeap("2048M")
+ .create()
+
+ @Test
+ void "check APKs"() {
+ project.execute("assembleDebug", "assembleAndroidTest")
+
+ // manually inspect the apk to ensure that the classes.dex that was created is the same
+ // one in the apk. This tests that the packaging didn't rename the multiple dex files
+ // around when we packaged them.
+ File classesDex = project.file(
+ "build/" + FD_INTERMEDIATES +
+ "/transforms/dex/" +
+ "ics/debug/" +
+ "folders/1000/1f/main/" +
+ "classes.dex")
+ File apk = project.getApk("ics", "debug")
+
+ assertThatZip(apk).containsFileWithContent("classes.dex", Files.toByteArray(classesDex))
+
+ // both test apk should contain a class from Junit
+ assertThatApk(project.getTestApk("ics", "debug")).containsClass("Lorg/junit/Assert;")
+ assertThatApk(project.getTestApk("lollipop", "debug")).containsClass("Lorg/junit/Assert;")
+ assertThatApk(project.getApk("ics", "debug")).containsClass("Landroid/support/multidex/MultiDexApplication;")
+ assertThatApk(project.getTestApk("ics", "debug")).doesNotContainClass("Landroid/support/multidex/MultiDexApplication;")
+ assertThatApk(project.getApk("lollipop", "debug")).doesNotContainClass("Landroid/support/multidex/MultiDexApplication;")
+ assertThatApk(project.getTestApk("lollipop", "debug")).doesNotContainClass("Landroid/support/multidex/MultiDexApplication;")
+ }
+
+ @Test
+ void "check multidex without obfuscate"() {
+ project.execute("assembleIcsProguard")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiProjectTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiProjectTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiProjectTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiProjectTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiresTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiresTest.groovy
new file mode 100644
index 0000000..d694f23
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/MultiresTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for multires.
+ */
+ at CompileStatic
+class MultiresTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("multires")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NdkJniPureSplitLibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NdkJniPureSplitLibTest.groovy
new file mode 100644
index 0000000..d52ffb0
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NdkJniPureSplitLibTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * Assemble tests for ndkJniPureSplitLib.
+ */
+ at CompileStatic
+class NdkJniPureSplitLibTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("ndkJniPureSplitLib")
+ .addGradleProperties("android.useDeprecatedNdk=true")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ project.execute("clean", ":app:assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check version code"() {
+ GradleTestProject app = project.getSubproject("app")
+ assertThatApk(app.getApk("free", "debug_armeabi-v7a")).hasVersionCode(123)
+ assertThatApk(app.getApk("free", "debug_mips")).hasVersionCode(123)
+ assertThatApk(app.getApk("free", "debug_x86")).hasVersionCode(123)
+ assertThatApk(app.getApk("paid", "debug_armeabi-v7a")).hasVersionCode(123)
+ assertThatApk(app.getApk("paid", "debug_mips")).hasVersionCode(123)
+ assertThatApk(app.getApk("paid", "debug_x86")).hasVersionCode(123)
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NoCruncherTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NoCruncherTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NoCruncherTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/NoCruncherTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryTest.groovy
new file mode 100644
index 0000000..38817e4
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryTest.groovy
@@ -0,0 +1,133 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.SyncIssue
+import com.android.repository.testframework.FakeProgressIndicator
+import com.android.sdklib.IAndroidTarget
+import com.android.sdklib.repositoryv2.AndroidSdkHandler
+import com.android.sdklib.repositoryv2.targets.AndroidTargetManager
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.SdkConstants.FN_FRAMEWORK_LIBRARY
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+/**
+ * Test for the new useLibrary mechanism
+ */
+ at CompileStatic
+class OptionalLibraryTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @After
+ void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "test unknown useLibrary trigger sync issue"() {
+ project.getBuildFile() << """
+ apply plugin: 'com.android.application'
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ useLibrary 'foo'
+
+ }
+ """.stripIndent()
+
+ AndroidProject project = project.getSingleModelIgnoringSyncIssues()
+
+ assertThat(project).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_OPTIONAL_LIB_NOT_FOUND,
+ 'foo');
+ }
+
+ @Test
+ void "test using optional library"() {
+ project.getBuildFile() << """
+ apply plugin: 'com.android.application'
+
+ android {
+ compileSdkVersion 23
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ useLibrary 'org.apache.http.legacy'
+
+ }
+ """.stripIndent()
+
+ AndroidProject project = project.getSingleModel()
+
+ // get the SDK folder
+ File sdkLocation = new File(System.getenv("ANDROID_HOME"))
+ FakeProgressIndicator progress = new FakeProgressIndicator()
+ AndroidTargetManager targetMgr = AndroidSdkHandler.getInstance(sdkLocation).
+ getAndroidTargetManager(progress)
+ IAndroidTarget target = targetMgr.getTargetFromHashString('android-23', progress)
+
+ File targetLocation = new File(target.getLocation())
+
+ // the files that the bootclasspath should contain.
+ File androidJar = new File(targetLocation, FN_FRAMEWORK_LIBRARY)
+ File httpJar = new File(targetLocation, "optional/org.apache.http.legacy.jar")
+ assertThat(project.getBootClasspath()).containsExactly(
+ androidJar.getAbsolutePath(),
+ httpJar.getAbsolutePath())
+
+ // for safety, let's make sure these files actually exists.
+ assertThat(androidJar).isFile()
+ assertThat(httpJar).isFile()
+ }
+
+ @Test
+ void "test not using optional library"() {
+ project.getBuildFile() << """
+ apply plugin: 'com.android.application'
+
+ android {
+ compileSdkVersion 23
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+ """.stripIndent()
+
+ AndroidProject project = project.getSingleModel()
+
+ // get the SDK folder
+ File sdkLocation = new File(System.getenv("ANDROID_HOME"))
+ FakeProgressIndicator progress = new FakeProgressIndicator()
+ AndroidTargetManager targetMgr = AndroidSdkHandler.getInstance(sdkLocation).
+ getAndroidTargetManager(progress)
+ IAndroidTarget target = targetMgr.getTargetFromHashString('android-23', progress)
+
+ File targetLocation = new File(target.getLocation())
+
+ assertThat(project.getBootClasspath()).containsExactly(
+ new File(targetLocation, FN_FRAMEWORK_LIBRARY).getAbsolutePath())
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryWithProguardTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryWithProguardTest.groovy
new file mode 100644
index 0000000..cd8dbb3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OptionalLibraryWithProguardTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.runner.FilterableParameterized
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test for the new optional library mechanism when a library dependency uses sone now optional
+ * classes and runs proguard, in which case proguard needs to see the optional classes.
+ */
+ at RunWith(FilterableParameterized)
+ at CompileStatic
+class OptionalLibraryWithProguardTest {
+
+ @Parameterized.Parameters(name = "useProguard = {0}")
+ public static Collection<Object[]> data() {
+ return [
+ [true] as Object[],
+ [false] as Object[],
+ ]
+ }
+
+ @Parameterized.Parameter(0)
+ public boolean useProguard
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("optionalLibInLibWithProguard")
+ .create()
+
+ @Before
+ public void chooseShrinker() throws Exception {
+ project.getSubproject("app").buildFile << "android.buildTypes.debug.useProguard = $useProguard"
+ }
+
+ @Test
+ void "test that proguard compiles with optional classes"() {
+ project.execute("clean", "app:assembleDebug")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OutputRenamingTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OutputRenamingTest.groovy
new file mode 100644
index 0000000..43acc94
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/OutputRenamingTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static com.android.builder.core.BuilderConstants.RELEASE
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Assemble tests for class densitySplitInL
+ */
+class OutputRenamingTest {
+
+ static AndroidProject model
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("densitySplitInL")
+ .create()
+
+ @BeforeClass
+ static void setup() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ project.getBuildFile() << """android {
+applicationVariants.all { variant ->
+ // Custom APK names (do not do this for 'dev' build type)
+ println variant.buildType.name
+ def baseFileName = "project-\${variant.flavorName}-\${variant.versionCode}-\${variant.buildType.name}"
+ variant.outputs.each { output ->
+ // Unaligned (name it 'unsigned' because we remove the signature from the apk later)
+ def unalignedFileName = "\${baseFileName}-unsigned.apk"
+ println "renaming from \${output.packageApplication.outputFile} to \${unalignedFileName}"
+ File parent = output.packageApplication.outputFile.parentFile
+ output.packageApplication.outputFile = new File(parent, unalignedFileName)
+ // Signed
+ def signedFileName = "\${baseFileName}-signed.apk"
+ parent = output.outputFile.parentFile
+ output.outputFile = new File(parent, signedFileName)
+ }
+ }
+ }"""
+ model = project.executeAndReturnModel("clean", "assemble")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check split outputs"() throws Exception {
+ Collection<Variant> variants = model.getVariants()
+ assertEquals("Variant Count", 2 , variants.size())
+
+ assertFileRenaming(DEBUG)
+ assertFileRenaming(RELEASE)
+ }
+
+ private assertFileRenaming(String buildType) {
+ Variant variant = ModelHelper.getVariant(model.getVariants(), buildType)
+ assertNotNull("Variant null-check", variant)
+ AndroidArtifact mainArtifact = variant.getMainArtifact()
+ assertNotNull("main info null-check", mainArtifact)
+
+ // get the outputs.
+ Collection<AndroidArtifactOutput> outputs = mainArtifact.getOutputs()
+ assertNotNull(outputs)
+
+ assertEquals(1, outputs.size())
+ AndroidArtifactOutput output = outputs.iterator().next()
+ assertEquals(5, output.getOutputs().size())
+
+ String expectedFileName = "project--12-"+ buildType.toLowerCase() + "-signed.apk"
+ File mainOutputFile = output.getMainOutputFile().getOutputFile();
+ assertEquals(expectedFileName, mainOutputFile.name)
+ assertTrue(mainOutputFile.exists());
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay1Test.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay1Test.groovy
new file mode 100644
index 0000000..10eb455
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay1Test.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ImageHelper
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for overlay1.
+ */
+ at CompileStatic
+class Overlay1Test {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("overlay1")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check image color"() {
+ int GREEN = ImageHelper.GREEN
+ File drawableOutput = project.
+ file("build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/debug/drawable")
+ ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "type_overlay.png", GREEN)
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay2Test.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay2Test.groovy
new file mode 100644
index 0000000..3324b8f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay2Test.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ImageHelper
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for overlay2.
+ */
+ at CompileStatic
+class Overlay2Test {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("overlay2")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check image color"() {
+ int GREEN = ImageHelper.GREEN
+ File drawableOutput = project.file(
+ "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/one/debug/drawable")
+
+ ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "type_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "flavor_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "type_flavor_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "variant_type_flavor_overlay.png", GREEN)
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay3Test.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay3Test.groovy
new file mode 100644
index 0000000..4c0dbed
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/Overlay3Test.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ImageHelper
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for overlay3.
+ */
+ at CompileStatic
+class Overlay3Test {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("overlay3")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check image color"() {
+ int GREEN = ImageHelper.GREEN
+ int RED = ImageHelper.RED
+
+ File drawableOutput = project.file(
+ "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/freeBeta/debug/drawable")
+
+ ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "debug_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "beta_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "free_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "free_beta_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "free_beta_debug_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "free_normal_overlay.png", RED)
+
+ drawableOutput = project.file(
+ "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/freeNormal/debug/drawable")
+
+ ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "debug_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "beta_overlay.png", RED)
+ ImageHelper.checkImageColor(drawableOutput, "free_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "free_beta_overlay.png", RED)
+ ImageHelper.checkImageColor(drawableOutput, "free_beta_debug_overlay.png", RED)
+ ImageHelper.checkImageColor(drawableOutput, "free_normal_overlay.png", GREEN)
+
+ drawableOutput = project.file(
+ "build/" + AndroidProject.FD_INTERMEDIATES + "/res/merged/paidBeta/debug/drawable")
+
+ ImageHelper.checkImageColor(drawableOutput, "no_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "debug_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "beta_overlay.png", GREEN)
+ ImageHelper.checkImageColor(drawableOutput, "free_overlay.png", RED)
+ ImageHelper.checkImageColor(drawableOutput, "free_beta_overlay.png", RED)
+ ImageHelper.checkImageColor(drawableOutput, "free_beta_debug_overlay.png", RED)
+ ImageHelper.checkImageColor(drawableOutput, "free_normal_overlay.png", RED)
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PackagingOptionsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PackagingOptionsTest.groovy
new file mode 100644
index 0000000..7436f6e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PackagingOptionsTest.groovy
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+/**
+ * Assemble tests for packagingOptions.
+ *
+ * Creates two jar files and test various packaging options.
+ */
+ at CompileStatic
+class PackagingOptionsTest {
+
+ // Projects to create jar files.
+ private static AndroidTestApp jarProject1 = new EmptyAndroidTestApp()
+ static {
+ jarProject1.addFile(new TestSourceFile("", "build.gradle", "apply plugin: 'java'"))
+ jarProject1.addFile(new TestSourceFile("src/main/resources", "conflict.txt", "foo"))
+ }
+ private static AndroidTestApp jarProject2 = new EmptyAndroidTestApp()
+ static {
+ jarProject2.addFile(new TestSourceFile("", "build.gradle", "apply plugin: 'java'"))
+ jarProject2.addFile(new TestSourceFile("src/main/resources", "conflict.txt", "foo"))
+ // add an extra file so that jar1 is different from jar2.
+ jarProject2.addFile(new TestSourceFile("src/main/resources", "dummy2.txt", "bar"))
+ }
+
+ @ClassRule
+ public static GradleTestProject jar1 = GradleTestProject.builder()
+ .fromTestApp(jarProject1)
+ .withName("jar1")
+ .create()
+ @ClassRule
+ public static GradleTestProject jar2 = GradleTestProject.builder()
+ .fromTestApp(jarProject2)
+ .withName("jar2")
+ .create()
+
+ @BeforeClass
+ static void createJars() {
+ jar1.execute("assemble")
+ jar2.execute("assemble")
+ }
+
+
+ // Main test project.
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @Before
+ void setUp() {
+ Files.copy(jar1.file("build/libs/jar1.jar"), project.file("jar1.jar"))
+ Files.copy(jar2.file("build/libs/jar2.jar"), project.file("jar2.jar"))
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+"""
+
+ }
+
+ @Test
+ void "check pickFirst"() {
+ project.getBuildFile() << """
+android {
+ packagingOptions {
+ pickFirst 'conflict.txt'
+ }
+}
+
+dependencies {
+ compile files('jar1.jar')
+ compile files('jar2.jar')
+}
+"""
+ project.execute("clean", "assembleDebug")
+ assertThatZip(project.getApk("debug")).contains("conflict.txt")
+ }
+
+ @Test
+ void "check exclude on jars"() {
+ project.getBuildFile() << """
+android {
+ packagingOptions {
+ exclude 'conflict.txt'
+ }
+}
+
+dependencies {
+ compile files('jar1.jar')
+ compile files('jar2.jar')
+}
+"""
+ project.execute("clean", "assembleDebug")
+ assertThatZip(project.getApk("debug")).doesNotContain("conflict.txt")
+ }
+
+ @Test
+ void "check exclude on direct files"() {
+ project.getBuildFile() << """
+android {
+ packagingOptions {
+ exclude 'conflict.txt'
+ }
+}
+"""
+ createFile('src/main/resources/conflict.txt')
+ project.execute("clean", "assembleDebug")
+ assertThatZip(project.getApk("debug")).doesNotContain('conflict.txt')
+ }
+
+ @Test
+ void "check merge on jar entries"() {
+ project.getBuildFile() << """
+android {
+ packagingOptions {
+ merge 'conflict.txt'
+ }
+}
+
+dependencies {
+ compile files('jar1.jar')
+ compile files('jar2.jar')
+}
+"""
+ project.execute("clean", "assembleDebug")
+
+ assertThatZip(project.getApk("debug")).containsFileWithContent("conflict.txt", "foofoo")
+ }
+
+ @Test
+ void "check merge on local res file"() {
+ project.getBuildFile() << """
+android {
+ packagingOptions {
+ // this will not be used since debug will override the main one.
+ merge 'file.txt'
+ }
+}
+"""
+ createFile('src/main/resources/file.txt') << "main"
+ createFile('src/debug/resources/file.txt') << "debug"
+ project.execute("clean", "assembleDebug")
+ assertThatZip(project.getApk("debug")).containsFileWithContent("file.txt", "debug")
+ }
+
+ @Test
+ void "check merge on a direct file and a jar entry"() {
+ project.getBuildFile() << """
+dependencies {
+ compile files('jar1.jar')
+}
+"""
+ createFile('src/main/resources/conflict.txt') << "project-foo"
+ project.execute("clean", "assembleDebug")
+ // we expect to only see the one in src/main because it overrides the dependency one.
+ assertThatZip(project.getApk("debug")).containsFileWithContent("conflict.txt", "project-foo")
+ }
+
+ /**
+ * Create a new empty file including its directories.
+ */
+ private File createFile(String filename) {
+ File newFile = project.file(filename)
+ newFile.getParentFile().mkdirs()
+ newFile.createNewFile()
+ assertThat(newFile).exists()
+ return newFile
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ParentLibsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ParentLibsTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ParentLibsTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ParentLibsTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PkgOverrideTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PkgOverrideTest.groovy
new file mode 100644
index 0000000..5b54bcd
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PkgOverrideTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for pkgOverride.
+ */
+ at CompileStatic
+class PkgOverrideTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("pkgOverride")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PlaceholderInLibsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PlaceholderInLibsTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PlaceholderInLibsTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PlaceholderInLibsTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PrivateResourceTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PrivateResourceTest.groovy
new file mode 100644
index 0000000..56dee89
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PrivateResourceTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Assemble tests for privateResources.
+ */
+ at CompileStatic
+class PrivateResourceTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("privateResources")
+ .create()
+
+ @BeforeClass
+ static void setup() {
+ project.execute("clean", "assemble");
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check private resources"() {
+ String expected = """\
+string mylib_app_name
+string mylib_public_string
+string mylib_shared_name
+id mylib_shared_name
+"""
+ assertThatZip(project.getSubproject('mylibrary').getAar("release")).containsFileWithContent('public.txt', expected);
+ assertThatZip(project.getSubproject('mylibrary').getAar("debug")).containsFileWithContent('public.txt', expected);
+
+ // No public resources: file should exist but be empty
+ assertThatZip(project.getSubproject('mylibrary2').getAar("debug")).containsFileWithContent('public.txt', "");
+ assertThatZip(project.getSubproject('mylibrary2').getAar("release")).containsFileWithContent('public.txt', "");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PseudoLocalizationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PseudoLocalizationTest.groovy
new file mode 100644
index 0000000..ad60eed
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/PseudoLocalizationTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * Test for pseudolocalized.
+ */
+ at CompileStatic
+class PseudoLocalizationTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("pseudolocalized")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void testPseudolocalization() throws Exception {
+ assertThatApk(project.getApk("debug")).locales().containsAllOf("en-XA", "ar-XB")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenamedApkTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenamedApkTest.groovy
new file mode 100644
index 0000000..4ce4cce
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenamedApkTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Assemble tests for renamedApk.
+ */
+ at CompileStatic
+class RenamedApkTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("renamedApk")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void "check model reflects renamed apk"() throws Exception {
+ File projectDir = project.getTestDir()
+
+ Collection<Variant> variants = model.getVariants()
+ assertEquals("Variant Count", 2 , variants.size())
+
+ File buildDir = new File(projectDir, "build")
+
+ for (Variant variant : variants) {
+ AndroidArtifact mainInfo = variant.getMainArtifact()
+ assertNotNull(
+ "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
+ mainInfo)
+
+ AndroidArtifactOutput output = mainInfo.getOutputs().iterator().next()
+
+ assertEquals("Output file for " + variant.getName(),
+ new File(buildDir, variant.getName() + ".apk"),
+ output.getMainOutputFile().getOutputFile())
+ }
+ }
+
+ @Test
+ void "check renamed apk"() {
+ File debugApk = project.file("build/debug.apk")
+ assertTrue("Check output file: " + debugApk, debugApk.isFile())
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptNdkTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptNdkTest.groovy
new file mode 100644
index 0000000..bbbfc1c
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RenderscriptNdkTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.truth.TruthHelper
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Assemble tests for renderscript with NDK mode enabled.
+ */
+ at CompileStatic
+class RenderscriptNdkTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("renderscriptNdk")
+ .addGradleProperties("android.useDeprecatedNdk=true")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check packaged .so files"() {
+ assertThatZip(project.getApk("debug")).contains("lib/armeabi-v7a/librs.mono.so")
+ assertThatZip(project.getApk("debug")).contains("lib/armeabi-v7a/librenderscript.so")
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RepoTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RepoTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RepoTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RepoTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTest.groovy
new file mode 100644
index 0000000..e5a46ab
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTest.groovy
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.ClassField
+import com.android.builder.model.Variant
+import com.android.utils.FileUtils
+import com.google.common.collect.Maps
+import groovy.transform.CompileStatic
+import org.junit.Assert
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+
+/**
+ * Test for Res Values declared in build type, flavors, and variant and how they
+ * override each other
+ */
+ at CompileStatic
+class ResValueTest {
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ private static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ project.getBuildFile() << """
+ apply plugin: 'com.android.application'
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ resValue "string", "VALUE_DEFAULT", "1"
+ resValue "string", "VALUE_DEBUG", "1"
+ resValue "string", "VALUE_FLAVOR", "1"
+ resValue "string", "VALUE_VARIANT", "1"
+ }
+
+ buildTypes {
+ debug {
+ resValue "string", "VALUE_DEBUG", "100"
+ resValue "string", "VALUE_VARIANT", "100"
+ }
+ }
+
+ productFlavors {
+ flavor1 {
+ resValue "string", "VALUE_DEBUG", "10"
+ resValue "string", "VALUE_FLAVOR", "10"
+ resValue "string", "VALUE_VARIANT", "10"
+ }
+ flavor2 {
+ resValue "string", "VALUE_DEBUG", "20"
+ resValue "string", "VALUE_FLAVOR", "20"
+ resValue "string", "VALUE_VARIANT", "20"
+ }
+ }
+
+ applicationVariants.all { variant ->
+ if (variant.buildType.name == "debug") {
+ variant.resValue "string", "VALUE_VARIANT", "1000"
+ }
+ }
+ }
+ """.stripIndent()
+
+ model = project.executeAndReturnModel(
+ 'clean',
+ 'generateFlavor1DebugResValue',
+ 'generateFlavor1ReleaseResValue',
+ 'generateFlavor2DebugResValue',
+ 'generateFlavor2ReleaseResValue')
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void builFlavor1Debug() {
+ String expected =
+"""<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- Automatically generated file. DO NOT MODIFY -->
+
+ <!-- Values from the variant -->
+ <string name="VALUE_VARIANT" translatable="false">1000</string>
+ <!-- Values from build type: debug -->
+ <string name="VALUE_DEBUG" translatable="false">100</string>
+ <!-- Values from product flavor: flavor1 -->
+ <string name="VALUE_FLAVOR" translatable="false">10</string>
+ <!-- Values from default config. -->
+ <string name="VALUE_DEFAULT" translatable="false">1</string>
+
+</resources>"""
+ checkBuildConfig(expected, 'flavor1/debug')
+ }
+
+ @Test
+ void modelFlavor1Debug() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '10')
+ map.put('VALUE_DEBUG', '100')
+ map.put('VALUE_VARIANT', '1000')
+ checkVariant(model.getVariants(), 'flavor1Debug', map)
+ }
+
+ @Test
+ void buildFlavor2Debug() {
+ String expected =
+"""<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- Automatically generated file. DO NOT MODIFY -->
+
+ <!-- Values from the variant -->
+ <string name="VALUE_VARIANT" translatable="false">1000</string>
+ <!-- Values from build type: debug -->
+ <string name="VALUE_DEBUG" translatable="false">100</string>
+ <!-- Values from product flavor: flavor2 -->
+ <string name="VALUE_FLAVOR" translatable="false">20</string>
+ <!-- Values from default config. -->
+ <string name="VALUE_DEFAULT" translatable="false">1</string>
+
+</resources>"""
+ checkBuildConfig(expected, 'flavor2/debug')
+ }
+
+ @Test
+ void modelFlavor2Debug() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '20')
+ map.put('VALUE_DEBUG', '100')
+ map.put('VALUE_VARIANT', '1000')
+ checkVariant(model.getVariants(), 'flavor2Debug', map)
+ }
+
+ @Test
+ void buildFlavor1Release() {
+ String expected =
+"""<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- Automatically generated file. DO NOT MODIFY -->
+
+ <!-- Values from product flavor: flavor1 -->
+ <string name="VALUE_DEBUG" translatable="false">10</string>
+ <string name="VALUE_FLAVOR" translatable="false">10</string>
+ <string name="VALUE_VARIANT" translatable="false">10</string>
+ <!-- Values from default config. -->
+ <string name="VALUE_DEFAULT" translatable="false">1</string>
+
+</resources>"""
+ checkBuildConfig(expected, 'flavor1/release')
+ }
+
+ @Test
+ void modelFlavor1Release() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '10')
+ map.put('VALUE_DEBUG', '10')
+ map.put('VALUE_VARIANT', '10')
+ checkVariant(model.getVariants(), 'flavor1Release', map)
+ }
+
+ @Test
+ void buildFlavor2Release() {
+ String expected =
+"""<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- Automatically generated file. DO NOT MODIFY -->
+
+ <!-- Values from product flavor: flavor2 -->
+ <string name="VALUE_DEBUG" translatable="false">20</string>
+ <string name="VALUE_FLAVOR" translatable="false">20</string>
+ <string name="VALUE_VARIANT" translatable="false">20</string>
+ <!-- Values from default config. -->
+ <string name="VALUE_DEFAULT" translatable="false">1</string>
+
+</resources>"""
+ checkBuildConfig(expected, 'flavor2/release')
+ }
+
+ @Test
+ void modelFlavor2Release() {
+ Map<String, String> map = Maps.newHashMap()
+ map.put('VALUE_DEFAULT', '1')
+ map.put('VALUE_FLAVOR', '20')
+ map.put('VALUE_DEBUG', '20')
+ map.put('VALUE_VARIANT', '20')
+ checkVariant(model.getVariants(), 'flavor2Release', map)
+ }
+
+ private static void checkBuildConfig(@NonNull String expected, @NonNull String variantDir) {
+ File outputFile = new File(project.getTestDir(),
+ "build/generated/res/resValues/$variantDir/values/generated.xml")
+ assertThat(outputFile).isFile();
+ assertThat(outputFile).contentWithUnixLineSeparatorsIsExactly(expected);
+ }
+
+ private static void checkVariant(
+ @NonNull Collection<Variant> variants,
+ @NonNull String variantName,
+ @Nullable Map<String, String> valueMap) {
+ Variant variant = ModelHelper.findVariantByName(variants, variantName)
+ assertNotNull("${variantName} variant null-check", variant)
+
+ AndroidArtifact artifact = variant.getMainArtifact()
+ assertNotNull("${variantName} main artifact null-check", artifact)
+
+ Map<String, ClassField> value = artifact.getResValues()
+ assertNotNull(value)
+
+ // check the map against the expected one.
+ assertEquals(valueMap.keySet(), value.keySet())
+ for (String key : valueMap.keySet()) {
+ ClassField field = value.get(key)
+ assertNotNull("${variantName}: expected field ${key}", field)
+ assertEquals(
+ "${variantName}: check Value of ${key}",
+ valueMap.get(key),
+ field.getValue())
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTypeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTypeTest.groovy
new file mode 100644
index 0000000..baad500
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResValueTypeTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * 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.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertTrue
+
+/**
+ * Test resValue for string type is treated as String.
+ */
+ at CompileStatic
+class ResValueTypeTest {
+ static AndroidTestApp app = HelloWorldApp.noBuildFile()
+ static {
+ app.removeFile(app.getFile("HelloWorldTest.java"))
+ app.addFile(new TestSourceFile("src/androidTest/java/com/example/helloworld", "ResValueTest.java",
+"""
+package com.example.helloworld;
+
+import android.test.AndroidTestCase;
+
+public class ResValueTest extends AndroidTestCase {
+ public void testResValue() {
+ assertEquals("00", getContext().getString(R.string.resString));
+ }
+}
+"""))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(app)
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ resValue "array", "resArray", "foo"
+ resValue "attr", "resAttr", "foo"
+ resValue "bool", "resBool", "true"
+ resValue "color", "resColor", "#ffffff"
+ resValue "declare-styleable", "resDeclareStyleable", "foo"
+ resValue "dimen", "resDimen", "42px"
+ resValue "fraction", "resFraction", "42%"
+ resValue "id", "resId", "42"
+ resValue "integer", "resInteger", "42"
+ resValue "plurals", "resPlurals", "s"
+ resValue "string", "resString", "00" // resString becomes "0" if it is incorrectly treated as int.
+ resValue "style", "resStyle", "foo"
+ }
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ app = null
+ }
+
+ @Test
+ void "check <string> tag is used in generated.xml" () {
+ project.execute("clean", "generateDebugResValue")
+ File outputFile = project.file("build/generated/res/resValues/debug/values/generated.xml")
+ assertTrue("Missing file: " + outputFile, outputFile.isFile())
+ assertEquals(
+"""<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- Automatically generated file. DO NOT MODIFY -->
+
+ <!-- Values from default config. -->
+ <array name="resArray">foo</array>
+
+ <attr name="resAttr">foo</attr>
+
+ <bool name="resBool">true</bool>
+
+ <color name="resColor">#ffffff</color>
+
+ <declare-styleable name="resDeclareStyleable">foo</declare-styleable>
+
+ <dimen name="resDimen">42px</dimen>
+
+ <fraction name="resFraction">42%</fraction>
+
+ <item name="resId" type="id">42</item>
+
+ <integer name="resInteger">42</integer>
+
+ <plurals name="resPlurals">s</plurals>
+
+ <string name="resString" translatable="false">00</string>
+
+ <style name="resStyle">foo</style>
+
+</resources>""", FileUtils.loadFileWithUnixLineSeparators(outputFile))
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void "check resValue is treated as string"() {
+ project.execute("clean")
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResourceValidationTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResourceValidationTest.java
new file mode 100644
index 0000000..455ba21
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ResourceValidationTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.build.gradle.integration.application;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static com.android.build.gradle.integration.common.utils.GradleExceptionsHelper.getTaskFailureMessage;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.gradle.tooling.GradleConnectionException;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+
+public class ResourceValidationTest {
+
+ public static final AndroidTestApp TEST_APP = HelloWorldApp
+ .forPlugin("com.android.application");
+
+ static {
+ TEST_APP.addFile(
+ new TestSourceFile("src/main/res/drawable", "not_a_drawable.ext", "Content"));
+ }
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder().fromTestApp(TEST_APP)
+ .captureStdErr(true).create();
+
+ @Test
+ public void checkResourceValidationCanBeDisabled() throws Exception {
+ project.getStderr().reset();
+ GradleConnectionException e = project.executeExpectingFailure("assembleDebug");
+
+ //noinspection ThrowableResultOfMethodCallIgnored
+ assertThat(getTaskFailureMessage(e)).contains("file name must end with");
+
+ assertThat(project.getStderr().toString()).contains(FileUtils.join("src", "main", "res",
+ "drawable", "not_a_drawable.ext"));
+
+ Files.append("\nproject.ext['android.disableResourceValidation'] = true",
+ project.getBuildFile(),
+ Charsets.UTF_8);
+
+ project.execute("assembleDebug");
+
+ File apk = project.getApk("debug");
+ assertThatApk(apk).containsResource("drawable/not_a_drawable.ext");
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsEnabledAnnotationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsEnabledAnnotationTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsEnabledAnnotationTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsEnabledAnnotationTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsSupportModeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsSupportModeTest.groovy
new file mode 100644
index 0000000..06e5ee3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/RsSupportModeTest.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.SdkConstants
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Dependencies
+import com.android.builder.model.JavaLibrary
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Assemble tests for rsSupportMode.
+ */
+ at CompileStatic
+class RsSupportModeTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("rsSupportMode")
+ .addGradleProperties("android.useDeprecatedNdk=true")
+ .create()
+ static AndroidProject model
+
+ @BeforeClass
+ static void setUp() {
+ model =project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void testRsSupportMode() throws Exception {
+ Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "x86Debug")
+ assertNotNull("x86Debug variant null-check", debugVariant)
+
+ AndroidArtifact mainArtifact = debugVariant.getMainArtifact()
+ Dependencies dependencies = mainArtifact.getDependencies()
+
+ assertFalse(dependencies.getJavaLibraries().isEmpty())
+
+ boolean foundSupportJar = false
+ for (JavaLibrary lib : dependencies.getJavaLibraries()) {
+ File file = lib.getJarFile()
+ if (SdkConstants.FN_RENDERSCRIPT_V8_JAR.equals(file.getName())) {
+ foundSupportJar = true
+ break
+ }
+ }
+
+ assertTrue("Found suppport jar check", foundSupportJar)
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ShrinkResourcesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ShrinkResourcesTest.groovy
new file mode 100644
index 0000000..367b18d
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ShrinkResourcesTest.groovy
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.tasks.ResourceUsageAnalyzer
+import com.android.builder.model.AndroidProject
+import com.google.common.base.Joiner
+import com.google.common.collect.Lists
+import com.google.common.io.ByteStreams
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import java.util.jar.JarInputStream
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static com.android.build.gradle.tasks.ResourceUsageAnalyzer.REPLACE_DELETED_WITH_EMPTY
+import static java.io.File.separator
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertTrue
+
+/**
+ * Assemble tests for shrink.
+ */
+ at CompileStatic
+class ShrinkResourcesTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("shrink")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleRelease", "assembleDebug", "assembleProguardNoShrink")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check shrink resources"() {
+ File intermediates = project.file("build/" + AndroidProject.FD_INTERMEDIATES)
+
+ // The release target has shrinking enabled.
+ // The proguardNoShrink target has proguard but no shrinking enabled.
+ // The debug target has neither proguard nor shrinking enabled.
+
+ File apkRelease = project.getApk("release", "unsigned")
+ File apkDebug = project.getApk("debug")
+ File apkProguardOnly = project.getApk("proguardNoShrink", "unsigned")
+
+ assertTrue(apkDebug.toString() + " is not a file", apkDebug.isFile())
+ assertTrue(apkRelease.toString() + " is not a file", apkRelease.isFile())
+ assertTrue(apkProguardOnly.toString() + " is not a file", apkProguardOnly.isFile())
+
+ File compressed = new File(intermediates,
+ "res" + separator + "resources-release-stripped.ap_")
+ File uncompressed =
+ new File(intermediates, "res" + separator + "resources-release.ap_")
+ assertTrue(compressed.toString() + " is not a file", compressed.isFile())
+ assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
+
+ // Check that there is no shrinking in the other two targets:
+ assertTrue(new File(intermediates,
+ "res" + separator + "resources-debug.ap_").exists())
+ assertFalse(new File(intermediates,
+ "res" + separator + "resources-debug-stripped.ap_").exists())
+ assertTrue(new File(intermediates,
+ "res" + separator + "resources-proguardNoShrink.ap_").exists())
+ assertFalse(new File(intermediates,
+ "res" + separator + "resources-proguardNoShrink-stripped.ap_").exists())
+
+ String expectedUnstrippedApk = """\
+AndroidManifest.xml
+classes.dex
+res/drawable/force_remove.xml
+res/raw/keep.xml
+res/layout/l_used_a.xml
+res/layout/l_used_b2.xml
+res/layout/l_used_c.xml
+res/layout/lib_unused.xml
+res/layout/prefix_3_suffix.xml
+res/layout/prefix_used_1.xml
+res/layout/prefix_used_2.xml
+resources.arsc
+res/layout/unused1.xml
+res/layout/unused2.xml
+res/drawable/unused9.xml
+res/drawable/unused10.xml
+res/drawable/unused11.xml
+res/menu/unused12.xml
+res/layout/unused13.xml
+res/layout/unused14.xml
+res/layout/used1.xml
+res/layout/used2.xml
+res/layout/used3.xml
+res/layout/used4.xml
+res/layout/used5.xml
+res/layout/used6.xml
+res/layout/used7.xml
+res/layout/used8.xml
+res/drawable/used9.xml
+res/drawable/used10.xml
+res/drawable/used11.xml
+res/drawable/used12.xml
+res/menu/used13.xml
+res/layout/used14.xml
+res/drawable/used15.xml
+res/layout/used16.xml
+res/layout/used17.xml
+res/layout/used18.xml
+res/layout/used19.xml
+res/layout/used20.xml
+res/layout/used21.xml"""
+
+ String expectedStrippedApkContents = """\
+AndroidManifest.xml
+classes.dex
+res/layout/l_used_a.xml
+res/layout/l_used_b2.xml
+res/layout/l_used_c.xml
+res/layout/prefix_3_suffix.xml
+res/layout/prefix_used_1.xml
+res/layout/prefix_used_2.xml
+resources.arsc
+res/layout/used1.xml
+res/layout/used2.xml
+res/layout/used3.xml
+res/layout/used4.xml
+res/layout/used5.xml
+res/layout/used6.xml
+res/layout/used7.xml
+res/layout/used8.xml
+res/drawable/used9.xml
+res/drawable/used10.xml
+res/drawable/used11.xml
+res/drawable/used12.xml
+res/menu/used13.xml
+res/layout/used14.xml
+res/drawable/used15.xml
+res/layout/used16.xml
+res/layout/used17.xml
+res/layout/used18.xml
+res/layout/used19.xml
+res/layout/used20.xml
+res/layout/used21.xml"""
+ if (REPLACE_DELETED_WITH_EMPTY) {
+ // If replacing deleted files with empty files, the file list will include
+ // the "unused" files too, though they will be much smaller. This is checked
+ // later on in the test.
+ expectedStrippedApkContents = """\
+AndroidManifest.xml
+classes.dex
+res/drawable/force_remove.xml
+res/layout/l_used_a.xml
+res/layout/l_used_b2.xml
+res/layout/l_used_c.xml
+res/layout/lib_unused.xml
+res/layout/prefix_3_suffix.xml
+res/layout/prefix_used_1.xml
+res/layout/prefix_used_2.xml
+resources.arsc
+res/layout/unused1.xml
+res/layout/unused2.xml
+res/drawable/unused9.xml
+res/drawable/unused10.xml
+res/drawable/unused11.xml
+res/menu/unused12.xml
+res/layout/unused13.xml
+res/layout/unused14.xml
+res/layout/used1.xml
+res/layout/used2.xml
+res/layout/used3.xml
+res/layout/used4.xml
+res/layout/used5.xml
+res/layout/used6.xml
+res/layout/used7.xml
+res/layout/used8.xml
+res/drawable/used9.xml
+res/drawable/used10.xml
+res/drawable/used11.xml
+res/drawable/used12.xml
+res/menu/used13.xml
+res/layout/used14.xml
+res/drawable/used15.xml
+res/layout/used16.xml
+res/layout/used17.xml
+res/layout/used18.xml
+res/layout/used19.xml
+res/layout/used20.xml
+res/layout/used21.xml"""
+ }
+
+ // Should not have any unused resources in the compressed list
+ if (!REPLACE_DELETED_WITH_EMPTY) {
+ assertFalse(expectedStrippedApkContents, expectedStrippedApkContents.contains("unused"))
+ }
+ // Should have *all* the used resources, currently 1-21
+ for (int i = 1; i <= 21; i++) {
+ assertTrue("Missing used" + i + " in " + expectedStrippedApkContents,
+ expectedStrippedApkContents.contains("/used" + i + "."))
+ }
+
+ // Check that the uncompressed resources (.ap_) for the release target have everything
+ // we expect
+ String expectedUncompressed = expectedUnstrippedApk.replace("classes.dex\n", "")
+ assertEquals("expectedUncompressed",
+ expectedUncompressed, dumpZipContents(uncompressed).trim())
+
+ // The debug target should have everything there in the APK
+ assertEquals("The debug target should have everything there in the APK",
+ expectedUnstrippedApk, dumpZipContents(apkDebug))
+ assertEquals("The debug target should have everything there in the APK",
+ expectedUnstrippedApk, dumpZipContents(apkProguardOnly))
+
+ // Make sure force_remove was replaced with a small file if replacing rather than removing
+ if (REPLACE_DELETED_WITH_EMPTY) {
+ assertThatZip(compressed).containsFileWithContent("res/drawable/force_remove.xml",
+ ResourceUsageAnalyzer.TINY_XML);
+ }
+
+ // Check the compressed .ap_:
+ String actualCompressed = dumpZipContents(compressed)
+ String expectedCompressed = expectedStrippedApkContents.replace("classes.dex\n", "")
+ assertEquals("Check the compressed .ap_:", expectedCompressed, actualCompressed)
+ if (!REPLACE_DELETED_WITH_EMPTY) {
+ assertFalse("expectedCompressed does not contain unused resources",
+ expectedCompressed.contains("unused"))
+ }
+ assertEquals("expectedStrippedApkContents",
+ expectedStrippedApkContents, dumpZipContents(apkRelease))
+
+ // Check splits -- just sample one of them
+ //noinspection SpellCheckingInspection
+ compressed = project.file(
+ "abisplits/build/intermediates/res/resources-arm64-v8a-release-stripped.ap_")
+ //noinspection SpellCheckingInspection
+ uncompressed =
+ project.file(
+ "abisplits/build/intermediates/res/resources-arm64-v8a-release.ap_")
+ assertTrue(compressed.toString() + " is not a file", compressed.isFile())
+ assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
+ //noinspection SpellCheckingInspection
+ assertEquals(""
+ + "AndroidManifest.xml\n"
+ + "resources.arsc\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/layout/unused.xml\n" : "")
+ + "res/layout/used.xml",
+ dumpZipContents(compressed))
+ //noinspection SpellCheckingInspection
+ assertEquals(""
+ + "AndroidManifest.xml\n"
+ + "resources.arsc\n"
+ + "res/layout/unused.xml\n"
+ + "res/layout/used.xml",
+ dumpZipContents(uncompressed))
+
+ // Check WebView string handling (android_res strings etc)
+
+ //noinspection SpellCheckingInspection
+ uncompressed = project.file("webview/build/intermediates/res/resources-release.ap_")
+ //noinspection SpellCheckingInspection
+ compressed = project.file("webview/build/intermediates/res/resources-release-stripped.ap_")
+ assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
+ assertTrue(compressed.toString() + " is not a file", compressed.isFile())
+
+ //noinspection SpellCheckingInspection
+ assertEquals(""
+ + "AndroidManifest.xml\n"
+ + "res/xml/my_xml.xml\n"
+ + "resources.arsc\n"
+ + "res/raw/unknown\n"
+ + "res/raw/unused_icon.png\n"
+ + "res/raw/unused_index.html\n"
+ + "res/drawable/used1.xml\n"
+ + "res/raw/used_icon.png\n"
+ + "res/raw/used_icon2.png\n"
+ + "res/raw/used_index.html\n"
+ + "res/raw/used_index2.html\n"
+ + "res/raw/used_index3.html\n"
+ + "res/layout/used_layout1.xml\n"
+ + "res/layout/used_layout2.xml\n"
+ + "res/layout/used_layout3.xml\n"
+ + "res/raw/used_script.js\n"
+ + "res/raw/used_styles.css\n"
+ + "res/layout/webview.xml",
+ dumpZipContents(uncompressed))
+
+ //noinspection SpellCheckingInspection
+ assertEquals(""
+ + "AndroidManifest.xml\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/xml/my_xml.xml\n" : "")
+ + "resources.arsc\n"
+ + "res/raw/unknown\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/raw/unused_icon.png\n" : "")
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/raw/unused_index.html\n" : "")
+ + "res/drawable/used1.xml\n"
+ + "res/raw/used_icon.png\n"
+ + "res/raw/used_icon2.png\n"
+ + "res/raw/used_index.html\n"
+ + "res/raw/used_index2.html\n"
+ + "res/raw/used_index3.html\n"
+ + "res/layout/used_layout1.xml\n"
+ + "res/layout/used_layout2.xml\n"
+ + "res/layout/used_layout3.xml\n"
+ + "res/raw/used_script.js\n"
+ + "res/raw/used_styles.css\n"
+ + "res/layout/webview.xml",
+ dumpZipContents(compressed))
+
+ // Check stored vs deflated state:
+ // This is the state of the original source _ap file:
+ assertEquals(""
+ + " stored resources.arsc\n"
+ + "deflated AndroidManifest.xml\n"
+ + "deflated res/xml/my_xml.xml\n"
+ + "deflated res/raw/unknown\n"
+ + " stored res/raw/unused_icon.png\n"
+ + "deflated res/raw/unused_index.html\n"
+ + "deflated res/drawable/used1.xml\n"
+ + " stored res/raw/used_icon.png\n"
+ + " stored res/raw/used_icon2.png\n"
+ + "deflated res/raw/used_index.html\n"
+ + "deflated res/raw/used_index2.html\n"
+ + "deflated res/raw/used_index3.html\n"
+ + "deflated res/layout/used_layout1.xml\n"
+ + "deflated res/layout/used_layout2.xml\n"
+ + "deflated res/layout/used_layout3.xml\n"
+ + "deflated res/raw/used_script.js\n"
+ + "deflated res/raw/used_styles.css\n"
+ + "deflated res/layout/webview.xml",
+ dumpZipContents(uncompressed, true))
+
+ // This is the state of the rewritten ap_ file: the zip states should match
+ assertEquals(""
+ + " stored resources.arsc\n"
+ + "deflated AndroidManifest.xml\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? "deflated res/xml/my_xml.xml\n" : "")
+ + "deflated res/raw/unknown\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? " stored res/raw/unused_icon.png\n" : "")
+ + (REPLACE_DELETED_WITH_EMPTY ? "deflated res/raw/unused_index.html\n" : "")
+ + "deflated res/drawable/used1.xml\n"
+ + " stored res/raw/used_icon.png\n"
+ + " stored res/raw/used_icon2.png\n"
+ + "deflated res/raw/used_index.html\n"
+ + "deflated res/raw/used_index2.html\n"
+ + "deflated res/raw/used_index3.html\n"
+ + "deflated res/layout/used_layout1.xml\n"
+ + "deflated res/layout/used_layout2.xml\n"
+ + "deflated res/layout/used_layout3.xml\n"
+ + "deflated res/raw/used_script.js\n"
+ + "deflated res/raw/used_styles.css\n"
+ + "deflated res/layout/webview.xml",
+ dumpZipContents(compressed, true))
+
+ // Make sure the (remaining) binary contents of the files in the compressed APK are
+ // identical to the ones in uncompressed:
+ FileInputStream fis1 = new FileInputStream(compressed)
+ JarInputStream zis1 = new JarInputStream(fis1)
+ FileInputStream fis2 = new FileInputStream(uncompressed)
+ JarInputStream zis2 = new JarInputStream(fis2)
+
+ ZipEntry entry1 = zis1.getNextEntry()
+ ZipEntry entry2 = zis2.getNextEntry()
+ while (entry1 != null) {
+ String name1 = entry1.getName()
+ String name2 = entry2.getName()
+ while (!name1.equals(name2)) {
+ // uncompressed should contain a superset of all the names in compressed
+ entry2 = zis2.getNextJarEntry()
+ name2 = entry2.getName()
+ }
+ assertEquals(name1, name2)
+ if (!entry1.isDirectory()) {
+ assertEquals(name1, entry1.getMethod(), entry2.getMethod());
+
+ byte[] bytes1 = ByteStreams.toByteArray(zis1)
+ byte[] bytes2 = ByteStreams.toByteArray(zis2)
+
+ if (REPLACE_DELETED_WITH_EMPTY) {
+ if (name1.equals("res/xml/my_xml.xml")) {
+ assertTrue(name1, Arrays.equals(bytes1, ResourceUsageAnalyzer.TINY_XML))
+ } else if (name1.equals("res/raw/unused_icon.png")) {
+ assertTrue(name1, Arrays.equals(bytes1, ResourceUsageAnalyzer.TINY_PNG))
+ } else if (name1.equals("res/raw/unused_index.html")) {
+ assertTrue(name1, Arrays.equals(bytes1, new byte[0]))
+ } else {
+ assertTrue(name1, Arrays.equals(bytes1, bytes2))
+ }
+ } else {
+ assertTrue(name1, Arrays.equals(bytes1, bytes2))
+ }
+ } else {
+ assertTrue(entry2.isDirectory())
+ }
+ entry1 = zis1.getNextEntry()
+ entry2 = zis2.getNextEntry()
+ }
+
+ zis1.close()
+ zis2.close()
+
+ //noinspection SpellCheckingInspection
+ uncompressed = project.file("keep/build/intermediates/res/resources-release.ap_")
+ //noinspection SpellCheckingInspection
+ compressed =
+ project.file("keep/build/intermediates/res/resources-release-stripped.ap_")
+ assertTrue(uncompressed.toString() + " is not a file", uncompressed.isFile())
+ assertTrue(compressed.toString() + " is not a file", compressed.isFile())
+
+ //noinspection SpellCheckingInspection
+ assertEquals(""
+ + "AndroidManifest.xml\n"
+ + "res/raw/keep.xml\n"
+ + "resources.arsc\n"
+ + "res/layout/unused1.xml\n"
+ + "res/layout/unused2.xml\n"
+ + "res/layout/used1.xml",
+ dumpZipContents(uncompressed))
+
+ //noinspection SpellCheckingInspection
+ assertEquals(""
+ + "AndroidManifest.xml\n"
+ + "resources.arsc\n"
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/layout/unused1.xml\n" : "")
+ + (REPLACE_DELETED_WITH_EMPTY ? "res/layout/unused2.xml\n" : "")
+ + "res/layout/used1.xml",
+ dumpZipContents(compressed))
+ }
+
+ private static List<String> getZipPaths(File zipFile, boolean includeMethod)
+ throws IOException {
+ List<String> lines = Lists.newArrayList()
+ FileInputStream fis = new FileInputStream(zipFile)
+ try {
+ ZipInputStream zis = new ZipInputStream(fis)
+ try {
+ ZipEntry entry = zis.getNextEntry()
+ while (entry != null) {
+ String path = entry.getName()
+ if (includeMethod) {
+ String method
+ switch (entry.getMethod()) {
+ case ZipEntry.STORED: method = " stored"; break
+ case ZipEntry.DEFLATED: method = "deflated"; break
+ default: method = " unknown"; break
+ }
+ path = method + " " + path
+ }
+ lines.add(path)
+ entry = zis.getNextEntry()
+ }
+ } finally {
+ zis.close()
+ }
+ } finally {
+ fis.close()
+ }
+
+ return lines
+ }
+
+ private static String dumpZipContents(File zipFile) throws IOException {
+ return dumpZipContents(zipFile, false)
+ }
+
+ private static String dumpZipContents(File zipFile, final boolean includeMethod)
+ throws IOException {
+ List<String> lines = getZipPaths(zipFile, includeMethod)
+
+ // Remove META-INF statements
+ ListIterator<String> iterator = lines.listIterator()
+ while (iterator.hasNext()) {
+ if (iterator.next().startsWith("META-INF/")) {
+ iterator.remove()
+ }
+ }
+
+ // Sort by base name (and numeric sort such that unused10 comes after unused9)
+ final Pattern pattern = Pattern.compile("(.*[^\\d])(\\d+)(\\..+)?")
+ Collections.sort(lines, new Comparator<String>() {
+
+ @Override
+ public int compare(String line1, String line2) {
+ String name1 = line1.substring(line1.lastIndexOf('/') + 1)
+ String name2 = line2.substring(line2.lastIndexOf('/') + 1)
+ int delta = name1.compareTo(name2)
+ if (delta != 0) {
+ // Try to do numeric sort
+ Matcher match1 = pattern.matcher(name1)
+ if (match1.matches()) {
+ Matcher match2 = pattern.matcher(name2)
+ //noinspection ConstantConditions
+ if (match2.matches() && match1.group(1).equals(match2.group(1))) {
+ //noinspection ConstantConditions
+ int num1 = Integer.parseInt(match1.group(2))
+ //noinspection ConstantConditions
+ int num2 = Integer.parseInt(match2.group(2))
+ if (num1 != num2) {
+ return num1 - num2
+ }
+ }
+ }
+ return delta
+ }
+
+ if (includeMethod) {
+ line1 = line1.substring(10)
+ line2 = line2.substring(10)
+ }
+ return line1.compareTo(line2)
+ }
+ })
+
+ return Joiner.on('\n').join(lines)
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/SigningTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/SigningTest.groovy
new file mode 100644
index 0000000..12ac4de
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/SigningTest.groovy
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.runner.FilterableParameterized
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.build.gradle.integration.common.utils.SigningConfigHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.SigningConfig
+import com.android.builder.model.Variant
+import com.google.common.collect.ImmutableList
+import com.google.common.io.Resources
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static com.android.builder.core.BuilderConstants.RELEASE
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_ALIAS
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_KEY_PASSWORD
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_FILE
+import static com.android.builder.model.AndroidProject.PROPERTY_SIGNING_STORE_PASSWORD
+/**
+ * Integration test for all signing-related features.
+ */
+ at CompileStatic
+ at RunWith(FilterableParameterized)
+class SigningTest {
+
+ public static final String STORE_PASSWORD = "store_password"
+
+ public static final String ALIAS_NAME = "alias_name"
+
+ public static final String KEY_PASSWORD = "key_password"
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ def parameters = [
+ ["rsa_keystore.jks", "CERT.RSA", 1] as Object[],
+ ]
+
+ // These are not available on 1.6. To test, run the following (with proper JAVA_HOME):
+ // JAVA_FOR_TESTS=1.8 ./gradlew :b:i:testPrebuilts --tests=*.SigningTest
+ if (!System.getProperty("java.version").startsWith("1.6")) {
+ parameters.add(["dsa_keystore.jks", "CERT.DSA", 1] as Object[])
+ parameters.add(["ec_keystore.jks", "CERT.EC", 18] as Object[])
+ }
+
+ return parameters
+ }
+
+ @Parameterized.Parameter(0)
+ public String keystoreName
+
+ @Parameterized.Parameter(1)
+ public String certEntryName
+
+ @Parameterized.Parameter(2)
+ public int minSdkVersion
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ private File keystore
+
+ @Before
+ public void setUp() throws Exception {
+ keystore = project.file("the.keystore")
+
+
+ createKeystoreFile(keystoreName, keystore)
+
+ project.buildFile << """
+ apply plugin: 'com.android.application'
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ minSdkVersion ${minSdkVersion}
+ }
+
+ signingConfigs {
+ customDebug {
+ storeFile file("${keystore.name}")
+ storePassword "$STORE_PASSWORD"
+ keyAlias "$ALIAS_NAME"
+ keyPassword "$KEY_PASSWORD"
+ }
+ }
+
+ buildTypes {
+ debug {
+ signingConfig signingConfigs.customDebug
+ }
+
+ customSigning {
+ initWith release
+ }
+ }
+
+ applicationVariants.all { variant ->
+ if (variant.buildType.name == "customSigning") {
+ variant.outputsAreSigned = true
+ // This usually means there is a task that generates the final outputs
+ // and variant.outputs*.outputFile is set to point to these files.
+ }
+ }
+ }
+"""
+
+ }
+
+ private void createKeystoreFile(String resourceName, File keystore) {
+ def keystoreBytes =
+ Resources.toByteArray(
+ Resources.getResource(SigningTest, "SigningTest/" + resourceName))
+ keystore << keystoreBytes
+ }
+
+ @Test
+ void "signing DSL"() throws Exception {
+ project.execute("assembleDebug")
+ assertThatZip(project.getApk("debug")).contains("META-INF/$certEntryName")
+ }
+
+ @Test
+ void "assemble with injected signing config"() {
+ // add prop args for signing override.
+ List<String> args = ImmutableList.of(
+ "-P" + PROPERTY_SIGNING_STORE_FILE + "=" + keystore.getPath(),
+ "-P" + PROPERTY_SIGNING_STORE_PASSWORD + "=" + STORE_PASSWORD,
+ "-P" + PROPERTY_SIGNING_KEY_ALIAS + "=" + ALIAS_NAME,
+ "-P" + PROPERTY_SIGNING_KEY_PASSWORD + "=" + KEY_PASSWORD)
+
+ project.execute(args, "assembleRelease")
+
+ // Check for signing file inside the archive.
+ assertThatZip(project.getApk("release")).contains("META-INF/$certEntryName")
+ }
+
+ @Test
+ void "check custom signing"() throws Exception {
+ Collection<Variant> variants = project.singleModel.variants
+
+ for (Variant variant : variants) {
+ // Release variant doesn't specify the signing config, so it should not be considered
+ // signed.
+ if (variant.getName().equals("release")) {
+ assertThat(variant.mainArtifact.signed).named(variant.name).isFalse()
+ }
+
+ // customSigning is identical to release, but overrides the signing check.
+ if (variant.getName().equals("customSigning")) {
+ assertThat(variant.mainArtifact.signed).named(variant.name).isTrue()
+ }
+ }
+ }
+
+ @Test
+ public void "signing configs model"() {
+ def model = project.getSingleModel()
+
+ Collection<SigningConfig> signingConfigs = model.signingConfigs
+ assertThat(signingConfigs.collect {it.name}).containsExactly("debug", "customDebug")
+
+ SigningConfig debugSigningConfig = ModelHelper.getSigningConfig(signingConfigs, DEBUG)
+ new SigningConfigHelper(debugSigningConfig, DEBUG, true).test()
+
+ SigningConfig mySigningConfig = ModelHelper.getSigningConfig(signingConfigs, "customDebug")
+ new SigningConfigHelper(mySigningConfig, "customDebug", true)
+ .setStoreFile(keystore)
+ .setStorePassword(STORE_PASSWORD)
+ .setKeyAlias(ALIAS_NAME)
+ .setKeyPassword(KEY_PASSWORD)
+ .test()
+
+ Variant debugVariant = ModelHelper.getVariant(model.variants, DEBUG)
+ assertThat(debugVariant.mainArtifact.signingConfigName).isEqualTo("customDebug")
+ Collection<AndroidArtifact> debugExtraAndroidArtifacts = debugVariant.getExtraAndroidArtifacts()
+ AndroidArtifact androidTestArtifact = ModelHelper.getAndroidArtifact(
+ debugExtraAndroidArtifacts,
+ AndroidProject.ARTIFACT_ANDROID_TEST)
+
+ assertThat(androidTestArtifact.signingConfigName).isEqualTo("customDebug")
+
+ Variant releaseVariant = ModelHelper.getVariant(model.variants, RELEASE)
+ assertThat(releaseVariant.mainArtifact.signingConfigName).isNull()
+ }
+
+ @Test
+ public void 'signingReport task'() throws Exception {
+ project.execute("signingReport")
+ }
+
+ @Test
+ public void 'SHA algorithm change'() throws Exception {
+ File apk = project.getApk("debug")
+
+ if (minSdkVersion < 18) {
+ project.execute("assembleDebug")
+
+ assertThatApk(apk).containsFileWithMatch("META-INF/CERT.SF", "SHA1-Digest");
+ assertThatApk(apk).containsFileWithoutContent("META-INF/CERT.SF", "SHA-256-Digest");
+ assertThatApk(apk).containsFileWithMatch("META-INF/MANIFEST.MF", "SHA1-Digest");
+ assertThatApk(apk).containsFileWithoutContent("META-INF/MANIFEST.MF", "SHA-256-Digest");
+
+ TestFileUtils.searchAndReplace(project.buildFile, "minSdkVersion \\d+", "minSdkVersion 18")
+ }
+
+ project.execute("assembleDebug")
+
+ assertThatApk(apk).containsFileWithMatch("META-INF/CERT.SF", "SHA-256-Digest");
+ assertThatApk(apk).containsFileWithoutContent("META-INF/CERT.SF", "SHA1-Digest");
+ assertThatApk(apk).containsFileWithMatch("META-INF/MANIFEST.MF", "SHA-256-Digest");
+ assertThatApk(apk).containsFileWithoutContent("META-INF/MANIFEST.MF", "SHA1-Digest");
+ }
+
+ @Test
+ @Category(DeviceTests)
+ public void 'SHA algorithm change - on device'() throws Exception {
+ project.executeConnectedCheck()
+
+ if (minSdkVersion < 18) {
+ TestFileUtils.searchAndReplace(project.buildFile, "minSdkVersion \\d+", "minSdkVersion 18")
+ project.executeConnectedCheck()
+ }
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TestWithDepTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TestWithDepTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TestWithDepTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TestWithDepTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ThirdPartyTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ThirdPartyTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ThirdPartyTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/ThirdPartyTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TictactoeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TictactoeTest.groovy
new file mode 100644
index 0000000..a1767b6
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/TictactoeTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidLibrary
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Dependencies
+import com.android.builder.model.Variant
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+/**
+ * Assemble tests for tictactoe.
+ */
+ at CompileStatic
+class TictactoeTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("tictactoe")
+ .create()
+ static Map<String, AndroidProject> models
+
+ @BeforeClass
+ static void setUp() {
+ models = project.executeAndReturnMultiModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ models = null
+ }
+
+ @Test
+ public void testModel() throws Exception {
+ AndroidProject libModel = models.get(":lib")
+ assertNotNull("lib module model null-check", libModel)
+ assertTrue("lib module library flag", libModel.isLibrary())
+
+ AndroidProject appModel = models.get(":app")
+ assertNotNull("app module model null-check", appModel)
+
+ Collection<Variant> variants = appModel.getVariants()
+ Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
+ assertNotNull("debug variant null-check", debugVariant)
+
+ Dependencies dependencies = debugVariant.getMainArtifact().getDependencies()
+ assertNotNull(dependencies)
+
+ Collection<AndroidLibrary> libs = dependencies.getLibraries()
+ assertNotNull(libs)
+ assertEquals(1, libs.size())
+
+ AndroidLibrary androidLibrary = libs.iterator().next()
+ assertNotNull(androidLibrary)
+
+ assertEquals("Dependency project path", ":lib", androidLibrary.getProject())
+
+ // TODO: right now we can only test the folder name efficiently
+ String path = androidLibrary.getFolder().getPath()
+ assertTrue(path, path.endsWith(FileUtils.join("tictactoe", "lib", "unspecified")))
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/UnitTestingModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/UnitTestingModelTest.groovy
new file mode 100644
index 0000000..3b1e480
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/UnitTestingModelTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.ArtifactMetaData
+import com.android.builder.model.JavaArtifact
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.builder.model.AndroidProject.ARTIFACT_UNIT_TEST
+/**
+ * Tests for the unit-tests related parts of the builder model.
+ */
+ at CompileStatic
+class UnitTestingModelTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("unitTestingComplexProject")
+ .create();
+
+ @Test
+ public void "Unit testing artifacts are included in the model"() {
+ // Build the project, so we can verify paths in the model exist.
+ project.execute("test")
+
+ AndroidProject model = project.allModels[":app"]
+
+ assertThat(model.extraArtifacts*.name).containsExactly(
+ AndroidProject.ARTIFACT_ANDROID_TEST,
+ ARTIFACT_UNIT_TEST)
+
+ def unitTestMetadata = model.extraArtifacts.find { it.name == ARTIFACT_UNIT_TEST }
+
+ assert unitTestMetadata.isTest()
+ assert unitTestMetadata.type == ArtifactMetaData.TYPE_JAVA
+
+ for (variant in model.variants) {
+ def unitTestArtifacts = variant.extraJavaArtifacts.findAll {
+ it.name == ARTIFACT_UNIT_TEST
+ }
+ assert unitTestArtifacts.size() == 1
+
+ JavaArtifact unitTestArtifact = unitTestArtifacts.first()
+ assert unitTestArtifact.name == ARTIFACT_UNIT_TEST
+ assertThat(unitTestArtifact.assembleTaskName).contains("UnitTest")
+ assertThat(unitTestArtifact.assembleTaskName).contains(variant.name.capitalize())
+ assertThat(unitTestArtifact.compileTaskName).contains("UnitTest")
+ assertThat(unitTestArtifact.compileTaskName).contains(variant.name.capitalize())
+
+ // No per-variant source code.
+ assertThat(unitTestArtifact.variantSourceProvider).isNull()
+ assertThat(unitTestArtifact.multiFlavorSourceProvider).isNull()
+
+ assertThat(variant.mainArtifact.classesFolder).isDirectory()
+ assertThat(variant.mainArtifact.javaResourcesFolder).isDirectory()
+ assertThat(unitTestArtifact.classesFolder).isDirectory()
+ assertThat(unitTestArtifact.javaResourcesFolder).isDirectory()
+
+ assertThat(unitTestArtifact.classesFolder)
+ .isNotEqualTo(variant.mainArtifact.classesFolder)
+ assertThat(unitTestArtifact.javaResourcesFolder)
+ .isNotEqualTo(variant.mainArtifact.javaResourcesFolder)
+ }
+
+ def sourceProvider = model.defaultConfig
+ .extraSourceProviders
+ .find { it.artifactName == ARTIFACT_UNIT_TEST }
+ .sourceProvider
+
+ assertThat(sourceProvider.javaDirectories).hasSize(1)
+ assertThat(sourceProvider.javaDirectories.first().absolutePath).endsWith(
+ FileUtils.join("test", "java"))
+ }
+
+ @Test
+ public void flavors() throws Exception {
+ project.getSubproject("app").buildFile << """
+android {
+ productFlavors { paid; free }
+}
+"""
+ AndroidProject model = project.allModels[":app"]
+
+ assertThat(model.productFlavors).hasSize(2)
+
+ for (flavor in model.productFlavors) {
+ def sourceProvider = flavor.extraSourceProviders
+ .find { it.artifactName == ARTIFACT_UNIT_TEST }
+ .sourceProvider
+
+ assertThat(sourceProvider.javaDirectories).hasSize(1)
+ assertThat(sourceProvider.javaDirectories.first().absolutePath)
+ .endsWith("test${flavor.productFlavor.name.capitalize()}" +
+ "${File.separatorChar}java")
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.java
new file mode 100644
index 0000000..6909d57
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest.java
@@ -0,0 +1,498 @@
+/*
+ * 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.build.gradle.integration.application;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertWithMessage;
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.searchAndReplace;
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+
+/**
+ * Tests for the PNG generation feature.
+ *
+ * The "v4" is added by resource merger to all dpi qualifiers, to make it clear dpi qualifiers are
+ * supported since API 4.
+ */
+public class VectorDrawableTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("vectorDrawables")
+ .create();
+
+ @BeforeClass
+ public static void checkBuildTools() {
+ GradleTestProject.assumeBuildToolsAtLeast(21);
+ }
+
+ @Test
+ public void vectorFileIsMovedAndPngsAreGenerated() throws Exception {
+ project.execute("clean", "assembleDebug");
+ File apk = project.getApk("debug");
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-v22/no_need.xml");
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v22/no_need.png");
+ assertThatApk(apk).doesNotContainResource("drawable-nodpi-v4/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable/heart.xml");
+
+ // Check HDPI. Test project contains the hdpi png, it should be used instead of the
+ // generated one.
+ File originalPng = new File(
+ project.getTestDir(),
+ "src/main/res/drawable-hdpi/special_heart.png");
+ File generatedPng = new File(
+ project.getTestDir(),
+ "build/generated/res/pngs/debug/drawable-hdpi/special_heart.png");
+ File pngToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable-hdpi-v4/special_heart.png");
+
+ assertThat(generatedPng).doesNotExist();
+ assertWithMessage("Wrong file used.")
+ .that(FileUtils.sha1(pngToUse))
+ .isEqualTo(FileUtils.sha1(originalPng));
+
+ // Check XHDPI.
+ generatedPng = new File(
+ project.getTestDir(),
+ "build/generated/res/pngs/debug/drawable-xhdpi/special_heart.png");
+ pngToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable-xhdpi-v4/special_heart.png");
+
+ assertWithMessage("Wrong file used.")
+ .that(FileUtils.sha1(pngToUse))
+ .isEqualTo(FileUtils.sha1(generatedPng));
+
+ // Check interactions with other qualifiers.
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/modern_heart.xml");
+ assertThatApk(apk).containsResource("drawable-fr-anydpi-v21/french_heart.xml");
+ assertThatApk(apk).containsResource("drawable-fr-anydpi-v21/french_heart.xml");
+ assertThatApk(apk).containsResource("drawable-fr-hdpi-v4/french_heart.png");
+ assertThatApk(apk).containsResource("drawable-hdpi-v16/modern_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-fr-hdpi-v21/french_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-fr-xhdpi-v21/french_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-fr/french_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-fr/french_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v16/modern_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/french_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/modern_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/modern_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/french_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/modern_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi/french_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi/modern_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-v16/modern_heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-v16/modern_heart.xml");
+ }
+
+ @Test
+ public void incrementalBuildAddXml() throws Exception {
+ project.execute("assembleDebug");
+ File apk = project.getApk("debug");
+
+ // Sanity check:
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart_copy.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart_copy.png");
+
+ File intermediatesXml =
+ project.file("build/intermediates/res/merged/debug/drawable-anydpi-v21/heart.xml");
+ File intermediatesHdpiPng =
+ project.file("build/intermediates/res/merged/debug/drawable-hdpi-v4/heart.png");
+ long xmlTimestamp = intermediatesXml.lastModified();
+ long pngTimestamp = intermediatesHdpiPng.lastModified();
+
+ File heartXml = new File(project.getTestDir(), "src/main/res/drawable/heart.xml");
+ File heartXmlCopy = new File(project.getTestDir(), "src/main/res/drawable/heart_copy.xml");
+ Files.copy(heartXml, heartXmlCopy);
+
+ project.execute("assembleDebug");
+ assertThat(intermediatesXml).wasModifiedAt(xmlTimestamp);
+ assertThat(intermediatesHdpiPng).wasModifiedAt(pngTimestamp);
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart_copy.xml");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart_copy.png");
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart_copy.png");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart_copy.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart_copy.xml");
+ assertThatApk(apk).doesNotContainResource("drawable/heart_copy.xml");
+ }
+
+ @Test
+ public void incrementalBuildDeleteXml() throws Exception {
+ project.execute("assembleDebug");
+ File intermediatesIconPng =
+ project.file("build/intermediates/res/merged/debug/drawable/icon.png");
+ long timestamp = intermediatesIconPng.lastModified();
+
+ FileUtils.delete(new File(project.getTestDir(), "src/main/res/drawable/heart.xml"));
+
+ project.execute("assembleDebug");
+
+ File apk = project.getApk("debug");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable/heart.xml");
+
+ assertThat(intermediatesIconPng).wasModifiedAt(timestamp);
+ }
+
+ @Test
+ public void incrementalBuildDeletePng() throws Exception {
+ project.execute("assembleDebug");
+ File intermediatesXml =
+ project.file("build/intermediates/res/merged/debug/drawable-anydpi-v21/heart.xml");
+ long xmlTimestamp = intermediatesXml.lastModified();
+
+
+ File generatedPng = new File(
+ project.getTestDir(),
+ "build/generated/res/pngs/debug/drawable-hdpi/special_heart.png");
+ File originalPng = new File(
+ project.getTestDir(),
+ "src/main/res/drawable-hdpi/special_heart.png");
+ File pngToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable-hdpi-v4/special_heart.png");
+
+ assertThat(generatedPng).doesNotExist();
+ assertWithMessage("Wrong file used.")
+ .that(FileUtils.sha1(pngToUse))
+ .isEqualTo(FileUtils.sha1(originalPng));
+
+ FileUtils.delete(originalPng);
+
+ project.execute("assembleDebug");
+
+ assertWithMessage("Wrong file used.")
+ .that(FileUtils.sha1(pngToUse))
+ .isEqualTo(FileUtils.sha1(generatedPng));
+
+ assertThat(intermediatesXml).wasModifiedAt(xmlTimestamp);
+ }
+
+ @Test
+ public void incrementalBuildAddPng() throws Exception {
+ project.execute("assembleDebug");
+ File intermediatesXml =
+ project.file("build/intermediates/res/merged/debug/drawable-anydpi-v21/heart.xml");
+ long xmlTimestamp = intermediatesXml.lastModified();
+
+ File generatedPng = new File(
+ project.getTestDir(),
+ "build/generated/res/pngs/debug/drawable-xhdpi/special_heart.png");
+ File pngToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable-xhdpi-v4/special_heart.png");
+
+ assertWithMessage("Wrong file used.")
+ .that(FileUtils.sha1(pngToUse))
+ .isEqualTo(FileUtils.sha1(generatedPng));
+
+ // Create a PNG file for XHDPI. It should be used instead of the generated one.
+ File hdpiPng = new File(project.getTestDir(), "src/main/res/drawable-hdpi/special_heart.png");
+ File xhdpiPng = new File(project.getTestDir(), "src/main/res/drawable-xhdpi/special_heart.png");
+ Files.createParentDirs(xhdpiPng);
+ Files.copy(hdpiPng, xhdpiPng);
+
+ project.execute("assembleDebug");
+
+ assertWithMessage("Wrong file used.")
+ .that(FileUtils.sha1(pngToUse))
+ .isNotEqualTo(FileUtils.sha1(generatedPng));
+
+ assertWithMessage("Wrong file used.")
+ .that(FileUtils.sha1(pngToUse))
+ .isEqualTo(FileUtils.sha1(xhdpiPng));
+
+ assertThat(intermediatesXml).wasModifiedAt(xmlTimestamp);
+ }
+
+ @Test
+ public void incrementalBuildModifyXml() throws Exception {
+ project.execute("assembleDebug");
+ File intermediatesIconPng =
+ project.file("build/intermediates/res/merged/debug/drawable/icon.png");
+ long timestamp = intermediatesIconPng.lastModified();
+
+ File heartPngToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable-hdpi-v4/heart.png");
+ File iconPngToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable/icon.png");
+
+ String oldHashCode = FileUtils.sha1(heartPngToUse);
+ long heartPngModified = heartPngToUse.lastModified();
+ long iconPngModified = iconPngToUse.lastModified();
+
+ File heartXml = new File(project.getTestDir(), "src/main/res/drawable/heart.xml");
+ String content = Files.toString(heartXml, UTF_8);
+ // Change the heart to blue.
+ Files.write(content.replace("ff0000", "0000ff"), heartXml, UTF_8);
+
+ project.execute("assembleDebug");
+
+ assertThat(iconPngToUse.lastModified()).isEqualTo(iconPngModified);
+ assertThat(heartPngToUse.lastModified()).isNotEqualTo(heartPngModified);
+ assertWithMessage("XML file change not reflected in PNG.")
+ .that(FileUtils.sha1(heartPngToUse))
+ .isNotEqualTo(oldHashCode);
+
+ assertThat(intermediatesIconPng).wasModifiedAt(timestamp);
+ }
+
+ @Test
+ public void incrementalBuildReplaceVectorDrawableWithBitmapAlias() throws Exception {
+ project.execute("assembleDebug");
+ File intermediatesIconPng =
+ project.file("build/intermediates/res/merged/debug/drawable/icon.png");
+ long timestamp = intermediatesIconPng.lastModified();
+
+ File heartXml = new File(project.getTestDir(), "src/main/res/drawable/heart.xml");
+ Files.write(
+ "<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\" " +
+ "android:src=\"@drawable/icon\" />",
+ heartXml,
+ UTF_8);
+
+ project.execute("assembleDebug");
+
+ File apk = project.getApk("debug");
+ assertThatApk(apk).containsResource("drawable/heart.xml");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi/heart.png");
+
+ File heartXmlToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable/heart.xml");
+
+ // They won't be equal, because of the source marker added in the XML.
+ assertThat(Files.toString(heartXmlToUse, UTF_8)).contains(Files.toString(heartXml, UTF_8));
+
+ assertThat(intermediatesIconPng).wasModifiedAt(timestamp);
+ }
+
+ @Test
+ public void incrementalBuildReplaceBitmapAliasWithVectorDrawable() throws Exception {
+ File heartXml = new File(project.getTestDir(), "src/main/res/drawable/heart.xml");
+
+ String vectorDrawable = Files.toString(heartXml, UTF_8);
+
+ Files.write(
+ "<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\" " +
+ "android:src=\"@drawable/icon\" />",
+ heartXml,
+ UTF_8);
+
+ project.execute("clean", "assembleDebug");
+ File intermediatesIconPng =
+ project.file("build/intermediates/res/merged/debug/drawable/icon.png");
+ long timestamp = intermediatesIconPng.lastModified();
+
+ File apk = project.getApk("debug");
+ assertThatApk(apk).containsResource("drawable/heart.xml");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi/heart.png");
+
+ File heartXmlToUse = new File(
+ project.getTestDir(),
+ "build/intermediates/res/merged/debug/drawable/heart.xml");
+
+ // They won't be equal, because of the source marker added in the XML.
+ assertThat(Files.toString(heartXmlToUse, UTF_8)).contains(Files.toString(heartXml, UTF_8));
+
+ Files.write(vectorDrawable, heartXml, UTF_8);
+ project.execute("assembleDebug");
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable/heart.xml");
+
+ assertThat(intermediatesIconPng).wasModifiedAt(timestamp);
+ }
+
+ @Test
+ public void defaultDensitiesWork() throws Exception {
+ project.execute(ImmutableList.of("-PcheckDefaultDensities=true"), "clean", "assembleDebug");
+ File apk = project.getApk("debug");
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).containsResource("drawable-xxxhdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-xxhdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-mdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-ldpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-ldpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable/heart.xml");
+ }
+
+ @Test
+ public void nothingIsDoneWhenMinSdk21AndAbove() throws Exception {
+ searchAndReplace(project.getBuildFile(), "minSdkVersion \\d+", "minSdkVersion 21");
+ project.execute("clean", "assembleDebug");
+ File apk = project.getApk("debug");
+
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/special_heart.png");
+ assertThatApk(apk).containsResource("drawable-v16/modern_heart.xml");
+ assertThatApk(apk).containsResource("drawable-v22/no_need.xml");
+ assertThatApk(apk).containsResource("drawable/heart.xml");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).containsResource("drawable/special_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v16/modern_heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v22/no_need.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v22/no_need.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-nodpi-v4/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png");
+ }
+
+ @Test
+ public void disablingPngGeneration() throws Exception {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "android.defaultConfig.vectorDrawables.generatedDensities = []");
+
+ project.execute("clean", "assembleDebug");
+ File apk = project.getApk("debug");
+ assertPngGenerationDisabled(apk);
+ }
+
+ @Test
+ public void disablingPngGeneration_oldDsl() throws Exception {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "android.defaultConfig.generatedDensities = []");
+
+ project.execute("clean", "assembleDebug");
+ File apk = project.getApk("debug");
+ assertPngGenerationDisabled(apk);
+ }
+
+ @Test
+ public void incrementalBuildDisablingPngGeneration() throws Exception {
+ File apk = project.getApk("debug");
+
+ project.execute("clean", "assembleDebug");
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-v22/no_need.xml");
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v22/no_need.png");
+ assertThatApk(apk).doesNotContainResource("drawable-nodpi-v4/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable/heart.xml");
+
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "android.defaultConfig.vectorDrawables.useSupportLibrary = true");
+
+ project.execute("assembleDebug");
+ assertPngGenerationDisabled(apk);
+ }
+
+ @Test
+ public void incrementalBuildChangingDensities() throws Exception {
+ File apk = project.getApk("debug");
+
+ project.execute("clean", "assembleDebug");
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-v22/no_need.xml");
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v22/no_need.png");
+ assertThatApk(apk).doesNotContainResource("drawable-nodpi-v4/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable/heart.xml");
+
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "android.defaultConfig.vectorDrawables.generatedDensities = ['hdpi']");
+
+ project.execute("assembleDebug");
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable-v22/no_need.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v22/no_need.png");
+ assertThatApk(apk).doesNotContainResource("drawable-nodpi-v4/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable/heart.xml");
+ }
+
+ private static void assertPngGenerationDisabled(File apk) throws Exception {
+ assertThatApk(apk).containsResource("drawable/heart.xml");
+ assertThatApk(apk).containsResource("drawable/icon.png");
+ assertThatApk(apk).containsResource("drawable-v22/no_need.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/heart.png");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v21/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v22/no_need.png");
+ assertThatApk(apk).doesNotContainResource("drawable-nodpi-v4/heart.xml");
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v21/heart.xml");
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest_Library.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest_Library.groovy
new file mode 100644
index 0000000..1f0f560
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/VectorDrawableTest_Library.groovy
@@ -0,0 +1,246 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+
+/**
+ * Tests for PNG generation in case of libraries.
+ */
+ at CompileStatic
+class VectorDrawableTest_Library {
+
+ public static final String VECTOR_XML_CONTENT = """
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="256dp"
+ android:width="256dp"
+ android:viewportWidth="32"
+ android:viewportHeight="32">
+
+ <path
+ android:fillColor="#ff0000"
+ android:pathData="M20.5,9.5
+ c-1.965,0,-3.83,1.268,-4.5,3
+ c-0.17,-1.732,-2.547,-3,-4.5,-3
+ C8.957,9.5,7,11.432,7,14
+ c0,3.53,3.793,6.257,9,11.5
+ c5.207,-5.242,9,-7.97,9,-11.5
+ C25,11.432,23.043,9.5,20.5,9.5z" />
+ </vector>
+ """
+
+ public static final String VECTOR_XML_PATH = "src/main/res/drawable/lib_vector.xml"
+
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new MultiModuleTestProject([
+ ":app": HelloWorldApp.forPlugin("com.android.application"),
+ ":lib": new EmptyAndroidTestApp("com.example.lib")
+ ]))
+ .create()
+
+ @Before
+ public void checkBuildTools() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ }
+
+ @Before
+ public void setUpApp() {
+ def app = project.getSubproject(":app")
+ app.buildFile << "dependencies { compile project(':lib') }"
+ Files.createParentDirs(app.file("src/main/res/drawable/app_vector.xml"))
+ app.file("src/main/res/drawable/app_vector.xml") << VECTOR_XML_CONTENT
+ }
+
+ @Before
+ public void setUpLib() {
+ def lib = project.getSubproject(":lib")
+ lib.buildFile << """
+ apply plugin: "com.android.library"
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+ """
+
+ Files.createParentDirs(lib.file(VECTOR_XML_PATH))
+ lib.file(VECTOR_XML_PATH) << VECTOR_XML_CONTENT
+ }
+
+ @Test
+ public void "Lib uses support library, app does not"() throws Exception {
+ project.getSubproject(":lib").buildFile << """
+ android.defaultConfig.vectorDrawables {
+ useSupportLibrary = true
+ }
+ """
+
+ project.execute(":app:assembleDebug")
+ File apk = project.getSubproject(":app").getApk("debug")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/app_vector.xml")
+
+ assertThatApk(apk).containsResource("drawable/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/lib_vector.png")
+
+ modifyVector()
+
+ // Verify incremental build.
+ project.execute(":app:assembleDebug")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/app_vector.xml")
+
+ assertThatApk(apk).containsResource("drawable/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/lib_vector.png")
+ }
+
+ private void modifyVector() {
+ TestFileUtils.searchAndReplace(
+ project.getSubproject(":lib").file(VECTOR_XML_PATH),
+ "ff0000",
+ "00ff00")
+ }
+
+ @Test
+ public void "App uses support library, lib does not"() throws Exception {
+ project.getSubproject(":app").buildFile << """
+ android.defaultConfig.vectorDrawables {
+ useSupportLibrary = true
+ }
+ """
+
+ project.execute(":app:assembleDebug")
+ File apk = project.getSubproject(":app").getApk("debug")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/lib_vector.xml")
+
+ assertThatApk(apk).containsResource("drawable/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/app_vector.png")
+
+ modifyVector()
+
+ project.execute(":app:assembleDebug")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/lib_vector.xml")
+
+ assertThatApk(apk).containsResource("drawable/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/app_vector.png")
+ }
+
+ @Test
+ public void "Both use support library"() throws Exception {
+ project.getSubproject(":app").buildFile << """
+ android.defaultConfig.vectorDrawables {
+ useSupportLibrary = true
+ }
+ """
+
+ project.getSubproject(":lib").buildFile << """
+ android.defaultConfig.vectorDrawables {
+ useSupportLibrary = true
+ }
+ """
+
+ project.execute(":app:assembleDebug")
+ File apk = project.getSubproject(":app").getApk("debug")
+
+ assertThatApk(apk).containsResource("drawable/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/app_vector.png")
+
+ assertThatApk(apk).containsResource("drawable/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/lib_vector.png")
+
+ modifyVector()
+
+ project.execute(":app:assembleDebug")
+ assertThatApk(apk).containsResource("drawable/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/app_vector.png")
+
+ assertThatApk(apk).containsResource("drawable/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).doesNotContainResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable-xhdpi-v4/lib_vector.png")
+ }
+
+ @Test
+ public void "None use support library"() throws Exception {
+ project.execute(":app:assembleDebug")
+ File apk = project.getSubproject(":app").getApk("debug")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/app_vector.xml")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/lib_vector.xml")
+
+ modifyVector()
+
+ project.execute(":app:assembleDebug")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/app_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/app_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/app_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/app_vector.xml")
+
+ assertThatApk(apk).containsResource("drawable-anydpi-v21/lib_vector.xml")
+ assertThatApk(apk).containsResource("drawable-hdpi-v4/lib_vector.png")
+ assertThatApk(apk).containsResource("drawable-xhdpi-v4/lib_vector.png")
+ assertThatApk(apk).doesNotContainResource("drawable/lib_vector.xml")
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleDebugOnlyTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleDebugOnlyTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleDebugOnlyTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleDebugOnlyTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearSimpleTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearVariantTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearVariantTest.groovy
new file mode 100644
index 0000000..2961979
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearVariantTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package com.android.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ZipHelper
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.SdkConstants.DOT_ANDROID_PACKAGE
+import static com.android.SdkConstants.FD_RES
+import static com.android.SdkConstants.FD_RES_RAW
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.builder.core.BuilderConstants.ANDROID_WEAR_MICRO_APK
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertNull
+/**
+ * Assemble tests for embedded.
+ */
+ at CompileStatic
+class WearVariantTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("embedded")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", ":main:assemble")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check embedded"() {
+ String embeddedApkPath = FD_RES + '/' + FD_RES_RAW + '/' + ANDROID_WEAR_MICRO_APK +
+ DOT_ANDROID_PACKAGE
+
+ // each micro app has a different version name to distinguish them from one another.
+ // here we record what we expect from which.
+ def variantData = [
+ //Output apk name Version name
+ //--------------- ------------
+ [ "flavor1-release-unsigned", "flavor1" ],
+ [ "flavor2-release-unsigned", "default" ],
+ [ "flavor1-custom-unsigned", "custom" ],
+ [ "flavor2-custom-unsigned", "custom" ],
+ [ "flavor1-debug", null ],
+ [ "flavor2-debug", null ]
+ ]
+
+ for (List<String> data : variantData) {
+ File fullApk = project.getSubproject("main").getApk(data[0])
+ File embeddedApk = ZipHelper.extractFile(fullApk, embeddedApkPath)
+
+ if (data[1] == null) {
+ assertNull("Expected no embedded app for " + data[0], embeddedApk)
+ break
+ }
+
+ assertNotNull("Failed to find embedded micro app for " + data[0], embeddedApk)
+
+ // check for the versionName
+ assertThatApk(embeddedApk).hasVersionName(data[1])
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearWithCustomApplicationIdTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearWithCustomApplicationIdTest.groovy
new file mode 100644
index 0000000..9cdc75b
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/application/WearWithCustomApplicationIdTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.integration.application
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.google.common.base.Throwables
+import groovy.transform.CompileStatic
+import org.gradle.tooling.BuildException
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static org.junit.Assert.fail
+/**
+ * Debug builds with a wearApp with applicationId that does not match that of the main application
+ * should fail.
+ */
+ at CompileStatic
+class WearWithCustomApplicationIdTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("embedded")
+ .create()
+
+ @Before
+ void setUp() {
+ def mainAppBuildGradle = project.file("main/build.gradle");
+
+ mainAppBuildGradle.text = mainAppBuildGradle.text.replaceFirst(
+ /flavor1 \{/,
+ "flavor1 {\n" +
+ " applicationId \"com.example.change.application.id.breaks.embed\"")
+ }
+
+ @Test
+ public void "build should fail on applicationId mismatch"() {
+ try {
+ project.execute("clean", ":main:assembleFlavor1Release")
+ fail("Build should fail: applicationId of wear app does not match the main application")
+ } catch (BuildException e) {
+ assert Throwables.getRootCause(e).message.contains(
+ "The main and the micro apps do not have the same package name");
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/automatic/CheckAll.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/automatic/CheckAll.java
new file mode 100644
index 0000000..72b6c01
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/automatic/CheckAll.java
@@ -0,0 +1,115 @@
+/*
+ * 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.build.gradle.integration.automatic;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.runner.ParallelParameterized;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test case that executes "standard" gradle tasks in all our tests projects.
+ *
+ * <p>You can run only one test like this:
+ * <p>{@code gw :b:i-t:automaticTest --tests=*.CheckAll.lint[abiPureSplits]}
+ */
+ at RunWith(ParallelParameterized.class)
+public class CheckAll {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ List<Object[]> parameters = Lists.newArrayList();
+
+ File[] testProjects = GradleTestProject.TEST_PROJECT_DIR.listFiles();
+ checkState(testProjects != null);
+
+ for (File testProject : testProjects) {
+ if (!isValidProjectDirectory(testProject)) {
+ continue;
+ }
+
+ parameters.add(new Object[]{testProject.getName()});
+ }
+
+ return parameters;
+ }
+
+ private static boolean isValidProjectDirectory(File testProject) {
+ if (!testProject.isDirectory()) {
+ return false;
+ }
+
+ File buildGradle = new File(testProject, "build.gradle");
+ File settingsGradle = new File(testProject, "settings.gradle");
+
+ return buildGradle.exists() || settingsGradle.exists();
+ }
+
+ @Rule
+ public GradleTestProject project;
+
+ public String projectName;
+
+ public CheckAll(String projectName) {
+ this.projectName = projectName;
+ this.project = GradleTestProject.builder()
+ .fromTestProject(projectName)
+ .forExperimentalPlugin(COMPONENT_MODEL_PROJECTS.contains(projectName))
+ .create();
+ }
+
+ @Test
+ public void lint() throws Exception {
+ Assume.assumeFalse(BROKEN_ASSEMBLE.contains(projectName));
+ project.execute("lint");
+ }
+
+ @Test
+ public void assemble() throws Exception {
+ Assume.assumeFalse(BROKEN_ASSEMBLE.contains(projectName));
+ project.execute("assembleDebug", "assembleAndroidTest");
+ }
+
+ // TODO: Investigate and clear these lists.
+ private static final ImmutableSet<String> BROKEN_ASSEMBLE = ImmutableSet.of(
+ "ndkRsHelloCompute", // TODO: Fails in C++ code, not sure what the issue is.
+
+ // These are all right:
+ "daggerTwo", // requires Java 7
+ "projectWithLocalDeps", // Doesn't have a build.gradle, not much to check anyway.
+ "duplicateNameImport", // Fails on purpose.
+ "instant-unit-tests", // Specific to testing instant run, not a "real" project.
+ "simpleManifestMergingTask" // Not an Android project.
+ );
+
+ private static final ImmutableSet<String> COMPONENT_MODEL_PROJECTS = ImmutableSet.of(
+ "componentModel",
+ "ndkSanAngeles2",
+ "ndkVariants");
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/DeviceTests.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/DeviceTests.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/DeviceTests.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/DeviceTests.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/SmokeTests.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/SmokeTests.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/SmokeTests.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/category/SmokeTests.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/Adb.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/Adb.java
new file mode 100644
index 0000000..6465c4f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/Adb.java
@@ -0,0 +1,118 @@
+/*
+ * 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.build.gradle.integration.common.fixture;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.integration.common.utils.DeviceHelper;
+import com.android.build.gradle.integration.common.utils.SdkHelper;
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+import com.android.sdklib.AndroidVersion;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Utilities for handling real devices.
+ *
+ * This is of the form of a test rule so it can cache things and do clean up when devices are used
+ * from a central pool.
+ */
+public class Adb implements TestRule {
+
+ private Adb() {
+
+ }
+
+ public static Adb create() {
+ return new Adb();
+ }
+
+ @Override
+ public Statement apply(@NonNull final Statement base, @NonNull Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ }
+ };
+ }
+
+ public IDevice getDevice(@NonNull Matcher<AndroidVersion> matcher) {
+ IDevice[] devices = getBridge().getDevices();
+ for (IDevice device: devices) {
+ if (matcher.matches(device.getVersion())) {
+ return device;
+ }
+ }
+
+ // Failed to find, make a pretty error message.
+ StringBuilder errorMessage = new StringBuilder("Test requires device that matches ")
+ .append(StringDescription.toString(matcher)).append("\nConnected Devices:");
+ for (IDevice device: devices) {
+ errorMessage.append(" ").append(device).append(": ");
+ StringDescription mismatch = new StringDescription();
+ matcher.describeMismatch(device.getVersion(), mismatch);
+ errorMessage.append(mismatch.toString()).append('\n');
+ }
+
+ throw new AssertionError(errorMessage);
+ }
+
+ private static Supplier<AndroidDebugBridge> sAdbGetter = Suppliers.memoizeWithExpiration(
+ new Supplier<AndroidDebugBridge>() {
+ @Override
+ public AndroidDebugBridge get() {
+ AndroidDebugBridge.init(false /*clientSupport*/);
+ AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
+ SdkHelper.getAdb().getAbsolutePath(), false /*forceNewBridge*/);
+ assertNotNull("Debug bridge", bridge);
+ long timeOut = DeviceHelper.DEFAULT_ADB_TIMEOUT_MSEC;
+ int sleepTime = 1000;
+ while (!bridge.hasInitialDeviceList() && timeOut > 0) {
+ try {
+ Thread.sleep(sleepTime);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ timeOut -= sleepTime;
+ }
+
+ if (timeOut <= 0 && !bridge.hasInitialDeviceList()) {
+ throw new RuntimeException("Timeout getting device list.");
+ }
+ return bridge;
+ }
+ },
+ 10, TimeUnit.MINUTES);
+
+
+ private static AndroidDebugBridge getBridge() {
+ return sAdbGetter.get();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetAndroidModelAction.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetAndroidModelAction.java
new file mode 100644
index 0000000..1367587
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetAndroidModelAction.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.fixture;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.gradle.BasicGradleProject;
+import org.gradle.tooling.model.gradle.GradleBuild;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * a Build Action that returns all the models of the parameterized type for all the Gradle projects
+ */
+public class GetAndroidModelAction<T> implements BuildAction<Map<String, T>> {
+
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+
+ private final Class<T> type;
+
+ // Determines whether models are fetched with multiple threads.
+ private final boolean isMultiThreaded;
+
+ public GetAndroidModelAction(Class<T> type) {
+ this(type, true);
+ }
+
+ public GetAndroidModelAction(Class<T> type, boolean isMultiThreaded) {
+ this.type = type;
+ this.isMultiThreaded = isMultiThreaded;
+ }
+
+ @Override
+ public Map<String, T> execute(BuildController buildController) {
+
+ long t1 = System.currentTimeMillis();
+ GradleBuild gradleBuild = buildController.getBuildModel();
+ DomainObjectSet<? extends BasicGradleProject> projects = gradleBuild.getProjects();
+
+ final int projectCount = projects.size();
+ Map<String, T> modelMap = Maps.newHashMapWithExpectedSize(projectCount);
+
+ List<BasicGradleProject> projectList = Lists.newArrayList(projects);
+ List<Thread> threads = Lists.newArrayListWithCapacity(CPU_COUNT);
+ List<ModelQuery> queries = Lists.newArrayListWithCapacity(CPU_COUNT);
+
+
+ if (isMultiThreaded) {
+ for (int i = 0; i < CPU_COUNT; i++) {
+ ModelQuery modelQuery = new ModelQuery(
+ projectList,
+ buildController);
+ queries.add(modelQuery);
+ Thread t = new Thread(modelQuery);
+ threads.add(t);
+ t.start();
+ }
+
+ for (int i = 0; i < CPU_COUNT; i++) {
+ try {
+ threads.get(i).join();
+ ModelQuery modelQuery = queries.get(i);
+ modelMap.putAll(modelQuery.getModels());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ } else {
+ ModelQuery modelQuery = new ModelQuery(
+ projectList,
+ buildController);
+ modelQuery.run();
+ modelMap.putAll(modelQuery.getModels());
+ }
+
+ long t2 = System.currentTimeMillis();
+ System.out.println("GetAndroidModelAction: " + (t2-t1) + "ms");
+
+ return modelMap;
+ }
+
+ // index used by threads to get the new project to query.
+ private volatile int currentIndex = 0;
+
+ protected synchronized int getNextIndex() {
+ return currentIndex++;
+ }
+
+ class ModelQuery implements Runnable {
+
+ private final Map<String, T> models;
+ private final List<BasicGradleProject> projects;
+ private final BuildController buildController;
+
+ public ModelQuery(
+ List<BasicGradleProject> projects,
+ BuildController buildController) {
+ this.projects = projects;
+ this.buildController = buildController;
+
+ models = Maps.newHashMapWithExpectedSize(projects.size() / CPU_COUNT);
+ }
+
+ public Map<String, T> getModels() {
+ return models;
+ }
+
+ @Override
+ public void run() {
+ final int count = projects.size();
+
+ int index;
+ while ((index = getNextIndex()) < count) {
+ BasicGradleProject project = projects.get(index);
+ T model = buildController.findModel(project, type);
+ if (model != null) {
+ models.put(project.getPath(), model);
+ }
+ }
+ }
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetEmptyModelAction.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetEmptyModelAction.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetEmptyModelAction.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GetEmptyModelAction.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GradleTestProject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GradleTestProject.java
new file mode 100644
index 0000000..3755ac9
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/GradleTestProject.java
@@ -0,0 +1,1319 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.fixture;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.fixture.app.AbstractAndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile;
+import com.android.build.gradle.integration.common.utils.JacocoAgent;
+import com.android.build.gradle.integration.common.utils.SdkHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.core.BuilderConstants;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.NativeAndroidProject;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.model.Version;
+import com.android.ide.common.util.ReferenceHolder;
+import com.android.io.StreamException;
+import com.android.repository.Revision;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.gradle.tooling.BuildAction;
+import org.gradle.tooling.BuildActionExecuter;
+import org.gradle.tooling.BuildLauncher;
+import org.gradle.tooling.GradleConnectionException;
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.LongRunningOperation;
+import org.gradle.tooling.ProjectConnection;
+import org.gradle.tooling.ResultHandler;
+import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.GradleTask;
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * JUnit4 test rule for integration test.
+ *
+ * This rule create a gradle project in a temporary directory.
+ * It can be use with the @Rule or @ClassRule annotations. Using this class with @Rule will create
+ * a gradle project in separate directories for each unit test, whereas using it with @ClassRule
+ * creates a single gradle project.
+ *
+ * The test directory is always deleted if it already exists at the start of the test to ensure a
+ * clean environment.
+ */
+public class GradleTestProject implements TestRule {
+
+ public static final File TEST_RES_DIR = new File("src/test/resources");
+ public static final File TEST_PROJECT_DIR = new File("test-projects");
+
+ public static final int DEFAULT_COMPILE_SDK_VERSION = 23;
+ public static final int LATEST_NDK_VERSION = 21;
+
+ /**
+ * Last SDK that contained java 6 bytecode in the platform jar. Since we run integration tests
+ * on Java 6, this is needed to cover unit testing support.
+ */
+ public static final int LAST_JAVA6_SDK_VERSION = 19;
+
+ public static final String DEFAULT_BUILD_TOOL_VERSION;
+ public static final String REMOTE_TEST_PROVIDER = System.getenv().get("REMOTE_TEST_PROVIDER");
+
+ public static final String DEVICE_PROVIDER_NAME = REMOTE_TEST_PROVIDER != null ?
+ REMOTE_TEST_PROVIDER : BuilderConstants.CONNECTED;
+
+ public static final String GRADLE_TEST_VERSION = "2.10";
+ public static final String GRADLE_EXP_TEST_VERSION = "2.10";
+
+ public static final String ANDROID_GRADLE_PLUGIN_VERSION;
+
+ public static final String CUSTOM_JACK;
+ public static final boolean USE_JACK;
+
+
+ private static final String RECORD_BENCHMARK_NAME = "com.android.benchmark.name";
+ private static final String RECORD_BENCHMARK_MODE = "com.android.benchmark.mode";
+
+ public enum BenchmarkMode {
+ EVALUATION, SYNC, BUILD_FULL, BUILD_INC_JAVA, BUILD_INC_RES_EDIT, BUILD_INC_RES_ADD
+ }
+
+ static {
+ String envBuildToolVersion = System.getenv("CUSTOM_BUILDTOOLS");
+ DEFAULT_BUILD_TOOL_VERSION = !Strings.isNullOrEmpty(envBuildToolVersion) ?
+ envBuildToolVersion : "23.0.2";
+ String envVersion = System.getenv().get("CUSTOM_GRADLE");
+ ANDROID_GRADLE_PLUGIN_VERSION = !Strings.isNullOrEmpty(envVersion) ? envVersion
+ : Version.ANDROID_GRADLE_PLUGIN_VERSION;
+ String envJack = System.getenv().get("CUSTOM_JACK");
+ CUSTOM_JACK = !Strings.isNullOrEmpty(envJack) ? envJack : "false";
+ USE_JACK = Boolean.parseBoolean(CUSTOM_JACK);
+ }
+
+ private static final String COMMON_HEADER = "commonHeader.gradle";
+ private static final String COMMON_LOCAL_REPO = "commonLocalRepo.gradle";
+ private static final String COMMON_BUILD_SCRIPT = "commonBuildScript.gradle";
+ private static final String COMMON_BUILD_SCRIPT_EXP = "commonBuildScriptExperimental.gradle";
+ private static final String COMMON_GRADLE_PLUGIN_VERSION = "commonGradlePluginVersion.gradle";
+ private static final String DEFAULT_TEST_PROJECT_NAME = "project";
+
+ private static final ResultHandler<Void> EXPECT_SUCCESS = new ResultHandler<Void>() {
+ @Override
+ public void onComplete(Void aVoid) {
+ // OK, that's what we want.
+ }
+
+ @Override
+ public void onFailure(GradleConnectionException e) {
+ throw e;
+ }
+ };
+
+ public static class Builder {
+
+ @Nullable
+ private String name;
+
+ @Nullable
+ private TestProject testProject = null;
+
+ @Nullable
+ File sdkDir = SdkHelper.findSdkDir();
+ @Nullable
+ File ndkDir = findNdkDir();
+ boolean captureStdOut = false;
+ boolean captureStdErr = false;
+ boolean experimentalMode = false;
+ @Nullable
+ private String targetGradleVersion;
+
+ boolean useJack = false;
+ boolean useMinify = false;
+ @NonNull
+ private List<String> gradleProperties = Lists.newArrayList();
+ @Nullable
+ private String heapSize;
+
+ /**
+ * Create a GradleTestProject.
+ */
+ public GradleTestProject create() {
+ if (targetGradleVersion == null) {
+ targetGradleVersion =
+ experimentalMode ? GRADLE_EXP_TEST_VERSION : GRADLE_TEST_VERSION;
+ }
+ return new GradleTestProject(
+ name,
+ testProject,
+ experimentalMode,
+ useMinify,
+ useJack,
+ targetGradleVersion,
+ captureStdOut,
+ captureStdErr,
+ sdkDir,
+ ndkDir,
+ gradleProperties,
+ heapSize);
+ }
+
+ /**
+ * Set the name of the project.
+ *
+ * Necessary if you have multiple projects in a test class.
+ */
+ public Builder withName(@NonNull String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder captureStdOut(boolean captureStdOut) {
+ this.captureStdOut = captureStdOut;
+ return this;
+ }
+
+ public Builder captureStdErr(boolean captureStdErr) {
+ this.captureStdErr = captureStdErr;
+ return this;
+ }
+
+ /**
+ * Use experimental plugin for the test project.
+ */
+ public Builder forExperimentalPlugin(boolean mode) {
+ this.experimentalMode = mode;
+ return this;
+ }
+
+ /**
+ * Use the gradle version for experimental plugin, but the test project do not necessarily
+ * have to use experimental plugin.
+ */
+ public Builder useExperimentalGradleVersion(boolean mode) {
+ if (mode) {
+ targetGradleVersion = GRADLE_EXP_TEST_VERSION;
+ }
+ return this;
+ }
+
+ /**
+ * Create a project without setting ndk.dir in local.properties.
+ */
+ public Builder withoutNdk() {
+ this.ndkDir = null;
+ return this;
+ }
+
+ /**
+ * Create GradleTestProject from a TestProject.
+ */
+ public Builder fromTestApp(@NonNull TestProject testProject) {
+ this.testProject = testProject;
+ return this;
+ }
+
+ /**
+ * Create GradleTestProject from an existing test project.
+ */
+ public Builder fromTestProject(@NonNull String project) {
+ return fromTestProject(project, null);
+ }
+
+ /**
+ * Create GradleTestProject from an existing test project.
+ * @param buildFileSuffix if non-null, imports files named build.suffix.gradle.
+ */
+ public Builder fromTestProject(@NonNull String project, @Nullable String buildFileSuffix) {
+ AndroidTestApp app = new EmptyTestApp();
+ name = project;
+ File projectDir = new File(TEST_PROJECT_DIR, project);
+ addAllFiles(app, projectDir, buildFileSuffix);
+ return fromTestApp(app);
+ }
+
+ /**
+ * Create GradleTestProject from an existing test project.
+ */
+ public Builder fromExternalProject(@NonNull String project) throws IOException {
+ AndroidTestApp app = new EmptyTestApp();
+ name = project;
+ // compute the root folder of the checkout, based on test-projects.
+ File parentDir = TEST_PROJECT_DIR.getCanonicalFile().getParentFile().getParentFile()
+ .getParentFile().getParentFile().getParentFile();
+ parentDir = new File(parentDir, "external");
+ File projectDir = new File(parentDir, project);
+ addAllFiles(app, projectDir, null /*buildFileSuffix*/);
+ return fromTestApp(app);
+ }
+
+ /**
+ * Add gradle properties.
+ */
+ public Builder addGradleProperties(@NonNull String property) {
+ gradleProperties.add(property);
+ return this;
+ }
+
+ /**
+ * Sets the test heap size requirement. Example values : 1024m, 2048m...
+ *
+ * @param heapSize the heap size in a format understood by the -Xmx JVM parameter
+ * @return itself.
+ */
+ public Builder withHeap(String heapSize) {
+ this.heapSize = heapSize;
+ return this;
+ }
+
+ public Builder withJack(boolean useJack) {
+ this.useJack = useJack;
+ return this;
+ }
+
+ public Builder withMinify(boolean useMinify) {
+ this.useMinify = useMinify;
+ return this;
+ }
+
+ private static class EmptyTestApp extends AbstractAndroidTestApp {
+ @Override
+ public boolean containsFullBuildScript() {
+ return true;
+ }
+ }
+ }
+
+ private final String name;
+ private final File outDir;
+ private File testDir;
+ private File sourceDir;
+ private File buildFile;
+ private final File ndkDir;
+ private final File sdkDir;
+
+ private final ByteArrayOutputStream stdout;
+ private final ByteArrayOutputStream stderr;
+
+ private final Collection<String> gradleProperties;
+
+ @Nullable
+ private final TestProject testProject;
+
+ private final boolean experimentalMode;
+ private final String targetGradleVersion;
+
+ private final boolean useJack;
+ private final boolean minifyEnabled;
+
+ @Nullable
+ private String heapSize;
+
+ private GradleTestProject(
+ @Nullable String name,
+ @Nullable TestProject testProject,
+ boolean experimentalMode,
+ boolean minifyEnabled,
+ boolean useJack,
+ String targetGradleVersion,
+ boolean captureStdOut,
+ boolean captureStdErr,
+ @Nullable File sdkDir,
+ @Nullable File ndkDir,
+ @NonNull Collection<String> gradleProperties,
+ @Nullable String heapSize) {
+ String buildDir = System.getenv("PROJECT_BUILD_DIR");
+ outDir = (buildDir == null) ? new File("build/tests") : new File(buildDir, "tests");
+ testDir = null;
+ buildFile = sourceDir = null;
+ this.name = (name == null) ? DEFAULT_TEST_PROJECT_NAME : name;
+ this.experimentalMode = experimentalMode;
+ this.minifyEnabled = minifyEnabled;
+ this.useJack = useJack;
+ this.targetGradleVersion = targetGradleVersion;
+ this.testProject = testProject;
+ stdout = captureStdOut ? new ByteArrayOutputStream() : null;
+ stderr = captureStdErr ? new ByteArrayOutputStream() : null;
+ this.sdkDir = sdkDir;
+ this.ndkDir = ndkDir;
+ this.heapSize = heapSize;
+ this.gradleProperties = gradleProperties;
+ }
+
+ /**
+ * Create a GradleTestProject representing a subProject of another GradleTestProject.
+ * @param subProject name of the subProject.
+ * @param rootProject root GradleTestProject.
+ */
+ private GradleTestProject(
+ @NonNull String subProject,
+ @NonNull GradleTestProject rootProject) {
+ name = subProject;
+ outDir = rootProject.outDir;
+
+ testDir = new File(rootProject.testDir, subProject);
+ assertTrue("No subproject dir at " + testDir.toString(), testDir.isDirectory());
+
+ buildFile = new File(testDir, "build.gradle");
+ sourceDir = new File(testDir, "src");
+ ndkDir = rootProject.ndkDir;
+ sdkDir = rootProject.sdkDir;
+ stdout = rootProject.stdout;
+ stderr = rootProject.stdout;
+ gradleProperties = ImmutableList.of();
+ testProject = null;
+ experimentalMode = rootProject.isExperimentalMode();
+ targetGradleVersion = rootProject.getTargetGradleVersion();
+ minifyEnabled = false;
+ useJack = false;
+ }
+
+ String getTargetGradleVersion() {
+ return targetGradleVersion;
+ }
+
+ boolean isExperimentalMode() {
+ return experimentalMode;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Recursively delete directory or file.
+ *
+ * @param root directory to delete
+ */
+ private static void deleteRecursive(File root) {
+ if (root.exists()) {
+ if (root.isDirectory()) {
+ File files[] = root.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ deleteRecursive(file);
+ }
+ }
+ }
+ assertTrue(root.delete());
+ }
+ }
+
+ private static final Pattern BUILD_FILE_PATTERN = Pattern.compile("build\\.(.+\\.)?gradle");
+
+ /**
+ * Add all files in a directory to an AndroidTestApp.
+ */
+ private static void addAllFiles(AndroidTestApp app, File projectDir, String buildFileSuffix) {
+ String buildFileNameToKeep = (buildFileSuffix != null) ?
+ "build." + buildFileSuffix + ".gradle" : "build.gradle";
+ for (String filePath : TestFileUtils.listFiles(projectDir)) {
+ File file = new File(filePath);
+ try {
+ String fileName = file.getName();
+ if (BUILD_FILE_PATTERN.matcher(fileName).matches()) {
+ if (!fileName.equals(buildFileNameToKeep)) {
+ continue;
+ }
+ fileName = "build.gradle";
+ }
+ app.addFile(
+ new TestSourceFile(
+ file.getParent(),
+ fileName,
+ Files.toByteArray(new File(projectDir, filePath))));
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ }
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ createTestDirectory(description.getTestClass(), description.getMethodName());
+ base.evaluate();
+ }
+ };
+ }
+
+ public void createTestDirectory(Class<?> testClass, String methodName)
+ throws IOException, StreamException {
+ // On windows, move the temporary copy as close to root to avoid running into path too
+ // long exceptions.
+ testDir = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS
+ ? new File(new File(new File(System.getProperty("user.home")), "android-tests"),
+ testClass.getSimpleName())
+ : new File(outDir, testClass.getSimpleName());
+
+ // Create separate directory based on test method name if @Rule is used.
+ // getMethodName() is null if this rule is used as a @ClassRule.
+ if (methodName != null) {
+ String dirName = methodName;
+ dirName = dirName.replaceAll("[^a-zA-Z0-9_]", "_");
+ testDir = new File(testDir, dirName);
+ }
+ testDir = new File(testDir, name);
+
+ buildFile = new File(testDir, "build.gradle");
+ sourceDir = new File(testDir, "src");
+
+ if (testDir.exists()) {
+ deleteRecursive(testDir);
+ }
+ FileUtils.mkdirs(testDir);
+ FileUtils.mkdirs(sourceDir);
+
+ Files.copy(
+ new File(TEST_PROJECT_DIR, COMMON_HEADER),
+ new File(testDir.getParent(), COMMON_HEADER));
+ Files.copy(
+ new File(TEST_PROJECT_DIR, COMMON_LOCAL_REPO),
+ new File(testDir.getParent(), COMMON_LOCAL_REPO));
+ Files.copy(
+ new File(TEST_PROJECT_DIR, COMMON_BUILD_SCRIPT),
+ new File(testDir.getParent(), COMMON_BUILD_SCRIPT));
+ Files.copy(
+ new File(TEST_PROJECT_DIR, COMMON_BUILD_SCRIPT_EXP),
+ new File(testDir.getParent(), COMMON_BUILD_SCRIPT_EXP));
+ Files.copy(
+ new File(TEST_PROJECT_DIR, COMMON_GRADLE_PLUGIN_VERSION),
+ new File(testDir.getParent(), COMMON_GRADLE_PLUGIN_VERSION));
+
+ if (testProject != null) {
+ testProject.write(
+ testDir,
+ testProject.containsFullBuildScript() ? "" :getGradleBuildscript());
+ } else {
+ Files.write(
+ getGradleBuildscript(),
+ buildFile,
+ Charsets.UTF_8);
+ }
+
+ createLocalProp(testDir, sdkDir, ndkDir);
+ createGradleProp();
+ }
+
+ /**
+ * Create a GradleTestProject representing a subproject.
+ */
+ public GradleTestProject getSubproject(String name) {
+ if (name.startsWith(":")) {
+ name = name.substring(1);
+ }
+ return new GradleTestProject(name, this);
+ }
+
+ /**
+ * Return the name of the test project.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the directory containing the test project.
+ */
+ public File getTestDir() {
+ return testDir;
+ }
+
+ /**
+ * Return the build.gradle of the test project.
+ */
+ public File getSettingsFile() {
+ return new File(testDir, "settings.gradle");
+ }
+
+ /**
+ * Return the build.gradle of the test project.
+ */
+ public File getBuildFile() {
+ return buildFile;
+ }
+
+ /**
+ * Return the output directory from Android plugins.
+ */
+ public File getOutputDir() {
+ return new File(testDir,
+ Joiner.on(File.separator).join("build", AndroidProject.FD_OUTPUTS));
+ }
+
+ /**
+ * Return a File under the output directory from Android plugins.
+ */
+ public File getOutputFile(String path) {
+ return new File(getOutputDir(), path);
+ }
+
+ /**
+ * Return the output apk File from the application plugin for the given dimension.
+ *
+ * Expected dimensions orders are:
+ * - product flavors
+ * - build type
+ * - other modifiers (e.g. "unsigned", "aligned")
+ */
+ public File getApk(String ... dimensions) {
+ // TODO: Add overload for tests and splits.
+ List<String> dimensionList = Lists.newArrayListWithExpectedSize(1 + dimensions.length);
+ dimensionList.add(getName());
+ dimensionList.addAll(Arrays.asList(dimensions));
+ return getOutputFile(
+ "apk/" + Joiner.on("-").join(dimensionList) + SdkConstants.DOT_ANDROID_PACKAGE);
+ }
+
+ public File getTestApk(String ... dimensions) {
+ String[] allDimensions = new String[dimensions.length + 2];
+ System.arraycopy(dimensions, 0, allDimensions, 0, dimensions.length);
+ allDimensions[allDimensions.length - 2] = "androidTest";
+ allDimensions[allDimensions.length - 1] = "unaligned";
+ return getApk(allDimensions);
+ }
+
+ /**
+ * Return the output aar File from the library plugin for the given dimension.
+ *
+ * Expected dimensions orders are:
+ * - product flavors
+ * - build type
+ * - other modifiers (e.g. "unsigned", "aligned")
+ */
+ public File getAar(String ... dimensions) {
+ // TODO: Add overload for tests and splits.
+ List<String> dimensionList = Lists.newArrayListWithExpectedSize(1 + dimensions.length);
+ dimensionList.add(getName());
+ dimensionList.addAll(Arrays.asList(dimensions));
+ return getOutputFile("aar/" + Joiner.on("-").join(dimensionList) + SdkConstants.DOT_AAR);
+ }
+
+ /**
+ * Returns the SDK dir
+ */
+ public File getSdkDir() {
+ return sdkDir;
+ }
+
+ /**
+ * Returns the NDK dir
+ */
+ public File getNdkDir() {
+ return ndkDir;
+ }
+
+ /**
+ * Returns a string that contains the gradle buildscript content
+ */
+ public String getGradleBuildscript() {
+ return "apply from: \"../commonHeader.gradle\"\n" +
+ "buildscript { apply from: \"../commonBuildScript" +
+ (experimentalMode ? "Experimental" : "") + ".gradle\", to: buildscript }\n" +
+ "\n" +
+ "apply from: \"../commonLocalRepo.gradle\"\n";
+ }
+
+ /**
+ * Return a list of all task names of the project.
+ */
+ public List<String> getTaskList() {
+ ProjectConnection connection = getProjectConnection();
+ try {
+ GradleProject project = connection.getModel(GradleProject.class);
+ List<String> tasks = Lists.newArrayList();
+ for (GradleTask gradleTask : project.getTasks()) {
+ tasks.add(gradleTask.getName());
+ }
+ return tasks;
+ } finally {
+ connection.close();
+ }
+
+ }
+
+ /**
+ * Runs gradle on the project. Throws exception on failure.
+ *
+ * @param tasks Variadic list of tasks to execute.
+ */
+ public void execute(String ... tasks) {
+ execute(AndroidProject.class,
+ Collections.<String>emptyList(),
+ false,
+ false,
+ EXPECT_SUCCESS,
+ tasks);
+ }
+
+ public void execute(@NonNull List<String> arguments, String ... tasks) {
+ execute(AndroidProject.class,
+ arguments,
+ false,
+ false,
+ EXPECT_SUCCESS,
+ tasks);
+ }
+
+ public void executeWithBenchmark(
+ @NonNull String benchmarkName,
+ @NonNull BenchmarkMode benchmarkMode,
+ String ... tasks) {
+ List<String> arguments = ImmutableList.of(
+ "-P" + RECORD_BENCHMARK_NAME + "=" + benchmarkName,
+ "-P" + RECORD_BENCHMARK_MODE + "=" + benchmarkMode.name().toLowerCase(Locale.US)
+ );
+ execute(AndroidProject.class,
+ arguments,
+ false,
+ false,
+ EXPECT_SUCCESS,
+ tasks);
+ }
+
+ public GradleConnectionException executeExpectingFailure(@NonNull String... tasks) {
+ return executeExpectingFailure(Collections.<String>emptyList(), tasks);
+ }
+
+ public GradleConnectionException executeExpectingFailure(
+ @NonNull final List<String> arguments,
+ final String... tasks) {
+ final ReferenceHolder<GradleConnectionException> result = ReferenceHolder.empty();
+
+ execute(AndroidProject.class,
+ arguments,
+ false /*returnModel*/,
+ false /*emulateStudio_1_0*/,
+ new ResultHandler<Void>() {
+ @Override
+ public void onComplete(Void aVoid) {
+ throw new AssertionError(
+ String.format(
+ "Expecting build to fail:\n" +
+ " Tasks: %s\n" +
+ " Arguments: %s",
+ Joiner.on(' ').join(tasks),
+ Joiner.on(' ').join(arguments)));
+ }
+
+ @Override
+ public void onFailure(GradleConnectionException e) {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ result.setValue(e);
+ }
+ },
+ tasks);
+
+ return result.getValue();
+ }
+
+ public void executeConnectedCheck() {
+ executeConnectedCheck(Collections.<String>emptyList());
+ }
+
+ public void executeConnectedCheck(List<String> arguments) {
+ //noinspection VariableNotUsedInsideIf
+ execute(arguments, REMOTE_TEST_PROVIDER == null ? "connectedCheck" : "deviceCheck");
+ }
+
+ /**
+ * Runs gradle on the project, and returns the project model. Throws exception on failure.
+ *
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the AndroidProject model for the project.
+ */
+ @NonNull
+ public AndroidProject executeAndReturnModel(String ... tasks) {
+ return executeAndReturnModel(false, tasks);
+ }
+
+ /**
+ * Runs gradle on the project, and returns the model of the specified type.
+ * Throws exception on failure.
+ *
+ * @param modelClass Class of the model to return
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the model for the project with the specified type.
+ */
+ @NonNull
+ public <T> T executeAndReturnModel(Class<T> modelClass, String ... tasks) {
+ return this.executeAndReturnModel(modelClass, false, tasks);
+ }
+
+ /**
+ * Runs gradle on the project, and returns the project model. Throws exception on failure.
+ *
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the AndroidProject model for the project.
+ */
+ @NonNull
+ public AndroidProject executeAndReturnModel(boolean emulateStudio_1_0, String ... tasks) {
+ //noinspection ConstantConditions
+ return execute(AndroidProject.class, Collections.<String>emptyList(), true, emulateStudio_1_0,
+ EXPECT_SUCCESS, tasks);
+ }
+
+ /**
+ * Runs gradle on the project, and returns the project model. Throws exception on failure.
+ *
+ * @param modelClass Class of the model to return
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the AndroidProject model for the project.
+ */
+ @NonNull
+ public <T> T executeAndReturnModel(
+ Class<T> modelClass,
+ boolean emulateStudio_1_0,
+ String ... tasks) {
+ //noinspection ConstantConditions
+ return execute(modelClass, Collections.<String>emptyList(), true, emulateStudio_1_0,
+ EXPECT_SUCCESS, tasks);
+ }
+
+ /**
+ * Runs gradle on the project, and returns a project model for each sub-project.
+ * Throws exception on failure.
+ *
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the AndroidProject model for the project.
+ */
+ @NonNull
+ public Map<String, AndroidProject> executeAndReturnMultiModel(String ... tasks) {
+ return executeAndReturnMultiModel(false, tasks);
+ }
+
+ /**
+ * Runs gradle on the project, and returns a project model for each sub-project.
+ * Throws exception on failure.
+ *
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the AndroidProject model for the project.
+ */
+ @NonNull
+ public <T> Map<String, T> executeAndReturnMultiModel(Class<T> modelClass, String ... tasks) {
+ return executeAndReturnMultiModel(modelClass, false, tasks);
+ }
+
+ /**
+ * Runs gradle on the project, and returns a AndroidProject model for each sub-project.
+ * Throws exception on failure.
+ *
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the AndroidProject model for the project.
+ */
+ @NonNull
+ public Map<String, AndroidProject> executeAndReturnMultiModel(
+ boolean emulateStudio_1_0,
+ String ... tasks) {
+ return executeAndReturnMultiModel(AndroidProject.class, emulateStudio_1_0, tasks);
+ }
+
+ /**
+ * Runs gradle on the project, and returns a project model for each sub-project.
+ * Throws exception on failure.
+ *
+ * @param modelClass Class of the model to return
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the AndroidProject model for the project.
+ */
+ @NonNull
+ public <T> Map<String, T> executeAndReturnMultiModel(
+ Class<T> modelClass,
+ boolean emulateStudio_1_0,
+ String ... tasks) {
+ ProjectConnection connection = getProjectConnection();
+ try {
+ executeBuild(Collections.<String>emptyList(), connection, tasks,
+ EXPECT_SUCCESS);
+
+ // TODO: Make buildModel multithreaded all the time.
+ // Getting multiple NativeAndroidProject results in duplicated class implemented error
+ // in a multithreaded environment. This is due to issues in Gradle relating to the
+ // automatic generation of the implementation class of NativeSourceSet. Make this
+ // multithreaded when the issue is resolved.
+ boolean isMultithreaded = !NativeAndroidProject.class.equals(modelClass);
+
+ return buildModel(
+ connection,
+ new GetAndroidModelAction<T>(modelClass, isMultithreaded),
+ emulateStudio_1_0,
+ null,
+ null);
+
+ } finally {
+ connection.close();
+ }
+ }
+
+ /**
+ * Returns the project model without building.
+ *
+ * This will fail if the project is a multi-project setup or if there are any sync issues
+ * while loading the project.
+ */
+ @NonNull
+ public AndroidProject getSingleModel() {
+ return getSingleModel(false /* emulateStudio_1_0 */, true /*assertNodSyncIssues */);
+ }
+
+ /**
+ * Returns the project model without building, querying it the way Studio 1.0 does.
+ *
+ * This will fail if the project is a multi-project setup or if there are any sync issues
+ * while loading the project.
+ */
+ @NonNull
+ public AndroidProject getSingleModelAsStudio1() {
+ return getSingleModel(true /* emulateStudio_1_0 */, true /*assertNodSyncIssues */);
+ }
+
+ /**
+ * Returns the project model without building.
+ *
+ * This will fail if the project is a multi-project setup.
+ */
+ @NonNull
+ public AndroidProject getSingleModelIgnoringSyncIssues() {
+ return getSingleModel(false /* emulateStudio_1_0 */, false /*assertNodSyncIssues */);
+ }
+
+ /**
+ * Returns the project model without building.
+ *
+ * This will fail if the project is a multi-project setup.
+ *
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param assertNoSyncIssues true if the presence of sync issues during the model evaluation
+ * should raise a {@link AssertionError}s
+ */
+ @NonNull
+ private AndroidProject getSingleModel(boolean emulateStudio_1_0, boolean assertNoSyncIssues) {
+ ProjectConnection connection = getProjectConnection();
+ try {
+ Map<String, AndroidProject> modelMap = buildModel(
+ connection,
+ new GetAndroidModelAction<AndroidProject>(AndroidProject.class),
+ emulateStudio_1_0,
+ null,
+ null);
+
+ // ensure there was only one project
+ assertEquals("Quering GradleTestProject.getModel() with multi-project settings",
+ 1, modelMap.size());
+
+ AndroidProject androidProject = modelMap.get(":");
+ if (assertNoSyncIssues) {
+ assertNoSyncIssues(androidProject);
+ }
+ return androidProject;
+ } finally {
+ connection.close();
+ }
+ }
+
+ /**
+ * Returns a project model for each sub-project without building generating a
+ * {@link AssertionError} if any sync issue is raised during the model loading.
+ */
+ @NonNull
+ public Map<String, AndroidProject> getAllModels() {
+ return getAllModelsWithBenchmark(null, null);
+ }
+
+ /**
+ * Returns a project model for each sub-project without building generating a
+ * @param benchmarkName optional benchmark name to pass to Gradle
+ * @param benchmarkMode optional benchmark mode to pass to gradle.
+
+ * {@link AssertionError} if any sync issue is raised during the model loading.
+ */
+ @NonNull
+ public Map<String, AndroidProject> getAllModelsWithBenchmark(
+ @Nullable String benchmarkName,
+ @Nullable BenchmarkMode benchmarkMode) {
+ Map<String, AndroidProject> allModels = getAllModels(
+ new GetAndroidModelAction<AndroidProject>(AndroidProject.class),
+ false,
+ benchmarkName,
+ benchmarkMode);
+ for (AndroidProject project : allModels.values()) {
+ assertNoSyncIssues(project);
+ }
+ return allModels;
+ }
+
+
+ private static void assertNoSyncIssues(AndroidProject project) {
+ if (!project.getSyncIssues().isEmpty()) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Project ")
+ .append(project.getName())
+ .append(" had sync issues :\n");
+ for (SyncIssue syncIssue : project.getSyncIssues()) {
+ msg.append(syncIssue);
+ msg.append("\n");
+ }
+ fail(msg.toString());
+ }
+ }
+
+ /**
+ * Returns a project model for each sub-project without building and ignoring potential sync
+ * issues. Sync issues will still be returned for each {@link AndroidProject} that failed to
+ * sync properly.
+ */
+ @NonNull
+ public Map<String, AndroidProject> getAllModelsIgnoringSyncIssues() {
+ return getAllModels(new GetAndroidModelAction<AndroidProject>(AndroidProject.class), false, null, null);
+ }
+
+ /**
+ * Returns a project model for each sub-project without building.
+ *
+ * @param action the build action to gather the model
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param benchmarkName optional benchmark name to pass to Gradle
+ * @param benchmarkMode optional benchmark mode to pass to gradle.
+ */
+ @NonNull
+ public <K, V> Map<K, V> getAllModels(
+ @NonNull BuildAction<Map<K, V>> action,
+ boolean emulateStudio_1_0,
+ @Nullable String benchmarkName,
+ @Nullable BenchmarkMode benchmarkMode) {
+ ProjectConnection connection = getProjectConnection();
+ try {
+ return buildModel(connection, action, emulateStudio_1_0, benchmarkName, benchmarkMode);
+
+ } finally {
+ connection.close();
+ }
+ }
+
+ /**
+ * Runs gradle on the project. Throws exception on failure.
+ *
+ * @param arguments List of arguments for the gradle command.
+ * @param returnModel whether the model should be queried and returned.
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param resultHandler Result handler that should verify the result (either success or failure
+ * is what the caller expects.
+ * @param tasks Variadic list of tasks to execute.
+ *
+ * @return the model, if <var>returnModel</var> was true, null otherwise
+ */
+ @Nullable
+ private <T> T execute(
+ Class<T> type,
+ @NonNull List<String> arguments,
+ boolean returnModel,
+ boolean emulateStudio_1_0,
+ ResultHandler<Void> resultHandler,
+ @NonNull String ... tasks) {
+ ProjectConnection connection = getProjectConnection();
+ try {
+ executeBuild(arguments, connection, tasks, resultHandler);
+
+ if (returnModel) {
+ Map<String, T> modelMap = this.buildModel(
+ connection,
+ new GetAndroidModelAction<T>(type),
+ emulateStudio_1_0,
+ null,
+ null);
+
+ // ensure there was only one project
+ assertEquals("Quering GradleTestProject.getModel() with multi-project settings",
+ 1, modelMap.size());
+
+ return modelMap.get(":");
+ }
+ } finally {
+ connection.close();
+ }
+
+ return null;
+ }
+
+ private static void syncFileSystem() {
+ try {
+ if (System.getProperty("os.name").contains("Linux")) {
+ if (Runtime.getRuntime().exec("/bin/sync").waitFor() != 0) {
+ throw new IOException("Failed to sync file system.");
+ }
+ }
+ } catch (IOException e) {
+ System.err.println(Throwables.getStackTraceAsString(e));
+ } catch (InterruptedException e) {
+ System.err.println(Throwables.getStackTraceAsString(e));
+ }
+ }
+
+ private void executeBuild(
+ final List<String> arguments,
+ ProjectConnection connection,
+ final String[] tasks,
+ ResultHandler<Void> resultHandler) {
+ syncFileSystem();
+ List<String> args = Lists.newArrayListWithCapacity(5 + arguments.size());
+ args.add("-i");
+ args.add("-u");
+ args.add("-Pcom.android.build.gradle.integratonTest.useJack=" + Boolean.toString(useJack));
+ args.add("-Pcom.android.build.gradle.integratonTest.minifyEnabled=" +
+ Boolean.toString(minifyEnabled));
+ args.add("-Pcom.android.build.gradle.integratonTest.useComponentModel=" +
+ Boolean.toString(experimentalMode));
+ args.addAll(arguments);
+
+ BuildLauncher launcher = connection.newBuild()
+ .forTasks(tasks)
+ .withArguments(Iterables.toArray(args, String.class));
+
+ setJvmArguments(launcher);
+
+ if (stdout != null) {
+ launcher.setStandardOutput(stdout);
+ }
+ if (stderr != null) {
+ launcher.setStandardError(stderr);
+ }
+
+ launcher.run(resultHandler);
+ }
+
+ private void setJvmArguments(LongRunningOperation launcher) {
+ List<String> jvmArguments = new ArrayList<String>();
+
+ if (!Strings.isNullOrEmpty(heapSize)) {
+ jvmArguments.add("-Xmx" + heapSize);
+ }
+
+ jvmArguments.add("-XX:MaxPermSize=1024m");
+
+ String debugIntegrationTest = System.getenv("DEBUG_INNER_TEST");
+ if (!Strings.isNullOrEmpty(debugIntegrationTest)) {
+ jvmArguments.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5006");
+ }
+
+ if (JacocoAgent.isJacocoEnabled()) {
+ jvmArguments.add(JacocoAgent.getJvmArg());
+ }
+
+ launcher.setJvmArguments(Iterables.toArray(jvmArguments, String.class));
+ }
+
+ /**
+ * Returns a project model for each sub-project without building.
+ * @param connection the opened ProjectConnection
+ * @param action the build action to gather the model
+ * @param emulateStudio_1_0 whether to emulate an older IDE (studio 1.0) querying the model.
+ * @param benchmarkName optional benchmark name to pass to Gradle
+ * @param benchmarkMode optional benchmark mode to pass to gradle.
+ */
+ @NonNull
+ private <K,V> Map<K, V> buildModel(
+ @NonNull ProjectConnection connection,
+ @NonNull BuildAction<Map<K, V>> action,
+ boolean emulateStudio_1_0,
+ @Nullable String benchmarkName,
+ @Nullable BenchmarkMode benchmarkMode) {
+
+ BuildActionExecuter<Map<K, V>> executor = connection.action(action);
+
+ List<String> arguments = Lists.newArrayListWithCapacity(5);
+ arguments.add("-P" + AndroidProject.PROPERTY_BUILD_MODEL_ONLY + "=true");
+ arguments.add("-P" + AndroidProject.PROPERTY_INVOKED_FROM_IDE + "=true");
+ if (!emulateStudio_1_0) {
+ arguments.add("-P" + AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED + "=true");
+ }
+ if (benchmarkName != null) {
+ arguments.add("-P" + RECORD_BENCHMARK_NAME + "=" + benchmarkName);
+ if (benchmarkMode != null) {
+ arguments.add("-P" + RECORD_BENCHMARK_MODE + "=" + benchmarkMode.name().toLowerCase(Locale.US));
+ }
+ }
+
+ setJvmArguments(executor);
+
+ executor.withArguments(Iterables.toArray(arguments, String.class));
+
+ executor.setStandardOutput(System.out);
+ executor.setStandardError(System.err);
+
+ return executor.run();
+ }
+
+ /**
+ * Return the stdout from all execute command.
+ */
+ public ByteArrayOutputStream getStdout() {
+ return stdout;
+ }
+
+ /** @see #getStdout() */
+ public String getStdoutString() throws UnsupportedEncodingException {
+ return stdout.toString(Charsets.UTF_8.name());
+ }
+
+ /**
+ * Return the stderr from all execute command.
+ */
+ public ByteArrayOutputStream getStderr() {
+ return stderr;
+ }
+
+ /** @see #getStderr() */
+ public String getStderrString() throws UnsupportedEncodingException {
+ return stderr.toString(Charsets.UTF_8.name());
+ }
+
+ /**
+ * Create a File object. getTestDir will be the base directory if a relative path is supplied.
+ *
+ * @param path Full path of the file. May be a relative path.
+ */
+ public File file(String path) {
+ File result = new File(FileUtils.toSystemDependentPath(path));
+ if (result.isAbsolute()) {
+ return result;
+ } else {
+ return new File(testDir, path);
+ }
+ }
+
+ /**
+ * Returns the NDK folder as built from the Android source tree.
+ */
+ private static File findNdkDir() {
+ String androidHome = System.getenv("ANDROID_NDK_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ } else {
+ System.out.println("Failed to find NDK in ANDROID_NDK_HOME=" + androidHome);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a Gradle project Connection
+ */
+ @NonNull
+ private ProjectConnection getProjectConnection() {
+ GradleConnector connector = GradleConnector.newConnector();
+
+ // Limit daemon idle time for tests. 10 seconds is enough for another test
+ // to start and reuse the daemon.
+ ((DefaultGradleConnector) connector).daemonMaxIdleTime(10, TimeUnit.SECONDS);
+
+ return connector
+ .useGradleVersion(targetGradleVersion)
+ .forProjectDirectory(testDir)
+ .connect();
+ }
+
+ private static File createLocalProp(
+ @NonNull File project,
+ @NonNull File sdkDir,
+ @Nullable File ndkDir) throws IOException, StreamException {
+ ProjectPropertiesWorkingCopy localProp = ProjectProperties.create(
+ project.getAbsolutePath(), ProjectProperties.PropertyType.LOCAL);
+ localProp.setProperty(ProjectProperties.PROPERTY_SDK, sdkDir.getAbsolutePath());
+ if (ndkDir != null) {
+ localProp.setProperty(ProjectProperties.PROPERTY_NDK, ndkDir.getAbsolutePath());
+ }
+ localProp.save();
+
+ return (File) localProp.getFile();
+ }
+
+ private void createGradleProp() throws IOException {
+ if (gradleProperties.isEmpty()) {
+ return;
+ }
+ File propertyFile = file("gradle.properties");
+ Files.write(Joiner.on('\n').join(gradleProperties), propertyFile, Charset.defaultCharset());
+ }
+
+
+ public static void assumeLocalDevice() {
+ Assume.assumeTrue(
+ "Install task not run against device provider",
+ GradleTestProject.REMOTE_TEST_PROVIDER == null);
+ }
+
+ public static void assumeBuildToolsAtLeast(int major) {
+ assumeBuildToolsAtLeast(
+ major, Revision.IMPLICIT_MINOR_REV, Revision.IMPLICIT_MICRO_REV);
+ }
+
+ public static void assumeBuildToolsAtLeast(int major, int minor, int micro) {
+ Revision currentVersion = Revision.parseRevision(DEFAULT_BUILD_TOOL_VERSION);
+ Assume.assumeTrue("Test is only applicable to build tools > " + major,
+ new Revision(major, minor, micro).compareTo(currentVersion) < 0);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/Logcat.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/Logcat.java
new file mode 100644
index 0000000..892d5da
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/Logcat.java
@@ -0,0 +1,225 @@
+/*
+ * 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.build.gradle.integration.common.fixture;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.utils.DeviceHelper;
+import com.android.builder.tasks.BooleanLatch;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.logcat.LogCatFilter;
+import com.android.ddmlib.logcat.LogCatListener;
+import com.android.ddmlib.logcat.LogCatMessage;
+import com.android.ddmlib.logcat.LogCatReceiverTask;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.EvictingQueue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+public class Logcat implements TestRule {
+
+ @Nullable
+ private LogCatReceiverTask mLogCatReceiverTask;
+
+ @Nullable
+ private Thread mThread;
+
+ @Nullable
+ private List<LogCatMessage> mFilteredLogCatMessages;
+
+ @Nullable
+ private EvictingQueue<LogCatMessage> mLogCatMessages;
+
+ public static Logcat create() {
+ return new Logcat();
+ }
+
+ private Logcat() {
+
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ boolean failed = false;
+ try {
+ base.evaluate();
+ } catch (Throwable e) {
+ failed = true;
+ throw e;
+ } finally {
+ after();
+ if (failed) {
+ printLogCatMessages();
+ }
+ }
+ }
+ };
+ }
+
+ protected synchronized void after() {
+ if (mLogCatReceiverTask != null) {
+ mLogCatReceiverTask.stop();
+ try {
+ if (mThread != null) {
+ mThread.join();
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Start collecting logcat.
+ *
+ * @param device The device to connect to.
+ * @param tagFilter the logcat message tag filter.
+ * Only record messages with tags that match this regular expression.
+ */
+ public void start(@NonNull IDevice device, @NonNull String tagFilter) {
+ start(device, new LogCatFilter("", tagFilter, "", "", "", Log.LogLevel.VERBOSE));
+ }
+
+ private synchronized void start(@NonNull IDevice device, @Nullable final LogCatFilter filter) {
+ mLogCatMessages = EvictingQueue.create(400);
+ mFilteredLogCatMessages = Lists.newArrayList();
+ mLogCatReceiverTask = new LogCatReceiverTask(device);
+ mLogCatReceiverTask.addLogCatListener(new LogCatListener() {
+ @Override
+ public void log(List<LogCatMessage> msgList) {
+ for (LogCatMessage message : msgList) {
+ mLogCatMessages.add(message);
+ if (filter == null || filter.matches(message)) {
+ mFilteredLogCatMessages.add(message);
+ }
+ }
+ }
+ });
+ mThread = new Thread(mLogCatReceiverTask);
+ mThread.setDaemon(true);
+ mThread.start();
+ }
+
+
+ /** Start listening for a logcat message. See {@link MessageListener}. */
+ @NonNull
+ public MessageListener listenForMessage(@NonNull final String messageText) {
+ return new MessageListener(messageText,
+ Preconditions.checkNotNull(mLogCatReceiverTask, "Call start() first!")).start();
+ }
+
+ /**
+ * Listens for a logcat message with the exact content given.<br />
+ *
+ * Tests will want to wait for a device to be ready, a common idiom is likely to be:
+ * <ol>
+ * <li>Test calls {@link #listenForMessage(String)} to start listening for the logcat
+ * message.</li>
+ * <li>Test performs some action (e.g. adb shell am start) which should result in the
+ * message being output.</li>
+ * <li>Test calls {@link #await(long, TimeUnit)} which blocks until the logcat message is
+ * received or indicates a timeout by throwing a {@link TimeoutException}.</li>
+ * </ol>
+ */
+ public static class MessageListener {
+ @NonNull
+ private final String mMessageText;
+
+ @NonNull
+ private final LogCatReceiverTask mLogCatReceiverTask;
+
+ @Nullable
+ private LogCatListener mListener;
+
+ private final BooleanLatch messageFound = new BooleanLatch();
+
+ MessageListener(
+ @NonNull String messageText,
+ @NonNull LogCatReceiverTask logCatReceiverTask) {
+ mMessageText = messageText;
+ mLogCatReceiverTask = logCatReceiverTask;
+ }
+
+ MessageListener start() {
+ LogCatListener listener = new LogCatListener() {
+ @Override
+ public void log(List<LogCatMessage> msgList) {
+ for (LogCatMessage message: msgList) {
+ if (mMessageText.equals(message.getMessage())) {
+ messageFound.signal();
+ }
+ }
+ }
+ };
+
+ mLogCatReceiverTask.addLogCatListener(listener);
+ return this;
+ }
+
+ public void await(long waitTime, @NonNull TimeUnit waitTimeUnit)
+ throws InterruptedException, TimeoutException {
+ try {
+ boolean found = messageFound.await(waitTimeUnit.toNanos(waitTime));
+
+ if (!found) {
+ throw new TimeoutException(
+ "Expected logcat message '" + mMessageText + "' never received.");
+ }
+ } finally {
+ mLogCatReceiverTask.removeLogCatListener(mListener);
+ }
+ }
+
+ public void await() throws InterruptedException, TimeoutException {
+ await(DeviceHelper.DEFAULT_ADB_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ public List<LogCatMessage> getFilteredLogCatMessages() {
+ return ImmutableList.copyOf(
+ Preconditions.checkNotNull(mFilteredLogCatMessages, "Call start() first!"));
+ }
+
+ private void printLogCatMessages() {
+ if (mLogCatMessages != null) {
+ System.out.println("------------ Logcat Messages ------------\n"
+ + Joiner.on('\n').join(mLogCatMessages) + "\n"
+ + "---------- End Logcat Messages ----------\n");
+ }
+ }
+
+ public void clearFiltered() {
+ Preconditions.checkNotNull(mFilteredLogCatMessages, "Call start() first!").clear();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TemporaryProjectModification.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TemporaryProjectModification.java
new file mode 100644
index 0000000..0b89a91
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TemporaryProjectModification.java
@@ -0,0 +1,227 @@
+/*
+ * 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.build.gradle.integration.common.fixture;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.annotations.NonNull;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.junit.runners.model.InitializationError;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Allows project files to be modified, but stores their original content, so it can be restored for
+ * the next test.
+ */
+public class TemporaryProjectModification {
+
+ /**
+ * The type of file change event.
+ */
+ enum FileChangeType {
+ CHANGED, ADDED, REMOVED
+ }
+
+ /**
+ * A file change event and the associated original file content.
+ */
+ private static class FileEvent {
+ private final FileChangeType type;
+ private final String fileContent;
+
+ private FileEvent(
+ FileChangeType type, String fileContent) {
+ this.type = type;
+ this.fileContent = fileContent;
+ }
+
+ public FileChangeType getType() {
+ return type;
+ }
+
+ public String getFileContent() {
+ return fileContent;
+ }
+
+ /**
+ * Creates a {@link FileChangeType#CHANGED} FileEvent with a given original file content.
+ * @param content the original file content
+ * @return a FileEvent instance
+ */
+ static FileEvent changed(@NonNull String content) {
+ return new FileEvent(FileChangeType.CHANGED, content);
+ }
+
+ /**
+ * Creates a {@link FileChangeType#REMOVED} FileEvent with a given original file content.
+ * @param content the original file content
+ * @return a FileEvent instance
+ */
+ static FileEvent removed(@NonNull String content) {
+ return new FileEvent(FileChangeType.REMOVED, content);
+ }
+
+ /**
+ * Creates a {@link FileChangeType#ADDED} FileEvent.
+ * @return a FileEvent instance
+ */
+ static FileEvent added() {
+ return new FileEvent(FileChangeType.ADDED, null);
+ }
+ }
+
+ private final GradleTestProject mTestProject;
+
+ /**
+ * Map of file change event. Key is relative path, value is the change event data
+ */
+ private final Map<String, FileEvent> mFileEvents = Maps.newHashMap();
+
+ private TemporaryProjectModification(GradleTestProject testProject) {
+ mTestProject = testProject;
+ }
+
+ /**
+ * Runs a test that mutates the project in a reversible way, and returns the project to its
+ * original state after the callback has been run.
+ *
+ * @param project The project to modify.
+ * @param test The test to run.
+ * @throws InitializationError if the project modification infrastructure fails.
+ * @throws Exception passed through if the test throws an exception.
+ */
+ public static void doTest(GradleTestProject project, ModifiedProjectTest test) throws
+ Exception {
+ TemporaryProjectModification modifiedProject = new TemporaryProjectModification(project);
+ try {
+ test.runTest(modifiedProject);
+ } finally {
+ modifiedProject.close();
+ }
+ }
+
+ public interface ModifiedProjectTest {
+ void runTest(TemporaryProjectModification modifiedProject) throws Exception;
+ }
+
+ public void replaceFile(
+ @NonNull String relativePath,
+ @NonNull final String content) throws IOException {
+ modifyFile(relativePath, new Function<String, String>() {
+ @Override
+ public String apply(String input) {
+ return content;
+ }
+ });
+ }
+
+ public void replaceInFile(
+ @NonNull String relativePath,
+ @NonNull final String search,
+ @NonNull final String replace) throws IOException {
+ modifyFile(relativePath, new Function<String, String>() {
+ @Override
+ public String apply(String input) {
+ return input.replaceAll(search, replace);
+ }
+ });
+
+ }
+
+ public void removeFile(@NonNull String relativePath) throws IOException {
+ File file = getFile(relativePath);
+
+ String currentContent = Files.toString(file, Charsets.UTF_8);
+
+ // We can modify multiple times, but we only want to store the original.
+ if (!mFileEvents.containsKey(relativePath)) {
+ mFileEvents.put(relativePath, FileEvent.removed(currentContent));
+ }
+
+ FileUtils.delete(file);
+ }
+
+ public void addFile(
+ @NonNull String relativePath,
+ @NonNull String content) throws IOException {
+ File file = getFile(relativePath);
+
+ if (file.exists()) {
+ throw new RuntimeException("File already exists: " + file);
+ }
+
+ FileUtils.mkdirs(file.getParentFile());
+
+ mFileEvents.put(relativePath, FileEvent.added());
+
+ Files.write(content, file, Charsets.UTF_8);
+ }
+
+ public void modifyFile(
+ @NonNull String relativePath,
+ @NonNull Function<String, String> modification) throws IOException {
+ File file = getFile(relativePath);
+
+ String currentContent = Files.toString(file, Charsets.UTF_8);
+
+ // We can modify multiple times, but we only want to store the original.
+ if (!mFileEvents.containsKey(relativePath)) {
+ mFileEvents.put(relativePath, FileEvent.changed(currentContent));
+ }
+
+ String newContent = modification.apply(currentContent);
+
+ if (newContent == null) {
+ assertTrue("File should have been deleted", file.delete());
+ } else {
+ Files.write(newContent, file, Charsets.UTF_8);
+ }
+ }
+
+ /**
+ * Returns the project back to its original state.
+ */
+ private void close() throws IOException {
+ for (Map.Entry<String, FileEvent> entry : mFileEvents.entrySet()) {
+ FileEvent fileEvent = entry.getValue();
+ switch (fileEvent.getType()) {
+ case REMOVED:
+ case CHANGED:
+ Files.write(fileEvent.getFileContent(),
+ getFile(entry.getKey()), Charsets.UTF_8);
+ break;
+ case ADDED:
+ // it's fine if the file was already removed somehow.
+ FileUtils.deleteIfExists(mTestProject.file(entry.getKey()));
+ }
+ }
+
+ mFileEvents.clear();
+ }
+
+ private File getFile(@NonNull String relativePath) {
+ return mTestProject.file(relativePath.replace('/', File.separatorChar));
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TestProject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TestProject.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TestProject.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/TestProject.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AbstractAndroidTestApp.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AbstractAndroidTestApp.java
new file mode 100644
index 0000000..c47da6f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AbstractAndroidTestApp.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.fixture.app;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+
+/**
+ * Abstract class implementing AndroidTestApp.
+ */
+public abstract class AbstractAndroidTestApp implements AndroidTestApp {
+
+ private Multimap<String, TestSourceFile> sourceFiles = ArrayListMultimap.create();
+
+ protected void addFiles(TestSourceFile... files) {
+ for (TestSourceFile file : files) {
+ sourceFiles.put(file.getName(), file);
+ }
+ }
+
+ @Override
+ public TestSourceFile getFile(String filename) {
+ Collection<TestSourceFile> files = sourceFiles.get(filename);
+ if (files.isEmpty()) {
+ throw new NoSuchElementException("Unable to file source file: " + filename + ".");
+ } else if (files.size() > 1) {
+ throw new IllegalArgumentException(
+ "Multiple source files named '" + filename + "'. Specify the path to get one "
+ + "of the following files: \n"
+ + Joiner.on('\n').join(files));
+ }
+ return files.iterator().next();
+ }
+
+ @Override
+ public TestSourceFile getFile(String filename, final String path) {
+ Collection<TestSourceFile> files = sourceFiles.get(filename);
+ return Iterables.find(files, new Predicate<TestSourceFile>() {
+ @Override
+ public boolean apply(TestSourceFile testSourceFile) {
+ return path.equals(testSourceFile.getPath());
+ }
+ });
+ }
+
+ @Override
+ public void addFile(TestSourceFile file) {
+ sourceFiles.put(file.getName(), file);
+ }
+
+ @Override
+ public boolean removeFile(TestSourceFile file) {
+ return sourceFiles.remove(file.getName(), file);
+ }
+
+ @Override
+ public Collection<TestSourceFile> getAllSourceFiles() {
+ return sourceFiles.values();
+ }
+
+ @Override
+ public void write(@NonNull File projectDir, @Nullable String buildScriptContent)
+ throws IOException {
+ for (TestSourceFile srcFile : getAllSourceFiles()) {
+ srcFile.writeToDir(projectDir);
+ }
+
+ // Create build.gradle.
+ if (buildScriptContent != null) {
+ File buildFile = new File(projectDir, "build.gradle");
+ String oldContent = buildFile.isFile()
+ ? Files.toString(buildFile, Charset.defaultCharset())
+ : "";
+
+ Files.write(
+ buildScriptContent
+ + "\n\n"
+ + oldContent,
+ buildFile,
+ Charset.defaultCharset());
+ }
+ }
+
+ @Override
+ public boolean containsFullBuildScript() {
+ return false;
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidComponentGradleModule.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidComponentGradleModule.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidComponentGradleModule.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidComponentGradleModule.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidGradleModule.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidGradleModule.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidGradleModule.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidGradleModule.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidTestApp.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidTestApp.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidTestApp.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/AndroidTestApp.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/EmptyAndroidTestApp.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/EmptyAndroidTestApp.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/EmptyAndroidTestApp.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/EmptyAndroidTestApp.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModule.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModule.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModule.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModule.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModuleFactory.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModuleFactory.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModuleFactory.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/GradleModuleFactory.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldApp.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldApp.groovy
new file mode 100644
index 0000000..cf3f2c8
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldApp.groovy
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package com.android.build.gradle.integration.common.fixture.app
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+/**
+ * Simple test application that prints "hello world!".
+ */
+public class HelloWorldApp extends AbstractAndroidTestApp implements AndroidTestApp {
+
+ static private final TestSourceFile javaSource =
+ new TestSourceFile("src/main/java/com/example/helloworld", "HelloWorld.java",
+ """
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class HelloWorld extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ // onCreate
+ }
+}
+""");
+
+ static private final TestSourceFile resValuesSource =
+ new TestSourceFile("src/main/res/values", "strings.xml",
+"""<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">HelloWorld</string>
+</resources>
+""");
+
+ static private final TestSourceFile resLayoutSource =
+ new TestSourceFile("src/main/res/layout", "main.xml",
+"""<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="hello world!"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+""");
+
+ static private final TestSourceFile manifest =
+ new TestSourceFile("src/main", "AndroidManifest.xml",
+"""<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.helloworld"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="3" />
+ <application android:label="@string/app_name">
+ <activity android:name=".HelloWorld"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+""");
+
+
+ static private final TestSourceFile androidTestSource =
+ new TestSourceFile("src/androidTest/java/com/example/helloworld", "HelloWorldTest.java",
+"""
+package com.example.helloworld;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class HelloWorldTest extends ActivityInstrumentationTestCase2<HelloWorld> {
+ private TextView mTextView;
+
+ public HelloWorldTest() {
+ super("com.example.helloworld", HelloWorld.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final HelloWorld a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+""");
+
+ private HelloWorldApp() {
+ addFiles(javaSource, resValuesSource, resLayoutSource, manifest, androidTestSource);
+ }
+
+ private HelloWorldApp(String plugin) {
+ this();
+
+ TestSourceFile buildFile = new TestSourceFile("", "build.gradle",
+ """
+ apply plugin: '$plugin'
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion '$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION'
+ }
+
+ """);
+
+ addFile(buildFile)
+ }
+
+ public static HelloWorldApp noBuildFile() {
+ return new HelloWorldApp()
+ }
+
+ public static HelloWorldApp forPlugin(String plugin) {
+ return new HelloWorldApp(plugin)
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldJniApp.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldJniApp.groovy
new file mode 100644
index 0000000..2f899aa
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldJniApp.groovy
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.fixture.app
+
+import com.android.annotations.NonNull
+
+/**
+ * Simple test application that uses JNI to print a "hello world!".
+ *
+ * NOTE: Android project must create an NDK module named "hello-jni".
+ */
+public class HelloWorldJniApp extends AbstractAndroidTestApp implements AndroidTestApp {
+
+ static private final TestSourceFile javaSource =
+ new TestSourceFile("src/main/java/com/example/hellojni", "HelloJni.java",
+ """
+package com.example.hellojni;
+
+import android.app.Activity;
+import android.widget.TextView;
+import android.os.Bundle;
+
+public class HelloJni extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Create a TextView and set its content from a native function.
+ TextView tv = new TextView(this);
+ tv.setText( stringFromJNI() );
+ setContentView(tv);
+ }
+
+ // A native method that is implemented by the 'hello-jni' native library.
+ public native String stringFromJNI();
+
+ static {
+ System.loadLibrary("hello-jni");
+ }
+}
+""");
+
+ // JNI Implementation in C.
+ static private final TestSourceFile cSource =
+ new TestSourceFile("src/main/jni", "hello-jni.c",
+"""
+#include <string.h>
+#include <jni.h>
+
+// This is a trivial JNI example where we use a native method
+// to return a new VM String.
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ return (*env)->NewStringUTF(env, "hello world!");
+}
+""");
+
+ // JNI Implementation in C++.
+ static private final TestSourceFile cppSource =
+ new TestSourceFile("src/main/jni", "hello-jni.cpp",
+"""
+#include <string.h>
+#include <jni.h>
+#include <cctype>
+
+// This is a trivial JNI example where we use a native method
+// to return a new VM String.
+extern "C"
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ char greeting[] = "HELLO WORLD!";
+ char* ptr = greeting;
+ while (*ptr) {
+ *ptr = std::tolower(*ptr);
+ ++ptr;
+ }
+ return env->NewStringUTF(greeting);
+}
+""");
+
+
+ static private final TestSourceFile resSource =
+ new TestSourceFile("src/main/res/values", "strings.xml",
+ """<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">HelloJni</string>
+</resources>
+""");
+
+ static private final TestSourceFile manifest =
+ new TestSourceFile("src/main", "AndroidManifest.xml",
+"""<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.hellojni"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="3" />
+ <application android:label="@string/app_name">
+ <activity android:name=".HelloJni"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+""");
+
+ static private final TestSourceFile androidTestSource =
+ new TestSourceFile("src/androidTest/java/com/example/hellojni", "HelloJniTest.java",
+"""
+package com.example.hellojni;
+
+import android.test.ActivityInstrumentationTestCase;
+
+/**
+ * This is a simple framework for a test of an Application. See
+ * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
+ * how to write and extend Application tests.
+ * <p/>
+ * To run this test, you can type:
+ * adb shell am instrument -w \
+ * -e class com.example.hellojni.HelloJniTest \
+ * com.example.hellojni.tests/android.test.InstrumentationTestRunner
+ */
+public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
+
+ public HelloJniTest() {
+ super("com.example.hellojni", HelloJni.class);
+ }
+
+
+ public void testJniName() {
+ final HelloJni a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ assertTrue("hello world!".equals(a.stringFromJNI()));
+ }
+}
+""");
+
+ public HelloWorldJniApp(Map args = [:]) {
+ def defaultArgs = [jniDir: "jni", useCppSource: false]
+ defaultArgs << args
+ TestSourceFile jniSource = defaultArgs.useCppSource ? cppSource : cSource
+ addFiles(
+ javaSource,
+ new TestSourceFile("src/main/$defaultArgs.jniDir", jniSource.name, jniSource.content),
+ resSource,
+ manifest,
+ androidTestSource);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String jniDir = "jni";
+ private boolean useCppSource = false;
+
+ public Builder withJniDir(@NonNull String jniDir) {
+ this.jniDir = jniDir;
+ return this;
+ }
+
+ public Builder useCppSource(boolean useCppSource) {
+ this.useCppSource = useCppSource
+ return this;
+ }
+
+ public HelloWorldJniApp build() {
+ return new HelloWorldJniApp(jniDir: jniDir, useCppSource: useCppSource);
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldLibraryApp.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldLibraryApp.groovy
new file mode 100644
index 0000000..278c2a0
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/HelloWorldLibraryApp.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.build.gradle.integration.common.fixture.app
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TestProject
+
+/**
+ * Simple test application with an Android library that prints "hello world!".
+ */
+class HelloWorldLibraryApp extends MultiModuleTestProject implements TestProject {
+ public HelloWorldLibraryApp() {
+ super(":app" : new EmptyAndroidTestApp(), ":lib" : HelloWorldApp.noBuildFile());
+
+ AndroidTestApp app = (AndroidTestApp) getSubproject(":app");
+
+ // Create AndroidManifest.xml that uses the Activity from the library.
+ app.addFile(new TestSourceFile("src/main", "AndroidManifest.xml",
+"""<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="3" />
+ <application android:label="@string/app_name">
+ <activity
+ android:name="com.example.helloworld.HelloWorld"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+"""));
+
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/JavaGradleModule.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/JavaGradleModule.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/JavaGradleModule.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/JavaGradleModule.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/LargeTestProject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/LargeTestProject.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/LargeTestProject.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/LargeTestProject.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/MultiModuleTestProject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/MultiModuleTestProject.java
new file mode 100644
index 0000000..a988428
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/MultiModuleTestProject.java
@@ -0,0 +1,91 @@
+package com.android.build.gradle.integration.common.fixture.app;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.fixture.TestProject;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+/**
+ * A TestProject containing multiple TestProject as modules.
+ */
+public class MultiModuleTestProject implements TestProject {
+
+ private Map<String, TestProject> subprojects;
+
+ /**
+ * Creates a MultiModuleTestProject.
+ *
+ * @param subprojects a map with gradle project path as key and the corresponding TestProject as
+ * value.
+ */
+ public MultiModuleTestProject(@NonNull Map<String, ? extends TestProject> subprojects) {
+ this.subprojects = Maps.newHashMap(subprojects);
+ }
+
+ /**
+ * Creates a MultiModuleTestProject with multiple subproject of the same TestProject.
+ *
+ * @param baseName Base name of the subproject. Actual project name will be baseName + index.
+ * @param subproject A TestProject.
+ * @param count Number of subprojects to create.
+ */
+ public MultiModuleTestProject(
+ @NonNull String baseName,
+ @NonNull TestProject subproject,
+ int count) {
+ subprojects = Maps.newHashMapWithExpectedSize(count);
+ for (int i = 0; i < count; i++) {
+ subprojects.put(baseName + i, subproject);
+ }
+ }
+
+ /**
+ * Return the test project with the given project path.
+ */
+ public TestProject getSubproject(String subprojectPath) {
+ return subprojects.get(subprojectPath);
+ }
+
+ @Override
+ public void write(
+ @NonNull final File projectDir,
+ @Nullable final String buildScriptContent) throws IOException {
+ for (Map.Entry<String, ? extends TestProject> entry : subprojects.entrySet()) {
+ String subprojectPath = entry.getKey();
+ TestProject subproject = entry.getValue();
+ File subprojectDir = new File(projectDir, convertGradlePathToDirectory(subprojectPath));
+ if (!subprojectDir.exists()) {
+ subprojectDir.mkdirs();
+ assert subprojectDir.isDirectory();
+ }
+ subproject.write(subprojectDir, null);
+ }
+
+ StringBuilder builder = new StringBuilder();
+ for (String subprojectName : subprojects.keySet()) {
+ builder.append("include '").append(subprojectName).append("'\n");
+ }
+ Files.write(builder.toString(),
+ new File(projectDir, "settings.gradle"),
+ Charset.defaultCharset());
+
+ Files.write(buildScriptContent,
+ new File(projectDir, "build.gradle"),
+ Charset.defaultCharset());
+ }
+
+ @Override
+ public boolean containsFullBuildScript() {
+ return false;
+ }
+
+ private static String convertGradlePathToDirectory(String gradlePath) {
+ return gradlePath.replace(":", "/");
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/TestSourceFile.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/TestSourceFile.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/TestSourceFile.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/TestSourceFile.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/VariantBuildScriptGenerator.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/VariantBuildScriptGenerator.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/VariantBuildScriptGenerator.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/fixture/app/VariantBuildScriptGenerator.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/AllTests.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/AllTests.java
new file mode 100644
index 0000000..25c3d02
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/AllTests.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.runner;
+
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.google.common.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+/**
+ * JUnit runner that includes all JUnit4 tests.
+ */
+public class AllTests extends Suite {
+ private static final String DEFAULT_CLASSPATH_PROPERTY = "java.class.path";
+
+ public AllTests(Class<?> clazz, RunnerBuilder builder) throws InitializationError {
+ super(builder, clazz, findTestClasses());
+ }
+
+ /**
+ * Find all test classes.
+ *
+ * Inspect all classes in classpath and include all classes that contains methods annotated with
+ * <code>@Test</code>.
+ */
+ private static Class<?>[] findTestClasses() {
+ String classPaths = System.getProperty(DEFAULT_CLASSPATH_PROPERTY);
+ final String separator = System.getProperty("path.separator");
+ List<Class<?>> classes = Lists.newArrayList();
+ for (String classPath : classPaths.split(separator)) {
+ File classPathDir = new File(classPath);
+ // Currently only support classes in .class files. Add support for .jar if necessary.
+ if (classPathDir.isDirectory()) {
+ findTestClassesInDirectory(classes, classPathDir);
+ }
+ }
+ return classes.toArray(new Class<?>[classes.size()]);
+ }
+
+ /**
+ * Find all test classes in a directory.
+ */
+ private static void findTestClassesInDirectory(List<Class<?>> classes,File base) {
+ for (String filename : TestFileUtils.listFiles(base)) {
+ if (!filename.endsWith(".class")) {
+ continue;
+ }
+ String className = getClassNameFromFile(filename);
+ try {
+ Class<?> testClass = Class.forName(className);
+ if (isJUnit4Test(testClass)) {
+ classes.add(testClass);
+ }
+ } catch (ClassNotFoundException ignore) {
+ }
+ }
+ }
+
+ private static String getClassNameFromFile(String classFileName) {
+ // convert /a/b.class to a.b
+ String className = classFileName
+ .substring(0, classFileName.length() - ".class".length()) // remove .class
+ .replace(File.separatorChar, '.'); // replace '/' with '.'
+ if (className.startsWith("."))
+ return className.substring(1);
+ return className;
+ }
+
+ private static boolean isJUnit4Test(Class<?> testClass) {
+ // Check testClass is not abstract.
+ if ((testClass.getModifiers() & Modifier.ABSTRACT) != 0) {
+ return false;
+ }
+
+ for (Method method : testClass.getMethods()) {
+ if (method.isAnnotationPresent(Test.class)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/FilterableParameterized.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/FilterableParameterized.java
new file mode 100644
index 0000000..0e9a372
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/FilterableParameterized.java
@@ -0,0 +1,65 @@
+/*
+ * 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.build.gradle.integration.common.runner;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runners.Parameterized;
+
+/**
+ * Version of {@link Parameterized} that supports filtering methods by name and category.
+ *
+ * <p>To use it, just assume all the generated methods have names like
+ * {@code com.example.ClassName.methodName[0]} etc. - the part in brackets can be configured with
+ * the {@link org.junit.runners.Parameterized.Parameters} annotation on the test class.
+ *
+ * <p>Concrete example: {@code gw :b:i:test --tests=*.ArchivesBaseNameTest.*application*}
+ *
+ * <p>It also supports filtering methods out by categories (but not classes, for now).
+ *
+ * <p>Note that regular {@link Parameterized} implements
+ * {@link org.junit.runner.manipulation.Filterable} but in a way that's not compatible with
+ * Gradle's --tests flag.
+ */
+public class FilterableParameterized extends Parameterized {
+ public FilterableParameterized(Class<?> klass) throws Throwable {
+ super(klass);
+ }
+
+ @Override
+ public void filter(final Filter filter) throws NoTestsRemainException {
+ Filter wrapper = new Filter() {
+ @Override
+ public boolean shouldRun(Description description) {
+ if (description.getTestClass() == null
+ && description.getClassName().startsWith("[")
+ && description.getClassName().endsWith("]")) {
+ // This is the artificial Description that Parameterized uses at class level.
+ return true;
+ }
+ return filter.shouldRun(description);
+ }
+
+ @Override
+ public String describe() {
+ return filter.describe();
+ }
+ };
+ super.filter(wrapper);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/ParallelParameterized.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/ParallelParameterized.java
new file mode 100644
index 0000000..315ad3a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/runner/ParallelParameterized.java
@@ -0,0 +1,64 @@
+/*
+ * 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.build.gradle.integration.common.runner;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.junit.runners.model.RunnerScheduler;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Version of {@link org.junit.runners.Parameterized} that executes methods in parallel.
+ */
+public class ParallelParameterized extends FilterableParameterized {
+
+ private static class ThreadPoolScheduler implements RunnerScheduler {
+ private ExecutorService executor;
+
+ public ThreadPoolScheduler() {
+ String threads = System.getProperty("junit.parallel.threads");
+ checkNotNull(threads, "You have to specify the junit.parallel.threads property");
+ executor = Executors.newFixedThreadPool(Integer.parseInt(threads));
+ }
+
+ @Override
+ public void finished() {
+ executor.shutdown();
+ try {
+ // Use the same timeout as Jenkins.
+ executor.awaitTermination(60, TimeUnit.MINUTES);
+ }
+ catch (InterruptedException exc) {
+ throw new RuntimeException(exc);
+ }
+ }
+
+ @Override
+ public void schedule(Runnable childStatement) {
+ executor.submit(childStatement);
+ }
+ }
+
+ public ParallelParameterized(Class klass) throws Throwable {
+ super(klass);
+ setScheduler(new ThreadPoolScheduler());
+ }
+}
+
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AarSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AarSubject.java
new file mode 100644
index 0000000..96532be
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AarSubject.java
@@ -0,0 +1,227 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FN_CLASSES_JAR;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.process.ProcessException;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharStreams;
+import com.google.common.io.Closer;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.SubjectFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Truth support for aar files.
+ */
+public class AarSubject extends AbstractAndroidSubject<AarSubject> {
+
+ static class Factory extends SubjectFactory<AarSubject, File> {
+ @NonNull
+ public static Factory get() {
+ return new Factory();
+ }
+
+ private Factory() {}
+
+ @Override
+ public AarSubject getSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull File subject) {
+ return new AarSubject(failureStrategy, subject);
+ }
+ }
+
+ public AarSubject(@NonNull FailureStrategy failureStrategy, @NonNull File subject) {
+ super(failureStrategy, subject);
+ }
+
+ @NonNull
+ public StringSubject textSymbolFile() throws IOException {
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ InputStream stream = getInputStream(zipFile, "R.txt");
+
+ InputStreamReader inputStreamReader = new InputStreamReader(stream, Charsets.UTF_8);
+ try {
+ return new StringSubject(failureStrategy, CharStreams.toString(inputStreamReader));
+ } finally {
+ inputStreamReader.close();
+ }
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ /**
+ * Returns true if the provided class is present in the file.
+ * @param expectedClassName the class name in the format Lpkg1/pk2/Name;
+ * @param scope the scope in which to search for the class.
+ */
+ @Override
+ protected boolean checkForClass(
+ @NonNull String expectedClassName,
+ @NonNull ClassFileScope scope)
+ throws ProcessException, IOException {
+ if (!expectedClassName.startsWith("L") || !expectedClassName.endsWith(";")) {
+ throw new RuntimeException("class name must be in the format Lcom/foo/Main;");
+ }
+
+ // in case of an aar, we look in the zip file, so we convert the class name to a zip entry
+ // path.
+ expectedClassName = expectedClassName.substring(1, expectedClassName.length() - 1) + DOT_CLASS;
+
+ switch (scope) {
+ case MAIN:
+ return searchForEntryinZip(expectedClassName, FN_CLASSES_JAR);
+ case ALL:
+ if (searchForEntryinZip(expectedClassName, FN_CLASSES_JAR)) {
+ return true;
+ }
+ // intended fall-through
+ case SECONDARY:
+ // get all the entries and search for local jars.
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ Enumeration<? extends ZipEntry> zipFileEntries = zipFile.entries();
+ while (zipFileEntries.hasMoreElements()) {
+ ZipEntry zipEntry = zipFileEntries.nextElement();
+ String path = zipEntry.getName();
+ if (path.startsWith("libs/") && path.endsWith(DOT_JAR)) {
+ if (searchForEntryinZip(expectedClassName, path)) {
+ return true;
+ }
+ }
+ }
+ } finally {
+ zipFile.close();
+ }
+
+ break;
+ }
+
+ return false;
+ }
+
+ private boolean searchForEntryinZip(
+ @NonNull String entryName,
+ @NonNull String zipPath) throws IOException {
+ Closer closer = Closer.create();
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ InputStream stream = closer.register(getInputStream(zipFile, zipPath));
+ ZipInputStream zis = closer.register(new ZipInputStream(stream));
+ ZipEntry zipEntry;
+ while ((zipEntry = zis.getNextEntry()) != null) {
+ if (entryName.equals(zipEntry.getName())) {
+ return true;
+ }
+ }
+
+ // didn't find the class.
+ return false;
+ } finally {
+ closer.close();
+ zipFile.close();
+ }
+ }
+
+ @Override
+ protected boolean checkForJavaResource(@NonNull String resourcePath)
+ throws ProcessException, IOException {
+ return searchForEntryinZip(resourcePath, FN_CLASSES_JAR);
+ }
+
+ /**
+ * Asserts the subject contains a java resources at the given path with the specified String content.
+ *
+ * Content is trimmed when compared.
+ */
+ @Override
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsJavaResourceWithContent(@NonNull String path, @NonNull String content)
+ throws IOException, ProcessException {
+ if (checkForJavaResource(path)) {
+ // extract the jar file.
+ File classesJar = extractClassesJar();
+
+ // now check
+ assertThatZip(classesJar).named("<" + getSubject().getName() + "/classes.jar>")
+ .containsFileWithContent(path, content);
+
+ FileUtils.delete(classesJar);
+ } else {
+ failWithRawMessage("'%s' does not contains java resource '%s'", getSubject(), path);
+ }
+ }
+
+ /**
+ * Asserts the subject contains a java resources at the given path with the specified
+ * byte array content.
+ */
+ @Override
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsJavaResourceWithContent(@NonNull String path, @NonNull byte[] content)
+ throws IOException, ProcessException {
+ if (checkForJavaResource(path)) {
+ // extract the jar file.
+ File classesJar = extractClassesJar();
+
+ // now check
+ assertThatZip(classesJar).named("<" + getSubject().getName() + "/classes.jar>")
+ .containsFileWithContent(path, content);
+
+ FileUtils.delete(classesJar);
+ } else {
+ failWithRawMessage("'%s' does not contains java resource '%s'", getSubject(), path);
+ }
+ }
+
+ @NonNull
+ private File extractClassesJar() throws IOException {
+ File classesJar = File.createTempFile("classes", ".jar");
+
+ Closer closer = Closer.create();
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ InputStream stream = closer.register(getInputStream(zipFile, FN_CLASSES_JAR));
+ ByteStreams.copy(stream, closer.register(new FileOutputStream(classesJar)));
+
+ return classesJar;
+ } finally {
+ closer.close();
+ zipFile.close();
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractAndroidSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractAndroidSubject.java
new file mode 100644
index 0000000..df309e3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractAndroidSubject.java
@@ -0,0 +1,190 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.FailureStrategy;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.zip.ZipFile;
+
+/**
+ * Base Truth support for android archives (aar and apk)
+ */
+public abstract class AbstractAndroidSubject<T extends AbstractZipSubject<T>> extends AbstractZipSubject<T> {
+
+ public AbstractAndroidSubject(@NonNull FailureStrategy failureStrategy, @NonNull File subject) {
+ super(failureStrategy, subject);
+ }
+
+ /**
+ * Scope in which to search for classes.
+ */
+ public enum ClassFileScope {
+ /**
+ * The main class file. classes.dex for APK, and classes.jar for AAR
+ */
+ MAIN,
+ /**
+ * The secondary class files.
+ * For APK: classes2.dex, classes3.dex, etc...
+ * For AAR: local jars packaged under libs/
+ */
+ SECONDARY,
+ /**
+ * Main and secondary class files.
+ */
+ ALL,
+
+ /**
+ * InstantRun type of packaging, where some classes can be in the main or secondary class
+ * files as well as in any dex file contained in an instant-run.zip file located in the
+ * APK root.
+ */
+ INSTANT_RUN
+ }
+
+ /**
+ * Returns true if the provided class is present in the file.
+ * @param expectedClassName the class name in the format Lpkg1/pk2/Name;
+ * @param scope the scope in which to search for the class.
+ */
+ protected abstract boolean checkForClass(
+ @NonNull String expectedClassName,
+ @NonNull ClassFileScope scope)
+ throws ProcessException, IOException;
+
+ protected abstract boolean checkForJavaResource(
+ @NonNull String resourcePath)
+ throws ProcessException, IOException;
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsClass(@NonNull String className) throws IOException, ProcessException {
+ containsClass(className, ClassFileScope.ALL);
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsClass(@NonNull String className, @NonNull ClassFileScope scope)
+ throws IOException, ProcessException {
+ if (!checkForClass(className, scope)) {
+ failWithRawMessage("'%s' does not contain '%s'", getDisplaySubject(), className);
+ }
+ }
+
+ @Override
+ public void contains(@NonNull String path) throws IOException {
+ checkArgument(
+ !path.startsWith("L") || !path.endsWith(";"),
+ "Use containsClass to check for classes.");
+ super.contains(path);
+ }
+
+ @Override
+ public void doesNotContain(@NonNull String path) throws IOException {
+ checkArgument(
+ !path.startsWith("L") || !path.endsWith(";"),
+ "Use doesNotContainClass to check for classes.");
+ super.doesNotContain(path);
+ }
+
+ public void doesNotContainClass(@NonNull String className)
+ throws IOException, ProcessException {
+ doesNotContainClass(className, ClassFileScope.ALL);
+ }
+
+ public void doesNotContainClass(@NonNull String className, @NonNull ClassFileScope scope)
+ throws IOException, ProcessException {
+ if (checkForClass(className, scope)) {
+ failWithRawMessage("'%s' unexpectedly contains '%s'", getDisplaySubject(), className);
+ }
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsResource(@NonNull String name) throws IOException, ProcessException {
+ if (!checkForResource(name)) {
+ failWithRawMessage("'%s' does not contain resource '%s'", getDisplaySubject(), name);
+ }
+ }
+
+ public void doesNotContainResource(@NonNull String name) throws IOException, ProcessException {
+ if (checkForResource(name)) {
+ failWithRawMessage("'%s' unexpectedly contains resource '%s'",
+ getDisplaySubject(), name);
+ }
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsJavaResource(@NonNull String name) throws IOException, ProcessException {
+ if (!checkForJavaResource(name)) {
+ failWithRawMessage("'%s' does not contain Java resource '%s'", getDisplaySubject(), name);
+ }
+ }
+
+ public void doesNotContainJavaResource(@NonNull String name) throws IOException, ProcessException {
+ if (checkForJavaResource(name)) {
+ failWithRawMessage("'%s' unexpectedly contains Java resource '%s'",
+ getDisplaySubject(), name);
+ }
+ }
+
+ /**
+ * Asserts the subject contains a java resource at the given path with the specified String content.
+ *
+ * Content is trimmed when compared.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public abstract void containsJavaResourceWithContent(
+ @NonNull String path, @NonNull String content) throws IOException, ProcessException;
+
+ /**
+ * Asserts the subject contains a java resource at the given path with the specified
+ * byte array content.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public abstract void containsJavaResourceWithContent(
+ @NonNull String path, @NonNull byte[] content) throws IOException, ProcessException;
+
+
+ protected IndirectSubject<DexFileSubject> getDexFile(final File extractedDexFile) {
+ return new IndirectSubject<DexFileSubject>() {
+ @Override
+ @NonNull
+ public DexFileSubject that() {
+ return DexFileSubject.FACTORY.getSubject(failureStrategy, extractedDexFile);
+ }
+ };
+ }
+
+ @Override
+ protected String getDisplaySubject() {
+ String name = (internalCustomName() == null) ? "" : "\"" + internalCustomName() + "\" ";
+ return name + "<" + getSubject().getName() + ">";
+ }
+
+ private boolean checkForResource(String name) throws IOException {
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ return zipFile.getEntry("res/" + name) != null;
+ } finally {
+ zipFile.close();
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractZipSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractZipSubject.java
new file mode 100644
index 0000000..a232bb2
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/AbstractZipSubject.java
@@ -0,0 +1,290 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharStreams;
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.Subject;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Truth support for zip files.
+ */
+public abstract class AbstractZipSubject<T extends Subject<T, File>> extends Subject<T, File> {
+ private final File subject;
+ public AbstractZipSubject(@NonNull FailureStrategy failureStrategy, @NonNull File subject) {
+ super(failureStrategy, subject);
+ this.subject = subject;
+ new FileSubject(failureStrategy, subject).exists();
+ }
+
+ @Nullable
+ private ZipFile getZip() throws IOException {
+ try {
+ return new ZipFile(subject);
+ } catch (ZipException e) {
+ failWithRawMessage("Problem opening zip %s", subject);
+ return null;
+ }
+ }
+
+ /**
+ * Asserts the zip file contains a file with the specified path.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void contains(@NonNull String path) throws IOException {
+ ZipFile zip = getZip();
+ if (zip == null) {
+ return;
+ }
+ try {
+ if (zip.getEntry(path) == null) {
+ failWithRawMessage("'%s' does not contain '%s'", zip.getName(), path);
+ }
+ } finally {
+ zip.close();
+ }
+ }
+
+ /**
+ * Asserts the zip file does not contains a file with the specified path.
+ */
+ public void doesNotContain(@NonNull String path) throws IOException {
+ ZipFile zip = getZip();
+ if (zip == null) {
+ return;
+ }
+ try {
+ if (zip.getEntry(path) != null) {
+ failWithRawMessage("'%s' unexpectedly contains '%s'", zip.getName(), path);
+ }
+ } finally {
+ zip.close();
+ }
+ }
+
+ /**
+ * Returns a {@link IterableSubject} of all the Zip entries which name matches the passed
+ * regular expression.
+ *
+ * @param conformingTo a regular expression to match entries we are interested in.
+ * @return a {@link IterableSubject} propositions for matching entries.
+ * @throws IOException of the zip file cannot be opened.
+ */
+ public IterableSubject<? extends IterableSubject<?, String, List<String>>, String, List<String>> entries(
+ @NonNull String conformingTo) throws IOException {
+
+ ImmutableList.Builder<String> entries = ImmutableList.builder();
+ Pattern pattern = Pattern.compile(conformingTo);
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ Enumeration<? extends ZipEntry> zipFileEntries = zipFile.entries();
+ while (zipFileEntries.hasMoreElements()) {
+ ZipEntry zipEntry = zipFileEntries.nextElement();
+ if (pattern.matcher(zipEntry.getName()).matches()) {
+ entries.add(zipEntry.getName());
+ }
+ }
+ } finally {
+ zipFile.close();
+ }
+ return check().that(entries.build());
+ }
+
+ /**
+ * Asserts the zip file contains a file with the specified String content.
+ *
+ * Content is trimmed when compared.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsFileWithContent(@NonNull String path, @NonNull String content) {
+ check().that(extractContentAsString(path).trim()).named(internalCustomName() + ": " + path).isEqualTo(
+ content.trim());
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsFileWithMatch(@NonNull String path, @NonNull String pattern) {
+ check().that(extractContentAsString(path)).containsMatch(pattern);
+ }
+
+ /**
+ * Asserts the zip file contains a file with the specified byte array content.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsFileWithContent(@NonNull String path, @NonNull byte[] content) {
+ check().that(extractContentAsByte(path)).named(internalCustomName() + ": " + path).isEqualTo(content);
+ }
+
+ /**
+ * Asserts the zip file contains a file <b>without</b> the specified byte sequence
+ * <b>anywhere</b> in the file
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void containsFileWithoutContent(@NonNull String path, @NonNull String sub) {
+ byte[] contents = extractContentAsByte(path);
+ if (contents == null) {
+ failWithRawMessage("No entry with path " + path);
+ }
+ int index = Bytes.indexOf(contents, sub.getBytes());
+ if (index != -1) {
+ failWithRawMessage("Found byte sequence at " + index + " in class file " + path);
+ }
+ }
+
+ protected String extractContentAsString(@NonNull String path) {
+ try {
+ ZipFile zip = new ZipFile(subject);
+ try {
+ InputStream stream = getInputStream(zip, path);
+ try {
+ // standardize on \n no matter which OS wrote the file.
+ return Joiner.on('\n').join(
+ CharStreams.readLines(new InputStreamReader(stream, Charsets.UTF_8)));
+ } finally {
+ stream.close();
+ }
+ } finally {
+ zip.close();
+ }
+ } catch (IOException e) {
+ failWithRawMessage("IOException when extracting %1$s from zip %2$s: %3$s",
+ subject.getAbsolutePath(),
+ e.toString());
+ return null;
+ }
+ }
+
+ @Nullable
+ protected byte[] extractContentAsByte(@NonNull String path) {
+ try {
+ ZipFile zip = new ZipFile(subject);
+ try {
+ InputStream stream = getInputStream(zip, path);
+ try {
+ return ByteStreams.toByteArray(stream);
+ } finally {
+ stream.close();
+ }
+ } finally {
+ zip.close();
+ }
+ } catch (IOException e) {
+ failWithRawMessage("IOException when extracting %1$s from zip %2$s: %3$s",
+ path,
+ subject.getAbsolutePath(),
+ e.toString());
+ return null;
+ }
+ }
+
+ protected InputStream getInputStream(@NonNull ZipFile zip, @NonNull String path) {
+ try {
+ ZipEntry entry = zip.getEntry(path);
+ if (entry == null) {
+ failWithRawMessage("'%s' does not contain '%s'", zip.getName(), path);
+ return null;
+ }
+
+ if (entry.isDirectory()) {
+ failWithRawMessage("Unable to compare content, '%s' is a directory.", path);
+ }
+ return zip.getInputStream(entry);
+ } catch (IOException e) {
+ failWithRawMessage("IOException when extracting %1$s from zip %2$s: %3$s",
+ path,
+ zip.getName(),
+ e.toString());
+ return null;
+ }
+ }
+
+ /**
+ * Perform an action on a Zip file entry. The zip file entry is extracted from the zip file
+ * and stored to a temporary file passed to the {@link #doOnZipEntry(File)}.
+ * @param <T> the expected return typ
+ */
+ interface ZipEntryAction<T> {
+
+ /**
+ * Perform an action on zip entry extracted to a temporary file. The file will only be
+ * valid during the execution of the method.
+ * @param extractedEntry the extract zip entry as a {@link File}
+ * @return a result or null if no result could be provided.
+ * @throws ProcessException
+ */
+ @Nullable
+ T doOnZipEntry(File extractedEntry) throws ProcessException;
+ }
+
+ /**
+ * Convenience method to extract an entry from the current zip file, save it as temporary file
+ * and run a {@link ZipEntryAction} on it.
+ * @param path the entry name in the subject's zip.
+ * @param action the action to run on the extracted entry.
+ * @param <T> the expected result type
+ * @return result or null if it could not produce one.
+ */
+ @Nullable
+ protected <T> T extractEntryAndRunAction(String path, ZipEntryAction<T> action)
+ throws IOException, ProcessException {
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ InputStream classDexStream = getInputStream(zipFile, path);
+ if (classDexStream == null) {
+ throw new IOException(path + " entry not found !");
+ }
+ try {
+ byte[] content = ByteStreams.toByteArray(classDexStream);
+ // write into tmp file
+ File dexFile = File.createTempFile("dex", "");
+ try {
+ Files.write(content, dexFile);
+ return action.doOnZipEntry(dexFile);
+ } finally {
+ dexFile.delete();
+ }
+ } finally {
+ classDexStream.close();
+ }
+
+ } finally {
+ zipFile.close();
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubject.java
new file mode 100644
index 0000000..2444fdf
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubject.java
@@ -0,0 +1,471 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import static com.android.SdkConstants.FN_APK_CLASSES_N_DEX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.gradle.integration.common.utils.ApkHelper;
+import com.android.build.gradle.integration.common.utils.SdkHelper;
+import com.android.builder.core.ApkInfoParser;
+import com.android.ide.common.process.DefaultProcessExecutor;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.utils.StdLogger;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.SubjectFactory;
+
+import org.junit.Assert;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Truth support for apk files.
+ */
+ at SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+public class ApkSubject extends AbstractAndroidSubject<ApkSubject> {
+
+ private static final Pattern PATTERN_CLASS_DESC = Pattern.compile(
+ "^Class descriptor\\W*:\\W*'(L.+;)'$");
+
+ private static final Pattern PATTERN_MAX_SDK_VERSION = Pattern.compile(
+ "^maxSdkVersion\\W*:\\W*'(.+)'$");
+
+ public static final SubjectFactory<ApkSubject, File> FACTORY =
+ new SubjectFactory<ApkSubject, File> () {
+ @Override
+ public ApkSubject getSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull File subject) {
+ return new ApkSubject(failureStrategy, subject);
+ }
+ };
+
+
+ public ApkSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull File subject) {
+ super(failureStrategy, subject);
+ }
+
+ @NonNull
+ public List<String> entries() throws IOException {
+ ImmutableList.Builder<String> entryList = ImmutableList.builder();
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ entryList.add(entries.nextElement().getName());
+ }
+ } finally {
+ zipFile.close();
+ }
+ return entryList.build();
+ }
+
+ @NonNull
+ public IndirectSubject<DexFileSubject> hasMainDexFile() throws IOException {
+ contains("classes.dex");
+ return new IndirectSubject<DexFileSubject>() {
+ @Override
+ @NonNull
+ public DexFileSubject that() {
+ return DexFileSubject.FACTORY.getSubject(failureStrategy, getSubject());
+ }
+ };
+ }
+
+ @NonNull
+ public IterableSubject<? extends IterableSubject<?, String, List<String>>, String, List<String>> locales() throws ProcessException {
+ File apk = getSubject();
+ List<String> locales = ApkHelper.getLocales(apk);
+
+ if (locales == null) {
+ Assert.fail(String.format("locales not found in badging output for %s", apk));
+ }
+
+ return check().that(locales);
+ }
+
+ public void hasPackageName(@NonNull String packageName) throws ProcessException {
+ File apk = getSubject();
+
+ ApkInfoParser.ApkInfo apkInfo = getApkInfo(apk);
+
+ String actualPackageName = apkInfo.getPackageName();
+
+ if (!actualPackageName.equals(packageName)) {
+ failWithBadResults("has packageName", packageName, "is", actualPackageName);
+ }
+ }
+
+ public void hasVersionCode(int versionCode) throws ProcessException {
+ File apk = getSubject();
+
+ ApkInfoParser.ApkInfo apkInfo = getApkInfo(apk);
+
+ Integer actualVersionCode = apkInfo.getVersionCode();
+ if (actualVersionCode == null) {
+ failWithRawMessage("Unable to query %s for versionCode", getDisplaySubject());
+ }
+
+ if (!apkInfo.getVersionCode().equals(versionCode)) {
+ failWithBadResults("has versionCode", versionCode, "is", actualVersionCode);
+ }
+ }
+
+ public void hasVersionName(@NonNull String versionName) throws ProcessException {
+ File apk = getSubject();
+
+ ApkInfoParser.ApkInfo apkInfo = getApkInfo(apk);
+
+ String actualVersionName = apkInfo.getVersionName();
+ if (actualVersionName == null) {
+ failWithRawMessage("Unable to query %s for versionName", getDisplaySubject());
+ }
+
+ if (!apkInfo.getVersionName().equals(versionName)) {
+ failWithBadResults("has versionName", versionName, "is", actualVersionName);
+ }
+ }
+
+ public void hasMaxSdkVersion(int maxSdkVersion) throws ProcessException {
+
+ List<String> output = ApkHelper.getApkBadging(getSubject());
+
+ checkMaxSdkVersion(output, maxSdkVersion);
+ }
+
+ @Override
+ protected String getDisplaySubject() {
+ String name = (internalCustomName() == null) ? "" : "\"" + internalCustomName() + "\" ";
+ return name + "<" + getSubject().getName() + ">";
+ }
+
+ @NonNull
+ public IndirectSubject<DexClassSubject> getClass(
+ @NonNull final String expectedClassName,
+ @NonNull final ClassFileScope scope) throws ProcessException, IOException {
+ if (!expectedClassName.startsWith("L") || !expectedClassName.endsWith(";")) {
+ throw new RuntimeException("class name must be in the format Lcom/foo/Main;");
+ }
+ IndirectSubject<DexClassSubject> classSubject;
+ switch (scope) {
+ case MAIN:
+ classSubject =
+ extractEntryAndRunAction("classes.dex", findClassAction(expectedClassName));
+ if (classSubject != null) {
+ return classSubject;
+ }
+ break;
+ case INSTANT_RUN:
+ // check first in the instant-run.zip file.
+ classSubject = extractEntryAndRunAction("instant-run.zip",
+ allEntriesAction(findClassAction(expectedClassName)));
+ if (classSubject != null) {
+ return classSubject;
+ }
+ break;
+ case ALL:
+ classSubject = extractEntryAndRunAction(
+ "classes.dex", findClassAction(expectedClassName));
+ if (classSubject != null) {
+ return classSubject;
+ }
+ // intended fall-through
+ case SECONDARY:
+ // while dexdump supports receiving directly an apk, this doesn't work for
+ // multi-dex.
+ // We're going to extract all the classes<N>.dex we find until one of them
+ // contains the class we're searching for.
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ int index = 2;
+ String dexFileName = String.format(FN_APK_CLASSES_N_DEX, index);
+ while (zipFile.getEntry(dexFileName) != null) {
+ classSubject = extractEntryAndRunAction(dexFileName,
+ findClassAction(expectedClassName));
+ if (classSubject != null) {
+ return classSubject;
+ }
+
+ // not found? switch to next index.
+ index++;
+ dexFileName = String.format(FN_APK_CLASSES_N_DEX, index);
+ }
+ } finally {
+ zipFile.close();
+ }
+ break;
+ }
+ fail(String.format("Class %1$s not found in APK %2$s",
+ expectedClassName, getSubject().getAbsolutePath()));
+ return new IndirectSubject<DexClassSubject>() {
+ @NonNull
+ @Override
+ public DexClassSubject that() {
+ return DexClassSubject.FACTORY.getSubject(failureStrategy, null);
+ }
+ };
+ }
+
+ /**
+ * Creates an {@link ZipEntryAction} that will consider each extracted entry as a zip file,
+ * will enumerate such zip file entries and call an delegated action on each entry.
+ */
+ @Nullable
+ protected <T> ZipEntryAction<T> allEntriesAction(final ZipEntryAction<T> action) {
+ return new ZipEntryAction<T>() {
+ @Nullable
+ @Override
+ public T doOnZipEntry(File extractedEntry) throws ProcessException {
+
+ ZipFileSubject instantRunZip =
+ new ZipFileSubject(failureStrategy, extractedEntry);
+
+ try {
+ ZipFile zipFile = new ZipFile(extractedEntry);
+ try {
+ Enumeration<? extends ZipEntry> zipFileEntries = zipFile.entries();
+ while (zipFileEntries.hasMoreElements()) {
+ ZipEntry zipEntry = zipFileEntries.nextElement();
+ T result = instantRunZip.extractEntryAndRunAction(
+ zipEntry.getName(), action);
+ if (result != null) {
+ return result;
+ }
+ }
+ } finally {
+ zipFile.close();
+ }
+ } catch (IOException e) {
+ throw new ProcessException(e);
+ }
+ return null;
+ }
+ };
+ }
+
+ private ZipEntryAction<IndirectSubject<DexClassSubject>> findClassAction(
+ final String expectedClassName) {
+
+ return new ZipEntryAction<IndirectSubject<DexClassSubject>>() {
+ @Nullable
+ @Override
+ public IndirectSubject<DexClassSubject> doOnZipEntry(File extractedEntry)
+ throws ProcessException {
+
+ if (!checkFileForClassWithDexDump(
+ expectedClassName, extractedEntry, SdkHelper.getDexDump())) {
+ return null;
+ }
+ IndirectSubject<DexFileSubject> dexFile = getDexFile(extractedEntry);
+ try {
+ return dexFile.that().hasClass(expectedClassName);
+ } catch (IOException e) {
+ throw new ProcessException(e);
+ }
+ }
+ };
+ }
+
+ private static ZipEntryAction<Boolean> hasClassAction(final String expectedClassName) {
+ return new ZipEntryAction<Boolean>() {
+ @Nullable
+ @Override
+ public Boolean doOnZipEntry(File extractedEntry) throws ProcessException {
+ return checkFileForClassWithDexDump(
+ expectedClassName, extractedEntry, SdkHelper.getDexDump())
+ ? Boolean.TRUE : null;
+
+ }
+ };
+ }
+
+ /**
+ * Returns true if the provided class is present in the file.
+ * @param expectedClassName the class name in the format Lpkg1/pk2/Name;
+ * @param scope the scope in which to search for the class.
+ */
+ @Override
+ protected boolean checkForClass(
+ @NonNull final String expectedClassName,
+ @NonNull final ClassFileScope scope)
+ throws ProcessException, IOException {
+ if (!expectedClassName.startsWith("L") || !expectedClassName.endsWith(";")) {
+ throw new RuntimeException("class name must be in the format Lcom/foo/Main;");
+ }
+
+ File apkFile = getSubject();
+
+ // get the dexdump exec
+ final File dexDumpExe = SdkHelper.getDexDump();
+
+ switch (scope) {
+ case MAIN:
+ return checkFileForClassWithDexDump(expectedClassName, apkFile, dexDumpExe);
+ case INSTANT_RUN:
+ // check first in the instant-run.zip file.
+ Boolean result = extractEntryAndRunAction("instant-run.zip",
+ allEntriesAction(hasClassAction(expectedClassName)));
+ if (result != null && result) {
+ return true;
+ }
+ break;
+ case ALL:
+ if (checkFileForClassWithDexDump(expectedClassName, apkFile, dexDumpExe)) {
+ return true;
+ }
+ // intended fall-through
+ case SECONDARY:
+ // while dexdump supports receiving directly an apk, this doesn't work for
+ // multi-dex.
+ // We're going to extract all the classes<N>.dex we find until one of them
+ // contains the class we're searching for.
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ int index = 2;
+ String dexFileName = String.format(FN_APK_CLASSES_N_DEX, index);
+ while (zipFile.getEntry(dexFileName) != null) {
+ result = extractEntryAndRunAction(dexFileName,
+ hasClassAction(expectedClassName));
+ if (result != null && result) {
+ return true;
+ }
+ // not found? switch to next index.
+ index++;
+ dexFileName = String.format(FN_APK_CLASSES_N_DEX, index);
+ }
+ } finally {
+ zipFile.close();
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected boolean checkForJavaResource(@NonNull String resourcePath)
+ throws ProcessException, IOException {
+ ZipFile zipFile = new ZipFile(getSubject());
+ try {
+ return zipFile.getEntry(resourcePath) != null;
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ /**
+ * Asserts the subject contains a java resources at the given path with the specified String content.
+ *
+ * Content is trimmed when compared.
+ */
+ @Override
+ public void containsJavaResourceWithContent(@NonNull String path, @NonNull String content)
+ throws IOException, ProcessException {
+ containsFileWithContent(path, content);
+ }
+
+ /**
+ * Asserts the subject contains a java resources at the given path with the specified
+ * byte array content.
+ */
+ @Override
+ public void containsJavaResourceWithContent(@NonNull String path, @NonNull byte[] content)
+ throws IOException, ProcessException {
+ containsFileWithContent(path, content);
+ }
+
+ /**
+ * Run dex dump on a file (apk or dex file) to check for the presence of a given class.
+ * @param expectedClassName the name of the class to search for
+ * @param file the file to search
+ * @param dexDumpExe the dex dump exe
+ * @return true if the class was found
+ * @throws ProcessException
+ */
+ private static boolean checkFileForClassWithDexDump(
+ @NonNull String expectedClassName,
+ @NonNull File file,
+ @NonNull File dexDumpExe) throws ProcessException {
+ ProcessExecutor executor = new DefaultProcessExecutor(
+ new StdLogger(StdLogger.Level.ERROR));
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.setExecutable(dexDumpExe);
+ builder.addArgs(file.getAbsolutePath());
+
+ List<String> output = ApkHelper.runAndGetOutput(builder.createProcess(), executor);
+
+ for (String line : output) {
+ Matcher m = PATTERN_CLASS_DESC.matcher(line.trim());
+ if (m.matches()) {
+ String className = m.group(1);
+ if (expectedClassName.equals(className)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @NonNull
+ private static ApkInfoParser.ApkInfo getApkInfo(@NonNull File apk) throws ProcessException {
+ ProcessExecutor processExecutor = new DefaultProcessExecutor(
+ new StdLogger(StdLogger.Level.ERROR));
+ ApkInfoParser parser = new ApkInfoParser(SdkHelper.getAapt(), processExecutor);
+ return parser.parseApk(apk);
+ }
+
+ @VisibleForTesting
+ void checkMaxSdkVersion(@NonNull List<String> output, int maxSdkVersion) {
+ for (String line : output) {
+ Matcher m = PATTERN_MAX_SDK_VERSION.matcher(line.trim());
+ if (m.matches()) {
+ String actual = m.group(1);
+ try {
+ Integer i = Integer.parseInt(actual);
+ if (!i.equals(maxSdkVersion)) {
+ failWithBadResults("has maxSdkVersion", maxSdkVersion, "is", i);
+ }
+ return;
+ } catch (NumberFormatException e) {
+ failureStrategy.fail(
+ String.format(
+ "maxSdkVersion in badging for %s is not a number: %s",
+ getDisplaySubject(), actual),
+ e);
+ }
+ }
+ }
+
+ failWithRawMessage("maxSdkVersion not found in badging output for %s", getDisplaySubject());
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubjectTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubjectTest.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubjectTest.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ApkSubjectTest.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ArtifactSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ArtifactSubject.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ArtifactSubject.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ArtifactSubject.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/CustomTestVerb.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/CustomTestVerb.java
new file mode 100644
index 0000000..3f20704
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/CustomTestVerb.java
@@ -0,0 +1,39 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.android.annotations.Nullable;
+import com.android.builder.model.SyncIssue;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.TestVerb;
+
+/**
+ * Convenience class to extend the functionality of {@link TestVerb} for custom Subject;
+ */
+public class CustomTestVerb extends TestVerb {
+ public CustomTestVerb(FailureStrategy failureStrategy) {
+ super(failureStrategy);
+ }
+
+ public CustomTestVerb(FailureStrategy failureStrategy, String failureMessage) {
+ super(failureStrategy, failureMessage);
+ }
+
+ public IssueSubject that(@Nullable SyncIssue target) {
+ return new IssueSubject(getFailureStrategy(), target);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DependenciesSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DependenciesSubject.java
new file mode 100644
index 0000000..5301ad3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DependenciesSubject.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.truth;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Dependencies;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+import java.util.Collection;
+
+
+public class DependenciesSubject extends Subject<DependenciesSubject, Dependencies> {
+
+ static class Factory extends
+ SubjectFactory<DependenciesSubject, Dependencies> {
+ @NonNull
+ public static Factory get() {
+ return new Factory();
+ }
+
+ private Factory() {}
+
+ @Override
+ public DependenciesSubject getSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull Dependencies subject) {
+ return new DependenciesSubject(failureStrategy, subject);
+ }
+ }
+
+ public DependenciesSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull Dependencies subject) {
+ super(failureStrategy, subject);
+ }
+
+ /**
+ * Checks that the dependencies has a single library.
+ *
+ * @return the library.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public AndroidLibrary hasOneLibrary() {
+ Collection<AndroidLibrary> libs = getSubject().getLibraries();
+
+ check().that(libs).hasSize(1);
+
+ return libs.iterator().next();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DexClassSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DexClassSubject.java
new file mode 100644
index 0000000..cebf89f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DexClassSubject.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.truth;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.utils.XmlHelper;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+import org.w3c.dom.Node;
+
+ at SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+public class DexClassSubject extends Subject<DexClassSubject, Node> {
+
+ public static final SubjectFactory<DexClassSubject, Node> FACTORY
+ = new SubjectFactory<DexClassSubject, Node>() {
+ @Override
+ public DexClassSubject getSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @Nullable Node subject) {
+ return new DexClassSubject(failureStrategy, subject);
+ }
+ };
+
+ private DexClassSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @Nullable Node subject) {
+ super(failureStrategy, subject);
+ }
+
+ public void hasMethod(@NonNull String name) {
+ if (assertSubjectIsNonNull() && !checkHasMethod(name)) {
+ fail("does not contain method", name);
+ }
+ }
+
+ public void hasMethods(@NonNull String... names) {
+ if (assertSubjectIsNonNull()) {
+ for (String name : names) {
+ hasMethod(name);
+ }
+ }
+ }
+
+ public void hasField(@NonNull String name) {
+ if (assertSubjectIsNonNull() && !checkHasField(name)) {
+ fail("does not contain field", name);
+ }
+ }
+
+ public void doesNotHaveField(@NonNull String name) {
+ if (assertSubjectIsNonNull() && checkHasField(name)) {
+ fail("should not contain field", name);
+ }
+ }
+
+ public void doesNotHaveMethod(@NonNull String name) {
+ if (assertSubjectIsNonNull() && checkHasMethod(name)) {
+ fail("should not contain method", name);
+ }
+ }
+
+ /**
+ * Should not be called when the subject is null.
+ */
+ private boolean checkHasMethod(@NonNull String name) {
+ return XmlHelper.findChildWithTagAndAttrs(getSubject(), "method", "name", name) != null;
+ }
+
+ /**
+ * Should not be called when the subject is null.
+ */
+ private boolean checkHasField(@NonNull String name) {
+ return XmlHelper.findChildWithTagAndAttrs(getSubject(), "field", "name", name) != null;
+ }
+
+ private boolean assertSubjectIsNonNull() {
+ if (getSubject() == null) {
+ fail("Cannot assert about the contents of a dex class that does not exist.");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected String getDisplaySubject() {
+ String subjectName = null;
+ if (getSubject() != null) {
+ subjectName = getSubject().getAttributes().getNamedItem("name").getTextContent();
+ }
+ if (internalCustomName() != null) {
+ return internalCustomName() + " (<" + subjectName + ">)";
+ } else {
+ return "<" + subjectName + ">";
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DexFileSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DexFileSubject.java
new file mode 100644
index 0000000..b810cf3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/DexFileSubject.java
@@ -0,0 +1,154 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.utils.ApkHelper;
+import com.android.build.gradle.integration.common.utils.SdkHelper;
+import com.android.build.gradle.integration.common.utils.XmlHelper;
+import com.android.ide.common.process.DefaultProcessExecutor;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.utils.StdLogger;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+ at SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+public class DexFileSubject extends Subject<DexFileSubject, File> {
+
+ private Node mainDexDump;
+
+ public static final SubjectFactory<DexFileSubject, File> FACTORY =
+ new SubjectFactory<DexFileSubject, File>() {
+ @Override
+ public DexFileSubject getSubject(@NonNull FailureStrategy fs, @Nullable File that) {
+ return new DexFileSubject(fs, that);
+ }
+ };
+
+ private DexFileSubject(@NonNull FailureStrategy fs, @Nullable File that) {
+ super(fs, that);
+ }
+
+ /**
+ * @deprecated TODO: Separate out utils for dealing with dex files from truth subjects.
+ */
+ @Deprecated
+ public boolean containsClass(String className) throws IOException, ProcessException {
+ return getClassDexDump(className) != null;
+ }
+
+ public IndirectSubject<DexClassSubject> hasClass(@NonNull String className)
+ throws ProcessException, IOException {
+ final Node classNode = getClassDexDump(className);
+ if (assertSubjectIsNonNull() && classNode == null) {
+ fail("contains class", getSubject(), className);
+ }
+ return new IndirectSubject<DexClassSubject>() {
+ @NonNull
+ @Override
+ public DexClassSubject that() {
+ return DexClassSubject.FACTORY.getSubject(failureStrategy, classNode);
+ }
+ };
+ }
+
+ @Nullable
+ private Node getClassDexDump(@NonNull String className) throws ProcessException, IOException {
+ if (!className.startsWith("L") || !className.endsWith(";")) {
+ throw new IllegalArgumentException(
+ "class name must be in the format L" + "com/foo/Main;");
+ }
+ if (getSubject() == null) {
+ return null;
+ }
+ className = className.substring(1, className.length() - 1).replace('/', '.');
+ final int lastDot = className.lastIndexOf('.');
+ final String pkg;
+ final String name;
+ if (lastDot < 0) {
+ name = className;
+ pkg = "";
+ } else {
+ pkg = className.substring(0, lastDot);
+ name = className.substring(lastDot + 1).replace('$', '.');
+ }
+ Node mainDexDump = getMainDexDump();
+ Node packageNode = XmlHelper
+ .findChildWithTagAndAttrs(mainDexDump, "package", "name", pkg);
+ if (packageNode == null) {
+ return null;
+ }
+
+ return XmlHelper.findChildWithTagAndAttrs(packageNode, "class", "name", name);
+ }
+
+ /**
+ * Should not be called when the subject is null.
+ */
+ @NonNull
+ private Node getMainDexDump() throws ProcessException, IOException {
+ if (mainDexDump != null) {
+ return mainDexDump;
+ }
+ mainDexDump = loadDexDump(Preconditions.checkNotNull(getSubject()), SdkHelper.getDexDump());
+ return mainDexDump;
+ }
+
+ /**
+ * Exports the dex information in XML format and returns it as a Document.
+ */
+ @NonNull
+ private static Node loadDexDump(@NonNull File file, @NonNull File dexDumpExe)
+ throws IOException, ProcessException {
+ ProcessExecutor executor = new DefaultProcessExecutor(new StdLogger(StdLogger.Level.ERROR));
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.setExecutable(dexDumpExe);
+ builder.addArgs("-l", "xml", "-d", file.getAbsolutePath());
+
+ String output = ApkHelper.runAndGetRawOutput(builder.createProcess(), executor);
+ try {
+ return XmlUtils.parseDocument(output, false).getChildNodes().item(0);
+ } catch (ParserConfigurationException e) {
+ throw new IOException(e);
+ } catch (SAXException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private boolean assertSubjectIsNonNull() {
+ if (getSubject() == null) {
+ fail("Cannot assert about the contents of a dex file that does not exist.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FakeFailureStrategy.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FakeFailureStrategy.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FakeFailureStrategy.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FakeFailureStrategy.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubject.java
new file mode 100644
index 0000000..a4e729e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubject.java
@@ -0,0 +1,121 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Truth support for validating File.
+ */
+ at SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion") // Functions do not return.
+public class FileSubject extends Subject<FileSubject, File> {
+
+ public static final SubjectFactory<FileSubject, File> FACTORY =
+ new SubjectFactory<FileSubject, File>() {
+ @Override
+ public FileSubject getSubject(FailureStrategy fs, File that) {
+ return new FileSubject(fs, that);
+ }
+ };
+
+ public FileSubject(FailureStrategy failureStrategy, File subject) {
+ super(failureStrategy, subject);
+ }
+
+ public void hasName(String name) {
+ check().that(getSubject().getName()).named(getDisplaySubject()).isEqualTo(name);
+ }
+
+ public void exists() {
+ if (!getSubject().exists()) {
+ fail("exists");
+ }
+ }
+
+ public void doesNotExist() {
+ if (getSubject().exists()) {
+ fail("does not exist");
+ }
+ }
+
+ public void isFile() {
+ if (!getSubject().isFile()) {
+ fail("is a file");
+ }
+ }
+
+ public void isDirectory() {
+ if (!getSubject().isDirectory()) {
+ fail("is a directory");
+ }
+ }
+
+ public void containsAllOf(String... expectedContents) {
+ isFile();
+
+ try {
+ String contents = Files.toString(getSubject(), Charsets.UTF_8);
+ for (String expectedContent : expectedContents) {
+ if (!contents.contains(expectedContent)) {
+ failWithBadResults("contains", expectedContent, "is", contents);
+ }
+ }
+ } catch (IOException e) {
+ failWithRawMessage("Unable to read %s", getSubject());
+ }
+ }
+
+ public void wasModifiedAt(long timestamp) {
+ long lastModified = getSubject().lastModified();
+ if (getSubject().lastModified() != timestamp) {
+ failWithBadResults("was not modified at", timestamp, "was modified at", lastModified);
+ }
+ }
+
+ public void isNewerThan(long timestamp) {
+ long lastModified = getSubject().lastModified();
+ if (getSubject().lastModified() <= timestamp) {
+ failWithBadResults("is newer than", timestamp, "was modified at", lastModified);
+ }
+ }
+
+ public void isNewerThan(File other) {
+ isNewerThan(other.lastModified());
+ }
+
+ public void isSameAgeAs(File other) {
+ wasModifiedAt(other.lastModified());
+ }
+
+ public void contentWithUnixLineSeparatorsIsExactly(String expected) {
+ try {
+ if (!FileUtils.loadFileWithUnixLineSeparators(getSubject()).equals(expected)) {
+ fail("content is not equal");
+ }
+ } catch (IOException e) {
+ fail(e.getMessage(), e);
+ }
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubjectFactory.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubjectFactory.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubjectFactory.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/FileSubjectFactory.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IncrementalFileSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IncrementalFileSubject.java
new file mode 100644
index 0000000..ce0e641
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IncrementalFileSubject.java
@@ -0,0 +1,83 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/**
+ * Truth support for validating whether changes to a file affects incremental tasks.
+ */
+public class IncrementalFileSubject extends Subject<IncrementalFileSubject, File> {
+
+ private final String gradleOutput;
+
+ public IncrementalFileSubject(FailureStrategy failureStrategy, File subject, String gradleOutput) {
+ super(failureStrategy, subject);
+ this.gradleOutput = gradleOutput;
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void hasNotBeenChanged() {
+ Pattern pattern = Pattern.compile("(Input|Output) file " + getSubjectPattern());
+ if (pattern.matcher(gradleOutput).find()) {
+ fail("has not been changed.");
+ }
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void hasBeenAdded() {
+ Pattern pattern =
+ Pattern.compile("Input file " + getSubjectPattern() + " has been added.");
+ if (!pattern.matcher(gradleOutput).find()) {
+ failWithRawMessage(
+ "Not true that a task was executed due to %s being added.",
+ getDisplaySubject());
+ }
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void hasChanged() {
+ Pattern pattern =
+ Pattern.compile("(Input|Output) file " + getSubjectPattern() + " has changed.");
+ if (!pattern.matcher(gradleOutput).find()) {
+ failWithRawMessage(
+ "Not true that a task was executed due to %s being changed.",
+ getDisplaySubject());
+ }
+ }
+
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void hasBeenRemoved() {
+ Pattern pattern =
+ Pattern.compile("(Input|Output) file " + getSubjectPattern()
+ + " has been removed.");
+ if (!pattern.matcher(gradleOutput).find()) {
+ failWithRawMessage(
+ "Not true that a task was executed due to %s being removed.",
+ getDisplaySubject());
+ }
+ }
+
+ private String getSubjectPattern() {
+ return getSubject().isAbsolute() ? getSubject().getPath() : "\\S*" + getSubject().getPath();
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IncrementalFileSubjectFactory.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IncrementalFileSubjectFactory.java
new file mode 100644
index 0000000..8686c80
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IncrementalFileSubjectFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.SubjectFactory;
+
+import java.io.File;
+
+/**
+ * Factory to add truth support for IncrementalFile.
+ */
+public class IncrementalFileSubjectFactory extends SubjectFactory<IncrementalFileSubject, File> {
+ private final String gradleOutput;
+
+ public static IncrementalFileSubjectFactory factory(String gradleOutput) {
+ return new IncrementalFileSubjectFactory(gradleOutput);
+ }
+
+ public IncrementalFileSubjectFactory(String gradleOutput) {
+ this.gradleOutput = gradleOutput;
+ }
+
+ @Override
+ public IncrementalFileSubject getSubject(FailureStrategy failureStrategy, File subject) {
+ return new IncrementalFileSubject(failureStrategy, subject, gradleOutput);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IndirectSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IndirectSubject.java
new file mode 100644
index 0000000..8686e1b
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IndirectSubject.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.truth;
+
+import com.android.annotations.NonNull;
+import com.google.common.truth.Subject;
+
+/**
+ * Make assertions more readable as english.
+ *
+ * Has the side effect of deferring the instantiation of subjects.
+ */
+public interface IndirectSubject<S extends Subject> {
+ @NonNull
+ S that();
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubject.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubject.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubject.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubjectTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubjectTest.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubjectTest.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/IssueSubjectTest.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/LogCatMessagesSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/LogCatMessagesSubject.java
new file mode 100644
index 0000000..b4f3379
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/LogCatMessagesSubject.java
@@ -0,0 +1,88 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.fixture.Logcat;
+import com.android.ddmlib.logcat.LogCatMessage;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+ at SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+public class LogCatMessagesSubject extends Subject<LogCatMessagesSubject, Logcat> {
+
+ public static final SubjectFactory<LogCatMessagesSubject, Logcat> FACTORY =
+ new SubjectFactory<LogCatMessagesSubject, Logcat>() {
+ @Override
+ public LogCatMessagesSubject getSubject(FailureStrategy fs, Logcat that) {
+ return new LogCatMessagesSubject(fs, that);
+ }
+ };
+
+ public LogCatMessagesSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @Nullable Logcat subject) {
+ super(failureStrategy, subject);
+ }
+
+
+ public void containsMessageWithText(@NonNull String text) {
+ if (!containsMessageThatMatches(messageTextOf(text))) {
+ fail("contains message with text ", text);
+ }
+ }
+
+ public void doesNotContainMessageWithText(@NonNull String text) {
+ if (containsMessageThatMatches(messageTextOf(text))) {
+ fail("does not contain message with text ", text);
+ }
+ }
+
+ @Override
+ protected String getDisplaySubject() {
+ if (getSubject() == null) {
+ return super.getDisplaySubject();
+ }
+ return Objects.toStringHelper(getSubject())
+ .addValue(getSubject().getFilteredLogCatMessages())
+ .toString();
+ }
+
+ private boolean containsMessageThatMatches(@NonNull Predicate<LogCatMessage> predicate) {
+ for (LogCatMessage message : getSubject().getFilteredLogCatMessages()) {
+ if (predicate.apply(message)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ @NonNull
+ private static Predicate<LogCatMessage> messageTextOf(@NonNull final String text) {
+ return new Predicate<LogCatMessage>() {
+ @Override
+ public boolean apply(LogCatMessage input) {
+ return text.equals(input.getMessage());
+ }
+ };
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ModelSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ModelSubject.java
new file mode 100644
index 0000000..fd2d106
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ModelSubject.java
@@ -0,0 +1,181 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+import java.util.Collection;
+
+/**
+ * Truth support for AndroidProject.
+ */
+public class ModelSubject extends Subject<ModelSubject, AndroidProject> {
+
+ static class Factory extends SubjectFactory<ModelSubject, AndroidProject> {
+
+ @NonNull
+ public static Factory get() {
+ return new Factory();
+ }
+
+ private Factory() {}
+
+ @Override
+ public ModelSubject getSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull AndroidProject subject) {
+ return new ModelSubject(failureStrategy, subject);
+ }
+ }
+
+ public ModelSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull AndroidProject subject) {
+ super(failureStrategy, subject);
+ }
+
+
+ /**
+ * Asserts that the issue collection has only a single element with the given properties.
+ * Not specified properties are not tested and could have any value.
+ *
+ * @param severity the expected severity
+ * @param type the expected type
+ * @return the found issue for further testing.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public SyncIssue hasSingleIssue(int severity, int type) {
+ Collection<SyncIssue> subject = getSubject().getSyncIssues();
+
+ check().that(subject).hasSize(1);
+
+ SyncIssue issue = subject.iterator().next();
+ check().that(issue).isNotNull();
+ check().that(issue).hasSeverity(severity);
+ check().that(issue).hasType(type);
+
+ return issue;
+ }
+
+ /**
+ * Asserts that the issue collection has only a single element with the given properties.
+ * Not specified properties are not tested and could have any value.
+ *
+ * @param severity the expected severity
+ * @param type the expected type
+ * @param data the expected data
+ * @return the found issue for further testing.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public SyncIssue hasSingleIssue(int severity, int type, String data) {
+ Collection<SyncIssue> subject = getSubject().getSyncIssues();
+
+ check().that(subject).hasSize(1);
+
+ SyncIssue issue = subject.iterator().next();
+ check().that(issue).isNotNull();
+ check().that(issue).hasSeverity(severity);
+ check().that(issue).hasType(type);
+ check().that(issue).hasData(data);
+
+ return issue;
+ }
+
+ /**
+ * Asserts that the issue collection has only a single element with the given properties.
+ *
+ * @param severity the expected severity
+ * @param type the expected type
+ * @param data the expected data
+ * @param message the expected message
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public void hasSingleIssue(int severity, int type, String data, String message) {
+ Collection<SyncIssue> subject = getSubject().getSyncIssues();
+
+ check().that(subject).hasSize(1);
+
+ SyncIssue issue = subject.iterator().next();
+ check().that(issue).isNotNull();
+ check().that(issue).hasSeverity(severity);
+ check().that(issue).hasType(type);
+ check().that(issue).hasData(data);
+ check().that(issue).hasMessage(message);
+ }
+
+ /**
+ * Asserts that the issue collection has only an element with the given properties.
+ * Not specified properties are not tested and could have any value.
+ *
+ * @param severity the expected severity
+ * @param type the expected type
+ * @return the found issue for further testing.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public SyncIssue hasIssue(int severity, int type) {
+ Collection<SyncIssue> subject = getSubject().getSyncIssues();
+
+ for (SyncIssue issue : subject) {
+ if (severity == issue.getSeverity() &&
+ type == issue.getType()) {
+ return issue;
+ }
+ }
+
+ failWithRawMessage("'%s' does not contain <%s / %s>", getDisplaySubject(),
+ severity, type);
+ // won't reach
+ return null;
+ }
+
+ /**
+ * Asserts that the issue collection has only an element with the given properties.
+ * Not specified properties are not tested and could have any value.
+ *
+ * @param severity the expected severity
+ * @param type the expected type
+ * @param data the expected data
+ * @return the found issue for further testing.
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ public SyncIssue hasIssue(int severity, int type, String data) {
+ Collection<SyncIssue> subject = getSubject().getSyncIssues();
+
+ for (SyncIssue issue : subject) {
+ if (severity == issue.getSeverity() &&
+ type == issue.getType() &&
+ data.equals(issue.getData())) {
+ return issue;
+ }
+ }
+
+ failWithRawMessage("'%s' does not contain <%s / %s / %s>", getDisplaySubject(),
+ severity, type, data);
+ // won't reach
+ return null;
+ }
+
+ @Override
+ public CustomTestVerb check() {
+ return new CustomTestVerb(failureStrategy);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/TruthHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/TruthHelper.java
new file mode 100644
index 0000000..25bf681
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/TruthHelper.java
@@ -0,0 +1,254 @@
+/*
+ * 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.build.gradle.integration.common.truth;
+
+import static com.google.common.truth.Truth.assert_;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.fixture.Logcat;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.SyncIssue;
+import com.android.builder.model.Variant;
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.base.Optional;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Table;
+import com.google.common.truth.BigDecimalSubject;
+import com.google.common.truth.BooleanSubject;
+import com.google.common.truth.ClassSubject;
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.DefaultSubject;
+import com.google.common.truth.DoubleSubject;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.ListMultimapSubject;
+import com.google.common.truth.LongSubject;
+import com.google.common.truth.MapSubject;
+import com.google.common.truth.MultimapSubject;
+import com.google.common.truth.MultisetSubject;
+import com.google.common.truth.ObjectArraySubject;
+import com.google.common.truth.GuavaOptionalSubject;
+import com.google.common.truth.PrimitiveBooleanArraySubject;
+import com.google.common.truth.PrimitiveByteArraySubject;
+import com.google.common.truth.PrimitiveCharArraySubject;
+import com.google.common.truth.PrimitiveDoubleArraySubject;
+import com.google.common.truth.PrimitiveFloatArraySubject;
+import com.google.common.truth.PrimitiveIntArraySubject;
+import com.google.common.truth.PrimitiveLongArraySubject;
+import com.google.common.truth.SetMultimapSubject;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.common.truth.TableSubject;
+import com.google.common.truth.TestVerb;
+import com.google.common.truth.ThrowableSubject;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.Map;
+
+/**
+ * Helper for custom Truth factories.
+ *
+ * TODO: Remove methods that should be imported directly by statically importing Truth.assertThat
+ */
+public class TruthHelper {
+ @NonNull
+ public static FileSubject assertThat(@Nullable File file) {
+ return assert_().about(FileSubjectFactory.factory()).that(file);
+ }
+
+ @NonNull
+ public static ApkSubject assertThatApk(@Nullable File apk) {
+ return assert_().about(ApkSubject.FACTORY).that(apk);
+ }
+
+ @NonNull
+ public static DexFileSubject assertThatDex(@Nullable File dex) {
+ return assert_().about(DexFileSubject.FACTORY).that(dex);
+ }
+
+ @NonNull
+ public static AarSubject assertThatAar(@Nullable File aar) {
+ return assert_().about(AarSubject.Factory.get()).that(aar);
+ }
+
+ @NonNull
+ public static ZipFileSubject assertThatZip(@Nullable File file) {
+ return assert_().about(ZipFileSubject.Factory.get()).that(file);
+ }
+
+ @NonNull
+ public static ModelSubject assertThat(@Nullable AndroidProject androidProject) {
+ return assert_().about(ModelSubject.Factory.get()).that(androidProject);
+ }
+
+ @NonNull
+ public static IssueSubject assertThat(@Nullable SyncIssue issue) {
+ return assert_().about(IssueSubject.Factory.get()).that(issue);
+ }
+
+ @NonNull
+ public static VariantSubject assertThat(@Nullable Variant variant) {
+ return assert_().about(VariantSubject.Factory.get()).that(variant);
+ }
+
+ @NonNull
+ public static ArtifactSubject assertThat(@Nullable AndroidArtifact artifact) {
+ return assert_().about(ArtifactSubject.Factory.get()).that(artifact);
+ }
+
+ @NonNull
+ public static DependenciesSubject assertThat(@Nullable Dependencies dependencies) {
+ return assert_().about(DependenciesSubject.Factory.get()).that(
+ dependencies);
+ }
+
+ public static LogCatMessagesSubject assertThat(Logcat logcat) {
+ return assert_().about(LogCatMessagesSubject.FACTORY).that(logcat);
+ }
+
+ // ---- helper method from com.google.common.truth.Truth
+ // this to allow a single static import of assertThat
+
+ /**
+ * Returns a {@link TestVerb} that will prepend the given message to the failure message in
+ * the event of a test failure.
+ */
+ public static TestVerb assertWithMessage(String messageToPrepend) {
+ return assert_().withFailureMessage(messageToPrepend);
+ }
+
+ public static <T extends Comparable<?>> ComparableSubject<?, T> assertThat(@Nullable T target) {
+ return assert_().that(target);
+ }
+
+ public static BigDecimalSubject assertThat(@Nullable BigDecimal target) {
+ return assert_().that(target);
+ }
+
+ public static Subject<DefaultSubject, Object> assertThat(@Nullable Object target) {
+ return assert_().that(target);
+ }
+
+ @GwtIncompatible("ClassSubject.java")
+ public static ClassSubject assertThat(@Nullable Class<?> target) {
+ return assert_().that(target);
+ }
+
+ public static ThrowableSubject assertThat(@Nullable Throwable target) {
+ return assert_().that(target);
+ }
+
+ public static LongSubject assertThat(@Nullable Long target) {
+ return assert_().that(target);
+ }
+
+ public static DoubleSubject assertThat(@Nullable Double target) {
+ return assert_().that(target);
+ }
+
+ public static IntegerSubject assertThat(@Nullable Integer target) {
+ return assert_().that(target);
+ }
+
+ public static BooleanSubject assertThat(@Nullable Boolean target) {
+ return assert_().that(target);
+ }
+
+ public static StringSubject assertThat(@Nullable String target) {
+ return assert_().that(target);
+ }
+
+ public static <T, C extends Iterable<T>> IterableSubject<? extends IterableSubject<?, T, C>, T, C>
+ assertThat(@Nullable Iterable<T> target) {
+ return assert_().that(target);
+ }
+
+ public static <T> ObjectArraySubject<T> assertThat(@Nullable T[] target) {
+ return assert_().that(target);
+ }
+
+ public static PrimitiveBooleanArraySubject assertThat(@Nullable boolean[] target) {
+ return assert_().that(target);
+ }
+
+ public static PrimitiveIntArraySubject assertThat(@Nullable int[] target) {
+ return assert_().that(target);
+ }
+
+ public static PrimitiveLongArraySubject assertThat(@Nullable long[] target) {
+ return assert_().that(target);
+ }
+
+ public static PrimitiveByteArraySubject assertThat(@Nullable byte[] target) {
+ return assert_().that(target);
+ }
+
+ public static PrimitiveCharArraySubject assertThat(@Nullable char[] target) {
+ return assert_().that(target);
+ }
+
+ public static PrimitiveFloatArraySubject assertThat(@Nullable float[] target) {
+ return assert_().that(target);
+ }
+
+ public static PrimitiveDoubleArraySubject assertThat(@Nullable double[] target) {
+ return assert_().that(target);
+ }
+
+ public static <T> GuavaOptionalSubject assertThat(@Nullable Optional<T> target) {
+ return assert_().that(target);
+ }
+
+ public static MapSubject assertThat(@Nullable Map<?, ?> target) {
+ return assert_().that(target);
+ }
+
+ public static <K, V, M extends Multimap<K, V>>
+ MultimapSubject<? extends MultimapSubject<?, K, V, M>, K, V, M> assertThat(
+ @Nullable Multimap<K, V> target) {
+ return assert_().that(target);
+ }
+
+ public static <K, V, M extends ListMultimap<K, V>>
+ ListMultimapSubject<? extends ListMultimapSubject<?, K, V, M>, K, V, M> assertThat(
+ @Nullable ListMultimap<K, V> target) {
+ return assert_().that(target);
+ }
+
+ public static <K, V, M extends SetMultimap<K, V>>
+ SetMultimapSubject<? extends SetMultimapSubject<?, K, V, M>, K, V, M> assertThat(
+ @Nullable SetMultimap<K, V> target) {
+ return assert_().that(target);
+ }
+
+ public static <E, M extends Multiset<E>>
+ MultisetSubject<? extends MultisetSubject<?, E, M>, E, M> assertThat(
+ @Nullable Multiset<E> target) {
+ return assert_().that(target);
+ }
+
+ public static TableSubject assertThat(@Nullable Table<?, ?, ?> target) {
+ return assert_().that(target);
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/VariantSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/VariantSubject.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/VariantSubject.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/VariantSubject.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ZipFileSubject.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ZipFileSubject.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ZipFileSubject.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/truth/ZipFileSubject.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/AndroidVersionMatcher.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/AndroidVersionMatcher.java
new file mode 100644
index 0000000..d878feb
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/AndroidVersionMatcher.java
@@ -0,0 +1,66 @@
+/*
+ * 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.build.gradle.integration.common.utils;
+
+import com.android.sdklib.AndroidVersion;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Matcher;
+
+public class AndroidVersionMatcher {
+
+ public static Matcher<AndroidVersion> thatUsesDalvik() {
+ return atMost(19);
+ }
+
+ public static Matcher<AndroidVersion> thatUsesArt() {
+ return atLeast(21);
+ }
+
+ public static Matcher<AndroidVersion> atLeast(final int version) {
+ return new BaseMatcher<AndroidVersion>() {
+ @Override
+ public boolean matches(Object item) {
+ return item instanceof AndroidVersion &&
+ ((AndroidVersion) item).isGreaterOrEqualThan(version);
+ }
+
+ @Override
+ public void describeTo(org.hamcrest.Description description) {
+ description.appendText("Android versions ").appendValue(version)
+ .appendText(" and above.");
+ }
+ };
+ }
+
+ public static Matcher<AndroidVersion> atMost(final int version) {
+ return new BaseMatcher<AndroidVersion>() {
+ @Override
+ public boolean matches(Object item) {
+ return item instanceof AndroidVersion &&
+ ((AndroidVersion) item).compareTo(version, null) <= 0;
+ }
+
+ @Override
+ public void describeTo(org.hamcrest.Description description) {
+ description.appendText("Android versions ").appendValue(version)
+ .appendText(" and below.");
+ }
+ };
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ApkHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ApkHelper.java
new file mode 100644
index 0000000..a427aea
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ApkHelper.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.utils;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.process.CachedProcessOutputHandler;
+import com.android.ide.common.process.DefaultProcessExecutor;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.ide.common.process.ProcessInfo;
+import com.android.ide.common.process.ProcessInfoBuilder;
+import com.android.utils.StdLogger;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper to help read/test the content of generated apk file.
+ */
+public class ApkHelper {
+
+ private static final Pattern PATTERN_LOCALES = Pattern.compile(
+ "^locales\\W*:\\W*(.+)$");
+
+ /**
+ * Runs a process, and returns the output.
+ *
+ * @param processInfo the process info to run
+ *
+ * @return the output as a list of files.
+ * @throws ProcessException
+ */
+ @NonNull
+ public static List<String> runAndGetOutput(@NonNull ProcessInfo processInfo)
+ throws ProcessException {
+
+ ProcessExecutor executor = new DefaultProcessExecutor(
+ new StdLogger(StdLogger.Level.ERROR));
+ return runAndGetOutput(processInfo, executor);
+ }
+
+ /**
+ * Runs a process, and returns the output.
+ *
+ * @param processInfo the process info to run
+ * @param processExecutor the process executor
+ *
+ * @return the output as a list of files.
+ * @throws ProcessException
+ */
+ @NonNull
+ public static List<String> runAndGetOutput(
+ @NonNull ProcessInfo processInfo,
+ @NonNull ProcessExecutor processExecutor)
+ throws ProcessException {
+ return Splitter.on(System.getProperty("line.separator")).splitToList(
+ runAndGetRawOutput(processInfo, processExecutor));
+ }
+
+ /**
+ * Runs a process, and returns the output.
+ *
+ * @param processInfo the process info to run
+ * @param processExecutor the process executor
+ *
+ * @return the output as a String
+ * @throws ProcessException
+ */
+ @NonNull
+ public static String runAndGetRawOutput(
+ @NonNull ProcessInfo processInfo,
+ @NonNull ProcessExecutor processExecutor)
+ throws ProcessException {
+ CachedProcessOutputHandler handler = new CachedProcessOutputHandler();
+ processExecutor.execute(processInfo, handler).rethrowFailure().assertNormalExitValue();
+ return handler.getProcessOutput().getStandardOutputAsString();
+ }
+
+ @NonNull
+ public static List<String> getApkBadging(@NonNull File apk) throws ProcessException {
+ File aapt = SdkHelper.getAapt();
+
+ ProcessInfoBuilder builder = new ProcessInfoBuilder();
+ builder.setExecutable(aapt);
+ builder.addArgs("dump", "badging", apk.getPath());
+
+ return ApkHelper.runAndGetOutput(builder.createProcess());
+ }
+
+ /**
+ * Returns the locales of an apk as found in the badging information
+ * @param apk the apk
+ * @return the list of locales or null.
+ * @throws ProcessException
+ *
+ * @see #getApkBadging(File)
+ */
+ @Nullable
+ public static List<String> getLocales(@NonNull File apk) throws ProcessException {
+ List<String> output = getApkBadging(apk);
+
+ for (String line : output) {
+ Matcher m = PATTERN_LOCALES.matcher(line.trim());
+ if (m.matches()) {
+ List<String> list = Splitter.on(' ').splitToList(m.group(1).trim());
+ List<String> result = Lists.newArrayListWithCapacity(list.size());
+ for (String local: list) {
+ // remove the '' on each side.
+ result.add(local.substring(1, local.length() - 1));
+ }
+
+ return result;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/DeviceHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/DeviceHelper.java
new file mode 100644
index 0000000..65b1b31
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/DeviceHelper.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.utils;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.builder.testing.ConnectedDevice;
+import com.android.builder.testing.ConnectedDeviceProvider;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.DdmPreferences;
+import com.android.ddmlib.IDevice;
+import com.android.sdklib.devices.Device;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import org.junit.Assert;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper for performing connected device related tasks.
+ */
+public class DeviceHelper {
+
+ /**
+ * This hardcoded timeout only impacts the gradle plugin integration tests, i.e. not anything
+ * that is externally published.
+ */
+ public static final int DEFAULT_ADB_TIMEOUT_MSEC = DdmPreferences.DEFAULT_TIMEOUT * 3;
+
+ /**
+ * Return the set of all ABIs supported by any of the connected devices.
+ */
+ @NonNull
+ public static Set<String> getDeviceAbis() throws DeviceException {
+ ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
+ ConnectedDeviceProvider deviceProvider =
+ new ConnectedDeviceProvider(SdkHelper.getAdb(), DEFAULT_ADB_TIMEOUT_MSEC, logger);
+ deviceProvider.init();
+ Set<String> abis = Sets.newHashSet();
+ for(DeviceConnector deviceConnector : deviceProvider.getDevices()) {
+ abis.addAll(deviceConnector.getAbis());
+ }
+ deviceProvider.terminate();
+ return abis;
+ }
+
+ public static IDevice getIDevice() throws DeviceException {
+ return Iterables.getOnlyElement(getIDevices());
+ }
+ public static List<IDevice> getIDevices() throws DeviceException {
+ AndroidDebugBridge.initIfNeeded(false /*clientSupport*/);
+
+ AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
+ SdkHelper.getAdb().getAbsolutePath(), false /*forceNewBridge*/);
+
+ assertNotNull("Debug bridge", bridge);
+
+ long timeOut = DEFAULT_ADB_TIMEOUT_MSEC;
+ int sleepTime = 1000;
+ while (!bridge.hasInitialDeviceList() && timeOut > 0) {
+ try {
+ Thread.sleep(sleepTime);
+ } catch (InterruptedException e) {
+ throw new DeviceException(e);
+ }
+ timeOut -= sleepTime;
+ }
+
+ if (timeOut <= 0 && !bridge.hasInitialDeviceList()) {
+ throw new DeviceException("Timeout getting device list.");
+ }
+
+ return ImmutableList.copyOf(bridge.getDevices());
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/FakeApiVersion.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/FakeApiVersion.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/FakeApiVersion.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/FakeApiVersion.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/GradleExceptionsHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/GradleExceptionsHelper.java
new file mode 100644
index 0000000..c3da002
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/GradleExceptionsHelper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.build.gradle.integration.common.utils;
+
+import com.google.common.base.Throwables;
+
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.messaging.remote.internal.PlaceholderException;
+import org.gradle.tooling.GradleConnectionException;
+
+/**
+ * Helper code for dealing with exceptions returned from the tooling API.
+ */
+public class GradleExceptionsHelper {
+ private GradleExceptionsHelper() {}
+
+ /**
+ * Gets the message printed at the bottom of the console output, after a task has failed.
+ */
+ public static String getTaskFailureMessage(GradleConnectionException e) {
+ for (Throwable throwable : Throwables.getCausalChain(e)) {
+ // Because of different class loaders involved, we are forced to do stringly-typed
+ // programming.
+ if (throwable.getClass().getName().equals(PlaceholderException.class.getName())) {
+ if (throwable.toString().startsWith(TaskExecutionException.class.getName())) {
+ return throwable.getCause().getMessage();
+ }
+ }
+ }
+
+ throw new AssertionError(
+ String.format(
+ "Exception was not caused by a task failure: \n%s",
+ Throwables.getStackTraceAsString(e)));
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ImageHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ImageHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ImageHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ImageHelper.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/IncrementalTaskOutputVerifier.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/IncrementalTaskOutputVerifier.java
new file mode 100644
index 0000000..0947687
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/IncrementalTaskOutputVerifier.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.utils;
+
+import static com.google.common.truth.Truth.assert_;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.integration.common.truth.IncrementalFileSubject;
+import com.android.build.gradle.integration.common.truth.IncrementalFileSubjectFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+
+/**
+ * Class for parsing Gradle output for verifying incremental tasks.
+ */
+public class IncrementalTaskOutputVerifier {
+
+ @NonNull
+ final String gradleOutput;
+
+ public IncrementalTaskOutputVerifier(@NonNull String gradleOutput) {
+ this.gradleOutput = gradleOutput;
+ }
+
+ /**
+ * Truth style assert for check changes to a file.
+ */
+ public IncrementalFileSubject assertThatFile(File subject) {
+ return assert_().about(IncrementalFileSubjectFactory.factory(gradleOutput)).that(subject);
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/JacocoAgent.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/JacocoAgent.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/JacocoAgent.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/JacocoAgent.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ModelHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ModelHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ModelHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ModelHelper.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ProductFlavorHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ProductFlavorHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ProductFlavorHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ProductFlavorHelper.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SdkHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SdkHelper.java
new file mode 100644
index 0000000..265979a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SdkHelper.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.utils;
+
+import static com.android.SdkConstants.FD_PLATFORM_TOOLS;
+import static com.android.SdkConstants.FN_ADB;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.repository.Revision;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.utils.FileUtils;
+
+import java.io.File;
+
+/**
+ * Helper for SDK related functions.
+ */
+public class SdkHelper {
+
+ /**
+ * Returns the SDK folder as built from the Android source tree.
+ */
+ public static File findSdkDir() {
+ String androidHome = System.getenv("ANDROID_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ } else {
+ System.out.println("Failed to find SDK in ANDROID_HOME=" + androidHome);
+ }
+ } else {
+ System.out.println("ANDROID_HOME not set.");
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public static File getAapt() {
+ return getBuildTool(
+ Revision.parseRevision(
+ GradleTestProject.DEFAULT_BUILD_TOOL_VERSION, Revision.Precision.MICRO),
+ BuildToolInfo.PathId.AAPT);
+ }
+
+ @NonNull
+ public static File getAapt(@NonNull Revision revision) {
+ return getBuildTool(revision, BuildToolInfo.PathId.AAPT);
+ }
+
+ @NonNull
+ public static File getDexDump() {
+ return getBuildTool(
+ Revision.parseRevision(
+ GradleTestProject.DEFAULT_BUILD_TOOL_VERSION, Revision.Precision.MICRO),
+ BuildToolInfo.PathId.DEXDUMP);
+ }
+
+ @NonNull
+ public static File getBuildTool(
+ @NonNull Revision revision,
+ @NonNull BuildToolInfo.PathId pathId) {
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ BuildToolInfo buildToolInfo = AndroidSdkHandler.getInstance(findSdkDir())
+ .getBuildToolInfo(revision, progress);
+ if (buildToolInfo == null) {
+ throw new RuntimeException("Test requires build-tools " + revision.toString());
+ }
+ return new File(buildToolInfo.getPath(pathId));
+ }
+
+ @NonNull
+ public static File getAdb() {
+ File adb = FileUtils.join(findSdkDir(), FD_PLATFORM_TOOLS, FN_ADB);
+ if (!adb.exists()) {
+ throw new RuntimeException("Unable to find adb.");
+ }
+ return adb;
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SigningConfigHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SigningConfigHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SigningConfigHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SigningConfigHelper.java
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SourceProviderHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SourceProviderHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SourceProviderHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/SourceProviderHelper.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/TestFileUtils.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/TestFileUtils.java
new file mode 100644
index 0000000..2930c67
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/TestFileUtils.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.common.utils;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Helper to help verify content of a file.
+ */
+public class TestFileUtils {
+
+ /**
+ * Return a list of relative path of the files in a directory.
+ */
+ public static List<String> listFiles(@NonNull File base) {
+ assertThat(base).isDirectory();
+
+ List<String> fileList = Lists.newArrayList();
+ for (File file : Files.fileTreeTraverser().preOrderTraversal(base).filter(
+ new Predicate<File>() {
+ @Override
+ public boolean apply(@Nullable File file) {
+ // we want to skip directories and symlinks, so isFile is the best check.
+ return file != null && file.isFile();
+ }
+ })) {
+ assertThat(file.toString()).startsWith(base.toString());
+ String fileName = file.toString().substring(base.toString().length());
+ fileList.add(fileName);
+
+ }
+ return fileList;
+ }
+
+ public static void checkContent(File file, String expectedContent) throws IOException {
+ checkContent(file, Collections.singleton(expectedContent));
+ }
+
+ public static void checkContent(File file, Iterable<String> expectedContents) throws IOException {
+ assertThat(file).isFile();
+
+ String contents = Files.toString(file, Charsets.UTF_8);
+ for (String expectedContent : expectedContents) {
+ assertTrue("File '" + file.getAbsolutePath() + "' does not contain: " + expectedContent,
+ contents.contains(expectedContent));
+ }
+ }
+
+ public static void searchAndReplace(
+ @NonNull File file,
+ @NonNull String search,
+ @NonNull String replace) throws IOException {
+ String content = Files.toString(file, Charset.defaultCharset());
+ String newContent = content.replaceAll(search, replace);
+ assertNotEquals("No match in file.", content, newContent);
+ Files.write(newContent, file, Charset.defaultCharset());
+ }
+
+ /**
+ * Replace a line from a file with another line.
+ * @param file file to change
+ * @param lineNumber the line number, starting at 1
+ * @param line the line to replace with
+ * @throws IOException
+ */
+ public static void replaceLine(
+ @NonNull File file,
+ int lineNumber,
+ @NonNull String line) throws IOException {
+ List<String> lines = Files.readLines(file, Charsets.UTF_8);
+
+ lines.add(lineNumber, line);
+ lines.remove(lineNumber - 1);
+
+ Files.write(
+ Joiner.on(System.getProperty("line.separator")).join(lines),
+ file,
+ Charsets.UTF_8);
+ }
+
+ public static void addMethod(
+ @NonNull File javaFile,
+ @NonNull String methodCode) throws IOException {
+ // Put the method code before the last closing brace.
+ searchAndReplace(javaFile, "\n}\\s+$", methodCode + "\n\n}");
+ }
+
+ public static void appendToFile(@NonNull File file, @NonNull String content) throws IOException {
+ Files.append(content, file, Charset.defaultCharset());
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/VariantHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/VariantHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/VariantHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/VariantHelper.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/XmlHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/XmlHelper.java
new file mode 100644
index 0000000..3eefe6c
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/XmlHelper.java
@@ -0,0 +1,59 @@
+/*
+ * 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.build.gradle.integration.common.utils;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Utility class to parse XML files for assertions.
+ */
+public class XmlHelper {
+
+ @Nullable
+ public static Node findChildWithTagAndAttrs(@NonNull Node node, @NonNull String childElementTag,
+ @NonNull String... attributeKeyValue) {
+ if (attributeKeyValue.length % 2 != 0) {
+ throw new IllegalArgumentException("attribute parameters should be a key value list");
+ }
+ NodeList childNodes = node.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node childNode = childNodes.item(i);
+ if (childElementTag.equals(childNode.getNodeName())) {
+ boolean hasAllAttributes = true;
+ NamedNodeMap attributes = childNode.getAttributes();
+ for (int j = 0; j < attributeKeyValue.length; j +=2) {
+ String attrName = attributeKeyValue[j];
+ String attrValue = attributeKeyValue[j + 1];
+ Node attrNode = attributes.getNamedItem(attrName);
+ if (attrNode == null || !attrValue.equals(attrNode.getTextContent())) {
+ hasAllAttributes = false;
+ break;
+ }
+ }
+ if (hasAllAttributes) {
+ return childNode;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ZipHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ZipHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ZipHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/common/utils/ZipHelper.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AndroidComponentPluginTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AndroidComponentPluginTest.groovy
new file mode 100644
index 0000000..b8ae1fd
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AndroidComponentPluginTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+/**
+ * Test AndroidComponentModelPlugin.
+ */
+ at CompileStatic
+class AndroidComponentPluginTest {
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .forExperimentalPlugin(true)
+ .create();
+
+ @BeforeClass
+ public static void setUp() {
+
+ project.buildFile << """
+import com.android.build.gradle.model.AndroidComponentModelPlugin
+apply plugin: AndroidComponentModelPlugin
+
+model {
+ android {
+ buildTypes {
+ create("custom")
+ }
+ productFlavors {
+ create("flavor1")
+ create("flavor2")
+ }
+ }
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void assemble() {
+ project.execute("assemble")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AppComponentPluginTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AppComponentPluginTest.groovy
new file mode 100644
index 0000000..3d041f5
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/AppComponentPluginTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.category.SmokeTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import com.android.builder.model.AndroidProject
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.google.common.truth.Truth.assertThat
+
+/**
+ * Basic integration test for AppComponentModelPlugin.
+ */
+ at Category(SmokeTests.class)
+ at CompileStatic
+class AppComponentPluginTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .forExperimentalPlugin(true)
+ .withoutNdk()
+ .create();
+
+ @Before
+ public void setUp() {
+ project.buildFile << """
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+}
+"""
+ }
+
+ @Test
+ public void basicAssemble() {
+ AndroidProject model = project.executeAndReturnModel("assemble");
+ assertThat(model).isNotNull();
+ assertThat(model.getName()).isEqualTo(project.name)
+ assertThat(model.getBuildTypes()).hasSize(2)
+ assertThat(model.getProductFlavors()).hasSize(0)
+ assertThat(model.getVariants()).hasSize(2)
+ }
+
+ @Test
+ public void flavors() {
+ project.buildFile << """
+model {
+ android {
+ buildTypes {
+ create("b1")
+ }
+ productFlavors {
+ create("f1")
+ create("f2")
+ }
+ }
+}
+"""
+ // Ensure all combinations of assemble* tasks are created.
+ List<String> tasks = project.getTaskList()
+ assertThat(tasks).containsAllOf(
+ "assemble",
+ "assembleB1",
+ "assembleDebug",
+ "assembleF1",
+ "assembleF1B1",
+ "assembleF1Debug",
+ "assembleF1Release",
+ "assembleF2",
+ "assembleF2B1",
+ "assembleF2Debug",
+ "assembleF2Release",
+ "assembleRelease",
+ "assembleAndroidTest",
+ "assembleF1DebugAndroidTest",
+ "assembleF2DebugAndroidTest");
+
+ AndroidProject model = project.executeAndReturnModel("assemble");
+ assertThat(model).isNotNull();
+ assertThat(model.getName()).isEqualTo(project.name)
+ assertThat(model.getBuildTypes()).hasSize(3)
+ assertThat(model.getProductFlavors()).hasSize(2)
+ assertThat(model.getVariants()).hasSize(6)
+ }
+
+ @Test
+ void generationInModel() {
+ AndroidProject model = project.getSingleModel()
+ assertThat(model.getPluginGeneration())
+ .named("Plugin Generation")
+ .isEqualTo(AndroidProject.GENERATION_COMPONENT)
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connnectedAndroidTest() {
+ project.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/BasicNdkComponentTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/BasicNdkComponentTest.groovy
new file mode 100644
index 0000000..91d03d1
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/BasicNdkComponentTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.category.SmokeTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Basic integration test for native plugin.
+ */
+ at RunWith(Parameterized.class)
+ at Category(SmokeTests.class)
+ at CompileStatic
+class BasicNdkComponentTest {
+
+ @Parameterized.Parameter(value = 0)
+ public String toolchain;
+
+ @Parameterized.Parameters(name="{0}")
+ public static Collection<Object[]> data() {
+ return [
+ ["gcc"].toArray(),
+ ["clang"].toArray(),
+ ];
+ }
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp(useCppSource: true))
+ .forExperimentalPlugin(true)
+ .withHeap("2048m")
+ .create();
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ ndk {
+ moduleName "hello-jni"
+ platformVersion 19
+ toolchain "$toolchain"
+ }
+ }
+}
+"""
+ }
+
+ @Test
+ public void assemble() {
+ project.execute("assemble")
+ }
+
+ @Test
+ public void assembleRelease() {
+ project.execute("assembleRelease")
+
+ // Verify .so are built for all platform.
+ File apk = project.getApk("release", "unsigned")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so")
+ assertThatZip(apk).contains("lib/mips/libhello-jni.so")
+ assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
+ assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
+ }
+
+ @Test
+ public void assembleDebug() {
+ project.execute("assembleDebug")
+
+ // Verify .so are built for all platform.
+ File apk = project.getApk("debug")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so")
+ assertThatZip(apk).contains("lib/mips/libhello-jni.so")
+ assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
+ assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
+
+ // 64-bits binaries will not be produced if platform version 19 is used.
+ assertThatZip(apk).doesNotContain("lib/x86_64/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/arm64-v8a/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/mips64/libhello-jni.so")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connnectedAndroidTest() {
+ project.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentDslTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentDslTest.groovy
new file mode 100644
index 0000000..63a846b
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentDslTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Test various options can be set without necessarily using it.
+ */
+ at CompileStatic
+public class ComponentDslTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .forExperimentalPlugin(true)
+ .create();
+
+ @Before
+ public void setUp() {
+ project.file("proguard.txt").createNewFile()
+ project.buildFile << """
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ defaultConfig {
+ minSdkVersion.apiLevel 7
+ }
+ ndk {
+ moduleName "hello-jni"
+ }
+ productFlavors {
+ create("f1") {
+ proguardFiles.add(file("proguard.txt"))
+ buildConfigFields.create {
+ type "String"
+ name "foo"
+ value "\\"bar\\""
+ }
+ }
+ create("f2")
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:22.2.0'
+}
+"""
+ }
+
+ @Test
+ public void assemble() {
+ AndroidProject model = project.executeAndReturnModel("assemble");
+ assertThat(model).isNotNull();
+ assertThat(model.getName()).isEqualTo(project.name)
+ assertThat(model.getBuildTypes()).hasSize(2)
+ assertThat(model.getProductFlavors()).hasSize(2)
+ assertThat(model.getVariants()).hasSize(4)
+ assertThat(project.getApk("f1", "debug")).exists()
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connnectedAndroidTest() {
+ project.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentInstantRunTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentInstantRunTest.java
new file mode 100644
index 0000000..0c57a40
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentInstantRunTest.java
@@ -0,0 +1,92 @@
+package com.android.build.gradle.integration.component;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.build.gradle.integration.instant.InstantRunTestUtils;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.InstantRun;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Simple test to ensure component model plugin do not crash when instant run is enabled.
+ */
+public class ComponentInstantRunTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldJniApp.builder().build())
+ .forExperimentalPlugin(true)
+ .create();
+
+ @Before
+ public void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "apply plugin: \"com.android.model.application\"\n"
+ + "model {\n"
+ + " android {\n"
+ + " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n"
+ + " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void basicAssemble() {
+ project.execute(InstantRunTestUtils.getInstantRunArgs(21, ColdswapMode.DEFAULT), "assembleDebug");
+ assertThat(project.getApk("debug")).exists();
+ }
+
+ @Ignore("Temporarily disabled until instant run works for component model plugin")
+ @Test
+ public void withNativeCode() throws Exception {
+ Files.append(
+ "model {\n"
+ + " android.ndk {\n"
+ + " moduleName \"hello-jni\"\n"
+ + " }\n"
+ + "}\n",
+ project.getBuildFile(),
+ Charsets.UTF_8);
+
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(
+ 21,
+ ColdswapMode.DEFAULT,
+ OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+ AndroidProject model = project.getSingleModel();
+ File apk = project.getApk("debug");
+ assertThat(apk).exists();
+ assertThatApk(apk).contains("lib/x86/libhello-jni.so");
+
+ File src = project.file("src/main/jni/hello-jni.c");
+ Files.append("\nvoid foo() {}\n", src, Charsets.UTF_8);
+
+ InstantRun instantRunModel = InstantRunTestUtils.getInstantRunModel(model);
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(21, ColdswapMode.DEFAULT),
+ instantRunModel.getIncrementalAssembleTaskName());
+ InstantRunBuildInfo context = InstantRunTestUtils.loadContext(instantRunModel);
+ assertThat(context.getVerifierStatus()).isEqualTo(
+ InstantRunVerifierStatus.JAVA_RESOURCES_CHANGED.toString());
+ assertThat(context.getArtifacts()).hasSize(0);
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSigningConfigTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSigningConfigTest.groovy
new file mode 100644
index 0000000..8440994
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSigningConfigTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.SdkConstants
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.ide.common.signing.KeystoreHelper
+import com.android.utils.StdLogger
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import java.security.KeyStore
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Integration test with signing config.
+ */
+class ComponentSigningConfigTest {
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .forExperimentalPlugin(true)
+ .create();
+
+ @BeforeClass
+ public static void setUp() {
+ KeystoreHelper.createDebugStore(
+ KeyStore.getDefaultType(),
+ project.file("debug.keystore"),
+ "android",
+ "android",
+ "androiddebugkey",
+ new StdLogger(StdLogger.Level.INFO));
+ // TODO: Let gradle resolve file when it is able to do that with File in a Managed type.
+ // I have to do some os specific magic due to \ interpretation in groovy.
+ String storeFile = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS ?
+ """storeFile = new File(/${project.file("debug.keystore")}/)""" :
+ """storeFile = new File("${project.file("debug.keystore")}")"""
+
+ project.buildFile << """
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ buildTypes {
+ debug {
+ buildConfigFields.with {
+ create() {
+ type "int"
+ name "VALUE"
+ value "1"
+ }
+ }
+ }
+ release {
+ signingConfig = \$("android.signingConfigs.myConfig")
+ }
+ }
+ }
+
+ android.signingConfigs {
+ create("myConfig") {
+""" + storeFile + """
+ storePassword "android"
+ keyAlias "androiddebugkey"
+ keyPassword "android"
+ storeType "jks"
+ }
+ }
+}
+"""
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ }
+
+ @Test
+ public void assembleRelease() {
+ project.execute("clean", "assembleRelease")
+ assertThatZip(project.getApk("release")).contains("META-INF/CERT.RSA")
+ }
+
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSourceSetTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSourceSetTest.groovy
new file mode 100644
index 0000000..8ba7090
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ComponentSourceSetTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Integration tests for different configuration of source sets.
+ */
+ at CompileStatic
+class ComponentSourceSetTest {
+
+ public static AndroidTestApp app = new HelloWorldJniApp()
+
+ static {
+ TestSourceFile manifest = app.getFile("AndroidManifest.xml")
+ app.removeFile(manifest)
+ app.addFile(new TestSourceFile("src", manifest.name, manifest.content));
+
+ // Remove the main hello-jni.c and place it in different directories for different flavors.
+ // Note that *not* all variant can be built.
+ TestSourceFile cSource = app.getFile("hello-jni.c");
+ app.removeFile(cSource);
+ app.addFile(
+ new TestSourceFile("src/release/jni", cSource.name, cSource.content))
+ app.addFile(
+ new TestSourceFile("src/flavor1/jni/hello-jni.c", cSource.name, cSource.content))
+ app.addFile(new TestSourceFile("src/flavor2Debug/jni/hello-jni.c", cSource.name,
+ cSource.content))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(app)
+ .forExperimentalPlugin(true)
+ .create();
+
+ @BeforeClass
+ public static void setUp() {
+
+ project.buildFile << """
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ }
+ productFlavors {
+ create("flavor1")
+ create("flavor2")
+ create("flavor3")
+ }
+ sources {
+ main {
+ manifest {
+ source {
+ srcDir 'src'
+ }
+ }
+ jni {
+ source {
+ exclude "**/fail.c"
+ }
+ }
+ }
+ flavor3 {
+ jni {
+ source {
+ srcDir 'src/flavor1/jni'
+ }
+ }
+ }
+ }
+ }
+}
+"""
+ project.file("src/main/jni").mkdirs()
+ project.file("src/main/jni/fail.c") << """
+Un-compilable file to test exclude source works.
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void defaultBuildTypeSourceDirectory() {
+ project.execute("assembleFlavor2Release");
+ def apk = project.getApk("flavor2", "release", "unsigned")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ }
+
+ @Test
+ void defaultProductFlavorSourceDirectory() {
+ project.execute("assembleFlavor1Debug");
+ def apk = project.getApk("flavor1", "debug")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ }
+
+ @Test
+ void defaultVariantSourceDirectory() {
+ project.execute("assembleFlavor2Debug");
+ def apk = project.getApk("flavor2", "debug")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ }
+
+ @Test
+ void nonDefaultSourceDirectory() {
+ project.execute("assembleFlavor3Debug");
+ def apk = project.getApk("flavor3", "debug")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ExternalBuildDependencyTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ExternalBuildDependencyTest.groovy
new file mode 100644
index 0000000..3656be6
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ExternalBuildDependencyTest.groovy
@@ -0,0 +1,147 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import org.junit.AfterClass
+import org.junit.ClassRule
+import org.junit.Ignore
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Tests for native dependencies
+ */
+ at Ignore
+class ExternalBuildDependencyTest {
+ static MultiModuleTestProject base = new MultiModuleTestProject(
+ app: new HelloWorldJniApp(),
+ lib: new EmptyAndroidTestApp())
+
+ static {
+ AndroidTestApp app = (HelloWorldJniApp) base.getSubproject("app")
+ app.removeFile(app.getFile("hello-jni.c"))
+ app.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ sources {
+ main {
+ jniLibs {
+ dependencies {
+ project ":lib"
+ }
+ }
+ }
+ }
+ }
+}
+"""))
+
+ AndroidTestApp lib = (AndroidTestApp) base.getSubproject("lib")
+ lib.addFile(new TestSourceFile("src/main/jni", "hello-jni.c",
+ """
+#include <string.h>
+#include <jni.h>
+
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ return (*env)->NewStringUTF(env, "hello world!");
+}
+"""));
+
+ lib.addFile(new TestSourceFile("", "Android.mk",
+ """
+LOCAL_PATH := \$(call my-dir)
+include \$(CLEAR_VARS)
+
+LOCAL_MODULE := hello-jni
+
+LOCAL_SRC_FILES := src/main/jni/hello-jni.c
+
+include \$(BUILD_SHARED_LIBRARY)
+"""))
+
+ }
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(base)
+ .forExperimentalPlugin(true)
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ base = null
+ }
+
+ @Test
+ void "check standalone lib properly creates library"() {
+ // File a clang compiler. Doesn't matter which one.
+ File compiler = new File(project.getNdkDir(), "ndk-build");
+ GradleTestProject lib = project.getSubproject("lib")
+ lib.buildFile << """
+apply plugin: "com.android.model.external"
+
+model {
+ nativeBuildConfig {
+ libraries {
+ create("foo") {
+ executable "${compiler.getPath()}"
+ args.addAll([
+ "APP_BUILD_SCRIPT=Android.mk",
+ "NDK_PROJECT_PATH=null",
+ "NDK_OUT=build/intermediate",
+ "NDK_LIBS_OUT=build/output",
+ "APP_ABI=x86"])
+ toolchain "gcc"
+ abi "x86"
+ output file("build/output/x86/libhello-jni.so")
+ files {
+ create() {
+ src "src/main/jni/hello-jni.c"
+ }
+ }
+
+ }
+ }
+ toolchains {
+ create("gcc") {
+ CCompilerExecutable = "${compiler.getPath()}"
+ }
+ }
+ }
+}
+"""
+ project.execute("clean", ":app:assembleDebug");
+
+ File apk = project.getSubproject("app").getApk("debug")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ExternalNativeComponentPluginTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ExternalNativeComponentPluginTest.groovy
new file mode 100644
index 0000000..b5f2e44
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/ExternalNativeComponentPluginTest.groovy
@@ -0,0 +1,269 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.builder.model.NativeAndroidProject
+import com.android.builder.model.NativeArtifact
+import com.android.builder.model.NativeSettings
+import com.android.builder.model.NativeToolchain
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Test the ExternalNativeComponentModelPlugin.
+ */
+ at Ignore
+class ExternalNativeComponentPluginTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .forExperimentalPlugin(true)
+ .create();
+
+ @Test
+ public void "check configurations using JSON data file"() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.external'
+
+model {
+ nativeBuild {
+ create {
+ config "config.json"
+ }
+ }
+}
+"""
+ project.file("config.json") << """
+{
+ "buildFiles" : ["CMakeLists.txt"],
+ "libraries" : {
+ "foo" : {
+ "executable" : "touch",
+ "args" : ["output.txt"],
+ "toolchain" : "toolchain1",
+ "output" : "build/libfoo.so",
+ "folders" : [
+ {
+ src : "src/main/jni",
+ cFlags : ["folderCFlag1", "folderCFlag2"],
+ cppFlags : ["folderCppFlag1", "folderCppFlag2"]
+ }
+ ],
+ "files" : [
+ {
+ "src" : "src/main/jni/hello.c",
+ "flags" : ["fileFlag1", "fileFlag2"]
+ }
+ ]
+ }
+ },
+ "toolchains" : {
+ "toolchain1" : {
+ "cCompilerExecutable" : "clang",
+ "cppCompilerExecutable" : "clang++"
+ }
+ },
+ "cFileExtensions" : ["c"],
+ "cppFileExtensions" : ["cpp"]
+}
+"""
+ NativeAndroidProject model = project.executeAndReturnModel(NativeAndroidProject.class, "assemble")
+ checkModel(model);
+ }
+
+ @Test
+ public void "check configurations with multiple JSON data file"() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.external'
+
+model {
+ nativeBuild {
+ create {
+ config "config1.json"
+ commandString "./generate_config1.sh"
+ }
+ create {
+ config "config2.json"
+ }
+ }
+}
+"""
+ project.file("generate_config1.sh") << """echo '
+{
+ "buildFiles" : ["CMakeLists.txt"],
+ "libraries" : {
+ "foo" : {
+ "executable" : "touch",
+ "args" : ["foo.txt"],
+ "toolchain" : "toolchain1",
+ "output" : "build/libfoo.so"
+ }
+ },
+ "toolchains" : {
+ "toolchain1" : {
+ "cCompilerExecutable" : "clang",
+ "cppCompilerExecutable" : "clang++"
+ }
+ }
+}' > config1.json
+"""
+ project.file("generate_config1.sh").setExecutable(true)
+
+ project.file("config2.json") << """
+{
+ "buildFiles" : ["CMakeLists.txt"],
+ "libraries" : {
+ "bar" : {
+ "executable" : "touch",
+ "args" : ["bar.txt"],
+ "toolchain" : "toolchain2",
+ "output" : "build/libbar.so"
+ }
+ },
+ "toolchains" : {
+ "toolchain2" : {
+ "cCompilerExecutable" : "gcc",
+ "cppCompilerExecutable" : "g++"
+ }
+ }
+}
+"""
+ assertThat(project.file("config1.json")).doesNotExist()
+ project.execute("generateConfigfile")
+ assertThat(project.file("config1.json")).exists()
+
+ NativeAndroidProject model = project.executeAndReturnModel(NativeAndroidProject.class, "assemble")
+ assertThat(model.getFileExtensions()).containsEntry("c", "c")
+ assertThat(model.getFileExtensions()).containsEntry("C", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("CPP", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("c++", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cc", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cp", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cpp", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cxx", "c++")
+
+ assertThat(model.artifacts).hasSize(2)
+ for (NativeArtifact artifact : model.artifacts) {
+ if (artifact.getName().equals("foo")) {
+ assertThat(artifact.getName()).isEqualTo("foo")
+ assertThat(artifact.getToolChain()).isEqualTo("toolchain1")
+ assertThat(artifact.getOutputFile()).hasName("libfoo.so")
+ } else {
+ assertThat(artifact.getName()).isEqualTo("bar")
+ assertThat(artifact.getToolChain()).isEqualTo("toolchain2")
+ assertThat(artifact.getOutputFile()).hasName("libbar.so")
+ }
+ }
+
+ assertThat(model.getToolChains()).hasSize(2)
+ for (NativeToolchain toolchain : model.getToolChains()) {
+ if (toolchain.getName().equals("toolchain1")) {
+
+ assertThat(toolchain.getName()).isEqualTo("toolchain1")
+ assertThat(toolchain.getCCompilerExecutable().getName()).isEqualTo("clang")
+ assertThat(toolchain.getCppCompilerExecutable().getName()).isEqualTo("clang++")
+ } else {
+ assertThat(toolchain.getName()).isEqualTo("toolchain2")
+ assertThat(toolchain.getCCompilerExecutable().getName()).isEqualTo("gcc")
+ assertThat(toolchain.getCppCompilerExecutable().getName()).isEqualTo("g++")
+ }
+ }
+ }
+
+ @Test
+ public void "check configurations using plugin DSL"() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.external'
+
+model {
+ nativeBuildConfig {
+ buildFiles.addAll([file("CMakeLists.txt")])
+ CFileExtensions.add("c")
+ cppFileExtensions.add("cpp")
+
+ libraries {
+ create("foo") {
+ executable "touch"
+ args.addAll(["output.txt"])
+ toolchain "toolchain1"
+ output file("build/libfoo.so")
+ folders {
+ create() {
+ src "src/main/jni"
+ CFlags.addAll(["folderCFlag1", "folderCFlag2"])
+ cppFlags.addAll(["folderCppFlag1", "folderCppFlag2"])
+ }
+ }
+ files {
+ create() {
+ src "src/main/jni/hello.c"
+ flags.addAll(["fileFlag1", "fileFlag2"])
+ }
+ }
+
+ }
+ }
+ toolchains {
+ create("toolchain1") {
+ CCompilerExecutable = "clang"
+ cppCompilerExecutable "clang++"
+
+ }
+ }
+ }
+}
+"""
+ NativeAndroidProject model = project.executeAndReturnModel(NativeAndroidProject.class, "assemble")
+ checkModel(model);
+ }
+
+ private void checkModel(NativeAndroidProject model) {
+ Collection<NativeSettings> settingsMap = model.getSettings();
+ assertThat(project.file("output.txt")).exists()
+
+ assertThat(model.artifacts).hasSize(1)
+
+ assertThat(model.getFileExtensions()).containsEntry("c", "c")
+ assertThat(model.getFileExtensions()).containsEntry("cpp", "c++")
+
+ NativeArtifact artifact = model.artifacts.first()
+ assertThat(artifact.getToolChain()).isEqualTo("toolchain1")
+ assertThat(artifact.sourceFolders).hasSize(1)
+ assertThat(artifact.sourceFolders.first().folderPath.toString()).endsWith("src/main/jni")
+ NativeSettings setting = settingsMap.find { it.getName() == artifact.sourceFolders.first().perLanguageSettings.get("c") }
+ assertThat(setting.getCompilerFlags()).containsAllOf("folderCFlag1", "folderCFlag2");
+ setting = settingsMap.find { it.getName() == artifact.sourceFolders.first().perLanguageSettings.get("c++") }
+ assertThat(setting.getCompilerFlags()).containsAllOf("folderCppFlag1", "folderCppFlag2");
+
+ assertThat(artifact.sourceFiles).hasSize(1)
+ assertThat(artifact.sourceFiles.first().filePath.toString()).endsWith("src/main/jni/hello.c")
+ setting = settingsMap.find { it.getName() == artifact.sourceFiles.first().settingsName }
+ assertThat(setting.getCompilerFlags()).containsAllOf("fileFlag1", "fileFlag2");
+
+ assertThat(model.getToolChains()).hasSize(1)
+ NativeToolchain toolchain = model.getToolChains().first()
+ assertThat(toolchain.getName()).isEqualTo("toolchain1")
+ assertThat(toolchain.getCCompilerExecutable().getName()).isEqualTo("clang")
+ assertThat(toolchain.getCppCompilerExecutable().getName()).isEqualTo("clang++")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/InvalidDependencyOnAppTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/InvalidDependencyOnAppTest.java
new file mode 100644
index 0000000..b43e205
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/InvalidDependencyOnAppTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.build.gradle.integration.component;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Checks that an app cannot depend on another app.
+ */
+public class InvalidDependencyOnAppTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(
+ new MultiModuleTestProject(ImmutableMap.of(
+ ":app", HelloWorldApp.forPlugin("com.android.application"),
+ ":dependency-app", HelloWorldApp.forPlugin("com.android.application"))))
+ .captureStdOut(true)
+ .create();
+
+ @Before
+ public void addBrokenDependency() throws Exception {
+ TestFileUtils.appendToFile(
+ project.getSubproject(":app").getBuildFile(),
+ "dependencies { compile project(':dependency-app') } ");
+ }
+
+ @Test
+ public void testBuildFails() throws Exception {
+ project.executeExpectingFailure("assembleDebug");
+ assertThat(project.getStdoutString()).contains("resolves to an APK");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/LibraryComponentPluginTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/LibraryComponentPluginTest.groovy
new file mode 100644
index 0000000..d5d475e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/LibraryComponentPluginTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.SmokeTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldLibraryApp
+import groovy.transform.CompileStatic
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+
+/**
+ * Basic integration test for LibraryComponentModelPlugin.
+ */
+ at Category(SmokeTests.class)
+ at CompileStatic
+class LibraryComponentPluginTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldLibraryApp())
+ .forExperimentalPlugin(true)
+ .create();
+
+
+ @Test
+ void "check build config file is included"() {
+ project.getSubproject("app").buildFile << """
+apply plugin: "com.android.model.application"
+
+dependencies {
+ compile project(":lib")
+}
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+}
+"""
+
+ project.getSubproject("lib").buildFile << """
+apply plugin: "com.android.model.library"
+
+dependencies {
+ /* Depend on annotations to trigger the creation of the ExtractAnnotations task */
+ compile 'com.android.support:support-annotations:22.2.0'
+}
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+}
+"""
+ project.execute("assemble")
+ File releaseAar = project.getSubproject("lib").getAar("release");
+ assertThatAar(releaseAar).containsClass("Lcom/example/helloworld/BuildConfig;");
+ }
+
+ @Test
+ void "check multi flavor dependencies"() {
+ project.getSubproject("app").buildFile << """
+apply plugin: "com.android.model.application"
+
+dependencies {
+ compile project(path: ":lib", configuration: "freeDebug")
+}
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+}
+"""
+
+ project.getSubproject("lib").buildFile << """
+apply plugin: "com.android.model.library"
+
+configurations {
+ freeDebug
+}
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ publishNonDefault true
+
+ productFlavors {
+ create("free")
+ create("premium")
+ }
+ }
+}
+"""
+ project.execute(":app:assembleDebug")
+ assertThat(project.getSubproject("app").file("build/intermediates/exploded-aar/project/lib/unspecified/freeDebug")).isDirectory()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentModelTest.groovy
new file mode 100644
index 0000000..0615e69
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentModelTest.groovy
@@ -0,0 +1,285 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.SdkConstants
+import com.android.build.gradle.integration.common.category.SmokeTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.NativeLibrary
+import com.android.builder.model.Variant
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Test the return model of the NDK.
+ */
+ at Category(SmokeTests.class)
+class NdkComponentModelTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @Before
+ void setUp() {
+ project.buildFile <<
+"""
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ CFlags.add("-DTEST_C_FLAG")
+ cppFlags.add("-DTEST_CPP_FLAG")
+ toolchain "clang"
+ }
+ }
+}
+"""
+ }
+
+ @Test
+ void "check native libraries in model"() {
+ checkModel(
+ debug : [
+ SdkConstants.ABI_ARMEABI,
+ SdkConstants.ABI_ARMEABI_V7A,
+ SdkConstants.ABI_ARM64_V8A,
+ SdkConstants.ABI_INTEL_ATOM,
+ SdkConstants.ABI_INTEL_ATOM64,
+ SdkConstants.ABI_MIPS,
+ SdkConstants.ABI_MIPS64
+ ]);
+ }
+
+ @Test
+ void "check targeted ABI"() {
+project.buildFile <<
+"""
+model {
+ android {
+ ndk {
+ abiFilters.add("x86")
+ abiFilters.add("armeabi-v7a")
+ }
+ abis {
+ create("x86") {
+ CFlags.add("-DX86")
+ }
+ create("armeabi-v7a") {
+ CFlags.add("-DARMEABI_V7A")
+ }
+ }
+ }
+}
+"""
+ AndroidProject model = checkModel(
+ debug : [
+ SdkConstants.ABI_ARMEABI_V7A,
+ SdkConstants.ABI_INTEL_ATOM
+ ]);
+ Collection<NativeLibrary> libs =
+ ModelHelper.getVariant(model.getVariants(), "debug").getMainArtifact().getNativeLibraries()
+
+ for (NativeLibrary nativeLibrary : libs) {
+ if (nativeLibrary.getAbi() == "x86") {
+ assertThat(nativeLibrary.getCCompilerFlags()).contains("-DX86")
+ assertThat(nativeLibrary.getCCompilerFlags()).doesNotContain("-DARMEABI_V7A")
+ } else {
+ assertThat(nativeLibrary.getCCompilerFlags()).doesNotContain("-DX86")
+ assertThat(nativeLibrary.getCCompilerFlags()).contains("-DARMEABI_V7A")
+ }
+ }
+ }
+
+ @Test
+ void "check native libraries with splits"() {
+ project.buildFile <<
+"""
+model {
+ android {
+ splits.with {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'armeabi-v7a', 'mips'
+ }
+ }
+ }
+}
+"""
+ checkModel(
+ debug: [SdkConstants.ABI_ARMEABI_V7A, SdkConstants.ABI_INTEL_ATOM, SdkConstants.ABI_MIPS]);
+ }
+
+ @Test
+ void "check native libraries with splits and universalApk"() {
+ project.buildFile <<
+ """
+model {
+ android {
+ splits.with {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'armeabi-v7a', 'mips'
+ universalApk true
+ }
+ }
+ }
+}
+"""
+ checkModel(
+ debug : [
+ SdkConstants.ABI_ARMEABI,
+ SdkConstants.ABI_ARMEABI_V7A,
+ SdkConstants.ABI_ARM64_V8A,
+ SdkConstants.ABI_INTEL_ATOM,
+ SdkConstants.ABI_INTEL_ATOM64,
+ SdkConstants.ABI_MIPS,
+ SdkConstants.ABI_MIPS64
+ ]);
+ }
+
+ @Test
+ void "check native libraries with abiFilters"() {
+ project.buildFile <<
+ """
+model {
+ android.productFlavors {
+ create("x86") {
+ ndk.abiFilters.add("x86")
+ }
+ create("arm") {
+ ndk.abiFilters.add("armeabi-v7a")
+ }
+ create("mips") {
+ ndk.abiFilters.add("mips")
+ }
+ }
+}
+"""
+ checkModel(
+ x86Debug : [SdkConstants.ABI_INTEL_ATOM],
+ armDebug : [SdkConstants.ABI_ARMEABI_V7A],
+ mipsDebug : [SdkConstants.ABI_MIPS]);
+ }
+
+ @Test
+ void "check variant specific flags"() {
+ project.buildFile <<
+ """
+model {
+ android.buildTypes {
+ debug {
+ ndk.CFlags.add("-DTEST_FLAG_DEBUG")
+ }
+ release {
+ ndk.CFlags.add("-DTEST_FLAG_RELEASE")
+ }
+ }
+ android.productFlavors {
+ create("f1") {
+ ndk.CFlags.add("-DTEST_FLAG_F1")
+ }
+ create("f2") {
+ ndk.CFlags.add("-DTEST_FLAG_F2")
+ }
+ }
+}
+"""
+ AndroidProject model = project.executeAndReturnModel("assembleDebug")
+ NativeLibrary f1Debug = ModelHelper.getVariant(model.getVariants(), "f1Debug").getMainArtifact()
+ .getNativeLibraries().first()
+ assertThat(f1Debug.getCCompilerFlags()).contains("-DTEST_FLAG_DEBUG")
+ assertThat(f1Debug.getCCompilerFlags()).contains("-DTEST_FLAG_F1")
+ NativeLibrary f2Release = ModelHelper.getVariant(model.getVariants(), "f2Release").getMainArtifact()
+ .getNativeLibraries().first()
+ assertThat(f2Release.getCCompilerFlags()).contains("-DTEST_FLAG_RELEASE")
+ assertThat(f2Release.getCCompilerFlags()).contains("-DTEST_FLAG_F2")
+ }
+
+ @Test
+ void "check using add on string for compileSdkVersion"() {
+ project.buildFile <<
+"""
+model {
+ android {
+ compileSdkVersion = "Google Inc.:Google APIs:$GradleTestProject.DEFAULT_COMPILE_SDK_VERSION"
+ }
+}
+"""
+ AndroidProject model = project.executeAndReturnModel("assembleDebug")
+ NativeLibrary lib = ModelHelper.getVariant(model.getVariants(), "debug").getMainArtifact()
+ .getNativeLibraries().first()
+ for (String flag : lib.getCCompilerFlags()) {
+ if (flag.contains("sysroot")) {
+ assertThat(flag).contains("android-${GradleTestProject.LATEST_NDK_VERSION}")
+ }
+ }
+ }
+
+ /**
+ * Verify resulting model is as expected.
+ *
+ * @param variantToolchains map of variant name to array of expected toolchains.
+ */
+ private AndroidProject checkModel(Map variantToolchains) {
+
+ AndroidProject model = project.executeAndReturnModel("assembleDebug")
+
+ Collection<Variant> variants = model.getVariants()
+ for (Map.Entry entry : variantToolchains) {
+ Variant variant = ModelHelper.getVariant(variants, (String) entry.getKey())
+ AndroidArtifact mainArtifact = variant.getMainArtifact()
+
+ assertThat(mainArtifact.getNativeLibraries()).hasSize(((Collection)entry.getValue()).size())
+ for (NativeLibrary nativeLibrary : mainArtifact.getNativeLibraries()) {
+ assertThat(nativeLibrary.getName()).isEqualTo("hello-jni")
+ assertThat(nativeLibrary.getCCompilerFlags()).contains("-DTEST_C_FLAG");
+ assertThat(nativeLibrary.getCCompilerFlags()).contains("-gcc-toolchain"); // check clang specific flags
+ assertThat(nativeLibrary.getCppCompilerFlags()).contains("-DTEST_CPP_FLAG");
+ assertThat(nativeLibrary.getCppCompilerFlags()).contains("-gcc-toolchain"); // check clang specific flags
+ assertThat(nativeLibrary.getCSystemIncludeDirs()).isEmpty();
+ assertThat(nativeLibrary.getCppSystemIncludeDirs()).isNotEmpty();
+ File solibSearchPath = nativeLibrary.getDebuggableLibraryFolders().first()
+ assertThat(new File(solibSearchPath, "libhello-jni.so")).exists()
+ }
+
+ Collection<String> expectedToolchainNames = entry.getValue().collect { "clang-" + it }
+ Collection<String> toolchainNames = model.getNativeToolchains().collect { it.getName() }
+ assertThat(toolchainNames).containsAllIn(expectedToolchainNames)
+ Collection<String> nativeLibToolchains = mainArtifact.getNativeLibraries().
+ collect { it.getToolchainName() }
+ assertThat(nativeLibToolchains).containsExactlyElementsIn(expectedToolchainNames)
+ }
+ return model
+
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentPluginTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentPluginTest.groovy
new file mode 100644
index 0000000..1c0f375
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentPluginTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+
+/**
+ * Tests for NdkComponentModelPlugin
+ */
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Basic integration test for ndk component plugin.
+ */
+ at CompileStatic
+class NdkComponentPluginTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @BeforeClass
+ public static void setUp() {
+ project.getBuildFile() << """
+import com.android.build.gradle.model.NdkComponentModelPlugin
+apply plugin: NdkComponentModelPlugin
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void assemble() {
+ project.execute("assemble");
+ assertThat(project.file("build/intermediates/binaries/debug/obj/x86/libhello-jni.so")).exists()
+ assertThat(project.file("build/intermediates/binaries/debug/lib/x86/libhello-jni.so")).exists()
+ assertThat(project.file("build/intermediates/binaries/release/lib/x86/libhello-jni.so")).exists()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentSplitTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentSplitTest.groovy
new file mode 100644
index 0000000..e990ff4
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentSplitTest.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Integration test of the native plugin with multiple variants.
+ */
+ at CompileStatic
+class NdkComponentSplitTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @BeforeClass
+ public static void setUp() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ generatePureSplits true
+
+ defaultConfig {
+ minSdkVersion {
+ apiLevel 21
+ }
+ }
+
+ splits.with {
+ abi {
+ enable true
+ reset()
+ include "x86", "armeabi-v7a", "mips"
+ }
+ }
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void assembleDebug() {
+ // Ensure compileDebugSource creates the shared object.
+ project.execute("compileDebugSources");
+ assertThat(project.file("build/intermediates/binaries/debug/lib/x86/libhello-jni.so"))
+ .exists();
+
+ project.execute("assembleDebug");
+
+ // Verify .so are built for all platform.
+ File apk = project.getApk("debug")
+ assertThatZip(apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/mips/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/x86/libhello-jni.so")
+
+ File armApk = project.getApk("debug_armeabi-v7a")
+ assertThatZip(armApk).contains("lib/armeabi-v7a/libhello-jni.so")
+ assertThatZip(armApk).doesNotContain("lib/mips/libhello-jni.so")
+ assertThatZip(armApk).doesNotContain("lib/x86/libhello-jni.so")
+
+ File mipsApk = project.getApk("debug_mips")
+ assertThatZip(mipsApk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
+ assertThatZip(mipsApk).contains("lib/mips/libhello-jni.so")
+ assertThatZip(mipsApk).doesNotContain("lib/x86/libhello-jni.so")
+
+ File x86Apk = project.getApk("debug_x86")
+ assertThatZip(x86Apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
+ assertThatZip(x86Apk).doesNotContain("lib/mips/libhello-jni.so")
+ assertThatZip(x86Apk).contains("lib/x86/libhello-jni.so")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connectedAndroidTest() {
+ project.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentVariantTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentVariantTest.groovy
new file mode 100644
index 0000000..f164edd
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkComponentVariantTest.groovy
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.utils.DeviceHelper
+import com.android.builder.core.BuilderConstants
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static com.google.common.truth.Truth.assertThat
+
+/**
+ * Integration test of the native plugin with multiple variants.
+ */
+ at CompileStatic
+class NdkComponentVariantTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @BeforeClass
+ public static void setUp() {
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ }
+ productFlavors {
+ create("x86") {
+ ndk.abiFilters.add("x86")
+ }
+ create("arm") {
+ ndk.abiFilters.add("armeabi-v7a")
+ ndk.abiFilters.add("armeabi")
+ }
+ create("mips") {
+ ndk.abiFilters.add("mips")
+ }
+ }
+ abis {
+ create("x86") {
+ CFlags.add("-DX86")
+ }
+ create("armeabi-v7a") {
+ CFlags.add("-DARMEABI_V7A")
+ }
+ }
+ }
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void "check old ndk tasks are not created"() {
+ List<String> tasks = project.getTaskList()
+ assertThat(tasks).containsNoneOf(
+ "compileArmDebugNdk",
+ "compileX86DebugNdk",
+ "compileMipsDebugNdk",
+ "compileArmReleaseNdk",
+ "compileX86ReleaseNdk",
+ "compileMipsReleaseNdk")
+ }
+
+ @Test
+ public void assembleX86Debug() {
+ project.execute("assembleX86Debug")
+
+ // Verify .so are built for all platform.
+ File apk = project.getApk("x86", "debug")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/mips/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/armeabi/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
+ }
+
+ @Test
+ public void assembleArmDebug() {
+ project.execute("assembleArmDebug")
+
+ // Verify .so are built for all platform.
+ File apk = project.getApk("arm", "debug")
+ assertThatZip(apk).doesNotContain("lib/x86/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/mips/libhello-jni.so")
+ assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
+ assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
+ }
+
+ @Test
+ public void assembleMipsDebug() {
+ project.execute("assembleMipsDebug")
+
+ // Verify .so are built for all platform.
+ File apk = project.getApk("mips", "debug")
+ assertThatZip(apk).doesNotContain("lib/x86/libhello-jni.so")
+ assertThatZip(apk).contains("lib/mips/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/armeabi/libhello-jni.so")
+ assertThatZip(apk).doesNotContain("lib/armeabi-v7a/libhello-jni.so")
+ }
+
+ @Test
+ public void "check release build"() {
+ project.execute("assembleArmRelease")
+
+ File apk = project.getApk("arm", "release", "unsigned")
+ assertThatZip(apk).contains("lib/armeabi/libhello-jni.so")
+ assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connectedAndroidTest() {
+ if (GradleTestProject.DEVICE_PROVIDER_NAME.equals(BuilderConstants.CONNECTED)) {
+ Collection<String> abis = DeviceHelper.getDeviceAbis()
+ if (abis.contains("x86")) {
+ project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "x86DebugAndroidTest")
+ } else {
+ project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "ArmDebugAndroidTest")
+ }
+ } else {
+ project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "X86DebugAndroidTest")
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkDependencyTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkDependencyTest.groovy
new file mode 100644
index 0000000..621f34a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkDependencyTest.groovy
@@ -0,0 +1,310 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.NativeAndroidProject
+import com.android.builder.model.NativeLibrary
+import com.android.builder.model.NativeSettings
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Test for dependencies on NDK projects.
+ */
+ at CompileStatic
+class NdkDependencyTest {
+ private static final String[] ABIS =
+ ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"];
+
+ static MultiModuleTestProject base = new MultiModuleTestProject(
+ app: new HelloWorldJniApp(),
+ lib1: new EmptyAndroidTestApp(),
+ lib2: new EmptyAndroidTestApp())
+
+ static {
+ AndroidTestApp app = (HelloWorldJniApp) base.getSubproject("app")
+
+ app.removeFile(app.getFile("hello-jni.c"))
+ app.addFile(new TestSourceFile("src/main/jni", "hello-jni.cpp",
+ """
+#include <string.h>
+#include <jni.h>
+#include "lib1.h"
+
+extern "C"
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ return env->NewStringUTF(getLib1String());
+}
+"""))
+
+ app.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+ android.ndk {
+ moduleName = "hello-jni"
+ }
+ android.sources {
+ main {
+ jni {
+ dependencies {
+ project ":lib1"
+ }
+ }
+ }
+ }
+}
+"""))
+
+ AndroidTestApp lib1 = (AndroidTestApp) base.getSubproject("lib1")
+ lib1.addFile(new TestSourceFile("src/main/headers/", "lib1.h", """
+#ifndef INCLUDED_LIB1_H
+#define INCLUDED_LIB1_H
+
+char* getLib1String();
+
+#endif
+"""))
+ lib1.addFile(new TestSourceFile("src/main/jni/", "lib1.cpp", """
+#include "lib1.h"
+#include "lib2.h"
+
+char* getLib1String() {
+ return getLib2String();
+}
+"""))
+ lib1.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.native"
+
+model {
+ android {
+ compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ }
+ android.ndk {
+ moduleName = "getstring1"
+ }
+ android.sources {
+ main {
+ jni {
+ dependencies {
+ project ":lib2"
+ }
+ exportedHeaders {
+ srcDir "src/main/headers"
+ }
+ }
+ }
+ }
+}
+"""))
+
+ AndroidTestApp lib2 = (AndroidTestApp) base.getSubproject("lib2")
+ lib2.addFile(new TestSourceFile("src/main/headers/", "lib2.h", """
+#ifndef INCLUDED_LIB2_H
+#define INCLUDED_LIB2_H
+
+char* getLib2String();
+
+#endif
+"""))
+ // Add c++ file that uses function from the STL.
+ lib2.addFile(new TestSourceFile("src/main/jni/", "lib2.cpp", """
+#include "lib2.h"
+#include <algorithm>
+#include <cstring>
+#include <cctype>
+
+char* getLib2String() {
+ char* greeting = new char[32];
+ std::strcpy(greeting, "HELLO WORLD!");
+ std::transform(greeting, greeting + strlen(greeting), greeting, std::tolower);
+ return greeting; // memory leak if greeting is not deallocated, but doesn't matter.
+}
+"""))
+ lib2.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.native"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ ndk {
+ moduleName "getstring2"
+ stl "stlport_shared"
+ }
+ sources {
+ main {
+ jni {
+ exportedHeaders {
+ srcDir "src/main/headers"
+ }
+ }
+ }
+ }
+ }
+}
+"""))
+ }
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(base)
+ .forExperimentalPlugin(true)
+ .captureStdOut(true)
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ base = null
+ }
+
+ @Test
+ void "check app contains compiled .so"() {
+ Map<String, AndroidProject> models =
+ project.executeAndReturnMultiModel("clean", ":app:assembleDebug")
+ GradleTestProject app = project.getSubproject("app")
+ GradleTestProject lib1 = project.getSubproject("lib1")
+ GradleTestProject lib2 = project.getSubproject("lib2")
+
+ assertThat(models).containsKey(":app")
+
+ AndroidProject model = models.get(":app")
+
+ final File apk = project.getSubproject("app").getApk("debug")
+ for (String abi : ABIS) {
+ assertThatZip(apk).contains("lib/$abi/libhello-jni.so")
+ assertThatZip(apk).contains("lib/$abi/libstlport_shared.so")
+ assertThatZip(apk).contains("lib/$abi/libgetstring1.so")
+ assertThatZip(apk).contains("lib/$abi/libgetstring2.so")
+
+ NativeLibrary libModel = findNativeLibraryByAbi(model, "debug", abi)
+ assertThat(libModel).isNotNull();
+ assertThat(libModel.getDebuggableLibraryFolders()).containsAllOf(
+ app.file("build/intermediates/binaries/debug/obj/$abi"),
+ lib1.file("build/intermediates/binaries/debug/obj/$abi"),
+ lib2.file("build/intermediates/binaries/debug/obj/$abi"),
+ )
+ }
+ }
+
+ @Test
+ void "check static linkage"() {
+ GradleTestProject lib1 = project.getSubproject("lib1")
+ lib1.buildFile.delete()
+ lib1.buildFile << """
+apply plugin: "com.android.model.native"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ ndk {
+ moduleName "hello-jni"
+ }
+ sources {
+ main {
+ jni {
+ dependencies {
+ project ":lib2" linkage "static"
+ }
+ exportedHeaders {
+ srcDir "src/main/headers"
+ }
+ }
+ }
+ }
+ }
+}
+"""
+ Map<String, NativeAndroidProject> models =
+ project.executeAndReturnMultiModel(
+ NativeAndroidProject.class,
+ "clean",
+ ":app:assembleDebug")
+ NativeAndroidProject model = models.get(":app")
+ File apk = project.getSubproject("app").getApk("debug")
+ for (String abi : ABIS) {
+ assertThatZip(apk).contains("lib/$abi/libhello-jni.so")
+ assertThatZip(apk).contains("lib/$abi/libstlport_shared.so")
+ assertThatZip(apk).doesNotContain("lib/$abi/libget-string.so")
+
+ // Check that the static library is compiled, but not the shared library.
+ GradleTestProject lib2 = project.getSubproject("lib2")
+ assertThat(lib2.file("build/intermediates/binaries/debug/obj/$abi/libgetstring2.a")).exists()
+ assertThat(lib2.file("build/intermediates/binaries/debug/obj/$abi/libgetstring2.so")).doesNotExist()
+ }
+ for (NativeSettings settings : model.getSettings()) {
+ assertThat(settings.getCompilerFlags()).contains("-I" + lib1.file("src/main/headers"))
+ }
+ }
+
+ @Test
+ void "check update in lib triggers rebuild"() {
+ project.execute("clean", ":app:assembleDebug")
+ GradleTestProject app = project.getSubproject("app")
+ GradleTestProject lib1 = project.getSubproject("lib1")
+ GradleTestProject lib2 = project.getSubproject("lib2")
+
+ File apk = project.getSubproject("app").getApk("debug")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so")
+ assertThatZip(apk).contains("lib/x86/libstlport_shared.so")
+
+ lib2.file("src/main/jni/lib2.cpp") << "void foo() {}"
+
+ File appSo = app.file("build/intermediates/binaries/debug/obj/x86/libhello-jni.so")
+ File lib1So = lib1.file("build/intermediates/binaries/debug/obj/x86/libgetstring1.so")
+ File lib2So = lib2.file("build/intermediates/binaries/debug/obj/x86/libgetstring2.so")
+
+ long appModifiedTime = appSo.lastModified()
+ long lib1ModifiedTime = lib1So.lastModified()
+ long lib2ModifiedTime = lib2So.lastModified()
+
+ project.stdout.reset()
+ project.execute(":app:assembleDebug")
+
+ assertThat(lib2So).isNewerThan(lib2ModifiedTime)
+ assertThat(lib1So).isNewerThan(lib1ModifiedTime)
+ assertThat(appSo).isNewerThan(appModifiedTime)
+ }
+
+ private static NativeLibrary findNativeLibraryByAbi(
+ AndroidProject model,
+ String variantName,
+ String abi) {
+ AndroidArtifact artifact =
+ ModelHelper.getVariant(model.getVariants(), variantName).getMainArtifact()
+ return artifact.getNativeLibraries().find { it.abi == abi }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkFlagsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkFlagsTest.groovy
new file mode 100644
index 0000000..b19583c
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkFlagsTest.groovy
@@ -0,0 +1,204 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Tests C/C++/ld flags in an NDK project.
+ */
+ at CompileStatic
+class NdkFlagsTest {
+
+ static AndroidTestApp cApp = new HelloWorldJniApp()
+ static {
+ TestSourceFile orig = cApp.getFile("hello-jni.c")
+ cApp.removeFile(orig)
+ cApp.addFile(new TestSourceFile(orig.path, orig.name,
+ """
+#include <string.h>
+#include <jni.h>
+
+// This is a trivial JNI example where we use a native method
+// to return a new VM String.
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ return (*env)->NewStringUTF(env, HELLO_WORLD EXCLAMATION_MARK);
+}
+"""
+ ))
+ }
+
+
+ @ClassRule
+ public static GradleTestProject cProject = GradleTestProject.builder()
+ .withName("c_project")
+ .fromTestApp(cApp)
+ .forExperimentalPlugin(true)
+ .create();
+
+ static AndroidTestApp cppApp = new HelloWorldJniApp(useCppSource: true)
+ static {
+ TestSourceFile orig = cppApp.getFile("hello-jni.cpp")
+ cppApp.removeFile(orig)
+ cppApp.addFile(new TestSourceFile(orig.path, orig.name,
+ """
+#include <string.h>
+#include <jni.h>
+
+// This is a trivial JNI example where we use a native method
+// to return a new VM String.
+extern "C"
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ // HELLO_WORLD and EXCLAMATION_MARK must be defined as follows during compilation.
+ // #define HELLO_WORLD "hello world"
+ // #define EXCLAMATION_MARK "!"
+ return env->NewStringUTF(HELLO_WORLD EXCLAMATION_MARK);
+}
+"""
+ ))
+
+ }
+
+ @ClassRule
+ public static GradleTestProject cppProject = GradleTestProject.builder()
+ .withName("cpp_project")
+ .fromTestApp(cppApp)
+ .forExperimentalPlugin(true)
+ .create();
+
+ static AndroidTestApp ldApp = new HelloWorldJniApp()
+ static {
+ ldApp.addFile(new TestSourceFile("src/main/jni", "log.c",
+ """
+#include <android/log.h>
+
+// Simple function that uses function from an external library. Should fail unless -llog is set
+// when linking.
+void log() {
+ __android_log_print(ANDROID_LOG_INFO, "hello-world", "Hello World!");
+}
+"""))
+ }
+
+ @ClassRule
+ public static GradleTestProject ldProject = GradleTestProject.builder()
+ .withName("ld_project")
+ .fromTestApp(ldApp)
+ .forExperimentalPlugin(true)
+ .create();
+
+
+ @BeforeClass
+ public static void setUp() {
+ cProject.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ CFlags.addAll(['-DHELLO_WORLD="hello world"', '-DEXCLAMATION_MARK="!"'])
+ CFlags.add(' -DFLAG_WITH_LEADING_SPACE')
+ }
+ }
+}
+"""
+
+ cppProject.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ cppFlags.addAll(['-DHELLO_WORLD="hello world"', '-DEXCLAMATION_MARK="!"'])
+ }
+ }
+}
+"""
+
+ ldProject.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ ldFlags.addAll("-llog")
+ }
+ }
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ cProject = null
+ }
+
+ @Test
+ public void "assemble C project"() {
+ cProject.execute("assembleDebug")
+ assertThatZip(cProject.getApk("debug")).contains("lib/x86/libhello-jni.so")
+ }
+
+ @Test
+ public void "assemble C++ project"() {
+ cppProject.execute("assembleDebug")
+ assertThatZip(cppProject.getApk("debug")).contains("lib/x86/libhello-jni.so")
+ }
+
+ @Test
+ public void "assemble ld project"() {
+ ldProject.execute("assembleDebug")
+ assertThatZip(ldProject.getApk("debug")).contains("lib/x86/libhello-jni.so")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void "connectedCheck C project"() {
+ cProject.executeConnectedCheck();
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void "connectedCheck C++ project"() {
+ cppProject.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkIncrementalTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkIncrementalTest.groovy
new file mode 100644
index 0000000..6074f89
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkIncrementalTest.groovy
@@ -0,0 +1,147 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.utils.IncrementalTaskOutputVerifier
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import java.util.regex.Pattern
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Test incremental compilation for NDK.
+ */
+ at CompileStatic
+class NdkIncrementalTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .forExperimentalPlugin(true)
+ .captureStdOut(true)
+ .create();
+
+ @Before
+ public void setUp() {
+ project.file("src/main/jni/empty.c").createNewFile()
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+}
+"""
+ project.execute("assembleDebug")
+ }
+
+ @Test
+ public void "check adding file"() {
+ File helloJniO = FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/hello-jni\\.o")).first()
+ long helloJniTimestamp = helloJniO.lastModified()
+
+ // check new-file.o does not exist.
+ assertThat(FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/new-file\\.o"))).hasSize(0)
+
+ project.file("src/main/jni/new-file.c") << " ";
+
+ project.stdout.reset()
+ project.execute("assembleDebug")
+
+ IncrementalTaskOutputVerifier verifier =
+ new IncrementalTaskOutputVerifier(project.stdout.toString());
+ verifier.assertThatFile(project.file("src/main/jni/new-file.c")).hasBeenAdded()
+ verifier.assertThatFile(project.file("src/main/jni/hello-jni.c")).hasNotBeenChanged()
+
+ assertThat(helloJniO).wasModifiedAt(helloJniTimestamp)
+
+ assertThat(FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/new-file\\.o"))).hasSize(1)
+ }
+
+ @Test
+ public void "check removing file"() {
+ File helloJniO = FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/hello-jni\\.o")).first()
+ long helloJniTimestamp = helloJniO.lastModified()
+
+ assertThat(FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/empty\\.o"))).hasSize(1)
+
+ project.file("src/main/jni/empty.c").delete()
+
+ project.stdout.reset()
+ project.execute("assembleDebug")
+
+ IncrementalTaskOutputVerifier verifier =
+ new IncrementalTaskOutputVerifier(project.stdout.toString());
+ verifier.assertThatFile(project.file("src/main/jni/empty.c")).hasBeenRemoved()
+ verifier.assertThatFile(project.file("src/main/jni/hello-jni.c")).hasNotBeenChanged()
+
+ assertThat(helloJniO).wasModifiedAt(helloJniTimestamp)
+
+ assertThat(FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/empty\\.o"))).hasSize(0)
+ }
+
+ @Test
+ public void "check changing file"() {
+ File helloJniO = FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/hello-jni\\.o")).first()
+ long helloJniTimestamp = helloJniO.lastModified()
+
+ File emptyO = FileUtils.find(
+ project.file("build/intermediates/objectFiles"),
+ Pattern.compile("x86Debug.*/empty\\.o")).first()
+ long emptyTimestamp = emptyO.lastModified()
+
+ project.file("src/main/jni/empty.c") << " ";
+
+ project.stdout.reset()
+ project.execute("assembleDebug")
+
+ IncrementalTaskOutputVerifier verifier =
+ new IncrementalTaskOutputVerifier(project.stdout.toString());
+ verifier.assertThatFile(project.file("src/main/jni/empty.c")).hasChanged()
+ verifier.assertThatFile(project.file("src/main/jni/hello-jni.c")).hasNotBeenChanged()
+
+ assertThat(helloJniO).wasModifiedAt(helloJniTimestamp)
+ assertThat(emptyO).isNewerThan(emptyTimestamp)
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkJniLib2Test.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkJniLib2Test.groovy
new file mode 100644
index 0000000..24acb36
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkJniLib2Test.groovy
@@ -0,0 +1,121 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+
+/**
+ * Integration test library plugin with JNI sources.
+ */
+class NdkJniLib2Test {
+ private static MultiModuleTestProject testApp = new MultiModuleTestProject(
+ ":app" : new EmptyAndroidTestApp(),
+ ":lib" : new HelloWorldJniApp());
+
+ static {
+ AndroidTestApp app = (AndroidTestApp) testApp.getSubproject(":app")
+ app.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.application"
+
+dependencies {
+ compile project(":lib")
+}
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+}
+"""))
+
+ // Create AndroidManifest.xml that uses the Activity from the library.
+ app.addFile(new TestSourceFile("src/main", "AndroidManifest.xml",
+ """<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="3" />
+ <application android:label="@string/app_name">
+ <activity
+ android:name="com.example.hellojni.HelloJni"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+"""));
+
+ AndroidTestApp lib = (AndroidTestApp) testApp.getSubproject(":lib")
+ lib.addFile(new TestSourceFile("", "build.gradle",
+ """
+apply plugin: "com.android.model.library"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+}
+"""))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(testApp)
+ .forExperimentalPlugin(true)
+ .create();
+
+ @BeforeClass
+ public static void assemble() {
+ project.execute("clean", ":app:assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ testApp = null
+ }
+
+ @Test
+ void "check .so are included in both app and library"() {
+ File releaseAar = project.getSubproject("lib").getAar("release")
+ assertThatAar(releaseAar).contains("jni/x86/libhello-jni.so")
+
+ File app = project.getSubproject("app").getApk("debug")
+ assertThatAar(app).contains("lib/x86/libhello-jni.so")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkSanAngeles2Test.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkSanAngeles2Test.groovy
new file mode 100644
index 0000000..5ee7589
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkSanAngeles2Test.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.SdkConstants
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.NativeLibrary
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Assemble tests for ndkSanAngeles2.
+ */
+ at CompileStatic
+class NdkSanAngeles2Test {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .forExperimentalPlugin(true)
+ .fromTestProject("ndkSanAngeles2")
+ .create()
+
+ private static AndroidProject model;
+
+ @BeforeClass
+ static void setUp() {
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ void "check model"() {
+ Collection<Variant> variants = model.getVariants()
+ assertThat(variants).hasSize(8)
+
+ Variant debugVariant = ModelHelper.getVariant(variants, "x86Debug")
+ AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
+ assertThat(debugMainArtifact.getNativeLibraries()).hasSize(1)
+
+ NativeLibrary nativeLibrary = debugMainArtifact.getNativeLibraries().first()
+ assertThat(nativeLibrary.getName()).isEqualTo("sanangeles")
+ assertThat(nativeLibrary.getToolchainName()).isEqualTo("clang-x86")
+ assertThat(nativeLibrary.getCCompilerFlags()).contains("-DDISABLE_IMPORTGL");
+ assertThat(nativeLibrary.getCSystemIncludeDirs()).isEmpty();
+ assertThat(nativeLibrary.getCppSystemIncludeDirs()).isNotEmpty()
+ File solibSearchPath = nativeLibrary.getDebuggableLibraryFolders().first()
+ assertThat(new File(solibSearchPath, "libsanangeles.so")).exists()
+
+ Collection<String> toolchainNames = model.getNativeToolchains().collect { it.getName() }
+ Collection<String> expectedToolchains = [
+ SdkConstants.ABI_INTEL_ATOM,
+ SdkConstants.ABI_ARMEABI_V7A,
+ SdkConstants.ABI_ARMEABI,
+ SdkConstants.ABI_MIPS].collect { "clang-" + it }
+ assertThat(toolchainNames).containsAllIn(expectedToolchains)
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStandaloneSoTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStandaloneSoTest.groovy
new file mode 100644
index 0000000..4c53b36
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStandaloneSoTest.groovy
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Tests for standalone NDK plugin and native dependencies.
+ *
+ * Test project consists of an app project that depends on an NDK project that depends on another
+ * NDK project.
+ */
+ at CompileStatic
+class NdkStandaloneSoTest {
+ static MultiModuleTestProject base = new MultiModuleTestProject(
+ app: new HelloWorldJniApp(),
+ lib1: new EmptyAndroidTestApp(),
+ lib2: new EmptyAndroidTestApp())
+
+ static {
+ AndroidTestApp app = (HelloWorldJniApp) base.getSubproject("app")
+ app.removeFile(app.getFile("hello-jni.c"))
+ app.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.application"
+
+model {
+ repositories {
+ libs(PrebuiltLibraries) {
+ prebuilt {
+ binaries.withType(SharedLibraryBinary) {
+ sharedLibraryFile = file("prebuilt.so")
+ }
+ }
+ }
+ }
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ sources {
+ main {
+ jniLibs {
+ dependencies {
+ project ":lib1" buildType "debug"
+ library "prebuilt"
+ }
+ }
+ }
+ }
+ }
+}
+"""))
+ // An empty .so just to check if it can be packaged
+ app.addFile(new TestSourceFile("", "prebuilt.so", ""));
+
+ AndroidTestApp lib1 = (AndroidTestApp) base.getSubproject("lib1")
+ lib1.addFile(new TestSourceFile("src/main/jni", "hello-jni.c",
+ """
+#include <string.h>
+#include <jni.h>
+
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ return (*env)->NewStringUTF(env, getString());
+}
+"""));
+
+ lib1.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.native"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ ndk {
+ moduleName "hello-jni"
+ }
+ sources {
+ main {
+ jni {
+ dependencies {
+ project ":lib2"
+ }
+ }
+ }
+ }
+ }
+}
+"""));
+
+ AndroidTestApp lib2 = (AndroidTestApp) base.getSubproject("lib2")
+ lib2.addFile(new TestSourceFile("src/main/headers/", "hello.h", """
+#ifndef HELLO_H
+#define HELLO_H
+
+char* getString();
+
+#endif
+"""))
+ lib2.addFile(new TestSourceFile("src/main/jni/", "hello.c", """
+char* getString() {
+ return "hello world!";
+}
+"""))
+ lib2.addFile(new TestSourceFile("", "build.gradle", """
+apply plugin: "com.android.model.native"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ ndk {
+ moduleName "hello-jni"
+ }
+ sources {
+ main {
+ jni {
+ exportedHeaders {
+ srcDir "src/main/headers"
+ }
+ }
+ }
+ }
+ }
+}
+"""))
+ }
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(base)
+ .forExperimentalPlugin(true)
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ base = null
+ }
+
+ @Test
+ void "check standalone lib properly creates library"() {
+ project.execute("clean", ":lib1:assembleDebug");
+
+ GradleTestProject lib = project.getSubproject("lib1")
+ assertThat(lib.file("build/outputs/native/debug/lib/x86/libhello-jni.so")).exists();
+ }
+
+ @Test
+ void "check app contains compiled .so"() {
+ project.execute("clean", ":app:assembleRelease");
+
+ GradleTestProject lib1 = project.getSubproject("lib1")
+ assertThat(lib1.file("build/intermediates/binaries/debug/obj/x86/libhello-jni.so")).exists();
+
+ // Check that release lib is not compiled.
+ assertThat(lib1.file("build/intermediates/binaries/release/obj/x86/libhello-jni.so")).doesNotExist();
+
+ File apk = project.getSubproject("app").getApk("release", "unsigned")
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ assertThatZip(apk).contains("lib/x86/prebuilt.so");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlTest.groovy
new file mode 100644
index 0000000..f7c720a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlTest.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.gradle.tooling.BuildException
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+import static com.google.common.truth.TruthJUnit.assume
+import static org.junit.Assert.fail
+
+/**
+ * Integration test for STL containers.
+ *
+ * This unit test is parameterized and will be executed for various values of STL.
+ */
+ at RunWith(Parameterized.class)
+ at CompileStatic
+public class NdkStlTest {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ return [
+ ["system"].toArray(),
+ ["stlport_static"].toArray(),
+ ["stlport_shared"].toArray(),
+ ["gnustl_static"].toArray(),
+ ["gnustl_shared"].toArray(),
+ ["gabi++_static"].toArray(),
+ ["gabi++_shared"].toArray(),
+ ["c++_static"].toArray(),
+ ["c++_shared"].toArray(),
+ ["invalid"].toArray(),
+ ];
+ }
+
+ private String stl;
+
+ NdkStlTest(String stl) {
+ this.stl = stl;
+ }
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp(useCppSource: true))
+ .forExperimentalPlugin(true)
+ .create()
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ ndk {
+ moduleName "hello-jni"
+ stl "$stl"
+ }
+ }
+}
+"""
+ }
+
+ @Test
+ public void buildAppWithStl() {
+ if (!stl.equals("invalid")) {
+ project.execute("assembleDebug");
+
+ File apk = project.getApk("debug");
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ assertThatZip(apk).contains("lib/mips/libhello-jni.so");
+ assertThatZip(apk).contains("lib/armeabi/libhello-jni.so");
+ assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so");
+
+ if (stl.endsWith("shared")) {
+ assertThatZip(apk).contains("lib/x86/lib" + stl + ".so");
+ assertThatZip(apk).contains("lib/mips/lib" + stl + ".so");
+ assertThatZip(apk).contains("lib/armeabi/lib" + stl + ".so");
+ assertThatZip(apk).contains("lib/armeabi-v7a/lib" + stl + ".so");
+ }
+ } else {
+ // Fail if it's invalid.
+ try {
+ project.execute("assembleDebug");
+ fail();
+ } catch (BuildException ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void "check with code that uses the STL"() {
+ assume().that(stl).isNotEqualTo("invalid")
+ assume().that(stl).isNotEqualTo("system")
+ assume().that(stl).isNotEqualTo("gabi++_shared")
+ assume().that(stl).isNotEqualTo("gabi++_static")
+
+ File src = FileUtils.find(project.testDir, "hello-jni.cpp").get()
+ src.delete()
+ src << """
+#include <jni.h>
+#include <string>
+
+extern "C"
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz) {
+ std::string greeting = "hello world!";
+ return env->NewStringUTF(greeting.c_str());
+}
+
+"""
+ project.execute("assembleDebug");
+ File apk = project.getApk("debug");
+ assertThatZip(apk).contains("lib/x86/libhello-jni.so");
+ assertThatZip(apk).contains("lib/mips/libhello-jni.so");
+ assertThatZip(apk).contains("lib/armeabi/libhello-jni.so");
+ assertThatZip(apk).contains("lib/armeabi-v7a/libhello-jni.so");
+
+ if (stl.endsWith("shared")) {
+ assertThatZip(apk).contains("lib/x86/lib" + stl + ".so");
+ assertThatZip(apk).contains("lib/mips/lib" + stl + ".so");
+ assertThatZip(apk).contains("lib/armeabi/lib" + stl + ".so");
+ assertThatZip(apk).contains("lib/armeabi-v7a/lib" + stl + ".so");
+ }
+
+ }
+}
+
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlVersionTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlVersionTest.java
new file mode 100644
index 0000000..7a7ff79
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkStlVersionTest.java
@@ -0,0 +1,70 @@
+package com.android.build.gradle.integration.component;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+
+import org.codehaus.groovy.runtime.ResourceGroovyMethods;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+
+/**
+ * Test STL version.
+ *
+ * This test will need to be updated as we update NDK version.
+ */
+public class NdkStlVersionTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldJniApp.builder().useCppSource(true).build())
+ .forExperimentalPlugin(true).create();
+
+ @Before
+ public void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "apply plugin: 'com.android.model.application'\n"
+ + "model {\n"
+ + " android {\n"
+ + " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n"
+ + " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n"
+ + " ndk {\n"
+ + " moduleName \"hello-jni\"\n"
+ + " abiFilters.addAll([\"x86\", \"armeabi-v7a\", \"mips\"])\n"
+ + " stl \"gnustl_shared\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void checkDefaultStlVersion() {
+ project.execute("clean", "assembleDebug");
+ File cppOptions = project.file("build/tmp/compileHello-jniX86DebugSharedLibraryHello-jniMainCpp/options.txt");
+ assertThat(cppOptions).containsAllOf("gnu-libstdc++/4.9/");
+ }
+
+ @Test
+ public void checkCustomStlVersion() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "apply plugin: 'com.android.model.application'\n"
+ + "model {\n"
+ + " android {"
+ + " ndk {\n"
+ + " stlVersion = \"4.8\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ project.execute("clean", "assembleDebug");
+ File cppOptions = project.file("build/tmp/compileHello-jniX86DebugSharedLibraryHello-jniMainCpp/options.txt");
+ assertThat(cppOptions).containsAllOf("gnu-libstdc++/4.8/");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkVariantsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkVariantsTest.groovy
new file mode 100644
index 0000000..ad83780
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/NdkVariantsTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for ndkVariants.
+ */
+ at CompileStatic
+class NdkVariantsTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .forExperimentalPlugin(true)
+ .fromTestProject("ndkVariants")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug");
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connnectedAndroidTest() {
+ project.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/StandaloneNdkModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/StandaloneNdkModelTest.groovy
new file mode 100644
index 0000000..9c6dab2
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/StandaloneNdkModelTest.groovy
@@ -0,0 +1,228 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.builder.model.NativeAndroidProject
+import com.android.builder.model.NativeArtifact
+import com.android.builder.model.NativeFolder
+import com.android.builder.model.NativeSettings
+import com.android.utils.FileUtils
+import com.google.common.collect.ImmutableSet
+import com.google.common.collect.Sets
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Test NativeAndroidProject model generated by StandaloneNdkPlugin.
+ */
+ at CompileStatic
+class StandaloneNdkModelTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new EmptyAndroidTestApp())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @Before
+ public void setUp() {
+ FileUtils.createFile(project.file("src/main/jni/empty.c"), "")
+ }
+
+ private final Set<String> ABIS = ImmutableSet.of(
+ "armeabi",
+ "armeabi-v7a",
+ "arm64-v8a",
+ "x86",
+ "x86_64",
+ "mips",
+ "mips64")
+
+ @Test
+ public void "check simple model"() {
+ project.buildFile << """
+apply plugin: "com.android.model.native"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ ndk {
+ moduleName "hello-jni"
+ }
+ sources {
+ main {
+ jni {
+ exportedHeaders.srcDir("src/main/headers")
+ }
+ }
+ }
+ }
+}
+"""
+ NativeAndroidProject model =
+ project.executeAndReturnModel(NativeAndroidProject.class, "clean", "assemble")
+ assertThat(model.buildFiles).isEmpty()
+ checkModel(model, ImmutableSet.of("debug", "release"), ImmutableSet.of());
+ }
+
+ @Test
+ public void "check model with variants"() {
+ project.buildFile << """
+apply plugin: "com.android.model.native"
+
+model {
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ ndk {
+ moduleName "hello-jni"
+ }
+ buildTypes {
+ debug {
+ ndk {
+ CFlags.add("-DDEBUG_C")
+ cppFlags.add("-DDEBUG_CPP")
+ }
+ }
+ release {
+ ndk {
+ CFlags.add("-DRELEASE_C")
+ cppFlags.add("-DRELEASE_CPP")
+ }
+ }
+ create("optimized") {
+ ndk {
+ CFlags.add("-DOPTIMIZED_C")
+ cppFlags.add("-DOPTIMIZED_CPP")
+ }
+ }
+ }
+ productFlavors {
+ create("free") {
+ ndk {
+ CFlags.add("-DFREE_C")
+ cppFlags.add("-DFREE_CPP")
+ }
+ }
+ create("premium") {
+ ndk {
+ CFlags.add("-DPREMIUM_C")
+ cppFlags.add("-DPREMIUM_CPP")
+ }
+ }
+ }
+ sources {
+ main {
+ jni {
+ exportedHeaders.srcDir("src/main/headers")
+ }
+ }
+ }
+ }
+}
+"""
+ NativeAndroidProject model =
+ project.executeAndReturnModel(NativeAndroidProject.class, "clean", "assemble")
+ assertThat(model.buildFiles).isEmpty()
+ checkModel(
+ model,
+ ImmutableSet.of("debug", "release", "optimized"),
+ ImmutableSet.of("free", "premium"));
+
+ // Check all setting contains the dimension specific flags.
+ List<String> allDimensions = [ "debug", "release", "optimized", "free", "premium" ];
+ for (NativeArtifact artifact : model.getArtifacts()) {
+ for (NativeFolder folder : artifact.sourceFolders) {
+ String cSettingName = folder.perLanguageSettings.get("c")
+ NativeSettings cSettings = model.getSettings().find { it.name == cSettingName }
+ assertThat(cSettings).isNotNull()
+
+ String cppSettingName = folder.perLanguageSettings.get("c++")
+ NativeSettings cppSettings = model.getSettings().find { it.name == cppSettingName }
+ assertThat(cppSettings).isNotNull()
+
+ for (String dimension : allDimensions) {
+ String expectedCFlag = "-D" + dimension.toUpperCase() + "_C"
+ String expectedCppFlag = "-D" + dimension.toUpperCase() + "_CPP"
+
+ if (artifact.name.toLowerCase().contains(dimension)) {
+ assertThat(cSettings.compilerFlags).contains(expectedCFlag)
+ assertThat(cppSettings.compilerFlags).contains(expectedCppFlag)
+ } else {
+ assertThat(cSettings.compilerFlags).doesNotContain(expectedCFlag)
+ assertThat(cppSettings.compilerFlags).doesNotContain(expectedCppFlag)
+ }
+ }
+ assertThat(cSettings.compilerFlags).contains("-I" + project.file("src/main/headers"))
+ assertThat(cppSettings.compilerFlags).contains("-I" + project.file("src/main/headers"))
+ }
+ }
+ }
+
+ private void checkModel(
+ NativeAndroidProject model,
+ Set<String> buildTypes,
+ Set<String> productFlavors) {
+ Collection<NativeArtifact> artifacts = model.getArtifacts()
+
+ assertThat(model.getFileExtensions()).containsEntry("c", "c")
+ assertThat(model.getFileExtensions()).containsEntry("C", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("CPP", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("c++", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cc", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cp", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cpp", "c++")
+ assertThat(model.getFileExtensions()).containsEntry("cxx", "c++")
+
+ productFlavors = productFlavors.isEmpty() ? ImmutableSet.of("") : productFlavors
+ for(List<String> combo : Sets.cartesianProduct(buildTypes, productFlavors, ABIS)) {
+ String buildType = combo.get(0)
+ String flavor = combo.get(1)
+ String abi = combo.get(2);
+ String variant = flavor.isEmpty() ? buildType : flavor + buildType.capitalize()
+ String name = variant + '-' + abi;
+
+ assertThat(artifacts.collect { it.getName() }).contains(name)
+ NativeArtifact artifact = null;
+ for (NativeArtifact a : artifacts) {
+ if (a.getName().equals(name)) {
+ artifact = a;
+ break;
+ }
+ }
+
+ assertThat(artifact.groupName).isEqualTo(variant)
+ assertThat(artifact.outputFile).hasName("libhello-jni.so")
+ assertThat(artifact.sourceFiles).isEmpty()
+ assertThat(artifact.toolChain).endsWith(abi)
+
+ List<File> expectedSrc =
+ ["main", buildType, flavor, variant]
+ .findAll { !it.isEmpty() }
+ .unique()
+ .collect { project.file("src/" + it + "/jni") }
+
+ assertThat(artifact.sourceFolders.collect { it.getFolderPath() }).containsAllIn(expectedSrc)
+ assertThat(artifact.exportedHeaders).containsExactly(project.file("src/main/headers"))
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/UnitTestingComponentTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/UnitTestingComponentTest.groovy
new file mode 100644
index 0000000..6b07472
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/component/UnitTestingComponentTest.groovy
@@ -0,0 +1,104 @@
+/*
+ * 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.build.gradle.integration.component
+
+import com.android.build.gradle.integration.common.category.SmokeTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Unit tests for component plugin.
+ */
+ at Category(SmokeTests.class)
+class UnitTestingComponentTest {
+ static AndroidTestApp app = HelloWorldApp.noBuildFile();
+ static {
+ app.addFile(new TestSourceFile("src/test/java/com/android/tests", "UnitTest.java", """
+package com.android.tests;
+
+import static org.junit.Assert.*;
+
+import android.util.ArrayMap;
+import android.os.Debug;
+import org.junit.Test;
+
+public class UnitTest {
+ @Test
+ public void defaultValues() {
+ ArrayMap map = new ArrayMap();
+
+ // Check different return types.
+ map.clear();
+ assertEquals(0, map.size());
+ assertEquals(false, map.isEmpty());
+ assertNull(map.keySet());
+
+ // Check a static method as well.
+ assertEquals(0, Debug.getGlobalAllocCount());
+ }
+}
+"""))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(app)
+ .forExperimentalPlugin(true)
+ .withoutNdk()
+ .create();
+
+ @BeforeClass
+ public static void setUp() {
+ project.buildFile << """
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
+ compileSdkVersion $GradleTestProject.LAST_JAVA6_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ testOptions.unitTests.returnDefaultValues = true
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void testDebug() {
+ project.execute("clean", "testDebug")
+ assertThat(project.file("build/test-results/debug/TEST-com.android.tests.UnitTest.xml")).exists()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/databinding/DataBindingIncrementalTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/databinding/DataBindingIncrementalTest.java
new file mode 100644
index 0000000..38c978c
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/databinding/DataBindingIncrementalTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.build.gradle.integration.databinding;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.runner.FilterableParameterized;
+import com.android.build.gradle.integration.common.truth.DexClassSubject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+
+ at RunWith(FilterableParameterized.class)
+public class DataBindingIncrementalTest {
+
+ @Rule
+ public GradleTestProject project;
+
+ private static final String EXPORT_INFO_TASK = ":dataBindingExportBuildInfoDebug";
+
+ private static final String PROCESS_LAYOUTS_TASK = ":dataBindingProcessLayoutsDebug";
+
+ private static final String MAIN_ACTIVITY_BINDING_CLASS =
+ "Landroid/databinding/testapp/databinding/ActivityMainBinding;";
+
+ private static final String ACTIVITY_MAIN_XML = "src/main/res/layout/activity_main.xml";
+
+ private static final String ACTIVITY_MAIN_JAVA
+ = "src/main/java/android/databinding/testapp/MainActivity.java";
+
+ @Parameterized.Parameters(name = "experimental_{0}")
+ public static List<Boolean> parameters() {
+ return Arrays.asList(true, false);
+ }
+
+ public DataBindingIncrementalTest(boolean experimental) {
+ project = GradleTestProject.builder()
+ .fromTestProject("databindingIncremental",
+ experimental ? "forexperimental" : null)
+ .captureStdOut(true)
+ .forExperimentalPlugin(experimental)
+ .create();
+ }
+
+
+ @Before
+ public void setUp() {
+ project.getStdout().reset();
+ }
+
+ @Test
+ public void compileWithoutChange() throws UnsupportedEncodingException {
+ project.execute("assembleDebug");
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ assertUpToDate(EXPORT_INFO_TASK, true);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, true);
+ assertRecompile();
+ }
+
+ @Test
+ public void changeJavaCode() throws IOException {
+ project.execute("assembleDebug");
+ TestFileUtils.replaceLine(project.file(ACTIVITY_MAIN_JAVA), 44, "return false;");
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, true);
+ assertRecompile();
+ }
+
+ @Test
+ public void changeVariableName()
+ throws IOException, ProcessException, ParserConfigurationException, SAXException {
+ project.execute("assembleDebug");
+ TestFileUtils.replaceLine(project.file(ACTIVITY_MAIN_XML), 20,
+ "<variable name=\"foo2\" type=\"String\"/>");
+ TestFileUtils.replaceLine(project.file(ACTIVITY_MAIN_XML), 29,
+ "<TextView android:text='@{foo2 + \" \" + foo2}'");
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+
+ DexClassSubject bindingClass = assertThatApk(project.getApk("debug")).hasMainDexFile()
+ .that().hasClass(MAIN_ACTIVITY_BINDING_CLASS).that();
+ bindingClass.doesNotHaveMethod("setFoo");
+ bindingClass.hasMethod("setFoo2");
+ assertRecompile();
+ }
+
+ @Test
+ public void addVariable()
+ throws IOException, ProcessException, SAXException, ParserConfigurationException {
+ project.execute("assembleDebug");
+ TestFileUtils.replaceLine(project.file(ACTIVITY_MAIN_XML), 20,
+ "<variable name=\"foo\" type=\"String\"/><variable name=\"foo2\" type=\"String\"/>");
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+ assertThatApk(project.getApk("debug")).hasMainDexFile()
+ .that().hasClass(MAIN_ACTIVITY_BINDING_CLASS)
+ .that().hasMethods("setFoo", "setFoo2");
+ assertRecompile();
+ }
+
+ @Test
+ public void addIdToView()
+ throws IOException, ProcessException, SAXException, ParserConfigurationException {
+ project.execute("assembleDebug");
+ TestFileUtils.replaceLine(project.file(ACTIVITY_MAIN_XML), 30,
+ "android:id=\"@+id/myTextView\"");
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+
+ assertThatApk(project.getApk("debug")).hasMainDexFile()
+ .that().hasClass(MAIN_ACTIVITY_BINDING_CLASS)
+ .that().hasField("myTextView");
+
+ TestFileUtils.replaceLine(project.file(ACTIVITY_MAIN_XML), 30, "");
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+ assertThatApk(project.getApk("debug")).hasMainDexFile()
+ .that().hasClass(MAIN_ACTIVITY_BINDING_CLASS)
+ .that().doesNotHaveField("myTextView");
+ assertRecompile();
+ }
+
+ @Test
+ public void addNewLayout()
+ throws IOException, ProcessException, SAXException, ParserConfigurationException {
+ project.execute("assembleDebug");
+ project.getStdout().reset();
+ File mainActivity = new File(project.getTestDir(), ACTIVITY_MAIN_XML);
+ File activity2 = new File(mainActivity.getParentFile(), "activity2.xml");
+ Files.copy(mainActivity, activity2);
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+
+ assertThatApk(project.getApk("debug")).hasMainDexFile()
+ .that().hasClass("Landroid/databinding/testapp/databinding/Activity2Binding;")
+ .that().hasMethod("setFoo");
+ assertRecompile();
+ }
+
+ @Test
+ public void removeLayout() throws IOException, ProcessException {
+ File mainActivity = new File(project.getTestDir(), ACTIVITY_MAIN_XML);
+ File activity2 = new File(mainActivity.getParentFile(), "activity2.xml");
+ Files.copy(mainActivity, activity2);
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ assertThatApk(project.getApk("debug")).containsClass(
+ "Landroid/databinding/testapp/databinding/Activity2Binding;");
+ assertThat(activity2.delete()).isTrue();
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ assertThatApk(project.getApk("debug")).doesNotContainClass(
+ "Landroid/databinding/testapp/databinding/Activity2Binding;");
+ assertRecompile();
+ }
+
+ @Test
+ public void renameLayout() throws IOException, ProcessException {
+ String activity2ClassName = "Landroid/databinding/testapp/databinding/Activity2Binding;";
+ File mainActivity = new File(project.getTestDir(), ACTIVITY_MAIN_XML);
+ File activity2 = new File(mainActivity.getParentFile(), "activity2.xml");
+ Files.copy(mainActivity, activity2);
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+
+ assertThatApk(project.getApk("debug")).containsClass(
+ activity2ClassName);
+ TestFileUtils.replaceLine(project.file("src/main/res/layout/activity2.xml"), 19,
+ "<data class=\"MyCustomName\">");
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+
+ assertUpToDate(EXPORT_INFO_TASK, false);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, false);
+
+ assertThatApk(project.getApk("debug")).doesNotContainClass(
+ activity2ClassName);
+ assertThatApk(project.getApk("debug")).containsClass(
+ "Landroid/databinding/testapp/databinding/MyCustomName;");
+ assertRecompile();
+ }
+
+ private void assertRecompile() {
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+
+ assertUpToDate(EXPORT_INFO_TASK, true);
+ assertUpToDate(PROCESS_LAYOUTS_TASK, true);
+ }
+
+ private void assertUpToDate(String task, boolean isUpToDate) {
+ String line = task + " UP-TO-DATE";
+ if (isUpToDate) {
+ assertThat(project.getStdout().toString()).contains(line);
+ } else {
+ assertThat(project.getStdout().toString()).doesNotContain(line);
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/databinding/DataBindingTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/databinding/DataBindingTest.java
new file mode 100644
index 0000000..66d6c12
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/databinding/DataBindingTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.build.gradle.integration.databinding;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static org.junit.Assert.assertTrue;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.runner.FilterableParameterized;
+import com.android.build.gradle.integration.common.truth.AarSubject;
+import com.android.build.gradle.integration.common.truth.ApkSubject;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.base.Joiner;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+ at RunWith(FilterableParameterized.class)
+public class DataBindingTest {
+
+ @Parameterized.Parameters(name="library={0},forExperimentalPlugin={1},withoutAdapters={2}")
+ public static Collection<Object[]> getParameters() {
+ List<Object[]> options = new ArrayList<Object[]>();
+ for (int i = 0 ; i < 8; i ++) {
+ options.add(new Object[]{
+ (i & 1) != 0, (i & 2) != 0, (i & 4) != 0
+ });
+ }
+ return options;
+ }
+ private final boolean myWithoutAdapters;
+ private final boolean myLibrary;
+
+ public DataBindingTest(boolean library, boolean forExperimentalPlugin, boolean withoutAdapters) {
+ myWithoutAdapters = withoutAdapters;
+ myLibrary = library;
+
+ List<String> options = new ArrayList<String>();
+ if (library) {
+ options.add("library");
+ }
+ if (withoutAdapters) {
+ options.add("withoutadapters");
+ }
+ if (forExperimentalPlugin) {
+ options.add("forexperimental");
+ }
+ project = GradleTestProject.builder()
+ .fromTestProject("databinding", options.isEmpty() ? null : Joiner.on('-').join(options))
+ .captureStdOut(true)
+ .forExperimentalPlugin(forExperimentalPlugin)
+ .create();
+ }
+
+ @Rule
+ public final GradleTestProject project;
+
+ private String buildOutput;
+
+ @Before
+ public void setUp() {
+ project.getStdout().reset();
+ project.execute("assembleDebug");
+ buildOutput = project.getStdout().toString();
+ }
+
+ @Test
+ public void checkApkContainsDatabindingClasses() throws IOException, ProcessException {
+ assertTrue(buildOutput.contains(":dataBindingProcessLayoutsDebug"));
+ if (myLibrary) {
+ AarSubject aar = assertThatAar(project.getAar("debug"));
+ aar.doesNotContainClass("Landroid/g/testapp/databinding/ActivityMainBinding;");
+ aar.doesNotContainClass("Landroid/databinding/adapters/Converters;");
+ aar.doesNotContainClass("Landroid/databinding/DataBindingComponent;");
+ } else {
+ ApkSubject apk = assertThatApk(project.getApk("debug"));
+ apk.containsClass("Landroid/databinding/testapp/databinding/ActivityMainBinding;");
+ apk.containsClass("Landroid/databinding/DataBindingComponent;");
+ if (myWithoutAdapters) {
+ apk.doesNotContainClass("Landroid/databinding/adapters/Converters;");
+ } else {
+ apk.containsClass("Landroid/databinding/adapters/Converters;");
+ }
+ }
+
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithClassifierDepTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithClassifierDepTest.java
new file mode 100644
index 0000000..618b1ef
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithClassifierDepTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.android.builder.model.Variant;
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * test for same dependency with and without classifier.
+ */
+public class AppWithClassifierDepTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithClassifierDep")
+ .create();
+ AndroidProject model;
+
+ @Before
+ public void setUp() {
+ model = project.getSingleModel();
+ }
+
+ @After
+ public void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkDebugDepInModel() {
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+
+ Collection<JavaLibrary> javaLibs = dependencies.getJavaLibraries();
+ assertNotNull(javaLibs);
+ assertEquals(1, javaLibs.size());
+
+ JavaLibrary javaLib = javaLibs.iterator().next();
+ assertEquals(
+ new File(project.getTestDir(), "repo/com/foo/sample/1.0/sample-1.0.jar"),
+ javaLib.getJarFile());
+ }
+
+ @Test
+ public void checkAndroidTestDepInModel() {
+ Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(debugVariant).isNotNull();
+
+ AndroidArtifact androidTestArtifact = ModelHelper.getAndroidArtifact(
+ debugVariant.getExtraAndroidArtifacts(), ARTIFACT_ANDROID_TEST);
+ Truth.assertThat(androidTestArtifact).isNotNull();
+
+ Dependencies dependencies = androidTestArtifact.getDependencies();
+
+ Collection<JavaLibrary> javaLibs = dependencies.getJavaLibraries();
+ assertNotNull(javaLibs);
+ assertEquals(1, javaLibs.size());
+
+ JavaLibrary javaLib = javaLibs.iterator().next();
+ assertEquals(
+ new File(project.getTestDir(), "repo/com/foo/sample/1.0/sample-1.0-testlib.jar"),
+ javaLib.getJarFile());
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileDirectJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileDirectJarTest.java
new file mode 100644
index 0000000..d65c3fa
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileDirectJarTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for compile jar in app
+ */
+public class AppWithCompileDirectJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile project(':jar')\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkCompiledJarIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkCompiledJarIsInTheModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<String> projectDeps = deps.getProjects();
+
+ assertThat(projectDeps).named("project deps").hasSize(1);
+ }
+
+ @Test
+ public void checkPackageJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkPackageJarIsIntheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileIndirectJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileIndirectJarTest.java
new file mode 100644
index 0000000..0753177
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileIndirectJarTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for compile jar in app through an aar dependency
+ */
+public class AppWithCompileIndirectJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":library\")\n" +
+ "}\n");
+
+ appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":jar\")\n" +
+ "}\n");
+
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkCompiledJarIsPackaged() throws IOException, ProcessException {
+ File apk = project.getSubproject("app").getApk("debug");
+
+ assertThatApk(apk).containsClass("Lcom/example/android/multiproject/person/People;");
+ assertThatApk(apk).containsClass("Lcom/example/android/multiproject/library/PersonView;");
+ }
+
+ @Test
+ public void checkCompiledJarIsInTheModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<String> projectDeps = deps.getProjects();
+
+ TruthHelper.assertThat(projectDeps).named("project deps").hasSize(1);
+ }
+
+ @Test
+ public void checkPackageJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkPackageJarIsInTheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLibTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLibTest.java
new file mode 100644
index 0000000..8b18228
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLibTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for compile library in app
+ */
+public class AppWithCompileLibTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":library\")\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkCompiledLibraryIsPackaged() throws IOException, ProcessException {
+ File apk = project.getSubproject("app").getApk("debug");
+
+ assertThatApk(apk).containsClass("Lcom/example/android/multiproject/library/PersonView;");
+ }
+
+ @Test
+ public void checkCompiledLibraryIsInTheModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<AndroidLibrary> libraries = deps.getLibraries();
+ assertThat(libraries).hasSize(1);
+ assertThat(Iterables.getOnlyElement(libraries).getProject()).isEqualTo(":library");
+ }
+
+ @Test
+ public void checkCompiledLibraryIsInTheAndroidTestDependency() {
+ // TODO
+ }
+
+ @Test
+ public void checkCompiledLibraryIsInTheUnitTestDependency() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarFromOlderIdeTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarFromOlderIdeTest.java
new file mode 100644
index 0000000..4829b47
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarFromOlderIdeTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+
+import org.gradle.tooling.BuildException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * test for package (apk) local aar in app
+ */
+public class AppWithCompileLocalAarFromOlderIdeTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " compile files(\"libs/baseLib-1.0.aar\")\n" +
+ "}\n");
+
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ }
+
+ @Test(expected=BuildException.class)
+ public void checkModelFailedToLoad() {
+ project.getSingleModelAsStudio1();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarTest.java
new file mode 100644
index 0000000..517e3a0
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithCompileLocalAarTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * test for package (apk) local aar in app
+ */
+public class AppWithCompileLocalAarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " compile files(\"libs/baseLib-1.0.aar\")\n" +
+ "}\n");
+
+ model = project.getSingleModelIgnoringSyncIssues();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkModelFailedToLoad() {
+ SyncIssue issue = assertThat(model).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_NON_JAR_LOCAL_DEP);
+ assertThat(new File(issue.getData()).getName()).isEqualTo("baseLib-1.0.aar");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithIvyDependencyTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithIvyDependencyTest.java
new file mode 100644
index 0000000..dff9995
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithIvyDependencyTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+
+import org.junit.AfterClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import groovy.transform.CompileStatic;
+
+/**
+ * test for Ivy dependencies.
+ */
+ at CompileStatic
+public class AppWithIvyDependencyTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithIvyDependency")
+ .create();
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ }
+
+ @Test
+ public void checkCompilationDependingOnIvyJarFile() {
+ project.execute("assembleDebug");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithJarDependOnLibTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithJarDependOnLibTest.java
new file mode 100644
index 0000000..79425f4
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithJarDependOnLibTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for dependency on a jar with a dependency on a library
+ */
+public class AppWithJarDependOnLibTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":jar\")\n" +
+ "}\n");
+
+ TestFileUtils.appendToFile(project.getSubproject("jar").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":library\")\n" +
+ "}\n");
+ models = project.getAllModelsIgnoringSyncIssues();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkModelFailedToLoad() {
+ assertThat(models.get(":app")).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_JAR_DEPEND_ON_AAR,
+ "projectWithModules:jar:jar:unspecified");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithNonExistentResolutionStrategyForAarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithNonExistentResolutionStrategyForAarTest.java
new file mode 100644
index 0000000..560dcb8
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithNonExistentResolutionStrategyForAarTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for flavored dependency on a different package.
+ */
+public class AppWithNonExistentResolutionStrategyForAarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "\n" +
+ "subprojects {\n" +
+ " apply from: \"$rootDir/../commonLocalRepo.gradle\"\n" +
+ "}\n");
+ TestFileUtils.appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "\n" +
+ "dependencies {\n" +
+ " debugCompile project(\":library\")\n" +
+ " releaseCompile project(\":library\")\n" +
+ "}\n" +
+ "\n" +
+ "configurations { _debugCompile }\n" +
+ "\n" +
+ "configurations._debugCompile {\n" +
+ " resolutionStrategy {\n" +
+ " eachDependency { DependencyResolveDetails details ->\n" +
+ " if (details.requested.name == \"jdeferred-android-aar\") {\n" +
+ " details.useVersion \"-1.-1.-1\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}\n" +
+ "\n");
+
+ TestFileUtils.appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile \"org.jdeferred:jdeferred-android-aar:1.2.3\"\n" +
+ "}\n");
+
+ models = project.getAllModelsIgnoringSyncIssues();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkWeReceivedASyncIssue() {
+ SyncIssue issue = assertThat(models.get(":app")).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_UNRESOLVED_DEPENDENCY);
+ assertThat(issue.getMessage()).contains("org.jdeferred:jdeferred-android-aar:-1.-1.-1");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageDirectJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageDirectJarTest.java
new file mode 100644
index 0000000..d8589bf
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageDirectJarTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for package (apk) jar in app
+ */
+public class AppWithPackageDirectJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " apk project(\":jar\")\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkPackageJarIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkPackagedJarIsNotInTheModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ TruthHelper.assertThat(deps.getProjects()).named("Project deps").isEmpty();
+ }
+
+ @Test
+ public void checkPackageJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkPackageJarIsIntheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLibTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLibTest.java
new file mode 100644
index 0000000..a95b69f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLibTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for package library in app
+ */
+public class AppWithPackageLibTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " apk project(\":library\")\n" +
+ "}\n");
+ models = project.getAllModelsIgnoringSyncIssues();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkModelFailedToLoad() {
+ assertThat(models.get(":app")).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_NON_JAR_PACKAGE_DEP,
+ "projectWithModules:library:aar:unspecified");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalAarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalAarTest.java
new file mode 100644
index 0000000..5a58ef0
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalAarTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * test for package (apk) local aar in app
+ */
+public class AppWithPackageLocalAarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " apk files(\"libs/baseLib-1.0.aar\")\n" +
+ "}\n");
+
+ model = project.getSingleModelIgnoringSyncIssues();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkModelFailedToLoad() {
+ SyncIssue issue = assertThat(model).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_NON_JAR_LOCAL_DEP);
+ assertThat(new File(issue.getData()).getName()).isEqualTo("baseLib-1.0.aar");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalJarTest.java
new file mode 100644
index 0000000..e6bb405
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithPackageLocalJarTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * test for package (apk) local jar in app
+ */
+public class AppWithPackageLocalJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " apk files(\"libs/util-1.0.jar\")\n" +
+ "}\n");
+
+ model = project.executeAndReturnModel("clean", "assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkPackageLocalJarIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkPackagedLocalJarIsNotIntheModel() {
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<JavaLibrary> javaLibs = deps.getJavaLibraries();
+ TruthHelper.assertThat(javaLibs).named("java libs").isEmpty();
+ }
+
+ @Test
+ public void checkPackagedLocalJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkPackagedLocalJarIsIntheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedAarAsJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedAarAsJarTest.java
new file mode 100644
index 0000000..82e9d25
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedAarAsJarTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for provided jar in library where the jar comes from a library project.
+ */
+public class AppWithProvidedAarAsJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "\n" +
+ "dependencies {\n" +
+ " provided project(path: \":library\", configuration: \"fakeJar\")\n" +
+ "}\n");
+
+ appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "configurations {\n" +
+ " fakeJar\n" +
+ "}\n" +
+ "\n" +
+ "task makeFakeJar(type: Jar) {\n" +
+ " from \"src/main/java\"\n" +
+ "}\n" +
+ "\n" +
+ "artifacts {\n" +
+ " fakeJar makeFakeJar\n" +
+ "}\n");
+
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkProvidedJarIsNotPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("debug"))
+ .doesNotContainClass("Lcom/example/android/multiproject/library/PersonView;");
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheMainArtifactDependency() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ TruthHelper.assertThat(deps.getProjects()).containsExactly(":library");
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheAndroidTestDependency() {
+ // TODO
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheUnitTestDependency() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLibTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLibTest.java
new file mode 100644
index 0000000..f232a12
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLibTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.*;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for provided library in app
+ */
+public class AppWithProvidedLibTest {
+
+ @ClassRule
+ public static GradleTestProject project = builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " provided project(\":library\")\n" +
+ "}\n");
+ models = project.getAllModelsIgnoringSyncIssues();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkModelFailedToLoad() {
+ assertThat(models.get(":app")).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_NON_JAR_PROVIDED_DEP,
+ "projectWithModules:library:aar:unspecified");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalAarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalAarTest.java
new file mode 100644
index 0000000..ea00cef
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalAarTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * test for provided (apk) local aar in app
+ */
+public class AppWithProvidedLocalAarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " provided files(\"libs/baseLib-1.0.aar\")\n" +
+ "}\n");
+
+ model = project.getSingleModelIgnoringSyncIssues();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkModelFailedToLoad() {
+ SyncIssue issue = assertThat(model).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_NON_JAR_LOCAL_DEP);
+ assertThat(new File(issue.getData()).getName()).isEqualTo("baseLib-1.0.aar");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalJarTest.java
new file mode 100644
index 0000000..5b01255
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedLocalJarTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * test for provided local jar in app
+ */
+public class AppWithProvidedLocalJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " provided files(\"libs/util-1.0.jar\")\n" +
+ "}\n");
+
+ model = project.executeAndReturnModel("clean", "assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsNotPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getApk("debug"))
+ .doesNotContainClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsInTheMainArtifactDependency() {
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<JavaLibrary> javaLibs = deps.getJavaLibraries();
+
+ assertThat(javaLibs).named("Java libs").hasSize(1);
+ assertThat(Iterables.getOnlyElement(javaLibs).isProvided())
+ .named("single java lib provided property")
+ .isTrue();
+
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsIntheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedProjectJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedProjectJarTest.java
new file mode 100644
index 0000000..643e9e5
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedProjectJarTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for provided java submodule in app
+ */
+public class AppWithProvidedProjectJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " provided project(\":jar\")\n" +
+ "}\n");
+
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkProvidedJarIsNotPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("debug"))
+ .doesNotContainClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheMainArtifactDependency() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ assertThat(deps.getProjects()).named("project deps").containsExactly(":jar");
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkProvidedJarIsIntheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedRemoteJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedRemoteJarTest.java
new file mode 100644
index 0000000..9def213
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithProvidedRemoteJarTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * test for provided local jar in app
+ */
+public class AppWithProvidedRemoteJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "repositories {\n" +
+ " maven { url System.env.CUSTOM_REPO }\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " provided \"com.google.guava:guava:17.0\"\n" +
+ "}\n");
+
+ model = project.executeAndReturnModel("clean", "assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkProvidedRemoteJarIsNotPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getApk("debug"))
+ .doesNotContainClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkProvidedRemoteJarIsInTheMainArtifactDependency() {
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<JavaLibrary> javaLibs = deps.getJavaLibraries();
+
+ assertThat(javaLibs).named("java libs").hasSize(1);
+ JavaLibrary onlyElement = Iterables.getOnlyElement(javaLibs);
+ assertThat(onlyElement.isProvided()).named("lib provided prop").isTrue();
+ assertThat(onlyElement.getResolvedCoordinates().toString())
+ .isEqualTo("com.google.guava:guava:jar:17.0");
+ }
+
+ @Test
+ public void checkProvidedRemoteJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkProvidedRemoteJarIsIntheUnitTestDeps() {
+ // TODO
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForAarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForAarTest.java
new file mode 100644
index 0000000..b9482fe
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForAarTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * test for flavored dependency on a different package.
+ */
+public class AppWithResolutionStrategyForAarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "subprojects {\n" +
+ " apply from: \"$rootDir/../commonLocalRepo.gradle\"\n" +
+ "}\n");
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "\n" +
+ "dependencies {\n" +
+ " debugCompile project(\":library\")\n" +
+ " releaseCompile project(\":library\")\n" +
+ "}\n" +
+ "\n" +
+ "configurations { _debugCompile }\n" +
+ "\n" +
+ "configurations._debugCompile {\n" +
+ " resolutionStrategy {\n" +
+ " eachDependency { DependencyResolveDetails details ->\n" +
+ " if (details.requested.name == \"jdeferred-android-aar\") {\n" +
+ " details.useVersion \"1.2.2\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}\n" +
+ "\n");
+
+ TestFileUtils.appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile \"org.jdeferred:jdeferred-android-aar:1.2.3\"\n" +
+ "}\n");
+
+ models = project.getAllModels();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkModelContainsCorrectDependencies() {
+ AndroidProject appProject = models.get(":app");
+ Collection<Variant> appVariants = appProject.getVariants();
+
+ checkJarDependency(appVariants, "debug", "org.jdeferred:jdeferred-android-aar:aar:1.2.2");
+ checkJarDependency(appVariants, "release", "org.jdeferred:jdeferred-android-aar:aar:1.2.3");
+ }
+
+ private static void checkJarDependency(
+ @NonNull Collection<Variant> appVariants,
+ @NonNull String variantName,
+ @NonNull String aarCoodinate) {
+ Variant appVariant = ModelHelper.getVariant(appVariants, variantName);
+ Truth.assertThat(appVariant).isNotNull();
+
+ AndroidArtifact appArtifact = appVariant.getMainArtifact();
+ Dependencies artifactDependencies = appArtifact.getDependencies();
+
+ Collection<AndroidLibrary> directLibraries = artifactDependencies.getLibraries();
+ assertThat(directLibraries)
+ .named("direct libs of " + variantName)
+ .hasSize(1);
+ AndroidLibrary directLibrary = Iterables.getOnlyElement(directLibraries);
+ assertThat(directLibrary.getProject())
+ .named("Single lib name of " + variantName)
+ .isEqualTo(":library");
+
+ List<? extends AndroidLibrary> transitiveLibraries = directLibrary.getLibraryDependencies();
+ assertThat(transitiveLibraries)
+ .named("transitive libs of single direct lib of " + variantName)
+ .hasSize(1);
+
+ AndroidLibrary transitiveLibrary = Iterables.getOnlyElement(transitiveLibraries);
+ assertThat(transitiveLibrary.getResolvedCoordinates().toString())
+ .named("coord of single transitive deps of " + variantName)
+ .isEqualTo(aarCoodinate);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForJarTest.java
new file mode 100644
index 0000000..984203a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/AppWithResolutionStrategyForJarTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaLibrary;
+import com.android.builder.model.Variant;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for flavored dependency on a different package.
+ */
+public class AppWithResolutionStrategyForJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "subprojects {\n" +
+ " apply from: \"$rootDir/../commonLocalRepo.gradle\"\n" +
+ "}\n");
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "\n" +
+ "dependencies {\n" +
+ " debugCompile project(\":library\")\n" +
+ " releaseCompile project(\":library\")\n" +
+ "}\n" +
+ "\n" +
+ "configurations { _debugCompile }\n" +
+ "\n" +
+ "configurations._debugCompile {\n" +
+ " resolutionStrategy {\n" +
+ " eachDependency { DependencyResolveDetails details ->\n" +
+ " if (details.requested.name == \"guava\") {\n" +
+ " details.useVersion \"15.0\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}\n" +
+ "\n");
+
+ TestFileUtils.appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile \"com.google.guava:guava:17.0\"\n" +
+ "}\n");
+
+ models = project.getAllModels();
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkModelContainsCorrectDependencies() {
+ AndroidProject appProject = models.get(":app");
+ Collection<Variant> appVariants = appProject.getVariants();
+
+ checkJarDependency(appVariants, "debug", "com.google.guava:guava:jar:15.0");
+ checkJarDependency(appVariants, "release", "com.google.guava:guava:jar:17.0");
+ }
+
+ private static void checkJarDependency(
+ @NonNull Collection<Variant> appVariants,
+ @NonNull String variantName,
+ @NonNull String jarCoodinate) {
+ Variant appVariant = ModelHelper.getVariant(appVariants, variantName);
+ Truth.assertThat(appVariant).isNotNull();
+
+ AndroidArtifact appArtifact = appVariant.getMainArtifact();
+ Dependencies artifactDependencies = appArtifact.getDependencies();
+
+ Collection<JavaLibrary> javaLibraries = artifactDependencies.getJavaLibraries();
+ assertThat(javaLibraries)
+ .named("java libs of " + variantName)
+ .hasSize(1);
+
+ JavaLibrary javaLibrary = Iterables.getOnlyElement(javaLibraries);
+ assertThat(javaLibrary.getResolvedCoordinates().toString())
+ .named("single java lib deps of " + variantName)
+ .isEqualTo(jarCoodinate);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/DepOnLocalJarThroughAModuleTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/DepOnLocalJarThroughAModuleTest.java
new file mode 100644
index 0000000..805771f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/DepOnLocalJarThroughAModuleTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * test for a dependency on a local jar through a module wrapper
+ */
+public class DepOnLocalJarThroughAModuleTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":localJarAsModule\")\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkJarIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("debug", "unaligned"))
+ .containsClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkJarModuleIsInTheTestArtifactModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ TruthHelper.assertThat(deps.getProjects()).containsExactly(":localJarAsModule");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithCompileLocalJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithCompileLocalJarTest.java
new file mode 100644
index 0000000..869a8bd
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithCompileLocalJarTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject;
+import com.android.builder.model.AndroidProject;
+import com.android.ide.common.process.ProcessException;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * test for compile local jar in libs
+ */
+public class LibWithCompileLocalJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.library\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " +
+ String.valueOf(GradleTestProject.DEFAULT_COMPILE_SDK_VERSION) +
+ "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " compile files(\"libs/util-1.0.jar\")\n" +
+ "}\n");
+
+ model = project.executeAndReturnModel("clean", "assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkCompileLocalJarIsPackaged() throws IOException, ProcessException {
+ // search in secondary jars only.
+ assertThatAar(project.getAar("debug")).containsClass(
+ "Lcom/example/android/multiproject/person/People;",
+ AbstractAndroidSubject.ClassFileScope.SECONDARY);
+ }
+
+ @Test
+ public void testLibraryTestContainsLocalJarClasses() throws IOException, ProcessException {
+ project.execute("assembleDebugAndroidTest");
+
+ assertThatApk(project.getTestApk("debug")).containsClass(
+ "Lcom/example/android/multiproject/person/People;");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithPackageLocalJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithPackageLocalJarTest.java
new file mode 100644
index 0000000..f71b9d7
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithPackageLocalJarTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * test for package (publish) local jar in libs
+ */
+public class LibWithPackageLocalJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.library\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " publish files(\"libs/util-1.0.jar\")\n" +
+ "}\n");
+
+ model = project.executeAndReturnModel("clean", "assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkPackagedLocalJarIsPackaged() throws IOException, ProcessException {
+ // search in secondary jars only.
+ assertThatAar(project.getAar("debug")).containsClass(
+ "Lcom/example/android/multiproject/person/People;",
+ AbstractAndroidSubject.ClassFileScope.SECONDARY);
+ }
+
+ @Test
+ public void checkPackagedLocalJarIsNotInTheModel() {
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ assertThat(deps.getJavaLibraries()).named("java libs").isEmpty();
+ }
+
+ @Test
+ public void checkPackagedLocalJarIsNotInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkPackagedLocalJarIsNotInTheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedAarAsJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedAarAsJarTest.java
new file mode 100644
index 0000000..d27b52a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedAarAsJarTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for provided jar in library where the jar comes from a library project.
+ */
+public class LibWithProvidedAarAsJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " provided project(path: \":library2\", configuration: \"fakeJar\")\n" +
+ "}\n");
+
+ appendToFile(project.getSubproject("library2").getBuildFile(),
+ "\n" +
+ "configurations {\n" +
+ " fakeJar\n" +
+ "}\n" +
+ "\n" +
+ "task makeFakeJar(type: Jar) {\n" +
+ " from \"src/main/java\"\n" +
+ "}\n" +
+ "\n" +
+ "artifacts {\n" +
+ " fakeJar makeFakeJar\n" +
+ "}\n");
+
+ models = project.executeAndReturnMultiModel("clean", ":library:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkProjectJarIsNotPackaged() throws IOException, ProcessException {
+ assertThatAar(project.getSubproject("library").getAar("debug"))
+ .doesNotContainClass("Lcom/example/android/multiproject/library2/PersionView2;");
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheMainArtifactDependency() {
+ Variant variant = ModelHelper.getVariant(models.get(":library").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<String> projectDeps = deps.getProjects();
+ TruthHelper.assertThat(projectDeps).containsExactly(":library2");
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedDirectJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedDirectJarTest.java
new file mode 100644
index 0000000..21c19c6
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedDirectJarTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for provided jar in library
+ */
+public class LibWithProvidedDirectJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":library\")\n" +
+ "}\n");
+
+ appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " provided project(\":jar\")\n" +
+ "}\n");
+
+ models = project.executeAndReturnMultiModel("clean", ":library:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkProvidedJarIsNotPackaged() throws IOException, ProcessException {
+ assertThatAar(project.getSubproject("library").getAar("debug"))
+ .doesNotContainClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkProvidedJarIsIntheMainArtifactDeps() {
+ Variant variant = ModelHelper.getVariant(models.get(":library").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ TruthHelper.assertThat(deps.getProjects()).containsExactly(":jar");
+ }
+
+ @Test
+ public void checkProvidedJarIsNotInThePublishedDeps() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ Collection<AndroidLibrary> libraries = deps.getLibraries();
+ assertThat(libraries).hasSize(1);
+
+ AndroidLibrary androidLibrary = Iterables.getOnlyElement(libraries);
+
+ // TODO: check that this library does not have a jar dependency since it's provided.
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheAndroidTestDependency() {
+ // TODO
+ }
+
+ @Test
+ public void checkProvidedJarIsInTheUnitTestDependency() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedLocalJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedLocalJarTest.java
new file mode 100644
index 0000000..21d19c7
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LibWithProvidedLocalJarTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+/**
+ * test for provided local jar in libs
+ */
+public class LibWithProvidedLocalJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithLocalDeps")
+ .create();
+ static AndroidProject model;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.library\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " provided files(\"libs/util-1.0.jar\")\n" +
+ "}\n");
+
+ model = project.executeAndReturnModel("clean", "assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsNotPackaged() throws IOException {
+ assertThatZip(project.getAar("debug")).doesNotContain("libs/util-1.0.jar");
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsInTheMainArtifactDependency() {
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies deps = variant.getMainArtifact().getDependencies();
+ assertThat(deps.getJavaLibraries()).hasSize(1);
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsInTheAndroidTestDeps() {
+ // TODO
+ }
+
+ @Test
+ public void checkProvidedLocalJarIsInTheUnitTestDeps() {
+ // TODO
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LocalJarInAarInModelTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LocalJarInAarInModelTest.java
new file mode 100644
index 0000000..ff807ef
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/LocalJarInAarInModelTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * test for the path of the local jars in aars before and after exploding them.
+ */
+public class LocalJarInAarInModelTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create();
+
+ @Before
+ public void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "\n" +
+ " defaultConfig {\n" +
+ " minSdkVersion 4\n" +
+ " }\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " compile \"com.android.support:support-v4:22.1.1\"\n" +
+ "}\n");
+ }
+
+ @After
+ public void cleanUp() {
+ project = null;
+ }
+
+ @Test
+ public void checkModelBeforeBuild() {
+ //clean the project and get the model. The aar won"t be exploded for this sync event.
+ AndroidProject model = project.executeAndReturnModel("clean");
+
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+ Collection<AndroidLibrary> libraries = dependencies.getLibraries();
+
+ assertThat(libraries).hasSize(1);
+
+ // now build the project.
+ project.execute("prepareDebugDependencies");
+
+ // now check the model validity
+ AndroidLibrary lib = libraries.iterator().next();
+ assertThat(lib.getJarFile()).isFile();
+ for (File localJar : lib.getLocalJars()) {
+ assertThat(localJar).isFile();
+ }
+ }
+
+ @Test
+ public void checkModelAfterBuild() {
+ //build the project and get the model. The aar is exploded for this sync event.
+ AndroidProject model = project.executeAndReturnModel("clean", "prepareDebugDependencies");
+
+ Variant variant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+ Collection<AndroidLibrary> libraries = dependencies.getLibraries();
+
+ assertThat(libraries).hasSize(1);
+
+ // now check the model validity
+ AndroidLibrary lib = libraries.iterator().next();
+ assertThat(lib.getJarFile()).isFile();
+ for (File localJar : lib.getLocalJars()) {
+ assertThat(localJar).isFile();
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/OptionalAarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/OptionalAarTest.java
new file mode 100644
index 0000000..d2c5644
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/OptionalAarTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for optional aar (using the provided scope)
+ */
+public class OptionalAarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "\n" +
+ "dependencies {\n" +
+ " compile project(\":library\")\n" +
+ "}\n");
+ appendToFile(project.getSubproject("library").getBuildFile(),
+ "\n" +
+ "\n" +
+ "dependencies {\n" +
+ " provided project(\":library2\")\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebug");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkAppDoesNotContainProvidedLibsLayout() throws IOException, ProcessException {
+ File apk = project.getSubproject("app").getApk("debug");
+
+ assertThatApk(apk).doesNotContainResource("layout/lib2layout.xml");
+ }
+
+ @Test
+ public void checkAppDoesNotContainProvidedLibsCode() throws IOException, ProcessException {
+ File apk = project.getSubproject("app").getApk("debug");
+
+ assertThatApk(apk).doesNotContainClass("Lcom/example/android/multiproject/library2/PersonView2;");
+ }
+
+ @Test
+ public void checkLIbDoesNotContainProvidedLibsLayout() throws IOException, ProcessException {
+ File aar = project.getSubproject("library").getAar("release");
+
+ assertThatAar(aar).doesNotContainResource("layout/lib2layout.xml");
+ assertThatAar(aar).textSymbolFile().contains("int layout liblayout");
+ assertThatAar(aar).textSymbolFile().doesNotContain("int layout lib2layout");
+ }
+
+ @Test
+ public void checkAppModelDoesNotIncludeOptionalLibrary() {
+ Collection<Variant> variants = models.get(":app").getVariants();
+
+ // get the main artifact of the debug artifact and its dependencies
+ Variant variant = ModelHelper.getVariant(variants, "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ AndroidArtifact artifact = variant.getMainArtifact();
+ Dependencies dependencies = artifact.getDependencies();
+ Collection<AndroidLibrary> libs = dependencies.getLibraries();
+
+ assertThat(libs).hasSize(1);
+
+ AndroidLibrary library = Iterables.getOnlyElement(libs);
+ assertThat(library.getProject()).isEqualTo(":library");
+ assertThat(library.isOptional()).isFalse();
+ }
+
+ @Test
+ public void checkLibraryModelIncludesOptionalLibrary() {
+ Collection<Variant> variants = models.get(":library").getVariants();
+
+ // get the main artifact of the debug artifact and its dependencies
+ Variant variant = ModelHelper.getVariant(variants, "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ AndroidArtifact artifact = variant.getMainArtifact();
+ Dependencies dependencies = artifact.getDependencies();
+ Collection<AndroidLibrary> libs = dependencies.getLibraries();
+
+ assertThat(libs).hasSize(1);
+
+ AndroidLibrary library = Iterables.getOnlyElement(libs);
+ assertThat(library.getProject()).isEqualTo(":library2");
+ assertThat(library.isOptional()).isTrue();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestLibraryWithDep.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestLibraryWithDep.java
new file mode 100644
index 0000000..dac97e2
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestLibraryWithDep.java
@@ -0,0 +1,48 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.ide.common.process.ProcessException;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class TestLibraryWithDep {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("libTestDep")
+ .create();
+
+ @BeforeClass
+ public static void setUp() {
+ project.executeAndReturnMultiModel("clean", "assembleDebugAndroidTest");
+ }
+
+ @Test
+ public void checkLibDependencyJarIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getApk("debug", "androidTest", "unaligned"))
+ .containsClass("Lcom/google/common/base/Splitter;");
+ }
+}
+
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileDirectJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileDirectJarTest.java
new file mode 100644
index 0000000..5baa444
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileDirectJarTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static com.android.build.gradle.integration.common.utils.ModelHelper.getAndroidArtifact;
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for compile jar in a test app
+ */
+
+public class TestWithCompileDirectJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " androidTestCompile project(\":jar\")\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebugAndroidTest");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkCompiledJarIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("debug", "androidTest", "unaligned"))
+ .containsClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkCompiledJarIsInTheTestArtifactModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Collection<AndroidArtifact> androidArtifacts = variant.getExtraAndroidArtifacts();
+ AndroidArtifact testArtifact = getAndroidArtifact(androidArtifacts, ARTIFACT_ANDROID_TEST);
+ assertNotNull(testArtifact);
+
+ Dependencies deps = testArtifact.getDependencies();
+ TruthHelper.assertThat(deps.getProjects()).hasSize(1);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileLibTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileLibTest.java
new file mode 100644
index 0000000..ebc21d7
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithCompileLibTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static com.android.build.gradle.integration.common.utils.ModelHelper.getAndroidArtifact;
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for compile library in a test app
+ */
+public class TestWithCompileLibTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " androidTestCompile project(\":library\")\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleDebugAndroidTest");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkCompiledLibraryIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("debug", "androidTest", "unaligned"))
+ .containsClass("Lcom/example/android/multiproject/library/PersonView;");
+ }
+
+ @Test
+ public void checkCompiledLibraryIsInTheTestArtifactModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "debug");
+ Truth.assertThat(variant).isNotNull();
+
+ Collection<AndroidArtifact> androidArtifacts = variant.getExtraAndroidArtifacts();
+ AndroidArtifact testArtifact = getAndroidArtifact(androidArtifacts, ARTIFACT_ANDROID_TEST);
+ assertNotNull(testArtifact);
+
+ Dependencies deps = testArtifact.getDependencies();
+ TruthHelper.assertThat(deps.getLibraries()).hasSize(1);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithFlavorsWithCompileDirectJarTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithFlavorsWithCompileDirectJarTest.java
new file mode 100644
index 0000000..4f69c12
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithFlavorsWithCompileDirectJarTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static com.android.build.gradle.integration.common.utils.ModelHelper.getAndroidArtifact;
+import static com.android.builder.model.AndroidProject.ARTIFACT_ANDROID_TEST;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.truth.Truth;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * test for compile jar in a test app
+ */
+public class TestWithFlavorsWithCompileDirectJarTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create();
+ static Map<String, AndroidProject> models;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getSubproject("app").getBuildFile(),
+ "\n" +
+ "android {\n" +
+ " productFlavors {\n" +
+ " pro { }\n" +
+ " free { }\n" +
+ " }\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " androidTestCompile project(\":jar\")\n" +
+ "}\n");
+ models = project.executeAndReturnMultiModel("clean", ":app:assembleFreeDebugAndroidTest");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ models = null;
+ }
+
+ @Test
+ public void checkCompiledJarIsPackaged() throws IOException, ProcessException {
+ assertThatApk(project.getSubproject("app").getApk("free", "debug", "androidTest", "unaligned"))
+ .containsClass("Lcom/example/android/multiproject/person/People;");
+ }
+
+ @Test
+ public void checkCompiledJarIsInTheTestArtifactModel() {
+ Variant variant = ModelHelper.getVariant(models.get(":app").getVariants(), "freeDebug");
+ Truth.assertThat(variant).isNotNull();
+
+ Collection<AndroidArtifact> androidArtifacts = variant.getExtraAndroidArtifacts();
+ AndroidArtifact testArtifact = getAndroidArtifact(androidArtifacts, ARTIFACT_ANDROID_TEST);
+ assertNotNull(testArtifact);
+
+ Dependencies deps = testArtifact.getDependencies();
+ assertThat(deps.getProjects()).containsExactly(":jar");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithMismatchDep.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithMismatchDep.java
new file mode 100644
index 0000000..40c57e2
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithMismatchDep.java
@@ -0,0 +1,110 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SyncIssue;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+/**
+ * Tests the handling of test dependencies.
+ */
+public class TestWithMismatchDep {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("testDependency")
+ .captureStdOut(true)
+ .captureStdErr(true)
+ .create();
+
+ @Before
+ public void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " androidTestCompile 'com.google.guava:guava:15.0'\n" +
+ "}\n");
+ }
+
+ private static final String ERROR_MSG = "Conflict with dependency \'com.google.guava:guava\'." +
+ " Resolved versions for app (17.0) and test app (15.0) differ." +
+ " See http://g.co/androidstudio/app-test-app-conflict for details.";
+
+ @Test
+ public void testMismatchDependencyErrorIsInTheModel() {
+ // Query the model to get the mismatch dep sync error.
+ AndroidProject model = project.getSingleModelIgnoringSyncIssues();
+
+ assertThat(model).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_MISMATCH_DEP,
+ "com.google.guava:guava",
+ ERROR_MSG);
+ }
+
+ @Test
+ public void testMismatchDependencyBreaksTestBuild() {
+ // want to check the log, so can't use Junit's expected exception mechanism.
+
+ try {
+ project.execute("assembleAndroidTest");
+ fail("build succeeded");
+ } catch (Exception e) {
+ Throwable t = e;
+ while (t.getCause() != null) {
+ t = t.getCause();
+ }
+
+ // looks like we can't actually test the instance t against GradleException
+ // due to it coming through the tooling API from a different class loader.
+ assertThat(t.getClass().getCanonicalName()).isEqualTo("org.gradle.api.GradleException");
+ assertThat(t.getMessage()).isEqualTo("Dependency Error. See console for details.");
+ }
+
+ // check there is a version of the error, after the task name:
+ assertThat(project.getStderr().toString()).named("stderr").contains(ERROR_MSG);
+
+ }
+
+ @Test
+ public void testMismatchDependencyDoesNotBreakDebugBuild() {
+ project.execute("assembleDebug");
+
+ // check there is a log output
+ assertThat(project.getStdout().toString()).named("stdout").contains(ERROR_MSG);
+ }
+
+ @Test
+ public void testMismatchDependencyCanRunNonBuildTasks() {
+ // it's important to be able to run the dependencies task to
+ // investigate dependency issues.
+ project.execute("dependencies");
+
+ // check there is a log output
+ assertThat(project.getStdout().toString()).named("stdout").contains(ERROR_MSG);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsApp.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsApp.java
new file mode 100644
index 0000000..1cf498c
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsApp.java
@@ -0,0 +1,174 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.integration.common.category.DeviceTests;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.runner.FilterableParameterized;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.ide.common.process.ProcessException;
+import com.google.common.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Tests the handling of test dependency.
+ */
+ at RunWith(FilterableParameterized.class)
+public class TestWithSameDepAsApp {
+
+ @Rule
+ public GradleTestProject project;
+
+ public String plugin;
+ public String appDependency;
+ public String testDependency;
+ public String className;
+ public String appUsage;
+ public String testUsage;
+
+ @Parameterized.Parameters(name = "{0}: {1}, {2}")
+ public static Collection<Object[]> data() {
+ List<String> plugins = Lists.newArrayList("com.android.application", "com.android.library");
+ List<Object[]> parameters = Lists.newArrayList();
+
+ for (String plugin : plugins) {
+ // Check two JARs.
+ parameters.add(Lists.newArrayList(
+ plugin,
+ "org.hamcrest:hamcrest-core:1.3",
+ "org.hamcrest:hamcrest-core:1.3",
+ "Lorg/hamcrest/Matcher;",
+ "org.hamcrest.Matcher<String> m = org.hamcrest.CoreMatchers.is(\"foo\");",
+ "org.hamcrest.Matcher<String> m = org.hamcrest.CoreMatchers.is(\"foo\");").toArray());
+
+ // Check two JARs, indirect conflict.
+ parameters.add(Lists.newArrayList(
+ plugin,
+ "org.hamcrest:hamcrest-core:1.3",
+ "junit:junit:4.12",
+ "Lorg/hamcrest/Matcher;",
+ "org.hamcrest.Matcher<String> m = org.hamcrest.CoreMatchers.is(\"foo\");",
+ "org.hamcrest.Matcher<String> m = org.hamcrest.CoreMatchers.is(\"foo\");").toArray());
+
+ // Check two AARs.
+ parameters.add(Lists.newArrayList(
+ plugin,
+ "com.android.support:support-v4:23.0.1",
+ "com.android.support:support-v4:23.0.1",
+ "Landroid/support/v4/widget/Space;",
+ "new android.support.v4.widget.Space(this);",
+ "new android.support.v4.widget.Space(getActivity());").toArray());
+
+ // Check two AARs, indirect conflict.
+ parameters.add(Lists.newArrayList(
+ plugin,
+ "com.android.support:support-v4:23.0.1",
+ "com.android.support:recyclerview-v7:23.0.1",
+ "Landroid/support/v4/widget/Space;",
+ "new android.support.v4.widget.Space(this);",
+ "new android.support.v4.widget.Space(getActivity());").toArray());
+ }
+
+ return parameters;
+ }
+
+ public TestWithSameDepAsApp(
+ String plugin,
+ String appDependency,
+ String testDependency,
+ String className,
+ String appUsage,
+ String testUsage) throws IOException, ProcessException {
+ this.plugin = plugin;
+ this.appDependency = appDependency;
+ this.testDependency = testDependency;
+ this.className = className;
+ this.appUsage = appUsage;
+ this.testUsage = testUsage;
+
+ this.project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin(plugin))
+ .create();
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "dependencies {\n" +
+ " compile \"" + this.appDependency + "\"\n" +
+ " androidTestCompile \"" + this.testDependency + "\"\n" +
+ "}\n" +
+ "\n" +
+ "android.defaultConfig.minSdkVersion 16\n");
+
+ TestFileUtils.addMethod(
+ project.file("src/main/java/com/example/helloworld/HelloWorld.java"),
+ "\n" +
+ "public void useDependency() {\n" +
+ " " + this.appUsage + "\n" +
+ "}\n" +
+ ""
+ );
+
+ TestFileUtils.addMethod(
+ project.file("src/androidTest/java/com/example/helloworld/HelloWorldTest.java"),
+ "\n" +
+ "public void testDependency() {\n" +
+ " " + this.testUsage + "\n" +
+ "}\n" +
+ ""
+ );
+ }
+
+ @Test
+ public void testWithSamedepVersionThanTestedDoesNotEmbedDependency()
+ throws IOException, ProcessException {
+ project.execute("assembleDebug", "assembleDebugAndroidTest");
+
+ if (plugin.contains("application")) {
+ assertThatApk(project.getApk("debug")).containsClass(this.className);
+ assertThatApk(project.getTestApk("debug")).doesNotContainClass(this.className);
+ } else {
+ // External dependencies are not packaged in AARs.
+ assertThatAar(project.getAar("debug")).doesNotContainClass(this.className);
+ // But should be in the test APK.
+ assertThatApk(project.getTestApk("debug")).containsClass(this.className);
+ }
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void runTestsOnDevices() {
+ project.executeConnectedCheck();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsAppWithProguard.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsAppWithProguard.java
new file mode 100644
index 0000000..1a49d27
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/TestWithSameDepAsAppWithProguard.java
@@ -0,0 +1,81 @@
+/*
+ * 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.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Tests the handling of test dependency.
+ */
+public class TestWithSameDepAsAppWithProguard {
+
+ private static AndroidTestApp testApp = HelloWorldApp.noBuildFile();
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(testApp)
+ .create();
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + String.valueOf(GradleTestProject.DEFAULT_COMPILE_SDK_VERSION) + "\n" +
+ " buildToolsVersion \"" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "\n" +
+ " defaultConfig {\n" +
+ " minSdkVersion 21\n" +
+ " }\n" +
+ "\n" +
+ " buildTypes {\n" +
+ " debug {\n" +
+ " minifyEnabled true\n" +
+ " proguardFiles getDefaultProguardFile(\"proguard-android.txt\")\n" +
+ " }\n" +
+ " }\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " compile \"com.android.tools:annotations:+\"\n" +
+ " androidTestCompile \"com.android.tools:annotations:+\"\n" +
+ "}\n");
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ }
+
+ @Test
+ public void testProguardOnTestVariantSucceeds() {
+ project.execute("clean", "assembleDebugAndroidTest");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/VariantDependencyTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/VariantDependencyTest.java
new file mode 100644
index 0000000..f73f90d
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dependencies/VariantDependencyTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dependencies;
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.DEFAULT_BUILD_TOOL_VERSION;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip;
+import static org.junit.Assert.assertTrue;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.utils.ModelHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.builder.core.ApkInfoParser;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.DefaultProcessExecutor;
+import com.android.ide.common.process.ProcessExecutor;
+import com.android.utils.FileUtils;
+import com.android.utils.StdLogger;
+import com.google.common.collect.Sets;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+
+public class VariantDependencyTest {
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create();
+
+ private static AndroidProject model;
+ private static ApkInfoParser apkInfoParser;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "\n" +
+ "apply plugin: \"com.android.application\"\n" +
+ "\n" +
+ "configurations {\n" +
+ " freeLollipopDebugCompile\n" +
+ " paidIcsCompile\n" +
+ "}\n" +
+ "\n" +
+ "android {\n" +
+ " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION + "\n" +
+ " buildToolsVersion \"" + DEFAULT_BUILD_TOOL_VERSION + "\"\n" +
+ "\n" +
+ " flavorDimensions \"model\", \"api\"\n" +
+ " productFlavors {\n" +
+ " Lollipop {\n" +
+ " dimension \"api\"\n" +
+ " minSdkVersion 21\n" +
+ " }\n" +
+ " ics {\n" +
+ " dimension \"api\"\n" +
+ " minSdkVersion 15\n" +
+ " }\n" +
+ " free {\n" +
+ " dimension \"model\"\n" +
+ " }\n" +
+ " paid {\n" +
+ " dimension \"model\"\n" +
+ " }\n" +
+ " }\n" +
+ "}\n" +
+ "\n" +
+ "dependencies {\n" +
+ " freeLollipopDebugCompile \"com.android.support:leanback-v17:21.0.0\"\n" +
+ " paidIcsCompile \"com.android.support:appcompat-v7:21.0.0\"\n" +
+ "}\n");
+
+ project.execute("clean", "assemble");
+ model = project.getSingleModel();
+
+ File aapt = FileUtils.join(
+ project.getSdkDir(),
+ "build-tools",
+ DEFAULT_BUILD_TOOL_VERSION,
+ SdkConstants.FN_AAPT);
+ assertTrue("Test requires build-tools " + DEFAULT_BUILD_TOOL_VERSION, aapt.isFile());
+ ProcessExecutor processExecutor = new DefaultProcessExecutor(
+ new StdLogger(StdLogger.Level.ERROR));
+ apkInfoParser = new ApkInfoParser(aapt, processExecutor);
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null;
+ model = null;
+ apkInfoParser = null;
+ }
+
+ @Test
+ public void buildVariantSpecificDependency() throws IOException {
+ // check that the dependency was added by looking for a res file coming from the
+ // dependency.
+ checkApkForContent("freeLollipopDebug", "res/drawable/lb_background.xml");
+ }
+
+ @Test
+ public void buildMultiFlavorDependency() throws IOException {
+ // check that the dependency was added by looking for a res file coming from the
+ // dependency.
+ checkApkForContent("paidIcsDebug", "res/anim/abc_fade_in.xml");
+ checkApkForContent("paidIcsRelease", "res/anim/abc_fade_in.xml");
+ }
+
+ @Test
+ public void buildDefaultDependency() throws IOException {
+ // make sure that the other variants do not include any file from the variant-specific
+ // and multi-flavor dependencies.
+ Set<String> paths = Sets.newHashSet(
+ "res/anim/abc_fade_in.xml",
+ "res/drawable/lb_background.xml");
+
+ checkApkForMissingContent("paidLollipopDebug", paths);
+ checkApkForMissingContent("paidLollipopRelease", paths);
+ checkApkForMissingContent("freeLollipopRelease", paths);
+ checkApkForMissingContent("freeIcsDebug", paths);
+ checkApkForMissingContent("freeIcsRelease", paths);
+ }
+
+ @Test
+ public void modelVariantCount() {
+ Collection<Variant> variants = model.getVariants();
+ assertThat(variants).named("variants").hasSize(8);
+ }
+
+ @Test
+ public void modelVariantSpecificDependency() {
+ Collection<Variant> variants = model.getVariants();
+ String variantName = "freeLollipopDebug";
+ checkVariant(variants, variantName, "com.android.support:leanback-v17:aar:21.0.0");
+ }
+
+ @Test
+ public void modelMultiFlavorDependency() {
+ Collection<Variant> variants = model.getVariants();
+
+ checkVariant(variants, "paidIcsDebug", "com.android.support:appcompat-v7:aar:21.0.0");
+ checkVariant(variants, "paidIcsRelease", "com.android.support:appcompat-v7:aar:21.0.0");
+ }
+
+ @Test
+ public void modelDefaultDependency() {
+ Collection<Variant> variants = model.getVariants();
+
+ checkVariant(variants, "paidLollipopDebug", null);
+ checkVariant(variants, "paidLollipopRelease", null);
+ checkVariant(variants, "freeLollipopRelease", null);
+ checkVariant(variants, "freeIcsDebug", null);
+ checkVariant(variants, "freeIcsRelease", null);
+ }
+
+ private static void checkVariant(
+ @NonNull Collection<Variant> variants,
+ @NonNull String variantName,
+ @Nullable String dependencyName) {
+ Variant variant = ModelHelper.findVariantByName(variants, variantName);
+ assertThat(variant).named(variantName).isNotNull();
+
+ AndroidArtifact artifact = variant.getMainArtifact();
+ assertThat(artifact)
+ .named("main artifact for " + variantName)
+ .isNotNull();
+
+ Dependencies dependencies = artifact.getDependencies();
+ assertThat(dependencies)
+ .named("dependencies for main artifact of " + variantName)
+ .isNotNull();
+
+ if (dependencyName != null) {
+ assertThat(dependencies.getLibraries())
+ .named("aar deps for " + variantName)
+ .isNotEmpty();
+
+ AndroidLibrary library = dependencies.getLibraries().iterator().next();
+ assertThat(library).named("first aar depts for " + variantName).isNotNull();
+
+ MavenCoordinates coordinates = library.getResolvedCoordinates();
+ assertThat(coordinates)
+ .named("first aar lib coord for " + variantName)
+ .isNotNull();
+ assertThat(coordinates.toString())
+ .named("first aar lib coord for " + variantName)
+ .isEqualTo(dependencyName);
+ } else {
+ assertTrue("${variantName} aar deps empty",
+ dependencies.getLibraries().isEmpty());
+ }
+ }
+
+ private static void checkApkForContent(
+ @NonNull String variantName,
+ @NonNull String checkFilePath) throws IOException {
+ // use the model to get the output APK!
+ File apk = ModelHelper.findOutputFileByVariantName(model.getVariants(), variantName);
+ assertThat(apk).isFile();
+ assertThatZip(apk).contains(checkFilePath);
+ }
+
+ private static void checkApkForMissingContent(
+ @NonNull String variantName,
+ @NonNull Set<String> checkFilePath) throws IOException {
+ // use the model to get the output APK!
+ File apk = ModelHelper.findOutputFileByVariantName(model.getVariants(), variantName);
+ assertThat(apk).isFile();
+ assertThatZip(apk).entries(".*").containsNoneIn(checkFilePath);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/DslTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/DslTest.groovy
new file mode 100644
index 0000000..290709e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/DslTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.dsl
+import com.android.build.gradle.integration.application.BuildConfigTest
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.util.slurpersupport.GPathResult
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+/**
+ * General DSL tests
+ */
+class DslTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @Before
+ public void setUp() {
+ HelloWorldApp.noBuildFile().write(project.testDir, null)
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+"""
+ }
+
+ @Test
+ public void versionNameSuffix() {
+ project.getBuildFile() << """
+android {
+ defaultConfig {
+ versionName 'foo'
+ }
+
+ buildTypes {
+ debug {
+ versionNameSuffix '-suffix'
+ }
+ }
+}
+"""
+ // no need to do a full build. Let's just run the manifest task.
+ project.execute("processDebugManifest")
+
+ File manifestFile = project.file(
+ "build/intermediates/manifests/full/debug/AndroidManifest.xml")
+
+ GPathResult xml = new XmlSlurper().parse(manifestFile).declareNamespace(
+ android: 'http://schemas.android.com/apk/res/android')
+
+ String versionName = xml.'@android:versionName'.text()
+
+ assertNotNull(versionName)
+ assertEquals("foo-suffix", versionName)
+ }
+
+
+
+ @Test
+ public void extraPropTest() {
+ project.getBuildFile() << """
+android {
+ buildTypes {
+ debug {
+ ext.foo = "bar"
+ }
+ }
+
+ applicationVariants.all { variant ->
+ if (variant.buildType.name == "debug") {
+ def foo = variant.buildType.foo
+ if (!foo.equals("bar")) {
+ throw new RuntimeException("direct access to dynamic property failed, got " + foo)
+ }
+ def hasProperty = variant.buildType.hasProperty("foo")
+ if (!hasProperty) {
+ throw new RuntimeException("hasProperty not returning property value, got " + hasProperty)
+ }
+ }
+ }
+}
+"""
+ // no need to do a full build. Let's just run the tasks.
+ project.execute("tasks")
+
+ }
+
+ @Test
+ public void buildConfigEncoding() {
+ project.getBuildFile() << """
+android {
+ defaultConfig {
+ buildConfigField 'String', 'test2', '"\\u0105"'
+ }
+}
+"""
+
+ project.execute("generateDebugBuildConfig")
+
+ String expected =
+"""/**
+ * Automatically generated file. DO NOT MODIFY
+ */
+package com.example.helloworld;
+
+public final class BuildConfig {
+ public static final boolean DEBUG = Boolean.parseBoolean("true");
+ public static final String APPLICATION_ID = "com.example.helloworld";
+ public static final String BUILD_TYPE = "debug";
+ public static final String FLAVOR = "";
+ public static final int VERSION_CODE = 1;
+ public static final String VERSION_NAME = "1.0";
+ // Fields from default config.
+ public static final String test2 = "ą";
+}
+"""
+ BuildConfigTest.checkBuildConfig(project, expected, 'debug')
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/TestedVariantTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/TestedVariantTest.groovy
new file mode 100644
index 0000000..6b2b1f6
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/dsl/TestedVariantTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.integration.dsl
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Test that the test variant returns what it should.
+ */
+ at CompileStatic
+class TestedVariantTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+
+android.testVariants.all {
+ assert it.testedVariant
+}
+"""
+ }
+
+ @Test
+ public void testEvaluation() {
+ // no need to do a full build, we just want evaluation.
+ project.execute(":tasks")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/DisabledServiceTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/DisabledServiceTest.groovy
new file mode 100644
index 0000000..08e5314
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/DisabledServiceTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * 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.build.gradle.integration.googleservices
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.google.common.base.Joiner
+import com.google.common.io.Files
+import com.google.common.truth.Truth
+import groovy.json.internal.Charsets
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+/**
+ * Test for mostly empty json file.
+ */
+ at CompileStatic
+class DisabledServiceTest {
+
+ public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.free")
+
+ private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "disabledservice")
+ static {
+ File source = new File(resDataFolder, "example.json")
+ helloWorldApp.addFile(new TestSourceFile(
+ "",
+ TestHelper.JSON_FILE_NAME,
+ Files.toString(source, Charsets.UTF_8)))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(helloWorldApp)
+ .create()
+
+ public static AndroidProject model
+ private static File generatedResFolder
+
+ @BeforeClass
+ public static void setUp() {
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ }
+}
+"""
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ generatedResFolder = new File(project.getTestDir(),
+ Joiner.on(File.separator).join("build", "generated", "res", "google-services", "debug"))
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null
+
+ }
+ @Test
+ public void "test values res file is generated"() {
+ File valuesFolder = new File(generatedResFolder, "values")
+ File values = new File(valuesFolder, "values.xml")
+ Assert.assertTrue(values.isFile())
+
+ File goldenFile = new File(resDataFolder, "values.xml")
+ Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
+ .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
+ }
+
+ @Test
+ public void "test generated res folder is in model"() {
+ Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Collection<File> generatedResFolders = debugVariant.getMainArtifact().getGeneratedResourceFolders()
+
+ Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/FlavorTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/FlavorTest.groovy
new file mode 100644
index 0000000..6bce378
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/FlavorTest.groovy
@@ -0,0 +1,149 @@
+/*
+ * 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.build.gradle.integration.googleservices
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.google.common.base.Joiner
+import com.google.common.io.Files
+import com.google.common.truth.Truth
+import groovy.json.internal.Charsets
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+/**
+ * Basic test with gcm + ga
+ */
+ at CompileStatic
+class FlavorTest {
+
+ public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app")
+
+ private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "flavor")
+ static {
+ File source = new File(resDataFolder, "example.json")
+ helloWorldApp.addFile(new TestSourceFile(
+ "src/free/",
+ TestHelper.JSON_FILE_NAME,
+ Files.toString(source, Charsets.UTF_8)))
+ helloWorldApp.addFile(new TestSourceFile(
+ "src/paid/",
+ TestHelper.JSON_FILE_NAME,
+ Files.toString(source, Charsets.UTF_8)))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(helloWorldApp)
+ .create()
+
+ public static AndroidProject model
+ private static File generatedFreeResFolder
+ private static File generatedPaidResFolder
+
+ @BeforeClass
+ public static void setUp() {
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ productFlavors {
+ free {
+ applicationId 'com.example.app.free'
+ }
+ paid {
+ applicationId 'com.example.app.paid'
+ }
+ }
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ }
+}
+"""
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+
+ generatedFreeResFolder = new File(project.getTestDir(),
+ Joiner.on(File.separator).join("build", "generated", "res", "google-services", "free", "debug"))
+ generatedPaidResFolder = new File(project.getTestDir(),
+ Joiner.on(File.separator).join("build", "generated", "res", "google-services", "paid", "debug"))
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ public void "test values res file is generated"() {
+ checkResValuesFile(generatedFreeResFolder, "free.values.xml")
+ checkResValuesFile(generatedPaidResFolder, "paid.values.xml")
+ }
+
+ private static void checkResValuesFile(File generatedResFolder, String goldenFileName) {
+ File valuesFolder = new File(generatedResFolder, "values")
+ File values = new File(valuesFolder, "values.xml")
+ Assert.assertTrue(values.isFile())
+
+ File goldenFile = new File(resDataFolder, goldenFileName)
+ Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
+ .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
+ }
+
+ @Test
+ public void "test ga res file is generated"() {
+ checkGlobalTracker(generatedFreeResFolder, "free.global_tracker.xml")
+ checkGlobalTracker(generatedPaidResFolder, "paid.global_tracker.xml")
+ }
+
+ private static void checkGlobalTracker(File generatedResFolder, String goldenFileName) {
+ File xmlFolder = new File(generatedResFolder, "xml")
+ File global_tracker = new File(xmlFolder, "global_tracker.xml")
+ Assert.assertTrue(global_tracker.isFile())
+
+ File goldenFile = new File(resDataFolder, goldenFileName)
+ Truth.assert_().that(Files.toString(global_tracker, Charsets.UTF_8).trim())
+ .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
+ }
+
+ @Test
+ public void "test generated res folder is in model"() {
+ checkModel("freeDebug", generatedFreeResFolder)
+ checkModel("paidDebug", generatedPaidResFolder)
+ }
+
+ private static void checkModel(String variantName, File generatedResFolder) {
+ Variant freeDebugVariant = ModelHelper.getVariant(model.getVariants(), variantName);
+ Collection<File> generatedResFolders = freeDebugVariant.getMainArtifact().getGeneratedResourceFolders()
+
+ Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/GoogleServicesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/GoogleServicesTest.groovy
new file mode 100644
index 0000000..d175608
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/GoogleServicesTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * 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.build.gradle.integration.googleservices
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.google.common.base.Joiner
+import com.google.common.io.Files
+import com.google.common.truth.Truth
+import groovy.json.internal.Charsets
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+/**
+ * Basic test with gcm + ga
+ */
+ at CompileStatic
+class GoogleServicesTest {
+
+ public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.free")
+
+ private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "basic")
+ static {
+ File source = new File(resDataFolder, "example.json")
+ helloWorldApp.addFile(new TestSourceFile(
+ "",
+ TestHelper.JSON_FILE_NAME,
+ Files.toString(source, Charsets.UTF_8)))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(helloWorldApp)
+ .create()
+
+ public static AndroidProject model
+ private static File generatedResFolder
+
+ @BeforeClass
+ public static void setUp() {
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ }
+}
+"""
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+
+ generatedResFolder = new File(project.getTestDir(),
+ Joiner.on(File.separator).join("build", "generated", "res", "google-services", "debug"))
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null
+ model = null
+ }
+
+ @Test
+ public void "test values res file is generated"() {
+ File valuesFolder = new File(generatedResFolder, "values")
+ File values = new File(valuesFolder, "values.xml")
+ Assert.assertTrue(values.isFile())
+
+ File goldenFile = new File(resDataFolder, "values.xml")
+ Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
+ .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
+ }
+
+ @Test
+ public void "test ga res file is generated"() {
+ File xmlFolder = new File(generatedResFolder, "xml")
+ File global_tracker = new File(xmlFolder, "global_tracker.xml")
+ Assert.assertTrue(global_tracker.isFile())
+
+ File goldenFile = new File(resDataFolder, "global_tracker.xml")
+ Truth.assert_().that(Files.toString(global_tracker, Charsets.UTF_8).trim())
+ .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
+ }
+
+ @Test
+ public void "test generated res folder is in model"() {
+ Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Collection<File> generatedResFolders = debugVariant.getMainArtifact().getGeneratedResourceFolders()
+
+ Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoClientTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoClientTest.groovy
new file mode 100644
index 0000000..7774689
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoClientTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.googleservices
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.google.common.io.Files
+import com.google.common.truth.Truth
+import groovy.json.internal.Charsets
+import groovy.transform.CompileStatic
+import org.gradle.api.GradleException;
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+
+/**
+ * Test with a mismatch json file vs the app package name.
+ */
+ at CompileStatic
+class NoClientTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.typo")
+
+ static {
+ File source = new File(new File(GradleTestProject.TEST_RES_DIR, "basic"), "example.json")
+ helloWorldApp.addFile(new TestSourceFile(
+ "",
+ TestHelper.JSON_FILE_NAME,
+ Files.toString(source, Charsets.UTF_8)))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(helloWorldApp)
+ .captureStdOut(true)
+ .create()
+
+ @BeforeClass
+ public static void setUp() {
+
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void "test exception is thrown"() {
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ }
+}
+"""
+ thrown.expect(org.gradle.tooling.BuildException.class)
+ project.execute("clean", "assembleDebug")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoServiceTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoServiceTest.groovy
new file mode 100644
index 0000000..f036ba3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/NoServiceTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * 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.build.gradle.integration.googleservices
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.google.common.base.Joiner
+import com.google.common.io.Files
+import com.google.common.truth.Truth
+import groovy.json.internal.Charsets
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Test for mostly empty json file.
+ */
+ at CompileStatic
+class NoServiceTest {
+
+ public static final AndroidTestApp helloWorldApp = new EmptyAndroidTestApp("com.example.app.free")
+
+ private static final File resDataFolder = new File(GradleTestProject.TEST_RES_DIR, "noservice")
+ static {
+ File source = new File(resDataFolder, "no_services.json")
+ helloWorldApp.addFile(new TestSourceFile(
+ "",
+ TestHelper.JSON_FILE_NAME,
+ Files.toString(source, Charsets.UTF_8)))
+ }
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(helloWorldApp)
+ .create()
+
+ public static AndroidProject model
+ private static File generatedResFolder
+
+ @BeforeClass
+ public static void setUp() {
+
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ }
+}
+"""
+ model = project.executeAndReturnModel("clean", "assembleDebug")
+ generatedResFolder = new File(project.getTestDir(),
+ Joiner.on(File.separator).join("build", "generated", "res", "google-services", "debug"))
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ project = null
+
+ }
+ @Test
+ public void "test values res file is generated"() {
+ File valuesFolder = new File(generatedResFolder, "values")
+ File values = new File(valuesFolder, "values.xml")
+ Assert.assertTrue(values.isFile())
+
+ File goldenFile = new File(resDataFolder, "values.xml")
+ Truth.assert_().that(Files.toString(values, Charsets.UTF_8).trim())
+ .isEqualTo(Files.toString(goldenFile, Charsets.UTF_8).trim());
+ }
+
+ @Test
+ public void "test generated res folder is in model"() {
+ Variant debugVariant = ModelHelper.getVariant(model.getVariants(), "debug");
+ Collection<File> generatedResFolders = debugVariant.getMainArtifact().getGeneratedResourceFolders()
+
+ Truth.assert_().that(generatedResFolders).contains(generatedResFolder.getCanonicalFile())
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/TestHelper.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/TestHelper.java
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/TestHelper.java
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/googleservices/TestHelper.java
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/ColdSwapTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/ColdSwapTest.java
new file mode 100644
index 0000000..98d2dcf
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/ColdSwapTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject;
+import com.android.build.gradle.integration.common.truth.ApkSubject;
+import com.android.build.gradle.integration.common.truth.DexClassSubject;
+import com.android.build.gradle.integration.common.truth.DexFileSubject;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.builder.model.InstantRun;
+import com.android.tools.fd.client.InstantRunArtifact;
+import com.android.tools.fd.client.InstantRunArtifactType;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Smoke test for cold swap builds.
+ */
+public class ColdSwapTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application")).create();
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @Before
+ public void activityClass() throws IOException {
+ createActivityClass("", "");
+ }
+
+ @Test
+ public void withDalvik() throws Exception {
+ final int apiLevel = 15;
+
+ // Initial build
+ InstantRun instantRunModel =
+ InstantRunTestUtils.doInitialBuild(project, apiLevel, ColdswapMode.AUTO);
+
+ ApkSubject apkSubject = expect.about(ApkSubject.FACTORY)
+ .that(project.getApk("debug"));
+
+ apkSubject.getClass("Lcom/example/helloworld/HelloWorld;",
+ AbstractAndroidSubject.ClassFileScope.MAIN)
+ .that().hasMethod("onCreate");
+ apkSubject.getClass("Lcom/android/tools/fd/runtime/BootstrapApplication;",
+ AbstractAndroidSubject.ClassFileScope.MAIN);
+ apkSubject.getClass("Lcom/android/tools/fd/runtime/AppInfo;",
+ AbstractAndroidSubject.ClassFileScope.MAIN);
+
+ InstantRunBuildInfo initialContext = InstantRunTestUtils.loadContext(instantRunModel);
+ String startBuildId = initialContext.getTimeStamp();
+
+ // Cold swap
+ makeColdSwapChange();
+
+ project.execute(InstantRunTestUtils.getInstantRunArgs(apiLevel, ColdswapMode.AUTO),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunBuildInfo coldSwapContext = InstantRunTestUtils.loadContext(instantRunModel);
+ expect.that(coldSwapContext.getVerifierStatus()).named("verifier status")
+ .isEqualTo(InstantRunVerifierStatus.METHOD_ADDED.toString());
+ expect.that(coldSwapContext.getTimeStamp()).named("build id").isNotEqualTo(startBuildId);
+
+ assertThat(coldSwapContext.getArtifacts()).isEmpty();
+ }
+
+ @Test
+ public void withLollipop() throws Exception {
+ final int apiLevel = 21;
+ // Initial build
+ InstantRun instantRunModel =
+ InstantRunTestUtils.doInitialBuild(project, apiLevel, ColdswapMode.MULTIDEX);
+
+ ApkSubject apkSubject = expect.about(ApkSubject.FACTORY)
+ .that(project.getApk("debug"));
+
+ apkSubject.getClass("Lcom/example/helloworld/HelloWorld;",
+ AbstractAndroidSubject.ClassFileScope.INSTANT_RUN)
+ .that().hasMethod("onCreate");
+ apkSubject.getClass("Lcom/android/tools/fd/runtime/BootstrapApplication;",
+ AbstractAndroidSubject.ClassFileScope.ALL);
+ apkSubject.getClass("Lcom/android/tools/fd/runtime/AppInfo;",
+ AbstractAndroidSubject.ClassFileScope.ALL);
+
+ InstantRunBuildInfo initialContext = InstantRunTestUtils.loadContext(instantRunModel);
+ String startBuildId = initialContext.getTimeStamp();
+
+ // Cold swap
+ makeColdSwapChange();
+
+ project.execute(InstantRunTestUtils.getInstantRunArgs(apiLevel, ColdswapMode.MULTIDEX),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunBuildInfo coldSwapContext = InstantRunTestUtils.loadContext(instantRunModel);
+
+ expect.that(coldSwapContext.getVerifierStatus()).named("verifier status")
+ .isEqualTo(InstantRunVerifierStatus.METHOD_ADDED.toString());
+ expect.that(coldSwapContext.getTimeStamp()).named("build id").isNotEqualTo(startBuildId);
+
+ assertThat(coldSwapContext.getArtifacts()).hasSize(1);
+ InstantRunArtifact artifact = Iterables.getOnlyElement(coldSwapContext.getArtifacts());
+
+ expect.that(artifact.type).isEqualTo(InstantRunArtifactType.DEX);
+
+ checkUpdatedClassPresence(artifact.file);
+ }
+
+ @Ignore
+ @Test
+ public void withMarshmallow() throws Exception {
+ final int apiLevel = 23;
+ // Initial build
+ InstantRun instantRunModel =
+ InstantRunTestUtils.doInitialBuild(project, apiLevel, ColdswapMode.MULTIAPK);
+
+ // Classes are sharded into split apks.
+ assertThatApk(project.getApk("debug")).doesNotContain("classes.dex");
+
+ InstantRunBuildInfo initialContext = InstantRunTestUtils.loadContext(instantRunModel);
+ String startBuildId = initialContext.getTimeStamp();
+
+ // Cold swap
+ makeColdSwapChange();
+
+ project.execute(InstantRunTestUtils.getInstantRunArgs(apiLevel, ColdswapMode.MULTIAPK),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunBuildInfo coldSwapContext = InstantRunTestUtils.loadContext(instantRunModel);
+ expect.that(coldSwapContext.getVerifierStatus()).named("verifier status")
+ .isEqualTo(InstantRunVerifierStatus.METHOD_ADDED.toString());
+ expect.that(coldSwapContext.getTimeStamp()).named("build id").isNotEqualTo(startBuildId);
+
+ assertThat(coldSwapContext.getArtifacts()).hasSize(2);
+ InstantRunArtifact artifact = Iterables.getLast(coldSwapContext.getArtifacts());
+ expect.that(artifact.type).named("artifact type").isEqualTo(InstantRunArtifactType.SPLIT);
+ checkUpdatedClassPresence(artifact.file);
+ }
+
+ private void makeColdSwapChange() throws IOException {
+ createActivityClass("import java.util.logging.Logger;", "newMethod();\n"
+ + " }\n"
+ + " public void newMethod() {\n"
+ + " Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)\n"
+ + " .warning(\"Added some logging\");\n"
+ + "");
+
+
+ }
+
+ private void checkUpdatedClassPresence(@NonNull File dexFile) throws Exception {
+ DexClassSubject helloWorldClass = expect.about(DexFileSubject.FACTORY)
+ .that(dexFile)
+ .hasClass("Lcom/example/helloworld/HelloWorld;")
+ .that();
+ helloWorldClass.hasMethod("onCreate");
+ helloWorldClass.hasMethod("newMethod");
+ }
+
+ private void createActivityClass(@NonNull String imports, @NonNull String newMethodBody)
+ throws IOException {
+ String javaCompile = "package com.example.helloworld;\n" + imports +
+ "\n"
+ + "import android.app.Activity;\n"
+ + "import android.os.Bundle;\n"
+ + "\n"
+ + "public class HelloWorld extends Activity {\n"
+ + " /** Called when the activity is first created. */\n"
+ + " @Override\n"
+ + " public void onCreate(Bundle savedInstanceState) {\n"
+ + " super.onCreate(savedInstanceState);\n"
+ + " setContentView(R.layout.main);\n"
+ + " " +
+ newMethodBody +
+ " }\n"
+ + "}";
+ Files.write(javaCompile,
+ project.file("src/main/java/com/example/helloworld/HelloWorld.java"),
+ Charsets.UTF_8);
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/ConnectedColdSwapTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/ConnectedColdSwapTest.java
new file mode 100644
index 0000000..4b62b60
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/ConnectedColdSwapTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.utils.AndroidVersionMatcher.thatUsesArt;
+import static com.android.build.gradle.integration.common.utils.AndroidVersionMatcher.thatUsesDalvik;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.category.DeviceTests;
+import com.android.build.gradle.integration.common.fixture.Adb;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.Logcat;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.InstantRun;
+import com.android.ddmlib.IDevice;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.android.tools.fd.client.AppState;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.android.tools.fd.client.InstantRunClient;
+import com.android.tools.fd.client.UpdateMode;
+import com.android.tools.fd.client.UserFeedback;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+/**
+ * Connected smoke test for cold swap.
+ */
+ at Category(DeviceTests.class)
+ at RunWith(MockitoJUnitRunner.class)
+public class ConnectedColdSwapTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application")).create();
+
+ @Rule
+ public Adb adb = Adb.create();
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @Rule
+ public Logcat logcat = Logcat.create();
+
+ @Rule
+ public TestWatcher onFailure = new TestWatcher() {
+ @Override
+ protected void failed(Throwable e, Description description) {
+ InstantRunTestUtils.printBuildInfoFile(instantRunModel);
+ }
+ };
+
+ @Mock
+ UserFeedback userFeedback;
+
+ @Mock
+ ILogger iLogger;
+
+ @Before
+ public void activityClass() throws IOException {
+ createActivityClass("Logger.getLogger(\"coldswaptest\").warning(\"coldswaptest_before\");\n");
+ }
+
+ @Test
+ public void dalvikTest() throws Exception {
+ doTest(ColdswapMode.DEFAULT, adb.getDevice(thatUsesDalvik()));
+ }
+
+ @Test
+ public void multiApkTest() throws Exception {
+ doTest(ColdswapMode.MULTIAPK, adb.getDevice(thatUsesArt()));
+ }
+
+ @Test
+ public void multidexTest() throws Exception {
+ doTest(ColdswapMode.MULTIDEX, adb.getDevice(thatUsesArt()));
+ }
+
+ private InstantRun instantRunModel;
+
+ private void doTest(@NonNull ColdswapMode coldswapMode, @NonNull IDevice device)
+ throws Exception {
+ // Set up
+ logcat.start(device, "coldswaptest");
+ AndroidProject model = project.getSingleModel();
+ instantRunModel = InstantRunTestUtils.getInstantRunModel(model);
+ long token = PackagingUtils.computeApplicationHash(model.getBuildFolder());
+
+ // Initial build
+ project.execute(InstantRunTestUtils
+ .getInstantRunArgs(device, coldswapMode, OptionalCompilationStep.RESTART_ONLY),
+ "clean", "assembleDebug");
+
+ InstantRunBuildInfo info = InstantRunTestUtils.loadContext(instantRunModel);
+ InstantRunTestUtils.doInstall(device, info.getArtifacts());
+ InstantRunTestUtils.unlockDevice(device);
+ Logcat.MessageListener messageListener = logcat.listenForMessage("coldswaptest_before");
+ InstantRunTestUtils.runApp(device, "com.example.helloworld/.HelloWorld");
+
+ //Connect to device
+ InstantRunClient client =
+ new InstantRunClient("com.example.helloworld", userFeedback, iLogger, token, 8125);
+
+ // Give the app a chance to start
+ messageListener.await();
+
+ // Check the app is running
+ assertThat(client.getAppState(device)).isEqualTo(AppState.FOREGROUND);
+
+ // Cold swap
+ makeColdSwapChange();
+ project.execute(InstantRunTestUtils.getInstantRunArgs(device, coldswapMode),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunBuildInfo coldSwapContext = InstantRunTestUtils.loadContext(instantRunModel);
+
+ if (thatUsesDalvik().matches(device.getVersion())) {
+ // No artifact should have been produced, and the verifier is marked as failed,
+ // so studio knows to then call assembleDebug.
+ assertThat(coldSwapContext.canHotswap()).named("verifier passed").isFalse();
+ assertThat(coldSwapContext.getArtifacts()).isEmpty();
+ device.uninstallPackage("com.example.helloworld");
+ return;
+ }
+
+ if (coldswapMode == ColdswapMode.MULTIAPK) {
+ InstantRunTestUtils.doInstall(device, info.getArtifacts());
+ } else {
+ UpdateMode updateMode = client
+ .pushPatches(device, coldSwapContext,
+ UpdateMode.HOT_SWAP,
+ /* NB: Intentionally HOT_SWAP, pushPatches should automatically
+ determine that the changes cannot be hot-swapped */
+ false /*restartActivity*/,
+ true /*showToast*/);
+
+ assertThat(updateMode).named("updateMode").isEqualTo(UpdateMode.COLD_SWAP);
+ Mockito.verify(userFeedback).notifyEnd(UpdateMode.COLD_SWAP);
+ }
+ Mockito.verifyNoMoreInteractions(userFeedback);
+
+ Logcat.MessageListener afterMessageListener = logcat.listenForMessage("coldswaptest_after");
+
+ InstantRunTestUtils.runApp(device, "com.example.helloworld/.HelloWorld");
+
+ // Check the app is running
+ afterMessageListener.await();
+ assertThat(client.getAppState(device)).isEqualTo(AppState.FOREGROUND);
+
+ device.uninstallPackage("com.example.helloworld");
+ }
+
+ private void makeColdSwapChange() throws IOException {
+ createActivityClass("newMethod();\n"
+ + " }\n"
+ + " public void newMethod() {\n"
+ + " Logger.getLogger(\"coldswaptest\").warning(\"coldswaptest_after\");\n"
+ + "");
+ }
+
+ private void createActivityClass(@NonNull String newMethodBody)
+ throws IOException {
+ String javaCompile = "package com.example.helloworld;\n"
+ + "\n"
+ + "import java.util.logging.Logger;\n" +
+ "\n"
+ + "import android.app.Activity;\n"
+ + "import android.os.Bundle;\n"
+ + "\n"
+ + "public class HelloWorld extends Activity {\n"
+ + " /** Called when the activity is first created. */\n"
+ + " @Override\n"
+ + " public void onCreate(Bundle savedInstanceState) {\n"
+ + " super.onCreate(savedInstanceState);\n"
+ + " setContentView(R.layout.main);\n"
+ + " " +
+ newMethodBody +
+ " }\n"
+ + "}";
+ Files.write(javaCompile,
+ project.file("src/main/java/com/example/helloworld/HelloWorld.java"),
+ Charsets.UTF_8);
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/DaggerTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/DaggerTest.java
new file mode 100644
index 0000000..d72c0a7
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/DaggerTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatDex;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.integration.common.category.DeviceTests;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.Logcat;
+import com.android.build.gradle.integration.common.runner.FilterableParameterized;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.builder.model.InstantRun;
+import com.android.ddmlib.IDevice;
+import com.android.tools.fd.client.InstantRunArtifact;
+import com.android.tools.fd.client.InstantRunArtifactType;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.android.tools.fd.client.InstantRunClient;
+import com.google.common.collect.Iterables;
+
+import org.apache.commons.lang.SystemUtils;
+import org.gradle.api.JavaVersion;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Tests support for Dagger and Instant Run.
+ */
+ at RunWith(FilterableParameterized.class)
+public class DaggerTest {
+ private static final ColdswapMode COLDSWAP_MODE = ColdswapMode.MULTIDEX;
+ private static final String ORIGINAL_MESSAGE = "from module";
+ private static final String HOTSWAP_MESSAGE = "hotswap";
+
+ @Rule
+ public Logcat logcat = Logcat.create();
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {{"daggerOne"}, {"daggerTwo"}});
+ }
+
+ @Rule
+ public GradleTestProject project;
+
+ private File mAppModule;
+
+ private final String testProject;
+
+ public DaggerTest(String testProject) {
+ this.testProject = testProject;
+
+ project = GradleTestProject.builder()
+ .fromTestProject(testProject)
+ .create();
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ Assume.assumeFalse("Disabled until instant run supports Jack", GradleTestProject.USE_JACK);
+ if (this.testProject.equals("daggerTwo")) {
+ Assume.assumeTrue(
+ "Dagger 2 only works on java 7+",
+ JavaVersion.current().isJava7Compatible());
+ }
+ mAppModule = project.file("src/main/java/com/android/tests/AppModule.java");
+ }
+
+ @Test
+ public void normalBuild() throws Exception {
+ project.execute("assembleDebug");
+ }
+
+ @Test
+ public void coldSwap() throws Exception {
+ InstantRun instantRunModel = InstantRunTestUtils.doInitialBuild(project, 23, COLDSWAP_MODE);
+
+ InstantRunBuildInfo initialContext = InstantRunTestUtils.loadContext(instantRunModel);
+ String startBuildId = initialContext.getTimeStamp();
+
+ makeColdSwapChange();
+
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(23, COLDSWAP_MODE),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunBuildInfo coldSwapContext = InstantRunTestUtils.loadContext(instantRunModel);
+
+ assertThat(coldSwapContext.getVerifierStatus()).named("verifier status")
+ .isEqualTo(InstantRunVerifierStatus.METHOD_ADDED.toString());
+ assertThat(coldSwapContext.getTimeStamp()).named("build id").isNotEqualTo(startBuildId);
+
+ assertThat(coldSwapContext.getArtifacts()).hasSize(1);
+ InstantRunArtifact artifact = Iterables.getOnlyElement(coldSwapContext.getArtifacts());
+
+ assertThat(artifact.type).isEqualTo(InstantRunArtifactType.DEX);
+ assertThatDex(artifact.file)
+ .hasClass("Lcom/android/tests/AppModule;")
+ .that().hasMethod("getMessage");
+ }
+
+ @Test
+ public void hotSwap() throws Exception {
+ InstantRun instantRunModel = InstantRunTestUtils.doInitialBuild(project, 23, COLDSWAP_MODE);
+
+ makeHotSwapChange();
+
+ project.execute(InstantRunTestUtils.getInstantRunArgs(23, COLDSWAP_MODE),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunArtifact artifact =
+ InstantRunTestUtils.getCompiledHotSwapCompatibleChange(instantRunModel);
+
+ assertThatDex(artifact.file).hasClass("Lcom/android/tests/AppModule$override;");
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void hotSwap_device() throws Exception {
+ HotSwapTester.run(
+ project,
+ "com.android.tests",
+ "MainActivity",
+ this.testProject,
+ logcat,
+ new HotSwapTester.Callbacks() {
+ @Override
+ public void verifyOriginalCode(
+ @NonNull InstantRunClient client,
+ @NonNull Logcat logcat,
+ @NonNull IDevice device) throws Exception {
+ assertThat(logcat).containsMessageWithText(ORIGINAL_MESSAGE);
+ assertThat(logcat).doesNotContainMessageWithText(HOTSWAP_MESSAGE);
+ }
+
+ @Override
+ public void makeChange() throws Exception {
+ makeHotSwapChange();
+ }
+
+ @Override
+ public void verifyNewCode(@NonNull InstantRunClient client,
+ @NonNull Logcat logcat,
+ @NonNull IDevice device) throws Exception {
+ // Should not have restarted activity
+ assertThat(logcat).doesNotContainMessageWithText(ORIGINAL_MESSAGE);
+ assertThat(logcat).doesNotContainMessageWithText(HOTSWAP_MESSAGE);
+
+ client.restartActivity(device);
+ Thread.sleep(500); // TODO: blocking logcat assertions with timeouts.
+
+ assertThat(logcat).doesNotContainMessageWithText(ORIGINAL_MESSAGE);
+ assertThat(logcat).containsMessageWithText(HOTSWAP_MESSAGE);
+ }
+ });
+ }
+
+ private void makeColdSwapChange() throws Exception {
+ TestFileUtils.addMethod(
+ mAppModule,
+ "public String getMessage() { return \"coldswap\"; }");
+ TestFileUtils.searchAndReplace(mAppModule, "\"from module\"", "getMessage()");
+ }
+
+ private void makeHotSwapChange() throws Exception {
+ TestFileUtils.searchAndReplace(mAppModule, "from module", "hotswap");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/HotSwapTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/HotSwapTest.java
new file mode 100644
index 0000000..e41d7b8
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/HotSwapTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.category.DeviceTests;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.Logcat;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject;
+import com.android.build.gradle.integration.common.truth.ApkSubject;
+import com.android.build.gradle.integration.common.truth.DexFileSubject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.builder.model.InstantRun;
+import com.android.ddmlib.IDevice;
+import com.android.tools.fd.client.InstantRunArtifact;
+import com.android.tools.fd.client.InstantRunClient;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import com.google.common.truth.Expect;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+/**
+ * Smoke test for hot swap builds.
+ */
+ at RunWith(MockitoJUnitRunner.class)
+public class HotSwapTest {
+
+ private static final ColdswapMode COLDSWAP_MODE = ColdswapMode.MULTIDEX;
+
+ private static final String LOG_TAG = "hotswapTest";
+
+ @Rule
+ public GradleTestProject project =
+ GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application")).create();
+
+ @Rule
+ public Logcat logcat = Logcat.create();
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @Before
+ public void activityClass() throws IOException {
+ createActivityClass("Original");
+ }
+
+ @Test
+ public void buildIncrementallyWithInstantRun() throws Exception {
+ project.execute("clean");
+ InstantRun instantRunModel = InstantRunTestUtils
+ .getInstantRunModel(project.getSingleModel());
+
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(19,
+ COLDSWAP_MODE, OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+
+ // As no injected API level, will default to no splits.
+ ApkSubject apkFile = expect.about(ApkSubject.FACTORY)
+ .that(project.getApk("debug"));
+ apkFile.getClass("Lcom/example/helloworld/HelloWorld;",
+ AbstractAndroidSubject.ClassFileScope.MAIN)
+ .that().hasMethod("onCreate");
+ apkFile.getClass("Lcom/android/tools/fd/runtime/BootstrapApplication;",
+ AbstractAndroidSubject.ClassFileScope.MAIN);
+
+ makeBasicHotswapChange();
+
+ project.execute(InstantRunTestUtils.getInstantRunArgs(19, COLDSWAP_MODE),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunArtifact artifact =
+ InstantRunTestUtils.getCompiledHotSwapCompatibleChange(instantRunModel);
+
+ expect.about(DexFileSubject.FACTORY)
+ .that(artifact.file)
+ .hasClass("Lcom/example/helloworld/HelloWorld$override;")
+ .that().hasMethod("onCreate");
+ }
+
+ @Test
+ public void testModel() throws Exception {
+ InstantRun instantRunModel = InstantRunTestUtils.getInstantRunModel(
+ project.getSingleModel());
+
+ assertTrue(instantRunModel.isSupportedByArtifact());
+
+ TestFileUtils.appendToFile(project.getBuildFile(), "\nandroid.buildTypes.debug.useJack = true");
+
+ instantRunModel = InstantRunTestUtils.getInstantRunModel(
+ project.getSingleModel());
+
+ assertFalse(instantRunModel.isSupportedByArtifact());
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void doHotSwapChangeTest() throws Exception {
+ HotSwapTester.run(
+ project,
+ "com.example.helloworld",
+ "HelloWorld",
+ LOG_TAG,
+ logcat,
+ new HotSwapTester.Callbacks() {
+ @Override
+ public void verifyOriginalCode(@NonNull InstantRunClient client,
+ @NonNull Logcat logcat,
+ @NonNull IDevice device) throws Exception {
+ assertThat(logcat).containsMessageWithText("Original");
+ assertThat(logcat).doesNotContainMessageWithText("HOT SWAP!");
+ }
+
+ @Override
+ public void makeChange() throws Exception {
+ makeBasicHotswapChange();
+ }
+
+ @Override
+ public void verifyNewCode(@NonNull InstantRunClient client,
+ @NonNull Logcat logcat,
+ @NonNull IDevice device) throws Exception {
+ // Should not have restarted activity
+ assertThat(logcat).doesNotContainMessageWithText("Original");
+ assertThat(logcat).doesNotContainMessageWithText("HOT SWAP!");
+
+ client.restartActivity(device);
+ Thread.sleep(500); // TODO: blocking logcat assertions with timeouts.
+
+ assertThat(logcat).doesNotContainMessageWithText("Original");
+ assertThat(logcat).containsMessageWithText("HOT SWAP!");
+ }
+ }
+ );
+
+ }
+
+ private void makeBasicHotswapChange() throws IOException {
+ createActivityClass("HOT SWAP!");
+ }
+
+ private void createActivityClass(String message)
+ throws IOException {
+ String javaCompile = "package com.example.helloworld;\n"
+ + "import android.app.Activity;\n"
+ + "import android.os.Bundle;\n"
+ + "import java.util.logging.Logger;\n"
+ + "\n"
+ + "public class HelloWorld extends Activity {\n"
+ + " /** Called when the activity is first created. */\n"
+ + " @Override\n"
+ + " public void onCreate(Bundle savedInstanceState) {\n"
+ + " super.onCreate(savedInstanceState);\n"
+ + " setContentView(R.layout.main);\n"
+ + " Logger.getLogger(\"" + LOG_TAG + "\")\n"
+ + " .warning(\"" + message + "\");"
+ + " }\n"
+ + "}\n";
+ Files.write(javaCompile,
+ project.file("src/main/java/com/example/helloworld/HelloWorld.java"),
+ Charsets.UTF_8);
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/HotSwapTester.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/HotSwapTester.java
new file mode 100644
index 0000000..00ebe0a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/HotSwapTester.java
@@ -0,0 +1,153 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.Logcat;
+import com.android.build.gradle.integration.common.utils.DeviceHelper;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.InstantRun;
+import com.android.ddmlib.IDevice;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.fd.client.AppState;
+import com.android.tools.fd.client.InstantRunArtifact;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.android.tools.fd.client.InstantRunClient;
+import com.android.tools.fd.client.InstantRunClient.FileTransfer;
+import com.android.tools.fd.client.UpdateMode;
+import com.android.tools.fd.client.UserFeedback;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Assume;
+import org.mockito.Mockito;
+
+/**
+ * Helper for automating HotSwap testing.
+ */
+class HotSwapTester {
+ private HotSwapTester() {}
+
+ public static void run(
+ @NonNull GradleTestProject project,
+ @NonNull String packageName,
+ @NonNull String activityName,
+ @NonNull String logTag,
+ @NonNull Logcat logcat,
+ @NonNull Callbacks callbacks) throws Exception {
+ IDevice device = DeviceHelper.getIDevice();
+ // TODO: Generalize apk deployment to any compatible device.
+ Assume.assumeTrue(device.getVersion().equals(new AndroidVersion(23, null)));
+ try {
+ logcat.start(device, logTag);
+
+ // Open project in simulated IDE
+ AndroidProject model = project.getSingleModel();
+ long token = PackagingUtils.computeApplicationHash(model.getBuildFolder());
+ InstantRun instantRunModel = InstantRunTestUtils.getInstantRunModel(model);
+
+ // Run first time on device
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(
+ device, ColdswapMode.MULTIDEX, OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+
+ // Deploy to device
+ InstantRunBuildInfo info = InstantRunTestUtils.loadContext(instantRunModel);
+ InstantRunTestUtils.doInstall(device, info.getArtifacts());
+
+ logcat.clearFiltered();
+
+ // Run app
+ InstantRunTestUtils.unlockDevice(device);
+
+ InstantRunTestUtils.runApp(
+ device,
+ String.format("%s/.%s", packageName, activityName));
+
+ UserFeedback userFeedback = Mockito.mock(UserFeedback.class);
+ ILogger iLogger = Mockito.mock(ILogger.class);
+
+ //Connect to device
+ InstantRunClient client =
+ new InstantRunClient(packageName, userFeedback, iLogger, token, 8125);
+
+ // Give the app a chance to start
+ Thread.sleep(2000); // TODO: Is there a way to determine that the app is ready?
+
+ // Check the app is running
+ assertThat(client.getAppState(device)).isEqualTo(AppState.FOREGROUND);
+
+ callbacks.verifyOriginalCode(client, logcat, device);
+
+ callbacks.makeChange();
+
+ // Now build the hot swap patch.
+ project.execute(InstantRunTestUtils.getInstantRunArgs(device, ColdswapMode.MULTIDEX),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunArtifact artifact =
+ InstantRunTestUtils.getCompiledHotSwapCompatibleChange(instantRunModel);
+
+ FileTransfer fileTransfer = FileTransfer.createHotswapPatch(artifact.file);
+
+ logcat.clearFiltered();
+
+ client.pushPatches(
+ device,
+ info.getTimeStamp(),
+ ImmutableList.of(fileTransfer.getPatch()),
+ UpdateMode.HOT_SWAP,
+ false /*restartActivity*/,
+ true /*showToast*/);
+
+ Mockito.verify(userFeedback).notifyEnd(UpdateMode.HOT_SWAP);
+ Mockito.verifyNoMoreInteractions(userFeedback);
+
+ assertThat(client.getAppState(device)).isEqualTo(AppState.FOREGROUND);
+
+ callbacks.verifyNewCode(client, logcat, device);
+ } finally {
+ try {
+ // Clean up
+ device.uninstallPackage(packageName);
+ } catch (Exception e) {
+ // No point hiding the original exception.
+ }
+ }
+ }
+
+ interface Callbacks {
+ void verifyOriginalCode(
+ @NonNull InstantRunClient client,
+ @NonNull Logcat logcat,
+ @NonNull IDevice device) throws Exception;
+
+ void makeChange() throws Exception;
+
+ void verifyNewCode(
+ @NonNull InstantRunClient client,
+ @NonNull Logcat logcat,
+ @NonNull IDevice device) throws Exception;
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunAddLibraryTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunAddLibraryTest.java
new file mode 100644
index 0000000..a94a107
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunAddLibraryTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.builder.model.InstantRun;
+import com.android.tools.fd.client.InstantRunArtifact;
+import com.android.tools.fd.client.InstantRunArtifactType;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import com.google.common.truth.Expect;
+
+import org.hamcrest.Factory;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Check that the super classes can be found when subclassing things in provided dependencies.
+ */
+public class InstantRunAddLibraryTest {
+
+ private static final AndroidTestApp TEST_APP = HelloWorldApp
+ .forPlugin("com.android.application");
+
+ static {
+ TEST_APP.addFile(new TestSourceFile(
+ "src/main/resources/com/example/helloworld", "someres.txt", "Content\n"));
+ }
+
+ @Rule
+ public GradleTestProject project =
+ GradleTestProject.builder()
+ .fromTestApp(TEST_APP)
+ .create();
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @Before
+ public void addBlankUtilClass() throws IOException {
+ writeClass("throw new RuntimeException();");
+ TestFileUtils.appendToFile(project.getBuildFile(), "\n"
+ + "android.packagingOptions.exclude 'META-INF/maven/com.google.guava/guava/pom.xml'\n"
+ + "android.packagingOptions.exclude 'META-INF/maven/com.google.guava/guava/pom.properties'\n");
+ }
+
+ @Test
+ public void checkAddedLibraryCausesColdSwap() throws Exception {
+ project.execute("clean");
+ InstantRun instantRunModel = InstantRunTestUtils
+ .getInstantRunModel(project.getSingleModel());
+
+ project.execute(InstantRunTestUtils.getInstantRunArgs(23,
+ ColdswapMode.DEFAULT, OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+
+ // Add dependency
+ TestFileUtils.appendToFile(project.getBuildFile(), "\n"
+ + "dependencies {\n"
+ + " compile 'com.google.guava:guava:17.0'\n"
+ + "}\n");
+
+ // Use that dependency
+ writeClass("com.google.common.base.Strings.nullToEmpty(null);");
+
+ project.execute(InstantRunTestUtils.getInstantRunArgs(23, ColdswapMode.MULTIDEX),
+ instantRunModel.getIncrementalAssembleTaskName());
+
+ InstantRunBuildInfo context = InstantRunTestUtils.loadContext(instantRunModel);
+
+ assertThat(context.getVerifierStatus()).isEqualTo(
+ InstantRunVerifierStatus.DEPENDENCY_CHANGED.toString());
+
+ boolean foundDependencies = false;
+ assertThat(context.getArtifacts()).hasSize(2);
+ for (InstantRunArtifact artifact: context.getArtifacts()) {
+ expect.that(artifact.type).isEqualTo(InstantRunArtifactType.DEX);
+ if (artifact.file.getParentFile().getName().contains("guava")) {
+ //TODO: a real test for the dependencies dex being rebuilt.
+ foundDependencies = true;
+ }
+ }
+ assertTrue("The dependencies dex needs to be redeployed", foundDependencies);
+ }
+
+
+ public void writeClass(String action) throws IOException {
+ String contents = "package com.example.helloworld;" +
+ "public class Util {\n" +
+ " public static void doStuff() {\n" +
+ " " + action + "\n" +
+ " }\n" +
+ "}\n";
+ Files.write(contents, project.file("src/main/java/com/example/helloworld/Util.java"),
+ Charsets.UTF_8);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunShrinkerTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunShrinkerTest.java
new file mode 100644
index 0000000..af7bab2
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunShrinkerTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile;
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Check that the shrinker keeps a custom application class.
+ */
+public class InstantRunShrinkerTest {
+
+ private static final AndroidTestApp TEST_APP = HelloWorldApp.forPlugin("com.android.application");
+
+ static {
+ TEST_APP.addFile(new TestSourceFile("src/main/java/com/example/helloworld",
+ "MyApplication.java",
+ "package com.example.helloworld;" +
+ "public class MyApplication extends android.app.Application{\n" +
+ " public void onCreate() {\n" +
+ " super.onCreate();" +
+ " }\n" +
+ "}\n"));
+ TEST_APP.removeFile(TEST_APP.getFile("AndroidManifest.xml", "src/main"));
+ TEST_APP.addFile(new TestSourceFile("src/main", "AndroidManifest.xml",
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"3\"/>\n"
+ + " <application android:label=\"@string/app_name\""
+ + " android:name=\".MyApplication\">\n"
+ + " <activity android:name=\".HelloWorld\"\n"
+ + " android:label=\"@string/app_name\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>"));
+ }
+
+ @Rule
+ public GradleTestProject project =
+ GradleTestProject.builder()
+ .fromTestApp(TEST_APP)
+ .captureStdErr(true).captureStdOut(true)
+ .create();
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @Before
+ public void addProvidedLibrary() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(), "\n"
+ + "android.buildTypes.debug {\n"
+ + " minifyEnabled true\n"
+ + " useProguard false\n"
+ + "}\n");
+
+
+ }
+
+ @Test
+ public void checkApplicationIsNotRemoved() throws Exception {
+ project.execute("clean");
+ project.execute(InstantRunTestUtils.getInstantRunArgs(23,
+ ColdswapMode.DEFAULT, OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+
+ // Check the custom application class was included.
+ assertThatApk(project.getApk("debug"))
+ .containsClass("Lcom/example/helloworld/MyApplication;",
+ AbstractAndroidSubject.ClassFileScope.INSTANT_RUN);
+ }
+
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunTestUtils.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunTestUtils.java
new file mode 100644
index 0000000..a7713bd
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/InstantRunTestUtils.java
@@ -0,0 +1,224 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.utils.DeviceHelper.DEFAULT_ADB_TIMEOUT_MSEC;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.TruthHelper;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.InstantRun;
+import com.android.builder.model.Variant;
+import com.android.builder.testing.api.DeviceException;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.InstallException;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+import com.android.resources.Density;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.fd.client.InstantRunArtifact;
+import com.android.tools.fd.client.InstantRunArtifactType;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+public final class InstantRunTestUtils {
+
+ @NonNull
+ public static InstantRunBuildInfo loadContext(@NonNull InstantRun instantRunModel)
+ throws Exception {
+ InstantRunBuildInfo context = InstantRunBuildInfo.get(
+ Files.toString(instantRunModel.getInfoFile(), Charsets.UTF_8));
+ assertNotNull(context);
+ return context;
+ }
+
+ @NonNull
+ public static InstantRun getInstantRunModel(@NonNull AndroidProject project) {
+ Collection<Variant> variants = project.getVariants();
+ for (Variant variant : variants) {
+ if ("debug".equals(variant.getName())) {
+ return variant.getMainArtifact().getInstantRun();
+ }
+ }
+ throw new AssertionError("Could not find debug variant.");
+ }
+
+ @NonNull
+ public static List<String> getInstantRunArgs(int apiLevel,
+ @NonNull ColdswapMode coldswapMode,
+ @NonNull OptionalCompilationStep... flags) {
+ return getInstantRunArgs(new AndroidVersion(apiLevel, null),
+ null /* density */, coldswapMode, flags);
+ }
+
+ static List<String> getInstantRunArgs(
+ @NonNull IDevice device,
+ @NonNull ColdswapMode coldswapMode,
+ @NonNull OptionalCompilationStep... flags) {
+ return getInstantRunArgs(device.getVersion(),
+ Density.getEnum(device.getDensity()), coldswapMode, flags);
+ }
+
+ @NonNull
+ private static List<String> getInstantRunArgs(
+ @Nullable AndroidVersion androidVersion,
+ @Nullable Density denisty,
+ @NonNull ColdswapMode coldswapMode,
+ @NonNull OptionalCompilationStep[] flags) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ if (androidVersion != null) {
+ builder.add(String.format(
+ "-Pandroid.injected.build.api=%s", androidVersion.getApiString()));
+ }
+ if (denisty != null) {
+ builder.add(String.format(
+ "-Pandroid.injected.build.density=%s", denisty.getResourceValue()));
+ }
+
+ builder.add(String.format("-Pandroid.injected.coldswap.mode=%s", coldswapMode.name()));
+
+ StringBuilder optionalSteps = new StringBuilder()
+ .append("-P").append("android.optional.compilation").append('=')
+ .append("INSTANT_DEV");
+ for (OptionalCompilationStep step : flags) {
+ optionalSteps.append(',').append(step);
+ }
+ builder.add(optionalSteps.toString());
+ return builder.build();
+ }
+
+ static void doInstall(
+ @NonNull IDevice device,
+ @NonNull List<InstantRunArtifact> artifacts) throws DeviceException,
+ InstallException {
+ List<File> apkFiles = Lists.newArrayList();
+ for (InstantRunArtifact artifact : artifacts) {
+ if (artifact.type == InstantRunArtifactType.SPLIT) {
+ apkFiles.add(artifact.file);
+ }
+ if (artifact.type == InstantRunArtifactType.MAIN ||
+ artifact.type == InstantRunArtifactType.SPLIT_MAIN ) {
+ apkFiles.add(0, artifact.file);
+ }
+ }
+
+ if (device.getVersion().isGreaterOrEqualThan(21)) {
+ device.installPackages(apkFiles, true /*reinstall*/, ImmutableList.<String>of(),
+ DEFAULT_ADB_TIMEOUT_MSEC, MILLISECONDS);
+
+ } else {
+ assertThat(apkFiles).hasSize(1);
+ device.installPackage(
+ Iterables.getOnlyElement(apkFiles).getAbsolutePath(), true /*reinstall*/);
+ }
+ }
+
+ static void runApp(IDevice device, String target) throws Exception {
+ IShellOutputReceiver receiver = new CollectingOutputReceiver();
+ String command = "am start" +
+ " -n " + target +
+ " -a android.intent.action.MAIN" +
+ " -c android.intent.category.LAUNCHER";
+ device.executeShellCommand(
+ command, receiver, DEFAULT_ADB_TIMEOUT_MSEC, MILLISECONDS);
+ }
+
+ static void stopApp(@NonNull IDevice device, @NonNull String target) throws Exception {
+ IShellOutputReceiver receiver = new CollectingOutputReceiver();
+ String command = "am start" +
+ " -n " + target +
+ " -a android.intent.action.MAIN" +
+ " -c android.intent.category.LAUNCHER";
+ device.executeShellCommand(
+ command, receiver, DEFAULT_ADB_TIMEOUT_MSEC, MILLISECONDS);
+ }
+
+ static void unlockDevice(@NonNull IDevice device) throws Exception {
+ IShellOutputReceiver receiver = new CollectingOutputReceiver();
+ device.executeShellCommand(
+ "input keyevent KEYCODE_WAKEUP", receiver,
+ DEFAULT_ADB_TIMEOUT_MSEC, MILLISECONDS);
+ device.executeShellCommand(
+ "wm dismiss-keyguard", receiver,
+ DEFAULT_ADB_TIMEOUT_MSEC, MILLISECONDS);
+ }
+
+ @NonNull
+ static InstantRun doInitialBuild(
+ @NonNull GradleTestProject project,
+ int apiLevel,
+ @NonNull ColdswapMode coldswapMode) {
+ project.execute("clean");
+ InstantRun instantRunModel = getInstantRunModel(project.getSingleModel());
+
+ project.execute(
+ getInstantRunArgs(apiLevel, coldswapMode, OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+
+ return instantRunModel;
+ }
+
+ /**
+ * Gets the {@link InstantRunArtifact} produced by last build.
+ */
+ @NonNull
+ static InstantRunArtifact getCompiledHotSwapCompatibleChange(
+ @NonNull InstantRun instantRunModel) throws Exception {
+ InstantRunBuildInfo context = loadContext(instantRunModel);
+
+ TruthHelper.assertThat(context.getArtifacts()).hasSize(1);
+ InstantRunArtifact artifact = Iterables.getOnlyElement(context.getArtifacts());
+
+ TruthHelper.assertThat(artifact.type).isEqualTo(InstantRunArtifactType.RELOAD_DEX);
+ return artifact;
+ }
+
+ public static void printBuildInfoFile(@Nullable InstantRun instantRunModel) {
+ if (instantRunModel == null) {
+ System.err.println("Cannot print build info file as model is null");
+ return;
+ }
+ try {
+ System.out.println("------------ build info file ------------\n"
+ + Files.toString(instantRunModel.getInfoFile(), Charsets.UTF_8)
+ + "---------- end build info file ----------\n");
+ } catch (IOException e) {
+ System.err.println("Unable to print build info xml file: \n" +
+ Throwables.getStackTraceAsString(e));
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/JavaResourcesTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/JavaResourcesTest.java
new file mode 100644
index 0000000..9f103e0
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/JavaResourcesTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.build.gradle.internal.incremental.InstantRunBuildContext;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.InstantRun;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Instant run test for changing Java resources.
+ */
+public class JavaResourcesTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application"))
+ .create();
+
+ private File resource;
+
+ @Before
+ public void setUp() throws IOException {
+ resource = project.file("src/main/resources/foo.txt");
+ FileUtils.createFile(resource, "foo");
+ }
+
+ @Test
+ public void testChangingJavaResources() throws Exception {
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(
+ 21,
+ ColdswapMode.DEFAULT,
+ OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+ AndroidProject model = project.getSingleModel();
+
+ assertThat(project.getApk("debug")).exists();
+ Files.write("bar", resource, Charsets.UTF_8);
+
+ InstantRun instantRunModel = InstantRunTestUtils.getInstantRunModel(model);
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(21, ColdswapMode.DEFAULT),
+ instantRunModel.getIncrementalAssembleTaskName());
+ InstantRunBuildInfo context = InstantRunTestUtils.loadContext(instantRunModel);
+ assertThat(context.getVerifierStatus()).isEqualTo(
+ InstantRunVerifierStatus.JAVA_RESOURCES_CHANGED.toString());
+ assertThat(context.getArtifacts()).hasSize(0);
+ }
+
+ @Test
+ public void testChangingJavaSources() throws Exception {
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(
+ 21,
+ ColdswapMode.DEFAULT,
+ OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+ AndroidProject model = project.getSingleModel();
+ assertThat(project.getApk("debug")).exists();
+
+ Files.write("bar", resource, Charsets.UTF_8);
+
+ InstantRun instantRunModel = InstantRunTestUtils.getInstantRunModel(model);
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(21, ColdswapMode.DEFAULT),
+ instantRunModel.getIncrementalAssembleTaskName());
+ InstantRunBuildInfo context = InstantRunTestUtils.loadContext(instantRunModel);
+ assertThat(context.getVerifierStatus()).isEqualTo(
+ InstantRunVerifierStatus.JAVA_RESOURCES_CHANGED.toString());
+ assertThat(context.getArtifacts()).hasSize(0);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/LibDependencyTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/LibDependencyTest.java
new file mode 100644
index 0000000..b25c182
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/LibDependencyTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk;
+import static com.android.build.gradle.integration.common.utils.TestFileUtils.appendToFile;
+import static com.android.build.gradle.integration.instant.InstantRunTestUtils.getInstantRunModel;
+import static com.android.utils.FileUtils.mkdirs;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject;
+import com.android.build.gradle.integration.common.truth.ApkSubject;
+import com.android.build.gradle.integration.common.truth.DexFileSubject;
+import com.android.build.gradle.integration.common.truth.FileSubject;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.build.gradle.internal.incremental.InstantRunVerifierStatus;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.InstantRun;
+import com.android.builder.model.Variant;
+import com.android.ide.common.process.ProcessException;
+import com.android.tools.fd.client.InstantRunArtifact;
+import com.android.tools.fd.client.InstantRunArtifactType;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+import com.google.common.truth.Expect;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Hot swap test for sub projects.
+ */
+public class LibDependencyTest {
+
+ @Rule
+ public GradleTestProject project =
+ GradleTestProject.builder()
+ .fromTestProject("libDependency")
+ .create();
+
+ @Rule
+ public Expect expect = Expect.create();
+
+ @Before
+ public void activityClass() throws IOException {
+ createLibraryClass("Before");
+ }
+
+ @Test
+ public void buildIncrementallyWithInstantRun() throws IOException, ProcessException {
+ project.execute("clean");
+ Map<String, AndroidProject> projects = project.getAllModels();
+ InstantRun instantRunModel = getInstantRunModel(projects.get(":app"));
+
+ // Check that original class is included.
+ project.execute(getInstantRunArgs(), "clean", "assembleRelease", "assembleDebug");
+ expect.about(ApkSubject.FACTORY)
+ .that(project.getSubproject("app").getApk("debug"))
+ .getClass("Lcom/android/tests/libstest/lib/MainActivity;",
+ AbstractAndroidSubject.ClassFileScope.MAIN)
+ .that().hasMethod("onCreate");
+
+ checkHotSwapCompatibleChange(instantRunModel);
+ }
+
+ @Test
+ public void hotSwapChangeInJavaLibrary() throws Exception {
+ appendToFile(project.file("settings.gradle"), "\n"
+ + "include 'javalib'\n");
+ appendToFile(project.file("lib/build.gradle"), "\n"
+ + "dependencies {\n"
+ + " compile project(':javalib')\n"
+ + "}\n");
+ mkdirs(project.file("javalib"));
+ Files.write("apply plugin: 'java'\n", project.file("javalib/build.gradle"), Charsets.UTF_8);
+ createJavaLibraryClass("original");
+
+ Map<String, AndroidProject> projects = project.getAllModels();
+ InstantRun instantRunModel = getInstantRunModel(projects.get(":app"));
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(
+ 23, ColdswapMode.MULTIDEX, OptionalCompilationStep.RESTART_ONLY),
+ "clean", ":app:assembleDebug");
+ createJavaLibraryClass("changed");
+ project.execute(InstantRunTestUtils.getInstantRunArgs(23, ColdswapMode.MULTIDEX),
+ instantRunModel.getIncrementalAssembleTaskName());
+ InstantRunBuildInfo context = InstantRunTestUtils.loadContext(instantRunModel);
+ assertThat(context.getVerifierStatus()).isEqualTo(
+ InstantRunVerifierStatus.COMPATIBLE.toString());
+ assertThat(context.getArtifacts()).hasSize(1);
+ InstantRunArtifact artifact = Iterables.getOnlyElement(context.getArtifacts());
+ assertThat(artifact.type).isEqualTo(InstantRunArtifactType.RELOAD_DEX);
+ }
+
+ @Test
+ public void checkVerifierFailsIfJavaResourceInLibraryChanged() throws Exception {
+ File resource = project.getSubproject(":lib").file("src/main/resources/properties.txt");
+ Files.write("java resource", resource, Charsets.UTF_8);
+
+ project.execute("clean");
+ Map<String, AndroidProject> projects = project.getAllModels();
+ InstantRun instantRunModel = getInstantRunModel(projects.get(":app"));
+
+ // Check that original class is included.
+ project.execute(getInstantRunArgs(), "clean", "assembleDebug");
+ expect.about(ApkSubject.FACTORY)
+ .that(project.getSubproject("app").getApk("debug"))
+ .getClass("Lcom/android/tests/libstest/lib/MainActivity;",
+ AbstractAndroidSubject.ClassFileScope.MAIN)
+ .that().hasMethod("onCreate");
+
+ Files.write("changed java resource", resource, Charsets.UTF_8);
+
+ project.execute(getInstantRunArgs(), instantRunModel.getIncrementalAssembleTaskName());
+ InstantRunBuildInfo context = InstantRunTestUtils.loadContext(instantRunModel);
+ assertThat(context.getVerifierStatus()).isEqualTo(
+ InstantRunVerifierStatus.JAVA_RESOURCES_CHANGED.toString());
+ assertThat(context.getArtifacts()).hasSize(0);
+ }
+
+ /**
+ * Check a hot-swap compatible change works as expected.
+ */
+ private void checkHotSwapCompatibleChange(@NonNull InstantRun instantRunModel)
+ throws IOException, ProcessException {
+ createLibraryClass("Hot swap change");
+
+ project.execute(getInstantRunArgs(), instantRunModel.getIncrementalAssembleTaskName());
+
+ // TODO: The instant run model methods are gone -- this test needs to be
+ // updated to use
+ // instantRunModel.getInfoFile()
+ // (See InstantRunBuildInfo in the IDE)
+ //expect.about(DexFileSubject.FACTORY)
+ // .that(instantRunModel.getReloadDexFile())
+ // .hasClass("Lcom/android/tests/libstest/lib/Lib$override;");
+ //
+ //// the restart .dex should not be present.
+ //expect.about(FileSubject.FACTORY).that(instantRunModel.getRestartDexFile()).doesNotExist();
+ }
+
+ private void createLibraryClass(String message)
+ throws IOException {
+ String javaCompile = "package com.android.tests.libstest.lib;\n"
+ +"public class Lib {\n"
+ +"public static String someString() {\n"
+ +"return \"someStringMessage=" + message + "\";\n"
+ +"}\n"
+ +"}\n";
+ Files.write(javaCompile,
+ project.file("lib/src/main/java/com/android/tests/libstest/lib/Lib.java"),
+ Charsets.UTF_8);
+ }
+
+ private void createJavaLibraryClass(String message)
+ throws IOException {
+ File dir = project.file("javalib/src/main/java/com/example/javalib");
+ mkdirs(dir);
+ String java = "package com.example.javalib;\n"
+ +"public class A {\n"
+ +" public static String someString() {\n"
+ +" return \"someStringMessage=" + message + "\";\n"
+ +" }\n"
+ +"}\n";
+ Files.write(java, new File(dir, "A.java"), Charsets.UTF_8);
+ }
+
+ private static List<String> getInstantRunArgs(OptionalCompilationStep... flags) {
+ String property = "-P" + AndroidProject.OPTIONAL_COMPILATION_STEPS + "=" +
+ OptionalCompilationStep.INSTANT_DEV + "," + Joiner.on(',').join(flags);
+ return Collections.singletonList(property);
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/NativeLibraryInstantRunTest.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/NativeLibraryInstantRunTest.java
new file mode 100644
index 0000000..9c4ecd4
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/instant/NativeLibraryInstantRunTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.build.gradle.integration.instant;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.OptionalCompilationStep;
+import com.android.build.gradle.integration.common.category.DeviceTests;
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp;
+import com.android.build.gradle.integration.common.utils.DeviceHelper;
+import com.android.build.gradle.integration.common.utils.TestFileUtils;
+import com.android.build.gradle.internal.incremental.ColdswapMode;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.InstantRun;
+import com.android.ddmlib.IDevice;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.android.tools.fd.client.AppState;
+import com.android.tools.fd.client.InstantRunBuildInfo;
+import com.android.tools.fd.client.InstantRunClient;
+import com.android.tools.fd.client.UserFeedback;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+/**
+ * Simple test to ensure component model plugin do not crash when instant run is enabled.
+ */
+ at RunWith(MockitoJUnitRunner.class)
+public class NativeLibraryInstantRunTest {
+
+ @Mock
+ UserFeedback userFeedback;
+
+ private final ILogger iLogger = new StdLogger(StdLogger.Level.INFO);
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldJniApp.builder().build())
+ .create();
+
+ @Before
+ public void setUp() throws IOException {
+ TestFileUtils.appendToFile(project.getBuildFile(),
+ "project.ext['android.useDeprecatedNdk'] = true"
+ + "\napply plugin: \"com.android.application\"\n"
+ + "android {\n"
+ + " compileSdkVersion " + GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ + "\n"
+ + " buildToolsVersion '" + GradleTestProject.DEFAULT_BUILD_TOOL_VERSION
+ + "'\n"
+ + " defaultConfig {\n"
+ + " ndk {\n"
+ + " moduleName 'hello-jni'\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void checkRuns() throws Exception {
+ IDevice device = DeviceHelper.getIDevice();
+ AndroidProject model = project.getSingleModel();
+ InstantRun instantRunModel = InstantRunTestUtils.getInstantRunModel(model);
+ project.execute(
+ InstantRunTestUtils.getInstantRunArgs(
+ 21,
+ ColdswapMode.DEFAULT,
+ OptionalCompilationStep.RESTART_ONLY),
+ "assembleDebug");
+ InstantRunBuildInfo info = InstantRunTestUtils.loadContext(instantRunModel);
+ InstantRunTestUtils.doInstall(device, info.getArtifacts());
+
+ // Run app
+ InstantRunTestUtils.unlockDevice(device);
+
+ InstantRunTestUtils.runApp(device, "com.example.hellojni/.HelloJni");
+
+
+ long token = PackagingUtils.computeApplicationHash(model.getBuildFolder());
+
+ //Connect to device
+ InstantRunClient client =
+ new InstantRunClient("com.example.hellojni", userFeedback, iLogger, token, 8125);
+
+ // Give the app a chance to start
+ Thread.sleep(1000); // TODO: Is there a way to determine that the app is ready?
+
+ // Check the app is running
+ assertThat(client.getAppState(device)).isEqualTo(AppState.FOREGROUND);
+
+ device.uninstallPackage("com.example.hellojni");
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AidlTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AidlTest.groovy
new file mode 100644
index 0000000..136d6ed
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AidlTest.groovy
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.runner.FilterableParameterized
+import com.android.build.gradle.integration.common.truth.AarSubject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import com.android.utils.FileUtils
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+/**
+ * Assemble tests for aidl.
+ */
+ at CompileStatic
+ at RunWith(FilterableParameterized)
+class AidlTest {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object[]> data() {
+ return [
+ ["com.android.application"].toArray(),
+ ["com.android.library"].toArray(),
+ ]
+ }
+
+ @Rule
+ public GradleTestProject project
+
+ private String plugin
+ private File iTestAidl
+ private File aidlDir
+ private File activity
+
+ public AidlTest(String plugin) {
+ this.plugin = plugin
+ this.project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin(plugin))
+ .create()
+ }
+
+ @Before
+ void setUp() {
+ aidlDir = project.file("src/main/aidl/com/example/helloworld")
+
+ FileUtils.mkdirs(aidlDir)
+
+ new File(aidlDir, "MyRect.aidl") << """
+package com.example.helloworld;
+
+// Declare MyRect so AIDL can find it and knows that it implements
+// the parcelable protocol.
+parcelable MyRect;
+"""
+
+ iTestAidl = new File(aidlDir, "ITest.aidl")
+ iTestAidl << """
+package com.example.helloworld;
+
+import com.example.helloworld.MyRect;
+
+interface ITest {
+ MyRect getMyRect();
+ int getInt();
+}"""
+
+ new File(aidlDir, "WhiteListed.aidl") << """
+package com.example.helloworld;
+
+import com.example.helloworld.MyRect;
+
+interface WhiteListed {
+ MyRect getMyRect();
+ int getInt();
+}"""
+
+ File javaDir = project.file("src/main/java/com/example/helloworld")
+ activity = new File(javaDir, "HelloWorld.java")
+
+ new File(javaDir, "MyRect.java") << """
+package com.example.helloworld;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class MyRect implements Parcelable {
+ public int left;
+ public int top;
+ public int right;
+ public int bottom;
+
+ public static final Parcelable.Creator<MyRect> CREATOR = new Parcelable.Creator<MyRect>() {
+ public MyRect createFromParcel(Parcel in) {
+ return new MyRect(in);
+ }
+
+ public MyRect[] newArray(int size) {
+ return new MyRect[size];
+ }
+ };
+
+ public MyRect() {
+ }
+
+ private MyRect(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void writeToParcel(Parcel out) {
+ out.writeInt(left);
+ out.writeInt(top);
+ out.writeInt(right);
+ out.writeInt(bottom);
+ }
+
+ public void readFromParcel(Parcel in) {
+ left = in.readInt();
+ top = in.readInt();
+ right = in.readInt();
+ bottom = in.readInt();
+ }
+
+ public int describeContents() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public void writeToParcel(Parcel arg0, int arg1) {
+ // TODO Auto-generated method stub
+
+ }
+}
+"""
+
+ TestFileUtils.addMethod(
+ activity,
+ """
+ void useAidlClasses(ITest instance) throws Exception {
+ MyRect r = instance.getMyRect();
+ r.toString();
+ }
+ """)
+
+ if (plugin.contains("library")) {
+ project.buildFile <<
+ 'android.aidlPackageWhiteList = ["com/example/helloworld/WhiteListed.aidl"]'
+ }
+
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ void testAidl() {
+ project.execute("assembleDebug")
+ checkAar("ITest")
+
+ TestFileUtils.searchAndReplace(iTestAidl, "int getInt\\(\\);", "")
+ project.execute("assembleDebug")
+ checkAar("ITest")
+
+ TestFileUtils.searchAndReplace(iTestAidl, "ITest", "IRenamed")
+ TestFileUtils.searchAndReplace(activity, "ITest", "IRenamed")
+ Files.move(iTestAidl, new File(aidlDir, "IRenamed.aidl"))
+
+ project.execute("assembleDebug")
+ checkAar("IRenamed")
+ checkAar("ITest")
+ }
+
+ private void checkAar(String dontInclude) {
+ if (!this.plugin.contains("library")) {
+ return
+ }
+
+ AarSubject aar = assertThatAar(project.getAar("debug"))
+ aar.contains("aidl/com/example/helloworld/MyRect.aidl")
+ aar.contains("aidl/com/example/helloworld/WhiteListed.aidl")
+ aar.doesNotContain("aidl/com/example/helloworld/${dontInclude}.aidl")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApiTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApiTest.groovy
new file mode 100644
index 0000000..0452be0
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApiTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Assemble tests for api.
+ */
+ at CompileStatic
+class ApiTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("api")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+
+ @Test
+ public void backwardsCompatible() throws Exception {
+ // ATTENTION Author and Reviewers - please make sure required changes to the build file
+ // are backwards compatible before updating this test.
+ assertThat(FileUtils.sha1(project.file("app/build.gradle")))
+ .isEqualTo("73f4266bf1cf99d3257112d4f46da19060079163")
+ assertThat(FileUtils.sha1(project.file("lib/build.gradle")))
+ .isEqualTo("a8e6745dacc2951d2fe6a94b8526f3e738439b8b")
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApplibtestTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApplibtestTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApplibtestTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ApplibtestTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AssetsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AssetsTest.groovy
new file mode 100644
index 0000000..2c1749f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AssetsTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.google.common.io.Files
+import com.google.common.io.Resources
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import java.nio.charset.Charset
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.builder.core.BuilderConstants.DEBUG
+/**
+ * Assemble tests for assets.
+ */
+ at CompileStatic
+class AssetsTest {
+ static byte[] simpleJarDataA
+ static byte[] simpleJarDataB
+ static byte[] simpleJarDataC
+ static byte[] simpleJarDataD
+ static File assetsDir;
+ static File resRawDir;
+ static File resourcesDir;
+ static File libsDir;
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("assets")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ simpleJarDataA = Resources.toByteArray(Resources.getResource(AssetsTest.class,
+ "/jars/simple-jar-with-A_DoIExist-class.jar"))
+ simpleJarDataB = Resources.toByteArray(Resources.getResource(AssetsTest.class,
+ "/jars/simple-jar-with-B_DoIExist-class.jar"))
+ simpleJarDataC = Resources.toByteArray(Resources.getResource(AssetsTest.class,
+ "/jars/simple-jar-with-C_DoIExist-class.jar"))
+ simpleJarDataD = Resources.toByteArray(Resources.getResource(AssetsTest.class,
+ "/jars/simple-jar-with-D_DoIExist-class.jar"))
+
+ // Make directories where we will place jars.
+ assetsDir = project.file("lib/src/main/assets")
+ assetsDir.mkdirs()
+ resRawDir = project.file("lib/src/main/res/raw")
+ resRawDir.mkdirs()
+ resourcesDir = project.file("lib/src/main/resources")
+ resourcesDir.mkdirs()
+ libsDir = project.file("lib/libs")
+ libsDir.mkdirs()
+
+ // Add the libs dependency in the library build file.
+ Files.append("\ndependencies {\ncompile fileTree(dir: 'libs', include: '*.jar')\n}\n"
+ .replaceAll("\n", System.getProperty("line.separator")),
+ project.file("lib/build.gradle"), Charset.defaultCharset());
+
+ // Create some jars.
+ Files.write(simpleJarDataA, new File(libsDir, "a1.jar"))
+ Files.write(simpleJarDataB, new File(assetsDir, "b1.jar"))
+ Files.write(simpleJarDataC, new File(resourcesDir, "c1.jar"))
+ Files.write(simpleJarDataD, new File(resRawDir, "d1.jar"))
+
+ // Run the project.
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void checkJarLocations() {
+ // Obtain the apk file.
+ File apk = project.getSubproject("app").getApk(DEBUG);
+ assertThat(apk).isNotNull()
+
+ // a1.jar was placed in libs so it should have been merged into the dex.
+ assertThatApk(apk).doesNotContain("jars/a1.jar");
+ assertThatApk(apk).containsClass("LA_DoIExist;");
+
+ // b1.jar was placed into assets and should be in assets.
+ assertThatApk(apk).containsFileWithContent("assets/b1.jar", simpleJarDataB);
+ assertThatApk(apk).doesNotContainClass("LB_DoIExist;");
+
+ // c1.jar was placed into resources and should be in the root.
+ assertThatApk(apk).containsFileWithContent("c1.jar", simpleJarDataC);
+ assertThatApk(apk).doesNotContainClass("LC_DoIExist;");
+
+ // d1.jar was placed into res/raw and should be in the root.
+ assertThatApk(apk).containsFileWithContent("res/raw/d1.jar", simpleJarDataD);
+ assertThatApk(apk).doesNotContainClass("LD_DoIExist;");
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AttrOrderTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AttrOrderTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AttrOrderTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/AttrOrderTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/DslTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/DslTest.groovy
new file mode 100644
index 0000000..0a90bfd
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/DslTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import groovy.transform.CompileStatic
+import org.gradle.tooling.BuildException
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.fail
+
+ at CompileStatic
+class DslTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .create()
+
+ @Before
+ public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+}
+"""
+ }
+
+ @Test
+ public void applicationIdInDefaultConfig() {
+ project.getBuildFile() << """
+android {
+ defaultConfig {
+ applicationId = 'foo'
+ }
+}
+"""
+ // Just need to run the 'tasks' task to trigger error
+ try {
+ project.execute("tasks")
+ fail('Broken build file did not throw exception')
+ } catch (BuildException e) {
+ Throwable cause = e
+ while (cause.getCause() != null) {
+ cause = cause.getCause()
+ }
+ String expectedMsg = "Library projects cannot set applicationId. applicationId is set to 'foo' in default config."
+ assertEquals(expectedMsg, cause.getMessage())
+ }
+ }
+
+ @Test
+ public void applicationIdSuffix() {
+ project.getBuildFile() << """
+android {
+ buildTypes {
+ debug {
+ applicationIdSuffix = 'foo'
+ }
+ }
+}
+"""
+ // Just need to run the 'tasks' task to trigger error
+ try {
+ project.execute("tasks")
+ fail('Broken build file did not throw exception')
+ } catch (BuildException e) {
+ Throwable cause = e
+ while (cause.getCause() != null) {
+ cause = cause.getCause()
+ }
+ String expectedMsg = "Library projects cannot set applicationIdSuffix. applicationIdSuffix is set to 'foo' in build type 'debug'."
+ assertEquals(expectedMsg, cause.getMessage())
+ }
+ }
+
+ @Test
+ public void applicationIdInProductFlavor() {
+ project.getBuildFile() << """
+android {
+ productFlavors {
+ myFlavor {
+ applicationId = 'foo'
+ }
+ }
+}
+"""
+ // Just need to run the 'tasks' task to trigger error
+ try {
+ project.execute("tasks")
+ fail('Broken build file did not throw exception')
+ } catch (BuildException e) {
+ Throwable cause = e
+ while (cause.getCause() != null) {
+ cause = cause.getCause()
+ }
+ String expectedMsg = "Library projects cannot set applicationId. applicationId is set to 'foo' in flavor 'myFlavor'."
+ assertEquals(expectedMsg, cause.getMessage())
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavoredlibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavoredlibTest.groovy
new file mode 100644
index 0000000..e60fc5f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavoredlibTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidLibrary
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Dependencies
+import com.android.builder.model.ProductFlavorContainer
+import com.android.builder.model.Variant
+import com.google.common.base.Joiner
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+/**
+ * Assemble tests for flavoredlib.
+ */
+ at CompileStatic
+class FlavoredlibTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("flavoredlib")
+ .create()
+ static Map<String, AndroidProject> models
+
+ @BeforeClass
+ static void setUp() {
+ models = project.executeAndReturnMultiModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ models = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ void testModel() {
+ AndroidProject appModel = models.get(":app")
+ assertNotNull("Module app null-check", appModel)
+
+ assertFalse("Library Project", appModel.isLibrary())
+
+ Collection<Variant> variants = appModel.getVariants()
+ Collection<ProductFlavorContainer> productFlavors = appModel.getProductFlavors()
+
+ ProductFlavorContainer flavor1 = ModelHelper.getProductFlavor(productFlavors, "flavor1")
+ assertNotNull(flavor1)
+
+ Variant flavor1Debug = ModelHelper.getVariant(variants, "flavor1Debug")
+ assertNotNull(flavor1Debug)
+
+ Dependencies dependencies = flavor1Debug.getMainArtifact().getDependencies()
+ assertNotNull(dependencies)
+ Collection<AndroidLibrary> libs = dependencies.getLibraries()
+ assertNotNull(libs)
+ assertEquals(1, libs.size())
+ AndroidLibrary androidLibrary = libs.iterator().next()
+ assertNotNull(androidLibrary)
+ assertEquals(":lib", androidLibrary.getProject())
+ assertEquals("flavor1Release", androidLibrary.getProjectVariant())
+ // TODO: right now we can only test the folder name efficiently
+ String path = androidLibrary.getFolder().getPath()
+ assertTrue(path, path.endsWith(File.separator + "flavoredlib" + File.separatorChar + "lib"
+ + File.separatorChar + "unspecified" + File.separatorChar + "flavor1Release"))
+
+ ProductFlavorContainer flavor2 = ModelHelper.getProductFlavor(productFlavors, "flavor2")
+ assertNotNull(flavor2)
+
+ Variant flavor2Debug = ModelHelper.getVariant(variants, "flavor2Debug")
+ assertNotNull(flavor2Debug)
+
+ dependencies = flavor2Debug.getMainArtifact().getDependencies()
+ assertNotNull(dependencies)
+ libs = dependencies.getLibraries()
+ assertNotNull(libs)
+ assertEquals(1, libs.size())
+ androidLibrary = libs.iterator().next()
+ assertNotNull(androidLibrary)
+ assertEquals(":lib", androidLibrary.getProject())
+ assertEquals("flavor2Release", androidLibrary.getProjectVariant())
+ // TODO: right now we can only test the folder name efficiently
+ path = androidLibrary.getFolder().getPath()
+ assertTrue(path, path.endsWith(Joiner.on(File.separatorChar).join(
+ "flavoredlib", "lib", "unspecified", "flavor2Release")))
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavorlibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavorlibTest.groovy
new file mode 100644
index 0000000..b0b58d1
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/FlavorlibTest.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidLibrary
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Dependencies
+import com.android.builder.model.ProductFlavorContainer
+import com.android.builder.model.Variant
+import com.google.common.base.Joiner
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Assemble tests for flavorlib.
+ */
+ at CompileStatic
+class FlavorlibTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("flavorlib")
+ .create()
+ static Map<String, AndroidProject> models
+
+ @BeforeClass
+ static void setUp() {
+ models = project.executeAndReturnMultiModel("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ models = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ void report() {
+ project.execute("androidDependencies", "signingReport")
+ }
+
+ @Test
+ void testModel() throws Exception {
+
+ AndroidProject appProject = models.get(":app")
+ assertNotNull("Module app null-check", appProject)
+
+ assertFalse("Library Project", appProject.isLibrary())
+
+ Collection<Variant> variants = appProject.getVariants()
+ Collection<ProductFlavorContainer> productFlavors = appProject.getProductFlavors()
+
+ ProductFlavorContainer flavor1 = ModelHelper.getProductFlavor(productFlavors, "flavor1")
+ assertNotNull(flavor1)
+
+ Variant flavor1Debug = ModelHelper.getVariant(variants, "flavor1Debug")
+ assertNotNull(flavor1Debug)
+
+ Dependencies dependencies = flavor1Debug.getMainArtifact().getDependencies()
+ assertNotNull(dependencies)
+ Collection<AndroidLibrary> libs = dependencies.getLibraries()
+ assertNotNull(libs)
+ assertEquals(1, libs.size())
+ AndroidLibrary androidLibrary = libs.iterator().next()
+ assertNotNull(androidLibrary)
+ assertEquals(":lib1", androidLibrary.getProject())
+ // TODO: right now we can only test the folder name efficiently
+ String path = androidLibrary.getFolder().getPath()
+ assertTrue(path, path.endsWith(Joiner.on(File.separatorChar).join(
+ "flavorlib", "lib1", "unspecified")))
+
+ ProductFlavorContainer flavor2 = ModelHelper.getProductFlavor(productFlavors, "flavor2")
+ assertNotNull(flavor2)
+
+ Variant flavor2Debug = ModelHelper.getVariant(variants, "flavor2Debug")
+ assertNotNull(flavor2Debug)
+
+ dependencies = flavor2Debug.getMainArtifact().getDependencies()
+ assertNotNull(dependencies)
+ libs = dependencies.getLibraries()
+ assertNotNull(libs)
+ Assert.assertEquals(1, libs.size())
+ androidLibrary = libs.iterator().next()
+ assertNotNull(androidLibrary)
+ assertEquals(":lib2", androidLibrary.getProject())
+ // TODO: right now we can only test the folder name efficiently
+ path = androidLibrary.getFolder().getPath()
+ assertTrue(path, path.endsWith(
+ Joiner.on(File.separatorChar).join("flavorlib", "lib2", "unspecified")))
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/GenerateAnnotationsClassPathTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/GenerateAnnotationsClassPathTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/GenerateAnnotationsClassPathTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/GenerateAnnotationsClassPathTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/JarJarLibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/JarJarLibTest.groovy
new file mode 100644
index 0000000..915da9e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/JarJarLibTest.groovy
@@ -0,0 +1,167 @@
+/*
+ * 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.build.gradle.integration.library
+import com.android.annotations.NonNull
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidArtifactOutput
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.SyncIssue
+import com.android.builder.model.Variant
+import com.google.common.collect.Iterators
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.AbstractInsnNode
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.MethodNode
+import org.objectweb.asm.tree.TypeInsnNode
+
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+import static com.android.builder.core.BuilderConstants.DEBUG
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+/**
+ * Test for the jarjar integration.
+ */
+ at CompileStatic
+public class JarJarLibTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("jarjarIntegrationLib")
+ .create()
+
+ @Before
+ void setUp() {
+ }
+
+ @After
+ void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check repackaged gson library"() {
+ project.getBuildFile() << """
+android {
+ registerTransform(new com.android.test.jarjar.JarJarTransform(false /*broken transform*/))
+}
+"""
+
+ AndroidProject model = project.executeAndReturnModel("clean", "assembleDebug")
+
+ Collection<Variant> variants = model.getVariants()
+ assertEquals("Variant Count", 2, variants.size())
+
+ // get the main artifact of the debug artifact
+ Variant debugVariant = ModelHelper.getVariant(variants, DEBUG)
+ assertNotNull("debug Variant null-check", debugVariant)
+ AndroidArtifact debugMainArtifact = debugVariant.getMainArtifact()
+ assertNotNull("Debug main info null-check", debugMainArtifact)
+
+ // get the outputs.
+ Collection<AndroidArtifactOutput> debugOutputs = debugMainArtifact.getOutputs()
+ assertNotNull(debugOutputs)
+ assertEquals(1, debugOutputs.size())
+
+ // make sure the Gson library has been renamed and the original one is not present.
+ File outputFile = Iterators.getOnlyElement(debugOutputs.iterator()).mainOutputFile.
+ getOutputFile()
+ assertThatAar(outputFile).containsClass('Lcom/android/tests/basic/Main;');
+
+ // libraries do not include their dependencies unless they are local (which is not
+ // the case here), so neither versions of Gson should be present here).
+ assertThatAar(outputFile).doesNotContainClass("Lcom/google/repacked/gson/Gson;");
+ assertThatAar(outputFile).doesNotContainClass("Lcom/google/gson/Gson;")
+
+ // check we do not have the R class of the library in there.
+ assertThatAar(outputFile).doesNotContainClass('Lcom/android/tests/basic/R;')
+ assertThatAar(outputFile).doesNotContainClass('Lcom/android/tests/basic/R$drawable;')
+
+ // check the content of the Main class.
+ File jarFile = project.file(
+ "build/" +
+ "$AndroidProject.FD_INTERMEDIATES/" +
+ "bundles/" +
+ "debug/" +
+ "classes.jar")
+ checkClassFile(jarFile)
+ }
+
+ @Test
+ void "check broken transform"() {
+ project.getBuildFile() << """
+android {
+ registerTransform(new com.android.test.jarjar.JarJarTransform(true /*broken transform*/))
+}
+"""
+
+ AndroidProject model = project.getSingleModelIgnoringSyncIssues()
+
+ assertThat(model).hasSingleIssue(
+ SyncIssue.SEVERITY_ERROR,
+ SyncIssue.TYPE_GENERIC)
+ }
+
+ @CompileDynamic
+ static def checkClassFile(@NonNull File jarFile) {
+ ZipFile zipFile = new ZipFile(jarFile);
+ try {
+ ZipEntry entry = zipFile.getEntry("com/android/tests/basic/Main.class")
+ assertThat(entry).named("Main.class entry").isNotNull()
+ ClassReader classReader = new ClassReader(zipFile.getInputStream(entry))
+ ClassNode mainTestClassNode = new ClassNode(Opcodes.ASM5)
+ classReader.accept(mainTestClassNode, 0)
+
+ // Make sure bytecode got rewritten to point to renamed classes.
+
+ // search for the onCrate method.
+ //noinspection GroovyUncheckedAssignmentOfMemberOfRawType
+ MethodNode onCreateMethod = mainTestClassNode.methods.find { it.name == "onCreate" };
+ assertThat(onCreateMethod).named("onCreate method").isNotNull();
+
+ // find the new instruction, and verify it's using the repackaged Gson.
+ TypeInsnNode newInstruction = null;
+ int count = onCreateMethod.instructions.size()
+ for (int i = 0; i < count ; i++) {
+ AbstractInsnNode instruction = onCreateMethod.instructions.get(i);
+ if (instruction.opcode == Opcodes.NEW) {
+ newInstruction = (TypeInsnNode) instruction;
+ break;
+ }
+ }
+
+ assertThat(newInstruction).named("new instruction").isNotNull();
+ assertThat(newInstruction.desc).isEqualTo("com/google/repacked/gson/Gson");
+
+ } finally {
+ zipFile.close();
+ }
+ }
+
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyJarDepTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyJarDepTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyJarDepTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyJarDepTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyLibDepTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyLibDepTest.groovy
new file mode 100644
index 0000000..9464c63
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyLibDepTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Assemble tests for libMinifyLibDep.
+ */
+ at CompileStatic
+class LibMinifyLibDepTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("libMinifyLibDep")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ void "check proguard"() {
+ File mapping = project.getSubproject("lib").file("build/outputs/mapping/release/mapping.txt");
+ // Check classes are obfuscated unless it is kept by the proguard configuration.
+ assertThat(mapping).containsAllOf(
+ "com.android.tests.basic.StringGetter -> com.android.tests.basic.StringGetter",
+ "com.android.tests.internal.StringGetterInternal -> com.android.tests.a.a")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyTest.groovy
new file mode 100644
index 0000000..5a33447
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibMinifyTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+
+/**
+ * Assemble tests for libMinify.
+ */
+ at CompileStatic
+class LibMinifyTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("libMinify")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "build")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check library has its fields obfuscated"() {
+ // test whether a library project has its fields obfuscated
+ TestFileUtils.checkContent(
+ project.getOutputFile("mapping/release/mapping.txt"),
+ "int obfuscatedInt -> a")
+ }
+
+ @Test
+ void "check R class is not packaged"() {
+ assertThatAar(project.getAar("debug")).doesNotContainClass(
+ "Lcom/android/tests/basic.R;",
+ AbstractAndroidSubject.ClassFileScope.MAIN)
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibProguardConsumerFilesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibProguardConsumerFilesTest.groovy
new file mode 100644
index 0000000..c31bb55
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibProguardConsumerFilesTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
+
+/**
+ * Assemble tests for libProguarConsumerFiles.
+ */
+ at CompileStatic
+class LibProguardConsumerFilesTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("libProguardConsumerFiles")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "build")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check proguard.txt has been correctly merged"() {
+ File debugFileOutput = project.file("build/" + FD_INTERMEDIATES + "/bundles/debug/proguard.txt")
+ File releaseFileOutput = project.file("build/" + FD_INTERMEDIATES + "/bundles/release/proguard.txt")
+
+ TestFileUtils.checkContent(debugFileOutput, "A")
+ TestFileUtils.checkContent(releaseFileOutput, ["A", "B", "C"])
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibTestDepTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibTestDepTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibTestDepTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibTestDepTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibsTestTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibsTestTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibsTestTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LibsTestTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalAarTestTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalAarTestTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalAarTestTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalAarTestTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalJarsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalJarsTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalJarsTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/LocalJarsTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MinifyLibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MinifyLibTest.groovy
new file mode 100644
index 0000000..c28fb30
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MinifyLibTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.build.gradle.integration.common.truth.TruthHelper
+import com.google.common.truth.Truth
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for minifyLib.
+ */
+ at CompileStatic
+class MinifyLibTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("minifyLib")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.execute("clean", "assembleDebug")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiDexWithLibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiDexWithLibTest.groovy
new file mode 100644
index 0000000..a456a84
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiDexWithLibTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.library
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Assemble tests for multiDexWithLib.
+ */
+ at CompileStatic
+class MultiDexWithLibTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("multiDexWithLib")
+ .withHeap("2048M")
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ GradleTestProject.assumeBuildToolsAtLeast(21)
+ project.execute("clean", "assembleDebug", "assembleDebugAndroidTest")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void lint() {
+ project.execute("lint")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ void connectedCheck() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiprojectTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiprojectTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiprojectTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/MultiprojectTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ProguardAarPackagingTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ProguardAarPackagingTest.groovy
new file mode 100644
index 0000000..53f056a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/ProguardAarPackagingTest.groovy
@@ -0,0 +1,150 @@
+package com.android.build.gradle.integration.library
+import com.android.SdkConstants
+import com.android.annotations.NonNull
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.EmptyAndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.build.gradle.integration.common.truth.TruthHelper
+import com.google.common.base.Joiner
+import com.google.common.io.Files
+import org.apache.commons.io.FileUtils
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertNull
+import static org.junit.Assert.assertTrue
+/**
+ * Integration test to check that libraries included directly as jar files are correctly handled
+ * when using proguard.
+ */
+class ProguardAarPackagingTest {
+
+ static public AndroidTestApp testApp = HelloWorldApp.noBuildFile()
+ static public AndroidTestApp libraryInJar = new EmptyAndroidTestApp()
+
+ static {
+ TestSourceFile oldHelloWorld = testApp.getFile("HelloWorld.java")
+ testApp.removeFile(oldHelloWorld)
+ testApp.addFile(new TestSourceFile(oldHelloWorld.path, oldHelloWorld.name, """\
+package com.example.helloworld;
+
+import com.example.libinjar.LibInJar;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class HelloWorld extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ LibInJar.method();
+ }
+}
+"""))
+
+ testApp.addFile(new TestSourceFile("", "config.pro", "-keeppackagenames"));
+
+ // Create simple library jar.
+ libraryInJar.addFile(new TestSourceFile(
+ "src/main/java/com/example/libinjar","LibInJar.java", """\
+package com.example.libinjar;
+
+public class LibInJar {
+ public static void method() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+}
+"""))
+ }
+
+ @ClassRule
+ static public GradleTestProject androidProject =
+ GradleTestProject.builder().withName("mainProject").fromTestApp(testApp).create()
+ @ClassRule
+ static public GradleTestProject libraryInJarProject =
+ GradleTestProject.builder().withName("libInJar").fromTestApp(libraryInJar).create()
+
+ @BeforeClass
+ static public void setUp() {
+ // Create android test application
+ androidProject.getBuildFile() << """\
+apply plugin: 'com.android.library'
+
+dependencies {
+ compile fileTree(dir: 'libs', include: '*.jar')
+}
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'config.pro'
+ }
+ }
+}
+"""
+
+ libraryInJarProject.buildFile << "apply plugin: 'java'"
+ libraryInJarProject.execute("assemble")
+
+ // Copy the generated jar into the android project.
+ androidProject.file("libs").mkdirs()
+ String libInJarName = Joiner.on(File.separatorChar)
+ .join("build", "libs", libraryInJarProject.getName() + SdkConstants.DOT_JAR)
+ FileUtils.copyFile(
+ libraryInJarProject.file(libInJarName),
+ androidProject.file("libs/libinjar.jar"))
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ androidProject = null
+ libraryInJarProject = null
+ }
+
+ @Test
+ public void "check debug AAR packaging"() {
+ androidProject.execute("assembleDebug")
+
+ // check that the classes from the local jars are still in a local jar
+ assertThatAar(androidProject.getAar("debug")).containsClass(
+ "Lcom/example/libinjar/LibInJar;",
+ AbstractAndroidSubject.ClassFileScope.SECONDARY)
+
+ // check that it's not in the main class file.
+ assertThatAar(androidProject.getAar("debug")).doesNotContainClass(
+ "Lcom/example/libinjar/LibInJar;",
+ AbstractAndroidSubject.ClassFileScope.MAIN)
+ }
+
+ @Test
+ public void "check release AAR packaging"() {
+ androidProject.execute("assembleRelease")
+
+ // check that the classes from the local jars are in the main class file
+ assertThatAar(androidProject.getAar("release")).containsClass(
+ "Lcom/example/libinjar/a;",
+ AbstractAndroidSubject.ClassFileScope.MAIN)
+
+ // check that it's not in any local jar
+ assertThatAar(androidProject.getAar("release")).doesNotContainClass(
+ "Lcom/example/libinjar/LibInJar;",
+ AbstractAndroidSubject.ClassFileScope.SECONDARY)
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/RenderscriptInLibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/RenderscriptInLibTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/RenderscriptInLibTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/RenderscriptInLibTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/SameNamedLibsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/SameNamedLibsTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/SameNamedLibsTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/library/SameNamedLibsTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkConnectedCheckTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkConnectedCheckTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkConnectedCheckTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkConnectedCheckTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkJniLibTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkJniLibTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkJniLibTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkJniLibTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkLibPrebuiltsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkLibPrebuiltsTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkLibPrebuiltsTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkLibPrebuiltsTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkModelTest.groovy
new file mode 100644
index 0000000..d688a5c
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkModelTest.groovy
@@ -0,0 +1,203 @@
+/*
+ * 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.build.gradle.integration.ndk
+
+import com.android.SdkConstants
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.NativeLibrary
+import com.android.builder.model.Variant
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+
+/**
+ * Test the return model of the NDK.
+ */
+class NdkModelTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .addGradleProperties("android.useDeprecatedNdk=true")
+ .create()
+
+ @Before
+ void setUp() {
+ project.buildFile <<
+"""
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ defaultConfig {
+ ndk {
+ moduleName "hello-jni"
+ cFlags = "-DTEST_FLAG"
+ }
+ }
+}
+"""
+ }
+
+ @Test
+ void "check native libraries in model"() {
+ checkModel(
+ debug : [
+ SdkConstants.ABI_ARMEABI,
+ SdkConstants.ABI_ARMEABI_V7A,
+ SdkConstants.ABI_ARM64_V8A,
+ SdkConstants.ABI_INTEL_ATOM,
+ SdkConstants.ABI_INTEL_ATOM64,
+ SdkConstants.ABI_MIPS,
+ SdkConstants.ABI_MIPS64
+ ]);
+ }
+
+ @Test
+ void "check native libraries with splits"() {
+ project.buildFile <<
+"""
+android {
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'armeabi-v7a', 'mips'
+ }
+ }
+}
+"""
+ checkModel(
+ debug: [SdkConstants.ABI_ARMEABI_V7A, SdkConstants.ABI_INTEL_ATOM, SdkConstants.ABI_MIPS]);
+ }
+
+ @Test
+ void "check native libraries with splits and universalApk"() {
+ project.buildFile <<
+ """
+android {
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'armeabi-v7a', 'mips'
+ universalApk true
+ }
+ }
+}
+"""
+ checkModel(
+ debug : [
+ SdkConstants.ABI_ARMEABI,
+ SdkConstants.ABI_ARMEABI_V7A,
+ SdkConstants.ABI_ARM64_V8A,
+ SdkConstants.ABI_INTEL_ATOM,
+ SdkConstants.ABI_INTEL_ATOM64,
+ SdkConstants.ABI_MIPS,
+ SdkConstants.ABI_MIPS64
+ ]);
+ }
+
+ @Test
+ void "check native libraries with abiFilters"() {
+ project.buildFile <<
+ """
+android {
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilters "armeabi-v7a"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
+"""
+ checkModel(
+ x86Debug : [SdkConstants.ABI_INTEL_ATOM],
+ armDebug : [SdkConstants.ABI_ARMEABI_V7A],
+ mipsDebug : [SdkConstants.ABI_MIPS]);
+ }
+
+ @Test
+ void "check using add on string for compileSdkVersion"() {
+ project.buildFile <<
+"""
+android {
+ compileSdkVersion "Google Inc.:Google APIs:$GradleTestProject.DEFAULT_COMPILE_SDK_VERSION"
+}
+"""
+ AndroidProject model = project.executeAndReturnModel("assembleDebug")
+ NativeLibrary lib = ModelHelper.getVariant(model.getVariants(), "debug").getMainArtifact()
+ .getNativeLibraries().first()
+ for (String flag : lib.getCCompilerFlags()) {
+ if (flag.contains("sysroot")) {
+ assertThat(flag).contains("android-${GradleTestProject.LATEST_NDK_VERSION}")
+ }
+ }
+ }
+
+ /**
+ * Verify resulting model is as expected.
+ *
+ * @param variantToolchains map of variant name to array of expected toolchains.
+ */
+ private void checkModel(Map variantToolchains) {
+
+ AndroidProject model = project.executeAndReturnModel("assembleDebug")
+
+ Collection<Variant> variants = model.getVariants()
+ for (Map.Entry entry : variantToolchains) {
+ Variant variant = ModelHelper.getVariant(variants, (String) entry.getKey())
+ AndroidArtifact mainArtifact = variant.getMainArtifact()
+
+ assertThat(mainArtifact.getNativeLibraries()).hasSize(((Collection)entry.getValue()).size())
+ for (NativeLibrary nativeLibrary : mainArtifact.getNativeLibraries()) {
+ assertThat(nativeLibrary.getName()).isEqualTo("hello-jni")
+ assertThat(nativeLibrary.getCCompilerFlags()).contains("-DTEST_FLAG");
+ assertThat(nativeLibrary.getCppCompilerFlags()).contains("-DTEST_FLAG");
+ assertThat(nativeLibrary.getCSystemIncludeDirs()).isEmpty();
+ assertThat(nativeLibrary.getCppSystemIncludeDirs()).isNotEmpty();
+ File solibSearchPath = nativeLibrary.getDebuggableLibraryFolders().first()
+ assertThat(new File(solibSearchPath, "libhello-jni.so")).exists()
+ }
+
+ Collection<String> expectedToolchainNames = entry.getValue().collect { "gcc-" + it }
+ Collection<String> toolchainNames = model.getNativeToolchains().collect { it.getName() }
+ assertThat(toolchainNames).containsAllIn(expectedToolchainNames)
+ Collection<String> nativeLibToolchains = mainArtifact.getNativeLibraries().
+ collect { it.getToolchainName() }
+ assertThat(nativeLibToolchains).containsExactlyElementsIn(expectedToolchainNames)
+ }
+
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkPrebuiltsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkPrebuiltsTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkPrebuiltsTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkPrebuiltsTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkSanAngelesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkSanAngelesTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkSanAngelesTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NdkSanAngelesTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NoSplitNdkVariantsTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NoSplitNdkVariantsTest.groovy
new file mode 100644
index 0000000..141ec6a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/NoSplitNdkVariantsTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.ndk
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldJniApp
+import com.android.build.gradle.integration.common.utils.DeviceHelper
+import com.android.builder.core.BuilderConstants
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import java.util.zip.ZipFile
+
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertNull
+
+/**
+ * Integration test of the native plugin with multiple variants without using splits.
+ */
+ at CompileStatic
+class NoSplitNdkVariantsTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(new HelloWorldJniApp())
+ .addGradleProperties("android.useDeprecatedNdk=true")
+ .create()
+
+ @BeforeClass
+ static public void setUp() {
+ project.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ defaultConfig {
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+ buildTypes {
+ release
+ debug {
+ jniDebuggable true
+ }
+ }
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilters "armeabi-v7a", "armeabi"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
+"""
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ public void assembleX86Release() {
+ project.execute("assembleX86Release")
+
+ // Verify .so are built for all platform.
+ ZipFile apk = new ZipFile(project.getApk("x86", "release", "unsigned"))
+ assertNotNull(apk.getEntry("lib/x86/libhello-jni.so"))
+ assertNull(apk.getEntry("lib/mips/libhello-jni.so"))
+ assertNull(apk.getEntry("lib/armeabi/libhello-jni.so"))
+ assertNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"))
+ }
+
+ @Test
+ public void assembleArmRelease() {
+ project.execute("assembleArmRelease")
+
+ // Verify .so are built for all platform.
+ ZipFile apk = new ZipFile(project.getApk("arm", "release", "unsigned"))
+ assertNull(apk.getEntry("lib/x86/libhello-jni.so"))
+ assertNull(apk.getEntry("lib/mips/libhello-jni.so"))
+ assertNotNull(apk.getEntry("lib/armeabi/libhello-jni.so"))
+ assertNotNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"))
+ }
+
+ @Test
+ public void assembleMipsRelease() {
+ project.execute("assembleMipsRelease")
+
+ // Verify .so are built for all platform.
+ ZipFile apk = new ZipFile(project.getApk("mips", "release", "unsigned"))
+ assertNull(apk.getEntry("lib/x86/libhello-jni.so"))
+ assertNotNull(apk.getEntry("lib/mips/libhello-jni.so"))
+ assertNull(apk.getEntry("lib/armeabi/libhello-jni.so"))
+ assertNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"))
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connectedAndroidTest() {
+ if (GradleTestProject.DEVICE_PROVIDER_NAME.equals(BuilderConstants.CONNECTED)) {
+ Collection<String> abis = DeviceHelper.getDeviceAbis()
+ if (abis.contains("x86")) {
+ project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "x86DebugAndroidTest")
+ } else {
+ project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "ArmDebugAndroidTest")
+ }
+ } else {
+ project.execute(GradleTestProject.DEVICE_PROVIDER_NAME + "X86DebugAndroidTest")
+ }
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/Pre21SplitTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/Pre21SplitTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/Pre21SplitTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/ndk/Pre21SplitTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/AssetPackagingTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/AssetPackagingTest.groovy
new file mode 100644
index 0000000..a9b31e3
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/AssetPackagingTest.groovy
@@ -0,0 +1,542 @@
+/*
+ * 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.build.gradle.integration.packaging
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.build.gradle.integration.common.truth.TruthHelper
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+
+/**
+ * test for packaging of asset files.
+ */
+ at CompileStatic
+class AssetPackagingTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create()
+
+ private GradleTestProject appProject
+ private GradleTestProject libProject
+ private GradleTestProject libProject2
+ private GradleTestProject testProject
+
+ @Before
+ void setUp() {
+ appProject = project.getSubproject('app')
+ libProject = project.getSubproject('library')
+ libProject2 = project.getSubproject('library2')
+ testProject = project.getSubproject('test')
+
+ // rewrite settings.gradle to remove un-needed modules
+ project.settingsFile.text = """
+include 'app'
+include 'library'
+include 'library2'
+include 'test'
+"""
+
+ // setup dependencies.
+ appProject.getBuildFile() << """
+android {
+ publishNonDefault true
+}
+
+dependencies {
+ compile project(':library')
+}
+"""
+
+ libProject.getBuildFile() << """
+dependencies {
+ compile project(':library2')
+}
+"""
+
+ testProject.getBuildFile() << """
+android {
+ targetProjectPath ':app'
+ targetVariant 'debug'
+}
+"""
+
+ // put some default files in the 4 projects, to check non incremental packaging as well,
+ // and to provide files to change to test incremental support.
+ File appDir = appProject.getTestDir()
+ createOriginalAsset(appDir, "main", "file.txt", "app:abcd")
+ createOriginalAsset(appDir, "androidTest", "filetest.txt", "appTest:abcd")
+
+ File testDir = testProject.getTestDir()
+ createOriginalAsset(testDir, "main", "file.txt", "test:abcd")
+
+ File libDir = libProject.getTestDir()
+ createOriginalAsset(libDir, "main", "filelib.txt", "library:abcd")
+ createOriginalAsset(libDir, "androidTest", "filelibtest.txt", "libraryTest:abcd")
+
+ File lib2Dir = libProject2.getTestDir()
+ createOriginalAsset(lib2Dir, "main", "filelib2.txt", "library2:abcd")
+ createOriginalAsset(lib2Dir, "androidTest", "filelib2test.txt", "library2Test:abcd")
+ }
+
+ @After
+ void cleanUp() {
+ project = null
+ appProject = null
+ testProject = null
+ libProject = null
+ libProject2 = null
+ }
+
+ private static void createOriginalAsset(
+ @NonNull File projectFolder,
+ @NonNull String dimension,
+ @NonNull String filename,
+ @NonNull String content) {
+ File assetFolder = FileUtils.join(projectFolder, "src", dimension, "assets")
+ FileUtils.mkdirs(assetFolder)
+ new File(assetFolder, filename) << content;
+ }
+
+ @Test
+ void "test non incremental packaging"() {
+ project.execute("clean", "assembleDebug", "assembleAndroidTest")
+
+ // chek the files are there. Start from the bottom of the dependency graph
+ checkAar( libProject2, "filelib2.txt", "library2:abcd")
+ checkTestApk(libProject2, "filelib2.txt", "library2:abcd")
+ checkTestApk(libProject2, "filelib2test.txt", "library2Test:abcd")
+
+ checkAar( libProject, "filelib.txt", "library:abcd")
+ // aar does not contain dependency's assets
+ checkAar( libProject, "filelib2.txt", null)
+ // test apk contains both test-ony assets, lib assets, and dependency assets.
+ checkTestApk(libProject, "filelib.txt", "library:abcd")
+ checkTestApk(libProject, "filelib2.txt", "library2:abcd")
+ checkTestApk(libProject, "filelibtest.txt", "libraryTest:abcd")
+ // but not the assets of the dependency's own test
+ checkTestApk(libProject, "filelib2test.txt", null)
+
+ // app contain own assets + all dependencies' assets.
+ checkApk( appProject, "file.txt", "app:abcd")
+ checkApk( appProject, "filelib.txt", "library:abcd")
+ checkApk( appProject, "filelib2.txt", "library2:abcd")
+ checkTestApk(appProject, "filetest.txt", "appTest:abcd")
+ // app test does not contain dependencies' own test assets.
+ checkTestApk(appProject, "filelibtest.txt", null)
+ checkTestApk(appProject, "filelib2test.txt", null)
+ }
+
+ // ---- APP DEFAULT ---
+
+ @Test
+ void "test app project with new asset file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/assets/newfile.txt", "newfile content");
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "newfile.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test app project with removed asset file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/main/assets/file.txt")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", null)
+ }
+ }
+
+ @Test
+ void "test app project with modified asset file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/main/assets/file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with new debug asset file overriding main"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/debug/assets/file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "file.txt", "app:abcd")
+ }
+
+ @Test
+ void "test app project with new asset file overriding dependency"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/assets/filelib.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "filelib.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "filelib.txt", "library:abcd")
+ }
+
+ @Test
+ void "test app project with new asset file in debug source set"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/debug/assets/file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "file.txt", "app:abcd")
+ }
+
+ @Test
+ void "test app project with modified asset in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/assets/filelib.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "filelib.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with added asset in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/assets/new_lib_file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "new_lib_file.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with removed asset in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/assets/filelib.txt")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "filelib.txt", null)
+ }
+ }
+
+ // ---- APP TEST ---
+
+ @Test
+ void "test app project test with new asset file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/androidTest/assets/newfile.txt", "new file content");
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "newfile.txt", "new file content")
+ }
+ }
+
+ @Test
+ void "test app project test with removed asset file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/androidTest/assets/filetest.txt")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "filetest.txt", null)
+ }
+ }
+
+ @Test
+ void "test app project test with modified asset file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/androidTest/assets/filetest.txt", "new content")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "filetest.txt", "new content")
+ }
+ }
+
+ // ---- LIB DEFAULT ---
+
+ @Test
+ void "test lib project with new asset file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/assets/newfile.txt", "newfile content");
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "newfile.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test lib project with removed asset file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/assets/filelib.txt")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "filelib.txt", null)
+ }
+ }
+
+ @Test
+ void "test lib project with modified asset file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/assets/filelib.txt", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "filelib.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project with new asset file in debug source set"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/debug/assets/filelib.txt", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "filelib.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleDebug")
+ checkAar(libProject, "filelib.txt", "library:abcd")
+ }
+
+ // ---- LIB TEST ---
+
+ @Test
+ void "test lib project test with new asset file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/assets/newfile.txt", "new file content");
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "newfile.txt", "new file content")
+ }
+ }
+
+ @Test
+ void "test lib project test with removed asset file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/androidTest/assets/filelibtest.txt")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelibtest.txt", null)
+ }
+ }
+
+ @Test
+ void "test lib project test with modified asset file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/androidTest/assets/filelibtest.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelibtest.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project test with new asset file overriding tested lib"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/assets/filelib.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelib.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleAT")
+ checkTestApk(libProject, "filelib.txt", "library:abcd")
+
+ }
+
+ @Test
+ void "test lib project test with new asset file overriding dependency"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/assets/filelib2.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelib2.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleAT")
+ checkTestApk(libProject, "filelib2.txt", "library2:abcd")
+ }
+
+ // ---- TEST DEFAULT ---
+
+ @Test
+ void "test test-project with new asset file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.addFile("src/main/assets/newfile.txt", "newfile content");
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "newfile.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test test-project with removed asset file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.removeFile("src/main/assets/file.txt")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "file.txt", null)
+ }
+ }
+
+ @Test
+ void "test test-project with modified asset file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.replaceFile("src/main/assets/file.txt", "new content")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "file.txt", "new content")
+ }
+ }
+
+ // -----------------------
+
+ /**
+ * check an apk has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getApk("debug")), filename, content)
+ }
+
+ /**
+ * check a test apk has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkTestApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getTestApk("debug")), filename, content)
+ }
+
+ /**
+ * check an aat has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkAar(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatAar(project.getAar("debug")), filename, content)
+ }
+
+ private static void check(
+ @NonNull AbstractAndroidSubject subject,
+ @NonNull String filename,
+ @Nullable String content) {
+ if (content != null) {
+ subject.containsFileWithContent("assets/" + filename, content)
+ } else {
+ subject.doesNotContain("assets/" + filename)
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/IncrementalCodeChangeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/IncrementalCodeChangeTest.groovy
new file mode 100644
index 0000000..0099fec
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/IncrementalCodeChangeTest.groovy
@@ -0,0 +1,155 @@
+/*
+ * 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.build.gradle.integration.packaging
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import com.google.common.base.Charsets
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+
+/**
+ * test for incremental code change.
+ *
+ * It's a simple two project setup, with an app and a library. Only the library
+ * gets changed and after the compilation of the app, we check code from both app and library
+ * is present in the dex file of the app.
+ *
+ * 3 cases:
+ * - no multi-dex
+ * - native multi-dex
+ * - legacy multi-dex
+ */
+ at CompileStatic
+class IncrementalCodeChangeTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create()
+
+ @Before
+ void setUp() {
+ }
+
+ @After
+ void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "check non-multi-dex"() {
+ Files.write("include 'app', 'library'", project.getSettingsFile(), Charsets.UTF_8);
+ project.getSubproject('app').getBuildFile() << """
+
+dependencies {
+ compile project(':library')
+}
+"""
+ project.execute("clean", ":app:assembleDebug")
+
+ TestFileUtils.replaceLine(
+ project.file("library/src/main/java/com/example/android/multiproject/library/PersonView.java"),
+ 9,
+ " setTextSize(30);")
+
+ project.execute(":app:assembleDebug")
+
+ // class from :library
+ assertThatApk(project.getSubproject('app').getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/library/PersonView;")
+
+ // class from :app
+ assertThatApk(project.getSubproject('app').getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/MainActivity;")
+ }
+
+ @Test
+ void "check legacy multi-dex"() {
+ Files.write("include 'app', 'library'", project.getSettingsFile(), Charsets.UTF_8);
+ project.getSubproject('app').getBuildFile() << """
+
+android {
+ defaultConfig {
+ multiDexEnabled = true
+ }
+}
+dependencies {
+ compile project(':library')
+}
+"""
+ project.execute("clean", ":app:assembleDebug")
+
+ TestFileUtils.replaceLine(
+ project.file("library/src/main/java/com/example/android/multiproject/library/PersonView.java"),
+ 9,
+ " setTextSize(30);")
+
+ project.execute(":app:assembleDebug")
+
+ // class from :library
+ assertThatApk(project.getSubproject('app').getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/library/PersonView;")
+
+ // class from :app
+ assertThatApk(project.getSubproject('app').getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/MainActivity;")
+
+ // class from legacy multi-dex lib
+ assertThatApk(project.getSubproject('app').getApk("debug"))
+ .containsClass("Landroid/support/multidex/MultiDex;")
+ }
+
+ @Test
+ void "check native multi-dex"() {
+ Files.write("include 'app', 'library'", project.getSettingsFile(), Charsets.UTF_8);
+ project.getSubproject('app').getBuildFile() << """
+
+android {
+ defaultConfig {
+ minSdkVersion 21
+ multiDexEnabled = true
+ }
+}
+dependencies {
+ compile project(':library')
+}
+"""
+ project.execute("clean", ":app:assembleDebug")
+
+ TestFileUtils.replaceLine(
+ project.file("library/src/main/java/com/example/android/multiproject/library/PersonView.java"),
+ 9,
+ " setTextSize(30);")
+
+ project.execute(":app:assembleDebug")
+
+ // class from :library
+ assertThatApk(project.getSubproject('app').getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/library/PersonView;")
+
+ // class from :app
+ assertThatApk(project.getSubproject('app').getApk("debug"))
+ .containsClass("Lcom/example/android/multiproject/MainActivity;")
+ }
+
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/JavaResPackagingTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/JavaResPackagingTest.groovy
new file mode 100644
index 0000000..19848fa
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/JavaResPackagingTest.groovy
@@ -0,0 +1,554 @@
+/*
+ * 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.build.gradle.integration.packaging
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+
+/**
+ * test for packaging of java resources.
+ */
+ at CompileStatic
+class JavaResPackagingTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create()
+
+ private GradleTestProject appProject
+ private GradleTestProject libProject
+ private GradleTestProject libProject2
+ private GradleTestProject testProject
+ private GradleTestProject jarProject
+
+ @Before
+ void setUp() {
+ appProject = project.getSubproject('app')
+ libProject = project.getSubproject('library')
+ libProject2 = project.getSubproject('library2')
+ testProject = project.getSubproject('test')
+ jarProject = project.getSubproject('jar')
+
+ // rewrite settings.gradle to remove un-needed modules
+ project.settingsFile.text = """
+include 'app'
+include 'library'
+include 'library2'
+include 'test'
+include 'jar'
+"""
+
+ // setup dependencies.
+ appProject.getBuildFile() << """
+android {
+ publishNonDefault true
+}
+
+dependencies {
+ compile project(':library')
+ compile project(':jar')
+}
+"""
+
+ libProject.getBuildFile() << """
+dependencies {
+ compile project(':library2')
+}
+"""
+
+ testProject.getBuildFile() << """
+android {
+ targetProjectPath ':app'
+ targetVariant 'debug'
+}
+"""
+
+ // put some default files in the 4 projects, to check non incremental packaging as well,
+ // and to provide files to change to test incremental support.
+ File appDir = appProject.getTestDir()
+ createOriginalResFile(appDir, "main", "app.txt", "app:abcd")
+ createOriginalResFile(appDir, "androidTest", "apptest.txt", "appTest:abcd")
+
+ File testDir = testProject.getTestDir()
+ createOriginalResFile(testDir, "main", "test.txt", "test:abcd")
+
+ File libDir = libProject.getTestDir()
+ createOriginalResFile(libDir, "main", "library.txt", "library:abcd")
+ createOriginalResFile(libDir, "androidTest", "librarytest.txt", "libraryTest:abcd")
+
+ File lib2Dir = libProject2.getTestDir()
+ createOriginalResFile(lib2Dir, "main", "library2.txt", "library2:abcd")
+ createOriginalResFile(lib2Dir, "androidTest", "library2test.txt", "library2Test:abcd")
+
+ File jarDir = jarProject.getTestDir()
+ File resFolder = FileUtils.join(jarDir, "src", "main", "resources", "com", "foo")
+ FileUtils.mkdirs(resFolder)
+ new File(resFolder, "jar.txt") << "jar:abcd";
+ }
+
+ @After
+ void cleanUp() {
+ project = null
+ appProject = null
+ testProject = null
+ libProject = null
+ libProject2 = null
+ jarProject = null
+ }
+
+ private static void createOriginalResFile(
+ @NonNull File projectFolder,
+ @NonNull String dimension,
+ @NonNull String filename,
+ @NonNull String content) {
+ File assetFolder = FileUtils.join(projectFolder, "src", dimension, "resources", "com", "foo")
+ FileUtils.mkdirs(assetFolder)
+ new File(assetFolder, filename) << content;
+ }
+
+ @Test
+ void "test non incremental packaging"() {
+ project.execute("clean", "assembleDebug", "assembleAndroidTest")
+
+ // chek the files are there. Start from the bottom of the dependency graph
+ checkAar( libProject2, "library2.txt", "library2:abcd")
+ checkTestApk(libProject2, "library2.txt", "library2:abcd")
+ checkTestApk(libProject2, "library2test.txt", "library2Test:abcd")
+
+ checkAar( libProject, "library.txt", "library:abcd")
+ // aar does not contain dependency's assets
+ checkAar( libProject, "library2.txt", null)
+ // test apk contains both test-ony assets, lib assets, and dependency assets.
+ checkTestApk(libProject, "library.txt", "library:abcd")
+ checkTestApk(libProject, "library2.txt", "library2:abcd")
+ checkTestApk(libProject, "librarytest.txt", "libraryTest:abcd")
+ // but not the assets of the dependency's own test
+ checkTestApk(libProject, "library2test.txt", null)
+
+ // app contain own assets + all dependencies' assets.
+ checkApk( appProject, "app.txt", "app:abcd")
+ checkApk( appProject, "library.txt", "library:abcd")
+ checkApk( appProject, "library2.txt", "library2:abcd")
+ checkApk( appProject, "jar.txt", "jar:abcd")
+ checkTestApk(appProject, "apptest.txt", "appTest:abcd")
+ // app test does not contain dependencies' own test assets.
+ checkTestApk(appProject, "librarytest.txt", null)
+ checkTestApk(appProject, "library2test.txt", null)
+ }
+
+ // ---- APP DEFAULT ---
+
+ @Test
+ void "test app project with new res file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/resources/com/foo/newapp.txt", "newfile content");
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "newapp.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test app project with removed res file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/main/resources/com/foo/app.txt")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "app.txt", null)
+ }
+ }
+
+ @Test
+ void "test app project with modified res file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/main/resources/com/foo/app.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "app.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with new debug res file overriding main"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/debug/resources/com/foo/app.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "app.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "app.txt", "app:abcd")
+ }
+
+ @Test
+ void "test app project with new res file overriding dependency"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/resources/com/foo/library.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "library.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "library.txt", "library:abcd")
+
+ }
+
+ @Test
+ void "test app project with new res file in debug source set"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/debug/resources/com/foo/app.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "app.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "app.txt", "app:abcd")
+ }
+
+ @Test
+ void "test app project with modified res in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/resources/com/foo/library.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "library.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with added res in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/resources/com/foo/newlibrary.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "newlibrary.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with removed res in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/resources/com/foo/library.txt")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "library.txt", null)
+ }
+ }
+
+ // ---- APP TEST ---
+
+ @Test
+ void "test app project test with new res file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/androidTest/resources/com/foo/newapp.txt", "new file content");
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "newapp.txt", "new file content")
+ }
+ }
+
+ @Test
+ void "test app project test with removed res file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/androidTest/resources/com/foo/apptest.txt")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "apptest.txt", null)
+ }
+ }
+
+ @Test
+ void "test app project test with modified res file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/androidTest/resources/com/foo/apptest.txt", "new content")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "apptest.txt", "new content")
+ }
+ }
+
+ // ---- LIB DEFAULT ---
+
+ @Test
+ void "test lib project with new res file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/resources/com/foo/newlibrary.txt", "newfile content");
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "newlibrary.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test lib project with removed res file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/resources/com/foo/library.txt")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "library.txt", null)
+ }
+ }
+
+ @Test
+ void "test lib project with modified res file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/resources/com/foo/library.txt", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "library.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project with new res file in debug source set"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/debug/resources/com/foo/library.txt", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "library.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleDebug")
+ checkAar(libProject, "library.txt", "library:abcd")
+
+ }
+
+ // ---- LIB TEST ---
+
+ @Test
+ void "test lib project test with new res file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/resources/com/foo/newlibrary.txt", "new file content");
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "newlibrary.txt", "new file content")
+ }
+ }
+
+ @Test
+ void "test lib project test with removed res file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/androidTest/resources/com/foo/librarytest.txt")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "librarytest.txt", null)
+ }
+ }
+
+ @Test
+ void "test lib project test with modified res file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/androidTest/resources/com/foo/librarytest.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "librarytest.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project test with new res file overriding tested lib"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/resources/com/foo/library.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "library.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleAT")
+ checkTestApk(libProject, "library.txt", "library:abcd")
+ }
+
+ @Test
+ void "test lib project test with new res file overriding dependency"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/resources/com/foo/library2.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "library2.txt", "new content")
+
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleAT")
+ checkTestApk(libProject, "library2.txt", "library2:abcd")
+ }
+
+ // ---- TEST DEFAULT ---
+
+ @Test
+ void "test test-project with new res file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.addFile("src/main/resources/com/foo/newtest.txt", "newfile content");
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "newtest.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test test-project with removed res file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.removeFile("src/main/resources/com/foo/test.txt")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "test.txt", null)
+ }
+ }
+
+ @Test
+ void "test test-project with modified res file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.replaceFile("src/main/resources/com/foo/test.txt", "new content")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "test.txt", "new content")
+ }
+ }
+
+ // --------------------------------
+
+ /**
+ * check an apk has (or not) the given res file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getApk("debug")), filename, content)
+ }
+
+ /**
+ * check a test apk has (or not) the given res file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkTestApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getTestApk("debug")), filename, content)
+ }
+
+ /**
+ * check an aat has (or not) the given res file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkAar(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatAar(project.getAar("debug")), filename, content)
+ }
+
+ private static void check(
+ @NonNull AbstractAndroidSubject subject,
+ @NonNull String filename,
+ @Nullable String content) {
+ if (content != null) {
+ subject.containsJavaResourceWithContent("com/foo/" + filename, content)
+ } else {
+ subject.doesNotContainJavaResource("com/foo/" + filename)
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/NativeSoPackagingFromRemoteAarTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/NativeSoPackagingFromRemoteAarTest.groovy
new file mode 100644
index 0000000..deffa11
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/NativeSoPackagingFromRemoteAarTest.groovy
@@ -0,0 +1,217 @@
+/*
+ * 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.build.gradle.integration.packaging
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.utils.FileUtils
+import com.google.common.base.Charsets
+import com.google.common.collect.ImmutableList
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * test for packaging of asset files.
+ */
+ at CompileStatic
+class NativeSoPackagingFromRemoteAarTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create()
+
+ private GradleTestProject appProject
+ private GradleTestProject libProject
+
+ @Before
+ void setUp() {
+ appProject = project.getSubproject('app')
+ libProject = project.getSubproject('library')
+
+ // rewrite settings.gradle to remove un-needed modules
+ Files.write("""
+include 'app'
+include 'library'
+""", new File(project.getTestDir(), "settings.gradle"), Charsets.UTF_8)
+
+ // setup dependencies.
+ appProject.getBuildFile() << """
+repositories {
+ maven { url '../testrepo' }
+}
+dependencies {
+ compile 'com.example.android.nativepackaging:library:1.0'
+}
+"""
+
+ libProject.getBuildFile() << """
+apply plugin: 'maven'
+
+group = 'com.example.android.nativepackaging'
+archivesBaseName = 'library'
+version = '1.0'
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri("../testrepo"))
+ }
+ }
+}
+
+"""
+
+ // put some default files in the library project, to check non incremental packaging
+ // as well, and to provide files to change to test incremental support.
+ File libDir = libProject.getTestDir()
+ createOriginalSoFile(libDir, "main", "liblibrary.so", "library:abcd")
+ createOriginalSoFile(libDir, "main", "liblibrary2.so", "library2:abcdef")
+
+ // build and deploy the library
+ project.execute(ImmutableList.of("--configure-on-demand"), "library:clean", "library:uploadArchives" )
+ project.execute("app:clean", "app:assembleDebug")
+ }
+
+ private static void createOriginalSoFile(
+ @NonNull File projectFolder,
+ @NonNull String dimension,
+ @NonNull String filename,
+ @NonNull String content) {
+ File assetFolder = FileUtils.join(projectFolder, "src", dimension, "jniLibs", "x86")
+ FileUtils.mkdirs(assetFolder)
+ new File(assetFolder, filename) << content;
+ }
+
+ @Test
+ void "test non incremental packaging"() {
+ checkApk(appProject, "liblibrary.so", "library:abcd")
+ checkApk(appProject, "liblibrary2.so", "library2:abcdef")
+ }
+
+ @Test
+ void "test app project with new so in aar"() {
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/jniLibs/x86/libnewapp.so", "newfile content");
+ // must be two calls as it's a single project that includes both modules and
+ // dependency is resolved at evaluation time, before the library published its new
+ // versions.
+ project.execute("library:uploadArchives")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "libnewapp.so", "newfile content")
+ }
+ }
+
+ @Test
+ void "test app project with removed so in aar"() {
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/jniLibs/x86/liblibrary2.so");
+ // must be two calls as it's a single project that includes both modules and
+ // dependency is resolved at evaluation time, before the library published its new
+ // versions.
+ project.execute("library:uploadArchives")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "liblibrary2.so", null)
+ }
+ }
+
+ @Test
+ void "test app project with edited so in aar"() {
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/jniLibs/x86/liblibrary2.so", "new content");
+ // must be two calls as it's a single project that includes both modules and
+ // dependency is resolved at evaluation time, before the library published its new
+ // versions.
+ project.execute("library:uploadArchives")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "liblibrary2.so", "new content")
+ }
+ }
+
+ /**
+ * check an apk has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getApk("debug")), "lib", filename, content)
+ }
+
+ /**
+ * check a test apk has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkTestApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getTestApk("debug")), "lib", filename, content)
+ }
+
+ /**
+ * check an aat has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkAar(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatAar(project.getAar("debug")), "jni", filename, content)
+ }
+
+ private static void check(
+ @NonNull AbstractAndroidSubject subject,
+ @NonNull String folderName,
+ @NonNull String filename,
+ @Nullable String content) {
+ if (content != null) {
+ subject.containsFileWithContent(folderName + "/x86/" + filename, content)
+ } else {
+ subject.doesNotContain(folderName + "/x86/" + filename)
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/NativeSoPackagingTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/NativeSoPackagingTest.groovy
new file mode 100644
index 0000000..c1b4530
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/NativeSoPackagingTest.groovy
@@ -0,0 +1,533 @@
+/*
+ * 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.build.gradle.integration.packaging
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.utils.FileUtils
+import com.google.common.base.Charsets
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatAar
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * test for packaging of asset files.
+ */
+ at CompileStatic
+class NativeSoPackagingTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create()
+
+ private GradleTestProject appProject
+ private GradleTestProject libProject
+ private GradleTestProject libProject2
+ private GradleTestProject testProject
+ private GradleTestProject jarProject
+
+ @Before
+ void setUp() {
+ appProject = project.getSubproject('app')
+ libProject = project.getSubproject('library')
+ libProject2 = project.getSubproject('library2')
+ testProject = project.getSubproject('test')
+ jarProject = project.getSubproject('jar')
+
+ // rewrite settings.gradle to remove un-needed modules
+ Files.write("""
+include 'app'
+include 'library'
+include 'library2'
+include 'test'
+include 'jar'
+""", new File(project.getTestDir(), "settings.gradle"), Charsets.UTF_8)
+
+ // setup dependencies.
+ appProject.getBuildFile() << """
+android {
+ publishNonDefault true
+}
+
+dependencies {
+ compile project(':library')
+ compile project(':jar')
+}
+"""
+
+ libProject.getBuildFile() << """
+dependencies {
+ compile project(':library2')
+}
+"""
+
+ testProject.getBuildFile() << """
+android {
+ targetProjectPath ':app'
+ targetVariant 'debug'
+}
+"""
+
+ // put some default files in the 4 projects, to check non incremental packaging as well,
+ // and to provide files to change to test incremental support.
+ File appDir = appProject.getTestDir()
+ createOriginalSoFile(appDir, "main", "libapp.so", "app:abcd")
+ createOriginalSoFile(appDir, "androidTest", "libapptest.so", "appTest:abcd")
+
+ File testDir = testProject.getTestDir()
+ createOriginalSoFile(testDir, "main", "libtest.so", "test:abcd")
+
+ File libDir = libProject.getTestDir()
+ createOriginalSoFile(libDir, "main", "liblibrary.so", "library:abcd")
+ createOriginalSoFile(libDir, "androidTest", "liblibrarytest.so", "libraryTest:abcd")
+
+ File lib2Dir = libProject2.getTestDir()
+ createOriginalSoFile(lib2Dir, "main", "liblibrary2.so", "library2:abcd")
+ createOriginalSoFile(lib2Dir, "androidTest", "liblibrary2test.so", "library2Test:abcd")
+
+ File jarDir = jarProject.getTestDir()
+ File resFolder = FileUtils.join(jarDir, "src", "main", "resources", "lib", "x86")
+ FileUtils.mkdirs(resFolder)
+ new File(resFolder, "libjar.so") << "jar:abcd";
+ }
+
+ private static void createOriginalSoFile(
+ @NonNull File projectFolder,
+ @NonNull String dimension,
+ @NonNull String filename,
+ @NonNull String content) {
+ File assetFolder = FileUtils.join(projectFolder, "src", dimension, "jniLibs", "x86")
+ FileUtils.mkdirs(assetFolder)
+ new File(assetFolder, filename) << content;
+ }
+
+ @Test
+ void "test non incremental packaging"() {
+ project.execute("clean", "assembleDebug", "assembleAndroidTest")
+
+ // check the files are there. Start from the bottom of the dependency graph
+ checkAar( libProject2, "liblibrary2.so", "library2:abcd")
+ checkTestApk(libProject2, "liblibrary2.so", "library2:abcd")
+ checkTestApk(libProject2, "liblibrary2test.so", "library2Test:abcd")
+
+ checkAar( libProject, "liblibrary.so", "library:abcd")
+ // aar does not contain dependency's assets
+ checkAar( libProject, "liblibrary2.so", null)
+ // test apk contains both test-ony assets, lib assets, and dependency assets.
+ checkTestApk(libProject, "liblibrary.so", "library:abcd")
+ checkTestApk(libProject, "liblibrary2.so", "library2:abcd")
+ checkTestApk(libProject, "liblibrarytest.so", "libraryTest:abcd")
+ // but not the assets of the dependency's own test
+ checkTestApk(libProject, "liblibrary2test.so", null)
+
+ // app contain own assets + all dependencies' assets.
+ checkApk( appProject, "libapp.so", "app:abcd")
+ checkApk( appProject, "liblibrary.so", "library:abcd")
+ checkApk( appProject, "liblibrary2.so", "library2:abcd")
+ checkApk( appProject, "libjar.so", "jar:abcd")
+ checkTestApk(appProject, "libapptest.so", "appTest:abcd")
+ // app test does not contain dependencies' own test assets.
+ checkTestApk(appProject, "liblibrarytest.so", null)
+ checkTestApk(appProject, "liblibrary2test.so", null)
+ }
+
+ // ---- APP DEFAULT ---
+
+ @Test
+ void "test app project with new asset file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/jniLibs/x86/libnewapp.so", "newfile content");
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "libnewapp.so", "newfile content")
+ }
+ }
+
+ @Test
+ void "test app project with removed asset file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/main/jniLibs/x86/libapp.so")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "libapp.so", null)
+ }
+ }
+
+ @Test
+ void "test app project with modified asset file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/main/jniLibs/x86/libapp.so", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "libapp.so", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with new asset file overriding dependency"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/jniLibs/x86/liblibrary.so", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "liblibrary.so", "new content")
+
+ // now remove it to test it works in the other direction
+ it.removeFile("src/main/jniLibs/x86/liblibrary.so")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "liblibrary.so", "library:abcd")
+ }
+ }
+
+ @Test
+ void "test app project with new asset file in debug source set"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/debug/jniLibs/x86/libapp.so", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "libapp.so", "new content")
+
+ // now remove it to test it works in the other direction
+ it.removeFile("src/debug/jniLibs/x86/libapp.so")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "libapp.so", "app:abcd")
+ }
+ }
+
+ @Test
+ void "test app project with modified asset in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/jniLibs/x86/liblibrary.so", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "liblibrary.so", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with added asset in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/jniLibs/x86/libnewlibrary.so", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "libnewlibrary.so", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with removed asset in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/jniLibs/x86/liblibrary.so")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "liblibrary.so", null)
+ }
+ }
+
+ // ---- APP TEST ---
+
+ @Test
+ void "test app project test with new asset file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/androidTest/jniLibs/x86/libnewapp.so", "new file content");
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "libnewapp.so", "new file content")
+ }
+ }
+
+ @Test
+ void "test app project test with removed asset file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/androidTest/jniLibs/x86/libapptest.so")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "libapptest.so", null)
+ }
+ }
+
+ @Test
+ void "test app project test with modified asset file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/androidTest/jniLibs/x86/libapptest.so", "new content")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "libapptest.so", "new content")
+ }
+ }
+
+ // ---- LIB DEFAULT ---
+
+ @Test
+ void "test lib project with new asset file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/jniLibs/x86/libnewlibrary.so", "newfile content");
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "libnewlibrary.so", "newfile content")
+ }
+ }
+
+ @Test
+ void "test lib project with removed asset file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/jniLibs/x86/liblibrary.so")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "liblibrary.so", null)
+ }
+ }
+
+ @Test
+ void "test lib project with modified asset file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/jniLibs/x86/liblibrary.so", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "liblibrary.so", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project with new asset file in debug source set"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/debug/jniLibs/x86/liblibrary.so", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "liblibrary.so", "new content")
+
+ // now remove it to test it works in the other direction
+ it.removeFile("src/debug/jniLibs/x86/liblibrary.so")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "liblibrary.so", "library:abcd")
+ }
+ }
+
+ // ---- LIB TEST ---
+
+ @Test
+ void "test lib project test with new asset file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/jniLibs/x86/libnewlibrary.so", "new file content");
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "libnewlibrary.so", "new file content")
+ }
+ }
+
+ @Test
+ void "test lib project test with removed asset file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/androidTest/jniLibs/x86/liblibrarytest.so")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "liblibrarytest.so", null)
+ }
+ }
+
+ @Test
+ void "test lib project test with modified asset file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/androidTest/jniLibs/x86/liblibrarytest.so", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "liblibrarytest.so", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project test with new asset file overriding tested lib"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/jniLibs/x86/liblibrary.so", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "liblibrary.so", "new content")
+
+ // now remove it to test it works in the other direction
+ it.removeFile("src/androidTest/jniLibs/x86/liblibrary.so")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "liblibrary.so", "library:abcd")
+ }
+ }
+
+ @Test
+ void "test lib project test with new asset file overriding dependency"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/jniLibs/x86/liblibrary2.so", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "liblibrary2.so", "new content")
+
+ // now remove it to test it works in the other direction
+ it.removeFile("src/androidTest/jniLibs/x86/liblibrary2.so")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "liblibrary2.so", "library2:abcd")
+ }
+ }
+
+ // ---- TEST DEFAULT ---
+
+ @Test
+ void "test test-project with new asset file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.addFile("src/main/jniLibs/x86/libnewtest.so", "newfile content");
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "libnewtest.so", "newfile content")
+ }
+ }
+
+ @Test
+ void "test test-project with removed asset file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.removeFile("src/main/jniLibs/x86/libtest.so")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "libtest.so", null)
+ }
+ }
+
+ @Test
+ void "test test-project with modified asset file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.replaceFile("src/main/jniLibs/x86/libtest.so", "new content")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "libtest.so", "new content")
+ }
+ }
+
+ /**
+ * check an apk has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getApk("debug")), "lib", filename, content)
+ }
+
+ /**
+ * check a test apk has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkTestApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getTestApk("debug")), "lib", filename, content)
+ }
+
+ /**
+ * check an aat has (or not) the given asset file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkAar(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatAar(project.getAar("debug")), "jni", filename, content)
+ }
+
+ private static void check(
+ @NonNull AbstractAndroidSubject subject,
+ @NonNull String folderName,
+ @NonNull String filename,
+ @Nullable String content) {
+ if (content != null) {
+ subject.containsFileWithContent(folderName + "/x86/" + filename, content)
+ } else {
+ subject.doesNotContain(folderName + "/x86/" + filename)
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/ResPackagingTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/ResPackagingTest.groovy
new file mode 100644
index 0000000..ec89c57
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/packaging/ResPackagingTest.groovy
@@ -0,0 +1,770 @@
+/*
+ * 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.build.gradle.integration.packaging
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TemporaryProjectModification
+import com.android.build.gradle.integration.common.truth.AbstractAndroidSubject
+import com.android.utils.FileUtils
+import com.google.common.io.Files
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import java.nio.charset.Charset
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * test for packaging of android asset files.
+ *
+ * This only uses raw files. This is not about running aapt tests, this is only about
+ * everything around it, so raw files are easier to test in isolation.
+ */
+ at CompileStatic
+class ResPackagingTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("projectWithModules")
+ .create()
+
+ private GradleTestProject appProject
+ private GradleTestProject libProject
+ private GradleTestProject libProject2
+ private GradleTestProject testProject
+
+ @Before
+ void setUp() {
+ appProject = project.getSubproject('app')
+ libProject = project.getSubproject('library')
+ libProject2 = project.getSubproject('library2')
+ testProject = project.getSubproject('test')
+
+ // rewrite settings.gradle to remove un-needed modules
+ project.settingsFile.text = """
+include 'app'
+include 'library'
+include 'library2'
+include 'test'
+"""
+
+ // setup dependencies.
+ appProject.getBuildFile() << """
+android {
+ publishNonDefault true
+}
+
+dependencies {
+ compile project(':library')
+}
+"""
+
+ libProject.getBuildFile() << """
+dependencies {
+ compile project(':library2')
+}
+"""
+
+ testProject.getBuildFile() << """
+android {
+ targetProjectPath ':app'
+ targetVariant 'debug'
+}
+"""
+
+ // put some default files in the 4 projects, to check non incremental packaging as well,
+ // and to provide files to change to test incremental support.
+ File appDir = appProject.getTestDir()
+ createOriginalResFile(appDir, "main", "file.txt", "app:abcd")
+ createOriginalResFile(appDir, "androidTest", "filetest.txt", "appTest:abcd")
+
+ File testDir = testProject.getTestDir()
+ createOriginalResFile(testDir, "main", "file.txt", "test:abcd")
+
+ File libDir = libProject.getTestDir()
+ createOriginalResFile(libDir, "main", "filelib.txt", "library:abcd")
+ createOriginalResFile(libDir, "androidTest", "filelibtest.txt", "libraryTest:abcd")
+
+ File lib2Dir = libProject2.getTestDir()
+ createOriginalResFile(lib2Dir, "main", "filelib2.txt", "library2:abcd")
+ createOriginalResFile(lib2Dir, "androidTest", "filelib2test.txt", "library2Test:abcd")
+ }
+
+ @After
+ void cleanUp() {
+ project = null
+ appProject = null
+ testProject = null
+ libProject = null
+ libProject2 = null
+ }
+
+ private static void createOriginalResFile(
+ @NonNull File projectFolder,
+ @NonNull String dimension,
+ @NonNull String filename,
+ @NonNull String content) {
+ File assetFolder = FileUtils.join(projectFolder, "src", dimension, "res", "raw")
+ FileUtils.mkdirs(assetFolder)
+ new File(assetFolder, filename) << content;
+ }
+
+ @Test
+ void "test non incremental packaging"() {
+ project.execute("clean", "assembleDebug", "assembleAndroidTest")
+
+ // chek the files are there. Start from the bottom of the dependency graph
+ checkAar( libProject2, "filelib2.txt", "library2:abcd")
+ checkTestApk(libProject2, "filelib2.txt", "library2:abcd")
+ checkTestApk(libProject2, "filelib2test.txt", "library2Test:abcd")
+
+ checkAar( libProject, "filelib.txt", "library:abcd")
+ // aar does not contain dependency's assets
+ checkAar( libProject, "filelib2.txt", null)
+ // test apk contains both test-ony assets, lib assets, and dependency assets.
+ checkTestApk(libProject, "filelib.txt", "library:abcd")
+ checkTestApk(libProject, "filelib2.txt", "library2:abcd")
+ checkTestApk(libProject, "filelibtest.txt", "libraryTest:abcd")
+ // but not the assets of the dependency's own test
+ checkTestApk(libProject, "filelib2test.txt", null)
+
+ // app contain own assets + all dependencies' assets.
+ checkApk( appProject, "file.txt", "app:abcd")
+ checkApk( appProject, "filelib.txt", "library:abcd")
+ checkApk( appProject, "filelib2.txt", "library2:abcd")
+ checkTestApk(appProject, "filetest.txt", "appTest:abcd")
+ // app test does not contain dependencies' own test assets.
+ checkTestApk(appProject, "filelibtest.txt", null)
+ checkTestApk(appProject, "filelib2test.txt", null)
+ }
+
+ // ---- APP DEFAULT ---
+
+ @Test
+ void "test app project with new res file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/res/raw/newfile.txt", "newfile content");
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "newfile.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test app project with removed res file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/main/res/raw/file.txt")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", null)
+ }
+ }
+
+ @Test
+ void "test app project with modified res file"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/main/res/raw/file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with new debug res file overriding main"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/debug/res/raw/file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "file.txt", "app:abcd")
+ }
+
+ @Test
+ void "test app project with new res file overriding dependency"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/main/res/raw/filelib.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "filelib.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "filelib.txt", "library:abcd")
+ }
+
+ @Test
+ void "test app project with new res file in debug source set"() {
+ project.execute("app:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/debug/res/raw/file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "file.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("app:assembleDebug")
+ checkApk(appProject, "file.txt", "app:abcd")
+ }
+
+ @Test
+ void "test app project with modified res in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/res/raw/filelib.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "filelib.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with added res in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/res/raw/new_lib_file.txt", "new content")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "new_lib_file.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test app project with removed res in dependency"() {
+ project.execute("app:clean", "library:clean", "app:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/res/raw/filelib.txt")
+ project.execute("app:assembleDebug")
+
+ checkApk(appProject, "filelib.txt", null)
+ }
+ }
+
+ @Test
+ void "test app resources are filtered by min sdk full"() {
+ // Here are which files go into where:
+ // (none) v14 v16
+ // f1
+ // f2 f2
+ // f3 f3 f3
+ // f4 f4
+ // f5
+ //
+ // If we build without minSdk defined, we should get everything exactly as shown.
+ //
+ // If we build with minSdkVersion = 14 we should end up with:
+ // (none) v14 v16
+ // f1
+ // f2
+ // f3 f3
+ // f4 f4
+ // f5
+ //
+ // If we build with minSdkVersion = 16 we should end up with:
+ // (none) v14 v16
+ // f1
+ // f2
+ // f3
+ // f4
+ // f5
+
+ File raw = appProject.file("src/main/res/raw")
+ raw.mkdirs()
+
+ File raw14 = appProject.file("src/main/res/raw-v14")
+ raw14.mkdirs()
+
+ File raw16 = appProject.file("src/main/res/raw-v16")
+ raw16.mkdirs()
+
+ byte[] f1NoneC = [ 0 ] as byte[]
+ byte[] f2NoneC = [ 1 ] as byte[]
+ byte[] f2v14C = [ 2 ] as byte[]
+ byte[] f3NoneC = [ 3 ] as byte[]
+ byte[] f3v14C = [ 4 ] as byte[]
+ byte[] f3v16C = [ 5 ] as byte[]
+ byte[] f4v14C = [ 6 ] as byte[]
+ byte[] f4v16C = [ 7 ] as byte[]
+ byte[] f5v16C = [ 8 ] as byte[]
+
+ File f1None = new File(raw, "f1")
+ Files.write(f1NoneC, f1None)
+
+ File f2None = new File(raw, "f2")
+ Files.write(f2NoneC, f2None)
+
+ File f2v14 = new File(raw14, "f2")
+ Files.write(f2v14C, f2v14);
+
+ File f3None = new File(raw, "f3")
+ Files.write(f3NoneC, f3None)
+
+ File f3v14 = new File(raw14, "f3")
+ Files.write(f3v14C, f3v14)
+
+ File f3v16 = new File(raw16, "f3")
+ Files.write(f3v16C, f3v16)
+
+ File f4v14 = new File(raw14, "f4")
+ Files.write(f4v14C, f4v14)
+
+ File f4v16 = new File(raw16, "f4")
+ Files.write(f4v16C, f4v16)
+
+ File f5v16 = new File(raw16, "f5")
+ Files.write(f5v16C, f5v16)
+
+
+ File appGradleFile = appProject.file("build.gradle")
+ String appGradleFileContents = Files.toString(appGradleFile, Charset.defaultCharset())
+
+ // Set no min SDK version and generate the APK.
+ String newBuild = appGradleFileContents.replaceAll("minSdkVersion 8", "")
+ Files.write(newBuild, appGradleFile, Charset.defaultCharset())
+ project.execute("clean", ":app:assembleDebug")
+
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f1", f1NoneC)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f2", f2NoneC)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f3", f3NoneC)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f2", f2v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f3", f3v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f4", f4v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f3", f3v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f4", f4v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f5", f5v16C)
+
+ // Set min SDK version 14 and generate the APK.
+ newBuild = appGradleFileContents.replaceAll("minSdkVersion 8", "minSdkVersion 14")
+ Files.write(newBuild, appGradleFile, Charset.defaultCharset())
+ project.execute("clean", ":app:assembleDebug")
+
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f1", f1NoneC)
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f2")
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f3")
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f2", f2v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f3", f3v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f4", f4v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f3", f3v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f4", f4v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f5", f5v16C)
+
+ // Set min SDK version 16 and generate the APK.
+ newBuild = appGradleFileContents.replaceAll("minSdkVersion 8", "minSdkVersion 16")
+ Files.write(newBuild, appGradleFile, Charset.defaultCharset())
+ project.execute("clean", ":app:assembleDebug")
+
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f1", f1NoneC)
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f2")
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f3")
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f2", f2v14C)
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw-v14/f3")
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw-v14/f4")
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f3", f3v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f4", f4v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f5", f5v16C)
+ }
+
+ @Test
+ void "test app resources are filtered by min sdk incremental"() {
+ // Note: this test is very similar to the previous one but, instead of trying all 3
+ // versions independently, we start with min SDK 14, then change to no min SDK and set
+ // min SDK to 16. The outputs should be the same as in the previous test.
+
+ File raw = appProject.file("src/main/res/raw")
+ raw.mkdirs()
+
+ File raw14 = appProject.file("src/main/res/raw-v14")
+ raw14.mkdirs()
+
+ File raw16 = appProject.file("src/main/res/raw-v16")
+ raw16.mkdirs()
+
+ byte[] f1NoneC = [ 0 ] as byte[]
+ byte[] f2NoneC = [ 1 ] as byte[]
+ byte[] f2v14C = [ 2 ] as byte[]
+ byte[] f3NoneC = [ 3 ] as byte[]
+ byte[] f3v14C = [ 4 ] as byte[]
+ byte[] f3v16C = [ 5 ] as byte[]
+ byte[] f4v14C = [ 6 ] as byte[]
+ byte[] f4v16C = [ 7 ] as byte[]
+ byte[] f5v16C = [ 8 ] as byte[]
+
+ File f1None = new File(raw, "f1")
+ Files.write(f1NoneC, f1None)
+
+ File f2None = new File(raw, "f2")
+ Files.write(f2NoneC, f2None)
+
+ File f2v14 = new File(raw14, "f2")
+ Files.write(f2v14C, f2v14);
+
+ File f3None = new File(raw, "f3")
+ Files.write(f3NoneC, f3None)
+
+ File f3v14 = new File(raw14, "f3")
+ Files.write(f3v14C, f3v14)
+
+ File f3v16 = new File(raw16, "f3")
+ Files.write(f3v16C, f3v16)
+
+ File f4v14 = new File(raw14, "f4")
+ Files.write(f4v14C, f4v14)
+
+ File f4v16 = new File(raw16, "f4")
+ Files.write(f4v16C, f4v16)
+
+ File f5v16 = new File(raw16, "f5")
+ Files.write(f5v16C, f5v16)
+
+
+ File appGradleFile = appProject.file("build.gradle")
+ String appGradleFileContents = Files.toString(appGradleFile, Charset.defaultCharset())
+
+ // Set min SDK version 14 and generate the APK.
+ String newBuild = appGradleFileContents.replaceAll("minSdkVersion 8", "minSdkVersion 14")
+ Files.write(newBuild, appGradleFile, Charset.defaultCharset())
+ project.execute("clean", ":app:assembleDebug")
+
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f1", f1NoneC)
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f2")
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f3")
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f2", f2v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f3", f3v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f4", f4v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f3", f3v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f4", f4v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f5", f5v16C)
+
+ // Set no min SDK version and generate the APK. Incremental update!
+ newBuild = appGradleFileContents.replaceAll("minSdkVersion 8", "")
+ Files.write(newBuild, appGradleFile, Charset.defaultCharset())
+ project.execute(":app:assembleDebug")
+
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f1", f1NoneC)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f2", f2NoneC)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f3", f3NoneC)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f2", f2v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f3", f3v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f4", f4v14C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f3", f3v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f4", f4v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f5", f5v16C)
+
+ // Set min SDK version 16 and generate the APK. Incremental update!
+ newBuild = appGradleFileContents.replaceAll("minSdkVersion 8", "minSdkVersion 16")
+ Files.write(newBuild, appGradleFile, Charset.defaultCharset())
+ project.execute(":app:assembleDebug")
+
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw/f1", f1NoneC)
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f2")
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw/f3")
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v14/f2", f2v14C)
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw-v14/f3")
+ assertThatApk(appProject.getApk("debug")).doesNotContain("res/raw-v14/f4")
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f3", f3v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f4", f4v16C)
+ assertThatApk(appProject.getApk("debug")).containsFileWithContent("res/raw-v16/f5", f5v16C)
+ }
+
+ // ---- APP TEST ---
+
+ @Test
+ void "test app project test with new res file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.addFile("src/androidTest/res/raw/newfile.txt", "new file content");
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "newfile.txt", "new file content")
+ }
+ }
+
+ @Test
+ void "test app project test with removed res file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.removeFile("src/androidTest/res/raw/filetest.txt")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "filetest.txt", null)
+ }
+ }
+
+ @Test
+ void "test app project test with modified res file"() {
+ project.execute("app:clean", "app:assembleAT")
+
+ TemporaryProjectModification.doTest(appProject) {
+ it.replaceFile("src/androidTest/res/raw/filetest.txt", "new content")
+ project.execute("app:assembleAT")
+
+ checkTestApk(appProject, "filetest.txt", "new content")
+ }
+ }
+
+ // ---- LIB DEFAULT ---
+
+ @Test
+ void "test lib project with new res file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/main/res/raw/newfile.txt", "newfile content");
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "newfile.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test lib project with removed res file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/main/res/raw/filelib.txt")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "filelib.txt", null)
+ }
+ }
+
+ @Test
+ void "test lib project with modified res file"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/main/res/raw/filelib.txt", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "filelib.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project with new res file in debug source set"() {
+ project.execute("library:clean", "library:assembleDebug")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/debug/res/raw/filelib.txt", "new content")
+ project.execute("library:assembleDebug")
+
+ checkAar(libProject, "filelib.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleDebug")
+ checkAar(libProject, "filelib.txt", "library:abcd")
+ }
+
+ // ---- LIB TEST ---
+
+ @Test
+ void "test lib project test with new res file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/res/raw/newfile.txt", "new file content");
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "newfile.txt", "new file content")
+ }
+ }
+
+ @Test
+ void "test lib project test with removed res file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.removeFile("src/androidTest/res/raw/filelibtest.txt")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelibtest.txt", null)
+ }
+ }
+
+ @Test
+ void "test lib project test with modified res file"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.replaceFile("src/androidTest/res/raw/filelibtest.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelibtest.txt", "new content")
+ }
+ }
+
+ @Test
+ void "test lib project test with new res file overriding tested lib"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/res/raw/filelib.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelib.txt", "new content")
+ }
+
+ // files been removed, checking in the other direction.
+ project.execute("library:assembleAT")
+ checkTestApk(libProject, "filelib.txt", "library:abcd")
+
+ }
+
+ @Test
+ void "test lib project test with new res file overriding dependency"() {
+ project.execute("library:clean", "library:assembleAT")
+
+ TemporaryProjectModification.doTest(libProject) {
+ it.addFile("src/androidTest/res/raw/filelib2.txt", "new content")
+ project.execute("library:assembleAT")
+
+ checkTestApk(libProject, "filelib2.txt", "new content")
+ }
+
+ // file's been removed, checking in the other direction.
+ project.execute("library:assembleAT")
+ checkTestApk(libProject, "filelib2.txt", "library2:abcd")
+ }
+
+ // ---- TEST DEFAULT ---
+
+ @Test
+ void "test test-project with new res file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.addFile("src/main/res/raw/newfile.txt", "newfile content");
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "newfile.txt", "newfile content")
+ }
+ }
+
+ @Test
+ void "test test-project with removed res file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.removeFile("src/main/res/raw/file.txt")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "file.txt", null)
+ }
+ }
+
+ @Test
+ void "test test-project with modified res file"() {
+ project.execute("test:clean", "test:assembleDebug")
+
+ TemporaryProjectModification.doTest(testProject) {
+ it.replaceFile("src/main/res/raw/file.txt", "new content")
+ project.execute("test:assembleDebug")
+
+ checkApk(testProject, "file.txt", "new content")
+ }
+ }
+
+ // -----------------------
+
+ /**
+ * check an apk has (or not) the given res file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getApk("debug")), filename, content)
+ }
+
+ /**
+ * check a test apk has (or not) the given res file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkTestApk(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getTestApk("debug")), filename, content)
+ }
+
+ /**
+ * check an aat has (or not) the given res file name.
+ *
+ * If the content is non-null the file is expected to be there with the same content. If the
+ * content is null the file is not expected to be there.
+ *
+ * @param project the project
+ * @param filename the filename
+ * @param content the content
+ */
+ private static void checkAar(
+ @NonNull GradleTestProject project,
+ @NonNull String filename,
+ @Nullable String content) {
+ check(assertThatApk(project.getAar("debug")), filename, content)
+ }
+
+ private static void check(
+ @NonNull AbstractAndroidSubject subject,
+ @NonNull String filename,
+ @Nullable String content) {
+ if (content != null) {
+ subject.containsFileWithContent("res/raw/" + filename, content)
+ } else {
+ subject.doesNotContainResource("raw/" + filename)
+ }
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleCodeChangeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleCodeChangeTest.groovy
new file mode 100644
index 0000000..bb57a16
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleCodeChangeTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_INC_JAVA
+/**
+ * Performance test for full and incremental build on ioschedule 2014
+ */
+ at RunWith(Parameterized.class)
+ at CompileStatic
+class IOScheduleCodeChangeTest {
+
+ @Parameterized.Parameters(name="minify={0} jack={1}")
+ public static Collection<Object[]> data() {
+ // returns an array of boolean for all combinations of (proguard, jack).
+ // Right now, only return the (false, false) and (false, true) cases.
+ return [
+// [true, false].toArray(),
+// [true, true].toArray(),
+ [false, false].toArray(),
+ [false, true].toArray(),
+ ];
+ }
+
+ private final boolean proguard
+ private final boolean jack
+
+ IOScheduleCodeChangeTest(boolean proguard, boolean jack) {
+ this.proguard = proguard
+ this.jack = jack
+ }
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromExternalProject("iosched")
+ .withJack(jack)
+ .withMinify(proguard)
+ .create()
+
+ @Before
+ public void setUp() {
+ project.executeWithBenchmark("iosched2014", BUILD_FULL, "clean" , "assembleDebug")
+ }
+
+ @After
+ void cleanUp() {
+ project = null;
+ }
+
+ @Test
+ void "Incremental Build on Java Change"() {
+ TestFileUtils.replaceLine(
+ project.file("android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItem.java"),
+ 30,
+ " public long startTime = 1;")
+ project.executeWithBenchmark("iosched2014", BUILD_INC_JAVA, "assembleDebug")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleResChangeTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleResChangeTest.groovy
new file mode 100644
index 0000000..6acdbde
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/IOScheduleResChangeTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import groovy.transform.CompileStatic
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_INC_RES_ADD
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_INC_RES_EDIT
+
+/**
+ * Performance test for full and incremental build on ioschedule 2014
+ */
+ at CompileStatic
+class IOScheduleResChangeTest {
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromExternalProject("iosched")
+ .create()
+
+ @Before
+ public void setUp() {
+ project.executeWithBenchmark("iosched2014", BUILD_FULL, "clean" , "assembleDebug")
+ }
+
+ @After
+ void cleanUp() {
+ project = null;
+ }
+
+ @Test
+ void "Incremental Build on Resource Edit Change"() {
+ TestFileUtils.replaceLine(
+ project.file("android/src/main/res/values/strings.xml"),
+ 97,
+ " <string name=\"app_name\">Google I/O 2015</string>")
+
+ project.executeWithBenchmark("iosched2014", BUILD_INC_RES_EDIT, "assembleDebug")
+ }
+
+ @Test
+ void "Incremental Build on Resource Add Change"() {
+ TestFileUtils.replaceLine(
+ project.file("android/src/main/res/values/strings.xml"),
+ 97,
+ " <string name=\"app_name\">Google I/O 2015</string><string name=\"aaaa\">aaa</string>")
+
+ project.executeWithBenchmark("iosched2014", BUILD_INC_RES_ADD, "assembleDebug")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidComponentTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidComponentTest.groovy
new file mode 100644
index 0000000..81c5d46
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidComponentTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
+
+/**
+ * Performance test on gradle experimantal plugin with a large number of variants
+ */
+class LargeVariantAndroidComponentTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .forExperimentalPlugin(true)
+ .create()
+
+
+ @BeforeClass
+ static void setUp() {
+ VariantBuildScriptGenerator generator = new VariantBuildScriptGenerator(
+ buildTypes: VariantBuildScriptGenerator.LARGE_NUMBER,
+ productFlavors: VariantBuildScriptGenerator.LARGE_NUMBER,
+ """
+ apply plugin: "com.android.model.application"
+
+ model {
+ android {
+ compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+
+ android.buildTypes {
+ \${buildTypes}
+ }
+
+ android.productFlavors {
+ \${productFlavors}
+ }
+ }
+ """.stripIndent())
+ generator.addPostProcessor("buildTypes") { return (String) "create(\"$it\")" }
+ generator.addPostProcessor("productFlavors") { return (String) "create(\"$it\")" }
+
+ project.buildFile << generator.createBuildScript()
+
+ // Execute before performance test to warm up the cache.
+ project.execute("help");
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void performanceTest() {
+ project.executeWithBenchmark("LargeVariantAndroid", EVALUATION, "projects")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidTest.groovy
new file mode 100644
index 0000000..4504eaf
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/LargeVariantAndroidTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
+
+/**
+ * Performance test on gradle plugin with a large number of variants
+ */
+class LargeVariantAndroidTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.noBuildFile())
+ .useExperimentalGradleVersion(true)
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ project.buildFile << new VariantBuildScriptGenerator(
+ buildTypes: VariantBuildScriptGenerator.LARGE_NUMBER,
+ productFlavors: VariantBuildScriptGenerator.LARGE_NUMBER,
+ """
+ apply plugin: "com.android.application"
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ buildTypes {
+ \${buildTypes}
+ }
+
+ productFlavors {
+ \${productFlavors}
+ }
+ }
+ """.stripIndent()).createBuildScript()
+
+ // Execute before performance test to warm up the cache.
+ project.execute("help");
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void performanceTest() {
+ project.executeWithBenchmark("LargeVariantAndroid", EVALUATION, "projects")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentEvaluationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentEvaluationTest.groovy
new file mode 100644
index 0000000..86e6a8a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentEvaluationTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
+import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
+import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_BREADTH
+import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_DEPTH
+
+/**
+ * test with ~120 projects that queries the IDE model
+ */
+ at CompileStatic
+class MediumAndroidComponentEvaluationTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(LargeTestProject.builder()
+ .withModule(AndroidComponentGradleModule)
+ .withDepth(MEDIUM_DEPTH)
+ .withBreadth(MEDIUM_BREADTH)
+ .create())
+ .forExperimentalPlugin(true)
+ .withHeap("2048m")
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "'projects' task run on 120 projects"() {
+ project.executeWithBenchmark("MediumAndroid", EVALUATION, "projects")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentModelTest.groovy
new file mode 100644
index 0000000..a42d90e
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidComponentModelTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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.build.gradle.integration.performance
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
+import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.ClassRule
+import org.junit.Ignore
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.SYNC
+import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_BREADTH
+import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.MEDIUM_DEPTH
+
+/**
+ * test with ~120 projects that queries the IDE model
+ */
+ at CompileStatic
+class MediumAndroidComponentModelTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(LargeTestProject.builder()
+ .withModule(AndroidComponentGradleModule)
+ .withDepth(MEDIUM_DEPTH)
+ .withBreadth(MEDIUM_BREADTH)
+ .create())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ @Ignore
+ void "model query for 120 projects"() {
+ Map<String, AndroidProject> models = project.getAllModelsWithBenchmark("MediumAndroid", SYNC)
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidEvaluationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidEvaluationTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidEvaluationTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidEvaluationTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidModelTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidModelTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumAndroidModelTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumJavaEvaluationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumJavaEvaluationTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumJavaEvaluationTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MediumJavaEvaluationTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidComponentTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidComponentTest.groovy
new file mode 100644
index 0000000..7d88b91
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidComponentTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
+
+/**
+ * Performance test on gradle experimental plugin with multiple sub-projects and multiple variants.
+ */
+class MultiProjectsAndroidComponentTest {
+ public static AndroidTestApp app = HelloWorldApp.noBuildFile()
+ static {
+ VariantBuildScriptGenerator generator = new VariantBuildScriptGenerator(
+ buildTypes: VariantBuildScriptGenerator.MEDIUM_NUMBER,
+ productFlavors: VariantBuildScriptGenerator.MEDIUM_NUMBER,
+ """
+ apply plugin: "com.android.model.application"
+
+ model {
+ android {
+ compileSdkVersion = $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion = "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ }
+
+ android.buildTypes {
+ \${buildTypes}
+ }
+
+ android.productFlavors {
+ \${productFlavors}
+ }
+ }
+ """.stripIndent())
+ generator.addPostProcessor("buildTypes") { return (String) "create(\"$it\")" }
+ generator.addPostProcessor("productFlavors") { return (String) "create(\"$it\")" }
+
+ app.addFile(new TestSourceFile("", "build.gradle", generator.createBuildScript()))
+ }
+
+ public static TestProject baseProject = new MultiModuleTestProject("app", app, 10)
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(baseProject)
+ .forExperimentalPlugin(true)
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ // Execute before performance test to warm up the cache.
+ project.execute("help");
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ app = null;
+ baseProject = null;
+ project = null;
+ }
+
+ @Test
+ void "performance test - projects"() {
+ project.executeWithBenchmark("MultiProjectsAndroid", EVALUATION, "projects")
+ }
+
+ @Test
+ void "performance test - single variant"() {
+ project.executeWithBenchmark("MultiProjectsAndroid", BUILD_FULL, ":app0:assembleProductFlavor0BuildType0")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidTest.groovy
new file mode 100644
index 0000000..d187968
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/MultiProjectsAndroidTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.TestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import com.android.build.gradle.integration.common.fixture.app.VariantBuildScriptGenerator
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.BUILD_FULL
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
+
+/**
+ * Performance test on gradle plugin with multiple subprojects and multiple variants.
+ */
+class MultiProjectsAndroidTest {
+ public static AndroidTestApp app = HelloWorldApp.noBuildFile()
+ static {
+ app.addFile(new TestSourceFile("", "build.gradle",
+ new VariantBuildScriptGenerator(
+ buildTypes: VariantBuildScriptGenerator.MEDIUM_NUMBER,
+ productFlavors: VariantBuildScriptGenerator.MEDIUM_NUMBER,
+ """
+ apply plugin: "com.android.application"
+
+ android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+
+ buildTypes {
+ \${buildTypes}
+ }
+
+ productFlavors {
+ \${productFlavors}
+ }
+ }
+ """.stripIndent()).createBuildScript())
+ )
+ }
+
+ public static TestProject baseProject = new MultiModuleTestProject("app", app, 10)
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(baseProject)
+ .useExperimentalGradleVersion(true)
+ .create()
+
+ @BeforeClass
+ static void setUp() {
+ // Execute before performance test to warm up the cache.
+ project.execute("help");
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ app = null;
+ baseProject = null;
+ project = null;
+ }
+
+ @Test
+ void "performance test - projects"() {
+ project.executeWithBenchmark("MultiProjectsAndroid", EVALUATION, "projects")
+ }
+
+ @Test
+ void "performance test - single variant"() {
+ project.executeWithBenchmark("MultiProjectsAndroid", BUILD_FULL, ":app0:assembleProductFlavor0BuildType0")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentEvaluationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentEvaluationTest.groovy
new file mode 100644
index 0000000..ba881d7
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentEvaluationTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
+import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.EVALUATION
+import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.SMALL_BREADTH
+import static com.android.build.gradle.integration.common.fixture.app.LargeTestProject.SMALL_DEPTH
+
+/**
+ * test with ~30 projects that queries the IDE model
+ */
+ at CompileStatic
+class SmallAndroidComponentEvaluationTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(LargeTestProject.builder()
+ .withModule(AndroidComponentGradleModule)
+ .withDepth(SMALL_DEPTH)
+ .withBreadth(SMALL_BREADTH)
+ .create())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ void "'projects' task run on 30 projects"() {
+ project.executeWithBenchmark("SmallAndroid", EVALUATION, "projects")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentModelTest.groovy
new file mode 100644
index 0000000..bd9db12
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidComponentModelTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.build.gradle.integration.performance
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidComponentGradleModule
+import com.android.build.gradle.integration.common.fixture.app.LargeTestProject
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.ClassRule
+import org.junit.Ignore
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.fixture.GradleTestProject.BenchmarkMode.SYNC
+
+/**
+ * test with ~30 projects that queries the IDE model
+ */
+ at CompileStatic
+class SmallAndroidComponentModelTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(LargeTestProject.builder()
+ .withModule(AndroidComponentGradleModule)
+ .withDepth(LargeTestProject.SMALL_DEPTH)
+ .withBreadth(LargeTestProject.SMALL_BREADTH)
+ .create())
+ .forExperimentalPlugin(true)
+ .create()
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ }
+
+ @Test
+ @Ignore
+ void "model query for 30 projects"() {
+ Map<String, AndroidProject> models = project.getAllModelsWithBenchmark("SmallAndroid", SYNC)
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidEvaluationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidEvaluationTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidEvaluationTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidEvaluationTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidModelTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidModelTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidModelTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallAndroidModelTest.groovy
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallJavaEvaluationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallJavaEvaluationTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallJavaEvaluationTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/performance/SmallJavaEvaluationTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/HelloWorldShrinkerTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/HelloWorldShrinkerTest.groovy
new file mode 100644
index 0000000..b190516
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/HelloWorldShrinkerTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * 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.build.gradle.integration.shrinker
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import com.android.utils.FileUtils
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.shrinker.ShrinkerTestUtils.checkShrinkerWasUsed
+/**
+ * Tests for integration of the new class shrinker with Gradle.
+ */
+ at CompileStatic
+class HelloWorldShrinkerTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application"))
+ .create()
+
+ private File helloWorldClass
+ private File utilsClass
+
+ @Before
+ public void enableShrinking() throws Exception {
+ project.buildFile << """
+ android {
+ buildTypes.debug {
+ minifyEnabled true
+ useProguard false
+ proguardFiles getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+ """
+ }
+
+ @Before
+ public void addUtilityClass() throws Exception {
+ project.file("src/main/java/com/example/helloworld/Utils.java") << """
+ package com.example.helloworld;
+
+ public class Utils {
+ public static void helper() {
+ android.util.Log.i("Utils", "test log entry");
+ }
+ }
+ """
+
+ TestFileUtils.searchAndReplace(
+ project.file("src/main/java/com/example/helloworld/HelloWorld.java"),
+ "// onCreate",
+ "Utils.helper();")
+
+ }
+
+ @Test
+ public void "APK is correct"() throws Exception {
+ project.execute("assembleDebug")
+ File helloWorld = findHelloWorld()
+ File utils = findUtils()
+
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/Utils;")
+
+ TestFileUtils.searchAndReplace(
+ project.file("src/main/java/com/example/helloworld/Utils.java"),
+ "test log entry",
+ "CHANGE")
+
+ project.execute("assembleDebug")
+ checkShrinkerWasUsed(project)
+
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/Utils;")
+
+ TestFileUtils.searchAndReplace(
+ project.file("src/main/java/com/example/helloworld/HelloWorld.java"),
+ "Utils.helper\\(\\);",
+ "// onCreate")
+
+ project.execute("assembleDebug")
+ checkShrinkerWasUsed(project)
+
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).doesNotContainClass("Lcom/example/helloworld/Utils;")
+ assertThat(helloWorld).isNewerThan(utils)
+
+ FileUtils.delete(project.file("src/main/java/com/example/helloworld/Utils.java"))
+ project.execute("assembleDebug")
+
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).doesNotContainClass("Lcom/example/helloworld/Utils;")
+
+ addUtilityClass()
+ project.execute("assembleDebug")
+
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/Utils;")
+
+ assertThat(helloWorld).isSameAgeAs(utils)
+ }
+
+ @Test
+ @Category(DeviceTests)
+ public void connectedCheck() throws Exception {
+ project.executeConnectedCheck()
+ checkShrinkerWasUsed(project)
+
+ TestFileUtils.searchAndReplace(
+ project.file("src/main/java/com/example/helloworld/Utils.java"),
+ "test log entry",
+ "CHANGE")
+
+ project.executeConnectedCheck()
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/Utils;")
+
+ TestFileUtils.searchAndReplace(
+ project.file("src/main/java/com/example/helloworld/HelloWorld.java"),
+ "Utils.helper\\(\\);",
+ "// onCreate")
+
+ project.executeConnectedCheck()
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).doesNotContainClass("Lcom/example/helloworld/Utils;")
+
+ FileUtils.delete(project.file("src/main/java/com/example/helloworld/Utils.java"))
+ project.executeConnectedCheck()
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).doesNotContainClass("Lcom/example/helloworld/Utils;")
+
+ addUtilityClass()
+ project.executeConnectedCheck()
+
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/HelloWorld;")
+ assertThatApk(project.getApk("debug")).containsClass("Lcom/example/helloworld/Utils;")
+ }
+
+ private File findHelloWorld() {
+ return FileUtils.find(
+ project.file("build/intermediates/transforms/newClassShrinker"),
+ "HelloWorld.class")
+ .get()
+ }
+
+ private File findUtils() {
+ return FileUtils.find(
+ project.file("build/intermediates/transforms/newClassShrinker"),
+ "Utils.class")
+ .get()
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/MinifyProjectShrinkerTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/MinifyProjectShrinkerTest.groovy
new file mode 100644
index 0000000..1c21197
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/MinifyProjectShrinkerTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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.build.gradle.integration.shrinker
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+import static com.android.build.gradle.integration.shrinker.ShrinkerTestUtils.checkShrinkerWasUsed
+/**
+ * Tests based on the "minify" test project, which contains unused classes, reflection and
+ * JaCoCo classes.
+ */
+ at CompileStatic
+class MinifyProjectShrinkerTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("minify")
+ .create()
+
+ @Before
+ public void enableShrinker() throws Exception {
+ project.buildFile << """
+ android {
+ buildTypes.minified {
+ useProguard false
+ }
+
+ testBuildType = "minified"
+ }
+ """
+ }
+
+ @Test
+ public void "APK is correct"() throws Exception {
+ project.execute("assembleMinified")
+ checkShrinkerWasUsed(project)
+ assertThatApk(project.getApk("minified")).containsClass("Lcom/android/tests/basic/Main;")
+ assertThatApk(project.getApk("minified")).containsClass("Lcom/android/tests/basic/StringProvider;")
+ assertThatApk(project.getApk("minified")).containsClass("Lcom/android/tests/basic/IndirectlyReferencedClass;")
+ assertThatApk(project.getApk("minified")).doesNotContainClass("Lcom/android/tests/basic/UnusedClass;")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/ShrinkerTestUtils.java b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/ShrinkerTestUtils.java
new file mode 100644
index 0000000..8823dfe
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/ShrinkerTestUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.shrinker;
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat;
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject;
+
+/**
+ * Utility code for testing the new built-in class shrinker.
+ */
+public class ShrinkerTestUtils {
+ public static void checkShrinkerWasUsed(GradleTestProject project) {
+ // Sanity check, to make sure we're testing the right thing.
+ assertThat(project.file("build/intermediates/transforms/newClassShrinker")).exists();
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/WarningsShrinkerTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/WarningsShrinkerTest.groovy
new file mode 100644
index 0000000..7aa6c6d
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/shrinker/WarningsShrinkerTest.groovy
@@ -0,0 +1,208 @@
+/*
+ * 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.build.gradle.integration.shrinker
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import groovy.transform.CompileStatic
+import org.gradle.tooling.GradleConnectionException
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.utils.GradleExceptionsHelper.getTaskFailureMessage
+/**
+ * Tests for -dontwarn handling
+ */
+ at CompileStatic
+class WarningsShrinkerTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(HelloWorldApp.forPlugin("com.android.application"))
+ .captureStdOut(true)
+ .create()
+
+ private File rules
+ private File activity
+ private int changeCounter
+
+ @Before
+ public void enableShrinking() throws Exception {
+ rules = project.file('proguard-rules.pro')
+
+ project.buildFile << """
+ android {
+ buildTypes.debug {
+ minifyEnabled true
+ useProguard false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), '${rules.name}'
+ }
+ }
+ """
+
+ rules << """
+ # Empty rules for now.
+ """
+ }
+
+ @Before
+ public void addGuavaDep() throws Exception {
+ project.buildFile << """
+ dependencies {
+ compile 'com.google.guava:guava:18.0'
+ }
+ """
+ }
+
+ @Before
+ public void prepareForChanges() throws Exception {
+ activity = project.file("src/main/java/com/example/helloworld/HelloWorld.java")
+
+ TestFileUtils.addMethod(
+ activity,
+ """
+ @Override
+ protected void onStop() {
+ android.util.Log.i("MainActivity", "CHANGE0");
+ super.onStop();
+ }
+ """)
+ }
+
+ @Test
+ public void "Warnings stop build"() throws Exception {
+ project.executeExpectingFailure("assembleDebug")
+
+ String output = project.stdout.toString()
+ assertThat(output).contains("references unknown")
+ assertThat(output).contains("Unsafe")
+ assertThat(output).contains("Nullable")
+ assertThat(output).contains("com/google/common/cache")
+
+ changeCode()
+
+ project.stdout.reset()
+ project.executeExpectingFailure("assembleDebug")
+
+ output = project.stdout.toString()
+ assertThat(output).contains("references unknown")
+ assertThat(output).contains("Unsafe")
+ assertThat(output).contains("Nullable")
+ assertThat(output).contains("com/google/common/cache")
+ }
+
+ @Test
+ public void "-dontwarn applies only to relevant classes"() throws Exception {
+ rules << """
+ -dontwarn sun.misc.Unsafe
+ """
+
+ project.executeExpectingFailure("assembleDebug")
+
+ String output = project.stdout.toString()
+ assertThat(output).contains("references unknown")
+ assertThat(output).doesNotContain("Unsafe")
+ assertThat(output).contains("Nullable")
+ assertThat(output).contains("com/google/common/cache")
+ }
+
+ @Test
+ public void "-dontwarn without arguments"() throws Exception {
+ rules << "-dontwarn"
+ project.execute("assembleDebug")
+
+ String output = project.stdout.toString()
+ assertThat(output).doesNotContain("references unknown")
+ assertThat(output).doesNotContain("Unsafe")
+ assertThat(output).doesNotContain("Nullable")
+ assertThat(output).doesNotContain("com/google/common/cache")
+
+ changeCode()
+
+ project.stdout.reset()
+ project.execute("assembleDebug")
+
+ output = project.stdout.toString()
+ assertThat(output).doesNotContain("references unknown")
+ assertThat(output).doesNotContain("Unsafe")
+ assertThat(output).doesNotContain("Nullable")
+ assertThat(output).doesNotContain("com/google/common/cache")
+ }
+
+ @Test
+ public void "-dontwarn on caller"() throws Exception {
+ rules << "-dontwarn com.google.common.**"
+ project.execute("assembleDebug")
+
+ String output = project.stdout.toString()
+ assertThat(output).doesNotContain("references unknown")
+ assertThat(output).doesNotContain("Unsafe")
+ assertThat(output).doesNotContain("Nullable")
+ assertThat(output).doesNotContain("com/google/common/cache")
+
+ changeCode()
+
+ project.stdout.reset()
+ project.execute("assembleDebug")
+
+ output = project.stdout.toString()
+ assertThat(output).doesNotContain("references unknown")
+ assertThat(output).doesNotContain("Unsafe")
+ assertThat(output).doesNotContain("Nullable")
+ assertThat(output).doesNotContain("com/google/common/cache")
+ }
+
+ @Test
+ public void "-dontwarn on callee"() throws Exception {
+ rules << """
+ -dontwarn sun.misc.Unsafe
+ -dontwarn javax.annotation.**
+ """
+
+ project.execute("assembleDebug")
+
+ String output = project.stdout.toString()
+ assertThat(output).doesNotContain("references unknown")
+ assertThat(output).doesNotContain("Unsafe")
+ assertThat(output).doesNotContain("Nullable")
+ assertThat(output).doesNotContain("com/google/common/cache")
+
+ changeCode()
+
+ project.stdout.reset()
+ project.execute("assembleDebug")
+
+ output = project.stdout.toString()
+ assertThat(output).doesNotContain("references unknown")
+ assertThat(output).doesNotContain("Unsafe")
+ assertThat(output).doesNotContain("Nullable")
+ assertThat(output).doesNotContain("com/google/common/cache")
+ }
+
+ @Test
+ public void "Parser errors are properly reported"() throws Exception {
+ rules << "-foo"
+
+ GradleConnectionException failure = project.executeExpectingFailure("assembleDebug")
+ assertThat(getTaskFailureMessage(failure)).contains("'-foo' expecting EOF")
+ }
+
+ private void changeCode() {
+ TestFileUtils.searchAndReplace(activity, "CHANGE\\d+", "CHANGE${++changeCounter}")
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/DuplicateModuleNameImportTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/DuplicateModuleNameImportTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/DuplicateModuleNameImportTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/DuplicateModuleNameImportTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleTest.groovy
new file mode 100644
index 0000000..91c1857
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.build.gradle.integration.test
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidProject
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+/**
+ * Test for setup with 2 modules: app and test-app
+ */
+ at CompileStatic
+class SeparateTestModuleTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("separateTestModule")
+ .create()
+
+ static Map<String, AndroidProject> models
+
+ @BeforeClass
+ static void setUp() {
+ models = project.executeAndReturnMultiModel("assemble")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ models = null
+ }
+
+ @Test
+ void "check model"() throws Exception {
+ // check the content of the test model.
+ }
+
+ @Test
+ public void "check dependencies between tasks"() throws Exception {
+ // Check :test:assembleDebug succeeds on its own, i.e. compiles the app module.
+ project.execute("clean", ":test:assembleDebug", ":test:checkDependencies")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleWithAppDependenciesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleWithAppDependenciesTest.groovy
new file mode 100644
index 0000000..b999c3a
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestModuleWithAppDependenciesTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.build.gradle.integration.test
+
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.builder.model.AndroidProject
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+/**
+ * Created by jedo on 9/1/15.
+ */
+class SeparateTestModuleWithAppDependenciesTest {
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("separateTestModule")
+ .create()
+
+ static Map<String, AndroidProject> models
+
+ @BeforeClass
+ static void setup() {
+ project.getSubproject("app").getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ publishNonDefault true
+
+ defaultConfig {
+ minSdkVersion 9
+ }
+}
+dependencies {
+ compile 'com.google.android.gms:play-services-base:7.5.0'
+}
+ """
+
+ models = project.executeAndReturnMultiModel("clean", "assemble")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ models = null
+ }
+
+ @Test
+ void "check model"() throws Exception {
+ // check the content of the test model.
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithAarDependencyTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithAarDependencyTest.groovy
new file mode 100644
index 0000000..726367f
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithAarDependencyTest.groovy
@@ -0,0 +1,128 @@
+/*
+ * 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.build.gradle.integration.test
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.ModelHelper
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Dependencies
+import com.android.builder.model.Variant
+import groovy.transform.CompileStatic
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThat
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * Test separate test module testing an app with aar dependencies.
+ */
+ at CompileStatic
+public class SeparateTestWithAarDependencyTest {
+
+ @ClassRule
+ static public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("separateTestModule")
+ .create()
+
+ static Map<String, AndroidProject> models
+
+ @BeforeClass
+ static void setup() {
+ project.getSubproject("app").getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion = '$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION'
+
+ publishNonDefault true
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
+dependencies {
+ compile 'com.android.support:appcompat-v7:22.1.0'
+}
+ """
+
+ models = project.executeAndReturnMultiModel("clean", "assemble")
+ }
+
+ @AfterClass
+ static void cleanUp() {
+ project = null
+ models = null
+ }
+
+ @Test
+ void "check app doesn't contain test app's code"() {
+ File apk = project.getSubproject('test').getApk("debug")
+ assertThatApk(apk).doesNotContainClass("Lcom/android/tests/basic/Main;")
+ }
+
+ @Test
+ void "check app doesn't contain test app's layout"() {
+ File apk = project.getSubproject('test').getApk("debug")
+ assertThatApk(apk).doesNotContainResource("layout/main.xml")
+ }
+
+ @Test
+ void "check app doesn't contain test app's dependency lib's code"() {
+ File apk = project.getSubproject('test').getApk("debug")
+ assertThatApk(apk).doesNotContainClass("Landroid/support/v7/app/ActionBar;")
+ }
+
+ @Test
+ void "check app doesn't contain test app's dependency lib's resources"() {
+ File apk = project.getSubproject('test').getApk("debug")
+ assertThatApk(apk).doesNotContainResource("layout/abc_action_bar_title_item.xml")
+ }
+
+ @Test
+ void "check test model includes the tested app"() {
+ Collection<Variant> variants = models.get(":test").getVariants()
+
+ // get the main artifact of the debug artifact and its dependencies
+ Variant variant = ModelHelper.getVariant(variants, "debug")
+ AndroidArtifact artifact = variant.getMainArtifact()
+ Dependencies dependencies = artifact.getDependencies()
+
+ // check the app project shows up as a project dependency
+ Collection<String> projects = dependencies.getProjects()
+ assertThat(projects).containsExactly(":app")
+
+ // and that nothing else shows up.
+ // TODO: fix this.
+// Collection<JavaLibrary> javaLibs = dependencies.getJavaLibraries();
+// assertThat(javaLibs).hasSize(0);
+// Collection<AndroidLibrary> libs = dependencies.getLibraries();
+// assertThat(libs).hasSize(0);
+ }
+
+ @Test
+ @Category(DeviceTests)
+ void "connected check"() {
+ GradleTestProject.assumeLocalDevice()
+ project.execute("clean",":test:connectedCheck");
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithDependenciesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithDependenciesTest.groovy
new file mode 100644
index 0000000..e151318
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithDependenciesTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.build.gradle.integration.test
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * Test separate test module that tests an application with some complicated dependencies :
+ * - the app imports a library importing a jar file itself.
+ * - use minification.
+ */
+ at CompileStatic
+public class SeparateTestWithDependenciesTest {
+
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("separateTestModuleWithDependencies")
+ .create()
+
+ @BeforeClass
+ static void setup() {
+ project.execute("clean", "assemble")
+ }
+ @Test
+ void "check app contains all dependent classes"() {
+ project.execute("clean", "assemble")
+ File apk = project.getSubproject('app').getApk("debug")
+ assertThatApk(apk).containsClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
+ }
+
+
+ @Test
+ void "check test app does not contain any minified application's dependent classes"() {
+ project.execute("clean", "assemble")
+ File apk = project.getSubproject('test').getApk("debug")
+ assertThatApk(apk).doesNotContainClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithMinificationButNoObfuscationTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithMinificationButNoObfuscationTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithMinificationButNoObfuscationTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithMinificationButNoObfuscationTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithoutMinificationWithDependenciesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithoutMinificationWithDependenciesTest.groovy
new file mode 100644
index 0000000..e9c7db4
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/test/SeparateTestWithoutMinificationWithDependenciesTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * 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.build.gradle.integration.test
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatApk
+/**
+ * Test separate test module that tests an application with some complicated dependencies :
+ * - the app imports a library importing a jar file itself.
+ */
+public class SeparateTestWithoutMinificationWithDependenciesTest {
+ @ClassRule
+ public static GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("separateTestModuleWithDependencies")
+ .create()
+
+
+ @BeforeClass
+ static void setup() {
+ project.getSubproject("test").getBuildFile() << """
+ apply plugin: 'com.android.test'
+
+ android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ targetProjectPath ':app'
+ targetVariant 'debug'
+ }
+ """
+ project.execute("clean", "assemble")
+ }
+ @Test
+ void "check app contains all dependent clases"() {
+ File apk = project.getSubproject('app').getApk("debug")
+ assertThatApk(apk).containsClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
+ }
+
+
+ @Test
+ void "check test app does not contain any application's dependent classes"() {
+ File apk = project.getSubproject('test').getApk("debug")
+ assertThatApk(apk).doesNotContainClass("Lcom/android/tests/jarDep/JarDependencyUtil;")
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/JUnitResults.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/JUnitResults.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/JUnitResults.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/JUnitResults.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/TestingSupportLibraryTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/TestingSupportLibraryTest.groovy
new file mode 100644
index 0000000..7d2dee7
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/TestingSupportLibraryTest.groovy
@@ -0,0 +1,134 @@
+/*
+ * 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.build.gradle.integration.testing
+
+import com.android.build.gradle.integration.common.category.DeviceTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.app.AndroidTestApp
+import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp
+import com.android.build.gradle.integration.common.fixture.app.TestSourceFile
+import groovy.transform.CompileStatic
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.common.truth.TruthHelper.assertThatZip
+
+/**
+ * Test project to cover the Android Gradle plugin's interaction with the testing support library.
+ */
+ at CompileStatic
+class TestingSupportLibraryTest {
+
+ public static final AndroidTestApp helloWorldApp = HelloWorldApp.noBuildFile();
+ static {
+ /* Junit 4 now maps tests annotated with @Ignore and tests that throw
+ AssumptionFailureExceptions as skipped. */
+ helloWorldApp.addFile(new TestSourceFile("src/androidTest/java/com/example/helloworld", "FailureAssumptionTest.java",
+ """
+package com.example.helloworld;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+ at RunWith(AndroidJUnit4.class)
+ at SmallTest
+public class FailureAssumptionTest {
+ @Test
+ public void checkAssumptionIsSkipped() {
+ assumeTrue(false);
+ fail("Tests with failing assumptions should be skipped");
+ }
+
+ @Test
+ @Ignore
+ public void checkIgnoreTestsArePossible() {
+ fail("Tests with @Ignore annotation should be skipped");
+ }
+
+ @Test
+ public void checkThisTestPasses() {
+ System.err.println("Test executed");
+ }
+}
+"""))
+ helloWorldApp.addFile(new TestSourceFile("src/main", "AndroidManifest.xml",
+ """<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.helloworld"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="18" />
+ <application android:label="@string/app_name">
+ <activity android:name=".HelloWorld"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+"""))
+ }
+
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestApp(helloWorldApp)
+ .create()
+
+ @Before
+ public void setUp() {
+ project
+ project.getBuildFile() << """
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion $GradleTestProject.DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion "$GradleTestProject.DEFAULT_BUILD_TOOL_VERSION"
+ defaultConfig {
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ dependencies {
+ androidTestCompile 'com.android.support.test:runner:0.3'
+ androidTestCompile 'com.android.support.test:rules:0.3'
+ }
+}
+"""
+ }
+
+ @Test
+ public void "check compile"() {
+ project.execute("assembleDebugAndroidTest")
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void "test ignored tests are not run"() {
+ project.executeConnectedCheck()
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingBuildTypesSupportTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingBuildTypesSupportTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingBuildTypesSupportTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingBuildTypesSupportTest.groovy
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingComplexProjectTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingComplexProjectTest.groovy
new file mode 100644
index 0000000..7161468
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingComplexProjectTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.build.gradle.integration.testing
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.utils.TestFileUtils
+import groovy.transform.CompileStatic
+import org.junit.Rule
+import org.junit.Test
+/**
+ * Runs tests in a big, complicated project.
+ */
+ at CompileStatic
+class UnitTestingComplexProjectTest {
+ @Rule
+ public GradleTestProject project = GradleTestProject.builder()
+ .fromTestProject("unitTestingComplexProject")
+ .create()
+
+ @Test
+ public void appProject() throws Exception {
+ project.execute("clean", "test")
+ }
+
+ @Test
+ public void libProject() throws Exception {
+ // Make the top-level project a library. Libraries depending on libraries are an edge case
+ // when it comes to generating and using R classes.
+ TestFileUtils.searchAndReplace(
+ project.getSubproject("app").buildFile,
+ "com.android.application",
+ "com.android.library")
+ project.execute("clean", "test")
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingDefaultValuesTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingDefaultValuesTest.groovy
new file mode 100644
index 0000000..ce712b9
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingDefaultValuesTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.testing
+
+import com.android.build.gradle.integration.common.category.SmokeTests
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import groovy.transform.CompileStatic
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+import static com.android.build.gradle.integration.testing.JUnitResults.Outcome.PASSED
+import static com.google.common.truth.Truth.assertThat
+/**
+ * Meta-level tests for the app-level unit testing support. Tests default values mode.
+ */
+ at Category(SmokeTests.class)
+class UnitTestingDefaultValuesTest {
+ @ClassRule
+ static public GradleTestProject simpleProject = GradleTestProject.builder()
+ .fromTestProject("unitTestingDefaultValues")
+ .create()
+
+ @Test
+ void testSimpleScenario() {
+ simpleProject.execute("testDebug")
+
+ def results = new JUnitResults(
+ simpleProject.file("build/test-results/debug/TEST-com.android.tests.UnitTest.xml"))
+
+ assertThat(results.allTestCases).containsExactly("defaultValues")
+ assert results.outcome("defaultValues") == PASSED
+ }
+}
diff --git a/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingFlavorsSupportTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingFlavorsSupportTest.groovy
new file mode 100644
index 0000000..4533acb
--- /dev/null
+++ b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingFlavorsSupportTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.testing
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.google.common.base.Throwables
+import org.gradle.tooling.BuildException
+import org.junit.ClassRule
+import org.junit.Test
+
+import static com.android.build.gradle.integration.testing.JUnitResults.Outcome.FAILED
+import static com.android.build.gradle.integration.testing.JUnitResults.Outcome.PASSED
+import static org.junit.Assert.fail
+/**
+ * Meta-level tests for the app-level unit testing support.
+ */
+class UnitTestingFlavorsSupportTest {
+ @ClassRule
+ static public GradleTestProject flavorsProject = GradleTestProject.builder()
+ .fromTestProject("unitTestingFlavors")
+ .create()
+
+ @Test
+ public void 'Tests for a given flavor are only compiled against the flavor'() throws Exception {
+ flavorsProject.file("src/doesntBuild/java/com/android/tests/Broken.java") << "this is broken"
+
+ flavorsProject.execute("clean", "testBuildsPassesDebug")
+
+ def results = new JUnitResults(
+ flavorsProject.file("build/test-results/buildsPassesDebug/TEST-com.android.tests.PassingTest.xml"))
+
+ assert results.outcome("referenceFlavorSpecificCode") == PASSED
+
+ try {
+ flavorsProject.execute("testDoesntBuildPassesDebug")
+ fail()
+ } catch (BuildException e) {
+ assert Throwables.getRootCause(e)
+ .exceptionClassName
+ .endsWith("CompilationFailedException")
+ }
+ }
+
+ @Test
+ public void 'Task for a given flavor only runs the correct tests'() throws Exception {
+ flavorsProject.execute("clean", "testBuildsPassesDebug")
+
+ try {
+ flavorsProject.execute("testBuildsFailsDebug")
+ fail()
+ } catch (BuildException e) {
+ assert Throwables.getRootCause(e).message.startsWith("There were failing tests.")
+
+ def results = new JUnitResults(
+ flavorsProject.file("build/test-results/buildsFailsDebug/TEST-com.android.tests.FailingTest.xml"))
+ assert results.outcome("failingTest") == FAILED
+ }
+ }
+}
diff --git a/base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingSupportTest.groovy b/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingSupportTest.groovy
similarity index 100%
rename from base/build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingSupportTest.groovy
rename to build-system/integration-test/src/test/groovy/com/android/build/gradle/integration/testing/UnitTestingSupportTest.groovy
diff --git a/build-system/integration-test/src/test/resources/basic/example.json b/build-system/integration-test/src/test/resources/basic/example.json
new file mode 100644
index 0000000..28a7630
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/basic/example.json
@@ -0,0 +1,107 @@
+{
+
+"project_info": {
+ "project_id": "red-ant-1",
+ "project_number": "1234567890",
+ "name": "My Awesome App"
+},
+
+"client": [{
+ "client_info": {
+ "client_type": 1,
+ "android_client_info": {
+ "package_name": "com.example.app.free"
+ },
+ "mobilesdk_app_id": "mobilesdk-app-id-free"
+ },
+
+ "oauth_client": [{
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
+ }
+ },
+ {
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "AFDD9pi4klj5vQ6D8gab7UNawhw="
+ }
+ }],
+
+ "api_key": [{
+ "current_key": "android-api-key-free"
+ }],
+
+ "services": {
+ "analytics_service": {
+ "status": 2,
+ "analytics_property": {
+ "tracking_id": "UA-123456789"
+ }
+ },
+ "cloud_messaging_service": {
+ "status": 2
+ },
+ "appinvite_service": {
+ "status": 2
+ },
+ "google_signin_service": {
+ "status": 2
+ },
+ "ads_service": {
+ "status": 2,
+ "test_banner_ad_unit_id": "ca-app-pub-3940256099942544\/1033173712",
+ "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544\/6300978111"
+ },
+ "maps_service": {
+ "status": 2
+ }
+ }
+},
+
+{
+ "client_info": {
+ "client_type": 1,
+ "android_client_info": {
+ "package_name": "com.example.app.paid"
+ },
+ "mobilesdk_app_id": "mobilesdk-app-id-paid"
+ },
+
+ "oauth_client": [{
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.paid",
+ "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
+ }
+ }],
+
+ "api_key": [{
+ "current_key": "android-api-key-paid"
+ }],
+
+ "services": {
+ "analytics_service": {
+ "status": 2,
+ "analytics_property": {
+ "tracking_id": "UA-1923912413"
+ }
+ },
+ "cloud_messaging_service": {
+ "status": 2
+ },
+ "appinvite_service": {
+ "status": 2
+ },
+ "google_signin_service": {
+ "status": 2
+ },
+ "maps_service": {
+ "status": 2
+ }
+ }
+}]
+
+}
diff --git a/build-system/integration-test/src/test/resources/basic/global_tracker.xml b/build-system/integration-test/src/test/resources/basic/global_tracker.xml
new file mode 100644
index 0000000..1c97f21
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/basic/global_tracker.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-123456789</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/basic/values.xml b/build-system/integration-test/src/test/resources/basic/values.xml
new file mode 100644
index 0000000..c75c580
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/basic/values.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-123456789</string>
+ <string name="gcm_defaultSenderId" translatable="false">1234567890</string>
+ <string name="google_app_id" translatable="false">mobilesdk-app-id-free</string>
+ <string name="google_maps_key" translatable="false">android-api-key-free</string>
+ <string name="test_banner_ad_unit_id" translatable="false">ca-app-pub-3940256099942544/1033173712</string>
+ <string name="test_interstitial_ad_unit_id" translatable="false">ca-app-pub-3940256099942544/6300978111</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/dsa_keystore.jks b/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/dsa_keystore.jks
new file mode 100644
index 0000000..36b41ae
Binary files /dev/null and b/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/dsa_keystore.jks differ
diff --git a/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/ec_keystore.jks b/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/ec_keystore.jks
new file mode 100644
index 0000000..c661990
Binary files /dev/null and b/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/ec_keystore.jks differ
diff --git a/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/rsa_keystore.jks b/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/rsa_keystore.jks
new file mode 100644
index 0000000..fbf7949
Binary files /dev/null and b/build-system/integration-test/src/test/resources/com/android/build/gradle/integration/application/SigningTest/rsa_keystore.jks differ
diff --git a/build-system/integration-test/src/test/resources/disabledservice/example.json b/build-system/integration-test/src/test/resources/disabledservice/example.json
new file mode 100644
index 0000000..bfd4be2
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/disabledservice/example.json
@@ -0,0 +1,107 @@
+{
+
+"project_info": {
+ "project_id": "red-ant-1",
+ "project_number": "1234567890",
+ "name": "My Awesome App"
+},
+
+"client": [{
+ "client_info": {
+ "client_type": 1,
+ "android_client_info": {
+ "package_name": "com.example.app.free"
+ },
+ "mobilesdk_app_id": "mobilesdk-app-id-free"
+ },
+
+ "oauth_client": [{
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
+ }
+ },
+ {
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "AFDD9pi4klj5vQ6D8gab7UNawhw="
+ }
+ }],
+
+ "api_key": [{
+ "current_key": "android-api-key-free"
+ }],
+
+ "services": {
+ "analytics_service": {
+ "status": 2,
+ "analytics_property": {
+ "tracking_id": "UA-123456789"
+ }
+ },
+ "cloud_messaging_service": {
+ "status": 2
+ },
+ "appinvite_service": {
+ "status": 2
+ },
+ "google_signin_service": {
+ "status": 2
+ },
+ "ads_service": {
+ "status": 1,
+ "test_banner_ad_unit_id": "ca-app-pub-3940256099942544\/1033173712",
+ "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544\/6300978111"
+ },
+ "maps_service": {
+ "status": 2
+ }
+ }
+},
+
+{
+ "client_info": {
+ "client_type": 1,
+ "android_client_info": {
+ "package_name": "com.example.app.paid"
+ },
+ "mobilesdk_app_id": "mobilesdk-app-id-paid"
+ },
+
+ "oauth_client": [{
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.paid",
+ "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
+ }
+ }],
+
+ "api_key": [{
+ "current_key": "android-api-key-paid"
+ }],
+
+ "services": {
+ "analytics_service": {
+ "status": 2,
+ "analytics_property": {
+ "tracking_id": "UA-1923912413"
+ }
+ },
+ "cloud_messaging_service": {
+ "status": 2
+ },
+ "appinvite_service": {
+ "status": 2
+ },
+ "google_signin_service": {
+ "status": 2
+ },
+ "maps_service": {
+ "status": 2
+ }
+ }
+}]
+
+}
diff --git a/build-system/integration-test/src/test/resources/disabledservice/global_tracker.xml b/build-system/integration-test/src/test/resources/disabledservice/global_tracker.xml
new file mode 100644
index 0000000..1c97f21
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/disabledservice/global_tracker.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-123456789</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/disabledservice/values.xml b/build-system/integration-test/src/test/resources/disabledservice/values.xml
new file mode 100644
index 0000000..cc1b32f
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/disabledservice/values.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-123456789</string>
+ <string name="gcm_defaultSenderId" translatable="false">1234567890</string>
+ <string name="google_app_id" translatable="false">mobilesdk-app-id-free</string>
+ <string name="google_maps_key" translatable="false">android-api-key-free</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/flavor/example.json b/build-system/integration-test/src/test/resources/flavor/example.json
new file mode 100644
index 0000000..d54bc93
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/flavor/example.json
@@ -0,0 +1,102 @@
+{
+
+"project_info": {
+ "project_id": "red-ant-1",
+ "project_number": "1234567890",
+ "name": "My Awesome App"
+},
+
+"client": [{
+ "client_info": {
+ "client_type": 1,
+ "android_client_info": {
+ "package_name": "com.example.app.free"
+ },
+ "mobilesdk_app_id": "mobilesdk-app-id-free"
+ },
+
+ "oauth_client": [{
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
+ }
+ },
+ {
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "AFDD9pi4klj5vQ6D8gab7UNawhw="
+ }
+ }],
+
+ "api_key": [{
+ "current_key": "android-api-key-free"
+ }],
+
+ "services": {
+ "analytics_service": {
+ "status": 2,
+ "analytics_property": {
+ "tracking_id": "UA-123456789"
+ }
+ },
+ "cloud_messaging_service": {
+ "status": 2
+ },
+ "appinvite_service": {
+ "status": 2
+ },
+ "google_signin_service": {
+ "status": 2
+ },
+ "maps_service": {
+ "status": 2
+ }
+ }
+},
+
+{
+ "client_info": {
+ "client_type": 1,
+ "android_client_info": {
+ "package_name": "com.example.app.paid"
+ },
+ "mobilesdk_app_id": "mobilesdk-app-id-paid"
+ },
+
+ "oauth_client": [{
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.paid",
+ "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
+ }
+ }],
+
+ "api_key": [{
+ "current_key": "android-api-key-paid"
+ }],
+
+ "services": {
+ "analytics_service": {
+ "status": 2,
+ "analytics_property": {
+ "tracking_id": "UA-1923912413"
+ }
+ },
+ "cloud_messaging_service": {
+ "status": 2
+ },
+ "appinvite_service": {
+ "status": 2
+ },
+ "google_signin_service": {
+ "status": 2
+ },
+ "maps_service": {
+ "status": 2
+ }
+ }
+}]
+
+}
diff --git a/build-system/integration-test/src/test/resources/flavor/free.global_tracker.xml b/build-system/integration-test/src/test/resources/flavor/free.global_tracker.xml
new file mode 100644
index 0000000..1c97f21
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/flavor/free.global_tracker.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-123456789</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/flavor/free.values.xml b/build-system/integration-test/src/test/resources/flavor/free.values.xml
new file mode 100644
index 0000000..cc1b32f
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/flavor/free.values.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-123456789</string>
+ <string name="gcm_defaultSenderId" translatable="false">1234567890</string>
+ <string name="google_app_id" translatable="false">mobilesdk-app-id-free</string>
+ <string name="google_maps_key" translatable="false">android-api-key-free</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/flavor/paid.global_tracker.xml b/build-system/integration-test/src/test/resources/flavor/paid.global_tracker.xml
new file mode 100644
index 0000000..3e4a5ac
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/flavor/paid.global_tracker.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-1923912413</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/flavor/paid.values.xml b/build-system/integration-test/src/test/resources/flavor/paid.values.xml
new file mode 100644
index 0000000..15ee832
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/flavor/paid.values.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-1923912413</string>
+ <string name="gcm_defaultSenderId" translatable="false">1234567890</string>
+ <string name="google_app_id" translatable="false">mobilesdk-app-id-paid</string>
+ <string name="google_maps_key" translatable="false">android-api-key-paid</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/noservice/global_tracker.xml b/build-system/integration-test/src/test/resources/noservice/global_tracker.xml
new file mode 100644
index 0000000..1c97f21
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/noservice/global_tracker.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="ga_trackingId" translatable="false">UA-123456789</string>
+</resources>
diff --git a/build-system/integration-test/src/test/resources/noservice/no_services.json b/build-system/integration-test/src/test/resources/noservice/no_services.json
new file mode 100644
index 0000000..047ea3d
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/noservice/no_services.json
@@ -0,0 +1,40 @@
+{
+
+"project_info": {
+ "project_id": "red-ant-1",
+ "project_number": "1234567890",
+ "name": "My Awesome App"
+},
+
+"client": [{
+ "client_info": {
+ "client_type": 1,
+ "android_client_info": {
+ "package_name": "com.example.app.free"
+ },
+ "mobilesdk_app_id": "mobilesdk-app-id-free"
+ },
+
+ "oauth_client": [{
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "IUW99pi4cVA5vQ6D8gab7UNawhw="
+ }
+ },
+ {
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.app.free",
+ "certificate_hash": "AFDD9pi4klj5vQ6D8gab7UNawhw="
+ }
+ }],
+
+ "api_key": [{
+ "current_key": "android-api-key-free"
+ }],
+
+ "services": {}
+},]
+
+}
diff --git a/build-system/integration-test/src/test/resources/noservice/values.xml b/build-system/integration-test/src/test/resources/noservice/values.xml
new file mode 100644
index 0000000..c15f1ef
--- /dev/null
+++ b/build-system/integration-test/src/test/resources/noservice/values.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="gcm_defaultSenderId" translatable="false">1234567890</string>
+ <string name="google_app_id" translatable="false">mobilesdk-app-id-free</string>
+</resources>
diff --git a/base/build-system/integration-test/test-projects/.gitignore b/build-system/integration-test/test-projects/.gitignore
similarity index 100%
rename from base/build-system/integration-test/test-projects/.gitignore
rename to build-system/integration-test/test-projects/.gitignore
diff --git a/build-system/integration-test/test-projects/3rdPartyTests/app/build.gradle b/build-system/integration-test/test-projects/3rdPartyTests/app/build.gradle
new file mode 100644
index 0000000..e617ceb
--- /dev/null
+++ b/build-system/integration-test/test-projects/3rdPartyTests/app/build.gradle
@@ -0,0 +1,44 @@
+apply plugin: 'com.android.application'
+
+project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
+project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
+
+apply from: "../../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ deviceProvider project.fakeProvider
+ testServer project.fakeServer
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
+}
+
+project.afterEvaluate {
+ configure(fakeDebugAndroidTest) {
+ doLast {
+ String error = project.fakeProvider.isValid()
+ if (error != null) {
+ throw new GradleException("Failed DeviceProvider usage: " + error)
+ }
+ }
+ }
+
+ configure(fake2Upload) {
+ doLast {
+ String error = project.fakeServer.isValid()
+ if (error != null) {
+ throw new GradleException("Failed TestServer usage: " + error)
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':lib')
+}
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/AndroidManifest.xml b/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/AndroidManifest.xml
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/3rdPartyTests/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/build.gradle b/build-system/integration-test/test-projects/3rdPartyTests/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/build.gradle
rename to build-system/integration-test/test-projects/3rdPartyTests/build.gradle
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/build.gradle b/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/build.gradle
rename to build-system/integration-test/test-projects/3rdPartyTests/buildSrc/build.gradle
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeDevice.java b/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeDevice.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeDevice.java
rename to build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeDevice.java
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeProvider.java b/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeProvider.java
rename to build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeProvider.java
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeServer.java b/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeServer.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeServer.java
rename to build-system/integration-test/test-projects/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeServer.java
diff --git a/build-system/integration-test/test-projects/3rdPartyTests/lib/build.gradle b/build-system/integration-test/test-projects/3rdPartyTests/lib/build.gradle
new file mode 100644
index 0000000..38caefc
--- /dev/null
+++ b/build-system/integration-test/test-projects/3rdPartyTests/lib/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.library'
+
+project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
+project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
+
+apply from: "../../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ deviceProvider project.fakeProvider
+ testServer project.fakeServer
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
+}
+
+project.afterEvaluate {
+ configure(fakeDebugAndroidTest) {
+ doLast {
+ String error = project.fakeProvider.isValid()
+ if (error != null) {
+ throw new GradleException("Failed DeviceProvider usage: " + error)
+ }
+ }
+ }
+
+ configure(fake2Upload) {
+ doLast {
+ String error = project.fakeServer.isValid()
+ if (error != null) {
+ throw new GradleException("Failed TestServer usage: " + error)
+ }
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/res/values/strings.xml b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/res/values/strings.xml
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/androidTest/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..86402d5
--- /dev/null
+++ b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.testprojecttest.lib">
+ <application>
+ <activity
+ android:name="com.android.tests.testprojecttest.lib.LibActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/3rdPartyTests/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/3rdPartyTests/settings.gradle b/build-system/integration-test/test-projects/3rdPartyTests/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/3rdPartyTests/settings.gradle
rename to build-system/integration-test/test-projects/3rdPartyTests/settings.gradle
diff --git a/build-system/integration-test/test-projects/abiPureSplits/build.gradle b/build-system/integration-test/test-projects/abiPureSplits/build.gradle
new file mode 100644
index 0000000..56058cb
--- /dev/null
+++ b/build-system/integration-test/test-projects/abiPureSplits/build.gradle
@@ -0,0 +1,31 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ generatePureSplits true
+
+ defaultConfig {
+ minSdkVersion 21
+ ndk {
+ moduleName "hello-jni"
+ }
+
+ // This actual the app version code. Giving ourselves 1,000,000 values
+ versionCode = 123
+
+ }
+
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'armeabi-v7a', 'mips'
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java b/build-system/integration-test/test-projects/abiPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java
rename to build-system/integration-test/test-projects/abiPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/abiPureSplits/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java b/build-system/integration-test/test-projects/abiPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/jni/hello-jni.c b/build-system/integration-test/test-projects/abiPureSplits/src/main/jni/hello-jni.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/jni/hello-jni.c
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/jni/hello-jni.c
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/abiPureSplits/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/abiPureSplits/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/abiPureSplits/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/abiPureSplits/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/androidManifestInTest/build.gradle b/build-system/integration-test/test-projects/androidManifestInTest/build.gradle
new file mode 100644
index 0000000..c82b28f
--- /dev/null
+++ b/build-system/integration-test/test-projects/androidManifestInTest/build.gradle
@@ -0,0 +1,27 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+ debugCompile 'com.android.support:support-v13:13.0.0'
+
+ compile 'com.google.android.gms:play-services:3.1.36'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion rootProject.buildToolsVersion
+
+ testBuildType "debug"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/AndroidManifest.xml b/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/AndroidManifest.xml
rename to build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/res/values/strings.xml b/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/res/values/strings.xml
rename to build-system/integration-test/test-projects/androidManifestInTest/src/androidTest/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/androidManifestInTest/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/androidManifestInTest/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/androidManifestInTest/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/androidManifestInTest/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/drawable/icon.png
similarity index 100%
copy from base/build-system/integration-test/test-projects/aidl/src/main/res/drawable/icon.png
copy to build-system/integration-test/test-projects/androidManifestInTest/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/androidManifestInTest/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/androidManifestInTest/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/androidTestLibDep/build.gradle b/build-system/integration-test/test-projects/androidTestLibDep/build.gradle
new file mode 100644
index 0000000..6e818a4
--- /dev/null
+++ b/build-system/integration-test/test-projects/androidTestLibDep/build.gradle
@@ -0,0 +1,16 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ androidTestCompile 'com.google.guava:guava:18.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java b/build-system/integration-test/test-projects/androidTestLibDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
rename to build-system/integration-test/test-projects/androidTestLibDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/androidTestLibDep/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/androidTestLibDep/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/java/com/android/tests/libdeps/MainActivity.java b/build-system/integration-test/test-projects/androidTestLibDep/src/main/java/com/android/tests/libdeps/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/java/com/android/tests/libdeps/MainActivity.java
rename to build-system/integration-test/test-projects/androidTestLibDep/src/main/java/com/android/tests/libdeps/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
copy from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-hdpi/ic_launcher.png
copy to build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
copy from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-ldpi/ic_launcher.png
copy to build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/layout/lib1_main.xml b/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/layout/lib1_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/layout/lib1_main.xml
rename to build-system/integration-test/test-projects/androidTestLibDep/src/main/res/layout/lib1_main.xml
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/androidTestLibDep/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/api/app/build.gradle b/build-system/integration-test/test-projects/api/app/build.gradle
new file mode 100644
index 0000000..b444576
--- /dev/null
+++ b/build-system/integration-test/test-projects/api/app/build.gradle
@@ -0,0 +1,20 @@
+// ATTENTION -- hash value of this file is checked in the corresponding
+// integration test. Please make sure any changes you make here are
+// backwards compatible.
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+project.afterEvaluate {
+ if (android.applicationVariants.size() != 2) {
+ throw new GradleException("Wrong number of app variants!")
+ }
+
+ if (android.testVariants.size() != 1) {
+ throw new GradleException("Wrong number of test variants!")
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
copy to build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/api/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/api/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/api/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/api/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/api/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/api/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/api/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidManifestInTest/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/api/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/api/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/api/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/api/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/api/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/api/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/api/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/api/app/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/api/app/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/app/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/api/app/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/api/build.gradle b/build-system/integration-test/test-projects/api/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/build.gradle
rename to build-system/integration-test/test-projects/api/build.gradle
diff --git a/base/build-system/integration-test/test-projects/api/lib/blah/foo.txt b/build-system/integration-test/test-projects/api/lib/blah/foo.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/blah/foo.txt
rename to build-system/integration-test/test-projects/api/lib/blah/foo.txt
diff --git a/build-system/integration-test/test-projects/api/lib/build.gradle b/build-system/integration-test/test-projects/api/lib/build.gradle
new file mode 100644
index 0000000..4248e9d
--- /dev/null
+++ b/build-system/integration-test/test-projects/api/lib/build.gradle
@@ -0,0 +1,29 @@
+// ATTENTION -- hash value of this file is checked in the corresponding
+// integration test. Please make sure any changes you make here are
+// backwards compatible.
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+// query for all (non-test) variants and inject a new step in the builds
+android.libraryVariants.all { variant ->
+ // create a task that copies some additional data in the library bundle
+ def copyBlahTask = tasks.create(name: "copy${variant.name.capitalize()}Blah", type: Copy) {
+ from file("$project.projectDir/blah")
+ destinationDir file("${buildDir}/bundles/${variant.dirName}")
+ }
+
+ // now make the package task depend on it
+ // only one output for libraries
+ variant.outputs.get(0).packageLibrary.dependsOn copyBlahTask
+}
+
+project.afterEvaluate {
+ if (android.libraryVariants.size() != 2) {
+ throw new GradleException("Wrong number of app variants!")
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/integration-test/test-projects/api/lib/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
rename to build-system/integration-test/test-projects/api/lib/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/api/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/api/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/Lib2.java b/build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/Lib2.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/Lib2.java
rename to build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/Lib2.java
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/MainActivity.java b/build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
rename to build-system/integration-test/test-projects/api/lib/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/api/lib/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/api/lib/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/api/lib/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/res/layout/lib2_main.xml b/build-system/integration-test/test-projects/api/lib/src/main/res/layout/lib2_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/res/layout/lib2_main.xml
rename to build-system/integration-test/test-projects/api/lib/src/main/res/layout/lib2_main.xml
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/api/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/api/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/api/lib/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt b/build-system/integration-test/test-projects/api/lib/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/lib/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
rename to build-system/integration-test/test-projects/api/lib/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
diff --git a/base/build-system/integration-test/test-projects/api/settings.gradle b/build-system/integration-test/test-projects/api/settings.gradle
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/settings.gradle
copy to build-system/integration-test/test-projects/api/settings.gradle
diff --git a/build-system/integration-test/test-projects/applibtest/app/build.gradle b/build-system/integration-test/test-projects/applibtest/app/build.gradle
new file mode 100644
index 0000000..cc4004b
--- /dev/null
+++ b/build-system/integration-test/test-projects/applibtest/app/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib')
+}
diff --git a/base/apps/SdkController/proguard-project.txt b/build-system/integration-test/test-projects/applibtest/app/proguard-project.txt
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/proguard-project.txt
rename to build-system/integration-test/test-projects/applibtest/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/androidTest/AndroidManifest.xml b/build-system/integration-test/test-projects/applibtest/app/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/androidTest/AndroidManifest.xml
rename to build-system/integration-test/test-projects/applibtest/app/src/androidTest/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/integration-test/test-projects/applibtest/app/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/applibtest/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/applibtest/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/applibtest/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/applibtest/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/applibtest/build.gradle b/build-system/integration-test/test-projects/applibtest/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/build.gradle
rename to build-system/integration-test/test-projects/applibtest/build.gradle
diff --git a/build-system/integration-test/test-projects/applibtest/lib/build.gradle b/build-system/integration-test/test-projects/applibtest/lib/build.gradle
new file mode 100644
index 0000000..d03f433
--- /dev/null
+++ b/build-system/integration-test/test-projects/applibtest/lib/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'com.android.library'
+
+apply from: "../../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ testApplicationId = "com.android.tests.testprojecttest.testlib"
+ }
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/applibtest/app/proguard-project.txt b/build-system/integration-test/test-projects/applibtest/lib/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/app/proguard-project.txt
rename to build-system/integration-test/test-projects/applibtest/lib/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
rename to build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
rename to build-system/integration-test/test-projects/applibtest/lib/src/androidTest/java/com/android/tests/testprojecttest/test/AllTests.java
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/res/values/strings.xml b/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/src/androidTest/res/values/strings.xml
rename to build-system/integration-test/test-projects/applibtest/lib/src/androidTest/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/applibtest/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/applibtest/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl b/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
similarity index 100%
rename from base/build-system/integration-test/test-projects/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
rename to build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl b/build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
similarity index 100%
rename from base/build-system/integration-test/test-projects/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
rename to build-system/integration-test/test-projects/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java b/build-system/integration-test/test-projects/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
rename to build-system/integration-test/test-projects/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/applibtest/lib/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/applibtest/lib/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/applibtest/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/applibtest/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/applibtest/settings.gradle b/build-system/integration-test/test-projects/applibtest/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/settings.gradle
rename to build-system/integration-test/test-projects/applibtest/settings.gradle
diff --git a/build-system/integration-test/test-projects/applicationIdInLibsTest/app/build.gradle b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/build.gradle
new file mode 100644
index 0000000..a56ffab
--- /dev/null
+++ b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.example.manifest_merger_example"
+ minSdkVersion 15
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ productFlavors {
+ flavor {
+ applicationId "com.example.manifest_merger_example.flavor"
+ minSdkVersion 15
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+dependencies {
+ compile project(':examplelibrary')
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/flavor/AndroidManifest.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/flavor/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/flavor/AndroidManifest.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/flavor/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/layout/activity_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/layout/activity_main.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/layout/activity_main.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/menu/menu_main.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/menu/menu_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/menu/menu_main.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/menu/menu_main.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/dimens.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/dimens.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/dimens.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/dimens.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/styles.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/styles.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/styles.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/app/src/main/res/values/styles.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/build.gradle b/build-system/integration-test/test-projects/applicationIdInLibsTest/build.gradle
similarity index 100%
copy from base/build-system/integration-test/test-projects/applicationIdInLibsTest/build.gradle
copy to build-system/integration-test/test-projects/applicationIdInLibsTest/build.gradle
diff --git a/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/build.gradle b/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/build.gradle
new file mode 100644
index 0000000..bcb27a8
--- /dev/null
+++ b/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+}
+
+android.testVariants.all {
+ it.mergedFlavor.manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example.flavor"]
+}
+
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java b/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java b/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/examplelibrary/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/settings.gradle b/build-system/integration-test/test-projects/applicationIdInLibsTest/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/settings.gradle
rename to build-system/integration-test/test-projects/applicationIdInLibsTest/settings.gradle
diff --git a/build-system/integration-test/test-projects/artifactApi/build.gradle b/build-system/integration-test/test-projects/artifactApi/build.gradle
new file mode 100644
index 0000000..e9e54bd
--- /dev/null
+++ b/build-system/integration-test/test-projects/artifactApi/build.gradle
@@ -0,0 +1,142 @@
+// ATTENTION -- hash value of this file is checked in the corresponding
+// integration test. Please make sure any changes you make here are
+// backwards compatible.
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+import com.android.builder.model.ArtifactMetaData
+import com.android.builder.model.SourceProvider
+
+// create a configuration to allow dependencies specific to the Java artifact
+configurations {
+ foo
+}
+
+// Register an artifact type and tie it to the name "__test__".
+// This name will show up in Studio. It must be unique
+android.registerArtifactType("__test__", false, ArtifactMetaData.TYPE_JAVA)
+
+// register a new SourceProvider per build type, and associate it with the artifact
+// registered above.
+android.buildTypes.all { type ->
+ project.android.registerBuildTypeSourceProvider(
+ "__test__", // registered name of the artifact type
+ type, // associate it with a BuildType
+ getProvider("buildType:$type.name")) // the source provider
+}
+
+// Same with ProductFlavor
+android.productFlavors.all { flavor ->
+ project.android.registerProductFlavorSourceProvider(
+ "__test__", // registered name of the artifact type
+ flavor, // associate it with a ProductFlavor
+ getProvider("productFlavor:$flavor.name")) // the source provider
+}
+
+// now register a new (java) artifact for each variant, still associated
+// with the artifact type registered above.
+android.applicationVariants.all { variant ->
+ project.android.registerJavaArtifact(
+ "__test__", // registered name of the artifact type
+ variant, // associate it with a variant
+ "assemble:$variant.name", // name of the assemble task for this artifact
+ "compile:$variant.name", // name of the java compile task for this artifact
+ [], // generated source folders
+ [], // tasks to generate sources etc. in the IDE.
+ configurations.foo,
+ new File("classesFolder:$variant.name"), // location of the classes folder (compile output)
+ new File("resources:$variant.name"), // location of the resources folder
+ getProvider("provider:$variant.name")) // source provider specific to this variant for the artifact.
+}
+
+// after the artifact is created, add a dependency
+dependencies {
+ foo files('libs/util-1.0.jar')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ flavorDimensions "pricing", "releaseType"
+
+ productFlavors {
+
+ beta {
+ dimension "releaseType"
+ }
+
+ normal {
+ dimension "releaseType"
+ }
+
+ free {
+ dimension "pricing"
+ }
+
+ paid {
+ dimension "pricing"
+ }
+ }
+}
+
+public class SourceProviderImpl implements SourceProvider, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final String name;
+
+ SourceProviderImpl(String name) {
+ this.name = name
+ }
+
+ String getName() {
+ return name
+ }
+
+ File getManifestFile() {
+ return new File(name)
+ }
+
+ Collection<File> getJavaDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getResourcesDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getAidlDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getRenderscriptDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getCDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getCppDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getJniLibsDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getResDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getAssetsDirectories() {
+ return Collections.emptyList()
+ }
+}
+
+SourceProvider getProvider(String name) {
+ return new SourceProviderImpl(name)
+}
diff --git a/base/build-system/integration-test/test-projects/artifactApi/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/artifactApi/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/artifactApi/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/artifactApi/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/artifactApi/src/main/java/com/android/tests/overlay2/Main.java b/build-system/integration-test/test-projects/artifactApi/src/main/java/com/android/tests/overlay2/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/artifactApi/src/main/java/com/android/tests/overlay2/Main.java
rename to build-system/integration-test/test-projects/artifactApi/src/main/java/com/android/tests/overlay2/Main.java
diff --git a/base/build-system/integration-test/test-projects/api/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/no_overlay.png b/build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/no_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/no_overlay.png
rename to build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/no_overlay.png
diff --git a/base/build-system/integration-test/test-projects/artifactApi/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/artifactApi/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/artifactApi/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/artifactApi/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/artifactApi/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/artifactApi/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/artifactApi/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/artifactApi/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/assets/app/build.gradle b/build-system/integration-test/test-projects/assets/app/build.gradle
new file mode 100644
index 0000000..fc7c4c3
--- /dev/null
+++ b/build-system/integration-test/test-projects/assets/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+dependencies {
+ compile project(':lib')
+}
diff --git a/base/build-system/integration-test/test-projects/assets/app/proguard-project.txt b/build-system/integration-test/test-projects/assets/app/proguard-project.txt
similarity index 100%
copy from base/build-system/integration-test/test-projects/assets/app/proguard-project.txt
copy to build-system/integration-test/test-projects/assets/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/androidTest/java/com/android/tests/assets/app/MainActivityTest.java b/build-system/integration-test/test-projects/assets/app/src/androidTest/java/com/android/tests/assets/app/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/androidTest/java/com/android/tests/assets/app/MainActivityTest.java
rename to build-system/integration-test/test-projects/assets/app/src/androidTest/java/com/android/tests/assets/app/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/assets/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/assets/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/assets/App.txt b/build-system/integration-test/test-projects/assets/app/src/main/assets/App.txt
similarity index 100%
copy from base/build-system/integration-test/test-projects/assets/app/src/main/assets/App.txt
copy to build-system/integration-test/test-projects/assets/app/src/main/assets/App.txt
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/App.java b/build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/App.java
rename to build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/App.java
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/MainActivity.java b/build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/MainActivity.java
rename to build-system/integration-test/test-projects/assets/app/src/main/java/com/android/tests/assets/app/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
copy from base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-hdpi/icon.png
copy to build-system/integration-test/test-projects/assets/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
copy from base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-ldpi/icon.png
copy to build-system/integration-test/test-projects/assets/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/artifactApi/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/assets/app/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/assets/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/assets/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/assets/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/assets/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/assets/build.gradle b/build-system/integration-test/test-projects/assets/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/build.gradle
rename to build-system/integration-test/test-projects/assets/build.gradle
diff --git a/build-system/integration-test/test-projects/assets/lib/build.gradle b/build-system/integration-test/test-projects/assets/lib/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/assets/lib/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/applibtest/lib/proguard-project.txt b/build-system/integration-test/test-projects/assets/lib/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/applibtest/lib/proguard-project.txt
rename to build-system/integration-test/test-projects/assets/lib/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/androidTest/java/com/android/tests/assets/lib/MainActivityTest.java b/build-system/integration-test/test-projects/assets/lib/src/androidTest/java/com/android/tests/assets/lib/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/androidTest/java/com/android/tests/assets/lib/MainActivityTest.java
rename to build-system/integration-test/test-projects/assets/lib/src/androidTest/java/com/android/tests/assets/lib/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/assets/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/assets/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/assets/Lib.txt b/build-system/integration-test/test-projects/assets/lib/src/main/assets/Lib.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/assets/Lib.txt
rename to build-system/integration-test/test-projects/assets/lib/src/main/assets/Lib.txt
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/Lib.java b/build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/Lib.java
rename to build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/Lib.java
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/MainActivity.java b/build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/MainActivity.java
rename to build-system/integration-test/test-projects/assets/lib/src/main/java/com/android/tests/assets/lib/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/assets/lib/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/res/layout/lib_main.xml b/build-system/integration-test/test-projects/assets/lib/src/main/res/layout/lib_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/res/layout/lib_main.xml
rename to build-system/integration-test/test-projects/assets/lib/src/main/res/layout/lib_main.xml
diff --git a/base/build-system/integration-test/test-projects/assets/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/assets/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/assets/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/assets/settings.gradle b/build-system/integration-test/test-projects/assets/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/settings.gradle
rename to build-system/integration-test/test-projects/assets/settings.gradle
diff --git a/build-system/integration-test/test-projects/attrOrder/app/build.gradle b/build-system/integration-test/test-projects/attrOrder/app/build.gradle
new file mode 100644
index 0000000..5c1ce16
--- /dev/null
+++ b/build-system/integration-test/test-projects/attrOrder/app/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+dependencies{
+ compile project(":lib")
+}
+
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/attrOrder/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/attrOrder/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/attrOrder/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/attrOrder/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/attrOrder/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/attrOrder/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/attrOrder/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/attrOrder/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/attrOrder/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/attrOrder/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings_additional.xml b/build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings_additional.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings_additional.xml
rename to build-system/integration-test/test-projects/attrOrder/app/src/main/res/values/strings_additional.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/build.gradle b/build-system/integration-test/test-projects/attrOrder/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/build.gradle
rename to build-system/integration-test/test-projects/attrOrder/build.gradle
diff --git a/build-system/integration-test/test-projects/attrOrder/lib/build.gradle b/build-system/integration-test/test-projects/attrOrder/lib/build.gradle
new file mode 100644
index 0000000..3b8743b
--- /dev/null
+++ b/build-system/integration-test/test-projects/attrOrder/lib/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/AndroidManifest.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/AndroidManifest.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivity.java b/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivity.java
rename to build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivity.java
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivityTest.java b/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivityTest.java
rename to build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/java/com/android/tests/libstest/lib/test/TestActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/layout/main.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/layout/main.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings_additional.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings_additional.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings_additional.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/values/strings_additional.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/java/com/android/tests/libstest/lib/Lib.java b/build-system/integration-test/test-projects/attrOrder/lib/src/main/java/com/android/tests/libstest/lib/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/java/com/android/tests/libstest/lib/Lib.java
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/java/com/android/tests/libstest/lib/Lib.java
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/layout/lib2_main.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/layout/lib2_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/layout/lib2_main.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/res/layout/lib2_main.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/styleable.xml b/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/styleable.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/styleable.xml
rename to build-system/integration-test/test-projects/attrOrder/lib/src/main/res/values/styleable.xml
diff --git a/base/build-system/integration-test/test-projects/attrOrder/settings.gradle b/build-system/integration-test/test-projects/attrOrder/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/settings.gradle
rename to build-system/integration-test/test-projects/attrOrder/settings.gradle
diff --git a/build-system/integration-test/test-projects/basic/build.gradle b/build-system/integration-test/test-projects/basic/build.gradle
new file mode 100644
index 0000000..c426a7d
--- /dev/null
+++ b/build-system/integration-test/test-projects/basic/build.gradle
@@ -0,0 +1,121 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply from: "../commonLocalRepo.gradle"
+
+apply plugin: 'com.android.application'
+
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+ compile 'com.google.android.gms:play-services:3.1.36'
+
+ debugCompile 'com.android.support:support-v13:13.0.0'
+ releaseCompile 'com.android.support:support-v13:21.0.0'
+
+ // hamcrest-library depends on hamcrest-core, both provide a /LICENSE.txt file
+ // which used to cause packaging conflict. We added a special case for license files.
+ androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testBuildType "debug"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+
+ testInstrumentationRunner "android.test.InstrumentationTestRunner"
+ testInstrumentationRunnerArgument "size", "medium"
+
+ testHandleProfiling false
+
+ buildConfigField "boolean", "DEFAULT", "true"
+ buildConfigField "String", "FOO", "\"foo\""
+ buildConfigField "String", "FOO", "\"foo2\""
+
+ resValue "string", "foo", "foo"
+
+ resConfig "en"
+ resConfigs "hdpi"
+
+ manifestPlaceholders = [ "someKey":12 ]
+ }
+
+ buildTypes {
+ debug {
+ applicationIdSuffix ".debug"
+
+ testCoverageEnabled true
+
+ buildConfigField "String", "FOO", "\"bar1\""
+ buildConfigField "String", "FOO", "\"bar\""
+
+ resValue "string", "foo", "foo2"
+
+ useJack project.ext.useJack
+ }
+ }
+
+ aaptOptions {
+ noCompress 'txt'
+ ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
+ }
+
+ adbOptions {
+ timeOutInMs 5000 // 5 seconds in ms.
+ installOptions "-d","-t"
+ }
+
+ lintOptions {
+ // set to true to turn off analysis progress reporting by lint
+ quiet true
+ // if true, stop the gradle build if errors are found
+ abortOnError false
+ // if true, only report errors
+ ignoreWarnings true
+ // if true, emit full/absolute paths to files with errors (true by default)
+ //absolutePaths true
+ // if true, check all issues, including those that are off by default
+ checkAllWarnings true
+ // if true, treat all warnings as errors
+ warningsAsErrors true
+ // turn off checking the given issue id's
+ disable 'TypographyFractions','TypographyQuotes'
+ // turn on the given issue id's
+ enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
+ // check *only* the given issue id's
+ check 'NewApi', 'InlinedApi'
+ // if true, don't include source code lines in the error output
+ noLines true
+ // if true, show all locations for an error, do not truncate lists, etc.
+ showAll true
+ // Fallback lint configuration (default severities, etc.)
+ lintConfig file("default-lint.xml")
+ // if true, generate a text report of issues (false by default)
+ textReport true
+ // location to write the output; can be a file or 'stdout'
+ textOutput 'stdout'
+ // if true, generate an XML report for use by for example Jenkins
+ xmlReport false
+ // file to write report to (if not specified, defaults to lint-results.xml)
+ xmlOutput file("lint-report.xml")
+ // if true, generate an HTML report (with issue explanations, sourcecode, etc)
+ htmlReport true
+ // optional path to report (default will be lint-results.html in the builddir)
+ htmlOutput file("lint-report.html")
+ // Reduce severity of this check to just informational
+ informational 'LogConditional'
+ }
+
+ // Override the versionCode of the release version
+ applicationVariants.all { variant ->
+ if (variant.buildType.name == "release") {
+ variant.outputs.get(0).versionCodeOverride = 13
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/basic/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/basic/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/basic/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/basic/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/basic/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/assets/notice.txt b/build-system/integration-test/test-projects/basic/src/main/assets/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/assets/notice.txt
rename to build-system/integration-test/test-projects/basic/src/main/assets/notice.txt
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/res/.vcs/INDEX b/build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/Base.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/basic/src/main/res/.vcs/INDEX
copy to build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/Base.java
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.jpeg b/build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/SomeClass.java
similarity index 100%
copy from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.jpeg
copy to build-system/integration-test/test-projects/basic/src/main/java/com/android/tests/basic/SomeClass.java
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/res/.vcs/INDEX b/build-system/integration-test/test-projects/basic/src/main/res/.vcs/INDEX
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/res/.vcs/INDEX
rename to build-system/integration-test/test-projects/basic/src/main/res/.vcs/INDEX
diff --git a/base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/drawable/icon.png b/build-system/integration-test/test-projects/basic/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/attrOrder/lib/src/androidTest/res/drawable/icon.png
rename to build-system/integration-test/test-projects/basic/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/basic/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/basic/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/res/raw/notice.txt b/build-system/integration-test/test-projects/basic/src/main/res/raw/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/res/raw/notice.txt
rename to build-system/integration-test/test-projects/basic/src/main/res/raw/notice.txt
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/basic/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/basic/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/basic/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/basic/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/basic/src/release/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/basicMultiFlavors/build.gradle b/build-system/integration-test/test-projects/basicMultiFlavors/build.gradle
new file mode 100644
index 0000000..00102d4
--- /dev/null
+++ b/build-system/integration-test/test-projects/basicMultiFlavors/build.gradle
@@ -0,0 +1,30 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ flavorDimensions "pricing", "releaseType"
+
+ productFlavors {
+
+ beta {
+ dimension "releaseType"
+ }
+
+ normal {
+ dimension "releaseType"
+ }
+
+ free {
+ dimension "pricing"
+ }
+
+ paid {
+ dimension "pricing"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/f1/res/values/strings.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/f1/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/f1/res/values/strings.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/f1/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/f1Staging/res/values/strings.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/f1Staging/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/f1Staging/res/values/strings.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/f1Staging/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/AndroidManifest.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/AndroidManifest.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/f2/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/java/com/android/tests/flavored/OtherActivity.java b/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/java/com/android/tests/flavored/OtherActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/java/com/android/tests/flavored/OtherActivity.java
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/f2/java/com/android/tests/flavored/OtherActivity.java
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/layout/main2.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/layout/main2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/layout/main2.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/layout/main2.xml
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/values/strings.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/values/strings.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/f2/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/java/com/android/tests/flavored/Main.java b/build-system/integration-test/test-projects/basicMultiFlavors/src/main/java/com/android/tests/flavored/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/java/com/android/tests/flavored/Main.java
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/main/java/com/android/tests/flavored/Main.java
diff --git a/base/build-system/integration-test/test-projects/basic/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/build.gradle b/build-system/integration-test/test-projects/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/build.gradle
rename to build-system/integration-test/test-projects/build.gradle
diff --git a/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/build.gradle b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/build.gradle
new file mode 100644
index 0000000..6cddd50
--- /dev/null
+++ b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/build.gradle
@@ -0,0 +1,35 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ generatePureSplits true
+
+ defaultConfig {
+ minSdkVersion 21
+ ndk {
+ moduleName "hello-jni"
+ }
+
+ // This actual the app version code. Giving ourselves 1,000,000 values
+ versionCode = 123
+
+ }
+
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'armeabi-v7a', 'mips'
+ }
+ density {
+ enable true
+ exclude "ldpi", "tvdpi", "xxxhdpi"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/androidTest/java/com/example/combinedSplits/DemoActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/java/com/example/combinedSplits/DemoActivity.java
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/jni/hello-jni.c b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/jni/hello-jni.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/jni/hello-jni.c
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/jni/hello-jni.c
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/combinedAbiDensityPureSplits/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/build.gradle b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/build.gradle
new file mode 100644
index 0000000..f4fae21
--- /dev/null
+++ b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/build.gradle
@@ -0,0 +1,29 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ generatePureSplits true
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 21
+ targetSdkVersion 21
+ }
+
+ splits {
+ density {
+ enable true
+ exclude "ldpi", "tvdpi", "xxxhdpi"
+ }
+ language {
+ enable true
+ include "fr,fr-rBE", "fr-rCA", "en"
+ }
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable/ic_launcher.png b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable/ic_launcher.png
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/drawable/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-en/strings.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-en/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-en/strings.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-en/strings.xml
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rBE/strings.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rBE/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rBE/strings.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rBE/strings.xml
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rCA/strings.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rCA/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rCA/strings.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr-rCA/strings.xml
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr/strings.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr/strings.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values-fr/strings.xml
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/combinedDensityAndLanguagePureSplits/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/commonBuildScript.gradle b/build-system/integration-test/test-projects/commonBuildScript.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/commonBuildScript.gradle
rename to build-system/integration-test/test-projects/commonBuildScript.gradle
diff --git a/base/build-system/integration-test/test-projects/commonBuildScriptExperimental.gradle b/build-system/integration-test/test-projects/commonBuildScriptExperimental.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/commonBuildScriptExperimental.gradle
rename to build-system/integration-test/test-projects/commonBuildScriptExperimental.gradle
diff --git a/build-system/integration-test/test-projects/commonGradlePluginVersion.gradle b/build-system/integration-test/test-projects/commonGradlePluginVersion.gradle
new file mode 100644
index 0000000..59852a4
--- /dev/null
+++ b/build-system/integration-test/test-projects/commonGradlePluginVersion.gradle
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+gradleVersion = System.env.CUSTOM_GRADLE
+experimentalGradleVersion = System.env.CUSTOM_EXPERIMENTAL_GRADLE
+
+if (gradleVersion == null && System.env.CUSTOM_REPO != null) {
+ // Extract the version from the top level buildSrc, relative to CUSTOM_REPO.
+ // Have a fake environment for buildSrc/base/version.gradle can insert the version info into.
+ def env = new Object() {
+ Object project = new Object() {
+ Object ext = new Object() {
+ String buildVersion
+ String baseVersion
+ String experimentalVersion
+ String apiVersion
+ String nativeApiVersion
+ }
+ }
+ }
+ apply from: "$System.env.CUSTOM_REPO/../../tools/buildSrc/base/version.gradle", to: env
+ gradleVersion = env.project.ext.buildVersion
+ experimentalGradleVersion = env.project.ext.experimentalVersion
+}
+
+if (gradleVersion == null) {
+ throw new RuntimeException(
+ "Unknown Android Gradle plugin version. Set 'CUSTOM_REPO' or 'CUSTOM_GRADLE' to " +
+ "proceed.")
+}
diff --git a/build-system/integration-test/test-projects/commonHeader.gradle b/build-system/integration-test/test-projects/commonHeader.gradle
new file mode 100644
index 0000000..2bf783e
--- /dev/null
+++ b/build-system/integration-test/test-projects/commonHeader.gradle
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+ext {
+ buildToolsVersion = [System.env.CUSTOM_BUILDTOOLS, project.properties["CUSTOM_BUILDTOOLS"], '23.0.2'].find()
+ latestCompileSdk = [System.env.COMPILE_SDK, project.properties["CUSTOM_COMPILE_SDK"], 23].find() as int
+
+ // Last SDK that contained java 6 bytecode.
+ java6BasedSdk = 19
+ // Some of our tests use deprecated RenderScript APIs. TODO: upgrade.
+ oldSdkForRenderscript = 15
+
+ useJack = System.env.CUSTOM_JACK ||
+ Boolean.valueOf((String) project.properties[
+ "com.android.build.gradle.integratonTest.useJack"])
+
+ minifyEnabled = System.env.CUSTOM_MINIFY ||
+ Boolean.valueOf((String) project.properties[
+ "com.android.build.gradle.integratonTest.minifyEnabled"])
+
+ useComponentModel = Boolean.valueOf((String) project.properties[
+ "com.android.build.gradle.integratonTest.useComponentModel"])
+
+ def remoteTestProvider = System.env.REMOTE_TEST_PROVIDER
+ if (remoteTestProvider != null) {
+ plugins.withId('com.android.application') {
+ apply plugin: remoteTestProvider
+ }
+ plugins.withId('com.android.library') {
+ apply plugin: remoteTestProvider
+ }
+ plugins.withId('com.android.model.application') {
+ apply plugin: remoteTestProvider
+ }
+ plugins.withId('com.android.model.library') {
+ apply plugin: remoteTestProvider
+ }
+ }
+}
+
+plugins.withId("com.android.application") { plugin ->
+ plugin.extension.defaultConfig.useJack = ext.useJack
+}
+
+plugins.withId("com.android.library") { plugin ->
+ plugin.extension.defaultConfig.useJack = ext.useJack
+}
diff --git a/build-system/integration-test/test-projects/commonLocalRepo.gradle b/build-system/integration-test/test-projects/commonLocalRepo.gradle
new file mode 100644
index 0000000..5aed50d
--- /dev/null
+++ b/build-system/integration-test/test-projects/commonLocalRepo.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+repositories {
+ if (System.env.CUSTOM_REPO != null) {
+ maven { url System.env.CUSTOM_REPO }
+ } else if (System.env.USE_EXTERNAL_REPO) {
+ jcenter()
+ } else {
+ throw new RuntimeException("Neither CUSTOM_REPO nor USE_EXTERNAL_REPO set.")
+ }
+ if (System.env.ADDITIONAL_TEST_CUSTOM_REPO != null) {
+ maven { url System.env.ADDITIONAL_TEST_CUSTOM_REPO }
+ }
+ if (System.env.DATA_BINDING_REPO != null) {
+ maven { url System.env.DATA_BINDING_REPO}
+ }
+ if (System.env.DATA_BINDING_INTERNAL_REPO != null) {
+ maven { url System.env.DATA_BINDING_INTERNAL_REPO}
+ }
+}
diff --git a/build-system/integration-test/test-projects/componentModel/build.gradle b/build-system/integration-test/test-projects/componentModel/build.gradle
new file mode 100644
index 0000000..2bb06ff
--- /dev/null
+++ b/build-system/integration-test/test-projects/componentModel/build.gradle
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+
+
+apply plugin: "com.android.model.application"
+
+model {
+ android {
+ compileSdkVersion = rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ }
+ android.productFlavors {
+ create("f1")
+ create("f2")
+ }
+
+ android.sources {
+ main {
+ java {
+ source {
+ srcDir "src/main/java"
+ }
+ }
+ }
+ f1 {
+ res {
+
+ }
+ }
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/componentModel/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/componentModel/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/componentModel/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/componentModel/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/componentModel/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/componentModel/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/componentModel/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/componentModel/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/componentModel/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/componentModel/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/componentModel/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/basicMultiFlavors/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/componentModel/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/componentModel/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/componentModel/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/componentModel/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/componentModel/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/componentModel/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/componentModel/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/componentModel/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/componentModel/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/componentModel/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/componentModel/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/componentModel/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/componentModel/src/release/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/customArtifactDep/app/build.gradle b/build-system/integration-test/test-projects/customArtifactDep/app/build.gradle
new file mode 100644
index 0000000..e056df4
--- /dev/null
+++ b/build-system/integration-test/test-projects/customArtifactDep/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+dependencies {
+ compile project(path: ':util', configuration: 'custom')
+}
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/java/com/example/android/multiproject/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/java/com/example/android/multiproject/MainActivity.java
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/java/com/example/android/multiproject/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/customArtifactDep/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/build.gradle b/build-system/integration-test/test-projects/customArtifactDep/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/build.gradle
rename to build-system/integration-test/test-projects/customArtifactDep/build.gradle
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/settings.gradle b/build-system/integration-test/test-projects/customArtifactDep/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/settings.gradle
rename to build-system/integration-test/test-projects/customArtifactDep/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/util/build.gradle b/build-system/integration-test/test-projects/customArtifactDep/util/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/util/build.gradle
rename to build-system/integration-test/test-projects/customArtifactDep/util/build.gradle
diff --git a/base/build-system/integration-test/test-projects/customArtifactDep/util/src/custom/java/com/example/android/custom/Foo.java b/build-system/integration-test/test-projects/customArtifactDep/util/src/custom/java/com/example/android/custom/Foo.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/customArtifactDep/util/src/custom/java/com/example/android/custom/Foo.java
rename to build-system/integration-test/test-projects/customArtifactDep/util/src/custom/java/com/example/android/custom/Foo.java
diff --git a/build-system/integration-test/test-projects/daggerOne/build.gradle b/build-system/integration-test/test-projects/daggerOne/build.gradle
new file mode 100644
index 0000000..e14a6b8
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerOne/build.gradle
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+repositories {
+ // TODO: add Dagger to prebuilts
+ jcenter()
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+dependencies {
+ compile "com.squareup.dagger:dagger:1.2.2"
+ provided "com.squareup.dagger:dagger-compiler:1.2.2"
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/daggerOne/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/daggerOne/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cac9d83
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerOne/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests">
+ <application android:label="Dagger One">
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/AppModule.java b/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/AppModule.java
new file mode 100644
index 0000000..f6c26a8
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/AppModule.java
@@ -0,0 +1,26 @@
+/*
+ * 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.tests;
+
+ at dagger.Module(injects = { MainActivity.class })
+public class AppModule {
+ @dagger.Provides
+ SomeService provideSomeService() {
+ SomeService someService = new SomeService("from module");
+ return someService;
+ }
+}
diff --git a/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/MainActivity.java
new file mode 100644
index 0000000..2afb872
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/MainActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.tests;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+
+ @javax.inject.Inject
+ SomeService mSomeService;
+
+ @Override
+ protected void onCreate(android.os.Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ dagger.ObjectGraph.create(AppModule.class).inject(this);
+
+ android.util.Log.d("daggerOne", mSomeService.message);
+ }
+}
diff --git a/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/SomeService.java b/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/SomeService.java
new file mode 100644
index 0000000..a69880e
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerOne/src/main/java/com/android/tests/SomeService.java
@@ -0,0 +1,10 @@
+package com.android.tests;
+
+public class SomeService {
+ public final String message;
+
+ @javax.inject.Inject
+ public SomeService(String message) {
+ this.message = message;
+ }
+}
diff --git a/build-system/integration-test/test-projects/daggerTwo/build.gradle b/build-system/integration-test/test-projects/daggerTwo/build.gradle
new file mode 100644
index 0000000..a158b74
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerTwo/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+buildscript {
+ repositories {
+ // TODO: remove
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
+ }
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'com.neenbedankt.android-apt'
+
+apply from: "../commonLocalRepo.gradle"
+
+repositories {
+ // TODO: add Dagger to prebuilts
+ jcenter()
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+dependencies {
+ apt 'com.google.dagger:dagger-compiler:2.0.2'
+ compile 'com.google.dagger:dagger:2.0.2'
+ provided 'javax.annotation:jsr250-api:1.0'
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/daggerTwo/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/daggerTwo/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..84c190b
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerTwo/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests">
+ <application android:label="Dagger Two">
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/AppComponent.java b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/AppComponent.java
new file mode 100644
index 0000000..86a4be4
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/AppComponent.java
@@ -0,0 +1,22 @@
+/*
+ * 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.tests;
+
+ at dagger.Component(modules = {AppModule.class})
+public interface AppComponent {
+ void injectMainActivity(MainActivity ma);
+}
diff --git a/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/AppModule.java b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/AppModule.java
new file mode 100644
index 0000000..c80586b
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/AppModule.java
@@ -0,0 +1,26 @@
+/*
+ * 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.tests;
+
+ at dagger.Module
+public class AppModule {
+ @dagger.Provides
+ SomeService provideSomeService() {
+ SomeService someService = new SomeService("from module");
+ return someService;
+ }
+}
diff --git a/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/MainActivity.java
new file mode 100644
index 0000000..85d998e
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/MainActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tests;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+
+ @javax.inject.Inject
+ SomeService mSomeService;
+
+ @Override
+ protected void onCreate(android.os.Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ AppComponent component = DaggerAppComponent.create();
+ component.injectMainActivity(this);
+
+ android.util.Log.d("daggerTwo", mSomeService.message);
+ }
+}
diff --git a/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/SomeService.java b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/SomeService.java
new file mode 100644
index 0000000..a69880e
--- /dev/null
+++ b/build-system/integration-test/test-projects/daggerTwo/src/main/java/com/android/tests/SomeService.java
@@ -0,0 +1,10 @@
+package com.android.tests;
+
+public class SomeService {
+ public final String message;
+
+ @javax.inject.Inject
+ public SomeService(String message) {
+ this.message = message;
+ }
+}
diff --git a/build-system/integration-test/test-projects/databinding/build.forexperimental.gradle b/build-system/integration-test/test-projects/databinding/build.forexperimental.gradle
new file mode 100644
index 0000000..edd056d
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.forexperimental.gradle
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+apply plugin: 'com.android.model.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+model {
+ android {
+ compileSdkVersion = 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ }
+
+ android.dataBinding {
+ enabled = true
+ addDefaultAdapters = true
+ }
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/databinding/build.gradle b/build-system/integration-test/test-projects/databinding/build.gradle
new file mode 100644
index 0000000..ae3d7e8
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.gradle
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ dataBinding {
+ enabled = true
+ addDefaultAdapters = true
+ }
+}
diff --git a/build-system/integration-test/test-projects/databinding/build.library-forexperimental.gradle b/build-system/integration-test/test-projects/databinding/build.library-forexperimental.gradle
new file mode 100644
index 0000000..23ae2d9
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.library-forexperimental.gradle
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+apply plugin: 'com.android.model.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+model {
+ android {
+ compileSdkVersion = 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ }
+
+ android.dataBinding {
+ enabled = true
+ addDefaultAdapters = true
+ }
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/databinding/build.library-withoutadapters-forexperimental.gradle b/build-system/integration-test/test-projects/databinding/build.library-withoutadapters-forexperimental.gradle
new file mode 100644
index 0000000..b99eb45
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.library-withoutadapters-forexperimental.gradle
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+apply plugin: 'com.android.model.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+model {
+ android {
+ compileSdkVersion = 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ }
+
+ android.dataBinding {
+ enabled = true
+ addDefaultAdapters = false
+ }
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/databinding/build.library-withoutadapters.gradle b/build-system/integration-test/test-projects/databinding/build.library-withoutadapters.gradle
new file mode 100644
index 0000000..a5c132f
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.library-withoutadapters.gradle
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply plugin: 'com.android.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ dataBinding {
+ enabled = true
+ addDefaultAdapters = false
+ }
+}
diff --git a/build-system/integration-test/test-projects/databinding/build.library.gradle b/build-system/integration-test/test-projects/databinding/build.library.gradle
new file mode 100644
index 0000000..907962b
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.library.gradle
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply plugin: 'com.android.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ dataBinding {
+ enabled = true
+ addDefaultAdapters = true
+ }
+}
diff --git a/build-system/integration-test/test-projects/databinding/build.withoutadapters-forexperimental.gradle b/build-system/integration-test/test-projects/databinding/build.withoutadapters-forexperimental.gradle
new file mode 100644
index 0000000..8acda19
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.withoutadapters-forexperimental.gradle
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+apply plugin: 'com.android.model.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+model {
+ android {
+ compileSdkVersion = 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ }
+
+ android.dataBinding {
+ enabled = true
+ addDefaultAdapters = false
+ }
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/databinding/build.withoutadapters.gradle b/build-system/integration-test/test-projects/databinding/build.withoutadapters.gradle
new file mode 100644
index 0000000..48e861d
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/build.withoutadapters.gradle
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ dataBinding {
+ enabled = true
+ addDefaultAdapters = false
+ }
+}
diff --git a/build-system/integration-test/test-projects/databinding/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/databinding/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..07f4d1e
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.databinding.testapp" >
+ <uses-sdk android:minSdkVersion="14"/>
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/databinding/src/main/java/android/databinding/testapp/MainActivity.java b/build-system/integration-test/test-projects/databinding/src/main/java/android/databinding/testapp/MainActivity.java
new file mode 100644
index 0000000..d6d6257
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/java/android/databinding/testapp/MainActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ActivityMainBinding;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+
+public class MainActivity extends Activity {
+ ActivityMainBinding mBinder;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinder = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(mBinder.getRoot());
+ }
+
+ public ActivityMainBinding getBinder() {
+ return mBinder;
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/drawable/ic_launcher.xml b/build-system/integration-test/test-projects/databinding/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000..5d481e4
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ff0000" />
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/databinding/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..7f32b15
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+<!--
+ ~ 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.
+ -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+ <data>
+ <variable name="foo" type="String"/>
+ </data>
+ <RelativeLayout android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context=".MainActivity">
+ <TextView android:text='@{foo + " " + foo}' android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </RelativeLayout>
+</layout>
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/menu/menu_main.xml b/build-system/integration-test/test-projects/databinding/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..4674d4d
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/menu/menu_main.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" android:showAsAction="never" />
+</menu>
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/values-v21/styles.xml b/build-system/integration-test/test-projects/databinding/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..9b24d4f
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/values-v21/styles.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <style name="AppTheme" parent="android:Theme.Material.Light">
+ </style>
+</resources>
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/values-w820dp/dimens.xml b/build-system/integration-test/test-projects/databinding/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..4719591
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/values/dimens.xml b/build-system/integration-test/test-projects/databinding/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..c06ae3f
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/databinding/src/main/res/values/strings.xml
new file mode 100644
index 0000000..20d68aa
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="app_name">Multi Module Test App</string>
+ <string name="hello_world">Hello world!</string>
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/build-system/integration-test/test-projects/databinding/src/main/res/values/styles.xml b/build-system/integration-test/test-projects/databinding/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7dc23c8
--- /dev/null
+++ b/build-system/integration-test/test-projects/databinding/src/main/res/values/styles.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.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/build-system/integration-test/test-projects/databindingIncremental/build.forexperimental.gradle b/build-system/integration-test/test-projects/databindingIncremental/build.forexperimental.gradle
new file mode 100644
index 0000000..edd056d
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/build.forexperimental.gradle
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+apply plugin: 'com.android.model.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+model {
+ android {
+ compileSdkVersion = 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ }
+
+ android.dataBinding {
+ enabled = true
+ addDefaultAdapters = true
+ }
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/databindingIncremental/build.gradle b/build-system/integration-test/test-projects/databindingIncremental/build.gradle
new file mode 100644
index 0000000..ae3d7e8
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/build.gradle
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion = rootProject.buildToolsVersion
+ dataBinding {
+ enabled = true
+ addDefaultAdapters = true
+ }
+}
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ada3a9f
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.databinding.testapp" >
+ <uses-sdk android:minSdkVersion="14"/>
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/java/android/databinding/testapp/MainActivity.java b/build-system/integration-test/test-projects/databindingIncremental/src/main/java/android/databinding/testapp/MainActivity.java
new file mode 100644
index 0000000..d6d6257
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/java/android/databinding/testapp/MainActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ActivityMainBinding;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+
+public class MainActivity extends Activity {
+ ActivityMainBinding mBinder;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinder = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(mBinder.getRoot());
+ }
+
+ public ActivityMainBinding getBinder() {
+ return mBinder;
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/drawable/ic_launcher.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000..5d481e4
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ff0000" />
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..41a2324
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ 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.
+ -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+ <data>
+ <variable name="foo" type="String"/>
+ </data>
+ <RelativeLayout android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context=".MainActivity">
+ <TextView android:text='@{foo + " " + foo}'
+
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </RelativeLayout>
+</layout>
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/menu/menu_main.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..4674d4d
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/menu/menu_main.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" android:showAsAction="never" />
+</menu>
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values-v21/styles.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..9b24d4f
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values-v21/styles.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <style name="AppTheme" parent="android:Theme.Material.Light">
+ </style>
+</resources>
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values-w820dp/dimens.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..4719591
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/dimens.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..c06ae3f
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/strings.xml
new file mode 100644
index 0000000..20d68aa
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="app_name">Multi Module Test App</string>
+ <string name="hello_world">Hello world!</string>
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/styles.xml b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7dc23c8
--- /dev/null
+++ b/build-system/integration-test/test-projects/databindingIncremental/src/main/res/values/styles.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.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/build-system/integration-test/test-projects/densitySplit/build.gradle b/build-system/integration-test/test-projects/densitySplit/build.gradle
new file mode 100644
index 0000000..3b3ce23
--- /dev/null
+++ b/build-system/integration-test/test-projects/densitySplit/build.gradle
@@ -0,0 +1,45 @@
+import com.android.build.OutputFile;
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+if (buildToolsVersion < '21.0.0') {
+ println ("Warning : this sample requires build-tools version 21 or above")
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+
+ buildConfigField "String", "FOO", "\"bar\""
+ }
+
+ splits {
+ density {
+ enable true
+ exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
+ compatibleScreens 'small', 'normal', 'large', 'xlarge'
+ }
+ }
+}
+
+// map for the version code
+ext.versionCodes = [all:1, mdpi:2, hdpi:3, xhdpi:4, xxhdpi:5]
+
+android.applicationVariants.all { variant ->
+ // assign different version code for each output
+ variant.outputs.each { output ->
+ def key = output.getFilter(OutputFile.DENSITY) == null ? "all" : output.getFilter(OutputFile.DENSITY)
+ def code = project.ext.versionCodes.get(key) * 100 + android.defaultConfig.versionCode
+ output.versionCodeOverride = code
+ output.versionNameOverride = "version ${code}"
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/densitySplit/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/densitySplit/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/densitySplit/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/densitySplit/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/densitySplit/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/densitySplit/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/other.png b/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/other.png
similarity index 100%
copy from base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/other.png
copy to build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/other.png
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/densitySplit/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/densitySplit/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/densitySplit/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/densitySplit/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/densitySplit/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/densitySplit/src/release/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/densitySplitInL/build.gradle b/build-system/integration-test/test-projects/densitySplitInL/build.gradle
new file mode 100644
index 0000000..5ea0d4e
--- /dev/null
+++ b/build-system/integration-test/test-projects/densitySplitInL/build.gradle
@@ -0,0 +1,25 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ generatePureSplits true
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 21
+ targetSdkVersion 21
+ }
+
+ splits {
+ density {
+ enable true
+ exclude "ldpi", "tvdpi", "xxxhdpi"
+ }
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/densitySplitInL/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/densitySplitInL/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/densitySplitInL/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/densitySplitInL/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable/ic_launcher.png b/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable/ic_launcher.png
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/res/drawable/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/densitySplitInL/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/densitySplitInL/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/densitySplitInL/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/densitySplitInL/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/densitySplitInL/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplitInL/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/densitySplitInL/src/release/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/dependencies/build.gradle b/build-system/integration-test/test-projects/dependencies/build.gradle
new file mode 100644
index 0000000..3f3f45c
--- /dev/null
+++ b/build-system/integration-test/test-projects/dependencies/build.gradle
@@ -0,0 +1,18 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+version='1.0'
+
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ apk project(':jarProject')
+ provided project(':jarProject2')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/dependencies/jarProject/build.gradle b/build-system/integration-test/test-projects/dependencies/jarProject/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/jarProject/build.gradle
rename to build-system/integration-test/test-projects/dependencies/jarProject/build.gradle
diff --git a/base/build-system/integration-test/test-projects/dependencies/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java b/build-system/integration-test/test-projects/dependencies/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
rename to build-system/integration-test/test-projects/dependencies/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
diff --git a/base/build-system/integration-test/test-projects/dependencies/jarProject2/build.gradle b/build-system/integration-test/test-projects/dependencies/jarProject2/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/jarProject2/build.gradle
rename to build-system/integration-test/test-projects/dependencies/jarProject2/build.gradle
diff --git a/base/build-system/integration-test/test-projects/dependencies/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java b/build-system/integration-test/test-projects/dependencies/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
rename to build-system/integration-test/test-projects/dependencies/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
diff --git a/base/build-system/integration-test/test-projects/dependencies/settings.gradle b/build-system/integration-test/test-projects/dependencies/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/settings.gradle
rename to build-system/integration-test/test-projects/dependencies/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java b/build-system/integration-test/test-projects/dependencies/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
rename to build-system/integration-test/test-projects/dependencies/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/dependencies/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/dependencies/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/java/com/android/tests/dependencies/MainActivity.java b/build-system/integration-test/test-projects/dependencies/src/main/java/com/android/tests/dependencies/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/java/com/android/tests/dependencies/MainActivity.java
rename to build-system/integration-test/test-projects/dependencies/src/main/java/com/android/tests/dependencies/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/dependencies/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/dependencies/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/dependencies/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/dependencies/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/dependencies/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/dependencies/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/dependencies/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/dependencies/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencies/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/dependencies/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/dependenciesWithVariants/build.gradle b/build-system/integration-test/test-projects/dependenciesWithVariants/build.gradle
new file mode 100644
index 0000000..3ff1b52
--- /dev/null
+++ b/build-system/integration-test/test-projects/dependenciesWithVariants/build.gradle
@@ -0,0 +1,17 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ productFlavors {
+ flavor1
+ }
+}
+
+dependencies {
+ flavor1Compile project(':jarProject')
+ androidTestFlavor1Compile project(':jarProject2')
+}
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/build.gradle b/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/build.gradle
rename to build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/build.gradle
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java b/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
rename to build-system/integration-test/test-projects/dependenciesWithVariants/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/build.gradle b/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/build.gradle
rename to build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/build.gradle
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java b/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
rename to build-system/integration-test/test-projects/dependenciesWithVariants/jarProject2/src/main/java/com/android/tests/dependencies/jar/StringHelper2.java
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/settings.gradle b/build-system/integration-test/test-projects/dependenciesWithVariants/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/settings.gradle
rename to build-system/integration-test/test-projects/dependenciesWithVariants/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java b/build-system/integration-test/test-projects/dependenciesWithVariants/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
rename to build-system/integration-test/test-projects/dependenciesWithVariants/src/androidTest/java/com/android/tests/dependencies/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/dependenciesWithVariants/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/java/com/android/tests/dependencies/MainActivity.java b/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/java/com/android/tests/dependencies/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/java/com/android/tests/dependencies/MainActivity.java
rename to build-system/integration-test/test-projects/dependenciesWithVariants/src/main/java/com/android/tests/dependencies/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/dependenciesWithVariants/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/duplicateNameImport/A/Project/build.gradle b/build-system/integration-test/test-projects/duplicateNameImport/A/Project/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/duplicateNameImport/A/Project/build.gradle
rename to build-system/integration-test/test-projects/duplicateNameImport/A/Project/build.gradle
diff --git a/base/build-system/integration-test/test-projects/duplicateNameImport/A/Project/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/duplicateNameImport/A/Project/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/duplicateNameImport/A/Project/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/duplicateNameImport/A/Project/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/duplicateNameImport/B/Project/build.gradle b/build-system/integration-test/test-projects/duplicateNameImport/B/Project/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/duplicateNameImport/B/Project/build.gradle
rename to build-system/integration-test/test-projects/duplicateNameImport/B/Project/build.gradle
diff --git a/base/build-system/integration-test/test-projects/duplicateNameImport/B/Project/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/duplicateNameImport/B/Project/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/duplicateNameImport/B/Project/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/duplicateNameImport/B/Project/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/duplicateNameImport/build.gradle b/build-system/integration-test/test-projects/duplicateNameImport/build.gradle
new file mode 100644
index 0000000..365176a
--- /dev/null
+++ b/build-system/integration-test/test-projects/duplicateNameImport/build.gradle
@@ -0,0 +1,26 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+allprojects {
+
+ version = '0.1'
+ group = 'a.b.c'
+
+ plugins.withId('android-library') {
+
+ android {
+ buildToolsVersion rootProject.buildToolsVersion
+ compileSdkVersion rootProject.latestCompileSdk
+
+ defaultConfig {
+ minSdkVersion 22
+ targetSdkVersion 22
+ }
+
+ }
+ }
+}
+
+
+
+
diff --git a/base/build-system/integration-test/test-projects/duplicateNameImport/settings.gradle b/build-system/integration-test/test-projects/duplicateNameImport/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/duplicateNameImport/settings.gradle
rename to build-system/integration-test/test-projects/duplicateNameImport/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/embedded/build.gradle b/build-system/integration-test/test-projects/embedded/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/build.gradle
rename to build-system/integration-test/test-projects/embedded/build.gradle
diff --git a/build-system/integration-test/test-projects/embedded/main/build.gradle b/build-system/integration-test/test-projects/embedded/main/build.gradle
new file mode 100644
index 0000000..6ddb5d6
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/main/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ productFlavors {
+ flavor1 {
+
+ }
+ flavor2 {
+
+ }
+ }
+
+ buildTypes {
+ custom.initWith(release)
+ custom {
+ applicationIdSuffix = '.custom'
+ }
+ }
+}
+
+dependencies {
+ wearApp project(':micro-apps:default')
+ flavor1WearApp project(':micro-apps:flavor1')
+ customWearApp project(':micro-apps:custom')
+}
+
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/embedded/main/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/embedded/main/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/embedded/main/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/embedded/main/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/embedded/main/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/embedded/main/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/componentModel/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/embedded/main/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/componentModel/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/embedded/main/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/embedded/main/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/embedded/main/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/embedded/main/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/embedded/main/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/embedded/micro-apps/custom/build.gradle b/build-system/integration-test/test-projects/embedded/micro-apps/custom/build.gradle
new file mode 100644
index 0000000..85d0aa8
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/micro-apps/custom/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 42
+ versionName "custom"
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:21.0.0'
+}
diff --git a/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/androidTest/java/com/android/tests/basic/custom/MainTest.java b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/androidTest/java/com/android/tests/basic/custom/MainTest.java
new file mode 100644
index 0000000..a881f17
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/androidTest/java/com/android/tests/basic/custom/MainTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.basic.custom;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b927d43
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic.custom">
+ <uses-sdk android:minSdkVersion="4"></uses-sdk>
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/java/com/android/tests/basic/custom/Main.java b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/java/com/android/tests/basic/custom/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/java/com/android/tests/basic/custom/Main.java
rename to build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/java/com/android/tests/basic/custom/Main.java
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/embedded/main/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/main/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/embedded/main/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/main/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/embedded/micro-apps/default/build.gradle b/build-system/integration-test/test-projects/embedded/micro-apps/default/build.gradle
new file mode 100644
index 0000000..61c5c49
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/micro-apps/default/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 42
+ versionName "default"
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:21.0.0'
+}
diff --git a/base/build-system/integration-test/test-projects/migrated/tests/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/embedded/micro-apps/default/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/tests/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/embedded/micro-apps/default/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3df66a7
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <uses-sdk android:minSdkVersion="4"></uses-sdk>
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/base/build-system/integration-test/test-projects/embedded/main/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/main/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/embedded/main/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/main/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/build.gradle b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/build.gradle
new file mode 100644
index 0000000..a641ccc
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 42
+ versionName "flavor1"
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:21.0.0'
+}
diff --git a/base/build-system/integration-test/test-projects/multires/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3df66a7
--- /dev/null
+++ b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <uses-sdk android:minSdkVersion="4"></uses-sdk>
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/custom/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/embedded/settings.gradle b/build-system/integration-test/test-projects/embedded/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/settings.gradle
rename to build-system/integration-test/test-projects/embedded/settings.gradle
diff --git a/build-system/integration-test/test-projects/emptySplit/build.gradle b/build-system/integration-test/test-projects/emptySplit/build.gradle
new file mode 100644
index 0000000..f7f7041
--- /dev/null
+++ b/build-system/integration-test/test-projects/emptySplit/build.gradle
@@ -0,0 +1,21 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ // create splits but remove all split values.
+ splits {
+ density {
+ enable true
+ reset()
+ }
+ abi {
+ enable true
+ reset()
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/emptySplit/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/emptySplit/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/emptySplit/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/emptySplit/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/extractAnnotations/build.gradle b/build-system/integration-test/test-projects/extractAnnotations/build.gradle
new file mode 100644
index 0000000..f072807
--- /dev/null
+++ b/build-system/integration-test/test-projects/extractAnnotations/build.gradle
@@ -0,0 +1,23 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.android.support:support-annotations:+'
+ compile 'com.android.support:support-v4:+'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
+
+defaultTasks 'extractDebugAnnotations'
diff --git a/base/build-system/integration-test/test-projects/extractAnnotations/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/extractAnnotations/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractAnnotations/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/extractAnnotations/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/Constants.java b/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/Constants.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/Constants.java
rename to build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/Constants.java
diff --git a/base/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/ExtractTest.java b/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/ExtractTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/ExtractTest.java
rename to build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/ExtractTest.java
diff --git a/base/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/TopLevelTypeDef.java b/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/TopLevelTypeDef.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/TopLevelTypeDef.java
rename to build-system/integration-test/test-projects/extractAnnotations/src/main/java/com/android/tests/extractannotations/TopLevelTypeDef.java
diff --git a/build-system/integration-test/test-projects/extractRsEnabledAnnotations/build.gradle b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/build.gradle
new file mode 100644
index 0000000..1f7f1b6
--- /dev/null
+++ b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/build.gradle
@@ -0,0 +1,22 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.library'
+
+dependencies {
+ compile 'com.android.support:support-annotations:+'
+}
+
+android {
+ resourcePrefix 'lib1_'
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 19
+ renderscriptTargetApi 18
+ renderscriptSupportModeEnabled true
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/Lib1.java b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/Lib1.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/Lib1.java
rename to build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/Lib1.java
diff --git a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/MainActivity.java b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
rename to build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/res/layout/lib1_main.xml b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/res/layout/lib1_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/res/layout/lib1_main.xml
rename to build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/res/layout/lib1_main.xml
diff --git a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/blend.rs b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/blend.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/blend.rs
rename to build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/blend.rs
diff --git a/base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/ip.rsh b/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/ip.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/ip.rsh
rename to build-system/integration-test/test-projects/extractRsEnabledAnnotations/src/main/rs/ip.rsh
diff --git a/build-system/integration-test/test-projects/filteredOutBuildType/build.gradle b/build-system/integration-test/test-projects/filteredOutBuildType/build.gradle
new file mode 100644
index 0000000..a51c148
--- /dev/null
+++ b/build-system/integration-test/test-projects/filteredOutBuildType/build.gradle
@@ -0,0 +1,16 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ variantFilter {
+ if (it.buildType.name.equals("debug")) {
+ it.ignore = true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/componentModel/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/filteredOutBuildType/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/componentModel/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/filteredOutBuildType/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/embedded/main/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/filteredOutBuildType/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/main/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/filteredOutBuildType/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/filteredOutBuildType/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/filteredOutBuildType/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/filteredOutVariants/build.gradle b/build-system/integration-test/test-projects/filteredOutVariants/build.gradle
new file mode 100644
index 0000000..32ad313
--- /dev/null
+++ b/build-system/integration-test/test-projects/filteredOutVariants/build.gradle
@@ -0,0 +1,37 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ variantFilter {
+ String abi = it.flavors.get(0).name
+ if ("cupcake".equals(it.flavors.get(1).name) && ("x86".equals(abi) || "mips".equals(abi))) {
+ it.ignore = true
+ }
+ }
+
+ flavorDimensions "abi", "api"
+
+ productFlavors {
+ x86 {
+ dimension "abi"
+ }
+ mips {
+ dimension "abi"
+ }
+ arm {
+ dimension "abi"
+ }
+ cupcake {
+ dimension "api"
+ }
+ gingerbread {
+ dimension "api"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/renamedApk/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/filteredOutVariants/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renamedApk/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/filteredOutVariants/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/filteredOutVariants/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/default/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/filteredOutVariants/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/filteredOutVariants/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/filteredOutVariants/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/filteredOutVariants/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/filteredOutVariants/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/filteredOutVariants/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/flavored/build.gradle b/build-system/integration-test/test-projects/flavored/build.gradle
new file mode 100644
index 0000000..7958be3
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavored/build.gradle
@@ -0,0 +1,50 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testBuildType = "staging"
+
+ defaultConfig {
+ }
+
+ productFlavors {
+ f1 {
+ applicationId = "com.android.tests.flavored.f1"
+ versionName = "1.0.0-f1"
+ ext.buildType = "F1PROD"
+ }
+ f2 {
+ applicationId = "com.android.tests.flavored.f2"
+ versionName = "1.0.0-f2"
+ ext.buildType = "F2PROD"
+ }
+ }
+
+ buildTypes {
+ debug {
+ applicationIdSuffix = ".debug"
+ versionNameSuffix = ".D"
+ }
+ staging {
+ applicationIdSuffix = ".staging"
+ versionNameSuffix = ".S"
+ signingConfig signingConfigs.debug
+ }
+ }
+
+ // This is not part of the test per se, it tests that adding dynamic properties on the
+ // flavor declaration can be retrieved later.
+ applicationVariants.all { variant ->
+ assert variant.productFlavors.size() == 1
+ def buildType = variant.productFlavors[0].buildType
+ if (!"F1PROD".equals(buildType)
+ && !"F2PROD".equals(buildType)) {
+ throw new RuntimeException("Invalid extension property value ${buildType}")
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/basic/debug.keystore b/build-system/integration-test/test-projects/flavored/debug.keystore
similarity index 100%
rename from base/build-system/integration-test/test-projects/basic/debug.keystore
rename to build-system/integration-test/test-projects/flavored/debug.keystore
diff --git a/base/build-system/integration-test/test-projects/flavored/src/androidTest/java/com/android/tests/flavored/MainTest.java b/build-system/integration-test/test-projects/flavored/src/androidTest/java/com/android/tests/flavored/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/androidTest/java/com/android/tests/flavored/MainTest.java
rename to build-system/integration-test/test-projects/flavored/src/androidTest/java/com/android/tests/flavored/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/flavored/src/androidTestF2/java/com/android/tests/flavored/OtherActivityTest.java b/build-system/integration-test/test-projects/flavored/src/androidTestF2/java/com/android/tests/flavored/OtherActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/androidTestF2/java/com/android/tests/flavored/OtherActivityTest.java
rename to build-system/integration-test/test-projects/flavored/src/androidTestF2/java/com/android/tests/flavored/OtherActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/flavored/src/f1/res/values/strings.xml b/build-system/integration-test/test-projects/flavored/src/f1/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/f1/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavored/src/f1/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavored/src/f1Staging/res/values/strings.xml b/build-system/integration-test/test-projects/flavored/src/f1Staging/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/f1Staging/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavored/src/f1Staging/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavored/src/f2/AndroidManifest.xml b/build-system/integration-test/test-projects/flavored/src/f2/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/f2/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavored/src/f2/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavored/src/f2/java/com/android/tests/flavored/OtherActivity.java b/build-system/integration-test/test-projects/flavored/src/f2/java/com/android/tests/flavored/OtherActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/f2/java/com/android/tests/flavored/OtherActivity.java
rename to build-system/integration-test/test-projects/flavored/src/f2/java/com/android/tests/flavored/OtherActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavored/src/f2/res/layout/main2.xml b/build-system/integration-test/test-projects/flavored/src/f2/res/layout/main2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/f2/res/layout/main2.xml
rename to build-system/integration-test/test-projects/flavored/src/f2/res/layout/main2.xml
diff --git a/base/build-system/integration-test/test-projects/flavored/src/f2/res/values/strings.xml b/build-system/integration-test/test-projects/flavored/src/f2/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/f2/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavored/src/f2/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavored/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavored/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavored/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavored/src/main/java/com/android/tests/flavored/Main.java b/build-system/integration-test/test-projects/flavored/src/main/java/com/android/tests/flavored/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/main/java/com/android/tests/flavored/Main.java
rename to build-system/integration-test/test-projects/flavored/src/main/java/com/android/tests/flavored/Main.java
diff --git a/base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/flavored/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/flavored/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavored/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/flavored/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/flavored/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/flavored/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavored/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavored/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/flavoredlib/app/build.gradle b/build-system/integration-test/test-projects/flavoredlib/app/build.gradle
new file mode 100644
index 0000000..fa16453
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavoredlib/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ productFlavors {
+ flavor1 {
+ applicationId = "com.android.tests.flavorlib.app.flavor1"
+ }
+ flavor2 {
+ applicationId = "com.android.tests.flavorlib.app.flavor2"
+ }
+ }
+
+ testOptions {
+ resultsDir = "$project.buildDir/foo/results"
+ reportDir = "$project.buildDir/foo/report"
+ }
+}
+
+dependencies {
+ flavor1Compile project(path: ':lib', configuration: 'flavor1Release')
+ flavor2Compile project(path: ':lib', configuration: 'flavor2Release')
+}
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/proguard-project.txt b/build-system/integration-test/test-projects/flavoredlib/app/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/proguard-project.txt
rename to build-system/integration-test/test-projects/flavoredlib/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/integration-test/test-projects/flavoredlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavoredlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
rename to build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
rename to build-system/integration-test/test-projects/flavoredlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/java/com/android/tests/flavorlib/app/LibWrapper.java b/build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/java/com/android/tests/flavorlib/app/LibWrapper.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/java/com/android/tests/flavorlib/app/LibWrapper.java
rename to build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/java/com/android/tests/flavorlib/app/LibWrapper.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/res/values/strings.xml b/build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavoredlib/app/src/flavor1/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/java/com/android/tests/flavorlib/app/LibWrapper.java b/build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/java/com/android/tests/flavorlib/app/LibWrapper.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/java/com/android/tests/flavorlib/app/LibWrapper.java
rename to build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/java/com/android/tests/flavorlib/app/LibWrapper.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/res/values/strings.xml b/build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavoredlib/app/src/flavor2/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavoredlib/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/App.java b/build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java b/build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt b/build-system/integration-test/test-projects/flavoredlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
rename to build-system/integration-test/test-projects/flavoredlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/build.gradle b/build-system/integration-test/test-projects/flavoredlib/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/build.gradle
rename to build-system/integration-test/test-projects/flavoredlib/build.gradle
diff --git a/build-system/integration-test/test-projects/flavoredlib/lib/build.gradle b/build-system/integration-test/test-projects/flavoredlib/lib/build.gradle
new file mode 100644
index 0000000..e6da341
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavoredlib/lib/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultPublishConfig "flavor1Release"
+ publishNonDefault true
+
+ productFlavors {
+ flavor1 { }
+ flavor2 { }
+ }
+
+ libraryVariants.all { variant ->
+ assert variant.productFlavors.size() != 0
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/assets/lib/proguard-project.txt b/build-system/integration-test/test-projects/flavoredlib/lib/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/lib/proguard-project.txt
rename to build-system/integration-test/test-projects/flavoredlib/lib/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivityTest.java b/build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivityTest.java b/build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/androidTestFlavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivityTest.java
diff --git a/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/AndroidManifest.xml b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/AndroidManifest.xml
new file mode 100644
index 0000000..dd64518
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib">
+
+ <application >
+ <activity
+ android:name=".flavor1.MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/Lib.java b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/Lib.java
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/Lib.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivity.java b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivity.java
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/java/com/android/tests/flavorlib/lib/flavor1/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/layout/lib_main.xml b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/layout/lib_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/layout/lib_main.xml
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/layout/lib_main.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/values/strings.xml b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/resources/com/android/tests/flavorlib/lib/flavor1/Lib.txt b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/resources/com/android/tests/flavorlib/lib/flavor1/Lib.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/resources/com/android/tests/flavorlib/lib/flavor1/Lib.txt
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor1/resources/com/android/tests/flavorlib/lib/flavor1/Lib.txt
diff --git a/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/AndroidManifest.xml b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/AndroidManifest.xml
new file mode 100644
index 0000000..d61d277
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib">
+
+ <application >
+ <activity
+ android:name=".flavor2.MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/Lib.java b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/Lib.java
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/Lib.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivity.java b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivity.java
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/java/com/android/tests/flavorlib/lib/flavor2/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/layout/lib_main.xml b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/layout/lib_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/layout/lib_main.xml
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/layout/lib_main.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/values/strings.xml b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/resources/com/android/tests/flavorlib/lib/flavor2/Lib.txt b/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/resources/com/android/tests/flavorlib/lib/flavor2/Lib.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/resources/com/android/tests/flavorlib/lib/flavor2/Lib.txt
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/flavor2/resources/com/android/tests/flavorlib/lib/flavor2/Lib.txt
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavoredlib/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavoredlib/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/settings.gradle b/build-system/integration-test/test-projects/flavoredlib/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/settings.gradle
rename to build-system/integration-test/test-projects/flavoredlib/settings.gradle
diff --git a/build-system/integration-test/test-projects/flavorlib/app/build.gradle b/build-system/integration-test/test-projects/flavorlib/app/build.gradle
new file mode 100644
index 0000000..f537430
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlib/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ productFlavors {
+ flavor1 {
+ applicationId = "com.android.tests.flavorlib.app.flavor1"
+ }
+ flavor2 {
+ applicationId = "com.android.tests.flavorlib.app.flavor2"
+ }
+ }
+
+ testOptions {
+ resultsDir = "$project.buildDir/foo/results"
+ reportDir = "$project.buildDir/foo/report"
+ }
+}
+
+dependencies {
+ flavor1Compile project(':lib1')
+ flavor2Compile project(':lib2')
+}
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/proguard-project.txt b/build-system/integration-test/test-projects/flavorlib/app/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/proguard-project.txt
rename to build-system/integration-test/test-projects/flavorlib/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/integration-test/test-projects/flavorlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavorlib/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
rename to build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
rename to build-system/integration-test/test-projects/flavorlib/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/flavor1/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlib/app/src/flavor1/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/flavor1/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlib/app/src/flavor1/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/flavor2/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlib/app/src/flavor2/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/flavor2/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlib/app/src/flavor2/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavorlib/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/App.java b/build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java b/build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavored/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/flavorlib/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlib/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt b/build-system/integration-test/test-projects/flavorlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
rename to build-system/integration-test/test-projects/flavorlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlib/build.gradle b/build-system/integration-test/test-projects/flavorlib/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/build.gradle
rename to build-system/integration-test/test-projects/flavorlib/build.gradle
diff --git a/build-system/integration-test/test-projects/flavorlib/lib1/build.gradle b/build-system/integration-test/test-projects/flavorlib/lib1/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlib/lib1/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/lib/proguard-project.txt b/build-system/integration-test/test-projects/flavorlib/lib1/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/lib/proguard-project.txt
rename to build-system/integration-test/test-projects/flavorlib/lib1/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/integration-test/test-projects/flavorlib/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/layout/lib_main.xml b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/layout/lib_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/layout/lib_main.xml
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/layout/lib_main.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/integration-test/test-projects/flavorlib/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
rename to build-system/integration-test/test-projects/flavorlib/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
diff --git a/build-system/integration-test/test-projects/flavorlib/lib2/build.gradle b/build-system/integration-test/test-projects/flavorlib/lib2/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlib/lib2/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib1/proguard-project.txt b/build-system/integration-test/test-projects/flavorlib/lib2/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib1/proguard-project.txt
rename to build-system/integration-test/test-projects/flavorlib/lib2/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/integration-test/test-projects/flavorlib/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/layout/lib_main.xml b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/layout/lib_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/layout/lib_main.xml
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/layout/lib_main.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/integration-test/test-projects/flavorlib/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
rename to build-system/integration-test/test-projects/flavorlib/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlib/settings.gradle b/build-system/integration-test/test-projects/flavorlib/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/settings.gradle
rename to build-system/integration-test/test-projects/flavorlib/settings.gradle
diff --git a/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/build.gradle b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/build.gradle
new file mode 100644
index 0000000..626893f
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ productFlavors {
+ flavor1 {
+ applicationId = "com.android.tests.flavorlib.app.flavor1"
+ }
+ flavor2 {
+ applicationId = "com.android.tests.flavorlib.app.flavor2"
+ }
+ }
+}
+
+dependencies {
+ flavor1Compile project(':lib1')
+ flavor2Compile project(':lib2')
+}
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/proguard-project.txt b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/proguard-project.txt
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/androidTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor1/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor1/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor1/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor1/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor2/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor2/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor2/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/flavor2/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/App.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/App.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/App.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavoredlib/app/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/resources/com/android/tests/flavorlib/app/App.txt b/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/build.gradle b/build-system/integration-test/test-projects/flavorlibWithFailedTests/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/build.gradle
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/build.gradle
diff --git a/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/build.gradle b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlib/lib2/proguard-project.txt b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/lib2/proguard-project.txt
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
diff --git a/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b01bf9e
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application>
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/layout/lib_main.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/layout/lib_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/layout/lib_main.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/layout/lib_main.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
diff --git a/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/build.gradle b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/proguard-project.txt b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib1/proguard-project.txt
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/androidTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
diff --git a/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b01bf9e
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application>
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/layout/lib_main.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/layout/lib_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/layout/lib_main.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/layout/lib_main.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/settings.gradle b/build-system/integration-test/test-projects/flavorlibWithFailedTests/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/settings.gradle
rename to build-system/integration-test/test-projects/flavorlibWithFailedTests/settings.gradle
diff --git a/build-system/integration-test/test-projects/flavors/build.gradle b/build-system/integration-test/test-projects/flavors/build.gradle
new file mode 100644
index 0000000..eb3afac
--- /dev/null
+++ b/build-system/integration-test/test-projects/flavors/build.gradle
@@ -0,0 +1,27 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ flavorDimensions "group1", "group2"
+
+ productFlavors {
+ f1 {
+ dimension "group1"
+ }
+ f2 {
+ dimension "group1"
+ }
+
+ fa {
+ dimension "group2"
+ }
+ fb {
+ dimension "group2"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/flavors/proguard-project.txt b/build-system/integration-test/test-projects/flavors/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/proguard-project.txt
rename to build-system/integration-test/test-projects/flavors/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/flavors/src/androidTest/java/com/android/tests/flavors/MainActivityCustomizedTest.java b/build-system/integration-test/test-projects/flavors/src/androidTest/java/com/android/tests/flavors/MainActivityCustomizedTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/androidTest/java/com/android/tests/flavors/MainActivityCustomizedTest.java
rename to build-system/integration-test/test-projects/flavors/src/androidTest/java/com/android/tests/flavors/MainActivityCustomizedTest.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/androidTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java b/build-system/integration-test/test-projects/flavors/src/androidTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/androidTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java
rename to build-system/integration-test/test-projects/flavors/src/androidTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/androidTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java b/build-system/integration-test/test-projects/flavors/src/androidTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/androidTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java
rename to build-system/integration-test/test-projects/flavors/src/androidTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/androidTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java b/build-system/integration-test/test-projects/flavors/src/androidTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/androidTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java
rename to build-system/integration-test/test-projects/flavors/src/androidTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/androidTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java b/build-system/integration-test/test-projects/flavors/src/androidTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/androidTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java
rename to build-system/integration-test/test-projects/flavors/src/androidTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f1/java/com/android/tests/flavors/group1/SomeClass.java b/build-system/integration-test/test-projects/flavors/src/f1/java/com/android/tests/flavors/group1/SomeClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f1/java/com/android/tests/flavors/group1/SomeClass.java
rename to build-system/integration-test/test-projects/flavors/src/f1/java/com/android/tests/flavors/group1/SomeClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f1/res/values/strings.xml b/build-system/integration-test/test-projects/flavors/src/f1/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f1/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavors/src/f1/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f1Fa/java/com/android/tests/flavors/CustomizedClass.java b/build-system/integration-test/test-projects/flavors/src/f1Fa/java/com/android/tests/flavors/CustomizedClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f1Fa/java/com/android/tests/flavors/CustomizedClass.java
rename to build-system/integration-test/test-projects/flavors/src/f1Fa/java/com/android/tests/flavors/CustomizedClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f1Fb/java/com/android/tests/flavors/CustomizedClass.java b/build-system/integration-test/test-projects/flavors/src/f1Fb/java/com/android/tests/flavors/CustomizedClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f1Fb/java/com/android/tests/flavors/CustomizedClass.java
rename to build-system/integration-test/test-projects/flavors/src/f1Fb/java/com/android/tests/flavors/CustomizedClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f2/java/com/android/tests/flavors/group1/SomeClass.java b/build-system/integration-test/test-projects/flavors/src/f2/java/com/android/tests/flavors/group1/SomeClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f2/java/com/android/tests/flavors/group1/SomeClass.java
rename to build-system/integration-test/test-projects/flavors/src/f2/java/com/android/tests/flavors/group1/SomeClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f2/res/values/strings.xml b/build-system/integration-test/test-projects/flavors/src/f2/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f2/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavors/src/f2/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f2Fa/java/com/android/tests/flavors/CustomizedClass.java b/build-system/integration-test/test-projects/flavors/src/f2Fa/java/com/android/tests/flavors/CustomizedClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f2Fa/java/com/android/tests/flavors/CustomizedClass.java
rename to build-system/integration-test/test-projects/flavors/src/f2Fa/java/com/android/tests/flavors/CustomizedClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f2FbDebug/java/com/android/tests/flavors/CustomizedClass.java b/build-system/integration-test/test-projects/flavors/src/f2FbDebug/java/com/android/tests/flavors/CustomizedClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f2FbDebug/java/com/android/tests/flavors/CustomizedClass.java
rename to build-system/integration-test/test-projects/flavors/src/f2FbDebug/java/com/android/tests/flavors/CustomizedClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/f2FbRelease/java/com/android/tests/flavors/CustomizedClass.java b/build-system/integration-test/test-projects/flavors/src/f2FbRelease/java/com/android/tests/flavors/CustomizedClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/f2FbRelease/java/com/android/tests/flavors/CustomizedClass.java
rename to build-system/integration-test/test-projects/flavors/src/f2FbRelease/java/com/android/tests/flavors/CustomizedClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/fa/java/com/android/tests/flavors/group2/SomeClass.java b/build-system/integration-test/test-projects/flavors/src/fa/java/com/android/tests/flavors/group2/SomeClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/fa/java/com/android/tests/flavors/group2/SomeClass.java
rename to build-system/integration-test/test-projects/flavors/src/fa/java/com/android/tests/flavors/group2/SomeClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/fa/res/values/strings.xml b/build-system/integration-test/test-projects/flavors/src/fa/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/fa/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavors/src/fa/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavors/src/fb/java/com/android/tests/flavors/group2/SomeClass.java b/build-system/integration-test/test-projects/flavors/src/fb/java/com/android/tests/flavors/group2/SomeClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/fb/java/com/android/tests/flavors/group2/SomeClass.java
rename to build-system/integration-test/test-projects/flavors/src/fb/java/com/android/tests/flavors/group2/SomeClass.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/fb/res/values/strings.xml b/build-system/integration-test/test-projects/flavors/src/fb/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/fb/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavors/src/fb/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/flavors/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/flavors/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/flavors/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/flavors/src/main/java/com/android/tests/flavors/MainActivity.java b/build-system/integration-test/test-projects/flavors/src/main/java/com/android/tests/flavors/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/main/java/com/android/tests/flavors/MainActivity.java
rename to build-system/integration-test/test-projects/flavors/src/main/java/com/android/tests/flavors/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/flavors/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/flavors/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/main/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/flavors/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavors/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/flavors/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/main/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/flavors/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/flavors/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlib/app/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/flavors/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/flavors/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/flavors/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/flavors/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/flavors/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/flavors/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/flavors/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/genFolderApi/build.gradle b/build-system/integration-test/test-projects/genFolderApi/build.gradle
new file mode 100644
index 0000000..2136897
--- /dev/null
+++ b/build-system/integration-test/test-projects/genFolderApi/build.gradle
@@ -0,0 +1,64 @@
+// ATTENTION -- hash value of this file is checked in the corresponding
+// integration test. Please make sure any changes you make here are
+// backwards compatible.
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+public class GenerateCode extends DefaultTask {
+ @Input
+ String value
+
+ @OutputFile
+ File outputFile
+
+ @TaskAction
+ void taskAction() {
+ getOutputFile().text =
+ "package com.custom;\n" +
+ "public class Foo {\n" +
+ " public static String getBuildDate() { return \"${getValue()}\"; }\n" +
+ "}\n";
+ }
+}
+
+public class GenerateRes extends DefaultTask {
+ @Input
+ String value
+
+ @OutputFile
+ File outputFile
+
+ @TaskAction
+ void taskAction() {
+ getOutputFile().text = "<xml>${getValue()}</xml>\n"
+ }
+}
+
+android.applicationVariants.all { variant ->
+
+ // create a task that generates a java class
+ File sourceFolder = file("${buildDir}/customCode/${variant.dirName}")
+ def javaGenerationTask = tasks.create(name: "generatedCodeFor${variant.name.capitalize()}", type: GenerateCode) {
+ value new Date().format("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC"))
+ outputFile file("${sourceFolder.absolutePath}/com/custom/Foo.java")
+ }
+
+ variant.registerJavaGeneratingTask(javaGenerationTask, sourceFolder)
+
+ // create a task that generates an XML file class
+ File resFolder = file("${buildDir}/customRes/${variant.dirName}")
+ def resGenerationTask = tasks.create(name: "generatedResFor${variant.name.capitalize()}", type: GenerateRes) {
+ value new Date().format("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC"))
+ outputFile file("${resFolder.absolutePath}/xml/generated.xml")
+ }
+
+ variant.registerResGeneratingTask(resGenerationTask, resFolder)
+}
diff --git a/base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/genFolderApi/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/embedded/micro-apps/flavor1/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/genFolderApi/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/genFolderApi/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/genFolderApi/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/genFolderApi/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/genFolderApi/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/app/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/genFolderApi/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/genFolderApi/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/genFolderApi/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/genFolderApi/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/genFolderApi/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/genFolderApi/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/genFolderApi/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/genFolderApi2/build.gradle b/build-system/integration-test/test-projects/genFolderApi2/build.gradle
new file mode 100644
index 0000000..edd4b30
--- /dev/null
+++ b/build-system/integration-test/test-projects/genFolderApi2/build.gradle
@@ -0,0 +1,27 @@
+// ATTENTION -- hash value of this file is checked in the corresponding
+// integration test. Please make sure any changes you make here are
+// backwards compatible.
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+android.applicationVariants.all { variant ->
+ File sourceFolder = file("${buildDir}/customCode/${variant.dirName}")
+ variant.addJavaSourceFoldersToModel(sourceFolder)
+
+ def unitTestVariant = variant.unitTestVariant
+ File unitTestSourceFolder = file("${buildDir}/customCode/${unitTestVariant.dirName}-1")
+ unitTestVariant.addJavaSourceFoldersToModel(unitTestSourceFolder)
+}
+
+android.unitTestVariants.all { unitTestVariant ->
+ File unitTestSourceFolder = file("${buildDir}/customCode/${unitTestVariant.dirName}-2")
+ unitTestVariant.addJavaSourceFoldersToModel(unitTestSourceFolder)
+}
diff --git a/base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/genFolderApi2/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutBuildType/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/genFolderApi2/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/genFolderApi2/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/genFolderApi2/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..4fc4ff2
--- /dev/null
+++ b/build-system/integration-test/test-projects/genFolderApi2/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,18 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.text);
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/flavors/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/genFolderApi2/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavors/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/genFolderApi2/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/genFolderApi2/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/genFolderApi2/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi2/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/genFolderApi2/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/genFolderApi2/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/genFolderApi2/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi2/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/genFolderApi2/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/instant-unit-tests/build.gradle b/build-system/integration-test/test-projects/instant-unit-tests/build.gradle
new file mode 100644
index 0000000..27c890e
--- /dev/null
+++ b/build-system/integration-test/test-projects/instant-unit-tests/build.gradle
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply from: "../commonLocalRepo.gradle"
+
+def env = new Object() {
+ Object project = new Object() {
+ Object ext = new Object() {
+ String buildVersion
+ String baseVersion
+ String apiVersion
+ String nativeApiVersion
+ String experimentalVersion
+ }
+ }
+}
+apply from: "$System.env.CUSTOM_REPO/../../tools/buildSrc/base/version.gradle", to: env
+
+
+import com.android.utils.FileUtils
+import com.google.common.base.Charsets
+import com.google.common.collect.ImmutableList
+import com.google.common.io.CharSink
+import com.google.common.io.Files
+
+def PROJECT_ROOT = new File(System.getenv("CUSTOM_REPO")).getParentFile().getParentFile()
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion rootProject.buildToolsVersion
+
+ defaultConfig {
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ minSdkVersion 15
+ targetSdkVersion 15
+ versionCode 1
+ versionName "1.0"
+ }
+
+ sourceSets {
+
+ main.java.srcDir("src/androidTest/java")
+
+ androidTest {
+ java {
+ setSrcDirs(["${PROJECT_ROOT}/tools/base/build-system/gradle-core/src/test/java"])
+ filter.setIncludes(["com/android/build/gradle/internal/incremental/hotswap/*.java"])
+ }
+ assets {
+ srcDir(file("build/generatedAssets/"))
+ }
+ }
+ }
+}
+
+File instantRunServer = new File(
+ PROJECT_ROOT,
+ "out/build/base/instant-run/instant-run-server/build/libs/instant-run-server.jar")
+if (!instantRunServer.exists()) {
+ throw new RuntimeException("Instant run server library not found: " + instantRunServer)
+}
+
+dependencies {
+ compile 'com.android.tools:common:' + env.project.ext.baseVersion
+ compile files(instantRunServer)
+ compile files("build/incremental-test-classes-jar/instrumentedBaseClasses.jar")
+
+ compile 'com.android.support:support-v4:23.0.1'
+ //provided gradleApi() // TODO: remove
+ //androidTestCompile 'junit:junit:4.12'
+ androidTestCompile 'com.google.code.findbugs:jsr305:1.3.9'
+ androidTestCompile 'com.google.truth:truth:0.28'
+ //androidTestCompile 'org.mockito:mockito-core:1.9.5'
+ androidTestCompile 'com.google.guava:guava:17.0'
+ compile 'com.android.support.test:runner:0.3'
+ androidTestCompile 'com.android.support.test:rules:0.3'
+}
+
+
+def jarInstrumentedBaseClasses = task("jarInstrumentedBaseClasses", type: Jar) {
+ from (files(new File(PROJECT_ROOT,
+ "out/build/base/gradle-core/build/classes/incremental-test/baseInstrumented")))
+ destinationDir = file("build/incremental-test-classes-jar/")
+ archiveName = "instrumentedBaseClasses.jar"
+}
+
+class DexTask extends DefaultTask {
+
+ @InputDirectory
+ File classesFolder
+
+ @OutputDirectory
+ File outputFolder
+
+ @TaskAction
+ public void compileDexFiles() {
+ File classesFolder = getClassesFolder()
+ File outputFolder = getOutputFolder()
+ def builder = project.getPlugins().findPlugin("com.android.library").androidBuilder
+ builder.convertByteCode(ImmutableList.of(classesFolder),
+ outputFolder,
+ false /* multiDexEnabled */,
+ null /*getMainDexListFile */,
+ new com.android.build.gradle.internal.dsl.DexOptions(),
+ ImmutableList.<String>of() /* getAdditionalParameters */,
+ false /* incremental */,
+ true /* optimize */,
+ new com.android.ide.common.process.LoggedProcessOutputHandler(
+ new com.android.build.gradle.internal.LoggerWrapper(getLogger())),
+ false /*instantRunMode*/)
+ Iterable<File> files =
+ Files.fileTreeTraverser().preOrderTraversal(classesFolder).filter(Files.isFile());
+
+ CharSink filesList = Files.asCharSink(new File(outputFolder, "classes.txt"), Charsets.UTF_8)
+ Writer filesListWriter = filesList.openBufferedStream()
+ for (File classFile : files) {
+ filesListWriter.append(FileUtils.relativePath(classFile, classesFolder)).append('\n');
+ }
+ filesListWriter.close()
+ }
+
+}
+
+def dexInstrumentedPatches = task("dexInstrumentedPatches")
+
+for (File f: new File(PROJECT_ROOT, "out/build/base/gradle-core/build/classes/incremental-test/instrumentedPatches").listFiles()) {
+ String patchName = f.getName()
+ Task dexInstrumentedPatch = task("dexInstrumentedPatch" + patchName.capitalize(), type:DexTask) {
+ classesFolder = f
+ outputFolder = file("build/generatedAssets/incremental-test-classes-dex/" + patchName)
+ }
+ dexInstrumentedPatches.dependsOn dexInstrumentedPatch
+
+}
+project.afterEvaluate {
+ tasks.getByPath(":preDebugBuild").dependsOn(dexInstrumentedPatches, jarInstrumentedBaseClasses)
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/instant-unit-tests/src/androidTest/java/com/android/build/gradle/internal/incremental/fixture/ClassEnhancement.java b/build-system/integration-test/test-projects/instant-unit-tests/src/androidTest/java/com/android/build/gradle/internal/incremental/fixture/ClassEnhancement.java
new file mode 100644
index 0000000..edcc6ca
--- /dev/null
+++ b/build-system/integration-test/test-projects/instant-unit-tests/src/androidTest/java/com/android/build/gradle/internal/incremental/fixture/ClassEnhancement.java
@@ -0,0 +1,253 @@
+/*
+ * 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.build.gradle.internal.incremental.fixture;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.Environment;
+import android.support.test.InstrumentationRegistry;
+import android.support.v4.content.ContextCompat;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.CharStreams;
+import com.google.common.io.Files;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+import dalvik.system.DexClassLoader;
+
+public class ClassEnhancement implements TestRule {
+
+ private final File mResourceBase;
+
+ private final File mBaseCompileOutputFolder;
+
+ private Map<String, File> mCompileOutputFolders;
+
+ private Map<String, ClassLoader> mEnhancedClassLoaders;
+
+ private Map<String, List<String>> mEnhancedClasses;
+
+ private Collection<String> mBaseClasses;
+
+ private String currentPatchState = null;
+
+ private final boolean tracing;
+
+ public ClassEnhancement() {
+ this(true);
+ }
+
+ public ClassEnhancement(boolean tracing) {
+ this.tracing = tracing;
+ mResourceBase = new File("incremental-test-classes-dex");
+ mBaseCompileOutputFolder = null;
+ }
+
+ public void reset()
+ throws ClassNotFoundException, InstantiationException, IllegalAccessException,
+ NoSuchFieldException {
+ applyPatch(null);
+ }
+
+ public void applyPatch(@Nullable String patch)
+ throws ClassNotFoundException, NoSuchFieldException, InstantiationException,
+ IllegalAccessException {
+
+ // if requested level is null, always reset no matter what state we think we are in since
+ // we share the same class loader among all ClassEnhancement instances.
+ if (patch == null || !Objects.equal(patch, currentPatchState)) {
+ Iterable<String> classNames = patch == null ? mBaseClasses
+ : mEnhancedClasses.get(patch);
+
+ for (String changedClassName : classNames) {
+ if (changedClassName.endsWith("$override")) {
+ changedClassName = changedClassName.substring(0, changedClassName.length() - 9);
+ }
+ patchClass(changedClassName, patch);
+ }
+ currentPatchState = patch;
+ }
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+
+ mCompileOutputFolders = getCompileFolders(mResourceBase);
+
+ final ClassLoader mainClassLoader = this.getClass().getClassLoader();
+ mEnhancedClassLoaders = setUpEnhancedClassLoaders(
+ mainClassLoader, mCompileOutputFolders, tracing);
+ mEnhancedClasses = findEnhancedClasses(mCompileOutputFolders);
+ ImmutableSet.Builder<String> baseClassesBuilder = ImmutableSet.builder();
+ for (List<String> classNames: mEnhancedClasses.values()) {
+ baseClassesBuilder.addAll(classNames);
+ }
+ mBaseClasses = baseClassesBuilder.build();
+ base.evaluate();
+
+ }
+ };
+ }
+
+ private static Map<String, File> getCompileFolders(File folder) throws IOException {
+ Context context = InstrumentationRegistry.getContext();
+ String[] names = context.getAssets().list(folder.getPath());
+ ImmutableMap.Builder<String, File> builder = ImmutableMap.builder();
+
+ for (String name : names) {
+ builder.put(name, new File(folder, name));
+ }
+
+ return builder.build();
+ }
+
+ private static Map<String, ClassLoader> setUpEnhancedClassLoaders(
+ final ClassLoader mainClassLoader,
+ final Map<String, File> compileOutputFolders,
+ final boolean tracing) {
+ return Maps.transformEntries(compileOutputFolders,
+ new Maps.EntryTransformer<String, File, ClassLoader>() {
+ @Override
+ public ClassLoader transformEntry(@Nullable String patch,
+ @Nullable File compileOutputFolder) {
+ Context context = InstrumentationRegistry.getContext();
+ File optimizedDir = new ContextCompat().getCodeCacheDir(context);
+
+ try {
+ InputStream is = context.getAssets()
+ .open(compileOutputFolder.getPath() + "/classes.dex");
+ File output;
+ try {
+ File patchDir = new File(
+ context.getDir("patches", Context.MODE_PRIVATE), patch);
+ patchDir.mkdir();
+ output = new File(patchDir, patch + ".dex");
+
+ Files.asByteSink(output).writeFrom(is);
+ } finally {
+ is.close();
+ }
+
+ return new DexClassLoader(output.getAbsolutePath(),
+ optimizedDir.getAbsolutePath(),
+ null,
+ ClassEnhancement.class.getClassLoader());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ private static Map<String, List<String>> findEnhancedClasses(
+ final Map<String, File> compileOutputFolders) {
+ final Context context = InstrumentationRegistry.getContext();
+ return Maps.transformEntries(compileOutputFolders,
+ new Maps.EntryTransformer<String, File, List<String>>() {
+ @Override
+ public List<String> transformEntry(@Nullable String patch,
+ @Nullable File outputFolder) {
+
+ try {
+ InputStream classesIS = context.getAssets()
+ .open(outputFolder.getPath() + "/classes.txt");
+ try {
+ Iterable<String> classPaths = Splitter.on('\n')
+ .omitEmptyStrings().split(
+ CharStreams.toString(
+ new InputStreamReader(classesIS,
+ Charsets.UTF_8)));
+ return Lists.newArrayList(Iterables
+ .transform(classPaths, new Function<String, String>() {
+ @Override
+ public String apply(@Nullable String relativePath) {
+ return relativePath
+ .substring(0, relativePath.length() - 6 /*.class */)
+ .replace('/', '.');
+ }
+ }));
+ } finally {
+ classesIS.close();
+ }
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Could not open patch classes.dex", e);
+ }
+ }
+ });
+ }
+
+
+ private void patchClass(@NonNull String name, @Nullable String patch)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException,
+ NoSuchFieldException {
+
+ Class<?> originalEnhancedClass = getClass().getClassLoader().loadClass(name);
+ if (originalEnhancedClass.isInterface()) {
+ // we don't patch interfaces.
+ return;
+ }
+ Field newImplementationField = originalEnhancedClass.getField("$change");
+ // class might not be accessible from there
+ newImplementationField.setAccessible(true);
+
+ if (patch == null) {
+ // Revert to original implementation.
+ newImplementationField.set(null, null);
+ return;
+ }
+
+ Object change = mEnhancedClassLoaders.get(patch)
+ .loadClass(name + "$override").newInstance();
+
+ newImplementationField.set(null, change);
+ }
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/instant-unit-tests/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/instant-unit-tests/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..519a1de
--- /dev/null
+++ b/build-system/integration-test/test-projects/instant-unit-tests/src/main/AndroidManifest.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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tools.build.tests.mylibrary">
+
+ <application>
+
+ </application>
+
+</manifest>
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.png b/build-system/integration-test/test-projects/instant-unit-tests/src/main/resources/dummyfile.txt
similarity index 100%
copy from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.png
copy to build-system/integration-test/test-projects/instant-unit-tests/src/main/resources/dummyfile.txt
diff --git a/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/build.gradle b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/build.gradle
new file mode 100644
index 0000000..065653c
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/build.gradle
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+apply from: "../../commonHeader.gradle"
+buildscript { apply from: "../../commonBuildScript.gradle", to: buildscript }
+apply from: "../../commonLocalRepo.gradle"
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion project.ext.latestCompileSdk
+ buildToolsVersion = project.ext.buildToolsVersion
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
+}
diff --git a/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9bff747
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.test.jacoco.annotation" >
+
+ <application
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true">
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/java/com/test/jacoco/annotation/MainActivity.java b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/java/com/test/jacoco/annotation/MainActivity.java
new file mode 100644
index 0000000..effa472
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/java/com/test/jacoco/annotation/MainActivity.java
@@ -0,0 +1,16 @@
+package com.test.jacoco.annotation;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_layout);
+ TextView textView = (TextView) findViewById(R.id.textView);
+ textView.setText("foo");
+ }
+}
diff --git a/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/layout/main_layout.xml b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/layout/main_layout.xml
new file mode 100644
index 0000000..0b30af1
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/layout/main_layout.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="New Text"
+ android:id="@+id/textView" />
+</LinearLayout>
\ No newline at end of file
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
copy from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png
copy to build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..070bdb2
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/app/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <string name="app_name">Jacoco_Subproject_BuildScript_Dependency</string>
+</resources>
diff --git a/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/build.gradle b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/build.gradle
new file mode 100644
index 0000000..4cc75d3
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/build.gradle
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+
+
+// This root project build.gradle does not have
+// the android plugin as a build script dependency.
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/settings.gradle b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/settings.gradle
new file mode 100644
index 0000000..abb4d3f
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoOnlySubprojectBuildScriptDependency/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+include ':app'
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/jacocoWithButterKnife/build.gradle b/build-system/integration-test/test-projects/jacocoWithButterKnife/build.gradle
new file mode 100644
index 0000000..c97e1e9
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoWithButterKnife/build.gradle
@@ -0,0 +1,24 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply from: "../commonLocalRepo.gradle"
+
+repositories {
+ jcenter()
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.ext.latestCompileSdk
+ buildToolsVersion = rootProject.ext.buildToolsVersion
+
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
+}
+
+dependencies {
+ compile 'com.jakewharton:butterknife:7.0.1'
+}
diff --git a/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6de5fea
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.test.jacoco.annotation" >
+
+ <application
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true">
+ <activity android:name=".BindActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/java/com/test/jacoco/annotation/BindActivity.java b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/java/com/test/jacoco/annotation/BindActivity.java
new file mode 100644
index 0000000..a35b394
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/java/com/test/jacoco/annotation/BindActivity.java
@@ -0,0 +1,20 @@
+package com.test.jacoco.annotation;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import butterknife.Bind;
+
+public class BindActivity extends Activity {
+
+ @Bind(R.id.textView)
+ TextView textView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_layout);
+ textView.setText("foo");
+ }
+}
diff --git a/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/layout/main_layout.xml b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/layout/main_layout.xml
new file mode 100644
index 0000000..0b30af1
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/layout/main_layout.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="New Text"
+ android:id="@+id/textView" />
+</LinearLayout>
\ No newline at end of file
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c6bc6b8
--- /dev/null
+++ b/build-system/integration-test/test-projects/jacocoWithButterKnife/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">Jacoco_Butterknife</string>
+</resources>
diff --git a/build-system/integration-test/test-projects/jarjarIntegration/build.gradle b/build-system/integration-test/test-projects/jarjarIntegration/build.gradle
new file mode 100644
index 0000000..f8c7061
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarIntegration/build.gradle
@@ -0,0 +1,30 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+if (buildToolsVersion < '21.0.0') {
+ println ("Warning : this sample requires build-tools version 21 or above")
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+ }
+
+ registerTransform(new com.android.test.jarjar.JarJarTransform())
+}
+
+repositories {
+ maven { url System.env.CUSTOM_REPO }
+}
+
+dependencies {
+ compile 'com.google.code.gson:gson:2.3'
+}
diff --git a/build-system/integration-test/test-projects/jarjarIntegration/buildSrc/build.gradle b/build-system/integration-test/test-projects/jarjarIntegration/buildSrc/build.gradle
new file mode 100644
index 0000000..a5810bf
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarIntegration/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+apply plugin: 'idea'
+
+def env = new Object() {
+ String gradleVersion
+ String experimentalGradleVersion
+};
+
+apply from: "../../commonGradlePluginVersion.gradle", to: env
+apply from: "../../commonLocalRepo.gradle"
+
+dependencies {
+ compile "com.android.tools.build:gradle-api:$env.gradleVersion"
+ compile 'com.googlecode.jarjar:jarjar:1.3'
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/jarjarIntegration/buildSrc/src/main/java/com/android/test/jarjar/JarJarTransform.java b/build-system/integration-test/test-projects/jarjarIntegration/buildSrc/src/main/java/com/android/test/jarjar/JarJarTransform.java
new file mode 100644
index 0000000..94cdea7
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarIntegration/buildSrc/src/main/java/com/android/test/jarjar/JarJarTransform.java
@@ -0,0 +1,222 @@
+package com.android.test.jarjar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Closer;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class JarJarTransform extends Transform {
+
+ @Override
+ public String getName() {
+ return "jarjar";
+ }
+
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return ImmutableSet.<ContentType>of(DefaultContentType.CLASSES);
+ }
+
+ @Override
+ public Set<Scope> getScopes() {
+ // needs to run on everything to rename what is using gson
+ return EnumSet.of(
+ Scope.PROJECT,
+ Scope.PROJECT_LOCAL_DEPS,
+ Scope.SUB_PROJECTS,
+ Scope.SUB_PROJECTS_LOCAL_DEPS,
+ Scope.EXTERNAL_LIBRARIES);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void transform(
+ @NonNull Context context,
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedStreams,
+ @Nullable TransformOutputProvider output,
+ boolean isIncremental) throws TransformException, IOException {
+
+ if (output == null) {
+ throw new RuntimeException("Missing output object for transform " + getName());
+ }
+ output.deleteAll();
+ File outputJar = output.getContentLocation("main", getOutputTypes(), getScopes(),
+ Format.JAR);
+ // create the parent folder
+ outputJar.getParentFile().mkdirs();
+
+ // create intermediate files to handle the jar input and the rule file.
+ File mergedInputs = File.createTempFile("jajar", "jar");
+ File jarjarRules = File.createTempFile("jajar", "rule");
+
+ try {
+ // create a tmp jar that contains all the inputs. This is because jarjar expects a jar input.
+ // this code is based on the JarMergingTransform
+ combineInputIntoJar(inputs, mergedInputs);
+
+ // create the jarjar rules file.
+ Files.write("rule com.google.gson.** com.google.repacked.gson. at 1", jarjarRules, Charsets.UTF_8);
+
+ // run jarjar by calling the main method as if it came from the command line.
+ String[] args = ImmutableList.of(
+ "process",
+ jarjarRules.getAbsolutePath(),
+ mergedInputs.getAbsolutePath(),
+ outputJar.getAbsolutePath()
+ ).toArray(new String[4]);
+ com.tonicsystems.jarjar.Main.main(args);
+
+ } catch (Exception e) {
+ throw new TransformException(e);
+ } finally {
+ // delete tmp files
+ mergedInputs.delete();
+ jarjarRules.delete();
+ }
+ }
+
+ private void combineInputIntoJar(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull File mergedInputs) throws TransformException, IOException {
+ Closer closer = Closer.create();
+ try {
+
+ FileOutputStream fos = closer.register(new FileOutputStream(mergedInputs));
+ JarOutputStream jos = closer.register(new JarOutputStream(fos));
+
+ final byte[] buffer = new byte[8192];
+
+ for (TransformInput input : inputs) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ processJarFile(jos, jarInput.getFile(), buffer);
+ }
+
+ for (DirectoryInput dirInput : input.getDirectoryInputs()) {
+ processFolder(jos, "", dirInput.getFile(), buffer);
+ }
+ }
+
+ } catch (FileNotFoundException e) {
+ throw new TransformException(e);
+ } catch (IOException e) {
+ throw new TransformException(e);
+ } finally {
+ closer.close();
+ }
+ }
+
+ private static void processFolder(
+ @NonNull JarOutputStream jos,
+ @NonNull String path,
+ @NonNull File folder,
+ @NonNull byte[] buffer)
+ throws IOException {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile()) {
+ // new entry
+ jos.putNextEntry(new JarEntry(path + file.getName()));
+
+ // put the file content
+ Closer closer = Closer.create();
+ try {
+ FileInputStream fis = closer.register(new FileInputStream(file));
+ int count;
+ while ((count = fis.read(buffer)) != -1) {
+ jos.write(buffer, 0, count);
+ }
+ } finally {
+ closer.close();
+ }
+
+ // close the entry
+ jos.closeEntry();
+ } else if (file.isDirectory()) {
+ processFolder(jos, path + file.getName() + "/", file, buffer);
+ }
+ }
+ }
+ }
+
+ private static void processJarFile(JarOutputStream jos, File file, byte[] buffer)
+ throws IOException {
+
+ Closer closer = Closer.create();
+ try {
+ FileInputStream fis = closer.register(new FileInputStream(file));
+ ZipInputStream zis = closer.register(new ZipInputStream(fis));
+
+ // loop on the entries of the jar file package and put them in the final jar
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // do not take directories or anything inside a potential META-INF folder.
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ String name = entry.getName();
+ if (!name.endsWith(".class")) {
+ continue;
+ }
+
+ JarEntry newEntry;
+
+ // Preserve the STORED method of the input entry.
+ if (entry.getMethod() == JarEntry.STORED) {
+ newEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ newEntry = new JarEntry(name);
+ }
+
+ // add the entry to the jar archive
+ jos.putNextEntry(newEntry);
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ int count;
+ while ((count = zis.read(buffer)) != -1) {
+ jos.write(buffer, 0, count);
+ }
+
+ // close the entries for this file
+ jos.closeEntry();
+ zis.closeEntry();
+ }
+ } finally {
+ closer.close();
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/proguard-rules.pro b/build-system/integration-test/test-projects/jarjarIntegration/proguard-rules.pro
similarity index 100%
copy from base/build-system/integration-test/test-projects/jarjarIntegration/proguard-rules.pro
copy to build-system/integration-test/test-projects/jarjarIntegration/proguard-rules.pro
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/jarjarIntegration/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/jarjarIntegration/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/jarjarIntegration/src/main/AndroidManifest.xml
similarity index 100%
copy from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/AndroidManifest.xml
copy to build-system/integration-test/test-projects/jarjarIntegration/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/jarjarIntegration/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/jarjarIntegration/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/other.png b/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/other.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/other.png
rename to build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-mdpi/other.png
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarIntegration/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/jarjarIntegration/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/values/strings.xml
similarity index 100%
copy from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/values/strings.xml
copy to build-system/integration-test/test-projects/jarjarIntegration/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/jarjarIntegration/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/jarjarIntegration/src/release/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/jarjarIntegrationLib/build.gradle b/build-system/integration-test/test-projects/jarjarIntegrationLib/build.gradle
new file mode 100644
index 0000000..f46347b
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarIntegrationLib/build.gradle
@@ -0,0 +1,28 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+if (buildToolsVersion < '21.0.0') {
+ println ("Warning : this sample requires build-tools version 21 or above")
+}
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+ }
+}
+
+repositories {
+ maven { url System.env.CUSTOM_REPO }
+}
+
+dependencies {
+ compile 'com.google.code.gson:gson:2.3'
+}
diff --git a/build-system/integration-test/test-projects/jarjarIntegrationLib/buildSrc/build.gradle b/build-system/integration-test/test-projects/jarjarIntegrationLib/buildSrc/build.gradle
new file mode 100644
index 0000000..a5810bf
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarIntegrationLib/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+apply plugin: 'idea'
+
+def env = new Object() {
+ String gradleVersion
+ String experimentalGradleVersion
+};
+
+apply from: "../../commonGradlePluginVersion.gradle", to: env
+apply from: "../../commonLocalRepo.gradle"
+
+dependencies {
+ compile "com.android.tools.build:gradle-api:$env.gradleVersion"
+ compile 'com.googlecode.jarjar:jarjar:1.3'
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/jarjarIntegrationLib/buildSrc/src/main/java/com/android/test/jarjar/JarJarTransform.java b/build-system/integration-test/test-projects/jarjarIntegrationLib/buildSrc/src/main/java/com/android/test/jarjar/JarJarTransform.java
new file mode 100644
index 0000000..bf4fdb2
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarIntegrationLib/buildSrc/src/main/java/com/android/test/jarjar/JarJarTransform.java
@@ -0,0 +1,234 @@
+package com.android.test.jarjar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent.ContentType;
+import com.android.build.api.transform.QualifiedContent.DefaultContentType;
+import com.android.build.api.transform.QualifiedContent.Scope;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Closer;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class JarJarTransform extends Transform {
+
+ private final boolean broken;
+
+ public JarJarTransform(boolean broken) {
+ this.broken = broken;
+ }
+
+ @Override
+ public String getName() {
+ return "jarjar";
+ }
+
+ @Override
+ public Set<ContentType> getInputTypes() {
+ return ImmutableSet.<ContentType>of(DefaultContentType.CLASSES);
+ }
+
+ @Override
+ public Set<Scope> getScopes() {
+ if (broken) {
+ // needs to run on everything to rename what is using gson
+ return EnumSet.of(
+ Scope.PROJECT,
+ Scope.PROJECT_LOCAL_DEPS,
+ Scope.SUB_PROJECTS,
+ Scope.SUB_PROJECTS_LOCAL_DEPS,
+ Scope.EXTERNAL_LIBRARIES);
+ }
+
+ return EnumSet.of(Scope.PROJECT, Scope.PROJECT_LOCAL_DEPS);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void transform(
+ @NonNull Context context,
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull Collection<TransformInput> referencedStreams,
+ @Nullable TransformOutputProvider output,
+ boolean isIncremental) throws TransformException, IOException {
+
+ if (output == null) {
+ throw new RuntimeException("Missing output object for transform " + getName());
+ }
+ output.deleteAll();
+ File outputJar = output.getContentLocation("main", getOutputTypes(), getScopes(),
+ Format.JAR);
+ // create the parent folder
+ outputJar.getParentFile().mkdirs();
+
+ // create intermediate files to handle the jar input and the rule file.
+ File mergedInputs = File.createTempFile("jajar", "jar");
+ File jarjarRules = File.createTempFile("jajar", "rule");
+
+ try {
+ // create a tmp jar that contains all the inputs. This is because jarjar expects a jar input.
+ // this code is based on the JarMergingTransform
+ combineInputIntoJar(inputs, mergedInputs);
+
+ // create the jarjar rules file.
+ Files.write("rule com.google.gson.** com.google.repacked.gson. at 1", jarjarRules, Charsets.UTF_8);
+
+ // run jarjar by calling the main method as if it came from the command line.
+ String[] args = ImmutableList.of(
+ "process",
+ jarjarRules.getAbsolutePath(),
+ mergedInputs.getAbsolutePath(),
+ outputJar.getAbsolutePath()
+ ).toArray(new String[4]);
+ com.tonicsystems.jarjar.Main.main(args);
+
+ } catch (Exception e) {
+ throw new TransformException(e);
+ } finally {
+ // delete tmp files
+ mergedInputs.delete();
+ jarjarRules.delete();
+ }
+ }
+
+ private void combineInputIntoJar(
+ @NonNull Collection<TransformInput> inputs,
+ @NonNull File mergedInputs) throws TransformException, IOException {
+ Closer closer = Closer.create();
+ try {
+
+ FileOutputStream fos = closer.register(new FileOutputStream(mergedInputs));
+ JarOutputStream jos = closer.register(new JarOutputStream(fos));
+
+ final byte[] buffer = new byte[8192];
+
+ for (TransformInput input : inputs) {
+ for (JarInput jarInput : input.getJarInputs()) {
+ processJarFile(jos, jarInput.getFile(), buffer);
+ }
+
+ for (DirectoryInput dirInput : input.getDirectoryInputs()) {
+ processFolder(jos, "", dirInput.getFile(), buffer);
+ }
+ }
+
+ } catch (FileNotFoundException e) {
+ throw new TransformException(e);
+ } catch (IOException e) {
+ throw new TransformException(e);
+ } finally {
+ closer.close();
+ }
+ }
+
+ private static void processFolder(
+ @NonNull JarOutputStream jos,
+ @NonNull String path,
+ @NonNull File folder,
+ @NonNull byte[] buffer)
+ throws IOException {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile()) {
+ // new entry
+ jos.putNextEntry(new JarEntry(path + file.getName()));
+
+ // put the file content
+ Closer closer = Closer.create();
+ try {
+ FileInputStream fis = closer.register(new FileInputStream(file));
+ int count;
+ while ((count = fis.read(buffer)) != -1) {
+ jos.write(buffer, 0, count);
+ }
+ } finally {
+ closer.close();
+ }
+
+ // close the entry
+ jos.closeEntry();
+ } else if (file.isDirectory()) {
+ processFolder(jos, path + file.getName() + "/", file, buffer);
+ }
+ }
+ }
+ }
+
+ private static void processJarFile(JarOutputStream jos, File file, byte[] buffer)
+ throws IOException {
+
+ Closer closer = Closer.create();
+ try {
+ FileInputStream fis = closer.register(new FileInputStream(file));
+ ZipInputStream zis = closer.register(new ZipInputStream(fis));
+
+ // loop on the entries of the jar file package and put them in the final jar
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // do not take directories or anything inside a potential META-INF folder.
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ String name = entry.getName();
+ if (!name.endsWith(".class")) {
+ continue;
+ }
+
+ JarEntry newEntry;
+
+ // Preserve the STORED method of the input entry.
+ if (entry.getMethod() == JarEntry.STORED) {
+ newEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ newEntry = new JarEntry(name);
+ }
+
+ // add the entry to the jar archive
+ jos.putNextEntry(newEntry);
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ int count;
+ while ((count = zis.read(buffer)) != -1) {
+ jos.write(buffer, 0, count);
+ }
+
+ // close the entries for this file
+ jos.closeEntry();
+ zis.closeEntry();
+ }
+ } finally {
+ closer.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/proguard-rules.pro b/build-system/integration-test/test-projects/jarjarIntegrationLib/proguard-rules.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/proguard-rules.pro
rename to build-system/integration-test/test-projects/jarjarIntegrationLib/proguard-rules.pro
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..15e5062
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,26 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import com.google.gson.Gson;
+
+import java.lang.String;
+
+public class Main extends Activity
+{
+ static final String[] STRINGS = { "some", "radom", "strings"};
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.text);
+ Gson gson = new Gson();
+ String jsonString = gson.toJson(STRINGS);
+ tv.setText(jsonString);
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/drawable/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/drawable/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/other.png b/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/drawable/other.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/other.png
rename to build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/drawable/other.png
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/jarjarIntegrationLib/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/jarjarWithJack/build.gradle b/build-system/integration-test/test-projects/jarjarWithJack/build.gradle
new file mode 100644
index 0000000..dbc0bed
--- /dev/null
+++ b/build-system/integration-test/test-projects/jarjarWithJack/build.gradle
@@ -0,0 +1,37 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+if (buildToolsVersion < '21.0.0') {
+ println ("Warning : this sample requires build-tools version 21 or above")
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+ useJack true
+ jarJarRuleFile 'jarjar.rules'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+}
+
+repositories {
+ maven { url System.env.CUSTOM_REPO }
+}
+
+dependencies {
+ compile 'com.google.code.gson:gson:2.3'
+}
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/jarjar.rules b/build-system/integration-test/test-projects/jarjarWithJack/jarjar.rules
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/jarjar.rules
rename to build-system/integration-test/test-projects/jarjarWithJack/jarjar.rules
diff --git a/base/build-system/integration-test/test-projects/minify/proguard-rules.pro b/build-system/integration-test/test-projects/jarjarWithJack/proguard-rules.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/proguard-rules.pro
rename to build-system/integration-test/test-projects/jarjarWithJack/proguard-rules.pro
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/jarjarWithJack/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/jarjarWithJack/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/jarjarWithJack/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/jarjarWithJack/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/other.png b/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/other.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/densitySplit/src/main/res/drawable-mdpi/other.png
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-mdpi/other.png
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/jarjarWithJack/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/jarjarWithJack/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/jarjarWithJack/src/release/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/libDependency/app/build.gradle b/build-system/integration-test/test-projects/libDependency/app/build.gradle
new file mode 100644
index 0000000..cc4004b
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/app/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib')
+}
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/proguard-project.txt b/build-system/integration-test/test-projects/libDependency/app/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/proguard-project.txt
rename to build-system/integration-test/test-projects/libDependency/app/proguard-project.txt
diff --git a/build-system/integration-test/test-projects/libDependency/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/integration-test/test-projects/libDependency/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
new file mode 100644
index 0000000..2346c38
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 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.tests.libstest.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mAppTextView1;
+ private TextView mAppTextView2;
+ private TextView mLibTextView1;
+ private TextView mLibTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mAppTextView1 = (TextView) a.findViewById(R.id.app_text1);
+ mAppTextView2 = (TextView) a.findViewById(R.id.app_text1);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mAppTextView1);
+ assertNotNull(mAppTextView2);
+ assertNotNull(mLibTextView1);
+ assertNotNull(mLibTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView1.getText().toString());
+ assertEquals("SUCCESS-LIB", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText().toString());
+ assertEquals("SUCCESS-LIB", mLibTextView2.getText().toString());
+ }
+}
diff --git a/build-system/integration-test/test-projects/libDependency/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libDependency/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4354533
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.tests.libstest.app"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk
+ android:minSdkVersion="15" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" tools:replace="label, icon"
+ tools:ignore="GoogleAppIndexingWarning">
+ <activity
+ android:name="com.android.tests.libstest.app.MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/libDependency/app/src/main/java/com/android/tests/libstest/app/App.java b/build-system/integration-test/test-projects/libDependency/app/src/main/java/com/android/tests/libstest/app/App.java
new file mode 100644
index 0000000..6b68b77
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/app/src/main/java/com/android/tests/libstest/app/App.java
@@ -0,0 +1,41 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class App {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.app_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+ private static String getContent() {
+ InputStream input = App.class.getResourceAsStream("App.txt");
+ if (input == null) {
+ return "FAILED TO FIND App.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/integration-test/test-projects/libDependency/app/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/integration-test/test-projects/libDependency/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
new file mode 100644
index 0000000..5b26ab8
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
@@ -0,0 +1,19 @@
+package com.android.tests.libstest.app;
+
+import com.android.tests.libstest.lib.Lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+import java.util.logging.Logger;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ App.handleTextView(this);
+ Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning(Lib.someString());
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/libDependency/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/libDependency/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/libDependency/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/libDependency/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/genFolderApi/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/libDependency/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/libDependency/app/src/main/res/drawable-mdpi/icon.png
diff --git a/build-system/integration-test/test-projects/libDependency/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/libDependency/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..de898bf
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/app/src/main/res/layout/main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/app_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_string" />
+
+ <TextView
+ android:id="@+id/app_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib_main" />
+
+</LinearLayout>
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libDependency/app/src/main/res/values/strings.xml
similarity index 100%
copy from base/build-system/integration-test/test-projects/libsTest/app/src/main/res/values/strings.xml
copy to build-system/integration-test/test-projects/libDependency/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/resources/com/android/tests/libstest/app/App.txt b/build-system/integration-test/test-projects/libDependency/app/src/main/resources/com/android/tests/libstest/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
rename to build-system/integration-test/test-projects/libDependency/app/src/main/resources/com/android/tests/libstest/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/build.gradle b/build-system/integration-test/test-projects/libDependency/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/build.gradle
rename to build-system/integration-test/test-projects/libDependency/build.gradle
diff --git a/build-system/integration-test/test-projects/libDependency/lib/build.gradle b/build-system/integration-test/test-projects/libDependency/lib/build.gradle
new file mode 100644
index 0000000..db5939a
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.library'
+
+dependencies {
+}
+
+android {
+ resourcePrefix 'lib_'
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 15
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/proguard-project.txt b/build-system/integration-test/test-projects/libDependency/lib/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavorlibWithFailedTests/lib2/proguard-project.txt
rename to build-system/integration-test/test-projects/libDependency/lib/proguard-project.txt
diff --git a/build-system/integration-test/test-projects/libDependency/lib/src/androidTest/java/com/android/tests/libstest/lib/MainActivityTest.java b/build-system/integration-test/test-projects/libDependency/lib/src/androidTest/java/com/android/tests/libstest/lib/MainActivityTest.java
new file mode 100644
index 0000000..2f4782c
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/src/androidTest/java/com/android/tests/libstest/lib/MainActivityTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008 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.tests.libstest.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLibTextView1;
+ private TextView mLibTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLibTextView1);
+ assertNotNull(mLibTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB", mLibTextView1.getText().toString());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB", mLibTextView2.getText().toString());
+ }
+}
diff --git a/build-system/integration-test/test-projects/libDependency/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libDependency/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2fc5ad1
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.tests.libstest.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application>
+ <activity
+ android:name="MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/libDependency/lib/src/main/java/com/android/tests/libstest/lib/Lib.java b/build-system/integration-test/test-projects/libDependency/lib/src/main/java/com/android/tests/libstest/lib/Lib.java
new file mode 100644
index 0000000..93dcca8
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/src/main/java/com/android/tests/libstest/lib/Lib.java
@@ -0,0 +1,7 @@
+package com.android.tests.libstest.lib;
+
+public class Lib {
+ public static String someString() {
+ return "Original-Unedited";
+ }
+}
diff --git a/build-system/integration-test/test-projects/libDependency/lib/src/main/java/com/android/tests/libstest/lib/MainActivity.java b/build-system/integration-test/test-projects/libDependency/lib/src/main/java/com/android/tests/libstest/lib/MainActivity.java
new file mode 100644
index 0000000..ccc9916
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/src/main/java/com/android/tests/libstest/lib/MainActivity.java
@@ -0,0 +1,17 @@
+package com.android.tests.libstest.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+import java.util.logging.Logger;
+
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib_main);
+
+ Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning(Lib.someString());
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/libDependency/lib/src/main/res/drawable-hdpi/lib_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/libDependency/lib/src/main/res/drawable-hdpi/lib_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/libDependency/lib/src/main/res/drawable-ldpi/lib_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/libDependency/lib/src/main/res/drawable-ldpi/lib_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/genFolderApi2/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/libDependency/lib/src/main/res/drawable-mdpi/lib_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi2/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/libDependency/lib/src/main/res/drawable-mdpi/lib_ic_launcher.png
diff --git a/build-system/integration-test/test-projects/libDependency/lib/src/main/res/layout/lib_main.xml b/build-system/integration-test/test-projects/libDependency/lib/src/main/res/layout/lib_main.xml
new file mode 100644
index 0000000..a328a7d
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/src/main/res/layout/lib_main.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/build-system/integration-test/test-projects/libDependency/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libDependency/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c4346fe
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib_name">LibsTest-lib</string>
+ <string name="lib_string">SUCCESS-LIB</string>
+
+</resources>
diff --git a/build-system/integration-test/test-projects/libDependency/lib/src/main/resources/com/android/tests/libstest/lib/Lib.txt b/build-system/integration-test/test-projects/libDependency/lib/src/main/resources/com/android/tests/libstest/lib/Lib.txt
new file mode 100644
index 0000000..d395289
--- /dev/null
+++ b/build-system/integration-test/test-projects/libDependency/lib/src/main/resources/com/android/tests/libstest/lib/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/settings.gradle b/build-system/integration-test/test-projects/libDependency/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/settings.gradle
rename to build-system/integration-test/test-projects/libDependency/settings.gradle
diff --git a/build-system/integration-test/test-projects/libMinify/build.gradle b/build-system/integration-test/test-projects/libMinify/build.gradle
new file mode 100644
index 0000000..a1d1470
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinify/build.gradle
@@ -0,0 +1,23 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ }
+ buildTypes {
+ release {
+ minifyEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libMinify/config.pro b/build-system/integration-test/test-projects/libMinify/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinify/config.pro
rename to build-system/integration-test/test-projects/libMinify/config.pro
diff --git a/base/build-system/integration-test/test-projects/libMinify/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libMinify/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinify/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libMinify/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libMinify/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/integration-test/test-projects/libMinify/src/main/java/com/android/tests/basic/StringProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinify/src/main/java/com/android/tests/basic/StringProvider.java
rename to build-system/integration-test/test-projects/libMinify/src/main/java/com/android/tests/basic/StringProvider.java
diff --git a/build-system/integration-test/test-projects/libMinifyJarDep/app/build.gradle b/build-system/integration-test/test-projects/libMinifyJarDep/app/build.gradle
new file mode 100644
index 0000000..1f090cb
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyJarDep/app/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'com.android.application'
+
+apply from: "../../commonLocalRepo.gradle"
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testBuildType "proguard"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ proguard.initWith(buildTypes.debug)
+ proguard {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'config.pro'
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/config.pro b/build-system/integration-test/test-projects/libMinifyJarDep/app/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/app/config.pro
rename to build-system/integration-test/test-projects/libMinifyJarDep/app/config.pro
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/libMinifyJarDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/libMinifyJarDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutVariants/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/build.gradle b/build-system/integration-test/test-projects/libMinifyJarDep/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/build.gradle
rename to build-system/integration-test/test-projects/libMinifyJarDep/build.gradle
diff --git a/build-system/integration-test/test-projects/libMinifyJarDep/lib/build.gradle b/build-system/integration-test/test-projects/libMinifyJarDep/lib/build.gradle
new file mode 100644
index 0000000..177d792
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyJarDep/lib/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.library'
+
+apply from: "../../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.google.guava:guava:15.0'
+ compile fileTree(dir: 'libs', include: '*.jar')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ consumerProguardFiles 'config.pro'
+ }
+ buildTypes {
+ debug {
+ minifyEnabled true
+ }
+ release {
+ minifyEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/lib/config.pro b/build-system/integration-test/test-projects/libMinifyJarDep/lib/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/lib/config.pro
rename to build-system/integration-test/test-projects/libMinifyJarDep/lib/config.pro
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/androidTest/java/com/andriod/tests/basic/StringGetterTest.java b/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/androidTest/java/com/andriod/tests/basic/StringGetterTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/androidTest/java/com/andriod/tests/basic/StringGetterTest.java
rename to build-system/integration-test/test-projects/libMinifyJarDep/lib/src/androidTest/java/com/andriod/tests/basic/StringGetterTest.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/java/com/android/tests/basic/StringGetter.java b/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
rename to build-system/integration-test/test-projects/libMinifyJarDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
diff --git a/base/build-system/integration-test/test-projects/minifyLib/settings.gradle b/build-system/integration-test/test-projects/libMinifyJarDep/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/settings.gradle
rename to build-system/integration-test/test-projects/libMinifyJarDep/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/util/build.gradle b/build-system/integration-test/test-projects/libMinifyJarDep/util/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/util/build.gradle
rename to build-system/integration-test/test-projects/libMinifyJarDep/util/build.gradle
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/People.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/People.java
rename to build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/People.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/Person.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/Person.java
rename to build-system/integration-test/test-projects/libMinifyJarDep/util/src/main/java/com/example/android/multiproject/person/Person.java
diff --git a/build-system/integration-test/test-projects/libMinifyLibDep/app/build.gradle b/build-system/integration-test/test-projects/libMinifyLibDep/app/build.gradle
new file mode 100644
index 0000000..6355003
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyLibDep/app/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testBuildType "proguard"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ proguard.initWith(buildTypes.debug)
+ proguard {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'config.pro'
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/config.pro b/build-system/integration-test/test-projects/libMinifyLibDep/app/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/app/config.pro
rename to build-system/integration-test/test-projects/libMinifyLibDep/app/config.pro
diff --git a/build-system/integration-test/test-projects/libMinifyLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/libMinifyLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..fa10e32
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyLibDep/app/src/androidTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,115 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private Main mainActivity;
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mainActivity = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(mainActivity);
+ mTextView = (TextView) mainActivity.findViewById(R.id.dateText);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ public void testNonObfuscatedMethod1() {
+ // check we can call the method since it shouldn't be obfuscated.
+ mainActivity.setUpTextView1();
+
+ // then test we can actually find the lib method
+ String className = "com.android.tests.basic.StringGetter";
+ String methodName = "getString";
+
+ searchMethod(className, methodName, true /*shouldExist*/);
+ }
+
+ public void testNonObfuscatedMethod2() {
+ // check we can call the method since it shouldn't be obfuscated.
+ mainActivity.setUpTextView2();
+
+ // then test we cannot find the lib method since it should be
+ // obfuscated by the app.
+ String className = "com.android.tests.basic.StringGetter";
+ String methodName = "getString2";
+
+ searchMethod(className, methodName, false /*shouldExist*/);
+ }
+
+ /**
+ * use reflection to get a method that should be obfuscated
+ */
+ public void testConsumerProguardRules() {
+ String className = "com.android.tests.basic.StringGetter";
+ String methodName = "getString3";
+
+ searchMethod(className, methodName, false /*shouldExist*/);
+ }
+
+ /**
+ * use reflection to get a class that should be obfuscated
+ */
+ public void testObfuscatedInternalClass() {
+ // in this case the whole class has been obfuscated.
+ String className = "com.android.tests.internal.StringGetterInternal";
+ try {
+ Class<?> theClass = Class.forName(className);
+ fail("Found " + className);
+ } catch (ClassNotFoundException e) {
+ // expected
+ }
+ }
+
+ public void testLib2ObfuscatedClass() {
+ // in this case the whole class has been obfuscated.
+ String className = "com.android.tests.basic.StringProvider";
+ try {
+ Class<?> theClass = Class.forName(className);
+ fail("Found " + className);
+ } catch (ClassNotFoundException e) {
+ // expected
+ }
+ }
+
+ private void searchMethod(String className, String methodName, boolean shouldExist) {
+ try {
+ Class<?> theClass = Class.forName(className);
+ Method method = theClass.getDeclaredMethod(methodName, int.class);
+ if (!shouldExist) {
+ fail("Found " + className + "." + methodName);
+ }
+ } catch (ClassNotFoundException e) {
+ fail("Did not find " + className);
+ } catch (NoSuchMethodException e) {
+ if (shouldExist) {
+ fail("Did not find " + className + "." + methodName);
+ }
+ }
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/filteredOutVariants/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutVariants/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyJarDep/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/build.gradle b/build-system/integration-test/test-projects/libMinifyLibDep/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/build.gradle
rename to build-system/integration-test/test-projects/libMinifyLibDep/build.gradle
diff --git a/build-system/integration-test/test-projects/libMinifyLibDep/lib/build.gradle b/build-system/integration-test/test-projects/libMinifyLibDep/lib/build.gradle
new file mode 100644
index 0000000..0ef5022
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyLibDep/lib/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.library'
+
+dependencies {
+ compile project(':lib2')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ consumerProguardFiles 'consumerRules.pro'
+ }
+
+ buildTypes {
+ debug {
+ minifyEnabled true
+ }
+ release {
+ minifyEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/config.pro b/build-system/integration-test/test-projects/libMinifyLibDep/lib/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib/config.pro
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib/config.pro
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/consumerRules.pro b/build-system/integration-test/test-projects/libMinifyLibDep/lib/consumerRules.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib/consumerRules.pro
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib/consumerRules.pro
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java b/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java b/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
new file mode 100644
index 0000000..6783413
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
@@ -0,0 +1,27 @@
+package com.android.tests.basic;
+
+import com.android.tests.internal.StringGetterInternal;
+
+public class StringGetter{
+
+ /**
+ * Public method that will not get obfuscated
+ */
+ public static String getString(int foo) {
+ return StringGetterInternal.getString(foo);
+ }
+
+ /**
+ * Public method that will get obfuscated by the app project.
+ */
+ public static String getString2(int foo) {
+ return StringGetterInternal.getString(foo);
+ }
+
+ /**
+ * method that will get obfuscated by the library.
+ */
+ public static String getString3(int foo) {
+ return StringGetterInternal.getString(foo);
+ }
+}
diff --git a/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/internal/StringGetterInternal.java b/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/internal/StringGetterInternal.java
new file mode 100644
index 0000000..cc43819
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyLibDep/lib/src/main/java/com/android/tests/internal/StringGetterInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tests.internal;
+
+/**
+ * Class that will get obfuscated by the library.
+ */
+public class StringGetterInternal {
+
+ public static String getString(int foo) {
+
+ // Do something useless to prevent the class from being inlined.
+ foo = foo + 1;
+ foo = foo - 1;
+
+ return Integer.toString(foo);
+ }
+}
diff --git a/build-system/integration-test/test-projects/libMinifyLibDep/lib2/build.gradle b/build-system/integration-test/test-projects/libMinifyLibDep/lib2/build.gradle
new file mode 100644
index 0000000..e9683d4
--- /dev/null
+++ b/build-system/integration-test/test-projects/libMinifyLibDep/lib2/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ }
+
+ buildTypes {
+ debug {
+ minifyEnabled true
+ }
+ release {
+ minifyEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/config.pro b/build-system/integration-test/test-projects/libMinifyLibDep/lib2/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/config.pro
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib2/config.pro
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/androidTest/java/com/android/tests/basic/StringProviderTest.java b/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/java/com/android/tests/basic/StringProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/java/com/android/tests/basic/StringProvider.java
rename to build-system/integration-test/test-projects/libMinifyLibDep/lib2/src/main/java/com/android/tests/basic/StringProvider.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/settings.gradle b/build-system/integration-test/test-projects/libMinifyLibDep/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/settings.gradle
rename to build-system/integration-test/test-projects/libMinifyLibDep/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/A.txt b/build-system/integration-test/test-projects/libProguardConsumerFiles/A.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libProguardConsumerFiles/A.txt
rename to build-system/integration-test/test-projects/libProguardConsumerFiles/A.txt
diff --git a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/B.txt b/build-system/integration-test/test-projects/libProguardConsumerFiles/B.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libProguardConsumerFiles/B.txt
rename to build-system/integration-test/test-projects/libProguardConsumerFiles/B.txt
diff --git a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/C.txt b/build-system/integration-test/test-projects/libProguardConsumerFiles/C.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libProguardConsumerFiles/C.txt
rename to build-system/integration-test/test-projects/libProguardConsumerFiles/C.txt
diff --git a/build-system/integration-test/test-projects/libProguardConsumerFiles/build.gradle b/build-system/integration-test/test-projects/libProguardConsumerFiles/build.gradle
new file mode 100644
index 0000000..89965e0
--- /dev/null
+++ b/build-system/integration-test/test-projects/libProguardConsumerFiles/build.gradle
@@ -0,0 +1,29 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ consumerProguardFiles 'A.txt'
+ }
+
+ buildTypes {
+ debug {
+ }
+
+ release {
+ minifyEnabled true
+ consumerProguardFiles 'B.txt', 'C.txt'
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/config.pro b/build-system/integration-test/test-projects/libProguardConsumerFiles/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/libProguardConsumerFiles/config.pro
rename to build-system/integration-test/test-projects/libProguardConsumerFiles/config.pro
diff --git a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/java/com/android/tests/basic/StringProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/java/com/android/tests/basic/StringProvider.java
rename to build-system/integration-test/test-projects/libProguardConsumerFiles/src/main/java/com/android/tests/basic/StringProvider.java
diff --git a/build-system/integration-test/test-projects/libTestDep/build.gradle b/build-system/integration-test/test-projects/libTestDep/build.gradle
new file mode 100644
index 0000000..7384017
--- /dev/null
+++ b/build-system/integration-test/test-projects/libTestDep/build.gradle
@@ -0,0 +1,17 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.google.code.findbugs:jsr305:1.3.9'
+ compile 'com.google.guava:guava:15.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java b/build-system/integration-test/test-projects/libTestDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
rename to build-system/integration-test/test-projects/libTestDep/src/androidTest/java/com/android/tests/libdeps/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libTestDep/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libTestDep/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/main/java/com/android/tests/libdeps/MainActivity.java b/build-system/integration-test/test-projects/libTestDep/src/main/java/com/android/tests/libdeps/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/main/java/com/android/tests/libdeps/MainActivity.java
rename to build-system/integration-test/test-projects/libTestDep/src/main/java/com/android/tests/libdeps/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/libTestDep/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/main/res/layout/lib1_main.xml b/build-system/integration-test/test-projects/libTestDep/src/main/res/layout/lib1_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/main/res/layout/lib1_main.xml
rename to build-system/integration-test/test-projects/libTestDep/src/main/res/layout/lib1_main.xml
diff --git a/base/build-system/integration-test/test-projects/libTestDep/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libTestDep/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libTestDep/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libTestDep/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/libsTest/app/build.gradle b/build-system/integration-test/test-projects/libsTest/app/build.gradle
new file mode 100644
index 0000000..02aa660
--- /dev/null
+++ b/build-system/integration-test/test-projects/libsTest/app/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib1')
+ compile project(':lib2b')
+ compile project(':libapp')
+}
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/proguard-project.txt b/build-system/integration-test/test-projects/libsTest/app/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/proguard-project.txt
rename to build-system/integration-test/test-projects/libsTest/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/integration-test/test-projects/libsTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
rename to build-system/integration-test/test-projects/libsTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libsTest/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libsTest/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/App.java b/build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/App.java
rename to build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/App.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
rename to build-system/integration-test/test-projects/libsTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/libsTest/app/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/libsTest/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/libsTest/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libsTest/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libsTest/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/src/main/resources/com/android/tests/libstest/app/App.txt b/build-system/integration-test/test-projects/libsTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
rename to build-system/integration-test/test-projects/libsTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/localAarTest/build.gradle b/build-system/integration-test/test-projects/libsTest/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/build.gradle
rename to build-system/integration-test/test-projects/libsTest/build.gradle
diff --git a/build-system/integration-test/test-projects/libsTest/lib1/build.gradle b/build-system/integration-test/test-projects/libsTest/lib1/build.gradle
new file mode 100644
index 0000000..641d1ab
--- /dev/null
+++ b/build-system/integration-test/test-projects/libsTest/lib1/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'com.android.library'
+
+dependencies {
+ compile project(':lib2')
+}
+
+android {
+ resourcePrefix 'lib1_'
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 15
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/proguard-project.txt b/build-system/integration-test/test-projects/libsTest/lib1/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/proguard-project.txt
rename to build-system/integration-test/test-projects/libsTest/lib1/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java b/build-system/integration-test/test-projects/libsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
rename to build-system/integration-test/test-projects/libsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libsTest/lib1/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java b/build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java b/build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/layout/lib1_main.xml b/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/layout/lib1_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/layout/lib1_main.xml
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/res/layout/lib1_main.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt b/build-system/integration-test/test-projects/libsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
rename to build-system/integration-test/test-projects/libsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
diff --git a/build-system/integration-test/test-projects/libsTest/lib2/build.gradle b/build-system/integration-test/test-projects/libsTest/lib2/build.gradle
new file mode 100644
index 0000000..7a52d8c
--- /dev/null
+++ b/build-system/integration-test/test-projects/libsTest/lib2/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.library'
+
+android {
+ resourcePrefix 'lib2_'
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
+
+dependencies {
+ compile 'com.google.android.gms:play-services:3.1.36'
+}
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/proguard-project.txt b/build-system/integration-test/test-projects/libsTest/lib2/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/proguard-project.txt
rename to build-system/integration-test/test-projects/libsTest/lib2/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/integration-test/test-projects/libsTest/lib2/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
rename to build-system/integration-test/test-projects/libsTest/lib2/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libsTest/lib2/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/Lib2.java b/build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/Lib2.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/Lib2.java
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/Lib2.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/MainActivity.java b/build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-hdpi/lib2_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-hdpi/lib2_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-hdpi/lib2_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-hdpi/lib2_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-ldpi/lib2_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-ldpi/lib2_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-ldpi/lib2_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-ldpi/lib2_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-mdpi/lib2_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-mdpi/lib2_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-mdpi/lib2_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/res/drawable-mdpi/lib2_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/layout/lib2_main.xml b/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/layout/lib2_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/layout/lib2_main.xml
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/res/layout/lib2_main.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt b/build-system/integration-test/test-projects/libsTest/lib2/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
rename to build-system/integration-test/test-projects/libsTest/lib2/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
diff --git a/build-system/integration-test/test-projects/libsTest/lib2b/build.gradle b/build-system/integration-test/test-projects/libsTest/lib2b/build.gradle
new file mode 100644
index 0000000..6716109
--- /dev/null
+++ b/build-system/integration-test/test-projects/libsTest/lib2b/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'com.android.library'
+
+android {
+ resourcePrefix 'lib2b_'
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/proguard-project.txt b/build-system/integration-test/test-projects/libsTest/lib2b/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/proguard-project.txt
rename to build-system/integration-test/test-projects/libsTest/lib2b/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java b/build-system/integration-test/test-projects/libsTest/lib2b/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/Lib2b.java b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-hdpi/lib2b_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-hdpi/lib2b_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-hdpi/lib2b_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-hdpi/lib2b_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-ldpi/lib2b_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-ldpi/lib2b_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-ldpi/lib2b_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-ldpi/lib2b_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-mdpi/lib2b_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-mdpi/lib2b_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-mdpi/lib2b_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/drawable-mdpi/lib2b_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/layout/lib2b_main.xml b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/layout/lib2b_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/layout/lib2b_main.xml
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/layout/lib2b_main.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt b/build-system/integration-test/test-projects/libsTest/lib2b/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/lib2b/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
rename to build-system/integration-test/test-projects/libsTest/lib2b/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
diff --git a/build-system/integration-test/test-projects/libsTest/libapp/build.gradle b/build-system/integration-test/test-projects/libsTest/libapp/build.gradle
new file mode 100644
index 0000000..5b32989
--- /dev/null
+++ b/build-system/integration-test/test-projects/libsTest/libapp/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'com.android.library'
+
+android {
+ resourcePrefix 'libapp_'
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/proguard-project.txt b/build-system/integration-test/test-projects/libsTest/libapp/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/proguard-project.txt
rename to build-system/integration-test/test-projects/libsTest/libapp/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java b/build-system/integration-test/test-projects/libsTest/libapp/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
rename to build-system/integration-test/test-projects/libsTest/libapp/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/libsTest/libapp/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/LibApp.java b/build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/LibApp.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/LibApp.java
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/LibApp.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java b/build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-hdpi/libapp_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-hdpi/libapp_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-hdpi/libapp_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-hdpi/libapp_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-ldpi/libapp_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-ldpi/libapp_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-ldpi/libapp_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-ldpi/libapp_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-mdpi/libapp_ic_launcher.png b/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-mdpi/libapp_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-mdpi/libapp_ic_launcher.png
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/res/drawable-mdpi/libapp_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/layout/libapp_main.xml b/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/layout/libapp_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/layout/libapp_main.xml
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/res/layout/libapp_main.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/libapp/src/main/resources/com/android/tests/libstest/app/Libapp.txt b/build-system/integration-test/test-projects/libsTest/libapp/src/main/resources/com/android/tests/libstest/app/Libapp.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/libapp/src/main/resources/com/android/tests/libstest/app/Libapp.txt
rename to build-system/integration-test/test-projects/libsTest/libapp/src/main/resources/com/android/tests/libstest/app/Libapp.txt
diff --git a/base/build-system/integration-test/test-projects/libsTest/settings.gradle b/build-system/integration-test/test-projects/libsTest/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/settings.gradle
rename to build-system/integration-test/test-projects/libsTest/settings.gradle
diff --git a/build-system/integration-test/test-projects/localAarTest/app/build.gradle b/build-system/integration-test/test-projects/localAarTest/app/build.gradle
new file mode 100644
index 0000000..fc27dac
--- /dev/null
+++ b/build-system/integration-test/test-projects/localAarTest/app/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib1')
+}
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/proguard-project.txt b/build-system/integration-test/test-projects/localAarTest/app/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/proguard-project.txt
rename to build-system/integration-test/test-projects/localAarTest/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/localAarTest/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/localAarTest/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/App.java b/build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/App.java
rename to build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/App.java
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
rename to build-system/integration-test/test-projects/localAarTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/localAarTest/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/localAarTest/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/localAarTest/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/localAarTest/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/resources/com/android/tests/libstest/app/App.txt b/build-system/integration-test/test-projects/localAarTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/resources/com/android/tests/libstest/app/App.txt
rename to build-system/integration-test/test-projects/localAarTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/build.gradle b/build-system/integration-test/test-projects/localAarTest/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/build.gradle
rename to build-system/integration-test/test-projects/localAarTest/build.gradle
diff --git a/build-system/integration-test/test-projects/localAarTest/lib1/build.gradle b/build-system/integration-test/test-projects/localAarTest/lib1/build.gradle
new file mode 100644
index 0000000..0492a01
--- /dev/null
+++ b/build-system/integration-test/test-projects/localAarTest/lib1/build.gradle
@@ -0,0 +1,17 @@
+configurations.create("default")
+artifacts.add("default", file('lib1.aar'))
+
+/* uncomment to generate lib1.aar
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 15
+ }
+}
+*/
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/localAarTest/lib1/lib1.aar b/build-system/integration-test/test-projects/localAarTest/lib1/lib1.aar
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/lib1/lib1.aar
rename to build-system/integration-test/test-projects/localAarTest/lib1/lib1.aar
diff --git a/base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/localAarTest/lib1/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/localAarTest/lib1/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/localAarTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/localAarTest/settings.gradle b/build-system/integration-test/test-projects/localAarTest/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/settings.gradle
rename to build-system/integration-test/test-projects/localAarTest/settings.gradle
diff --git a/build-system/integration-test/test-projects/localJars/app/build.gradle b/build-system/integration-test/test-projects/localJars/app/build.gradle
new file mode 100644
index 0000000..4b05821
--- /dev/null
+++ b/build-system/integration-test/test-projects/localJars/app/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ packagingOptions {
+ exclude 'META-INF/exclude.txt'
+ exclude 'META-INF/LICENSE'
+ }
+}
+
+dependencies {
+ compile project(':library')
+}
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/localJars/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/localJars/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/integration-test/test-projects/localJars/app/src/main/java/com/example/android/multiproject/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/java/com/example/android/multiproject/MainActivity.java
rename to build-system/integration-test/test-projects/localJars/app/src/main/java/com/example/android/multiproject/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/localJars/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/localJars/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/localJars/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/localJars/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/localJars/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/localJars/app/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/localJars/baseLibrary/build.gradle b/build-system/integration-test/test-projects/localJars/baseLibrary/build.gradle
new file mode 100644
index 0000000..3a8c6df
--- /dev/null
+++ b/build-system/integration-test/test-projects/localJars/baseLibrary/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ packagingOptions {
+ exclude 'META-INF/exclude.txt'
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: '*.jar')
+ compile 'com.google.guava:guava:15.0'
+}
diff --git a/base/build-system/integration-test/test-projects/localJars/baseLibrary/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/localJars/baseLibrary/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/baseLibrary/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/localJars/baseLibrary/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/localJars/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/integration-test/test-projects/localJars/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
rename to build-system/integration-test/test-projects/localJars/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
diff --git a/base/build-system/integration-test/test-projects/localJars/build.gradle b/build-system/integration-test/test-projects/localJars/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/build.gradle
rename to build-system/integration-test/test-projects/localJars/build.gradle
diff --git a/build-system/integration-test/test-projects/localJars/library/build.gradle b/build-system/integration-test/test-projects/localJars/library/build.gradle
new file mode 100644
index 0000000..78efe59
--- /dev/null
+++ b/build-system/integration-test/test-projects/localJars/library/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+dependencies {
+ compile project(':baseLibrary')
+}
diff --git a/base/build-system/integration-test/test-projects/localJars/library/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/localJars/library/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/library/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/localJars/library/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/localJars/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/integration-test/test-projects/localJars/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
rename to build-system/integration-test/test-projects/localJars/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
diff --git a/base/build-system/integration-test/test-projects/localJars/library/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/localJars/library/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/library/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/localJars/library/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/localJars/settings.gradle b/build-system/integration-test/test-projects/localJars/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/settings.gradle
rename to build-system/integration-test/test-projects/localJars/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/localJars/util/build.gradle b/build-system/integration-test/test-projects/localJars/util/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/util/build.gradle
rename to build-system/integration-test/test-projects/localJars/util/build.gradle
diff --git a/base/build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/People.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/People.java
rename to build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/People.java
diff --git a/base/build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/Person.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/Person.java
rename to build-system/integration-test/test-projects/localJars/util/src/main/java/com/example/android/multiproject/person/Person.java
diff --git a/base/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/exclude.txt b/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/exclude.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/exclude.txt
rename to build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/exclude.txt
diff --git a/base/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/foo.txt b/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/foo.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/foo.txt
rename to build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/foo.txt
diff --git a/base/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/services/com.foo.MyService b/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/services/com.foo.MyService
similarity index 100%
rename from base/build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/services/com.foo.MyService
rename to build-system/integration-test/test-projects/localJars/util/src/main/resources/META-INF/services/com.foo.MyService
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/.gitignore b/build-system/integration-test/test-projects/mavenLocal/.gitignore
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/.gitignore
rename to build-system/integration-test/test-projects/mavenLocal/.gitignore
diff --git a/build-system/integration-test/test-projects/mavenLocal/app/build.gradle b/build-system/integration-test/test-projects/mavenLocal/app/build.gradle
new file mode 100644
index 0000000..02bacc0
--- /dev/null
+++ b/build-system/integration-test/test-projects/mavenLocal/app/build.gradle
@@ -0,0 +1,18 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+apply plugin: 'maven'
+
+repositories {
+ mavenLocal()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:lib:1.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/mavenLocal/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/integration-test/test-projects/mavenLocal/app/src/main/java/com/example/android/multiproject/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/java/com/example/android/multiproject/MainActivity.java
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/java/com/example/android/multiproject/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/mavenLocal/app/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/mavenLocal/baseLibrary/build.gradle b/build-system/integration-test/test-projects/mavenLocal/baseLibrary/build.gradle
new file mode 100644
index 0000000..7137fff
--- /dev/null
+++ b/build-system/integration-test/test-projects/mavenLocal/baseLibrary/build.gradle
@@ -0,0 +1,29 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+repositories {
+ mavenLocal()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:util:1.0'
+ releaseCompile 'com.google.guava:guava:15.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'baseLib'
+version = '1.0'
+
+uploadArchives {
+ repositories {
+ mavenInstaller()
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
rename to build-system/integration-test/test-projects/mavenLocal/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
diff --git a/build-system/integration-test/test-projects/mavenLocal/library/build.gradle b/build-system/integration-test/test-projects/mavenLocal/library/build.gradle
new file mode 100644
index 0000000..155b6e0
--- /dev/null
+++ b/build-system/integration-test/test-projects/mavenLocal/library/build.gradle
@@ -0,0 +1,28 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+repositories {
+ mavenLocal()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:baseLib:1.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'lib'
+version = '2.0'
+
+uploadArchives {
+ repositories {
+ mavenInstaller()
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/library/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/mavenLocal/library/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/library/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/mavenLocal/library/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/integration-test/test-projects/mavenLocal/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
rename to build-system/integration-test/test-projects/mavenLocal/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/library/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/mavenLocal/library/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/library/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/mavenLocal/library/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/util/build.gradle b/build-system/integration-test/test-projects/mavenLocal/util/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/util/build.gradle
rename to build-system/integration-test/test-projects/mavenLocal/util/build.gradle
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/People.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/People.java
rename to build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/People.java
diff --git a/base/build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/Person.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/Person.java
rename to build-system/integration-test/test-projects/mavenLocal/util/src/main/java/com/example/android/multiproject/person/Person.java
diff --git a/build-system/integration-test/test-projects/maxSdkVersion/build.gradle b/build-system/integration-test/test-projects/maxSdkVersion/build.gradle
new file mode 100644
index 0000000..472bb71
--- /dev/null
+++ b/build-system/integration-test/test-projects/maxSdkVersion/build.gradle
@@ -0,0 +1,52 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+ debugCompile 'com.android.support:support-v13:13.0.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testBuildType "debug"
+
+ signingConfigs {
+ myConfig {
+ storeFile file("debug.keystore")
+ storePassword "android"
+ keyAlias "androiddebugkey"
+ keyPassword "android"
+ }
+ }
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ maxSdkVersion 19
+ }
+
+ productFlavors {
+ f1 {
+ maxSdkVersion 21
+ applicationId = "com.android.tests.maxsdkversion.f1"
+ }
+ f2 {
+ applicationId = "com.android.tests.maxsdkversion.f2"
+ }
+ }
+
+ buildTypes {
+ debug {
+ applicationIdSuffix ".debug"
+ signingConfig signingConfigs.myConfig
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/flavored/debug.keystore b/build-system/integration-test/test-projects/maxSdkVersion/debug.keystore
similarity index 100%
rename from base/build-system/integration-test/test-projects/flavored/debug.keystore
rename to build-system/integration-test/test-projects/maxSdkVersion/debug.keystore
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/maxSdkVersion/src/main/AndroidManifest.xml
similarity index 100%
copy from base/build-system/integration-test/test-projects/maxSdkVersion/src/main/AndroidManifest.xml
copy to build-system/integration-test/test-projects/maxSdkVersion/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/filteredOutVariants/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/maxSdkVersion/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/filteredOutVariants/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/maxSdkVersion/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/maxSdkVersion/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/maxSdkVersion/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/maxSdkVersion/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/jarjarWithJack/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/maxSdkVersion/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarWithJack/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/maxSdkVersion/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/genFolderApi/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/migrated/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/migrated/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/main/assets/notice.txt b/build-system/integration-test/test-projects/migrated/assets/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/main/assets/notice.txt
rename to build-system/integration-test/test-projects/migrated/assets/notice.txt
diff --git a/build-system/integration-test/test-projects/migrated/build.gradle b/build-system/integration-test/test-projects/migrated/build.gradle
new file mode 100644
index 0000000..1fb589b
--- /dev/null
+++ b/build-system/integration-test/test-projects/migrated/build.gradle
@@ -0,0 +1,53 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ sourceSets {
+ main {
+ manifest {
+ // there's only ever one file so srcFile replaces it.
+ srcFile 'AndroidManifest.xml'
+ }
+ java {
+ // writing:
+ // srcDir 'src'
+ // would *add* to the default folder so we use a different syntax
+ srcDirs = ['src']
+ exclude 'some/unwanted/packageName/**'
+ }
+ res {
+ srcDirs = ['res']
+ }
+ assets {
+ srcDirs = ['assets']
+ }
+ resources {
+ srcDirs = ['src']
+ }
+ aidl {
+ srcDirs = ['src']
+ }
+ renderscript {
+ srcDirs = ['src']
+ }
+ }
+
+ // this moves src/androidTest to tests so all folders follow:
+ // tests/java, tests/res, tests/assets, ...
+ // This is a *reset* so it replaces the default paths
+ androidTest.setRoot('tests')
+
+ // Could also be done with:
+ //main.manifest.srcFile 'AndroidManifest.xml'
+ //main.java.srcDir 'src'
+ //main.res.srcDir 'res'
+ //main.assets.srcDir 'assets'
+ //main.resources.srcDir 'src'
+ //androidTest.java.srcDir 'tests/src'
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/migrated/res/drawable/icon.png b/build-system/integration-test/test-projects/migrated/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/res/drawable/icon.png
rename to build-system/integration-test/test-projects/migrated/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/migrated/res/layout/main.xml b/build-system/integration-test/test-projects/migrated/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/res/layout/main.xml
rename to build-system/integration-test/test-projects/migrated/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/raw/notice.txt b/build-system/integration-test/test-projects/migrated/res/raw/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/dependencyChecker/src/main/res/raw/notice.txt
rename to build-system/integration-test/test-projects/migrated/res/raw/notice.txt
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/migrated/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/maxSdkVersion/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/migrated/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.aidl b/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.aidl
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.aidl
rename to build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.aidl
diff --git a/base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.java b/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.java
rename to build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Foo.java
diff --git a/base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/IService.aidl b/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/IService.aidl
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/IService.aidl
rename to build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/IService.aidl
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/maxSdkVersion/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/migrated/src/some/unwanted/packageName/Useless.java b/build-system/integration-test/test-projects/migrated/src/some/unwanted/packageName/Useless.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/src/some/unwanted/packageName/Useless.java
rename to build-system/integration-test/test-projects/migrated/src/some/unwanted/packageName/Useless.java
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/migrated/tests/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
copy to build-system/integration-test/test-projects/migrated/tests/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/integration-test/test-projects/minify/build.gradle b/build-system/integration-test/test-projects/minify/build.gradle
new file mode 100644
index 0000000..0ebe141
--- /dev/null
+++ b/build-system/integration-test/test-projects/minify/build.gradle
@@ -0,0 +1,50 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+apply from: "../commonLocalRepo.gradle"
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testBuildType rootProject.useJack ? "debug" : "minified"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ minified.initWith(buildTypes.debug)
+ minified {
+ minifyEnabled true
+ useJack rootProject.useJack
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard-rules.pro"
+ testProguardFile "test-proguard-rules.pro"
+ testCoverageEnabled true
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+
+ lintOptions {
+ abortOnError !rootProject.useJack
+ }
+
+ // Included in both hamcrest-core and hamcrest-library.
+ packagingOptions.exclude 'LICENSE.txt'
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+
+ // This library references java.beans classes that are not part of Android,
+ // so ProGuard can't find references and fails without the rule from
+ // test-proguard-rules.pro.
+ androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
+}
diff --git a/base/build-system/integration-test/test-projects/jarjarIntegration/proguard-rules.pro b/build-system/integration-test/test-projects/minify/proguard-rules.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/jarjarIntegration/proguard-rules.pro
rename to build-system/integration-test/test-projects/minify/proguard-rules.pro
diff --git a/base/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UnusedTestClass.java b/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UnusedTestClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UnusedTestClass.java
rename to build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UnusedTestClass.java
diff --git a/base/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UsedTestClass.java b/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UsedTestClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UsedTestClass.java
rename to build-system/integration-test/test-projects/minify/src/androidTest/java/com/android/tests/basic/UsedTestClass.java
diff --git a/base/build-system/integration-test/test-projects/genFolderApi2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/minify/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/genFolderApi2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/minify/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/IndirectlyReferencedClass.java b/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/IndirectlyReferencedClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/IndirectlyReferencedClass.java
rename to build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/IndirectlyReferencedClass.java
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/StringProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/StringProvider.java
rename to build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/StringProvider.java
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/UnusedClass.java b/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/UnusedClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/UnusedClass.java
rename to build-system/integration-test/test-projects/minify/src/main/java/com/android/tests/basic/UnusedClass.java
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/minify/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/minify/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/minify/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/minify/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/migrated/res/values/strings.xml b/build-system/integration-test/test-projects/minify/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/res/values/strings.xml
rename to build-system/integration-test/test-projects/minify/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/minify/src/test/java/android/UnitTest.java b/build-system/integration-test/test-projects/minify/src/test/java/android/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/test/java/android/UnitTest.java
rename to build-system/integration-test/test-projects/minify/src/test/java/android/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/minify/test-proguard-rules.pro b/build-system/integration-test/test-projects/minify/test-proguard-rules.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/test-proguard-rules.pro
rename to build-system/integration-test/test-projects/minify/test-proguard-rules.pro
diff --git a/build-system/integration-test/test-projects/minifyLib/app/build.gradle b/build-system/integration-test/test-projects/minifyLib/app/build.gradle
new file mode 100644
index 0000000..ad8d330
--- /dev/null
+++ b/build-system/integration-test/test-projects/minifyLib/app/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testBuildType rootProject.useJack ? "debug" : "minified"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ minified.initWith(buildTypes.debug)
+ minified {
+ minifyEnabled true
+ useJack rootProject.useJack
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/minifyLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/minifyLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/minifyLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/minifyLib/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libMinifyLibDep/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/minifyLib/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLib/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/minifyLib/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/minifyLib/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/minifyLib/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/minifyLib/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/minifyLib/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/minifyLib/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/minifyLib/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/minifyLib/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/minifyLib/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/minifyLib/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLib/build.gradle b/build-system/integration-test/test-projects/minifyLib/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/build.gradle
rename to build-system/integration-test/test-projects/minifyLib/build.gradle
diff --git a/build-system/integration-test/test-projects/minifyLib/lib/build.gradle b/build-system/integration-test/test-projects/minifyLib/lib/build.gradle
new file mode 100644
index 0000000..a72251f
--- /dev/null
+++ b/build-system/integration-test/test-projects/minifyLib/lib/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ consumerProguardFiles 'config.pro'
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/minifyLib/lib/config.pro b/build-system/integration-test/test-projects/minifyLib/lib/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/lib/config.pro
rename to build-system/integration-test/test-projects/minifyLib/lib/config.pro
diff --git a/base/build-system/integration-test/test-projects/minifyLib/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java b/build-system/integration-test/test-projects/minifyLib/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
rename to build-system/integration-test/test-projects/minifyLib/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
diff --git a/base/build-system/integration-test/test-projects/minifyLib/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/minifyLib/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/minifyLib/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLib/lib/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/integration-test/test-projects/minifyLib/lib/src/main/java/com/android/tests/basic/StringProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/lib/src/main/java/com/android/tests/basic/StringProvider.java
rename to build-system/integration-test/test-projects/minifyLib/lib/src/main/java/com/android/tests/basic/StringProvider.java
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/settings.gradle b/build-system/integration-test/test-projects/minifyLib/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/settings.gradle
rename to build-system/integration-test/test-projects/minifyLib/settings.gradle
diff --git a/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/build.gradle b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/build.gradle
new file mode 100644
index 0000000..78d7849
--- /dev/null
+++ b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/build.gradle
@@ -0,0 +1,39 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ signingConfigs {
+ myConfig {
+ storeFile file("debug.keystore")
+ storePassword "android"
+ keyAlias "androiddebugkey"
+ keyPassword "android"
+ }
+ }
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ useJack rootProject.useJack
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ signingConfig signingConfigs.myConfig
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/debug.keystore b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/debug.keystore
similarity index 100%
rename from base/build-system/integration-test/test-projects/maxSdkVersion/debug.keystore
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/debug.keystore
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/migrated/AndroidManifest.xml b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/AndroidManifest.xml
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/util/AppStringProvider.java b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/util/AppStringProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/util/AppStringProvider.java
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/java/com/android/tests/util/AppStringProvider.java
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLib/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/another.properties b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/another.properties
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/another.properties
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/another.properties
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/resources.properties b/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/resources.properties
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/resources.properties
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/resources/com/android/tests/util/resources.properties
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/build.gradle b/build-system/integration-test/test-projects/minifyLibWithJavaRes/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/build.gradle
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/build.gradle
diff --git a/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/build.gradle b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/build.gradle
new file mode 100644
index 0000000..a72251f
--- /dev/null
+++ b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ consumerProguardFiles 'config.pro'
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/config.pro b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/config.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/config.pro
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/config.pro
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/androidTest/java/com/android/tests/basic/StringProviderTest.java
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/basic/StringProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/basic/StringProvider.java
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/basic/StringProvider.java
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/other/PropertiesProvider.java b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/other/PropertiesProvider.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/other/PropertiesProvider.java
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/java/com/android/tests/other/PropertiesProvider.java
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/another.properties b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/another.properties
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/another.properties
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/another.properties
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/resources.properties b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/resources.properties
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/resources.properties
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/resources.properties
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/some.xml b/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/some.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/some.xml
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/lib/src/main/resources/com/android/tests/other/some.xml
diff --git a/base/build-system/integration-test/test-projects/api/settings.gradle b/build-system/integration-test/test-projects/minifyLibWithJavaRes/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/settings.gradle
rename to build-system/integration-test/test-projects/minifyLibWithJavaRes/settings.gradle
diff --git a/build-system/integration-test/test-projects/multiDex/build.gradle b/build-system/integration-test/test-projects/multiDex/build.gradle
new file mode 100644
index 0000000..0501982
--- /dev/null
+++ b/build-system/integration-test/test-projects/multiDex/build.gradle
@@ -0,0 +1,45 @@
+apply from: "../commonHeader.gradle"
+apply from: "../commonLocalRepo.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ buildConfigField "String", "FOO", "\"foo\""
+ multiDexEnabled = true
+ useJack project.ext.useJack
+ }
+
+ productFlavors {
+ ics {
+ minSdkVersion 14
+ }
+ lollipop {
+ minSdkVersion 21
+ }
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField "String", "FOO", "\"bar\""
+ resValue "string", "foo", "foo2"
+ }
+ proguard {
+ minifyEnabled true
+ proguardFile file('proguard-android.txt')
+ }
+ }
+
+ lintOptions {
+ abortOnError !rootProject.useJack
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:20.0.0'
+ androidTestCompile 'junit:junit:4.12'
+}
diff --git a/base/build-system/integration-test/test-projects/multiDex/proguard-android.txt b/build-system/integration-test/test-projects/multiDex/proguard-android.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/proguard-android.txt
rename to build-system/integration-test/test-projects/multiDex/proguard-android.txt
diff --git a/build-system/integration-test/test-projects/multiDex/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/multiDex/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..fd85ad6
--- /dev/null
+++ b/build-system/integration-test/test-projects/multiDex/src/androidTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,49 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("bar", BuildConfig.FOO);
+ }
+
+ public void testDependency() {
+ // Make sure JUnit 4 is on the classpath, which means multidex works.
+ org.junit.Assert.assertEquals(4, 2+2);
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/ics/AndroidManifest.xml b/build-system/integration-test/test-projects/multiDex/src/ics/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/ics/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiDex/src/ics/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/multiDex/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiDex/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big001.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big001.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big001.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big001.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big002.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big002.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big002.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big002.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big003.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big003.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big003.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big003.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big004.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big004.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big004.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big004.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big005.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big005.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big005.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big005.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big006.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big006.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big006.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big006.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big007.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big007.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big007.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big007.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big008.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big008.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big008.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big008.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big009.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big009.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big009.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big009.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big010.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big010.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big010.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big010.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big011.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big011.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big011.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big011.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big012.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big012.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big012.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big012.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big013.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big013.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big013.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big013.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big014.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big014.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big014.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big014.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big015.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big015.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big015.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big015.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big016.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big016.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big016.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big016.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big017.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big017.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big017.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big017.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big018.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big018.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big018.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big018.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big019.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big019.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big019.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big019.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big020.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big020.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big020.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big020.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big021.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big021.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big021.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big021.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big022.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big022.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big022.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big022.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big023.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big023.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big023.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big023.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big024.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big024.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big024.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big024.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big025.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big025.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big025.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big025.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big026.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big026.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big026.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big026.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big027.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big027.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big027.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big027.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big028.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big028.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big028.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big028.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big029.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big029.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big029.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big029.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big030.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big030.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big030.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big030.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big031.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big031.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big031.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big031.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big032.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big032.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big032.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big032.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big033.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big033.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big033.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big033.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big034.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big034.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big034.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big034.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big035.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big035.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big035.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big035.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big036.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big036.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big036.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big036.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big037.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big037.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big037.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big037.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big038.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big038.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big038.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big038.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big039.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big039.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big039.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big039.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big040.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big040.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big040.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big040.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big041.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big041.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big041.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big041.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big042.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big042.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big042.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big042.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big043.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big043.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big043.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big043.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big044.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big044.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big044.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big044.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big045.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big045.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big045.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big045.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big046.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big046.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big046.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big046.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big047.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big047.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big047.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big047.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big048.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big048.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big048.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big048.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big049.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big049.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big049.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big049.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big050.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big050.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big050.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big050.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big051.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big051.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big051.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big051.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big052.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big052.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big052.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big052.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big053.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big053.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big053.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big053.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big054.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big054.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big054.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big054.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big055.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big055.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big055.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big055.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big056.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big056.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big056.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big056.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big057.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big057.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big057.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big057.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big058.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big058.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big058.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big058.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big059.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big059.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big059.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big059.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big060.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big060.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big060.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big060.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big061.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big061.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big061.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big061.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big062.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big062.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big062.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big062.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big063.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big063.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big063.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big063.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big064.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big064.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big064.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big064.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big065.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big065.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big065.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big065.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big066.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big066.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big066.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big066.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big067.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big067.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big067.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big067.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big068.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big068.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big068.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big068.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big069.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big069.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big069.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big069.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big070.java b/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big070.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big070.java
rename to build-system/integration-test/test-projects/multiDex/src/main/java/com/android/tests/basic/manymethods/Big070.java
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/multiDex/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/multiDex/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/multiDex/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/multiDex/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/multiDex/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/multiDex/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/multiDexWithLib/app/build.gradle b/build-system/integration-test/test-projects/multiDexWithLib/app/build.gradle
new file mode 100644
index 0000000..f9819c7
--- /dev/null
+++ b/build-system/integration-test/test-projects/multiDexWithLib/app/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ buildConfigField "String", "FOO", "\"foo\""
+
+ multiDexEnabled = true
+ useJack rootProject.useJack
+ }
+
+ productFlavors {
+ ics {
+ minSdkVersion 14
+ }
+ lollipop {
+ minSdkVersion 21
+ }
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField "String", "FOO", "\"bar\""
+ resValue "string", "foo", "foo2"
+ }
+ }
+}
+
+dependencies {
+ compile project(':lib')
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/multiDexWithLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/multiDexWithLib/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/ics/AndroidManifest.xml b/build-system/integration-test/test-projects/multiDexWithLib/app/src/ics/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/app/src/ics/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiDexWithLib/app/src/ics/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiDexWithLib/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/multiDexWithLib/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/multiDex/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDex/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/build.gradle b/build-system/integration-test/test-projects/multiDexWithLib/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/build.gradle
rename to build-system/integration-test/test-projects/multiDexWithLib/build.gradle
diff --git a/build-system/integration-test/test-projects/multiDexWithLib/lib/build.gradle b/build-system/integration-test/test-projects/multiDexWithLib/lib/build.gradle
new file mode 100644
index 0000000..30afb37
--- /dev/null
+++ b/build-system/integration-test/test-projects/multiDexWithLib/lib/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ useJack rootProject.useJack
+ minSdkVersion 4
+ }
+
+ buildTypes {
+ debug {
+ // needed for the test app.
+ multiDexEnabled = true
+ }
+ }
+
+ packagingOptions.exclude '.readme'
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: '*.jar')
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/androidTest/AndroidManifest.xml b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/androidTest/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/androidTest/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big001.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big001.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big001.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big001.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big002.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big002.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big002.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big002.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big003.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big003.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big003.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big003.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big004.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big004.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big004.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big004.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big005.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big005.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big005.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big005.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big006.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big006.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big006.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big006.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big007.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big007.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big007.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big007.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big008.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big008.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big008.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big008.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big009.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big009.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big009.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big009.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big010.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big010.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big010.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big010.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big011.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big011.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big011.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big011.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big012.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big012.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big012.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big012.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big013.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big013.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big013.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big013.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big014.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big014.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big014.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big014.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big015.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big015.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big015.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big015.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big016.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big016.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big016.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big016.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big017.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big017.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big017.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big017.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big018.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big018.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big018.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big018.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big019.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big019.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big019.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big019.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big020.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big020.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big020.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big020.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big021.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big021.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big021.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big021.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big022.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big022.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big022.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big022.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big023.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big023.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big023.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big023.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big024.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big024.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big024.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big024.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big025.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big025.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big025.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big025.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big026.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big026.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big026.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big026.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big027.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big027.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big027.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big027.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big028.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big028.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big028.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big028.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big029.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big029.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big029.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big029.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big030.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big030.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big030.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big030.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big031.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big031.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big031.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big031.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big032.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big032.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big032.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big032.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big033.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big033.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big033.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big033.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big034.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big034.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big034.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big034.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big035.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big035.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big035.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big035.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big036.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big036.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big036.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big036.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big037.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big037.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big037.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big037.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big038.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big038.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big038.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big038.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big039.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big039.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big039.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big039.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big040.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big040.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big040.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big040.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big041.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big041.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big041.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big041.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big042.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big042.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big042.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big042.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big043.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big043.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big043.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big043.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big044.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big044.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big044.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big044.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big045.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big045.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big045.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big045.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big046.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big046.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big046.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big046.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big047.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big047.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big047.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big047.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big048.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big048.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big048.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big048.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big049.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big049.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big049.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big049.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big050.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big050.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big050.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big050.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big051.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big051.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big051.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big051.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big052.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big052.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big052.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big052.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big053.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big053.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big053.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big053.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big054.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big054.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big054.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big054.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big055.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big055.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big055.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big055.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big056.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big056.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big056.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big056.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big057.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big057.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big057.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big057.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big058.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big058.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big058.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big058.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big059.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big059.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big059.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big059.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big060.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big060.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big060.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big060.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big061.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big061.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big061.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big061.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big062.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big062.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big062.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big062.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big063.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big063.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big063.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big063.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big064.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big064.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big064.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big064.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big065.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big065.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big065.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big065.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big066.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big066.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big066.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big066.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big067.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big067.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big067.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big067.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big068.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big068.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big068.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big068.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big069.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big069.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big069.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big069.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big070.java b/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big070.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big070.java
rename to build-system/integration-test/test-projects/multiDexWithLib/lib/src/main/java/com/android/tests/basic/manymethods/Big070.java
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/settings.gradle b/build-system/integration-test/test-projects/multiDexWithLib/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/settings.gradle
rename to build-system/integration-test/test-projects/multiDexWithLib/settings.gradle
diff --git a/build-system/integration-test/test-projects/multiproject/app/build.gradle b/build-system/integration-test/test-projects/multiproject/app/build.gradle
new file mode 100644
index 0000000..98e0ef7
--- /dev/null
+++ b/build-system/integration-test/test-projects/multiproject/app/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
+
+dependencies {
+ compile project(':library')
+}
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/androidTest/java/com/example/android/multiproject/MainActivityTest.java b/build-system/integration-test/test-projects/multiproject/app/src/androidTest/java/com/example/android/multiproject/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/androidTest/java/com/example/android/multiproject/MainActivityTest.java
rename to build-system/integration-test/test-projects/multiproject/app/src/androidTest/java/com/example/android/multiproject/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/multiproject/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiproject/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/integration-test/test-projects/multiproject/app/src/main/java/com/example/android/multiproject/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/java/com/example/android/multiproject/MainActivity.java
rename to build-system/integration-test/test-projects/multiproject/app/src/main/java/com/example/android/multiproject/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/multiproject/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/multiproject/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/multiproject/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/multiproject/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/multiproject/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/multiproject/app/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/multiproject/baseLibrary/build.gradle b/build-system/integration-test/test-projects/multiproject/baseLibrary/build.gradle
new file mode 100644
index 0000000..ac277cb
--- /dev/null
+++ b/build-system/integration-test/test-projects/multiproject/baseLibrary/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
+
+dependencies {
+ compile project(':util')
+}
diff --git a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/AndroidManifest.xml b/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivity.java b/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivity.java
rename to build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivity.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivityTest.java b/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivityTest.java
rename to build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/java/com/example/android/multiproject/library/base/test/TestActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/res/layout/testlayout.xml b/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/res/layout/testlayout.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/res/layout/testlayout.xml
rename to build-system/integration-test/test-projects/multiproject/baseLibrary/src/androidTest/res/layout/testlayout.xml
diff --git a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
rename to build-system/integration-test/test-projects/multiproject/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/build.gradle b/build-system/integration-test/test-projects/multiproject/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/build.gradle
rename to build-system/integration-test/test-projects/multiproject/build.gradle
diff --git a/build-system/integration-test/test-projects/multiproject/library/build.gradle b/build-system/integration-test/test-projects/multiproject/library/build.gradle
new file mode 100644
index 0000000..54213ad
--- /dev/null
+++ b/build-system/integration-test/test-projects/multiproject/library/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
+
+dependencies {
+ compile project(':baseLibrary')
+ compile project(':util')
+}
diff --git a/base/build-system/integration-test/test-projects/multiproject/library/src/androidTest/java/com/example/android/multiproject/library/ShowPeopleActivityTest.java b/build-system/integration-test/test-projects/multiproject/library/src/androidTest/java/com/example/android/multiproject/library/ShowPeopleActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/library/src/androidTest/java/com/example/android/multiproject/library/ShowPeopleActivityTest.java
rename to build-system/integration-test/test-projects/multiproject/library/src/androidTest/java/com/example/android/multiproject/library/ShowPeopleActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/library/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/multiproject/library/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/library/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multiproject/library/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multiproject/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/integration-test/test-projects/multiproject/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
rename to build-system/integration-test/test-projects/multiproject/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/library/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/multiproject/library/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/library/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/multiproject/library/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/multiproject/settings.gradle b/build-system/integration-test/test-projects/multiproject/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/settings.gradle
rename to build-system/integration-test/test-projects/multiproject/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/multiproject/util/build.gradle b/build-system/integration-test/test-projects/multiproject/util/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/util/build.gradle
rename to build-system/integration-test/test-projects/multiproject/util/build.gradle
diff --git a/base/build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/People.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/People.java
rename to build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/People.java
diff --git a/base/build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/Person.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/Person.java
rename to build-system/integration-test/test-projects/multiproject/util/src/main/java/com/example/android/multiproject/person/Person.java
diff --git a/build-system/integration-test/test-projects/multires/build.gradle b/build-system/integration-test/test-projects/multires/build.gradle
new file mode 100644
index 0000000..d3b4862
--- /dev/null
+++ b/build-system/integration-test/test-projects/multires/build.gradle
@@ -0,0 +1,17 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ sourceSets {
+ main {
+ res {
+ srcDirs 'src/main/res1', 'src/main/res2'
+ }
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/multires/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
copy to build-system/integration-test/test-projects/multires/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/minify/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/multires/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minify/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/multires/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multires/src/main/assets/notice.txt b/build-system/integration-test/test-projects/multires/src/main/assets/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/main/assets/notice.txt
rename to build-system/integration-test/test-projects/multires/src/main/assets/notice.txt
diff --git a/base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/multires/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/src/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/multires/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/multires/src/main/res1/raw/notice.txt b/build-system/integration-test/test-projects/multires/src/main/res1/raw/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/main/res1/raw/notice.txt
rename to build-system/integration-test/test-projects/multires/src/main/res1/raw/notice.txt
diff --git a/base/build-system/integration-test/test-projects/multires/src/main/res1/values/strings.xml b/build-system/integration-test/test-projects/multires/src/main/res1/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/main/res1/values/strings.xml
rename to build-system/integration-test/test-projects/multires/src/main/res1/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/multires/src/main/res2/drawable/icon.png b/build-system/integration-test/test-projects/multires/src/main/res2/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/main/res2/drawable/icon.png
rename to build-system/integration-test/test-projects/multires/src/main/res2/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/multires/src/main/res2/layout/main.xml b/build-system/integration-test/test-projects/multires/src/main/res2/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/main/res2/layout/main.xml
rename to build-system/integration-test/test-projects/multires/src/main/res2/layout/main.xml
diff --git a/build-system/integration-test/test-projects/ndkJniLib/app/build.gradle b/build-system/integration-test/test-projects/ndkJniLib/app/build.gradle
new file mode 100644
index 0000000..a2471a7
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkJniLib/app/build.gradle
@@ -0,0 +1,55 @@
+apply plugin: 'com.android.application'
+
+import com.android.build.OutputFile;
+
+dependencies {
+ compile project(':lib')
+}
+
+// map for the version code for ABIs.
+// x86 is more important because x86 devices also support arm.
+// Same for mips
+ext.versionCodes = ["armeabi-v7a":1, "mips":2, "x86":3, "all":0]
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ // This actual the app version code. Giving ourselves 100,000 values [0, 99999]
+ defaultConfig.versionCode = 123
+
+ productFlavors {
+ gingerbread {
+ minSdkVersion 10
+ versionCode = 1
+ }
+ icecreamSandwich {
+ minSdkVersion 14
+ versionCode = 2
+ }
+ }
+
+ splits {
+ abi {
+ enable = true
+ universalApk = true
+ exclude "x86_64", "mips64", "arm64-v8a", "armeabi"
+ }
+ }
+
+ // make per-variant version code
+ applicationVariants.all { variant ->
+ // get the version code for the flavor
+ def apiVersion = variant.productFlavors.get(0).versionCode
+
+ // assign a composite version code for each output, based on the flavor above
+ // and the density component.
+ variant.outputs.each { output ->
+ // get the key for the abi component
+ def key = output.getFilter(OutputFile.ABI) == null ? "all" : output.getFilter(OutputFile.ABI)
+
+ // set the versionCode on the output.
+ output.versionCodeOverride = apiVersion * 1000000 + project.ext.versionCodes.get(key) * 100000 + defaultConfig.versionCode
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/integration-test/test-projects/ndkJniLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/integration-test/test-projects/ndkJniLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkJniLib/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkJniLib/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkJniLib/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkJniLib/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/build.gradle b/build-system/integration-test/test-projects/ndkJniLib/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/build.gradle
rename to build-system/integration-test/test-projects/ndkJniLib/build.gradle
diff --git a/build-system/integration-test/test-projects/ndkJniLib/lib/build.gradle b/build-system/integration-test/test-projects/ndkJniLib/lib/build.gradle
new file mode 100644
index 0000000..85e1025
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkJniLib/lib/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.library'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/integration-test/test-projects/ndkJniLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/integration-test/test-projects/ndkJniLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkJniLib/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java b/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
rename to build-system/integration-test/test-projects/ndkJniLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/jni/hello-jni.c b/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/jni/hello-jni.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/jni/hello-jni.c
rename to build-system/integration-test/test-projects/ndkJniLib/lib/src/main/jni/hello-jni.c
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkJniLib/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib/settings.gradle b/build-system/integration-test/test-projects/ndkJniLib/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib/settings.gradle
rename to build-system/integration-test/test-projects/ndkJniLib/settings.gradle
diff --git a/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/build.gradle b/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/build.gradle
new file mode 100644
index 0000000..ce1ba42
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion rootProject.buildToolsVersion
+ generatePureSplits true
+
+ // This actual the app version code. Giving ourselves 100,000 values [0, 99999]
+ defaultConfig.versionCode = 123
+
+ productFlavors {
+ free {
+ minSdkVersion 21
+ }
+ paid {
+ minSdkVersion 21
+ }
+ }
+
+ splits {
+ abi {
+ enable = true
+ exclude "x86_64", "mips64", "arm64-v8a", "armeabi"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/build.gradle b/build-system/integration-test/test-projects/ndkJniPureSplitLib/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/build.gradle
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/build.gradle
diff --git a/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/build.gradle b/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/build.gradle
new file mode 100644
index 0000000..85e1025
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.library'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/java/com/example/hellojni/lib/HelloJni.java b/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/jni/hello-jni.c b/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/jni/hello-jni.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/jni/hello-jni.c
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/jni/hello-jni.c
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniLib2/settings.gradle b/build-system/integration-test/test-projects/ndkJniPureSplitLib/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniLib2/settings.gradle
rename to build-system/integration-test/test-projects/ndkJniPureSplitLib/settings.gradle
diff --git a/build-system/integration-test/test-projects/ndkLibPrebuilts/build.gradle b/build-system/integration-test/test-projects/ndkLibPrebuilts/build.gradle
new file mode 100644
index 0000000..37ea925
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkLibPrebuilts/build.gradle
@@ -0,0 +1,10 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/integration-test/test-projects/ndkLibPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/integration-test/test-projects/ndkLibPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java b/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
rename to build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/ndkPrebuilts/build.gradle b/build-system/integration-test/test-projects/ndkPrebuilts/build.gradle
new file mode 100644
index 0000000..85eff26
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkPrebuilts/build.gradle
@@ -0,0 +1,30 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilters "armeabi-v7a", "armeabi"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+
+}
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/integration-test/test-projects/ndkPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
rename to build-system/integration-test/test-projects/ndkPrebuilts/src/androidTest/java/com/example/hellojni/lib/HelloJniTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkPrebuilts/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkPrebuilts/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkPrebuilts/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java b/build-system/integration-test/test-projects/ndkPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkLibPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
rename to build-system/integration-test/test-projects/ndkPrebuilts/src/main/java/com/example/hellojni/lib/HelloJni.java
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkPrebuilts/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkPrebuilts/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/Android.mk.old b/build-system/integration-test/test-projects/ndkRsHelloCompute/Android.mk.old
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/Android.mk.old
rename to build-system/integration-test/test-projects/ndkRsHelloCompute/Android.mk.old
diff --git a/build-system/integration-test/test-projects/ndkRsHelloCompute/build.gradle b/build-system/integration-test/test-projects/ndkRsHelloCompute/build.gradle
new file mode 100644
index 0000000..ae180a3
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkRsHelloCompute/build.gradle
@@ -0,0 +1,40 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ renderscriptNdkModeEnabled true
+ ndk {
+ moduleName "libhellocomputendk"
+ stl "stlport_shared"
+ }
+
+ }
+
+ buildTypes.debug.jniDebuggable true
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilter "armeabi-v7a"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/libhellocomputendk/Android.mk.old b/build-system/integration-test/test-projects/ndkRsHelloCompute/libhellocomputendk/Android.mk.old
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/libhellocomputendk/Android.mk.old
rename to build-system/integration-test/test-projects/ndkRsHelloCompute/libhellocomputendk/Android.mk.old
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/java/com/example/android/rs/hellocomputendk/HelloComputeNDK.java b/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/java/com/example/android/rs/hellocomputendk/HelloComputeNDK.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/java/com/example/android/rs/hellocomputendk/HelloComputeNDK.java
rename to build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/java/com/example/android/rs/hellocomputendk/HelloComputeNDK.java
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/jni/helloComputeNDK.cpp b/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/jni/helloComputeNDK.cpp
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/jni/helloComputeNDK.cpp
rename to build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/jni/helloComputeNDK.cpp
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/drawable/data.jpg b/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/drawable/data.jpg
similarity index 100%
copy from base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/drawable/data.jpg
copy to build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/drawable/data.jpg
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/rs/mono.rs b/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/rs/mono.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/rs/mono.rs
rename to build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/rs/mono.rs
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/README.txt b/build-system/integration-test/test-projects/ndkSanAngeles/README.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/README.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles/README.txt
diff --git a/build-system/integration-test/test-projects/ndkSanAngeles/build.gradle b/build-system/integration-test/test-projects/ndkSanAngeles/build.gradle
new file mode 100644
index 0000000..24a4f3d
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkSanAngeles/build.gradle
@@ -0,0 +1,47 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ ndk {
+ moduleName "sanangeles"
+ cFlags "-DANDROID_NDK -DDISABLE_IMPORTGL"
+ ldLibs "GLESv1_CM", "dl", "log"
+ stl "stlport_static"
+ jobs 4
+ }
+
+ // This actual the app version code. Giving ourselves 1,000,000 values
+ versionCode = 123
+
+ }
+
+ buildTypes.debug.jniDebuggable true
+
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'armeabi-v7a', 'mips'
+ }
+ }
+}
+
+// map for the version code. x86/mips must be higher than arm due to binary
+// code conversion libraries
+ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]
+
+android.applicationVariants.all { variant ->
+ // assign different version code for each output
+ variant.outputs.each { output ->
+ output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/license-BSD.txt b/build-system/integration-test/test-projects/ndkSanAngeles/license-BSD.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/license-BSD.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles/license-BSD.txt
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/license-LGPL.txt b/build-system/integration-test/test-projects/ndkSanAngeles/license-LGPL.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/license-LGPL.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles/license-LGPL.txt
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/license.txt b/build-system/integration-test/test-projects/ndkSanAngeles/license.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/license.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles/license.txt
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/misc/app-linux.c b/build-system/integration-test/test-projects/ndkSanAngeles/misc/app-linux.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/misc/app-linux.c
rename to build-system/integration-test/test-projects/ndkSanAngeles/misc/app-linux.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/misc/app-win32.c b/build-system/integration-test/test-projects/ndkSanAngeles/misc/app-win32.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/misc/app-win32.c
rename to build-system/integration-test/test-projects/ndkSanAngeles/misc/app-win32.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/androidTest/java/com/example/SanAngeles/DemoActivityTest.java b/build-system/integration-test/test-projects/ndkSanAngeles/src/androidTest/java/com/example/SanAngeles/DemoActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/androidTest/java/com/example/SanAngeles/DemoActivityTest.java
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/androidTest/java/com/example/SanAngeles/DemoActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/java/com/example/SanAngeles/DemoActivity.java b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/java/com/example/SanAngeles/DemoActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/java/com/example/SanAngeles/DemoActivity.java
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/java/com/example/SanAngeles/DemoActivity.java
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app-android.c b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app-android.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app-android.c
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app-android.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app.h b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app.h
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/app.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/cams.h b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/cams.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/cams.h
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/cams.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/demo.c b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/demo.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/demo.c
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/demo.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.c b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.c
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.h b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.h
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/importgl.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/shapes.h b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/shapes.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/shapes.h
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/jni/shapes.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkSanAngeles/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/README.txt b/build-system/integration-test/test-projects/ndkSanAngeles2/README.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/README.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles2/README.txt
diff --git a/build-system/integration-test/test-projects/ndkSanAngeles2/build.gradle b/build-system/integration-test/test-projects/ndkSanAngeles2/build.gradle
new file mode 100644
index 0000000..16d8d2d
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkSanAngeles2/build.gradle
@@ -0,0 +1,50 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion rootProject.buildToolsVersion
+
+ defaultConfig {
+ // This actual the app version code. Giving ourselves 1,000,000 values
+ versionCode 123
+ }
+
+ ndk {
+ moduleName "sanangeles"
+ CFlags.add("-DDISABLE_IMPORTGL")
+ ldLibs.add("GLESv1_CM")
+ ldLibs.add("dl")
+ ldLibs.add("log")
+ stl "stlport_static"
+ toolchain "clang"
+ }
+
+ productFlavors {
+ create("x86") {
+ ndk.abiFilters.add("x86")
+ // this is the flavor part of the version code.
+ // It must be higher than the arm one for devices supporting
+ // both, as x86 is preferred.
+ versionCode 3
+ }
+ create("arm") {
+ ndk.abiFilters.add("armeabi-v7a")
+ versionCode 2
+ }
+ create("mips") {
+ ndk.abiFilters.add("mips")
+ versionCode 1
+ }
+ create("fat") {
+ // fat binary, lowest version code to be
+ // the last option
+ versionCode 0
+ }
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/license-BSD.txt b/build-system/integration-test/test-projects/ndkSanAngeles2/license-BSD.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/license-BSD.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles2/license-BSD.txt
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/license-LGPL.txt b/build-system/integration-test/test-projects/ndkSanAngeles2/license-LGPL.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/license-LGPL.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles2/license-LGPL.txt
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/license.txt b/build-system/integration-test/test-projects/ndkSanAngeles2/license.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/license.txt
rename to build-system/integration-test/test-projects/ndkSanAngeles2/license.txt
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-linux.c b/build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-linux.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-linux.c
rename to build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-linux.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-win32.c b/build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-win32.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-win32.c
rename to build-system/integration-test/test-projects/ndkSanAngeles2/misc/app-win32.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/java/com/example/SanAngeles/DemoActivity.java b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/java/com/example/SanAngeles/DemoActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/java/com/example/SanAngeles/DemoActivity.java
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/java/com/example/SanAngeles/DemoActivity.java
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app-android.c b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app-android.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app-android.c
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app-android.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app.h b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app.h
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/app.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/cams.h b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/cams.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/cams.h
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/cams.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/demo.c b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/demo.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/demo.c
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/demo.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.c b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.c
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.c
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.c
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.h b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.h
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/importgl.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/shapes.h b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/shapes.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/shapes.h
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/jni/shapes.h
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkSanAngeles2/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/ndkVariants/build.gradle b/build-system/integration-test/test-projects/ndkVariants/build.gradle
new file mode 100644
index 0000000..a364023
--- /dev/null
+++ b/build-system/integration-test/test-projects/ndkVariants/build.gradle
@@ -0,0 +1,34 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScriptExperimental.gradle", to: buildscript }
+
+// This test ensures each variant compiles the correct source set with the appropriate NDK settings.
+
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion rootProject.buildToolsVersion
+
+ ndk {
+ moduleName "simple-jni"
+
+ // TODO: Include a way to set include directories the DSL.
+ cppFlags.add("-I$rootDir/src/include".toString())
+ stl "stlport_static"
+ }
+
+ productFlavors {
+ create("free")
+ create("premium")
+ }
+ }
+
+ // Set binary specific C++ flags.
+ components.android {
+ binaries.afterEach { binary ->
+ binary.mergedNdkConfig.cppFlags.add("-DVARIANT=\"" + binary.name + "\"")
+ }
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/androidTest/java/com/example/simplejni/SimpleJniTest.java b/build-system/integration-test/test-projects/ndkVariants/src/androidTest/java/com/example/simplejni/SimpleJniTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/androidTest/java/com/example/simplejni/SimpleJniTest.java
rename to build-system/integration-test/test-projects/ndkVariants/src/androidTest/java/com/example/simplejni/SimpleJniTest.java
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/debug/jni/buildType.cpp b/build-system/integration-test/test-projects/ndkVariants/src/debug/jni/buildType.cpp
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/debug/jni/buildType.cpp
rename to build-system/integration-test/test-projects/ndkVariants/src/debug/jni/buildType.cpp
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/free/java/com/example/simplejni/TestConstants.java b/build-system/integration-test/test-projects/ndkVariants/src/free/java/com/example/simplejni/TestConstants.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/free/java/com/example/simplejni/TestConstants.java
rename to build-system/integration-test/test-projects/ndkVariants/src/free/java/com/example/simplejni/TestConstants.java
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/free/jni/productFlavor.cpp b/build-system/integration-test/test-projects/ndkVariants/src/free/jni/productFlavor.cpp
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/free/jni/productFlavor.cpp
rename to build-system/integration-test/test-projects/ndkVariants/src/free/jni/productFlavor.cpp
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/include/buildType.h b/build-system/integration-test/test-projects/ndkVariants/src/include/buildType.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/include/buildType.h
rename to build-system/integration-test/test-projects/ndkVariants/src/include/buildType.h
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/include/productFlavor.h b/build-system/integration-test/test-projects/ndkVariants/src/include/productFlavor.h
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/include/productFlavor.h
rename to build-system/integration-test/test-projects/ndkVariants/src/include/productFlavor.h
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/ndkVariants/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/ndkVariants/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/main/java/com/example/simplejni/SimpleJni.java b/build-system/integration-test/test-projects/ndkVariants/src/main/java/com/example/simplejni/SimpleJni.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/main/java/com/example/simplejni/SimpleJni.java
rename to build-system/integration-test/test-projects/ndkVariants/src/main/java/com/example/simplejni/SimpleJni.java
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/main/jni/simple-jni.cpp b/build-system/integration-test/test-projects/ndkVariants/src/main/jni/simple-jni.cpp
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/main/jni/simple-jni.cpp
rename to build-system/integration-test/test-projects/ndkVariants/src/main/jni/simple-jni.cpp
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/ndkVariants/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/ndkVariants/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/premium/java/com/example/simplejni/TestConstants.java b/build-system/integration-test/test-projects/ndkVariants/src/premium/java/com/example/simplejni/TestConstants.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/premium/java/com/example/simplejni/TestConstants.java
rename to build-system/integration-test/test-projects/ndkVariants/src/premium/java/com/example/simplejni/TestConstants.java
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/premium/jni/productFlavor.cpp b/build-system/integration-test/test-projects/ndkVariants/src/premium/jni/productFlavor.cpp
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/premium/jni/productFlavor.cpp
rename to build-system/integration-test/test-projects/ndkVariants/src/premium/jni/productFlavor.cpp
diff --git a/base/build-system/integration-test/test-projects/ndkVariants/src/release/jni/buildType.cpp b/build-system/integration-test/test-projects/ndkVariants/src/release/jni/buildType.cpp
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkVariants/src/release/jni/buildType.cpp
rename to build-system/integration-test/test-projects/ndkVariants/src/release/jni/buildType.cpp
diff --git a/build-system/integration-test/test-projects/noPngCrunch/build.gradle b/build-system/integration-test/test-projects/noPngCrunch/build.gradle
new file mode 100644
index 0000000..8481b21
--- /dev/null
+++ b/build-system/integration-test/test-projects/noPngCrunch/build.gradle
@@ -0,0 +1,22 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion rootProject.buildToolsVersion
+
+ aaptOptions {
+ cruncherEnabled = false
+ }
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/noPngCrunch/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/noPngCrunch/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPngCrunch/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/noPngCrunch/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/multires/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/noPngCrunch/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/noPngCrunch/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/lib_bg.9.png b/build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/lib_bg.9.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/lib_bg.9.png
rename to build-system/integration-test/test-projects/noPngCrunch/src/main/res/drawable/lib_bg.9.png
diff --git a/base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/noPngCrunch/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/noPngCrunch/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/noPngCrunch/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multiDexWithLib/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/noPngCrunch/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/noPreDex/build.gradle b/build-system/integration-test/test-projects/noPreDex/build.gradle
new file mode 100644
index 0000000..92ce133
--- /dev/null
+++ b/build-system/integration-test/test-projects/noPreDex/build.gradle
@@ -0,0 +1,15 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ dexOptions.preDexLibraries = false
+}
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/noPreDex/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
copy to build-system/integration-test/test-projects/noPreDex/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/minifyLib/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/noPreDex/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLib/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/noPreDex/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/noPngCrunch/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/noPreDex/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPngCrunch/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/noPreDex/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/noPreDex/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/noPreDex/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPreDex/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/noPreDex/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/noPreDex/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/noPreDex/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPreDex/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/noPreDex/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/noPreDex/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPngCrunch/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/noPreDex/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/noPreDex/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/maxSdkVersion/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/noPreDex/src/release/res/values/strings.xml
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/root/project_ignore b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/.gitignore
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidAutoProject/root/project_ignore
rename to build-system/integration-test/test-projects/optionalLibInLibWithProguard/.gitignore
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/module_ignore b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/.gitignore
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/module_ignore
rename to build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/.gitignore
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/build.gradle b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/build.gradle
new file mode 100644
index 0000000..a478537
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 23
+ targetSdkVersion 23
+ }
+
+ buildTypes {
+ debug {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+}
+
+dependencies {
+ compile project(':mylibrary')
+}
+
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/androidTest/java/com/works/customization/my/myapplication/ApplicationTest.java b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/androidTest/java/com/works/customization/my/myapplication/ApplicationTest.java
new file mode 100644
index 0000000..20cd1c8
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/androidTest/java/com/works/customization/my/myapplication/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.works.customization.my.myapplication;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..00e195b
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.optionallib.app" >
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/java/com/example/android/optionallib/app/MainActivity.java b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/java/com/example/android/optionallib/app/MainActivity.java
new file mode 100644
index 0000000..1d27061
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/java/com/example/android/optionallib/app/MainActivity.java
@@ -0,0 +1,20 @@
+package com.example.android.optionallib.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.example.android.optionallib.library.HttpUser;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ TextView tv = (TextView) findViewById(R.id.text);
+ tv.setText(HttpUser.downloadStuff());
+ }
+}
+
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..577a471
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,5 @@
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:text="http response here" android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/text"/>
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
copy from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xhdpi/ic_launcher.png
copy to build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..efd3073
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">My Application</string>
+</resources>
diff --git a/base/build-system/integration-test/test-projects/ndkJniPureSplitLib/build.gradle b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkJniPureSplitLib/build.gradle
rename to build-system/integration-test/test-projects/optionalLibInLibWithProguard/build.gradle
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/module_ignore b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/.gitignore
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/module_ignore
rename to build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/.gitignore
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/build.gradle b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/build.gradle
new file mode 100644
index 0000000..c479e0a
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ useLibrary 'org.apache.http.legacy'
+
+ defaultConfig {
+ minSdkVersion 23
+ targetSdkVersion 23
+ }
+}
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..247c0c4
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.optionallib.library" >
+
+ <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/src/main/java/com/example/android/optionallib/library/HttpUser.java b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/src/main/java/com/example/android/optionallib/library/HttpUser.java
new file mode 100644
index 0000000..b7ff468
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/mylibrary/src/main/java/com/example/android/optionallib/library/HttpUser.java
@@ -0,0 +1,36 @@
+package com.example.android.optionallib.library;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class HttpUser {
+
+ @SuppressWarnings("deprecation")
+ public static String downloadStuff() {
+ try {
+ DefaultHttpClient client = new DefaultHttpClient();
+ HttpGet get = new HttpGet("http://www.google.com");
+
+ HttpResponse response = client.execute(get);
+ HttpEntity entity = response.getEntity();
+
+ BufferedReader rd = new BufferedReader(new InputStreamReader(entity.getContent()));
+ StringBuffer buffer = new StringBuffer();
+ String line;
+ while ((line = rd.readLine()) != null) {
+ buffer.append(line);
+ }
+
+ return buffer.toString();
+
+ } catch (IOException e) {
+ return e.getMessage();
+ }
+ }
+}
diff --git a/build-system/integration-test/test-projects/optionalLibInLibWithProguard/settings.gradle b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/settings.gradle
new file mode 100644
index 0000000..9c982a3
--- /dev/null
+++ b/build-system/integration-test/test-projects/optionalLibInLibWithProguard/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':mylibrary'
diff --git a/build-system/integration-test/test-projects/overlay1/build.gradle b/build-system/integration-test/test-projects/overlay1/build.gradle
new file mode 100644
index 0000000..ca709be
--- /dev/null
+++ b/build-system/integration-test/test-projects/overlay1/build.gradle
@@ -0,0 +1,10 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/androidTest/java/com/android/tests/overlay1/MainTest.java b/build-system/integration-test/test-projects/overlay1/src/androidTest/java/com/android/tests/overlay1/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/androidTest/java/com/android/tests/overlay1/MainTest.java
rename to build-system/integration-test/test-projects/overlay1/src/androidTest/java/com/android/tests/overlay1/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/debug/res/drawable/type_overlay.png b/build-system/integration-test/test-projects/overlay1/src/debug/res/drawable/type_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/debug/res/drawable/type_overlay.png
rename to build-system/integration-test/test-projects/overlay1/src/debug/res/drawable/type_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/overlay1/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/overlay1/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/main/java/com/android/tests/overlay1/Main.java b/build-system/integration-test/test-projects/overlay1/src/main/java/com/android/tests/overlay1/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/main/java/com/android/tests/overlay1/Main.java
rename to build-system/integration-test/test-projects/overlay1/src/main/java/com/android/tests/overlay1/Main.java
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/overlay1/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/no_overlay.png b/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/no_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/no_overlay.png
rename to build-system/integration-test/test-projects/overlay1/src/main/res/drawable/no_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/type_overlay.png b/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/type_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/main/res/drawable/type_overlay.png
rename to build-system/integration-test/test-projects/overlay1/src/main/res/drawable/type_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/overlay1/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/overlay1/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/overlay1/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/overlay1/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay1/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/overlay1/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/overlay2/build.gradle b/build-system/integration-test/test-projects/overlay2/build.gradle
new file mode 100644
index 0000000..224f921
--- /dev/null
+++ b/build-system/integration-test/test-projects/overlay2/build.gradle
@@ -0,0 +1,14 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ productFlavors {
+ one {}
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/androidTest/java/com/android/tests/overlay2/MainTest.java b/build-system/integration-test/test-projects/overlay2/src/androidTest/java/com/android/tests/overlay2/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/androidTest/java/com/android/tests/overlay2/MainTest.java
rename to build-system/integration-test/test-projects/overlay2/src/androidTest/java/com/android/tests/overlay2/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable-hdpi-v4/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/overlay2/src/debug/res/drawable-hdpi-v4/icon.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_overlay.png b/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/type_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/variant_type_flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/variant_type_flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/variant_type_flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/debug/res/drawable/variant_type_flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/overlay2/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/overlay2/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/java/com/android/tests/overlay2/Main.java b/build-system/integration-test/test-projects/overlay2/src/main/java/com/android/tests/overlay2/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/java/com/android/tests/overlay2/Main.java
rename to build-system/integration-test/test-projects/overlay2/src/main/java/com/android/tests/overlay2/Main.java
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/overlay2/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/overlay2/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/main/res/drawable/flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/overlay2/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/no_overlay.png b/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/no_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/no_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/main/res/drawable/no_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_overlay.png b/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/main/res/drawable/type_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/variant_type_flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/variant_type_flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/drawable/variant_type_flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/main/res/drawable/variant_type_flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/overlay2/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/overlay2/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/overlay2/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/overlay2/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/one/res/drawable/flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/type_flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/type_flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/type_flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/one/res/drawable/type_flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/variant_type_flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/variant_type_flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/one/res/drawable/variant_type_flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/one/res/drawable/variant_type_flavor_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay2/src/oneDebug/res/drawable/variant_type_flavor_overlay.png b/build-system/integration-test/test-projects/overlay2/src/oneDebug/res/drawable/variant_type_flavor_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay2/src/oneDebug/res/drawable/variant_type_flavor_overlay.png
rename to build-system/integration-test/test-projects/overlay2/src/oneDebug/res/drawable/variant_type_flavor_overlay.png
diff --git a/build-system/integration-test/test-projects/overlay3/build.gradle b/build-system/integration-test/test-projects/overlay3/build.gradle
new file mode 100644
index 0000000..ab8903b
--- /dev/null
+++ b/build-system/integration-test/test-projects/overlay3/build.gradle
@@ -0,0 +1,40 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ flavorDimensions "pricing", "releaseType"
+
+ sourceSets {
+ beta.setRoot('movedSrc/beta')
+ free.setRoot('movedSrc/free')
+ debug.setRoot('movedSrc/debug')
+ freeBeta.setRoot('movedSrc/freeBeta')
+ freeBetaDebug.setRoot('movedSrc/freeBetaDebug')
+ freeNormal.setRoot('movedSrc/freeNormal')
+ }
+
+ productFlavors {
+
+ beta {
+ dimension "releaseType"
+ }
+
+ normal {
+ dimension "releaseType"
+ }
+
+ free {
+ dimension "pricing"
+ }
+
+ paid {
+ dimension "pricing"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/beta_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/beta_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/beta_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/beta_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_beta_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/beta/res/drawable/free_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/free_beta_debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/free_beta_debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/free_beta_debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/debug/res/drawable/free_beta_debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_beta_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/free/res/drawable/free_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/freeBeta/res/drawable/free_beta_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBetaDebug/res/drawable/free_beta_debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/freeBetaDebug/res/drawable/free_beta_debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/freeBetaDebug/res/drawable/free_beta_debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/freeBetaDebug/res/drawable/free_beta_debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/debug_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/free_normal_overlay.png b/build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/free_normal_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/free_normal_overlay.png
rename to build-system/integration-test/test-projects/overlay3/movedSrc/freeNormal/res/drawable/free_normal_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/androidTest/java/com/android/tests/overlay2/MainTest.java b/build-system/integration-test/test-projects/overlay3/src/androidTest/java/com/android/tests/overlay2/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/androidTest/java/com/android/tests/overlay2/MainTest.java
rename to build-system/integration-test/test-projects/overlay3/src/androidTest/java/com/android/tests/overlay2/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/overlay3/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/overlay3/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/java/com/android/tests/overlay2/Main.java b/build-system/integration-test/test-projects/overlay3/src/main/java/com/android/tests/overlay2/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/java/com/android/tests/overlay2/Main.java
rename to build-system/integration-test/test-projects/overlay3/src/main/java/com/android/tests/overlay2/Main.java
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/beta_overlay.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/beta_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/beta_overlay.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/beta_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/debug_overlay.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_debug_overlay.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_debug_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_debug_overlay.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_debug_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_overlay.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_overlay.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_beta_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_normal_overlay.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_normal_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_normal_overlay.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_normal_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_overlay.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_overlay.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/free_overlay.png
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/no_overlay.png b/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/no_overlay.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/drawable/no_overlay.png
rename to build-system/integration-test/test-projects/overlay3/src/main/res/drawable/no_overlay.png
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/overlay3/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/overlay3/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/overlay3/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/overlay3/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/overlay3/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/overlay3/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/packagingOptions/build.gradle b/build-system/integration-test/test-projects/packagingOptions/build.gradle
new file mode 100644
index 0000000..b4dde99
--- /dev/null
+++ b/build-system/integration-test/test-projects/packagingOptions/build.gradle
@@ -0,0 +1,22 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ packagingOptions {
+ exclude 'excluded.txt'
+ pickFirst 'first_pick.txt'
+ merge 'merge.txt'
+
+ pickFirst 'lib/x86/libdummy.so'
+ }
+}
+
+dependencies {
+ compile files('jar1.jar')
+ compile files('jar2.jar')
+}
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/build.gradle b/build-system/integration-test/test-projects/packagingOptions/jars/jar1/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/build.gradle
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar1/build.gradle
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/excluded.txt b/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/excluded.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/excluded.txt
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/excluded.txt
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/first_pick.txt b/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/first_pick.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/first_pick.txt
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/first_pick.txt
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/merge.txt b/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/merge.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/merge.txt
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar1/src/main/resources/merge.txt
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/build.gradle b/build-system/integration-test/test-projects/packagingOptions/jars/jar2/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/build.gradle
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar2/build.gradle
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/excluded.txt b/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/excluded.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/excluded.txt
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/excluded.txt
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/first_pick.txt b/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/first_pick.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/first_pick.txt
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/first_pick.txt
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/merge.txt b/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/merge.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/merge.txt
rename to build-system/integration-test/test-projects/packagingOptions/jars/jar2/src/main/resources/merge.txt
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/packagingOptions/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/packagingOptions/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/packagingOptions/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/minifyLibWithJavaRes/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/packagingOptions/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/packagingOptions/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/packagingOptions/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/pkgOverride/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/packagingOptions/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/pkgOverride/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/packagingOptions/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/packagingOptions/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/packagingOptions/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/noPreDex/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/packagingOptions/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPreDex/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/packagingOptions/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/parentLibsTest/app/application/build.gradle b/build-system/integration-test/test-projects/parentLibsTest/app/application/build.gradle
new file mode 100644
index 0000000..d75b684
--- /dev/null
+++ b/build-system/integration-test/test-projects/parentLibsTest/app/application/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':..:lib1')
+}
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/proguard-project.txt b/build-system/integration-test/test-projects/parentLibsTest/app/application/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/proguard-project.txt
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/App.java b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/App.java
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/App.java
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/MainActivity.java
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/java/com/android/tests/libstest/app/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/resources/com/android/tests/libstest/app/App.txt b/build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/resources/com/android/tests/libstest/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/resources/com/android/tests/libstest/app/App.txt
rename to build-system/integration-test/test-projects/parentLibsTest/app/application/src/main/resources/com/android/tests/libstest/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/build.gradle b/build-system/integration-test/test-projects/parentLibsTest/app/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/build.gradle
rename to build-system/integration-test/test-projects/parentLibsTest/app/build.gradle
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/app/settings.gradle b/build-system/integration-test/test-projects/parentLibsTest/app/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/app/settings.gradle
rename to build-system/integration-test/test-projects/parentLibsTest/app/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/build.gradle b/build-system/integration-test/test-projects/parentLibsTest/lib1/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/build.gradle
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/build.gradle
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/proguard-project.txt b/build-system/integration-test/test-projects/parentLibsTest/lib1/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/proguard-project.txt
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-hdpi/lib1_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-ldpi/lib1_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/drawable-mdpi/lib1_ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/layout/lib1_main.xml b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/layout/lib1_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/layout/lib1_main.xml
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/layout/lib1_main.xml
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt b/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
rename to build-system/integration-test/test-projects/parentLibsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
diff --git a/build-system/integration-test/test-projects/pkgOverride/build.gradle b/build-system/integration-test/test-projects/pkgOverride/build.gradle
new file mode 100644
index 0000000..d3f71a2
--- /dev/null
+++ b/build-system/integration-test/test-projects/pkgOverride/build.gradle
@@ -0,0 +1,13 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.android.tests.basic.foo"
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/pkgOverride/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/pkgOverride/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/pkgOverride/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/pkgOverride/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/multires/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/pkgOverride/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/multires/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/pkgOverride/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/pkgOverride/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/pkgOverride/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/pkgOverride/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/pkgOverride/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/pseudolocalized/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/pkgOverride/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/pseudolocalized/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/pkgOverride/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/pkgOverride/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/pkgOverride/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/pkgOverride/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/pkgOverride/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/pkgOverride/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/pkgOverride/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/placeholderInLibsTest/app/build.gradle b/build-system/integration-test/test-projects/placeholderInLibsTest/app/build.gradle
new file mode 100644
index 0000000..1d8e226
--- /dev/null
+++ b/build-system/integration-test/test-projects/placeholderInLibsTest/app/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.example.manifest_merger_example"
+ manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example"]
+ minSdkVersion 15
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ productFlavors {
+ flavor {
+ applicationId "com.example.manifest_merger_example.flavor"
+ manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example.flavor"]
+ minSdkVersion 15
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+dependencies {
+ compile project(':examplelibrary')
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/flavor/AndroidManifest.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/flavor/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/flavor/AndroidManifest.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/flavor/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/layout/activity_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/layout/activity_main.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/layout/activity_main.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/menu/menu_main.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/menu/menu_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/menu/menu_main.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/menu/menu_main.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-v21/styles.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-v21/styles.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-v21/styles.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-v21/styles.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-w820dp/dimens.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-w820dp/dimens.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values-w820dp/dimens.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/dimens.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/dimens.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/dimens.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/dimens.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/styles.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/styles.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/styles.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/app/src/main/res/values/styles.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/build.gradle b/build-system/integration-test/test-projects/placeholderInLibsTest/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/build.gradle
rename to build-system/integration-test/test-projects/placeholderInLibsTest/build.gradle
diff --git a/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/build.gradle b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/build.gradle
new file mode 100644
index 0000000..bcb27a8
--- /dev/null
+++ b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+}
+
+android.testVariants.all {
+ it.mergedFlavor.manifestPlaceholders = [ localApplicationId:"com.example.manifest_merger_example.flavor"]
+}
+
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/androidTest/java/com/example/examplelibrary/ApplicationTest.java
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/java/com/example/examplelibrary/LoginActivity.java
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/placeholderInLibsTest/examplelibrary/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/placeholderInLibsTest/settings.gradle b/build-system/integration-test/test-projects/placeholderInLibsTest/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/placeholderInLibsTest/settings.gradle
rename to build-system/integration-test/test-projects/placeholderInLibsTest/settings.gradle
diff --git a/build-system/integration-test/test-projects/privateResources/app/build.gradle b/build-system/integration-test/test-projects/privateResources/app/build.gradle
new file mode 100644
index 0000000..a16a89e
--- /dev/null
+++ b/build-system/integration-test/test-projects/privateResources/app/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.android.tools.test.publicsymbols"
+ minSdkVersion 21
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+}
+
+dependencies {
+ compile project(':mylibrary')
+ compile project(':mylibrary2')
+}
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/privateResources/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/privateResources/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/src/main/java/com/android/tools/test/publicsymbols/MainActivity.java b/build-system/integration-test/test-projects/privateResources/app/src/main/java/com/android/tools/test/publicsymbols/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/app/src/main/java/com/android/tools/test/publicsymbols/MainActivity.java
rename to build-system/integration-test/test-projects/privateResources/app/src/main/java/com/android/tools/test/publicsymbols/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/privateResources/app/src/main/res/layout/activity_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/app/src/main/res/layout/activity_main.xml
rename to build-system/integration-test/test-projects/privateResources/app/src/main/res/layout/activity_main.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/src/main/res/menu/menu_main.xml b/build-system/integration-test/test-projects/privateResources/app/src/main/res/menu/menu_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/app/src/main/res/menu/menu_main.xml
rename to build-system/integration-test/test-projects/privateResources/app/src/main/res/menu/menu_main.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/dimens.xml b/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/dimens.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/dimens.xml
rename to build-system/integration-test/test-projects/privateResources/app/src/main/res/values/dimens.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/privateResources/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/styles.xml b/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/styles.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/app/src/main/res/values/styles.xml
rename to build-system/integration-test/test-projects/privateResources/app/src/main/res/values/styles.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/build.gradle b/build-system/integration-test/test-projects/privateResources/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/build.gradle
rename to build-system/integration-test/test-projects/privateResources/build.gradle
diff --git a/build-system/integration-test/test-projects/privateResources/mylibrary/build.gradle b/build-system/integration-test/test-projects/privateResources/mylibrary/build.gradle
new file mode 100644
index 0000000..8cab599
--- /dev/null
+++ b/build-system/integration-test/test-projects/privateResources/mylibrary/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ resourcePrefix 'mylib_'
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/privateResources/mylibrary/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/layout/mylib_shared_name.xml b/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/layout/mylib_shared_name.xml
new file mode 100644
index 0000000..9c8d9fd
--- /dev/null
+++ b/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/layout/mylib_shared_name.xml
@@ -0,0 +1,5 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/mylib_shared_name"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/public.xml b/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/public.xml
new file mode 100644
index 0000000..44aa582
--- /dev/null
+++ b/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/public.xml
@@ -0,0 +1,6 @@
+<resources>
+ <public name="mylib_app_name" type="string"/>
+ <public name="mylib_public_string" type="string"/>
+ <public name="mylib_shared_name" type="string"/>
+ <public name="mylib_shared_name" type="id"/>
+</resources>
diff --git a/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e1f68c9
--- /dev/null
+++ b/build-system/integration-test/test-projects/privateResources/mylibrary/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<resources>
+ <string name="mylib_app_name">My Library</string>
+ <string name="mylib_public_string">Public String in Library</string>
+ <string name="mylib_private_string">Private String in Library</string>
+ <string name="mylib_shared_name">String that has the same name as an ID</string>
+</resources>
diff --git a/build-system/integration-test/test-projects/privateResources/mylibrary2/build.gradle b/build-system/integration-test/test-projects/privateResources/mylibrary2/build.gradle
new file mode 100644
index 0000000..ad67a27
--- /dev/null
+++ b/build-system/integration-test/test-projects/privateResources/mylibrary2/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+ resourcePrefix 'mylib2_'
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/public.xml b/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/public.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/public.xml
rename to build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/public.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/privateResources/mylibrary2/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/privateResources/settings.gradle b/build-system/integration-test/test-projects/privateResources/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/privateResources/settings.gradle
rename to build-system/integration-test/test-projects/privateResources/settings.gradle
diff --git a/build-system/integration-test/test-projects/projectWithClassifierDep/build.gradle b/build-system/integration-test/test-projects/projectWithClassifierDep/build.gradle
new file mode 100644
index 0000000..6e9f75d
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithClassifierDep/build.gradle
@@ -0,0 +1,18 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+repositories {
+ maven { url 'repo' }
+}
+
+dependencies {
+ compile group: 'com.foo', name: 'sample', version: '1.0'
+ androidTestCompile group: 'com.foo', name: 'sample', version: '1.0', classifier: 'testlib'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/1.0/sample-1.0.pom b/build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/1.0/sample-1.0.pom
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/1.0/sample-1.0.pom
rename to build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/1.0/sample-1.0.pom
diff --git a/base/build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/maven-metadata.xml b/build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/maven-metadata.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/maven-metadata.xml
rename to build-system/integration-test/test-projects/projectWithClassifierDep/repo/com/foo/sample/maven-metadata.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/projectWithClassifierDep/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/java/com/android/test/ivyapp/MainActivity.java b/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/java/com/android/test/ivyapp/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/java/com/android/test/ivyapp/MainActivity.java
rename to build-system/integration-test/test-projects/projectWithClassifierDep/src/main/java/com/android/test/ivyapp/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/layout/activity_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/layout/activity_main.xml
rename to build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/layout/activity_main.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/projectWithClassifierDep/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/projectWithIvyDependency/build.gradle b/build-system/integration-test/test-projects/projectWithIvyDependency/build.gradle
new file mode 100644
index 0000000..9b40f86
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithIvyDependency/build.gradle
@@ -0,0 +1,24 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+repositories {
+ ivy {
+ url "ivy-repo/"
+ layout "pattern", {
+ ivy '[organisation]/[module]/[revision]/[module]-[revision].ivy'
+ artifact '[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]'
+ m2compatible = true
+ }
+ }
+}
+
+dependencies {
+ compile 'com.foo:sample:1.0'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/projectWithIvyDependency/ivy-repo/com/foo/sample/1.0/sample-1.0.ivy b/build-system/integration-test/test-projects/projectWithIvyDependency/ivy-repo/com/foo/sample/1.0/sample-1.0.ivy
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithIvyDependency/ivy-repo/com/foo/sample/1.0/sample-1.0.ivy
rename to build-system/integration-test/test-projects/projectWithIvyDependency/ivy-repo/com/foo/sample/1.0/sample-1.0.ivy
diff --git a/base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/projectWithIvyDependency/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/java/com/android/test/ivyapp/MainActivity.java b/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/java/com/android/test/ivyapp/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/java/com/android/test/ivyapp/MainActivity.java
rename to build-system/integration-test/test-projects/projectWithIvyDependency/src/main/java/com/android/test/ivyapp/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/layout/activity_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/layout/activity_main.xml
rename to build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/layout/activity_main.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/projectWithIvyDependency/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithLocalDeps/build.gradle b/build-system/integration-test/test-projects/projectWithLocalDeps/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithLocalDeps/build.gradle
rename to build-system/integration-test/test-projects/projectWithLocalDeps/build.gradle
diff --git a/base/build-system/integration-test/test-projects/projectWithLocalDeps/libs/baseLib-1.0.aar b/build-system/integration-test/test-projects/projectWithLocalDeps/libs/baseLib-1.0.aar
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithLocalDeps/libs/baseLib-1.0.aar
rename to build-system/integration-test/test-projects/projectWithLocalDeps/libs/baseLib-1.0.aar
diff --git a/base/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/projectWithLocalDeps/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/renamedApk/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/renamedApk/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/pkgOverride/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/pkgOverride/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/projectWithModules/app/build.gradle b/build-system/integration-test/test-projects/projectWithModules/app/build.gradle
new file mode 100644
index 0000000..eafb373
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithModules/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/projectWithModules/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/projectWithModules/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/integration-test/test-projects/projectWithModules/app/src/main/java/com/example/android/multiproject/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/app/src/main/java/com/example/android/multiproject/MainActivity.java
rename to build-system/integration-test/test-projects/projectWithModules/app/src/main/java/com/example/android/multiproject/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/projectWithModules/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..d70232f
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/layout/main.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/button_send"
+ android:id="@+id/foo" />
+</LinearLayout>
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/projectWithModules/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/build.gradle b/build-system/integration-test/test-projects/projectWithModules/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/build.gradle
rename to build-system/integration-test/test-projects/projectWithModules/build.gradle
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/jar/build.gradle b/build-system/integration-test/test-projects/projectWithModules/jar/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/jar/build.gradle
rename to build-system/integration-test/test-projects/projectWithModules/jar/build.gradle
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/People.java b/build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/People.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/People.java
rename to build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/People.java
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/Person.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/Person.java
rename to build-system/integration-test/test-projects/projectWithModules/jar/src/main/java/com/example/android/multiproject/person/Person.java
diff --git a/build-system/integration-test/test-projects/projectWithModules/library/build.gradle b/build-system/integration-test/test-projects/projectWithModules/library/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithModules/library/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/projectWithModules/library/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/library/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/projectWithModules/library/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library/src/main/java/com/example/android/multiproject/library/PersonView.java b/build-system/integration-test/test-projects/projectWithModules/library/src/main/java/com/example/android/multiproject/library/PersonView.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/library/src/main/java/com/example/android/multiproject/library/PersonView.java
rename to build-system/integration-test/test-projects/projectWithModules/library/src/main/java/com/example/android/multiproject/library/PersonView.java
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library/src/main/res/layout/liblayout.xml b/build-system/integration-test/test-projects/projectWithModules/library/src/main/res/layout/liblayout.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/library/src/main/res/layout/liblayout.xml
rename to build-system/integration-test/test-projects/projectWithModules/library/src/main/res/layout/liblayout.xml
diff --git a/build-system/integration-test/test-projects/projectWithModules/library2/build.gradle b/build-system/integration-test/test-projects/projectWithModules/library2/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithModules/library2/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/projectWithModules/library2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/projectWithModules/library2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4b22a6f
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithModules/library2/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject.library2.base">
+</manifest>
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library2/src/main/java/com/example/android/multiproject/library2/PersonView2.java b/build-system/integration-test/test-projects/projectWithModules/library2/src/main/java/com/example/android/multiproject/library2/PersonView2.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/library2/src/main/java/com/example/android/multiproject/library2/PersonView2.java
rename to build-system/integration-test/test-projects/projectWithModules/library2/src/main/java/com/example/android/multiproject/library2/PersonView2.java
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library2/src/main/res/layout/lib2layout.xml b/build-system/integration-test/test-projects/projectWithModules/library2/src/main/res/layout/lib2layout.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/library2/src/main/res/layout/lib2layout.xml
rename to build-system/integration-test/test-projects/projectWithModules/library2/src/main/res/layout/lib2layout.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/localJarAsModule/build.gradle b/build-system/integration-test/test-projects/projectWithModules/localJarAsModule/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/localJarAsModule/build.gradle
rename to build-system/integration-test/test-projects/projectWithModules/localJarAsModule/build.gradle
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/settings.gradle b/build-system/integration-test/test-projects/projectWithModules/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/settings.gradle
rename to build-system/integration-test/test-projects/projectWithModules/settings.gradle
diff --git a/build-system/integration-test/test-projects/projectWithModules/test/build.gradle b/build-system/integration-test/test-projects/projectWithModules/test/build.gradle
new file mode 100644
index 0000000..baad58e
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithModules/test/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.test'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/build-system/integration-test/test-projects/projectWithModules/test/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/projectWithModules/test/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54181a5
--- /dev/null
+++ b/build-system/integration-test/test-projects/projectWithModules/test/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic.test">
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
diff --git a/build-system/integration-test/test-projects/pseudolocalized/build.gradle b/build-system/integration-test/test-projects/pseudolocalized/build.gradle
new file mode 100644
index 0000000..390aaf2
--- /dev/null
+++ b/build-system/integration-test/test-projects/pseudolocalized/build.gradle
@@ -0,0 +1,24 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion rootProject.buildToolsVersion
+
+ testBuildType "debug"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ debug {
+ pseudoLocalesEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/noPreDex/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/pseudolocalized/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPreDex/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/pseudolocalized/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/pseudolocalized/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/pseudolocalized/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/pseudolocalized/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/pseudolocalized/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/pseudolocalized/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/pseudolocalized/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/pseudolocalized/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/pseudolocalized/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/pseudolocalized/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/pseudolocalized/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/pseudolocalized/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/pseudolocalized/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/pseudolocalized/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/pseudolocalized/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/renamedApk/build.gradle b/build-system/integration-test/test-projects/renamedApk/build.gradle
new file mode 100644
index 0000000..7be9689
--- /dev/null
+++ b/build-system/integration-test/test-projects/renamedApk/build.gradle
@@ -0,0 +1,19 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ buildTypes.debug {
+ zipAlignEnabled true
+ }
+}
+
+android.applicationVariants.all { variant ->
+ variant.outputs[0].outputFile = file("$project.buildDir/${variant.name}.apk")
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/renamedApk/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
copy to build-system/integration-test/test-projects/renamedApk/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/packagingOptions/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/renamedApk/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/packagingOptions/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/renamedApk/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/noPreDex/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/renamedApk/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPreDex/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/renamedApk/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/renamedApk/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/renamedApk/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/renamedApk/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/renamedApk/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renamedApk/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/renamedApk/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/renamedApk/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithLocalDeps/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/renamedApk/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/noPreDex/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/renamedApk/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/noPreDex/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/renamedApk/src/release/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/renderscript/build.gradle b/build-system/integration-test/test-projects/renderscript/build.gradle
new file mode 100644
index 0000000..a8d9d52
--- /dev/null
+++ b/build-system/integration-test/test-projects/renderscript/build.gradle
@@ -0,0 +1,14 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 17
+ renderscriptTargetApi = 17
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/renderscript/src/main/AndroidManifest.xml
similarity index 100%
copy from base/build-system/integration-test/test-projects/renderscript/src/main/AndroidManifest.xml
copy to build-system/integration-test/test-projects/renderscript/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java b/build-system/integration-test/test-projects/renderscript/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscript/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java
rename to build-system/integration-test/test-projects/renderscript/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/res/drawable/data.jpg b/build-system/integration-test/test-projects/renderscript/src/main/res/drawable/data.jpg
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscript/src/main/res/drawable/data.jpg
rename to build-system/integration-test/test-projects/renderscript/src/main/res/drawable/data.jpg
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/renderscript/src/main/res/layout/main.xml
similarity index 100%
copy from base/build-system/integration-test/test-projects/renderscript/src/main/res/layout/main.xml
copy to build-system/integration-test/test-projects/renderscript/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs b/build-system/integration-test/test-projects/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs
similarity index 100%
copy from base/build-system/integration-test/test-projects/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs
copy to build-system/integration-test/test-projects/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs
diff --git a/build-system/integration-test/test-projects/renderscriptInLib/app/build.gradle b/build-system/integration-test/test-projects/renderscriptInLib/app/build.gradle
new file mode 100644
index 0000000..a66eef5
--- /dev/null
+++ b/build-system/integration-test/test-projects/renderscriptInLib/app/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.oldSdkForRenderscript
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ renderscriptTargetApi = 11
+ }
+}
+
+dependencies {
+ compile project(':lib')
+}
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/Balls.java b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/Balls.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/Balls.java
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/Balls.java
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsRS.java b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsRS.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsRS.java
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsRS.java
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsView.java b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsView.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsView.java
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsView.java
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/flares.png b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/flares.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/flares.png
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/flares.png
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/test_pattern.png b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/test_pattern.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/test_pattern.png
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/res/drawable/test_pattern.png
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/ball_physics.rs b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/ball_physics.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/ball_physics.rs
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/ball_physics.rs
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/balls.rs b/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/balls.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/balls.rs
rename to build-system/integration-test/test-projects/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/balls.rs
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/build.gradle b/build-system/integration-test/test-projects/renderscriptInLib/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/build.gradle
rename to build-system/integration-test/test-projects/renderscriptInLib/build.gradle
diff --git a/build-system/integration-test/test-projects/renderscriptInLib/lib/build.gradle b/build-system/integration-test/test-projects/renderscriptInLib/lib/build.gradle
new file mode 100644
index 0000000..ac779fa
--- /dev/null
+++ b/build-system/integration-test/test-projects/renderscriptInLib/lib/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.oldSdkForRenderscript
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/rs/com/example/android/rs/balls/balls.rsh b/build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/rs/com/example/android/rs/balls/balls.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/rs/com/example/android/rs/balls/balls.rsh
rename to build-system/integration-test/test-projects/renderscriptInLib/lib/src/main/rs/com/example/android/rs/balls/balls.rsh
diff --git a/base/build-system/integration-test/test-projects/renderscriptInLib/settings.gradle b/build-system/integration-test/test-projects/renderscriptInLib/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptInLib/settings.gradle
rename to build-system/integration-test/test-projects/renderscriptInLib/settings.gradle
diff --git a/build-system/integration-test/test-projects/renderscriptMultiSrc/build.gradle b/build-system/integration-test/test-projects/renderscriptMultiSrc/build.gradle
new file mode 100644
index 0000000..bd9243a
--- /dev/null
+++ b/build-system/integration-test/test-projects/renderscriptMultiSrc/build.gradle
@@ -0,0 +1,13 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.oldSdkForRenderscript
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ renderscriptTargetApi = 11
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/debug/rs/com/example/android/rs/balls/balls.rsh b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/debug/rs/com/example/android/rs/balls/balls.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/debug/rs/com/example/android/rs/balls/balls.rsh
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/debug/rs/com/example/android/rs/balls/balls.rsh
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/Balls.java b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/Balls.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/Balls.java
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/Balls.java
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsRS.java b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsRS.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsRS.java
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsRS.java
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsView.java b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsView.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsView.java
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsView.java
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/flares.png b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/flares.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/flares.png
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/flares.png
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/test_pattern.png b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/test_pattern.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/test_pattern.png
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/res/drawable/test_pattern.png
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/ball_physics.rs b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/ball_physics.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/ball_physics.rs
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/ball_physics.rs
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/balls.rs b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/balls.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/balls.rs
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/balls.rs
diff --git a/base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/release/rs/com/example/android/rs/balls/balls.rsh b/build-system/integration-test/test-projects/renderscriptMultiSrc/src/release/rs/com/example/android/rs/balls/balls.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscriptMultiSrc/src/release/rs/com/example/android/rs/balls/balls.rsh
rename to build-system/integration-test/test-projects/renderscriptMultiSrc/src/release/rs/com/example/android/rs/balls/balls.rsh
diff --git a/build-system/integration-test/test-projects/renderscriptNdk/build.gradle b/build-system/integration-test/test-projects/renderscriptNdk/build.gradle
new file mode 100644
index 0000000..e39e7f8
--- /dev/null
+++ b/build-system/integration-test/test-projects/renderscriptNdk/build.gradle
@@ -0,0 +1,23 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 17
+ renderscriptTargetApi = 17
+ renderscriptSupportModeEnabled true
+ renderscriptNdkModeEnabled true
+ ndk {
+ moduleName "renderscript"
+ abiFilter "armeabi-v7a"
+ stl "c++_shared"
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/renderscriptNdk/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscript/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/renderscriptNdk/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/renderscriptNdk/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java b/build-system/integration-test/test-projects/renderscriptNdk/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java
new file mode 100644
index 0000000..f3aabbf
--- /dev/null
+++ b/build-system/integration-test/test-projects/renderscriptNdk/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.hellocompute;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.renderscript.Allocation;
+import android.renderscript.RenderScript;
+import android.widget.ImageView;
+
+public class HelloCompute extends Activity {
+ private Bitmap mBitmapIn;
+ private Bitmap mBitmapOut;
+
+ private Allocation mInAllocation;
+ private Allocation mOutAllocation;
+
+ private native void mono(Bitmap src, Bitmap dst);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mBitmapIn = loadBitmap(R.drawable.data);
+ mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(),
+ mBitmapIn.getConfig());
+
+ ImageView in = (ImageView) findViewById(R.id.displayin);
+ in.setImageBitmap(mBitmapIn);
+
+ ImageView out = (ImageView) findViewById(R.id.displayout);
+ out.setImageBitmap(mBitmapOut);
+
+ mono(mBitmapIn, mBitmapOut);
+ }
+
+ private Bitmap loadBitmap(int resource) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeResource(getResources(), resource, options);
+ }
+
+ static {
+ System.loadLibrary("renderscript");
+ }
+}
diff --git a/build-system/integration-test/test-projects/renderscriptNdk/src/main/jni/renderscript.cpp b/build-system/integration-test/test-projects/renderscriptNdk/src/main/jni/renderscript.cpp
new file mode 100644
index 0000000..1da9967
--- /dev/null
+++ b/build-system/integration-test/test-projects/renderscriptNdk/src/main/jni/renderscript.cpp
@@ -0,0 +1,15 @@
+#include <android/bitmap.h>
+#include <jni.h>
+#include <RenderScript.h>
+
+using namespace android::RSC;
+
+extern "C" {
+
+JNIEXPORT void JNICALL
+Java_com_example_android_rs_hellocompute_HelloCompute_mono(JNIEnv* env, jobject src, jobject dst) {
+ // TODO: Implement this function to process the image. For now, just make sure the project
+ // compiles and the
+}
+
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/drawable/data.jpg b/build-system/integration-test/test-projects/renderscriptNdk/src/main/res/drawable/data.jpg
similarity index 100%
rename from base/build-system/integration-test/test-projects/ndkRsHelloCompute/src/main/res/drawable/data.jpg
rename to build-system/integration-test/test-projects/renderscriptNdk/src/main/res/drawable/data.jpg
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/renderscriptNdk/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscript/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/renderscriptNdk/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs b/build-system/integration-test/test-projects/renderscriptNdk/src/main/rs/com/example/android/rs/hellocompute/mono.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs
rename to build-system/integration-test/test-projects/renderscriptNdk/src/main/rs/com/example/android/rs/hellocompute/mono.rs
diff --git a/base/build-system/integration-test/test-projects/repo/.gitignore b/build-system/integration-test/test-projects/repo/.gitignore
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/.gitignore
rename to build-system/integration-test/test-projects/repo/.gitignore
diff --git a/build-system/integration-test/test-projects/repo/app/build.gradle b/build-system/integration-test/test-projects/repo/app/build.gradle
new file mode 100644
index 0000000..568dd93
--- /dev/null
+++ b/build-system/integration-test/test-projects/repo/app/build.gradle
@@ -0,0 +1,20 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+apply plugin: 'maven'
+
+repositories {
+ maven { url '../testrepo' }
+}
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.example.android.multiproject:lib:1.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/repo/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/repo/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/integration-test/test-projects/repo/app/src/main/java/com/example/android/multiproject/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/java/com/example/android/multiproject/MainActivity.java
rename to build-system/integration-test/test-projects/repo/app/src/main/java/com/example/android/multiproject/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/repo/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/repo/app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/repo/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/repo/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/repo/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/repo/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/repo/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/repo/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/repo/app/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/repo/baseLibrary/build.gradle b/build-system/integration-test/test-projects/repo/baseLibrary/build.gradle
new file mode 100644
index 0000000..5c01e16
--- /dev/null
+++ b/build-system/integration-test/test-projects/repo/baseLibrary/build.gradle
@@ -0,0 +1,32 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+repositories {
+ maven { url '../testrepo' }
+}
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.example.android.multiproject:util:1.0'
+ releaseCompile 'com.google.guava:guava:17.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'baseLib'
+version = '1.0'
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri("../testrepo"))
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/projectWithModules/library2/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/repo/baseLibrary/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/projectWithModules/library2/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/repo/baseLibrary/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/repo/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/integration-test/test-projects/repo/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
rename to build-system/integration-test/test-projects/repo/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
diff --git a/build-system/integration-test/test-projects/repo/library/build.gradle b/build-system/integration-test/test-projects/repo/library/build.gradle
new file mode 100644
index 0000000..7e1eb38
--- /dev/null
+++ b/build-system/integration-test/test-projects/repo/library/build.gradle
@@ -0,0 +1,32 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+repositories {
+ maven { url '../testrepo' }
+}
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.example.android.multiproject:baseLib:1.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'lib'
+version = '1.0'
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri("../testrepo"))
+ pom.groupId = 'com.example.android.multiproject'
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/repo/library/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/repo/library/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/library/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/repo/library/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/repo/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/integration-test/test-projects/repo/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
rename to build-system/integration-test/test-projects/repo/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
diff --git a/base/build-system/integration-test/test-projects/repo/library/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/repo/library/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/library/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/repo/library/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/repo/util/build.gradle b/build-system/integration-test/test-projects/repo/util/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/util/build.gradle
rename to build-system/integration-test/test-projects/repo/util/build.gradle
diff --git a/base/build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/People.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/People.java
rename to build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/People.java
diff --git a/base/build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/Person.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/Person.java
rename to build-system/integration-test/test-projects/repo/util/src/main/java/com/example/android/multiproject/person/Person.java
diff --git a/build-system/integration-test/test-projects/rsSupportMode/build.gradle b/build-system/integration-test/test-projects/rsSupportMode/build.gradle
new file mode 100644
index 0000000..f5247bf
--- /dev/null
+++ b/build-system/integration-test/test-projects/rsSupportMode/build.gradle
@@ -0,0 +1,36 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+project.ext['android.useDeprecatedNdk'] = true
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 8
+ targetSdkVersion 16
+ renderscriptTargetApi 18
+ renderscriptSupportModeEnabled true
+ }
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilter "armeabi-v7a"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/proguard-project.txt b/build-system/integration-test/test-projects/rsSupportMode/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/proguard-project.txt
rename to build-system/integration-test/test-projects/rsSupportMode/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/project.properties b/build-system/integration-test/test-projects/rsSupportMode/project.properties
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/project.properties
rename to build-system/integration-test/test-projects/rsSupportMode/project.properties
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/rsSupportMode/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/BWFilter.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/BWFilter.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/BWFilter.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/BWFilter.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blend.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blend.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blend.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blend.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25G.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25G.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25G.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Blur25G.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorCube.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorCube.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorCube.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorCube.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorMatrix.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorMatrix.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorMatrix.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ColorMatrix.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Contrast.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Contrast.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Contrast.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Contrast.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve3x3.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve3x3.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve3x3.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve3x3.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve5x5.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve5x5.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve5x5.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Convolve5x5.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Copy.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Copy.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Copy.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Copy.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/CrossProcess.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/CrossProcess.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/CrossProcess.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/CrossProcess.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Exposure.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Exposure.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Exposure.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Exposure.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Fisheye.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Fisheye.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Fisheye.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Fisheye.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Grain.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Grain.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Grain.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Grain.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Greyscale.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Greyscale.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Greyscale.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Greyscale.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/GroupTest.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/GroupTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/GroupTest.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/GroupTest.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ImageProcessingActivity2.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ImageProcessingActivity2.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ImageProcessingActivity2.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/ImageProcessingActivity2.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/LevelsV4.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/LevelsV4.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/LevelsV4.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/LevelsV4.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Mandelbrot.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Mandelbrot.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Mandelbrot.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Mandelbrot.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Shadows.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Shadows.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Shadows.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Shadows.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/TestBase.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/TestBase.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/TestBase.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/TestBase.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vibrance.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vibrance.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vibrance.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vibrance.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vignette.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vignette.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vignette.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/Vignette.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/WhiteBalance.java b/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/WhiteBalance.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/WhiteBalance.java
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/java/com/android/rs/image/WhiteBalance.java
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/city.png b/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/city.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/city.png
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/city.png
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067.jpg b/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067.jpg
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067.jpg
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067.jpg
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067b.jpg b/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067b.jpg
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067b.jpg
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067b.jpg
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/spinner_layout.xml b/build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/spinner_layout.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/spinner_layout.xml
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/res/layout/spinner_layout.xml
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/rsSupportMode/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/blend.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/blend.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/blend.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/blend.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/bwfilter.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/bwfilter.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/bwfilter.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/bwfilter.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colorcube.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colorcube.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colorcube.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colorcube.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colormatrix.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colormatrix.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colormatrix.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/colormatrix.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/contrast.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/contrast.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/contrast.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/contrast.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/convolve5x5.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/convolve5x5.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/convolve5x5.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/convolve5x5.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/copy.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/copy.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/copy.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/copy.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/exposure.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/exposure.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/exposure.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/exposure.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye.rsh b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye.rsh
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye.rsh
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx.rsh b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx.rsh
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx.rsh
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_full.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_full.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_full.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_full.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_relaxed.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_relaxed.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_relaxed.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_approx_relaxed.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_full.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_full.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_full.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_full.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_relaxed.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_relaxed.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_relaxed.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/fisheye_relaxed.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/grain.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/grain.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/grain.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/grain.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/greyscale.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/greyscale.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/greyscale.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/greyscale.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip.rsh b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip.rsh
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip.rsh
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip2_convolve3x3.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip2_convolve3x3.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip2_convolve3x3.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/ip2_convolve3x3.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels.rsh b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels.rsh
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels.rsh
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_full.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_full.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_full.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_full.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_relaxed.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_relaxed.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_relaxed.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/levels_relaxed.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/mandelbrot.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/mandelbrot.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/mandelbrot.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/mandelbrot.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/shadows.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/shadows.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/shadows.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/shadows.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/threshold.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/threshold.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/threshold.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/threshold.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vibrance.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vibrance.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vibrance.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vibrance.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette.rsh b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette.rsh
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette.rsh
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx.rsh b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx.rsh
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx.rsh
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx.rsh
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_full.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_full.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_full.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_full.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_relaxed.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_relaxed.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_relaxed.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_approx_relaxed.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_full.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_full.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_full.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_full.rs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_relaxed.fs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_relaxed.fs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_relaxed.fs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/vignette_relaxed.fs
diff --git a/base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/wbalance.rs b/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/wbalance.rs
similarity index 100%
rename from base/build-system/integration-test/test-projects/rsSupportMode/src/main/rs/wbalance.rs
rename to build-system/integration-test/test-projects/rsSupportMode/src/main/rs/wbalance.rs
diff --git a/build-system/integration-test/test-projects/sameNamedLibs/app/build.gradle b/build-system/integration-test/test-projects/sameNamedLibs/app/build.gradle
new file mode 100644
index 0000000..79ce849
--- /dev/null
+++ b/build-system/integration-test/test-projects/sameNamedLibs/app/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib1:libs')
+ compile project(':lib2b:libs')
+ compile project(':libapp:libs')
+}
diff --git a/base/build-system/integration-test/test-projects/assets/app/proguard-project.txt b/build-system/integration-test/test-projects/sameNamedLibs/app/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/proguard-project.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/app/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/localAarTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/integration-test/test-projects/sameNamedLibs/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/localAarTest/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/androidTest/java/com/android/tests/libstest/app/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/App.java b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/App.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/App.java
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/App.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
diff --git a/base/layoutlib-api/sample/testproject/res/drawable-hdpi/icon.png b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/layoutlib-api/sample/testproject/res/drawable-hdpi/icon.png
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-hdpi/icon.png
diff --git a/base/layoutlib-api/sample/testproject/res/drawable-ldpi/icon.png b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/layoutlib-api/sample/testproject/res/drawable-ldpi/icon.png
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-ldpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/drawable-mdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/libsTest/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/libsTest/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/assets/App.txt b/build-system/integration-test/test-projects/sameNamedLibs/app/src/main/resources/com/android/tests/libstest/app/App.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/assets/App.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/app/src/main/resources/com/android/tests/libstest/app/App.txt
diff --git a/base/build-system/integration-test/test-projects/applicationIdInLibsTest/build.gradle b/build-system/integration-test/test-projects/sameNamedLibs/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/applicationIdInLibsTest/build.gradle
rename to build-system/integration-test/test-projects/sameNamedLibs/build.gradle
diff --git a/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/build.gradle b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/build.gradle
new file mode 100644
index 0000000..1bc9a9d
--- /dev/null
+++ b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.library'
+
+dependencies {
+ compile project(':lib2:libs')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 15
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/proguard-project.txt b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/proguard-project.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/androidTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/Lib1.java b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/Lib1.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/Lib1.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/Lib1.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/MainActivity.java b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/layout/lib1_main.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/layout/lib1_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/layout/lib1_main.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/layout/lib1_main.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt b/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/lib1/libs/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
diff --git a/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/build.gradle b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/proguard-project.txt b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/proguard-project.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/Lib2.java b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/Lib2.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/Lib2.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/Lib2.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity.java b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/layout/lib2_main.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/layout/lib2_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/layout/lib2_main.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/layout/lib2_main.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt b/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
diff --git a/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/build.gradle b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/proguard-project.txt b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/proguard-project.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/androidTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/Lib2b.java b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/layout/lib2b_main.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/layout/lib2b_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/layout/lib2b_main.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/layout/lib2b_main.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt b/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/lib2b/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
diff --git a/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/build.gradle b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/build.gradle
new file mode 100644
index 0000000..7f5eaa9
--- /dev/null
+++ b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/proguard-project.txt b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/proguard-project.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/proguard-project.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/proguard-project.txt
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/androidTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/LibApp.java b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/LibApp.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/LibApp.java
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/LibApp.java
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-ldpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-ldpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/layout/libapp_main.xml b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/layout/libapp_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/layout/libapp_main.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/layout/libapp_main.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/resources/com/android/tests/libstest/app/Libapp.txt b/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/resources/com/android/tests/libstest/app/Libapp.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/resources/com/android/tests/libstest/app/Libapp.txt
rename to build-system/integration-test/test-projects/sameNamedLibs/libapp/libs/src/main/resources/com/android/tests/libstest/app/Libapp.txt
diff --git a/base/build-system/integration-test/test-projects/sameNamedLibs/settings.gradle b/build-system/integration-test/test-projects/sameNamedLibs/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/sameNamedLibs/settings.gradle
rename to build-system/integration-test/test-projects/sameNamedLibs/settings.gradle
diff --git a/build-system/integration-test/test-projects/separateTestModule/app/build.gradle b/build-system/integration-test/test-projects/separateTestModule/app/build.gradle
new file mode 100644
index 0000000..0c3eebb
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestModule/app/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ publishNonDefault true
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/pkgOverride/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestModule/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/pkgOverride/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestModule/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/renamedApk/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/separateTestModule/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/renamedApk/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/separateTestModule/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/separateTestModule/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/separateTestModule/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/renamedApk/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renamedApk/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestModule/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/renamedApk/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestModule/app/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renamedApk/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestModule/app/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/build.gradle b/build-system/integration-test/test-projects/separateTestModule/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/build.gradle
rename to build-system/integration-test/test-projects/separateTestModule/build.gradle
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/settings.gradle b/build-system/integration-test/test-projects/separateTestModule/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/settings.gradle
rename to build-system/integration-test/test-projects/separateTestModule/settings.gradle
diff --git a/build-system/integration-test/test-projects/separateTestModule/test/build.gradle b/build-system/integration-test/test-projects/separateTestModule/test/build.gradle
new file mode 100644
index 0000000..a4ab58a
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestModule/test/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.test'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ targetProjectPath ':app'
+ targetVariant 'debug'
+}
+
+task checkDependencies << {
+ assert project.connectedCheck instanceof Task
+ def deps = project.connectedCheck.taskDependencies.getDependencies(project.connectedCheck)
+
+ def connectedAndroidTest = deps.find { it.name == "connectedAndroidTest" }
+ assert connectedAndroidTest instanceof Task
+
+ deps = connectedAndroidTest.taskDependencies.getDependencies(connectedAndroidTest)
+ def actualTestTask = deps.find { it.name == "connectedDebugAndroidTest" }
+ // The actual class name has the _Decorated suffix.
+ assert actualTestTask.class.name.contains(".DeviceProviderInstrumentTestTask")
+
+ deps = actualTestTask.taskDependencies.getDependencies(actualTestTask)
+ def assembleTestModule = deps.find { it.name == "assembleDebug" }
+ assert assembleTestModule != null
+
+ // :test:assembleDebug will fail if the app module is not built, so other tests verify
+ // that this ends up compiling the app module.
+}
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/test/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestModule/test/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/test/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestModule/test/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/build.gradle
new file mode 100644
index 0000000..af30a3c
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ publishNonDefault true
+
+ defaultConfig {
+ minSdkVersion 16
+ }
+
+ buildTypes {
+ minified.initWith(buildTypes.debug)
+ minified {
+ minifyEnabled true
+ useJack rootProject.useJack
+ proguardFiles getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+}
+
+dependencies {
+ compile project(':lib')
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/pseudolocalized/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/pseudolocalized/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/app/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/build.gradle
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/build.gradle
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/build.gradle
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/build.gradle
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/src/main/java/com/android/tests/jarDep/JarDependencyUtil.java b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/src/main/java/com/android/tests/jarDep/JarDependencyUtil.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/src/main/java/com/android/tests/jarDep/JarDependencyUtil.java
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/jar/src/main/java/com/android/tests/jarDep/JarDependencyUtil.java
diff --git a/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/build.gradle
new file mode 100644
index 0000000..a0c3aa4
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+
+dependencies {
+ compile project(':jar')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/androidTest/java/com/android/tests/basic/StringGetterTest.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/java/com/android/tests/basic/StringGetter.java b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/java/com/android/tests/basic/StringGetter.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/java/com/android/tests/basic/StringGetter.java
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/lib/src/main/java/com/android/tests/basic/StringGetter.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/settings.gradle b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/settings.gradle
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/settings.gradle
diff --git a/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/build.gradle
new file mode 100644
index 0000000..3f11c7d
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'com.android.test'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ targetProjectPath ':app'
+ targetVariant 'minified'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestModuleWithDependencies/test/src/main/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/build.gradle
new file mode 100644
index 0000000..18c2d68
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/build.gradle
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.application'
+
+apply from: "../../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.google.code.findbugs:jsr305:1.3.9'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ publishNonDefault true
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+
+ buildTypes {
+ minified.initWith(buildTypes.debug)
+ minified {
+ minifyEnabled true
+ useJack rootProject.useJack
+ proguardFiles getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/utils/Utility.java b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/utils/Utility.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/utils/Utility.java
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/java/com/android/tests/utils/Utility.java
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/app/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/build.gradle
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/build.gradle
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/settings.gradle b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/settings.gradle
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/settings.gradle
diff --git a/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/build.gradle b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/build.gradle
new file mode 100644
index 0000000..e5f2937
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.test'
+
+apply from: "../../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ targetProjectPath ':app'
+ targetVariant 'minified'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestModuleWithMinifiedApp/test/src/main/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/build.gradle b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/build.gradle
new file mode 100644
index 0000000..7d7134f
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/build.gradle
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.application'
+
+apply from: "../../commonLocalRepo.gradle"
+
+dependencies {
+ compile 'com.google.code.findbugs:jsr305:1.3.9'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ publishNonDefault true
+
+ defaultConfig {
+ minSdkVersion 8
+ }
+
+ buildTypes {
+ minified.initWith(buildTypes.debug)
+ minified {
+ minifyEnabled true
+ useJack rootProject.useJack
+ proguardFiles 'proguard.txt'
+ }
+ }
+}
diff --git a/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/proguard.txt b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/proguard.txt
new file mode 100644
index 0000000..cdbc8c5
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/proguard.txt
@@ -0,0 +1,2 @@
+-dontobfuscate
+-dontoptimize
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/utils/Utility.java b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/utils/Utility.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/utils/Utility.java
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/java/com/android/tests/utils/Utility.java
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/app/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/build.gradle b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/build.gradle
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/build.gradle
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/settings.gradle b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/settings.gradle
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/settings.gradle
diff --git a/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/build.gradle b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/build.gradle
new file mode 100644
index 0000000..e5f2937
--- /dev/null
+++ b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.test'
+
+apply from: "../../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ targetProjectPath ':app'
+ targetVariant 'minified'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/separateTestWithMinificationButNoObfuscation/test/src/main/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/shrink/README.txt b/build-system/integration-test/test-projects/shrink/README.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/README.txt
rename to build-system/integration-test/test-projects/shrink/README.txt
diff --git a/build-system/integration-test/test-projects/shrink/abisplits/build.gradle b/build-system/integration-test/test-projects/shrink/abisplits/build.gradle
new file mode 100644
index 0000000..718b5e2
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/abisplits/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+ }
+
+ buildTypes {
+ release {
+ shrinkResources true
+ minifyEnabled true
+ }
+ }
+
+ splits {
+ abi {
+ enable true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/shrink/abisplits/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/shrink/abisplits/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/abisplits/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/shrink/abisplits/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/abisplits/src/main/java/com/android/tests/shrink/UsedActivity.java b/build-system/integration-test/test-projects/shrink/abisplits/src/main/java/com/android/tests/shrink/UsedActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/abisplits/src/main/java/com/android/tests/shrink/UsedActivity.java
rename to build-system/integration-test/test-projects/shrink/abisplits/src/main/java/com/android/tests/shrink/UsedActivity.java
diff --git a/base/build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/unused.xml b/build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/unused.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/unused.xml
rename to build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/unused.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/used.xml b/build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/used.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/used.xml
rename to build-system/integration-test/test-projects/shrink/abisplits/src/main/res/layout/used.xml
diff --git a/build-system/integration-test/test-projects/shrink/build.gradle b/build-system/integration-test/test-projects/shrink/build.gradle
new file mode 100644
index 0000000..992bec1
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/build.gradle
@@ -0,0 +1,43 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ debug {
+ shrinkResources false
+ minifyEnabled false
+ }
+ proguardNoShrink {
+ shrinkResources false
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ release {
+ shrinkResources true
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
+
+dependencies {
+ compile project(':lib')
+ compile 'com.android.support:support-annotations:+'
+ compile 'com.android.support:support-v4:+'
+}
diff --git a/build-system/integration-test/test-projects/shrink/keep/build.gradle b/build-system/integration-test/test-projects/shrink/keep/build.gradle
new file mode 100644
index 0000000..aae82e1
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/keep/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+ }
+
+ buildTypes {
+ release {
+ shrinkResources true
+ minifyEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/shrink/keep/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/keep/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/shrink/keep/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/src/main/java/com/android/tests/shrink/keep/KeepActivity.java b/build-system/integration-test/test-projects/shrink/keep/src/main/java/com/android/tests/shrink/keep/KeepActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/keep/src/main/java/com/android/tests/shrink/keep/KeepActivity.java
rename to build-system/integration-test/test-projects/shrink/keep/src/main/java/com/android/tests/shrink/keep/KeepActivity.java
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused1.xml b/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused1.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused1.xml
rename to build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused1.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused2.xml b/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused2.xml
rename to build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/unused2.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/used1.xml b/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/used1.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/used1.xml
rename to build-system/integration-test/test-projects/shrink/keep/src/main/res/layout/used1.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/src/main/res/raw/keep.xml b/build-system/integration-test/test-projects/shrink/keep/src/main/res/raw/keep.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/keep/src/main/res/raw/keep.xml
rename to build-system/integration-test/test-projects/shrink/keep/src/main/res/raw/keep.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/keep/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/shrink/keep/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/keep/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/shrink/keep/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/shrink/lib/build.gradle b/build-system/integration-test/test-projects/shrink/lib/build.gradle
new file mode 100644
index 0000000..b4b358b
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/lib/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/shrink/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/shrink/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/shrink/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/lib/src/main/res/layout/lib_unused.xml b/build-system/integration-test/test-projects/shrink/lib/src/main/res/layout/lib_unused.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/lib/src/main/res/layout/lib_unused.xml
rename to build-system/integration-test/test-projects/shrink/lib/src/main/res/layout/lib_unused.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/proguard-rules.pro b/build-system/integration-test/test-projects/shrink/proguard-rules.pro
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/proguard-rules.pro
rename to build-system/integration-test/test-projects/shrink/proguard-rules.pro
diff --git a/base/build-system/integration-test/test-projects/shrink/settings.gradle b/build-system/integration-test/test-projects/shrink/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/settings.gradle
rename to build-system/integration-test/test-projects/shrink/settings.gradle
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/shrink/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/shrink/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/AnnotationInflation.java b/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/AnnotationInflation.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/AnnotationInflation.java
rename to build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/AnnotationInflation.java
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layout.java b/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layout.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layout.java
rename to build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layout.java
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layouts.java b/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layouts.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layouts.java
rename to build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/Layouts.java
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/ResourceReferences.java b/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/ResourceReferences.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/ResourceReferences.java
rename to build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/ResourceReferences.java
diff --git a/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/RootActivity.java b/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/RootActivity.java
new file mode 100644
index 0000000..c76e8d6
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/RootActivity.java
@@ -0,0 +1,81 @@
+package com.android.tests.shrink;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.Menu;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+public class RootActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.used1);
+ ResourceReferences.referenceResources(this);
+ System.out.println(R.layout.used7);
+ System.out.println(R.drawable.force_remove);
+ AnnotationInflation.createView(this, ScreenType1.class, null);
+ AnnotationInflation.createView(this, ScreenType2.class, null);
+
+ for (int id : layout_ids) {
+ System.out.println(id);
+ }
+
+ dynamicResourceNames(1);
+ }
+
+ public void dynamicResourceNames(int version) {
+ // Normal string concatenation:
+ String versionNumber = String.valueOf(version);
+ int res = getResources().getIdentifier("prefix_used_" + version, "layout",
+ getPackageName());
+ System.out.println(res);
+
+ String name = String.format("prefix_used_%1d", version + 1);
+ res = getResources().getIdentifier(name, "layout", getPackageName());
+ System.out.println(res);
+
+ name = String.format("prefix_%1$s_suffix", version + 2);
+ res = getResources().getIdentifier(name, "layout", getPackageName());
+ System.out.println(res);
+ }
+
+ public void unusedMethod() {
+ Drawable drawable = getResources().getDrawable(R.drawable.unused10);
+ System.out.println(drawable);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.used13, menu);
+ return true;
+ }
+
+
+ @Layout(R.layout.used17)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({METHOD, PARAMETER, TYPE, LOCAL_VARIABLE, FIELD})
+ public @interface Indirect {
+ int[] value();
+ }
+
+ @Layout(R.layout.used16)
+ private static class ScreenType1 {
+ }
+
+ @Indirect(5)
+ @Layouts({R.layout.used18,R.layout.used19})
+ private static class ScreenType2 {
+ }
+
+ private static final int[] layout_ids = { R.layout.used20 };
+}
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/UnusedActivity.java b/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/UnusedActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/UnusedActivity.java
rename to build-system/integration-test/test-projects/shrink/src/main/java/com/android/tests/shrink/UnusedActivity.java
diff --git a/build-system/integration-test/test-projects/shrink/src/main/res/drawable/force_remove.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/force_remove.xml
new file mode 100644
index 0000000..f426e98
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/force_remove.xml
@@ -0,0 +1,2 @@
+<!-- referenced from RootActivity, but removed with tools:discard -->
+<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ff00ff" />
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused10.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused10.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused10.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused10.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused11.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused11.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused11.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused11.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused9.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused9.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused9.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/unused9.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used10.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used10.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used10.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/used10.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used11.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used11.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used11.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/used11.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used12.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used12.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used12.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/used12.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used15.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used15.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used15.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/used15.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used9.xml b/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used9.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/drawable/used9.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/drawable/used9.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_a.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_a.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_a.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_a.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_b2.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_b2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_b2.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_b2.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_c.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_c.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_c.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/l_used_c.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_3_suffix.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_3_suffix.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_3_suffix.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_3_suffix.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_1.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_1.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_1.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_1.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_2.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_2.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/prefix_used_2.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused1.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused1.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused1.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/unused1.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused13.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused13.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused13.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/unused13.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused14.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused14.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused14.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/unused14.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused2.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/unused2.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/unused2.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used1.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used1.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used1.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used1.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used14.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used14.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used14.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used14.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used16.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used16.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used16.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used16.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used17.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used17.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used17.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used17.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used18.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used18.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used18.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used18.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used19.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used19.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used19.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used19.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used2.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used2.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used2.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used20.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used20.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used20.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used20.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used21.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used21.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used21.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used21.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used3.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used3.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used3.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used3.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used4.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used4.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used4.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used4.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used5.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used5.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used5.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used5.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used6.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used6.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used6.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used6.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used7.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used7.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used7.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used7.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used8.xml b/build-system/integration-test/test-projects/shrink/src/main/res/layout/used8.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/layout/used8.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/layout/used8.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/menu/unused12.xml b/build-system/integration-test/test-projects/shrink/src/main/res/menu/unused12.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/menu/unused12.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/menu/unused12.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/menu/used13.xml b/build-system/integration-test/test-projects/shrink/src/main/res/menu/used13.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/menu/used13.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/menu/used13.xml
diff --git a/build-system/integration-test/test-projects/shrink/src/main/res/raw/keep.xml b/build-system/integration-test/test-projects/shrink/src/main/res/raw/keep.xml
new file mode 100644
index 0000000..36b37a5
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/src/main/res/raw/keep.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools"
+ tools:keep="@layout/l_used*_c, at layout/l_used_a, at layout/l_used_b*"
+ tools:discard="@drawable/force_remove"/>
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/values-w820dp/aliases.xml b/build-system/integration-test/test-projects/shrink/src/main/res/values-w820dp/aliases.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/values-w820dp/aliases.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/values-w820dp/aliases.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/values/aliases.xml b/build-system/integration-test/test-projects/shrink/src/main/res/values/aliases.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/values/aliases.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/values/aliases.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/shrink/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/src/main/res/values/styles.xml b/build-system/integration-test/test-projects/shrink/src/main/res/values/styles.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/src/main/res/values/styles.xml
rename to build-system/integration-test/test-projects/shrink/src/main/res/values/styles.xml
diff --git a/build-system/integration-test/test-projects/shrink/webview/build.gradle b/build-system/integration-test/test-projects/shrink/webview/build.gradle
new file mode 100644
index 0000000..aae82e1
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/webview/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 16
+ targetSdkVersion 20
+ }
+
+ buildTypes {
+ release {
+ shrinkResources true
+ minifyEnabled true
+ }
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/shrink/webview/src/main/java/com/android/tests/shrink/webview/WebViewActivity.java b/build-system/integration-test/test-projects/shrink/webview/src/main/java/com/android/tests/shrink/webview/WebViewActivity.java
new file mode 100644
index 0000000..495abfe
--- /dev/null
+++ b/build-system/integration-test/test-projects/shrink/webview/src/main/java/com/android/tests/shrink/webview/WebViewActivity.java
@@ -0,0 +1,29 @@
+package com.android.tests.shrink.webview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+public class WebViewActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.webview);
+
+ WebView webview = (WebView) findViewById(R.id.webview);
+
+ // Should mark R.drawable.used1 as used
+ webview.loadUrl("file:///android_res/drawable/used1.xml");
+
+ String html = "<html><img src=\"used2.xml\"/></html>";
+ // This call should make me whitelist all strings
+ webview.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html",
+ "utf-8", null);
+
+ // Should mark R.drawable.used1 as used
+ webview.loadUrl("file:///android_res/raw/used_index.html");
+
+ // Should make R.raw.unknown and dependencies as used
+ System.out.println(R.raw.unknown);
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/drawable/used1.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/res/drawable/used1.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/drawable/used1.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/drawable/used1.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout1.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout1.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout1.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout1.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout2.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout2.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout2.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout2.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout3.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout3.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout3.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/used_layout3.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/webview.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/webview.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/webview.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/layout/webview.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unknown b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unknown
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unknown
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unknown
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_icon.png b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_icon.png
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_icon.png
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_index.html b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_index.html
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_index.html
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/unused_index.html
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon.png b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon.png
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon.png
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon2.png b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon2.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon2.png
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_icon2.png
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index.html b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index.html
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index.html
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index.html
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index2.html b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index2.html
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index2.html
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index2.html
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index3.html b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index3.html
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index3.html
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_index3.html
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_script.js b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_script.js
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_script.js
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_script.js
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_styles.css b/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_styles.css
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_styles.css
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/raw/used_styles.css
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/shrink/webview/src/main/res/xml/my_xml.xml b/build-system/integration-test/test-projects/shrink/webview/src/main/res/xml/my_xml.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/shrink/webview/src/main/res/xml/my_xml.xml
rename to build-system/integration-test/test-projects/shrink/webview/src/main/res/xml/my_xml.xml
diff --git a/base/build-system/integration-test/test-projects/simpleManifestMergingTask/build.gradle b/build-system/integration-test/test-projects/simpleManifestMergingTask/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleManifestMergingTask/build.gradle
rename to build-system/integration-test/test-projects/simpleManifestMergingTask/build.gradle
diff --git a/base/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib1_AndroidManifest.xml b/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib1_AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib1_AndroidManifest.xml
rename to build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib1_AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib2_AndroidManifest.xml b/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib2_AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib2_AndroidManifest.xml
rename to build-system/integration-test/test-projects/simpleManifestMergingTask/src/main/Lib2_AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/build.gradle b/build-system/integration-test/test-projects/simpleMicroApp/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/build.gradle
rename to build-system/integration-test/test-projects/simpleMicroApp/build.gradle
diff --git a/build-system/integration-test/test-projects/simpleMicroApp/main/build.gradle b/build-system/integration-test/test-projects/simpleMicroApp/main/build.gradle
new file mode 100644
index 0000000..224ba30
--- /dev/null
+++ b/build-system/integration-test/test-projects/simpleMicroApp/main/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+}
+
+dependencies {
+ wearApp project(':wear')
+}
+
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/simpleMicroApp/main/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
copy to build-system/integration-test/test-projects/simpleMicroApp/main/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/renamedApk/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/renamedApk/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/simpleMicroApp/main/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/simpleMicroApp/main/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/testDependency/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/testDependency/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/settings.gradle b/build-system/integration-test/test-projects/simpleMicroApp/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/settings.gradle
rename to build-system/integration-test/test-projects/simpleMicroApp/settings.gradle
diff --git a/build-system/integration-test/test-projects/simpleMicroApp/wear/build.gradle b/build-system/integration-test/test-projects/simpleMicroApp/wear/build.gradle
new file mode 100644
index 0000000..fd5a98e
--- /dev/null
+++ b/build-system/integration-test/test-projects/simpleMicroApp/wear/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 42
+ versionName "default"
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+}
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/simpleMicroApp/wear/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
copy from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
copy to build-system/integration-test/test-projects/simpleMicroApp/wear/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModule/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModule/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/testWithDep/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/main/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/build.gradle b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/build.gradle
new file mode 100644
index 0000000..bc4027e
--- /dev/null
+++ b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ publishNonDefault true
+ generatePureSplits true
+
+ defaultConfig {
+ versionCode 12
+ minSdkVersion 21
+ targetSdkVersion 21
+ }
+
+ splits {
+ density {
+ enable true
+ exclude "ldpi", "tvdpi", "xxxhdpi"
+ }
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/api/app/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-hdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/ic_launcher.png b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xhdpi/ic_launcher.png b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xxhdpi/ic_launcher.png
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable/ic_launcher.png b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable/ic_launcher.png
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/drawable/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/build.gradle b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/build.gradle
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/build.gradle
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/settings.gradle b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/settings.gradle
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/settings.gradle
diff --git a/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/build.gradle b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/build.gradle
new file mode 100644
index 0000000..f88100c
--- /dev/null
+++ b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'com.android.test'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ targetProjectPath ':app'
+ targetVariant 'debug'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/splitAwareSeparateTestModule/test/src/main/java/com/android/tests/basic/MainTest.java
diff --git a/build-system/integration-test/test-projects/testDependency/build.gradle b/build-system/integration-test/test-projects/testDependency/build.gradle
new file mode 100644
index 0000000..f4adbe7
--- /dev/null
+++ b/build-system/integration-test/test-projects/testDependency/build.gradle
@@ -0,0 +1,25 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+}
+
+dependencies {
+ compile 'com.google.guava:guava:17.0'
+
+ // this is added by the test to control the version.
+ //androidTestCompile 'com.google.guava:guava:x.y'
+}
diff --git a/base/build-system/integration-test/test-projects/testDependency/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/testDependency/src/androidTest/java/com/android/tests/basic/MainTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/testDependency/src/androidTest/java/com/android/tests/basic/MainTest.java
rename to build-system/integration-test/test-projects/testDependency/src/androidTest/java/com/android/tests/basic/MainTest.java
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/testDependency/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/testDependency/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/testDependency/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/testDependency/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/testDependency/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/testDependency/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/testDependency/src/main/res/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/testDependency/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/testDependency/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/testDependency/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/testDependency/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/testDependency/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/testDependency/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/simpleMicroApp/wear/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/testDependency/src/main/res/values/strings.xml
diff --git a/build-system/integration-test/test-projects/testWithDep/build.gradle b/build-system/integration-test/test-projects/testWithDep/build.gradle
new file mode 100644
index 0000000..32c5284
--- /dev/null
+++ b/build-system/integration-test/test-projects/testWithDep/build.gradle
@@ -0,0 +1,15 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+dependencies {
+ androidTestCompile 'com.google.guava:guava:17.0'
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/build-system/integration-test/test-projects/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java b/build-system/integration-test/test-projects/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..0fe22a4
--- /dev/null
+++ b/build-system/integration-test/test-projects/testWithDep/src/androidTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,47 @@
+package com.android.tests.basic;
+
+import com.google.common.collect.ImmutableList;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private ImmutableList<TextView> mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ // Wrapped in an immutable list from guava, to check the dependency worked.
+ mTextView = ImmutableList.of((TextView) a.findViewById(R.id.text));
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ assertEquals(1, mTextView.size());
+ assertNotNull(mTextView.get(0));
+ }
+}
+
diff --git a/base/build-system/integration-test/test-projects/maxSdkVersion/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/testWithDep/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/maxSdkVersion/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/testWithDep/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/migrated/assets/notice.txt b/build-system/integration-test/test-projects/testWithDep/src/main/assets/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/assets/notice.txt
rename to build-system/integration-test/test-projects/testWithDep/src/main/assets/notice.txt
diff --git a/base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/java/com/android/tests/basic/Main.java b/build-system/integration-test/test-projects/testWithDep/src/main/java/com/android/tests/basic/Main.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/splitAwareSeparateTestModule/app/src/main/java/com/android/tests/basic/Main.java
rename to build-system/integration-test/test-projects/testWithDep/src/main/java/com/android/tests/basic/Main.java
diff --git a/base/layoutlib-api/sample/testproject/res/drawable-mdpi/icon.png b/build-system/integration-test/test-projects/testWithDep/src/main/res/drawable/icon.png
similarity index 100%
rename from base/layoutlib-api/sample/testproject/res/drawable-mdpi/icon.png
rename to build-system/integration-test/test-projects/testWithDep/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/testWithDep/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/testWithDep/src/main/res/layout/main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/testWithDep/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/testWithDep/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/migrated/res/raw/notice.txt b/build-system/integration-test/test-projects/testWithDep/src/main/res/raw/notice.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/migrated/res/raw/notice.txt
rename to build-system/integration-test/test-projects/testWithDep/src/main/res/raw/notice.txt
diff --git a/base/build-system/integration-test/test-projects/testDependency/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/testWithDep/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/testDependency/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/testWithDep/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/release/res/values/strings.xml b/build-system/integration-test/test-projects/testWithDep/src/release/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/separateTestModuleWithDependencies/app/src/release/res/values/strings.xml
rename to build-system/integration-test/test-projects/testWithDep/src/release/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/README.txt b/build-system/integration-test/test-projects/tictactoe/README.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/README.txt
rename to build-system/integration-test/test-projects/tictactoe/README.txt
diff --git a/build-system/integration-test/test-projects/tictactoe/app/build.gradle b/build-system/integration-test/test-projects/tictactoe/app/build.gradle
new file mode 100644
index 0000000..fabf88b
--- /dev/null
+++ b/build-system/integration-test/test-projects/tictactoe/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+}
diff --git a/base/build-system/integration-test/test-projects/tictactoe/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/tictactoe/app/src/main/AndroidManifest.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/tictactoe/app/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/app/src/main/java/com/example/android/tictactoe/MainActivity.java b/build-system/integration-test/test-projects/tictactoe/app/src/main/java/com/example/android/tictactoe/MainActivity.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/app/src/main/java/com/example/android/tictactoe/MainActivity.java
rename to build-system/integration-test/test-projects/tictactoe/app/src/main/java/com/example/android/tictactoe/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/tictactoe/app/src/main/res/drawable/icon.png b/build-system/integration-test/test-projects/tictactoe/app/src/main/res/drawable/icon.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/app/src/main/res/drawable/icon.png
rename to build-system/integration-test/test-projects/tictactoe/app/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/tictactoe/app/src/main/res/layout/main.xml b/build-system/integration-test/test-projects/tictactoe/app/src/main/res/layout/main.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/app/src/main/res/layout/main.xml
rename to build-system/integration-test/test-projects/tictactoe/app/src/main/res/layout/main.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/tictactoe/app/src/main/res/values/strings.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/tictactoe/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/build.gradle b/build-system/integration-test/test-projects/tictactoe/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/build.gradle
rename to build-system/integration-test/test-projects/tictactoe/build.gradle
diff --git a/build-system/integration-test/test-projects/tictactoe/lib/build.gradle b/build-system/integration-test/test-projects/tictactoe/lib/build.gradle
new file mode 100644
index 0000000..970656b
--- /dev/null
+++ b/build-system/integration-test/test-projects/tictactoe/lib/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 3
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/tictactoe/lib/src/main/AndroidManifest.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameActivity.java b/build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameActivity.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameActivity.java
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameActivity.java
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameView.java b/build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameView.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameView.java
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameView.java
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_bg.9.png b/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_bg.9.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_bg.9.png
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_bg.9.png
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_circle.png b/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_circle.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_circle.png
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_circle.png
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_cross.png b/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_cross.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_cross.png
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/res/drawable/lib_cross.png
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout-land/lib_game.xml b/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout-land/lib_game.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout-land/lib_game.xml
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout-land/lib_game.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout/lib_game.xml b/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout/lib_game.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout/lib_game.xml
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/res/layout/lib_game.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/values/strings.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/tictactoe/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/tictactoe/settings.gradle b/build-system/integration-test/test-projects/tictactoe/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/tictactoe/settings.gradle
rename to build-system/integration-test/test-projects/tictactoe/settings.gradle
diff --git a/build-system/integration-test/test-projects/unitTesting/build.gradle b/build-system/integration-test/test-projects/unitTesting/build.gradle
new file mode 100644
index 0000000..f57e30f
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTesting/build.gradle
@@ -0,0 +1,25 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
+ compileSdkVersion rootProject.java6BasedSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testOptions {
+ unitTests.all {
+ systemProperty 'foo', 'bar'
+ }
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.9.5'
+ testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
+ testCompile 'commons-logging:commons-logging:1.1.1'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTesting/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTesting/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/Foo.java b/build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/Foo.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/Foo.java
rename to build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/Foo.java
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/MainActivity.java
rename to build-system/integration-test/test-projects/unitTesting/src/main/java/com/android/tests/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/unitTesting/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/unitTesting/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/main/resources/prod_resource_file.txt b/build-system/integration-test/test-projects/unitTesting/src/main/resources/prod_resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/main/resources/prod_resource_file.txt
rename to build-system/integration-test/test-projects/unitTesting/src/main/resources/prod_resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/NonStandardName.java b/build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/NonStandardName.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/NonStandardName.java
rename to build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/NonStandardName.java
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTesting/src/test/java/com/android/tests/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTesting/src/test/resources/resource_file.txt b/build-system/integration-test/test-projects/unitTesting/src/test/resources/resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTesting/src/test/resources/resource_file.txt
rename to build-system/integration-test/test-projects/unitTesting/src/test/resources/resource_file.txt
diff --git a/build-system/integration-test/test-projects/unitTestingBuildTypes/build.gradle b/build-system/integration-test/test-projects/unitTestingBuildTypes/build.gradle
new file mode 100644
index 0000000..129338f
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingBuildTypes/build.gradle
@@ -0,0 +1,24 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
+ compileSdkVersion rootProject.java6BasedSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ buildTypes {
+ buildTypeWithResource
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+ testCompile "org.mockito:mockito-core:1.9.5"
+
+ testDebugCompile 'com.google.guava:guava:17.0'
+}
+
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/java/com/android/tests/DebugOnlyClass.java b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/java/com/android/tests/DebugOnlyClass.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/java/com/android/tests/DebugOnlyClass.java
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/debug/java/com/android/tests/DebugOnlyClass.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/java/com/android/tests/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/java/com/android/tests/MainActivity.java
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/java/com/android/tests/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/resources/prod_resource_file.txt b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/resources/prod_resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/resources/prod_resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/main/resources/prod_resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/java/com/android/tests/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/resources/resource_file.txt b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/resources/resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/resources/resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/testBuildTypeWithResource/resources/resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testDebug/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testDebug/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testDebug/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/testDebug/java/com/android/tests/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testRelease/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testRelease/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingBuildTypes/src/testRelease/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingBuildTypes/src/testRelease/java/com/android/tests/UnitTest.java
diff --git a/build-system/integration-test/test-projects/unitTestingComplexProject/app/build.gradle b/build-system/integration-test/test-projects/unitTestingComplexProject/app/build.gradle
new file mode 100644
index 0000000..e71ac90
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingComplexProject/app/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+apply from: "../../commonLocalRepo.gradle"
+
+android {
+ // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
+ compileSdkVersion rootProject.java6BasedSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testOptions {
+ unitTests.all {
+ systemProperty 'foo', 'bar'
+ }
+ }
+}
+
+dependencies {
+ compile project(':lib')
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.9.5'
+ testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
+ testCompile 'commons-logging:commons-logging:1.1.1'
+}
\ No newline at end of file
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/Foo.java b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/Foo.java
new file mode 100644
index 0000000..ad531a5
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/Foo.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tests;
+
+import com.android.tests.lib.LibFoo;
+
+public class Foo {
+ public String foo() {
+ useRClass();
+ return "production code";
+ }
+
+ public int useRClass() {
+ return R.string.app_name;
+ }
+
+ public String callLibFoo() {
+ LibFoo libFoo = new LibFoo();
+ return libFoo.foo();
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/MainActivity.java
rename to build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/java/com/android/tests/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/resources/prod_resource_file.txt b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/resources/prod_resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/resources/prod_resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingComplexProject/app/src/main/resources/prod_resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/java/com/android/tests/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/resources/resource_file.txt b/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/resources/resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/resources/resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingComplexProject/app/src/test/resources/resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/build.gradle b/build-system/integration-test/test-projects/unitTestingComplexProject/build.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/build.gradle
rename to build-system/integration-test/test-projects/unitTestingComplexProject/build.gradle
diff --git a/build-system/integration-test/test-projects/unitTestingComplexProject/lib/build.gradle b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/build.gradle
new file mode 100644
index 0000000..0df7146
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.library'
+
+apply from: "../../commonLocalRepo.gradle"
+
+android {
+ // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
+ compileSdkVersion rootProject.java6BasedSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testOptions {
+ unitTests.all {
+ systemProperty 'foo', 'bar'
+ }
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.9.5'
+ testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
+ testCompile 'commons-logging:commons-logging:1.1.1'
+}
+
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/AndroidManifest.xml
diff --git a/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/java/com/android/tests/lib/LibFoo.java b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/java/com/android/tests/lib/LibFoo.java
new file mode 100644
index 0000000..2e8273d
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/java/com/android/tests/lib/LibFoo.java
@@ -0,0 +1,29 @@
+/*
+ * 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.tests.lib;
+
+public class LibFoo {
+ public String foo() {
+ useRClass();
+ return "library code";
+ }
+
+ public int useRClass() {
+ // Make sure R class is in the classpath.
+ return R.string.app_name;
+ }
+}
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/resources/lib_prod_resource_file.txt b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/resources/lib_prod_resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/resources/lib_prod_resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/main/resources/lib_prod_resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/java/com/android/tests/lib/UnitTest.java b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/java/com/android/tests/lib/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/java/com/android/tests/lib/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/java/com/android/tests/lib/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/resources/lib_resource_file.txt b/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/resources/lib_resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/resources/lib_resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingComplexProject/lib/src/test/resources/lib_resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTestingComplexProject/settings.gradle b/build-system/integration-test/test-projects/unitTestingComplexProject/settings.gradle
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingComplexProject/settings.gradle
rename to build-system/integration-test/test-projects/unitTestingComplexProject/settings.gradle
diff --git a/build-system/integration-test/test-projects/unitTestingDefaultValues/build.gradle b/build-system/integration-test/test-projects/unitTestingDefaultValues/build.gradle
new file mode 100644
index 0000000..e96d58f
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingDefaultValues/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
+ compileSdkVersion rootProject.java6BasedSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+ testCompile "org.mockito:mockito-core:1.9.5"
+}
+
diff --git a/base/build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/java/com/android/tests/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/java/com/android/tests/MainActivity.java
rename to build-system/integration-test/test-projects/unitTestingDefaultValues/src/main/java/com/android/tests/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingDefaultValues/src/test/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTestingDefaultValues/src/test/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingDefaultValues/src/test/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingDefaultValues/src/test/java/com/android/tests/UnitTest.java
diff --git a/build-system/integration-test/test-projects/unitTestingFlavors/build.gradle b/build-system/integration-test/test-projects/unitTestingFlavors/build.gradle
new file mode 100644
index 0000000..5310e43
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingFlavors/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.java6BasedSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ flavorDimensions "buildStatus", "testStatus"
+
+ productFlavors {
+ builds {
+ dimension "buildStatus"
+ }
+
+ doesntBuild {
+ dimension "buildStatus"
+ }
+
+ passes {
+ dimension "testStatus"
+ }
+
+ fails {
+ dimension "testStatus"
+ }
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+ testCompile "org.mockito:mockito-core:1.9.5"
+}
+
diff --git a/build-system/integration-test/test-projects/unitTestingFlavors/src/doesntBuild/java/com/android/tests/Broken.java b/build-system/integration-test/test-projects/unitTestingFlavors/src/doesntBuild/java/com/android/tests/Broken.java
new file mode 100644
index 0000000..69c4db2
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingFlavors/src/doesntBuild/java/com/android/tests/Broken.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests;
+
+public class Broken {
+}
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTestingFlavors/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingFlavors/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTestingFlavors/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/Foo.java b/build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/Foo.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/Foo.java
rename to build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/Foo.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/MainActivity.java
rename to build-system/integration-test/test-projects/unitTestingFlavors/src/main/java/com/android/tests/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/passes/java/com/android/tests/ClassThatBuilds.java b/build-system/integration-test/test-projects/unitTestingFlavors/src/passes/java/com/android/tests/ClassThatBuilds.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingFlavors/src/passes/java/com/android/tests/ClassThatBuilds.java
rename to build-system/integration-test/test-projects/unitTestingFlavors/src/passes/java/com/android/tests/ClassThatBuilds.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/test/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTestingFlavors/src/test/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingFlavors/src/test/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingFlavors/src/test/java/com/android/tests/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/testFails/java/com/android/tests/FailingTest.java b/build-system/integration-test/test-projects/unitTestingFlavors/src/testFails/java/com/android/tests/FailingTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingFlavors/src/testFails/java/com/android/tests/FailingTest.java
rename to build-system/integration-test/test-projects/unitTestingFlavors/src/testFails/java/com/android/tests/FailingTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingFlavors/src/testPasses/java/com/android/tests/PassingTest.java b/build-system/integration-test/test-projects/unitTestingFlavors/src/testPasses/java/com/android/tests/PassingTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingFlavors/src/testPasses/java/com/android/tests/PassingTest.java
rename to build-system/integration-test/test-projects/unitTestingFlavors/src/testPasses/java/com/android/tests/PassingTest.java
diff --git a/build-system/integration-test/test-projects/unitTestingLibraryModules/build.gradle b/build-system/integration-test/test-projects/unitTestingLibraryModules/build.gradle
new file mode 100644
index 0000000..5fbe75a
--- /dev/null
+++ b/build-system/integration-test/test-projects/unitTestingLibraryModules/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.library'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ // We need an android.jar that contains Java 6 bytecode, since Jenkins runs on Java 6.
+ compileSdkVersion rootProject.java6BasedSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ testOptions {
+ unitTests.all {
+ systemProperty 'foo', 'bar'
+ }
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.9.5'
+ testCompile 'org.jdeferred:jdeferred-android-aar:1.2.3'
+ testCompile 'commons-logging:commons-logging:1.1.1'
+}
+
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/Foo.java b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/Foo.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/Foo.java
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/Foo.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/MainActivity.java b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/MainActivity.java
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/java/com/android/tests/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/resources/prod_resource_file.txt b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/resources/prod_resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/resources/prod_resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/main/resources/prod_resource_file.txt
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/NonStandardName.java b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/NonStandardName.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/NonStandardName.java
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/NonStandardName.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/UnitTest.java b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/UnitTest.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/UnitTest.java
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/java/com/android/tests/UnitTest.java
diff --git a/base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/resources/resource_file.txt b/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/resources/resource_file.txt
similarity index 100%
rename from base/build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/resources/resource_file.txt
rename to build-system/integration-test/test-projects/unitTestingLibraryModules/src/test/resources/resource_file.txt
diff --git a/build-system/integration-test/test-projects/vectorDrawables/build.gradle b/build-system/integration-test/test-projects/vectorDrawables/build.gradle
new file mode 100644
index 0000000..5babf26
--- /dev/null
+++ b/build-system/integration-test/test-projects/vectorDrawables/build.gradle
@@ -0,0 +1,22 @@
+apply from: "../commonHeader.gradle"
+buildscript { apply from: "../commonBuildScript.gradle", to: buildscript }
+
+apply plugin: 'com.android.application'
+
+apply from: "../commonLocalRepo.gradle"
+
+android {
+ compileSdkVersion rootProject.latestCompileSdk
+ buildToolsVersion = rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion 19
+ if (project.properties['checkDefaultDensities'] == null) {
+ generatedDensities = ["hdpi"]
+ generatedDensities += "xhdpi"
+ }
+ }
+
+ // Don't modify files when merging.
+ aaptOptions.cruncherEnabled = false
+}
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/AndroidManifest.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/AndroidManifest.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/AndroidManifest.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/AndroidManifest.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/java/com/example/bendowski/vectors/MainActivity.java b/build-system/integration-test/test-projects/vectorDrawables/src/main/java/com/example/bendowski/vectors/MainActivity.java
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/java/com/example/bendowski/vectors/MainActivity.java
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/java/com/example/bendowski/vectors/MainActivity.java
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-fr/french_heart.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-fr/french_heart.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-fr/french_heart.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-fr/french_heart.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-hdpi/special_heart.png b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-hdpi/special_heart.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-hdpi/special_heart.png
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-hdpi/special_heart.png
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v16/modern_heart.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v16/modern_heart.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v16/modern_heart.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v16/modern_heart.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v22/no_need.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v22/no_need.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v22/no_need.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable-v22/no_need.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/heart.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/heart.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/heart.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/heart.xml
diff --git a/base/sdk-common/src/test/resources/testData/assets/dupSet/assets1/icon.png b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/dupSet/assets1/icon.png
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/special_heart.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/special_heart.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/special_heart.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/drawable/special_heart.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/layout/activity_main.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/layout/activity_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/layout/activity_main.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/layout/activity_main.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/menu/menu_main.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/menu/menu_main.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/menu/menu_main.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/menu/menu_main.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/dimens.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/dimens.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/dimens.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/dimens.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/strings.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/strings.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/strings.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/strings.xml
diff --git a/base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/styles.xml b/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/styles.xml
similarity index 100%
rename from base/build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/styles.xml
rename to build-system/integration-test/test-projects/vectorDrawables/src/main/res/values/styles.xml
diff --git a/base/build-system/manifest-merger/.classpath b/build-system/manifest-merger/.classpath
similarity index 100%
rename from base/build-system/manifest-merger/.classpath
rename to build-system/manifest-merger/.classpath
diff --git a/base/build-system/manifest-merger/.gitignore b/build-system/manifest-merger/.gitignore
similarity index 100%
rename from base/build-system/manifest-merger/.gitignore
rename to build-system/manifest-merger/.gitignore
diff --git a/base/build-system/manifest-merger/.project b/build-system/manifest-merger/.project
similarity index 100%
rename from base/build-system/manifest-merger/.project
rename to build-system/manifest-merger/.project
diff --git a/base/build-system/manifest-merger/.settings/org.eclipse.jdt.core.prefs b/build-system/manifest-merger/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/build-system/manifest-merger/.settings/org.eclipse.jdt.core.prefs
rename to build-system/manifest-merger/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/build-system/manifest-merger/NOTICE b/build-system/manifest-merger/NOTICE
similarity index 100%
copy from base/build-system/manifest-merger/NOTICE
copy to build-system/manifest-merger/NOTICE
diff --git a/base/build-system/manifest-merger/build.gradle b/build-system/manifest-merger/build.gradle
similarity index 100%
rename from base/build-system/manifest-merger/build.gradle
rename to build-system/manifest-merger/build.gradle
diff --git a/base/build-system/manifest-merger/etc/manifmerger b/build-system/manifest-merger/etc/manifmerger
similarity index 100%
rename from base/build-system/manifest-merger/etc/manifmerger
rename to build-system/manifest-merger/etc/manifmerger
diff --git a/build-system/manifest-merger/manifest-merger-base.iml b/build-system/manifest-merger/manifest-merger-base.iml
new file mode 100644
index 0000000..7a5aad8
--- /dev/null
+++ b/build-system/manifest-merger/manifest-merger-base.iml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="library" name="gson" level="project" />
+ <orderEntry type="module" module-name="sdk-common-base" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/base/build-system/manifest-merger/manifest-merger.iml b/build-system/manifest-merger/manifest-merger.iml
similarity index 100%
rename from base/build-system/manifest-merger/manifest-merger.iml
rename to build-system/manifest-merger/manifest-merger.iml
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java
new file mode 100644
index 0000000..3fc2de0
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ActionRecorder.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.XmlNode.NodeKey;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Records all the actions taken by the merging tool.
+ * <p>
+ * Each action generates at least one {@link com.android.manifmerger.Actions.Record}
+ * containing enough information to generate a machine or human readable report.
+ * <p>
+ *
+ * The records are not organized in a temporal structure as the merging tool takes such decisions
+ * but are keyed by xml elements and attributes. For each node (elements or attributes), a linked
+ * list of actions that happened to the node is recorded to display all decisions that were made
+ * for that particular node.
+ * <p>
+ *
+ * This structure will permit displaying logs with co-located decisions records for each element,
+ * for instance :
+ * <pre>
+ * activity:com.foo.bar.MyApp
+ * Added from manifest.xml:31
+ * Rejected from lib1_manifest.xml:65
+ * </pre>
+ *
+ * <p>
+ * Each record for a node (element or attribute) will contain the following metadata :
+ * <p>
+ *
+ * <ul>
+ * <li>{@link com.android.manifmerger.Actions.ActionType} to identify whether the action
+ * applies to an attribute or an element.</li>
+ * <li>{@link com.android.ide.common.blame.SourceFilePosition} to identify the source xml
+ * location for the node.</li>
+ * </ul>
+ *
+ * <p>
+ * Elements will also contain:
+ * <ul>
+ * <li>Element name : a name composed of the element type and its key.</li>
+ * <li>{@link NodeOperationType} the highest priority tool annotation justifying the merging
+ * tool decision.</li>
+ * </ul>
+ *
+ * <p>
+ * While attributes will have:
+ * <ul>
+ * <li>element name</li>
+ * <li>attribute name : the namespace aware xml name</li>
+ * <li>{@link AttributeOperationType} the highest priority annotation justifying the merging
+ * tool decision.</li>
+ * </ul>
+ */
+public class ActionRecorder {
+
+ /**
+ * Defines all the records for the merging tool activity, indexed by element name+key. Iterator
+ * should be ordered by the key insertion order.
+ *
+ * <p>This is not a concurrent map, so we will need to guard multi-threaded access when
+ * adding/removing elements.
+ */
+ @GuardedBy("this")
+ @NonNull
+ private final Map<NodeKey, Actions.DecisionTreeRecord> mRecords =
+ new LinkedHashMap<NodeKey, Actions.DecisionTreeRecord>();
+
+ /**
+ * When the first xml file is loaded, there is nothing to merge with, however, each xml element
+ * and attribute added to the initial merged file need to be recorded.
+ *
+ * @param xmlElement xml element added to the initial merged document.
+ */
+ synchronized void recordDefaultNodeAction(@NonNull XmlElement xmlElement) {
+ if (!mRecords.containsKey(xmlElement.getOriginalId())) {
+ recordNodeAction(xmlElement, Actions.ActionType.ADDED);
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ AttributeOperationType attributeOperation = xmlElement
+ .getAttributeOperationType(xmlAttribute.getName());
+ recordAttributeAction(
+ xmlAttribute, Actions.ActionType.ADDED,
+ attributeOperation);
+ }
+ for (XmlElement childNode : xmlElement.getMergeableElements()) {
+ recordDefaultNodeAction(childNode);
+ }
+ }
+ }
+
+ /**
+ * Record a node that was added due to an implicit presence in earlier SDK release but requires
+ * an explicit declaration in the application targeted SDK.
+ * @param xmlElement the implied element that was added to the resulting xml.
+ * @param reason optional contextual information whey the implied element was added.
+ */
+ synchronized void recordImpliedNodeAction(@NonNull XmlElement xmlElement, @Nullable String reason) {
+ NodeKey storageKey = xmlElement.getOriginalId();
+ Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
+ if (nodeDecisionTree == null) {
+ nodeDecisionTree = new Actions.DecisionTreeRecord();
+ mRecords.put(storageKey, nodeDecisionTree);
+ }
+ Actions.NodeRecord record = new Actions.NodeRecord(Actions.ActionType.IMPLIED,
+ new SourceFilePosition(
+ xmlElement.getDocument().getSourceFile(),
+ xmlElement.getDocument().getRootNode().getPosition()),
+ xmlElement.getOriginalId(),
+ reason,
+ xmlElement.getOperationType()
+ );
+ nodeDecisionTree.addNodeRecord(record);
+ }
+
+ /**
+ * Record a node action taken by the merging tool.
+ *
+ * @param xmlElement the action's target xml element
+ * @param actionType the action's type
+ */
+ synchronized void recordNodeAction(
+ @NonNull XmlElement xmlElement,
+ @NonNull Actions.ActionType actionType) {
+ recordNodeAction(xmlElement, actionType, xmlElement);
+ }
+
+ /**
+ * Record a node action taken by the merging tool.
+ *
+ * @param mergedElement the merged xml element
+ * @param actionType the action's type
+ * @param targetElement the action's target when the action is rejected or replaced, it
+ * indicates what is the element being rejected or replaced.
+ */
+ synchronized void recordNodeAction(
+ @NonNull XmlElement mergedElement,
+ @NonNull Actions.ActionType actionType,
+ @NonNull XmlElement targetElement) {
+
+ Actions.NodeRecord record = new Actions.NodeRecord(actionType,
+ new SourceFilePosition(
+ targetElement.getDocument().getSourceFile(),
+ targetElement.getPosition()),
+ targetElement.getOriginalId(),
+ null, /* reason */
+ mergedElement.getOperationType()
+ );
+ recordNodeAction(mergedElement, record);
+ }
+
+ /**
+ * Records a {@link com.android.manifmerger.Actions.NodeRecord} action on a xml element.
+ * @param mergedElement the target element of the action.
+ * @param nodeRecord the record of the action.
+ */
+ synchronized void recordNodeAction(
+ @NonNull XmlElement mergedElement,
+ @NonNull Actions.NodeRecord nodeRecord) {
+
+ NodeKey storageKey = mergedElement.getOriginalId();
+ Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
+ if (nodeDecisionTree == null) {
+ nodeDecisionTree = new Actions.DecisionTreeRecord();
+ mRecords.put(storageKey, nodeDecisionTree);
+ }
+ nodeDecisionTree.addNodeRecord(nodeRecord);
+ }
+
+ /**
+ * Records an attribute action taken by the merging tool
+ *
+ * @param attribute the attribute in question.
+ * @param actionType the action's type
+ * @param attributeOperationType the original tool annotation leading to the merging tool
+ * decision.
+ */
+ synchronized void recordAttributeAction(
+ @NonNull XmlAttribute attribute,
+ @NonNull Actions.ActionType actionType,
+ @Nullable AttributeOperationType attributeOperationType) {
+
+ recordAttributeAction(
+ attribute, attribute.getPosition(), actionType, attributeOperationType);
+ }
+
+ /**
+ * Records an attribute action taken by the merging tool
+ *
+ * @param attribute the attribute in question.
+ * @param attributePosition the attribute's position.
+ * @param actionType the action's type
+ * @param attributeOperationType the original tool annotation leading to the merging tool
+ * decision.
+ */
+ synchronized void recordAttributeAction(
+ @NonNull XmlAttribute attribute,
+ @NonNull SourcePosition attributePosition,
+ @NonNull Actions.ActionType actionType,
+ @Nullable AttributeOperationType attributeOperationType) {
+
+ XmlElement originElement = attribute.getOwnerElement();
+ Actions.AttributeRecord attributeRecord = new Actions.AttributeRecord(
+ actionType,
+ new SourceFilePosition(
+ originElement.getDocument().getSourceFile(),
+ attributePosition),
+ attribute.getOriginalId(),
+ null, /* reason */
+ attributeOperationType
+ );
+ recordAttributeAction(attribute, attributeRecord);
+ }
+
+ /**
+ * Record a {@link com.android.manifmerger.Actions.AttributeRecord} action for an attribute of
+ * an xml element.
+ * @param attribute the attribute in question.
+ * @param attributeRecord the record of the action.
+ */
+ synchronized void recordAttributeAction(
+ @NonNull XmlAttribute attribute,
+ @NonNull Actions.AttributeRecord attributeRecord) {
+
+ List<Actions.AttributeRecord> attributeRecords = getAttributeRecords(attribute);
+ attributeRecords.add(attributeRecord);
+ }
+
+ /**
+ * Records when a default value that should be merged was rejected due to a tools:replace
+ * annotation.
+ *
+ * @param attribute the attribute which default value was ignored.
+ * @param implicitAttributeOwner the element owning the implicit default value.
+ */
+ synchronized void recordImplicitRejection(
+ @NonNull XmlAttribute attribute,
+ @NonNull XmlElement implicitAttributeOwner) {
+
+ List<Actions.AttributeRecord> attributeRecords = getAttributeRecords(attribute);
+ Actions.AttributeRecord attributeRecord = new Actions.AttributeRecord(
+ Actions.ActionType.REJECTED,
+ new SourceFilePosition(
+ implicitAttributeOwner.getDocument().getSourceFile(),
+ implicitAttributeOwner.getPosition()),
+ attribute.getOriginalId(),
+ null, /* reason */
+ AttributeOperationType.REPLACE
+ );
+ attributeRecords.add(attributeRecord);
+ }
+
+ /**
+ * Returns the record for an attribute creation event. The attribute is "created" when it is
+ * added for the first time into the resulting merged xml document.
+ */
+ @Nullable
+ synchronized Actions.AttributeRecord getAttributeCreationRecord(
+ @NonNull XmlAttribute attribute) {
+ for (Actions.AttributeRecord attributeRecord : getAttributeRecords(attribute)) {
+ if (attributeRecord.getActionType() == Actions.ActionType.ADDED) {
+ return attributeRecord;
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ private synchronized List<Actions.AttributeRecord> getAttributeRecords(@NonNull XmlAttribute attribute) {
+ XmlElement originElement = attribute.getOwnerElement();
+ NodeKey storageKey = originElement.getOriginalId();
+ @Nullable Actions.DecisionTreeRecord nodeDecisionTree = mRecords.get(storageKey);
+ // by now the node should have been added for this element.
+ Preconditions.checkNotNull(nodeDecisionTree, "No record for key [%s]", storageKey);
+ List<Actions.AttributeRecord> attributeRecords =
+ nodeDecisionTree.mAttributeRecords.get(attribute.getName());
+ if (attributeRecords == null) {
+ attributeRecords = new ArrayList<Actions.AttributeRecord>();
+ nodeDecisionTree.mAttributeRecords.put(attribute.getName(), attributeRecords);
+ }
+ return attributeRecords;
+ }
+
+ @NonNull
+ synchronized Actions build() {
+ return new Actions(ImmutableMap.copyOf(mRecords));
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java
new file mode 100644
index 0000000..c1dcf8d
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Actions.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.concurrency.Immutable;
+import com.android.ide.common.blame.MessageJsonSerializer;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.utils.ILogger;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.io.LineReader;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+import org.xml.sax.SAXException;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Contains all actions taken during a merging invocation.
+ */
+ at Immutable
+public class Actions {
+
+ // TODO: i18n
+ @VisibleForTesting
+ static final String HEADER = "-- Merging decision tree log ---\n";
+
+ // defines all the records for the merging tool activity, indexed by element name+key.
+ // iterator should be ordered by the key insertion order.
+ private final Map<XmlNode.NodeKey, DecisionTreeRecord> mRecords;
+
+ public Actions(Map<XmlNode.NodeKey, DecisionTreeRecord> records) {
+ mRecords = records;
+ }
+
+ /**
+ * Returns a {@link com.google.common.collect.ImmutableSet} of all the element's keys that have
+ * at least one {@link NodeRecord}.
+ */
+ @NonNull
+ public Set<XmlNode.NodeKey> getNodeKeys() {
+ return mRecords.keySet();
+ }
+
+ /**
+ * Returns an {@link ImmutableList} of {@link NodeRecord} for the element identified with the
+ * passed key.
+ */
+ @NonNull
+ public ImmutableList<NodeRecord> getNodeRecords(XmlNode.NodeKey key) {
+ return mRecords.containsKey(key)
+ ? mRecords.get(key).getNodeRecords()
+ : ImmutableList.<NodeRecord>of();
+ }
+
+ /**
+ * Returns a {@link ImmutableList} of all attributes names that have at least one record for
+ * the element identified with the passed key.
+ */
+ @NonNull
+ public ImmutableList<XmlNode.NodeName> getRecordedAttributeNames(XmlNode.NodeKey nodeKey) {
+ DecisionTreeRecord decisionTreeRecord = mRecords.get(nodeKey);
+ if (decisionTreeRecord == null) {
+ return ImmutableList.of();
+ }
+ return decisionTreeRecord.getAttributesRecords().keySet().asList();
+ }
+
+ /**
+ * Returns the {@link com.google.common.collect.ImmutableList} of {@link AttributeRecord} for
+ * the attribute identified by attributeName of the element identified by elementKey.
+ */
+ @NonNull
+ public ImmutableList<AttributeRecord> getAttributeRecords(XmlNode.NodeKey elementKey,
+ XmlNode.NodeName attributeName) {
+
+ DecisionTreeRecord decisionTreeRecord = mRecords.get(elementKey);
+ if (decisionTreeRecord == null) {
+ return ImmutableList.of();
+ }
+ return decisionTreeRecord.getAttributeRecords(attributeName);
+ }
+
+ /**
+ * Initial dump of the merging tool actions, need to be refined and spec'ed out properly.
+ * @param logger logger to log to at INFO level.
+ */
+ void log(@NonNull ILogger logger) {
+ logger.verbose(getLogs());
+ }
+
+ /**
+ * Dump merging tool actions to a text file.
+ * @param fileWriter the file to write all actions into.
+ * @throws IOException
+ */
+ void log(@NonNull FileWriter fileWriter) throws IOException {
+ fileWriter.append(getLogs());
+ }
+
+ private String getLogs() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(HEADER);
+ for (Map.Entry<XmlNode.NodeKey, Actions.DecisionTreeRecord> record : mRecords.entrySet()) {
+ stringBuilder.append(record.getKey()).append("\n");
+ for (Actions.NodeRecord nodeRecord : record.getValue().getNodeRecords()) {
+ nodeRecord.print(stringBuilder);
+ stringBuilder.append('\n');
+ }
+ for (Map.Entry<XmlNode.NodeName, List<Actions.AttributeRecord>> attributeRecords :
+ record.getValue().mAttributeRecords.entrySet()) {
+ stringBuilder.append('\t').append(attributeRecords.getKey()).append('\n');
+ for (Actions.AttributeRecord attributeRecord : attributeRecords.getValue()) {
+ stringBuilder.append("\t\t");
+ attributeRecord.print(stringBuilder);
+ stringBuilder.append('\n');
+ }
+ }
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Defines all possible actions taken from the merging tool for an xml element or attribute.
+ */
+ enum ActionType {
+ /**
+ * The element was added into the resulting merged manifest.
+ */
+ ADDED,
+ /**
+ * The element was injected from the merger invocation parameters.
+ */
+ INJECTED,
+ /**
+ * The element was merged with another element into the resulting merged manifest.
+ */
+ MERGED,
+ /**
+ * The element was rejected.
+ */
+ REJECTED,
+ /**
+ * The implied element was added was added when importing a library that expected the
+ * element to be present by default while targeted SDK requires its declaration.
+ */
+ IMPLIED,
+ }
+
+ /**
+ * Defines an abstract record contain common metadata for elements and attributes actions.
+ */
+ public abstract static class Record {
+
+ @NonNull protected final ActionType mActionType;
+ @NonNull protected final SourceFilePosition mActionLocation;
+ @NonNull protected final XmlNode.NodeKey mTargetId;
+ @Nullable protected final String mReason;
+
+ private Record(@NonNull ActionType actionType,
+ @NonNull SourceFilePosition actionLocation,
+ @NonNull XmlNode.NodeKey targetId,
+ @Nullable String reason) {
+ mActionType = Preconditions.checkNotNull(actionType);
+ mActionLocation = Preconditions.checkNotNull(actionLocation);
+ mTargetId = Preconditions.checkNotNull(targetId);
+ mReason = reason;
+ }
+
+ @NonNull
+ public ActionType getActionType() {
+ return mActionType;
+ }
+
+ @NonNull
+ public SourceFilePosition getActionLocation() {
+ return mActionLocation;
+ }
+
+ @NonNull
+ public XmlNode.NodeKey getTargetId() {
+ return mTargetId;
+ }
+
+ public void print(@NonNull StringBuilder stringBuilder) {
+ stringBuilder.append(mActionType)
+ .append(" from ")
+ .append(mActionLocation);
+ if (mReason != null) {
+ stringBuilder.append(" reason: ")
+ .append(mReason);
+ }
+ }
+ }
+
+ /**
+ * Defines a merging tool action for an xml element.
+ */
+ public static class NodeRecord extends Record {
+
+ @NonNull
+ private final NodeOperationType mNodeOperationType;
+
+ NodeRecord(@NonNull ActionType actionType,
+ @NonNull SourceFilePosition actionLocation,
+ @NonNull XmlNode.NodeKey targetId,
+ @Nullable String reason,
+ @NonNull NodeOperationType nodeOperationType) {
+ super(actionType, actionLocation, targetId, reason);
+ this.mNodeOperationType = Preconditions.checkNotNull(nodeOperationType);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "Id=" + mTargetId.toString() + " actionType=" + getActionType()
+ + " location=" + getActionLocation()
+ + " opType=" + mNodeOperationType;
+ }
+ }
+
+ /**
+ * Defines a merging tool action for an xml attribute
+ */
+ public static class AttributeRecord extends Record {
+
+ // first in wins which should be fine, the first
+ // operation type will be the highest priority one
+ @Nullable
+ private final AttributeOperationType mOperationType;
+
+ AttributeRecord(
+ @NonNull ActionType actionType,
+ @NonNull SourceFilePosition actionLocation,
+ @NonNull XmlNode.NodeKey targetId,
+ @Nullable String reason,
+ @Nullable AttributeOperationType operationType) {
+ super(actionType, actionLocation, targetId, reason);
+ this.mOperationType = operationType;
+ }
+
+ @Nullable
+ public AttributeOperationType getOperationType() {
+ return mOperationType;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this).add("Id", mTargetId)
+ .add("actionType=",getActionType())
+ .add("location", getActionLocation())
+ .add("opType", getOperationType()).toString();
+ }
+ }
+
+ @NonNull
+ public String persist() throws IOException {
+ //noinspection SpellCheckingInspection
+ GsonBuilder gson = new GsonBuilder().setPrettyPrinting();
+ gson.enableComplexMapKeySerialization();
+ MessageJsonSerializer.registerTypeAdapters(gson);
+ return gson.create().toJson(this);
+ }
+
+ @Nullable
+ public static Actions load(@NonNull InputStream inputStream) throws IOException {
+
+ return getGsonParser().fromJson(new InputStreamReader(inputStream), Actions.class);
+ }
+
+ private static class NodeNameDeserializer implements JsonDeserializer<XmlNode.NodeName> {
+
+ @Override
+ public XmlNode.NodeName deserialize(@NonNull JsonElement json, Type typeOfT,
+ @NonNull JsonDeserializationContext context) throws JsonParseException {
+ if (json.getAsJsonObject().get("mNamespaceURI") != null) {
+ return context.deserialize(json, XmlNode.NamespaceAwareName.class);
+ } else {
+ return context.deserialize(json, XmlNode.Name.class);
+ }
+ }
+ }
+
+ @Nullable
+ public static Actions load(String xml) {
+ return getGsonParser().fromJson(xml, Actions.class);
+ }
+
+ @SuppressWarnings("SpellCheckingInspection")
+ @NonNull
+ private static Gson getGsonParser() {
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.enableComplexMapKeySerialization();
+ gsonBuilder.registerTypeAdapter(XmlNode.NodeName.class, new NodeNameDeserializer());
+ MessageJsonSerializer.registerTypeAdapters(gsonBuilder);
+ return gsonBuilder.create();
+ }
+
+ public ImmutableMultimap<Integer, Record> getResultingSourceMapping(@NonNull XmlDocument xmlDocument)
+ throws ParserConfigurationException, SAXException, IOException {
+
+ SourceFile inMemory = SourceFile.UNKNOWN;
+
+ XmlDocument loadedWithLineNumbers = XmlLoader.load(
+ xmlDocument.getSelectors(),
+ xmlDocument.getSystemPropertyResolver(),
+ inMemory,
+ xmlDocument.prettyPrint(),
+ XmlDocument.Type.MAIN,
+ Optional.<String>absent() /* mainManifestPackageName */);
+
+ ImmutableMultimap.Builder<Integer, Record> mappingBuilder = ImmutableMultimap.builder();
+ for (XmlElement xmlElement : loadedWithLineNumbers.getRootNode().getMergeableElements()) {
+ parse(xmlElement, mappingBuilder);
+ }
+ return mappingBuilder.build();
+ }
+
+ private void parse(@NonNull XmlElement element,
+ @NonNull ImmutableMultimap.Builder<Integer, Record> mappings) {
+ DecisionTreeRecord decisionTreeRecord = mRecords.get(element.getId());
+ if (decisionTreeRecord != null) {
+ Actions.NodeRecord nodeRecord = findNodeRecord(decisionTreeRecord);
+ if (nodeRecord != null) {
+ mappings.put(element.getPosition().getStartLine(), nodeRecord);
+ }
+ for (XmlAttribute xmlAttribute : element.getAttributes()) {
+ Actions.AttributeRecord attributeRecord = findAttributeRecord(decisionTreeRecord,
+ xmlAttribute);
+ if (attributeRecord != null) {
+ mappings.put(xmlAttribute.getPosition().getStartLine(), attributeRecord);
+ }
+ }
+ }
+ for (XmlElement xmlElement : element.getMergeableElements()) {
+ parse(xmlElement, mappings);
+ }
+ }
+
+ @NonNull
+ public String blame(@NonNull XmlDocument xmlDocument)
+ throws IOException, SAXException, ParserConfigurationException {
+
+ ImmutableMultimap<Integer, Record> resultingSourceMapping =
+ getResultingSourceMapping(xmlDocument);
+ LineReader lineReader = new LineReader(
+ new StringReader(xmlDocument.prettyPrint()));
+
+ StringBuilder actualMappings = new StringBuilder();
+ String line;
+ int count = 0;
+ while ((line = lineReader.readLine()) != null) {
+ actualMappings.append(count + 1).append(line).append("\n");
+ if (resultingSourceMapping.containsKey(count)) {
+ for (Record record : resultingSourceMapping.get(count)) {
+ actualMappings.append(count + 1).append("-->")
+ .append(record.getActionLocation().toString())
+ .append("\n");
+ }
+ }
+ count++;
+ }
+ return actualMappings.toString();
+ }
+
+ @Nullable
+ private static Actions.NodeRecord findNodeRecord(@NonNull DecisionTreeRecord decisionTreeRecord) {
+ for (Actions.NodeRecord nodeRecord : decisionTreeRecord.getNodeRecords()) {
+ if (nodeRecord.getActionType() == Actions.ActionType.ADDED) {
+ return nodeRecord;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static Actions.AttributeRecord findAttributeRecord(
+ @NonNull DecisionTreeRecord decisionTreeRecord,
+ @NonNull XmlAttribute xmlAttribute) {
+ for (Actions.AttributeRecord attributeRecord : decisionTreeRecord
+ .getAttributeRecords(xmlAttribute.getName())) {
+ if (attributeRecord.getActionType() == Actions.ActionType.ADDED) {
+ return attributeRecord;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Internal structure on how {@link com.android.manifmerger.Actions.Record}s are kept for an
+ * xml element.
+ *
+ * Each xml element should have an associated DecisionTreeRecord which keeps a list of
+ * {@link com.android.manifmerger.Actions.NodeRecord} for all the node actions related
+ * to this xml element.
+ *
+ * It will also contain a map indexed by attribute name on all the attribute actions related
+ * to that particular attribute within the xml element.
+ *
+ */
+ static class DecisionTreeRecord {
+ // all other occurrences of the nodes decisions, in order of decisions.
+ private final List<NodeRecord> mNodeRecords = new ArrayList<NodeRecord>();
+
+ // all attributes decisions indexed by attribute name.
+ @NonNull
+ final Map<XmlNode.NodeName, List<AttributeRecord>> mAttributeRecords =
+ new HashMap<XmlNode.NodeName, List<AttributeRecord>>();
+
+ @NonNull
+ ImmutableList<NodeRecord> getNodeRecords() {
+ return ImmutableList.copyOf(mNodeRecords);
+ }
+
+ @NonNull
+ ImmutableMap<XmlNode.NodeName, List<AttributeRecord>> getAttributesRecords() {
+ return ImmutableMap.copyOf(mAttributeRecords);
+ }
+
+ DecisionTreeRecord() {
+ }
+
+ void addNodeRecord(@NonNull NodeRecord nodeRecord) {
+ mNodeRecords.add(Preconditions.checkNotNull(nodeRecord));
+ }
+
+ @NonNull
+ ImmutableList<AttributeRecord> getAttributeRecords(XmlNode.NodeName attributeName) {
+ List<AttributeRecord> attributeRecords = mAttributeRecords.get(attributeName);
+ return attributeRecords == null
+ ? ImmutableList.<AttributeRecord>of()
+ : ImmutableList.copyOf(attributeRecords);
+ }
+ }
+}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java
new file mode 100644
index 0000000..abd5fc6
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeModel.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Describes an attribute characteristics like if it supports smart package name replacement, has
+ * a default value and a validator for its values.
+ */
+class AttributeModel {
+
+ @NonNull private final XmlNode.NodeName mName;
+ private final boolean mIsPackageDependent;
+ @Nullable private final String mDefaultValue;
+ @Nullable private final Validator mOnReadValidator;
+ @Nullable private final Validator mOnWriteValidator;
+ @NonNull private final MergingPolicy mMergingPolicy;
+
+ /**
+ * Define a new attribute with specific characteristics.
+ *
+ * @param name name of the attribute, so far assumed to be in the
+ * {@link com.android.SdkConstants#ANDROID_URI} namespace.
+ * @param isPackageDependent true if the attribute support smart substitution of package name.
+ * @param defaultValue an optional default value.
+ * @param onReadValidator an optional validator to validate values against.
+ */
+ private AttributeModel(@NonNull XmlNode.NodeName name,
+ boolean isPackageDependent,
+ @Nullable String defaultValue,
+ @Nullable Validator onReadValidator,
+ @Nullable Validator onWriteValidator,
+ @NonNull MergingPolicy mergingPolicy) {
+ mName = name;
+ mIsPackageDependent = isPackageDependent;
+ mDefaultValue = defaultValue;
+ mOnReadValidator = onReadValidator;
+ mOnWriteValidator = onWriteValidator;
+ mMergingPolicy = mergingPolicy;
+ }
+
+ @NonNull
+ XmlNode.NodeName getName() {
+ return mName;
+ }
+
+ /**
+ * Return true if the attribute support smart substitution of partially fully qualified
+ * class names with package settings as provided by the manifest node's package attribute
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>}
+ *
+ * @return true if this attribute supports smart substitution or false if not.
+ */
+ boolean isPackageDependent() {
+ return mIsPackageDependent;
+ }
+
+ /**
+ * Returns the attribute's default value or null if none.
+ */
+ @Nullable
+ String getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /**
+ * Returns the attribute's {@link com.android.manifmerger.AttributeModel.Validator} to
+ * validate its value when read from xml files or null if no validation is necessary.
+ */
+ @Nullable
+ public Validator getOnReadValidator() {
+ return mOnReadValidator;
+ }
+
+ /**
+ * Returns the attribute's {@link com.android.manifmerger.AttributeModel.Validator} to
+ * validate its value when the merged file is about to be persisted.
+ */
+ @Nullable
+ public Validator getOnWriteValidator() {
+ return mOnWriteValidator;
+ }
+
+ /**
+ * Returns the {@link com.android.manifmerger.AttributeModel.MergingPolicy} for this
+ * attribute.
+ */
+ @NonNull
+ public MergingPolicy getMergingPolicy() {
+ return mMergingPolicy;
+ }
+
+ /**
+ * Creates a new {@link Builder} to describe an attribute.
+ * @param attributeName the to be described attribute name
+ */
+ @NonNull
+ static Builder newModel(String attributeName) {
+ return new Builder(attributeName);
+ }
+
+
+ static class Builder {
+
+ private final String mName;
+ private boolean mIsPackageDependent = false;
+ private String mDefaultValue;
+ private Validator mOnReadValidator;
+ private Validator mOnWriteValidator;
+ @NonNull
+ private MergingPolicy mMergingPolicy = STRICT_MERGING_POLICY;
+
+ Builder(String name) {
+ this.mName = name;
+ }
+
+ /**
+ * Sets the attribute support for smart substitution of partially fully qualified
+ * class names with package settings as provided by the manifest node's package attribute
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>}
+ */
+ @NonNull
+ Builder setIsPackageDependent() {
+ mIsPackageDependent = true;
+ return this;
+ }
+
+ /**
+ * Sets the attribute default value.
+ */
+ @NonNull
+ Builder setDefaultValue(String value) {
+ mDefaultValue = value;
+ return this;
+ }
+
+ /**
+ * Sets a {@link com.android.manifmerger.AttributeModel.Validator} to validate the
+ * attribute's values coming from xml files.
+ */
+ @NonNull
+ Builder setOnReadValidator(Validator validator) {
+ mOnReadValidator = validator;
+ return this;
+ }
+
+ /**
+ * Sets a {@link com.android.manifmerger.AttributeModel.Validator} to validate values
+ * before they are written to the final merged document.
+ */
+ @NonNull
+ Builder setOnWriteValidator(Validator validator) {
+ mOnWriteValidator = validator;
+ return this;
+ }
+
+ @NonNull
+ Builder setMergingPolicy(@NonNull MergingPolicy mergingPolicy) {
+ mMergingPolicy = mergingPolicy;
+ return this;
+ }
+
+ /**
+ * Build an immutable {@link com.android.manifmerger.AttributeModel}
+ */
+ @NonNull
+ AttributeModel build() {
+ return new AttributeModel(
+ XmlNode.fromXmlName("android:" + mName),
+ mIsPackageDependent,
+ mDefaultValue,
+ mOnReadValidator,
+ mOnWriteValidator,
+ mMergingPolicy);
+ }
+ }
+
+ /**
+ * Defines a merging policy between two attribute values. Example of merging policies can be
+ * strict when it is illegal to try to merge or override a value by another. Another example
+ * is a OR merging policy on boolean attribute values.
+ */
+ interface MergingPolicy {
+
+ /**
+ * Returns true if it should be attempted to merge this attribute value with
+ * the attribute default value when merging with a node that does not contain
+ * the attribute declaration.
+ */
+ boolean shouldMergeDefaultValues();
+
+ /**
+ * Merges the two attributes values and returns the merged value. If the values cannot be
+ * merged, return null.
+ */
+ @Nullable
+ String merge(@NonNull String higherPriority, @NonNull String lowerPriority);
+ }
+
+ /**
+ * Standard attribute value merging policy, generates an error unless both values are equal.
+ */
+ @NonNull
+ static final MergingPolicy STRICT_MERGING_POLICY = new MergingPolicy() {
+
+ @Override
+ public boolean shouldMergeDefaultValues() {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
+ // it's ok if the values are equal, otherwise it's not.
+ return higherPriority.equals(lowerPriority)
+ ? higherPriority
+ : null;
+ }
+ };
+
+ /**
+ * Boolean OR merging policy.
+ */
+ static final MergingPolicy OR_MERGING_POLICY = new MergingPolicy() {
+ @Override
+ public boolean shouldMergeDefaultValues() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
+ return Boolean.toString(BooleanValidator.isTrue(higherPriority) ||
+ BooleanValidator.isTrue(lowerPriority));
+ }
+ };
+
+ /**
+ * Merging policy that will return the higher priority value regardless of the lower priority
+ * value
+ */
+ static final MergingPolicy NO_MERGING_POLICY = new MergingPolicy() {
+
+ @Override
+ public boolean shouldMergeDefaultValues() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public String merge(@NonNull String higherPriority, @NonNull String lowerPriority) {
+ return higherPriority;
+ }
+ };
+
+ /**
+ * Decode a decimal or hexadecimal {@link String} into an {@link Integer}.
+ * String starting with 0 will be considered decimal, not octal.
+ */
+ private static int decodeDecOrHexString(@NonNull String s) {
+ long decodedValue = s.startsWith("0x") || s.startsWith("0X")
+ ? Long.decode(s)
+ : Long.parseLong(s);
+ if (decodedValue < 0xFFFFFFFFL) {
+ return (int) decodedValue;
+ } else {
+ throw new IllegalArgumentException("Value " + s + " too big for 32 bits.");
+ }
+ }
+
+ /**
+ * Validates an attribute value.
+ *
+ * The validator can be called when xml documents are read to ensure the xml file contains
+ * valid statements.
+ *
+ * This is a poor-mans replacement for not having a proper XML Schema do perform such
+ * validations.
+ */
+ interface Validator {
+
+ /**
+ * Validates a value, issuing a warning or error in case of failure through the passed
+ * merging report.
+ * @param mergingReport to report validation warnings or error
+ * @param attribute the attribute to validate.
+ * @param value the proposed or existing attribute value.
+ * @return true if the value is legal for this attribute.
+ */
+ boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute,
+ @NonNull String value);
+ }
+
+ /**
+ * Validates a boolean attribute type.
+ */
+ static class BooleanValidator implements Validator {
+
+ // TODO: check with @xav where to find the acceptable values by runtime.
+ private static final Pattern TRUE_PATTERN = Pattern.compile("true|True|TRUE");
+ private static final Pattern FALSE_PATTERN = Pattern.compile("false|False|FALSE");
+
+ private static boolean isTrue(@NonNull String value) {
+ return TRUE_PATTERN.matcher(value).matches();
+ }
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute,
+ @NonNull String value) {
+ boolean matches = TRUE_PATTERN.matcher(value).matches() ||
+ FALSE_PATTERN.matcher(value).matches();
+ if (!matches) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s has an illegal value=(%3$s), "
+ + "expected 'true' or 'false'",
+ attribute.getId(),
+ attribute.printPosition(),
+ value
+ )
+ );
+ }
+ return matches;
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} for verifying that a proposed
+ * value is part of the acceptable list of possible values.
+ */
+ static class MultiValueValidator implements Validator {
+
+ @NonNull
+ private final String[] multiValues;
+ @NonNull
+ private final String allValues;
+
+ MultiValueValidator(@NonNull String... multiValues) {
+ this.multiValues = multiValues;
+ allValues = Joiner.on(',').join(multiValues);
+ }
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ for (String multiValue : multiValues) {
+ if (multiValue.equals(value)) {
+ return true;
+ }
+ }
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Invalid value for attribute %1$s at %2$s, value=(%3$s), "
+ + "acceptable values are (%4$s)",
+ attribute.getId(),
+ attribute.printPosition(),
+ value,
+ allValues
+ )
+ );
+ return false;
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} for verifying that a proposed
+ * value is a numerical integer value.
+ */
+ static class IntegerValueValidator implements Validator {
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ try {
+ return Integer.parseInt(value) > 0;
+ } catch (NumberFormatException e) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s must be an integer, found %3$s",
+ attribute.getId(),
+ attribute.printPosition(),
+ value)
+ );
+ return false;
+ }
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} to validate that a string is
+ * a valid 32 bits hexadecimal representation.
+ */
+ static class Hexadecimal32Bits implements Validator {
+ protected static final Pattern PATTERN = Pattern.compile("0[xX]([0-9a-fA-F]+)");
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ Matcher matcher = PATTERN.matcher(value);
+ boolean valid = matcher.matches() && matcher.group(1).length() <= 8;
+ if (!valid) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s is not a valid hexadecimal 32 bit value,"
+ + " found %3$s",
+ attribute.getId(),
+ attribute.printPosition(),
+ value
+ ));
+ }
+ return valid;
+ }
+ }
+
+ /**
+ * A {@link com.android.manifmerger.AttributeModel.Validator} to validate that a string is
+ * a valid 32 positive hexadecimal representation with a minimum value requirement.
+ */
+ static class Hexadecimal32BitsWithMinimumValue extends Hexadecimal32Bits {
+
+ private final int mMinimumValue;
+
+ Hexadecimal32BitsWithMinimumValue(int minimumValue) {
+ mMinimumValue = minimumValue;
+ }
+
+ @Override
+ public boolean validates(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlAttribute attribute, @NonNull String value) {
+ boolean valid = super.validates(mergingReport, attribute, value);
+ if (valid) {
+ try {
+ Long decodedValue = Long.decode(value);
+ valid = decodedValue >= mMinimumValue && decodedValue < 0xFFFFFFFFL;
+ } catch(NumberFormatException e) {
+ valid = false;
+ }
+ if (!valid) {
+ attribute.addMessage(mergingReport, MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Attribute %1$s at %2$s is not a valid hexadecimal value,"
+ + " minimum is 0x%3$08X, maximum is 0x%4$08X, found %5$s",
+ attribute.getId(),
+ attribute.printPosition(),
+ mMinimumValue,
+ Integer.MAX_VALUE,
+ value
+ ));
+ }
+ return valid;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeOperationType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeOperationType.java
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeOperationType.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/AttributeOperationType.java
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ConvertibleName.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ConvertibleName.java
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ConvertibleName.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/ConvertibleName.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java
new file mode 100644
index 0000000..3f9f6da
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ElementsTrimmer.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.xml.AndroidManifest;
+
+import org.w3c.dom.Attr;
+
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+/**
+ * Trims the document from unwanted, repeated elements.
+ */
+public class ElementsTrimmer {
+
+ /**
+ * Trims unwanted, duplicated elements from the merged document.
+ * <p>
+ * Current trimmed elements are :
+ * <p>
+ * <ul>
+ * <li>uses-features with glEsVersion key</li>
+ * <ul>
+ * <li>The highest 1.x version element will be kept regardless of 'required' flag value</li>
+ * <li>If the above element is present and has a 'false' required flag, there can be at most
+ * one element of a lesser version with 'required' attribute set to true.</li>
+ * <li>The highest 2.x or superior element will be kept regardless of 'required' flag value
+ * </li>
+ * <li>If the above element is present and has a 'false' required flag, there can be at
+ * most one element of a lesser version (but higher than 2.0) with a 'required' attribute
+ * set to true.</li>
+ * </ul>
+ * </ul>
+ *
+ * @param xmlDocument the xml document to trim.
+ * @param mergingReport the report to log errors and actions.
+ */
+ public static void trim(
+ @NonNull XmlDocument xmlDocument,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ // I sort the glEsVersion declaration by value.
+ NavigableMap<Integer, XmlElement> glEsVersionDeclarations = new TreeMap<Integer, XmlElement>();
+
+ for (XmlElement childElement : xmlDocument.getRootNode().getMergeableElements()) {
+ if (childElement.getType().equals(ManifestModel.NodeTypes.USES_FEATURE)) {
+ Integer value = getGlEsVersion(childElement);
+ if (value != null) {
+ glEsVersionDeclarations.put(value, childElement);
+ }
+ }
+ }
+
+ // now eliminate all unwanted declarations, revert the sorted map, so we get the
+ // higher elements first.
+ glEsVersionDeclarations = glEsVersionDeclarations.descendingMap();
+ boolean doneWithAboveTwoTrue = false;
+ boolean doneWithAboveTwoFalse = false;
+ boolean doneWithBelowTwoTrue = false;
+ boolean doneWithBelowTwoFalse = false;
+ for (Map.Entry<Integer, XmlElement> glEsVersionDeclaration :
+ glEsVersionDeclarations.entrySet()) {
+
+ boolean removeElement;
+
+ Attr requiredAttribute = glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS(
+ ANDROID_URI, AndroidManifest.ATTRIBUTE_REQUIRED);
+
+ boolean isRequired = requiredAttribute == null ||
+ Boolean.parseBoolean(requiredAttribute.getValue());
+
+ if (glEsVersionDeclaration.getKey() < 0x20000) {
+ // version one.
+ removeElement = (doneWithBelowTwoFalse && doneWithBelowTwoTrue)
+ || (isRequired && doneWithBelowTwoTrue)
+ || (!isRequired && doneWithBelowTwoFalse);
+
+ if (!removeElement) {
+ doneWithBelowTwoFalse = true;
+ doneWithBelowTwoTrue = isRequired;
+ }
+ } else {
+ // version two or above.
+ removeElement = (doneWithAboveTwoFalse && doneWithAboveTwoTrue)
+ || (isRequired && doneWithAboveTwoTrue)
+ || (!isRequired && doneWithAboveTwoFalse);
+
+ if (!removeElement) {
+ doneWithAboveTwoFalse = true;
+ doneWithAboveTwoTrue = isRequired;
+ }
+ }
+ if (removeElement) {
+ // if the node only contains glEsVersion, then remove the entire node,
+ // if it also contains android:name, just remove the glEsVersion attribute
+ if (glEsVersionDeclaration.getValue().getXml().getAttributeNodeNS(ANDROID_URI,
+ SdkConstants.ATTR_NAME) != null) {
+ glEsVersionDeclaration.getValue().getXml().removeAttributeNS(ANDROID_URI,
+ AndroidManifest.ATTRIBUTE_GLESVERSION);
+ mergingReport.getActionRecorder().recordAttributeAction(
+ glEsVersionDeclaration.getValue().getAttribute(XmlNode.fromXmlName(
+ "android:" + AndroidManifest.ATTRIBUTE_GLESVERSION)).get(),
+ Actions.ActionType.REJECTED,
+ null /* attributeOperationType */);
+ } else {
+ xmlDocument.getRootNode().getXml().removeChild(
+ glEsVersionDeclaration.getValue().getXml());
+ mergingReport.getActionRecorder().recordNodeAction(
+ glEsVersionDeclaration.getValue(),
+ Actions.ActionType.REJECTED);
+
+ }
+ }
+
+ }
+
+ }
+
+ private static Integer getGlEsVersion(@NonNull XmlElement xmlElement) {
+ Attr glEsVersion = xmlElement.getXml()
+ .getAttributeNodeNS(ANDROID_URI, AndroidManifest.ATTRIBUTE_GLESVERSION);
+ if (glEsVersion == null) {
+ return null;
+ }
+ return getHexValue(glEsVersion);
+ }
+
+ private static Integer getHexValue(@NonNull Attr attribute) {
+ return Integer.decode(attribute.getValue());
+ }
+}
+
+
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ICallback.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ICallback.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ICallback.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/ICallback.java
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/KeyResolver.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/KeyResolver.java
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/KeyResolver.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/KeyResolver.java
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Main.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Main.java
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/Main.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/Main.java
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java
new file mode 100644
index 0000000..ff52a91
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java
@@ -0,0 +1,1282 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.PlaceholderHandler.APPLICATION_ID;
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+import static com.android.manifmerger.PlaceholderHandler.PACKAGE_NAME;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * merges android manifest files, idempotent.
+ */
+ at Immutable
+public class ManifestMerger2 {
+
+ static final String BOOTSTRAP_APPLICATION
+ = "com.android.tools.fd.runtime.BootstrapApplication";
+
+ @NonNull
+ private final File mManifestFile;
+
+ @NonNull
+ private final Map<String, Object> mPlaceHolderValues;
+
+ @NonNull
+ private final KeyBasedValueResolver<SystemProperty> mSystemPropertyResolver;
+
+ @NonNull
+ private final ILogger mLogger;
+ @NonNull
+ private final ImmutableList<Pair<String, File>> mLibraryFiles;
+ @NonNull
+ private final ImmutableList<File> mFlavorsAndBuildTypeFiles;
+ @NonNull
+ private final ImmutableList<Invoker.Feature> mOptionalFeatures;
+ @NonNull
+ private final MergeType mMergeType;
+ @NonNull
+ private final Optional<File> mReportFile;
+ @NonNull
+ private final FileStreamProvider mFileStreamProvider;
+
+ private ManifestMerger2(
+ @NonNull ILogger logger,
+ @NonNull File mainManifestFile,
+ @NonNull ImmutableList<Pair<String, File>> libraryFiles,
+ @NonNull ImmutableList<File> flavorsAndBuildTypeFiles,
+ @NonNull ImmutableList<Invoker.Feature> optionalFeatures,
+ @NonNull Map<String, Object> placeHolderValues,
+ @NonNull KeyBasedValueResolver<SystemProperty> systemPropertiesResolver,
+ @NonNull MergeType mergeType,
+ @NonNull Optional<File> reportFile,
+ @NonNull FileStreamProvider fileStreamProvider) {
+ this.mSystemPropertyResolver = systemPropertiesResolver;
+ this.mPlaceHolderValues = placeHolderValues;
+ this.mManifestFile = mainManifestFile;
+ this.mLogger = logger;
+ this.mLibraryFiles = libraryFiles;
+ this.mFlavorsAndBuildTypeFiles = flavorsAndBuildTypeFiles;
+ this.mOptionalFeatures = optionalFeatures;
+ this.mMergeType = mergeType;
+ this.mReportFile = reportFile;
+ this.mFileStreamProvider = fileStreamProvider;
+ }
+
+ /**
+ * Perform high level ordering of files merging and delegates actual merging to
+ * {@link XmlDocument#merge(XmlDocument, com.android.manifmerger.MergingReport.Builder)}
+ *
+ * @return the merging activity report.
+ * @throws MergeFailureException if the merging cannot be completed (for instance, if xml
+ * files cannot be loaded).
+ */
+ @NonNull
+ private MergingReport merge() throws MergeFailureException {
+ // initiate a new merging report
+ MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);
+
+ SelectorResolver selectors = new SelectorResolver();
+ // load all the libraries xml files up front to have a list of all possible node:selector
+ // values.
+ List<LoadedManifestInfo> loadedLibraryDocuments =
+ loadLibraries(selectors, mergingReportBuilder);
+
+ // load the main manifest file to do some checking along the way.
+ LoadedManifestInfo loadedMainManifestInfo = load(
+ new ManifestInfo(
+ mManifestFile.getName(),
+ mManifestFile,
+ XmlDocument.Type.MAIN,
+ Optional.<String>absent() /* mainManifestPackageName */),
+ selectors,
+ mergingReportBuilder);
+
+ // first do we have a package declaration in the main manifest ?
+ Optional<XmlAttribute> mainPackageAttribute =
+ loadedMainManifestInfo.getXmlDocument().getPackage();
+ if (!mainPackageAttribute.isPresent()) {
+ mergingReportBuilder.addMessage(
+ loadedMainManifestInfo.getXmlDocument().getSourceFile(),
+ MergingReport.Record.Severity.ERROR,
+ String.format(
+ "Main AndroidManifest.xml at %1$s manifest:package attribute "
+ + "is not declared",
+ loadedMainManifestInfo.getXmlDocument().getSourceFile()
+ .print(true)));
+ return mergingReportBuilder.build();
+ }
+
+ // perform system property injection
+ performSystemPropertiesInjection(mergingReportBuilder,
+ loadedMainManifestInfo.getXmlDocument());
+
+ // force the re-parsing of the xml as elements may have been added through system
+ // property injection.
+ loadedMainManifestInfo = new LoadedManifestInfo(loadedMainManifestInfo,
+ loadedMainManifestInfo.getOriginalPackageName(),
+ loadedMainManifestInfo.getXmlDocument().reparse());
+
+ // invariant : xmlDocumentOptional holds the higher priority document and we try to
+ // merge in lower priority documents.
+ Optional<XmlDocument> xmlDocumentOptional = Optional.absent();
+ for (File inputFile : mFlavorsAndBuildTypeFiles) {
+ mLogger.info("Merging flavors and build manifest %s \n", inputFile.getPath());
+ LoadedManifestInfo overlayDocument = load(
+ new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY,
+ Optional.of(mainPackageAttribute.get().getValue())),
+ selectors,
+ mergingReportBuilder);
+
+ // check package declaration.
+ Optional<XmlAttribute> packageAttribute =
+ overlayDocument.getXmlDocument().getPackage();
+ // if both files declare a package name, it should be the same.
+ if (loadedMainManifestInfo.getOriginalPackageName().isPresent() &&
+ packageAttribute.isPresent()
+ && !loadedMainManifestInfo.getOriginalPackageName().get().equals(
+ packageAttribute.get().getValue())) {
+ // no suggestion for library since this is actually forbidden to change the
+ // the package name per flavor.
+ String message = mMergeType == MergeType.APPLICATION
+ ? String.format(
+ "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ + "\thas a different value=(%3$s) "
+ + "declared in main manifest at %4$s\n"
+ + "\tSuggestion: remove the overlay declaration at %5$s "
+ + "\tand place it in the build.gradle:\n"
+ + "\t\tflavorName {\n"
+ + "\t\t\tapplicationId = \"%2$s\"\n"
+ + "\t\t}",
+ packageAttribute.get().printPosition(),
+ packageAttribute.get().getValue(),
+ mainPackageAttribute.get().getValue(),
+ mainPackageAttribute.get().printPosition(),
+ packageAttribute.get().getSourceFile().print(true))
+ : String.format(
+ "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ + "\thas a different value=(%3$s) "
+ + "declared in main manifest at %4$s",
+ packageAttribute.get().printPosition(),
+ packageAttribute.get().getValue(),
+ mainPackageAttribute.get().getValue(),
+ mainPackageAttribute.get().printPosition());
+ mergingReportBuilder.addMessage(
+ overlayDocument.getXmlDocument().getSourceFile(),
+ MergingReport.Record.Severity.ERROR,
+ message);
+ return mergingReportBuilder.build();
+ }
+
+ overlayDocument.getXmlDocument().getRootNode().getXml().setAttribute("package",
+ mainPackageAttribute.get().getValue());
+ xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder);
+
+ if (!xmlDocumentOptional.isPresent()) {
+ return mergingReportBuilder.build();
+ }
+ }
+
+ mLogger.info("Merging main manifest %s\n", mManifestFile.getPath());
+ xmlDocumentOptional =
+ merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder);
+
+ if (!xmlDocumentOptional.isPresent()) {
+ return mergingReportBuilder.build();
+ }
+
+ // force main manifest package into resulting merged file when creating a library manifest.
+ if (mMergeType == MergeType.LIBRARY) {
+ // extract the package name...
+ String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode()
+ .getXml().getAttribute("package");
+ // save it in the selector instance.
+ if (!Strings.isNullOrEmpty(mainManifestPackageName)) {
+ xmlDocumentOptional.get().getRootNode().getXml()
+ .setAttribute("package", mainManifestPackageName);
+ }
+ }
+ for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
+ mLogger.info("Merging library manifest " + libraryDocument.getLocation());
+ xmlDocumentOptional = merge(
+ xmlDocumentOptional, libraryDocument, mergingReportBuilder);
+ if (!xmlDocumentOptional.isPresent()) {
+ return mergingReportBuilder.build();
+ }
+ }
+
+ // done with proper merging phase, now we need to trim unwanted elements, placeholder
+ // substitution and system properties injection.
+ ElementsTrimmer.trim(xmlDocumentOptional.get(), mergingReportBuilder);
+ if (mergingReportBuilder.hasErrors()) {
+ return mergingReportBuilder.build();
+ }
+
+ if (!mOptionalFeatures.contains(Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)) {
+ // do one last placeholder substitution, this is useful as we don't stop the build
+ // when a library failed a placeholder substitution, but the element might have
+ // been overridden so the problem was transient. However, with the final document
+ // ready, all placeholders values must have been provided.
+ KeyBasedValueResolver<String> placeHolderValueResolver =
+ new MapBasedKeyBasedValueResolver<String>(mPlaceHolderValues);
+ PlaceholderHandler.visit(
+ mMergeType,
+ xmlDocumentOptional.get(),
+ placeHolderValueResolver,
+ mergingReportBuilder);
+ if (mergingReportBuilder.hasErrors()) {
+ return mergingReportBuilder.build();
+ }
+ }
+
+ // perform system property injection.
+ performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());
+
+ XmlDocument finalMergedDocument = xmlDocumentOptional.get();
+ PostValidator.validate(finalMergedDocument, mergingReportBuilder);
+ if (mergingReportBuilder.hasErrors()) {
+ finalMergedDocument.getRootNode().addMessage(mergingReportBuilder,
+ MergingReport.Record.Severity.WARNING,
+ "Post merge validation failed");
+ }
+ // finally optional features handling.
+ processOptionalFeatures(finalMergedDocument, mergingReportBuilder);
+
+ MergingReport mergingReport = mergingReportBuilder.build();
+
+ if (mReportFile.isPresent()) {
+ writeReport(mergingReport);
+ }
+
+ return mergingReport;
+ }
+
+ private void processOptionalFeatures(
+ @Nullable XmlDocument document,
+ @NonNull MergingReport.Builder mergingReport) {
+ if (document == null) {
+ return;
+ }
+ // only remove tools annotations if we are packaging an application.
+ if (mOptionalFeatures.contains(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)) {
+ document = ToolsInstructionsCleaner.cleanToolsReferences(document, mLogger);
+ }
+
+ if (document != null) {
+ if (mOptionalFeatures.contains(Invoker.Feature.EXTRACT_FQCNS)) {
+ extractFcqns(document);
+ }
+ mergingReport.setMergedDocument(
+ MergingReport.MergedManifestKind.MERGED, document.prettyPrint());
+
+ try {
+ mergingReport.setMergedDocument(MergingReport.MergedManifestKind.BLAME,
+ mergingReport.blame(document));
+ } catch (Exception e) {
+ mLogger.error(e, "Error while saving blame file, build will continue");
+ }
+
+ if (mOptionalFeatures.contains(Invoker.Feature.MAKE_AAPT_SAFE)) {
+ PlaceholderEncoder.visit(document);
+ mergingReport.setMergedDocument(
+ MergingReport.MergedManifestKind.AAPT_SAFE,
+ document.prettyPrint());
+ }
+
+ // Always save the pre InstantRun state in case some APT plugins require it.
+ if (mOptionalFeatures.contains(Invoker.Feature.INSTANT_RUN_REPLACEMENT)) {
+ mergingReport.setMergedDocument(
+ MergingReport.MergedManifestKind.INSTANT_RUN,
+ instantRunReplacement(document).prettyPrint());
+ }
+ }
+ }
+
+ @NonNull
+ private static XmlDocument instantRunReplacement(XmlDocument document) {
+ Optional<XmlElement> applicationOptional = document
+ .getByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null /* keyValue */);
+ if (applicationOptional.isPresent()) {
+ XmlElement application = applicationOptional.get();
+ Attr nameAttribute = application.getXml().getAttributeNodeNS(
+ SdkConstants.ANDROID_URI, "name");
+
+ if (nameAttribute != null) {
+ String originalAppName = nameAttribute.getValue();
+ if (BOOTSTRAP_APPLICATION.equals(originalAppName)) {
+ return document;
+ }
+ application.getXml().setAttribute(SdkConstants.ATTR_NAME, originalAppName);
+ application.getXml().setAttributeNS(
+ SdkConstants.ANDROID_URI,
+ nameAttribute.getName(),
+ BOOTSTRAP_APPLICATION);
+ } else {
+ application.getXml().setAttributeNS(
+ SdkConstants.ANDROID_URI,
+ SdkConstants.ANDROID_NS_NAME_PREFIX + SdkConstants.ATTR_NAME,
+ BOOTSTRAP_APPLICATION);
+ }
+ }
+ return document.reparse();
+ }
+
+ /**
+ * Returns the {@link FileStreamProvider} used by this manifest merger. Use this
+ * to read files if you need to access the content of a {@link XmlDocument}.
+ */
+ @SuppressWarnings("unused") // Allow future library usage, if necessary
+ @NonNull
+ public FileStreamProvider getFileStreamProvider() {
+ return mFileStreamProvider;
+ }
+
+ /**
+ * Creates the merging report file.
+ * @param mergingReport the merging activities report to serialize.
+ */
+ private void writeReport(@NonNull MergingReport mergingReport) {
+ FileWriter fileWriter = null;
+ try {
+ if (!mReportFile.get().getParentFile().exists()
+ && !mReportFile.get().getParentFile().mkdirs()) {
+ mLogger.warning(String.format(
+ "Cannot create %1$s manifest merger report file,"
+ + "build will continue but merging activities "
+ + "will not be documented",
+ mReportFile.get().getAbsolutePath()));
+ } else {
+ fileWriter = new FileWriter(mReportFile.get());
+ mergingReport.getActions().log(fileWriter);
+ }
+ } catch (IOException e) {
+ mLogger.warning(String.format(
+ "Error '%1$s' while writing the merger report file, "
+ + "build can continue but merging activities "
+ + "will not be documented ",
+ e.getMessage()));
+ } finally {
+ if (fileWriter != null) {
+ try {
+ fileWriter.close();
+ } catch (IOException e) {
+ mLogger.warning(String.format(
+ "Error '%1$s' while closing the merger report file, "
+ + "build can continue but merging activities "
+ + "will not be documented ",
+ e.getMessage()));
+ }
+ }
+ }
+ }
+
+ /**
+ * shorten all fully qualified class name that belong to the same package as the manifest's
+ * package attribute value.
+ * @param finalMergedDocument the AndroidManifest.xml document.
+ */
+ private static void extractFcqns(@NonNull XmlDocument finalMergedDocument) {
+ extractFcqns(finalMergedDocument.getPackageName(), finalMergedDocument.getRootNode());
+ }
+
+ /**
+ * shorten recursively all attributes that are package dependent of the passed nodes and all
+ * its child nodes.
+ * @param packageName the manifest package name.
+ * @param xmlElement the xml element to process recursively.
+ */
+ private static void extractFcqns(@NonNull String packageName, @NonNull XmlElement xmlElement) {
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ if (xmlAttribute.getModel() !=null && xmlAttribute.getModel().isPackageDependent()) {
+ String value = xmlAttribute.getValue();
+ if (value.startsWith(packageName) &&
+ value.charAt(packageName.length()) == '.') {
+ xmlAttribute.getXml().setValue(value.substring(packageName.length()));
+ }
+ }
+ }
+ for (XmlElement child : xmlElement.getMergeableElements()) {
+ extractFcqns(packageName, child);
+ }
+ }
+
+ /**
+ * Load an xml file and perform placeholder substitution
+ * @param manifestInfo the android manifest information like if it is a library, an
+ * overlay or a main manifest file.
+ * @param selectors all the libraries selectors
+ * @param mergingReportBuilder the merging report to store events and errors.
+ * @return a loaded manifest info.
+ * @throws MergeFailureException
+ */
+ @NonNull
+ private LoadedManifestInfo load(
+ @NonNull ManifestInfo manifestInfo,
+ @NonNull KeyResolver<String> selectors,
+ @NonNull MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
+
+ File xmlFile = manifestInfo.mLocation;
+ XmlDocument xmlDocument;
+ try {
+ InputStream inputStream = mFileStreamProvider.getInputStream(xmlFile);
+ xmlDocument = XmlLoader.load(selectors,
+ mSystemPropertyResolver,
+ manifestInfo.mName,
+ xmlFile,
+ inputStream,
+ manifestInfo.getType(),
+ manifestInfo.getMainManifestPackageName());
+ } catch (Exception e) {
+ throw new MergeFailureException(e);
+ }
+
+ String originalPackageName = xmlDocument.getPackageName();
+ MergingReport.Builder builder = manifestInfo.getType() == XmlDocument.Type.MAIN
+ ? mergingReportBuilder
+ : new MergingReport.Builder(mergingReportBuilder.getLogger());
+
+ builder.getActionRecorder().recordDefaultNodeAction(
+ xmlDocument.getRootNode());
+
+ // perform place holder substitution, this is necessary to do so early in case placeholders
+ // are used in key attributes.
+ performPlaceHolderSubstitution(manifestInfo, xmlDocument, builder);
+
+ return new LoadedManifestInfo(manifestInfo,
+ Optional.fromNullable(originalPackageName), xmlDocument);
+ }
+
+ private void performPlaceHolderSubstitution(@NonNull ManifestInfo manifestInfo,
+ @NonNull XmlDocument xmlDocument,
+ @NonNull MergingReport.Builder mergingReportBuilder) {
+
+ if (mOptionalFeatures.contains(Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)) {
+ return;
+ }
+
+ // check for placeholders presence, switch first the packageName and application id if
+ // it is not explicitly set.
+ Map<String, Object> finalPlaceHolderValues = mPlaceHolderValues;
+ if (!mPlaceHolderValues.containsKey(PlaceholderHandler.APPLICATION_ID)) {
+ String packageName = manifestInfo.getMainManifestPackageName().isPresent()
+ ? manifestInfo.getMainManifestPackageName().get()
+ : xmlDocument.getPackageName();
+ // add all existing placeholders except package name that will be swapped.
+ ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
+ for (Map.Entry<String, Object> entry : mPlaceHolderValues.entrySet()) {
+ if (!entry.getKey().equals(PlaceholderHandler.PACKAGE_NAME)) {
+ builder.put(entry);
+ }
+ }
+ builder.put(PlaceholderHandler.PACKAGE_NAME, packageName);
+ if (mMergeType != MergeType.LIBRARY) {
+ builder.put(PlaceholderHandler.APPLICATION_ID, packageName);
+ }
+ finalPlaceHolderValues = builder.build();
+ }
+
+ KeyBasedValueResolver<String> placeHolderValueResolver =
+ new MapBasedKeyBasedValueResolver<String>(finalPlaceHolderValues);
+ PlaceholderHandler.visit(
+ mMergeType,
+ xmlDocument,
+ placeHolderValueResolver,
+ mergingReportBuilder);
+ }
+
+ // merge the optionally existing xmlDocument with a lower priority xml file.
+ private Optional<XmlDocument> merge(
+ @NonNull Optional<XmlDocument> xmlDocument,
+ @NonNull LoadedManifestInfo lowerPriorityDocument,
+ @NonNull MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
+
+ MergingReport.Result validationResult = PreValidator
+ .validate(mergingReportBuilder, lowerPriorityDocument.getXmlDocument());
+ if (validationResult == MergingReport.Result.ERROR) {
+ mergingReportBuilder.addMessage(
+ lowerPriorityDocument.getXmlDocument().getSourceFile(),
+ MergingReport.Record.Severity.ERROR,
+ "Validation failed, exiting");
+ return Optional.absent();
+ }
+ Optional<XmlDocument> result;
+ if (xmlDocument.isPresent()) {
+ result = xmlDocument.get().merge(
+ lowerPriorityDocument.getXmlDocument(), mergingReportBuilder);
+ } else {
+ mergingReportBuilder.getActionRecorder().recordDefaultNodeAction(
+ lowerPriorityDocument.getXmlDocument().getRootNode());
+ result = Optional.of(lowerPriorityDocument.getXmlDocument());
+ }
+
+ // if requested, dump each intermediary merging stage into the report.
+ if (mOptionalFeatures.contains(Invoker.Feature.KEEP_INTERMEDIARY_STAGES)
+ && result.isPresent()) {
+ mergingReportBuilder.addMergingStage(result.get().prettyPrint());
+ }
+
+ return result;
+ }
+
+ private List<LoadedManifestInfo> loadLibraries(@NonNull SelectorResolver selectors,
+ @NonNull MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
+
+ ImmutableList.Builder<LoadedManifestInfo> loadedLibraryDocuments = ImmutableList.builder();
+ for (Pair<String, File> libraryFile : mLibraryFiles) {
+ mLogger.info("Loading library manifest " + libraryFile.getSecond().getPath());
+ ManifestInfo manifestInfo = new ManifestInfo(libraryFile.getFirst(),
+ libraryFile.getSecond(),
+ XmlDocument.Type.LIBRARY, Optional.<String>absent());
+ File xmlFile = manifestInfo.mLocation;
+ XmlDocument libraryDocument;
+ try {
+ InputStream inputStream = mFileStreamProvider.getInputStream(xmlFile);
+ libraryDocument = XmlLoader.load(selectors,
+ mSystemPropertyResolver,
+ manifestInfo.mName,
+ xmlFile,
+ inputStream,
+ XmlDocument.Type.LIBRARY,
+ Optional.<String>absent() /* mainManifestPackageName */);
+ } catch (Exception e) {
+ throw new MergeFailureException(e);
+ }
+ // extract the package name...
+ String libraryPackage = libraryDocument.getRootNode().getXml().getAttribute("package");
+ // save it in the selector instance.
+ if (!Strings.isNullOrEmpty(libraryPackage)) {
+ selectors.addSelector(libraryPackage, libraryFile.getFirst());
+ }
+
+ // perform placeholder substitution, this is useful when the library is using
+ // a placeholder in a key element, we however do not need to record these
+ // substitutions so feed it with a fake merging report.
+ MergingReport.Builder builder = new MergingReport.Builder(mergingReportBuilder.getLogger());
+ builder.getActionRecorder().recordDefaultNodeAction(libraryDocument.getRootNode());
+ performPlaceHolderSubstitution(manifestInfo, libraryDocument, builder);
+ if (builder.hasErrors()) {
+ // we log the errors but continue, in case the error is of no consequence
+ // to the application consuming the library.
+ builder.build().log(mLogger);
+ }
+
+ loadedLibraryDocuments.add(new LoadedManifestInfo(manifestInfo,
+ Optional.fromNullable(libraryDocument.getPackageName()),
+ libraryDocument));
+ }
+ return loadedLibraryDocuments.build();
+ }
+
+ /**
+ * Creates a new {@link com.android.manifmerger.ManifestMerger2.Invoker} instance to invoke
+ * the merging tool to merge manifest files for an application.
+ *
+ * @param mainManifestFile application main manifest file.
+ * @param logger the logger interface to use.
+ * @return an {@link com.android.manifmerger.ManifestMerger2.Invoker} instance that will allow
+ * further customization and trigger the merging tool.
+ */
+ @NonNull
+ public static Invoker newMerger(@NonNull File mainManifestFile,
+ @NonNull ILogger logger,
+ @NonNull MergeType mergeType) {
+ return new Invoker(mainManifestFile, logger, mergeType);
+ }
+
+ /**
+ * List of manifest files properties that can be directly overridden without using a
+ * placeholder.
+ */
+ public enum SystemProperty implements AutoAddingProperty {
+
+ /**
+ * Allow setting the merged manifest file package name.
+ */
+ PACKAGE {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElement(this, actionRecorder, value, document.getRootNode());
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/manifest-element.html#vcode
+ */
+ VERSION_CODE {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode());
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/manifest-element.html#vname
+ */
+ VERSION_NAME {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value, document.getRootNode());
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#min
+ */
+ MIN_SDK_VERSION {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value,
+ createOrGetUseSdk(actionRecorder, document));
+ }
+ },
+ /**
+ * http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#target
+ */
+ TARGET_SDK_VERSION {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value,
+ createOrGetUseSdk(actionRecorder, document));
+ }
+ },
+
+ MAX_SDK_VERSION {
+ @Override
+ public void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value) {
+ addToElementInAndroidNS(this, actionRecorder, value,
+ createOrGetUseSdk(actionRecorder, document));
+ }
+ };
+
+ public String toCamelCase() {
+ return SdkUtils.constantNameToCamelCase(name());
+ }
+
+ // utility method to add an attribute which name is derived from the enum name().
+ private static void addToElement(
+ @NonNull SystemProperty systemProperty,
+ @NonNull ActionRecorder actionRecorder,
+ String value,
+ @NonNull XmlElement to) {
+
+ to.getXml().setAttribute(systemProperty.toCamelCase(), value);
+ XmlAttribute xmlAttribute = new XmlAttribute(to,
+ to.getXml().getAttributeNode(systemProperty.toCamelCase()), null);
+ actionRecorder.recordAttributeAction(xmlAttribute, new Actions.AttributeRecord(
+ Actions.ActionType.INJECTED,
+ new SourceFilePosition(to.getSourceFile(), SourcePosition.UNKNOWN),
+ xmlAttribute.getId(),
+ null, /* reason */
+ null /* attributeOperationType */));
+ }
+
+ // utility method to add an attribute in android namespace which local name is derived from
+ // the enum name().
+ private static void addToElementInAndroidNS(
+ @NonNull SystemProperty systemProperty,
+ @NonNull ActionRecorder actionRecorder,
+ String value,
+ @NonNull XmlElement to) {
+
+ String toolsPrefix = getAndroidPrefix(to.getXml());
+ to.getXml().setAttributeNS(SdkConstants.ANDROID_URI,
+ toolsPrefix + XmlUtils.NS_SEPARATOR + systemProperty.toCamelCase(),
+ value);
+ Attr attr = to.getXml().getAttributeNodeNS(SdkConstants.ANDROID_URI,
+ systemProperty.toCamelCase());
+
+ XmlAttribute xmlAttribute = new XmlAttribute(to, attr, null);
+ actionRecorder.recordAttributeAction(xmlAttribute,
+ new Actions.AttributeRecord(
+ Actions.ActionType.INJECTED,
+ new SourceFilePosition(to.getSourceFile(), SourcePosition.UNKNOWN),
+ xmlAttribute.getId(),
+ null, /* reason */
+ null /* attributeOperationType */
+ )
+ );
+
+ }
+
+ // utility method to create or get an existing use-sdk xml element under manifest.
+ // this could be made more generic by adding more metadata to the enum but since there is
+ // only one case so far, keep it simple.
+ @NonNull
+ private static XmlElement createOrGetUseSdk(
+ @NonNull ActionRecorder actionRecorder, @NonNull XmlDocument document) {
+
+ Element manifest = document.getXml().getDocumentElement();
+ NodeList usesSdks = manifest
+ .getElementsByTagName(ManifestModel.NodeTypes.USES_SDK.toXmlName());
+ if (usesSdks.getLength() == 0) {
+ usesSdks = manifest
+ .getElementsByTagNameNS(
+ SdkConstants.ANDROID_URI,
+ ManifestModel.NodeTypes.USES_SDK.toXmlName());
+ }
+ if (usesSdks.getLength() == 0) {
+ // create it first.
+ Element useSdk = manifest.getOwnerDocument().createElement(
+ ManifestModel.NodeTypes.USES_SDK.toXmlName());
+ manifest.appendChild(useSdk);
+ XmlElement xmlElement = new XmlElement(useSdk, document);
+ Actions.NodeRecord nodeRecord = new Actions.NodeRecord(
+ Actions.ActionType.INJECTED,
+ new SourceFilePosition(xmlElement.getSourceFile(),
+ SourcePosition.UNKNOWN),
+ xmlElement.getId(),
+ "use-sdk injection requested",
+ NodeOperationType.STRICT);
+ actionRecorder.recordNodeAction(xmlElement, nodeRecord);
+ return xmlElement;
+ } else {
+ return new XmlElement((Element) usesSdks.item(0), document);
+ }
+ }
+ }
+
+ private static String getAndroidPrefix(@NonNull Element xml) {
+ String toolsPrefix = XmlUtils.lookupNamespacePrefix(
+ xml, SdkConstants.ANDROID_URI, SdkConstants.ANDROID_NS_NAME, false);
+ if (!toolsPrefix.equals(SdkConstants.ANDROID_NS_NAME) && xml.getOwnerDocument()
+ .getDocumentElement().getAttribute("xmlns:" + toolsPrefix) == null) {
+ // this is weird, the document is using "android" prefix but it's not bound
+ // to our namespace. Add the proper xmlns declaration.
+ xml.setAttribute("xmlns:" + toolsPrefix, SdkConstants.ANDROID_URI);
+ }
+ return toolsPrefix;
+ }
+
+ /**
+ * Defines the merging type expected from the tool.
+ */
+ public enum MergeType {
+ /**
+ * Application merging type is used when packaging an application with a set of imported
+ * libraries. The resulting merged android manifest is final and is not expected to be
+ * imported in another application.
+ */
+ APPLICATION,
+
+ /**
+ * Library merging typee is used when packaging a library. The resulting android manifest
+ * file will not merge in all the imported libraries this library depends on. Also the tools
+ * annotations will not be removed as they can be useful when later importing the resulting
+ * merged android manifest into an application.
+ */
+ LIBRARY
+ }
+
+ /**
+ * Defines a property that can add or override itself into an XML document.
+ */
+ public interface AutoAddingProperty {
+
+ /**
+ * Add itself (possibly just override the current value) with the passed value
+ * @param actionRecorder to record actions.
+ * @param document the xml document to add itself to.
+ * @param value the value to set of this property.
+ */
+ void addTo(@NonNull ActionRecorder actionRecorder,
+ @NonNull XmlDocument document,
+ @NonNull String value);
+ }
+
+ /**
+ * Perform {@link com.android.manifmerger.ManifestMerger2.SystemProperty} injection.
+ * @param mergingReport to log actions and errors.
+ * @param xmlDocument the xml document to inject into.
+ */
+ protected void performSystemPropertiesInjection(
+ @NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlDocument xmlDocument) {
+ for (SystemProperty systemProperty : SystemProperty.values()) {
+ String propertyOverride = mSystemPropertyResolver.getValue(systemProperty);
+ if (propertyOverride != null) {
+ systemProperty.addTo(
+ mergingReport.getActionRecorder(), xmlDocument, propertyOverride);
+ }
+ }
+ }
+
+ /**
+ * A {@linkplain FileStreamProvider} provides (buffered, if necessary) {@link InputStream}
+ * instances for a given {@link File} handle.
+ */
+ public static class FileStreamProvider {
+ /**
+ * Creates a reader for the given file -- which may not necessarily read the contents of the
+ * file on disk. For example, in the IDE, the client will map the file handle to a document in
+ * the editor, and read the current contents of that editor whether or not it has been saved.
+ * <p>
+ * This method is responsible for providing its own buffering, if necessary (e.g. when
+ * reading from disk, make sure you wrap the file stream in a buffering input stream.)
+ *
+ * @param file the file handle
+ * @return the contents of the file
+ * @throws FileNotFoundException if the file handle is invalid
+ */
+ @SuppressWarnings("MethodMayBeStatic") // Intended for overrides outside this library
+ protected InputStream getInputStream(@NonNull File file) throws FileNotFoundException {
+ return new BufferedInputStream(new FileInputStream(file));
+ }
+ }
+
+ /**
+ * This class will hold all invocation parameters for the manifest merging tool.
+ *
+ * There are broadly three types of input to the merging tool :
+ * <ul>
+ * <li>Build types and flavors overriding manifests</li>
+ * <li>Application main manifest</li>
+ * <li>Library manifest files</li></lib>
+ * </ul>
+ *
+ * Only the main manifest file is a mandatory parameter.
+ *
+ * High level description of the merging will be as follow :
+ * <ol>
+ * <li>Build type and flavors will be merged first in the order they were added. Highest
+ * priority file added first, lowest added last.</li>
+ * <li>Resulting document is merged with lower priority application main manifest file.</li>
+ * <li>Resulting document is merged with each library file manifest file in the order
+ * they were added. Highest priority added first, lowest added last.</li>
+ * <li>Resulting document is returned as results of the merging process.</li>
+ * </ol>
+ *
+ */
+ public static class Invoker<T extends Invoker<T>>{
+
+ protected final File mMainManifestFile;
+
+ protected final ImmutableMap.Builder<SystemProperty, Object> mSystemProperties =
+ new ImmutableMap.Builder<SystemProperty, Object>();
+
+ @NonNull
+ protected final ILogger mLogger;
+ @NonNull
+ protected final ImmutableMap.Builder<String, Object> mPlaceholders =
+ new ImmutableMap.Builder<String, Object>();
+ @NonNull
+ private final ImmutableList.Builder<Pair<String, File>> mLibraryFilesBuilder =
+ new ImmutableList.Builder<Pair<String, File>>();
+ @NonNull
+ private final ImmutableList.Builder<File> mFlavorsAndBuildTypeFiles =
+ new ImmutableList.Builder<File>();
+ @NonNull
+ private final ImmutableList.Builder<Feature> mFeaturesBuilder =
+ new ImmutableList.Builder<Feature>();
+ @NonNull
+ private final MergeType mMergeType;
+ @Nullable private File mReportFile;
+
+ @Nullable
+ private FileStreamProvider mFileStreamProvider;
+
+ /**
+ * Sets a value for a {@link com.android.manifmerger.ManifestMerger2.SystemProperty}
+ * @param override the property to set
+ * @param value the value for the property
+ * @return itself.
+ */
+ @NonNull
+ public Invoker setOverride(@NonNull SystemProperty override, @NonNull String value) {
+ mSystemProperties.put(override, value);
+ return thisAsT();
+ }
+
+ /**
+ * Adds placeholders names and associated values for substitution.
+ * @return itself.
+ */
+ @NonNull
+ public Invoker setPlaceHolderValues(@NonNull Map<String, Object> keyValuePairs) {
+ mPlaceholders.putAll(keyValuePairs);
+ return thisAsT();
+ }
+
+ /**
+ * Adds a new placeholder name and value for substitution.
+ * @return itself.
+ */
+ @NonNull
+ public Invoker setPlaceHolderValue(@NonNull String placeHolderName, @NonNull String value) {
+ mPlaceholders.put(placeHolderName, value);
+ return thisAsT();
+ }
+
+ /**
+ * Optional behavior of the merging tool can be turned on by setting these Feature.
+ */
+ public enum Feature {
+
+ /**
+ * Keep all intermediary merged files during the merging process. This is particularly
+ * useful for debugging/tracing purposes.
+ */
+ KEEP_INTERMEDIARY_STAGES,
+
+ /**
+ * When logging file names, use {@link java.io.File#getName()} rather than
+ * {@link java.io.File#getPath()}
+ */
+ PRINT_SIMPLE_FILENAMES,
+
+ /**
+ * Perform a sweep after all merging activities to remove all fully qualified class
+ * names and replace them with the equivalent short version.
+ */
+ EXTRACT_FQCNS,
+
+ /**
+ * Perform a sweep after all merging activities to remove all tools: decorations.
+ */
+ REMOVE_TOOLS_DECLARATIONS,
+
+ /**
+ * Do no perform placeholders replacement.
+ */
+ NO_PLACEHOLDER_REPLACEMENT,
+
+ /**
+ * Encode unresolved placeholders to be AAPT friendly.
+ */
+ MAKE_AAPT_SAFE,
+
+ /**
+ * Perform InstantRun related swapping in the merged manifest file.
+ */
+ INSTANT_RUN_REPLACEMENT
+ }
+
+ /**
+ * Creates a new builder with the mandatory main manifest file.
+ * @param mainManifestFile application main manifest file.
+ * @param logger the logger interface to use.
+ */
+ private Invoker(
+ @NonNull File mainManifestFile,
+ @NonNull ILogger logger,
+ @NonNull MergeType mergeType) {
+ this.mMainManifestFile = Preconditions.checkNotNull(mainManifestFile);
+ this.mLogger = logger;
+ this.mMergeType = mergeType;
+ }
+
+ /**
+ * Sets the file to use to write the merging report. If not called,
+ * the merging process will not write a report.
+ * @param mergeReport the file to write the report in.
+ * @return itself.
+ */
+ @NonNull
+ public Invoker setMergeReportFile(@Nullable File mergeReport) {
+ mReportFile = mergeReport;
+ return this;
+ }
+
+ /**
+ * Add one library file manifest, will be added last in the list of library files which will
+ * make the parameter the lowest priority library manifest file.
+ * @param file the library manifest file to add.
+ * @return itself.
+ */
+ @NonNull
+ public Invoker addLibraryManifest(@NonNull File file) {
+ if (mMergeType == MergeType.LIBRARY) {
+ throw new IllegalStateException(
+ "Cannot add library dependencies manifests when creating a library");
+ }
+ mLibraryFilesBuilder.add(Pair.of(file.getName(), file));
+ return thisAsT();
+ }
+
+ @NonNull
+ public Invoker addLibraryManifests(@NonNull List<Pair<String, File>> namesAndFiles) {
+ if (mMergeType == MergeType.LIBRARY && !namesAndFiles.isEmpty()) {
+ throw new IllegalStateException(
+ "Cannot add library dependencies manifests when creating a library");
+ }
+ mLibraryFilesBuilder.addAll(namesAndFiles);
+ return thisAsT();
+ }
+
+ /**
+ * Add several library file manifests at then end of the list which will make them the
+ * lowest priority manifest files. The relative priority between all the files passed as
+ * parameters will be respected.
+ * @param files library manifest files to add last.
+ * @return itself.
+ */
+ @NonNull
+ public Invoker addLibraryManifests(@NonNull File... files) {
+ for (File file : files) {
+ addLibraryManifest(file);
+ }
+ return thisAsT();
+ }
+
+ /**
+ * Add a flavor or build type manifest file last in the list.
+ * @param file build type or flavor manifest file
+ * @return itself.
+ */
+ @NonNull
+ public Invoker addFlavorAndBuildTypeManifest(@NonNull File file) {
+ this.mFlavorsAndBuildTypeFiles.add(file);
+ return thisAsT();
+ }
+
+ /**
+ * Add several flavor or build type manifest files last in the list. Relative priorities
+ * between the passed files as parameters will be respected.
+ * @param files build type of flavor manifest files to add.
+ * @return itself.
+ */
+ @NonNull
+ public Invoker addFlavorAndBuildTypeManifests(File... files) {
+ this.mFlavorsAndBuildTypeFiles.add(files);
+ return thisAsT();
+ }
+
+ /**
+ * Sets some optional features for the merge tool.
+ *
+ * @param features one to many features to set.
+ * @return itself.
+ */
+ @NonNull
+ public Invoker withFeatures(Feature...features) {
+ mFeaturesBuilder.add(features);
+ return thisAsT();
+ }
+
+ /**
+ * Sets a file stream provider which allows the client of the manifest merger to provide
+ * arbitrary content lookup for files. <p> NOTE: There should only be one.
+ *
+ * @param provider the provider to use
+ * @return itself.
+ */
+ @NonNull
+ public Invoker withFileStreamProvider(@Nullable FileStreamProvider provider) {
+ assert mFileStreamProvider == null || provider == null;
+ mFileStreamProvider = provider;
+ return thisAsT();
+ }
+
+ /**
+ * Perform the merging and return the result.
+ *
+ * @return an instance of {@link com.android.manifmerger.MergingReport} that will give
+ * access to all the logging and merging records.
+ *
+ * This method can be invoked several time and will re-do the file merges.
+ *
+ * @throws com.android.manifmerger.ManifestMerger2.MergeFailureException if the merging
+ * cannot be completed successfully.
+ */
+ @NonNull
+ public MergingReport merge() throws MergeFailureException {
+
+ // provide some free placeholders values.
+ ImmutableMap<SystemProperty, Object> systemProperties = mSystemProperties.build();
+ if (systemProperties.containsKey(SystemProperty.PACKAGE)) {
+ // if the package is provided, make it available for placeholder replacement.
+ mPlaceholders.put(PACKAGE_NAME, systemProperties.get(SystemProperty.PACKAGE));
+ // as well as applicationId since package system property overrides everything
+ // but not when output is a library since only the final (application)
+ // application Id should be used to replace libraries "applicationId" placeholders.
+ if (mMergeType != MergeType.LIBRARY) {
+ mPlaceholders.put(APPLICATION_ID, systemProperties.get(SystemProperty.PACKAGE));
+ }
+ }
+
+ FileStreamProvider fileStreamProvider = mFileStreamProvider != null
+ ? mFileStreamProvider : new FileStreamProvider();
+ ManifestMerger2 manifestMerger =
+ new ManifestMerger2(
+ mLogger,
+ mMainManifestFile,
+ mLibraryFilesBuilder.build(),
+ mFlavorsAndBuildTypeFiles.build(),
+ mFeaturesBuilder.build(),
+ mPlaceholders.build(),
+ new MapBasedKeyBasedValueResolver<SystemProperty>(systemProperties),
+ mMergeType,
+ Optional.fromNullable(mReportFile),
+ fileStreamProvider);
+ return manifestMerger.merge();
+ }
+
+ @NonNull
+ @SuppressWarnings("unchecked")
+ private T thisAsT() {
+ return (T) this;
+ }
+ }
+
+ /**
+ * Helper class for map based placeholders key value pairs.
+ */
+ public static class MapBasedKeyBasedValueResolver<T> implements KeyBasedValueResolver<T> {
+
+ private final ImmutableMap<T, Object> keyValues;
+
+ public MapBasedKeyBasedValueResolver(@NonNull Map<T, Object> keyValues) {
+ this.keyValues = ImmutableMap.copyOf(keyValues);
+ }
+
+ @Nullable
+ @Override
+ public String getValue(@NonNull T key) {
+ Object value = keyValues.get(key);
+ return value == null ? null : value.toString();
+ }
+ }
+
+ private static class ManifestInfo {
+
+ private ManifestInfo(
+ String name,
+ File location,
+ XmlDocument.Type type,
+ Optional<String> mainManifestPackageName) {
+ mName = name;
+ mLocation = location;
+ mType = type;
+ mMainManifestPackageName = mainManifestPackageName;
+ }
+
+ private final String mName;
+ private final File mLocation;
+ private final XmlDocument.Type mType;
+ private final Optional<String> mMainManifestPackageName;
+
+ File getLocation() {
+ return mLocation;
+ }
+
+ XmlDocument.Type getType() {
+ return mType;
+ }
+
+ Optional<String> getMainManifestPackageName() {
+ return mMainManifestPackageName;
+ }
+ }
+
+ private static class LoadedManifestInfo extends ManifestInfo {
+
+ @NonNull private final XmlDocument mXmlDocument;
+ @NonNull private final Optional<String> mOriginalPackageName;
+
+ private LoadedManifestInfo(@NonNull ManifestInfo manifestInfo,
+ @NonNull Optional<String> originalPackageName,
+ @NonNull XmlDocument xmlDocument) {
+ super(manifestInfo.mName,
+ manifestInfo.mLocation,
+ manifestInfo.mType,
+ manifestInfo.getMainManifestPackageName());
+ mXmlDocument = xmlDocument;
+ mOriginalPackageName = originalPackageName;
+ }
+
+ @NonNull
+ public XmlDocument getXmlDocument() {
+ return mXmlDocument;
+ }
+
+ @NonNull
+ public Optional<String> getOriginalPackageName() {
+ return mOriginalPackageName;
+ }
+ }
+
+ /**
+ * Implementation a {@link com.android.manifmerger.KeyResolver} capable of resolving all
+ * selectors value in the context of the passed libraries to this merging activities.
+ */
+ static class SelectorResolver implements KeyResolver<String> {
+
+ private final Map<String, String> mSelectors = new HashMap<String, String>();
+
+ protected void addSelector(String key, String value) {
+ mSelectors.put(key, value);
+ }
+
+ @Nullable
+ @Override
+ public String resolve(String key) {
+ return mSelectors.get(key);
+ }
+
+ @NonNull
+ @Override
+ public Iterable<String> getKeys() {
+ return mSelectors.keySet();
+ }
+ }
+
+ // a wrapper exception to all sorts of failure exceptions that can be thrown during merging.
+ public static class MergeFailureException extends Exception {
+
+ protected MergeFailureException(Exception cause) {
+ super(cause);
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java
new file mode 100644
index 0000000..ff2552a
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestModel.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.manifmerger.AttributeModel.Hexadecimal32BitsWithMinimumValue;
+import static com.android.manifmerger.AttributeModel.MultiValueValidator;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.utils.SdkUtils;
+import com.android.xml.AndroidManifest;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Model for the manifest file merging activities.
+ * <p>
+ *
+ * This model will describe each element that is eligible for merging and associated merging
+ * policies. It is not reusable as most of its interfaces are private but a future enhancement
+ * could easily make this more generic/reusable if we need to merge more than manifest files.
+ *
+ */
+ at Immutable
+class ManifestModel {
+
+ /**
+ * Interface responsible for providing a key extraction capability from a xml element.
+ * Some elements store their keys as an attribute, some as a sub-element attribute, some don't
+ * have any key.
+ */
+ @Immutable
+ interface NodeKeyResolver {
+
+ /**
+ * Returns the key associated with this xml element.
+ * @param xmlElement the xml element to get the key from
+ * @return the key as a string to uniquely identify xmlElement from similarly typed elements
+ * in the xml document or null if there is no key.
+ */
+ @Nullable String getKey(Element xmlElement);
+
+ /**
+ * Returns the attribute(s) used to store the xml element key.
+ * @return the key attribute(s) name(s) or null of this element does not have a key.
+ */
+ @NonNull
+ ImmutableList<String> getKeyAttributesNames();
+ }
+
+ /**
+ * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that do not
+ * provide any key (the element has to be unique in the xml document).
+ */
+ private static class NoKeyNodeResolver implements NodeKeyResolver {
+
+ @Override
+ @Nullable
+ public String getKey(Element xmlElement) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of();
+ }
+ }
+
+ /**
+ * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that uses an
+ * attribute to resolve the key value.
+ */
+ private static class AttributeBasedNodeKeyResolver implements NodeKeyResolver {
+
+ @Nullable private final String mNamespaceUri;
+ private final String mAttributeName;
+
+ /**
+ * Build a new instance capable of resolving an xml element key from the passed attribute
+ * namespace and local name.
+ * @param namespaceUri optional namespace for the attribute name.
+ * @param attributeName attribute name
+ */
+ private AttributeBasedNodeKeyResolver(@Nullable String namespaceUri,
+ @NonNull String attributeName) {
+ this.mNamespaceUri = namespaceUri;
+ this.mAttributeName = Preconditions.checkNotNull(attributeName);
+ }
+
+ @Override
+ @Nullable
+ public String getKey(@NonNull Element xmlElement) {
+ String key = mNamespaceUri == null
+ ? xmlElement.getAttribute(mAttributeName)
+ : xmlElement.getAttributeNS(mNamespaceUri, mAttributeName);
+ if (Strings.isNullOrEmpty(key)) return null;
+ return key;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of(mAttributeName);
+ }
+ }
+
+ /**
+ * Subclass of {@link com.android.manifmerger.ManifestModel.AttributeBasedNodeKeyResolver} that
+ * uses "android:name" as the attribute.
+ */
+ private static final NodeKeyResolver DEFAULT_NAME_ATTRIBUTE_RESOLVER =
+ new AttributeBasedNodeKeyResolver(ANDROID_URI, SdkConstants.ATTR_NAME);
+
+ private static final NoKeyNodeResolver DEFAULT_NO_KEY_NODE_RESOLVER = new NoKeyNodeResolver();
+
+ /**
+ * A {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} capable of extracting the
+ * element key first in an "android:name" attribute and if not value found there, in the
+ * "android:glEsVersion" attribute.
+ */
+ @Nullable
+ private static final NodeKeyResolver NAME_AND_GLESVERSION_KEY_RESOLVER = new NodeKeyResolver() {
+ private final NodeKeyResolver nameAttrResolver = DEFAULT_NAME_ATTRIBUTE_RESOLVER;
+ private final NodeKeyResolver glEsVersionResolver =
+ new AttributeBasedNodeKeyResolver(ANDROID_URI,
+ AndroidManifest.ATTRIBUTE_GLESVERSION);
+
+ @Nullable
+ @Override
+ public String getKey(Element xmlElement) {
+ @Nullable String key = nameAttrResolver.getKey(xmlElement);
+ return Strings.isNullOrEmpty(key)
+ ? glEsVersionResolver.getKey(xmlElement)
+ : key;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of(SdkConstants.ATTR_NAME, AndroidManifest.ATTRIBUTE_GLESVERSION);
+ }
+ };
+
+ /**
+ * Specific {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} for intent-filter
+ * elements.
+ * Intent filters do not have a proper key, therefore their identity is really carried by
+ * the presence of the action and category sub-elements.
+ * We concatenate such elements sub-keys (after sorting them to work around declaration order)
+ * and use that for the intent-filter unique key.
+ */
+ @Nullable
+ private static final NodeKeyResolver INTENT_FILTER_KEY_RESOLVER = new NodeKeyResolver() {
+ @Nullable
+ @Override
+ public String getKey(@NonNull Element element) {
+ @NonNull OrphanXmlElement xmlElement = new OrphanXmlElement(element);
+ assert(xmlElement.getType() == NodeTypes.INTENT_FILTER);
+ // concatenate all actions and categories attribute names.
+ @NonNull List<String> allSubElementKeys = new ArrayList<String>();
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() != Node.ELEMENT_NODE) continue;
+ @NonNull OrphanXmlElement subElement = new OrphanXmlElement((Element) child);
+ if (subElement.getType() == NodeTypes.ACTION
+ || subElement.getType() == NodeTypes.CATEGORY) {
+ Attr nameAttribute = subElement.getXml()
+ .getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (nameAttribute != null) {
+ allSubElementKeys.add(nameAttribute.getValue());
+ }
+ }
+ }
+ Collections.sort(allSubElementKeys);
+ return Joiner.on('+').join(allSubElementKeys);
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of("action#name", "category#name");
+ }
+ };
+
+ /**
+ * Implementation of {@link com.android.manifmerger.ManifestModel.NodeKeyResolver} that
+ * combined two attributes values to create the key value.
+ */
+ private static final class TwoAttributesBasedKeyResolver implements NodeKeyResolver {
+ private final NodeKeyResolver firstAttributeKeyResolver;
+ private final NodeKeyResolver secondAttributeKeyResolver;
+
+ private TwoAttributesBasedKeyResolver(NodeKeyResolver firstAttributeKeyResolver,
+ NodeKeyResolver secondAttributeKeyResolver) {
+ this.firstAttributeKeyResolver = firstAttributeKeyResolver;
+ this.secondAttributeKeyResolver = secondAttributeKeyResolver;
+ }
+
+ @Nullable
+ @Override
+ public String getKey(Element xmlElement) {
+ @Nullable String firstKey = firstAttributeKeyResolver.getKey(xmlElement);
+ @Nullable String secondKey = secondAttributeKeyResolver.getKey(xmlElement);
+
+ return Strings.isNullOrEmpty(firstKey)
+ ? secondKey
+ : Strings.isNullOrEmpty(secondKey)
+ ? firstKey
+ : firstKey + "+" + secondKey;
+ }
+
+ @NonNull
+ @Override
+ public ImmutableList<String> getKeyAttributesNames() {
+ return ImmutableList.of(firstAttributeKeyResolver.getKeyAttributesNames().get(0),
+ secondAttributeKeyResolver.getKeyAttributesNames().get(0));
+ }
+ }
+
+ private static final AttributeModel.BooleanValidator BOOLEAN_VALIDATOR =
+ new AttributeModel.BooleanValidator();
+
+ private static final boolean MULTIPLE_DECLARATION_FOR_SAME_KEY_ALLOWED = true;
+
+ /**
+ * Definitions of the support node types in the Android Manifest file.
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-intro.html/>}
+ * for more details about the xml format.
+ *
+ * There is no DTD or schema associated with the file type so this is best effort in providing
+ * some metadata on the elements of the Android's xml file.
+ *
+ * Each xml element is defined as an enum value and for each node, extra metadata is added
+ * <ul>
+ * <li>{@link com.android.manifmerger.MergeType} to identify how the merging engine
+ * should process this element.</li>
+ * <li>{@link com.android.manifmerger.ManifestModel.NodeKeyResolver} to resolve the
+ * element's key. Elements can have an attribute like "android:name", others can use
+ * a sub-element, and finally some do not have a key and are meant to be unique.</li>
+ * <li>List of attributes models with special behaviors :
+ * <ul>
+ * <li>Smart substitution of class names to fully qualified class names using the
+ * document's package declaration. The list's size can be 0..n</li>
+ * <li>Implicit default value when no defined on the xml element.</li>
+ * <li>{@link AttributeModel.Validator} to validate attribute value against.</li>
+ * </ul>
+ * </ul>
+ *
+ * It is of the outermost importance to keep this model correct as it is used by the merging
+ * engine to make all its decisions. There should not be special casing in the engine, all
+ * decisions must be represented here.
+ *
+ * If you find yourself needing to extend the model to support future requirements, do it here
+ * and modify the engine to make proper decision based on the added metadata.
+ */
+ enum NodeTypes {
+
+ /**
+ * Action (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/action-element.html>
+ * Action Xml documentation</a>}
+ */
+ ACTION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Activity (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/activity-element.html>
+ * Activity Xml documentation</a>}
+ */
+ ACTIVITY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel("parentActivityName").setIsPackageDependent(),
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Activity-alias (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/activity-alias-element.html>
+ * Activity-alias Xml documentation</a>}
+ */
+ ACTIVITY_ALIAS(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel("targetActivity").setIsPackageDependent(),
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Application (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/application-element.html>
+ * Application Xml documentation</a>}
+ */
+ APPLICATION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER,
+ AttributeModel.newModel("backupAgent").setIsPackageDependent(),
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Category (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/category-element.html>
+ * Category Xml documentation</a>}
+ */
+ CATEGORY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Compatible-screens (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/compatible-screens-element.html>
+ * Category Xml documentation</a>}
+ */
+ COMPATIBLE_SCREENS(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Data (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/data-element.html>
+ * Category Xml documentation</a>}
+ */
+ DATA(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Grant-uri-permission (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/grant-uri-permission-element.html>
+ * Category Xml documentation</a>}
+ */
+ GRANT_URI_PERMISSION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Instrumentation (contained in intent-filter)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/instrumentation-element.html>
+ * Instrunentation Xml documentation</a>}
+ */
+ INSTRUMENTATION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Intent-filter (contained in activity, activity-alias, service, receiver)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/intent-filter-element.html>
+ * Intent-filter Xml documentation</a>}
+ */
+ INTENT_FILTER(MergeType.ALWAYS, INTENT_FILTER_KEY_RESOLVER,
+ MULTIPLE_DECLARATION_FOR_SAME_KEY_ALLOWED),
+
+ /**
+ * Manifest (top level node)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/manifest-element.html>
+ * Manifest Xml documentation</a>}
+ */
+ MANIFEST(MergeType.MERGE_CHILDREN_ONLY, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Meta-data (contained in activity, activity-alias, application, provider, receiver)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/meta-data-element.html>
+ * Meta-data Xml documentation</a>}
+ */
+ META_DATA(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Path-permission (contained in provider)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/path-permission-element.html>
+ * Meta-data Xml documentation</a>}
+ */
+ PATH_PERMISSION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Permission-group (contained in manifest).
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-group-element.html>
+ * Permission-group Xml documentation</a>}
+ *
+ */
+ PERMISSION_GROUP(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME)),
+
+ /**
+ * Permission (contained in manifest).
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-element.html>
+ * Permission Xml documentation</a>}
+ *
+ */
+ PERMISSION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME),
+ AttributeModel.newModel("protectionLevel")
+ .setDefaultValue("normal")
+ // TODO : this will need to be populated from
+ // sdk/platforms/android-19/data/res/values.attrs_manifest.xml
+ .setOnReadValidator(new MultiValueValidator(
+ "normal", "dangerous", "signature", "signatureOrSystem"))),
+
+ /**
+ * Permission-tree (contained in manifest).
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/permission-tree-element.html>
+ * Permission-tree Xml documentation</a>}
+ *
+ */
+ PERMISSION_TREE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME)),
+
+ /**
+ * Provider (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/provider-element.html>
+ * Provider Xml documentation</a>}
+ */
+ PROVIDER(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME)
+ .setIsPackageDependent()),
+
+ /**
+ * Receiver (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/receiver-element.html>
+ * Receiver Xml documentation</a>}
+ */
+ RECEIVER(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Screen (contained in compatible-screens)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/compatible-screens-element.html>
+ * Receiver Xml documentation</a>}
+ */
+ SCREEN(MergeType.MERGE, new TwoAttributesBasedKeyResolver(
+ new AttributeBasedNodeKeyResolver(ANDROID_URI, "screenSize"),
+ new AttributeBasedNodeKeyResolver(ANDROID_URI, "screenDensity"))),
+
+ /**
+ * Service (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/application-element.html>
+ * Service Xml documentation</a>}
+ */
+ SERVICE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(SdkConstants.ATTR_NAME).setIsPackageDependent()),
+
+ /**
+ * Supports-gl-texture (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/supports-gl-texture-element.html>
+ * Support-screens Xml documentation</a>}
+ */
+ SUPPORTS_GL_TEXTURE(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Support-screens (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/supports-screens-element.html>
+ * Support-screens Xml documentation</a>}
+ */
+ SUPPORTS_SCREENS(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Uses-configuration (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-configuration-element.html>
+ * Support-screens Xml documentation</a>}
+ */
+ USES_CONFIGURATION(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER),
+
+ /**
+ * Uses-feature (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-feature-element.html>
+ * Uses-feature Xml documentation</a>}
+ */
+ USES_FEATURE(MergeType.MERGE, NAME_AND_GLESVERSION_KEY_RESOLVER,
+ AttributeModel.newModel(AndroidManifest.ATTRIBUTE_REQUIRED)
+ .setDefaultValue(SdkConstants.VALUE_TRUE)
+ .setOnReadValidator(BOOLEAN_VALIDATOR)
+ .setMergingPolicy(AttributeModel.OR_MERGING_POLICY),
+ AttributeModel.newModel(AndroidManifest.ATTRIBUTE_GLESVERSION)
+ .setDefaultValue("0x00010000")
+ .setOnReadValidator(new Hexadecimal32BitsWithMinimumValue(0x00010000))),
+
+ /**
+ * Use-library (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-library-element.html>
+ * Use-library Xml documentation</a>}
+ */
+ USES_LIBRARY(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER,
+ AttributeModel.newModel(AndroidManifest.ATTRIBUTE_REQUIRED)
+ .setDefaultValue(SdkConstants.VALUE_TRUE)
+ .setOnReadValidator(BOOLEAN_VALIDATOR)
+ .setMergingPolicy(AttributeModel.OR_MERGING_POLICY)),
+
+ /**
+ * Uses-permission (contained in application)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-permission-element.html>
+ * Uses-permission Xml documentation</a>}
+ */
+ USES_PERMISSION(MergeType.MERGE, DEFAULT_NAME_ATTRIBUTE_RESOLVER),
+
+ /**
+ * Uses-sdk (contained in manifest)
+ * <br>
+ * <b>See also : </b>
+ * {@link <a href=http://developer.android.com/guide/topics/manifest/uses-sdk-element.html>
+ * Uses-sdk Xml documentation</a>}
+ */
+ USES_SDK(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER,
+ AttributeModel.newModel("minSdkVersion")
+ .setDefaultValue(SdkConstants.VALUE_1)
+ .setMergingPolicy(AttributeModel.NO_MERGING_POLICY),
+ AttributeModel.newModel("maxSdkVersion")
+ .setMergingPolicy(AttributeModel.NO_MERGING_POLICY),
+ // TODO : model target's default value is minSdkVersion value.
+ AttributeModel.newModel("targetSdkVersion")
+ .setMergingPolicy(AttributeModel.NO_MERGING_POLICY)
+ ),
+
+ /**
+ * Custom tag for any application specific element
+ */
+ CUSTOM(MergeType.MERGE, DEFAULT_NO_KEY_NODE_RESOLVER);
+
+
+ private final MergeType mMergeType;
+ private final NodeKeyResolver mNodeKeyResolver;
+ private final ImmutableList<AttributeModel> mAttributeModels;
+ private final boolean mMultipleDeclarationAllowed;
+
+ NodeTypes(
+ @NonNull MergeType mergeType,
+ @NonNull NodeKeyResolver nodeKeyResolver,
+ @Nullable AttributeModel.Builder... attributeModelBuilders) {
+ this(mergeType, nodeKeyResolver, false, attributeModelBuilders);
+ }
+
+ NodeTypes(
+ @NonNull MergeType mergeType,
+ @NonNull NodeKeyResolver nodeKeyResolver,
+ boolean mutipleDeclarationAllowed,
+ @Nullable AttributeModel.Builder... attributeModelBuilders) {
+ this.mMergeType = Preconditions.checkNotNull(mergeType);
+ this.mNodeKeyResolver = Preconditions.checkNotNull(nodeKeyResolver);
+ @NonNull ImmutableList.Builder<AttributeModel> attributeModels =
+ new ImmutableList.Builder<AttributeModel>();
+ if (attributeModelBuilders != null) {
+ for (AttributeModel.Builder attributeModelBuilder : attributeModelBuilders) {
+ attributeModels.add(attributeModelBuilder.build());
+ }
+ }
+ this.mAttributeModels = attributeModels.build();
+ this.mMultipleDeclarationAllowed = mutipleDeclarationAllowed;
+ }
+
+ @NonNull
+ NodeKeyResolver getNodeKeyResolver() {
+ return mNodeKeyResolver;
+ }
+
+ ImmutableList<AttributeModel> getAttributeModels() {
+ return mAttributeModels.asList();
+ }
+
+ @Nullable
+ AttributeModel getAttributeModel(XmlNode.NodeName attributeName) {
+ // mAttributeModels could be replaced with a Map if the number of models grows.
+ for (AttributeModel attributeModel : mAttributeModels) {
+ if (attributeModel.getName().equals(attributeName)) {
+ return attributeModel;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the Xml name for this node type
+ */
+ String toXmlName() {
+ return SdkUtils.constantNameToXmlName(this.name());
+ }
+
+ /**
+ * Returns the {@link NodeTypes} instance from an xml element name (without namespace
+ * decoration). For instance, an xml element
+ * <pre>
+ * {@code
+ * <activity android:name="foo">
+ * ...
+ * </activity>}
+ * </pre>
+ * has a xml simple name of "activity" which will resolve to {@link NodeTypes#ACTIVITY} value.
+ *
+ * Note : a runtime exception will be generated if no mapping from the simple name to a
+ * {@link com.android.manifmerger.ManifestModel.NodeTypes} exists.
+ *
+ * @param xmlSimpleName the xml (lower-hyphen separated words) simple name.
+ * @return the {@link NodeTypes} associated with that element name.
+ */
+ static NodeTypes fromXmlSimpleName(String xmlSimpleName) {
+ String constantName = SdkUtils.xmlNameToConstantName(xmlSimpleName);
+
+ try {
+ return NodeTypes.valueOf(constantName);
+ } catch (IllegalArgumentException e) {
+ // if this element name is not a known tag, we categorize it as 'custom' which will
+ // be simply merged. It will prevent us from catching simple spelling mistakes but
+ // extensibility is a must have feature.
+ return NodeTypes.CUSTOM;
+ }
+ }
+
+ MergeType getMergeType() {
+ return mMergeType;
+ }
+
+ /**
+ * Returns true if multiple declaration for the same type and key are allowed or false if
+ * there must be only one declaration of this element for a particular key value.
+ */
+ boolean areMultipleDeclarationAllowed() {
+ return mMultipleDeclarationAllowed;
+ }
+ }
+}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergeType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergeType.java
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergeType.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/MergeType.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/Merger.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Merger.java
new file mode 100644
index 0000000..7367c5d
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Merger.java
@@ -0,0 +1,215 @@
+/*
+ * 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.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+/**
+ * Command line interface to the {@link ManifestMerger2}
+ */
+public class Merger {
+
+ public static void main(String[] args) {
+ try {
+ System.exit(new Merger().process(args));
+ } catch (FileNotFoundException e) {
+ System.exit(1);
+ }
+ System.exit(0);
+ }
+
+ public int process(String[] args) throws FileNotFoundException {
+
+ Iterator<String> arguments = Arrays.asList(args).iterator();
+ // first pass to get all mandatory parameters.
+ String mainManifest = null;
+ StdLogger.Level logLevel = StdLogger.Level.INFO;
+ ILogger logger = new StdLogger(logLevel);
+ while (arguments.hasNext()) {
+ String selector = arguments.next();
+ if (!selector.startsWith("--")) {
+ logger.error(null /* throwable */,
+ "Invalid parameter " + selector + ", expected a command switch");
+ return 1;
+ }
+ if ("--usage".equals(selector)) {
+ usage();
+ return 0;
+ }
+ if (!arguments.hasNext()) {
+ logger.error(null /* throwable */,
+ "Command switch " + selector + " has no value associated");
+ return 1;
+ }
+ String value = arguments.next();
+
+ if ("--main".equals(selector)) {
+ mainManifest = value;
+ }
+ if ("--log".equals(selector)) {
+ logLevel = StdLogger.Level.valueOf(value);
+ }
+ }
+
+ if (mainManifest == null) {
+ System.err.println("--main command switch not provided.");
+ return 1;
+ }
+
+ // recreate the logger with the provided log level for the rest of the processing.
+ logger = createLogger(logLevel);
+ File mainManifestFile = checkPath(mainManifest);
+ ManifestMerger2.Invoker invoker = createInvoker(
+ mainManifestFile, logger);
+
+ // second pass, get optional parameters and store them in the invoker.
+ arguments = Arrays.asList(args).iterator();
+ File outFile = null;
+
+ // first pass to get all mandatory parameters.
+ while (arguments.hasNext()) {
+ String selector = arguments.next();
+ String value = arguments.next();
+ if (Strings.isNullOrEmpty(value)) {
+ logger.error(null /* throwable */,
+ "Empty value for switch " + selector);
+ return 1;
+ }
+ if ("--libs".equals(selector)) {
+ StringTokenizer stringTokenizer = new StringTokenizer(value, File.pathSeparator);
+ while (stringTokenizer.hasMoreElements()) {
+ File library = checkPath(stringTokenizer.nextToken());
+ invoker.addLibraryManifest(library);
+ }
+ }
+ if ("--overlays".equals(selector)) {
+ StringTokenizer stringTokenizer = new StringTokenizer(value, File.pathSeparator);
+ while (stringTokenizer.hasMoreElements()) {
+ File library = checkPath(stringTokenizer.nextToken());
+ invoker.addFlavorAndBuildTypeManifest(library);
+ }
+
+ }
+ if ("--property".equals(selector)) {
+ if (!value.contains("=")) {
+ logger.error(null /* throwable */,
+ "Invalid property setting, should be NAME=VALUE format");
+ return 1;
+ }
+ try {
+ ManifestMerger2.SystemProperty systemProperty = ManifestMerger2.SystemProperty
+ .valueOf(value.substring(0, value.indexOf('='))
+ .toUpperCase(Locale.ENGLISH));
+ invoker.setOverride(systemProperty, value.substring(value.indexOf('=') + 1));
+ } catch (IllegalArgumentException e) {
+ logger.error(e, "Invalid property name "+ value.substring(0, value.indexOf('='))
+ + ", allowed properties are : " + Joiner
+ .on(',').join(ManifestMerger2.SystemProperty.values()));
+ return 1;
+ }
+ }
+ if ("--placeholder".equals(selector)) {
+ if (!value.contains("=")) {
+ logger.error(null /* throwable */,
+ "Invalid placeholder setting, should be NAME=VALUE format");
+ return 1;
+ }
+ invoker.setPlaceHolderValue(value.substring(0, value.indexOf('=')),
+ value.substring(value.indexOf('=') + 1));
+ }
+ if ("--out".equals(selector)) {
+ outFile = new File(value);
+ }
+ }
+ try {
+ MergingReport merge = invoker.merge();
+ if (merge.getResult().isSuccess()) {
+ String mergedDocument = merge.getMergedDocument(MergingReport.MergedManifestKind.MERGED);
+ if (mergedDocument != null) {
+ if (outFile != null) {
+ try {
+ Files.write(mergedDocument, outFile, Charsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ System.out.println(mergedDocument);
+ }
+ }
+ } else {
+ for (MergingReport.Record record : merge.getLoggingRecords()) {
+ System.err.println(record);
+ }
+ }
+ } catch (ManifestMerger2.MergeFailureException e) {
+ logger.error(e, "Exception while merging manifests");
+ return 1;
+ }
+ return 0;
+ }
+
+ protected ManifestMerger2.Invoker createInvoker(@NonNull File mainManifestFile,
+ @NonNull ILogger logger) {
+ return ManifestMerger2.newMerger(mainManifestFile, logger, ManifestMerger2.MergeType.APPLICATION);
+ }
+
+ public static void usage() {
+ System.out.println("Android Manifest Merger Tool Version 2\n");
+ System.out.println("Usage:");
+ System.out.println("Merger --main mainAndroidManifest.xml");
+ System.out.println("\t--log [VERBOSE, INFO, WARNING, ERROR]");
+ System.out.println("\t--libs [path separated list of lib's manifests]");
+ System.out.println("\t--overlays [path separated list of overlay's manifests]");
+ System.out.println("\t--property ["
+ + Joiner.on(" | ").join(ManifestMerger2.SystemProperty.values())
+ + "=value]");
+ System.out.println("\t--placeholder [name=value]");
+ System.out.println("\t--out [path of the output file]");
+ }
+
+
+ @VisibleForTesting
+ protected File checkPath(@NonNull String path) throws FileNotFoundException {
+ @NonNull File file = new File(path);
+ if (!file.exists()) {
+ System.err.println(path + " does not exist");
+ throw new FileNotFoundException(path);
+ }
+ return file;
+ }
+
+ @VisibleForTesting
+ protected ILogger createLogger(@NonNull StdLogger.Level level) {
+ return new StdLogger(level);
+ }
+
+}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java
new file mode 100644
index 0000000..bc80a0d
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergingReport.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.concurrency.Immutable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.utils.ILogger;
+import com.google.common.base.CaseFormat;
+import com.google.common.collect.ImmutableList;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.EnumMap;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Contains the result of 2 files merging.
+ *
+ * TODO: more work necessary, this is pretty raw as it stands.
+ */
+ at Immutable
+public class MergingReport {
+
+ public enum MergedManifestKind {
+ /**
+ * Merged manifest file
+ */
+ MERGED,
+
+ /**
+ * Merged manifest file with Instant Run related decorations.
+ */
+ INSTANT_RUN,
+
+ /**
+ * Merged manifest file with unresolved placeholders encoded to be AAPT friendly.
+ */
+ AAPT_SAFE,
+
+ /**
+ * Blame file for merged manifest file.
+ */
+ BLAME
+ }
+
+ @NonNull
+ private final Map<MergedManifestKind, String> mMergedDocuments;
+ @NonNull
+ private final Result mResult;
+ // list of logging events, ordered by their recording time.
+ @NonNull
+ private final ImmutableList<Record> mRecords;
+ @NonNull
+ private final ImmutableList<String> mIntermediaryStages;
+ @NonNull
+ private final Actions mActions;
+
+ private MergingReport(@NonNull Map<MergedManifestKind, String> mergedDocuments,
+ @NonNull Result result,
+ @NonNull ImmutableList<Record> records,
+ @NonNull ImmutableList<String> intermediaryStages,
+ @NonNull Actions actions) {
+ mMergedDocuments = mergedDocuments;
+ mResult = result;
+ mRecords = records;
+ mIntermediaryStages = intermediaryStages;
+ mActions = actions;
+ }
+
+ /**
+ * dumps all logging records to a logger.
+ */
+ public void log(@NonNull ILogger logger) {
+ for (Record record : mRecords) {
+ switch(record.mSeverity) {
+ case WARNING:
+ logger.warning(record.toString());
+ break;
+ case ERROR:
+ logger.error(null /* throwable */, record.toString());
+ break;
+ case INFO:
+ logger.verbose(record.toString());
+ break;
+ default:
+ logger.error(null /* throwable */, "Unhandled record type " + record.mSeverity);
+ }
+ }
+ mActions.log(logger);
+
+ if (!mResult.isSuccess()) {
+ logger.warning("\nSee http://g.co/androidstudio/manifest-merger for more information"
+ + " about the manifest merger.\n");
+ }
+ }
+
+ @Nullable
+ public String getMergedDocument(MergedManifestKind state) {
+ return mMergedDocuments.get(state);
+ }
+
+ /**
+ * Returns all the merging intermediary stages if
+ * {@link com.android.manifmerger.ManifestMerger2.Invoker.Feature#KEEP_INTERMEDIARY_STAGES}
+ * is set.
+ */
+ @NonNull
+ public ImmutableList<String> getIntermediaryStages() {
+ return mIntermediaryStages;
+ }
+
+ /**
+ * Overall result of the merging process.
+ */
+ public enum Result {
+ SUCCESS,
+
+ WARNING,
+
+ ERROR;
+
+ public boolean isSuccess() {
+ return this == SUCCESS || this == WARNING;
+ }
+
+ public boolean isWarning() {
+ return this == WARNING;
+ }
+
+ public boolean isError() {
+ return this == ERROR;
+ }
+ }
+
+ @NonNull
+ public Result getResult() {
+ return mResult;
+ }
+
+ @NonNull
+ public ImmutableList<Record> getLoggingRecords() {
+ return mRecords;
+ }
+
+ @NonNull
+ public Actions getActions() {
+ return mActions;
+ }
+
+ @NonNull
+ public String getReportString() {
+ switch (mResult) {
+ case SUCCESS:
+ return "Manifest merger executed successfully";
+ case WARNING:
+ return mRecords.size() > 1
+ ? "Manifest merger exited with warnings, see logs"
+ : "Manifest merger warning : " + mRecords.get(0).mLog;
+ case ERROR:
+ return mRecords.size() > 1
+ ? "Manifest merger failed with multiple errors, see logs"
+ : "Manifest merger failed : " + mRecords.get(0).mLog;
+ default:
+ return "Manifest merger returned an invalid result " + mResult;
+ }
+ }
+
+ /**
+ * Log record. This is used to give users some information about what is happening and
+ * what might have gone wrong.
+ */
+ public static class Record {
+
+
+ public enum Severity {WARNING, ERROR, INFO }
+
+ @NonNull
+ private final Severity mSeverity;
+ @NonNull
+ private final String mLog;
+ @NonNull
+ private final SourceFilePosition mSourceLocation;
+
+ private Record(
+ @NonNull SourceFilePosition sourceLocation,
+ @NonNull Severity severity,
+ @NonNull String mLog) {
+ this.mSourceLocation = sourceLocation;
+ this.mSeverity = severity;
+ this.mLog = mLog;
+ }
+
+ @NonNull
+ public Severity getSeverity() {
+ return mSeverity;
+ }
+
+ @NonNull
+ public String getMessage() {
+ return mLog;
+ }
+
+ @NonNull
+ public SourceFilePosition getSourceLocation() {
+ return mSourceLocation;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return mSourceLocation.toString() // needs short string.
+ + " "
+ + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, mSeverity.toString())
+ + ":\n\t"
+ + mLog;
+ }
+ }
+
+ /**
+ * This builder is used to accumulate logging, action recording and intermediary results as
+ * well as final result of the merging activity.
+ *
+ * Once the merging is finished, the {@link #build()} is called to return an immutable version
+ * of itself with all the logging, action recordings and xml files obtainable.
+ *
+ */
+ static class Builder {
+
+ private Map<MergedManifestKind, String> mergedDocuments =
+ new EnumMap<MergedManifestKind, String>(MergedManifestKind.class);
+ @NonNull
+ private ImmutableList.Builder<Record> mRecordBuilder = new ImmutableList.Builder<Record>();
+ @NonNull
+ private ImmutableList.Builder<String> mIntermediaryStages = new ImmutableList.Builder<String>();
+ @Nullable
+ private boolean mHasWarnings = false;
+ private boolean mHasErrors = false;
+ @NonNull
+ private ActionRecorder mActionRecorder = new ActionRecorder();
+ private final ILogger mLogger;
+
+ Builder(ILogger logger) {
+ mLogger = logger;
+ }
+
+ Builder setMergedDocument(@NonNull MergedManifestKind mergedManifestKind, @NonNull String mergedDocument) {
+ this.mergedDocuments.put(mergedManifestKind, mergedDocument);
+ return this;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ Builder addMessage(@NonNull SourceFile sourceFile,
+ int line,
+ int column,
+ @NonNull Record.Severity severity,
+ @NonNull String message) {
+ // The line and column used are 1-based, but SourcePosition uses zero-based.
+ return addMessage(
+ new SourceFilePosition(sourceFile, new SourcePosition(line - 1, column -1, -1)),
+ severity,
+ message);
+ }
+
+ @NonNull
+ Builder addMessage(@NonNull SourceFile sourceFile,
+ @NonNull Record.Severity severity,
+ @NonNull String message) {
+ return addMessage(
+ new SourceFilePosition(sourceFile, SourcePosition.UNKNOWN),
+ severity,
+ message);
+ }
+
+ @NonNull
+ Builder addMessage(@NonNull SourceFilePosition sourceFilePosition,
+ @NonNull Record.Severity severity,
+ @NonNull String message) {
+ switch (severity) {
+ case ERROR:
+ mHasErrors = true;
+ break;
+ case WARNING:
+ mHasWarnings = true;
+ break;
+ }
+ mRecordBuilder.add(new Record(sourceFilePosition, severity, message));
+ return this;
+ }
+
+ @NonNull
+ Builder addMergingStage(@NonNull String xml) {
+ mIntermediaryStages.add(xml);
+ return this;
+ }
+
+ /**
+ * Returns true if some fatal errors were reported.
+ */
+ boolean hasErrors() {
+ return mHasErrors;
+ }
+
+ @NonNull
+ ActionRecorder getActionRecorder() {
+ return mActionRecorder;
+ }
+
+ @NonNull
+ MergingReport build() {
+ Result result = mHasErrors
+ ? Result.ERROR
+ : mHasWarnings
+ ? Result.WARNING
+ : Result.SUCCESS;
+
+ return new MergingReport(
+ mergedDocuments,
+ result,
+ mRecordBuilder.build(),
+ mIntermediaryStages.build(),
+ mActionRecorder.build());
+ }
+
+ public ILogger getLogger() {
+ return mLogger;
+ }
+
+ public String blame(XmlDocument document)
+ throws ParserConfigurationException, SAXException, IOException {
+ return mActionRecorder.build().blame(document);
+ }
+ }
+}
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/NodeOperationType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/NodeOperationType.java
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/NodeOperationType.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/NodeOperationType.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java
new file mode 100644
index 0000000..601f985
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/OrphanXmlElement.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.ManifestModel.NodeTypes;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+import org.w3c.dom.Element;
+
+/**
+ * An xml element that does not belong to a {@link com.android.manifmerger.XmlDocument}
+ */
+public class OrphanXmlElement extends XmlNode {
+
+ @NonNull
+ private final Element mXml;
+
+ @NonNull
+ private final NodeTypes mType;
+
+ public OrphanXmlElement(@NonNull Element xml) {
+
+ mXml = Preconditions.checkNotNull(xml);
+ NodeTypes nodeType;
+ String elementName = mXml.getNodeName();
+ // this is bit more complicated than it should be. Look first if there is a namespace
+ // prefix in the name, most elements don't. If they do, however, strip it off if it is the
+ // android prefix, but if it's custom namespace prefix, classify the node as CUSTOM.
+ int indexOfColon = elementName.indexOf(':');
+ if (indexOfColon != -1) {
+ String androidPrefix = XmlUtils.lookupNamespacePrefix(xml, SdkConstants.ANDROID_URI);
+ if (androidPrefix.equals(elementName.substring(0, indexOfColon))) {
+ nodeType = NodeTypes.fromXmlSimpleName(elementName.substring(indexOfColon + 1));
+ } else {
+ nodeType = NodeTypes.CUSTOM;
+ }
+ } else {
+ nodeType = NodeTypes.fromXmlSimpleName(elementName);
+ }
+ mType = nodeType;
+ }
+
+ /**
+ * Returns true if this xml element's {@link NodeTypes} is
+ * the passed one.
+ */
+ public boolean isA(NodeTypes type) {
+ return this.mType == type;
+ }
+
+ @NonNull
+ @Override
+ public Element getXml() {
+ return mXml;
+ }
+
+
+ @NonNull
+ @Override
+ public NodeKey getId() {
+ return new NodeKey(Strings.isNullOrEmpty(getKey())
+ ? getName().toString()
+ : getName().toString() + "#" + getKey());
+ }
+
+ @NonNull
+ @Override
+ public NodeName getName() {
+ return XmlNode.unwrapName(mXml);
+ }
+
+ /**
+ * Returns this xml element {@link NodeTypes}
+ */
+ @NonNull
+ public NodeTypes getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the unique key for this xml element within the xml file or null if there can be only
+ * one element of this type.
+ */
+ @Nullable
+ public String getKey() {
+ return mType.getNodeKeyResolver().getKey(mXml);
+ }
+
+ @NonNull
+ @Override
+ public SourcePosition getPosition() {
+ return SourcePosition.UNKNOWN;
+ }
+
+ @Override
+ @NonNull
+ public SourceFile getSourceFile() {
+ return SourceFile.UNKNOWN;
+ }
+}
+
diff --git a/base/build-system/manifest-merger/src/main/java/com/android/manifmerger/OtherOperationType.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/OtherOperationType.java
similarity index 100%
rename from base/build-system/manifest-merger/src/main/java/com/android/manifmerger/OtherOperationType.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/OtherOperationType.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderEncoder.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderEncoder.java
new file mode 100644
index 0000000..581f9f4
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderEncoder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+
+import java.util.regex.Matcher;
+
+/**
+ * encode all non resolved placeholders key names.
+ */
+public class PlaceholderEncoder {
+
+ /**
+ * Visits a document's entire tree and check each attribute for a placeholder existence.
+ * If one is found, encode its name so tools like aapt will not object invalid characters and
+ * such.
+ * <p>
+ *
+ * @param xmlDocument the xml document to visit
+ */
+ public static void visit(@NonNull XmlDocument xmlDocument) {
+
+ visit(xmlDocument.getRootNode());
+ }
+
+ private static void visit(@NonNull XmlElement xmlElement) {
+
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ Matcher matcher = PlaceholderHandler.PATTERN.matcher(xmlAttribute.getValue());
+ if (matcher.matches()) {
+ String encodedValue = "dollar_openBracket_" + matcher.group(2) + "_closeBracket";
+ xmlAttribute.getXml().setValue(encodedValue);
+ }
+ }
+ for (XmlElement childElement : xmlElement.getMergeableElements()) {
+ visit(childElement);
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java
new file mode 100644
index 0000000..561669a
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PlaceholderHandler.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourcePosition;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Replaces all placeholders of the form ${name} with a tool invocation provided value
+ */
+public class PlaceholderHandler {
+
+ // interesting placeholders names that are documented to be automatically provided.
+ public static final String INSTRUMENTATION_RUNNER = "instrumentationRunner";
+ public static final String PACKAGE_NAME = "packageName";
+ public static final String APPLICATION_ID = "applicationId";
+
+ // regular expression to recognize placeholders like ${name}, potentially surrounded by a
+ // prefix and suffix string. this will split in 3 groups, the prefix, the placeholder name, and
+ // the suffix.
+ static final Pattern PATTERN = Pattern.compile("([^\\$]*)\\$\\{([^\\}]*)\\}(.*)");
+
+ /**
+ * Interface to provide a value for a placeholder key.
+ * @param <T> the key type
+ */
+ public interface KeyBasedValueResolver<T> {
+
+ /**
+ * Returns a placeholder value for the placeholder key or null if none exists.
+ */
+ @Nullable
+ String getValue(@NonNull T key);
+ }
+
+ /**
+ * Returns true if the passed string is a placeholder value, false otherwise.
+ */
+ public static boolean isPlaceHolder(@NonNull String string) {
+ return PATTERN.matcher(string).matches();
+ }
+
+ /**
+ * Visits a document's entire tree and check each attribute for a placeholder existence.
+ * If one is found, delegate to the provided {@link KeyBasedValueResolver} to provide a value
+ * for the placeholder.
+ * <p>
+ * If no value is provided, an error will be generated.
+ *
+ * @param xmlDocument the xml document to visit
+ * @param valueProvider the placeholder value provider.
+ * @param mergingReportBuilder to report errors and log actions.
+ */
+ public static void visit(
+ @NonNull ManifestMerger2.MergeType mergeType,
+ @NonNull XmlDocument xmlDocument,
+ @NonNull KeyBasedValueResolver<String> valueProvider,
+ @NonNull MergingReport.Builder mergingReportBuilder) {
+
+ visit(mergeType, xmlDocument.getRootNode(), valueProvider, mergingReportBuilder);
+ }
+
+ private static void visit(
+ @NonNull ManifestMerger2.MergeType mergeType,
+ @NonNull XmlElement xmlElement,
+ @NonNull KeyBasedValueResolver<String> valueProvider,
+ @NonNull MergingReport.Builder mergingReportBuilder) {
+
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+
+ StringBuilder resultString = new StringBuilder();
+ String inputString = xmlAttribute.getValue();
+ Matcher matcher = PATTERN.matcher(inputString);
+ if (matcher.matches()) {
+ while (matcher.matches()) {
+ String placeholderValue = valueProvider.getValue(matcher.group(2));
+ // whatever precedes the placeholder key is added back to the string.
+ resultString.append(matcher.group(1));
+ if (placeholderValue == null) {
+ // if this is a library, ignore the failure
+ MergingReport.Record.Severity severity =
+ mergeType == ManifestMerger2.MergeType.LIBRARY
+ ? MergingReport.Record.Severity.INFO
+ : MergingReport.Record.Severity.ERROR;
+
+ xmlAttribute.addMessage(mergingReportBuilder, severity,
+ String.format(
+ "Attribute %1$s at %2$s requires a placeholder substitution"
+ + " but no value for <%3$s> is provided.",
+ xmlAttribute.getId(),
+ xmlAttribute.printPosition(),
+ matcher.group(2)
+ ));
+ // we add back the placeholder key, since this is not an error for libraries
+ resultString.append("${");
+ resultString.append(matcher.group(2));
+ resultString.append("}");
+ } else {
+ // record the attribute set
+ mergingReportBuilder.getActionRecorder().recordAttributeAction(
+ xmlAttribute,
+ SourcePosition.UNKNOWN,
+ Actions.ActionType.INJECTED,
+ null /* attributeOperationType */);
+
+ // substitute the placeholder key with its value.
+ resultString.append(placeholderValue);
+ }
+ // the new input string is the tail of the previous match, as it may contain
+ // more placeholders to substitute.
+ inputString = matcher.group(3);
+ // reset the pattern matching with that new string to test for more placeholders
+ matcher = PATTERN.matcher(inputString);
+ }
+ // append the last remainder (without placeholders) in the result string.
+ resultString.append(inputString);
+ xmlAttribute.getXml().setValue(resultString.toString());
+ }
+ }
+ for (XmlElement childElement : xmlElement.getMergeableElements()) {
+ visit(mergeType, childElement, valueProvider, mergingReportBuilder);
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java
new file mode 100644
index 0000000..95b968e
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PostValidator.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.Actions.ActionType;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Validator that runs post merging activities and verifies that all "tools:" instructions
+ * triggered an action by the merging tool.
+ * <p>
+ *
+ * This is primarily to catch situations like a user entered a tools:remove="foo" directory on one
+ * of its elements and that particular attribute was never removed during the merges possibly
+ * indicating an unforeseen change of configuration.
+ * <p>
+ *
+ * Most of the output from this validation should be warnings.
+ */
+public class PostValidator {
+
+ /**
+ * Post validation of the merged document. This will essentially check that all merging
+ * instructions were applied at least once.
+ *
+ * @param xmlDocument merged document to check.
+ * @param mergingReport report for errors and warnings.
+ */
+ public static void validate(
+ @NonNull XmlDocument xmlDocument,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ Preconditions.checkNotNull(xmlDocument);
+ Preconditions.checkNotNull(mergingReport);
+ enforceAndroidNamespaceDeclaration(xmlDocument);
+ reOrderElements(xmlDocument.getRootNode());
+ validate(xmlDocument.getRootNode(),
+ mergingReport.getActionRecorder().build(),
+ mergingReport);
+ }
+
+ /**
+ * Enforces {@link com.android.SdkConstants#ANDROID_URI} declaration in the top level element.
+ * It is possible that the original manifest file did not contain any attribute declaration,
+ * therefore not requiring a xmlns: declaration. Yet the implicit elements handling may have
+ * added attributes requiring the namespace declaration.
+ */
+ private static void enforceAndroidNamespaceDeclaration(@NonNull XmlDocument xmlDocument) {
+ XmlElement manifest = xmlDocument.getRootNode();
+ for (XmlAttribute xmlAttribute : manifest.getAttributes()) {
+ if (xmlAttribute.getXml().getName().startsWith(SdkConstants.XMLNS) &&
+ SdkConstants.ANDROID_URI.equals(xmlAttribute.getValue())) {
+ return;
+ }
+ }
+ // if we are here, we did not find the namespace declaration, add it.
+ manifest.getXml().setAttribute(SdkConstants.XMLNS + ":" + "android",
+ SdkConstants.ANDROID_URI);
+ }
+
+ /**
+ * Reorder child elements :
+ * <li>
+ * <ul> <application> is moved last in the list of children
+ * of the <manifest> element.
+ * <ul> uses-sdk is moved first in the list of children of the <manifest> element </ul>
+ * </li>
+ * @param xmlElement the root element of the manifest document.
+ */
+ private static void reOrderElements(@NonNull XmlElement xmlElement) {
+
+ reOrderApplication(xmlElement);
+ reOrderUsesSdk(xmlElement);
+ }
+
+ /**
+ * Reorder application element
+ *
+ * @param xmlElement the root element of the manifest document.
+ */
+ private static void reOrderApplication(@NonNull XmlElement xmlElement) {
+
+ // look up application element.
+ Optional<XmlElement> element = xmlElement
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.APPLICATION, null);
+ if (!element.isPresent()) {
+ return;
+ }
+ XmlElement applicationElement = element.get();
+
+ List<Node> comments = XmlElement.getLeadingComments(applicationElement.getXml());
+
+ // move the application's comments if any.
+ for (Node comment : comments) {
+ xmlElement.getXml().removeChild(comment);
+ xmlElement.getXml().appendChild(comment);
+ }
+ // remove the application element and add it back, it will be automatically placed last.
+ xmlElement.getXml().removeChild(applicationElement.getXml());
+ xmlElement.getXml().appendChild(applicationElement.getXml());
+ }
+
+ /**
+ * Reorder uses-sdk element
+ *
+ * @param xmlElement the root element of the manifest document.
+ */
+ private static void reOrderUsesSdk(@NonNull XmlElement xmlElement) {
+
+ // look up application element.
+ Optional<XmlElement> element = xmlElement
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.USES_SDK, null);
+ if (!element.isPresent()) {
+ return;
+ }
+
+ XmlElement usesSdk = element.get();
+ Node firstChild = xmlElement.getXml().getFirstChild();
+ // already the first element ?
+ if (firstChild == usesSdk.getXml()) {
+ return;
+ }
+
+ List<Node> comments = XmlElement.getLeadingComments(usesSdk.getXml());
+
+ // move the application's comments if any.
+ for (Node comment : comments) {
+ xmlElement.getXml().removeChild(comment);
+ xmlElement.getXml().insertBefore(comment, firstChild);
+ }
+ // remove the application element and add it back, it will be automatically placed last.
+ xmlElement.getXml().removeChild(usesSdk.getXml());
+ xmlElement.getXml().insertBefore(usesSdk.getXml(), firstChild);
+ }
+
+ /**
+ * Validate an xml element and recursively its children elements, ensuring that all merging
+ * instructions were applied.
+ *
+ * @param xmlElement xml element to validate.
+ * @param actions the actions recorded during the merging activities.
+ * @param mergingReport report for errors and warnings.
+ * instructions were applied once or {@link MergingReport.Result#WARNING} otherwise.
+ */
+ private static void validate(
+ @NonNull XmlElement xmlElement,
+ @NonNull Actions actions,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ NodeOperationType operationType = xmlElement.getOperationType();
+ switch (operationType) {
+ case REPLACE:
+ // we should find at least one rejected twin.
+ if (!isNodeOperationPresent(xmlElement, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s was tagged at %2$s:%3$d to replace another declaration "
+ + "but no other declaration present",
+ xmlElement.getId(),
+ xmlElement.getDocument().getSourceFile().print(true),
+ xmlElement.getPosition().getStartLine() + 1
+ ));
+ }
+ break;
+ case REMOVE:
+ case REMOVE_ALL:
+ // we should find at least one rejected twin.
+ if (!isNodeOperationPresent(xmlElement, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s was tagged at %2$s:%3$d to remove other declarations "
+ + "but no other declaration present",
+ xmlElement.getId(),
+ xmlElement.getDocument().getSourceFile().print(true),
+ xmlElement.getPosition().getStartLine() + 1
+ ));
+ }
+ break;
+ }
+ validateAttributes(xmlElement, actions, mergingReport);
+ validateAndroidAttributes(xmlElement, mergingReport);
+ for (XmlElement child : xmlElement.getMergeableElements()) {
+ validate(child, actions, mergingReport);
+ }
+ }
+
+
+ /**
+ * Verifies that all merging attributes on a passed xml element were applied.
+ */
+ private static void validateAttributes(
+ @NonNull XmlElement xmlElement,
+ @NonNull Actions actions,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ @NonNull Collection<Map.Entry<XmlNode.NodeName, AttributeOperationType>> attributeOperations
+ = xmlElement.getAttributeOperations();
+ for (Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperation :
+ attributeOperations) {
+ switch (attributeOperation.getValue()) {
+ case REMOVE:
+ if (!isAttributeOperationPresent(
+ xmlElement, attributeOperation, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s@%2$s was tagged at %3$s:%4$d to remove other"
+ + " declarations but no other declaration present",
+ xmlElement.getId(),
+ attributeOperation.getKey(),
+ xmlElement.getDocument().getSourceFile().print(true),
+ xmlElement.getPosition().getStartLine() + 1
+ ));
+ }
+ break;
+ case REPLACE:
+ if (!isAttributeOperationPresent(
+ xmlElement, attributeOperation, actions, ActionType.REJECTED)) {
+ xmlElement.addMessage(mergingReport, MergingReport.Record.Severity.WARNING,
+ String.format(
+ "%1$s@%2$s was tagged at %3$s:%4$d to replace other"
+ + " declarations but no other declaration present",
+ xmlElement.getId(),
+ attributeOperation.getKey(),
+ xmlElement.getDocument().getSourceFile().print(true),
+ xmlElement.getPosition().getStartLine() + 1
+ ));
+ }
+ break;
+ }
+ }
+
+ }
+
+ /**
+ * Check in our list of applied actions that a particular
+ * {@link com.android.manifmerger.Actions.ActionType} action was recorded on the passed element.
+ * @return true if it was applied, false otherwise.
+ */
+ private static boolean isNodeOperationPresent(@NonNull XmlElement xmlElement,
+ @NonNull Actions actions,
+ ActionType action) {
+
+ for (Actions.NodeRecord nodeRecord : actions.getNodeRecords(xmlElement.getId())) {
+ if (nodeRecord.getActionType() == action) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check in our list of attribute actions that a particular
+ * {@link com.android.manifmerger.Actions.ActionType} action was recorded on the passed element.
+ * @return true if it was applied, false otherwise.
+ */
+ private static boolean isAttributeOperationPresent(@NonNull XmlElement xmlElement,
+ @NonNull Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperation,
+ @NonNull Actions actions,
+ ActionType action) {
+
+ for (Actions.AttributeRecord attributeRecord : actions.getAttributeRecords(
+ xmlElement.getId(), attributeOperation.getKey())) {
+ if (attributeRecord.getActionType() == action) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Validates all {@link com.android.manifmerger.XmlElement} attributes belonging to the
+ * {@link com.android.SdkConstants#ANDROID_URI} namespace.
+ *
+ * @param xmlElement xml element to check the attributes from.
+ * @param mergingReport report for errors and warnings.
+ */
+ private static void validateAndroidAttributes(@NonNull XmlElement xmlElement,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ if (xmlAttribute.getModel() != null) {
+ AttributeModel.Validator onWriteValidator = xmlAttribute.getModel()
+ .getOnWriteValidator();
+ if (onWriteValidator != null) {
+ onWriteValidator.validates(
+ mergingReport, xmlAttribute, xmlAttribute.getValue());
+ }
+ }
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java
new file mode 100644
index 0000000..856cb42
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/PreValidator.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.MergingReport.Record.Severity.ERROR;
+import static com.android.manifmerger.MergingReport.Record.Severity.WARNING;
+import static com.android.manifmerger.XmlNode.NodeKey;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.xml.AndroidManifest;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Validates a loaded {@link XmlDocument} and check for potential inconsistencies in the model due
+ * to user error or omission.
+ *
+ * This is implemented as a separate class so it can be invoked by tools independently from the
+ * merging process.
+ *
+ * This validator will check the state of the loaded xml document before any merging activity is
+ * attempted. It verifies things like a "tools:replace="foo" attribute has a "android:foo"
+ * attribute also declared on the same element (since we want to replace its value).
+ */
+public class PreValidator {
+
+ private PreValidator(){
+ }
+
+ /**
+ * Validates a loaded {@link com.android.manifmerger.XmlDocument} and return a status of the
+ * merging model.
+ *
+ * Will return one the following status :
+ * <ul>
+ * <li>{@link com.android.manifmerger.MergingReport.Result#SUCCESS} : the merging model is
+ * correct, merging should be attempted</li>
+ * <li>{@link com.android.manifmerger.MergingReport.Result#WARNING} : the merging model
+ * contains non fatal error, user should be notified, merging can be attempted</li>
+ * <li>{@link com.android.manifmerger.MergingReport.Result#ERROR} : the merging model
+ * contains errors, user must be notified, merging should not be attempted</li>
+ * </ul>
+ *
+ * A successful validation does not mean that the merging will be successful, it only means
+ * that the {@link com.android.SdkConstants#TOOLS_URI} instructions are correct and consistent.
+ *
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlDocument the loaded xml part.
+ * @return one the {@link com.android.manifmerger.MergingReport.Result} value.
+ */
+ @NonNull
+ public static MergingReport.Result validate(
+ @NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlDocument xmlDocument) {
+
+ validateManifestAttribute(
+ mergingReport, xmlDocument.getRootNode(), xmlDocument.getFileType());
+ return validate(mergingReport, xmlDocument.getRootNode());
+ }
+
+ @NonNull
+ private static MergingReport.Result validate(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlElement xmlElement) {
+
+ validateAttributeInstructions(mergingReport, xmlElement);
+
+ validateAndroidAttributes(mergingReport, xmlElement);
+
+ checkSelectorPresence(mergingReport, xmlElement);
+
+ // create a temporary hash map of children indexed by key to ensure key uniqueness.
+ Map<NodeKey, XmlElement> childrenKeys = new HashMap<NodeKey, XmlElement>();
+ for (XmlElement childElement : xmlElement.getMergeableElements()) {
+
+ // if this element is tagged with 'tools:node=removeAll', ensure it has no other
+ // attributes.
+ if (childElement.getOperationType() == NodeOperationType.REMOVE_ALL) {
+ validateRemoveAllOperation(mergingReport, childElement);
+ } else {
+ if (checkKeyPresence(mergingReport, childElement)) {
+ XmlElement twin = childrenKeys.get(childElement.getId());
+ if (twin != null && !childElement.getType().areMultipleDeclarationAllowed()) {
+ // we have 2 elements with the same identity, if they are equals,
+ // issue a warning, if not, issue an error.
+ String message = String.format(
+ "Element %1$s at %2$s duplicated with element declared at %3$s",
+ childElement.getId(),
+ childElement.printPosition(),
+ childrenKeys.get(childElement.getId()).printPosition());
+ if (twin.compareTo(childElement).isPresent()) {
+ childElement.addMessage(mergingReport, ERROR, message);
+ } else {
+ childElement.addMessage(mergingReport, WARNING, message);
+ }
+ }
+ childrenKeys.put(childElement.getId(), childElement);
+ }
+ validate(mergingReport, childElement);
+ }
+ }
+ return mergingReport.hasErrors()
+ ? MergingReport.Result.ERROR : MergingReport.Result.SUCCESS;
+ }
+
+ /**
+ * Validate an xml declaration with 'tools:node="removeAll" annotation. There should not
+ * be any other attribute declaration on this element.
+ */
+ private static void validateRemoveAllOperation(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlElement element) {
+
+ NamedNodeMap attributes = element.getXml().getAttributes();
+ if (attributes.getLength() > 1) {
+ List<String> extraAttributeNames = new ArrayList<String>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node item = attributes.item(i);
+ if (!(SdkConstants.TOOLS_URI.equals(item.getNamespaceURI()) &&
+ NodeOperationType.NODE_LOCAL_NAME.equals(item.getLocalName()))) {
+ extraAttributeNames.add(item.getNodeName());
+ }
+ }
+ String message = String.format(
+ "Element %1$s at %2$s annotated with 'tools:node=\"removeAll\"' cannot "
+ + "have other attributes : %3$s",
+ element.getId(),
+ element.printPosition(),
+ Joiner.on(',').join(extraAttributeNames)
+ );
+ element.addMessage(mergingReport, ERROR, message);
+ }
+ }
+
+ private static void checkSelectorPresence(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlElement element) {
+
+ Attr selectorAttribute =
+ element.getXml().getAttributeNodeNS(SdkConstants.TOOLS_URI, Selector.SELECTOR_LOCAL_NAME);
+ if (selectorAttribute!=null && !element.supportsSelector()) {
+ String message = String.format(
+ "Unsupported tools:selector=\"%1$s\" found on node %2$s at %3$s",
+ selectorAttribute.getValue(),
+ element.getId(),
+ element.printPosition());
+ element.addMessage(mergingReport, ERROR, message);
+ }
+ }
+
+ private static void validateManifestAttribute(
+ @NonNull MergingReport.Builder mergingReport, @NonNull XmlElement manifest, XmlDocument.Type fileType) {
+ Attr attributeNode = manifest.getXml().getAttributeNode(AndroidManifest.ATTRIBUTE_PACKAGE);
+ // it's ok for an overlay to not have a package name, it's not ok for a main manifest
+ // and it's a warning for a library.
+ if (attributeNode == null && fileType != XmlDocument.Type.OVERLAY) {
+ manifest.addMessage(mergingReport,
+ fileType == XmlDocument.Type.MAIN ? ERROR : WARNING,
+ String.format(
+ "Missing 'package' declaration in manifest at %1$s",
+ manifest.printPosition()));
+ }
+ }
+
+ /**
+ * Checks that an element which is supposed to have a key does have one.
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlElement xml element to check for key presence.
+ * @return true if the element has a valid key or false it does not need one or it is invalid.
+ */
+ private static boolean checkKeyPresence(
+ @NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlElement xmlElement) {
+ ManifestModel.NodeKeyResolver nodeKeyResolver = xmlElement.getType().getNodeKeyResolver();
+ ImmutableList<String> keyAttributesNames = nodeKeyResolver.getKeyAttributesNames();
+ if (keyAttributesNames.isEmpty()) {
+ return false;
+ }
+ if (Strings.isNullOrEmpty(xmlElement.getKey())) {
+ // we should have a key but we don't.
+ String message = keyAttributesNames.size() > 1
+ ? String.format(
+ "Missing one of the key attributes '%1$s' on element %2$s at %3$s",
+ Joiner.on(',').join(keyAttributesNames),
+ xmlElement.getId(),
+ xmlElement.printPosition())
+ : String.format(
+ "Missing '%1$s' key attribute on element %2$s at %3$s",
+ keyAttributesNames.get(0),
+ xmlElement.getId(),
+ xmlElement.printPosition());
+ xmlElement.addMessage(mergingReport, ERROR, message);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Validate attributes part of the {@link com.android.SdkConstants#ANDROID_URI}
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlElement xml element to check its attributes.
+ */
+ private static void validateAndroidAttributes(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlElement xmlElement) {
+ for (XmlAttribute xmlAttribute : xmlElement.getAttributes()) {
+ AttributeModel model = xmlAttribute.getModel();
+ if (model != null && model.getOnReadValidator() != null) {
+ model.getOnReadValidator().validates(
+ mergingReport, xmlAttribute, xmlAttribute.getValue());
+ }
+ }
+ }
+
+ /**
+ * Validates attributes part of the {@link com.android.SdkConstants#TOOLS_URI}
+ * @param mergingReport report to log warnings and errors.
+ * @param xmlElement xml element to check its attributes.
+ */
+ private static void validateAttributeInstructions(
+ @NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlElement xmlElement) {
+
+ for (Map.Entry<XmlNode.NodeName, AttributeOperationType> attributeOperationTypeEntry :
+ xmlElement.getAttributeOperations()) {
+
+ Optional<XmlAttribute> attribute = xmlElement
+ .getAttribute(attributeOperationTypeEntry.getKey());
+ switch(attributeOperationTypeEntry.getValue()) {
+ case STRICT:
+ break;
+ case REMOVE:
+ // check we are not provided a new value.
+ if (attribute.isPresent()) {
+ // Add one to startLine so the first line is displayed as 1.
+ xmlElement.addMessage(mergingReport, ERROR, String.format(
+ "tools:remove specified at line:%d for attribute %s, but "
+ + "attribute also declared at line:%d, "
+ + "do you want to use tools:replace instead ?",
+ xmlElement.getPosition().getStartLine() + 1,
+ attributeOperationTypeEntry.getKey(),
+ attribute.get().getPosition().getStartLine() + 1
+ ));
+ }
+ break;
+ case REPLACE:
+ // check we are provided a new value
+ if (!attribute.isPresent()) {
+ // Add one to startLine so the first line is displayed as 1.
+ xmlElement.addMessage(mergingReport, ERROR, String.format(
+ "tools:replace specified at line:%d for attribute %s, but "
+ + "no new value specified",
+ xmlElement.getPosition().getStartLine() + 1,
+ attributeOperationTypeEntry.getKey()
+ ));
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unhandled AttributeOperationType " +
+ attributeOperationTypeEntry.getValue());
+ }
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java
new file mode 100644
index 0000000..5eb7a2e
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Selector.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+/**
+ * Represents a selector to be able to identify manifest file xml elements.
+ */
+ at Immutable
+public class Selector {
+
+ /**
+ * local name for tools:selector attributes.
+ */
+ public static final String SELECTOR_LOCAL_NAME = "selector";
+
+ @NonNull private final String mPackageName;
+
+ public Selector(@NonNull String packageName) {
+ mPackageName = Preconditions.checkNotNull(packageName);
+ }
+
+ /**
+ * Returns true if the passed element is "selected" by this selector. If so, any action this
+ * selector decorated will be applied to the element.
+ */
+ boolean appliesTo(@NonNull XmlElement element) {
+ Optional<XmlAttribute> packageName = element.getDocument().getPackage();
+ return packageName.isPresent() && mPackageName.equals(packageName.get().getValue());
+ }
+
+ /**
+ * Returns true if the passed resolver can resolve this selector, false otherwise.
+ */
+ boolean isResolvable(@NonNull KeyResolver<String> resolver) {
+ return resolver.resolve(mPackageName) != null;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return mPackageName;
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java
new file mode 100644
index 0000000..1c77303
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ToolsInstructionsCleaner.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.MergingReport.Result.ERROR;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.utils.ILogger;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Removes all "tools:" statements from the resulting xml.
+ *
+ * All attributes belonging to the {@link com.android.SdkConstants#ANDROID_URI} namespace will be
+ * removed. If an element contained a "tools:node=\"remove\"" attribute, the element will be
+ * deleted.
+ */
+ at Immutable
+public class ToolsInstructionsCleaner {
+
+ private static final String REMOVE_OPERATION_XML_MAME =
+ NodeOperationType.REMOVE.toCamelCaseName();
+ private static final String REMOVE_ALL_OPERATION_XML_MAME =
+ NodeOperationType.REMOVE_ALL.toCamelCaseName();
+
+ /**
+ * Cleans all attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace.
+ *
+ * @param document the xml document to clean
+ * @param logger logger to use in case of errors and warnings.
+ * @return the cleaned document or null if an error occurred.
+ */
+ @Nullable
+ public static XmlDocument cleanToolsReferences(
+ @NonNull XmlDocument document,
+ @NonNull ILogger logger) {
+
+ Preconditions.checkNotNull(document);
+ Preconditions.checkNotNull(logger);
+ MergingReport.Result result = cleanToolsReferences(document.getRootNode().getXml(),
+ logger);
+ return result == MergingReport.Result.SUCCESS
+ ? document.reparse()
+ : null;
+ }
+
+ @NonNull
+ private static MergingReport.Result cleanToolsReferences(
+ @NonNull Element element,
+ @NonNull ILogger logger) {
+
+ NamedNodeMap namedNodeMap = element.getAttributes();
+ if (namedNodeMap != null) {
+ // make a copy of the original list of attributes as we will remove some during this
+ // process.
+ List<Node> attributes = new ArrayList<Node>();
+ for (int i = 0; i < namedNodeMap.getLength(); i++) {
+ attributes.add(namedNodeMap.item(i));
+ }
+ for (Node attribute : attributes) {
+ if (SdkConstants.TOOLS_URI.equals(attribute.getNamespaceURI())) {
+ // we need to special case when the element contained tools:node="remove"
+ // since it also needs to be deleted unless it had a selector.
+ // if this is ools:node="removeAll", we always delete the element whether or
+ // not there is a tools:selector.
+ boolean hasSelector = namedNodeMap.getNamedItemNS(
+ SdkConstants.TOOLS_URI, "selector") != null;
+ if (attribute.getLocalName().equals(NodeOperationType.NODE_LOCAL_NAME)
+ && (attribute.getNodeValue().equals(REMOVE_ALL_OPERATION_XML_MAME)
+ || (attribute.getNodeValue().equals(REMOVE_OPERATION_XML_MAME))
+ && !hasSelector)) {
+
+ if (element.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
+ logger.error(null /* Throwable */,
+ String.format(
+ "tools:node=\"%1$s\" not allowed on top level %2$s element",
+ attribute.getNodeValue(),
+ XmlNode.unwrapName(element)));
+ return ERROR;
+ } else {
+ element.getParentNode().removeChild(element);
+ }
+ } else {
+ // anything else, we just clean the attribute.
+ element.removeAttributeNS(
+ attribute.getNamespaceURI(), attribute.getLocalName());
+ }
+ }
+ // this could also be the xmlns:tools declaration.
+ if (attribute.getNodeName().startsWith(SdkConstants.XMLNS_PREFIX)
+ && SdkConstants.TOOLS_URI.equals(attribute.getNodeValue())) {
+ element.removeAttribute(attribute.getNodeName());
+ }
+ }
+ }
+ // make a copy of the element children since we will be removing some during
+ // this process, we don't want side effects.
+ NodeList childNodes = element.getChildNodes();
+ ImmutableList.Builder<Element> childElements = ImmutableList.builder();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ childElements.add((Element) node);
+ }
+ }
+ for (Element childElement : childElements.build()) {
+ if (cleanToolsReferences(childElement, logger) == ERROR) {
+ return ERROR;
+ }
+ }
+ return MergingReport.Result.SUCCESS;
+ }
+
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java
new file mode 100644
index 0000000..1b7e77f
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlAttribute.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+
+import org.w3c.dom.Attr;
+
+/**
+ * Defines an XML attribute inside a {@link XmlElement}.
+ *
+ * Basically a facade object on {@link Attr} objects with some added features like automatic
+ * namespace handling, manifest merger friendly identifiers and smart replacement of shortened
+ * full qualified class names using manifest node's package setting from the the owning Android's
+ * document.
+ */
+public class XmlAttribute extends XmlNode {
+
+ @NonNull
+ private final XmlElement mOwnerElement;
+ @NonNull
+ private final Attr mXml;
+ @Nullable
+ private final AttributeModel mAttributeModel;
+
+ /**
+ * Creates a new facade object to a {@link Attr} xml attribute in a
+ * {@link XmlElement}.
+ *
+ * @param ownerElement the xml node object owning this attribute.
+ * @param xml the xml definition of the attribute.
+ */
+ public XmlAttribute(
+ @NonNull XmlElement ownerElement,
+ @NonNull Attr xml,
+ @Nullable AttributeModel attributeModel) {
+ this.mOwnerElement = Preconditions.checkNotNull(ownerElement);
+ this.mXml = Preconditions.checkNotNull(xml);
+ this.mAttributeModel = attributeModel;
+ if (mAttributeModel != null && mAttributeModel.isPackageDependent()) {
+ String value = mXml.getValue();
+ if (value == null || value.isEmpty()) return;
+ // placeholders are never expanded.
+ if (!PlaceholderHandler.isPlaceHolder(value)) {
+ String pkg = mOwnerElement.getDocument().getPackageNameForAttributeExpansion();
+ // We know it's a shortened FQCN if it starts with a dot
+ // or does not contain any dot.
+ if (value.indexOf('.') == -1 || value.charAt(0) == '.') {
+ if (value.charAt(0) == '.') {
+ value = pkg + value;
+ } else {
+ value = pkg + '.' + value;
+ }
+ mXml.setValue(value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the attribute's name, providing isolation from details like namespaces handling.
+ */
+ @NonNull
+ @Override
+ public NodeName getName() {
+ return XmlNode.unwrapName(mXml);
+ }
+
+ /**
+ * Returns the attribute's value
+ */
+ @NonNull
+ public String getValue() {
+ return mXml.getValue();
+ }
+
+ /**
+ * Returns a display friendly identification string that can be used in machine and user
+ * readable messages.
+ */
+ @NonNull
+ @Override
+ public NodeKey getId() {
+ // (Id of the parent element)@(my name)
+ String myName = mXml.getNamespaceURI() == null ? mXml.getName() : mXml.getLocalName();
+ return new NodeKey(mOwnerElement.getId() + "@" + myName);
+ }
+
+ @NonNull
+ @Override
+ public SourcePosition getPosition() {
+ try {
+ return XmlDocument.getNodePosition(this);
+ } catch(Exception e) {
+ return SourcePosition.UNKNOWN;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Attr getXml() {
+ return mXml;
+ }
+
+ @Nullable
+ public AttributeModel getModel() {
+ return mAttributeModel;
+ }
+
+ @NonNull
+ XmlElement getOwnerElement() {
+ return mOwnerElement;
+ }
+
+ void mergeInHigherPriorityElement(@NonNull XmlElement higherPriorityElement,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ // does the higher priority has the same attribute as myself ?
+ Optional<XmlAttribute> higherPriorityAttributeOptional =
+ higherPriorityElement.getAttribute(getName());
+
+ @NonNull AttributeOperationType attributeOperationType =
+ higherPriorityElement.getAttributeOperationType(getName());
+
+ if (higherPriorityAttributeOptional.isPresent()) {
+
+ XmlAttribute higherPriorityAttribute = higherPriorityAttributeOptional.get();
+ handleBothAttributePresent(
+ mergingReport, higherPriorityAttribute, attributeOperationType);
+ return;
+ }
+
+ // it does not exist, verify if we are supposed to remove it.
+ if (attributeOperationType == AttributeOperationType.REMOVE) {
+ // record the fact the attribute was actively removed.
+ mergingReport.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.REJECTED,
+ AttributeOperationType.REMOVE);
+ return;
+ }
+
+ // the node is not defined in the higher priority element, it's defined in this lower
+ // priority element, we need to merge this lower priority attribute value with a potential
+ // higher priority default value (implicitly set on the higher priority element).
+ String mergedValue = mergeThisAndDefaultValue(mergingReport, higherPriorityElement);
+ if (mergedValue == null) {
+ return;
+ }
+
+ // ok merge it in the higher priority element.
+ getName().addToNode(higherPriorityElement.getXml(), mergedValue);
+
+ // and record the action.
+ mergingReport.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.ADDED,
+ getOwnerElement().getAttributeOperationType(getName()));
+ }
+
+ /**
+ * Handles merging of two attributes value explicitly declared in xml elements.
+ *
+ * @param report report to log errors and actions.
+ * @param higherPriority higher priority attribute we should merge this attribute with.
+ * @param operationType user operation type optionally requested by the user.
+ */
+ private void handleBothAttributePresent(
+ @NonNull MergingReport.Builder report,
+ @NonNull XmlAttribute higherPriority,
+ AttributeOperationType operationType) {
+
+ // handles tools: attribute separately.
+
+ if (getXml().getNamespaceURI() != null
+ && getXml().getNamespaceURI().equals(SdkConstants.TOOLS_URI)) {
+ handleBothToolsAttributePresent(higherPriority);
+ return;
+ }
+
+ // the attribute is present on both elements, there are 2 possibilities :
+ // 1. tools:replace was specified, replace the value.
+ // 2. nothing was specified, the values should be equal or this is an error.
+ if (operationType == AttributeOperationType.REPLACE) {
+ // record the fact the lower priority attribute was rejected.
+ report.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.REJECTED,
+ AttributeOperationType.REPLACE);
+ return;
+ }
+ // if the values are the same, then it's fine, otherwise flag the error.
+ if (mAttributeModel != null) {
+ String mergedValue = mAttributeModel.getMergingPolicy()
+ .merge(higherPriority.getValue(), getValue());
+ if (mergedValue != null) {
+ higherPriority.mXml.setValue(mergedValue);
+ } else {
+ addConflictingValueMessage(report, higherPriority);
+ }
+ return;
+ }
+ // no merging policy, for now revert on checking manually for equality.
+ if (!getValue().equals(higherPriority.getValue())) {
+ addConflictingValueMessage(report, higherPriority);
+ }
+ }
+
+ /**
+ * Handles tools: namespace attributes presence in both documents.
+ * @param higherPriority the higherPriority attribute
+ */
+ private void handleBothToolsAttributePresent(
+ @NonNull XmlAttribute higherPriority) {
+
+ // do not merge tools:node attributes, the higher priority one wins.
+ if (getName().getLocalName().equals(NodeOperationType.NODE_LOCAL_NAME)) {
+ return;
+ }
+
+ // everything else should be merged, duplicates should be eliminated.
+ @NonNull Splitter splitter = Splitter.on(',');
+ @NonNull ImmutableSet.Builder<String> targetValues = ImmutableSet.builder();
+ targetValues.addAll(splitter.split(higherPriority.getValue()));
+ targetValues.addAll(splitter.split(getValue()));
+ higherPriority.getXml().setValue(Joiner.on(',').join(targetValues.build()));
+ }
+
+ /**
+ * Merge this attribute value (on a lower priority element) with a implicit default value
+ * (implicitly declared on the implicitNode).
+ * @param mergingReport report to log errors and actions.
+ * @param implicitNode the lower priority node where the implicit attribute value resides.
+ * @return the merged value that should be stored in the attribute or null if nothing should
+ * be stored.
+ */
+ @Nullable
+ private String mergeThisAndDefaultValue(@NonNull MergingReport.Builder mergingReport,
+ @NonNull XmlElement implicitNode) {
+
+ String mergedValue = getValue();
+ if (mAttributeModel == null || mAttributeModel.getDefaultValue() == null
+ || !mAttributeModel.getMergingPolicy().shouldMergeDefaultValues()) {
+ return mergedValue;
+ }
+ String defaultValue = mAttributeModel.getDefaultValue();
+ if (defaultValue.equals(mergedValue)) {
+ // even though the lower priority attribute is only declared and its value is the same
+ // as the default value, ensure it gets added to the higher priority node.
+ return mergedValue;
+ } else {
+ // ok, the default value and actual declaration are different, delegate to the
+ // merging policy to figure out what value should be used if any.
+ mergedValue = mAttributeModel.getMergingPolicy().merge(defaultValue, mergedValue);
+ if (mergedValue == null) {
+ addIllegalImplicitOverrideMessage(mergingReport, mAttributeModel, implicitNode);
+ return null;
+ }
+ if (mergedValue.equals(defaultValue)) {
+ // no need to forcefully add an attribute to the parent with its default value
+ // since it was not declared to start with.
+ return null;
+ }
+ }
+ return mergedValue;
+ }
+
+ /**
+ * Merge this attribute value with a lower priority node attribute default value.
+ * The attribute is not explicitly set on the implicitNode, yet it exist on this attribute
+ * {@link com.android.manifmerger.XmlElement} higher priority owner.
+ *
+ * @param mergingReport report to log errors and actions.
+ * @param implicitNode the lower priority node where the implicit attribute value resides.
+ */
+ void mergeWithLowerPriorityDefaultValue(
+ @NonNull MergingReport.Builder mergingReport, @NonNull XmlElement implicitNode) {
+
+ if (mAttributeModel == null || mAttributeModel.getDefaultValue() == null
+ || !mAttributeModel.getMergingPolicy().shouldMergeDefaultValues()) {
+ return;
+ }
+ // if this value has been explicitly set to replace the implicit default value, just
+ // log the action.
+ if (mOwnerElement.getAttributeOperationType(getName()) == AttributeOperationType.REPLACE) {
+ mergingReport.getActionRecorder().recordImplicitRejection(this, implicitNode);
+ return;
+ }
+ @Nullable String mergedValue = mAttributeModel.getMergingPolicy().merge(
+ getValue(), mAttributeModel.getDefaultValue());
+ if (mergedValue == null) {
+ addIllegalImplicitOverrideMessage(mergingReport, mAttributeModel, implicitNode);
+ } else {
+ getXml().setValue(mergedValue);
+ mergingReport.getActionRecorder().recordAttributeAction(
+ this,
+ Actions.ActionType.MERGED,
+ null /* attributeOperationType */);
+ }
+ }
+
+ private void addIllegalImplicitOverrideMessage(
+ @NonNull MergingReport.Builder mergingReport,
+ @NonNull AttributeModel attributeModel,
+ @NonNull XmlElement implicitNode) {
+ String error = String.format("Attribute %1$s value=(%2$s) at %3$s"
+ + " cannot override implicit default value=(%4$s) at %5$s",
+ getId(),
+ getValue(),
+ printPosition(),
+ attributeModel.getDefaultValue(),
+ implicitNode.printPosition());
+ addMessage(mergingReport, MergingReport.Record.Severity.ERROR, error);
+ }
+
+ private void addConflictingValueMessage(
+ @NonNull MergingReport.Builder report,
+ @NonNull XmlAttribute higherPriority) {
+
+ Actions.AttributeRecord attributeRecord = report.getActionRecorder()
+ .getAttributeCreationRecord(higherPriority);
+
+ String error;
+ if (getOwnerElement().getType().getMergeType() == MergeType.MERGE_CHILDREN_ONLY) {
+ error = String.format(
+ "Attribute %1$s value=(%2$s) from %3$s\n"
+ + "\tis also present at %4$s value=(%5$s).\n"
+ + "\tAttributes of <%6$s> elements are not merged.",
+ higherPriority.getId(),
+ higherPriority.getValue(),
+ attributeRecord != null
+ ? attributeRecord.getActionLocation().print(true /*shortFormat*/)
+ : "(unknown)",
+ printPosition(),
+ getValue(),
+ getOwnerElement().getType().toXmlName());
+ } else {
+ error = String.format(
+ "Attribute %1$s value=(%2$s) from %3$s\n"
+ + "\tis also present at %4$s value=(%5$s).\n"
+ + "\tSuggestion: add 'tools:replace=\"%6$s\"' to <%7$s> element "
+ + "at %8$s to override.",
+ higherPriority.getId(),
+ higherPriority.getValue(),
+ attributeRecord != null
+ ? attributeRecord.getActionLocation().print(true /*shortFormat*/)
+ : "(unknown)",
+ printPosition(),
+ getValue(),
+ mXml.getName(),
+ getOwnerElement().getType().toXmlName(),
+ higherPriority.getOwnerElement().printPosition());
+ }
+ higherPriority.addMessage(report,
+ attributeRecord != null
+ ? attributeRecord.getActionLocation().getPosition()
+ : SourcePosition.UNKNOWN,
+ MergingReport.Record.Severity.ERROR, error);
+ }
+
+ void addMessage(@NonNull MergingReport.Builder report,
+ @NonNull MergingReport.Record.Severity severity,
+ @NonNull String message) {
+ addMessage(report, getPosition(), severity, message);
+ }
+
+ void addMessage(@NonNull MergingReport.Builder report,
+ @NonNull SourcePosition position,
+ @NonNull MergingReport.Record.Severity severity,
+ @NonNull String message) {
+ report.addMessage(
+ new SourceFilePosition(getOwnerElement().getDocument().getSourceFile(), position),
+ severity, message);
+ }
+
+ @NonNull
+ @Override
+ public SourceFile getSourceFile() {
+ return getOwnerElement().getSourceFile();
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java
new file mode 100644
index 0000000..25787ec
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlDocument.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.android.manifmerger.ManifestModel.NodeTypes.USES_PERMISSION;
+import static com.android.manifmerger.ManifestModel.NodeTypes.USES_SDK;
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.ide.common.xml.XmlFormatPreferences;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.utils.Pair;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Represents a loaded xml document.
+ *
+ * Has pointers to the root {@link XmlElement} element and provides services to persist the document
+ * to an external format. Also provides abilities to be merged with other
+ * {@link com.android.manifmerger.XmlDocument} as well as access to the line numbers for all
+ * document's xml elements and attributes.
+ *
+ */
+public class XmlDocument {
+
+ private static final String DEFAULT_SDK_VERSION = "1";
+
+ /**
+ * The document type.
+ */
+ enum Type {
+ /**
+ * A manifest overlay as found in the build types and variants.
+ */
+ OVERLAY,
+ /**
+ * The main android manifest file.
+ */
+ MAIN,
+ /**
+ * A library manifest that is imported in the application.
+ */
+ LIBRARY
+ }
+
+ private final Element mRootElement;
+ // this is initialized lazily to avoid un-necessary early parsing.
+ @NonNull
+ private final AtomicReference<XmlElement> mRootNode = new AtomicReference<XmlElement>(null);
+ @NonNull
+ private final SourceFile mSourceFile;
+ @NonNull
+ private final KeyResolver<String> mSelectors;
+ @NonNull
+ private final KeyBasedValueResolver<SystemProperty> mSystemPropertyResolver;
+ @NonNull
+ private final Type mType;
+ @NonNull
+ private final Optional<String> mMainManifestPackageName;
+
+ public XmlDocument(
+ @NonNull SourceFile sourceLocation,
+ @NonNull KeyResolver<String> selectors,
+ @NonNull KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
+ @NonNull Element element,
+ @NonNull Type type,
+ @NonNull Optional<String> mainManifestPackageName) {
+ this.mSourceFile = Preconditions.checkNotNull(sourceLocation);
+ this.mRootElement = Preconditions.checkNotNull(element);
+ this.mSelectors = Preconditions.checkNotNull(selectors);
+ this.mSystemPropertyResolver = Preconditions.checkNotNull(systemPropertyResolver);
+ this.mType = type;
+ this.mMainManifestPackageName = mainManifestPackageName;
+ }
+
+ @NonNull
+ public Type getFileType() {
+ return mType;
+ }
+
+ /**
+ * Returns a pretty string representation of this document.
+ */
+ @NonNull
+ public String prettyPrint() {
+ return XmlPrettyPrinter.prettyPrint(
+ getXml(),
+ XmlFormatPreferences.defaults(),
+ XmlFormatStyle.get(getRootNode().getXml()),
+ null, /* endOfLineSeparator */
+ false /* endWithNewLine */);
+ }
+
+ /**
+ * merge this higher priority document with a higher priority document.
+ * @param lowerPriorityDocument the lower priority document to merge in.
+ * @param mergingReportBuilder the merging report to record errors and actions.
+ * @return a new merged {@link com.android.manifmerger.XmlDocument} or
+ * {@link Optional#absent()} if there were errors during the merging activities.
+ */
+ @NonNull
+ public Optional<XmlDocument> merge(
+ @NonNull XmlDocument lowerPriorityDocument,
+ @NonNull MergingReport.Builder mergingReportBuilder) {
+
+ if (getFileType() == Type.MAIN) {
+ mergingReportBuilder.getActionRecorder().recordDefaultNodeAction(getRootNode());
+ }
+
+ getRootNode().mergeWithLowerPriorityNode(
+ lowerPriorityDocument.getRootNode(), mergingReportBuilder);
+
+ addImplicitElements(lowerPriorityDocument, mergingReportBuilder);
+
+ // force re-parsing as new nodes may have appeared.
+ return mergingReportBuilder.hasErrors()
+ ? Optional.<XmlDocument>absent()
+ : Optional.of(reparse());
+ }
+
+ /**
+ * Forces a re-parsing of the document
+ * @return a new {@link com.android.manifmerger.XmlDocument} with up to date information.
+ */
+ @NonNull
+ public XmlDocument reparse() {
+ return new XmlDocument(
+ mSourceFile,
+ mSelectors,
+ mSystemPropertyResolver,
+ mRootElement,
+ mType,
+ mMainManifestPackageName);
+ }
+
+ /**
+ * Returns a {@link com.android.manifmerger.KeyResolver} capable of resolving all selectors
+ * types
+ */
+ @NonNull
+ public KeyResolver<String> getSelectors() {
+ return mSelectors;
+ }
+
+ /**
+ * Returns the {@link com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver} capable
+ * of resolving all injected {@link com.android.manifmerger.ManifestMerger2.SystemProperty}
+ */
+ @NonNull
+ public KeyBasedValueResolver<SystemProperty> getSystemPropertyResolver() {
+ return mSystemPropertyResolver;
+ }
+
+ /**
+ * Compares this document to another {@link com.android.manifmerger.XmlDocument} ignoring all
+ * attributes belonging to the {@link com.android.SdkConstants#TOOLS_URI} namespace.
+ *
+ * @param other the other document to compare against.
+ * @return a {@link String} describing the differences between the two XML elements or
+ * {@link Optional#absent()} if they are equals.
+ */
+ @SuppressWarnings("CovariantCompareTo")
+ public Optional<String> compareTo(@NonNull XmlDocument other) {
+ return getRootNode().compareTo(other.getRootNode());
+ }
+
+ /**
+ * Returns the position of the specified {@link XmlNode}.
+ */
+ @NonNull
+ static SourcePosition getNodePosition(@NonNull XmlNode node) {
+ return getNodePosition(node.getXml());
+ }
+
+ /**
+ * Returns the position of the specified {@link org.w3c.dom.Node}.
+ */
+ @NonNull
+ static SourcePosition getNodePosition(@NonNull Node xml) {
+ return PositionXmlParser.getPosition(xml);
+ }
+
+ /**
+ * Returns the {@link SourceFile} associated with this XML document.
+ * <p>
+ * NOTE: You should <b>not</b> read the contents of the file directly; if you need to
+ * access the content, use {@link ManifestMerger2#getFileStreamProvider()} instead.
+ *
+ * @return the source file
+ */
+ @NonNull
+ public SourceFile getSourceFile() {
+ return mSourceFile;
+ }
+
+ public synchronized XmlElement getRootNode() {
+ if (mRootNode.get() == null) {
+ this.mRootNode.set(new XmlElement(mRootElement, this));
+ }
+ return mRootNode.get();
+ }
+
+ public Optional<XmlElement> getByTypeAndKey(
+ ManifestModel.NodeTypes type,
+ @Nullable String keyValue) {
+
+ return getRootNode().getNodeByTypeAndKey(type, keyValue);
+ }
+
+ /**
+ * Package name for this android manifest which will be used to resolve
+ * partial path. In the case of Overlays, this is absent and the main
+ * manifest packageName must be used.
+ * @return the package name to do partial class names resolution.
+ */
+ public String getPackageName() {
+ return mMainManifestPackageName.or(mRootElement.getAttribute("package"));
+ }
+
+ /**
+ * Returns the package name to use to expand the attributes values with the
+ * document's package name
+ * @return the package name to use for attribute expansion.
+ */
+ public String getPackageNameForAttributeExpansion() {
+ String aPackage = mRootElement.getAttribute("package");
+ if (aPackage != null) {
+ return aPackage;
+ }
+ if (mMainManifestPackageName.isPresent()) {
+ return mMainManifestPackageName.get();
+ }
+ throw new RuntimeException("No package present in overlay or main manifest file");
+ }
+
+ public Optional<XmlAttribute> getPackage() {
+ Optional<XmlAttribute> packageAttribute =
+ getRootNode().getAttribute(XmlNode.fromXmlName("package"));
+ return packageAttribute.isPresent()
+ ? packageAttribute
+ : getRootNode().getAttribute(XmlNode.fromNSName(
+ SdkConstants.ANDROID_URI, "android", "package"));
+ }
+
+ public Document getXml() {
+ return mRootElement.getOwnerDocument();
+ }
+
+ /**
+ * Returns the minSdk version specified in the uses_sdk element if present or the
+ * default value.
+ */
+ @NonNull
+ private String getRawMinSdkVersion() {
+ Optional<XmlElement> usesSdk = getByTypeAndKey(
+ ManifestModel.NodeTypes.USES_SDK, null);
+ if (usesSdk.isPresent()) {
+ Optional<XmlAttribute> minSdkVersion = usesSdk.get()
+ .getAttribute(XmlNode.fromXmlName("android:minSdkVersion"));
+ if (minSdkVersion.isPresent()) {
+ return minSdkVersion.get().getValue();
+ }
+ }
+ return DEFAULT_SDK_VERSION;
+ }
+
+ /**
+ * Returns the minSdk version for this manifest file. It can be injected from the outer
+ * build.gradle or can be expressed in the uses_sdk element.
+ */
+ @NonNull
+ private String getMinSdkVersion() {
+ // check for system properties.
+ String injectedMinSdk = mSystemPropertyResolver.getValue(SystemProperty.MIN_SDK_VERSION);
+ if (injectedMinSdk != null) {
+ return injectedMinSdk;
+ }
+ return getRawMinSdkVersion();
+ }
+
+ /**
+ * Returns the targetSdk version specified in the uses_sdk element if present or the
+ * default value.
+ */
+ @NonNull
+ private String getRawTargetSdkVersion() {
+
+ Optional<XmlElement> usesSdk = getByTypeAndKey(
+ ManifestModel.NodeTypes.USES_SDK, null);
+ if (usesSdk.isPresent()) {
+ Optional<XmlAttribute> targetSdkVersion = usesSdk.get()
+ .getAttribute(XmlNode.fromXmlName("android:targetSdkVersion"));
+ if (targetSdkVersion.isPresent()) {
+ return targetSdkVersion.get().getValue();
+ }
+ }
+ return getRawMinSdkVersion();
+ }
+
+ /**
+ * Returns the targetSdk version for this manifest file. It can be injected from the outer
+ * build.gradle or can be expressed in the uses_sdk element.
+ */
+ @NonNull
+ private String getTargetSdkVersion() {
+
+ // check for system properties.
+ String injectedTargetVersion = mSystemPropertyResolver
+ .getValue(SystemProperty.TARGET_SDK_VERSION);
+ if (injectedTargetVersion != null) {
+ return injectedTargetVersion;
+ }
+ return getRawTargetSdkVersion();
+ }
+
+ /**
+ * Decodes a sdk version from either its decimal representation or from a platform code name.
+ * @param attributeVersion the sdk version attribute as specified by users.
+ * @return the integer representation of the platform level.
+ */
+ private static int getApiLevelFromAttribute(@NonNull String attributeVersion) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(attributeVersion));
+ if (Character.isDigit(attributeVersion.charAt(0))) {
+ return Integer.parseInt(attributeVersion);
+ }
+ return SdkVersionInfo.getApiByPreviewName(attributeVersion, true);
+ }
+
+ /**
+ * Add all implicit elements from the passed lower priority document that are
+ * required in the target SDK.
+ */
+ @SuppressWarnings("unchecked") // compiler confused about varargs and generics.
+ private void addImplicitElements(@NonNull XmlDocument lowerPriorityDocument,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ // if this document is an overlay, tolerate the absence of uses-sdk and do not
+ // assume implicit minimum versions.
+ Optional<XmlElement> usesSdk = getByTypeAndKey(
+ ManifestModel.NodeTypes.USES_SDK, null);
+ if (mType == Type.OVERLAY && !usesSdk.isPresent()) {
+ return;
+ }
+
+ // check that the uses-sdk element does not have any tools:node instruction.
+ if (usesSdk.isPresent()) {
+ XmlElement usesSdkElement = usesSdk.get();
+ if (usesSdkElement.getOperationType() != NodeOperationType.MERGE) {
+ mergingReport
+ .addMessage(
+ new SourceFilePosition(
+ getSourceFile(),
+ usesSdkElement.getPosition()),
+ MergingReport.Record.Severity.ERROR,
+ "uses-sdk element cannot have a \"tools:node\" attribute");
+ return;
+ }
+ }
+ int thisTargetSdk = getApiLevelFromAttribute(getTargetSdkVersion());
+
+ // when we are importing a library, we should never use the build.gradle injected
+ // values (only valid for overlay, main manifest) so use the raw versions coming from
+ // the AndroidManifest.xml
+ int libraryTargetSdk = getApiLevelFromAttribute(
+ lowerPriorityDocument.getFileType() == Type.LIBRARY
+ ? lowerPriorityDocument.getRawTargetSdkVersion()
+ : lowerPriorityDocument.getTargetSdkVersion());
+
+ // if library is using a code name rather than an API level, make sure this document target
+ // sdk version is using the same code name.
+ String libraryTargetSdkVersion = lowerPriorityDocument.getTargetSdkVersion();
+ if (!Character.isDigit(libraryTargetSdkVersion.charAt(0))) {
+ // this is a code name, ensure this document uses the same code name.
+ if (!libraryTargetSdkVersion.equals(getTargetSdkVersion())) {
+ mergingReport.addMessage(getSourceFile(), MergingReport.Record.Severity.ERROR,
+ String.format(
+ "uses-sdk:targetSdkVersion %1$s cannot be different than version "
+ + "%2$s declared in library %3$s",
+ getTargetSdkVersion(),
+ libraryTargetSdkVersion,
+ lowerPriorityDocument.getSourceFile().print(false)
+ )
+ );
+ return;
+ }
+ }
+ // same for minSdkVersion, if the library is using a code name, the application must
+ // also be using the same code name.
+ String libraryMinSdkVersion = lowerPriorityDocument.getRawMinSdkVersion();
+ if (!Character.isDigit(libraryMinSdkVersion.charAt(0))) {
+ // this is a code name, ensure this document uses the same code name.
+ if (!libraryMinSdkVersion.equals(getMinSdkVersion())) {
+ mergingReport.addMessage(getSourceFile(), MergingReport.Record.Severity.ERROR,
+ String.format(
+ "uses-sdk:minSdkVersion %1$s cannot be different than version "
+ + "%2$s declared in library %3$s",
+ getMinSdkVersion(),
+ libraryMinSdkVersion,
+ lowerPriorityDocument.getSourceFile().print(false)
+ )
+ );
+ return;
+ }
+ }
+
+ if (!checkUsesSdkMinVersion(lowerPriorityDocument, mergingReport)) {
+ String error = String.format(
+ "uses-sdk:minSdkVersion %1$s cannot be smaller than version "
+ + "%2$s declared in library %3$s\n"
+ + "\tSuggestion: use tools:overrideLibrary=\"%4$s\" to force usage",
+ getMinSdkVersion(),
+ lowerPriorityDocument.getRawMinSdkVersion(),
+ lowerPriorityDocument.getSourceFile().print(false),
+ lowerPriorityDocument.getPackageName());
+ if (usesSdk.isPresent()) {
+ mergingReport.addMessage(
+ new SourceFilePosition(getSourceFile(), usesSdk.get().getPosition()),
+ MergingReport.Record.Severity.ERROR,
+ error);
+ } else {
+ mergingReport.addMessage(
+ getSourceFile(), MergingReport.Record.Severity.ERROR, error);
+ }
+ return;
+ }
+
+ // if the merged document target SDK is equal or smaller than the library's, nothing to do.
+ if (thisTargetSdk <= libraryTargetSdk) {
+ return;
+ }
+
+ // There is no need to add any implied permissions when targeting an old runtime.
+ if (thisTargetSdk < 4) {
+ return;
+ }
+
+ boolean hasWriteToExternalStoragePermission =
+ lowerPriorityDocument.getByTypeAndKey(
+ USES_PERMISSION, permission("WRITE_EXTERNAL_STORAGE")).isPresent();
+
+ if (libraryTargetSdk < 4) {
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION,
+ permission("WRITE_EXTERNAL_STORAGE"),
+ lowerPriorityDocument.getPackageName() + " has a targetSdkVersion < 4");
+ hasWriteToExternalStoragePermission = true;
+
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION,
+ permission("READ_PHONE_STATE"),
+ lowerPriorityDocument.getPackageName() + " has a targetSdkVersion < 4");
+ }
+
+ // If the application has requested WRITE_EXTERNAL_STORAGE, we will
+ // force them to always take READ_EXTERNAL_STORAGE as well. We always
+ // do this (regardless of target API version) because we can't have
+ // an app with write permission but not read permission.
+ if (hasWriteToExternalStoragePermission) {
+
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION,
+ permission("READ_EXTERNAL_STORAGE"),
+ lowerPriorityDocument.getPackageName() + " requested WRITE_EXTERNAL_STORAGE");
+ }
+
+ // Pre-JellyBean call log permission compatibility.
+ if (thisTargetSdk >= 16 && libraryTargetSdk < 16) {
+ if (lowerPriorityDocument.getByTypeAndKey(
+ USES_PERMISSION, permission("READ_CONTACTS")).isPresent()) {
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION, permission("READ_CALL_LOG"),
+ lowerPriorityDocument.getPackageName()
+ + " has targetSdkVersion < 16 and requested READ_CONTACTS");
+ }
+ if (lowerPriorityDocument.getByTypeAndKey(
+ USES_PERMISSION, permission("WRITE_CONTACTS")).isPresent()) {
+ addIfAbsent(mergingReport.getActionRecorder(),
+ USES_PERMISSION, permission("WRITE_CALL_LOG"),
+ lowerPriorityDocument.getPackageName()
+ + " has targetSdkVersion < 16 and requested WRITE_CONTACTS");
+ }
+ }
+ }
+
+ /**
+ * Returns true if the minSdkVersion of the application and the library are compatible, false
+ * otherwise.
+ */
+ private boolean checkUsesSdkMinVersion(@NonNull XmlDocument lowerPriorityDocument,
+ MergingReport.Builder mergingReport) {
+
+ int thisMinSdk = getApiLevelFromAttribute(getMinSdkVersion());
+ int libraryMinSdk = getApiLevelFromAttribute(
+ lowerPriorityDocument.getRawMinSdkVersion());
+
+ // the merged document minSdk cannot be lower than a library
+ if (thisMinSdk < libraryMinSdk) {
+
+ // check if this higher priority document has any tools instructions for the node
+ Optional<XmlElement> xmlElementOptional = getByTypeAndKey(USES_SDK, null);
+ if (!xmlElementOptional.isPresent()) {
+ return false;
+ }
+ XmlElement xmlElement = xmlElementOptional.get();
+
+ // if we find a selector that applies to this library. the users wants to explicitly
+ // allow this higher version library to be allowed.
+ for (Selector selector : xmlElement.getOverrideUsesSdkLibrarySelectors()) {
+ if (selector.appliesTo(lowerPriorityDocument.getRootNode())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Adds a new element of type nodeType with a specific keyValue if the element is absent in this
+ * document. Will also add attributes expressed through key value pairs.
+ *
+ * @param actionRecorder to records creation actions.
+ * @param nodeType the node type to crete
+ * @param keyValue the optional key for the element.
+ * @param attributes the optional array of key value pairs for extra element attribute.
+ * @return the Xml element whether it was created or existed or {@link Optional#absent()} if
+ * it does not exist in this document.
+ */
+ private Optional<Element> addIfAbsent(
+ @NonNull ActionRecorder actionRecorder,
+ @NonNull ManifestModel.NodeTypes nodeType,
+ @Nullable String keyValue,
+ @Nullable String reason,
+ @Nullable Pair<String, String>... attributes) {
+
+ Optional<XmlElement> xmlElementOptional = getByTypeAndKey(nodeType, keyValue);
+ if (xmlElementOptional.isPresent()) {
+ return Optional.absent();
+ }
+ Element elementNS = getXml()
+ .createElementNS(SdkConstants.ANDROID_URI, "android:" + nodeType.toXmlName());
+
+
+ ImmutableList<String> keyAttributesNames = nodeType.getNodeKeyResolver()
+ .getKeyAttributesNames();
+ if (keyAttributesNames.size() == 1) {
+ elementNS.setAttributeNS(
+ SdkConstants.ANDROID_URI, "android:" + keyAttributesNames.get(0), keyValue);
+ }
+ if (attributes != null) {
+ for (Pair<String, String> attribute : attributes) {
+ elementNS.setAttributeNS(
+ SdkConstants.ANDROID_URI, "android:" + attribute.getFirst(),
+ attribute.getSecond());
+ }
+ }
+
+ // record creation.
+ XmlElement xmlElement = new XmlElement(elementNS, this);
+ actionRecorder.recordImpliedNodeAction(xmlElement, reason);
+
+ getRootNode().getXml().appendChild(elementNS);
+ return Optional.of(elementNS);
+ }
+
+ @NonNull
+ private static String permission(String permissionName) {
+ return "android.permission." + permissionName;
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java
new file mode 100644
index 0000000..8d9fd33
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlElement.java
@@ -0,0 +1,904 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.ide.common.res2.MergingException;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Xml {@link org.w3c.dom.Element} which is mergeable.
+ *
+ * A mergeable element can contains 3 types of children :
+ * <ul>
+ * <li>a child element, which itself may or may not be mergeable.</li>
+ * <li>xml attributes which are related to the element.</li>
+ * <li>tools oriented attributes to trigger specific behaviors from the merging tool</li>
+ * </ul>
+ *
+ * The two main responsibilities of this class is to be capable of comparing itself against
+ * another instance of the same type as well as providing XML element merging capabilities.
+ */
+public class XmlElement extends OrphanXmlElement {
+
+ @NonNull
+ private final XmlDocument mDocument;
+
+ @Nullable
+ private final NodeOperationType mNodeOperationType;
+
+ // list of non tools related attributes.
+ @NonNull
+ private final ImmutableList<XmlAttribute> mAttributes;
+
+ // map of all tools related attributes keyed by target attribute name
+ @NonNull
+ private final Map<NodeName, AttributeOperationType> mAttributesOperationTypes;
+
+ // list of mergeable children elements.
+ @NonNull
+ private final ImmutableList<XmlElement> mMergeableChildren;
+
+ // optional selector declared on this xml element.
+ @Nullable
+ private final Selector mSelector;
+
+ // optional list of libraries that we should ignore the minSdk version
+ @NonNull
+ private final List<Selector> mOverrideUsesSdkLibrarySelectors;
+
+
+ public XmlElement(@NonNull Element xml, @NonNull XmlDocument document) {
+ super(xml);
+
+ mDocument = Preconditions.checkNotNull(document);
+ Selector selector = null;
+ List<Selector> overrideUsesSdkLibrarySelectors = ImmutableList.of();
+
+ ImmutableMap.Builder<NodeName, AttributeOperationType> attributeOperationTypeBuilder =
+ ImmutableMap.builder();
+ ImmutableList.Builder<XmlAttribute> attributesListBuilder = ImmutableList.builder();
+ NamedNodeMap namedNodeMap = getXml().getAttributes();
+ NodeOperationType lastNodeOperationType = null;
+ for (int i = 0; i < namedNodeMap.getLength(); i++) {
+ Node attribute = namedNodeMap.item(i);
+ if (SdkConstants.TOOLS_URI.equals(attribute.getNamespaceURI())) {
+ String instruction = attribute.getLocalName();
+ if (instruction.equals(NodeOperationType.NODE_LOCAL_NAME)) {
+ // should we flag an error when there are more than one operation type on a node ?
+ lastNodeOperationType = NodeOperationType.valueOf(
+ SdkUtils.camelCaseToConstantName(
+ attribute.getNodeValue()));
+ } else if (instruction.equals(Selector.SELECTOR_LOCAL_NAME)) {
+ selector = new Selector(attribute.getNodeValue());
+ } else if (instruction.equals(NodeOperationType.OVERRIDE_USES_SDK)) {
+ String nodeValue = attribute.getNodeValue();
+ ImmutableList.Builder<Selector> builder = ImmutableList.builder();
+ for (String selectorValue : Splitter.on(',').split(nodeValue)) {
+ builder.add(new Selector(selectorValue.trim()));
+ }
+ overrideUsesSdkLibrarySelectors = builder.build();
+ } else {
+ AttributeOperationType attributeOperationType;
+ try {
+ attributeOperationType =
+ AttributeOperationType.valueOf(
+ SdkUtils.xmlNameToConstantName(instruction));
+ } catch (IllegalArgumentException e) {
+ try {
+ // is this another tool's operation type that we do not care about.
+ OtherOperationType.valueOf(instruction);
+ break;
+ } catch (IllegalArgumentException e1) {
+
+ String errorMessage =
+ String.format("Invalid instruction '%1$s', "
+ + "valid instructions are : %2$s",
+ instruction,
+ Joiner.on(',').join(AttributeOperationType.values())
+ );
+ throw new RuntimeException(MergingException.wrapException(e)
+ .withMessage(errorMessage)
+ .withFile(mDocument.getSourceFile())
+ .withPosition(XmlDocument.getNodePosition(xml)).build());
+ }
+ }
+ for (String attributeName : Splitter.on(',').trimResults()
+ .split(attribute.getNodeValue())) {
+ if (attributeName.indexOf(XmlUtils.NS_SEPARATOR) == -1) {
+ String toolsPrefix = XmlUtils
+ .lookupNamespacePrefix(getXml(), SdkConstants.TOOLS_URI,
+ SdkConstants.ANDROID_NS_NAME, false);
+ // automatically provide the prefix.
+ attributeName = toolsPrefix + XmlUtils.NS_SEPARATOR + attributeName;
+ }
+ NodeName nodeName = XmlNode.fromXmlName(attributeName);
+ attributeOperationTypeBuilder.put(nodeName, attributeOperationType);
+ }
+ }
+ }
+ }
+ mAttributesOperationTypes = attributeOperationTypeBuilder.build();
+ for (int i = 0; i < namedNodeMap.getLength(); i++) {
+ Node attribute = namedNodeMap.item(i);
+ XmlAttribute xmlAttribute = new XmlAttribute(
+ this, (Attr) attribute, getType().getAttributeModel(XmlNode.fromXmlName(
+ ((Attr) attribute).getName())));
+ attributesListBuilder.add(xmlAttribute);
+ }
+ mNodeOperationType = lastNodeOperationType;
+ mAttributes = attributesListBuilder.build();
+ mMergeableChildren = initMergeableChildren();
+ mSelector = selector;
+ mOverrideUsesSdkLibrarySelectors = overrideUsesSdkLibrarySelectors;
+ }
+
+ /**
+ * Returns the owning {@link com.android.manifmerger.XmlDocument}
+ */
+ @NonNull
+ public XmlDocument getDocument() {
+ return mDocument;
+ }
+
+ /**
+ * Returns the list of attributes for this xml element.
+ */
+ public List<XmlAttribute> getAttributes() {
+ return mAttributes;
+ }
+
+ /**
+ * Returns the {@link com.android.manifmerger.XmlAttribute} for an attribute present on this
+ * xml element, or {@link com.google.common.base.Optional#absent} if not present.
+ * @param attributeName the attribute name.
+ */
+ public Optional<XmlAttribute> getAttribute(NodeName attributeName) {
+ for (XmlAttribute xmlAttribute : mAttributes) {
+ if (xmlAttribute.getName().equals(attributeName)) {
+ return Optional.of(xmlAttribute);
+ }
+ }
+ return Optional.absent();
+ }
+
+ /**
+ * Get the node operation type as optionally specified by the user. If the user did not
+ * explicitly specify how conflicting elements should be handled, a
+ * {@link com.android.manifmerger.NodeOperationType#MERGE} will be returned.
+ */
+ @NonNull
+ public NodeOperationType getOperationType() {
+ return mNodeOperationType != null
+ ? mNodeOperationType
+ : NodeOperationType.MERGE;
+ }
+
+ /**
+ * Get the attribute operation type as optionally specified by the user. If the user did not
+ * explicitly specify how conflicting attributes should be handled, a
+ * {@link AttributeOperationType#STRICT} will be returned.
+ */
+ @NonNull
+ public AttributeOperationType getAttributeOperationType(NodeName attributeName) {
+ return mAttributesOperationTypes.containsKey(attributeName)
+ ? mAttributesOperationTypes.get(attributeName)
+ : AttributeOperationType.STRICT;
+ }
+
+ @NonNull
+ public Collection<Map.Entry<NodeName, AttributeOperationType>> getAttributeOperations() {
+ return mAttributesOperationTypes.entrySet();
+ }
+
+ @NonNull
+ public List<Selector> getOverrideUsesSdkLibrarySelectors() {
+ return mOverrideUsesSdkLibrarySelectors;
+ }
+
+
+ @NonNull
+ @Override
+ public SourcePosition getPosition() {
+ return XmlDocument.getNodePosition(this);
+ }
+
+ @NonNull
+ @Override
+ public SourceFile getSourceFile() {
+ return mDocument.getSourceFile();
+ }
+
+
+ /**
+ * Merge this xml element with a lower priority node.
+ *
+ * For now, attributes will be merged. If present on both xml elements, a warning will be
+ * issued and the attribute merge will be rejected.
+ *
+ * @param lowerPriorityNode lower priority Xml element to merge with.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ public void mergeWithLowerPriorityNode(
+ @NonNull XmlElement lowerPriorityNode,
+ @NonNull MergingReport.Builder mergingReport) {
+
+
+ if (mSelector != null && !mSelector.isResolvable(getDocument().getSelectors())) {
+ mergingReport.addMessage(getSourceFilePosition(),
+ MergingReport.Record.Severity.ERROR,
+ String.format("'tools:selector=\"%1$s\"' is not a valid library identifier, "
+ + "valid identifiers are : %2$s",
+ mSelector.toString(),
+ Joiner.on(',').join(mDocument.getSelectors().getKeys())));
+ return;
+
+ }
+ mergingReport.getLogger().info("Merging " + getId()
+ + " with lower " + lowerPriorityNode.printPosition());
+
+ // workaround for 0.12 release and overlay treatment of manifest entries. This will
+ // need to be expressed in the model instead.
+ MergeType mergeType = getType().getMergeType();
+ // if element we are merging in is not a library (an overlay or an application), we should
+ // always merge the <manifest> attributes otherwise, we do not merge the libraries
+ // <manifest> attributes.
+ if (isA(ManifestModel.NodeTypes.MANIFEST)
+ && lowerPriorityNode.getDocument().getFileType() != XmlDocument.Type.LIBRARY) {
+ mergeType = MergeType.MERGE;
+ }
+
+ if (mergeType != MergeType.MERGE_CHILDREN_ONLY) {
+ // make a copy of all the attributes metadata, it will eliminate elements from this
+ // list as it finds them explicitly defined in the lower priority node.
+ // At the end of the explicit attributes processing, the remaining elements of this
+ // list will need to be checked for default value that may clash with a locally
+ // defined attribute.
+ List<AttributeModel> attributeModels =
+ new ArrayList<AttributeModel>(lowerPriorityNode.getType().getAttributeModels());
+
+ // merge explicit attributes from lower priority node.
+ for (XmlAttribute lowerPriorityAttribute : lowerPriorityNode.getAttributes()) {
+ lowerPriorityAttribute.mergeInHigherPriorityElement(this, mergingReport);
+ if (lowerPriorityAttribute.getModel() != null) {
+ attributeModels.remove(lowerPriorityAttribute.getModel());
+ }
+ }
+ // merge implicit default values from lower priority node when we have an explicit
+ // attribute declared on this node.
+ for (AttributeModel attributeModel : attributeModels) {
+ if (attributeModel.getDefaultValue() != null) {
+ Optional<XmlAttribute> myAttribute = getAttribute(attributeModel.getName());
+ if (myAttribute.isPresent()) {
+ myAttribute.get().mergeWithLowerPriorityDefaultValue(
+ mergingReport, lowerPriorityNode);
+ }
+ }
+ }
+ }
+ // are we supposed to merge children ?
+ if (mNodeOperationType != NodeOperationType.MERGE_ONLY_ATTRIBUTES) {
+ mergeChildren(lowerPriorityNode, mergingReport);
+ } else {
+ // record rejection of the lower priority node's children .
+ for (XmlElement lowerPriorityChild : lowerPriorityNode.getMergeableElements()) {
+ mergingReport.getActionRecorder().recordNodeAction(this,
+ Actions.ActionType.REJECTED,
+ lowerPriorityChild);
+ }
+ }
+ }
+
+ @NonNull
+ public ImmutableList<XmlElement> getMergeableElements() {
+ return mMergeableChildren;
+ }
+
+ /**
+ * Returns a child of a particular type and a particular key.
+ * @param type the requested child type.
+ * @param keyValue the requested child key.
+ * @return the child of {@link com.google.common.base.Optional#absent()} if no child of this
+ * type and key exist.
+ */
+ @NonNull
+ public Optional<XmlElement> getNodeByTypeAndKey(
+ ManifestModel.NodeTypes type,
+ @Nullable String keyValue) {
+
+ for (XmlElement xmlElement : mMergeableChildren) {
+ if (xmlElement.isA(type) &&
+ (keyValue == null || keyValue.equals(xmlElement.getKey()))) {
+ return Optional.of(xmlElement);
+ }
+ }
+ return Optional.absent();
+ }
+
+ /**
+ * Returns all immediate children of this node for a particular type, irrespective of their
+ * key.
+ * @param type the type of children element requested.
+ * @return the list (potentially empty) of children.
+ */
+ @NonNull
+ public ImmutableList<XmlElement> getAllNodesByType(ManifestModel.NodeTypes type) {
+ ImmutableList.Builder<XmlElement> listBuilder = ImmutableList.builder();
+ for (XmlElement mergeableChild : initMergeableChildren()) {
+ if (mergeableChild.isA(type)) {
+ listBuilder.add(mergeableChild);
+ }
+ }
+ return listBuilder.build();
+ }
+
+ // merge this higher priority node with a lower priority node.
+ public void mergeChildren(@NonNull XmlElement lowerPriorityNode,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ // read all lower priority mergeable nodes.
+ // if the same node is not defined in this document merge it in.
+ // if the same is defined, so far, give an error message.
+ for (XmlElement lowerPriorityChild : lowerPriorityNode.getMergeableElements()) {
+
+ if (shouldIgnore(lowerPriorityChild, mergingReport)) {
+ continue;
+ }
+ mergeChild(lowerPriorityChild, mergingReport);
+ }
+ }
+
+ /**
+ * Returns true if this element supports having a tools:selector decoration, false otherwise.
+ */
+ public boolean supportsSelector() {
+ return getOperationType().isSelectable();
+ }
+
+ // merge a child of a lower priority node into this higher priority node.
+ private void mergeChild(@NonNull XmlElement lowerPriorityChild, @NonNull MergingReport.Builder mergingReport) {
+
+ ILogger logger = mergingReport.getLogger();
+
+ // If this a custom element, we just blindly merge it in.
+ if (lowerPriorityChild.getType() == ManifestModel.NodeTypes.CUSTOM) {
+ handleCustomElement(lowerPriorityChild, mergingReport);
+ return;
+ }
+
+ Optional<XmlElement> thisChildOptional =
+ getNodeByTypeAndKey(lowerPriorityChild.getType(),lowerPriorityChild.getKey());
+
+ // only in the lower priority document ?
+ if (!thisChildOptional.isPresent()) {
+ addElement(lowerPriorityChild, mergingReport);
+ return;
+ }
+ // it's defined in both files.
+ logger.verbose(lowerPriorityChild.getId() + " defined in both files...");
+
+ XmlElement thisChild = thisChildOptional.get();
+ switch (thisChild.getType().getMergeType()) {
+ case CONFLICT:
+ addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format(
+ "Node %1$s cannot be present in more than one input file and it's "
+ + "present at %2$s and %3$s",
+ thisChild.getType(),
+ thisChild.printPosition(),
+ lowerPriorityChild.printPosition()
+ ));
+ break;
+ case ALWAYS:
+
+ // no merging, we consume the lower priority node unmodified.
+ // if the two elements are equal, just skip it.
+
+ // but check first that we are not supposed to replace or remove it.
+ @NonNull NodeOperationType operationType =
+ calculateNodeOperationType(thisChild, lowerPriorityChild);
+ if (operationType == NodeOperationType.REMOVE ||
+ operationType == NodeOperationType.REPLACE) {
+ mergingReport.getActionRecorder().recordNodeAction(thisChild,
+ Actions.ActionType.REJECTED, lowerPriorityChild);
+ break;
+ }
+
+ if (thisChild.getType().areMultipleDeclarationAllowed()) {
+ mergeChildrenWithMultipleDeclarations(lowerPriorityChild, mergingReport);
+ } else {
+ if (!thisChild.isEquals(lowerPriorityChild)) {
+ addElement(lowerPriorityChild, mergingReport);
+ }
+ }
+ break;
+ default:
+ // 2 nodes exist, some merging need to happen
+ handleTwoElementsExistence(thisChild, lowerPriorityChild, mergingReport);
+ break;
+ }
+ }
+
+ /**
+ * Handles presence of custom elements (elements not part of the android or tools
+ * namespaces). Such elements are merged unchanged into the resulting document, and
+ * optionally, the namespace definition is added to the merged document root element.
+ * @param customElement the custom element present in the lower priority document.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void handleCustomElement(@NonNull XmlElement customElement,
+ @NonNull MergingReport.Builder mergingReport) {
+ addElement(customElement, mergingReport);
+
+ // add the custom namespace to the document generation.
+ String nodeName = customElement.getXml().getNodeName();
+ if (!nodeName.contains(":")) {
+ return;
+ }
+ @NonNull String prefix = nodeName.substring(0, nodeName.indexOf(':'));
+ String namespace = customElement.getDocument().getRootNode()
+ .getXml().getAttribute(SdkConstants.XMLNS_PREFIX + prefix);
+
+ if (namespace != null) {
+ getDocument().getRootNode().getXml().setAttributeNS(
+ SdkConstants.XMLNS_URI, SdkConstants.XMLNS_PREFIX + prefix, namespace);
+ }
+ }
+
+ /**
+ * Merges two children when this children's type allow multiple elements declaration with the
+ * same key value. In that case, we only merge the lower priority child if there is not already
+ * an element with the same key value that is equal to the lower priority child. Two children
+ * are equals if they have the same attributes and children declared irrespective of the
+ * declaration order.
+ *
+ * @param lowerPriorityChild the lower priority element's child.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void mergeChildrenWithMultipleDeclarations(
+ @NonNull XmlElement lowerPriorityChild,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ Preconditions.checkArgument(lowerPriorityChild.getType().areMultipleDeclarationAllowed());
+ if (lowerPriorityChild.getType().areMultipleDeclarationAllowed()) {
+ for (XmlElement sameTypeChild : getAllNodesByType(lowerPriorityChild.getType())) {
+ if (sameTypeChild.getId().equals(lowerPriorityChild.getId()) &&
+ sameTypeChild.isEquals(lowerPriorityChild)) {
+ return;
+ }
+ }
+ }
+ // if we end up here, we never found a child of this element with the same key and strictly
+ // equals to the lowerPriorityChild so we should merge it in.
+ addElement(lowerPriorityChild, mergingReport);
+ }
+
+ /**
+ * Determine if we should completely ignore a child from any merging activity.
+ * There are 2 situations where we should ignore a lower priority child :
+ * <p>
+ * <ul>
+ * <li>The associate {@link com.android.manifmerger.ManifestModel.NodeTypes} is
+ * annotated with {@link com.android.manifmerger.MergeType#IGNORE}</li>
+ * <li>This element has a child of the same type with no key that has a '
+ * tools:node="removeAll' attribute.</li>
+ * </ul>
+ * @param lowerPriorityChild the lower priority child we should determine eligibility for
+ * merging.
+ * @return true if the element should be ignored, false otherwise.
+ */
+ private boolean shouldIgnore(
+ @NonNull XmlElement lowerPriorityChild,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ if (lowerPriorityChild.getType().getMergeType() == MergeType.IGNORE) {
+ return true;
+ }
+
+ // do we have an element of the same type of that child with no key ?
+ Optional<XmlElement> thisChildElementOptional =
+ getNodeByTypeAndKey(lowerPriorityChild.getType(), null /* keyValue */);
+ if (!thisChildElementOptional.isPresent()) {
+ return false;
+ }
+ XmlElement thisChild = thisChildElementOptional.get();
+
+ // are we supposed to delete all occurrences and if yes, is there a selector defined to
+ // filter which elements should be deleted.
+ boolean shouldDelete = thisChild.mNodeOperationType == NodeOperationType.REMOVE_ALL
+ && (thisChild.mSelector == null
+ || thisChild.mSelector.appliesTo(lowerPriorityChild));
+ // if we should discard this child element, record the action.
+ if (shouldDelete) {
+ mergingReport.getActionRecorder().recordNodeAction(thisChildElementOptional.get(),
+ Actions.ActionType.REJECTED,
+ lowerPriorityChild);
+ }
+ return shouldDelete;
+ }
+
+ /**
+ * Handle 2 elements (of same identity) merging.
+ * higher priority one has a tools:node="remove", remove the low priority one
+ * higher priority one has a tools:node="replace", replace the low priority one
+ * higher priority one has a tools:node="strict", flag the error if not equals.
+ * default or tools:node="merge", merge the two elements.
+ * @param higherPriority the higher priority node.
+ * @param lowerPriority the lower priority element.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void handleTwoElementsExistence(
+ @NonNull XmlElement higherPriority,
+ @NonNull XmlElement lowerPriority,
+ @NonNull MergingReport.Builder mergingReport) {
+
+ @NonNull NodeOperationType operationType = calculateNodeOperationType(higherPriority, lowerPriority);
+ // 2 nodes exist, 3 possibilities :
+ // higher priority one has a tools:node="remove", remove the low priority one
+ // higher priority one has a tools:node="replace", replace the low priority one
+ // higher priority one has a tools:node="strict", flag the error if not equals.
+ switch(operationType) {
+ case MERGE:
+ case MERGE_ONLY_ATTRIBUTES:
+ // record the action
+ mergingReport.getActionRecorder().recordNodeAction(higherPriority,
+ Actions.ActionType.MERGED, lowerPriority);
+ // and perform the merge
+ higherPriority.mergeWithLowerPriorityNode(lowerPriority, mergingReport);
+ break;
+ case REMOVE:
+ case REPLACE:
+ // so far remove and replace and similar, the post validation will take
+ // care of removing this node in the case of REMOVE.
+
+ // just don't import the lower priority node and record the action.
+ mergingReport.getActionRecorder().recordNodeAction(higherPriority,
+ Actions.ActionType.REJECTED, lowerPriority);
+ break;
+ case STRICT:
+ Optional<String> compareMessage = higherPriority.compareTo(lowerPriority);
+ if (compareMessage.isPresent()) {
+ // flag error.
+ addMessage(mergingReport, MergingReport.Record.Severity.ERROR, String.format(
+ "Node %1$s at %2$s is tagged with tools:node=\"strict\", yet "
+ + "%3$s at %4$s is different : %5$s",
+ higherPriority.getId(),
+ higherPriority.printPosition(),
+ lowerPriority.getId(),
+ lowerPriority.printPosition(),
+ compareMessage.get()
+ ));
+ }
+ break;
+ default:
+ mergingReport.getLogger().error(null /* throwable */,
+ "Unhandled node operation type %s", higherPriority.getOperationType());
+ break;
+ }
+ }
+
+ /**
+ * Calculate the effective node operation type for a higher priority node when a lower priority
+ * node is queried for merge.
+ * @param higherPriority the higher priority node which may have a {@link NodeOperationType}
+ * declaration and may also have a {@link Selector} declaration.
+ * @param lowerPriority the lower priority node that is elected for merging with the higher
+ * priority node.
+ * @return the effective {@link NodeOperationType} that should be used to affect higher and
+ * lower priority nodes merging.
+ */
+ @NonNull
+ private static NodeOperationType calculateNodeOperationType(
+ @NonNull XmlElement higherPriority,
+ @NonNull XmlElement lowerPriority) {
+
+ @NonNull NodeOperationType operationType = higherPriority.getOperationType();
+ // if the operation's selector exists and the lower priority node is not selected,
+ // we revert to default operation type which is merge.
+ if (higherPriority.supportsSelector()
+ && higherPriority.mSelector != null
+ && !higherPriority.mSelector.appliesTo(lowerPriority)) {
+ operationType = NodeOperationType.MERGE;
+ }
+ return operationType;
+ }
+
+ /**
+ * Add an element and its leading comments as the last sub-element of the current element.
+ * @param elementToBeAdded xml element to be added to the current element.
+ * @param mergingReport the merging report to log errors and actions.
+ */
+ private void addElement(
+ @NonNull XmlElement elementToBeAdded, @NonNull MergingReport.Builder mergingReport) {
+
+ List<Node> comments = getLeadingComments(elementToBeAdded.getXml());
+ // record all the actions before the node is moved from the library document to the main
+ // merged document.
+ mergingReport.getActionRecorder().recordDefaultNodeAction(elementToBeAdded);
+
+ // only in the new file, just import it.
+ Node node = getXml().getOwnerDocument().adoptNode(elementToBeAdded.getXml());
+ getXml().appendChild(node);
+
+ // also adopt the child's comments if any.
+ for (Node comment : comments) {
+ Node newComment = getXml().getOwnerDocument().adoptNode(comment);
+ getXml().insertBefore(newComment, node);
+ }
+
+ mergingReport.getLogger().verbose("Adopted " + node);
+ }
+
+ public boolean isEquals(XmlElement otherNode) {
+ return !compareTo(otherNode).isPresent();
+ }
+
+ /**
+ * Returns a potentially null (if not present) selector decoration on this element.
+ */
+ @Nullable
+ public Selector getSelector() {
+ return mSelector;
+ }
+
+ /**
+ * Compares this element with another {@link XmlElement} ignoring all attributes belonging to
+ * the {@link com.android.SdkConstants#TOOLS_URI} namespace.
+ *
+ * @param other the other element to compare against.
+ * @return a {@link String} describing the differences between the two XML elements or
+ * {@link Optional#absent()} if they are equals.
+ */
+ @NonNull
+ public Optional<String> compareTo(Object other) {
+
+ if (!(other instanceof XmlElement)) {
+ return Optional.of("Wrong type");
+ }
+ XmlElement otherNode = (XmlElement) other;
+
+ // compare element names
+ if (getXml().getNamespaceURI() != null) {
+ if (!getXml().getLocalName().equals(otherNode.getXml().getLocalName())) {
+ return Optional.of(
+ String.format("Element names do not match: %1$s versus %2$s",
+ getXml().getLocalName(),
+ otherNode.getXml().getLocalName()));
+ }
+ // compare element ns
+ String thisNS = getXml().getNamespaceURI();
+ String otherNS = otherNode.getXml().getNamespaceURI();
+ if ((thisNS == null && otherNS != null)
+ || (thisNS != null && !thisNS.equals(otherNS))) {
+ return Optional.of(
+ String.format("Element namespaces names do not match: %1$s versus %2$s",
+ thisNS, otherNS));
+ }
+ } else {
+ if (!getXml().getNodeName().equals(otherNode.getXml().getNodeName())) {
+ return Optional.of(String.format("Element names do not match: %1$s versus %2$s",
+ getXml().getNodeName(),
+ otherNode.getXml().getNodeName()));
+ }
+ }
+
+ // compare attributes, we do it twice to identify added/missing elements in both lists.
+ Optional<String> message = checkAttributes(this, otherNode);
+ if (message.isPresent()) {
+ return message;
+ }
+ message = checkAttributes(otherNode, this);
+ if (message.isPresent()) {
+ return message;
+ }
+
+ // compare children
+ @NonNull List<Node> expectedChildren = filterUninterestingNodes(getXml().getChildNodes());
+ @NonNull List<Node> actualChildren = filterUninterestingNodes(otherNode.getXml().getChildNodes());
+ if (expectedChildren.size() != actualChildren.size()) {
+
+ if (expectedChildren.size() > actualChildren.size()) {
+ // missing some.
+ @NonNull List<String> missingChildrenNames =
+ Lists.transform(expectedChildren, NODE_TO_NAME);
+ missingChildrenNames.removeAll(Lists.transform(actualChildren, NODE_TO_NAME));
+ return Optional.of(String.format(
+ "%1$s: Number of children do not match up: "
+ + "expected %2$d versus %3$d at %4$s, missing %5$s",
+ getId(),
+ expectedChildren.size(),
+ actualChildren.size(),
+ otherNode.printPosition(),
+ Joiner.on(",").join(missingChildrenNames)));
+ } else {
+ // extra ones.
+ @NonNull List<String> extraChildrenNames = Lists.transform(actualChildren, NODE_TO_NAME);
+ extraChildrenNames.removeAll(Lists.transform(expectedChildren, NODE_TO_NAME));
+ return Optional.of(String.format(
+ "%1$s: Number of children do not match up: "
+ + "expected %2$d versus %3$d at %4$s, extra elements found : %5$s",
+ getId(),
+ expectedChildren.size(),
+ actualChildren.size(),
+ otherNode.printPosition(),
+ Joiner.on(",").join(expectedChildren)));
+ }
+ }
+ for (Node expectedChild : expectedChildren) {
+ if (expectedChild.getNodeType() == Node.ELEMENT_NODE) {
+ @NonNull XmlElement expectedChildNode = new XmlElement((Element) expectedChild, mDocument);
+ message = findAndCompareNode(otherNode, actualChildren, expectedChildNode);
+ if (message.isPresent()) {
+ return message;
+ }
+ }
+ }
+ return Optional.absent();
+ }
+
+ private Optional<String> findAndCompareNode(
+ @NonNull XmlElement otherElement,
+ @NonNull List<Node> otherElementChildren,
+ @NonNull XmlElement childNode) {
+
+ Optional<String> message = Optional.absent();
+ for (Node potentialNode : otherElementChildren) {
+ if (potentialNode.getNodeType() == Node.ELEMENT_NODE) {
+ @NonNull XmlElement otherChildNode = new XmlElement((Element) potentialNode, mDocument);
+ if (childNode.getType() == otherChildNode.getType()) {
+ // check if this element uses a key.
+ if (childNode.getType().getNodeKeyResolver().getKeyAttributesNames()
+ .isEmpty()) {
+ // no key... try all the other elements, if we find one equal, we are done.
+ message = childNode.compareTo(otherChildNode);
+ if (!message.isPresent()) {
+ return Optional.absent();
+ }
+ } else {
+ // key...
+ if (childNode.getKey() == null) {
+ // other key MUST also be null.
+ if (otherChildNode.getKey() == null) {
+ return childNode.compareTo(otherChildNode);
+ }
+ } else {
+ if (childNode.getKey().equals(otherChildNode.getKey())) {
+ return childNode.compareTo(otherChildNode);
+ }
+ }
+ }
+ }
+ }
+ }
+ return message.isPresent()
+ ? message
+ : Optional.of(String.format("Child %1$s not found in document %2$s",
+ childNode.getId(),
+ otherElement.printPosition()));
+ }
+
+ @NonNull
+ private static List<Node> filterUninterestingNodes(@NonNull NodeList nodeList) {
+ List<Node> interestingNodes = new ArrayList<Node>();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ if (node.getNodeType() == Node.TEXT_NODE) {
+ Text t = (Text) node;
+ if (!t.getData().trim().isEmpty()) {
+ interestingNodes.add(node);
+ }
+ } else if (node.getNodeType() != Node.COMMENT_NODE) {
+ interestingNodes.add(node);
+ }
+
+ }
+ return interestingNodes;
+ }
+
+ private static Optional<String> checkAttributes(
+ @NonNull XmlElement expected,
+ @NonNull XmlElement actual) {
+
+ for (XmlAttribute expectedAttr : expected.getAttributes()) {
+ XmlAttribute.NodeName attributeName = expectedAttr.getName();
+ if (attributeName.isInNamespace(SdkConstants.TOOLS_URI)) {
+ continue;
+ }
+ Optional<XmlAttribute> actualAttr = actual.getAttribute(attributeName);
+ if (actualAttr.isPresent()) {
+ if (!expectedAttr.getValue().equals(actualAttr.get().getValue())) {
+ return Optional.of(
+ String.format("Attribute %1$s do not match: %2$s versus %3$s at %4$s",
+ expectedAttr.getId(),
+ expectedAttr.getValue(),
+ actualAttr.get().getValue(),
+ actual.printPosition()));
+ }
+ } else {
+ return Optional.of(String.format("Attribute %1$s not found at %2$s",
+ expectedAttr.getId(), actual.printPosition()));
+ }
+ }
+ return Optional.absent();
+ }
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private ImmutableList<XmlElement> initMergeableChildren() {
+ ImmutableList.Builder<XmlElement> mergeableNodes = new ImmutableList.Builder<XmlElement>();
+ NodeList nodeList = getXml().getChildNodes();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ if (node instanceof Element) {
+ XmlElement xmlElement = new XmlElement((Element) node, mDocument);
+ mergeableNodes.add(xmlElement);
+ }
+ }
+ return mergeableNodes.build();
+ }
+
+ /**
+ * Returns all leading comments in the source xml before the node to be adopted.
+ * @param nodeToBeAdopted node that will be added as a child to this node.
+ */
+ static List<Node> getLeadingComments(@NonNull Node nodeToBeAdopted) {
+ @NonNull ImmutableList.Builder<Node> nodesToAdopt = new ImmutableList.Builder<Node>();
+ Node previousSibling = nodeToBeAdopted.getPreviousSibling();
+ while (previousSibling != null
+ && (previousSibling.getNodeType() == Node.COMMENT_NODE
+ || previousSibling.getNodeType() == Node.TEXT_NODE)) {
+ // we really only care about comments.
+ if (previousSibling.getNodeType() == Node.COMMENT_NODE) {
+ nodesToAdopt.add(previousSibling);
+ }
+ previousSibling = previousSibling.getPreviousSibling();
+ }
+ return nodesToAdopt.build().reverse();
+ }
+
+ void addMessage(@NonNull MergingReport.Builder mergingReport,
+ @NonNull MergingReport.Record.Severity severity,
+ @NonNull String message) {
+ mergingReport.addMessage(getSourceFilePosition(),
+ severity,
+ message);
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java
new file mode 100644
index 0000000..aa7e930
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlLoader.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.blame.SourceFile;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Optional;
+import com.google.common.io.Closeables;
+
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Responsible for loading XML files.
+ */
+public final class XmlLoader {
+
+ private XmlLoader() {}
+
+ /**
+ * Loads an xml file without doing xml validation and return a {@link XmlDocument}
+ *
+ * @param displayName the xml file display name.
+ * @param xmlFile the xml file.
+ * @return the initialized {@link com.android.manifmerger.XmlDocument}
+ */
+ @NonNull
+ public static XmlDocument load(
+ @NonNull KeyResolver<String> selectors,
+ @NonNull KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
+ @NonNull String displayName,
+ @NonNull File xmlFile,
+ @NonNull InputStream inputStream,
+ @NonNull XmlDocument.Type type,
+ @NonNull Optional<String> mainManifestPackageName)
+ throws IOException, SAXException, ParserConfigurationException {
+ Document domDocument = PositionXmlParser.parse(inputStream);
+ return new XmlDocument(
+ new SourceFile(xmlFile, displayName),
+ selectors,
+ systemPropertyResolver,
+ domDocument.getDocumentElement(),
+ type,
+ mainManifestPackageName);
+ }
+
+ /**
+ * Loads a xml document from its {@link String} representation without doing xml validation and
+ * return a {@link com.android.manifmerger.XmlDocument}
+ * @param sourceFile the source location to use for logging and record collection.
+ * @param xml the persisted xml.
+ * @return the initialized {@link com.android.manifmerger.XmlDocument}
+ * @throws IOException this should never be thrown.
+ * @throws SAXException if the xml is incorrect
+ * @throws ParserConfigurationException if the xml engine cannot be configured.
+ */
+ @NonNull
+ public static XmlDocument load(
+ @NonNull KeyResolver<String> selectors,
+ @NonNull KeyBasedValueResolver<SystemProperty> systemPropertyResolver,
+ @NonNull SourceFile sourceFile,
+ @NonNull String xml,
+ @NonNull XmlDocument.Type type,
+ @NonNull Optional<String> mainManifestPackageName)
+ throws IOException, SAXException, ParserConfigurationException {
+ Document domDocument = PositionXmlParser.parse(xml);
+ return new XmlDocument(
+ sourceFile,
+ selectors,
+ systemPropertyResolver,
+ domDocument.getDocumentElement(),
+ type,
+ mainManifestPackageName);
+ }
+}
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java
new file mode 100644
index 0000000..3e847a0
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/XmlNode.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * Common behavior of any xml declaration.
+ */
+public abstract class XmlNode {
+
+ protected static final Function<Node, String> NODE_TO_NAME =
+ new Function<Node, String>() {
+ @Override
+ public String apply(Node input) {
+ return input.getNodeName();
+ }
+ };
+
+ @Nullable
+ private NodeKey mOriginalId = null;
+
+ /**
+ * Returns a constant Nodekey that can be used throughout the lifecycle of the xml element.
+ * The {@link #getId} can return different values over time as the key of the element can be
+ * for instance, changed through placeholder replacement.
+ */
+ @NonNull
+ public synchronized NodeKey getOriginalId() {
+ if (mOriginalId == null) {
+ mOriginalId = getId();
+ }
+ return mOriginalId;
+ }
+
+ /**
+ * Returns an unique id within the manifest file for the element.
+ */
+ @NonNull
+ public abstract NodeKey getId();
+
+ /**
+ * Returns the element's position
+ */
+ @NonNull
+ public abstract SourcePosition getPosition();
+
+ /**
+ * Returns the element's document xml source file location.
+ */
+ @NonNull
+ public abstract SourceFile getSourceFile();
+
+ /**
+ * Returns the element's document xml source file location.
+ */
+ @NonNull
+ public SourceFilePosition getSourceFilePosition() {
+ return new SourceFilePosition(getSourceFile(), getPosition());
+ }
+
+ /**
+ * Returns the element's xml
+ */
+ @NonNull
+ public abstract Node getXml();
+
+ /**
+ * Returns the name of this xml element or attribute.
+ */
+ @NonNull
+ public abstract NodeName getName();
+
+ /**
+ * Abstraction to an xml name to isolate whether the name has a namespace or not.
+ */
+ public interface NodeName {
+
+ /**
+ * Returns true if this attribute name has a namespace declaration and that namespapce is
+ * the same as provided, false otherwise.
+ */
+ boolean isInNamespace(@NonNull String namespaceURI);
+
+ /**
+ * Adds a new attribute of this name to a xml element with a value.
+ * @param to the xml element to add the attribute to.
+ * @param withValue the new attribute's value.
+ */
+ void addToNode(@NonNull Element to, String withValue);
+
+ /**
+ * The local name.
+ */
+ String getLocalName();
+ }
+
+ /**
+ * Factory method to create an instance of {@link com.android.manifmerger.XmlNode.NodeName}
+ * for an existing xml node.
+ * @param node the xml definition.
+ * @return an instance of {@link com.android.manifmerger.XmlNode.NodeName} providing
+ * namespace handling.
+ */
+ @NonNull
+ public static NodeName unwrapName(@NonNull Node node) {
+ return node.getNamespaceURI() == null
+ ? new Name(node.getNodeName())
+ : new NamespaceAwareName(node);
+ }
+
+ @NonNull
+ public static NodeName fromXmlName(@NonNull String name) {
+ return (name.contains(":"))
+ ? new NamespaceAwareName(SdkConstants.ANDROID_URI,
+ name.substring(0, name.indexOf(':')),
+ name.substring(name.indexOf(':') + 1))
+ : new Name(name);
+ }
+
+ @NonNull
+ public static NodeName fromNSName(
+ @NonNull String namespaceUri, @NonNull String prefix, @NonNull String localName) {
+ return new NamespaceAwareName(namespaceUri, prefix, localName);
+ }
+
+ /**
+ * Returns the position of this attribute in the original xml file. This may return an invalid
+ * location as this xml fragment does not exist in any xml file but is the temporary result
+ * of the merging process.
+ * @return a human readable position.
+ */
+ @NonNull
+ public String printPosition() {
+ return getSourceFilePosition().print(true /*shortFormat*/);
+ }
+
+ /**
+ * Implementation of {@link com.android.manifmerger.XmlNode.NodeName} for an
+ * node's declaration not using a namespace.
+ */
+ public static final class Name implements NodeName {
+ private final String mName;
+
+ private Name(@NonNull String name) {
+ this.mName = Preconditions.checkNotNull(name);
+ }
+
+ @Override
+ public boolean isInNamespace(@NonNull String namespaceURI) {
+ return false;
+ }
+
+ @Override
+ public void addToNode(@NonNull Element to, String withValue) {
+ to.setAttribute(mName, withValue);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ return (o != null && o instanceof Name && ((Name) o).mName.equals(this.mName));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mName);
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+
+ @Override
+ public String getLocalName() {
+ return mName;
+ }
+ }
+
+ /**
+ * Implementation of the {@link com.android.manifmerger.XmlNode.NodeName} for a namespace aware attribute.
+ */
+ public static final class NamespaceAwareName implements NodeName {
+
+ @NonNull
+ private final String mNamespaceURI;
+
+ // ignore for comparison and hashcoding since different documents can use different
+ // prefixes for the same namespace URI.
+ @NonNull
+ private final String mPrefix;
+ @NonNull
+ private final String mLocalName;
+
+ private NamespaceAwareName(@NonNull Node node) {
+ this.mNamespaceURI = Preconditions.checkNotNull(node.getNamespaceURI());
+ this.mPrefix = Preconditions.checkNotNull(node.getPrefix());
+ this.mLocalName = Preconditions.checkNotNull(node.getLocalName());
+ }
+
+ private NamespaceAwareName(@NonNull String namespaceURI,
+ @NonNull String prefix,
+ @NonNull String localName) {
+ mNamespaceURI = Preconditions.checkNotNull(namespaceURI);
+ mPrefix = Preconditions.checkNotNull(prefix);
+ mLocalName = Preconditions.checkNotNull(localName);
+ }
+
+ @Override
+ public boolean isInNamespace(@NonNull String namespaceURI) {
+ return mNamespaceURI.equals(namespaceURI);
+ }
+
+ @Override
+ public void addToNode(@NonNull Element to, String withValue) {
+ // TODO: consider standardizing everything on "android:"
+ to.setAttributeNS(mNamespaceURI, mPrefix + ":" + mLocalName, withValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mNamespaceURI, mLocalName);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ return (o != null && o instanceof NamespaceAwareName
+ && ((NamespaceAwareName) o).mLocalName.equals(this.mLocalName)
+ && ((NamespaceAwareName) o).mNamespaceURI.equals(this.mNamespaceURI));
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return mPrefix + ":" + mLocalName;
+ }
+
+ @NonNull
+ @Override
+ public String getLocalName() {
+ return mLocalName;
+ }
+ }
+
+ /**
+ * A xml element or attribute key.
+ */
+ @Immutable
+ public static class NodeKey {
+
+ @NonNull
+ private final String mKey;
+
+ NodeKey(@NonNull String key) {
+ mKey = key;
+ }
+
+ public static NodeKey fromXml(@NonNull Element element) {
+ return new OrphanXmlElement(element).getId();
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ return (o != null && o instanceof NodeKey && ((NodeKey) o).mKey.equals(this.mKey));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mKey);
+ }
+ }
+}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionRecorderTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionRecorderTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionRecorderTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionRecorderTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionsTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionsTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionsTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/ActionsTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/AttributeModelTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/AttributeModelTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/AttributeModelTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/AttributeModelTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ElementsTrimmerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ElementsTrimmerTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ElementsTrimmerTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/ElementsTrimmerTest.java
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java
new file mode 100644
index 0000000..06d78de
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2SmallTest.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.manifmerger.MergingReport.MergedManifestKind;
+import com.android.sdklib.mock.MockLog;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link ManifestMergerTestUtil} class
+ */
+ at RunWith(MockitoJUnitRunner.class)
+public class ManifestMerger2SmallTest {
+
+ @Mock
+ private ActionRecorder mActionRecorder;
+
+ @Test
+ public void testValidationFailure() throws Exception {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" />\n"
+ + "\n"
+ + " <activity android:name=\"activityOne\" "
+ + " tools:replace=\"exported\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ File tmpFile = inputAsFile("ManifestMerger2Test_testValidationFailure", input);
+ assertTrue(tmpFile.exists());
+
+ try {
+ MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
+ ManifestMerger2.MergeType.APPLICATION).merge();
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ // check the log complains about the incorrect "tools:replace"
+ assertStringPresenceInLogRecords(mergingReport, "tools:replace");
+ assertNull(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ } finally {
+ assertTrue(tmpFile.delete());
+ }
+ }
+
+ @Test
+ public void testToolsAnnotationRemoval() throws Exception {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" "
+ + " tools:replace=\"label\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ File tmpFile = inputAsFile("testToolsAnnotationRemoval", input);
+ assertTrue(tmpFile.exists());
+
+ try {
+ MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
+ ManifestMerger2.MergeType.APPLICATION)
+ .withFeatures(ManifestMerger2.Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)
+ .merge();
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ // ensure tools annotation removal.
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ NodeList applications = xmlDocument.getElementsByTagName(SdkConstants.TAG_APPLICATION);
+ assertTrue(applications.getLength() == 1);
+ Node replace = applications.item(0).getAttributes()
+ .getNamedItemNS(SdkConstants.TOOLS_URI, "replace");
+ assertNull(replace);
+ } finally {
+ assertTrue(tmpFile.delete());
+ }
+ }
+
+ @Test
+ public void testToolsAnnotationPresence() throws Exception {
+
+ MockLog mockLog = new MockLog();
+ String input = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" "
+ + " tools:replace=\"label\"/>\n"
+ + "\n"
+ + "</manifest>";
+
+ File tmpFile = inputAsFile("testToolsAnnotationRemoval", input);
+ assertTrue(tmpFile.exists());
+
+ try {
+ MergingReport mergingReport = ManifestMerger2.newMerger(tmpFile, mockLog,
+ ManifestMerger2.MergeType.LIBRARY)
+ .merge();
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ // ensure tools annotation removal.
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ NodeList applications = xmlDocument.getElementsByTagName(SdkConstants.TAG_APPLICATION);
+ assertTrue(applications.getLength() == 1);
+ Node replace = applications.item(0).getAttributes()
+ .getNamedItemNS(SdkConstants.TOOLS_URI, "replace");
+ assertNotNull(replace);
+ assertEquals("tools:replace value not correct", "label", replace.getNodeValue());
+ } finally {
+ assertTrue(tmpFile.delete());
+ }
+ }
+
+
+ @Test
+ public void testPackageOverride() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ + " package=\"com.foo.old\" >\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), "testPackageOverride#xml"), xml);
+
+ ManifestMerger2.SystemProperty.PACKAGE.addTo(mActionRecorder, refDocument, "com.bar.new");
+ // verify the package value was overridden.
+ assertEquals("com.bar.new", refDocument.getRootNode().getXml().getAttribute("package"));
+ }
+
+ @Test
+ public void testMissingPackageOverride() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), "testMissingPackageOverride#xml"), xml);
+
+ ManifestMerger2.SystemProperty.PACKAGE.addTo(mActionRecorder, refDocument, "com.bar.new");
+ // verify the package value was added.
+ assertEquals("com.bar.new", refDocument.getRootNode().getXml().getAttribute("package"));
+ }
+
+ @Test
+ public void testAddingSystemProperties() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument document = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(),
+ "testAddingSystemProperties#xml"), xml);
+
+ ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
+ assertEquals("101",
+ document.getXml().getDocumentElement().getAttribute("android:versionCode"));
+
+ ManifestMerger2.SystemProperty.VERSION_NAME.addTo(mActionRecorder, document, "1.0.1");
+ assertEquals("1.0.1",
+ document.getXml().getDocumentElement().getAttribute("android:versionName"));
+
+ ManifestMerger2.SystemProperty.MIN_SDK_VERSION.addTo(mActionRecorder, document, "10");
+ Element usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("10", usesSdk.getAttribute("android:minSdkVersion"));
+
+ ManifestMerger2.SystemProperty.TARGET_SDK_VERSION.addTo(mActionRecorder, document, "14");
+ usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("14", usesSdk.getAttribute("android:targetSdkVersion"));
+
+ ManifestMerger2.SystemProperty.MAX_SDK_VERSION.addTo(mActionRecorder, document, "16");
+ usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("16", usesSdk.getAttribute("android:maxSdkVersion"));
+ }
+
+ @Test
+ public void testAddingSystemProperties_withDifferentPrefix() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity t:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument document = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(),
+ "testAddingSystemProperties#xml"), xml
+ );
+
+ ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
+ // using the non namespace aware API to make sure the prefix is the expected one.
+ assertEquals("101",
+ document.getXml().getDocumentElement().getAttribute("t:versionCode"));
+ }
+
+ @Test
+ public void testOverridingSystemProperties() throws Exception {
+ String xml = ""
+ + "<manifest versionCode=\"34\" versionName=\"3.4\"\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <uses-sdk minSdkVersion=\"9\" targetSdkVersion=\".9\"/>\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + "</manifest>";
+
+ XmlDocument document = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(),
+ "testAddingSystemProperties#xml"), xml);
+ // check initial state.
+ assertEquals("34", document.getXml().getDocumentElement().getAttribute("versionCode"));
+ assertEquals("3.4", document.getXml().getDocumentElement().getAttribute("versionName"));
+ Element usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("9", usesSdk.getAttribute("minSdkVersion"));
+ assertEquals(".9", usesSdk.getAttribute("targetSdkVersion"));
+
+
+ ManifestMerger2.SystemProperty.VERSION_CODE.addTo(mActionRecorder, document, "101");
+ assertEquals("101",
+ document.getXml().getDocumentElement().getAttribute("android:versionCode"));
+
+ ManifestMerger2.SystemProperty.VERSION_NAME.addTo(mActionRecorder, document, "1.0.1");
+ assertEquals("1.0.1",
+ document.getXml().getDocumentElement().getAttribute("android:versionName"));
+
+ ManifestMerger2.SystemProperty.MIN_SDK_VERSION.addTo(mActionRecorder, document, "10");
+ usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("10", usesSdk.getAttribute("android:minSdkVersion"));
+
+ ManifestMerger2.SystemProperty.TARGET_SDK_VERSION.addTo(mActionRecorder, document, "14");
+ usesSdk = (Element) document.getXml().getElementsByTagName("uses-sdk").item(0);
+ assertNotNull(usesSdk);
+ assertEquals("14", usesSdk.getAttribute("android:targetSdkVersion"));
+ }
+
+ @Test
+ public void testPlaceholderSubstitution() throws Exception {
+ String xml = ""
+ + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\".activityOne\" android:label=\"${labelName}\"/>\n"
+ + "</manifest>";
+
+ Map<String, String> placeholders = ImmutableMap.of("labelName", "injectedLabelName");
+ MockLog mockLog = new MockLog();
+ File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
+ try {
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .setPlaceHolderValues(placeholders)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document document = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ assertNotNull(document);
+ Optional<Element> activityOne = getElementByTypeAndKey(
+ document, "activity", "foo.activityOne");
+ assertTrue(activityOne.isPresent());
+ Attr label = activityOne.get().getAttributeNodeNS(SdkConstants.ANDROID_URI, "label");
+ assertNotNull(label);
+ assertEquals("injectedLabelName", label.getValue());
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ inputFile.delete();
+ }
+ }
+
+ @Test
+ public void testApplicationIdSubstitution() throws Exception {
+ String xml = ""
+ + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"${applicationId}.activityOne\"/>\n"
+ + "</manifest>";
+
+ MockLog mockLog = new MockLog();
+ File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
+ try {
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .setOverride(ManifestMerger2.SystemProperty.PACKAGE, "bar")
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document document = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ assertEquals("bar", document.getElementsByTagName("manifest")
+ .item(0).getAttributes().getNamedItem("package").getNodeValue());
+ Optional<Element> activityOne = getElementByTypeAndKey(document, "activity",
+ "bar.activityOne");
+ assertTrue(activityOne.isPresent());
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ inputFile.delete();
+ }
+ }
+
+ @Test
+ public void testNoApplicationIdValueProvided() throws Exception {
+ String xml = ""
+ + "<manifest package=\"foo\" versionCode=\"34\" versionName=\"3.4\"\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"${applicationId}.activityOne\"/>\n"
+ + "</manifest>";
+
+ MockLog mockLog = new MockLog();
+ File inputFile = inputAsFile("testPlaceholderSubstitution", xml);
+ try {
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ assertNotNull(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ Document document = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ assertEquals("foo", document.getElementsByTagName("manifest")
+ .item(0).getAttributes().getNamedItem("package").getNodeValue());
+ Optional<Element> activityOne = getElementByTypeAndKey(document, "activity",
+ "foo.activityOne");
+ assertTrue(activityOne.isPresent());
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ inputFile.delete();
+ }
+ }
+
+ @Test
+ public void testNoFqcnsExtraction() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " package=\"com.foo.example\""
+ + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity t:name=\"activityOne\"/>\n"
+ + " <activity t:name=\"com.foo.bar.example.activityTwo\"/>\n"
+ + " <activity t:name=\"com.foo.example.activityThree\"/>\n"
+ + " <application t:name=\".applicationOne\" "
+ + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
+ + "</manifest>";
+
+ File inputFile = inputAsFile("testFcqnsExtraction", xml);
+
+ MockLog mockLog = new MockLog();
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ assertEquals("com.foo.example.activityOne",
+ xmlDocument.getElementsByTagName("activity").item(0).getAttributes()
+ .item(0).getNodeValue());
+ assertEquals("com.foo.bar.example.activityTwo",
+ xmlDocument.getElementsByTagName("activity").item(1).getAttributes()
+ .item(0).getNodeValue());
+ assertEquals("com.foo.example.activityThree",
+ xmlDocument.getElementsByTagName("activity").item(2).getAttributes()
+ .item(0).getNodeValue());
+ assertEquals("com.foo.example.applicationOne",
+ xmlDocument.getElementsByTagName("application").item(0).getAttributes()
+ .getNamedItemNS("http://schemas.android.com/apk/res/android", "name")
+ .getNodeValue());
+ assertEquals("com.foo.example.myBackupAgent",
+ xmlDocument.getElementsByTagName("application").item(0).getAttributes()
+ .getNamedItemNS("http://schemas.android.com/apk/res/android", "backupAgent")
+ .getNodeValue());
+ }
+
+ @Test
+ public void testFqcnsExtraction() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " package=\"com.foo.example\""
+ + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity t:name=\"activityOne\"/>\n"
+ + " <activity t:name=\"com.foo.bar.example.activityTwo\"/>\n"
+ + " <activity t:name=\"com.foo.example.activityThree\"/>\n"
+ + " <application t:name=\".applicationOne\" "
+ + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
+ + "</manifest>";
+
+ File inputFile = inputAsFile("testFcqnsExtraction", xml);
+
+ MockLog mockLog = new MockLog();
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .withFeatures(ManifestMerger2.Invoker.Feature.EXTRACT_FQCNS)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ assertEquals(".activityOne",
+ xmlDocument.getElementsByTagName("activity").item(0).getAttributes()
+ .item(0).getNodeValue());
+ assertEquals("com.foo.bar.example.activityTwo",
+ xmlDocument.getElementsByTagName("activity").item(1).getAttributes()
+ .item(0).getNodeValue());
+ assertEquals(".activityThree",
+ xmlDocument.getElementsByTagName("activity").item(2).getAttributes()
+ .item(0).getNodeValue());
+ assertEquals(".applicationOne",
+ xmlDocument.getElementsByTagName("application").item(0).getAttributes()
+ .getNamedItemNS("http://schemas.android.com/apk/res/android", "name")
+ .getNodeValue());
+ assertEquals(".myBackupAgent",
+ xmlDocument.getElementsByTagName("application").item(0).getAttributes()
+ .getNamedItemNS("http://schemas.android.com/apk/res/android", "backupAgent")
+ .getNodeValue());
+ }
+
+ @Test
+ public void testNoPlaceholderReplacement() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " package=\"${applicationId}\""
+ + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity t:name=\"activityOne\"/>\n"
+ + " <application t:name=\".applicationOne\" "
+ + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
+ + "</manifest>";
+
+ File inputFile = inputAsFile("testNoPlaceHolderReplacement", xml);
+
+ MockLog mockLog = new MockLog();
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .withFeatures(ManifestMerger2.Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ assertEquals("${applicationId}",
+ xmlDocument.getElementsByTagName("manifest")
+ .item(0).getAttributes().getNamedItem("package").getNodeValue());
+ }
+
+ @Test
+ public void testReplaceInputStream() throws Exception {
+ // This test is identical to testNoPlaceholderReplacement but instead
+ // of reading from a string, we test the ManifestMerger's ability to
+ // supply a custom input stream
+ final String xml = ""
+ + "<manifest\n"
+ + " package=\"${applicationId}\""
+ + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity t:name=\"activityOne\"/>\n"
+ + " <application t:name=\".applicationOne\" "
+ + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
+ + "</manifest>";
+ String staleContent = "<manifest />";
+
+ // Note: disk content is wrong/stale; make sure we read the live content instead
+ File inputFile = inputAsFile("testNoPlaceHolderReplacement", staleContent);
+
+ MockLog mockLog = new MockLog();
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .withFeatures(ManifestMerger2.Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)
+ .withFileStreamProvider(new ManifestMerger2.FileStreamProvider() {
+ @Override
+ protected InputStream getInputStream(@NonNull File file)
+ throws FileNotFoundException {
+ return new ByteArrayInputStream(xml.getBytes(Charsets.UTF_8));
+ }
+ })
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.MERGED));
+ assertEquals("${applicationId}",
+ xmlDocument.getElementsByTagName("manifest")
+ .item(0).getAttributes().getNamedItem("package").getNodeValue());
+ }
+
+ @Test
+ public void testInstantRunReplacement() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " package=\"com.foo.bar\""
+ + " xmlns:t=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity t:name=\"activityOne\"/>\n"
+ + " <application t:name=\".applicationOne\" "
+ + " t:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
+ + "</manifest>";
+
+ File inputFile = inputAsFile("testNoPlaceHolderReplacement", xml);
+
+ MockLog mockLog = new MockLog();
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .withFeatures(ManifestMerger2.Invoker.Feature.INSTANT_RUN_REPLACEMENT)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.INSTANT_RUN));
+ assertEquals("com.foo.bar.applicationOne",
+ xmlDocument.getElementsByTagName(SdkConstants.TAG_APPLICATION)
+ .item(0).getAttributes().getNamedItem(
+ SdkConstants.ATTR_NAME).getNodeValue());
+ assertEquals(ManifestMerger2.BOOTSTRAP_APPLICATION,
+ xmlDocument.getElementsByTagName(SdkConstants.TAG_APPLICATION)
+ .item(0).getAttributes().getNamedItemNS(SdkConstants.ANDROID_URI,
+ SdkConstants.ATTR_NAME).getNodeValue());
+ }
+
+ @Test
+ public void testInstantRunReplacementWithNoAppName() throws Exception {
+ String xml = ""
+ + "<manifest\n"
+ + " package=\"com.foo.bar\""
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"/>\n"
+ + " <application android:backupAgent=\"com.foo.example.myBackupAgent\"/>\n"
+ + "</manifest>";
+
+ File inputFile = inputAsFile("testNoPlaceHolderReplacement", xml);
+
+ MockLog mockLog = new MockLog();
+ MergingReport mergingReport = ManifestMerger2
+ .newMerger(inputFile, mockLog, ManifestMerger2.MergeType.APPLICATION)
+ .withFeatures(ManifestMerger2.Invoker.Feature.INSTANT_RUN_REPLACEMENT)
+ .merge();
+
+ assertTrue(mergingReport.getResult().isSuccess());
+ Document xmlDocument = parse(mergingReport.getMergedDocument(MergedManifestKind.INSTANT_RUN));
+ assertEquals(ManifestMerger2.BOOTSTRAP_APPLICATION,
+ xmlDocument.getElementsByTagName(SdkConstants.TAG_APPLICATION)
+ .item(0).getAttributes().getNamedItemNS(SdkConstants.ANDROID_URI,
+ SdkConstants.ATTR_NAME).getNodeValue());
+ }
+
+ public static Optional<Element> getElementByTypeAndKey(Document xmlDocument, String nodeType, String key) {
+ NodeList elementsByTagName = xmlDocument.getElementsByTagName(nodeType);
+ for (int i = 0; i < elementsByTagName.getLength(); i++) {
+ Node item = elementsByTagName.item(i);
+ Node name = item.getAttributes().getNamedItemNS(SdkConstants.ANDROID_URI, "name");
+ if ((name == null && key == null) || (name != null && key.equals(name.getNodeValue()))) {
+ return Optional.of((Element) item);
+ }
+ }
+ return Optional.absent();
+ }
+
+ /**
+ * Utility method to save a {@link String} XML into a file.
+ */
+ private static File inputAsFile(String testName, String input) throws IOException {
+ File tmpFile = File.createTempFile(testName, ".xml");
+ tmpFile.deleteOnExit();
+ Files.write(input, tmpFile, Charsets.UTF_8);
+ return tmpFile;
+ }
+
+ private static void assertStringPresenceInLogRecords(MergingReport mergingReport, String s) {
+ for (MergingReport.Record record : mergingReport.getLoggingRecords()) {
+ if (record.toString().contains(s)) {
+ return;
+ }
+ }
+ // failed, dump the records
+ for (MergingReport.Record record : mergingReport.getLoggingRecords()) {
+ Logger.getAnonymousLogger().info(record.toString());
+ }
+ fail("could not find " + s + " in logging records");
+ }
+
+ private static Document parse(String xml)
+ throws IOException, SAXException, ParserConfigurationException {
+ return XmlUtils.parseDocument(xml, true /* namespaceAware */);
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java
new file mode 100644
index 0000000..ecd446a
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMerger2Test.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.ManifestMerger2.SystemProperty;
+import static com.android.manifmerger.ManifestMergerTestUtil.loadTestData;
+import static com.android.manifmerger.ManifestMergerTestUtil.transformParameters;
+import static com.android.manifmerger.MergingReport.Record;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.android.annotations.Nullable;
+import com.android.utils.StdLogger;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests for the {@link com.android.manifmerger.ManifestMerger2} class
+ */
+ at RunWith(Parameterized.class)
+public class ManifestMerger2Test {
+
+ private static final String TEST_DATA_DIRECTORY = "data2";
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private static final String[] DATA_FILES = new String[]{
+ "00_noop",
+ "03_inject_attributes.xml",
+ "05_inject_package.xml",
+ "05_inject_package_placeholder.xml",
+ "05_inject_package_with_overlays.xml",
+ "06_inject_attributes_with_specific_prefix.xml",
+ "07_no_package_provided.xml",
+ "08_no_library_package_provided.xml",
+ "09_overlay_package_provided.xml",
+ "08b_library_injection.xml",
+ "09b_overlay_package_different.xml",
+ "09c_overlay_package_not_provided.xml",
+ "10_activity_merge",
+ "11_activity_dup",
+ "12_alias_dup",
+ "13_service_dup",
+ "14_receiver_dup",
+ "15_provider_dup",
+ "16_fqcn_merge",
+ "17_fqcn_conflict",
+ "18_fqcn_success",
+ "20_uses_lib_merge",
+ "21_uses_main_errors",
+ "22_uses_lib_errors",
+ "25_permission_merge",
+ "26_permission_dup",
+ "28_uses_perm_merge",
+ "29_uses_perm_selector",
+ "29b_uses_perm_invalidSelector",
+ "30_uses_sdk_ok",
+ "32_uses_sdk_minsdk_ok",
+ "33_uses_sdk_minsdk_conflict",
+ "33b_uses_sdk_minsdk_override.xml",
+ "33c_uses_sdk_minsdk_override_and_conflict.xml",
+ "34_inject_uses_sdk_no_dup.xml",
+ "36_uses_sdk_targetsdk_warning",
+ "40_uses_feat_merge",
+ "41_uses_feat_errors",
+ "45_uses_feat_gles_once",
+ "47_uses_feat_gles_conflict",
+ "50_uses_conf_warning",
+ "52_support_screens_warning",
+ "54_compat_screens_warning",
+ "56_support_gltext_warning",
+ "60_merge_order",
+ "65_override_app",
+ "66_remove_app",
+ "67_override_activities",
+ "68_override_uses",
+ "69_remove_uses",
+ "70_expand_fqcns",
+ "71_extract_package_prefix",
+ "75_app_metadata_merge",
+ "76_app_metadata_ignore",
+ "77_app_metadata_conflict",
+ "78_removeAll",
+ "79_custom_node.xml",
+ };
+
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> getParameters() {
+ return transformParameters(DATA_FILES);
+ }
+
+ private final String fileName;
+
+ public ManifestMerger2Test(String fileName) {
+ this.fileName = fileName;
+ }
+
+ @Test
+ public void processTestFiles() throws Exception {
+ ManifestMergerTestUtil.TestFiles testFiles =
+ loadTestData(TEST_DATA_DIRECTORY, fileName, getClass().getSimpleName());
+
+ StdLogger stdLogger = new StdLogger(StdLogger.Level.VERBOSE);
+ ManifestMerger2.Invoker invoker = ManifestMerger2.newMerger(testFiles.getMain(),
+ stdLogger, ManifestMerger2.MergeType.APPLICATION)
+ .addLibraryManifests(testFiles.getLibs())
+ .addFlavorAndBuildTypeManifests(testFiles.getOverlayFiles())
+ .withFeatures(ManifestMerger2.Invoker.Feature.KEEP_INTERMEDIARY_STAGES,
+ ManifestMerger2.Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
+
+ if (!Strings.isNullOrEmpty(testFiles.getPackageOverride())) {
+ invoker.setOverride(SystemProperty.PACKAGE, testFiles.getPackageOverride());
+ }
+
+ for (Map.Entry<String, String> injectable : testFiles.getInjectAttributes().entrySet()) {
+ SystemProperty systemProperty = getSystemProperty(injectable.getKey());
+ if (systemProperty != null) {
+ invoker.setOverride(systemProperty, injectable.getValue());
+ } else {
+ invoker.setPlaceHolderValue(injectable.getKey(), injectable.getValue());
+ }
+ }
+
+ MergingReport mergeReport = invoker.merge();
+
+ // this is obviously quite hacky, refine once merge output is better defined.
+ boolean notExpectingError = !isExpectingError(testFiles.getExpectedErrors());
+ mergeReport.log(stdLogger);
+ if (mergeReport.getResult().isSuccess()) {
+ String xmlDocument = mergeReport.getMergedDocument(
+ MergingReport.MergedManifestKind.MERGED);
+ assertNotNull(xmlDocument);
+ stdLogger.info(xmlDocument);
+
+ if (testFiles.getActualResult() != null) {
+ FileWriter writer = new FileWriter(testFiles.getActualResult());
+ try {
+ writer.append(xmlDocument);
+ } finally {
+ writer.close();
+ }
+ }
+
+ if (!notExpectingError) {
+ fail("Did not get expected error : " + testFiles.getExpectedErrors());
+ }
+
+ XmlDocument expectedResult = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), testFiles.getMain().getName()),
+ testFiles.getExpectedResult());
+
+ XmlDocument actualResult = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), testFiles.getMain().getName()),
+ xmlDocument);
+
+ Optional<String> comparingMessage =
+ expectedResult.compareTo(actualResult);
+
+ if (comparingMessage.isPresent()) {
+ Logger.getAnonymousLogger().severe(comparingMessage.get());
+ fail(comparingMessage.get());
+ }
+ // process any warnings.
+ if (mergeReport.getResult() == MergingReport.Result.WARNING) {
+ compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
+ }
+ } else {
+ for (Record record : mergeReport.getLoggingRecords()) {
+ Logger.getAnonymousLogger().info("Returned log: " + record);
+ }
+ compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
+ assertFalse(notExpectingError);
+ }
+ }
+
+ private static boolean isExpectingError(String expectedOutput) throws IOException {
+ StringReader stringReader = new StringReader(expectedOutput);
+ BufferedReader reader = new BufferedReader(stringReader);
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("ERROR")) {
+ return true;
+ }
+ }
+ return false;
+ } finally {
+ reader.close();
+ }
+ }
+
+ private static void compareExpectedAndActualErrors(
+ MergingReport mergeReport,
+ String expectedOutput) throws IOException {
+
+ StringReader stringReader = new StringReader(expectedOutput);
+ List<Record> records = new ArrayList<Record>(mergeReport.getLoggingRecords());
+ BufferedReader reader = new BufferedReader(stringReader);
+ try {
+ String line = reader.readLine();
+ while (line != null) {
+ if (line.startsWith("WARNING") || line.startsWith("ERROR")) {
+ String message = line;
+ do {
+ line = reader.readLine();
+ if (line != null && line.startsWith(" ")) {
+ message = message + "\n" + line;
+ }
+ } while (line != null && line.startsWith(" "));
+
+ // next might generate an exception which will make the test fail when we
+ // get unexpected error message.
+ if (!findLineInRecords(message, records)) {
+
+ StringBuilder errorMessage = new StringBuilder();
+ dumpRecords(records, errorMessage);
+ errorMessage.append("Cannot find expected error : \n").append(message);
+ fail(errorMessage.toString());
+ }
+ }
+ }
+ } finally {
+ reader.close();
+ }
+ // check that we do not have any unexpected error messages.
+ if (!records.isEmpty()) {
+ StringBuilder message = new StringBuilder();
+ dumpRecords(records, message);
+ message.append("Unexpected error message(s)");
+ fail(message.toString());
+ }
+ }
+
+ private static boolean findLineInRecords(String errorLine, List<Record> records) {
+ String severity = errorLine.substring(0, errorLine.indexOf(':'));
+ String message = errorLine.substring(errorLine.indexOf(':') + 1);
+ for (Record record : records) {
+ int indexOfSuggestions = record.getMessage().indexOf("\n\tSuggestion:");
+ String messageRecord = indexOfSuggestions != -1
+ ? record.getMessage().substring(0, indexOfSuggestions)
+ : record.getMessage();
+ Pattern pattern = Pattern.compile(message);
+ Matcher matcher = pattern.matcher(messageRecord.replaceAll("\t", " "));
+ if (matcher.matches() && record.getSeverity() == Record.Severity.valueOf(severity)) {
+ records.remove(record);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private static SystemProperty getSystemProperty(String name) {
+ for (SystemProperty systemProperty : SystemProperty.values()) {
+ if (systemProperty.toCamelCase().equals(name)) {
+ return systemProperty;
+ }
+ }
+ return null;
+ }
+
+ private static void dumpRecords(List<Record> records, StringBuilder stringBuilder) {
+ stringBuilder.append("\n------------ Records : \n");
+ for (Record record : records) {
+ stringBuilder.append(record.toString());
+ stringBuilder.append("\n");
+ }
+ stringBuilder.append("------------ End of records.\n");
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java
new file mode 100644
index 0000000..1b954bc
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.manifmerger;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.manifmerger.IMergerLog.FileAndLine;
+import com.android.sdklib.mock.MockLog;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+
+import java.io.File;
+
+public class ManifestMergerSourceLinkTest extends TestCase {
+ public void testSourceLinks() throws Exception {
+ MockLog log = new MockLog();
+ IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
+ ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
+ @Override
+ public int queryCodenameApiLevel(@NonNull String codename) {
+ if ("ApiCodename1".equals(codename)) {
+ return 1;
+ } else if ("ApiCodename10".equals(codename)) {
+ return 10;
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ });
+ merger.setInsertSourceMarkers(true);
+
+ Document mainDoc = MergerXmlUtils.parseDocument(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.app1\"\n"
+ + " android:versionCode=\"100\"\n"
+ + " android:versionName=\"1.0.0\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"3\" android:targetSdkVersion=\"11\"/>\n"
+ + "\n"
+ + " <application\n"
+ + " android:name=\"TheApp\"\n"
+ + " android:backupAgent=\".MyBackupAgent\" >\n"
+ + " <activity android:name=\".MainActivity\" />\n"
+ + " <receiver android:name=\"AppReceiver\" />\n"
+ + " <activity android:name=\"com.example.lib2.LibActivity\" />\n"
+ + "\n"
+ + " <!-- This key is defined in the main application. -->\n"
+ + " <meta-data\n"
+ + " android:name=\"name.for.yet.another.api.key\"\n"
+ + " android:value=\"your_yet_another_api_key\"/>\n"
+ + "\n"
+ + " <!-- Merged elements will be appended here at the end. -->\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("main", 1));
+ assertNotNull(mainDoc);
+ Document library1 = MergerXmlUtils.parseDocument(""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.app1\">\n"
+ + "\n"
+ + " <application android:name=\"TheApp\" >\n"
+ + " <activity android:name=\".Library1\" />\n"
+ + "\n"
+ + " <!-- The library maps API key gets merged in the main application. -->\n"
+ + " <meta-data\n"
+ + " android:name=\"name.for.maps.api.key\"\n"
+ + " android:value=\"your_maps_api_key\"/>\n"
+ + "\n"
+ + " <!-- The library backup key gets merged in the main application. -->\n"
+ + " <meta-data\n"
+ + " android:name=\"name.for.backup.api.key\"\n"
+ + " android:value=\"your_backup_api_key\" />\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("library1", 1));
+ assertNotNull(library1);
+ Document library2 = MergerXmlUtils.parseDocument(""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <!-- This comment is ignored. -->\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" >\n"
+ + "\n"
+ + " <!-- The first comment just before the element\n"
+ + " is carried over as-is.\n"
+ + " -->\n"
+ + " <!-- Formatting is preserved. -->\n"
+ + " <!-- All consecutive comments are taken together. -->\n"
+ + "\n"
+ + " <activity-alias\n"
+ + " android:name=\"com.example.alias.MyActivity\"\n"
+ + " android:targetActivity=\"com.example.MainActivity\"\n"
+ + " android:label=\"@string/alias_name\"\n"
+ + " android:icon=\"@drawable/alias_icon\"\n"
+ + " >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity-alias>\n"
+ + "\n"
+ + " <!-- This is a dup of the 2nd activity in lib2 -->\n"
+ + " <activity\n"
+ + " android:name=\"com.example.LibActivity2\"\n"
+ + " android:label=\"@string/lib_activity_name\"\n"
+ + " android:icon=\"@drawable/lib_activity_icon\"\n"
+ + " android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("library2", 1));
+ assertNotNull(library2);
+
+ Document library3 = MergerXmlUtils.parseDocument(""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" >\n"
+ + " <activity\n"
+ + " android:name=\"com.example.LibActivity3\"\n"
+ + " android:label=\"@string/lib_activity_name3\"\n"
+ + " android:icon=\"@drawable/lib_activity_icon3\"\n"
+ + " android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("library3", 1));
+ assertNotNull(library3);
+
+ MergerXmlUtils.setSource(mainDoc, new File("/path/to/main/doc"));
+ MergerXmlUtils.setSource(library1, new File("/path/to/library1"));
+ MergerXmlUtils.setSource(library2, new File("/path/to/library2"));
+ MergerXmlUtils.setSource(library3, new File("/path/to/library3"));
+
+ boolean ok = merger.process(mainDoc, library1, library2, library3);
+ assertTrue(ok);
+ String actual = MergerXmlUtils.printXmlString(mainDoc, mergerLog);
+ assertEquals("Encountered unexpected errors/warnings", "", log.toString());
+ String expected = ""
+ + "<!-- From: file:///path/to/main/doc -->\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" android:versionCode=\"100\" android:versionName=\"1.0.0\" package=\"com.example.app1\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"3\" android:targetSdkVersion=\"11\"/>\n"
+ + "\n"
+ + " <application android:backupAgent=\"com.example.app1.MyBackupAgent\" android:name=\"com.example.app1.TheApp\">\n"
+ + " <activity android:name=\"com.example.app1.MainActivity\"/>\n"
+ + " <receiver android:name=\"com.example.app1.AppReceiver\"/>\n"
+ + " <activity android:name=\"com.example.lib2.LibActivity\"/>\n"
+ + "\n"
+ + " <!-- This key is defined in the main application. -->\n"
+ + " <meta-data android:name=\"name.for.yet.another.api.key\" android:value=\"your_yet_another_api_key\"/>\n"
+ + "\n"
+ + " <!-- Merged elements will be appended here at the end. -->\n"
+ + " <!-- From: file:///path/to/library1 -->\n"
+ + " <activity android:name=\"com.example.app1.Library1\"/>\n"
+ + "\n"
+ + " <!-- The library maps API key gets merged in the main application. -->\n"
+ + " <meta-data android:name=\"name.for.maps.api.key\" android:value=\"your_maps_api_key\"/>\n"
+ + "\n"
+ + " <!-- The library backup key gets merged in the main application. -->\n"
+ + " <meta-data android:name=\"name.for.backup.api.key\" android:value=\"your_backup_api_key\"/>\n"
+ + "\n"
+ + " <!-- From: file:///path/to/library2 -->\n"
+ + " <!-- This is a dup of the 2nd activity in lib2 -->\n"
+ + " <activity android:icon=\"@drawable/lib_activity_icon\" android:label=\"@string/lib_activity_name\" android:name=\"com.example.LibActivity2\" android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + " <!-- The first comment just before the element\n"
+ + " is carried over as-is.\n"
+ + " -->\n"
+ + " <!-- Formatting is preserved. -->\n"
+ + " <!-- All consecutive comments are taken together. -->\n"
+ + "\n"
+ + " <activity-alias android:icon=\"@drawable/alias_icon\" android:label=\"@string/alias_name\" android:name=\"com.example.alias.MyActivity\" android:targetActivity=\"com.example.MainActivity\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity-alias>\n"
+ + " <!-- From: file:///path/to/library3 -->\n"
+ + " <activity android:icon=\"@drawable/lib_activity_icon3\" android:label=\"@string/lib_activity_name3\" android:name=\"com.example.LibActivity3\" android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <!-- From: file:///path/to/main/doc -->\n"
+ + " \n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n";
+
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ // Adjust mock paths & EOLs for windows
+ actual = actual.replace("\r\n", "\n");
+ expected = expected.replace("file:///path/to/", "file:///C:/path/to/");
+ }
+
+ try {
+ assertEquals(expected, actual);
+
+ } catch (Exception originalFailure) {
+ // DOM implementations vary slightly whether they'll insert a newline for comment
+ // inserted outside document
+ // JDK 7 doesn't, JDK 6 does
+ int index = expected.indexOf('\n');
+ assertTrue(index != -1);
+ expected = expected.substring(0, index) + expected.substring(index + 1);
+ try {
+ assertEquals(expected, actual);
+ } catch (Throwable ignore) {
+ // If the second test fails too, throw the *original* exception,
+ // before we tried to tweak the EOL.
+ throw originalFailure;
+ }
+ }
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
new file mode 100644
index 0000000..4048510
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.manifmerger;
+
+import static com.android.manifmerger.ManifestMergerTestUtil.loadTestData;
+import static com.android.manifmerger.ManifestMergerTestUtil.transformParameters;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.mock.MockLog;
+import com.google.common.base.Preconditions;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.w3c.dom.Document;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Some utilities to reduce repetitions in the {@link ManifestMergerTest} and
+ * {@link ManifestMerger2Test}. <p/> See {@link
+ * ManifestMergerTestUtil#loadTestData(String, String, String)} for an explanation of the data
+ * file format.
+ */
+
+ at RunWith(Parameterized.class)
+public class ManifestMergerTest {
+
+ private static final String TEST_DATA_DIRECTORY = "data";
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private static final String[] DATA_FILES = new String[]{
+ "00_noop",
+ "01_ignore_app_attr",
+ "02_ignore_instrumentation",
+ "03_inject_attributes",
+ "04_inject_attributes",
+ "05_inject_package",
+ "10_activity_merge",
+ "11_activity_dup",
+ "12_alias_dup",
+ "13_service_dup",
+ "14_receiver_dup",
+ "15_provider_dup",
+ "16_fqcn_merge",
+ "17_fqcn_conflict",
+ "20_uses_lib_merge",
+ "21_uses_lib_errors",
+ "25_permission_merge",
+ "26_permission_dup",
+ "28_uses_perm_merge",
+ "30_uses_sdk_ok",
+ "32_uses_sdk_minsdk_ok",
+ "33_uses_sdk_minsdk_conflict",
+ "36_uses_sdk_targetsdk_warning",
+ "40_uses_feat_merge",
+ "41_uses_feat_errors",
+ "45_uses_feat_gles_once",
+ "47_uses_feat_gles_conflict",
+ "50_uses_conf_warning",
+ "52_support_screens_warning",
+ "54_compat_screens_warning",
+ "56_support_gltext_warning",
+ "60_merge_order",
+ "65_override_app",
+ "66_remove_app",
+ "67_override_activities",
+ "68_override_uses",
+ "69_remove_uses",
+ "70_expand_fqcns",
+ "71_extract_package_prefix",
+ "75_app_metadata_merge",
+ "76_app_metadata_ignore",
+ "77_app_metadata_conflict",
+ };
+
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> getParameters() {
+ return transformParameters(DATA_FILES);
+ }
+
+ public ManifestMergerTest(String fileName) {
+ this.fileName = fileName;
+ }
+
+ private final String fileName;
+
+
+ @Test
+ public void processTestFiles() throws Exception {
+ ManifestMergerTestUtil.TestFiles testFiles =
+ loadTestData(TEST_DATA_DIRECTORY, fileName, getClass().getSimpleName());
+
+ MockLog log = new MockLog();
+ IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
+ ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
+ @Override
+ public int queryCodenameApiLevel(@NonNull String codename) {
+ if ("ApiCodename1".equals(codename)) {
+ return 1;
+ } else if ("ApiCodename10".equals(codename)) {
+ return 10;
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ });
+
+ for (Map.Entry<String, Boolean> feature : testFiles.getFeatures().entrySet()) {
+ Method m = merger.getClass().getMethod(
+ feature.getKey(),
+ boolean.class);
+ m.invoke(merger, feature.getValue());
+ }
+
+ boolean processOK = merger.process(testFiles.getActualResult(),
+ testFiles.getMain(),
+ testFiles.getLibs(),
+ testFiles.getInjectAttributes(),
+ testFiles.getPackageOverride());
+
+ // Convert relative path names to absolute.
+ String expectedErrors = testFiles.getExpectedErrors().trim();
+ expectedErrors = expectedErrors.replaceAll(
+ Pattern.quote(testFiles.getMain().getName()),
+ Matcher.quoteReplacement(testFiles.getMain().getAbsolutePath()));
+ for (File file : testFiles.getLibs()) {
+ expectedErrors = expectedErrors.replaceAll(
+ Pattern.quote(file.getName()),
+ Matcher.quoteReplacement(file.getAbsolutePath()));
+ }
+
+ StringBuilder actualErrors = new StringBuilder();
+ for (String s : log.getMessages()) {
+ actualErrors.append(s);
+ if (!s.endsWith("\n")) {
+ actualErrors.append('\n');
+ }
+ }
+ assertEquals("Error generated during merging",
+ expectedErrors, actualErrors.toString().trim());
+
+ if (testFiles.getShouldFail()) {
+ assertFalse("Merge process() returned true, expected false", processOK);
+ } else {
+ assertTrue("Merge process() returned false, expected true", processOK);
+ }
+
+ // Test result XML. There should always be one created
+ // since the process action does not stop on errors.
+ log.clear();
+ Document document = MergerXmlUtils.parseDocument(
+ Preconditions.checkNotNull(testFiles.getActualResult()),
+ mergerLog,
+ merger);
+ assertNotNull(document);
+ String actual = MergerXmlUtils.printXmlString(document, mergerLog);
+ assertEquals("Error parsing actual result XML", "", log.toString());
+ log.clear();
+ document = MergerXmlUtils.parseDocument(
+ testFiles.getExpectedResult(),
+ mergerLog,
+ new IMergerLog.FileAndLine("<expected-result>", 0));
+ assertNotNull("Failed to parse result document: " + testFiles.getExpectedResult(),
+ document);
+ String expected = MergerXmlUtils.printXmlString(document, mergerLog);
+ assertEquals("Error parsing expected result XML", "", log.toString());
+ assertEquals("Error comparing expected to actual result", expected, actual);
+
+ testFiles.cleanup();
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTestUtil.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTestUtil.java
new file mode 100644
index 0000000..f35dfe4
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTestUtil.java
@@ -0,0 +1,433 @@
+/*
+ * 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.manifmerger;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.FileUtils;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.junit.runner.RunWith;
+import org.w3c.dom.Document;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ManifestMergerTestUtil {
+
+ /**
+ * Delimiter that indicates the test must fail. An XML output and errors are still generated and
+ * checked.
+ */
+ private static final String DELIM_FAILS = "fails";
+
+ /**
+ * Delimiter that starts a library XML content. The delimiter name must be in the form {@code
+ * @libSomeName} and it will be used as the base for the test file name. Using separate lib
+ * names is encouraged since it makes the error output easier to read.
+ */
+ private static final String DELIM_LIB = "lib";
+
+ /**
+ * Delimiter that starts the main manifest XML content.
+ */
+ private static final String DELIM_MAIN = "main";
+
+ /**
+ * Delimiter that starts an overlay XML content. The delimiter must follow the same rules as
+ * {@link #DELIM_LIB}
+ */
+ private static final String DELIM_OVERLAY = "overlay";
+
+ /**
+ * Delimiter that starts the resulting XML content, whatever is generated by the merge.
+ */
+ private static final String DELIM_RESULT = "result";
+
+ /**
+ * Delimiter that starts the SdkLog output. The logger prints each entry on its lines, prefixed
+ * with E for errors, W for warnings and P for regular printfs.
+ */
+ private static final String DELIM_ERRORS = "errors";
+
+ /**
+ * Delimiter for starts a section that declares how to inject an attribute. The section is
+ * composed of one or more lines with the syntax: "/node/node|attr-URI attrName=attrValue". This
+ * is essentially a pseudo XPath-like expression that is described in {@link
+ * ManifestMerger#process(Document, File[], Map, String)}.
+ */
+ private static final String DELIM_INJECT_ATTR = "inject";
+
+ /**
+ * Delimiter for a section that declares how to toggle a ManifMerger option. The section is
+ * composed of one or more lines with the syntax: "functionName=false|true".
+ */
+ private static final String DELIM_FEATURES = "features";
+
+ /**
+ * Delimiter for a section that declares how to override the package. The section is composed of
+ * one line containing the new package name.
+ */
+ private static final String DELIM_PACKAGE = "package";
+
+ /**
+ * Loads test data for a given test case.
+ * The input (main + libs) are stored in temp files.
+ * A new destination temp file is created to store the actual result output.
+ * The expected result is actually kept in a string.
+ * <p/>
+ * Data File Syntax:
+ * <ul>
+ * <li> Lines starting with # are ignored (anywhere, as long as # is the first char).
+ * <li> Lines before the first {@code @delimiter} are ignored.
+ * <li> Empty lines just after the {@code @delimiter}
+ * and before the first < XML line are ignored.
+ * <li> Valid delimiters are {@code @main} for the XML of the main app manifest.
+ * <li> Following delimiters are {@code @libXYZ}, read in the order of definition.
+ * The name can be anything as long as it starts with "{@code @lib}".
+ * </ul>
+ *
+ * @param testDataDirectory The resource directory name the data file is located in.
+ * @param filename The test data filename. If no extension is provided, this will
+ * try with .xml or .txt. Must not be null.
+ * @param className The simple name of the test class,
+ * the manifest files include it in their output.
+ * @return A new {@link ManifestMergerTestUtil.TestFiles} instance. Must not be null.
+ * @throws Exception when things fail to load properly.
+ */
+ @NonNull
+ static TestFiles loadTestData(
+ @NonNull String testDataDirectory,
+ @NonNull String filename,
+ @NonNull String className) throws Exception {
+
+ String resName = testDataDirectory + File.separator + filename;
+ InputStream is = null;
+ BufferedReader reader = null;
+ BufferedWriter writer = null;
+
+ try {
+ is = ManifestMergerTestUtil.class.getResourceAsStream(resName);
+ if (is == null && !filename.endsWith(".xml")) {
+ String resName2 = resName + ".xml";
+ is = ManifestMergerTestUtil.class.getResourceAsStream(resName2);
+ if (is != null) {
+ filename = resName2;
+ }
+ }
+ if (is == null && !filename.endsWith(".txt")) {
+ String resName3 = resName + ".txt";
+ is = ManifestMergerTestUtil.class.getResourceAsStream(resName3);
+ if (is != null) {
+ filename = resName3;
+ }
+ }
+ assertNotNull("Test data file not found for " + filename, is);
+
+ reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+
+ final File tempDir = Files.createTempDir();
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ try {
+ FileUtils.deleteFolder(tempDir);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+ });
+ tempDir.deleteOnExit();
+
+ String line = null;
+ String delimiter = null;
+ boolean skipEmpty = true;
+
+ boolean shouldFail = false;
+ Map<String, Boolean> features = new HashMap<String, Boolean>();
+ String packageOverride = null;
+ Map<String, String> injectAttributes = new HashMap<String, String>();
+ StringBuilder expectedResult = new StringBuilder();
+ StringBuilder expectedErrors = new StringBuilder();
+ File mainFile = null;
+ File actualResultFile = null;
+ List<File> libFiles = new ArrayList<File>();
+ List<File> overlayFiles = new ArrayList<File>();
+ int tempIndex = 0;
+
+ while ((line = reader.readLine()) != null) {
+ if (skipEmpty && line.trim().isEmpty()) {
+ continue;
+ }
+ if (!line.isEmpty() && line.charAt(0) == '#') {
+ continue;
+ }
+ if (!line.isEmpty() && line.charAt(0) == '@') {
+ delimiter = line.substring(1);
+ assertTrue(
+ "Unknown delimiter @" + delimiter + " in " + filename,
+ delimiter.startsWith(DELIM_OVERLAY) ||
+ delimiter.startsWith(DELIM_LIB) ||
+ delimiter.equals(DELIM_MAIN) ||
+ delimiter.equals(DELIM_RESULT) ||
+ delimiter.equals(DELIM_ERRORS) ||
+ delimiter.equals(DELIM_FAILS) ||
+ delimiter.equals(DELIM_FEATURES) ||
+ delimiter.equals(DELIM_INJECT_ATTR) ||
+ delimiter.equals(DELIM_PACKAGE));
+
+ skipEmpty = true;
+
+ if (writer != null) {
+ writer.close();
+ writer = null;
+ }
+
+ if (delimiter.equals(DELIM_FAILS)) {
+ shouldFail = true;
+
+ } else if (!delimiter.equals(DELIM_ERRORS) &&
+ !delimiter.equals(DELIM_FEATURES) &&
+ !delimiter.equals(DELIM_INJECT_ATTR) &&
+ !delimiter.equals(DELIM_PACKAGE)) {
+ File tempFile = new File(tempDir, String.format("%1$s%2$d_%3$s.xml",
+ className,
+ tempIndex++,
+ delimiter.replaceAll("[^a-zA-Z0-9_-]", "")
+ ));
+ tempFile.deleteOnExit();
+
+ if (delimiter.startsWith(DELIM_OVERLAY)) {
+ overlayFiles.add(tempFile);
+ } else if (delimiter.startsWith(DELIM_LIB)) {
+ libFiles.add(tempFile);
+
+ } else if (delimiter.equals(DELIM_MAIN)) {
+ mainFile = tempFile;
+
+ } else if (delimiter.equals(DELIM_RESULT)) {
+ actualResultFile = tempFile;
+
+ } else {
+ fail("Unexpected data file delimiter @" + delimiter +
+ " in " + filename);
+ }
+
+ if (!delimiter.equals(DELIM_RESULT)) {
+ writer = new BufferedWriter(new FileWriter(tempFile));
+ }
+ }
+
+ continue;
+ }
+ if (delimiter != null &&
+ skipEmpty &&
+ !line.isEmpty() &&
+ line.charAt(0) != '#' &&
+ line.charAt(0) != '@') {
+ skipEmpty = false;
+ }
+ if (writer != null) {
+ writer.write(line);
+ writer.write('\n');
+ } else if (DELIM_RESULT.equals(delimiter)) {
+ expectedResult.append(line).append('\n');
+ } else if (DELIM_ERRORS.equals(delimiter)) {
+ expectedErrors.append(line).append('\n');
+ } else if (DELIM_INJECT_ATTR.equals(delimiter)) {
+ String[] in = line.split("=");
+ if (in != null && in.length == 2) {
+ injectAttributes.put(in[0], "null".equals(in[1]) ? null : in[1]);
+ }
+ } else if (DELIM_FEATURES.equals(delimiter)) {
+ String[] in = line.split("=");
+ if (in != null && in.length == 2) {
+ features.put(in[0], Boolean.parseBoolean(in[1]));
+ }
+ } else if (DELIM_PACKAGE.equals(delimiter)) {
+ if (packageOverride == null) {
+ packageOverride = line;
+ }
+ }
+ }
+
+ assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile);
+
+ assert mainFile != null;
+
+ Collections.sort(libFiles);
+
+ return new ManifestMergerTestUtil.TestFiles(
+ shouldFail,
+ overlayFiles.toArray(new File[overlayFiles.size()]),
+ mainFile,
+ libFiles.toArray(new File[libFiles.size()]),
+ features,
+ injectAttributes,
+ packageOverride,
+ actualResultFile,
+ expectedResult.toString(),
+ expectedErrors.toString());
+
+ } finally {
+ Closeables.closeQuietly(reader);
+ Closeables.closeQuietly(is);
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ }
+
+
+ static class TestFiles {
+ private final File[] mOverlayFiles;
+ private final File mMain;
+ private final File[] mLibs;
+ private final Map<String, String> mInjectAttributes;
+ private final String mPackageOverride;
+ private final File mActualResult;
+ private final String mExpectedResult;
+ private final String mExpectedErrors;
+ private final boolean mShouldFail;
+ private final Map<String, Boolean> mFeatures;
+
+ /** Files used by a given test case. */
+ public TestFiles(
+ boolean shouldFail,
+ @NonNull File[] overlayFiles,
+ @NonNull File main,
+ @NonNull File[] libs,
+ @NonNull Map<String, Boolean> features,
+ @NonNull Map<String, String> injectAttributes,
+ @Nullable String packageOverride,
+ @Nullable File actualResult,
+ @NonNull String expectedResult,
+ @NonNull String expectedErrors) {
+ mShouldFail = shouldFail;
+ mMain = main;
+ mLibs = libs;
+ mFeatures = features;
+ mPackageOverride = packageOverride;
+ mInjectAttributes = injectAttributes;
+ mActualResult = actualResult;
+ mExpectedResult = expectedResult;
+ mExpectedErrors = expectedErrors;
+ mOverlayFiles = overlayFiles;
+ }
+
+ public boolean getShouldFail() {
+ return mShouldFail;
+ }
+
+ @NonNull
+ public File[] getOverlayFiles() {
+ return mOverlayFiles;
+ }
+
+ @NonNull
+ public File getMain() {
+ return mMain;
+ }
+
+ @NonNull
+ public File[] getLibs() {
+ return mLibs;
+ }
+
+ @NonNull
+ public Map<String, Boolean> getFeatures() {
+ return mFeatures;
+ }
+
+ @NonNull
+ public Map<String, String> getInjectAttributes() {
+ return mInjectAttributes;
+ }
+
+ @Nullable
+ public String getPackageOverride() {
+ return mPackageOverride;
+ }
+
+ @Nullable
+ public File getActualResult() {
+ return mActualResult;
+ }
+
+ @NonNull
+ public String getExpectedResult() {
+ return mExpectedResult;
+ }
+
+ public String getExpectedErrors() {
+ return mExpectedErrors;
+ }
+
+ // Try to delete any temp file potentially created.
+ public void cleanup() {
+ try {
+ if (mMain != null && mMain.isFile()) {
+ FileUtils.delete(mMain);
+ }
+
+ if (mActualResult != null && mActualResult.isFile()) {
+ FileUtils.delete(mActualResult);
+ }
+
+ for (File f : mLibs) {
+ if (f != null && f.isFile()) {
+ FileUtils.delete(f);
+ }
+ }
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ static Collection<Object[]> transformParameters(String[] params) {
+
+ return ImmutableList.copyOf(
+ Iterables.transform(Arrays.asList(params),
+ new Function<Object, Object[]>() {
+ @Override
+ public Object[] apply(Object input) {
+ return new Object[]{input};
+ }
+ }));
+ }
+}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestModelTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestModelTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestModelTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestModelTest.java
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergerTest.java
new file mode 100644
index 0000000..e1b9e19
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergerTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.manifmerger;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.mock.MockLog;
+import com.android.utils.ILogger;
+import com.android.utils.StdLogger;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.CharBuffer;
+
+/**
+ * Tests for {@link Merger} class
+ */
+public class MergerTest extends TestCase {
+
+ @Mock
+ ManifestMerger2.Invoker mInvoker;
+
+ @Mock
+ MergingReport mMergingReport;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private class MergerWithMock extends Merger {
+
+ @Override
+ protected ManifestMerger2.Invoker createInvoker(@NonNull File mainManifestFile, @NonNull ILogger logger) {
+ try {
+ when(mMergingReport.getResult()).thenReturn(MergingReport.Result.ERROR);
+ when(mMergingReport.getLoggingRecords()).thenReturn(
+ ImmutableList.<MergingReport.Record>of());
+ when(mInvoker.merge()).thenReturn(mMergingReport);
+ } catch (ManifestMerger2.MergeFailureException e) {
+ fail(e.getMessage());
+ }
+ return mInvoker;
+ }
+
+ @Override
+ protected File checkPath(@NonNull String path) throws FileNotFoundException {
+ return new File(path); // always exists...
+ }
+ }
+
+ public void testMainParameter() throws FileNotFoundException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml" };
+ new MergerWithMock() {
+ @Override
+ protected ManifestMerger2.Invoker createInvoker(@NonNull File mainManifestFile, @NonNull ILogger logger) {
+ assertEquals(args[1], mainManifestFile.getPath());
+ return super.createInvoker(mainManifestFile, logger);
+ }
+ }.process(args);
+ }
+
+ public void testDefaultLoggerParameter() throws FileNotFoundException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml" };
+ new MergerWithMock() {
+ @Override
+ protected ILogger createLogger(@NonNull StdLogger.Level level) {
+ assertEquals(StdLogger.Level.INFO, level);
+ return super.createLogger(level);
+ }
+ }.process(args);
+ }
+
+ public void testLoggerParameter() throws FileNotFoundException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--log", "VERBOSE" };
+ new MergerWithMock() {
+ @Override
+ protected ILogger createLogger(@NonNull StdLogger.Level level) {
+ assertEquals(StdLogger.Level.VERBOSE, level);
+ return super.createLogger(level);
+ }
+ }.process(args);
+ }
+
+ public void testLibParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--libs", "src/lib/AndroidManifest.xml" };
+ new MergerWithMock().process(args);
+ verify(mInvoker).addLibraryManifest(new File("src/lib/AndroidManifest.xml"));
+ verify(mInvoker).merge();
+ verifyNoMoreInteractions(mInvoker);
+ }
+
+ public void testLibsParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--libs", "src/lib1/AndroidManifest.xml" + File.pathSeparator
+ + "src/lib2/AndroidManifest.xml" + File.pathSeparator
+ + "src/lib3/AndroidManifest.xml" };
+ new MergerWithMock().process(args);
+ verify(mInvoker).addLibraryManifest(new File("src/lib1/AndroidManifest.xml"));
+ verify(mInvoker).addLibraryManifest(new File("src/lib2/AndroidManifest.xml"));
+ verify(mInvoker).addLibraryManifest(new File("src/lib3/AndroidManifest.xml"));
+ verify(mInvoker).merge();
+ verifyNoMoreInteractions(mInvoker);
+ }
+
+ public void testOverlayParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--overlays", "src/flavor1/AndroidManifest.xml" };
+ new MergerWithMock().process(args);
+ verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor1/AndroidManifest.xml"));
+ verify(mInvoker).merge();
+ verifyNoMoreInteractions(mInvoker);
+ }
+
+ public void testOverlaysParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--overlays", "src/flavor1/AndroidManifest.xml" + File.pathSeparator
+ + "src/flavor2/AndroidManifest.xml" + File.pathSeparator
+ + "src/flavor3/AndroidManifest.xml" };
+ new MergerWithMock().process(args);
+ verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor1/AndroidManifest.xml"));
+ verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor2/AndroidManifest.xml"));
+ verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor3/AndroidManifest.xml"));
+ verify(mInvoker).merge();
+ verifyNoMoreInteractions(mInvoker);
+ }
+
+ public void testPropertyParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--property", "min_sdk_version=19" };
+ new MergerWithMock().process(args);
+ verify(mInvoker).setOverride(ManifestMerger2.SystemProperty.MIN_SDK_VERSION, "19");
+ verify(mInvoker).merge();
+ verifyNoMoreInteractions(mInvoker);
+ }
+
+ public void testInvalidPropertyParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--property", "Foo=19" };
+ final MockLog iLogger = new MockLog();
+ Merger merger = new MergerWithMock() {
+ @Override
+ protected ILogger createLogger(@NonNull StdLogger.Level level) {
+ return iLogger;
+ }
+ };
+ // check that return value marked the failure.
+ assertEquals(1, merger.process(args));
+ assertEquals(2, iLogger.getMessages().size());
+ assertTrue(iLogger.getMessages().get(1).startsWith(
+ "E Invalid property name Foo, allowed properties are :"));
+ }
+
+ public void testInvalidFormatPropertyParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--property", "Foo:19" };
+ final MockLog iLogger = new MockLog();
+ Merger merger = new MergerWithMock() {
+ @Override
+ protected ILogger createLogger(@NonNull StdLogger.Level level) {
+ return iLogger;
+ }
+ };
+ // check that return value marked the failure.
+ assertEquals(1, merger.process(args));
+ assertEquals(1, iLogger.getMessages().size());
+ assertEquals("E Invalid property setting, should be NAME=VALUE format",
+ iLogger.getMessages().get(0));
+ }
+
+ public void testPlaceholderParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--placeholder", "foo=bar" };
+ new MergerWithMock().process(args);
+ verify(mInvoker).setPlaceHolderValue("foo", "bar");
+ verify(mInvoker).merge();
+ verifyNoMoreInteractions(mInvoker);
+ }
+
+ public void testInvalidFormatPlaceholderParameter()
+ throws FileNotFoundException, ManifestMerger2.MergeFailureException {
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--placeholder", "Foo:19" };
+ final MockLog iLogger = new MockLog();
+ Merger merger = new MergerWithMock() {
+ @Override
+ protected ILogger createLogger(@NonNull StdLogger.Level level) {
+ return iLogger;
+ }
+ };
+ // check that return value marked the failure.
+ assertEquals(1, merger.process(args));
+ assertEquals(1, iLogger.getMessages().size());
+ assertEquals("E Invalid placeholder setting, should be NAME=VALUE format",
+ iLogger.getMessages().get(0));
+ }
+
+ public void testCombinedParameters()
+ throws IOException, ManifestMerger2.MergeFailureException {
+
+ File outFile = File.createTempFile("test", "merger");
+
+ final String[] args = { "--main", "src/main/AndroidManifest.xml",
+ "--libs", "src/lib1/AndroidManifest.xml" + File.pathSeparator
+ + "src/lib2/AndroidManifest.xml" + File.pathSeparator
+ + "src/lib3/AndroidManifest.xml",
+ "--overlays", "src/flavor1/AndroidManifest.xml" + File.pathSeparator
+ + "src/flavor2/AndroidManifest.xml" + File.pathSeparator
+ + "src/flavor3/AndroidManifest.xml",
+ "--placeholder", "Foo=bar",
+ "--property", "max_sdk_version=21",
+ "--out", outFile.getAbsolutePath()};
+ Merger merger = new MergerWithMock() {
+ @Override
+ protected ManifestMerger2.Invoker createInvoker(@NonNull File mainManifestFile, @NonNull ILogger logger) {
+ try {
+ XmlDocument xmlDocument = Mockito.mock(XmlDocument.class);
+ when(mMergingReport.getResult()).thenReturn(MergingReport.Result.SUCCESS);
+ when(mMergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED))
+ .thenReturn("Pretty combined");
+ when(mInvoker.merge()).thenReturn(mMergingReport);
+ } catch (ManifestMerger2.MergeFailureException e) {
+ fail(e.getMessage());
+ }
+ return mInvoker;
+ }
+ };
+ merger.process(args);
+ verify(mInvoker).addLibraryManifest(new File("src/lib1/AndroidManifest.xml"));
+ verify(mInvoker).addLibraryManifest(new File("src/lib2/AndroidManifest.xml"));
+ verify(mInvoker).addLibraryManifest(new File("src/lib3/AndroidManifest.xml"));
+ verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor1/AndroidManifest.xml"));
+ verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor2/AndroidManifest.xml"));
+ verify(mInvoker).addFlavorAndBuildTypeManifest(new File("src/flavor3/AndroidManifest.xml"));
+ verify(mInvoker).setOverride(ManifestMerger2.SystemProperty.MAX_SDK_VERSION, "21");
+ verify(mInvoker).setPlaceHolderValue("Foo", "bar");
+ verify(mInvoker).merge();
+ verifyNoMoreInteractions(mInvoker);
+
+ // check the resulting file content.
+ FileReader fileReader = null;
+ try {
+ fileReader = new FileReader(outFile);
+ CharBuffer buffer = CharBuffer.allocate(256);
+ fileReader.read(buffer);
+ int endOfRead = buffer.position();
+ assertEquals("Pretty combined", buffer.rewind().toString().substring(0, endOfRead));
+ } finally {
+ if (fileReader != null) {
+ fileReader.close();
+ }
+ }
+ assertTrue(outFile.delete());
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java
new file mode 100644
index 0000000..89d3327
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/MergingReportTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.MergingReport.Record.Severity;
+
+import com.android.ide.common.blame.SourceFile;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the {@link com.android.manifmerger.MergingReport} class
+ */
+public class MergingReportTest extends TestCase {
+
+ @Mock ILogger mLoggerMock;
+ SourceFile mSourceLocation = new SourceFile("location");
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testJustError() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR,"Something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+
+ public void testJustWarning() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "Something weird happened")
+ .build();
+
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ }
+
+ public void testJustInfo() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .build();
+
+ assertEquals(MergingReport.Result.SUCCESS, mergingReport.getResult());
+ }
+
+
+ public void testJustInfoAndWarning() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "Something weird happened")
+ .build();
+
+ assertEquals(MergingReport.Result.WARNING, mergingReport.getResult());
+ }
+
+ public void testJustInfoAndError() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+
+ public void testJustWarningAndError() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "something weird happened")
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+ public void testAllTypes() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,0, 0, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,0, 0, Severity.WARNING, "something weird happened")
+ .addMessage(mSourceLocation,0, 0, Severity.ERROR, "something bad happened")
+ .build();
+
+ assertEquals(MergingReport.Result.ERROR, mergingReport.getResult());
+ }
+
+ public void testLogging() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMessage(mSourceLocation,1, 1, Severity.INFO, "merging info")
+ .addMessage(mSourceLocation,1, 1, Severity.WARNING, "something weird happened")
+ .addMessage(mSourceLocation,1, 1, Severity.ERROR, "something bad happened")
+ .build();
+
+ mergingReport.log(mLoggerMock);
+ Mockito.verify(mLoggerMock).verbose("location:1:1 Info:\n\tmerging info");
+ Mockito.verify(mLoggerMock).warning("location:1:1 Warning:\n\tsomething weird happened");
+ Mockito.verify(mLoggerMock).error(null /* throwable */,
+ "location:1:1 Error:\n\tsomething bad happened");
+ Mockito.verify(mLoggerMock).verbose(Actions.HEADER);
+ Mockito.verify(mLoggerMock).warning("\nSee http://g.co/androidstudio/manifest-merger "
+ + "for more information about the manifest merger.\n");
+ Mockito.verifyNoMoreInteractions(mLoggerMock);
+ }
+
+ public void testItermediaryMerges() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .addMergingStage("<first/>")
+ .addMergingStage("<second/>")
+ .addMergingStage("<third/>")
+ .build();
+
+ ImmutableList<String> intermediaryStages = mergingReport.getIntermediaryStages();
+ assertEquals(3, intermediaryStages.size());
+ assertEquals("<first/>", intermediaryStages.get(0));
+ assertEquals("<second/>", intermediaryStages.get(1));
+ assertEquals("<third/>", intermediaryStages.get(2));
+ }
+
+ public void testGetMergedDocument() {
+ MergingReport mergingReport = new MergingReport.Builder(mLoggerMock)
+ .setMergedDocument(MergingReport.MergedManifestKind.MERGED, "Some String")
+ .build();
+
+ assertNotNull(mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED));
+ assertEquals("Some String",
+ mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED));
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java
new file mode 100644
index 0000000..fa0b39a
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PlaceholderHandlerTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import static com.android.manifmerger.PlaceholderHandler.KeyBasedValueResolver;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.sdklib.mock.MockLog;
+import com.google.common.base.Optional;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests for the {@link com.android.manifmerger.PlaceholderHandler}
+ */
+public class PlaceholderHandlerTest extends TestCase {
+
+ @Mock
+ ActionRecorder mActionRecorder;
+
+ @Mock
+ MergingReport.Builder mBuilder;
+
+ MockLog mMockLog = new MockLog();
+
+ KeyBasedValueResolver<String> nullResolver = new KeyBasedValueResolver<String>() {
+ @Override
+ public String getValue(@NonNull String key) {
+ // not provided a placeholder value should generate an error.
+ return null;
+ }
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ when(mBuilder.getLogger()).thenReturn(mMockLog);
+ when(mBuilder.getActionRecorder()).thenReturn(mActionRecorder);
+ }
+
+ public void testPlaceholders() throws ParserConfigurationException, SAXException, IOException {
+
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:attr1=\"${landscapePH}\"\n"
+ + " android:attr2=\"prefix.${landscapePH}\"\n"
+ + " android:attr3=\"${landscapePH}.suffix\"\n"
+ + " android:attr4=\"prefix${landscapePH}suffix\">\n"
+ + " </activity>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
+
+ PlaceholderHandler.visit(
+ ManifestMerger2.MergeType.APPLICATION,
+ refDocument, new KeyBasedValueResolver<String>() {
+ @Override
+ public String getValue(@NonNull String key) {
+ return "newValue";
+ }
+ }, mBuilder);
+
+ Optional<XmlElement> activityOne = refDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, ".activityOne");
+ assertTrue(activityOne.isPresent());
+ assertEquals(5, activityOne.get().getAttributes().size());
+ // check substitution.
+ assertEquals("newValue",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr1")).get().getValue());
+ assertEquals("prefix.newValue",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr2")).get().getValue());
+ assertEquals("newValue.suffix",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr3")).get().getValue());
+ assertEquals("prefixnewValuesuffix",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr4")).get().getValue());
+
+ for (XmlAttribute xmlAttribute : activityOne.get().getAttributes()) {
+ // any attribute other than android:name should have been injected.
+ if (!xmlAttribute.getName().toString().contains("name")) {
+ verify(mActionRecorder).recordAttributeAction(
+ xmlAttribute,
+ SourcePosition.UNKNOWN,
+ Actions.ActionType.INJECTED,
+ null);
+ }
+ }
+ }
+
+ public void testSeveralPlaceholders() throws ParserConfigurationException, SAXException, IOException {
+
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:attr1=\"prefix${first}${second}\"\n"
+ + " android:attr2=\"${first}${second}suffix\"\n"
+ + " android:attr3=\"prefix${first}.${second}suffix\"\n"
+ + " android:attr4=\"${first}.${second}\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
+
+ PlaceholderHandler.visit(
+ ManifestMerger2.MergeType.APPLICATION,
+ refDocument, new KeyBasedValueResolver<String>() {
+ @Override
+ public String getValue(@NonNull String key) {
+ if (key.equals("first")) {
+ return "firstValue";
+ } else {
+ return "secondValue";
+ }
+ }
+ }, mBuilder);
+
+ Optional<XmlElement> activityOne = refDocument.getRootNode()
+ .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, ".activityOne");
+ assertTrue(activityOne.isPresent());
+ assertEquals(5, activityOne.get().getAttributes().size());
+ // check substitution.
+
+ assertEquals("prefixfirstValuesecondValue",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr1")).get().getValue());
+
+ assertEquals("firstValuesecondValuesuffix",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr2")).get().getValue());
+
+ assertEquals("prefixfirstValue.secondValuesuffix",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr3")).get().getValue());
+
+ assertEquals("firstValue.secondValue",
+ activityOne.get().getAttribute(
+ XmlNode.fromXmlName("android:attr4")).get().getValue());
+
+ for (XmlAttribute xmlAttribute : activityOne.get().getAttributes()) {
+ // any attribute other than android:name should have been injected.
+ if (!xmlAttribute.getName().toString().contains("name")) {
+ verify(mActionRecorder, times(2)).recordAttributeAction(
+ xmlAttribute,
+ SourcePosition.UNKNOWN,
+ Actions.ActionType.INJECTED,
+ null);
+
+ }
+ }
+ }
+
+ public void testPlaceHolder_notProvided()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:attr1=\"${landscapePH}\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
+
+ PlaceholderHandler
+ .visit(ManifestMerger2.MergeType.APPLICATION, refDocument, nullResolver, mBuilder);
+ // verify the error was recorded.
+ verify(mBuilder).addMessage(
+ any(SourceFilePosition.class),
+ eq(MergingReport.Record.Severity.ERROR), anyString());
+ }
+
+ public void testPlaceHolder_notProvided_inLibrary()
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = ""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <activity android:name=\"activityOne\"\n"
+ + " android:attr1=\"${landscapePH}\"/>\n"
+ + "</manifest>";
+
+ XmlDocument refDocument = TestUtils.xmlDocumentFromString(
+ TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);
+
+ PlaceholderHandler
+ .visit(ManifestMerger2.MergeType.LIBRARY, refDocument, nullResolver, mBuilder);
+ // verify the error was recorded.
+ verify(mBuilder).addMessage(
+ any(SourceFilePosition.class),
+ eq(MergingReport.Record.Severity.INFO), anyString());
+ }
+}
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/PostValidatorTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PostValidatorTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/PostValidatorTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/PostValidatorTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/PreValidatorTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/PreValidatorTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/PreValidatorTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/PreValidatorTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/TestUtils.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/TestUtils.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/TestUtils.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/TestUtils.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ToolsInstructionsCleanerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ToolsInstructionsCleanerTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/ToolsInstructionsCleanerTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/ToolsInstructionsCleanerTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlAttributeTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlAttributeTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlAttributeTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlAttributeTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlDocumentTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlDocumentTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlDocumentTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlDocumentTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlElementTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlElementTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlElementTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlElementTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlLoaderTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlLoaderTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlLoaderTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlLoaderTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlNodeTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlNodeTest.java
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlNodeTest.java
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/XmlNodeTest.java
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/00_noop.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/00_noop.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/00_noop.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/00_noop.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/01_ignore_app_attr.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/01_ignore_app_attr.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/01_ignore_app_attr.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/01_ignore_app_attr.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/02_ignore_instrumentation.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/02_ignore_instrumentation.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/02_ignore_instrumentation.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/02_ignore_instrumentation.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/03_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/03_inject_attributes.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/03_inject_attributes.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/03_inject_attributes.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/04_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/04_inject_attributes.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/04_inject_attributes.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/04_inject_attributes.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_placeholder.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_placeholder.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_placeholder.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_placeholder.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_with_overlays.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_with_overlays.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_with_overlays.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/05_inject_package_with_overlays.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/06_inject_attributes_with_specific_prefix.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/06_inject_attributes_with_specific_prefix.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/06_inject_attributes_with_specific_prefix.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/06_inject_attributes_with_specific_prefix.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/07_no_package_provided.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/07_no_package_provided.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/07_no_package_provided.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/07_no_package_provided.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08_no_library_package_provided.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08_no_library_package_provided.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08_no_library_package_provided.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08_no_library_package_provided.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08b_library_injection.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08b_library_injection.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08b_library_injection.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/08b_library_injection.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09_overlay_package_provided.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09_overlay_package_provided.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09_overlay_package_provided.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09_overlay_package_provided.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09b_overlay_package_different.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09b_overlay_package_different.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09b_overlay_package_different.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09b_overlay_package_different.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09c_overlay_package_not_provided.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09c_overlay_package_not_provided.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09c_overlay_package_not_provided.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/09c_overlay_package_not_provided.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/10_activity_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/10_activity_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/10_activity_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/10_activity_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11_activity_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11_activity_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11_activity_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11_activity_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11b_activity_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11b_activity_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11b_activity_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/11b_activity_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/12_alias_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/12_alias_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/12_alias_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/12_alias_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/13_service_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/13_service_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/13_service_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/13_service_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/14_receiver_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/14_receiver_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/14_receiver_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/14_receiver_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/15_provider_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/15_provider_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/15_provider_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/15_provider_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/16_fqcn_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/16_fqcn_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/16_fqcn_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/16_fqcn_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/17_fqcn_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/17_fqcn_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/17_fqcn_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/17_fqcn_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/18_fqcn_success.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/18_fqcn_success.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/18_fqcn_success.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/18_fqcn_success.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/20_uses_lib_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/20_uses_lib_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/20_uses_lib_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/20_uses_lib_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/21_uses_main_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/21_uses_main_errors.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/21_uses_main_errors.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/21_uses_main_errors.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/22_uses_lib_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/22_uses_lib_errors.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/22_uses_lib_errors.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/22_uses_lib_errors.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/25_permission_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/25_permission_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/25_permission_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/25_permission_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/26_permission_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/26_permission_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/26_permission_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/26_permission_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/28_uses_perm_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/28_uses_perm_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/28_uses_perm_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/28_uses_perm_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29_uses_perm_selector.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29_uses_perm_selector.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29_uses_perm_selector.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29_uses_perm_selector.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29b_uses_perm_invalidSelector.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29b_uses_perm_invalidSelector.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29b_uses_perm_invalidSelector.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/29b_uses_perm_invalidSelector.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/30_uses_sdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/30_uses_sdk_ok.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/30_uses_sdk_ok.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/30_uses_sdk_ok.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/32_uses_sdk_minsdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/32_uses_sdk_minsdk_ok.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/32_uses_sdk_minsdk_ok.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/32_uses_sdk_minsdk_ok.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33_uses_sdk_minsdk_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33_uses_sdk_minsdk_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33_uses_sdk_minsdk_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33_uses_sdk_minsdk_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33b_uses_sdk_minsdk_override.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33b_uses_sdk_minsdk_override.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33b_uses_sdk_minsdk_override.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33b_uses_sdk_minsdk_override.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33c_uses_sdk_minsdk_override_and_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33c_uses_sdk_minsdk_override_and_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33c_uses_sdk_minsdk_override_and_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33c_uses_sdk_minsdk_override_and_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33d_uses_sdk_minsdk_codename.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33d_uses_sdk_minsdk_codename.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33d_uses_sdk_minsdk_codename.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/33d_uses_sdk_minsdk_codename.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/34_inject_uses_sdk_no_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/34_inject_uses_sdk_no_dup.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/34_inject_uses_sdk_no_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/34_inject_uses_sdk_no_dup.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/36_uses_sdk_targetsdk_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/36_uses_sdk_targetsdk_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/36_uses_sdk_targetsdk_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/36_uses_sdk_targetsdk_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/40_uses_feat_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/40_uses_feat_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/40_uses_feat_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/40_uses_feat_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/41_uses_feat_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/41_uses_feat_errors.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/41_uses_feat_errors.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/41_uses_feat_errors.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/45_uses_feat_gles_once.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/45_uses_feat_gles_once.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/45_uses_feat_gles_once.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/45_uses_feat_gles_once.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/47_uses_feat_gles_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/47_uses_feat_gles_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/47_uses_feat_gles_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/47_uses_feat_gles_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/50_uses_conf_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/50_uses_conf_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/50_uses_conf_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/50_uses_conf_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/52_support_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/52_support_screens_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/52_support_screens_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/52_support_screens_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/54_compat_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/54_compat_screens_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/54_compat_screens_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/54_compat_screens_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/56_support_gltext_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/56_support_gltext_warning.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/56_support_gltext_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/56_support_gltext_warning.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/60_merge_order.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/60_merge_order.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/60_merge_order.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/60_merge_order.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/65_override_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/65_override_app.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/65_override_app.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/65_override_app.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/66_remove_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/66_remove_app.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/66_remove_app.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/66_remove_app.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/67_override_activities.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/67_override_activities.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/67_override_activities.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/67_override_activities.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/68_override_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/68_override_uses.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/68_override_uses.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/68_override_uses.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/69_remove_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/69_remove_uses.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/69_remove_uses.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/69_remove_uses.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/70_expand_fqcns.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/70_expand_fqcns.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/70_expand_fqcns.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/70_expand_fqcns.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/71_extract_package_prefix.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/71_extract_package_prefix.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/71_extract_package_prefix.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/71_extract_package_prefix.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/75_app_metadata_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/75_app_metadata_merge.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/75_app_metadata_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/75_app_metadata_merge.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/76_app_metadata_ignore.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/76_app_metadata_ignore.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/76_app_metadata_ignore.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/76_app_metadata_ignore.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/77_app_metadata_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/77_app_metadata_conflict.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/77_app_metadata_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/77_app_metadata_conflict.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/78_removeAll.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/78_removeAll.xml
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/78_removeAll.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/78_removeAll.xml
diff --git a/base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/79_custom_node.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/79_custom_node.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/79_custom_node.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data2/79_custom_node.xml
diff --git a/base/build-system/profile/build.gradle b/build-system/profile/build.gradle
similarity index 100%
rename from base/build-system/profile/build.gradle
rename to build-system/profile/build.gradle
diff --git a/base/build-system/profile/profile.iml b/build-system/profile/profile.iml
similarity index 100%
rename from base/build-system/profile/profile.iml
rename to build-system/profile/profile.iml
diff --git a/build-system/profile/src/main/java/com/android/builder/profile/AsyncRecorder.java b/build-system/profile/src/main/java/com/android/builder/profile/AsyncRecorder.java
new file mode 100644
index 0000000..5e3afb4
--- /dev/null
+++ b/build-system/profile/src/main/java/com/android/builder/profile/AsyncRecorder.java
@@ -0,0 +1,47 @@
+/*
+ * 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.builder.profile;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.logging.Logger;
+
+/**
+ * Sync recorder capable of recording asynchronous recording events.
+ */
+public class AsyncRecorder extends ThreadRecorder {
+
+ private static final Logger logger = Logger.getLogger(AsyncRecorder.class.getName());
+
+ private static final Recorder recorder = new AsyncRecorder();
+
+ public static Recorder get() {
+ return ProcessRecorderFactory.getFactory().isInitialized() ? recorder : dummyRecorder;
+ }
+
+ @Override
+ public void closeRecord(ExecutionRecord executionRecord) {
+ // there is no contract that allocationRecordId and closeRecord will be called in the
+ // right order to maintain the stack integrity. Therefore, I used an API which makes
+ // no assumption on where in the stack the allocated ID is so these Apis can be called
+ // in various orders as long as allocationRecordId is called before closeRecord.
+ if (!recordStacks.get().removeFirstOccurrence(executionRecord.id)) {
+ logger.severe("Internal Error : mixed records in profiling stack");
+ }
+ ProcessRecorder.get().writeRecord(executionRecord);
+ }
+}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/profile/ExecutionRecord.java b/build-system/profile/src/main/java/com/android/builder/profile/ExecutionRecord.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/profile/ExecutionRecord.java
rename to build-system/profile/src/main/java/com/android/builder/profile/ExecutionRecord.java
diff --git a/build-system/profile/src/main/java/com/android/builder/profile/ExecutionType.java b/build-system/profile/src/main/java/com/android/builder/profile/ExecutionType.java
new file mode 100644
index 0000000..de73ceb
--- /dev/null
+++ b/build-system/profile/src/main/java/com/android/builder/profile/ExecutionType.java
@@ -0,0 +1,124 @@
+/*
+ * 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.builder.profile;
+
+/**
+ * Defines a type of processing.
+ *
+ * Use range for similar categories events :
+ * 0-1000 initial setup
+ * 1000-2000 application related task creation
+ * 2000-3000 library related task creation
+ * 3000-4000 Tasks related events.
+ */
+public enum ExecutionType {
+
+ SOME_RANDOM_PROCESSING(1),
+ BASE_PLUGIN_PROJECT_CONFIGURE(2),
+ BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION(3),
+ BASE_PLUGIN_PROJECT_TASKS_CREATION(4),
+ BASE_PLUGIN_BUILD_FINISHED(5),
+ TASK_MANAGER_CREATE_TASKS(6),
+ BASE_PLUGIN_CREATE_ANDROID_TASKS(7),
+ VARIANT_MANAGER_CREATE_ANDROID_TASKS(8),
+ VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT(9),
+ VARIANT_MANAGER_CREATE_LINT_TASKS(10),
+ VARIANT_MANAGER_CREATE_TESTS_TASKS(11),
+ VARIANT_MANAGER_CREATE_VARIANTS(12),
+ RESOLVE_DEPENDENCIES(12),
+ INITIAL_METADATA(100),
+ FINAL_METADATA(101),
+ GENERAL_CONFIG(102),
+ VARIANT_CONFIG(103),
+
+ // ApplicationTaskManager per variant tasks.
+ APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK(1000),
+ APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK(1001),
+ APP_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK(1002),
+ APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK(1003),
+ APP_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK(1004),
+ APP_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK(1005),
+ APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK(1006),
+ APP_TASK_MANAGER_CREATE_AIDL_TASK(1007),
+ APP_TASK_MANAGER_CREATE_COMPILE_TASK(1008),
+ APP_TASK_MANAGER_CREATE_NDK_TASK(1009),
+ APP_TASK_MANAGER_CREATE_SPLIT_TASK(1010),
+ APP_TASK_MANAGER_CREATE_PACKAGING_TASK(1011),
+ @Deprecated APP_TASK_MANAGER_CREATE_PREPROCESS_RESOURCES_TASK(1012),
+ @Deprecated APP_TASK_MANAGER_CREATE_BACKPORT_RESOURCES_TASK(1013),
+ APP_TASK_MANAGER_CREATE_LINT_TASK(1014),
+ APP_TASK_MANAGER_CREATE_MERGE_JNILIBS_FOLDERS_TASK(1015),
+
+ // LibraryTaskManager per variant tasks.
+ LIB_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK(2000),
+ LIB_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK(2001),
+ LIB_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK(2002),
+ LIB_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK(2003),
+ LIB_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK(2004),
+ LIB_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK(2005),
+ LIB_TASK_MANAGER_CREATE_PROCESS_RES_TASK(2006),
+ LIB_TASK_MANAGER_CREATE_AIDL_TASK(2007),
+ LIB_TASK_MANAGER_CREATE_COMPILE_TASK(2008),
+ LIB_TASK_MANAGER_CREATE_NDK_TASK(2009),
+ LIB_TASK_MANAGER_CREATE_SPLIT_TASK(2010),
+ LIB_TASK_MANAGER_CREATE_PACKAGING_TASK(2011),
+ LIB_TASK_MANAGER_CREATE_MERGE_PROGUARD_FILE_TASK(2012),
+ LIB_TASK_MANAGER_CREATE_POST_COMPILATION_TASK(2013),
+ @Deprecated LIB_TASK_MANAGER_CREATE_PROGUARD_TASK(2014),
+ @Deprecated LIB_TASK_MANAGER_CREATE_PACKAGE_LOCAL_JAR(2015),
+ @Deprecated LIB_TASK_MANAGER_CREATE_BACKPORT_RESOURCES_TASK(2016),
+ LIB_TASK_MANAGER_CREATE_LINT_TASK(2017),
+
+ // TASK_EXECUTION
+ GENERIC_TASK_EXECUTION(3000),
+ TASK_AIDL_COMPILE(3001),
+ TASK_DELETE(3002),
+ TASK_CHECK_MANIFEST(3003),
+ TASK_PREPARE_DEPENDENCIES_TASK(3004),
+ TASK_RENDERSCRIPT_COMPILE(3005),
+ TASK_GENERATE_BUILD_CONFIG(3006),
+ TASK_MERGE_ASSETS(3007),
+ TASK_GENERATE_RES_VALUES(3008),
+ TASK_MERGE_RESOURCES(3009),
+ TASK_MERGE_MANIFESTS(3010),
+ TASK_PROCESS_ANDROID_RESOURCES(3011),
+ TASK_JAVA_COMPILE(3012),
+ TASK_NDK_COMPILE(3013),
+ TASK_PRE_DEX(3014),
+ TASK_DEX(3015),
+ TASK_PACKAGE_SPLIT_RES(3016),
+ TASK_PROCESS_RESOURCES(3017),
+ TASK_VALIDATE_SIGNING_TASK(3018),
+ TASK_PACKAGE_APPLICATION(3019),
+ TASK_SPLIT_ZIP_ALIGN(3020),
+ TASK_ZIP_ALIGN(3021),
+ TASK_COPY(3022),
+ TASK_LINT(3023),
+ TASK_TRANSFORM_PREPARATION(3024),
+ TASK_TRANSFORM(3025),
+ TASK_FILE_VERIFICATION(3026);
+
+ int getId() {
+ return id;
+ }
+
+ private final int id;
+
+ ExecutionType(int id) {
+ this.id = id;
+ }
+}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorder.java b/build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorder.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorder.java
rename to build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorder.java
diff --git a/base/build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorderFactory.java b/build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorderFactory.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorderFactory.java
rename to build-system/profile/src/main/java/com/android/builder/profile/ProcessRecorderFactory.java
diff --git a/base/build-system/profile/src/main/java/com/android/builder/profile/Recorder.java b/build-system/profile/src/main/java/com/android/builder/profile/Recorder.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/profile/Recorder.java
rename to build-system/profile/src/main/java/com/android/builder/profile/Recorder.java
diff --git a/build-system/profile/src/main/java/com/android/builder/profile/ThreadRecorder.java b/build-system/profile/src/main/java/com/android/builder/profile/ThreadRecorder.java
new file mode 100644
index 0000000..b38dabd
--- /dev/null
+++ b/build-system/profile/src/main/java/com/android/builder/profile/ThreadRecorder.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.profile;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Facility to record block execution time on a single thread. Threads should not be spawned during
+ * the block execution as its processing will not be recorded as of the parent's execution time.
+ *
+ * // TODO : provide facilities to create a new ThreadRecorder using a parent so the slave threads
+ * can be connected to the parent's task.
+ */
+public class ThreadRecorder implements Recorder {
+
+ private static final Logger logger = Logger.getLogger(ThreadRecorder.class.getName());
+
+ // Dummy implementation that records nothing but comply to the overall recording contracts.
+ protected static final Recorder dummyRecorder = new Recorder() {
+ @Nullable
+ @Override
+ public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
+ Property... properties) {
+ return record(executionType, block, Collections.<Property>emptyList());
+ }
+
+ @Nullable
+ @Override
+ public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
+ @NonNull List<Property> properties) {
+ try {
+ return block.call();
+ } catch (Exception e) {
+ block.handleException(e);
+ }
+ return null;
+ }
+
+ @Override
+ public long allocationRecordId() {
+ return 0;
+ }
+
+ @Override
+ public void closeRecord(ExecutionRecord record) {
+ }
+ };
+
+ private static final Recorder recorder = new ThreadRecorder();
+
+
+ public static Recorder get() {
+ return ProcessRecorderFactory.getFactory().isInitialized() ? recorder : dummyRecorder;
+ }
+
+ private static class PartialRecord {
+ final ExecutionType executionType;
+ final long recordId;
+ final long parentRecordId;
+ final long startTimeInMs;
+
+ final List<Recorder.Property> extraArgs;
+
+ PartialRecord(ExecutionType executionType,
+ long recordId,
+ long parentId,
+ long startTimeInMs,
+ List<Recorder.Property> extraArgs) {
+ this.executionType = executionType;
+ this.recordId = recordId;
+ this.parentRecordId = parentId;
+ this.startTimeInMs = startTimeInMs;
+ this.extraArgs = extraArgs;
+ }
+ }
+
+ /**
+ * Do not put anything else than JDK classes in the ThreadLocal as it prevents that class
+ * and therefore the plugin classloader to be gc'ed leading to OOM or PermGen issues.
+ */
+ protected final ThreadLocal<Deque<Long>> recordStacks =
+ new ThreadLocal<Deque<Long>>() {
+ @Override
+ protected Deque<Long> initialValue() {
+ return new ArrayDeque<Long>();
+ }
+ };
+
+
+ @Override
+ public long allocationRecordId() {
+ long recordId = ProcessRecorder.allocateRecordId();
+ recordStacks.get().push(recordId);
+ return recordId;
+ }
+
+ @Override
+ public void closeRecord(ExecutionRecord executionRecord) {
+ if (recordStacks.get().pop() != executionRecord.id) {
+ logger.severe("Internal Error : mixed records in profiling stack");
+ }
+ ProcessRecorder.get().writeRecord(executionRecord);
+ }
+
+ @Nullable
+ @Override
+ public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
+ Property... properties) {
+
+ List<Recorder.Property> propertyList = properties == null
+ ? ImmutableList.<Recorder.Property>of()
+ : ImmutableList.copyOf(properties);
+
+ return record(executionType, block, propertyList);
+ }
+
+ @Nullable
+ @Override
+ public <T> T record(@NonNull ExecutionType executionType, @NonNull Block<T> block,
+ @NonNull List<Property> properties) {
+
+ long thisRecordId = ProcessRecorder.allocateRecordId();
+
+ // am I a child ?
+ Long parentId = recordStacks.get().peek();
+
+ long startTimeInMs = System.currentTimeMillis();
+
+ final PartialRecord currentRecord = new PartialRecord(executionType,
+ thisRecordId, parentId == null ? 0 : parentId,
+ startTimeInMs, properties);
+
+ recordStacks.get().push(thisRecordId);
+ try {
+ return block.call();
+ } catch (Exception e) {
+ block.handleException(e);
+ } finally {
+ // pop this record from the stack.
+ if (recordStacks.get().pop() != currentRecord.recordId) {
+ logger.log(Level.SEVERE, "Profiler stack corrupted");
+ }
+ ProcessRecorder.get().writeRecord(
+ new ExecutionRecord(currentRecord.recordId,
+ currentRecord.parentRecordId,
+ currentRecord.startTimeInMs,
+ System.currentTimeMillis() - currentRecord.startTimeInMs,
+ currentRecord.executionType,
+ currentRecord.extraArgs));
+ }
+ // we always return null when an exception occurred and was not rethrown.
+ return null;
+ }
+}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/tasks/BooleanLatch.java b/build-system/profile/src/main/java/com/android/builder/tasks/BooleanLatch.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/tasks/BooleanLatch.java
rename to build-system/profile/src/main/java/com/android/builder/tasks/BooleanLatch.java
diff --git a/build-system/profile/src/main/java/com/android/builder/tasks/Job.java b/build-system/profile/src/main/java/com/android/builder/tasks/Job.java
new file mode 100644
index 0000000..786b08a
--- /dev/null
+++ b/build-system/profile/src/main/java/com/android/builder/tasks/Job.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Objects;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Definition of a queued job. A job has a title, a task to execute, a latch to signal its
+ * completion and a boolean result for success or failure.
+ */
+public class Job<T> {
+
+ private final String mJobTitle;
+ private final Task<T> mTask;
+ private final BooleanLatch mBooleanLatch;
+ private final AtomicBoolean mResult = new AtomicBoolean(false);
+ private final AtomicReference<Exception> mException;
+
+ public Job(String jobTile, Task<T> task) {
+ mJobTitle = jobTile;
+ mTask = task;
+ mBooleanLatch = new BooleanLatch();
+ mException = new AtomicReference(null);
+ }
+
+ public String getJobTitle() {
+ return mJobTitle;
+ }
+
+ public void runTask(@NonNull JobContext<T> jobContext) throws IOException {
+ mTask.run(this, jobContext);
+ }
+
+ public void finished() {
+ mResult.set(true);
+ mBooleanLatch.signal();
+ }
+
+ public void error(@Nullable Exception e) {
+ mResult.set(false);
+ mException.set(e);
+ mBooleanLatch.signal();
+ }
+
+ @Nullable
+ public Exception getFailureReason() {
+ return mException.get();
+ }
+
+ public boolean await() throws InterruptedException {
+
+ mBooleanLatch.await();
+ return mResult.get();
+ }
+
+ public boolean awaitRethrowExceptions() throws InterruptedException, RuntimeException {
+ boolean result = await();
+ if (!result && mException.get() != null) {
+ throw new RuntimeException(mException.get());
+ }
+ return result;
+ }
+
+ public boolean failed() {
+ return !mResult.get();
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("\ntitle", mJobTitle)
+ .add("\ntask", mTask)
+ .add("\nlatch", mBooleanLatch)
+ .add("\nresult", mResult.get())
+ .toString();
+ }
+}
diff --git a/base/build-system/profile/src/main/java/com/android/builder/tasks/JobContext.java b/build-system/profile/src/main/java/com/android/builder/tasks/JobContext.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/tasks/JobContext.java
rename to build-system/profile/src/main/java/com/android/builder/tasks/JobContext.java
diff --git a/base/build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContext.java b/build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContext.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContext.java
rename to build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContext.java
diff --git a/base/build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContextAdapter.java b/build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContextAdapter.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContextAdapter.java
rename to build-system/profile/src/main/java/com/android/builder/tasks/QueueThreadContextAdapter.java
diff --git a/base/build-system/profile/src/main/java/com/android/builder/tasks/Task.java b/build-system/profile/src/main/java/com/android/builder/tasks/Task.java
similarity index 100%
rename from base/build-system/profile/src/main/java/com/android/builder/tasks/Task.java
rename to build-system/profile/src/main/java/com/android/builder/tasks/Task.java
diff --git a/build-system/profile/src/main/java/com/android/builder/tasks/WorkQueue.java b/build-system/profile/src/main/java/com/android/builder/tasks/WorkQueue.java
new file mode 100644
index 0000000..8c01e77
--- /dev/null
+++ b/build-system/profile/src/main/java/com/android/builder/tasks/WorkQueue.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.tasks;
+
+import com.android.annotations.NonNull;
+import com.android.utils.ILogger;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A work queue that accepts jobs and treat them in order.
+ *
+ * @author jedo at google.com (Jerome Dochez)
+ */
+public class WorkQueue<T> implements Runnable {
+
+ private static final boolean VERBOSE = System.getenv("GRADLE_WORK_QUEUE_VERBOSE") != null;
+
+ private final ILogger mLogger;
+
+ // queue name as human would understand.
+ private final String mName;
+
+ // the user throttling has already happened before so I am using a potentially
+ // infinite linked list of request.
+ private final LinkedBlockingQueue<QueueTask<T>> mPendingJobs =
+ new LinkedBlockingQueue<QueueTask<T>>();
+
+ // List of working threads pumping from this queue.
+ private final List<Thread> mWorkThreads = new ArrayList<Thread>();
+
+ private final float mGrowthTriggerRation;
+ private final int mMWorkforceIncrement;
+ private final AtomicInteger mThreadId = new AtomicInteger(0);
+ private final QueueThreadContext<T> mQueueThreadContext;
+
+ // we could base this on the number of processors this machine has, etc...
+ private static final int MAX_WORKFORCE_SIZE = 20;
+
+
+ /**
+ * Private queue structure to store queue items.
+ */
+ private static class QueueTask<T> {
+
+ enum ActionType { Death, Normal }
+ final ActionType actionType;
+ final Job<T> job;
+
+ private QueueTask(ActionType actionType, Job<T> job) {
+ this.actionType = actionType;
+ this.job = job;
+ }
+ }
+
+ /**
+ * Creates a non expanding queue, with a number of dedicated threads to process
+ * the queue's jobs.
+ *
+ * @param logger to log messages
+ * @param queueName a meaningful descriptive name.
+ * @param workforce the number of dedicated threads for this queue.
+ */
+ public WorkQueue(
+ @NonNull ILogger logger,
+ @NonNull QueueThreadContext<T> queueThreadContext,
+ @NonNull String queueName,
+ int workforce) {
+ this(logger, queueThreadContext, queueName, workforce, Float.MAX_VALUE);
+ }
+
+ /**
+ * Creates a new queue, with a number of dedicated threads to process
+ * the queue's jobs.
+ *
+ * @param logger to log messages
+ * @param queueName a meaningful descriptive name.
+ * @param workforce the number of dedicated threads for this queue.
+ * @param growthTriggerRatio the ratio between outstanding requests and worker threads that
+ * should trigger a growth in worker threads.
+ */
+ public WorkQueue(
+ @NonNull ILogger logger,
+ @NonNull QueueThreadContext<T> queueThreadContext,
+ @NonNull String queueName,
+ int workforce,
+ float growthTriggerRatio) {
+
+ this.mLogger = logger;
+ this.mName = queueName;
+ this.mGrowthTriggerRation = growthTriggerRatio;
+ this.mMWorkforceIncrement = workforce;
+ this.mQueueThreadContext = queueThreadContext;
+ }
+
+ public void push(Job<T> job) throws InterruptedException {
+ _push(new QueueTask<T>(QueueTask.ActionType.Normal, job));
+ checkWorkforce();
+ }
+
+ private void _push(QueueTask<T> task) throws InterruptedException {
+ // at this point, I am not trying to limit the number of pending jobs.
+ // eventually we would want to put some limit to the size of the pending jobs
+ // queue so it does not grow out of control.
+ mPendingJobs.put(task);
+ }
+
+ private synchronized void checkWorkforce() {
+ if (mWorkThreads.isEmpty()
+ || (mPendingJobs.size() / mWorkThreads.size() > mGrowthTriggerRation)) {
+ verbose("Request to incrementing workforce from %1$d", mWorkThreads.size());
+ if (mWorkThreads.size() >= MAX_WORKFORCE_SIZE) {
+ verbose("Already at max workforce %1$d, denied.", MAX_WORKFORCE_SIZE);
+ return;
+ }
+ for (int i = 0; i < mMWorkforceIncrement; i++) {
+ Thread t = new Thread(this, mName + "_" + mThreadId.incrementAndGet());
+ t.setDaemon(true);
+ mWorkThreads.add(t);
+ t.start();
+ }
+ verbose("thread-pool size=%1$d", mWorkThreads.size());
+ }
+ }
+
+ private synchronized void reduceWorkforce() throws InterruptedException {
+ verbose("Decrementing workforce from " + mWorkThreads.size());
+ // push a the right number of kiss of death tasks to shutdown threads.
+ for (int i = 0; i < mMWorkforceIncrement; i++) {
+ _push(new QueueTask<T>(QueueTask.ActionType.Death, null));
+ }
+ }
+
+ /**
+ * Shutdowns the working queue and wait until all pending requests have
+ * been processed. This needs to be reviewed as jobs can still be added
+ * to the queue once the shutdown process has started....
+ * @throws InterruptedException if the shutdown sequence is interrupted
+ */
+ public synchronized void shutdown() throws InterruptedException {
+
+ // push as many death pills as necessary
+ for (Thread t : mWorkThreads) {
+ _push(new QueueTask<T>(QueueTask.ActionType.Death, null));
+ }
+ // we could use a latch.
+ for (Thread t : mWorkThreads) {
+ t.join();
+ }
+ mWorkThreads.clear();
+ mQueueThreadContext.shutdown();
+ }
+
+ /**
+ * Return a human readable queue name, mainly used for identification
+ * purposes.
+ *
+ * @return a unique meaningful descriptive name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the number of jobs waiting to be scheduled.
+ *
+ * @return the size of the queue.
+ */
+ public int size() {
+ return mPendingJobs.size();
+ }
+
+
+ /**
+ * each thread in the mWorkThreads will run this single infinite processing loop until a
+ * death action is received.
+ */
+ @Override
+ public void run() {
+ final String threadName = Thread.currentThread().getName();
+ // this
+ try {
+ try {
+ verbose("Creating a new working thread %1$s", threadName);
+ mQueueThreadContext.creation(Thread.currentThread());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ while(true) {
+ final QueueTask<T> queueTask = mPendingJobs.take();
+ if (queueTask.actionType== QueueTask.ActionType.Death) {
+ verbose("Thread(%1$s): Death requested", threadName);
+ // we are done.
+ return;
+ }
+ final Job<T> job = queueTask.job;
+ if (job == null) {
+ // this clearly should not happen.
+ mLogger.error(null, "I got a null pending job out of the priority queue");
+ return;
+ }
+ verbose("Thread(%1$s): scheduling %2$s", threadName, job.getJobTitle());
+
+ try {
+ mQueueThreadContext.runTask(job);
+ } catch (Exception e) {
+ mLogger.warning("Exception while processing task %1$s", e);
+ job.error(e);
+ return;
+ }
+ // wait for the job completion.
+ boolean result = job.await();
+ verbose("Thread(%1$s): job %2$s finished, result=%3$b",
+ threadName, job.getJobTitle(), result);
+
+ // we could potentially reduce the workforce at this point if we have little
+ // queuing comparatively to the number of worker threads but at this point, the
+ // overall process (gradle activity) is fairly short lived so skipping at this
+ // point.
+ verbose("Thread(%1$s): queue size %2$d", threadName, mPendingJobs.size());
+ }
+ } catch (InterruptedException e) {
+ mLogger.error(e, "Thread(%1$s): Interrupted", threadName);
+ } finally {
+ try {
+ verbose("Thread(%1$s): destruction", threadName);
+ mQueueThreadContext.destruction(Thread.currentThread());
+ } catch (IOException e) {
+ mLogger.error(e, "Thread(%1$s): %2$s", threadName, e.getMessage());
+ } catch (InterruptedException e) {
+ mLogger.error(e, "Thread(%1$s): %2$s", threadName, e.getMessage());
+ }
+ }
+ }
+
+ private void verbose(String format, Object...args) {
+ if (VERBOSE) {
+ mLogger.verbose(format, args);
+ }
+ }
+}
diff --git a/base/build-system/profile/src/test/java/com/android/builder/profile/ProcessRecorderTest.java b/build-system/profile/src/test/java/com/android/builder/profile/ProcessRecorderTest.java
similarity index 100%
rename from base/build-system/profile/src/test/java/com/android/builder/profile/ProcessRecorderTest.java
rename to build-system/profile/src/test/java/com/android/builder/profile/ProcessRecorderTest.java
diff --git a/base/build-system/profile/src/test/java/com/android/builder/profile/ThreadRecorderTest.java b/build-system/profile/src/test/java/com/android/builder/profile/ThreadRecorderTest.java
similarity index 100%
rename from base/build-system/profile/src/test/java/com/android/builder/profile/ThreadRecorderTest.java
rename to build-system/profile/src/test/java/com/android/builder/profile/ThreadRecorderTest.java
diff --git a/base/build-system/project-test-lib/build.gradle b/build-system/project-test-lib/build.gradle
similarity index 100%
rename from base/build-system/project-test-lib/build.gradle
rename to build-system/project-test-lib/build.gradle
diff --git a/base/build-system/project-test-lib/project-test-lib.iml b/build-system/project-test-lib/project-test-lib.iml
similarity index 100%
rename from base/build-system/project-test-lib/project-test-lib.iml
rename to build-system/project-test-lib/project-test-lib.iml
diff --git a/build-system/project-test-lib/src/main/java/com/android/build/tests/AndroidProjectConnector.java b/build-system/project-test-lib/src/main/java/com/android/build/tests/AndroidProjectConnector.java
new file mode 100644
index 0000000..fd522ee
--- /dev/null
+++ b/build-system/project-test-lib/src/main/java/com/android/build/tests/AndroidProjectConnector.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.tests;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.io.StreamException;
+import com.android.utils.SdkUtils;
+import com.google.common.collect.Lists;
+
+import org.gradle.tooling.BuildLauncher;
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ */
+public class AndroidProjectConnector {
+
+ @NonNull
+ private final File mSdkDir;
+
+ @Nullable
+ private final File mNdkDir;
+
+ public AndroidProjectConnector(@NonNull File sdkDir, @Nullable File ndkDir) {
+ mSdkDir = sdkDir;
+ mNdkDir = ndkDir;
+ }
+
+ public void runGradleTasks(
+ @NonNull File project,
+ @NonNull String gradleVersion,
+ @NonNull List<String> arguments,
+ @NonNull Map<String, String> jvmDefines,
+ @NonNull String... tasks) throws IOException, StreamException {
+ File localProp = createLocalProp(project);
+
+ try {
+ GradleConnector connector = GradleConnector.newConnector();
+
+ ProjectConnection connection = connector
+ .useGradleVersion(gradleVersion)
+ .forProjectDirectory(project)
+ .connect();
+ try {
+ List<String> args = Lists.newArrayListWithCapacity(2 + arguments.size());
+ args.add("-i");
+ args.add("-u");
+ args.addAll(arguments);
+
+ BuildLauncher build = connection.newBuild().forTasks(tasks).withArguments(
+ args.toArray(new String[args.size()]));
+
+ if (!jvmDefines.isEmpty()) {
+ String[] jvmArgs = new String[jvmDefines.size()];
+ int index = 0;
+ for (Map.Entry<String, String> entry : jvmDefines.entrySet()) {
+ jvmArgs[index++] = "-D" + entry.getKey() + "=" + entry.getValue();
+ }
+
+ build.setJvmArguments(jvmArgs);
+ }
+
+ build.run();
+ } finally {
+ connection.close();
+ }
+ } finally {
+ localProp.delete();
+ }
+ }
+
+ public AndroidProject getModel(@NonNull File project) {
+ // Configure the connector and create the connection
+ GradleConnector connector = GradleConnector.newConnector();
+
+ connector.forProjectDirectory(project);
+
+ ProjectConnection connection = connector.connect();
+ try {
+ // Load the custom model for the project
+ return connection.getModel(AndroidProject.class);
+ } finally {
+ connection.close();
+ }
+ }
+
+ private File createLocalProp(@NonNull File project) throws IOException, StreamException {
+ Properties p = new Properties();
+ p.put("sdk.dir", SdkUtils.escapePropertyValue(mSdkDir.getAbsolutePath())) ;
+ if (mNdkDir != null) {
+ p.put("ndk.dir", SdkUtils.escapePropertyValue(mNdkDir.getAbsolutePath()));
+ }
+
+ File file = new File(project, "local.properties");
+ p.store(new FileOutputStream(file), "automatically generated local.prop. Should be removed after test.");
+
+ return file;
+ }
+}
diff --git a/base/build-system/project-test/build.gradle b/build-system/project-test/build.gradle
similarity index 100%
rename from base/build-system/project-test/build.gradle
rename to build-system/project-test/build.gradle
diff --git a/base/build-system/project-test/build.xml b/build-system/project-test/build.xml
similarity index 100%
rename from base/build-system/project-test/build.xml
rename to build-system/project-test/build.xml
diff --git a/base/build-system/project-test/project-test.iml b/build-system/project-test/project-test.iml
similarity index 100%
rename from base/build-system/project-test/project-test.iml
rename to build-system/project-test/project-test.iml
diff --git a/base/build-system/project-test/src/main/java/com/android/build/tests/ProjectTest.java b/build-system/project-test/src/main/java/com/android/build/tests/ProjectTest.java
similarity index 100%
rename from base/build-system/project-test/src/main/java/com/android/build/tests/ProjectTest.java
rename to build-system/project-test/src/main/java/com/android/build/tests/ProjectTest.java
diff --git a/build-system/transform-api/build.gradle b/build-system/transform-api/build.gradle
new file mode 100644
index 0000000..8cb0dea
--- /dev/null
+++ b/build-system/transform-api/build.gradle
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'java'
+
+group = 'com.android.tools.build'
+archivesBaseName = 'transform-api'
+version ='2.0.0-deprecated-use-gradle-api'
+
+project.ext.pomName = 'Android Transform API'
+project.ext.pomDesc = 'Deprecated, use gradle-api'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
+
diff --git a/build.gradle b/build.gradle
index a6c30b5..7babcaf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,153 +1,10 @@
-apply plugin: 'clone-artifacts'
-
-// Currently, the minimum requirement to run Android SDK tools is Java 1.6
-// So make sure that we are compiling with 1.6
-task('checkJavaVersion') << {
- def jvmVersion = System.getProperty('java.version')
- def requiredVersion = System.getenv('JAVA_FOR_TESTS') ?: '1.6'
- if (!jvmVersion.startsWith(requiredVersion)) {
- throw new RuntimeException("Tools need to be compiled with Java $requiredVersion, you are using Java $jvmVersion.")
- }
-}
-final def checkJavaVersionTask = tasks['checkJavaVersion']
-
-allprojects { subproject ->
- tasks.withType(JavaForkOptions) {
- // Prevent forked processes from stealing focus (on MacOS at least)
- jvmArgs '-Djava.awt.headless=true'
- }
-
- afterEvaluate {
- // Only check if we are doing build type tasks. Things like dependency listing
- // should still work with a "wrong" java version.
- final def buildTasks = ['build', 'compileJava', 'compileGroovy', 'classes', 'assemble', 'javadoc', 'groovydoc', 'check']
- // Task.doFirst does not run if the task has no work to do. Need to be more aggressive than that.
- // Some projects won't have all of these tasks, so need to use findByName.
- buildTasks.each { subproject.tasks.findByName(it)?.dependsOn(checkJavaVersionTask) }
- }
-}
-
-// artifact cloning destinations
-cloneArtifacts {
- repository = "$rootDir/../prebuilts/tools/common/m2/repository"
-}
-
-if (System.env.USE_EXTERNAL_REPO != null) {
- allprojects {
- repositories {
- maven { url = uri(rootProject.cloneArtifacts.repository) }
- jcenter()
- maven { url 'https://repo.gradle.org/gradle/libs-snapshots-local' }
- maven { url 'https://repo.gradle.org/gradle/libs-releases-local' }
- }
- }
-}
-
-/*
- * With the build server you are given two env variables.
- * The OUT_DIR is a temporary directory you can use to put things during the build.
- * The DIST_DIR is where you want to save things from the build.
- *
- * The build server will copy the contents of DIST_DIR to somewhere and make it available.
- */
-if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
- ext.androidHostOut = file(System.env.OUT_DIR)
- ext.androidHostDist = file(System.env.DIST_DIR)
-} else {
- // ext.androidHostOut is shared by all tools/{base,build,swt} gradle projects/
- ext.androidHostOut = file("$rootDir/../out")
- ext.androidHostDist = new File(ext.androidHostOut, "dist")
-}
-
-
-// rootProject.buildDir is specific to this gradle build.
-buildDir = new File(ext.androidHostOut, "build/root")
-
-
-// apply this after the buildDir has been changed.
-apply plugin: 'sdk-tools'
-
-
-ext.localRepo = project.hasProperty('localRepo') ? localRepo : "$ext.androidHostOut/repo"
-
-apply from: "$rootDir/buildSrc/base/version.gradle"
-
subprojects { Project project ->
- // Change buildDir first so that all plugins pick up the new value.
- project.buildDir = project.file("$project.parent.buildDir/../$project.name/build")
-}
-
-// delay evaluation of this project before all subprojects have been evaluated.
-subprojects.each { subproject -> evaluationDependsOn(subproject.path) }
-
-def testTasks = subprojects.collect { it.tasks.withType(Test) }.flatten()
-
-// Print failed tests to the console early.
-testTasks.each { task ->
- task.testLogging {
- events "failed", "skipped"
- }
-}
+ // only configure leaf projects.
+ if (!project.getSubprojects().isEmpty()) return
-task aggregateResults(type: Copy) {
- from { testTasks*.binResultsDir*.parentFile*.parentFile }
- into { file("$buildDir/results") }
-}
-
-task prepareRepo(type: Copy) {
- from { rootProject.cloneArtifacts.repository }
- into { "$rootProject.ext.androidHostOut/repo" }
-}
-
-task copyGradleProperty(type: Copy) {
- from { "${System.env.HOME}/.gradle/gradle.properties" }
- into { gradle.gradleUserHomeDir }
-}
+ // exclude aar projects.
+ if ("fdr".equals(project.name)) return
-tasks.create(name: 'publishLocal')
-subprojects { project ->
- if (project.tasks.findByName('publishLocal') != null) {
- rootProject.publishLocal.dependsOn project.publishLocal
- }
-}
-
-task setupGradleInIde << {
- File dir = gradle.gradleHomeDir
- File gradleDistLink = new File(project.ext.androidHostOut, "gradle-dist-link")
- if (gradleDistLink.exists()) {
- gradleDistLink.delete()
- }
- String link = dir.path.substring(project.ext.androidHostOut.path.length() + 1)
- String command = "ln -s $link ${gradleDistLink.path}"
- command.execute()
-}
-
-// basic task for custom distribution of project should the build server.
-task dist << {
-}
-
-apply plugin: 'offline-repo'
-
-task clean << {
- delete 'build'
-
- new File("$localRepo/com/android/tools").eachFile {
- if (it.name != "external") {
- delete it
- }
- }
-}
-apply plugin: 'presubmit-runner'
-// Task for initializing a fresh repo.
-task init {
- dependsOn prepareRepo
- dependsOn setupGradleInIde
- dependsOn copyGradleProperty
- dependsOn tasks.findByPath(':base:gradle-experimental:setupGradleInIde')
- dependsOn tasks.findByPath(':base:builder:generateVersionConstantsJava')
+ apply from: "$rootDir/buildSrc/base/baseJava.gradle"
}
-// The hierarchy viewer tests require loading SWT jar files.
-// That fails with the error saying "can't load 32 bit library on 64 bit JVM
-// Disable these tests from running until that is fixed
-tasks.findByPath(':swt:hierarchyviewer2lib:test').enabled = false
diff --git a/base/changes.txt b/changes.txt
similarity index 100%
rename from base/changes.txt
rename to changes.txt
diff --git a/base/chartlib/build.gradle b/chartlib/build.gradle
similarity index 100%
rename from base/chartlib/build.gradle
rename to chartlib/build.gradle
diff --git a/base/chartlib/chartlib.iml b/chartlib/chartlib.iml
similarity index 100%
rename from base/chartlib/chartlib.iml
rename to chartlib/chartlib.iml
diff --git a/base/chartlib/src/main/java/com/android/tools/chartlib/AnimatedComponent.java b/chartlib/src/main/java/com/android/tools/chartlib/AnimatedComponent.java
similarity index 100%
rename from base/chartlib/src/main/java/com/android/tools/chartlib/AnimatedComponent.java
rename to chartlib/src/main/java/com/android/tools/chartlib/AnimatedComponent.java
diff --git a/base/chartlib/src/main/java/com/android/tools/chartlib/CircularArrayList.java b/chartlib/src/main/java/com/android/tools/chartlib/CircularArrayList.java
similarity index 100%
rename from base/chartlib/src/main/java/com/android/tools/chartlib/CircularArrayList.java
rename to chartlib/src/main/java/com/android/tools/chartlib/CircularArrayList.java
diff --git a/base/chartlib/src/main/java/com/android/tools/chartlib/EventData.java b/chartlib/src/main/java/com/android/tools/chartlib/EventData.java
similarity index 100%
rename from base/chartlib/src/main/java/com/android/tools/chartlib/EventData.java
rename to chartlib/src/main/java/com/android/tools/chartlib/EventData.java
diff --git a/base/chartlib/src/main/java/com/android/tools/chartlib/SunburstComponent.java b/chartlib/src/main/java/com/android/tools/chartlib/SunburstComponent.java
similarity index 100%
rename from base/chartlib/src/main/java/com/android/tools/chartlib/SunburstComponent.java
rename to chartlib/src/main/java/com/android/tools/chartlib/SunburstComponent.java
diff --git a/chartlib/src/main/java/com/android/tools/chartlib/TimelineComponent.java b/chartlib/src/main/java/com/android/tools/chartlib/TimelineComponent.java
new file mode 100644
index 0000000..8023963
--- /dev/null
+++ b/chartlib/src/main/java/com/android/tools/chartlib/TimelineComponent.java
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.chartlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.event.ActionListener;
+import java.awt.event.HierarchyListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.util.*;
+
+import javax.swing.Icon;
+
+import gnu.trove.TIntObjectHashMap;
+
+/**
+ * A component to display a TimelineData object. It locks the timeline object to prevent
+ * modifications to it while it's begin rendered, but objects of this class should not be accessed
+ * from different threads.
+ */
+public final class TimelineComponent extends AnimatedComponent
+ implements ActionListener, HierarchyListener {
+
+ private static final Color TEXT_COLOR = new Color(128, 128, 128);
+
+ private static final int LEFT_MARGIN = 120;
+
+ private static final int RIGHT_MARGIN = 200;
+
+ private static final int TOP_MARGIN = 10;
+
+ private static final int BOTTOM_MARGIN = 30;
+
+ private static final int FPS = 40;
+
+ /**
+ * The number of pixels a second in the timeline takes on the screen.
+ */
+ private static final float X_SCALE = 20;
+
+ private final float mBufferTime;
+
+ @NonNull
+ private final TimelineData mData;
+
+ @NonNull
+ private final EventData mEvents;
+
+ private final float mInitialMax;
+
+ private final float mAbsoluteMax;
+
+ private final float mInitialMarkerSeparation;
+
+ private final List<StreamInfo> mStreams = new ArrayList<StreamInfo>();
+
+ private Map<Integer, Style> mStyles;
+
+ private boolean mFirstFrame;
+
+ /**
+ * The boolean value whether to stack all streams together, by default it is true.
+ */
+ private boolean mStackStreams = true;
+
+ /**
+ * The current maximum range in y-axis units.
+ */
+ private float mCurrentMax;
+
+ /**
+ * The current minimum non-negative range in y-axis units.
+ */
+ private float mCurrentMin;
+
+ /**
+ * Marker separation in y-axis units.
+ */
+ private float mMarkerSeparation;
+
+ /**
+ * The current alpha of markers at even positions. When there are not enough/too many markers,
+ * the markers at even positions are faded in/out respectively. This tracks the animated alpha
+ * of such markers.
+ */
+ private float mEvenMarkersAlpha;
+
+ /**
+ * The current value in pixels where the x-axis is drawn.
+ */
+ private int mBottom;
+
+ /**
+ * The current value in pixels where the right hand side y-axis is drawn.
+ */
+ private int mRight;
+
+ /**
+ * The current scale from y-axis values to pixels.
+ */
+ private float mYScale;
+
+ /**
+ * The current time value at the right edge of the timeline in seconds.
+ */
+ private float mEndTime;
+
+ /**
+ * The current time value at the left edge of the timeline in seconds.
+ */
+ private float mBeginTime;
+
+ /**
+ * How to render each event type.
+ */
+ private TIntObjectHashMap<EventInfo> mEventsInfo;
+
+ /**
+ * The units of the y-axis values.
+ */
+ private String mUnits;
+
+ /**
+ * The number of available local samples.
+ */
+ private int mSize;
+
+ /**
+ * The times at which the samples occurred.
+ */
+ private float[] mTimes;
+
+ /**
+ * The times at which the samples occurred.
+ */
+ private int[] mTypes;
+
+ /**
+ * The render values of the samples depending on the layout mode, as in mValues[stream][sample]
+ */
+ private final float[][] mValues;
+
+ /**
+ * The last values of the samples for each stream
+ */
+ private final float[] mCurrent;
+
+ /**
+ * The number of events to render.
+ */
+ private int mEventsSize;
+
+ /**
+ * The start time of each event.
+ */
+ private float[] mEventStart;
+
+ /**
+ * The end time of each event, if NaN then the event did not end.
+ */
+ private float[] mEventEnd;
+
+ /**
+ * The type of each event.
+ */
+ private int[] mEventTypes;
+
+ /**
+ * The animated angle of an event in progress.
+ */
+ private float mEventProgressStart;
+
+ /**
+ * The direction of the event animation.
+ */
+ private float mEventProgressDir = 1.0f;
+
+ /**
+ * The current state for all in-progress events.
+ */
+ private float mEventProgress;
+
+ /**
+ * The reference values for which guiding horizontal lines are drawn. The colored lines provide guidance like the stream values
+ * should be at most a reference value, or at least a reference value.
+ */
+ private List<Reference> mReferences = new ArrayList<Reference>();
+
+ /**
+ * Creates a timeline component that renders the given timeline data. It will animate the
+ * timeline data by showing the value at the current time on the right y-axis of the graph.
+ *
+ * @param data the data to be displayed.
+ * @param bufferTime the time, in seconds, to lag behind the given {@code data}.
+ * @param initialMax the initial maximum value for the y-axis.
+ * @param absoluteMax the absolute maximum value for the y-axis.
+ * @param initialMarkerSeparation the initial separations for the markers on the y-axis.
+ */
+ public TimelineComponent(
+ @NonNull TimelineData data,
+ @NonNull EventData events,
+ float bufferTime,
+ float initialMax,
+ float absoluteMax,
+ float initialMarkerSeparation) {
+ super(FPS);
+ mData = data;
+ mEvents = events;
+ mBufferTime = bufferTime;
+ mInitialMax = initialMax;
+ mAbsoluteMax = absoluteMax;
+ mInitialMarkerSeparation = initialMarkerSeparation;
+ int streams = mData.getStreamCount();
+ addHierarchyListener(this);
+ mValues = new float[streams][];
+ mCurrent = new float[streams];
+ mSize = 0;
+ for (int i = 0; i < streams; i++) {
+ mStreams.add(new StreamInfo("Stream " + i, Color.BLACK));
+ }
+ mStyles = new HashMap<Integer, Style>();
+ mUnits = "";
+ mEventsInfo = new TIntObjectHashMap<EventInfo>();
+ setOpaque(true);
+ reset();
+ }
+
+ public void setStackStreams(boolean stackStreams) {
+ mStackStreams = stackStreams;
+ }
+
+ public void configureStream(int stream, String name, Color color) {
+ configureStream(stream, name, color, false);
+ }
+
+ public void configureStream(int stream, String name, Color color, boolean isMirrored) {
+ assert stream < mStreams.size() : String
+ .format("Attempting to configure out of bounds stream: Stream: %1$d, Size %2$d", stream, mStreams.size());
+ StreamInfo data = mStreams.get(stream);
+ data.name = name;
+ data.color = color;
+ data.isMirrored = isMirrored;
+ }
+
+ public void configureEvent(int type, int stream, Icon icon, Color color,
+ Color progress, boolean range) {
+ mEventsInfo.put(type, new EventInfo(type, stream, icon, color, progress, range));
+ }
+
+ public void configureType(int type, Style style) {
+ mStyles.put(type, style);
+ }
+
+ public void configureUnits(String units) {
+ mUnits = units;
+ }
+
+ public void reset() {
+ mCurrentMax = mInitialMax;
+ mCurrentMin = 0.0f;
+ mMarkerSeparation = mInitialMarkerSeparation;
+ mEvenMarkersAlpha = 1.0f;
+ mFirstFrame = true;
+ }
+
+ @Override
+ protected void draw(Graphics2D g2d) {
+ Dimension dim = getSize();
+
+ mBottom = Math.max(TOP_MARGIN, dim.height - BOTTOM_MARGIN);
+ mRight = Math.max(LEFT_MARGIN, dim.width - RIGHT_MARGIN);
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setFont(DEFAULT_FONT);
+ g2d.setClip(0, 0, dim.width, dim.height);
+ g2d.setColor(getBackground());
+ g2d.fillRect(0, 0, dim.width, dim.height);
+ g2d.setClip(LEFT_MARGIN, TOP_MARGIN, mRight - LEFT_MARGIN, mBottom - TOP_MARGIN);
+ drawTimelineData(g2d);
+ drawEvents(g2d);
+ g2d.setClip(0, 0, dim.width, dim.height);
+ drawLabels(g2d);
+ drawTimeMarkers(g2d);
+ drawMarkers(g2d);
+ drawReferenceLines(g2d);
+ drawGuides(g2d);
+
+ mFirstFrame = false;
+ }
+
+ @Override
+ protected void debugDraw(Graphics2D g2d) {
+ int drawn = 0;
+ g2d.setFont(DEFAULT_FONT.deriveFont(5.0f));
+ for (int i = 0; i < mSize; ++i) {
+ if (mTimes[i] > mBeginTime && mTimes[i] < mEndTime) {
+ for (int j = 0; j < mValues.length; ++j) {
+ int x = (int) timeToX(mTimes[i]);
+ int y = (int) valueToY(mValues[j][i]);
+ g2d.setColor(new Color((17 * mTypes[i]) % 255, (121 * mTypes[i]) % 255,
+ (71 * mTypes[i]) % 255));
+ g2d.drawLine(x, y - 2, x, y + 2);
+ g2d.drawLine(x - 2, y, x + 2, y);
+ g2d.setColor(TEXT_COLOR);
+ }
+ drawn++;
+ }
+ }
+
+ addDebugInfo("Drawn samples: %d", drawn);
+ }
+
+ private void drawTimelineData(Graphics2D g2d) {
+ mYScale = (mBottom - TOP_MARGIN) / (mCurrentMax - mCurrentMin);
+ if (mSize > 1) {
+ int from = 0;
+ // Optimize to not render too many samples since they get clipped.
+ while (from < mSize - 1 && mTimes[from + 1] < mBeginTime) {
+ from++;
+ }
+ int to = from;
+ while (to + 1 < mSize && mTimes[to] <= mEndTime) {
+ to++;
+ }
+ if (from == to) {
+ return;
+ }
+ int drawnSegments = 0;
+ for (int j = mValues.length - 1; j >= 0; j--) {
+ Path2D.Float path = new Path2D.Float();
+ path.moveTo(timeToX(mTimes[from]), valueToY(0.0f));
+ for (int i = from; i <= to; i++) {
+ float val = mValues[j][i];
+ path.lineTo(timeToX(mTimes[i]), valueToY(Math.min(val, mAbsoluteMax)));
+ }
+ path.lineTo(timeToX(mTimes[to]), valueToY(0.0f));
+ g2d.setColor(mStreams.get(j).color);
+ g2d.fill(path);
+
+ if (!mStyles.isEmpty()) {
+ path = new Path2D.Float();
+ Stroke current = g2d.getStroke();
+ float step = 3.0f;
+ float x0 = timeToX(mTimes[from]);
+ float y0 = valueToY(mValues[j][from]);
+ g2d.setColor(mStreams.get(j).color.darker());
+ Stroke stroke = null;
+ float strokeScale = Float.NaN;
+ for (int i = from + 1; i <= to; i++) {
+ float x1 = timeToX(mTimes[i]);
+ float y1 = valueToY(mValues[j][i]);
+ Style style = mStyles.get(mTypes[i]);
+ if (style != null && style != Style.NONE) {
+ BasicStroke str = new BasicStroke(1.0f);
+ float scale = 0;
+ if (style == Style.DASHED) {
+ float distance = (float) Point2D.distance(x0, y0, x1, y1);
+ float delta = mTimes[i] * X_SCALE;
+ scale = distance / (x1 - x0);
+ str = new BasicStroke(1.0f, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND, 0.0f, new float[]{step * scale},
+ (delta * scale) % (step * scale * 2));
+ }
+ if (scale != strokeScale) {
+ if (stroke != null) {
+ g2d.setStroke(stroke);
+ g2d.draw(path);
+ path.reset();
+ drawnSegments++;
+ }
+ strokeScale = scale;
+ stroke = str;
+ path.moveTo(x0, y0);
+ }
+ path.lineTo(x1, y1);
+ }
+ x0 = x1;
+ y0 = y1;
+ }
+ if (stroke != null) {
+ g2d.setStroke(stroke);
+ g2d.draw(path);
+ drawnSegments++;
+ }
+ g2d.setStroke(current);
+ }
+ }
+ addDebugInfo("Drawn segments: %d", drawnSegments);
+ }
+ addDebugInfo("Total samples: %d", mSize);
+ }
+
+ private float interpolate(int stream, int sample, float time) {
+ int prev = sample > 0 ? sample - 1 : 0;
+ int next = sample < mSize ? sample : mSize - 1;
+ float a = mValues[stream][prev];
+ float b = mValues[stream][next];
+ float delta = mTimes[next] - mTimes[prev];
+ float ratio = delta != 0 ? (time - mTimes[prev]) / delta : 1.0f;
+ return (b - a) * ratio + a;
+ }
+
+ private void drawEvents(Graphics2D g2d) {
+
+ if (mSize > 0) {
+ int drawnEvents = 0;
+ AffineTransform tx = g2d.getTransform();
+ Stroke stroke = g2d.getStroke();
+ int s = 0;
+ int e = 0;
+ while (e < mEventsSize) {
+ if (s < mSize && mTimes[s] < mEventStart[e]) {
+ s++;
+ } else if (Float.isNaN(mEventEnd[e])
+ || mEventEnd[e] > mBeginTime && mEventEnd[e] > mTimes[0]) {
+ drawnEvents++;
+ EventInfo info = mEventsInfo.get(mEventTypes[e]);
+ float x = timeToX(mEventStart[e]);
+ float y = valueToY(interpolate(info.stream, s, mEventStart[e]));
+ AffineTransform dt = new AffineTransform(tx);
+ dt.translate(x, y);
+ g2d.setTransform(dt);
+ info.icon.paintIcon(this, g2d, -info.icon.getIconWidth() / 2,
+ -info.icon.getIconHeight() - 5);
+ g2d.setTransform(tx);
+
+ g2d.setStroke(
+ new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+ Path2D.Float p = new Path2D.Float();
+ boolean closed = !Float.isNaN(mEventEnd[e]);
+ if (info.range) {
+ p.moveTo(x, mBottom);
+ p.lineTo(x, y);
+ float endTime = Float.isNaN(mEventEnd[e]) ? mEndTime : mEventEnd[e];
+ int i = s;
+ for (; i < mSize && mTimes[i] < endTime; i++) {
+ float val = mValues[info.stream][i];
+ p.lineTo(timeToX(mTimes[i]), valueToY(val));
+ }
+ p.lineTo(timeToX(endTime), valueToY(interpolate(info.stream, i, endTime)));
+ p.lineTo(timeToX(closed ? mEventEnd[e] : endTime), valueToY(0));
+ if (info.color != null) {
+ g2d.setColor(info.color);
+ g2d.fill(p);
+ }
+ g2d.setColor(info.progress);
+ g2d.draw(p);
+ } else {
+ p.moveTo(x, y - 2.0f);
+ p.lineTo(x, y + 2.0f);
+ g2d.setColor(info.progress);
+ g2d.draw(p);
+ }
+ if (!closed) {
+ g2d.setColor(info.progress);
+ // Draw in progress marker
+ float end = 360 * mEventProgress;
+ float start = mEventProgressStart;
+ if (mEventProgressDir < 0.0f) {
+ start += end;
+ end = 360 - end;
+ }
+ g2d.draw(new Arc2D.Float(
+ x + info.icon.getIconWidth() / 2 + 3,
+ y - info.icon.getIconHeight() - 3,
+ 6, 6,
+ start, end, Arc2D.OPEN));
+
+ }
+ e++;
+ } else {
+ e++;
+ }
+ }
+ g2d.setStroke(stroke);
+ addDebugInfo("Drawn events: %d", drawnEvents);
+ }
+ }
+
+ private float valueToY(float val) {
+ return mBottom - (val - mCurrentMin) * mYScale;
+ }
+
+ private float timeToX(float time) {
+ return LEFT_MARGIN + (time - mBeginTime) * X_SCALE;
+ }
+
+ private void drawLabels(Graphics2D g2d) {
+ g2d.setFont(DEFAULT_FONT);
+ FontMetrics metrics = g2d.getFontMetrics();
+ for (int i = 0; i < mStreams.size() && mSize > 0; i++) {
+ StreamInfo stream = mStreams.get(i);
+ g2d.setColor(stream.color);
+ int y = TOP_MARGIN + 15 + (mStreams.size() - i - 1) * 20;
+ g2d.fillRect(mRight + 20, y, 15, 15);
+ g2d.setColor(TEXT_COLOR);
+ g2d.drawString(String.format("%s [%.2f %s]", stream.name, mCurrent[i], mUnits), mRight + 40,
+ y + 7 + metrics.getAscent() * .5f);
+ }
+ }
+
+ private void drawTimeMarkers(Graphics2D g2d) {
+ g2d.setFont(DEFAULT_FONT);
+ g2d.setColor(TEXT_COLOR);
+ FontMetrics metrics = g2d.getFontMetrics();
+ float offset = metrics.stringWidth("000") * 0.5f;
+ Path2D.Float lines = new Path2D.Float();
+ float zeroY = valueToY(0.0f);
+ for (int sec = Math.max((int) Math.ceil(mBeginTime), 0); sec < mEndTime; sec++) {
+ float x = timeToX(sec);
+ boolean big = sec % 5 == 0;
+ if (big) {
+ String text = formatTime(sec);
+ g2d.drawString(text, x - metrics.stringWidth(text) + offset, zeroY + metrics.getAscent() + 5);
+ }
+ lines.moveTo(x, zeroY);
+ lines.lineTo(x, zeroY + (big ? 5 : 2));
+ }
+ g2d.draw(lines);
+ }
+
+ @VisibleForTesting
+ static String formatTime(int seconds) {
+ final char[] suffix = {'h', 'm', 's'};
+ final int[] secsPer = {60*60, 60, 1};
+
+ StringBuilder sb = new StringBuilder(12); // "999h 59m 59s"
+ for (int i = 0; i < suffix.length; i++) {
+ int value = seconds / secsPer[i];
+ seconds = seconds % secsPer[i];
+ if (value == 0 && sb.length() == 0 && i != (suffix.length - 1)) {
+ continue;
+ }
+
+ if (sb.length() > 0) {
+ sb.append(' ');
+ }
+ sb.append(value);
+ sb.append(suffix[i]);
+ }
+ return sb.toString();
+ }
+
+ private void drawMarkers(Graphics2D g2d) {
+ drawMarkers(g2d, 1.0f, mCurrentMax);
+ drawMarkers(g2d, -1.0f, mCurrentMin);
+ if (mCurrentMin < 0) {
+ int zeroY = (int)valueToY(0);
+ int minimumGap = getFontMetrics(DEFAULT_FONT).getAscent();
+ // Draw the zero marker if it is not overlapped by other markers.
+ if (mBottom - zeroY > minimumGap && zeroY - TOP_MARGIN > minimumGap) {
+ drawValueMarker(0, zeroY, g2d);
+ }
+ }
+ }
+
+ private void drawMarkers(Graphics2D g2d, float direction, float max) {
+ if (mYScale <= 0) {
+ return;
+ }
+
+ boolean drawNegativeMarkersAsPositive = hasMirroredStream();
+ int markers = (int) (max / mMarkerSeparation * direction);
+ for (int i = 0; i < markers + 1; i++) {
+ float markerValue = (i + 1) * mMarkerSeparation * direction;
+ int y = (int) valueToY(markerValue);
+ // Too close to the end
+ if (direction * (max - markerValue) < mMarkerSeparation * 0.5f) {
+ markerValue = max;
+ //noinspection AssignmentToForLoopParameter
+ i = markers;
+ y = (int) valueToY(max);
+ }
+ if (i < markers && i % 2 == 0 && mEvenMarkersAlpha < 1.0f) {
+ g2d.setColor(
+ new Color(TEXT_COLOR.getColorSpace(), TEXT_COLOR.getColorComponents(null),
+ mEvenMarkersAlpha));
+ } else {
+ g2d.setColor(TEXT_COLOR);
+ }
+ g2d.drawLine(LEFT_MARGIN - 2, y, LEFT_MARGIN, y);
+ drawValueMarker(drawNegativeMarkersAsPositive ? Math.abs(markerValue) : markerValue, y, g2d);
+ }
+ }
+
+ private void drawValueMarker(float value, int y, Graphics2D g2d) {
+ FontMetrics metrics = getFontMetrics(DEFAULT_FONT);
+ String marker = String.format("%.2f %s", value, mUnits);
+ g2d.drawString(marker, LEFT_MARGIN - 10 - metrics.stringWidth(marker), y + metrics.getAscent() * 0.5f);
+ }
+
+ private boolean hasMirroredStream() {
+ for (int i = 0; i < mStreams.size(); i++) {
+ if (mStreams.get(i).isMirrored) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void drawReferenceLines(Graphics2D g2d) {
+ for (Reference reference : mReferences) {
+ if (reference.value <= mCurrentMax && reference.value >= mCurrentMin) {
+ g2d.setColor(reference.color);
+ int y = (int)valueToY(reference.value);
+ g2d.drawLine(LEFT_MARGIN, y, mRight, y);
+ }
+ }
+ }
+
+ public void addReference(float reference, @NonNull Color color) {
+ mReferences.add(new Reference(reference, color));
+ }
+
+ private void drawGuides(Graphics2D g2d) {
+ g2d.setColor(TEXT_COLOR);
+ int zeroY = (int) valueToY(0.0f);
+ g2d.drawLine(LEFT_MARGIN - 10, zeroY, mRight + 10, zeroY);
+ if (mYScale > 0) {
+ g2d.drawLine(LEFT_MARGIN, mBottom, LEFT_MARGIN, TOP_MARGIN);
+ g2d.drawLine(mRight, mBottom, mRight, TOP_MARGIN);
+ }
+ }
+
+ @Override
+ protected void updateData() {
+ long start;
+ synchronized (mData) {
+ start = mData.getStartTime();
+ mSize = mData.size();
+ assert mData.getStreamCount() == mValues.length;
+ float lastUpdatedTime = mTimes != null ? mTimes[mTimes.length - 1] : 0;
+ if (mTimes == null || mTimes.length < mSize) {
+ int alloc = Math.max(mSize, mTimes == null ? 64 : mTimes.length * 2);
+ mTimes = new float[alloc];
+ mTypes = new int[alloc];
+ for (int j = 0; j < mData.getStreamCount(); ++j) {
+ mValues[j] = new float[alloc];
+ }
+ }
+ float cappedMax = 0;
+ float cappedMin = 0;
+ for (int i = 0; i < mSize; ++i) {
+ TimelineData.Sample sample = mData.get(i);
+ mTimes[i] = sample.time;
+ mTypes[i] = sample.type;
+ float value = 0.0f;
+ float mirroredValue = 0.0f;
+ for (int j = 0; j < mData.getStreamCount(); ++j) {
+ if (mStackStreams) {
+ mValues[j][i] = mStreams.get(j).isMirrored ? (mirroredValue -= sample.values[j]) : (value += sample.values[j]);
+ } else {
+ mValues[j][i] = mStreams.get(j).isMirrored ? -sample.values[j] : sample.values[j];
+ }
+ if (mTimes[i] > lastUpdatedTime) {
+ cappedMax = Math.max(cappedMax, mValues[j][i]);
+ cappedMin = Math.min(cappedMin, mValues[j][i]);
+ }
+ }
+ }
+ for (int j = 0; j < mData.getStreamCount(); ++j) {
+ mCurrent[j] = mSize > 0 ? mData.get(mSize - 1).values[j] : 0.0f;
+ }
+
+ // Calculate begin and end times in seconds.
+ mEndTime = mData.getEndTime() - mBufferTime;
+ mBeginTime = mEndTime - (mRight - LEFT_MARGIN) / X_SCALE;
+ // Animate the current maximum towards the real one.
+ cappedMax = Math.min(mAbsoluteMax, Math.max(mCurrentMax, cappedMax));
+ cappedMin = Math.max(-mAbsoluteMax, Math.min(mCurrentMin, cappedMin));
+ if (cappedMax > mCurrentMax) {
+ mCurrentMax = lerp(mCurrentMax, cappedMax, mFirstFrame ? 1.f : .95f);
+ }
+ if (cappedMin == 0.0f || cappedMin < mCurrentMin) {
+ mCurrentMin = lerp(mCurrentMin, cappedMin, mFirstFrame ? 1.f : .95f);
+ }
+
+ // Animate the fade in/out of markers.
+ FontMetrics metrics = getFontMetrics(DEFAULT_FONT);
+ int ascent = metrics.getAscent();
+ float distance = mMarkerSeparation * mYScale;
+ float evenMarkersTarget = 1.0f;
+ if (distance < ascent * 2) { // Too many markers
+ if (mEvenMarkersAlpha < 0.1f) {
+ mMarkerSeparation *= 2;
+ mEvenMarkersAlpha = 1.0f;
+ } else {
+ evenMarkersTarget = 0.0f;
+ }
+ } else if (distance > ascent * 5) { // Not enough
+ if (mEvenMarkersAlpha > 0.9f) {
+ mMarkerSeparation /= 2;
+ mEvenMarkersAlpha = 0.0f;
+ }
+ }
+ mEvenMarkersAlpha = lerp(mEvenMarkersAlpha, evenMarkersTarget, 0.999f);
+ }
+ synchronized (mEvents) {
+ mEventsSize = mEvents.size();
+ if (mEventStart == null || mEventStart.length < mEventsSize) {
+ int alloc = Math.max(mEventsSize, mEventStart == null ? 64 : mEventStart.length * 2);
+ mEventStart = new float[alloc];
+ mEventEnd = new float[alloc];
+ mEventTypes = new int[alloc];
+
+ }
+ for (int i = 0; i < mEventsSize; i++) {
+ EventData.Event event = mEvents.get(i);
+ mEventStart[i] = (event.from - start) / 1000.0f;
+ mEventEnd[i] = event.to == -1 ? Float.NaN : (event.to - start) / 1000.0f;
+ mEventTypes[i] = event.type;
+ }
+
+ // Animate events in progress
+ if (mEventProgress > 0.95f) {
+ mEventProgressDir = -mEventProgressDir;
+ mEventProgress = 0.0f;
+ }
+ mEventProgressStart = (mEventProgressStart + mFrameLength * 200.0f) % 360.0f;
+ mEventProgress = lerp(mEventProgress, 1.0f, .99f);
+ }
+ }
+
+ private static class StreamInfo {
+
+ public String name;
+
+ public Color color;
+
+ public boolean isMirrored;
+
+ public StreamInfo(@NonNull String name, @NonNull Color color) {
+ this(name, color, false);
+ }
+
+ public StreamInfo(@NonNull String name, @NonNull Color color, boolean isMirrored) {
+ this.name = name;
+ this.color = color;
+ this.isMirrored = isMirrored;
+ }
+ }
+
+ public enum Style {
+ NONE,
+ SOLID,
+ DASHED
+ }
+
+ private static class Reference {
+
+ public final float value;
+
+ @NonNull
+ public final Color color;
+
+ private Reference(float value, @NonNull Color color) {
+ this.value = value;
+ this.color = color;
+ }
+ }
+
+ private static class EventInfo {
+
+ public final int type;
+
+ public final int stream;
+
+ public final Icon icon;
+
+ public final Color color;
+
+ public final Color progress;
+
+ public final boolean range;
+
+ private EventInfo(int type, int stream, Icon icon, Color color,
+ Color progress, boolean range) {
+ this.type = type;
+ this.stream = stream;
+ this.icon = icon;
+ this.color = color;
+ this.progress = progress;
+ this.range = range;
+ }
+ }
+}
diff --git a/chartlib/src/main/java/com/android/tools/chartlib/TimelineData.java b/chartlib/src/main/java/com/android/tools/chartlib/TimelineData.java
new file mode 100644
index 0000000..752d626
--- /dev/null
+++ b/chartlib/src/main/java/com/android/tools/chartlib/TimelineData.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.chartlib;
+
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.concurrency.GuardedBy;
+
+import java.util.*;
+
+/**
+ * A group of streams of data sampled over time. This object is thread safe as it can be
+ * read/modified from any thread. It uses itself as the mutex object so it is possible to
+ * synchronize on it if modifications from other threads want to be prevented.
+ */
+public class TimelineData {
+
+ private final int myStreams;
+
+ @GuardedBy("this")
+ private final List<Sample> mSamples;
+
+ @GuardedBy("this")
+ private long mStart;
+
+ // The highest value across all streams being stacked together.
+ @GuardedBy("this")
+ private float mMaxTotal;
+
+ // The lowest value across all streams being stacked together.
+ @GuardedBy("this")
+ private float mMinTotal;
+
+ // The highest value of any single stream.
+ @GuardedBy("this")
+ private float mStreamMax;
+
+ // The lowest value of any single stream.
+ @GuardedBy("this")
+ private float mStreamMin;
+
+ public TimelineData(int streams, int capacity) {
+ myStreams = streams;
+ mSamples = new CircularArrayList<Sample>(capacity);
+ clear();
+ }
+
+ @VisibleForTesting
+ public synchronized long getStartTime() {
+ return mStart;
+ }
+
+ public int getStreamCount() {
+ return myStreams;
+ }
+
+ public synchronized float getMaxTotal() {
+ return mMaxTotal;
+ }
+
+
+ public synchronized float getMinTotal() {
+ return mMinTotal;
+ }
+
+ public synchronized float getStreamMax() {
+ return mStreamMax;
+ }
+
+ public synchronized float getStreamMin() {
+ return mStreamMin;
+ }
+
+ public synchronized void add(long time, int type, float... values) {
+ add(new Sample((time - mStart) / 1000.0f, type, values));
+ }
+
+ /**
+ * Converts stream area values to stream samples. All streams have different starting values from last sample, different areas,
+ * and result in different sample shapes. The conversion may break into multiple samples time points to make the shapes' areas are
+ * correct. For example, every stream flow is a triangle when not stacked with each other; it need four time points for all streams,
+ * one triangle is split into four parts at every time point, each part's shape may be changed while the area size is the same.
+ *
+ * @param time The current time in seconds from the start timestamp.
+ * @param areas The streams' area sizes.
+ * @param lastSample The last recent sample, which may be null.
+ * @param type The timeline data type.
+ */
+ private static List<Sample> convertAreasToSamples(float time, int type, float[] areas, @Nullable Sample lastSample) {
+ int streamSize = areas.length;
+ // The starting time and value are from last sample, to be consecutive.
+ float startTime = lastSample != null ? lastSample.time : 0.0f;
+ float[] startValues = lastSample != null ? lastSample.values : new float[streamSize];
+ assert streamSize == startValues.length;
+
+ // Computes how long every stream's value is non-zero and the ending value at last.
+ float maxInterval = time - startTime;
+ if (maxInterval <= 0) {
+ return new ArrayList<Sample>();
+ }
+ float[] nonZeroIntervalsForStreams = new float[streamSize];
+ float[] endValuesForStreams = new float[streamSize];
+ for (int i = 0; i < streamSize; i++) {
+ if (Math.abs(startValues[i]) * maxInterval / 2 < Math.abs(areas[i])) {
+ nonZeroIntervalsForStreams[i] = maxInterval;
+ endValuesForStreams[i] = areas[i] * 2 / maxInterval - startValues[i];
+ }
+ else if (areas[i] == 0) {
+ nonZeroIntervalsForStreams[i] = maxInterval;
+ endValuesForStreams[i] = 0;
+ }
+ else {
+ // startValues[i] should be non-zero to be greater than areas[i].
+ nonZeroIntervalsForStreams[i] = areas[i] * 2 / startValues[i];
+ endValuesForStreams[i] = 0;
+ }
+ }
+
+ // Sorts the intervals, every different interval should be a sample.
+ float[] ascendingIntervals = Arrays.copyOf(nonZeroIntervalsForStreams, streamSize);
+ Arrays.sort(ascendingIntervals);
+ List<Sample> sampleList = new ArrayList<Sample>();
+ for (float interval : ascendingIntervals) {
+ float[] sampleValues = new float[streamSize];
+ for (int j = 0; j < streamSize; j++) {
+ sampleValues[j] = nonZeroIntervalsForStreams[j] < interval
+ ? 0.0f
+ : startValues[j] - (startValues[j] - endValuesForStreams[j]) * interval / nonZeroIntervalsForStreams[j];
+ }
+ sampleList.add(new Sample(interval + startTime, type, sampleValues));
+ if (interval == maxInterval) {
+ break;
+ }
+ }
+ if (ascendingIntervals[streamSize - 1] < maxInterval) {
+ // Adds the ending sample that all stream values are zero.
+ sampleList.add(new Sample(time, type, new float[streamSize]));
+ }
+ return sampleList;
+ }
+
+ /**
+ * Adds the stream values which are values converted from the areas values. The values depends on both last sample values and
+ * the current areas' sizes. It should be a synchronized method to let the last recent sample be accurate.
+ *
+ * @param timeMills The current time in mills.
+ * @param type Sample data type.
+ * @param areas Value multiple time area sizes for all streams.
+ */
+ public synchronized void addFromArea(long timeMills, int type, float... areas) {
+ float timeForStart = (timeMills - mStart) / 1000.0f;
+ Sample lastSample = mSamples.isEmpty() ? null : mSamples.get(mSamples.size() - 1);
+ for (Sample sample : convertAreasToSamples(timeForStart, type, areas, lastSample)) {
+ add(sample);
+ }
+ }
+
+ private void add(Sample sample) {
+ float[] values = sample.values;
+ assert values.length == myStreams;
+ float stacked = 0.0f;
+ for (float value : values) {
+ stacked += value;
+ mMaxTotal = Math.max(mMaxTotal, stacked);
+ mMinTotal = Math.min(mMinTotal, stacked);
+ mStreamMax = Math.max(mStreamMax, value);
+ mStreamMin = Math.min(mStreamMin, value);
+ }
+ mSamples.add(sample);
+ }
+
+ public synchronized void clear() {
+ mSamples.clear();
+ mMaxTotal = 0.0f;
+ mStreamMax = 0.0f;
+ mStart = System.currentTimeMillis();
+ }
+
+ public int size() {
+ return mSamples.size();
+ }
+
+ public Sample get(int index) {
+ return mSamples.get(index);
+ }
+
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ public synchronized float getEndTime() {
+ return (mSamples.isEmpty() ? 0.0f : (System.currentTimeMillis() - mStart)) / 1000.f;
+ }
+
+ /**
+ * A sample of all the streams at a given moment in time.
+ */
+ public static class Sample {
+
+ /**
+ * The time of the sample. In seconds since the start of the sampling.
+ */
+ public final float time;
+
+ public final float[] values;
+
+ public final int type;
+
+ public Sample(float time, int type, float[] values) {
+ this.time = time;
+ this.values = values;
+ this.type = type;
+ }
+ }
+}
diff --git a/base/chartlib/src/main/java/com/android/tools/chartlib/ValuedTreeNode.java b/chartlib/src/main/java/com/android/tools/chartlib/ValuedTreeNode.java
similarity index 100%
rename from base/chartlib/src/main/java/com/android/tools/chartlib/ValuedTreeNode.java
rename to chartlib/src/main/java/com/android/tools/chartlib/ValuedTreeNode.java
diff --git a/chartlib/src/test/java/AnimatedComponentVisualTests.java b/chartlib/src/test/java/AnimatedComponentVisualTests.java
new file mode 100644
index 0000000..54a4403
--- /dev/null
+++ b/chartlib/src/test/java/AnimatedComponentVisualTests.java
@@ -0,0 +1,572 @@
+/*
+ * 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.
+ */
+
+import com.android.tools.chartlib.AnimatedComponent;
+import com.android.tools.chartlib.EventData;
+import com.android.tools.chartlib.SunburstComponent;
+import com.android.tools.chartlib.TimelineComponent;
+import com.android.tools.chartlib.TimelineData;
+import com.android.tools.chartlib.ValuedTreeNode;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class AnimatedComponentVisualTests extends JDialog {
+
+ private List<AnimatedComponent> mComponents = new LinkedList<AnimatedComponent>();
+
+ public AnimatedComponentVisualTests() {
+ JPanel contentPane = new JPanel(new BorderLayout());
+ JButton close = new JButton("Close");
+ JTabbedPane tabs = new JTabbedPane();
+ tabs.addTab("PieChart", getPieChartExample());
+ tabs.addTab("Timeline", getTimelineExample());
+
+ contentPane.setPreferredSize(new Dimension(1280, 1024));
+ contentPane.add(tabs, BorderLayout.CENTER);
+
+ JPanel bottom = new JPanel(new BorderLayout());
+ bottom.add(close, BorderLayout.EAST);
+ contentPane.add(bottom, BorderLayout.SOUTH);
+
+ JPanel controls = new JPanel();
+ controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
+ controls.add(Box.createRigidArea(new Dimension(100, 20)));
+ final JCheckBox debug = new JCheckBox("Debug");
+ debug.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ for (AnimatedComponent component : mComponents) {
+ component.setDrawDebugInfo(debug.isSelected());
+ }
+ }
+ });
+ controls.add(debug);
+
+ final JButton step = new JButton("Step");
+ step.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ for (AnimatedComponent component : mComponents) {
+ component.step();
+ }
+ }
+ });
+ final JCheckBox update = new JCheckBox("Update");
+ update.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ for (AnimatedComponent component : mComponents) {
+ component.setUpdateData(update.isSelected());
+ }
+ step.setEnabled(!update.isSelected());
+ }
+ });
+ update.setSelected(true);
+ step.setEnabled(false);
+ final JCheckBox dark = new JCheckBox("Dark");
+ dark.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ setDarkMode(dark.isSelected());
+ }
+ });
+ controls.add(dark);
+ controls.add(update);
+ controls.add(step);
+ contentPane.add(controls, BorderLayout.WEST);
+
+ setDarkMode(false);
+ setContentPane(contentPane);
+ setModal(true);
+ getRootPane().setDefaultButton(close);
+
+ close.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dispose();
+ }
+ });
+ }
+
+ private void setDarkMode(boolean dark) {
+ for (AnimatedComponent c : mComponents) {
+ c.setBackground(dark ? new Color(60, 63, 65) : new Color(244, 244, 244));
+ }
+ }
+
+ interface Value {
+ void set(int v);
+ int get();
+ }
+
+ private static JPanel createVaribleSlider(String name, final int a, final int b,
+ final Value value) {
+ JPanel panel = new JPanel(new BorderLayout());
+ final JLabel text = new JLabel();
+ final JSlider slider = new JSlider(a, b);
+ ChangeListener listener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent changeEvent) {
+ value.set(slider.getValue());
+ text.setText(String.format("%d [%d,%d]", slider.getValue(), a, b));
+ }
+ };
+ slider.setValue(value.get());
+ listener.stateChanged(null);
+ slider.addChangeListener(listener);
+ panel.add(slider, BorderLayout.CENTER);
+ panel.add(new JLabel(name + ": "), BorderLayout.WEST);
+ panel.add(text, BorderLayout.EAST);
+ panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ return panel;
+ }
+
+ private JPanel createControlledPane(JPanel panel, AnimatedComponent animated) {
+ panel.setLayout(new BorderLayout());
+ mComponents.add(animated);
+ panel.add(animated, BorderLayout.CENTER);
+
+ JPanel controls = new JPanel();
+ LayoutManager manager = new BoxLayout(controls, BoxLayout.Y_AXIS);
+ controls.setLayout(manager);
+ panel.add(controls, BorderLayout.WEST);
+ return controls;
+ }
+
+ static class DataNode extends DefaultMutableTreeNode implements ValuedTreeNode {
+
+ private int mCount;
+ private int mValue;
+
+ public DataNode() {
+ this(0, 0);
+ }
+
+ public DataNode(int count, int value) {
+ mCount = count;
+ mValue = value;
+ }
+
+ @Override
+ public int getCount() {
+ return mCount;
+ }
+
+ @Override
+ public int getValue() {
+ return mValue;
+ }
+
+ public void add(int count, int value) {
+ mCount += count;
+ mValue += value;
+ if (parent instanceof DataNode) {
+ ((DataNode)parent).add(count, value);
+ }
+ }
+
+ public void addDataNode(DataNode dataNode) {
+ super.add(dataNode);
+ add(dataNode.getCount(), dataNode.getValue());
+ }
+ }
+
+ private JPanel getPieChartExample() {
+
+ final DataNode data = new DataNode();
+ data.addDataNode(new DataNode(1, 10));
+
+ final SunburstComponent layout = new SunburstComponent(data);
+
+ JPanel panel = new JPanel();
+ JPanel controls = createControlledPane(panel, layout);
+ final JLabel info = new JLabel("<No information yet>");
+ panel.add(info, BorderLayout.SOUTH);
+
+ controls.add(createVaribleSlider("Gap", 0, 200, new Value() {
+ @Override
+ public void set(int v) {
+ layout.setGap(v);
+ }
+
+ @Override
+ public int get() {
+ return (int) layout.getGap();
+ }
+ }));
+ controls.add(createVaribleSlider("Size", 0, 200, new Value() {
+ @Override
+ public void set(int v) {
+ layout.setSliceWidth(v);
+ }
+
+ @Override
+ public int get() {
+ return (int) layout.getSliceWidth();
+ }
+ }));
+ controls.add(createVaribleSlider("Angle", 0, 360, new Value() {
+ @Override
+ public void set(int v) {
+ layout.setAngle(v);
+ }
+
+ @Override
+ public int get() {
+ return (int) layout.getAngle();
+ }
+ }));
+ controls.add(createVaribleSlider("Start", 0, 360, new Value() {
+ @Override
+ public void set(int v) {
+ layout.setStart(v);
+ }
+
+ @Override
+ public int get() {
+ return (int) layout.getStart();
+ }
+ }));
+ controls.add(createVaribleSlider("Fixed", 1, 100, new Value() {
+ @Override
+ public void set(int v) {
+ layout.setFixed(v);
+ }
+
+ @Override
+ public int get() {
+ return (int) layout.getFixed();
+ }
+ }));
+ controls.add(createVaribleSlider("Separator", 0, 100, new Value() {
+ @Override
+ public void set(int v) {
+ layout.setSeparator(v);
+ }
+
+ @Override
+ public int get() {
+ return (int) layout.getSeparator();
+ }
+ }));
+ controls.add(createButton("Generate", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ generateLayoutData((DataNode) layout.getData(), 5);
+ }
+ }));
+ controls.add(createButton("Tree A", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ DataNode g = new DataNode();
+ g.addDataNode(createTree(1));
+ g.addDataNode(createValue());
+ g.addDataNode(createTree(1));
+ g.addDataNode(createValue());
+ g.addDataNode(createTree(0));
+ layout.setData(g);
+ }
+ }));
+ controls.add(createButton("Tree B", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ DataNode g = new DataNode();
+ g.addDataNode(createValue());
+ g.addDataNode(createValue());
+ g.addDataNode(createTree(0));
+ layout.setData(g);
+ }
+ }));
+ controls.add(createButton("Value", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ DataNode g = new DataNode();
+ g.addDataNode(new DataNode(1, (int) (Math.random() * 50)));
+ layout.setData(g);
+ }
+ }));
+ controls.add(createCheckbox("Auto size", new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent itemEvent) {
+ layout.setAutoSize(itemEvent.getStateChange() == ItemEvent.SELECTED);
+ }
+ }));
+ controls.add(
+ new Box.Filler(new Dimension(0, 0), new Dimension(300, Integer.MAX_VALUE),
+ new Dimension(300, Integer.MAX_VALUE)));
+
+ layout.addSelectionListener(new SunburstComponent.SliceSelectionListener() {
+ @Override
+ public void valueChanged(SunburstComponent.SliceSelectionEvent e) {
+ ValuedTreeNode node = e.getNode();
+ info.setText(node == null ? "<No selection>" : String.format("Value %d Count %d",
+ node.getValue(), node.getCount()));
+ }
+ });
+ return panel;
+ }
+
+ private static DataNode createValue() {
+ return new DataNode(1, (int)(Math.random() * 50));
+ }
+
+ private static DataNode createTree(int depth) {
+ DataNode b = depth == 0 ? createValue() : createTree(depth - 1);
+ DataNode c = depth == 0 ? createValue() : createTree(depth - 1);
+ DataNode a = new DataNode();
+ a.addDataNode(b);
+ a.addDataNode(c);
+ return a;
+ }
+
+ private static Component createButton(String label, ActionListener action) {
+ JButton button = new JButton(label);
+ button.addActionListener(action);
+ button.setMaximumSize(new Dimension(Integer.MAX_VALUE, button.getMaximumSize().height));
+ return button;
+ }
+
+ private static Component createCheckbox(String label, ItemListener action) {
+ return createCheckbox(label, action, false);
+ }
+
+ private static Component createCheckbox(String label, ItemListener action, boolean selected) {
+ JCheckBox button = new JCheckBox(label);
+ button.addItemListener(action);
+ button.setMaximumSize(new Dimension(Integer.MAX_VALUE, button.getMaximumSize().height));
+ button.setSelected(selected);
+ return button;
+ }
+
+ private static void generateLayoutData(DataNode data, int maxDepth) {
+ Random random = new Random();
+ int branch = random.nextInt(9) + 1;
+ for (int i = 0; i < branch; i++) {
+ int value = random.nextInt(1024);
+ if (maxDepth > 0 && random.nextInt(4) == 0) {
+ DataNode group = new DataNode();
+ group.add(new DataNode(1, value));
+ generateLayoutData(group, maxDepth - 1);
+ data.addDataNode(group);
+ } else {
+ data.addDataNode(new DataNode(1, value));
+ }
+ }
+ }
+
+ private enum TimelineBehavior {
+ POSITIVE("Positive values only"),
+ NEGATIVE("Negative and positive values"),
+ MIRRORED("Mirrored streams");
+
+ public final String description;
+
+ TimelineBehavior(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+ }
+
+ private JPanel getTimelineExample() {
+
+ // Actually, there are three timelines, but we show one at a time.
+ // Use the pulldown to switch.
+
+ JPanel topPanel = new JPanel(new BorderLayout());
+ final CardLayout cardLayout = new CardLayout();
+ final JPanel timelinePanels = new JPanel(cardLayout);
+
+ final JComboBox choices = new JComboBox(TimelineBehavior.values());
+ for (TimelineBehavior timelineBehavior : TimelineBehavior.values()) {
+ timelinePanels.add(createTimelinePanel(timelineBehavior), timelineBehavior.name());
+ }
+
+ choices.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ TimelineBehavior selectedMode = (TimelineBehavior)choices.getSelectedItem();
+ cardLayout.show(timelinePanels, selectedMode.name());
+ }
+ });
+
+ JPanel wrapPanel = new JPanel();
+ wrapPanel.add(choices); // Wrap the combobox so it shows up centered instead of stretched
+ topPanel.add(wrapPanel, BorderLayout.NORTH);
+ topPanel.add(timelinePanels, BorderLayout.CENTER);
+ return topPanel;
+ }
+
+ private JPanel createTimelinePanel(final TimelineBehavior timelineBehavior) {
+ final int numStreams = 2;
+
+ final EventData events = new EventData();
+ final TimelineData data = new TimelineData(numStreams, 2000);
+ final AtomicInteger variance = new AtomicInteger(10);
+ final AtomicInteger delay = new AtomicInteger(100);
+ final AtomicInteger type = new AtomicInteger(0);
+ new Thread() {
+ @Override
+ public void run() {
+ super.run();
+ try {
+ float[] values = new float[numStreams];
+ while (true) {
+ int v = variance.get();
+
+ for (int i = 0; i < numStreams; i++) {
+ float delta = (float) Math.random() * variance.get() - v * 0.5f;
+ values[i] = delta + values[i];
+ }
+ float[] valuesCopy = new float[numStreams];
+ System.arraycopy(values, 0, valuesCopy, 0, numStreams);
+ if (timelineBehavior != TimelineBehavior.NEGATIVE) {
+ for (int i = 0; i < numStreams; i++) {
+ valuesCopy[i] = Math.abs(valuesCopy[i]);
+ }
+ }
+ else {
+ // In the "negative" example, always make stream 2 negative so you
+ // can definitely see at least one negative stream at all times.
+ if (valuesCopy[1] > 0) {
+ valuesCopy[1] = -valuesCopy[1];
+ }
+ }
+ synchronized (data) {
+ data.add(System.currentTimeMillis(), type.get() + (v == 0 ? 1 : 0), valuesCopy);
+ }
+ Thread.sleep(delay.get());
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+ }.start();
+
+ final TimelineComponent timeline = new TimelineComponent(data, events, 1.0f, 10.0f, 1000.0f, 10.0f);
+ timeline.configureStream(0, "Data 0", new Color(0x78abd9));
+ timeline.configureStream(1, "Data 1", new Color(0xbaccdc), timelineBehavior == TimelineBehavior.MIRRORED);
+
+ timeline.configureUnits("@");
+ timeline.configureEvent(1, 0, UIManager.getIcon("Tree.leafIcon"), new Color(0x92ADC6), new Color(0x2B4E8C), false);
+ timeline.configureEvent(2, 1, UIManager.getIcon("Tree.leafIcon"), new Color(255, 191, 176), new Color(76, 14, 29), true);
+ timeline.configureType(1, TimelineComponent.Style.SOLID);
+ timeline.configureType(2, TimelineComponent.Style.DASHED);
+
+ final JPanel panel = new JPanel();
+ final JPanel controls = createControlledPane(panel, timeline);
+
+ controls.add(createVaribleSlider("Delay", 10, 5000, new Value() {
+ @Override
+ public void set(int v) {
+ delay.set(v);
+ }
+
+ @Override
+ public int get() {
+ return delay.get();
+ }
+ }));
+ controls.add(createVaribleSlider("Variance", 0, 50, new Value() {
+ @Override
+ public void set(int v) {
+ variance.set(v);
+ }
+
+ @Override
+ public int get() {
+ return variance.get();
+ }
+ }));
+ controls.add(createVaribleSlider("Type", 0, 2, new Value() {
+ @Override
+ public void set(int v) {
+ type.set(v);
+ }
+
+ @Override
+ public int get() {
+ return type.get();
+ }
+ }));
+ controls.add(createEventButton(1, events, variance));
+ controls.add(createEventButton(1, events, null));
+ controls.add(createEventButton(2, events, variance));
+ controls.add(createCheckbox("Stack streams", new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ timeline.setStackStreams(e.getStateChange() == ItemEvent.SELECTED);
+ }
+ }, true));
+
+ controls.add(new Box.Filler(new Dimension(0, 0), new Dimension(300, Integer.MAX_VALUE), new Dimension(300, Integer.MAX_VALUE)));
+ panel.add(timeline, BorderLayout.CENTER);
+ return panel;
+ }
+
+ private Component createEventButton(final int type, final EventData events,
+ final AtomicInteger variance) {
+ final String start = "Start " + (variance != null ? "blocking " : "") + "event type " + type;
+ final String stop = "Stop event type " + type;
+ return createButton(start, new ActionListener() {
+ EventData.Event event = null;
+ int var = 0;
+
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ JButton button = (JButton) actionEvent.getSource();
+ if (event != null) {
+ event.stop(System.currentTimeMillis());
+ event = null;
+ if (variance != null) {
+ variance.set(var);
+ }
+ button.setText(start);
+ } else {
+ event = events.start(System.currentTimeMillis(), type);
+ if (variance != null) {
+ var = variance.get();
+ variance.set(0);
+ }
+ button.setText(stop);
+ }
+ }
+ });
+ }
+
+ public static void main(String[] args) {
+ AnimatedComponentVisualTests dialog = new AnimatedComponentVisualTests();
+
+ dialog.pack();
+ dialog.setVisible(true);
+ System.exit(0);
+ }
+}
diff --git a/base/chartlib/src/test/java/com/android/tools/chartlib/CircularArrayListTest.java b/chartlib/src/test/java/com/android/tools/chartlib/CircularArrayListTest.java
similarity index 100%
rename from base/chartlib/src/test/java/com/android/tools/chartlib/CircularArrayListTest.java
rename to chartlib/src/test/java/com/android/tools/chartlib/CircularArrayListTest.java
diff --git a/base/chartlib/src/test/java/com/android/tools/chartlib/TimelineComponentTest.java b/chartlib/src/test/java/com/android/tools/chartlib/TimelineComponentTest.java
similarity index 100%
rename from base/chartlib/src/test/java/com/android/tools/chartlib/TimelineComponentTest.java
rename to chartlib/src/test/java/com/android/tools/chartlib/TimelineComponentTest.java
diff --git a/chartlib/src/test/java/com/android/tools/chartlib/TimelineDataTest.java b/chartlib/src/test/java/com/android/tools/chartlib/TimelineDataTest.java
new file mode 100644
index 0000000..db83037
--- /dev/null
+++ b/chartlib/src/test/java/com/android/tools/chartlib/TimelineDataTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.chartlib;
+
+import junit.framework.TestCase;
+
+public class TimelineDataTest extends TestCase {
+
+ private static final int TYPE_DATA = 0;
+
+ private TimelineData mData;
+
+ private long mCreationTime;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mCreationTime = System.currentTimeMillis();
+ mData = new TimelineData(2, 2);
+ }
+
+ public void testStreamGetters() throws Exception {
+ assertEquals(2, mData.getStreamCount());
+ }
+
+ public void testGetStartTime() throws Exception {
+ assertTrue(mCreationTime <= mData.getStartTime());
+ assertTrue(mData.getStartTime() <= System.currentTimeMillis());
+ Thread.sleep(10);
+ long now = System.currentTimeMillis();
+ mData.clear();
+ assertTrue(now <= mData.getStartTime());
+ }
+
+ public void testGetMaxTotal() throws Exception {
+ assertEquals(0.0f, mData.getMaxTotal());
+ long now = System.currentTimeMillis();
+ mData.add(now + 1, 0, 1.0f, 2.0f);
+ assertEquals(3.0f, mData.getMaxTotal());
+ mData.add(now + 2, 0, 1.0f, 1.0f);
+ assertEquals(3.0f, mData.getMaxTotal());
+ mData.add(now + 3, 0, 2.0f, 2.0f);
+ assertEquals(4.0f, mData.getMaxTotal());
+ }
+
+ public void testAdd() throws Exception {
+ assertEquals(0, mData.size());
+ long start = mData.getStartTime();
+
+ mData.add(start, 0, 1.0f, 2.0f);
+ assertEquals(1, mData.size());
+ assertEquals(0.0f, mData.get(0).time, 0.0001f);
+ assertEquals(1.0f, mData.get(0).values[0]);
+ assertEquals(2.0f, mData.get(0).values[1]);
+
+ mData.add(start + 1000, 0, 3.0f, 4.0f);
+ assertEquals(2, mData.size());
+ assertEquals(1.0f, mData.get(1).time, 0.0001f);
+ assertEquals(3.0f, mData.get(1).values[0]);
+ assertEquals(4.0f, mData.get(1).values[1]);
+
+ mData.add(start + 2000, 0, 5.0f, 6.0f);
+ assertEquals(2, mData.size());
+ assertEquals(2.0f, mData.get(1).time, 0.0001);
+ assertEquals(5.0f, mData.get(1).values[0]);
+ assertEquals(6.0f, mData.get(1).values[1]);
+
+ mData.clear();
+ assertEquals(0, mData.size());
+ }
+
+ public void testAddFromAreaForFirstSample() {
+ assertEquals(0, mData.size());
+ long start = mData.getStartTime();
+ mData.addFromArea(start + 1000, TYPE_DATA, 500.0f, 100.0f);
+ assertEquals(1, mData.size());
+ TimelineData.Sample sample = mData.get(0);
+ assertEquals(1.0f, sample.time);
+ assertEquals(1000.0f, sample.values[0]);
+ assertEquals(200.0f, sample.values[1]);
+ }
+
+ public void testAddFromAreaExpectSpeedNotGoingDown() {
+ assertEquals(0, mData.size());
+ long start = mData.getStartTime();
+ mData.addFromArea(start + 1000, TYPE_DATA, 500.0f, 100.0f);
+ mData.addFromArea(start + 1500, TYPE_DATA, 500.0f, 100.0f);
+ assertEquals(2, mData.size());
+ TimelineData.Sample sample = mData.get(1);
+ assertEquals(1.5f, sample.time);
+ assertEquals(1000.0f, sample.values[0]);
+ assertEquals(200.0f, sample.values[1]);
+ }
+
+ public void testAddFromAreaExpectFirstStreamSpeedGoingToZeroFirst() {
+ mData = new TimelineData(2, 4);
+ assertEquals(0, mData.size());
+ long start = mData.getStartTime();
+ mData.addFromArea(start + 1000, TYPE_DATA, 500.0f, 100.0f);
+ mData.addFromArea(start + 2000, TYPE_DATA, 250.0f, 75.0f);
+ assertEquals(4, mData.size());
+ TimelineData.Sample sample = mData.get(1);
+ assertEquals(1.5f, sample.time);
+ assertEquals(0.0f, sample.values[0]);
+ assertEquals(66.66667f, sample.values[1]);
+ sample = mData.get(2);
+ assertEquals(1.75f, sample.time);
+ assertEquals(0.0f, sample.values[0]);
+ assertEquals(0.0f, sample.values[1]);
+ sample = mData.get(3);
+ assertEquals(2.0f, sample.time);
+ assertEquals(0.0f, sample.values[0]);
+ assertEquals(0.0f, sample.values[1]);
+ }
+
+ public void testAddFromAreaExpectSecondStreamSpeedGoingToZeroFirst() {
+ mData = new TimelineData(2, 4);
+ assertEquals(0, mData.size());
+ long start = mData.getStartTime();
+ mData.addFromArea(start + 1000, TYPE_DATA, 500.0f, 100.0f);
+ mData.addFromArea(start + 2000, TYPE_DATA, 400.0f, 50.0f);
+ assertEquals(4, mData.size());
+ TimelineData.Sample sample = mData.get(1);
+ assertEquals(1.5f, sample.time);
+ assertEquals(375.0f, sample.values[0]);
+ assertEquals(0.0f, sample.values[1]);
+ sample = mData.get(2);
+ assertEquals(1.8f, sample.time);
+ assertEquals(0.0f, sample.values[0]);
+ assertEquals(0.0f, sample.values[1]);
+ sample = mData.get(3);
+ assertEquals(2.0f, sample.time);
+ assertEquals(0.0f, sample.values[0]);
+ assertEquals(0.0f, sample.values[1]);
+ }
+
+ public void testAddFromAreaWithZeroAreas() {
+ assertEquals(0, mData.size());
+ long start = mData.getStartTime();
+ mData.addFromArea(start + 1000, TYPE_DATA, 500.0f, 100.0f);
+ mData.addFromArea(start + 2000, TYPE_DATA, 0.0f, 0.0f);
+ assertEquals(2, mData.size());
+ TimelineData.Sample sample = mData.get(1);
+ assertEquals(2.0f, sample.time);
+ assertEquals(0.0f, sample.values[0]);
+ assertEquals(0.0f, sample.values[1]);
+ }
+}
diff --git a/base/common/.classpath b/common/.classpath
similarity index 100%
rename from base/common/.classpath
rename to common/.classpath
diff --git a/base/common/.gitignore b/common/.gitignore
similarity index 100%
rename from base/common/.gitignore
rename to common/.gitignore
diff --git a/base/common/.project b/common/.project
similarity index 100%
rename from base/common/.project
rename to common/.project
diff --git a/base/common/.settings/org.eclipse.jdt.core.prefs b/common/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/common/.settings/org.eclipse.jdt.core.prefs
rename to common/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/common/.settings/org.moreunit.prefs b/common/.settings/org.moreunit.prefs
similarity index 100%
rename from base/common/.settings/org.moreunit.prefs
rename to common/.settings/org.moreunit.prefs
diff --git a/base/common/NOTICE b/common/NOTICE
similarity index 100%
rename from base/common/NOTICE
rename to common/NOTICE
diff --git a/base/common/README.txt b/common/README.txt
similarity index 100%
rename from base/common/README.txt
rename to common/README.txt
diff --git a/common/build.gradle b/common/build.gradle
new file mode 100644
index 0000000..4a47944
--- /dev/null
+++ b/common/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'java'
+apply plugin: 'jacoco'
+apply plugin: 'sdk-java-lib'
+
+dependencies {
+ compile project(':base:annotations')
+ compile 'com.google.guava:guava:17.0'
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'com.google.truth:truth:0.28'
+}
+
+group = 'com.android.tools'
+archivesBaseName = 'common'
+version = rootProject.ext.baseVersion
+
+project.ext.pomName = 'Android Tools common library'
+project.ext.pomDesc = 'common library used by other Android tools libraries.'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
diff --git a/common/common.iml b/common/common.iml
new file mode 100644
index 0000000..7ca21ec
--- /dev/null
+++ b/common/common.iml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="library" exported="" name="guava-tools" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="android-annotations" exported="" />
+ <orderEntry type="library" scope="TEST" name="truth" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/common/src/main/java/com/android/SdkConstants.java b/common/src/main/java/com/android/SdkConstants.java
new file mode 100644
index 0000000..728a968
--- /dev/null
+++ b/common/src/main/java/com/android/SdkConstants.java
@@ -0,0 +1,1484 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.io.File;
+
+/**
+ * Constant definition class.<br>
+ * <br>
+ * Most constants have a prefix defining the content.
+ * <ul>
+ * <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
+ * <li><code>FN_</code> File name constant.</li>
+ * <li><code>FD_</code> Folder name constant.</li>
+ * <li><code>TAG_</code> XML element tag name</li>
+ * <li><code>ATTR_</code> XML attribute name</li>
+ * <li><code>VALUE_</code> XML attribute value</li>
+ * <li><code>CLASS_</code> Class name</li>
+ * <li><code>DOT_</code> File name extension, including the dot </li>
+ * <li><code>EXT_</code> File name extension, without the dot </li>
+ * </ul>
+ */
+ at SuppressWarnings({"javadoc", "unused"}) // Not documenting all the fields here
+public final class SdkConstants {
+ public static final int PLATFORM_UNKNOWN = 0;
+ public static final int PLATFORM_LINUX = 1;
+ public static final int PLATFORM_WINDOWS = 2;
+ public static final int PLATFORM_DARWIN = 3;
+
+ /**
+ * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
+ * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
+ */
+ public static final int CURRENT_PLATFORM = currentPlatform();
+
+ /** Environment variable that specifies the path of an Android SDK. */
+ public static final String ANDROID_HOME_ENV = "ANDROID_HOME";
+
+ /** Property in local.properties file that specifies the path of the Android SDK. */
+ public static final String SDK_DIR_PROPERTY = "sdk.dir";
+
+ /** Property in local.properties file that specifies the path of the Android NDK. */
+ public static final String NDK_DIR_PROPERTY = "ndk.dir";
+
+ /** Property in gradle-wrapper.properties file that specifies the URL to the correct Gradle distribution. */
+ public static final String GRADLE_DISTRIBUTION_URL_PROPERTY = "distributionUrl"; //$NON-NLS-1$
+
+ /**
+ * The encoding we strive to use for all files we write.
+ * <p>
+ * When possible, use the APIs which take a {@link java.nio.charset.Charset} and pass in
+ * {@link com.google.common.base.Charsets#UTF_8} instead of using the String encoding
+ * method.
+ */
+ public static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
+
+ /**
+ * Charset for the ini file handled by the SDK.
+ */
+ public static final String INI_CHARSET = UTF_8;
+
+ /** Path separator used by Gradle */
+ public static final String GRADLE_PATH_SEPARATOR = ":"; //$NON-NLS-1$
+
+ /** An SDK Project's AndroidManifest.xml file */
+ public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml"; //$NON-NLS-1$
+ /** pre-dex jar filename. i.e. "classes.jar" */
+ public static final String FN_CLASSES_JAR = "classes.jar"; //$NON-NLS-1$
+ /** Dex filename inside the APK. i.e. "classes.dex" */
+ public static final String FN_APK_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
+ /** Dex filename inside the APK. i.e. "classes.dex" */
+ public static final String FN_APK_CLASSES_N_DEX = "classes%d.dex"; //$NON-NLS-1$
+
+ /** An SDK Project's build.xml file */
+ public static final String FN_BUILD_XML = "build.xml"; //$NON-NLS-1$
+ /** An SDK Project's build.gradle file */
+ public static final String FN_BUILD_GRADLE = "build.gradle"; //$NON-NLS-1$
+ /** An SDK Project's settings.gradle file */
+ public static final String FN_SETTINGS_GRADLE = "settings.gradle"; //$NON-NLS-1$
+ /** An SDK Project's gradle.properties file */
+ public static final String FN_GRADLE_PROPERTIES = "gradle.properties"; //$NON-NLS-1$
+ /** An SDK Project's gradle daemon executable */
+ public static final String FN_GRADLE_UNIX = "gradle"; //$NON-NLS-1$
+ /** An SDK Project's gradle.bat daemon executable (gradle for windows) */
+ public static final String FN_GRADLE_WIN = FN_GRADLE_UNIX + ".bat"; //$NON-NLS-1$
+ /** An SDK Project's gradlew file */
+ public static final String FN_GRADLE_WRAPPER_UNIX = "gradlew"; //$NON-NLS-1$
+ /** An SDK Project's gradlew.bat file (gradlew for windows) */
+ public static final String FN_GRADLE_WRAPPER_WIN = FN_GRADLE_WRAPPER_UNIX + ".bat"; //$NON-NLS-1$
+ /** An SDK Project's gradle wrapper library */
+ public static final String FN_GRADLE_WRAPPER_JAR = "gradle-wrapper.jar"; //$NON-NLS-1$
+ /** Name of the framework library, i.e. "android.jar" */
+ public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; //$NON-NLS-1$
+ /** Name of the framework library, i.e. "uiautomator.jar" */
+ public static final String FN_UI_AUTOMATOR_LIBRARY = "uiautomator.jar"; //$NON-NLS-1$
+ /** Name of the layout attributes, i.e. "attrs.xml" */
+ public static final String FN_ATTRS_XML = "attrs.xml"; //$NON-NLS-1$
+ /** Name of the layout attributes, i.e. "attrs_manifest.xml" */
+ public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml"; //$NON-NLS-1$
+ /** framework aidl import file */
+ public static final String FN_FRAMEWORK_AIDL = "framework.aidl"; //$NON-NLS-1$
+ /** framework renderscript folder */
+ public static final String FN_FRAMEWORK_RENDERSCRIPT = "renderscript"; //$NON-NLS-1$
+ /** framework include folder */
+ public static final String FN_FRAMEWORK_INCLUDE = "include"; //$NON-NLS-1$
+ /** framework include (clang) folder */
+ public static final String FN_FRAMEWORK_INCLUDE_CLANG = "clang-include"; //$NON-NLS-1$
+ /** layoutlib.jar file */
+ public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar"; //$NON-NLS-1$
+ /** widget list file */
+ public static final String FN_WIDGETS = "widgets.txt"; //$NON-NLS-1$
+ /** Intent activity actions list file */
+ public static final String FN_INTENT_ACTIONS_ACTIVITY = "activity_actions.txt"; //$NON-NLS-1$
+ /** Intent broadcast actions list file */
+ public static final String FN_INTENT_ACTIONS_BROADCAST = "broadcast_actions.txt"; //$NON-NLS-1$
+ /** Intent service actions list file */
+ public static final String FN_INTENT_ACTIONS_SERVICE = "service_actions.txt"; //$NON-NLS-1$
+ /** Intent category list file */
+ public static final String FN_INTENT_CATEGORIES = "categories.txt"; //$NON-NLS-1$
+
+ /** annotations support jar */
+ public static final String FN_ANNOTATIONS_JAR = "annotations.jar"; //$NON-NLS-1$
+
+ /** platform build property file */
+ public static final String FN_BUILD_PROP = "build.prop"; //$NON-NLS-1$
+ /** plugin properties file */
+ public static final String FN_PLUGIN_PROP = "plugin.prop"; //$NON-NLS-1$
+ /** add-on manifest file */
+ public static final String FN_MANIFEST_INI = "manifest.ini"; //$NON-NLS-1$
+ /** add-on layout device XML file. */
+ public static final String FN_DEVICES_XML = "devices.xml"; //$NON-NLS-1$
+ /** hardware properties definition file */
+ public static final String FN_HARDWARE_INI = "hardware-properties.ini"; //$NON-NLS-1$
+
+ /** project property file */
+ public static final String FN_PROJECT_PROPERTIES = "project.properties"; //$NON-NLS-1$
+
+ /** project local property file */
+ public static final String FN_LOCAL_PROPERTIES = "local.properties"; //$NON-NLS-1$
+
+ /** project ant property file */
+ public static final String FN_ANT_PROPERTIES = "ant.properties"; //$NON-NLS-1$
+
+ /** project local property file */
+ public static final String FN_GRADLE_WRAPPER_PROPERTIES = "gradle-wrapper.properties"; //$NON-NLS-1$
+
+ /** Skin layout file */
+ public static final String FN_SKIN_LAYOUT = "layout"; //$NON-NLS-1$
+
+ /** dx.jar file */
+ public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
+
+ /** dx executable (with extension for the current OS) */
+ public static final String FN_DX =
+ "dx" + ext(".bat", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** aapt executable (with extension for the current OS) */
+ public static final String FN_AAPT =
+ "aapt" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** aidl executable (with extension for the current OS) */
+ public static final String FN_AIDL =
+ "aidl" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** renderscript executable (with extension for the current OS) */
+ public static final String FN_RENDERSCRIPT =
+ "llvm-rs-cc" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** renderscript support exe (with extension for the current OS) */
+ public static final String FN_BCC_COMPAT =
+ "bcc_compat" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** renderscript support linker for ARM (with extension for the current OS) */
+ public static final String FN_LD_ARM =
+ "arm-linux-androideabi-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** renderscript support linker for X86 (with extension for the current OS) */
+ public static final String FN_LD_X86 =
+ "i686-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** renderscript support linker for MIPS (with extension for the current OS) */
+ public static final String FN_LD_MIPS =
+ "mipsel-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** adb executable (with extension for the current OS) */
+ public static final String FN_ADB =
+ "adb" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** emulator executable for the current OS */
+ public static final String FN_EMULATOR =
+ "emulator" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** emulator-check executable for the current OS */
+ public static final String FN_EMULATOR_CHECK =
+ "emulator-check" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** zipalign executable (with extension for the current OS) */
+ public static final String FN_ZIPALIGN =
+ "zipalign" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** dexdump executable (with extension for the current OS) */
+ public static final String FN_DEXDUMP =
+ "dexdump" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** proguard executable (with extension for the current OS) */
+ public static final String FN_PROGUARD =
+ "proguard" + ext(".bat", ".sh"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** find_lock for Windows (with extension for the current OS) */
+ public static final String FN_FIND_LOCK =
+ "find_lock" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** hprof-conv executable (with extension for the current OS) */
+ public static final String FN_HPROF_CONV =
+ "hprof-conv" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** jack.jar */
+ public static final String FN_JACK = "jack.jar"; //$NON-NLS-1$
+ /** jill.jar */
+ public static final String FN_JILL = "jill.jar"; //$NON-NLS-1$
+
+ /** split-select */
+ public static final String FN_SPLIT_SELECT = "split-select" + ext(".exe", "");
+
+
+ /** properties file for SDK Updater packages */
+ public static final String FN_SOURCE_PROP = "source.properties"; //$NON-NLS-1$
+ /** properties file for content hash of installed packages */
+ public static final String FN_CONTENT_HASH_PROP = "content_hash.properties"; //$NON-NLS-1$
+ /** properties file for the SDK */
+ public static final String FN_SDK_PROP = "sdk.properties"; //$NON-NLS-1$
+
+
+ public static final String FN_RENDERSCRIPT_V8_JAR = "renderscript-v8.jar"; //$NON-NLS-1$
+
+ /**
+ * filename for gdbserver.
+ */
+ public static final String FN_GDBSERVER = "gdbserver"; //$NON-NLS-1$
+ public static final String FN_GDB_SETUP = "gdb.setup"; //$NON-NLS-1$
+
+ /** global Android proguard config file */
+ public static final String FN_ANDROID_PROGUARD_FILE = "proguard-android.txt"; //$NON-NLS-1$
+ /** global Android proguard config file with optimization enabled */
+ public static final String FN_ANDROID_OPT_PROGUARD_FILE = "proguard-android-optimize.txt"; //$NON-NLS-1$
+ /** default proguard config file with new file extension (for project specific stuff) */
+ public static final String FN_PROJECT_PROGUARD_FILE = "proguard-project.txt"; //$NON-NLS-1$
+
+ /* Folder Names for Android Projects . */
+
+ /** Resources folder name, i.e. "res". */
+ public static final String FD_RESOURCES = "res"; //$NON-NLS-1$
+ /** Assets folder name, i.e. "assets" */
+ public static final String FD_ASSETS = "assets"; //$NON-NLS-1$
+ /** Default source folder name in an SDK project, i.e. "src".
+ * <p/>
+ * Note: this is not the same as {@link #FD_PKG_SOURCES}
+ * which is an SDK sources folder for packages. */
+ public static final String FD_SOURCES = "src"; //$NON-NLS-1$
+ /** Default main source set folder name, i.e. "main" */
+ public static final String FD_MAIN = "main"; //$NON-NLS-1$
+ /** Default test source set folder name, i.e. "androidTest" */
+ public static final String FD_TEST = "androidTest"; //$NON-NLS-1$
+ /** Default java code folder name, i.e. "java" */
+ public static final String FD_JAVA = "java"; //$NON-NLS-1$
+ /** Default native code folder name, i.e. "jni" */
+ public static final String FD_JNI = "jni"; //$NON-NLS-1$
+ /** Default gradle folder name, i.e. "gradle" */
+ public static final String FD_GRADLE = "gradle"; //$NON-NLS-1$
+ /** Default gradle wrapper folder name, i.e. "gradle/wrapper" */
+ public static final String FD_GRADLE_WRAPPER = FD_GRADLE + File.separator + "wrapper"; //$NON-NLS-1$
+ /** Default generated source folder name, i.e. "gen" */
+ public static final String FD_GEN_SOURCES = "gen"; //$NON-NLS-1$
+ /** Default native library folder name inside the project, i.e. "libs"
+ * While the folder inside the .apk is "lib", we call that one libs because
+ * that's what we use in ant for both .jar and .so and we need to make the 2 development ways
+ * compatible. */
+ public static final String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$
+ /** Native lib folder inside the APK: "lib" */
+ public static final String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
+ /** Default output folder name, i.e. "bin" */
+ public static final String FD_OUTPUT = "bin"; //$NON-NLS-1$
+ /** Classes output folder name, i.e. "classes" */
+ public static final String FD_CLASSES_OUTPUT = "classes"; //$NON-NLS-1$
+ /** proguard output folder for mapping, etc.. files */
+ public static final String FD_PROGUARD = "proguard"; //$NON-NLS-1$
+ /** aidl output folder for copied aidl files */
+ public static final String FD_AIDL = "aidl"; //$NON-NLS-1$
+
+ /** rs Libs output folder for support mode */
+ public static final String FD_RS_LIBS = "rsLibs"; //$NON-NLS-1$
+ /** rs Libs output folder for support mode */
+ public static final String FD_RS_OBJ = "rsObj"; //$NON-NLS-1$
+
+ /** jars folder */
+ public static final String FD_JARS = "jars"; //$NON-NLS-1$
+
+ /* Folder Names for the Android SDK */
+
+ /** Name of the SDK platforms folder. */
+ public static final String FD_PLATFORMS = "platforms"; //$NON-NLS-1$
+ /** Name of the SDK addons folder. */
+ public static final String FD_ADDONS = "add-ons"; //$NON-NLS-1$
+ /** Name of the SDK system-images folder. */
+ public static final String FD_SYSTEM_IMAGES = "system-images"; //$NON-NLS-1$
+ /** Name of the SDK sources folder where source packages are installed.
+ * <p/>
+ * Note this is not the same as {@link #FD_SOURCES} which is the folder name where sources
+ * are installed inside a project. */
+ public static final String FD_PKG_SOURCES = "sources"; //$NON-NLS-1$
+ /** Name of the SDK tools folder. */
+ public static final String FD_TOOLS = "tools"; //$NON-NLS-1$
+ /** Name of the SDK tools/support folder. */
+ public static final String FD_SUPPORT = "support"; //$NON-NLS-1$
+ /** Name of the SDK platform tools folder. */
+ public static final String FD_PLATFORM_TOOLS = "platform-tools"; //$NON-NLS-1$
+ /** Name of the SDK build tools folder. */
+ public static final String FD_BUILD_TOOLS = "build-tools"; //$NON-NLS-1$
+ /** Name of the SDK tools/lib folder. */
+ public static final String FD_LIB = "lib"; //$NON-NLS-1$
+ /** Name of the SDK docs folder. */
+ public static final String FD_DOCS = "docs"; //$NON-NLS-1$
+ /** Name of the doc folder containing API reference doc (javadoc) */
+ public static final String FD_DOCS_REFERENCE = "reference"; //$NON-NLS-1$
+ /** Name of the SDK images folder. */
+ public static final String FD_IMAGES = "images"; //$NON-NLS-1$
+ /** Name of the ABI to support. */
+ public static final String ABI_ARMEABI = "armeabi"; //$NON-NLS-1$
+ public static final String ABI_ARMEABI_V7A = "armeabi-v7a"; //$NON-NLS-1$
+ public static final String ABI_ARM64_V8A = "arm64-v8a"; //$NON-NLS-1$
+ public static final String ABI_INTEL_ATOM = "x86"; //$NON-NLS-1$
+ public static final String ABI_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$
+ public static final String ABI_MIPS = "mips"; //$NON-NLS-1$
+ public static final String ABI_MIPS64 = "mips64"; //$NON-NLS-1$
+ /** Name of the CPU arch to support. */
+ public static final String CPU_ARCH_ARM = "arm"; //$NON-NLS-1$
+ public static final String CPU_ARCH_ARM64 = "arm64"; //$NON-NLS-1$
+ public static final String CPU_ARCH_INTEL_ATOM = "x86"; //$NON-NLS-1$
+ public static final String CPU_ARCH_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$
+ public static final String CPU_ARCH_MIPS = "mips"; //$NON-NLS-1$
+ /** TODO double-check this is appropriate value for mips64 */
+ public static final String CPU_ARCH_MIPS64 = "mips64"; //$NON-NLS-1$
+ /** Name of the CPU model to support. */
+ public static final String CPU_MODEL_CORTEX_A8 = "cortex-a8"; //$NON-NLS-1$
+
+ /** Name of the SDK skins folder. */
+ public static final String FD_SKINS = "skins"; //$NON-NLS-1$
+ /** Name of the SDK samples folder. */
+ public static final String FD_SAMPLES = "samples"; //$NON-NLS-1$
+ /** Name of the SDK extras folder. */
+ public static final String FD_EXTRAS = "extras"; //$NON-NLS-1$
+ public static final String FD_M2_REPOSITORY = "m2repository"; //$NON-NLS-1$
+ public static final String FD_NDK = "ndk-bundle"; //$NON-NLS-1$
+ public static final String FD_LLDB = "lldb"; //$NON-NLS-1$
+ /**
+ * Name of an extra's sample folder.
+ * Ideally extras should have one {@link #FD_SAMPLES} folder containing
+ * one or more sub-folders (one per sample). However some older extras
+ * might contain a single "sample" folder with directly the samples files
+ * in it. When possible we should encourage extras' owners to move to the
+ * multi-samples format.
+ */
+ public static final String FD_SAMPLE = "sample"; //$NON-NLS-1$
+ /** Name of the SDK templates folder, i.e. "templates" */
+ public static final String FD_TEMPLATES = "templates"; //$NON-NLS-1$
+ /** Name of the SDK Ant folder, i.e. "ant" */
+ public static final String FD_ANT = "ant"; //$NON-NLS-1$
+ /** Name of the SDK data folder, i.e. "data" */
+ public static final String FD_DATA = "data"; //$NON-NLS-1$
+ /** Name of the SDK renderscript folder, i.e. "rs" */
+ public static final String FD_RENDERSCRIPT = "rs"; //$NON-NLS-1$
+ /** Name of the Java resources folder, i.e. "resources" */
+ public static final String FD_JAVA_RES = "resources"; //$NON-NLS-1$
+ /** Name of the SDK resources folder, i.e. "res" */
+ public static final String FD_RES = "res"; //$NON-NLS-1$
+ /** Name of the SDK font folder, i.e. "fonts" */
+ public static final String FD_FONTS = "fonts"; //$NON-NLS-1$
+ /** Name of the android sources directory and the root of the SDK sources package folder. */
+ public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
+ /** Name of the addon libs folder. */
+ public static final String FD_ADDON_LIBS = "libs"; //$NON-NLS-1$
+
+ /** Name of the cache folder in the $HOME/.android. */
+ public static final String FD_CACHE = "cache"; //$NON-NLS-1$
+
+ /** API codename of a release (non preview) system image or platform. **/
+ public static final String CODENAME_RELEASE = "REL"; //$NON-NLS-1$
+
+ /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
+ public static final String NS_RESOURCES =
+ "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
+
+ /**
+ * Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s"
+ * <p/>
+ * This string contains a %s. It must be combined with the desired Java package, e.g.:
+ * <pre>
+ * String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "android");
+ * String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "com.test.mycustomapp");
+ * </pre>
+ *
+ * Note: if you need an URI specifically for the "android" namespace, consider using
+ * {@link SdkConstants#NS_RESOURCES} instead.
+ */
+ public static final String NS_CUSTOM_RESOURCES_S = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$
+
+
+ /** The name of the uses-library that provides "android.test.runner" */
+ public static final String ANDROID_TEST_RUNNER_LIB =
+ "android.test.runner"; //$NON-NLS-1$
+
+ /* Folder path relative to the SDK root */
+ /** Path of the documentation directory relative to the sdk folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator;
+
+ /** Path of the tools directory relative to the sdk folder, or to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator;
+
+ /** Path of the lib directory relative to the sdk folder, or to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_TOOLS_LIB_FOLDER =
+ OS_SDK_TOOLS_FOLDER + FD_LIB + File.separator;
+
+ /**
+ * Path of the lib directory relative to the sdk folder, or to a platform
+ * folder. This is an OS path, ending with a separator.
+ */
+ public static final String OS_SDK_TOOLS_LIB_EMULATOR_FOLDER = OS_SDK_TOOLS_LIB_FOLDER
+ + "emulator" + File.separator; //$NON-NLS-1$
+
+ /** Path of the platform tools directory relative to the sdk folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_PLATFORM_TOOLS_FOLDER = FD_PLATFORM_TOOLS + File.separator;
+
+ /** Path of the build tools directory relative to the sdk folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_BUILD_TOOLS_FOLDER = FD_BUILD_TOOLS + File.separator;
+
+ /** Path of the Platform tools Lib directory relative to the sdk folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_PLATFORM_TOOLS_LIB_FOLDER =
+ OS_SDK_PLATFORM_TOOLS_FOLDER + FD_LIB + File.separator;
+
+ /** Path of the bin folder of proguard folder relative to the sdk folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_TOOLS_PROGUARD_BIN_FOLDER =
+ OS_SDK_TOOLS_FOLDER +
+ "proguard" + File.separator + //$NON-NLS-1$
+ "bin" + File.separator; //$NON-NLS-1$
+
+ /** Path of the template gradle wrapper folder relative to the sdk folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SDK_TOOLS_TEMPLATES_GRADLE_WRAPPER_FOLDER =
+ OS_SDK_TOOLS_FOLDER + FD_TEMPLATES + File.separator + FD_GRADLE_WRAPPER + File.separator;
+
+ /* Folder paths relative to a platform or add-on folder */
+
+ /** Path of the images directory relative to a platform or addon folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_IMAGES_FOLDER = FD_IMAGES + File.separator;
+
+ /** Path of the skin directory relative to a platform or addon folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_SKINS_FOLDER = FD_SKINS + File.separator;
+
+ /* Folder paths relative to a Platform folder */
+
+ /** Path of the data directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_DATA_FOLDER = FD_DATA + File.separator;
+
+ /** Path of the renderscript directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_RENDERSCRIPT_FOLDER = FD_RENDERSCRIPT + File.separator;
+
+
+ /** Path of the samples directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_SAMPLES_FOLDER = FD_SAMPLES + File.separator;
+
+ /** Path of the resources directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_RESOURCES_FOLDER =
+ OS_PLATFORM_DATA_FOLDER + FD_RES + File.separator;
+
+ /** Path of the fonts directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_FONTS_FOLDER =
+ OS_PLATFORM_DATA_FOLDER + FD_FONTS + File.separator;
+
+ /** Path of the android source directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_SOURCES_FOLDER = FD_ANDROID_SOURCES + File.separator;
+
+ /** Path of the android templates directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_TEMPLATES_FOLDER = FD_TEMPLATES + File.separator;
+
+ /** Path of the Ant build rules directory relative to a platform folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_PLATFORM_ANT_FOLDER = FD_ANT + File.separator;
+
+ /** Path of the attrs.xml file relative to a platform folder. */
+ public static final String OS_PLATFORM_ATTRS_XML =
+ OS_PLATFORM_RESOURCES_FOLDER + SdkConstants.FD_RES_VALUES + File.separator +
+ FN_ATTRS_XML;
+
+ /** Path of the attrs_manifest.xml file relative to a platform folder. */
+ public static final String OS_PLATFORM_ATTRS_MANIFEST_XML =
+ OS_PLATFORM_RESOURCES_FOLDER + SdkConstants.FD_RES_VALUES + File.separator +
+ FN_ATTRS_MANIFEST_XML;
+
+ /** Path of the layoutlib.jar file relative to a platform folder. */
+ public static final String OS_PLATFORM_LAYOUTLIB_JAR =
+ OS_PLATFORM_DATA_FOLDER + FN_LAYOUTLIB_JAR;
+
+ /** Path of the renderscript include folder relative to a platform folder. */
+ public static final String OS_FRAMEWORK_RS =
+ FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE;
+ /** Path of the renderscript (clang) include folder relative to a platform folder. */
+ public static final String OS_FRAMEWORK_RS_CLANG =
+ FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE_CLANG;
+
+ /* Folder paths relative to a addon folder */
+ /** Path of the images directory relative to a folder folder.
+ * This is an OS path, ending with a separator. */
+ public static final String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator;
+
+ /** Skin default **/
+ public static final String SKIN_DEFAULT = "default"; //$NON-NLS-1$
+
+ /** SDK property: ant templates revision */
+ public static final String PROP_SDK_ANT_TEMPLATES_REVISION =
+ "sdk.ant.templates.revision"; //$NON-NLS-1$
+
+ /** SDK property: default skin */
+ public static final String PROP_SDK_DEFAULT_SKIN = "sdk.skin.default"; //$NON-NLS-1$
+
+ /* Android Class Constants */
+ public static final String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
+ public static final String CLASS_APPLICATION = "android.app.Application"; //$NON-NLS-1$
+ public static final String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$
+ public static final String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
+ public static final String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
+ public static final String CLASS_ATTRIBUTE_SET = "android.util.AttributeSet"; //$NON-NLS-1$
+ public static final String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
+ public static final String CLASS_INSTRUMENTATION_RUNNER =
+ "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
+ public static final String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
+ public static final String CLASS_R = "android.R"; //$NON-NLS-1$
+ public static final String CLASS_R_PREFIX = CLASS_R + "."; //$NON-NLS-1$
+ public static final String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
+ public static final String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
+ public static final String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
+ public static final String CLASS_RESOURCES = "android.content.res.Resources"; //$NON-NLS-1$
+ public static final String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
+ public static final String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
+ public static final String CLASS_NAME_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
+ public static final String CLASS_VIEWGROUP_LAYOUTPARAMS =
+ CLASS_VIEWGROUP + "$" + CLASS_NAME_LAYOUTPARAMS; //$NON-NLS-1$
+ public static final String CLASS_NAME_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$
+ public static final String CLASS_FRAMELAYOUT =
+ "android.widget." + CLASS_NAME_FRAMELAYOUT; //$NON-NLS-1$
+ public static final String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$
+ public static final String CLASS_NAME_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
+ public static final String CLASS_PREFERENCES =
+ "android.preference." + CLASS_NAME_PREFERENCE_SCREEN; //$NON-NLS-1$
+ public static final String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$
+ public static final String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$
+ public static final String CLASS_PARCEL = "android.os.Parcel"; //$NON-NLS-1$
+ public static final String CLASS_FRAGMENT = "android.app.Fragment"; //$NON-NLS-1$
+ public static final String CLASS_V4_FRAGMENT = "android.support.v4.app.Fragment"; //$NON-NLS-1$
+ public static final String CLASS_ACTION_PROVIDER = "android.view.ActionProvider"; //$NON-NLS-1$
+ public static final String CLASS_BACKUP_AGENT = "android.app.backup.BackupAgent"; //$NON-NLS-1$
+ /** MockView is part of the layoutlib bridge and used to display classes that have
+ * no rendering in the graphical layout editor. */
+ public static final String CLASS_MOCK_VIEW = "com.android.layoutlib.bridge.MockView"; //$NON-NLS-1$
+ public static final String CLASS_LAYOUT_INFLATER = "android.view.LayoutInflater"; //$NON-NLS-1$
+ public static final String CLASS_DATA_BINDING_COMPONENT = "android.databinding.DataBindingComponent"; //$NON-NLS-1$
+ public static final String CLASS_NAME_DATA_BINDING_COMPONENT = "DataBindingComponent"; //$NON-NLS-1$
+ public static final String CLASS_DATA_BINDING_BASE_BINDING = "android.databinding.ViewDataBinding";
+
+ /* Android Design Support Class Constants */
+ public static final String CLASS_COORDINATOR_LAYOUT = "android.support.design.widget.CoordinatorLayout"; //$NON-NLS-1$
+ public static final String CLASS_APP_BAR_LAYOUT = "android.support.design.widget.AppBarLayout"; //$NON-NLS-1$
+ public static final String CLASS_FLOATING_ACTION_BUTTON = "android.support.design.widget.FloatingActionButton"; //$NON-NLS-1$
+ public static final String CLASS_COLLAPSING_TOOLBAR_LAYOUT = "android.support.design.widget.CollapsingToolbarLayout"; //$NON-NLS-1$
+ public static final String CLASS_NAVIGATION_VIEW = "android.support.design.widget.NavigationView"; //$NON-NLS-1$
+ public static final String CLASS_SNACKBAR = "android.support.design.widget.Snackbar"; //$NON-NLS-1$
+ public static final String CLASS_TAB_LAYOUT = "android.support.design.widget.TabLayout"; //$NON-NLS-1$
+ public static final String CLASS_TEXT_INPUT_LAYOUT = "android.support.design.widget.TextInputLayout"; //$NON-NLS-1$
+ public static final String CLASS_NESTED_SCROLL_VIEW = "android.support.v4.widget.NestedScrollView"; //$NON-NLS-1$
+
+
+ /** Returns the appropriate name for the 'android' command, which is 'android.exe' for
+ * Windows and 'android' for all other platforms. */
+ public static String androidCmdName() {
+ String os = System.getProperty("os.name"); //$NON-NLS-1$
+ String cmd = "android"; //$NON-NLS-1$
+ if (os.startsWith("Windows")) { //$NON-NLS-1$
+ cmd += ".bat"; //$NON-NLS-1$
+ }
+ return cmd;
+ }
+
+ /** Returns the appropriate name for the 'mksdcard' command, which is 'mksdcard.exe' for
+ * Windows and 'mkdsdcard' for all other platforms. */
+ public static String mkSdCardCmdName() {
+ String os = System.getProperty("os.name"); //$NON-NLS-1$
+ String cmd = "mksdcard"; //$NON-NLS-1$
+ if (os.startsWith("Windows")) { //$NON-NLS-1$
+ cmd += ".exe"; //$NON-NLS-1$
+ }
+ return cmd;
+ }
+
+ /**
+ * Returns current platform
+ *
+ * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
+ * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
+ */
+ public static int currentPlatform() {
+ String os = System.getProperty("os.name"); //$NON-NLS-1$
+ if (os.startsWith("Mac OS")) { //$NON-NLS-1$
+ return PLATFORM_DARWIN;
+ } else if (os.startsWith("Windows")) { //$NON-NLS-1$
+ return PLATFORM_WINDOWS;
+ } else if (os.startsWith("Linux")) { //$NON-NLS-1$
+ return PLATFORM_LINUX;
+ }
+
+ return PLATFORM_UNKNOWN;
+ }
+
+ /**
+ * Returns current platform's UI name
+ *
+ * @return one of "Windows", "Mac OS X", "Linux" or "other".
+ */
+ public static String currentPlatformName() {
+ String os = System.getProperty("os.name"); //$NON-NLS-1$
+ if (os.startsWith("Mac OS")) { //$NON-NLS-1$
+ return "Mac OS X"; //$NON-NLS-1$
+ } else if (os.startsWith("Windows")) { //$NON-NLS-1$
+ return "Windows"; //$NON-NLS-1$
+ } else if (os.startsWith("Linux")) { //$NON-NLS-1$
+ return "Linux"; //$NON-NLS-1$
+ }
+
+ return "Other";
+ }
+
+ private static String ext(String windowsExtension, String nonWindowsExtension) {
+ if (CURRENT_PLATFORM == PLATFORM_WINDOWS) {
+ return windowsExtension;
+ } else {
+ return nonWindowsExtension;
+ }
+ }
+
+ /** Default anim resource folder name, i.e. "anim" */
+ public static final String FD_RES_ANIM = "anim"; //$NON-NLS-1$
+ /** Default animator resource folder name, i.e. "animator" */
+ public static final String FD_RES_ANIMATOR = "animator"; //$NON-NLS-1$
+ /** Default color resource folder name, i.e. "color" */
+ public static final String FD_RES_COLOR = "color"; //$NON-NLS-1$
+ /** Default drawable resource folder name, i.e. "drawable" */
+ public static final String FD_RES_DRAWABLE = "drawable"; //$NON-NLS-1$
+ /** Default interpolator resource folder name, i.e. "interpolator" */
+ public static final String FD_RES_INTERPOLATOR = "interpolator"; //$NON-NLS-1$
+ /** Default layout resource folder name, i.e. "layout" */
+ public static final String FD_RES_LAYOUT = "layout"; //$NON-NLS-1$
+ /** Default menu resource folder name, i.e. "menu" */
+ public static final String FD_RES_MENU = "menu"; //$NON-NLS-1$
+ /** Default menu resource folder name, i.e. "mipmap" */
+ public static final String FD_RES_MIPMAP = "mipmap"; //$NON-NLS-1$
+ /** Default values resource folder name, i.e. "values" */
+ public static final String FD_RES_VALUES = "values"; //$NON-NLS-1$
+ /** Default xml resource folder name, i.e. "xml" */
+ public static final String FD_RES_XML = "xml"; //$NON-NLS-1$
+ /** Default raw resource folder name, i.e. "raw" */
+ public static final String FD_RES_RAW = "raw"; //$NON-NLS-1$
+ /** Separator between the resource folder qualifier. */
+ public static final String RES_QUALIFIER_SEP = "-"; //$NON-NLS-1$
+ /** Namespace used in XML files for Android attributes */
+
+ // ---- XML ----
+
+ /** URI of the reserved "xmlns" prefix */
+ public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; //$NON-NLS-1$
+ /** The "xmlns" attribute name */
+ public static final String XMLNS = "xmlns"; //$NON-NLS-1$
+ /** The default prefix used for the {@link #XMLNS_URI} */
+ public static final String XMLNS_PREFIX = "xmlns:"; //$NON-NLS-1$
+ /** Qualified name of the xmlns android declaration element */
+ public static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$
+ /** The default prefix used for the {@link #ANDROID_URI} name space */
+ public static final String ANDROID_NS_NAME = "android"; //$NON-NLS-1$
+ /** The default prefix used for the {@link #ANDROID_URI} name space including the colon */
+ public static final String ANDROID_NS_NAME_PREFIX = "android:"; //$NON-NLS-1$
+ public static final int ANDROID_NS_NAME_PREFIX_LEN = ANDROID_NS_NAME_PREFIX.length();
+
+ /** The default prefix used for the app */
+ public static final String APP_PREFIX = "app"; //$NON-NLS-1$
+ /** The entity for the ampersand character */
+ public static final String AMP_ENTITY = "&"; //$NON-NLS-1$
+ /** The entity for the quote character */
+ public static final String QUOT_ENTITY = """; //$NON-NLS-1$
+ /** The entity for the apostrophe character */
+ public static final String APOS_ENTITY = "'"; //$NON-NLS-1$
+ /** The entity for the less than character */
+ public static final String LT_ENTITY = "<"; //$NON-NLS-1$
+ /** The entity for the greater than character */
+ public static final String GT_ENTITY = ">"; //$NON-NLS-1$
+
+ // ---- Elements and Attributes ----
+
+ /** Namespace prefix used for all resources */
+ public static final String URI_PREFIX =
+ "http://schemas.android.com/apk/res/"; //$NON-NLS-1$
+ /** Namespace used in XML files for Android attributes */
+ public static final String ANDROID_URI =
+ "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
+ /** Namespace used in XML files for Android Tooling attributes */
+ public static final String TOOLS_URI =
+ "http://schemas.android.com/tools"; //$NON-NLS-1$
+ /** Namespace used for auto-adjusting namespaces */
+ public static final String AUTO_URI =
+ "http://schemas.android.com/apk/res-auto"; //$NON-NLS-1$
+ /** Namespace for xliff in string resources. */
+ public static final String XLIFF_URI = "urn:oasis:names:tc:xliff:document:1.2";
+ /** Default prefix used for tools attributes */
+ public static final String TOOLS_PREFIX = "tools"; //$NON-NLS-1$
+ /** Default prefix used for xliff tags. */
+ public static final String XLIFF_PREFIX = "xliff"; //$NON-NLS-1$
+ public static final String R_CLASS = "R"; //$NON-NLS-1$
+ public static final String ANDROID_PKG = "android"; //$NON-NLS-1$
+
+ // Tags: Manifest
+ public static final String TAG_SERVICE = "service"; //$NON-NLS-1$
+ public static final String TAG_PERMISSION = "permission"; //$NON-NLS-1$
+ public static final String TAG_USES_FEATURE = "uses-feature"; //$NON-NLS-1$
+ public static final String TAG_USES_PERMISSION = "uses-permission";//$NON-NLS-1$
+ public static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23";//$NON-NLS-1$
+ public static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m";//$NON-NLS-1$
+ public static final String TAG_USES_LIBRARY = "uses-library"; //$NON-NLS-1$
+ public static final String TAG_APPLICATION = "application"; //$NON-NLS-1$
+ public static final String TAG_INTENT_FILTER = "intent-filter"; //$NON-NLS-1$
+ public static final String TAG_USES_SDK = "uses-sdk"; //$NON-NLS-1$
+ public static final String TAG_ACTIVITY = "activity"; //$NON-NLS-1$
+ public static final String TAG_RECEIVER = "receiver"; //$NON-NLS-1$
+ public static final String TAG_PROVIDER = "provider"; //$NON-NLS-1$
+ public static final String TAG_GRANT_PERMISSION = "grant-uri-permission"; //$NON-NLS-1$
+ public static final String TAG_PATH_PERMISSION = "path-permission"; //$NON-NLS-1$
+
+ // Tags: Resources
+ public static final String TAG_RESOURCES = "resources"; //$NON-NLS-1$
+ public static final String TAG_STRING = "string"; //$NON-NLS-1$
+ public static final String TAG_ARRAY = "array"; //$NON-NLS-1$
+ public static final String TAG_STYLE = "style"; //$NON-NLS-1$
+ public static final String TAG_ITEM = "item"; //$NON-NLS-1$
+ public static final String TAG_GROUP = "group"; //$NON-NLS-1$
+ public static final String TAG_STRING_ARRAY = "string-array"; //$NON-NLS-1$
+ public static final String TAG_PLURALS = "plurals"; //$NON-NLS-1$
+ public static final String TAG_INTEGER_ARRAY = "integer-array"; //$NON-NLS-1$
+ public static final String TAG_COLOR = "color"; //$NON-NLS-1$
+ public static final String TAG_DIMEN = "dimen"; //$NON-NLS-1$
+ public static final String TAG_DRAWABLE = "drawable"; //$NON-NLS-1$
+ public static final String TAG_MENU = "menu"; //$NON-NLS-1$
+ public static final String TAG_ENUM = "enum"; //$NON-NLS-1$
+ public static final String TAG_FLAG = "flag"; //$NON-NLS-1$
+ public static final String TAG_ATTR = "attr"; //$NON-NLS-1$
+ public static final String TAG_DECLARE_STYLEABLE = "declare-styleable"; //$NON-NLS-1$
+ public static final String TAG_EAT_COMMENT = "eat-comment"; //$NON-NLS-1$
+ public static final String TAG_SKIP = "skip"; //$NON-NLS-1$
+ public static final String TAG_SELECTOR = "selector"; //$NON-NLS-1$
+
+ // Tags: XML
+ public static final String TAG_HEADER = "header"; //$NON-NLS-1$
+ public static final String TAG_APPWIDGET_PROVIDER = "appwidget-provider"; //$NON-NLS-1$
+ public static final String TAG_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
+
+ // Tags: Layouts
+ public static final String VIEW_TAG = "view"; //$NON-NLS-1$
+ public static final String VIEW_INCLUDE = "include"; //$NON-NLS-1$
+ public static final String VIEW_MERGE = "merge"; //$NON-NLS-1$
+ public static final String VIEW_FRAGMENT = "fragment"; //$NON-NLS-1$
+ public static final String REQUEST_FOCUS = "requestFocus"; //$NON-NLS-1$
+ public static final String TAG = "tag"; //$NON-NLS-1$
+
+ public static final String VIEW = "View"; //$NON-NLS-1$
+ public static final String VIEW_GROUP = "ViewGroup"; //$NON-NLS-1$
+ public static final String FRAME_LAYOUT = "FrameLayout"; //$NON-NLS-1$
+ public static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$
+ public static final String RELATIVE_LAYOUT = "RelativeLayout"; //$NON-NLS-1$
+ public static final String GRID_LAYOUT = "GridLayout"; //$NON-NLS-1$
+ public static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$
+ public static final String BUTTON = "Button"; //$NON-NLS-1$
+ public static final String COMPOUND_BUTTON = "CompoundButton"; //$NON-NLS-1$
+ public static final String ADAPTER_VIEW = "AdapterView"; //$NON-NLS-1$
+ public static final String GALLERY = "Gallery"; //$NON-NLS-1$
+ public static final String GRID_VIEW = "GridView"; //$NON-NLS-1$
+ public static final String TAB_HOST = "TabHost"; //$NON-NLS-1$
+ public static final String RADIO_GROUP = "RadioGroup"; //$NON-NLS-1$
+ public static final String RADIO_BUTTON = "RadioButton"; //$NON-NLS-1$
+ public static final String SWITCH = "Switch"; //$NON-NLS-1$
+ public static final String EDIT_TEXT = "EditText"; //$NON-NLS-1$
+ public static final String LIST_VIEW = "ListView"; //$NON-NLS-1$
+ public static final String TEXT_VIEW = "TextView"; //$NON-NLS-1$
+ public static final String CHECKED_TEXT_VIEW = "CheckedTextView"; //$NON-NLS-1$
+ public static final String IMAGE_VIEW = "ImageView"; //$NON-NLS-1$
+ public static final String SURFACE_VIEW = "SurfaceView"; //$NON-NLS-1$
+ public static final String ABSOLUTE_LAYOUT = "AbsoluteLayout"; //$NON-NLS-1$
+ public static final String TABLE_LAYOUT = "TableLayout"; //$NON-NLS-1$
+ public static final String TABLE_ROW = "TableRow"; //$NON-NLS-1$
+ public static final String TAB_WIDGET = "TabWidget"; //$NON-NLS-1$
+ public static final String IMAGE_BUTTON = "ImageButton"; //$NON-NLS-1$
+ public static final String SEEK_BAR = "SeekBar"; //$NON-NLS-1$
+ public static final String VIEW_STUB = "ViewStub"; //$NON-NLS-1$
+ public static final String SPINNER = "Spinner"; //$NON-NLS-1$
+ public static final String WEB_VIEW = "WebView"; //$NON-NLS-1$
+ public static final String TOGGLE_BUTTON = "ToggleButton"; //$NON-NLS-1$
+ public static final String CHECK_BOX = "CheckBox"; //$NON-NLS-1$
+ public static final String ABS_LIST_VIEW = "AbsListView"; //$NON-NLS-1$
+ public static final String PROGRESS_BAR = "ProgressBar"; //$NON-NLS-1$
+ public static final String ABS_SPINNER = "AbsSpinner"; //$NON-NLS-1$
+ public static final String ABS_SEEK_BAR = "AbsSeekBar"; //$NON-NLS-1$
+ public static final String VIEW_ANIMATOR = "ViewAnimator"; //$NON-NLS-1$
+ public static final String VIEW_SWITCHER = "ViewSwitcher"; //$NON-NLS-1$
+ public static final String EXPANDABLE_LIST_VIEW = "ExpandableListView"; //$NON-NLS-1$
+ public static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$
+ public static final String MULTI_AUTO_COMPLETE_TEXT_VIEW = "MultiAutoCompleteTextView"; //$NON-NLS-1$
+ public static final String AUTO_COMPLETE_TEXT_VIEW = "AutoCompleteTextView"; //$NON-NLS-1$
+ public static final String CHECKABLE = "Checkable"; //$NON-NLS-1$
+ public static final String TEXTURE_VIEW = "TextureView"; //$NON-NLS-1$
+
+ /* Android Design Support Tag Constants */
+ public static final String COORDINATOR_LAYOUT = CLASS_COORDINATOR_LAYOUT;
+ public static final String APP_BAR_LAYOUT = CLASS_APP_BAR_LAYOUT;
+ public static final String FLOATING_ACTION_BUTTON = CLASS_FLOATING_ACTION_BUTTON;
+ public static final String COLLAPSING_TOOLBAR_LAYOUT = CLASS_COLLAPSING_TOOLBAR_LAYOUT;
+ public static final String NAVIGATION_VIEW = CLASS_NAVIGATION_VIEW;
+ public static final String SNACKBAR = CLASS_SNACKBAR;
+ public static final String TAB_LAYOUT = CLASS_TAB_LAYOUT;
+ public static final String TEXT_INPUT_LAYOUT = CLASS_TEXT_INPUT_LAYOUT;
+
+ // Tags: Drawables
+ public static final String TAG_BITMAP = "bitmap"; //$NON-NLS-1$
+
+ // Tags: Data-Binding
+ public static final String TAG_LAYOUT = "layout"; //$NON-NLS-1$
+ public static final String TAG_DATA = "data"; //$NON-NLS-1$
+ public static final String TAG_VARIABLE = "variable"; //$NON-NLS-1$
+ public static final String TAG_IMPORT = "import"; //$NON-NLS-1$
+
+ // Attributes: Manifest
+ public static final String ATTR_EXPORTED = "exported"; //$NON-NLS-1$
+ public static final String ATTR_PERMISSION = "permission"; //$NON-NLS-1$
+ public static final String ATTR_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-1$
+ public static final String ATTR_TARGET_SDK_VERSION = "targetSdkVersion"; //$NON-NLS-1$
+ public static final String ATTR_ICON = "icon"; //$NON-NLS-1$
+ public static final String ATTR_PACKAGE = "package"; //$NON-NLS-1$
+ public static final String ATTR_CORE_APP = "coreApp"; //$NON-NLS-1$
+ public static final String ATTR_THEME = "theme"; //$NON-NLS-1$
+ public static final String ATTR_SCHEME = "scheme"; //$NON_NLS-1$
+ public static final String ATTR_HOST = "host"; //$NON_NLS-1$
+ public static final String ATTR_PATH = "path"; //$NON-NLS-1$
+ public static final String ATTR_PATH_PREFIX = "pathPrefix"; //$NON-NLS-1$
+ public static final String ATTR_PATH_PATTERN = "pathPattern"; //$NON-NLS-1$
+ public static final String ATTR_ALLOW_BACKUP = "allowBackup"; //$NON_NLS-1$
+ public static final String ATTR_DEBUGGABLE = "debuggable"; //$NON-NLS-1$
+ public static final String ATTR_READ_PERMISSION = "readPermission"; //$NON_NLS-1$
+ public static final String ATTR_WRITE_PERMISSION = "writePermission"; //$NON_NLS-1$
+ public static final String ATTR_VERSION_CODE = "versionCode"; //$NON_NLS-1$
+ public static final String ATTR_VERSION_NAME = "versionName"; //$NON_NLS-1$
+ public static final String ATTR_FULL_BACKUP_CONTENT = "fullBackupContent"; //$NON_NLS-1$
+
+ // Attributes: Resources
+ public static final String ATTR_NAME = "name"; //$NON-NLS-1$
+ public static final String ATTR_FRAGMENT = "fragment"; //$NON-NLS-1$
+ public static final String ATTR_TYPE = "type"; //$NON-NLS-1$
+ public static final String ATTR_PARENT = "parent"; //$NON-NLS-1$
+ public static final String ATTR_TRANSLATABLE = "translatable"; //$NON-NLS-1$
+ public static final String ATTR_COLOR = "color"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE = "drawable"; //$NON-NLS-1$
+ public static final String ATTR_VALUE = "value"; //$NON-NLS-1$
+ public static final String ATTR_QUANTITY = "quantity"; //$NON-NLS-1$
+ public static final String ATTR_FORMAT = "format"; //$NON-NLS-1$
+ public static final String ATTR_PREPROCESSING = "preprocessing"; //$NON-NLS-1$
+
+ // Attributes: Data-Binding
+ public static final String ATTR_ALIAS = "alias"; //$NON-NLS-1$
+
+ // Attributes: Layout
+ public static final String ATTR_LAYOUT_RESOURCE_PREFIX = "layout_";//$NON-NLS-1$
+ public static final String ATTR_CLASS = "class"; //$NON-NLS-1$
+ public static final String ATTR_STYLE = "style"; //$NON-NLS-1$
+ public static final String ATTR_CONTEXT = "context"; //$NON-NLS-1$
+ public static final String ATTR_ID = "id"; //$NON-NLS-1$
+ public static final String ATTR_TEXT = "text"; //$NON-NLS-1$
+ public static final String ATTR_TEXT_SIZE = "textSize"; //$NON-NLS-1$
+ public static final String ATTR_LABEL = "label"; //$NON-NLS-1$
+ public static final String ATTR_HINT = "hint"; //$NON-NLS-1$
+ public static final String ATTR_PROMPT = "prompt"; //$NON-NLS-1$
+ public static final String ATTR_ON_CLICK = "onClick"; //$NON-NLS-1$
+ public static final String ATTR_INPUT_TYPE = "inputType"; //$NON-NLS-1$
+ public static final String ATTR_INPUT_METHOD = "inputMethod"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_WIDTH = "layout_width"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_WEIGHT = "layout_weight"; //$NON-NLS-1$
+ public static final String ATTR_PADDING = "padding"; //$NON-NLS-1$
+ public static final String ATTR_PADDING_BOTTOM = "paddingBottom"; //$NON-NLS-1$
+ public static final String ATTR_PADDING_TOP = "paddingTop"; //$NON-NLS-1$
+ public static final String ATTR_PADDING_RIGHT = "paddingRight"; //$NON-NLS-1$
+ public static final String ATTR_PADDING_LEFT = "paddingLeft"; //$NON-NLS-1$
+ public static final String ATTR_PADDING_START = "paddingStart"; //$NON-NLS-1$
+ public static final String ATTR_PADDING_END = "paddingEnd"; //$NON-NLS-1$
+ public static final String ATTR_FOREGROUND = "foreground"; //$NON-NLS-1$
+ public static final String ATTR_BACKGROUND = "background"; //$NON-NLS-1$
+ public static final String ATTR_ORIENTATION = "orientation"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$
+ public static final String ATTR_ROW_COUNT = "rowCount"; //$NON-NLS-1$
+ public static final String ATTR_COLUMN_COUNT = "columnCount"; //$NON-NLS-1$
+ public static final String ATTR_LABEL_FOR = "labelFor"; //$NON-NLS-1$
+ public static final String ATTR_BASELINE_ALIGNED = "baselineAligned"; //$NON-NLS-1$
+ public static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; //$NON-NLS-1$
+ public static final String ATTR_IME_ACTION_LABEL = "imeActionLabel"; //$NON-NLS-1$
+ public static final String ATTR_PRIVATE_IME_OPTIONS = "privateImeOptions"; //$NON-NLS-1$
+ public static final String VALUE_NONE = "none"; //$NON-NLS-1$
+ public static final String VALUE_NO = "no"; //$NON-NLS-1$
+ public static final String ATTR_NUMERIC = "numeric"; //$NON-NLS-1$
+ public static final String ATTR_IME_ACTION_ID = "imeActionId"; //$NON-NLS-1$
+ public static final String ATTR_IME_OPTIONS = "imeOptions"; //$NON-NLS-1$
+ public static final String ATTR_FREEZES_TEXT = "freezesText"; //$NON-NLS-1$
+ public static final String ATTR_EDITOR_EXTRAS = "editorExtras"; //$NON-NLS-1$
+ public static final String ATTR_EDITABLE = "editable"; //$NON-NLS-1$
+ public static final String ATTR_DIGITS = "digits"; //$NON-NLS-1$
+ public static final String ATTR_CURSOR_VISIBLE = "cursorVisible"; //$NON-NLS-1$
+ public static final String ATTR_CAPITALIZE = "capitalize"; //$NON-NLS-1$
+ public static final String ATTR_PHONE_NUMBER = "phoneNumber"; //$NON-NLS-1$
+ public static final String ATTR_PASSWORD = "password"; //$NON-NLS-1$
+ public static final String ATTR_BUFFER_TYPE = "bufferType"; //$NON-NLS-1$
+ public static final String ATTR_AUTO_TEXT = "autoText"; //$NON-NLS-1$
+ public static final String ATTR_ENABLED = "enabled"; //$NON-NLS-1$
+ public static final String ATTR_SINGLE_LINE = "singleLine"; //$NON-NLS-1$
+ public static final String ATTR_SCALE_TYPE = "scaleType"; //$NON-NLS-1$
+ public static final String ATTR_VISIBILITY = "visibility"; //$NON-NLS-1$
+ public static final String ATTR_TEXT_IS_SELECTABLE =
+ "textIsSelectable"; //$NON-NLS-1$
+ public static final String ATTR_IMPORTANT_FOR_ACCESSIBILITY =
+ "importantForAccessibility"; //$NON-NLS-1$
+ public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT =
+ "listPreferredItemPaddingLeft"; //$NON-NLS-1$
+ public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT =
+ "listPreferredItemPaddingRight"; //$NON-NLS-1$
+ public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_START =
+ "listPreferredItemPaddingStart"; //$NON-NLS-1$
+ public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_END =
+ "listPreferredItemPaddingEnd"; //$NON-NLS-1$
+ public static final String ATTR_INDEX = "index"; //$NON-NLS-1$
+ public static final String ATTR_ACTION_BAR_NAV_MODE = "actionBarNavMode"; //$NON-NLS-1$
+ public static final String ATTR_MENU = "menu"; //$NON-NLS-1$
+ public static final String ATTR_SHOW_IN = "showIn"; //$NON-NLS-1$
+
+ // Tools attributes for AdapterView inheritors
+ public static final String ATTR_LISTFOOTER = "listfooter"; //$NON-NLS-1$
+ public static final String ATTR_LISTHEADER = "listheader"; //$NON-NLS-1$
+ public static final String ATTR_LISTITEM = "listitem"; //$NON-NLS-1$
+
+ // AbsoluteLayout layout params
+ public static final String ATTR_LAYOUT_Y = "layout_y"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_X = "layout_x"; //$NON-NLS-1$
+
+ // GridLayout layout params
+ public static final String ATTR_LAYOUT_ROW = "layout_row"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ROW_SPAN = "layout_rowSpan";//$NON-NLS-1$
+ public static final String ATTR_LAYOUT_COLUMN = "layout_column"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_COLUMN_SPAN = "layout_columnSpan"; //$NON-NLS-1$
+
+ // TableRow
+ public static final String ATTR_LAYOUT_SPAN = "layout_span"; //$NON-NLS-1$
+
+ // RelativeLayout layout params:
+ public static final String ATTR_LAYOUT_ALIGN_LEFT = "layout_alignLeft"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_RIGHT = "layout_alignRight"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_START = "layout_alignStart"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_END = "layout_alignEnd"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_TOP = "layout_alignTop"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_BOTTOM = "layout_alignBottom"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_PARENT_LEFT = "layout_alignParentLeft"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_PARENT_RIGHT = "layout_alignParentRight"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_PARENT_START = "layout_alignParentStart"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_PARENT_END = "layout_alignParentEnd"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_PARENT_TOP = "layout_alignParentTop"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_PARENT_BOTTOM = "layout_alignParentBottom"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING = "layout_alignWithParentIfMissing"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ALIGN_BASELINE = "layout_alignBaseline"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_CENTER_IN_PARENT = "layout_centerInParent"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_CENTER_VERTICAL = "layout_centerVertical"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_CENTER_HORIZONTAL = "layout_centerHorizontal"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_TO_RIGHT_OF = "layout_toRightOf"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_TO_LEFT_OF = "layout_toLeftOf"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_TO_START_OF = "layout_toStartOf"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_TO_END_OF = "layout_toEndOf"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_BELOW = "layout_below"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ABOVE = "layout_above"; //$NON-NLS-1$
+
+ // Margins
+ public static final String ATTR_LAYOUT_MARGIN = "layout_margin"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_MARGIN_LEFT = "layout_marginLeft"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_MARGIN_RIGHT = "layout_marginRight"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_MARGIN_START = "layout_marginStart"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_MARGIN_END = "layout_marginEnd"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_MARGIN_TOP = "layout_marginTop"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_MARGIN_BOTTOM = "layout_marginBottom"; //$NON-NLS-1$
+
+ // Attributes: Drawables
+ public static final String ATTR_TILE_MODE = "tileMode"; //$NON-NLS-1$
+
+ // Attributes: CoordinatorLayout
+ public static final String ATTR_LAYOUT_ANCHOR = "layout_anchor"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_ANCHOR_GRAVITY = "layout_anchorGravity"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_BEHAVIOR = "layout_behavior"; //$NON-NLS-1$
+ public static final String ATTR_LAYOUT_KEYLINE = "layout_keyline"; //$NON-NLS-1$
+
+ // Values: Manifest
+ public static final String VALUE_SPLIT_ACTION_BAR_WHEN_NARROW = "splitActionBarWhenNarrow"; // NON-NLS-$1
+
+ // Values: Layouts
+ public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$
+ public static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$
+ public static final String VALUE_VERTICAL = "vertical"; //$NON-NLS-1$
+ public static final String VALUE_TRUE = "true"; //$NON-NLS-1$
+ public static final String VALUE_EDITABLE = "editable"; //$NON-NLS-1$
+ public static final String VALUE_AUTO_FIT = "auto_fit"; //$NON-NLS-1$
+ public static final String VALUE_SELECTABLE_ITEM_BACKGROUND =
+ "?android:attr/selectableItemBackground"; //$NON-NLS-1$
+
+ // Values: Resources
+ public static final String VALUE_ID = "id"; //$NON-NLS-1$
+
+ // Values: Drawables
+ public static final String VALUE_DISABLED = "disabled"; //$NON-NLS-1$
+ public static final String VALUE_CLAMP = "clamp"; //$NON-NLS-1$
+
+ // Menus
+ public static final String ATTR_SHOW_AS_ACTION = "showAsAction"; //$NON-NLS-1$
+ public static final String ATTR_TITLE = "title"; //$NON-NLS-1$
+ public static final String ATTR_VISIBLE = "visible"; //$NON-NLS-1$
+ public static final String VALUE_IF_ROOM = "ifRoom"; //$NON-NLS-1$
+ public static final String VALUE_ALWAYS = "always"; //$NON-NLS-1$
+
+ // Units
+ public static final String UNIT_DP = "dp"; //$NON-NLS-1$
+ public static final String UNIT_DIP = "dip"; //$NON-NLS-1$
+ public static final String UNIT_SP = "sp"; //$NON-NLS-1$
+ public static final String UNIT_PX = "px"; //$NON-NLS-1$
+ public static final String UNIT_IN = "in"; //$NON-NLS-1$
+ public static final String UNIT_MM = "mm"; //$NON-NLS-1$
+ public static final String UNIT_PT = "pt"; //$NON-NLS-1$
+
+ // Filenames and folder names
+ public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$
+ public static final String OLD_PROGUARD_FILE = "proguard.cfg"; //$NON-NLS-1$
+ public static final String CLASS_FOLDER =
+ "bin" + File.separator + "classes"; //$NON-NLS-1$ //$NON-NLS-2$
+ public static final String GEN_FOLDER = "gen"; //$NON-NLS-1$
+ public static final String SRC_FOLDER = "src"; //$NON-NLS-1$
+ public static final String LIBS_FOLDER = "libs"; //$NON-NLS-1$
+ public static final String BIN_FOLDER = "bin"; //$NON-NLS-1$
+
+ public static final String RES_FOLDER = "res"; //$NON-NLS-1$
+ public static final String DOT_XML = ".xml"; //$NON-NLS-1$
+ public static final String DOT_XSD = ".xsd"; //$NON-NLS-1$
+ public static final String DOT_GIF = ".gif"; //$NON-NLS-1$
+ public static final String DOT_JPG = ".jpg"; //$NON-NLS-1$
+ public static final String DOT_JPEG = ".jpeg"; //$NON-NLS-1$
+ public static final String DOT_WEBP = ".webp"; //$NON-NLS-1$
+ public static final String DOT_PNG = ".png"; //$NON-NLS-1$
+ public static final String DOT_9PNG = ".9.png"; //$NON-NLS-1$
+ public static final String DOT_JAVA = ".java"; //$NON-NLS-1$
+ public static final String DOT_CLASS = ".class"; //$NON-NLS-1$
+ public static final String DOT_JAR = ".jar"; //$NON-NLS-1$
+ public static final String DOT_GRADLE = ".gradle"; //$NON-NLS-1$
+ public static final String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$
+ public static final String DOT_JSON = ".json"; //$NON-NLS-1$
+
+ /** Extension of the Application package Files, i.e. "apk". */
+ public static final String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$
+ /** Extension of java files, i.e. "java" */
+ public static final String EXT_JAVA = "java"; //$NON-NLS-1$
+ /** Extension of compiled java files, i.e. "class" */
+ public static final String EXT_CLASS = "class"; //$NON-NLS-1$
+ /** Extension of xml files, i.e. "xml" */
+ public static final String EXT_XML = "xml"; //$NON-NLS-1$
+ /** Extension of gradle files, i.e. "gradle" */
+ public static final String EXT_GRADLE = "gradle"; //$NON-NLS-1$
+ /** Extension of jar files, i.e. "jar" */
+ public static final String EXT_JAR = "jar"; //$NON-NLS-1$
+ /** Extension of ZIP files, i.e. "zip" */
+ public static final String EXT_ZIP = "zip"; //$NON-NLS-1$
+ /** Extension of aidl files, i.e. "aidl" */
+ public static final String EXT_AIDL = "aidl"; //$NON-NLS-1$
+ /** Extension of Renderscript files, i.e. "rs" */
+ public static final String EXT_RS = "rs"; //$NON-NLS-1$
+ /** Extension of Renderscript files, i.e. "rsh" */
+ public static final String EXT_RSH = "rsh"; //$NON-NLS-1$
+ /** Extension of FilterScript files, i.e. "fs" */
+ public static final String EXT_FS = "fs"; //$NON-NLS-1$
+ /** Extension of Renderscript bitcode files, i.e. "bc" */
+ public static final String EXT_BC = "bc"; //$NON-NLS-1$
+ /** Extension of dependency files, i.e. "d" */
+ public static final String EXT_DEP = "d"; //$NON-NLS-1$
+ /** Extension of native libraries, i.e. "so" */
+ public static final String EXT_NATIVE_LIB = "so"; //$NON-NLS-1$
+ /** Extension of dex files, i.e. "dex" */
+ public static final String EXT_DEX = "dex"; //$NON-NLS-1$
+ /** Extension for temporary resource files, ie "ap_ */
+ public static final String EXT_RES = "ap_"; //$NON-NLS-1$
+ /** Extension for pre-processable images. Right now pngs */
+ public static final String EXT_PNG = "png"; //$NON-NLS-1$
+ /** Extension for Android archive files */
+ public static final String EXT_AAR = "aar"; //$NON-NLS-1$
+ /** Extension for Java heap dumps. */
+ public static final String EXT_HPROF = "hprof"; //$NON-NLS-1$
+
+ private static final String DOT = "."; //$NON-NLS-1$
+
+ /** Dot-Extension of the Application package Files, i.e. ".apk". */
+ public static final String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE;
+ /** Dot-Extension of aidl files, i.e. ".aidl" */
+ public static final String DOT_AIDL = DOT + EXT_AIDL;
+ /** Dot-Extension of renderscript files, i.e. ".rs" */
+ public static final String DOT_RS = DOT + EXT_RS;
+ /** Dot-Extension of renderscript header files, i.e. ".rsh" */
+ public static final String DOT_RSH = DOT + EXT_RSH;
+ /** Dot-Extension of FilterScript files, i.e. ".fs" */
+ public static final String DOT_FS = DOT + EXT_FS;
+ /** Dot-Extension of renderscript bitcode files, i.e. ".bc" */
+ public static final String DOT_BC = DOT + EXT_BC;
+ /** Dot-Extension of dependency files, i.e. ".d" */
+ public static final String DOT_DEP = DOT + EXT_DEP;
+ /** Dot-Extension of native dynamic libraries, i.e. ".so" */
+ public static final String DOT_NATIVE_LIBS = DOT + EXT_NATIVE_LIB;
+ /** Dot-Extension of dex files, i.e. ".dex" */
+ public static final String DOT_DEX = DOT + EXT_DEX;
+ /** Dot-Extension for temporary resource files, ie "ap_ */
+ public static final String DOT_RES = DOT + EXT_RES;
+ /** Dot-Extension for BMP files, i.e. ".bmp" */
+ public static final String DOT_BMP = ".bmp"; //$NON-NLS-1$
+ /** Dot-Extension for SVG files, i.e. ".svg" */
+ public static final String DOT_SVG = ".svg"; //$NON-NLS-1$
+ /** Dot-Extension for template files */
+ public static final String DOT_FTL = ".ftl"; //$NON-NLS-1$
+ /** Dot-Extension of text files, i.e. ".txt" */
+ public static final String DOT_TXT = ".txt"; //$NON-NLS-1$
+ /** Dot-Extension for Android archive files */
+ public static final String DOT_AAR = DOT + EXT_AAR; //$NON-NLS-1$
+ /** Dot-Extension for Java heap dumps. */
+ public static final String DOT_HPROF = DOT + EXT_HPROF; //$NON-NLS-1$
+
+ /** Resource base name for java files and classes */
+ public static final String FN_RESOURCE_BASE = "R"; //$NON-NLS-1$
+ /** Resource java class filename, i.e. "R.java" */
+ public static final String FN_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_JAVA;
+ /** Resource class file filename, i.e. "R.class" */
+ public static final String FN_COMPILED_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_CLASS;
+ /** Resource text filename, i.e. "R.txt" */
+ public static final String FN_RESOURCE_TEXT = FN_RESOURCE_BASE + DOT_TXT;
+ /** Filename for public resources in AAR archives */
+ public static final String FN_PUBLIC_TXT = "public.txt";
+ /** Generated manifest class name */
+ public static final String FN_MANIFEST_BASE = "Manifest"; //$NON-NLS-1$
+ /** Generated BuildConfig class name */
+ public static final String FN_BUILD_CONFIG_BASE = "BuildConfig"; //$NON-NLS-1$
+ /** Manifest java class filename, i.e. "Manifest.java" */
+ public static final String FN_MANIFEST_CLASS = FN_MANIFEST_BASE + DOT_JAVA;
+ /** BuildConfig java class filename, i.e. "BuildConfig.java" */
+ public static final String FN_BUILD_CONFIG = FN_BUILD_CONFIG_BASE + DOT_JAVA;
+
+ public static final String DRAWABLE_FOLDER = "drawable"; //$NON-NLS-1$
+ public static final String DRAWABLE_XHDPI = "drawable-xhdpi"; //$NON-NLS-1$
+ public static final String DRAWABLE_XXHDPI = "drawable-xxhdpi"; //$NON-NLS-1$
+ public static final String DRAWABLE_XXXHDPI = "drawable-xxxhdpi"; //$NON-NLS-1$
+ public static final String DRAWABLE_HDPI = "drawable-hdpi"; //$NON-NLS-1$
+ public static final String DRAWABLE_MDPI = "drawable-mdpi"; //$NON-NLS-1$
+ public static final String DRAWABLE_LDPI = "drawable-ldpi"; //$NON-NLS-1$
+
+ // Resources
+ public static final String PREFIX_RESOURCE_REF = "@"; //$NON-NLS-1$
+ public static final String PREFIX_THEME_REF = "?"; //$NON-NLS-1$
+ public static final String PREFIX_BINDING_EXPR = "@{"; //$NON-NLS-1$
+ public static final String PREFIX_TWOWAY_BINDING_EXPR = "@={"; //$NON-NLS-1$
+ public static final String ANDROID_PREFIX = "@android:"; //$NON-NLS-1$
+ public static final String ANDROID_THEME_PREFIX = "?android:"; //$NON-NLS-1$
+ public static final String LAYOUT_RESOURCE_PREFIX = "@layout/"; //$NON-NLS-1$
+ public static final String STYLE_RESOURCE_PREFIX = "@style/"; //$NON-NLS-1$
+ public static final String COLOR_RESOURCE_PREFIX = "@color/"; //$NON-NLS-1$
+ public static final String NEW_ID_PREFIX = "@+id/"; //$NON-NLS-1$
+ public static final String ID_PREFIX = "@id/"; //$NON-NLS-1$
+ public static final String DRAWABLE_PREFIX = "@drawable/"; //$NON-NLS-1$
+ public static final String STRING_PREFIX = "@string/"; //$NON-NLS-1$
+ public static final String DIMEN_PREFIX = "@dimen/"; //$NON-NLS-1$
+ public static final String MIPMAP_PREFIX = "@mipmap/"; //$NON-NLS-1$
+
+ public static final String ANDROID_LAYOUT_RESOURCE_PREFIX = "@android:layout/"; //$NON-NLS-1$
+ public static final String ANDROID_STYLE_RESOURCE_PREFIX = "@android:style/"; //$NON-NLS-1$
+ public static final String ANDROID_COLOR_RESOURCE_PREFIX = "@android:color/"; //$NON-NLS-1$
+ public static final String ANDROID_NEW_ID_PREFIX = "@android:+id/"; //$NON-NLS-1$
+ public static final String ANDROID_ID_PREFIX = "@android:id/"; //$NON-NLS-1$
+ public static final String ANDROID_DRAWABLE_PREFIX = "@android:drawable/"; //$NON-NLS-1$
+ public static final String ANDROID_STRING_PREFIX = "@android:string/"; //$NON-NLS-1$
+
+ public static final String RESOURCE_CLZ_ID = "id"; //$NON-NLS-1$
+ public static final String RESOURCE_CLZ_COLOR = "color"; //$NON-NLS-1$
+ public static final String RESOURCE_CLZ_ARRAY = "array"; //$NON-NLS-1$
+ public static final String RESOURCE_CLZ_ATTR = "attr"; //$NON-NLS-1$
+ public static final String RESOURCE_CLR_STYLEABLE = "styleable"; //$NON-NLS-1$
+ public static final String NULL_RESOURCE = "@null"; //$NON-NLS-1$
+ public static final String TRANSPARENT_COLOR = "@android:color/transparent"; //$NON-NLS-1$
+ public static final String REFERENCE_STYLE = "style/"; //$NON-NLS-1$
+ public static final String PREFIX_ANDROID = "android:"; //$NON-NLS-1$
+
+ // Resource Types
+ public static final String DRAWABLE_TYPE = "drawable"; //$NON-NLS-1$
+ public static final String MENU_TYPE = "menu"; //$NON-NLS-1$
+
+ // Packages
+ public static final String ANDROID_PKG_PREFIX = "android."; //$NON-NLS-1$
+ public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$
+ public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$
+
+ // Project properties
+ public static final String ANDROID_LIBRARY = "android.library"; //$NON-NLS-1$
+ public static final String PROGUARD_CONFIG = "proguard.config"; //$NON-NLS-1$
+ public static final String ANDROID_LIBRARY_REFERENCE_FORMAT = "android.library.reference.%1$d";//$NON-NLS-1$
+ public static final String PROJECT_PROPERTIES = "project.properties";//$NON-NLS-1$
+
+ // Java References
+ public static final String ATTR_REF_PREFIX = "?attr/"; //$NON-NLS-1$
+ public static final String R_PREFIX = "R."; //$NON-NLS-1$
+ public static final String R_ID_PREFIX = "R.id."; //$NON-NLS-1$
+ public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$
+ public static final String R_DRAWABLE_PREFIX = "R.drawable."; //$NON-NLS-1$
+ public static final String R_STYLEABLE_PREFIX = "R.styleable."; //$NON-NLS-1$
+ public static final String R_ATTR_PREFIX = "R.attr."; //$NON-NLS-1$
+
+ // Attributes related to tools
+ public static final String ATTR_IGNORE = "ignore"; //$NON-NLS-1$
+ public static final String ATTR_LOCALE = "locale"; //$NON-NLS-1$
+
+ // SuppressLint
+ public static final String SUPPRESS_ALL = "all"; //$NON-NLS-1$
+ public static final String SUPPRESS_LINT = "SuppressLint"; //$NON-NLS-1$
+ public static final String TARGET_API = "TargetApi"; //$NON-NLS-1$
+ public static final String ATTR_TARGET_API = "targetApi"; //$NON-NLS-1$
+ public static final String FQCN_SUPPRESS_LINT = "android.annotation." + SUPPRESS_LINT; //$NON-NLS-1$
+ public static final String FQCN_TARGET_API = "android.annotation." + TARGET_API; //$NON-NLS-1$
+
+ // Class Names
+ public static final String CONSTRUCTOR_NAME = "<init>"; //$NON-NLS-1$
+ public static final String CLASS_CONSTRUCTOR = "<clinit>"; //$NON-NLS-1$
+ public static final String ANDROID_VIEW_VIEW = "android/view/View"; //$NON-NLS-1$
+
+ // Method Names
+ public static final String FORMAT_METHOD = "format"; //$NON-NLS-1$
+ public static final String GET_STRING_METHOD = "getString"; //$NON-NLS-1$
+
+
+ public static final String ATTR_TAG = "tag"; //$NON-NLS-1$
+ public static final String ATTR_NUM_COLUMNS = "numColumns"; //$NON-NLS-1$
+
+ // Some common layout element names
+ public static final String CALENDAR_VIEW = "CalendarView"; //$NON-NLS-1$
+ public static final String SPACE = "Space"; //$NON-NLS-1$
+ public static final String GESTURE_OVERLAY_VIEW = "GestureOverlayView";//$NON-NLS-1$
+
+ public static final String ATTR_HANDLE = "handle"; //$NON-NLS-1$
+ public static final String ATTR_CONTENT = "content"; //$NON-NLS-1$
+ public static final String ATTR_CHECKED = "checked"; //$NON-NLS-1$
+
+ // TextView
+ public static final String ATTR_DRAWABLE_RIGHT = "drawableRight"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE_LEFT = "drawableLeft"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE_START = "drawableStart"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE_END = "drawableEnd"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE_BOTTOM = "drawableBottom"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE_TOP = "drawableTop"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE_PADDING = "drawablePadding"; //$NON-NLS-1$
+
+ public static final String ATTR_USE_DEFAULT_MARGINS = "useDefaultMargins"; //$NON-NLS-1$
+ public static final String ATTR_MARGINS_INCLUDED_IN_ALIGNMENT = "marginsIncludedInAlignment"; //$NON-NLS-1$
+
+ public static final String VALUE_WRAP_CONTENT = "wrap_content"; //$NON-NLS-1$
+ public static final String VALUE_FALSE= "false"; //$NON-NLS-1$
+ public static final String VALUE_N_DP = "%ddp"; //$NON-NLS-1$
+ public static final String VALUE_ZERO_DP = "0dp"; //$NON-NLS-1$
+ public static final String VALUE_ONE_DP = "1dp"; //$NON-NLS-1$
+ public static final String VALUE_TOP = "top"; //$NON-NLS-1$
+ public static final String VALUE_BOTTOM = "bottom"; //$NON-NLS-1$
+ public static final String VALUE_CENTER_VERTICAL = "center_vertical"; //$NON-NLS-1$
+ public static final String VALUE_CENTER_HORIZONTAL = "center_horizontal"; //$NON-NLS-1$
+ public static final String VALUE_FILL_HORIZONTAL = "fill_horizontal"; //$NON-NLS-1$
+ public static final String VALUE_FILL_VERTICAL = "fill_vertical"; //$NON-NLS-1$
+ public static final String VALUE_0 = "0"; //$NON-NLS-1$
+ public static final String VALUE_1 = "1"; //$NON-NLS-1$
+
+ // Gravity values. These have the GRAVITY_ prefix in front of value because we already
+ // have VALUE_CENTER_HORIZONTAL defined for layouts, and its definition conflicts
+ // (centerHorizontal versus center_horizontal)
+ public static final String GRAVITY_VALUE_ = "center"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_CENTER = "center"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_LEFT = "left"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_RIGHT = "right"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_START = "start"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_END = "end"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_BOTTOM = "bottom"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_TOP = "top"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_FILL_HORIZONTAL = "fill_horizontal"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_FILL_VERTICAL = "fill_vertical"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_CENTER_HORIZONTAL = "center_horizontal"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_CENTER_VERTICAL = "center_vertical"; //$NON-NLS-1$
+ public static final String GRAVITY_VALUE_FILL = "fill"; //$NON-NLS-1$
+
+ /**
+ * The top level android package as a prefix, "android.".
+ */
+ public static final String ANDROID_SUPPORT_PKG_PREFIX = ANDROID_PKG_PREFIX + "support."; //$NON-NLS-1$
+
+ /** The android.view. package prefix */
+ public static final String ANDROID_VIEW_PKG = ANDROID_PKG_PREFIX + "view."; //$NON-NLS-1$
+
+ /** The android.widget. package prefix */
+ public static final String ANDROID_WIDGET_PREFIX = ANDROID_PKG_PREFIX + "widget."; //$NON-NLS-1$
+
+ /** The android.webkit. package prefix */
+ public static final String ANDROID_WEBKIT_PKG = ANDROID_PKG_PREFIX + "webkit."; //$NON-NLS-1$
+
+ /** The android.app. package prefix */
+ public static final String ANDROID_APP_PKG = ANDROID_PKG_PREFIX + "app."; //$NON-NLS-1$
+
+ /** The LayoutParams inner-class name suffix, .LayoutParams */
+ public static final String DOT_LAYOUT_PARAMS = ".LayoutParams"; //$NON-NLS-1$
+
+ /** The fully qualified class name of an EditText view */
+ public static final String FQCN_EDIT_TEXT = "android.widget.EditText"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a LinearLayout view */
+ public static final String FQCN_LINEAR_LAYOUT = "android.widget.LinearLayout"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a RelativeLayout view */
+ public static final String FQCN_RELATIVE_LAYOUT = "android.widget.RelativeLayout"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a RelativeLayout view */
+ public static final String FQCN_GRID_LAYOUT = "android.widget.GridLayout"; //$NON-NLS-1$
+ public static final String FQCN_GRID_LAYOUT_V7 = "android.support.v7.widget.GridLayout"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a FrameLayout view */
+ public static final String FQCN_FRAME_LAYOUT = "android.widget.FrameLayout"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a TableRow view */
+ public static final String FQCN_TABLE_ROW = "android.widget.TableRow"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a TableLayout view */
+ public static final String FQCN_TABLE_LAYOUT = "android.widget.TableLayout"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a GridView view */
+ public static final String FQCN_GRID_VIEW = "android.widget.GridView"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a TabWidget view */
+ public static final String FQCN_TAB_WIDGET = "android.widget.TabWidget"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a Button view */
+ public static final String FQCN_BUTTON = "android.widget.Button"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a RadioButton view */
+ public static final String FQCN_RADIO_BUTTON = "android.widget.RadioButton"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a ToggleButton view */
+ public static final String FQCN_TOGGLE_BUTTON = "android.widget.ToggleButton"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a Spinner view */
+ public static final String FQCN_SPINNER = "android.widget.Spinner"; //$NON-NLS-1$
+
+ /** The fully qualified class name of an AdapterView */
+ public static final String FQCN_ADAPTER_VIEW = "android.widget.AdapterView"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a ListView */
+ public static final String FQCN_LIST_VIEW = "android.widget.ListView"; //$NON-NLS-1$
+
+ /** The fully qualified class name of an ExpandableListView */
+ public static final String FQCN_EXPANDABLE_LIST_VIEW = "android.widget.ExpandableListView"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a GestureOverlayView */
+ public static final String FQCN_GESTURE_OVERLAY_VIEW = "android.gesture.GestureOverlayView"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a DatePicker */
+ public static final String FQCN_DATE_PICKER = "android.widget.DatePicker"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a TimePicker */
+ public static final String FQCN_TIME_PICKER = "android.widget.TimePicker"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a RadioGroup */
+ public static final String FQCN_RADIO_GROUP = "android.widgets.RadioGroup"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a Space */
+ public static final String FQCN_SPACE = "android.widget.Space"; //$NON-NLS-1$
+ public static final String FQCN_SPACE_V7 = "android.support.v7.widget.Space"; //$NON-NLS-1$
+
+ /** The fully qualified class name of a TextView view */
+ public static final String FQCN_TEXT_VIEW = "android.widget.TextView"; //$NON-NLS-1$
+
+ /** The fully qualified class name of an ImageView view */
+ public static final String FQCN_IMAGE_VIEW = "android.widget.ImageView"; //$NON-NLS-1$
+
+ public static final String ATTR_SRC = "src"; //$NON-NLS-1$
+
+ public static final String ATTR_GRAVITY = "gravity"; //$NON-NLS-1$
+
+ public static final String ATTR_WEIGHT_SUM = "weightSum"; //$NON-NLS-1$
+ public static final String ATTR_EMS = "ems"; //$NON-NLS-1$
+
+ public static final String VALUE_HORIZONTAL = "horizontal"; //$NON-NLS-1$
+
+ public static final String GRADLE_PLUGIN_NAME = "com.android.tools.build:gradle:";
+ public static final String GRADLE_MINIMUM_VERSION = "2.2.1";
+ public static final String GRADLE_LATEST_VERSION = "2.10";
+ public static final String GRADLE_PLUGIN_MINIMUM_VERSION = "1.0.0";
+ public static final String GRADLE_PLUGIN_RECOMMENDED_VERSION = "2.0.0";
+ public static final String GRADLE_PLUGIN_LATEST_VERSION = GRADLE_PLUGIN_RECOMMENDED_VERSION;
+ public static final String MIN_BUILD_TOOLS_VERSION = "19.1.0";
+ public static final String SUPPORT_LIB_ARTIFACT = "com.android.support:support-v4";
+ public static final String APPCOMPAT_LIB_ARTIFACT = "com.android.support:appcompat-v7";
+
+ // Annotations
+ public static final String SUPPORT_ANNOTATIONS_PREFIX = "android.support.annotation.";
+ public static final String INT_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntDef";
+ public static final String STRING_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "StringDef";
+ public static final String TYPE_DEF_VALUE_ATTRIBUTE = "value";
+ public static final String TYPE_DEF_FLAG_ATTRIBUTE = "flag";
+ public static final String FN_ANNOTATIONS_ZIP = "annotations.zip";
+ public static final String BINDING_ADAPTER_ANNOTATION = "android.databinding.BindingAdapter";
+
+ // Data Binding MISC
+ public static final String DATA_BINDING_LIB_ARTIFACT = "com.android.databinding:library";
+ public static final String DATA_BINDING_BASELIB_ARTIFACT = "com.android.databinding:baseLibrary";
+ public static final String DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT =
+ "com.android.databinding:compiler";
+ public static final String DATA_BINDING_ADAPTER_LIB_ARTIFACT =
+ "com.android.databinding:adapters";
+ public static final String[] TAGS_DATA_BINDING = new String[]{TAG_VARIABLE,
+ TAG_IMPORT, TAG_LAYOUT, TAG_DATA};
+ public static final String[] ATTRS_DATA_BINDING = new String[]{ATTR_NAME,
+ ATTR_TYPE, ATTR_CLASS, ATTR_ALIAS};
+
+ /** Name of keep attribute in XML */
+ public static final String ATTR_KEEP = "keep";
+ /** Name of discard attribute in XML (to mark resources as not referenced, despite guesses) */
+ public static final String ATTR_DISCARD = "discard";
+ /** Name of attribute in XML to control whether we should guess resources to keep */
+ public static final String ATTR_SHRINK_MODE = "shrinkMode";
+ /** {@linkplain #ATTR_SHRINK_MODE} value to only shrink explicitly encountered resources */
+ public static final String VALUE_STRICT = "strict";
+ /** {@linkplain #ATTR_SHRINK_MODE} value to keep possibly referenced resources */
+ public static final String VALUE_SAFE = "safe";
+}
diff --git a/common/src/main/java/com/android/ide/common/blame/Message.java b/common/src/main/java/com/android/ide/common/blame/Message.java
new file mode 100644
index 0000000..180448d
--- /dev/null
+++ b/common/src/main/java/com/android/ide/common/blame/Message.java
@@ -0,0 +1,218 @@
+/*
+ * 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.ide.common.blame;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.List;
+
+ at Immutable
+public final class Message {
+
+ @NonNull
+ private final Kind mKind;
+
+ @NonNull
+ private final String mText;
+
+ @NonNull
+ private final List<SourceFilePosition> mSourceFilePositions;
+
+ @NonNull
+ private final String mRawMessage;
+
+ @NonNull
+ private final Optional<String> mToolName;
+
+ /**
+ * Create a new message, which has a {@link Kind}, a String which will be shown to the user and
+ * at least one {@link SourceFilePosition}.
+ *
+ * @param kind the message type.
+ * @param text the text of the message.
+ * @param sourceFilePosition the first source file position the message .
+ * @param sourceFilePositions any additional source file positions, may be empty.
+ */
+ public Message(@NonNull Kind kind,
+ @NonNull String text,
+ @NonNull SourceFilePosition sourceFilePosition,
+ @NonNull SourceFilePosition... sourceFilePositions) {
+ mKind = kind;
+ mText = text;
+ mRawMessage = text;
+ mSourceFilePositions = ImmutableList.<SourceFilePosition>builder()
+ .add(sourceFilePosition).add(sourceFilePositions).build();
+ mToolName = Optional.absent();
+ }
+
+ /**
+ * Create a new message, which has a {@link Kind}, a String which will be shown to the user and
+ * at least one {@link SourceFilePosition}.
+ *
+ * It also has a rawMessage, to store the original string for cases when the message is
+ * constructed by parsing the output from another tool.
+ *
+ * @param kind the message kind.
+ * @param text a human-readable string explaining the issue.
+ * @param rawMessage the original text of the message, usually from an external tool.
+ * @param toolName the name of the tool that produced the message, e.g. AAPT.
+ * @param sourceFilePosition the first source file position.
+ * @param sourceFilePositions any additional source file positions, may be empty.
+ */
+ public Message(@NonNull Kind kind,
+ @NonNull String text,
+ @NonNull String rawMessage,
+ @Nullable String toolName,
+ @NonNull SourceFilePosition sourceFilePosition,
+ @NonNull SourceFilePosition... sourceFilePositions) {
+ mKind = kind;
+ mText = text;
+ mRawMessage = rawMessage;
+ mToolName = Optional.fromNullable(toolName);
+ mSourceFilePositions = ImmutableList.<SourceFilePosition>builder()
+ .add(sourceFilePosition).add(sourceFilePositions).build();
+ }
+
+ public Message(@NonNull Kind kind,
+ @NonNull String text,
+ @NonNull String rawMessage,
+ @NonNull Optional<String> toolName,
+ @NonNull ImmutableList<SourceFilePosition> positions) {
+ mKind = kind;
+ mText = text;
+ mRawMessage = rawMessage;
+ mToolName = toolName;
+
+ if (positions.isEmpty()) {
+ mSourceFilePositions = ImmutableList.of(SourceFilePosition.UNKNOWN);
+ } else {
+ mSourceFilePositions = positions;
+ }
+ }
+
+ @NonNull
+ public Kind getKind() {
+ return mKind;
+ }
+
+ @NonNull
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Returns a list of source positions. Will always contain at least one item.
+ */
+ @NonNull
+ public List<SourceFilePosition> getSourceFilePositions() {
+ return mSourceFilePositions;
+ }
+
+ @NonNull
+ public String getRawMessage() {
+ return mRawMessage;
+ }
+
+ @NonNull
+ public Optional<String> getToolName() {
+ return mToolName;
+ }
+
+ @Nullable
+ public String getSourcePath() {
+ File file = mSourceFilePositions.get(0).getFile().getSourceFile();
+ if (file == null) {
+ return null;
+ }
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * Returns a legacy 1-based line number.
+ */
+ @Deprecated
+ public int getLineNumber() {
+ return mSourceFilePositions.get(0).getPosition().getStartLine() + 1;
+ }
+
+ /**
+ * @return a legacy 1-based column number.
+ */
+ @Deprecated
+ public int getColumn() {
+ return mSourceFilePositions.get(0).getPosition().getStartColumn() + 1;
+ }
+
+ public enum Kind {
+ ERROR, WARNING, INFO, STATISTICS, UNKNOWN, SIMPLE;
+
+ public static Kind findIgnoringCase(String s, Kind defaultKind) {
+ for (Kind kind : values()) {
+ if (kind.toString().equalsIgnoreCase(s)) {
+ return kind;
+ }
+ }
+ return defaultKind;
+ }
+
+ @Nullable
+ public static Kind findIgnoringCase(String s) {
+ return findIgnoringCase(s, null);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Message)) {
+ return false;
+ }
+ Message that = (Message) o;
+ return Objects.equal(mKind, that.mKind) &&
+ Objects.equal(mText, that.mText) &&
+ Objects.equal(mRawMessage, that.mRawMessage) &&
+ Objects.equal(mToolName, that.mToolName) &&
+ Objects.equal(mSourceFilePositions, that.mSourceFilePositions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mKind, mText, mSourceFilePositions);
+ }
+
+ @Override
+ public String toString() {
+ Objects.ToStringHelper toStringHelper =
+ Objects.toStringHelper(this).add("kind", mKind).add("text", mText)
+ .add("sources", mSourceFilePositions);
+ if (!mText.equals(mRawMessage)) {
+ toStringHelper.add("original message", mRawMessage);
+ }
+ if (mToolName.isPresent()) {
+ toStringHelper.add("tool name", mToolName);
+ }
+ return toStringHelper.toString();
+ }
+}
diff --git a/common/src/main/java/com/android/ide/common/blame/SourceFile.java b/common/src/main/java/com/android/ide/common/blame/SourceFile.java
new file mode 100644
index 0000000..72659c0
--- /dev/null
+++ b/common/src/main/java/com/android/ide/common/blame/SourceFile.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ide.common.blame;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.google.common.base.Objects;
+
+import java.io.File;
+
+/**
+ * Represents a source file.
+ */
+ at Immutable
+public final class SourceFile {
+
+ @NonNull
+ public static final SourceFile UNKNOWN = new SourceFile();
+
+ @Nullable
+ private final File mSourceFile;
+
+ /**
+ * A human readable description
+ *
+ * Usually the file name is OK for the short output, but for the manifest merger,
+ * where all of the files will be named AndroidManifest.xml the variant name is more useful.
+ */
+ @Nullable
+ private final String mDescription;
+
+ @SuppressWarnings("NullableProblems")
+ public SourceFile(
+ @NonNull File sourceFile,
+ @NonNull String description) {
+ mSourceFile = sourceFile;
+ mDescription = description;
+ }
+
+ public SourceFile(
+ @SuppressWarnings("NullableProblems") @NonNull File sourceFile) {
+ mSourceFile = sourceFile;
+ mDescription = null;
+ }
+
+ public SourceFile(
+ @SuppressWarnings("NullableProblems") @NonNull String description) {
+ mSourceFile = null;
+ mDescription = description;
+ }
+
+ private SourceFile() {
+ mSourceFile = null;
+ mDescription = null;
+ }
+
+ @Nullable
+ public File getSourceFile() {
+ return mSourceFile;
+ }
+
+ @Nullable
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SourceFile)) {
+ return false;
+ }
+ SourceFile other = (SourceFile) obj;
+
+ return Objects.equal(mDescription, other.mDescription) &&
+ Objects.equal(mSourceFile, other.mSourceFile);
+ }
+
+ @Override
+ public int hashCode() {
+ String filePath = mSourceFile != null ? mSourceFile.getPath() : null;
+ return Objects.hashCode(filePath, mDescription);
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return print(false /* shortFormat */);
+ }
+
+ @NonNull
+ public String print(boolean shortFormat) {
+ if (mSourceFile == null) {
+ if (mDescription == null) {
+ return "Unknown source file";
+ }
+ return mDescription;
+ }
+ String fileName = mSourceFile.getName();
+ String fileDisplayName = shortFormat ? fileName : mSourceFile.getAbsolutePath();
+ if (mDescription == null || mDescription.equals(fileName)) {
+ return fileDisplayName;
+ } else {
+ return String.format("[%1$s] %2$s", mDescription, fileDisplayName);
+ }
+ }
+
+}
diff --git a/common/src/main/java/com/android/ide/common/blame/SourceFilePosition.java b/common/src/main/java/com/android/ide/common/blame/SourceFilePosition.java
new file mode 100644
index 0000000..6e600ee
--- /dev/null
+++ b/common/src/main/java/com/android/ide/common/blame/SourceFilePosition.java
@@ -0,0 +1,90 @@
+/*
+ * 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.ide.common.blame;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.Immutable;
+import com.google.common.base.Objects;
+
+import java.io.File;
+
+ at Immutable
+public final class SourceFilePosition {
+
+ public static final com.android.ide.common.blame.SourceFilePosition UNKNOWN =
+ new SourceFilePosition(SourceFile.UNKNOWN, SourcePosition.UNKNOWN);
+
+ @NonNull
+ private final SourceFile mSourceFile;
+
+ @NonNull
+ private final SourcePosition mSourcePosition;
+
+ public SourceFilePosition(@NonNull SourceFile sourceFile,
+ @NonNull SourcePosition sourcePosition) {
+ mSourceFile = sourceFile;
+ mSourcePosition = sourcePosition;
+ }
+
+ public SourceFilePosition(@NonNull File file,
+ @NonNull SourcePosition sourcePosition) {
+ this(new SourceFile(file), sourcePosition);
+ }
+
+ @NonNull
+ public SourcePosition getPosition() {
+ return mSourcePosition;
+ }
+
+ @NonNull
+ public SourceFile getFile() {
+ return mSourceFile;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return print(false);
+ }
+
+ @NonNull
+ public String print(boolean shortFormat) {
+ if (mSourcePosition.equals(SourcePosition.UNKNOWN)) {
+ return mSourceFile.print(shortFormat);
+ } else {
+ return mSourceFile.print(shortFormat) + ':' + mSourcePosition.toString();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mSourceFile, mSourcePosition);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SourceFilePosition)) {
+ return false;
+ }
+ SourceFilePosition other = (SourceFilePosition) obj;
+ return Objects.equal(mSourceFile, other.mSourceFile) &&
+ Objects.equal(mSourcePosition, other.mSourcePosition);
+ }
+}
diff --git a/common/src/main/java/com/android/ide/common/blame/SourcePosition.java b/common/src/main/java/com/android/ide/common/blame/SourcePosition.java
new file mode 100644
index 0000000..f914505
--- /dev/null
+++ b/common/src/main/java/com/android/ide/common/blame/SourcePosition.java
@@ -0,0 +1,184 @@
+/*
+ * 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.ide.common.blame;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.Immutable;
+import com.google.common.base.Objects;
+
+/**
+ * An immutable position in a text file, used in errors to point the user to an issue.
+ *
+ * Positions that are unknown are represented by -1.
+ */
+ at Immutable
+public final class SourcePosition {
+
+ @NonNull
+ public static final SourcePosition UNKNOWN = new SourcePosition();
+
+ private final int mStartLine, mStartColumn, mStartOffset, mEndLine, mEndColumn, mEndOffset;
+
+ public SourcePosition(int startLine, int startColumn, int startOffset,
+ int endLine, int endColumn, int endOffset) {
+ mStartLine = startLine;
+ mStartColumn = startColumn;
+ mStartOffset = startOffset;
+ mEndLine = endLine;
+ mEndColumn = endColumn;
+ mEndOffset = endOffset;
+ }
+
+ public SourcePosition(int lineNumber, int column, int offset) {
+ mStartLine = mEndLine = lineNumber;
+ mStartColumn = mEndColumn = column;
+ mStartOffset = mEndOffset = offset;
+ }
+
+ private SourcePosition() {
+ mStartLine = mStartColumn = mStartOffset = mEndLine = mEndColumn = mEndOffset = -1;
+ }
+
+ protected SourcePosition(SourcePosition copy) {
+ mStartLine = copy.getStartLine();
+ mStartColumn = copy.getStartColumn();
+ mStartOffset = copy.getStartOffset();
+ mEndLine = copy.getEndLine();
+ mEndColumn = copy.getEndColumn();
+ mEndOffset = copy.getEndOffset();
+ }
+
+ /**
+ * Outputs positions as human-readable formatted strings.
+ *
+ * e.g.
+ * <pre>84
+ * 84-86
+ * 84:5
+ * 84:5-28
+ * 85:5-86:47</pre>
+ *
+ * @return a human readable position.
+ */
+ @Override
+ public String toString() {
+ if (mStartLine == -1) {
+ return "?";
+ }
+ StringBuilder sB = new StringBuilder(15);
+ sB.append(mStartLine + 1); // Humans think that the first line is line 1.
+ if (mStartColumn != -1) {
+ sB.append(':');
+ sB.append(mStartColumn + 1);
+ }
+ if (mEndLine != -1) {
+
+ if (mEndLine == mStartLine) {
+ if (mEndColumn != -1 && mEndColumn != mStartColumn) {
+ sB.append('-');
+ sB.append(mEndColumn + 1);
+ }
+ } else {
+ sB.append('-');
+ sB.append(mEndLine + 1);
+ if (mEndColumn != -1) {
+ sB.append(':');
+ sB.append(mEndColumn + 1);
+ }
+ }
+ }
+ return sB.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SourcePosition)) {
+ return false;
+ }
+ SourcePosition other = (SourcePosition) obj;
+
+ return other.mStartLine == mStartLine &&
+ other.mStartColumn == mStartColumn &&
+ other.mStartOffset == mStartOffset &&
+ other.mEndLine == mEndLine &&
+ other.mEndColumn == mEndColumn &&
+ other.mEndOffset == mEndOffset;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects
+ .hashCode(mStartLine, mStartColumn, mStartOffset, mEndLine, mEndColumn, mEndOffset);
+ }
+
+ public int getStartLine() {
+ return mStartLine;
+ }
+
+ public int getStartColumn() {
+ return mStartColumn;
+ }
+
+ public int getStartOffset() {
+ return mStartOffset;
+ }
+
+
+ public int getEndLine() {
+ return mEndLine;
+ }
+
+ public int getEndColumn() {
+ return mEndColumn;
+ }
+
+ public int getEndOffset() {
+ return mEndOffset;
+ }
+
+ /**
+ * Compares the start of this SourcePosition with another.
+ * @return 0 if they are the same, < 0 if this < other and > 0 if this > other
+ */
+ public int compareStart(@NonNull SourcePosition other) {
+ if (mStartOffset != -1 && other.mStartOffset != -1) {
+ return mStartOffset - other.mStartOffset;
+ }
+ if (mStartLine == other.mStartLine) {
+ return mStartColumn - other.mStartColumn;
+ }
+ return mStartLine - other.mStartLine;
+ }
+
+ /**
+ * Compares the end of this SourcePosition with another.
+ * @return 0 if they are the same, < 0 if this < other and > 0 if this > other
+ */
+ public int compareEnd(@NonNull SourcePosition other) {
+ if (mEndOffset != -1 && other.mEndOffset != -1) {
+ return mEndOffset - other.mEndOffset;
+ }
+ if (mEndLine == other.mEndLine) {
+ return mEndColumn - other.mEndColumn;
+ }
+ return mEndLine - other.mEndLine;
+ }
+}
diff --git a/base/common/src/main/java/com/android/io/FileWrapper.java b/common/src/main/java/com/android/io/FileWrapper.java
similarity index 100%
rename from base/common/src/main/java/com/android/io/FileWrapper.java
rename to common/src/main/java/com/android/io/FileWrapper.java
diff --git a/base/common/src/main/java/com/android/io/FolderWrapper.java b/common/src/main/java/com/android/io/FolderWrapper.java
similarity index 100%
rename from base/common/src/main/java/com/android/io/FolderWrapper.java
rename to common/src/main/java/com/android/io/FolderWrapper.java
diff --git a/base/common/src/main/java/com/android/io/IAbstractFile.java b/common/src/main/java/com/android/io/IAbstractFile.java
similarity index 100%
rename from base/common/src/main/java/com/android/io/IAbstractFile.java
rename to common/src/main/java/com/android/io/IAbstractFile.java
diff --git a/base/common/src/main/java/com/android/io/IAbstractFolder.java b/common/src/main/java/com/android/io/IAbstractFolder.java
similarity index 100%
rename from base/common/src/main/java/com/android/io/IAbstractFolder.java
rename to common/src/main/java/com/android/io/IAbstractFolder.java
diff --git a/base/common/src/main/java/com/android/io/IAbstractResource.java b/common/src/main/java/com/android/io/IAbstractResource.java
similarity index 100%
rename from base/common/src/main/java/com/android/io/IAbstractResource.java
rename to common/src/main/java/com/android/io/IAbstractResource.java
diff --git a/base/common/src/main/java/com/android/io/NonClosingInputStream.java b/common/src/main/java/com/android/io/NonClosingInputStream.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/common/src/main/java/com/android/io/NonClosingInputStream.java
rename to common/src/main/java/com/android/io/NonClosingInputStream.java
diff --git a/base/common/src/main/java/com/android/io/StreamException.java b/common/src/main/java/com/android/io/StreamException.java
similarity index 100%
rename from base/common/src/main/java/com/android/io/StreamException.java
rename to common/src/main/java/com/android/io/StreamException.java
diff --git a/common/src/main/java/com/android/prefs/AndroidLocation.java b/common/src/main/java/com/android/prefs/AndroidLocation.java
new file mode 100644
index 0000000..7bfb316
--- /dev/null
+++ b/common/src/main/java/com/android/prefs/AndroidLocation.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2008 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.prefs;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.FileUtils;
+
+import java.io.File;
+
+/**
+ * Manages the location of the android files (including emulator files, ddms config, debug keystore)
+ */
+public final class AndroidLocation {
+
+ /**
+ * The name of the .android folder returned by {@link #getFolder}.
+ */
+ public static final String FOLDER_DOT_ANDROID = ".android";
+
+ /**
+ * Virtual Device folder inside the path returned by {@link #getFolder}
+ */
+ public static final String FOLDER_AVD = "avd";
+
+ /**
+ * Throw when the location of the android folder couldn't be found.
+ */
+ public static final class AndroidLocationException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public AndroidLocationException(String string) {
+ super(string);
+ }
+ }
+
+ private static String sPrefsLocation = null;
+ private static String sAvdLocation = null;
+
+ /**
+ * Enum describing which variables to check and whether they should
+ * be checked via {@link System#getProperty(String)} or {@link System#getenv()} or both.
+ */
+ public enum EnvVar {
+ ANDROID_AVD_HOME("ANDROID_AVD_HOME", true, true), // both sys prop and env var
+ ANDROID_SDK_HOME("ANDROID_SDK_HOME", true, true), // both sys prop and env var
+ USER_HOME ("user.home", true, false), // sys prop only
+ HOME ("HOME", false, true); // env var only
+
+ final String mName;
+ final boolean mIsSysProp;
+ final boolean mIsEnvVar;
+
+ EnvVar(String name, boolean isSysProp, boolean isEnvVar) {
+ mName = name;
+ mIsSysProp = isSysProp;
+ mIsEnvVar = isEnvVar;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Nullable
+ public String validatePath(boolean silent) throws AndroidLocationException {
+ String path;
+ if (mIsSysProp) {
+ path = checkPath(System.getProperty(mName), silent);
+ if (path != null) {
+ return path;
+ }
+ }
+
+ if (mIsEnvVar) {
+ path = checkPath(System.getenv(mName), silent);
+ if (path != null) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private String checkPath(@Nullable String path, boolean silent)
+ throws AndroidLocationException {
+ if (path == null) {
+ return null;
+ }
+ File file = new File(path);
+ if (!file.isDirectory()) {
+ return null;
+ }
+ if (!(this == ANDROID_SDK_HOME && isSdkRootWithoutDotAndroid(file))) {
+ return path;
+ }
+ if (!silent) {
+ throw new AndroidLocationException(String.format(
+ "ANDROID_SDK_HOME is set to the root of your SDK: %1$s\n" +
+ "This is the path of the preference folder expected by the Android tools.\n" +
+ "It should NOT be set to the same as the root of your SDK.\n" +
+ "Please set it to a different folder or do not set it at all.\n" +
+ "If this is not set we default to: %2$s",
+ path, findValidPath(USER_HOME, HOME)));
+ }
+ return null;
+ }
+
+ private static boolean isSdkRootWithoutDotAndroid(@NonNull File folder) {
+ return subFolderExist(folder, "platforms") &&
+ subFolderExist(folder, "platform-tools") &&
+ !subFolderExist(folder, FOLDER_DOT_ANDROID);
+ }
+
+ private static boolean subFolderExist(@NonNull File folder, @NonNull String subFolder) {
+ return new File(folder, subFolder).isDirectory();
+ }
+ }
+
+ /**
+ * Returns the folder used to store android related files.
+ * If the folder is not created yet, it will be created here.
+ * @return an OS specific path, terminated by a separator.
+ * @throws AndroidLocationException
+ */
+ public static String getFolder() throws AndroidLocationException {
+ if (sPrefsLocation == null) {
+ sPrefsLocation = findHomeFolder();
+ }
+
+ // make sure the folder exists!
+ File f = new File(sPrefsLocation);
+ if (!f.exists()) {
+ try {
+ FileUtils.mkdirs(f);
+ } catch (SecurityException e) {
+ AndroidLocationException e2 = new AndroidLocationException(String.format(
+ "Unable to create folder '%1$s'. " +
+ "This is the path of preference folder expected by the Android tools.",
+ sPrefsLocation));
+ e2.initCause(e);
+ throw e2;
+ }
+ } else if (f.isFile()) {
+ throw new AndroidLocationException(String.format(
+ "%1$s is not a directory!\n" +
+ "This is the path of preference folder expected by the Android tools.", sPrefsLocation));
+ }
+ return sPrefsLocation;
+ }
+
+ /**
+ * Returns the folder used to store android related files.
+ * This method will not create the folder if it doesn't exist yet.\
+ *
+ * @return an OS specific path, terminated by a separator or null
+ * if no path is found or an error occurred.
+ */
+ public static String getFolderWithoutWrites() {
+ if (sPrefsLocation == null) {
+ try {
+ sPrefsLocation = findHomeFolder();
+ }
+ catch (AndroidLocationException e) {
+ return null;
+ }
+ }
+ return sPrefsLocation;
+ }
+
+
+ /**
+ * Check the if ANDROID_SDK_HOME variable points to a SDK.
+ * If it points to an SDK
+ * @throws AndroidLocationException
+ */
+ public static void checkAndroidSdkHome() throws AndroidLocationException {
+ EnvVar.ANDROID_SDK_HOME.validatePath(false);
+ }
+
+ /**
+ * Returns the folder where the users AVDs are stored.
+ * @return an OS specific path, terminated by a separator.
+ * @throws AndroidLocationException
+ */
+ @NonNull
+ public static String getAvdFolder() throws AndroidLocationException {
+ if (sAvdLocation == null) {
+ String home = findValidPath(EnvVar.ANDROID_AVD_HOME);
+ if (home == null) {
+ home = getFolder() + FOLDER_AVD;
+ }
+ sAvdLocation = home;
+ if (!sAvdLocation.endsWith(File.separator)) {
+ sAvdLocation += File.separator;
+ }
+ }
+ return sAvdLocation;
+ }
+
+ private static String findHomeFolder()
+ throws AndroidLocationException {
+ String home = findValidPath(EnvVar.ANDROID_SDK_HOME, EnvVar.USER_HOME, EnvVar.HOME);
+
+ // if the above failed, we throw an exception.
+ if (home == null) {
+ throw new AndroidLocationException(
+ "Unable to get the Android SDK home directory.\n" +
+ "Make sure the environment variable ANDROID_SDK_HOME is set up.");
+ }
+ if (!home.endsWith(File.separator)) {
+ home += File.separator;
+ }
+ return home + FOLDER_DOT_ANDROID + File.separator;
+ }
+
+ /**
+ * Resets the folder used to store android related files. For testing.
+ */
+ public static void resetFolder() {
+ sPrefsLocation = null;
+ sAvdLocation = null;
+ }
+
+ /**
+ * Checks a list of system properties and/or system environment variables for validity,
+ * and returns the first one.
+ * @param vars The variables to check. Order does matter.
+ * @return the content of the first property/variable that is a valid directory.
+ */
+ @Nullable
+ private static String findValidPath(EnvVar... vars) throws AndroidLocationException {
+ for (EnvVar var : vars) {
+ String path = var.validatePath(true);
+ if (path != null) {
+ return path;
+ }
+ }
+ return null;
+ }
+}
diff --git a/common/src/main/java/com/android/sdklib/AndroidVersion.java b/common/src/main/java/com/android/sdklib/AndroidVersion.java
new file mode 100644
index 0000000..ed03951
--- /dev/null
+++ b/common/src/main/java/com/android/sdklib/AndroidVersion.java
@@ -0,0 +1,338 @@
+/*
+ * 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.sdklib;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.regex.Pattern;
+
+/**
+ * Represents the version of a target or device.
+ * <p/>
+ * A version is defined by an API level and an optional code name.
+ * <ul><li>Release versions of the Android platform are identified by their API level (integer),
+ * (technically the code name for release version is "REL" but this class will return
+ * <code>null<code> instead.)</li>
+ * <li>Preview versions of the platform are identified by a code name. Their API level
+ * is usually set to the value of the previous platform.</li></ul>
+ * <p/>
+ * While this class contains both values, its goal is to abstract them, so that code comparing 2+
+ * versions doesn't have to deal with the logic of handle both values.
+ * <p/>
+ * There are some cases where ones may want to access the values directly. This can be done
+ * with {@link #getApiLevel()} and {@link #getCodename()}.
+ * <p/>
+ * For generic UI display of the API version, {@link #getApiString()} is to be used.
+ */
+public final class AndroidVersion implements Comparable<AndroidVersion> {
+
+ private final int mApiLevel;
+ private final String mCodename;
+
+ /** The default AndroidVersion for minSdkVersion and targetSdkVersion if not specified. */
+ public static final AndroidVersion DEFAULT = new AndroidVersion(1, null);
+
+ /** First version to use ART by default. */
+ public static final AndroidVersion ART_RUNTIME = new AndroidVersion(21, null);
+
+ /**
+ * Thrown when an {@link AndroidVersion} object could not be created.
+ */
+ public static final class AndroidVersionException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ AndroidVersionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Creates an {@link AndroidVersion} with the given api level and codename.
+ * Codename should be null for a release version, otherwise it's a preview codename.
+ */
+ public AndroidVersion(int apiLevel, @Nullable String codename) {
+ mApiLevel = apiLevel;
+ mCodename = sanitizeCodename(codename);
+ }
+
+ /**
+ * Creates an {@link AndroidVersion} from a string that may be an integer API
+ * level or a string codename.
+ * <p/>
+ * <Em>Important</em>: An important limitation of this method is that cannot possible
+ * recreate the API level integer from a pure string codename. This is only OK to use
+ * if the caller can guarantee that only {@link #getApiString()} will be used later.
+ * Wrong things will happen if the caller then tries to resolve the numeric
+ * {@link #getApiLevel()}.
+ *
+ * @param apiOrCodename A non-null API integer or a codename in its "ALL_CAPS" format.
+ * "REL" is notable not a valid codename.
+ * @throws AndroidVersionException if the input isn't a pure integer or doesn't look like
+ * a valid string codename.
+ */
+ public AndroidVersion(@NonNull String apiOrCodename) throws AndroidVersionException {
+ int apiLevel = 0;
+ String codename = null;
+ try {
+ apiLevel = Integer.parseInt(apiOrCodename);
+ } catch (NumberFormatException ignore) {
+ // We don't know the API level. Android platform codenames are all caps.
+ // REL is a release-reserved keyword which we can use here.
+
+ if (!SdkConstants.CODENAME_RELEASE.equals(apiOrCodename)) {
+ if (Pattern.matches("[A-Z_]+", apiOrCodename)) {
+ codename = apiOrCodename;
+ }
+ }
+ }
+
+ mApiLevel = apiLevel;
+ mCodename = sanitizeCodename(codename);
+
+ if (mApiLevel <= 0 && codename == null) {
+ throw new AndroidVersionException(
+ "Invalid android API or codename " + apiOrCodename, //$NON-NLS-1$
+ null);
+ }
+ }
+
+ /**
+ * Returns the api level as an integer.
+ * <p/>For target that are in preview mode, this can be superseded by
+ * {@link #getCodename()}.
+ * <p/>To display the API level in the UI, use {@link #getApiString()}, which will use the
+ * codename if applicable.
+ * @see #getCodename()
+ * @see #getApiString()
+ */
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ /**
+ * Returns the API level as an integer. If this is a preview platform, it
+ * will return the expected final version of the API rather than the current API
+ * level. This is the "feature level" as opposed to the "release level" returned by
+ * {@link #getApiLevel()} in the sense that it is useful when you want
+ * to check the presence of a given feature from an API, and we consider the feature
+ * present in preview platforms as well.
+ *
+ * @return the API level of this version, +1 for preview platforms
+ */
+ public int getFeatureLevel() {
+ //noinspection VariableNotUsedInsideIf
+ return mCodename != null ? mApiLevel + 1 : mApiLevel;
+ }
+
+ /**
+ * Returns the version code name if applicable, null otherwise.
+ * <p/>If the codename is non null, then the API level should be ignored, and this should be
+ * used as a unique identifier of the target instead.
+ */
+ @Nullable
+ public String getCodename() {
+ return mCodename;
+ }
+
+ /**
+ * Returns a string representing the API level and/or the code name.
+ */
+ @NonNull
+ public String getApiString() {
+ if (mCodename != null) {
+ return mCodename;
+ }
+
+ return Integer.toString(mApiLevel);
+ }
+
+ /**
+ * Returns whether or not the version is a preview version.
+ */
+ public boolean isPreview() {
+ return mCodename != null;
+ }
+
+ /**
+ * Checks whether a device running a version similar to the receiver can run a project compiled
+ * for the given <var>version</var>.
+ * <p/>
+ * Be aware that this is not a perfect test, as other properties could break compatibility
+ * despite this method returning true.
+ * <p/>
+ * Nevertheless, when testing if an application can run on a device (where there is no
+ * access to the list of optional libraries), this method can give a good indication of whether
+ * there is a chance the application could run, or if there's a direct incompatibility.
+ */
+ public boolean canRun(@NonNull AndroidVersion appVersion) {
+ // if the application is compiled for a preview version, the device must be running exactly
+ // the same.
+ if (appVersion.mCodename != null) {
+ return appVersion.mCodename.equals(mCodename);
+ }
+
+ // otherwise, we check the api level (note that a device running a preview version
+ // will have the api level of the previous platform).
+ return mApiLevel >= appVersion.mApiLevel;
+ }
+
+ /**
+ * Returns <code>true</code> if the AndroidVersion is an API level equals to
+ * <var>apiLevel</var>.
+ */
+ public boolean equals(int apiLevel) {
+ return mCodename == null && apiLevel == mApiLevel;
+ }
+
+ /**
+ * Compares the receiver with either an {@link AndroidVersion} object or a {@link String}
+ * object.
+ * <p/>If <var>obj</var> is a {@link String}, then the method will first check if it's a string
+ * representation of a number, in which case it'll compare it to the api level. Otherwise, it'll
+ * compare it against the code name.
+ * <p/>For all other type of object give as parameter, this method will return
+ * <code>false</code>.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AndroidVersion) {
+ AndroidVersion version = (AndroidVersion)obj;
+
+ if (mCodename == null) {
+ return version.mCodename == null &&
+ mApiLevel == version.mApiLevel;
+ } else {
+ return mCodename.equals(version.mCodename) &&
+ mApiLevel == version.mApiLevel;
+ }
+
+ } else if (obj instanceof String) {
+ // if we have a code name, this must match.
+ if (mCodename != null) {
+ return mCodename.equals(obj);
+ }
+
+ // else we try to convert to a int and compare to the api level
+ try {
+ int value = Integer.parseInt((String)obj);
+ return value == mApiLevel;
+ } catch (NumberFormatException e) {
+ // not a number? we'll return false below.
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mCodename != null) {
+ return mCodename.hashCode();
+ }
+
+ // there may be some collisions between the hashcode of the codename and the api level
+ // but it's acceptable.
+ return mApiLevel;
+ }
+
+ /**
+ * Returns a string with the API Level and optional codename.
+ * Useful for debugging.
+ * For display purpose, please use {@link #getApiString()} instead.
+ */
+ @Override
+ public String toString() {
+ String s = String.format("API %1$d", mApiLevel); //$NON-NLS-1$
+ if (isPreview()) {
+ s += String.format(", %1$s preview", mCodename); //$NON-NLS-1$
+ }
+ return s;
+ }
+
+ /**
+ * Compares this object with the specified object for order. Returns a
+ * negative integer, zero, or a positive integer as this object is less
+ * than, equal to, or greater than the specified object.
+ *
+ * @param o the Object to be compared.
+ * @return a negative integer, zero, or a positive integer as this object is
+ * less than, equal to, or greater than the specified object.
+ */
+ @Override
+ public int compareTo(@NonNull AndroidVersion o) {
+ return compareTo(o.mApiLevel, o.mCodename);
+ }
+
+ public int compareTo(int apiLevel, @Nullable String codename) {
+ if (mCodename == null) {
+ if (codename == null) {
+ return mApiLevel - apiLevel;
+ } else {
+ if (mApiLevel == apiLevel) {
+ return -1; // same api level but argument is a preview for next version
+ }
+
+ return mApiLevel - apiLevel;
+ }
+ } else {
+ // 'this' is a preview
+ if (mApiLevel == apiLevel) {
+ if (codename == null) {
+ return +1;
+ } else {
+ return mCodename.compareTo(codename); // strange case where the 2 previews
+ // have different codename?
+ }
+ } else {
+ return mApiLevel - apiLevel;
+ }
+ }
+ }
+
+ /**
+ * Compares this version with the specified API and returns true if this version
+ * is greater or equal than the requested API -- that is the current version is a
+ * suitable min-api-level for the argument API.
+ */
+ public boolean isGreaterOrEqualThan(int api) {
+ return compareTo(api, null /*codename*/) >= 0;
+ }
+
+ /**
+ * Sanitizes the codename string according to the following rules:
+ * - A codename should be {@code null} for a release version or it should be a non-empty
+ * string for an actual preview.
+ * - In input, spacing is trimmed since it is irrelevant.
+ * - An empty string or the special codename "REL" means a release version
+ * and is converted to {@code null}.
+ *
+ * @param codename A possible-null codename.
+ * @return Null for a release version or a non-empty codename.
+ */
+ @Nullable
+ private static String sanitizeCodename(@Nullable String codename) {
+ if (codename != null) {
+ codename = codename.trim();
+ if (codename.isEmpty() || SdkConstants.CODENAME_RELEASE.equals(codename)) {
+ codename = null;
+ }
+ }
+ return codename;
+ }
+}
diff --git a/base/common/src/main/java/com/android/utils/ArrayUtils.java b/common/src/main/java/com/android/utils/ArrayUtils.java
similarity index 100%
rename from base/common/src/main/java/com/android/utils/ArrayUtils.java
rename to common/src/main/java/com/android/utils/ArrayUtils.java
diff --git a/common/src/main/java/com/android/utils/AsmUtils.java b/common/src/main/java/com/android/utils/AsmUtils.java
new file mode 100644
index 0000000..a748cdf
--- /dev/null
+++ b/common/src/main/java/com/android/utils/AsmUtils.java
@@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Preconditions;
+
+/**
+ * Utilities for working with ASM.
+ */
+public class AsmUtils {
+ private AsmUtils() {}
+
+ public static final String CONSTRUCTOR = "<init>";
+ public static final String CLASS_INITIALIZER = "<clinit>";
+
+ /**
+ * Converts a class name from the Java language naming convention (foo.bar.baz) to the JVM
+ * internal naming convention (foo/bar/baz).
+ */
+ @NonNull
+ public static String toInternalName(@NonNull String className) {
+ return className.replace('.', '/');
+ }
+
+ /**
+ * Gets the class name from a class member internal name, like {@code com/foo/Bar.baz:(I)V}.
+ */
+ @NonNull
+ public static String getClassName(@NonNull String memberName) {
+ Preconditions.checkArgument(memberName.contains("."), "Class name passed as argument.");
+ return memberName.substring(0, memberName.indexOf('.'));
+ }
+}
diff --git a/common/src/main/java/com/android/utils/FileUtils.java b/common/src/main/java/com/android/utils/FileUtils.java
new file mode 100644
index 0000000..7cd718c
--- /dev/null
+++ b/common/src/main/java/com/android/utils/FileUtils.java
@@ -0,0 +1,373 @@
+/*
+ * 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.utils;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public final class FileUtils {
+
+ private FileUtils() {}
+
+ private static final Joiner PATH_JOINER = Joiner.on(File.separatorChar);
+ private static final Joiner COMMA_SEPARATED_JOINER = Joiner.on(", ");
+ private static final Joiner UNIX_NEW_LINE_JOINER = Joiner.on('\n');
+
+ public static final Function<File, String> GET_NAME = new Function<File, String>() {
+ @Override
+ public String apply(File file) {
+ return file.getName();
+ }
+ };
+
+ public static final Function<File, String> GET_PATH = new Function<File, String>() {
+ @Override
+ public String apply(File file) {
+ return file.getPath();
+ }
+ };
+
+ @NonNull
+ public static Predicate<File> withExtension(@NonNull final String extension) {
+ checkArgument(extension.charAt(0) != '.', "Extension should not start with a dot.");
+
+ return new Predicate<File>() {
+ @Override
+ public boolean apply(File input) {
+ return Files.getFileExtension(input.getName()).equals(extension);
+ }
+ };
+ }
+
+ public static void deleteFolder(@NonNull final File folder) throws IOException {
+ if (!folder.exists()) {
+ return;
+ }
+ File[] files = folder.listFiles();
+ if (files != null) { // i.e. is a directory.
+ for (final File file : files) {
+ deleteFolder(file);
+ }
+ }
+ if (!folder.delete()) {
+ throw new IOException(String.format("Could not delete folder %s", folder));
+ }
+ }
+
+ public static void emptyFolder(@NonNull final File folder) throws IOException {
+ deleteFolder(folder);
+ if (!folder.mkdirs()) {
+ throw new IOException(String.format("Could not create empty folder %s", folder));
+ }
+ }
+
+ public static void copy(@NonNull final File from, @NonNull final File toDir)
+ throws IOException {
+ File to = new File(toDir, from.getName());
+ if (from.isDirectory()) {
+ mkdirs(to);
+
+ File[] children = from.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ copy(child, to);
+ }
+ }
+ } else if (from.isFile()) {
+ Files.copy(from, to);
+ }
+ }
+
+ public static void mkdirs(@NonNull File folder) {
+ // attempt to create first.
+ // if failure only throw if folder does not exist.
+ // This makes this method able to create the same folder(s) from different thread
+ if (!folder.mkdirs() && !folder.exists()) {
+ throw new RuntimeException("Cannot create directory " + folder);
+ }
+ }
+
+ public static void delete(@NonNull File file) throws IOException {
+ boolean result = file.delete();
+ if (!result) {
+ throw new IOException("Failed to delete " + file.getAbsolutePath());
+ }
+ }
+
+ public static void deleteIfExists(@NonNull File file) throws IOException {
+ boolean result = file.delete();
+ if (!result && file.exists()) {
+ throw new IOException("Failed to delete " + file.getAbsolutePath());
+ }
+ }
+
+ public static void renameTo(@NonNull File file, @NonNull File to) throws IOException {
+ boolean result = file.renameTo(to);
+ if (!result) {
+ throw new IOException("Failed to rename " + file.getAbsolutePath() + " to " + to);
+ }
+ }
+
+ /**
+ * Joins a list of path segments to a given File object.
+ *
+ * @param dir the file object.
+ * @param paths the segments.
+ * @return a new File object.
+ */
+ @NonNull
+ public static File join(@NonNull File dir, @NonNull String... paths) {
+ if (paths.length == 0) {
+ return dir;
+ }
+
+ return new File(dir, PATH_JOINER.join(paths));
+ }
+
+ /**
+ * Joins a list of path segments to a given File object.
+ *
+ * @param dir the file object.
+ * @param paths the segments.
+ * @return a new File object.
+ */
+ @NonNull
+ public static File join(@NonNull File dir, @NonNull Iterable<String> paths) {
+ return new File(dir, PATH_JOINER.join(paths));
+ }
+
+ /**
+ * Joins a set of segment into a string, separating each segments with a host-specific
+ * path separator.
+ *
+ * @param paths the segments.
+ * @return a string with the segments.
+ */
+ @NonNull
+ public static String join(@NonNull String... paths) {
+ return PATH_JOINER.join(paths);
+ }
+
+ /**
+ * Joins a set of segment into a string, separating each segments with a host-specific
+ * path separator.
+ *
+ * @param paths the segments.
+ * @return a string with the segments.
+ */
+ @NonNull
+ public static String join(@NonNull Iterable<String> paths) {
+ return PATH_JOINER.join(paths);
+ }
+
+ /**
+ * Loads a text file forcing the line separator to be of Unix style '\n' rather than being
+ * Windows style '\r\n'.
+ */
+ @NonNull
+ public static String loadFileWithUnixLineSeparators(@NonNull File file) throws IOException {
+ return UNIX_NEW_LINE_JOINER.join(Files.readLines(file, Charsets.UTF_8));
+ }
+
+ /**
+ * Computes the relative of a file or directory with respect to a directory.
+ *
+ * @param file the file or directory, which must exist in the filesystem
+ * @param dir the directory to compute the path relative to
+ * @return the relative path from {@code dir} to {@code file}; if {@code file} is a directory
+ * the path comes appended with the file separator (see documentation on {@code relativize}
+ * on java's {@code URI} class)
+ */
+ @NonNull
+ public static String relativePath(@NonNull File file, @NonNull File dir) {
+ checkArgument(file.isFile() || file.isDirectory(), "%s is not a file nor a directory.",
+ file.getPath());
+ checkArgument(dir.isDirectory(), "%s is not a directory.", dir.getPath());
+ return relativePossiblyNonExistingPath(file, dir);
+ }
+
+ /**
+ * Computes the relative of a file or directory with respect to a directory.
+ * For example, if the file's absolute path is {@code /a/b/c} and the directory
+ * is {@code /a}, this method returns {@code b/c}.
+ *
+ * @param file the path that may not correspond to any existing path in the filesystem
+ * @param dir the directory to compute the path relative to
+ * @return the relative path from {@code dir} to {@code file}; if {@code file} is a directory
+ * the path comes appended with the file separator (see documentation on {@code relativize}
+ * on java's {@code URI} class)
+ */
+ @NonNull
+ public static String relativePossiblyNonExistingPath(@NonNull File file, @NonNull File dir) {
+ String path = dir.toURI().relativize(file.toURI()).getPath();
+ return toSystemDependentPath(path);
+ }
+
+ /**
+ * Converts a /-based path into a path using the system dependent separator.
+ * @param path the system independent path to convert
+ * @return the system dependent path
+ */
+ @NonNull
+ public static String toSystemDependentPath(@NonNull String path) {
+ if (File.separatorChar != '/') {
+ path = path.replace('/', File.separatorChar);
+ }
+ return path;
+ }
+
+ /**
+ * Converts a system-dependent path into a /-based path.
+ * @param path the system dependent path
+ * @return the system independent path
+ */
+ @NonNull
+ public static String toSystemIndependentPath(@NonNull String path) {
+ if (File.separatorChar != '/') {
+ path = path.replace(File.separatorChar, '/');
+ }
+ return path;
+ }
+
+ @NonNull
+ public static String sha1(@NonNull File file) throws IOException {
+ return Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
+ }
+
+ @NonNull
+ public static FluentIterable<File> getAllFiles(@NonNull File dir) {
+ return Files.fileTreeTraverser().preOrderTraversal(dir).filter(Files.isFile());
+ }
+
+ @NonNull
+ public static String getNamesAsCommaSeparatedList(@NonNull Iterable<File> files) {
+ return COMMA_SEPARATED_JOINER.join(Iterables.transform(files, GET_NAME));
+ }
+
+ /**
+ * Chooses a directory name, based on a JAR file name, considering exploded-aar and classes.jar.
+ */
+ @NonNull
+ public static String getDirectoryNameForJar(@NonNull File inputFile) {
+ // add a hash of the original file path.
+ HashFunction hashFunction = Hashing.sha1();
+ HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath(), Charsets.UTF_16LE);
+
+ String name = Files.getNameWithoutExtension(inputFile.getName());
+ if (name.equals("classes") && inputFile.getAbsolutePath().contains("exploded-aar")) {
+ // This naming scheme is coming from DependencyManager#computeArtifactPath.
+ File versionDir = inputFile.getParentFile().getParentFile();
+ File artifactDir = versionDir.getParentFile();
+ File groupDir = artifactDir.getParentFile();
+
+ name = Joiner.on('-').join(
+ groupDir.getName(), artifactDir.getName(), versionDir.getName());
+ }
+ name = name + "_" + hashCode.toString();
+ return name;
+ }
+
+ public static void createFile(@NonNull File file, @NonNull String content) throws IOException {
+ checkArgument(!file.exists(), "%s exists already.", file);
+
+ Files.createParentDirs(file);
+ Files.write(content, file, Charsets.UTF_8);
+ }
+
+ /**
+ * Find a list of files in a directory, using a specified path pattern.
+ */
+ public static List<File> find(@NonNull File base, @NonNull final Pattern pattern) {
+ checkArgument(base.isDirectory(), "'base' must be a directory.");
+ return Files.fileTreeTraverser()
+ .preOrderTraversal(base)
+ .filter(Predicates.compose(Predicates.contains(pattern), GET_PATH))
+ .toList();
+ }
+
+ /**
+ * Find a file with the specified name in a given directory .
+ */
+ public static Optional<File> find(@NonNull File base, @NonNull final String name) {
+ checkArgument(base.isDirectory(), "'base' must be a directory.");
+ return Files.fileTreeTraverser()
+ .preOrderTraversal(base)
+ .filter(Predicates.compose(Predicates.equalTo(name), GET_NAME))
+ .last();
+ }
+
+ /**
+ * Reads a portion of a file to memory.
+ *
+ * @param file the file to read data from
+ * @param start the offset in the file to start reading
+ * @param length the number of bytes to read
+ * @return the bytes read
+ * @throws Exception failed to read the file
+ */
+ @NonNull
+ public static byte[] readSegment(@NonNull File file, long start, int length) throws Exception {
+ Preconditions.checkArgument(start >= 0, "start < 0");
+ Preconditions.checkArgument(length >= 0, "length < 0");
+
+ byte data[];
+ boolean threw = true;
+ RandomAccessFile raf = new RandomAccessFile(file, "r");
+ try {
+ raf.seek(start);
+
+ data = new byte[length];
+ int tot = 0;
+ while (tot < length) {
+ int r = raf.read(data, tot, length - tot);
+ if (r < 0) {
+ throw new EOFException();
+ }
+
+ tot += r;
+ }
+
+ threw = false;
+ } finally {
+ Closeables.close(raf, threw);
+ }
+
+ return data;
+ }
+}
diff --git a/base/common/src/main/java/com/android/utils/GrabProcessOutput.java b/common/src/main/java/com/android/utils/GrabProcessOutput.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/common/src/main/java/com/android/utils/GrabProcessOutput.java
rename to common/src/main/java/com/android/utils/GrabProcessOutput.java
diff --git a/common/src/main/java/com/android/utils/HtmlBuilder.java b/common/src/main/java/com/android/utils/HtmlBuilder.java
new file mode 100644
index 0000000..bc1f1b2
--- /dev/null
+++ b/common/src/main/java/com/android/utils/HtmlBuilder.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.utils;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.net.URL;
+
+public class HtmlBuilder {
+ @NonNull private final StringBuilder mStringBuilder;
+ private String mTableDataExtra;
+
+ public HtmlBuilder(@NonNull StringBuilder stringBuilder) {
+ mStringBuilder = stringBuilder;
+ }
+
+ public HtmlBuilder() {
+ mStringBuilder = new StringBuilder(100);
+ }
+
+ public HtmlBuilder openHtmlBody() {
+ addHtml("<html><body>");
+
+ return this;
+ }
+
+ public HtmlBuilder closeHtmlBody() {
+ addHtml("</body></html>");
+
+ return this;
+ }
+
+ public HtmlBuilder addHtml(@NonNull String html) {
+ mStringBuilder.append(html);
+
+ return this;
+ }
+
+ public HtmlBuilder addNbsp() {
+ mStringBuilder.append(" ");
+
+ return this;
+ }
+
+ public HtmlBuilder addNbsps(int count) {
+ for (int i = 0; i < count; i++) {
+ addNbsp();
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder newline() {
+ mStringBuilder.append("<BR/>");
+
+ return this;
+ }
+
+ public HtmlBuilder newlineIfNecessary() {
+ if (!SdkUtils.endsWith(mStringBuilder, "<BR/>")) {
+ mStringBuilder.append("<BR/>");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder addLink(@Nullable String textBefore,
+ @NonNull String linkText,
+ @Nullable String textAfter,
+ @NonNull String url) {
+ if (textBefore != null) {
+ add(textBefore);
+ }
+
+ addLink(linkText, url);
+
+ if (textAfter != null) {
+ add(textAfter);
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder addLink(@NonNull String text, @NonNull String url) {
+ int begin = 0;
+ int length = text.length();
+ for (; begin < length; begin++) {
+ char c = text.charAt(begin);
+ if (Character.isWhitespace(c)) {
+ mStringBuilder.append(c);
+ } else {
+ break;
+ }
+ }
+ mStringBuilder.append("<A HREF=\"");
+ mStringBuilder.append(url);
+ mStringBuilder.append("\">");
+
+ XmlUtils.appendXmlTextValue(mStringBuilder, text.trim());
+ mStringBuilder.append("</A>");
+
+ int end = length - 1;
+ for (; end > begin; end--) {
+ char c = text.charAt(begin);
+ if (Character.isWhitespace(c)) {
+ mStringBuilder.append(c);
+ }
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder add(@NonNull String text) {
+ XmlUtils.appendXmlTextValue(mStringBuilder, text);
+
+ return this;
+ }
+
+ @NonNull
+ public String getHtml() {
+ return mStringBuilder.toString();
+ }
+
+ public HtmlBuilder beginBold() {
+ mStringBuilder.append("<B>");
+
+ return this;
+ }
+
+ public HtmlBuilder endBold() {
+ mStringBuilder.append("</B>");
+
+ return this;
+ }
+
+ public HtmlBuilder addBold(String text) {
+ beginBold();
+ add(text);
+ endBold();
+
+ return this;
+ }
+
+ public HtmlBuilder beginItalic() {
+ mStringBuilder.append("<I>");
+
+ return this;
+ }
+
+ public HtmlBuilder endItalic() {
+ mStringBuilder.append("</I>");
+
+ return this;
+ }
+
+ public HtmlBuilder addItalic(String text) {
+ beginItalic();
+ add(text);
+ endItalic();
+
+ return this;
+ }
+
+ private HtmlBuilder appendStyle(@Nullable String cssStyle) {
+ if (cssStyle != null) {
+ mStringBuilder.append(" style=\"");
+ mStringBuilder.append(cssStyle);
+ mStringBuilder.append("\"");
+ }
+ return this;
+ }
+
+ public HtmlBuilder beginDiv() {
+ return beginDiv(null);
+ }
+
+ public HtmlBuilder beginDiv(@Nullable String cssStyle) {
+ mStringBuilder.append("<div");
+ appendStyle(cssStyle);
+ mStringBuilder.append('>');
+ return this;
+ }
+
+ public HtmlBuilder endDiv() {
+ mStringBuilder.append("</div>");
+ return this;
+ }
+
+
+ public HtmlBuilder beginSpan() {
+ return beginSpan(null);
+ }
+
+ public HtmlBuilder beginSpan(@Nullable String cssStyle) {
+ mStringBuilder.append("<span");
+ appendStyle(cssStyle);
+ mStringBuilder.append('>');
+ return this;
+ }
+
+ public HtmlBuilder endSpan() {
+ mStringBuilder.append("</span>");
+ return this;
+ }
+
+ public HtmlBuilder addHeading(@NonNull String text, @NonNull String fontColor) {
+ mStringBuilder.append("<font");
+ appendStyle("font-weight:bold; color:" + fontColor + ";");
+ mStringBuilder.append(">");
+ add(text);
+ mStringBuilder.append("</font>");
+ return this;
+ }
+
+ /**
+ * The JEditorPane HTML renderer creates really ugly bulleted lists; the
+ * size is hardcoded to use a giant heavy bullet. So, use a definition
+ * list instead.
+ */
+ private static final boolean USE_DD_LISTS = true;
+
+ public HtmlBuilder beginList() {
+ if (USE_DD_LISTS) {
+ mStringBuilder.append("<DL>");
+ } else {
+ mStringBuilder.append("<UL>");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder endList() {
+ if (USE_DD_LISTS) {
+ mStringBuilder.append("</DL>");
+ } else {
+ mStringBuilder.append("</UL>");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder listItem() {
+ if (USE_DD_LISTS) {
+ mStringBuilder.append("<DD>");
+ mStringBuilder.append("-&NBSP;");
+ } else {
+ mStringBuilder.append("<LI>");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder addImage(URL url, @Nullable String altText) {
+ String link = "";
+ try {
+ link = url.toURI().toURL().toExternalForm();
+ }
+ catch (Throwable t) {
+ // pass
+ }
+ mStringBuilder.append("<img src='");
+ mStringBuilder.append(link);
+ mStringBuilder.append("'");
+
+ if (altText != null) {
+ mStringBuilder.append(" alt=\"");
+ mStringBuilder.append(altText);
+ mStringBuilder.append("\"");
+ }
+ mStringBuilder.append(" />");
+
+ return this;
+ }
+
+ public HtmlBuilder addIcon(@Nullable String src) {
+ if (src != null) {
+ mStringBuilder.append("<img src='");
+ mStringBuilder.append(src);
+ mStringBuilder.append("' width=16 height=16 border=0 />");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder beginTable(@Nullable String tdExtra) {
+ mStringBuilder.append("<table>");
+ mTableDataExtra = tdExtra;
+ return this;
+ }
+
+ public HtmlBuilder beginTable() {
+ return beginTable(null);
+ }
+
+ public HtmlBuilder endTable() {
+ mStringBuilder.append("</table>");
+ return this;
+ }
+
+ public HtmlBuilder beginTableRow() {
+ mStringBuilder.append("<tr>");
+ return this;
+ }
+
+ public HtmlBuilder endTableRow() {
+ mStringBuilder.append("</tr>");
+ return this;
+ }
+
+ public HtmlBuilder addTableRow(boolean isHeader, String... columns) {
+ if (columns == null || columns.length == 0) {
+ return this;
+ }
+
+ String tag = "t" + (isHeader ? 'h' : 'd');
+
+ beginTableRow();
+ for (String c : columns) {
+ mStringBuilder.append('<');
+ mStringBuilder.append(tag);
+ if (mTableDataExtra != null) {
+ mStringBuilder.append(' ');
+ mStringBuilder.append(mTableDataExtra);
+ }
+ mStringBuilder.append('>');
+
+ mStringBuilder.append(c);
+
+ mStringBuilder.append("</");
+ mStringBuilder.append(tag);
+ mStringBuilder.append('>');
+ }
+ endTableRow();
+
+ return this;
+ }
+
+ public HtmlBuilder addTableRow(String... columns) {
+ return addTableRow(false, columns);
+ }
+
+ @NonNull
+ public StringBuilder getStringBuilder() {
+ return mStringBuilder;
+ }
+}
diff --git a/base/common/src/main/java/com/android/utils/ILogger.java b/common/src/main/java/com/android/utils/ILogger.java
similarity index 100%
rename from base/common/src/main/java/com/android/utils/ILogger.java
rename to common/src/main/java/com/android/utils/ILogger.java
diff --git a/base/common/src/main/java/com/android/utils/IReaderLogger.java b/common/src/main/java/com/android/utils/IReaderLogger.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/common/src/main/java/com/android/utils/IReaderLogger.java
rename to common/src/main/java/com/android/utils/IReaderLogger.java
diff --git a/base/common/src/main/java/com/android/utils/NullLogger.java b/common/src/main/java/com/android/utils/NullLogger.java
similarity index 100%
rename from base/common/src/main/java/com/android/utils/NullLogger.java
rename to common/src/main/java/com/android/utils/NullLogger.java
diff --git a/base/common/src/main/java/com/android/utils/Pair.java b/common/src/main/java/com/android/utils/Pair.java
similarity index 100%
rename from base/common/src/main/java/com/android/utils/Pair.java
rename to common/src/main/java/com/android/utils/Pair.java
diff --git a/common/src/main/java/com/android/utils/PositionXmlParser.java b/common/src/main/java/com/android/utils/PositionXmlParser.java
new file mode 100644
index 0000000..f70d642
--- /dev/null
+++ b/common/src/main/java/com/android/utils/PositionXmlParser.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.utils;
+
+import static com.android.SdkConstants.UTF_8;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourcePosition;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.DefaultHandler2;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * A simple DOM XML parser which can retrieve exact beginning and end offsets
+ * (and line and column numbers) for element nodes as well as attribute nodes.
+ */
+public class PositionXmlParser {
+ private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$
+ private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$
+ private static final String CONTENT_KEY = "contents"; //$NON-NLS-1$
+ private static final String POS_KEY = "offsets"; //$NON-NLS-1$
+ private static final String NAMESPACE_PREFIX_FEATURE =
+ "http://xml.org/sax/features/namespace-prefixes"; //$NON-NLS-1$
+ private static final String NAMESPACE_FEATURE =
+ "http://xml.org/sax/features/namespaces"; //$NON-NLS-1$
+ private static final String PROVIDE_XMLNS_URIS =
+ "http://xml.org/sax/features/xmlns-uris"; //$NON-NLS-1$
+ /** See http://www.w3.org/TR/REC-xml/#NT-EncodingDecl */
+ private static final Pattern ENCODING_PATTERN =
+ Pattern.compile("encoding=['\"](\\S*)['\"]"); //$NON-NLS-1$
+ private static final String LOAD_EXTERNAL_DTD =
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd";; //$NON-NLS-1$
+
+ /**
+ * Parses the XML content from the given input stream.
+ *
+ * @param input the input stream containing the XML to be parsed
+ * @param checkDtd whether or not download the DTD and validate it
+ * @return the corresponding document
+ * @throws ParserConfigurationException if a SAX parser is not available
+ * @throws SAXException if the document contains a parsing error
+ * @throws IOException if something is seriously wrong. This should not
+ * happen since the input source is known to be constructed from
+ * a string.
+ */
+ @NonNull
+ public static Document parse(@NonNull InputStream input, boolean checkDtd)
+ throws ParserConfigurationException, SAXException, IOException {
+ // Read in all the data
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buf = new byte[1024];
+ while (true) {
+ int r = input.read(buf);
+ if (r == -1) {
+ break;
+ }
+ out.write(buf, 0, r);
+ }
+ input.close();
+ return parse(out.toByteArray(), checkDtd);
+ }
+
+ /**
+ * @see PositionXmlParser#parse(InputStream, boolean)
+ */
+ @NonNull
+ public static Document parse(@NonNull InputStream input)
+ throws IOException, SAXException, ParserConfigurationException {
+ return parse(input, true);
+ }
+
+ /**
+ * @see PositionXmlParser#parse(byte[], boolean)
+ */
+ @NonNull
+ public static Document parse(@NonNull byte[] data)
+ throws ParserConfigurationException, SAXException, IOException {
+ return parse(data, true);
+ }
+
+ /**
+ * Parses the XML content from the given byte array
+ *
+ * @param data the raw XML data (with unknown encoding)
+ * @param checkDtd whether or not download the DTD and validate it
+ * @return the corresponding document
+ * @throws ParserConfigurationException if a SAX parser is not available
+ * @throws SAXException if the document contains a parsing error
+ * @throws IOException if something is seriously wrong. This should not
+ * happen since the input source is known to be constructed from
+ * a string.
+ */
+ @NonNull
+ public static Document parse(@NonNull byte[] data, boolean checkDtd)
+ throws ParserConfigurationException, SAXException, IOException {
+ String xml = getXmlString(data);
+ xml = XmlUtils.stripBom(xml);
+ return parse(xml, new InputSource(new StringReader(xml)), true, checkDtd);
+ }
+
+ /**
+ * Parses the given XML content.
+ *
+ * @param xml the XML string to be parsed. This must be in the correct
+ * encoding already.
+ * @return the corresponding document
+ * @throws ParserConfigurationException if a SAX parser is not available
+ * @throws SAXException if the document contains a parsing error
+ * @throws IOException if something is seriously wrong. This should not
+ * happen since the input source is known to be constructed from
+ * a string.
+ */
+ @NonNull
+ public static Document parse(@NonNull String xml)
+ throws ParserConfigurationException, SAXException, IOException {
+ xml = XmlUtils.stripBom(xml);
+ return parse(xml, new InputSource(new StringReader(xml)), true, true);
+ }
+
+ @NonNull
+ private static Document parse(@NonNull String xml, @NonNull InputSource input, boolean checkBom,
+ boolean checkDtd)
+ throws ParserConfigurationException, SAXException, IOException {
+ try {
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ if (checkDtd) {
+ factory.setFeature(NAMESPACE_FEATURE, true);
+ factory.setFeature(NAMESPACE_PREFIX_FEATURE, true);
+ factory.setFeature(PROVIDE_XMLNS_URIS, true);
+ } else {
+ factory.setFeature(LOAD_EXTERNAL_DTD, false);
+ }
+ SAXParser parser = factory.newSAXParser();
+ DomBuilder handler = new DomBuilder(xml);
+ XMLReader xmlReader = parser.getXMLReader();
+ xmlReader.setProperty(
+ "http://xml.org/sax/properties/lexical-handler",
+ handler
+ );
+ parser.parse(input, handler);
+ return handler.getDocument();
+ } catch (SAXException e) {
+ if (checkBom && e.getMessage().contains("Content is not allowed in prolog")) {
+ // Byte order mark in the string? Skip it. There are many markers
+ // (see http://en.wikipedia.org/wiki/Byte_order_mark) so here we'll
+ // just skip those up to the XML prolog beginning character, <
+ xml = xml.replaceFirst("^([\\W]+)<","<"); //$NON-NLS-1$ //$NON-NLS-2$
+ return parse(xml, new InputSource(new StringReader(xml)), false, checkDtd);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the String corresponding to the given byte array of XML data
+ * (with unknown encoding). This method attempts to guess the encoding based
+ * on the XML prologue.
+ * @param data the XML data to be decoded into a string
+ * @return a string corresponding to the XML data
+ */
+ @NonNull
+ public static String getXmlString(@NonNull byte[] data) {
+ return getXmlString(data, UTF_8);
+ }
+
+ /**
+ * Returns the String corresponding to the given byte array of XML data
+ * (with unknown encoding). This method attempts to guess the encoding based
+ * on the XML prologue.
+ * @param data the XML data to be decoded into a string
+ * @param defaultCharset the default charset to use if not specified by an encoding prologue
+ * attribute or a byte order mark
+ * @return a string corresponding to the XML data
+ */
+ @NonNull
+ public static String getXmlString(@NonNull byte[] data, @NonNull String defaultCharset) {
+ int offset = 0;
+
+ String charset = null;
+ // Look for the byte order mark, to see if we need to remove bytes from
+ // the input stream (and to determine whether files are big endian or little endian) etc
+ // for files which do not specify the encoding.
+ // See http://unicode.org/faq/utf_bom.html#BOM for more.
+ if (data.length > 4) {
+ if (data[0] == (byte)0xef && data[1] == (byte)0xbb && data[2] == (byte)0xbf) {
+ // UTF-8
+ defaultCharset = charset = UTF_8;
+ offset += 3;
+ } else if (data[0] == (byte)0xfe && data[1] == (byte)0xff) {
+ // UTF-16, big-endian
+ defaultCharset = charset = UTF_16;
+ offset += 2;
+ } else if (data[0] == (byte)0x0 && data[1] == (byte)0x0
+ && data[2] == (byte)0xfe && data[3] == (byte)0xff) {
+ // UTF-32, big-endian
+ defaultCharset = charset = "UTF_32"; //$NON-NLS-1$
+ offset += 4;
+ } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe
+ && data[2] == (byte)0x0 && data[3] == (byte)0x0) {
+ // UTF-32, little-endian. We must check for this *before* looking for
+ // UTF_16LE since UTF_32LE has the same prefix!
+ defaultCharset = charset = "UTF_32LE"; //$NON-NLS-1$
+ offset += 4;
+ } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe) {
+ // UTF-16, little-endian
+ defaultCharset = charset = UTF_16LE;
+ offset += 2;
+ }
+ }
+ int length = data.length - offset;
+
+ // Guess encoding by searching for an encoding= entry in the first line.
+ // The prologue, and the encoding names, will always be in ASCII - which means
+ // we don't need to worry about strange character encodings for the prologue characters.
+ // However, one wrinkle is that the whole file may be encoded in something like UTF-16
+ // where there are two bytes per character, so we can't just look for
+ // ['e','n','c','o','d','i','n','g'] etc in the byte array since there could be
+ // multiple bytes for each character. However, since again the prologue is in ASCII,
+ // we can just drop the zeroes.
+ boolean seenOddZero = false;
+ boolean seenEvenZero = false;
+ int prologueStart = -1;
+ for (int lineEnd = offset; lineEnd < data.length; lineEnd++) {
+ if (data[lineEnd] == 0) {
+ if ((lineEnd - offset) % 2 == 0) {
+ seenEvenZero = true;
+ } else {
+ seenOddZero = true;
+ }
+ } else if (data[lineEnd] == '\n' || data[lineEnd] == '\r') {
+ break;
+ } else if (data[lineEnd] == '<') {
+ prologueStart = lineEnd;
+ } else if (data[lineEnd] == '>') {
+ // End of prologue. Quick check to see if this is a utf-8 file since that's
+ // common
+ for (int i = lineEnd - 4; i >= 0; i--) {
+ if ((data[i] == 'u' || data[i] == 'U')
+ && (data[i + 1] == 't' || data[i + 1] == 'T')
+ && (data[i + 2] == 'f' || data[i + 2] == 'F')
+ && (data[i + 3] == '-' || data[i + 3] == '_')
+ && (data[i + 4] == '8')
+ ) {
+ charset = UTF_8;
+ break;
+ }
+ }
+
+ if (charset == null) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = prologueStart; i <= lineEnd; i++) {
+ if (data[i] != 0) {
+ sb.append((char) data[i]);
+ }
+ }
+ String prologue = sb.toString();
+ int encodingIndex = prologue.indexOf("encoding"); //$NON-NLS-1$
+ if (encodingIndex != -1) {
+ Matcher matcher = ENCODING_PATTERN.matcher(prologue);
+ if (matcher.find(encodingIndex)) {
+ charset = matcher.group(1);
+ }
+ }
+ }
+
+ break;
+ }
+ }
+
+ // No prologue on the first line, and no byte order mark: Assume UTF-8/16
+ if (charset == null) {
+ charset = seenOddZero ? UTF_16LE : seenEvenZero ? UTF_16 : defaultCharset;
+ }
+
+ String xml = null;
+ try {
+ xml = new String(data, offset, length, charset);
+ } catch (UnsupportedEncodingException e) {
+ try {
+ if (!charset.equals(defaultCharset)) {
+ xml = new String(data, offset, length, defaultCharset);
+ }
+ } catch (UnsupportedEncodingException u) {
+ // Just use the default encoding below
+ }
+ }
+ if (xml == null) {
+ xml = new String(data, offset, length);
+ }
+ return xml;
+ }
+
+ /**
+ * Returns the position for the given node. This is the start position. The
+ * end position can be obtained via {@link Position#getEnd()}.
+ *
+ * @param node the node to look up position for
+ * @return the position, or null if the node type is not supported for
+ * position info
+ */
+ @NonNull
+ public static SourcePosition getPosition(@NonNull Node node) {
+ return getPosition(node, -1, -1);
+ }
+
+ /**
+ * Returns the position for the given node. This is the start position. The
+ * end position can be obtained via {@link Position#getEnd()}. A specific
+ * range within the node can be specified with the {@code start} and
+ * {@code end} parameters.
+ *
+ * @param node the node to look up position for
+ * @param start the relative offset within the node range to use as the
+ * starting position, inclusive, or -1 to not limit the range
+ * @param end the relative offset within the node range to use as the ending
+ * position, or -1 to not limit the range
+ * @return the position, or null if the node type is not supported for
+ * position info
+ */
+
+ @NonNull
+ public static SourcePosition getPosition(@NonNull Node node, int start, int end) {
+ Position p = getPositionHelper(node, start, end);
+ return p == null ? SourcePosition.UNKNOWN : p.toSourcePosition();
+ }
+
+ @Nullable
+ private static Position getPositionHelper(@NonNull Node node, int start, int end) {
+ // Look up the position information stored while parsing for the given node.
+ // Note however that we only store position information for elements (because
+ // there is no SAX callback for individual attributes).
+ // Therefore, this method special cases this:
+ // -- First, it looks at the owner element and uses its position
+ // information as a first approximation.
+ // -- Second, it uses that, as well as the original XML text, to search
+ // within the node range for an exact text match on the attribute name
+ // and if found uses that as the exact node offsets instead.
+ if (node instanceof Attr) {
+ Attr attr = (Attr) node;
+ Position pos = (Position) attr.getOwnerElement().getUserData(POS_KEY);
+ if (pos != null) {
+ int startOffset = pos.getOffset();
+ int endOffset = pos.getEnd().getOffset();
+ if (start != -1) {
+ startOffset += start;
+ if (end != -1) {
+ endOffset = startOffset + (end - start);
+ }
+ }
+
+ // Find attribute in the text
+ String contents = (String) node.getOwnerDocument().getUserData(CONTENT_KEY);
+ if (contents == null) {
+ return null;
+ }
+
+ // Locate the name=value attribute in the source text
+ // Fast string check first for the common occurrence
+ String name = attr.getName();
+ Pattern pattern = Pattern.compile(attr.getPrefix() != null
+ ? String.format("(%1$s\\s*=\\s*[\"'].*?[\"'])", name) //$NON-NLS-1$
+ : String.format("[^:](%1$s\\s*=\\s*[\"'].*?[\"'])", name));//$NON-NLS-1$
+ Matcher matcher = pattern.matcher(contents);
+ if (matcher.find(startOffset) && matcher.start(1) <= endOffset) {
+ int index = matcher.start(1);
+ // Adjust the line and column to this new offset
+ int line = pos.getLine();
+ int column = pos.getColumn();
+ for (int offset = pos.getOffset(); offset < index; offset++) {
+ char t = contents.charAt(offset);
+ if (t == '\n') {
+ line++;
+ column = 0;
+ } else {
+ column++;
+ }
+ }
+
+ Position attributePosition = new Position(line, column, index);
+ // Also set end range for retrieval in getLocation
+ attributePosition.setEnd(
+ new Position(line, column + matcher.end(1) - index, matcher.end(1)));
+ return attributePosition;
+ } else {
+ // No regexp match either: just fall back to element position
+ return pos;
+ }
+ }
+ } else if (node instanceof Text) {
+ // Position of parent element, if any
+ Position pos = null;
+ if (node.getPreviousSibling() != null) {
+ pos = (Position) node.getPreviousSibling().getUserData(POS_KEY);
+ }
+ if (pos == null) {
+ pos = (Position) node.getParentNode().getUserData(POS_KEY);
+ }
+ if (pos != null) {
+ // Attempt to point forward to the actual text node
+ int startOffset = pos.getOffset();
+ int endOffset = pos.getEnd().getOffset();
+ int line = pos.getLine();
+ int column = pos.getColumn();
+
+ // Find attribute in the text
+ String contents = (String) node.getOwnerDocument().getUserData(CONTENT_KEY);
+ if (contents == null || contents.length() < endOffset) {
+ return null;
+ }
+
+ boolean inAttribute = false;
+ for (int offset = startOffset; offset <= endOffset; offset++) {
+ char c = contents.charAt(offset);
+ if (c == '>' && !inAttribute) {
+ // Found the end of the element open tag: this is where the
+ // text begins.
+
+ // Skip >
+ offset++;
+ column++;
+
+ String text = node.getNodeValue();
+ int textIndex = 0;
+ int textLength = text.length();
+ int newLine = line;
+ int newColumn = column;
+ if (start != -1) {
+ textLength = Math.min(textLength, start);
+ for (; textIndex < textLength; textIndex++) {
+ char t = text.charAt(textIndex);
+ if (t == '\n') {
+ newLine++;
+ newColumn = 0;
+ } else {
+ newColumn++;
+ }
+ }
+ } else {
+ // Skip text whitespace prefix, if the text node contains
+ // non-whitespace characters
+ for (; textIndex < textLength; textIndex++) {
+ char t = text.charAt(textIndex);
+ if (t == '\n') {
+ newLine++;
+ newColumn = 0;
+ } else if (!Character.isWhitespace(t)) {
+ break;
+ } else {
+ newColumn++;
+ }
+ }
+ }
+ if (textIndex == text.length()) {
+ textIndex = 0; // Whitespace node
+ } else {
+ line = newLine;
+ column = newColumn;
+ }
+
+ Position attributePosition = new Position(line, column, offset + textIndex);
+ // Also set end range for retrieval in getLocation
+ if (end != -1) {
+ attributePosition.setEnd(new Position(line, column, offset + end));
+ } else {
+ attributePosition.setEnd(
+ new Position(line, column, offset + textLength));
+ }
+ return attributePosition;
+ } else if (c == '"') {
+ inAttribute = !inAttribute;
+ } else if (c == '\n') {
+ line++;
+ column = -1; // pre-subtract column added below
+ }
+ column++;
+ }
+
+ return pos;
+ }
+ }
+
+ return (Position) node.getUserData(POS_KEY);
+ }
+
+ /**
+ * SAX parser handler which incrementally builds up a DOM document as we go
+ * along, and updates position information along the way. Position
+ * information is attached to the DOM nodes by setting user data with the
+ * {@link #POS_KEY} key.
+ */
+ private static final class DomBuilder extends DefaultHandler2 {
+ private final String mXml;
+ private final Document mDocument;
+ private Locator mLocator;
+ private int mCurrentLine = 0;
+ private int mCurrentOffset;
+ private int mCurrentColumn;
+ private final List<Element> mStack = new ArrayList<Element>();
+ private final StringBuilder mPendingText = new StringBuilder();
+
+ private DomBuilder(String xml) throws ParserConfigurationException {
+ mXml = xml;
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder docBuilder = factory.newDocumentBuilder();
+ mDocument = docBuilder.newDocument();
+ mDocument.setUserData(CONTENT_KEY, xml, null);
+ }
+
+ /** Returns the document parsed by the handler */
+ @NonNull
+ Document getDocument() {
+ return mDocument;
+ }
+
+ @Override
+ public void setDocumentLocator(Locator locator) {
+ this.mLocator = locator;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+ try {
+ flushText();
+ Element element = mDocument.createElementNS(uri, qName);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ if (attributes.getURI(i) != null && !attributes.getURI(i).isEmpty()) {
+ Attr attr = mDocument.createAttributeNS(attributes.getURI(i),
+ attributes.getQName(i));
+ attr.setValue(attributes.getValue(i));
+ element.setAttributeNodeNS(attr);
+ assert attr.getOwnerElement() == element;
+ } else {
+ Attr attr = mDocument.createAttribute(attributes.getQName(i));
+ attr.setValue(attributes.getValue(i));
+ element.setAttributeNode(attr);
+ assert attr.getOwnerElement() == element;
+ }
+ }
+
+ Position pos = getCurrentPosition();
+
+ // The starting position reported to us by SAX is really the END of the
+ // open tag in an element, when all the attributes have been processed.
+ // We have to scan backwards to find the real beginning. We'll do that
+ // by scanning backwards.
+ // -1: Make sure that when we have <foo></foo> we don't consider </foo>
+ // the beginning since pos.offset will typically point to the first character
+ // AFTER the element open tag, which could be a closing tag or a child open
+ // tag
+ element.setUserData(POS_KEY, findOpeningTag(pos), null);
+ mStack.add(element);
+ } catch (Exception t) {
+ throw new SAXException(t);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ flushText();
+ Element element = mStack.remove(mStack.size() - 1);
+
+ Position pos = (Position) element.getUserData(POS_KEY);
+ assert pos != null;
+ pos.setEnd(getCurrentPosition());
+
+ addNodeToParent(element);
+ }
+
+ @Override
+ public void comment(char[] chars, int start, int length) throws SAXException {
+
+ flushText();
+ String comment = new String(chars, start, length);
+ Comment domComment = mDocument.createComment(comment);
+
+ // current position is the closing comment tag.
+ Position currentPosition = getCurrentPosition();
+ Position startPosition = findOpeningTag(currentPosition);
+ startPosition.setEnd(currentPosition);
+
+ domComment.setUserData(POS_KEY, startPosition, null);
+ addNodeToParent(domComment);
+ }
+
+ /**
+ * Adds a node to the current parent element being visited, or to the document if there is
+ * no parent in context.
+ * @param nodeToAdd xml node to add.
+ */
+ private void addNodeToParent(Node nodeToAdd) {
+ if (mStack.isEmpty()){
+ mDocument.appendChild(nodeToAdd);
+ } else {
+ Element parent = mStack.get(mStack.size() - 1);
+ parent.appendChild(nodeToAdd);
+ }
+ }
+
+ /**
+ * Find opening tags from the current position.
+ * < cannot appear in attribute values or anywhere else within
+ * an element open tag, so we know the first occurrence is the real
+ * element start
+ * For comments, it is not legal to put < in a comment, however we are not
+ * validating so we will return an invalid column in that case.
+ * @param startingPosition the position to walk backwards until < is reached.
+ * @return the opening tag position or startPosition if cannot be found.
+ */
+ private Position findOpeningTag(Position startingPosition) {
+ for (int offset = startingPosition.getOffset() - 1; offset >= 0; offset--) {
+ char c = mXml.charAt(offset);
+
+ if (c == '<') {
+ // Adjust line position
+ int line = startingPosition.getLine();
+ for (int i = offset, n = startingPosition.getOffset(); i < n; i++) {
+ if (mXml.charAt(i) == '\n') {
+ line--;
+ }
+ }
+
+ // Compute new column position
+ int column = 0;
+ for (int i = offset - 1; i >= 0; i--, column++) {
+ if (mXml.charAt(i) == '\n') {
+ break;
+ }
+ }
+
+ return new Position(line, column, offset);
+ }
+ }
+ // we did not find it, approximate.
+ return startingPosition;
+ }
+
+ /**
+ * Returns a position holder for the current position. The most
+ * important part of this function is to incrementally compute the
+ * offset as well, by counting forwards until it reaches the new line
+ * number and column position of the XML parser, counting characters as
+ * it goes along.
+ */
+ private Position getCurrentPosition() {
+ int line = mLocator.getLineNumber() - 1;
+ int column = mLocator.getColumnNumber() - 1;
+
+ // Compute offset incrementally now that we have the new line and column
+ // numbers
+ int xmlLength = mXml.length();
+ while (mCurrentLine < line && mCurrentOffset < xmlLength) {
+ char c = mXml.charAt(mCurrentOffset);
+ if (c == '\r' && mCurrentOffset < xmlLength - 1) {
+ if (mXml.charAt(mCurrentOffset + 1) != '\n') {
+ mCurrentLine++;
+ mCurrentColumn = 0;
+ }
+ } else if (c == '\n') {
+ mCurrentLine++;
+ mCurrentColumn = 0;
+ } else {
+ mCurrentColumn++;
+ }
+ mCurrentOffset++;
+ }
+
+ mCurrentOffset += column - mCurrentColumn;
+ if (mCurrentOffset >= xmlLength) {
+ // The parser sometimes passes wrong column numbers at the
+ // end of the file: Ensure that the offset remains valid.
+ mCurrentOffset = xmlLength;
+ }
+ mCurrentColumn = column;
+
+ return new Position(mCurrentLine, mCurrentColumn, mCurrentOffset);
+ }
+
+ @Override
+ public void characters(char c[], int start, int length) throws SAXException {
+ mPendingText.append(c, start, length);
+ }
+
+ private void flushText() {
+ if (mPendingText.length() > 0 && !mStack.isEmpty()) {
+ Element element = mStack.get(mStack.size() - 1);
+ Node textNode = mDocument.createTextNode(mPendingText.toString());
+ element.appendChild(textNode);
+ mPendingText.setLength(0);
+ }
+ }
+ }
+
+ private static class Position {
+ /** The line number (0-based where the first line is line 0) */
+ private final int mLine;
+ private final int mColumn;
+ private final int mOffset;
+ private Position mEnd;
+
+ /**
+ * Creates a new {@link Position}
+ *
+ * @param line the 0-based line number, or -1 if unknown
+ * @param column the 0-based column number, or -1 if unknown
+ * @param offset the offset, or -1 if unknown
+ */
+ public Position(int line, int column, int offset) {
+ this.mLine = line;
+ this.mColumn = column;
+ this.mOffset = offset;
+ }
+
+ public int getLine() {
+ return mLine;
+ }
+
+ public int getOffset() {
+ return mOffset;
+ }
+
+ public int getColumn() {
+ return mColumn;
+ }
+
+ public Position getEnd() {
+ return mEnd;
+ }
+
+ public void setEnd(@NonNull Position end) {
+ mEnd = end;
+ }
+
+ public SourcePosition toSourcePosition() {
+ int endLine = mLine, endColumn = mColumn, endOffset = mOffset;
+
+ if (mEnd != null) {
+ endLine = mEnd.getLine();
+ endColumn = mEnd.getColumn();
+ endOffset = mEnd.getOffset();
+ }
+
+ return new SourcePosition(mLine, mColumn, mOffset, endLine, endColumn, endOffset);
+ }
+ }
+
+ private PositionXmlParser() { }
+}
diff --git a/common/src/main/java/com/android/utils/SdkUtils.java b/common/src/main/java/com/android/utils/SdkUtils.java
new file mode 100644
index 0000000..5caa5d2
--- /dev/null
+++ b/common/src/main/java/com/android/utils/SdkUtils.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.utils;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Properties;
+
+import static com.android.SdkConstants.DOT_WEBP;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.DOT_GIF;
+import static com.android.SdkConstants.DOT_9PNG;
+import static com.android.SdkConstants.DOT_JPEG;
+import static com.android.SdkConstants.DOT_JPG;
+import static com.android.SdkConstants.DOT_BMP;
+
+/** Miscellaneous utilities used by the Android SDK tools */
+public class SdkUtils {
+ /**
+ * Returns true if the given string ends with the given suffix, using a
+ * case-insensitive comparison.
+ *
+ * @param string the full string to be checked
+ * @param suffix the suffix to be checked for
+ * @return true if the string case-insensitively ends with the given suffix
+ */
+ public static boolean endsWithIgnoreCase(@NonNull String string, @NonNull String suffix) {
+ return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
+ suffix, 0, suffix.length());
+ }
+
+ /**
+ * Returns true if the given sequence ends with the given suffix (case
+ * sensitive).
+ *
+ * @param sequence the character sequence to be checked
+ * @param suffix the suffix to look for
+ * @return true if the given sequence ends with the given suffix
+ */
+ public static boolean endsWith(@NonNull CharSequence sequence, @NonNull CharSequence suffix) {
+ return endsWith(sequence, sequence.length(), suffix);
+ }
+
+ /**
+ * Returns true if the given sequence ends at the given offset with the given suffix (case
+ * sensitive)
+ *
+ * @param sequence the character sequence to be checked
+ * @param endOffset the offset at which the sequence is considered to end
+ * @param suffix the suffix to look for
+ * @return true if the given sequence ends with the given suffix
+ */
+ public static boolean endsWith(@NonNull CharSequence sequence, int endOffset,
+ @NonNull CharSequence suffix) {
+ if (endOffset < suffix.length()) {
+ return false;
+ }
+
+ for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) {
+ if (sequence.charAt(i) != suffix.charAt(j)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the given string starts with the given prefix, using a
+ * case-insensitive comparison.
+ *
+ * @param string the full string to be checked
+ * @param prefix the prefix to be checked for
+ * @return true if the string case-insensitively starts with the given prefix
+ */
+ public static boolean startsWithIgnoreCase(@NonNull String string, @NonNull String prefix) {
+ return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Returns true if the given string starts at the given offset with the
+ * given prefix, case insensitively.
+ *
+ * @param string the full string to be checked
+ * @param offset the offset in the string to start looking
+ * @param prefix the prefix to be checked for
+ * @return true if the string case-insensitively starts at the given offset
+ * with the given prefix
+ */
+ public static boolean startsWith(@NonNull String string, int offset, @NonNull String prefix) {
+ return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Strips the whitespace from the given string
+ *
+ * @param string the string to be cleaned up
+ * @return the string, without whitespace
+ */
+ public static String stripWhitespace(@NonNull String string) {
+ StringBuilder sb = new StringBuilder(string.length());
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns true if the given string has an upper case character.
+ *
+ * @param s the string to check
+ * @return true if it contains uppercase characters
+ */
+ public static boolean hasUpperCaseCharacter(@NonNull String s) {
+ for (int i = 0; i < s.length(); i++) {
+ if (Character.isUpperCase(s.charAt(i))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** For use by {@link #getLineSeparator()} */
+ private static String sLineSeparator;
+
+ /**
+ * Returns the default line separator to use.
+ * <p>
+ * NOTE: If you have an associated IDocument (Eclipse), it is better to call
+ * TextUtilities#getDefaultLineDelimiter(IDocument) since that will
+ * allow (for example) editing a \r\n-delimited document on a \n-delimited
+ * platform and keep a consistent usage of delimiters in the file.
+ *
+ * @return the delimiter string to use
+ */
+ @NonNull
+ public static String getLineSeparator() {
+ if (sLineSeparator == null) {
+ // This is guaranteed to exist:
+ sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
+ }
+
+ return sLineSeparator;
+ }
+
+ /**
+ * Wraps the given text at the given line width, with an optional hanging
+ * indent.
+ *
+ * @param text the text to be wrapped
+ * @param lineWidth the number of characters to wrap the text to
+ * @param hangingIndent the hanging indent (to be used for the second and
+ * subsequent lines in each paragraph, or null if not known
+ * @return the string, wrapped
+ */
+ @NonNull
+ public static String wrap(
+ @NonNull String text,
+ int lineWidth,
+ @Nullable String hangingIndent) {
+ if (hangingIndent == null) {
+ hangingIndent = "";
+ }
+ int explanationLength = text.length();
+ StringBuilder sb = new StringBuilder(explanationLength * 2);
+ int index = 0;
+
+ while (index < explanationLength) {
+ int lineEnd = text.indexOf('\n', index);
+ int next;
+
+ if (lineEnd != -1 && (lineEnd - index) < lineWidth) {
+ next = lineEnd + 1;
+ } else {
+ // Line is longer than available width; grab as much as we can
+ lineEnd = Math.min(index + lineWidth, explanationLength);
+ if (lineEnd - index < lineWidth) {
+ next = explanationLength;
+ } else {
+ // then back up to the last space
+ int lastSpace = text.lastIndexOf(' ', lineEnd);
+ if (lastSpace > index) {
+ lineEnd = lastSpace;
+ next = lastSpace + 1;
+ } else {
+ // No space anywhere on the line: it contains something wider than
+ // can fit (like a long URL) so just hard break it
+ next = lineEnd + 1;
+ }
+ }
+ }
+
+ if (sb.length() > 0) {
+ sb.append(hangingIndent);
+ } else {
+ lineWidth -= hangingIndent.length();
+ }
+
+ sb.append(text.substring(index, lineEnd));
+ sb.append('\n');
+ index = next;
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the given localized string as an int. For example, in the
+ * US locale, "1,000", will return 1000. In the French locale, "1.000" will return
+ * 1000. It will return 0 for empty strings.
+ * <p>
+ * To parse a string without catching parser exceptions, call
+ * {@link #parseLocalizedInt(String, int)} instead, passing the
+ * default value to be returned if the format is invalid.
+ *
+ * @param string the string to be parsed
+ * @return the integer value
+ * @throws ParseException if the format is not correct
+ */
+ public static int parseLocalizedInt(@NonNull String string) throws ParseException {
+ if (string.isEmpty()) {
+ return 0;
+ }
+ return NumberFormat.getIntegerInstance().parse(string).intValue();
+ }
+
+ /**
+ * Returns the given localized string as an int. For example, in the
+ * US locale, "1,000", will return 1000. In the French locale, "1.000" will return
+ * 1000. If the format is invalid, returns the supplied default value instead.
+ *
+ * @param string the string to be parsed
+ * @param defaultValue the value to be returned if there is a parsing error
+ * @return the integer value
+ */
+ public static int parseLocalizedInt(@NonNull String string, int defaultValue) {
+ try {
+ return parseLocalizedInt(string);
+ } catch (ParseException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the given localized string as a double. For example, in the
+ * US locale, "3.14", will return 3.14. In the French locale, "3,14" will return
+ * 3.14. It will return 0 for empty strings.
+ * <p>
+ * To parse a string without catching parser exceptions, call
+ * {@link #parseLocalizedDouble(String, double)} instead, passing the
+ * default value to be returned if the format is invalid.
+ *
+ * @param string the string to be parsed
+ * @return the double value
+ * @throws ParseException if the format is not correct
+ */
+ public static double parseLocalizedDouble(@NonNull String string) throws ParseException {
+ if (string.isEmpty()) {
+ return 0.0;
+ }
+ return NumberFormat.getNumberInstance().parse(string).doubleValue();
+ }
+
+ /**
+ * Returns the given localized string as a double. For example, in the
+ * US locale, "3.14", will return 3.14. In the French locale, "3,14" will return
+ * 3.14. If the format is invalid, returns the supplied default value instead.
+ *
+ * @param string the string to be parsed
+ * @param defaultValue the value to be returned if there is a parsing error
+ * @return the double value
+ */
+ public static double parseLocalizedDouble(@NonNull String string, double defaultValue) {
+ try {
+ return parseLocalizedDouble(string);
+ } catch (ParseException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the corresponding {@link File} for the given file:// url
+ *
+ * @param url the URL string, e.g. file://foo/bar
+ * @return the corresponding {@link File} (which may or may not exist)
+ * @throws MalformedURLException if the URL string is malformed or is not a file: URL
+ */
+ @NonNull
+ public static File urlToFile(@NonNull String url) throws MalformedURLException {
+ return urlToFile(new URL(url));
+ }
+
+ @NonNull
+ public static File urlToFile(@NonNull URL url) throws MalformedURLException {
+ try {
+ return new File(url.toURI());
+ }
+ catch (IllegalArgumentException e) {
+ MalformedURLException ex = new MalformedURLException(e.getLocalizedMessage());
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (URISyntaxException e) {
+ return new File(url.getPath());
+ }
+ }
+
+ /**
+ * Returns the corresponding URL string for the given {@link File}
+ *
+ * @param file the file to look up the URL for
+ * @return the corresponding URL
+ * @throws MalformedURLException in very unexpected cases
+ */
+ public static String fileToUrlString(@NonNull File file) throws MalformedURLException {
+ String url = fileToUrl(file).toExternalForm();
+ // Use three slashes, which is the form most widely recognized by terminal emulators.
+ if (!url.startsWith("file:///")) {
+ url = url.replaceFirst("file:/", "file:///");
+ }
+ return url;
+ }
+
+ /**
+ * Returns the corresponding URL for the given {@link File}
+ *
+ * @param file the file to look up the URL for
+ * @return the corresponding URL
+ * @throws MalformedURLException in very unexpected cases
+ */
+ public static URL fileToUrl(@NonNull File file) throws MalformedURLException {
+ return file.toURI().toURL();
+ }
+
+ /** Prefix in comments which mark the source locations for merge results */
+ public static final String FILENAME_PREFIX = "From: ";
+
+ /**
+ * Creates the path comment XML string. Note that it does not escape characters
+ * such as & and <; those are expected to be escaped by the caller (for
+ * example, handled by a call to {@link org.w3c.dom.Document#createComment(String)})
+ *
+ *
+ * @param file the file to create a path comment for
+ * @param includePadding whether to include padding. The final comment recognized by
+ * error recognizers expect padding between the {@code <!--} and
+ * the start marker (From:); you can disable padding if the caller
+ * already is in a context where the padding has been added.
+ * @return the corresponding XML contents of the string
+ */
+ public static String createPathComment(@NonNull File file, boolean includePadding)
+ throws MalformedURLException {
+ String url = fileToUrlString(file);
+ int dashes = url.indexOf("--");
+ if (dashes != -1) { // Not allowed inside XML comments - for SGML compatibility. Sigh.
+ url = url.replace("--", "%2D%2D");
+ }
+
+ if (includePadding) {
+ return ' ' + FILENAME_PREFIX + url + ' ';
+ } else {
+ return FILENAME_PREFIX + url;
+ }
+ }
+
+ /**
+ * Translates an XML name (e.g. xml-name) into a Java / C++ constant name (e.g. XML_NAME)
+ * @param xmlName the hyphen separated lower case xml name.
+ * @return the equivalent constant name.
+ */
+ public static String xmlNameToConstantName(String xmlName) {
+ return CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, xmlName);
+ }
+
+ /**
+ * Translates a camel case name (e.g. xmlName) into a Java / C++ constant name (e.g. XML_NAME)
+ * @param camelCaseName the camel case name.
+ * @return the equivalent constant name.
+ */
+ public static String camelCaseToConstantName(String camelCaseName) {
+ return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, camelCaseName);
+ }
+
+ /**
+ * Translates a Java / C++ constant name (e.g. XML_NAME) into camel case name (e.g. xmlName)
+ * @param constantName the constant name.
+ * @return the equivalent camel case name.
+ */
+ public static String constantNameToCamelCase(String constantName) {
+ return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, constantName);
+ }
+
+ /**
+ * Translates a Java / C++ constant name (e.g. XML_NAME) into a XML case name (e.g. xml-name)
+ * @param constantName the constant name.
+ * @return the equivalent XML name.
+ */
+ public static String constantNameToXmlName(String constantName) {
+ return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, constantName);
+ }
+
+
+ /**
+ * Get the R field name from a resource name, since
+ * AAPT will flatten the namespace, turning dots, dashes and colons into _
+ *
+ * @param resourceName the name to convert
+ * @return the corresponding R field name
+ */
+ @NonNull
+ public static String getResourceFieldName(@NonNull String resourceName) {
+ // AAPT will flatten the namespace, turning dots, dashes and colons into _
+ for (int i = 0, n = resourceName.length(); i < n; i++) {
+ char c = resourceName.charAt(i);
+ if (c == '.' || c == ':' || c == '-') {
+ return resourceName.replace('.', '_').replace('-', '_').replace(':', '_');
+ }
+ }
+
+ return resourceName;
+ }
+
+ public static final List<String> IMAGE_EXTENSIONS = ImmutableList.of(
+ DOT_PNG, DOT_9PNG, DOT_GIF, DOT_JPEG, DOT_JPG, DOT_BMP, DOT_WEBP);
+
+ /**
+ * Returns true if the given file path points to an image file recognized by
+ * Android. See http://developer.android.com/guide/appendix/media-formats.html
+ * for details.
+ *
+ * @param path the filename to be tested
+ * @return true if the file represents an image file
+ */
+ public static boolean hasImageExtension(String path) {
+ for (String ext: IMAGE_EXTENSIONS) {
+ if (endsWithIgnoreCase(path, ext)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Escapes the given property file value (right hand side of property assignment)
+ * as required by the property file format (e.g. escapes colons and backslashes)
+ *
+ * @param value the value to be escaped
+ * @return the escaped value
+ */
+ @NonNull
+ public static String escapePropertyValue(@NonNull String value) {
+ // Slow, stupid implementation, but is 100% compatible with Java's property file
+ // implementation
+ Properties properties = new Properties();
+ properties.setProperty("k", value); // key doesn't matter
+ StringWriter writer = new StringWriter();
+ try {
+ properties.store(writer, null);
+ String s = writer.toString();
+ int end = s.length();
+
+ // Writer inserts trailing newline
+ String lineSeparator = SdkUtils.getLineSeparator();
+ if (s.endsWith(lineSeparator)) {
+ end -= lineSeparator.length();
+ }
+
+ int start = s.indexOf('=');
+ assert start != -1 : s;
+ return s.substring(start + 1, end);
+ }
+ catch (IOException e) {
+ return value; // shouldn't happen; we're not going to disk
+ }
+ }
+}
diff --git a/base/common/src/main/java/com/android/utils/SparseArray.java b/common/src/main/java/com/android/utils/SparseArray.java
similarity index 100%
rename from base/common/src/main/java/com/android/utils/SparseArray.java
rename to common/src/main/java/com/android/utils/SparseArray.java
diff --git a/base/common/src/main/java/com/android/utils/SparseIntArray.java b/common/src/main/java/com/android/utils/SparseIntArray.java
similarity index 100%
rename from base/common/src/main/java/com/android/utils/SparseIntArray.java
rename to common/src/main/java/com/android/utils/SparseIntArray.java
diff --git a/base/common/src/main/java/com/android/utils/StdLogger.java b/common/src/main/java/com/android/utils/StdLogger.java
similarity index 100%
rename from base/common/src/main/java/com/android/utils/StdLogger.java
rename to common/src/main/java/com/android/utils/StdLogger.java
diff --git a/common/src/main/java/com/android/utils/StringHelper.java b/common/src/main/java/com/android/utils/StringHelper.java
new file mode 100644
index 0000000..ee64f2b
--- /dev/null
+++ b/common/src/main/java/com/android/utils/StringHelper.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.utils;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ */
+public class StringHelper {
+
+ @NonNull
+ public static String capitalize(@NonNull String string) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(string.substring(0, 1).toUpperCase(Locale.US)).append(string.substring(1));
+
+ return sb.toString();
+ }
+
+ @NonNull
+ public static String combineAsCamelCase(@NonNull Iterable<String> stringList) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (String str : stringList) {
+ if (first) {
+ sb.append(str);
+ first = false;
+ } else {
+ sb.append(StringHelper.capitalize(str));
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a list of Strings containing the objects passed in argument.
+ *
+ * If the objects are strings, they are directly added to the list.
+ * If the objects are collections of strings, the strings are added.
+ * For other objects, the result of their toString() is added.
+ * @param objects the objects to add
+ * @return the list of objects.
+ */
+ @NonNull
+ public static List<String> toStrings(@NonNull Object... objects) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ for (Object path : objects) {
+ if (path instanceof String) {
+ builder.add((String) path);
+ } else if (path instanceof Collection) {
+ Collection pathCollection = (Collection) path;
+ for (Object item : pathCollection) {
+ if (item instanceof String) {
+ builder.add((String) item);
+ } else {
+ builder.add(path.toString());
+ }
+ }
+ } else {
+ builder.add(path.toString());
+ }
+ }
+
+ return builder.build();
+ }
+
+ public static void appendCamelCase(@NonNull StringBuilder sb, @Nullable String word) {
+ if (word != null) {
+ if (sb.length() == 0) {
+ sb.append(word);
+ } else {
+ sb.append(StringHelper.capitalize(word));
+ }
+ }
+ }
+
+ /**
+ * Tokenize a command line string.
+ */
+ @NonNull
+ public static List<String> tokenizeCommand(@NonNull String commandLine) {
+ Iterable<String> split =
+ Splitter.on(' ').trimResults().split(commandLine);
+ List<String> command = Lists.newArrayList();
+ char quote = '\0';
+ StringBuilder quotedText = new StringBuilder();
+ for (String arg : split) {
+ if (quote == '\0') {
+ quote = findFirstQuoteChar(arg, "'\"");
+ }
+
+ if (quote != '\0') {
+ if (quotedText.length() > 0) {
+ quotedText.append(" ");
+ }
+ quotedText.append(arg);
+ if (!arg.isEmpty() && arg.charAt(arg.length() - 1) == quote) {
+ if (arg.length() == 1 || arg.charAt(arg.length() - 2) != '\\') {
+ quote = '\0';
+ command.add(quotedText.toString());
+ quotedText = new StringBuilder();
+ }
+ }
+ } else {
+ if (!arg.isEmpty()) {
+ command.add(arg);
+ }
+ }
+ }
+ if (quote != '\0') {
+ throw new RuntimeException(
+ "Unable to parse command string: " + commandLine + "\n"
+ + "Missing " + quote + ".");
+ }
+ return command;
+ }
+
+ /**
+ * Find the first quote character that appear in 'str'.
+ *
+ * Return '\0' if 'str' does not contain any character in 'quote'.
+ */
+ private static char findFirstQuoteChar(@NonNull String str, @NonNull CharSequence quote) {
+ int firstIndex = -1;
+ char firstQuote = '\0';
+ for (int i = 0; i < quote.length(); i++) {
+ int index = str.indexOf(quote.charAt(i));
+ if (index != -1) {
+ if (firstIndex == -1 || index < firstIndex) {
+ firstIndex = index;
+ firstQuote = quote.charAt(i);
+ }
+ }
+ }
+ return firstQuote;
+ }
+}
diff --git a/common/src/main/java/com/android/utils/XmlUtils.java b/common/src/main/java/com/android/utils/XmlUtils.java
new file mode 100644
index 0000000..03759fb
--- /dev/null
+++ b/common/src/main/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,679 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.utils;
+
+import static com.android.SdkConstants.AMP_ENTITY;
+import static com.android.SdkConstants.ANDROID_NS_NAME;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.APOS_ENTITY;
+import static com.android.SdkConstants.APP_PREFIX;
+import static com.android.SdkConstants.GT_ENTITY;
+import static com.android.SdkConstants.LT_ENTITY;
+import static com.android.SdkConstants.QUOT_ENTITY;
+import static com.android.SdkConstants.XMLNS;
+import static com.android.SdkConstants.XMLNS_PREFIX;
+import static com.android.SdkConstants.XMLNS_URI;
+import static com.google.common.base.Charsets.UTF_16BE;
+import static com.google.common.base.Charsets.UTF_16LE;
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/** XML Utilities */
+public class XmlUtils {
+ public static final String XML_COMMENT_BEGIN = "<!--"; //$NON-NLS-1$
+ public static final String XML_COMMENT_END = "-->"; //$NON-NLS-1$
+ public static final String XML_PROLOG =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$
+
+ /**
+ * Separator for xml namespace and localname
+ */
+ public static final char NS_SEPARATOR = ':'; //$NON-NLS-1$
+
+ private static final String SOURCE_FILE_USER_DATA_KEY = "sourcefile";
+
+ /**
+ * Returns the namespace prefix matching the requested namespace URI.
+ * If no such declaration is found, returns the default "android" prefix for
+ * the Android URI, and "app" for other URI's. By default the app namespace
+ * will be created. If this is not desirable, call
+ * {@link #lookupNamespacePrefix(Node, String, boolean)} instead.
+ *
+ * @param node The current node. Must not be null.
+ * @param nsUri The namespace URI of which the prefix is to be found,
+ * e.g. {@link SdkConstants#ANDROID_URI}
+ * @return The first prefix declared or the default "android" prefix
+ * (or "app" for non-Android URIs)
+ */
+ @NonNull
+ public static String lookupNamespacePrefix(@NonNull Node node, @NonNull String nsUri) {
+ String defaultPrefix = ANDROID_URI.equals(nsUri) ? ANDROID_NS_NAME : APP_PREFIX;
+ return lookupNamespacePrefix(node, nsUri, defaultPrefix, true /*create*/);
+ }
+
+ /**
+ * Returns the namespace prefix matching the requested namespace URI. If no
+ * such declaration is found, returns the default "android" prefix for the
+ * Android URI, and "app" for other URI's.
+ *
+ * @param node The current node. Must not be null.
+ * @param nsUri The namespace URI of which the prefix is to be found, e.g.
+ * {@link SdkConstants#ANDROID_URI}
+ * @param create whether the namespace declaration should be created, if
+ * necessary
+ * @return The first prefix declared or the default "android" prefix (or
+ * "app" for non-Android URIs)
+ */
+ @NonNull
+ public static String lookupNamespacePrefix(@NonNull Node node, @NonNull String nsUri,
+ boolean create) {
+ String defaultPrefix = ANDROID_URI.equals(nsUri) ? ANDROID_NS_NAME : APP_PREFIX;
+ return lookupNamespacePrefix(node, nsUri, defaultPrefix, create);
+ }
+
+ /**
+ * Returns the namespace prefix matching the requested namespace URI. If no
+ * such declaration is found, returns the default "android" prefix.
+ *
+ * @param node The current node. Must not be null.
+ * @param nsUri The namespace URI of which the prefix is to be found, e.g.
+ * {@link SdkConstants#ANDROID_URI}
+ * @param defaultPrefix The default prefix (root) to use if the namespace is
+ * not found. If null, do not create a new namespace if this URI
+ * is not defined for the document.
+ * @param create whether the namespace declaration should be created, if
+ * necessary
+ * @return The first prefix declared or the provided prefix (possibly with a
+ * number appended to avoid conflicts with existing prefixes.
+ */
+ public static String lookupNamespacePrefix(
+ @Nullable Node node, @Nullable String nsUri, @Nullable String defaultPrefix,
+ boolean create) {
+ // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
+ // The following code emulates this simple call:
+ // String prefix = node.lookupPrefix(NS_RESOURCES);
+
+ // if the requested URI is null, it denotes an attribute with no namespace.
+ if (nsUri == null) {
+ return null;
+ }
+
+ // per XML specification, the "xmlns" URI is reserved
+ if (XMLNS_URI.equals(nsUri)) {
+ return XMLNS;
+ }
+
+ HashSet<String> visited = new HashSet<String>();
+ Document doc = node == null ? null : node.getOwnerDocument();
+
+ // Ask the document about it. This method may not be implemented by the Document.
+ String nsPrefix = null;
+ try {
+ nsPrefix = doc != null ? doc.lookupPrefix(nsUri) : null;
+ if (nsPrefix != null) {
+ return nsPrefix;
+ }
+ } catch (Throwable t) {
+ // ignore
+ }
+
+ // If that failed, try to look it up manually.
+ // This also gathers prefixed in use in the case we want to generate a new one below.
+ for (; node != null && node.getNodeType() == Node.ELEMENT_NODE;
+ node = node.getParentNode()) {
+ NamedNodeMap attrs = node.getAttributes();
+ for (int n = attrs.getLength() - 1; n >= 0; --n) {
+ Node attr = attrs.item(n);
+ if (XMLNS.equals(attr.getPrefix())) {
+ String uri = attr.getNodeValue();
+ nsPrefix = attr.getLocalName();
+ // Is this the URI we are looking for? If yes, we found its prefix.
+ if (nsUri.equals(uri)) {
+ return nsPrefix;
+ }
+ visited.add(nsPrefix);
+ }
+ }
+ }
+
+ // Failed the find a prefix. Generate a new sensible default prefix, unless
+ // defaultPrefix was null in which case the caller does not want the document
+ // modified.
+ if (defaultPrefix == null) {
+ return null;
+ }
+
+ //
+ // We need to make sure the prefix is not one that was declared in the scope
+ // visited above. Pick a unique prefix from the provided default prefix.
+ String prefix = defaultPrefix;
+ String base = prefix;
+ for (int i = 1; visited.contains(prefix); i++) {
+ prefix = base + Integer.toString(i);
+ }
+ // Also create & define this prefix/URI in the XML document as an attribute in the
+ // first element of the document.
+ if (doc != null) {
+ node = doc.getFirstChild();
+ while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
+ node = node.getNextSibling();
+ }
+ if (node != null && create) {
+ // This doesn't work:
+ //Attr attr = doc.createAttributeNS(XMLNS_URI, prefix);
+ //attr.setPrefix(XMLNS);
+ //
+ // Xerces throws
+ //org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or
+ // change an object in a way which is incorrect with regard to namespaces.
+ //
+ // Instead pass in the concatenated prefix. (This is covered by
+ // the UiElementNodeTest#testCreateNameSpace() test.)
+ Attr attr = doc.createAttributeNS(XMLNS_URI, XMLNS_PREFIX + prefix);
+ attr.setValue(nsUri);
+ node.getAttributes().setNamedItemNS(attr);
+ }
+ }
+
+ return prefix;
+ }
+
+ /**
+ * Converts the given attribute value to an XML-attribute-safe value, meaning that
+ * single and double quotes are replaced with their corresponding XML entities.
+ *
+ * @param attrValue the value to be escaped
+ * @return the escaped value
+ */
+ @NonNull
+ public static String toXmlAttributeValue(@NonNull String attrValue) {
+ for (int i = 0, n = attrValue.length(); i < n; i++) {
+ char c = attrValue.charAt(i);
+ if (c == '"' || c == '\'' || c == '<' || c == '&') {
+ StringBuilder sb = new StringBuilder(2 * attrValue.length());
+ appendXmlAttributeValue(sb, attrValue);
+ return sb.toString();
+ }
+ }
+
+ return attrValue;
+ }
+
+ /**
+ * Converts the given XML-attribute-safe value to a java string
+ *
+ * @param escapedAttrValue the escaped value
+ * @return the unescaped value
+ */
+ @NonNull
+ public static String fromXmlAttributeValue(@NonNull String escapedAttrValue) {
+ String workingString = escapedAttrValue.replace(QUOT_ENTITY, "\"");
+ workingString = workingString.replace(LT_ENTITY, "<");
+ workingString = workingString.replace(APOS_ENTITY, "'");
+ workingString = workingString.replace(AMP_ENTITY, "&");
+ workingString = workingString.replace(GT_ENTITY, ">");
+
+ return workingString;
+ }
+
+ /**
+ * Converts the given attribute value to an XML-text-safe value, meaning that
+ * less than and ampersand characters are escaped.
+ *
+ * @param textValue the text value to be escaped
+ * @return the escaped value
+ */
+ @NonNull
+ public static String toXmlTextValue(@NonNull String textValue) {
+ for (int i = 0, n = textValue.length(); i < n; i++) {
+ char c = textValue.charAt(i);
+ if (c == '<' || c == '&') {
+ StringBuilder sb = new StringBuilder(2 * textValue.length());
+ appendXmlTextValue(sb, textValue);
+ return sb.toString();
+ }
+ }
+
+ return textValue;
+ }
+
+ /**
+ * Appends text to the given {@link StringBuilder} and escapes it as required for a
+ * DOM attribute node.
+ *
+ * @param sb the string builder
+ * @param attrValue the attribute value to be appended and escaped
+ */
+ public static void appendXmlAttributeValue(@NonNull StringBuilder sb,
+ @NonNull String attrValue) {
+ int n = attrValue.length();
+ // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
+ // (' legal in a " string and " is legal in a ' string but here we'll stay on the safe
+ // side)
+ for (int i = 0; i < n; i++) {
+ char c = attrValue.charAt(i);
+ if (c == '"') {
+ sb.append(QUOT_ENTITY);
+ } else if (c == '<') {
+ sb.append(LT_ENTITY);
+ } else if (c == '\'') {
+ sb.append(APOS_ENTITY);
+ } else if (c == '&') {
+ sb.append(AMP_ENTITY);
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+ /**
+ * Appends text to the given {@link StringBuilder} and escapes it as required for a
+ * DOM text node.
+ *
+ * @param sb the string builder
+ * @param textValue the text value to be appended and escaped
+ */
+ public static void appendXmlTextValue(@NonNull StringBuilder sb, @NonNull String textValue) {
+ for (int i = 0, n = textValue.length(); i < n; i++) {
+ char c = textValue.charAt(i);
+ if (c == '<') {
+ sb.append(LT_ENTITY);
+ } else if (c == '&') {
+ sb.append(AMP_ENTITY);
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given node has one or more element children
+ *
+ * @param node the node to test for element children
+ * @return true if the node has one or more element children
+ */
+ public static boolean hasElementChildren(@NonNull Node node) {
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a character reader for the given file, which must be a UTF encoded file.
+ * <p>
+ * The reader does not need to be closed by the caller (because the file is read in
+ * full in one shot and the resulting array is then wrapped in a byte array input stream,
+ * which does not need to be closed.)
+ */
+ public static Reader getUtfReader(@NonNull File file) throws IOException {
+ byte[] bytes = Files.toByteArray(file);
+ int length = bytes.length;
+ if (length == 0) {
+ return new StringReader("");
+ }
+
+ switch (bytes[0]) {
+ case (byte)0xEF: {
+ if (length >= 3
+ && bytes[1] == (byte)0xBB
+ && bytes[2] == (byte)0xBF) {
+ // UTF-8 BOM: EF BB BF: Skip it
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 3, length - 3),
+ UTF_8);
+ }
+ break;
+ }
+ case (byte)0xFE: {
+ if (length >= 2
+ && bytes[1] == (byte)0xFF) {
+ // UTF-16 Big Endian BOM: FE FF
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2),
+ UTF_16BE);
+ }
+ break;
+ }
+ case (byte)0xFF: {
+ if (length >= 2
+ && bytes[1] == (byte)0xFE) {
+ if (length >= 4
+ && bytes[2] == (byte)0x00
+ && bytes[3] == (byte)0x00) {
+ // UTF-32 Little Endian BOM: FF FE 00 00
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 4,
+ length - 4), "UTF-32LE");
+ }
+
+ // UTF-16 Little Endian BOM: FF FE
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2),
+ UTF_16LE);
+ }
+ break;
+ }
+ case (byte)0x00: {
+ if (length >= 4
+ && bytes[0] == (byte)0x00
+ && bytes[1] == (byte)0x00
+ && bytes[2] == (byte)0xFE
+ && bytes[3] == (byte)0xFF) {
+ // UTF-32 Big Endian BOM: 00 00 FE FF
+ return new InputStreamReader(new ByteArrayInputStream(bytes, 4, length - 4),
+ "UTF-32BE");
+ }
+ break;
+ }
+ }
+
+ // No byte order mark: Assume UTF-8 (where the BOM is optional).
+ return new InputStreamReader(new ByteArrayInputStream(bytes), UTF_8);
+ }
+
+ /**
+ * Parses the given XML string as a DOM document, using the JDK parser. The parser does not
+ * validate, and is optionally namespace aware.
+ *
+ * @param xml the XML content to be parsed (must be well formed)
+ * @param namespaceAware whether the parser is namespace aware
+ * @return the DOM document
+ */
+ @NonNull
+ public static Document parseDocument(@NonNull String xml, boolean namespaceAware)
+ throws ParserConfigurationException, IOException, SAXException {
+ xml = stripBom(xml);
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(new StringReader(xml));
+ factory.setNamespaceAware(namespaceAware);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ return builder.parse(is);
+ }
+
+ /**
+ * Parses the given UTF file as a DOM document, using the JDK parser. The parser does not
+ * validate, and is optionally namespace aware.
+ *
+ * @param file the UTF encoded file to parse
+ * @param namespaceAware whether the parser is namespace aware
+ * @return the DOM document
+ */
+ @NonNull
+ public static Document parseUtfXmlFile(@NonNull File file, boolean namespaceAware)
+ throws ParserConfigurationException, IOException, SAXException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Reader reader = getUtfReader(file);
+ try {
+ InputSource is = new InputSource(reader);
+ factory.setNamespaceAware(namespaceAware);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ return builder.parse(is);
+ } finally {
+ reader.close();
+ }
+ }
+
+ /** Strips out a leading UTF byte order mark, if present */
+ @NonNull
+ public static String stripBom(@NonNull String xml) {
+ if (!xml.isEmpty() && xml.charAt(0) == '\uFEFF') {
+ return xml.substring(1);
+ }
+ return xml;
+ }
+
+ /**
+ * Parses the given XML string as a DOM document, using the JDK parser. The parser does not
+ * validate, and is optionally namespace aware. Any parsing errors are silently ignored.
+ *
+ * @param xml the XML content to be parsed (must be well formed)
+ * @param namespaceAware whether the parser is namespace aware
+ * @return the DOM document, or null
+ */
+ @Nullable
+ public static Document parseDocumentSilently(@NonNull String xml, boolean namespaceAware) {
+ try {
+ return parseDocument(xml, namespaceAware);
+ } catch (Exception e) {
+ // pass
+ // This method is deliberately silent; will return null
+ }
+
+ return null;
+ }
+
+ /**
+ * Dump an XML tree to string. This does not perform any pretty printing.
+ * To perform pretty printing, use {@code XmlPrettyPrinter.prettyPrint(node)} in
+ * {@code sdk-common}.
+ */
+ public static String toXml(@NonNull Node node) {
+ return toXml(node, null);
+ }
+ public static String toXml(
+ @NonNull Node node,
+ @Nullable Map<SourcePosition, SourceFilePosition> blame) {
+ PositionAwareStringBuilder sb = new PositionAwareStringBuilder(1000);
+ append(sb, node, blame);
+ return sb.toString();
+ }
+
+ /** Dump node to string without indentation adjustments */
+ private static void append(
+ @NonNull PositionAwareStringBuilder sb,
+ @NonNull Node node,
+ @Nullable Map<SourcePosition, SourceFilePosition> blame) {
+ short nodeType = node.getNodeType();
+ int currentLine = sb.line;
+ int currentColumn = sb.column;
+ int currentOffset = sb.getOffset();
+ switch (nodeType) {
+ case Node.DOCUMENT_NODE:
+ case Node.DOCUMENT_FRAGMENT_NODE: {
+ sb.append(XML_PROLOG);
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ append(sb, children.item(i), blame);
+ }
+ break;
+ }
+ case Node.COMMENT_NODE:
+ sb.append(XML_COMMENT_BEGIN);
+ sb.append(node.getNodeValue());
+ sb.append(XML_COMMENT_END);
+ break;
+ case Node.TEXT_NODE: {
+ sb.append(toXmlTextValue(node.getNodeValue()));
+ break;
+ }
+ case Node.CDATA_SECTION_NODE: {
+ sb.append("<![CDATA["); //$NON-NLS-1$
+ sb.append(node.getNodeValue());
+ sb.append("]]>"); //$NON-NLS-1$
+ break;
+ }
+ case Node.ELEMENT_NODE: {
+ sb.append('<');
+ Element element = (Element) node;
+ sb.append(element.getTagName());
+
+ NamedNodeMap attributes = element.getAttributes();
+ NodeList children = element.getChildNodes();
+ int childCount = children.getLength();
+ int attributeCount = attributes.getLength();
+
+ if (attributeCount > 0) {
+ for (int i = 0; i < attributeCount; i++) {
+ Node attribute = attributes.item(i);
+ sb.append(' ');
+ sb.append(attribute.getNodeName());
+ sb.append('=').append('"');
+ sb.append(toXmlAttributeValue(attribute.getNodeValue()));
+ sb.append('"');
+ }
+ }
+
+ if (childCount == 0) {
+ sb.append('/');
+ }
+ sb.append('>');
+ if (childCount > 0) {
+ for (int i = 0; i < childCount; i++) {
+ Node child = children.item(i);
+ append(sb, child, blame);
+ }
+ sb.append('<').append('/');
+ sb.append(element.getTagName());
+ sb.append('>');
+ }
+
+ if (blame != null) {
+ SourceFilePosition position = getSourceFilePosition(node);
+ if (!position.equals(SourceFilePosition.UNKNOWN)) {
+ blame.put(
+ new SourcePosition(
+ currentLine, currentColumn, currentOffset,
+ sb.line, sb.column, sb.getOffset()),
+ position);
+ }
+ }
+ break;
+ }
+
+ default:
+ throw new UnsupportedOperationException(
+ "Unsupported node type " + nodeType + ": not yet implemented");
+ }
+ }
+
+ /**
+ * Wraps a StringBuilder, but keeps track of the line and column of the end of the string.
+ *
+ * It implements append(String) and append(char) which as well as delegating to the underlying
+ * StringBuilder also keep track of any new lines, and set the line and column fields.
+ * The StringBuilder itself keeps track of the actual character offset.
+ */
+ private static class PositionAwareStringBuilder {
+ @SuppressWarnings("StringBufferField")
+ private final StringBuilder sb;
+ int line = 0;
+ int column = 0;
+
+ public PositionAwareStringBuilder(int size) {
+ sb = new StringBuilder(size);
+ }
+
+ public PositionAwareStringBuilder append(String text) {
+ sb.append(text);
+ // we find the last, as it might be useful later.
+ int lastNewLineIndex = text.lastIndexOf('\n');
+ if (lastNewLineIndex == -1) {
+ // If it does not contain a new line, we just increase the column number.
+ column += text.length();
+ } else {
+ // The string could contain multiple new lines.
+ line += CharMatcher.is('\n').countIn(text);
+ // But for column we only care about the number of characters after the last one.
+ column = text.length() - lastNewLineIndex - 1;
+ }
+ return this;
+ }
+
+ public PositionAwareStringBuilder append(char character) {
+ sb.append(character);
+ if (character == '\n') {
+ line += 1;
+ column = 0;
+ } else {
+ column++;
+ }
+ return this;
+ }
+
+ public int getOffset() {
+ return sb.length();
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+ }
+
+ public static void attachSourceFile(Node node, SourceFile sourceFile) {
+ node.setUserData(SOURCE_FILE_USER_DATA_KEY, sourceFile, null);
+ }
+
+ public static SourceFilePosition getSourceFilePosition(Node node) {
+ SourceFile sourceFile = (SourceFile) node.getUserData(SOURCE_FILE_USER_DATA_KEY);
+ if (sourceFile == null) {
+ sourceFile = SourceFile.UNKNOWN;
+ }
+ return new SourceFilePosition(sourceFile, PositionXmlParser.getPosition(node));
+ }
+
+ /**
+ * Format the given floating value into an XML string, omitting decimals if
+ * 0
+ *
+ * @param value the value to be formatted
+ * @return the corresponding XML string for the value
+ */
+ public static String formatFloatAttribute(double value) {
+ if (value != (int) value) {
+ // Run String.format without a locale, because we don't want locale-specific
+ // conversions here like separating the decimal part with a comma instead of a dot!
+ return String.format((Locale) null, "%.2f", value); //$NON-NLS-1$
+ } else {
+ return Integer.toString((int) value);
+ }
+ }
+}
diff --git a/base/common/src/main/java/com/android/xml/AndroidManifest.java b/common/src/main/java/com/android/xml/AndroidManifest.java
similarity index 100%
rename from base/common/src/main/java/com/android/xml/AndroidManifest.java
rename to common/src/main/java/com/android/xml/AndroidManifest.java
diff --git a/base/common/src/main/java/com/android/xml/AndroidXPathFactory.java b/common/src/main/java/com/android/xml/AndroidXPathFactory.java
similarity index 100%
rename from base/common/src/main/java/com/android/xml/AndroidXPathFactory.java
rename to common/src/main/java/com/android/xml/AndroidXPathFactory.java
diff --git a/base/common/src/test/.classpath b/common/src/test/.classpath
similarity index 100%
rename from base/common/src/test/.classpath
rename to common/src/test/.classpath
diff --git a/base/common/src/test/.project b/common/src/test/.project
similarity index 100%
rename from base/common/src/test/.project
rename to common/src/test/.project
diff --git a/base/common/src/test/.settings/org.moreunit.prefs b/common/src/test/.settings/org.moreunit.prefs
similarity index 100%
rename from base/common/src/test/.settings/org.moreunit.prefs
rename to common/src/test/.settings/org.moreunit.prefs
diff --git a/common/src/test/java/com/android/ide/common/blame/SourcePositionTest.java b/common/src/test/java/com/android/ide/common/blame/SourcePositionTest.java
new file mode 100644
index 0000000..3484ce8
--- /dev/null
+++ b/common/src/test/java/com/android/ide/common/blame/SourcePositionTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.ide.common.blame;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SourcePositionTest {
+
+ @Test
+ public void testComparisonOffset() {
+ SourcePosition pos1 = new SourcePosition(-1, -1, 5, -1, -1, 20);
+ SourcePosition posInner = new SourcePosition(-1, -1, 6, -1, -1, 8);
+ SourcePosition posOverlap = new SourcePosition(-1, -1, 18, -1, -1, 29);
+
+ doComparisons(pos1, posInner, posOverlap);
+ }
+
+
+ @Test
+ public void testComparisonLineColumn() {
+ SourcePosition pos1 = new SourcePosition(1, 8, -1, 7, 1, -1);
+ SourcePosition posInner = new SourcePosition(2, 0, -1, 5, 9, -1);
+ SourcePosition posOverlap = new SourcePosition(6, 23, -1, 10, 200, -1);
+
+ doComparisons(pos1, posInner, posOverlap);
+ }
+
+ /**
+ * Three positions as follows:
+ *
+ * <pre>
+ * |-------pos1--------|
+ * |--posInner-|
+ * |-----posOverlap----|
+ * --------------------------------------------->
+ * position in file
+ * </pre>
+ */
+ private static void doComparisons(
+ SourcePosition pos1,
+ SourcePosition posInner,
+ SourcePosition posOverlap) {
+
+ assertTrue(posInner.compareStart(posInner) == 0);
+ assertTrue(posInner.compareEnd(posInner) == 0);
+
+ assertTrue(pos1.compareStart(posInner) < 0);
+ assertTrue(posInner.compareStart(pos1) > 0);
+ assertTrue(pos1.compareEnd(posInner) > 0);
+ assertTrue(posInner.compareEnd(pos1) < 0);
+
+
+ assertTrue(posInner.compareStart(posOverlap) < 0);
+ assertTrue(posOverlap.compareStart(posInner) > 0);
+ assertTrue(posInner.compareEnd(posOverlap) < 0);
+ assertTrue(posOverlap.compareEnd(posInner) > 0);
+
+ assertTrue(pos1.compareStart(posOverlap) < 0);
+ assertTrue(posOverlap.compareStart(pos1) > 0);
+ assertTrue(pos1.compareEnd(posOverlap) < 0);
+ assertTrue(posOverlap.compareEnd(pos1) > 0);
+
+
+ }
+}
diff --git a/base/common/src/test/java/com/android/io/NonClosingInputStreamTest.java b/common/src/test/java/com/android/io/NonClosingInputStreamTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/common/src/test/java/com/android/io/NonClosingInputStreamTest.java
rename to common/src/test/java/com/android/io/NonClosingInputStreamTest.java
diff --git a/common/src/test/java/com/android/utils/FileUtilsTest.java b/common/src/test/java/com/android/utils/FileUtilsTest.java
new file mode 100644
index 0000000..c0565bf
--- /dev/null
+++ b/common/src/test/java/com/android/utils/FileUtilsTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.io.Files;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+
+/**
+ * Test cases for {@link FileUtils}.
+ */
+public class FileUtilsTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void computeRelativePathOfFile() throws Exception {
+ File d1 = mTemporaryFolder.newFolder("foo");
+ File f2 = new File(d1, "bar");
+ Files.touch(f2);
+
+ assertEquals("bar", FileUtils.relativePath(f2, d1));
+ }
+
+ @Test
+ public void computeRelativePathOfDirectory() throws Exception {
+ File d1 = mTemporaryFolder.newFolder("foo");
+ File f2 = new File(d1, "bar");
+ f2.mkdir();
+
+ assertEquals("bar" + File.separator, FileUtils.relativePath(f2, d1));
+ }
+}
diff --git a/common/src/test/java/com/android/utils/HtmlBuilderTest.java b/common/src/test/java/com/android/utils/HtmlBuilderTest.java
new file mode 100644
index 0000000..1aa5637
--- /dev/null
+++ b/common/src/test/java/com/android/utils/HtmlBuilderTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.utils;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class HtmlBuilderTest extends TestCase {
+
+ public void testAddLink() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.add("Plain.");
+ builder.addLink(" (link) ", "runnable:0");
+ builder.add("Plain.");
+ // Check that the spaces surrounding the link text are not included in the link range
+ assertEquals("Plain. <A HREF=\"runnable:0\">(link)</A>Plain.", builder.getHtml());
+ }
+
+ public void testAddBold() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.addBold("This is bold");
+ assertEquals("<B>This is bold</B>", builder.getHtml());
+ }
+
+ public void testAddItalic() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.addItalic("This is italic");
+ assertEquals("<I>This is italic</I>", builder.getHtml());
+ }
+
+ public void testNestLinkInBold() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.add("Plain. ");
+ builder.beginBold();
+ builder.add("Bold. ");
+ builder.addLink("mylink", "foo://bar:123");
+ builder.endBold();
+ assertEquals("Plain. <B>Bold. <A HREF=\"foo://bar:123\">mylink</A></B>",
+ builder.getHtml());
+ }
+
+ public void testAddList() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.add("Plain").newline();
+ builder.beginList().listItem().add("item 1").listItem().add("item 2").endList();
+
+ assertEquals("Plain<BR/>" +
+ "<DL>" +
+ "<DD>-&NBSP;item 1" +
+ "<DD>-&NBSP;item 2" +
+ "</DL>", builder.getHtml());
+ }
+
+ public void testAddLinkWithBeforeAndAfterText() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.addLink("This is the ", "linked text", "!", "foo://bar");
+ assertEquals("This is the <A HREF=\"foo://bar\">linked text</A>!", builder.getHtml());
+ }
+
+ public void testAddTable() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.beginTable().addTableRow(true, "Header1", "Header2")
+ .addTableRow("Data1", "Data2")
+ .endTable();
+ assertEquals(
+ "<table><tr><th>Header1</th><th>Header2</th></tr><tr><td>Data1</td><td>Data2</td></tr></table>",
+ builder.getHtml());
+ }
+
+ public void testAddTableWithAlignment() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.beginTable("valign=\"top\"").addTableRow("Data1", "Data2").endTable();
+ assertEquals(
+ "<table><tr><td valign=\"top\">Data1</td><td valign=\"top\">Data2</td></tr></table>",
+ builder.getHtml());
+ }
+
+ public void testAddDiv() {
+ HtmlBuilder builder = new HtmlBuilder();
+ assertEquals("<div>Hello</div>", builder.beginDiv().add("Hello").endDiv().getHtml());
+ }
+
+ public void testAddDivWithPadding() {
+ HtmlBuilder builder = new HtmlBuilder();
+ assertEquals("<div style=\"padding: 10px; text-color: gray\">Hello</div>",
+ builder.beginDiv("padding: 10px; text-color: gray").add("Hello").endDiv()
+ .getHtml());
+ }
+
+ public void testAddSpan() {
+ HtmlBuilder builder = new HtmlBuilder();
+ assertEquals("<span>Hello</span>", builder.beginSpan().add("Hello").endSpan().getHtml());
+ }
+
+ public void testAddSpanWithPadding() {
+ HtmlBuilder builder = new HtmlBuilder();
+ assertEquals("<span style=\"padding: 10px; text-color: gray\">Hello</span>",
+ builder.beginSpan("padding: 10px; text-color: gray").add("Hello").endSpan()
+ .getHtml());
+ }
+
+
+ public void testAddImage() throws IOException {
+ File f = File.createTempFile("img", "png");
+ f.deleteOnExit();
+
+ String actual = new HtmlBuilder().addImage(SdkUtils.fileToUrl(f), "preview").getHtml();
+ String path = f.getAbsolutePath();
+
+ if (!path.startsWith("/")) {
+ path = '/' + path;
+ }
+ String expected = String.format("<img src='file:%1$s' alt=\"preview\" />", path);
+ if (File.separatorChar != '/') {
+ // SdkUtil.fileToUrl always returns / as a separator so adjust
+ // Windows path accordingly.
+ expected = expected.replace(File.separatorChar, '/');
+ }
+ assertEquals(expected, actual);
+ }
+
+ public void testNewlineIfNecessary() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>", builder.getHtml());
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>", builder.getHtml());
+ builder.add("a");
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>a<BR/>", builder.getHtml());
+ builder.newline();
+ builder.newlineIfNecessary();
+ builder.newlineIfNecessary();
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>a<BR/><BR/>", builder.getHtml());
+ }
+
+ public void testAddHeading() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.addHeading("Welcome to Android Studio!", "black");
+ assertEquals(
+ "<font style=\"font-weight:bold; color:black;\">Welcome to Android Studio!</font>",
+ builder.getHtml());
+ }
+}
diff --git a/base/common/src/test/java/com/android/utils/PositionXmlParserTest.java b/common/src/test/java/com/android/utils/PositionXmlParserTest.java
similarity index 100%
rename from base/common/src/test/java/com/android/utils/PositionXmlParserTest.java
rename to common/src/test/java/com/android/utils/PositionXmlParserTest.java
diff --git a/common/src/test/java/com/android/utils/SdkUtilsTest.java b/common/src/test/java/com/android/utils/SdkUtilsTest.java
new file mode 100644
index 0000000..1d2ae67
--- /dev/null
+++ b/common/src/test/java/com/android/utils/SdkUtilsTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.utils;
+
+import static com.android.utils.SdkUtils.FILENAME_PREFIX;
+import static com.android.utils.SdkUtils.createPathComment;
+import static com.android.utils.SdkUtils.fileToUrlString;
+import static com.android.utils.SdkUtils.urlToFile;
+
+import com.android.SdkConstants;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Locale;
+
+ at SuppressWarnings("javadoc")
+public class SdkUtilsTest extends TestCase {
+
+ @Override
+ public void setUp() throws Exception {
+ // TODO: Use Files.createTempDir() to avoid this.
+ if (new File("/tmp/foo").isDirectory()) {
+ fail("This test will fail if /tmp/foo exists and is a directory. Please remove it.");
+ }
+ }
+
+ public void testEndsWithIgnoreCase() {
+ assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("foo", "Foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("Barfoo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foO"));
+
+ assertFalse(SdkUtils.endsWithIgnoreCase("foob", "foo"));
+ assertFalse(SdkUtils.endsWithIgnoreCase("foo", "fo"));
+ }
+
+ public void testStartsWithIgnoreCase() {
+ assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("foo", "Foo"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("barfoo", "bar"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bar"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bAr"));
+
+ assertFalse(SdkUtils.startsWithIgnoreCase("bfoo", "foo"));
+ assertFalse(SdkUtils.startsWithIgnoreCase("fo", "foo"));
+ }
+
+ public void testStartsWith() {
+ assertTrue(SdkUtils.startsWith("foo", 0, "foo"));
+ assertTrue(SdkUtils.startsWith("foo", 0, "Foo"));
+ assertTrue(SdkUtils.startsWith("Foo", 0, "foo"));
+ assertTrue(SdkUtils.startsWith("aFoo", 1, "foo"));
+
+ assertFalse(SdkUtils.startsWith("aFoo", 0, "foo"));
+ assertFalse(SdkUtils.startsWith("aFoo", 2, "foo"));
+ }
+
+ public void testEndsWith() {
+ assertTrue(SdkUtils.endsWith("foo", "foo"));
+ assertTrue(SdkUtils.endsWith("foobar", "obar"));
+ assertTrue(SdkUtils.endsWith("foobar", "bar"));
+ assertTrue(SdkUtils.endsWith("foobar", "ar"));
+ assertTrue(SdkUtils.endsWith("foobar", "r"));
+ assertTrue(SdkUtils.endsWith("foobar", ""));
+
+ assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), "bar"));
+ assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), new StringBuffer("obar")));
+ assertTrue(SdkUtils.endsWith("foobar", new StringBuffer("obar")));
+
+ assertFalse(SdkUtils.endsWith("foo", "fo"));
+ assertFalse(SdkUtils.endsWith("foobar", "Bar"));
+ assertFalse(SdkUtils.endsWith("foobar", "longfoobar"));
+ }
+
+ public void testEndsWith2() {
+ assertTrue(SdkUtils.endsWith("foo", "foo".length(), "foo"));
+ assertTrue(SdkUtils.endsWith("foo", "fo".length(), "fo"));
+ assertTrue(SdkUtils.endsWith("foo", "f".length(), "f"));
+ }
+
+ public void testStripWhitespace() {
+ assertEquals("foo", SdkUtils.stripWhitespace("foo"));
+ assertEquals("foobar", SdkUtils.stripWhitespace("foo bar"));
+ assertEquals("foobar", SdkUtils.stripWhitespace(" foo bar \n\t"));
+ }
+
+ public void testWrap() {
+ String s =
+ "Hardcoding text attributes directly in layout files is bad for several reasons:\n" +
+ "\n" +
+ "* When creating configuration variations (for example for landscape or portrait)" +
+ "you have to repeat the actual text (and keep it up to date when making changes)\n" +
+ "\n" +
+ "* The application cannot be translated to other languages by just adding new " +
+ "translations for existing string resources.";
+ String wrapped = SdkUtils.wrap(s, 70, "");
+ assertEquals(
+ "Hardcoding text attributes directly in layout files is bad for several\n" +
+ "reasons:\n" +
+ "\n" +
+ "* When creating configuration variations (for example for landscape or\n" +
+ "portrait)you have to repeat the actual text (and keep it up to date\n" +
+ "when making changes)\n" +
+ "\n" +
+ "* The application cannot be translated to other languages by just\n" +
+ "adding new translations for existing string resources.\n",
+ wrapped);
+ }
+
+ public void testWrapPrefix() {
+ String s =
+ "Hardcoding text attributes directly in layout files is bad for several reasons:\n" +
+ "\n" +
+ "* When creating configuration variations (for example for landscape or portrait)" +
+ "you have to repeat the actual text (and keep it up to date when making changes)\n" +
+ "\n" +
+ "* The application cannot be translated to other languages by just adding new " +
+ "translations for existing string resources.";
+ String wrapped = SdkUtils.wrap(s, 70, " ");
+ assertEquals(
+ "Hardcoding text attributes directly in layout files is bad for several\n" +
+ " reasons:\n" +
+ " \n" +
+ " * When creating configuration variations (for example for\n" +
+ " landscape or portrait)you have to repeat the actual text (and keep\n" +
+ " it up to date when making changes)\n" +
+ " \n" +
+ " * The application cannot be translated to other languages by just\n" +
+ " adding new translations for existing string resources.\n",
+ wrapped);
+ }
+
+ public void testParseInt() throws Exception {
+ Locale.setDefault(Locale.US);
+ assertEquals(1000, SdkUtils.parseLocalizedInt("1000"));
+ assertEquals(0, SdkUtils.parseLocalizedInt("0"));
+ assertEquals(0, SdkUtils.parseLocalizedInt(""));
+ assertEquals(1, SdkUtils.parseLocalizedInt("1"));
+ assertEquals(-1, SdkUtils.parseLocalizedInt("-1"));
+ assertEquals(1000, SdkUtils.parseLocalizedInt("1,000"));
+ assertEquals(1000000, SdkUtils.parseLocalizedInt("1,000,000"));
+
+ Locale.setDefault(Locale.ITALIAN);
+ assertSame(Locale.ITALIAN, Locale.getDefault());
+ assertEquals(1000, SdkUtils.parseLocalizedInt("1000"));
+ assertEquals(0, SdkUtils.parseLocalizedInt("0"));
+ assertEquals(1, SdkUtils.parseLocalizedInt("1"));
+ assertEquals(-1, SdkUtils.parseLocalizedInt("-1"));
+ assertEquals(1000, SdkUtils.parseLocalizedInt("1.000"));
+ assertEquals(1000000, SdkUtils.parseLocalizedInt("1.000.000"));
+
+ // Make sure it throws exceptions
+ try {
+ SdkUtils.parseLocalizedInt("X");
+ fail("Should have thrown exception");
+ } catch (ParseException e) {
+ // Expected
+ }
+ }
+
+ public void testParseIntWithDefault() throws Exception {
+ Locale.setDefault(Locale.US);
+ assertEquals(1000, SdkUtils.parseLocalizedInt("1000", 0)); // Valid
+ assertEquals(2, SdkUtils.parseLocalizedInt("2.X", 2)); // Parses prefix
+ assertEquals(5, SdkUtils.parseLocalizedInt("X", 5)); // Parses prefix
+
+ Locale.setDefault(Locale.ITALIAN);
+ assertEquals(1000, SdkUtils.parseLocalizedInt("1000", -1)); // Valid
+ assertEquals(7, SdkUtils.parseLocalizedInt("X", 7));
+ }
+
+ public void testParseDouble() throws Exception {
+ Locale.setDefault(Locale.US);
+ assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000"));
+ assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000.0"));
+ assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1000.5"));
+ assertEquals(0.0, SdkUtils.parseLocalizedDouble("0"));
+ assertEquals(0.0, SdkUtils.parseLocalizedDouble(""));
+ assertEquals(1.0, SdkUtils.parseLocalizedDouble("1"));
+ assertEquals(-1.0, SdkUtils.parseLocalizedDouble("-1"));
+ assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1,000"));
+ assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1,000.5"));
+ assertEquals(1000000.0, SdkUtils.parseLocalizedDouble("1,000,000"));
+ assertEquals(1000000.5, SdkUtils.parseLocalizedDouble("1,000,000.5"));
+
+ Locale.setDefault(Locale.ITALIAN);
+ assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000"));
+ assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1000,5"));
+ assertEquals(0.0, SdkUtils.parseLocalizedDouble("0"));
+ assertEquals(1.0, SdkUtils.parseLocalizedDouble("1"));
+ assertEquals(-1.0, SdkUtils.parseLocalizedDouble("-1"));
+ assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1.000"));
+ assertEquals(1000.5, SdkUtils.parseLocalizedDouble("1.000,5"));
+ assertEquals(1000000.0, SdkUtils.parseLocalizedDouble("1.000.000"));
+ assertEquals(1000000.5, SdkUtils.parseLocalizedDouble("1.000.000,5"));
+
+ // Make sure it throws exceptions
+ try {
+ SdkUtils.parseLocalizedDouble("X");
+ fail("Should have thrown exception");
+ } catch (ParseException e) {
+ // Expected
+ }
+ }
+
+ public void testParseDoubleWithDefault() throws Exception {
+ Locale.setDefault(Locale.US);
+ assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000", 0)); // Valid
+ assertEquals(2.0, SdkUtils.parseLocalizedDouble("2x", 3)); // Uses prefix
+ assertEquals(0.0, SdkUtils.parseLocalizedDouble("", 4));
+ assertEquals(5.0, SdkUtils.parseLocalizedDouble("test", 5)); // Invalid
+
+ Locale.setDefault(Locale.FRANCE);
+ assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000", -1)); // Valid
+ assertEquals(0.0, SdkUtils.parseLocalizedDouble("", 8));
+ }
+
+ public void testFileToUrl() throws Exception {
+ // path -- drive "C:" used as prefix in paths, empty for mac/linux.
+ String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
+ // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
+ String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
+
+ assertEquals(
+ "file://" + uDrive + "/tmp/foo/bar",
+ fileToUrlString(new File(pDrive + "/tmp/foo/bar")));
+ assertEquals(
+ "file://" + uDrive + "/tmp/$&+,:;=%3F@/foo%20bar%25",
+ fileToUrlString(new File(pDrive + "/tmp/$&+,:;=?@/foo bar%")));
+ }
+
+ public void testUrlToFile() throws Exception {
+ // path -- drive "C:" used as prefix in paths, empty for mac/linux.
+ String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
+ // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
+ String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
+
+ assertEquals(
+ new File(pDrive + "/tmp/foo/bar"),
+ urlToFile("file:" + uDrive + "/tmp/foo/bar"));
+ assertEquals(
+ new File(pDrive + "/tmp/$&+,:;=?@/foo bar%"),
+ urlToFile("file:" + uDrive + "/tmp/$&+,:;=%3F@/foo%20bar%25"));
+
+ assertEquals(
+ new File(pDrive + "/tmp/foo/bar"),
+ urlToFile(new URL("file:" + uDrive + "/tmp/foo/bar")));
+ assertEquals(
+ new File(pDrive + "/tmp/$&+,:;=?@/foo bar%"),
+ urlToFile(new URL("file:" + uDrive + "/tmp/$&+,:;=%3F@/foo%20bar%25")));
+ }
+
+ public void testCreatePathComment() throws Exception {
+ // path -- drive "C:" used as prefix in paths, empty for mac/linux.
+ String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
+ // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
+ String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
+
+ assertEquals(
+ "From: file://" + uDrive + "/tmp/foo",
+ createPathComment(new File(pDrive + "/tmp/foo"), false));
+ assertEquals(
+ " From: file://" + uDrive + "/tmp/foo ",
+ createPathComment(new File(pDrive + "/tmp/foo"), true));
+ assertEquals(
+ "From: file://" + uDrive + "/tmp-/%2D%2D/a%2D%2Da/foo",
+ createPathComment(new File(pDrive + "/tmp-/--/a--a/foo"), false));
+
+ String path = "/tmp/foo";
+ String urlString =
+ createPathComment(new File(pDrive + path), false).substring(5); // 5: "From:".length()
+ assertEquals(
+ (pDrive + path).replace('/', File.separatorChar),
+ urlToFile(new URL(urlString)).getPath());
+
+ path = "/tmp-/--/a--a/foo";
+ urlString = createPathComment(new File(pDrive + path), false).substring(5);
+ assertEquals(
+ (pDrive + path).replace('/', File.separatorChar),
+ urlToFile(new URL(urlString)).getPath());
+
+ // Make sure we handle file://path too, not just file:path
+ urlString = "file:///tmp-/%2D%2D/a%2D%2Da/foo";
+ assertEquals(
+ path.replace('/', File.separatorChar),
+ urlToFile(new URL(urlString)).getPath());
+ }
+
+ public void testFormattedComment() throws Exception {
+ // path -- drive "C:" used as prefix in paths, empty for mac/linux.
+ String pDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "C:" : "";
+ // url -- drive becomes "/C:" when used in URLs, empty for mac/linux.
+ String uDrive = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS ? "/C:" : "";
+
+ Document document = XmlUtils.parseDocumentSilently("<root/>", true);
+ assertNotNull(document);
+
+ // Many invalid characters in XML, such as -- and <, and characters invalid in URLs, such
+ // as spaces
+ String path = pDrive + "/My Program Files/--/Q&A/X<Y/foo";
+ String comment = createPathComment(new File(path), true);
+ Element root = document.getDocumentElement();
+ assertNotNull(root);
+ root.appendChild(document.createComment(comment));
+ String xml = XmlUtils.toXml(document);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<root>"
+ + "<!-- From: file://" + uDrive + "/My%20Program%20Files/%2D%2D/Q&A/X%3CY/foo -->"
+ + "</root>",
+ xml);
+ int index = xml.indexOf(FILENAME_PREFIX);
+ assertTrue(index != -1);
+ String urlString = xml.substring(index + FILENAME_PREFIX.length(),
+ xml.indexOf("-->")).trim();
+ assertEquals(
+ path.replace('/', File.separatorChar),
+ urlToFile(new URL(urlString)).getPath());
+ }
+
+ public void testNameConversionRoutines() {
+ assertEquals("xml-name", SdkUtils.constantNameToXmlName("XML_NAME"));
+ assertEquals("XML_NAME", SdkUtils.xmlNameToConstantName("xml-name"));
+ assertEquals("xmlName", SdkUtils.constantNameToCamelCase("XML_NAME"));
+ assertEquals("XML_NAME", SdkUtils.camelCaseToConstantName("xmlName"));
+ }
+
+ public void testGetResourceFieldName() {
+ assertEquals("", SdkUtils.getResourceFieldName(""));
+ assertEquals("foo", SdkUtils.getResourceFieldName("foo"));
+ assertEquals("Theme_Light", SdkUtils.getResourceFieldName("Theme.Light"));
+ assertEquals("Theme_Light", SdkUtils.getResourceFieldName("Theme.Light"));
+ assertEquals("abc____", SdkUtils.getResourceFieldName("abc:-._"));
+ }
+}
diff --git a/common/src/test/java/com/android/utils/StringHelperTest.java b/common/src/test/java/com/android/utils/StringHelperTest.java
new file mode 100644
index 0000000..4110a2a
--- /dev/null
+++ b/common/src/test/java/com/android/utils/StringHelperTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Tests for StringHelper
+ */
+public class StringHelperTest {
+
+ @Test
+ public void checkNoArg() throws Exception {
+ assertThat(StringHelper.tokenizeCommand("a")).containsExactly("a");
+ }
+
+ @Test
+ public void checkMultipleArgs() throws Exception {
+ assertThat(StringHelper.tokenizeCommand("a b c")).containsExactly("a", "b", "c");
+ }
+
+ @Test
+ public void checkDoubleQuote() throws Exception {
+ assertThat(StringHelper.tokenizeCommand("a \"b c\" d")).containsExactly("a", "\"b c\"", "d");
+ }
+
+ @Test
+ public void checkSingleQuote() throws Exception {
+ assertThat(StringHelper.tokenizeCommand("a 'b c'")).containsExactly("a", "'b c'");
+ }
+
+ @Test
+ public void checkSingleQuoteWithinDoubleQuote() throws Exception {
+ assertThat(StringHelper.tokenizeCommand("a \"b's c\"")).containsExactly("a", "\"b's c\"");
+ }
+}
\ No newline at end of file
diff --git a/common/src/test/java/com/android/utils/XmlUtilsTest.java b/common/src/test/java/com/android/utils/XmlUtilsTest.java
new file mode 100644
index 0000000..f74e1b6
--- /dev/null
+++ b/common/src/test/java/com/android/utils/XmlUtilsTest.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.utils;
+
+import static com.android.SdkConstants.XMLNS;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Maps;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+ at SuppressWarnings("javadoc")
+public class XmlUtilsTest extends TestCase {
+ public void testlookupNamespacePrefix() throws Exception {
+ // Setup
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.newDocument();
+ Element rootElement = document.createElement("root");
+ Attr attr = document.createAttributeNS(SdkConstants.XMLNS_URI,
+ "xmlns:customPrefix");
+ attr.setValue(SdkConstants.ANDROID_URI);
+ rootElement.getAttributes().setNamedItemNS(attr);
+ document.appendChild(rootElement);
+ Element root = document.getDocumentElement();
+ root.appendChild(document.createTextNode(" "));
+ Element foo = document.createElement("foo");
+ root.appendChild(foo);
+ root.appendChild(document.createTextNode(" "));
+ Element bar = document.createElement("bar");
+ root.appendChild(bar);
+ Element baz = document.createElement("baz");
+ root.appendChild(baz);
+
+ String prefix = XmlUtils.lookupNamespacePrefix(baz, SdkConstants.ANDROID_URI);
+ assertEquals("customPrefix", prefix);
+
+ prefix = XmlUtils.lookupNamespacePrefix(baz,
+ "http://schemas.android.com/tools", "tools", false);
+ assertEquals("tools", prefix);
+
+ prefix = XmlUtils.lookupNamespacePrefix(baz,
+ "http://schemas.android.com/apk/res/my/pkg", "app", false);
+ assertEquals("app", prefix);
+ assertFalse(declaresNamespace(document, "http://schemas.android.com/apk/res/my/pkg"));
+
+ prefix = XmlUtils.lookupNamespacePrefix(baz,
+ "http://schemas.android.com/apk/res/my/pkg", "app", true /*create*/);
+ assertEquals("app", prefix);
+ assertTrue(declaresNamespace(document, "http://schemas.android.com/apk/res/my/pkg"));
+ }
+
+ private static boolean declaresNamespace(Document document, String uri) {
+ NamedNodeMap attributes = document.getDocumentElement().getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String name = attribute.getName();
+ if (name.startsWith(XMLNS) && uri.equals(attribute.getValue())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void testToXmlAttributeValue() throws Exception {
+ assertEquals("", XmlUtils.toXmlAttributeValue(""));
+ assertEquals("foo", XmlUtils.toXmlAttributeValue("foo"));
+ assertEquals("foo<bar", XmlUtils.toXmlAttributeValue("foo<bar"));
+ assertEquals("foo>bar", XmlUtils.toXmlAttributeValue("foo>bar"));
+
+ assertEquals(""", XmlUtils.toXmlAttributeValue("\""));
+ assertEquals("'", XmlUtils.toXmlAttributeValue("'"));
+ assertEquals("foo"b''ar",
+ XmlUtils.toXmlAttributeValue("foo\"b''ar"));
+ assertEquals("<"'>&", XmlUtils.toXmlAttributeValue("<\"'>&"));
+ }
+
+ public void testFromXmlAttributeValue() throws Exception {
+ assertEquals("", XmlUtils.fromXmlAttributeValue(""));
+ assertEquals("foo", XmlUtils.fromXmlAttributeValue("foo"));
+ assertEquals("foo<bar", XmlUtils.fromXmlAttributeValue("foo<bar"));
+ assertEquals("foo<bar<bar>foo", XmlUtils.fromXmlAttributeValue("foo<bar<bar>foo"));
+ assertEquals("foo>bar", XmlUtils.fromXmlAttributeValue("foo>bar"));
+
+ assertEquals("\"", XmlUtils.fromXmlAttributeValue("""));
+ assertEquals("'", XmlUtils.fromXmlAttributeValue("'"));
+ assertEquals("foo\"b''ar", XmlUtils.fromXmlAttributeValue("foo"b''ar"));
+ assertEquals("<\"'>&", XmlUtils.fromXmlAttributeValue("<"'>&"));
+ }
+
+ public void testAppendXmlAttributeValue() throws Exception {
+ StringBuilder sb = new StringBuilder();
+ XmlUtils.appendXmlAttributeValue(sb, "<\"'>&");
+ assertEquals("<"'>&", sb.toString());
+ }
+
+ public void testToXmlTextValue() throws Exception {
+ assertEquals("<\"'>&", XmlUtils.toXmlTextValue("<\"'>&"));
+ }
+
+ public void testAppendXmlTextValue() throws Exception {
+ StringBuilder sb = new StringBuilder();
+ XmlUtils.appendXmlTextValue(sb, "<\"'>&");
+ assertEquals("<\"'>&", sb.toString());
+ }
+
+ public void testHasChildren() throws Exception {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.newDocument();
+ assertFalse(XmlUtils.hasElementChildren(document));
+ document.appendChild(document.createElement("A"));
+ Element a = document.getDocumentElement();
+ assertFalse(XmlUtils.hasElementChildren(a));
+ a.appendChild(document.createTextNode("foo"));
+ assertFalse(XmlUtils.hasElementChildren(a));
+ Element b = document.createElement("B");
+ a.appendChild(b);
+ assertTrue(XmlUtils.hasElementChildren(a));
+ assertFalse(XmlUtils.hasElementChildren(b));
+ }
+
+ public void testToXml() throws Exception {
+ Document doc = createEmptyPlainDocument();
+ assertNotNull(doc);
+ Element root = doc.createElement("myroot");
+ doc.appendChild(root);
+ root.setAttribute("foo", "bar");
+ root.setAttribute("baz", "baz");
+ Element child = doc.createElement("mychild");
+ root.appendChild(child);
+ Element child2 = doc.createElement("hasComment");
+ root.appendChild(child2);
+ Node comment = doc.createComment("This is my comment");
+ child2.appendChild(comment);
+ Element child3 = doc.createElement("hasText");
+ root.appendChild(child3);
+ Node text = doc.createTextNode(" This is my text ");
+ child3.appendChild(text);
+
+ String xml = XmlUtils.toXml(doc);
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<myroot baz=\"baz\" foo=\"bar\"><mychild/><hasComment><!--This is my comment--></hasComment><hasText> This is my text </hasText></myroot>",
+ xml);
+ }
+
+ public void testToXml2() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string \n"
+ + " name=\"description_search\">Search</string>\n"
+ + " <string \n"
+ + " name=\"description_map\">Map</string>\n"
+ + " <string\n"
+ + " name=\"description_refresh\">Refresh</string>\n"
+ + " <string \n"
+ + " name=\"description_share\">Share</string>\n"
+ + "</resources>";
+
+ Document doc = parse(xml);
+
+ String formatted = XmlUtils.toXml(doc);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"description_search\">Search</string>\n"
+ + " <string name=\"description_map\">Map</string>\n"
+ + " <string name=\"description_refresh\">Refresh</string>\n"
+ + " <string name=\"description_share\">Share</string>\n"
+ + "</resources>",
+ formatted);
+ }
+
+ public void testToXml3() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<root>\n"
+ + " <!-- ============== -->\n"
+ + " <!-- Generic styles -->\n"
+ + " <!-- ============== -->\n"
+ + "</root>";
+ Document doc = parse(xml);
+
+ String formatted = XmlUtils.toXml(doc);
+ assertEquals(xml, formatted);
+ }
+
+ public void testToXml3b() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <!-- ============== -->\n"
+ + " <!-- Generic styles -->\n"
+ + " <!-- ============== -->\n"
+ + " <string name=\"test\">test</string>\n"
+ + "</resources>";
+ Document doc = parse(xml);
+
+ String formatted = XmlUtils.toXml(doc);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <!-- ============== -->\n"
+ + " <!-- Generic styles -->\n"
+ + " <!-- ============== -->\n"
+ + " <string name=\"test\">test</string>\n"
+ + "</resources>",
+ formatted);
+ }
+
+
+ public void testToXml4() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<!-- ============== -->\n"
+ + "<!-- Generic styles -->\n"
+ + "<!-- ============== -->\n"
+ + "<root/>";
+ Document doc = parse(xml);
+
+ xml = XmlUtils.toXml(doc);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<!-- ============== --><!-- Generic styles --><!-- ============== --><root/>",
+ xml);
+ }
+
+ public void testToXml5() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<root>\n"
+ + " <!-- <&'>\" -->\n"
+ + "</root>";
+ Document doc = parse(xml);
+
+ String formatted = XmlUtils.toXml(doc);
+ assertEquals(xml, formatted);
+ }
+
+ public void testToXml6() throws Exception {
+ // Check CDATA
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string \n"
+ + " name=\"description_search\">Search</string>\n"
+ + " <string name=\"map_at\">At %1$s:<![CDATA[<br><b>%2$s</b>]]></string>\n"
+ + " <string name=\"map_now_playing\">Now playing:\n"
+ + "<![CDATA[\n"
+ + "<br><b>%1$s</b>\n"
+ + "]]></string>\n"
+ + "</resources>";
+
+ Document doc = parse(xml);
+
+ String formatted = XmlUtils.toXml(doc);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"description_search\">Search</string>\n"
+ + " <string name=\"map_at\">At %1$s:<![CDATA[<br><b>%2$s</b>]]></string>\n"
+ + " <string name=\"map_now_playing\">Now playing:\n"
+ + "<![CDATA[\n"
+ + "<br><b>%1$s</b>\n"
+ + "]]></string>\n"
+ + "</resources>",
+ formatted);
+ }
+
+ public void testPositionAwareXmlXmlBuilder() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string \n"
+ + " name=\"description_search\">Search</string>\n"
+ + " <string \n"
+ + " name=\"description_map\">Map</string>\n"
+ + " <string\n"
+ + " name=\"description_refresh\">Refresh</string>\n"
+ + " <string \n"
+ + " name=\"description_share\">Share</string>\n"
+ + "</resources>";
+
+ Document doc = PositionXmlParser.parse(xml);
+
+ Node string1 = doc.getFirstChild().getFirstChild().getNextSibling();
+ XmlUtils.attachSourceFile(string1, new SourceFile("source for first string"));
+
+ Node string2 = string1.getNextSibling().getNextSibling();
+ XmlUtils.attachSourceFile(string2, new SourceFile("source for second string"));
+
+ Map<SourcePosition, SourceFilePosition> positions = Maps.newLinkedHashMap();
+
+ String formatted = XmlUtils.toXml(doc, positions);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"description_search\">Search</string>\n"
+ + " <string name=\"description_map\">Map</string>\n"
+ + " <string name=\"description_refresh\">Refresh</string>\n"
+ + " <string name=\"description_share\">Share</string>\n"
+ + "</resources>",
+ formatted);
+
+ assertEquals(
+ new SourceFilePosition(
+ new SourceFile("source for first string"),
+ new SourcePosition(2, 4, 55, 3, 49, 113)),
+ positions.get(new SourcePosition(2, 4, 55, 2, 53, 104)));
+
+ assertEquals(
+ new SourceFilePosition(
+ new SourceFile("source for second string"),
+ new SourcePosition(4, 4, 118, 5, 43, 170)),
+ positions.get(new SourcePosition(3, 4, 109, 3, 47, 152)));
+
+ }
+
+ @Nullable
+ private static Document createEmptyPlainDocument() throws Exception {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder;
+ builder = factory.newDocumentBuilder();
+ return builder.newDocument();
+ }
+
+ @Nullable
+ private static Document parse(String xml) throws Exception {
+ if (true) {
+ return XmlUtils.parseDocumentSilently(xml, true);
+ }
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setExpandEntityReferences(false);
+ factory.setXIncludeAware(false);
+ factory.setIgnoringComments(false);
+ factory.setCoalescing(false);
+ DocumentBuilder builder;
+ builder = factory.newDocumentBuilder();
+ return builder.parse(new InputSource(new StringReader(xml)));
+ }
+
+ public void testFormatFloatValue() throws Exception {
+ assertEquals("1", XmlUtils.formatFloatAttribute(1.0f));
+ assertEquals("2", XmlUtils.formatFloatAttribute(2.0f));
+ assertEquals("1.50", XmlUtils.formatFloatAttribute(1.5f));
+ assertEquals("1.50", XmlUtils.formatFloatAttribute(1.50f));
+ assertEquals("1.51", XmlUtils.formatFloatAttribute(1.51f));
+ assertEquals("1.51", XmlUtils.formatFloatAttribute(1.514542f));
+ assertEquals("1.52", XmlUtils.formatFloatAttribute(1.516542f));
+ assertEquals("-1.51", XmlUtils.formatFloatAttribute(-1.51f));
+ assertEquals("-1", XmlUtils.formatFloatAttribute(-1f));
+ }
+
+ public void testFormatFloatValueLocale() throws Exception {
+ // Ensure that the layout float values aren't affected by
+ // locale settings, like using commas instead of of periods
+ Locale originalDefaultLocale = Locale.getDefault();
+
+ try {
+ Locale.setDefault(Locale.FRENCH);
+
+ // Ensure that this is a locale which uses a comma instead of a period:
+ assertEquals("5,24", String.format("%.2f", 5.236f));
+
+ // Ensure that the formatFloatAttribute is immune
+ assertEquals("1.50", XmlUtils.formatFloatAttribute(1.5f));
+ } finally {
+ Locale.setDefault(originalDefaultLocale);
+ }
+ }
+
+ public void testGetUtfReader() throws IOException {
+ File file = File.createTempFile(getName(), SdkConstants.DOT_XML);
+
+ BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
+ OutputStreamWriter writer = new OutputStreamWriter(stream, Charsets.UTF_8);
+ try {
+ stream.write(0xef);
+ stream.write(0xbb);
+ stream.write(0xbf);
+ writer.write("OK");
+ } finally {
+ writer.close();
+ }
+
+ Reader reader = XmlUtils.getUtfReader(file);
+ assertEquals('O', reader.read());
+ assertEquals('K', reader.read());
+ assertEquals(-1, reader.read());
+
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ }
+
+ public void testStripBom() {
+ assertEquals("", XmlUtils.stripBom(""));
+ assertEquals("Hello", XmlUtils.stripBom("Hello"));
+ assertEquals("Hello", XmlUtils.stripBom("\uFEFFHello"));
+ }
+
+ public void testParseDocument() throws Exception {
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n" +
+ " some text\n" +
+ "\n" +
+ "</LinearLayout>\n";
+
+ Document document = XmlUtils.parseDocument(xml, true);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ assertEquals("LinearLayout", document.getDocumentElement().getTagName());
+
+ // Add BOM
+ xml = '\uFEFF' + xml;
+ document = XmlUtils.parseDocument(xml, true);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ assertEquals("LinearLayout", document.getDocumentElement().getTagName());
+ }
+
+ public void testParseUtfXmlFile() throws Exception {
+ File file = File.createTempFile(getName(), SdkConstants.DOT_XML);
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n" +
+ " some text\n" +
+ "\n" +
+ "</LinearLayout>\n";
+
+ BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
+ OutputStreamWriter writer = new OutputStreamWriter(stream, Charsets.UTF_8);
+ try {
+ stream.write(0xef);
+ stream.write(0xbb);
+ stream.write(0xbf);
+ writer.write(xml);
+ } finally {
+ writer.close();
+ }
+
+ Document document = XmlUtils.parseUtfXmlFile(file, true);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ assertEquals("LinearLayout", document.getDocumentElement().getTagName());
+
+ //noinspection ResultOfMethodCallIgnored
+ file.delete();
+ }
+}
diff --git a/base/ddmlib/.classpath b/ddmlib/.classpath
similarity index 100%
rename from base/ddmlib/.classpath
rename to ddmlib/.classpath
diff --git a/base/ddmlib/.gitignore b/ddmlib/.gitignore
similarity index 100%
rename from base/ddmlib/.gitignore
rename to ddmlib/.gitignore
diff --git a/base/ddmlib/.project b/ddmlib/.project
similarity index 100%
rename from base/ddmlib/.project
rename to ddmlib/.project
diff --git a/base/ddmlib/.settings/org.eclipse.jdt.core.prefs b/ddmlib/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/ddmlib/.settings/org.eclipse.jdt.core.prefs
rename to ddmlib/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/ddmlib/NOTICE b/ddmlib/NOTICE
similarity index 100%
rename from base/ddmlib/NOTICE
rename to ddmlib/NOTICE
diff --git a/base/ddmlib/build.gradle b/ddmlib/build.gradle
similarity index 100%
rename from base/ddmlib/build.gradle
rename to ddmlib/build.gradle
diff --git a/base/ddmlib/ddmlib.iml b/ddmlib/ddmlib.iml
similarity index 100%
rename from base/ddmlib/ddmlib.iml
rename to ddmlib/ddmlib.iml
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/AdbCommandRejectedException.java b/ddmlib/src/main/java/com/android/ddmlib/AdbCommandRejectedException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/AdbCommandRejectedException.java
rename to ddmlib/src/main/java/com/android/ddmlib/AdbCommandRejectedException.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java b/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java
new file mode 100644
index 0000000..3b4f01e
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/AdbHelper.java
@@ -0,0 +1,940 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.log.LogReceiver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class to handle requests and connections to adb.
+ * <p/>{@link AndroidDebugBridge} is the public API to connection to adb, while {@link AdbHelper}
+ * does the low level stuff.
+ * <p/>This currently uses spin-wait non-blocking I/O. A Selector would be more efficient,
+ * but seems like overkill for what we're doing here.
+ */
+final class AdbHelper {
+
+ // public static final long kOkay = 0x59414b4fL;
+ // public static final long kFail = 0x4c494146L;
+
+ static final int WAIT_TIME = 5; // spin-wait sleep, in ms
+
+ static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
+
+ /** do not instantiate */
+ private AdbHelper() {
+ }
+
+ /**
+ * Response from ADB.
+ */
+ static class AdbResponse {
+ public AdbResponse() {
+ message = "";
+ }
+
+ public boolean okay; // first 4 bytes in response were "OKAY"?
+
+ public String message; // diagnostic string if #okay is false
+ }
+
+ /**
+ * Create and connect a new pass-through socket, from the host to a port on
+ * the device.
+ *
+ * @param adbSockAddr
+ * @param device the device to connect to. Can be null in which case the connection will be
+ * to the first available device.
+ * @param devicePort the port we're opening
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws IOException in case of I/O error on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ */
+ public static SocketChannel open(InetSocketAddress adbSockAddr,
+ Device device, int devicePort) throws IOException, TimeoutException, AdbCommandRejectedException {
+
+ SocketChannel adbChan = SocketChannel.open(adbSockAddr);
+ try {
+ adbChan.socket().setTcpNoDelay(true);
+ adbChan.configureBlocking(false);
+
+ // if the device is not -1, then we first tell adb we're looking to
+ // talk to a specific device
+ setDevice(adbChan, device);
+
+ byte[] req = createAdbForwardRequest(null, devicePort);
+ // Log.hexDump(req);
+
+ write(adbChan, req);
+
+ AdbResponse resp = readAdbResponse(adbChan, false);
+ if (!resp.okay) {
+ throw new AdbCommandRejectedException(resp.message);
+ }
+
+ adbChan.configureBlocking(true);
+ } catch (TimeoutException e) {
+ adbChan.close();
+ throw e;
+ } catch (IOException e) {
+ adbChan.close();
+ throw e;
+ } catch (AdbCommandRejectedException e) {
+ adbChan.close();
+ throw e;
+ }
+
+ return adbChan;
+ }
+
+ /**
+ * Creates and connects a new pass-through socket, from the host to a port on
+ * the device.
+ *
+ * @param adbSockAddr
+ * @param device the device to connect to. Can be null in which case the connection will be
+ * to the first available device.
+ * @param pid the process pid to connect to.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public static SocketChannel createPassThroughConnection(InetSocketAddress adbSockAddr,
+ Device device, int pid) throws TimeoutException, AdbCommandRejectedException, IOException {
+
+ SocketChannel adbChan = SocketChannel.open(adbSockAddr);
+ try {
+ adbChan.socket().setTcpNoDelay(true);
+ adbChan.configureBlocking(false);
+
+ // if the device is not -1, then we first tell adb we're looking to
+ // talk to a specific device
+ setDevice(adbChan, device);
+
+ byte[] req = createJdwpForwardRequest(pid);
+ // Log.hexDump(req);
+
+ write(adbChan, req);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ throw new AdbCommandRejectedException(resp.message);
+ }
+
+ adbChan.configureBlocking(true);
+ } catch (TimeoutException e) {
+ adbChan.close();
+ throw e;
+ } catch (IOException e) {
+ adbChan.close();
+ throw e;
+ } catch (AdbCommandRejectedException e) {
+ adbChan.close();
+ throw e;
+ }
+
+ return adbChan;
+ }
+
+ /**
+ * Creates a port forwarding request for adb. This returns an array
+ * containing "####tcp:{port}:{addStr}".
+ * @param addrStr the host. Can be null.
+ * @param port the port on the device. This does not need to be numeric.
+ */
+ private static byte[] createAdbForwardRequest(String addrStr, int port) {
+ String reqStr;
+
+ if (addrStr == null)
+ reqStr = "tcp:" + port;
+ else
+ reqStr = "tcp:" + port + ":" + addrStr;
+ return formAdbRequest(reqStr);
+ }
+
+ /**
+ * Creates a port forwarding request to a jdwp process. This returns an array
+ * containing "####jwdp:{pid}".
+ * @param pid the jdwp process pid on the device.
+ */
+ private static byte[] createJdwpForwardRequest(int pid) {
+ String reqStr = String.format("jdwp:%1$d", pid); //$NON-NLS-1$
+ return formAdbRequest(reqStr);
+ }
+
+ /**
+ * Create an ASCII string preceded by four hex digits. The opening "####"
+ * is the length of the rest of the string, encoded as ASCII hex (case
+ * doesn't matter).
+ */
+ public static byte[] formAdbRequest(String req) {
+ String resultStr = String.format("%04X%s", req.length(), req); //$NON-NLS-1$
+ byte[] result;
+ try {
+ result = resultStr.getBytes(DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException uee) {
+ uee.printStackTrace(); // not expected
+ return null;
+ }
+ assert result.length == req.length() + 4;
+ return result;
+ }
+
+ /**
+ * Reads the response from ADB after a command.
+ * @param chan The socket channel that is connected to adb.
+ * @param readDiagString If true, we're expecting an OKAY response to be
+ * followed by a diagnostic string. Otherwise, we only expect the
+ * diagnostic string to follow a FAIL.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws IOException in case of I/O error on the connection.
+ */
+ static AdbResponse readAdbResponse(SocketChannel chan, boolean readDiagString)
+ throws TimeoutException, IOException {
+
+ AdbResponse resp = new AdbResponse();
+
+ byte[] reply = new byte[4];
+ read(chan, reply);
+
+ if (isOkay(reply)) {
+ resp.okay = true;
+ } else {
+ readDiagString = true; // look for a reason after the FAIL
+ resp.okay = false;
+ }
+
+ // not a loop -- use "while" so we can use "break"
+ try {
+ while (readDiagString) {
+ // length string is in next 4 bytes
+ byte[] lenBuf = new byte[4];
+ read(chan, lenBuf);
+
+ String lenStr = replyToString(lenBuf);
+
+ int len;
+ try {
+ len = Integer.parseInt(lenStr, 16);
+ } catch (NumberFormatException nfe) {
+ Log.w("ddms", "Expected digits, got '" + lenStr + "': "
+ + lenBuf[0] + " " + lenBuf[1] + " " + lenBuf[2] + " "
+ + lenBuf[3]);
+ Log.w("ddms", "reply was " + replyToString(reply));
+ break;
+ }
+
+ byte[] msg = new byte[len];
+ read(chan, msg);
+
+ resp.message = replyToString(msg);
+ Log.v("ddms", "Got reply '" + replyToString(reply) + "', diag='"
+ + resp.message + "'");
+
+ break;
+ }
+ } catch (Exception e) {
+ // ignore those, since it's just reading the diagnose string, the response will
+ // contain okay==false anyway.
+ }
+
+ return resp;
+ }
+
+ /**
+ * Retrieve the frame buffer from the device with the given timeout. A timeout of 0 indicates
+ * that it will wait forever.
+ *
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device, long timeout, TimeUnit unit)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+
+ RawImage imageParams = new RawImage();
+ byte[] request = formAdbRequest("framebuffer:"); //$NON-NLS-1$
+ byte[] nudge = {
+ 0
+ };
+ byte[] reply;
+
+ SocketChannel adbChan = null;
+ try {
+ adbChan = SocketChannel.open(adbSockAddr);
+ adbChan.configureBlocking(false);
+
+ // if the device is not -1, then we first tell adb we're looking to talk
+ // to a specific device
+ setDevice(adbChan, device);
+
+ write(adbChan, request);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ throw new AdbCommandRejectedException(resp.message);
+ }
+
+ // first the protocol version.
+ reply = new byte[4];
+ read(adbChan, reply);
+
+ ByteBuffer buf = ByteBuffer.wrap(reply);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ int version = buf.getInt();
+
+ // get the header size (this is a count of int)
+ int headerSize = RawImage.getHeaderSize(version);
+
+ // read the header
+ reply = new byte[headerSize * 4];
+ read(adbChan, reply);
+
+ buf = ByteBuffer.wrap(reply);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ // fill the RawImage with the header
+ if (!imageParams.readHeader(version, buf)) {
+ Log.e("Screenshot", "Unsupported protocol: " + version);
+ return null;
+ }
+
+ Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size="
+ + imageParams.size + ", width=" + imageParams.width
+ + ", height=" + imageParams.height);
+
+ write(adbChan, nudge);
+
+ reply = new byte[imageParams.size];
+ read(adbChan, reply, imageParams.size, unit.toMillis(timeout));
+
+ imageParams.data = reply;
+ } finally {
+ if (adbChan != null) {
+ adbChan.close();
+ }
+ }
+
+ return imageParams;
+ }
+
+ /**
+ * @deprecated Use {@link #executeRemoteCommand(java.net.InetSocketAddress, String, IDevice, IShellOutputReceiver, long, java.util.concurrent.TimeUnit)}.
+ */
+ @Deprecated
+ static void executeRemoteCommand(InetSocketAddress adbSockAddr,
+ String command, IDevice device, IShellOutputReceiver rcvr, int maxTimeToOutputResponse)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ executeRemoteCommand(adbSockAddr, command, device, rcvr, maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Executes a shell command on the device and retrieve the output. The output is
+ * handed to <var>rcvr</var> as it arrives.
+ *
+ * @param adbSockAddr the {@link InetSocketAddress} to adb.
+ * @param command the shell command to execute
+ * @param device the {@link IDevice} on which to execute the command.
+ * @param rcvr the {@link IShellOutputReceiver} that will receives the output of the shell
+ * command
+ * @param maxTimeToOutputResponse max time between command output. If more time passes
+ * between command output, the method will throw
+ * {@link ShellCommandUnresponsiveException}. A value of 0 means the method will
+ * wait forever for command output and never throw.
+ * @param maxTimeUnits Units for non-zero {@code maxTimeToOutputResponse} values.
+ * @throws TimeoutException in case of timeout on the connection when sending the command.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
+ * for a period longer than <var>maxTimeToOutputResponse</var>.
+ * @throws IOException in case of I/O error on the connection.
+ *
+ * @see DdmPreferences#getTimeOut()
+ */
+ static void executeRemoteCommand(InetSocketAddress adbSockAddr,
+ String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+
+ executeRemoteCommand(adbSockAddr, AdbService.SHELL, command, device, rcvr, maxTimeToOutputResponse,
+ maxTimeUnits, null /* inputStream */);
+ }
+
+ /**
+ * Identify which adb service the command should target.
+ */
+ public enum AdbService {
+ /**
+ * the shell service
+ */
+ SHELL,
+
+ /**
+ * The exec service.
+ */
+ EXEC
+ }
+
+ /**
+ * Executes a remote command on the device and retrieve the output. The output is
+ * handed to <var>rcvr</var> as it arrives. The command is execute by the remote service
+ * identified by the adbService parameter.
+ *
+ * @param adbSockAddr the {@link InetSocketAddress} to adb.
+ * @param adbService the {@link com.android.ddmlib.AdbHelper.AdbService} to use to run the
+ * command.
+ * @param command the shell command to execute
+ * @param device the {@link IDevice} on which to execute the command.
+ * @param rcvr the {@link IShellOutputReceiver} that will receives the output of the shell
+ * command
+ * @param maxTimeToOutputResponse max time between command output. If more time passes
+ * between command output, the method will throw
+ * {@link ShellCommandUnresponsiveException}. A value of 0 means the method will
+ * wait forever for command output and never throw.
+ * @param maxTimeUnits Units for non-zero {@code maxTimeToOutputResponse} values.
+ * @param is a optional {@link InputStream} to be streamed up after invoking the command
+ * and before retrieving the response.
+ * @throws TimeoutException in case of timeout on the connection when sending the command.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
+ * for a period longer than <var>maxTimeToOutputResponse</var>.
+ * @throws IOException in case of I/O error on the connection.
+ *
+ * @see DdmPreferences#getTimeOut()
+ */
+ static void executeRemoteCommand(InetSocketAddress adbSockAddr, AdbService adbService,
+ String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse,
+ TimeUnit maxTimeUnits,
+ @Nullable InputStream is) throws TimeoutException, AdbCommandRejectedException,
+ ShellCommandUnresponsiveException, IOException {
+
+ long maxTimeToOutputMs = 0;
+ if (maxTimeToOutputResponse > 0) {
+ if (maxTimeUnits == null) {
+ throw new NullPointerException("Time unit must not be null for non-zero max.");
+ }
+ maxTimeToOutputMs = maxTimeUnits.toMillis(maxTimeToOutputResponse);
+ }
+
+ Log.v("ddms", "execute: running " + command);
+
+ SocketChannel adbChan = null;
+ try {
+ adbChan = SocketChannel.open(adbSockAddr);
+ adbChan.configureBlocking(false);
+
+ // if the device is not -1, then we first tell adb we're looking to
+ // talk to a specific device
+ setDevice(adbChan, device);
+
+ byte[] request = formAdbRequest(adbService.name().toLowerCase() + ":" + command); //$NON-NLS-1$
+ write(adbChan, request);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message);
+ throw new AdbCommandRejectedException(resp.message);
+ }
+
+ byte[] data = new byte[16384];
+
+ // stream the input file if present.
+ if (is != null) {
+ int read;
+ while ((read = is.read(data)) != -1) {
+ ByteBuffer buf = ByteBuffer.wrap(data, 0, read);
+ int written = 0;
+ while (buf.hasRemaining()) {
+ written += adbChan.write(buf);
+ }
+ if (written != read) {
+ Log.e("ddms",
+ "ADB write inconsistency, wrote " + written + "expected " + read);
+ throw new AdbCommandRejectedException("write failed");
+ }
+ }
+ }
+
+ ByteBuffer buf = ByteBuffer.wrap(data);
+ buf.clear();
+ long timeToResponseCount = 0;
+ while (true) {
+ int count;
+
+ if (rcvr != null && rcvr.isCancelled()) {
+ Log.v("ddms", "execute: cancelled");
+ break;
+ }
+
+ count = adbChan.read(buf);
+ if (count < 0) {
+ // we're at the end, we flush the output
+ rcvr.flush();
+ Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: "
+ + count);
+ break;
+ } else if (count == 0) {
+ try {
+ int wait = WAIT_TIME * 5;
+ timeToResponseCount += wait;
+ if (maxTimeToOutputMs > 0 && timeToResponseCount > maxTimeToOutputMs) {
+ throw new ShellCommandUnresponsiveException();
+ }
+ Thread.sleep(wait);
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // Throw a timeout exception in place of interrupted exception to avoid API changes.
+ throw new TimeoutException("executeRemoteCommand interrupted with immediate timeout via interruption.");
+ }
+ } else {
+ // reset timeout
+ timeToResponseCount = 0;
+
+ // send data to receiver if present
+ if (rcvr != null) {
+ rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
+ }
+ buf.rewind();
+ }
+ }
+ } finally {
+ if (adbChan != null) {
+ adbChan.close();
+ }
+ Log.v("ddms", "execute: returning");
+ }
+ }
+
+ /**
+ * Runs the Event log service on the {@link Device}, and provides its output to the
+ * {@link LogReceiver}.
+ * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+ * @param adbSockAddr the socket address to connect to adb
+ * @param device the Device on which to run the service
+ * @param rcvr the {@link LogReceiver} to receive the log output
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public static void runEventLogService(InetSocketAddress adbSockAddr, Device device,
+ LogReceiver rcvr) throws TimeoutException, AdbCommandRejectedException, IOException {
+ runLogService(adbSockAddr, device, "events", rcvr); //$NON-NLS-1$
+ }
+
+ /**
+ * Runs a log service on the {@link Device}, and provides its output to the {@link LogReceiver}.
+ * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+ * @param adbSockAddr the socket address to connect to adb
+ * @param device the Device on which to run the service
+ * @param logName the name of the log file to output
+ * @param rcvr the {@link LogReceiver} to receive the log output
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public static void runLogService(InetSocketAddress adbSockAddr, Device device, String logName,
+ LogReceiver rcvr) throws TimeoutException, AdbCommandRejectedException, IOException {
+ SocketChannel adbChan = null;
+
+ try {
+ adbChan = SocketChannel.open(adbSockAddr);
+ adbChan.configureBlocking(false);
+
+ // if the device is not -1, then we first tell adb we're looking to talk
+ // to a specific device
+ setDevice(adbChan, device);
+
+ byte[] request = formAdbRequest("log:" + logName);
+ write(adbChan, request);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ throw new AdbCommandRejectedException(resp.message);
+ }
+
+ byte[] data = new byte[16384];
+ ByteBuffer buf = ByteBuffer.wrap(data);
+ while (true) {
+ int count;
+
+ if (rcvr != null && rcvr.isCancelled()) {
+ break;
+ }
+
+ count = adbChan.read(buf);
+ if (count < 0) {
+ break;
+ } else if (count == 0) {
+ try {
+ Thread.sleep(WAIT_TIME * 5);
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // Throw a timeout exception in place of interrupted exception to avoid API changes.
+ throw new TimeoutException("runLogService interrupted with immediate timeout via interruption.");
+ }
+ } else {
+ if (rcvr != null) {
+ rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position());
+ }
+ buf.rewind();
+ }
+ }
+ } finally {
+ if (adbChan != null) {
+ adbChan.close();
+ }
+ }
+ }
+
+ /**
+ * Creates a port forwarding between a local and a remote port.
+ * @param adbSockAddr the socket address to connect to adb
+ * @param device the device on which to do the port forwarding
+ * @param localPortSpec specification of the local port to forward, should be of format
+ * tcp:<port number>
+ * @param remotePortSpec specification of the remote port to forward to, one of:
+ * tcp:<port>
+ * localabstract:<unix domain socket name>
+ * localreserved:<unix domain socket name>
+ * localfilesystem:<unix domain socket name>
+ * dev:<character device name>
+ * jdwp:<process pid> (remote only)
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public static void createForward(InetSocketAddress adbSockAddr, Device device,
+ String localPortSpec, String remotePortSpec)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+
+ SocketChannel adbChan = null;
+ try {
+ adbChan = SocketChannel.open(adbSockAddr);
+ adbChan.configureBlocking(false);
+
+ byte[] request = formAdbRequest(String.format(
+ "host-serial:%1$s:forward:%2$s;%3$s", //$NON-NLS-1$
+ device.getSerialNumber(), localPortSpec, remotePortSpec));
+
+ write(adbChan, request);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ Log.w("create-forward", "Error creating forward: " + resp.message);
+ throw new AdbCommandRejectedException(resp.message);
+ }
+ } finally {
+ if (adbChan != null) {
+ adbChan.close();
+ }
+ }
+ }
+
+ /**
+ * Remove a port forwarding between a local and a remote port.
+ * @param adbSockAddr the socket address to connect to adb
+ * @param device the device on which to remove the port forwarding
+ * @param localPortSpec specification of the local port that was forwarded, should be of format
+ * tcp:<port number>
+ * @param remotePortSpec specification of the remote port forwarded to, one of:
+ * tcp:<port>
+ * localabstract:<unix domain socket name>
+ * localreserved:<unix domain socket name>
+ * localfilesystem:<unix domain socket name>
+ * dev:<character device name>
+ * jdwp:<process pid> (remote only)
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public static void removeForward(InetSocketAddress adbSockAddr, Device device,
+ String localPortSpec, String remotePortSpec)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+
+ SocketChannel adbChan = null;
+ try {
+ adbChan = SocketChannel.open(adbSockAddr);
+ adbChan.configureBlocking(false);
+
+ byte[] request = formAdbRequest(String.format(
+ "host-serial:%1$s:killforward:%2$s", //$NON-NLS-1$
+ device.getSerialNumber(), localPortSpec));
+
+ write(adbChan, request);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ Log.w("remove-forward", "Error creating forward: " + resp.message);
+ throw new AdbCommandRejectedException(resp.message);
+ }
+ } finally {
+ if (adbChan != null) {
+ adbChan.close();
+ }
+ }
+ }
+
+ /**
+ * Checks to see if the first four bytes in "reply" are OKAY.
+ */
+ static boolean isOkay(byte[] reply) {
+ return reply[0] == (byte)'O' && reply[1] == (byte)'K'
+ && reply[2] == (byte)'A' && reply[3] == (byte)'Y';
+ }
+
+ /**
+ * Converts an ADB reply to a string.
+ */
+ static String replyToString(byte[] reply) {
+ String result;
+ try {
+ result = new String(reply, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException uee) {
+ uee.printStackTrace(); // not expected
+ result = "";
+ }
+ return result;
+ }
+
+ /**
+ * Reads from the socket until the array is filled, or no more data is coming (because
+ * the socket closed or the timeout expired).
+ * <p/>This uses the default time out value.
+ *
+ * @param chan the opened socket to read from. It must be in non-blocking
+ * mode for timeouts to work
+ * @param data the buffer to store the read data into.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws IOException in case of I/O error on the connection.
+ */
+ static void read(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
+ read(chan, data, -1, DdmPreferences.getTimeOut());
+ }
+
+ /**
+ * Reads from the socket until the array is filled, the optional length
+ * is reached, or no more data is coming (because the socket closed or the
+ * timeout expired). After "timeout" milliseconds since the
+ * previous successful read, this will return whether or not new data has
+ * been found.
+ *
+ * @param chan the opened socket to read from. It must be in non-blocking
+ * mode for timeouts to work
+ * @param data the buffer to store the read data into.
+ * @param length the length to read or -1 to fill the data buffer completely
+ * @param timeout The timeout value in ms. A timeout of zero means "wait forever".
+ */
+ static void read(SocketChannel chan, byte[] data, int length, long timeout) throws TimeoutException, IOException {
+ ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
+ int numWaits = 0;
+
+ while (buf.position() != buf.limit()) {
+ int count;
+
+ count = chan.read(buf);
+ if (count < 0) {
+ Log.d("ddms", "read: channel EOF");
+ throw new IOException("EOF");
+ } else if (count == 0) {
+ // TODO: need more accurate timeout?
+ if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
+ Log.d("ddms", "read: timeout");
+ throw new TimeoutException();
+ }
+ try {
+ // non-blocking spin
+ Thread.sleep(WAIT_TIME);
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // Throw a timeout exception in place of interrupted exception to avoid API changes.
+ throw new TimeoutException("Read interrupted with immediate timeout via interruption.");
+ }
+ numWaits++;
+ } else {
+ numWaits = 0;
+ }
+ }
+ }
+
+ /**
+ * Write until all data in "data" is written or the connection fails or times out.
+ * <p/>This uses the default time out value.
+ * @param chan the opened socket to write to.
+ * @param data the buffer to send.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws IOException in case of I/O error on the connection.
+ */
+ static void write(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
+ write(chan, data, -1, DdmPreferences.getTimeOut());
+ }
+
+ /**
+ * Write until all data in "data" is written, the optional length is reached,
+ * the timeout expires, or the connection fails. Returns "true" if all
+ * data was written.
+ * @param chan the opened socket to write to.
+ * @param data the buffer to send.
+ * @param length the length to write or -1 to send the whole buffer.
+ * @param timeout The timeout value. A timeout of zero means "wait forever".
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws IOException in case of I/O error on the connection.
+ */
+ static void write(SocketChannel chan, byte[] data, int length, int timeout) throws TimeoutException, IOException {
+ ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
+ int numWaits = 0;
+
+ while (buf.position() != buf.limit()) {
+ int count;
+
+ count = chan.write(buf);
+ if (count < 0) {
+ Log.d("ddms", "write: channel EOF");
+ throw new IOException("channel EOF");
+ } else if (count == 0) {
+ // TODO: need more accurate timeout?
+ if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
+ Log.d("ddms", "write: timeout");
+ throw new TimeoutException();
+ }
+ try {
+ // non-blocking spin
+ Thread.sleep(WAIT_TIME);
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // Throw a timeout exception in place of interrupted exception to avoid API changes.
+ throw new TimeoutException("Write interrupted with immediate timeout via interruption.");
+ }
+ numWaits++;
+ } else {
+ numWaits = 0;
+ }
+ }
+ }
+
+ /**
+ * tells adb to talk to a specific device
+ *
+ * @param adbChan the socket connection to adb
+ * @param device The device to talk to.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ static void setDevice(SocketChannel adbChan, IDevice device)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ // if the device is not -1, then we first tell adb we're looking to talk
+ // to a specific device
+ if (device != null) {
+ String msg = "host:transport:" + device.getSerialNumber(); //$NON-NLS-1$
+ byte[] device_query = formAdbRequest(msg);
+
+ write(adbChan, device_query);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ throw new AdbCommandRejectedException(resp.message,
+ true/*errorDuringDeviceSelection*/);
+ }
+ }
+ }
+
+ /**
+ * Reboot the device.
+ *
+ * @param into what to reboot into (recovery, bootloader). Or null to just reboot.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public static void reboot(String into, InetSocketAddress adbSockAddr, Device device)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ byte[] request;
+ if (into == null) {
+ request = formAdbRequest("reboot:"); //$NON-NLS-1$
+ } else {
+ request = formAdbRequest("reboot:" + into); //$NON-NLS-1$
+ }
+
+ SocketChannel adbChan = null;
+ try {
+ adbChan = SocketChannel.open(adbSockAddr);
+ adbChan.configureBlocking(false);
+
+ // if the device is not -1, then we first tell adb we're looking to talk
+ // to a specific device
+ setDevice(adbChan, device);
+
+ write(adbChan, request);
+ } finally {
+ if (adbChan != null) {
+ adbChan.close();
+ }
+ }
+ }
+
+
+ /**
+ * Ask the adb demon to become root on the device.
+ * This may silently fail, and can only succeed on developer builds.
+ * See "adb root" for more information.
+ * If you need to know if succeeded, you can check the result of executeRemoteCommand on 'echo \$USER_ID', if it is 0 then adbd is
+ * running as root.
+ *
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public static void root(@NonNull InetSocketAddress adbSockAddr, @NonNull Device device)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ byte[] request = formAdbRequest("root:"); //$NON-NLS-1$
+ SocketChannel adbChan = null;
+ try {
+ adbChan = SocketChannel.open(adbSockAddr);
+ adbChan.configureBlocking(false);
+
+ // if the device is not -1, then we first tell adb we're looking to talk
+ // to a specific device
+ setDevice(adbChan, device);
+
+ write(adbChan, request);
+
+ AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+ if (!resp.okay) {
+ Log.w("root", "Error setting root: " + resp.message);
+ throw new AdbCommandRejectedException(resp.message);
+ }
+
+ } finally {
+ if (adbChan != null) {
+ adbChan.close();
+ }
+ }
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/AdbVersion.java b/ddmlib/src/main/java/com/android/ddmlib/AdbVersion.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/AdbVersion.java
rename to ddmlib/src/main/java/com/android/ddmlib/AdbVersion.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java b/ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java
rename to ddmlib/src/main/java/com/android/ddmlib/AllocationInfo.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/AllocationsParser.java b/ddmlib/src/main/java/com/android/ddmlib/AllocationsParser.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/AllocationsParser.java
rename to ddmlib/src/main/java/com/android/ddmlib/AllocationsParser.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java b/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
new file mode 100644
index 0000000..464067e
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
@@ -0,0 +1,1126 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.ddmlib.Log.LogLevel;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closeables;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Thread.State;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A connection to the host-side android debug bridge (adb)
+ * <p/>This is the central point to communicate with any devices, emulators, or the applications
+ * running on them.
+ * <p/><b>{@link #init(boolean)} must be called before anything is done.</b>
+ */
+public final class AndroidDebugBridge {
+
+ /*
+ * Minimum and maximum version of adb supported. This correspond to
+ * ADB_SERVER_VERSION found in //device/tools/adb/adb.h
+ */
+ private static final AdbVersion MIN_ADB_VERSION = AdbVersion.parseFrom("1.0.20");
+
+ private static final String ADB = "adb"; //$NON-NLS-1$
+ private static final String DDMS = "ddms"; //$NON-NLS-1$
+ private static final String SERVER_PORT_ENV_VAR = "ANDROID_ADB_SERVER_PORT"; //$NON-NLS-1$
+
+ // Where to find the ADB bridge.
+ static final String DEFAULT_ADB_HOST = "127.0.0.1"; //$NON-NLS-1$
+ static final int DEFAULT_ADB_PORT = 5037;
+
+ /** Port where adb server will be started **/
+ private static int sAdbServerPort = 0;
+
+ private static InetAddress sHostAddr;
+ private static InetSocketAddress sSocketAddr;
+
+ private static AndroidDebugBridge sThis;
+ private static boolean sInitialized = false;
+ private static boolean sClientSupport;
+
+ /** Full path to adb. */
+ private String mAdbOsLocation = null;
+
+ private boolean mVersionCheck;
+
+ private boolean mStarted = false;
+
+ private DeviceMonitor mDeviceMonitor;
+
+ // lock object for synchronization
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static final Set<IDebugBridgeChangeListener> sBridgeListeners =
+ Sets.newCopyOnWriteArraySet();
+
+ private static final Set<IDeviceChangeListener> sDeviceListeners =
+ Sets.newCopyOnWriteArraySet();
+ private static final Set<IClientChangeListener> sClientListeners =
+ Sets.newCopyOnWriteArraySet();
+
+ /**
+ * Classes which implement this interface provide a method that deals
+ * with {@link AndroidDebugBridge} changes.
+ */
+ public interface IDebugBridgeChangeListener {
+ /**
+ * Sent when a new {@link AndroidDebugBridge} is connected.
+ * <p/>
+ * This is sent from a non UI thread.
+ * @param bridge the new {@link AndroidDebugBridge} object, null if there were errors while
+ * initializing the bridge
+ */
+ void bridgeChanged(@Nullable AndroidDebugBridge bridge);
+ }
+
+ /**
+ * Classes which implement this interface provide methods that deal
+ * with {@link IDevice} addition, deletion, and changes.
+ */
+ public interface IDeviceChangeListener {
+ /**
+ * Sent when the a device is connected to the {@link AndroidDebugBridge}.
+ * <p/>
+ * This is sent from a non UI thread.
+ * @param device the new device.
+ */
+ void deviceConnected(@NonNull IDevice device);
+
+ /**
+ * Sent when the a device is connected to the {@link AndroidDebugBridge}.
+ * <p/>
+ * This is sent from a non UI thread.
+ * @param device the new device.
+ */
+ void deviceDisconnected(@NonNull IDevice device);
+
+ /**
+ * Sent when a device data changed, or when clients are started/terminated on the device.
+ * <p/>
+ * This is sent from a non UI thread.
+ * @param device the device that was updated.
+ * @param changeMask the mask describing what changed. It can contain any of the following
+ * values: {@link IDevice#CHANGE_BUILD_INFO}, {@link IDevice#CHANGE_STATE},
+ * {@link IDevice#CHANGE_CLIENT_LIST}
+ */
+ void deviceChanged(@NonNull IDevice device, int changeMask);
+ }
+
+ /**
+ * Classes which implement this interface provide methods that deal
+ * with {@link Client} changes.
+ */
+ public interface IClientChangeListener {
+ /**
+ * Sent when an existing client information changed.
+ * <p/>
+ * This is sent from a non UI thread.
+ * @param client the updated client.
+ * @param changeMask the bit mask describing the changed properties. It can contain
+ * any of the following values: {@link Client#CHANGE_INFO},
+ * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
+ * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
+ * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
+ */
+ void clientChanged(@NonNull Client client, int changeMask);
+ }
+
+ /**
+ * Initialized the library only if needed.
+ *
+ * @param clientSupport Indicates whether the library should enable the monitoring and
+ * interaction with applications running on the devices.
+ *
+ * @see #init(boolean)
+ */
+ public static synchronized void initIfNeeded(boolean clientSupport) {
+ if (sInitialized) {
+ return;
+ }
+
+ init(clientSupport);
+ }
+
+ /**
+ * Initializes the <code>ddm</code> library.
+ * <p/>This must be called once <b>before</b> any call to
+ * {@link #createBridge(String, boolean)}.
+ * <p>The library can be initialized in 2 ways:
+ * <ul>
+ * <li>Mode 1: <var>clientSupport</var> == <code>true</code>.<br>The library monitors the
+ * devices and the applications running on them. It will connect to each application, as a
+ * debugger of sort, to be able to interact with them through JDWP packets.</li>
+ * <li>Mode 2: <var>clientSupport</var> == <code>false</code>.<br>The library only monitors
+ * devices. The applications are left untouched, letting other tools built on
+ * <code>ddmlib</code> to connect a debugger to them.</li>
+ * </ul>
+ * <p/><b>Only one tool can run in mode 1 at the same time.</b>
+ * <p/>Note that mode 1 does not prevent debugging of applications running on devices. Mode 1
+ * lets debuggers connect to <code>ddmlib</code> which acts as a proxy between the debuggers and
+ * the applications to debug. See {@link Client#getDebuggerListenPort()}.
+ * <p/>The preferences of <code>ddmlib</code> should also be initialized with whatever default
+ * values were changed from the default values.
+ * <p/>When the application quits, {@link #terminate()} should be called.
+ * @param clientSupport Indicates whether the library should enable the monitoring and
+ * interaction with applications running on the devices.
+ * @see AndroidDebugBridge#createBridge(String, boolean)
+ * @see DdmPreferences
+ */
+ public static synchronized void init(boolean clientSupport) {
+ if (sInitialized) {
+ throw new IllegalStateException("AndroidDebugBridge.init() has already been called.");
+ }
+ sInitialized = true;
+ sClientSupport = clientSupport;
+
+ // Determine port and instantiate socket address.
+ initAdbSocketAddr();
+
+ MonitorThread monitorThread = MonitorThread.createInstance();
+ monitorThread.start();
+
+ HandleHello.register(monitorThread);
+ HandleAppName.register(monitorThread);
+ HandleTest.register(monitorThread);
+ HandleThread.register(monitorThread);
+ HandleHeap.register(monitorThread);
+ HandleWait.register(monitorThread);
+ HandleProfiling.register(monitorThread);
+ HandleNativeHeap.register(monitorThread);
+ HandleViewDebug.register(monitorThread);
+ }
+
+ /**
+ * Terminates the ddm library. This must be called upon application termination.
+ */
+ public static synchronized void terminate() {
+ // kill the monitoring services
+ if (sThis != null && sThis.mDeviceMonitor != null) {
+ sThis.mDeviceMonitor.stop();
+ sThis.mDeviceMonitor = null;
+ }
+
+ MonitorThread monitorThread = MonitorThread.getInstance();
+ if (monitorThread != null) {
+ monitorThread.quit();
+ }
+
+ sInitialized = false;
+ }
+
+ /**
+ * Returns whether the ddmlib is setup to support monitoring and interacting with
+ * {@link Client}s running on the {@link IDevice}s.
+ */
+ static boolean getClientSupport() {
+ return sClientSupport;
+ }
+
+ /**
+ * Returns the socket address of the ADB server on the host.
+ */
+ public static InetSocketAddress getSocketAddress() {
+ return sSocketAddr;
+ }
+
+ /**
+ * Creates a {@link AndroidDebugBridge} that is not linked to any particular executable.
+ * <p/>This bridge will expect adb to be running. It will not be able to start/stop/restart
+ * adb.
+ * <p/>If a bridge has already been started, it is directly returned with no changes (similar
+ * to calling {@link #getBridge()}).
+ * @return a connected bridge.
+ */
+ public static AndroidDebugBridge createBridge() {
+ synchronized (sLock) {
+ if (sThis != null) {
+ return sThis;
+ }
+
+ try {
+ sThis = new AndroidDebugBridge();
+ sThis.start();
+ } catch (InvalidParameterException e) {
+ sThis = null;
+ }
+
+ // notify the listeners of the change
+ for (IDebugBridgeChangeListener listener : sBridgeListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our thread
+ try {
+ listener.bridgeChanged(sThis);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+
+ return sThis;
+ }
+ }
+
+
+ /**
+ * Creates a new debug bridge from the location of the command line tool. <p/>
+ * Any existing server will be disconnected, unless the location is the same and
+ * <code>forceNewBridge</code> is set to false.
+ * @param osLocation the location of the command line tool 'adb'
+ * @param forceNewBridge force creation of a new bridge even if one with the same location
+ * already exists.
+ * @return a connected bridge, or null if there were errors while creating or connecting
+ * to the bridge
+ */
+ @Nullable
+ public static AndroidDebugBridge createBridge(@NonNull String osLocation,
+ boolean forceNewBridge) {
+ synchronized (sLock) {
+ if (sThis != null) {
+ if (sThis.mAdbOsLocation != null && sThis.mAdbOsLocation.equals(osLocation) &&
+ !forceNewBridge) {
+ return sThis;
+ } else {
+ // stop the current server
+ sThis.stop();
+ }
+ }
+
+ try {
+ sThis = new AndroidDebugBridge(osLocation);
+ if (!sThis.start()) {
+ return null;
+ }
+ } catch (InvalidParameterException e) {
+ sThis = null;
+ }
+
+ // notify the listeners of the change
+ for (IDebugBridgeChangeListener listener : sBridgeListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our thread
+ try {
+ listener.bridgeChanged(sThis);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+
+ return sThis;
+ }
+ }
+
+ /**
+ * Returns the current debug bridge. Can be <code>null</code> if none were created.
+ */
+ @Nullable
+ public static AndroidDebugBridge getBridge() {
+ return sThis;
+ }
+
+ /**
+ * Disconnects the current debug bridge, and destroy the object.
+ * <p/>This also stops the current adb host server.
+ * <p/>
+ * A new object will have to be created with {@link #createBridge(String, boolean)}.
+ */
+ public static void disconnectBridge() {
+ synchronized (sLock) {
+ if (sThis != null) {
+ sThis.stop();
+ sThis = null;
+
+ // notify the listeners.
+ for (IDebugBridgeChangeListener listener : sBridgeListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our
+ // thread
+ try {
+ listener.bridgeChanged(sThis);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the listener to the collection of listeners who will be notified when a new
+ * {@link AndroidDebugBridge} is connected, by sending it one of the messages defined
+ * in the {@link IDebugBridgeChangeListener} interface.
+ * @param listener The listener which should be notified.
+ */
+ public static void addDebugBridgeChangeListener(@NonNull IDebugBridgeChangeListener listener) {
+ synchronized (sLock) {
+ sBridgeListeners.add(listener);
+
+ if (sThis != null) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our thread
+ try {
+ listener.bridgeChanged(sThis);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes the listener from the collection of listeners who will be notified when a new
+ * {@link AndroidDebugBridge} is started.
+ * @param listener The listener which should no longer be notified.
+ */
+ public static void removeDebugBridgeChangeListener(IDebugBridgeChangeListener listener) {
+ synchronized (sLock) {
+ sBridgeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Adds the listener to the collection of listeners who will be notified when a {@link IDevice}
+ * is connected, disconnected, or when its properties or its {@link Client} list changed, by
+ * sending it one of the messages defined in the {@link IDeviceChangeListener} interface.
+ *
+ * @param listener The listener which should be notified.
+ */
+ public static void addDeviceChangeListener(@NonNull IDeviceChangeListener listener) {
+ sDeviceListeners.add(listener);
+ }
+
+ /**
+ * Removes the listener from the collection of listeners who will be notified when a {@link
+ * IDevice} is connected, disconnected, or when its properties or its {@link Client} list
+ * changed.
+ *
+ * @param listener The listener which should no longer be notified.
+ */
+ public static void removeDeviceChangeListener(IDeviceChangeListener listener) {
+ sDeviceListeners.remove(listener);
+ }
+
+ /**
+ * Adds the listener to the collection of listeners who will be notified when a {@link Client}
+ * property changed, by sending it one of the messages defined in the {@link
+ * IClientChangeListener} interface.
+ *
+ * @param listener The listener which should be notified.
+ */
+ public static void addClientChangeListener(IClientChangeListener listener) {
+ sClientListeners.add(listener);
+ }
+
+ /**
+ * Removes the listener from the collection of listeners who will be notified when a {@link
+ * Client} property changes.
+ *
+ * @param listener The listener which should no longer be notified.
+ */
+ public static void removeClientChangeListener(IClientChangeListener listener) {
+ sClientListeners.remove(listener);
+ }
+
+
+ /**
+ * Returns the devices.
+ * @see #hasInitialDeviceList()
+ */
+ @NonNull
+ public IDevice[] getDevices() {
+ synchronized (sLock) {
+ if (mDeviceMonitor != null) {
+ return mDeviceMonitor.getDevices();
+ }
+ }
+
+ return new IDevice[0];
+ }
+
+ /**
+ * Returns whether the bridge has acquired the initial list from adb after being created.
+ * <p/>Calling {@link #getDevices()} right after {@link #createBridge(String, boolean)} will
+ * generally result in an empty list. This is due to the internal asynchronous communication
+ * mechanism with <code>adb</code> that does not guarantee that the {@link IDevice} list has been
+ * built before the call to {@link #getDevices()}.
+ * <p/>The recommended way to get the list of {@link IDevice} objects is to create a
+ * {@link IDeviceChangeListener} object.
+ */
+ public boolean hasInitialDeviceList() {
+ if (mDeviceMonitor != null) {
+ return mDeviceMonitor.hasInitialDeviceList();
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the client to accept debugger connection on the custom "Selected debug port".
+ * @param selectedClient the client. Can be null.
+ */
+ public void setSelectedClient(Client selectedClient) {
+ MonitorThread monitorThread = MonitorThread.getInstance();
+ if (monitorThread != null) {
+ monitorThread.setSelectedClient(selectedClient);
+ }
+ }
+
+ /**
+ * Returns whether the {@link AndroidDebugBridge} object is still connected to the adb daemon.
+ */
+ public boolean isConnected() {
+ MonitorThread monitorThread = MonitorThread.getInstance();
+ if (mDeviceMonitor != null && monitorThread != null) {
+ return mDeviceMonitor.isMonitoring() && monitorThread.getState() != State.TERMINATED;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of times the {@link AndroidDebugBridge} object attempted to connect
+ * to the adb daemon.
+ */
+ public int getConnectionAttemptCount() {
+ if (mDeviceMonitor != null) {
+ return mDeviceMonitor.getConnectionAttemptCount();
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the number of times the {@link AndroidDebugBridge} object attempted to restart
+ * the adb daemon.
+ */
+ public int getRestartAttemptCount() {
+ if (mDeviceMonitor != null) {
+ return mDeviceMonitor.getRestartAttemptCount();
+ }
+ return -1;
+ }
+
+ /**
+ * Creates a new bridge.
+ * @param osLocation the location of the command line tool
+ * @throws InvalidParameterException
+ */
+ private AndroidDebugBridge(String osLocation) throws InvalidParameterException {
+ if (osLocation == null || osLocation.isEmpty()) {
+ throw new InvalidParameterException();
+ }
+ mAdbOsLocation = osLocation;
+
+ try {
+ checkAdbVersion();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Creates a new bridge not linked to any particular adb executable.
+ */
+ private AndroidDebugBridge() {
+ }
+
+ /**
+ * Queries adb for its version number and checks that it is atleast {@link #MIN_ADB_VERSION}.
+ */
+ private void checkAdbVersion() throws IOException {
+ // default is bad check
+ mVersionCheck = false;
+
+ if (mAdbOsLocation == null) {
+ return;
+ }
+
+ File adb = new File(mAdbOsLocation);
+ ListenableFuture<AdbVersion> future = getAdbVersion(adb);
+ AdbVersion version;
+ try {
+ version = future.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return;
+ } catch (java.util.concurrent.TimeoutException e) {
+ String msg = "Unable to obtain result of 'adb version'";
+ Log.logAndDisplay(LogLevel.ERROR, ADB, msg);
+ return;
+ } catch (ExecutionException e) {
+ Log.logAndDisplay(LogLevel.ERROR, ADB, e.getCause().getMessage());
+ Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
+ return;
+ }
+
+ if (version.compareTo(MIN_ADB_VERSION) > 0) {
+ mVersionCheck = true;
+ } else {
+ String message = String.format(
+ "Required minimum version of adb: %1$s."
+ + "Current version is %2$s", MIN_ADB_VERSION, version);
+ Log.logAndDisplay(LogLevel.ERROR, ADB, message);
+ }
+ }
+
+ public static ListenableFuture<AdbVersion> getAdbVersion(@NonNull final File adb) {
+ final SettableFuture<AdbVersion> future = SettableFuture.create();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ ProcessBuilder pb = new ProcessBuilder(adb.getPath(), "version");
+ pb.redirectErrorStream(true);
+
+ Process p = null;
+ try {
+ p = pb.start();
+ } catch (IOException e) {
+ future.setException(e);
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+ try {
+ String line;
+ while ((line = br.readLine()) != null) {
+ AdbVersion version = AdbVersion.parseFrom(line);
+ if (version != AdbVersion.UNKNOWN) {
+ future.set(version);
+ return;
+ }
+ sb.append(line);
+ sb.append('\n');
+ }
+ } catch (IOException e) {
+ future.setException(e);
+ return;
+ } finally {
+ try {
+ br.close();
+ } catch (IOException e) {
+ future.setException(e);
+ }
+ }
+
+ future.setException(new RuntimeException(
+ "Unable to detect adb version, adb output: " + sb.toString()));
+ }
+ }, "Obtaining adb version").start();
+ return future;
+ }
+
+ /**
+ * Starts the debug bridge.
+ *
+ * @return true if success.
+ */
+ boolean start() {
+ if (mAdbOsLocation != null && sAdbServerPort != 0 && (!mVersionCheck || !startAdb())) {
+ return false;
+ }
+
+ mStarted = true;
+
+ // now that the bridge is connected, we start the underlying services.
+ mDeviceMonitor = new DeviceMonitor(this);
+ mDeviceMonitor.start();
+
+ return true;
+ }
+
+ /**
+ * Kills the debug bridge, and the adb host server.
+ * @return true if success
+ */
+ boolean stop() {
+ // if we haven't started we return false;
+ if (!mStarted) {
+ return false;
+ }
+
+ // kill the monitoring services
+ if (mDeviceMonitor != null) {
+ mDeviceMonitor.stop();
+ mDeviceMonitor = null;
+ }
+
+ if (!stopAdb()) {
+ return false;
+ }
+
+ mStarted = false;
+ return true;
+ }
+
+ /**
+ * Restarts adb, but not the services around it.
+ * @return true if success.
+ */
+ public boolean restart() {
+ if (mAdbOsLocation == null) {
+ Log.e(ADB,
+ "Cannot restart adb when AndroidDebugBridge is created without the location of adb."); //$NON-NLS-1$
+ return false;
+ }
+
+ if (sAdbServerPort == 0) {
+ Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set."); //$NON-NLS-1$
+ return false;
+ }
+
+ if (!mVersionCheck) {
+ Log.logAndDisplay(LogLevel.ERROR, ADB,
+ "Attempting to restart adb, but version check failed!"); //$NON-NLS-1$
+ return false;
+ }
+ synchronized (this) {
+ stopAdb();
+
+ boolean restart = startAdb();
+
+ if (restart && mDeviceMonitor == null) {
+ mDeviceMonitor = new DeviceMonitor(this);
+ mDeviceMonitor.start();
+ }
+
+ return restart;
+ }
+ }
+
+ /**
+ * Notify the listener of a new {@link IDevice}.
+ * <p/>
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ * <p/>
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param device the new <code>IDevice</code>.
+ * @see #getLock()
+ */
+ static void deviceConnected(@NonNull IDevice device) {
+ for (IDeviceChangeListener listener : sDeviceListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our thread
+ try {
+ listener.deviceConnected(device);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+ }
+
+ /**
+ * Notify the listener of a disconnected {@link IDevice}.
+ * <p/>
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ * <p/>
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param device the disconnected <code>IDevice</code>.
+ * @see #getLock()
+ */
+ static void deviceDisconnected(@NonNull IDevice device) {
+ for (IDeviceChangeListener listener : sDeviceListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our
+ // thread
+ try {
+ listener.deviceDisconnected(device);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+ }
+
+ /**
+ * Notify the listener of a modified {@link IDevice}.
+ * <p/>
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ * <p/>
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param device the modified <code>IDevice</code>.
+ * @see #getLock()
+ */
+ static void deviceChanged(@NonNull IDevice device, int changeMask) {
+ // Notify the listeners
+ for (IDeviceChangeListener listener : sDeviceListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our
+ // thread
+ try {
+ listener.deviceChanged(device, changeMask);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+ }
+
+ /**
+ * Notify the listener of a modified {@link Client}.
+ * <p/>
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ * <p/>
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param client the modified <code>Client</code>.
+ * @param changeMask the mask indicating what changed in the <code>Client</code>
+ * @see #getLock()
+ */
+ static void clientChanged(@NonNull Client client, int changeMask) {
+ // Notify the listeners
+ for (IClientChangeListener listener : sClientListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our
+ // thread
+ try {
+ listener.clientChanged(client, changeMask);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link DeviceMonitor} object.
+ */
+ DeviceMonitor getDeviceMonitor() {
+ return mDeviceMonitor;
+ }
+
+ /**
+ * Starts the adb host side server.
+ * @return true if success
+ */
+ synchronized boolean startAdb() {
+ if (mAdbOsLocation == null) {
+ Log.e(ADB,
+ "Cannot start adb when AndroidDebugBridge is created without the location of adb."); //$NON-NLS-1$
+ return false;
+ }
+
+ if (sAdbServerPort == 0) {
+ Log.w(ADB, "ADB server port for starting AndroidDebugBridge is not set."); //$NON-NLS-1$
+ return false;
+ }
+
+ Process proc;
+ int status = -1;
+
+ String[] command = getAdbLaunchCommand("start-server");
+ String commandString = Joiner.on(',').join(command);
+ try {
+ Log.d(DDMS, String.format("Launching '%1$s' to ensure ADB is running.", commandString));
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ if (DdmPreferences.getUseAdbHost()) {
+ String adbHostValue = DdmPreferences.getAdbHostValue();
+ if (adbHostValue != null && !adbHostValue.isEmpty()) {
+ //TODO : check that the String is a valid IP address
+ Map<String, String> env = processBuilder.environment();
+ env.put("ADBHOST", adbHostValue);
+ }
+ }
+ proc = processBuilder.start();
+
+ ArrayList<String> errorOutput = new ArrayList<String>();
+ ArrayList<String> stdOutput = new ArrayList<String>();
+ status = grabProcessOutput(proc, errorOutput, stdOutput, false /* waitForReaders */);
+ } catch (IOException ioe) {
+ Log.e(DDMS, "Unable to run 'adb': " + ioe.getMessage()); //$NON-NLS-1$
+ // we'll return false;
+ } catch (InterruptedException ie) {
+ Log.e(DDMS, "Unable to run 'adb': " + ie.getMessage()); //$NON-NLS-1$
+ // we'll return false;
+ }
+
+ if (status != 0) {
+ Log.e(DDMS,
+ String.format("'%1$s' failed -- run manually if necessary", commandString)); //$NON-NLS-1$
+ return false;
+ } else {
+ Log.d(DDMS, String.format("'%1$s' succeeded", commandString)); //$NON-NLS-1$
+ return true;
+ }
+ }
+
+ private String[] getAdbLaunchCommand(String option) {
+ List<String> command = new ArrayList<String>(4);
+ command.add(mAdbOsLocation);
+ if (sAdbServerPort != DEFAULT_ADB_PORT) {
+ command.add("-P"); //$NON-NLS-1$
+ command.add(Integer.toString(sAdbServerPort));
+ }
+ command.add(option);
+ return command.toArray(new String[command.size()]);
+ }
+
+ /**
+ * Stops the adb host side server.
+ *
+ * @return true if success
+ */
+ private synchronized boolean stopAdb() {
+ if (mAdbOsLocation == null) {
+ Log.e(ADB,
+ "Cannot stop adb when AndroidDebugBridge is created without the location of adb.");
+ return false;
+ }
+
+ if (sAdbServerPort == 0) {
+ Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set");
+ return false;
+ }
+
+ Process proc;
+ int status = -1;
+
+ String[] command = getAdbLaunchCommand("kill-server"); //$NON-NLS-1$
+ try {
+ proc = Runtime.getRuntime().exec(command);
+ status = proc.waitFor();
+ }
+ catch (IOException ioe) {
+ // we'll return false;
+ }
+ catch (InterruptedException ie) {
+ // we'll return false;
+ }
+
+ String commandString = Joiner.on(',').join(command);
+ if (status != 0) {
+ Log.w(DDMS, String.format("'%1$s' failed -- run manually if necessary", commandString));
+ return false;
+ } else {
+ Log.d(DDMS, String.format("'%1$s' succeeded", commandString));
+ return true;
+ }
+ }
+
+ /**
+ * Get the stderr/stdout outputs of a process and return when the process is done.
+ * Both <b>must</b> be read or the process will block on windows.
+ * @param process The process to get the output from
+ * @param errorOutput The array to store the stderr output. cannot be null.
+ * @param stdOutput The array to store the stdout output. cannot be null.
+ * @param waitForReaders if true, this will wait for the reader threads.
+ * @return the process return code.
+ * @throws InterruptedException
+ */
+ private static int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
+ final ArrayList<String> stdOutput, boolean waitForReaders)
+ throws InterruptedException {
+ assert errorOutput != null;
+ assert stdOutput != null;
+ // read the lines as they come. if null is returned, it's
+ // because the process finished
+ Thread t1 = new Thread("adb:stderr reader") { //$NON-NLS-1$
+ @Override
+ public void run() {
+ // create a buffer to read the stderr output
+ InputStreamReader is = new InputStreamReader(process.getErrorStream(),
+ Charsets.UTF_8);
+ BufferedReader errReader = new BufferedReader(is);
+
+ try {
+ while (true) {
+ String line = errReader.readLine();
+ if (line != null) {
+ Log.e(ADB, line);
+ errorOutput.add(line);
+ } else {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ // do nothing.
+ } finally {
+ Closeables.closeQuietly(errReader);
+ }
+ }
+ };
+
+ Thread t2 = new Thread("adb:stdout reader") { //$NON-NLS-1$
+ @Override
+ public void run() {
+ InputStreamReader is = new InputStreamReader(process.getInputStream(),
+ Charsets.UTF_8);
+ BufferedReader outReader = new BufferedReader(is);
+
+ try {
+ while (true) {
+ String line = outReader.readLine();
+ if (line != null) {
+ Log.d(ADB, line);
+ stdOutput.add(line);
+ } else {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ // do nothing.
+ } finally {
+ Closeables.closeQuietly(outReader);
+ }
+ }
+ };
+
+ t1.start();
+ t2.start();
+
+ // it looks like on windows process#waitFor() can return
+ // before the thread have filled the arrays, so we wait for both threads and the
+ // process itself.
+ if (waitForReaders) {
+ try {
+ t1.join();
+ } catch (InterruptedException e) {
+ }
+ try {
+ t2.join();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // get the return code from the process
+ return process.waitFor();
+ }
+
+ /**
+ * Returns the singleton lock used by this class to protect any access to the listener.
+ * <p/>
+ * This includes adding/removing listeners, but also notifying listeners of new bridges,
+ * devices, and clients.
+ */
+ private static Object getLock() {
+ return sLock;
+ }
+
+ /**
+ * Instantiates sSocketAddr with the address of the host's adb process.
+ */
+ private static void initAdbSocketAddr() {
+ try {
+ sAdbServerPort = getAdbServerPort();
+ sHostAddr = InetAddress.getByName(DEFAULT_ADB_HOST);
+ sSocketAddr = new InetSocketAddress(sHostAddr, sAdbServerPort);
+ } catch (UnknownHostException e) {
+ // localhost should always be known.
+ }
+ }
+
+ /**
+ * Returns the port where adb server should be launched. This looks at:
+ * <ol>
+ * <li>The system property ANDROID_ADB_SERVER_PORT</li>
+ * <li>The environment variable ANDROID_ADB_SERVER_PORT</li>
+ * <li>Defaults to {@link #DEFAULT_ADB_PORT} if neither the system property nor the env var
+ * are set.</li>
+ * </ol>
+ *
+ * @return The port number where the host's adb should be expected or started.
+ */
+ private static int getAdbServerPort() {
+ // check system property
+ Integer prop = Integer.getInteger(SERVER_PORT_ENV_VAR);
+ if (prop != null) {
+ try {
+ return validateAdbServerPort(prop.toString());
+ } catch (IllegalArgumentException e) {
+ String msg = String.format(
+ "Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT system property.",
+ prop);
+ Log.w(DDMS, msg);
+ }
+ }
+
+ // when system property is not set or is invalid, parse environment property
+ try {
+ String env = System.getenv(SERVER_PORT_ENV_VAR);
+ if (env != null) {
+ return validateAdbServerPort(env);
+ }
+ } catch (SecurityException ex) {
+ // A security manager has been installed that doesn't allow access to env vars.
+ // So an environment variable might have been set, but we can't tell.
+ // Let's log a warning and continue with ADB's default port.
+ // The issue is that adb would be started (by the forked process having access
+ // to the env vars) on the desired port, but within this process, we can't figure out
+ // what that port is. However, a security manager not granting access to env vars
+ // but allowing to fork is a rare and interesting configuration, so the right
+ // thing seems to be to continue using the default port, as forking is likely to
+ // fail later on in the scenario of the security manager.
+ Log.w(DDMS,
+ "No access to env variables allowed by current security manager. "
+ + "If you've set ANDROID_ADB_SERVER_PORT: it's being ignored.");
+ } catch (IllegalArgumentException e) {
+ String msg = String.format(
+ "Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT environment variable (%2$s).",
+ prop, e.getMessage());
+ Log.w(DDMS, msg);
+ }
+
+ // use default port if neither are set
+ return DEFAULT_ADB_PORT;
+ }
+
+ /**
+ * Returns the integer port value if it is a valid value for adb server port
+ * @param adbServerPort adb server port to validate
+ * @return {@code adbServerPort} as a parsed integer
+ * @throws IllegalArgumentException when {@code adbServerPort} is not bigger than 0 or it is
+ * not a number at all
+ */
+ private static int validateAdbServerPort(@NonNull String adbServerPort)
+ throws IllegalArgumentException {
+ try {
+ // C tools (adb, emulator) accept hex and octal port numbers, so need to accept them too
+ int port = Integer.decode(adbServerPort);
+ if (port <= 0 || port >= 65535) {
+ throw new IllegalArgumentException("Should be > 0 and < 65535");
+ }
+ return port;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Not a valid port number");
+ }
+ }
+
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/BadPacketException.java b/ddmlib/src/main/java/com/android/ddmlib/BadPacketException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/BadPacketException.java
rename to ddmlib/src/main/java/com/android/ddmlib/BadPacketException.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/BatteryFetcher.java b/ddmlib/src/main/java/com/android/ddmlib/BatteryFetcher.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/BatteryFetcher.java
rename to ddmlib/src/main/java/com/android/ddmlib/BatteryFetcher.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/BitmapDecoder.java b/ddmlib/src/main/java/com/android/ddmlib/BitmapDecoder.java
new file mode 100644
index 0000000..bab0d80
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/BitmapDecoder.java
@@ -0,0 +1,168 @@
+/*
+ * 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.ImmutableMap;
+
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+public class BitmapDecoder {
+ public static final String BITMAP_FQCN = "android.graphics.Bitmap";
+
+ public static final String BITMAP_DRAWABLE_FQCN = "android.graphics.drawable.BitmapDrawable";
+
+ public interface BitmapDataProvider {
+ @Nullable
+ String getBitmapConfigName() throws Exception;
+
+ @Nullable
+ Dimension getDimension() throws Exception;
+
+ // Downsizes the bitmap, in-place, to the newSize.
+ boolean downsizeBitmap(@NonNull Dimension newSize) throws Exception;
+
+ @Nullable
+ byte[] getPixelBytes(@NonNull Dimension size) throws Exception;
+ }
+
+ private interface BitmapExtractor {
+ BufferedImage getImage(int w, int h, byte[] data);
+ }
+
+ protected static final Map<String, BitmapExtractor> SUPPORTED_FORMATS = ImmutableMap.of(
+ "\"ARGB_8888\"", new ARGB8888_BitmapExtractor(),
+ "\"RGB_565\"", new RGB565_BitmapExtractor(),
+ "\"ALPHA_8\"", new ALPHA8_BitmapExtractor());
+
+ /**
+ * Maximum height or width of image beyond which we scale it on the device before retrieving.
+ */
+ private static final int MAX_DIMENSION = 1024;
+
+ @Nullable
+ public static BufferedImage getBitmap(@NonNull BitmapDataProvider dataProvider)
+ throws Exception {
+ String config = dataProvider.getBitmapConfigName();
+ if (config == null) {
+ throw new RuntimeException("Unable to determine bitmap configuration");
+ }
+
+ BitmapExtractor bitmapExtractor = SUPPORTED_FORMATS.get(config);
+ if (bitmapExtractor == null) {
+ throw new RuntimeException("Unsupported bitmap configuration: " + config);
+ }
+
+ Dimension size = dataProvider.getDimension();
+ if (size == null) {
+ throw new RuntimeException("Unable to determine image dimensions.");
+ }
+
+ // if the image is rather large, then scale it down
+ if (size.width > MAX_DIMENSION || size.height > MAX_DIMENSION) {
+ boolean couldDownsize = dataProvider.downsizeBitmap(size);
+ if (!couldDownsize) {
+ throw new RuntimeException("Unable to create scaled bitmap");
+ }
+
+ size = dataProvider.getDimension();
+ if (size == null) {
+ throw new RuntimeException("Unable to obtained scaled bitmap's dimensions");
+ }
+ }
+
+ return bitmapExtractor.getImage(size.width, size.height, dataProvider.getPixelBytes(size));
+ }
+
+ private static class ARGB8888_BitmapExtractor implements BitmapExtractor {
+ @Override
+ public BufferedImage getImage(int width, int height, byte[] rgba) {
+ @SuppressWarnings("UndesirableClassUsage")
+ BufferedImage bufferedImage = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ for (int y = 0; y < height; y++) {
+ int stride = y * width;
+ for (int x = 0; x < width; x++) {
+ int i = (stride + x) * 4;
+ long rgb = 0;
+ rgb |= ((long) rgba[i ] & 0xff) << 16; // r
+ rgb |= ((long) rgba[i + 1] & 0xff) << 8; // g
+ rgb |= ((long) rgba[i + 2] & 0xff); // b
+ rgb |= ((long) rgba[i + 3] & 0xff) << 24; // a
+ bufferedImage.setRGB(x, y, (int) (rgb & 0xffffffffl));
+ }
+ }
+
+ return bufferedImage;
+ }
+ }
+
+ private static class RGB565_BitmapExtractor implements BitmapExtractor {
+ @Override
+ public BufferedImage getImage(int width, int height, byte[] rgb) {
+ int bytesPerPixel = 2;
+
+ @SuppressWarnings("UndesirableClassUsage")
+ BufferedImage bufferedImage = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ for (int y = 0; y < height; y++) {
+ int stride = y * width;
+ for (int x = 0; x < width; x++) {
+ int index = (stride + x) * bytesPerPixel;
+ int value = (rgb[index] & 0x00ff) | (rgb[index + 1] << 8) & 0xff00;
+ // RGB565 to RGB888
+ // Multiply by 255/31 to convert from 5 bits (31 max) to 8 bits (255)
+ int r = ((value >>> 11) & 0x1f) * 255 / 31;
+ int g = ((value >>> 5) & 0x3f) * 255 / 63;
+ int b = ((value) & 0x1f) * 255 / 31;
+ int a = 0xFF;
+ int rgba = a << 24 | r << 16 | g << 8 | b;
+ bufferedImage.setRGB(x, y, rgba);
+ }
+ }
+
+ return bufferedImage;
+ }
+ }
+
+ private static class ALPHA8_BitmapExtractor implements BitmapExtractor {
+ @Override
+ public BufferedImage getImage(int width, int height, byte[] rgb) {
+ int bytesPerPixel = 1;
+
+ //noinspection UndesirableClassUsage
+ BufferedImage bufferedImage = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ for (int y = 0; y < height; y++) {
+ int stride = y * width;
+ for (int x = 0; x < width; x++) {
+ int index = stride + x;
+ int value = rgb[index];
+ int rgba = value << 24 | 0xff << 16 | 0xff << 8 | 0xff;
+ bufferedImage.setRGB(x, y, rgba);
+ }
+ }
+
+ return bufferedImage;
+ }
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/ByteBufferUtil.java b/ddmlib/src/main/java/com/android/ddmlib/ByteBufferUtil.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/ByteBufferUtil.java
rename to ddmlib/src/main/java/com/android/ddmlib/ByteBufferUtil.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/CanceledException.java b/ddmlib/src/main/java/com/android/ddmlib/CanceledException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/CanceledException.java
rename to ddmlib/src/main/java/com/android/ddmlib/CanceledException.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java b/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java
new file mode 100644
index 0000000..7d8b883
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/ChunkHandler.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
+import com.android.ddmlib.jdwp.JdwpAgent;
+import com.android.ddmlib.jdwp.JdwpInterceptor;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Subclass this with a class that handles one or more chunk types.
+ */
+abstract class ChunkHandler extends JdwpInterceptor {
+
+ public static final int CHUNK_HEADER_LEN = 8; // 4-byte type, 4-byte len
+ public static final ByteOrder CHUNK_ORDER = ByteOrder.BIG_ENDIAN;
+
+ public static final int CHUNK_FAIL = type("FAIL");
+
+ public static final int DDMS_CMD_SET = 0xc7; // 'G' + 128
+
+ public static final int DDMS_CMD = 0x01;
+
+ ChunkHandler() {}
+
+ /**
+ * Client is ready. The monitor thread calls this method on all
+ * handlers when the client is determined to be DDM-aware (usually
+ * after receiving a HELO response.)
+ *
+ * The handler can use this opportunity to initialize client-side
+ * activity. Because there's a fair chance we'll want to send a
+ * message to the client, this method can throw an IOException.
+ */
+ abstract void clientReady(Client client) throws IOException;
+
+ /**
+ * Client has gone away. Can be used to clean up any resources
+ * associated with this client connection.
+ */
+ abstract void clientDisconnected(Client client);
+
+ /**
+ * Handle an incoming chunk. The data, of chunk type "type", begins
+ * at the start of "data" and continues to data.limit().
+ *
+ * If "isReply" is set, then "msgId" will be the ID of the request
+ * we sent to the client. Otherwise, it's the ID generated by the
+ * client for this event. Note that it's possible to receive chunks
+ * in reply packets for which we are not registered.
+ *
+ * The handler may not modify the contents of "data".
+ */
+ abstract void handleChunk(Client client, int type,
+ ByteBuffer data, boolean isReply, int msgId);
+
+ /**
+ * Handle chunks not recognized by handlers. The handleChunk() method
+ * in sub-classes should call this if the chunk type isn't recognized.
+ */
+ protected void handleUnknownChunk(Client client, int type,
+ ByteBuffer data, boolean isReply, int msgId) {
+ if (type == CHUNK_FAIL) {
+ int errorCode, msgLen;
+ String msg;
+
+ errorCode = data.getInt();
+ msgLen = data.getInt();
+ msg = ByteBufferUtil.getString(data, msgLen);
+ Log.w("ddms", "WARNING: failure code=" + errorCode + " msg=" + msg);
+ } else {
+ Log.w("ddms", "WARNING: received unknown chunk " + name(type)
+ + ": len=" + data.limit() + ", reply=" + isReply
+ + ", msgId=0x" + Integer.toHexString(msgId));
+ }
+ Log.w("ddms", " client " + client + ", handler " + this);
+ }
+
+ /**
+ * Utility function to copy a String out of a ByteBuffer.
+ */
+ public static String getString(ByteBuffer buf, int len) {
+ return ByteBufferUtil.getString(buf, len);
+ }
+
+ /**
+ * Convert a 4-character string to a 32-bit type.
+ */
+ static int type(String typeName) {
+ int val = 0;
+
+ if (typeName.length() != 4) {
+ Log.e("ddms", "Type name must be 4 letter long");
+ throw new RuntimeException("Type name must be 4 letter long");
+ }
+
+ for (int i = 0; i < 4; i++) {
+ val <<= 8;
+ val |= (byte) typeName.charAt(i);
+ }
+
+ return val;
+ }
+
+ /**
+ * Convert an integer type to a 4-character string.
+ */
+ static String name(int type) {
+ char[] ascii = new char[4];
+
+ ascii[0] = (char) ((type >> 24) & 0xff);
+ ascii[1] = (char) ((type >> 16) & 0xff);
+ ascii[2] = (char) ((type >> 8) & 0xff);
+ ascii[3] = (char) (type & 0xff);
+
+ return new String(ascii);
+ }
+
+ /**
+ * Allocate a ByteBuffer with enough space to hold the JDWP packet
+ * header and one chunk header in addition to the demands of the
+ * chunk being created.
+ *
+ * "maxChunkLen" indicates the size of the chunk contents only.
+ */
+ static ByteBuffer allocBuffer(int maxChunkLen) {
+ ByteBuffer buf =
+ ByteBuffer.allocate(JdwpPacket.JDWP_HEADER_LEN + 8 +maxChunkLen);
+ buf.order(CHUNK_ORDER);
+ return buf;
+ }
+
+ /**
+ * Return the slice of the JDWP packet buffer that holds just the
+ * chunk data.
+ */
+ static ByteBuffer getChunkDataBuf(ByteBuffer jdwpBuf) {
+ ByteBuffer slice;
+
+ assert jdwpBuf.position() == 0;
+
+ jdwpBuf.position(JdwpPacket.JDWP_HEADER_LEN + CHUNK_HEADER_LEN);
+ slice = jdwpBuf.slice();
+ slice.order(CHUNK_ORDER);
+ jdwpBuf.position(0);
+
+ return slice;
+ }
+
+ /**
+ * Write the chunk header at the start of the chunk.
+ *
+ * Pass in the byte buffer returned by JdwpPacket.getPayload().
+ */
+ static void finishChunkPacket(JdwpPacket packet, int type, int chunkLen) {
+ ByteBuffer buf = packet.getPayload();
+
+ buf.putInt(0x00, type);
+ buf.putInt(0x04, chunkLen);
+
+ packet.finishPacket(DDMS_CMD_SET, DDMS_CMD, CHUNK_HEADER_LEN + chunkLen);
+ }
+
+ /**
+ * Check that the client is opened with the proper debugger port for the
+ * specified application name, and if not, reopen it.
+ * @param client
+ * @param appName
+ * @return
+ */
+ protected static Client checkDebuggerPortForAppName(Client client, String appName) {
+ IDebugPortProvider provider = DebugPortManager.getProvider();
+ if (provider != null) {
+ Device device = client.getDeviceImpl();
+ int newPort = provider.getPort(device, appName);
+
+ if (newPort != IDebugPortProvider.NO_STATIC_PORT &&
+ newPort != client.getDebuggerListenPort()) {
+
+ AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
+ if (bridge != null) {
+ DeviceMonitor deviceMonitor = bridge.getDeviceMonitor();
+ if (deviceMonitor != null) {
+ deviceMonitor.addClientToDropAndReopen(client, newPort);
+ client = null;
+ }
+ }
+ }
+ }
+
+ return client;
+ }
+
+ void handlePacket(Client client, JdwpPacket packet) {
+ ByteBuffer buf = packet.getPayload();
+ int type = buf.getInt();
+ int length = buf.getInt();
+ Log.d("ddms", "Calling handler for " + name(type)
+ + " [" + this + "] (len=" + length + ")");
+ ByteBuffer ibuf = buf.slice();
+ ByteBuffer roBuf = ibuf.asReadOnlyBuffer(); // enforce R/O
+ roBuf.order(CHUNK_ORDER);
+
+ handleChunk(client, type, roBuf, packet.isReply(), packet.getId());
+ }
+
+ @Override
+ public JdwpPacket intercept(@NonNull JdwpAgent agent, @NonNull JdwpPacket packet) {
+ // TODO: ChunkHandlers are specific to client only packages. Further refactoring
+ // is needed to properly generalize them to JdwpInterceptors
+ if (agent instanceof Client) {
+ Client client = (Client)agent;
+ // TODO: ChunkHandlers are currently all static objects created in static
+ // initializers. For many different reasons they should not be there and should
+ // be moved to another creation mechanism where they are part of the ddm extension
+ // workflow. For now, access the ddmextension directly.
+ MonitorThread.getInstance().getDdmExtension().ddmSeen(client);
+
+ if (packet.isError()) {
+ client.packetFailed(packet);
+ } else if (packet.isEmpty()) {
+ Log.d("ddms", "Got empty reply for 0x" + Integer.toHexString(packet.getId()));
+ } else {
+ handlePacket(client, packet);
+ }
+ return null;
+ }
+ return packet;
+ }
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Client.java b/ddmlib/src/main/java/com/android/ddmlib/Client.java
new file mode 100644
index 0000000..e08c5f5
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/Client.java
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
+import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
+import com.android.ddmlib.jdwp.JdwpAgent;
+import com.android.ddmlib.jdwp.JdwpProtocol;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This represents a single client, usually a Dalvik VM process.
+ * <p/>This class gives access to basic client information, as well as methods to perform actions
+ * on the client.
+ * <p/>More detailed information, usually updated in real time, can be access through the
+ * {@link ClientData} class. Each <code>Client</code> object has its own <code>ClientData</code>
+ * accessed through {@link #getClientData()}.
+ */
+public class Client extends JdwpAgent {
+
+ private static final int SERVER_PROTOCOL_VERSION = 1;
+
+ /** Client change bit mask: application name change */
+ public static final int CHANGE_NAME = 0x0001;
+ /** Client change bit mask: debugger status change */
+ public static final int CHANGE_DEBUGGER_STATUS = 0x0002;
+ /** Client change bit mask: debugger port change */
+ public static final int CHANGE_PORT = 0x0004;
+ /** Client change bit mask: thread update flag change */
+ public static final int CHANGE_THREAD_MODE = 0x0008;
+ /** Client change bit mask: thread data updated */
+ public static final int CHANGE_THREAD_DATA = 0x0010;
+ /** Client change bit mask: heap update flag change */
+ public static final int CHANGE_HEAP_MODE = 0x0020;
+ /** Client change bit mask: head data updated */
+ public static final int CHANGE_HEAP_DATA = 0x0040;
+ /** Client change bit mask: native heap data updated */
+ public static final int CHANGE_NATIVE_HEAP_DATA = 0x0080;
+ /** Client change bit mask: thread stack trace updated */
+ public static final int CHANGE_THREAD_STACKTRACE = 0x0100;
+ /** Client change bit mask: allocation information updated */
+ public static final int CHANGE_HEAP_ALLOCATIONS = 0x0200;
+ /** Client change bit mask: allocation information updated */
+ public static final int CHANGE_HEAP_ALLOCATION_STATUS = 0x0400;
+ /** Client change bit mask: allocation information updated */
+ public static final int CHANGE_METHOD_PROFILING_STATUS = 0x0800;
+ /** Client change bit mask: hprof data updated */
+ public static final int CHANGE_HPROF = 0x1000;
+
+ /** Client change bit mask: combination of {@link Client#CHANGE_NAME},
+ * {@link Client#CHANGE_DEBUGGER_STATUS}, and {@link Client#CHANGE_PORT}.
+ */
+ public static final int CHANGE_INFO = CHANGE_NAME | CHANGE_DEBUGGER_STATUS | CHANGE_PORT;
+
+ private SocketChannel mChan;
+
+ // debugger we're associated with, if any
+ private Debugger mDebugger;
+ private int mDebuggerListenPort;
+
+ // chunk handlers stash state data in here
+ private ClientData mClientData;
+
+ // User interface state. Changing the value causes a message to be
+ // sent to the client.
+ private boolean mThreadUpdateEnabled;
+ private boolean mHeapInfoUpdateEnabled;
+ private boolean mHeapSegmentUpdateEnabled;
+
+ /*
+ * Read/write buffers. We can get large quantities of data from the
+ * client, e.g. the response to a "give me the list of all known classes"
+ * request from the debugger. Requests from the debugger, and from us,
+ * are much smaller.
+ *
+ * Pass-through debugger traffic is sent without copying. "mWriteBuffer"
+ * is only used for data generated within Client.
+ */
+ private static final int INITIAL_BUF_SIZE = 2*1024;
+ private static final int MAX_BUF_SIZE = 800*1024*1024;
+ private ByteBuffer mReadBuffer;
+
+ private Device mDevice;
+
+ private int mConnState;
+
+ private static final int ST_INIT = 1;
+ private static final int ST_NOT_JDWP = 2;
+ private static final int ST_AWAIT_SHAKE = 10;
+ private static final int ST_NEED_DDM_PKT = 11;
+ private static final int ST_NOT_DDM = 12;
+ private static final int ST_READY = 13;
+ private static final int ST_ERROR = 20;
+ private static final int ST_DISCONNECTED = 21;
+
+
+ /**
+ * Create an object for a new client connection.
+ *
+ * @param device the device this client belongs to
+ * @param chan the connected {@link SocketChannel}.
+ * @param pid the client pid.
+ */
+ Client(Device device, SocketChannel chan, int pid) {
+ super(new JdwpProtocol());
+ mDevice = device;
+ mChan = chan;
+
+ mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
+
+ mConnState = ST_INIT;
+
+ mClientData = new ClientData(pid);
+
+ mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate();
+ mHeapInfoUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
+ mHeapSegmentUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
+ }
+
+ /**
+ * Returns a string representation of the {@link Client} object.
+ */
+ @Override
+ public String toString() {
+ return "[Client pid: " + mClientData.getPid() + "]";
+ }
+
+ /**
+ * Returns the {@link IDevice} on which this Client is running.
+ */
+ public IDevice getDevice() {
+ return mDevice;
+ }
+
+ /** Returns the {@link Device} on which this Client is running.
+ */
+ Device getDeviceImpl() {
+ return mDevice;
+ }
+
+ /**
+ * Returns the debugger port for this client.
+ */
+ public int getDebuggerListenPort() {
+ return mDebuggerListenPort;
+ }
+
+ /**
+ * Returns <code>true</code> if the client VM is DDM-aware.
+ *
+ * Calling here is only allowed after the connection has been
+ * established.
+ */
+ public boolean isDdmAware() {
+ switch (mConnState) {
+ case ST_INIT:
+ case ST_NOT_JDWP:
+ case ST_AWAIT_SHAKE:
+ case ST_NEED_DDM_PKT:
+ case ST_NOT_DDM:
+ case ST_ERROR:
+ case ST_DISCONNECTED:
+ return false;
+ case ST_READY:
+ return true;
+ default:
+ assert false;
+ return false;
+ }
+ }
+
+ /**
+ * Returns <code>true</code> if a debugger is currently attached to the client.
+ */
+ public boolean isDebuggerAttached() {
+ return mDebugger.isDebuggerAttached();
+ }
+
+ /**
+ * Return the Debugger object associated with this client.
+ */
+ public Debugger getDebugger() {
+ return mDebugger;
+ }
+
+ /**
+ * Returns the {@link ClientData} object containing this client information.
+ */
+ @NonNull
+ public ClientData getClientData() {
+ return mClientData;
+ }
+
+ /**
+ * Forces the client to execute its garbage collector.
+ */
+ public void executeGarbageCollector() {
+ try {
+ HandleHeap.sendHPGC(this);
+ } catch (IOException ioe) {
+ Log.w("ddms", "Send of HPGC message failed");
+ // ignore
+ }
+ }
+
+ /**
+ * Makes the VM dump an HPROF file
+ */
+ public void dumpHprof() {
+ boolean canStream = mClientData.hasFeature(ClientData.FEATURE_HPROF_STREAMING);
+ try {
+ if (canStream) {
+ HandleHeap.sendHPDS(this);
+ } else {
+ String file = "/sdcard/" + mClientData.getClientDescription().replaceAll(
+ "\\:.*", "") + ".hprof";
+ HandleHeap.sendHPDU(this, file);
+ }
+ } catch (IOException e) {
+ Log.w("ddms", "Send of HPDU message failed");
+ // ignore
+ }
+ }
+
+ /**
+ * Toggles method profiling state.
+ * @deprecated Use {@link #startMethodTracer()}, {@link #stopMethodTracer()},
+ * {@link #startSamplingProfiler(int, java.util.concurrent.TimeUnit)} or
+ * {@link #stopSamplingProfiler()} instead.
+ */
+ @Deprecated
+ public void toggleMethodProfiling() {
+ try {
+ switch (mClientData.getMethodProfilingStatus()) {
+ case TRACER_ON:
+ stopMethodTracer();
+ break;
+ case SAMPLER_ON:
+ stopSamplingProfiler();
+ break;
+ case OFF:
+ startMethodTracer();
+ break;
+ }
+ } catch (IOException e) {
+ Log.w("ddms", "Toggle method profiling failed");
+ // ignore
+ }
+ }
+
+ private int getProfileBufferSize() {
+ return DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024;
+ }
+
+ public void startMethodTracer() throws IOException {
+ boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
+ int bufferSize = getProfileBufferSize();
+ if (canStream) {
+ HandleProfiling.sendMPSS(this, bufferSize, 0 /*flags*/);
+ } else {
+ String file = "/sdcard/" +
+ mClientData.getClientDescription().replaceAll("\\:.*", "") +
+ DdmConstants.DOT_TRACE;
+ HandleProfiling.sendMPRS(this, file, bufferSize, 0 /*flags*/);
+ }
+ }
+
+ public void stopMethodTracer() throws IOException {
+ boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
+
+ if (canStream) {
+ HandleProfiling.sendMPSE(this);
+ } else {
+ HandleProfiling.sendMPRE(this);
+ }
+ }
+
+ public void startSamplingProfiler(int samplingInterval, TimeUnit timeUnit) throws IOException {
+ int bufferSize = getProfileBufferSize();
+ HandleProfiling.sendSPSS(this, bufferSize, samplingInterval, timeUnit);
+ }
+
+ public void stopSamplingProfiler() throws IOException {
+ HandleProfiling.sendSPSE(this);
+ }
+
+ public boolean startOpenGlTracing() {
+ boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING);
+ if (!canTraceOpenGl) {
+ return false;
+ }
+
+ try {
+ HandleViewDebug.sendStartGlTracing(this);
+ return true;
+ } catch (IOException e) {
+ Log.w("ddms", "Start OpenGL Tracing failed");
+ return false;
+ }
+ }
+
+ public boolean stopOpenGlTracing() {
+ boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING);
+ if (!canTraceOpenGl) {
+ return false;
+ }
+
+ try {
+ HandleViewDebug.sendStopGlTracing(this);
+ return true;
+ } catch (IOException e) {
+ Log.w("ddms", "Stop OpenGL Tracing failed");
+ return false;
+ }
+ }
+
+ /**
+ * Sends a request to the VM to send the enable status of the method profiling.
+ * This is asynchronous.
+ * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
+ * The notification that the new status is available will be received through
+ * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
+ * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
+ */
+ public void requestMethodProfilingStatus() {
+ try {
+ HandleHeap.sendREAQ(this);
+ } catch (IOException e) {
+ Log.e("ddmlib", e);
+ }
+ }
+
+
+ /**
+ * Enables or disables the thread update.
+ * <p/>If <code>true</code> the VM will be able to send thread information. Thread information
+ * must be requested with {@link #requestThreadUpdate()}.
+ * @param enabled the enable flag.
+ */
+ public void setThreadUpdateEnabled(boolean enabled) {
+ mThreadUpdateEnabled = enabled;
+ if (!enabled) {
+ mClientData.clearThreads();
+ }
+
+ try {
+ HandleThread.sendTHEN(this, enabled);
+ } catch (IOException ioe) {
+ // ignore it here; client will clean up shortly
+ ioe.printStackTrace();
+ }
+
+ update(CHANGE_THREAD_MODE);
+ }
+
+ /**
+ * Returns whether the thread update is enabled.
+ */
+ public boolean isThreadUpdateEnabled() {
+ return mThreadUpdateEnabled;
+ }
+
+ /**
+ * Sends a thread update request. This is asynchronous.
+ * <p/>The thread info can be accessed by {@link ClientData#getThreads()}. The notification
+ * that the new data is available will be received through
+ * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
+ * containing the mask {@link #CHANGE_THREAD_DATA}.
+ */
+ public void requestThreadUpdate() {
+ HandleThread.requestThreadUpdate(this);
+ }
+
+ /**
+ * Sends a thread stack trace update request. This is asynchronous.
+ * <p/>The thread info can be accessed by {@link ClientData#getThreads()} and
+ * {@link ThreadInfo#getStackTrace()}.
+ * <p/>The notification that the new data is available
+ * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
+ * with a <code>changeMask</code> containing the mask {@link #CHANGE_THREAD_STACKTRACE}.
+ */
+ public void requestThreadStackTrace(int threadId) {
+ HandleThread.requestThreadStackCallRefresh(this, threadId);
+ }
+
+ /**
+ * Enables or disables the heap update.
+ * <p/>If <code>true</code>, any GC will cause the client to send its heap information.
+ * <p/>The heap information can be accessed by {@link ClientData#getVmHeapData()}.
+ * <p/>The notification that the new data is available
+ * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
+ * with a <code>changeMask</code> containing the value {@link #CHANGE_HEAP_DATA}.
+ * @param enabled the enable flag
+ */
+ public void setHeapUpdateEnabled(boolean enabled) {
+ setHeapInfoUpdateEnabled(enabled);
+ setHeapSegmentUpdateEnabled(enabled);
+ }
+
+ public void setHeapInfoUpdateEnabled(boolean enabled) {
+ mHeapInfoUpdateEnabled = enabled;
+
+ try {
+ HandleHeap.sendHPIF(this,
+ enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER);
+
+ } catch (IOException ioe) {
+ // ignore it here; client will clean up shortly
+ }
+
+ update(CHANGE_HEAP_MODE);
+ }
+
+ public void setHeapSegmentUpdateEnabled(boolean enabled) {
+ mHeapSegmentUpdateEnabled = enabled;
+
+ try {
+ HandleHeap.sendHPSG(this,
+ enabled ? HandleHeap.WHEN_GC : HandleHeap.WHEN_DISABLE,
+ HandleHeap.WHAT_MERGE);
+ } catch (IOException ioe) {
+ // ignore it here; client will clean up shortly
+ }
+
+ update(CHANGE_HEAP_MODE);
+ }
+
+ void initializeHeapUpdateStatus() throws IOException {
+ setHeapInfoUpdateEnabled(mHeapInfoUpdateEnabled);
+ }
+
+ /**
+ * Fires a single heap update.
+ */
+ public void updateHeapInfo() {
+ try {
+ HandleHeap.sendHPIF(this, HandleHeap.HPIF_WHEN_NOW);
+ } catch (IOException ioe) {
+ // ignore it here; client will clean up shortly
+ }
+ }
+
+ /**
+ * Returns whether any heap update is enabled.
+ * @see #setHeapUpdateEnabled(boolean)
+ */
+ public boolean isHeapUpdateEnabled() {
+ return mHeapInfoUpdateEnabled || mHeapSegmentUpdateEnabled;
+ }
+
+ /**
+ * Sends a native heap update request. this is asynchronous.
+ * <p/>The native heap info can be accessed by {@link ClientData#getNativeAllocationList()}.
+ * The notification that the new data is available will be received through
+ * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
+ * containing the mask {@link #CHANGE_NATIVE_HEAP_DATA}.
+ */
+ public boolean requestNativeHeapInformation() {
+ try {
+ HandleNativeHeap.sendNHGT(this);
+ return true;
+ } catch (IOException e) {
+ Log.e("ddmlib", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Enables or disables the Allocation tracker for this client.
+ * <p/>If enabled, the VM will start tracking allocation information. A call to
+ * {@link #requestAllocationDetails()} will make the VM sends the information about all the
+ * allocations that happened between the enabling and the request.
+ * @param enable
+ * @see #requestAllocationDetails()
+ */
+ public void enableAllocationTracker(boolean enable) {
+ try {
+ HandleHeap.sendREAE(this, enable);
+ } catch (IOException e) {
+ Log.e("ddmlib", e);
+ }
+ }
+
+ /**
+ * Sends a request to the VM to send the enable status of the allocation tracking.
+ * This is asynchronous.
+ * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
+ * The notification that the new status is available will be received through
+ * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
+ * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
+ */
+ public void requestAllocationStatus() {
+ try {
+ HandleHeap.sendREAQ(this);
+ } catch (IOException e) {
+ Log.e("ddmlib", e);
+ }
+ }
+
+ /**
+ * Sends a request to the VM to send the information about all the allocations that have
+ * happened since the call to {@link #enableAllocationTracker(boolean)} with <var>enable</var>
+ * set to <code>null</code>. This is asynchronous.
+ * <p/>The allocation information can be accessed by {@link ClientData#getAllocations()}.
+ * The notification that the new data is available will be received through
+ * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
+ * containing the mask {@link #CHANGE_HEAP_ALLOCATIONS}.
+ */
+ public void requestAllocationDetails() {
+ try {
+ HandleHeap.sendREAL(this);
+ } catch (IOException e) {
+ Log.e("ddmlib", e);
+ }
+ }
+
+ /**
+ * Sends a kill message to the VM.
+ */
+ public void kill() {
+ try {
+ HandleExit.sendEXIT(this, 1);
+ } catch (IOException ioe) {
+ Log.w("ddms", "Send of EXIT message failed");
+ // ignore
+ }
+ }
+
+ /**
+ * Registers the client with a Selector.
+ */
+ void register(Selector sel) throws IOException {
+ if (mChan != null) {
+ mChan.register(sel, SelectionKey.OP_READ, this);
+ }
+ }
+
+ /**
+ * Sets the client to accept debugger connection on the "selected debugger port".
+ *
+ * @see AndroidDebugBridge#setSelectedClient(Client)
+ * @see DdmPreferences#setSelectedDebugPort(int)
+ */
+ public void setAsSelectedClient() {
+ MonitorThread monitorThread = MonitorThread.getInstance();
+ if (monitorThread != null) {
+ monitorThread.setSelectedClient(this);
+ }
+ }
+
+ /**
+ * Returns whether this client is the current selected client, accepting debugger connection
+ * on the "selected debugger port".
+ *
+ * @see #setAsSelectedClient()
+ * @see AndroidDebugBridge#setSelectedClient(Client)
+ * @see DdmPreferences#setSelectedDebugPort(int)
+ */
+ public boolean isSelectedClient() {
+ MonitorThread monitorThread = MonitorThread.getInstance();
+ if (monitorThread != null) {
+ return monitorThread.getSelectedClient() == this;
+ }
+
+ return false;
+ }
+
+ /**
+ * Tell the client to open a server socket channel and listen for
+ * connections on the specified port.
+ */
+ void listenForDebugger(int listenPort) throws IOException {
+ mDebuggerListenPort = listenPort;
+ mDebugger = new Debugger(this, listenPort);
+ }
+
+ /**
+ * Initiate the JDWP handshake.
+ *
+ * On failure, closes the socket and returns false.
+ */
+ boolean sendHandshake() {
+ ByteBuffer tempBuffer = ByteBuffer.allocate(JdwpHandshake.HANDSHAKE_LEN);
+ try {
+ // assume write buffer can hold 14 bytes
+ JdwpHandshake.putHandshake(tempBuffer);
+ int expectedLen = tempBuffer.position();
+ tempBuffer.flip();
+ if (mChan.write(tempBuffer) != expectedLen)
+ throw new IOException("partial handshake write");
+ }
+ catch (IOException ioe) {
+ Log.e("ddms-client", "IO error during handshake: " + ioe.getMessage());
+ mConnState = ST_ERROR;
+ close(true /* notify */);
+ return false;
+ }
+
+ mConnState = ST_AWAIT_SHAKE;
+
+ return true;
+ }
+
+ /**
+ * Send a DDM packet to the client.
+ *
+ * Ideally, we can do this with a single channel write. If that doesn't
+ * happen, we have to prevent anybody else from writing to the channel
+ * until this packet completes, so we synchronize on the channel.
+ *
+ * Another goal is to avoid unnecessary buffer copies, so we write
+ * directly out of the JdwpPacket's ByteBuffer.
+ */
+ @Override
+ protected void send(@NonNull JdwpPacket packet) throws IOException {
+ // Fix to avoid a race condition on mChan. This should be better synchronized
+ // but just capturing the channel here, avoids a NPE.
+ SocketChannel chan = mChan;
+ if (chan == null) {
+ // can happen for e.g. THST packets
+ Log.v("ddms", "Not sending packet -- client is closed");
+ return;
+ }
+
+ // Synchronizing on this variable is still useful as we do not want to threads
+ // reading at the same time from the same channel, and the only change that
+ // can happen to this channel is to be closed and mChan become null.
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized (chan) {
+ try {
+ packet.write(chan);
+ }
+ catch (IOException ioe) {
+ removeReplyInterceptor(packet.getId());
+ throw ioe;
+ }
+ }
+ }
+
+ /**
+ * Read data from our channel.
+ *
+ * This is called when data is known to be available, and we don't yet
+ * have a full packet in the buffer. If the buffer is at capacity,
+ * expand it.
+ */
+ void read()
+ throws IOException, BufferOverflowException {
+
+ int count;
+
+ if (mReadBuffer.position() == mReadBuffer.capacity()) {
+ if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
+ Log.e("ddms", "Exceeded MAX_BUF_SIZE!");
+ throw new BufferOverflowException();
+ }
+ Log.d("ddms", "Expanding read buffer to "
+ + mReadBuffer.capacity() * 2);
+
+ ByteBuffer newBuffer = ByteBuffer.allocate(mReadBuffer.capacity() * 2);
+
+ // copy entire buffer to new buffer
+ mReadBuffer.position(0);
+ newBuffer.put(mReadBuffer); // leaves "position" at end of copied
+
+ mReadBuffer = newBuffer;
+ }
+
+ count = mChan.read(mReadBuffer);
+ if (count < 0)
+ throw new IOException("read failed");
+
+ if (Log.Config.LOGV) Log.v("ddms", "Read " + count + " bytes from " + this);
+ //Log.hexDump("ddms", Log.DEBUG, mReadBuffer.array(),
+ // mReadBuffer.arrayOffset(), mReadBuffer.position());
+ }
+
+ /**
+ * Return information for the first full JDWP packet in the buffer.
+ *
+ * If we don't yet have a full packet, return null.
+ *
+ * If we haven't yet received the JDWP handshake, we watch for it here
+ * and consume it without admitting to have done so. Upon receipt
+ * we send out the "HELO" message, which is why this can throw an
+ * IOException.
+ */
+ JdwpPacket getJdwpPacket() throws IOException {
+
+ /*
+ * On entry, the data starts at offset 0 and ends at "position".
+ * "limit" is set to the buffer capacity.
+ */
+ if (mConnState == ST_AWAIT_SHAKE) {
+ /*
+ * The first thing we get from the client is a response to our
+ * handshake. It doesn't look like a packet, so we have to
+ * handle it specially.
+ */
+ int result;
+
+ result = JdwpHandshake.findHandshake(mReadBuffer);
+ //Log.v("ddms", "findHand: " + result);
+ switch (result) {
+ case JdwpHandshake.HANDSHAKE_GOOD:
+ Log.d("ddms",
+ "Good handshake from client, sending HELO to " + mClientData.getPid());
+ JdwpHandshake.consumeHandshake(mReadBuffer);
+ mConnState = ST_NEED_DDM_PKT;
+ HandleHello.sendHelloCommands(this, SERVER_PROTOCOL_VERSION);
+ // see if we have another packet in the buffer
+ return getJdwpPacket();
+ case JdwpHandshake.HANDSHAKE_BAD:
+ Log.d("ddms", "Bad handshake from client");
+ if (MonitorThread.getInstance().getRetryOnBadHandshake()) {
+ // we should drop the client, but also attempt to reopen it.
+ // This is done by the DeviceMonitor.
+ mDevice.getMonitor().addClientToDropAndReopen(this,
+ IDebugPortProvider.NO_STATIC_PORT);
+ } else {
+ // mark it as bad, close the socket, and don't retry
+ mConnState = ST_NOT_JDWP;
+ close(true /* notify */);
+ }
+ break;
+ case JdwpHandshake.HANDSHAKE_NOTYET:
+ Log.d("ddms", "No handshake from client yet.");
+ break;
+ default:
+ Log.e("ddms", "Unknown packet while waiting for client handshake");
+ }
+ return null;
+ } else if (mConnState == ST_NEED_DDM_PKT ||
+ mConnState == ST_NOT_DDM ||
+ mConnState == ST_READY) {
+ /*
+ * Normal packet traffic.
+ */
+ if (mReadBuffer.position() != 0) {
+ if (Log.Config.LOGV) Log.v("ddms",
+ "Checking " + mReadBuffer.position() + " bytes");
+ }
+ return JdwpPacket.findPacket(mReadBuffer);
+ } else {
+ /*
+ * Not expecting data when in this state.
+ */
+ Log.e("ddms", "Receiving data in state = " + mConnState);
+ }
+
+ return null;
+ }
+
+ /**
+ * An earlier request resulted in a failure. This is the expected
+ * response to a HELO message when talking to a non-DDM client.
+ */
+ void packetFailed(JdwpPacket reply) {
+ if (mConnState == ST_NEED_DDM_PKT) {
+ Log.d("ddms", "Marking " + this + " as non-DDM client");
+ mConnState = ST_NOT_DDM;
+ } else if (mConnState != ST_NOT_DDM) {
+ Log.w("ddms", "WEIRD: got JDWP failure packet on DDM req");
+ }
+ }
+
+ /**
+ * The MonitorThread calls this when it sees a DDM request or reply.
+ * If we haven't seen a DDM packet before, we advance the state to
+ * ST_READY and return "false". Otherwise, just return true.
+ *
+ * The idea is to let the MonitorThread know when we first see a DDM
+ * packet, so we can send a broadcast to the handlers when a client
+ * connection is made. This method is synchronized so that we only
+ * send the broadcast once.
+ */
+ synchronized boolean ddmSeen() {
+ if (mConnState == ST_NEED_DDM_PKT) {
+ mConnState = ST_READY;
+ return false;
+ } else if (mConnState != ST_READY) {
+ Log.w("ddms", "WEIRD: in ddmSeen with state=" + mConnState);
+ }
+ return true;
+ }
+
+ /**
+ * Close the client socket channel. If there is a debugger associated
+ * with us, close that too.
+ *
+ * Closing a channel automatically unregisters it from the selector.
+ * However, we have to iterate through the selector loop before it
+ * actually lets them go and allows the file descriptors to close.
+ * The caller is expected to manage that.
+ * @param notify Whether or not to notify the listeners of a change.
+ */
+ void close(boolean notify) {
+ Log.d("ddms", "Closing " + this.toString());
+
+ clear();
+ try {
+ if (mChan != null) {
+ mChan.close();
+ mChan = null;
+ }
+
+ if (mDebugger != null) {
+ mDebugger.close();
+ mDebugger = null;
+ }
+ }
+ catch (IOException ioe) {
+ Log.w("ddms", "failed to close " + this);
+ // swallow it -- not much else to do
+ }
+
+ mDevice.removeClient(this, notify);
+ }
+
+ /**
+ * Returns whether this {@link Client} has a valid connection to the application VM.
+ */
+ public boolean isValid() {
+ return mChan != null;
+ }
+
+ void update(int changeMask) {
+ mDevice.update(this, changeMask);
+ }
+
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/ClientData.java b/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
new file mode 100644
index 0000000..61f1f5b
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.HeapSegment.HeapSegmentElement;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+
+/**
+ * Contains the data of a {@link Client}.
+ */
+public class ClientData {
+ /* This is a place to stash data associated with a Client, such as thread
+ * states or heap data. ClientData maps 1:1 to Client, but it's a little
+ * cleaner if we separate the data out.
+ *
+ * Message handlers are welcome to stash arbitrary data here.
+ *
+ * IMPORTANT: The data here is written by HandleFoo methods and read by
+ * FooPanel methods, which run in different threads. All non-trivial
+ * access should be synchronized against the ClientData object.
+ */
+
+
+ /** Temporary name of VM to be ignored. */
+ private static final String PRE_INITIALIZED = "<pre-initialized>"; //$NON-NLS-1$
+
+ public enum DebuggerStatus {
+ /** Debugger connection status: not waiting on one, not connected to one, but accepting
+ * new connections. This is the default value. */
+ DEFAULT,
+ /**
+ * Debugger connection status: the application's VM is paused, waiting for a debugger to
+ * connect to it before resuming. */
+ WAITING,
+ /** Debugger connection status : Debugger is connected */
+ ATTACHED,
+ /** Debugger connection status: The listening port for debugger connection failed to listen.
+ * No debugger will be able to connect. */
+ ERROR
+ }
+
+ public enum AllocationTrackingStatus {
+ /**
+ * Allocation tracking status: unknown.
+ * <p/>This happens right after a {@link Client} is discovered
+ * by the {@link AndroidDebugBridge}, and before the {@link Client} answered the query
+ * regarding its allocation tracking status.
+ * @see Client#requestAllocationStatus()
+ */
+ UNKNOWN,
+ /** Allocation tracking status: the {@link Client} is not tracking allocations. */
+ OFF,
+ /** Allocation tracking status: the {@link Client} is tracking allocations. */
+ ON
+ }
+
+ public enum MethodProfilingStatus {
+ /**
+ * Method profiling status: unknown.
+ * <p/>This happens right after a {@link Client} is discovered
+ * by the {@link AndroidDebugBridge}, and before the {@link Client} answered the query
+ * regarding its method profiling status.
+ * @see Client#requestMethodProfilingStatus()
+ */
+ UNKNOWN,
+ /** Method profiling status: the {@link Client} is not profiling method calls. */
+ OFF,
+ /** Method profiling status: the {@link Client} is tracing method calls. */
+ TRACER_ON,
+ /** Method profiling status: the {@link Client} is being profiled via sampling. */
+ SAMPLER_ON
+ }
+
+ /**
+ * String for feature enabling starting/stopping method profiling
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_PROFILING = "method-trace-profiling"; //$NON-NLS-1$
+
+ /**
+ * String for feature enabling direct streaming of method profiling data
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_PROFILING_STREAMING = "method-trace-profiling-streaming"; //$NON-NLS-1$
+
+ /**
+ * String for feature enabling sampling profiler.
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_SAMPLING_PROFILER = "method-sample-profiling"; //$NON-NLS-1$
+
+ /**
+ * String for feature indicating support for tracing OpenGL calls.
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_OPENGL_TRACING = "opengl-tracing"; //$NON-NLS-1$
+
+ /**
+ * String for feature indicating support for providing view hierarchy.
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_VIEW_HIERARCHY = "view-hierarchy"; //$NON-NLS-1$
+
+ /**
+ * String for feature allowing to dump hprof files
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_HPROF = "hprof-heap-dump"; //$NON-NLS-1$
+
+ /**
+ * String for feature allowing direct streaming of hprof dumps
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_HPROF_STREAMING = "hprof-heap-dump-streaming"; //$NON-NLS-1$
+
+ @Deprecated
+ private static IHprofDumpHandler sHprofDumpHandler;
+ private static IMethodProfilingHandler sMethodProfilingHandler;
+ private static IAllocationTrackingHandler sAllocationTrackingHandler;
+
+ // is this a DDM-aware client?
+ private boolean mIsDdmAware;
+
+ // the client's process ID
+ private final int mPid;
+
+ // Java VM identification string
+ private String mVmIdentifier;
+
+ // client's self-description
+ private String mClientDescription;
+
+ // client's user id (on device in a multi user environment)
+ private int mUserId;
+
+ // client's user id is valid
+ private boolean mValidUserId;
+
+ // client's ABI
+ private String mAbi;
+
+ // jvm flag: currently only indicates whether checkJni is enabled
+ private String mJvmFlags;
+
+ // how interested are we in a debugger?
+ private DebuggerStatus mDebuggerInterest;
+
+ // List of supported features by the client.
+ private final HashSet<String> mFeatures = new HashSet<String>();
+
+ // Thread tracking (THCR, THDE).
+ private TreeMap<Integer,ThreadInfo> mThreadMap;
+
+ /** VM Heap data */
+ private final HeapData mHeapData = new HeapData();
+ /** Native Heap data */
+ private final HeapData mNativeHeapData = new HeapData();
+
+ /** Hprof data */
+ private HprofData mHprofData = null;
+
+ private HashMap<Integer, HeapInfo> mHeapInfoMap = new HashMap<Integer, HeapInfo>();
+
+ /** library map info. Stored here since the backtrace data
+ * is computed on a need to display basis.
+ */
+ private ArrayList<NativeLibraryMapInfo> mNativeLibMapInfo =
+ new ArrayList<NativeLibraryMapInfo>();
+
+ /** Native Alloc info list */
+ private ArrayList<NativeAllocationInfo> mNativeAllocationList =
+ new ArrayList<NativeAllocationInfo>();
+ private int mNativeTotalMemory;
+
+ private byte[] mAllocationsData;
+ private AllocationInfo[] mAllocations;
+ private AllocationTrackingStatus mAllocationStatus = AllocationTrackingStatus.UNKNOWN;
+
+ @Deprecated
+ private String mPendingHprofDump;
+
+ private MethodProfilingStatus mProfilingStatus = MethodProfilingStatus.UNKNOWN;
+ private String mPendingMethodProfiling;
+
+ /**
+ * Heap Information.
+ * <p/>The heap is composed of several {@link HeapSegment} objects.
+ * <p/>A call to {@link #isHeapDataComplete()} will indicate if the segments (available through
+ * {@link #getHeapSegments()}) represent the full heap.
+ */
+ public static class HeapData {
+ private TreeSet<HeapSegment> mHeapSegments = new TreeSet<HeapSegment>();
+ private boolean mHeapDataComplete = false;
+ private byte[] mProcessedHeapData;
+ private Map<Integer, ArrayList<HeapSegmentElement>> mProcessedHeapMap;
+
+ /**
+ * Abandon the current list of heap segments.
+ */
+ public synchronized void clearHeapData() {
+ /* Abandon the old segments instead of just calling .clear().
+ * This lets the user hold onto the old set if it wants to.
+ */
+ mHeapSegments = new TreeSet<HeapSegment>();
+ mHeapDataComplete = false;
+ }
+
+ /**
+ * Add raw HPSG chunk data to the list of heap segments.
+ *
+ * @param data The raw data from an HPSG chunk.
+ */
+ synchronized void addHeapData(ByteBuffer data) {
+ HeapSegment hs;
+
+ if (mHeapDataComplete) {
+ clearHeapData();
+ }
+
+ try {
+ hs = new HeapSegment(data);
+ } catch (BufferUnderflowException e) {
+ System.err.println("Discarding short HPSG data (length " + data.limit() + ")");
+ return;
+ }
+
+ mHeapSegments.add(hs);
+ }
+
+ /**
+ * Called when all heap data has arrived.
+ */
+ synchronized void sealHeapData() {
+ mHeapDataComplete = true;
+ }
+
+ /**
+ * Returns whether the heap data has been sealed.
+ */
+ public boolean isHeapDataComplete() {
+ return mHeapDataComplete;
+ }
+
+ /**
+ * Get the collected heap data, if sealed.
+ *
+ * @return The list of heap segments if the heap data has been sealed, or null if it hasn't.
+ */
+ public Collection<HeapSegment> getHeapSegments() {
+ if (isHeapDataComplete()) {
+ return mHeapSegments;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the processed heap data.
+ *
+ * @param heapData The new heap data (can be null)
+ */
+ public void setProcessedHeapData(byte[] heapData) {
+ mProcessedHeapData = heapData;
+ }
+
+ /**
+ * Get the processed heap data, if present.
+ *
+ * @return the processed heap data, or null.
+ */
+ public byte[] getProcessedHeapData() {
+ return mProcessedHeapData;
+ }
+
+ public void setProcessedHeapMap(Map<Integer, ArrayList<HeapSegmentElement>> heapMap) {
+ mProcessedHeapMap = heapMap;
+ }
+
+ public Map<Integer, ArrayList<HeapSegmentElement>> getProcessedHeapMap() {
+ return mProcessedHeapMap;
+ }
+ }
+
+ public static class HeapInfo {
+ public long maxSizeInBytes;
+ public long sizeInBytes;
+ public long bytesAllocated;
+ public long objectsAllocated;
+ public long timeStamp;
+ public byte reason;
+
+ public HeapInfo(long maxSizeInBytes,
+ long sizeInBytes,
+ long bytesAllocated,
+ long objectsAllocated,
+ long timeStamp,
+ byte reason) {
+ this.maxSizeInBytes = maxSizeInBytes;
+ this.sizeInBytes = sizeInBytes;
+ this.bytesAllocated = bytesAllocated;
+ this.objectsAllocated = objectsAllocated;
+ this.timeStamp = timeStamp;
+ this.reason = reason;
+ }
+ }
+
+ public static class HprofData {
+ public enum Type {
+ FILE,
+ DATA
+ }
+
+ public final Type type;
+ public final String filename;
+ public final byte[] data;
+
+ public HprofData(@NonNull String filename) {
+ type = Type.FILE;
+ this.filename = filename;
+ this.data = null;
+ }
+
+ public HprofData(@NonNull byte[] data) {
+ type = Type.DATA;
+ this.data = data;
+ this.filename = null;
+ }
+ }
+
+ /**
+ * Handlers able to act on HPROF dumps.
+ */
+ @Deprecated
+ public interface IHprofDumpHandler {
+ /**
+ * Called when a HPROF dump succeeded.
+ * @param remoteFilePath the device-side path of the HPROF file.
+ * @param client the client for which the HPROF file was.
+ */
+ void onSuccess(String remoteFilePath, Client client);
+
+ /**
+ * Called when a HPROF dump was successful.
+ * @param data the data containing the HPROF file, streamed from the VM
+ * @param client the client that was profiled.
+ */
+ void onSuccess(byte[] data, Client client);
+
+ /**
+ * Called when a hprof dump failed to end on the VM side
+ * @param client the client that was profiled.
+ * @param message an optional (<code>null<code> ok) error message to be displayed.
+ */
+ void onEndFailure(Client client, String message);
+ }
+
+ /**
+ * Handlers able to act on Method profiling info
+ */
+ public interface IMethodProfilingHandler {
+ /**
+ * Called when a method tracing was successful.
+ * @param remoteFilePath the device-side path of the trace file.
+ * @param client the client that was profiled.
+ */
+ void onSuccess(String remoteFilePath, Client client);
+
+ /**
+ * Called when a method tracing was successful.
+ * @param data the data containing the trace file, streamed from the VM
+ * @param client the client that was profiled.
+ */
+ void onSuccess(byte[] data, Client client);
+
+ /**
+ * Called when method tracing failed to start
+ * @param client the client that was profiled.
+ * @param message an optional (<code>null<code> ok) error message to be displayed.
+ */
+ void onStartFailure(Client client, String message);
+
+ /**
+ * Called when method tracing failed to end on the VM side
+ * @param client the client that was profiled.
+ * @param message an optional (<code>null<code> ok) error message to be displayed.
+ */
+ void onEndFailure(Client client, String message);
+ }
+
+ /*
+ * Handlers able to act on allocation tracking info
+ */
+ public interface IAllocationTrackingHandler {
+ /**
+ * Called when an allocation tracking was successful.
+ * @param data the data containing the encoded allocations.
+ * See {@link AllocationsParser#parse(java.nio.ByteBuffer)} for parsing this data.
+ * @param client the client for which allocations were tracked.
+ */
+ void onSuccess(@NonNull byte[] data, @NonNull Client client);
+ }
+
+ public void setHprofData(byte[] data) {
+ mHprofData = new HprofData(data);
+ }
+
+ public void setHprofData(String filename) {
+ mHprofData = new HprofData(filename);
+ }
+
+ public void clearHprofData() {
+ mHprofData = null;
+ }
+
+ public HprofData getHprofData() {
+ return mHprofData;
+ }
+
+ /**
+ * Sets the handler to receive notifications when an HPROF dump succeeded or failed.
+ * This method is deprecated, please register a client listener and listen for CHANGE_HPROF.
+ */
+ @Deprecated
+ public static void setHprofDumpHandler(IHprofDumpHandler handler) {
+ sHprofDumpHandler = handler;
+ }
+
+ @Deprecated
+ static IHprofDumpHandler getHprofDumpHandler() {
+ return sHprofDumpHandler;
+ }
+
+ /**
+ * Sets the handler to receive notifications when an HPROF dump succeeded or failed.
+ * This method is deprecated, please register a client listener and listen for CHANGE_HPROF.
+ */
+ public static void setMethodProfilingHandler(IMethodProfilingHandler handler) {
+ sMethodProfilingHandler = handler;
+ }
+
+ static IMethodProfilingHandler getMethodProfilingHandler() {
+ return sMethodProfilingHandler;
+ }
+
+ /**
+ * This method is deprecated. Please register an {@link com.android.ddmlib.AndroidDebugBridge.IClientChangeListener} with
+ * {@link AndroidDebugBridge#addClientChangeListener(AndroidDebugBridge.IClientChangeListener)}
+ */
+ @Deprecated
+ public static void setAllocationTrackingHandler(@NonNull IAllocationTrackingHandler handler) {
+ sAllocationTrackingHandler = handler;
+ }
+
+ @Deprecated
+ @Nullable
+ static IAllocationTrackingHandler getAllocationTrackingHandler() {
+ return sAllocationTrackingHandler;
+ }
+
+ /**
+ * Generic constructor.
+ */
+ ClientData(int pid) {
+ mPid = pid;
+
+ mDebuggerInterest = DebuggerStatus.DEFAULT;
+ mThreadMap = new TreeMap<Integer,ThreadInfo>();
+ }
+
+ /**
+ * Returns whether the process is DDM-aware.
+ */
+ public boolean isDdmAware() {
+ return mIsDdmAware;
+ }
+
+ /**
+ * Sets DDM-aware status.
+ */
+ void isDdmAware(boolean aware) {
+ mIsDdmAware = aware;
+ }
+
+ /**
+ * Returns the process ID.
+ */
+ public int getPid() {
+ return mPid;
+ }
+
+ /**
+ * Returns the Client's VM identifier.
+ */
+ public String getVmIdentifier() {
+ return mVmIdentifier;
+ }
+
+ /**
+ * Sets VM identifier.
+ */
+ void setVmIdentifier(String ident) {
+ mVmIdentifier = ident;
+ }
+
+ /**
+ * Returns the client description.
+ * <p/>This is generally the name of the package defined in the
+ * <code>AndroidManifest.xml</code>.
+ *
+ * @return the client description or <code>null</code> if not the description was not yet
+ * sent by the client.
+ */
+ public String getClientDescription() {
+ return mClientDescription;
+ }
+
+ /**
+ * Returns the client's user id.
+ * @return user id if set, -1 otherwise
+ */
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
+ * Returns true if the user id of this client was set. Only devices that support multiple
+ * users will actually return the user id to ddms. For other/older devices, this will not
+ * be set.
+ */
+ public boolean isValidUserId() {
+ return mValidUserId;
+ }
+
+ /** Returns the abi flavor (32-bit or 64-bit) of the application, null if unknown or not set. */
+ @Nullable
+ public String getAbi() {
+ return mAbi;
+ }
+
+ /** Returns the VM flags in use, or null if unknown. */
+ public String getJvmFlags() {
+ return mJvmFlags;
+ }
+
+ /**
+ * Sets client description.
+ *
+ * There may be a race between HELO and APNM. Rather than try
+ * to enforce ordering on the device, we just don't allow an empty
+ * name to replace a specified one.
+ */
+ void setClientDescription(String description) {
+ if (mClientDescription == null && !description.isEmpty()) {
+ /*
+ * The application VM is first named <pre-initialized> before being assigned
+ * its real name.
+ * Depending on the timing, we can get an APNM chunk setting this name before
+ * another one setting the final actual name. So if we get a SetClientDescription
+ * with this value we ignore it.
+ */
+ if (!PRE_INITIALIZED.equals(description)) {
+ mClientDescription = description;
+ }
+ }
+ }
+
+ void setUserId(int id) {
+ mUserId = id;
+ mValidUserId = true;
+ }
+
+ void setAbi(String abi) {
+ mAbi = abi;
+ }
+
+ void setJvmFlags(String jvmFlags) {
+ mJvmFlags = jvmFlags;
+ }
+
+ /**
+ * Returns the debugger connection status.
+ */
+ public DebuggerStatus getDebuggerConnectionStatus() {
+ return mDebuggerInterest;
+ }
+
+ /**
+ * Sets debugger connection status.
+ */
+ void setDebuggerConnectionStatus(DebuggerStatus status) {
+ mDebuggerInterest = status;
+ }
+
+ /**
+ * Sets the current heap info values for the specified heap.
+ * @param heapId The heap whose info to update
+ * @param sizeInBytes The size of the heap, in bytes
+ * @param bytesAllocated The number of bytes currently allocated in the heap
+ * @param objectsAllocated The number of objects currently allocated in
+ * @param timeStamp
+ * @param reason
+ */
+ synchronized void setHeapInfo(int heapId,
+ long maxSizeInBytes,
+ long sizeInBytes,
+ long bytesAllocated,
+ long objectsAllocated,
+ long timeStamp,
+ byte reason) {
+ mHeapInfoMap.put(heapId, new HeapInfo(maxSizeInBytes, sizeInBytes, bytesAllocated,
+ objectsAllocated, timeStamp, reason));
+ }
+
+ /**
+ * Returns the {@link HeapData} object for the VM.
+ */
+ public HeapData getVmHeapData() {
+ return mHeapData;
+ }
+
+ /**
+ * Returns the {@link HeapData} object for the native code.
+ */
+ HeapData getNativeHeapData() {
+ return mNativeHeapData;
+ }
+
+ /**
+ * Returns an iterator over the list of known VM heap ids.
+ * <p/>
+ * The caller must synchronize on the {@link ClientData} object while iterating.
+ *
+ * @return an iterator over the list of heap ids
+ */
+ public synchronized Iterator<Integer> getVmHeapIds() {
+ return mHeapInfoMap.keySet().iterator();
+ }
+
+ /**
+ * Returns the most-recent info values for the specified VM heap.
+ *
+ * @param heapId The heap whose info should be returned
+ * @return a map containing the info values for the specified heap.
+ * Returns <code>null</code> if the heap ID is unknown.
+ */
+ public synchronized HeapInfo getVmHeapInfo(int heapId) {
+ return mHeapInfoMap.get(heapId);
+ }
+
+ /**
+ * Adds a new thread to the list.
+ */
+ synchronized void addThread(int threadId, String threadName) {
+ ThreadInfo attr = new ThreadInfo(threadId, threadName);
+ mThreadMap.put(threadId, attr);
+ }
+
+ /**
+ * Removes a thread from the list.
+ */
+ synchronized void removeThread(int threadId) {
+ mThreadMap.remove(threadId);
+ }
+
+ /**
+ * Returns the list of threads as {@link ThreadInfo} objects.
+ * <p/>The list is empty until a thread update was requested with
+ * {@link Client#requestThreadUpdate()}.
+ */
+ public synchronized ThreadInfo[] getThreads() {
+ Collection<ThreadInfo> threads = mThreadMap.values();
+ return threads.toArray(new ThreadInfo[threads.size()]);
+ }
+
+ /**
+ * Returns the {@link ThreadInfo} by thread id.
+ */
+ synchronized ThreadInfo getThread(int threadId) {
+ return mThreadMap.get(threadId);
+ }
+
+ synchronized void clearThreads() {
+ mThreadMap.clear();
+ }
+
+ /**
+ * Returns the list of {@link NativeAllocationInfo}.
+ * @see Client#requestNativeHeapInformation()
+ */
+ public synchronized List<NativeAllocationInfo> getNativeAllocationList() {
+ return Collections.unmodifiableList(mNativeAllocationList);
+ }
+
+ /**
+ * adds a new {@link NativeAllocationInfo} to the {@link Client}
+ * @param allocInfo The {@link NativeAllocationInfo} to add.
+ */
+ synchronized void addNativeAllocation(NativeAllocationInfo allocInfo) {
+ mNativeAllocationList.add(allocInfo);
+ }
+
+ /**
+ * Clear the current malloc info.
+ */
+ synchronized void clearNativeAllocationInfo() {
+ mNativeAllocationList.clear();
+ }
+
+ /**
+ * Returns the total native memory.
+ * @see Client#requestNativeHeapInformation()
+ */
+ public synchronized int getTotalNativeMemory() {
+ return mNativeTotalMemory;
+ }
+
+ synchronized void setTotalNativeMemory(int totalMemory) {
+ mNativeTotalMemory = totalMemory;
+ }
+
+ synchronized void addNativeLibraryMapInfo(long startAddr, long endAddr, String library) {
+ mNativeLibMapInfo.add(new NativeLibraryMapInfo(startAddr, endAddr, library));
+ }
+
+ /**
+ * Returns the list of native libraries mapped in memory for this client.
+ */
+ public synchronized List<NativeLibraryMapInfo> getMappedNativeLibraries() {
+ return Collections.unmodifiableList(mNativeLibMapInfo);
+ }
+
+ synchronized void setAllocationStatus(AllocationTrackingStatus status) {
+ mAllocationStatus = status;
+ }
+
+ /**
+ * Returns the allocation tracking status.
+ * @see Client#requestAllocationStatus()
+ */
+ public synchronized AllocationTrackingStatus getAllocationStatus() {
+ return mAllocationStatus;
+ }
+
+ synchronized void setAllocationsData(byte[] data) {
+ mAllocationsData = data;
+ }
+
+ /**
+ * Returns the raw data for tracked allocations.
+ * @see Client#requestAllocationDetails()
+ */
+ public synchronized byte[] getAllocationsData() {
+ return mAllocationsData;
+ }
+
+ @Deprecated
+ synchronized void setAllocations(AllocationInfo[] allocs) {
+ mAllocations = allocs;
+ }
+
+ /**
+ * Returns the list of tracked allocations.
+ * @see Client#requestAllocationDetails()
+ */
+ @Nullable
+ public synchronized AllocationInfo[] getAllocations() {
+ if (mAllocationsData != null) {
+ return AllocationsParser.parse(ByteBuffer.wrap(mAllocationsData));
+ }
+ return null;
+ }
+
+ void addFeature(String feature) {
+ mFeatures.add(feature);
+ }
+
+ /**
+ * Returns true if the {@link Client} supports the given <var>feature</var>
+ * @param feature The feature to test.
+ * @return true if the feature is supported
+ *
+ * @see ClientData#FEATURE_PROFILING
+ * @see ClientData#FEATURE_HPROF
+ */
+ public boolean hasFeature(String feature) {
+ return mFeatures.contains(feature);
+ }
+
+ /**
+ * Sets the device-side path to the hprof file being written
+ * @param pendingHprofDump the file to the hprof file
+ */
+ @Deprecated
+ void setPendingHprofDump(String pendingHprofDump) {
+ mPendingHprofDump = pendingHprofDump;
+ }
+
+ /**
+ * Returns the path to the device-side hprof file being written.
+ */
+ @Deprecated
+ String getPendingHprofDump() {
+ return mPendingHprofDump;
+ }
+
+ @Deprecated
+ public boolean hasPendingHprofDump() {
+ return mPendingHprofDump != null;
+ }
+
+ synchronized void setMethodProfilingStatus(MethodProfilingStatus status) {
+ mProfilingStatus = status;
+ }
+
+ /**
+ * Returns the method profiling status.
+ * @see Client#requestMethodProfilingStatus()
+ */
+ public synchronized MethodProfilingStatus getMethodProfilingStatus() {
+ return mProfilingStatus;
+ }
+
+ /**
+ * Sets the device-side path to the method profile file being written
+ * @param pendingMethodProfiling the file being written
+ */
+ void setPendingMethodProfiling(String pendingMethodProfiling) {
+ mPendingMethodProfiling = pendingMethodProfiling;
+ }
+
+ /**
+ * Returns the path to the device-side method profiling file being written.
+ */
+ String getPendingMethodProfiling() {
+ return mPendingMethodProfiling;
+ }
+}
+
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
rename to ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/DdmConstants.java b/ddmlib/src/main/java/com/android/ddmlib/DdmConstants.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/DdmConstants.java
rename to ddmlib/src/main/java/com/android/ddmlib/DdmConstants.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/DdmJdwpExtension.java b/ddmlib/src/main/java/com/android/ddmlib/DdmJdwpExtension.java
new file mode 100644
index 0000000..8f06103
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/DdmJdwpExtension.java
@@ -0,0 +1,134 @@
+/*
+ * 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.jdwp.JdwpAgent;
+import com.android.ddmlib.jdwp.JdwpExtension;
+import com.android.ddmlib.jdwp.JdwpInterceptor;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class DdmJdwpExtension extends JdwpExtension {
+
+ // For broadcasts to message handlers
+ enum Event {
+ // CLIENT_CONNECTED,
+ CLIENT_READY,
+ CLIENT_DISCONNECTED
+ }
+
+ @NonNull
+ private final ConcurrentMap<Integer, ChunkHandler> mHandlerMap;
+
+ public DdmJdwpExtension() {
+ mHandlerMap = new ConcurrentHashMap<Integer, ChunkHandler>();
+ }
+
+ @Override
+ public void intercept(@NonNull Client client) {
+ client.addJdwpInterceptor(new DdmInterceptor(client));
+ }
+
+ public void registerHandler(int type, @NonNull ChunkHandler handler) {
+ mHandlerMap.putIfAbsent(type, handler);
+ }
+
+ void broadcast(Event event, @NonNull Client client) {
+ Log.d("ddms", "broadcast " + event + ": " + client);
+
+ /*
+ * The handler objects appear once in mHandlerMap for each message they
+ * handle. We want to notify them once each, so we convert the HashMap
+ * to a HashSet before we iterate.
+ */
+ HashSet<ChunkHandler> set = new HashSet<ChunkHandler>(mHandlerMap.values());
+ for (ChunkHandler handler : set) {
+ switch (event) {
+ case CLIENT_READY:
+ try {
+ handler.clientReady(client);
+ } catch (IOException ioe) {
+ // Something failed with the client. It should
+ // fall out of the list the next time we try to
+ // do something with it, so we discard the
+ // exception here and assume cleanup will happen
+ // later. May need to propagate farther. The
+ // trouble is that not all values for "event" may
+ // actually throw an exception.
+ Log.w("ddms",
+ "Got exception while broadcasting 'ready'");
+ return;
+ }
+ break;
+ case CLIENT_DISCONNECTED:
+ handler.clientDisconnected(client);
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ void ddmSeen(@NonNull Client client) {
+ // on first DDM packet received, broadcast a "ready" message
+ if (!client.ddmSeen()) {
+ broadcast(Event.CLIENT_READY, client);
+ }
+ }
+
+ /**
+ * Returns "true" if this JDWP packet has a JDWP command type.
+ *
+ * This never returns "true" for reply packets.
+ * @param packet
+ */
+ static boolean isDdmPacket(JdwpPacket packet) {
+ return !packet.isReply() && packet.is(ChunkHandler.DDMS_CMD_SET, ChunkHandler.DDMS_CMD);
+ }
+
+ public class DdmInterceptor extends JdwpInterceptor {
+
+ @NonNull
+ private final Client mClient;
+
+ public DdmInterceptor(@NonNull Client client) {
+ mClient = client;
+ }
+
+ @Override
+ public JdwpPacket intercept(@NonNull JdwpAgent agent, @NonNull JdwpPacket packet) {
+ if (isDdmPacket(packet)) {
+ ddmSeen(mClient);
+ ByteBuffer buf = packet.getPayload();
+ int type = buf.getInt(buf.position());
+ ChunkHandler handler = mHandlerMap.get(type);
+
+ if (handler == null) {
+ Log.w("ddms", "Received unsupported chunk type " + "ChunkHandler.name(type)");
+ } else {
+ handler.handlePacket(mClient, packet);
+ }
+ return null;
+ }
+ return packet;
+ }
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/DdmPreferences.java b/ddmlib/src/main/java/com/android/ddmlib/DdmPreferences.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/DdmPreferences.java
rename to ddmlib/src/main/java/com/android/ddmlib/DdmPreferences.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/DebugPortManager.java b/ddmlib/src/main/java/com/android/ddmlib/DebugPortManager.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/DebugPortManager.java
rename to ddmlib/src/main/java/com/android/ddmlib/DebugPortManager.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Debugger.java b/ddmlib/src/main/java/com/android/ddmlib/Debugger.java
new file mode 100644
index 0000000..9b94e26
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/Debugger.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.ClientData.DebuggerStatus;
+import com.android.ddmlib.jdwp.JdwpAgent;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+/**
+ * This represents a pending or established connection with a JDWP debugger.
+ */
+public class Debugger extends JdwpAgent {
+
+ /*
+ * Messages from the debugger should be pretty small; may not even
+ * need an expanding-buffer implementation for this.
+ */
+ private static final int INITIAL_BUF_SIZE = 1 * 1024;
+ private static final int MAX_BUF_SIZE = 32 * 1024;
+ private ByteBuffer mReadBuffer;
+
+ private static final int PRE_DATA_BUF_SIZE = 256;
+ private ByteBuffer mPreDataBuffer;
+
+ /* connection state */
+ private int mConnState;
+ private static final int ST_NOT_CONNECTED = 1;
+ private static final int ST_AWAIT_SHAKE = 2;
+ private static final int ST_READY = 3;
+
+ /* peer */
+ private Client mClient; // client we're forwarding to/from
+ private int mListenPort; // listen to me
+ private ServerSocketChannel mListenChannel;
+
+ /* this goes up and down; synchronize methods that access the field */
+ private SocketChannel mChannel;
+
+ /**
+ * Create a new Debugger object, configured to listen for connections
+ * on a specific port.
+ */
+ Debugger(Client client, int listenPort) throws IOException {
+ super(client.getJdwpProtocol());
+ mClient = client;
+ mListenPort = listenPort;
+
+ mListenChannel = ServerSocketChannel.open();
+ mListenChannel.configureBlocking(false); // required for Selector
+
+ InetSocketAddress addr = new InetSocketAddress(
+ InetAddress.getByName("localhost"), //$NON-NLS-1$
+ listenPort);
+ mListenChannel.socket().setReuseAddress(true); // enable SO_REUSEADDR
+ mListenChannel.socket().bind(addr);
+
+ mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
+ mPreDataBuffer = ByteBuffer.allocate(PRE_DATA_BUF_SIZE);
+ mConnState = ST_NOT_CONNECTED;
+
+ Log.d("ddms", "Created: " + this.toString());
+ }
+
+ /**
+ * Returns "true" if a debugger is currently attached to us.
+ */
+ boolean isDebuggerAttached() {
+ return mChannel != null;
+ }
+
+ /**
+ * Represent the Debugger as a string.
+ */
+ @Override
+ public String toString() {
+ // mChannel != null means we have connection, ST_READY means it's going
+ return "[Debugger " + mListenPort + "-->" + mClient.getClientData().getPid()
+ + ((mConnState != ST_READY) ? " inactive]" : " active]");
+ }
+
+ /**
+ * Register the debugger's listen socket with the Selector.
+ */
+ void registerListener(Selector sel) throws IOException {
+ mListenChannel.register(sel, SelectionKey.OP_ACCEPT, this);
+ }
+
+ /**
+ * Return the Client being debugged.
+ */
+ Client getClient() {
+ return mClient;
+ }
+
+ /**
+ * Accept a new connection, but only if we don't already have one.
+ *
+ * Must be synchronized with other uses of mChannel and mPreBuffer.
+ *
+ * Returns "null" if we're already talking to somebody.
+ */
+ synchronized SocketChannel accept() throws IOException {
+ return accept(mListenChannel);
+ }
+
+ /**
+ * Accept a new connection from the specified listen channel. This
+ * is so we can listen on a dedicated port for the "current" client,
+ * where "current" is constantly in flux.
+ *
+ * Must be synchronized with other uses of mChannel and mPreBuffer.
+ *
+ * Returns "null" if we're already talking to somebody.
+ */
+ synchronized SocketChannel accept(ServerSocketChannel listenChan)
+ throws IOException {
+
+ if (listenChan != null) {
+ SocketChannel newChan;
+
+ newChan = listenChan.accept();
+ if (mChannel != null) {
+ Log.w("ddms", "debugger already talking to " + mClient
+ + " on " + mListenPort);
+ newChan.close();
+ return null;
+ }
+ mChannel = newChan;
+ mChannel.configureBlocking(false); // required for Selector
+ mConnState = ST_AWAIT_SHAKE;
+ return mChannel;
+ }
+
+ return null;
+ }
+
+ /**
+ * Close the data connection only.
+ */
+ synchronized void closeData() {
+ try {
+ if (mChannel != null) {
+ mChannel.close();
+ mChannel = null;
+ mConnState = ST_NOT_CONNECTED;
+
+ ClientData cd = mClient.getClientData();
+ cd.setDebuggerConnectionStatus(DebuggerStatus.DEFAULT);
+ mClient.update(Client.CHANGE_DEBUGGER_STATUS);
+ }
+ } catch (IOException ioe) {
+ Log.w("ddms", "Failed to close data " + this);
+ }
+ }
+
+ /**
+ * Close the socket that's listening for new connections and (if
+ * we're connected) the debugger data socket.
+ */
+ synchronized void close() {
+ try {
+ if (mListenChannel != null) {
+ mListenChannel.close();
+ }
+ mListenChannel = null;
+ closeData();
+ } catch (IOException ioe) {
+ Log.w("ddms", "Failed to close listener " + this);
+ }
+ }
+
+ // TODO: ?? add a finalizer that verifies the channel was closed
+
+ /**
+ * Read data from our channel.
+ *
+ * This is called when data is known to be available, and we don't yet
+ * have a full packet in the buffer. If the buffer is at capacity,
+ * expand it.
+ */
+ void read() throws IOException {
+ int count;
+
+ if (mReadBuffer.position() == mReadBuffer.capacity()) {
+ if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
+ throw new BufferOverflowException();
+ }
+ Log.d("ddms", "Expanding read buffer to "
+ + mReadBuffer.capacity() * 2);
+
+ ByteBuffer newBuffer =
+ ByteBuffer.allocate(mReadBuffer.capacity() * 2);
+ mReadBuffer.position(0);
+ newBuffer.put(mReadBuffer); // leaves "position" at end
+
+ mReadBuffer = newBuffer;
+ }
+
+ count = mChannel.read(mReadBuffer);
+ Log.v("ddms", "Read " + count + " bytes from " + this);
+ if (count < 0) throw new IOException("read failed");
+ }
+
+ /**
+ * Return information for the first full JDWP packet in the buffer.
+ *
+ * If we don't yet have a full packet, return null.
+ *
+ * If we haven't yet received the JDWP handshake, we watch for it here
+ * and consume it without admitting to have done so. We also send
+ * the handshake response to the debugger, along with any pending
+ * pre-connection data, which is why this can throw an IOException.
+ */
+ JdwpPacket getJdwpPacket() throws IOException {
+ /*
+ * On entry, the data starts at offset 0 and ends at "position".
+ * "limit" is set to the buffer capacity.
+ */
+ if (mConnState == ST_AWAIT_SHAKE) {
+ int result;
+
+ result = JdwpHandshake.findHandshake(mReadBuffer);
+ //Log.v("ddms", "findHand: " + result);
+ switch (result) {
+ case JdwpHandshake.HANDSHAKE_GOOD:
+ Log.d("ddms", "Good handshake from debugger");
+ JdwpHandshake.consumeHandshake(mReadBuffer);
+ sendHandshake();
+ mConnState = ST_READY;
+
+ ClientData cd = mClient.getClientData();
+ cd.setDebuggerConnectionStatus(DebuggerStatus.ATTACHED);
+ mClient.update(Client.CHANGE_DEBUGGER_STATUS);
+
+ // see if we have another packet in the buffer
+ return getJdwpPacket();
+ case JdwpHandshake.HANDSHAKE_BAD:
+ // not a debugger, throw an exception so we drop the line
+ Log.d("ddms", "Bad handshake from debugger");
+ throw new IOException("bad handshake");
+ case JdwpHandshake.HANDSHAKE_NOTYET:
+ break;
+ default:
+ Log.e("ddms", "Unknown packet while waiting for client handshake");
+ }
+ return null;
+ } else if (mConnState == ST_READY) {
+ if (mReadBuffer.position() != 0) {
+ Log.v("ddms", "Checking " + mReadBuffer.position() + " bytes");
+ }
+ return JdwpPacket.findPacket(mReadBuffer);
+ } else {
+ Log.e("ddms", "Receiving data in state = " + mConnState);
+ }
+
+ return null;
+ }
+
+ /**
+ * Send the handshake to the debugger. We also send along any packets
+ * we already received from the client (usually just a VM_START event,
+ * if anything at all).
+ */
+ private synchronized void sendHandshake() throws IOException {
+ ByteBuffer tempBuffer = ByteBuffer.allocate(JdwpHandshake.HANDSHAKE_LEN);
+ JdwpHandshake.putHandshake(tempBuffer);
+ int expectedLength = tempBuffer.position();
+ tempBuffer.flip();
+ if (mChannel.write(tempBuffer) != expectedLength) {
+ throw new IOException("partial handshake write");
+ }
+
+ expectedLength = mPreDataBuffer.position();
+ if (expectedLength > 0) {
+ Log.d("ddms", "Sending " + mPreDataBuffer.position()
+ + " bytes of saved data");
+ mPreDataBuffer.flip();
+ if (mChannel.write(mPreDataBuffer) != expectedLength) {
+ throw new IOException("partial pre-data write");
+ }
+ mPreDataBuffer.clear();
+ }
+ }
+
+ /**
+ * Send a packet to the debugger.
+ *
+ * Ideally, we can do this with a single channel write. If that doesn't
+ * happen, we have to prevent anybody else from writing to the channel
+ * until this packet completes, so we synchronize on the channel.
+ *
+ * Another goal is to avoid unnecessary buffer copies, so we write
+ * directly out of the JdwpPacket's ByteBuffer.
+ *
+ * We must synchronize on "mChannel" before writing to it. We want to
+ * coordinate the buffered data with mChannel creation, so this whole
+ * method is synchronized.
+ */
+ @Override
+ protected void send(@NonNull JdwpPacket packet) throws IOException {
+ synchronized (this) {
+ if (mChannel == null) {
+ /*
+ * Buffer this up so we can send it to the debugger when it
+ * finally does connect. This is essential because the VM_START
+ * message might be telling the debugger that the VM is
+ * suspended. The alternative approach would be for us to
+ * capture and interpret VM_START and send it later if we
+ * didn't choose to un-suspend the VM for our own purposes.
+ */
+ Log.d("ddms", "Saving packet 0x"
+ + Integer.toHexString(packet.getId()));
+ packet.move(mPreDataBuffer);
+ } else {
+ packet.write(mChannel);
+ }
+ }
+ }
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Device.java b/ddmlib/src/main/java/com/android/ddmlib/Device.java
new file mode 100644
index 0000000..d064451
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/Device.java
@@ -0,0 +1,1134 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.ddmlib.log.LogReceiver;
+import com.android.sdklib.AndroidVersion;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Atomics;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * A Device. It can be a physical device or an emulator.
+ */
+final class Device implements IDevice {
+ /** Emulator Serial Number regexp. */
+ static final String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
+
+ /** Serial number of the device */
+ private final String mSerialNumber;
+
+ /** Name of the AVD */
+ private String mAvdName = null;
+
+ /** State of the device. */
+ private DeviceState mState = null;
+
+ /** True if ADB is running as root */
+ private boolean mIsRoot = false;
+
+ /** Device properties. */
+ private final PropertyFetcher mPropFetcher = new PropertyFetcher(this);
+ private final Map<String, String> mMountPoints = new HashMap<String, String>();
+
+ private final BatteryFetcher mBatteryFetcher = new BatteryFetcher(this);
+
+ @GuardedBy("mClients")
+ private final List<Client> mClients = new ArrayList<Client>();
+
+ /** Maps pid's of clients in {@link #mClients} to their package name. */
+ private final Map<Integer, String> mClientInfo = new ConcurrentHashMap<Integer, String>();
+
+ private DeviceMonitor mMonitor;
+
+ private static final String LOG_TAG = "Device";
+ private static final char SEPARATOR = '-';
+ private static final String UNKNOWN_PACKAGE = ""; //$NON-NLS-1$
+
+ private static final long GET_PROP_TIMEOUT_MS = 250;
+ private static final long INITIAL_GET_PROP_TIMEOUT_MS = 2000;
+ private static final int QUERY_IS_ROOT_TIMEOUT_MS = 1000;
+
+ private static final long INSTALL_TIMEOUT_MINUTES;
+
+ static {
+ String installTimeout = System.getenv("ADB_INSTALL_TIMEOUT");
+ long time = 4;
+ if (installTimeout != null) {
+ try {
+ time = Long.parseLong(installTimeout);
+ } catch (NumberFormatException e) {
+ // use default value
+ }
+ }
+ INSTALL_TIMEOUT_MINUTES = time;
+ }
+
+ /**
+ * Socket for the connection monitoring client connection/disconnection.
+ */
+ private SocketChannel mSocketChannel;
+
+ private Integer mLastBatteryLevel = null;
+ private long mLastBatteryCheckTime = 0;
+
+ /** Path to the screen recorder binary on the device. */
+ private static final String SCREEN_RECORDER_DEVICE_PATH = "/system/bin/screenrecord";
+ private static final long LS_TIMEOUT_SEC = 2;
+
+ /** Flag indicating whether the device has the screen recorder binary. */
+ private Boolean mHasScreenRecorder;
+
+ /** Cached list of hardware characteristics */
+ private Set<String> mHardwareCharacteristics;
+
+ private int mApiLevel;
+ @Nullable private AndroidVersion mVersion;
+ private String mName;
+
+ /**
+ * Output receiver for "pm install package.apk" command line.
+ */
+ static final class InstallReceiver extends MultiLineReceiver {
+
+ private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
+ private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
+
+ private String mErrorMessage = null;
+
+ public InstallReceiver() {
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ if (!line.isEmpty()) {
+ if (line.startsWith(SUCCESS_OUTPUT)) {
+ mErrorMessage = null;
+ } else {
+ Matcher m = FAILURE_PATTERN.matcher(line);
+ if (m.matches()) {
+ mErrorMessage = m.group(1);
+ } else {
+ mErrorMessage = "Unknown failure (" + line + ")";
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#getSerialNumber()
+ */
+ @NonNull
+ @Override
+ public String getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ @Override
+ public String getAvdName() {
+ return mAvdName;
+ }
+
+ /**
+ * Sets the name of the AVD
+ */
+ void setAvdName(String avdName) {
+ if (!isEmulator()) {
+ throw new IllegalArgumentException(
+ "Cannot set the AVD name of the device is not an emulator");
+ }
+
+ mAvdName = avdName;
+ }
+
+ @Override
+ public String getName() {
+ if (mName != null) {
+ return mName;
+ }
+
+ if (isOnline()) {
+ // cache name only if device is online
+ mName = constructName();
+ return mName;
+ } else {
+ return constructName();
+ }
+ }
+
+ private String constructName() {
+ if (isEmulator()) {
+ String avdName = getAvdName();
+ if (avdName != null) {
+ return String.format("%s [%s]", avdName, getSerialNumber());
+ } else {
+ return getSerialNumber();
+ }
+ } else {
+ String manufacturer = null;
+ String model = null;
+
+ try {
+ manufacturer = cleanupStringForDisplay(getProperty(PROP_DEVICE_MANUFACTURER));
+ model = cleanupStringForDisplay(getProperty(PROP_DEVICE_MODEL));
+ } catch (Exception e) {
+ // If there are exceptions thrown while attempting to get these properties,
+ // we can just use the serial number, so ignore these exceptions.
+ }
+
+ StringBuilder sb = new StringBuilder(20);
+
+ if (manufacturer != null) {
+ sb.append(manufacturer);
+ sb.append(SEPARATOR);
+ }
+
+ if (model != null) {
+ sb.append(model);
+ sb.append(SEPARATOR);
+ }
+
+ sb.append(getSerialNumber());
+ return sb.toString();
+ }
+ }
+
+ private String cleanupStringForDisplay(String s) {
+ if (s == null) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+
+ if (Character.isLetterOrDigit(c)) {
+ sb.append(Character.toLowerCase(c));
+ } else {
+ sb.append('_');
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#getState()
+ */
+ @Override
+ public DeviceState getState() {
+ return mState;
+ }
+
+ /**
+ * Changes the state of the device.
+ */
+ void setState(DeviceState state) {
+ mState = state;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return Collections.unmodifiableMap(mPropFetcher.getProperties());
+ }
+
+ @Override
+ public int getPropertyCount() {
+ return mPropFetcher.getProperties().size();
+ }
+
+ @Override
+ public String getProperty(String name) {
+ Map<String, String> properties = mPropFetcher.getProperties();
+ long timeout = properties.isEmpty() ? INITIAL_GET_PROP_TIMEOUT_MS : GET_PROP_TIMEOUT_MS;
+
+ Future<String> future = mPropFetcher.getProperty(name);
+ try {
+ return future.get(timeout, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // ignore
+ } catch (ExecutionException e) {
+ // ignore
+ } catch (java.util.concurrent.TimeoutException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ @Override
+ public boolean arePropertiesSet() {
+ return mPropFetcher.arePropertiesSet();
+ }
+
+ @Override
+ public String getPropertyCacheOrSync(String name) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ Future<String> future = mPropFetcher.getProperty(name);
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ // ignore
+ } catch (ExecutionException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ @Override
+ public String getPropertySync(String name) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ Future<String> future = mPropFetcher.getProperty(name);
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ // ignore
+ } catch (ExecutionException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Future<String> getSystemProperty(@NonNull String name) {
+ return mPropFetcher.getProperty(name);
+ }
+
+ @Override
+ public boolean supportsFeature(@NonNull Feature feature) {
+ switch (feature) {
+ case SCREEN_RECORD:
+ if (!getVersion().isGreaterOrEqualThan(19)) {
+ return false;
+ }
+ if (mHasScreenRecorder == null) {
+ mHasScreenRecorder = hasBinary(SCREEN_RECORDER_DEVICE_PATH);
+ }
+ return mHasScreenRecorder;
+ case PROCSTATS:
+ return getVersion().isGreaterOrEqualThan(19);
+ default:
+ return false;
+ }
+ }
+
+ // The full list of features can be obtained from /etc/permissions/features*
+ // However, the smaller set of features we are interested in can be obtained by
+ // reading the build characteristics property.
+ @Override
+ public boolean supportsFeature(@NonNull HardwareFeature feature) {
+ if (mHardwareCharacteristics == null) {
+ try {
+ String characteristics = getProperty(PROP_BUILD_CHARACTERISTICS);
+ if (characteristics == null) {
+ return false;
+ }
+
+ mHardwareCharacteristics = Sets.newHashSet(Splitter.on(',').split(characteristics));
+ } catch (Exception e) {
+ mHardwareCharacteristics = Collections.emptySet();
+ }
+ }
+
+ return mHardwareCharacteristics.contains(feature.getCharacteristic());
+ }
+
+ @NonNull
+ @Override
+ public AndroidVersion getVersion() {
+ if (mVersion != null) {
+ return mVersion;
+ }
+
+ try {
+ String buildApi = getProperty(PROP_BUILD_API_LEVEL);
+ if (buildApi == null) {
+ return AndroidVersion.DEFAULT;
+ }
+
+ int api = Integer.parseInt(buildApi);
+ String codeName = getProperty(PROP_BUILD_CODENAME);
+ mVersion = new AndroidVersion(api, codeName);
+ return mVersion;
+ } catch (Exception e) {
+ return AndroidVersion.DEFAULT;
+ }
+ }
+
+ private boolean hasBinary(String path) {
+ CountDownLatch latch = new CountDownLatch(1);
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver(latch);
+ try {
+ executeShellCommand("ls " + path, receiver, LS_TIMEOUT_SEC, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ return false;
+ }
+
+ try {
+ latch.await(LS_TIMEOUT_SEC, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+
+ String value = receiver.getOutput().trim();
+ return !value.endsWith("No such file or directory");
+ }
+
+ @Nullable
+ @Override
+ public String getMountPoint(@NonNull String name) {
+ String mount = mMountPoints.get(name);
+ if (mount == null) {
+ try {
+ mount = queryMountPoint(name);
+ mMountPoints.put(name, mount);
+ } catch (TimeoutException ignored) {
+ } catch (AdbCommandRejectedException ignored) {
+ } catch (ShellCommandUnresponsiveException ignored) {
+ } catch (IOException ignored) {
+ }
+ }
+ return mount;
+ }
+
+ @Nullable
+ private String queryMountPoint(@NonNull final String name)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+
+ final AtomicReference<String> ref = Atomics.newReference();
+ executeShellCommand("echo $" + name, new MultiLineReceiver() { //$NON-NLS-1$
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ if (!line.isEmpty()) {
+ // this should be the only one.
+ ref.set(line);
+ }
+ }
+ }
+ });
+ return ref.get();
+ }
+
+ @Override
+ public String toString() {
+ return mSerialNumber;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#isOnline()
+ */
+ @Override
+ public boolean isOnline() {
+ return mState == DeviceState.ONLINE;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#isEmulator()
+ */
+ @Override
+ public boolean isEmulator() {
+ return mSerialNumber.matches(RE_EMULATOR_SN);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#isOffline()
+ */
+ @Override
+ public boolean isOffline() {
+ return mState == DeviceState.OFFLINE;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#isBootLoader()
+ */
+ @Override
+ public boolean isBootLoader() {
+ return mState == DeviceState.BOOTLOADER;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#getSyncService()
+ */
+ @Override
+ public SyncService getSyncService()
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ SyncService syncService = new SyncService(AndroidDebugBridge.getSocketAddress(), this);
+ if (syncService.openSync()) {
+ return syncService;
+ }
+
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#getFileListingService()
+ */
+ @Override
+ public FileListingService getFileListingService() {
+ return new FileListingService(this);
+ }
+
+ @Override
+ public RawImage getScreenshot()
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ return getScreenshot(0, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public RawImage getScreenshot(long timeout, TimeUnit unit)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ return AdbHelper.getFrameBuffer(AndroidDebugBridge.getSocketAddress(), this, timeout, unit);
+ }
+
+ @Override
+ public void startScreenRecorder(String remoteFilePath, ScreenRecorderOptions options,
+ IShellOutputReceiver receiver) throws TimeoutException, AdbCommandRejectedException,
+ IOException, ShellCommandUnresponsiveException {
+ executeShellCommand(getScreenRecorderCommand(remoteFilePath, options), receiver, 0, null);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static String getScreenRecorderCommand(@NonNull String remoteFilePath,
+ @NonNull ScreenRecorderOptions options) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("screenrecord");
+ sb.append(' ');
+
+ if (options.width > 0 && options.height > 0) {
+ sb.append("--size ");
+ sb.append(options.width);
+ sb.append('x');
+ sb.append(options.height);
+ sb.append(' ');
+ }
+
+ if (options.bitrateMbps > 0) {
+ sb.append("--bit-rate ");
+ sb.append(options.bitrateMbps * 1000000);
+ sb.append(' ');
+ }
+
+ if (options.timeLimit > 0) {
+ sb.append("--time-limit ");
+ long seconds = TimeUnit.SECONDS.convert(options.timeLimit, options.timeLimitUnits);
+ if (seconds > 180) {
+ seconds = 180;
+ }
+ sb.append(seconds);
+ sb.append(' ');
+ }
+
+ sb.append(remoteFilePath);
+
+ return sb.toString();
+ }
+
+ @Override
+ public void executeShellCommand(String command, IShellOutputReceiver receiver)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+ AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
+ receiver, DdmPreferences.getTimeOut());
+ }
+
+ @Override
+ public void executeShellCommand(String command, IShellOutputReceiver receiver,
+ int maxTimeToOutputResponse)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+ AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
+ receiver, maxTimeToOutputResponse);
+ }
+
+ @Override
+ public void executeShellCommand(String command, IShellOutputReceiver receiver,
+ long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+ AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
+ receiver, maxTimeToOutputResponse, maxTimeUnits);
+ }
+
+ @Override
+ public void runEventLogService(LogReceiver receiver)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ AdbHelper.runEventLogService(AndroidDebugBridge.getSocketAddress(), this, receiver);
+ }
+
+ @Override
+ public void runLogService(String logname, LogReceiver receiver)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ AdbHelper.runLogService(AndroidDebugBridge.getSocketAddress(), this, logname, receiver);
+ }
+
+ @Override
+ public void createForward(int localPort, int remotePort)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this,
+ String.format("tcp:%d", localPort), //$NON-NLS-1$
+ String.format("tcp:%d", remotePort)); //$NON-NLS-1$
+ }
+
+ @Override
+ public void createForward(int localPort, String remoteSocketName,
+ DeviceUnixSocketNamespace namespace) throws TimeoutException,
+ AdbCommandRejectedException, IOException {
+ AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this,
+ String.format("tcp:%d", localPort), //$NON-NLS-1$
+ String.format("%s:%s", namespace.getType(), remoteSocketName)); //$NON-NLS-1$
+ }
+
+ @Override
+ public void removeForward(int localPort, int remotePort)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ AdbHelper.removeForward(AndroidDebugBridge.getSocketAddress(), this,
+ String.format("tcp:%d", localPort), //$NON-NLS-1$
+ String.format("tcp:%d", remotePort)); //$NON-NLS-1$
+ }
+
+ @Override
+ public void removeForward(int localPort, String remoteSocketName,
+ DeviceUnixSocketNamespace namespace) throws TimeoutException,
+ AdbCommandRejectedException, IOException {
+ AdbHelper.removeForward(AndroidDebugBridge.getSocketAddress(), this,
+ String.format("tcp:%d", localPort), //$NON-NLS-1$
+ String.format("%s:%s", namespace.getType(), remoteSocketName)); //$NON-NLS-1$
+ }
+
+ Device(DeviceMonitor monitor, String serialNumber, DeviceState deviceState) {
+ mMonitor = monitor;
+ mSerialNumber = serialNumber;
+ mState = deviceState;
+ }
+
+ DeviceMonitor getMonitor() {
+ return mMonitor;
+ }
+
+ @Override
+ public boolean hasClients() {
+ synchronized (mClients) {
+ return !mClients.isEmpty();
+ }
+ }
+
+ @Override
+ public Client[] getClients() {
+ synchronized (mClients) {
+ return mClients.toArray(new Client[mClients.size()]);
+ }
+ }
+
+ @Override
+ public Client getClient(String applicationName) {
+ synchronized (mClients) {
+ for (Client c : mClients) {
+ if (applicationName.equals(c.getClientData().getClientDescription())) {
+ return c;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ void addClient(Client client) {
+ synchronized (mClients) {
+ mClients.add(client);
+ }
+
+ addClientInfo(client);
+ }
+
+ List<Client> getClientList() {
+ return mClients;
+ }
+
+ void clearClientList() {
+ synchronized (mClients) {
+ mClients.clear();
+ }
+
+ clearClientInfo();
+ }
+
+ /**
+ * Removes a {@link Client} from the list.
+ * @param client the client to remove.
+ * @param notify Whether or not to notify the listeners of a change.
+ */
+ void removeClient(Client client, boolean notify) {
+ mMonitor.addPortToAvailableList(client.getDebuggerListenPort());
+ synchronized (mClients) {
+ mClients.remove(client);
+ }
+ if (notify) {
+ AndroidDebugBridge.deviceChanged(this, CHANGE_CLIENT_LIST);
+ }
+
+ removeClientInfo(client);
+ }
+
+ /** Sets the socket channel on which a track-jdwp command for this device has been sent. */
+ void setClientMonitoringSocket(@NonNull SocketChannel socketChannel) {
+ mSocketChannel = socketChannel;
+ }
+
+ /**
+ * Returns the channel on which responses to the track-jdwp command will be available if it
+ * has been set, null otherwise. The channel is set via {@link #setClientMonitoringSocket(SocketChannel)},
+ * which is usually invoked when the device goes online.
+ */
+ @Nullable
+ SocketChannel getClientMonitoringSocket() {
+ return mSocketChannel;
+ }
+
+ void update(int changeMask) {
+ AndroidDebugBridge.deviceChanged(this, changeMask);
+ }
+
+ void update(@NonNull Client client, int changeMask) {
+ AndroidDebugBridge.clientChanged(client, changeMask);
+ updateClientInfo(client, changeMask);
+ }
+
+ void setMountingPoint(String name, String value) {
+ mMountPoints.put(name, value);
+ }
+
+ private void addClientInfo(Client client) {
+ ClientData cd = client.getClientData();
+ setClientInfo(cd.getPid(), cd.getClientDescription());
+ }
+
+ private void updateClientInfo(Client client, int changeMask) {
+ if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) {
+ addClientInfo(client);
+ }
+ }
+
+ private void removeClientInfo(Client client) {
+ int pid = client.getClientData().getPid();
+ mClientInfo.remove(pid);
+ }
+
+ private void clearClientInfo() {
+ mClientInfo.clear();
+ }
+
+ private void setClientInfo(int pid, String pkgName) {
+ if (pkgName == null) {
+ pkgName = UNKNOWN_PACKAGE;
+ }
+
+ mClientInfo.put(pid, pkgName);
+ }
+
+ @Override
+ public String getClientName(int pid) {
+ String pkgName = mClientInfo.get(pid);
+ return pkgName == null ? UNKNOWN_PACKAGE : pkgName;
+ }
+
+ @Override
+ public void pushFile(String local, String remote)
+ throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
+ SyncService sync = null;
+ try {
+ String targetFileName = getFileName(local);
+
+ Log.d(targetFileName, String.format("Uploading %1$s onto device '%2$s'",
+ targetFileName, getSerialNumber()));
+
+ sync = getSyncService();
+ if (sync != null) {
+ String message = String.format("Uploading file onto device '%1$s'",
+ getSerialNumber());
+ Log.d(LOG_TAG, message);
+ sync.pushFile(local, remote, SyncService.getNullProgressMonitor());
+ } else {
+ throw new IOException("Unable to open sync connection!");
+ }
+ } catch (TimeoutException e) {
+ Log.e(LOG_TAG, "Error during Sync: timeout.");
+ throw e;
+
+ } catch (SyncException e) {
+ Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
+ throw e;
+
+ } catch (IOException e) {
+ Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
+ throw e;
+
+ } finally {
+ if (sync != null) {
+ sync.close();
+ }
+ }
+ }
+
+ @Override
+ public void pullFile(String remote, String local)
+ throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
+ SyncService sync = null;
+ try {
+ String targetFileName = getFileName(remote);
+
+ Log.d(targetFileName, String.format("Downloading %1$s from device '%2$s'",
+ targetFileName, getSerialNumber()));
+
+ sync = getSyncService();
+ if (sync != null) {
+ String message = String.format("Downloading file from device '%1$s'",
+ getSerialNumber());
+ Log.d(LOG_TAG, message);
+ sync.pullFile(remote, local, SyncService.getNullProgressMonitor());
+ } else {
+ throw new IOException("Unable to open sync connection!");
+ }
+ } catch (TimeoutException e) {
+ Log.e(LOG_TAG, "Error during Sync: timeout.");
+ throw e;
+
+ } catch (SyncException e) {
+ Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
+ throw e;
+
+ } catch (IOException e) {
+ Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
+ throw e;
+
+ } finally {
+ if (sync != null) {
+ sync.close();
+ }
+ }
+ }
+
+ @Override
+ public void installPackage(String packageFilePath, boolean reinstall,
+ String... extraArgs)
+ throws InstallException {
+ try {
+ String remoteFilePath = syncPackageToDevice(packageFilePath);
+ installRemotePackage(remoteFilePath, reinstall, extraArgs);
+ removeRemotePackage(remoteFilePath);
+ } catch (IOException e) {
+ throw new InstallException(e);
+ } catch (AdbCommandRejectedException e) {
+ throw new InstallException(e);
+ } catch (TimeoutException e) {
+ throw new InstallException(e);
+ } catch (SyncException e) {
+ throw new InstallException(e);
+ }
+ }
+
+ @Override
+ public void installPackages(@NonNull List<File> apks, boolean reinstall,
+ @NonNull List<String> installOptions, long timeout, @NonNull TimeUnit timeoutUnit)
+ throws InstallException {
+ try {
+ SplitApkInstaller.create(this, apks, reinstall, installOptions)
+ .install(timeout, timeoutUnit);
+ } catch (InstallException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new InstallException(e);
+ }
+ }
+
+ @Override
+ public String syncPackageToDevice(String localFilePath)
+ throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
+ SyncService sync = null;
+ try {
+ String packageFileName = getFileName(localFilePath);
+ String remoteFilePath = String.format("/data/local/tmp/%1$s", packageFileName); //$NON-NLS-1$
+
+ Log.d(packageFileName, String.format("Uploading %1$s onto device '%2$s'",
+ packageFileName, getSerialNumber()));
+
+ sync = getSyncService();
+ if (sync != null) {
+ String message = String.format("Uploading file onto device '%1$s'",
+ getSerialNumber());
+ Log.d(LOG_TAG, message);
+ sync.pushFile(localFilePath, remoteFilePath, SyncService.getNullProgressMonitor());
+ } else {
+ throw new IOException("Unable to open sync connection!");
+ }
+ return remoteFilePath;
+ } catch (TimeoutException e) {
+ Log.e(LOG_TAG, "Error during Sync: timeout.");
+ throw e;
+
+ } catch (SyncException e) {
+ Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
+ throw e;
+
+ } catch (IOException e) {
+ Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
+ throw e;
+
+ } finally {
+ if (sync != null) {
+ sync.close();
+ }
+ }
+ }
+
+ /**
+ * Helper method to retrieve the file name given a local file path
+ * @param filePath full directory path to file
+ * @return {@link String} file name
+ */
+ private static String getFileName(String filePath) {
+ return new File(filePath).getName();
+ }
+
+ @Override
+ public void installRemotePackage(String remoteFilePath, boolean reinstall,
+ String... extraArgs) throws InstallException {
+ try {
+ InstallReceiver receiver = new InstallReceiver();
+ StringBuilder optionString = new StringBuilder();
+ if (reinstall) {
+ optionString.append("-r ");
+ }
+ if (extraArgs != null) {
+ optionString.append(Joiner.on(' ').join(extraArgs));
+ }
+ String cmd = String.format("pm install %1$s \"%2$s\"", optionString.toString(),
+ remoteFilePath);
+ executeShellCommand(cmd, receiver, INSTALL_TIMEOUT_MINUTES, TimeUnit.MINUTES);
+ String error = receiver.getErrorMessage();
+ if (error != null) {
+ throw new InstallException(error);
+ }
+ } catch (TimeoutException e) {
+ throw new InstallException(e);
+ } catch (AdbCommandRejectedException e) {
+ throw new InstallException(e);
+ } catch (ShellCommandUnresponsiveException e) {
+ throw new InstallException(e);
+ } catch (IOException e) {
+ throw new InstallException(e);
+ }
+ }
+
+ @Override
+ public void removeRemotePackage(String remoteFilePath) throws InstallException {
+ try {
+ executeShellCommand(String.format("rm \"%1$s\"", remoteFilePath),
+ new NullOutputReceiver(), INSTALL_TIMEOUT_MINUTES, TimeUnit.MINUTES);
+ } catch (IOException e) {
+ throw new InstallException(e);
+ } catch (TimeoutException e) {
+ throw new InstallException(e);
+ } catch (AdbCommandRejectedException e) {
+ throw new InstallException(e);
+ } catch (ShellCommandUnresponsiveException e) {
+ throw new InstallException(e);
+ }
+ }
+
+ @Override
+ public String uninstallPackage(String packageName) throws InstallException {
+ try {
+ InstallReceiver receiver = new InstallReceiver();
+ executeShellCommand("pm uninstall " + packageName, receiver, INSTALL_TIMEOUT_MINUTES,
+ TimeUnit.MINUTES);
+ return receiver.getErrorMessage();
+ } catch (TimeoutException e) {
+ throw new InstallException(e);
+ } catch (AdbCommandRejectedException e) {
+ throw new InstallException(e);
+ } catch (ShellCommandUnresponsiveException e) {
+ throw new InstallException(e);
+ } catch (IOException e) {
+ throw new InstallException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#reboot()
+ */
+ @Override
+ public void reboot(String into)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ AdbHelper.reboot(into, AndroidDebugBridge.getSocketAddress(), this);
+ }
+
+ @Override
+ public boolean root() throws TimeoutException, AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException {
+ if (!mIsRoot) {
+ AdbHelper.root(AndroidDebugBridge.getSocketAddress(), this);
+ }
+ return isRoot();
+ }
+
+ @Override
+ public boolean isRoot() throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ if (mIsRoot) {
+ return true;
+ }
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ executeShellCommand("echo $USER_ID", receiver, QUERY_IS_ROOT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ String userID = receiver.getOutput().trim();
+ mIsRoot = userID.equals("0");
+ return mIsRoot;
+ }
+
+ @Override
+ public Integer getBatteryLevel() throws TimeoutException, AdbCommandRejectedException,
+ IOException, ShellCommandUnresponsiveException {
+ // use default of 5 minutes
+ return getBatteryLevel(5 * 60 * 1000);
+ }
+
+ @Override
+ public Integer getBatteryLevel(long freshnessMs) throws TimeoutException,
+ AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException {
+ Future<Integer> futureBattery = getBattery(freshnessMs, TimeUnit.MILLISECONDS);
+ try {
+ return futureBattery.get();
+ } catch (InterruptedException e) {
+ return null;
+ } catch (ExecutionException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Future<Integer> getBattery() {
+ return getBattery(5, TimeUnit.MINUTES);
+ }
+
+ @NonNull
+ @Override
+ public Future<Integer> getBattery(long freshnessTime, @NonNull TimeUnit timeUnit) {
+ return mBatteryFetcher.getBattery(freshnessTime, timeUnit);
+ }
+
+ @NonNull
+ @Override
+ public List<String> getAbis() {
+ /* Try abiList (implemented in L onwards) otherwise fall back to abi and abi2. */
+ String abiList = getProperty(IDevice.PROP_DEVICE_CPU_ABI_LIST);
+ if(abiList != null) {
+ return Lists.newArrayList(abiList.split(","));
+ } else {
+ List<String> abis = Lists.newArrayListWithExpectedSize(2);
+ String abi = getProperty(IDevice.PROP_DEVICE_CPU_ABI);
+ if (abi != null) {
+ abis.add(abi);
+ }
+
+ abi = getProperty(IDevice.PROP_DEVICE_CPU_ABI2);
+ if (abi != null) {
+ abis.add(abi);
+ }
+
+ return abis;
+ }
+ }
+
+ @Override
+ public int getDensity() {
+ String densityValue = getProperty(IDevice.PROP_DEVICE_DENSITY);
+ if (densityValue == null) {
+ densityValue = getProperty(IDevice.PROP_DEVICE_EMULATOR_DENSITY);
+ }
+ if (densityValue != null) {
+ try {
+ return Integer.parseInt(densityValue);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ public String getLanguage() {
+ return getProperties().get(IDevice.PROP_DEVICE_LANGUAGE);
+ }
+
+ @Override
+ public String getRegion() {
+ return getProperty(IDevice.PROP_DEVICE_REGION);
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java b/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java
new file mode 100644
index 0000000..365e821
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/DeviceMonitor.java
@@ -0,0 +1,893 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ddmlib.AdbHelper.AdbResponse;
+import com.android.ddmlib.ClientData.DebuggerStatus;
+import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
+import com.android.ddmlib.IDevice.DeviceState;
+import com.android.ddmlib.utils.DebuggerPorts;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Queues;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The {@link DeviceMonitor} monitors devices attached to adb.
+ *
+ * On one thread, it runs the {@link com.android.ddmlib.DeviceMonitor.DeviceListMonitorTask}.
+ * This establishes a socket connection to the adb host, and issues a
+ * {@link #ADB_TRACK_DEVICES_COMMAND}. It then monitors that socket for all changes about device
+ * connection and device state.
+ *
+ * For each device that is detected to be online, it then opens a new socket connection to adb,
+ * and issues a "track-jdwp" command to that device. On this connection, it monitors active
+ * clients on the device. Note: a single thread monitors jdwp connections from all devices.
+ * The different socket connections to adb (one per device) are multiplexed over a single selector.
+ */
+final class DeviceMonitor {
+ private static final String ADB_TRACK_DEVICES_COMMAND = "host:track-devices";
+ private static final String ADB_TRACK_JDWP_COMMAND = "track-jdwp";
+
+ private final byte[] mLengthBuffer2 = new byte[4];
+
+ private volatile boolean mQuit = false;
+
+ private final AndroidDebugBridge mServer;
+ private DeviceListMonitorTask mDeviceListMonitorTask;
+
+ private Selector mSelector;
+
+ private final List<Device> mDevices = Lists.newCopyOnWriteArrayList();
+ private final DebuggerPorts mDebuggerPorts =
+ new DebuggerPorts(DdmPreferences.getDebugPortBase());
+ private final Map<Client, Integer> mClientsToReopen = new HashMap<Client, Integer>();
+ private final BlockingQueue<Pair<SocketChannel,Device>> mChannelsToRegister =
+ Queues.newLinkedBlockingQueue();
+
+ /**
+ * Creates a new {@link DeviceMonitor} object and links it to the running
+ * {@link AndroidDebugBridge} object.
+ * @param server the running {@link AndroidDebugBridge}.
+ */
+ DeviceMonitor(@NonNull AndroidDebugBridge server) {
+ mServer = server;
+ }
+
+ /**
+ * Starts the monitoring.
+ */
+ void start() {
+ mDeviceListMonitorTask = new DeviceListMonitorTask(mServer, new DeviceListUpdateListener());
+ new Thread(mDeviceListMonitorTask, "Device List Monitor").start(); //$NON-NLS-1$
+ }
+
+ /**
+ * Stops the monitoring.
+ */
+ void stop() {
+ mQuit = true;
+
+ if (mDeviceListMonitorTask != null) {
+ mDeviceListMonitorTask.stop();
+ }
+
+ // wake up the secondary loop by closing the selector.
+ if (mSelector != null) {
+ mSelector.wakeup();
+ }
+ }
+
+ /**
+ * Returns whether the monitor is currently connected to the debug bridge server.
+ */
+ boolean isMonitoring() {
+ return mDeviceListMonitorTask != null && mDeviceListMonitorTask.isMonitoring();
+ }
+
+ int getConnectionAttemptCount() {
+ return mDeviceListMonitorTask == null ? 0
+ : mDeviceListMonitorTask.getConnectionAttemptCount();
+ }
+
+ int getRestartAttemptCount() {
+ return mDeviceListMonitorTask == null ? 0 : mDeviceListMonitorTask.getRestartAttemptCount();
+ }
+
+ boolean hasInitialDeviceList() {
+ return mDeviceListMonitorTask != null && mDeviceListMonitorTask.hasInitialDeviceList();
+ }
+
+ /**
+ * Returns the devices.
+ */
+ @NonNull Device[] getDevices() {
+ // Since this is a copy of write array list, we don't want to do a compound operation
+ // (toArray with an appropriate size) without locking, so we just let the container provide
+ // an appropriately sized array
+ //noinspection ToArrayCallWithZeroLengthArrayArgument
+ return mDevices.toArray(new Device[0]);
+ }
+
+ @NonNull
+ AndroidDebugBridge getServer() {
+ return mServer;
+ }
+
+ void addClientToDropAndReopen(Client client, int port) {
+ synchronized (mClientsToReopen) {
+ Log.d("DeviceMonitor",
+ "Adding " + client + " to list of client to reopen (" + port + ").");
+ if (mClientsToReopen.get(client) == null) {
+ mClientsToReopen.put(client, port);
+ }
+ }
+ mSelector.wakeup();
+ }
+
+ /**
+ * Attempts to connect to the debug bridge server.
+ * @return a connect socket if success, null otherwise
+ */
+ @Nullable
+ private static SocketChannel openAdbConnection() {
+ try {
+ SocketChannel adbChannel = SocketChannel.open(AndroidDebugBridge.getSocketAddress());
+ adbChannel.socket().setTcpNoDelay(true);
+ return adbChannel;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Updates the device list with the new items received from the monitoring service.
+ */
+ private void updateDevices(@NonNull List<Device> newList) {
+ DeviceListComparisonResult result = DeviceListComparisonResult.compare(mDevices, newList);
+ for (IDevice device : result.removed) {
+ removeDevice((Device) device);
+ AndroidDebugBridge.deviceDisconnected(device);
+ }
+
+ List<Device> newlyOnline = Lists.newArrayListWithExpectedSize(mDevices.size());
+
+ for (Map.Entry<IDevice, DeviceState> entry : result.updated.entrySet()) {
+ Device device = (Device) entry.getKey();
+ device.setState(entry.getValue());
+ device.update(Device.CHANGE_STATE);
+
+ if (device.isOnline()) {
+ newlyOnline.add(device);
+ }
+ }
+
+ for (IDevice device : result.added) {
+ mDevices.add((Device) device);
+ AndroidDebugBridge.deviceConnected(device);
+ if (device.isOnline()) {
+ newlyOnline.add((Device) device);
+ }
+ }
+
+ if (AndroidDebugBridge.getClientSupport()) {
+ for (Device device : newlyOnline) {
+ if (!startMonitoringDevice(device)) {
+ Log.e("DeviceMonitor", "Failed to start monitoring "
+ + device.getSerialNumber());
+ }
+ }
+ }
+
+ for (Device device : newlyOnline) {
+ queryAvdName(device);
+
+ // Initiate a property fetch so that future requests can be served out of this cache.
+ // This is necessary for backwards compatibility
+ device.getSystemProperty(IDevice.PROP_BUILD_API_LEVEL);
+ }
+ }
+
+ private void removeDevice(@NonNull Device device) {
+ device.setState(DeviceState.DISCONNECTED);
+ device.clearClientList();
+ mDevices.remove(device);
+
+ SocketChannel channel = device.getClientMonitoringSocket();
+ if (channel != null) {
+ try {
+ channel.close();
+ } catch (IOException e) {
+ // doesn't really matter if the close fails.
+ }
+ }
+ }
+
+ private static void queryAvdName(@NonNull Device device) {
+ if (!device.isEmulator()) {
+ return;
+ }
+
+ EmulatorConsole console = EmulatorConsole.getConsole(device);
+ if (console != null) {
+ device.setAvdName(console.getAvdName());
+ console.close();
+ }
+ }
+
+ /**
+ * Starts a monitoring service for a device.
+ * @param device the device to monitor.
+ * @return true if success.
+ */
+ private boolean startMonitoringDevice(@NonNull Device device) {
+ SocketChannel socketChannel = openAdbConnection();
+
+ if (socketChannel != null) {
+ try {
+ boolean result = sendDeviceMonitoringRequest(socketChannel, device);
+ if (result) {
+
+ if (mSelector == null) {
+ startDeviceMonitorThread();
+ }
+
+ device.setClientMonitoringSocket(socketChannel);
+
+ socketChannel.configureBlocking(false);
+
+ try {
+ mChannelsToRegister.put(Pair.of(socketChannel, device));
+ } catch (InterruptedException e) {
+ // the queue is unbounded, and isn't going to block
+ }
+ mSelector.wakeup();
+
+ return true;
+ }
+ } catch (TimeoutException e) {
+ try {
+ // attempt to close the socket if needed.
+ socketChannel.close();
+ } catch (IOException e1) {
+ // we can ignore that one. It may already have been closed.
+ }
+ Log.d("DeviceMonitor",
+ "Connection Failure when starting to monitor device '"
+ + device + "' : timeout");
+ } catch (AdbCommandRejectedException e) {
+ try {
+ // attempt to close the socket if needed.
+ socketChannel.close();
+ } catch (IOException e1) {
+ // we can ignore that one. It may already have been closed.
+ }
+ Log.d("DeviceMonitor",
+ "Adb refused to start monitoring device '"
+ + device + "' : " + e.getMessage());
+ } catch (IOException e) {
+ try {
+ // attempt to close the socket if needed.
+ socketChannel.close();
+ } catch (IOException e1) {
+ // we can ignore that one. It may already have been closed.
+ }
+ Log.d("DeviceMonitor",
+ "Connection Failure when starting to monitor device '"
+ + device + "' : " + e.getMessage());
+ }
+ }
+
+ return false;
+ }
+
+ private void startDeviceMonitorThread() throws IOException {
+ mSelector = Selector.open();
+ new Thread("Device Client Monitor") { //$NON-NLS-1$
+ @Override
+ public void run() {
+ deviceClientMonitorLoop();
+ }
+ }.start();
+ }
+
+ private void deviceClientMonitorLoop() {
+ do {
+ try {
+ int count = mSelector.select();
+
+ if (mQuit) {
+ return;
+ }
+
+ synchronized (mClientsToReopen) {
+ if (!mClientsToReopen.isEmpty()) {
+ Set<Client> clients = mClientsToReopen.keySet();
+ MonitorThread monitorThread = MonitorThread.getInstance();
+
+ for (Client client : clients) {
+ Device device = client.getDeviceImpl();
+ int pid = client.getClientData().getPid();
+
+ monitorThread.dropClient(client, false /* notify */);
+
+ // This is kinda bad, but if we don't wait a bit, the client
+ // will never answer the second handshake!
+ Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+
+ int port = mClientsToReopen.get(client);
+
+ if (port == IDebugPortProvider.NO_STATIC_PORT) {
+ port = getNextDebuggerPort();
+ }
+ Log.d("DeviceMonitor", "Reopening " + client);
+ openClient(device, pid, port, monitorThread);
+ device.update(Device.CHANGE_CLIENT_LIST);
+ }
+
+ mClientsToReopen.clear();
+ }
+ }
+
+ // register any new channels
+ while (!mChannelsToRegister.isEmpty()) {
+ try {
+ Pair<SocketChannel, Device> data = mChannelsToRegister.take();
+ data.getFirst().register(mSelector, SelectionKey.OP_READ, data.getSecond());
+ } catch (InterruptedException e) {
+ // doesn't block: this thread is the only reader and it reads only when
+ // there is data
+ }
+ }
+
+ if (count == 0) {
+ continue;
+ }
+
+ Set<SelectionKey> keys = mSelector.selectedKeys();
+ Iterator<SelectionKey> iter = keys.iterator();
+
+ while (iter.hasNext()) {
+ SelectionKey key = iter.next();
+ iter.remove();
+
+ if (key.isValid() && key.isReadable()) {
+ Object attachment = key.attachment();
+
+ if (attachment instanceof Device) {
+ Device device = (Device)attachment;
+
+ SocketChannel socket = device.getClientMonitoringSocket();
+
+ if (socket != null) {
+ try {
+ int length = readLength(socket, mLengthBuffer2);
+
+ processIncomingJdwpData(device, socket, length);
+ } catch (IOException ioe) {
+ Log.d("DeviceMonitor",
+ "Error reading jdwp list: " + ioe.getMessage());
+ socket.close();
+
+ // restart the monitoring of that device
+ if (mDevices.contains(device)) {
+ Log.d("DeviceMonitor",
+ "Restarting monitoring service for " + device);
+ startMonitoringDevice(device);
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ Log.e("DeviceMonitor", "Connection error while monitoring clients.");
+ }
+
+ } while (!mQuit);
+ }
+
+ private static boolean sendDeviceMonitoringRequest(@NonNull SocketChannel socket,
+ @NonNull Device device)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+
+ try {
+ AdbHelper.setDevice(socket, device);
+ AdbHelper.write(socket, AdbHelper.formAdbRequest(ADB_TRACK_JDWP_COMMAND));
+ AdbResponse resp = AdbHelper.readAdbResponse(socket, false);
+
+ if (!resp.okay) {
+ // request was refused by adb!
+ Log.e("DeviceMonitor", "adb refused request: " + resp.message);
+ }
+
+ return resp.okay;
+ } catch (TimeoutException e) {
+ Log.e("DeviceMonitor", "Sending jdwp tracking request timed out!");
+ throw e;
+ } catch (IOException e) {
+ Log.e("DeviceMonitor", "Sending jdwp tracking request failed!");
+ throw e;
+ }
+ }
+
+ private void processIncomingJdwpData(@NonNull Device device,
+ @NonNull SocketChannel monitorSocket, int length) throws IOException {
+
+ // This methods reads @length bytes from the @monitorSocket channel.
+ // These bytes correspond to the pids of the current set of processes on the device.
+ // It takes this set of pids and compares them with the existing set of clients
+ // for the device. Clients that correspond to pids that are not alive anymore are
+ // dropped, and new clients are created for pids that don't have a corresponding Client.
+
+ if (length >= 0) {
+ // array for the current pids.
+ Set<Integer> newPids = new HashSet<Integer>();
+
+ // get the string data if there are any
+ if (length > 0) {
+ byte[] buffer = new byte[length];
+ String result = read(monitorSocket, buffer);
+
+ // split each line in its own list and create an array of integer pid
+ String[] pids = result == null ? new String[0] : result.split("\n"); //$NON-NLS-1$
+
+ for (String pid : pids) {
+ try {
+ newPids.add(Integer.valueOf(pid));
+ } catch (NumberFormatException nfe) {
+ // looks like this pid is not really a number. Lets ignore it.
+ continue;
+ }
+ }
+ }
+
+ MonitorThread monitorThread = MonitorThread.getInstance();
+
+ List<Client> clients = device.getClientList();
+ Map<Integer, Client> existingClients = new HashMap<Integer, Client>();
+
+ synchronized (clients) {
+ for (Client c : clients) {
+ existingClients.put(c.getClientData().getPid(), c);
+ }
+ }
+
+ Set<Client> clientsToRemove = new HashSet<Client>();
+ for (Integer pid : existingClients.keySet()) {
+ if (!newPids.contains(pid)) {
+ clientsToRemove.add(existingClients.get(pid));
+ }
+ }
+
+ Set<Integer> pidsToAdd = new HashSet<Integer>(newPids);
+ pidsToAdd.removeAll(existingClients.keySet());
+
+ monitorThread.dropClients(clientsToRemove, false);
+
+ // at this point whatever pid is left in the list needs to be converted into Clients.
+ for (int newPid : pidsToAdd) {
+ openClient(device, newPid, getNextDebuggerPort(), monitorThread);
+ }
+
+ if (!pidsToAdd.isEmpty() || !clientsToRemove.isEmpty()) {
+ AndroidDebugBridge.deviceChanged(device, Device.CHANGE_CLIENT_LIST);
+ }
+ }
+ }
+
+ /** Opens and creates a new client. */
+ private static void openClient(@NonNull Device device, int pid, int port,
+ @NonNull MonitorThread monitorThread) {
+
+ SocketChannel clientSocket;
+ try {
+ clientSocket = AdbHelper.createPassThroughConnection(
+ AndroidDebugBridge.getSocketAddress(), device, pid);
+
+ // required for Selector
+ clientSocket.configureBlocking(false);
+ } catch (UnknownHostException uhe) {
+ Log.d("DeviceMonitor", "Unknown Jdwp pid: " + pid);
+ return;
+ } catch (TimeoutException e) {
+ Log.w("DeviceMonitor",
+ "Failed to connect to client '" + pid + "': timeout");
+ return;
+ } catch (AdbCommandRejectedException e) {
+ Log.w("DeviceMonitor",
+ "Adb rejected connection to client '" + pid + "': " + e.getMessage());
+ return;
+
+ } catch (IOException ioe) {
+ Log.w("DeviceMonitor",
+ "Failed to connect to client '" + pid + "': " + ioe.getMessage());
+ return ;
+ }
+
+ createClient(device, pid, clientSocket, port, monitorThread);
+ }
+
+ /** Creates a client and register it to the monitor thread */
+ private static void createClient(@NonNull Device device, int pid, @NonNull SocketChannel socket,
+ int debuggerPort, @NonNull MonitorThread monitorThread) {
+
+ /*
+ * Successfully connected to something. Create a Client object, add
+ * it to the list, and initiate the JDWP handshake.
+ */
+
+ Client client = new Client(device, socket, pid);
+
+ if (client.sendHandshake()) {
+ try {
+ if (AndroidDebugBridge.getClientSupport()) {
+ client.listenForDebugger(debuggerPort);
+ String msg = String.format(Locale.US, "Opening a debugger listener at port %1$d for client with pid %2$d",
+ debuggerPort, pid);
+ Log.i("ddms", msg);
+ }
+ } catch (IOException ioe) {
+ client.getClientData().setDebuggerConnectionStatus(DebuggerStatus.ERROR);
+ Log.e("ddms", "Can't bind to local " + debuggerPort + " for debugger");
+ // oh well
+ }
+
+ client.requestAllocationStatus();
+ } else {
+ Log.e("ddms", "Handshake with " + client + " failed!");
+ /*
+ * The handshake send failed. We could remove it now, but if the
+ * failure is "permanent" we'll just keep banging on it and
+ * getting the same result. Keep it in the list with its "error"
+ * state so we don't try to reopen it.
+ */
+ }
+
+ if (client.isValid()) {
+ device.addClient(client);
+ monitorThread.addClient(client);
+ }
+ }
+
+ private int getNextDebuggerPort() {
+ return mDebuggerPorts.next();
+ }
+
+ void addPortToAvailableList(int port) {
+ mDebuggerPorts.free(port);
+ }
+
+ /**
+ * Reads the length of the next message from a socket.
+ * @param socket The {@link SocketChannel} to read from.
+ * @return the length, or 0 (zero) if no data is available from the socket.
+ * @throws IOException if the connection failed.
+ */
+ private static int readLength(@NonNull SocketChannel socket, @NonNull byte[] buffer)
+ throws IOException {
+ String msg = read(socket, buffer);
+
+ if (msg != null) {
+ try {
+ return Integer.parseInt(msg, 16);
+ } catch (NumberFormatException nfe) {
+ // we'll throw an exception below.
+ }
+ }
+
+ // we receive something we can't read. It's better to reset the connection at this point.
+ throw new IOException("Unable to read length");
+ }
+
+ /**
+ * Fills a buffer by reading data from a socket.
+ * @return the content of the buffer as a string, or null if it failed to convert the buffer.
+ * @throws IOException if there was not enough data to fill the buffer
+ */
+ @Nullable
+ private static String read(@NonNull SocketChannel socket, @NonNull byte[] buffer)
+ throws IOException {
+ ByteBuffer buf = ByteBuffer.wrap(buffer, 0, buffer.length);
+
+ while (buf.position() != buf.limit()) {
+ int count;
+
+ count = socket.read(buf);
+ if (count < 0) {
+ throw new IOException("EOF");
+ }
+ }
+
+ try {
+ return new String(buffer, 0, buf.position(), AdbHelper.DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ private class DeviceListUpdateListener implements DeviceListMonitorTask.UpdateListener {
+ @Override
+ public void connectionError(@NonNull Exception e) {
+ for (Device device : mDevices) {
+ removeDevice(device);
+ AndroidDebugBridge.deviceDisconnected(device);
+ }
+ }
+
+ @Override
+ public void deviceListUpdate(@NonNull Map<String, DeviceState> devices) {
+ List<Device> l = Lists.newArrayListWithExpectedSize(devices.size());
+ for (Map.Entry<String, DeviceState> entry : devices.entrySet()) {
+ l.add(new Device(DeviceMonitor.this, entry.getKey(), entry.getValue()));
+ }
+ // now merge the new devices with the old ones.
+ updateDevices(l);
+ }
+ }
+
+ @VisibleForTesting
+ static class DeviceListComparisonResult {
+ @NonNull public final Map<IDevice,DeviceState> updated;
+ @NonNull public final List<IDevice> added;
+ @NonNull public final List<IDevice> removed;
+
+ private DeviceListComparisonResult(@NonNull Map<IDevice,DeviceState> updated,
+ @NonNull List<IDevice> added,
+ @NonNull List<IDevice> removed) {
+ this.updated = updated;
+ this.added = added;
+ this.removed = removed;
+ }
+
+ @NonNull
+ public static DeviceListComparisonResult compare(@NonNull List<? extends IDevice> previous,
+ @NonNull List<? extends IDevice> current) {
+ current = Lists.newArrayList(current);
+
+ final Map<IDevice,DeviceState> updated = Maps.newHashMapWithExpectedSize(current.size());
+ final List<IDevice> added = Lists.newArrayListWithExpectedSize(1);
+ final List<IDevice> removed = Lists.newArrayListWithExpectedSize(1);
+
+ for (IDevice device : previous) {
+ IDevice currentDevice = find(current, device);
+ if (currentDevice != null) {
+ if (currentDevice.getState() != device.getState()) {
+ updated.put(device, currentDevice.getState());
+ }
+ current.remove(currentDevice);
+ } else {
+ removed.add(device);
+ }
+ }
+
+ added.addAll(current);
+
+ return new DeviceListComparisonResult(updated, added, removed);
+ }
+
+ @Nullable
+ private static IDevice find(@NonNull List<? extends IDevice> devices,
+ @NonNull IDevice device) {
+ for (IDevice d : devices) {
+ if (d.getSerialNumber().equals(device.getSerialNumber())) {
+ return d;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ static class DeviceListMonitorTask implements Runnable {
+ private final byte[] mLengthBuffer = new byte[4];
+
+ private final AndroidDebugBridge mBridge;
+ private final UpdateListener mListener;
+
+ private SocketChannel mAdbConnection = null;
+ private boolean mMonitoring = false;
+ private int mConnectionAttempt = 0;
+ private int mRestartAttemptCount = 0;
+ private boolean mInitialDeviceListDone = false;
+
+ private volatile boolean mQuit;
+
+ private interface UpdateListener {
+ void connectionError(@NonNull Exception e);
+ void deviceListUpdate(@NonNull Map<String,DeviceState> devices);
+ }
+
+ public DeviceListMonitorTask(@NonNull AndroidDebugBridge bridge,
+ @NonNull UpdateListener listener) {
+ mBridge = bridge;
+ mListener = listener;
+ }
+
+ @Override
+ public void run() {
+ do {
+ if (mAdbConnection == null) {
+ Log.d("DeviceMonitor", "Opening adb connection");
+ mAdbConnection = openAdbConnection();
+ if (mAdbConnection == null) {
+ mConnectionAttempt++;
+ Log.e("DeviceMonitor", "Connection attempts: " + mConnectionAttempt);
+ if (mConnectionAttempt > 10) {
+ if (!mBridge.startAdb()) {
+ mRestartAttemptCount++;
+ Log.e("DeviceMonitor",
+ "adb restart attempts: " + mRestartAttemptCount);
+ } else {
+ Log.i("DeviceMonitor", "adb restarted");
+ mRestartAttemptCount = 0;
+ }
+ }
+ Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+ } else {
+ Log.d("DeviceMonitor", "Connected to adb for device monitoring");
+ mConnectionAttempt = 0;
+ }
+ }
+
+ try {
+ if (mAdbConnection != null && !mMonitoring) {
+ mMonitoring = sendDeviceListMonitoringRequest();
+ }
+
+ if (mMonitoring) {
+ int length = readLength(mAdbConnection, mLengthBuffer);
+
+ if (length >= 0) {
+ // read the incoming message
+ processIncomingDeviceData(length);
+
+ // flag the fact that we have build the list at least once.
+ mInitialDeviceListDone = true;
+ }
+ }
+ } catch (AsynchronousCloseException ace) {
+ // this happens because of a call to Quit. We do nothing, and the loop will break.
+ } catch (TimeoutException ioe) {
+ handleExceptionInMonitorLoop(ioe);
+ } catch (IOException ioe) {
+ handleExceptionInMonitorLoop(ioe);
+ }
+ } while (!mQuit);
+ }
+
+ private boolean sendDeviceListMonitoringRequest() throws TimeoutException, IOException {
+ byte[] request = AdbHelper.formAdbRequest(ADB_TRACK_DEVICES_COMMAND);
+
+ try {
+ AdbHelper.write(mAdbConnection, request);
+ AdbResponse resp = AdbHelper.readAdbResponse(mAdbConnection, false);
+ if (!resp.okay) {
+ // request was refused by adb!
+ Log.e("DeviceMonitor", "adb refused request: " + resp.message);
+ }
+
+ return resp.okay;
+ } catch (IOException e) {
+ Log.e("DeviceMonitor", "Sending Tracking request failed!");
+ mAdbConnection.close();
+ throw e;
+ }
+ }
+
+ private void handleExceptionInMonitorLoop(@NonNull Exception e) {
+ if (!mQuit) {
+ if (e instanceof TimeoutException) {
+ Log.e("DeviceMonitor", "Adb connection Error: timeout");
+ } else {
+ Log.e("DeviceMonitor", "Adb connection Error:" + e.getMessage());
+ }
+ mMonitoring = false;
+ if (mAdbConnection != null) {
+ try {
+ mAdbConnection.close();
+ } catch (IOException ioe) {
+ // we can safely ignore that one.
+ }
+ mAdbConnection = null;
+
+ mListener.connectionError(e);
+ }
+ }
+ }
+
+ /** Processes an incoming device message from the socket */
+ private void processIncomingDeviceData(int length) throws IOException {
+ Map<String, DeviceState> result;
+ if (length <= 0) {
+ result = Collections.emptyMap();
+ } else {
+ String response = read(mAdbConnection, new byte[length]);
+ result = parseDeviceListResponse(response);
+ }
+
+ mListener.deviceListUpdate(result);
+ }
+
+ @VisibleForTesting
+ static Map<String, DeviceState> parseDeviceListResponse(@Nullable String result) {
+ Map<String, DeviceState> deviceStateMap = Maps.newHashMap();
+ String[] devices = result == null ? new String[0] : result.split("\n"); //$NON-NLS-1$
+
+ for (String d : devices) {
+ String[] param = d.split("\t"); //$NON-NLS-1$
+ if (param.length == 2) {
+ // new adb uses only serial numbers to identify devices
+ deviceStateMap.put(param[0], DeviceState.getState(param[1]));
+ }
+ }
+ return deviceStateMap;
+ }
+
+ boolean isMonitoring() {
+ return mMonitoring;
+ }
+
+ boolean hasInitialDeviceList() {
+ return mInitialDeviceListDone;
+ }
+
+ int getConnectionAttemptCount() {
+ return mConnectionAttempt;
+ }
+
+ int getRestartAttemptCount() {
+ return mRestartAttemptCount;
+ }
+
+ public void stop() {
+ mQuit = true;
+
+ // wakeup the main loop thread by closing the main connection to adb.
+ if (mAdbConnection != null) {
+ try {
+ mAdbConnection.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java b/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java
new file mode 100644
index 0000000..2b8cd7f
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/EmulatorConsole.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.concurrency.GuardedBy;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.security.InvalidParameterException;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Provides control over emulated hardware of the Android emulator.
+ * <p/>This is basically a wrapper around the command line console normally used with telnet.
+ *<p/>
+ * Regarding line termination handling:<br>
+ * One of the issues is that the telnet protocol <b>requires</b> usage of <code>\r\n</code>. Most
+ * implementations don't enforce it (the dos one does). In this particular case, this is mostly
+ * irrelevant since we don't use telnet in Java, but that means we want to make
+ * sure we use the same line termination than what the console expects. The console
+ * code removes <code>\r</code> and waits for <code>\n</code>.
+ * <p/>However this means you <i>may</i> receive <code>\r\n</code> when reading from the console.
+ * <p/>
+ * <b>This API will change in the near future.</b>
+ */
+public final class EmulatorConsole {
+
+ private static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
+
+ private static final int WAIT_TIME = 5; // spin-wait sleep, in ms
+
+ private static final int STD_TIMEOUT = 5000; // standard delay, in ms
+
+ private static final String HOST = "127.0.0.1"; //$NON-NLS-1$
+
+ private static final String COMMAND_PING = "help\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_AVD_NAME = "avd name\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_KILL = "kill\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_GSM_STATUS = "gsm status\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_GSM_CALL = "gsm call %1$s\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_GSM_CANCEL_CALL = "gsm cancel %1$s\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_GSM_DATA = "gsm data %1$s\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_GSM_VOICE = "gsm voice %1$s\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_SMS_SEND = "sms send %1$s %2$s\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_NETWORK_STATUS = "network status\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_NETWORK_SPEED = "network speed %1$s\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_NETWORK_LATENCY = "network delay %1$s\r\n"; //$NON-NLS-1$
+ private static final String COMMAND_GPS = "geo fix %1$f %2$f %3$f\r\n"; //$NON-NLS-1$
+
+ private static final Pattern RE_KO = Pattern.compile("KO:\\s+(.*)"); //$NON-NLS-1$
+
+ /**
+ * Array of delay values: no delay, gprs, edge/egprs, umts/3d
+ */
+ public static final int[] MIN_LATENCIES = new int[] {
+ 0, // No delay
+ 150, // gprs
+ 80, // edge/egprs
+ 35 // umts/3g
+ };
+
+ /**
+ * Array of download speeds: full speed, gsm, hscsd, gprs, edge/egprs, umts/3g, hsdpa.
+ */
+ public static final int[] DOWNLOAD_SPEEDS = new int[] {
+ 0, // full speed
+ 14400, // gsm
+ 43200, // hscsd
+ 80000, // gprs
+ 236800, // edge/egprs
+ 1920000, // umts/3g
+ 14400000 // hsdpa
+ };
+
+ /** Arrays of valid network speeds */
+ public static final String[] NETWORK_SPEEDS = new String[] {
+ "full", //$NON-NLS-1$
+ "gsm", //$NON-NLS-1$
+ "hscsd", //$NON-NLS-1$
+ "gprs", //$NON-NLS-1$
+ "edge", //$NON-NLS-1$
+ "umts", //$NON-NLS-1$
+ "hsdpa", //$NON-NLS-1$
+ };
+
+ /** Arrays of valid network latencies */
+ public static final String[] NETWORK_LATENCIES = new String[] {
+ "none", //$NON-NLS-1$
+ "gprs", //$NON-NLS-1$
+ "edge", //$NON-NLS-1$
+ "umts", //$NON-NLS-1$
+ };
+
+ /** Gsm Mode enum. */
+ public enum GsmMode {
+ UNKNOWN((String)null),
+ UNREGISTERED(new String[] { "unregistered", "off" }),
+ HOME(new String[] { "home", "on" }),
+ ROAMING("roaming"),
+ SEARCHING("searching"),
+ DENIED("denied");
+
+ private final String[] tags;
+
+ GsmMode(String tag) {
+ if (tag != null) {
+ this.tags = new String[] { tag };
+ } else {
+ this.tags = new String[0];
+ }
+ }
+
+ GsmMode(String[] tags) {
+ this.tags = tags;
+ }
+
+ public static GsmMode getEnum(String tag) {
+ for (GsmMode mode : values()) {
+ for (String t : mode.tags) {
+ if (t.equals(tag)) {
+ return mode;
+ }
+ }
+ }
+ return UNKNOWN;
+ }
+
+ /**
+ * Returns the first tag of the enum.
+ */
+ public String getTag() {
+ if (tags.length > 0) {
+ return tags[0];
+ }
+ return null;
+ }
+ }
+
+ public static final String RESULT_OK = null;
+
+ private static final Pattern sEmulatorRegexp = Pattern.compile(Device.RE_EMULATOR_SN);
+ private static final Pattern sVoiceStatusRegexp = Pattern.compile(
+ "gsm\\s+voice\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+ private static final Pattern sDataStatusRegexp = Pattern.compile(
+ "gsm\\s+data\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+ private static final Pattern sDownloadSpeedRegexp = Pattern.compile(
+ "\\s+download\\s+speed:\\s+(\\d+)\\s+bits.*", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+ private static final Pattern sMinLatencyRegexp = Pattern.compile(
+ "\\s+minimum\\s+latency:\\s+(\\d+)\\s+ms", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+
+ @GuardedBy(value = "sEmulators")
+ private static final HashMap<Integer, EmulatorConsole> sEmulators =
+ new HashMap<Integer, EmulatorConsole>();
+
+ private static final String LOG_TAG = "EmulatorConsole";
+
+ /** Gsm Status class */
+ public static class GsmStatus {
+ /** Voice status. */
+ public GsmMode voice = GsmMode.UNKNOWN;
+ /** Data status. */
+ public GsmMode data = GsmMode.UNKNOWN;
+ }
+
+ /** Network Status class */
+ public static class NetworkStatus {
+ /** network speed status. This is an index in the {@link #DOWNLOAD_SPEEDS} array. */
+ public int speed = -1;
+ /** network latency status. This is an index in the {@link #MIN_LATENCIES} array. */
+ public int latency = -1;
+ }
+
+ private int mPort = -1;
+
+ private SocketChannel mSocketChannel;
+
+ private byte[] mBuffer = new byte[1024];
+
+ /**
+ * Returns an {@link EmulatorConsole} object for the given {@link Device}. This can
+ * be an already existing console, or a new one if it hadn't been created yet.
+ * Note: emulator consoles don't automatically close when an emulator exists. It is the
+ * responsibility of higher level code to explicitly call {@link #close()} when the emulator
+ * corresponding to a open console is killed.
+ * @param d The device that the console links to.
+ * @return an <code>EmulatorConsole</code> object or <code>null</code> if the connection failed.
+ */
+ @Nullable
+ public static EmulatorConsole getConsole(IDevice d) {
+ // we need to make sure that the device is an emulator
+ // get the port number. This is the console port.
+ Integer port = getEmulatorPort(d.getSerialNumber());
+ if (port == null) {
+ Log.w(LOG_TAG, "Failed to find emulator port from serial: " + d.getSerialNumber());
+ return null;
+ }
+
+ EmulatorConsole console = retrieveConsole(port);
+
+ if (!console.checkConnection()) {
+ removeConsole(console.mPort);
+ console = null;
+ }
+
+ return console;
+ }
+
+ /**
+ * Return port of emulator given its serial number.
+ *
+ * @param serialNumber the emulator's serial number
+ * @return the integer port or <code>null</code> if it could not be determined
+ */
+ public static Integer getEmulatorPort(String serialNumber) {
+ Matcher m = sEmulatorRegexp.matcher(serialNumber);
+ if (m.matches()) {
+ // get the port number. This is the console port.
+ int port;
+ try {
+ port = Integer.parseInt(m.group(1));
+ if (port > 0) {
+ return port;
+ }
+ } catch (NumberFormatException e) {
+ // looks like we failed to get the port number. This is a bit strange since
+ // it's coming from a regexp that only accept digit, but we handle the case
+ // and return null.
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve a console object for this port, creating if necessary.
+ */
+ @NonNull
+ private static EmulatorConsole retrieveConsole(int port) {
+ synchronized (sEmulators) {
+ EmulatorConsole console = sEmulators.get(port);
+ if (console == null) {
+ Log.v(LOG_TAG, "Creating emulator console for " + Integer.toString(port));
+ console = new EmulatorConsole(port);
+ sEmulators.put(port, console);
+ }
+ return console;
+ }
+ }
+
+ /**
+ * Removes the console object associated with a port from the map.
+ * @param port The port of the console to remove.
+ */
+ private static void removeConsole(int port) {
+ synchronized (sEmulators) {
+ Log.v(LOG_TAG, "Removing emulator console for " + Integer.toString(port));
+ EmulatorConsole console = sEmulators.get(port);
+ if (console != null) {
+ console.closeConnection();
+ sEmulators.remove(port);
+ }
+ }
+ }
+
+ private EmulatorConsole(int port) {
+ mPort = port;
+ }
+
+ /**
+ * Determine if connection to emulator console is functioning. Starts the connection if
+ * necessary
+ * @return true if success.
+ */
+ private synchronized boolean checkConnection() {
+ if (mSocketChannel == null) {
+ // connection not established, try to connect
+ InetSocketAddress socketAddr;
+ try {
+ InetAddress hostAddr = InetAddress.getByName(HOST);
+ socketAddr = new InetSocketAddress(hostAddr, mPort);
+ mSocketChannel = SocketChannel.open(socketAddr);
+ mSocketChannel.configureBlocking(false);
+ // read initial output from console
+ readLines();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to start Emulator console for " + Integer.toString(mPort));
+ return false;
+ }
+ }
+
+ return ping();
+ }
+
+ /**
+ * Ping the emulator to check if the connection is still alive.
+ * @return true if the connection is alive.
+ */
+ private synchronized boolean ping() {
+ // it looks like we can send stuff, even when the emulator quit, but we can't read
+ // from the socket. So we check the return of readLines()
+ if (sendCommand(COMMAND_PING)) {
+ return readLines() != null;
+ }
+
+ return false;
+ }
+
+ /**
+ * Close the socket channel and reset internal console state.
+ */
+ private synchronized void closeConnection() {
+ try {
+ if (mSocketChannel != null) {
+ mSocketChannel.close();
+ }
+ mSocketChannel = null;
+ mPort = -1;
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to close EmulatorConsole channel");
+ }
+ }
+
+ /**
+ * Sends a KILL command to the emulator.
+ */
+ public synchronized void kill() {
+ if (sendCommand(COMMAND_KILL)) {
+ close();
+ }
+ }
+
+ /**
+ * Closes this instance of the emulator console.
+ */
+ public synchronized void close() {
+ if (mPort == -1) {
+ return;
+ }
+
+ removeConsole(mPort);
+ }
+
+ public synchronized String getAvdName() {
+ if (sendCommand(COMMAND_AVD_NAME)) {
+ String[] result = readLines();
+ // qemu2's readline implementation sends some formatting characters in the first line
+ // let's make sure that only the last two lines are fine
+ if (result != null && result.length >= 2) { // this should be the name on the (length - 1)th line,
+ // and ok on last one
+ return result[result.length - 2];
+ } else {
+ // try to see if there's a message after KO
+ Matcher m = RE_KO.matcher(result[result.length-1]);
+ if (m.matches()) {
+ return m.group(1);
+ }
+ Log.w(LOG_TAG, "avd name result did not match expected");
+ for (int i=0; i < result.length; i++) {
+ Log.d(LOG_TAG, result[i]);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the network status of the emulator.
+ * @return a {@link NetworkStatus} object containing the {@link GsmStatus}, or
+ * <code>null</code> if the query failed.
+ */
+ public synchronized NetworkStatus getNetworkStatus() {
+ if (sendCommand(COMMAND_NETWORK_STATUS)) {
+ /* Result is in the format
+ Current network status:
+ download speed: 14400 bits/s (1.8 KB/s)
+ upload speed: 14400 bits/s (1.8 KB/s)
+ minimum latency: 0 ms
+ maximum latency: 0 ms
+ */
+ String[] result = readLines();
+
+ if (isValid(result)) {
+ // we only compare against the min latency and the download speed
+ // let's not rely on the order of the output, and simply loop through
+ // the line testing the regexp.
+ NetworkStatus status = new NetworkStatus();
+ for (String line : result) {
+ Matcher m = sDownloadSpeedRegexp.matcher(line);
+ if (m.matches()) {
+ // get the string value
+ String value = m.group(1);
+
+ // get the index from the list
+ status.speed = getSpeedIndex(value);
+
+ // move on to next line.
+ continue;
+ }
+
+ m = sMinLatencyRegexp.matcher(line);
+ if (m.matches()) {
+ // get the string value
+ String value = m.group(1);
+
+ // get the index from the list
+ status.latency = getLatencyIndex(value);
+
+ // move on to next line.
+ continue;
+ }
+ }
+
+ return status;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the current gsm status of the emulator
+ * @return a {@link GsmStatus} object containing the gms status, or <code>null</code>
+ * if the query failed.
+ */
+ public synchronized GsmStatus getGsmStatus() {
+ if (sendCommand(COMMAND_GSM_STATUS)) {
+ /*
+ * result is in the format:
+ * gsm status
+ * gsm voice state: home
+ * gsm data state: home
+ */
+
+ String[] result = readLines();
+ if (isValid(result)) {
+
+ GsmStatus status = new GsmStatus();
+
+ // let's not rely on the order of the output, and simply loop through
+ // the line testing the regexp.
+ for (String line : result) {
+ Matcher m = sVoiceStatusRegexp.matcher(line);
+ if (m.matches()) {
+ // get the string value
+ String value = m.group(1);
+
+ // get the index from the list
+ status.voice = GsmMode.getEnum(value.toLowerCase(Locale.US));
+
+ // move on to next line.
+ continue;
+ }
+
+ m = sDataStatusRegexp.matcher(line);
+ if (m.matches()) {
+ // get the string value
+ String value = m.group(1);
+
+ // get the index from the list
+ status.data = GsmMode.getEnum(value.toLowerCase(Locale.US));
+
+ // move on to next line.
+ continue;
+ }
+ }
+
+ return status;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the GSM voice mode.
+ * @param mode the {@link GsmMode} value.
+ * @return RESULT_OK if success, an error String otherwise.
+ * @throws InvalidParameterException if mode is an invalid value.
+ */
+ public synchronized String setGsmVoiceMode(GsmMode mode) throws InvalidParameterException {
+ if (mode == GsmMode.UNKNOWN) {
+ throw new InvalidParameterException();
+ }
+
+ String command = String.format(COMMAND_GSM_VOICE, mode.getTag());
+ return processCommand(command);
+ }
+
+ /**
+ * Sets the GSM data mode.
+ * @param mode the {@link GsmMode} value
+ * @return {@link #RESULT_OK} if success, an error String otherwise.
+ * @throws InvalidParameterException if mode is an invalid value.
+ */
+ public synchronized String setGsmDataMode(GsmMode mode) throws InvalidParameterException {
+ if (mode == GsmMode.UNKNOWN) {
+ throw new InvalidParameterException();
+ }
+
+ String command = String.format(COMMAND_GSM_DATA, mode.getTag());
+ return processCommand(command);
+ }
+
+ /**
+ * Initiate an incoming call on the emulator.
+ * @param number a string representing the calling number.
+ * @return {@link #RESULT_OK} if success, an error String otherwise.
+ */
+ public synchronized String call(String number) {
+ String command = String.format(COMMAND_GSM_CALL, number);
+ return processCommand(command);
+ }
+
+ /**
+ * Cancels a current call.
+ * @param number the number of the call to cancel
+ * @return {@link #RESULT_OK} if success, an error String otherwise.
+ */
+ public synchronized String cancelCall(String number) {
+ String command = String.format(COMMAND_GSM_CANCEL_CALL, number);
+ return processCommand(command);
+ }
+
+ /**
+ * Sends an SMS to the emulator
+ * @param number The sender phone number
+ * @param message The SMS message. \ characters must be escaped. The carriage return is
+ * the 2 character sequence {'\', 'n' }
+ *
+ * @return {@link #RESULT_OK} if success, an error String otherwise.
+ */
+ public synchronized String sendSms(String number, String message) {
+ String command = String.format(COMMAND_SMS_SEND, number, message);
+ return processCommand(command);
+ }
+
+ /**
+ * Sets the network speed.
+ * @param selectionIndex The index in the {@link #NETWORK_SPEEDS} table.
+ * @return {@link #RESULT_OK} if success, an error String otherwise.
+ */
+ public synchronized String setNetworkSpeed(int selectionIndex) {
+ String command = String.format(COMMAND_NETWORK_SPEED, NETWORK_SPEEDS[selectionIndex]);
+ return processCommand(command);
+ }
+
+ /**
+ * Sets the network latency.
+ * @param selectionIndex The index in the {@link #NETWORK_LATENCIES} table.
+ * @return {@link #RESULT_OK} if success, an error String otherwise.
+ */
+ public synchronized String setNetworkLatency(int selectionIndex) {
+ String command = String.format(COMMAND_NETWORK_LATENCY, NETWORK_LATENCIES[selectionIndex]);
+ return processCommand(command);
+ }
+
+ public synchronized String sendLocation(double longitude, double latitude, double elevation) {
+
+ // need to make sure the string format uses dot and not comma
+ Formatter formatter = new Formatter(Locale.US);
+ try {
+ formatter.format(COMMAND_GPS, longitude, latitude, elevation);
+
+ return processCommand(formatter.toString());
+ } finally {
+ formatter.close();
+ }
+ }
+
+ /**
+ * Sends a command to the emulator console.
+ * @param command The command string. <b>MUST BE TERMINATED BY \n</b>.
+ * @return true if success
+ */
+ private boolean sendCommand(String command) {
+ boolean result = false;
+ try {
+ byte[] bCommand;
+ try {
+ bCommand = command.getBytes(DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ Log.w(LOG_TAG, "wrong encoding when sending " + command + " to " +
+ Integer.toString(mPort));
+ // wrong encoding...
+ return result;
+ }
+
+ // write the command
+ AdbHelper.write(mSocketChannel, bCommand, bCommand.length, DdmPreferences.getTimeOut());
+
+ result = true;
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "Exception sending command " + command + " to " +
+ Integer.toString(mPort));
+ return false;
+ } finally {
+ if (!result) {
+ // FIXME connection failed somehow, we need to disconnect the console.
+ removeConsole(mPort);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Sends a command to the emulator and parses its answer.
+ * @param command the command to send.
+ * @return {@link #RESULT_OK} if success, an error message otherwise.
+ */
+ private String processCommand(String command) {
+ if (sendCommand(command)) {
+ String[] result = readLines();
+
+ if (result != null && result.length > 0) {
+ Matcher m = RE_KO.matcher(result[result.length-1]);
+ if (m.matches()) {
+ return m.group(1);
+ }
+ return RESULT_OK;
+ }
+
+ return "Unable to communicate with the emulator";
+ }
+
+ return "Unable to send command to the emulator";
+ }
+
+ /**
+ * Reads line from the console socket. This call is blocking until we read the lines:
+ * <ul>
+ * <li>OK\r\n</li>
+ * <li>KO<msg>\r\n</li>
+ * </ul>
+ * @return the array of strings read from the emulator.
+ */
+ private String[] readLines() {
+ try {
+ ByteBuffer buf = ByteBuffer.wrap(mBuffer, 0, mBuffer.length);
+ int numWaits = 0;
+ boolean stop = false;
+
+ while (buf.position() != buf.limit() && !stop) {
+ int count;
+
+ count = mSocketChannel.read(buf);
+ if (count < 0) {
+ return null;
+ } else if (count == 0) {
+ if (numWaits * WAIT_TIME > STD_TIMEOUT) {
+ return null;
+ }
+ // non-blocking spin
+ try {
+ Thread.sleep(WAIT_TIME);
+ } catch (InterruptedException ie) {
+ }
+ numWaits++;
+ } else {
+ numWaits = 0;
+ }
+
+ // check the last few char aren't OK. For a valid message to test
+ // we need at least 4 bytes (OK/KO + \r\n)
+ if (buf.position() >= 4) {
+ int pos = buf.position();
+ if (endsWithOK(pos) || lastLineIsKO(pos)) {
+ stop = true;
+ }
+ }
+ }
+
+ String msg = new String(mBuffer, 0, buf.position(), DEFAULT_ENCODING);
+ return msg.split("\r\n"); //$NON-NLS-1$
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Exception reading lines for " + Integer.toString(mPort));
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if the 4 characters *before* the current position are "OK\r\n"
+ * @param currentPosition The current position
+ */
+ private boolean endsWithOK(int currentPosition) {
+ return mBuffer[currentPosition - 1] == '\n' &&
+ mBuffer[currentPosition - 2] == '\r' &&
+ mBuffer[currentPosition - 3] == 'K' &&
+ mBuffer[currentPosition - 4] == 'O';
+
+ }
+
+ /**
+ * Returns true if the last line starts with KO and is also terminated by \r\n
+ * @param currentPosition the current position
+ */
+ private boolean lastLineIsKO(int currentPosition) {
+ // first check that the last 2 characters are CRLF
+ if (mBuffer[currentPosition-1] != '\n' ||
+ mBuffer[currentPosition-2] != '\r') {
+ return false;
+ }
+
+ // now loop backward looking for the previous CRLF, or the beginning of the buffer
+ int i = 0;
+ for (i = currentPosition-3 ; i >= 0; i--) {
+ if (mBuffer[i] == '\n') {
+ // found \n!
+ if (i > 0 && mBuffer[i-1] == '\r') {
+ // found \r!
+ break;
+ }
+ }
+ }
+
+ // here it is either -1 if we reached the start of the buffer without finding
+ // a CRLF, or the position of \n. So in both case we look at the characters at i+1 and i+2
+ if (mBuffer[i+1] == 'K' && mBuffer[i+2] == 'O') {
+ // found error!
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the last line of the result does not start with KO
+ */
+ private boolean isValid(String[] result) {
+ if (result != null && result.length > 0) {
+ return !(RE_KO.matcher(result[result.length-1]).matches());
+ }
+ return false;
+ }
+
+ private int getLatencyIndex(String value) {
+ try {
+ // get the int value
+ int latency = Integer.parseInt(value);
+
+ // check for the speed from the index
+ for (int i = 0 ; i < MIN_LATENCIES.length; i++) {
+ if (MIN_LATENCIES[i] == latency) {
+ return i;
+ }
+ }
+ } catch (NumberFormatException e) {
+ // Do nothing, we'll just return -1.
+ }
+
+ return -1;
+ }
+
+ private int getSpeedIndex(String value) {
+ try {
+ // get the int value
+ int speed = Integer.parseInt(value);
+
+ // check for the speed from the index
+ for (int i = 0 ; i < DOWNLOAD_SPEEDS.length; i++) {
+ if (DOWNLOAD_SPEEDS[i] == speed) {
+ return i;
+ }
+ }
+ } catch (NumberFormatException e) {
+ // Do nothing, we'll just return -1.
+ }
+
+ return -1;
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/FileListingService.java b/ddmlib/src/main/java/com/android/ddmlib/FileListingService.java
new file mode 100644
index 0000000..2ce8b12
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/FileListingService.java
@@ -0,0 +1,860 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Provides {@link Device} side file listing service.
+ * <p/>To get an instance for a known {@link Device}, call {@link Device#getFileListingService()}.
+ */
+public final class FileListingService {
+
+ /** Pattern to find filenames that match "*.apk" */
+ private static final Pattern sApkPattern =
+ Pattern.compile(".*\\.apk", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+
+ private static final String PM_FULL_LISTING = "pm list packages -f"; //$NON-NLS-1$
+
+ /** Pattern to parse the output of the 'pm -lf' command.<br>
+ * The output format looks like:<br>
+ * /data/app/myapp.apk=com.mypackage.myapp */
+ private static final Pattern sPmPattern = Pattern.compile("^package:(.+?)=(.+)$"); //$NON-NLS-1$
+
+ /** Top level data folder. */
+ public static final String DIRECTORY_DATA = "data"; //$NON-NLS-1$
+ /** Top level sdcard folder. */
+ public static final String DIRECTORY_SDCARD = "sdcard"; //$NON-NLS-1$
+ /** Top level mount folder. */
+ public static final String DIRECTORY_MNT = "mnt"; //$NON-NLS-1$
+ /** Top level system folder. */
+ public static final String DIRECTORY_SYSTEM = "system"; //$NON-NLS-1$
+ /** Top level temp folder. */
+ public static final String DIRECTORY_TEMP = "tmp"; //$NON-NLS-1$
+ /** Application folder. */
+ public static final String DIRECTORY_APP = "app"; //$NON-NLS-1$
+
+ public static final long REFRESH_RATE = 5000L;
+ /**
+ * Refresh test has to be slightly lower for precision issue.
+ */
+ static final long REFRESH_TEST = (long)(REFRESH_RATE * .8);
+
+ /** Entry type: File */
+ public static final int TYPE_FILE = 0;
+ /** Entry type: Directory */
+ public static final int TYPE_DIRECTORY = 1;
+ /** Entry type: Directory Link */
+ public static final int TYPE_DIRECTORY_LINK = 2;
+ /** Entry type: Block */
+ public static final int TYPE_BLOCK = 3;
+ /** Entry type: Character */
+ public static final int TYPE_CHARACTER = 4;
+ /** Entry type: Link */
+ public static final int TYPE_LINK = 5;
+ /** Entry type: Socket */
+ public static final int TYPE_SOCKET = 6;
+ /** Entry type: FIFO */
+ public static final int TYPE_FIFO = 7;
+ /** Entry type: Other */
+ public static final int TYPE_OTHER = 8;
+
+ /** Device side file separator. */
+ public static final String FILE_SEPARATOR = "/"; //$NON-NLS-1$
+
+ private static final String FILE_ROOT = "/"; //$NON-NLS-1$
+
+
+ /**
+ * Regexp pattern to parse the result from ls.
+ */
+ public static final Pattern LS_L_PATTERN = Pattern.compile(
+ "^([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])\\s+" +
+ "(?:\\d+\\s+)?" + // toolbox ls (<=M) didn't have nlink; toybox's POSIX ls does.
+ "(\\S+)\\s+(\\S+)\\s+" +
+ "([\\d\\s,]*)\\s+(\\d{4}-\\d\\d-\\d\\d)\\s+(\\d\\d:\\d\\d)\\s+(.*)$"); //$NON-NLS-1$
+
+ public static final Pattern LS_LD_PATTERN = Pattern.compile(
+ "d[rwx-]{9}\\s+" + // We only use this to match directories!
+ "(\\d+\\s+)?" + // toolbox ls (<=M) didn't have nlink; toybox's POSIX ls does.
+ "\\S+\\s+\\S+\\s+" +
+ "(\\d+\\s+)?" + // toolbox ls (<=M) didn't have size; toybox's POSIX ls does.
+ "[0-9-]{10}\\s+\\d{2}:\\d{2}" +
+ ".*" + // toolbox ls (<=M) didn't have the filename in the "/sdcard/" case!
+ "$"); //$NON-NLS-1$
+
+ private Device mDevice;
+ private FileEntry mRoot;
+
+ // Used for locking so final.
+ final private ArrayList<Thread> mThreadList = new ArrayList<Thread>();
+
+ /**
+ * Represents an entry in a directory. This can be a file or a directory.
+ */
+ public static final class FileEntry {
+ /** Pattern to escape filenames for shell command consumption.
+ * This pattern identifies any special characters that need to be escaped with a
+ * backslash. */
+ private static final Pattern sEscapePattern = Pattern.compile(
+ "([\\\\()*+?\"'&#/\\s])"); //$NON-NLS-1$
+
+ /**
+ * Comparator object for FileEntry
+ */
+ private static Comparator<FileEntry> sEntryComparator = new Comparator<FileEntry>() {
+ @Override
+ public int compare(FileEntry o1, FileEntry o2) {
+ if (o1 instanceof FileEntry && o2 instanceof FileEntry) {
+ FileEntry fe1 = o1;
+ FileEntry fe2 = o2;
+ return fe1.name.compareTo(fe2.name);
+ }
+ return 0;
+ }
+ };
+
+ FileEntry parent;
+ String name;
+ String info;
+ String permissions;
+ String size;
+ String date;
+ String time;
+ String owner;
+ String group;
+ int type;
+ boolean isAppPackage;
+
+ boolean isRoot;
+
+ /**
+ * Indicates whether the entry content has been fetched yet, or not.
+ */
+ long fetchTime = 0;
+
+ final ArrayList<FileEntry> mChildren = new ArrayList<FileEntry>();
+
+ /**
+ * Creates a new file entry.
+ * @param parent parent entry or null if entry is root
+ * @param name name of the entry.
+ * @param type entry type. Can be one of the following: {@link FileListingService#TYPE_FILE},
+ * {@link FileListingService#TYPE_DIRECTORY}, {@link FileListingService#TYPE_OTHER}.
+ */
+ private FileEntry(FileEntry parent, String name, int type, boolean isRoot) {
+ this.parent = parent;
+ this.name = name;
+ this.type = type;
+ this.isRoot = isRoot;
+
+ checkAppPackageStatus();
+ }
+
+ /**
+ * Returns the name of the entry
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the size string of the entry, as returned by <code>ls</code>.
+ */
+ public String getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the size of the entry.
+ */
+ public int getSizeValue() {
+ return Integer.parseInt(size);
+ }
+
+ /**
+ * Returns the date string of the entry, as returned by <code>ls</code>.
+ */
+ public String getDate() {
+ return date;
+ }
+
+ /**
+ * Returns the time string of the entry, as returned by <code>ls</code>.
+ */
+ public String getTime() {
+ return time;
+ }
+
+ /**
+ * Returns the permission string of the entry, as returned by <code>ls</code>.
+ */
+ public String getPermissions() {
+ return permissions;
+ }
+
+ /**
+ * Returns the owner string of the entry, as returned by <code>ls</code>.
+ */
+ public String getOwner() {
+ return owner;
+ }
+
+ /**
+ * Returns the group owner of the entry, as returned by <code>ls</code>.
+ */
+ public String getGroup() {
+ return group;
+ }
+
+ /**
+ * Returns the extra info for the entry.
+ * <p/>For a link, it will be a description of the link.
+ * <p/>For an application apk file it will be the application package as returned
+ * by the Package Manager.
+ */
+ public String getInfo() {
+ return info;
+ }
+
+ /**
+ * Return the full path of the entry.
+ * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator.
+ */
+ public String getFullPath() {
+ if (isRoot) {
+ return FILE_ROOT;
+ }
+ StringBuilder pathBuilder = new StringBuilder();
+ fillPathBuilder(pathBuilder, false);
+
+ return pathBuilder.toString();
+ }
+
+ /**
+ * Return the fully escaped path of the entry. This path is safe to use in a
+ * shell command line.
+ * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator
+ */
+ public String getFullEscapedPath() {
+ StringBuilder pathBuilder = new StringBuilder();
+ fillPathBuilder(pathBuilder, true);
+
+ return pathBuilder.toString();
+ }
+
+ /**
+ * Returns the path as a list of segments.
+ */
+ public String[] getPathSegments() {
+ ArrayList<String> list = new ArrayList<String>();
+ fillPathSegments(list);
+
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Returns the Entry type as an int, which will match one of the TYPE_(...) constants
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Sets a new type.
+ */
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns if the entry is a folder or a link to a folder.
+ */
+ public boolean isDirectory() {
+ return type == TYPE_DIRECTORY || type == TYPE_DIRECTORY_LINK;
+ }
+
+ /**
+ * Returns the parent entry.
+ */
+ public FileEntry getParent() {
+ return parent;
+ }
+
+ /**
+ * Returns the cached children of the entry. This returns the cache created from calling
+ * <code>FileListingService.getChildren()</code>.
+ */
+ public FileEntry[] getCachedChildren() {
+ return mChildren.toArray(new FileEntry[mChildren.size()]);
+ }
+
+ /**
+ * Returns the child {@link FileEntry} matching the name.
+ * This uses the cached children list.
+ * @param name the name of the child to return.
+ * @return the FileEntry matching the name or null.
+ */
+ public FileEntry findChild(String name) {
+ for (FileEntry entry : mChildren) {
+ if (entry.name.equals(name)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether the entry is the root.
+ */
+ public boolean isRoot() {
+ return isRoot;
+ }
+
+ void addChild(FileEntry child) {
+ mChildren.add(child);
+ }
+
+ void setChildren(ArrayList<FileEntry> newChildren) {
+ mChildren.clear();
+ mChildren.addAll(newChildren);
+ }
+
+ boolean needFetch() {
+ if (fetchTime == 0) {
+ return true;
+ }
+ long current = System.currentTimeMillis();
+ return current - fetchTime > REFRESH_TEST;
+
+ }
+
+ /**
+ * Returns if the entry is a valid application package.
+ */
+ public boolean isApplicationPackage() {
+ return isAppPackage;
+ }
+
+ /**
+ * Returns if the file name is an application package name.
+ */
+ public boolean isAppFileName() {
+ Matcher m = sApkPattern.matcher(name);
+ return m.matches();
+ }
+
+ /**
+ * Recursively fills the pathBuilder with the full path
+ * @param pathBuilder a StringBuilder used to create the path.
+ * @param escapePath Whether the path need to be escaped for consumption by
+ * a shell command line.
+ */
+ protected void fillPathBuilder(StringBuilder pathBuilder, boolean escapePath) {
+ if (isRoot) {
+ return;
+ }
+
+ if (parent != null) {
+ parent.fillPathBuilder(pathBuilder, escapePath);
+ }
+ pathBuilder.append(FILE_SEPARATOR);
+ pathBuilder.append(escapePath ? escape(name) : name);
+ }
+
+ /**
+ * Recursively fills the segment list with the full path.
+ * @param list The list of segments to fill.
+ */
+ protected void fillPathSegments(ArrayList<String> list) {
+ if (isRoot) {
+ return;
+ }
+
+ if (parent != null) {
+ parent.fillPathSegments(list);
+ }
+
+ list.add(name);
+ }
+
+ /**
+ * Sets the internal app package status flag. This checks whether the entry is in an app
+ * directory like /data/app or /system/app
+ */
+ private void checkAppPackageStatus() {
+ isAppPackage = false;
+
+ String[] segments = getPathSegments();
+ if (type == TYPE_FILE && segments.length == 3 && isAppFileName()) {
+ isAppPackage = DIRECTORY_APP.equals(segments[1]) &&
+ (DIRECTORY_SYSTEM.equals(segments[0]) || DIRECTORY_DATA.equals(segments[0]));
+ }
+ }
+
+ /**
+ * Returns an escaped version of the entry name.
+ * @param entryName
+ */
+ public static String escape(String entryName) {
+ return sEscapePattern.matcher(entryName).replaceAll("\\\\$1"); //$NON-NLS-1$
+ }
+ }
+
+ private static class LsReceiver extends MultiLineReceiver {
+
+ private ArrayList<FileEntry> mEntryList;
+ private ArrayList<String> mLinkList;
+ private FileEntry[] mCurrentChildren;
+ private FileEntry mParentEntry;
+
+ /**
+ * Create an ls receiver/parser.
+ * @param currentChildren The list of current children. To prevent
+ * collapse during update, reusing the same FileEntry objects for
+ * files that were already there is paramount.
+ * @param entryList the list of new children to be filled by the
+ * receiver.
+ * @param linkList the list of link path to compute post ls, to figure
+ * out if the link pointed to a file or to a directory.
+ */
+ public LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList,
+ ArrayList<String> linkList) {
+ mParentEntry = parentEntry;
+ mCurrentChildren = parentEntry.getCachedChildren();
+ mEntryList = entryList;
+ mLinkList = linkList;
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ // no need to handle empty lines.
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ // run the line through the regexp
+ Matcher m = LS_L_PATTERN.matcher(line);
+ if (!m.matches()) {
+ continue;
+ }
+
+ // get the name
+ String name = m.group(7);
+
+ // get the rest of the groups
+ String permissions = m.group(1);
+ String owner = m.group(2);
+ String group = m.group(3);
+ String size = m.group(4);
+ String date = m.group(5);
+ String time = m.group(6);
+ String info = null;
+
+ // and the type
+ int objectType = TYPE_OTHER;
+ switch (permissions.charAt(0)) {
+ case '-' :
+ objectType = TYPE_FILE;
+ break;
+ case 'b' :
+ objectType = TYPE_BLOCK;
+ break;
+ case 'c' :
+ objectType = TYPE_CHARACTER;
+ break;
+ case 'd' :
+ objectType = TYPE_DIRECTORY;
+ break;
+ case 'l' :
+ objectType = TYPE_LINK;
+ break;
+ case 's' :
+ objectType = TYPE_SOCKET;
+ break;
+ case 'p' :
+ objectType = TYPE_FIFO;
+ break;
+ }
+
+
+ // now check what we may be linking to
+ if (objectType == TYPE_LINK) {
+ String[] segments = name.split("\\s->\\s"); //$NON-NLS-1$
+
+ // we should have 2 segments
+ if (segments.length == 2) {
+ // update the entry name to not contain the link
+ name = segments[0];
+
+ // and the link name
+ info = segments[1];
+
+ // now get the path to the link
+ String[] pathSegments = info.split(FILE_SEPARATOR);
+ if (pathSegments.length == 1) {
+ // the link is to something in the same directory,
+ // unless the link is ..
+ if ("..".equals(pathSegments[0])) { //$NON-NLS-1$
+ // set the type and we're done.
+ objectType = TYPE_DIRECTORY_LINK;
+ } else {
+ // either we found the object already
+ // or we'll find it later.
+ }
+ }
+ }
+
+ // add an arrow in front to specify it's a link.
+ info = "-> " + info; //$NON-NLS-1$;
+ }
+
+ // get the entry, either from an existing one, or a new one
+ FileEntry entry = getExistingEntry(name);
+ if (entry == null) {
+ entry = new FileEntry(mParentEntry, name, objectType, false /* isRoot */);
+ }
+
+ // add some misc info
+ entry.permissions = permissions;
+ entry.size = size;
+ entry.date = date;
+ entry.time = time;
+ entry.owner = owner;
+ entry.group = group;
+ if (objectType == TYPE_LINK) {
+ entry.info = info;
+ }
+
+ mEntryList.add(entry);
+ }
+ }
+
+ /**
+ * Queries for an already existing Entry per name
+ * @param name the name of the entry
+ * @return the existing FileEntry or null if no entry with a matching
+ * name exists.
+ */
+ private FileEntry getExistingEntry(String name) {
+ for (int i = 0 ; i < mCurrentChildren.length; i++) {
+ FileEntry e = mCurrentChildren[i];
+
+ // since we're going to "erase" the one we use, we need to
+ // check that the item is not null.
+ if (e != null) {
+ // compare per name, case-sensitive.
+ if (name.equals(e.name)) {
+ // erase from the list
+ mCurrentChildren[i] = null;
+
+ // and return the object
+ return e;
+ }
+ }
+ }
+
+ // couldn't find any matching object, return null
+ return null;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ /**
+ * Determine if any symlinks in the <code entries> list are links-to-directories, and if so
+ * mark them as such. This allows us to traverse them properly later on.
+ */
+ public void finishLinks(IDevice device, ArrayList<FileEntry> entries)
+ throws TimeoutException, AdbCommandRejectedException,
+ ShellCommandUnresponsiveException, IOException {
+ final int[] nLines = {0};
+ MultiLineReceiver receiver = new MultiLineReceiver() {
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ Matcher m = LS_LD_PATTERN.matcher(line);
+ if (m.matches()) {
+ nLines[0]++;
+ }
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ };
+
+ for (FileEntry entry : entries) {
+ if (entry.getType() != TYPE_LINK) continue;
+
+ // We simply need to determine whether the referent is a directory or not.
+ // We do this by running `ls -ld ${link}/`. If the referent exists and is a
+ // directory, we'll see the normal directory listing. Otherwise, we'll see an
+ // error of some sort.
+ nLines[0] = 0;
+
+ final String command = String.format("ls -l -d %s%s", entry.getFullEscapedPath(),
+ FILE_SEPARATOR);
+
+ device.executeShellCommand(command, receiver);
+
+ if (nLines[0] > 0) {
+ // We saw lines matching the directory pattern, so it's a directory!
+ entry.setType(TYPE_DIRECTORY_LINK);
+ }
+ }
+ }
+ }
+
+ /**
+ * Classes which implement this interface provide a method that deals with asynchronous
+ * result from <code>ls</code> command on the device.
+ *
+ * @see FileListingService#getChildren(com.android.ddmlib.FileListingService.FileEntry, boolean, com.android.ddmlib.FileListingService.IListingReceiver)
+ */
+ public interface IListingReceiver {
+ void setChildren(FileEntry entry, FileEntry[] children);
+
+ void refreshEntry(FileEntry entry);
+ }
+
+ /**
+ * Creates a File Listing Service for a specified {@link Device}.
+ * @param device The Device the service is connected to.
+ */
+ FileListingService(Device device) {
+ mDevice = device;
+ }
+
+ /**
+ * Returns the root element.
+ * @return the {@link FileEntry} object representing the root element or
+ * <code>null</code> if the device is invalid.
+ */
+ public FileEntry getRoot() {
+ if (mDevice != null) {
+ if (mRoot == null) {
+ mRoot = new FileEntry(null /* parent */, "" /* name */, TYPE_DIRECTORY,
+ true /* isRoot */);
+ }
+
+ return mRoot;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the children of a {@link FileEntry}.
+ * <p/>
+ * This method supports a cache mechanism and synchronous and asynchronous modes.
+ * <p/>
+ * If <var>receiver</var> is <code>null</code>, the device side <code>ls</code>
+ * command is done synchronously, and the method will return upon completion of the command.<br>
+ * If <var>receiver</var> is non <code>null</code>, the command is launched is a separate
+ * thread and upon completion, the receiver will be notified of the result.
+ * <p/>
+ * The result for each <code>ls</code> command is cached in the parent
+ * <code>FileEntry</code>. <var>useCache</var> allows usage of this cache, but only if the
+ * cache is valid. The cache is valid only for {@link FileListingService#REFRESH_RATE} ms.
+ * After that a new <code>ls</code> command is always executed.
+ * <p/>
+ * If the cache is valid and <code>useCache == true</code>, the method will always simply
+ * return the value of the cache, whether a {@link IListingReceiver} has been provided or not.
+ *
+ * @param entry The parent entry.
+ * @param useCache A flag to use the cache or to force a new ls command.
+ * @param receiver A receiver for asynchronous calls.
+ * @return The list of children or <code>null</code> for asynchronous calls.
+ *
+ * @see FileEntry#getCachedChildren()
+ */
+ public FileEntry[] getChildren(final FileEntry entry, boolean useCache,
+ final IListingReceiver receiver) {
+ // first thing we do is check the cache, and if we already have a recent
+ // enough children list, we just return that.
+ if (useCache && !entry.needFetch()) {
+ return entry.getCachedChildren();
+ }
+
+ // if there's no receiver, then this is a synchronous call, and we
+ // return the result of ls
+ if (receiver == null) {
+ doLs(entry);
+ return entry.getCachedChildren();
+ }
+
+ // this is a asynchronous call.
+ // we launch a thread that will do ls and give the listing
+ // to the receiver
+ Thread t = new Thread("ls " + entry.getFullPath()) { //$NON-NLS-1$
+ @Override
+ public void run() {
+ doLs(entry);
+
+ receiver.setChildren(entry, entry.getCachedChildren());
+
+ final FileEntry[] children = entry.getCachedChildren();
+ if (children.length > 0 && children[0].isApplicationPackage()) {
+ final HashMap<String, FileEntry> map = new HashMap<String, FileEntry>();
+
+ for (FileEntry child : children) {
+ String path = child.getFullPath();
+ map.put(path, child);
+ }
+
+ // call pm.
+ String command = PM_FULL_LISTING;
+ try {
+ mDevice.executeShellCommand(command, new MultiLineReceiver() {
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ if (!line.isEmpty()) {
+ // get the filepath and package from the line
+ Matcher m = sPmPattern.matcher(line);
+ if (m.matches()) {
+ // get the children with that path
+ FileEntry entry = map.get(m.group(1));
+ if (entry != null) {
+ entry.info = m.group(2);
+ receiver.refreshEntry(entry);
+ }
+ }
+ }
+ }
+ }
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ });
+ } catch (Exception e) {
+ // adb failed somehow, we do nothing.
+ }
+ }
+
+
+ // if another thread is pending, launch it
+ synchronized (mThreadList) {
+ // first remove ourselves from the list
+ mThreadList.remove(this);
+
+ // then launch the next one if applicable.
+ if (!mThreadList.isEmpty()) {
+ Thread t = mThreadList.get(0);
+ t.start();
+ }
+ }
+ }
+ };
+
+ // we don't want to run multiple ls on the device at the same time, so we
+ // store the thread in a list and launch it only if there's no other thread running.
+ // the thread will launch the next one once it's done.
+ synchronized (mThreadList) {
+ // add to the list
+ mThreadList.add(t);
+
+ // if it's the only one, launch it.
+ if (mThreadList.size() == 1) {
+ t.start();
+ }
+ }
+
+ // and we return null.
+ return null;
+ }
+
+ /**
+ * Returns the children of a {@link FileEntry}.
+ * <p/>
+ * This method is the explicit synchronous version of
+ * {@link #getChildren(FileEntry, boolean, IListingReceiver)}. It is roughly equivalent to
+ * calling
+ * getChildren(FileEntry, false, null)
+ *
+ * @param entry The parent entry.
+ * @return The list of children
+ * @throws TimeoutException in case of timeout on the connection when sending the command.
+ * @throws AdbCommandRejectedException if adb rejects the command.
+ * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
+ * for a period longer than <var>maxTimeToOutputResponse</var>.
+ * @throws IOException in case of I/O error on the connection.
+ */
+ public FileEntry[] getChildrenSync(final FileEntry entry) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ doLsAndThrow(entry);
+ return entry.getCachedChildren();
+ }
+
+ private void doLs(FileEntry entry) {
+ try {
+ doLsAndThrow(entry);
+ } catch (Exception e) {
+ // do nothing
+ }
+ }
+
+ private void doLsAndThrow(FileEntry entry) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ // create a list that will receive the list of the entries
+ ArrayList<FileEntry> entryList = new ArrayList<FileEntry>();
+
+ // create a list that will receive the link to compute post ls;
+ ArrayList<String> linkList = new ArrayList<String>();
+
+ try {
+ // create the command
+ String command = "ls -l " + entry.getFullEscapedPath(); //$NON-NLS-1$
+ if (entry.isDirectory()) {
+ // If we expect a file to behave like a directory, we should stick a "/" at the end.
+ // This is a good habit, and is mandatory for symlinks-to-directories, which will
+ // otherwise behave like symlinks.
+ command += FILE_SEPARATOR;
+ }
+
+ // create the receiver object that will parse the result from ls
+ LsReceiver receiver = new LsReceiver(entry, entryList, linkList);
+
+ // call ls.
+ mDevice.executeShellCommand(command, receiver);
+
+ // finish the process of the receiver to handle links
+ receiver.finishLinks(mDevice, entryList);
+ } finally {
+ // at this point we need to refresh the viewer
+ entry.fetchTime = System.currentTimeMillis();
+
+ // sort the children and set them as the new children
+ Collections.sort(entryList, FileEntry.sEntryComparator);
+ entry.setChildren(entryList);
+ }
+ }
+
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java b/ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java
rename to ddmlib/src/main/java/com/android/ddmlib/HandleAppName.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleExit.java b/ddmlib/src/main/java/com/android/ddmlib/HandleExit.java
new file mode 100644
index 0000000..b937f8e
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleExit.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Submit an exit request.
+ */
+final class HandleExit extends ChunkHandler {
+
+ public static final int CHUNK_EXIT = type("EXIT");
+
+ private static final HandleExit mInst = new HandleExit();
+
+
+ private HandleExit() {}
+
+ /**
+ * Register for the packets we expect to get from the client.
+ */
+ public static void register(MonitorThread mt) {}
+
+ /**
+ * Client is ready.
+ */
+ @Override
+ public void clientReady(Client client) throws IOException {}
+
+ /**
+ * Client went away.
+ */
+ @Override
+ public void clientDisconnected(Client client) {}
+
+ /**
+ * Chunk handler entry point.
+ */
+ @Override
+ public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
+ handleUnknownChunk(client, type, data, isReply, msgId);
+ }
+
+ /**
+ * Send an EXIT request to the client.
+ */
+ public static void sendEXIT(Client client, int status)
+ throws IOException
+ {
+ ByteBuffer rawBuf = allocBuffer(4);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(status);
+
+ finishChunkPacket(packet, CHUNK_EXIT, buf.position());
+ Log.d("ddm-exit", "Sending " + name(CHUNK_EXIT) + ": " + status);
+ client.send(packet, mInst);
+ }
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java b/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java
new file mode 100644
index 0000000..5afaa3c
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleHeap.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.ddmlib.ClientData.AllocationTrackingStatus;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle heap status updates.
+ */
+final class HandleHeap extends ChunkHandler {
+
+ public static final int CHUNK_HPIF = type("HPIF");
+ public static final int CHUNK_HPST = type("HPST");
+ public static final int CHUNK_HPEN = type("HPEN");
+ public static final int CHUNK_HPSG = type("HPSG");
+ public static final int CHUNK_HPGC = type("HPGC");
+ public static final int CHUNK_HPDU = type("HPDU");
+ public static final int CHUNK_HPDS = type("HPDS");
+ public static final int CHUNK_REAE = type("REAE");
+ public static final int CHUNK_REAQ = type("REAQ");
+ public static final int CHUNK_REAL = type("REAL");
+
+ // args to sendHPSG
+ public static final int WHEN_DISABLE = 0;
+ public static final int WHEN_GC = 1;
+ public static final int WHAT_MERGE = 0; // merge adjacent objects
+ public static final int WHAT_OBJ = 1; // keep objects distinct
+
+ // args to sendHPIF
+ public static final int HPIF_WHEN_NEVER = 0;
+ public static final int HPIF_WHEN_NOW = 1;
+ public static final int HPIF_WHEN_NEXT_GC = 2;
+ public static final int HPIF_WHEN_EVERY_GC = 3;
+
+ private static final HandleHeap mInst = new HandleHeap();
+
+ private HandleHeap() {}
+
+ /**
+ * Register for the packets we expect to get from the client.
+ */
+ public static void register(MonitorThread mt) {
+ mt.registerChunkHandler(CHUNK_HPIF, mInst);
+ mt.registerChunkHandler(CHUNK_HPST, mInst);
+ mt.registerChunkHandler(CHUNK_HPEN, mInst);
+ mt.registerChunkHandler(CHUNK_HPSG, mInst);
+ mt.registerChunkHandler(CHUNK_HPDS, mInst);
+ mt.registerChunkHandler(CHUNK_REAQ, mInst);
+ mt.registerChunkHandler(CHUNK_REAL, mInst);
+ }
+
+ /**
+ * Client is ready.
+ */
+ @Override
+ public void clientReady(Client client) throws IOException {
+ client.initializeHeapUpdateStatus();
+ }
+
+ /**
+ * Client went away.
+ */
+ @Override
+ public void clientDisconnected(Client client) {}
+
+ /**
+ * Chunk handler entry point.
+ */
+ @Override
+ public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
+ Log.d("ddm-heap", "handling " + ChunkHandler.name(type));
+
+ if (type == CHUNK_HPIF) {
+ handleHPIF(client, data);
+ } else if (type == CHUNK_HPST) {
+ handleHPST(client, data);
+ } else if (type == CHUNK_HPEN) {
+ handleHPEN(client, data);
+ } else if (type == CHUNK_HPSG) {
+ handleHPSG(client, data);
+ } else if (type == CHUNK_HPDU) {
+ handleHPDU(client, data);
+ } else if (type == CHUNK_HPDS) {
+ handleHPDS(client, data);
+ } else if (type == CHUNK_REAQ) {
+ handleREAQ(client, data);
+ } else if (type == CHUNK_REAL) {
+ handleREAL(client, data);
+ } else {
+ handleUnknownChunk(client, type, data, isReply, msgId);
+ }
+ }
+
+ /*
+ * Handle a heap info message.
+ */
+ private void handleHPIF(Client client, ByteBuffer data) {
+ Log.d("ddm-heap", "HPIF!");
+ try {
+ int numHeaps = data.getInt();
+
+ for (int i = 0; i < numHeaps; i++) {
+ int heapId = data.getInt();
+ long timeStamp = data.getLong();
+ byte reason = data.get();
+ long maxHeapSize = (long)data.getInt() & 0x00ffffffff;
+ long heapSize = (long)data.getInt() & 0x00ffffffff;
+ long bytesAllocated = (long)data.getInt() & 0x00ffffffff;
+ long objectsAllocated = (long)data.getInt() & 0x00ffffffff;
+
+ client.getClientData().setHeapInfo(heapId, maxHeapSize,
+ heapSize, bytesAllocated, objectsAllocated, timeStamp, reason);
+ client.update(Client.CHANGE_HEAP_DATA);
+ }
+ } catch (BufferUnderflowException ex) {
+ Log.w("ddm-heap", "malformed HPIF chunk from client");
+ }
+ }
+
+ /**
+ * Send an HPIF (HeaP InFo) request to the client.
+ */
+ public static void sendHPIF(Client client, int when) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(1);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.put((byte)when);
+
+ finishChunkPacket(packet, CHUNK_HPIF, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_HPIF) + ": when=" + when);
+ client.send(packet, mInst);
+ }
+
+ /*
+ * Handle a heap segment series start message.
+ */
+ private void handleHPST(Client client, ByteBuffer data) {
+ /* Clear out any data that's sitting around to
+ * get ready for the chunks that are about to come.
+ */
+//xxx todo: only clear data that belongs to the heap mentioned in <data>.
+ client.getClientData().getVmHeapData().clearHeapData();
+ }
+
+ /*
+ * Handle a heap segment series end message.
+ */
+ private void handleHPEN(Client client, ByteBuffer data) {
+ /* Let the UI know that we've received all of the
+ * data for this heap.
+ */
+//xxx todo: only seal data that belongs to the heap mentioned in <data>.
+ client.getClientData().getVmHeapData().sealHeapData();
+ client.update(Client.CHANGE_HEAP_DATA);
+ }
+
+ /*
+ * Handle a heap segment message.
+ */
+ private void handleHPSG(Client client, ByteBuffer data) {
+ byte dataCopy[] = new byte[data.limit()];
+ data.rewind();
+ data.get(dataCopy);
+ data = ByteBuffer.wrap(dataCopy);
+ client.getClientData().getVmHeapData().addHeapData(data);
+//xxx todo: add to the heap mentioned in <data>
+ }
+
+ /**
+ * Sends an HPSG (HeaP SeGment) request to the client.
+ */
+ public static void sendHPSG(Client client, int when, int what)
+ throws IOException {
+
+ ByteBuffer rawBuf = allocBuffer(2);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.put((byte)when);
+ buf.put((byte)what);
+
+ finishChunkPacket(packet, CHUNK_HPSG, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_HPSG) + ": when="
+ + when + ", what=" + what);
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Sends an HPGC request to the client.
+ */
+ public static void sendHPGC(Client client)
+ throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_HPGC, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_HPGC));
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Sends an HPDU request to the client.
+ *
+ * We will get an HPDU response when the heap dump has completed. On
+ * failure we get a generic failure response.
+ *
+ * @param fileName name of output file (on device)
+ */
+ public static void sendHPDU(Client client, String fileName)
+ throws IOException {
+ ByteBuffer rawBuf = allocBuffer(4 + fileName.length() * 2);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(fileName.length());
+ ByteBufferUtil.putString(buf, fileName);
+
+ finishChunkPacket(packet, CHUNK_HPDU, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_HPDU) + " '" + fileName +"'");
+ client.send(packet, mInst);
+ client.getClientData().setPendingHprofDump(fileName);
+ }
+
+ /**
+ * Sends an HPDS request to the client.
+ *
+ * We will get an HPDS response when the heap dump has completed. On
+ * failure we get a generic failure response.
+ *
+ * This is more expensive for the device than HPDU, because the entire
+ * heap dump is held in RAM instead of spooled out to a temp file. On
+ * the other hand, permission to write to /sdcard is not required.
+ *
+ * @param fileName name of output file (on device)
+ */
+ public static void sendHPDS(Client client)
+ throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ finishChunkPacket(packet, CHUNK_HPDS, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_HPDS));
+ client.send(packet, mInst);
+ }
+
+ /*
+ * Handle notification of completion of a HeaP DUmp.
+ */
+ private void handleHPDU(Client client, ByteBuffer data) {
+ byte result;
+
+ // get the filename and make the client not have pending HPROF dump anymore.
+ String filename = client.getClientData().getPendingHprofDump();
+ client.getClientData().setPendingHprofDump(null);
+
+ // get the dump result
+ result = data.get();
+
+ // get the app-level handler for HPROF dump
+ IHprofDumpHandler handler = ClientData.getHprofDumpHandler();
+ if (result == 0) {
+ if (handler != null) {
+ handler.onSuccess(filename, client);
+ }
+ client.getClientData().setHprofData(filename);
+ Log.d("ddm-heap", "Heap dump request has finished");
+ } else {
+ if (handler != null) {
+ handler.onEndFailure(client, null);
+ }
+ client.getClientData().clearHprofData();
+ Log.w("ddm-heap", "Heap dump request failed (check device log)");
+ }
+ client.update(Client.CHANGE_HPROF);
+ client.getClientData().clearHprofData();
+ }
+
+ /*
+ * Handle HeaP Dump Streaming response. "data" contains the full
+ * hprof dump.
+ */
+ private void handleHPDS(Client client, ByteBuffer data) {
+ byte[] stuff = new byte[data.capacity()];
+ data.get(stuff, 0, stuff.length);
+
+ Log.d("ddm-hprof", "got hprof file, size: " + data.capacity() + " bytes");
+ client.getClientData().setHprofData(stuff);
+ IHprofDumpHandler handler = ClientData.getHprofDumpHandler();
+ if (handler != null) {
+ handler.onSuccess(stuff, client);
+ }
+ client.update(Client.CHANGE_HPROF);
+ client.getClientData().clearHprofData();
+ }
+
+ /**
+ * Sends a REAE (REcent Allocation Enable) request to the client.
+ */
+ public static void sendREAE(Client client, boolean enable)
+ throws IOException {
+ ByteBuffer rawBuf = allocBuffer(1);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.put((byte) (enable ? 1 : 0));
+
+ finishChunkPacket(packet, CHUNK_REAE, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_REAE) + ": " + enable);
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Sends a REAQ (REcent Allocation Query) request to the client.
+ */
+ public static void sendREAQ(Client client)
+ throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_REAQ, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_REAQ));
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Sends a REAL (REcent ALlocation) request to the client.
+ */
+ public static void sendREAL(Client client)
+ throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_REAL, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_REAL));
+ client.send(packet, mInst);
+ }
+
+ /*
+ * Handle the response from our REcent Allocation Query message.
+ */
+ private void handleREAQ(Client client, ByteBuffer data) {
+ boolean enabled;
+
+ enabled = (data.get() != 0);
+ Log.d("ddm-heap", "REAQ says: enabled=" + enabled);
+
+ client.getClientData().setAllocationStatus(enabled ? AllocationTrackingStatus.ON : AllocationTrackingStatus.OFF);
+ client.update(Client.CHANGE_HEAP_ALLOCATION_STATUS);
+ }
+
+ /*
+ * Handle a REcent ALlocation response.
+ */
+ private void handleREAL(Client client, ByteBuffer data) {
+ Log.e("ddm-heap", "*** Received " + name(CHUNK_REAL));
+
+ byte[] stuff = new byte[data.capacity()];
+ data.get(stuff, 0, stuff.length);
+ data.rewind();
+
+ // Work with legacy global handler.
+ ClientData.IAllocationTrackingHandler handler = ClientData.getAllocationTrackingHandler();
+ if (handler != null) {
+ Log.d("ddm-prof", "got allocations file, size: " + stuff.length + " bytes");
+ handler.onSuccess(stuff, client);
+ }
+
+ client.getClientData().setAllocationsData(stuff);
+ client.update(Client.CHANGE_HEAP_ALLOCATIONS);
+
+ // Clean up after everything has been notified (synchronously).
+ client.getClientData().setAllocationsData(null);
+ }
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java b/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java
new file mode 100644
index 0000000..272d636
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleHello.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle the "hello" chunk (HELO) and feature discovery.
+ */
+final class HandleHello extends ChunkHandler {
+
+ public static final int CHUNK_HELO = ChunkHandler.type("HELO");
+ public static final int CHUNK_FEAT = ChunkHandler.type("FEAT");
+
+ private static final HandleHello mInst = new HandleHello();
+
+ private HandleHello() {}
+
+ /**
+ * Register for the packets we expect to get from the client.
+ */
+ public static void register(MonitorThread mt) {
+ mt.registerChunkHandler(CHUNK_HELO, mInst);
+ }
+
+ /**
+ * Client is ready.
+ */
+ @Override
+ public void clientReady(Client client) throws IOException {
+ Log.d("ddm-hello", "Now ready: " + client);
+ }
+
+ /**
+ * Client went away.
+ */
+ @Override
+ public void clientDisconnected(Client client) {
+ Log.d("ddm-hello", "Now disconnected: " + client);
+ }
+
+ /**
+ * Sends HELLO-type commands to the VM after a good handshake.
+ * @param client
+ * @param serverProtocolVersion
+ * @throws IOException
+ */
+ public static void sendHelloCommands(Client client, int serverProtocolVersion)
+ throws IOException {
+ sendHELO(client, serverProtocolVersion);
+ sendFEAT(client);
+ HandleProfiling.sendMPRQ(client);
+ }
+
+ /**
+ * Chunk handler entry point.
+ */
+ @Override
+ public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
+
+ Log.d("ddm-hello", "handling " + ChunkHandler.name(type));
+
+ if (type == CHUNK_HELO) {
+ assert isReply;
+ handleHELO(client, data);
+ } else if (type == CHUNK_FEAT) {
+ handleFEAT(client, data);
+ } else {
+ handleUnknownChunk(client, type, data, isReply, msgId);
+ }
+ }
+
+ /*
+ * Handle a reply to our HELO message.
+ */
+ private static void handleHELO(Client client, ByteBuffer data) {
+ int version, pid, vmIdentLen, appNameLen;
+ String vmIdent, appName;
+
+ version = data.getInt();
+ pid = data.getInt();
+ vmIdentLen = data.getInt();
+ appNameLen = data.getInt();
+
+ vmIdent = ByteBufferUtil.getString(data, vmIdentLen);
+ appName = ByteBufferUtil.getString(data, appNameLen);
+
+ // Newer devices send user id in the APNM packet.
+ int userId = -1;
+ boolean validUserId = false;
+ if (data.hasRemaining()) {
+ try {
+ userId = data.getInt();
+ validUserId = true;
+ } catch (BufferUnderflowException e) {
+ // five integers + two utf-16 strings
+ int expectedPacketLength = 20 + appNameLen * 2 + vmIdentLen * 2;
+
+ Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve user id.");
+ Log.e("ddm-hello", "Actual chunk length: " + data.capacity());
+ Log.e("ddm-hello", "Expected chunk length: " + expectedPacketLength);
+ }
+ }
+
+ // check if the VM has reported information about the ABI
+ boolean validAbi = false;
+ String abi = null;
+ if (data.hasRemaining()) {
+ try {
+ int abiLength = data.getInt();
+ abi = ByteBufferUtil.getString(data, abiLength);
+ validAbi = true;
+ } catch (BufferUnderflowException e) {
+ Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve ABI.");
+ }
+ }
+
+ boolean hasJvmFlags = false;
+ String jvmFlags = null;
+ if (data.hasRemaining()) {
+ try {
+ int jvmFlagsLength = data.getInt();
+ jvmFlags = ByteBufferUtil.getString(data, jvmFlagsLength);
+ hasJvmFlags = true;
+ } catch (BufferUnderflowException e) {
+ Log.e("ddm-hello", "Insufficient data in HELO chunk to retrieve JVM flags");
+ }
+ }
+
+ Log.d("ddm-hello", "HELO: v=" + version + ", pid=" + pid
+ + ", vm='" + vmIdent + "', app='" + appName + "'");
+
+ ClientData cd = client.getClientData();
+
+ if (cd.getPid() == pid) {
+ cd.setVmIdentifier(vmIdent);
+ cd.setClientDescription(appName);
+ cd.isDdmAware(true);
+
+ if (validUserId) {
+ cd.setUserId(userId);
+ }
+
+ if (validAbi) {
+ cd.setAbi(abi);
+ }
+
+ if (hasJvmFlags) {
+ cd.setJvmFlags(jvmFlags);
+ }
+ } else {
+ Log.e("ddm-hello", "Received pid (" + pid + ") does not match client pid ("
+ + cd.getPid() + ")");
+ }
+
+ client = checkDebuggerPortForAppName(client, appName);
+
+ if (client != null) {
+ client.update(Client.CHANGE_NAME);
+ }
+ }
+
+
+ /**
+ * Send a HELO request to the client.
+ */
+ public static void sendHELO(Client client, int serverProtocolVersion)
+ throws IOException
+ {
+ ByteBuffer rawBuf = allocBuffer(4);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(serverProtocolVersion);
+
+ finishChunkPacket(packet, CHUNK_HELO, buf.position());
+ Log.d("ddm-hello", "Sending " + name(CHUNK_HELO)
+ + " ID=0x" + Integer.toHexString(packet.getId()));
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Handle a reply to our FEAT request.
+ */
+ private static void handleFEAT(Client client, ByteBuffer data) {
+ int featureCount;
+ int i;
+
+ featureCount = data.getInt();
+ for (i = 0; i < featureCount; i++) {
+ int len = data.getInt();
+ String feature = ByteBufferUtil.getString(data, len);
+ client.getClientData().addFeature(feature);
+
+ Log.d("ddm-hello", "Feature: " + feature);
+ }
+ }
+
+ /**
+ * Send a FEAT request to the client.
+ */
+ public static void sendFEAT(Client client) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_FEAT, buf.position());
+ Log.d("ddm-heap", "Sending " + name(CHUNK_FEAT));
+ client.send(packet, mInst);
+ }
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleNativeHeap.java b/ddmlib/src/main/java/com/android/ddmlib/HandleNativeHeap.java
new file mode 100644
index 0000000..f9fb0f5
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleNativeHeap.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Handle thread status updates.
+ */
+final class HandleNativeHeap extends ChunkHandler {
+
+ public static final int CHUNK_NHGT = type("NHGT"); //$NON-NLS-1$
+ public static final int CHUNK_NHSG = type("NHSG"); //$NON-NLS-1$
+ public static final int CHUNK_NHST = type("NHST"); //$NON-NLS-1$
+ public static final int CHUNK_NHEN = type("NHEN"); //$NON-NLS-1$
+
+ private static final HandleNativeHeap mInst = new HandleNativeHeap();
+
+ /**
+ * Handle getting different sized size_t and pointer reads.
+ */
+ abstract class NativeBuffer {
+ public NativeBuffer(ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ public abstract int getSizeT();
+ public abstract long getPtr();
+
+ protected ByteBuffer mBuffer;
+ }
+
+ /**
+ * This class treats size_t and pointer values as 32 bit.
+ */
+ final class NativeBuffer32 extends NativeBuffer {
+ public NativeBuffer32(ByteBuffer buffer) {
+ super(buffer);
+ }
+
+ @Override
+ public int getSizeT() {
+ return mBuffer.getInt();
+ }
+ @Override
+ public long getPtr() {
+ return (long)mBuffer.getInt() & 0x00000000ffffffffL;
+ }
+ }
+
+ /**
+ * This class treats size_t and pointer values as 64 bit.
+ */
+ final class NativeBuffer64 extends NativeBuffer {
+ public NativeBuffer64(ByteBuffer buffer) {
+ super(buffer);
+ }
+
+ @Override
+ public int getSizeT() {
+ return (int)mBuffer.getLong();
+ }
+ @Override
+ public long getPtr() {
+ return mBuffer.getLong();
+ }
+ }
+
+ private HandleNativeHeap() {
+ }
+
+
+ /**
+ * Register for the packets we expect to get from the client.
+ */
+ public static void register(MonitorThread mt) {
+ mt.registerChunkHandler(CHUNK_NHGT, mInst);
+ mt.registerChunkHandler(CHUNK_NHSG, mInst);
+ mt.registerChunkHandler(CHUNK_NHST, mInst);
+ mt.registerChunkHandler(CHUNK_NHEN, mInst);
+ }
+
+ /**
+ * Client is ready.
+ */
+ @Override
+ public void clientReady(Client client) throws IOException {}
+
+ /**
+ * Client went away.
+ */
+ @Override
+ public void clientDisconnected(Client client) {}
+
+ /**
+ * Chunk handler entry point.
+ */
+ @Override
+ public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
+
+ Log.d("ddm-nativeheap", "handling " + ChunkHandler.name(type));
+
+ if (type == CHUNK_NHGT) {
+ handleNHGT(client, data);
+ } else if (type == CHUNK_NHST) {
+ // start chunk before any NHSG chunk(s)
+ client.getClientData().getNativeHeapData().clearHeapData();
+ } else if (type == CHUNK_NHEN) {
+ // end chunk after NHSG chunk(s)
+ client.getClientData().getNativeHeapData().sealHeapData();
+ } else if (type == CHUNK_NHSG) {
+ handleNHSG(client, data);
+ } else {
+ handleUnknownChunk(client, type, data, isReply, msgId);
+ }
+
+ client.update(Client.CHANGE_NATIVE_HEAP_DATA);
+ }
+
+ /**
+ * Send an NHGT (Native Thread GeT) request to the client.
+ */
+ public static void sendNHGT(Client client) throws IOException {
+
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data in request message
+
+ finishChunkPacket(packet, CHUNK_NHGT, buf.position());
+ Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHGT));
+ client.send(packet, mInst);
+
+ rawBuf = allocBuffer(2);
+ packet = new JdwpPacket(rawBuf);
+ buf = getChunkDataBuf(rawBuf);
+
+ buf.put((byte)HandleHeap.WHEN_DISABLE);
+ buf.put((byte)HandleHeap.WHAT_OBJ);
+
+ finishChunkPacket(packet, CHUNK_NHSG, buf.position());
+ Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHSG));
+ client.send(packet, mInst);
+ }
+
+ /*
+ * Handle our native heap data.
+ */
+ private void handleNHGT(Client client, ByteBuffer data) {
+ ClientData clientData = client.getClientData();
+
+ Log.d("ddm-nativeheap", "NHGT: " + data.limit() + " bytes");
+
+ data.order(ByteOrder.LITTLE_ENDIAN);
+
+ // There are two supported header formats.
+ //
+ // The original version of the header for 32 bit processes:
+ //
+ // uint32_t mapSize;
+ // uint32_t mapSize;
+ // uint32_t allocSize;
+ // uint32_t allocInfoSize;
+ // uint32_t totalMemory;
+ // uint32_t backtrace_size;
+ //
+ // The new header which includes a signature and pointer size:
+ //
+ // uint32_t signature; (Which is always 0x812345dd)
+ // uint16_t version; (Only version 2 of the new format supported)
+ // uint16_t pointerSize; (Size in bytes of size_t/pointer values)
+ // size_t mapSize;
+ // size_t allocSize;
+ // size_t allocInfoSize;
+ // size_t totalMemory;
+ // size_t backtrace_size;
+ //
+ // If the signature doesn't match, then the code uses the original
+ // header format. If the signature matches, then use the new
+ // header format with variable sizes of size_t and pointers.
+ int signature = data.getInt(0);
+ short pointerSize = 4;
+ if (signature == 0x812345dd) {
+ // Consume signature value.
+ int ignore = data.getInt();
+ short version = data.getShort();
+ if (version != 2) {
+ Log.e("ddms", "Unknown header version: " + version);
+ return;
+ }
+ pointerSize = data.getShort();
+ }
+ NativeBuffer buffer;
+ if (pointerSize == 4) {
+ buffer = new NativeBuffer32(data);
+ } else if (pointerSize == 8) {
+ buffer = new NativeBuffer64(data);
+ } else {
+ Log.e("ddms", "Unknown pointer size: " + pointerSize);
+ return;
+ }
+
+ // clear the previous run
+ clientData.clearNativeAllocationInfo();
+
+ int mapSize = buffer.getSizeT();
+ int allocSize = buffer.getSizeT();
+ int allocInfoSize = buffer.getSizeT();
+ int totalMemory = buffer.getSizeT();
+ int backtraceSize = buffer.getSizeT();
+
+ Log.d("ddms", "mapSize: " + mapSize);
+ Log.d("ddms", "allocSize: " + allocSize);
+ Log.d("ddms", "allocInfoSize: " + allocInfoSize);
+ Log.d("ddms", "totalMemory: " + totalMemory);
+
+ clientData.setTotalNativeMemory(totalMemory);
+
+ // this means that updates aren't turned on.
+ if (allocInfoSize == 0) {
+ return;
+ }
+
+ if (mapSize > 0) {
+ byte[] maps = new byte[mapSize];
+ data.get(maps, 0, mapSize);
+ parseMaps(clientData, maps);
+ }
+
+ int iterations = allocSize / allocInfoSize;
+ for (int i = 0 ; i < iterations ; i++) {
+ NativeAllocationInfo info = new NativeAllocationInfo(
+ buffer.getSizeT() /* size */,
+ buffer.getSizeT() /* allocations */);
+
+ for (int j = 0 ; j < backtraceSize ; j++) {
+ long addr = buffer.getPtr();
+ if (addr == 0x0) {
+ // skip past null addresses
+ continue;
+ }
+
+ info.addStackCallAddress(addr);
+ }
+ clientData.addNativeAllocation(info);
+ }
+ }
+
+ private void handleNHSG(Client client, ByteBuffer data) {
+ byte dataCopy[] = new byte[data.limit()];
+ data.rewind();
+ data.get(dataCopy);
+ data = ByteBuffer.wrap(dataCopy);
+ client.getClientData().getNativeHeapData().addHeapData(data);
+
+ if (true) {
+ return;
+ }
+
+ byte[] copy = new byte[data.limit()];
+ data.get(copy);
+
+ ByteBuffer buffer = ByteBuffer.wrap(copy);
+ buffer.order(ByteOrder.BIG_ENDIAN);
+
+ int id = buffer.getInt();
+ int unitsize = buffer.get();
+ long startAddress = buffer.getInt() & 0x00000000ffffffffL;
+ int offset = buffer.getInt();
+ int allocationUnitCount = buffer.getInt();
+
+ // read the usage
+ while (buffer.position() < buffer.limit()) {
+ int eState = buffer.get() & 0x000000ff;
+ int eLen = (buffer.get() & 0x000000ff) + 1;
+ }
+ }
+
+ private void parseMaps(ClientData clientData, byte[] maps) {
+ InputStreamReader input = new InputStreamReader(new ByteArrayInputStream(maps));
+ BufferedReader reader = new BufferedReader(input);
+
+ String line;
+
+ try {
+ while ((line = reader.readLine()) != null) {
+ Log.d("ddms", "line: " + line);
+ // Expected format:
+ // 7fe51f2000-7fe5213000 rw-p 00000000 00:00 0 [stack]
+
+ int library_start = line.lastIndexOf(' ');
+ if (library_start == -1) {
+ continue;
+ }
+
+ // Assume that any string that starts with a / is a
+ // shared library or executable that we will try to symbolize.
+ String library = line.substring(library_start+1);
+ if (!library.startsWith("/")) {
+ continue;
+ }
+
+ // Parse the start and end address range.
+ int dashIndex = line.indexOf('-');
+ int spaceIndex = line.indexOf(' ', dashIndex);
+ if (dashIndex == -1 || spaceIndex == -1) {
+ continue;
+ }
+
+ long startAddr = 0;
+ long endAddr = 0;
+ try {
+ startAddr = Long.parseLong(line.substring(0, dashIndex), 16);
+ endAddr = Long.parseLong(line.substring(dashIndex+1, spaceIndex), 16);
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ clientData.addNativeLibraryMapInfo(startAddr, endAddr, library);
+ Log.d("ddms", library + "(" + Long.toHexString(startAddr) +
+ " - " + Long.toHexString(endAddr) + ")");
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java b/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
new file mode 100644
index 0000000..e049073
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2009 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.ddmlib;
+
+import com.android.ddmlib.ClientData.IMethodProfilingHandler;
+import com.android.ddmlib.ClientData.MethodProfilingStatus;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handle heap status updates.
+ */
+final class HandleProfiling extends ChunkHandler {
+
+ public static final int CHUNK_MPRS = type("MPRS");
+ public static final int CHUNK_MPRE = type("MPRE");
+ public static final int CHUNK_MPSS = type("MPSS");
+ public static final int CHUNK_MPSE = type("MPSE");
+ public static final int CHUNK_SPSS = type("SPSS");
+ public static final int CHUNK_SPSE = type("SPSE");
+ public static final int CHUNK_MPRQ = type("MPRQ");
+ public static final int CHUNK_FAIL = type("FAIL");
+
+ private static final HandleProfiling mInst = new HandleProfiling();
+
+ private HandleProfiling() {}
+
+ /**
+ * Register for the packets we expect to get from the client.
+ */
+ public static void register(MonitorThread mt) {
+ mt.registerChunkHandler(CHUNK_MPRE, mInst);
+ mt.registerChunkHandler(CHUNK_MPSE, mInst);
+ mt.registerChunkHandler(CHUNK_MPRQ, mInst);
+ }
+
+ /**
+ * Client is ready.
+ */
+ @Override
+ public void clientReady(Client client) throws IOException {}
+
+ /**
+ * Client went away.
+ */
+ @Override
+ public void clientDisconnected(Client client) {}
+
+ /**
+ * Chunk handler entry point.
+ */
+ @Override
+ public void handleChunk(Client client, int type, ByteBuffer data,
+ boolean isReply, int msgId) {
+
+ Log.d("ddm-prof", "handling " + ChunkHandler.name(type));
+
+ if (type == CHUNK_MPRE) {
+ handleMPRE(client, data);
+ } else if (type == CHUNK_MPSE) {
+ handleMPSE(client, data);
+ } else if (type == CHUNK_MPRQ) {
+ handleMPRQ(client, data);
+ } else if (type == CHUNK_FAIL) {
+ handleFAIL(client, data);
+ } else {
+ handleUnknownChunk(client, type, data, isReply, msgId);
+ }
+ }
+
+ /**
+ * Send a MPRS (Method PRofiling Start) request to the client.
+ *
+ * The arguments to this method will eventually be passed to
+ * android.os.Debug.startMethodTracing() on the device.
+ *
+ * @param fileName is the name of the file to which profiling data
+ * will be written (on the device); it will have {@link DdmConstants#DOT_TRACE}
+ * appended if necessary
+ * @param bufferSize is the desired buffer size in bytes (8MB is good)
+ * @param flags see startMethodTracing() docs; use 0 for default behavior
+ */
+ public static void sendMPRS(Client client, String fileName, int bufferSize,
+ int flags) throws IOException {
+
+ ByteBuffer rawBuf = allocBuffer(3*4 + fileName.length() * 2);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(bufferSize);
+ buf.putInt(flags);
+ buf.putInt(fileName.length());
+ ByteBufferUtil.putString(buf, fileName);
+
+ finishChunkPacket(packet, CHUNK_MPRS, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_MPRS) + " '" + fileName
+ + "', size=" + bufferSize + ", flags=" + flags);
+ client.send(packet, mInst);
+
+ // record the filename we asked for.
+ client.getClientData().setPendingMethodProfiling(fileName);
+
+ // send a status query. this ensure that the status is properly updated if for some
+ // reason starting the tracing failed.
+ sendMPRQ(client);
+ }
+
+ /**
+ * Send a MPRE (Method PRofiling End) request to the client.
+ */
+ public static void sendMPRE(Client client) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_MPRE, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_MPRE));
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Handle notification that method profiling has finished writing
+ * data to disk.
+ */
+ private void handleMPRE(Client client, ByteBuffer data) {
+ byte result;
+
+ // get the filename and make the client not have pending HPROF dump anymore.
+ String filename = client.getClientData().getPendingMethodProfiling();
+ client.getClientData().setPendingMethodProfiling(null);
+
+ result = data.get();
+
+ // get the app-level handler for method tracing dump
+ IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
+ if (handler != null) {
+ if (result == 0) {
+ handler.onSuccess(filename, client);
+
+ Log.d("ddm-prof", "Method profiling has finished");
+ } else {
+ handler.onEndFailure(client, null /*message*/);
+
+ Log.w("ddm-prof", "Method profiling has failed (check device log)");
+ }
+ }
+
+ client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
+ client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
+ }
+
+ /**
+ * Send a MPSS (Method Profiling Streaming Start) request to the client.
+ *
+ * The arguments to this method will eventually be passed to
+ * android.os.Debug.startMethodTracing() on the device.
+ *
+ * @param bufferSize is the desired buffer size in bytes (8MB is good)
+ * @param flags see startMethodTracing() docs; use 0 for default behavior
+ */
+ public static void sendMPSS(Client client, int bufferSize,
+ int flags) throws IOException {
+
+ ByteBuffer rawBuf = allocBuffer(2*4);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(bufferSize);
+ buf.putInt(flags);
+
+ finishChunkPacket(packet, CHUNK_MPSS, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_MPSS)
+ + "', size=" + bufferSize + ", flags=" + flags);
+ client.send(packet, mInst);
+
+ // send a status query. this ensure that the status is properly updated if for some
+ // reason starting the tracing failed.
+ sendMPRQ(client);
+ }
+
+ /**
+ * Send a SPSS (Sampling Profiling Streaming Start) request to the client.
+ *
+ * @param bufferSize is the desired buffer size in bytes (8MB is good)
+ * @param samplingInterval sampling interval
+ * @param samplingIntervalTimeUnits units for sampling interval
+ */
+ public static void sendSPSS(Client client, int bufferSize, int samplingInterval,
+ TimeUnit samplingIntervalTimeUnits) throws IOException {
+ int interval = (int) samplingIntervalTimeUnits.toMicros(samplingInterval);
+
+ ByteBuffer rawBuf = allocBuffer(3*4);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(bufferSize);
+ buf.putInt(0); // flags
+ buf.putInt(interval);
+
+ finishChunkPacket(packet, CHUNK_SPSS, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_SPSS)
+ + "', size=" + bufferSize + ", flags=0, samplingInterval=" + interval);
+ client.send(packet, mInst);
+
+ // send a status query. this ensure that the status is properly updated if for some
+ // reason starting the tracing failed.
+ sendMPRQ(client);
+ }
+
+ /**
+ * Send a MPSE (Method Profiling Streaming End) request to the client.
+ */
+ public static void sendMPSE(Client client) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_MPSE, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_MPSE));
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Send a SPSE (Sampling Profiling Streaming End) request to the client.
+ */
+ public static void sendSPSE(Client client) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_SPSE, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_SPSE));
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Handle incoming profiling data. The MPSE packet includes the
+ * complete .trace file.
+ */
+ private void handleMPSE(Client client, ByteBuffer data) {
+ IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
+ if (handler != null) {
+ byte[] stuff = new byte[data.capacity()];
+ data.get(stuff, 0, stuff.length);
+
+ Log.d("ddm-prof", "got trace file, size: " + stuff.length + " bytes");
+
+ handler.onSuccess(stuff, client);
+ }
+
+ client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
+ client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
+ }
+
+ /**
+ * Send a MPRQ (Method PRofiling Query) request to the client.
+ */
+ public static void sendMPRQ(Client client) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_MPRQ, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_MPRQ));
+ client.send(packet, mInst);
+ }
+
+ /**
+ * Receive response to query.
+ */
+ private void handleMPRQ(Client client, ByteBuffer data) {
+ byte result;
+
+ result = data.get();
+
+ if (result == 0) {
+ client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
+ Log.d("ddm-prof", "Method profiling is not running");
+ } else if (result == 1) {
+ client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.TRACER_ON);
+ Log.d("ddm-prof", "Method tracing is active");
+ } else if (result == 2) {
+ client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.SAMPLER_ON);
+ Log.d("ddm-prof", "Sampler based profiling is active");
+ }
+ client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
+ }
+
+ private void handleFAIL(Client client, ByteBuffer data) {
+ /*int errorCode =*/ data.getInt();
+ int length = data.getInt() * 2;
+ String message = null;
+ if (length > 0) {
+ byte[] messageBuffer = new byte[length];
+ data.get(messageBuffer, 0, length);
+ message = new String(messageBuffer);
+ }
+
+ // this can be sent if
+ // - MPRS failed (like wrong permission)
+ // - MPSE failed for whatever reason
+
+ String filename = client.getClientData().getPendingMethodProfiling();
+ if (filename != null) {
+ // reset the pending file.
+ client.getClientData().setPendingMethodProfiling(null);
+
+ // and notify of failure
+ IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
+ if (handler != null) {
+ handler.onStartFailure(client, message);
+ }
+ } else {
+ // this is MPRE
+ // notify of failure
+ IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
+ if (handler != null) {
+ handler.onEndFailure(client, message);
+ }
+ }
+
+ // send a query to know the current status
+ try {
+ sendMPRQ(client);
+ } catch (IOException e) {
+ Log.e("HandleProfiling", e);
+ }
+ }
+}
+
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleTest.java b/ddmlib/src/main/java/com/android/ddmlib/HandleTest.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/HandleTest.java
rename to ddmlib/src/main/java/com/android/ddmlib/HandleTest.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java b/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java
new file mode 100644
index 0000000..8a11c86
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleThread.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle thread status updates.
+ */
+final class HandleThread extends ChunkHandler {
+
+ public static final int CHUNK_THEN = type("THEN");
+ public static final int CHUNK_THCR = type("THCR");
+ public static final int CHUNK_THDE = type("THDE");
+ public static final int CHUNK_THST = type("THST");
+ public static final int CHUNK_THNM = type("THNM");
+ public static final int CHUNK_STKL = type("STKL");
+
+ private static final HandleThread mInst = new HandleThread();
+
+ // only read/written by requestThreadUpdates()
+ private static volatile boolean sThreadStatusReqRunning = false;
+ private static volatile boolean sThreadStackTraceReqRunning = false;
+
+ private HandleThread() {}
+
+
+ /**
+ * Register for the packets we expect to get from the client.
+ */
+ public static void register(MonitorThread mt) {
+ mt.registerChunkHandler(CHUNK_THCR, mInst);
+ mt.registerChunkHandler(CHUNK_THDE, mInst);
+ mt.registerChunkHandler(CHUNK_THST, mInst);
+ mt.registerChunkHandler(CHUNK_THNM, mInst);
+ mt.registerChunkHandler(CHUNK_STKL, mInst);
+ }
+
+ /**
+ * Client is ready.
+ */
+ @Override
+ public void clientReady(Client client) throws IOException {
+ Log.d("ddm-thread", "Now ready: " + client);
+ if (client.isThreadUpdateEnabled())
+ sendTHEN(client, true);
+ }
+
+ /**
+ * Client went away.
+ */
+ @Override
+ public void clientDisconnected(Client client) {}
+
+ /**
+ * Chunk handler entry point.
+ */
+ @Override
+ public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
+
+ Log.d("ddm-thread", "handling " + ChunkHandler.name(type));
+
+ if (type == CHUNK_THCR) {
+ handleTHCR(client, data);
+ } else if (type == CHUNK_THDE) {
+ handleTHDE(client, data);
+ } else if (type == CHUNK_THST) {
+ handleTHST(client, data);
+ } else if (type == CHUNK_THNM) {
+ handleTHNM(client, data);
+ } else if (type == CHUNK_STKL) {
+ handleSTKL(client, data);
+ } else {
+ handleUnknownChunk(client, type, data, isReply, msgId);
+ }
+ }
+
+ /*
+ * Handle a thread creation message.
+ *
+ * We should be tolerant of receiving a duplicate create message. (It
+ * shouldn't happen with the current implementation.)
+ */
+ private void handleTHCR(Client client, ByteBuffer data) {
+ int threadId, nameLen;
+ String name;
+
+ threadId = data.getInt();
+ nameLen = data.getInt();
+ name = ByteBufferUtil.getString(data, nameLen);
+
+ Log.v("ddm-thread", "THCR: " + threadId + " '" + name + "'");
+
+ client.getClientData().addThread(threadId, name);
+ client.update(Client.CHANGE_THREAD_DATA);
+ }
+
+ /*
+ * Handle a thread death message.
+ */
+ private void handleTHDE(Client client, ByteBuffer data) {
+ int threadId;
+
+ threadId = data.getInt();
+ Log.v("ddm-thread", "THDE: " + threadId);
+
+ client.getClientData().removeThread(threadId);
+ client.update(Client.CHANGE_THREAD_DATA);
+ }
+
+ /*
+ * Handle a thread status update message.
+ *
+ * Response has:
+ * (1b) header len
+ * (1b) bytes per entry
+ * (2b) thread count
+ * Then, for each thread:
+ * (4b) threadId (matches value from THCR)
+ * (1b) thread status
+ * (4b) tid
+ * (4b) utime
+ * (4b) stime
+ */
+ private void handleTHST(Client client, ByteBuffer data) {
+ int headerLen, bytesPerEntry, extraPerEntry;
+ int threadCount;
+
+ headerLen = (data.get() & 0xff);
+ bytesPerEntry = (data.get() & 0xff);
+ threadCount = data.getShort();
+
+ headerLen -= 4; // we've read 4 bytes
+ while (headerLen-- > 0)
+ data.get();
+
+ extraPerEntry = bytesPerEntry - 18; // we want 18 bytes
+
+ Log.v("ddm-thread", "THST: threadCount=" + threadCount);
+
+ /*
+ * For each thread, extract the data, find the appropriate
+ * client, and add it to the ClientData.
+ */
+ for (int i = 0; i < threadCount; i++) {
+ int threadId, status, tid, utime, stime;
+ boolean isDaemon = false;
+
+ threadId = data.getInt();
+ status = data.get();
+ tid = data.getInt();
+ utime = data.getInt();
+ stime = data.getInt();
+ if (bytesPerEntry >= 18)
+ isDaemon = (data.get() != 0);
+
+ Log.v("ddm-thread", " id=" + threadId
+ + ", status=" + status + ", tid=" + tid
+ + ", utime=" + utime + ", stime=" + stime);
+
+ ClientData cd = client.getClientData();
+ ThreadInfo threadInfo = cd.getThread(threadId);
+ if (threadInfo != null)
+ threadInfo.updateThread(status, tid, utime, stime, isDaemon);
+ else
+ Log.d("ddms", "Thread with id=" + threadId + " not found");
+
+ // slurp up any extra
+ for (int slurp = extraPerEntry; slurp > 0; slurp--)
+ data.get();
+ }
+
+ client.update(Client.CHANGE_THREAD_DATA);
+ }
+
+ /*
+ * Handle a THNM (THread NaMe) message. We get one of these after
+ * somebody calls Thread.setName() on a running thread.
+ */
+ private void handleTHNM(Client client, ByteBuffer data) {
+ int threadId, nameLen;
+ String name;
+
+ threadId = data.getInt();
+ nameLen = data.getInt();
+ name = ByteBufferUtil.getString(data, nameLen);
+
+ Log.v("ddm-thread", "THNM: " + threadId + " '" + name + "'");
+
+ ThreadInfo threadInfo = client.getClientData().getThread(threadId);
+ if (threadInfo != null) {
+ threadInfo.setThreadName(name);
+ client.update(Client.CHANGE_THREAD_DATA);
+ } else {
+ Log.d("ddms", "Thread with id=" + threadId + " not found");
+ }
+ }
+
+
+ /**
+ * Parse an incoming STKL.
+ */
+ private void handleSTKL(Client client, ByteBuffer data) {
+ StackTraceElement[] trace;
+ int i, threadId, stackDepth;
+ @SuppressWarnings("unused")
+ int future;
+
+ future = data.getInt();
+ threadId = data.getInt();
+
+ Log.v("ddms", "STKL: " + threadId);
+
+ /* un-serialize the StackTraceElement[] */
+ stackDepth = data.getInt();
+ trace = new StackTraceElement[stackDepth];
+ for (i = 0; i < stackDepth; i++) {
+ String className, methodName, fileName;
+ int len, lineNumber;
+
+ len = data.getInt();
+ className = ByteBufferUtil.getString(data, len);
+ len = data.getInt();
+ methodName = ByteBufferUtil.getString(data, len);
+ len = data.getInt();
+ if (len == 0) {
+ fileName = null;
+ } else {
+ fileName = ByteBufferUtil.getString(data, len);
+ }
+ lineNumber = data.getInt();
+
+ trace[i] = new StackTraceElement(className, methodName, fileName,
+ lineNumber);
+ }
+
+ ThreadInfo threadInfo = client.getClientData().getThread(threadId);
+ if (threadInfo != null) {
+ threadInfo.setStackCall(trace);
+ client.update(Client.CHANGE_THREAD_STACKTRACE);
+ } else {
+ Log.d("STKL", String.format(
+ "Got stackcall for thread %1$d, which does not exists (anymore?).", //$NON-NLS-1$
+ threadId));
+ }
+ }
+
+
+ /**
+ * Send a THEN (THread notification ENable) request to the client.
+ */
+ public static void sendTHEN(Client client, boolean enable)
+ throws IOException {
+
+ ByteBuffer rawBuf = allocBuffer(1);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ if (enable)
+ buf.put((byte)1);
+ else
+ buf.put((byte)0);
+
+ finishChunkPacket(packet, CHUNK_THEN, buf.position());
+ Log.d("ddm-thread", "Sending " + name(CHUNK_THEN) + ": " + enable);
+ client.send(packet, mInst);
+ }
+
+
+ /**
+ * Send a STKL (STacK List) request to the client. The VM will suspend
+ * the target thread, obtain its stack, and return it. If the thread
+ * is no longer running, a failure result will be returned.
+ */
+ public static void sendSTKL(Client client, int threadId)
+ throws IOException {
+
+ if (false) {
+ Log.d("ddm-thread", "would send STKL " + threadId);
+ return;
+ }
+
+ ByteBuffer rawBuf = allocBuffer(4);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(threadId);
+
+ finishChunkPacket(packet, CHUNK_STKL, buf.position());
+ Log.d("ddm-thread", "Sending " + name(CHUNK_STKL) + ": " + threadId);
+ client.send(packet, mInst);
+ }
+
+
+ /**
+ * This is called periodically from the UI thread. To avoid locking
+ * the UI while we request the updates, we create a new thread.
+ *
+ */
+ static void requestThreadUpdate(final Client client) {
+ if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
+ if (sThreadStatusReqRunning) {
+ Log.w("ddms", "Waiting for previous thread update req to finish");
+ return;
+ }
+
+ new Thread("Thread Status Req") {
+ @Override
+ public void run() {
+ sThreadStatusReqRunning = true;
+ try {
+ sendTHST(client);
+ } catch (IOException ioe) {
+ Log.d("ddms", "Unable to request thread updates from "
+ + client + ": " + ioe.getMessage());
+ } finally {
+ sThreadStatusReqRunning = false;
+ }
+ }
+ }.start();
+ }
+ }
+
+ static void requestThreadStackCallRefresh(final Client client, final int threadId) {
+ if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
+ if (sThreadStackTraceReqRunning) {
+ Log.w("ddms", "Waiting for previous thread stack call req to finish");
+ return;
+ }
+
+ new Thread("Thread Status Req") {
+ @Override
+ public void run() {
+ sThreadStackTraceReqRunning = true;
+ try {
+ sendSTKL(client, threadId);
+ } catch (IOException ioe) {
+ Log.d("ddms", "Unable to request thread stack call updates from "
+ + client + ": " + ioe.getMessage());
+ } finally {
+ sThreadStackTraceReqRunning = false;
+ }
+ }
+ }.start();
+ }
+
+ }
+
+ /*
+ * Send a THST request to the specified client.
+ */
+ private static void sendTHST(Client client) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // nothing much to say
+
+ finishChunkPacket(packet, CHUNK_THST, buf.position());
+ Log.d("ddm-thread", "Sending " + name(CHUNK_THST));
+ client.send(packet, mInst);
+ }
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java b/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java
new file mode 100644
index 0000000..a313e42
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleViewDebug.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public final class HandleViewDebug extends ChunkHandler {
+ /** Enable/Disable tracing of OpenGL calls. */
+ public static final int CHUNK_VUGL = type("VUGL");
+
+ /** List {@link ViewRootImpl}'s of this process. */
+ public static final int CHUNK_VULW = type("VULW");
+
+ /** Operation on view root, first parameter in packet should be one of VURT_* constants */
+ public static final int CHUNK_VURT = type("VURT");
+
+ /** Dump view hierarchy. */
+ private static final int VURT_DUMP_HIERARCHY = 1;
+
+ /** Capture View Layers. */
+ private static final int VURT_CAPTURE_LAYERS = 2;
+
+ /** Dump View Theme. */
+ private static final int VURT_DUMP_THEME = 3;
+
+ /**
+ * Generic View Operation, first parameter in the packet should be one of the
+ * VUOP_* constants below.
+ */
+ public static final int CHUNK_VUOP = type("VUOP");
+
+ /** Capture View. */
+ private static final int VUOP_CAPTURE_VIEW = 1;
+
+ /** Obtain the Display List corresponding to the view. */
+ private static final int VUOP_DUMP_DISPLAYLIST = 2;
+
+ /** Profile a view. */
+ private static final int VUOP_PROFILE_VIEW = 3;
+
+ /** Invoke a method on the view. */
+ private static final int VUOP_INVOKE_VIEW_METHOD = 4;
+
+ /** Set layout parameter. */
+ private static final int VUOP_SET_LAYOUT_PARAMETER = 5;
+
+ private static final String TAG = "ddmlib"; //$NON-NLS-1$
+
+ private static final HandleViewDebug sInstance = new HandleViewDebug();
+
+ private static final ViewDumpHandler sViewOpNullChunkHandler =
+ new NullChunkHandler(CHUNK_VUOP);
+
+ private HandleViewDebug() {}
+
+ public static void register(MonitorThread mt) {
+ // TODO: add chunk type for auto window updates
+ // and register here
+ mt.registerChunkHandler(CHUNK_VUGL, sInstance);
+ mt.registerChunkHandler(CHUNK_VULW, sInstance);
+ mt.registerChunkHandler(CHUNK_VUOP, sInstance);
+ mt.registerChunkHandler(CHUNK_VURT, sInstance);
+ }
+
+ @Override
+ public void clientReady(Client client) throws IOException {}
+
+ @Override
+ public void clientDisconnected(Client client) {}
+
+ public abstract static class ViewDumpHandler extends ChunkHandler {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final int mChunkType;
+
+ public ViewDumpHandler(int chunkType) {
+ mChunkType = chunkType;
+ }
+
+ @Override
+ void clientReady(Client client) throws IOException {
+ }
+
+ @Override
+ void clientDisconnected(Client client) {
+ }
+
+ @Override
+ void handleChunk(Client client, int type, ByteBuffer data,
+ boolean isReply, int msgId) {
+ if (type != mChunkType) {
+ handleUnknownChunk(client, type, data, isReply, msgId);
+ return;
+ }
+
+ handleViewDebugResult(data);
+ mLatch.countDown();
+ }
+
+ protected abstract void handleViewDebugResult(ByteBuffer data);
+
+ protected void waitForResult(long timeout, TimeUnit unit) {
+ try {
+ mLatch.await(timeout, unit);
+ } catch (InterruptedException e) {
+ // pass
+ }
+ }
+ }
+
+ public static void listViewRoots(Client client, ViewDumpHandler replyHandler)
+ throws IOException {
+ ByteBuffer buf = allocBuffer(8);
+ JdwpPacket packet = new JdwpPacket(buf);
+ ByteBuffer chunkBuf = getChunkDataBuf(buf);
+ chunkBuf.putInt(1);
+ finishChunkPacket(packet, CHUNK_VULW, chunkBuf.position());
+ client.send(packet, replyHandler);
+ }
+
+ public static void dumpViewHierarchy(@NonNull Client client, @NonNull String viewRoot,
+ boolean skipChildren, boolean includeProperties, @NonNull ViewDumpHandler handler)
+ throws IOException {
+ ByteBuffer buf = allocBuffer(4 // opcode
+ + 4 // view root length
+ + viewRoot.length() * 2 // view root
+ + 4 // skip children
+ + 4); // include view properties
+ JdwpPacket packet = new JdwpPacket(buf);
+ ByteBuffer chunkBuf = getChunkDataBuf(buf);
+
+ chunkBuf.putInt(VURT_DUMP_HIERARCHY);
+ chunkBuf.putInt(viewRoot.length());
+ ByteBufferUtil.putString(chunkBuf, viewRoot);
+ chunkBuf.putInt(skipChildren ? 1 : 0);
+ chunkBuf.putInt(includeProperties ? 1 : 0);
+
+ finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
+ client.send(packet, handler);
+ }
+
+ public static void captureLayers(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull ViewDumpHandler handler) throws IOException {
+ int bufLen = 8 + viewRoot.length() * 2;
+
+ ByteBuffer buf = allocBuffer(bufLen);
+ JdwpPacket packet = new JdwpPacket(buf);
+ ByteBuffer chunkBuf = getChunkDataBuf(buf);
+
+ chunkBuf.putInt(VURT_CAPTURE_LAYERS);
+ chunkBuf.putInt(viewRoot.length());
+ ByteBufferUtil.putString(chunkBuf, viewRoot);
+
+ finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
+ client.send(packet, handler);
+ }
+
+ private static void sendViewOpPacket(@NonNull Client client, int op, @NonNull String viewRoot,
+ @NonNull String view, @Nullable byte[] extra, @Nullable ViewDumpHandler handler)
+ throws IOException {
+ int bufLen = 4 + // opcode
+ 4 + viewRoot.length() * 2 + // view root strlen + view root
+ 4 + view.length() * 2; // view strlen + view
+
+ if (extra != null) {
+ bufLen += extra.length;
+ }
+
+ ByteBuffer buf = allocBuffer(bufLen);
+ JdwpPacket packet = new JdwpPacket(buf);
+ ByteBuffer chunkBuf = getChunkDataBuf(buf);
+
+ chunkBuf.putInt(op);
+ chunkBuf.putInt(viewRoot.length());
+ ByteBufferUtil.putString(chunkBuf, viewRoot);
+
+ chunkBuf.putInt(view.length());
+ ByteBufferUtil.putString(chunkBuf, view);
+
+ if (extra != null) {
+ chunkBuf.put(extra);
+ }
+
+ finishChunkPacket(packet, CHUNK_VUOP, chunkBuf.position());
+ client.send(packet, handler);
+ }
+
+ public static void profileView(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull String view, @NonNull ViewDumpHandler handler) throws IOException {
+ sendViewOpPacket(client, VUOP_PROFILE_VIEW, viewRoot, view, null, handler);
+ }
+
+ public static void captureView(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull String view, @NonNull ViewDumpHandler handler) throws IOException {
+ sendViewOpPacket(client, VUOP_CAPTURE_VIEW, viewRoot, view, null, handler);
+ }
+
+ public static void invalidateView(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull String view) throws IOException {
+ invokeMethod(client, viewRoot, view, "invalidate");
+ }
+
+ public static void requestLayout(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull String view) throws IOException {
+ invokeMethod(client, viewRoot, view, "requestLayout");
+ }
+
+ public static void dumpDisplayList(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull String view) throws IOException {
+ sendViewOpPacket(client, VUOP_DUMP_DISPLAYLIST, viewRoot, view, null,
+ sViewOpNullChunkHandler);
+ }
+
+ public static void dumpTheme(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull ViewDumpHandler handler)
+ throws IOException {
+ ByteBuffer buf = allocBuffer(4 // opcode
+ + 4 // view root length
+ + viewRoot.length() * 2); // view root
+ JdwpPacket packet = new JdwpPacket(buf);
+ ByteBuffer chunkBuf = getChunkDataBuf(buf);
+
+ chunkBuf.putInt(VURT_DUMP_THEME);
+ chunkBuf.putInt(viewRoot.length());
+ ByteBufferUtil.putString(chunkBuf, viewRoot);
+
+ finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
+ client.send(packet, handler);
+ }
+
+ /** A {@link ViewDumpHandler} to use when no response is expected. */
+ private static class NullChunkHandler extends ViewDumpHandler {
+ public NullChunkHandler(int chunkType) {
+ super(chunkType);
+ }
+
+ @Override
+ protected void handleViewDebugResult(ByteBuffer data) {
+ }
+ }
+
+ public static void invokeMethod(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull String view, @NonNull String method, Object... args) throws IOException {
+ int len = 4 + method.length() * 2;
+ if (args != null) {
+ // # of args
+ len += 4;
+
+ // for each argument, we send a char type specifier (2 bytes) and
+ // the arg value (max primitive size = sizeof(double) = 8
+ len += 10 * args.length;
+ }
+
+ byte[] extra = new byte[len];
+ ByteBuffer b = ByteBuffer.wrap(extra);
+
+ b.putInt(method.length());
+ ByteBufferUtil.putString(b, method);
+
+ if (args != null) {
+ b.putInt(args.length);
+
+ for (int i = 0; i < args.length; i++) {
+ Object arg = args[i];
+ if (arg instanceof Boolean) {
+ b.putChar('Z');
+ b.put((byte) ((Boolean) arg ? 1 : 0));
+ } else if (arg instanceof Byte) {
+ b.putChar('B');
+ b.put((Byte) arg);
+ } else if (arg instanceof Character) {
+ b.putChar('C');
+ b.putChar((Character) arg);
+ } else if (arg instanceof Short) {
+ b.putChar('S');
+ b.putShort((Short) arg);
+ } else if (arg instanceof Integer) {
+ b.putChar('I');
+ b.putInt((Integer) arg);
+ } else if (arg instanceof Long) {
+ b.putChar('J');
+ b.putLong((Long) arg);
+ } else if (arg instanceof Float) {
+ b.putChar('F');
+ b.putFloat((Float) arg);
+ } else if (arg instanceof Double) {
+ b.putChar('D');
+ b.putDouble((Double) arg);
+ } else {
+ Log.e(TAG, "View method invocation only supports primitive arguments, supplied: " + arg);
+ return;
+ }
+ }
+ }
+
+ sendViewOpPacket(client, VUOP_INVOKE_VIEW_METHOD, viewRoot, view, extra,
+ sViewOpNullChunkHandler );
+ }
+
+ public static void setLayoutParameter(@NonNull Client client, @NonNull String viewRoot,
+ @NonNull String view, @NonNull String parameter, int value) throws IOException {
+ int len = 4 + parameter.length() * 2 + 4;
+ byte[] extra = new byte[len];
+ ByteBuffer b = ByteBuffer.wrap(extra);
+
+ b.putInt(parameter.length());
+ ByteBufferUtil.putString(b, parameter);
+ b.putInt(value);
+ sendViewOpPacket(client, VUOP_SET_LAYOUT_PARAMETER, viewRoot, view, extra,
+ sViewOpNullChunkHandler);
+ }
+
+ @Override
+ public void handleChunk(Client client, int type, ByteBuffer data,
+ boolean isReply, int msgId) {
+ }
+
+ public static void sendStartGlTracing(Client client) throws IOException {
+ ByteBuffer buf = allocBuffer(4);
+ JdwpPacket packet = new JdwpPacket(buf);
+
+ ByteBuffer chunkBuf = getChunkDataBuf(buf);
+ chunkBuf.putInt(1);
+ finishChunkPacket(packet, CHUNK_VUGL, chunkBuf.position());
+
+ client.send(packet, null);
+ }
+
+ public static void sendStopGlTracing(Client client) throws IOException {
+ ByteBuffer buf = allocBuffer(4);
+ JdwpPacket packet = new JdwpPacket(buf);
+
+ ByteBuffer chunkBuf = getChunkDataBuf(buf);
+ chunkBuf.putInt(0);
+ finishChunkPacket(packet, CHUNK_VUGL, chunkBuf.position());
+
+ client.send(packet, null);
+ }
+}
+
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HandleWait.java b/ddmlib/src/main/java/com/android/ddmlib/HandleWait.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/HandleWait.java
rename to ddmlib/src/main/java/com/android/ddmlib/HandleWait.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/HeapSegment.java b/ddmlib/src/main/java/com/android/ddmlib/HeapSegment.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/HeapSegment.java
rename to ddmlib/src/main/java/com/android/ddmlib/HeapSegment.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/IDevice.java b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
new file mode 100644
index 0000000..8433b56
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
@@ -0,0 +1,681 @@
+/*
+ * Copyright (C) 2008 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.log.LogReceiver;
+import com.android.sdklib.AndroidVersion;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Device. It can be a physical device or an emulator.
+ */
+public interface IDevice extends IShellEnabledDevice {
+
+ String PROP_BUILD_VERSION = "ro.build.version.release";
+ String PROP_BUILD_API_LEVEL = "ro.build.version.sdk";
+ String PROP_BUILD_CODENAME = "ro.build.version.codename";
+ String PROP_BUILD_TAGS = "ro.build.tags";
+ String PROP_BUILD_TYPE = "ro.build.type";
+ String PROP_DEVICE_MODEL = "ro.product.model";
+ String PROP_DEVICE_MANUFACTURER = "ro.product.manufacturer";
+ String PROP_DEVICE_CPU_ABI_LIST = "ro.product.cpu.abilist";
+ String PROP_DEVICE_CPU_ABI = "ro.product.cpu.abi";
+ String PROP_DEVICE_CPU_ABI2 = "ro.product.cpu.abi2";
+ String PROP_BUILD_CHARACTERISTICS = "ro.build.characteristics";
+ String PROP_DEVICE_DENSITY = "ro.sf.lcd_density";
+ String PROP_DEVICE_EMULATOR_DENSITY = "qemu.sf.lcd_density";
+ String PROP_DEVICE_LANGUAGE = "persist.sys.language";
+ String PROP_DEVICE_REGION = "persist.sys.country";
+
+ String PROP_DEBUGGABLE = "ro.debuggable";
+
+ /** Serial number of the first connected emulator. */
+ String FIRST_EMULATOR_SN = "emulator-5554"; //$NON-NLS-1$
+ /** Device change bit mask: {@link DeviceState} change. */
+ int CHANGE_STATE = 0x0001;
+ /** Device change bit mask: {@link Client} list change. */
+ int CHANGE_CLIENT_LIST = 0x0002;
+ /** Device change bit mask: build info change. */
+ int CHANGE_BUILD_INFO = 0x0004;
+
+ /** Device level software features. */
+ enum Feature {
+ SCREEN_RECORD, // screen recorder available?
+ PROCSTATS, // procstats service (dumpsys procstats) available
+ }
+
+ /** Device level hardware features. */
+ enum HardwareFeature {
+ WATCH("watch"),
+ TV("tv");
+
+ private final String mCharacteristic;
+
+ HardwareFeature(String characteristic) {
+ mCharacteristic = characteristic;
+ }
+
+ public String getCharacteristic() {
+ return mCharacteristic;
+ }
+ }
+
+ /** @deprecated Use {@link #PROP_BUILD_API_LEVEL}. */
+ @Deprecated
+ String PROP_BUILD_VERSION_NUMBER = PROP_BUILD_API_LEVEL;
+
+ String MNT_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"; //$NON-NLS-1$
+ String MNT_ROOT = "ANDROID_ROOT"; //$NON-NLS-1$
+ String MNT_DATA = "ANDROID_DATA"; //$NON-NLS-1$
+
+ /**
+ * The state of a device.
+ */
+ enum DeviceState {
+ BOOTLOADER("bootloader"), //$NON-NLS-1$
+ OFFLINE("offline"), //$NON-NLS-1$
+ ONLINE("device"), //$NON-NLS-1$
+ RECOVERY("recovery"), //$NON-NLS-1$
+ UNAUTHORIZED("unauthorized"), //$NON-NLS-1$
+ DISCONNECTED("disconnected"), //$NON-NLS-1$
+ ;
+
+ private String mState;
+
+ DeviceState(String state) {
+ mState = state;
+ }
+
+ /**
+ * Returns a {@link DeviceState} from the string returned by <code>adb devices</code>.
+ *
+ * @param state the device state.
+ * @return a {@link DeviceState} object or <code>null</code> if the state is unknown.
+ */
+ @Nullable
+ public static DeviceState getState(String state) {
+ for (DeviceState deviceState : values()) {
+ if (deviceState.mState.equals(state)) {
+ return deviceState;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Namespace of a Unix Domain Socket created on the device.
+ */
+ enum DeviceUnixSocketNamespace {
+ ABSTRACT("localabstract"), //$NON-NLS-1$
+ FILESYSTEM("localfilesystem"), //$NON-NLS-1$
+ RESERVED("localreserved"); //$NON-NLS-1$
+
+ private String mType;
+
+ DeviceUnixSocketNamespace(String type) {
+ mType = type;
+ }
+
+ String getType() {
+ return mType;
+ }
+ }
+
+ /** Returns the serial number of the device. */
+ @NonNull
+ String getSerialNumber();
+
+ /**
+ * Returns the name of the AVD the emulator is running.
+ * <p/>This is only valid if {@link #isEmulator()} returns true.
+ * <p/>If the emulator is not running any AVD (for instance it's running from an Android source
+ * tree build), this method will return "<code><build></code>".
+ *
+ * @return the name of the AVD or <code>null</code> if there isn't any.
+ */
+ @Nullable
+ String getAvdName();
+
+ /**
+ * Returns the state of the device.
+ */
+ DeviceState getState();
+
+ /**
+ * Returns the cached device properties. It contains the whole output of 'getprop'
+ *
+ * @deprecated use {@link #getSystemProperty(String)} instead
+ */
+ @Deprecated
+ Map<String, String> getProperties();
+
+ /**
+ * Returns the number of property for this device.
+ *
+ * @deprecated implementation detail
+ */
+ @Deprecated
+ int getPropertyCount();
+
+ /**
+ * Convenience method that attempts to retrieve a property via
+ * {@link #getSystemProperty(String)} with a very short wait time, and swallows exceptions.
+ *
+ * <p><em>Note: Prefer using {@link #getSystemProperty(String)} if you want control over the
+ * timeout.</em>
+ *
+ * @param name the name of the value to return.
+ * @return the value or <code>null</code> if the property value was not immediately available
+ */
+ @Nullable
+ String getProperty(@NonNull String name);
+
+ /**
+ * Returns <code>true></code> if properties have been cached
+ */
+ boolean arePropertiesSet();
+
+ /**
+ * A variant of {@link #getProperty(String)} that will attempt to retrieve the given
+ * property from device directly, without using cache.
+ * This method should (only) be used for any volatile properties.
+ *
+ * @param name the name of the value to return.
+ * @return the value or <code>null</code> if the property does not exist
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws ShellCommandUnresponsiveException in case the shell command doesn't send output for a
+ * given time.
+ * @throws IOException in case of I/O error on the connection.
+ * @deprecated use {@link #getSystemProperty(String)}
+ */
+ @Deprecated
+ String getPropertySync(String name) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
+
+ /**
+ * A combination of {@link #getProperty(String)} and {@link #getPropertySync(String)} that
+ * will attempt to retrieve the property from cache. If not found, will synchronously
+ * attempt to query device directly and repopulate the cache if successful.
+ *
+ * @param name the name of the value to return.
+ * @return the value or <code>null</code> if the property does not exist
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws ShellCommandUnresponsiveException in case the shell command doesn't send output for a
+ * given time.
+ * @throws IOException in case of I/O error on the connection.
+ * @deprecated use {@link #getSystemProperty(String)} instead
+ */
+ @Deprecated
+ String getPropertyCacheOrSync(String name) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
+
+ /** Returns whether this device supports the given software feature. */
+ boolean supportsFeature(@NonNull Feature feature);
+
+ /** Returns whether this device supports the given hardware feature. */
+ boolean supportsFeature(@NonNull HardwareFeature feature);
+
+ /**
+ * Returns a mount point.
+ *
+ * @param name the name of the mount point to return
+ *
+ * @see #MNT_EXTERNAL_STORAGE
+ * @see #MNT_ROOT
+ * @see #MNT_DATA
+ */
+ @Nullable
+ String getMountPoint(@NonNull String name);
+
+ /**
+ * Returns if the device is ready.
+ *
+ * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#ONLINE}.
+ */
+ boolean isOnline();
+
+ /**
+ * Returns <code>true</code> if the device is an emulator.
+ */
+ boolean isEmulator();
+
+ /**
+ * Returns if the device is offline.
+ *
+ * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#OFFLINE}.
+ */
+ boolean isOffline();
+
+ /**
+ * Returns if the device is in bootloader mode.
+ *
+ * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#BOOTLOADER}.
+ */
+ boolean isBootLoader();
+
+ /**
+ * Returns whether the {@link Device} has {@link Client}s.
+ */
+ boolean hasClients();
+
+ /**
+ * Returns the array of clients.
+ */
+ Client[] getClients();
+
+ /**
+ * Returns a {@link Client} by its application name.
+ *
+ * @param applicationName the name of the application
+ * @return the <code>Client</code> object or <code>null</code> if no match was found.
+ */
+ Client getClient(String applicationName);
+
+ /**
+ * Returns a {@link SyncService} object to push / pull files to and from the device.
+ *
+ * @return <code>null</code> if the SyncService couldn't be created. This can happen if adb
+ * refuse to open the connection because the {@link IDevice} is invalid
+ * (or got disconnected).
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException if the connection with adb failed.
+ */
+ SyncService getSyncService()
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Returns a {@link FileListingService} for this device.
+ */
+ FileListingService getFileListingService();
+
+ /**
+ * Takes a screen shot of the device and returns it as a {@link RawImage}.
+ *
+ * @return the screenshot as a <code>RawImage</code> or <code>null</code> if something
+ * went wrong.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ RawImage getScreenshot() throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ RawImage getScreenshot(long timeout, TimeUnit unit)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Initiates screen recording on the device if the device supports {@link Feature#SCREEN_RECORD}.
+ */
+ void startScreenRecorder(@NonNull String remoteFilePath,
+ @NonNull ScreenRecorderOptions options, @NonNull IShellOutputReceiver receiver) throws
+ TimeoutException, AdbCommandRejectedException, IOException,
+ ShellCommandUnresponsiveException;
+
+ /**
+ * @deprecated Use {@link #executeShellCommand(String, IShellOutputReceiver, long, java.util.concurrent.TimeUnit)}.
+ */
+ @Deprecated
+ void executeShellCommand(String command, IShellOutputReceiver receiver,
+ int maxTimeToOutputResponse)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException;
+
+ /**
+ * Executes a shell command on the device, and sends the result to a <var>receiver</var>
+ * <p/>This is similar to calling
+ * <code>executeShellCommand(command, receiver, DdmPreferences.getTimeOut())</code>.
+ *
+ * @param command the shell command to execute
+ * @param receiver the {@link IShellOutputReceiver} that will receives the output of the shell
+ * command
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws ShellCommandUnresponsiveException in case the shell command doesn't send output
+ * for a given time.
+ * @throws IOException in case of I/O error on the connection.
+ *
+ * @see #executeShellCommand(String, IShellOutputReceiver, int)
+ * @see DdmPreferences#getTimeOut()
+ */
+ void executeShellCommand(String command, IShellOutputReceiver receiver)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException;
+
+ /**
+ * Runs the event log service and outputs the event log to the {@link LogReceiver}.
+ * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+ * @param receiver the receiver to receive the event log entries.
+ * @throws TimeoutException in case of timeout on the connection. This can only be thrown if the
+ * timeout happens during setup. Once logs start being received, no timeout will occur as it's
+ * not possible to detect a difference between no log and timeout.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ void runEventLogService(LogReceiver receiver)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Runs the log service for the given log and outputs the log to the {@link LogReceiver}.
+ * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+ *
+ * @param logname the logname of the log to read from.
+ * @param receiver the receiver to receive the event log entries.
+ * @throws TimeoutException in case of timeout on the connection. This can only be thrown if the
+ * timeout happens during setup. Once logs start being received, no timeout will
+ * occur as it's not possible to detect a difference between no log and timeout.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ void runLogService(String logname, LogReceiver receiver)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Creates a port forwarding between a local and a remote port.
+ *
+ * @param localPort the local port to forward
+ * @param remotePort the remote port.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ void createForward(int localPort, int remotePort)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Creates a port forwarding between a local TCP port and a remote Unix Domain Socket.
+ *
+ * @param localPort the local port to forward
+ * @param remoteSocketName name of the unix domain socket created on the device
+ * @param namespace namespace in which the unix domain socket was created
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ void createForward(int localPort, String remoteSocketName,
+ DeviceUnixSocketNamespace namespace)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Removes a port forwarding between a local and a remote port.
+ *
+ * @param localPort the local port to forward
+ * @param remotePort the remote port.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ void removeForward(int localPort, int remotePort)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Removes an existing port forwarding between a local and a remote port.
+ *
+ * @param localPort the local port to forward
+ * @param remoteSocketName the remote unix domain socket name.
+ * @param namespace namespace in which the unix domain socket was created
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ */
+ void removeForward(int localPort, String remoteSocketName,
+ DeviceUnixSocketNamespace namespace)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Returns the name of the client by pid or <code>null</code> if pid is unknown
+ * @param pid the pid of the client.
+ */
+ String getClientName(int pid);
+
+ /**
+ * Push a single file.
+ * @param local the local filepath.
+ * @param remote The remote filepath.
+ *
+ * @throws IOException in case of I/O error on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws TimeoutException in case of a timeout reading responses from the device.
+ * @throws SyncException if file could not be pushed
+ */
+ void pushFile(String local, String remote)
+ throws IOException, AdbCommandRejectedException, TimeoutException, SyncException;
+
+ /**
+ * Pulls a single file.
+ *
+ * @param remote the full path to the remote file
+ * @param local The local destination.
+ *
+ * @throws IOException in case of an IO exception.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws TimeoutException in case of a timeout reading responses from the device.
+ * @throws SyncException in case of a sync exception.
+ */
+ void pullFile(String remote, String local)
+ throws IOException, AdbCommandRejectedException, TimeoutException, SyncException;
+
+ /**
+ * Installs an Android application on device. This is a helper method that combines the
+ * syncPackageToDevice, installRemotePackage, and removePackage steps
+ *
+ * @param packageFilePath the absolute file system path to file on local host to install
+ * @param reinstall set to <code>true</code> if re-install of app should be performed
+ * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
+ * available options.
+ * @throws InstallException if the installation fails.
+ */
+ void installPackage(String packageFilePath, boolean reinstall, String... extraArgs)
+ throws InstallException;
+
+ /**
+ * Installs an Android application made of several APK files (one main and 0..n split packages)
+ *
+ * @param apks list of apks to install (1 main APK + 0..n split apks)
+ * @param reinstall set to <code>true</code> if re-install of app should be performed
+ * @param installOptions optional extra arguments to pass. See 'adb shell pm install --help' for
+ * available options.
+ * @param timeout installation timeout
+ * @param timeoutUnit {@link TimeUnit} corresponding to the timeout parameter
+ * @throws InstallException if the installation fails.
+ */
+ void installPackages(@NonNull List<File> apks, boolean reinstall,
+ @NonNull List<String> installOptions, long timeout, @NonNull TimeUnit timeoutUnit)
+ throws InstallException;
+
+ /**
+ * Pushes a file to device
+ *
+ * @param localFilePath the absolute path to file on local host
+ * @return {@link String} destination path on device for file
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException in case of I/O error on the connection.
+ * @throws SyncException if an error happens during the push of the package on the device.
+ */
+ String syncPackageToDevice(String localFilePath)
+ throws TimeoutException, AdbCommandRejectedException, IOException, SyncException;
+
+ /**
+ * Installs the application package that was pushed to a temporary location on the device.
+ *
+ * @param remoteFilePath absolute file path to package file on device
+ * @param reinstall set to <code>true</code> if re-install of app should be performed
+ * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
+ * available options.
+ * @throws InstallException if the installation fails.
+ */
+ void installRemotePackage(String remoteFilePath, boolean reinstall,
+ String... extraArgs) throws InstallException;
+
+ /**
+ * Removes a file from device.
+ *
+ * @param remoteFilePath path on device of file to remove
+ * @throws InstallException if the installation fails.
+ */
+ void removeRemotePackage(String remoteFilePath) throws InstallException;
+
+ /**
+ * Uninstalls an package from the device.
+ *
+ * @param packageName the Android application package name to uninstall
+ * @return a {@link String} with an error code, or <code>null</code> if success.
+ * @throws InstallException if the uninstallation fails.
+ */
+ String uninstallPackage(String packageName) throws InstallException;
+
+ /**
+ * Reboot the device.
+ *
+ * @param into the bootloader name to reboot into, or null to just reboot the device.
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command
+ * @throws IOException
+ */
+ void reboot(String into)
+ throws TimeoutException, AdbCommandRejectedException, IOException;
+
+ /**
+ * Ask the adb daemon to become root on the device.
+ * This may silently fail, and can only succeed on developer builds.
+ * See "adb root" for more information.
+ *
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command.
+ * @throws ShellCommandUnresponsiveException if the root status cannot be queried.
+ * @throws IOException
+ * @return true if the adb daemon is running as root, otherwise false.
+ */
+ boolean root() throws TimeoutException, AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException;
+
+ /**
+ * Queries the current root-status of the device.
+ * See "adb root" for more information.
+ *
+ * @throws TimeoutException in case of timeout on the connection.
+ * @throws AdbCommandRejectedException if adb rejects the command.
+ * @return true if the adb daemon is running as root, otherwise false.
+ */
+ boolean isRoot() throws TimeoutException, AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException;
+
+ /**
+ * Return the device's battery level, from 0 to 100 percent.
+ * <p/>
+ * The battery level may be cached. Only queries the device for its
+ * battery level if 5 minutes have expired since the last successful query.
+ *
+ * @return the battery level or <code>null</code> if it could not be retrieved
+ * @deprecated use {@link #getBattery()}
+ */
+ @Deprecated
+ Integer getBatteryLevel() throws TimeoutException,
+ AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException;
+
+ /**
+ * Return the device's battery level, from 0 to 100 percent.
+ * <p/>
+ * The battery level may be cached. Only queries the device for its
+ * battery level if <code>freshnessMs</code> ms have expired since the last successful query.
+ *
+ * @param freshnessMs
+ * @return the battery level or <code>null</code> if it could not be retrieved
+ * @throws ShellCommandUnresponsiveException
+ * @deprecated use {@link #getBattery(long, TimeUnit))}
+ */
+ @Deprecated
+ Integer getBatteryLevel(long freshnessMs) throws TimeoutException,
+ AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException;
+
+ /**
+ * Return the device's battery level, from 0 to 100 percent.
+ * <p/>
+ * The battery level may be cached. Only queries the device for its
+ * battery level if 5 minutes have expired since the last successful query.
+ *
+ * @return a {@link Future} that can be used to query the battery level. The Future will return
+ * a {@link ExecutionException} if battery level could not be retrieved.
+ */
+ @NonNull
+ Future<Integer> getBattery();
+
+ /**
+ * Return the device's battery level, from 0 to 100 percent.
+ * <p/>
+ * The battery level may be cached. Only queries the device for its
+ * battery level if <code>freshnessTime</code> has expired since the last successful query.
+ *
+ * @param freshnessTime the desired recency of battery level
+ * @param timeUnit the {@link TimeUnit} of freshnessTime
+ * @return a {@link Future} that can be used to query the battery level. The Future will return
+ * a {@link ExecutionException} if battery level could not be retrieved.
+ */
+ @NonNull
+ Future<Integer> getBattery(long freshnessTime, @NonNull TimeUnit timeUnit);
+
+
+ /**
+ * Returns the ABIs supported by this device. The ABIs are sorted in preferred order, with the
+ * first ABI being the most preferred.
+ * @return the list of ABIs.
+ */
+ @NonNull
+ List<String> getAbis();
+
+ /**
+ * Returns the density bucket of the device screen by reading the value for system property
+ * {@link #PROP_DEVICE_DENSITY}.
+ *
+ * @return the density, or -1 if it cannot be determined.
+ */
+ int getDensity();
+
+ /**
+ * Returns the user's language.
+ *
+ * @return the user's language, or null if it's unknown
+ */
+ @Nullable
+ String getLanguage();
+
+ /**
+ * Returns the user's region.
+ *
+ * @return the user's region, or null if it's unknown
+ */
+ @Nullable
+ String getRegion();
+
+ /**
+ * Returns the API level of the device.
+ *
+ * @return the API level if it can be determined, {@link AndroidVersion#DEFAULT} otherwise.
+ */
+ @NonNull
+ AndroidVersion getVersion();
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/IShellEnabledDevice.java b/ddmlib/src/main/java/com/android/ddmlib/IShellEnabledDevice.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/IShellEnabledDevice.java
rename to ddmlib/src/main/java/com/android/ddmlib/IShellEnabledDevice.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/IShellOutputReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/IShellOutputReceiver.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/IShellOutputReceiver.java
rename to ddmlib/src/main/java/com/android/ddmlib/IShellOutputReceiver.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/IStackTraceInfo.java b/ddmlib/src/main/java/com/android/ddmlib/IStackTraceInfo.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/IStackTraceInfo.java
rename to ddmlib/src/main/java/com/android/ddmlib/IStackTraceInfo.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/InstallException.java b/ddmlib/src/main/java/com/android/ddmlib/InstallException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/InstallException.java
rename to ddmlib/src/main/java/com/android/ddmlib/InstallException.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/JdwpHandshake.java b/ddmlib/src/main/java/com/android/ddmlib/JdwpHandshake.java
new file mode 100644
index 0000000..ac7bb4d
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/JdwpHandshake.java
@@ -0,0 +1,74 @@
+/*
+ * 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.ddmlib;
+
+import java.nio.ByteBuffer;
+
+public class JdwpHandshake {
+ // results from findHandshake
+ public static final int HANDSHAKE_GOOD = 1;
+ public static final int HANDSHAKE_NOTYET = 2;
+ public static final int HANDSHAKE_BAD = 3;
+ // this is sent and expected at the start of a JDWP connection
+ private static final byte[] HANDSHAKE = {
+ 'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'
+ };
+ public static final int HANDSHAKE_LEN = HANDSHAKE.length;
+
+ /**
+ * Like findPacket(), but when we're expecting the JDWP handshake.
+ *
+ * Returns one of:
+ * HANDSHAKE_GOOD - found handshake, looks good
+ * HANDSHAKE_BAD - found enough data, but it's wrong
+ * HANDSHAKE_NOTYET - not enough data has been read yet
+ */
+ static int findHandshake(ByteBuffer buf) {
+ int count = buf.position();
+ int i;
+
+ if (count < HANDSHAKE.length)
+ return HANDSHAKE_NOTYET;
+
+ for (i = HANDSHAKE.length - 1; i >= 0; --i) {
+ if (buf.get(i) != HANDSHAKE[i])
+ return HANDSHAKE_BAD;
+ }
+
+ return HANDSHAKE_GOOD;
+ }
+
+ /**
+ * Remove the handshake string from the buffer.
+ *
+ * On entry and exit, "position" is the #of bytes in the buffer.
+ */
+ static void consumeHandshake(ByteBuffer buf) {
+ // in theory, nothing else can have arrived, so this is overkill
+ buf.flip(); // limit<-posn, posn<-0
+ buf.position(HANDSHAKE.length);
+ buf.compact(); // shift posn...limit, posn<-pending data
+ }
+
+ /**
+ * Copy the handshake string into the output buffer.
+ *
+ * On exit, "buf"s position will be advanced.
+ */
+ static void putHandshake(ByteBuffer buf) {
+ buf.put(HANDSHAKE);
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/JdwpPacket.java b/ddmlib/src/main/java/com/android/ddmlib/JdwpPacket.java
new file mode 100644
index 0000000..19e8588
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/JdwpPacket.java
@@ -0,0 +1,292 @@
+/* //device/tools/ddms/libs/ddmlib/src/com/android/ddmlib/JdwpPacket.java
+**
+** Copyright 2007, 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.ddmlib;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.SocketChannel;
+
+/**
+ * A JDWP packet, sitting at the start of a ByteBuffer somewhere.
+ *
+ * This allows us to wrap a "pointer" to the data with the results of
+ * decoding the packet.
+ *
+ * None of the operations here are synchronized. If multiple threads will
+ * be accessing the same ByteBuffers, external sync will be required.
+ *
+ * Use the constructor to create an empty packet, or "findPacket()" to
+ * wrap a JdwpPacket around existing data.
+ */
+public final class JdwpPacket {
+ public static final int JDWP_HEADER_LEN = 11;
+
+ private static final int REPLY_PACKET = 0x80;
+
+ private ByteBuffer mBuffer;
+ private int mLength;
+ private int mId;
+ private int mFlags;
+ private int mCmdSet;
+ private int mCmd;
+ private int mErrCode;
+
+ private static int sSerialId = 0x40000000;
+
+
+ /**
+ * Create a new, empty packet, in "buf".
+ */
+ JdwpPacket(ByteBuffer buf) {
+ mBuffer = buf;
+ }
+
+ /**
+ * Finish a packet created with newPacket().
+ *
+ * This always creates a command packet, with the next serial number
+ * in sequence.
+ *
+ * We have to take "payloadLength" as an argument because we can't
+ * see the position in the "slice" returned by getPayload(). We could
+ * fish it out of the chunk header, but it's legal for there to be
+ * more than one chunk in a JDWP packet.
+ *
+ * On exit, "position" points to the end of the data.
+ */
+ void finishPacket(int cmdSet, int cmd, int payloadLength) {
+
+ ByteOrder oldOrder = mBuffer.order();
+ mBuffer.order(ChunkHandler.CHUNK_ORDER);
+
+ mLength = JDWP_HEADER_LEN + payloadLength;
+ mId = getNextSerial();
+ mFlags = 0;
+ mCmdSet = cmdSet;
+ mCmd = cmd;
+
+ mBuffer.putInt(0x00, mLength);
+ mBuffer.putInt(0x04, mId);
+ mBuffer.put(0x08, (byte) mFlags);
+ mBuffer.put(0x09, (byte) mCmdSet);
+ mBuffer.put(0x0a, (byte) mCmd);
+
+ mBuffer.order(oldOrder);
+ mBuffer.position(mLength);
+ }
+
+ /**
+ * Get the next serial number. This creates a unique serial number
+ * across all connections, not just for the current connection. This
+ * is a useful property when debugging, but isn't necessary.
+ *
+ * We can't synchronize on an int, so we use a sync method.
+ */
+ private static synchronized int getNextSerial() {
+ return sSerialId++;
+ }
+
+ /**
+ * Return a slice of the byte buffer, positioned past the JDWP header
+ * to the start of the chunk header. The buffer's limit will be set
+ * to the size of the payload if the size is known; if this is a
+ * packet under construction the limit will be set to the end of the
+ * buffer.
+ *
+ * Doesn't examine the packet at all -- works on empty buffers.
+ */
+ public ByteBuffer getPayload() {
+ ByteBuffer buf;
+ int oldPosn = mBuffer.position();
+
+ mBuffer.position(JDWP_HEADER_LEN);
+ buf = mBuffer.slice(); // goes from position to limit
+ mBuffer.position(oldPosn);
+
+ if (mLength > 0)
+ buf.limit(mLength - JDWP_HEADER_LEN);
+ buf.order(ChunkHandler.CHUNK_ORDER);
+ return buf;
+ }
+
+ /**
+ * Returns "true" if this JDWP packet is tagged as a reply.
+ */
+ public boolean isReply() {
+ return (mFlags & REPLY_PACKET) != 0;
+ }
+
+ /**
+ * Returns "true" if this JDWP packet is a reply with a nonzero
+ * error code.
+ */
+ boolean isError() {
+ return isReply() && mErrCode != 0;
+ }
+
+ /**
+ * Returns "true" if this JDWP packet has no data.
+ */
+ boolean isEmpty() {
+ return (mLength == JDWP_HEADER_LEN);
+ }
+
+ /**
+ * Return the packet's ID. For a reply packet, this allows us to
+ * match the reply with the original request.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Return the length of a packet. This includes the header, so an
+ * empty packet is 11 bytes long.
+ */
+ int getLength() {
+ return mLength;
+ }
+
+ /**
+ * Write our packet to "chan".
+ *
+ * The JDWP packet starts at offset 0 and ends at mBuffer.position().
+ */
+ void write(SocketChannel chan) throws IOException {
+ assert mLength > 0;
+
+ int oldPosn = mBuffer.position();
+ mBuffer.position(0);
+ mBuffer.limit(mLength);
+
+ while (mBuffer.position() != mBuffer.limit()) {
+ chan.write(mBuffer);
+ }
+ // position should now be at end of packet
+ assert mBuffer.position() == mLength;
+
+ mBuffer.limit(mBuffer.capacity());
+ mBuffer.position(oldPosn);
+ }
+
+ /**
+ * "Move" the packet data out of the buffer we're sitting on and into
+ * buf at the current position.
+ */
+ void move(ByteBuffer buf) {
+ int oldPosn = mBuffer.position();
+
+ mBuffer.position(0);
+ mBuffer.limit(mLength);
+ buf.put(mBuffer);
+
+ mBuffer.limit(mBuffer.capacity());
+ mBuffer.position(oldPosn);
+ }
+
+ /**
+ * Consume the JDWP packet.
+ *
+ * On entry and exit, "position" is at the end of data in buffer.
+ */
+ void consume()
+ {
+ /*
+ * The "flip" call sets "limit" equal to the position (usually the
+ * end of data) and "position" equal to zero.
+ *
+ * compact() copies everything from "position" and "limit" to the
+ * start of the buffer, sets "position" to the end of data, and
+ * sets "limit" to the capacity.
+ *
+ * On entry, "position" is set to the amount of data in the buffer
+ * and "limit" is set to the capacity. We want to call flip()
+ * so that position..limit spans our data, advance "position" past
+ * the current packet, then compact.
+ */
+ mBuffer.flip();
+ mBuffer.position(mLength);
+ mBuffer.compact();
+ mLength = 0;
+ }
+
+ /**
+ * Find the JDWP packet at the start of "buf". The start is known,
+ * but the length has to be parsed out.
+ *
+ * On entry, the packet data in "buf" must start at offset 0 and end
+ * at "position". "limit" should be set to the buffer capacity. This
+ * method does not alter "buf"s attributes.
+ *
+ * Returns a new JdwpPacket if a full one is found in the buffer. If
+ * not, returns null. Throws an exception if the data doesn't look like
+ * a valid JDWP packet.
+ */
+ static JdwpPacket findPacket(ByteBuffer buf) {
+ int count = buf.position();
+ int length, id, flags, cmdSet, cmd;
+
+ if (count < JDWP_HEADER_LEN)
+ return null;
+
+ ByteOrder oldOrder = buf.order();
+ buf.order(ChunkHandler.CHUNK_ORDER);
+
+ length = buf.getInt(0x00);
+ id = buf.getInt(0x04);
+ flags = buf.get(0x08) & 0xff;
+ cmdSet = buf.get(0x09) & 0xff;
+ cmd = buf.get(0x0a) & 0xff;
+
+ buf.order(oldOrder);
+
+ if (length < JDWP_HEADER_LEN)
+ throw new BadPacketException();
+ if (count < length)
+ return null;
+
+ JdwpPacket pkt = new JdwpPacket(buf);
+ //pkt.mBuffer = buf;
+ pkt.mLength = length;
+ pkt.mId = id;
+ pkt.mFlags = flags;
+
+ if ((flags & REPLY_PACKET) == 0) {
+ pkt.mCmdSet = cmdSet;
+ pkt.mCmd = cmd;
+ pkt.mErrCode = -1;
+ } else {
+ pkt.mCmdSet = -1;
+ pkt.mCmd = -1;
+ pkt.mErrCode = cmdSet | (cmd << 8);
+ }
+
+ return pkt;
+ }
+
+ @Override
+ public String toString() {
+ return isReply() ? " < # " + mId : " > " + mCmdSet + "." + mCmd + " # " + mId;
+ }
+
+ public boolean is(int cmdSet, int cmd) {
+ return cmdSet == mCmdSet && cmd == mCmd;
+ }
+}
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Log.java b/ddmlib/src/main/java/com/android/ddmlib/Log.java
new file mode 100644
index 0000000..f63dc0f
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/Log.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.Sets;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Log class that mirrors the API in main Android sources.
+ * <p/>Default behavior outputs the log to {@link System#out}. Use
+ * {@link #addLogger(ILogOutput)} to redirect the log somewhere else.
+ */
+public final class Log {
+
+ /**
+ * Log Level enum.
+ */
+ public enum LogLevel {
+ VERBOSE(2, "verbose", 'V'), //$NON-NLS-1$
+ DEBUG(3, "debug", 'D'), //$NON-NLS-1$
+ INFO(4, "info", 'I'), //$NON-NLS-1$
+ WARN(5, "warn", 'W'), //$NON-NLS-1$
+ ERROR(6, "error", 'E'), //$NON-NLS-1$
+ ASSERT(7, "assert", 'A'); //$NON-NLS-1$
+
+ private int mPriorityLevel;
+ private String mStringValue;
+ private char mPriorityLetter;
+
+ LogLevel(int intPriority, String stringValue, char priorityChar) {
+ mPriorityLevel = intPriority;
+ mStringValue = stringValue;
+ mPriorityLetter = priorityChar;
+ }
+
+ public static LogLevel getByString(String value) {
+ for (LogLevel mode : values()) {
+ if (mode.mStringValue.equals(value)) {
+ return mode;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link LogLevel} enum matching the specified letter.
+ * @param letter the letter matching a <code>LogLevel</code> enum
+ * @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
+ */
+ public static LogLevel getByLetter(char letter) {
+ for (LogLevel mode : values()) {
+ if (mode.mPriorityLetter == letter) {
+ return mode;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link LogLevel} enum matching the specified letter.
+ * <p/>
+ * The letter is passed as a {@link String} argument, but only the first character
+ * is used.
+ * @param letter the letter matching a <code>LogLevel</code> enum
+ * @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
+ */
+ public static LogLevel getByLetterString(String letter) {
+ if (!letter.isEmpty()) {
+ return getByLetter(letter.charAt(0));
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the letter identifying the priority of the {@link LogLevel}.
+ */
+ public char getPriorityLetter() {
+ return mPriorityLetter;
+ }
+
+ /**
+ * Returns the numerical value of the priority.
+ */
+ public int getPriority() {
+ return mPriorityLevel;
+ }
+
+ /**
+ * Returns a non translated string representing the LogLevel.
+ */
+ public String getStringValue() {
+ return mStringValue;
+ }
+ }
+
+ /**
+ * Classes which implement this interface provides methods that deal with outputting log
+ * messages.
+ */
+ public interface ILogOutput {
+ /**
+ * Sent when a log message needs to be printed.
+ * @param logLevel The {@link LogLevel} enum representing the priority of the message.
+ * @param tag The tag associated with the message.
+ * @param message The message to display.
+ */
+ void printLog(LogLevel logLevel, String tag, String message);
+
+ /**
+ * Sent when a log message needs to be printed, and, if possible, displayed to the user
+ * in a dialog box.
+ * @param logLevel The {@link LogLevel} enum representing the priority of the message.
+ * @param tag The tag associated with the message.
+ * @param message The message to display.
+ */
+ void printAndPromptLog(LogLevel logLevel, String tag, String message);
+ }
+
+ private static LogLevel sLevel = DdmPreferences.getLogLevel();
+
+ @Nullable
+ private static ILogOutput sLogOutput;
+
+ @NonNull
+ private static final Set<ILogOutput> sOutputLoggers = Sets.newCopyOnWriteArraySet();
+
+ private static final char[] mSpaceLine = new char[72];
+ private static final char[] mHexDigit = new char[]
+ { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
+ static {
+ /* prep for hex dump */
+ int i = mSpaceLine.length-1;
+ while (i >= 0)
+ mSpaceLine[i--] = ' ';
+ mSpaceLine[0] = mSpaceLine[1] = mSpaceLine[2] = mSpaceLine[3] = '0';
+ mSpaceLine[4] = '-';
+ }
+
+ static final class Config {
+ static final boolean LOGV = true;
+ static final boolean LOGD = true;
+ }
+
+ private Log() {}
+
+ /**
+ * Outputs a {@link LogLevel#VERBOSE} level message.
+ * @param tag The tag associated with the message.
+ * @param message The message to output.
+ */
+ public static void v(String tag, String message) {
+ println(LogLevel.VERBOSE, tag, message);
+ }
+
+ /**
+ * Outputs a {@link LogLevel#DEBUG} level message.
+ * @param tag The tag associated with the message.
+ * @param message The message to output.
+ */
+ public static void d(String tag, String message) {
+ println(LogLevel.DEBUG, tag, message);
+ }
+
+ /**
+ * Outputs a {@link LogLevel#INFO} level message.
+ * @param tag The tag associated with the message.
+ * @param message The message to output.
+ */
+ public static void i(String tag, String message) {
+ println(LogLevel.INFO, tag, message);
+ }
+
+ /**
+ * Outputs a {@link LogLevel#WARN} level message.
+ * @param tag The tag associated with the message.
+ * @param message The message to output.
+ */
+ public static void w(String tag, String message) {
+ println(LogLevel.WARN, tag, message);
+ }
+
+ /**
+ * Outputs a {@link LogLevel#ERROR} level message.
+ * @param tag The tag associated with the message.
+ * @param message The message to output.
+ */
+ public static void e(String tag, String message) {
+ println(LogLevel.ERROR, tag, message);
+ }
+
+ /**
+ * Outputs a log message and attempts to display it in a dialog.
+ * @param tag The tag associated with the message.
+ * @param message The message to output.
+ */
+ public static void logAndDisplay(LogLevel logLevel, String tag, String message) {
+ if (!sOutputLoggers.isEmpty()) {
+ for (ILogOutput logger : sOutputLoggers) {
+ logger.printAndPromptLog(logLevel, tag, message);
+ }
+ }
+
+ if (sLogOutput != null) {
+ sLogOutput.printAndPromptLog(logLevel, tag, message);
+ } else if (sOutputLoggers.isEmpty()) {
+ println(logLevel, tag, message);
+ }
+ }
+
+ /**
+ * Outputs a {@link LogLevel#ERROR} level {@link Throwable} information.
+ * @param tag The tag associated with the message.
+ * @param throwable The {@link Throwable} to output.
+ */
+ public static void e(String tag, Throwable throwable) {
+ if (throwable != null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ throwable.printStackTrace(pw);
+ println(LogLevel.ERROR, tag, throwable.getMessage() + '\n' + sw.toString());
+ }
+ }
+
+ static void setLevel(LogLevel logLevel) {
+ sLevel = logLevel;
+ }
+
+ /**
+ * @deprecated Use {@link #addLogger(ILogOutput)} instead. <p/>
+ * Sets the {@link ILogOutput} to use to print the logs. If not set, {@link System#out}
+ * will be used.
+ * @param logOutput The {@link ILogOutput} to use to print the log.
+ */
+ @Deprecated
+ public static void setLogOutput(ILogOutput logOutput) {
+ sLogOutput = logOutput;
+ }
+
+ public static void addLogger(@NonNull ILogOutput logOutput) {
+ sOutputLoggers.add(logOutput);
+ }
+
+ public static void removeLogger(@NonNull ILogOutput logOutput) {
+ sOutputLoggers.remove(logOutput);
+ }
+
+ /**
+ * Show hex dump.
+ * <p/>
+ * Local addition. Output looks like:
+ * 1230- 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef
+ * <p/>
+ * Uses no string concatenation; creates one String object per line.
+ */
+ static void hexDump(String tag, LogLevel level, byte[] data, int offset, int length) {
+
+ int kHexOffset = 6;
+ int kAscOffset = 55;
+ char[] line = new char[mSpaceLine.length];
+ int addr, baseAddr, count;
+ int i, ch;
+ boolean needErase = true;
+
+ //Log.w(tag, "HEX DUMP: off=" + offset + ", length=" + length);
+
+ baseAddr = 0;
+ while (length != 0) {
+ if (length > 16) {
+ // full line
+ count = 16;
+ } else {
+ // partial line; re-copy blanks to clear end
+ count = length;
+ needErase = true;
+ }
+
+ if (needErase) {
+ System.arraycopy(mSpaceLine, 0, line, 0, mSpaceLine.length);
+ needErase = false;
+ }
+
+ // output the address (currently limited to 4 hex digits)
+ addr = baseAddr;
+ addr &= 0xffff;
+ ch = 3;
+ while (addr != 0) {
+ line[ch] = mHexDigit[addr & 0x0f];
+ ch--;
+ addr >>>= 4;
+ }
+
+ // output hex digits and ASCII chars
+ ch = kHexOffset;
+ for (i = 0; i < count; i++) {
+ byte val = data[offset + i];
+
+ line[ch++] = mHexDigit[(val >>> 4) & 0x0f];
+ line[ch++] = mHexDigit[val & 0x0f];
+ ch++;
+
+ if (val >= 0x20 && val < 0x7f)
+ line[kAscOffset + i] = (char) val;
+ else
+ line[kAscOffset + i] = '.';
+ }
+
+ println(level, tag, new String(line));
+
+ // advance to next chunk of data
+ length -= count;
+ offset += count;
+ baseAddr += count;
+ }
+
+ }
+
+ /**
+ * Dump the entire contents of a byte array with DEBUG priority.
+ */
+ static void hexDump(byte[] data) {
+ hexDump("ddms", LogLevel.DEBUG, data, 0, data.length);
+ }
+
+ private static void println(LogLevel logLevel, String tag, String message) {
+ if (logLevel.getPriority() < sLevel.getPriority()) {
+ return;
+ }
+
+ if (!sOutputLoggers.isEmpty()) {
+ for (ILogOutput logger : sOutputLoggers) {
+ logger.printLog(logLevel, tag, message);
+ }
+ }
+
+ if (sLogOutput != null) {
+ sLogOutput.printLog(logLevel, tag, message);
+ } else if (sOutputLoggers.isEmpty()) {
+ printLog(logLevel, tag, message);
+ }
+ }
+
+ public static void printLog(LogLevel logLevel, String tag, String message) {
+ System.out.print(getLogFormatString(logLevel, tag, message));
+ }
+
+ public static String getLogFormatString(LogLevel logLevel, String tag, String message) {
+ SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss", Locale.getDefault());
+ return String.format("%s %c/%s: %s\n", formatter.format(new Date()),
+ logLevel.getPriorityLetter(), tag, message);
+ }
+}
+
+
diff --git a/ddmlib/src/main/java/com/android/ddmlib/MonitorThread.java b/ddmlib/src/main/java/com/android/ddmlib/MonitorThread.java
new file mode 100644
index 0000000..2c847e2
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/MonitorThread.java
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+
+import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.jdwp.JdwpExtension;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.BufferOverflowException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.NotYetBoundException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Monitor open connections.
+ */
+final class MonitorThread extends Thread {
+
+ private final DdmJdwpExtension mDdmJdwpExtension;
+
+ private volatile boolean mQuit = false;
+
+ // List of clients we're paying attention to
+ // Used for locking so final.
+ final private ArrayList<Client> mClientList;
+
+ // The almighty mux
+ private Selector mSelector;
+
+ private final List<JdwpExtension> mJdwpExtensions;
+
+ // port for "debug selected"
+ private ServerSocketChannel mDebugSelectedChan;
+
+ private int mNewDebugSelectedPort;
+
+ private int mDebugSelectedPort = -1;
+
+ /**
+ * "Selected" client setup to answer debugging connection to the mNewDebugSelectedPort port.
+ */
+ private Client mSelectedClient = null;
+
+ // singleton
+ private static MonitorThread sInstance;
+
+ /**
+ * Generic constructor.
+ */
+ private MonitorThread() {
+ super("Monitor");
+ mClientList = new ArrayList<Client>();
+
+ mNewDebugSelectedPort = DdmPreferences.getSelectedDebugPort();
+
+ mDdmJdwpExtension = new DdmJdwpExtension();
+ mJdwpExtensions = new LinkedList<JdwpExtension>();
+ mJdwpExtensions.add(mDdmJdwpExtension);
+ }
+
+ /**
+ * Creates and return the singleton instance of the client monitor thread.
+ */
+ static MonitorThread createInstance() {
+ return sInstance = new MonitorThread();
+ }
+
+ /**
+ * Get singleton instance of the client monitor thread.
+ */
+ static MonitorThread getInstance() {
+ return sInstance;
+ }
+
+
+ /**
+ * Sets or changes the port number for "debug selected".
+ */
+ synchronized void setDebugSelectedPort(int port) throws IllegalStateException {
+ if (sInstance == null) {
+ return;
+ }
+
+ if (!AndroidDebugBridge.getClientSupport()) {
+ return;
+ }
+
+ if (mDebugSelectedChan != null) {
+ Log.d("ddms", "Changing debug-selected port to " + port);
+ mNewDebugSelectedPort = port;
+ wakeup();
+ } else {
+ // we set mNewDebugSelectedPort instead of mDebugSelectedPort so that it's automatically
+ // opened on the first run loop.
+ mNewDebugSelectedPort = port;
+ }
+ }
+
+ /**
+ * Sets the client to accept debugger connection on the custom "Selected debug port".
+ * @param selectedClient the client. Can be null.
+ */
+ synchronized void setSelectedClient(Client selectedClient) {
+ if (sInstance == null) {
+ return;
+ }
+
+ if (mSelectedClient != selectedClient) {
+ Client oldClient = mSelectedClient;
+ mSelectedClient = selectedClient;
+
+ if (oldClient != null) {
+ oldClient.update(Client.CHANGE_PORT);
+ }
+
+ if (mSelectedClient != null) {
+ mSelectedClient.update(Client.CHANGE_PORT);
+ }
+ }
+ }
+
+ /**
+ * Returns the client accepting debugger connection on the custom "Selected debug port".
+ */
+ Client getSelectedClient() {
+ return mSelectedClient;
+ }
+
+
+ /**
+ * Returns "true" if we want to retry connections to clients if we get a bad
+ * JDWP handshake back, "false" if we want to just mark them as bad and
+ * leave them alone.
+ */
+ boolean getRetryOnBadHandshake() {
+ return true; // TODO? make configurable
+ }
+
+ /**
+ * Get an array of known clients.
+ */
+ Client[] getClients() {
+ synchronized (mClientList) {
+ return mClientList.toArray(new Client[mClientList.size()]);
+ }
+ }
+
+ /**
+ * Register "handler" as the handler for type "type".
+ */
+ synchronized void registerChunkHandler(int type, ChunkHandler handler) {
+ if (sInstance == null) {
+ return;
+ }
+ mDdmJdwpExtension.registerHandler(type, handler);
+ }
+
+ /**
+ * Watch for activity from clients and debuggers.
+ */
+ @Override
+ public void run() {
+ Log.d("ddms", "Monitor is up");
+
+ // create a selector
+ try {
+ mSelector = Selector.open();
+ } catch (IOException ioe) {
+ Log.logAndDisplay(LogLevel.ERROR, "ddms",
+ "Failed to initialize Monitor Thread: " + ioe.getMessage());
+ return;
+ }
+
+ while (!mQuit) {
+
+ try {
+ /*
+ * sync with new registrations: we wait until addClient is done before going through
+ * and doing mSelector.select() again.
+ * @see {@link #addClient(Client)}
+ */
+ synchronized (mClientList) {
+ }
+
+ // (re-)open the "debug selected" port, if it's not opened yet or
+ // if the port changed.
+ try {
+ if (AndroidDebugBridge.getClientSupport()) {
+ if ((mDebugSelectedChan == null ||
+ mNewDebugSelectedPort != mDebugSelectedPort) &&
+ mNewDebugSelectedPort != -1) {
+ if (reopenDebugSelectedPort()) {
+ mDebugSelectedPort = mNewDebugSelectedPort;
+ }
+ }
+ }
+ } catch (IOException ioe) {
+ Log.e("ddms",
+ "Failed to reopen debug port for Selected Client to: " + mNewDebugSelectedPort);
+ Log.e("ddms", ioe);
+ mNewDebugSelectedPort = mDebugSelectedPort; // no retry
+ }
+
+ int count;
+ try {
+ count = mSelector.select();
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ continue;
+ } catch (CancelledKeyException cke) {
+ continue;
+ }
+
+ if (count == 0) {
+ // somebody called wakeup() ?
+ // Log.i("ddms", "selector looping");
+ continue;
+ }
+
+ Set<SelectionKey> keys = mSelector.selectedKeys();
+ Iterator<SelectionKey> iter = keys.iterator();
+
+ while (iter.hasNext()) {
+ SelectionKey key = iter.next();
+ iter.remove();
+
+ try {
+ if (key.attachment() instanceof Client) {
+ processClientActivity(key);
+ }
+ else if (key.attachment() instanceof Debugger) {
+ processDebuggerActivity(key);
+ }
+ else if (key.attachment() instanceof MonitorThread) {
+ processDebugSelectedActivity(key);
+ }
+ else {
+ Log.e("ddms", "unknown activity key");
+ }
+ } catch (Exception e) {
+ // we don't want to have our thread be killed because of any uncaught
+ // exception, so we intercept all here.
+ Log.e("ddms", "Exception during activity from Selector.");
+ Log.e("ddms", e);
+ }
+ }
+ } catch (Exception e) {
+ // we don't want to have our thread be killed because of any uncaught
+ // exception, so we intercept all here.
+ Log.e("ddms", "Exception MonitorThread.run()");
+ Log.e("ddms", e);
+ }
+ }
+ }
+
+
+ /**
+ * Returns the port on which the selected client listen for debugger
+ */
+ int getDebugSelectedPort() {
+ return mDebugSelectedPort;
+ }
+
+ /*
+ * Something happened. Figure out what.
+ */
+ private void processClientActivity(SelectionKey key) {
+ Client client = (Client)key.attachment();
+
+ try {
+ if (!key.isReadable() || !key.isValid()) {
+ Log.d("ddms", "Invalid key from " + client + ". Dropping client.");
+ dropClient(client, true /* notify */);
+ return;
+ }
+
+ client.read();
+
+ /*
+ * See if we have a full packet in the buffer. It's possible we have
+ * more than one packet, so we have to loop.
+ */
+ JdwpPacket packet = client.getJdwpPacket();
+ while (packet != null) {
+ client.incoming(packet, client.getDebugger());
+
+ packet.consume();
+ // find next
+ packet = client.getJdwpPacket();
+ }
+ } catch (CancelledKeyException e) {
+ // key was canceled probably due to a disconnected client before we could
+ // read stuff coming from the client, so we drop it.
+ dropClient(client, true /* notify */);
+ } catch (IOException ex) {
+ // something closed down, no need to print anything. The client is simply dropped.
+ dropClient(client, true /* notify */);
+ } catch (Exception ex) {
+ Log.e("ddms", ex);
+
+ /* close the client; automatically un-registers from selector */
+ dropClient(client, true /* notify */);
+
+ if (ex instanceof BufferOverflowException) {
+ Log.w("ddms",
+ "Client data packet exceeded maximum buffer size "
+ + client);
+ } else {
+ // don't know what this is, display it
+ Log.e("ddms", ex);
+ }
+ }
+ }
+
+ /**
+ * Drops a client from the monitor.
+ * <p/>This will lock the {@link Client} list of the {@link Device} running <var>client</var>.
+ * @param client
+ * @param notify
+ */
+ synchronized void dropClient(Client client, boolean notify) {
+ if (sInstance == null) {
+ return;
+ }
+
+ synchronized (mClientList) {
+ if (!mClientList.remove(client)) {
+ return;
+ }
+ }
+ client.close(notify);
+ mDdmJdwpExtension.broadcast(DdmJdwpExtension.Event.CLIENT_DISCONNECTED, client);
+
+ /*
+ * http://forum.java.sun.com/thread.jspa?threadID=726715&start=0
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5073504
+ */
+ wakeup();
+ }
+
+ /**
+ * Drops the provided list of clients from the monitor. This will lock the {@link Client}
+ * list of the {@link Device} running each of the clients.
+ */
+ synchronized void dropClients(Collection<? extends Client> clients, boolean notify) {
+ for (Client c : clients) {
+ dropClient(c, notify);
+ }
+ }
+
+ /*
+ * Process activity from one of the debugger sockets. This could be a new
+ * connection or a data packet.
+ */
+ private void processDebuggerActivity(SelectionKey key) {
+ Debugger dbg = (Debugger)key.attachment();
+
+ try {
+ if (key.isAcceptable()) {
+ try {
+ acceptNewDebugger(dbg, null);
+ } catch (IOException ioe) {
+ Log.w("ddms", "debugger accept() failed");
+ ioe.printStackTrace();
+ }
+ } else if (key.isReadable()) {
+ processDebuggerData(key);
+ } else {
+ Log.d("ddm-debugger", "key in unknown state");
+ }
+ } catch (CancelledKeyException cke) {
+ // key has been cancelled we can ignore that.
+ }
+ }
+
+ /*
+ * Accept a new connection from a debugger. If successful, register it with
+ * the Selector.
+ */
+ private void acceptNewDebugger(Debugger dbg, ServerSocketChannel acceptChan)
+ throws IOException {
+
+ synchronized (mClientList) {
+ SocketChannel chan;
+
+ if (acceptChan == null)
+ chan = dbg.accept();
+ else
+ chan = dbg.accept(acceptChan);
+
+ if (chan != null) {
+ chan.socket().setTcpNoDelay(true);
+
+ wakeup();
+
+ try {
+ chan.register(mSelector, SelectionKey.OP_READ, dbg);
+ } catch (IOException ioe) {
+ // failed, drop the connection
+ dbg.closeData();
+ throw ioe;
+ } catch (RuntimeException re) {
+ // failed, drop the connection
+ dbg.closeData();
+ throw re;
+ }
+ } else {
+ Log.w("ddms", "ignoring duplicate debugger");
+ // new connection already closed
+ }
+ }
+ }
+
+ /*
+ * We have incoming data from the debugger. Forward it to the client.
+ */
+ private void processDebuggerData(SelectionKey key) {
+ Debugger dbg = (Debugger)key.attachment();
+
+ try {
+ /*
+ * Read pending data.
+ */
+ dbg.read();
+
+ /*
+ * See if we have a full packet in the buffer. It's possible we have
+ * more than one packet, so we have to loop.
+ */
+ JdwpPacket packet = dbg.getJdwpPacket();
+ while (packet != null) {
+ Log.v("ddms", "Forwarding dbg req 0x"
+ + Integer.toHexString(packet.getId()) + " to "
+ + dbg.getClient());
+
+ dbg.incoming(packet, dbg.getClient());
+
+ packet.consume();
+ packet = dbg.getJdwpPacket();
+ }
+ } catch (IOException ioe) {
+ /*
+ * Close data connection; automatically un-registers dbg from
+ * selector. The failure could be caused by the debugger going away,
+ * or by the client going away and failing to accept our data.
+ * Either way, the debugger connection does not need to exist any
+ * longer. We also need to recycle the connection to the client, so
+ * that the VM sees the debugger disconnect. For a DDM-aware client
+ * this won't be necessary, and we can just send a "debugger
+ * disconnected" message.
+ */
+ Log.d("ddms", "Closing connection to debugger " + dbg);
+ dbg.closeData();
+ Client client = dbg.getClient();
+ if (client.isDdmAware()) {
+ // TODO: soft-disconnect DDM-aware clients
+ Log.d("ddms", " (recycling client connection as well)");
+
+ // we should drop the client, but also attempt to reopen it.
+ // This is done by the DeviceMonitor.
+ client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client,
+ IDebugPortProvider.NO_STATIC_PORT);
+ } else {
+ Log.d("ddms", " (recycling client connection as well)");
+ // we should drop the client, but also attempt to reopen it.
+ // This is done by the DeviceMonitor.
+ client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client,
+ IDebugPortProvider.NO_STATIC_PORT);
+ }
+ }
+ }
+
+ /*
+ * Tell the thread that something has changed.
+ */
+ private void wakeup() {
+ mSelector.wakeup();
+ }
+
+ /**
+ * Tell the thread to stop. Called from UI thread.
+ */
+ synchronized void quit() {
+ mQuit = true;
+ wakeup();
+ Log.d("ddms", "Waiting for Monitor thread");
+ try {
+ this.join();
+ // since we're quitting, lets drop all the client and disconnect
+ // the DebugSelectedPort
+ synchronized (mClientList) {
+ for (Client c : mClientList) {
+ c.close(false /* notify */);
+ mDdmJdwpExtension.broadcast(DdmJdwpExtension.Event.CLIENT_DISCONNECTED, c);
+ }
+ mClientList.clear();
+ }
+
+ if (mDebugSelectedChan != null) {
+ mDebugSelectedChan.close();
+ mDebugSelectedChan.socket().close();
+ mDebugSelectedChan = null;
+ }
+ mSelector.close();
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ sInstance = null;
+ }
+
+ /**
+ * Add a new Client to the list of things we monitor. Also adds the client's
+ * channel and the client's debugger listener to the selection list. This
+ * should only be called from one thread (the VMWatcherThread) to avoid a
+ * race between "alreadyOpen" and Client creation.
+ */
+ synchronized void addClient(Client client) {
+ if (sInstance == null) {
+ return;
+ }
+
+ Log.d("ddms", "Adding new client " + client);
+
+ synchronized (mClientList) {
+ mClientList.add(client);
+
+ for (JdwpExtension extension : mJdwpExtensions) {
+ extension.intercept(client);
+ }
+
+ /*
+ * Register the Client's socket channel with the selector. We attach
+ * the Client to the SelectionKey. If you try to register a new
+ * channel with the Selector while it is waiting for I/O, you will
+ * block. The solution is to call wakeup() and then hold a lock to
+ * ensure that the registration happens before the Selector goes
+ * back to sleep.
+ */
+ try {
+ wakeup();
+
+ client.register(mSelector);
+
+ Debugger dbg = client.getDebugger();
+ if (dbg != null) {
+ dbg.registerListener(mSelector);
+ }
+ } catch (IOException ioe) {
+ // not really expecting this to happen
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Opens (or reopens) the "debug selected" port and listen for connections.
+ * @return true if the port was opened successfully.
+ * @throws IOException
+ */
+ private boolean reopenDebugSelectedPort() throws IOException {
+
+ Log.d("ddms", "reopen debug-selected port: " + mNewDebugSelectedPort);
+ if (mDebugSelectedChan != null) {
+ mDebugSelectedChan.close();
+ }
+
+ mDebugSelectedChan = ServerSocketChannel.open();
+ mDebugSelectedChan.configureBlocking(false); // required for Selector
+
+ InetSocketAddress addr = new InetSocketAddress(
+ InetAddress.getByName("localhost"), //$NON-NLS-1$
+ mNewDebugSelectedPort);
+ mDebugSelectedChan.socket().setReuseAddress(true); // enable SO_REUSEADDR
+
+ try {
+ mDebugSelectedChan.socket().bind(addr);
+ if (mSelectedClient != null) {
+ mSelectedClient.update(Client.CHANGE_PORT);
+ }
+
+ mDebugSelectedChan.register(mSelector, SelectionKey.OP_ACCEPT, this);
+
+ return true;
+ } catch (java.net.BindException e) {
+ displayDebugSelectedBindError(mNewDebugSelectedPort);
+
+ // do not attempt to reopen it.
+ mDebugSelectedChan = null;
+ mNewDebugSelectedPort = -1;
+
+ return false;
+ }
+ }
+
+ /*
+ * We have some activity on the "debug selected" port. Handle it.
+ */
+ private void processDebugSelectedActivity(SelectionKey key) {
+ assert key.isAcceptable();
+
+ ServerSocketChannel acceptChan = (ServerSocketChannel)key.channel();
+
+ /*
+ * Find the debugger associated with the currently-selected client.
+ */
+ if (mSelectedClient != null) {
+ Debugger dbg = mSelectedClient.getDebugger();
+
+ if (dbg != null) {
+ Log.d("ddms", "Accepting connection on 'debug selected' port");
+ try {
+ acceptNewDebugger(dbg, acceptChan);
+ } catch (IOException ioe) {
+ // client should be gone, keep going
+ }
+
+ return;
+ }
+ }
+
+ Log.w("ddms",
+ "Connection on 'debug selected' port, but none selected");
+ try {
+ SocketChannel chan = acceptChan.accept();
+ chan.close();
+ } catch (IOException ioe) {
+ // not expected; client should be gone, keep going
+ } catch (NotYetBoundException e) {
+ displayDebugSelectedBindError(mDebugSelectedPort);
+ }
+ }
+
+ private void displayDebugSelectedBindError(int port) {
+ String message = String.format(
+ "Could not open Selected VM debug port (%1$d). Make sure you do not have another instance of DDMS or of the eclipse plugin running. If it's being used by something else, choose a new port number in the preferences.",
+ port);
+
+ Log.logAndDisplay(LogLevel.ERROR, "ddms", message);
+ }
+
+ public DdmJdwpExtension getDdmExtension() {
+ return mDdmJdwpExtension;
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java
rename to ddmlib/src/main/java/com/android/ddmlib/MultiLineReceiver.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/NativeAllocationInfo.java b/ddmlib/src/main/java/com/android/ddmlib/NativeAllocationInfo.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/NativeAllocationInfo.java
rename to ddmlib/src/main/java/com/android/ddmlib/NativeAllocationInfo.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/NativeLibraryMapInfo.java b/ddmlib/src/main/java/com/android/ddmlib/NativeLibraryMapInfo.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/NativeLibraryMapInfo.java
rename to ddmlib/src/main/java/com/android/ddmlib/NativeLibraryMapInfo.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java b/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java
new file mode 100644
index 0000000..4785833
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/NativeStackCallInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 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.ddmlib;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a stack call. This is used to return all of the call
+ * information as one object.
+ */
+public final class NativeStackCallInfo {
+ private static final Pattern SOURCE_NAME_PATTERN = Pattern.compile("^(.+):(\\d+)(\\s+\\(discriminator\\s+\\d+\\))?$");
+
+ /** address of this stack frame */
+ private long mAddress;
+
+ /** name of the library */
+ private String mLibrary;
+
+ /** name of the method */
+ private String mMethod;
+
+ /**
+ * name of the source file + line number in the format<br>
+ * <sourcefile>:<linenumber>
+ */
+ private String mSourceFile;
+
+ private int mLineNumber = -1;
+
+ /**
+ * Basic constructor with library, method, and sourcefile information
+ *
+ * @param address address of this stack frame
+ * @param lib The name of the library
+ * @param method the name of the method
+ * @param sourceFile the name of the source file and the line number
+ * as "[sourcefile]:[fileNumber]"
+ */
+ public NativeStackCallInfo(long address, String lib, String method, String sourceFile) {
+ mAddress = address;
+ mLibrary = lib;
+ mMethod = method;
+
+ Matcher m = SOURCE_NAME_PATTERN.matcher(sourceFile);
+ if (m.matches()) {
+ mSourceFile = m.group(1);
+ try {
+ mLineNumber = Integer.parseInt(m.group(2));
+ } catch (NumberFormatException e) {
+ // do nothing, the line number will stay at -1
+ }
+ if (m.groupCount() == 3 && m.group(3) != null) {
+ // A discriminator was found, add that in the source file name.
+ mSourceFile += m.group(3);
+ }
+ } else {
+ mSourceFile = sourceFile;
+ }
+ }
+
+ /**
+ * Returns the address of this stack frame.
+ */
+ public long getAddress() {
+ return mAddress;
+ }
+
+ /**
+ * Returns the name of the library name.
+ */
+ public String getLibraryName() {
+ return mLibrary;
+ }
+
+ /**
+ * Returns the name of the method.
+ */
+ public String getMethodName() {
+ return mMethod;
+ }
+
+ /**
+ * Returns the name of the source file.
+ */
+ public String getSourceFile() {
+ return mSourceFile;
+ }
+
+ /**
+ * Returns the line number, or -1 if unknown.
+ */
+ public int getLineNumber() {
+ return mLineNumber;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("\t%1$08x\t%2$s --- %3$s --- %4$s:%5$d",
+ getAddress(), getLibraryName(), getMethodName(), getSourceFile(), getLineNumber());
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/NullOutputReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/NullOutputReceiver.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/NullOutputReceiver.java
rename to ddmlib/src/main/java/com/android/ddmlib/NullOutputReceiver.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/PropertyFetcher.java b/ddmlib/src/main/java/com/android/ddmlib/PropertyFetcher.java
new file mode 100644
index 0000000..6179632
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/PropertyFetcher.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Fetches and caches 'getprop' values from device.
+ */
+class PropertyFetcher {
+ /** the amount of time to wait between unsuccessful prop fetch attempts */
+ private static final String GETPROP_COMMAND = "getprop"; //$NON-NLS-1$
+ private static final Pattern GETPROP_PATTERN = Pattern.compile("^\\[([^]]+)\\]\\:\\s*\\[(.*)\\]$"); //$NON-NLS-1$
+ private static final int GETPROP_TIMEOUT_SEC = 2;
+ private static final int EXPECTED_PROP_COUNT = 150;
+
+ private enum CacheState {
+ UNPOPULATED, FETCHING, POPULATED
+ }
+
+ /**
+ * Shell output parser for a getprop command
+ */
+ @VisibleForTesting
+ static class GetPropReceiver extends MultiLineReceiver {
+
+ private final Map<String, String> mCollectedProperties =
+ Maps.newHashMapWithExpectedSize(EXPECTED_PROP_COUNT);
+
+ @Override
+ public void processNewLines(String[] lines) {
+ // We receive an array of lines. We're expecting
+ // to have the build info in the first line, and the build
+ // date in the 2nd line. There seems to be an empty line
+ // after all that.
+
+ for (String line : lines) {
+ if (line.isEmpty() || line.startsWith("#")) {
+ continue;
+ }
+
+ Matcher m = GETPROP_PATTERN.matcher(line);
+ if (m.matches()) {
+ String label = m.group(1);
+ String value = m.group(2);
+
+ if (!label.isEmpty()) {
+ mCollectedProperties.put(label, value);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ Map<String, String> getCollectedProperties() {
+ return mCollectedProperties;
+ }
+ }
+
+ private final Map<String, String> mProperties = Maps.newHashMapWithExpectedSize(
+ EXPECTED_PROP_COUNT);
+ private final IDevice mDevice;
+ private CacheState mCacheState = CacheState.UNPOPULATED;
+ private final Map<String, SettableFuture<String>> mPendingRequests =
+ Maps.newHashMapWithExpectedSize(4);
+
+ public PropertyFetcher(IDevice device) {
+ mDevice = device;
+ }
+
+ /**
+ * Returns the full list of cached properties.
+ */
+ public synchronized Map<String, String> getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Make a possibly asynchronous request for a system property value.
+ *
+ * @param name the property name to retrieve
+ * @return a {@link Future} that can be used to retrieve the prop value
+ */
+ @NonNull
+ public synchronized Future<String> getProperty(@NonNull String name) {
+ SettableFuture<String> result;
+ if (mCacheState.equals(CacheState.FETCHING)) {
+ result = addPendingRequest(name);
+ } else if (mDevice.isOnline() && mCacheState.equals(CacheState.UNPOPULATED) || !isRoProp(name)) {
+ // cache is empty, or this is a volatile prop that requires a query
+ result = addPendingRequest(name);
+ mCacheState = CacheState.FETCHING;
+ initiatePropertiesQuery();
+ } else {
+ result = SettableFuture.create();
+ // cache is populated and this is a ro prop
+ result.set(mProperties.get(name));
+ }
+ return result;
+ }
+
+ private SettableFuture<String> addPendingRequest(String name) {
+ SettableFuture<String> future = mPendingRequests.get(name);
+ if (future == null) {
+ future = SettableFuture.create();
+ mPendingRequests.put(name, future);
+ }
+ return future;
+ }
+
+ private void initiatePropertiesQuery() {
+ String threadName = String.format("query-prop-%s", mDevice.getSerialNumber());
+ Thread propThread = new Thread(threadName) {
+ @Override
+ public void run() {
+ try {
+ GetPropReceiver propReceiver = new GetPropReceiver();
+ mDevice.executeShellCommand(GETPROP_COMMAND, propReceiver, GETPROP_TIMEOUT_SEC,
+ TimeUnit.SECONDS);
+ populateCache(propReceiver.getCollectedProperties());
+ } catch (Exception e) {
+ handleException(e);
+ }
+ }
+ };
+ propThread.setDaemon(true);
+ propThread.start();
+ }
+
+ private synchronized void populateCache(@NonNull Map<String, String> props) {
+ mCacheState = props.isEmpty() ? CacheState.UNPOPULATED : CacheState.POPULATED;
+ if (!props.isEmpty()) {
+ mProperties.putAll(props);
+ }
+ for (Map.Entry<String, SettableFuture<String>> entry : mPendingRequests.entrySet()) {
+ entry.getValue().set(mProperties.get(entry.getKey()));
+ }
+ mPendingRequests.clear();
+ }
+
+ private synchronized void handleException(Exception e) {
+ mCacheState = CacheState.UNPOPULATED;
+ Log.w("PropertyFetcher",
+ String.format("%s getting properties for device %s: %s",
+ e.getClass().getSimpleName(), mDevice.getSerialNumber(),
+ e.getMessage()));
+ for (Map.Entry<String, SettableFuture<String>> entry : mPendingRequests.entrySet()) {
+ entry.getValue().setException(e);
+ }
+ mPendingRequests.clear();
+ }
+
+ /**
+ * Return true if cache is populated.
+ *
+ * @deprecated implementation detail
+ */
+ @Deprecated
+ public synchronized boolean arePropertiesSet() {
+ return CacheState.POPULATED.equals(mCacheState);
+ }
+
+ private static boolean isRoProp(@NonNull String propName) {
+ return propName.startsWith("ro.") || propName.equals(IDevice.PROP_DEVICE_EMULATOR_DENSITY);
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/RawImage.java b/ddmlib/src/main/java/com/android/ddmlib/RawImage.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/RawImage.java
rename to ddmlib/src/main/java/com/android/ddmlib/RawImage.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/ScreenRecorderOptions.java b/ddmlib/src/main/java/com/android/ddmlib/ScreenRecorderOptions.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/ScreenRecorderOptions.java
rename to ddmlib/src/main/java/com/android/ddmlib/ScreenRecorderOptions.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/ShellCommandUnresponsiveException.java b/ddmlib/src/main/java/com/android/ddmlib/ShellCommandUnresponsiveException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/ShellCommandUnresponsiveException.java
rename to ddmlib/src/main/java/com/android/ddmlib/ShellCommandUnresponsiveException.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/SplitApkInstaller.java b/ddmlib/src/main/java/com/android/ddmlib/SplitApkInstaller.java
new file mode 100644
index 0000000..10a786c
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/SplitApkInstaller.java
@@ -0,0 +1,269 @@
+/*
+ * 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.ddmlib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SplitApkInstaller {
+ private static final String LOG_TAG = "SplitApkInstaller";
+
+ @NonNull private final IDevice mDevice;
+ @NonNull private final List<File> mApks;
+ @NonNull private final String mOptions;
+
+ private SplitApkInstaller(@NonNull IDevice device, @NonNull List<File> apks,
+ @NonNull String options) {
+ mDevice = device;
+ mApks = apks;
+ mOptions = options;
+ }
+
+ public void install(long timeout, @NonNull TimeUnit unit) throws InstallException {
+ // Installing multiple APK's is perfomed as follows:
+ // # First we create a install session passing in the total size of all APKs
+ // $ pm install-create -S <total_size>
+ // Success: [integer-session-id] # error if session-id < 0
+ // # Then for each APK, we perform the following. A unique id per APK is generated
+ // # as <index>_<name>, the - at the end means that the APK is streamed via stdin
+ // $ pm install-write -S <session-id> <per_apk_unique_id> -
+ // # Finally, we close the session
+ // $ pm install-commit <session-id> (or)
+ // $ pm install-abandon <session-id>
+
+ try {
+ // create a installation session.
+ String sessionId = createMultiInstallSession(mApks, mOptions, timeout, unit);
+ if (sessionId == null) {
+ Log.d(LOG_TAG, "Failed to establish session, quit installation");
+ throw new InstallException("Failed to establish session");
+ }
+
+ // now upload each APK in turn.
+ int index = 0;
+ boolean allUploadSucceeded = true;
+ while (allUploadSucceeded && index < mApks.size()) {
+ allUploadSucceeded = uploadApk(sessionId, mApks.get(index), index++, timeout,
+ unit);
+ }
+
+ // if all files were upload successfully, commit otherwise abandon the installation.
+ String command = "pm install-" +
+ (allUploadSucceeded ? "commit " : "abandon ") +
+ sessionId;
+ Device.InstallReceiver receiver = new Device.InstallReceiver();
+ mDevice.executeShellCommand(command, receiver, timeout, unit);
+ String errorMessage = receiver.getErrorMessage();
+ if (errorMessage != null) {
+ String message = String.format("Failed to finalize session : %1$s", errorMessage);
+ Log.e(LOG_TAG, message);
+ throw new InstallException(message);
+ }
+
+ if (!allUploadSucceeded) {
+ throw new InstallException("Failed to install all ");
+ }
+ } catch (InstallException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new InstallException(e);
+ }
+ }
+
+ @Nullable
+ private String createMultiInstallSession(@NonNull List<File> apkFiles,
+ @NonNull String pmOptions, long timeout, @NonNull TimeUnit unit)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+
+ long totalFileSize = 0L;
+ for (File apkFile : apkFiles) {
+ totalFileSize += apkFile.length();
+ }
+
+ MultiInstallReceiver receiver = new MultiInstallReceiver();
+ String cmd = String.format("pm install-create %1$s -S %2$d", pmOptions, totalFileSize);
+ mDevice.executeShellCommand(cmd, receiver, timeout, unit);
+ return receiver.getSessionId();
+ }
+
+ private static final CharMatcher UNSAFE_PM_INSTALL_SESSION_SPLIT_NAME_CHARS =
+ CharMatcher.inRange('a','z').or(CharMatcher.inRange('A','Z'))
+ .or(CharMatcher.anyOf("_-")).negate();
+
+ private boolean uploadApk(@NonNull String sessionId, @NonNull File fileToUpload, int uniqueId,
+ long timeout, @NonNull TimeUnit unit) {
+ Log.d(sessionId, String.format("Uploading APK %1$s ", fileToUpload.getPath()));
+ if (!fileToUpload.exists()) {
+ Log.e(sessionId, String.format("File not found: %1$s", fileToUpload.getPath()));
+ return false;
+ }
+ if (fileToUpload.isDirectory()) {
+ Log.e(sessionId, String.format("Directory upload not supported: %1$s",
+ fileToUpload.getAbsolutePath()));
+ return false;
+ }
+ String baseName = fileToUpload.getName().lastIndexOf('.') != -1
+ ? fileToUpload.getName().substring(0, fileToUpload.getName().lastIndexOf('.'))
+ : fileToUpload.getName();
+
+ baseName = UNSAFE_PM_INSTALL_SESSION_SPLIT_NAME_CHARS.replaceFrom(baseName, '_');
+
+ String command = String.format("pm install-write -S %d %s %d_%s -",
+ fileToUpload.length(), sessionId, uniqueId, baseName);
+
+ Log.d(sessionId, String.format("Executing : %1$s", command));
+ InputStream inputStream = null;
+ try {
+ inputStream = new BufferedInputStream(new FileInputStream(fileToUpload));
+ Device.InstallReceiver receiver = new Device.InstallReceiver();
+ AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(),
+ AdbHelper.AdbService.EXEC, command, mDevice,
+ receiver, timeout, unit, inputStream);
+ if (receiver.getErrorMessage() != null) {
+ Log.e(sessionId, String.format("Error while uploading %1$s : %2$s", fileToUpload.getName(),
+ receiver.getErrorMessage()));
+ } else {
+ Log.d(sessionId, String.format("Successfully uploaded %1$s", fileToUpload.getName()));
+ }
+ return receiver.getErrorMessage() == null;
+ } catch (Exception e) {
+ Log.e(sessionId, e);
+ return false;
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ Log.e(sessionId, e);
+ }
+ }
+
+ }
+ }
+
+ @NonNull
+ private static String getOptions(boolean reInstall, @NonNull List<String> pmOptions) {
+ return getOptions(reInstall, false, null, pmOptions);
+ }
+
+ @NonNull
+ private static String getOptions(boolean reInstall, boolean partialInstall,
+ @Nullable String applicationId, List<String> pmOptions) {
+ StringBuilder sb = new StringBuilder();
+
+ if (reInstall) {
+ sb.append("-r ");
+ }
+
+ if (partialInstall) {
+ if (applicationId == null) {
+ throw new IllegalArgumentException(
+ "Cannot do a partial install without knowing the application id");
+ }
+
+ sb.append("-r ");
+ sb.append(applicationId);
+ sb.append(' ');
+ }
+
+ sb.append(Joiner.on(' ').join(pmOptions));
+
+ return sb.toString();
+ }
+
+ private static void validateArguments(@NonNull IDevice device, @NonNull List<File> apks) {
+ if (apks.isEmpty()) {
+ throw new IllegalArgumentException(
+ "List of APKs is empty: the main APK must be specified.");
+ }
+
+ for (File apk: apks) {
+ if (!apk.isFile()) {
+ throw new IllegalArgumentException("Invalid File: " + apk.getPath());
+ }
+ }
+
+ if (!device.getVersion().isGreaterOrEqualThan(21)) {
+ if (apks.size() > 1) {
+ throw new IllegalArgumentException(
+ "Cannot install split APKs on device with API level < 21");
+ }
+ }
+ }
+
+ /**
+ * Returns a {@link SplitApkInstaller} for the given list of APKs on the given device.
+ * @param apks list of APKs, must include at least the main APK
+ */
+ public static SplitApkInstaller create(@NonNull IDevice device, @NonNull List<File> apks,
+ boolean reInstall, @NonNull List<String> pmOptions) {
+ validateArguments(device, apks);
+ return new SplitApkInstaller(device, apks, getOptions(reInstall, pmOptions));
+ }
+
+ public static SplitApkInstaller create(@NonNull IDevice device, @NonNull String applicationId,
+ @NonNull List<File> apks, boolean reInstall, @NonNull List<String> pmOptions) {
+ validateArguments(device, apks);
+ return new SplitApkInstaller(device, apks,
+ getOptions(reInstall, true, applicationId, pmOptions));
+ }
+
+ /**
+ * Implementation of {@link com.android.ddmlib.MultiLineReceiver} that can receive a
+ * Success message from ADB followed by a session ID.
+ */
+ private static class MultiInstallReceiver extends MultiLineReceiver {
+
+ private static final Pattern successPattern = Pattern.compile("Success: .*\\[(\\d*)\\]");
+
+ @Nullable String sessionId = null;
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ Matcher matcher = successPattern.matcher(line);
+ if (matcher.matches()) {
+ sessionId = matcher.group(1);
+ }
+ }
+ }
+
+ @Nullable
+ public String getSessionId() {
+ return sessionId;
+ }
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/SyncException.java b/ddmlib/src/main/java/com/android/ddmlib/SyncException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/SyncException.java
rename to ddmlib/src/main/java/com/android/ddmlib/SyncException.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/SyncService.java b/ddmlib/src/main/java/com/android/ddmlib/SyncService.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/SyncService.java
rename to ddmlib/src/main/java/com/android/ddmlib/SyncService.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/ThreadInfo.java b/ddmlib/src/main/java/com/android/ddmlib/ThreadInfo.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/ThreadInfo.java
rename to ddmlib/src/main/java/com/android/ddmlib/ThreadInfo.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/TimeoutException.java b/ddmlib/src/main/java/com/android/ddmlib/TimeoutException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/TimeoutException.java
rename to ddmlib/src/main/java/com/android/ddmlib/TimeoutException.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpAgent.java b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpAgent.java
new file mode 100644
index 0000000..191a71d
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpAgent.java
@@ -0,0 +1,101 @@
+/*
+ * 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.ddmlib.jdwp;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.JdwpPacket;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public abstract class JdwpAgent {
+
+ /**
+ * Interceptors waiting for a specific reply id.
+ */
+ @NonNull
+ private final ConcurrentMap<Integer, JdwpInterceptor> mReplyInterceptors;
+
+ @NonNull
+ private final List<JdwpInterceptor> mInterceptors;
+
+ @NonNull
+ private final JdwpProtocol mProtocol;
+
+ public JdwpAgent(@NonNull JdwpProtocol protocol) {
+ mReplyInterceptors = new ConcurrentHashMap<Integer, JdwpInterceptor>();
+ mInterceptors = new LinkedList<JdwpInterceptor>();
+ mProtocol = protocol;
+ }
+
+ /**
+ * Adds an interceptor for a specific reply id. Once this interceptor
+ * handles the response, it will be removed.
+ */
+ protected void addReplyInterceptor(int id, @NonNull JdwpInterceptor interceptor) {
+ mReplyInterceptors.put(id, interceptor);
+ }
+
+ /**
+ * Removes, if present, the interceptor to handle a reply with the given id.
+ */
+ protected void removeReplyInterceptor(int id) {
+ mReplyInterceptors.remove(id);
+ }
+
+ public void clear() {
+ mReplyInterceptors.clear();
+ }
+
+ public void addJdwpInterceptor(@NonNull JdwpInterceptor interceptor) {
+ mInterceptors.add(interceptor);
+ }
+
+ public void incoming(@NonNull JdwpPacket packet, @Nullable JdwpAgent target) throws IOException {
+ mProtocol.incoming(packet, target);
+ int id = packet.getId();
+ if (packet.isReply()) {
+ JdwpInterceptor interceptor = mReplyInterceptors.remove(id);
+ if (interceptor != null) {
+ packet = interceptor.intercept(this, packet);
+ }
+ }
+ for (JdwpInterceptor interceptor : mInterceptors) {
+ if (packet == null) break;
+ packet = interceptor.intercept(this, packet);
+ }
+
+ if (target != null && packet != null) {
+ target.send(packet);
+ }
+ }
+
+ public void send(@NonNull JdwpPacket packet, @NonNull JdwpInterceptor interceptor) throws IOException {
+ mReplyInterceptors.put(packet.getId(), interceptor);
+ send(packet);
+ }
+
+ protected abstract void send(@NonNull JdwpPacket packet) throws IOException;
+
+ @NonNull
+ public JdwpProtocol getJdwpProtocol() {
+ return mProtocol;
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpCommands.java b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpCommands.java
new file mode 100644
index 0000000..cb06970
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpCommands.java
@@ -0,0 +1,138 @@
+/*
+ * 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.ddmlib.jdwp;
+
+/**
+ * JDWP command constants as specified here:
+ * http://docs.oracle.com/javase/7/docs/platform/jpda/jdwp/jdwp-protocol.html
+ */
+public class JdwpCommands {
+ public static final int SET_VM = 1;
+ public static final int CMD_VM_VERSION = 1;
+ public static final int CMD_VM_CLASSESBYSIGNATURE = 2;
+ public static final int CMD_VM_ALLCLASSES = 3;
+ public static final int CMD_VM_ALLTHREADS = 4;
+ public static final int CMD_VM_TOPLEVELTHREADGROUPS = 5;
+ public static final int CMD_VM_DISPOSE = 6;
+ public static final int CMD_VM_IDSIZES = 7;
+ public static final int CMD_VM_SUSPEND = 8;
+ public static final int CMD_VM_RESUME = 9;
+ public static final int CMD_VM_EXIT = 10;
+ public static final int CMD_VM_CREATESTRING = 11;
+ public static final int CMD_VM_CAPABILITIES = 12;
+ public static final int CMD_VM_CLASSPATHS = 13;
+ public static final int CMD_VM_DISPOSEOBJECTS = 14;
+ public static final int CMD_VM_HOLDEVENTS = 15;
+ public static final int CMD_VM_RELEASEEVENTS = 16;
+ public static final int CMD_VM_CAPABILITIESNEW = 17;
+ public static final int CMD_VM_REDEFINECLASSES = 18;
+ public static final int CMD_VM_SETDEFAULTSTRATUM = 19;
+ public static final int CMD_VM_ALLCLASSESWITHGENERIC = 20;
+
+ public static final int SET_REFTYPE = 2;
+ public static final int CMD_REFTYPE_SIGNATURE = 1;
+ public static final int CMD_REFTYPE_CLASSLOADER = 2;
+ public static final int CMD_REFTYPE_MODIFIERS = 3;
+ public static final int CMD_REFTYPE_FIELDS = 4;
+ public static final int CMD_REFTYPE_METHODS = 5;
+ public static final int CMD_REFTYPE_GETVALUES = 6;
+ public static final int CMD_REFTYPE_SOURCEFILE = 7;
+ public static final int CMD_REFTYPE_NESTEDTYPES = 8;
+ public static final int CMD_REFTYPE_STATUS = 9;
+ public static final int CMD_REFTYPE_INTERFACES = 10;
+ public static final int CMD_REFTYPE_CLASSOBJECT = 11;
+ public static final int CMD_REFTYPE_SOURCEDEBUGEXTENSION = 12;
+ public static final int CMD_REFTYPE_SIGNATUREWITHGENERIC = 13;
+ public static final int CMD_REFTYPE_FIELDSWITHGENERIC = 14;
+ public static final int CMD_REFTYPE_METHODSWITHGENERIC = 15;
+
+ public static final int SET_CLASSTYPE = 3;
+ public static final int CMD_CLASSTYPE_SUPERCLASS = 1;
+ public static final int CMD_CLASSTYPE_SETVALUES = 2;
+ public static final int CMD_CLASSTYPE_INVOKEMETHOD = 3;
+ public static final int CMD_CLASSTYPE_NEWINSTANCE = 4;
+
+ public static final int SET_ARRAYTYPE = 4;
+ public static final int CMD_ARRAYTYPE_NEWINSTANCE = 1;
+
+ public static final int SET_INTERFACETYPE = 5;
+
+ public static final int SET_METHOD = 6;
+ public static final int CMD_METHOD_LINETABLE = 1;
+ public static final int CMD_METHOD_VARIABLETABLE = 2;
+ public static final int CMD_METHOD_BYTECODES = 3;
+ public static final int CMD_METHOD_ISOBSOLETE = 4;
+ public static final int CMD_METHOD_VARIABLETABLEWITHGENERIC = 5;
+
+ public static final int SET_FIELD = 8;
+
+ public static final int SET_OBJREF = 9;
+ public static final int CMD_OBJREF_REFERENCETYPE = 1;
+ public static final int CMD_OBJREF_GETVALUES = 2;
+ public static final int CMD_OBJREF_SETVALUES = 3;
+ public static final int CMD_OBJREF_MONITORINFO = 5;
+ public static final int CMD_OBJREF_INVOKEMETHOD = 6;
+ public static final int CMD_OBJREF_DISABLECOLLECTION = 7;
+ public static final int CMD_OBJREF_ENABLECOLLECTION = 8;
+ public static final int CMD_OBJREF_ISCOLLECTED = 9;
+
+ public static final int SET_STRINGREF = 10;
+ public static final int CMD_STRINGREF_VALUE = 1;
+
+ public static final int SET_THREADREF = 11;
+ public static final int CMD_THREADREF_NAME = 1;
+ public static final int CMD_THREADREF_SUSPEND = 2;
+ public static final int CMD_THREADREF_RESUME = 3;
+ public static final int CMD_THREADREF_STATUS = 4;
+ public static final int CMD_THREADREF_THREADGROUP = 5;
+ public static final int CMD_THREADREF_FRAMES = 6;
+ public static final int CMD_THREADREF_FRAMECOUNT = 7;
+ public static final int CMD_THREADREF_OWNEDMONITORS = 8;
+ public static final int CMD_THREADREF_CURRENTCONTENDEDMONITOR = 9;
+ public static final int CMD_THREADREF_STOP = 10;
+ public static final int CMD_THREADREF_INTERRUPT = 11;
+ public static final int CMD_THREADREF_SUSPENDCOUNT = 12;
+
+ public static final int SET_THREADGROUPREF = 12;
+ public static final int CMD_THREADGROUPREF_NAME = 1;
+ public static final int CMD_THREADGROUPREF_PARENT = 2;
+ public static final int CMD_THREADGROUPREF_CHILDREN = 3;
+
+ public static final int SET_ARRAYREF = 13;
+ public static final int CMD_ARRAYREF_LENGTH = 1;
+ public static final int CMD_ARRAYREF_GETVALUES = 2;
+ public static final int CMD_ARRAYREF_SETVALUES = 3;
+
+ public static final int SET_CLASSLOADERREF = 14;
+ public static final int CMD_CLASSLOADERREF_VISIBLECLASSES = 1;
+
+ public static final int SET_EVENTREQUEST = 15;
+ public static final int CMD_EVENTREQUEST_SET = 1;
+ public static final int CMD_EVENTREQUEST_CLEAR = 2;
+ public static final int CMD_EVENTREQUEST_CLEARALLBREAKPOINTS = 3;
+
+ public static final int SET_STACKFRAME = 16;
+ public static final int CMD_STACKFRAME_GETVALUES = 1;
+ public static final int CMD_STACKFRAME_SETVALUES = 2;
+ public static final int CMD_STACKFRAME_THISOBJECT = 3;
+ public static final int CMD_STACKFRAME_POPFRAMES = 4;
+
+ public static final int SET_CLASSOBJECTREF = 17;
+ public static final int CMD_CLASSOBJECTREF_REFLECTEDTYPE = 1;
+
+ public static final int SET_EVENT = 64;
+ public static final int CMD_EVENT_COMPOSITE = 100;
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpExtension.java b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpExtension.java
new file mode 100644
index 0000000..eea5530
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpExtension.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ddmlib.jdwp;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.Client;
+
+public abstract class JdwpExtension {
+
+ /**
+ * Allows an extension to register interceptors to capture JDWP traffic.
+ */
+ public abstract void intercept(@NonNull Client client);
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpInterceptor.java b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpInterceptor.java
new file mode 100644
index 0000000..64941be
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpInterceptor.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ddmlib.jdwp;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.JdwpPacket;
+
+public abstract class JdwpInterceptor {
+
+ @Nullable
+ public abstract JdwpPacket intercept(@NonNull JdwpAgent agent, @NonNull JdwpPacket packet);
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpPayload.java b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpPayload.java
new file mode 100644
index 0000000..9a069e0
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpPayload.java
@@ -0,0 +1,25 @@
+/*
+ * 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.ddmlib.jdwp;
+
+import com.android.annotations.NonNull;
+
+import java.nio.ByteBuffer;
+
+public abstract class JdwpPayload {
+
+ public abstract void parse(@NonNull ByteBuffer buffer, @NonNull JdwpProtocol protocol);
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpProtocol.java b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpProtocol.java
new file mode 100644
index 0000000..9b460f9
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/jdwp/JdwpProtocol.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ddmlib.jdwp;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.JdwpPacket;
+import com.android.ddmlib.jdwp.packets.IdSizesReply;
+import com.google.common.base.Charsets;
+
+import java.nio.ByteBuffer;
+
+public class JdwpProtocol {
+
+ @Nullable
+ private IdSizesReply mIdSizes;
+
+ public long readObjectId(@NonNull ByteBuffer buffer) {
+ assert mIdSizes != null;
+ return readId(buffer, mIdSizes.objectIDSize);
+ }
+
+ public long readRefTypeId(@NonNull ByteBuffer buffer) {
+ assert mIdSizes != null;
+ return readId(buffer, mIdSizes.refTypeIDSize);
+ }
+
+ public long readMethodId(@NonNull ByteBuffer buffer) {
+ assert mIdSizes != null;
+ return readId(buffer, mIdSizes.methodIDSize);
+ }
+
+ public long readFieldId(@NonNull ByteBuffer buffer) {
+ assert mIdSizes != null;
+ return readId(buffer, mIdSizes.fieldIDSize);
+ }
+
+ private long readId(@NonNull ByteBuffer buffer, int size) {
+ switch (size) {
+ case 1: return buffer.get();
+ case 2: return buffer.getShort();
+ case 4: return buffer.getInt();
+ case 8: return buffer.getLong();
+ default: throw new IllegalArgumentException("Unsupported Id size: " + size);
+ }
+ }
+
+ public String readString(@NonNull ByteBuffer buffer) {
+ int len = buffer.getInt();
+ byte[] utf8 = new byte[len];
+ buffer.get(utf8);
+ return new String(utf8, Charsets.UTF_8);
+ }
+
+ public void incoming(@NonNull JdwpPacket packet, @NonNull JdwpAgent target) {
+ if (packet.is(JdwpCommands.SET_VM, JdwpCommands.CMD_VM_IDSIZES)) {
+ target.addReplyInterceptor(packet.getId(), new JdwpInterceptor() {
+ @Override
+ public JdwpPacket intercept(@NonNull JdwpAgent agent, @NonNull JdwpPacket packet) {
+ mIdSizes = new IdSizesReply();
+ mIdSizes.parse(packet.getPayload(), JdwpProtocol.this);
+ return packet;
+ }
+ });
+ }
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/jdwp/packets/IdSizesReply.java b/ddmlib/src/main/java/com/android/ddmlib/jdwp/packets/IdSizesReply.java
new file mode 100644
index 0000000..db45254
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/jdwp/packets/IdSizesReply.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ddmlib.jdwp.packets;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.jdwp.JdwpPayload;
+import com.android.ddmlib.jdwp.JdwpProtocol;
+
+import java.nio.ByteBuffer;
+
+public class IdSizesReply extends JdwpPayload {
+
+ public int fieldIDSize;
+ public int methodIDSize;
+ public int objectIDSize;
+ public int refTypeIDSize;
+ public int frameIDSize;
+
+ @Override
+ public void parse(@NonNull ByteBuffer buffer, @NonNull JdwpProtocol protocol) {
+ fieldIDSize = buffer.getInt();
+ methodIDSize = buffer.getInt();
+ objectIDSize = buffer.getInt();
+ refTypeIDSize = buffer.getInt();
+ frameIDSize = buffer.getInt();
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/log/EventContainer.java b/ddmlib/src/main/java/com/android/ddmlib/log/EventContainer.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/log/EventContainer.java
rename to ddmlib/src/main/java/com/android/ddmlib/log/EventContainer.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java b/ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java
rename to ddmlib/src/main/java/com/android/ddmlib/log/EventLogParser.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/log/EventValueDescription.java b/ddmlib/src/main/java/com/android/ddmlib/log/EventValueDescription.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/log/EventValueDescription.java
rename to ddmlib/src/main/java/com/android/ddmlib/log/EventValueDescription.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/log/GcEventContainer.java b/ddmlib/src/main/java/com/android/ddmlib/log/GcEventContainer.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/log/GcEventContainer.java
rename to ddmlib/src/main/java/com/android/ddmlib/log/GcEventContainer.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/log/InvalidTypeException.java b/ddmlib/src/main/java/com/android/ddmlib/log/InvalidTypeException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/log/InvalidTypeException.java
rename to ddmlib/src/main/java/com/android/ddmlib/log/InvalidTypeException.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/log/InvalidValueTypeException.java b/ddmlib/src/main/java/com/android/ddmlib/log/InvalidValueTypeException.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/log/InvalidValueTypeException.java
rename to ddmlib/src/main/java/com/android/ddmlib/log/InvalidValueTypeException.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/log/LogReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/log/LogReceiver.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/log/LogReceiver.java
rename to ddmlib/src/main/java/com/android/ddmlib/log/LogReceiver.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatFilter.java b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatFilter.java
new file mode 100644
index 0000000..151fd3c
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatFilter.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ddmlib.logcat;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.Log.LogLevel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A Filter for logcat messages. A filter can be constructed to match
+ * different fields of a logcat message. It can then be queried to see if
+ * a message matches the filter's settings.
+ */
+public final class LogCatFilter {
+ private static final String PID_KEYWORD = "pid:"; //$NON-NLS-1$
+ private static final String APP_KEYWORD = "app:"; //$NON-NLS-1$
+ private static final String TAG_KEYWORD = "tag:"; //$NON-NLS-1$
+ private static final String TEXT_KEYWORD = "text:"; //$NON-NLS-1$
+
+ private final String mName;
+ private final String mTag;
+ private final String mText;
+ private final String mPid;
+ private final String mAppName;
+ private final LogLevel mLogLevel;
+
+ private boolean mCheckPid;
+ private boolean mCheckAppName;
+ private boolean mCheckTag;
+ private boolean mCheckText;
+
+ private Pattern mAppNamePattern;
+ private Pattern mTagPattern;
+ private Pattern mTextPattern;
+
+ /**
+ * Construct a filter with the provided restrictions for the logcat message. All the text
+ * fields accept Java regexes as input, but ignore invalid regexes.
+ * @param name name for the filter
+ * @param tag value for the logcat message's tag field.
+ * @param text value for the logcat message's text field.
+ * @param pid value for the logcat message's pid field.
+ * @param appName value for the logcat message's app name field.
+ * @param logLevel value for the logcat message's log level. Only messages of
+ * higher priority will be accepted by the filter.
+ */
+ public LogCatFilter(@NonNull String name, @NonNull String tag, @NonNull String text,
+ @NonNull String pid, @NonNull String appName, @NonNull LogLevel logLevel) {
+ mName = name.trim();
+ mTag = tag.trim();
+ mText = text.trim();
+ mPid = pid.trim();
+ mAppName = appName.trim();
+ mLogLevel = logLevel;
+
+ mCheckPid = !mPid.isEmpty();
+
+ if (!mAppName.isEmpty()) {
+ try {
+ mAppNamePattern = Pattern.compile(mAppName, getPatternCompileFlags(mAppName));
+ mCheckAppName = true;
+ } catch (PatternSyntaxException e) {
+ mCheckAppName = false;
+ }
+ }
+
+ if (!mTag.isEmpty()) {
+ try {
+ mTagPattern = Pattern.compile(mTag, getPatternCompileFlags(mTag));
+ mCheckTag = true;
+ } catch (PatternSyntaxException e) {
+ mCheckTag = false;
+ }
+ }
+
+ if (!mText.isEmpty()) {
+ try {
+ mTextPattern = Pattern.compile(mText, getPatternCompileFlags(mText));
+ mCheckText = true;
+ } catch (PatternSyntaxException e) {
+ mCheckText = false;
+ }
+ }
+ }
+
+ /**
+ * Obtain the flags to pass to {@link Pattern#compile(String, int)}. This method
+ * tries to figure out whether case sensitive matching should be used. It is based on
+ * the following heuristic: if the regex has an upper case character, then the match
+ * will be case sensitive. Otherwise it will be case insensitive.
+ */
+ private int getPatternCompileFlags(String regex) {
+ for (char c : regex.toCharArray()) {
+ if (Character.isUpperCase(c)) {
+ return 0;
+ }
+ }
+
+ return Pattern.CASE_INSENSITIVE;
+ }
+
+ /**
+ * Construct a list of {@link LogCatFilter} objects by decoding the query.
+ * @param query encoded search string. The query is simply a list of words (can be regexes)
+ * a user would type in a search bar. These words are searched for in the text field of
+ * each collected logcat message. To search in a different field, the word could be prefixed
+ * with a keyword corresponding to the field name. Currently, the following keywords are
+ * supported: "pid:", "tag:" and "text:". Invalid regexes are ignored.
+ * @param minLevel minimum log level to match
+ * @return list of filter settings that fully match the given query
+ */
+ public static List<LogCatFilter> fromString(String query, LogLevel minLevel) {
+ List<LogCatFilter> filterSettings = new ArrayList<LogCatFilter>();
+
+ for (String s : query.trim().split(" ")) {
+ String tag = "";
+ String text = "";
+ String pid = "";
+ String app = "";
+
+ if (s.startsWith(PID_KEYWORD)) {
+ pid = s.substring(PID_KEYWORD.length());
+ } else if (s.startsWith(APP_KEYWORD)) {
+ app = s.substring(APP_KEYWORD.length());
+ } else if (s.startsWith(TAG_KEYWORD)) {
+ tag = s.substring(TAG_KEYWORD.length());
+ } else {
+ if (s.startsWith(TEXT_KEYWORD)) {
+ text = s.substring(TEXT_KEYWORD.length());
+ } else {
+ text = s;
+ }
+ }
+ filterSettings.add(new LogCatFilter("livefilter-" + s,
+ tag, text, pid, app, minLevel));
+ }
+
+ return filterSettings;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ public String getTag() {
+ return mTag;
+ }
+
+ @NonNull
+ public String getText() {
+ return mText;
+ }
+
+ @NonNull
+ public String getPid() {
+ return mPid;
+ }
+
+ @NonNull
+ public String getAppName() {
+ return mAppName;
+ }
+
+ @NonNull
+ public LogLevel getLogLevel() {
+ return mLogLevel;
+ }
+
+ /**
+ * Check whether a given message will make it through this filter.
+ * @param m message to check
+ * @return true if the message matches the filter's conditions.
+ */
+ public boolean matches(LogCatMessage m) {
+ /* filter out messages of a lower priority */
+ if (m.getLogLevel().getPriority() < mLogLevel.getPriority()) {
+ return false;
+ }
+
+ /* if pid filter is enabled, filter out messages whose pid does not match
+ * the filter's pid */
+ if (mCheckPid && !Integer.toString(m.getPid()).equals(mPid)) {
+ return false;
+ }
+
+ /* if app name filter is enabled, filter out messages not matching the app name */
+ if (mCheckAppName) {
+ Matcher matcher = mAppNamePattern.matcher(m.getAppName());
+ if (!matcher.find()) {
+ return false;
+ }
+ }
+
+ /* if tag filter is enabled, filter out messages not matching the tag */
+ if (mCheckTag) {
+ Matcher matcher = mTagPattern.matcher(m.getTag());
+ if (!matcher.find()) {
+ return false;
+ }
+ }
+
+ if (mCheckText) {
+ Matcher matcher = mTextPattern.matcher(m.getMessage());
+ if (!matcher.find()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatHeader.java b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatHeader.java
new file mode 100644
index 0000000..1f870a4
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatHeader.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib.logcat;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.Log.LogLevel;
+
+/**
+ * Data class for message header information which gets reported by logcat.
+ */
+public final class LogCatHeader {
+
+ @NonNull
+ private final LogLevel mLogLevel;
+
+ private final int mPid;
+
+ private final int mTid;
+
+ @NonNull
+ private final String mAppName;
+
+ @NonNull
+ private final String mTag;
+
+ @NonNull
+ private final LogCatTimestamp mTimestamp;
+
+ /**
+ * Construct an immutable log message object.
+ */
+ public LogCatHeader(@NonNull LogLevel logLevel, int pid, int tid, @NonNull String appName,
+ @NonNull String tag, @NonNull LogCatTimestamp timestamp) {
+ mLogLevel = logLevel;
+ mAppName = appName;
+ mTag = tag;
+ mTimestamp = timestamp;
+ mPid = pid;
+ mTid = tid;
+ }
+
+ @NonNull
+ public LogLevel getLogLevel() {
+ return mLogLevel;
+ }
+
+ public int getPid() {
+ return mPid;
+ }
+
+ public int getTid() {
+ return mTid;
+ }
+
+ @NonNull
+ public String getAppName() {
+ return mAppName;
+ }
+
+ @NonNull
+ public String getTag() {
+ return mTag;
+ }
+
+ @NonNull
+ public LogCatTimestamp getTimestamp() {
+ return mTimestamp;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s: %s/%s(%s)", mTimestamp, mLogLevel.getPriorityLetter(), mTag,
+ mPid);
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatListener.java b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatListener.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatListener.java
rename to ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatListener.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessage.java b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessage.java
new file mode 100644
index 0000000..64ffb81
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessage.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib.logcat;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.Log.LogLevel;
+
+/**
+ * Model a single log message output from {@code logcat -v long}.
+ *
+ * Every message is furthermore associated with a {@link LogCatHeader} which contains additionally
+ * meta information about the message.
+ */
+public final class LogCatMessage {
+
+ @NonNull
+ private final LogCatHeader mHeader;
+
+ @NonNull
+ private final String mMessage;
+
+ /**
+ * @deprecated Create a {@link LogCatHeader} separately and call {@link
+ * #LogCatMessage(LogCatHeader, String)} instead. This approach shares the same header data
+ * across multiple messages.
+ */
+ @Deprecated
+ public LogCatMessage(@NonNull LogLevel logLevel, int pid, int tid,
+ @NonNull String appName, @NonNull String tag,
+ @NonNull LogCatTimestamp timestamp, @NonNull String msg) {
+ this(new LogCatHeader(logLevel, pid, tid, appName, tag, timestamp), msg);
+ }
+
+ /**
+ * Construct an immutable log message object.
+ */
+ public LogCatMessage(@NonNull LogCatHeader header, @NonNull String msg) {
+ mHeader = header;
+ mMessage = msg;
+ }
+
+ /**
+ * Helper constructor to generate a dummy message, useful if we want to add message from code
+ * that matches the logcat format.
+ */
+ public LogCatMessage(@NonNull LogLevel logLevel, @NonNull String message) {
+ this(logLevel, -1, -1, "", "", LogCatTimestamp.ZERO, message);
+ }
+
+
+ @NonNull
+ public LogCatHeader getHeader() {
+ return mHeader;
+ }
+
+ @NonNull
+ public String getMessage() {
+ return mMessage;
+ }
+
+ @NonNull
+ public LogLevel getLogLevel() {
+ return mHeader.getLogLevel();
+ }
+
+ public int getPid() {
+ return mHeader.getPid();
+ }
+
+ public int getTid() {
+ return mHeader.getTid();
+ }
+
+ @NonNull
+ public String getAppName() {
+ return mHeader.getAppName();
+ }
+
+ @NonNull
+ public String getTag() {
+ return mHeader.getTag();
+ }
+
+ @NonNull
+ public LogCatTimestamp getTimestamp() {
+ return mHeader.getTimestamp();
+ }
+
+ @Override
+ public String toString() {
+ return mHeader.toString() + ": " + mMessage;
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessageParser.java b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessageParser.java
new file mode 100644
index 0000000..9d220ec
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatMessageParser.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib.logcat;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log.LogLevel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class to parse raw output of {@code adb logcat -v long} to {@link LogCatMessage} objects.
+ */
+public final class LogCatMessageParser {
+
+ /**
+ * Pattern for logcat -v long header ([ MM-DD HH:MM:SS.mmm PID:TID LEVEL/TAG ])
+ * Ex: [ 08-18 16:39:11.760 2977: 2988 D/PhoneInterfaceManager ]
+ * Group 1: Date + Time
+ * Group 2: PID
+ * Group 3: TID (hex on some systems!)
+ * Group 4: Log Level character
+ * Group 5: Tag
+ */
+ private static final Pattern sLogHeaderPattern = Pattern.compile(
+ "^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)"
+ + "\\s+(\\d*):\\s*(\\S+)\\s([VDIWEAF])/(.*[^\\s])\\s+\\]$");
+
+ @Nullable
+ LogCatHeader mPrevHeader;
+
+ /**
+ * Parse a header line into a {@link LogCatHeader} object, or {@code null} if the input line
+ * doesn't match the expected format.
+ *
+ * @param line raw text that should be the header line from logcat -v long
+ * @param device device from which these log messages have been received
+ * @return a {@link LogCatHeader} which represents the passed in text
+ */
+ @Nullable
+ public LogCatHeader processLogHeader(@NonNull String line, @Nullable IDevice device) {
+ Matcher matcher = sLogHeaderPattern.matcher(line);
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ int pid = -1;
+ try {
+ pid = Integer.parseInt(matcher.group(2));
+ } catch (NumberFormatException ignored) {
+ }
+
+ int tid = -1;
+ try {
+ // Thread id's may be in hex on some platforms.
+ // Decode and store them in radix 10.
+ tid = Integer.decode(matcher.group(3));
+ } catch (NumberFormatException ignored) {
+ }
+
+ String pkgName = null;
+ if (device != null && pid != -1) {
+ pkgName = device.getClientName(pid);
+ }
+ if (pkgName == null || pkgName.isEmpty()) {
+ pkgName = "?"; //$NON-NLS-1$
+ }
+
+ LogLevel logLevel = LogLevel.getByLetterString(matcher.group(4));
+ if (logLevel == null && matcher.group(4).equals("F")) {
+ logLevel = LogLevel.ASSERT;
+ }
+ if (logLevel == null) {
+ // Should never happen but warn seems like a decent default just in case
+ logLevel = LogLevel.WARN;
+ }
+
+ mPrevHeader = new LogCatHeader(logLevel, pid, tid, pkgName,
+ matcher.group(5), LogCatTimestamp.fromString(matcher.group(1)));
+
+ return mPrevHeader;
+ }
+
+ /**
+ * Parse a list of strings into {@link LogCatMessage} objects. This method maintains state from
+ * previous calls regarding the last seen header of logcat messages.
+ *
+ * @param lines list of raw strings obtained from logcat -v long
+ * @param device device from which these log messages have been received
+ * @return list of LogMessage objects parsed from the input
+ * @throws IllegalStateException if given text before ever parsing a header
+ */
+ @NonNull
+ public List<LogCatMessage> processLogLines(@NonNull String[] lines, @Nullable IDevice device) {
+ List<LogCatMessage> messages = new ArrayList<LogCatMessage>(lines.length);
+
+ for (String line : lines) {
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ if (processLogHeader(line, device) == null) {
+ // If not a header line, this is a message line
+ if (mPrevHeader == null) {
+ // If we are fed a log line without a header, there's nothing we can do with
+ // it - the header metadata is very important! So, we have no choice but to drop
+ // this line.
+ //
+ // This should rarely happen, if ever - for example, perhaps we're running over
+ // old logs where some earlier lines have been truncated.
+ continue;
+ }
+ messages.add(new LogCatMessage(mPrevHeader, line));
+ }
+ }
+
+ return messages;
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatReceiverTask.java b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatReceiverTask.java
new file mode 100644
index 0000000..1c3be2c
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatReceiverTask.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib.logcat;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class LogCatReceiverTask implements Runnable {
+ private static final String LOGCAT_COMMAND = "logcat -v long"; //$NON-NLS-1$
+ private static final int DEVICE_POLL_INTERVAL_MSEC = 1000;
+
+ private static final LogCatMessage sDeviceDisconnectedMsg =
+ new LogCatMessage(LogLevel.ERROR, "Device disconnected: 1");
+ private static final LogCatMessage sConnectionTimeoutMsg =
+ new LogCatMessage(LogLevel.ERROR, "LogCat Connection timed out");
+ private static final LogCatMessage sConnectionErrorMsg =
+ new LogCatMessage(LogLevel.ERROR, "LogCat Connection error");
+
+ private final IDevice mDevice;
+ private final LogCatOutputReceiver mReceiver;
+ private final LogCatMessageParser mParser;
+ private final AtomicBoolean mCancelled;
+
+ @GuardedBy("this")
+ private final Set<LogCatListener> mListeners = new HashSet<LogCatListener>();
+
+ public LogCatReceiverTask(@NonNull IDevice device) {
+ mDevice = device;
+
+ mReceiver = new LogCatOutputReceiver();
+ mParser = new LogCatMessageParser();
+ mCancelled = new AtomicBoolean();
+ }
+
+ @Override
+ public void run() {
+ // wait while device comes online
+ while (!mDevice.isOnline()) {
+ try {
+ Thread.sleep(DEVICE_POLL_INTERVAL_MSEC);
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+
+ try {
+ mDevice.executeShellCommand(LOGCAT_COMMAND, mReceiver, 0);
+ } catch (TimeoutException e) {
+ notifyListeners(Collections.singletonList(sConnectionTimeoutMsg));
+ } catch (AdbCommandRejectedException ignored) {
+ // will not be thrown as long as the shell supports logcat
+ } catch (ShellCommandUnresponsiveException ignored) {
+ // this will not be thrown since the last argument is 0
+ } catch (IOException e) {
+ notifyListeners(Collections.singletonList(sConnectionErrorMsg));
+ }
+
+ notifyListeners(Collections.singletonList(sDeviceDisconnectedMsg));
+ }
+
+ public void stop() {
+ mCancelled.set(true);
+ }
+
+ private class LogCatOutputReceiver extends MultiLineReceiver {
+ public LogCatOutputReceiver() {
+ setTrimLine(false);
+ }
+
+ /** Implements {@link IShellOutputReceiver#isCancelled() }. */
+ @Override
+ public boolean isCancelled() {
+ return mCancelled.get();
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ if (!mCancelled.get()) {
+ processLogLines(lines);
+ }
+ }
+
+ private void processLogLines(String[] lines) {
+ List<LogCatMessage> newMessages = mParser.processLogLines(lines, mDevice);
+ if (!newMessages.isEmpty()) {
+ notifyListeners(newMessages);
+ }
+ }
+ }
+
+ public synchronized void addLogCatListener(LogCatListener l) {
+ mListeners.add(l);
+ }
+
+ public synchronized void removeLogCatListener(LogCatListener l) {
+ mListeners.remove(l);
+ }
+
+ private synchronized void notifyListeners(List<LogCatMessage> messages) {
+ for (LogCatListener l: mListeners) {
+ l.log(messages);
+ }
+ }
+}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatTimestamp.java b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatTimestamp.java
new file mode 100644
index 0000000..fd14e85
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/logcat/LogCatTimestamp.java
@@ -0,0 +1,188 @@
+/*
+ * 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.ddmlib.logcat;
+
+import com.android.annotations.NonNull;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Data class for timestamp information which gets reported by logcat.
+ */
+public final class LogCatTimestamp {
+
+ public static final LogCatTimestamp ZERO = new LogCatTimestamp(1, 1, 0, 0, 0, 0);
+
+ private final int mMonth;
+ private final int mDay;
+ private final int mHour;
+ private final int mMinute;
+ private final int mSecond;
+ private final int mMilli;
+
+ private static final Pattern sTimePattern = Pattern.compile(
+ "^(\\d\\d)-(\\d\\d)\\s(\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d+)$");
+
+ public static LogCatTimestamp fromString(@NonNull String timeString) {
+ Matcher matcher = sTimePattern.matcher(timeString);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Invalid timestamp. Expected MM-DD HH:MM:SS:mmm");
+ }
+
+ int month = Integer.parseInt(matcher.group(1));
+ int day = Integer.parseInt(matcher.group(2));
+ int hour = Integer.parseInt(matcher.group(3));
+ int minute = Integer.parseInt(matcher.group(4));
+ int second = Integer.parseInt(matcher.group(5));
+ int millisecond = Integer.parseInt(matcher.group(6));
+
+ // ms is 3 digits max. e.g. convert "123456" into "123" (and rounding error is fine)
+ while (millisecond >= 1000) {
+ millisecond /= 10;
+ }
+
+ return new LogCatTimestamp(month, day, hour, minute, second, millisecond);
+ }
+
+ /**
+ * Construct an immutable timestamp object.
+ */
+ public LogCatTimestamp(int month, int day, int hour, int minute, int second, int milli) {
+ if (month < 1 || month > 12) {
+ throw new IllegalArgumentException(
+ String.format("Month should be between 1-12: %d", month));
+ }
+
+ if (day < 1 || day > 31) {
+ throw new IllegalArgumentException(
+ String.format("Day should be between 1-31: %d", day));
+ }
+
+ if (hour < 0 || hour > 23) {
+ throw new IllegalArgumentException(
+ String.format("Hour should be between 0-23: %d", hour));
+ }
+
+ if (minute < 0 || minute > 59) {
+ throw new IllegalArgumentException(
+ String.format("Minute should be between 0-59: %d", minute));
+ }
+
+ if (second < 0 || second > 59) {
+ throw new IllegalArgumentException(
+ String.format("Second should be between 0-59 %d", second));
+ }
+
+ if (milli < 0 || milli > 999) {
+ throw new IllegalArgumentException(
+ String.format("Millisecond should be between 0-999: %d", milli));
+ }
+
+ mMonth = month;
+ mDay = day;
+ mHour = hour;
+ mMinute = minute;
+ mSecond = second;
+ mMilli = milli;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ LogCatTimestamp that = (LogCatTimestamp)o;
+
+ if (mMonth != that.mMonth) return false;
+ if (mDay != that.mDay) return false;
+ if (mHour != that.mHour) return false;
+ if (mMinute != that.mMinute) return false;
+ if (mSecond != that.mSecond) return false;
+ if (mMilli != that.mMilli) return false;
+
+ return true;
+ }
+
+ public boolean isBefore(@NonNull LogCatTimestamp other) {
+ if (mMonth == 12 && other.mMonth == 1) {
+ // Timestamps don't indicate year, so in practice, if you get two timestamps in short
+ // succession:
+ // 12-31 23:59:59.999
+ // 01-01 00:00:01.111
+ // we assume that the latter timestamp is newer than the previous
+ // Unfortunately, if someone leaves their Android running for a whole year, this logic
+ // would only take us so far, but that's unlikely to be an issue, at least compared to
+ // someone leaving their Android device running overnight on the new year.
+ return true;
+ }
+ else if (mMonth == 1 && other.mMonth == 12) {
+ return false;
+ }
+
+ if (mMonth < other.mMonth) {
+ return true;
+ }
+ else if (mMonth > other.mMonth) {
+ return false;
+ }
+
+ if (mDay < other.mDay) {
+ return true;
+ }
+ else if (mDay > other.mDay) {
+ return false;
+ }
+
+ if (mHour < other.mHour) {
+ return true;
+ }
+ else if (mHour > other.mHour) {
+ return false;
+ }
+
+ if (mMinute < other.mMinute) {
+ return true;
+ }
+ else if (mMinute > other.mMinute) {
+ return false;
+ }
+
+ if (mSecond < other.mSecond) {
+ return true;
+ }
+ else if (mSecond > other.mSecond) {
+ return false;
+ }
+
+ if (mMilli < other.mMilli) {
+ return true;
+ }
+ else if (mMilli > other.mMilli) {
+ return false;
+ }
+
+ return false;
+
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%02d-%02d %02d:%02d:%02d.%03d", mMonth, mDay, mHour, mMinute, mSecond,
+ mMilli);
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/IRemoteAndroidTestRunner.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/IRemoteAndroidTestRunner.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/testrunner/IRemoteAndroidTestRunner.java
rename to ddmlib/src/main/java/com/android/ddmlib/testrunner/IRemoteAndroidTestRunner.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/ITestRunListener.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/ITestRunListener.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/testrunner/ITestRunListener.java
rename to ddmlib/src/main/java/com/android/ddmlib/testrunner/ITestRunListener.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/InstrumentationResultParser.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/InstrumentationResultParser.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/testrunner/InstrumentationResultParser.java
rename to ddmlib/src/main/java/com/android/ddmlib/testrunner/InstrumentationResultParser.java
diff --git a/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
new file mode 100644
index 0000000..d1f8d1f
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2008 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.ddmlib.testrunner;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellEnabledDevice;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Runs a Android test command remotely and reports results.
+ */
+public class RemoteAndroidTestRunner implements IRemoteAndroidTestRunner {
+
+ private final String mPackageName;
+ private final String mRunnerName;
+ private IShellEnabledDevice mRemoteDevice;
+ // default to no timeout
+ private long mMaxTimeToOutputResponse = 0;
+ private TimeUnit mMaxTimeUnits = TimeUnit.MILLISECONDS;
+ private String mRunName = null;
+
+ /** map of name-value instrumentation argument pairs */
+ private Map<String, String> mArgMap;
+ private InstrumentationResultParser mParser;
+
+ private static final String LOG_TAG = "RemoteAndroidTest";
+ private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner";
+
+ private static final char CLASS_SEPARATOR = ',';
+ private static final char METHOD_SEPARATOR = '#';
+ private static final char RUNNER_SEPARATOR = '/';
+
+ // defined instrumentation argument names
+ private static final String CLASS_ARG_NAME = "class";
+ private static final String LOG_ARG_NAME = "log";
+ private static final String DEBUG_ARG_NAME = "debug";
+ private static final String COVERAGE_ARG_NAME = "coverage";
+ private static final String PACKAGE_ARG_NAME = "package";
+ private static final String SIZE_ARG_NAME = "size";
+ private static final String DELAY_MSEC_ARG_NAME = "delay_msec";
+ private String mRunOptions = "";
+
+ private static final int TEST_COLLECTION_TIMEOUT = 2 * 60 * 1000; //2 min
+
+ /**
+ * Creates a remote Android test runner.
+ *
+ * @param packageName the Android application package that contains the tests to run
+ * @param runnerName the instrumentation test runner to execute. If null, will use default
+ * runner
+ * @param remoteDevice the Android device to execute tests on
+ */
+ public RemoteAndroidTestRunner(String packageName,
+ String runnerName,
+ IShellEnabledDevice remoteDevice) {
+
+ mPackageName = packageName;
+ mRunnerName = runnerName;
+ mRemoteDevice = remoteDevice;
+ mArgMap = new Hashtable<String, String>();
+ }
+
+ /**
+ * Alternate constructor. Uses default instrumentation runner.
+ *
+ * @param packageName the Android application package that contains the tests to run
+ * @param remoteDevice the Android device to execute tests on
+ */
+ public RemoteAndroidTestRunner(String packageName,
+ IShellEnabledDevice remoteDevice) {
+ this(packageName, null, remoteDevice);
+ }
+
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public String getRunnerName() {
+ if (mRunnerName == null) {
+ return DEFAULT_RUNNER_NAME;
+ }
+ return mRunnerName;
+ }
+
+ /**
+ * Returns the complete instrumentation component path.
+ */
+ private String getRunnerPath() {
+ return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
+ }
+
+ @Override
+ public void setClassName(String className) {
+ addInstrumentationArg(CLASS_ARG_NAME, className);
+ }
+
+ @Override
+ public void setClassNames(String[] classNames) {
+ StringBuilder classArgBuilder = new StringBuilder();
+
+ for (int i = 0; i < classNames.length; i++) {
+ if (i != 0) {
+ classArgBuilder.append(CLASS_SEPARATOR);
+ }
+ classArgBuilder.append(classNames[i]);
+ }
+ setClassName(classArgBuilder.toString());
+ }
+
+ @Override
+ public void setMethodName(String className, String testName) {
+ setClassName(className + METHOD_SEPARATOR + testName);
+ }
+
+ @Override
+ public void setTestPackageName(String packageName) {
+ addInstrumentationArg(PACKAGE_ARG_NAME, packageName);
+ }
+
+ @Override
+ public void addInstrumentationArg(String name, String value) {
+ if (name == null || value == null) {
+ throw new IllegalArgumentException("name or value arguments cannot be null");
+ }
+ mArgMap.put(name, value);
+ }
+
+ @Override
+ public void removeInstrumentationArg(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name argument cannot be null");
+ }
+ mArgMap.remove(name);
+ }
+
+ @Override
+ public void addBooleanArg(String name, boolean value) {
+ addInstrumentationArg(name, Boolean.toString(value));
+ }
+
+ @Override
+ public void setLogOnly(boolean logOnly) {
+ addBooleanArg(LOG_ARG_NAME, logOnly);
+ }
+
+ @Override
+ public void setDebug(boolean debug) {
+ addBooleanArg(DEBUG_ARG_NAME, debug);
+ }
+
+ @Override
+ public void setCoverage(boolean coverage) {
+ addBooleanArg(COVERAGE_ARG_NAME, coverage);
+ }
+
+ @Override
+ public void setTestSize(TestSize size) {
+ addInstrumentationArg(SIZE_ARG_NAME, size.getRunnerValue());
+ }
+
+ @Override
+ public void setTestCollection(boolean collect) {
+ if (collect) {
+ // skip test execution
+ setLogOnly(true);
+ // force a timeout for test collection
+ setMaxTimeToOutputResponse(TEST_COLLECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+ if (getApiLevel() < 16 ) {
+ // On older platforms, collecting tests can fail for large volume of tests.
+ // Insert a small delay between each test to prevent this
+ addInstrumentationArg(DELAY_MSEC_ARG_NAME, "15" /* ms */);
+ }
+ } else {
+ setLogOnly(false);
+ // restore timeout to its original set value
+ setMaxTimeToOutputResponse(mMaxTimeToOutputResponse, mMaxTimeUnits);
+ if (getApiLevel() < 16 ) {
+ // remove delay
+ removeInstrumentationArg(DELAY_MSEC_ARG_NAME);
+ }
+ }
+ }
+
+ /**
+ * Attempts to retrieve the Api level of the Android device
+ * @return the api level or -1 if the communication with the device wasn't successful
+ */
+ private int getApiLevel() {
+ try {
+ return Integer.parseInt(mRemoteDevice.getSystemProperty(
+ IDevice.PROP_BUILD_API_LEVEL).get());
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ @Override
+ public void setMaxtimeToOutputResponse(int maxTimeToOutputResponse) {
+ setMaxTimeToOutputResponse(maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void setMaxTimeToOutputResponse(long maxTimeToOutputResponse, TimeUnit maxTimeUnits) {
+ mMaxTimeToOutputResponse = maxTimeToOutputResponse;
+ mMaxTimeUnits = maxTimeUnits;
+ }
+
+ @Override
+ public void setRunName(String runName) {
+ mRunName = runName;
+ }
+
+ @Override
+ public void run(ITestRunListener... listeners)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+ run(Arrays.asList(listeners));
+ }
+
+ @Override
+ public void run(Collection<ITestRunListener> listeners)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+ final String runCaseCommandStr = getAmInstrumentCommand();
+ Log.i(LOG_TAG, String.format("Running %1$s on %2$s", runCaseCommandStr,
+ mRemoteDevice.getName()));
+ String runName = mRunName == null ? mPackageName : mRunName;
+ mParser = new InstrumentationResultParser(runName, listeners);
+
+ try {
+ mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser, mMaxTimeToOutputResponse,
+ mMaxTimeUnits);
+ } catch (IOException e) {
+ Log.w(LOG_TAG, String.format("IOException %1$s when running tests %2$s on %3$s",
+ e.toString(), getPackageName(), mRemoteDevice.getName()));
+ // rely on parser to communicate results to listeners
+ mParser.handleTestRunFailed(e.toString());
+ throw e;
+ } catch (ShellCommandUnresponsiveException e) {
+ Log.w(LOG_TAG, String.format(
+ "ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s",
+ e.toString(), getPackageName(), mRemoteDevice.getName()));
+ mParser.handleTestRunFailed(String.format(
+ "Failed to receive adb shell test output within %1$d ms. " +
+ "Test may have timed out, or adb connection to device became unresponsive",
+ mMaxTimeToOutputResponse));
+ throw e;
+ } catch (TimeoutException e) {
+ Log.w(LOG_TAG, String.format(
+ "TimeoutException when running tests %1$s on %2$s", getPackageName(),
+ mRemoteDevice.getName()));
+ mParser.handleTestRunFailed(e.toString());
+ throw e;
+ } catch (AdbCommandRejectedException e) {
+ Log.w(LOG_TAG, String.format(
+ "AdbCommandRejectedException %1$s when running tests %2$s on %3$s",
+ e.toString(), getPackageName(), mRemoteDevice.getName()));
+ mParser.handleTestRunFailed(e.toString());
+ throw e;
+ }
+ }
+
+ @NonNull
+ public String getAmInstrumentCommand() {
+ return String.format("am instrument -w -r %1$s %2$s %3$s", getRunOptions(),
+ getArgsCommand(), getRunnerPath());
+ }
+
+ /**
+ * Returns options for the am instrument command.
+ */
+ @NonNull public String getRunOptions() {
+ return mRunOptions;
+ }
+
+ /**
+ * Sets options for the am instrument command.
+ * See com/android/commands/am/Am.java for full list of options.
+ */
+ public void setRunOptions(@NonNull String options) {
+ mRunOptions = options;
+ }
+
+ @Override
+ public void cancel() {
+ if (mParser != null) {
+ mParser.cancel();
+ }
+ }
+
+ /**
+ * Returns the full instrumentation command line syntax for the provided instrumentation
+ * arguments.
+ * Returns an empty string if no arguments were specified.
+ */
+ private String getArgsCommand() {
+ StringBuilder commandBuilder = new StringBuilder();
+ for (Entry<String, String> argPair : mArgMap.entrySet()) {
+ final String argCmd = String.format(" -e %1$s %2$s", argPair.getKey(),
+ argPair.getValue());
+ commandBuilder.append(argCmd);
+ }
+ return commandBuilder.toString();
+ }
+}
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestIdentifier.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestIdentifier.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestIdentifier.java
rename to ddmlib/src/main/java/com/android/ddmlib/testrunner/TestIdentifier.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestResult.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestResult.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestResult.java
rename to ddmlib/src/main/java/com/android/ddmlib/testrunner/TestResult.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestRunResult.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestRunResult.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/testrunner/TestRunResult.java
rename to ddmlib/src/main/java/com/android/ddmlib/testrunner/TestRunResult.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java b/ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java
rename to ddmlib/src/main/java/com/android/ddmlib/testrunner/XmlTestRunListener.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/utils/ArrayHelper.java b/ddmlib/src/main/java/com/android/ddmlib/utils/ArrayHelper.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/utils/ArrayHelper.java
rename to ddmlib/src/main/java/com/android/ddmlib/utils/ArrayHelper.java
diff --git a/base/ddmlib/src/main/java/com/android/ddmlib/utils/DebuggerPorts.java b/ddmlib/src/main/java/com/android/ddmlib/utils/DebuggerPorts.java
similarity index 100%
rename from base/ddmlib/src/main/java/com/android/ddmlib/utils/DebuggerPorts.java
rename to ddmlib/src/main/java/com/android/ddmlib/utils/DebuggerPorts.java
diff --git a/base/ddmlib/src/test/.classpath b/ddmlib/src/test/.classpath
similarity index 100%
rename from base/ddmlib/src/test/.classpath
rename to ddmlib/src/test/.classpath
diff --git a/base/ddmlib/src/test/.project b/ddmlib/src/test/.project
similarity index 100%
rename from base/ddmlib/src/test/.project
rename to ddmlib/src/test/.project
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/AdbVersionTest.java b/ddmlib/src/test/java/com/android/ddmlib/AdbVersionTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/AdbVersionTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/AdbVersionTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/AndroidDebugBridgeTest.java b/ddmlib/src/test/java/com/android/ddmlib/AndroidDebugBridgeTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/AndroidDebugBridgeTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/AndroidDebugBridgeTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/BatteryFetcherTest.java b/ddmlib/src/test/java/com/android/ddmlib/BatteryFetcherTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/BatteryFetcherTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/BatteryFetcherTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/DeviceMonitorTest.java b/ddmlib/src/test/java/com/android/ddmlib/DeviceMonitorTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/DeviceMonitorTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/DeviceMonitorTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/DeviceTest.java b/ddmlib/src/test/java/com/android/ddmlib/DeviceTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/DeviceTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/DeviceTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/EmulatorConsoleTest.java b/ddmlib/src/test/java/com/android/ddmlib/EmulatorConsoleTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/EmulatorConsoleTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/EmulatorConsoleTest.java
diff --git a/ddmlib/src/test/java/com/android/ddmlib/FileListingServiceTest.java b/ddmlib/src/test/java/com/android/ddmlib/FileListingServiceTest.java
new file mode 100644
index 0000000..729b3dc
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/FileListingServiceTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ddmlib;
+
+import java.util.regex.Matcher;
+import junit.framework.TestCase;
+
+public class FileListingServiceTest extends TestCase {
+ public void test_LS_L_PATTERN() {
+ Matcher m;
+
+ // Traditional Android file output.
+ m = FileListingService.LS_L_PATTERN.matcher(
+ "-rw-r--r-- root root 193 1970-01-01 00:00 build.prop");
+ assertTrue(m.matches());
+ assertEquals("193", m.group(4));
+
+ // Traditional Android directory output.
+ m = FileListingService.LS_L_PATTERN.matcher(
+ "drwxrwx--- system cache 2015-07-20 23:01 cache");
+ assertTrue(m.matches());
+ assertEquals("system", m.group(2));
+ assertEquals("23:01", m.group(6));
+
+ // POSIX file output.
+ m = FileListingService.LS_L_PATTERN.matcher(
+ "-rw-r--r-- 1 root root 193 1970-01-01 00:00 build.prop");
+ assertTrue(m.matches());
+ assertEquals("193", m.group(4));
+
+ // POSIX directory output.
+ m = FileListingService.LS_L_PATTERN.matcher(
+ "drwxrwx--- 5 system cache 4096 2015-07-20 23:01 cache");
+ assertTrue(m.matches());
+ assertEquals("system", m.group(2));
+ assertEquals("23:01", m.group(6));
+ }
+
+ public void test_LS_LD_PATTERN() {
+ Matcher m;
+
+ // adb shell toolbox ls -ld /init
+ m = FileListingService.LS_LD_PATTERN.matcher(
+ "-rwxr-x--- root root 924136 1970-01-01 00:00 init");
+ assertFalse(m.matches());
+ // adb shell toolbox ls -ld /sdcard
+ m = FileListingService.LS_LD_PATTERN.matcher(
+ "lrwxrwxrwx root root 2015-08-20 21:57 sdcard -> /storage/emulated/legacy");
+ assertFalse(m.matches());
+ // adb shell toolbox ls -ld /sdcard/
+ m = FileListingService.LS_LD_PATTERN.matcher(
+ "drwxrwx--x root sdcard_r 2015-07-20 23:01 ");
+ assertTrue(m.matches());
+
+ // adb shell toybox ls -ld /init
+ m = FileListingService.LS_LD_PATTERN.matcher(
+ "-rwxr-x--- 1 root root 924136 1970-01-01 00:00 /init");
+ assertFalse(m.matches());
+ // adb shell toybox ls -ld /sdcard
+ m = FileListingService.LS_LD_PATTERN.matcher(
+ "lrwxrwxrwx 1 root root 24 2015-08-20 21:57 /sdcard -> /storage/emulated/legacy");
+ assertFalse(m.matches());
+ // adb shell toybox ls -ld /sdcard/
+ m = FileListingService.LS_LD_PATTERN.matcher(
+ "drwxrwx--x 12 root sdcard_r 4096 2015-07-20 23:01 /sdcard/");
+ assertTrue(m.matches());
+ }
+}
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/PropertyFetcherTest.java b/ddmlib/src/test/java/com/android/ddmlib/PropertyFetcherTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/PropertyFetcherTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/PropertyFetcherTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/SysFsBatteryLevelReceiverTest.java b/ddmlib/src/test/java/com/android/ddmlib/SysFsBatteryLevelReceiverTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/SysFsBatteryLevelReceiverTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/SysFsBatteryLevelReceiverTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/allocations/AllocationsParserTest.java b/ddmlib/src/test/java/com/android/ddmlib/allocations/AllocationsParserTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/allocations/AllocationsParserTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/allocations/AllocationsParserTest.java
diff --git a/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatFilterTest.java b/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatFilterTest.java
new file mode 100644
index 0000000..ad5d071
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatFilterTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ddmlib.logcat;
+
+import com.android.ddmlib.Log.LogLevel;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+public class LogCatFilterTest extends TestCase {
+ public void testFilterByLogLevel() {
+ LogCatFilter filter = new LogCatFilter("",
+ "", "", "", "", LogLevel.DEBUG);
+
+ /* filter message below filter's log level */
+ LogCatMessage msg = new MessageBuilder().setLevel(LogLevel.VERBOSE).build();
+ assertEquals(false, filter.matches(msg));
+
+ /* do not filter message above filter's log level */
+ msg = new MessageBuilder().setLevel(LogLevel.ERROR).build();
+ assertEquals(true, filter.matches(msg));
+ }
+
+ public void testFilterByPid() {
+ LogCatFilter filter = new LogCatFilter("",
+ "", "", "123", "", LogLevel.VERBOSE);
+
+ /* show message with pid matching filter */
+ LogCatMessage msg = new MessageBuilder().setPid(123).build();
+ assertEquals(true, filter.matches(msg));
+
+ /* don't show message with pid not matching filter */
+ msg = new MessageBuilder().setPid(12).build();
+ assertEquals(false, filter.matches(msg));
+ }
+
+ public void testFilterByAppNameRegex() {
+ LogCatFilter filter = new LogCatFilter("",
+ "", "", "", "dalvik.*", LogLevel.VERBOSE);
+
+ /* show message with process name matching filter */
+ LogCatMessage msg = new MessageBuilder().setAppName("dalvikvm1").build();
+ assertEquals(true, filter.matches(msg));
+
+ /* don't show message with process name not matching filter */
+ msg = new MessageBuilder().setAppName("system").build();
+ assertEquals(false, filter.matches(msg));
+ }
+
+ public void testFilterByTagRegex() {
+ LogCatFilter filter = new LogCatFilter("",
+ "tag.*", "", "", "", LogLevel.VERBOSE);
+
+ /* show message with tag matching filter */
+ LogCatMessage msg = new MessageBuilder().setTag("tag123").build();
+ assertEquals(true, filter.matches(msg));
+
+ msg = new MessageBuilder().setTag("ta123").build();
+ assertEquals(false, filter.matches(msg));
+ }
+
+ public void testFilterByTextRegex() {
+ LogCatFilter filter = new LogCatFilter("",
+ "", "text.*", "", "", LogLevel.VERBOSE);
+
+ /* show message with text matching filter */
+ LogCatMessage msg = new MessageBuilder().setMessage("text123").build();
+ assertEquals(true, filter.matches(msg));
+
+ msg = new MessageBuilder().setMessage("te123").build();
+ assertEquals(false, filter.matches(msg));
+ }
+
+ public void testMatchingText() {
+ LogCatMessage msg = new MessageBuilder().setMessage("message with word1 and word2").build();
+ assertEquals(true, search("word1 with", msg));
+ assertEquals(true, search("text:w.* ", msg));
+ assertEquals(false, search("absent", msg));
+ }
+
+ public void testTagKeyword() {
+ LogCatMessage msg = new MessageBuilder().setTag("tag").setMessage("sample message").build();
+ assertEquals(false, search("t.*", msg));
+ assertEquals(true, search("tag:t.*", msg));
+ }
+
+ public void testPidKeyword() {
+ LogCatMessage msg = new MessageBuilder().setPid(123).setMessage("sample message").build();
+ assertEquals(false, search("123", msg));
+ assertEquals(true, search("pid:123", msg));
+ }
+
+ public void testAppNameKeyword() {
+ LogCatMessage msg = new MessageBuilder().setAppName("dalvik").setMessage("sample message")
+ .build();
+ assertEquals(false, search("dalv.*", msg));
+ assertEquals(true, search("app:dal.*k", msg));
+ }
+
+ public void testCaseSensitivity() {
+ LogCatMessage msg = new MessageBuilder().setMessage("Sample message").build();
+
+ // if regex has an upper case character, it should be
+ // treated as a case sensitive search
+ assertEquals(false, search("Message", msg));
+
+ // if regex is all lower case, then it should be a
+ // case insensitive search
+ assertEquals(true, search("sample", msg));
+ }
+
+ /**
+ * Helper method: search if the query string matches the message.
+ * @param query words to search for
+ * @param message text to search in
+ * @return true if the encoded query is present in message
+ */
+ private static boolean search(String query, LogCatMessage message) {
+ List<LogCatFilter> filters = LogCatFilter.fromString(query,
+ LogLevel.VERBOSE);
+
+ /* all filters have to match for the query to match */
+ for (LogCatFilter f : filters) {
+ if (!f.matches(message)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static class MessageBuilder {
+
+ private LogLevel mLevel = LogLevel.VERBOSE;
+ private int mPid = -1;
+ private int mTid = -1;
+ private String mAppName = "";
+ private String mTagName = "";
+ private LogCatTimestamp mTimestamp = LogCatTimestamp.ZERO;
+ private String mMessage = "";
+
+ public MessageBuilder setLevel(LogLevel level) {
+ mLevel = level;
+ return this;
+ }
+
+ public MessageBuilder setPid(int pid) {
+ mPid = pid;
+ return this;
+ }
+
+ public MessageBuilder setAppName(String appName) {
+ mAppName = appName;
+ return this;
+ }
+
+ public MessageBuilder setTag(String tagName) {
+ mTagName = tagName;
+ return this;
+ }
+
+ public MessageBuilder setTimestamp(LogCatTimestamp timestamp) {
+ mTimestamp = timestamp;
+ return this;
+ }
+
+ public MessageBuilder setMessage(String message) {
+ mMessage = message;
+ return this;
+ }
+
+ public LogCatMessage build() {
+ return new LogCatMessage(mLevel, mPid, mTid, mAppName, mTagName, mTimestamp, mMessage);
+ }
+ }
+}
diff --git a/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatMessageParserTest.java b/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatMessageParserTest.java
new file mode 100644
index 0000000..c087c62
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatMessageParserTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ddmlib.logcat;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log.LogLevel;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link LogCatMessageParser}.
+ */
+public final class LogCatMessageParserTest extends TestCase {
+ private List<LogCatMessage> mParsedMessages;
+
+ /** A list of messages generated with the following code:
+ * <pre>
+ * {@code
+ * Log.d("dtag", "debug message");
+ * Log.e("etag", "error message");
+ * Log.i("itag", "info message");
+ * Log.v("vtag", "verbose message");
+ * Log.w("wtag", "warning message");
+ * Log.wtf("wtftag", "wtf message");
+ * Log.d("dtag", "debug message");
+ * }
+ * </pre>
+ * Note: On Android 2.3, Log.wtf doesn't really generate the message.
+ * It only produces the message header, but swallows the message tag.
+ * This string has been modified to include the message.
+ */
+ private static final String[] MESSAGES = new String[] {
+ "[ 08-11 19:11:07.132 495:0x1ef D/dtag ]", //$NON-NLS-1$
+ "debug message", //$NON-NLS-1$
+ "[ 08-11 19:11:07.132 495: 234 E/etag ]", //$NON-NLS-1$
+ "error message", //$NON-NLS-1$
+ "[ 08-11 19:11:07.132 495:0x1ef I/itag ]", //$NON-NLS-1$
+ "info message", //$NON-NLS-1$
+ "[ 08-11 19:11:07.132 495:0x1ef V/vtag ]", //$NON-NLS-1$
+ "verbose message", //$NON-NLS-1$
+ "[ 08-11 19:11:07.132 495:0x1ef W/wtag ]", //$NON-NLS-1$
+ "warning message", //$NON-NLS-1$
+ "[ 08-11 19:11:07.132 495:0x1ef F/wtftag ]", //$NON-NLS-1$
+ "wtf message", //$NON-NLS-1$
+ "[ 08-11 21:15:35.754 540:0x21c D/dtag ]", //$NON-NLS-1$
+ "debug message", //$NON-NLS-1$
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ LogCatMessageParser parser = new LogCatMessageParser();
+
+ IDevice d = EasyMock.createMock(IDevice.class);
+ EasyMock.expect(d.getClientName(495)).andStubReturn("com.example.name");
+ EasyMock.expect(d.getClientName(EasyMock.anyInt())).andStubReturn("");
+ EasyMock.replay(d);
+
+ mParsedMessages = parser.processLogLines(MESSAGES, d);
+ }
+
+ /** Check that the correct number of messages are received. */
+ public void testMessageCount() {
+ assertEquals(7, mParsedMessages.size());
+ }
+
+ /** Check the log level in a few of the parsed messages. */
+ public void testLogLevel() {
+ assertEquals(mParsedMessages.get(0).getLogLevel(), LogLevel.DEBUG);
+ assertEquals(mParsedMessages.get(5).getLogLevel(), LogLevel.ASSERT);
+ }
+
+ /** Check the parsed tag. */
+ public void testTag() {
+ assertEquals(mParsedMessages.get(1).getTag(), "etag");
+ }
+
+ /** Check the time field. */
+ public void testTime() {
+ assertEquals(mParsedMessages.get(6).getTimestamp().toString(), "08-11 21:15:35.754");
+ }
+
+ /** Check the message field. */
+ public void testMessage() {
+ assertEquals(mParsedMessages.get(2).getMessage(), MESSAGES[5]);
+ }
+
+ public void testTid() {
+ assertEquals(mParsedMessages.get(0).getTid(), 0x1ef);
+ assertEquals(mParsedMessages.get(1).getTid(), 234);
+ }
+
+ public void testTimeAsDate() {
+ LogCatHeader header = mParsedMessages.get(0).getHeader();
+ // Test date against "08-11 19:11:07.132"
+ assertEquals(header.getTimestamp().toString(), "08-11 19:11:07.132");
+ }
+
+ public void testPackageName() {
+ assertEquals(mParsedMessages.get(0).getAppName(), "com.example.name");
+ assertEquals(mParsedMessages.get(6).getAppName(), "?");
+ }
+
+ public void testLinesWithoutHeadersAreIgnored() throws Exception {
+ String[] TRUNCATED_LOGS = new String[] {
+ "Log[0] logline2", //$NON-NLS-1$
+ "Log[0] logline3", //$NON-NLS-1$
+ "", //$NON-NLS-1$
+ "[ 08-11 19:11:07.132 495:0x1ef D/dtag ]", //$NON-NLS-1$
+ "Log[1] logline1", //$NON-NLS-1$
+ "Log[1] logline2", //$NON-NLS-1$
+ "Log[1] logline3", //$NON-NLS-1$
+ };
+
+ LogCatMessageParser parser = new LogCatMessageParser();
+ mParsedMessages = parser.processLogLines(TRUNCATED_LOGS, null);
+ assertEquals(mParsedMessages.size(), 3);
+ assertEquals(mParsedMessages.get(0).getMessage(), "Log[1] logline1");
+ }
+}
diff --git a/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatTimestampTest.java b/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatTimestampTest.java
new file mode 100644
index 0000000..318a499
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/logcat/LogCatTimestampTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.ddmlib.logcat;
+
+import junit.framework.TestCase;
+
+public class LogCatTimestampTest extends TestCase {
+
+ public void testParseFromString() throws Exception {
+ String time = "01-23 12:34:56.789";
+ LogCatTimestamp parsed = LogCatTimestamp.fromString(time);
+ assertEquals(parsed.toString(), time);
+ }
+
+ public void testTimestampComparisons() throws Exception {
+ LogCatTimestamp lateDec = LogCatTimestamp.fromString("12-31 23:59:59.999");
+ LogCatTimestamp earlyJan = LogCatTimestamp.fromString("01-01 00:00:00.123");
+ LogCatTimestamp midYearMorning1 = LogCatTimestamp.fromString("06-15 06:00:30.000");
+ LogCatTimestamp midYearMorning2 = LogCatTimestamp.fromString("06-15 06:01:20.777");
+ LogCatTimestamp midYearMorning3 = LogCatTimestamp.fromString("06-15 06:01:30.666");
+ LogCatTimestamp midYearMorning4 = LogCatTimestamp.fromString("06-15 06:01:30.888");
+ LogCatTimestamp midYearNextDay = LogCatTimestamp.fromString("06-16 00:00:00.000");
+
+ assertTrue(lateDec.isBefore(earlyJan));
+ assertTrue(earlyJan.isBefore(midYearMorning1));
+ assertTrue(midYearMorning1.isBefore(midYearMorning2));
+ assertTrue(midYearMorning2.isBefore(midYearMorning3));
+ assertTrue(midYearMorning3.isBefore(midYearMorning4));
+ assertTrue(midYearMorning4.isBefore(midYearNextDay));
+
+ assertFalse(midYearNextDay.isBefore(midYearMorning4));
+ assertFalse(midYearMorning4.isBefore(midYearMorning3));
+ assertFalse(midYearMorning3.isBefore(midYearMorning2));
+ assertFalse(midYearMorning2.isBefore(midYearMorning1));
+ assertFalse(midYearMorning1.isBefore(earlyJan));
+ assertFalse(earlyJan.isBefore(lateDec));
+
+ assertFalse(earlyJan.isBefore(earlyJan));
+ }
+
+ public void testTimestampMillisecondsTruncated() throws Exception {
+ LogCatTimestamp msTimestamp = LogCatTimestamp.fromString("01-01 00:00:00.1234");
+ assertEquals(msTimestamp.toString(), "01-01 00:00:00.123");
+ }
+}
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java b/ddmlib/src/test/java/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java b/ddmlib/src/test/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/testrunner/TestRunResultTest.java b/ddmlib/src/test/java/com/android/ddmlib/testrunner/TestRunResultTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/testrunner/TestRunResultTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/testrunner/TestRunResultTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/testrunner/XmlTestRunListenerTest.java b/ddmlib/src/test/java/com/android/ddmlib/testrunner/XmlTestRunListenerTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/testrunner/XmlTestRunListenerTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/testrunner/XmlTestRunListenerTest.java
diff --git a/base/ddmlib/src/test/java/com/android/ddmlib/utils/DebuggerPortsTest.java b/ddmlib/src/test/java/com/android/ddmlib/utils/DebuggerPortsTest.java
similarity index 100%
rename from base/ddmlib/src/test/java/com/android/ddmlib/utils/DebuggerPortsTest.java
rename to ddmlib/src/test/java/com/android/ddmlib/utils/DebuggerPortsTest.java
diff --git a/base/device_validator/.gitignore b/device_validator/.gitignore
similarity index 100%
rename from base/device_validator/.gitignore
rename to device_validator/.gitignore
diff --git a/base/draw9patch/MODULE_LICENSE_APACHE2 b/device_validator/MODULE_LICENSE_APACHE2
similarity index 100%
rename from base/draw9patch/MODULE_LICENSE_APACHE2
rename to device_validator/MODULE_LICENSE_APACHE2
diff --git a/base/device_validator/app/.classpath b/device_validator/app/.classpath
similarity index 100%
rename from base/device_validator/app/.classpath
rename to device_validator/app/.classpath
diff --git a/base/device_validator/app/.gitignore b/device_validator/app/.gitignore
similarity index 100%
rename from base/device_validator/app/.gitignore
rename to device_validator/app/.gitignore
diff --git a/base/device_validator/app/.project b/device_validator/app/.project
similarity index 100%
rename from base/device_validator/app/.project
rename to device_validator/app/.project
diff --git a/base/device_validator/app/.settings/org.eclipse.jdt.ui.prefs b/device_validator/app/.settings/org.eclipse.jdt.ui.prefs
similarity index 100%
rename from base/device_validator/app/.settings/org.eclipse.jdt.ui.prefs
rename to device_validator/app/.settings/org.eclipse.jdt.ui.prefs
diff --git a/base/device_validator/app/etc/README b/device_validator/app/etc/README
similarity index 100%
rename from base/device_validator/app/etc/README
rename to device_validator/app/etc/README
diff --git a/base/device_validator/app/etc/device_validator b/device_validator/app/etc/device_validator
similarity index 100%
rename from base/device_validator/app/etc/device_validator
rename to device_validator/app/etc/device_validator
diff --git a/base/device_validator/app/src/com/android/validator/DeviceValidator.java b/device_validator/app/src/com/android/validator/DeviceValidator.java
similarity index 100%
rename from base/device_validator/app/src/com/android/validator/DeviceValidator.java
rename to device_validator/app/src/com/android/validator/DeviceValidator.java
diff --git a/base/device_validator/dvlib/.classpath b/device_validator/dvlib/.classpath
similarity index 100%
rename from base/device_validator/dvlib/.classpath
rename to device_validator/dvlib/.classpath
diff --git a/base/device_validator/dvlib/.gitignore b/device_validator/dvlib/.gitignore
similarity index 100%
rename from base/device_validator/dvlib/.gitignore
rename to device_validator/dvlib/.gitignore
diff --git a/base/device_validator/dvlib/.project b/device_validator/dvlib/.project
similarity index 100%
rename from base/device_validator/dvlib/.project
rename to device_validator/dvlib/.project
diff --git a/base/device_validator/dvlib/.settings/org.eclipse.jdt.core.prefs b/device_validator/dvlib/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/device_validator/dvlib/.settings/org.eclipse.jdt.core.prefs
rename to device_validator/dvlib/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/device_validator/dvlib/NOTICE b/device_validator/dvlib/NOTICE
similarity index 100%
rename from base/device_validator/dvlib/NOTICE
rename to device_validator/dvlib/NOTICE
diff --git a/base/device_validator/dvlib/build.gradle b/device_validator/dvlib/build.gradle
similarity index 100%
rename from base/device_validator/dvlib/build.gradle
rename to device_validator/dvlib/build.gradle
diff --git a/base/device_validator/dvlib/dvlib.iml b/device_validator/dvlib/dvlib.iml
similarity index 100%
rename from base/device_validator/dvlib/dvlib.iml
rename to device_validator/dvlib/dvlib.iml
diff --git a/base/device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java b/device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java
similarity index 100%
rename from base/device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java
rename to device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java
diff --git a/base/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-1.xsd b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-1.xsd
similarity index 100%
rename from base/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-1.xsd
rename to device_validator/dvlib/src/main/resources/com/android/dvlib/devices-1.xsd
diff --git a/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd
new file mode 100644
index 0000000..461eb90
--- /dev/null
+++ b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices-2.xsd
@@ -0,0 +1,955 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+-->
+
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/devices/2"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:c="http://schemas.android.com/sdk/devices/2"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The devices element contains a collection of device definitions.
+
+ History:
+ - v1 is used by the dvlib in Tools r20-22..
+
+ - v2 is used by the dvlib in Tools r23.
+ - It adds support for new ABIs arm64-v8a, x86_64 and mips64.
+ -->
+ <xsd:element name="devices" type="c:devicesType" />
+
+ <xsd:complexType name="devicesType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The "devices" element is the root element of this schema.
+
+ It must contain one or more "device" elements that each define the configurations
+ and states available for a given device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="device" minOccurs="1" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A device element contains one hardware profile for a device, along with
+ 1 or more software profiles and 1 or more states. Each software profile
+ defines the supported software for a given API release, and each state
+ profile defines a different possible state of the device (screen in
+ portrait orientation, screen in landscape orientation with the keyboard
+ out, etc.)
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="name" type= "xsd:token" />
+ <xsd:element name="id" type= "xsd:token" minOccurs="0" />
+ <xsd:element name="manufacturer" type= "xsd:token" />
+ <xsd:element name="meta" type= "c:metaType" minOccurs="0" />
+ <xsd:element name="hardware" type= "c:hardwareType" />
+ <xsd:element name="software" type= "c:softwareType"
+ maxOccurs="unbounded" />
+ <xsd:element name="state" type= "c:stateType"
+ maxOccurs="unbounded" />
+ <xsd:element name="tag-id" type= "c:idType" minOccurs="0" />
+ <xsd:element name="boot-props" type= "c:bootPropsType" minOccurs="0" />
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A tag string for a system image can only be a simple alphanumeric string.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="bootPropsType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ List of boot properties.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="boot-prop">
+ <xsd:complexType>
+ <xsd:all>
+ <xsd:element name="prop-name">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[^\n\r\t =]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="prop-value" type= "xsd:string" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="hardwareType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The hardwareType contains all of the hardware information for
+ a given device. This includes things like the GPU type, screen
+ size, mic presence, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="screen" type= "c:screenType" />
+ <xsd:element name="networking" type= "c:networkingType" />
+ <xsd:element name="sensors" type= "c:sensorsType" />
+ <xsd:element name="mic" type= "c:micType" />
+ <xsd:element name="camera" type= "c:cameraType"
+ minOccurs="0" maxOccurs="unbounded" />
+ <xsd:element name="keyboard" type= "c:keyboardType" />
+ <xsd:element name="nav" type= "c:navType" />
+ <xsd:element name="ram" type= "c:ramType" />
+ <xsd:element name="buttons" type= "c:buttonsType" />
+ <xsd:element name="internal-storage" type= "c:internalStorageType" />
+ <xsd:element name="removable-storage" type= "c:removableStorageType" />
+ <xsd:element name="cpu" type= "c:cpuType" />
+ <xsd:element name="gpu" type= "c:gpuType" />
+ <xsd:element name="abi" type= "c:abiType" />
+ <xsd:element name="dock" type= "c:dockType" />
+ <xsd:element name="power-type" type= "c:powerType" />
+ <xsd:element name="skin" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Path to a custom skin directory.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="softwareType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The softwareType contains all of the device's software
+ information for a given API version. This includes things like
+ live wallpaper support, OpenGL version, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="api-level">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies which API version(s) this this element is
+ defining. This can in the form of a single number
+ or a range of low to high, separated with a dash and
+ with either limit missing. The default lower limit is
+ one, and the default upper limit is unbounded.
+ The following are valid:
+ 10
+ 7-10
+ -10
+ 7-
+ -
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[\d]*-[\d]*|[\d]+" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="live-wallpaper-support" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device supports live wallpapers.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="bluetooth-profiles">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies all of the available Bluetooth profiles.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="A2DP" />
+ <xsd:enumeration value="ATT" />
+ <xsd:enumeration value="AVRCP" />
+ <xsd:enumeration value="AVDTP" />
+ <xsd:enumeration value="BIP" />
+ <xsd:enumeration value="BPP" />
+ <xsd:enumeration value="CIP" />
+ <xsd:enumeration value="CTP" />
+ <xsd:enumeration value="DIP" />
+ <xsd:enumeration value="DUN" />
+ <xsd:enumeration value="FAX" />
+ <xsd:enumeration value="FTP" />
+ <xsd:enumeration value="GAVDP" />
+ <xsd:enumeration value="GAP" />
+ <xsd:enumeration value="GATT" />
+ <xsd:enumeration value="GOEP" />
+ <xsd:enumeration value="HCRP" />
+ <xsd:enumeration value="HDP" />
+ <xsd:enumeration value="HFP" />
+ <xsd:enumeration value="HID" />
+ <xsd:enumeration value="HSP" />
+ <xsd:enumeration value="ICP" />
+ <xsd:enumeration value="LAP" />
+ <xsd:enumeration value="MAP" />
+ <xsd:enumeration value="OPP" />
+ <xsd:enumeration value="PAN" />
+ <xsd:enumeration value="PBA" />
+ <xsd:enumeration value="PBAP" />
+ <xsd:enumeration value="SPP" />
+ <xsd:enumeration value="SDAP" />
+ <xsd:enumeration value="SAP" />
+ <xsd:enumeration value="SIM" />
+ <xsd:enumeration value="rSAP" />
+ <xsd:enumeration value="SYNCH" />
+ <xsd:enumeration value="VDP" />
+ <xsd:enumeration value="WAPB" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="gl-version">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the OpenGL version supported for this release.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <xsd:pattern value="[0-9]\.[0-9]" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="gl-extensions">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies all of the supported OpenGL extensions for
+ this release.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:list itemType="xsd:NMTOKEN" />
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="status-bar" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has a status bar in this
+ software configuration.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="stateType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The stateType contains the information for a given state of
+ of the device. States include things like portrait mode,
+ landscape with the keyboard exposed, etc. States can also
+ modify the hardware attributes of a device. For instance, if
+ sliding out the keyboard increased the available screen
+ real estate, you can define a new screenType to override the
+ default one defined in the device's hardwareType.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="description" type="xsd:token">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A description of the defined state.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="screen-orientation">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the orientation of the screen. Use square if
+ the device's screen has equal height and width,
+ otherwise use landscape or portrait.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="port" />
+ <xsd:enumeration value="land" />
+ <xsd:enumeration value="square" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="keyboard-state">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the state of the keyboard. If the device has no
+ keyboard use keysoft, otherwise use keysexposed or keyshidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="keyssoft" />
+ <xsd:enumeration value="keyshidden" />
+ <xsd:enumeration value="keysexposed" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="nav-state">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines the state of the primary non-touchscreen
+ navigation hardware on the devices. If the device
+ doesn't have non-touchscreen navigation hardware use
+ nonav, otherwise use navexposed or navhidden.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="nonav" />
+ <xsd:enumeration value="navhidden" />
+ <xsd:enumeration value="navexposed" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="screen" type="c:screenType" minOccurs="0" />
+ <xsd:element name="networking" type="c:networkingType"
+ minOccurs="0" />
+ <xsd:element name="sensors" type="c:sensorsType" minOccurs="0" />
+ <xsd:element name="mic" type="c:micType" minOccurs="0" />
+ <xsd:element name="camera" type="c:cameraType"
+ minOccurs="0" maxOccurs="unbounded" />
+ <xsd:element name="keyboard" type="c:keyboardType" minOccurs="0" />
+ <xsd:element name="nav" type="c:navType" minOccurs="0" />
+ <xsd:element name="ram" type="c:ramType" minOccurs="0" />
+ <xsd:element name="buttons" type="c:buttonsType" minOccurs="0" />
+ <xsd:element name="internal-storage" type="c:internalStorageType"
+ minOccurs="0" />
+ <xsd:element name="removable-storage" type="c:removableStorageType"
+ minOccurs="0" />
+ <xsd:element name="cpu" type="c:cpuType" minOccurs="0" />
+ <xsd:element name="gpu" type="c:gpuType" minOccurs="0" />
+ <xsd:element name="abi" type="c:abiType" minOccurs="0" />
+ <xsd:element name="dock" type="c:dockType" minOccurs="0" />
+ <xsd:element name="power-type" type="c:powerType"
+ minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:token" />
+ <xsd:attribute name="default" use="optional" type="xsd:boolean" />
+ </xsd:complexType>
+
+ <xsd:complexType name="metaType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Details where more device information can be found, such as
+ icons and frame images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="icons" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains the relative paths to the icon files for this
+ device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="sixty-four" type="xsd:normalizedString">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Relative path for the 64x64 icon.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="sixteen" type="xsd:normalizedString"
+ minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Relative path for the 16x16 icon.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="frame" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains information about the frame for the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="path"
+ type="xsd:normalizedString">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The relative path to the emulator frame for
+ the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="portrait-x-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the x direction,
+ in portrait mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="portrait-y-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the y direction,
+ in portrait mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="landscape-x-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the x direction,
+ in landscape mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="landscape-y-offset"
+ type="xsd:nonNegativeInteger">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ The offset for the frame in the y direction,
+ in landscape mode.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="screenType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Contains the specifications for the device's screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="screen-size">
+ <xsd:simpleType>
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the class of the screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="small" />
+ <xsd:enumeration value="normal" />
+ <xsd:enumeration value="large" />
+ <xsd:enumeration value="xlarge" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="diagonal-length">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the diagonal length of the screen in inches.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative lengths are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="pixel-density">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the screen density of the device. The
+ medium density of traditional HVGA screens (mdpi)
+ is defined to be approximately 160dpi; low density
+ (ldpi) is 120, and high density (hdpi) is 240. There
+ is thus a 4:3 scaling factor between each density,
+ so a 9x9 bitmap in ldpi would be 12x12 in mdpi and
+ 16x16 in hdpi.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="ldpi" />
+ <xsd:enumeration value="mdpi" />
+ <xsd:enumeration value="tvdpi" />
+ <xsd:enumeration value="hdpi" />
+ <xsd:enumeration value="280dpi" />
+ <xsd:enumeration value="xhdpi" />
+ <xsd:enumeration value="360dpi" />
+ <xsd:enumeration value="400dpi" />
+ <xsd:enumeration value="420dpi" />
+ <xsd:enumeration value="xxhdpi" />
+ <xsd:enumeration value="560dpi" />
+ <xsd:enumeration value="xxxhdpi" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-ratio">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the configuration is for a taller or
+ wider than traditional screen. This is based purely on
+ the aspect ratio of the screen: QVGA, HVGA, and VGA are
+ notlong; WQVGA, WVGA, FWVGA are long. Note that long may
+ mean either wide or tall, depending on the current
+ orientation.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notlong" />
+ <xsd:enumeration value="long" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="dimensions">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device screen resolution in pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="x-dimension">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the x-dimension's resolution in
+ pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger" />
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="y-dimension">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the y-dimension's resolution in
+ pixels.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger" />
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="xdpi">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in X of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative DPIs are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="ydpi">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the actual density in Y of the device screen.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:decimal">
+ <!-- Negative DPIs are not valid -->
+ <xsd:minInclusive value="0" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="touch">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the touch properties of the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="multitouch">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the multitouch capabilities of the
+ device. This can be none if multitouch is
+ not supported, basic if the device can track
+ only basic two finger gestures, distinct if
+ the device can track two or more fingers
+ simultaneously, or jazz-hands if the device
+ can track 5 or more fingers simultaneously.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="none" />
+ <xsd:enumeration value="basic" />
+ <xsd:enumeration value="distinct" />
+ <xsd:enumeration value="jazz-hands" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="mechanism">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the mechanism the device was
+ created for.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notouch" />
+ <xsd:enumeration value="stylus" />
+ <xsd:enumeration value="finger" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="screen-type">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the type of touch screen on the
+ device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="notouch" />
+ <xsd:enumeration value="capacitive" />
+ <xsd:enumeration value="resistive" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="networkingType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the available networking hardware.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="NFC" />
+ <xsd:enumeration value="Bluetooth" />
+ <xsd:enumeration value="Wifi" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="sensorsType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the available sensors.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="Accelerometer" />
+ <xsd:enumeration value="Barometer" />
+ <xsd:enumeration value="Compass" />
+ <xsd:enumeration value="GPS" />
+ <xsd:enumeration value="Gyroscope" />
+ <xsd:enumeration value="LightSensor" />
+ <xsd:enumeration value="ProximitySensor" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="micType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has a mic or not.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:boolean" />
+ </xsd:simpleType>
+
+ <xsd:complexType name="cameraType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the attributes of the camera.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="location">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the location of the camera.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="front" />
+ <xsd:enumeration value="back" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+
+ <xsd:element name="autofocus" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the camera can autofocus
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="flash" type="xsd:boolean">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the camera has flash.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:simpleType name="keyboardType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the type of keyboard on the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="qwerty" />
+ <xsd:enumeration value="12key" />
+ <xsd:enumeration value="nokeys" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="navType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the primary non-touchscreen navigation
+ hardware on the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="dpad" />
+ <xsd:enumeration value="trackball" />
+ <xsd:enumeration value="wheel" />
+ <xsd:enumeration value="nonav" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="ramType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the amount of RAM on the device in the unit provided.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:positiveInteger">
+ <xsd:attribute name="unit" type="c:storageUnitType" use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="buttonsType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device has physical (hard) buttons
+ (Home, Search, etc.), or uses soft buttons.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="hard" />
+ <xsd:enumeration value="soft" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="internalStorageType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ A list specifying the sizes of internal storage in
+ the device, in the storage size unit provided.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="c:storageListType">
+ <xsd:attribute name="unit" type="c:storageUnitType"
+ use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="removableStorageType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the range of available removable storage sizes
+ in the unit provided. A positive value indicates the device is
+ available with that storage size included while a zero value
+ indicates an empty storage slot.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="c:storageListType">
+ <xsd:attribute name="unit" type="c:storageUnitType"
+ use="required" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="storageListType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Defines a list for storage configurations such as internal or
+ removable storage. A positive value indicates the the device
+ has a storage unit of that size, while a zero value indicates
+ there is an empty location for a storage unit (such as an empty
+ SD card slot).
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:nonNegativeInteger" />
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ <xsd:simpleType name="gpuType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device's GPU.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:minLength value="1" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="cpuType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the device's CPU.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:minLength value="1" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies which ABIs the device conforms to.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="arm64-v8a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ <xsd:enumeration value="mips" />
+ <!-- TODO double-check this is appropriate value for mips64 -->
+ <xsd:enumeration value="mips64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="dockType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the official docks available for the device.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="desk" />
+ <xsd:enumeration value="television" />
+ <xsd:enumeration value="car" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="powerType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies whether the device is plugged in.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="plugged-in" />
+ <xsd:enumeration value="battery" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="storageUnitType">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Specifies the unit of storage. This can be MiB, GiB, etc.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="B" />
+ <xsd:enumeration value="KiB" />
+ <xsd:enumeration value="MiB" />
+ <xsd:enumeration value="GiB" />
+ <xsd:enumeration value="TiB" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/base/device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java b/device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java
similarity index 100%
rename from base/device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java
rename to device_validator/dvlib/src/test/java/com/android/dvlib/DeviceSchemaTest.java
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices_minimal.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_default.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_default.xml
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_default.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_default.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_hardware.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_hardware.xml
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_hardware.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_hardware.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_software.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_software.xml
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_software.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_software.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_states.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_states.xml
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_states.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices_no_states.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_too_many_defaults.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_too_many_defaults.xml
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_too_many_defaults.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices_too_many_defaults.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_v2.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_v2.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/devices_v2.xml
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/devices_v2.xml
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.jpeg b/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.jpeg
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.jpeg
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.jpeg
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.png b/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.png
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.png
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/extras/frame.png
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.jpeg b/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.jpeg
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.jpeg
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.jpeg
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.png b/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.png
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.png
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixteen.png
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.jpeg b/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.jpeg
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.jpeg
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.jpeg
diff --git a/base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.png b/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.png
similarity index 100%
rename from base/device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.png
rename to device_validator/dvlib/src/test/resources/com/android/dvlib/extras/sixtyfour.png
diff --git a/base/lint/MODULE_LICENSE_APACHE2 b/draw9patch/MODULE_LICENSE_APACHE2
similarity index 100%
rename from base/lint/MODULE_LICENSE_APACHE2
rename to draw9patch/MODULE_LICENSE_APACHE2
diff --git a/base/draw9patch/NOTICE b/draw9patch/NOTICE
similarity index 100%
rename from base/draw9patch/NOTICE
rename to draw9patch/NOTICE
diff --git a/base/draw9patch/build.gradle b/draw9patch/build.gradle
similarity index 100%
rename from base/draw9patch/build.gradle
rename to draw9patch/build.gradle
diff --git a/base/draw9patch/draw9patch.iml b/draw9patch/draw9patch.iml
similarity index 100%
rename from base/draw9patch/draw9patch.iml
rename to draw9patch/draw9patch.iml
diff --git a/base/draw9patch/etc/draw9patch b/draw9patch/etc/draw9patch
similarity index 100%
rename from base/draw9patch/etc/draw9patch
rename to draw9patch/etc/draw9patch
diff --git a/base/draw9patch/etc/draw9patch.bat b/draw9patch/etc/draw9patch.bat
similarity index 100%
rename from base/draw9patch/etc/draw9patch.bat
rename to draw9patch/etc/draw9patch.bat
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/Application.java b/draw9patch/src/main/java/com/android/draw9patch/Application.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/Application.java
rename to draw9patch/src/main/java/com/android/draw9patch/Application.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java b/draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java
rename to draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java b/draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/GradientPanel.java b/draw9patch/src/main/java/com/android/draw9patch/ui/GradientPanel.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/GradientPanel.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/GradientPanel.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/ImageTransferHandler.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageTransferHandler.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/ImageTransferHandler.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/ImageTransferHandler.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java b/draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/OpenFilePanel.java b/draw9patch/src/main/java/com/android/draw9patch/ui/OpenFilePanel.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/OpenFilePanel.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/OpenFilePanel.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java b/draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/PngFileFilter.java b/draw9patch/src/main/java/com/android/draw9patch/ui/PngFileFilter.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/PngFileFilter.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/PngFileFilter.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java b/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/action/BackgroundAction.java b/draw9patch/src/main/java/com/android/draw9patch/ui/action/BackgroundAction.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/action/BackgroundAction.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/action/BackgroundAction.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/action/ExitAction.java b/draw9patch/src/main/java/com/android/draw9patch/ui/action/ExitAction.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/action/ExitAction.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/action/ExitAction.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/action/OpenAction.java b/draw9patch/src/main/java/com/android/draw9patch/ui/action/OpenAction.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/action/OpenAction.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/action/OpenAction.java
diff --git a/base/draw9patch/src/main/java/com/android/draw9patch/ui/action/SaveAction.java b/draw9patch/src/main/java/com/android/draw9patch/ui/action/SaveAction.java
similarity index 100%
rename from base/draw9patch/src/main/java/com/android/draw9patch/ui/action/SaveAction.java
rename to draw9patch/src/main/java/com/android/draw9patch/ui/action/SaveAction.java
diff --git a/base/draw9patch/src/main/resources/images/checker.png b/draw9patch/src/main/resources/images/checker.png
similarity index 100%
rename from base/draw9patch/src/main/resources/images/checker.png
rename to draw9patch/src/main/resources/images/checker.png
diff --git a/base/draw9patch/src/main/resources/images/drop.png b/draw9patch/src/main/resources/images/drop.png
similarity index 100%
rename from base/draw9patch/src/main/resources/images/drop.png
rename to draw9patch/src/main/resources/images/drop.png
diff --git a/base/draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java b/draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java
similarity index 100%
rename from base/draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java
rename to draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java
diff --git a/base/files/UPDATING_DEVICES.txt b/files/UPDATING_DEVICES.txt
similarity index 100%
rename from base/files/UPDATING_DEVICES.txt
rename to files/UPDATING_DEVICES.txt
diff --git a/base/files/android.el b/files/android.el
similarity index 100%
rename from base/files/android.el
rename to files/android.el
diff --git a/base/files/proguard-android-optimize.txt b/files/proguard-android-optimize.txt
similarity index 100%
rename from base/files/proguard-android-optimize.txt
rename to files/proguard-android-optimize.txt
diff --git a/base/files/proguard-android.txt b/files/proguard-android.txt
similarity index 100%
rename from base/files/proguard-android.txt
rename to files/proguard-android.txt
diff --git a/base/files/proguard-project.txt b/files/proguard-project.txt
similarity index 100%
rename from base/files/proguard-project.txt
rename to files/proguard-project.txt
diff --git a/base/files/typos/typos-de.txt b/files/typos/typos-de.txt
similarity index 100%
rename from base/files/typos/typos-de.txt
rename to files/typos/typos-de.txt
diff --git a/base/files/typos/typos-en.txt b/files/typos/typos-en.txt
similarity index 100%
rename from base/files/typos/typos-en.txt
rename to files/typos/typos-en.txt
diff --git a/base/files/typos/typos-es.txt b/files/typos/typos-es.txt
similarity index 100%
rename from base/files/typos/typos-es.txt
rename to files/typos/typos-es.txt
diff --git a/base/files/typos/typos-hu.txt b/files/typos/typos-hu.txt
similarity index 100%
rename from base/files/typos/typos-hu.txt
rename to files/typos/typos-hu.txt
diff --git a/base/files/typos/typos-it.txt b/files/typos/typos-it.txt
similarity index 100%
rename from base/files/typos/typos-it.txt
rename to files/typos/typos-it.txt
diff --git a/base/files/typos/typos-nb.txt b/files/typos/typos-nb.txt
similarity index 100%
rename from base/files/typos/typos-nb.txt
rename to files/typos/typos-nb.txt
diff --git a/base/files/typos/typos-pt.txt b/files/typos/typos-pt.txt
similarity index 100%
rename from base/files/typos/typos-pt.txt
rename to files/typos/typos-pt.txt
diff --git a/base/files/typos/typos-tr.txt b/files/typos/typos-tr.txt
similarity index 100%
rename from base/files/typos/typos-tr.txt
rename to files/typos/typos-tr.txt
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index 9b1dbdf..0000000
--- a/gradle.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m
-org.gradle.daemon=true
diff --git a/instant-run/instant-run-annotations/build.gradle b/instant-run/instant-run-annotations/build.gradle
new file mode 100644
index 0000000..f05c804
--- /dev/null
+++ b/instant-run/instant-run-annotations/build.gradle
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'java'
+apply plugin: 'jacoco'
+
+sourceCompatibility = JavaVersion.VERSION_1_6
+targetCompatibility = JavaVersion.VERSION_1_6
+
+dependencies {
+ compile project(':base:annotations')
+}
\ No newline at end of file
diff --git a/instant-run/instant-run-annotations/instant-run-annotations.iml b/instant-run/instant-run-annotations/instant-run-annotations.iml
new file mode 100644
index 0000000..a04dfa9
--- /dev/null
+++ b/instant-run/instant-run-annotations/instant-run-annotations.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="android-annotations" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/instant-run/instant-run-annotations/src/main/java/com/android/tools/ir/api/DisableInstantRun.java b/instant-run/instant-run-annotations/src/main/java/com/android/tools/ir/api/DisableInstantRun.java
new file mode 100644
index 0000000..8ef9fb6
--- /dev/null
+++ b/instant-run/instant-run-annotations/src/main/java/com/android/tools/ir/api/DisableInstantRun.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.ir.api;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotate a method, a class, a package when InstantRun should be disabled for the annotated
+ * element.
+ */
+ at Documented
+ at Retention(RetentionPolicy.CLASS)
+ at Target({PACKAGE, TYPE, CONSTRUCTOR, METHOD})
+public @interface DisableInstantRun {
+}
diff --git a/instant-run/instant-run-client/build.gradle b/instant-run/instant-run-client/build.gradle
new file mode 100644
index 0000000..faa8e28
--- /dev/null
+++ b/instant-run/instant-run-client/build.gradle
@@ -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.
+ */
+
+apply plugin: 'java'
+apply plugin: 'jacoco'
+
+dependencies {
+ compile project(':base:instant-run:instant-run-common')
+ compile project(':base:ddmlib')
+ testCompile 'junit:junit:4.12'
+}
\ No newline at end of file
diff --git a/instant-run/instant-run-client/instant-run-client.iml b/instant-run/instant-run-client/instant-run-client.iml
new file mode 100644
index 0000000..75e18c8
--- /dev/null
+++ b/instant-run/instant-run-client/instant-run-client.iml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="instant-run-common" />
+ <orderEntry type="module" module-name="ddmlib" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/AppState.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/AppState.java
new file mode 100644
index 0000000..06586d4
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/AppState.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.client;
+
+/**
+ * The application state on device/emulator: the app is not running (or we cannot find it due to
+ * connection problems etc), or it's in the foreground or background.
+ */
+public enum AppState {
+ /** The app is not running (or we cannot find it due to connection problems etc) */
+ NOT_RUNNING,
+ /** The app is running an obsolete/older version of the runtime library */
+ OBSOLETE,
+ /** The app is actively running in the foreground */
+ FOREGROUND,
+ /** The app is running, but is not in the foreground */
+ BACKGROUND
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/ApplicationPatchUtil.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/ApplicationPatchUtil.java
new file mode 100644
index 0000000..001c6a7
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/ApplicationPatchUtil.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.client;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.fd.runtime.ApplicationPatch;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Application patch methods only needed by the client.
+ */
+public class ApplicationPatchUtil {
+
+ public static void write(
+ @NonNull DataOutputStream output,
+ @Nullable List<ApplicationPatch> changes,
+ @NonNull UpdateMode updateMode)
+ throws IOException {
+ if (changes == null) {
+ output.writeInt(0);
+ } else {
+ output.writeInt(changes.size());
+ for (ApplicationPatch change : changes) {
+ write(output, change);
+ }
+ }
+ output.writeInt(updateMode.getId());
+ }
+
+ private static void write(@NonNull DataOutputStream output, @NonNull ApplicationPatch change)
+ throws IOException {
+ output.writeUTF(change.path);
+ byte[] bytes = change.data;
+ output.writeInt(bytes.length);
+ output.write(bytes);
+ }
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/Communicator.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/Communicator.java
new file mode 100644
index 0000000..e1b0a26
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/Communicator.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.client;
+
+import com.android.annotations.NonNull;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+abstract class Communicator<T> {
+
+ abstract T communicate(@NonNull DataInputStream input, @NonNull DataOutputStream output) throws
+ IOException;
+
+ int getTimeout() {
+ return 2000;
+ }
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunArtifact.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunArtifact.java
new file mode 100644
index 0000000..6aa79fe
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunArtifact.java
@@ -0,0 +1,39 @@
+/*
+ * 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.tools.fd.client;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+public class InstantRunArtifact {
+
+ @NonNull
+ public final InstantRunArtifactType type;
+
+ @NonNull
+ public final File file;
+
+ public InstantRunArtifact(@NonNull InstantRunArtifactType type, @NonNull File file) {
+ this.type = type;
+ this.file = file;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Artifact %s: %s", type, file.getPath());
+ }
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunArtifactType.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunArtifactType.java
new file mode 100644
index 0000000..6e1faaa
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunArtifactType.java
@@ -0,0 +1,60 @@
+/*
+ * 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.tools.fd.client;
+
+/**
+ * {@link InstantRunArtifactType} lists the possible artifacts as specified by the type attribute of
+ * artifact tag in the build-info.xml The names given here match the names expected in the XML
+ * file.
+ */
+public enum InstantRunArtifactType {
+ /**
+ * Main APK (contains resources)
+ */
+ MAIN,
+
+ /**
+ * Main APK in a split scenario (contains resources)
+ */
+ SPLIT_MAIN,
+
+ /**
+ * APK split (that can be installed on API 23+, maybe 22)
+ */
+ SPLIT,
+
+ /**
+ * Shard dex file that can be deployed on L devices
+ */
+ DEX,
+
+ /**
+ * Hot swappable classes
+ */
+ RELOAD_DEX,
+
+ /**
+ * Classes that can be used on Dalvik devices
+ * @deprecated No longer used; remove once the Gradle parts are gone (InstantRunBuildContext.FileType.RESTART_DEX)
+ */
+ @Deprecated
+ RESTART_DEX,
+
+ /**
+ * Resources and manifest data
+ */
+ RESOURCES
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunBuildInfo.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunBuildInfo.java
new file mode 100644
index 0000000..5eea247
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunBuildInfo.java
@@ -0,0 +1,272 @@
+/*
+ * 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.tools.fd.client;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * {@link InstantRunBuildInfo} models the build-info.xml file that is generated by an instant-run
+ * aware Gradle build.
+ */
+ at SuppressWarnings({"WeakerAccess", "unused"}) // Used in studio and in integration tests.
+public class InstantRunBuildInfo {
+
+ /**
+ * Right now Gradle plugin doesn't sort the build id's, but when it does we can rely on element
+ * order in the doc
+ */
+ private static final boolean BUILDS_ARE_SORTED = false;
+
+ private static final String ATTR_TIMESTAMP = "timestamp";
+
+ public static final String ATTR_API_LEVEL = "api-level";
+
+ private static final String ATTR_FORMAT = "format";
+
+ private static final String ATTR_VERIFIER_STATUS = "verifier";
+
+ // Note: The verifier status can be any number of values (See InstantRunVerifierStatus enum in gradle).
+ // Currently, the only contract between gradle and the IDE is that the value is set to COMPATIBLE if the build can be hotswapped
+ public static final String VALUE_VERIFIER_STATUS_COMPATIBLE = "COMPATIBLE";
+
+ private static final String TAG_ARTIFACT = "artifact";
+
+ private static final String TAG_BUILD = "build";
+
+ private static final String ATTR_ARTIFACT_LOCATION = "location";
+
+ private static final String ATTR_ARTIFACT_TYPE = "type";
+
+ @NonNull
+ private final Element mRoot;
+
+ @Nullable
+ private List<InstantRunArtifact> mArtifacts;
+
+ public InstantRunBuildInfo(@NonNull Element root) {
+ mRoot = root;
+ }
+
+
+ @NonNull
+ public String getTimeStamp() {
+ return mRoot.getAttribute(ATTR_TIMESTAMP);
+ }
+
+ @NonNull
+ public String getVerifierStatus() {
+ return mRoot.getAttribute(ATTR_VERIFIER_STATUS);
+ }
+
+ public boolean canHotswap() {
+ String verifierStatus = getVerifierStatus();
+ if (VALUE_VERIFIER_STATUS_COMPATIBLE.equals(verifierStatus)) {
+ return true;
+ } else if (verifierStatus.isEmpty()) {
+ // build-info.xml doesn't currently specify a verifier status if there is *only* a resource
+ // change!
+ List<InstantRunArtifact> artifacts = getArtifacts();
+ if (artifacts.size() == 1
+ && artifacts.get(0).type == InstantRunArtifactType.RESOURCES) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** Returns whether there were NO changes from the previous build. */
+ public boolean hasNoChanges() {
+ // In order to say that there are no changes:
+ // 1. The artifacts have to be empty
+ // 2. The verifier status must also be empty, since otherwise it indicates that there are changes that were not captured by
+ // the current incremental build.
+ return getArtifacts().isEmpty() && getVerifierStatus().isEmpty();
+ }
+
+ public int getFeatureLevel() { // The build info calls it as the API level, but we always pass the feature level to Gradle..
+ String attribute = mRoot.getAttribute(ATTR_API_LEVEL);
+ if (attribute != null && !attribute.isEmpty()) {
+ try {
+ return Integer.parseInt(attribute);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ return -1; // unknown
+ }
+
+ @NonNull
+ public List<InstantRunArtifact> getArtifacts() {
+ if (mArtifacts == null) {
+ List<InstantRunArtifact> artifacts = Lists.newArrayList();
+
+ Element oldestBuild = null;
+ long oldestTimeStamp = Long.MAX_VALUE;
+
+ NodeList children = mRoot.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) child;
+ String tagName = element.getTagName();
+ if (!TAG_ARTIFACT.equals(tagName)) {
+ if (!BUILDS_ARE_SORTED && TAG_BUILD.equals(tagName)) {
+ String timestamp = element.getAttribute(ATTR_TIMESTAMP);
+ if (!timestamp.isEmpty()) {
+ try {
+ long time = Long.parseLong(timestamp);
+ if (time < oldestTimeStamp) {
+ oldestTimeStamp = time;
+ oldestBuild = element;
+ }
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ }
+ continue;
+ }
+
+ String location = element.getAttribute(ATTR_ARTIFACT_LOCATION);
+ String typeAttribute = element.getAttribute(ATTR_ARTIFACT_TYPE);
+ InstantRunArtifactType type = InstantRunArtifactType.valueOf(typeAttribute);
+ artifacts.add(new InstantRunArtifact(type, new File(location)));
+ }
+ }
+
+ mArtifacts = artifacts;
+
+ if (hasOneOf(InstantRunArtifactType.SPLIT_MAIN)) {
+ // If main has changed, we need ALL the slices. Look in the history for these.
+ // This is always available from the LAST build history.
+ if (BUILDS_ARE_SORTED) {
+ for (int i = children.getLength() - 1; i >= 0; i--) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) child;
+ if (!TAG_BUILD.equals(element.getTagName())) {
+ continue;
+ }
+ oldestBuild = element;
+ break;
+ }
+ }
+ }
+
+ if (oldestBuild != null) {
+ ListIterator<InstantRunArtifact> iterator = artifacts.listIterator();
+ while (iterator.hasNext()) {
+ InstantRunArtifact artifact = iterator.next();
+ if (artifact.type == InstantRunArtifactType.SPLIT) {
+ iterator.remove();
+ }
+ }
+
+ NodeList nestedChildren = oldestBuild.getChildNodes();
+ for (int j = 0, n = nestedChildren.getLength(); j < n; j++) {
+ Node nestedChild = nestedChildren.item(j);
+ if (nestedChild.getNodeType() == Node.ELEMENT_NODE) {
+ Element artifactElement = (Element) nestedChild;
+ if (!TAG_ARTIFACT.equals(artifactElement.getTagName())) {
+ continue;
+ }
+
+ String typeAttribute = artifactElement.getAttribute(ATTR_ARTIFACT_TYPE);
+ InstantRunArtifactType type = InstantRunArtifactType
+ .valueOf(typeAttribute);
+ if (type == InstantRunArtifactType.SPLIT) {
+ String location = artifactElement
+ .getAttribute(ATTR_ARTIFACT_LOCATION);
+ artifacts.add(new InstantRunArtifact(type, new File(location)));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return mArtifacts;
+ }
+
+ /**
+ * Returns true if the given list of artifacts contains at least one artifact of any of the
+ * given types
+ *
+ * @param types the types to look for
+ * @return true if and only if the list of artifacts contains an artifact of any of the given
+ * types
+ */
+ public boolean hasOneOf(@NonNull InstantRunArtifactType... types) {
+ for (InstantRunArtifact artifact : getArtifacts()) {
+ for (InstantRunArtifactType type : types) {
+ if (artifact.type == type) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean hasMainApk() {
+ return hasOneOf(InstantRunArtifactType.MAIN) || hasOneOf(InstantRunArtifactType.SPLIT_MAIN);
+ }
+
+ @Nullable
+ public static InstantRunBuildInfo get(@NonNull String xml) {
+ Document doc = XmlUtils.parseDocumentSilently(xml, false);
+ if (doc == null) {
+ return null;
+ }
+
+ return new InstantRunBuildInfo(doc.getDocumentElement());
+ }
+
+ // Keep roughly in sync with InstantRunBuildContext#CURRENT_FORMAT.
+ //
+ // See longer comment on that field for why they're separate fields rather
+ // than this code just referencing that field.
+ public boolean isCompatibleFormat() {
+ // Right don't accept older versions; due to bugs we want to force everyone to use the latest or no instant run at all.
+ // In the future we'll probably accept a range of values here.
+ return getFormat() == 7;
+ }
+
+ public int getFormat() {
+ String attribute = mRoot.getAttribute(ATTR_FORMAT);
+ if (Strings.isNullOrEmpty(attribute)) {
+ return -1;
+ }
+
+ try {
+ return Integer.parseInt(attribute);
+ } catch (NumberFormatException nfe) {
+ return -1;
+ }
+ }
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunClient.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunClient.java
new file mode 100644
index 0000000..84a11dd
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunClient.java
@@ -0,0 +1,809 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.client;
+
+import static com.android.tools.fd.client.InstantRunArtifactType.DEX;
+import static com.android.tools.fd.client.InstantRunArtifactType.SPLIT;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_EOF;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_PATCHES;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_PING;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_RESTART_ACTIVITY;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_SHOW_TOAST;
+import static com.android.tools.fd.common.ProtocolConstants.PROTOCOL_IDENTIFIER;
+import static com.android.tools.fd.common.ProtocolConstants.PROTOCOL_VERSION;
+import static com.android.tools.fd.runtime.Paths.getDeviceIdFolder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.SyncException;
+import com.android.ddmlib.TimeoutException;
+import com.android.tools.fd.runtime.ApplicationPatch;
+import com.android.tools.fd.runtime.Paths;
+import com.android.utils.ILogger;
+import com.android.utils.NullLogger;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class InstantRunClient {
+ public static final String BROKEN_RUN_AS = "run-as command broken on this device";
+
+ private static final String LOCAL_HOST = "127.0.0.1";
+
+ /** Local port on the desktop machine via which we tunnel to the Android device */
+ private static final int DEFAULT_LOCAL_PORT = 46622; // Note: just a random number, hopefully it is a free/available port on the host
+
+ /** Prefix for classes.dex files */
+ private static final String CLASSES_DEX_PREFIX = "classes";
+
+ /** Suffix for classes.dex files */
+ private static final String CLASSES_DEX_SUFFIX = ".dex";
+
+
+ /**
+ * Instead of writing to the data folder, we can read/write to a local temp file instead.
+ * This is required because some devices (Samsung Galaxy Edge atleast) doesn't allow access into the package folder even with run-as.
+ */
+ public static final boolean USE_BUILD_ID_TEMP_FILE =
+ !Boolean.getBoolean("instantrun.use_datadir");
+
+ @NonNull
+ private final UserFeedback mUserFeedback;
+
+ @NonNull
+ private final ILogger mLogger;
+
+ @NonNull
+ private final String mPackageName;
+
+ private final long mToken;
+ private final int mLocalPort;
+
+ public InstantRunClient(
+ @NonNull String packageName,
+ @NonNull UserFeedback userFeedback,
+ @NonNull ILogger logger,
+ long token) {
+ this(packageName, userFeedback, logger, token, DEFAULT_LOCAL_PORT);
+ }
+
+ @VisibleForTesting
+ public InstantRunClient(
+ @NonNull String packageName,
+ @NonNull UserFeedback userFeedback,
+ @NonNull ILogger logger,
+ long token,
+ int port) {
+ mPackageName = packageName;
+ mUserFeedback = userFeedback;
+ mLogger = logger;
+ mToken = token;
+ mLocalPort = port;
+ }
+
+ @NonNull
+ private static String copyToDeviceScratchFile(@NonNull IDevice device, @NonNull String pkgName,
+ @NonNull String contents)
+ throws IOException, AdbCommandRejectedException, SyncException, TimeoutException {
+
+ File local = null;
+ try {
+ local = createTempFile("data", "fdr");
+ Files.write(contents.getBytes(Charsets.UTF_8), local);
+ return copyToDeviceScratchFile(device, pkgName, local);
+ } finally {
+ if (local != null) {
+ //noinspection ResultOfMethodCallIgnored
+ local.delete();
+ }
+ }
+ }
+
+ @NonNull
+ private static String copyToDeviceScratchFile(@NonNull IDevice device, @NonNull String pkgName,
+ @NonNull File local)
+ throws IOException, AdbCommandRejectedException, SyncException, TimeoutException {
+ String remoteTmpBuildId = Paths.DEVICE_TEMP_DIR + "/" + pkgName + "-data.fdr";
+ device.pushFile(local.getAbsolutePath(), remoteTmpBuildId);
+ return remoteTmpBuildId;
+ }
+
+ private static int getMaxDexFileNumber(@NonNull String fileListing) {
+ int max = -1;
+
+ for (String name : Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings()
+ .splitToList(fileListing)) {
+ if (name.startsWith(CLASSES_DEX_PREFIX) && name.endsWith(CLASSES_DEX_SUFFIX)) {
+ String middle = name.substring(CLASSES_DEX_PREFIX.length(),
+ name.length() - CLASSES_DEX_SUFFIX.length());
+ try {
+ int version = Integer.decode(middle);
+ if (version > max) {
+ max = version;
+ }
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ }
+
+ return max;
+ }
+
+ private static File createTempFile(String prefix, String suffix) throws IOException {
+ //noinspection SSBasedInspection Tests use this in tools/base
+ File file = File.createTempFile(prefix, suffix);
+ file.deleteOnExit();
+ return file;
+ }
+
+ /**
+ * Attempts to connect to a given device and sees if an instant run enabled app is running
+ * there.
+ */
+ @NonNull
+ public AppState getAppState(@NonNull IDevice device) {
+ try {
+ return talkToApp(device,
+ new Communicator<AppState>() {
+ @Override
+ public AppState communicate(@NonNull DataInputStream input,
+ @NonNull DataOutputStream output) throws
+ IOException {
+ output.writeInt(MESSAGE_PING);
+ // Wait for "pong"
+ boolean foreground = input.readBoolean();
+ mLogger.info(
+ "Ping sent and replied successfully, application seems to be running. Foreground="
+ + foreground);
+ return foreground ? AppState.FOREGROUND : AppState.BACKGROUND;
+ }
+ }, AppState.NOT_RUNNING);
+ } catch (Throwable e) {
+ return AppState.NOT_RUNNING;
+ }
+ }
+
+ @NonNull
+ private <T> T talkToApp(@NonNull IDevice device,
+ @NonNull Communicator<T> communicator,
+ @NonNull T errorValue) {
+
+ try {
+ device.createForward(mLocalPort, mPackageName,
+ IDevice.DeviceUnixSocketNamespace.ABSTRACT);
+ try {
+ return talkToAppWithinPortForward(communicator, errorValue, mLocalPort);
+ } catch (UnknownHostException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (SocketException e) {
+ if (e.getMessage().equals("Broken pipe")) {
+ mUserFeedback.error("No connection to app; cannot sync changes");
+ return errorValue;
+ }
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (IOException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (Throwable e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ return errorValue;
+ } finally {
+ device.removeForward(mLocalPort, mPackageName,
+ IDevice.DeviceUnixSocketNamespace.ABSTRACT);
+ }
+ } catch (TimeoutException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (AdbCommandRejectedException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (Throwable e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ }
+
+ return errorValue;
+ }
+
+ private static <T> T talkToAppWithinPortForward(@NonNull Communicator<T> communicator,
+ @NonNull T errorValue, int localPort) throws IOException {
+ Socket socket = new Socket(LOCAL_HOST, localPort);
+ try {
+ socket.setSoTimeout(8 * 1000); // Allow up to 8 second before timing out
+ DataOutputStream output = new DataOutputStream(socket.getOutputStream());
+ try {
+ DataInputStream input = new DataInputStream(socket.getInputStream());
+ try {
+ output.writeLong(PROTOCOL_IDENTIFIER);
+ output.writeInt(PROTOCOL_VERSION);
+
+ int version = input.readInt();
+ if (version != PROTOCOL_VERSION) {
+ return errorValue;
+ }
+
+ socket.setSoTimeout(communicator.getTimeout());
+ T value = communicator.communicate(input, output);
+
+ output.writeInt(MESSAGE_EOF);
+
+ return value;
+ } finally {
+ input.close();
+ }
+ } finally {
+ output.close();
+ }
+ } finally {
+ socket.close();
+ }
+ }
+
+ public void showToast(@NonNull IDevice device, @NonNull final String message) {
+ try {
+ talkToApp(device, new Communicator<Boolean>() {
+ @Override
+ public Boolean communicate(@NonNull DataInputStream input,
+ @NonNull DataOutputStream output) throws IOException {
+ output.writeInt(MESSAGE_SHOW_TOAST);
+ output.writeUTF(message);
+ return false;
+ }
+ }, true);
+ } catch (Throwable e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ }
+ }
+
+ /**
+ * Restart the activity on this device, if it's running and is in the foreground.
+ */
+ public void restartActivity(@NonNull IDevice device) {
+ AppState appState = getAppState(device);
+ if (appState == AppState.FOREGROUND || appState == AppState.BACKGROUND) {
+ talkToApp(device, new Communicator<Boolean>() {
+ @Override
+ public Boolean communicate(@NonNull DataInputStream input,
+ @NonNull DataOutputStream output) throws IOException {
+ output.writeInt(MESSAGE_RESTART_ACTIVITY);
+ writeToken(output);
+ return false;
+ }
+ }, true);
+ }
+ }
+
+ public UpdateMode pushPatches(@NonNull IDevice device,
+ @NonNull final InstantRunBuildInfo buildInfo,
+ @NonNull UpdateMode updateMode,
+ final boolean isRestartActivity,
+ final boolean isShowToastEnabled) throws InstantRunPushFailedException {
+ if (!buildInfo.canHotswap()) {
+ updateMode = updateMode.combine(UpdateMode.COLD_SWAP);
+ }
+
+ List<FileTransfer> files = Lists.newArrayList();
+
+ AppState appState = getAppState(device);
+ boolean appInForeground = appState == AppState.FOREGROUND;
+ boolean appRunning = appState == AppState.FOREGROUND || appState == AppState.BACKGROUND;
+
+ List<InstantRunArtifact> artifacts = buildInfo.getArtifacts();
+ for (InstantRunArtifact artifact : artifacts) {
+ InstantRunArtifactType type = artifact.type;
+ File file = artifact.file;
+ switch (type) {
+ case MAIN:
+ case SPLIT_MAIN:
+ // Should only be used here when we're doing a *compatible*
+ // resource swap and also got an APK for split. Ignore here.
+ continue;
+ case SPLIT:
+ // Should never be used with this method: APK splits should
+ // be pushed by SplitApkDeployTask
+ assert false : artifact;
+ break;
+ case RESOURCES:
+ updateMode = updateMode.combine(UpdateMode.WARM_SWAP);
+ files.add(FileTransfer.createResourceFile(file));
+ break;
+ case DEX:
+ String name = file.getParentFile().getName() + "-" + file.getName();
+ files.add(FileTransfer.createSliceDex(file, name));
+ break;
+ case RELOAD_DEX:
+ if (appInForeground) {
+ files.add(FileTransfer.createHotswapPatch(file));
+ } else {
+ // Gradle created a reload dex, but the app is no longer running.
+ // If it created a cold swap artifact, we can use it; otherwise we're out of luck.
+ if (!buildInfo.hasOneOf(DEX, SPLIT)) {
+ throw new InstantRunPushFailedException("Can't apply hot swap patch: app is no longer running");
+ }
+ }
+ break;
+ default:
+ assert false : artifact;
+ }
+ }
+
+ boolean needRestart;
+
+ if (appRunning) {
+ List<ApplicationPatch> changes = new ArrayList<ApplicationPatch>(files.size());
+ for (FileTransfer file : files) {
+ try {
+ changes.add(file.getPatch());
+ }
+ catch (IOException e) {
+ throw new InstantRunPushFailedException("Could not read file " + file);
+ }
+ }
+ pushPatches(device, buildInfo.getTimeStamp(), changes, updateMode, isRestartActivity, isShowToastEnabled);
+
+ needRestart = false;
+ if (!appInForeground || !buildInfo.canHotswap()) {
+ stopApp(device, false /* sendChangeBroadcast */);
+ needRestart = true;
+ }
+ }
+ else {
+ // Push to data directory
+ pushFiles(files, device, buildInfo.getTimeStamp());
+ needRestart = true;
+ }
+
+ logFilesPushed(files, needRestart);
+
+ if (needRestart) {
+ // TODO: this should not need to be explicit, but leaving in to ensure no behaviour change.
+ return UpdateMode.COLD_SWAP;
+ }
+ return updateMode;
+ }
+
+ public void pushPatches(@Nullable IDevice device,
+ @NonNull final String buildId,
+ @NonNull final List<ApplicationPatch> changes,
+ @NonNull UpdateMode updateMode,
+ final boolean isRestartActivity,
+ final boolean isShowToastEnabled) {
+ if (changes.isEmpty() || updateMode == UpdateMode.NO_CHANGES) {
+ // Sync the build id to the device; Gradle might rev the build id even when there are no changes,
+ // and we need to make sure that the device id reflects this new build id, or the next
+ // build will discover different id's and will conclude that it needs to do a full rebuild
+ if (device != null) {
+ transferLocalIdToDeviceId(device, buildId);
+ }
+
+ mUserFeedback.noChanges();
+ return;
+ }
+
+ if (updateMode == UpdateMode.HOT_SWAP && isRestartActivity) {
+ updateMode = updateMode.combine(UpdateMode.WARM_SWAP);
+ }
+
+ if (device != null) {
+ final UpdateMode updateMode1 = updateMode;
+ talkToApp(device, new Communicator<Boolean>() {
+ @Override
+ public Boolean communicate(@NonNull DataInputStream input,
+ @NonNull DataOutputStream output) throws IOException {
+ output.writeInt(MESSAGE_PATCHES);
+ writeToken(output);
+ ApplicationPatchUtil.write(output, changes, updateMode1);
+
+ // Let the app know whether it should show toasts
+ output.writeBoolean(isShowToastEnabled);
+
+ // Finally read a boolean back from the other side; this has the net effect of
+ // waiting until applying/verifying code on the other side is done. (It doesn't
+ // count the actual restart time, but for activity restarts it's typically instant,
+ // and for cold starts we have no easy way to handle it (the process will die and a
+ // new process come up; to measure that we'll need to work a lot harder.)
+ input.readBoolean();
+
+ return false;
+ }
+
+ @Override
+ int getTimeout() {
+ return 8000; // allow up to 8 seconds for resource push
+ }
+ }, true);
+
+ transferLocalIdToDeviceId(device, buildId);
+ }
+
+ mUserFeedback.notifyEnd(updateMode);
+ }
+
+ /**
+ * Called after a build & successful push to device: updates the build id on the device to
+ * whatever the build id was assigned by Gradle.
+ *
+ * @param device the device to push to
+ */
+ public void transferLocalIdToDeviceId(@NonNull IDevice device, @NonNull String buildId) {
+ transferBuildIdToDevice(device, buildId, mPackageName, mLogger);
+ }
+
+ // Note: This method can be called even if IR is turned off, as even when IR is off, we want to
+ // trash any existing build ids saved on the device.
+ public static void transferBuildIdToDevice(@NonNull IDevice device,
+ @NonNull String buildId,
+ @NonNull String applicationId,
+ @Nullable ILogger logger) {
+ if (logger == null) {
+ logger = new NullLogger();
+ }
+
+ try {
+ if (USE_BUILD_ID_TEMP_FILE) {
+ String remoteIdFile = getDeviceIdFolder(applicationId);
+ //noinspection SSBasedInspection This should work
+ File local = File.createTempFile("build-id", "txt");
+ local.deleteOnExit();
+ Files.write(buildId, local, Charsets.UTF_8);
+ device.pushFile(local.getPath(), remoteIdFile);
+ } else {
+ String remote = copyToDeviceScratchFile(device, applicationId, buildId);
+ String dataDir = Paths.getDataDirectory(applicationId);
+
+ // We used to do this here:
+ //String cmd = "run-as " + pkg + " mkdir -p " + dataDir + "; run-as " + pkg + " cp " + remote + " " + dataDir + "/" + BUILD_ID_TXT;
+ // but it turns out "cp" is missing on API 15! Let's use cat and sh instead which seems to be available everywhere.
+ // (Note: echo is not, it's missing on API 19.)
+ String cmd = "run-as " + applicationId + " mkdir -p " + dataDir + "; cat " + remote
+ + " | run-as " + applicationId + " sh -c 'cat > " + dataDir + "/"
+ + Paths.BUILD_ID_TXT + "'";
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ device.executeShellCommand(cmd, receiver);
+ String output = receiver.getOutput();
+ if (!output.trim().isEmpty()) {
+ logger.warning("Unexpected shell output: " + output);
+ }
+ }
+ } catch (IOException ioe) {
+ logger.warning("Couldn't write build id file: %s", ioe);
+ } catch (AdbCommandRejectedException e) {
+ logger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (TimeoutException e) {
+ logger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (ShellCommandUnresponsiveException e) {
+ logger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (SyncException e) {
+ logger.warning("%s", Throwables.getStackTraceAsString(e));
+ }
+ }
+
+ /**
+ * Returns the build timestamp on the device, or null if it is not found.
+ */
+ @Nullable
+ public String getDeviceBuildTimestamp(@NonNull IDevice device) {
+ try {
+ if (USE_BUILD_ID_TEMP_FILE) {
+ String remoteIdFile = getDeviceIdFolder(mPackageName);
+ File localIdFile = createTempFile("build-id", "txt");
+ try {
+ device.pullFile(remoteIdFile, localIdFile.getPath());
+ return Files.toString(localIdFile, Charsets.UTF_8).trim();
+ } catch (SyncException ignore) {
+ return null;
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ localIdFile.delete();
+ }
+ } else {
+ String remoteIdFile = Paths.getDataDirectory(mPackageName) + "/"
+ + Paths.BUILD_ID_TXT;
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ device.executeShellCommand("run-as " + mPackageName + " cat " + remoteIdFile,
+ receiver);
+ String output = receiver.getOutput().trim();
+ String id;
+ if (output.contains(":")) { // cat: command not found, cat: permission denied etc
+ if (output.startsWith(remoteIdFile)) {
+ // /data/data/my.pkg.path/files/instant-run/build-id.txt: No such file or directory
+ return null;
+ }
+ // on a user device, we cannot pull from a path where the segments aren't readable (I think this is a ddmlib limitation)
+ // So we first copy to /data/local/tmp and pull from there..
+ String remoteTmpFile = "/data/local/tmp/build-id.txt";
+ device.executeShellCommand("cp " + remoteIdFile + " " + remoteTmpFile,
+ receiver);
+ output = receiver.getOutput().trim();
+ if (!output.isEmpty()) {
+ mLogger.info(output);
+ }
+ File localIdFile = createTempFile("build-id", "txt");
+ device.pullFile(remoteTmpFile, localIdFile.getPath());
+ id = Files.toString(localIdFile, Charsets.UTF_8).trim();
+ //noinspection ResultOfMethodCallIgnored
+ localIdFile.delete();
+ } else {
+ id = output;
+ }
+ return id;
+ }
+ } catch (IOException ignore) {
+ } catch (AdbCommandRejectedException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (SyncException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (TimeoutException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ } catch (ShellCommandUnresponsiveException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ }
+
+ return null;
+ }
+
+ private void writeToken(@NonNull DataOutputStream output) throws IOException {
+ output.writeLong(mToken);
+ }
+
+ /**
+ * Transfer the file as a slice/sharded dex file. This means
+ * that its remote path should be the slice name, in the dex
+ * directory.
+ */
+ public static final int TRANSFER_MODE_SLICE = 1;
+
+ /**
+ * Transfer the file as a hotswap overlay file. This means
+ * that its remote path should be a temporary file.
+ */
+ public static final int TRANSFER_MODE_HOTSWAP = 3;
+
+ /**
+ * Transfer the file as a resource file. This means that it
+ * should be written to the inactive resource file section
+ * in the app data directory.
+ */
+ public static final int TRANSFER_MODE_RESOURCES = 4;
+
+ /**
+ * File to be transferred to the device. For use with
+ * {@link #pushFiles(List, IDevice, String)}
+ */
+ public static class FileTransfer {
+ public final int mode;
+ public final File source;
+ public final String name;
+
+ public FileTransfer(int mode, @NonNull File source, @NonNull String name) {
+ this.mode = mode;
+ this.source = source;
+ this.name = name;
+ }
+
+ @NonNull
+ public static FileTransfer createSliceDex(@NonNull File source, @NonNull String name) {
+ return new FileTransfer(TRANSFER_MODE_SLICE, source, name);
+ }
+
+ @NonNull
+ public static FileTransfer createResourceFile(@NonNull File source) {
+ return new FileTransfer(TRANSFER_MODE_RESOURCES, source, Paths.RESOURCE_FILE_NAME);
+ }
+
+ @NonNull
+ public static FileTransfer createHotswapPatch(@NonNull File source) {
+ return new FileTransfer(TRANSFER_MODE_HOTSWAP, source, Paths.RELOAD_DEX_FILE_NAME);
+ }
+
+ @NonNull
+ public ApplicationPatch getPatch() throws IOException {
+ byte[] bytes = Files.toByteArray(source);
+ String path;
+ // These path names are specially handled on the client side
+ // (e.g. it interprets "classes.dex" as meaning create a new
+ // unique class file in the class folder
+ switch (mode) {
+ case TRANSFER_MODE_SLICE:
+ path = Paths.DEX_SLICE_PREFIX + name;
+ break;
+ case TRANSFER_MODE_HOTSWAP:
+ case TRANSFER_MODE_RESOURCES:
+ path = name;
+ break;
+ default:
+ throw new IllegalArgumentException(Integer.toString(mode));
+ }
+
+ return new ApplicationPatch(path, bytes);
+ }
+
+ @Override
+ public String toString() {
+ return source + " as " + name + " with mode " + mode;
+ }
+ }
+
+ /**
+ * Stops the given app (via adb).
+ *
+ * @param device the device
+ * @param sendChangeBroadcast whether to also send a package change broadcast
+ * @throws InstantRunPushFailedException if there's a problem
+ */
+ public void stopApp(@NonNull IDevice device, boolean sendChangeBroadcast) throws InstantRunPushFailedException {
+ try {
+ runCommand(device, "am force-stop " + mPackageName);
+ } catch (Throwable t) {
+ throw new InstantRunPushFailedException("Exception while stopping app: " + t.toString());
+ }
+ if (sendChangeBroadcast) {
+ try {
+ // We think this might necessary to force the system not hold on to any data from the previous
+ // version of the process, such as the scenario described in
+ // https://code.google.com/p/android/issues/detail?id=200895#c9
+ runCommand(device, "am broadcast -a android.intent.action.PACKAGE_CHANGED -p " + mPackageName);
+ }
+ catch (Throwable ignore) {
+ // We can live with this one not succeeding; may require root etc
+ }
+ }
+ }
+
+ /**
+ * Install dex and resource files on the given device (using adb to push files
+ * to the device when the app isn't running so we can't send it patches via
+ * the socket connection.)
+ *
+ * @param files the files to push to the device, and the paths to push them as.
+ * @param device the device to push to
+ */
+ public void pushFiles(
+ @NonNull List<FileTransfer> files,
+ @NonNull IDevice device,
+ @NonNull final String buildId) throws InstantRunPushFailedException {
+ try {
+ Set<String> createdDirs = Sets.newHashSet();
+
+ for (FileTransfer file : files) {
+ String folder;
+ String name;
+ switch (file.mode) {
+ case TRANSFER_MODE_SLICE:
+ folder = Paths.getDexFileDirectory(mPackageName);
+ name = Paths.DEX_SLICE_PREFIX + file.name;
+ break;
+ case TRANSFER_MODE_RESOURCES:
+ folder = Paths.getInboxDirectory(mPackageName);
+ name = Paths.RESOURCE_FILE_NAME;
+ break;
+ case TRANSFER_MODE_HOTSWAP:
+ throw new IllegalArgumentException("Hotswap patches can only be applied "
+ + "when the app is running");
+ default:
+ throw new IllegalArgumentException(Integer.toString(file.mode));
+ }
+
+ // Copy the restart .dex file over to the device in the dex folder with the new name
+ String remote = copyToDeviceScratchFile(device, mPackageName, file.source);
+
+ // Make sure directory exists
+ if (!createdDirs.contains(folder)) {
+ createdDirs.add(folder);
+ String cmd = "run-as " + mPackageName + " mkdir -p " + folder;
+ if (!runAsCommand(device, cmd)) {
+ mLogger.warning("pushFiles: %s", "Error creating folder with: " + cmd);
+ throw new InstantRunPushFailedException("Error creating folder with: " + cmd);
+ }
+ }
+
+ String cmd = "run-as " + mPackageName + " cp " + remote + " " + folder + "/" + name;
+ if (!runAsCommand(device, cmd)) {
+ mLogger.warning("pushFiles: %s", "Error copying file with: " + cmd);
+ throw new InstantRunPushFailedException("Error copying file with: " + cmd);
+ }
+ }
+
+ transferLocalIdToDeviceId(device, buildId);
+ } catch (IOException ioe) {
+ mLogger.warning("Couldn't write build id file: %s", ioe);
+ throw new InstantRunPushFailedException("IOException while pushing files: " + ioe.toString());
+ } catch (AdbCommandRejectedException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ throw new InstantRunPushFailedException("Exception while pushing files: " + e.toString());
+ } catch (TimeoutException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ throw new InstantRunPushFailedException("Exception while pushing files: " + e.toString());
+ } catch (ShellCommandUnresponsiveException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ throw new InstantRunPushFailedException("Exception while pushing files: " + e.toString());
+ } catch (SyncException e) {
+ mLogger.warning("%s", Throwables.getStackTraceAsString(e));
+ throw new InstantRunPushFailedException("Exception while pushing files: " + e.toString());
+ }
+ }
+
+ private boolean runAsCommand(@NonNull IDevice device, @NonNull String cmd)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException, InstantRunPushFailedException {
+ String output = getCommandOutput(device, cmd).trim();
+ if (!output.isEmpty()) {
+ mLogger.warning("Unexpected shell output for " + cmd + ": " + output);
+
+ if (output.startsWith("run-as: Package '") && output.endsWith("' is unknown")) {
+ throw new InstantRunPushFailedException(BROKEN_RUN_AS);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private boolean runCommand(@NonNull IDevice device, @NonNull String cmd)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ String output = getCommandOutput(device, cmd).trim();
+ if (!output.isEmpty()) {
+ mLogger.warning("Unexpected shell output for " + cmd + ": " + output);
+ return false;
+ }
+ return true;
+ }
+
+ @NonNull
+ private static String getCommandOutput(@NonNull IDevice device, @NonNull String cmd)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ CollectingOutputReceiver receiver;
+ receiver = new CollectingOutputReceiver();
+ device.executeShellCommand(cmd, receiver);
+ return receiver.getOutput();
+ }
+
+ private void logFilesPushed(@NonNull List<FileTransfer> files, boolean needRestart) {
+ StringBuilder sb = new StringBuilder("Pushing files: ");
+ if (needRestart) {
+ sb.append("(needs restart) ");
+ }
+
+ sb.append('[');
+ String separator = "";
+ for (FileTransfer file : files) {
+ sb.append(separator);
+ sb.append(file.source.getName());
+ sb.append(" as ");
+ sb.append(file.name);
+
+ separator = ", ";
+ }
+ sb.append(']');
+
+ mLogger.info(sb.toString());
+ }
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunPushFailedException.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunPushFailedException.java
new file mode 100644
index 0000000..8dc7b2f
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/InstantRunPushFailedException.java
@@ -0,0 +1,24 @@
+/*
+ * 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.tools.fd.client;
+
+import com.android.annotations.NonNull;
+
+public class InstantRunPushFailedException extends Exception {
+ public InstantRunPushFailedException(@NonNull String s) {
+ super(s);
+ }
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/UpdateMode.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/UpdateMode.java
new file mode 100644
index 0000000..b8e15f2
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/UpdateMode.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.client;
+
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_COLD_SWAP;
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_HOT_SWAP;
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_NONE;
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_WARM_SWAP;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Mode which describes what kind of patch update we'll apply in the app: a hot swap (apply code and
+ * just continue running), a warm swap (restart activity), or a cold swap: a full app restart.
+ */
+public enum UpdateMode {
+ /**
+ * No updates
+ */
+ NO_CHANGES(UPDATE_MODE_NONE),
+ /**
+ * Patch changes directly, keep app running without any restarting
+ */
+ HOT_SWAP(UPDATE_MODE_HOT_SWAP),
+ /**
+ * Patch changes, restart activity to reflect changes
+ */
+ WARM_SWAP(UPDATE_MODE_WARM_SWAP),
+ /**
+ * Store change in app directory, restart app
+ */
+ COLD_SWAP(UPDATE_MODE_COLD_SWAP);
+
+ private final int myId;
+
+ UpdateMode(int id) {
+ myId = id;
+ }
+
+ /**
+ * The ID for this mode, which is the actual value sent across the wire to the app
+ */
+ public int getId() {
+ return myId;
+ }
+
+ @NonNull
+ public UpdateMode combine(@NonNull UpdateMode with) {
+ return values()[Math.max(ordinal(), with.ordinal())];
+ }
+}
diff --git a/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/UserFeedback.java b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/UserFeedback.java
new file mode 100644
index 0000000..668c4aa
--- /dev/null
+++ b/instant-run/instant-run-client/src/main/java/com/android/tools/fd/client/UserFeedback.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.client;
+
+/**
+ * Implemented by Android Studio to post balloon messages back to the user.
+ */
+public interface UserFeedback {
+
+ void error(String message);
+
+ void warning(String message);
+
+ void info(String message);
+
+ void noChanges();
+
+ void notifyEnd(UpdateMode updateMode);
+}
diff --git a/instant-run/instant-run-client/src/test/java/com/android/tools/fd/client/InstantRunBuildInfoTest.java b/instant-run/instant-run-client/src/test/java/com/android/tools/fd/client/InstantRunBuildInfoTest.java
new file mode 100644
index 0000000..e8b68ff
--- /dev/null
+++ b/instant-run/instant-run-client/src/test/java/com/android/tools/fd/client/InstantRunBuildInfoTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.tools.fd.client;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.io.Resources;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class InstantRunBuildInfoTest {
+
+ @Test
+ public void testBuildId() throws IOException {
+ InstantRunBuildInfo info = getBuildInfo("instantrun", "build-info1.xml");
+ assertEquals("1451508349243", info.getTimeStamp());
+ }
+
+ @Test
+ public void testApiLevel() throws IOException {
+ InstantRunBuildInfo info = getBuildInfo("instantrun", "build-info1.xml");
+ assertEquals(23, info.getFeatureLevel());
+ }
+
+ @Test
+ public void testHasNoChanges() throws IOException {
+ InstantRunBuildInfo info = getBuildInfo("instantrun", "build-info-no-artifacts.xml");
+ assertFalse("If verifier is not empty, then there are changes that weren't captured in this build.", info.hasNoChanges());
+
+ info = getBuildInfo("instantrun", "build-info-res.xml");
+ assertFalse("If there is an artifact, then there are changes", info.hasNoChanges());
+
+ info = getBuildInfo("instantrun", "no-changes.xml");
+ assertTrue(info.hasNoChanges());
+ }
+
+ @Test
+ public void testFormat() throws IOException {
+ InstantRunBuildInfo info = getBuildInfo("instantrun", "build-info1.xml");
+ assertEquals(1, info.getFormat());
+ }
+
+ @Test
+ public void testSplitApks() throws IOException {
+ InstantRunBuildInfo info = getBuildInfo("instantrun", "build-info1.xml");
+
+ List<InstantRunArtifact> artifacts = info.getArtifacts();
+ assertEquals(11, artifacts.size());
+ assertTrue(info.hasMainApk());
+ }
+
+ @Test
+ public void testSplitApks2() throws IOException {
+ // Ensure that when we get a main APK (but not all the splits) as part of
+ // a build info, we pull in all the slices from the first build too
+ InstantRunBuildInfo info = getBuildInfo("instantrun", "build-info2.xml");
+
+ List<InstantRunArtifact> artifacts = info.getArtifacts();
+ assertEquals(12, artifacts.size());
+ assertTrue(info.hasMainApk());
+ assertTrue(info.hasOneOf(InstantRunArtifactType.SPLIT));
+ }
+
+ @NonNull
+ private static InstantRunBuildInfo getBuildInfo(@NonNull String... buildInfoPath)
+ throws IOException {
+ String path = Joiner.on('/').join(buildInfoPath);
+ String xml = Resources.toString(Resources.getResource(path), Charsets.UTF_8);
+ InstantRunBuildInfo buildInfo = InstantRunBuildInfo.get(xml);
+ assertNotNull("Unable to create build info from resource @" + path, buildInfo);
+ return buildInfo;
+ }
+
+}
diff --git a/instant-run/instant-run-client/src/test/resources/instantrun/build-info-no-artifacts.xml b/instant-run/instant-run-client/src/test/resources/instantrun/build-info-no-artifacts.xml
new file mode 100644
index 0000000..5c0d3dc
--- /dev/null
+++ b/instant-run/instant-run-client/src/test/resources/instantrun/build-info-no-artifacts.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<instant-run
+ api-level="23"
+ format="1"
+ verifier="MANIFEST_RES_CHANGED"
+ timestamp="1451508349243" >
+
+ <task
+ name="javac"
+ duration="1131" />
+ <task
+ name="instant-run-dex"
+ duration="0" />
+ <task
+ name="instant-run-transform"
+ duration="237" />
+ <task
+ name="verifier"
+ duration="40" />
+
+</instant-run>
diff --git a/instant-run/instant-run-client/src/test/resources/instantrun/build-info-res.xml b/instant-run/instant-run-client/src/test/resources/instantrun/build-info-res.xml
new file mode 100644
index 0000000..1b056c1
--- /dev/null
+++ b/instant-run/instant-run-client/src/test/resources/instantrun/build-info-res.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<instant-run
+ api-level="23"
+ format="1"
+ verifier=""
+ timestamp="1451508349243" >
+
+ <task
+ name="javac"
+ duration="1131" />
+ <task
+ name="instant-run-dex"
+ duration="0" />
+ <task
+ name="instant-run-transform"
+ duration="237" />
+ <task
+ name="verifier"
+ duration="40" />
+
+ <!-- Usually this is a resource only change and a resources artifact if verifier is empty -->
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/outputs/apk/app-debug.apk"
+ type="SPLIT_MAIN" />
+
+</instant-run>
diff --git a/instant-run/instant-run-client/src/test/resources/instantrun/build-info1.xml b/instant-run/instant-run-client/src/test/resources/instantrun/build-info1.xml
new file mode 100644
index 0000000..7fce472
--- /dev/null
+++ b/instant-run/instant-run-client/src/test/resources/instantrun/build-info1.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<instant-run
+ api-level="23"
+ format="1"
+ timestamp="1451508349243" >
+
+ <task
+ name="javac"
+ duration="1131" />
+ <task
+ name="instant-run-dex"
+ duration="0" />
+ <task
+ name="instant-run-transform"
+ duration="237" />
+ <task
+ name="verifier"
+ duration="40" />
+
+ <artifact
+ location="app/build/intermediates/split-apk/debug/dependencies_55eb4309070c4cbdf50e953d07e2911c4f7feb8f.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_0_9a2a1b807c92374c42e2a81bcf23721131781f4e.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_1_c32add4ce83882cfbe166853d3819f12b70e918d.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_2_1345e0fbaa52f02ad6d719e6bd5b98ea28e96910.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_3_7b0df69ad95658f8a72e78a05dd0feffc2ee8a8e.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_4_f0bfdf80f202c422926f470b5f5ada66c417b01e.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_5_dc740c112e2ae895c795f1dcf2eb4840b9b6d463.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_6_ec725566a695cd8fd9f3daffac78f8c975d7ca4f.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_7_7f8749168bd38933f8bc6808f6c8babe427aa930.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_8_f49dadc9ed0f83a8df55e44f27c05cb2457b4efb.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/outputs/apk/app-debug.apk"
+ type="MAIN" />
+
+ <build timestamp="1451508349243" >
+ <artifact
+ location="app/build/intermediates/split-apk/debug/dependencies_55eb4309070c4cbdf50e953d07e2911c4f7feb8f.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_0_9a2a1b807c92374c42e2a81bcf23721131781f4e.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_1_c32add4ce83882cfbe166853d3819f12b70e918d.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_2_1345e0fbaa52f02ad6d719e6bd5b98ea28e96910.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_3_7b0df69ad95658f8a72e78a05dd0feffc2ee8a8e.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_4_f0bfdf80f202c422926f470b5f5ada66c417b01e.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_5_dc740c112e2ae895c795f1dcf2eb4840b9b6d463.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_6_ec725566a695cd8fd9f3daffac78f8c975d7ca4f.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_7_7f8749168bd38933f8bc6808f6c8babe427aa930.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/intermediates/split-apk/debug/slice_8_f49dadc9ed0f83a8df55e44f27c05cb2457b4efb.apk"
+ type="SPLIT" />
+ <artifact
+ location="app/build/outputs/apk/app-debug.apk"
+ type="MAIN" />
+ </build>
+
+</instant-run>
diff --git a/instant-run/instant-run-client/src/test/resources/instantrun/build-info2.xml b/instant-run/instant-run-client/src/test/resources/instantrun/build-info2.xml
new file mode 100644
index 0000000..76475de
--- /dev/null
+++ b/instant-run/instant-run-client/src/test/resources/instantrun/build-info2.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<instant-run
+ api-level="23"
+ format="2"
+ timestamp="1452207930094" >
+
+ <task
+ name="javac"
+ duration="0" />
+ <task
+ name="instant-run-dex"
+ duration="0" />
+ <task
+ name="instant-run-transform"
+ duration="0" />
+ <task
+ name="verifier"
+ duration="0" />
+
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/outputs/apk/app-debug.apk"
+ type="SPLIT_MAIN" />
+
+ <build timestamp="1452205343311" >
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/dependencies_f446d1f1d3dddfda734cabfe37f06b7416d2b888.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_0_321039353eef5d5fa0f2de2b1e20084af0485dba.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_1_30ac6f7da7d6bf620af8d4897beda69f1c758fce.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_2_3261e9818005a89d16adc1e7df0e62a5d9122858.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_3_64b9357e89a1391ac0bc832954f521c8b6083654.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_4_868650df59e53543ec3d0cb108bb42f2c7def546.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_5_aa8226d5139b1e9b33623e1e2c85ed500232340b.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_6_cd68e51e4390e67b5f08c34d74d34731957bf152.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_7_2c9aaee7cb7bb5966f50dde6561a581c364376ea.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_8_539e2e55ad2242451559130b47859158acd5e11d.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/intermediates/split-apk/debug/slice_9_440e6801164d4a7ae73d9fb82461086782e9e9ce.apk"
+ type="SPLIT" />
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/outputs/apk/app-debug.apk"
+ type="SPLIT_MAIN" />
+ </build>
+ <build timestamp="1452207930094" >
+ <artifact
+ location="/Users/tnorbye/AndroidStudioProjects/ColdswapTest/app/build/outputs/apk/app-debug.apk"
+ type="SPLIT_MAIN" />
+ </build>
+
+</instant-run>
diff --git a/instant-run/instant-run-client/src/test/resources/instantrun/no-changes.xml b/instant-run/instant-run-client/src/test/resources/instantrun/no-changes.xml
new file mode 100644
index 0000000..2428cc8
--- /dev/null
+++ b/instant-run/instant-run-client/src/test/resources/instantrun/no-changes.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<instant-run
+ api-level="23"
+ format="1"
+ verifier=""
+ timestamp="1451508349243" >
+
+ <task
+ name="javac"
+ duration="1131" />
+ <task
+ name="instant-run-dex"
+ duration="0" />
+ <task
+ name="instant-run-transform"
+ duration="237" />
+ <task
+ name="verifier"
+ duration="40" />
+
+</instant-run>
diff --git a/instant-run/instant-run-common/build.gradle b/instant-run/instant-run-common/build.gradle
new file mode 100644
index 0000000..e6e4389
--- /dev/null
+++ b/instant-run/instant-run-common/build.gradle
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'java'
+apply plugin: 'jacoco'
+
+sourceCompatibility = JavaVersion.VERSION_1_6
+targetCompatibility = JavaVersion.VERSION_1_6
+
+dependencies {
+ compile project(':base:instant-run:instant-run-annotations')
+}
\ No newline at end of file
diff --git a/instant-run/instant-run-common/instant-run-common.iml b/instant-run/instant-run-common/instant-run-common.iml
new file mode 100644
index 0000000..a04dfa9
--- /dev/null
+++ b/instant-run/instant-run-common/instant-run-common.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="android-annotations" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/instant-run/instant-run-common/src/main/java/com/android/tools/fd/common/Log.java b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/common/Log.java
new file mode 100644
index 0000000..d40492f
--- /dev/null
+++ b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/common/Log.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.common;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.logging.Level;
+
+public class Log {
+
+ @Nullable
+ public static Logging logging = null;
+
+ public interface Logging {
+ void log(@NonNull Level level, @NonNull String string);
+
+ boolean isLoggable(@NonNull Level level);
+
+ void log(@NonNull Level level, @NonNull String string, @Nullable Throwable throwable);
+ }
+
+}
\ No newline at end of file
diff --git a/instant-run/instant-run-common/src/main/java/com/android/tools/fd/common/ProtocolConstants.java b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/common/ProtocolConstants.java
new file mode 100644
index 0000000..52c571d
--- /dev/null
+++ b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/common/ProtocolConstants.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.common;
+
+/**
+ * Constants shared between Android Studio and the Instant Run runtime.
+ */
+public interface ProtocolConstants {
+
+ /**
+ * Magic (random) number used to identify the protocol
+ */
+ long PROTOCOL_IDENTIFIER = 0x35107124L;
+
+ /**
+ * Version of the protocol
+ */
+ int PROTOCOL_VERSION = 4;
+
+ /**
+ * Message: sending patches
+ */
+ int MESSAGE_PATCHES = 1;
+
+ /**
+ * Message: ping, send ack back
+ */
+ int MESSAGE_PING = 2;
+
+ /**
+ * Message: look up a very quick checksum of the given path; this
+ * may not pick up on edits in the middle of the file but should be a
+ * quick way to determine if a path exists and some basic information
+ * about it.
+ */
+ int MESSAGE_PATH_EXISTS = 3;
+
+ /**
+ * Message: query whether the app has a given file and if so return
+ * its checksum. (This is used to determine whether the app can receive
+ * a small delta on top of a (typically resource ) file instead of resending the whole
+ * file over again.)
+ */
+ int MESSAGE_PATH_CHECKSUM = 4;
+
+ /**
+ * Message: restart activities
+ */
+ int MESSAGE_RESTART_ACTIVITY = 5;
+
+ /**
+ * Message: show toast
+ */
+ int MESSAGE_SHOW_TOAST = 6;
+
+ /**
+ * Done transmitting
+ */
+ int MESSAGE_EOF = 7;
+
+ /**
+ * No updates
+ */
+ int UPDATE_MODE_NONE = 0;
+
+ /**
+ * Patch changes directly, keep app running without any restarting
+ */
+ int UPDATE_MODE_HOT_SWAP = 1;
+
+ /**
+ * Patch changes, restart activity to reflect changes
+ */
+ int UPDATE_MODE_WARM_SWAP = 2;
+
+ /**
+ * Store change in app directory, restart app
+ */
+ int UPDATE_MODE_COLD_SWAP = 3;
+}
diff --git a/instant-run/instant-run-common/src/main/java/com/android/tools/fd/runtime/ApplicationPatch.java b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/runtime/ApplicationPatch.java
new file mode 100644
index 0000000..38ceca6
--- /dev/null
+++ b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/runtime/ApplicationPatch.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.fd.runtime;
+
+import static com.android.tools.fd.common.Log.logging;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+
+// This class is used in both the Android runtime and in the IDE.
+// Technically we only need the write protocol on the IDE side and the
+// read protocol on the Android app size, but keeping it all together and
+// in sync right now.
+public class ApplicationPatch {
+ @NonNull public final String path;
+ @NonNull public final byte[] data;
+
+ public ApplicationPatch(@NonNull String path, @NonNull byte[] data) {
+ this.path = path;
+ this.data = data;
+ }
+
+ @Override
+ public String toString() {
+ return "ApplicationPatch{" +
+ "path='" + path + '\'' +
+ ", data.length='" + data.length + '\'' +
+ '}';
+ }
+
+ // Only needed on the Android side
+ @Nullable
+ public static List<ApplicationPatch> read(@NonNull DataInputStream input) throws IOException {
+ int changeCount = input.readInt();
+
+ if (logging != null && logging.isLoggable(Level.FINE)) {
+ logging.log(Level.FINE, "Receiving " + changeCount + " changes");
+ }
+
+ List<ApplicationPatch> changes = new ArrayList<ApplicationPatch>(changeCount);
+ for (int i = 0; i < changeCount; i++) {
+ String path = input.readUTF();
+ int size = input.readInt();
+ byte[] bytes = new byte[size];
+ input.readFully(bytes);
+ changes.add(new ApplicationPatch(path, bytes));
+ }
+
+ return changes;
+ }
+
+ @NonNull
+ public String getPath() {
+ return path;
+ }
+
+ @NonNull
+ public byte[] getBytes() {
+ return data;
+ }
+}
diff --git a/instant-run/instant-run-common/src/main/java/com/android/tools/fd/runtime/Paths.java b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/runtime/Paths.java
new file mode 100644
index 0000000..9a5384f
--- /dev/null
+++ b/instant-run/instant-run-common/src/main/java/com/android/tools/fd/runtime/Paths.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Shared path-related logic between Android Studio and the Instant Run server.
+ */
+public final class Paths {
+ /** Name of the dex folder in the app's data directory */
+ public static final String DEX_DIRECTORY_NAME = "dex";
+
+ /** Temp directory on the device */
+ public static final String DEVICE_TEMP_DIR = "/data/local/tmp";
+
+ /** The name of the build timestamp file on the device in the data folder */
+ public static final String BUILD_ID_TXT = "build-id.txt";
+
+ /** Name of file to write resource data into, if not extracting resources */
+ public static final String RESOURCE_FILE_NAME = "resources.ap_";
+
+ /** Name for reload dex files */
+ public static final String RELOAD_DEX_FILE_NAME = "classes.dex.3";
+
+ /** Prefix for dex shard files */
+ public static final String DEX_SLICE_PREFIX = "slice-";
+
+ @NonNull
+ public static String getMainApkDataDirectory(@NonNull String applicationId) {
+ return "/data/data/" + applicationId;
+ }
+
+ @NonNull
+ public static String getDataDirectory(@NonNull String applicationId) {
+ return "/data/data/" + applicationId + "/files/instant-run";
+ }
+
+ @NonNull
+ public static String getDexFileDirectory(@NonNull String applicationId) {
+ return getDataDirectory(applicationId) + "/" + DEX_DIRECTORY_NAME;
+ }
+
+ @NonNull
+ public static String getInboxDirectory(@NonNull String applicationId) {
+ return getDataDirectory(applicationId) + "/inbox";
+ }
+
+ @NonNull
+ public static String getDeviceIdFolder(@NonNull String pkg) {
+ return DEVICE_TEMP_DIR + "/" + pkg + "-" + BUILD_ID_TXT;
+ }
+
+}
diff --git a/instant-run/instant-run-runtime/build.gradle b/instant-run/instant-run-runtime/build.gradle
new file mode 100644
index 0000000..ad31db0
--- /dev/null
+++ b/instant-run/instant-run-runtime/build.gradle
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'java'
+apply plugin: 'jacoco'
+
+sourceCompatibility = JavaVersion.VERSION_1_6
+targetCompatibility = JavaVersion.VERSION_1_6
+
+dependencies {
+ compile project(':base:instant-run:instant-run-common')
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'com.google.truth:truth:0.28'
+ testCompile 'org.mockito:mockito-all:1.9.5'
+ testCompile project(':base:instant-run:instant-run-client')
+}
\ No newline at end of file
diff --git a/instant-run/instant-run-runtime/instant-run-runtime.iml b/instant-run/instant-run-runtime/instant-run-runtime.iml
new file mode 100644
index 0000000..85c6b6c
--- /dev/null
+++ b/instant-run/instant-run-runtime/instant-run-runtime.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="instant-run-common" />
+ <orderEntry type="module" module-name="android-annotations" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="library" scope="TEST" name="truth" level="project" />
+ <orderEntry type="module" module-name="instant-run-client" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="guava-tools" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/AbstractPatchesLoaderImpl.java b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/AbstractPatchesLoaderImpl.java
new file mode 100644
index 0000000..e51108a
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/AbstractPatchesLoaderImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+
+import static com.android.tools.fd.common.Log.logging;
+
+import java.lang.reflect.Field;
+import java.util.logging.Level;
+
+public abstract class AbstractPatchesLoaderImpl implements PatchesLoader {
+
+ public abstract String[] getPatchedClasses();
+
+ @Override
+ public boolean load() {
+ try {
+ for (String className : getPatchedClasses()) {
+ ClassLoader cl = getClass().getClassLoader();
+ Class<?> aClass = cl.loadClass(className + "$override");
+ Object o = aClass.newInstance();
+ Class<?> originalClass = cl.loadClass(className);
+ Field changeField = originalClass.getDeclaredField("$change");
+ // force the field accessibility as the class might not be "visible"
+ // from this package.
+ changeField.setAccessible(true);
+
+ // If there was a previous change set, mark it as obsolete:
+ Object previous = changeField.get(null);
+ if (previous != null) {
+ Field isObsolete = previous.getClass().getDeclaredField("$obsolete");
+ if (isObsolete != null) {
+ isObsolete.set(null, true);
+ }
+ }
+ changeField.set(null, o);
+
+ if (logging != null && logging.isLoggable(Level.FINE)) {
+ logging.log(Level.FINE, String.format("patched %s", className));
+ }
+ }
+ } catch (Exception e) {
+ if (logging != null) {
+ logging.log(Level.SEVERE, String.format("Exception while patching %s", "foo.bar"), e);
+ }
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/AndroidInstantRuntime.java b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/AndroidInstantRuntime.java
new file mode 100644
index 0000000..a03fbca
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/AndroidInstantRuntime.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+import static com.android.tools.fd.common.Log.logging;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.fd.common.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.NoSuchElementException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Generic Instant Run services. must not depend on Android APIs.
+ *
+ * TODO: transform this static methods into interface/implementation.
+ */
+ at SuppressWarnings("unused")
+public class AndroidInstantRuntime {
+
+ protected interface Logging {
+ void log(@NonNull Level level, @NonNull String string);
+
+ boolean isLoggable(@NonNull Level level);
+
+ void log(@NonNull Level level, @NonNull String string, @Nullable Throwable throwable);
+ }
+
+ public static void setLogger(final Logger logger) {
+
+ logging = new Log.Logging() {
+ @Override
+ public void log(@NonNull Level level, @NonNull String string) {
+ logger.log(level, string);
+ }
+
+ @Override
+ public boolean isLoggable(@NonNull Level level) {
+ return logger.isLoggable(level);
+ }
+
+ @Override
+ public void log(@NonNull Level level, @NonNull String string,
+ @Nullable Throwable throwable) {
+ logger.log(level, string, throwable);
+ }
+ };
+ }
+
+ @Nullable
+ public static Object getStaticPrivateField(Class targetClass, String fieldName) {
+ return getPrivateField(null /* targetObject */, targetClass, fieldName);
+ }
+
+ public static void setStaticPrivateField(
+ @NonNull Object value, @NonNull Class targetClass, @NonNull String fieldName) {
+ setPrivateField(null /* targetObject */, value, targetClass, fieldName);
+ }
+
+ public static void setPrivateField(
+ @Nullable Object targetObject,
+ @Nullable Object value,
+ @NonNull Class targetClass,
+ @NonNull String fieldName) {
+
+ try {
+ Field declaredField = getField(targetClass, fieldName);
+ declaredField.set(targetObject, value);
+ } catch (IllegalAccessException e) {
+ if (logging != null) {
+ logging.log(Level.SEVERE,
+ String.format("Exception during setPrivateField %s", fieldName), e);
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public static Object getPrivateField(
+ @Nullable Object targetObject,
+ @NonNull Class targetClass,
+ @NonNull String fieldName) {
+
+ try {
+ Field declaredField = getField(targetClass, fieldName);
+ return declaredField.get(targetObject);
+ } catch (IllegalAccessException e) {
+ if (logging != null) {
+ logging.log(Level.SEVERE,
+ String.format("Exception during%1$s getField %2$s",
+ targetObject == null ? " static" : "",
+ fieldName), e);
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @NonNull
+ private static Field getField(Class target, String name) {
+ Field declareField = getFieldByName(target, name);
+ if (declareField == null) {
+ throw new RuntimeException(new NoSuchElementException(name));
+ }
+ declareField.setAccessible(true);
+ return declareField;
+ }
+
+ public static Object invokeProtectedMethod(Object receiver,
+ Object[] params,
+ Class[] parameterTypes,
+ String methodName) throws Throwable {
+
+ if (logging!=null && logging.isLoggable(Level.FINE)) {
+ logging.log(Level.FINE, String.format("protectedMethod:%s on %s", methodName, receiver));
+ }
+ try {
+ Method toDispatchTo = getMethodByName(receiver.getClass(), methodName, parameterTypes);
+ if (toDispatchTo == null) {
+ throw new RuntimeException(new NoSuchMethodException(methodName));
+ }
+ toDispatchTo.setAccessible(true);
+ return toDispatchTo.invoke(receiver, params);
+ } catch (InvocationTargetException e) {
+ // The called method threw an exception, rethrow
+ throw e.getCause();
+ } catch (IllegalAccessException e) {
+ logging.log(Level.SEVERE, String.format("Exception while invoking %s", methodName), e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Object invokeProtectedStaticMethod(
+ Object[] params,
+ Class[] parameterTypes,
+ String methodName,
+ Class receiverClass) throws Throwable {
+
+ if (logging!=null && logging.isLoggable(Level.FINE)) {
+ logging.log(Level.FINE,
+ String.format("protectedStaticMethod:%s on %s", methodName, receiverClass.getName()));
+ }
+ try {
+ Method toDispatchTo = getMethodByName(receiverClass, methodName, parameterTypes);
+ if (toDispatchTo == null) {
+ throw new RuntimeException(new NoSuchMethodException(
+ methodName + " in class " + receiverClass.getName()));
+ }
+ toDispatchTo.setAccessible(true);
+ return toDispatchTo.invoke(null /* target */, params);
+ } catch (InvocationTargetException e) {
+ // The called method threw an exception, rethrow
+ throw e.getCause();
+ } catch (IllegalAccessException e) {
+ logging.log(Level.SEVERE, String.format("Exception while invoking %s", methodName), e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static <T> T newForClass(Object[] params, Class[] paramTypes, Class<T> targetClass)
+ throws Throwable {
+ Constructor declaredConstructor;
+ try {
+ declaredConstructor = targetClass.getDeclaredConstructor(paramTypes);
+ } catch (NoSuchMethodException e) {
+ logging.log(Level.SEVERE, "Exception while resolving constructor", e);
+ throw new RuntimeException(e);
+ }
+ declaredConstructor.setAccessible(true);
+ try {
+ return targetClass.cast(declaredConstructor.newInstance(params));
+ } catch (InvocationTargetException e) {
+ // The called method threw an exception, rethrow
+ throw e.getCause();
+ } catch (InstantiationException e) {
+ logging.log(Level.SEVERE, String.format("Exception while instantiating %s", targetClass), e);
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ logging.log(Level.SEVERE, String.format("Exception while instantiating %s", targetClass), e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Field getFieldByName(Class<?> aClass, String name) {
+
+ if (logging!= null && logging.isLoggable(Level.FINE)) {
+ logging.log(Level.FINE, String.format("getFieldByName:%s in %s", name, aClass.getName()));
+ }
+
+ Class<?> currentClass = aClass;
+ while (currentClass != null) {
+ try {
+ return currentClass.getDeclaredField(name);
+ } catch (NoSuchFieldException e) {
+ // ignored.
+ }
+ currentClass = currentClass.getSuperclass();
+ }
+ return null;
+ }
+
+ private static Method getMethodByName(Class<?> aClass, String name, Class[] paramTypes) {
+
+ if (aClass == null) {
+ return null;
+ }
+
+ Class<?> currentClass = aClass;
+ while (currentClass != null) {
+ try {
+ return currentClass.getDeclaredMethod(name, paramTypes);
+ } catch (NoSuchMethodException e) {
+ // ignored.
+ }
+ currentClass = currentClass.getSuperclass();
+ if (currentClass!= null && logging!=null && logging.isLoggable(Level.FINE)) {
+ logging.log(Level.FINE, String.format(
+ "getMethodByName:Looking in %s now", currentClass.getName()));
+ }
+
+ }
+ return null;
+ }
+
+ public static void trace(String s) {
+ if (logging != null) {
+ logging.log(Level.FINE, s);
+ }
+ }
+
+ public static void trace(String s1, String s2) {
+ if (logging != null) {
+ logging.log(Level.FINE, String.format("%s %s", s1, s2));
+ }
+ }
+
+ public static void trace(String s1, String s2, String s3) {
+ if (logging != null) {
+ logging.log(Level.FINE, String.format("%s %s %s", s1, s2, s3));
+ }
+ }
+
+ public static void trace(String s1, String s2, String s3, String s4) {
+ if (logging != null) {
+ logging.log(Level.FINE, String.format("%s %s %s %s", s1, s2, s3, s4));
+ }
+ }
+}
diff --git a/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/BasicType.java b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/BasicType.java
new file mode 100644
index 0000000..131f10b
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/BasicType.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * Boxing/unboxing services for primitive types.
+ */
+public enum BasicType {
+
+ I(Integer.TYPE),
+ J(Long.TYPE),
+ C(Character.TYPE),
+ Z(Boolean.TYPE),
+ F(Float.TYPE),
+ D(Double.TYPE),
+ V(Void.TYPE);
+
+ @NonNull
+ private final Class<?> primitiveJavaType;
+
+ BasicType(@NonNull Class<?> primitiveType) {
+ this.primitiveJavaType = primitiveType;
+ }
+
+ @NonNull
+ public Class getJavaType() {
+ return primitiveJavaType;
+ }
+
+ @Nullable
+ public static BasicType parse(String name) {
+ for (BasicType basicType : BasicType.values()) {
+ if (basicType.getJavaType().getName().equals(name)) {
+ return basicType;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/IncrementalChange.java b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/IncrementalChange.java
new file mode 100644
index 0000000..409667e
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/IncrementalChange.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+public interface IncrementalChange {
+ Object access$dispatch(String id, Object... args);
+}
\ No newline at end of file
diff --git a/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/InstantReloadException.java b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/InstantReloadException.java
new file mode 100644
index 0000000..faf0626
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/InstantReloadException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+/**
+ * Exception thrown by the instant reload runtime when something preventing the byte code enhanced
+ * code to function properly. This is a generic error that something went wrong in the instant
+ * reload runtime and it should be considered a implementation bug.
+ *
+ * For instance, this can be generated when trying to invoke a super method that the instant runtime
+ * cannot find in the generated $super method.
+ */
+public class InstantReloadException extends Exception {
+
+ public InstantReloadException(String s) {
+ super(s);
+ }
+}
diff --git a/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/PatchesLoader.java b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/PatchesLoader.java
new file mode 100644
index 0000000..7203631
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/PatchesLoader.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+public interface PatchesLoader {
+
+ boolean load();
+}
diff --git a/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/PatchesLoaderDumper.java b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/PatchesLoaderDumper.java
new file mode 100644
index 0000000..00a6f26
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/main/java/com/android/tools/fd/runtime/PatchesLoaderDumper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+public class PatchesLoaderDumper {
+
+ public static void main(String[] args) {
+ try {
+ Class<?> aClass = Class.forName("com.android.tools.fd.runtime.AppPatchesLoaderImpl");
+ PatchesLoader patchesLoader = (PatchesLoader) aClass.newInstance();
+ patchesLoader.load();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/instant-run/instant-run-runtime/src/test/java/com/android/tools/fd/runtime/ApplicationPatchTest.java b/instant-run/instant-run-runtime/src/test/java/com/android/tools/fd/runtime/ApplicationPatchTest.java
new file mode 100644
index 0000000..ef98cf2
--- /dev/null
+++ b/instant-run/instant-run-runtime/src/test/java/com/android/tools/fd/runtime/ApplicationPatchTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.fd.client.ApplicationPatchUtil;
+import com.android.tools.fd.client.UpdateMode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+ at RunWith(Parameterized.class)
+public class ApplicationPatchTest {
+
+ @Parameterized.Parameters(name="{0}")
+ public static Collection<Object[]> getPatches() {
+ ApplicationPatch emptyData = new ApplicationPatch("path", new byte[] {});
+ ApplicationPatch patch1 = new ApplicationPatch("path1", new byte[] {54, 23, -128, 4, 127, -5});
+ ApplicationPatch patch2 = new ApplicationPatch("patch2", new byte[] {122, -2, 73});
+
+ return Arrays.asList(new Object[][] {
+ {ImmutableList.of()},
+ {ImmutableList.of(emptyData)},
+ {ImmutableList.of(patch1)},
+ {ImmutableList.of(patch1, patch2)},
+ });
+ }
+
+ private final List<ApplicationPatch> mPatches;
+
+ public ApplicationPatchTest(List<ApplicationPatch> patches) {
+ mPatches = patches;
+ }
+
+ @Rule
+ public Expect mExpect = Expect.create();
+
+ @Test
+ public void checkApplicationPatchReadWrite() throws IOException {
+ PipedInputStream input = new PipedInputStream();
+ PipedOutputStream outputStream = new PipedOutputStream(input);
+ try {
+ DataOutputStream output = new DataOutputStream(outputStream);
+ ApplicationPatchUtil.write(output, mPatches, UpdateMode.HOT_SWAP);
+
+ List<ApplicationPatch> patches = ApplicationPatch.read(new DataInputStream(input));
+ assertNotNull(patches);
+ assertEquals("Should not lose or gain patches", mPatches.size(), patches.size());
+
+ for (int i = 0; i < mPatches.size(); i++) {
+ ApplicationPatch expected = mPatches.get(i);
+ ApplicationPatch actual = patches.get(i);
+ mExpect.that(actual.getBytes()).isEqualTo(expected.getBytes());
+ mExpect.that(actual.getPath()).isEqualTo(expected.getPath());
+ }
+ } finally {
+ outputStream.close();
+ }
+ }
+}
diff --git a/instant-run/instant-run-server/.gitignore b/instant-run/instant-run-server/.gitignore
new file mode 100644
index 0000000..42bbe8e
--- /dev/null
+++ b/instant-run/instant-run-server/.gitignore
@@ -0,0 +1,3 @@
+gen/
+res/
+local.properties
diff --git a/instant-run/instant-run-server/AndroidManifest.xml b/instant-run/instant-run-server/AndroidManifest.xml
new file mode 100644
index 0000000..d316f7b
--- /dev/null
+++ b/instant-run/instant-run-server/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Solely for editing support -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.instant_run_server"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <uses-sdk android:minSdkVersion="15" />
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+ <activity android:name="ACTIVITY_ENTRY_NAME"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/instant-run/instant-run-server/build.gradle b/instant-run/instant-run-server/build.gradle
new file mode 100644
index 0000000..89e2722
--- /dev/null
+++ b/instant-run/instant-run-server/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'java'
+
+sourceCompatibility = JavaVersion.VERSION_1_6
+targetCompatibility = JavaVersion.VERSION_1_6
+
+File androidJar = new File(System.env.ANDROID_HOME + '/platforms/android-23/android.jar');
+if (!androidJar.exists()) {
+ throw new RuntimeException("android-23 android.jar not found at " + androidJar.absolutePath)
+}
+
+repositories {
+ maven { url System.env.ANDROID_HOME + '/extras/android/m2repository/' }
+}
+
+configurations {
+ provided
+}
+
+dependencies {
+ compile project(':base:instant-run:instant-run-runtime')
+ provided files(androidJar)
+}
+
+sourceSets {
+ main { compileClasspath += configurations.provided }
+}
+
+jar {
+ from {
+ configurations.compile.collect { it.isFile() ? zipTree(it) : it }
+ }
+ exclude('com/android/annotations/**', 'NOTICE')
+}
\ No newline at end of file
diff --git a/instant-run/instant-run-server/instant-run-server.iml b/instant-run/instant-run-server/instant-run-server.iml
new file mode 100644
index 0000000..88f0b99
--- /dev/null
+++ b/instant-run/instant-run-server/instant-run-server.iml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="android" name="Android">
+ <configuration>
+ <option name="LIBRARY_PROJECT" value="true" />
+ <option name="UPDATE_PROPERTY_FILES" value="true" />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/build/gen" isTestSource="false" generated="true" />
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
+ </content>
+ <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="instant-run-runtime" />
+ <orderEntry type="module" module-name="android-annotations" />
+ <orderEntry type="module" module-name="instant-run-common" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/instant-run/instant-run-server/project.properties b/instant-run/instant-run-server/project.properties
new file mode 100644
index 0000000..def83b4
--- /dev/null
+++ b/instant-run/instant-run-server/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+android.library=true
+# Project target.
+target=android-23
diff --git a/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/AppInfo.java b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/AppInfo.java
new file mode 100644
index 0000000..6ee9270
--- /dev/null
+++ b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/AppInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.fd.runtime;
+
+public class AppInfo {
+ // Keep the structure of this class in sync with
+ // GenerateInstantRunAppInfoTask#writeAppInfoClass
+
+ private AppInfo() {
+ }
+ /**
+ * The application id of this app (e.g. the package name). Used to pick a unique
+ * directory for the app's reloaded resources. (We can't look for it in the manifest,
+ * since we need this information very early in the app life cycle, and we don't want
+ * to call into the framework and cause more parts of it to be initialized before
+ * we've monkey-patched the application class and resource loaders.)
+ * <p>
+ * (Not final: Will be replaced by byte-code manipulation at build time)
+ */
+ @SuppressWarnings({"CanBeFinal", "StaticVariableNamingConvention"})
+ public static String applicationId = null;
+
+ /**
+ * The fully qualified name of the real application to run. This is the user's app,
+ * which has been hidden from the manifest during build time. Can be null if the
+ * app does not have a custom application (in which case a default android.app.Application
+ * is used.)
+ * <p>
+ */
+ @SuppressWarnings({"CanBeFinal", "StaticVariableNamingConvention"})
+ public static String applicationClass = null;
+
+ /**
+ * A token assigned to this app at build time. This is used such that the running
+ * app socket server can be reasonably sure that it's responding to requests from
+ * the IDE.
+ */
+ @SuppressWarnings("StaticVariableNamingConvention")
+ public static long token = 0L;
+
+ /** Set when building on API 23 (or API 22 if multiapk is enabled) */
+ public static boolean usingApkSplits = true;
+}
diff --git a/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/BootstrapApplication.java b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/BootstrapApplication.java
new file mode 100644
index 0000000..758c260
--- /dev/null
+++ b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/BootstrapApplication.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import android.app.ActivityManager;
+import android.os.Process;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.Application;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.logging.Level;
+
+// This is based on the reflection parts of
+// com.google.devtools.build.android.incrementaldeployment.StubApplication,
+// plus changes to compile on JDK 6.
+//
+// (The code to handle resource loading etc is different; see FileManager.)
+//
+// The original is
+// https://cs.corp.google.com/codesearch/f/piper///depot/google3/third_party/bazel/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java?cl=93287264
+// Public (May 11 revision, ca96e11)
+// https://github.com/google/bazel/blob/master/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java
+
+/**
+ * A stub application that patches the class loader, then replaces itself with the real application
+ * by applying a liberal amount of reflection on Android internals.
+ * <p/>
+ * <p>This is, of course, terribly error-prone. Most of this code was tested with API versions
+ * 8, 10, 14, 15, 16, 17, 18, 19 and 21 on the Android emulator, a Nexus 5 running Lollipop LRX22C
+ * and a Samsung GT-I5800 running Froyo XWJPE. The exception is {@code monkeyPatchAssetManagers},
+ * which only works on Kitkat and Lollipop.
+ * <p/>
+ * <p>Note that due to a bug in Dalvik, this only works on Kitkat if ART is the Java runtime.
+ * <p/>
+ * <p>Unfortunately, if this does not work, we don't have a fallback mechanism: as soon as we
+ * build the APK with this class as the Application, we are committed to going through with it.
+ * <p/>
+ * <p>This class should use as few other classes as possible before the class loader is patched
+ * because any class loaded before it cannot be incrementally deployed.
+ */
+public class BootstrapApplication extends Application {
+ public static final String LOG_TAG = "InstantRun";
+
+ static {
+ com.android.tools.fd.common.Log.logging =
+ new com.android.tools.fd.common.Log.Logging() {
+ @Override
+ public void log(@NonNull Level level, @NonNull String string) {
+ log(level, string, null /* throwable */);
+ }
+
+ @Override
+ public boolean isLoggable(@NonNull Level level) {
+ if (level == Level.SEVERE) {
+ return Log.isLoggable(LOG_TAG, Log.ERROR);
+ } else if (level == Level.FINE) {
+ return Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ } else return Log.isLoggable(LOG_TAG, Log.INFO);
+ }
+
+ @Override
+ public void log(@NonNull Level level, @NonNull String string,
+ @Nullable Throwable throwable) {
+ if (level == Level.SEVERE) {
+ if (throwable == null) {
+ Log.e(LOG_TAG, string);
+ } else {
+ Log.e(LOG_TAG, string, throwable);
+ }
+ } else if (level == Level.FINE) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ if (throwable == null) {
+ Log.v(LOG_TAG, string);
+ } else {
+ Log.v(LOG_TAG, string, throwable);
+ }
+ }
+ } else if (Log.isLoggable(LOG_TAG, Log.INFO)) {
+ if (throwable == null) {
+ Log.i(LOG_TAG, string);
+ } else {
+ Log.i(LOG_TAG, string, throwable);
+ }
+ }
+ }
+ };
+ }
+
+ private String externalResourcePath;
+ private Application realApplication;
+
+ public BootstrapApplication() {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, String.format(
+ "BootstrapApplication created. Android package is %s, real application class is %s.",
+ AppInfo.applicationId, AppInfo.applicationClass));
+ }
+ }
+
+ private void createResources(long apkModified) {
+ // Look for changes stashed in the inbox folder while the server was not running
+ FileManager.checkInbox();
+
+ File file = FileManager.getExternalResourceFile();
+ externalResourcePath = file != null ? file.getPath() : null;
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Resource override is " + externalResourcePath);
+ }
+
+ if (file != null) {
+ try {
+ long resourceModified = file.lastModified();
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Resource patch last modified: " + resourceModified);
+ Log.v(LOG_TAG, "APK last modified: " + apkModified + " " +
+ (apkModified > resourceModified ? ">" : "<") + " resource patch");
+ }
+
+ if (apkModified == 0L || resourceModified <= apkModified) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Ignoring resource file, older than APK");
+ }
+ externalResourcePath = null;
+ }
+ } catch (Throwable t) {
+ Log.e(LOG_TAG, "Failed to check patch timestamps", t);
+ }
+ }
+ }
+
+ private static void setupClassLoaders(Context context, String codeCacheDir, long apkModified) {
+ List<String> dexList = FileManager.getDexList(context, apkModified);
+
+ // Make sure class loader finds these
+ @SuppressWarnings("unused") Class<Server> server = Server.class;
+ @SuppressWarnings("unused") Class<MonkeyPatcher> patcher = MonkeyPatcher.class;
+
+ if (!dexList.isEmpty()) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Bootstrapping class loader with dex list " + join('\n', dexList));
+ }
+
+ ClassLoader classLoader = BootstrapApplication.class.getClassLoader();
+ String nativeLibraryPath;
+ try {
+ nativeLibraryPath = (String) classLoader.getClass().getMethod("getLdLibraryPath")
+ .invoke(classLoader);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Native library path: " + nativeLibraryPath);
+ }
+ } catch (Throwable t) {
+ Log.e(LOG_TAG, "Failed to determine native library path " + t.getMessage());
+ nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();
+ }
+ IncrementalClassLoader.inject(
+ classLoader,
+ nativeLibraryPath,
+ codeCacheDir,
+ dexList);
+ }
+ }
+
+ public static String join(char on, List<String> list) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (String item : list) {
+ stringBuilder.append(item).append(on);
+ }
+ stringBuilder.deleteCharAt(stringBuilder.length() - 1);
+ return stringBuilder.toString();
+ }
+
+ private void createRealApplication() {
+ if (AppInfo.applicationClass != null) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "About to create real application of class name = " +
+ AppInfo.applicationClass);
+ }
+
+ try {
+ @SuppressWarnings("unchecked")
+ Class<? extends Application> realClass =
+ (Class<? extends Application>) Class.forName(AppInfo.applicationClass);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Created delegate app class successfully : " + realClass +
+ " with class loader " + realClass.getClassLoader());
+ }
+ Constructor<? extends Application> constructor = realClass.getConstructor();
+ realApplication = constructor.newInstance();
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Created real app instance successfully :" + realApplication);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ } else {
+ realApplication = new Application();
+ }
+ }
+
+ @Override
+ protected void attachBaseContext(Context context) {
+ // As of Marshmallow, we use APK splits and don't need to rely on
+ // reflection to inject classes and resources for coldswap
+ //noinspection PointlessBooleanExpression
+ if (!AppInfo.usingApkSplits) {
+ String apkFile = context.getApplicationInfo().sourceDir;
+ long apkModified = apkFile != null ? new File(apkFile).lastModified() : 0L;
+ createResources(apkModified);
+ setupClassLoaders(context, context.getCacheDir().getPath(), apkModified);
+ }
+
+ createRealApplication();
+
+ // This is called from ActivityThread#handleBindApplication() -> LoadedApk#makeApplication().
+ // Application#mApplication is changed right after this call, so we cannot do the monkey
+ // patching here. So just forward this method to the real Application instance.
+ super.attachBaseContext(context);
+
+ if (realApplication != null) {
+ try {
+ Method attachBaseContext =
+ ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
+ attachBaseContext.setAccessible(true);
+ attachBaseContext.invoke(realApplication, context);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ // As of Marshmallow, we use APK splits and don't need to rely on
+ // reflection to inject classes and resources for coldswap
+ //noinspection PointlessBooleanExpression
+ if (!AppInfo.usingApkSplits) {
+ MonkeyPatcher.monkeyPatchApplication(
+ BootstrapApplication.this, BootstrapApplication.this,
+ realApplication, externalResourcePath);
+ MonkeyPatcher.monkeyPatchExistingResources(BootstrapApplication.this,
+ externalResourcePath, null);
+ } else {
+ // We still need to set the application instance in the LoadedApk etc
+ // such that getApplication() returns the new application
+ MonkeyPatcher.monkeyPatchApplication(
+ BootstrapApplication.this, BootstrapApplication.this,
+ realApplication, null);
+ }
+ super.onCreate();
+
+ // Start server, unless we're in a multiprocess scenario and this isn't the
+ // primary process
+ if (AppInfo.applicationId != null) {
+ try {
+ boolean foundPackage = false;
+ int pid = Process.myPid();
+ ActivityManager manager = (ActivityManager) getSystemService(
+ Context.ACTIVITY_SERVICE);
+ List<RunningAppProcessInfo> processes = manager.getRunningAppProcesses();
+
+ boolean startServer;
+ if (processes != null && processes.size() > 1) {
+ // Multiple processes: look at each, and if the process name matches
+ // the package name (for the current pid), it's the main process.
+ startServer = false;
+ for (RunningAppProcessInfo processInfo : processes) {
+ if (AppInfo.applicationId.equals(processInfo.processName)) {
+ foundPackage = true;
+ if (processInfo.pid == pid) {
+ startServer = true;
+ break;
+ }
+ }
+ }
+ if (!startServer && !foundPackage) {
+ // Safety check: If for some reason we didn't even find the main package,
+ // start the server anyway. This safeguards against apps doing strange
+ // things with the process name.
+ startServer = true;
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Multiprocess but didn't find process with package: "
+ + "starting server anyway");
+ }
+ }
+ } else {
+ // If there is only one process, start the server.
+ startServer = true;
+ }
+
+ if (startServer) {
+ Server.create(AppInfo.applicationId, BootstrapApplication.this);
+ }
+ } catch (Throwable t) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Failed during multi process check", t);
+ }
+ Server.create(AppInfo.applicationId, BootstrapApplication.this);
+ }
+ }
+
+ if (realApplication != null) {
+ realApplication.onCreate();
+ }
+ }
+}
diff --git a/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/FileManager.java b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/FileManager.java
new file mode 100644
index 0000000..ba027ad
--- /dev/null
+++ b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/FileManager.java
@@ -0,0 +1,859 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+import static com.android.tools.fd.runtime.AppInfo.applicationId;
+import static com.android.tools.fd.runtime.BootstrapApplication.LOG_TAG;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Class which handles locating existing code and resource files on the device,
+ * as well as writing new versions of these.
+ */
+public class FileManager {
+ /**
+ * According to Dianne, using an extracted directory tree of resources rather than
+ * in an archive was implemented before 1.0 and never used or tested... so we should
+ * tread carefully here.
+ */
+ private static final boolean USE_EXTRACTED_RESOURCES = false;
+
+ /** Name of file to write resource data into, if not extracting resources */
+ private static final String RESOURCE_FILE_NAME = Paths.RESOURCE_FILE_NAME;
+
+ /** Name of folder to write extracted resource data into, if extracting resources */
+ private static final String RESOURCE_FOLDER_NAME = "resources";
+
+ /** Name of the file which points to either the left or the right data directory */
+ private static final String FILE_NAME_ACTIVE = "active";
+
+ /** Name of the left directory */
+ private static final String FOLDER_NAME_LEFT = "left";
+
+ /** Name of the right directory */
+ private static final String FOLDER_NAME_RIGHT = "right";
+
+ /** Prefix for reload.dex files */
+ private static final String RELOAD_DEX_PREFIX = "reload";
+
+ /** Suffix for classes.dex files */
+ public static final String CLASSES_DEX_SUFFIX = ".dex";
+
+ /** Whether we've purged temp dex files in this session */
+ private static boolean sHavePurgedTempDexFolder;
+
+ /**
+ * The folder where resources and code are located. Within this folder we have two
+ * alternatives: "left" and "right". One is in the foreground (in use), one is in the
+ * background (to write to). These are named {@link #FOLDER_NAME_LEFT} and
+ * {@link #FOLDER_NAME_RIGHT} and the current one is pointed to by
+ * {@link #FILE_NAME_ACTIVE}. */
+ private static File getDataFolder() {
+ // TODO: Call Context#getFilesDir(), but since we don't have a context yet figure
+ // out what to do
+ // Keep in sync with ResourceDeltaManager in the IDE (which needs this path
+ // in order to run an adb wipe command when reinstalling a freshly built app
+ // to avoid using stale data)
+ return new File(Paths.getDataDirectory(applicationId));
+ }
+
+ @NonNull
+ private static File getResourceFile(File base) {
+ //noinspection ConstantConditions
+ return new File(base, USE_EXTRACTED_RESOURCES ? RESOURCE_FOLDER_NAME : RESOURCE_FILE_NAME);
+ }
+
+ /**
+ * Returns the folder used for .dex files used during the next app start
+ */
+ @Nullable
+ private static File getDexFileFolder(File base, boolean createIfNecessary) {
+ File file = new File(base, Paths.DEX_DIRECTORY_NAME);
+ if (createIfNecessary) {
+ if (!file.isDirectory()) {
+ boolean created = file.mkdirs();
+ if (!created) {
+ Log.e(LOG_TAG, "Failed to create directory " + file);
+ return null;
+ }
+ }
+ }
+
+ return file;
+ }
+
+ /**
+ * Returns the folder used for temporary .dex files (e.g. classes loaded on the fly
+ * and only needing to exist during the current app process
+ */
+ @NonNull
+ private static File getTempDexFileFolder(File base) {
+ return new File(base, "dex-temp");
+ }
+
+ public static File getNativeLibraryFolder() {
+ return new File(Paths.getMainApkDataDirectory(applicationId), "lib");
+ }
+
+ /**
+ * Returns the "foreground" folder: the location to read code and resources from.
+ */
+ @NonNull
+ public static File getReadFolder() {
+ String name = leftIsActive() ? FOLDER_NAME_LEFT : FOLDER_NAME_RIGHT;
+ return new File(getDataFolder(), name);
+ }
+
+ /**
+ * Swaps the read/write folders such that the next time somebody asks for the
+ * read or write folders, they'll get the opposite.
+ */
+ public static void swapFolders() {
+ setLeftActive(!leftIsActive());
+ }
+
+ /**
+ * Returns the "background" folder: the location to write code and resources to.
+ */
+ @NonNull
+ public static File getWriteFolder(boolean wipe) {
+ String name = leftIsActive() ? FOLDER_NAME_RIGHT : FOLDER_NAME_LEFT;
+ File folder = new File(getDataFolder(), name);
+ if (wipe && folder.exists()) {
+ delete(folder);
+ boolean mkdirs = folder.mkdirs();
+ if (!mkdirs) {
+ Log.e(LOG_TAG, "Failed to create folder " + folder);
+ }
+ }
+ return folder;
+ }
+
+ private static void delete(@NonNull File file) {
+ if (file.isDirectory()) {
+ // Delete the contents
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ delete(child);
+ }
+ }
+ }
+
+ //noinspection ResultOfMethodCallIgnored
+ boolean deleted = file.delete();
+ if (!deleted) {
+ Log.e(LOG_TAG, "Failed to delete file " + file);
+ }
+ }
+
+ private static boolean leftIsActive() {
+ File folder = getDataFolder();
+ File pointer = new File(folder, FILE_NAME_ACTIVE);
+ if (!pointer.exists()) {
+ return true;
+ }
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(pointer));
+ try {
+ String line = reader.readLine();
+ return FOLDER_NAME_LEFT.equals(line);
+ } finally {
+ reader.close();
+ }
+ } catch (IOException ignore) {
+ return true;
+ }
+ }
+
+ private static void setLeftActive(boolean active) {
+ File folder = getDataFolder();
+ File pointer = new File(folder, FILE_NAME_ACTIVE);
+ if (pointer.exists()) {
+ boolean deleted = pointer.delete();
+ if (!deleted) {
+ Log.e(LOG_TAG, "Failed to delete file " + pointer);
+ }
+ } else if (!folder.exists()) {
+ boolean create = folder.mkdirs();
+ if (!create) {
+ Log.e(LOG_TAG, "Failed to create directory " + folder);
+ }
+ return;
+ }
+
+ try {
+ Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(pointer),
+ "UTF-8"));
+ try {
+ writer.write(active ? FOLDER_NAME_LEFT : FOLDER_NAME_RIGHT);
+ } finally {
+ writer.close();
+ }
+ } catch (IOException ignore) {
+ }
+ }
+
+ /** Looks in the inbox for new changes sent while the app wasn't running and apply them */
+ public static void checkInbox() {
+ File inbox = new File(Paths.getInboxDirectory(applicationId));
+ if (inbox.isDirectory()) {
+ File resources = new File(inbox, RESOURCE_FILE_NAME);
+ if (resources.isFile()) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Processing resource file from inbox (" + resources + ")");
+ }
+ byte[] bytes = readRawBytes(resources);
+ if (bytes != null) {
+ FileManager.startUpdate();
+ FileManager.writeAaptResources(RESOURCE_FILE_NAME, bytes);
+ FileManager.finishUpdate(true);
+ boolean deleted = resources.delete();
+ if (!deleted) {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Couldn't remove inbox resource file: " + resources);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Returns the current/active resource file, if it exists */
+ @Nullable
+ public static File getExternalResourceFile() {
+ File file = getResourceFile(getReadFolder());
+ if (!file.exists()) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Cannot find external resources, not patching them in");
+ }
+ return null;
+ }
+
+ return file;
+ }
+
+ /** Returns the list of available .dex files to be loaded, possibly empty
+ * @param apkModified main apk installation time to purge old dex files from previous
+ * installation.
+ */
+ @NonNull
+ public static List<String> getDexList(Context context, long apkModified) {
+ File dataFolder = getDataFolder();
+
+ long newestHotswapPatch = FileManager.getMostRecentTempDexTime(dataFolder);
+
+ // We don't need "double buffering" for dex files - we never rewrite files, so we
+ // can accumulate in the same dir
+ File dexFolder = getDexFileFolder(dataFolder, false);
+
+ // Extract slices.
+ //
+ // Imagine this scenario -- you run your app (so the device dex folder is filled).
+ // Then you do a clean build etc -- so Gradle doesn't know there is existing state
+ // on the device. If we *only* extract slices when there are no slices there already,
+ // then we'd end up here just running the old slices already on the device.
+ // On the other hand, we can't just always extract slices, since then each time
+ // you run we'll overwrite coldswap and freezeswap slices.
+ //
+ // So what this code does is pass the APK timestamp to the extractor, and in the
+ // extractor, if the timestamp is positive, we check before writing each slice that
+ // it doesn't already exist and is newer than the APK.
+ boolean extractedSlices = false;
+ File[] dexFiles;
+ if (dexFolder == null || !dexFolder.isDirectory()) {
+ // It's the first run of a freshly installed app, and we need to extract the
+ // slices from within the APK into the dex folder
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "No local dex slice folder: First run since installation.");
+ }
+ dexFolder = getDexFileFolder(dataFolder, true);
+ if (dexFolder == null) {
+ // Failed to create dex folder.
+ Log.wtf(LOG_TAG, "Couldn't create dex code folder");
+ return Collections.emptyList(); // unreachable
+ }
+ dexFiles = extractSlices(dexFolder, null, -1); // -1: unconditionally extract all
+ extractedSlices = dexFiles.length > 0;
+ } else {
+ dexFiles = dexFolder.listFiles();
+ }
+ if (dexFiles == null || dexFiles.length == 0) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Cannot find dex classes, not patching them in");
+ }
+ return Collections.emptyList();
+ }
+
+ // See if any of the slices are older than the APK. This will only be the case
+ // if it's not the first run, and the APK has been reinstalled while there are some
+ // potentially stale dex files.
+ //
+ // Note that we're *also* computing the timestamp of the *newest* coldswap slice.
+ // We'll use that below to post a toast if the app seems to be missing hotswap patches.
+ long newestColdswapPatch = apkModified;
+ if (!extractedSlices && dexFiles.length > 0) {
+ long oldestColdSwapPatch = apkModified;
+ for (File dex : dexFiles) {
+ long dexModified = dex.lastModified();
+ oldestColdSwapPatch = Math.min(dexModified, oldestColdSwapPatch);
+ newestColdswapPatch = Math.max(dexModified, newestColdswapPatch);
+ }
+ if (oldestColdSwapPatch < apkModified) {
+ // At least one slice is older than the APK: re-extract those that
+ // need it
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "One or more slices were older than APK: extracting newer slices");
+ }
+ dexFiles = extractSlices(dexFolder, dexFiles, apkModified);
+ }
+ } else if (newestHotswapPatch > 0L) {
+ // If the code is newer than the hotswap patches, delete them such that we don't
+ // have to keep iterating through them each successive startup
+ purgeTempDexFiles(dataFolder);
+ }
+
+ if (newestHotswapPatch > newestColdswapPatch) {
+ String message = "Your app does not have the latest code changes because it "
+ + "was restarted manually. Please run from IDE instead.";
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, message);
+ }
+
+ // We now want to show a toast to the user showing that the app is older.
+ // However, it's too early to show it here:
+ Restarter.showToastWhenPossible(context, message);
+ }
+
+ List<String> list = new ArrayList<String>(dexFiles.length);
+ for (File dex : dexFiles) {
+ if (dex.getName().endsWith(CLASSES_DEX_SUFFIX)) {
+ list.add(dex.getPath());
+ }
+ }
+
+ // Dex files should be sorted in reverse order such that the class loader finds
+ // most recent updates first
+ Collections.sort(list, Collections.reverseOrder());
+
+ return list;
+ }
+
+ /**
+ * Extracts the slices found in the APK root directory (instant-run.zip) into the dex folder,
+ * and skipping any files that already exist and are newer than apkModified (unless apkModified
+ * <= 0). It <b>also</b> deletes any <b>unrecognized</b> slices. This is necessary
+ * since there are scenarios (such as b.android.com/204341) where we end up with slice files
+ * in the dex folder that should <b>not</b> be loaded.
+ */
+ private static File[] extractSlices(@NonNull File dexFolder, @Nullable File[] dexFolderFiles,
+ long apkModified) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Extracting slices into " + dexFolder);
+ }
+ InputStream stream = BootstrapApplication.class.getResourceAsStream("/instant-run.zip");
+ if (stream == null) {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Could not find slices in APK; aborting.");
+ }
+ return new File[0];
+ }
+ List<File> slices = new ArrayList<File>(30);
+ Set<String> sliceNames = new HashSet<String>(30);
+ try {
+ ZipInputStream zipInputStream = new ZipInputStream(stream);
+ try {
+ byte[] buffer = new byte[2000];
+
+ for (ZipEntry entry = zipInputStream.getNextEntry();
+ entry != null;
+ entry = zipInputStream.getNextEntry()) {
+ String name = entry.getName();
+ // Don't extract META-INF data
+ if (name.startsWith("META-INF")) {
+ continue;
+ }
+ if (!entry.isDirectory()
+ && name.indexOf('/') == -1 // only files in root directory
+ && name.endsWith(CLASSES_DEX_SUFFIX)) {
+ // Using / as separators in both .zip files and on Android, no need to convert
+ // to File.separator
+
+ // Map slice name to the scheme already used by the code to push slices
+ // via the embedded server as well as the code to push via adb:
+ // slice-<slicedir>
+ String sliceName = Paths.DEX_SLICE_PREFIX + name;
+ sliceNames.add(sliceName);
+ File dest = new File(dexFolder, sliceName);
+ slices.add(dest);
+
+ if (apkModified > 0) {
+ long sliceModified = dest.lastModified();
+ if (sliceModified > apkModified) {
+ // Ignore this slice: disk copy more recent than APK copy
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Ignoring slice " + name
+ + ": newer on disk than in APK");
+ }
+ continue;
+ }
+ }
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Extracting slice " + name + " into " + dest);
+ }
+ File parent = dest.getParentFile();
+ if (parent != null && !parent.exists()) {
+ boolean created = parent.mkdirs();
+ if (!created) {
+ Log.wtf(LOG_TAG, "Failed to create directory " + dest);
+ return new File[0];
+ }
+ }
+
+ OutputStream src = new BufferedOutputStream(new FileOutputStream(dest));
+ try {
+ int bytesRead;
+ while ((bytesRead = zipInputStream.read(buffer)) != -1) {
+ src.write(buffer, 0, bytesRead);
+ }
+ } finally {
+ src.close();
+ }
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "File written at " + System.currentTimeMillis());
+ Log.v(LOG_TAG, "File last modified reported : " + dest.lastModified());
+ }
+ }
+ }
+
+ // Remove old slice names
+ if (dexFolderFiles != null) {
+ for (File file : dexFolderFiles) {
+ if (!sliceNames.contains(file.getName())) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Removing old slice " + file);
+ }
+ boolean deleted = file.delete();
+ if (!deleted) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Could not delete " + file);
+ }
+ }
+ }
+ }
+ }
+
+ return slices.toArray(new File[slices.size()]);
+ } catch (IOException ioe) {
+ Log.wtf(LOG_TAG, "Failed to extract slices into directory " + dexFolder, ioe);
+ return new File[0];
+ } finally {
+ try {
+ zipInputStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ /** Produces the next available dex file name */
+ @Nullable
+ public static File getTempDexFile() {
+ // Find the file name of the next dex file to write
+ File dataFolder = getDataFolder();
+ File dexFolder = getTempDexFileFolder(dataFolder);
+ if (!dexFolder.exists()) {
+ boolean created = dexFolder.mkdirs();
+ if (!created) {
+ Log.e(LOG_TAG, "Failed to create directory " + dexFolder);
+ return null;
+ }
+ } else {
+ // The *first* time we write a reload dex file in the new process, we'll
+ // delete previously stashes reload dex files. (We keep them around
+ // such that we can (repeatedly) warn an app on startup if its hotswap patches
+ // are more recent than the app itself, such that developers aren't confused
+ // when the app is not reflecting the most recent changes
+ if (!sHavePurgedTempDexFolder) {
+ purgeTempDexFiles(dataFolder);
+ }
+ }
+
+ File[] files = dexFolder.listFiles();
+ int max = -1;
+
+ // Pick highest available number + 1 - we want these to be sortable
+ if (files != null) {
+ for (File file : files) {
+ String name = file.getName();
+ if (name.startsWith(RELOAD_DEX_PREFIX) && name.endsWith(CLASSES_DEX_SUFFIX)) {
+ String middle = name.substring(RELOAD_DEX_PREFIX.length(),
+ name.length() - CLASSES_DEX_SUFFIX.length());
+ try {
+ int version = Integer.decode(middle);
+ if (version > max) {
+ max = version;
+ }
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ }
+ }
+
+ String fileName = String.format("%s0x%04x%s", RELOAD_DEX_PREFIX, max + 1,
+ CLASSES_DEX_SUFFIX);
+ File file = new File(dexFolder, fileName);
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Writing new dex file: " + file);
+ }
+
+ return file;
+ }
+
+ public static boolean writeRawBytes(@NonNull File destination, @NonNull byte[] bytes) {
+ try {
+ BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(destination));
+ try {
+ output.write(bytes);
+ output.flush();
+ return true;
+ } finally {
+ output.close();
+ }
+ } catch (IOException ioe) {
+ Log.wtf(LOG_TAG, "Failed to write file, clean project and rebuild " + destination, ioe);
+ throw new RuntimeException(
+ String.format(
+ "InstantRun could not write file %1$s, clean project and rebuild ",
+ destination));
+ }
+ }
+
+ public static boolean extractZip(@NonNull File destination, @NonNull byte[] zipBytes) {
+ InputStream inputStream = new ByteArrayInputStream(zipBytes);
+ return extractZip(destination, inputStream);
+ }
+
+ public static boolean extractZip(@NonNull File destDir, @NonNull InputStream inputStream) {
+ ZipInputStream zipInputStream = new ZipInputStream(inputStream);
+ try {
+ byte[] buffer = new byte[2000];
+
+ for (ZipEntry entry = zipInputStream.getNextEntry();
+ entry != null;
+ entry = zipInputStream.getNextEntry()) {
+ String name = entry.getName();
+ // Don't extract META-INF data
+ if (name.startsWith("META-INF")) {
+ continue;
+ }
+ if (!entry.isDirectory()) {
+ // Using / as separators in both .zip files and on Android, no need to convert
+ // to File.separator
+ File dest = new File(destDir, name);
+ File parent = dest.getParentFile();
+ if (parent != null && !parent.exists()) {
+ boolean created = parent.mkdirs();
+ if (!created) {
+ Log.e(LOG_TAG, "Failed to create directory " + dest);
+ return false;
+ }
+ }
+
+ OutputStream src = new BufferedOutputStream(new FileOutputStream(dest));
+ try {
+ int bytesRead;
+ while ((bytesRead = zipInputStream.read(buffer)) != -1) {
+ src.write(buffer, 0, bytesRead);
+ }
+ } finally {
+ src.close();
+ }
+ }
+ }
+
+ return true;
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Failed to extract zip contents into directory " + destDir, ioe);
+ return false;
+ } finally {
+ try {
+ zipInputStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ public static void startUpdate() {
+ // Wipe the back-buffer, if already present
+ getWriteFolder(true);
+ }
+
+ public static void finishUpdate(boolean wroteResources) {
+ if (wroteResources) {
+ swapFolders();
+ }
+ }
+
+ @Nullable
+ public static File writeDexShard(@NonNull byte[] bytes, @NonNull String name) {
+ File dexFolder = getDexFileFolder(getDataFolder(), true);
+ if (dexFolder == null) {
+ return null;
+ }
+ File file = new File(dexFolder, name);
+ writeRawBytes(file, bytes);
+ return file;
+ }
+
+ public static void writeAaptResources(@NonNull String relativePath, @NonNull byte[] bytes) {
+ // TODO: Take relativePath into account for the actual destination file
+ File resourceFile = getResourceFile(getWriteFolder(false));
+ File file = resourceFile;
+ if (USE_EXTRACTED_RESOURCES) {
+ file = new File(file, relativePath);
+ }
+ File folder = file.getParentFile();
+ if (!folder.isDirectory()) {
+ boolean created = folder.mkdirs();
+ if (!created) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Cannot create local resource file directory " + folder);
+ }
+ return;
+ }
+ }
+
+ if (relativePath.equals(RESOURCE_FILE_NAME)) {
+ //noinspection ConstantConditions
+ if (USE_EXTRACTED_RESOURCES) {
+ extractZip(resourceFile, bytes);
+ } else {
+ writeRawBytes(file, bytes);
+ }
+ } else {
+ writeRawBytes(file, bytes);
+ }
+ }
+
+ @Nullable
+ public static String writeTempDexFile(byte[] bytes) {
+ File file = getTempDexFile();
+ if (file != null) {
+ writeRawBytes(file, bytes);
+ return file.getPath();
+ } else {
+ Log.e(LOG_TAG, "No file to write temp dex content to");
+ }
+ return null;
+ }
+
+ /**
+ * Returns the modification time of the newest hotswap (reload) dex file
+ * or 0 if there are no hotswap dex files in the passed dataFolder
+ */
+ public static long getMostRecentTempDexTime(@NonNull File dataFolder) {
+ File dexFolder = getTempDexFileFolder(dataFolder);
+ if (!dexFolder.isDirectory()) {
+ return 0L;
+ }
+ File[] files = dexFolder.listFiles();
+ if (files == null) {
+ return 0L;
+ }
+
+ long newest = 0L;
+ for (File file : files) {
+ if (file.getPath().endsWith(CLASSES_DEX_SUFFIX)) {
+ newest = Math.max(newest, file.lastModified());
+ }
+ }
+
+ return newest;
+ }
+
+ /**
+ * Removes .dex files from the temp dex file folder
+ */
+ public static void purgeTempDexFiles(@NonNull File dataFolder) {
+ sHavePurgedTempDexFolder = true;
+
+ File dexFolder = getTempDexFileFolder(dataFolder);
+ if (!dexFolder.isDirectory()) {
+ return;
+ }
+ File[] files = dexFolder.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ for (File file : files) {
+ if (file.getPath().endsWith(CLASSES_DEX_SUFFIX)) {
+ boolean deleted = file.delete();
+ if (!deleted) {
+ Log.e(LOG_TAG, "Could not delete temp dex file " + file);
+ }
+ }
+ }
+ }
+
+ public static long getFileSize(@NonNull String path) {
+ // Currently only handle this for resource files
+ if (path.equals(RESOURCE_FILE_NAME)) {
+ File file = getExternalResourceFile();
+ if (file != null) {
+ return file.length();
+ }
+ }
+
+ return -1;
+ }
+
+ @Nullable
+ public static byte[] getCheckSum(@NonNull String path) {
+ // Currently only handle this for resource files
+ if (path.equals(RESOURCE_FILE_NAME)) {
+ File file = getExternalResourceFile();
+ if (file != null) {
+ return getCheckSum(file);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Computes a checksum of a file.
+ *
+ * @param file the file to compute the fingerprint for
+ * @return a fingerprint
+ */
+ @Nullable
+ public static byte[] getCheckSum(@NonNull File file) {
+ try {
+ // Create MD5 Hash
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+ byte[] buffer = new byte[4096];
+ BufferedInputStream input = new BufferedInputStream(new FileInputStream(file));
+ try {
+ while (true) {
+ int read = input.read(buffer);
+ if (read == -1) {
+ break;
+ }
+ digest.update(buffer, 0, read);
+ }
+ return digest.digest();
+ } finally {
+ input.close();
+ }
+ } catch (NoSuchAlgorithmException e) {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Couldn't look up message digest", e);
+ }
+ } catch (IOException ioe) {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Failed to read file " + file, ioe);
+ }
+ } catch (Throwable t) {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Unexpected checksum exception", t);
+ }
+ }
+ return null;
+ }
+
+ public static byte[] readRawBytes(@NonNull File source) {
+ try {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Reading the bytes for file " + source);
+ }
+ long length = source.length();
+ if (length > Integer.MAX_VALUE) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "File too large (" + length + ")");
+ }
+ return null;
+ }
+ byte[] result = new byte[(int)length];
+
+ BufferedInputStream input = new BufferedInputStream(new FileInputStream(source));
+ try {
+ int index = 0;
+ int remaining = result.length - index;
+ while (remaining > 0) {
+ int numRead = input.read(result, index, remaining);
+ if (numRead == -1) {
+ break;
+ }
+ index += numRead;
+ remaining -= numRead;
+ }
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Returning length " + result.length + " for file " + source);
+ }
+ return result;
+ } finally {
+ input.close();
+ }
+ } catch (IOException ioe) {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Failed to read file " + source, ioe);
+ }
+ }
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "I/O error, no bytes returned for " + source);
+ }
+ return null;
+ }
+}
diff --git a/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/IncrementalClassLoader.java b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/IncrementalClassLoader.java
new file mode 100644
index 0000000..5e4a8d4
--- /dev/null
+++ b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/IncrementalClassLoader.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.fd.runtime;
+
+import com.android.annotations.NonNull;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.List;
+
+import dalvik.system.BaseDexClassLoader;
+
+import static com.android.tools.fd.runtime.BootstrapApplication.LOG_TAG;
+
+// This is based on com.google.devtools.build.android.incrementaldeployment.IncrementalClassLoader
+// with some cleanup around path handling and made it compile on JDK 6 (e.g. removed multicatch
+// etc)
+// See
+// https://github.com/google/bazel/blob/master/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/IncrementalClassLoader.java
+// (May 11 revision, ca96e11)
+
+/**
+ * A class loader that loads classes from any .dex file in a particular directory on the SD card.
+ * <p>
+ * <p>Used to implement incremental deployment to Android phones.
+ */
+public class IncrementalClassLoader extends ClassLoader {
+ /** When false, compiled out of runtime library */
+ public static final boolean DEBUG_CLASS_LOADING = false;
+
+ private final DelegateClassLoader delegateClassLoader;
+
+ public IncrementalClassLoader(
+ ClassLoader original, String nativeLibraryPath, String codeCacheDir, List<String> dexes) {
+ super(original.getParent());
+
+ // TODO(bazel-team): For some mysterious reason, we need to use two class loaders so that
+ // everything works correctly. Investigate why that is the case so that the code can be
+ // simplified.
+ delegateClassLoader = createDelegateClassLoader(nativeLibraryPath, codeCacheDir, dexes,
+ original);
+ }
+
+ @Override
+ public Class<?> findClass(String className) throws ClassNotFoundException {
+ try {
+ Class<?> aClass = delegateClassLoader.findClass(className);
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Incremental class loader: findClass(" + className + ") = " + aClass);
+ }
+
+ return aClass;
+ } catch (ClassNotFoundException e) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Incremental class loader: findClass(" + className + ") : not found");
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * A class loader whose only purpose is to make {@code findClass()} public.
+ */
+ private static class DelegateClassLoader extends BaseDexClassLoader {
+ private DelegateClassLoader(
+ String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
+ super(dexPath, optimizedDirectory, libraryPath, parent);
+ }
+
+ @Override
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ Class<?> aClass = super.findClass(name);
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Delegate class loader: findClass(" + name + ") = " + aClass);
+ }
+
+ return aClass;
+ } catch (ClassNotFoundException e) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Delegate class loader: findClass(" + name + ") : not found");
+ }
+ throw e;
+ }
+ }
+ }
+
+ private static DelegateClassLoader createDelegateClassLoader(
+ String nativeLibraryPath, String codeCacheDir, List<String> dexes,
+ ClassLoader original) {
+ String pathBuilder = createDexPath(dexes);
+ return new DelegateClassLoader(pathBuilder, new File(codeCacheDir),
+ nativeLibraryPath, original);
+ }
+
+ @NonNull
+ private static String createDexPath(List<String> dexes) {
+ StringBuilder pathBuilder = new StringBuilder();
+ boolean first = true;
+ for (String dex : dexes) {
+ if (first) {
+ first = false;
+ } else {
+ pathBuilder.append(File.pathSeparator);
+ }
+
+ pathBuilder.append(dex);
+ }
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Incremental dex path is "
+ + BootstrapApplication.join('\n', dexes));
+ }
+ return pathBuilder.toString();
+ }
+
+ private static void setParent(ClassLoader classLoader, ClassLoader newParent) {
+ try {
+ Field parent = ClassLoader.class.getDeclaredField("parent");
+ parent.setAccessible(true);
+ parent.set(classLoader, newParent);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ClassLoader inject(
+ ClassLoader classLoader, String nativeLibraryPath, String codeCacheDir,
+ List<String> dexes) {
+ IncrementalClassLoader incrementalClassLoader =
+ new IncrementalClassLoader(classLoader, nativeLibraryPath, codeCacheDir, dexes);
+ setParent(classLoader, incrementalClassLoader);
+
+ // This works as follows:
+ // We're given the current class loader that's used to load the bootstrap application.
+ // We have a new class loader which reads patches/overrides from the data directory
+ // instead. We want *that* class loader to have the bootstrap class loader's parent
+ // as its parent, and then we make the bootstrap class loader parented by our
+ // class loader.
+ //
+ // In other words, we have this:
+ // BootstrapApplication.classLoader = ClassLoader1, parent=ClassLoader2
+ // We create ClassLoader3 from the .dex files in the data directory, and arrange for
+ // the hierarchy to be like this:
+ // BootstrapApplication.classLoader = ClassLoader1, parent=ClassLoader3, parent=ClassLoader2
+ // With this approach, a class find (which should always look at the parents first) should
+ // find anything from ClassLoader3 before they get them from ClassLoader1.
+ // (Note that ClassLoader2 in the above is generally the BootClassLoader, not containing
+ // any classes we care about.)
+
+ return incrementalClassLoader;
+ }
+}
diff --git a/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/MonkeyPatcher.java b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/MonkeyPatcher.java
new file mode 100644
index 0000000..e476107
--- /dev/null
+++ b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/MonkeyPatcher.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.fd.runtime;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.M;
+import static com.android.tools.fd.runtime.BootstrapApplication.LOG_TAG;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A utility class which uses reflection hacks to replace the application instance and
+ * the resource data for the current app.
+ * This is based on the reflection parts of
+ * com.google.devtools.build.android.incrementaldeployment.StubApplication,
+ * plus changes to compile on JDK 6.
+ * <p>
+ * It now also has a lot of extra reflection machinery to do live resource swapping
+ * in a running app (e.g. swiping through data structures, updating resource managers,
+ * flushing cached theme entries, etc.)
+ * <p>
+ * The original is
+ * https://github.com/google/bazel/blob/master/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java
+ * (May 11 revision, ca96e11)
+ * <p>
+ * (The code to handle resource loading etc is different; see FileManager.)
+ * Furthermore, the resource patching was hacked on some more such that it can
+ * handle live (activity-restart) changes, which allows us to for example patch
+ * the theme and have existing activities have their themes updated!
+ * <p>
+ * Original comment for the StubApplication, which contained the reflection methods:
+ * <p>
+ * A stub application that patches the class loader, then replaces itself with the real application
+ * by applying a liberal amount of reflection on Android internals.
+ * <p/>
+ * <p>This is, of course, terribly error-prone. Most of this code was tested with API versions
+ * 8, 10, 14, 15, 16, 17, 18, 19 and 21 on the Android emulator, a Nexus 5 running Lollipop LRX22C
+ * and a Samsung GT-I5800 running Froyo XWJPE. The exception is {@code monkeyPatchAssetManagers},
+ * which only works on Kitkat and Lollipop.
+ * <p/>
+ * <p>Note that due to a bug in Dalvik, this only works on Kitkat if ART is the Java runtime.
+ * <p/>
+ * <p>Unfortunately, if this does not work, we don't have a fallback mechanism: as soon as we
+ * build the APK with this class as the Application, we are committed to going through with it.
+ * <p/>
+ * <p>This class should use as few other classes as possible before the class loader is patched
+ * because any class loaded before it cannot be incrementally deployed.
+ */
+public class MonkeyPatcher {
+ @SuppressWarnings("unchecked") // Lots of conversions with generic types
+ public static void monkeyPatchApplication(@Nullable Context context,
+ @Nullable Application bootstrap,
+ @Nullable Application realApplication,
+ @Nullable String externalResourceFile) {
+ /*
+ The code seems to perform this:
+ Application realApplication = the newly instantiated (in attachBaseContext) user app
+
+ currentActivityThread = ActivityThread.currentActivityThread;
+ Application initialApplication = currentActivityThread.mInitialApplication;
+ if (initialApplication == BootstrapApplication.this) {
+ currentActivityThread.mInitialApplication = realApplication;
+
+ // Replace all instance of the stub application in ActivityThread#mAllApplications with the
+ // real one
+ List<Application> allApplications = currentActivityThread.mAllApplications;
+ for (int i = 0; i < allApplications.size(); i++) {
+ if (allApplications.get(i) == BootstrapApplication.this) {
+ allApplications.set(i, realApplication);
+ }
+ }
+
+ // Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and
+ // ActivityThread#mResourcePackages and do two things:
+ // - Replace the Application instance in its mApplication field with the real one
+ // - Replace mResDir to point to the external resource file instead of the .apk. This is
+ // used as the asset path for new Resources objects.
+ // - Set Application#mLoadedApk to the found LoadedApk instance
+
+ ArrayMap<String, WeakReference<LoadedApk>> map1 = currentActivityThread.mPackages;
+ for (Map.Entry<String, WeakReference<?>> entry : map1.entrySet()) {
+ Object loadedApk = entry.getValue().get();
+ if (loadedApk == null) {
+ continue;
+ }
+
+ if (loadedApk.mApplication == BootstrapApplication.this) {
+ loadedApk.mApplication = realApplication;
+ if (externalResourceFile != null) {
+ loadedApk.mResDir = externalResourceFile;
+ }
+ realApplication.mLoadedApk = loadedApk;
+ }
+ }
+
+ // Exactly the same as above, except done for mResourcePackages instead of mPackages
+ ArrayMap<String, WeakReference<LoadedApk>> map2 = currentActivityThread.mResourcePackages;
+ for (Map.Entry<String, WeakReference<?>> entry : map2.entrySet()) {
+ Object loadedApk = entry.getValue().get();
+ if (loadedApk == null) {
+ continue;
+ }
+
+ if (loadedApk.mApplication == BootstrapApplication.this) {
+ loadedApk.mApplication = realApplication;
+ if (externalResourceFile != null) {
+ loadedApk.mResDir = externalResourceFile;
+ }
+ realApplication.mLoadedApk = loadedApk;
+ }
+ }
+ */
+
+ // BootstrapApplication is created by reflection in Application#handleBindApplication() ->
+ // LoadedApk#makeApplication(), and its return value is used to set the Application field in all
+ // sorts of Android internals.
+ //
+ // Fortunately, Application#onCreate() is called quite soon after, so what we do is monkey
+ // patch in the real Application instance in BootstrapApplication#onCreate().
+ //
+ // A few places directly use the created Application instance (as opposed to the fields it is
+ // eventually stored in). Fortunately, it's easy to forward those to the actual real
+ // Application class.
+ try {
+ // Find the ActivityThread instance for the current thread
+ Class<?> activityThread = Class.forName("android.app.ActivityThread");
+ Object currentActivityThread = getActivityThread(context, activityThread);
+
+ // Find the mInitialApplication field of the ActivityThread to the real application
+ Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication");
+ mInitialApplication.setAccessible(true);
+ Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);
+ if (realApplication != null && initialApplication == bootstrap) {
+ mInitialApplication.set(currentActivityThread, realApplication);
+ }
+
+ // Replace all instance of the stub application in ActivityThread#mAllApplications with the
+ // real one
+ if (realApplication != null) {
+ Field mAllApplications = activityThread.getDeclaredField("mAllApplications");
+ mAllApplications.setAccessible(true);
+ List<Application> allApplications = (List<Application>) mAllApplications
+ .get(currentActivityThread);
+ for (int i = 0; i < allApplications.size(); i++) {
+ if (allApplications.get(i) == bootstrap) {
+ allApplications.set(i, realApplication);
+ }
+ }
+ }
+
+ // Figure out how loaded APKs are stored.
+
+ // API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know.
+ Class<?> loadedApkClass;
+ try {
+ loadedApkClass = Class.forName("android.app.LoadedApk");
+ } catch (ClassNotFoundException e) {
+ loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
+ }
+ Field mApplication = loadedApkClass.getDeclaredField("mApplication");
+ mApplication.setAccessible(true);
+ Field mResDir = loadedApkClass.getDeclaredField("mResDir");
+ mResDir.setAccessible(true);
+
+ // 10 doesn't have this field, 14 does. Fortunately, there are not many Honeycomb devices
+ // floating around.
+ Field mLoadedApk = null;
+ try {
+ mLoadedApk = Application.class.getDeclaredField("mLoadedApk");
+ } catch (NoSuchFieldException e) {
+ // According to testing, it's okay to ignore this.
+ }
+
+ // Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and
+ // ActivityThread#mResourcePackages and do two things:
+ // - Replace the Application instance in its mApplication field with the real one
+ // - Replace mResDir to point to the external resource file instead of the .apk. This is
+ // used as the asset path for new Resources objects.
+ // - Set Application#mLoadedApk to the found LoadedApk instance
+ for (String fieldName : new String[]{"mPackages", "mResourcePackages"}) {
+ Field field = activityThread.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Object value = field.get(currentActivityThread);
+
+ for (Map.Entry<String, WeakReference<?>> entry :
+ ((Map<String, WeakReference<?>>) value).entrySet()) {
+ Object loadedApk = entry.getValue().get();
+ if (loadedApk == null) {
+ continue;
+ }
+
+ if (mApplication.get(loadedApk) == bootstrap) {
+ if (realApplication != null) {
+ mApplication.set(loadedApk, realApplication);
+ }
+ if (externalResourceFile != null) {
+ mResDir.set(loadedApk, externalResourceFile);
+ }
+
+ if (realApplication != null && mLoadedApk != null) {
+ mLoadedApk.set(realApplication, loadedApk);
+ }
+ }
+ }
+ }
+ } catch (Throwable e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Nullable
+ public static Object getActivityThread(@Nullable Context context,
+ @Nullable Class<?> activityThread) {
+ try {
+ if (activityThread == null) {
+ activityThread = Class.forName("android.app.ActivityThread");
+ }
+ Method m = activityThread.getMethod("currentActivityThread");
+ m.setAccessible(true);
+ Object currentActivityThread = m.invoke(null);
+ if (currentActivityThread == null && context != null) {
+ // In older versions of Android (prior to frameworks/base 66a017b63461a22842)
+ // the currentActivityThread was built on thread locals, so we'll need to try
+ // even harder
+ Field mLoadedApk = context.getClass().getField("mLoadedApk");
+ mLoadedApk.setAccessible(true);
+ Object apk = mLoadedApk.get(context);
+ Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
+ mActivityThreadField.setAccessible(true);
+ currentActivityThread = mActivityThreadField.get(apk);
+ }
+ return currentActivityThread;
+ } catch (Throwable ignore) {
+ return null;
+ }
+ }
+
+ public static void monkeyPatchExistingResources(@Nullable Context context,
+ @Nullable String externalResourceFile,
+ @Nullable Collection<Activity> activities) {
+ if (externalResourceFile == null) {
+ return;
+ }
+
+ /*
+ (Note: the resource directory is *also* inserted into the loadedApk in
+ monkeyPatchApplication)
+ The code seems to perform this:
+ File externalResourceFile = <path to resources.ap_ or extracted directory>
+
+ AssetManager newAssetManager = new AssetManager();
+ newAssetManager.addAssetPath(externalResourceFile)
+
+ // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
+ // in L, so we do it unconditionally.
+ newAssetManager.ensureStringBlocks();
+
+ // Find the singleton instance of ResourcesManager
+ ResourcesManager resourcesManager = ResourcesManager.getInstance();
+
+ // Iterate over all known Resources objects
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ for (WeakReference<Resources> wr : resourcesManager.mActiveResources.values()) {
+ Resources resources = wr.get();
+ // Set the AssetManager of the Resources instance to our brand new one
+ resources.mAssets = newAssetManager;
+ resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
+ }
+ }
+
+ // Also, for each context, call getTheme() to get the current theme; null out its
+ // mTheme field, then invoke initializeTheme() to force it to be recreated (with the
+ // new asset manager!)
+
+ */
+
+ try {
+ // Create a new AssetManager instance and point it to the resources installed under
+ // /sdcard
+ AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance();
+ Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
+ mAddAssetPath.setAccessible(true);
+ if (((Integer) mAddAssetPath.invoke(newAssetManager, externalResourceFile)) == 0) {
+ throw new IllegalStateException("Could not create new AssetManager");
+ }
+
+ // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
+ // in L, so we do it unconditionally.
+ Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
+ mEnsureStringBlocks.setAccessible(true);
+ mEnsureStringBlocks.invoke(newAssetManager);
+
+ if (activities != null) {
+ for (Activity activity : activities) {
+ Resources resources = activity.getResources();
+
+ try {
+ Field mAssets = Resources.class.getDeclaredField("mAssets");
+ mAssets.setAccessible(true);
+ mAssets.set(resources, newAssetManager);
+ } catch (Throwable ignore) {
+ Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
+ mResourcesImpl.setAccessible(true);
+ Object resourceImpl = mResourcesImpl.get(resources);
+ Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
+ implAssets.setAccessible(true);
+ implAssets.set(resourceImpl, newAssetManager);
+ }
+
+ Resources.Theme theme = activity.getTheme();
+ try {
+ try {
+ Field ma = Resources.Theme.class.getDeclaredField("mAssets");
+ ma.setAccessible(true);
+ ma.set(theme, newAssetManager);
+ } catch (NoSuchFieldException ignore) {
+ Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");
+ themeField.setAccessible(true);
+ Object impl = themeField.get(theme);
+ Field ma = impl.getClass().getDeclaredField("mAssets");
+ ma.setAccessible(true);
+ ma.set(impl, newAssetManager);
+ }
+
+ Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme");
+ mt.setAccessible(true);
+ mt.set(activity, null);
+ Method mtm = ContextThemeWrapper.class.getDeclaredMethod("initializeTheme");
+ mtm.setAccessible(true);
+ mtm.invoke(activity);
+
+ Method mCreateTheme = AssetManager.class.getDeclaredMethod("createTheme");
+ mCreateTheme.setAccessible(true);
+ Object internalTheme = mCreateTheme.invoke(newAssetManager);
+ Field mTheme = Resources.Theme.class.getDeclaredField("mTheme");
+ mTheme.setAccessible(true);
+ mTheme.set(theme, internalTheme);
+ } catch (Throwable e) {
+ Log.e(LOG_TAG, "Failed to update existing theme for activity " + activity,
+ e);
+ }
+
+ pruneResourceCaches(resources);
+ }
+ }
+
+ // Iterate over all known Resources objects
+ Collection<WeakReference<Resources>> references;
+ if (SDK_INT >= KITKAT) {
+ // Find the singleton instance of ResourcesManager
+ Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
+ Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance");
+ mGetInstance.setAccessible(true);
+ Object resourcesManager = mGetInstance.invoke(null);
+ try {
+ Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
+ fMActiveResources.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ ArrayMap<?, WeakReference<Resources>> arrayMap =
+ (ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);
+ references = arrayMap.values();
+ } catch (NoSuchFieldException ignore) {
+ Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
+ mResourceReferences.setAccessible(true);
+ //noinspection unchecked
+ references = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);
+ }
+ } else {
+ Class<?> activityThread = Class.forName("android.app.ActivityThread");
+ Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");
+ fMActiveResources.setAccessible(true);
+ Object thread = getActivityThread(context, activityThread);
+ @SuppressWarnings("unchecked")
+ HashMap<?, WeakReference<Resources>> map =
+ (HashMap<?, WeakReference<Resources>>) fMActiveResources.get(thread);
+ references = map.values();
+ }
+ for (WeakReference<Resources> wr : references) {
+ Resources resources = wr.get();
+ if (resources != null) {
+ // Set the AssetManager of the Resources instance to our brand new one
+ try {
+ Field mAssets = Resources.class.getDeclaredField("mAssets");
+ mAssets.setAccessible(true);
+ mAssets.set(resources, newAssetManager);
+ } catch (Throwable ignore) {
+ Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
+ mResourcesImpl.setAccessible(true);
+ Object resourceImpl = mResourcesImpl.get(resources);
+ Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
+ implAssets.setAccessible(true);
+ implAssets.set(resourceImpl, newAssetManager);
+ }
+
+ resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
+ }
+ }
+ } catch (Throwable e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void pruneResourceCaches(@NonNull Object resources) {
+ // Drain TypedArray instances from the typed array pool since these can hold on
+ // to stale asset data
+ if (SDK_INT >= LOLLIPOP) {
+ try {
+ Field typedArrayPoolField =
+ Resources.class.getDeclaredField("mTypedArrayPool");
+ typedArrayPoolField.setAccessible(true);
+ Object pool = typedArrayPoolField.get(resources);
+ Class<?> poolClass = pool.getClass();
+ Method acquireMethod = poolClass.getDeclaredMethod("acquire");
+ acquireMethod.setAccessible(true);
+ while (true) {
+ Object typedArray = acquireMethod.invoke(pool);
+ if (typedArray == null) {
+ break;
+ }
+ }
+ } catch (Throwable ignore) {
+ }
+ }
+
+ if (SDK_INT >= Build.VERSION_CODES.M) {
+ // Really should only be N; fix this as soon as it has its own API level
+ try {
+ Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
+ mResourcesImpl.setAccessible(true);
+ // For the remainder, use the ResourcesImpl instead, where all the fields
+ // now live
+ resources = mResourcesImpl.get(resources);
+ } catch (Throwable ignore) {
+ }
+ }
+
+ // Prune bitmap and color state lists etc caches
+ Object lock = null;
+ if (SDK_INT >= JELLY_BEAN_MR2) {
+ try {
+ Field field = resources.getClass().getDeclaredField("mAccessLock");
+ field.setAccessible(true);
+ lock = field.get(resources);
+ } catch (Throwable ignore) {
+ }
+ } else {
+ try {
+ Field field = Resources.class.getDeclaredField("mTmpValue");
+ field.setAccessible(true);
+ lock = field.get(resources);
+ } catch (Throwable ignore) {
+ }
+ }
+
+ if (lock == null) {
+ lock = MonkeyPatcher.class;
+ }
+
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized (lock) {
+ // Prune bitmap and color caches
+ pruneResourceCache(resources, "mDrawableCache");
+ pruneResourceCache(resources,"mColorDrawableCache");
+ pruneResourceCache(resources,"mColorStateListCache");
+ if (SDK_INT >= M) {
+ pruneResourceCache(resources, "mAnimatorCache");
+ pruneResourceCache(resources, "mStateListAnimatorCache");
+ }
+ }
+ }
+
+ private static boolean pruneResourceCache(@NonNull Object resources,
+ @NonNull String fieldName) {
+ try {
+ Class<?> resourcesClass = resources.getClass();
+ Field cacheField;
+ try {
+ cacheField = resourcesClass.getDeclaredField(fieldName);
+ } catch (NoSuchFieldException ignore) {
+ cacheField = Resources.class.getDeclaredField(fieldName);
+ }
+ cacheField.setAccessible(true);
+ Object cache = cacheField.get(resources);
+
+ // Find the class which defines the onConfigurationChange method
+ Class<?> type = cacheField.getType();
+ if (SDK_INT < JELLY_BEAN) {
+ if (cache instanceof SparseArray) {
+ ((SparseArray) cache).clear();
+ return true;
+ } else if (SDK_INT >= ICE_CREAM_SANDWICH && cache instanceof LongSparseArray) {
+ // LongSparseArray has API level 16 but was private (and available inside
+ // the framework) in 15 and is used for this cache.
+ //noinspection AndroidLintNewApi
+ ((LongSparseArray) cache).clear();
+ return true;
+ }
+ } else if (SDK_INT < M) {
+ // JellyBean, KitKat, Lollipop
+ if ("mColorStateListCache".equals(fieldName)) {
+ // For some reason framework doesn't call clearDrawableCachesLocked on
+ // this field
+ if (cache instanceof LongSparseArray) {
+ //noinspection AndroidLintNewApi
+ ((LongSparseArray)cache).clear();
+ }
+ } else if (type.isAssignableFrom(ArrayMap.class)) {
+ Method clearArrayMap = Resources.class.getDeclaredMethod(
+ "clearDrawableCachesLocked", ArrayMap.class, Integer.TYPE);
+ clearArrayMap.setAccessible(true);
+ clearArrayMap.invoke(resources, cache, -1);
+ return true;
+ } else if (type.isAssignableFrom(LongSparseArray.class)) {
+ Method clearSparseMap = Resources.class.getDeclaredMethod(
+ "clearDrawableCachesLocked", LongSparseArray.class, Integer.TYPE);
+ clearSparseMap.setAccessible(true);
+ clearSparseMap.invoke(resources, cache, -1);
+ return true;
+ }
+ } else {
+ // Marshmallow: DrawableCache class
+ while (type != null) {
+ try {
+ Method configChangeMethod = type.getDeclaredMethod(
+ "onConfigurationChange", Integer.TYPE);
+ configChangeMethod.setAccessible(true);
+ configChangeMethod.invoke(cache, -1);
+ return true;
+ } catch (Throwable ignore) {
+ }
+
+ type = type.getSuperclass();
+ }
+ }
+ } catch (Throwable ignore) {
+ // Not logging these; while there is some checking of SDK_INT here to avoid
+ // doing a lot of unnecessary field lookups, it's not entirely accurate and
+ // errs on the side of caution (since different devices may have picked up
+ // different snapshots of the framework); therefore, it's normal for this
+ // to attempt to look up a field for a cache that isn't there; only if it's
+ // really there will it continue to flush that particular cache.
+ }
+
+ return false;
+ }
+}
diff --git a/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/Restarter.java b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/Restarter.java
new file mode 100644
index 0000000..9686c36
--- /dev/null
+++ b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/Restarter.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.fd.runtime;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.Build;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.tools.fd.runtime.BootstrapApplication.LOG_TAG;
+
+/**
+ * Handler capable of restarting parts of the application in order for changes to become
+ * apparent to the user:
+ * <ul>
+ * <li> Apply a tiny change immediately (possible if we can detect that the change
+ * is only used in a limited context (such as in a layout) and we can directly
+ * poke the view hierarchy and schedule a paint
+ * <li> Apply a change to the current activity. We can restart just the activity
+ * while the app continues running.
+ * <li> Restart the app with state persistence (simulates what happens when a user
+ * puts an app in the background, then it gets killed by the memory monitor,
+ * and then restored when the user brings it back
+ * <li> Restart the app completely.
+ * </ul>
+ */
+public class Restarter {
+ /** Restart an activity. Should preserve as much state as possible. */
+ public static void restartActivityOnUiThread(@NonNull final Activity activity) {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Resources updated: notify activities");
+ }
+ updateActivity(activity);
+ }
+ });
+ }
+
+ private static void restartActivity(@NonNull Activity activity) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "About to restart " + activity.getClass().getSimpleName());
+ }
+
+ // You can't restart activities that have parents: find the top-most activity
+ while (activity.getParent() != null) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, activity.getClass().getSimpleName()
+ + " is not a top level activity; restarting "
+ + activity.getParent().getClass().getSimpleName() + " instead");
+ }
+ activity = activity.getParent();
+ }
+
+ // Directly supported by the framework!
+ activity.recreate();
+ }
+
+ /**
+ * Attempt to restart the app. Ideally this should also try to preserve as much state as
+ * possible:
+ * <ul>
+ * <li>The current activity</li>
+ * <li>If possible, state in the current activity</li>, and
+ * <li>The activity stack</li>
+ * </ul>
+ *
+ * This may require some framework support. Apparently it may already be possible
+ * (Dianne says to put the app in the background, kill it then restart it; need to
+ * figure out how to do this.)
+ */
+ public static void restartApp(@Nullable Context appContext,
+ @NonNull Collection<Activity> knownActivities,
+ boolean toast) {
+ if (!knownActivities.isEmpty()) {
+ // Can't live patch resources; instead, try to restart the current activity
+ Activity foreground = getForegroundActivity(appContext);
+
+ if (foreground != null) {
+ // http://stackoverflow.com/questions/6609414/howto-programatically-restart-android-app
+ //noinspection UnnecessaryLocalVariable
+ if (toast) {
+ showToast(foreground, "Restarting app to apply incompatible changes");
+ }
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "RESTARTING APP");
+ }
+ @SuppressWarnings("UnnecessaryLocalVariable") // fore code clarify
+ Context context = foreground;
+ Intent intent = new Intent(context, foreground.getClass());
+ int intentId = 0;
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, intentId,
+ intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Scheduling activity " + foreground
+ + " to start after exiting process");
+ }
+ } else {
+ showToast(knownActivities.iterator().next(), "Unable to restart app");
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Couldn't find any foreground activities to restart " +
+ "for resource refresh");
+ }
+ }
+ System.exit(0);
+ }
+ }
+
+ static void showToast(@NonNull final Activity activity, @NonNull final String text) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "About to show toast for activity " + activity + ": " + text);
+ }
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Context context = activity.getApplicationContext();
+ if (context instanceof ContextWrapper) {
+ Context base = ((ContextWrapper) context).getBaseContext();
+ if (base == null) {
+ if (Log.isLoggable(LOG_TAG, Log.WARN)) {
+ Log.w(LOG_TAG, "Couldn't show toast: no base context");
+ }
+ return;
+ }
+ }
+
+ // For longer messages, leave the message up longer
+ int duration = Toast.LENGTH_SHORT;
+ if (text.length() >= 60 || text.indexOf('\n') != -1) {
+ duration = Toast.LENGTH_LONG;
+ }
+
+ // Avoid crashing when not available, e.g.
+ // java.lang.RuntimeException: Can't create handler inside thread that has
+ // not called Looper.prepare()
+ Toast.makeText(activity, text, duration).show();
+ } catch (Throwable e) {
+ if (Log.isLoggable(LOG_TAG, Log.WARN)) {
+ Log.w(LOG_TAG, "Couldn't show toast", e);
+ }
+ }
+ }
+ });
+ }
+
+ @Nullable
+ public static Activity getForegroundActivity(@Nullable Context context) {
+ List<Activity> list = getActivities(context, true);
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ // http://stackoverflow.com/questions/11411395/how-to-get-current-foreground-activity-context-in-android
+ @NonNull
+ public static List<Activity> getActivities(@Nullable Context context, boolean foregroundOnly) {
+ List<Activity> list = new ArrayList<Activity>();
+ try {
+ Class activityThreadClass = Class.forName("android.app.ActivityThread");
+ Object activityThread = MonkeyPatcher.getActivityThread(context, activityThreadClass);
+ Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
+ activitiesField.setAccessible(true);
+
+ // TODO: On older platforms, cast this to a HashMap
+
+ Collection c;
+ Object collection = activitiesField.get(activityThread);
+
+ if (collection instanceof HashMap) {
+ // Older platforms
+ Map activities = (HashMap) collection;
+ c = activities.values();
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
+ collection instanceof ArrayMap) {
+ ArrayMap activities = (ArrayMap) collection;
+ c = activities.values();
+ } else {
+ return list;
+ }
+ for (Object activityRecord : c) {
+ Class activityRecordClass = activityRecord.getClass();
+ if (foregroundOnly) {
+ Field pausedField = activityRecordClass.getDeclaredField("paused");
+ pausedField.setAccessible(true);
+ if (pausedField.getBoolean(activityRecord)) {
+ continue;
+ }
+ }
+ Field activityField = activityRecordClass.getDeclaredField("activity");
+ activityField.setAccessible(true);
+ Activity activity = (Activity) activityField.get(activityRecord);
+ if (activity != null) {
+ list.add(activity);
+ }
+ }
+ } catch (Throwable ignore) {
+ }
+ return list;
+ }
+
+ private static void updateActivity(@NonNull Activity activity) {
+ // This method can be called for activities that are not in the foreground, as long
+ // as some of its resources have been updated. Therefore we'll need to make sure
+ // that this activity is in the foreground, and if not do nothing. Ways to do
+ // that are outlined here:
+ // http://stackoverflow.com/questions/3667022/checking-if-an-android-application-is-running-in-the-background/5862048#5862048
+
+ // Try to force re-layout; there are many approaches; see
+ // http://stackoverflow.com/questions/5991968/how-to-force-an-entire-layout-view-refresh
+
+ // This doesn't seem to update themes properly -- may need to do recreate() instead!
+ //getWindow().getDecorView().findViewById(android.R.id.content).invalidate();
+
+ // This is a bit of a sledgehammer. We should consider having an incremental updater,
+ // similar to IntelliJ's Look & Feel updater which iterates to the view hierarchy
+ // and tries to incrementally refresh the LAF delegates and force a repaint.
+ // On the other hand, we may never be able to succeed with that, since there could be
+ // UI elements on the screen cached from callbacks. I should probably *not* attempt
+ // to try to poke the user's data models; recreating the current layout should be
+ // enough (e.g. if a layout references @string/foo, we'll recreate those widgets
+ // if (mLastContentView != -1) {
+ // setContentView(mLastContentView);
+ // } else {
+ // recreate();
+ // }
+ // -- nope, even that's iffy. I had code which *after* calling setContentView would
+ // do some findViewById calls etc to reinitialize views.
+ //
+ // So what I should really try to do is have some knowledge about what changed,
+ // and see if I can figure out that the change is minor (e.g. doesn't affect themes
+ // or layout parameters etc), and if so, just try to poke the view hierarchy directly,
+ // and if not, just recreate
+
+ // if (changeManager.isSimpleDelta()) {
+ // changeManager.applyDirectly(this);
+ // } else {
+
+
+ // Note: This doesn't handle manifest changes like changing the application title
+
+ restartActivity(activity);
+ }
+
+ /** Show a toast when an activity becomes available (if possible). */
+ public static void showToastWhenPossible(@Nullable Context context, @NonNull String message) {
+ Activity activity = Restarter.getForegroundActivity(context);
+ if (activity != null) {
+ Restarter.showToast(activity, message);
+ } else {
+ // Only try for about 10 seconds
+ showToastWhenPossible(context, message, 10);
+ }
+ }
+
+ private static void showToastWhenPossible(
+ @Nullable final Context context,
+ @NonNull final String message,
+ final int remainingAttempts) {
+ Looper mainLooper = Looper.getMainLooper();
+ Handler handler = new Handler(mainLooper);
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Activity activity = getForegroundActivity(context);
+ if (activity != null) {
+ showToast(activity, message);
+ } else {
+ if (remainingAttempts > 0) {
+ showToastWhenPossible(context, message, remainingAttempts - 1);
+ }
+ }
+ }
+ }, 1000);
+ }
+}
diff --git a/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/Server.java b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/Server.java
new file mode 100644
index 0000000..2edbdb9
--- /dev/null
+++ b/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/Server.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.fd.runtime;
+
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_EOF;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_PATCHES;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_PATH_CHECKSUM;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_PATH_EXISTS;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_PING;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_RESTART_ACTIVITY;
+import static com.android.tools.fd.common.ProtocolConstants.MESSAGE_SHOW_TOAST;
+import static com.android.tools.fd.common.ProtocolConstants.PROTOCOL_IDENTIFIER;
+import static com.android.tools.fd.common.ProtocolConstants.PROTOCOL_VERSION;
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_COLD_SWAP;
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_HOT_SWAP;
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_NONE;
+import static com.android.tools.fd.common.ProtocolConstants.UPDATE_MODE_WARM_SWAP;
+import static com.android.tools.fd.runtime.BootstrapApplication.LOG_TAG;
+import static com.android.tools.fd.runtime.FileManager.CLASSES_DEX_SUFFIX;
+import static com.android.tools.fd.runtime.Paths.RELOAD_DEX_FILE_NAME;
+import static com.android.tools.fd.runtime.Paths.RESOURCE_FILE_NAME;
+
+import com.android.annotations.NonNull;
+
+import android.app.Activity;
+import android.app.Application;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import dalvik.system.DexClassLoader;
+
+/**
+ * Server running in the app listening for messages from the IDE and updating the code and resources
+ * when provided
+ */
+public class Server {
+
+ /**
+ * If true, app restarts itself after receiving coldswap patches. If false,
+ * it will just wait for the client to kill it remotely and restart via activity manager.
+ * If we restart locally, there could be problems around: a) getting all the right intent
+ * data to the restarted activity, and b) sometimes, the activity state is saved by the
+ * system, and it could lead to conflicts with the new version of the app.
+ * So this is currently turned off. See
+ * https://code.google.com/p/android/issues/detail?id=200895#c9
+ */
+ private static final boolean RESTART_LOCALLY = false;
+
+ /**
+ * Temporary debugging: have the server emit a message to the log every 30 seconds to
+ * indicate whether it's still alive
+ */
+ private static final boolean POST_ALIVE_STATUS = false;
+
+ private LocalServerSocket mServerSocket;
+
+ private final Application mApplication;
+
+ private static int sWrongTokenCount;
+
+ public static void create(@NonNull String packageName, @NonNull Application application) {
+ //noinspection ResultOfObjectAllocationIgnored
+ new Server(packageName, application);
+ }
+
+ private Server(@NonNull String packageName, @NonNull Application application) {
+ mApplication = application;
+ try {
+ mServerSocket = new LocalServerSocket(packageName);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Starting server socket listening for package " + packageName
+ + " on " + mServerSocket.getLocalSocketAddress());
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IO Error creating local socket at " + packageName, e);
+ return;
+ }
+ startServer();
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Started server for package " + packageName);
+ }
+ }
+
+ private void startServer() {
+ try {
+ Thread socketServerThread = new Thread(new SocketServerThread());
+ socketServerThread.start();
+ } catch (Throwable e) {
+ // Make sure an exception doesn't cause the rest of the user's
+ // onCreate() method to be invoked
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Fatal error starting Instant Run server", e);
+ }
+ }
+ }
+
+ private class SocketServerThread extends Thread {
+ @Override
+ public void run() {
+ if (POST_ALIVE_STATUS) {
+ final Handler handler = new Handler();
+ Timer timer = new Timer();
+ TimerTask task = new TimerTask() {
+ @Override
+ public void run() {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Log.v(LOG_TAG, "Instant Run server still here...");
+ }
+ });
+ }
+ };
+
+ timer.schedule(task, 1, 30000L);
+ }
+
+ while (true) {
+ try {
+ LocalServerSocket serverSocket = mServerSocket;
+ if (serverSocket == null) {
+ break; // stopped?
+ }
+ LocalSocket socket = serverSocket.accept();
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received connection from IDE: spawning connection thread");
+ }
+
+ SocketServerReplyThread socketServerReplyThread = new SocketServerReplyThread(
+ socket);
+ socketServerReplyThread.run();
+
+ if (sWrongTokenCount > 50) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Stopping server: too many wrong token connections");
+ }
+ mServerSocket.close();
+ break;
+ }
+ } catch (Throwable e) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Fatal error accepting connection on local socket", e);
+ }
+ }
+ }
+ }
+ }
+
+ private class SocketServerReplyThread extends Thread {
+
+ private final LocalSocket mSocket;
+
+ SocketServerReplyThread(LocalSocket socket) {
+ mSocket = socket;
+ }
+
+ @Override
+ public void run() {
+ try {
+ DataInputStream input = new DataInputStream(mSocket.getInputStream());
+ DataOutputStream output = new DataOutputStream(mSocket.getOutputStream());
+ try {
+ handle(input, output);
+ } finally {
+ try {
+ input.close();
+ } catch (IOException ignore) {
+ }
+ try {
+ output.close();
+ } catch (IOException ignore) {
+ }
+ }
+ } catch (IOException e) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Fatal error receiving messages", e);
+ }
+ }
+ }
+
+ private void handle(DataInputStream input, DataOutputStream output) throws IOException {
+ long magic = input.readLong();
+ if (magic != PROTOCOL_IDENTIFIER) {
+ Log.w(LOG_TAG, "Unrecognized header format "
+ + Long.toHexString(magic));
+ return;
+ }
+ int version = input.readInt();
+
+ // Send current protocol version to the IDE so it can decide what to do
+ output.writeInt(PROTOCOL_VERSION);
+
+ if (version != PROTOCOL_VERSION) {
+ Log.w(LOG_TAG, "Mismatched protocol versions; app is "
+ + "using version " + PROTOCOL_VERSION + " and tool is using version "
+ + version);
+ return;
+ }
+
+ while (true) {
+ int message = input.readInt();
+ switch (message) {
+ case MESSAGE_EOF: {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received EOF from the IDE");
+ }
+ return;
+ }
+
+ case MESSAGE_PING: {
+ // Send an "ack" back to the IDE.
+ // The value of the boolean is true only when the app is in the
+ // foreground.
+ boolean active = Restarter.getForegroundActivity(mApplication) != null;
+ output.writeBoolean(active);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received Ping message from the IDE; " +
+ "returned active = " + active);
+ }
+ continue;
+ }
+
+ case MESSAGE_PATH_EXISTS: {
+ String path = input.readUTF();
+ long size = FileManager.getFileSize(path);
+ output.writeLong(size);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received path-exists(" + path + ") from the " +
+ "IDE; returned size=" + size);
+ }
+ continue;
+ }
+
+ case MESSAGE_PATH_CHECKSUM: {
+ long begin = System.currentTimeMillis();
+ String path = input.readUTF();
+ byte[] checksum = FileManager.getCheckSum(path);
+ if (checksum != null) {
+ output.writeInt(checksum.length);
+ output.write(checksum);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ long end = System.currentTimeMillis();
+ String hash = new BigInteger(1, checksum).toString(16);
+ Log.v(LOG_TAG, "Received checksum(" + path + ") from the " +
+ "IDE: took " + (end - begin) + "ms to compute " + hash);
+ }
+ } else {
+ output.writeInt(0);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received checksum(" + path + ") from the " +
+ "IDE: returning <null>");
+ }
+ }
+ continue;
+ }
+
+ case MESSAGE_RESTART_ACTIVITY: {
+ if (!authenticate(input)) {
+ return;
+ }
+
+ Activity activity = Restarter.getForegroundActivity(mApplication);
+ if (activity != null) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Restarting activity per user request");
+ }
+ Restarter.restartActivityOnUiThread(activity);
+ }
+ continue;
+ }
+
+ case MESSAGE_PATCHES: {
+ if (!authenticate(input)) {
+ return;
+ }
+
+ List<ApplicationPatch> changes = ApplicationPatch.read(input);
+ if (changes == null) {
+ continue;
+ }
+
+ boolean hasResources = hasResources(changes);
+ int updateMode = input.readInt();
+ updateMode = handlePatches(changes, hasResources, updateMode);
+
+ boolean showToast = input.readBoolean();
+
+ // Send an "ack" back to the IDE; this is used for timing purposes only
+ output.writeBoolean(true);
+
+ restart(updateMode, hasResources, showToast);
+ continue;
+ }
+
+ case MESSAGE_SHOW_TOAST: {
+ String text = input.readUTF();
+ Activity foreground = Restarter.getForegroundActivity(mApplication);
+ if (foreground != null) {
+ Restarter.showToast(foreground, text);
+ } else if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Couldn't show toast (no activity) : " + text);
+ }
+ continue;
+ }
+
+ default: {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Unexpected message type: " + message);
+ }
+ // If we hit unexpected message types we can't really continue
+ // the conversation: we can misinterpret data for the unexpected
+ // command as separate messages with different meanings than intended
+ return;
+ }
+ }
+ }
+ }
+
+ private boolean authenticate(@NonNull DataInputStream input) throws IOException {
+ long token = input.readLong();
+ if (token != AppInfo.token) {
+ Log.w(LOG_TAG, "Mismatched identity token from client; received " + token
+ + " and expected " + AppInfo.token);
+ sWrongTokenCount++;
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private static boolean isResourcePath(String path) {
+ return path.equals(RESOURCE_FILE_NAME) || path.startsWith("res/");
+ }
+
+ private static boolean hasResources(@NonNull List<ApplicationPatch> changes) {
+ // Any non-code patch is a resource patch (normally resources.ap_ but could
+ // also be individual resource files such as res/layout/activity_main.xml)
+ for (ApplicationPatch change : changes) {
+ String path = change.getPath();
+ if (isResourcePath(path)) {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ private int handlePatches(@NonNull List<ApplicationPatch> changes, boolean hasResources,
+ int updateMode) {
+ if (hasResources) {
+ FileManager.startUpdate();
+ }
+
+ for (ApplicationPatch change : changes) {
+ String path = change.getPath();
+ if (path.endsWith(CLASSES_DEX_SUFFIX)) {
+ handleColdSwapPatch(change);
+
+ // Gradle sometimes sends a restart dex even when there is a hotswap patch,
+ // so don't take the presence of a restart dex as a conclusion that we must
+ // do a coldswap. Check.
+ boolean canHotSwap = false;
+ for (ApplicationPatch c : changes) {
+ if (c.getPath().equals(RELOAD_DEX_FILE_NAME)) {
+ canHotSwap = true;
+ break;
+ }
+ }
+
+ if (!canHotSwap) {
+ updateMode = UPDATE_MODE_COLD_SWAP;
+ }
+
+ } else if (path.equals(RELOAD_DEX_FILE_NAME)) {
+ updateMode = handleHotSwapPatch(updateMode, change);
+ } else if (isResourcePath(path)) {
+ updateMode = handleResourcePatch(updateMode, change, path);
+ }
+ }
+
+ if (hasResources) {
+ FileManager.finishUpdate(true);
+ }
+
+ return updateMode;
+ }
+
+ private static int handleResourcePatch(int updateMode, @NonNull ApplicationPatch patch,
+ @NonNull String path) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received resource changes (" + path + ")");
+ }
+ FileManager.writeAaptResources(path, patch.getBytes());
+ //noinspection ResourceType
+ updateMode = Math.max(updateMode, UPDATE_MODE_WARM_SWAP);
+ return updateMode;
+ }
+
+ private int handleHotSwapPatch(int updateMode, @NonNull ApplicationPatch patch) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received incremental code patch");
+ }
+ try {
+ String dexFile = FileManager.writeTempDexFile(patch.getBytes());
+ if (dexFile == null) {
+ Log.e(LOG_TAG, "No file to write the code to");
+ return updateMode;
+ } else if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Reading live code from " + dexFile);
+ }
+ String nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();
+ DexClassLoader dexClassLoader = new DexClassLoader(dexFile,
+ mApplication.getCacheDir().getPath(), nativeLibraryPath,
+ getClass().getClassLoader());
+
+ // we should transform this process with an interface/impl
+ Class<?> aClass = Class.forName(
+ "com.android.tools.fd.runtime.AppPatchesLoaderImpl", true, dexClassLoader);
+ try {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Got the patcher class " + aClass);
+ }
+
+ PatchesLoader loader = (PatchesLoader) aClass.newInstance();
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Got the patcher instance " + loader);
+ }
+ String[] getPatchedClasses = (String[]) aClass
+ .getDeclaredMethod("getPatchedClasses").invoke(loader);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Got the list of classes ");
+ for (String getPatchedClass : getPatchedClasses) {
+ Log.v(LOG_TAG, "class " + getPatchedClass);
+ }
+ }
+ if (!loader.load()) {
+ updateMode = UPDATE_MODE_COLD_SWAP;
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Couldn't apply code changes", e);
+ e.printStackTrace();
+ updateMode = UPDATE_MODE_COLD_SWAP;
+ }
+ } catch (Throwable e) {
+ Log.e(LOG_TAG, "Couldn't apply code changes", e);
+ updateMode = UPDATE_MODE_COLD_SWAP;
+ }
+ return updateMode;
+ }
+
+ private static void handleColdSwapPatch(@NonNull ApplicationPatch patch) {
+ if (patch.path.startsWith(Paths.DEX_SLICE_PREFIX)) {
+ File file = FileManager.writeDexShard(patch.getBytes(), patch.path);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Received dex shard " + file);
+ }
+ }
+ }
+
+ private void restart(int updateMode, boolean incrementalResources, boolean toast) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Finished loading changes; update mode =" + updateMode);
+ }
+
+ if (updateMode == UPDATE_MODE_NONE || updateMode == UPDATE_MODE_HOT_SWAP) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Applying incremental code without restart");
+ }
+
+ if (toast) {
+ Activity foreground = Restarter.getForegroundActivity(mApplication);
+ if (foreground != null) {
+ Restarter.showToast(foreground, "Applied code changes without activity " +
+ "restart");
+ } else if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Couldn't show toast: no activity found");
+ }
+ }
+ return;
+ }
+
+ List<Activity> activities = Restarter.getActivities(mApplication, false);
+
+ if (incrementalResources && updateMode == UPDATE_MODE_WARM_SWAP) {
+ // Try to just replace the resources on the fly!
+ File file = FileManager.getExternalResourceFile();
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "About to update resource file=" + file +
+ ", activities=" + activities);
+ }
+
+ if (file != null) {
+ String resources = file.getPath();
+ MonkeyPatcher.monkeyPatchApplication(mApplication, null, null, resources);
+ MonkeyPatcher.monkeyPatchExistingResources(mApplication, resources, activities);
+ } else {
+ Log.e(LOG_TAG, "No resource file found to apply");
+ updateMode = UPDATE_MODE_COLD_SWAP;
+ }
+ }
+
+ Activity activity = Restarter.getForegroundActivity(mApplication);
+ if (updateMode == UPDATE_MODE_WARM_SWAP) {
+ if (activity != null) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Restarting activity only!");
+ }
+
+ boolean handledRestart = false;
+ try {
+ // Allow methods to handle their own restart by implementing
+ // public boolean onHandleCodeChange(long flags) { .... }
+ // and returning true if the change was handled manually
+ Method method = activity.getClass().getMethod("onHandleCodeChange", Long.TYPE);
+ Object result = method.invoke(activity, 0L);
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Activity " + activity
+ + " provided manual restart method; return " + result);
+ }
+ if (Boolean.TRUE.equals(result)) {
+ handledRestart = true;
+ if (toast) {
+ Restarter.showToast(activity, "Applied changes");
+ }
+ }
+ } catch (Throwable ignore) {
+ }
+
+ if (!handledRestart) {
+ if (toast) {
+ Restarter.showToast(activity, "Applied changes, restarted activity");
+ }
+ Restarter.restartActivityOnUiThread(activity);
+ }
+ return;
+ }
+
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "No activity found, falling through to do a full app restart");
+ }
+ updateMode = UPDATE_MODE_COLD_SWAP;
+ }
+
+ if (updateMode != UPDATE_MODE_COLD_SWAP) {
+ if (Log.isLoggable(LOG_TAG, Log.ERROR)) {
+ Log.e(LOG_TAG, "Unexpected update mode: " + updateMode);
+ }
+ return;
+ }
+
+ if (RESTART_LOCALLY) {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Performing full app restart");
+ }
+
+ Restarter.restartApp(mApplication, activities, toast);
+ } else {
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Waiting for app to be killed and restarted by the IDE...");
+ }
+ }
+ }
+}
diff --git a/instant-run/instant-run.iml b/instant-run/instant-run.iml
new file mode 100644
index 0000000..19dbd15
--- /dev/null
+++ b/instant-run/instant-run.iml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/base/jack/jack-api/README.txt b/jack/jack-api/README.txt
similarity index 100%
rename from base/jack/jack-api/README.txt
rename to jack/jack-api/README.txt
diff --git a/base/jack/jack-api/build.gradle b/jack/jack-api/build.gradle
similarity index 100%
rename from base/jack/jack-api/build.gradle
rename to jack/jack-api/build.gradle
diff --git a/base/jack/jack-api/jack-api.iml b/jack/jack-api/jack-api.iml
similarity index 100%
rename from base/jack/jack-api/jack-api.iml
rename to jack/jack-api/jack-api.iml
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/ConfigNotSupportedException.java b/jack/jack-api/src/main/java/com/android/jack/api/ConfigNotSupportedException.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/ConfigNotSupportedException.java
rename to jack/jack-api/src/main/java/com/android/jack/api/ConfigNotSupportedException.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/JackConfig.java b/jack/jack-api/src/main/java/com/android/jack/api/JackConfig.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/JackConfig.java
rename to jack/jack-api/src/main/java/com/android/jack/api/JackConfig.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/JackProvider.java b/jack/jack-api/src/main/java/com/android/jack/api/JackProvider.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/JackProvider.java
rename to jack/jack-api/src/main/java/com/android/jack/api/JackProvider.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/Api01CompilationTask.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/Api01CompilationTask.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/Api01CompilationTask.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/Api01CompilationTask.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/Api01Config.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/Api01Config.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/Api01Config.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/Api01Config.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/ChainedException.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/ChainedException.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/ChainedException.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/ChainedException.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/CompilationException.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/CompilationException.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/CompilationException.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/CompilationException.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/ConfigurationException.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/ConfigurationException.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/ConfigurationException.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/ConfigurationException.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/DebugInfoLevel.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/DebugInfoLevel.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/DebugInfoLevel.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/DebugInfoLevel.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/JavaSourceVersion.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/JavaSourceVersion.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/JavaSourceVersion.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/JavaSourceVersion.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/MultiDexKind.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/MultiDexKind.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/MultiDexKind.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/MultiDexKind.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/ReporterKind.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/ReporterKind.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/ReporterKind.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/ReporterKind.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/ResourceCollisionPolicy.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/ResourceCollisionPolicy.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/ResourceCollisionPolicy.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/ResourceCollisionPolicy.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/TypeCollisionPolicy.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/TypeCollisionPolicy.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/TypeCollisionPolicy.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/TypeCollisionPolicy.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/UnrecoverableException.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/UnrecoverableException.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/UnrecoverableException.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/UnrecoverableException.java
diff --git a/base/jack/jack-api/src/main/java/com/android/jack/api/v01/VerbosityLevel.java b/jack/jack-api/src/main/java/com/android/jack/api/v01/VerbosityLevel.java
similarity index 100%
rename from base/jack/jack-api/src/main/java/com/android/jack/api/v01/VerbosityLevel.java
rename to jack/jack-api/src/main/java/com/android/jack/api/v01/VerbosityLevel.java
diff --git a/base/jack/jill-api/README.txt b/jack/jill-api/README.txt
similarity index 100%
rename from base/jack/jill-api/README.txt
rename to jack/jill-api/README.txt
diff --git a/base/jack/jill-api/build.gradle b/jack/jill-api/build.gradle
similarity index 100%
rename from base/jack/jill-api/build.gradle
rename to jack/jill-api/build.gradle
diff --git a/base/jack/jill-api/jill-api.iml b/jack/jill-api/jill-api.iml
similarity index 100%
rename from base/jack/jill-api/jill-api.iml
rename to jack/jill-api/jill-api.iml
diff --git a/base/jack/jill-api/src/main/java/com/android/jill/api/ConfigNotSupportedException.java b/jack/jill-api/src/main/java/com/android/jill/api/ConfigNotSupportedException.java
similarity index 100%
rename from base/jack/jill-api/src/main/java/com/android/jill/api/ConfigNotSupportedException.java
rename to jack/jill-api/src/main/java/com/android/jill/api/ConfigNotSupportedException.java
diff --git a/base/jack/jill-api/src/main/java/com/android/jill/api/JillConfig.java b/jack/jill-api/src/main/java/com/android/jill/api/JillConfig.java
similarity index 100%
rename from base/jack/jill-api/src/main/java/com/android/jill/api/JillConfig.java
rename to jack/jill-api/src/main/java/com/android/jill/api/JillConfig.java
diff --git a/base/jack/jill-api/src/main/java/com/android/jill/api/JillProvider.java b/jack/jill-api/src/main/java/com/android/jill/api/JillProvider.java
similarity index 100%
rename from base/jack/jill-api/src/main/java/com/android/jill/api/JillProvider.java
rename to jack/jill-api/src/main/java/com/android/jill/api/JillProvider.java
diff --git a/base/jack/jill-api/src/main/java/com/android/jill/api/v01/Api01Config.java b/jack/jill-api/src/main/java/com/android/jill/api/v01/Api01Config.java
similarity index 100%
rename from base/jack/jill-api/src/main/java/com/android/jill/api/v01/Api01Config.java
rename to jack/jill-api/src/main/java/com/android/jill/api/v01/Api01Config.java
diff --git a/base/jack/jill-api/src/main/java/com/android/jill/api/v01/Api01TranslationTask.java b/jack/jill-api/src/main/java/com/android/jill/api/v01/Api01TranslationTask.java
similarity index 100%
rename from base/jack/jill-api/src/main/java/com/android/jill/api/v01/Api01TranslationTask.java
rename to jack/jill-api/src/main/java/com/android/jill/api/v01/Api01TranslationTask.java
diff --git a/base/jack/jill-api/src/main/java/com/android/jill/api/v01/ConfigurationException.java b/jack/jill-api/src/main/java/com/android/jill/api/v01/ConfigurationException.java
similarity index 100%
rename from base/jack/jill-api/src/main/java/com/android/jill/api/v01/ConfigurationException.java
rename to jack/jill-api/src/main/java/com/android/jill/api/v01/ConfigurationException.java
diff --git a/base/jack/jill-api/src/main/java/com/android/jill/api/v01/TranslationException.java b/jack/jill-api/src/main/java/com/android/jill/api/v01/TranslationException.java
similarity index 100%
rename from base/jack/jill-api/src/main/java/com/android/jill/api/v01/TranslationException.java
rename to jack/jill-api/src/main/java/com/android/jill/api/v01/TranslationException.java
diff --git a/jaxb-inheritance-plugin/build.gradle b/jaxb-inheritance-plugin/build.gradle
new file mode 100644
index 0000000..3d6c60c
--- /dev/null
+++ b/jaxb-inheritance-plugin/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'java'
+
+group = 'com.android.tools'
+archivesBaseName = 'jaxb-inheritance-plugin'
+version = 1
+
+dependencies {
+ compile project(':base:annotations')
+ compile 'com.google.guava:guava:17.0'
+ compile 'org.glassfish.jaxb:jaxb-xjc:2.2.11'
+}
diff --git a/jaxb-inheritance-plugin/jaxb-inheritance-plugin.iml b/jaxb-inheritance-plugin/jaxb-inheritance-plugin.iml
new file mode 100644
index 0000000..6255d49
--- /dev/null
+++ b/jaxb-inheritance-plugin/jaxb-inheritance-plugin.iml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="android-annotations" />
+ <orderEntry type="library" name="guava-tools" level="project" />
+ <orderEntry type="library" name="xjc" level="project" />
+ <orderEntry type="module" module-name="repository" />
+ <orderEntry type="module" module-name="sdklib-base" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/jaxb-inheritance-plugin/src/main/java/com/android/jaxb/inheritance/InheritancePlugin.java b/jaxb-inheritance-plugin/src/main/java/com/android/jaxb/inheritance/InheritancePlugin.java
new file mode 100644
index 0000000..f24070c
--- /dev/null
+++ b/jaxb-inheritance-plugin/src/main/java/com/android/jaxb/inheritance/InheritancePlugin.java
@@ -0,0 +1,557 @@
+/*
+ * 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.jaxb.inheritance;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.sun.codemodel.JAnnotationArrayMember;
+import com.sun.codemodel.JArray;
+import com.sun.codemodel.JBlock;
+import com.sun.codemodel.JClass;
+import com.sun.codemodel.JCodeModel;
+import com.sun.codemodel.JDefinedClass;
+import com.sun.codemodel.JExpr;
+import com.sun.codemodel.JExpression;
+import com.sun.codemodel.JFieldVar;
+import com.sun.codemodel.JInvocation;
+import com.sun.codemodel.JMethod;
+import com.sun.codemodel.JMod;
+import com.sun.codemodel.JOp;
+import com.sun.codemodel.JPackage;
+import com.sun.codemodel.JType;
+import com.sun.codemodel.JVar;
+import com.sun.tools.xjc.Options;
+import com.sun.tools.xjc.Plugin;
+import com.sun.tools.xjc.model.CElementPropertyInfo;
+import com.sun.tools.xjc.model.CPluginCustomization;
+import com.sun.tools.xjc.model.CPropertyInfo;
+import com.sun.tools.xjc.model.CTypeInfo;
+import com.sun.tools.xjc.outline.ClassOutline;
+import com.sun.tools.xjc.outline.FieldOutline;
+import com.sun.tools.xjc.outline.Outline;
+import com.sun.xml.xsom.XSComponent;
+import com.sun.xml.xsom.XSElementDecl;
+import com.sun.xml.xsom.XSFacet;
+import com.sun.xml.xsom.XSParticle;
+import com.sun.xml.xsom.XSSimpleType;
+import com.sun.xml.xsom.XSTerm;
+import com.sun.xml.xsom.XSType;
+
+import org.w3c.dom.Element;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.JAXBIntrospector;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.transform.dom.DOMSource;
+
+/**
+ * JAXB plugin to support specifying superclasses/interfaces for generated classes. Behavior is as
+ * follows:<br> When a type declaration in an xsd is annotated as follows:
+ * <pre>{@code
+ * <xsd:schema
+ * ...
+ * xmlns:plugin="http://schemas.android.com/repository/android/plugin/1">
+ *
+ * <xsd:complexType name="myType">
+ * <xsd:annotation>
+ * <xsd:appinfo>
+ * <plugin:super name="my.package.ClassName$InnerClass"/>
+ * </xsd:appinfo>
+ * </xsd:annotation>
+ * </xsd:complexType>
+ * }</pre>
+ *
+ * The generated MyType class will have the given class (in this case {@code
+ * my.package.ClassName$InnerClassName}) specified as the superclass. Method parameter and return
+ * types will also be rewritten to reference supertypes.
+ */
+public class InheritancePlugin extends Plugin {
+
+ // Namespace for tags handled by this plugin
+ private static final String NS = "http://schemas.android.com/android/jaxb/plugin/1";
+
+ // Tag specifying the superclass for a type
+ private static final String SUPER_TAG = "super";
+
+ // We generate our own header comment, since the default one contains a timestamp, which causes
+ // diffs whenever the schema is recompiled (and would cause non-repeatable builds, if the
+ // schema compilation were to be done automatically at build time). It also doesn't contain the
+ // schema filename, which is nice to have for reference.
+ // To suppress the default header, always run xjc with -no-header.
+ private static final String HEADER_COMMENT =
+ "DO NOT EDIT\nThis file was generated by xjc from %1$s. Any changes will be lost " +
+ "upon recompilation of the schema.\nSee the schema file for instructions on " +
+ "running xjc.%2$s";
+
+ @Override
+ public String getOptionName() {
+ return "Xandroid-inheritance";
+ }
+
+ @Override
+ public String getUsage() {
+ return " -Xandroid-inheritance: enable the android inheritance plugin";
+ }
+
+ @Override
+ public boolean isCustomizationTagName(String nsUri, String localName) {
+ return NS.equals(nsUri);
+ }
+
+ @Override
+ public List<String> getCustomizationURIs() {
+ return ImmutableList.of(NS);
+ }
+
+ @Override
+ public boolean run(Outline outline, Options options, ErrorHandler errorHandler)
+ throws SAXException {
+ // TODO: would be nice to support enums
+ assert outline.getEnums().isEmpty();
+
+ // Get the schema filename from the first class (it will be the same for all generated
+ // classes, since we always use episodic compilation.
+ String filename = outline.getClasses().iterator().next().target.getLocator().getSystemId();
+ filename = filename.substring(filename.lastIndexOf('/') + 1);
+
+ // Add package-info header comment
+ JPackage pack = outline.getCodeModel().packages().next();
+ pack.javadoc().add(String.format(HEADER_COMMENT, filename, ""));
+
+ JDefinedClass objFactory = handleObjectFactoryCustomization(outline, filename);
+
+ Map<String, Class> supers = Maps.newHashMap();
+ for (ClassOutline classOutline : outline.getClasses()) {
+ classOutline.implClass.javadoc()
+ .add(0, String.format(HEADER_COMMENT, filename, "\n\n"));
+ acknowledgeCustomizationsOnInherited(classOutline);
+ // xjc has a problem if types referenced in imported schemas aren't referenced
+ // (it seems that there's no good way to mark the customizations as accepted). So we
+ // have to reference everything in a type called XjcWorkaround, which is then hidden
+ // in the generated code.
+ if (classOutline.implClass.name().equals("XjcWorkaround")) {
+ classOutline.implClass.hide();
+ continue;
+ }
+ addAndCollectParents(classOutline, supers);
+ addValidityChecks(classOutline, outline.getCodeModel());
+ }
+
+ JCodeModel model = outline.getCodeModel();
+ List<JMethod> orig = ImmutableList.copyOf(objFactory.methods());
+ for (JMethod method : orig) {
+ convertMethod(supers, model, objFactory, method);
+ }
+
+ for (ClassOutline classOutline : outline.getClasses()) {
+ JAnnotationArrayMember suppress =
+ classOutline.implClass.annotate(SuppressWarnings.class).paramArray("value");
+ // The generated methods optionally have stubs in the superclass. In order to keep
+ // the existance of stubs optional from version to version, and to not have warnings
+ // in the generated code, we suppress "method is override" warnings.
+ suppress.param("override");
+ // Sometimes we generate unsafe casts to allow us to expose a consistent interface.
+ // Suppress those warnings as well.
+ suppress.param("unchecked");
+
+ orig = ImmutableList.copyOf(classOutline.implClass.methods());
+ for (JMethod method : orig) {
+ convertMethod(supers, model, classOutline.implClass, method);
+ }
+
+ // Add a method to each class to create the corresponding ObjectFactory (only if it's a
+ // concrete class, since otherwise overriding methods in other modules will have
+ // clashing return types).
+ if (!classOutline.implClass.isAbstract()) {
+ JMethod getOf = classOutline.implClass
+ .method(JMod.PUBLIC, objFactory, "createFactory");
+ getOf.body()._return(JExpr._new(objFactory));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Converts a method to take superclass information into account. If it takes a parameter of a
+ * type that has a superclass specified, the method is renamed, and then a new method with the
+ * original name is created that takes the superclass type, then casts it and calls the original
+ * method. If it returns a generic type with a parameter with a superclass specified, it is
+ * changed to be of the parent type and casted appropriately before being returned.
+ *
+ * For example, Consider a schema with a type T1Sub which has an element of type T2Sub.
+ * Without customization, xjc would generate a method like
+ * {@code void setT2(T2Sub t)}. If the element is repeated, it would have a method like
+ * {@code List<T2Sub> getT2()}.
+ * Now, we declare T1Sub and T2Sub to be subclasses of T1Super and T2Super respectively, and
+ * will interact with them solely through those classes. In order for this to be possible
+ * we must (unsafely) cast in the generated classes. This method will modify the generated code
+ * to look like:
+ *
+ * <pre><code>
+ * void setT2Internal(T2Sub t) { ... }
+ *
+ * void setT2(T2Super t) {
+ * setT2Internal((T2Sub)t);
+ * }
+ * </code></pre>
+ *
+ * or in the repeated case,
+ *
+ * <pre><code>
+ * List<T2Sub> getT2Internal() { ... }
+ *
+ * List<T2Super> getT2() {
+ * return (List)getT2Internal();
+ * }
+ * </code></pre>
+ */
+ private static void convertMethod(Map<String, Class> supers, JCodeModel model,
+ JDefinedClass definedClass, JMethod method) {
+ JType convertedParamType = null;
+ JType convertedReturnType = null;
+ JType currentParamType = null;
+ if (method.listParamTypes().length > 0) {
+ // Right now there's no way to get a method with more than one param, so that's all we
+ // support.
+ currentParamType = method.listParamTypes()[0];
+ // TODO: better way to do android check
+ if (isAndroid(currentParamType) && (supers.containsKey(currentParamType.fullName()) || (
+ currentParamType instanceof JClass && ((JClass) currentParamType)._extends() != null &&
+ !((JClass) currentParamType)._extends().name().equals("Object")))) {
+ convertedParamType = convertType(currentParamType, supers, model);
+ }
+ }
+ JType currentReturnType = method.type();
+ if (isAndroid(currentReturnType) && currentReturnType.erasure() != currentReturnType
+ && currentReturnType instanceof JClass) {
+ convertedReturnType = convertType(currentReturnType, supers, model);
+ }
+
+ if (convertedParamType != null || convertedReturnType != null) {
+ String name = method.name();
+ // If there are annotations we don't have a good way to copy them over. Make sure the
+ // new method doesn't get picked up by JAXB (which will be confused by the missing
+ // annotations).
+ if (!method.annotations().isEmpty()) {
+ name = name.replace("create", "generate");
+ }
+ method.name(method.name() + "Internal");
+ JMethod newMethod = definedClass.method(JMod.PUBLIC,
+ convertedReturnType == null ? model.VOID : convertedReturnType, name);
+ JBlock body = newMethod.body();
+ JInvocation call = JExpr.invoke(method);
+ if (convertedParamType != null) {
+ JVar paramName = method.listParams()[0];
+ newMethod.param(convertedParamType,
+ paramName.name());
+ call.arg(JExpr.cast(currentParamType, paramName));
+ }
+ if (convertedReturnType != null) {
+ body._return(JExpr.cast(currentReturnType.erasure(), call));
+ }
+ else {
+ body.add(call);
+ }
+ }
+ }
+
+ /**
+ * For the given class, get the superclass customization specified in the xsd. Adds the
+ * specified class/interface as a superclass/implemented interface of this class, and adds
+ * the child to parent mapping to superCollector.
+ */
+ private static void addAndCollectParents(ClassOutline classOutline,
+ Map<String, Class> superCollector)
+ throws SAXException {
+ for (CPluginCustomization c : classOutline.target.getCustomizations()) {
+ Element customizationElement = c.element;
+ if (customizationElement.getNamespaceURI().equals(NS) && customizationElement
+ .getLocalName().equals(SUPER_TAG)) {
+ c.markAsAcknowledged();
+
+ Class superclass = getSuperclass(c);
+
+ if (classOutline.implClass._extends().name().equals("Object") &&
+ !superclass.isInterface()) {
+ classOutline.implClass._extends(superclass);
+ superCollector.put(classOutline.implClass.fullName(), superclass);
+ } else {
+ classOutline.implClass._implements(superclass);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses the superclass customization and returns a {@link Class} instance for the specified
+ * type.
+ */
+ @NonNull
+ private static Class getSuperclass(CPluginCustomization c) throws SAXException {
+ JAXBContext context;
+ Unmarshaller unmarshaller;
+ try {
+ context = JAXBContext.newInstance(SuperClass.class);
+ unmarshaller = context.createUnmarshaller();
+ } catch (Exception e) {
+ // shouldn't ever happen
+ throw new SAXException(e);
+ }
+ Object result;
+ SuperClass superClass;
+ try {
+ result = unmarshaller.unmarshal(new DOMSource(c.element));
+ } catch (JAXBException e) {
+ System.err.println("Couldn't parse superclass element");
+ throw new SAXException(e);
+ }
+ superClass = (SuperClass) JAXBIntrospector.getValue(result);
+ return superClass.getClassInstance();
+ }
+
+ /**
+ * For types used in subclasses, we need to get the type declaration in the imported schema
+ * and mark any customization as accepted. This seems to be because of a bug/design problem
+ * in xjc.
+ */
+ private static void acknowledgeCustomizationsOnInherited(ClassOutline classOutline) {
+ for (FieldOutline field : classOutline.getDeclaredFields()) {
+ for (CTypeInfo ref : field.getPropertyInfo().ref()) {
+ CPluginCustomization c = ref.getCustomizations().find(NS, SUPER_TAG);
+ if (c != null) {
+ c.markAsAcknowledged();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the superclass of the generated ObjectFactory to the type specified in the top-level
+ * customization.
+ */
+ @NonNull
+ private static JDefinedClass handleObjectFactoryCustomization(Outline outline,
+ String schemaFileName)
+ throws SAXException {
+ JAXBContext context;
+ Unmarshaller unmarshaller;
+ try {
+ context = JAXBContext.newInstance(SuperClass.class);
+ unmarshaller = context.createUnmarshaller();
+ } catch (Exception e) {
+ // shouldn't ever happen
+ throw new SAXException(e);
+ }
+
+ String factoryName = outline.getAllPackageContexts().iterator().next()._package().name()
+ + ".ObjectFactory";
+ JDefinedClass objFactory = outline.getCodeModel()._getClass(factoryName);
+ objFactory.annotate(SuppressWarnings.class).param("value", "override");
+ objFactory.javadoc().add(0, String.format(HEADER_COMMENT, schemaFileName, "\n\n"));
+
+
+ // Can't find() the customization, since we'll pick up imported ones as well. So need to
+ // iterate and check the locators to see if the customization came from the same file.
+ for (CPluginCustomization ofCustomization : outline.getModel().getCustomizations()) {
+ Element customizationElement = ofCustomization.element;
+ if (customizationElement.getNamespaceURI().equals(NS) && customizationElement
+ .getLocalName().equals(SUPER_TAG)) {
+ ofCustomization.markAsAcknowledged();
+ ClassOutline aClass = outline.getClasses().iterator().next();
+ if (ofCustomization.locator.getSystemId()
+ .equals(aClass.target.getLocator().getSystemId())) {
+ Object result;
+ SuperClass ofSuperClass;
+ try {
+ result = unmarshaller.unmarshal(new DOMSource(ofCustomization.element));
+ } catch (JAXBException e) {
+ System.err.println("Couldn't parse superclass element");
+ throw new SAXException(e);
+ }
+ ofSuperClass = (SuperClass) JAXBIntrospector.getValue(result);
+ objFactory._extends(ofSuperClass.getClassInstance());
+ }
+ }
+ }
+
+ Iterator<JMethod> methodIterator = objFactory.methods().iterator();
+ while (methodIterator.hasNext()) {
+ JMethod method = methodIterator.next();
+ if (method.name().endsWith("XjcWorkaround")) {
+ methodIterator.remove();
+ break;
+ }
+ }
+ return objFactory;
+ }
+
+ /**
+ * If we have a method taking e.g. Integer, we don't want to convert to the super type.
+ * For now we just check like this; ideally we would check whether it was created in this or
+ * and imported schema.
+ */
+ private static boolean isAndroid(JType type) {
+ return type.fullName().contains("android");
+ }
+
+ /**
+ * Given a type, return either the supertype, or (if it's a generic type), the same generic
+ * with the parameter being the supertype of the original parameter.
+ */
+ private static JType convertType(JType type, Map<String, Class> supers, JCodeModel model) {
+ if (type.erasure() != type && type instanceof JClass) {
+ boolean found = false;
+ List<JClass> newParams = Lists.newArrayList();
+ for (JClass param : ((JClass) type).getTypeParameters()) {
+ JType newType = convertBasicType(param, supers, model);
+ if (!param.equals(newType)) {
+ found = true;
+ }
+ // It must be a class if it's a superclass of type, which is also a class.
+ newParams.add((JClass)newType);
+ }
+ if (found) {
+ return ((JClass) type.erasure()).narrow(newParams);
+ }
+ }
+ return convertBasicType(type, supers, model);
+ }
+
+ private static JType convertBasicType(JType type, Map<String, Class> supers, JCodeModel model) {
+ Class superClass = supers.get(type.fullName());
+ if (superClass != null) {
+ return model.ref(superClass);
+ } else if (type instanceof JClass) {
+ JClass sup = ((JClass)type)._extends();
+ if (sup != null && !sup.fullName().equals(Object.class.getName())) {
+ return sup;
+ }
+ }
+ return type;
+ }
+
+ /**
+ * Adds a method to a class allowing the caller to check whether a string value is valid with
+ * respect to the facets of a field.<br/>
+ * Currently only pattern facets are supported.
+ */
+ private static void addValidityChecks(ClassOutline classOutline, JCodeModel codeModel) {
+ for (FieldOutline field : classOutline.getDeclaredFields()) {
+ CPropertyInfo info = field.getPropertyInfo();
+ XSComponent component = info.getSchemaComponent();
+ List<JExpression> conditions = Lists.newArrayList();
+ if (component instanceof XSParticle) {
+ XSTerm term = ((XSParticle)component).getTerm();
+ if (term instanceof XSElementDecl) {
+ if (((XSElementDecl) term).getDefaultValue() != null) {
+ // If the a default value is specified in the schema, it won't actually be
+ // applied if the element is missing altogether. We must apply it ourselves.
+ JFieldVar fieldRef = classOutline.implClass.fields()
+ .get(info.getName(false));
+ fieldRef.init(JExpr.lit(((XSElementDecl) term).getDefaultValue().value));
+ }
+
+ XSType type = ((XSElementDecl)term).getType();
+ if (type instanceof XSSimpleType) {
+ // TODO: support other facets
+ XSFacet patternFacet = ((XSSimpleType)type).getFacet("pattern");
+ if (patternFacet != null) {
+ String pattern = patternFacet.getValue().toString();
+ conditions.add(JExpr.direct("value.matches(\"^" + pattern + "$\")"));
+ }
+ List<XSFacet> enums = ((XSSimpleType)type).getFacets("enumeration");
+ if (!enums.isEmpty()) {
+ JExpression enumExpr = JExpr.FALSE;
+ for (XSFacet enumVal : enums) {
+ enumExpr = enumExpr.cor(JExpr.direct(
+ "value.equals(\"" + enumVal.getValue().toString() + "\")"));
+ }
+ conditions.add(enumExpr);
+
+ // Also create a "get valid options" method. This allows e.g. a choice
+ // between the valid options to be presented in the UI.
+ JMethod method = classOutline.implClass
+ .method(JMod.PUBLIC, codeModel.ref(String.class).array(),
+ "getValid" + info.getName(true) + "s");
+ JArray arr = JExpr.newArray(codeModel.ref(String.class));
+ for (XSFacet enumVal : enums) {
+ arr.add(JExpr.lit(enumVal.getValue().toString()));
+ }
+ method.body()._return(arr);
+ }
+ }
+ }
+ }
+ if (!conditions.isEmpty()) {
+ JMethod method = classOutline.implClass
+ .method(JMod.PUBLIC, codeModel.BOOLEAN,
+ "isValid" + info.getName(true));
+ method.param(String.class, "value");
+ JBlock body = method.body();
+ JExpression combined;
+ Iterator<JExpression> conditionIter = conditions.iterator();
+ combined = conditionIter.next();
+ while (conditionIter.hasNext()) {
+ combined = JOp.cand(combined, conditionIter.next());
+ }
+
+ if (combined != null) {
+ if (info instanceof CElementPropertyInfo) {
+ if (((CElementPropertyInfo) info).isRequired()) {
+ combined = JOp.cand(JExpr.direct("value != null"), combined);
+ } else {
+ combined = JOp.cor(JExpr.direct("value == null"), combined);
+ }
+ }
+ }
+ body._return(combined);
+ }
+ }
+ }
+
+ /**
+ * Class used for unmarshalling the customizations.
+ */
+ @XmlRootElement(namespace = NS, name = SUPER_TAG)
+ public static class SuperClass {
+
+ @XmlAttribute(required = true, name = "name")
+ public String mName;
+
+ @NonNull
+ public Class getClassInstance() throws SAXException {
+ try {
+ return Thread.currentThread().getContextClassLoader().loadClass(mName);
+ } catch (ClassNotFoundException e) {
+ System.err.println("Superclass " + mName + " not found");
+ throw new SAXException(e);
+ }
+ }
+ }
+}
diff --git a/jaxb-inheritance-plugin/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin b/jaxb-inheritance-plugin/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin
new file mode 100644
index 0000000..c6c06f3
--- /dev/null
+++ b/jaxb-inheritance-plugin/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin
@@ -0,0 +1 @@
+com.android.jaxb.inheritance.InheritancePlugin
diff --git a/base/jobb/.gitignore b/jobb/.gitignore
similarity index 100%
rename from base/jobb/.gitignore
rename to jobb/.gitignore
diff --git a/base/jobb/NOTICE b/jobb/NOTICE
similarity index 100%
rename from base/jobb/NOTICE
rename to jobb/NOTICE
diff --git a/base/jobb/build.gradle b/jobb/build.gradle
similarity index 100%
rename from base/jobb/build.gradle
rename to jobb/build.gradle
diff --git a/base/jobb/etc/jobb b/jobb/etc/jobb
similarity index 100%
rename from base/jobb/etc/jobb
rename to jobb/etc/jobb
diff --git a/base/jobb/etc/jobb.bat b/jobb/etc/jobb.bat
similarity index 100%
rename from base/jobb/etc/jobb.bat
rename to jobb/etc/jobb.bat
diff --git a/base/jobb/jobb.iml b/jobb/jobb.iml
similarity index 100%
rename from base/jobb/jobb.iml
rename to jobb/jobb.iml
diff --git a/jobb/src/main/java/Twofish/Twofish_Algorithm.java b/jobb/src/main/java/Twofish/Twofish_Algorithm.java
new file mode 100644
index 0000000..543a9c4
--- /dev/null
+++ b/jobb/src/main/java/Twofish/Twofish_Algorithm.java
@@ -0,0 +1,836 @@
+// $Id: $
+//
+// $Log: $
+// Revision 1.0 1998/03/24 raif
+// + start of history.
+//
+// $Endlog$
+/*
+ * Copyright (c) 1997, 1998 Systemics Ltd on behalf of
+ * the Cryptix Development Team. All rights reserved.
+ */
+package Twofish;
+
+import java.io.PrintWriter;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+
+//...........................................................................
+/**
+ * Twofish is an AES candidate algorithm. It is a balanced 128-bit Feistel
+ * cipher, consisting of 16 rounds. In each round, a 64-bit S-box value is
+ * computed from 64 bits of the block, and this value is xored into the other
+ * half of the block. The two half-blocks are then exchanged, and the next
+ * round begins. Before the first round, all input bits are xored with key-
+ * dependent "whitening" subkeys, and after the final round the output bits
+ * are xored with other key-dependent whitening subkeys; these subkeys are
+ * not used anywhere else in the algorithm.<p>
+ *
+ * Twofish was submitted by Bruce Schneier, Doug Whiting, John Kelsey, Chris
+ * Hall and David Wagner.<p>
+ *
+ * Reference:<ol>
+ * <li>TWOFISH2.C -- Optimized C API calls for TWOFISH AES submission,
+ * Version 1.00, April 1998, by Doug Whiting.</ol><p>
+ *
+ * <b>Copyright</b> © 1998
+ * <a href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the
+ * <a href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>.
+ * <br>All rights reserved.<p>
+ *
+ * <b>$Revision: $</b>
+ * @author Raif S. Naffah
+ */
+public final class Twofish_Algorithm // implicit no-argument constructor
+{
+// Debugging methods and variables
+//...........................................................................
+
+ static final String NAME = "Twofish_Algorithm";
+ static final boolean IN = true, OUT = false;
+
+ static final boolean DEBUG = Twofish_Properties.GLOBAL_DEBUG;
+ static final int debuglevel = DEBUG ? Twofish_Properties.getLevel(NAME) : 0;
+ static final PrintWriter err = DEBUG ? Twofish_Properties.getOutput() : null;
+
+ static final boolean TRACE = Twofish_Properties.isTraceable(NAME);
+
+ static void debug (String s) { err.println(">>> "+NAME+": "+s); }
+ static void trace (boolean in, String s) {
+ if (TRACE) err.println((in?"==> ":"<== ")+NAME+"."+s);
+ }
+ static void trace (String s) { if (TRACE) err.println("<=> "+NAME+"."+s); }
+
+
+// Constants and variables
+//...........................................................................
+
+ static final int BLOCK_SIZE = 16; // bytes in a data-block
+ private static final int ROUNDS = 16;
+ private static final int MAX_ROUNDS = 16; // max # rounds (for allocating subkeys)
+
+ /* Subkey array indices */
+ private static final int INPUT_WHITEN = 0;
+ private static final int OUTPUT_WHITEN = INPUT_WHITEN + BLOCK_SIZE/4;
+ private static final int ROUND_SUBKEYS = OUTPUT_WHITEN + BLOCK_SIZE/4; // 2*(# rounds)
+
+ private static final int TOTAL_SUBKEYS = ROUND_SUBKEYS + 2*MAX_ROUNDS;
+
+ private static final int SK_STEP = 0x02020202;
+ private static final int SK_BUMP = 0x01010101;
+ private static final int SK_ROTL = 9;
+
+ /** Fixed 8x8 permutation S-boxes */
+ private static final byte[][] P = new byte[][] {
+ { // p0
+ (byte) 0xA9, (byte) 0x67, (byte) 0xB3, (byte) 0xE8,
+ (byte) 0x04, (byte) 0xFD, (byte) 0xA3, (byte) 0x76,
+ (byte) 0x9A, (byte) 0x92, (byte) 0x80, (byte) 0x78,
+ (byte) 0xE4, (byte) 0xDD, (byte) 0xD1, (byte) 0x38,
+ (byte) 0x0D, (byte) 0xC6, (byte) 0x35, (byte) 0x98,
+ (byte) 0x18, (byte) 0xF7, (byte) 0xEC, (byte) 0x6C,
+ (byte) 0x43, (byte) 0x75, (byte) 0x37, (byte) 0x26,
+ (byte) 0xFA, (byte) 0x13, (byte) 0x94, (byte) 0x48,
+ (byte) 0xF2, (byte) 0xD0, (byte) 0x8B, (byte) 0x30,
+ (byte) 0x84, (byte) 0x54, (byte) 0xDF, (byte) 0x23,
+ (byte) 0x19, (byte) 0x5B, (byte) 0x3D, (byte) 0x59,
+ (byte) 0xF3, (byte) 0xAE, (byte) 0xA2, (byte) 0x82,
+ (byte) 0x63, (byte) 0x01, (byte) 0x83, (byte) 0x2E,
+ (byte) 0xD9, (byte) 0x51, (byte) 0x9B, (byte) 0x7C,
+ (byte) 0xA6, (byte) 0xEB, (byte) 0xA5, (byte) 0xBE,
+ (byte) 0x16, (byte) 0x0C, (byte) 0xE3, (byte) 0x61,
+ (byte) 0xC0, (byte) 0x8C, (byte) 0x3A, (byte) 0xF5,
+ (byte) 0x73, (byte) 0x2C, (byte) 0x25, (byte) 0x0B,
+ (byte) 0xBB, (byte) 0x4E, (byte) 0x89, (byte) 0x6B,
+ (byte) 0x53, (byte) 0x6A, (byte) 0xB4, (byte) 0xF1,
+ (byte) 0xE1, (byte) 0xE6, (byte) 0xBD, (byte) 0x45,
+ (byte) 0xE2, (byte) 0xF4, (byte) 0xB6, (byte) 0x66,
+ (byte) 0xCC, (byte) 0x95, (byte) 0x03, (byte) 0x56,
+ (byte) 0xD4, (byte) 0x1C, (byte) 0x1E, (byte) 0xD7,
+ (byte) 0xFB, (byte) 0xC3, (byte) 0x8E, (byte) 0xB5,
+ (byte) 0xE9, (byte) 0xCF, (byte) 0xBF, (byte) 0xBA,
+ (byte) 0xEA, (byte) 0x77, (byte) 0x39, (byte) 0xAF,
+ (byte) 0x33, (byte) 0xC9, (byte) 0x62, (byte) 0x71,
+ (byte) 0x81, (byte) 0x79, (byte) 0x09, (byte) 0xAD,
+ (byte) 0x24, (byte) 0xCD, (byte) 0xF9, (byte) 0xD8,
+ (byte) 0xE5, (byte) 0xC5, (byte) 0xB9, (byte) 0x4D,
+ (byte) 0x44, (byte) 0x08, (byte) 0x86, (byte) 0xE7,
+ (byte) 0xA1, (byte) 0x1D, (byte) 0xAA, (byte) 0xED,
+ (byte) 0x06, (byte) 0x70, (byte) 0xB2, (byte) 0xD2,
+ (byte) 0x41, (byte) 0x7B, (byte) 0xA0, (byte) 0x11,
+ (byte) 0x31, (byte) 0xC2, (byte) 0x27, (byte) 0x90,
+ (byte) 0x20, (byte) 0xF6, (byte) 0x60, (byte) 0xFF,
+ (byte) 0x96, (byte) 0x5C, (byte) 0xB1, (byte) 0xAB,
+ (byte) 0x9E, (byte) 0x9C, (byte) 0x52, (byte) 0x1B,
+ (byte) 0x5F, (byte) 0x93, (byte) 0x0A, (byte) 0xEF,
+ (byte) 0x91, (byte) 0x85, (byte) 0x49, (byte) 0xEE,
+ (byte) 0x2D, (byte) 0x4F, (byte) 0x8F, (byte) 0x3B,
+ (byte) 0x47, (byte) 0x87, (byte) 0x6D, (byte) 0x46,
+ (byte) 0xD6, (byte) 0x3E, (byte) 0x69, (byte) 0x64,
+ (byte) 0x2A, (byte) 0xCE, (byte) 0xCB, (byte) 0x2F,
+ (byte) 0xFC, (byte) 0x97, (byte) 0x05, (byte) 0x7A,
+ (byte) 0xAC, (byte) 0x7F, (byte) 0xD5, (byte) 0x1A,
+ (byte) 0x4B, (byte) 0x0E, (byte) 0xA7, (byte) 0x5A,
+ (byte) 0x28, (byte) 0x14, (byte) 0x3F, (byte) 0x29,
+ (byte) 0x88, (byte) 0x3C, (byte) 0x4C, (byte) 0x02,
+ (byte) 0xB8, (byte) 0xDA, (byte) 0xB0, (byte) 0x17,
+ (byte) 0x55, (byte) 0x1F, (byte) 0x8A, (byte) 0x7D,
+ (byte) 0x57, (byte) 0xC7, (byte) 0x8D, (byte) 0x74,
+ (byte) 0xB7, (byte) 0xC4, (byte) 0x9F, (byte) 0x72,
+ (byte) 0x7E, (byte) 0x15, (byte) 0x22, (byte) 0x12,
+ (byte) 0x58, (byte) 0x07, (byte) 0x99, (byte) 0x34,
+ (byte) 0x6E, (byte) 0x50, (byte) 0xDE, (byte) 0x68,
+ (byte) 0x65, (byte) 0xBC, (byte) 0xDB, (byte) 0xF8,
+ (byte) 0xC8, (byte) 0xA8, (byte) 0x2B, (byte) 0x40,
+ (byte) 0xDC, (byte) 0xFE, (byte) 0x32, (byte) 0xA4,
+ (byte) 0xCA, (byte) 0x10, (byte) 0x21, (byte) 0xF0,
+ (byte) 0xD3, (byte) 0x5D, (byte) 0x0F, (byte) 0x00,
+ (byte) 0x6F, (byte) 0x9D, (byte) 0x36, (byte) 0x42,
+ (byte) 0x4A, (byte) 0x5E, (byte) 0xC1, (byte) 0xE0
+ },
+ { // p1
+ (byte) 0x75, (byte) 0xF3, (byte) 0xC6, (byte) 0xF4,
+ (byte) 0xDB, (byte) 0x7B, (byte) 0xFB, (byte) 0xC8,
+ (byte) 0x4A, (byte) 0xD3, (byte) 0xE6, (byte) 0x6B,
+ (byte) 0x45, (byte) 0x7D, (byte) 0xE8, (byte) 0x4B,
+ (byte) 0xD6, (byte) 0x32, (byte) 0xD8, (byte) 0xFD,
+ (byte) 0x37, (byte) 0x71, (byte) 0xF1, (byte) 0xE1,
+ (byte) 0x30, (byte) 0x0F, (byte) 0xF8, (byte) 0x1B,
+ (byte) 0x87, (byte) 0xFA, (byte) 0x06, (byte) 0x3F,
+ (byte) 0x5E, (byte) 0xBA, (byte) 0xAE, (byte) 0x5B,
+ (byte) 0x8A, (byte) 0x00, (byte) 0xBC, (byte) 0x9D,
+ (byte) 0x6D, (byte) 0xC1, (byte) 0xB1, (byte) 0x0E,
+ (byte) 0x80, (byte) 0x5D, (byte) 0xD2, (byte) 0xD5,
+ (byte) 0xA0, (byte) 0x84, (byte) 0x07, (byte) 0x14,
+ (byte) 0xB5, (byte) 0x90, (byte) 0x2C, (byte) 0xA3,
+ (byte) 0xB2, (byte) 0x73, (byte) 0x4C, (byte) 0x54,
+ (byte) 0x92, (byte) 0x74, (byte) 0x36, (byte) 0x51,
+ (byte) 0x38, (byte) 0xB0, (byte) 0xBD, (byte) 0x5A,
+ (byte) 0xFC, (byte) 0x60, (byte) 0x62, (byte) 0x96,
+ (byte) 0x6C, (byte) 0x42, (byte) 0xF7, (byte) 0x10,
+ (byte) 0x7C, (byte) 0x28, (byte) 0x27, (byte) 0x8C,
+ (byte) 0x13, (byte) 0x95, (byte) 0x9C, (byte) 0xC7,
+ (byte) 0x24, (byte) 0x46, (byte) 0x3B, (byte) 0x70,
+ (byte) 0xCA, (byte) 0xE3, (byte) 0x85, (byte) 0xCB,
+ (byte) 0x11, (byte) 0xD0, (byte) 0x93, (byte) 0xB8,
+ (byte) 0xA6, (byte) 0x83, (byte) 0x20, (byte) 0xFF,
+ (byte) 0x9F, (byte) 0x77, (byte) 0xC3, (byte) 0xCC,
+ (byte) 0x03, (byte) 0x6F, (byte) 0x08, (byte) 0xBF,
+ (byte) 0x40, (byte) 0xE7, (byte) 0x2B, (byte) 0xE2,
+ (byte) 0x79, (byte) 0x0C, (byte) 0xAA, (byte) 0x82,
+ (byte) 0x41, (byte) 0x3A, (byte) 0xEA, (byte) 0xB9,
+ (byte) 0xE4, (byte) 0x9A, (byte) 0xA4, (byte) 0x97,
+ (byte) 0x7E, (byte) 0xDA, (byte) 0x7A, (byte) 0x17,
+ (byte) 0x66, (byte) 0x94, (byte) 0xA1, (byte) 0x1D,
+ (byte) 0x3D, (byte) 0xF0, (byte) 0xDE, (byte) 0xB3,
+ (byte) 0x0B, (byte) 0x72, (byte) 0xA7, (byte) 0x1C,
+ (byte) 0xEF, (byte) 0xD1, (byte) 0x53, (byte) 0x3E,
+ (byte) 0x8F, (byte) 0x33, (byte) 0x26, (byte) 0x5F,
+ (byte) 0xEC, (byte) 0x76, (byte) 0x2A, (byte) 0x49,
+ (byte) 0x81, (byte) 0x88, (byte) 0xEE, (byte) 0x21,
+ (byte) 0xC4, (byte) 0x1A, (byte) 0xEB, (byte) 0xD9,
+ (byte) 0xC5, (byte) 0x39, (byte) 0x99, (byte) 0xCD,
+ (byte) 0xAD, (byte) 0x31, (byte) 0x8B, (byte) 0x01,
+ (byte) 0x18, (byte) 0x23, (byte) 0xDD, (byte) 0x1F,
+ (byte) 0x4E, (byte) 0x2D, (byte) 0xF9, (byte) 0x48,
+ (byte) 0x4F, (byte) 0xF2, (byte) 0x65, (byte) 0x8E,
+ (byte) 0x78, (byte) 0x5C, (byte) 0x58, (byte) 0x19,
+ (byte) 0x8D, (byte) 0xE5, (byte) 0x98, (byte) 0x57,
+ (byte) 0x67, (byte) 0x7F, (byte) 0x05, (byte) 0x64,
+ (byte) 0xAF, (byte) 0x63, (byte) 0xB6, (byte) 0xFE,
+ (byte) 0xF5, (byte) 0xB7, (byte) 0x3C, (byte) 0xA5,
+ (byte) 0xCE, (byte) 0xE9, (byte) 0x68, (byte) 0x44,
+ (byte) 0xE0, (byte) 0x4D, (byte) 0x43, (byte) 0x69,
+ (byte) 0x29, (byte) 0x2E, (byte) 0xAC, (byte) 0x15,
+ (byte) 0x59, (byte) 0xA8, (byte) 0x0A, (byte) 0x9E,
+ (byte) 0x6E, (byte) 0x47, (byte) 0xDF, (byte) 0x34,
+ (byte) 0x35, (byte) 0x6A, (byte) 0xCF, (byte) 0xDC,
+ (byte) 0x22, (byte) 0xC9, (byte) 0xC0, (byte) 0x9B,
+ (byte) 0x89, (byte) 0xD4, (byte) 0xED, (byte) 0xAB,
+ (byte) 0x12, (byte) 0xA2, (byte) 0x0D, (byte) 0x52,
+ (byte) 0xBB, (byte) 0x02, (byte) 0x2F, (byte) 0xA9,
+ (byte) 0xD7, (byte) 0x61, (byte) 0x1E, (byte) 0xB4,
+ (byte) 0x50, (byte) 0x04, (byte) 0xF6, (byte) 0xC2,
+ (byte) 0x16, (byte) 0x25, (byte) 0x86, (byte) 0x56,
+ (byte) 0x55, (byte) 0x09, (byte) 0xBE, (byte) 0x91
+ }
+ };
+
+ /**
+ * Define the fixed p0/p1 permutations used in keyed S-box lookup.
+ * By changing the following constant definitions, the S-boxes will
+ * automatically get changed in the Twofish engine.
+ */
+ private static final int P_00 = 1;
+ private static final int P_01 = 0;
+ private static final int P_02 = 0;
+ private static final int P_03 = P_01 ^ 1;
+ private static final int P_04 = 1;
+
+ private static final int P_10 = 0;
+ private static final int P_11 = 0;
+ private static final int P_12 = 1;
+ private static final int P_13 = P_11 ^ 1;
+ private static final int P_14 = 0;
+
+ private static final int P_20 = 1;
+ private static final int P_21 = 1;
+ private static final int P_22 = 0;
+ private static final int P_23 = P_21 ^ 1;
+ private static final int P_24 = 0;
+
+ private static final int P_30 = 0;
+ private static final int P_31 = 1;
+ private static final int P_32 = 1;
+ private static final int P_33 = P_31 ^ 1;
+ private static final int P_34 = 1;
+
+ /** Primitive polynomial for GF(256) */
+ private static final int GF256_FDBK = 0x169;
+ private static final int GF256_FDBK_2 = 0x169 / 2;
+ private static final int GF256_FDBK_4 = 0x169 / 4;
+
+ /** MDS matrix */
+ private static final int[][] MDS = new int[4][256]; // blank final
+
+ private static final int RS_GF_FDBK = 0x14D; // field generator
+
+ /** data for hexadecimal visualisation. */
+ private static final char[] HEX_DIGITS = {
+ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
+ };
+
+
+// Static code - to intialise the MDS matrix
+//...........................................................................
+
+ static {
+ long time = System.currentTimeMillis();
+
+if (DEBUG && debuglevel > 6) {
+System.out.println("Algorithm Name: "+Twofish_Properties.FULL_NAME);
+System.out.println("Electronic Codebook (ECB) Mode");
+System.out.println();
+}
+ //
+ // precompute the MDS matrix
+ //
+ int[] m1 = new int[2];
+ int[] mX = new int[2];
+ int[] mY = new int[2];
+ int i, j;
+ for (i = 0; i < 256; i++) {
+ j = P[0][i] & 0xFF; // compute all the matrix elements
+ m1[0] = j;
+ mX[0] = Mx_X( j ) & 0xFF;
+ mY[0] = Mx_Y( j ) & 0xFF;
+
+ j = P[1][i] & 0xFF;
+ m1[1] = j;
+ mX[1] = Mx_X( j ) & 0xFF;
+ mY[1] = Mx_Y( j ) & 0xFF;
+
+ MDS[0][i] = m1[P_00] << 0 | // fill matrix w/ above elements
+ mX[P_00] << 8 |
+ mY[P_00] << 16 |
+ mY[P_00] << 24;
+ MDS[1][i] = mY[P_10] << 0 |
+ mY[P_10] << 8 |
+ mX[P_10] << 16 |
+ m1[P_10] << 24;
+ MDS[2][i] = mX[P_20] << 0 |
+ mY[P_20] << 8 |
+ m1[P_20] << 16 |
+ mY[P_20] << 24;
+ MDS[3][i] = mX[P_30] << 0 |
+ m1[P_30] << 8 |
+ mY[P_30] << 16 |
+ mX[P_30] << 24;
+ }
+
+ time = System.currentTimeMillis() - time;
+
+if (DEBUG && debuglevel > 8) {
+System.out.println("==========");
+System.out.println();
+System.out.println("Static Data");
+System.out.println();
+System.out.println("MDS[0][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[0][i*4+j])+", "); System.out.println();}
+System.out.println();
+System.out.println("MDS[1][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[1][i*4+j])+", "); System.out.println();}
+System.out.println();
+System.out.println("MDS[2][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[2][i*4+j])+", "); System.out.println();}
+System.out.println();
+System.out.println("MDS[3][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[3][i*4+j])+", "); System.out.println();}
+System.out.println();
+System.out.println("Total initialization time: "+time+" ms.");
+System.out.println();
+}
+ }
+
+ private static final int LFSR1( int x ) {
+ return (x >> 1) ^
+ ((x & 0x01) != 0 ? GF256_FDBK_2 : 0);
+ }
+
+ private static final int LFSR2( int x ) {
+ return (x >> 2) ^
+ ((x & 0x02) != 0 ? GF256_FDBK_2 : 0) ^
+ ((x & 0x01) != 0 ? GF256_FDBK_4 : 0);
+ }
+
+ private static final int Mx_1( int x ) { return x; }
+ private static final int Mx_X( int x ) { return x ^ LFSR2(x); } // 5B
+ private static final int Mx_Y( int x ) { return x ^ LFSR1(x) ^ LFSR2(x); } // EF
+
+
+// Basic API methods
+//...........................................................................
+
+ /**
+ * Expand a user-supplied key material into a session key.
+ *
+ * @param key The 64/128/192/256-bit user-key to use.
+ * @return This cipher's round keys.
+ * @exception InvalidKeyException If the key is invalid.
+ */
+ public static synchronized Object makeKey (byte[] k)
+ throws InvalidKeyException {
+if (DEBUG) trace(IN, "makeKey("+ Arrays.toString(k) +")");
+ if (k == null)
+ throw new InvalidKeyException("Empty key");
+ int length = k.length;
+ if (!(length == 8 || length == 16 || length == 24 || length == 32))
+ throw new InvalidKeyException("Incorrect key length");
+
+if (DEBUG && debuglevel > 7) {
+System.out.println("Intermediate Session Key Values");
+System.out.println();
+System.out.println("Raw="+toString(k));
+System.out.println();
+}
+ int k64Cnt = length / 8;
+ int subkeyCnt = ROUND_SUBKEYS + 2*ROUNDS;
+ int[] k32e = new int[4]; // even 32-bit entities
+ int[] k32o = new int[4]; // odd 32-bit entities
+ int[] sBoxKey = new int[4];
+ //
+ // split user key material into even and odd 32-bit entities and
+ // compute S-box keys using (12, 8) Reed-Solomon code over GF(256)
+ //
+ int i, j, offset = 0;
+ for (i = 0, j = k64Cnt-1; i < 4 && offset < length; i++, j--) {
+ k32e[i] = (k[offset++] & 0xFF) |
+ (k[offset++] & 0xFF) << 8 |
+ (k[offset++] & 0xFF) << 16 |
+ (k[offset++] & 0xFF) << 24;
+ k32o[i] = (k[offset++] & 0xFF) |
+ (k[offset++] & 0xFF) << 8 |
+ (k[offset++] & 0xFF) << 16 |
+ (k[offset++] & 0xFF) << 24;
+ sBoxKey[j] = RS_MDS_Encode( k32e[i], k32o[i] ); // reverse order
+ }
+ // compute the round decryption subkeys for PHT. these same subkeys
+ // will be used in encryption but will be applied in reverse order.
+ int q, A, B;
+ int[] subKeys = new int[subkeyCnt];
+ for (i = q = 0; i < subkeyCnt/2; i++, q += SK_STEP) {
+ A = F32( k64Cnt, q , k32e ); // A uses even key entities
+ B = F32( k64Cnt, q+SK_BUMP, k32o ); // B uses odd key entities
+ B = B << 8 | B >>> 24;
+ A += B;
+ subKeys[2*i ] = A; // combine with a PHT
+ A += B;
+ subKeys[2*i + 1] = A << SK_ROTL | A >>> (32-SK_ROTL);
+ }
+ //
+ // fully expand the table for speed
+ //
+ int k0 = sBoxKey[0];
+ int k1 = sBoxKey[1];
+ int k2 = sBoxKey[2];
+ int k3 = sBoxKey[3];
+ int b0, b1, b2, b3;
+ int[] sBox = new int[4 * 256];
+ for (i = 0; i < 256; i++) {
+ b0 = b1 = b2 = b3 = i;
+ switch (k64Cnt & 3) {
+ case 1:
+ sBox[ 2*i ] = MDS[0][(P[P_01][b0] & 0xFF) ^ b0(k0)];
+ sBox[ 2*i+1] = MDS[1][(P[P_11][b1] & 0xFF) ^ b1(k0)];
+ sBox[0x200+2*i ] = MDS[2][(P[P_21][b2] & 0xFF) ^ b2(k0)];
+ sBox[0x200+2*i+1] = MDS[3][(P[P_31][b3] & 0xFF) ^ b3(k0)];
+ break;
+ case 0: // same as 4
+ b0 = (P[P_04][b0] & 0xFF) ^ b0(k3);
+ b1 = (P[P_14][b1] & 0xFF) ^ b1(k3);
+ b2 = (P[P_24][b2] & 0xFF) ^ b2(k3);
+ b3 = (P[P_34][b3] & 0xFF) ^ b3(k3);
+ case 3:
+ b0 = (P[P_03][b0] & 0xFF) ^ b0(k2);
+ b1 = (P[P_13][b1] & 0xFF) ^ b1(k2);
+ b2 = (P[P_23][b2] & 0xFF) ^ b2(k2);
+ b3 = (P[P_33][b3] & 0xFF) ^ b3(k2);
+ case 2: // 128-bit keys
+ sBox[ 2*i ] = MDS[0][(P[P_01][(P[P_02][b0] & 0xFF) ^ b0(k1)] & 0xFF) ^ b0(k0)];
+ sBox[ 2*i+1] = MDS[1][(P[P_11][(P[P_12][b1] & 0xFF) ^ b1(k1)] & 0xFF) ^ b1(k0)];
+ sBox[0x200+2*i ] = MDS[2][(P[P_21][(P[P_22][b2] & 0xFF) ^ b2(k1)] & 0xFF) ^ b2(k0)];
+ sBox[0x200+2*i+1] = MDS[3][(P[P_31][(P[P_32][b3] & 0xFF) ^ b3(k1)] & 0xFF) ^ b3(k0)];
+ }
+ }
+
+ Object sessionKey = new Object[] { sBox, subKeys };
+
+if (DEBUG && debuglevel > 7) {
+System.out.println("S-box[]:");
+for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[i*4+j])+", "); System.out.println();}
+System.out.println();
+for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[256+i*4+j])+", "); System.out.println();}
+System.out.println();
+for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[512+i*4+j])+", "); System.out.println();}
+System.out.println();
+for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[768+i*4+j])+", "); System.out.println();}
+System.out.println();
+System.out.println("User (odd, even) keys --> S-Box keys:");
+for(i=0;i<k64Cnt;i++) { System.out.println("0x"+intToString(k32o[i])+" 0x"+intToString(k32e[i])+" --> 0x"+intToString(sBoxKey[k64Cnt-1-i])); }
+System.out.println();
+System.out.println("Round keys:");
+for(i=0;i<ROUND_SUBKEYS + 2*ROUNDS;i+=2) { System.out.println("0x"+intToString(subKeys[i])+" 0x"+intToString(subKeys[i+1])); }
+System.out.println();
+
+}
+if (DEBUG) trace(OUT, "makeKey()");
+ return sessionKey;
+ }
+
+ /**
+ * Encrypt exactly one block of plaintext.
+ *
+ * @param in The plaintext.
+ * @param inOffset Index of in from which to start considering data.
+ * @param sessionKey The session key to use for encryption.
+ * @return The ciphertext generated from a plaintext using the session key.
+ */
+ public static byte[]
+ blockEncrypt (byte[] in, int inOffset, Object sessionKey) {
+if (DEBUG) trace(IN, "blockEncrypt("+Arrays.toString(in)+", "+inOffset+", "+sessionKey+")");
+ Object[] sk = (Object[]) sessionKey; // extract S-box and session key
+ int[] sBox = (int[]) sk[0];
+ int[] sKey = (int[]) sk[1];
+
+if (DEBUG && debuglevel > 6) System.out.println("PT="+toString(in, inOffset, BLOCK_SIZE));
+
+ int x0 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+ int x1 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+ int x2 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+ int x3 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+
+ x0 ^= sKey[INPUT_WHITEN ];
+ x1 ^= sKey[INPUT_WHITEN + 1];
+ x2 ^= sKey[INPUT_WHITEN + 2];
+ x3 ^= sKey[INPUT_WHITEN + 3];
+if (DEBUG && debuglevel > 6) System.out.println("PTw="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
+
+ int t0, t1;
+ int k = ROUND_SUBKEYS;
+ for (int R = 0; R < ROUNDS; R += 2) {
+ t0 = Fe32( sBox, x0, 0 );
+ t1 = Fe32( sBox, x1, 3 );
+ x2 ^= t0 + t1 + sKey[k++];
+ x2 = x2 >>> 1 | x2 << 31;
+ x3 = x3 << 1 | x3 >>> 31;
+ x3 ^= t0 + 2*t1 + sKey[k++];
+if (DEBUG && debuglevel > 6) System.out.println("CT"+(R)+"="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
+
+ t0 = Fe32( sBox, x2, 0 );
+ t1 = Fe32( sBox, x3, 3 );
+ x0 ^= t0 + t1 + sKey[k++];
+ x0 = x0 >>> 1 | x0 << 31;
+ x1 = x1 << 1 | x1 >>> 31;
+ x1 ^= t0 + 2*t1 + sKey[k++];
+if (DEBUG && debuglevel > 6) System.out.println("CT"+(R+1)+"="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
+ }
+ x2 ^= sKey[OUTPUT_WHITEN ];
+ x3 ^= sKey[OUTPUT_WHITEN + 1];
+ x0 ^= sKey[OUTPUT_WHITEN + 2];
+ x1 ^= sKey[OUTPUT_WHITEN + 3];
+if (DEBUG && debuglevel > 6) System.out.println("CTw="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3));
+
+ byte[] result = new byte[] {
+ (byte) x2, (byte)(x2 >>> 8), (byte)(x2 >>> 16), (byte)(x2 >>> 24),
+ (byte) x3, (byte)(x3 >>> 8), (byte)(x3 >>> 16), (byte)(x3 >>> 24),
+ (byte) x0, (byte)(x0 >>> 8), (byte)(x0 >>> 16), (byte)(x0 >>> 24),
+ (byte) x1, (byte)(x1 >>> 8), (byte)(x1 >>> 16), (byte)(x1 >>> 24),
+ };
+
+if (DEBUG && debuglevel > 6) {
+System.out.println("CT="+toString(result));
+System.out.println();
+}
+if (DEBUG) trace(OUT, "blockEncrypt()");
+ return result;
+ }
+
+ /**
+ * Decrypt exactly one block of ciphertext.
+ *
+ * @param in The ciphertext.
+ * @param inOffset Index of in from which to start considering data.
+ * @param sessionKey The session key to use for decryption.
+ * @return The plaintext generated from a ciphertext using the session key.
+ */
+ public static byte[]
+ blockDecrypt (byte[] in, int inOffset, Object sessionKey) {
+if (DEBUG) trace(IN, "blockDecrypt("+Arrays.toString(in)+", "+inOffset+", "+sessionKey+")");
+ Object[] sk = (Object[]) sessionKey; // extract S-box and session key
+ int[] sBox = (int[]) sk[0];
+ int[] sKey = (int[]) sk[1];
+
+if (DEBUG && debuglevel > 6) System.out.println("CT="+toString(in, inOffset, BLOCK_SIZE));
+
+ int x2 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+ int x3 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+ int x0 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+ int x1 = (in[inOffset++] & 0xFF) |
+ (in[inOffset++] & 0xFF) << 8 |
+ (in[inOffset++] & 0xFF) << 16 |
+ (in[inOffset++] & 0xFF) << 24;
+
+ x2 ^= sKey[OUTPUT_WHITEN ];
+ x3 ^= sKey[OUTPUT_WHITEN + 1];
+ x0 ^= sKey[OUTPUT_WHITEN + 2];
+ x1 ^= sKey[OUTPUT_WHITEN + 3];
+if (DEBUG && debuglevel > 6) System.out.println("CTw="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
+
+ int k = ROUND_SUBKEYS + 2*ROUNDS - 1;
+ int t0, t1;
+ for (int R = 0; R < ROUNDS; R += 2) {
+ t0 = Fe32( sBox, x2, 0 );
+ t1 = Fe32( sBox, x3, 3 );
+ x1 ^= t0 + 2*t1 + sKey[k--];
+ x1 = x1 >>> 1 | x1 << 31;
+ x0 = x0 << 1 | x0 >>> 31;
+ x0 ^= t0 + t1 + sKey[k--];
+if (DEBUG && debuglevel > 6) System.out.println("PT"+(ROUNDS-R)+"="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
+
+ t0 = Fe32( sBox, x0, 0 );
+ t1 = Fe32( sBox, x1, 3 );
+ x3 ^= t0 + 2*t1 + sKey[k--];
+ x3 = x3 >>> 1 | x3 << 31;
+ x2 = x2 << 1 | x2 >>> 31;
+ x2 ^= t0 + t1 + sKey[k--];
+if (DEBUG && debuglevel > 6) System.out.println("PT"+(ROUNDS-R-1)+"="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
+ }
+ x0 ^= sKey[INPUT_WHITEN ];
+ x1 ^= sKey[INPUT_WHITEN + 1];
+ x2 ^= sKey[INPUT_WHITEN + 2];
+ x3 ^= sKey[INPUT_WHITEN + 3];
+if (DEBUG && debuglevel > 6) System.out.println("PTw="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1));
+
+ byte[] result = new byte[] {
+ (byte) x0, (byte)(x0 >>> 8), (byte)(x0 >>> 16), (byte)(x0 >>> 24),
+ (byte) x1, (byte)(x1 >>> 8), (byte)(x1 >>> 16), (byte)(x1 >>> 24),
+ (byte) x2, (byte)(x2 >>> 8), (byte)(x2 >>> 16), (byte)(x2 >>> 24),
+ (byte) x3, (byte)(x3 >>> 8), (byte)(x3 >>> 16), (byte)(x3 >>> 24),
+ };
+
+if (DEBUG && debuglevel > 6) {
+System.out.println("PT="+toString(result));
+System.out.println();
+}
+if (DEBUG) trace(OUT, "blockDecrypt()");
+ return result;
+ }
+
+ /** A basic symmetric encryption/decryption test. */
+ public static boolean self_test() { return self_test(BLOCK_SIZE); }
+
+
+// own methods
+//...........................................................................
+
+ private static final int b0( int x ) { return x & 0xFF; }
+ private static final int b1( int x ) { return (x >>> 8) & 0xFF; }
+ private static final int b2( int x ) { return (x >>> 16) & 0xFF; }
+ private static final int b3( int x ) { return (x >>> 24) & 0xFF; }
+
+ /**
+ * Use (12, 8) Reed-Solomon code over GF(256) to produce a key S-box
+ * 32-bit entity from two key material 32-bit entities.
+ *
+ * @param k0 1st 32-bit entity.
+ * @param k1 2nd 32-bit entity.
+ * @return Remainder polynomial generated using RS code
+ */
+ private static final int RS_MDS_Encode( int k0, int k1) {
+ int r = k1;
+ for (int i = 0; i < 4; i++) // shift 1 byte at a time
+ r = RS_rem( r );
+ r ^= k0;
+ for (int i = 0; i < 4; i++)
+ r = RS_rem( r );
+ return r;
+ }
+
+ /*
+ * Reed-Solomon code parameters: (12, 8) reversible code:<p>
+ * <pre>
+ * g(x) = x**4 + (a + 1/a) x**3 + a x**2 + (a + 1/a) x + 1
+ * </pre>
+ * where a = primitive root of field generator 0x14D
+ */
+ private static final int RS_rem( int x ) {
+ int b = (x >>> 24) & 0xFF;
+ int g2 = ((b << 1) ^ ( (b & 0x80) != 0 ? RS_GF_FDBK : 0 )) & 0xFF;
+ int g3 = (b >>> 1) ^ ( (b & 0x01) != 0 ? (RS_GF_FDBK >>> 1) : 0 ) ^ g2 ;
+ int result = (x << 8) ^ (g3 << 24) ^ (g2 << 16) ^ (g3 << 8) ^ b;
+ return result;
+ }
+
+ private static final int F32( int k64Cnt, int x, int[] k32 ) {
+ int b0 = b0(x);
+ int b1 = b1(x);
+ int b2 = b2(x);
+ int b3 = b3(x);
+ int k0 = k32[0];
+ int k1 = k32[1];
+ int k2 = k32[2];
+ int k3 = k32[3];
+
+ int result = 0;
+ switch (k64Cnt & 3) {
+ case 1:
+ result =
+ MDS[0][(P[P_01][b0] & 0xFF) ^ b0(k0)] ^
+ MDS[1][(P[P_11][b1] & 0xFF) ^ b1(k0)] ^
+ MDS[2][(P[P_21][b2] & 0xFF) ^ b2(k0)] ^
+ MDS[3][(P[P_31][b3] & 0xFF) ^ b3(k0)];
+ break;
+ case 0: // same as 4
+ b0 = (P[P_04][b0] & 0xFF) ^ b0(k3);
+ b1 = (P[P_14][b1] & 0xFF) ^ b1(k3);
+ b2 = (P[P_24][b2] & 0xFF) ^ b2(k3);
+ b3 = (P[P_34][b3] & 0xFF) ^ b3(k3);
+ case 3:
+ b0 = (P[P_03][b0] & 0xFF) ^ b0(k2);
+ b1 = (P[P_13][b1] & 0xFF) ^ b1(k2);
+ b2 = (P[P_23][b2] & 0xFF) ^ b2(k2);
+ b3 = (P[P_33][b3] & 0xFF) ^ b3(k2);
+ case 2: // 128-bit keys (optimize for this case)
+ result =
+ MDS[0][(P[P_01][(P[P_02][b0] & 0xFF) ^ b0(k1)] & 0xFF) ^ b0(k0)] ^
+ MDS[1][(P[P_11][(P[P_12][b1] & 0xFF) ^ b1(k1)] & 0xFF) ^ b1(k0)] ^
+ MDS[2][(P[P_21][(P[P_22][b2] & 0xFF) ^ b2(k1)] & 0xFF) ^ b2(k0)] ^
+ MDS[3][(P[P_31][(P[P_32][b3] & 0xFF) ^ b3(k1)] & 0xFF) ^ b3(k0)];
+ break;
+ }
+ return result;
+ }
+
+ private static final int Fe32( int[] sBox, int x, int R ) {
+ return sBox[ 2*_b(x, R ) ] ^
+ sBox[ 2*_b(x, R+1) + 1] ^
+ sBox[0x200 + 2*_b(x, R+2) ] ^
+ sBox[0x200 + 2*_b(x, R+3) + 1];
+ }
+
+ private static final int _b( int x, int N) {
+ int result = 0;
+ switch (N%4) {
+ case 0: result = b0(x); break;
+ case 1: result = b1(x); break;
+ case 2: result = b2(x); break;
+ case 3: result = b3(x); break;
+ }
+ return result;
+ }
+
+ /** @return The length in bytes of the Algorithm input block. */
+ public static int blockSize() { return BLOCK_SIZE; }
+
+ /** A basic symmetric encryption/decryption test for a given key size. */
+ private static boolean self_test (int keysize) {
+if (DEBUG) trace(IN, "self_test("+keysize+")");
+ boolean ok = false;
+ try {
+ byte[] kb = new byte[keysize];
+ byte[] pt = new byte[BLOCK_SIZE];
+ int i;
+
+ for (i = 0; i < keysize; i++)
+ kb[i] = (byte) i;
+ for (i = 0; i < BLOCK_SIZE; i++)
+ pt[i] = (byte) i;
+
+if (DEBUG && debuglevel > 6) {
+System.out.println("==========");
+System.out.println();
+System.out.println("KEYSIZE="+(8*keysize));
+System.out.println("KEY="+toString(kb));
+System.out.println();
+}
+ Object key = makeKey(kb);
+
+if (DEBUG && debuglevel > 6) {
+System.out.println("Intermediate Ciphertext Values (Encryption)");
+System.out.println();
+}
+ byte[] ct = blockEncrypt(pt, 0, key);
+
+if (DEBUG && debuglevel > 6) {
+System.out.println("Intermediate Plaintext Values (Decryption)");
+System.out.println();
+}
+ byte[] cpt = blockDecrypt(ct, 0, key);
+
+ ok = areEqual(pt, cpt);
+ if (!ok)
+ throw new RuntimeException("Symmetric operation failed");
+ } catch (Exception x) {
+if (DEBUG && debuglevel > 0) {
+ debug("Exception encountered during self-test: " + x.getMessage());
+ x.printStackTrace();
+}
+ }
+if (DEBUG && debuglevel > 0) debug("Self-test OK? " + ok);
+if (DEBUG) trace(OUT, "self_test()");
+ return ok;
+ }
+
+
+// utility static methods (from cryptix.util.core ArrayUtil and Hex classes)
+//...........................................................................
+
+ /** @return True iff the arrays have identical contents. */
+ private static boolean areEqual (byte[] a, byte[] b) {
+ int aLength = a.length;
+ if (aLength != b.length)
+ return false;
+ for (int i = 0; i < aLength; i++)
+ if (a[i] != b[i])
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns a string of 8 hexadecimal digits (most significant
+ * digit first) corresponding to the integer <i>n</i>, which is
+ * treated as unsigned.
+ */
+ private static String intToString (int n) {
+ char[] buf = new char[8];
+ for (int i = 7; i >= 0; i--) {
+ buf[i] = HEX_DIGITS[n & 0x0F];
+ n >>>= 4;
+ }
+ return new String(buf);
+ }
+
+ /**
+ * Returns a string of hexadecimal digits from a byte array. Each
+ * byte is converted to 2 hex symbols.
+ */
+ private static String toString (byte[] ba) {
+ return toString(ba, 0, ba.length);
+ }
+ private static String toString (byte[] ba, int offset, int length) {
+ char[] buf = new char[length * 2];
+ for (int i = offset, j = 0, k; i < offset+length; ) {
+ k = ba[i++];
+ buf[j++] = HEX_DIGITS[(k >>> 4) & 0x0F];
+ buf[j++] = HEX_DIGITS[ k & 0x0F];
+ }
+ return new String(buf);
+ }
+
+
+// main(): use to generate the Intermediate Values KAT
+//...........................................................................
+
+ public static void main (String[] args) {
+ self_test(16);
+ self_test(24);
+ self_test(32);
+ }
+}
diff --git a/base/jobb/src/main/java/Twofish/Twofish_Properties.java b/jobb/src/main/java/Twofish/Twofish_Properties.java
similarity index 100%
rename from base/jobb/src/main/java/Twofish/Twofish_Properties.java
rename to jobb/src/main/java/Twofish/Twofish_Properties.java
diff --git a/base/jobb/src/main/java/com/android/jobb/Base64.java b/jobb/src/main/java/com/android/jobb/Base64.java
similarity index 100%
rename from base/jobb/src/main/java/com/android/jobb/Base64.java
rename to jobb/src/main/java/com/android/jobb/Base64.java
diff --git a/base/jobb/src/main/java/com/android/jobb/Encoder.java b/jobb/src/main/java/com/android/jobb/Encoder.java
similarity index 100%
rename from base/jobb/src/main/java/com/android/jobb/Encoder.java
rename to jobb/src/main/java/com/android/jobb/Encoder.java
diff --git a/base/jobb/src/main/java/com/android/jobb/EncryptedBlockFile.java b/jobb/src/main/java/com/android/jobb/EncryptedBlockFile.java
similarity index 100%
rename from base/jobb/src/main/java/com/android/jobb/EncryptedBlockFile.java
rename to jobb/src/main/java/com/android/jobb/EncryptedBlockFile.java
diff --git a/base/jobb/src/main/java/com/android/jobb/Main.java b/jobb/src/main/java/com/android/jobb/Main.java
similarity index 100%
rename from base/jobb/src/main/java/com/android/jobb/Main.java
rename to jobb/src/main/java/com/android/jobb/Main.java
diff --git a/base/jobb/src/main/java/com/android/jobb/ObbFile.java b/jobb/src/main/java/com/android/jobb/ObbFile.java
similarity index 100%
rename from base/jobb/src/main/java/com/android/jobb/ObbFile.java
rename to jobb/src/main/java/com/android/jobb/ObbFile.java
diff --git a/base/jobb/src/main/java/com/android/jobb/PBKDF.java b/jobb/src/main/java/com/android/jobb/PBKDF.java
similarity index 100%
rename from base/jobb/src/main/java/com/android/jobb/PBKDF.java
rename to jobb/src/main/java/com/android/jobb/PBKDF.java
diff --git a/base/layoutlib-api/.classpath b/layoutlib-api/.classpath
similarity index 100%
rename from base/layoutlib-api/.classpath
rename to layoutlib-api/.classpath
diff --git a/base/layoutlib-api/.gitignore b/layoutlib-api/.gitignore
similarity index 100%
rename from base/layoutlib-api/.gitignore
rename to layoutlib-api/.gitignore
diff --git a/base/layoutlib-api/.project b/layoutlib-api/.project
similarity index 100%
rename from base/layoutlib-api/.project
rename to layoutlib-api/.project
diff --git a/base/layoutlib-api/.settings/org.eclipse.jdt.core.prefs b/layoutlib-api/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/layoutlib-api/.settings/org.eclipse.jdt.core.prefs
rename to layoutlib-api/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/layoutlib-api/NOTICE b/layoutlib-api/NOTICE
similarity index 100%
rename from base/layoutlib-api/NOTICE
rename to layoutlib-api/NOTICE
diff --git a/base/layoutlib-api/README.txt b/layoutlib-api/README.txt
similarity index 100%
rename from base/layoutlib-api/README.txt
rename to layoutlib-api/README.txt
diff --git a/base/layoutlib-api/build.gradle b/layoutlib-api/build.gradle
similarity index 100%
rename from base/layoutlib-api/build.gradle
rename to layoutlib-api/build.gradle
diff --git a/base/layoutlib-api/layoutlib-api-base.iml b/layoutlib-api/layoutlib-api-base.iml
similarity index 100%
rename from base/layoutlib-api/layoutlib-api-base.iml
rename to layoutlib-api/layoutlib-api-base.iml
diff --git a/base/layoutlib-api/layoutlib-api.iml b/layoutlib-api/layoutlib-api.iml
similarity index 100%
rename from base/layoutlib-api/layoutlib-api.iml
rename to layoutlib-api/layoutlib-api.iml
diff --git a/base/layoutlib-api/sample/.classpath b/layoutlib-api/sample/.classpath
similarity index 100%
rename from base/layoutlib-api/sample/.classpath
rename to layoutlib-api/sample/.classpath
diff --git a/base/layoutlib-api/sample/.project b/layoutlib-api/sample/.project
similarity index 100%
rename from base/layoutlib-api/sample/.project
rename to layoutlib-api/sample/.project
diff --git a/base/layoutlib-api/sample/README.txt b/layoutlib-api/sample/README.txt
similarity index 100%
rename from base/layoutlib-api/sample/README.txt
rename to layoutlib-api/sample/README.txt
diff --git a/base/layoutlib-api/sample/src/com/example/android/render/Main.java b/layoutlib-api/sample/src/com/example/android/render/Main.java
similarity index 100%
rename from base/layoutlib-api/sample/src/com/example/android/render/Main.java
rename to layoutlib-api/sample/src/com/example/android/render/Main.java
diff --git a/base/layoutlib-api/sample/src/com/example/android/render/ProjectCallback.java b/layoutlib-api/sample/src/com/example/android/render/ProjectCallback.java
similarity index 100%
rename from base/layoutlib-api/sample/src/com/example/android/render/ProjectCallback.java
rename to layoutlib-api/sample/src/com/example/android/render/ProjectCallback.java
diff --git a/base/layoutlib-api/sample/src/com/example/android/render/RenderService.java b/layoutlib-api/sample/src/com/example/android/render/RenderService.java
similarity index 100%
rename from base/layoutlib-api/sample/src/com/example/android/render/RenderService.java
rename to layoutlib-api/sample/src/com/example/android/render/RenderService.java
diff --git a/base/layoutlib-api/sample/src/com/example/android/render/RenderServiceFactory.java b/layoutlib-api/sample/src/com/example/android/render/RenderServiceFactory.java
similarity index 100%
rename from base/layoutlib-api/sample/src/com/example/android/render/RenderServiceFactory.java
rename to layoutlib-api/sample/src/com/example/android/render/RenderServiceFactory.java
diff --git a/base/layoutlib-api/sample/src/com/example/android/render/StdOutLogger.java b/layoutlib-api/sample/src/com/example/android/render/StdOutLogger.java
similarity index 100%
rename from base/layoutlib-api/sample/src/com/example/android/render/StdOutLogger.java
rename to layoutlib-api/sample/src/com/example/android/render/StdOutLogger.java
diff --git a/base/layoutlib-api/sample/src/com/example/android/render/XmlParser.java b/layoutlib-api/sample/src/com/example/android/render/XmlParser.java
similarity index 100%
rename from base/layoutlib-api/sample/src/com/example/android/render/XmlParser.java
rename to layoutlib-api/sample/src/com/example/android/render/XmlParser.java
diff --git a/base/layoutlib-api/sample/testproject/AndroidManifest.xml b/layoutlib-api/sample/testproject/AndroidManifest.xml
similarity index 100%
rename from base/layoutlib-api/sample/testproject/AndroidManifest.xml
rename to layoutlib-api/sample/testproject/AndroidManifest.xml
diff --git a/base/layoutlib-api/sample/testproject/build.properties b/layoutlib-api/sample/testproject/build.properties
similarity index 100%
rename from base/layoutlib-api/sample/testproject/build.properties
rename to layoutlib-api/sample/testproject/build.properties
diff --git a/base/layoutlib-api/sample/testproject/build.xml b/layoutlib-api/sample/testproject/build.xml
similarity index 100%
rename from base/layoutlib-api/sample/testproject/build.xml
rename to layoutlib-api/sample/testproject/build.xml
diff --git a/base/layoutlib-api/sample/testproject/default.properties b/layoutlib-api/sample/testproject/default.properties
similarity index 100%
rename from base/layoutlib-api/sample/testproject/default.properties
rename to layoutlib-api/sample/testproject/default.properties
diff --git a/base/layoutlib-api/sample/testproject/proguard.cfg b/layoutlib-api/sample/testproject/proguard.cfg
similarity index 100%
rename from base/layoutlib-api/sample/testproject/proguard.cfg
rename to layoutlib-api/sample/testproject/proguard.cfg
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-hdpi/icon.png b/layoutlib-api/sample/testproject/res/drawable-hdpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-hdpi/icon.png
rename to layoutlib-api/sample/testproject/res/drawable-hdpi/icon.png
diff --git a/base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-ldpi/icon.png b/layoutlib-api/sample/testproject/res/drawable-ldpi/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/assets/app/src/main/res/drawable-ldpi/icon.png
rename to layoutlib-api/sample/testproject/res/drawable-ldpi/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/dupSet/assets2/icon.png b/layoutlib-api/sample/testproject/res/drawable-mdpi/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/dupSet/assets2/icon.png
rename to layoutlib-api/sample/testproject/res/drawable-mdpi/icon.png
diff --git a/base/layoutlib-api/sample/testproject/res/layout/main.xml b/layoutlib-api/sample/testproject/res/layout/main.xml
similarity index 100%
rename from base/layoutlib-api/sample/testproject/res/layout/main.xml
rename to layoutlib-api/sample/testproject/res/layout/main.xml
diff --git a/base/layoutlib-api/sample/testproject/res/values/strings.xml b/layoutlib-api/sample/testproject/res/values/strings.xml
similarity index 100%
rename from base/layoutlib-api/sample/testproject/res/values/strings.xml
rename to layoutlib-api/sample/testproject/res/values/strings.xml
diff --git a/base/layoutlib-api/sample/testproject/src/com/example/layoutlib/testproject/Main.java b/layoutlib-api/sample/testproject/src/com/example/layoutlib/testproject/Main.java
similarity index 100%
rename from base/layoutlib-api/sample/testproject/src/com/example/layoutlib/testproject/Main.java
rename to layoutlib-api/sample/testproject/src/com/example/layoutlib/testproject/Main.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ActionBarCallback.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ActionBarCallback.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ActionBarCallback.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ActionBarCallback.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AdapterBinding.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AdapterBinding.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AdapterBinding.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AdapterBinding.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ArrayResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ArrayResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ArrayResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ArrayResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AssetRepository.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AssetRepository.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AssetRepository.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AssetRepository.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AttrResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AttrResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AttrResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/AttrResourceValue.java
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java
new file mode 100644
index 0000000..6e50fbf
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010 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.ide.common.rendering.api;
+
+
+import static com.android.ide.common.rendering.api.Result.Status.NOT_IMPLEMENTED;
+
+import com.android.ide.common.rendering.api.Result.Status;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.EnumSet;
+import java.util.Map;
+
+/**
+ * Entry point of the Layout Library. Extensions of this class provide a method to compute
+ * and render a layout.
+ */
+ at SuppressWarnings({"MethodMayBeStatic", "UnusedDeclaration"})
+public abstract class Bridge {
+
+ public static final int API_CURRENT = 17;
+
+ /**
+ * Returns the API level of the layout library.
+ * <p/>
+ * While no methods will ever be removed, some may become deprecated, and some new ones
+ * will appear.
+ * <p/>All Layout libraries based on {@link Bridge} return at minimum an API level of 5.
+ */
+ public abstract int getApiLevel();
+
+ /**
+ * Returns the revision of the library inside a given (layoutlib) API level.
+ * The true revision number of the library is {@link #getApiLevel()}.{@link #getRevision()}
+ */
+ @SuppressWarnings("JavaDoc") // javadoc pointing to itself.
+ public int getRevision() {
+ return 0;
+ }
+
+ /**
+ * Returns an {@link EnumSet} of the supported {@link Capability}.
+ *
+ * @return an {@link EnumSet} with the supported capabilities.
+ *
+ * @deprecated use {@link #supports(int)}
+ */
+ @Deprecated
+ public EnumSet<Capability> getCapabilities() {
+ return EnumSet.noneOf(Capability.class);
+ }
+
+ /**
+ * Returns true if the layout library supports the given feature.
+ *
+ * @see com.android.ide.common.rendering.api.Features
+ */
+ public boolean supports(int feature) {
+ return false;
+ }
+
+ /**
+ * Initializes the Bridge object.
+ *
+ * @param platformProperties The build properties for the platform.
+ * @param fontLocation the location of the fonts.
+ * @param enumValueMap map attrName => { map enumFlagName => Integer value }. This is typically
+ * read from attrs.xml in the SDK target.
+ * @param log a {@link LayoutLog} object. Can be null.
+ * @return true if success.
+ */
+ public boolean init(Map<String, String> platformProperties,
+ File fontLocation,
+ Map<String, Map<String, Integer>> enumValueMap,
+ LayoutLog log) {
+ return false;
+ }
+
+ /**
+ * Prepares the layoutlib to unloaded.
+ */
+ public boolean dispose() {
+ return false;
+ }
+
+ /**
+ * Starts a layout session by inflating and rendering it. The method returns a
+ * {@link RenderSession} on which further actions can be taken.
+ *
+ * @return a new {@link RenderSession} object that contains the result of the scene creation and
+ * first rendering.
+ */
+ public RenderSession createSession(SessionParams params) {
+ return null;
+ }
+
+ /**
+ * Renders a Drawable. If the rendering is successful, the result image is accessible through
+ * {@link Result#getData()}. It is of type {@link BufferedImage}
+ * @param params the rendering parameters.
+ * @return the result of the action.
+ */
+ public Result renderDrawable(DrawableParams params) {
+ return Status.NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Clears the resource cache for a specific project.
+ * <p/>This cache contains bitmaps and nine patches that are loaded from the disk and reused
+ * until this method is called.
+ * <p/>The cache is not configuration dependent and should only be cleared when a
+ * resource changes (at this time only bitmaps and 9 patches go into the cache).
+ * <p/>
+ * The project key provided must be similar to the one passed in {@link RenderParams}.
+ *
+ * @param projectKey the key for the project.
+ */
+ public void clearCaches(Object projectKey) {
+
+ }
+
+ /**
+ * Utility method returning the parent of a given view object.
+ *
+ * @param viewObject the object for which to return the parent.
+ *
+ * @return a {@link Result} indicating the status of the action, and if success, the parent
+ * object in {@link Result#getData()}
+ */
+ public Result getViewParent(Object viewObject) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Utility method returning the index of a given view in its parent.
+ * @param viewObject the object for which to return the index.
+ *
+ * @return a {@link Result} indicating the status of the action, and if success, the index in
+ * the parent in {@link Result#getData()}
+ */
+ public Result getViewIndex(Object viewObject) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Returns true if the character orientation of the locale is right to left.
+ * @param locale The locale formatted as language-region
+ * @return true if the locale is right to left.
+ */
+ public boolean isRtl(String locale) {
+ return false;
+ }
+
+ /**
+ * Utility method returning the baseline value for a given view object. This basically returns
+ * View.getBaseline().
+ *
+ * @param viewObject the object for which to return the index.
+ *
+ * @return the baseline value or -1 if not applicable to the view object or if this layout
+ * library does not implement this method.
+ *
+ * @deprecated use the extended ViewInfo.
+ */
+ @Deprecated
+ public Result getViewBaseline(Object viewObject) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DataBindingItem.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DataBindingItem.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DataBindingItem.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DataBindingItem.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DensityBasedResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DensityBasedResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DensityBasedResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DensityBasedResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DrawableParams.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DrawableParams.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DrawableParams.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DrawableParams.java
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Features.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Features.java
new file mode 100644
index 0000000..5c57c0e
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Features.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.rendering.api;
+
+/**
+ * List of features describing the LayoutLib capabilities.
+ */
+public class Features {
+ /** Ability to render at full size, as required by the layout, and unbound by the screen */
+ public static final int UNBOUND_RENDERING = 0;
+ /** Ability to override the background of the rendering with transparency using
+ * {@link SessionParams#setOverrideBgColor(int)} */
+ public static final int CUSTOM_BACKGROUND_COLOR = 1;
+ /** Ability to call {@link RenderSession#render()} and {@link RenderSession#render(long)}. */
+ public static final int RENDER = 2;
+ /** Ability to ask for a layout only with no rendering through
+ * {@link SessionParams#setLayoutOnly()}
+ */
+ public static final int LAYOUT_ONLY = 3;
+ /**
+ * Ability to control embedded layout parsers through {@link ILayoutPullParser#getParser(String)}
+ */
+ public static final int EMBEDDED_LAYOUT = 4;
+ /** Ability to call<br>
+ * {@link RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)}<br>
+ * {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}<br>
+ * {@link RenderSession#setProperty(Object, String, String)}<br>
+ * The method that receives an animation listener can only use it if the
+ * ANIMATED_VIEW_MANIPULATION, or FULL_ANIMATED_VIEW_MANIPULATION is also supported.
+ */
+ public static final int VIEW_MANIPULATION = 5;
+ /** Ability to play animations with<br>
+ * {@link RenderSession#animate(Object, String, boolean, IAnimationListener)}
+ */
+ public static final int PLAY_ANIMATION = 6;
+ /**
+ * Ability to manipulate views with animation, as long as the view does not change parent.
+ * {@link RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)}<br>
+ * {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}<br>
+ * {@link RenderSession#removeChild(Object, IAnimationListener)}<br>
+ */
+ public static final int ANIMATED_VIEW_MANIPULATION = 7;
+ /**
+ * Ability to move views (even into a different ViewGroup) with animation.
+ * see {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}
+ */
+ public static final int FULL_ANIMATED_VIEW_MANIPULATION = 7;
+ public static final int ADAPTER_BINDING = 8;
+ public static final int EXTENDED_VIEWINFO = 9;
+ /**
+ * Ability to properly resize nine-patch assets.
+ */
+ public static final int FIXED_SCALABLE_NINE_PATCH = 10;
+ /**
+ * Ability to render RTL layouts.
+ */
+ public static final int RTL = 11;
+ /**
+ * Ability to render ActionBar.
+ */
+ public static final int ACTION_BAR = 12;
+ /**
+ * Ability to simulate older Platform Versions.
+ * <p/>
+ * This is the last feature supported by API 12.
+ */
+ public static final int SIMULATE_PLATFORM = 13;
+ /**
+ * All features before this map to the ones in {@link Capability}. Any feature greater than this
+ * is guaranteed to be not supported by a LayoutLib using the older api.
+ */
+ public static final int LAST_CAPABILITY = SIMULATE_PLATFORM;
+ /**
+ * Ability to render preferences.
+ */
+ public static final int PREFERENCES_RENDERING = 14;
+ /**
+ * Ability to render all states of a StateListDrawable and return all in a
+ * single call.
+ */
+ public static final int RENDER_ALL_DRAWABLE_STATES = 15;
+ /**
+ * Ability to use custom layouts for RecyclerView$Adapter.
+ */
+ public static final int RECYCLER_VIEW_ADAPTER = 16;
+ /**
+ * Ability to set system time.
+ * {@link RenderSession#setElapsedFrameTimeNanos(long)}
+ * {@link RenderSession#setSystemBootTimeNanos(long)}
+ * {@link RenderSession#setSystemTimeNanos(long)}
+ */
+ public static final int SYSTEM_TIME = 17;
+ /**
+ * Ability to use choreographer animations.
+ */
+ public static final int CHOREOGRAPHER = 18;
+ /**
+ * Ability to use a layout specific to the Theme Editor Preview for the navigation bar.
+ */
+ public static final int THEME_PREVIEW_NAVIGATION_BAR = 19;
+ /**
+ * Last known feature.
+ * <p/>
+ * This should be avoided on the LayoutLib since, since using this makes updating the API used
+ * by the LayoutLib without implementing any newly added features.
+ */
+ public static final int LAST_FEATURE = THEME_PREVIEW_NAVIGATION_BAR;
+}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/HardwareConfig.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/HardwareConfig.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/HardwareConfig.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/HardwareConfig.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IAnimationListener.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IAnimationListener.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IAnimationListener.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IAnimationListener.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IImageFactory.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IImageFactory.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IImageFactory.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IImageFactory.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ILayoutPullParser.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ILayoutPullParser.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ILayoutPullParser.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ILayoutPullParser.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/IProjectCallback.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ItemResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ItemResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ItemResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ItemResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutlibCallback.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutlibCallback.java
new file mode 100644
index 0000000..c4dc927
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutlibCallback.java
@@ -0,0 +1,134 @@
+/*
+ * 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.ide.common.rendering.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.intellij.lang.annotations.MagicConstant;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Intermediary class implementing parts of both the old and new ProjectCallback from the
+ * LayoutLib API.
+ * <p/>
+ * Even newer LayoutLibs use this directly instead of the the interface. This allows the flexibility
+ * to add newer methods without having to update {@link Bridge#API_CURRENT LayoutLib API version}.
+ * <p/>
+ * Clients should use this instead of {@link IProjectCallback} to target both old and new
+ * Layout Libraries.
+ */
+ at SuppressWarnings({"deprecation", "MethodMayBeStatic", "unused"})
+public abstract class LayoutlibCallback implements IProjectCallback,
+ com.android.layoutlib.api.IProjectCallback {
+
+ /**
+ * Like {@link #loadView(String, Class[], Object[])}, but intended for loading classes that may
+ * not be custom views.
+ *
+ * @param name className in binary format (see {@link ClassLoader})
+ * @return an new instance created by calling the given constructor.
+ * @throws ClassNotFoundException any exceptions thrown when creating the instance is wrapped in
+ * ClassNotFoundException.
+ * @since API 15
+ */
+ public Object loadClass(@NonNull String name, @Nullable Class[] constructorSignature,
+ @Nullable Object[] constructorArgs) throws ClassNotFoundException {
+ try {
+ return loadView(name, constructorSignature, constructorArgs);
+ }
+ catch (ClassNotFoundException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new ClassNotFoundException(name + " not found.", e);
+ }
+ }
+
+ /**
+ * Returns if the IDE supports the requested feature.
+ * @see Features
+ * @since API 15
+ */
+ public abstract boolean supports(
+ @MagicConstant(valuesFromClass = Features.class) int ideFeature);
+
+ /**
+ * A callback to query arbitrary data. This is similar to {@link RenderParams#setFlag(SessionParams.Key,
+ * Object)}. The main difference is that when using this, the IDE doesn't have to compute the
+ * value in advance and thus may save on some computation.
+ * @since API 15
+ */
+ @Nullable
+ public <T> T getFlag(@NonNull SessionParams.Key<T> key) {
+ return null;
+ }
+
+ /**
+ * Get a ParserFactory which can be used to create XmlPullParsers.
+ * @since API 15
+ */
+ @NonNull
+ public ParserFactory getParserFactory() {
+ throw new UnsupportedOperationException("getParserFactory not supported.");
+ }
+
+ /**
+ * Find a custom class in the project.
+ * <p/>
+ * Like {@link #loadClass(String, Class[], Object[])}, but doesn't instantiate
+ * an object and just returns the class found.
+ * @param name className in binary format. (see {@link ClassLoader}.
+ * @since API 15
+ */
+ @NonNull
+ public Class<?> findClass(@NonNull String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException(name + " not found.");
+ }
+
+ /**
+ * Returns an {@link XmlPullParser} for the psi version of an xml file
+ * @param fileName name of the file to parse
+ */
+ @Nullable
+ public XmlPullParser getXmlFileParser(String fileName) {
+ return null;
+ }
+
+ // ------ implementation of the old interface using the new interface.
+
+ @Override
+ public final Integer getResourceValue(String type, String name) {
+ return getResourceId(ResourceType.getEnum(type), name);
+ }
+
+ @Override
+ public final String[] resolveResourceValue(int id) {
+ Pair<ResourceType, String> info = resolveResourceId(id);
+ if (info != null) {
+ return new String[] { info.getSecond(), info.getFirst().getName() };
+ }
+
+ return null;
+ }
+
+ @Override
+ public final String resolveResourceValue(int[] id) {
+ return resolveResourceId(id);
+ }
+}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/MergeCookie.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/MergeCookie.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/MergeCookie.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/MergeCookie.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ParserFactory.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ParserFactory.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ParserFactory.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ParserFactory.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/PluralsResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/PluralsResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/PluralsResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/PluralsResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderResources.java
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java
new file mode 100644
index 0000000..38ed896
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderSession.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2010 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.ide.common.rendering.api;
+
+import static com.android.ide.common.rendering.api.Result.Status.NOT_IMPLEMENTED;
+
+import com.android.ide.common.rendering.api.Result.Status;
+
+import java.awt.image.BufferedImage;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An object allowing interaction with an Android layout.
+ *
+ * This is returned by {@link Bridge#createSession(SessionParams)}.
+ * and can then be used for subsequent actions on the layout.
+ *
+ * @since 5
+ *
+ */
+public class RenderSession {
+
+ /**
+ * Returns the last operation result.
+ */
+ public Result getResult() {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Returns the {@link ViewInfo} objects for the top level views.
+ * <p/>
+ * It contains {@code ViewInfo} for only the views in the layout. For {@code ViewInfo} of the
+ * System UI surrounding the layout use {@link #getSystemRootViews()}. In most cases the list
+ * will only contain one item. If the top level node is a {@code merge} though then it will
+ * contain all the items under the {@code merge} tag.
+ * <p/>
+ * This is reset to a new instance every time {@link #render()} is called and can be
+ * <code>null</code> if the call failed (and the method returned a {@link Result} with
+ * {@link Status#ERROR_UNKNOWN} or {@link Status#NOT_IMPLEMENTED}.
+ * <p/>
+ * This can be safely modified by the caller, but {@code #getSystemRootViews} and
+ * {@code #getRootViews} share some view infos, so modifying one result can affect the other.
+ *
+ * @return the list of {@link ViewInfo} or null if there aren't any.
+ *
+ * @see #getSystemRootViews()
+ */
+ public List<ViewInfo> getRootViews() {
+ return null;
+ }
+
+ /**
+ * Returns the {@link ViewInfo} objects for the system decor views, like the ActionBar.
+ * <p/>
+ * This is reset to a new instance every time {@link #render()} is called and can be
+ * <code>null</code> if the call failed, or there was no system decor.
+ * <p/>
+ * This can be safely modified by the caller, but {@code #getSystemRootViews} and
+ * {@code #getRootViews} share some view infos, so modifying one result can affect the other.
+ *
+ * @return the list of {@link ViewInfo} or null if there aren't any.
+ */
+ public List<ViewInfo> getSystemRootViews() {
+ return null;
+ }
+
+ /**
+ * Returns the rendering of the full layout.
+ * <p>
+ * This is reset to a new instance every time {@link #render()} is called and can be
+ * <code>null</code> if the call failed (and the method returned a {@link Result} with
+ * {@link Status#ERROR_UNKNOWN} or {@link Status#NOT_IMPLEMENTED}.
+ * <p/>
+ * This can be safely modified by the caller.
+ */
+ public BufferedImage getImage() {
+ return null;
+ }
+
+ /**
+ * Returns true if the current image alpha channel is relevant.
+ *
+ * @return whether the image alpha channel is relevant.
+ */
+ public boolean isAlphaChannelImage() {
+ return true;
+ }
+
+ /**
+ * Returns a map of (XML attribute name, attribute value) containing only default attribute
+ * values, for the given view Object.
+ * @param viewObject the view object.
+ * @return a map of the default property values or null.
+ */
+ public Map<String, String> getDefaultProperties(Object viewObject) {
+ return null;
+ }
+
+ /**
+ * Re-renders the layout as-is.
+ * In case of success, this should be followed by calls to {@link #getRootViews()} and
+ * {@link #getImage()} to access the result of the rendering.
+ *
+ * This is equivalent to calling <code>render(SceneParams.DEFAULT_TIMEOUT)</code>
+ *
+ * @return a {@link Result} indicating the status of the action.
+ */
+ public Result render() {
+ return render(RenderParams.DEFAULT_TIMEOUT);
+ }
+
+ /**
+ * Re-renders the layout as-is, with a given timeout in case other renderings are being done.
+ * In case of success, this should be followed by calls to {@link #getRootViews()} and
+ * {@link #getImage()} to access the result of the rendering.
+ *
+ * The {@link Bridge} is only able to inflate or render one layout at a time. There
+ * is an internal lock object whenever such an action occurs. The timeout parameter is used
+ * when attempting to acquire the lock. If the timeout expires, the method will return
+ * {@link Status#ERROR_TIMEOUT}.
+ *
+ * @param timeout timeout for the rendering, in milliseconds.
+ *
+ * @return a {@link Result} indicating the status of the action.
+ */
+ public Result render(long timeout) {
+ return render(timeout, false);
+ }
+
+ /**
+ * Re-renders the layout as-is, with a given timeout in case other renderings are being done.
+ * In case of success, this should be followed by calls to {@link #getRootViews()} and
+ * {@link #getImage()} to access the result of the rendering.
+ * This call also allows triggering a forced measure.
+ *
+ * The {@link Bridge} is only able to inflate or render one layout at a time. There
+ * is an internal lock object whenever such an action occurs. The timeout parameter is used
+ * when attempting to acquire the lock. If the timeout expires, the method will return
+ * {@link Status#ERROR_TIMEOUT}.
+ *
+ * @param timeout timeout for the rendering, in milliseconds.
+ * @param forceMeasure force running measure for the layout.
+ *
+ * @return a {@link Result} indicating the status of the action.
+ */
+ public Result render(long timeout, boolean forceMeasure) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Sets the value of a given property on a given object.
+ * <p/>
+ * This does nothing more than change the property. To render the scene in its new state, a
+ * call to {@link #render()} is required.
+ * <p/>
+ * Any amount of actions can be taken on the scene before {@link #render()} is called.
+ *
+ * @param objectView
+ * @param propertyName
+ * @param propertyValue
+ *
+ * @return a {@link Result} indicating the status of the action.
+ *
+ * @throws IllegalArgumentException if the view object is not an android.view.View
+ */
+ public Result setProperty(Object objectView, String propertyName, String propertyValue) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * returns the value of a given property on a given object.
+ * <p/>
+ * This returns a {@link Result} object. If the operation of querying the object for its
+ * property was successful (check {@link Result#isSuccess()}), then the property value
+ * is set in the result and can be accessed through {@link Result#getData()}.
+ *
+ * @param objectView
+ * @param propertyName
+ *
+ * @return a {@link Result} indicating the status of the action.
+ *
+ * @throws IllegalArgumentException if the view object is not an android.view.View
+ */
+ public Result getProperty(Object objectView, String propertyName) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Inserts a new child in a ViewGroup object, and renders the result.
+ * <p/>
+ * The child is first inflated and then added to its new parent, at the given <var>index<var>
+ * position. If the <var>index</var> is -1 then the child is added at the end of the parent.
+ * <p/>
+ * If an animation listener is passed then the rendering is done asynchronously and the
+ * result is sent to the listener.
+ * If the listener is null, then the rendering is done synchronously.
+ * <p/>
+ * The child stays in the view hierarchy after the rendering is done. To remove it call
+ * {@link #removeChild(Object, IAnimationListener)}
+ * <p/>
+ * The returned {@link Result} object will contain the android.view.View object for
+ * the newly inflated child. It is accessible through {@link Result#getData()}.
+ *
+ * @param parentView the parent View object to receive the new child.
+ * @param childXml an {@link ILayoutPullParser} containing the content of the new child,
+ * including ViewGroup.LayoutParams attributes.
+ * @param index the index at which position to add the new child into the parent. -1 means at
+ * the end.
+ * @param listener an optional {@link IAnimationListener}.
+ *
+ * @return a {@link Result} indicating the status of the action.
+ */
+ public Result insertChild(Object parentView, ILayoutPullParser childXml, int index,
+ IAnimationListener listener) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Move a new child to a different ViewGroup object.
+ * <p/>
+ * The child is first removed from its current parent, and then added to its new parent, at the
+ * given <var>index<var> position. In case the <var>parentView</var> is the current parent of
+ * <var>childView</var> then the index must be the value with the <var>childView</var> removed
+ * from its parent. If the <var>index</var> is -1 then the child is added at the end of
+ * the parent.
+ * <p/>
+ * If an animation listener is passed then the rendering is done asynchronously and the
+ * result is sent to the listener.
+ * If the listener is null, then the rendering is done synchronously.
+ * <p/>
+ * The child stays in the view hierarchy after the rendering is done. To remove it call
+ * {@link #removeChild(Object, IAnimationListener)}
+ * <p/>
+ * The returned {@link Result} object will contain the android.view.ViewGroup.LayoutParams
+ * object created from the <var>layoutParams</var> map if it was non <code>null</code>.
+ *
+ * @param parentView the parent View object to receive the child. Can be the current parent
+ * already.
+ * @param childView the view to move.
+ * @param index the index at which position to add the new child into the parent. -1 means at
+ * the end.
+ * @param layoutParams an optional map of new ViewGroup.LayoutParams attribute. If non null,
+ * then the current layout params of the view will be removed and a new one will
+ * be inflated and set with the content of the map.
+ * @param listener an optional {@link IAnimationListener}.
+ *
+ * @return a {@link Result} indicating the status of the action.
+ */
+ public Result moveChild(Object parentView, Object childView, int index,
+ Map<String, String> layoutParams, IAnimationListener listener) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Removes a child from a ViewGroup object.
+ * <p/>
+ * This does nothing more than change the layout. To render the scene in its new state, a
+ * call to {@link #render()} is required.
+ * <p/>
+ * Any amount of actions can be taken on the scene before {@link #render()} is called.
+ *
+ * @param childView the view object to remove from its parent
+ * @param listener an optional {@link IAnimationListener}.
+ *
+ * @return a {@link Result} indicating the status of the action.
+ */
+ public Result removeChild(Object childView, IAnimationListener listener) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Starts playing an given animation on a given object.
+ * <p/>
+ * The animation playback is asynchronous and the rendered frame is sent vi the
+ * <var>listener</var>.
+ *
+ * @param targetObject the view object to animate
+ * @param animationName the name of the animation (res/anim) to play.
+ * @param listener the listener callback.
+ *
+ * @return a {@link Result} indicating the status of the action.
+ */
+ public Result animate(Object targetObject, String animationName,
+ boolean isFrameworkAnimation, IAnimationListener listener) {
+ return NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Sets the current system time in nanos.
+ * <p/>Calls to this method must check that layoutlib supports {@link Features#SYSTEM_TIME}
+ */
+ public void setSystemTimeNanos(long nanos) {
+ throw new UnsupportedOperationException(
+ "Current layoutlib version doesn't support Features.SYSTEM_TIME");
+ }
+
+ /**
+ * Sets the system boot time in nanos.
+ * <p/>Calls to this method must check that layoutlib supports {@link Features#SYSTEM_TIME}
+ */
+ public void setSystemBootTimeNanos(long nanos) {
+ throw new UnsupportedOperationException(
+ "Current layoutlib version doesn't support Features.SYSTEM_TIME");
+ }
+
+ /**
+ * Sets the time for which the next frame will be selected. The time is the elapsed time from
+ * the current system nanos time.
+ * <p/>Calls to this method must check that layoutlib supports {@link Features#SYSTEM_TIME}
+ */
+ public void setElapsedFrameTimeNanos(long nanos) {
+ throw new UnsupportedOperationException(
+ "Current layoutlib version doesn't support Features.SYSTEM_TIME");
+ }
+
+ /**
+ * Discards the layout. No more actions can be called on this object.
+ */
+ public void dispose() {
+ }
+}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceReference.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceReference.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceReference.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceReference.java
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java
new file mode 100644
index 0000000..04be6e1
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ResourceValue.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2008 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.ide.common.rendering.api;
+
+import com.android.annotations.Nullable;
+import com.android.layoutlib.api.IResourceValue;
+import com.android.resources.ResourceType;
+
+/**
+ * Represents an android resource with a name and a string value.
+ */
+ at SuppressWarnings("deprecation")
+public class ResourceValue extends ResourceReference implements IResourceValue {
+ private final ResourceType mType;
+ protected String mValue;
+
+ public ResourceValue(ResourceType type, String name, boolean isFramework) {
+ super(name, isFramework);
+ mType = type;
+ }
+
+ public ResourceValue(ResourceType type, String name, String value, boolean isFramework) {
+ super(name, isFramework);
+ mType = type;
+ mValue = value;
+ }
+
+ public ResourceType getResourceType() {
+ return mType;
+ }
+
+ /**
+ * Returns the type of the resource. For instance "drawable", "color", etc...
+ * @deprecated use {@link #getResourceType()} instead.
+ */
+ @Override
+ @Deprecated
+ public String getType() {
+ return mType.getName();
+ }
+
+ /**
+ * Returns the value of the resource, as defined in the XML. This can be <code>null</code>,
+ * for example for instances of {@link StyleResourceValue}.
+ */
+ @Override
+ @Nullable
+ public String getValue() {
+ return mValue;
+ }
+
+ /**
+ * Similar to {@link #getValue()}, but returns the raw XML value. This is <b>usually</b>
+ * the same as getValue, but with a few exceptions. For example, for markup strings,
+ * you can have * {@code <string name="markup">This is <b>bold</b></string>}.
+ * Here, {@link #getValue()} will return "{@code This is bold}" -- e.g. just
+ * the plain text flattened. However, this method will return "{@code This is <b>bold</b>}",
+ * which preserves the XML markup elements.
+ */
+ public String getRawXmlValue() {
+ return getValue();
+ }
+
+ /**
+ * Sets the value of the resource.
+ * @param value the new value
+ */
+ public void setValue(String value) {
+ mValue = value;
+ }
+
+ /**
+ * Sets the value from another resource.
+ * @param value the resource value
+ */
+ public void replaceWith(ResourceValue value) {
+ mValue = value.mValue;
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceValue [" + mType + "/" + getName() + " = " + mValue //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ + " (framework:" + isFramework() + ")]"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mType == null) ? 0 : mType.hashCode());
+ result = prime * result + ((mValue == null) ? 0 : mValue.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ResourceValue other = (ResourceValue) obj;
+ if (mType == null) {
+ //noinspection VariableNotUsedInsideIf
+ if (other.mType != null)
+ return false;
+ } else if (!mType.equals(other.mType))
+ return false;
+ if (mValue == null) {
+ //noinspection VariableNotUsedInsideIf
+ if (other.mValue != null)
+ return false;
+ } else if (!mValue.equals(other.mValue))
+ return false;
+ return true;
+ }
+}
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Result.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Result.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Result.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Result.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/SessionParams.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/StyleResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/TextResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/TextResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/TextResourceValue.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/TextResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewInfo.java
diff --git a/base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewType.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewType.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewType.java
rename to layoutlib-api/src/main/java/com/android/ide/common/rendering/api/ViewType.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/IDensityBasedResourceValue.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/IDensityBasedResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/IDensityBasedResourceValue.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/IDensityBasedResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutBridge.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutBridge.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutBridge.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutBridge.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutLog.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutLog.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutLog.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutLog.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutResult.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutResult.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutResult.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/ILayoutResult.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/IProjectCallback.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/IProjectCallback.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/IProjectCallback.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/IProjectCallback.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/IResourceValue.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/IResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/IResourceValue.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/IResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/IStyleResourceValue.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/IStyleResourceValue.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/IStyleResourceValue.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/IStyleResourceValue.java
diff --git a/base/layoutlib-api/src/main/java/com/android/layoutlib/api/IXmlPullParser.java b/layoutlib-api/src/main/java/com/android/layoutlib/api/IXmlPullParser.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/layoutlib/api/IXmlPullParser.java
rename to layoutlib-api/src/main/java/com/android/layoutlib/api/IXmlPullParser.java
diff --git a/layoutlib-api/src/main/java/com/android/resources/Density.java b/layoutlib-api/src/main/java/com/android/resources/Density.java
new file mode 100644
index 0000000..5d5bcd2
--- /dev/null
+++ b/layoutlib-api/src/main/java/com/android/resources/Density.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010 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.resources;
+
+
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Density enum.
+ * <p/>This is used in the manifest in the uses-configuration node and in the resource folder names
+ * as well as other places needing to know the density values.
+ */
+public enum Density implements ResourceEnum {
+ XXXHIGH("xxxhdpi", "XXX-High Density", 640, 18), //$NON-NLS-1$
+ DPI_560("560dpi", "560 DPI Density", 560, 1), //$NON-NLS-1$
+ XXHIGH( "xxhdpi", "XX-High Density", 480, 16), //$NON-NLS-1$
+ DPI_420("420dpi", "420 DPI Density", 420, 23), //$NON-NLS-1$
+ DPI_400("400dpi", "400 DPI Density", 400, 1), //$NON-NLS-1$
+ DPI_360("360dpi", "360 DPI Density", 360, 23), //$NON-NLS-1$
+ XHIGH( "xhdpi", "X-High Density", 320, 8), //$NON-NLS-1$
+ DPI_280("280dpi", "280 DPI Density", 280, 22), //$NON-NLS-1$
+ HIGH( "hdpi", "High Density", 240, 4), //$NON-NLS-1$
+ TV( "tvdpi", "TV Density", 213, 13), //$NON-NLS-1$
+ MEDIUM( "mdpi", "Medium Density", 160, 4), //$NON-NLS-1$
+ LOW( "ldpi", "Low Density", 120, 4), //$NON-NLS-1$
+ ANYDPI( "anydpi", "Any Density", 0, 21), //$NON-NLS-1$
+ NODPI( "nodpi", "No Density", 0, 4); //$NON-NLS-1$
+
+ public static final int DEFAULT_DENSITY = 160;
+
+ private final String mValue;
+ private final String mDisplayValue;
+ private final int mDensity;
+ private final int mSince;
+
+ Density(String value, String displayValue, int density, int since) {
+ mValue = value;
+ mDisplayValue = displayValue;
+ mDensity = density;
+ mSince = since;
+ }
+
+ /**
+ * Returns the enum matching the provided qualifier value.
+ * @param value The qualifier value.
+ * @return the enum for the qualifier value or null if no match was found.
+ */
+ public static Density getEnum(String value) {
+ for (Density orient : values()) {
+ if (orient.mValue.equals(value)) {
+ return orient;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the enum matching the given density value
+ * @param value The density value.
+ * @return the enum for the density value or null if no match was found.
+ */
+ public static Density getEnum(int value) {
+ for (Density d : values()) {
+ if (d.mDensity == value) {
+ return d;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getResourceValue() {
+ return mValue;
+ }
+
+ public int getDpiValue() {
+ return mDensity;
+ }
+
+ public int since() {
+ return mSince;
+ }
+
+ public String getLegacyValue() {
+ if (this != NODPI && this != ANYDPI) {
+ return String.format("%1$ddpi", getDpiValue());
+ }
+
+ return "";
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ return mDisplayValue;
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ return mDisplayValue;
+ }
+
+ public static int getIndex(Density value) {
+ int i = 0;
+ for (Density input : values()) {
+ if (value == input) {
+ return i;
+ }
+
+ i++;
+ }
+
+ return -1;
+ }
+
+ public static Density getByIndex(int index) {
+ Density[] values = values();
+ if (index >=0 && index < values.length) {
+ return values[index];
+ }
+ return null;
+ }
+
+ /**
+ * Returns all densities which are recommended and valid for a device.
+ *
+ * @see #isRecommended()
+ * @see #isValidValueForDevice()
+ */
+ public static Set<Density> getRecommendedValuesForDevice() {
+ EnumSet<Density> result = EnumSet.allOf(Density.class);
+ for (Density value : values()) {
+ if (!value.isRecommended() || !value.isValidValueForDevice()) {
+ result.remove(value);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns true if this density is relevant for app developers (e.g.
+ * a density you should consider providing resources for)
+ */
+ public boolean isRecommended() {
+ switch (this) {
+ case TV:
+ case DPI_280:
+ case DPI_360:
+ case DPI_400:
+ case DPI_420:
+ case DPI_560:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public boolean isFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean isValidValueForDevice() {
+ return this != NODPI && this != ANYDPI; // nodpi/anydpi is not a valid config for devices.
+ }
+}
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java b/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
rename to layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/Keyboard.java b/layoutlib-api/src/main/java/com/android/resources/Keyboard.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/Keyboard.java
rename to layoutlib-api/src/main/java/com/android/resources/Keyboard.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/KeyboardState.java b/layoutlib-api/src/main/java/com/android/resources/KeyboardState.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/KeyboardState.java
rename to layoutlib-api/src/main/java/com/android/resources/KeyboardState.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java b/layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java
rename to layoutlib-api/src/main/java/com/android/resources/LayoutDirection.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/Navigation.java b/layoutlib-api/src/main/java/com/android/resources/Navigation.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/Navigation.java
rename to layoutlib-api/src/main/java/com/android/resources/Navigation.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/NavigationState.java b/layoutlib-api/src/main/java/com/android/resources/NavigationState.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/NavigationState.java
rename to layoutlib-api/src/main/java/com/android/resources/NavigationState.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/NightMode.java b/layoutlib-api/src/main/java/com/android/resources/NightMode.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/NightMode.java
rename to layoutlib-api/src/main/java/com/android/resources/NightMode.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java b/layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java
rename to layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ResourceEnum.java b/layoutlib-api/src/main/java/com/android/resources/ResourceEnum.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ResourceEnum.java
rename to layoutlib-api/src/main/java/com/android/resources/ResourceEnum.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java b/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java
rename to layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ResourceType.java b/layoutlib-api/src/main/java/com/android/resources/ResourceType.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ResourceType.java
rename to layoutlib-api/src/main/java/com/android/resources/ResourceType.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java b/layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java
rename to layoutlib-api/src/main/java/com/android/resources/ScreenOrientation.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java b/layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java
rename to layoutlib-api/src/main/java/com/android/resources/ScreenRatio.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ScreenRound.java b/layoutlib-api/src/main/java/com/android/resources/ScreenRound.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ScreenRound.java
rename to layoutlib-api/src/main/java/com/android/resources/ScreenRound.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/ScreenSize.java b/layoutlib-api/src/main/java/com/android/resources/ScreenSize.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/ScreenSize.java
rename to layoutlib-api/src/main/java/com/android/resources/ScreenSize.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/TouchScreen.java b/layoutlib-api/src/main/java/com/android/resources/TouchScreen.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/TouchScreen.java
rename to layoutlib-api/src/main/java/com/android/resources/TouchScreen.java
diff --git a/base/layoutlib-api/src/main/java/com/android/resources/UiMode.java b/layoutlib-api/src/main/java/com/android/resources/UiMode.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/resources/UiMode.java
rename to layoutlib-api/src/main/java/com/android/resources/UiMode.java
diff --git a/base/layoutlib-api/src/main/java/com/android/util/Pair.java b/layoutlib-api/src/main/java/com/android/util/Pair.java
similarity index 100%
rename from base/layoutlib-api/src/main/java/com/android/util/Pair.java
rename to layoutlib-api/src/main/java/com/android/util/Pair.java
diff --git a/base/layoutlib-api/src/test/.classpath b/layoutlib-api/src/test/.classpath
similarity index 100%
rename from base/layoutlib-api/src/test/.classpath
rename to layoutlib-api/src/test/.classpath
diff --git a/base/layoutlib-api/src/test/.project b/layoutlib-api/src/test/.project
similarity index 100%
rename from base/layoutlib-api/src/test/.project
rename to layoutlib-api/src/test/.project
diff --git a/base/layoutlib-api/src/test/java/com/android/resources/FolderTypeRelationShipTest.java b/layoutlib-api/src/test/java/com/android/resources/FolderTypeRelationShipTest.java
similarity index 100%
rename from base/layoutlib-api/src/test/java/com/android/resources/FolderTypeRelationShipTest.java
rename to layoutlib-api/src/test/java/com/android/resources/FolderTypeRelationShipTest.java
diff --git a/base/layoutlib-api/src/test/java/com/android/resources/ResourceFolderTypeTest.java b/layoutlib-api/src/test/java/com/android/resources/ResourceFolderTypeTest.java
similarity index 100%
rename from base/layoutlib-api/src/test/java/com/android/resources/ResourceFolderTypeTest.java
rename to layoutlib-api/src/test/java/com/android/resources/ResourceFolderTypeTest.java
diff --git a/base/legacy/archquery/.classpath b/legacy/archquery/.classpath
similarity index 100%
rename from base/legacy/archquery/.classpath
rename to legacy/archquery/.classpath
diff --git a/base/legacy/archquery/.gitignore b/legacy/archquery/.gitignore
similarity index 100%
rename from base/legacy/archquery/.gitignore
rename to legacy/archquery/.gitignore
diff --git a/base/legacy/archquery/.project b/legacy/archquery/.project
similarity index 100%
rename from base/legacy/archquery/.project
rename to legacy/archquery/.project
diff --git a/base/legacy/ant-tasks/NOTICE b/legacy/archquery/NOTICE
similarity index 100%
rename from base/legacy/ant-tasks/NOTICE
rename to legacy/archquery/NOTICE
diff --git a/base/legacy/archquery/build.gradle b/legacy/archquery/build.gradle
similarity index 100%
rename from base/legacy/archquery/build.gradle
rename to legacy/archquery/build.gradle
diff --git a/base/legacy/archquery/src/main/java/com/android/archquery/Main.java b/legacy/archquery/src/main/java/com/android/archquery/Main.java
similarity index 100%
rename from base/legacy/archquery/src/main/java/com/android/archquery/Main.java
rename to legacy/archquery/src/main/java/com/android/archquery/Main.java
diff --git a/base/lint/.gitignore b/lint/.gitignore
similarity index 100%
rename from base/lint/.gitignore
rename to lint/.gitignore
diff --git a/base/sdklib/MODULE_LICENSE_APACHE2 b/lint/MODULE_LICENSE_APACHE2
similarity index 100%
rename from base/sdklib/MODULE_LICENSE_APACHE2
rename to lint/MODULE_LICENSE_APACHE2
diff --git a/base/lint/cli/.classpath b/lint/cli/.classpath
similarity index 100%
rename from base/lint/cli/.classpath
rename to lint/cli/.classpath
diff --git a/base/lint/cli/.project b/lint/cli/.project
similarity index 100%
rename from base/lint/cli/.project
rename to lint/cli/.project
diff --git a/base/legacy/ant-tasks/.settings/org.eclipse.jdt.core.prefs b/lint/cli/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/legacy/ant-tasks/.settings/org.eclipse.jdt.core.prefs
rename to lint/cli/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/lint/cli/NOTICE b/lint/cli/NOTICE
similarity index 100%
rename from base/lint/cli/NOTICE
rename to lint/cli/NOTICE
diff --git a/base/lint/cli/build.gradle b/lint/cli/build.gradle
similarity index 100%
rename from base/lint/cli/build.gradle
rename to lint/cli/build.gradle
diff --git a/base/lint/cli/etc/lint b/lint/cli/etc/lint
similarity index 100%
rename from base/lint/cli/etc/lint
rename to lint/cli/etc/lint
diff --git a/base/lint/cli/etc/lint.bat b/lint/cli/etc/lint.bat
similarity index 100%
rename from base/lint/cli/etc/lint.bat
rename to lint/cli/etc/lint.bat
diff --git a/lint/cli/lint-cli.iml b/lint/cli/lint-cli.iml
new file mode 100644
index 0000000..93aac33
--- /dev/null
+++ b/lint/cli/lint-cli.iml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ <excludeFolder url="file://$MODULE_DIR$/src/test/.settings" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="module" module-name="testutils" exported="" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="builder-model" exported="" />
+ <orderEntry type="library" name="ecj" level="project" />
+ <orderEntry type="library" scope="TEST" name="groovy" level="project" />
+ <orderEntry type="module" module-name="lint-checks-base" />
+ <orderEntry type="module" module-name="lint-api-base" />
+ <orderEntry type="library" scope="TEST" name="intellij-annotations" level="project" />
+ <orderEntry type="module" module-name="lint-tests" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="module" module-name="common" />
+ <orderEntry type="module" module-name="sdklib-base" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java b/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
new file mode 100644
index 0000000..5a59ea1
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/EcjParser.java
@@ -0,0 +1,2669 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.SdkConstants.INT_DEF_ANNOTATION;
+import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
+import static com.android.SdkConstants.UTF_8;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.sdklib.IAndroidTarget;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
+import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
+import org.eclipse.jdt.internal.compiler.IProblemFactory;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
+import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
+import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
+import org.eclipse.jdt.internal.compiler.ast.Literal;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
+import org.eclipse.jdt.internal.compiler.ast.MagicLiteral;
+import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.NumberLiteral;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TryStatement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
+import org.eclipse.jdt.internal.compiler.impl.ByteConstant;
+import org.eclipse.jdt.internal.compiler.impl.CharConstant;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
+import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
+import org.eclipse.jdt.internal.compiler.impl.IntConstant;
+import org.eclipse.jdt.internal.compiler.impl.LongConstant;
+import org.eclipse.jdt.internal.compiler.impl.ShortConstant;
+import org.eclipse.jdt.internal.compiler.impl.StringConstant;
+import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.CatchParameterBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemPackageBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
+import org.eclipse.jdt.internal.compiler.parser.Parser;
+import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.Catch;
+import lombok.ast.Identifier;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+import lombok.ast.Position;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.Try;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.ecj.EcjTreeConverter;
+
+/**
+ * Java parser which uses ECJ for parsing and type attribution
+ */
+public class EcjParser extends JavaParser {
+ private static final boolean DEBUG_DUMP_PARSE_ERRORS = false;
+
+ /**
+ * Whether we're going to keep the ECJ compiler mLookupEnvironment around between
+ * the parse phase and disposal. The lookup environment is important for type attribution.
+ * We should be able to inline this field to true, but making it optional now to allow
+ * people to revert this behavior in the field immediately if there's an unexpected
+ * problem.
+ */
+ private static final boolean KEEP_LOOKUP_ENVIRONMENT = !Boolean.getBoolean("lint.reset.ecj");
+
+ private final LintClient mClient;
+ private final Project mProject;
+ private Map<File, ICompilationUnit> mSourceUnits;
+ private Map<String, TypeDeclaration> mTypeUnits;
+ private Parser mParser;
+ protected EcjResult mEcjResult;
+
+ public EcjParser(@NonNull LintCliClient client, @Nullable Project project) {
+ mClient = client;
+ mProject = project;
+ mParser = getParser();
+ }
+
+ /**
+ * Create the default compiler options
+ */
+ public static CompilerOptions createCompilerOptions() {
+ CompilerOptions options = new CompilerOptions();
+
+ // Always using JDK 7 rather than basing it on project metadata since we
+ // don't do compilation error validation in lint (we leave that to the IDE's
+ // error parser or the command line build's compilation step); we want an
+ // AST that is as tolerant as possible.
+ long languageLevel = ClassFileConstants.JDK1_7;
+ options.complianceLevel = languageLevel;
+ options.sourceLevel = languageLevel;
+ options.targetJDK = languageLevel;
+ options.originalComplianceLevel = languageLevel;
+ options.originalSourceLevel = languageLevel;
+ options.inlineJsrBytecode = true; // >1.5
+
+ options.parseLiteralExpressionsAsConstants = true;
+ options.analyseResourceLeaks = false;
+ options.docCommentSupport = false;
+ options.defaultEncoding = UTF_8;
+ options.suppressOptionalErrors = true;
+ options.generateClassFiles = false;
+ options.isAnnotationBasedNullAnalysisEnabled = false;
+ options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false;
+ options.reportUnusedDeclaredThrownExceptionIncludeDocCommentReference = false;
+ options.reportUnusedDeclaredThrownExceptionWhenOverriding = false;
+ options.reportUnusedParameterIncludeDocCommentReference = false;
+ options.reportUnusedParameterWhenImplementingAbstract = false;
+ options.reportUnusedParameterWhenOverridingConcrete = false;
+ options.suppressWarnings = true;
+ options.processAnnotations = true;
+ options.storeAnnotations = true;
+ options.verbose = false;
+ return options;
+ }
+
+ public static long getLanguageLevel(int major, int minor) {
+ assert major == 1;
+ switch (minor) {
+ case 5: return ClassFileConstants.JDK1_5;
+ case 6: return ClassFileConstants.JDK1_6;
+ case 7:
+ default:
+ return ClassFileConstants.JDK1_7;
+ }
+ }
+
+ private Parser getParser() {
+ if (mParser == null) {
+ CompilerOptions options = createCompilerOptions();
+ ProblemReporter problemReporter = new ProblemReporter(
+ DefaultErrorHandlingPolicies.exitOnFirstError(),
+ options,
+ new DefaultProblemFactory());
+ mParser = new Parser(problemReporter,
+ options.parseLiteralExpressionsAsConstants);
+ mParser.javadocParser.checkDocComment = false;
+ }
+ return mParser;
+ }
+
+ @Override
+ public void prepareJavaParse(@NonNull final List<JavaContext> contexts) {
+ if (mProject == null || contexts.isEmpty()) {
+ return;
+ }
+
+ List<ICompilationUnit> sources = Lists.newArrayListWithExpectedSize(contexts.size());
+ mSourceUnits = Maps.newHashMapWithExpectedSize(sources.size());
+ for (JavaContext context : contexts) {
+ String contents = context.getContents();
+ if (contents == null) {
+ continue;
+ }
+ File file = context.file;
+ CompilationUnit unit = new CompilationUnit(contents.toCharArray(), file.getPath(),
+ UTF_8);
+ sources.add(unit);
+ mSourceUnits.put(file, unit);
+ }
+ List<String> classPath = computeClassPath(contexts);
+ try {
+ mEcjResult = parse(createCompilerOptions(), sources, classPath, mClient);
+
+ if (DEBUG_DUMP_PARSE_ERRORS) {
+ for (CompilationUnitDeclaration unit : mEcjResult.getCompilationUnits()) {
+ // so maybe I don't need my map!!
+ CategorizedProblem[] problems = unit.compilationResult()
+ .getAllProblems();
+ if (problems != null) {
+ for (IProblem problem : problems) {
+ if (problem == null || !problem.isError()) {
+ continue;
+ }
+ System.out.println(
+ new String(problem.getOriginatingFileName()) + ":"
+ + (problem.isError() ? "Error" : "Warning") + ": "
+ + problem.getSourceLineNumber() + ": " + problem.getMessage());
+ }
+ }
+ }
+ }
+ } catch (Throwable t) {
+ mClient.log(t, "ECJ compiler crashed");
+ }
+ }
+
+ /**
+ * A result from an ECJ compilation. In addition to the {@link #compilationUnits} it also
+ * returns the {@link #mNameEnvironment} and {@link #mLookupEnvironment} which are sometimes
+ * needed after parsing to perform for example type attribution. <b>NOTE</b>: Clients are
+ * expected to dispose of the {@link #mNameEnvironment} when done with the compilation units!
+ */
+ public static class EcjResult {
+ @Nullable private final INameEnvironment mNameEnvironment;
+ @Nullable private final LookupEnvironment mLookupEnvironment;
+ @NonNull private final Map<ICompilationUnit, CompilationUnitDeclaration> compilationUnits;
+
+ public EcjResult(@Nullable INameEnvironment nameEnvironment,
+ @Nullable LookupEnvironment lookupEnvironment,
+ @NonNull Map<ICompilationUnit, CompilationUnitDeclaration> compilationUnits) {
+ this.mNameEnvironment = nameEnvironment;
+ this.mLookupEnvironment = lookupEnvironment;
+ this.compilationUnits = compilationUnits;
+ }
+
+ /**
+ * Returns the collection of compilation units found by the parse task
+ *
+ * @return a read-only collection of compilation units
+ */
+ @NonNull
+ public Collection<CompilationUnitDeclaration> getCompilationUnits() {
+ return compilationUnits.values();
+ }
+
+ /**
+ * Returns the compilation unit parsed from the given source unit, if any
+ *
+ * @param sourceUnit the original source passed to ECJ
+ * @return the corresponding compilation unit, if created
+ */
+ @Nullable
+ public CompilationUnitDeclaration getCompilationUnit(
+ @NonNull ICompilationUnit sourceUnit) {
+ return compilationUnits.get(sourceUnit);
+ }
+
+ /**
+ * Removes the compilation unit for the given source unit, if any. Used when individual
+ * source units are disposed to allow memory to be freed up.
+ *
+ * @param sourceUnit the source unit
+ */
+ void removeCompilationUnit(@NonNull ICompilationUnit sourceUnit) {
+ compilationUnits.remove(sourceUnit);
+ }
+
+ /**
+ * Disposes this parser result, allowing various ECJ data structures to be freed up even if
+ * the parser instance hangs around.
+ */
+ public void dispose() {
+ if (mNameEnvironment != null) {
+ mNameEnvironment.cleanup();
+ }
+
+ if (mLookupEnvironment != null) {
+ mLookupEnvironment.reset();
+ }
+
+ compilationUnits.clear();
+ }
+ }
+
+ /** Parse the given source units and class path and store it into the given output map */
+ @NonNull
+ public static EcjResult parse(
+ CompilerOptions options,
+ @NonNull List<ICompilationUnit> sourceUnits,
+ @NonNull List<String> classPath,
+ @Nullable LintClient client) {
+ Map<ICompilationUnit, CompilationUnitDeclaration> outputMap =
+ Maps.newHashMapWithExpectedSize(sourceUnits.size());
+
+ INameEnvironment environment = new FileSystem(
+ classPath.toArray(new String[classPath.size()]), new String[0],
+ options.defaultEncoding);
+ IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.proceedWithAllProblems();
+ IProblemFactory problemFactory = new DefaultProblemFactory(Locale.getDefault());
+ ICompilerRequestor requestor = new ICompilerRequestor() {
+ @Override
+ public void acceptResult(CompilationResult result) {
+ // Not used; we need the corresponding CompilationUnitDeclaration for the source
+ // units (the AST parsed from source) which we don't get access to here, so we
+ // instead subclass AST to get our hands on them.
+ }
+ };
+
+ NonGeneratingCompiler compiler = new NonGeneratingCompiler(environment, policy, options,
+ requestor, problemFactory, outputMap);
+ try {
+ compiler.compile(sourceUnits.toArray(new ICompilationUnit[sourceUnits.size()]));
+ } catch (OutOfMemoryError e) {
+ environment.cleanup();
+
+ // Since we're running out of memory, if it's all still held we could potentially
+ // fail attempting to log the failure. Actively get rid of the large ECJ data
+ // structure references first so minimize the chance of that
+ //noinspection UnusedAssignment
+ compiler = null;
+ //noinspection UnusedAssignment
+ environment = null;
+ //noinspection UnusedAssignment
+ requestor = null;
+ //noinspection UnusedAssignment
+ problemFactory = null;
+ //noinspection UnusedAssignment
+ policy = null;
+
+ String msg = "Ran out of memory analyzing .java sources with ECJ: Some lint checks "
+ + "may not be accurate (missing type information from the compiler)";
+ if (client != null) {
+ // Don't log exception too; this isn't a compiler error per se where we
+ // need to pin point the exact unlucky code that asked for memory when it
+ // had already run out
+ client.log(null, msg);
+ } else {
+ System.out.println(msg);
+ }
+ } catch (Throwable t) {
+ if (client != null) {
+ CompilationUnitDeclaration currentUnit = compiler.getCurrentUnit();
+ if (currentUnit == null || currentUnit.getFileName() == null) {
+ client.log(t, "ECJ compiler crashed");
+ } else {
+ client.log(t, "ECJ compiler crashed processing %1$s",
+ new String(currentUnit.getFileName()));
+ }
+ } else {
+ t.printStackTrace();
+ }
+
+ environment.cleanup();
+ environment = null;
+ }
+
+ LookupEnvironment lookupEnvironment = compiler != null ? compiler.lookupEnvironment : null;
+ return new EcjResult(environment, lookupEnvironment, outputMap);
+ }
+
+ @NonNull
+ private List<String> computeClassPath(@NonNull List<JavaContext> contexts) {
+ assert mProject != null;
+ List<String> classPath = Lists.newArrayList();
+
+ IAndroidTarget compileTarget = mProject.getBuildTarget();
+ if (compileTarget != null) {
+ String androidJar = compileTarget.getPath(IAndroidTarget.ANDROID_JAR);
+ if (androidJar != null && new File(androidJar).exists()) {
+ classPath.add(androidJar);
+ }
+ }
+
+ Set<File> libraries = Sets.newHashSet();
+ Set<String> names = Sets.newHashSet();
+ for (File library : mProject.getJavaLibraries(true)) {
+ libraries.add(library);
+ names.add(getLibraryName(library));
+ }
+ for (Project project : mProject.getAllLibraries()) {
+ for (File library : project.getJavaLibraries(true)) {
+ String name = getLibraryName(library);
+ // Avoid pulling in android-support-v4.jar from libraries etc
+ // since we're pointing to the local copies rather than the real
+ // maven/gradle source copies
+ if (!names.contains(name)) {
+ libraries.add(library);
+ names.add(name);
+ }
+ }
+ }
+
+ for (File file : libraries) {
+ if (file.exists()) {
+ classPath.add(file.getPath());
+ }
+ }
+
+ // In incremental mode we may need to point to other sources in the project
+ // for type resolution
+ EnumSet<Scope> scope = contexts.get(0).getScope();
+ if (!scope.contains(Scope.ALL_JAVA_FILES)) {
+ // May need other compiled classes too
+ for (File dir : mProject.getJavaClassFolders()) {
+ if (dir.exists()) {
+ classPath.add(dir.getPath());
+ }
+ }
+ }
+
+ return classPath;
+ }
+
+ @NonNull
+ private static String getLibraryName(@NonNull File library) {
+ String name = library.getName();
+ if (name.equals(SdkConstants.FN_CLASSES_JAR)) {
+ // For AAR artifacts they'll all clash with "classes.jar"; include more unique
+ // context
+ String path = library.getPath();
+ int index = path.indexOf("exploded-aar");
+ if (index != -1) {
+ return path.substring(index);
+ } else {
+ index = path.indexOf("exploded-bundles");
+ if (index != -1) {
+ return path.substring(index);
+ }
+ }
+ File parent = library.getParentFile();
+ if (parent != null) {
+ return parent.getName() + File.separatorChar + name;
+ }
+ }
+ return name;
+ }
+
+ @Override
+ public Node parseJava(@NonNull JavaContext context) {
+ String code = context.getContents();
+ if (code == null) {
+ return null;
+ }
+
+ CompilationUnitDeclaration unit = getParsedUnit(context, code);
+ try {
+ EcjTreeConverter converter = new EcjTreeConverter();
+ converter.visit(code, unit);
+ List<? extends Node> nodes = converter.getAll();
+
+ if (nodes != null) {
+ // There could be more than one node when there are errors; pick out the
+ // compilation unit node
+ for (Node node : nodes) {
+ if (node instanceof lombok.ast.CompilationUnit) {
+ return node;
+ }
+ }
+ }
+
+ return null;
+ } catch (Throwable t) {
+ mClient.log(t, "Failed converting ECJ parse tree to Lombok for file %1$s",
+ context.file.getPath());
+ return null;
+ }
+ }
+
+ @Nullable
+ private CompilationUnitDeclaration getParsedUnit(
+ @NonNull JavaContext context,
+ @NonNull String code) {
+ ICompilationUnit sourceUnit = null;
+ if (mSourceUnits != null && mEcjResult != null) {
+ sourceUnit = mSourceUnits.get(context.file);
+ if (sourceUnit != null) {
+ CompilationUnitDeclaration unit = mEcjResult.getCompilationUnit(sourceUnit);
+ if (unit != null) {
+ return unit;
+ }
+ }
+ }
+
+ if (sourceUnit == null) {
+ sourceUnit = new CompilationUnit(code.toCharArray(), context.file.getName(), UTF_8);
+ }
+ try {
+ CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0);
+ return getParser().parse(sourceUnit, compilationResult);
+ } catch (AbortCompilation e) {
+ // No need to report Java parsing errors while running in Eclipse.
+ // Eclipse itself will already provide problem markers for these files,
+ // so all this achieves is creating "multiple annotations on this line"
+ // tooltips instead.
+ return null;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Location getLocation(@NonNull JavaContext context, @NonNull Node node) {
+ lombok.ast.Position position = node.getPosition();
+
+ // Not all ECJ nodes have offsets; in particular, VariableDefinitionEntries
+ while (position == Position.UNPLACED) {
+ node = node.getParent();
+ //noinspection ConstantConditions
+ if (node == null) {
+ break;
+ }
+ position = node.getPosition();
+ }
+ return Location.create(context.file, context.getContents(),
+ position.getStart(), position.getEnd());
+ }
+
+ @NonNull
+ @Override
+ public Location getRangeLocation(
+ @NonNull JavaContext context,
+ @NonNull Node from,
+ int fromDelta,
+ @NonNull Node to,
+ int toDelta) {
+ String contents = context.getContents();
+ int start = Math.max(0, from.getPosition().getStart() + fromDelta);
+ int end = Math.min(contents == null ? Integer.MAX_VALUE : contents.length(),
+ to.getPosition().getEnd() + toDelta);
+ return Location.create(context.file, contents, start, end);
+ }
+
+ @Override
+ @NonNull
+ public Location getNameLocation(@NonNull JavaContext context, @NonNull Node node) {
+ // The range on method name identifiers is wrong in the ECJ nodes; just take start of
+ // name + length of name
+ if (node instanceof MethodDeclaration) {
+ MethodDeclaration declaration = (MethodDeclaration) node;
+ Identifier identifier = declaration.astMethodName();
+ Location location = getLocation(context, identifier);
+ com.android.tools.lint.detector.api.Position start = location.getStart();
+ com.android.tools.lint.detector.api.Position end = location.getEnd();
+ int methodNameLength = identifier.astValue().length();
+ if (start != null && end != null &&
+ end.getOffset() - start.getOffset() > methodNameLength) {
+ end = new DefaultPosition(start.getLine(), start.getColumn() + methodNameLength,
+ start.getOffset() + methodNameLength);
+ return Location.create(location.getFile(), start, end);
+ }
+ return location;
+ }
+ return super.getNameLocation(context, node);
+ }
+
+ @NonNull
+ @Override
+ public
+ Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) {
+ return new LocationHandle(context.file, node);
+ }
+
+ @Override
+ public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) {
+ if (mSourceUnits != null) {
+ ICompilationUnit sourceUnit = mSourceUnits.get(context.file);
+ if (sourceUnit != null) {
+ mSourceUnits.remove(context.file);
+ if (mEcjResult != null) {
+ CompilationUnitDeclaration unit = mEcjResult.getCompilationUnit(sourceUnit);
+ if (unit != null) {
+ // See if this compilation unit defines any enum types; if so,
+ // keep those around for the type map (see #findAnnotationDeclaration())
+ if (unit.types != null) {
+ for (TypeDeclaration type : unit.types) {
+ if (isAnnotationType(type)) {
+ return;
+ }
+ if (type.memberTypes != null) {
+ for (TypeDeclaration member : type.memberTypes) {
+ if (isAnnotationType(member)) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ // Compilation unit is not defining an annotation type at the top two
+ // levels: we can remove it now; findAnnotationDeclaration will not need
+ // to go looking for it
+ mEcjResult.removeCompilationUnit(sourceUnit);
+ }
+ }
+ }
+ }
+ }
+
+ private static boolean isAnnotationType(@NonNull TypeDeclaration type) {
+ return TypeDeclaration.kind(type.modifiers) == TypeDeclaration.ANNOTATION_TYPE_DECL;
+ }
+
+ @Override
+ public void dispose() {
+ if (mEcjResult != null) {
+ mEcjResult.dispose();
+ mEcjResult = null;
+ }
+
+ mSourceUnits = null;
+ mTypeUnits = null;
+ }
+
+ @Nullable
+ private static Object getNativeNode(@NonNull Node node) {
+ Object nativeNode = node.getNativeNode();
+ if (nativeNode != null) {
+ return nativeNode;
+ }
+
+ // Special case the handling for variables: these are missing
+ // native nodes in Lombok, but we can generally reconstruct them
+ // by looking at the context and fishing into the ECJ hierarchy.
+ // For example, for a method parameter, we can look at the surrounding
+ // method declaration, which we do have an ECJ node for, and then
+ // iterate through its Argument nodes and match those up with the
+ // variable name.
+ if (node instanceof VariableDeclaration) {
+ node = ((VariableDeclaration)node).astDefinition();
+ }
+ if (node instanceof VariableDefinition) {
+ StrictListAccessor<VariableDefinitionEntry, VariableDefinition>
+ variables = ((VariableDefinition)node).astVariables();
+ if (variables.size() == 1) {
+ node = variables.first();
+ }
+ }
+ if (node instanceof VariableDefinitionEntry) {
+ VariableDefinitionEntry entry = (VariableDefinitionEntry) node;
+ String name = entry.astName().astValue();
+
+ // Find the nearest surrounding native node
+ Node parent = node.getParent();
+ while (parent != null) {
+ Object parentNativeNode = parent.getNativeNode();
+ if (parentNativeNode != null) {
+ if (parentNativeNode instanceof AbstractMethodDeclaration) {
+ // Parameter in a method declaration?
+ AbstractMethodDeclaration method =
+ (AbstractMethodDeclaration) parentNativeNode;
+ for (Argument argument : method.arguments) {
+ if (sameChars(name, argument.name)) {
+ return argument;
+ }
+ }
+ for (Statement statement : method.statements) {
+ if (statement instanceof LocalDeclaration) {
+ LocalDeclaration declaration = (LocalDeclaration)statement;
+ if (sameChars(name, declaration.name)) {
+ return declaration;
+ }
+ }
+ }
+ } else if (parentNativeNode instanceof TypeDeclaration) {
+ TypeDeclaration typeDeclaration = (TypeDeclaration) parentNativeNode;
+ for (FieldDeclaration fieldDeclaration : typeDeclaration.fields) {
+ if (sameChars(name, fieldDeclaration.name)) {
+ return fieldDeclaration;
+ }
+ }
+ } else if (parentNativeNode instanceof Block) {
+ Block block = (Block)parentNativeNode;
+ for (Statement statement : block.statements) {
+ if (statement instanceof LocalDeclaration) {
+ LocalDeclaration declaration = (LocalDeclaration)statement;
+ if (sameChars(name, declaration.name)) {
+ return declaration;
+ }
+ }
+ }
+ }
+ break;
+ }
+ parent = parent.getParent();
+ }
+ }
+
+ Node parent = node.getParent();
+ // The ECJ native nodes are sometimes spotty; for example, for a
+ // MethodInvocation node we can have a null native node, but its
+ // parent expression statement will point to the real MessageSend node
+ if (parent != null) {
+ nativeNode = parent.getNativeNode();
+ if (nativeNode != null) {
+ return nativeNode;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node) {
+ Object nativeNode = getNativeNode(node);
+ if (nativeNode == null) {
+ return null;
+ }
+
+ if (nativeNode instanceof NameReference) {
+ return resolve(((NameReference) nativeNode).binding);
+ } else if (nativeNode instanceof TypeReference) {
+ return resolve(((TypeReference) nativeNode).resolvedType);
+ } else if (nativeNode instanceof MessageSend) {
+ return resolve(((MessageSend) nativeNode).binding);
+ } else if (nativeNode instanceof AllocationExpression) {
+ return resolve(((AllocationExpression) nativeNode).binding);
+ } else if (nativeNode instanceof TypeDeclaration) {
+ return resolve(((TypeDeclaration) nativeNode).binding);
+ } else if (nativeNode instanceof ExplicitConstructorCall) {
+ return resolve(((ExplicitConstructorCall) nativeNode).binding);
+ } else if (nativeNode instanceof Annotation) {
+ AnnotationBinding compilerAnnotation =
+ ((Annotation) nativeNode).getCompilerAnnotation();
+ if (compilerAnnotation != null) {
+ return new EcjResolvedAnnotation(compilerAnnotation);
+ }
+ return resolve(((Annotation) nativeNode).resolvedType);
+ } else if (nativeNode instanceof AbstractMethodDeclaration) {
+ return resolve(((AbstractMethodDeclaration) nativeNode).binding);
+ } else if (nativeNode instanceof AbstractVariableDeclaration) {
+ if (nativeNode instanceof LocalDeclaration) {
+ return resolve(((LocalDeclaration) nativeNode).binding);
+ } else if (nativeNode instanceof FieldDeclaration) {
+ FieldDeclaration fieldDeclaration = (FieldDeclaration) nativeNode;
+ if (fieldDeclaration.initialization instanceof AllocationExpression) {
+ AllocationExpression allocation =
+ (AllocationExpression)fieldDeclaration.initialization;
+ if (allocation.binding != null) {
+ // Field constructor call: this is an enum constant.
+ return new EcjResolvedMethod(allocation.binding);
+ }
+ }
+ return resolve(fieldDeclaration.binding);
+ }
+ }
+
+ // TODO: Handle org.eclipse.jdt.internal.compiler.ast.SuperReference. It
+ // doesn't contain an actual method binding; the parent node call should contain
+ // it, but is missing a native node reference; investigate the ECJ bridge's super
+ // handling.
+
+ return null;
+ }
+
+ private ResolvedNode resolve(@Nullable Binding binding) {
+ if (binding == null || binding instanceof ProblemBinding) {
+ return null;
+ }
+
+ if (binding instanceof TypeBinding) {
+ TypeBinding tb = (TypeBinding) binding;
+ return new EcjResolvedClass(tb);
+ } else if (binding instanceof MethodBinding) {
+ MethodBinding mb = (MethodBinding) binding;
+ if (mb instanceof ProblemMethodBinding) {
+ return null;
+ }
+ //noinspection VariableNotUsedInsideIf
+ if (mb.declaringClass != null) {
+ return new EcjResolvedMethod(mb);
+ }
+ } else if (binding instanceof LocalVariableBinding) {
+ LocalVariableBinding lvb = (LocalVariableBinding) binding;
+ //noinspection VariableNotUsedInsideIf
+ if (lvb.type != null) {
+ return new EcjResolvedVariable(lvb);
+ }
+ } else if (binding instanceof FieldBinding) {
+ FieldBinding fb = (FieldBinding) binding;
+ if (fb instanceof ProblemFieldBinding) {
+ return null;
+ }
+ if (fb.type != null && fb.declaringClass != null) {
+ return new EcjResolvedField(fb);
+ }
+ }
+
+ return null;
+ }
+
+ private TypeDeclaration findAnnotationDeclaration(@NonNull String signature) {
+ if (mTypeUnits == null) {
+ Collection<CompilationUnitDeclaration> units = mEcjResult.getCompilationUnits();
+ mTypeUnits = Maps.newHashMapWithExpectedSize(units.size());
+ for (CompilationUnitDeclaration unit : units) {
+ if (unit.types != null) {
+ for (TypeDeclaration typeDeclaration : unit.types) {
+ addTypeDeclaration(typeDeclaration);
+ }
+ }
+ }
+ }
+
+ return mTypeUnits.get(signature);
+ }
+
+ private void addTypeDeclaration(TypeDeclaration typeDeclaration) {
+ String type = new String(typeDeclaration.binding.readableName());
+ mTypeUnits.put(type, typeDeclaration);
+ // Recurse on member types
+ if (typeDeclaration.memberTypes != null) {
+ for (TypeDeclaration member : typeDeclaration.memberTypes) {
+ addTypeDeclaration(member);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node) {
+ Object nativeNode = getNativeNode(node);
+ if (nativeNode == null) {
+ return null;
+ }
+
+ if (nativeNode instanceof MessageSend) {
+ nativeNode = ((MessageSend)nativeNode).binding;
+ } else if (nativeNode instanceof AllocationExpression) {
+ nativeNode = ((AllocationExpression)nativeNode).resolvedType;
+ } else if (nativeNode instanceof NameReference) {
+ nativeNode = ((NameReference)nativeNode).resolvedType;
+ } else if (nativeNode instanceof Expression) {
+ if (nativeNode instanceof Literal) {
+ if (nativeNode instanceof StringLiteral) {
+ return getTypeDescriptor(TYPE_STRING);
+ } else if (nativeNode instanceof NumberLiteral) {
+ if (nativeNode instanceof IntLiteral) {
+ return getTypeDescriptor(TYPE_INT);
+ } else if (nativeNode instanceof LongLiteral) {
+ return getTypeDescriptor(TYPE_LONG);
+ } else if (nativeNode instanceof CharLiteral) {
+ return getTypeDescriptor(TYPE_CHAR);
+ } else if (nativeNode instanceof FloatLiteral) {
+ return getTypeDescriptor(TYPE_FLOAT);
+ } else if (nativeNode instanceof DoubleLiteral) {
+ return getTypeDescriptor(TYPE_DOUBLE);
+ }
+ } else if (nativeNode instanceof MagicLiteral) {
+ if (nativeNode instanceof TrueLiteral || nativeNode instanceof FalseLiteral) {
+ return getTypeDescriptor(TYPE_BOOLEAN);
+ } else if (nativeNode instanceof NullLiteral) {
+ return getTypeDescriptor(TYPE_NULL);
+ }
+ }
+ }
+ nativeNode = ((Expression)nativeNode).resolvedType;
+ } else if (nativeNode instanceof TypeDeclaration) {
+ nativeNode = ((TypeDeclaration) nativeNode).binding;
+ } else if (nativeNode instanceof AbstractMethodDeclaration) {
+ nativeNode = ((AbstractMethodDeclaration) nativeNode).binding;
+ } else if (nativeNode instanceof FieldDeclaration) {
+ nativeNode = ((FieldDeclaration) nativeNode).binding;
+ } else if (nativeNode instanceof LocalDeclaration) {
+ nativeNode = ((LocalDeclaration) nativeNode).binding;
+ }
+
+ if (nativeNode instanceof Binding) {
+ Binding binding = (Binding) nativeNode;
+ if (binding instanceof TypeBinding) {
+ TypeBinding tb = (TypeBinding) binding;
+ return getTypeDescriptor(tb);
+ } else if (binding instanceof LocalVariableBinding) {
+ LocalVariableBinding lvb = (LocalVariableBinding) binding;
+ if (lvb.type != null) {
+ return getTypeDescriptor(lvb.type);
+ }
+ } else if (binding instanceof FieldBinding) {
+ FieldBinding fb = (FieldBinding) binding;
+ if (fb.type != null) {
+ return getTypeDescriptor(fb.type);
+ }
+ } else if (binding instanceof MethodBinding) {
+ return getTypeDescriptor(((MethodBinding) binding).returnType);
+ } else if (binding instanceof ProblemBinding) {
+ // Unresolved type. We just don't know.
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ResolvedClass findClass(@NonNull JavaContext context,
+ @NonNull String fullyQualifiedName) {
+ // Inner classes must use $ as separators. Switch to internal name first
+ // to make it more likely that we handle this correctly:
+ String internal = ClassContext.getInternalName(fullyQualifiedName);
+
+ // Convert "foo/bar/Baz" into char[][] 'foo','bar','Baz' as required for
+ // ECJ name lookup
+ List<char[]> arrays = Lists.newArrayList();
+ for (String segment : Splitter.on('/').split(internal)) {
+ arrays.add(segment.toCharArray());
+ }
+ char[][] compoundName = new char[arrays.size()][];
+ for (int i = 0, n = arrays.size(); i < n; i++) {
+ compoundName[i] = arrays.get(i);
+ }
+
+ LookupEnvironment lookup = mEcjResult.mLookupEnvironment;
+ if (lookup != null) {
+ ReferenceBinding type = lookup.getType(compoundName);
+ if (type != null && !(type instanceof ProblemReferenceBinding)) {
+ return new EcjResolvedClass(type);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public List<TypeDescriptor> getCatchTypes(@NonNull JavaContext context,
+ @NonNull Catch catchBlock) {
+ Try aTry = catchBlock.upToTry();
+ if (aTry != null) {
+ Object nativeNode = getNativeNode(aTry);
+ if (nativeNode instanceof TryStatement) {
+ TryStatement tryStatement = (TryStatement) nativeNode;
+ Argument[] catchArguments = tryStatement.catchArguments;
+ Argument argument = null;
+ if (catchArguments.length > 1) {
+ int index = 0;
+ for (Catch aCatch : aTry.astCatches()) {
+ if (aCatch == catchBlock) {
+ if (index < catchArguments.length) {
+ argument = catchArguments[index];
+ break;
+ }
+ }
+ index++;
+ }
+ } else {
+ argument = catchArguments[0];
+ }
+ if (argument != null) {
+ if (argument.type instanceof UnionTypeReference) {
+ UnionTypeReference typeRef = (UnionTypeReference) argument.type;
+ List<TypeDescriptor> types = Lists.newArrayListWithCapacity(typeRef.typeReferences.length);
+ for (TypeReference typeReference : typeRef.typeReferences) {
+ TypeBinding binding = typeReference.resolvedType;
+ if (binding != null) {
+ types.add(new EcjTypeDescriptor(binding));
+ }
+ }
+ return types;
+ } else if (argument.type.resolvedType != null) {
+ TypeDescriptor t = new EcjTypeDescriptor(argument.type.resolvedType);
+ return Collections.singletonList(t);
+ }
+ }
+ }
+ }
+
+ return super.getCatchTypes(context, catchBlock);
+ }
+
+ @Nullable
+ private TypeDescriptor getTypeDescriptor(@Nullable TypeBinding resolvedType) {
+ if (resolvedType == null) {
+ return null;
+ }
+ return new EcjTypeDescriptor(resolvedType);
+ }
+
+ private static TypeDescriptor getTypeDescriptor(String fqn) {
+ return new DefaultTypeDescriptor(fqn);
+ }
+
+ /** Computes the super method, if any, given a method binding */
+ private static MethodBinding findSuperMethodBinding(@NonNull MethodBinding binding) {
+ try {
+ ReferenceBinding superclass = binding.declaringClass.superclass();
+ while (superclass != null) {
+ MethodBinding[] methods = superclass.getMethods(binding.selector,
+ binding.parameters.length);
+ for (MethodBinding method : methods) {
+ if (method.areParameterErasuresEqual(binding)) {
+ return method;
+ }
+ }
+
+ superclass = superclass.superclass();
+ }
+ } catch (Exception ignore) {
+ // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268
+ }
+
+ return null;
+ }
+
+ @NonNull
+ private static Collection<ResolvedAnnotation> merge(
+ @Nullable Collection<ResolvedAnnotation> first,
+ @Nullable Collection<ResolvedAnnotation> second) {
+ if (first == null || first.isEmpty()) {
+ if (second == null) {
+ return Collections.emptyList();
+ } else {
+ return second;
+ }
+ } else if (second == null || second.isEmpty()) {
+ return first;
+ } else {
+ int size = first.size() + second.size();
+ List<ResolvedAnnotation> merged = Lists.newArrayListWithExpectedSize(size);
+ merged.addAll(first);
+ merged.addAll(second);
+ return merged;
+ }
+ }
+
+ /* Handle for creating positions cheaply and returning full fledged locations later */
+ private static class LocationHandle implements Location.Handle {
+ private File mFile;
+ private Node mNode;
+ private Object mClientData;
+
+ public LocationHandle(File file, Node node) {
+ mFile = file;
+ mNode = node;
+ }
+
+ @NonNull
+ @Override
+ public Location resolve() {
+ lombok.ast.Position pos = mNode.getPosition();
+ return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
+ }
+
+ @Override
+ public void setClientData(@Nullable Object clientData) {
+ mClientData = clientData;
+ }
+
+ @Override
+ @Nullable
+ public Object getClientData() {
+ return mClientData;
+ }
+ }
+
+ // Custom version of the compiler which skips code generation and records source units
+ private static class NonGeneratingCompiler extends Compiler {
+ private Map<ICompilationUnit, CompilationUnitDeclaration> mUnits;
+ private CompilationUnitDeclaration mCurrentUnit;
+
+ public NonGeneratingCompiler(INameEnvironment environment, IErrorHandlingPolicy policy,
+ CompilerOptions options, ICompilerRequestor requestor,
+ IProblemFactory problemFactory,
+ Map<ICompilationUnit, CompilationUnitDeclaration> units) {
+ super(environment, policy, options, requestor, problemFactory, null, null);
+ mUnits = units;
+ }
+
+ @Nullable
+ CompilationUnitDeclaration getCurrentUnit() {
+ // Can't use mLookupEnvironment.unitBeingCompleted directly; it gets nulled out
+ // as part of the exception catch handling in the compiler before this method
+ // is called from lint -- therefore we stash a copy in our own mCurrentUnit field
+ return mCurrentUnit;
+ }
+
+ @Override
+ protected synchronized void addCompilationUnit(ICompilationUnit sourceUnit,
+ CompilationUnitDeclaration parsedUnit) {
+ super.addCompilationUnit(sourceUnit, parsedUnit);
+ mUnits.put(sourceUnit, parsedUnit);
+ }
+
+ @Override
+ public void process(CompilationUnitDeclaration unit, int unitNumber) {
+ mCurrentUnit = lookupEnvironment.unitBeingCompleted = unit;
+
+ parser.getMethodBodies(unit);
+ if (unit.scope != null) {
+ unit.scope.faultInTypes();
+ unit.scope.verifyMethods(lookupEnvironment.methodVerifier());
+ }
+ unit.resolve();
+ unit.analyseCode();
+
+ // This is where we differ from super: DON'T call generateCode().
+ // Sadly we can't just set ignoreMethodBodies=true to have the same effect,
+ // since that would also skip the analyseCode call, which we DO, want:
+ // unit.generateCode();
+
+ if (options.produceReferenceInfo && unit.scope != null) {
+ unit.scope.storeDependencyInfo();
+ }
+ unit.finalizeProblems();
+ unit.compilationResult.totalUnitsKnown = totalUnits;
+ lookupEnvironment.unitBeingCompleted = null;
+ }
+
+ @Override
+ public void reset() {
+ if (KEEP_LOOKUP_ENVIRONMENT) {
+ // Same as super.reset() in ECJ 4.4.2, but omits the following statement:
+ // this.mLookupEnvironment.reset();
+ // because we need the lookup environment to stick around even after the
+ // parse phase is done: at that point we're going to use the parse trees
+ // from java detectors which may need to resolve types
+ this.parser.scanner.source = null;
+ this.unitsToProcess = null;
+ if (DebugRequestor != null) DebugRequestor.reset();
+ this.problemReporter.reset();
+
+ } else {
+ super.reset();
+ }
+ }
+ }
+
+ private class EcjTypeDescriptor extends TypeDescriptor {
+ private final TypeBinding mBinding;
+
+ private EcjTypeDescriptor(@NonNull TypeBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return new String(mBinding.readableName());
+ }
+
+ @Override
+ public boolean matchesName(@NonNull String name) {
+ return sameChars(name, mBinding.readableName());
+ }
+
+ @Override
+ public boolean matchesSignature(@NonNull String signature) {
+ return sameChars(signature, mBinding.readableName());
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return mBinding.isPrimitiveType();
+ }
+
+ @Override
+ public boolean isArray() {
+ return mBinding.isArrayType();
+ }
+
+ @NonNull
+ @Override
+ public String getSignature() {
+ return getName();
+ }
+
+ @NonNull
+ @Override
+ public String getSimpleName() {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding ref = (ReferenceBinding) mBinding;
+ char[][] name = ref.compoundName;
+ char[] lastSegment = name[name.length - 1];
+ StringBuilder sb = new StringBuilder(lastSegment.length);
+ for (char c : lastSegment) {
+ if (c == '$') {
+ c = '.';
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+ return super.getSimpleName();
+ }
+
+ @NonNull
+ @Override
+ public String getInternalName() {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding ref = (ReferenceBinding) mBinding;
+ StringBuilder sb = new StringBuilder(100);
+ char[][] name = ref.compoundName;
+ if (name == null) {
+ return super.getInternalName();
+ }
+ for (char[] segment : name) {
+ if (sb.length() != 0) {
+ sb.append('/');
+ }
+ for (char c : segment) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+ return super.getInternalName();
+ }
+
+ @Override
+ @Nullable
+ public ResolvedClass getTypeClass() {
+ if (!mBinding.isPrimitiveType()) {
+ return new EcjResolvedClass(mBinding);
+ }
+ return null;
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EcjTypeDescriptor that = (EcjTypeDescriptor) o;
+
+ if (!mBinding.equals(that.mBinding)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mBinding.hashCode();
+ }
+ }
+
+ private class EcjResolvedMethod extends ResolvedMethod {
+ private MethodBinding mBinding;
+
+ private EcjResolvedMethod(MethodBinding binding) {
+ mBinding = binding;
+ assert mBinding.declaringClass != null;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
+ return new String(c);
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
+ return sameChars(name, c);
+ }
+
+ @NonNull
+ @Override
+ public ResolvedClass getContainingClass() {
+ return new EcjResolvedClass(mBinding.declaringClass);
+ }
+
+ @Override
+ public int getArgumentCount() {
+ return mBinding.parameters != null ? mBinding.parameters.length : 0;
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getArgumentType(int index) {
+ TypeBinding parameterType = mBinding.parameters[index];
+ TypeDescriptor typeDescriptor = getTypeDescriptor(parameterType);
+ assert typeDescriptor != null; // because parameter is not null
+ return typeDescriptor;
+ }
+
+ @Override
+ public boolean argumentMatchesType(int index, @NonNull String signature) {
+ return sameChars(signature, mBinding.parameters[index].readableName());
+ }
+
+ @Nullable
+ @Override
+ public TypeDescriptor getReturnType() {
+ return isConstructor() ? null : getTypeDescriptor(mBinding.returnType);
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return mBinding.isConstructor();
+ }
+
+ @Override
+ @Nullable
+ public ResolvedMethod getSuperMethod() {
+ MethodBinding superBinding = findSuperMethodBinding(mBinding);
+ if (superBinding != null) {
+ return new EcjResolvedMethod(superBinding);
+ }
+
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(4);
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
+
+ MethodBinding binding = this.mBinding;
+ while (binding != null) {
+ AnnotationBinding[] annotations = binding.getAnnotations();
+ int count = annotations.length;
+ if (count > 0) {
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ all.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ }
+
+ // Look for external annotations
+ Collection<ResolvedAnnotation> external = manager.getAnnotations(
+ new EcjResolvedMethod(binding));
+ if (external != null) {
+ all.addAll(external);
+ }
+
+ binding = findSuperMethodBinding(binding);
+ }
+
+ all = ensureUnique(all);
+ return all;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getParameterAnnotations(int index) {
+ List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(4);
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
+
+ MethodBinding binding = this.mBinding;
+ while (binding != null) {
+ AnnotationBinding[][] parameterAnnotations = binding.getParameterAnnotations();
+ if (parameterAnnotations != null &&
+ index >= 0 && index < parameterAnnotations.length) {
+ AnnotationBinding[] annotations = parameterAnnotations[index];
+ int count = annotations.length;
+ if (count > 0) {
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ all.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ }
+ }
+
+ // Look for external annotations
+ Collection<ResolvedAnnotation> external = manager.getAnnotations(
+ new EcjResolvedMethod(binding), index);
+ if (external != null) {
+ all.addAll(external);
+ }
+
+ binding = findSuperMethodBinding(binding);
+ }
+
+ all = ensureUnique(all);
+ return all;
+ }
+
+ @Override
+ public int getModifiers() {
+ return mBinding.getAccessFlags();
+ }
+
+ @Override
+ public String getSignature() {
+ return mBinding.toString();
+ }
+
+ @Override
+ public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
+ PackageBinding pkg = mBinding.declaringClass.getPackage();
+ //noinspection SimplifiableIfStatement
+ if (pkg != null) {
+ return includeSubPackages ?
+ startsWithCompound(pkgName, pkg.compoundName) :
+ equalsCompound(pkgName, pkg.compoundName);
+ }
+ return false;
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EcjResolvedMethod that = (EcjResolvedMethod) o;
+
+ if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mBinding != null ? mBinding.hashCode() : 0;
+ }
+ }
+
+ /**
+ * It is valid (and in fact encouraged by IntelliJ's inspections) to specify the same
+ * annotation on overriding methods and overriding parameters. However, we shouldn't
+ * return all these "duplicates" when you ask for the annotation on a given element.
+ * This method filters out duplicates.
+ */
+ @VisibleForTesting
+ @NonNull
+ static List<ResolvedAnnotation> ensureUnique(@NonNull List<ResolvedAnnotation> list) {
+ if (list.size() < 2) {
+ return list;
+ }
+
+ // The natural way to deduplicate would be to create a Set of seen names, iterate
+ // through the list and look to see if the current annotation's name is already in the
+ // set (if so, remove this annotation from the list, else add it to the set of seen names)
+ // but this involves creating the set and all the Map entry objects; that's not
+ // necessary here since these lists are always very short 2-5 elements.
+ // Instead we'll just do an O(n^2) iteration comparing each subsequent element with each
+ // previous element and removing if matches, which is fine for these tiny lists.
+ int n = list.size();
+ for (int i = 0; i < n - 1; i++) {
+ ResolvedAnnotation current = list.get(i);
+ String currentName = current.getName();
+ // Deleting duplicates at end reduces number of elements that have to be shifted
+ for (int j = n - 1; j > i; j--) {
+ ResolvedAnnotation later = list.get(j);
+ String laterName = later.getName();
+ if (currentName.equals(laterName)) {
+ list.remove(j);
+ n--;
+ }
+ }
+ }
+
+ return list;
+ }
+
+ private class EcjResolvedClass extends ResolvedClass {
+ protected final TypeBinding mBinding;
+
+ private EcjResolvedClass(TypeBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ String name = new String(mBinding.readableName());
+ if (name.indexOf('.') == -1 && mBinding.enclosingType() != null) {
+ name = new String(mBinding.enclosingType().readableName()) + '.' + name;
+ }
+ return stripTypeVariables(name);
+ }
+
+ @NonNull
+ @Override
+ public String getSimpleName() {
+ return stripTypeVariables(new String(mBinding.sourceName()));
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return sameChars(name, mBinding.readableName());
+ }
+
+ @Nullable
+ @Override
+ public ResolvedClass getSuperClass() {
+ ReferenceBinding superClass = mBinding.superclass();
+ if (superClass != null) {
+ return new EcjResolvedClass(superClass);
+ }
+
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public Iterable<ResolvedClass> getInterfaces() {
+ ReferenceBinding[] interfaces = mBinding.superInterfaces();
+ if (interfaces.length == 0) {
+ return Collections.emptyList();
+ }
+ List<ResolvedClass> classes = Lists.newArrayListWithExpectedSize(interfaces.length);
+ for (ReferenceBinding binding : interfaces) {
+ classes.add(new EcjResolvedClass(binding));
+ }
+ return classes;
+ }
+
+ @Nullable
+ @Override
+ public ResolvedClass getContainingClass() {
+ if (mBinding instanceof NestedTypeBinding) {
+ NestedTypeBinding ntb = (NestedTypeBinding) mBinding;
+ if (ntb.enclosingType != null) {
+ return new EcjResolvedClass(ntb.enclosingType);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isSubclassOf(@NonNull String name, boolean strict) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ if (strict) {
+ cls = cls.superclass();
+ }
+ for (; cls != null; cls = cls.superclass()) {
+ if (equalsCompound(name, cls.compoundName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isImplementing(@NonNull String name, boolean strict) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ if (strict) {
+ cls = cls.superclass();
+ }
+ return isInheritor(cls, name);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isInheritingFrom(@NonNull String name, boolean strict) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ if (strict) {
+ cls = cls.superclass();
+ }
+ return isInheritor(cls, name);
+ }
+
+ return false;
+ }
+
+ @Override
+ @NonNull
+ public Iterable<ResolvedMethod> getConstructors() {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ MethodBinding[] methods = cls.getMethods(TypeConstants.INIT);
+ if (methods != null) {
+ int count = methods.length;
+ List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
+ for (MethodBinding method : methods) {
+ if (method.isConstructor()) {
+ result.add(new EcjResolvedMethod(method));
+ }
+ }
+ return result;
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ @NonNull
+ public Iterable<ResolvedMethod> getMethods(@NonNull String name,
+ boolean includeInherited) {
+ return findMethods(name, includeInherited);
+ }
+
+ @Override
+ @NonNull
+ public Iterable<ResolvedMethod> getMethods(boolean includeInherited) {
+ return findMethods(null, includeInherited);
+ }
+
+ @NonNull
+ private Iterable<ResolvedMethod> findMethods(@Nullable String name,
+ boolean includeInherited) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ if (includeInherited) {
+ List<ResolvedMethod> result = null;
+ while (cls != null) {
+ MethodBinding[] methods =
+ name != null ? cls.getMethods(name.toCharArray()) : cls.methods();
+ if (methods != null) {
+ int count = methods.length;
+ if (count > 0) {
+ if (result == null) {
+ result = Lists.newArrayListWithExpectedSize(count);
+ }
+ for (MethodBinding method : methods) {
+ if (!method.isConstructor()) {
+ // See if this method looks like it's masked
+ boolean masked = false;
+ for (ResolvedMethod m : result) {
+ MethodBinding mb = ((EcjResolvedMethod) m).mBinding;
+ if (mb.areParameterErasuresEqual(method)) {
+ masked = true;
+ break;
+ }
+ }
+ if (masked) {
+ continue;
+ }
+
+ result.add(new EcjResolvedMethod(method));
+ }
+ }
+ }
+ }
+ cls = cls.superclass();
+ }
+
+ return result != null ? result : Collections.<ResolvedMethod>emptyList();
+ } else {
+ MethodBinding[] methods =
+ name != null ? cls.getMethods(name.toCharArray()) : cls.methods();
+ if (methods != null) {
+ int count = methods.length;
+ List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
+ for (MethodBinding method : methods) {
+ if (!method.isConstructor()) {
+ result.add(new EcjResolvedMethod(method));
+ }
+ }
+ return result;
+ }
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(2);
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
+
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ while (cls != null) {
+ AnnotationBinding[] annotations = cls.getAnnotations();
+ int count = annotations.length;
+ if (count > 0) {
+ all = Lists.newArrayListWithExpectedSize(count);
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ all.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ }
+
+ // Look for external annotations
+ Collection<ResolvedAnnotation> external = manager.getAnnotations(
+ new EcjResolvedClass(cls));
+ if (external != null) {
+ all.addAll(external);
+ }
+
+ cls = cls.superclass();
+ }
+ } else {
+ Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
+ if (external != null) {
+ all.addAll(external);
+ }
+ }
+
+ all = ensureUnique(all);
+ return all;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedField> getFields(boolean includeInherited) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ if (includeInherited) {
+ List<ResolvedField> result = null;
+ while (cls != null) {
+ FieldBinding[] fields = cls.fields();
+ if (fields != null) {
+ int count = fields.length;
+ if (count > 0) {
+ if (result == null) {
+ result = Lists.newArrayListWithExpectedSize(count);
+ }
+ for (FieldBinding field : fields) {
+ // See if this field looks like it's masked
+ boolean masked = false;
+ for (ResolvedField f : result) {
+ FieldBinding mb = ((EcjResolvedField) f).mBinding;
+ if (Arrays.equals(mb.readableName(),
+ field.readableName())) {
+ masked = true;
+ break;
+ }
+ }
+ if (masked) {
+ continue;
+ }
+
+ result.add(new EcjResolvedField(field));
+ }
+ }
+ }
+ cls = cls.superclass();
+ }
+
+ return result != null ? result : Collections.<ResolvedField>emptyList();
+ } else {
+ FieldBinding[] fields = cls.fields();
+ if (fields != null) {
+ int count = fields.length;
+ List<ResolvedField> result = Lists.newArrayListWithExpectedSize(count);
+ for (FieldBinding field : fields) {
+ result.add(new EcjResolvedField(field));
+ }
+ return result;
+ }
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ @Nullable
+ public ResolvedField getField(@NonNull String name, boolean includeInherited) {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ while (cls != null) {
+ FieldBinding[] fields = cls.fields();
+ if (fields != null) {
+ for (FieldBinding field : fields) {
+ if (sameChars(name, field.name)) {
+ return new EcjResolvedField(field);
+ }
+ }
+ }
+ if (includeInherited) {
+ cls = cls.superclass();
+ } else {
+ break;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ResolvedPackage getPackage() {
+ return new EcjResolvedPackage(mBinding.getPackage());
+ }
+
+ @Override
+ public int getModifiers() {
+ if (mBinding instanceof ReferenceBinding) {
+ ReferenceBinding cls = (ReferenceBinding) mBinding;
+ // These constants from ClassFileConstants luckily agree with the Modifier
+ // constants in the low bits we care about (public, abstract, static, etc)
+ return cls.getAccessFlags();
+ }
+ return 0;
+ }
+
+ @Override
+ public TypeDescriptor getType() {
+ return new EcjTypeDescriptor(mBinding);
+ }
+
+ @Override
+ public String getSignature() {
+ return getName();
+ }
+
+ @Override
+ public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
+ PackageBinding pkg = mBinding.getPackage();
+ //noinspection SimplifiableIfStatement
+ if (pkg != null) {
+ return includeSubPackages ?
+ startsWithCompound(pkgName, pkg.compoundName) :
+ equalsCompound(pkgName, pkg.compoundName);
+ }
+ return false;
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EcjResolvedClass that = (EcjResolvedClass) o;
+
+ if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mBinding != null ? mBinding.hashCode() : 0;
+ }
+ }
+
+ @NonNull
+ private static String stripTypeVariables(String name) {
+ // Strip out type variables; there doesn't seem to be a way to
+ // do it from the ECJ APIs; it unconditionally includes this.
+ // (Converts for example
+ // android.support.v7.widget.RecyclerView.Adapter<VH>
+ // to
+ // android.support.v7.widget.RecyclerView.Adapter
+ if (name.indexOf('<') != -1) {
+ StringBuilder sb = new StringBuilder(name.length());
+ int depth = 0;
+ for (int i = 0, n = name.length(); i < n; i++) {
+ char c = name.charAt(i);
+ if (c == '<') {
+ depth++;
+ } else if (c == '>') {
+ depth--;
+ } else if (depth == 0) {
+ sb.append(c);
+ }
+ }
+ name = sb.toString();
+ }
+ return name;
+ }
+
+ // "package-info" as a char
+ private static final char[] PACKAGE_INFO_CHARS = new char[] {
+ 'p', 'a', 'c', 'k', 'a', 'g', 'e', '-', 'i', 'n', 'f', 'o'
+ };
+
+ private class EcjResolvedPackage extends ResolvedPackage {
+ private final PackageBinding mBinding;
+
+ public EcjResolvedPackage(PackageBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return new String(mBinding.readableName());
+ }
+
+ @Override
+ public String getSignature() {
+ return getName();
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(2);
+
+ AnnotationBinding[] annotations = mBinding.getAnnotations();
+ int count = annotations.length;
+ if (count == 0) {
+ Binding pkgInfo = mBinding.getTypeOrPackage(PACKAGE_INFO_CHARS);
+ if (pkgInfo != null) {
+ annotations = pkgInfo.getAnnotations();
+ }
+ count = annotations.length;
+ }
+ if (count > 0) {
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ all.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ }
+
+ // Merge external annotations
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
+ Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
+ if (external != null) {
+ all.addAll(external);
+ }
+
+ all = ensureUnique(all);
+ return all;
+ }
+
+ @Override
+ @Nullable
+ public ResolvedPackage getParentPackage() {
+ char[][] compoundName = mBinding.compoundName;
+ if (compoundName.length == 1) {
+ return null;
+ } else {
+ PackageBinding defaultPackage = mBinding.environment.defaultPackage;
+ PackageBinding packageBinding =
+ (PackageBinding) defaultPackage.getTypeOrPackage(compoundName[0]);
+ if (packageBinding == null || packageBinding instanceof ProblemPackageBinding) {
+ return null;
+ }
+
+ for (int i = 1, packageLength = compoundName.length - 1; i < packageLength; i++) {
+ Binding next = packageBinding.getTypeOrPackage(compoundName[i]);
+ if (next == null) {
+ return null;
+ }
+ if (next instanceof PackageBinding) {
+ if (next instanceof ProblemPackageBinding) {
+ return null;
+ }
+ packageBinding = (PackageBinding) next;
+ }
+ }
+
+ return new EcjResolvedPackage(packageBinding);
+ }
+ }
+
+ @Override
+ public int getModifiers() {
+ return 0;
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EcjResolvedPackage that = (EcjResolvedPackage) o;
+
+ if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mBinding != null ? mBinding.hashCode() : 0;
+ }
+ }
+
+ private class EcjResolvedField extends ResolvedField {
+ private FieldBinding mBinding;
+
+ private EcjResolvedField(FieldBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return new String(mBinding.readableName());
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return sameChars(name, mBinding.readableName());
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getType() {
+ TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
+ assert typeDescriptor != null; // because mBinding.type is known not to be null
+ return typeDescriptor;
+ }
+
+ @NonNull
+ @Override
+ public ResolvedClass getContainingClass() {
+ return new EcjResolvedClass(mBinding.declaringClass);
+ }
+
+ @Nullable
+ @Override
+ public Object getValue() {
+ return getConstantValue(mBinding.constant());
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ List<ResolvedAnnotation> compiled = null;
+ AnnotationBinding[] annotations = mBinding.getAnnotations();
+ int count = annotations.length;
+ if (count > 0) {
+ compiled = Lists.newArrayListWithExpectedSize(count);
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ compiled.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ }
+
+ // Look for external annotations
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
+ Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
+
+ return merge(compiled, external);
+ }
+
+ @Override
+ public int getModifiers() {
+ return mBinding.getAccessFlags();
+ }
+
+ @Override
+ public String getSignature() {
+ return mBinding.toString();
+ }
+
+ @Override
+ public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
+ PackageBinding pkg = mBinding.declaringClass.getPackage();
+ //noinspection SimplifiableIfStatement
+ if (pkg != null) {
+ return includeSubPackages ?
+ startsWithCompound(pkgName, pkg.compoundName) :
+ equalsCompound(pkgName, pkg.compoundName);
+ }
+ return false;
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EcjResolvedField that = (EcjResolvedField) o;
+
+ if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mBinding != null ? mBinding.hashCode() : 0;
+ }
+ }
+
+ private class EcjResolvedVariable extends ResolvedVariable {
+ private VariableBinding mBinding;
+
+ private EcjResolvedVariable(VariableBinding binding) {
+ mBinding = binding;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return new String(mBinding.readableName());
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return sameChars(name, mBinding.readableName());
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getType() {
+ TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
+ assert typeDescriptor != null; // because mBinding.type is known not to be null
+ return typeDescriptor;
+ }
+
+ @Override
+ public int getModifiers() {
+ return mBinding.modifiers;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ AnnotationBinding[] annotations = mBinding.getAnnotations();
+ int count = annotations.length;
+ if (count > 0) {
+ List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(count);
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ result.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ return result;
+ }
+
+ // No external annotations for variables
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getSignature() {
+ return mBinding.toString();
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EcjResolvedVariable that = (EcjResolvedVariable) o;
+
+ if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mBinding != null ? mBinding.hashCode() : 0;
+ }
+ }
+
+ private class EcjResolvedAnnotation extends ResolvedAnnotation {
+ private final AnnotationBinding mBinding;
+ private final String mName;
+
+ private EcjResolvedAnnotation(@NonNull final AnnotationBinding binding) {
+ mBinding = binding;
+ mName = new String(mBinding.getAnnotationType().readableName());
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return name.equals(mName);
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getType() {
+ TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.getAnnotationType());
+ assert typeDescriptor != null; // because mBinding.type is known not to be null
+ return typeDescriptor;
+ }
+
+ @Override
+ public ResolvedClass getClassType() {
+ ReferenceBinding annotationType = mBinding.getAnnotationType();
+ return new EcjResolvedClass(annotationType) {
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ AnnotationBinding[] annotations = mBinding.getAnnotations();
+ int count = annotations.length;
+ if (count > 0) {
+ List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(count);
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ // Special case: If you look up the annotations *on* annotations,
+ // you're probably working with the typedef annotations, @IntDef
+ // and @StringDef. For these, we can't use the normal annotation
+ // handling, because the compiler only keeps the values of the
+ // constants, not the references to the constants which is what we
+ // care about for those annotations. So in this case, construct
+ // a special subclass of ResolvedAnnotation: EcjAstAnnotation, where
+ // we keep the AST node for the annotation definition such that
+ // we can look up the constant references themselves when queries
+ // via the annotation's getValue() lookup methods.
+ char[] readableName = annotation.getAnnotationType().readableName();
+ if (sameChars(INT_DEF_ANNOTATION, readableName)
+ || sameChars(STRING_DEF_ANNOTATION, readableName)) {
+ TypeDeclaration typeDeclaration =
+ findAnnotationDeclaration(getName());
+ if (typeDeclaration != null && typeDeclaration.annotations != null) {
+ Annotation astAnnotation = null;
+ for (Annotation a : typeDeclaration.annotations) {
+ if (a.resolvedType != null
+ && (sameChars(INT_DEF_ANNOTATION, a.resolvedType.readableName()) ||
+ sameChars(STRING_DEF_ANNOTATION, a.resolvedType.readableName()))) {
+ astAnnotation = a;
+ break;
+ }
+ }
+
+ if (astAnnotation != null) {
+ result.add(new EcjAstAnnotation(annotation, astAnnotation));
+ continue;
+ }
+ } else {
+ // Don't record these typedef annotations; without
+ // finding the bindings, we'll get the literal values
+ // from the ECJ annotation, and that will lead to incorrect
+ // typedef warnings.
+ continue;
+ }
+ }
+
+ result.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ return result;
+ }
+
+ return Collections.emptyList();
+ }
+ };
+ }
+
+ @NonNull
+ @Override
+ public List<Value> getValues() {
+ ElementValuePair[] pairs = mBinding.getElementValuePairs();
+ if (pairs != null && pairs.length > 0) {
+ List<Value> values = Lists.newArrayListWithExpectedSize(pairs.length);
+ for (ElementValuePair pair : pairs) {
+ values.add(new Value(new String(pair.getName()), getPairValue(pair)));
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Nullable
+ @Override
+ public Object getValue(@NonNull String name) {
+ ElementValuePair[] pairs = mBinding.getElementValuePairs();
+ if (pairs != null) {
+ for (ElementValuePair pair : pairs) {
+ if (sameChars(name, pair.getName())) {
+ return getPairValue(pair);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Object getPairValue(ElementValuePair pair) {
+ return getConstantValue(pair.getValue());
+ }
+
+ @Override
+ public String getSignature() {
+ return mName;
+ }
+
+ @Override
+ public int getModifiers() {
+ // Not applicable; move from ResolvedNode into ones that matter?
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ List<ResolvedAnnotation> compiled = null;
+ AnnotationBinding[] annotations = mBinding.getAnnotationType().getAnnotations();
+ int count = annotations.length;
+ if (count > 0) {
+ compiled = Lists.newArrayListWithExpectedSize(count);
+ for (AnnotationBinding annotation : annotations) {
+ if (annotation != null) {
+ compiled.add(new EcjResolvedAnnotation(annotation));
+ }
+ }
+ }
+
+ // Look for external annotations
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
+ Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
+
+ return merge(compiled, external);
+ }
+
+ private class EcjAstAnnotation extends EcjResolvedAnnotation {
+
+ private final Annotation mAstAnnotation;
+ private List<Value> mValues;
+
+ public EcjAstAnnotation(
+ @NonNull AnnotationBinding binding, @NonNull Annotation astAnnotation) {
+ super(binding);
+ mAstAnnotation = astAnnotation;
+ }
+
+ @NonNull
+ @Override
+ public List<Value> getValues() {
+ if (mValues == null) {
+ MemberValuePair[] memberValuePairs = mAstAnnotation.memberValuePairs();
+ List<Value> result = Lists
+ .newArrayListWithExpectedSize(memberValuePairs.length);
+
+ for (MemberValuePair pair : memberValuePairs) {
+ // String n = new String(pair.name);
+ Expression expression = pair.value;
+ Object value = null;
+ if (expression instanceof ArrayInitializer) {
+ ArrayInitializer initializer = (ArrayInitializer) expression;
+ Expression[] expressions = initializer.expressions;
+ List<Object> values = Lists.newArrayList();
+ for (Expression e : expressions) {
+ if (e instanceof NameReference) {
+ ResolvedNode resolved = resolve(((NameReference) e).binding);
+ if (resolved != null) {
+ values.add(resolved);
+ }
+ } else if (e instanceof IntLiteral) {
+ values.add(((IntLiteral) e).value);
+ } else if (e instanceof StringLiteral) {
+ values.add(String.valueOf(((StringLiteral) e).source()));
+ } else {
+ values.add(e.toString());
+ }
+ }
+ value = values.toArray();
+ } else if (expression instanceof IntLiteral) {
+ IntLiteral intLiteral = (IntLiteral) expression;
+ value = intLiteral.value;
+ } else if (expression instanceof TrueLiteral) {
+ value = true;
+ } else if (expression instanceof FalseLiteral) {
+ value = false;
+ } else if (expression instanceof StringLiteral) {
+ value = String.valueOf(((StringLiteral) expression).source());
+ }
+ // Unfortunately, FloatLiteral, LongLiteral etc do not
+ // expose the value field as public. Luckily, we don't need that
+ // for our current annotations.
+
+ result.add(new Value(new String(pair.name), value));
+ }
+ mValues = result;
+ }
+
+ return mValues;
+ }
+
+ @Nullable
+ @Override
+ public Object getValue(@NonNull String name) {
+ for (Value value : getValues()) {
+ if (name.equals(value.name)) {
+ return value.value;
+ }
+ }
+ return null;
+ }
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ EcjResolvedAnnotation that = (EcjResolvedAnnotation) o;
+
+ if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mBinding != null ? mBinding.hashCode() : 0;
+ }
+ }
+
+ @Nullable
+ private Object getConstantValue(@Nullable Object value) {
+ if (value instanceof Constant) {
+ if (value == Constant.NotAConstant) {
+ return null;
+ }
+ if (value instanceof StringConstant) {
+ return ((StringConstant) value).stringValue();
+ } else if (value instanceof IntConstant) {
+ return ((IntConstant) value).intValue();
+ } else if (value instanceof BooleanConstant) {
+ return ((BooleanConstant) value).booleanValue();
+ } else if (value instanceof FloatConstant) {
+ return ((FloatConstant) value).floatValue();
+ } else if (value instanceof LongConstant) {
+ return ((LongConstant) value).longValue();
+ } else if (value instanceof DoubleConstant) {
+ return ((DoubleConstant) value).doubleValue();
+ } else if (value instanceof ShortConstant) {
+ return ((ShortConstant) value).shortValue();
+ } else if (value instanceof CharConstant) {
+ return ((CharConstant) value).charValue();
+ } else if (value instanceof ByteConstant) {
+ return ((ByteConstant) value).byteValue();
+ }
+ } else if (value instanceof Object[]) {
+ Object[] array = (Object[]) value;
+ if (array.length > 0) {
+ List<Object> list = Lists.newArrayListWithExpectedSize(array.length);
+ for (Object element : array) {
+ list.add(getConstantValue(element));
+ }
+ // Pick type of array. Annotations are limited to Strings, Classes
+ // and Annotations
+ if (!list.isEmpty()) {
+ Object first = list.get(0);
+ if (first instanceof String) {
+ //noinspection SuspiciousToArrayCall
+ return list.toArray(new String[list.size()]);
+ } else if (first instanceof java.lang.annotation.Annotation) {
+ //noinspection SuspiciousToArrayCall
+ return list.toArray(new Annotation[list.size()]);
+ } else if (first instanceof Class) {
+ //noinspection SuspiciousToArrayCall
+ return list.toArray(new Class[list.size()]);
+ }
+ }
+
+ return list.toArray();
+ }
+ } else if (value instanceof AnnotationBinding) {
+ return new EcjResolvedAnnotation((AnnotationBinding) value);
+ } else if (value instanceof FieldBinding) {
+ return new EcjResolvedField((FieldBinding)value);
+ }
+
+ return value;
+ }
+
+ private static boolean sameChars(String str, char[] chars) {
+ int length = str.length();
+ if (chars.length != length) {
+ return false;
+ }
+
+ for (int i = 0; i < length; i++) {
+ if (chars[i] != str.charAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Does the given compound name match the given string?
+ * <p>
+ * TODO: Check if ECJ already has this as a utility somewhere
+ */
+ @VisibleForTesting
+ static boolean startsWithCompound(@NonNull String name, @NonNull char[][] compoundName) {
+ int length = name.length();
+ if (length == 0) {
+ return false;
+ }
+ int index = 0;
+ for (int i = 0, n = compoundName.length; i < n; i++) {
+ char[] o = compoundName[i];
+ //noinspection ForLoopReplaceableByForEach
+ for (int j = 0, m = o.length; j < m; j++) {
+ if (index == length) {
+ return false; // Don't allow prefix in a compound name
+ }
+ if (name.charAt(index) != o[j]
+ // Allow using . as an inner class separator whereas the
+ // symbol table will always use $
+ && !(o[j] == '$' && name.charAt(index) == '.')) {
+ return false;
+ }
+ index++;
+ }
+ if (i < n - 1) {
+ if (index == length) {
+ return true;
+ }
+ if (name.charAt(index) != '.') {
+ return false;
+ }
+ index++;
+ if (index == length) {
+ return true;
+ }
+ }
+ }
+
+ return index == length;
+ }
+
+ @VisibleForTesting
+ static boolean equalsCompound(@NonNull String name, @NonNull char[][] compoundName) {
+ int length = name.length();
+ if (length == 0) {
+ return false;
+ }
+ int index = 0;
+ for (int i = 0, n = compoundName.length; i < n; i++) {
+ char[] o = compoundName[i];
+ //noinspection ForLoopReplaceableByForEach
+ for (int j = 0, m = o.length; j < m; j++) {
+ if (index == length) {
+ return false; // Don't allow prefix in a compound name
+ }
+ if (name.charAt(index) != o[j]
+ // Allow using . as an inner class separator whereas the
+ // symbol table will always use $
+ && !(o[j] == '$' && name.charAt(index) == '.')) {
+ return false;
+ }
+ index++;
+ }
+ if (i < n - 1) {
+ if (index == length) {
+ return false;
+ }
+ if (name.charAt(index) != '.') {
+ return false;
+ }
+ index++;
+ if (index == length) {
+ return false;
+ }
+ }
+ }
+
+ return index == length;
+ }
+
+ /** Checks whether the given class extends or implements a class with the given name */
+ private static boolean isInheritor(@Nullable ReferenceBinding cls, @NonNull String name) {
+ for (; cls != null; cls = cls.superclass()) {
+ ReferenceBinding[] interfaces = cls.superInterfaces();
+ for (ReferenceBinding binding : interfaces) {
+ if (isInheritor(binding, name)) {
+ return true;
+ }
+ }
+
+ if (equalsCompound(name, cls.compoundName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java b/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java
new file mode 100644
index 0000000..df17a3a
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/ExternalAnnotationRepository.java
@@ -0,0 +1,1178 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FN_ANNOTATIONS_ZIP;
+import static com.android.SdkConstants.VALUE_FALSE;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.CHECK_RESULT_ANNOTATION;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.client.api.JavaParser.DefaultTypeDescriptor;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation.Value;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedPackage;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Project;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarInputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+
+/**
+ * Handler for IntelliJ database files for external annotations.
+ * It can be pointed to an annotations .jar file, which it then reads,
+ * and can return {@link ResolvedAnnotation} instances when queried
+ * for annotations on a {@link ResolvedClass} or a {@link ResolvedMethod},
+ * including its parameters.
+ */
+public class ExternalAnnotationRepository {
+ public static final String SDK_ANNOTATIONS_PATH = "platform-tools/api/annotations.zip"; //$NON-NLS-1$
+ public static final String FN_ANNOTATIONS_XML = "annotations.xml"; //$NON-NLS-1$
+
+ private static final boolean DEBUG = false;
+
+ private static ExternalAnnotationRepository sSingleton;
+
+ private final List<AnnotationsDatabase> mDatabases;
+
+ private ExternalAnnotationRepository(@NonNull List<AnnotationsDatabase> databases) {
+ mDatabases = databases;
+ }
+
+ @NonNull
+ public static synchronized ExternalAnnotationRepository get(@NonNull LintClient client) {
+ if (sSingleton == null) {
+ HashSet<AndroidLibrary> seen = Sets.newHashSet();
+ Collection<Project> projects = client.getKnownProjects();
+ List<File> files = Lists.newArrayListWithExpectedSize(2);
+ for (Project project : projects) {
+ if (project.isGradleProject()) {
+ Variant variant = project.getCurrentVariant();
+ AndroidProject model = project.getGradleProjectModel();
+ if (model != null && variant != null) {
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+ for (AndroidLibrary library : dependencies.getLibraries()) {
+ addLibraries(files, library, seen);
+ }
+ }
+ }
+ }
+
+ File sdkAnnotations = client.findResource(SDK_ANNOTATIONS_PATH);
+ if (sdkAnnotations == null) {
+ // Until the SDK annotations are bundled in platform tools, provide
+ // a fallback for Gradle builds to point to a locally installed version
+ String path = System.getenv("SDK_ANNOTATIONS");
+ if (path != null) {
+ sdkAnnotations = new File(path);
+ if (!sdkAnnotations.exists()) {
+ sdkAnnotations = null;
+ }
+ }
+ }
+ if (sdkAnnotations != null) {
+ files.add(sdkAnnotations);
+ }
+
+ sSingleton = create(client, files);
+ }
+
+ return sSingleton;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ static synchronized ExternalAnnotationRepository create(
+ @Nullable LintClient client,
+ @NonNull List<File> files) {
+ long begin;
+ if (DEBUG) {
+ begin = System.currentTimeMillis();
+ }
+
+ List<AnnotationsDatabase> databases = Lists.newArrayListWithExpectedSize(files.size());
+ for (File file : files) {
+ try {
+ AnnotationsDatabase database = getDatabase(file);
+ if (database != null) {
+ databases.add(database);
+ }
+ } catch (IOException ioe) {
+ if (client != null) {
+ client.log(ioe, "Could not read %1$s", file.getPath());
+ } else {
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ ExternalAnnotationRepository manager = new ExternalAnnotationRepository(databases);
+
+ if (DEBUG) {
+ long end = System.currentTimeMillis();
+ System.out.println("Initialization of annotations took " + (end - begin) + " ms");
+ }
+
+ return manager;
+ }
+
+ private static void addLibraries(
+ @NonNull List<File> result,
+ @NonNull AndroidLibrary library,
+ Set<AndroidLibrary> seen) {
+ if (seen.contains(library)) {
+ return;
+ }
+ seen.add(library);
+
+ // As of 1.2 this is available in the model:
+ // https://android-review.googlesource.com/#/c/137750/
+ // Switch over to this when it's in more common usage
+ // (until it is, we'll pay for failed proxying errors)
+ File zip = new File(library.getResFolder().getParent(), FN_ANNOTATIONS_ZIP);
+ if (zip.exists()) {
+ result.add(zip);
+ }
+
+ for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+ addLibraries(result, dependency, seen);
+ }
+ }
+
+ @Nullable
+ private static AnnotationsDatabase getDatabase(
+ @NonNull LintClient client,
+ @NonNull File file) {
+ try {
+ return file.isFile() ? new AnnotationsDatabase(file) : null;
+ } catch (IOException ioe) {
+ client.log(ioe, "Could not read %1$s", file.getPath());
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ @Nullable
+ static AnnotationsDatabase getDatabase(@NonNull File file) throws IOException {
+ return file.exists() ? new AnnotationsDatabase(file) : null;
+ }
+
+ @Nullable
+ private static AnnotationsDatabase getDatabase(
+ @NonNull LintClient client,
+ @NonNull AndroidLibrary library) {
+ // As of 1.2 this is available in the model:
+ // https://android-review.googlesource.com/#/c/137750/
+ // Switch over to this when it's in more common usage
+ // (until it is, we'll pay for failed proxying errors)
+ File zip = new File(library.getResFolder().getParent(), FN_ANNOTATIONS_ZIP);
+ return getDatabase(client, zip);
+ }
+
+ // ---- Query methods ----
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method, @NonNull String type) {
+ for (AnnotationsDatabase database : mDatabases) {
+ ResolvedAnnotation annotation = database.getAnnotation(method, type);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedMethod method) {
+ for (AnnotationsDatabase database : mDatabases) {
+ Collection<ResolvedAnnotation> annotations = database.getAnnotations(method);
+ if (annotations != null) {
+ return annotations;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method,
+ int parameterIndex, @NonNull String type) {
+ for (AnnotationsDatabase database : mDatabases) {
+ ResolvedAnnotation annotation = database.getAnnotation(method, parameterIndex, type);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public Collection<ResolvedAnnotation> getAnnotations(
+ @NonNull ResolvedMethod method,
+ int parameterIndex) {
+ for (AnnotationsDatabase database : mDatabases) {
+ Collection<ResolvedAnnotation> annotations = database.getAnnotations(method,
+ parameterIndex);
+ if (annotations != null) {
+ return annotations;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedClass cls, @NonNull String type) {
+ for (AnnotationsDatabase database : mDatabases) {
+ ResolvedAnnotation annotation = database.getAnnotation(cls, type);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedClass cls) {
+ for (AnnotationsDatabase database : mDatabases) {
+ Collection<ResolvedAnnotation> annotations = database.getAnnotations(cls);
+ if (annotations != null) {
+ return annotations;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedField field, @NonNull String type) {
+ for (AnnotationsDatabase database : mDatabases) {
+ ResolvedAnnotation annotation = database.getAnnotation(field, type);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedField field) {
+ for (AnnotationsDatabase database : mDatabases) {
+ Collection<ResolvedAnnotation> annotations = database.getAnnotations(field);
+ if (annotations != null) {
+ return annotations;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedAnnotation cls) {
+ for (AnnotationsDatabase database : mDatabases) {
+ Collection<ResolvedAnnotation> annotations = database.getAnnotations(cls);
+ if (annotations != null) {
+ return annotations;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedPackage pkg, @NonNull String type) {
+ for (AnnotationsDatabase database : mDatabases) {
+ ResolvedAnnotation annotation = database.getAnnotation(pkg, type);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+ @Nullable
+ public Collection<ResolvedAnnotation> getAnnotations(@NonNull ResolvedPackage pkg) {
+ for (AnnotationsDatabase database : mDatabases) {
+ Collection<ResolvedAnnotation> annotations = database.getAnnotations(pkg);
+ if (annotations != null) {
+ return annotations;
+ }
+ }
+
+ return null;
+ }
+
+ // ---- Reading from storage ----
+
+ private static final Pattern XML_SIGNATURE = Pattern.compile(
+ // Class (FieldName | Type? Name(ArgList) Argnum?)
+ "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)");
+
+ /** Map from class fully qualified name to the class annotations info */
+ // Query database
+ private static class ClassInfo {
+ public List<ResolvedAnnotation> annotations;
+ public Multimap<String,MethodInfo> methods;
+ public Map<String,FieldInfo> fields;
+ }
+
+ private static class MethodInfo {
+ public String parameters;
+ public boolean constructor;
+ public List<ResolvedAnnotation> annotations;
+ public Multimap<Integer,ResolvedAnnotation> parameterAnnotations;
+ }
+
+ private static class FieldInfo {
+ public List<ResolvedAnnotation> annotations;
+ }
+
+ /** An {@linkplain AnnotationsDatabase} corresponds to a single external annotations .zip
+ * file (or if in the dev tree, a corresponding directory tree.
+ * <p>
+ * The SDK has an annotations database, and AAR libraries can also supply individual databases.
+ * The {@linkplain ExternalAnnotationRepository} class manages all of these and performs lookup
+ * into the various databases through a single entrypoint.
+ * */
+ static class AnnotationsDatabase {
+ AnnotationsDatabase(@NonNull File file) throws IOException {
+ String path = file.getPath();
+ if (path.endsWith(DOT_JAR) || path.endsWith(FN_ANNOTATIONS_ZIP)) {
+ initializeFromJar(file);
+ } else {
+ assert file.isDirectory() : file;
+ initializeFromDirectory(file);
+ }
+ }
+
+ // ---- Query methods ----
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method,
+ @NonNull String type) {
+ MethodInfo m = findMethod(method);
+ if (m == null) {
+ return null;
+ }
+
+ if (m.annotations != null) {
+ for (ResolvedAnnotation annotation : m.annotations) {
+ if (type.equals(annotation.getSignature())) {
+ return annotation;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedMethod method) {
+ MethodInfo m = findMethod(method);
+ if (m == null) {
+ return null;
+ }
+ return m.annotations;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedMethod method,
+ int parameterIndex, @NonNull String type) {
+ MethodInfo m = findMethod(method);
+ if (m == null) {
+ return null;
+ }
+
+ if (m.parameterAnnotations != null) {
+ Collection<ResolvedAnnotation> annotations = m.parameterAnnotations.get(parameterIndex);
+ if (annotations != null) {
+ for (ResolvedAnnotation annotation : annotations) {
+ if (type.equals(annotation.getSignature())) {
+ return annotation;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public Collection<ResolvedAnnotation> getAnnotations(
+ @NonNull ResolvedMethod method,
+ int parameterIndex) {
+ MethodInfo m = findMethod(method);
+ if (m == null) {
+ return null;
+ }
+
+ if (m.parameterAnnotations != null) {
+ return m.parameterAnnotations.get(parameterIndex);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedClass cls, @NonNull String type) {
+ ClassInfo c = findClass(cls);
+ if (c == null) {
+ return null;
+ }
+
+ if (c.annotations != null) {
+ for (ResolvedAnnotation annotation : c.annotations) {
+ if (type.equals(annotation.getSignature())) {
+ return annotation;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedClass cls) {
+ ClassInfo c = findClass(cls);
+ if (c == null) {
+ return null;
+ }
+
+ return c.annotations;
+ }
+
+ @Nullable
+ public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedAnnotation cls) {
+ ClassInfo c = findClass(cls);
+ if (c == null) {
+ return null;
+ }
+
+ return c.annotations;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedPackage pkg, @NonNull String type) {
+ ClassInfo c = findPackage(pkg);
+
+ if (c == null) {
+ return null;
+ }
+
+ if (c.annotations != null) {
+ for (ResolvedAnnotation annotation : c.annotations) {
+ if (type.equals(annotation.getSignature())) {
+ return annotation;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedPackage pkg) {
+ ClassInfo c = findPackage(pkg);
+ if (c == null) {
+ return null;
+ }
+
+ return c.annotations;
+ }
+
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull ResolvedField field, @NonNull String type) {
+ FieldInfo f = findField(field);
+
+ if (f == null) {
+ return null;
+ }
+ if (f.annotations != null) {
+ for (ResolvedAnnotation annotation : f.annotations) {
+ if (type.equals(annotation.getSignature())) {
+ return annotation;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public List<ResolvedAnnotation> getAnnotations(@NonNull ResolvedField field) {
+ FieldInfo f = findField(field);
+
+ if (f == null) {
+ return null;
+ }
+ return f.annotations;
+ }
+
+ // ---- Initialization ----
+
+ private void initializeFromDirectory(File file) throws IOException {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ initializeFromDirectory(f);
+ }
+ }
+ } else if (file.getPath().endsWith(FN_ANNOTATIONS_XML)) {
+ String xml = Files.toString(file, Charsets.UTF_8);
+ initializePackage(xml, file.getPath());
+ }
+ }
+
+ private void initializeFromJar(File file) throws IOException {
+ // Reads in an existing annotations jar and merges in entries found there
+ // with the annotations analyzed from source.
+ JarInputStream zis = null;
+ try {
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ FileInputStream fis = new FileInputStream(file);
+ zis = new JarInputStream(fis);
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ if (entry.getName().endsWith(".xml")) {
+ byte[] bytes = ByteStreams.toByteArray(zis);
+ String xml = new String(bytes, Charsets.UTF_8);
+ initializePackage(xml, entry.getName());
+ }
+ entry = zis.getNextEntry();
+ }
+ } finally {
+ try {
+ Closeables.close(zis, true);
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ }
+
+ /**
+ * Takes the XML contents of an annotations.xml file, parses it and initialize
+ * the necessary data structures
+ */
+ private void initializePackage(@NonNull String xml, @NonNull String path)
+ throws IOException {
+ try {
+ Document document = XmlUtils.parseDocument(xml, false);
+
+ Element root = document.getDocumentElement();
+ String rootTag = root.getTagName();
+ assert rootTag.equals("root") : rootTag;
+
+ for (Element item : LintUtils.getChildren(root)) {
+ String signature = item.getAttribute(ATTR_NAME);
+ if (signature == null || signature.equals("null")) {
+ continue; // malformed item
+ }
+
+ signature = XmlUtils.fromXmlAttributeValue(signature);
+ Matcher matcher = XML_SIGNATURE.matcher(signature);
+ if (matcher.matches()) {
+ String containingClass = matcher.group(1);
+ if (containingClass == null) {
+ throw new IOException("Could not find class for " + signature);
+ }
+ String methodName = matcher.group(5);
+ if (methodName != null) {
+ String type = matcher.group(4);
+ boolean isConstructor = type == null;
+ String parameters = matcher.group(6);
+ mergeMethodOrParameter(item, matcher, containingClass, methodName,
+ isConstructor, parameters);
+ } else {
+ String fieldName = matcher.group(2);
+ mergeField(item, containingClass, fieldName);
+ }
+ } else if (signature.indexOf(' ') == -1 && signature.indexOf('.') != -1) {
+ mergeClass(item, signature);
+ } else {
+ throw new IOException("No merge match for signature " + signature);
+ }
+ }
+ } catch (Exception e) {
+ throw new IOException("Could not parse XML from " + path);
+ }
+ }
+
+ // SDK annotations
+ private Map<String,ClassInfo> mClassMap = Maps.newHashMapWithExpectedSize(800);
+
+ @Nullable
+ private ClassInfo findClass(@NonNull ResolvedClass cls) {
+ return mClassMap.get(cls.getName());
+ }
+
+ @Nullable
+ private ClassInfo findClass(@NonNull ResolvedAnnotation cls) {
+ return mClassMap.get(cls.getName());
+ }
+
+ private ClassInfo findPackage(@NonNull ResolvedPackage pkg) {
+ return mClassMap.get(pkg.getName() +".package-info");
+ }
+
+ @Nullable
+ private MethodInfo findMethod(@NonNull ResolvedMethod method) {
+ ClassInfo c = findClass(method.getContainingClass());
+ if (c == null) {
+ return null;
+ }
+ if (c.methods == null) {
+ return null;
+ }
+ Collection<MethodInfo> methods = c.methods.get(method.getName());
+ if (methods == null) {
+ return null;
+ }
+ boolean constructor = method.isConstructor();
+ for (MethodInfo m : methods) {
+ if (constructor != m.constructor) {
+ continue;
+ }
+ // Check parameter types
+ // TODO: Perform faster parameter check! This is inefficient
+ // Stash parameter count such that I can quickly compare the two
+ String signature = m.parameters;
+ int index = 0;
+ boolean matches = true;
+ for (int i = 0, n = method.getArgumentCount(); i < n; i++) {
+ String parameterType = method.getArgumentType(i).getSignature();
+ int length = parameterType.indexOf('<');
+ if (length == -1) {
+ length = parameterType.length();
+ }
+ if (!signature.regionMatches(false, index, parameterType, 0, length)) {
+ // Check if we have a varargs match: x... vs x[]
+ if (length <= 3 || index <= 3 || ((parameterType.charAt(length - 1) != '.')
+ && (signature.length() < index + length
+ || signature.charAt(index + length - 1) != '.'))
+ || !isVarArgsMatch(signature, index, parameterType, length)) {
+ matches = false;
+ break;
+ }
+ }
+ index += length;
+ if (i < n - 1) {
+ if (index == signature.length()) {
+ matches = false;
+ break;
+ } else if (signature.charAt(index) == '<') {
+ // Skip raw types
+ int balance = 1;
+ for (int j = index + 1, max = signature.length(); j < max; j++) {
+ char ch = signature.charAt(j);
+ if (ch == '<') {
+ balance++;
+ } else if (ch == '>') {
+ balance--;
+ if (balance == 0) {
+ index = j + 1;
+ break;
+ }
+ }
+ }
+ if (balance > 0) {
+ matches = false;
+ break;
+ }
+ } else if (signature.charAt(index) != ',') {
+ matches = false;
+ break;
+ }
+ }
+ index++; // skip comma
+ }
+
+ if (matches) {
+ return m;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks whether the string at parameterType(0,length) and signature(index,index+length)
+ * are the same, except with one possibly ending with [] and the other with ... - if
+ * so these should be taken to match
+ */
+ private static boolean isVarArgsMatch(String signature, int index, String parameterType,
+ int length) {
+ return parameterType.regionMatches(false, length - 3, "...", 0, 3) &&
+ signature.regionMatches(false, index + length - 3, "[]", 0, 2) &&
+ parameterType.regionMatches(false, 0, signature, index, length - 3)
+ || parameterType.regionMatches(false, length - 2, "[]", 0, 2) &&
+ signature.regionMatches(false, index + length - 2, "...", 0, 3) &&
+ parameterType.regionMatches(false, 0, signature, index, length - 2);
+ }
+
+ @Nullable
+ private FieldInfo findField(@NonNull ResolvedField field) {
+ ResolvedClass containingClass = field.getContainingClass();
+ if (containingClass == null) {
+ return null;
+ }
+ ClassInfo c = findClass(containingClass);
+ if (c == null) {
+ return null;
+ }
+ if (c.fields == null) {
+ return null;
+ }
+ return c.fields.get(field.getName());
+ }
+
+ @NonNull
+ private MethodInfo createMethod(@NonNull String containingClass, @NonNull String methodName,
+ boolean constructor, @NonNull String parameters) {
+ ClassInfo cls = createClass(containingClass);
+ if (cls.methods != null) {
+ Collection<MethodInfo> methods = cls.methods.get(methodName);
+ if (methods != null) {
+ for (MethodInfo method : methods) {
+ if (parameters.equals(method.parameters)
+ && constructor == method.constructor) {
+ return method;
+ }
+ }
+ }
+ }
+
+ MethodInfo method = new MethodInfo();
+ method.parameters = parameters;
+ method.constructor = constructor;
+
+ if (cls.methods == null) {
+ cls.methods = ArrayListMultimap.create(); // TODO: Size me
+ }
+ cls.methods.put(methodName, method);
+ return method;
+ }
+
+ @NonNull
+ private ClassInfo createClass(@NonNull String containingClass) {
+ ClassInfo cls = mClassMap.get(containingClass);
+ if (cls == null) {
+ cls = new ClassInfo();
+ mClassMap.put(containingClass, cls);
+ }
+ return cls;
+ }
+
+ @NonNull
+ private FieldInfo createField(@NonNull String containingClass, @NonNull String fieldName) {
+ ClassInfo cls = createClass(containingClass);
+ if (cls.fields != null) {
+ FieldInfo field = cls.fields.get(fieldName);
+ if (field != null) {
+ return field;
+ }
+ }
+
+ FieldInfo field = new FieldInfo();
+ if (cls.fields == null) {
+ cls.fields = Maps.newHashMap(); // TODO: Size me
+ }
+ cls.fields.put(fieldName, field);
+ return field;
+ }
+
+ private void mergeMethodOrParameter(Element item, Matcher matcher, String containingClass,
+ String methodName, boolean constructor, String parameters) {
+ parameters = fixParameterString(parameters);
+
+ MethodInfo method = createMethod(containingClass, methodName, constructor, parameters);
+ List<ResolvedAnnotation> annotations = createAnnotations(item);
+
+ String argNum = matcher.group(7);
+ if (argNum != null) {
+ argNum = argNum.trim();
+ int parameter = Integer.parseInt(argNum);
+
+ if (method.parameterAnnotations == null) {
+ // Do I know the parameter count here?
+ int parameterCount = 4;
+ method.parameterAnnotations = ArrayListMultimap
+ .create(parameterCount, annotations.size());
+ }
+ for (ResolvedAnnotation annotation : annotations) {
+ method.parameterAnnotations.put(parameter, annotation);
+ }
+ } else {
+ if (method.annotations == null) {
+ method.annotations = Lists.newArrayListWithExpectedSize(annotations.size());
+ }
+ method.annotations.addAll(annotations);
+ }
+ }
+
+ private void mergeField(Element item, String containingClass, String fieldName) {
+ FieldInfo field = createField(containingClass, fieldName);
+ List<ResolvedAnnotation> annotations = createAnnotations(item);
+ if (field.annotations == null) {
+ field.annotations = Lists.newArrayListWithExpectedSize(annotations.size());
+ }
+ field.annotations.addAll(annotations);
+ }
+
+ private void mergeClass(Element item, String containingClass) {
+ ClassInfo cls = createClass(containingClass);
+ List<ResolvedAnnotation> annotations = createAnnotations(item);
+ if (cls.annotations == null) {
+ cls.annotations = Lists.newArrayListWithExpectedSize(annotations.size());
+ }
+ cls.annotations.addAll(annotations);
+ }
+
+ private List<ResolvedAnnotation> createAnnotations(Element itemElement) {
+ List<Element> children = getChildren(itemElement);
+ List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(children.size());
+ for (Element annotationElement : children) {
+ ResolvedAnnotation annotation = createAnnotation(annotationElement);
+ result.add(annotation);
+ }
+
+ return result;
+ }
+
+ private static class ResolvedExternalAnnotation extends ResolvedAnnotation {
+
+ @NonNull
+ private String mSignature;
+
+ @Nullable
+ private List<Value> mValues;
+
+ public ResolvedExternalAnnotation(@NonNull String signature) {
+ mSignature = signature;
+ }
+
+ void addValue(@NonNull Value value) {
+ if (mValues == null) {
+ mValues = Lists.newArrayList();
+ }
+ mValues.add(value);
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return mSignature;
+ }
+
+ @NonNull
+ @Override
+ public String getSignature() {
+ return mSignature;
+ }
+
+ @Override
+ public int getModifiers() {
+ return Modifier.PUBLIC;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return mSignature.equals(name);
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getType() {
+ return new DefaultTypeDescriptor(mSignature);
+ }
+
+ @Nullable
+ @Override
+ public ResolvedClass getClassType() {
+ // No nested annotations in the database
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<Value> getValues() {
+ return mValues == null ? Collections.<Value>emptyList() : mValues;
+ }
+ }
+
+ private Map<String, ResolvedExternalAnnotation> mMarkerAnnotations = Maps.newHashMapWithExpectedSize(30);
+
+ private ResolvedAnnotation createAnnotation(Element annotationElement) {
+ String tagName = annotationElement.getTagName();
+ assert tagName.equals("annotation") : tagName;
+ String name = annotationElement.getAttribute(ATTR_NAME);
+ assert name != null && !name.isEmpty();
+
+ ResolvedExternalAnnotation annotation = mMarkerAnnotations.get(name);
+ if (annotation != null) {
+ return annotation;
+ }
+
+ annotation = new ResolvedExternalAnnotation(name);
+
+ List<Element> valueElements = getChildren(annotationElement);
+ if (valueElements.isEmpty()
+ // Permission annotations are sometimes used as marker annotations (on
+ // parameters) but that shouldn't let us conclude that any future
+ // permission annotations are. Ditto for @CheckResult, where we sometimes
+ // specify a suggestion.
+ && !name.startsWith(PERMISSION_ANNOTATION)
+ && !name.equals(CHECK_RESULT_ANNOTATION)) {
+ mMarkerAnnotations.put(name, annotation);
+ return annotation;
+ }
+
+ for (Element valueElement : valueElements) {
+ if (valueElement.getTagName().equals("val")) {
+ String valueName = valueElement.getAttribute(ATTR_NAME);
+ String valueString = valueElement.getAttribute("val");
+ if (!valueName.isEmpty() && !valueString.isEmpty()) {
+ // Guess type
+ Object value;
+ if (valueString.equals(VALUE_TRUE)) {
+ value = true;
+ } else if (valueString.equals(VALUE_FALSE)) {
+ value = false;
+ } else if (valueString.startsWith("\"") && valueString.endsWith("\"") &&
+ valueString.length() >= 2) {
+ value = valueString.substring(1, valueString.length() - 1);
+ } else if (valueString.startsWith("{") && valueString.endsWith("}")) {
+ // Array of values
+ String listString = valueString.substring(1, valueString.length() - 1);
+ // We don't know the types, but we'll assume that they're either
+ // all strings (the most common array type in our annotations), or
+ // field references. We can't know the types of the fields; it's
+ // not part of the annotation metadata. We'll place them in an Object[]
+ // for now.
+ boolean allStrings = true;
+ Splitter splitter = Splitter.on(',').omitEmptyStrings().trimResults();
+ List<Object> result = Lists.newArrayList();
+ for (String reference : splitter.split(listString)) {
+ if (reference.startsWith("\"")) {
+ result.add(reference.substring(1, reference.length() - 1));
+ } else {
+ result.add(new ResolvedExternalField(reference));
+ allStrings = false;
+ }
+ }
+ if (allStrings) {
+ value = result.toArray(new String[result.size()]);
+ } else {
+ value = result.toArray();
+ }
+
+ // We don't know the actual type of these fields; we'll assume they're
+ // a special form of
+ } else if (Character.isDigit(valueString.charAt(0))) {
+ try {
+ if (valueString.contains(".")) {
+ value = Double.parseDouble(valueString);
+ } else {
+ value = Long.parseLong(valueString);
+ }
+ } catch (NumberFormatException nufe) {
+ value = valueString;
+ }
+ } else {
+ value = valueString; // unknown type
+ }
+ annotation.addValue(new Value(valueName, value));
+ }
+ }
+ }
+
+ return annotation;
+ }
+ }
+
+ /** Special implementation of a {@link ResolvedField} which can
+ * do equality comparisons with {@link EcjParser.EcjResolvedField} */
+ private static class ResolvedExternalField extends ResolvedField {
+ private final String mSignature;
+
+ public ResolvedExternalField(String signature) {
+ mSignature = signature;
+ assert mSignature.indexOf(' ') == -1 : '"' + mSignature + '"';
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return mSignature.substring(mSignature.lastIndexOf('.') + 1);
+ }
+
+ @Override
+ public String getSignature() {
+ return mSignature;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ResolvedExternalField) {
+ return mSignature.equals(((ResolvedExternalField)obj).mSignature);
+ } else if (obj instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField)obj;
+ if (mSignature.endsWith(field.getName())) {
+ ResolvedClass containingClass = field.getContainingClass();
+ String signature = containingClass != null
+ ? containingClass.getSignature() + "." + field.getName()
+ : field.getName();
+ return mSignature.equals(signature);
+ }
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mSignature.hashCode();
+ }
+
+ @Override
+ public int getModifiers() {
+ return 0;
+ }
+
+ @Override
+ public boolean matches(@NonNull String name) {
+ return mSignature.equals(name);
+ }
+
+ @NonNull
+ @Override
+ public TypeDescriptor getType() {
+ return new DefaultTypeDescriptor(mSignature);
+ }
+
+ @Nullable
+ @Override
+ public ResolvedClass getContainingClass() {
+ return null;
+ }
+
+ @Override
+ public String getContainingClassName() {
+ return mSignature.substring(0, mSignature.lastIndexOf('.'));
+ }
+
+ @Nullable
+ @Override
+ public Object getValue() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ return Collections.emptyList();
+ }
+ }
+
+ @NonNull
+ private static List<Element> getChildren(@NonNull Element element) {
+ NodeList itemList = element.getChildNodes();
+ int length = itemList.getLength();
+ if (length == 0) {
+ return Collections.emptyList();
+ }
+ List<Element> result = new ArrayList<Element>(Math.max(5, length / 2 + 1));
+ for (int i = 0; i < length; i++) {
+ Node node = itemList.item(i);
+ if (node.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ result.add((Element) node);
+ }
+
+ return result;
+ }
+
+ // The parameter declaration used in XML files should not have duplicated spaces,
+ // and there should be no space after commas (we can't however strip out all spaces,
+ // since for example the spaces around the "extends" keyword needs to be there in
+ // types like Map<String,? extends Number>
+ private static String fixParameterString(String parameters) {
+ return parameters.replace(" ", " ").replace(", ", ",");
+ }
+
+ /** For test usage only */
+ @VisibleForTesting
+ static synchronized void set(ExternalAnnotationRepository singleton) {
+ sSingleton = singleton;
+ }
+}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
new file mode 100644
index 0000000..555b01d
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.SdkConstants.DOT_JPG;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.tools.lint.detector.api.LintUtils.endsWith;
+import static com.android.tools.lint.detector.api.TextFormat.HTML;
+import static com.android.tools.lint.detector.api.TextFormat.RAW;
+
+import com.android.tools.lint.checks.BuiltinIssueRegistry;
+import com.android.tools.lint.client.api.Configuration;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.SdkUtils;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A reporter which emits lint results into an HTML report.
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class HtmlReporter extends Reporter {
+ private static final boolean USE_HOLO_STYLE = true;
+ @SuppressWarnings("ConstantConditions")
+ private static final String CSS = USE_HOLO_STYLE
+ ? "hololike.css" : "default.css"; //$NON-NLS-1$ //$NON-NLS-2$
+
+ /**
+ * Maximum number of warnings allowed for a single issue type before we
+ * split up and hide all but the first {@link #SHOWN_COUNT} items.
+ */
+ private static final int SPLIT_LIMIT = 8;
+ /**
+ * When a warning has at least {@link #SPLIT_LIMIT} items, then we show the
+ * following number of items before the "Show more" button/link.
+ */
+ private static final int SHOWN_COUNT = SPLIT_LIMIT - 3;
+
+ protected final Writer mWriter;
+ private String mStripPrefix;
+ private String mFixUrl;
+
+ /**
+ * Creates a new {@link HtmlReporter}
+ *
+ * @param client the associated client
+ * @param output the output file
+ * @throws IOException if an error occurs
+ */
+ public HtmlReporter(LintCliClient client, File output) throws IOException {
+ super(client, output);
+ mWriter = new BufferedWriter(Files.newWriter(output, Charsets.UTF_8));
+ }
+
+ @Override
+ public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException {
+ Map<Issue, String> missing = computeMissingIssues(issues);
+
+ mWriter.write(
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + //$NON-NLS-1$
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + //$NON-NLS-1$
+ "<head>\n" + //$NON-NLS-1$
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" + //$NON-NLS-1$
+ "<title>" + mTitle + "</title>\n"); //$NON-NLS-1$//$NON-NLS-2$
+
+ writeStyleSheet();
+
+ if (!mSimpleFormat) {
+ // JavaScript for collapsing/expanding long lists
+ mWriter.write(
+ "<script language=\"javascript\" type=\"text/javascript\"> \n" + //$NON-NLS-1$
+ "<!--\n" + //$NON-NLS-1$
+ "function reveal(id) {\n" + //$NON-NLS-1$
+ "if (document.getElementById) {\n" + //$NON-NLS-1$
+ "document.getElementById(id).style.display = 'block';\n" + //$NON-NLS-1$
+ "document.getElementById(id+'Link').style.display = 'none';\n" + //$NON-NLS-1$
+ "}\n" + //$NON-NLS-1$
+ "}\n" + //$NON-NLS-1$
+ "//--> \n" + //$NON-NLS-1$
+ "</script>\n"); //$NON-NLS-1$
+ }
+
+ mWriter.write(
+ "</head>\n" + //$NON-NLS-1$
+ "<body>\n" + //$NON-NLS-1$
+ "<h1>" + //$NON-NLS-1$
+ mTitle +
+ "</h1>\n" + //$NON-NLS-1$
+ "<div class=\"titleSeparator\"></div>\n"); //$NON-NLS-1$
+
+ mWriter.write(String.format("Check performed at %1$s.",
+ new Date().toString()));
+ mWriter.write("<br/>\n"); //$NON-NLS-1$
+ mWriter.write(String.format("%1$d errors and %2$d warnings found:",
+ errorCount, warningCount));
+ mWriter.write("<br/><br/>\n"); //$NON-NLS-1$
+
+ Issue previousIssue = null;
+ if (!issues.isEmpty()) {
+ List<List<Warning>> related = new ArrayList<List<Warning>>();
+ List<Warning> currentList = null;
+ for (Warning warning : issues) {
+ if (warning.issue != previousIssue) {
+ previousIssue = warning.issue;
+ currentList = new ArrayList<Warning>();
+ related.add(currentList);
+ }
+ assert currentList != null;
+ currentList.add(warning);
+ }
+
+ writeOverview(related, missing.size());
+
+ Category previousCategory = null;
+ for (List<Warning> warnings : related) {
+ Warning first = warnings.get(0);
+ Issue issue = first.issue;
+
+ if (issue.getCategory() != previousCategory) {
+ previousCategory = issue.getCategory();
+ mWriter.write("\n<a name=\""); //$NON-NLS-1$
+ mWriter.write(issue.getCategory().getFullName());
+ mWriter.write("\"></a>\n"); //$NON-NLS-1$
+ mWriter.write("<div class=\"category\"><a href=\"#\" title=\"Return to top\">"); //$NON-NLS-1$
+ mWriter.write(issue.getCategory().getFullName());
+ mWriter.write("</a><div class=\"categorySeparator\"></div>\n");//$NON-NLS-1$
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ }
+
+ mWriter.write("<a name=\"" + issue.getId() + "\"></a>\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ mWriter.write("<div class=\"issue\">\n"); //$NON-NLS-1$
+
+ // Explain this issue
+ mWriter.write("<div class=\"id\"><a href=\"#\" title=\"Return to top\">"); //$NON-NLS-1$
+ mWriter.write(issue.getId());
+ mWriter.write(": "); //$NON-NLS-1$
+ mWriter.write(issue.getBriefDescription(HTML));
+ mWriter.write("</a><div class=\"issueSeparator\"></div>\n"); //$NON-NLS-1$
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+
+ mWriter.write("<div class=\"warningslist\">\n"); //$NON-NLS-1$
+ boolean partialHide = !mSimpleFormat && warnings.size() > SPLIT_LIMIT;
+
+ int count = 0;
+ for (Warning warning : warnings) {
+ if (partialHide && count == SHOWN_COUNT) {
+ String id = warning.issue.getId() + "Div"; //$NON-NLS-1$
+ mWriter.write("<button id=\""); //$NON-NLS-1$
+ mWriter.write(id);
+ mWriter.write("Link\" onclick=\"reveal('"); //$NON-NLS-1$
+ mWriter.write(id);
+ mWriter.write("');\" />"); //$NON-NLS-1$
+ mWriter.write(String.format("+ %1$d More Occurrences...",
+ warnings.size() - SHOWN_COUNT));
+ mWriter.write("</button>\n"); //$NON-NLS-1$
+ mWriter.write("<div id=\""); //$NON-NLS-1$
+ mWriter.write(id);
+ mWriter.write("\" style=\"display: none\">\n"); //$NON-NLS-1$
+ }
+ count++;
+ String url = null;
+ if (warning.path != null) {
+ url = writeLocation(warning.file, warning.path, warning.line);
+ mWriter.write(':');
+ mWriter.write(' ');
+ }
+
+ // Is the URL for a single image? If so, place it here near the top
+ // of the error floating on the right. If there are multiple images,
+ // they will instead be placed in a horizontal box below the error
+ boolean addedImage = false;
+ if (url != null && warning.location != null
+ && warning.location.getSecondary() == null) {
+ addedImage = addImage(url, warning.location);
+ }
+ mWriter.write("<span class=\"message\">"); //$NON-NLS-1$
+ mWriter.append(RAW.convertTo(warning.message, HTML));
+ mWriter.write("</span>"); //$NON-NLS-1$
+ if (addedImage) {
+ mWriter.write("<br clear=\"right\"/>"); //$NON-NLS-1$
+ } else {
+ mWriter.write("<br />"); //$NON-NLS-1$
+ }
+
+ // Insert surrounding code block window
+ if (warning.line >= 0 && warning.fileContents != null) {
+ mWriter.write("<pre class=\"errorlines\">\n"); //$NON-NLS-1$
+ appendCodeBlock(warning.fileContents, warning.line, warning.offset);
+ mWriter.write("\n</pre>"); //$NON-NLS-1$
+ }
+ mWriter.write('\n');
+ if (warning.location != null && warning.location.getSecondary() != null) {
+ mWriter.write("<ul>");
+ Location l = warning.location.getSecondary();
+ int otherLocations = 0;
+ while (l != null) {
+ String message = l.getMessage();
+ if (message != null && !message.isEmpty()) {
+ Position start = l.getStart();
+ int line = start != null ? start.getLine() : -1;
+ String path = mClient.getDisplayPath(warning.project, l.getFile());
+ writeLocation(l.getFile(), path, line);
+ mWriter.write(':');
+ mWriter.write(' ');
+ mWriter.write("<span class=\"message\">"); //$NON-NLS-1$
+ mWriter.append(RAW.convertTo(message, HTML));
+ mWriter.write("</span>"); //$NON-NLS-1$
+ mWriter.write("<br />"); //$NON-NLS-1$
+
+ String name = l.getFile().getName();
+ if (!(endsWith(name, DOT_PNG) || endsWith(name, DOT_JPG))) {
+ String s = mClient.readFile(l.getFile());
+ if (s != null && !s.isEmpty()) {
+ mWriter.write("<pre class=\"errorlines\">\n"); //$NON-NLS-1$
+ int offset = start != null ? start.getOffset() : -1;
+ appendCodeBlock(s, line, offset);
+ mWriter.write("\n</pre>"); //$NON-NLS-1$
+ }
+ }
+ } else {
+ otherLocations++;
+ }
+
+ l = l.getSecondary();
+ }
+ mWriter.write("</ul>");
+ if (otherLocations > 0) {
+ String id = "Location" + count + "Div"; //$NON-NLS-1$
+ mWriter.write("<button id=\""); //$NON-NLS-1$
+ mWriter.write(id);
+ mWriter.write("Link\" onclick=\"reveal('"); //$NON-NLS-1$
+ mWriter.write(id);
+ mWriter.write("');\" />"); //$NON-NLS-1$
+ mWriter.write(String.format("+ %1$d Additional Locations...",
+ otherLocations));
+ mWriter.write("</button>\n"); //$NON-NLS-1$
+ mWriter.write("<div id=\""); //$NON-NLS-1$
+ mWriter.write(id);
+ mWriter.write("\" style=\"display: none\">\n"); //$NON-NLS-1$
+
+ mWriter.write("Additional locations: ");
+ mWriter.write("<ul>\n"); //$NON-NLS-1$
+ l = warning.location.getSecondary();
+ while (l != null) {
+ Position start = l.getStart();
+ int line = start != null ? start.getLine() : -1;
+ String path = mClient.getDisplayPath(warning.project, l.getFile());
+ mWriter.write("<li> "); //$NON-NLS-1$
+ writeLocation(l.getFile(), path, line);
+ mWriter.write("\n"); //$NON-NLS-1$
+ l = l.getSecondary();
+ }
+ mWriter.write("</ul>\n"); //$NON-NLS-1$
+
+ mWriter.write("</div><br/><br/>\n"); //$NON-NLS-1$
+ }
+ }
+
+ // Place a block of images?
+ if (!addedImage && url != null && warning.location != null
+ && warning.location.getSecondary() != null) {
+ addImage(url, warning.location);
+ }
+
+ if (warning.isVariantSpecific()) {
+ mWriter.write("\n");
+ mWriter.write("Applies to variants: ");
+ mWriter.write(Joiner.on(", ").join(warning.getIncludedVariantNames()));
+ mWriter.write("<br/>\n");
+ mWriter.write("Does <b>not</b> apply to variants: ");
+ mWriter.write(Joiner.on(", ").join(warning.getExcludedVariantNames()));
+ mWriter.write("<br/>\n");
+ }
+ }
+ if (partialHide) { // Close up the extra div
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ }
+
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ writeIssueMetadata(issue, first.severity, null);
+
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ }
+
+ if (!mClient.isCheckingSpecificIssues()) {
+ writeMissingIssues(missing);
+ }
+
+ writeSuppressInfo();
+ } else {
+ mWriter.write("Congratulations!");
+ }
+ mWriter.write("\n</body>\n</html>"); //$NON-NLS-1$
+ mWriter.close();
+
+ if (!mClient.getFlags().isQuiet()
+ && (mDisplayEmpty || errorCount > 0 || warningCount > 0)) {
+ String url = SdkUtils.fileToUrlString(mOutput.getAbsoluteFile());
+ System.out.println(String.format("Wrote HTML report to %1$s", url));
+ }
+ }
+
+ private void writeIssueMetadata(Issue issue, Severity severity, String disabledBy)
+ throws IOException {
+ mWriter.write("<div class=\"metadata\">"); //$NON-NLS-1$
+
+ if (mClient.getRegistry() instanceof BuiltinIssueRegistry) {
+ boolean adtHasFix = QuickfixHandler.ADT.hasAutoFix(issue);
+ boolean studioHasFix = QuickfixHandler.STUDIO.hasAutoFix(issue);
+ if (adtHasFix || studioHasFix) {
+ String adt = "Eclipse/ADT";
+ String studio = "Android Studio/IntelliJ";
+ String tools = adtHasFix && studioHasFix
+ ? (studio + " & " + adt) : studioHasFix ? studio : adt;
+ mWriter.write("Note: This issue has an associated quickfix operation in " + tools);
+ if (mFixUrl != null) {
+ mWriter.write(" <img alt=\"Fix\" border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
+ mWriter.write(mFixUrl);
+ mWriter.write("\" />\n"); //$NON-NLS-1$
+ }
+
+ mWriter.write("<br>\n");
+ }
+ }
+
+ if (disabledBy != null) {
+ mWriter.write(String.format("Disabled By: %1$s<br/>\n", disabledBy));
+ }
+
+ mWriter.write("Priority: ");
+ mWriter.write(String.format("%1$d / 10", issue.getPriority()));
+ mWriter.write("<br/>\n"); //$NON-NLS-1$
+ mWriter.write("Category: ");
+ mWriter.write(issue.getCategory().getFullName());
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+
+ mWriter.write("Severity: ");
+ if (severity == Severity.ERROR || severity == Severity.FATAL) {
+ mWriter.write("<span class=\"error\">"); //$NON-NLS-1$
+ } else if (severity == Severity.WARNING) {
+ mWriter.write("<span class=\"warning\">"); //$NON-NLS-1$
+ } else {
+ mWriter.write("<span>"); //$NON-NLS-1$
+ }
+ appendEscapedText(severity.getDescription());
+ mWriter.write("</span>"); //$NON-NLS-1$
+
+ mWriter.write("<div class=\"summary\">\n"); //$NON-NLS-1$
+ mWriter.write("Explanation: ");
+ String description = issue.getBriefDescription(HTML);
+ mWriter.write(description);
+ if (!description.isEmpty()
+ && Character.isLetter(description.charAt(description.length() - 1))) {
+ mWriter.write('.');
+ }
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ mWriter.write("<div class=\"explanation\">\n"); //$NON-NLS-1$
+ String explanationHtml = issue.getExplanation(HTML);
+ mWriter.write(explanationHtml);
+ mWriter.write("\n</div>\n"); //$NON-NLS-1$;
+ List<String> moreInfo = issue.getMoreInfo();
+ mWriter.write("<br/>"); //$NON-NLS-1$
+ mWriter.write("<div class=\"moreinfo\">"); //$NON-NLS-1$
+ mWriter.write("More info: ");
+ int count = moreInfo.size();
+ if (count > 1) {
+ mWriter.write("<ul>"); //$NON-NLS-1$
+ }
+ for (String uri : moreInfo) {
+ if (count > 1) {
+ mWriter.write("<li>"); //$NON-NLS-1$
+ }
+ mWriter.write("<a href=\""); //$NON-NLS-1$
+ mWriter.write(uri);
+ mWriter.write("\">" ); //$NON-NLS-1$
+ mWriter.write(uri);
+ mWriter.write("</a>\n"); //$NON-NLS-1$
+ }
+ if (count > 1) {
+ mWriter.write("</ul>"); //$NON-NLS-1$
+ }
+ mWriter.write("</div>"); //$NON-NLS-1$
+
+ mWriter.write("<br/>"); //$NON-NLS-1$
+ mWriter.write(String.format(
+ "To suppress this error, use the issue id \"%1$s\" as explained in the " +
+ "%2$sSuppressing Warnings and Errors%3$s section.",
+ issue.getId(),
+ "<a href=\"#SuppressInfo\">", "</a>")); //$NON-NLS-1$ //$NON-NLS-2$
+ mWriter.write("<br/>\n");
+ }
+
+ private void writeSuppressInfo() throws IOException {
+ //getSuppressHelp
+ mWriter.write("\n<a name=\"SuppressInfo\"></a>\n"); //$NON-NLS-1$
+ mWriter.write("<div class=\"category\">"); //$NON-NLS-1$
+ mWriter.write("Suppressing Warnings and Errors");
+ mWriter.write("<div class=\"categorySeparator\"></div>\n");//$NON-NLS-1$
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ mWriter.write(TextFormat.RAW.convertTo(Main.getSuppressHelp(), TextFormat.HTML));
+ mWriter.write('\n');
+ }
+
+ protected Map<Issue, String> computeMissingIssues(List<Warning> warnings) {
+ Set<Project> projects = new HashSet<Project>();
+ Set<Issue> seen = new HashSet<Issue>();
+ for (Warning warning : warnings) {
+ projects.add(warning.project);
+ seen.add(warning.issue);
+ }
+ Configuration cliConfiguration = mClient.getConfiguration();
+ Map<Issue, String> map = Maps.newHashMap();
+ for (Issue issue : mClient.getRegistry().getIssues()) {
+ if (!seen.contains(issue)) {
+ if (mClient.isSuppressed(issue)) {
+ map.put(issue, "Command line flag");
+ continue;
+ }
+
+ if (!issue.isEnabledByDefault() && !mClient.isAllEnabled()) {
+ map.put(issue, "Default");
+ continue;
+ }
+
+ if (cliConfiguration != null && !cliConfiguration.isEnabled(issue)) {
+ map.put(issue, "Command line supplied --config lint.xml file");
+ continue;
+ }
+
+ // See if any projects disable this warning
+ for (Project project : projects) {
+ if (!project.getConfiguration(null).isEnabled(issue)) {
+ map.put(issue, "Project lint.xml file");
+ break;
+ }
+ }
+ }
+ }
+
+ return map;
+ }
+
+ private void writeMissingIssues(Map<Issue, String> missing) throws IOException {
+ mWriter.write("\n<a name=\"MissingIssues\"></a>\n"); //$NON-NLS-1$
+ mWriter.write("<div class=\"category\">"); //$NON-NLS-1$
+ mWriter.write("Disabled Checks");
+ mWriter.write("<div class=\"categorySeparator\"></div>\n"); //$NON-NLS-1$
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+
+ mWriter.write(
+ "The following issues were not run by lint, either " +
+ "because the check is not enabled by default, or because " +
+ "it was disabled with a command line flag or via one or " +
+ "more lint.xml configuration files in the project directories.");
+ mWriter.write("\n<br/><br/>\n"); //$NON-NLS-1$
+
+ List<Issue> list = new ArrayList<Issue>(missing.keySet());
+ Collections.sort(list);
+
+
+ for (Issue issue : list) {
+ mWriter.write("<a name=\"" + issue.getId() + "\"></a>\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ mWriter.write("<div class=\"issue\">\n"); //$NON-NLS-1$
+
+ // Explain this issue
+ mWriter.write("<div class=\"id\">"); //$NON-NLS-1$
+ mWriter.write(issue.getId());
+ mWriter.write("<div class=\"issueSeparator\"></div>\n"); //$NON-NLS-1$
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ String disabledBy = missing.get(issue);
+ writeIssueMetadata(issue, issue.getDefaultSeverity(), disabledBy);
+ mWriter.write("</div>\n"); //$NON-NLS-1$
+ }
+ }
+
+ protected void writeStyleSheet() throws IOException {
+ if (USE_HOLO_STYLE) {
+ mWriter.write(
+ "<link rel=\"stylesheet\" type=\"text/css\" " + //$NON-NLS-1$
+ "href=\"http://fonts.googleapis.com/css?family=Roboto\" />\n" );//$NON-NLS-1$
+ }
+
+ URL cssUrl = HtmlReporter.class.getResource(CSS);
+ if (mSimpleFormat) {
+ // Inline the CSS
+ mWriter.write("<style>\n"); //$NON-NLS-1$
+ InputStream input = cssUrl.openStream();
+ byte[] bytes = ByteStreams.toByteArray(input);
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ String css = new String(bytes, Charsets.UTF_8);
+ mWriter.write(css);
+ mWriter.write("</style>\n"); //$NON-NLS-1$
+ } else {
+ String ref = addLocalResources(cssUrl);
+ if (ref != null) {
+ mWriter.write(
+ "<link rel=\"stylesheet\" type=\"text/css\" href=\"" //$NON-NLS-1$
+ + ref + "\" />\n"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ private void writeOverview(List<List<Warning>> related, int missingCount)
+ throws IOException {
+ // Write issue id summary
+ mWriter.write("<table class=\"overview\">\n"); //$NON-NLS-1$
+
+ String errorUrl = null;
+ String warningUrl = null;
+ if (!mSimpleFormat) {
+ errorUrl = addLocalResources(getErrorIconUrl());
+ warningUrl = addLocalResources(getWarningIconUrl());
+ mFixUrl = addLocalResources(HtmlReporter.class.getResource("lint-run.png")); //$NON-NLS-1$)
+ }
+
+ Category previousCategory = null;
+ for (List<Warning> warnings : related) {
+ Issue issue = warnings.get(0).issue;
+
+ boolean isError = false;
+ for (Warning warning : warnings) {
+ if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) {
+ isError = true;
+ break;
+ }
+ }
+
+ if (issue.getCategory() != previousCategory) {
+ mWriter.write("<tr><td></td><td class=\"categoryColumn\">");
+ previousCategory = issue.getCategory();
+ String categoryName = issue.getCategory().getFullName();
+ mWriter.write("<a href=\"#"); //$NON-NLS-1$
+ mWriter.write(categoryName);
+ mWriter.write("\">"); //$NON-NLS-1$
+ mWriter.write(categoryName);
+ mWriter.write("</a>\n"); //$NON-NLS-1$
+ mWriter.write("</td></tr>"); //$NON-NLS-1$
+ mWriter.write("\n"); //$NON-NLS-1$
+ }
+ mWriter.write("<tr>\n"); //$NON-NLS-1$
+
+ // Count column
+ mWriter.write("<td class=\"countColumn\">"); //$NON-NLS-1$
+ mWriter.write(Integer.toString(warnings.size()));
+ mWriter.write("</td>"); //$NON-NLS-1$
+
+ mWriter.write("<td class=\"issueColumn\">"); //$NON-NLS-1$
+
+ String imageUrl = isError ? errorUrl : warningUrl;
+ if (imageUrl != null) {
+ mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
+ mWriter.write(imageUrl);
+ mWriter.write("\" alt=\"");
+ mWriter.write(isError ? "Error" : "Warning");
+ mWriter.write("\" />\n"); //$NON-NLS-1$
+ }
+
+ mWriter.write("<a href=\"#"); //$NON-NLS-1$
+ mWriter.write(issue.getId());
+ mWriter.write("\">"); //$NON-NLS-1$
+ mWriter.write(issue.getId());
+ mWriter.write(": "); //$NON-NLS-1$
+ mWriter.write(issue.getBriefDescription(HTML));
+ mWriter.write("</a>\n"); //$NON-NLS-1$
+
+ mWriter.write("</td></tr>\n");
+ }
+
+ if (missingCount > 0 && !mClient.isCheckingSpecificIssues()) {
+ mWriter.write("<tr><td></td>"); //$NON-NLS-1$
+ mWriter.write("<td class=\"categoryColumn\">"); //$NON-NLS-1$
+ mWriter.write("<a href=\"#MissingIssues\">"); //$NON-NLS-1$
+ mWriter.write(String.format("Disabled Checks (%1$d)",
+ missingCount));
+
+ mWriter.write("</a>\n"); //$NON-NLS-1$
+ mWriter.write("</td></tr>"); //$NON-NLS-1$
+ }
+
+ mWriter.write("</table>\n"); //$NON-NLS-1$
+ mWriter.write("<br/>"); //$NON-NLS-1$
+ }
+
+ private String writeLocation(File file, String path, int line) throws IOException {
+ String url;
+ mWriter.write("<span class=\"location\">"); //$NON-NLS-1$
+
+ url = getUrl(file);
+ if (url != null) {
+ mWriter.write("<a href=\""); //$NON-NLS-1$
+ mWriter.write(url);
+ mWriter.write("\">"); //$NON-NLS-1$
+ }
+
+ String displayPath = stripPath(path);
+ if (url != null && url.startsWith("../") && new File(displayPath).isAbsolute()) {
+ displayPath = url;
+ }
+ mWriter.write(displayPath);
+ //noinspection VariableNotUsedInsideIf
+ if (url != null) {
+ mWriter.write("</a>"); //$NON-NLS-1$
+ }
+ if (line >= 0) {
+ // 0-based line numbers, but display 1-based
+ mWriter.write(':');
+ mWriter.write(Integer.toString(line + 1));
+ }
+ mWriter.write("</span>"); //$NON-NLS-1$
+ return url;
+ }
+
+ private boolean addImage(String url, Location location) throws IOException {
+ if (url != null && endsWith(url, DOT_PNG)) {
+ if (location.getSecondary() != null) {
+ // Emit many images
+ // Add in linked images as well
+ List<String> urls = new ArrayList<String>();
+ while (location != null && location.getFile() != null) {
+ String imageUrl = getUrl(location.getFile());
+ if (imageUrl != null
+ && endsWith(imageUrl, DOT_PNG)) {
+ urls.add(imageUrl);
+ }
+ location = location.getSecondary();
+ }
+ if (!urls.isEmpty()) {
+ // Sort in order
+ Collections.sort(urls, new Comparator<String>() {
+ @Override
+ public int compare(String s1, String s2) {
+ return getDpiRank(s1) - getDpiRank(s2);
+ }
+ });
+ mWriter.write("<table>"); //$NON-NLS-1$
+ mWriter.write("<tr>"); //$NON-NLS-1$
+ for (String linkedUrl : urls) {
+ // Image series: align top
+ mWriter.write("<td>"); //$NON-NLS-1$
+ mWriter.write("<a href=\""); //$NON-NLS-1$
+ mWriter.write(linkedUrl);
+ mWriter.write("\">"); //$NON-NLS-1$
+ mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$
+ mWriter.write(linkedUrl);
+ mWriter.write("\" /></a>\n"); //$NON-NLS-1$
+ mWriter.write("</td>"); //$NON-NLS-1$
+ }
+ mWriter.write("</tr>"); //$NON-NLS-1$
+
+ mWriter.write("<tr>"); //$NON-NLS-1$
+ for (String linkedUrl : urls) {
+ mWriter.write("<th>"); //$NON-NLS-1$
+ int index = linkedUrl.lastIndexOf("drawable-"); //$NON-NLS-1$
+ if (index != -1) {
+ index += "drawable-".length(); //$NON-NLS-1$
+ int end = linkedUrl.indexOf('/', index);
+ if (end != -1) {
+ mWriter.write(linkedUrl.substring(index, end));
+ }
+ }
+ mWriter.write("</th>"); //$NON-NLS-1$
+ }
+ mWriter.write("</tr>\n"); //$NON-NLS-1$
+
+ mWriter.write("</table>\n"); //$NON-NLS-1$
+ }
+ } else {
+ // Just this image: float to the right
+ mWriter.write("<img class=\"embedimage\" align=\"right\" src=\""); //$NON-NLS-1$
+ mWriter.write(url);
+ mWriter.write("\" />"); //$NON-NLS-1$
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /** Provide a sorting rank for a url */
+ private static int getDpiRank(String url) {
+ if (url.contains("-xhdpi")) { //$NON-NLS-1$
+ return 0;
+ } else if (url.contains("-hdpi")) { //$NON-NLS-1$
+ return 1;
+ } else if (url.contains("-mdpi")) { //$NON-NLS-1$
+ return 2;
+ } else if (url.contains("-ldpi")) { //$NON-NLS-1$
+ return 3;
+ } else {
+ return 4;
+ }
+ }
+
+ private void appendCodeBlock(String contents, int lineno, int offset)
+ throws IOException {
+ int max = lineno + 3;
+ int min = lineno - 3;
+ for (int l = min; l < max; l++) {
+ if (l >= 0) {
+ int lineOffset = LintCliClient.getLineOffset(contents, l);
+ if (lineOffset == -1) {
+ break;
+ }
+
+ mWriter.write(String.format("<span class=\"lineno\">%1$4d</span> ", (l + 1))); //$NON-NLS-1$
+
+ String line = LintCliClient.getLineOfOffset(contents, lineOffset);
+ if (offset != -1 && lineOffset <= offset && lineOffset+line.length() >= offset) {
+ // Text nodes do not always have correct lines/offsets
+ //assert l == lineno;
+
+ // This line contains the beginning of the offset
+ // First print everything before
+ int delta = offset - lineOffset;
+ appendEscapedText(line.substring(0, delta));
+ mWriter.write("<span class=\"errorspan\">"); //$NON-NLS-1$
+ appendEscapedText(line.substring(delta));
+ mWriter.write("</span>"); //$NON-NLS-1$
+ } else if (offset == -1 && l == lineno) {
+ mWriter.write("<span class=\"errorline\">"); //$NON-NLS-1$
+ appendEscapedText(line);
+ mWriter.write("</span>"); //$NON-NLS-1$
+ } else {
+ appendEscapedText(line);
+ }
+ if (l < max - 1) {
+ mWriter.write("\n"); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ protected void appendEscapedText(String textValue) throws IOException {
+ for (int i = 0, n = textValue.length(); i < n; i++) {
+ char c = textValue.charAt(i);
+ if (c == '<') {
+ mWriter.write("<"); //$NON-NLS-1$
+ } else if (c == '&') {
+ mWriter.write("&"); //$NON-NLS-1$
+ } else if (c == '\n') {
+ mWriter.write("<br/>\n");
+ } else {
+ if (c > 255) {
+ mWriter.write("&#"); //$NON-NLS-1$
+ mWriter.write(Integer.toString(c));
+ mWriter.write(';');
+ } else {
+ mWriter.write(c);
+ }
+ }
+ }
+ }
+
+ private String stripPath(String path) {
+ if (mStripPrefix != null && path.startsWith(mStripPrefix)
+ && path.length() > mStripPrefix.length()) {
+ int index = mStripPrefix.length();
+ if (path.charAt(index) == File.separatorChar) {
+ index++;
+ }
+ return path.substring(index);
+ }
+
+ return path;
+ }
+
+ /** Sets path prefix to strip from displayed file names */
+ void setStripPrefix(String prefix) {
+ mStripPrefix = prefix;
+ }
+
+ static URL getWarningIconUrl() {
+ return HtmlReporter.class.getResource("lint-warning.png"); //$NON-NLS-1$
+ }
+
+ static URL getErrorIconUrl() {
+ return HtmlReporter.class.getResource("lint-error.png"); //$NON-NLS-1$
+ }
+}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
new file mode 100644
index 0000000..91f0ebb
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.tools.lint.LintCliFlags.ERRNO_ERRORS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_SUCCESS;
+import static com.android.tools.lint.client.api.IssueRegistry.LINT_ERROR;
+import static com.android.tools.lint.client.api.IssueRegistry.PARSER_ERROR;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.checks.HardcodedValuesDetector;
+import com.android.tools.lint.client.api.Configuration;
+import com.android.tools.lint.client.api.DefaultConfiguration;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.client.api.LintListener;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.client.api.XmlParser;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closeables;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Lint client for command line usage. Supports the flags in {@link LintCliFlags},
+ * and offers text, HTML and XML reporting, etc.
+ * <p>
+ * Minimal example:
+ * <pre>
+ * // files is a list of java.io.Files, typically a directory containing
+ * // lint projects or direct references to project root directories
+ * IssueRegistry registry = new BuiltinIssueRegistry();
+ * LintCliFlags flags = new LintCliFlags();
+ * LintCliClient client = new LintCliClient(flags);
+ * int exitCode = client.run(registry, files);
+ * </pre>
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class LintCliClient extends LintClient {
+ protected final List<Warning> mWarnings = new ArrayList<Warning>();
+ protected boolean mHasErrors;
+ protected int mErrorCount;
+ protected int mWarningCount;
+ protected IssueRegistry mRegistry;
+ protected LintDriver mDriver;
+ protected final LintCliFlags mFlags;
+ private Configuration mConfiguration;
+ private boolean mValidatedIds;
+
+ /** Creates a CLI driver */
+ public LintCliClient() {
+ super(CLIENT_CLI);
+ mFlags = new LintCliFlags();
+ TextReporter reporter = new TextReporter(this, mFlags, new PrintWriter(System.out, true),
+ false);
+ mFlags.getReporters().add(reporter);
+ }
+
+ /** Creates a CLI driver */
+ public LintCliClient(@NonNull LintCliFlags flags, @NonNull String clientName) {
+ super(clientName);
+ mFlags = flags;
+ }
+
+ /**
+ * Runs the static analysis command line driver. You need to add at least one error reporter
+ * to the command line flags.
+ */
+ public int run(@NonNull IssueRegistry registry, @NonNull List<File> files) throws IOException {
+ assert !mFlags.getReporters().isEmpty();
+ mRegistry = registry;
+ mDriver = new LintDriver(registry, this);
+
+ mDriver.setAbbreviating(!mFlags.isShowEverything());
+ addProgressPrinter();
+ mDriver.addLintListener(new LintListener() {
+ @Override
+ public void update(@NonNull LintDriver driver, @NonNull EventType type,
+ @Nullable Context context) {
+ if (type == EventType.SCANNING_PROJECT && !mValidatedIds) {
+ // Make sure all the id's are valid once the driver is all set up and
+ // ready to run (such that custom rules are available in the registry etc)
+ validateIssueIds(context != null ? context.getProject() : null);
+ }
+ }
+ });
+
+ mDriver.analyze(createLintRequest(files));
+
+ Collections.sort(mWarnings);
+
+ boolean hasConsoleOutput = false;
+ for (Reporter reporter : mFlags.getReporters()) {
+ reporter.write(mErrorCount, mWarningCount, mWarnings);
+ if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
+ hasConsoleOutput = true;
+ }
+ }
+
+ if (!mFlags.isQuiet() && !hasConsoleOutput) {
+ System.out.println(String.format(
+ "Lint found %1$d errors and %2$d warnings", mErrorCount, mWarningCount));
+ }
+
+ return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
+ }
+
+ protected void addProgressPrinter() {
+ if (!mFlags.isQuiet()) {
+ mDriver.addLintListener(new ProgressPrinter());
+ }
+ }
+
+ /** Creates a lint request */
+ @NonNull
+ protected LintRequest createLintRequest(@NonNull List<File> files) {
+ return new LintRequest(this, files);
+ }
+
+ @Override
+ public void log(
+ @NonNull Severity severity,
+ @Nullable Throwable exception,
+ @Nullable String format,
+ @Nullable Object... args) {
+ System.out.flush();
+ if (!mFlags.isQuiet()) {
+ // Place the error message on a line of its own since we're printing '.' etc
+ // with newlines during analysis
+ System.err.println();
+ }
+ if (format != null) {
+ System.err.println(String.format(format, args));
+ }
+ if (exception != null) {
+ exception.printStackTrace();
+ }
+ }
+
+ @Override
+ public XmlParser getXmlParser() {
+ return new LintCliXmlParser();
+ }
+
+ @NonNull
+ @Override
+ public Configuration getConfiguration(@NonNull Project project, @Nullable LintDriver driver) {
+ return new CliConfiguration(getConfiguration(), project, mFlags.isFatalOnly());
+ }
+
+ /** File content cache */
+ private final Map<File, String> mFileContents = new HashMap<File, String>(100);
+
+ /** Read the contents of the given file, possibly cached */
+ private String getContents(File file) {
+ String s = mFileContents.get(file);
+ if (s == null) {
+ s = readFile(file);
+ mFileContents.put(file, s);
+ }
+
+ return s;
+ }
+
+ @Override
+ public JavaParser getJavaParser(@Nullable Project project) {
+ return new EcjParser(this, project);
+ }
+
+ @Override
+ public void report(
+ @NonNull Context context,
+ @NonNull Issue issue,
+ @NonNull Severity severity,
+ @Nullable Location location,
+ @NonNull String message,
+ @NonNull TextFormat format) {
+ assert context.isEnabled(issue) || issue == LINT_ERROR;
+
+ if (severity == Severity.IGNORE) {
+ return;
+ }
+
+ if (severity == Severity.ERROR || severity == Severity.FATAL) {
+ mHasErrors = true;
+ mErrorCount++;
+ } else {
+ mWarningCount++;
+ }
+
+ // Store the message in the raw format internally such that we can
+ // convert it to text for the text reporter, HTML for the HTML reporter
+ // and so on.
+ message = format.convertTo(message, TextFormat.RAW);
+ Warning warning = new Warning(issue, message, severity, context.getProject());
+ mWarnings.add(warning);
+
+ if (location != null) {
+ warning.location = location;
+ File file = location.getFile();
+ if (file != null) {
+ warning.file = file;
+ warning.path = getDisplayPath(context.getProject(), file);
+ }
+
+ Position startPosition = location.getStart();
+ if (startPosition != null) {
+ int line = startPosition.getLine();
+ warning.line = line;
+ warning.offset = startPosition.getOffset();
+ if (line >= 0) {
+ if (context.file == location.getFile()) {
+ warning.fileContents = context.getContents();
+ }
+ if (warning.fileContents == null) {
+ warning.fileContents = getContents(location.getFile());
+ }
+
+ if (mFlags.isShowSourceLines()) {
+ // Compute error line contents
+ warning.errorLine = getLine(warning.fileContents, line);
+ if (warning.errorLine != null) {
+ // Replace tabs with spaces such that the column
+ // marker (^) lines up properly:
+ warning.errorLine = warning.errorLine.replace('\t', ' ');
+ int column = startPosition.getColumn();
+ if (column < 0) {
+ column = 0;
+ for (int i = 0; i < warning.errorLine.length(); i++, column++) {
+ if (!Character.isWhitespace(warning.errorLine.charAt(i))) {
+ break;
+ }
+ }
+ }
+ StringBuilder sb = new StringBuilder(100);
+ sb.append(warning.errorLine);
+ sb.append('\n');
+ for (int i = 0; i < column; i++) {
+ sb.append(' ');
+ }
+
+ boolean displayCaret = true;
+ Position endPosition = location.getEnd();
+ if (endPosition != null) {
+ int endLine = endPosition.getLine();
+ int endColumn = endPosition.getColumn();
+ if (endLine == line && endColumn > column) {
+ for (int i = column; i < endColumn; i++) {
+ sb.append('~');
+ }
+ displayCaret = false;
+ }
+ }
+
+ if (displayCaret) {
+ sb.append('^');
+ }
+ sb.append('\n');
+ warning.errorLine = sb.toString();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Look up the contents of the given line */
+ static String getLine(String contents, int line) {
+ int index = getLineOffset(contents, line);
+ if (index != -1) {
+ return getLineOfOffset(contents, index);
+ } else {
+ return null;
+ }
+ }
+
+ static String getLineOfOffset(String contents, int offset) {
+ int end = contents.indexOf('\n', offset);
+ if (end == -1) {
+ end = contents.indexOf('\r', offset);
+ }
+ return contents.substring(offset, end != -1 ? end : contents.length());
+ }
+
+
+ /** Look up the contents of the given line */
+ static int getLineOffset(String contents, int line) {
+ int index = 0;
+ for (int i = 0; i < line; i++) {
+ index = contents.indexOf('\n', index);
+ if (index == -1) {
+ return -1;
+ }
+ index++;
+ }
+
+ return index;
+ }
+
+ @NonNull
+ @Override
+ public String readFile(@NonNull File file) {
+ try {
+ return LintUtils.getEncodedString(this, file);
+ } catch (IOException e) {
+ return ""; //$NON-NLS-1$
+ }
+ }
+
+ boolean isCheckingSpecificIssues() {
+ return mFlags.getExactCheckedIds() != null;
+ }
+
+ private Map<Project, ClassPathInfo> mProjectInfo;
+
+ @Override
+ @NonNull
+ protected ClassPathInfo getClassPath(@NonNull Project project) {
+ ClassPathInfo classPath = super.getClassPath(project);
+
+ List<File> sources = mFlags.getSourcesOverride();
+ List<File> classes = mFlags.getClassesOverride();
+ List<File> libraries = mFlags.getLibrariesOverride();
+ if (classes == null && sources == null && libraries == null) {
+ return classPath;
+ }
+
+ ClassPathInfo info;
+ if (mProjectInfo == null) {
+ mProjectInfo = Maps.newHashMap();
+ info = null;
+ } else {
+ info = mProjectInfo.get(project);
+ }
+
+ if (info == null) {
+ if (sources == null) {
+ sources = classPath.getSourceFolders();
+ }
+ if (classes == null) {
+ classes = classPath.getClassFolders();
+ }
+ if (libraries == null) {
+ libraries = classPath.getLibraries(true);
+ }
+
+ info = new ClassPathInfo(sources, classes, libraries, classPath.getLibraries(false),
+ classPath.getTestSourceFolders());
+ mProjectInfo.put(project, info);
+ }
+
+ return info;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getResourceFolders(@NonNull Project project) {
+ List<File> resources = mFlags.getResourcesOverride();
+ if (resources == null) {
+ return super.getResourceFolders(project);
+ }
+
+ return resources;
+ }
+
+ /**
+ * Consult the lint.xml file, but override with the --enable and --disable
+ * flags supplied on the command line
+ */
+ class CliConfiguration extends DefaultConfiguration {
+ private boolean mFatalOnly;
+
+ CliConfiguration(@NonNull Configuration parent, @NonNull Project project,
+ boolean fatalOnly) {
+ super(LintCliClient.this, project, parent);
+ mFatalOnly = fatalOnly;
+ }
+
+ CliConfiguration(File lintFile, boolean fatalOnly) {
+ super(LintCliClient.this, null /*project*/, null /*parent*/, lintFile);
+ mFatalOnly = fatalOnly;
+ }
+
+ @NonNull
+ @Override
+ public Severity getSeverity(@NonNull Issue issue) {
+ Severity severity = computeSeverity(issue);
+
+ if (mFatalOnly && severity != Severity.FATAL) {
+ return Severity.IGNORE;
+ }
+
+ if (mFlags.isWarningsAsErrors() && severity == Severity.WARNING) {
+ severity = Severity.ERROR;
+ }
+
+ if (mFlags.isIgnoreWarnings() && severity == Severity.WARNING) {
+ severity = Severity.IGNORE;
+ }
+
+ return severity;
+ }
+
+ @NonNull
+ @Override
+ protected Severity getDefaultSeverity(@NonNull Issue issue) {
+ if (mFlags.isCheckAllWarnings()) {
+ return issue.getDefaultSeverity();
+ }
+
+ return super.getDefaultSeverity(issue);
+ }
+
+ private Severity computeSeverity(@NonNull Issue issue) {
+ Severity severity = super.getSeverity(issue);
+
+ String id = issue.getId();
+ Set<String> suppress = mFlags.getSuppressedIds();
+ if (suppress.contains(id)) {
+ return Severity.IGNORE;
+ }
+
+ Severity manual = mFlags.getSeverityOverrides().get(id);
+ if (manual != null) {
+ return manual;
+ }
+
+ Set<String> enabled = mFlags.getEnabledIds();
+ Set<String> check = mFlags.getExactCheckedIds();
+ if (enabled.contains(id) || (check != null && check.contains(id))) {
+ // Overriding default
+ // Detectors shouldn't be returning ignore as a default severity,
+ // but in case they do, force it up to warning here to ensure that
+ // it's run
+ if (severity == Severity.IGNORE) {
+ severity = issue.getDefaultSeverity();
+ if (severity == Severity.IGNORE) {
+ severity = Severity.WARNING;
+ }
+ }
+
+ return severity;
+ }
+
+ if (check != null && issue != LINT_ERROR && issue != PARSER_ERROR) {
+ return Severity.IGNORE;
+ }
+
+ return severity;
+ }
+ }
+
+ /**
+ * Checks that any id's specified by id refer to valid, known, issues. This
+ * typically can't be done right away (in for example the Gradle code which
+ * handles DSL references to strings, or in the command line parser for the
+ * lint command) because the full set of valid id's is not known until lint
+ * actually starts running and for example gathers custom rules from all
+ * AAR dependencies reachable from libraries, etc.
+ */
+ private void validateIssueIds(@Nullable Project project) {
+ if (mDriver != null) {
+ IssueRegistry registry = mDriver.getRegistry();
+ if (!registry.isIssueId(HardcodedValuesDetector.ISSUE.getId())) {
+ // This should not be necessary, but there have been some strange
+ // reports where lint has reported some well known builtin issues
+ // to not exist:
+ //
+ // Error: Unknown issue id "DuplicateDefinition" [LintError]
+ // Error: Unknown issue id "GradleIdeError" [LintError]
+ // Error: Unknown issue id "InvalidPackage" [LintError]
+ // Error: Unknown issue id "JavascriptInterface" [LintError]
+ // ...
+ //
+ // It's not clear how this can happen, though it's probably related
+ // to using 3rd party lint rules (where lint will create new composite
+ // issue registries to wrap the various additional issues) - but
+ // we definitely don't want to validate issue id's if we can't find
+ // well known issues.
+ return;
+ }
+ mValidatedIds = true;
+ validateIssueIds(project, registry, mFlags.getExactCheckedIds());
+ validateIssueIds(project, registry, mFlags.getEnabledIds());
+ validateIssueIds(project, registry, mFlags.getSuppressedIds());
+ validateIssueIds(project, registry, mFlags.getSeverityOverrides().keySet());
+ }
+ }
+
+ private void validateIssueIds(@Nullable Project project, @NonNull IssueRegistry registry,
+ @Nullable Collection<String> ids) {
+ if (ids != null) {
+ for (String id : ids) {
+ if (registry.getIssue(id) == null) {
+ reportNonExistingIssueId(project, id);
+ }
+ }
+ }
+ }
+
+ protected void reportNonExistingIssueId(@Nullable Project project, @NonNull String id) {
+ String message = String.format("Unknown issue id \"%1$s\"", id);
+
+ if (mDriver != null && project != null) {
+ Location location = Location.create(project.getDir());
+ if (!isSuppressed(IssueRegistry.LINT_ERROR)) {
+ report(new Context(mDriver, project, project, project.getDir()),
+ IssueRegistry.LINT_ERROR,
+ project.getConfiguration(mDriver).getSeverity(IssueRegistry.LINT_ERROR),
+ location, message, TextFormat.RAW);
+ }
+ } else {
+ log(Severity.ERROR, null, "Lint: %1$s", message);
+ }
+ }
+
+ private static class ProgressPrinter implements LintListener {
+ @Override
+ public void update(
+ @NonNull LintDriver lint,
+ @NonNull EventType type,
+ @Nullable Context context) {
+ switch (type) {
+ case SCANNING_PROJECT: {
+ String name = context != null ? context.getProject().getName() : "?";
+ if (lint.getPhase() > 1) {
+ System.out.print(String.format(
+ "\nScanning %1$s (Phase %2$d): ",
+ name,
+ lint.getPhase()));
+ } else {
+ System.out.print(String.format(
+ "\nScanning %1$s: ",
+ name));
+ }
+ break;
+ }
+ case SCANNING_LIBRARY_PROJECT: {
+ String name = context != null ? context.getProject().getName() : "?";
+ System.out.print(String.format(
+ "\n - %1$s: ",
+ name));
+ break;
+ }
+ case SCANNING_FILE:
+ System.out.print('.');
+ break;
+ case NEW_PHASE:
+ // Ignored for now: printing status as part of next project's status
+ break;
+ case CANCELED:
+ case COMPLETED:
+ System.out.println();
+ break;
+ case STARTING:
+ // Ignored for now
+ break;
+ }
+ }
+ }
+
+ /**
+ * Given a file, it produces a cleaned up path from the file.
+ * This will clean up the path such that
+ * <ul>
+ * <li> {@code foo/./bar} becomes {@code foo/bar}
+ * <li> {@code foo/bar/../baz} becomes {@code foo/baz}
+ * </ul>
+ *
+ * Unlike {@link java.io.File#getCanonicalPath()} however, it will <b>not</b> attempt
+ * to make the file canonical, such as expanding symlinks and network mounts.
+ *
+ * @param file the file to compute a clean path for
+ * @return the cleaned up path
+ */
+ @VisibleForTesting
+ @NonNull
+ static String getCleanPath(@NonNull File file) {
+ String path = file.getPath();
+ StringBuilder sb = new StringBuilder(path.length());
+
+ if (path.startsWith(File.separator)) {
+ sb.append(File.separator);
+ }
+ elementLoop:
+ for (String element : Splitter.on(File.separatorChar).omitEmptyStrings().split(path)) {
+ if (element.equals(".")) { //$NON-NLS-1$
+ continue;
+ } else if (element.equals("..")) { //$NON-NLS-1$
+ if (sb.length() > 0) {
+ for (int i = sb.length() - 1; i >= 0; i--) {
+ char c = sb.charAt(i);
+ if (c == File.separatorChar) {
+ sb.setLength(i == 0 ? 1 : i);
+ continue elementLoop;
+ }
+ }
+ sb.setLength(0);
+ continue;
+ }
+ }
+
+ if (sb.length() > 1) {
+ sb.append(File.separatorChar);
+ } else if (sb.length() > 0 && sb.charAt(0) != File.separatorChar) {
+ sb.append(File.separatorChar);
+ }
+ sb.append(element);
+ }
+ if (path.endsWith(File.separator) && sb.length() > 0
+ && sb.charAt(sb.length() - 1) != File.separatorChar) {
+ sb.append(File.separator);
+ }
+
+ return sb.toString();
+ }
+
+ String getDisplayPath(Project project, File file) {
+ String path = file.getPath();
+ if (!mFlags.isFullPath() && path.startsWith(project.getReferenceDir().getPath())) {
+ int chop = project.getReferenceDir().getPath().length();
+ if (path.length() > chop && path.charAt(chop) == File.separatorChar) {
+ chop++;
+ }
+ path = path.substring(chop);
+ if (path.isEmpty()) {
+ path = file.getName();
+ }
+ } else if (mFlags.isFullPath()) {
+ path = getCleanPath(file.getAbsoluteFile());
+ }
+
+ return path;
+ }
+
+ /** Returns whether all warnings are enabled, including those disabled by default */
+ boolean isAllEnabled() {
+ return mFlags.isCheckAllWarnings();
+ }
+
+ /** Returns the issue registry used by this client */
+ IssueRegistry getRegistry() {
+ return mRegistry;
+ }
+
+ /** Returns the driver running the lint checks */
+ LintDriver getDriver() {
+ return mDriver;
+ }
+
+ private static Set<File> sAlreadyWarned;
+
+ /** Returns the configuration used by this client */
+ Configuration getConfiguration() {
+ if (mConfiguration == null) {
+ File configFile = mFlags.getDefaultConfiguration();
+ if (configFile != null) {
+ if (!configFile.exists()) {
+ if (sAlreadyWarned == null || !sAlreadyWarned.contains(configFile)) {
+ log(Severity.ERROR, null,
+ "Warning: Configuration file %1$s does not exist", configFile);
+ }
+ if (sAlreadyWarned == null) {
+ sAlreadyWarned = Sets.newHashSet();
+ }
+ sAlreadyWarned.add(configFile);
+ }
+ mConfiguration = createConfigurationFromFile(configFile);
+ }
+ }
+
+ return mConfiguration;
+ }
+
+ /** Returns true if the given issue has been explicitly disabled */
+ boolean isSuppressed(Issue issue) {
+ return mFlags.getSuppressedIds().contains(issue.getId());
+ }
+
+ public Configuration createConfigurationFromFile(File file) {
+ return new CliConfiguration(file, mFlags.isFatalOnly());
+ }
+
+ @Nullable
+ String getRevision() {
+ File file = findResource("tools" + File.separator + //$NON-NLS-1$
+ "source.properties"); //$NON-NLS-1$
+ if (file != null && file.exists()) {
+ FileInputStream input = null;
+ try {
+ input = new FileInputStream(file);
+ Properties properties = new Properties();
+ properties.load(input);
+
+ String revision = properties.getProperty("Pkg.Revision"); //$NON-NLS-1$
+ if (revision != null && !revision.isEmpty()) {
+ return revision;
+ }
+ } catch (IOException e) {
+ // Couldn't find or read the version info: just print out unknown below
+ } finally {
+ try {
+ Closeables.close(input, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public LintCliFlags getFlags() {
+ return mFlags;
+ }
+
+ public boolean haveErrors() {
+ return mErrorCount > 0;
+ }
+}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
rename to lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java
rename to lint/cli/src/main/java/com/android/tools/lint/LintCliXmlParser.java
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Main.java b/lint/cli/src/main/java/com/android/tools/lint/Main.java
new file mode 100644
index 0000000..fc3d857
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/Main.java
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.VALUE_NONE;
+import static com.android.tools.lint.LintCliFlags.ERRNO_ERRORS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_EXISTS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_HELP;
+import static com.android.tools.lint.LintCliFlags.ERRNO_INVALID_ARGS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_SUCCESS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_USAGE;
+import static com.android.tools.lint.detector.api.LintUtils.endsWith;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.checks.BuiltinIssueRegistry;
+import com.android.tools.lint.client.api.Configuration;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.SdkUtils;
+import com.google.common.annotations.Beta;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Command line driver for the lint framework
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class Main {
+ static final int MAX_LINE_WIDTH = 78;
+ private static final String ARG_ENABLE = "--enable"; //$NON-NLS-1$
+ private static final String ARG_DISABLE = "--disable"; //$NON-NLS-1$
+ private static final String ARG_CHECK = "--check"; //$NON-NLS-1$
+ private static final String ARG_IGNORE = "--ignore"; //$NON-NLS-1$
+ private static final String ARG_LIST_IDS = "--list"; //$NON-NLS-1$
+ private static final String ARG_SHOW = "--show"; //$NON-NLS-1$
+ private static final String ARG_QUIET = "--quiet"; //$NON-NLS-1$
+ private static final String ARG_FULL_PATH = "--fullpath"; //$NON-NLS-1$
+ private static final String ARG_SHOW_ALL = "--showall"; //$NON-NLS-1$
+ private static final String ARG_HELP = "--help"; //$NON-NLS-1$
+ private static final String ARG_NO_LINES = "--nolines"; //$NON-NLS-1$
+ private static final String ARG_HTML = "--html"; //$NON-NLS-1$
+ private static final String ARG_SIMPLE_HTML= "--simplehtml"; //$NON-NLS-1$
+ private static final String ARG_XML = "--xml"; //$NON-NLS-1$
+ private static final String ARG_TEXT = "--text"; //$NON-NLS-1$
+ private static final String ARG_CONFIG = "--config"; //$NON-NLS-1$
+ private static final String ARG_URL = "--url"; //$NON-NLS-1$
+ private static final String ARG_VERSION = "--version"; //$NON-NLS-1$
+ private static final String ARG_EXIT_CODE = "--exitcode"; //$NON-NLS-1$
+ private static final String ARG_CLASSES = "--classpath"; //$NON-NLS-1$
+ private static final String ARG_SOURCES = "--sources"; //$NON-NLS-1$
+ private static final String ARG_RESOURCES = "--resources"; //$NON-NLS-1$
+ private static final String ARG_LIBRARIES = "--libraries"; //$NON-NLS-1$
+
+ private static final String ARG_NO_WARN_2 = "--nowarn"; //$NON-NLS-1$
+ // GCC style flag names for options
+ private static final String ARG_NO_WARN_1 = "-w"; //$NON-NLS-1$
+ private static final String ARG_WARN_ALL = "-Wall"; //$NON-NLS-1$
+ private static final String ARG_ALL_ERROR = "-Werror"; //$NON-NLS-1$
+
+ private static final String PROP_WORK_DIR = "com.android.tools.lint.workdir"; //$NON-NLS-1$
+
+ private LintCliFlags mFlags = new LintCliFlags();
+ private IssueRegistry mGlobalRegistry;
+
+ /** Creates a CLI driver */
+ public Main() {
+ }
+
+ /**
+ * Runs the static analysis command line driver
+ *
+ * @param args program arguments
+ */
+ public static void main(String[] args) {
+ new Main().run(args);
+ }
+
+ /**
+ * Runs the static analysis command line driver
+ *
+ * @param args program arguments
+ */
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ public void run(String[] args) {
+ if (args.length < 1) {
+ printUsage(System.err);
+ System.exit(ERRNO_USAGE);
+ }
+
+
+ // When running lint from the command line, warn if the project is a Gradle project
+ // since those projects may have custom project configuration that the command line
+ // runner won't know about.
+ LintCliClient client = new LintCliClient(mFlags, LintClient.CLIENT_CLI) {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ Project project = super.createProject(dir, referenceDir);
+ if (project.isGradleProject()) {
+ @SuppressWarnings("SpellCheckingInspection")
+ String message = String.format("\"`%1$s`\" is a Gradle project. To correctly "
+ + "analyze Gradle projects, you should run \"`gradlew :lint`\" instead.",
+ project.getName());
+ Location location = Location.create(project.getDir());
+ Context context = new Context(mDriver, project, project, project.getDir());
+ if (context.isEnabled(IssueRegistry.LINT_ERROR) &&
+ !getConfiguration(project, null).isIgnored(context,
+ IssueRegistry.LINT_ERROR, location, message)) {
+ report(context,
+ IssueRegistry.LINT_ERROR,
+ project.getConfiguration(null).getSeverity(
+ IssueRegistry.LINT_ERROR), location, message,
+ TextFormat.RAW);
+ }
+ }
+ return project;
+ }
+
+ @NonNull
+ @Override
+ public Configuration getConfiguration(@NonNull final Project project,
+ @Nullable LintDriver driver) {
+ if (project.isGradleProject()) {
+ // Don't report any issues when analyzing a Gradle project from the
+ // non-Gradle runner; they are likely to be false, and will hide the real
+ // problem reported above
+ return new CliConfiguration(getConfiguration(), project, true) {
+ @NonNull
+ @Override
+ public Severity getSeverity(@NonNull Issue issue) {
+ return issue == IssueRegistry.LINT_ERROR
+ ? Severity.FATAL : Severity.IGNORE;
+ }
+
+ @Override
+ public boolean isIgnored(@NonNull Context context, @NonNull Issue issue,
+ @Nullable Location location, @NonNull String message) {
+ // If you've deliberately ignored IssueRegistry.LINT_ERROR
+ // don't flag that one either
+ if (issue == IssueRegistry.LINT_ERROR
+ && new LintCliClient(mFlags, LintClient.getClientName())
+ .isSuppressed(IssueRegistry.LINT_ERROR)) {
+ return true;
+ }
+
+ return issue != IssueRegistry.LINT_ERROR;
+ }
+ };
+ }
+ return super.getConfiguration(project, driver);
+ }
+ };
+
+ // Mapping from file path prefix to URL. Applies only to HTML reports
+ String urlMap = null;
+
+ List<File> files = new ArrayList<File>();
+ for (int index = 0; index < args.length; index++) {
+ String arg = args[index];
+
+ if (arg.equals(ARG_HELP)
+ || arg.equals("-h") || arg.equals("-?")) { //$NON-NLS-1$ //$NON-NLS-2$
+ if (index < args.length - 1) {
+ String topic = args[index + 1];
+ if (topic.equals("suppress") || topic.equals("ignore")) {
+ printHelpTopicSuppress();
+ System.exit(ERRNO_HELP);
+ } else {
+ System.err.println(String.format("Unknown help topic \"%1$s\"", topic));
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ }
+ printUsage(System.out);
+ System.exit(ERRNO_HELP);
+ } else if (arg.equals(ARG_LIST_IDS)) {
+ IssueRegistry registry = getGlobalRegistry(client);
+ // Did the user provide a category list?
+ if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$
+ String[] ids = args[++index].split(",");
+ for (String id : ids) {
+ if (registry.isCategoryName(id)) {
+ // List all issues with the given category
+ String category = id;
+ for (Issue issue : registry.getIssues()) {
+ // Check prefix such that filtering on the "Usability" category
+ // will match issue category "Usability:Icons" etc.
+ if (issue.getCategory().getName().startsWith(category) ||
+ issue.getCategory().getFullName().startsWith(category)) {
+ listIssue(System.out, issue);
+ }
+ }
+ } else {
+ System.err.println("Invalid category \"" + id + "\".\n");
+ displayValidIds(registry, System.err);
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ }
+ } else {
+ displayValidIds(registry, System.out);
+ }
+ System.exit(ERRNO_SUCCESS);
+ } else if (arg.equals(ARG_SHOW)) {
+ IssueRegistry registry = getGlobalRegistry(client);
+ // Show specific issues?
+ if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$
+ String[] ids = args[++index].split(",");
+ for (String id : ids) {
+ if (registry.isCategoryName(id)) {
+ // Show all issues in the given category
+ String category = id;
+ for (Issue issue : registry.getIssues()) {
+ // Check prefix such that filtering on the "Usability" category
+ // will match issue category "Usability:Icons" etc.
+ if (issue.getCategory().getName().startsWith(category) ||
+ issue.getCategory().getFullName().startsWith(category)) {
+ describeIssue(issue);
+ System.out.println();
+ }
+ }
+ } else if (registry.isIssueId(id)) {
+ describeIssue(registry.getIssue(id));
+ System.out.println();
+ } else {
+ System.err.println("Invalid id or category \"" + id + "\".\n");
+ displayValidIds(registry, System.err);
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ }
+ } else {
+ showIssues(registry);
+ }
+ System.exit(ERRNO_SUCCESS);
+ } else if (arg.equals(ARG_FULL_PATH)
+ || arg.equals(ARG_FULL_PATH + "s")) { // allow "--fullpaths" too
+ mFlags.setFullPath(true);
+ } else if (arg.equals(ARG_SHOW_ALL)) {
+ mFlags.setShowEverything(true);
+ } else if (arg.equals(ARG_QUIET) || arg.equals("-q")) {
+ mFlags.setQuiet(true);
+ } else if (arg.equals(ARG_NO_LINES)) {
+ mFlags.setShowSourceLines(false);
+ } else if (arg.equals(ARG_EXIT_CODE)) {
+ mFlags.setSetExitCode(true);
+ } else if (arg.equals(ARG_VERSION)) {
+ printVersion(client);
+ System.exit(ERRNO_SUCCESS);
+ } else if (arg.equals(ARG_URL)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing URL mapping string");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ String map = args[++index];
+ // Allow repeated usage of the argument instead of just comma list
+ if (urlMap != null) {
+ urlMap = urlMap + ',' + map;
+ } else {
+ urlMap = map;
+ }
+ } else if (arg.equals(ARG_CONFIG)) {
+ if (index == args.length - 1 || !endsWith(args[index + 1], DOT_XML)) {
+ System.err.println("Missing XML configuration file argument");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ File file = getInArgumentPath(args[++index]);
+ if (!file.exists()) {
+ System.err.println(file.getAbsolutePath() + " does not exist");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ mFlags.setDefaultConfiguration(file);
+ } else if (arg.equals(ARG_HTML) || arg.equals(ARG_SIMPLE_HTML)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing HTML output file name");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ File output = getOutArgumentPath(args[++index]);
+ // Get an absolute path such that we can ask its parent directory for
+ // write permission etc.
+ output = output.getAbsoluteFile();
+ if (output.isDirectory() ||
+ (!output.exists() && output.getName().indexOf('.') == -1)) {
+ if (!output.exists()) {
+ boolean mkdirs = output.mkdirs();
+ if (!mkdirs) {
+ log(null, "Could not create output directory %1$s", output);
+ System.exit(ERRNO_EXISTS);
+ }
+ }
+ try {
+ MultiProjectHtmlReporter reporter =
+ new MultiProjectHtmlReporter(client, output);
+ if (arg.equals(ARG_SIMPLE_HTML)) {
+ reporter.setSimpleFormat(true);
+ }
+ mFlags.getReporters().add(reporter);
+ } catch (IOException e) {
+ log(e, null);
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ continue;
+ }
+ if (output.exists()) {
+ boolean delete = output.delete();
+ if (!delete) {
+ System.err.println("Could not delete old " + output);
+ System.exit(ERRNO_EXISTS);
+ }
+ }
+ if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
+ System.err.println("Cannot write HTML output file " + output);
+ System.exit(ERRNO_EXISTS);
+ }
+ try {
+ HtmlReporter htmlReporter = new HtmlReporter(client, output);
+ if (arg.equals(ARG_SIMPLE_HTML)) {
+ htmlReporter.setSimpleFormat(true);
+ }
+ mFlags.getReporters().add(htmlReporter);
+ } catch (IOException e) {
+ log(e, null);
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ } else if (arg.equals(ARG_XML)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing XML output file name");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ File output = getOutArgumentPath(args[++index]);
+ // Get an absolute path such that we can ask its parent directory for
+ // write permission etc.
+ output = output.getAbsoluteFile();
+
+ if (output.exists()) {
+ boolean delete = output.delete();
+ if (!delete) {
+ System.err.println("Could not delete old " + output);
+ System.exit(ERRNO_EXISTS);
+ }
+ }
+ if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
+ System.err.println("Cannot write XML output file " + output);
+ System.exit(ERRNO_EXISTS);
+ }
+ try {
+ mFlags.getReporters().add(new XmlReporter(client, output));
+ } catch (IOException e) {
+ log(e, null);
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ } else if (arg.equals(ARG_TEXT)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing text output file name");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+
+ Writer writer = null;
+ boolean closeWriter;
+ String outputName = args[++index];
+ if (outputName.equals("stdout")) { //$NON-NLS-1$
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ writer = new PrintWriter(System.out, true);
+ closeWriter = false;
+ } else {
+ File output = getOutArgumentPath(outputName);
+
+ // Get an absolute path such that we can ask its parent directory for
+ // write permission etc.
+ output = output.getAbsoluteFile();
+
+ if (output.exists()) {
+ boolean delete = output.delete();
+ if (!delete) {
+ System.err.println("Could not delete old " + output);
+ System.exit(ERRNO_EXISTS);
+ }
+ }
+ if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
+ System.err.println("Cannot write text output file " + output);
+ System.exit(ERRNO_EXISTS);
+ }
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ writer = new BufferedWriter(new FileWriter(output));
+ } catch (IOException e) {
+ log(e, null);
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ closeWriter = true;
+ }
+ mFlags.getReporters().add(new TextReporter(client, mFlags, writer, closeWriter));
+ } else if (arg.equals(ARG_DISABLE) || arg.equals(ARG_IGNORE)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing categories or id's to disable");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ IssueRegistry registry = getGlobalRegistry(client);
+ String[] ids = args[++index].split(",");
+ for (String id : ids) {
+ if (registry.isCategoryName(id)) {
+ // Suppress all issues with the given category
+ String category = id;
+ for (Issue issue : registry.getIssues()) {
+ // Check prefix such that filtering on the "Usability" category
+ // will match issue category "Usability:Icons" etc.
+ if (issue.getCategory().getName().startsWith(category) ||
+ issue.getCategory().getFullName().startsWith(category)) {
+ mFlags.getSuppressedIds().add(issue.getId());
+ }
+ }
+ } else if (!registry.isIssueId(id)) {
+ System.err.println("Invalid id or category \"" + id + "\".\n");
+ displayValidIds(registry, System.err);
+ System.exit(ERRNO_INVALID_ARGS);
+ } else {
+ mFlags.getSuppressedIds().add(id);
+ }
+ }
+ } else if (arg.equals(ARG_ENABLE)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing categories or id's to enable");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ IssueRegistry registry = getGlobalRegistry(client);
+ String[] ids = args[++index].split(",");
+ for (String id : ids) {
+ if (registry.isCategoryName(id)) {
+ // Enable all issues with the given category
+ String category = id;
+ for (Issue issue : registry.getIssues()) {
+ if (issue.getCategory().getName().startsWith(category) ||
+ issue.getCategory().getFullName().startsWith(category)) {
+ mFlags.getEnabledIds().add(issue.getId());
+ }
+ }
+ } else if (!registry.isIssueId(id)) {
+ System.err.println("Invalid id or category \"" + id + "\".\n");
+ displayValidIds(registry, System.err);
+ System.exit(ERRNO_INVALID_ARGS);
+ } else {
+ mFlags.getEnabledIds().add(id);
+ }
+ }
+ } else if (arg.equals(ARG_CHECK)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing categories or id's to check");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ Set<String> checkedIds = mFlags.getExactCheckedIds();
+ if (checkedIds == null) {
+ checkedIds = new HashSet<String>();
+ mFlags.setExactCheckedIds(checkedIds);
+ }
+ IssueRegistry registry = getGlobalRegistry(client);
+ String[] ids = args[++index].split(",");
+ for (String id : ids) {
+ if (registry.isCategoryName(id)) {
+ // Check all issues with the given category
+ String category = id;
+ for (Issue issue : registry.getIssues()) {
+ // Check prefix such that filtering on the "Usability" category
+ // will match issue category "Usability:Icons" etc.
+ if (issue.getCategory().getName().startsWith(category) ||
+ issue.getCategory().getFullName().startsWith(category)) {
+ checkedIds.add(issue.getId());
+ }
+ }
+ } else if (!registry.isIssueId(id)) {
+ System.err.println("Invalid id or category \"" + id + "\".\n");
+ displayValidIds(registry, System.err);
+ System.exit(ERRNO_INVALID_ARGS);
+ } else {
+ checkedIds.add(id);
+ }
+ }
+ } else if (arg.equals(ARG_NO_WARN_1) || arg.equals(ARG_NO_WARN_2)) {
+ mFlags.setIgnoreWarnings(true);
+ } else if (arg.equals(ARG_WARN_ALL)) {
+ mFlags.setCheckAllWarnings(true);
+ } else if (arg.equals(ARG_ALL_ERROR)) {
+ mFlags.setWarningsAsErrors(true);
+ } else if (arg.equals(ARG_CLASSES)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing class folder name");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ String paths = args[++index];
+ for (String path : LintUtils.splitPath(paths)) {
+ File input = getInArgumentPath(path);
+ if (!input.exists()) {
+ System.err.println("Class path entry " + input + " does not exist.");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ List<File> classes = mFlags.getClassesOverride();
+ if (classes == null) {
+ classes = new ArrayList<File>();
+ mFlags.setClassesOverride(classes);
+ }
+ classes.add(input);
+ }
+ } else if (arg.equals(ARG_SOURCES)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing source folder name");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ String paths = args[++index];
+ for (String path : LintUtils.splitPath(paths)) {
+ File input = getInArgumentPath(path);
+ if (!input.exists()) {
+ System.err.println("Source folder " + input + " does not exist.");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ List<File> sources = mFlags.getSourcesOverride();
+ if (sources == null) {
+ sources = new ArrayList<File>();
+ mFlags.setSourcesOverride(sources);
+ }
+ sources.add(input);
+ }
+ } else if (arg.equals(ARG_RESOURCES)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing resource folder name");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ String paths = args[++index];
+ for (String path : LintUtils.splitPath(paths)) {
+ File input = getInArgumentPath(path);
+ if (!input.exists()) {
+ System.err.println("Resource folder " + input + " does not exist.");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ List<File> resources = mFlags.getResourcesOverride();
+ if (resources == null) {
+ resources = new ArrayList<File>();
+ mFlags.setResourcesOverride(resources);
+ }
+ resources.add(input);
+ }
+ } else if (arg.equals(ARG_LIBRARIES)) {
+ if (index == args.length - 1) {
+ System.err.println("Missing library folder name");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ String paths = args[++index];
+ for (String path : LintUtils.splitPath(paths)) {
+ File input = getInArgumentPath(path);
+ if (!input.exists()) {
+ System.err.println("Library " + input + " does not exist.");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ List<File> libraries = mFlags.getLibrariesOverride();
+ if (libraries == null) {
+ libraries = new ArrayList<File>();
+ mFlags.setLibrariesOverride(libraries);
+ }
+ libraries.add(input);
+ }
+ } else if (arg.startsWith("--")) {
+ System.err.println("Invalid argument " + arg + "\n");
+ printUsage(System.err);
+ System.exit(ERRNO_INVALID_ARGS);
+ } else {
+ String filename = arg;
+ File file = getInArgumentPath(filename);
+
+ if (!file.exists()) {
+ System.err.println(String.format("%1$s does not exist.", filename));
+ System.exit(ERRNO_EXISTS);
+ }
+ files.add(file);
+ }
+ }
+
+ if (files.isEmpty()) {
+ System.err.println("No files to analyze.");
+ System.exit(ERRNO_INVALID_ARGS);
+ } else if (files.size() > 1
+ && (mFlags.getClassesOverride() != null
+ || mFlags.getSourcesOverride() != null
+ || mFlags.getLibrariesOverride() != null
+ || mFlags.getResourcesOverride() != null)) {
+ System.err.println(String.format(
+ "The %1$s, %2$s, %3$s and %4$s arguments can only be used with a single project",
+ ARG_SOURCES, ARG_CLASSES, ARG_LIBRARIES, ARG_RESOURCES));
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+
+ List<Reporter> reporters = mFlags.getReporters();
+ if (reporters.isEmpty()) {
+ //noinspection VariableNotUsedInsideIf
+ if (urlMap != null) {
+ System.err.println(String.format(
+ "Warning: The %1$s option only applies to HTML reports (%2$s)",
+ ARG_URL, ARG_HTML));
+ }
+
+ reporters.add(new TextReporter(client, mFlags,
+ new PrintWriter(System.out, true), false));
+ } else {
+ //noinspection VariableNotUsedInsideIf
+ if (urlMap != null) {
+ for (Reporter reporter : reporters) {
+ if (!reporter.isSimpleFormat()) {
+ reporter.setBundleResources(true);
+ }
+ }
+
+ if (!urlMap.equals(VALUE_NONE)) {
+ Map<String, String> map = new HashMap<String, String>();
+ String[] replace = urlMap.split(","); //$NON-NLS-1$
+ for (String s : replace) {
+ // Allow ='s in the suffix part
+ int index = s.indexOf('=');
+ if (index == -1) {
+ System.err.println(
+ "The URL map argument must be of the form 'path_prefix=url_prefix'");
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ String key = s.substring(0, index);
+ String value = s.substring(index + 1);
+ map.put(key, value);
+ }
+ for (Reporter reporter : reporters) {
+ reporter.setUrlMap(map);
+ }
+ }
+ }
+ }
+
+ try {
+ // Not using mGlobalRegistry; LintClient will do its own registry merging
+ // also including project rules.
+ int exitCode = client.run(new BuiltinIssueRegistry(), files);
+ System.exit(exitCode);
+ } catch (IOException e) {
+ log(e, null);
+ System.exit(ERRNO_INVALID_ARGS);
+ }
+ }
+
+ private IssueRegistry getGlobalRegistry(LintCliClient client) {
+ if (mGlobalRegistry == null) {
+ mGlobalRegistry = client.addCustomLintRules(new BuiltinIssueRegistry());
+ }
+
+ return mGlobalRegistry;
+ }
+
+ /**
+ * Converts a relative or absolute command-line argument into an input file.
+ *
+ * @param filename The filename given as a command-line argument.
+ * @return A File matching filename, either absolute or relative to lint.workdir if defined.
+ */
+ private static File getInArgumentPath(String filename) {
+ File file = new File(filename);
+
+ if (!file.isAbsolute()) {
+ File workDir = getLintWorkDir();
+ if (workDir != null) {
+ File file2 = new File(workDir, filename);
+ if (file2.exists()) {
+ try {
+ file = file2.getCanonicalFile();
+ } catch (IOException e) {
+ file = file2;
+ }
+ }
+ }
+ }
+ return file;
+ }
+
+ /**
+ * Converts a relative or absolute command-line argument into an output file.
+ * <p/>
+ * The difference with {@code getInArgumentPath} is that we can't check whether the
+ * a relative path turned into an absolute compared to lint.workdir actually exists.
+ *
+ * @param filename The filename given as a command-line argument.
+ * @return A File matching filename, either absolute or relative to lint.workdir if defined.
+ */
+ private static File getOutArgumentPath(String filename) {
+ File file = new File(filename);
+
+ if (!file.isAbsolute()) {
+ File workDir = getLintWorkDir();
+ if (workDir != null) {
+ File file2 = new File(workDir, filename);
+ try {
+ file = file2.getCanonicalFile();
+ } catch (IOException e) {
+ file = file2;
+ }
+ }
+ }
+ return file;
+ }
+
+ /**
+ * Returns the File corresponding to the system property or the environment variable
+ * for {@link #PROP_WORK_DIR}.
+ * This property is typically set by the SDK/tools/lint[.bat] wrapper.
+ * It denotes the path where the command-line client was originally invoked from
+ * and can be used to convert relative input/output paths.
+ *
+ * @return A new File corresponding to {@link #PROP_WORK_DIR} or null.
+ */
+ @Nullable
+ private static File getLintWorkDir() {
+ // First check the Java properties (e.g. set using "java -jar ... -Dname=value")
+ String path = System.getProperty(PROP_WORK_DIR);
+ if (path == null || path.isEmpty()) {
+ // If not found, check environment variables.
+ path = System.getenv(PROP_WORK_DIR);
+ }
+ if (path != null && !path.isEmpty()) {
+ return new File(path);
+ }
+ return null;
+ }
+
+ private static void printHelpTopicSuppress() {
+ System.out.println(wrap(TextFormat.RAW.convertTo(getSuppressHelp(), TextFormat.TEXT)));
+ }
+
+ static String getSuppressHelp() {
+ // \\u00a0 is a non-breaking space
+ final String NBSP = "\u00a0\u00a0\u00a0\u00a0";
+
+ return
+ "Lint errors can be suppressed in a variety of ways:\n" +
+ "\n" +
+ "1. With a `@SuppressLint` annotation in the Java code\n" +
+ "2. With a `tools:ignore` attribute in the XML file\n" +
+ "3. With ignore flags specified in the `build.gradle` file, " +
+ "as explained below\n" +
+ "4. With a `lint.xml` configuration file in the project\n" +
+ "5. With a `lint.xml` configuration file passed to lint " +
+ "via the " + ARG_CONFIG + " flag\n" +
+ "6. With the " + ARG_IGNORE + " flag passed to lint.\n" +
+ "\n" +
+ "To suppress a lint warning with an annotation, add " +
+ "a `@SuppressLint(\"id\")` annotation on the class, method " +
+ "or variable declaration closest to the warning instance " +
+ "you want to disable. The id can be one or more issue " +
+ "id's, such as `\"UnusedResources\"` or `{\"UnusedResources\"," +
+ "\"UnusedIds\"}`, or it can be `\"all\"` to suppress all lint " +
+ "warnings in the given scope.\n" +
+ "\n" +
+ "To suppress a lint warning in an XML file, add a " +
+ "`tools:ignore=\"id\"` attribute on the element containing " +
+ "the error, or one of its surrounding elements. You also " +
+ "need to define the namespace for the tools prefix on the " +
+ "root element in your document, next to the `xmlns:android` " +
+ "declaration:\n" +
+ "`xmlns:tools=\"http://schemas.android.com/tools\"`\n" +
+ "\n" +
+ "To suppress a lint warning in a `build.gradle` file, add a " +
+ "section like this:\n" +
+ "\n" +
+ "android {\n" +
+ NBSP + "lintOptions {\n" +
+ NBSP + NBSP + "disable 'TypographyFractions','TypographyQuotes'\n" +
+ NBSP + "}\n" +
+ "}\n" +
+ "\n" +
+ "Here we specify a comma separated list of issue id's after the " +
+ "disable command. You can also use `warning` or `error` instead " +
+ "of `disable` to change the severity of issues.\n" +
+ "\n" +
+ "To suppress lint warnings with a configuration XML file, " +
+ "create a file named `lint.xml` and place it at the root " +
+ "directory of the project in which it applies.\n" +
+ "\n" +
+ "The format of the `lint.xml` file is something like the " +
+ "following:\n" +
+ "\n" +
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<lint>\n" +
+ NBSP + "<!-- Disable this given check in this project -->\n" +
+ NBSP + "<issue id=\"IconMissingDensityFolder\" severity=\"ignore\" />\n" +
+ "\n" +
+ NBSP + "<!-- Ignore the ObsoleteLayoutParam issue in the given files -->\n" +
+ NBSP + "<issue id=\"ObsoleteLayoutParam\">\n" +
+ NBSP + NBSP + "<ignore path=\"res/layout/activation.xml\" />\n" +
+ NBSP + NBSP + "<ignore path=\"res/layout-xlarge/activation.xml\" />\n" +
+ NBSP + "</issue>\n" +
+ "\n" +
+ NBSP + "<!-- Ignore the UselessLeaf issue in the given file -->\n" +
+ NBSP + "<issue id=\"UselessLeaf\">\n" +
+ NBSP + NBSP + "<ignore path=\"res/layout/main.xml\" />\n" +
+ NBSP + "</issue>\n" +
+ "\n" +
+ NBSP + "<!-- Change the severity of hardcoded strings to \"error\" -->\n" +
+ NBSP + "<issue id=\"HardcodedText\" severity=\"error\" />\n" +
+ "</lint>\n" +
+ "\n" +
+ "To suppress lint checks from the command line, pass the " + ARG_IGNORE + " " +
+ "flag with a comma separated list of ids to be suppressed, such as:\n" +
+ "`$ lint --ignore UnusedResources,UselessLeaf /my/project/path`\n" +
+ "\n" +
+ "For more information, see " +
+ "http://g.co/androidstudio/suppressing-lint-warnings\n";
+ }
+
+ private static void printVersion(LintCliClient client) {
+ String revision = client.getRevision();
+ if (revision != null) {
+ System.out.println(String.format("lint: version %1$s", revision));
+ } else {
+ System.out.println("lint: unknown version");
+ }
+ }
+
+ private static void displayValidIds(IssueRegistry registry, PrintStream out) {
+ List<Category> categories = registry.getCategories();
+ out.println("Valid issue categories:");
+ for (Category category : categories) {
+ out.println(" " + category.getFullName());
+ }
+ out.println();
+ List<Issue> issues = registry.getIssues();
+ out.println("Valid issue id's:");
+ for (Issue issue : issues) {
+ listIssue(out, issue);
+ }
+ }
+
+ private static void listIssue(PrintStream out, Issue issue) {
+ out.print(wrapArg("\"" + issue.getId() + "\": " + issue.getBriefDescription(TEXT)));
+ }
+
+ private static void showIssues(IssueRegistry registry) {
+ List<Issue> issues = registry.getIssues();
+ List<Issue> sorted = new ArrayList<Issue>(issues);
+ Collections.sort(sorted, new Comparator<Issue>() {
+ @Override
+ public int compare(Issue issue1, Issue issue2) {
+ int d = issue1.getCategory().compareTo(issue2.getCategory());
+ if (d != 0) {
+ return d;
+ }
+ d = issue2.getPriority() - issue1.getPriority();
+ if (d != 0) {
+ return d;
+ }
+
+ return issue1.getId().compareTo(issue2.getId());
+ }
+ });
+
+ System.out.println("Available issues:\n");
+ Category previousCategory = null;
+ for (Issue issue : sorted) {
+ Category category = issue.getCategory();
+ if (!category.equals(previousCategory)) {
+ String name = category.getFullName();
+ System.out.println(name);
+ for (int i = 0, n = name.length(); i < n; i++) {
+ System.out.print('=');
+ }
+ System.out.println('\n');
+ previousCategory = category;
+ }
+
+ describeIssue(issue);
+ System.out.println();
+ }
+ }
+
+ private static void describeIssue(Issue issue) {
+ System.out.println(issue.getId());
+ for (int i = 0; i < issue.getId().length(); i++) {
+ System.out.print('-');
+ }
+ System.out.println();
+ System.out.println(wrap("Summary: " + issue.getBriefDescription(TEXT)));
+ System.out.println("Priority: " + issue.getPriority() + " / 10");
+ System.out.println("Severity: " + issue.getDefaultSeverity().getDescription());
+ System.out.println("Category: " + issue.getCategory().getFullName());
+
+ if (!issue.isEnabledByDefault()) {
+ System.out.println("NOTE: This issue is disabled by default!");
+ System.out.println(String.format("You can enable it by adding %1$s %2$s", ARG_ENABLE,
+ issue.getId()));
+ }
+
+ System.out.println();
+ System.out.println(wrap(issue.getExplanation(TEXT)));
+ List<String> moreInfo = issue.getMoreInfo();
+ if (!moreInfo.isEmpty()) {
+ System.out.println("More information: ");
+ for (String uri : moreInfo) {
+ System.out.println(uri);
+ }
+ }
+ }
+
+ static String wrapArg(String explanation) {
+ // Wrap arguments such that the wrapped lines are not showing up in the left column
+ return wrap(explanation, MAX_LINE_WIDTH, " ");
+ }
+
+ static String wrap(String explanation) {
+ return wrap(explanation, MAX_LINE_WIDTH, "");
+ }
+
+ static String wrap(String explanation, int lineWidth, String hangingIndent) {
+ return SdkUtils.wrap(explanation, lineWidth, hangingIndent);
+ }
+
+ private static void printUsage(PrintStream out) {
+ // TODO: Look up launcher script name!
+ String command = "lint"; //$NON-NLS-1$
+
+ out.println("Usage: " + command + " [flags] <project directories>\n");
+ out.println("Flags:\n");
+
+ printUsage(out, new String[] {
+ ARG_HELP, "This message.",
+ ARG_HELP + " <topic>", "Help on the given topic, such as \"suppress\".",
+ ARG_LIST_IDS, "List the available issue id's and exit.",
+ ARG_VERSION, "Output version information and exit.",
+ ARG_EXIT_CODE, "Set the exit code to " + ERRNO_ERRORS + " if errors are found.",
+ ARG_SHOW, "List available issues along with full explanations.",
+ ARG_SHOW + " <ids>", "Show full explanations for the given list of issue id's.",
+
+ "", "\nEnabled Checks:",
+ ARG_DISABLE + " <list>", "Disable the list of categories or " +
+ "specific issue id's. The list should be a comma-separated list of issue " +
+ "id's or categories.",
+ ARG_ENABLE + " <list>", "Enable the specific list of issues. " +
+ "This checks all the default issues plus the specifically enabled issues. The " +
+ "list should be a comma-separated list of issue id's or categories.",
+ ARG_CHECK + " <list>", "Only check the specific list of issues. " +
+ "This will disable everything and re-enable the given list of issues. " +
+ "The list should be a comma-separated list of issue id's or categories.",
+ ARG_NO_WARN_1 + ", " + ARG_NO_WARN_2, "Only check for errors (ignore warnings)",
+ ARG_WARN_ALL, "Check all warnings, including those off by default",
+ ARG_ALL_ERROR, "Treat all warnings as errors",
+ ARG_CONFIG + " <filename>", "Use the given configuration file to " +
+ "determine whether issues are enabled or disabled. If a project contains " +
+ "a lint.xml file, then this config file will be used as a fallback.",
+
+
+ "", "\nOutput Options:",
+ ARG_QUIET, "Don't show progress.",
+ ARG_FULL_PATH, "Use full paths in the error output.",
+ ARG_SHOW_ALL, "Do not truncate long messages, lists of alternate locations, etc.",
+ ARG_NO_LINES, "Do not include the source file lines with errors " +
+ "in the output. By default, the error output includes snippets of source code " +
+ "on the line containing the error, but this flag turns it off.",
+ ARG_HTML + " <filename>", "Create an HTML report instead. If the filename is a " +
+ "directory (or a new filename without an extension), lint will create a " +
+ "separate report for each scanned project.",
+ ARG_URL + " filepath=url", "Add links to HTML report, replacing local " +
+ "path prefixes with url prefix. The mapping can be a comma-separated list of " +
+ "path prefixes to corresponding URL prefixes, such as " +
+ "C:\\temp\\Proj1=http://buildserver/sources/temp/Proj1. To turn off linking " +
+ "to files, use " + ARG_URL + " " + VALUE_NONE,
+ ARG_SIMPLE_HTML + " <filename>", "Create a simple HTML report",
+ ARG_XML + " <filename>", "Create an XML report instead.",
+
+ "", "\nProject Options:",
+ ARG_RESOURCES + " <dir>", "Add the given folder (or path) as a resource directory " +
+ "for the project. Only valid when running lint on a single project.",
+ ARG_SOURCES + " <dir>", "Add the given folder (or path) as a source directory for " +
+ "the project. Only valid when running lint on a single project.",
+ ARG_CLASSES + " <dir>", "Add the given folder (or jar file, or path) as a class " +
+ "directory for the project. Only valid when running lint on a single project.",
+ ARG_LIBRARIES + " <dir>", "Add the given folder (or jar file, or path) as a class " +
+ "library for the project. Only valid when running lint on a single project.",
+
+ "", "\nExit Status:",
+ "0", "Success.",
+ Integer.toString(ERRNO_ERRORS), "Lint errors detected.",
+ Integer.toString(ERRNO_USAGE), "Lint usage.",
+ Integer.toString(ERRNO_EXISTS), "Cannot clobber existing file.",
+ Integer.toString(ERRNO_HELP), "Lint help.",
+ Integer.toString(ERRNO_INVALID_ARGS), "Invalid command-line argument.",
+ });
+ }
+
+ private static void printUsage(PrintStream out, String[] args) {
+ int argWidth = 0;
+ for (int i = 0; i < args.length; i += 2) {
+ String arg = args[i];
+ argWidth = Math.max(argWidth, arg.length());
+ }
+ argWidth += 2;
+ StringBuilder sb = new StringBuilder(20);
+ for (int i = 0; i < argWidth; i++) {
+ sb.append(' ');
+ }
+ String indent = sb.toString();
+ String formatString = "%1$-" + argWidth + "s%2$s"; //$NON-NLS-1$
+
+ for (int i = 0; i < args.length; i += 2) {
+ String arg = args[i];
+ String description = args[i + 1];
+ if (arg.isEmpty()) {
+ out.println(description);
+ } else {
+ out.print(wrap(String.format(formatString, arg, description),
+ MAX_LINE_WIDTH, indent));
+ }
+ }
+ }
+
+ public void log(
+ @Nullable Throwable exception,
+ @Nullable String format,
+ @Nullable Object... args) {
+ System.out.flush();
+ if (!mFlags.isQuiet()) {
+ // Place the error message on a line of its own since we're printing '.' etc
+ // with newlines during analysis
+ System.err.println();
+ }
+ if (format != null) {
+ System.err.println(String.format(format, args));
+ }
+ if (exception != null) {
+ exception.printStackTrace();
+ }
+ }
+}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java
rename to lint/cli/src/main/java/com/android/tools/lint/MultiProjectHtmlReporter.java
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Reporter.java b/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
new file mode 100644
index 0000000..11ba13d
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.SdkConstants.CURRENT_PLATFORM;
+import static com.android.SdkConstants.DOT_9PNG;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.PLATFORM_LINUX;
+import static com.android.SdkConstants.UTF_8;
+import static com.android.tools.lint.detector.api.LintUtils.endsWith;
+import static java.io.File.separatorChar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.checks.AccessibilityDetector;
+import com.android.tools.lint.checks.AlwaysShowActionDetector;
+import com.android.tools.lint.checks.AndroidAutoDetector;
+import com.android.tools.lint.checks.AndroidTvDetector;
+import com.android.tools.lint.checks.AnnotationDetector;
+import com.android.tools.lint.checks.ApiDetector;
+import com.android.tools.lint.checks.AppCompatCallDetector;
+import com.android.tools.lint.checks.AppIndexingApiDetector;
+import com.android.tools.lint.checks.ByteOrderMarkDetector;
+import com.android.tools.lint.checks.CommentDetector;
+import com.android.tools.lint.checks.DetectMissingPrefix;
+import com.android.tools.lint.checks.DosLineEndingDetector;
+import com.android.tools.lint.checks.DuplicateResourceDetector;
+import com.android.tools.lint.checks.GradleDetector;
+import com.android.tools.lint.checks.GridLayoutDetector;
+import com.android.tools.lint.checks.HardcodedValuesDetector;
+import com.android.tools.lint.checks.IncludeDetector;
+import com.android.tools.lint.checks.InefficientWeightDetector;
+import com.android.tools.lint.checks.JavaPerformanceDetector;
+import com.android.tools.lint.checks.ManifestDetector;
+import com.android.tools.lint.checks.MissingClassDetector;
+import com.android.tools.lint.checks.MissingIdDetector;
+import com.android.tools.lint.checks.NamespaceDetector;
+import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector;
+import com.android.tools.lint.checks.ParcelDetector;
+import com.android.tools.lint.checks.PropertyFileDetector;
+import com.android.tools.lint.checks.PxUsageDetector;
+import com.android.tools.lint.checks.ReadParcelableDetector;
+import com.android.tools.lint.checks.RtlDetector;
+import com.android.tools.lint.checks.ScrollViewChildDetector;
+import com.android.tools.lint.checks.SecurityDetector;
+import com.android.tools.lint.checks.SharedPrefsDetector;
+import com.android.tools.lint.checks.SignatureOrSystemDetector;
+import com.android.tools.lint.checks.SupportAnnotationDetector;
+import com.android.tools.lint.checks.TextFieldDetector;
+import com.android.tools.lint.checks.TextViewDetector;
+import com.android.tools.lint.checks.TitleDetector;
+import com.android.tools.lint.checks.TranslationDetector;
+import com.android.tools.lint.checks.TypoDetector;
+import com.android.tools.lint.checks.TypographyDetector;
+import com.android.tools.lint.checks.UseCompoundDrawableDetector;
+import com.android.tools.lint.checks.UselessViewDetector;
+import com.android.tools.lint.checks.Utf8Detector;
+import com.android.tools.lint.checks.WrongCallDetector;
+import com.android.tools.lint.checks.WrongCaseDetector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.utils.SdkUtils;
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/** A reporter is an output generator for lint warnings
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public abstract class Reporter {
+ protected final LintCliClient mClient;
+ protected final File mOutput;
+ protected String mTitle = "Lint Report";
+ protected boolean mSimpleFormat;
+ protected boolean mBundleResources;
+ protected Map<String, String> mUrlMap;
+ protected File mResources;
+ protected final Map<File, String> mResourceUrl = new HashMap<File, String>();
+ protected final Map<String, File> mNameToFile = new HashMap<String, File>();
+ protected boolean mDisplayEmpty = true;
+
+ /**
+ * Write the given warnings into the report
+ *
+ * @param errorCount the number of errors
+ * @param warningCount the number of warnings
+ * @param issues the issues to be reported
+ * @throws IOException if an error occurs
+ */
+ public abstract void write(int errorCount, int warningCount, List<Warning> issues)
+ throws IOException;
+
+ protected Reporter(LintCliClient client, File output) {
+ mClient = client;
+ mOutput = output;
+ }
+
+ /**
+ * Sets the report title
+ *
+ * @param title the title of the report
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ /** @return the title of the report */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets whether the report should bundle up resources along with the HTML report.
+ * This implies a non-simple format (see {@link #setSimpleFormat(boolean)}).
+ *
+ * @param bundleResources if true, copy images into a directory relative to
+ * the report
+ */
+ public void setBundleResources(boolean bundleResources) {
+ mBundleResources = bundleResources;
+ mSimpleFormat = false;
+ }
+
+ /**
+ * Sets whether the report should use simple formatting (meaning no JavaScript,
+ * embedded images, etc).
+ *
+ * @param simpleFormat whether the formatting should be simple
+ */
+ public void setSimpleFormat(boolean simpleFormat) {
+ mSimpleFormat = simpleFormat;
+ }
+
+ /**
+ * Returns whether the report should use simple formatting (meaning no JavaScript,
+ * embedded images, etc).
+ *
+ * @return whether the report should use simple formatting
+ */
+ public boolean isSimpleFormat() {
+ return mSimpleFormat;
+ }
+
+
+ String getUrl(File file) {
+ if (mBundleResources && !mSimpleFormat) {
+ String url = getRelativeResourceUrl(file);
+ if (url != null) {
+ return url;
+ }
+ }
+
+ if (mUrlMap != null) {
+ String path = file.getAbsolutePath();
+ // Perform the comparison using URLs such that we properly escape spaces etc.
+ String pathUrl = encodeUrl(path);
+ for (Map.Entry<String, String> entry : mUrlMap.entrySet()) {
+ String prefix = entry.getKey();
+ String prefixUrl = encodeUrl(prefix);
+ if (pathUrl.startsWith(prefixUrl)) {
+ String relative = pathUrl.substring(prefixUrl.length());
+ return entry.getValue() + relative;
+ }
+ }
+ }
+
+ if (file.isAbsolute()) {
+ String relativePath = getRelativePath(mOutput.getParentFile(), file);
+ if (relativePath != null) {
+ relativePath = relativePath.replace(separatorChar, '/');
+ return encodeUrl(relativePath);
+ }
+ }
+
+ try {
+ return SdkUtils.fileToUrlString(file);
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ /** Encodes the given String as a safe URL substring, escaping spaces etc */
+ static String encodeUrl(String url) {
+ try {
+ url = url.replace('\\', '/');
+ return URLEncoder.encode(url, UTF_8).replace("%2F", "/"); //$NON-NLS-1$
+ } catch (UnsupportedEncodingException e) {
+ // This shouldn't happen for UTF-8
+ System.err.println("Invalid string " + e.getLocalizedMessage());
+ return url;
+ }
+ }
+
+ /** Set mapping of path prefixes to corresponding URLs in the HTML report */
+ public void setUrlMap(@Nullable Map<String, String> urlMap) {
+ mUrlMap = urlMap;
+ }
+
+ /** Gets a pointer to the local resource directory, if any */
+ File getResourceDir() {
+ if (mResources == null && mBundleResources) {
+ mResources = computeResourceDir();
+ if (mResources == null) {
+ mBundleResources = false;
+ }
+ }
+
+ return mResources;
+ }
+
+ /** Finds/creates the local resource directory, if possible */
+ File computeResourceDir() {
+ String fileName = mOutput.getName();
+ int dot = fileName.indexOf('.');
+ if (dot != -1) {
+ fileName = fileName.substring(0, dot);
+ }
+
+ File resources = new File(mOutput.getParentFile(), fileName + "_files"); //$NON-NLS-1$
+ if (!resources.exists() && !resources.mkdir()) {
+ resources = null;
+ }
+
+ return resources;
+ }
+
+ /** Returns a URL to a local copy of the given file, or null */
+ protected String getRelativeResourceUrl(File file) {
+ String resource = mResourceUrl.get(file);
+ if (resource != null) {
+ return resource;
+ }
+
+ String name = file.getName();
+ if (!endsWith(name, DOT_PNG) || endsWith(name, DOT_9PNG)) {
+ return null;
+ }
+
+ // Attempt to make local copy
+ File resourceDir = getResourceDir();
+ if (resourceDir != null) {
+ String base = file.getName();
+
+ File path = mNameToFile.get(base);
+ if (path != null && !path.equals(file)) {
+ // That filename already exists and is associated with a different path:
+ // make a new unique version
+ for (int i = 0; i < 100; i++) {
+ base = '_' + base;
+ path = mNameToFile.get(base);
+ if (path == null || path.equals(file)) {
+ break;
+ }
+ }
+ }
+
+ File target = new File(resourceDir, base);
+ try {
+ Files.copy(file, target);
+ } catch (IOException e) {
+ return null;
+ }
+ return resourceDir.getName() + '/' + encodeUrl(base);
+ }
+ return null;
+ }
+
+ /** Returns a URL to a local copy of the given resource, or null. There is
+ * no filename conflict resolution. */
+ protected String addLocalResources(URL url) throws IOException {
+ // Attempt to make local copy
+ File resourceDir = computeResourceDir();
+ if (resourceDir != null) {
+ String base = url.getFile();
+ base = base.substring(base.lastIndexOf('/') + 1);
+ mNameToFile.put(base, new File(url.toExternalForm()));
+
+ File target = new File(resourceDir, base);
+ Closer closer = Closer.create();
+ try {
+ FileOutputStream output = closer.register(new FileOutputStream(target));
+ InputStream input = closer.register(url.openStream());
+ ByteStreams.copy(input, output);
+ } catch (Throwable e) {
+ closer.rethrow(e);
+ } finally {
+ closer.close();
+ }
+ return resourceDir.getName() + '/' + encodeUrl(base);
+ }
+ return null;
+ }
+
+ // Based on similar code in com.intellij.openapi.util.io.FileUtilRt
+ @Nullable
+ static String getRelativePath(File base, File file) {
+ if (base == null || file == null) {
+ return null;
+ }
+ if (!base.isDirectory()) {
+ base = base.getParentFile();
+ if (base == null) {
+ return null;
+ }
+ }
+ if (base.equals(file)) {
+ return ".";
+ }
+
+ final String filePath = file.getAbsolutePath();
+ String basePath = base.getAbsolutePath();
+
+ // TODO: Make this return null if we go all the way to the root!
+
+ basePath = !basePath.isEmpty() && basePath.charAt(basePath.length() - 1) == separatorChar
+ ? basePath : basePath + separatorChar;
+
+ // Whether filesystem is case sensitive. Technically on OSX you could create a
+ // sensitive one, but it's not the default.
+ boolean caseSensitive = CURRENT_PLATFORM == PLATFORM_LINUX;
+ Locale l = Locale.getDefault();
+ String basePathToCompare = caseSensitive ? basePath : basePath.toLowerCase(l);
+ String filePathToCompare = caseSensitive ? filePath : filePath.toLowerCase(l);
+ if (basePathToCompare.equals(!filePathToCompare.isEmpty()
+ && filePathToCompare.charAt(filePathToCompare.length() - 1) == separatorChar
+ ? filePathToCompare : filePathToCompare + separatorChar)) {
+ return ".";
+ }
+ int len = 0;
+ int lastSeparatorIndex = 0;
+ // bug in inspection; see http://youtrack.jetbrains.com/issue/IDEA-118971
+ //noinspection ConstantConditions
+ while (len < filePath.length() && len < basePath.length()
+ && filePathToCompare.charAt(len) == basePathToCompare.charAt(len)) {
+ if (basePath.charAt(len) == separatorChar) {
+ lastSeparatorIndex = len;
+ }
+ len++;
+ }
+ if (len == 0) {
+ return null;
+ }
+
+ StringBuilder relativePath = new StringBuilder();
+ for (int i = len; i < basePath.length(); i++) {
+ if (basePath.charAt(i) == separatorChar) {
+ relativePath.append("..");
+ relativePath.append(separatorChar);
+ }
+ }
+ relativePath.append(filePath.substring(lastSeparatorIndex + 1));
+ return relativePath.toString();
+ }
+
+ /**
+ * Returns whether this report should display info (such as a path to the report) if
+ * no issues were found
+ */
+ public boolean isDisplayEmpty() {
+ return mDisplayEmpty;
+ }
+
+ /**
+ * Sets whether this report should display info (such as a path to the report) if
+ * no issues were found
+ */
+ public void setDisplayEmpty(boolean displayEmpty) {
+ mDisplayEmpty = displayEmpty;
+ }
+
+ private static Set<Issue> sAdtFixes;
+ private static Set<Issue> sStudioFixes;
+
+ /** Tools known to have quickfixes for lint */
+ enum QuickfixHandler {
+ /** Android Studio or IntelliJ */
+ STUDIO,
+ /** Eclipse */
+ ADT;
+
+ public boolean hasAutoFix(Issue issue) {
+ return Reporter.hasAutoFix(this, issue);
+ }
+ }
+
+ /**
+ * Returns true if the given issue has an automatic IDE fix.
+ *
+ * @param tool the name of the tool to be checked
+ * @param issue the issue to be checked
+ * @return true if the given tool is known to have an automatic fix for the
+ * given issue
+ */
+ public static boolean hasAutoFix(@NonNull QuickfixHandler tool, Issue issue) {
+ if (tool == QuickfixHandler.ADT) {
+ if (sAdtFixes == null) {
+ sAdtFixes = Sets.newHashSet(
+ InefficientWeightDetector.INEFFICIENT_WEIGHT,
+ AccessibilityDetector.ISSUE,
+ InefficientWeightDetector.BASELINE_WEIGHTS,
+ HardcodedValuesDetector.ISSUE,
+ UselessViewDetector.USELESS_LEAF,
+ UselessViewDetector.USELESS_PARENT,
+ PxUsageDetector.PX_ISSUE,
+ TextFieldDetector.ISSUE,
+ SecurityDetector.EXPORTED_SERVICE,
+ DetectMissingPrefix.MISSING_NAMESPACE,
+ ScrollViewChildDetector.ISSUE,
+ ObsoleteLayoutParamsDetector.ISSUE,
+ TypographyDetector.DASHES,
+ TypographyDetector.ELLIPSIS,
+ TypographyDetector.FRACTIONS,
+ TypographyDetector.OTHER,
+ TypographyDetector.QUOTES,
+ UseCompoundDrawableDetector.ISSUE,
+ ApiDetector.UNSUPPORTED,
+ ApiDetector.INLINED,
+ TypoDetector.ISSUE,
+ ManifestDetector.ALLOW_BACKUP,
+ MissingIdDetector.ISSUE,
+ TranslationDetector.MISSING,
+ DosLineEndingDetector.ISSUE
+ );
+ }
+ return sAdtFixes.contains(issue);
+ } else if (tool == QuickfixHandler.STUDIO) {
+ // List generated by AndroidLintInspectionToolProviderTest in tools/adt/idea;
+ // set LIST_ISSUES_WITH_QUICK_FIXES to true
+ if (sStudioFixes == null) {
+ sStudioFixes = Sets.newHashSet(
+ AccessibilityDetector.ISSUE,
+ AlwaysShowActionDetector.ISSUE,
+ AndroidAutoDetector.INVALID_USES_TAG_ISSUE,
+ AndroidTvDetector.MISSING_BANNER,
+ AndroidTvDetector.MISSING_LEANBACK_SUPPORT,
+ AndroidTvDetector.PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE,
+ AndroidTvDetector.UNSUPPORTED_TV_HARDWARE,
+ AnnotationDetector.SWITCH_TYPE_DEF,
+ ApiDetector.INLINED,
+ ApiDetector.OVERRIDE,
+ ApiDetector.UNSUPPORTED,
+ ApiDetector.UNUSED,
+ AppCompatCallDetector.ISSUE,
+ AppIndexingApiDetector.ISSUE_APP_INDEXING,
+ AppIndexingApiDetector.ISSUE_APP_INDEXING_API,
+ AppIndexingApiDetector.ISSUE_URL_ERROR,
+ ByteOrderMarkDetector.BOM,
+ CommentDetector.STOP_SHIP,
+ DetectMissingPrefix.MISSING_NAMESPACE,
+ DuplicateResourceDetector.TYPE_MISMATCH,
+ GradleDetector.COMPATIBILITY,
+ GradleDetector.DEPENDENCY,
+ GradleDetector.DEPRECATED,
+ GradleDetector.NOT_INTERPOLATED,
+ GradleDetector.PLUS,
+ GradleDetector.REMOTE_VERSION,
+ GradleDetector.STRING_INTEGER,
+ GridLayoutDetector.ISSUE,
+ IncludeDetector.ISSUE,
+ InefficientWeightDetector.BASELINE_WEIGHTS,
+ InefficientWeightDetector.INEFFICIENT_WEIGHT,
+ InefficientWeightDetector.ORIENTATION,
+ JavaPerformanceDetector.USE_VALUE_OF,
+ ManifestDetector.ALLOW_BACKUP,
+ ManifestDetector.APPLICATION_ICON,
+ ManifestDetector.MIPMAP,
+ ManifestDetector.MOCK_LOCATION,
+ ManifestDetector.TARGET_NEWER,
+ MissingClassDetector.INNERCLASS,
+ MissingIdDetector.ISSUE,
+ NamespaceDetector.RES_AUTO,
+ ObsoleteLayoutParamsDetector.ISSUE,
+ ParcelDetector.ISSUE,
+ PropertyFileDetector.ESCAPE,
+ PropertyFileDetector.HTTP,
+ PxUsageDetector.DP_ISSUE,
+ PxUsageDetector.PX_ISSUE,
+ ReadParcelableDetector.ISSUE,
+ RtlDetector.COMPAT,
+ ScrollViewChildDetector.ISSUE,
+ SecurityDetector.EXPORTED_SERVICE,
+ SharedPrefsDetector.ISSUE,
+ SignatureOrSystemDetector.ISSUE,
+ SupportAnnotationDetector.CHECK_PERMISSION,
+ SupportAnnotationDetector.CHECK_RESULT,
+ SupportAnnotationDetector.MISSING_PERMISSION,
+ TextFieldDetector.ISSUE,
+ TextViewDetector.SELECTABLE,
+ TitleDetector.ISSUE,
+ TypoDetector.ISSUE,
+ TypographyDetector.DASHES,
+ TypographyDetector.ELLIPSIS,
+ TypographyDetector.FRACTIONS,
+ TypographyDetector.OTHER,
+ TypographyDetector.QUOTES,
+ UselessViewDetector.USELESS_LEAF,
+ Utf8Detector.ISSUE,
+ WrongCallDetector.ISSUE,
+ WrongCaseDetector.WRONG_CASE
+ );
+ }
+ return sStudioFixes.contains(issue);
+ }
+
+ return false;
+ }
+}
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java b/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
rename to lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Warning.java b/lint/cli/src/main/java/com/android/tools/lint/Warning.java
new file mode 100644
index 0000000..c2dbe30
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/Warning.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link Warning} represents a specific warning that a {@link LintClient}
+ * has been told about. The context stores these as they are reported into a
+ * list of warnings such that it can sort them all before presenting them all at
+ * the end.
+ */
+public class Warning implements Comparable<Warning> {
+ public final Issue issue;
+ public final String message;
+ public final Severity severity;
+ public final Project project;
+ public AndroidProject gradleProject;
+ public Location location;
+ public File file;
+ public String path;
+ public int line = -1;
+ public int offset = -1;
+ public String errorLine;
+ public String fileContents;
+ public Set<Variant> variants;
+
+ public Warning(Issue issue, String message, Severity severity, Project project) {
+ this.issue = issue;
+ this.message = message;
+ this.severity = severity;
+ this.project = project;
+ }
+
+ // ---- Implements Comparable<Warning> ----
+ @SuppressWarnings({"VariableNotUsedInsideIf", "ConstantConditions"})
+ @Override
+ public int compareTo(@NonNull Warning other) {
+ // Sort by category, then by priority, then by id,
+ // then by file, then by line
+ int categoryDelta = issue.getCategory().compareTo(other.issue.getCategory());
+ if (categoryDelta != 0) {
+ return categoryDelta;
+ }
+ // DECREASING priority order
+ int priorityDelta = other.issue.getPriority() - issue.getPriority();
+ if (priorityDelta != 0) {
+ return priorityDelta;
+ }
+ String id1 = issue.getId();
+ String id2 = other.issue.getId();
+ assert id1 != null;
+ assert id2 != null;
+ int idDelta = id1.compareTo(id2);
+ if (idDelta != 0) {
+ return idDelta;
+ }
+ if (file != null) {
+ if (other.file != null) {
+ int fileDelta = file.getName().compareTo(
+ other.file.getName());
+ if (fileDelta != 0) {
+ return fileDelta;
+ }
+ } else {
+ return -1;
+ }
+ } else if (other.file != null) {
+ return 1;
+ }
+ if (line != other.line) {
+ return line - other.line;
+ }
+
+ int delta = message.compareTo(other.message);
+ if (delta != 0) {
+ return delta;
+ }
+
+ if (file != null) {
+ if (other.file != null) {
+ delta = file.compareTo(other.file);
+ if (delta != 0) {
+ return delta;
+ }
+ } else {
+ return -1;
+ }
+ } else if (other.file != null) {
+ return 1;
+ }
+
+ Location secondary1 = location != null ? location.getSecondary() : null;
+ File secondaryFile1 = secondary1 != null ? secondary1.getFile() : null;
+ Location secondary2 = other.location != null ? other.location.getSecondary() : null;
+ File secondaryFile2 = secondary2 != null ? secondary2.getFile() : null;
+ if (secondaryFile1 != null) {
+ if (secondaryFile2 != null) {
+ return secondaryFile1.compareTo(secondaryFile2);
+ } else {
+ return -1;
+ }
+ } else if (secondaryFile2 != null) {
+ return 1;
+ }
+
+ // This handles the case where you have a huge XML document without hewlines,
+ // such that all the errors end up on the same line.
+ if (location != null && other.location != null &&
+ location.getStart() != null && other.location.getStart() != null) {
+ delta = location.getStart().getColumn() - other.location.getStart().getColumn();
+ if (delta != 0) {
+ return delta;
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Warning warning = (Warning) o;
+
+ if (line != warning.line) {
+ return false;
+ }
+ if (file != null ? !file.equals(warning.file) : warning.file != null) {
+ return false;
+ }
+ if (!issue.getCategory().equals(warning.issue.getCategory())) {
+ return false;
+ }
+ if (issue.getPriority() != warning.issue.getPriority()) {
+ return false;
+ }
+ if (!issue.getId().equals(warning.issue.getId())) {
+ return false;
+ }
+ //noinspection RedundantIfStatement
+ if (!message.equals(warning.message)) {
+ return false;
+ }
+
+ Location secondary1 = location != null ? location.getSecondary() : null;
+ Location secondary2 = warning.location != null ? warning.location.getSecondary() : null;
+ if (secondary1 != null) {
+ if (secondary2 != null) {
+ if (!Objects.equal(secondary1.getFile(), secondary2.getFile())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else //noinspection VariableNotUsedInsideIf
+ if (secondary2 != null) {
+ return false;
+ }
+
+ // This handles the case where you have a huge XML document without newlines,
+ // such that all the errors end up on the same line.
+ //noinspection RedundantIfStatement
+ if (location != null && warning.location != null &&
+ location.getStart() != null && warning.location.getStart() != null &&
+ location.getStart().getColumn() != warning.location.getStart().getColumn()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = message.hashCode();
+ result = 31 * result + (file != null ? file.hashCode() : 0);
+ return result;
+ }
+
+ public boolean isVariantSpecific() {
+ return variants != null && variants.size() < gradleProject.getVariants().size();
+ }
+
+ public boolean includesMoreThanExcludes() {
+ assert isVariantSpecific();
+ int variantCount = variants.size();
+ int allVariantCount = gradleProject.getVariants().size();
+ return variantCount <= allVariantCount - variantCount;
+ }
+
+ public List<String> getIncludedVariantNames() {
+ assert isVariantSpecific();
+ List<String> names = new ArrayList<String>();
+ if (variants != null) {
+ for (Variant variant : variants) {
+ names.add(variant.getName());
+ }
+ }
+ Collections.sort(names);
+ return names;
+ }
+
+ public List<String> getExcludedVariantNames() {
+ assert isVariantSpecific();
+ Collection<Variant> variants = gradleProject.getVariants();
+ Set<String> allVariants = new HashSet<String>(variants.size());
+ for (Variant variant : variants) {
+ allVariants.add(variant.getName());
+ }
+ Set<String> included = new HashSet<String>(getIncludedVariantNames());
+ Set<String> excluded = Sets.difference(allVariants, included);
+ List<String> sorted = Lists.newArrayList(excluded);
+ Collections.sort(sorted);
+ return sorted;
+ }
+
+ @Override
+ public String toString() {
+ return "Warning{" +
+ "issue=" + issue +
+ ", message='" + message + '\'' +
+ ", file=" + file +
+ ", line=" + line +
+ '}';
+ }
+}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
new file mode 100644
index 0000000..2d1ac37
--- /dev/null
+++ b/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.tools.lint.detector.api.TextFormat.RAW;
+
+import com.android.tools.lint.checks.BuiltinIssueRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.utils.SdkUtils;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.io.Files;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * A reporter which emits lint results into an XML report.
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class XmlReporter extends Reporter {
+ private final Writer mWriter;
+
+ /**
+ * Constructs a new {@link XmlReporter}
+ *
+ * @param client the client
+ * @param output the output file
+ * @throws IOException if an error occurs
+ */
+ public XmlReporter(LintCliClient client, File output) throws IOException {
+ super(client, output);
+ mWriter = new BufferedWriter(Files.newWriter(output, Charsets.UTF_8));
+ }
+
+ @Override
+ public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException {
+ mWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); //$NON-NLS-1$
+ // Format 4: added urls= attribute with all more info links, comma separated
+ mWriter.write("<issues format=\"4\""); //$NON-NLS-1$
+ String revision = mClient.getRevision();
+ if (revision != null) {
+ mWriter.write(String.format(" by=\"lint %1$s\"", revision)); //$NON-NLS-1$
+ }
+ mWriter.write(">\n"); //$NON-NLS-1$
+
+ if (!issues.isEmpty()) {
+ for (Warning warning : issues) {
+ mWriter.write('\n');
+ indent(mWriter, 1);
+ mWriter.write("<issue"); //$NON-NLS-1$
+ Issue issue = warning.issue;
+ writeAttribute(mWriter, 2, "id", issue.getId()); //$NON-NLS-1$
+ writeAttribute(mWriter, 2, "severity",
+ warning.severity.getDescription());
+ writeAttribute(mWriter, 2, "message", warning.message); //$NON-NLS-1$
+
+ writeAttribute(mWriter, 2, "category", //$NON-NLS-1$
+ issue.getCategory().getFullName());
+ writeAttribute(mWriter, 2, "priority", //$NON-NLS-1$
+ Integer.toString(issue.getPriority()));
+ writeAttribute(mWriter, 2, "summary", issue.getBriefDescription(RAW));//$NON-NLS-1$
+ writeAttribute(mWriter, 2, "explanation", issue.getExplanation(RAW)); //$NON-NLS-1$
+ List<String> moreInfo = issue.getMoreInfo();
+ if (!moreInfo.isEmpty()) {
+ // Compatibility with old format: list first URL
+ writeAttribute(mWriter, 2, "url", moreInfo.get(0)); //$NON-NLS-1$
+ writeAttribute(mWriter, 2, "urls", //$NON-NLS-1$
+ Joiner.on(',').join(issue.getMoreInfo()));
+ }
+ if (warning.errorLine != null && !warning.errorLine.isEmpty()) {
+ String line = warning.errorLine;
+ int index1 = line.indexOf('\n');
+ if (index1 != -1) {
+ int index2 = line.indexOf('\n', index1 + 1);
+ if (index2 != -1) {
+ String line1 = line.substring(0, index1);
+ String line2 = line.substring(index1 + 1, index2);
+ writeAttribute(mWriter, 2, "errorLine1", line1); //$NON-NLS-1$
+ writeAttribute(mWriter, 2, "errorLine2", line2); //$NON-NLS-1$
+ }
+ }
+ }
+
+ if (warning.isVariantSpecific()) {
+ writeAttribute(mWriter, 2, "includedVariants", Joiner.on(',').join(warning.getIncludedVariantNames()));
+ writeAttribute(mWriter, 2, "excludedVariants", Joiner.on(',').join(warning.getExcludedVariantNames()));
+ }
+
+ if (mClient.getRegistry() instanceof BuiltinIssueRegistry) {
+ boolean adt = QuickfixHandler.ADT.hasAutoFix(issue);
+ boolean studio = QuickfixHandler.STUDIO.hasAutoFix(issue);
+ if (adt || studio) { //$NON-NLS-1$
+ String value = adt && studio ? "studio,adt" : studio ? "studio" : "adt";
+ writeAttribute(mWriter, 2, "quickfix", value); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ assert (warning.file != null) == (warning.location != null);
+
+ if (warning.file != null) {
+ assert warning.location.getFile() == warning.file;
+ }
+
+ Location location = warning.location;
+ if (location != null) {
+ mWriter.write(">\n"); //$NON-NLS-1$
+ while (location != null) {
+ indent(mWriter, 2);
+ mWriter.write("<location"); //$NON-NLS-1$
+ String path = mClient.getDisplayPath(warning.project, location.getFile());
+ writeAttribute(mWriter, 3, "file", path); //$NON-NLS-1$
+ Position start = location.getStart();
+ if (start != null) {
+ int line = start.getLine();
+ int column = start.getColumn();
+ if (line >= 0) {
+ // +1: Line numbers internally are 0-based, report should be
+ // 1-based.
+ writeAttribute(mWriter, 3, "line", //$NON-NLS-1$
+ Integer.toString(line + 1));
+ if (column >= 0) {
+ writeAttribute(mWriter, 3, "column", //$NON-NLS-1$
+ Integer.toString(column + 1));
+ }
+ }
+ }
+
+ mWriter.write("/>\n"); //$NON-NLS-1$
+ location = location.getSecondary();
+ }
+ indent(mWriter, 1);
+ mWriter.write("</issue>\n"); //$NON-NLS-1$
+ } else {
+ mWriter.write('\n');
+ indent(mWriter, 1);
+ mWriter.write("/>\n"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ mWriter.write("\n</issues>\n"); //$NON-NLS-1$
+ mWriter.close();
+
+ if (!mClient.getFlags().isQuiet()
+ && (mDisplayEmpty || errorCount > 0 || warningCount > 0)) {
+ String url = SdkUtils.fileToUrlString(mOutput.getAbsoluteFile());
+ System.out.println(String.format("Wrote XML report to %1$s", url));
+ }
+ }
+
+ private static void writeAttribute(Writer writer, int indent, String name, String value)
+ throws IOException {
+ writer.write('\n');
+ indent(writer, indent);
+ writer.write(name);
+ writer.write('=');
+ writer.write('"');
+ for (int i = 0, n = value.length(); i < n; i++) {
+ char c = value.charAt(i);
+ switch (c) {
+ case '"':
+ writer.write("""); //$NON-NLS-1$
+ break;
+ case '\'':
+ writer.write("'"); //$NON-NLS-1$
+ break;
+ case '&':
+ writer.write("&"); //$NON-NLS-1$
+ break;
+ case '<':
+ writer.write("<"); //$NON-NLS-1$
+ break;
+ default:
+ writer.write(c);
+ break;
+ }
+ }
+ writer.write('"');
+ }
+
+ private static void indent(Writer writer, int indent) throws IOException {
+ for (int level = 0; level < indent; level++) {
+ writer.write(" "); //$NON-NLS-1$
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/default.css b/lint/cli/src/main/java/com/android/tools/lint/default.css
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/default.css
rename to lint/cli/src/main/java/com/android/tools/lint/default.css
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/hololike.css b/lint/cli/src/main/java/com/android/tools/lint/hololike.css
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/hololike.css
rename to lint/cli/src/main/java/com/android/tools/lint/hololike.css
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/lint-error.png b/lint/cli/src/main/java/com/android/tools/lint/lint-error.png
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/lint-error.png
rename to lint/cli/src/main/java/com/android/tools/lint/lint-error.png
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/lint-run.png b/lint/cli/src/main/java/com/android/tools/lint/lint-run.png
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/lint-run.png
rename to lint/cli/src/main/java/com/android/tools/lint/lint-run.png
diff --git a/base/lint/cli/src/main/java/com/android/tools/lint/lint-warning.png b/lint/cli/src/main/java/com/android/tools/lint/lint-warning.png
similarity index 100%
rename from base/lint/cli/src/main/java/com/android/tools/lint/lint-warning.png
rename to lint/cli/src/main/java/com/android/tools/lint/lint-warning.png
diff --git a/base/lint/cli/src/test/.classpath b/lint/cli/src/test/.classpath
similarity index 100%
rename from base/lint/cli/src/test/.classpath
rename to lint/cli/src/test/.classpath
diff --git a/base/lint/cli/src/test/.project b/lint/cli/src/test/.project
similarity index 100%
rename from base/lint/cli/src/test/.project
rename to lint/cli/src/test/.project
diff --git a/base/lint/cli/src/test/.settings/org.eclipse.core.resources.prefs b/lint/cli/src/test/.settings/org.eclipse.core.resources.prefs
similarity index 100%
rename from base/lint/cli/src/test/.settings/org.eclipse.core.resources.prefs
rename to lint/cli/src/test/.settings/org.eclipse.core.resources.prefs
diff --git a/base/lint/cli/src/test/.settings/org.moreunit.prefs b/lint/cli/src/test/.settings/org.moreunit.prefs
similarity index 100%
rename from base/lint/cli/src/test/.settings/org.moreunit.prefs
rename to lint/cli/src/test/.settings/org.moreunit.prefs
diff --git a/base/lint/libs/lint-api/.classpath b/lint/libs/lint-api/.classpath
similarity index 100%
rename from base/lint/libs/lint-api/.classpath
rename to lint/libs/lint-api/.classpath
diff --git a/base/lint/libs/lint-api/.project b/lint/libs/lint-api/.project
similarity index 100%
rename from base/lint/libs/lint-api/.project
rename to lint/libs/lint-api/.project
diff --git a/base/lint/cli/.settings/org.eclipse.jdt.core.prefs b/lint/libs/lint-api/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/lint/cli/.settings/org.eclipse.jdt.core.prefs
rename to lint/libs/lint-api/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/lint/libs/lint-api/.settings/org.moreunit.prefs b/lint/libs/lint-api/.settings/org.moreunit.prefs
similarity index 100%
rename from base/lint/libs/lint-api/.settings/org.moreunit.prefs
rename to lint/libs/lint-api/.settings/org.moreunit.prefs
diff --git a/base/lint/libs/lint-api/NOTICE b/lint/libs/lint-api/NOTICE
similarity index 100%
rename from base/lint/libs/lint-api/NOTICE
rename to lint/libs/lint-api/NOTICE
diff --git a/base/lint/libs/lint-api/build.gradle b/lint/libs/lint-api/build.gradle
similarity index 100%
rename from base/lint/libs/lint-api/build.gradle
rename to lint/libs/lint-api/build.gradle
diff --git a/lint/libs/lint-api/lint-api-base.iml b/lint/libs/lint-api/lint-api-base.iml
new file mode 100644
index 0000000..bb41d17
--- /dev/null
+++ b/lint/libs/lint-api/lint-api-base.iml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="library" exported="" name="asm-tools" level="project" />
+ <orderEntry type="library" exported="" name="lombok-ast" level="project" />
+ <orderEntry type="library" exported="" name="builder-model" level="project" />
+ <orderEntry type="module" module-name="android-annotations" exported="" />
+ <orderEntry type="library" exported="" name="guava-tools" level="project" />
+ <orderEntry type="module" module-name="common" />
+ <orderEntry type="module" module-name="layoutlib-api-base" />
+ <orderEntry type="module" module-name="sdk-common-base" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/base/lint/libs/lint-api/lint-api.iml b/lint/libs/lint-api/lint-api.iml
similarity index 100%
rename from base/lint/libs/lint-api/lint-api.iml
rename to lint/libs/lint-api/lint-api.iml
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ClassEntry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ClassEntry.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ClassEntry.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ClassEntry.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CompositeIssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CompositeIssueRegistry.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CompositeIssueRegistry.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CompositeIssueRegistry.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/Configuration.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/Configuration.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/Configuration.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/Configuration.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
new file mode 100644
index 0000000..d7481f9
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Registry which provides a list of checks to be performed on an Android project
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public abstract class IssueRegistry {
+ private static volatile List<Category> sCategories;
+ private static volatile Map<String, Issue> sIdToIssue;
+ private static Map<EnumSet<Scope>, List<Issue>> sScopeIssues = Maps.newHashMap();
+
+ /**
+ * Creates a new {@linkplain IssueRegistry}
+ */
+ protected IssueRegistry() {
+ }
+
+ private static final Implementation DUMMY_IMPLEMENTATION = new Implementation(Detector.class,
+ EnumSet.noneOf(Scope.class));
+ /**
+ * Issue reported by lint (not a specific detector) when it cannot even
+ * parse an XML file prior to analysis
+ */
+ @NonNull
+ public static final Issue PARSER_ERROR = Issue.create(
+ "ParserError", //$NON-NLS-1$
+ "Parser Errors",
+ "Lint will ignore any files that contain fatal parsing errors. These may contain " +
+ "other errors, or contain code which affects issues in other files.",
+ Category.CORRECTNESS,
+ 10,
+ Severity.ERROR,
+ DUMMY_IMPLEMENTATION);
+
+ /**
+ * Issue reported by lint for various other issues which prevents lint from
+ * running normally when it's not necessarily an error in the user's code base.
+ */
+ @NonNull
+ public static final Issue LINT_ERROR = Issue.create(
+ "LintError", //$NON-NLS-1$
+ "Lint Failure",
+ "This issue type represents a problem running lint itself. Examples include " +
+ "failure to find bytecode for source files (which means certain detectors " +
+ "could not be run), parsing errors in lint configuration files, etc." +
+ "\n" +
+ "These errors are not errors in your own code, but they are shown to make " +
+ "it clear that some checks were not completed.",
+
+ Category.LINT,
+ 10,
+ Severity.ERROR,
+ DUMMY_IMPLEMENTATION);
+
+ /**
+ * Issue reported when lint is canceled
+ */
+ @NonNull
+ public static final Issue CANCELLED = Issue.create(
+ "LintCanceled", //$NON-NLS-1$
+ "Lint Canceled",
+ "Lint canceled by user; the issue report may not be complete.",
+
+ Category.LINT,
+ 0,
+ Severity.INFORMATIONAL,
+ DUMMY_IMPLEMENTATION);
+
+ /**
+ * Returns the list of issues that can be found by all known detectors.
+ *
+ * @return the list of issues to be checked (including those that may be
+ * disabled!)
+ */
+ @NonNull
+ public abstract List<Issue> getIssues();
+
+ /**
+ * Get an approximate issue count for a given scope. This is just an optimization,
+ * so the number does not have to be accurate.
+ *
+ * @param scope the scope set
+ * @return an approximate ceiling of the number of issues expected for a given scope set
+ */
+ protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) {
+ return 20;
+ }
+
+ /**
+ * Returns all available issues of a given scope (regardless of whether
+ * they are actually enabled for a given configuration etc)
+ *
+ * @param scope the applicable scope set
+ * @return a list of issues
+ */
+ @NonNull
+ protected List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
+ List<Issue> list = sScopeIssues.get(scope);
+ if (list == null) {
+ List<Issue> issues = getIssues();
+ if (scope.equals(Scope.ALL)) {
+ list = issues;
+ } else {
+ list = new ArrayList<Issue>(getIssueCapacity(scope));
+ for (Issue issue : issues) {
+ // Determine if the scope matches
+ if (issue.getImplementation().isAdequate(scope)) {
+ list.add(issue);
+ }
+ }
+ }
+ sScopeIssues.put(scope, list);
+ }
+
+ return list;
+ }
+
+ /**
+ * Creates a list of detectors applicable to the given scope, and with the
+ * given configuration.
+ *
+ * @param client the client to report errors to
+ * @param configuration the configuration to look up which issues are
+ * enabled etc from
+ * @param scope the scope for the analysis, to filter out detectors that
+ * require wider analysis than is currently being performed
+ * @param scopeToDetectors an optional map which (if not null) will be
+ * filled by this method to contain mappings from each scope to
+ * the applicable detectors for that scope
+ * @return a list of new detector instances
+ */
+ @NonNull
+ final List<? extends Detector> createDetectors(
+ @NonNull LintClient client,
+ @NonNull Configuration configuration,
+ @NonNull EnumSet<Scope> scope,
+ @Nullable Map<Scope, List<Detector>> scopeToDetectors) {
+
+ List<Issue> issues = getIssuesForScope(scope);
+ if (issues.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ Set<Class<? extends Detector>> detectorClasses = new HashSet<Class<? extends Detector>>();
+ Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope =
+ new HashMap<Class<? extends Detector>, EnumSet<Scope>>();
+
+ for (Issue issue : issues) {
+ Implementation implementation = issue.getImplementation();
+ Class<? extends Detector> detectorClass = implementation.getDetectorClass();
+ EnumSet<Scope> issueScope = implementation.getScope();
+ if (!detectorClasses.contains(detectorClass)) {
+ // Determine if the issue is enabled
+ if (!configuration.isEnabled(issue)) {
+ continue;
+ }
+
+ assert implementation.isAdequate(scope); // Ensured by getIssuesForScope above
+
+ detectorClass = client.replaceDetector(detectorClass);
+
+ assert detectorClass != null : issue.getId();
+ detectorClasses.add(detectorClass);
+ }
+
+ if (scopeToDetectors != null) {
+ EnumSet<Scope> s = detectorToScope.get(detectorClass);
+ if (s == null) {
+ detectorToScope.put(detectorClass, issueScope);
+ } else if (!s.containsAll(issueScope)) {
+ EnumSet<Scope> union = EnumSet.copyOf(s);
+ union.addAll(issueScope);
+ detectorToScope.put(detectorClass, union);
+ }
+ }
+ }
+
+ List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size());
+ for (Class<? extends Detector> clz : detectorClasses) {
+ try {
+ Detector detector = clz.newInstance();
+ detectors.add(detector);
+
+ if (scopeToDetectors != null) {
+ EnumSet<Scope> union = detectorToScope.get(clz);
+ for (Scope s : union) {
+ List<Detector> list = scopeToDetectors.get(s);
+ if (list == null) {
+ list = new ArrayList<Detector>();
+ scopeToDetectors.put(s, list);
+ }
+ list.add(detector);
+ }
+
+ }
+ } catch (Throwable t) {
+ client.log(t, "Can't initialize detector %1$s", clz.getName()); //$NON-NLS-1$
+ }
+ }
+
+ return detectors;
+ }
+
+ /**
+ * Returns true if the given id represents a valid issue id
+ *
+ * @param id the id to be checked
+ * @return true if the given id is valid
+ */
+ public final boolean isIssueId(@NonNull String id) {
+ return getIssue(id) != null;
+ }
+
+ /**
+ * Returns true if the given category is a valid category
+ *
+ * @param name the category name to be checked
+ * @return true if the given string is a valid category
+ */
+ public final boolean isCategoryName(@NonNull String name) {
+ for (Category category : getCategories()) {
+ if (category.getName().equals(name) || category.getFullName().equals(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the available categories
+ *
+ * @return an iterator for all the categories, never null
+ */
+ @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
+ @NonNull
+ public List<Category> getCategories() {
+ List<Category> categories = sCategories;
+ if (categories == null) {
+ synchronized (IssueRegistry.class) {
+ categories = sCategories;
+ if (categories == null) {
+ sCategories = categories = Collections.unmodifiableList(createCategoryList());
+ }
+ }
+ }
+
+ return categories;
+ }
+
+ @NonNull
+ private List<Category> createCategoryList() {
+ Set<Category> categorySet = Sets.newHashSetWithExpectedSize(20);
+ for (Issue issue : getIssues()) {
+ categorySet.add(issue.getCategory());
+ }
+ List<Category> sorted = new ArrayList<Category>(categorySet);
+ Collections.sort(sorted);
+ return sorted;
+ }
+
+ /**
+ * Returns the issue for the given id, or null if it's not a valid id
+ *
+ * @param id the id to be checked
+ * @return the corresponding issue, or null
+ */
+ @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
+ @Nullable
+ public final Issue getIssue(@NonNull String id) {
+ Map<String, Issue> map = sIdToIssue;
+ if (map == null) {
+ synchronized (IssueRegistry.class) {
+ map = sIdToIssue;
+ if (map == null) {
+ map = createIdToIssueMap();
+ sIdToIssue = map;
+ }
+ }
+ }
+
+ return map.get(id);
+ }
+
+ @NonNull
+ private Map<String, Issue> createIdToIssueMap() {
+ List<Issue> issues = getIssues();
+ Map<String, Issue> map = Maps.newHashMapWithExpectedSize(issues.size() + 2);
+ for (Issue issue : issues) {
+ map.put(issue.getId(), issue);
+ }
+
+ map.put(PARSER_ERROR.getId(), PARSER_ERROR);
+ map.put(LINT_ERROR.getId(), LINT_ERROR);
+ return map;
+ }
+
+ /**
+ * Reset the registry such that it recomputes its available issues.
+ */
+ protected static void reset() {
+ sIdToIssue = null;
+ sCategories = null;
+ sScopeIssues = Maps.newHashMap();
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
new file mode 100644
index 0000000..b39adec
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.client.api;
+
+import static com.android.SdkConstants.DOT_CLASS;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+/**
+ * <p> An {@link IssueRegistry} for a custom lint rule jar file. The rule jar should provide a
+ * manifest entry with the key {@code Lint-Registry} and the value of the fully qualified name of an
+ * implementation of {@link IssueRegistry} (with a default constructor). </p>
+ *
+ * <p> NOTE: The custom issue registry should not extend this file; it should be a plain
+ * IssueRegistry! This file is used internally to wrap the given issue registry.</p>
+ */
+class JarFileIssueRegistry extends IssueRegistry {
+ /**
+ * Manifest constant for declaring an issue provider. Example: Lint-Registry:
+ * foo.bar.CustomIssueRegistry
+ */
+ private static final String MF_LINT_REGISTRY = "Lint-Registry"; //$NON-NLS-1$
+
+ private static Map<File, SoftReference<JarFileIssueRegistry>> sCache;
+
+ private final List<Issue> myIssues;
+
+ @NonNull
+ static IssueRegistry get(@NonNull LintClient client, @NonNull File jarFile) throws IOException,
+ ClassNotFoundException, IllegalAccessException, InstantiationException {
+ if (sCache == null) {
+ sCache = new HashMap<File, SoftReference<JarFileIssueRegistry>>();
+ } else {
+ SoftReference<JarFileIssueRegistry> reference = sCache.get(jarFile);
+ if (reference != null) {
+ JarFileIssueRegistry registry = reference.get();
+ if (registry != null) {
+ return registry;
+ }
+ }
+ }
+
+ // Ensure that the scope-to-detector map doesn't return stale results
+ IssueRegistry.reset();
+
+ JarFileIssueRegistry registry = new JarFileIssueRegistry(client, jarFile);
+ sCache.put(jarFile, new SoftReference<JarFileIssueRegistry>(registry));
+ return registry;
+ }
+
+ private JarFileIssueRegistry(@NonNull LintClient client, @NonNull File file)
+ throws IOException, ClassNotFoundException, IllegalAccessException,
+ InstantiationException {
+ myIssues = Lists.newArrayList();
+ JarFile jarFile = null;
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ jarFile = new JarFile(file);
+ Manifest manifest = jarFile.getManifest();
+ Attributes attrs = manifest.getMainAttributes();
+ Object object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY));
+ if (object instanceof String) {
+ String className = (String) object;
+ // Make a class loader for this jar
+ URL url = SdkUtils.fileToUrl(file);
+ ClassLoader loader = client.createUrlClassLoader(new URL[]{url},
+ JarFileIssueRegistry.class.getClassLoader());
+ Class<?> registryClass = Class.forName(className, true, loader);
+ IssueRegistry registry = (IssueRegistry) registryClass.newInstance();
+ myIssues.addAll(registry.getIssues());
+
+ if (loader instanceof URLClassLoader) {
+ loadAndCloseURLClassLoader(client, file, (URLClassLoader)loader);
+ }
+ } else {
+ client.log(Severity.ERROR, null,
+ "Custom lint rule jar %1$s does not contain a valid registry manifest key " +
+ "(%2$s).\n" +
+ "Either the custom jar is invalid, or it uses an outdated API not supported " +
+ "this lint client", file.getPath(), MF_LINT_REGISTRY);
+ }
+ } finally {
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ }
+ }
+
+ /**
+ * Work around http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5041014 :
+ * URLClassLoader, on Windows, locks the .jar file forever.
+ * As of Java 7, there's a workaround: you can call close() when you're "done"
+ * with the file. We'll do that here. However, the whole point of the
+ * {@linkplain JarFileIssueRegistry} is that when lint is run over and over again
+ * as the user is editing in the IDE and we're background checking the code, we
+ * don't to keep loading the custom view classes over and over again: we want to
+ * cache them. Therefore, just closing the URLClassLoader right away isn't great
+ * either. However, it turns out it's safe to close the URLClassLoader once you've
+ * loaded the classes you need, since the URLClassLoader will continue to serve
+ * those classes even after its close() methods has been called.
+ * <p>
+ * Therefore, if we can call close() on this URLClassLoader, we'll proactively load
+ * all class files we find in the .jar file, then close it.
+ *
+ * @param client the client to report errors to
+ * @param file the .jar file
+ * @param loader the URLClassLoader we should close
+ */
+ private static void loadAndCloseURLClassLoader(
+ @NonNull LintClient client,
+ @NonNull File file,
+ @NonNull URLClassLoader loader) {
+ try {
+ // Proactively close out the .jar file. This is only available on Java 7.
+ Method closeMethod = loader.getClass().getDeclaredMethod("close");
+
+ // But first, proactively load all classes:
+ try {
+ InputStream inputStream = new FileInputStream(file);
+ try {
+ JarInputStream jarInputStream = new JarInputStream(inputStream);
+ try {
+ ZipEntry entry = jarInputStream.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+ // Load non-inner-classes
+ if (name.endsWith(DOT_CLASS) && name.indexOf('$') == -1) {
+ // Strip .class suffix and change .jar file path (/)
+ // to class name (.'s).
+ name = name.substring(0,
+ name.length() - DOT_CLASS.length());
+ name = name.replace('/', '.');
+ try {
+ Class.forName(name, true, loader);
+ } catch (Throwable e) {
+ client.log(Severity.ERROR, e,
+ "Failed to prefetch " + name + " from " + file);
+ }
+ }
+ entry = jarInputStream.getNextEntry();
+ }
+ } finally {
+ jarInputStream.close();
+ }
+ } finally {
+ inputStream.close();
+ }
+ } catch (Throwable ignore) {
+ } finally {
+ // Finally close the URL class loader
+ try {
+ closeMethod.invoke(loader);
+ } catch (Throwable ignore) {
+ // Couldn't close. This is unlikely.
+ }
+ }
+ } catch (NoSuchMethodException ignore) {
+ // No close method - we're on 1.6
+ }
+ }
+
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return myIssues;
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
new file mode 100644
index 0000000..4ce10dc
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaParser.java
@@ -0,0 +1,790 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.client.api;
+
+import static com.android.SdkConstants.ATTR_VALUE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.Catch;
+import lombok.ast.For;
+import lombok.ast.Identifier;
+import lombok.ast.If;
+import lombok.ast.Node;
+import lombok.ast.Return;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.Switch;
+import lombok.ast.Throw;
+import lombok.ast.TypeReference;
+import lombok.ast.TypeReferencePart;
+import lombok.ast.While;
+
+/**
+ * A wrapper for a Java parser. This allows tools integrating lint to map directly
+ * to builtin services, such as already-parsed data structures in Java editors.
+ * <p/>
+ * <b>NOTE: This is not public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public abstract class JavaParser {
+ public static final String TYPE_OBJECT = "java.lang.Object"; //$NON-NLS-1$
+ public static final String TYPE_STRING = "java.lang.String"; //$NON-NLS-1$
+ public static final String TYPE_INT = "int"; //$NON-NLS-1$
+ public static final String TYPE_LONG = "long"; //$NON-NLS-1$
+ public static final String TYPE_CHAR = "char"; //$NON-NLS-1$
+ public static final String TYPE_FLOAT = "float"; //$NON-NLS-1$
+ public static final String TYPE_DOUBLE = "double"; //$NON-NLS-1$
+ public static final String TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$
+ public static final String TYPE_SHORT = "short"; //$NON-NLS-1$
+ public static final String TYPE_BYTE = "byte"; //$NON-NLS-1$
+ public static final String TYPE_NULL = "null"; //$NON-NLS-1$
+
+ /**
+ * Prepare to parse the given contexts. This method will be called before
+ * a series of {@link #parseJava(JavaContext)} calls, which allows some
+ * parsers to do up front global computation in case they want to more
+ * efficiently process multiple files at the same time. This allows a single
+ * type-attribution pass for example, which is a lot more efficient than
+ * performing global type analysis over and over again for each individual
+ * file
+ *
+ * @param contexts a list of contexts to be parsed
+ */
+ public abstract void prepareJavaParse(@NonNull List<JavaContext> contexts);
+
+ /**
+ * Parse the file pointed to by the given context.
+ *
+ * @param context the context pointing to the file to be parsed, typically
+ * via {@link Context#getContents()} but the file handle (
+ * {@link Context#file} can also be used to map to an existing
+ * editor buffer in the surrounding tool, etc)
+ * @return the compilation unit node for the file
+ */
+ @Nullable
+ public abstract Node parseJava(@NonNull JavaContext context);
+
+ /**
+ * Returns a {@link Location} for the given node
+ *
+ * @param context information about the file being parsed
+ * @param node the node to create a location for
+ * @return a location for the given node
+ */
+ @NonNull
+ public abstract Location getLocation(@NonNull JavaContext context, @NonNull Node node);
+
+ /**
+ * Returns a {@link Location} for the given node range (from the starting offset of the first
+ * node to the ending offset of the second node).
+ *
+ * @param from the AST node to get a starting location from
+ * @param fromDelta Offset delta to apply to the starting offset
+ * @param to the AST node to get a ending location from
+ * @param toDelta Offset delta to apply to the ending offset
+ * @return a location for the given node
+ */
+ @NonNull
+ public abstract Location getRangeLocation(
+ @NonNull JavaContext context,
+ @NonNull Node from,
+ int fromDelta,
+ @NonNull Node to,
+ int toDelta);
+
+ /**
+ * Returns a {@link Location} for the given node. This attempts to pick a shorter
+ * location range than the entire node; for a class or method for example, it picks
+ * the name node (if found). For statement constructs such as a {@code switch} statement
+ * it will highlight the keyword, etc.
+ *
+ * @param context information about the file being parsed
+ * @param node the node to create a location for
+ * @return a location for the given node
+ */
+ @NonNull
+ public Location getNameLocation(@NonNull JavaContext context, @NonNull Node node) {
+ Node nameNode = JavaContext.findNameNode(node);
+ if (nameNode != null) {
+ node = nameNode;
+ } else {
+ if (node instanceof Switch
+ || node instanceof For
+ || node instanceof If
+ || node instanceof While
+ || node instanceof Throw
+ || node instanceof Return) {
+ // Lint doesn't want to highlight the entire statement/block associated
+ // with this node, it wants to just highlight the keyword.
+ Location location = getLocation(context, node);
+ Position start = location.getStart();
+ if (start != null) {
+ // The Lombok classes happen to have the same length as the target keyword
+ int length = node.getClass().getSimpleName().length();
+ return Location.create(location.getFile(), start,
+ new DefaultPosition(start.getLine(), start.getColumn() + length,
+ start.getOffset() + length));
+ }
+ }
+ }
+
+ return getLocation(context, node);
+ }
+
+ /**
+ * Creates a light-weight handle to a location for the given node. It can be
+ * turned into a full fledged location by
+ * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
+ *
+ * @param context the context providing the node
+ * @param node the node (element or attribute) to create a location handle
+ * for
+ * @return a location handle
+ */
+ @NonNull
+ public abstract Location.Handle createLocationHandle(@NonNull JavaContext context,
+ @NonNull Node node);
+
+ /**
+ * Dispose any data structures held for the given context.
+ * @param context information about the file previously parsed
+ * @param compilationUnit the compilation unit being disposed
+ */
+ public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) {
+ }
+
+ /**
+ * Dispose any remaining data structures held for all contexts.
+ * Typically frees up any resources allocated by
+ * {@link #prepareJavaParse(List)}
+ */
+ public void dispose() {
+ }
+
+ /**
+ * Resolves the given expression node: computes the declaration for the given symbol
+ *
+ * @param context information about the file being parsed
+ * @param node the node to resolve
+ * @return a node representing the resolved fully type: class/interface/annotation,
+ * field, method or variable
+ */
+ @Nullable
+ public abstract ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node);
+
+ /**
+ * Finds the given type, if possible (which should be reachable from the compilation
+ * patch of the given node.
+ *
+ * @param context information about the file being parsed
+ * @param fullyQualifiedName the fully qualified name of the class to look up
+ * @return the class, or null if not found
+ */
+ @Nullable
+ public ResolvedClass findClass(
+ @NonNull JavaContext context,
+ @NonNull String fullyQualifiedName) {
+ return null;
+ }
+
+ /**
+ * Returns the set of exception types handled by the given catch block.
+ * <p>
+ * This is a workaround for the fact that the Lombok AST API (and implementation)
+ * doesn't support multi-catch statements.
+ */
+ public List<TypeDescriptor> getCatchTypes(@NonNull JavaContext context,
+ @NonNull Catch catchBlock) {
+ TypeReference typeReference = catchBlock.astExceptionDeclaration().astTypeReference();
+ return Collections.<TypeDescriptor>singletonList(new DefaultTypeDescriptor(
+ typeReference.getTypeName()));
+ }
+
+ /**
+ * Gets the type of the given node
+ *
+ * @param context information about the file being parsed
+ * @param node the node to look up the type for
+ * @return the type of the node, if known
+ */
+ @Nullable
+ public abstract TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node);
+
+ /** A description of a type, such as a primitive int or the android.app.Activity class */
+ public abstract static class TypeDescriptor {
+ /**
+ * Returns the fully qualified name of the type, such as "int" or "android.app.Activity"
+ * */
+ @NonNull public abstract String getName();
+
+ /** Returns the simple name of this class */
+ @NonNull
+ public String getSimpleName() {
+ // This doesn't handle inner classes properly, so subclasses with more
+ // accurate type information will override to handle it correctly.
+ String name = getName();
+ int index = name.lastIndexOf('.');
+ if (index != -1) {
+ return name.substring(index + 1);
+ }
+ return name;
+ }
+
+ /**
+ * Returns the full signature of the type, which is normally the same as {@link #getName()}
+ * but for arrays can include []'s, for generic methods can include generics parameters
+ * etc
+ */
+ @NonNull public abstract String getSignature();
+
+ /**
+ * Computes the internal class name of the given fully qualified class name.
+ * For example, it converts foo.bar.Foo.Bar into foo/bar/Foo$Bar.
+ * This should only be called for class types, not primitives.
+ *
+ * @return the internal class name
+ */
+ @NonNull public String getInternalName() {
+ return ClassContext.getInternalName(getName());
+ }
+
+ public abstract boolean matchesName(@NonNull String name);
+
+ /**
+ * Returns true if the given TypeDescriptor represents an array
+ * @return true if this type represents an array
+ */
+ public abstract boolean isArray();
+
+ /**
+ * Returns true if the given TypeDescriptor represents a primitive
+ * @return true if this type represents a primitive
+ */
+ public abstract boolean isPrimitive();
+
+ public abstract boolean matchesSignature(@NonNull String signature);
+
+ @NonNull
+ public TypeReference getNode() {
+ TypeReference typeReference = new TypeReference();
+ StrictListAccessor<TypeReferencePart, TypeReference> parts = typeReference.astParts();
+ for (String part : Splitter.on('.').split(getName())) {
+ Identifier identifier = Identifier.of(part);
+ parts.addToEnd(new TypeReferencePart().astIdentifier(identifier));
+ }
+
+ return typeReference;
+ }
+
+ /** If the type is not primitive, returns the class of the type if known */
+ @Nullable
+ public abstract ResolvedClass getTypeClass();
+
+ @Override
+ public abstract boolean equals(Object o);
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ }
+
+ /** Convenience implementation of {@link TypeDescriptor} */
+ public static class DefaultTypeDescriptor extends TypeDescriptor {
+
+ private String mName;
+
+ public DefaultTypeDescriptor(String name) {
+ mName = name;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ @Override
+ public String getSignature() {
+ return getName();
+ }
+
+ @Override
+ public boolean matchesName(@NonNull String name) {
+ return mName.equals(name);
+ }
+
+ @Override
+ public boolean isArray() {
+ return mName.endsWith("[]");
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return mName.indexOf('.') != -1;
+ }
+
+ @Override
+ public boolean matchesSignature(@NonNull String signature) {
+ return matchesName(signature);
+ }
+
+ @Override
+ public String toString() {
+ return getSignature();
+ }
+
+ @Override
+ @Nullable
+ public ResolvedClass getTypeClass() {
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DefaultTypeDescriptor that = (DefaultTypeDescriptor) o;
+
+ return !(mName != null ? !mName.equals(that.mName) : that.mName != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return mName != null ? mName.hashCode() : 0;
+ }
+ }
+
+ /** A resolved declaration from an AST Node reference */
+ public abstract static class ResolvedNode {
+ @NonNull
+ public abstract String getName();
+
+ /** Returns the signature of the resolved node */
+ public abstract String getSignature();
+
+ public abstract int getModifiers();
+
+ @Override
+ public String toString() {
+ return getSignature();
+ }
+
+ /** Returns any annotations defined on this node */
+ @NonNull
+ public abstract Iterable<ResolvedAnnotation> getAnnotations();
+
+ /**
+ * Searches for the annotation of the given type on this node
+ *
+ * @param type the fully qualified name of the annotation to check
+ * @return the annotation, or null if not found
+ */
+ @Nullable
+ public ResolvedAnnotation getAnnotation(@NonNull String type) {
+ for (ResolvedAnnotation annotation : getAnnotations()) {
+ if (annotation.getType().matchesSignature(type)) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if this element is in the given package (or optionally, in one of its sub
+ * packages)
+ *
+ * @param pkg the package name
+ * @param includeSubPackages whether to include subpackages
+ * @return true if the element is in the given package
+ */
+ public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
+ return getSignature().startsWith(pkg);
+ }
+
+ /**
+ * Attempts to find the corresponding AST node, if possible. This won't work if for example
+ * the resolved node is from a binary (such as a compiled class in a .jar) or if the
+ * underlying parser doesn't support it.
+ * <p>
+ * Note that looking up the AST node can result in different instances for each lookup.
+ *
+ * @return an AST node, if possible.
+ */
+ @Nullable
+ public Node findAstNode() {
+ return null;
+ }
+ }
+
+ /** A resolved class declaration (class, interface, enumeration or annotation) */
+ public abstract static class ResolvedClass extends ResolvedNode {
+ /** Returns the fully qualified name of this class */
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns the simple name of this class */
+ @NonNull
+ public abstract String getSimpleName();
+
+ /** Returns the package name of this class */
+ @NonNull
+ public String getPackageName() {
+ String name = getName();
+ String simpleName = getSimpleName();
+ if (name.length() > simpleName.length() + 1) {
+ return name.substring(0, name.length() - simpleName.length() - 1);
+ }
+ return name;
+ }
+
+ /** Returns whether this class' fully qualified name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @Nullable
+ public abstract ResolvedClass getSuperClass();
+
+ @NonNull
+ public abstract Iterable<ResolvedClass> getInterfaces();
+
+ @Nullable
+ public abstract ResolvedClass getContainingClass();
+
+ public TypeDescriptor getType() {
+ return new DefaultTypeDescriptor(getName());
+ }
+
+ /**
+ * Determines whether this class extends the given name. If strict is true,
+ * it will not consider C extends C true.
+ * <p>
+ * The target must be a class; to check whether this class extends an interface,
+ * use {@link #isImplementing(String,boolean)} instead. If you're not sure, use
+ * {@link #isInheritingFrom(String, boolean)}.
+ *
+ * @param name the fully qualified class name
+ * @param strict if true, do not consider a class to be extending itself
+ * @return true if this class extends the given class
+ */
+ public abstract boolean isSubclassOf(@NonNull String name, boolean strict);
+
+ /**
+ * Determines whether this is implementing the given interface.
+ * <p>
+ * The target must be an interface; to check whether this class extends a class,
+ * use {@link #isSubclassOf(String, boolean)} instead. If you're not sure, use
+ * {@link #isInheritingFrom(String, boolean)}.
+ *
+ * @param name the fully qualified interface name
+ * @param strict if true, do not consider a class to be extending itself
+ * @return true if this class implements the given interface
+ */
+ public abstract boolean isImplementing(@NonNull String name, boolean strict);
+
+ /**
+ * Determines whether this class extends or implements the class of the given name.
+ * If strict is true, it will not consider C extends C true.
+ * <p>
+ * For performance reasons, if you know that the target is a class, consider using
+ * {@link #isSubclassOf(String, boolean)} instead, and if the target is an interface,
+ * consider using {@link #isImplementing(String,boolean)}.
+ *
+ * @param name the fully qualified class name
+ * @param strict if true, do not consider a class to be inheriting from itself
+ * @return true if this class extends or implements the given class
+ */
+ public abstract boolean isInheritingFrom(@NonNull String name, boolean strict);
+
+ @NonNull
+ public abstract Iterable<ResolvedMethod> getConstructors();
+
+ /** Returns the methods defined in this class, and optionally any methods inherited from any superclasses as well */
+ @NonNull
+ public abstract Iterable<ResolvedMethod> getMethods(boolean includeInherited);
+
+ /** Returns the methods of a given name defined in this class, and optionally any methods inherited from any superclasses as well */
+ @NonNull
+ public abstract Iterable<ResolvedMethod> getMethods(@NonNull String name, boolean includeInherited);
+
+ /** Returns the fields defined in this class, and optionally any fields declared in any superclasses as well */
+ @NonNull
+ public abstract Iterable<ResolvedField> getFields(boolean includeInherited);
+
+ /** Returns the named field defined in this class, or optionally inherited from a superclass */
+ @Nullable
+ public abstract ResolvedField getField(@NonNull String name, boolean includeInherited);
+
+ /** Returns the package containing this class */
+ @Nullable
+ public abstract ResolvedPackage getPackage();
+
+ @Override
+ public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
+ String packageName = getPackageName();
+
+ //noinspection SimplifiableIfStatement
+ if (pkg.equals(packageName)) {
+ return true;
+ }
+
+ return includeSubPackages && packageName.length() > pkg.length() &&
+ packageName.charAt(pkg.length()) == '.' &&
+ packageName.startsWith(pkg);
+ }
+ }
+
+ /** A method or constructor declaration */
+ public abstract static class ResolvedMethod extends ResolvedNode {
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this method name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @NonNull
+ public abstract ResolvedClass getContainingClass();
+
+ public abstract int getArgumentCount();
+
+ @NonNull
+ public abstract TypeDescriptor getArgumentType(int index);
+
+ /** Returns true if the parameter at the given index matches the given type signature */
+ public boolean argumentMatchesType(int index, @NonNull String signature) {
+ return getArgumentType(index).matchesSignature(signature);
+ }
+
+ @Nullable
+ public abstract TypeDescriptor getReturnType();
+
+ public boolean isConstructor() {
+ return getReturnType() == null;
+ }
+
+ /** Returns any annotations defined on the given parameter of this method */
+ @NonNull
+ public abstract Iterable<ResolvedAnnotation> getParameterAnnotations(int index);
+
+ /**
+ * Searches for the annotation of the given type on the method
+ *
+ * @param type the fully qualified name of the annotation to check
+ * @param parameterIndex the index of the parameter to look up
+ * @return the annotation, or null if not found
+ */
+ @Nullable
+ public ResolvedAnnotation getParameterAnnotation(@NonNull String type,
+ int parameterIndex) {
+ for (ResolvedAnnotation annotation : getParameterAnnotations(parameterIndex)) {
+ if (annotation.getType().matchesSignature(type)) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ /** Returns the super implementation of the given method, if any */
+ @Nullable
+ public ResolvedMethod getSuperMethod() {
+ ResolvedClass cls = getContainingClass().getSuperClass();
+ if (cls != null) {
+ String methodName = getName();
+ int argCount = getArgumentCount();
+ for (ResolvedMethod method : cls.getMethods(methodName, true)) {
+ if (argCount != method.getArgumentCount()) {
+ continue;
+ }
+ boolean sameTypes = true;
+ for (int arg = 0; arg < argCount; arg++) {
+ if (!method.getArgumentType(arg).equals(getArgumentType(arg))) {
+ sameTypes = false;
+ break;
+ }
+ }
+ if (sameTypes) {
+ return method;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
+ String packageName = getContainingClass().getPackageName();
+
+ //noinspection SimplifiableIfStatement
+ if (pkg.equals(packageName)) {
+ return true;
+ }
+
+ return includeSubPackages && packageName.length() > pkg.length() &&
+ packageName.charAt(pkg.length()) == '.' &&
+ packageName.startsWith(pkg);
+ }
+ }
+
+ /** A field declaration */
+ public abstract static class ResolvedField extends ResolvedNode {
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this field name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @NonNull
+ public abstract TypeDescriptor getType();
+
+ @Nullable
+ public abstract ResolvedClass getContainingClass();
+
+ @Nullable
+ public abstract Object getValue();
+
+ @Nullable
+ public String getContainingClassName() {
+ ResolvedClass containingClass = getContainingClass();
+ return containingClass != null ? containingClass.getName() : null;
+ }
+
+ @Override
+ public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) {
+ ResolvedClass containingClass = getContainingClass();
+ if (containingClass == null) {
+ return false;
+ }
+
+ String packageName = containingClass.getPackageName();
+
+ //noinspection SimplifiableIfStatement
+ if (pkg.equals(packageName)) {
+ return true;
+ }
+
+ return includeSubPackages && packageName.length() > pkg.length() &&
+ packageName.charAt(pkg.length()) == '.' &&
+ packageName.startsWith(pkg);
+ }
+ }
+
+ /**
+ * An annotation <b>reference</b>. Note that this refers to a usage of an annotation,
+ * not a declaraton of an annotation. You can call {@link #getClassType()} to
+ * find the declaration for the annotation.
+ */
+ public abstract static class ResolvedAnnotation extends ResolvedNode {
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this field name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @NonNull
+ public abstract TypeDescriptor getType();
+
+ /** Returns the {@link ResolvedClass} which defines the annotation */
+ @Nullable
+ public abstract ResolvedClass getClassType();
+
+ public static class Value {
+ @NonNull public final String name;
+ @Nullable public final Object value;
+
+ public Value(@NonNull String name, @Nullable Object value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ @NonNull
+ public abstract List<Value> getValues();
+
+ @Nullable
+ public Object getValue(@NonNull String name) {
+ for (Value value : getValues()) {
+ if (name.equals(value.name)) {
+ return value.value;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public Object getValue() {
+ return getValue(ATTR_VALUE);
+ }
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ return Collections.emptyList();
+ }
+ }
+
+ /** A package declaration */
+ public abstract static class ResolvedPackage extends ResolvedNode {
+ /** Returns the parent package of this package, if any. */
+ @Nullable
+ public abstract ResolvedPackage getParentPackage();
+
+ @NonNull
+ @Override
+ public Iterable<ResolvedAnnotation> getAnnotations() {
+ return Collections.emptyList();
+ }
+ }
+
+ /** A local variable or parameter declaration */
+ public abstract static class ResolvedVariable extends ResolvedNode {
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /** Returns whether this variable name matches the given name */
+ public abstract boolean matches(@NonNull String name);
+
+ @NonNull
+ public abstract TypeDescriptor getType();
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
new file mode 100644
index 0000000..2621fc9
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
@@ -0,0 +1,1478 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.client.api;
+
+import static com.android.SdkConstants.ANDROID_PKG;
+import static com.android.SdkConstants.R_CLASS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Detector.XmlScanner;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.AlternateConstructorInvocation;
+import lombok.ast.Annotation;
+import lombok.ast.AnnotationDeclaration;
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
+import lombok.ast.AnnotationValueArray;
+import lombok.ast.ArrayAccess;
+import lombok.ast.ArrayCreation;
+import lombok.ast.ArrayDimension;
+import lombok.ast.ArrayInitializer;
+import lombok.ast.Assert;
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.Block;
+import lombok.ast.BooleanLiteral;
+import lombok.ast.Break;
+import lombok.ast.Case;
+import lombok.ast.Cast;
+import lombok.ast.Catch;
+import lombok.ast.CharLiteral;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ClassLiteral;
+import lombok.ast.Comment;
+import lombok.ast.CompilationUnit;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Continue;
+import lombok.ast.Default;
+import lombok.ast.DoWhile;
+import lombok.ast.EmptyDeclaration;
+import lombok.ast.EmptyStatement;
+import lombok.ast.EnumConstant;
+import lombok.ast.EnumDeclaration;
+import lombok.ast.EnumTypeBody;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.FloatingPointLiteral;
+import lombok.ast.For;
+import lombok.ast.ForEach;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Identifier;
+import lombok.ast.If;
+import lombok.ast.ImportDeclaration;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.InstanceInitializer;
+import lombok.ast.InstanceOf;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.InterfaceDeclaration;
+import lombok.ast.KeywordModifier;
+import lombok.ast.LabelledStatement;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Modifiers;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.NullLiteral;
+import lombok.ast.PackageDeclaration;
+import lombok.ast.Return;
+import lombok.ast.Select;
+import lombok.ast.StaticInitializer;
+import lombok.ast.StringLiteral;
+import lombok.ast.Super;
+import lombok.ast.SuperConstructorInvocation;
+import lombok.ast.Switch;
+import lombok.ast.Synchronized;
+import lombok.ast.This;
+import lombok.ast.Throw;
+import lombok.ast.Try;
+import lombok.ast.TypeReference;
+import lombok.ast.TypeReferencePart;
+import lombok.ast.TypeVariable;
+import lombok.ast.UnaryExpression;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+import lombok.ast.While;
+
+/**
+ * Specialized visitor for running detectors on a Java AST.
+ * It operates in three phases:
+ * <ol>
+ * <li> First, it computes a set of maps where it generates a map from each
+ * significant AST attribute (such as method call names) to a list
+ * of detectors to consult whenever that attribute is encountered.
+ * Examples of "attributes" are method names, Android resource identifiers,
+ * and general AST node types such as "cast" nodes etc. These are
+ * defined on the {@link JavaScanner} interface.
+ * <li> Second, it iterates over the document a single time, delegating to
+ * the detectors found at each relevant AST attribute.
+ * <li> Finally, it calls the remaining visitors (those that need to process a
+ * whole document on their own).
+ * </ol>
+ * It also notifies all the detectors before and after the document is processed
+ * such that they can do pre- and post-processing.
+ */
+public class JavaVisitor {
+ /** Default size of lists holding detectors of the same type for a given node type */
+ private static final int SAME_TYPE_COUNT = 8;
+
+ private final Map<String, List<VisitingDetector>> mMethodDetectors =
+ Maps.newHashMapWithExpectedSize(40);
+ private final Map<String, List<VisitingDetector>> mConstructorDetectors =
+ Maps.newHashMapWithExpectedSize(12);
+ private Set<String> mConstructorSimpleNames;
+ private final List<VisitingDetector> mResourceFieldDetectors =
+ new ArrayList<VisitingDetector>();
+ private final List<VisitingDetector> mAllDetectors;
+ private final List<VisitingDetector> mFullTreeDetectors;
+ private final Map<Class<? extends Node>, List<VisitingDetector>> mNodeTypeDetectors =
+ new HashMap<Class<? extends Node>, List<VisitingDetector>>(16);
+ private final JavaParser mParser;
+ private final Map<String, List<VisitingDetector>> mSuperClassDetectors =
+ new HashMap<String, List<VisitingDetector>>();
+
+ /**
+ * Number of fatal exceptions (internal errors, usually from ECJ) we've
+ * encountered; we don't log each and every one to avoid massive log spam
+ * in code which triggers this condition
+ */
+ private static int sExceptionCount;
+ /** Max number of logs to include */
+ private static final int MAX_REPORTED_CRASHES = 20;
+
+ JavaVisitor(@NonNull JavaParser parser, @NonNull List<Detector> detectors) {
+ mParser = parser;
+ mAllDetectors = new ArrayList<VisitingDetector>(detectors.size());
+ mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size());
+
+ for (Detector detector : detectors) {
+ VisitingDetector v = new VisitingDetector(detector, (JavaScanner) detector);
+ mAllDetectors.add(v);
+
+ List<String> applicableSuperClasses = detector.applicableSuperClasses();
+ if (applicableSuperClasses != null) {
+ for (String fqn : applicableSuperClasses) {
+ List<VisitingDetector> list = mSuperClassDetectors.get(fqn);
+ if (list == null) {
+ list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
+ mSuperClassDetectors.put(fqn, list);
+ }
+ list.add(v);
+ }
+ continue;
+ }
+
+ List<Class<? extends Node>> nodeTypes = detector.getApplicableNodeTypes();
+ if (nodeTypes != null) {
+ for (Class<? extends Node> type : nodeTypes) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(type);
+ if (list == null) {
+ list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
+ mNodeTypeDetectors.put(type, list);
+ }
+ list.add(v);
+ }
+ }
+
+ List<String> names = detector.getApplicableMethodNames();
+ if (names != null) {
+ // not supported in Java visitors; adding a method invocation node is trivial
+ // for that case.
+ assert names != XmlScanner.ALL;
+
+ for (String name : names) {
+ List<VisitingDetector> list = mMethodDetectors.get(name);
+ if (list == null) {
+ list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
+ mMethodDetectors.put(name, list);
+ }
+ list.add(v);
+ }
+ }
+
+ List<String> types = detector.getApplicableConstructorTypes();
+ if (types != null) {
+ // not supported in Java visitors; adding a method invocation node is trivial
+ // for that case.
+ assert types != XmlScanner.ALL;
+ if (mConstructorSimpleNames == null) {
+ mConstructorSimpleNames = Sets.newHashSet();
+ }
+ for (String type : types) {
+ List<VisitingDetector> list = mConstructorDetectors.get(type);
+ if (list == null) {
+ list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
+ mConstructorDetectors.put(type, list);
+ mConstructorSimpleNames.add(type.substring(type.lastIndexOf('.')+1));
+ }
+ list.add(v);
+ }
+ }
+
+ if (detector.appliesToResourceRefs()) {
+ mResourceFieldDetectors.add(v);
+ } else if ((names == null || names.isEmpty())
+ && (nodeTypes == null || nodeTypes.isEmpty())
+ && (types == null || types.isEmpty())) {
+ mFullTreeDetectors.add(v);
+ }
+ }
+ }
+
+ void visitFile(@NonNull JavaContext context) {
+ Node compilationUnit = null;
+ try {
+ compilationUnit = mParser.parseJava(context);
+ if (compilationUnit == null) {
+ // No need to log this; the parser should be reporting
+ // a full warning (such as IssueRegistry#PARSER_ERROR)
+ // with details, location, etc.
+ return;
+ }
+ context.setCompilationUnit(compilationUnit);
+
+ for (VisitingDetector v : mAllDetectors) {
+ v.setContext(context);
+ v.getDetector().beforeCheckFile(context);
+ }
+
+ if (!mSuperClassDetectors.isEmpty()) {
+ SuperclassVisitor visitor = new SuperclassVisitor(context);
+ compilationUnit.accept(visitor);
+ }
+
+ for (VisitingDetector v : mFullTreeDetectors) {
+ AstVisitor visitor = v.getVisitor();
+ compilationUnit.accept(visitor);
+ }
+
+ if (!mMethodDetectors.isEmpty() || !mResourceFieldDetectors.isEmpty() ||
+ !mConstructorDetectors.isEmpty()) {
+ AstVisitor visitor = new DelegatingJavaVisitor(context);
+ compilationUnit.accept(visitor);
+ } else if (!mNodeTypeDetectors.isEmpty()) {
+ AstVisitor visitor = new DispatchVisitor();
+ compilationUnit.accept(visitor);
+ }
+
+ for (VisitingDetector v : mAllDetectors) {
+ v.getDetector().afterCheckFile(context);
+ }
+ } catch (RuntimeException e) {
+ if (sExceptionCount++ > MAX_REPORTED_CRASHES) {
+ // No need to keep spamming the user that a lot of the files
+ // are tripping up ECJ, they get the picture.
+ return;
+ }
+
+ if (e.getClass().getSimpleName().equals("IndexNotReadyException")) {
+ // Attempting to access PSI during startup before indices are ready; ignore these.
+ // See http://b.android.com/176644 for an example.
+ return;
+ } else if (e.getClass().getSimpleName().equals("ProcessCanceledException")) {
+ // Cancelling inspections in the IDE
+ context.getDriver().cancel();
+ return;
+ }
+
+ // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268
+ // Don't allow lint bugs to take down the whole build. TRY to log this as a
+ // lint error instead!
+ StringBuilder sb = new StringBuilder(100);
+ sb.append("Unexpected failure during lint analysis of ");
+ sb.append(context.file.getName());
+ sb.append(" (this is a bug in lint or one of the libraries it depends on)\n");
+
+ StackTraceElement[] stackTrace = e.getStackTrace();
+ int count = 0;
+ for (StackTraceElement frame : stackTrace) {
+ if (count > 0) {
+ sb.append("<-");
+ }
+
+ String className = frame.getClassName();
+ sb.append(className.substring(className.lastIndexOf('.') + 1));
+ sb.append('.').append(frame.getMethodName());
+ sb.append('(');
+ sb.append(frame.getFileName()).append(':').append(frame.getLineNumber());
+ sb.append(')');
+ count++;
+ // Only print the top 3-4 frames such that we can identify the bug
+ if (count == 4) {
+ break;
+ }
+ }
+ Throwable throwable = null; // NOT e: this makes for very noisy logs
+ //noinspection ConstantConditions
+ context.log(throwable, sb.toString());
+ } finally {
+ if (compilationUnit != null) {
+ mParser.dispose(context, compilationUnit);
+ }
+ }
+ }
+
+ /**
+ * For testing only: returns the number of exceptions thrown during Java AST analysis
+ *
+ * @return the number of internal errors found
+ */
+ @VisibleForTesting
+ public static int getCrashCount() {
+ return sExceptionCount;
+ }
+
+ /**
+ * For testing only: clears the crash counter
+ */
+ @VisibleForTesting
+ public static void clearCrashCount() {
+ sExceptionCount = 0;
+ }
+
+ public void prepare(@NonNull List<JavaContext> contexts) {
+ mParser.prepareJavaParse(contexts);
+ }
+
+ public void dispose() {
+ mParser.dispose();
+ }
+
+ @Nullable
+ private static Set<String> getInterfaceNames(
+ @Nullable Set<String> addTo,
+ @NonNull ResolvedClass cls) {
+ Iterable<ResolvedClass> interfaces = cls.getInterfaces();
+ for (ResolvedClass resolvedInterface : interfaces) {
+ String name = resolvedInterface.getName();
+ if (addTo == null) {
+ addTo = Sets.newHashSet();
+ } else if (addTo.contains(name)) {
+ // Superclasses can explicitly implement the same interface,
+ // so keep track of visited interfaces as we traverse up the
+ // super class chain to avoid checking the same interface
+ // more than once.
+ continue;
+ }
+ addTo.add(name);
+ getInterfaceNames(addTo, resolvedInterface);
+ }
+
+ return addTo;
+ }
+
+ private static class VisitingDetector {
+ private AstVisitor mVisitor; // construct lazily, and clear out on context switch!
+ private JavaContext mContext;
+ public final Detector mDetector;
+ public final JavaScanner mJavaScanner;
+
+ public VisitingDetector(@NonNull Detector detector, @NonNull JavaScanner javaScanner) {
+ mDetector = detector;
+ mJavaScanner = javaScanner;
+ }
+
+ @NonNull
+ public Detector getDetector() {
+ return mDetector;
+ }
+
+ @NonNull
+ public JavaScanner getJavaScanner() {
+ return mJavaScanner;
+ }
+
+ public void setContext(@NonNull JavaContext context) {
+ mContext = context;
+
+ // The visitors are one-per-context, so clear them out here and construct
+ // lazily only if needed
+ mVisitor = null;
+ }
+
+ @NonNull
+ AstVisitor getVisitor() {
+ if (mVisitor == null) {
+ mVisitor = mDetector.createJavaVisitor(mContext);
+ if (mVisitor == null) {
+ mVisitor = new ForwardingAstVisitor() {
+ };
+ }
+ }
+ return mVisitor;
+ }
+ }
+
+ private class SuperclassVisitor extends ForwardingAstVisitor {
+ private JavaContext mContext;
+
+ public SuperclassVisitor(@NonNull JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (!(resolved instanceof ResolvedClass)) {
+ return true;
+ }
+
+ ResolvedClass resolvedClass = (ResolvedClass) resolved;
+ ResolvedClass cls = resolvedClass;
+ int depth = 0;
+ while (cls != null) {
+ List<VisitingDetector> list = mSuperClassDetectors.get(cls.getName());
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getJavaScanner().checkClass(mContext, node, node, resolvedClass);
+ }
+ }
+
+ // Check interfaces too
+ Set<String> interfaceNames = getInterfaceNames(null, cls);
+ if (interfaceNames != null) {
+ for (String name : interfaceNames) {
+ list = mSuperClassDetectors.get(name);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getJavaScanner().checkClass(mContext, node, node,
+ resolvedClass);
+ }
+ }
+ }
+ }
+
+ cls = cls.getSuperClass();
+ depth++;
+ if (depth == 500) {
+ // Shouldn't happen in practice; this prevents the IDE from
+ // hanging if the user has accidentally typed in an incorrect
+ // super class which creates a cycle.
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ NormalTypeBody anonymous = node.astAnonymousClassBody();
+ if (anonymous != null) {
+ ResolvedNode resolved = mContext.resolve(node.astTypeReference());
+ if (!(resolved instanceof ResolvedClass)) {
+ return true;
+ }
+
+ ResolvedClass resolvedClass = (ResolvedClass) resolved;
+ ResolvedClass cls = resolvedClass;
+ while (cls != null) {
+ List<VisitingDetector> list = mSuperClassDetectors.get(cls.getName());
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getJavaScanner().checkClass(mContext, null, anonymous,
+ resolvedClass);
+ }
+ }
+
+ // Check interfaces too
+ Set<String> interfaceNames = getInterfaceNames(null, cls);
+ if (interfaceNames != null) {
+ for (String name : interfaceNames) {
+ list = mSuperClassDetectors.get(name);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getJavaScanner().checkClass(mContext, null, anonymous,
+ resolvedClass);
+ }
+ }
+ }
+ }
+
+ cls = cls.getSuperClass();
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean visitImportDeclaration(ImportDeclaration node) {
+ return true;
+ }
+ }
+
+ /**
+ * Generic dispatcher which visits all nodes (once) and dispatches to
+ * specific visitors for each node. Each visitor typically only wants to
+ * look at a small part of a tree, such as a method call or a class
+ * declaration, so this means we avoid visiting all "uninteresting" nodes in
+ * the tree repeatedly.
+ */
+ private class DispatchVisitor extends ForwardingAstVisitor {
+ @Override
+ public void endVisit(Node node) {
+ for (VisitingDetector v : mAllDetectors) {
+ v.getVisitor().endVisit(node);
+ }
+ }
+
+ @Override
+ public boolean visitAlternateConstructorInvocation(AlternateConstructorInvocation node) {
+ List<VisitingDetector> list =
+ mNodeTypeDetectors.get(AlternateConstructorInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAlternateConstructorInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotation(Annotation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Annotation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationDeclaration(AnnotationDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationElement(AnnotationElement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationElement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationElement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationMethodDeclaration(AnnotationMethodDeclaration node) {
+ List<VisitingDetector> list =
+ mNodeTypeDetectors.get(AnnotationMethodDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationMethodDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationValueArray(AnnotationValueArray node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationValueArray.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationValueArray(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayAccess(ArrayAccess node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayAccess.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayAccess(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayCreation(ArrayCreation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayCreation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayCreation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayDimension(ArrayDimension node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayDimension.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayDimension(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayInitializer(ArrayInitializer node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayInitializer.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayInitializer(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAssert(Assert node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Assert.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAssert(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBinaryExpression(BinaryExpression node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(BinaryExpression.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBinaryExpression(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBlock(Block node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Block.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBlock(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBooleanLiteral(BooleanLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(BooleanLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBooleanLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBreak(Break node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Break.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBreak(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCase(Case node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Case.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCase(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCast(Cast node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Cast.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCast(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCatch(Catch node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Catch.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCatch(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCharLiteral(CharLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(CharLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCharLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ClassDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitClassDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitClassLiteral(ClassLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ClassLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitClassLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitComment(Comment node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Comment.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitComment(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCompilationUnit(CompilationUnit node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(CompilationUnit.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCompilationUnit(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitConstructorDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitConstructorInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitContinue(Continue node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Continue.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitContinue(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitDefault(Default node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Default.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitDefault(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitDoWhile(DoWhile node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(DoWhile.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitDoWhile(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEmptyDeclaration(EmptyDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEmptyDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEmptyStatement(EmptyStatement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyStatement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEmptyStatement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEnumConstant(EnumConstant node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EnumConstant.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEnumConstant(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEnumDeclaration(EnumDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EnumDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEnumDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEnumTypeBody(EnumTypeBody node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EnumTypeBody.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEnumTypeBody(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitExpressionStatement(ExpressionStatement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ExpressionStatement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitExpressionStatement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitFloatingPointLiteral(FloatingPointLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(FloatingPointLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitFloatingPointLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitFor(For node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(For.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitFor(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitForEach(ForEach node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ForEach.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitForEach(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitIdentifier(Identifier node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Identifier.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitIdentifier(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitIf(If node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(If.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitIf(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitImportDeclaration(ImportDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ImportDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitImportDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInlineIfExpression(InlineIfExpression node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InlineIfExpression.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInlineIfExpression(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInstanceInitializer(InstanceInitializer node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceInitializer.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInstanceInitializer(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInstanceOf(InstanceOf node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceOf.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInstanceOf(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitIntegralLiteral(IntegralLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(IntegralLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitIntegralLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInterfaceDeclaration(InterfaceDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InterfaceDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInterfaceDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitKeywordModifier(KeywordModifier node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(KeywordModifier.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitKeywordModifier(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitLabelledStatement(LabelledStatement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(LabelledStatement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitLabelledStatement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(MethodDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitMethodDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(MethodInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitMethodInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitModifiers(Modifiers node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Modifiers.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitModifiers(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitNormalTypeBody(NormalTypeBody node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(NormalTypeBody.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitNormalTypeBody(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitNullLiteral(NullLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(NullLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitNullLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitPackageDeclaration(PackageDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(PackageDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitPackageDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitParseArtefact(Node node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Node.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitParseArtefact(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitReturn(Return node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Return.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitReturn(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSelect(Select node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Select.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSelect(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitStaticInitializer(StaticInitializer node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(StaticInitializer.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitStaticInitializer(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitStringLiteral(StringLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(StringLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitStringLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSuper(Super node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Super.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSuper(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSuperConstructorInvocation(SuperConstructorInvocation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(SuperConstructorInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSuperConstructorInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSwitch(Switch node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Switch.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSwitch(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSynchronized(Synchronized node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Synchronized.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSynchronized(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitThis(This node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(This.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitThis(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitThrow(Throw node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Throw.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitThrow(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTry(Try node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Try.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTry(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTypeReference(TypeReference node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReference.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTypeReference(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTypeReferencePart(TypeReferencePart node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReferencePart.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTypeReferencePart(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTypeVariable(TypeVariable node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(TypeVariable.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTypeVariable(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitUnaryExpression(UnaryExpression node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(UnaryExpression.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitUnaryExpression(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableDeclaration(VariableDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableDefinition(VariableDefinition node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinition.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableDefinition(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinitionEntry.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableDefinitionEntry(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableReference(VariableReference node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableReference.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableReference(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitWhile(While node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(While.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitWhile(node);
+ }
+ }
+ return false;
+ }
+ }
+
+ /** Performs common AST searches for method calls and R-type-field references.
+ * Note that this is a specialized form of the {@link DispatchVisitor}. */
+ private class DelegatingJavaVisitor extends DispatchVisitor {
+ private final JavaContext mContext;
+ private final boolean mVisitResources;
+ private final boolean mVisitMethods;
+ private final boolean mVisitConstructors;
+
+ public DelegatingJavaVisitor(JavaContext context) {
+ mContext = context;
+
+ mVisitMethods = !mMethodDetectors.isEmpty();
+ mVisitConstructors = !mConstructorDetectors.isEmpty();
+ mVisitResources = !mResourceFieldDetectors.isEmpty();
+ }
+
+ @Override
+ public boolean visitSelect(Select node) {
+ if (mVisitResources) {
+ // R.type.name
+ if (node.astOperand() instanceof Select) {
+ Select select = (Select) node.astOperand();
+ if (select.astOperand() instanceof VariableReference) {
+ VariableReference reference = (VariableReference) select.astOperand();
+ if (reference.astIdentifier().astValue().equals(R_CLASS)) {
+ String type = select.astIdentifier().astValue();
+ String name = node.astIdentifier().astValue();
+
+ // R -could- be referenced locally and really have been
+ // imported as "import android.R;" in the import statements,
+ // but this is not recommended (and in fact there's a specific
+ // lint rule warning against it)
+ boolean isFramework = false;
+
+ for (VisitingDetector v : mResourceFieldDetectors) {
+ JavaScanner detector = v.getJavaScanner();
+ //noinspection ConstantConditions
+ detector.visitResourceReference(mContext, v.getVisitor(),
+ node, type, name, isFramework);
+ }
+
+ return super.visitSelect(node);
+ }
+ }
+ }
+
+ // Arbitrary packages -- android.R.type.name, foo.bar.R.type.name
+ if (node.astIdentifier().astValue().equals(R_CLASS)) {
+ Node parent = node.getParent();
+ if (parent instanceof Select) {
+ Node grandParent = parent.getParent();
+ if (grandParent instanceof Select) {
+ Select select = (Select) grandParent;
+ String name = select.astIdentifier().astValue();
+ Expression typeOperand = select.astOperand();
+ if (typeOperand instanceof Select) {
+ Select typeSelect = (Select) typeOperand;
+ String type = typeSelect.astIdentifier().astValue();
+ boolean isFramework = node.astOperand().toString().equals(
+ ANDROID_PKG);
+ for (VisitingDetector v : mResourceFieldDetectors) {
+ JavaScanner detector = v.getJavaScanner();
+ detector.visitResourceReference(mContext, v.getVisitor(),
+ node, type, name, isFramework);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return super.visitSelect(node);
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (mVisitMethods) {
+ String methodName = node.astName().astValue();
+ List<VisitingDetector> list = mMethodDetectors.get(methodName);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getJavaScanner().visitMethod(mContext, v.getVisitor(), node);
+ }
+ }
+ }
+
+ return super.visitMethodInvocation(node);
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ if (mVisitConstructors) {
+ TypeReference typeReference = node.astTypeReference();
+ if (typeReference != null) {
+ TypeReferencePart last = typeReference.astParts().last();
+ if (last != null) {
+ String name = last.astIdentifier().astValue();
+ if (mConstructorSimpleNames.contains(name)) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ String type = method.getContainingClass().getName();
+ List<VisitingDetector> list = mConstructorDetectors.get(type);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getJavaScanner().visitConstructor(mContext,
+ v.getVisitor(), node, method);
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ return super.visitConstructorInvocation(node);
+ }
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
new file mode 100644
index 0000000..981d89e
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
@@ -0,0 +1,1243 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.client.api;
+
+import static com.android.SdkConstants.CLASS_FOLDER;
+import static com.android.SdkConstants.DOT_AAR;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FD_ASSETS;
+import static com.android.SdkConstants.GEN_FOLDER;
+import static com.android.SdkConstants.LIBS_FOLDER;
+import static com.android.SdkConstants.RES_FOLDER;
+import static com.android.SdkConstants.SRC_FOLDER;
+import static com.android.tools.lint.detector.api.LintUtils.endsWith;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Variant;
+import com.android.ide.common.repository.ResourceVisibilityLookup;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.prefs.AndroidLocation;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.ProgressIndicatorAdapter;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.XmlUtils;
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Information about the tool embedding the lint analyzer. IDEs and other tools
+ * implementing lint support will extend this to integrate logging, displaying errors,
+ * etc.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public abstract class LintClient {
+ private static final String PROP_BIN_DIR = "com.android.tools.lint.bindir"; //$NON-NLS-1$
+
+ protected LintClient(@NonNull String clientName) {
+ sClientName = clientName;
+ }
+
+ protected LintClient() {
+ sClientName = "unknown";
+ }
+
+ /**
+ * Returns a configuration for use by the given project. The configuration
+ * provides information about which issues are enabled, any customizations
+ * to the severity of an issue, etc.
+ * <p>
+ * By default this method returns a {@link DefaultConfiguration}.
+ *
+ * @param project the project to obtain a configuration for
+ * @param driver the current driver, if any
+ * @return a configuration, never null.
+ */
+ @NonNull
+ public Configuration getConfiguration(@NonNull Project project, @Nullable LintDriver driver) {
+ return DefaultConfiguration.create(this, project, null);
+ }
+
+ /**
+ * Report the given issue. This method will only be called if the configuration
+ * provided by {@link #getConfiguration(Project,LintDriver)} has reported the corresponding
+ * issue as enabled and has not filtered out the issue with its
+ * {@link Configuration#ignore(Context,Issue,Location,String)} method.
+ * <p>
+ * @param context the context used by the detector when the issue was found
+ * @param issue the issue that was found
+ * @param severity the severity of the issue
+ * @param location the location of the issue
+ * @param message the associated user message
+ * @param format the format of the description and location descriptions
+ */
+ public abstract void report(
+ @NonNull Context context,
+ @NonNull Issue issue,
+ @NonNull Severity severity,
+ @Nullable Location location,
+ @NonNull String message,
+ @NonNull TextFormat format);
+
+ /**
+ * Send an exception or error message (with warning severity) to the log
+ *
+ * @param exception the exception, possibly null
+ * @param format the error message using {@link String#format} syntax, possibly null
+ * (though in that case the exception should not be null)
+ * @param args any arguments for the format string
+ */
+ public void log(
+ @Nullable Throwable exception,
+ @Nullable String format,
+ @Nullable Object... args) {
+ log(Severity.WARNING, exception, format, args);
+ }
+
+ /**
+ * Send an exception or error message to the log
+ *
+ * @param severity the severity of the warning
+ * @param exception the exception, possibly null
+ * @param format the error message using {@link String#format} syntax, possibly null
+ * (though in that case the exception should not be null)
+ * @param args any arguments for the format string
+ */
+ public abstract void log(
+ @NonNull Severity severity,
+ @Nullable Throwable exception,
+ @Nullable String format,
+ @Nullable Object... args);
+
+ /**
+ * Returns a {@link XmlParser} to use to parse XML
+ *
+ * @return a new {@link XmlParser}, or null if this client does not support
+ * XML analysis
+ */
+ @Nullable
+ public abstract XmlParser getXmlParser();
+
+ /**
+ * Returns a {@link JavaParser} to use to parse Java
+ *
+ * @param project the project to parse, if known (this can be used to look up
+ * the class path for type attribution etc, and it can also be used
+ * to more efficiently process a set of files, for example to
+ * perform type attribution for multiple units in a single pass)
+ * @return a new {@link JavaParser}, or null if this client does not
+ * support Java analysis
+ */
+ @Nullable
+ public abstract JavaParser getJavaParser(@Nullable Project project);
+
+ /**
+ * Returns an optimal detector, if applicable. By default, just returns the
+ * original detector, but tools can replace detectors using this hook with a version
+ * that takes advantage of native capabilities of the tool.
+ *
+ * @param detectorClass the class of the detector to be replaced
+ * @return the new detector class, or just the original detector (not null)
+ */
+ @NonNull
+ public Class<? extends Detector> replaceDetector(
+ @NonNull Class<? extends Detector> detectorClass) {
+ return detectorClass;
+ }
+
+ /**
+ * Reads the given text file and returns the content as a string
+ *
+ * @param file the file to read
+ * @return the string to return, never null (will be empty if there is an
+ * I/O error)
+ */
+ @NonNull
+ public abstract String readFile(@NonNull File file);
+
+ /**
+ * Reads the given binary file and returns the content as a byte array.
+ * By default this method will read the bytes from the file directly,
+ * but this can be customized by a client if for example I/O could be
+ * held in memory and not flushed to disk yet.
+ *
+ * @param file the file to read
+ * @return the bytes in the file, never null
+ * @throws IOException if the file does not exist, or if the file cannot be
+ * read for some reason
+ */
+ @NonNull
+ public byte[] readBytes(@NonNull File file) throws IOException {
+ return Files.toByteArray(file);
+ }
+
+ /**
+ * Returns the list of source folders for Java source files
+ *
+ * @param project the project to look up Java source file locations for
+ * @return a list of source folders to search for .java files
+ */
+ @NonNull
+ public List<File> getJavaSourceFolders(@NonNull Project project) {
+ return getClassPath(project).getSourceFolders();
+ }
+
+ /**
+ * Returns the list of output folders for class files
+ *
+ * @param project the project to look up class file locations for
+ * @return a list of output folders to search for .class files
+ */
+ @NonNull
+ public List<File> getJavaClassFolders(@NonNull Project project) {
+ return getClassPath(project).getClassFolders();
+
+ }
+
+ /**
+ * Returns the list of Java libraries
+ *
+ * @param project the project to look up jar dependencies for
+ * @param includeProvided If true, included provided libraries too (libraries that are not
+ * packaged with the app, but are provided for compilation purposes and
+ * are assumed to be present in the running environment)
+ * @return a list of jar dependencies containing .class files
+ */
+ @NonNull
+ public List<File> getJavaLibraries(@NonNull Project project, boolean includeProvided) {
+ return getClassPath(project).getLibraries(includeProvided);
+ }
+
+ /**
+ * Returns the list of source folders for test source files
+ *
+ * @param project the project to look up test source file locations for
+ * @return a list of source folders to search for .java files
+ */
+ @NonNull
+ public List<File> getTestSourceFolders(@NonNull Project project) {
+ return getClassPath(project).getTestSourceFolders();
+ }
+
+ /**
+ * Returns the resource folders.
+ *
+ * @param project the project to look up the resource folder for
+ * @return a list of files pointing to the resource folders, possibly empty
+ */
+ @NonNull
+ public List<File> getResourceFolders(@NonNull Project project) {
+ File res = new File(project.getDir(), RES_FOLDER);
+ if (res.exists()) {
+ return Collections.singletonList(res);
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns the asset folders.
+ *
+ * @param project the project to look up the asset folder for
+ * @return a list of files pointing to the asset folders, possibly empty
+ */
+ @NonNull
+ public List<File> getAssetFolders(@NonNull Project project) {
+ File assets = new File(project.getDir(), FD_ASSETS);
+ if (assets.exists()) {
+ return Collections.singletonList(assets);
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns the {@link SdkInfo} to use for the given project.
+ *
+ * @param project the project to look up an {@link SdkInfo} for
+ * @return an {@link SdkInfo} for the project
+ */
+ @NonNull
+ public SdkInfo getSdkInfo(@NonNull Project project) {
+ // By default no per-platform SDK info
+ return new DefaultSdkInfo();
+ }
+
+ /**
+ * Returns a suitable location for storing cache files. Note that the
+ * directory may not exist.
+ *
+ * @param create if true, attempt to create the cache dir if it does not
+ * exist
+ * @return a suitable location for storing cache files, which may be null if
+ * the create flag was false, or if for some reason the directory
+ * could not be created
+ */
+ @Nullable
+ public File getCacheDir(boolean create) {
+ String home = System.getProperty("user.home");
+ String relative = ".android" + File.separator + "cache"; //$NON-NLS-1$ //$NON-NLS-2$
+ File dir = new File(home, relative);
+ if (create && !dir.exists()) {
+ if (!dir.mkdirs()) {
+ return null;
+ }
+ }
+ return dir;
+ }
+
+ /**
+ * Returns the File corresponding to the system property or the environment variable
+ * for {@link #PROP_BIN_DIR}.
+ * This property is typically set by the SDK/tools/lint[.bat] wrapper.
+ * It denotes the path of the wrapper on disk.
+ *
+ * @return A new File corresponding to {@link LintClient#PROP_BIN_DIR} or null.
+ */
+ @Nullable
+ private static File getLintBinDir() {
+ // First check the Java properties (e.g. set using "java -jar ... -Dname=value")
+ String path = System.getProperty(PROP_BIN_DIR);
+ if (path == null || path.isEmpty()) {
+ // If not found, check environment variables.
+ path = System.getenv(PROP_BIN_DIR);
+ }
+ if (path != null && !path.isEmpty()) {
+ File file = new File(path);
+ if (file.exists()) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the File pointing to the user's SDK install area. This is generally
+ * the root directory containing the lint tool (but also platforms/ etc).
+ *
+ * @return a file pointing to the user's install area
+ */
+ @Nullable
+ public File getSdkHome() {
+ File binDir = getLintBinDir();
+ if (binDir != null) {
+ assert binDir.getName().equals("tools");
+
+ File root = binDir.getParentFile();
+ if (root != null && root.isDirectory()) {
+ return root;
+ }
+ }
+
+ String home = System.getenv("ANDROID_HOME"); //$NON-NLS-1$
+ if (home != null) {
+ return new File(home);
+ }
+
+ return null;
+ }
+
+ /**
+ * Locates an SDK resource (relative to the SDK root directory).
+ * <p>
+ * TODO: Consider switching to a {@link URL} return type instead.
+ *
+ * @param relativePath A relative path (using {@link File#separator} to
+ * separate path components) to the given resource
+ * @return a {@link File} pointing to the resource, or null if it does not
+ * exist
+ */
+ @Nullable
+ public File findResource(@NonNull String relativePath) {
+ File top = getSdkHome();
+ if (top == null) {
+ throw new IllegalArgumentException("Lint must be invoked with the System property "
+ + PROP_BIN_DIR + " pointing to the ANDROID_SDK tools directory");
+ }
+
+ File file = new File(top, relativePath);
+ if (file.exists()) {
+ return file;
+ } else {
+ return null;
+ }
+ }
+
+ private Map<Project, ClassPathInfo> mProjectInfo;
+
+ /**
+ * Returns true if this project is a Gradle-based Android project
+ *
+ * @param project the project to check
+ * @return true if this is a Gradle-based project
+ */
+ public boolean isGradleProject(Project project) {
+ // This is not an accurate test; specific LintClient implementations (e.g.
+ // IDEs or a gradle-integration of lint) have more context and can perform a more accurate
+ // check
+ if (new File(project.getDir(), SdkConstants.FN_BUILD_GRADLE).exists()) {
+ return true;
+ }
+
+ File parent = project.getDir().getParentFile();
+ if (parent != null && parent.getName().equals(SdkConstants.FD_SOURCES)) {
+ File root = parent.getParentFile();
+ if (root != null && new File(root, SdkConstants.FN_BUILD_GRADLE).exists()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Information about class paths (sources, class files and libraries)
+ * usually associated with a project.
+ */
+ protected static class ClassPathInfo {
+ private final List<File> mClassFolders;
+ private final List<File> mSourceFolders;
+ private final List<File> mLibraries;
+ private final List<File> mNonProvidedLibraries;
+ private final List<File> mTestFolders;
+
+ public ClassPathInfo(
+ @NonNull List<File> sourceFolders,
+ @NonNull List<File> classFolders,
+ @NonNull List<File> libraries,
+ @NonNull List<File> nonProvidedLibraries,
+ @NonNull List<File> testFolders) {
+ mSourceFolders = sourceFolders;
+ mClassFolders = classFolders;
+ mLibraries = libraries;
+ mNonProvidedLibraries = nonProvidedLibraries;
+ mTestFolders = testFolders;
+ }
+
+ @NonNull
+ public List<File> getSourceFolders() {
+ return mSourceFolders;
+ }
+
+ @NonNull
+ public List<File> getClassFolders() {
+ return mClassFolders;
+ }
+
+ @NonNull
+ public List<File> getLibraries(boolean includeProvided) {
+ return includeProvided ? mLibraries : mNonProvidedLibraries;
+ }
+
+ public List<File> getTestSourceFolders() {
+ return mTestFolders;
+ }
+ }
+
+ /**
+ * Considers the given project as an Eclipse project and returns class path
+ * information for the project - the source folder(s), the output folder and
+ * any libraries.
+ * <p>
+ * Callers will not cache calls to this method, so if it's expensive to compute
+ * the classpath info, this method should perform its own caching.
+ *
+ * @param project the project to look up class path info for
+ * @return a class path info object, never null
+ */
+ @NonNull
+ protected ClassPathInfo getClassPath(@NonNull Project project) {
+ ClassPathInfo info;
+ if (mProjectInfo == null) {
+ mProjectInfo = Maps.newHashMap();
+ info = null;
+ } else {
+ info = mProjectInfo.get(project);
+ }
+
+ if (info == null) {
+ List<File> sources = new ArrayList<File>(2);
+ List<File> classes = new ArrayList<File>(1);
+ List<File> libraries = new ArrayList<File>();
+ // No test folders in Eclipse:
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=224708
+ List<File> tests = Collections.emptyList();
+
+ File projectDir = project.getDir();
+ File classpathFile = new File(projectDir, ".classpath"); //$NON-NLS-1$
+ if (classpathFile.exists()) {
+ String classpathXml = readFile(classpathFile);
+ try {
+ Document document = XmlUtils.parseDocument(classpathXml, false);
+ NodeList tags = document.getElementsByTagName("classpathentry"); //$NON-NLS-1$
+ for (int i = 0, n = tags.getLength(); i < n; i++) {
+ Element element = (Element) tags.item(i);
+ String kind = element.getAttribute("kind"); //$NON-NLS-1$
+ List<File> addTo = null;
+ if (kind.equals("src")) { //$NON-NLS-1$
+ addTo = sources;
+ } else if (kind.equals("output")) { //$NON-NLS-1$
+ addTo = classes;
+ } else if (kind.equals("lib")) { //$NON-NLS-1$
+ addTo = libraries;
+ }
+ if (addTo != null) {
+ String path = element.getAttribute("path"); //$NON-NLS-1$
+ File folder = new File(projectDir, path);
+ if (folder.exists()) {
+ addTo.add(folder);
+ }
+ }
+ }
+ } catch (Exception e) {
+ log(null, null);
+ }
+ }
+
+ // Add in libraries that aren't specified in the .classpath file
+ File libs = new File(project.getDir(), LIBS_FOLDER);
+ if (libs.isDirectory()) {
+ File[] jars = libs.listFiles();
+ if (jars != null) {
+ for (File jar : jars) {
+ if (LintUtils.endsWith(jar.getPath(), DOT_JAR)
+ && !libraries.contains(jar)) {
+ libraries.add(jar);
+ }
+ }
+ }
+ }
+
+ if (classes.isEmpty()) {
+ File folder = new File(projectDir, CLASS_FOLDER);
+ if (folder.exists()) {
+ classes.add(folder);
+ } else {
+ // Maven checks
+ folder = new File(projectDir,
+ "target" + File.separator + "classes"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (folder.exists()) {
+ classes.add(folder);
+
+ // If it's maven, also correct the source path, "src" works but
+ // it's in a more specific subfolder
+ if (sources.isEmpty()) {
+ File src = new File(projectDir,
+ "src" + File.separator //$NON-NLS-1$
+ + "main" + File.separator //$NON-NLS-1$
+ + "java"); //$NON-NLS-1$
+ if (src.exists()) {
+ sources.add(src);
+ } else {
+ src = new File(projectDir, SRC_FOLDER);
+ if (src.exists()) {
+ sources.add(src);
+ }
+ }
+
+ File gen = new File(projectDir,
+ "target" + File.separator //$NON-NLS-1$
+ + "generated-sources" + File.separator //$NON-NLS-1$
+ + "r"); //$NON-NLS-1$
+ if (gen.exists()) {
+ sources.add(gen);
+ }
+ }
+ }
+ }
+ }
+
+ // Fallback, in case there is no Eclipse project metadata here
+ if (sources.isEmpty()) {
+ File src = new File(projectDir, SRC_FOLDER);
+ if (src.exists()) {
+ sources.add(src);
+ }
+ File gen = new File(projectDir, GEN_FOLDER);
+ if (gen.exists()) {
+ sources.add(gen);
+ }
+ }
+
+ info = new ClassPathInfo(sources, classes, libraries, libraries, tests);
+ mProjectInfo.put(project, info);
+ }
+
+ return info;
+ }
+
+ /**
+ * A map from directory to existing projects, or null. Used to ensure that
+ * projects are unique for a directory (in case we process a library project
+ * before its including project for example)
+ */
+ private Map<File, Project> mDirToProject;
+
+ /**
+ * Returns a project for the given directory. This should return the same
+ * project for the same directory if called repeatedly.
+ *
+ * @param dir the directory containing the project
+ * @param referenceDir See {@link Project#getReferenceDir()}.
+ * @return a project, never null
+ */
+ @NonNull
+ public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
+ if (mDirToProject == null) {
+ mDirToProject = new HashMap<File, Project>();
+ }
+
+ File canonicalDir = dir;
+ try {
+ // Attempt to use the canonical handle for the file, in case there
+ // are symlinks etc present (since when handling library projects,
+ // we also call getCanonicalFile to compute the result of appending
+ // relative paths, which can then resolve symlinks and end up with
+ // a different prefix)
+ canonicalDir = dir.getCanonicalFile();
+ } catch (IOException ioe) {
+ // pass
+ }
+
+ Project project = mDirToProject.get(canonicalDir);
+ if (project != null) {
+ return project;
+ }
+
+ project = createProject(dir, referenceDir);
+ mDirToProject.put(canonicalDir, project);
+ return project;
+ }
+
+ /**
+ * Returns the list of known projects (projects registered via
+ * {@link #getProject(File, File)}
+ *
+ * @return a collection of projects in any order
+ */
+ public Collection<Project> getKnownProjects() {
+ return mDirToProject != null ? mDirToProject.values() : Collections.<Project>emptyList();
+ }
+
+ /**
+ * Registers the given project for the given directory. This can
+ * be used when projects are initialized outside of the client itself.
+ *
+ * @param dir the directory of the project, which must be unique
+ * @param project the project
+ */
+ public void registerProject(@NonNull File dir, @NonNull Project project) {
+ File canonicalDir = dir;
+ try {
+ // Attempt to use the canonical handle for the file, in case there
+ // are symlinks etc present (since when handling library projects,
+ // we also call getCanonicalFile to compute the result of appending
+ // relative paths, which can then resolve symlinks and end up with
+ // a different prefix)
+ canonicalDir = dir.getCanonicalFile();
+ } catch (IOException ioe) {
+ // pass
+ }
+
+
+ if (mDirToProject == null) {
+ mDirToProject = new HashMap<File, Project>();
+ } else {
+ assert !mDirToProject.containsKey(dir) : dir;
+ }
+ mDirToProject.put(canonicalDir, project);
+ }
+
+ private Set<File> mProjectDirs = Sets.newHashSet();
+
+ /**
+ * Create a project for the given directory
+ * @param dir the root directory of the project
+ * @param referenceDir See {@link Project#getReferenceDir()}.
+ * @return a new project
+ */
+ @NonNull
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ if (mProjectDirs.contains(dir)) {
+ throw new CircularDependencyException(
+ "Circular library dependencies; check your project.properties files carefully");
+ }
+ mProjectDirs.add(dir);
+ return Project.create(this, dir, referenceDir);
+ }
+
+ /**
+ * Returns the name of the given project
+ *
+ * @param project the project to look up
+ * @return the name of the project
+ */
+ @NonNull
+ public String getProjectName(@NonNull Project project) {
+ return project.getDir().getName();
+ }
+
+ protected IAndroidTarget[] mTargets;
+
+ /**
+ * Returns all the {@link IAndroidTarget} versions installed in the user's SDK install
+ * area.
+ *
+ * @return all the installed targets
+ */
+ @NonNull
+ public IAndroidTarget[] getTargets() {
+ if (mTargets == null) {
+ AndroidSdkHandler sdkHandler = getSdk();
+ if (sdkHandler != null) {
+ ProgressIndicator logger = getRepositoryLogger();
+ Collection<IAndroidTarget> targets = sdkHandler.getAndroidTargetManager(logger)
+ .getTargets(logger);
+ mTargets = targets.toArray(new IAndroidTarget[targets.size()]);
+ } else {
+ mTargets = new IAndroidTarget[0];
+ }
+ }
+
+ return mTargets;
+ }
+
+ protected AndroidSdkHandler mSdk;
+
+ /**
+ * Returns the SDK installation (used to look up platforms etc)
+ *
+ * @return the SDK if known
+ */
+ @Nullable
+ public AndroidSdkHandler getSdk() {
+ if (mSdk == null) {
+ File sdkHome = getSdkHome();
+ if (sdkHome != null) {
+ mSdk = AndroidSdkHandler.getInstance(sdkHome);
+ }
+ }
+
+ return mSdk;
+ }
+
+ /**
+ * Returns the compile target to use for the given project
+ *
+ * @param project the project in question
+ *
+ * @return the compile target to use to build the given project
+ */
+ @Nullable
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ int buildSdk = project.getBuildSdk();
+ IAndroidTarget[] targets = getTargets();
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform() && target.getVersion().getApiLevel() == buildSdk) {
+ return target;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the highest known API level.
+ *
+ * @return the highest known API level
+ */
+ public int getHighestKnownApiLevel() {
+ int max = SdkVersionInfo.HIGHEST_KNOWN_STABLE_API;
+
+ for (IAndroidTarget target : getTargets()) {
+ if (target.isPlatform()) {
+ int api = target.getVersion().getApiLevel();
+ if (api > max && !target.getVersion().isPreview()) {
+ max = api;
+ }
+ }
+ }
+
+ return max;
+ }
+
+ /**
+ * Returns the specific version of the build tools being used for the given project, if known
+ *
+ * @param project the project in question
+ *
+ * @return the build tools version in use by the project, or null if not known
+ */
+ @Nullable
+ public BuildToolInfo getBuildTools(@NonNull Project project) {
+ AndroidSdkHandler sdk = getSdk();
+ // Build systems like Eclipse and ant just use the latest available
+ // build tools, regardless of project metadata. In Gradle, this
+ // method is overridden to use the actual build tools specified in the
+ // project.
+ return sdk != null ? sdk.getLatestBuildTool(getRepositoryLogger()) : null;
+ }
+
+ /**
+ * Returns the super class for the given class name, which should be in VM
+ * format (e.g. java/lang/Integer, not java.lang.Integer, and using $ rather
+ * than . for inner classes). If the super class is not known, returns null.
+ * <p>
+ * This is typically not necessary, since lint analyzes all the available
+ * classes. However, if this lint client is invoking lint in an incremental
+ * context (for example, an IDE offering incremental analysis of a single
+ * source file), then lint may not see all the classes, and the client can
+ * provide its own super class lookup.
+ *
+ * @param project the project containing the class
+ * @param name the fully qualified class name
+ * @return the corresponding super class name (in VM format), or null if not
+ * known
+ */
+ @Nullable
+ public String getSuperClass(@NonNull Project project, @NonNull String name) {
+ assert name.indexOf('.') == -1 : "Use VM signatures, e.g. java/lang/Integer";
+
+ if ("java/lang/Object".equals(name)) { //$NON-NLS-1$
+ return null;
+ }
+
+ String superClass = project.getSuperClassMap().get(name);
+ if (superClass != null) {
+ return superClass;
+ }
+
+ for (Project library : project.getAllLibraries()) {
+ superClass = library.getSuperClassMap().get(name);
+ if (superClass != null) {
+ return superClass;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates a super class map for the given project. The map maps from
+ * internal class name (e.g. java/lang/Integer, not java.lang.Integer) to its
+ * corresponding super class name. The root class, java/lang/Object, is not in the map.
+ *
+ * @param project the project to initialize the super class with; this will include
+ * local classes as well as any local .jar libraries; not transitive
+ * dependencies
+ * @return a map from class to its corresponding super class; never null
+ */
+ @NonNull
+ public Map<String, String> createSuperClassMap(@NonNull Project project) {
+ List<File> libraries = project.getJavaLibraries(true);
+ List<File> classFolders = project.getJavaClassFolders();
+ List<ClassEntry> classEntries = ClassEntry.fromClassPath(this, classFolders, true);
+ if (libraries.isEmpty()) {
+ return ClassEntry.createSuperClassMap(this, classEntries);
+ }
+ List<ClassEntry> libraryEntries = ClassEntry.fromClassPath(this, libraries, true);
+ return ClassEntry.createSuperClassMap(this, libraryEntries, classEntries);
+ }
+
+ /**
+ * Checks whether the given name is a subclass of the given super class. If
+ * the method does not know, it should return null, and otherwise return
+ * {@link Boolean#TRUE} or {@link Boolean#FALSE}.
+ * <p>
+ * Note that the class names are in internal VM format (java/lang/Integer,
+ * not java.lang.Integer, and using $ rather than . for inner classes).
+ *
+ * @param project the project context to look up the class in
+ * @param name the name of the class to be checked
+ * @param superClassName the name of the super class to compare to
+ * @return true if the class of the given name extends the given super class
+ */
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
+ @Nullable
+ public Boolean isSubclassOf(
+ @NonNull Project project,
+ @NonNull String name,
+ @NonNull String superClassName) {
+ return null;
+ }
+
+ /**
+ * Finds any custom lint rule jars that should be included for analysis,
+ * regardless of project.
+ * <p>
+ * The default implementation locates custom lint jars in ~/.android/lint/ and
+ * in $ANDROID_LINT_JARS
+ *
+ * @return a list of rule jars (possibly empty).
+ */
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
+ @NonNull
+ public List<File> findGlobalRuleJars() {
+ // Look for additional detectors registered by the user, via
+ // (1) an environment variable (useful for build servers etc), and
+ // (2) via jar files in the .android/lint directory
+ List<File> files = null;
+ try {
+ String androidHome = AndroidLocation.getFolder();
+ File lint = new File(androidHome + File.separator + "lint"); //$NON-NLS-1$
+ if (lint.exists()) {
+ File[] list = lint.listFiles();
+ if (list != null) {
+ for (File jarFile : list) {
+ if (endsWith(jarFile.getName(), DOT_JAR)) {
+ if (files == null) {
+ files = new ArrayList<File>();
+ }
+ files.add(jarFile);
+ }
+ }
+ }
+ }
+ } catch (AndroidLocation.AndroidLocationException e) {
+ // Ignore -- no android dir, so no rules to load.
+ }
+
+ String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$
+ if (lintClassPath != null && !lintClassPath.isEmpty()) {
+ String[] paths = lintClassPath.split(File.pathSeparator);
+ for (String path : paths) {
+ File jarFile = new File(path);
+ if (jarFile.exists()) {
+ if (files == null) {
+ files = new ArrayList<File>();
+ } else if (files.contains(jarFile)) {
+ continue;
+ }
+ files.add(jarFile);
+ }
+ }
+ }
+
+ return files != null ? files : Collections.<File>emptyList();
+ }
+
+ /**
+ * Finds any custom lint rule jars that should be included for analysis
+ * in the given project
+ *
+ * @param project the project to look up rule jars from
+ * @return a list of rule jars (possibly empty).
+ */
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
+ @NonNull
+ public List<File> findRuleJars(@NonNull Project project) {
+ if (project.isGradleProject()) {
+ if (project.isLibrary()) {
+ AndroidLibrary model = project.getGradleLibraryModel();
+ if (model != null) {
+ File lintJar = model.getLintJar();
+ if (lintJar.exists()) {
+ return Collections.singletonList(lintJar);
+ }
+ }
+ } else if (project.getSubset() != null) {
+ // Probably just analyzing a single file: we still want to look for custom
+ // rules applicable to the file
+ List<File> rules = null;
+ final Variant variant = project.getCurrentVariant();
+ if (variant != null) {
+ Collection<AndroidLibrary> libraries = variant.getMainArtifact()
+ .getDependencies().getLibraries();
+ for (AndroidLibrary library : libraries) {
+ File lintJar = library.getLintJar();
+ if (lintJar.exists()) {
+ if (rules == null) {
+ rules = Lists.newArrayListWithExpectedSize(4);
+ }
+ rules.add(lintJar);
+ }
+ }
+ if (rules != null) {
+ return rules;
+ }
+ }
+ } else if (project.getDir().getPath().endsWith(DOT_AAR)) {
+ File lintJar = new File(project.getDir(), "lint.jar"); //$NON-NLS-1$
+ if (lintJar.exists()) {
+ return Collections.singletonList(lintJar);
+ }
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
+ * Opens a URL connection.
+ *
+ * Clients such as IDEs can override this to for example consider the user's IDE proxy
+ * settings.
+ *
+ * @param url the URL to read
+ * @return a {@link java.net.URLConnection} or null
+ * @throws IOException if any kind of IO exception occurs
+ */
+ @Nullable
+ public URLConnection openConnection(@NonNull URL url) throws IOException {
+ return url.openConnection();
+ }
+
+ /** Closes a connection previously returned by {@link #openConnection(java.net.URL)} */
+ public void closeConnection(@NonNull URLConnection connection) throws IOException {
+ if (connection instanceof HttpURLConnection) {
+ ((HttpURLConnection)connection).disconnect();
+ }
+ }
+
+ /**
+ * Returns true if the given directory is a lint project directory.
+ * By default, a project directory is the directory containing a manifest file,
+ * but in Gradle projects for example it's the root gradle directory.
+ *
+ * @param dir the directory to check
+ * @return true if the directory represents a lint project
+ */
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
+ public boolean isProjectDirectory(@NonNull File dir) {
+ return LintUtils.isManifestFolder(dir) || Project.isAospFrameworksProject(dir);
+ }
+
+ /**
+ * Returns whether lint should look for suppress comments. Tools that already do
+ * this on their own can return false here to avoid doing unnecessary work.
+ */
+ public boolean checkForSuppressComments() {
+ return true;
+ }
+
+ /**
+ * Adds in any custom lint rules and returns the result as a new issue registry,
+ * or the same one if no custom rules were found
+ *
+ * @param registry the main registry to add rules to
+ * @return a new registry containing the passed in rules plus any custom rules,
+ * or the original registry if no custom rules were found
+ */
+ public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) {
+ List<File> jarFiles = findGlobalRuleJars();
+
+ if (!jarFiles.isEmpty()) {
+ List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
+ registries.add(registry);
+ for (File jarFile : jarFiles) {
+ try {
+ registries.add(JarFileIssueRegistry.get(this, jarFile));
+ } catch (Throwable e) {
+ log(e, "Could not load custom rule jar file %1$s", jarFile);
+ }
+ }
+ if (registries.size() > 1) { // the first item is the passed in registry itself
+ return new CompositeIssueRegistry(registries);
+ }
+ }
+
+ return registry;
+ }
+
+ /**
+ * Creates a {@link ClassLoader} which can load in a set of Jar files.
+ *
+ * @param urls the URLs
+ * @param parent the parent class loader
+ * @return a new class loader
+ */
+ public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) {
+ return new URLClassLoader(urls, parent);
+ }
+
+ /**
+ * Returns true if this client supports project resource repository lookup via
+ * {@link #getProjectResources(Project,boolean)}
+ *
+ * @return true if the client can provide project resources
+ */
+ public boolean supportsProjectResources() {
+ return false;
+ }
+
+ /**
+ * Returns the project resources, if available
+ *
+ * @param includeDependencies if true, include merged view of all dependencies
+ * @return the project resources, or null if not available
+ */
+ @Nullable
+ public AbstractResourceRepository getProjectResources(Project project,
+ boolean includeDependencies) {
+ return null;
+ }
+
+ /**
+ * For a lint client which supports resource items (via {@link #supportsProjectResources()})
+ * return a handle for a resource item
+ *
+ * @param item the resource item to look up a location handle for
+ * @return a corresponding handle
+ */
+ @NonNull
+ public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
+ return new Location.ResourceItemHandle(item);
+ }
+
+ private ResourceVisibilityLookup.Provider mResourceVisibility;
+
+ /**
+ * Returns a shared {@link ResourceVisibilityLookup.Provider}
+ *
+ * @return a shared provider for looking up resource visibility
+ */
+ @NonNull
+ public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() {
+ if (mResourceVisibility == null) {
+ mResourceVisibility = new ResourceVisibilityLookup.Provider();
+ }
+ return mResourceVisibility;
+ }
+
+ /**
+ * The client name returned by {@link #getClientName()} when running in
+ * Android Studio/IntelliJ IDEA
+ */
+ public static final String CLIENT_STUDIO = "studio";
+
+ /**
+ * The client name returned by {@link #getClientName()} when running in
+ * Gradle
+ */
+ public static final String CLIENT_GRADLE = "gradle";
+
+ /**
+ * The client name returned by {@link #getClientName()} when running in
+ * the CLI (command line interface) version of lint, {@code lint}
+ */
+ public static final String CLIENT_CLI = "cli";
+
+ /**
+ * The client name returned by {@link #getClientName()} when running in
+ * some unknown client
+ */
+ public static final String CLIENT_UNKNOWN = "unknown";
+
+ /** The client name. */
+ @NonNull
+ private static String sClientName = CLIENT_UNKNOWN;
+
+ /**
+ * Returns the name of the embedding client. It could be not just
+ * {@link #CLIENT_STUDIO}, {@link #CLIENT_GRADLE}, {@link #CLIENT_CLI}
+ * etc but other values too as lint is integrated in other embedding contexts.
+ *
+ * @return the name of the embedding client
+ */
+ @NonNull
+ public static String getClientName() {
+ return sClientName;
+ }
+
+ /**
+ * Returns true if the embedding client currently running lint is Android Studio
+ * (or IntelliJ IDEA)
+ *
+ * @return true if running in Android Studio / IntelliJ IDEA
+ */
+ public static boolean isStudio() {
+ return CLIENT_STUDIO.equals(sClientName);
+ }
+
+ /**
+ * Returns true if the embedding client currently running lint is Gradle
+ *
+ * @return true if running in Gradle
+ */
+ public static boolean isGradle() {
+ return CLIENT_GRADLE.equals(sClientName);
+ }
+
+ /** Returns a repository logger used by this client. */
+ @NonNull
+ public ProgressIndicator getRepositoryLogger() {
+ return new RepoLogger();
+ }
+
+ private static final class RepoLogger extends ProgressIndicatorAdapter {
+ // Intentionally not logging these: the SDK manager is
+ // logging events such as package.xml parsing
+ // Parsing /path/to/sdk//build-tools/19.1.0/package.xml
+ // Parsing /path/to/sdk//build-tools/20.0.0/package.xml
+ // Parsing /path/to/sdk//build-tools/21.0.0/package.xml
+ // which we don't want to spam on the console.
+ // It's also warning about packages that it's encountering
+ // multiple times etc; that's not something we should include
+ // in lint command line output.
+
+ @Override
+ public void logError(@NonNull String s, @Nullable Throwable e) {
+ }
+
+ @Override
+ public void logInfo(@NonNull String s) {
+ }
+
+ @Override
+ public void logWarning(@NonNull String s, @Nullable Throwable e) {
+ }
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
new file mode 100644
index 0000000..d99240a
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
@@ -0,0 +1,2609 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.client.api;
+
+import static com.android.SdkConstants.ATTR_IGNORE;
+import static com.android.SdkConstants.CLASS_CONSTRUCTOR;
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.FD_GRADLE_WRAPPER;
+import static com.android.SdkConstants.FN_GRADLE_WRAPPER_PROPERTIES;
+import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
+import static com.android.SdkConstants.RES_FOLDER;
+import static com.android.SdkConstants.SUPPRESS_ALL;
+import static com.android.SdkConstants.SUPPRESS_LINT;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.ide.common.resources.configuration.FolderConfiguration.QUALIFIER_SPLITTER;
+import static com.android.tools.lint.detector.api.LintUtils.isAnonymousClass;
+import static java.io.File.separator;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.repository.ResourceVisibilityLookup;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.resources.ResourceFolderType;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.tools.lint.client.api.LintListener.EventType;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceContext;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import lombok.ast.Annotation;
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
+import lombok.ast.AnnotationValue;
+import lombok.ast.ArrayInitializer;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Modifiers;
+import lombok.ast.Node;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+import lombok.ast.TypeDeclaration;
+import lombok.ast.TypeReference;
+import lombok.ast.VariableDefinition;
+
+/**
+ * Analyzes Android projects and files
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class LintDriver {
+ /**
+ * Max number of passes to run through the lint runner if requested by
+ * {@link #requestRepeat}
+ */
+ private static final int MAX_PHASES = 3;
+ private static final String SUPPRESS_LINT_VMSIG = '/' + SUPPRESS_LINT + ';';
+ /** Prefix used by the comment suppress mechanism in Studio/IntelliJ */
+ private static final String STUDIO_ID_PREFIX = "AndroidLint";
+
+ private final LintClient mClient;
+ private LintRequest mRequest;
+ private IssueRegistry mRegistry;
+ private volatile boolean mCanceled;
+ private EnumSet<Scope> mScope;
+ private List<? extends Detector> mApplicableDetectors;
+ private Map<Scope, List<Detector>> mScopeDetectors;
+ private List<LintListener> mListeners;
+ private int mPhase;
+ private List<Detector> mRepeatingDetectors;
+ private EnumSet<Scope> mRepeatScope;
+ private Project[] mCurrentProjects;
+ private Project mCurrentProject;
+ private boolean mAbbreviating = true;
+ private boolean mParserErrors;
+ private Map<Object,Object> mProperties;
+
+ /**
+ * Creates a new {@link LintDriver}
+ *
+ * @param registry The registry containing issues to be checked
+ * @param client the tool wrapping the analyzer, such as an IDE or a CLI
+ */
+ public LintDriver(@NonNull IssueRegistry registry, @NonNull LintClient client) {
+ mRegistry = registry;
+ mClient = new LintClientWrapper(client);
+ }
+
+ /** Cancels the current lint run as soon as possible */
+ public void cancel() {
+ mCanceled = true;
+ }
+
+ /**
+ * Returns the scope for the lint job
+ *
+ * @return the scope, never null
+ */
+ @NonNull
+ public EnumSet<Scope> getScope() {
+ return mScope;
+ }
+
+ /**
+ * Sets the scope for the lint job
+ *
+ * @param scope the scope to use
+ */
+ public void setScope(@NonNull EnumSet<Scope> scope) {
+ mScope = scope;
+ }
+
+ /**
+ * Returns the lint client requesting the lint check. This may not be the same
+ * instance as the one passed in to this driver; lint uses a wrapper which performs
+ * additional validation to ensure that for example badly behaved detectors which report
+ * issues that have been disabled will get muted without the real lint client getting
+ * notified. Thus, this {@link LintClient} is suitable for use by detectors to look
+ * up a client to for example get location handles from, but tool handling code should
+ * never try to cast this client back to their original lint client. For the original
+ * lint client, use {@link LintRequest} instead.
+ *
+ * @return the client, never null
+ */
+ @NonNull
+ public LintClient getClient() {
+ return mClient;
+ }
+
+ /**
+ * Returns the current request, which points to the original files to be checked,
+ * the original scope, the original {@link LintClient}, as well as the release mode.
+ *
+ * @return the request
+ */
+ @NonNull
+ public LintRequest getRequest() {
+ return mRequest;
+ }
+
+ /**
+ * Records a property for later retrieval by {@link #getProperty(Object)}
+ *
+ * @param key the key to associate the value with
+ * @param value the value, or null to remove a previous binding
+ */
+ public void putProperty(@NonNull Object key, @Nullable Object value) {
+ if (mProperties == null) {
+ mProperties = Maps.newHashMap();
+ }
+ if (value == null) {
+ mProperties.remove(key);
+ } else {
+ mProperties.put(key, value);
+ }
+ }
+
+ /**
+ * Returns the property previously stored with the given key, or null
+ *
+ * @param key the key
+ * @return the value or null if not found
+ */
+ @Nullable
+ public Object getProperty(@NonNull Object key) {
+ if (mProperties != null) {
+ return mProperties.get(key);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the current phase number. The first pass is numbered 1. Only one pass
+ * will be performed, unless a {@link Detector} calls {@link #requestRepeat}.
+ *
+ * @return the current phase, usually 1
+ */
+ public int getPhase() {
+ return mPhase;
+ }
+
+ /**
+ * Returns the current {@link IssueRegistry}.
+ *
+ * @return the current {@link IssueRegistry}
+ */
+ @NonNull
+ public IssueRegistry getRegistry() {
+ return mRegistry;
+ }
+
+ /**
+ * Returns the project containing a given file, or null if not found. This searches
+ * only among the currently checked project and its library projects, not among all
+ * possible projects being scanned sequentially.
+ *
+ * @param file the file to be checked
+ * @return the corresponding project, or null if not found
+ */
+ @Nullable
+ public Project findProjectFor(@NonNull File file) {
+ if (mCurrentProjects != null) {
+ if (mCurrentProjects.length == 1) {
+ return mCurrentProjects[0];
+ }
+ String path = file.getPath();
+ for (Project project : mCurrentProjects) {
+ if (path.startsWith(project.getDir().getPath())) {
+ return project;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets whether lint should abbreviate output when appropriate.
+ *
+ * @param abbreviating true to abbreviate output, false to include everything
+ */
+ public void setAbbreviating(boolean abbreviating) {
+ mAbbreviating = abbreviating;
+ }
+
+ /**
+ * Returns whether lint should abbreviate output when appropriate.
+ *
+ * @return true if lint should abbreviate output, false when including everything
+ */
+ public boolean isAbbreviating() {
+ return mAbbreviating;
+ }
+
+ /**
+ * Returns whether lint has encountered any files with fatal parser errors
+ * (e.g. broken source code, or even broken parsers)
+ * <p>
+ * This is useful for checks that need to make sure they've seen all data in
+ * order to be conclusive (such as an unused resource check).
+ *
+ * @return true if any files were not properly processed because they
+ * contained parser errors
+ */
+ public boolean hasParserErrors() {
+ return mParserErrors;
+ }
+
+ /**
+ * Sets whether lint has encountered files with fatal parser errors.
+ *
+ * @see #hasParserErrors()
+ * @param hasErrors whether parser errors have been encountered
+ */
+ public void setHasParserErrors(boolean hasErrors) {
+ mParserErrors = hasErrors;
+ }
+
+ /**
+ * Returns the projects being analyzed
+ *
+ * @return the projects being analyzed
+ */
+ @NonNull
+ public List<Project> getProjects() {
+ if (mCurrentProjects != null) {
+ return Arrays.asList(mCurrentProjects);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Analyze the given file (which can point to an Android project). Issues found
+ * are reported to the associated {@link LintClient}.
+ *
+ * @param files the files and directories to be analyzed
+ * @param scope the scope of the analysis; detectors with a wider scope will
+ * not be run. If null, the scope will be inferred from the files.
+ * @deprecated use {@link #analyze(LintRequest) instead}
+ */
+ @Deprecated
+ public void analyze(@NonNull List<File> files, @Nullable EnumSet<Scope> scope) {
+ analyze(new LintRequest(mClient, files).setScope(scope));
+ }
+
+ /**
+ * Analyze the given files (which can point to Android projects or directories
+ * containing Android projects). Issues found are reported to the associated
+ * {@link LintClient}.
+ * <p>
+ * Note that the {@link LintDriver} is not multi thread safe or re-entrant;
+ * if you want to run potentially overlapping lint jobs, create a separate driver
+ * for each job.
+ *
+ * @param request the files and directories to be analyzed
+ */
+ public void analyze(@NonNull LintRequest request) {
+ try {
+ mRequest = request;
+ analyze();
+ } finally {
+ mRequest = null;
+ }
+ }
+
+ /** Runs the driver to analyze the requested files */
+ private void analyze() {
+ mCanceled = false;
+ mScope = mRequest.getScope();
+ assert mScope == null || !mScope.contains(Scope.ALL_RESOURCE_FILES) ||
+ mScope.contains(Scope.RESOURCE_FILE);
+
+ Collection<Project> projects;
+ try {
+ projects = mRequest.getProjects();
+ if (projects == null) {
+ projects = computeProjects(mRequest.getFiles());
+ }
+ } catch (CircularDependencyException e) {
+ mCurrentProject = e.getProject();
+ if (mCurrentProject != null) {
+ Location location = e.getLocation();
+ File file = location != null ? location.getFile() : mCurrentProject.getDir();
+ Context context = new Context(this, mCurrentProject, null, file);
+ context.report(IssueRegistry.LINT_ERROR, e.getLocation(), e.getMessage());
+ mCurrentProject = null;
+ }
+ return;
+ }
+ if (projects.isEmpty()) {
+ mClient.log(null, "No projects found for %1$s", mRequest.getFiles().toString());
+ return;
+ }
+ if (mCanceled) {
+ return;
+ }
+
+ registerCustomDetectors(projects);
+
+ if (mScope == null) {
+ mScope = Scope.infer(projects);
+ }
+
+ fireEvent(EventType.STARTING, null);
+
+ for (Project project : projects) {
+ mPhase = 1;
+
+ Project main = mRequest.getMainProject(project);
+
+ // The set of available detectors varies between projects
+ computeDetectors(project);
+
+ if (mApplicableDetectors.isEmpty()) {
+ // No detectors enabled in this project: skip it
+ continue;
+ }
+
+ checkProject(project, main);
+ if (mCanceled) {
+ break;
+ }
+
+ runExtraPhases(project, main);
+ }
+
+ fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null);
+ }
+
+ @Nullable
+ private Set<Issue> myCustomIssues;
+
+ /**
+ * Returns true if the given issue is an issue that was loaded as a custom rule
+ * (e.g. a 3rd-party library provided the detector, it's not built in)
+ *
+ * @param issue the issue to be looked up
+ * @return true if this is a custom (non-builtin) check
+ */
+ public boolean isCustomIssue(@NonNull Issue issue) {
+ return myCustomIssues != null && myCustomIssues.contains(issue);
+ }
+
+ private void registerCustomDetectors(Collection<Project> projects) {
+ // Look at the various projects, and if any of them provide a custom
+ // lint jar, "add" them (this will replace the issue registry with
+ // a CompositeIssueRegistry containing the original issue registry
+ // plus JarFileIssueRegistry instances for each lint jar
+ Set<File> jarFiles = Sets.newHashSet();
+ for (Project project : projects) {
+ jarFiles.addAll(mClient.findRuleJars(project));
+ for (Project library : project.getAllLibraries()) {
+ jarFiles.addAll(mClient.findRuleJars(library));
+ }
+ }
+
+ jarFiles.addAll(mClient.findGlobalRuleJars());
+
+ if (!jarFiles.isEmpty()) {
+ List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
+ registries.add(mRegistry);
+ for (File jarFile : jarFiles) {
+ try {
+ IssueRegistry registry = JarFileIssueRegistry.get(mClient, jarFile);
+ if (myCustomIssues == null) {
+ myCustomIssues = Sets.newHashSet();
+ }
+ myCustomIssues.addAll(registry.getIssues());
+ registries.add(registry);
+ } catch (Throwable e) {
+ mClient.log(e, "Could not load custom rule jar file %1$s", jarFile);
+ }
+ }
+ if (registries.size() > 1) { // the first item is mRegistry itself
+ mRegistry = new CompositeIssueRegistry(registries);
+ }
+ }
+ }
+
+ private void runExtraPhases(@NonNull Project project, @NonNull Project main) {
+ // Did any detectors request another phase?
+ if (mRepeatingDetectors != null) {
+ // Yes. Iterate up to MAX_PHASES times.
+
+ // During the extra phases, we might be narrowing the scope, and setting it in the
+ // scope field such that detectors asking about the available scope will get the
+ // correct result. However, we need to restore it to the original scope when this
+ // is done in case there are other projects that will be checked after this, since
+ // the repeated phases is done *per project*, not after all projects have been
+ // processed.
+ EnumSet<Scope> oldScope = mScope;
+
+ do {
+ mPhase++;
+ fireEvent(EventType.NEW_PHASE,
+ new Context(this, project, null, project.getDir()));
+
+ // Narrow the scope down to the set of scopes requested by
+ // the rules.
+ if (mRepeatScope == null) {
+ mRepeatScope = Scope.ALL;
+ }
+ mScope = Scope.intersect(mScope, mRepeatScope);
+ if (mScope.isEmpty()) {
+ break;
+ }
+
+ // Compute the detectors to use for this pass.
+ // Unlike the normal computeDetectors(project) call,
+ // this is going to use the existing instances, and include
+ // those that apply for the configuration.
+ computeRepeatingDetectors(mRepeatingDetectors, project);
+
+ if (mApplicableDetectors.isEmpty()) {
+ // No detectors enabled in this project: skip it
+ continue;
+ }
+
+ checkProject(project, main);
+ if (mCanceled) {
+ break;
+ }
+ } while (mPhase < MAX_PHASES && mRepeatingDetectors != null);
+
+ mScope = oldScope;
+ }
+ }
+
+ private void computeRepeatingDetectors(List<Detector> detectors, Project project) {
+ // Ensure that the current visitor is recomputed
+ mCurrentFolderType = null;
+ mCurrentVisitor = null;
+ mCurrentXmlDetectors = null;
+ mCurrentBinaryDetectors = null;
+
+ // Create map from detector class to issue such that we can
+ // compute applicable issues for each detector in the list of detectors
+ // to be repeated
+ List<Issue> issues = mRegistry.getIssues();
+ Multimap<Class<? extends Detector>, Issue> issueMap =
+ ArrayListMultimap.create(issues.size(), 3);
+ for (Issue issue : issues) {
+ issueMap.put(issue.getImplementation().getDetectorClass(), issue);
+ }
+
+ Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope =
+ new HashMap<Class<? extends Detector>, EnumSet<Scope>>();
+ Map<Scope, List<Detector>> scopeToDetectors =
+ new EnumMap<Scope, List<Detector>>(Scope.class);
+
+ List<Detector> detectorList = new ArrayList<Detector>();
+ // Compute the list of detectors (narrowed down from mRepeatingDetectors),
+ // and simultaneously build up the detectorToScope map which tracks
+ // the scopes each detector is affected by (this is used to populate
+ // the mScopeDetectors map which is used during iteration).
+ Configuration configuration = project.getConfiguration(this);
+ for (Detector detector : detectors) {
+ Class<? extends Detector> detectorClass = detector.getClass();
+ Collection<Issue> detectorIssues = issueMap.get(detectorClass);
+ if (detectorIssues != null) {
+ boolean add = false;
+ for (Issue issue : detectorIssues) {
+ // The reason we have to check whether the detector is enabled
+ // is that this is a per-project property, so when running lint in multiple
+ // projects, a detector enabled only in a different project could have
+ // requested another phase, and we end up in this project checking whether
+ // the detector is enabled here.
+ if (!configuration.isEnabled(issue)) {
+ continue;
+ }
+
+ add = true; // Include detector if any of its issues are enabled
+
+ EnumSet<Scope> s = detectorToScope.get(detectorClass);
+ EnumSet<Scope> issueScope = issue.getImplementation().getScope();
+ if (s == null) {
+ detectorToScope.put(detectorClass, issueScope);
+ } else if (!s.containsAll(issueScope)) {
+ EnumSet<Scope> union = EnumSet.copyOf(s);
+ union.addAll(issueScope);
+ detectorToScope.put(detectorClass, union);
+ }
+ }
+
+ if (add) {
+ detectorList.add(detector);
+ EnumSet<Scope> union = detectorToScope.get(detector.getClass());
+ for (Scope s : union) {
+ List<Detector> list = scopeToDetectors.get(s);
+ if (list == null) {
+ list = new ArrayList<Detector>();
+ scopeToDetectors.put(s, list);
+ }
+ list.add(detector);
+ }
+ }
+ }
+ }
+
+ mApplicableDetectors = detectorList;
+ mScopeDetectors = scopeToDetectors;
+ mRepeatingDetectors = null;
+ mRepeatScope = null;
+
+ validateScopeList();
+ }
+
+ private void computeDetectors(@NonNull Project project) {
+ // Ensure that the current visitor is recomputed
+ mCurrentFolderType = null;
+ mCurrentVisitor = null;
+
+ Configuration configuration = project.getConfiguration(this);
+ mScopeDetectors = new EnumMap<Scope, List<Detector>>(Scope.class);
+ mApplicableDetectors = mRegistry.createDetectors(mClient, configuration,
+ mScope, mScopeDetectors);
+
+ validateScopeList();
+ }
+
+ /** Development diagnostics only, run with assertions on */
+ @SuppressWarnings("all") // Turn off warnings for the intentional assertion side effect below
+ private void validateScopeList() {
+ boolean assertionsEnabled = false;
+ assert assertionsEnabled = true; // Intentional side-effect
+ if (assertionsEnabled) {
+ List<Detector> resourceFileDetectors = mScopeDetectors.get(Scope.RESOURCE_FILE);
+ if (resourceFileDetectors != null) {
+ for (Detector detector : resourceFileDetectors) {
+ // This is wrong; it should allow XmlScanner instead of ResourceXmlScanner!
+ assert detector instanceof ResourceXmlDetector : detector;
+ }
+ }
+
+ List<Detector> manifestDetectors = mScopeDetectors.get(Scope.MANIFEST);
+ if (manifestDetectors != null) {
+ for (Detector detector : manifestDetectors) {
+ assert detector instanceof Detector.XmlScanner : detector;
+ }
+ }
+ List<Detector> javaCodeDetectors = mScopeDetectors.get(Scope.ALL_JAVA_FILES);
+ if (javaCodeDetectors != null) {
+ for (Detector detector : javaCodeDetectors) {
+ assert detector instanceof Detector.JavaScanner : detector;
+ }
+ }
+ List<Detector> javaFileDetectors = mScopeDetectors.get(Scope.JAVA_FILE);
+ if (javaFileDetectors != null) {
+ for (Detector detector : javaFileDetectors) {
+ assert detector instanceof Detector.JavaScanner : detector;
+ }
+ }
+
+ List<Detector> classDetectors = mScopeDetectors.get(Scope.CLASS_FILE);
+ if (classDetectors != null) {
+ for (Detector detector : classDetectors) {
+ assert detector instanceof Detector.ClassScanner : detector;
+ }
+ }
+
+ List<Detector> classCodeDetectors = mScopeDetectors.get(Scope.ALL_CLASS_FILES);
+ if (classCodeDetectors != null) {
+ for (Detector detector : classCodeDetectors) {
+ assert detector instanceof Detector.ClassScanner : detector;
+ }
+ }
+
+ List<Detector> gradleDetectors = mScopeDetectors.get(Scope.GRADLE_FILE);
+ if (gradleDetectors != null) {
+ for (Detector detector : gradleDetectors) {
+ assert detector instanceof Detector.GradleScanner : detector;
+ }
+ }
+
+ List<Detector> otherDetectors = mScopeDetectors.get(Scope.OTHER);
+ if (otherDetectors != null) {
+ for (Detector detector : otherDetectors) {
+ assert detector instanceof Detector.OtherFileScanner : detector;
+ }
+ }
+
+ List<Detector> dirDetectors = mScopeDetectors.get(Scope.RESOURCE_FOLDER);
+ if (dirDetectors != null) {
+ for (Detector detector : dirDetectors) {
+ assert detector instanceof Detector.ResourceFolderScanner : detector;
+ }
+ }
+
+ List<Detector> binaryDetectors = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE);
+ if (binaryDetectors != null) {
+ for (Detector detector : binaryDetectors) {
+ assert detector instanceof Detector.BinaryResourceScanner : detector;
+ }
+ }
+ }
+ }
+
+ private void registerProjectFile(
+ @NonNull Map<File, Project> fileToProject,
+ @NonNull File file,
+ @NonNull File projectDir,
+ @NonNull File rootDir) {
+ fileToProject.put(file, mClient.getProject(projectDir, rootDir));
+ }
+
+ private Collection<Project> computeProjects(@NonNull List<File> files) {
+ // Compute list of projects
+ Map<File, Project> fileToProject = new LinkedHashMap<File, Project>();
+
+ File sharedRoot = null;
+
+ // Ensure that we have absolute paths such that if you lint
+ // "foo bar" in "baz" we can show baz/ as the root
+ if (files.size() > 1) {
+ List<File> absolute = new ArrayList<File>(files.size());
+ for (File file : files) {
+ absolute.add(file.getAbsoluteFile());
+ }
+ files = absolute;
+
+ sharedRoot = LintUtils.getCommonParent(files);
+ if (sharedRoot != null && sharedRoot.getParentFile() == null) { // "/" ?
+ sharedRoot = null;
+ }
+ }
+
+ for (File file : files) {
+ if (file.isDirectory()) {
+ File rootDir = sharedRoot;
+ if (rootDir == null) {
+ rootDir = file;
+ if (files.size() > 1) {
+ rootDir = file.getParentFile();
+ if (rootDir == null) {
+ rootDir = file;
+ }
+ }
+ }
+
+ // Figure out what to do with a directory. Note that the meaning of the
+ // directory can be ambiguous:
+ // If you pass a directory which is unknown, we don't know if we should
+ // search upwards (in case you're pointing at a deep java package folder
+ // within the project), or if you're pointing at some top level directory
+ // containing lots of projects you want to scan. We attempt to do the
+ // right thing, which is to see if you're pointing right at a project or
+ // right within it (say at the src/ or res/) folder, and if not, you're
+ // hopefully pointing at a project tree that you want to scan recursively.
+ if (mClient.isProjectDirectory(file)) {
+ registerProjectFile(fileToProject, file, file, rootDir);
+ continue;
+ } else {
+ File parent = file.getParentFile();
+ if (parent != null) {
+ if (mClient.isProjectDirectory(parent)) {
+ registerProjectFile(fileToProject, file, parent, parent);
+ continue;
+ } else {
+ parent = parent.getParentFile();
+ if (parent != null && mClient.isProjectDirectory(parent)) {
+ registerProjectFile(fileToProject, file, parent, parent);
+ continue;
+ }
+ }
+ }
+
+ // Search downwards for nested projects
+ addProjects(file, fileToProject, rootDir);
+ }
+ } else {
+ // Pointed at a file: Search upwards for the containing project
+ File parent = file.getParentFile();
+ while (parent != null) {
+ if (mClient.isProjectDirectory(parent)) {
+ registerProjectFile(fileToProject, file, parent, parent);
+ break;
+ }
+ parent = parent.getParentFile();
+ }
+ }
+
+ if (mCanceled) {
+ return Collections.emptySet();
+ }
+ }
+
+ for (Map.Entry<File, Project> entry : fileToProject.entrySet()) {
+ File file = entry.getKey();
+ Project project = entry.getValue();
+ if (!file.equals(project.getDir())) {
+ if (file.isDirectory()) {
+ try {
+ File dir = file.getCanonicalFile();
+ if (dir.equals(project.getDir())) {
+ continue;
+ }
+ } catch (IOException ioe) {
+ // pass
+ }
+ }
+
+ project.addFile(file);
+ }
+ }
+
+ // Partition the projects up such that we only return projects that aren't
+ // included by other projects (e.g. because they are library projects)
+
+ Collection<Project> allProjects = fileToProject.values();
+ Set<Project> roots = new HashSet<Project>(allProjects);
+ for (Project project : allProjects) {
+ roots.removeAll(project.getAllLibraries());
+ }
+
+ // Report issues for all projects that are explicitly referenced. We need to
+ // do this here, since the project initialization will mark all library
+ // projects as no-report projects by default.
+ for (Project project : allProjects) {
+ // Report issues for all projects explicitly listed or found via a directory
+ // traversal -- including library projects.
+ project.setReportIssues(true);
+ }
+
+ if (LintUtils.assertionsEnabled()) {
+ // Make sure that all the project directories are unique. This ensures
+ // that we didn't accidentally end up with different project instances
+ // for a library project discovered as a directory as well as one
+ // initialized from the library project dependency list
+ IdentityHashMap<Project, Project> projects =
+ new IdentityHashMap<Project, Project>();
+ for (Project project : roots) {
+ projects.put(project, project);
+ for (Project library : project.getAllLibraries()) {
+ projects.put(library, library);
+ }
+ }
+ Set<File> dirs = new HashSet<File>();
+ for (Project project : projects.keySet()) {
+ assert !dirs.contains(project.getDir());
+ dirs.add(project.getDir());
+ }
+ }
+
+ return roots;
+ }
+
+ private void addProjects(
+ @NonNull File dir,
+ @NonNull Map<File, Project> fileToProject,
+ @NonNull File rootDir) {
+ if (mCanceled) {
+ return;
+ }
+
+ if (mClient.isProjectDirectory(dir)) {
+ registerProjectFile(fileToProject, dir, dir, rootDir);
+ } else {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ addProjects(file, fileToProject, rootDir);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkProject(@NonNull Project project, @NonNull Project main) {
+ File projectDir = project.getDir();
+
+ Context projectContext = new Context(this, project, null, projectDir);
+ fireEvent(EventType.SCANNING_PROJECT, projectContext);
+
+ List<Project> allLibraries = project.getAllLibraries();
+ Set<Project> allProjects = new HashSet<Project>(allLibraries.size() + 1);
+ allProjects.add(project);
+ allProjects.addAll(allLibraries);
+ mCurrentProjects = allProjects.toArray(new Project[allProjects.size()]);
+
+ mCurrentProject = project;
+
+ for (Detector check : mApplicableDetectors) {
+ check.beforeCheckProject(projectContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ assert mCurrentProject == project;
+ runFileDetectors(project, main);
+
+ if (!Scope.checkSingleFile(mScope)) {
+ List<Project> libraries = project.getAllLibraries();
+ for (Project library : libraries) {
+ Context libraryContext = new Context(this, library, project, projectDir);
+ fireEvent(EventType.SCANNING_LIBRARY_PROJECT, libraryContext);
+ mCurrentProject = library;
+
+ for (Detector check : mApplicableDetectors) {
+ check.beforeCheckLibraryProject(libraryContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+ assert mCurrentProject == library;
+
+ runFileDetectors(library, main);
+ if (mCanceled) {
+ return;
+ }
+
+ assert mCurrentProject == library;
+
+ for (Detector check : mApplicableDetectors) {
+ check.afterCheckLibraryProject(libraryContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
+
+ mCurrentProject = project;
+
+ for (Detector check : mApplicableDetectors) {
+ check.afterCheckProject(projectContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ if (mCanceled) {
+ mClient.report(
+ projectContext,
+ // Must provide an issue since API guarantees that the issue parameter
+ IssueRegistry.CANCELLED,
+ Severity.INFORMATIONAL,
+ null /*range*/,
+ "Lint canceled by user", TextFormat.RAW);
+ }
+
+ mCurrentProjects = null;
+ }
+
+ private void runFileDetectors(@NonNull Project project, @Nullable Project main) {
+ // Look up manifest information (but not for library projects)
+ if (project.isAndroidProject()) {
+ for (File manifestFile : project.getManifestFiles()) {
+ XmlParser parser = mClient.getXmlParser();
+ if (parser != null) {
+ XmlContext context = new XmlContext(this, project, main, manifestFile, null,
+ parser);
+ context.document = parser.parseXml(context);
+ if (context.document != null) {
+ try {
+ project.readManifest(context.document);
+
+ if ((!project.isLibrary() || (main != null
+ && main.isMergingManifests()))
+ && mScope.contains(Scope.MANIFEST)) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.MANIFEST);
+ if (detectors != null) {
+ ResourceVisitor v = new ResourceVisitor(parser, detectors,
+ null);
+ fireEvent(EventType.SCANNING_FILE, context);
+ v.visitFile(context, manifestFile);
+ }
+ }
+ } finally {
+ if (context.document != null) { // else: freed by XmlVisitor above
+ parser.dispose(context, context.document);
+ }
+ }
+ }
+ }
+ }
+
+ // Process both Scope.RESOURCE_FILE and Scope.ALL_RESOURCE_FILES detectors together
+ // in a single pass through the resource directories.
+ if (mScope.contains(Scope.ALL_RESOURCE_FILES)
+ || mScope.contains(Scope.RESOURCE_FILE)
+ || mScope.contains(Scope.RESOURCE_FOLDER)
+ || mScope.contains(Scope.BINARY_RESOURCE_FILE)) {
+ List<Detector> dirChecks = mScopeDetectors.get(Scope.RESOURCE_FOLDER);
+ List<Detector> binaryChecks = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE);
+ List<Detector> checks = union(mScopeDetectors.get(Scope.RESOURCE_FILE),
+ mScopeDetectors.get(Scope.ALL_RESOURCE_FILES));
+ boolean haveXmlChecks = checks != null && !checks.isEmpty();
+ List<ResourceXmlDetector> xmlDetectors;
+ if (haveXmlChecks) {
+ xmlDetectors = new ArrayList<ResourceXmlDetector>(checks.size());
+ for (Detector detector : checks) {
+ if (detector instanceof ResourceXmlDetector) {
+ xmlDetectors.add((ResourceXmlDetector) detector);
+ }
+ }
+ haveXmlChecks = !xmlDetectors.isEmpty();
+ } else {
+ xmlDetectors = Collections.emptyList();
+ }
+ if (haveXmlChecks
+ || dirChecks != null && !dirChecks.isEmpty()
+ || binaryChecks != null && !binaryChecks.isEmpty()) {
+ List<File> files = project.getSubset();
+ if (files != null) {
+ checkIndividualResources(project, main, xmlDetectors, dirChecks,
+ binaryChecks, files);
+ } else {
+ List<File> resourceFolders = project.getResourceFolders();
+ if (!resourceFolders.isEmpty()) {
+ for (File res : resourceFolders) {
+ checkResFolder(project, main, res, xmlDetectors, dirChecks,
+ binaryChecks);
+ }
+ }
+ }
+ }
+ }
+
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) {
+ List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE),
+ mScopeDetectors.get(Scope.ALL_JAVA_FILES));
+ if (checks != null && !checks.isEmpty()) {
+ List<File> files = project.getSubset();
+ if (files != null) {
+ checkIndividualJavaFiles(project, main, checks, files);
+ } else {
+ List<File> sourceFolders = project.getJavaSourceFolders();
+ if (mScope.contains(Scope.TEST_SOURCES)) {
+ List<File> testFolders = project.getTestSourceFolders();
+ if (!testFolders.isEmpty()) {
+ List<File> combined = Lists.newArrayListWithExpectedSize(
+ sourceFolders.size() + testFolders.size());
+ combined.addAll(sourceFolders);
+ combined.addAll(testFolders);
+ sourceFolders = combined;
+ }
+ }
+
+ checkJava(project, main, sourceFolders, checks);
+
+ }
+ }
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ if (mScope.contains(Scope.CLASS_FILE)
+ || mScope.contains(Scope.ALL_CLASS_FILES)
+ || mScope.contains(Scope.JAVA_LIBRARIES)) {
+ checkClasses(project, main);
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ if (mScope.contains(Scope.GRADLE_FILE)) {
+ checkBuildScripts(project, main);
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ if (mScope.contains(Scope.OTHER)) {
+ List<Detector> checks = mScopeDetectors.get(Scope.OTHER);
+ if (checks != null) {
+ OtherFileVisitor visitor = new OtherFileVisitor(checks);
+ visitor.scan(this, project, main);
+ }
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ if (project == main && mScope.contains(Scope.PROGUARD_FILE) &&
+ project.isAndroidProject()) {
+ checkProGuard(project, main);
+ }
+
+ if (project == main && mScope.contains(Scope.PROPERTY_FILE)) {
+ checkProperties(project, main);
+ }
+ }
+
+ private void checkBuildScripts(Project project, Project main) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.GRADLE_FILE);
+ if (detectors != null) {
+ List<File> files = project.getSubset();
+ if (files == null) {
+ files = project.getGradleBuildScripts();
+ }
+ for (File file : files) {
+ Context context = new Context(this, project, main, file);
+ fireEvent(EventType.SCANNING_FILE, context);
+ for (Detector detector : detectors) {
+ if (detector.appliesTo(context, file)) {
+ detector.beforeCheckFile(context);
+ detector.visitBuildScript(context, Maps.<String, Object>newHashMap());
+ detector.afterCheckFile(context);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkProGuard(Project project, Project main) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE);
+ if (detectors != null) {
+ List<File> files = project.getProguardFiles();
+ for (File file : files) {
+ Context context = new Context(this, project, main, file);
+ fireEvent(EventType.SCANNING_FILE, context);
+ for (Detector detector : detectors) {
+ if (detector.appliesTo(context, file)) {
+ detector.beforeCheckFile(context);
+ detector.run(context);
+ detector.afterCheckFile(context);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkProperties(Project project, Project main) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.PROPERTY_FILE);
+ if (detectors != null) {
+ checkPropertyFile(project, main, detectors, FN_LOCAL_PROPERTIES);
+ checkPropertyFile(project, main, detectors, FD_GRADLE_WRAPPER + separator +
+ FN_GRADLE_WRAPPER_PROPERTIES);
+ }
+ }
+
+ private void checkPropertyFile(Project project, Project main, List<Detector> detectors,
+ String relativePath) {
+ File file = new File(project.getDir(), relativePath);
+ if (file.exists()) {
+ Context context = new Context(this, project, main, file);
+ fireEvent(EventType.SCANNING_FILE, context);
+ for (Detector detector : detectors) {
+ if (detector.appliesTo(context, file)) {
+ detector.beforeCheckFile(context);
+ detector.run(context);
+ detector.afterCheckFile(context);
+ }
+ }
+ }
+ }
+
+ /** True if execution has been canceled */
+ boolean isCanceled() {
+ return mCanceled;
+ }
+
+ /**
+ * Returns the super class for the given class name,
+ * which should be in VM format (e.g. java/lang/Integer, not java.lang.Integer).
+ * If the super class is not known, returns null. This can happen if
+ * the given class is not a known class according to the project or its
+ * libraries, for example because it refers to one of the core libraries which
+ * are not analyzed by lint.
+ *
+ * @param name the fully qualified class name
+ * @return the corresponding super class name (in VM format), or null if not known
+ */
+ @Nullable
+ public String getSuperClass(@NonNull String name) {
+ return mClient.getSuperClass(mCurrentProject, name);
+ }
+
+ /**
+ * Returns true if the given class is a subclass of the given super class.
+ *
+ * @param classNode the class to check whether it is a subclass of the given
+ * super class name
+ * @param superClassName the fully qualified super class name (in VM format,
+ * e.g. java/lang/Integer, not java.lang.Integer.
+ * @return true if the given class is a subclass of the given super class
+ */
+ public boolean isSubclassOf(@NonNull ClassNode classNode, @NonNull String superClassName) {
+ if (superClassName.equals(classNode.superName)) {
+ return true;
+ }
+
+ if (mCurrentProject != null) {
+ Boolean isSub = mClient.isSubclassOf(mCurrentProject, classNode.name, superClassName);
+ if (isSub != null) {
+ return isSub;
+ }
+ }
+
+ String className = classNode.name;
+ while (className != null) {
+ if (className.equals(superClassName)) {
+ return true;
+ }
+ className = getSuperClass(className);
+ }
+
+ return false;
+ }
+ @Nullable
+ private static List<Detector> union(
+ @Nullable List<Detector> list1,
+ @Nullable List<Detector> list2) {
+ if (list1 == null) {
+ return list2;
+ } else if (list2 == null) {
+ return list1;
+ } else {
+ // Use set to pick out unique detectors, since it's possible for there to be overlap,
+ // e.g. the DuplicateIdDetector registers both a cross-resource issue and a
+ // single-file issue, so it shows up on both scope lists:
+ Set<Detector> set = new HashSet<Detector>(list1.size() + list2.size());
+ set.addAll(list1);
+ set.addAll(list2);
+
+ return new ArrayList<Detector>(set);
+ }
+ }
+
+ /** Check the classes in this project (and if applicable, in any library projects */
+ private void checkClasses(Project project, Project main) {
+ List<File> files = project.getSubset();
+ if (files != null) {
+ checkIndividualClassFiles(project, main, files);
+ return;
+ }
+
+ // We need to read in all the classes up front such that we can initialize
+ // the parent chains (such that for example for a virtual dispatch, we can
+ // also check the super classes).
+
+ List<File> libraries = project.getJavaLibraries(false);
+ List<ClassEntry> libraryEntries = ClassEntry.fromClassPath(mClient, libraries, true);
+
+ List<File> classFolders = project.getJavaClassFolders();
+ List<ClassEntry> classEntries;
+ if (classFolders.isEmpty()) {
+ String message = String.format("No `.class` files were found in project \"%1$s\", "
+ + "so none of the classfile based checks could be run. "
+ + "Does the project need to be built first?", project.getName());
+ Location location = Location.create(project.getDir());
+ mClient.report(new Context(this, project, main, project.getDir()),
+ IssueRegistry.LINT_ERROR,
+ project.getConfiguration(this).getSeverity(IssueRegistry.LINT_ERROR),
+ location, message, TextFormat.RAW);
+ classEntries = Collections.emptyList();
+ } else {
+ classEntries = ClassEntry.fromClassPath(mClient, classFolders, true);
+ }
+
+ // Actually run the detectors. Libraries should be called before the
+ // main classes.
+ runClassDetectors(Scope.JAVA_LIBRARIES, libraryEntries, project, main);
+
+ if (mCanceled) {
+ return;
+ }
+
+ runClassDetectors(Scope.CLASS_FILE, classEntries, project, main);
+ runClassDetectors(Scope.ALL_CLASS_FILES, classEntries, project, main);
+ }
+
+ private void checkIndividualClassFiles(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull List<File> files) {
+ List<File> classFiles = Lists.newArrayListWithExpectedSize(files.size());
+ List<File> classFolders = project.getJavaClassFolders();
+ if (!classFolders.isEmpty()) {
+ for (File file : files) {
+ String path = file.getPath();
+ if (file.isFile() && path.endsWith(DOT_CLASS)) {
+ classFiles.add(file);
+ }
+ }
+ }
+
+ List<ClassEntry> entries = ClassEntry.fromClassFiles(mClient, classFiles, classFolders,
+ true);
+ if (!entries.isEmpty()) {
+ Collections.sort(entries);
+ runClassDetectors(Scope.CLASS_FILE, entries, project, main);
+ }
+ }
+
+ /**
+ * Stack of {@link ClassNode} nodes for outer classes of the currently
+ * processed class, including that class itself. Populated by
+ * {@link #runClassDetectors(Scope, List, Project, Project)} and used by
+ * {@link #getOuterClassNode(ClassNode)}
+ */
+ private Deque<ClassNode> mOuterClasses;
+
+ private void runClassDetectors(Scope scope, List<ClassEntry> entries,
+ Project project, Project main) {
+ if (mScope.contains(scope)) {
+ List<Detector> classDetectors = mScopeDetectors.get(scope);
+ if (classDetectors != null && !classDetectors.isEmpty() && !entries.isEmpty()) {
+ AsmVisitor visitor = new AsmVisitor(mClient, classDetectors);
+
+ String sourceContents = null;
+ String sourceName = "";
+ mOuterClasses = new ArrayDeque<ClassNode>();
+ ClassEntry prev = null;
+ for (ClassEntry entry : entries) {
+ if (prev != null && prev.compareTo(entry) == 0) {
+ // Duplicate entries for some reason: ignore
+ continue;
+ }
+ prev = entry;
+
+ ClassReader reader;
+ ClassNode classNode;
+ try {
+ reader = new ClassReader(entry.bytes);
+ classNode = new ClassNode();
+ reader.accept(classNode, 0 /* flags */);
+ } catch (Throwable t) {
+ mClient.log(null, "Error processing %1$s: broken class file?",
+ entry.path());
+ continue;
+ }
+
+ ClassNode peek;
+ while ((peek = mOuterClasses.peek()) != null) {
+ if (classNode.name.startsWith(peek.name)) {
+ break;
+ } else {
+ mOuterClasses.pop();
+ }
+ }
+ mOuterClasses.push(classNode);
+
+ if (isSuppressed(null, classNode)) {
+ // Class was annotated with suppress all -- no need to look any further
+ continue;
+ }
+
+ if (sourceContents != null) {
+ // Attempt to reuse the source buffer if initialized
+ // This means making sure that the source files
+ // foo/bar/MyClass and foo/bar/MyClass$Bar
+ // and foo/bar/MyClass$3 and foo/bar/MyClass$3$1 have the same prefix.
+ String newName = classNode.name;
+ int newRootLength = newName.indexOf('$');
+ if (newRootLength == -1) {
+ newRootLength = newName.length();
+ }
+ int oldRootLength = sourceName.indexOf('$');
+ if (oldRootLength == -1) {
+ oldRootLength = sourceName.length();
+ }
+ if (newRootLength != oldRootLength ||
+ !sourceName.regionMatches(0, newName, 0, newRootLength)) {
+ sourceContents = null;
+ }
+ }
+
+ ClassContext context = new ClassContext(this, project, main,
+ entry.file, entry.jarFile, entry.binDir, entry.bytes,
+ classNode, scope == Scope.JAVA_LIBRARIES /*fromLibrary*/,
+ sourceContents);
+
+ try {
+ visitor.runClassDetectors(context);
+ } catch (Exception e) {
+ mClient.log(e, null);
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ sourceContents = context.getSourceContents(false/*read*/);
+ sourceName = classNode.name;
+ }
+
+ mOuterClasses = null;
+ }
+ }
+ }
+
+ /** Returns the outer class node of the given class node
+ * @param classNode the inner class node
+ * @return the outer class node */
+ public ClassNode getOuterClassNode(@NonNull ClassNode classNode) {
+ String outerName = classNode.outerClass;
+
+ Iterator<ClassNode> iterator = mOuterClasses.iterator();
+ while (iterator.hasNext()) {
+ ClassNode node = iterator.next();
+ if (outerName != null) {
+ if (node.name.equals(outerName)) {
+ return node;
+ }
+ } else if (node == classNode) {
+ return iterator.hasNext() ? iterator.next() : null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link ClassNode} corresponding to the given type, if possible, or null
+ *
+ * @param type the fully qualified type, using JVM signatures (/ and $, not . as path
+ * separators)
+ * @param flags the ASM flags to pass to the {@link ClassReader}, normally 0 but can
+ * for example be {@link ClassReader#SKIP_CODE} and/oor
+ * {@link ClassReader#SKIP_DEBUG}
+ * @return the class node for the type, or null
+ */
+ @Nullable
+ public ClassNode findClass(@NonNull ClassContext context, @NonNull String type, int flags) {
+ String relative = type.replace('/', File.separatorChar) + DOT_CLASS;
+ File classFile = findClassFile(context.getProject(), relative);
+ if (classFile != null) {
+ if (classFile.getPath().endsWith(DOT_JAR)) {
+ // TODO: Handle .jar files
+ return null;
+ }
+
+ try {
+ byte[] bytes = mClient.readBytes(classFile);
+ ClassReader reader = new ClassReader(bytes);
+ ClassNode classNode = new ClassNode();
+ reader.accept(classNode, flags);
+
+ return classNode;
+ } catch (Throwable t) {
+ mClient.log(null, "Error processing %1$s: broken class file?",
+ classFile.getPath());
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private File findClassFile(@NonNull Project project, String relativePath) {
+ for (File root : mClient.getJavaClassFolders(project)) {
+ File path = new File(root, relativePath);
+ if (path.exists()) {
+ return path;
+ }
+ }
+ // Search in the libraries
+ for (File root : mClient.getJavaLibraries(project, true)) {
+ // TODO: Handle .jar files!
+ //if (root.getPath().endsWith(DOT_JAR)) {
+ //}
+
+ File path = new File(root, relativePath);
+ if (path.exists()) {
+ return path;
+ }
+ }
+
+ // Search dependent projects
+ for (Project library : project.getDirectLibraries()) {
+ File path = findClassFile(library, relativePath);
+ if (path != null) {
+ return path;
+ }
+ }
+
+ return null;
+ }
+
+ private void checkJava(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull List<File> sourceFolders,
+ @NonNull List<Detector> checks) {
+ JavaParser javaParser = mClient.getJavaParser(project);
+ if (javaParser == null) {
+ mClient.log(null, "No java parser provided to lint: not running Java checks");
+ return;
+ }
+
+ assert !checks.isEmpty();
+
+ // Gather all Java source files in a single pass; more efficient.
+ List<File> sources = new ArrayList<File>(100);
+ for (File folder : sourceFolders) {
+ gatherJavaFiles(folder, sources);
+ }
+ if (!sources.isEmpty()) {
+ JavaVisitor visitor = new JavaVisitor(javaParser, checks);
+ List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(sources.size());
+ for (File file : sources) {
+ JavaContext context = new JavaContext(this, project, main, file, javaParser);
+ contexts.add(context);
+ }
+
+ visitor.prepare(contexts);
+ for (JavaContext context : contexts) {
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context);
+ if (mCanceled) {
+ return;
+ }
+ }
+ visitor.dispose();
+ }
+ }
+
+ private void checkIndividualJavaFiles(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull List<Detector> checks,
+ @NonNull List<File> files) {
+
+ JavaParser javaParser = mClient.getJavaParser(project);
+ if (javaParser == null) {
+ mClient.log(null, "No java parser provided to lint: not running Java checks");
+ return;
+ }
+
+ JavaVisitor visitor = new JavaVisitor(javaParser, checks);
+
+ List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(files.size());
+ for (File file : files) {
+ if (file.isFile() && file.getPath().endsWith(DOT_JAVA)) {
+ contexts.add(new JavaContext(this, project, main, file, javaParser));
+ }
+ }
+
+ if (contexts.isEmpty()) {
+ return;
+ }
+
+ visitor.prepare(contexts);
+
+ if (mCanceled) {
+ return;
+ }
+
+ for (JavaContext context : contexts) {
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context);
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ visitor.dispose();
+ }
+
+ private static void gatherJavaFiles(@NonNull File dir, @NonNull List<File> result) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile() && file.getName().endsWith(".java")) { //$NON-NLS-1$
+ result.add(file);
+ } else if (file.isDirectory()) {
+ gatherJavaFiles(file, result);
+ }
+ }
+ }
+ }
+
+ private ResourceFolderType mCurrentFolderType;
+ private List<ResourceXmlDetector> mCurrentXmlDetectors;
+ private List<Detector> mCurrentBinaryDetectors;
+ private ResourceVisitor mCurrentVisitor;
+
+ @Nullable
+ private ResourceVisitor getVisitor(
+ @NonNull ResourceFolderType type,
+ @NonNull List<ResourceXmlDetector> checks,
+ @Nullable List<Detector> binaryChecks) {
+ if (type != mCurrentFolderType) {
+ mCurrentFolderType = type;
+
+ // Determine which XML resource detectors apply to the given folder type
+ List<ResourceXmlDetector> applicableXmlChecks =
+ new ArrayList<ResourceXmlDetector>(checks.size());
+ for (ResourceXmlDetector check : checks) {
+ if (check.appliesTo(type)) {
+ applicableXmlChecks.add(check);
+ }
+ }
+ List<Detector> applicableBinaryChecks = null;
+ if (binaryChecks != null) {
+ applicableBinaryChecks = new ArrayList<Detector>(binaryChecks.size());
+ for (Detector check : binaryChecks) {
+ if (check.appliesTo(type)) {
+ applicableBinaryChecks.add(check);
+ }
+ }
+ }
+
+ // If the list of detectors hasn't changed, then just use the current visitor!
+ if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableXmlChecks)
+ && Objects.equal(mCurrentBinaryDetectors, applicableBinaryChecks)) {
+ return mCurrentVisitor;
+ }
+
+ mCurrentXmlDetectors = applicableXmlChecks;
+ mCurrentBinaryDetectors = applicableBinaryChecks;
+
+ if (applicableXmlChecks.isEmpty()
+ && (applicableBinaryChecks == null || applicableBinaryChecks.isEmpty())) {
+ mCurrentVisitor = null;
+ return null;
+ }
+
+ XmlParser parser = mClient.getXmlParser();
+ if (parser != null) {
+ mCurrentVisitor = new ResourceVisitor(parser, applicableXmlChecks,
+ applicableBinaryChecks);
+ } else {
+ mCurrentVisitor = null;
+ }
+ }
+
+ return mCurrentVisitor;
+ }
+
+ private void checkResFolder(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull File res,
+ @NonNull List<ResourceXmlDetector> xmlChecks,
+ @Nullable List<Detector> dirChecks,
+ @Nullable List<Detector> binaryChecks) {
+ File[] resourceDirs = res.listFiles();
+ if (resourceDirs == null) {
+ return;
+ }
+
+ // Sort alphabetically such that we can process related folder types at the
+ // same time, and to have a defined behavior such that detectors can rely on
+ // predictable ordering, e.g. layouts are seen before menus are seen before
+ // values, etc (l < m < v).
+
+ Arrays.sort(resourceDirs);
+ for (File dir : resourceDirs) {
+ ResourceFolderType type = ResourceFolderType.getFolderType(dir.getName());
+ if (type != null) {
+ checkResourceFolder(project, main, dir, type, xmlChecks, dirChecks, binaryChecks);
+ }
+
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+
+ private void checkResourceFolder(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull File dir,
+ @NonNull ResourceFolderType type,
+ @NonNull List<ResourceXmlDetector> xmlChecks,
+ @Nullable List<Detector> dirChecks,
+ @Nullable List<Detector> binaryChecks) {
+
+ // Process the resource folder
+
+ if (dirChecks != null && !dirChecks.isEmpty()) {
+ ResourceContext context = new ResourceContext(this, project, main, dir, type);
+ String folderName = dir.getName();
+ fireEvent(EventType.SCANNING_FILE, context);
+ for (Detector check : dirChecks) {
+ if (check.appliesTo(type)) {
+ check.beforeCheckFile(context);
+ check.checkFolder(context, folderName);
+ check.afterCheckFile(context);
+ }
+ }
+ if (binaryChecks == null && xmlChecks.isEmpty()) {
+ return;
+ }
+ }
+
+ File[] files = dir.listFiles();
+ if (files == null || files.length <= 0) {
+ return;
+ }
+
+ ResourceVisitor visitor = getVisitor(type, xmlChecks, binaryChecks);
+ if (visitor != null) { // if not, there are no applicable rules in this folder
+ // Process files in alphabetical order, to ensure stable output
+ // (for example for the duplicate resource detector)
+ Arrays.sort(files);
+ for (File file : files) {
+ if (LintUtils.isXmlFile(file)) {
+ XmlContext context = new XmlContext(this, project, main, file, type,
+ visitor.getParser());
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context, file);
+ } else if (binaryChecks != null && (LintUtils.isBitmapFile(file) ||
+ type == ResourceFolderType.RAW)) {
+ ResourceContext context = new ResourceContext(this, project, main, file, type);
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitBinaryResource(context);
+ }
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
+
+ /** Checks individual resources */
+ private void checkIndividualResources(
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull List<ResourceXmlDetector> xmlDetectors,
+ @Nullable List<Detector> dirChecks,
+ @Nullable List<Detector> binaryChecks,
+ @NonNull List<File> files) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ // Is it a resource folder?
+ ResourceFolderType type = ResourceFolderType.getFolderType(file.getName());
+ if (type != null && new File(file.getParentFile(), RES_FOLDER).exists()) {
+ // Yes.
+ checkResourceFolder(project, main, file, type, xmlDetectors, dirChecks,
+ binaryChecks);
+ } else if (file.getName().equals(RES_FOLDER)) { // Is it the res folder?
+ // Yes
+ checkResFolder(project, main, file, xmlDetectors, dirChecks, binaryChecks);
+ } else {
+ mClient.log(null, "Unexpected folder %1$s; should be project, " +
+ "\"res\" folder or resource folder", file.getPath());
+ }
+ } else if (file.isFile() && LintUtils.isXmlFile(file)) {
+ // Yes, find out its resource type
+ String folderName = file.getParentFile().getName();
+ ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
+ if (type != null) {
+ ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks);
+ if (visitor != null) {
+ XmlContext context = new XmlContext(this, project, main, file, type,
+ visitor.getParser());
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context, file);
+ }
+ }
+ } else if (binaryChecks != null && file.isFile() && LintUtils.isBitmapFile(file)) {
+ // Yes, find out its resource type
+ String folderName = file.getParentFile().getName();
+ ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
+ if (type != null) {
+ ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks);
+ if (visitor != null) {
+ ResourceContext context = new ResourceContext(this, project, main, file,
+ type);
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitBinaryResource(context);
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a listener to be notified of lint progress
+ *
+ * @param listener the listener to be added
+ */
+ public void addLintListener(@NonNull LintListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<LintListener>(1);
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener such that it is no longer notified of progress
+ *
+ * @param listener the listener to be removed
+ */
+ public void removeLintListener(@NonNull LintListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.isEmpty()) {
+ mListeners = null;
+ }
+ }
+
+ /** Notifies listeners, if any, that the given event has occurred */
+ private void fireEvent(@NonNull LintListener.EventType type, @Nullable Context context) {
+ if (mListeners != null) {
+ for (LintListener listener : mListeners) {
+ listener.update(this, type, context);
+ }
+ }
+ }
+
+ /**
+ * Wrapper around the lint client. This sits in the middle between a
+ * detector calling for example {@link LintClient#report} and
+ * the actual embedding tool, and performs filtering etc such that detectors
+ * and lint clients don't have to make sure they check for ignored issues or
+ * filtered out warnings.
+ */
+ private class LintClientWrapper extends LintClient {
+ @NonNull
+ private final LintClient mDelegate;
+
+ public LintClientWrapper(@NonNull LintClient delegate) {
+ super(getClientName());
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void report(
+ @NonNull Context context,
+ @NonNull Issue issue,
+ @NonNull Severity severity,
+ @Nullable Location location,
+ @NonNull String message,
+ @NonNull TextFormat format) {
+ assert mCurrentProject != null;
+ if (!mCurrentProject.getReportIssues()) {
+ return;
+ }
+
+ Configuration configuration = context.getConfiguration();
+ if (!configuration.isEnabled(issue)) {
+ if (issue != IssueRegistry.PARSER_ERROR && issue != IssueRegistry.LINT_ERROR) {
+ mDelegate.log(null, "Incorrect detector reported disabled issue %1$s",
+ issue.toString());
+ }
+ return;
+ }
+
+ if (configuration.isIgnored(context, issue, location, message)) {
+ return;
+ }
+
+ if (severity == Severity.IGNORE) {
+ return;
+ }
+
+ mDelegate.report(context, issue, severity, location, message, format);
+ }
+
+ // Everything else just delegates to the embedding lint client
+
+ @Override
+ @NonNull
+ public Configuration getConfiguration(@NonNull Project project,
+ @Nullable LintDriver driver) {
+ return mDelegate.getConfiguration(project, driver);
+ }
+
+ @Override
+ public void log(@NonNull Severity severity, @Nullable Throwable exception,
+ @Nullable String format, @Nullable Object... args) {
+ mDelegate.log(exception, format, args);
+ }
+
+ @Override
+ @NonNull
+ public String readFile(@NonNull File file) {
+ return mDelegate.readFile(file);
+ }
+
+ @Override
+ @NonNull
+ public byte[] readBytes(@NonNull File file) throws IOException {
+ return mDelegate.readBytes(file);
+ }
+
+ @Override
+ @NonNull
+ public List<File> getJavaSourceFolders(@NonNull Project project) {
+ return mDelegate.getJavaSourceFolders(project);
+ }
+
+ @Override
+ @NonNull
+ public List<File> getJavaClassFolders(@NonNull Project project) {
+ return mDelegate.getJavaClassFolders(project);
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaLibraries(@NonNull Project project, boolean includeProvided) {
+ return mDelegate.getJavaLibraries(project, includeProvided);
+ }
+
+ @NonNull
+ @Override
+ public List<File> getTestSourceFolders(@NonNull Project project) {
+ return mDelegate.getTestSourceFolders(project);
+ }
+
+ @Override
+ public Collection<Project> getKnownProjects() {
+ return mDelegate.getKnownProjects();
+ }
+
+ @Nullable
+ @Override
+ public BuildToolInfo getBuildTools(@NonNull Project project) {
+ return mDelegate.getBuildTools(project);
+ }
+
+ @NonNull
+ @Override
+ public Map<String, String> createSuperClassMap(@NonNull Project project) {
+ return mDelegate.createSuperClassMap(project);
+ }
+
+ @NonNull
+ @Override
+ public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() {
+ return mDelegate.getResourceVisibilityProvider();
+ }
+
+ @Override
+ @NonNull
+ public List<File> getResourceFolders(@NonNull Project project) {
+ return mDelegate.getResourceFolders(project);
+ }
+
+ @Override
+ @Nullable
+ public XmlParser getXmlParser() {
+ return mDelegate.getXmlParser();
+ }
+
+ @Override
+ @NonNull
+ public Class<? extends Detector> replaceDetector(
+ @NonNull Class<? extends Detector> detectorClass) {
+ return mDelegate.replaceDetector(detectorClass);
+ }
+
+ @Override
+ @NonNull
+ public SdkInfo getSdkInfo(@NonNull Project project) {
+ return mDelegate.getSdkInfo(project);
+ }
+
+ @Override
+ @NonNull
+ public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
+ return mDelegate.getProject(dir, referenceDir);
+ }
+
+ @Nullable
+ @Override
+ public JavaParser getJavaParser(@Nullable Project project) {
+ return mDelegate.getJavaParser(project);
+ }
+
+ @Override
+ public File findResource(@NonNull String relativePath) {
+ return mDelegate.findResource(relativePath);
+ }
+
+ @Override
+ @Nullable
+ public File getCacheDir(boolean create) {
+ return mDelegate.getCacheDir(create);
+ }
+
+ @Override
+ @NonNull
+ protected ClassPathInfo getClassPath(@NonNull Project project) {
+ return mDelegate.getClassPath(project);
+ }
+
+ @Override
+ public void log(@Nullable Throwable exception, @Nullable String format,
+ @Nullable Object... args) {
+ mDelegate.log(exception, format, args);
+ }
+
+ @Override
+ @Nullable
+ public File getSdkHome() {
+ return mDelegate.getSdkHome();
+ }
+
+ @Override
+ @NonNull
+ public IAndroidTarget[] getTargets() {
+ return mDelegate.getTargets();
+ }
+
+ @Nullable
+ @Override
+ public AndroidSdkHandler getSdk() {
+ return mDelegate.getSdk();
+ }
+
+ @Nullable
+ @Override
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ return mDelegate.getCompileTarget(project);
+ }
+
+ @Override
+ public int getHighestKnownApiLevel() {
+ return mDelegate.getHighestKnownApiLevel();
+ }
+
+ @Override
+ @Nullable
+ public String getSuperClass(@NonNull Project project, @NonNull String name) {
+ return mDelegate.getSuperClass(project, name);
+ }
+
+ @Override
+ @Nullable
+ public Boolean isSubclassOf(@NonNull Project project, @NonNull String name,
+ @NonNull String superClassName) {
+ return mDelegate.isSubclassOf(project, name, superClassName);
+ }
+
+ @Override
+ @NonNull
+ public String getProjectName(@NonNull Project project) {
+ return mDelegate.getProjectName(project);
+ }
+
+ @Override
+ public boolean isGradleProject(Project project) {
+ return mDelegate.isGradleProject(project);
+ }
+
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return mDelegate.createProject(dir, referenceDir);
+ }
+
+ @NonNull
+ @Override
+ public List<File> findGlobalRuleJars() {
+ return mDelegate.findGlobalRuleJars();
+ }
+
+ @NonNull
+ @Override
+ public List<File> findRuleJars(@NonNull Project project) {
+ return mDelegate.findRuleJars(project);
+ }
+
+ @Override
+ public boolean isProjectDirectory(@NonNull File dir) {
+ return mDelegate.isProjectDirectory(dir);
+ }
+
+ @Override
+ public void registerProject(@NonNull File dir, @NonNull Project project) {
+ log(Severity.WARNING, null, "Too late to register projects");
+ mDelegate.registerProject(dir, project);
+ }
+
+ @Override
+ public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) {
+ return mDelegate.addCustomLintRules(registry);
+ }
+
+ @NonNull
+ @Override
+ public List<File> getAssetFolders(@NonNull Project project) {
+ return mDelegate.getAssetFolders(project);
+ }
+
+ @Override
+ public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) {
+ return mDelegate.createUrlClassLoader(urls, parent);
+ }
+
+ @Override
+ public boolean checkForSuppressComments() {
+ return mDelegate.checkForSuppressComments();
+ }
+
+ @Override
+ public boolean supportsProjectResources() {
+ return mDelegate.supportsProjectResources();
+ }
+
+ @Nullable
+ @Override
+ public AbstractResourceRepository getProjectResources(Project project,
+ boolean includeDependencies) {
+ return mDelegate.getProjectResources(project, includeDependencies);
+ }
+
+ @NonNull
+ @Override
+ public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
+ return mDelegate.createResourceItemHandle(item);
+ }
+
+ @Nullable
+ @Override
+ public URLConnection openConnection(@NonNull URL url) throws IOException {
+ return mDelegate.openConnection(url);
+ }
+
+ @Override
+ public void closeConnection(@NonNull URLConnection connection) throws IOException {
+ mDelegate.closeConnection(connection);
+ }
+ }
+
+ /**
+ * Requests another pass through the data for the given detector. This is
+ * typically done when a detector needs to do more expensive computation,
+ * but it only wants to do this once it <b>knows</b> that an error is
+ * present, or once it knows more specifically what to check for.
+ *
+ * @param detector the detector that should be included in the next pass.
+ * Note that the lint runner may refuse to run more than a couple
+ * of runs.
+ * @param scope the scope to be revisited. This must be a subset of the
+ * current scope ({@link #getScope()}, and it is just a performance hint;
+ * in particular, the detector should be prepared to be called on other
+ * scopes as well (since they may have been requested by other detectors).
+ * You can pall null to indicate "all".
+ */
+ public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) {
+ if (mRepeatingDetectors == null) {
+ mRepeatingDetectors = new ArrayList<Detector>();
+ }
+ mRepeatingDetectors.add(detector);
+
+ if (scope != null) {
+ if (mRepeatScope == null) {
+ mRepeatScope = scope;
+ } else {
+ mRepeatScope = EnumSet.copyOf(mRepeatScope);
+ mRepeatScope.addAll(scope);
+ }
+ } else {
+ mRepeatScope = Scope.ALL;
+ }
+ }
+
+ // Unfortunately, ASMs nodes do not extend a common DOM node type with parent
+ // pointers, so we have to have multiple methods which pass in each type
+ // of node (class, method, field) to be checked.
+
+ /**
+ * Returns whether the given issue is suppressed in the given method.
+ *
+ * @param issue the issue to be checked, or null to just check for "all"
+ * @param classNode the class containing the issue
+ * @param method the method containing the issue
+ * @param instruction the instruction within the method, if any
+ * @return true if there is a suppress annotation covering the specific
+ * issue on this method
+ */
+ public boolean isSuppressed(
+ @Nullable Issue issue,
+ @NonNull ClassNode classNode,
+ @NonNull MethodNode method,
+ @Nullable AbstractInsnNode instruction) {
+ if (method.invisibleAnnotations != null) {
+ @SuppressWarnings("unchecked")
+ List<AnnotationNode> annotations = method.invisibleAnnotations;
+ return isSuppressed(issue, annotations);
+ }
+
+ // Initializations of fields end up placed in generated methods (<init>
+ // for members and <clinit> for static fields).
+ if (instruction != null && method.name.charAt(0) == '<') {
+ AbstractInsnNode next = LintUtils.getNextInstruction(instruction);
+ if (next != null && next.getType() == AbstractInsnNode.FIELD_INSN) {
+ FieldInsnNode fieldRef = (FieldInsnNode) next;
+ FieldNode field = findField(classNode, fieldRef.owner, fieldRef.name);
+ if (field != null && isSuppressed(issue, field)) {
+ return true;
+ }
+ } else if (classNode.outerClass != null && classNode.outerMethod == null
+ && isAnonymousClass(classNode)) {
+ if (isSuppressed(issue, classNode)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Nullable
+ private static MethodInsnNode findConstructorInvocation(
+ @NonNull MethodNode method,
+ @NonNull String className) {
+ InsnList nodes = method.instructions;
+ for (int i = 0, n = nodes.size(); i < n; i++) {
+ AbstractInsnNode instruction = nodes.get(i);
+ if (instruction.getOpcode() == Opcodes.INVOKESPECIAL) {
+ MethodInsnNode call = (MethodInsnNode) instruction;
+ if (className.equals(call.owner)) {
+ return call;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private FieldNode findField(
+ @NonNull ClassNode classNode,
+ @NonNull String owner,
+ @NonNull String name) {
+ ClassNode current = classNode;
+ while (current != null) {
+ if (owner.equals(current.name)) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List fieldList = current.fields;
+ for (Object f : fieldList) {
+ FieldNode field = (FieldNode) f;
+ if (field.name.equals(name)) {
+ return field;
+ }
+ }
+ return null;
+ }
+ current = getOuterClassNode(current);
+ }
+ return null;
+ }
+
+ @Nullable
+ private MethodNode findMethod(
+ @NonNull ClassNode classNode,
+ @NonNull String name,
+ boolean includeInherited) {
+ ClassNode current = classNode;
+ while (current != null) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List methodList = current.methods;
+ for (Object f : methodList) {
+ MethodNode method = (MethodNode) f;
+ if (method.name.equals(name)) {
+ return method;
+ }
+ }
+
+ if (includeInherited) {
+ current = getOuterClassNode(current);
+ } else {
+ break;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether the given issue is suppressed for the given field.
+ *
+ * @param issue the issue to be checked, or null to just check for "all"
+ * @param field the field potentially annotated with a suppress annotation
+ * @return true if there is a suppress annotation covering the specific
+ * issue on this field
+ */
+ @SuppressWarnings("MethodMayBeStatic") // API; reserve need to require driver state later
+ public boolean isSuppressed(@Nullable Issue issue, @NonNull FieldNode field) {
+ if (field.invisibleAnnotations != null) {
+ @SuppressWarnings("unchecked")
+ List<AnnotationNode> annotations = field.invisibleAnnotations;
+ return isSuppressed(issue, annotations);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the given issue is suppressed in the given class.
+ *
+ * @param issue the issue to be checked, or null to just check for "all"
+ * @param classNode the class containing the issue
+ * @return true if there is a suppress annotation covering the specific
+ * issue in this class
+ */
+ public boolean isSuppressed(@Nullable Issue issue, @NonNull ClassNode classNode) {
+ if (classNode.invisibleAnnotations != null) {
+ @SuppressWarnings("unchecked")
+ List<AnnotationNode> annotations = classNode.invisibleAnnotations;
+ return isSuppressed(issue, annotations);
+ }
+
+ if (classNode.outerClass != null && classNode.outerMethod == null
+ && isAnonymousClass(classNode)) {
+ ClassNode outer = getOuterClassNode(classNode);
+ if (outer != null) {
+ MethodNode m = findMethod(outer, CONSTRUCTOR_NAME, false);
+ if (m != null) {
+ MethodInsnNode call = findConstructorInvocation(m, classNode.name);
+ if (call != null) {
+ if (isSuppressed(issue, outer, m, call)) {
+ return true;
+ }
+ }
+ }
+ m = findMethod(outer, CLASS_CONSTRUCTOR, false);
+ if (m != null) {
+ MethodInsnNode call = findConstructorInvocation(m, classNode.name);
+ if (call != null) {
+ if (isSuppressed(issue, outer, m, call)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isSuppressed(@Nullable Issue issue, List<AnnotationNode> annotations) {
+ for (AnnotationNode annotation : annotations) {
+ String desc = annotation.desc;
+
+ // We could obey @SuppressWarnings("all") too, but no need to look for it
+ // because that annotation only has source retention.
+
+ if (desc.endsWith(SUPPRESS_LINT_VMSIG)) {
+ if (annotation.values != null) {
+ for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
+ String key = (String) annotation.values.get(i);
+ if (key.equals("value")) { //$NON-NLS-1$
+ Object value = annotation.values.get(i + 1);
+ if (value instanceof String) {
+ String id = (String) value;
+ if (matches(issue, id)) {
+ return true;
+ }
+ } else if (value instanceof List) {
+ @SuppressWarnings("rawtypes")
+ List list = (List) value;
+ for (Object v : list) {
+ if (v instanceof String) {
+ String id = (String) v;
+ if (matches(issue, id)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean matches(@Nullable Issue issue, @NonNull String id) {
+ if (id.equalsIgnoreCase(SUPPRESS_ALL)) {
+ return true;
+ }
+
+ if (issue != null) {
+ String issueId = issue.getId();
+ if (id.equalsIgnoreCase(issueId)) {
+ return true;
+ }
+ if (id.startsWith(STUDIO_ID_PREFIX)
+ && id.regionMatches(true, STUDIO_ID_PREFIX.length(), issueId, 0, issueId.length())
+ && id.substring(STUDIO_ID_PREFIX.length()).equalsIgnoreCase(issueId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the given issue is suppressed in the given parse tree node.
+ *
+ * @param context the context for the source being scanned
+ * @param issue the issue to be checked, or null to just check for "all"
+ * @param scope the AST node containing the issue
+ * @return true if there is a suppress annotation covering the specific
+ * issue in this class
+ */
+ public boolean isSuppressed(@Nullable JavaContext context, @NonNull Issue issue,
+ @Nullable Node scope) {
+ boolean checkComments = mClient.checkForSuppressComments() &&
+ context != null && context.containsCommentSuppress();
+ while (scope != null) {
+ Class<? extends Node> type = scope.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == VariableDefinition.class) {
+ // Variable
+ VariableDefinition declaration = (VariableDefinition) scope;
+ if (isSuppressed(issue, declaration.astModifiers())) {
+ return true;
+ }
+ } else if (type == MethodDeclaration.class) {
+ // Method
+ // Look for annotations on the method
+ MethodDeclaration declaration = (MethodDeclaration) scope;
+ if (isSuppressed(issue, declaration.astModifiers())) {
+ return true;
+ }
+ } else if (type == ConstructorDeclaration.class) {
+ // Constructor
+ // Look for annotations on the method
+ ConstructorDeclaration declaration = (ConstructorDeclaration) scope;
+ if (isSuppressed(issue, declaration.astModifiers())) {
+ return true;
+ }
+ } else if (TypeDeclaration.class.isAssignableFrom(type)) {
+ // Class, annotation, enum, interface
+ TypeDeclaration declaration = (TypeDeclaration) scope;
+ if (isSuppressed(issue, declaration.astModifiers())) {
+ return true;
+ }
+ } else if (type == AnnotationMethodDeclaration.class) {
+ // Look for annotations on the method
+ AnnotationMethodDeclaration declaration = (AnnotationMethodDeclaration) scope;
+ if (isSuppressed(issue, declaration.astModifiers())) {
+ return true;
+ }
+ }
+
+ if (checkComments && context.isSuppressedWithComment(scope, issue)) {
+ return true;
+ }
+
+ scope = scope.getParent();
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the given AST modifier has a suppress annotation for the
+ * given issue (which can be null to check for the "all" annotation)
+ *
+ * @param issue the issue to be checked
+ * @param modifiers the modifier to check
+ * @return true if the issue or all issues should be suppressed for this
+ * modifier
+ */
+ private static boolean isSuppressed(@Nullable Issue issue, @Nullable Modifiers modifiers) {
+ if (modifiers == null) {
+ return false;
+ }
+ StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations();
+ if (annotations == null) {
+ return false;
+ }
+
+ for (Annotation annotation : annotations) {
+ TypeReference t = annotation.astAnnotationTypeReference();
+ String typeName = t.getTypeName();
+ if (typeName.endsWith(SUPPRESS_LINT)
+ || typeName.endsWith("SuppressWarnings")) { //$NON-NLS-1$
+ StrictListAccessor<AnnotationElement, Annotation> values =
+ annotation.astElements();
+ if (values != null) {
+ for (AnnotationElement element : values) {
+ AnnotationValue valueNode = element.astValue();
+ if (valueNode == null) {
+ continue;
+ }
+ if (valueNode instanceof StringLiteral) {
+ StringLiteral literal = (StringLiteral) valueNode;
+ String value = literal.astValue();
+ if (matches(issue, value)) {
+ return true;
+ }
+ } else if (valueNode instanceof ArrayInitializer) {
+ ArrayInitializer array = (ArrayInitializer) valueNode;
+ StrictListAccessor<Expression, ArrayInitializer> expressions =
+ array.astExpressions();
+ if (expressions == null) {
+ continue;
+ }
+ for (Expression arrayElement : expressions) {
+ if (arrayElement instanceof StringLiteral) {
+ String value = ((StringLiteral) arrayElement).astValue();
+ if (matches(issue, value)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the given issue is suppressed in the given XML DOM node.
+ *
+ * @param issue the issue to be checked, or null to just check for "all"
+ * @param node the DOM node containing the issue
+ * @return true if there is a suppress annotation covering the specific
+ * issue in this class
+ */
+ public boolean isSuppressed(@Nullable XmlContext context, @NonNull Issue issue,
+ @Nullable org.w3c.dom.Node node) {
+ if (node instanceof Attr) {
+ node = ((Attr) node).getOwnerElement();
+ }
+ boolean checkComments = mClient.checkForSuppressComments()
+ && context != null && context.containsCommentSuppress();
+ while (node != null) {
+ if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ if (element.hasAttributeNS(TOOLS_URI, ATTR_IGNORE)) {
+ String ignore = element.getAttributeNS(TOOLS_URI, ATTR_IGNORE);
+ if (ignore.indexOf(',') == -1) {
+ if (matches(issue, ignore)) {
+ return true;
+ }
+ } else {
+ for (String id : ignore.split(",")) { //$NON-NLS-1$
+ if (matches(issue, id)) {
+ return true;
+ }
+ }
+ }
+ } else if (checkComments && context.isSuppressedWithComment(node, issue)) {
+ return true;
+ }
+ }
+
+ node = node.getParentNode();
+ }
+
+ return false;
+ }
+
+ private File mCachedFolder = null;
+ private int mCachedFolderVersion = -1;
+ /** Pattern for version qualifiers */
+ private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$"); //$NON-NLS-1$
+
+ /**
+ * Returns the folder version of the given file. For example, for the file values-v14/foo.xml,
+ * it returns 14.
+ *
+ * @param resourceFile the file to be checked
+ * @return the folder version, or -1 if no specific version was specified
+ */
+ public int getResourceFolderVersion(@NonNull File resourceFile) {
+ File parent = resourceFile.getParentFile();
+ if (parent == null) {
+ return -1;
+ }
+ if (parent.equals(mCachedFolder)) {
+ return mCachedFolderVersion;
+ }
+
+ mCachedFolder = parent;
+ mCachedFolderVersion = -1;
+
+ for (String qualifier : QUALIFIER_SPLITTER.split(parent.getName())) {
+ Matcher matcher = VERSION_PATTERN.matcher(qualifier);
+ if (matcher.matches()) {
+ String group = matcher.group(1);
+ assert group != null;
+ mCachedFolderVersion = Integer.parseInt(group);
+ break;
+ }
+ }
+
+ return mCachedFolderVersion;
+ }
+
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintListener.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintListener.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintListener.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintListener.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ResourceVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ResourceVisitor.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ResourceVisitor.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/ResourceVisitor.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlParser.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlParser.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlParser.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/XmlParser.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java
new file mode 100644
index 0000000..3800671
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.annotations.Beta;
+
+/**
+ * A category is a container for related issues.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public final class Category implements Comparable<Category> {
+ private final String mName;
+ private final int mPriority;
+ private final Category mParent;
+
+ /**
+ * Creates a new {@link Category}.
+ *
+ * @param parent the name of a parent category, or null
+ * @param name the name of the category
+ * @param priority a sorting priority, with higher being more important
+ */
+ private Category(
+ @Nullable Category parent,
+ @NonNull String name,
+ int priority) {
+ mParent = parent;
+ mName = name;
+ mPriority = priority;
+ }
+
+ /**
+ * Creates a new top level {@link Category} with the given sorting priority.
+ *
+ * @param name the name of the category
+ * @param priority a sorting priority, with higher being more important
+ * @return a new category
+ */
+ @NonNull
+ public static Category create(@NonNull String name, int priority) {
+ return new Category(null, name, priority);
+ }
+
+ /**
+ * Creates a new top level {@link Category} with the given sorting priority.
+ *
+ * @param parent the name of a parent category, or null
+ * @param name the name of the category
+ * @param priority a sorting priority, with higher being more important
+ * @return a new category
+ */
+ @NonNull
+ public static Category create(@Nullable Category parent, @NonNull String name, int priority) {
+ return new Category(parent, name, priority);
+ }
+
+ /**
+ * Returns the parent category, or null if this is a top level category
+ *
+ * @return the parent category, or null if this is a top level category
+ */
+ public Category getParent() {
+ return mParent;
+ }
+
+ /**
+ * Returns the name of this category
+ *
+ * @return the name of this category
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns a full name for this category. For a top level category, this is just
+ * the {@link #getName()} value, but for nested categories it will include the parent
+ * names as well.
+ *
+ * @return a full name for this category
+ */
+ public String getFullName() {
+ if (mParent != null) {
+ return mParent.getFullName() + ':' + mName;
+ } else {
+ return mName;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Category category = (Category) o;
+
+ //noinspection SimplifiableIfStatement
+ if (!mName.equals(category.mName)) {
+ return false;
+ }
+ return mParent != null ? mParent.equals(category.mParent) : category.mParent == null;
+
+ }
+
+ @Override
+ public String toString() {
+ return getFullName();
+ }
+
+ @Override
+ public int hashCode() {
+ return mName.hashCode();
+ }
+
+ @Override
+ public int compareTo(Category other) {
+ if (other.mPriority == mPriority) {
+ if (mParent == other) {
+ return 1;
+ } else if (other.mParent == this) {
+ return -1;
+ }
+ }
+
+ int delta = other.mPriority - mPriority;
+ if (delta != 0) {
+ return delta;
+ }
+
+ return mName.compareTo(other.mName);
+ }
+
+ /** Issues related to running lint itself */
+ public static final Category LINT = create("Lint", 110);
+
+ /** Issues related to correctness */
+ public static final Category CORRECTNESS = create("Correctness", 100);
+
+ /** Issues related to security */
+ public static final Category SECURITY = create("Security", 90);
+
+ /** Issues related to performance */
+ public static final Category PERFORMANCE = create("Performance", 80);
+
+ /** Issues related to usability */
+ public static final Category USABILITY = create("Usability", 70);
+
+ /** Issues related to accessibility */
+ public static final Category A11Y = create("Accessibility", 60);
+
+ /** Issues related to internationalization */
+ public static final Category I18N = create("Internationalization", 50);
+
+ // Sub categories
+
+ /** Issues related to icons */
+ public static final Category ICONS = create(USABILITY, "Icons", 73);
+
+ /** Issues related to typography */
+ public static final Category TYPOGRAPHY = create(USABILITY, "Typography", 76);
+
+ /** Issues related to messages/strings */
+ public static final Category MESSAGES = create(CORRECTNESS, "Messages", 95);
+
+ /** Issues related to right to left and bidirectional text support */
+ public static final Category RTL = create(I18N, "Bidirectional Text", 40);
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
new file mode 100644
index 0000000..a1283d2
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.EOL_BACKWARD;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Location.SearchDirection;
+import com.android.tools.lint.detector.api.Location.SearchHints;
+import com.android.utils.AsmUtils;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
+
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.LineNumberNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A {@link Context} used when checking .class files.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class ClassContext extends Context {
+ private final File mBinDir;
+ /** The class file DOM root node */
+ private final ClassNode mClassNode;
+ /** The class file byte data */
+ private final byte[] mBytes;
+ /** The source file, if known/found */
+ private File mSourceFile;
+ /** The contents of the source file, if source file is known/found */
+ private String mSourceContents;
+ /** Whether we've searched for the source file (used to avoid repeated failed searches) */
+ private boolean mSearchedForSource;
+ /** If the file is a relative path within a jar file, this is the jar file, otherwise null */
+ private final File mJarFile;
+ /** Whether this class is part of a library (rather than corresponding to one of the
+ * source files in this project */
+ private final boolean mFromLibrary;
+
+ /**
+ * Construct a new {@link ClassContext}
+ *
+ * @param driver the driver running through the checks
+ * @param project the project containing the file being checked
+ * @param main the main project if this project is a library project, or
+ * null if this is not a library project. The main project is the
+ * root project of all library projects, not necessarily the
+ * directly including project.
+ * @param file the file being checked
+ * @param jarFile If the file is a relative path within a jar file, this is
+ * the jar file, otherwise null
+ * @param binDir the root binary directory containing this .class file.
+ * @param bytes the bytecode raw data
+ * @param classNode the bytecode object model
+ * @param fromLibrary whether this class is from a library rather than part
+ * of this project
+ * @param sourceContents initial contents of the Java source, if known, or
+ * null
+ */
+ public ClassContext(
+ @NonNull LintDriver driver,
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull File file,
+ @Nullable File jarFile,
+ @NonNull File binDir,
+ @NonNull byte[] bytes,
+ @NonNull ClassNode classNode,
+ boolean fromLibrary,
+ @Nullable String sourceContents) {
+ super(driver, project, main, file);
+ mJarFile = jarFile;
+ mBinDir = binDir;
+ mBytes = bytes;
+ mClassNode = classNode;
+ mFromLibrary = fromLibrary;
+ mSourceContents = sourceContents;
+ }
+
+ /**
+ * Returns the raw bytecode data for this class file
+ *
+ * @return the byte array containing the bytecode data
+ */
+ @NonNull
+ public byte[] getBytecode() {
+ return mBytes;
+ }
+
+ /**
+ * Returns the bytecode object model
+ *
+ * @return the bytecode object model, never null
+ */
+ @NonNull
+ public ClassNode getClassNode() {
+ return mClassNode;
+ }
+
+ /**
+ * Returns the jar file, if any. If this is null, the .class file is a real file
+ * on disk, otherwise it represents a relative path within the jar file.
+ *
+ * @return the jar file, or null
+ */
+ @Nullable
+ public File getJarFile() {
+ return mJarFile;
+ }
+
+ /**
+ * Returns whether this class is part of a library (not this project).
+ *
+ * @return true if this class is part of a library
+ */
+ public boolean isFromClassLibrary() {
+ return mFromLibrary;
+ }
+
+ /**
+ * Returns the source file for this class file, if possible.
+ *
+ * @return the source file, or null
+ */
+ @Nullable
+ public File getSourceFile() {
+ if (mSourceFile == null && !mSearchedForSource) {
+ mSearchedForSource = true;
+
+ String source = mClassNode.sourceFile;
+ if (source == null) {
+ source = file.getName();
+ if (source.endsWith(DOT_CLASS)) {
+ source = source.substring(0, source.length() - DOT_CLASS.length()) + DOT_JAVA;
+ }
+ int index = source.indexOf('$');
+ if (index != -1) {
+ source = source.substring(0, index) + DOT_JAVA;
+ }
+ }
+ if (source != null) {
+ if (mJarFile != null) {
+ String relative = file.getParent() + File.separator + source;
+ List<File> sources = getProject().getJavaSourceFolders();
+ for (File dir : sources) {
+ File sourceFile = new File(dir, relative);
+ if (sourceFile.exists()) {
+ mSourceFile = sourceFile;
+ break;
+ }
+ }
+ } else {
+ // Determine package
+ String topPath = mBinDir.getPath();
+ String parentPath = file.getParentFile().getPath();
+ if (parentPath.startsWith(topPath)) {
+ int start = topPath.length() + 1;
+ String relative = start > parentPath.length() ? // default package?
+ "" : parentPath.substring(start);
+ List<File> sources = getProject().getJavaSourceFolders();
+ for (File dir : sources) {
+ File sourceFile = new File(dir, relative + File.separator + source);
+ if (sourceFile.exists()) {
+ mSourceFile = sourceFile;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return mSourceFile;
+ }
+
+ /**
+ * Returns the contents of the source file for this class file, if found.
+ *
+ * @return the source contents, or ""
+ */
+ @NonNull
+ public String getSourceContents() {
+ if (mSourceContents == null) {
+ File sourceFile = getSourceFile();
+ if (sourceFile != null) {
+ mSourceContents = getClient().readFile(mSourceFile);
+ }
+
+ if (mSourceContents == null) {
+ mSourceContents = "";
+ }
+ }
+
+ return mSourceContents;
+ }
+
+ /**
+ * Returns the contents of the source file for this class file, if found. If
+ * {@code read} is false, do not read the source contents if it has not
+ * already been read. (This is primarily intended for the lint
+ * infrastructure; most client code would call {@link #getSourceContents()}
+ * .)
+ *
+ * @param read whether to read the source contents if it has not already
+ * been initialized
+ * @return the source contents, which will never be null if {@code read} is
+ * true, or null if {@code read} is false and the source contents
+ * hasn't already been read.
+ */
+ @Nullable
+ public String getSourceContents(boolean read) {
+ if (read) {
+ return getSourceContents();
+ } else {
+ return mSourceContents;
+ }
+ }
+
+ /**
+ * Returns a location for the given source line number in this class file's
+ * source file, if available.
+ *
+ * @param line the line number (1-based, which is what ASM uses)
+ * @param patternStart optional pattern to search for in the source for
+ * range start
+ * @param patternEnd optional pattern to search for in the source for range
+ * end
+ * @param hints additional hints about the pattern search (provided
+ * {@code patternStart} is non null)
+ * @return a location, never null
+ */
+ @NonNull
+ public Location getLocationForLine(int line, @Nullable String patternStart,
+ @Nullable String patternEnd, @Nullable SearchHints hints) {
+ File sourceFile = getSourceFile();
+ if (sourceFile != null) {
+ // ASM line numbers are 1-based, and lint line numbers are 0-based
+ if (line != -1) {
+ return Location.create(sourceFile, getSourceContents(), line - 1,
+ patternStart, patternEnd, hints);
+ } else {
+ return Location.create(sourceFile);
+ }
+ }
+
+ return Location.create(file);
+ }
+
+ /**
+ * Reports an issue.
+ * <p>
+ * Detectors should only call this method if an error applies to the whole class
+ * scope and there is no specific method or field that applies to the error.
+ * If so, use
+ * {@link #report(Issue, org.objectweb.asm.tree.MethodNode, org.objectweb.asm.tree.AbstractInsnNode, Location, String)} or
+ * {@link #report(Issue, org.objectweb.asm.tree.FieldNode, Location, String)}, such that
+ * suppress annotations are checked.
+ *
+ * @param issue the issue to report
+ * @param location the location of the issue, or null if not known
+ * @param message the message for this warning
+ */
+ @Override
+ public void report(
+ @NonNull Issue issue,
+ @Nullable Location location,
+ @NonNull String message) {
+ if (mDriver.isSuppressed(issue, mClassNode)) {
+ return;
+ }
+ ClassNode curr = mClassNode;
+ while (curr != null) {
+ ClassNode prev = curr;
+ curr = mDriver.getOuterClassNode(curr);
+ if (curr != null) {
+ if (prev.outerMethod != null) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List methods = curr.methods;
+ for (Object m : methods) {
+ MethodNode method = (MethodNode) m;
+ if (method.name.equals(prev.outerMethod)
+ && method.desc.equals(prev.outerMethodDesc)) {
+ // Found the outer method for this anonymous class; continue
+ // reporting on it (which will also work its way up the parent
+ // class hierarchy)
+ if (method != null && mDriver.isSuppressed(issue, mClassNode, method,
+ null)) {
+ return;
+ }
+ break;
+ }
+ }
+ }
+ if (mDriver.isSuppressed(issue, curr)) {
+ return;
+ }
+ }
+ }
+
+ super.report(issue, location, message);
+ }
+
+ // Unfortunately, ASMs nodes do not extend a common DOM node type with parent
+ // pointers, so we have to have multiple methods which pass in each type
+ // of node (class, method, field) to be checked.
+
+ /**
+ * Reports an issue applicable to a given method node.
+ *
+ * @param issue the issue to report
+ * @param method the method scope the error applies to. The lint
+ * infrastructure will check whether there are suppress
+ * annotations on this method (or its enclosing class) and if so
+ * suppress the warning without involving the client.
+ * @param instruction the instruction within the method the error applies
+ * to. You cannot place annotations on individual method
+ * instructions (for example, annotations on local variables are
+ * allowed, but are not kept in the .class file). However, this
+ * instruction is needed to handle suppressing errors on field
+ * initializations; in that case, the errors may be reported in
+ * the {@code <clinit>} method, but the annotation is found not
+ * on that method but for the {@link FieldNode}'s.
+ * @param location the location of the issue, or null if not known
+ * @param message the message for this warning
+ */
+ public void report(
+ @NonNull Issue issue,
+ @Nullable MethodNode method,
+ @Nullable AbstractInsnNode instruction,
+ @Nullable Location location,
+ @NonNull String message) {
+ if (method != null && mDriver.isSuppressed(issue, mClassNode, method, instruction)) {
+ return;
+ }
+ report(issue, location, message); // also checks the class node
+ }
+
+ /**
+ * Reports an issue applicable to a given method node.
+ *
+ * @param issue the issue to report
+ * @param field the scope the error applies to. The lint infrastructure
+ * will check whether there are suppress annotations on this field (or its enclosing
+ * class) and if so suppress the warning without involving the client.
+ * @param location the location of the issue, or null if not known
+ * @param message the message for this warning
+ */
+ public void report(
+ @NonNull Issue issue,
+ @Nullable FieldNode field,
+ @Nullable Location location,
+ @NonNull String message) {
+ if (field != null && mDriver.isSuppressed(issue, field)) {
+ return;
+ }
+ report(issue, location, message); // also checks the class node
+ }
+
+ /**
+ * Report an error.
+ * Like {@link #report(Issue, MethodNode, AbstractInsnNode, Location, String)} but with
+ * a now-unused data parameter at the end.
+ *
+ * @deprecated Use {@link #report(Issue, FieldNode, Location, String)} instead;
+ * this method is here for custom rule compatibility
+ */
+ @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules
+ @Deprecated
+ public void report(
+ @NonNull Issue issue,
+ @Nullable MethodNode method,
+ @Nullable AbstractInsnNode instruction,
+ @Nullable Location location,
+ @NonNull String message,
+ @SuppressWarnings("UnusedParameters") @Nullable Object data) {
+ report(issue, method, instruction, location, message);
+ }
+
+ /**
+ * Report an error.
+ * Like {@link #report(Issue, FieldNode, Location, String)} but with
+ * a now-unused data parameter at the end.
+ *
+ * @deprecated Use {@link #report(Issue, FieldNode, Location, String)} instead;
+ * this method is here for custom rule compatibility
+ */
+ @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules
+ @Deprecated
+ public void report(
+ @NonNull Issue issue,
+ @Nullable FieldNode field,
+ @Nullable Location location,
+ @NonNull String message,
+ @SuppressWarnings("UnusedParameters") @Nullable Object data) {
+ report(issue, field, location, message);
+ }
+
+ /**
+ * Finds the line number closest to the given node
+ *
+ * @param node the instruction node to get a line number for
+ * @return the closest line number, or -1 if not known
+ */
+ public static int findLineNumber(@NonNull AbstractInsnNode node) {
+ AbstractInsnNode curr = node;
+
+ // First search backwards
+ while (curr != null) {
+ if (curr.getType() == AbstractInsnNode.LINE) {
+ return ((LineNumberNode) curr).line;
+ }
+ curr = curr.getPrevious();
+ }
+
+ // Then search forwards
+ curr = node;
+ while (curr != null) {
+ if (curr.getType() == AbstractInsnNode.LINE) {
+ return ((LineNumberNode) curr).line;
+ }
+ curr = curr.getNext();
+ }
+
+ return -1;
+ }
+
+ /**
+ * Finds the line number closest to the given method declaration
+ *
+ * @param node the method node to get a line number for
+ * @return the closest line number, or -1 if not known
+ */
+ public static int findLineNumber(@NonNull MethodNode node) {
+ if (node.instructions != null && node.instructions.size() > 0) {
+ return findLineNumber(node.instructions.get(0));
+ }
+
+ return -1;
+ }
+
+ /**
+ * Finds the line number closest to the given class declaration
+ *
+ * @param node the method node to get a line number for
+ * @return the closest line number, or -1 if not known
+ */
+ public static int findLineNumber(@NonNull ClassNode node) {
+ if (node.methods != null && !node.methods.isEmpty()) {
+ MethodNode firstMethod = getFirstRealMethod(node);
+ if (firstMethod != null) {
+ return findLineNumber(firstMethod);
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns a location for the given {@link ClassNode}, where class node is
+ * either the top level class, or an inner class, in the current context.
+ *
+ * @param classNode the class in the current context
+ * @return a location pointing to the class declaration, or as close to it
+ * as possible
+ */
+ @NonNull
+ public Location getLocation(@NonNull ClassNode classNode) {
+ // Attempt to find a proper location for this class. This is tricky
+ // since classes do not have line number entries in the class file; we need
+ // to find a method, look up the corresponding line number then search
+ // around it for a suitable tag, such as the class name.
+ String pattern;
+ if (isAnonymousClass(classNode.name)) {
+ pattern = classNode.superName;
+ } else {
+ pattern = classNode.name;
+ }
+ int index = pattern.lastIndexOf('$');
+ if (index != -1) {
+ pattern = pattern.substring(index + 1);
+ }
+ index = pattern.lastIndexOf('/');
+ if (index != -1) {
+ pattern = pattern.substring(index + 1);
+ }
+
+ return getLocationForLine(findLineNumber(classNode), pattern, null,
+ SearchHints.create(BACKWARD).matchJavaSymbol());
+ }
+
+ @Nullable
+ private static MethodNode getFirstRealMethod(@NonNull ClassNode classNode) {
+ // Return the first method in the class for line number purposes. Skip <init>,
+ // since it's typically not located near the real source of the method.
+ if (classNode.methods != null) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List methods = classNode.methods;
+ for (Object m : methods) {
+ MethodNode method = (MethodNode) m;
+ if (method.name.charAt(0) != '<') {
+ return method;
+ }
+ }
+
+ if (!classNode.methods.isEmpty()) {
+ return (MethodNode) classNode.methods.get(0);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a location for the given {@link MethodNode}.
+ *
+ * @param methodNode the class in the current context
+ * @param classNode the class containing the method
+ * @return a location pointing to the class declaration, or as close to it
+ * as possible
+ */
+ @NonNull
+ public Location getLocation(@NonNull MethodNode methodNode,
+ @NonNull ClassNode classNode) {
+ // Attempt to find a proper location for this class. This is tricky
+ // since classes do not have line number entries in the class file; we need
+ // to find a method, look up the corresponding line number then search
+ // around it for a suitable tag, such as the class name.
+ String pattern;
+ SearchDirection searchMode;
+ if (methodNode.name.equals(CONSTRUCTOR_NAME)) {
+ searchMode = EOL_BACKWARD;
+ if (isAnonymousClass(classNode.name)) {
+ pattern = classNode.superName.substring(classNode.superName.lastIndexOf('/') + 1);
+ } else {
+ pattern = classNode.name.substring(classNode.name.lastIndexOf('$') + 1);
+ }
+ } else {
+ searchMode = BACKWARD;
+ pattern = methodNode.name;
+ }
+
+ return getLocationForLine(findLineNumber(methodNode), pattern, null,
+ SearchHints.create(searchMode).matchJavaSymbol());
+ }
+
+ /**
+ * Returns a location for the given {@link AbstractInsnNode}.
+ *
+ * @param instruction the instruction to look up the location for
+ * @return a location pointing to the instruction, or as close to it
+ * as possible
+ */
+ @NonNull
+ public Location getLocation(@NonNull AbstractInsnNode instruction) {
+ SearchHints hints = SearchHints.create(FORWARD).matchJavaSymbol();
+ String pattern = null;
+ if (instruction instanceof MethodInsnNode) {
+ MethodInsnNode call = (MethodInsnNode) instruction;
+ if (call.name.equals(CONSTRUCTOR_NAME)) {
+ pattern = call.owner;
+ hints = hints.matchConstructor();
+ } else {
+ pattern = call.name;
+ }
+ int index = pattern.lastIndexOf('$');
+ if (index != -1) {
+ pattern = pattern.substring(index + 1);
+ }
+ index = pattern.lastIndexOf('/');
+ if (index != -1) {
+ pattern = pattern.substring(index + 1);
+ }
+ }
+
+ int line = findLineNumber(instruction);
+ return getLocationForLine(line, pattern, null, hints);
+ }
+
+ private static boolean isAnonymousClass(@NonNull String fqcn) {
+ int lastIndex = fqcn.lastIndexOf('$');
+ if (lastIndex != -1 && lastIndex < fqcn.length() - 1) {
+ if (Character.isDigit(fqcn.charAt(lastIndex + 1))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Converts from a VM owner name (such as foo/bar/Foo$Baz) to a
+ * fully qualified class name (such as foo.bar.Foo.Baz).
+ *
+ * @param owner the owner name to convert
+ * @return the corresponding fully qualified class name
+ */
+ @NonNull
+ public static String getFqcn(@NonNull String owner) {
+ return owner.replace('/', '.').replace('$','.');
+ }
+
+ /**
+ * Computes a user-readable type signature from the given class owner, name
+ * and description. For example, for owner="foo/bar/Foo$Baz", name="foo",
+ * description="(I)V", it returns "void foo.bar.Foo.Bar#foo(int)".
+ *
+ * @param owner the class name
+ * @param name the method name
+ * @param desc the method description
+ * @return a user-readable string
+ */
+ public static String createSignature(String owner, String name, String desc) {
+ StringBuilder sb = new StringBuilder(100);
+
+ if (desc != null) {
+ Type returnType = Type.getReturnType(desc);
+ sb.append(getTypeString(returnType));
+ sb.append(' ');
+ }
+
+ if (owner != null) {
+ sb.append(getFqcn(owner));
+ }
+ if (name != null) {
+ sb.append('#');
+ sb.append(name);
+ if (desc != null) {
+ Type[] argumentTypes = Type.getArgumentTypes(desc);
+ if (argumentTypes != null && argumentTypes.length > 0) {
+ sb.append('(');
+ boolean first = true;
+ for (Type type : argumentTypes) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(getTypeString(type));
+ }
+ sb.append(')');
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static String getTypeString(Type type) {
+ String s = type.getClassName();
+ if (s.startsWith("java.lang.")) { //$NON-NLS-1$
+ s = s.substring("java.lang.".length()); //$NON-NLS-1$
+ }
+
+ return s;
+ }
+
+ /**
+ * Computes the internal class name of the given fully qualified class name.
+ * For example, it converts foo.bar.Foo.Bar into foo/bar/Foo$Bar
+ *
+ * @param fqcn the fully qualified class name
+ * @return the internal class name
+ */
+ @NonNull
+ public static String getInternalName(@NonNull String fqcn) {
+ if (fqcn.indexOf('.') == -1) {
+ return fqcn;
+ }
+
+ // If class name contains $, it's not an ambiguous inner class name.
+ if (fqcn.indexOf('$') != -1) {
+ return AsmUtils.toInternalName(fqcn);
+ }
+ // Let's assume that components that start with Caps are class names.
+ StringBuilder sb = new StringBuilder(fqcn.length());
+ String prev = null;
+ for (String part : Splitter.on('.').split(fqcn)) {
+ if (prev != null && !prev.isEmpty()) {
+ if (Character.isUpperCase(prev.charAt(0))) {
+ sb.append('$');
+ } else {
+ sb.append('/');
+ }
+ }
+ sb.append(part);
+ prev = part;
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java
new file mode 100644
index 0000000..23225e5
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ConstantEvaluator.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.detector.api;
+
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BYTE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_DOUBLE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_FLOAT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_OBJECT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_SHORT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.google.common.collect.Lists;
+
+import java.lang.reflect.Array;
+import java.util.List;
+import java.util.ListIterator;
+
+import lombok.ast.ArrayCreation;
+import lombok.ast.ArrayInitializer;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.BooleanLiteral;
+import lombok.ast.Cast;
+import lombok.ast.CharLiteral;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.FloatingPointLiteral;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.Node;
+import lombok.ast.NullLiteral;
+import lombok.ast.Select;
+import lombok.ast.Statement;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+import lombok.ast.TypeReference;
+import lombok.ast.UnaryExpression;
+import lombok.ast.UnaryOperator;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/** Evaluates constant expressions */
+public class ConstantEvaluator {
+ private final JavaContext mContext;
+ private boolean mAllowUnknown;
+
+ /**
+ * Creates a new constant evaluator
+ *
+ * @param context the context to use to resolve field references, if any
+ */
+ public ConstantEvaluator(@Nullable JavaContext context) {
+ mContext = context;
+ }
+
+ /**
+ * Whether we allow computing values where some terms are unknown. For example, the expression
+ * {@code "foo" + x + "bar"} would return {@code null} without and {@code "foobar"} with.
+ *
+ * @return this for constructor chaining
+ */
+ public ConstantEvaluator allowUnknowns() {
+ mAllowUnknown = true;
+ return this;
+ }
+
+ /**
+ * Evaluates the given node and returns the constant value it resolves to, if any
+ *
+ * @param node the node to compute the constant value for
+ * @return the corresponding constant value - a String, an Integer, a Float, and so on
+ */
+ @Nullable
+ public Object evaluate(@NonNull Node node) {
+ if (node instanceof NullLiteral) {
+ return null;
+ } else if (node instanceof BooleanLiteral) {
+ return ((BooleanLiteral)node).astValue();
+ } else if (node instanceof StringLiteral) {
+ StringLiteral string = (StringLiteral) node;
+ return string.astValue();
+ } else if (node instanceof CharLiteral) {
+ return ((CharLiteral)node).astValue();
+ } else if (node instanceof IntegralLiteral) {
+ IntegralLiteral literal = (IntegralLiteral) node;
+ // Don't combine to ?: since that will promote astIntValue to a long
+ if (literal.astMarkedAsLong()) {
+ return literal.astLongValue();
+ } else {
+ return literal.astIntValue();
+ }
+ } else if (node instanceof FloatingPointLiteral) {
+ FloatingPointLiteral literal = (FloatingPointLiteral) node;
+ // Don't combine to ?: since that will promote astFloatValue to a double
+ if (literal.astMarkedAsFloat()) {
+ return literal.astFloatValue();
+ } else {
+ return literal.astDoubleValue();
+ }
+ } else if (node instanceof UnaryExpression) {
+ UnaryOperator operator = ((UnaryExpression) node).astOperator();
+ Object operand = evaluate(((UnaryExpression) node).astOperand());
+ if (operand == null) {
+ return null;
+ }
+ switch (operator) {
+ case LOGICAL_NOT:
+ if (operand instanceof Boolean) {
+ return !(Boolean) operand;
+ }
+ break;
+ case UNARY_PLUS:
+ return operand;
+ case BINARY_NOT:
+ if (operand instanceof Integer) {
+ return ~(Integer) operand;
+ } else if (operand instanceof Long) {
+ return ~(Long) operand;
+ } else if (operand instanceof Short) {
+ return ~(Short) operand;
+ } else if (operand instanceof Character) {
+ return ~(Character) operand;
+ } else if (operand instanceof Byte) {
+ return ~(Byte) operand;
+ }
+ break;
+ case UNARY_MINUS:
+ if (operand instanceof Integer) {
+ return -(Integer) operand;
+ } else if (operand instanceof Long) {
+ return -(Long) operand;
+ } else if (operand instanceof Double) {
+ return -(Double) operand;
+ } else if (operand instanceof Float) {
+ return -(Float) operand;
+ } else if (operand instanceof Short) {
+ return -(Short) operand;
+ } else if (operand instanceof Character) {
+ return -(Character) operand;
+ } else if (operand instanceof Byte) {
+ return -(Byte) operand;
+ }
+ break;
+ }
+ } else if (node instanceof InlineIfExpression) {
+ InlineIfExpression expression = (InlineIfExpression) node;
+ Object known = evaluate(expression.astCondition());
+ if (known == Boolean.TRUE && expression.astIfTrue() != null) {
+ return evaluate(expression.astIfTrue());
+ } else if (known == Boolean.FALSE && expression.astIfFalse() != null) {
+ return evaluate(expression.astIfFalse());
+ }
+ } else if (node instanceof BinaryExpression) {
+ BinaryOperator operator = ((BinaryExpression) node).astOperator();
+ Object operandLeft = evaluate(((BinaryExpression) node).astLeft());
+ Object operandRight = evaluate(((BinaryExpression) node).astRight());
+ if (operandLeft == null || operandRight == null) {
+ if (mAllowUnknown) {
+ if (operandLeft == null) {
+ return operandRight;
+ } else {
+ return operandLeft;
+ }
+ }
+ return null;
+ }
+ if (operandLeft instanceof String && operandRight instanceof String) {
+ if (operator == BinaryOperator.PLUS) {
+ return operandLeft.toString() + operandRight.toString();
+ }
+ return null;
+ } else if (operandLeft instanceof Boolean && operandRight instanceof Boolean) {
+ boolean left = (Boolean) operandLeft;
+ boolean right = (Boolean) operandRight;
+ switch (operator) {
+ case LOGICAL_OR:
+ return left || right;
+ case LOGICAL_AND:
+ return left && right;
+ case BITWISE_OR:
+ return left | right;
+ case BITWISE_XOR:
+ return left ^ right;
+ case BITWISE_AND:
+ return left & right;
+ case EQUALS:
+ return left == right;
+ case NOT_EQUALS:
+ return left != right;
+ }
+ } else if (operandLeft instanceof Number && operandRight instanceof Number) {
+ Number left = (Number) operandLeft;
+ Number right = (Number) operandRight;
+ boolean isInteger =
+ !(left instanceof Float || left instanceof Double
+ || right instanceof Float || right instanceof Double);
+ boolean isWide =
+ isInteger ? (left instanceof Long || right instanceof Long)
+ : (left instanceof Double || right instanceof Double);
+
+ switch (operator) {
+ case BITWISE_OR:
+ if (isWide) {
+ return left.longValue() | right.longValue();
+ } else {
+ return left.intValue() | right.intValue();
+ }
+ case BITWISE_XOR:
+ if (isWide) {
+ return left.longValue() ^ right.longValue();
+ } else {
+ return left.intValue() ^ right.intValue();
+ }
+ case BITWISE_AND:
+ if (isWide) {
+ return left.longValue() & right.longValue();
+ } else {
+ return left.intValue() & right.intValue();
+ }
+ case EQUALS:
+ if (isInteger) {
+ return left.longValue() == right.longValue();
+ } else {
+ return left.doubleValue() == right.doubleValue();
+ }
+ case NOT_EQUALS:
+ if (isInteger) {
+ return left.longValue() != right.longValue();
+ } else {
+ return left.doubleValue() != right.doubleValue();
+ }
+ case GREATER:
+ if (isInteger) {
+ return left.longValue() > right.longValue();
+ } else {
+ return left.doubleValue() > right.doubleValue();
+ }
+ case GREATER_OR_EQUAL:
+ if (isInteger) {
+ return left.longValue() >= right.longValue();
+ } else {
+ return left.doubleValue() >= right.doubleValue();
+ }
+ case LESS:
+ if (isInteger) {
+ return left.longValue() < right.longValue();
+ } else {
+ return left.doubleValue() < right.doubleValue();
+ }
+ case LESS_OR_EQUAL:
+ if (isInteger) {
+ return left.longValue() <= right.longValue();
+ } else {
+ return left.doubleValue() <= right.doubleValue();
+ }
+ case SHIFT_LEFT:
+ if (isWide) {
+ return left.longValue() << right.intValue();
+ } else {
+ return left.intValue() << right.intValue();
+ }
+ case SHIFT_RIGHT:
+ if (isWide) {
+ return left.longValue() >> right.intValue();
+ } else {
+ return left.intValue() >> right.intValue();
+ }
+ case BITWISE_SHIFT_RIGHT:
+ if (isWide) {
+ return left.longValue() >>> right.intValue();
+ } else {
+ return left.intValue() >>> right.intValue();
+ }
+ case PLUS:
+ if (isInteger) {
+ if (isWide) {
+ return left.longValue() + right.longValue();
+ } else {
+ return left.intValue() + right.intValue();
+ }
+ } else {
+ if (isWide) {
+ return left.doubleValue() + right.doubleValue();
+ } else {
+ return left.floatValue() + right.floatValue();
+ }
+ }
+ case MINUS:
+ if (isInteger) {
+ if (isWide) {
+ return left.longValue() - right.longValue();
+ } else {
+ return left.intValue() - right.intValue();
+ }
+ } else {
+ if (isWide) {
+ return left.doubleValue() - right.doubleValue();
+ } else {
+ return left.floatValue() - right.floatValue();
+ }
+ }
+ case MULTIPLY:
+ if (isInteger) {
+ if (isWide) {
+ return left.longValue() * right.longValue();
+ } else {
+ return left.intValue() * right.intValue();
+ }
+ } else {
+ if (isWide) {
+ return left.doubleValue() * right.doubleValue();
+ } else {
+ return left.floatValue() * right.floatValue();
+ }
+ }
+ case DIVIDE:
+ if (isInteger) {
+ if (isWide) {
+ return left.longValue() / right.longValue();
+ } else {
+ return left.intValue() / right.intValue();
+ }
+ } else {
+ if (isWide) {
+ return left.doubleValue() / right.doubleValue();
+ } else {
+ return left.floatValue() / right.floatValue();
+ }
+ }
+ case REMAINDER:
+ if (isInteger) {
+ if (isWide) {
+ return left.longValue() % right.longValue();
+ } else {
+ return left.intValue() % right.intValue();
+ }
+ } else {
+ if (isWide) {
+ return left.doubleValue() % right.doubleValue();
+ } else {
+ return left.floatValue() % right.floatValue();
+ }
+ }
+ default:
+ return null;
+ }
+ }
+ } else if (node instanceof Cast) {
+ Cast cast = (Cast)node;
+ Object operandValue = evaluate(cast.astOperand());
+ if (operandValue instanceof Number) {
+ Number number = (Number)operandValue;
+ String typeName = cast.astTypeReference().getTypeName();
+ if (typeName.equals("float")) {
+ return number.floatValue();
+ } else if (typeName.equals("double")) {
+ return number.doubleValue();
+ } else if (typeName.equals("int")) {
+ return number.intValue();
+ } else if (typeName.equals("long")) {
+ return number.longValue();
+ } else if (typeName.equals("short")) {
+ return number.shortValue();
+ } else if (typeName.equals("byte")) {
+ return number.byteValue();
+ }
+ }
+ return operandValue;
+ } else if (mContext != null && (node instanceof VariableReference ||
+ node instanceof Select)) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) resolved;
+ Object value = field.getValue();
+ if (value != null) {
+ return value;
+ }
+ Node astNode = field.findAstNode();
+ if (astNode instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) astNode;
+ VariableDefinition definition = declaration.astDefinition();
+ if (definition != null && definition.astModifiers().isFinal()) {
+ StrictListAccessor<VariableDefinitionEntry, VariableDefinition> variables =
+ definition.astVariables();
+ if (variables.size() == 1) {
+ VariableDefinitionEntry first = variables.first();
+ if (first.astInitializer() != null) {
+ return evaluate(first.astInitializer());
+ }
+ }
+ }
+ }
+ return null;
+ } else if (node instanceof VariableReference) {
+ Statement statement = getParentOfType(node, Statement.class, false);
+ if (statement != null) {
+ ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next() == statement) {
+ if (iterator.hasPrevious()) { // should always be true
+ iterator.previous();
+ }
+ break;
+ }
+ }
+
+ String targetName = ((VariableReference)node).astIdentifier().astValue();
+ while (iterator.hasPrevious()) {
+ Node previous = iterator.previous();
+ if (previous instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) previous;
+ VariableDefinition definition = declaration.astDefinition();
+ for (VariableDefinitionEntry entry : definition
+ .astVariables()) {
+ if (entry.astInitializer() != null
+ && entry.astName().astValue().equals(targetName)) {
+ return evaluate(entry.astInitializer());
+ }
+ }
+ } else if (previous instanceof ExpressionStatement) {
+ ExpressionStatement expressionStatement = (ExpressionStatement) previous;
+ Expression expression = expressionStatement.astExpression();
+ if (expression instanceof BinaryExpression &&
+ ((BinaryExpression) expression).astOperator()
+ == BinaryOperator.ASSIGN) {
+ BinaryExpression binaryExpression = (BinaryExpression) expression;
+ if (targetName.equals(binaryExpression.astLeft().toString())) {
+ return evaluate(binaryExpression.astRight());
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (node instanceof ArrayCreation) {
+ ArrayCreation creation = (ArrayCreation) node;
+ ArrayInitializer initializer = creation.astInitializer();
+ if (initializer != null) {
+ TypeReference typeReference = creation.astComponentTypeReference();
+ StrictListAccessor<Expression, ArrayInitializer> expressions = initializer
+ .astExpressions();
+ List<Object> values = Lists.newArrayListWithExpectedSize(expressions.size());
+ Class<?> commonType = null;
+ for (Expression expression : expressions) {
+ Object value = evaluate(expression);
+ if (value != null) {
+ values.add(value);
+ if (commonType == null) {
+ commonType = value.getClass();
+ } else {
+ while (!commonType.isAssignableFrom(value.getClass())) {
+ commonType = commonType.getSuperclass();
+ }
+ }
+ } else if (!mAllowUnknown) {
+ // Inconclusive
+ return null;
+ }
+ }
+ if (!values.isEmpty()) {
+ Object o = Array.newInstance(commonType, values.size());
+ return values.toArray((Object[]) o);
+ } else {
+ ResolvedNode type = mContext.resolve(typeReference);
+ System.out.println(type);
+ // TODO: return new array of this type
+ }
+ } else {
+ // something like "new byte[3]" but with no initializer.
+ String type = creation.astComponentTypeReference().toString();
+ // TODO: Look up the size and only if small, use it. E.g. if it was byte[3]
+ // we could return a byte[3] array, but if it's say byte[1024*1024] we don't
+ // want to do that.
+ int size = 0;
+ if (TYPE_BYTE.equals(type)) {
+ return new byte[size];
+ }
+ if (TYPE_BOOLEAN.equals(type)) {
+ return new boolean[size];
+ }
+ if (TYPE_INT.equals(type)) {
+ return new int[size];
+ }
+ if (TYPE_LONG.equals(type)) {
+ return new long[size];
+ }
+ if (TYPE_CHAR.equals(type)) {
+ return new char[size];
+ }
+ if (TYPE_FLOAT.equals(type)) {
+ return new float[size];
+ }
+ if (TYPE_DOUBLE.equals(type)) {
+ return new double[size];
+ }
+ if (TYPE_STRING.equals(type)) {
+ //noinspection SSBasedInspection
+ return new String[size];
+ }
+ if (TYPE_SHORT.equals(type)) {
+ return new short[size];
+ }
+ if (TYPE_OBJECT.equals(type)) {
+ //noinspection SSBasedInspection
+ return new Object[size];
+ }
+ }
+ }
+
+ // TODO: Check for MethodInvocation and perform some common operations -
+ // Math.* methods, String utility methods like notNullize, etc
+
+ return null;
+ }
+
+ /**
+ * Evaluates the given node and returns the constant value it resolves to, if any. Convenience
+ * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns
+ * the result.
+ *
+ * @param context the context to use to resolve field references, if any
+ * @param node the node to compute the constant value for
+ * @return the corresponding constant value - a String, an Integer, a Float, and so on
+ */
+ @Nullable
+ public static Object evaluate(@NonNull JavaContext context, @NonNull Node node) {
+ return new ConstantEvaluator(context).evaluate(node);
+ }
+
+ /**
+ * Evaluates the given node and returns the constant string it resolves to, if any. Convenience
+ * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns
+ * the result if the result is a string.
+ *
+ * @param context the context to use to resolve field references, if any
+ * @param node the node to compute the constant value for
+ * @param allowUnknown whether we should construct the string even if some parts of it are
+ * unknown
+ * @return the corresponding string, if any
+ */
+ @Nullable
+ public static String evaluateString(@NonNull JavaContext context, @NonNull Node node,
+ boolean allowUnknown) {
+ ConstantEvaluator evaluator = new ConstantEvaluator(context);
+ if (allowUnknown) {
+ evaluator.allowUnknowns();
+ }
+ Object value = evaluator.evaluate(node);
+ return value instanceof String ? (String) value : null;
+ }
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Context.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
new file mode 100644
index 0000000..168c3a3
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.java
@@ -0,0 +1,782 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.LintDriver;
+import com.google.common.annotations.Beta;
+
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
+/**
+ * A detector is able to find a particular problem (or a set of related problems).
+ * Each problem type is uniquely identified as an {@link Issue}.
+ * <p>
+ * Detectors will be called in a predefined order:
+ * <ol>
+ * <li> Manifest file
+ * <li> Resource files, in alphabetical order by resource type
+ * (therefore, "layout" is checked before "values", "values-de" is checked before
+ * "values-en" but after "values", and so on.
+ * <li> Java sources
+ * <li> Java classes
+ * <li> Gradle files
+ * <li> Generic files
+ * <li> Proguard files
+ * <li> Property files
+ * </ol>
+ * If a detector needs information when processing a file type that comes from a type of
+ * file later in the order above, they can request a second phase; see
+ * {@link LintDriver#requestRepeat}.
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public abstract class Detector {
+ /** Specialized interface for detectors that scan Java source file parse trees */
+ public interface JavaScanner {
+ /**
+ * Create a parse tree visitor to process the parse tree. All
+ * {@link JavaScanner} detectors must provide a visitor, unless they
+ * either return true from {@link #appliesToResourceRefs()} or return
+ * non null from {@link #getApplicableMethodNames()}.
+ * <p>
+ * If you return specific AST node types from
+ * {@link #getApplicableNodeTypes()}, then the visitor will <b>only</b>
+ * be called for the specific requested node types. This is more
+ * efficient, since it allows many detectors that apply to only a small
+ * part of the AST (such as method call nodes) to share iteration of the
+ * majority of the parse tree.
+ * <p>
+ * If you return null from {@link #getApplicableNodeTypes()}, then your
+ * visitor will be called from the top and all node types visited.
+ * <p>
+ * Note that a new visitor is created for each separate compilation
+ * unit, so you can store per file state in the visitor.
+ *
+ * @param context the {@link Context} for the file being analyzed
+ * @return a visitor, or null.
+ */
+ @Nullable
+ AstVisitor createJavaVisitor(@NonNull JavaContext context);
+
+ /**
+ * Return the types of AST nodes that the visitor returned from
+ * {@link #createJavaVisitor(JavaContext)} should visit. See the
+ * documentation for {@link #createJavaVisitor(JavaContext)} for details
+ * on how the shared visitor is used.
+ * <p>
+ * If you return null from this method, then the visitor will process
+ * the full tree instead.
+ * <p>
+ * Note that for the shared visitor, the return codes from the visit
+ * methods are ignored: returning true will <b>not</b> prune iteration
+ * of the subtree, since there may be other node types interested in the
+ * children. If you need to ensure that your visitor only processes a
+ * part of the tree, use a full visitor instead. See the
+ * OverdrawDetector implementation for an example of this.
+ *
+ * @return the list of applicable node types (AST node classes), or null
+ */
+ @Nullable
+ List<Class<? extends Node>> getApplicableNodeTypes();
+
+ /**
+ * Return the list of method names this detector is interested in, or
+ * null. If this method returns non-null, then any AST nodes that match
+ * a method call in the list will be passed to the
+ * {@link #visitMethod(JavaContext, AstVisitor, MethodInvocation)}
+ * method for processing. The visitor created by
+ * {@link #createJavaVisitor(JavaContext)} is also passed to that
+ * method, although it can be null.
+ * <p>
+ * This makes it easy to write detectors that focus on some fixed calls.
+ * For example, the StringFormatDetector uses this mechanism to look for
+ * "format" calls, and when found it looks around (using the AST's
+ * {@link Node#getParent()} method) to see if it's called on
+ * a String class instance, and if so do its normal processing. Note
+ * that since it doesn't need to do any other AST processing, that
+ * detector does not actually supply a visitor.
+ *
+ * @return a set of applicable method names, or null.
+ */
+ @Nullable
+ List<String> getApplicableMethodNames();
+
+ /**
+ * Method invoked for any method calls found that matches any names
+ * returned by {@link #getApplicableMethodNames()}. This also passes
+ * back the visitor that was created by
+ * {@link #createJavaVisitor(JavaContext)}, but a visitor is not
+ * required. It is intended for detectors that need to do additional AST
+ * processing, but also want the convenience of not having to look for
+ * method names on their own.
+ *
+ * @param context the context of the lint request
+ * @param visitor the visitor created from
+ * {@link #createJavaVisitor(JavaContext)}, or null
+ * @param node the {@link MethodInvocation} node for the invoked method
+ */
+ void visitMethod(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node);
+
+ /**
+ * Return the list of constructor types this detector is interested in, or
+ * null. If this method returns non-null, then any AST nodes that match
+ * a constructor call in the list will be passed to the
+ * {@link #visitConstructor(JavaContext, AstVisitor, ConstructorInvocation, ResolvedMethod)}
+ * method for processing. The visitor created by
+ * {@link #createJavaVisitor(JavaContext)} is also passed to that
+ * method, although it can be null.
+ * <p>
+ * This makes it easy to write detectors that focus on some fixed constructors.
+ *
+ * @return a set of applicable fully qualified types, or null.
+ */
+ @Nullable
+ List<String> getApplicableConstructorTypes();
+
+ /**
+ * Method invoked for any constructor calls found that matches any names
+ * returned by {@link #getApplicableConstructorTypes()}. This also passes
+ * back the visitor that was created by
+ * {@link #createJavaVisitor(JavaContext)}, but a visitor is not
+ * required. It is intended for detectors that need to do additional AST
+ * processing, but also want the convenience of not having to look for
+ * method names on their own.
+ *
+ * @param context the context of the lint request
+ * @param visitor the visitor created from
+ * {@link #createJavaVisitor(JavaContext)}, or null
+ * @param node the {@link ConstructorInvocation} node for the invoked method
+ * @param constructor the resolved constructor method with type information
+ */
+ void visitConstructor(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull ConstructorInvocation node,
+ @NonNull ResolvedMethod constructor);
+
+ /**
+ * Returns whether this detector cares about Android resource references
+ * (such as {@code R.layout.main} or {@code R.string.app_name}). If it
+ * does, then the visitor will look for these patterns, and if found, it
+ * will invoke {@link #visitResourceReference} passing the resource type
+ * and resource name. It also passes the visitor, if any, that was
+ * created by {@link #createJavaVisitor(JavaContext)}, such that a
+ * detector can do more than just look for resources.
+ *
+ * @return true if this detector wants to be notified of R resource
+ * identifiers found in the code.
+ */
+ boolean appliesToResourceRefs();
+
+ /**
+ * Called for any resource references (such as {@code R.layout.main}
+ * found in Java code, provided this detector returned {@code true} from
+ * {@link #appliesToResourceRefs()}.
+ *
+ * @param context the lint scanning context
+ * @param visitor the visitor created from
+ * {@link #createJavaVisitor(JavaContext)}, or null
+ * @param node the variable reference for the resource
+ * @param type the resource type, such as "layout" or "string"
+ * @param name the resource name, such as "main" from
+ * {@code R.layout.main}
+ * @param isFramework whether the resource is a framework resource
+ * (android.R) or a local project resource (R)
+ */
+ void visitResourceReference(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull Node node,
+ @NonNull String type,
+ @NonNull String name,
+ boolean isFramework);
+
+ /**
+ * Returns a list of fully qualified names for super classes that this
+ * detector cares about. If not null, this detector will *only* be called
+ * if the current class is a subclass of one of the specified superclasses.
+ *
+ * @return a list of fully qualified names
+ */
+ @Nullable
+ List<String> applicableSuperClasses();
+
+ /**
+ * Called for each class that extends one of the super classes specified with
+ * {@link #applicableSuperClasses()}
+ *
+ * @param context the lint scanning context
+ * @param declaration the class declaration node, or null for anonymous classes
+ * @param node the class declaration node or the anonymous class construction node
+ * @param resolvedClass the resolved class
+ */
+ // TODO: Change signature to pass in the NormalTypeBody instead of the plain Node?
+ void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
+ @NonNull Node node, @NonNull ResolvedClass resolvedClass);
+ }
+
+ /** Specialized interface for detectors that scan Java class files */
+ public interface ClassScanner {
+ /**
+ * Checks the given class' bytecode for issues.
+ *
+ * @param context the context of the lint check, pointing to for example
+ * the file
+ * @param classNode the root class node
+ */
+ void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode);
+
+ /**
+ * Returns the list of node types (corresponding to the constants in the
+ * {@link AbstractInsnNode} class) that this scanner applies to. The
+ * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)}
+ * method will be called for each match.
+ *
+ * @return an array containing all the node types this detector should be
+ * called for, or null if none.
+ */
+ @Nullable
+ int[] getApplicableAsmNodeTypes();
+
+ /**
+ * Process a given instruction node, and register lint issues if
+ * applicable.
+ *
+ * @param context the context of the lint check, pointing to for example
+ * the file
+ * @param classNode the root class node
+ * @param method the method node containing the call
+ * @param instruction the actual instruction
+ */
+ void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull AbstractInsnNode instruction);
+
+ /**
+ * Return the list of method call names (in VM format, e.g. "<init>" for
+ * constructors, etc) for method calls this detector is interested in,
+ * or null. T his will be used to dispatch calls to
+ * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
+ * for only the method calls in owners that the detector is interested
+ * in.
+ * <p>
+ * <b>NOTE</b>: If you return non null from this method, then <b>only</b>
+ * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
+ * will be called if a suitable method is found;
+ * {@link #checkClass(ClassContext, ClassNode)} will not be called under
+ * any circumstances.
+ * <p>
+ * This makes it easy to write detectors that focus on some fixed calls,
+ * and allows lint to make a single pass over the bytecode over a class,
+ * and efficiently dispatch method calls to any detectors that are
+ * interested in it. Without this, each new lint check interested in a
+ * single method, would be doing a complete pass through all the
+ * bytecode instructions of the class via the
+ * {@link #checkClass(ClassContext, ClassNode)} method, which would make
+ * each newly added lint check make lint slower. Now a single dispatch
+ * map is used instead, and for each encountered call in the single
+ * dispatch, it looks up in the map which if any detectors are
+ * interested in the given call name, and dispatches to each one in
+ * turn.
+ *
+ * @return a list of applicable method names, or null.
+ */
+ @Nullable
+ List<String> getApplicableCallNames();
+
+ /**
+ * Just like {@link Detector#getApplicableCallNames()}, but for the owner
+ * field instead. The
+ * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
+ * method will be called for all {@link MethodInsnNode} instances where the
+ * owner field matches any of the members returned in this node.
+ * <p>
+ * Note that if your detector provides both a name and an owner, the
+ * method will be called for any nodes matching either the name <b>or</b>
+ * the owner, not only where they match <b>both</b>. Note also that it will
+ * be called twice - once for the name match, and (at least once) for the owner
+ * match.
+ *
+ * @return a list of applicable owner names, or null.
+ */
+ @Nullable
+ List<String> getApplicableCallOwners();
+
+ /**
+ * Process a given method call node, and register lint issues if
+ * applicable. This is similar to the
+ * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)}
+ * method, but has the additional advantage that it is only called for known
+ * method names or method owners, according to
+ * {@link #getApplicableCallNames()} and {@link #getApplicableCallOwners()}.
+ *
+ * @param context the context of the lint check, pointing to for example
+ * the file
+ * @param classNode the root class node
+ * @param method the method node containing the call
+ * @param call the actual method call node
+ */
+ void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull MethodInsnNode call);
+ }
+
+ /**
+ * Specialized interface for detectors that scan binary resource files
+ * (typically bitmaps but also files in res/raw)
+ */
+ public interface BinaryResourceScanner {
+ /**
+ * Called for each resource folder
+ *
+ * @param context the context for the resource file
+ */
+ void checkBinaryResource(@NonNull ResourceContext context);
+
+ /**
+ * Returns whether this detector applies to the given folder type. This
+ * allows the detectors to be pruned from iteration, so for example when we
+ * are analyzing a string value file we don't need to look up detectors
+ * related to layout.
+ *
+ * @param folderType the folder type to be visited
+ * @return true if this detector can apply to resources in folders of the
+ * given type
+ */
+ boolean appliesTo(@NonNull ResourceFolderType folderType);
+ }
+
+ /** Specialized interface for detectors that scan resource folders (the folder directory
+ * itself, not the individual files within it */
+ public interface ResourceFolderScanner {
+ /**
+ * Called for each resource folder
+ *
+ * @param context the context for the resource folder
+ * @param folderName the resource folder name
+ */
+ void checkFolder(@NonNull ResourceContext context, @NonNull String folderName);
+
+ /**
+ * Returns whether this detector applies to the given folder type. This
+ * allows the detectors to be pruned from iteration, so for example when we
+ * are analyzing a string value file we don't need to look up detectors
+ * related to layout.
+ *
+ * @param folderType the folder type to be visited
+ * @return true if this detector can apply to resources in folders of the
+ * given type
+ */
+ boolean appliesTo(@NonNull ResourceFolderType folderType);
+ }
+
+ /** Specialized interface for detectors that scan XML files */
+ public interface XmlScanner {
+ /**
+ * Visit the given document. The detector is responsible for its own iteration
+ * through the document.
+ * @param context information about the document being analyzed
+ * @param document the document to examine
+ */
+ void visitDocument(@NonNull XmlContext context, @NonNull Document document);
+
+ /**
+ * Visit the given element.
+ * @param context information about the document being analyzed
+ * @param element the element to examine
+ */
+ void visitElement(@NonNull XmlContext context, @NonNull Element element);
+
+ /**
+ * Visit the given element after its children have been analyzed.
+ * @param context information about the document being analyzed
+ * @param element the element to examine
+ */
+ void visitElementAfter(@NonNull XmlContext context, @NonNull Element element);
+
+ /**
+ * Visit the given attribute.
+ * @param context information about the document being analyzed
+ * @param attribute the attribute node to examine
+ */
+ void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute);
+
+ /**
+ * Returns the list of elements that this detector wants to analyze. If non
+ * null, this detector will be called (specifically, the
+ * {@link #visitElement} method) for each matching element in the document.
+ * <p>
+ * If this method returns null, and {@link #getApplicableAttributes()} also returns
+ * null, then the {@link #visitDocument} method will be called instead.
+ *
+ * @return a collection of elements, or null, or the special
+ * {@link XmlScanner#ALL} marker to indicate that every single
+ * element should be analyzed.
+ */
+ @Nullable
+ Collection<String> getApplicableElements();
+
+ /**
+ * Returns the list of attributes that this detector wants to analyze. If non
+ * null, this detector will be called (specifically, the
+ * {@link #visitAttribute} method) for each matching attribute in the document.
+ * <p>
+ * If this method returns null, and {@link #getApplicableElements()} also returns
+ * null, then the {@link #visitDocument} method will be called instead.
+ *
+ * @return a collection of attributes, or null, or the special
+ * {@link XmlScanner#ALL} marker to indicate that every single
+ * attribute should be analyzed.
+ */
+ @Nullable
+ Collection<String> getApplicableAttributes();
+
+ /**
+ * Special marker collection returned by {@link #getApplicableElements()} or
+ * {@link #getApplicableAttributes()} to indicate that the check should be
+ * invoked on all elements or all attributes
+ */
+ @NonNull
+ List<String> ALL = new ArrayList<String>(0); // NOT Collections.EMPTY!
+ // We want to distinguish this from just an *empty* list returned by the caller!
+ }
+
+ /** Specialized interface for detectors that scan Gradle files */
+ public interface GradleScanner {
+ void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData);
+ }
+
+ /** Specialized interface for detectors that scan other files */
+ public interface OtherFileScanner {
+ /**
+ * Returns the set of files this scanner wants to consider. If this includes
+ * {@link Scope#OTHER} then all source files will be checked. Note that the
+ * set of files will not just include files of the indicated type, but all files
+ * within the relevant source folder. For example, returning {@link Scope#JAVA_FILE}
+ * will not just return {@code .java} files, but also other resource files such as
+ * {@code .html} and other files found within the Java source folders.
+ * <p>
+ * Lint will call the {@link #run(Context)}} method when the file should be checked.
+ *
+ * @return set of scopes that define the types of source files the
+ * detector wants to consider
+ */
+ @NonNull
+ EnumSet<Scope> getApplicableFiles();
+ }
+
+ /**
+ * Runs the detector. This method will not be called for certain specialized
+ * detectors, such as {@link XmlScanner} and {@link JavaScanner}, where
+ * there are specialized analysis methods instead such as
+ * {@link XmlScanner#visitElement(XmlContext, Element)}.
+ *
+ * @param context the context describing the work to be done
+ */
+ public void run(@NonNull Context context) {
+ }
+
+ /**
+ * Returns true if this detector applies to the given file
+ *
+ * @param context the context to check
+ * @param file the file in the context to check
+ * @return true if this detector applies to the given context and file
+ */
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return false;
+ }
+
+ /**
+ * Analysis is about to begin, perform any setup steps.
+ *
+ * @param context the context for the check referencing the project, lint
+ * client, etc
+ */
+ public void beforeCheckProject(@NonNull Context context) {
+ }
+
+ /**
+ * Analysis has just been finished for the whole project, perform any
+ * cleanup or report issues that require project-wide analysis.
+ *
+ * @param context the context for the check referencing the project, lint
+ * client, etc
+ */
+ public void afterCheckProject(@NonNull Context context) {
+ }
+
+ /**
+ * Analysis is about to begin for the given library project, perform any setup steps.
+ *
+ * @param context the context for the check referencing the project, lint
+ * client, etc
+ */
+ public void beforeCheckLibraryProject(@NonNull Context context) {
+ }
+
+ /**
+ * Analysis has just been finished for the given library project, perform any
+ * cleanup or report issues that require library-project-wide analysis.
+ *
+ * @param context the context for the check referencing the project, lint
+ * client, etc
+ */
+ public void afterCheckLibraryProject(@NonNull Context context) {
+ }
+
+ /**
+ * Analysis is about to be performed on a specific file, perform any setup
+ * steps.
+ * <p>
+ * Note: When this method is called at the beginning of checking an XML
+ * file, the context is guaranteed to be an instance of {@link XmlContext},
+ * and similarly for a Java source file, the context will be a
+ * {@link JavaContext} and so on.
+ *
+ * @param context the context for the check referencing the file to be
+ * checked, the project, etc.
+ */
+ public void beforeCheckFile(@NonNull Context context) {
+ }
+
+ /**
+ * Analysis has just been finished for a specific file, perform any cleanup
+ * or report issues found
+ * <p>
+ * Note: When this method is called at the end of checking an XML
+ * file, the context is guaranteed to be an instance of {@link XmlContext},
+ * and similarly for a Java source file, the context will be a
+ * {@link JavaContext} and so on.
+ *
+ * @param context the context for the check referencing the file to be
+ * checked, the project, etc.
+ */
+ public void afterCheckFile(@NonNull Context context) {
+ }
+
+ /**
+ * Returns the expected speed of this detector
+ *
+ * @return the expected speed of this detector
+ */
+ @NonNull
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ /**
+ * Returns the expected speed of this detector.
+ * The issue parameter is made available for subclasses which analyze multiple issues
+ * and which need to distinguish implementation cost by issue. If the detector does
+ * not analyze multiple issues or does not vary in speed by issue type, just override
+ * {@link #getSpeed()} instead.
+ *
+ * @param issue the issue to look up the analysis speed for
+ * @return the expected speed of this detector
+ */
+ @NonNull
+ public Speed getSpeed(@SuppressWarnings("UnusedParameters") @NonNull Issue issue) {
+ // If not overridden, this detector does not distinguish speed by issue type
+ return getSpeed();
+ }
+
+ // ---- Dummy implementations to make implementing XmlScanner easier: ----
+
+ @SuppressWarnings("javadoc")
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ // This method must be overridden if your detector does
+ // not return something from getApplicableElements or
+ // getApplicableAttributes
+ assert false;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ // This method must be overridden if your detector returns
+ // tag names from getApplicableElements
+ assert false;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) {
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ // This method must be overridden if your detector returns
+ // attribute names from getApplicableAttributes
+ assert false;
+ }
+
+ @SuppressWarnings("javadoc")
+ @Nullable
+ public Collection<String> getApplicableElements() {
+ return null;
+ }
+
+ @Nullable
+ @SuppressWarnings("javadoc")
+ public Collection<String> getApplicableAttributes() {
+ return null;
+ }
+
+ // ---- Dummy implementations to make implementing JavaScanner easier: ----
+
+ @Nullable @SuppressWarnings("javadoc")
+ public List<String> getApplicableMethodNames() {
+ return null;
+ }
+
+ @Nullable @SuppressWarnings("javadoc")
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ return null;
+ }
+
+ @Nullable @SuppressWarnings("javadoc")
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ }
+
+ @SuppressWarnings("javadoc")
+ public boolean appliesToResourceRefs() {
+ return false;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull Node node, @NonNull String type, @NonNull String name,
+ boolean isFramework) {
+ }
+
+ @Nullable
+ public List<String> applicableSuperClasses() {
+ return null;
+ }
+
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
+ @NonNull Node node, @NonNull ResolvedClass resolvedClass) {
+ }
+
+ @Nullable @SuppressWarnings("javadoc")
+ public List<String> getApplicableConstructorTypes() {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitConstructor(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull ConstructorInvocation node,
+ @NonNull ResolvedMethod constructor) {
+ }
+
+ // ---- Dummy implementations to make implementing a ClassScanner easier: ----
+
+ @SuppressWarnings("javadoc")
+ public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
+ }
+
+ @SuppressWarnings("javadoc")
+ @Nullable
+ public List<String> getApplicableCallNames() {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ @Nullable
+ public List<String> getApplicableCallOwners() {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull MethodInsnNode call) {
+ }
+
+ @SuppressWarnings("javadoc")
+ @Nullable
+ public int[] getApplicableAsmNodeTypes() {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull AbstractInsnNode instruction) {
+ }
+
+ // ---- Dummy implementations to make implementing an OtherFileScanner easier: ----
+
+ public boolean appliesToFolder(@NonNull Scope scope, @Nullable ResourceFolderType folderType) {
+ return false;
+ }
+
+ @NonNull
+ public EnumSet<Scope> getApplicableFiles() {
+ return Scope.OTHER_SCOPE;
+ }
+
+ // ---- Dummy implementations to make implementing an GradleScanner easier: ----
+
+ public void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData) {
+ }
+
+ // ---- Dummy implementations to make implementing a resource folder scanner easier: ----
+
+ public void checkFolder(@NonNull ResourceContext context, @NonNull String folderName) {
+ }
+
+ // ---- Dummy implementations to make implementing a binary resource scanner easier: ----
+
+ public void checkBinaryResource(@NonNull ResourceContext context) {
+ }
+
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return true;
+ }
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
new file mode 100644
index 0000000..b6009b9
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.SdkConstants.CLASS_CONTEXT;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.LintDriver;
+import com.google.common.collect.Iterators;
+
+import java.io.File;
+import java.util.Iterator;
+
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.EnumConstant;
+import lombok.ast.Expression;
+import lombok.ast.LabelledStatement;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Position;
+import lombok.ast.TypeDeclaration;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableReference;
+
+/**
+ * A {@link Context} used when checking Java files.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public class JavaContext extends Context {
+ static final String SUPPRESS_COMMENT_PREFIX = "//noinspection "; //$NON-NLS-1$
+
+ /** The parse tree */
+ private Node mCompilationUnit;
+
+ /** The parser which produced the parse tree */
+ private final JavaParser mParser;
+
+ /**
+ * Constructs a {@link JavaContext} for running lint on the given file, with
+ * the given scope, in the given project reporting errors to the given
+ * client.
+ *
+ * @param driver the driver running through the checks
+ * @param project the project to run lint on which contains the given file
+ * @param main the main project if this project is a library project, or
+ * null if this is not a library project. The main project is
+ * the root project of all library projects, not necessarily the
+ * directly including project.
+ * @param file the file to be analyzed
+ * @param parser the parser to use
+ */
+ public JavaContext(
+ @NonNull LintDriver driver,
+ @NonNull Project project,
+ @Nullable Project main,
+ @NonNull File file,
+ @NonNull JavaParser parser) {
+ super(driver, project, main, file);
+ mParser = parser;
+ }
+
+ /**
+ * Returns a location for the given node
+ *
+ * @param node the AST node to get a location for
+ * @return a location for the given node
+ */
+ @NonNull
+ public Location getLocation(@NonNull Node node) {
+ return mParser.getLocation(this, node);
+ }
+
+ /**
+ * Returns a location for the given node range (from the starting offset of the first node to
+ * the ending offset of the second node).
+ *
+ * @param from the AST node to get a starting location from
+ * @param fromDelta Offset delta to apply to the starting offset
+ * @param to the AST node to get a ending location from
+ * @param toDelta Offset delta to apply to the ending offset
+ * @return a location for the given node
+ */
+ @NonNull
+ public Location getRangeLocation(
+ @NonNull Node from,
+ int fromDelta,
+ @NonNull Node to,
+ int toDelta) {
+ return mParser.getRangeLocation(this, from, fromDelta, to, toDelta);
+ }
+
+ /**
+ * Returns a {@link Location} for the given node. This attempts to pick a shorter
+ * location range than the entire node; for a class or method for example, it picks
+ * the name node (if found). For statement constructs such as a {@code switch} statement
+ * it will highlight the keyword, etc.
+ *
+ * @param node the AST node to create a location for
+ * @return a location for the given node
+ */
+ @NonNull
+ public Location getNameLocation(@NonNull Node node) {
+ return mParser.getNameLocation(this, node);
+ }
+
+ @NonNull
+ public JavaParser getParser() {
+ return mParser;
+ }
+
+ @Nullable
+ public Node getCompilationUnit() {
+ return mCompilationUnit;
+ }
+
+ /**
+ * Sets the compilation result. Not intended for client usage; the lint infrastructure
+ * will set this when a context has been processed
+ *
+ * @param compilationUnit the parse tree
+ */
+ public void setCompilationUnit(@Nullable Node compilationUnit) {
+ mCompilationUnit = compilationUnit;
+ }
+
+ @Override
+ public void report(@NonNull Issue issue, @Nullable Location location,
+ @NonNull String message) {
+ if (mDriver.isSuppressed(this, issue, mCompilationUnit)) {
+ return;
+ }
+ super.report(issue, location, message);
+ }
+
+ /**
+ * Reports an issue applicable to a given AST node. The AST node is used as the
+ * scope to check for suppress lint annotations.
+ *
+ * @param issue the issue to report
+ * @param scope the AST node scope the error applies to. The lint infrastructure
+ * will check whether there are suppress annotations on this node (or its enclosing
+ * nodes) and if so suppress the warning without involving the client.
+ * @param location the location of the issue, or null if not known
+ * @param message the message for this warning
+ */
+ public void report(
+ @NonNull Issue issue,
+ @Nullable Node scope,
+ @Nullable Location location,
+ @NonNull String message) {
+ if (scope != null && mDriver.isSuppressed(this, issue, scope)) {
+ return;
+ }
+ super.report(issue, location, message);
+ }
+
+ /**
+ * Report an error.
+ * Like {@link #report(Issue, Node, Location, String)} but with
+ * a now-unused data parameter at the end.
+ *
+ * @deprecated Use {@link #report(Issue, Node, Location, String)} instead;
+ * this method is here for custom rule compatibility
+ */
+ @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules
+ @Deprecated
+ public void report(
+ @NonNull Issue issue,
+ @Nullable Node scope,
+ @Nullable Location location,
+ @NonNull String message,
+ @SuppressWarnings("UnusedParameters") @Nullable Object data) {
+ report(issue, scope, location, message);
+ }
+
+ @Nullable
+ public static Node findSurroundingMethod(Node scope) {
+ while (scope != null) {
+ Class<? extends Node> type = scope.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) {
+ return scope;
+ }
+
+ scope = scope.getParent();
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public static ClassDeclaration findSurroundingClass(@Nullable Node scope) {
+ while (scope != null) {
+ Class<? extends Node> type = scope.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == ClassDeclaration.class) {
+ return (ClassDeclaration) scope;
+ }
+
+ scope = scope.getParent();
+ }
+
+ return null;
+ }
+
+ @Override
+ @Nullable
+ protected String getSuppressCommentPrefix() {
+ return SUPPRESS_COMMENT_PREFIX;
+ }
+
+ public boolean isSuppressedWithComment(@NonNull Node scope, @NonNull Issue issue) {
+ // Check whether there is a comment marker
+ String contents = getContents();
+ assert contents != null; // otherwise we wouldn't be here
+ Position position = scope.getPosition();
+ if (position == null) {
+ return false;
+ }
+
+ int start = position.getStart();
+ return isSuppressedWithComment(start, issue);
+ }
+
+ @NonNull
+ public Location.Handle createLocationHandle(@NonNull Node node) {
+ return mParser.createLocationHandle(this, node);
+ }
+
+ @Nullable
+ public ResolvedNode resolve(@NonNull Node node) {
+ return mParser.resolve(this, node);
+ }
+
+ @Nullable
+ public ResolvedClass findClass(@NonNull String fullyQualifiedName) {
+ return mParser.findClass(this, fullyQualifiedName);
+ }
+
+ @Nullable
+ public TypeDescriptor getType(@NonNull Node node) {
+ return mParser.getType(this, node);
+ }
+
+ @Nullable
+ public static String getMethodName(@NonNull Node call) {
+ if (call instanceof MethodInvocation) {
+ return ((MethodInvocation)call).astName().astValue();
+ } else if (call instanceof ConstructorInvocation) {
+ return ((ConstructorInvocation)call).astTypeReference().getTypeName();
+ } else if (call instanceof EnumConstant) {
+ return ((EnumConstant)call).astName().astValue();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Searches for a name node corresponding to the given node
+ * @return the name node to use, if applicable
+ */
+ @Nullable
+ public static Node findNameNode(@NonNull Node node) {
+ if (node instanceof TypeDeclaration) {
+ // ClassDeclaration, AnnotationDeclaration, EnumDeclaration, InterfaceDeclaration
+ return ((TypeDeclaration) node).astName();
+ } else if (node instanceof MethodDeclaration) {
+ return ((MethodDeclaration)node).astMethodName();
+ } else if (node instanceof ConstructorDeclaration) {
+ return ((ConstructorDeclaration)node).astTypeName();
+ } else if (node instanceof MethodInvocation) {
+ return ((MethodInvocation)node).astName();
+ } else if (node instanceof ConstructorInvocation) {
+ return ((ConstructorInvocation)node).astTypeReference();
+ } else if (node instanceof EnumConstant) {
+ return ((EnumConstant)node).astName();
+ } else if (node instanceof AnnotationElement) {
+ return ((AnnotationElement)node).astName();
+ } else if (node instanceof AnnotationMethodDeclaration) {
+ return ((AnnotationMethodDeclaration)node).astMethodName();
+ } else if (node instanceof VariableReference) {
+ return ((VariableReference)node).astIdentifier();
+ } else if (node instanceof LabelledStatement) {
+ return ((LabelledStatement)node).astLabel();
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public static Iterator<Expression> getParameters(@NonNull Node call) {
+ if (call instanceof MethodInvocation) {
+ return ((MethodInvocation) call).astArguments().iterator();
+ } else if (call instanceof ConstructorInvocation) {
+ return ((ConstructorInvocation) call).astArguments().iterator();
+ } else if (call instanceof EnumConstant) {
+ return ((EnumConstant) call).astArguments().iterator();
+ } else {
+ return Iterators.emptyIterator();
+ }
+ }
+
+ @Nullable
+ public static Node getParameter(@NonNull Node call, int parameter) {
+ Iterator<Expression> iterator = getParameters(call);
+
+ for (int i = 0; i < parameter - 1; i++) {
+ if (!iterator.hasNext()) {
+ return null;
+ }
+ iterator.next();
+ }
+ return iterator.hasNext() ? iterator.next() : null;
+ }
+
+ /**
+ * Returns true if the given method invocation node corresponds to a call on a
+ * {@code android.content.Context}
+ *
+ * @param node the method call node
+ * @return true iff the method call is on a class extending context
+ */
+ public boolean isContextMethod(@NonNull MethodInvocation node) {
+ // Method name used in many other contexts where it doesn't have the
+ // same semantics; only use this one if we can resolve types
+ // and we're certain this is the Context method
+ ResolvedNode resolved = resolve(node);
+ if (resolved instanceof JavaParser.ResolvedMethod) {
+ JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
+ ResolvedClass containingClass = method.getContainingClass();
+ if (containingClass.isSubclassOf(CLASS_CONTEXT, false)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the first ancestor node of the given type
+ *
+ * @param element the element to search from
+ * @param clz the target node type
+ * @param <T> the target node type
+ * @return the nearest ancestor node in the parent chain, or null if not found
+ */
+ @Nullable
+ public static <T extends Node> T getParentOfType(
+ @Nullable Node element,
+ @NonNull Class<T> clz) {
+ return getParentOfType(element, clz, true);
+ }
+
+ /**
+ * Returns the first ancestor node of the given type
+ *
+ * @param element the element to search from
+ * @param clz the target node type
+ * @param strict if true, do not consider the element itself, only its parents
+ * @param <T> the target node type
+ * @return the nearest ancestor node in the parent chain, or null if not found
+ */
+ @Nullable
+ public static <T extends Node> T getParentOfType(
+ @Nullable Node element,
+ @NonNull Class<T> clz,
+ boolean strict) {
+ if (element == null) {
+ return null;
+ }
+
+ if (strict) {
+ element = element.getParent();
+ }
+
+ while (element != null) {
+ if (clz.isInstance(element)) {
+ //noinspection unchecked
+ return (T) element;
+ }
+ element = element.getParent();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the first ancestor node of the given type, stopping at the given type
+ *
+ * @param element the element to search from
+ * @param clz the target node type
+ * @param strict if true, do not consider the element itself, only its parents
+ * @param terminators optional node types to terminate the search at
+ * @param <T> the target node type
+ * @return the nearest ancestor node in the parent chain, or null if not found
+ */
+ @Nullable
+ public static <T extends Node> T getParentOfType(@Nullable Node element,
+ @NonNull Class<T> clz,
+ boolean strict,
+ @NonNull Class<? extends Node>... terminators) {
+ if (element == null) {
+ return null;
+ }
+ if (strict) {
+ element = element.getParent();
+ }
+
+ while (element != null && !clz.isInstance(element)) {
+ for (Class<?> terminator : terminators) {
+ if (terminator.isInstance(element)) {
+ return null;
+ }
+ }
+ element = element.getParent();
+ }
+
+ //noinspection unchecked
+ return (T) element;
+ }
+
+ /**
+ * Returns the first sibling of the given node that is of the given class
+ *
+ * @param sibling the sibling to search from
+ * @param clz the type to look for
+ * @param <T> the type
+ * @return the first sibling of the given type, or null
+ */
+ @Nullable
+ public static <T extends Node> T getNextSiblingOfType(@Nullable Node sibling,
+ @NonNull Class<T> clz) {
+ if (sibling == null) {
+ return null;
+ }
+ Node parent = sibling.getParent();
+ if (parent == null) {
+ return null;
+ }
+
+ Iterator<Node> iterator = parent.getChildren().iterator();
+ while (iterator.hasNext()) {
+ if (iterator.next() == sibling) {
+ break;
+ }
+ }
+
+ while (iterator.hasNext()) {
+ Node child = iterator.next();
+ if (clz.isInstance(child)) {
+ //noinspection unchecked
+ return (T) child;
+ }
+
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Returns the given argument of the given call
+ *
+ * @param call the call containing arguments
+ * @param index the index of the target argument
+ * @return the argument at the given index
+ * @throws IllegalArgumentException if index is outside the valid range
+ */
+ @NonNull
+ public static Node getArgumentNode(@NonNull MethodInvocation call, int index) {
+ int i = 0;
+ for (Expression parameter : call.astArguments()) {
+ if (i == index) {
+ return parameter;
+ }
+ i++;
+ }
+ throw new IllegalArgumentException(Integer.toString(index));
+ }
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
new file mode 100644
index 0000000..a294957
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
@@ -0,0 +1,1266 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_LOCALE;
+import static com.android.SdkConstants.BIN_FOLDER;
+import static com.android.SdkConstants.DOT_GIF;
+import static com.android.SdkConstants.DOT_JPEG;
+import static com.android.SdkConstants.DOT_JPG;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.DOT_WEBP;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FN_BUILD_GRADLE;
+import static com.android.SdkConstants.ID_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.UTF_8;
+import static com.android.ide.common.resources.configuration.FolderConfiguration.QUALIFIER_SPLITTER;
+import static com.android.ide.common.resources.configuration.LocaleQualifier.BCP_47_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+import com.android.ide.common.rendering.api.ItemResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LocaleQualifier;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.repository.Revision;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.utils.PositionXmlParser;
+import com.android.utils.SdkUtils;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import lombok.ast.ImportDeclaration;
+
+
+/**
+ * Useful utility methods related to lint.
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class LintUtils {
+ // Utility class, do not instantiate
+ private LintUtils() {
+ }
+
+ /**
+ * Format a list of strings, and cut of the list at {@code maxItems} if the
+ * number of items are greater.
+ *
+ * @param strings the list of strings to print out as a comma separated list
+ * @param maxItems the maximum number of items to print
+ * @return a comma separated list
+ */
+ @NonNull
+ public static String formatList(@NonNull List<String> strings, int maxItems) {
+ StringBuilder sb = new StringBuilder(20 * strings.size());
+
+ for (int i = 0, n = strings.size(); i < n; i++) {
+ if (sb.length() > 0) {
+ sb.append(", "); //$NON-NLS-1$
+ }
+ sb.append(strings.get(i));
+
+ if (maxItems > 0 && i == maxItems - 1 && n > maxItems) {
+ sb.append(String.format("... (%1$d more)", n - i - 1));
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Determine if the given type corresponds to a resource that has a unique
+ * file
+ *
+ * @param type the resource type to check
+ * @return true if the given type corresponds to a file-type resource
+ */
+ public static boolean isFileBasedResourceType(@NonNull ResourceType type) {
+ List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);
+ for (ResourceFolderType folderType : folderTypes) {
+ if (folderType != ResourceFolderType.VALUES) {
+ return type != ResourceType.ID;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given file represents an XML file
+ *
+ * @param file the file to be checked
+ * @return true if the given file is an xml file
+ */
+ public static boolean isXmlFile(@NonNull File file) {
+ return SdkUtils.endsWithIgnoreCase(file.getPath(), DOT_XML);
+ }
+
+ /**
+ * Returns true if the given file represents a bitmap drawable file
+ *
+ * @param file the file to be checked
+ * @return true if the given file is an xml file
+ */
+ public static boolean isBitmapFile(@NonNull File file) {
+ String path = file.getPath();
+ // endsWith(name, DOT_PNG) is also true for endsWith(name, DOT_9PNG)
+ return endsWith(path, DOT_PNG)
+ || endsWith(path, DOT_JPG)
+ || endsWith(path, DOT_GIF)
+ || endsWith(path, DOT_JPEG)
+ || endsWith(path, DOT_WEBP);
+ }
+
+ /**
+ * Case insensitive ends with
+ *
+ * @param string the string to be tested whether it ends with the given
+ * suffix
+ * @param suffix the suffix to check
+ * @return true if {@code string} ends with {@code suffix},
+ * case-insensitively.
+ */
+ public static boolean endsWith(@NonNull String string, @NonNull String suffix) {
+ return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
+ suffix, 0, suffix.length());
+ }
+
+ /**
+ * Case insensitive starts with
+ *
+ * @param string the string to be tested whether it starts with the given prefix
+ * @param prefix the prefix to check
+ * @param offset the offset to start checking with
+ * @return true if {@code string} starts with {@code prefix},
+ * case-insensitively.
+ */
+ public static boolean startsWith(@NonNull String string, @NonNull String prefix, int offset) {
+ return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Returns the basename of the given filename, unless it's a dot-file such as ".svn".
+ *
+ * @param fileName the file name to extract the basename from
+ * @return the basename (the filename without the file extension)
+ */
+ public static String getBaseName(@NonNull String fileName) {
+ int extension = fileName.indexOf('.');
+ if (extension > 0) {
+ return fileName.substring(0, extension);
+ } else {
+ return fileName;
+ }
+ }
+
+ /**
+ * Returns the children elements of the given node
+ *
+ * @param node the parent node
+ * @return a list of element children, never null
+ */
+ @NonNull
+ public static List<Element> getChildren(@NonNull Node node) {
+ NodeList childNodes = node.getChildNodes();
+ List<Element> children = new ArrayList<Element>(childNodes.getLength());
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ children.add((Element) child);
+ }
+ }
+
+ return children;
+ }
+
+ /**
+ * Returns the <b>number</b> of children of the given node
+ *
+ * @param node the parent node
+ * @return the count of element children
+ */
+ public static int getChildCount(@NonNull Node node) {
+ NodeList childNodes = node.getChildNodes();
+ int childCount = 0;
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ childCount++;
+ }
+ }
+
+ return childCount;
+ }
+
+ /**
+ * Returns true if the given element is the root element of its document
+ *
+ * @param element the element to test
+ * @return true if the element is the root element
+ */
+ public static boolean isRootElement(Element element) {
+ return element == element.getOwnerDocument().getDocumentElement();
+ }
+
+ /**
+ * Returns the corresponding R field name for the given XML resource name
+ * @param styleName the XML name
+ * @return the corresponding R field name
+ */
+ public static String getFieldName(@NonNull String styleName) {
+ for (int i = 0, n = styleName.length(); i < n; i++) {
+ char c = styleName.charAt(i);
+ if (c == '.' || c == '-' || c == ':') {
+ return styleName.replace('.', '_').replace('-', '_').replace(':', '_');
+ }
+ }
+
+ return styleName;
+ }
+
+ /**
+ * Returns the given id without an {@code @id/} or {@code @+id} prefix
+ *
+ * @param id the id to strip
+ * @return the stripped id, never null
+ */
+ @NonNull
+ public static String stripIdPrefix(@Nullable String id) {
+ if (id == null) {
+ return "";
+ } else if (id.startsWith(NEW_ID_PREFIX)) {
+ return id.substring(NEW_ID_PREFIX.length());
+ } else if (id.startsWith(ID_PREFIX)) {
+ return id.substring(ID_PREFIX.length());
+ }
+
+ return id;
+ }
+
+ /**
+ * Returns true if the given two id references match. This is similar to
+ * String equality, but it also considers "{@code @+id/foo == @id/foo}.
+ *
+ * @param id1 the first id to compare
+ * @param id2 the second id to compare
+ * @return true if the two id references refer to the same id
+ */
+ public static boolean idReferencesMatch(@Nullable String id1, @Nullable String id2) {
+ if (id1 == null || id2 == null || id1.isEmpty() || id2.isEmpty()) {
+ return false;
+ }
+ if (id1.startsWith(NEW_ID_PREFIX)) {
+ if (id2.startsWith(NEW_ID_PREFIX)) {
+ return id1.equals(id2);
+ } else {
+ assert id2.startsWith(ID_PREFIX) : id2;
+ return ((id1.length() - id2.length())
+ == (NEW_ID_PREFIX.length() - ID_PREFIX.length()))
+ && id1.regionMatches(NEW_ID_PREFIX.length(), id2,
+ ID_PREFIX.length(),
+ id2.length() - ID_PREFIX.length());
+ }
+ } else {
+ assert id1.startsWith(ID_PREFIX) : id1;
+ if (id2.startsWith(ID_PREFIX)) {
+ return id1.equals(id2);
+ } else {
+ assert id2.startsWith(NEW_ID_PREFIX);
+ return (id2.length() - id1.length()
+ == (NEW_ID_PREFIX.length() - ID_PREFIX.length()))
+ && id2.regionMatches(NEW_ID_PREFIX.length(), id1,
+ ID_PREFIX.length(),
+ id1.length() - ID_PREFIX.length());
+ }
+ }
+ }
+
+ /**
+ * Computes the edit distance (number of insertions, deletions or substitutions
+ * to edit one string into the other) between two strings. In particular,
+ * this will compute the Levenshtein distance.
+ * <p>
+ * See http://en.wikipedia.org/wiki/Levenshtein_distance for details.
+ *
+ * @param s the first string to compare
+ * @param t the second string to compare
+ * @return the edit distance between the two strings
+ */
+ public static int editDistance(@NonNull String s, @NonNull String t) {
+ int m = s.length();
+ int n = t.length();
+ int[][] d = new int[m + 1][n + 1];
+ for (int i = 0; i <= m; i++) {
+ d[i][0] = i;
+ }
+ for (int j = 0; j <= n; j++) {
+ d[0][j] = j;
+ }
+ for (int j = 1; j <= n; j++) {
+ for (int i = 1; i <= m; i++) {
+ if (s.charAt(i - 1) == t.charAt(j - 1)) {
+ d[i][j] = d[i - 1][j - 1];
+ } else {
+ int deletion = d[i - 1][j] + 1;
+ int insertion = d[i][j - 1] + 1;
+ int substitution = d[i - 1][j - 1] + 1;
+ d[i][j] = Math.min(deletion, Math.min(insertion, substitution));
+ }
+ }
+ }
+
+ return d[m][n];
+ }
+
+ /**
+ * Returns true if assertions are enabled
+ *
+ * @return true if assertions are enabled
+ */
+ @SuppressWarnings("all")
+ public static boolean assertionsEnabled() {
+ boolean assertionsEnabled = false;
+ assert assertionsEnabled = true; // Intentional side-effect
+ return assertionsEnabled;
+ }
+
+ /**
+ * Returns the layout resource name for the given layout file
+ *
+ * @param layoutFile the file pointing to the layout
+ * @return the layout resource name, not including the {@code @layout}
+ * prefix
+ */
+ public static String getLayoutName(File layoutFile) {
+ String name = layoutFile.getName();
+ int dotIndex = name.indexOf('.');
+ if (dotIndex != -1) {
+ name = name.substring(0, dotIndex);
+ }
+ return name;
+ }
+
+ /**
+ * Splits the given path into its individual parts, attempting to be
+ * tolerant about path separators (: or ;). It can handle possibly ambiguous
+ * paths, such as {@code c:\foo\bar:\other}, though of course these are to
+ * be avoided if possible.
+ *
+ * @param path the path variable to split, which can use both : and ; as
+ * path separators.
+ * @return the individual path components as an Iterable of strings
+ */
+ public static Iterable<String> splitPath(@NonNull String path) {
+ if (path.indexOf(';') != -1) {
+ return Splitter.on(';').omitEmptyStrings().trimResults().split(path);
+ }
+
+ List<String> combined = new ArrayList<String>();
+ Iterables.addAll(combined, Splitter.on(':').omitEmptyStrings().trimResults().split(path));
+ for (int i = 0, n = combined.size(); i < n; i++) {
+ String p = combined.get(i);
+ if (p.length() == 1 && i < n - 1 && Character.isLetter(p.charAt(0))
+ // Technically, Windows paths do not have to have a \ after the :,
+ // which means it would be using the current directory on that drive,
+ // but that's unlikely to be the case in a path since it would have
+ // unpredictable results
+ && !combined.get(i+1).isEmpty() && combined.get(i+1).charAt(0) == '\\') {
+ combined.set(i, p + ':' + combined.get(i+1));
+ combined.remove(i+1);
+ n--;
+ continue;
+ }
+ }
+
+ return combined;
+ }
+
+ /**
+ * Computes the shared parent among a set of files (which may be null).
+ *
+ * @param files the set of files to be checked
+ * @return the closest common ancestor file, or null if none was found
+ */
+ @Nullable
+ public static File getCommonParent(@NonNull List<File> files) {
+ int fileCount = files.size();
+ if (fileCount == 0) {
+ return null;
+ } else if (fileCount == 1) {
+ return files.get(0);
+ } else if (fileCount == 2) {
+ return getCommonParent(files.get(0), files.get(1));
+ } else {
+ File common = files.get(0);
+ for (int i = 1; i < fileCount; i++) {
+ common = getCommonParent(common, files.get(i));
+ if (common == null) {
+ return null;
+ }
+ }
+
+ return common;
+ }
+ }
+
+ /**
+ * Computes the closest common parent path between two files.
+ *
+ * @param file1 the first file to be compared
+ * @param file2 the second file to be compared
+ * @return the closest common ancestor file, or null if the two files have
+ * no common parent
+ */
+ @Nullable
+ public static File getCommonParent(@NonNull File file1, @NonNull File file2) {
+ if (file1.equals(file2)) {
+ return file1;
+ } else if (file1.getPath().startsWith(file2.getPath())) {
+ return file2;
+ } else if (file2.getPath().startsWith(file1.getPath())) {
+ return file1;
+ } else {
+ // Dumb and simple implementation
+ File first = file1.getParentFile();
+ while (first != null) {
+ File second = file2.getParentFile();
+ while (second != null) {
+ if (first.equals(second)) {
+ return first;
+ }
+ second = second.getParentFile();
+ }
+
+ first = first.getParentFile();
+ }
+ }
+ return null;
+ }
+
+ private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$
+ private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$
+
+ /**
+ * Returns the encoded String for the given file. This is usually the
+ * same as {@code Files.toString(file, Charsets.UTF8}, but if there's a UTF byte order mark
+ * (for UTF8, UTF_16 or UTF_16LE), use that instead.
+ *
+ * @param client the client to use for I/O operations
+ * @param file the file to read from
+ * @return the string
+ * @throws IOException if the file cannot be read properly
+ */
+ @NonNull
+ public static String getEncodedString(
+ @NonNull LintClient client,
+ @NonNull File file) throws IOException {
+ byte[] bytes = client.readBytes(file);
+ if (endsWith(file.getName(), DOT_XML)) {
+ return PositionXmlParser.getXmlString(bytes);
+ }
+
+ return getEncodedString(bytes);
+ }
+
+ /**
+ * Returns the String corresponding to the given data. This is usually the
+ * same as {@code new String(data)}, but if there's a UTF byte order mark
+ * (for UTF8, UTF_16 or UTF_16LE), use that instead.
+ * <p>
+ * NOTE: For XML files, there is the additional complication that there
+ * could be a {@code encoding=} attribute in the prologue. For those files,
+ * use {@link PositionXmlParser#getXmlString(byte[])} instead.
+ *
+ * @param data the byte array to construct the string from
+ * @return the string
+ */
+ @NonNull
+ public static String getEncodedString(@Nullable byte[] data) {
+ if (data == null) {
+ return "";
+ }
+
+ int offset = 0;
+ String defaultCharset = UTF_8;
+ String charset = null;
+ // Look for the byte order mark, to see if we need to remove bytes from
+ // the input stream (and to determine whether files are big endian or little endian) etc
+ // for files which do not specify the encoding.
+ // See http://unicode.org/faq/utf_bom.html#BOM for more.
+ if (data.length > 4) {
+ if (data[0] == (byte)0xef && data[1] == (byte)0xbb && data[2] == (byte)0xbf) {
+ // UTF-8
+ defaultCharset = charset = UTF_8;
+ offset += 3;
+ } else if (data[0] == (byte)0xfe && data[1] == (byte)0xff) {
+ // UTF-16, big-endian
+ defaultCharset = charset = UTF_16;
+ offset += 2;
+ } else if (data[0] == (byte)0x0 && data[1] == (byte)0x0
+ && data[2] == (byte)0xfe && data[3] == (byte)0xff) {
+ // UTF-32, big-endian
+ defaultCharset = charset = "UTF_32"; //$NON-NLS-1$
+ offset += 4;
+ } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe
+ && data[2] == (byte)0x0 && data[3] == (byte)0x0) {
+ // UTF-32, little-endian. We must check for this *before* looking for
+ // UTF_16LE since UTF_32LE has the same prefix!
+ defaultCharset = charset = "UTF_32LE"; //$NON-NLS-1$
+ offset += 4;
+ } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe) {
+ // UTF-16, little-endian
+ defaultCharset = charset = UTF_16LE;
+ offset += 2;
+ }
+ }
+ int length = data.length - offset;
+
+ // Guess encoding by searching for an encoding= entry in the first line.
+ boolean seenOddZero = false;
+ boolean seenEvenZero = false;
+ for (int lineEnd = offset; lineEnd < data.length; lineEnd++) {
+ if (data[lineEnd] == 0) {
+ if ((lineEnd - offset) % 2 == 0) {
+ seenEvenZero = true;
+ } else {
+ seenOddZero = true;
+ }
+ } else if (data[lineEnd] == '\n' || data[lineEnd] == '\r') {
+ break;
+ }
+ }
+
+ if (charset == null) {
+ charset = seenOddZero ? UTF_16LE : seenEvenZero ? UTF_16 : UTF_8;
+ }
+
+ String text = null;
+ try {
+ text = new String(data, offset, length, charset);
+ } catch (UnsupportedEncodingException e) {
+ try {
+ if (!charset.equals(defaultCharset)) {
+ text = new String(data, offset, length, defaultCharset);
+ }
+ } catch (UnsupportedEncodingException u) {
+ // Just use the default encoding below
+ }
+ }
+ if (text == null) {
+ text = new String(data, offset, length);
+ }
+ return text;
+ }
+
+ /**
+ * Returns true if the given class node represents a static inner class.
+ *
+ * @param classNode the inner class to be checked
+ * @return true if the class node represents an inner class that is static
+ */
+ public static boolean isStaticInnerClass(@NonNull ClassNode classNode) {
+ // Note: We can't just filter out static inner classes like this:
+ // (classNode.access & Opcodes.ACC_STATIC) != 0
+ // because the static flag only appears on methods and fields in the class
+ // file. Instead, look for the synthetic this pointer.
+
+ @SuppressWarnings("rawtypes") // ASM API
+ List fieldList = classNode.fields;
+ for (Object f : fieldList) {
+ FieldNode field = (FieldNode) f;
+ if (field.name.startsWith("this$") && (field.access & Opcodes.ACC_SYNTHETIC) != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the given class node represents an anonymous inner class
+ *
+ * @param classNode the class to be checked
+ * @return true if the class appears to be an anonymous class
+ */
+ public static boolean isAnonymousClass(@NonNull ClassNode classNode) {
+ if (classNode.outerClass == null) {
+ return false;
+ }
+
+ String name = classNode.name;
+ int index = name.lastIndexOf('$');
+ if (index == -1 || index == name.length() - 1) {
+ return false;
+ }
+
+ return Character.isDigit(name.charAt(index + 1));
+ }
+
+ /**
+ * Returns the previous opcode prior to the given node, ignoring label and
+ * line number nodes
+ *
+ * @param node the node to look up the previous opcode for
+ * @return the previous opcode, or {@link Opcodes#NOP} if no previous node
+ * was found
+ */
+ public static int getPrevOpcode(@NonNull AbstractInsnNode node) {
+ AbstractInsnNode prev = getPrevInstruction(node);
+ if (prev != null) {
+ return prev.getOpcode();
+ } else {
+ return Opcodes.NOP;
+ }
+ }
+
+ /**
+ * Returns the previous instruction prior to the given node, ignoring label
+ * and line number nodes.
+ *
+ * @param node the node to look up the previous instruction for
+ * @return the previous instruction, or null if no previous node was found
+ */
+ @Nullable
+ public static AbstractInsnNode getPrevInstruction(@NonNull AbstractInsnNode node) {
+ AbstractInsnNode prev = node;
+ while (true) {
+ prev = prev.getPrevious();
+ if (prev == null) {
+ return null;
+ } else {
+ int type = prev.getType();
+ if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL
+ && type != AbstractInsnNode.FRAME) {
+ return prev;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the next opcode after to the given node, ignoring label and line
+ * number nodes
+ *
+ * @param node the node to look up the next opcode for
+ * @return the next opcode, or {@link Opcodes#NOP} if no next node was found
+ */
+ public static int getNextOpcode(@NonNull AbstractInsnNode node) {
+ AbstractInsnNode next = getNextInstruction(node);
+ if (next != null) {
+ return next.getOpcode();
+ } else {
+ return Opcodes.NOP;
+ }
+ }
+
+ /**
+ * Returns the next instruction after to the given node, ignoring label and
+ * line number nodes.
+ *
+ * @param node the node to look up the next node for
+ * @return the next instruction, or null if no next node was found
+ */
+ @Nullable
+ public static AbstractInsnNode getNextInstruction(@NonNull AbstractInsnNode node) {
+ AbstractInsnNode next = node;
+ while (true) {
+ next = next.getNext();
+ if (next == null) {
+ return null;
+ } else {
+ int type = next.getType();
+ if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL
+ && type != AbstractInsnNode.FRAME) {
+ return next;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given directory is a lint manifest file directory.
+ *
+ * @param dir the directory to check
+ * @return true if the directory contains a manifest file
+ */
+ public static boolean isManifestFolder(File dir) {
+ boolean hasManifest = new File(dir, ANDROID_MANIFEST_XML).exists();
+ if (hasManifest) {
+ // Special case: the bin/ folder can also contain a copy of the
+ // manifest file, but this is *not* a project directory
+ if (dir.getName().equals(BIN_FOLDER)) {
+ // ...unless of course it just *happens* to be a project named bin, in
+ // which case we peek at its parent to see if this is the case
+ dir = dir.getParentFile();
+ //noinspection ConstantConditions
+ if (dir != null && isManifestFolder(dir)) {
+ // Yes, it's a bin/ directory inside a real project: ignore this dir
+ return false;
+ }
+ }
+ }
+
+ return hasManifest;
+ }
+
+ /**
+ * Look up the locale and region from the given parent folder name and
+ * return it as a combined string, such as "en", "en-rUS", b+eng-US, etc, or null if
+ * no language is specified.
+ *
+ * @param folderName the folder name
+ * @return the locale+region string or null
+ */
+ @Nullable
+ public static String getLocaleAndRegion(@NonNull String folderName) {
+ if (folderName.indexOf('-') == -1) {
+ return null;
+ }
+
+ String locale = null;
+
+ for (String qualifier : QUALIFIER_SPLITTER.split(folderName)) {
+ int qualifierLength = qualifier.length();
+ if (qualifierLength == 2) {
+ char first = qualifier.charAt(0);
+ char second = qualifier.charAt(1);
+ if (first >= 'a' && first <= 'z' && second >= 'a' && second <= 'z') {
+ locale = qualifier;
+ }
+ } else if (qualifierLength == 3 && qualifier.charAt(0) == 'r' && locale != null) {
+ char first = qualifier.charAt(1);
+ char second = qualifier.charAt(2);
+ if (first >= 'A' && first <= 'Z' && second >= 'A' && second <= 'Z') {
+ return locale + '-' + qualifier;
+ }
+ break;
+ } else if (qualifier.startsWith(BCP_47_PREFIX)) {
+ return qualifier;
+ }
+ }
+
+ return locale;
+ }
+
+ /**
+ * Returns true if the given class (specified by a fully qualified class
+ * name) name is imported in the given compilation unit either through a fully qualified
+ * import or by a wildcard import.
+ *
+ * @param compilationUnit the compilation unit
+ * @param fullyQualifiedName the fully qualified class name
+ * @return true if the given imported name refers to the given fully
+ * qualified name
+ */
+ public static boolean isImported(
+ @Nullable lombok.ast.Node compilationUnit,
+ @NonNull String fullyQualifiedName) {
+ if (compilationUnit == null) {
+ return false;
+ }
+ int dotIndex = fullyQualifiedName.lastIndexOf('.');
+ int dotLength = fullyQualifiedName.length() - dotIndex;
+
+ boolean imported = false;
+ for (lombok.ast.Node rootNode : compilationUnit.getChildren()) {
+ if (rootNode instanceof ImportDeclaration) {
+ ImportDeclaration importDeclaration = (ImportDeclaration) rootNode;
+ String fqn = importDeclaration.asFullyQualifiedName();
+ if (fqn.equals(fullyQualifiedName)) {
+ return true;
+ } else if (fullyQualifiedName.regionMatches(dotIndex, fqn,
+ fqn.length() - dotLength, dotLength)) {
+ // This import is importing the class name using some other prefix, so there
+ // fully qualified class name cannot be imported under that name
+ return false;
+ } else if (importDeclaration.astStarImport()
+ && fqn.regionMatches(0, fqn, 0, dotIndex + 1)) {
+ imported = true;
+ // but don't break -- keep searching in case there's a non-wildcard
+ // import of the specific class name, e.g. if we're looking for
+ // android.content.SharedPreferences.Editor, don't match on the following:
+ // import android.content.SharedPreferences.*;
+ // import foo.bar.Editor;
+ }
+ }
+ }
+
+ return imported;
+ }
+
+ /**
+ * Looks up the resource values for the given attribute given a style. Note that
+ * this only looks project-level style values, it does not resume into the framework
+ * styles.
+ */
+ @Nullable
+ public static List<ResourceValue> getStyleAttributes(
+ @NonNull Project project, @NonNull LintClient client,
+ @NonNull String styleUrl, @NonNull String namespace, @NonNull String attribute) {
+ if (!client.supportsProjectResources()) {
+ return null;
+ }
+
+ AbstractResourceRepository resources = client.getProjectResources(project, true);
+ if (resources == null) {
+ return null;
+ }
+
+ ResourceUrl style = ResourceUrl.parse(styleUrl);
+ if (style == null || style.framework) {
+ return null;
+ }
+
+ List<ResourceValue> result = null;
+
+ Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>();
+ queue.add(new ResourceValue(style.type, style.name, false));
+ Set<String> seen = Sets.newHashSet();
+ int count = 0;
+ boolean isFrameworkAttribute = ANDROID_URI.equals(namespace);
+ while (count < 30 && !queue.isEmpty()) {
+ ResourceValue front = queue.remove();
+ String name = front.getName();
+ seen.add(name);
+ List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name);
+ if (items != null) {
+ for (ResourceItem item : items) {
+ ResourceValue rv = item.getResourceValue(false);
+ if (rv instanceof StyleResourceValue) {
+ StyleResourceValue srv = (StyleResourceValue) rv;
+ ItemResourceValue value = srv.getItem(attribute, isFrameworkAttribute);
+ if (value != null) {
+ if (result == null) {
+ result = Lists.newArrayList();
+ }
+ if (!result.contains(value)) {
+ result.add(value);
+ }
+ }
+
+ String parent = srv.getParentStyle();
+ if (parent != null && !parent.startsWith(ANDROID_PREFIX)) {
+ ResourceUrl p = ResourceUrl.parse(parent);
+ if (p != null && !p.framework && !seen.contains(p.name)) {
+ seen.add(p.name);
+ queue.add(new ResourceValue(ResourceType.STYLE, p.name,
+ false));
+ }
+ }
+
+ int index = name.lastIndexOf('.');
+ if (index > 0) {
+ String parentName = name.substring(0, index);
+ if (!seen.contains(parentName)) {
+ seen.add(parentName);
+ queue.add(new ResourceValue(ResourceType.STYLE, parentName,
+ false));
+ }
+ }
+ }
+ }
+ }
+
+ count++;
+ }
+
+ return result;
+ }
+
+ @Nullable
+ public static List<StyleResourceValue> getInheritedStyles(
+ @NonNull Project project, @NonNull LintClient client,
+ @NonNull String styleUrl) {
+ if (!client.supportsProjectResources()) {
+ return null;
+ }
+
+ AbstractResourceRepository resources = client.getProjectResources(project, true);
+ if (resources == null) {
+ return null;
+ }
+
+ ResourceUrl style = ResourceUrl.parse(styleUrl);
+ if (style == null || style.framework) {
+ return null;
+ }
+
+ List<StyleResourceValue> result = null;
+
+ Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>();
+ queue.add(new ResourceValue(style.type, style.name, false));
+ Set<String> seen = Sets.newHashSet();
+ int count = 0;
+ while (count < 30 && !queue.isEmpty()) {
+ ResourceValue front = queue.remove();
+ String name = front.getName();
+ seen.add(name);
+ List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name);
+ if (items != null) {
+ for (ResourceItem item : items) {
+ ResourceValue rv = item.getResourceValue(false);
+ if (rv instanceof StyleResourceValue) {
+ StyleResourceValue srv = (StyleResourceValue) rv;
+ if (result == null) {
+ result = Lists.newArrayList();
+ }
+ result.add(srv);
+
+ String parent = srv.getParentStyle();
+ if (parent != null && !parent.startsWith(ANDROID_PREFIX)) {
+ ResourceUrl p = ResourceUrl.parse(parent);
+ if (p != null && !p.framework && !seen.contains(p.name)) {
+ seen.add(p.name);
+ queue.add(new ResourceValue(ResourceType.STYLE, p.name,
+ false));
+ }
+ }
+
+ int index = name.lastIndexOf('.');
+ if (index > 0) {
+ String parentName = name.substring(0, index);
+ if (!seen.contains(parentName)) {
+ seen.add(parentName);
+ queue.add(new ResourceValue(ResourceType.STYLE, parentName,
+ false));
+ }
+ }
+ }
+ }
+ }
+
+ count++;
+ }
+
+ return result;
+ }
+
+ /** Returns true if the given two paths point to the same logical resource file within
+ * a source set. This means that it only checks the parent folder name and individual
+ * file name, not the path outside the parent folder.
+ *
+ * @param file1 the first file to compare
+ * @param file2 the second file to compare
+ * @return true if the two files have the same parent and file names
+ */
+ public static boolean isSameResourceFile(@Nullable File file1, @Nullable File file2) {
+ if (file1 != null && file2 != null
+ && file1.getName().equals(file2.getName())) {
+ File parent1 = file1.getParentFile();
+ File parent2 = file2.getParentFile();
+ if (parent1 != null && parent2 != null &&
+ parent1.getName().equals(parent2.getName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Whether we should attempt to look up the prefix from the model. Set to false
+ * if we encounter a model which is too old.
+ * <p>
+ * This is public such that code which for example syncs to a new gradle model
+ * can reset it.
+ */
+ public static boolean sTryPrefixLookup = true;
+
+ /** Looks up the resource prefix for the given Gradle project, if possible */
+ @Nullable
+ public static String computeResourcePrefix(@Nullable AndroidProject project) {
+ try {
+ if (sTryPrefixLookup && project != null) {
+ return project.getResourcePrefix();
+ }
+ } catch (Exception e) {
+ // This happens if we're talking to an older model than 0.10
+ // Ignore; fall through to normal handling and never try again.
+ //noinspection AssignmentToStaticFieldFromInstanceMethod
+ sTryPrefixLookup = false;
+ }
+
+ return null;
+ }
+
+ /** Computes a suggested name given a resource prefix and resource name */
+ public static String computeResourceName(@NonNull String prefix, @NonNull String name) {
+ if (prefix.isEmpty()) {
+ return name;
+ } else if (name.isEmpty()) {
+ return prefix;
+ } else if (prefix.endsWith("_")) {
+ return prefix + name;
+ } else {
+ return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ }
+ }
+
+
+ /**
+ * Convert an {@link com.android.builder.model.ApiVersion} to a {@link
+ * com.android.sdklib.AndroidVersion}. The chief problem here is that the {@link
+ * com.android.builder.model.ApiVersion}, when using a codename, will not encode the
+ * corresponding API level (it just reflects the string entered by the user in the gradle file)
+ * so we perform a search here (since lint really wants to know the actual numeric API level)
+ *
+ * @param api the api version to convert
+ * @param targets if known, the installed targets (used to resolve platform codenames, only
+ * needed to resolve platforms newer than the tools since {@link
+ * com.android.sdklib.SdkVersionInfo} knows the rest)
+ * @return the corresponding version
+ */
+ @NonNull
+ public static AndroidVersion convertVersion(
+ @NonNull ApiVersion api,
+ @Nullable IAndroidTarget[] targets) {
+ String codename = api.getCodename();
+ if (codename != null) {
+ AndroidVersion version = SdkVersionInfo.getVersion(codename, targets);
+ if (version != null) {
+ return version;
+ }
+ return new AndroidVersion(api.getApiLevel(), codename);
+ }
+ return new AndroidVersion(api.getApiLevel(), null);
+ }
+
+ /**
+ * Returns true if the given Gradle model is older than the given version number
+ */
+ public static boolean isModelOlderThan(@Nullable AndroidProject project,
+ int major, int minor, int micro) {
+ if (project != null) {
+ String modelVersion = project.getModelVersion();
+ try {
+ Revision version = Revision.parseRevision(modelVersion);
+ if (version.getMajor() != major) {
+ return version.getMajor() < major;
+ }
+ if (version.getMinor() != minor) {
+ return version.getMinor() < minor;
+ }
+ return version.getMicro() < micro;
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Looks for a certain string within a larger string, which should immediately follow
+ * the given prefix and immediately precede the given suffix.
+ *
+ * @param string the full string to search
+ * @param prefix the optional prefix to follow
+ * @param suffix the optional suffix to precede
+ * @return the corresponding substring, if present
+ */
+ @Nullable
+ public static String findSubstring(@NonNull String string, @Nullable String prefix,
+ @Nullable String suffix) {
+ int start = 0;
+ if (prefix != null) {
+ start = string.indexOf(prefix);
+ if (start == -1) {
+ return null;
+ }
+ start += prefix.length();
+ }
+
+ if (suffix != null) {
+ int end = string.indexOf(suffix, start);
+ if (end == -1) {
+ return null;
+ }
+ return string.substring(start, end);
+ }
+
+ return string.substring(start);
+ }
+
+ /**
+ * Splits up the given message coming from a given string format (where the string
+ * format follows the very specific convention of having only strings formatted exactly
+ * with the format %n$s where n is between 1 and 9 inclusive, and each formatting parameter
+ * appears exactly once, and in increasing order.
+ *
+ * @param format the format string responsible for creating the error message
+ * @param errorMessage an error message formatted with the format string
+ * @return the specific values inserted into the format
+ */
+ @NonNull
+ public static List<String> getFormattedParameters(
+ @NonNull String format,
+ @NonNull String errorMessage) {
+ StringBuilder pattern = new StringBuilder(format.length());
+ int parameter = 1;
+ for (int i = 0, n = format.length(); i < n; i++) {
+ char c = format.charAt(i);
+ if (c == '%') {
+ // Only support formats of the form %n$s where n is 1 <= n <=9
+ assert i < format.length() - 4 : format;
+ assert format.charAt(i + 1) == ('0' + parameter) : format;
+ assert Character.isDigit(format.charAt(i + 1)) : format;
+ assert format.charAt(i + 2) == '$' : format;
+ assert format.charAt(i + 3) == 's' : format;
+ parameter++;
+ i += 3;
+ pattern.append("(.*)");
+ } else {
+ pattern.append(c);
+ }
+ }
+ try {
+ Pattern compile = Pattern.compile(pattern.toString());
+ Matcher matcher = compile.matcher(errorMessage);
+ if (matcher.find()) {
+ int groupCount = matcher.groupCount();
+ List<String> parameters = Lists.newArrayListWithExpectedSize(groupCount);
+ for (int i = 1; i <= groupCount; i++) {
+ parameters.add(matcher.group(i));
+ }
+
+ return parameters;
+ }
+
+ } catch (PatternSyntaxException pse) {
+ // Internal error: string format is not valid. Should be caught by unit tests
+ // as a failure to return the formatted parameters.
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns the locale for the given parent folder.
+ *
+ * @param parent the name of the parent folder
+ * @return null if the locale is not known, or a locale qualifier providing the language
+ * and possibly region
+ */
+ @Nullable
+ public static LocaleQualifier getLocale(@NonNull String parent) {
+ if (parent.indexOf('-') != -1) {
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder(parent);
+ if (config != null) {
+ return config.getLocaleQualifier();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the locale for the given context.
+ *
+ * @param context the context to look up the locale for
+ * @return null if the locale is not known, or a locale qualifier providing the language
+ * and possibly region
+ */
+ @Nullable
+ public static LocaleQualifier getLocale(@NonNull XmlContext context) {
+ Element root = context.document.getDocumentElement();
+ if (root != null) {
+ String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
+ if (locale != null && !locale.isEmpty()) {
+ return getLocale(locale);
+ }
+ }
+
+ return getLocale(context.file.getParentFile().getName());
+ }
+
+ /**
+ * Check whether the given resource file is in an English locale
+ * @param context the XML context for the resource file
+ * @param assumeForBase whether the base folder (e.g. no locale specified) should be
+ * treated as English
+ */
+ public static boolean isEnglishResource(@NonNull XmlContext context, boolean assumeForBase) {
+ LocaleQualifier locale = LintUtils.getLocale(context);
+ if (locale == null) {
+ return assumeForBase;
+ } else {
+ return "en".equals(locale.getLanguage()); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Create a {@link Location} for an error in the top level build.gradle file.
+ * This is necessary when we're doing an analysis based on the Gradle interpreted model,
+ * not from parsing Gradle files - and the model doesn't provide source positions.
+ * @param project the project containing the gradle file being analyzed
+ * @return location for the top level gradle file if it exists, otherwise fall back to
+ * the project directory.
+ */
+ public static Location guessGradleLocation(@NonNull Project project) {
+ File dir = project.getDir();
+ Location location;
+ File topLevel = new File(dir, FN_BUILD_GRADLE);
+ if (topLevel.exists()) {
+ location = Location.create(topLevel);
+ } else {
+ location = Location.create(dir);
+ }
+ return location;
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
new file mode 100644
index 0000000..9e1b01f
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Location.java
@@ -0,0 +1,805 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+
+/**
+ * Location information for a warning
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class Location {
+ private static final String SUPER_KEYWORD = "super"; //$NON-NLS-1$
+
+ private final File mFile;
+ private final Position mStart;
+ private final Position mEnd;
+ private String mMessage;
+ private Location mSecondary;
+ private Object mClientData;
+
+ /**
+ * (Private constructor, use one of the factory methods
+ * {@link Location#create(File)},
+ * {@link Location#create(File, Position, Position)}, or
+ * {@link Location#create(File, String, int, int)}.
+ * <p>
+ * Constructs a new location range for the given file, from start to end. If
+ * the length of the range is not known, end may be null.
+ *
+ * @param file the associated file (but see the documentation for
+ * {@link #getFile()} for more information on what the file
+ * represents)
+ * @param start the starting position, or null
+ * @param end the ending position, or null
+ */
+ protected Location(@NonNull File file, @Nullable Position start, @Nullable Position end) {
+ super();
+ mFile = file;
+ mStart = start;
+ mEnd = end;
+ }
+
+ /**
+ * Returns the file containing the warning. Note that the file *itself* may
+ * not yet contain the error. When editing a file in the IDE for example,
+ * the tool could generate warnings in the background even before the
+ * document is saved. However, the file is used as a identifying token for
+ * the document being edited, and the IDE integration can map this back to
+ * error locations in the editor source code.
+ *
+ * @return the file handle for the location
+ */
+ @NonNull
+ public File getFile() {
+ return mFile;
+ }
+
+ /**
+ * The start position of the range
+ *
+ * @return the start position of the range, or null
+ */
+ @Nullable
+ public Position getStart() {
+ return mStart;
+ }
+
+ /**
+ * The end position of the range
+ *
+ * @return the start position of the range, may be null for an empty range
+ */
+ @Nullable
+ public Position getEnd() {
+ return mEnd;
+ }
+
+ /**
+ * Returns a secondary location associated with this location (if
+ * applicable), or null.
+ *
+ * @return a secondary location or null
+ */
+ @Nullable
+ public Location getSecondary() {
+ return mSecondary;
+ }
+
+ /**
+ * Sets a secondary location for this location.
+ *
+ * @param secondary a secondary location associated with this location
+ */
+ public void setSecondary(@Nullable Location secondary) {
+ mSecondary = secondary;
+ }
+
+ /**
+ * Sets a custom message for this location. This is typically used for
+ * secondary locations, to describe the significance of this alternate
+ * location. For example, for a duplicate id warning, the primary location
+ * might say "This is a duplicate id", pointing to the second occurrence of
+ * id declaration, and then the secondary location could point to the
+ * original declaration with the custom message "Originally defined here".
+ *
+ * @param message the message to apply to this location
+ */
+ public void setMessage(@NonNull String message) {
+ mMessage = message;
+ }
+
+ /**
+ * Returns the custom message for this location, if any. This is typically
+ * used for secondary locations, to describe the significance of this
+ * alternate location. For example, for a duplicate id warning, the primary
+ * location might say "This is a duplicate id", pointing to the second
+ * occurrence of id declaration, and then the secondary location could point
+ * to the original declaration with the custom message
+ * "Originally defined here".
+ *
+ * @return the custom message for this location, or null
+ */
+ @Nullable
+ public String getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Sets the client data associated with this location. This is an optional
+ * field which can be used by the creator of the {@link Location} to store
+ * temporary state associated with the location.
+ *
+ * @param clientData the data to store with this location
+ */
+ public void setClientData(@Nullable Object clientData) {
+ mClientData = clientData;
+ }
+
+ /**
+ * Returns the client data associated with this location - an optional field
+ * which can be used by the creator of the {@link Location} to store
+ * temporary state associated with the location.
+ *
+ * @return the data associated with this location
+ */
+ @Nullable
+ public Object getClientData() {
+ return mClientData;
+ }
+
+ @Override
+ public String toString() {
+ return "Location [file=" + mFile + ", start=" + mStart + ", end=" + mEnd + ", message="
+ + mMessage + ']';
+ }
+
+ /**
+ * Creates a new location for the given file
+ *
+ * @param file the file to create a location for
+ * @return a new location
+ */
+ @NonNull
+ public static Location create(@NonNull File file) {
+ return new Location(file, null /*start*/, null /*end*/);
+ }
+
+ /**
+ * Creates a new location for the given file and SourcePosition.
+ *
+ * @param file the file containing the positions
+ * @param position the source position
+ * @return a new location
+ */
+ @NonNull
+ public static Location create(
+ @NonNull File file,
+ @NonNull SourcePosition position) {
+ if (position.equals(SourcePosition.UNKNOWN)) {
+ return new Location(file, null /*start*/, null /*end*/);
+ }
+ return new Location(file,
+ new DefaultPosition(
+ position.getStartLine(),
+ position.getStartColumn(),
+ position.getStartOffset()),
+ new DefaultPosition(
+ position.getEndLine(),
+ position.getEndColumn(),
+ position.getEndOffset()));
+ }
+
+ /**
+ * Creates a new location for the given file and starting and ending
+ * positions.
+ *
+ * @param file the file containing the positions
+ * @param start the starting position
+ * @param end the ending position
+ * @return a new location
+ */
+ @NonNull
+ public static Location create(
+ @NonNull File file,
+ @NonNull Position start,
+ @Nullable Position end) {
+ return new Location(file, start, end);
+ }
+
+ /**
+ * Creates a new location for the given file, with the given contents, for
+ * the given offset range.
+ *
+ * @param file the file containing the location
+ * @param contents the current contents of the file
+ * @param startOffset the starting offset
+ * @param endOffset the ending offset
+ * @return a new location
+ */
+ @NonNull
+ public static Location create(
+ @NonNull File file,
+ @Nullable String contents,
+ int startOffset,
+ int endOffset) {
+ if (startOffset < 0 || endOffset < startOffset) {
+ throw new IllegalArgumentException("Invalid offsets");
+ }
+
+ if (contents == null) {
+ return new Location(file,
+ new DefaultPosition(-1, -1, startOffset),
+ new DefaultPosition(-1, -1, endOffset));
+ }
+
+ int size = contents.length();
+ endOffset = Math.min(endOffset, size);
+ startOffset = Math.min(startOffset, endOffset);
+ Position start = null;
+ int line = 0;
+ int lineOffset = 0;
+ char prev = 0;
+ for (int offset = 0; offset <= size; offset++) {
+ if (offset == startOffset) {
+ start = new DefaultPosition(line, offset - lineOffset, offset);
+ }
+ if (offset == endOffset) {
+ Position end = new DefaultPosition(line, offset - lineOffset, offset);
+ return new Location(file, start, end);
+ }
+ char c = contents.charAt(offset);
+ if (c == '\n') {
+ lineOffset = offset + 1;
+ if (prev != '\r') {
+ line++;
+ }
+ } else if (c == '\r') {
+ line++;
+ lineOffset = offset + 1;
+ }
+ prev = c;
+ }
+ return create(file);
+ }
+
+ /**
+ * Creates a new location for the given file, with the given contents, for
+ * the given line number.
+ *
+ * @param file the file containing the location
+ * @param contents the current contents of the file
+ * @param line the line number (0-based) for the position
+ * @return a new location
+ */
+ @NonNull
+ public static Location create(@NonNull File file, @NonNull String contents, int line) {
+ return create(file, contents, line, null, null, null);
+ }
+
+ /**
+ * Creates a new location for the given file, with the given contents, for
+ * the given line number.
+ *
+ * @param file the file containing the location
+ * @param contents the current contents of the file
+ * @param line the line number (0-based) for the position
+ * @param patternStart an optional pattern to search for from the line
+ * match; if found, adjust the column and offsets to begin at the
+ * pattern start
+ * @param patternEnd an optional pattern to search for behind the start
+ * pattern; if found, adjust the end offset to match the end of
+ * the pattern
+ * @param hints optional additional information regarding the pattern search
+ * @return a new location
+ */
+ @NonNull
+ public static Location create(@NonNull File file, @NonNull String contents, int line,
+ @Nullable String patternStart, @Nullable String patternEnd,
+ @Nullable SearchHints hints) {
+ int currentLine = 0;
+ int offset = 0;
+ while (currentLine < line) {
+ offset = contents.indexOf('\n', offset);
+ if (offset == -1) {
+ return create(file);
+ }
+ currentLine++;
+ offset++;
+ }
+
+ if (line == currentLine) {
+ if (patternStart != null) {
+ SearchDirection direction = SearchDirection.NEAREST;
+ if (hints != null) {
+ direction = hints.mDirection;
+ }
+
+ int index;
+ if (direction == SearchDirection.BACKWARD) {
+ index = findPreviousMatch(contents, offset, patternStart, hints);
+ line = adjustLine(contents, line, offset, index);
+ } else if (direction == SearchDirection.EOL_BACKWARD) {
+ int lineEnd = contents.indexOf('\n', offset);
+ if (lineEnd == -1) {
+ lineEnd = contents.length();
+ }
+
+ index = findPreviousMatch(contents, lineEnd, patternStart, hints);
+ line = adjustLine(contents, line, offset, index);
+ } else if (direction == SearchDirection.FORWARD) {
+ index = findNextMatch(contents, offset, patternStart, hints);
+ line = adjustLine(contents, line, offset, index);
+ } else {
+ assert direction == SearchDirection.NEAREST ||
+ direction == SearchDirection.EOL_NEAREST;
+
+ int lineEnd = contents.indexOf('\n', offset);
+ if (lineEnd == -1) {
+ lineEnd = contents.length();
+ }
+ offset = lineEnd;
+
+ int before = findPreviousMatch(contents, offset, patternStart, hints);
+ int after = findNextMatch(contents, offset, patternStart, hints);
+
+ if (before == -1) {
+ index = after;
+ line = adjustLine(contents, line, offset, index);
+ } else if (after == -1) {
+ index = before;
+ line = adjustLine(contents, line, offset, index);
+ } else {
+ int newLinesBefore = 0;
+ for (int i = before; i < offset; i++) {
+ if (contents.charAt(i) == '\n') {
+ newLinesBefore++;
+ }
+ }
+ int newLinesAfter = 0;
+ for (int i = offset; i < after; i++) {
+ if (contents.charAt(i) == '\n') {
+ newLinesAfter++;
+ }
+ }
+ if (newLinesBefore < newLinesAfter || newLinesBefore == newLinesAfter
+ && offset - before < after - offset) {
+ index = before;
+ line = adjustLine(contents, line, offset, index);
+ } else {
+ index = after;
+ line = adjustLine(contents, line, offset, index);
+ }
+ }
+ }
+
+ if (index != -1) {
+ int lineStart = contents.lastIndexOf('\n', index);
+ if (lineStart == -1) {
+ lineStart = 0;
+ } else {
+ lineStart++; // was pointing to the previous line's CR, not line start
+ }
+ int column = index - lineStart;
+ if (patternEnd != null) {
+ int end = contents.indexOf(patternEnd, offset + patternStart.length());
+ if (end != -1) {
+ return new Location(file, new DefaultPosition(line, column, index),
+ new DefaultPosition(line, -1, end + patternEnd.length()));
+ }
+ } else if (hints != null && (hints.isJavaSymbol() || hints.isWholeWord())) {
+ if (hints.isConstructor() && contents.startsWith(SUPER_KEYWORD, index)) {
+ patternStart = SUPER_KEYWORD;
+ }
+ return new Location(file, new DefaultPosition(line, column, index),
+ new DefaultPosition(line, column + patternStart.length(),
+ index + patternStart.length()));
+ }
+ return new Location(file, new DefaultPosition(line, column, index),
+ new DefaultPosition(line, column, index + patternStart.length()));
+ }
+ }
+
+ Position position = new DefaultPosition(line, -1, offset);
+ return new Location(file, position, position);
+ }
+
+ return create(file);
+ }
+
+ private static int findPreviousMatch(@NonNull String contents, int offset, String pattern,
+ @Nullable SearchHints hints) {
+ while (true) {
+ int index = contents.lastIndexOf(pattern, offset);
+ if (index == -1) {
+ return -1;
+ } else {
+ if (isMatch(contents, index, pattern, hints)) {
+ return index;
+ } else {
+ offset = index - pattern.length();
+ }
+ }
+ }
+ }
+
+ private static int findNextMatch(@NonNull String contents, int offset, String pattern,
+ @Nullable SearchHints hints) {
+ int constructorIndex = -1;
+ if (hints != null && hints.isConstructor()) {
+ // Special condition: See if the call is referenced as "super" instead.
+ assert hints.isWholeWord();
+ int index = contents.indexOf(SUPER_KEYWORD, offset);
+ if (index != -1 && isMatch(contents, index, SUPER_KEYWORD, hints)) {
+ constructorIndex = index;
+ }
+ }
+
+ while (true) {
+ int index = contents.indexOf(pattern, offset);
+ if (index == -1) {
+ return constructorIndex;
+ } else {
+ if (isMatch(contents, index, pattern, hints)) {
+ if (constructorIndex != -1) {
+ return Math.min(constructorIndex, index);
+ }
+ return index;
+ } else {
+ offset = index + pattern.length();
+ }
+ }
+ }
+ }
+
+ private static boolean isMatch(@NonNull String contents, int offset, String pattern,
+ @Nullable SearchHints hints) {
+ if (!contents.startsWith(pattern, offset)) {
+ return false;
+ }
+
+ if (hints != null) {
+ char prevChar = offset > 0 ? contents.charAt(offset - 1) : 0;
+ int lastIndex = offset + pattern.length() - 1;
+ char nextChar = lastIndex < contents.length() - 1 ? contents.charAt(lastIndex + 1) : 0;
+
+ if (hints.isWholeWord() && (Character.isLetter(prevChar)
+ || Character.isLetter(nextChar))) {
+ return false;
+
+ }
+
+ if (hints.isJavaSymbol()) {
+ if (Character.isJavaIdentifierPart(prevChar)
+ || Character.isJavaIdentifierPart(nextChar)) {
+ return false;
+ }
+
+ if (prevChar == '"') {
+ return false;
+ }
+
+ // TODO: Additional validation to see if we're in a comment, string, etc.
+ // This will require lexing from the beginning of the buffer.
+ }
+
+ if (hints.isConstructor() && SUPER_KEYWORD.equals(pattern)) {
+ // Only looking for super(), not super.x, so assert that the next
+ // non-space character is (
+ int index = lastIndex + 1;
+ while (index < contents.length() - 1) {
+ char c = contents.charAt(index);
+ if (c == '(') {
+ break;
+ } else if (!Character.isWhitespace(c)) {
+ return false;
+ }
+ index++;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static int adjustLine(String doc, int line, int offset, int newOffset) {
+ if (newOffset == -1) {
+ return line;
+ }
+
+ if (newOffset < offset) {
+ return line - countLines(doc, newOffset, offset);
+ } else {
+ return line + countLines(doc, offset, newOffset);
+ }
+ }
+
+ private static int countLines(String doc, int start, int end) {
+ int lines = 0;
+ for (int offset = start; offset < end; offset++) {
+ char c = doc.charAt(offset);
+ if (c == '\n') {
+ lines++;
+ }
+ }
+
+ return lines;
+ }
+
+ /**
+ * Reverses the secondary location list initiated by the given location
+ *
+ * @param location the first location in the list
+ * @return the first location in the reversed list
+ */
+ public static Location reverse(@NonNull Location location) {
+ Location next = location.getSecondary();
+ location.setSecondary(null);
+ while (next != null) {
+ Location nextNext = next.getSecondary();
+ next.setSecondary(location);
+ location = next;
+ next = nextNext;
+ }
+
+ return location;
+ }
+
+ /**
+ * A {@link Handle} is a reference to a location. The point of a location
+ * handle is to be able to create them cheaply, and then resolve them into
+ * actual locations later (if needed). This makes it possible to for example
+ * delay looking up line numbers, for locations that are offset based.
+ */
+ public interface Handle {
+ /**
+ * Compute a full location for the given handle
+ *
+ * @return create a location for this handle
+ */
+ @NonNull
+ Location resolve();
+
+ /**
+ * Sets the client data associated with this location. This is an optional
+ * field which can be used by the creator of the {@link Location} to store
+ * temporary state associated with the location.
+ *
+ * @param clientData the data to store with this location
+ */
+ void setClientData(@Nullable Object clientData);
+
+ /**
+ * Returns the client data associated with this location - an optional field
+ * which can be used by the creator of the {@link Location} to store
+ * temporary state associated with the location.
+ *
+ * @return the data associated with this location
+ */
+ @Nullable
+ Object getClientData();
+ }
+
+ /** A default {@link Handle} implementation for simple file offsets */
+ public static class DefaultLocationHandle implements Handle {
+ private final File mFile;
+ private final String mContents;
+ private final int mStartOffset;
+ private final int mEndOffset;
+ private Object mClientData;
+
+ /**
+ * Constructs a new {@link DefaultLocationHandle}
+ *
+ * @param context the context pointing to the file and its contents
+ * @param startOffset the start offset within the file
+ * @param endOffset the end offset within the file
+ */
+ public DefaultLocationHandle(@NonNull Context context, int startOffset, int endOffset) {
+ mFile = context.file;
+ mContents = context.getContents();
+ mStartOffset = startOffset;
+ mEndOffset = endOffset;
+ }
+
+ @Override
+ @NonNull
+ public Location resolve() {
+ return create(mFile, mContents, mStartOffset, mEndOffset);
+ }
+
+ @Override
+ public void setClientData(@Nullable Object clientData) {
+ mClientData = clientData;
+ }
+
+ @Override
+ @Nullable
+ public Object getClientData() {
+ return mClientData;
+ }
+ }
+
+ public static class ResourceItemHandle implements Handle {
+ private final ResourceItem mItem;
+
+ public ResourceItemHandle(@NonNull ResourceItem item) {
+ mItem = item;
+ }
+ @NonNull
+ @Override
+ public Location resolve() {
+ // TODO: Look up the exact item location more
+ // closely
+ ResourceFile source = mItem.getSource();
+ assert source != null : mItem;
+ return create(source.getFile());
+ }
+
+ @Override
+ public void setClientData(@Nullable Object clientData) {
+ }
+
+ @Nullable
+ @Override
+ public Object getClientData() {
+ return null;
+ }
+ }
+
+ /**
+ * Whether to look forwards, or backwards, or in both directions, when
+ * searching for a pattern in the source code to determine the right
+ * position range for a given symbol.
+ * <p>
+ * When dealing with bytecode for example, there are only line number entries
+ * within method bodies, so when searching for the method declaration, we should only
+ * search backwards from the first line entry in the method.
+ */
+ public enum SearchDirection {
+ /** Only search forwards */
+ FORWARD,
+
+ /** Only search backwards */
+ BACKWARD,
+
+ /** Search backwards from the current end of line (normally it's the beginning of
+ * the current line) */
+ EOL_BACKWARD,
+
+ /**
+ * Search both forwards and backwards from the given line, and prefer
+ * the match that is closest
+ */
+ NEAREST,
+
+ /**
+ * Search both forwards and backwards from the end of the given line, and prefer
+ * the match that is closest
+ */
+ EOL_NEAREST,
+ }
+
+ /**
+ * Extra information pertaining to finding a symbol in a source buffer,
+ * used by {@link Location#create(File, String, int, String, String, SearchHints)}
+ */
+ public static class SearchHints {
+ /**
+ * the direction to search for the nearest match in (provided
+ * {@code patternStart} is non null)
+ */
+ @NonNull
+ private final SearchDirection mDirection;
+
+ /** Whether the matched pattern should be a whole word */
+ private boolean mWholeWord;
+
+ /**
+ * Whether the matched pattern should be a Java symbol (so for example,
+ * a match inside a comment or string literal should not be used)
+ */
+ private boolean mJavaSymbol;
+
+ /**
+ * Whether the matched pattern corresponds to a constructor; if so, look for
+ * some other possible source aliases too, such as "super".
+ */
+ private boolean mConstructor;
+
+ private SearchHints(@NonNull SearchDirection direction) {
+ super();
+ mDirection = direction;
+ }
+
+ /**
+ * Constructs a new {@link SearchHints} object
+ *
+ * @param direction the direction to search in for the pattern
+ * @return a new @link SearchHints} object
+ */
+ @NonNull
+ public static SearchHints create(@NonNull SearchDirection direction) {
+ return new SearchHints(direction);
+ }
+
+ /**
+ * Indicates that pattern matches should apply to whole words only
+
+ * @return this, for constructor chaining
+ */
+ @NonNull
+ public SearchHints matchWholeWord() {
+ mWholeWord = true;
+
+ return this;
+ }
+
+ /** @return true if the pattern match should be for whole words only */
+ public boolean isWholeWord() {
+ return mWholeWord;
+ }
+
+ /**
+ * Indicates that pattern matches should apply to Java symbols only
+ *
+ * @return this, for constructor chaining
+ */
+ @NonNull
+ public SearchHints matchJavaSymbol() {
+ mJavaSymbol = true;
+ mWholeWord = true;
+
+ return this;
+ }
+
+ /** @return true if the pattern match should be for Java symbols only */
+ public boolean isJavaSymbol() {
+ return mJavaSymbol;
+ }
+
+ /**
+ * Indicates that pattern matches should apply to constructors. If so, look for
+ * some other possible source aliases too, such as "super".
+ *
+ * @return this, for constructor chaining
+ */
+ @NonNull
+ public SearchHints matchConstructor() {
+ mConstructor = true;
+ mWholeWord = true;
+ mJavaSymbol = true;
+
+ return this;
+ }
+
+ /** @return true if the pattern match should be for a constructor */
+ public boolean isConstructor() {
+ return mConstructor;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Position.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Position.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Position.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Position.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
new file mode 100644
index 0000000..d91fbe7
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
@@ -0,0 +1,1411 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.SdkConstants.ANDROID_LIBRARY;
+import static com.android.SdkConstants.ANDROID_LIBRARY_REFERENCE_FORMAT;
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT;
+import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
+import static com.android.SdkConstants.ATTR_PACKAGE;
+import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
+import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
+import static com.android.SdkConstants.OLD_PROGUARD_FILE;
+import static com.android.SdkConstants.PROGUARD_CONFIG;
+import static com.android.SdkConstants.PROJECT_PROPERTIES;
+import static com.android.SdkConstants.RES_FOLDER;
+import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT;
+import static com.android.SdkConstants.TAG_USES_SDK;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.sdklib.SdkVersionInfo.HIGHEST_KNOWN_API;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidArtifactOutput;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.Variant;
+import com.android.ide.common.repository.ResourceVisibilityLookup;
+import com.android.resources.Density;
+import com.android.resources.ResourceFolderType;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.tools.lint.client.api.CircularDependencyException;
+import com.android.tools.lint.client.api.Configuration;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.client.api.SdkInfo;
+import com.google.common.annotations.Beta;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A project contains information about an Android project being scanned for
+ * Lint errors.
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public class Project {
+ protected final LintClient mClient;
+ protected final File mDir;
+ protected final File mReferenceDir;
+ protected Configuration mConfiguration;
+ protected String mPackage;
+ protected int mBuildSdk = -1;
+ protected IAndroidTarget mTarget;
+
+ protected AndroidVersion mManifestMinSdk = AndroidVersion.DEFAULT;
+ protected AndroidVersion mManifestTargetSdk = AndroidVersion.DEFAULT;
+
+ protected boolean mLibrary;
+ protected String mName;
+ protected String mProguardPath;
+ protected boolean mMergeManifests;
+
+ /** The SDK info, if any */
+ protected SdkInfo mSdkInfo;
+
+ /**
+ * If non null, specifies a non-empty list of specific files under this
+ * project which should be checked.
+ */
+ protected List<File> mFiles;
+ protected List<File> mProguardFiles;
+ protected List<File> mGradleFiles;
+ protected List<File> mManifestFiles;
+ protected List<File> mJavaSourceFolders;
+ protected List<File> mJavaClassFolders;
+ protected List<File> mNonProvidedJavaLibraries;
+ protected List<File> mJavaLibraries;
+ protected List<File> mTestSourceFolders;
+ protected List<File> mResourceFolders;
+ protected List<File> mAssetFolders;
+ protected List<Project> mDirectLibraries;
+ protected List<Project> mAllLibraries;
+ protected boolean mReportIssues = true;
+ protected Boolean mGradleProject;
+ protected Boolean mSupportLib;
+ protected Boolean mAppCompat;
+ private Map<String, String> mSuperClassMap;
+ private ResourceVisibilityLookup mResourceVisibility;
+
+ /**
+ * Creates a new {@link Project} for the given directory.
+ *
+ * @param client the tool running the lint check
+ * @param dir the root directory of the project
+ * @param referenceDir See {@link #getReferenceDir()}.
+ * @return a new {@link Project}
+ */
+ @NonNull
+ public static Project create(
+ @NonNull LintClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir) {
+ return new Project(client, dir, referenceDir);
+ }
+
+ /**
+ * Returns true if this project is a Gradle-based Android project
+ *
+ * @return true if this is a Gradle-based project
+ */
+ public boolean isGradleProject() {
+ if (mGradleProject == null) {
+ mGradleProject = mClient.isGradleProject(this);
+ }
+
+ return mGradleProject;
+ }
+
+ /**
+ * Returns true if this project is an Android project.
+ *
+ * @return true if this project is an Android project.
+ */
+ @SuppressWarnings("MethodMayBeStatic")
+ public boolean isAndroidProject() {
+ return true;
+ }
+
+ /**
+ * Returns the project model for this project if it corresponds to
+ * a Gradle project. This is the case if {@link #isGradleProject()}
+ * is true and {@link #isLibrary()} is false.
+ *
+ * @return the project model, or null
+ */
+ @Nullable
+ public AndroidProject getGradleProjectModel() {
+ return null;
+ }
+
+ /**
+ * Returns the project model for this project if it corresponds to
+ * a Gradle library. This is the case if both
+ * {@link #isGradleProject()} and {@link #isLibrary()} return true.
+ *
+ * @return the project model, or null
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ @Nullable
+ public AndroidLibrary getGradleLibraryModel() {
+ return null;
+ }
+
+ /**
+ * Returns the current selected variant, if any (and if the current project is a Gradle
+ * project). This can be used by incremental lint rules to warn about problems in the current
+ * context. Lint rules should however strive to perform cross variant analysis and warn about
+ * problems in any configuration.
+ *
+ * @return the select variant, or null
+ */
+ @Nullable
+ public Variant getCurrentVariant() {
+ return null;
+ }
+
+ /** Creates a new Project. Use one of the factory methods to create. */
+ protected Project(
+ @NonNull LintClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir) {
+ mClient = client;
+ mDir = dir;
+ mReferenceDir = referenceDir;
+ initialize();
+ }
+
+ protected void initialize() {
+ // Default initialization: Use ADT/ant style project.properties file
+ try {
+ // Read properties file and initialize library state
+ Properties properties = new Properties();
+ File propFile = new File(mDir, PROJECT_PROPERTIES);
+ if (propFile.exists()) {
+ BufferedInputStream is = new BufferedInputStream(new FileInputStream(propFile));
+ try {
+ properties.load(is);
+ String value = properties.getProperty(ANDROID_LIBRARY);
+ mLibrary = VALUE_TRUE.equals(value);
+ String proguardPath = properties.getProperty(PROGUARD_CONFIG);
+ if (proguardPath != null) {
+ mProguardPath = proguardPath;
+ }
+ mMergeManifests = VALUE_TRUE.equals(properties.getProperty(
+ "manifestmerger.enabled")); //$NON-NLS-1$
+ String target = properties.getProperty("target"); //$NON-NLS-1$
+ if (target != null) {
+ int index = target.lastIndexOf('-');
+ if (index == -1) {
+ index = target.lastIndexOf(':');
+ }
+ if (index != -1) {
+ String versionString = target.substring(index + 1);
+ try {
+ mBuildSdk = Integer.parseInt(versionString);
+ } catch (NumberFormatException nufe) {
+ mClient.log(Severity.WARNING, null,
+ "Unexpected build target format: %1$s", target);
+ }
+ }
+ }
+
+ for (int i = 1; i < 1000; i++) {
+ String key = String.format(ANDROID_LIBRARY_REFERENCE_FORMAT, i);
+ String library = properties.getProperty(key);
+ if (library == null || library.isEmpty()) {
+ // No holes in the numbering sequence is allowed
+ break;
+ }
+
+ File libraryDir = new File(mDir, library).getCanonicalFile();
+
+ if (mDirectLibraries == null) {
+ mDirectLibraries = new ArrayList<Project>();
+ }
+
+ // Adjust the reference dir to be a proper prefix path of the
+ // library dir
+ File libraryReferenceDir = mReferenceDir;
+ if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) {
+ // Symlinks etc might have been resolved, so do those to
+ // the reference dir as well
+ libraryReferenceDir = libraryReferenceDir.getCanonicalFile();
+ if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) {
+ File file = libraryReferenceDir;
+ while (file != null && !file.getPath().isEmpty()) {
+ if (libraryDir.getPath().startsWith(file.getPath())) {
+ libraryReferenceDir = file;
+ break;
+ }
+ file = file.getParentFile();
+ }
+ }
+ }
+
+ try {
+ Project libraryPrj = mClient.getProject(libraryDir,
+ libraryReferenceDir);
+ mDirectLibraries.add(libraryPrj);
+ // By default, we don't report issues in inferred library projects.
+ // The driver will set report = true for those library explicitly
+ // requested.
+ libraryPrj.setReportIssues(false);
+ } catch (CircularDependencyException e) {
+ e.setProject(this);
+ e.setLocation(Location.create(propFile));
+ throw e;
+ }
+ }
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+ }
+ } catch (IOException ioe) {
+ mClient.log(ioe, "Initializing project state");
+ }
+
+ if (mDirectLibraries != null) {
+ mDirectLibraries = Collections.unmodifiableList(mDirectLibraries);
+ } else {
+ mDirectLibraries = Collections.emptyList();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Project [dir=" + mDir + ']';
+ }
+
+ @Override
+ public int hashCode() {
+ return mDir.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Project other = (Project) obj;
+ return mDir.equals(other.mDir);
+ }
+
+ /**
+ * Adds the given file to the list of files which should be checked in this
+ * project. If no files are added, the whole project will be checked.
+ *
+ * @param file the file to be checked
+ */
+ public void addFile(@NonNull File file) {
+ if (mFiles == null) {
+ mFiles = new ArrayList<File>();
+ }
+ mFiles.add(file);
+ }
+
+ /**
+ * The list of files to be checked in this project. If null, the whole
+ * project should be checked.
+ *
+ * @return the subset of files to be checked, or null for the whole project
+ */
+ @Nullable
+ public List<File> getSubset() {
+ return mFiles;
+ }
+
+ /**
+ * Returns the list of source folders for Java source files
+ *
+ * @return a list of source folders to search for .java files
+ */
+ @NonNull
+ public List<File> getJavaSourceFolders() {
+ if (mJavaSourceFolders == null) {
+ if (isAospFrameworksProject(mDir)) {
+ return Collections.singletonList(new File(mDir, "java")); //$NON-NLS-1$
+ }
+ if (isAospBuildEnvironment()) {
+ String top = getAospTop();
+ if (mDir.getAbsolutePath().startsWith(top)) {
+ mJavaSourceFolders = getAospJavaSourcePath();
+ return mJavaSourceFolders;
+ }
+ }
+
+ mJavaSourceFolders = mClient.getJavaSourceFolders(this);
+ }
+
+ return mJavaSourceFolders;
+ }
+
+ /**
+ * Returns the list of output folders for class files
+ * @return a list of output folders to search for .class files
+ */
+ @NonNull
+ public List<File> getJavaClassFolders() {
+ if (mJavaClassFolders == null) {
+ if (isAospFrameworksProject(mDir)) {
+ File top = mDir.getParentFile().getParentFile().getParentFile();
+ if (top != null) {
+ File out = new File(top, "out");
+ if (out.exists()) {
+ String relative =
+ "target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar";
+ File jar = new File(out, relative.replace('/', File.separatorChar));
+ if (jar.exists()) {
+ mJavaClassFolders = Collections.singletonList(jar);
+ return mJavaClassFolders;
+ }
+ }
+ }
+ }
+ if (isAospBuildEnvironment()) {
+ String top = getAospTop();
+ if (mDir.getAbsolutePath().startsWith(top)) {
+ mJavaClassFolders = getAospJavaClassPath();
+ return mJavaClassFolders;
+ }
+ }
+
+ mJavaClassFolders = mClient.getJavaClassFolders(this);
+ }
+ return mJavaClassFolders;
+ }
+
+ /**
+ * Returns the list of Java libraries (typically .jar files) that this
+ * project depends on. Note that this refers to jar libraries, not Android
+ * library projects which are processed in a separate pass with their own
+ * source and class folders.
+ *
+ * @param includeProvided If true, included provided libraries too (libraries
+ * that are not packaged with the app, but are provided
+ * for compilation purposes and are assumed to be present
+ * in the running environment)
+ * @return a list of .jar files (or class folders) that this project depends
+ * on.
+ */
+ @NonNull
+ public List<File> getJavaLibraries(boolean includeProvided) {
+ if (includeProvided) {
+ if (mJavaLibraries == null) {
+ // AOSP builds already merge libraries and class folders into
+ // the single classes.jar file, so these have already been processed
+ // in getJavaClassFolders.
+ mJavaLibraries = mClient.getJavaLibraries(this, true);
+ }
+ return mJavaLibraries;
+ } else {
+ if (mNonProvidedJavaLibraries == null) {
+ mNonProvidedJavaLibraries = mClient.getJavaLibraries(this, false);
+ }
+ return mNonProvidedJavaLibraries;
+ }
+ }
+
+ /**
+ * Returns the list of source folders for Java test source files
+ *
+ * @return a list of source folders to search for .java files
+ */
+ @NonNull
+ public List<File> getTestSourceFolders() {
+ if (mTestSourceFolders == null) {
+ mTestSourceFolders = mClient.getTestSourceFolders(this);
+ }
+
+ return mTestSourceFolders;
+ }
+
+ /**
+ * Returns the resource folders.
+ *
+ * @return a list of files pointing to the resource folders, which might be empty if the project
+ * does not provide any resources.
+ */
+ @NonNull
+ public List<File> getResourceFolders() {
+ if (mResourceFolders == null) {
+ List<File> folders = mClient.getResourceFolders(this);
+
+ if (folders.size() == 1 && isAospFrameworksProject(mDir)) {
+ // No manifest file for this project: just init the manifest values here
+ mManifestMinSdk = mManifestTargetSdk = new AndroidVersion(HIGHEST_KNOWN_API, null);
+ File folder = new File(folders.get(0), RES_FOLDER);
+ if (!folder.exists()) {
+ folders = Collections.emptyList();
+ }
+ }
+
+ mResourceFolders = folders;
+ }
+
+ return mResourceFolders;
+ }
+
+ /**
+ * Returns the asset folders.
+ *
+ * @return a list of files pointing to the asset folders, which might be empty if the project
+ * does not provide any resources.
+ */
+ @NonNull
+ public List<File> getAssetFolders() {
+ if (mAssetFolders == null) {
+ mAssetFolders = mClient.getAssetFolders(this);
+ }
+
+ return mAssetFolders;
+ }
+
+ /**
+ * Returns the relative path of a given file relative to the user specified
+ * directory (which is often the project directory but sometimes a higher up
+ * directory when a directory tree is being scanned
+ *
+ * @param file the file under this project to check
+ * @return the path relative to the reference directory (often the project directory)
+ */
+ @NonNull
+ public String getDisplayPath(@NonNull File file) {
+ String path = file.getPath();
+ String referencePath = mReferenceDir.getPath();
+ if (path.startsWith(referencePath)) {
+ int length = referencePath.length();
+ if (path.length() > length && path.charAt(length) == File.separatorChar) {
+ length++;
+ }
+
+ return path.substring(length);
+ }
+
+ return path;
+ }
+
+ /**
+ * Returns the relative path of a given file within the current project.
+ *
+ * @param file the file under this project to check
+ * @return the path relative to the project
+ */
+ @NonNull
+ public String getRelativePath(@NonNull File file) {
+ String path = file.getPath();
+ String referencePath = mDir.getPath();
+ if (path.startsWith(referencePath)) {
+ int length = referencePath.length();
+ if (path.length() > length && path.charAt(length) == File.separatorChar) {
+ length++;
+ }
+
+ return path.substring(length);
+ }
+
+ return path;
+ }
+
+ /**
+ * Returns the project root directory
+ *
+ * @return the dir
+ */
+ @NonNull
+ public File getDir() {
+ return mDir;
+ }
+
+ /**
+ * Returns the original user supplied directory where the lint search
+ * started. For example, if you run lint against {@code /tmp/foo}, and it
+ * finds a project to lint in {@code /tmp/foo/dev/src/project1}, then the
+ * {@code dir} is {@code /tmp/foo/dev/src/project1} and the
+ * {@code referenceDir} is {@code /tmp/foo/}.
+ *
+ * @return the reference directory, never null
+ */
+ @NonNull
+ public File getReferenceDir() {
+ return mReferenceDir;
+ }
+
+ /**
+ * Gets the configuration associated with this project
+ *
+ * @param driver the current driver, if any
+ * @return the configuration associated with this project
+ */
+ @NonNull
+ public Configuration getConfiguration(@Nullable LintDriver driver) {
+ if (mConfiguration == null) {
+ mConfiguration = mClient.getConfiguration(this, driver);
+ }
+ return mConfiguration;
+ }
+
+ /**
+ * Returns the application package specified by the manifest
+ *
+ * @return the application package, or null if unknown
+ */
+ @Nullable
+ public String getPackage() {
+ return mPackage;
+ }
+
+ /**
+ * Returns the minimum API level for the project
+ *
+ * @return the minimum API level or {@link AndroidVersion#DEFAULT} if unknown
+ */
+ @NonNull
+ public AndroidVersion getMinSdkVersion() {
+ return mManifestMinSdk == null ? AndroidVersion.DEFAULT : mManifestMinSdk;
+ }
+
+ /**
+ * Returns the minimum API <b>level</b> requested by the manifest, or -1 if not
+ * specified. Use {@link #getMinSdkVersion()} to get a full version if you need
+ * to check if the platform is a preview platform etc.
+ *
+ * @return the minimum API level or -1 if unknown
+ */
+ public int getMinSdk() {
+ AndroidVersion version = getMinSdkVersion();
+ return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel();
+ }
+
+ /**
+ * Returns the target API level for the project
+ *
+ * @return the target API level or {@link AndroidVersion#DEFAULT} if unknown
+ */
+ @NonNull
+ public AndroidVersion getTargetSdkVersion() {
+ return mManifestTargetSdk == AndroidVersion.DEFAULT
+ ? getMinSdkVersion() : mManifestTargetSdk;
+ }
+
+ /**
+ * Returns the target API <b>level</b> specified by the manifest, or -1 if not
+ * specified. Use {@link #getTargetSdkVersion()} to get a full version if you need
+ * to check if the platform is a preview platform etc.
+ *
+ * @return the target API level or -1 if unknown
+ */
+ public int getTargetSdk() {
+ AndroidVersion version = getTargetSdkVersion();
+ return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel();
+ }
+
+ /**
+ * Returns the target API used to build the project, or -1 if not known
+ *
+ * @return the build target API or -1 if unknown
+ */
+ public int getBuildSdk() {
+ return mBuildSdk;
+ }
+
+ /**
+ * Returns the specific version of the build tools being used, if known
+ *
+ * @return the build tools version in use, or null if not known
+ */
+ @Nullable
+ public BuildToolInfo getBuildTools() {
+ return mClient.getBuildTools(this);
+ }
+
+ /**
+ * Returns the target used to build the project, or null if not known
+ *
+ * @return the build target, or null
+ */
+ @Nullable
+ public IAndroidTarget getBuildTarget() {
+ if (mTarget == null) {
+ mTarget = mClient.getCompileTarget(this);
+ }
+
+ return mTarget;
+ }
+
+ /**
+ * Initialized the manifest state from the given manifest model
+ *
+ * @param document the DOM document for the manifest XML document
+ */
+ public void readManifest(@NonNull Document document) {
+ Element root = document.getDocumentElement();
+ if (root == null) {
+ return;
+ }
+
+ mPackage = root.getAttribute(ATTR_PACKAGE);
+
+ // Treat support libraries as non-reportable (in Eclipse where we don't
+ // have binary libraries, the support libraries have to be source-copied into
+ // the workspace which then triggers warnings in these libraries that users
+ // shouldn't have to investigate)
+ if (mPackage != null && mPackage.startsWith("android.support.")) {
+ mReportIssues = false;
+ }
+
+ // Initialize minSdk and targetSdk
+ mManifestMinSdk = AndroidVersion.DEFAULT;
+ mManifestTargetSdk = AndroidVersion.DEFAULT;
+ NodeList usesSdks = root.getElementsByTagName(TAG_USES_SDK);
+ if (usesSdks.getLength() > 0) {
+ Element element = (Element) usesSdks.item(0);
+
+ String minSdk = null;
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
+ minSdk = element.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
+ }
+ if (minSdk != null) {
+ IAndroidTarget[] targets = mClient.getTargets();
+ mManifestMinSdk = SdkVersionInfo.getVersion(minSdk, targets);
+ }
+
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
+ String targetSdk = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
+ if (targetSdk != null) {
+ IAndroidTarget[] targets = mClient.getTargets();
+ mManifestTargetSdk = SdkVersionInfo.getVersion(targetSdk, targets);
+ }
+ } else {
+ mManifestTargetSdk = mManifestMinSdk;
+ }
+ } else if (isAospBuildEnvironment()) {
+ extractAospMinSdkVersion();
+ mManifestTargetSdk = mManifestMinSdk;
+ }
+ }
+
+ /**
+ * Returns true if this project is an Android library project
+ *
+ * @return true if this project is an Android library project
+ */
+ public boolean isLibrary() {
+ return mLibrary;
+ }
+
+ /**
+ * Returns the list of library projects referenced by this project
+ *
+ * @return the list of library projects referenced by this project, never
+ * null
+ */
+ @NonNull
+ public List<Project> getDirectLibraries() {
+ return mDirectLibraries != null ? mDirectLibraries : Collections.<Project>emptyList();
+ }
+
+ /**
+ * Returns the transitive closure of the library projects for this project
+ *
+ * @return the transitive closure of the library projects for this project
+ */
+ @NonNull
+ public List<Project> getAllLibraries() {
+ if (mAllLibraries == null) {
+ if (mDirectLibraries.isEmpty()) {
+ return mDirectLibraries;
+ }
+
+ List<Project> all = new ArrayList<Project>();
+ Set<Project> seen = Sets.newHashSet();
+ Set<Project> path = Sets.newHashSet();
+ seen.add(this);
+ path.add(this);
+ addLibraryProjects(all, seen, path);
+ mAllLibraries = all;
+ }
+
+ return mAllLibraries;
+ }
+
+ /**
+ * Adds this project's library project and their library projects
+ * recursively into the given collection of projects
+ *
+ * @param collection the collection to add the projects into
+ * @param seen full set of projects we've processed
+ * @param path the current path of library dependencies followed
+ */
+ private void addLibraryProjects(@NonNull Collection<Project> collection,
+ @NonNull Set<Project> seen, @NonNull Set<Project> path) {
+ for (Project library : mDirectLibraries) {
+ if (seen.contains(library)) {
+ if (path.contains(library)) {
+ mClient.log(Severity.WARNING, null,
+ "Internal lint error: cyclic library dependency for %1$s", library);
+ }
+ continue;
+ }
+ collection.add(library);
+ seen.add(library);
+ path.add(library);
+ // Recurse
+ library.addLibraryProjects(collection, seen, path);
+ path.remove(library);
+ }
+ }
+
+ /**
+ * Gets the SDK info for the current project.
+ *
+ * @return the SDK info for the current project, never null
+ */
+ @NonNull
+ public SdkInfo getSdkInfo() {
+ if (mSdkInfo == null) {
+ mSdkInfo = mClient.getSdkInfo(this);
+ }
+
+ return mSdkInfo;
+ }
+
+ /**
+ * Gets the paths to the manifest files in this project, if any exists. The manifests
+ * should be provided such that the main manifest comes first, then any flavor versions,
+ * then any build types.
+ *
+ * @return the path to the manifest file, or null if it does not exist
+ */
+ @NonNull
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ File manifestFile = new File(mDir, ANDROID_MANIFEST_XML);
+ if (manifestFile.exists()) {
+ mManifestFiles = Collections.singletonList(manifestFile);
+ } else {
+ mManifestFiles = Collections.emptyList();
+ }
+ }
+
+ return mManifestFiles;
+ }
+
+ /**
+ * Returns the proguard files configured for this project, if any
+ *
+ * @return the proguard files, if any
+ */
+ @NonNull
+ public List<File> getProguardFiles() {
+ if (mProguardFiles == null) {
+ List<File> files = new ArrayList<File>();
+ if (mProguardPath != null) {
+ Splitter splitter = Splitter.on(CharMatcher.anyOf(":;")); //$NON-NLS-1$
+ for (String path : splitter.split(mProguardPath)) {
+ if (path.contains("${")) { //$NON-NLS-1$
+ // Don't analyze the global/user proguard files
+ continue;
+ }
+ File file = new File(path);
+ if (!file.isAbsolute()) {
+ file = new File(getDir(), path);
+ }
+ if (file.exists()) {
+ files.add(file);
+ }
+ }
+ }
+ if (files.isEmpty()) {
+ File file = new File(getDir(), OLD_PROGUARD_FILE);
+ if (file.exists()) {
+ files.add(file);
+ }
+ file = new File(getDir(), FN_PROJECT_PROGUARD_FILE);
+ if (file.exists()) {
+ files.add(file);
+ }
+ }
+ mProguardFiles = files;
+ }
+ return mProguardFiles;
+ }
+
+ /**
+ * Returns the Gradle build script files configured for this project, if any
+ *
+ * @return the Gradle files, if any
+ */
+ @NonNull
+ public List<File> getGradleBuildScripts() {
+ if (mGradleFiles == null) {
+ if (isGradleProject()) {
+ mGradleFiles = Lists.newArrayListWithExpectedSize(2);
+ File build = new File(mDir, SdkConstants.FN_BUILD_GRADLE);
+ if (build.exists()) {
+ mGradleFiles.add(build);
+ }
+ File settings = new File(mDir, SdkConstants.FN_SETTINGS_GRADLE);
+ if (settings.exists()) {
+ mGradleFiles.add(settings);
+ }
+ } else {
+ mGradleFiles = Collections.emptyList();
+ }
+ }
+
+ return mGradleFiles;
+ }
+
+ /**
+ * Returns the name of the project
+ *
+ * @return the name of the project, never null
+ */
+ @NonNull
+ public String getName() {
+ if (mName == null) {
+ mName = mClient.getProjectName(this);
+ }
+
+ return mName;
+ }
+
+ /**
+ * Sets the name of the project
+ *
+ * @param name the name of the project, never null
+ */
+ public void setName(@NonNull String name) {
+ assert !name.isEmpty();
+ mName = name;
+ }
+
+ /**
+ * Sets whether lint should report issues in this project. See
+ * {@link #getReportIssues()} for a full description of what that means.
+ *
+ * @param reportIssues whether lint should report issues in this project
+ */
+ public void setReportIssues(boolean reportIssues) {
+ mReportIssues = reportIssues;
+ }
+
+ /**
+ * Returns whether lint should report issues in this project.
+ * <p>
+ * If a user specifies a project and its library projects for analysis, then
+ * those library projects are all "included", and all errors found in all
+ * the projects are reported. But if the user is only running lint on the
+ * main project, we shouldn't report errors in any of the library projects.
+ * We still need to <b>consider</b> them for certain types of checks, such
+ * as determining whether resources found in the main project are unused, so
+ * the detectors must still get a chance to look at these projects. The
+ * {@code #getReportIssues()} attribute is used for this purpose.
+ *
+ * @return whether lint should report issues in this project
+ */
+ public boolean getReportIssues() {
+ return mReportIssues;
+ }
+
+ /**
+ * Returns whether manifest merging is in effect
+ *
+ * @return true if manifests in library projects should be merged into main projects
+ */
+ public boolean isMergingManifests() {
+ return mMergeManifests;
+ }
+
+
+ // ---------------------------------------------------------------------------
+ // Support for running lint on the AOSP source tree itself
+
+ private static Boolean sAospBuild;
+
+ /** Is lint running in an AOSP build environment */
+ private static boolean isAospBuildEnvironment() {
+ if (sAospBuild == null) {
+ sAospBuild = getAospTop() != null;
+ }
+
+ return sAospBuild;
+ }
+
+ /**
+ * Is this the frameworks AOSP project? Needs some hardcoded support since
+ * it doesn't have a manifest file, etc.
+ *
+ * @param dir the project directory to check
+ * @return true if this looks like the frameworks/base/core project
+ */
+ public static boolean isAospFrameworksProject(@NonNull File dir) {
+ if (!dir.getPath().endsWith("core")) { //$NON-NLS-1$
+ return false;
+ }
+
+ File parent = dir.getParentFile();
+ if (parent == null || !parent.getName().equals("base")) { //$NON-NLS-1$
+ return false;
+ }
+
+ parent = parent.getParentFile();
+ //noinspection RedundantIfStatement
+ if (parent == null || !parent.getName().equals("frameworks")) { //$NON-NLS-1$
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Get the root AOSP dir, if any */
+ private static String getAospTop() {
+ return System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
+ }
+
+ /** Get the host out directory in AOSP, if any */
+ private static String getAospHostOut() {
+ return System.getenv("ANDROID_HOST_OUT"); //$NON-NLS-1$
+ }
+
+ /** Get the product out directory in AOSP, if any */
+ private static String getAospProductOut() {
+ return System.getenv("ANDROID_PRODUCT_OUT"); //$NON-NLS-1$
+ }
+
+ private List<File> getAospJavaSourcePath() {
+ List<File> sources = new ArrayList<File>(2);
+ // Normal sources
+ File src = new File(mDir, "src"); //$NON-NLS-1$
+ if (src.exists()) {
+ sources.add(src);
+ }
+
+ // Generates sources
+ for (File dir : getIntermediateDirs()) {
+ File classes = new File(dir, "src"); //$NON-NLS-1$
+ if (classes.exists()) {
+ sources.add(classes);
+ }
+ }
+
+ if (sources.isEmpty()) {
+ mClient.log(null,
+ "Warning: Could not find sources or generated sources for project %1$s",
+ getName());
+ }
+
+ return sources;
+ }
+
+ private List<File> getAospJavaClassPath() {
+ List<File> classDirs = new ArrayList<File>(1);
+
+ for (File dir : getIntermediateDirs()) {
+ File classes = new File(dir, "classes"); //$NON-NLS-1$
+ if (classes.exists()) {
+ classDirs.add(classes);
+ } else {
+ classes = new File(dir, "classes.jar"); //$NON-NLS-1$
+ if (classes.exists()) {
+ classDirs.add(classes);
+ }
+ }
+ }
+
+ if (classDirs.isEmpty()) {
+ mClient.log(null,
+ "No bytecode found: Has the project been built? (%1$s)", getName());
+ }
+
+ return classDirs;
+ }
+
+ /** Find the _intermediates directories for a given module name */
+ private List<File> getIntermediateDirs() {
+ // See build/core/definitions.mk and in particular the "intermediates-dir-for" definition
+ List<File> intermediates = new ArrayList<File>();
+
+ // TODO: Look up the module name, e.g. LOCAL_MODULE. However,
+ // some Android.mk files do some complicated things with it - and most
+ // projects use the same module name as the directory name.
+ String moduleName = mDir.getName();
+
+ String top = getAospTop();
+ final String[] outFolders = new String[] {
+ top + "/out/host/common/obj", //$NON-NLS-1$
+ top + "/out/target/common/obj", //$NON-NLS-1$
+ getAospHostOut() + "/obj", //$NON-NLS-1$
+ getAospProductOut() + "/obj" //$NON-NLS-1$
+ };
+ final String[] moduleClasses = new String[] {
+ "APPS", //$NON-NLS-1$
+ "JAVA_LIBRARIES", //$NON-NLS-1$
+ };
+
+ for (String out : outFolders) {
+ assert new File(out.replace('/', File.separatorChar)).exists() : out;
+ for (String moduleClass : moduleClasses) {
+ String path = out + '/' + moduleClass + '/' + moduleName
+ + "_intermediates"; //$NON-NLS-1$
+ File file = new File(path.replace('/', File.separatorChar));
+ if (file.exists()) {
+ intermediates.add(file);
+ }
+ }
+ }
+
+ return intermediates;
+ }
+
+ private void extractAospMinSdkVersion() {
+ // Is the SDK level specified by a Makefile?
+ boolean found = false;
+ File makefile = new File(mDir, "Android.mk"); //$NON-NLS-1$
+ if (makefile.exists()) {
+ try {
+ List<String> lines = Files.readLines(makefile, Charsets.UTF_8);
+ Pattern p = Pattern.compile("LOCAL_SDK_VERSION\\s*:=\\s*(.*)"); //$NON-NLS-1$
+ for (String line : lines) {
+ line = line.trim();
+ Matcher matcher = p.matcher(line);
+ if (matcher.matches()) {
+ found = true;
+ String version = matcher.group(1);
+ if (version.equals("current")) { //$NON-NLS-1$
+ mManifestMinSdk = findCurrentAospVersion();
+ } else {
+ mManifestMinSdk = SdkVersionInfo.getVersion(version,
+ mClient.getTargets());
+ }
+ break;
+ }
+ }
+ } catch (IOException ioe) {
+ mClient.log(ioe, null);
+ }
+ }
+
+ if (!found) {
+ mManifestMinSdk = findCurrentAospVersion();
+ }
+ }
+
+ /** Cache for {@link #findCurrentAospVersion()} */
+ private static AndroidVersion sCurrentVersion;
+
+ /** In an AOSP build environment, identify the currently built image version, if available */
+ private static AndroidVersion findCurrentAospVersion() {
+ if (sCurrentVersion == null) {
+ File apiDir = new File(getAospTop(), "frameworks/base/api" //$NON-NLS-1$
+ .replace('/', File.separatorChar));
+ File[] apiFiles = apiDir.listFiles();
+ if (apiFiles == null) {
+ sCurrentVersion = AndroidVersion.DEFAULT;
+ return sCurrentVersion;
+ }
+ int max = 1;
+ for (File apiFile : apiFiles) {
+ String name = apiFile.getName();
+ int index = name.indexOf('.');
+ if (index > 0) {
+ String base = name.substring(0, index);
+ if (Character.isDigit(base.charAt(0))) {
+ try {
+ int version = Integer.parseInt(base);
+ if (version > max) {
+ max = version;
+ }
+ } catch (NumberFormatException nufe) {
+ // pass
+ }
+ }
+ }
+ }
+ sCurrentVersion = new AndroidVersion(max, null);
+ }
+
+ return sCurrentVersion;
+ }
+
+ /**
+ * Returns true if this project depends on the given artifact. Note that
+ * the project doesn't have to be a Gradle project; the artifact is just
+ * an identifier for name a specific library, such as com.android.support:support-v4
+ * to identify the support library
+ *
+ * @param artifact the Gradle/Maven name of a library
+ * @return true if the library is installed, false if it is not, and null if
+ * we're not sure
+ */
+ @Nullable
+ public Boolean dependsOn(@NonNull String artifact) {
+ if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
+ if (mSupportLib == null) {
+ for (File file : getJavaLibraries(true)) {
+ String name = file.getName();
+ if (name.equals("android-support-v4.jar") //$NON-NLS-1$
+ || name.startsWith("support-v4-")) { //$NON-NLS-1$
+ mSupportLib = true;
+ break;
+ }
+ }
+ if (mSupportLib == null) {
+ for (Project dependency : getDirectLibraries()) {
+ Boolean b = dependency.dependsOn(artifact);
+ if (b != null && b) {
+ mSupportLib = true;
+ break;
+ }
+ }
+ }
+ if (mSupportLib == null) {
+ mSupportLib = false;
+ }
+ }
+
+ return mSupportLib;
+ } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
+ if (mAppCompat == null) {
+ for (File file : getJavaLibraries(true)) {
+ String name = file.getName();
+ if (name.startsWith("appcompat-v7-")) { //$NON-NLS-1$
+ mAppCompat = true;
+ break;
+ }
+ }
+ if (mAppCompat == null) {
+ for (Project dependency : getDirectLibraries()) {
+ Boolean b = dependency.dependsOn(artifact);
+ if (b != null && b) {
+ mAppCompat = true;
+ break;
+ }
+ }
+ }
+ if (mAppCompat == null) {
+ mAppCompat = false;
+ }
+ }
+
+ return mAppCompat;
+ }
+
+ return null;
+ }
+
+ private List<String> mCachedApplicableDensities;
+
+ /**
+ * Returns the set of applicable densities for this project. If null, there are no density
+ * restrictions and all densities apply.
+ *
+ * @return the list of specific densities that apply in this project, or null if all densities
+ * apply
+ */
+ @Nullable
+ public List<String> getApplicableDensities() {
+ if (mCachedApplicableDensities == null) {
+ // Use the gradle API to set up relevant densities. For example, if the
+ // build.gradle file contains this:
+ // android {
+ // defaultConfig {
+ // resConfigs "nodpi", "hdpi"
+ // }
+ // }
+ // ...then we should only enforce hdpi densities, not all these others!
+ if (isGradleProject() && getGradleProjectModel() != null &&
+ getCurrentVariant() != null) {
+ Set<String> relevantDensities = Sets.newHashSet();
+ Variant variant = getCurrentVariant();
+ List<String> variantFlavors = variant.getProductFlavors();
+ AndroidProject gradleProjectModel = getGradleProjectModel();
+
+ addResConfigsFromFlavor(relevantDensities, null,
+ getGradleProjectModel().getDefaultConfig());
+ for (ProductFlavorContainer container : gradleProjectModel.getProductFlavors()) {
+ addResConfigsFromFlavor(relevantDensities, variantFlavors, container);
+ }
+
+ // Are there any splits that specify densities?
+ if (relevantDensities.isEmpty()) {
+ AndroidArtifact mainArtifact = variant.getMainArtifact();
+ Collection<AndroidArtifactOutput> outputs = mainArtifact.getOutputs();
+ for (AndroidArtifactOutput output : outputs) {
+ for (OutputFile file : output.getOutputs()) {
+ final String DENSITY_NAME = OutputFile.FilterType.DENSITY.name();
+ if (file.getFilterTypes().contains(DENSITY_NAME)) {
+ for (FilterData data : file.getFilters()) {
+ if (DENSITY_NAME.equals(data.getFilterType())) {
+ relevantDensities.add(data.getIdentifier());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!relevantDensities.isEmpty()) {
+ mCachedApplicableDensities = Lists.newArrayListWithExpectedSize(10);
+ for (String density : relevantDensities) {
+ String folder = ResourceFolderType.DRAWABLE.getName() + '-' + density;
+ mCachedApplicableDensities.add(folder);
+ }
+ Collections.sort(mCachedApplicableDensities);
+ } else {
+ mCachedApplicableDensities = Collections.emptyList();
+ }
+ } else {
+ mCachedApplicableDensities = Collections.emptyList();
+ }
+ }
+
+ return mCachedApplicableDensities.isEmpty() ? null : mCachedApplicableDensities;
+ }
+
+ /**
+ * Returns a super class map for this project. The keys and values are internal
+ * class names (e.g. java/lang/Integer, not java.lang.Integer).
+ * @return a map, possibly empty but never null
+ */
+ @NonNull
+ public Map<String, String> getSuperClassMap() {
+ if (mSuperClassMap == null) {
+ mSuperClassMap = mClient.createSuperClassMap(this);
+ }
+
+ return mSuperClassMap;
+ }
+
+ /**
+ * Adds in the resConfig values specified by the given flavor container, assuming
+ * it's in one of the relevant variantFlavors, into the given set
+ */
+ private static void addResConfigsFromFlavor(@NonNull Set<String> relevantDensities,
+ @Nullable List<String> variantFlavors,
+ @NonNull ProductFlavorContainer container) {
+ ProductFlavor flavor = container.getProductFlavor();
+ if (variantFlavors == null || variantFlavors.contains(flavor.getName())) {
+ if (!flavor.getResourceConfigurations().isEmpty()) {
+ for (String densityName : flavor.getResourceConfigurations()) {
+ Density density = Density.getEnum(densityName);
+ if (density != null && density.isRecommended()
+ && density != Density.NODPI && density != Density.ANYDPI) {
+ relevantDensities.add(densityName);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a shared {@link ResourceVisibilityLookup}
+ *
+ * @return a shared provider for looking up resource visibility
+ */
+ @NonNull
+ public ResourceVisibilityLookup getResourceVisibility() {
+ if (mResourceVisibility == null) {
+ if (isGradleProject()) {
+ AndroidProject project = getGradleProjectModel();
+ Variant variant = getCurrentVariant();
+ if (project != null && variant != null) {
+ mResourceVisibility = mClient.getResourceVisibilityProvider().get(project,
+ variant);
+
+ } else if (getGradleLibraryModel() != null) {
+ try {
+ mResourceVisibility = mClient.getResourceVisibilityProvider()
+ .get(getGradleLibraryModel());
+ } catch (Exception ignore) {
+ // Handle talking to older Gradle plugins (where we don't
+ // have access to the model version to check up front
+ }
+ }
+ }
+ if (mResourceVisibility == null) {
+ mResourceVisibility = ResourceVisibilityLookup.NONE;
+ }
+ }
+
+ return mResourceVisibility;
+ }
+
+ /**
+ * Returns the associated client
+ *
+ * @return the client
+ */
+ @NonNull
+ public LintClient getClient() {
+ return mClient;
+ }
+
+ /**
+ * Returns the compile target to use for this project
+ *
+ * @return the compile target to use to build this project
+ */
+ @Nullable
+ public IAndroidTarget getCompileTarget() {
+ return mClient.getCompileTarget(this);
+ }
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceContext.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceContext.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceContext.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
new file mode 100644
index 0000000..8d3f2a2
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_GRADLE;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.DOT_PROPERTIES;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
+import static com.android.SdkConstants.OLD_PROGUARD_FILE;
+import static com.android.SdkConstants.RES_FOLDER;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * The scope of a detector is the set of files a detector must consider when
+ * performing its analysis. This can be used to determine when issues are
+ * potentially obsolete, whether a detector should re-run on a file save, etc.
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+public enum Scope {
+ /**
+ * The analysis only considers a single XML resource file at a time.
+ * <p>
+ * Issues which are only affected by a single resource file can be checked
+ * for incrementally when a file is edited.
+ */
+ RESOURCE_FILE,
+
+ /**
+ * The analysis only considers a single binary (typically a bitmap) resource file at a time.
+ * <p>
+ * Issues which are only affected by a single resource file can be checked
+ * for incrementally when a file is edited.
+ */
+ BINARY_RESOURCE_FILE,
+
+ /**
+ * The analysis considers the resource folders (which also includes asset folders)
+ */
+ RESOURCE_FOLDER,
+
+ /**
+ * The analysis considers <b>all</b> the resource file. This scope must not
+ * be used in conjunction with {@link #RESOURCE_FILE}; an issue scope is
+ * either considering just a single resource file or all the resources, not
+ * both.
+ */
+ ALL_RESOURCE_FILES,
+
+ /**
+ * The analysis only considers a single Java source file at a time.
+ * <p>
+ * Issues which are only affected by a single Java source file can be
+ * checked for incrementally when a Java source file is edited.
+ */
+ JAVA_FILE,
+
+ /**
+ * The analysis considers <b>all</b> the Java source files together.
+ * <p>
+ * This flag is mutually exclusive with {@link #JAVA_FILE}.
+ */
+ ALL_JAVA_FILES,
+
+ /**
+ * The analysis only considers a single Java class file at a time.
+ * <p>
+ * Issues which are only affected by a single Java class file can be checked
+ * for incrementally when a Java source file is edited and then recompiled.
+ */
+ CLASS_FILE,
+
+ /**
+ * The analysis considers <b>all</b> the Java class files together.
+ * <p>
+ * This flag is mutually exclusive with {@link #CLASS_FILE}.
+ */
+ ALL_CLASS_FILES,
+
+ /** The analysis considers the manifest file */
+ MANIFEST,
+
+ /** The analysis considers the Proguard configuration file */
+ PROGUARD_FILE,
+
+ /**
+ * The analysis considers classes in the libraries for this project. These
+ * will be analyzed before the classes themselves. NOTE: This excludes
+ * provided libraries.
+ */
+ JAVA_LIBRARIES,
+
+ /** The analysis considers a Gradle build file */
+ GRADLE_FILE,
+
+ /** The analysis considers Java property files */
+ PROPERTY_FILE,
+
+ /** The analysis considers test sources as well */
+ TEST_SOURCES,
+
+ /**
+ * Scope for other files. Issues that specify a custom scope will be called unconditionally.
+ * This will call {@link Detector#run(Context)}} on the detectors unconditionally.
+ */
+ OTHER;
+
+ /**
+ * Returns true if the given scope set corresponds to scanning a single file
+ * rather than a whole project
+ *
+ * @param scopes the scope set to check
+ * @return true if the scope set references a single file
+ */
+ public static boolean checkSingleFile(@NonNull EnumSet<Scope> scopes) {
+ int size = scopes.size();
+ if (size == 2) {
+ // When single checking a Java source file, we check both its Java source
+ // and the associated class files
+ return scopes.contains(JAVA_FILE) && scopes.contains(CLASS_FILE);
+ } else {
+ return size == 1 &&
+ (scopes.contains(JAVA_FILE)
+ || scopes.contains(CLASS_FILE)
+ || scopes.contains(RESOURCE_FILE)
+ || scopes.contains(PROGUARD_FILE)
+ || scopes.contains(PROPERTY_FILE)
+ || scopes.contains(GRADLE_FILE)
+ || scopes.contains(MANIFEST));
+ }
+ }
+
+ /**
+ * Returns the intersection of two scope sets
+ *
+ * @param scope1 the first set to intersect
+ * @param scope2 the second set to intersect
+ * @return the intersection of the two sets
+ */
+ @NonNull
+ public static EnumSet<Scope> intersect(
+ @NonNull EnumSet<Scope> scope1,
+ @NonNull EnumSet<Scope> scope2) {
+ EnumSet<Scope> scope = EnumSet.copyOf(scope1);
+ scope.retainAll(scope2);
+
+ return scope;
+ }
+
+ /**
+ * Infers a suitable scope to use from the given projects to be analyzed
+ * @param projects the projects to find a suitable scope for
+ * @return the scope to use
+ */
+ @NonNull
+ public static EnumSet<Scope> infer(@NonNull Collection<Project> projects) {
+ // Infer the scope
+ EnumSet<Scope> scope = EnumSet.noneOf(Scope.class);
+ for (Project project : projects) {
+ List<File> subset = project.getSubset();
+ if (subset != null) {
+ for (File file : subset) {
+ String name = file.getName();
+ if (name.equals(ANDROID_MANIFEST_XML)) {
+ scope.add(MANIFEST);
+ } else if (name.endsWith(DOT_XML)) {
+ scope.add(RESOURCE_FILE);
+ } else if (name.endsWith(DOT_JAVA)) {
+ scope.add(JAVA_FILE);
+ } else if (name.endsWith(DOT_CLASS)) {
+ scope.add(CLASS_FILE);
+ } else if (name.endsWith(DOT_GRADLE)) {
+ scope.add(GRADLE_FILE);
+ } else if (name.equals(OLD_PROGUARD_FILE)
+ || name.equals(FN_PROJECT_PROGUARD_FILE)) {
+ scope.add(PROGUARD_FILE);
+ } else if (name.endsWith(DOT_PROPERTIES)) {
+ scope.add(PROPERTY_FILE);
+ } else if (name.endsWith(DOT_PNG)) {
+ scope.add(BINARY_RESOURCE_FILE);
+ } else if (name.equals(RES_FOLDER)
+ || file.getParent().equals(RES_FOLDER)) {
+ scope.add(ALL_RESOURCE_FILES);
+ scope.add(RESOURCE_FILE);
+ scope.add(BINARY_RESOURCE_FILE);
+ scope.add(RESOURCE_FOLDER);
+ }
+ }
+ } else {
+ // Specified a full project: just use the full project scope
+ scope = Scope.ALL;
+ break;
+ }
+ }
+
+ return scope;
+ }
+
+ /** All scopes: running lint on a project will check these scopes */
+ public static final EnumSet<Scope> ALL = EnumSet.allOf(Scope.class);
+ /** Scope-set used for detectors which are affected by a single resource file */
+ public static final EnumSet<Scope> RESOURCE_FILE_SCOPE = EnumSet.of(RESOURCE_FILE);
+ /** Scope-set used for detectors which are affected by a single resource folder */
+ public static final EnumSet<Scope> RESOURCE_FOLDER_SCOPE = EnumSet.of(RESOURCE_FOLDER);
+ /** Scope-set used for detectors which scan all resources */
+ public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
+ /** Scope-set used for detectors which are affected by a single Java source file */
+ public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE);
+ /** Scope-set used for detectors which are affected by a single Java class file */
+ public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE);
+ /** Scope-set used for detectors which are affected by a single Gradle build file */
+ public static final EnumSet<Scope> GRADLE_SCOPE = EnumSet.of(GRADLE_FILE);
+ /** Scope-set used for detectors which are affected by the manifest only */
+ public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST);
+ /** Scope-set used for detectors which correspond to some other context */
+ public static final EnumSet<Scope> OTHER_SCOPE = EnumSet.of(OTHER);
+ /** Scope-set used for detectors which are affected by a single ProGuard class file */
+ public static final EnumSet<Scope> PROGUARD_SCOPE = EnumSet.of(PROGUARD_FILE);
+ /** Scope-set used for detectors which correspond to property files */
+ public static final EnumSet<Scope> PROPERTY_SCOPE = EnumSet.of(PROPERTY_FILE);
+ /** Resource XML files and manifest files */
+ public static final EnumSet<Scope> MANIFEST_AND_RESOURCE_SCOPE =
+ EnumSet.of(Scope.MANIFEST, Scope.RESOURCE_FILE);
+ /** Scope-set used for detectors which are affected by single XML and Java source files */
+ public static final EnumSet<Scope> JAVA_AND_RESOURCE_FILES =
+ EnumSet.of(RESOURCE_FILE, JAVA_FILE);
+ /** Scope-set used for analyzing individual class files and all resource files */
+ public static final EnumSet<Scope> CLASS_AND_ALL_RESOURCE_FILES =
+ EnumSet.of(ALL_RESOURCE_FILES, CLASS_FILE);
+ /** Scope-set used for analyzing all class files, including those in libraries */
+ public static final EnumSet<Scope> ALL_CLASSES_AND_LIBRARIES =
+ EnumSet.of(Scope.ALL_CLASS_FILES, Scope.JAVA_LIBRARIES);
+ /** Scope-set used for detectors which are affected by Java libraries */
+ public static final EnumSet<Scope> JAVA_LIBRARY_SCOPE = EnumSet.of(JAVA_LIBRARIES);
+ /** Scope-set used for detectors which are affected by a single binary resource file */
+ public static final EnumSet<Scope> BINARY_RESOURCE_FILE_SCOPE =
+ EnumSet.of(BINARY_RESOURCE_FILE);
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.java
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Speed.java
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java
new file mode 100644
index 0000000..9012411
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TextFormat.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import com.android.annotations.NonNull;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+
+/**
+ * Lint error message, issue explanations and location descriptions
+ * are described in a {@link #RAW} format which looks similar to text
+ * but which can contain bold, symbols and links. These issues can
+ * also be converted to plain text and to HTML markup, using the
+ * {@link #convertTo(String, TextFormat)} method.
+ *
+ * @see Issue#getDescription(TextFormat)
+ * @see Issue#getExplanation(TextFormat)
+ * @see Issue#getBriefDescription(TextFormat)
+ */
+public enum TextFormat {
+ /**
+ * Raw output format which is similar to text but allows some markup:
+ * <ul>
+ * <li>HTTP urls (http://...)
+ * <li>Sentences immediately surrounded by * will be shown as bold.
+ * <li>Sentences immediately surrounded by ` will be shown using monospace
+ * fonts
+ * </ul>
+ * Furthermore, newlines are converted to br's when converting newlines.
+ * Note: It does not insert {@code <html>} tags around the fragment for HTML output.
+ * <p>
+ * TODO: Consider switching to the restructured text format -
+ * http://docutils.sourceforge.net/docs/user/rst/quickstart.html
+ */
+ RAW,
+
+ /**
+ * Plain text output
+ */
+ TEXT,
+
+ /**
+ * HTML formatted output (note: does not include surrounding {@code <html></html>} tags)
+ */
+ HTML,
+
+ /**
+ * HTML formatted output (note: does not include surrounding {@code <html></html>} tags).
+ * This is like {@link #HTML}, but it does not escape unicode characters with entities.
+ * <p>
+ * (This is used for example in the IDE, where some partial HTML support in some
+ * label widgets support some HTML markup, but not numeric code character entities.)
+ */
+ HTML_WITH_UNICODE;
+
+ /**
+ * Converts the given text to HTML
+ *
+ * @param text the text to format
+ * @return the corresponding text formatted as HTML
+ */
+ @NonNull
+ public String toHtml(@NonNull String text) {
+ return convertTo(text, HTML);
+ }
+
+ /**
+ * Converts the given text to plain text
+ *
+ * @param text the tetx to format
+ * @return the corresponding text formatted as HTML
+ */
+ @NonNull
+ public String toText(@NonNull String text) {
+ return convertTo(text, TEXT);
+ }
+
+ /**
+ * Converts the given message to the given format. Note that some
+ * conversions are lossy; e.g. once converting away from the raw format
+ * (which contains all the markup) you can't convert back to it.
+ * Note that you can convert to the format it's already in; that just
+ * returns the same string.
+ *
+ * @param message the message to convert
+ * @param to the format to convert to
+ * @return a converted message
+ */
+ public String convertTo(@NonNull String message, @NonNull TextFormat to) {
+ if (this == to) {
+ return message;
+ }
+ switch (this) {
+ case RAW: {
+ switch (to) {
+ case RAW:
+ return message;
+ case TEXT:
+ case HTML:
+ case HTML_WITH_UNICODE:
+ return to.fromRaw(message);
+ }
+ }
+ case TEXT: {
+ switch (to) {
+ case TEXT:
+ case RAW:
+ return message;
+ case HTML:
+ case HTML_WITH_UNICODE:
+ return XmlUtils.toXmlTextValue(message);
+ }
+ }
+ case HTML: {
+ switch (to) {
+ case HTML:
+ return message;
+ case HTML_WITH_UNICODE:
+ return removeNumericEntities(message);
+ case RAW:
+ case TEXT: {
+ return to.fromHtml(message);
+
+ }
+ }
+ }
+ case HTML_WITH_UNICODE: {
+ switch (to) {
+ case HTML:
+ case HTML_WITH_UNICODE:
+ return message;
+ case RAW:
+ case TEXT: {
+ return to.fromHtml(message);
+
+ }
+ }
+ }
+ }
+ return message;
+ }
+
+ /** Converts to this output format from the given HTML-format text */
+ @NonNull
+ private String fromHtml(@NonNull String html) {
+ assert this == RAW || this == TEXT : this;
+
+ // Drop all tags; replace all entities, insert newlines
+ // (this won't do wrapping)
+ StringBuilder sb = new StringBuilder(html.length());
+ for (int i = 0, n = html.length(); i < n; i++) {
+ char c = html.charAt(i);
+ if (c == '<') {
+ // Scan forward to the end
+ if (html.startsWith("<br>", i) ||
+ html.startsWith("<br />", i) ||
+ html.startsWith("<BR>", i) ||
+ html.startsWith("<BR />", i)) {
+ sb.append('\n');
+ } else if (html.startsWith("<!--")) {
+ i = Math.max(i, html.indexOf("-->", i));
+ }
+ i = html.indexOf('>', i);
+ } else if (c == '&') {
+ int end = html.indexOf(';', i);
+ if (end > i) {
+ String entity = html.substring(i, end + 1);
+ sb.append(XmlUtils.fromXmlAttributeValue(entity));
+ i = end;
+ } else {
+ sb.append(c);
+ }
+ } else if (c == '\n') {
+ sb.append(' ');
+ } else {
+ sb.append(c);
+ }
+ }
+
+ // Collapse repeated spaces
+ String s = sb.toString();
+ sb.setLength(0);
+ boolean wasSpace = false;
+ for (int i = 0, n = s.length(); i < n; i++) {
+ char c = s.charAt(i);
+ if (c == '\t') { // we keep newlines; came from <br>'s
+ c = ' ';
+ }
+ boolean isSpace = c == ' ';
+ if (!isSpace || !wasSpace) {
+ wasSpace = isSpace;
+ sb.append(c);
+ }
+ }
+ s = sb.toString();
+
+ // Line-wrap
+ s = SdkUtils.wrap(s, 60, null);
+
+ return s;
+ }
+
+ private static final String HTTP_PREFIX = "http://"; //$NON-NLS-1$
+
+ /** Converts to this output format from the given raw-format text */
+ @NonNull
+ private String fromRaw(@NonNull String text) {
+ assert this == HTML || this == HTML_WITH_UNICODE || this == TEXT : this;
+ StringBuilder sb = new StringBuilder(3 * text.length() / 2);
+ boolean html = this == HTML || this == HTML_WITH_UNICODE;
+ boolean escapeUnicode = this == HTML;
+
+ char prev = 0;
+ int flushIndex = 0;
+ int n = text.length();
+ for (int i = 0; i < n; i++) {
+ char c = text.charAt(i);
+ if ((c == '*' || c == '`') && i < n - 1) {
+ // Scout ahead for range end
+ if (!Character.isLetterOrDigit(prev)
+ && !Character.isWhitespace(text.charAt(i + 1))) {
+ // Found * or ` immediately before a letter, and not in the middle of a word
+ // Find end
+ int end = text.indexOf(c, i + 1);
+ if (end != -1 && (end == n - 1 || !Character.isLetter(text.charAt(end + 1)))) {
+ if (i > flushIndex) {
+ appendEscapedText(sb, text, html, flushIndex, i, escapeUnicode);
+ }
+ if (html) {
+ String tag = c == '*' ? "b" : "code"; //$NON-NLS-1$ //$NON-NLS-2$
+ sb.append('<').append(tag).append('>');
+ appendEscapedText(sb, text, html, i + 1, end, escapeUnicode);
+ sb.append('<').append('/').append(tag).append('>');
+ } else {
+ appendEscapedText(sb, text, html, i + 1, end, escapeUnicode);
+ }
+ flushIndex = end + 1;
+ i = flushIndex - 1; // -1: account for the i++ in the loop
+ }
+ }
+ } else if (html && c == 'h' && i < n - 1 && text.charAt(i + 1) == 't'
+ && text.startsWith(HTTP_PREFIX, i) && !Character.isLetterOrDigit(prev)) {
+ // Find url end
+ int end = i + HTTP_PREFIX.length();
+ while (end < n) {
+ char d = text.charAt(end);
+ if (Character.isWhitespace(d)) {
+ break;
+ }
+ end++;
+ }
+ char last = text.charAt(end - 1);
+ if (last == '.' || last == ')' || last == '!') {
+ end--;
+ }
+ if (end > i + HTTP_PREFIX.length()) {
+ if (i > flushIndex) {
+ appendEscapedText(sb, text, html, flushIndex, i, escapeUnicode);
+ }
+
+ String url = text.substring(i, end);
+ sb.append("<a href=\""); //$NON-NLS-1$
+ sb.append(url);
+ sb.append('"').append('>');
+ sb.append(url);
+ sb.append("</a>"); //$NON-NLS-1$
+
+ flushIndex = end;
+ i = flushIndex - 1; // -1: account for the i++ in the loop
+ }
+ }
+ prev = c;
+ }
+
+ if (flushIndex < n) {
+ appendEscapedText(sb, text, html, flushIndex, n, escapeUnicode);
+ }
+
+ return sb.toString();
+ }
+
+ private static String removeNumericEntities(@NonNull String html) {
+ if (!html.contains("&#")) {
+ return html;
+ }
+
+ StringBuilder sb = new StringBuilder(html.length());
+ for (int i = 0, n = html.length(); i < n; i++) {
+ char c = html.charAt(i);
+ if (c == '&' && i < n - 1 && html.charAt(i + 1) == '#') {
+ int end = html.indexOf(';', i + 2);
+ if (end != -1) {
+ String decimal = html.substring(i + 2, end);
+ try {
+ c = (char)Integer.parseInt(decimal);
+ sb.append(c);
+ i = end;
+ continue;
+ } catch (NumberFormatException ignore) {
+ // fall through to not escape this
+ }
+ }
+ }
+ sb.append(c);
+ }
+
+ return sb.toString();
+ }
+
+ private static void appendEscapedText(@NonNull StringBuilder sb, @NonNull String text,
+ boolean html, int start, int end, boolean escapeUnicode) {
+ if (html) {
+ for (int i = start; i < end; i++) {
+ char c = text.charAt(i);
+ if (c == '<') {
+ sb.append("<"); //$NON-NLS-1$
+ } else if (c == '&') {
+ sb.append("&"); //$NON-NLS-1$
+ } else if (c == '\n') {
+ sb.append("<br/>\n");
+ } else {
+ if (c > 255 && escapeUnicode) {
+ sb.append("&#"); //$NON-NLS-1$
+ sb.append(Integer.toString(c));
+ sb.append(';');
+ } else if (c == '\u00a0') {
+ sb.append(" "); //$NON-NLS-1$
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+ } else {
+ for (int i = start; i < end; i++) {
+ char c = text.charAt(i);
+ sb.append(c);
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TypeEvaluator.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TypeEvaluator.java
new file mode 100644
index 0000000..bbafced
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/TypeEvaluator.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_DOUBLE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_FLOAT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.DefaultTypeDescriptor;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.BooleanLiteral;
+import lombok.ast.Cast;
+import lombok.ast.Catch;
+import lombok.ast.CharLiteral;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.FloatingPointLiteral;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.Literal;
+import lombok.ast.Node;
+import lombok.ast.NullLiteral;
+import lombok.ast.Statement;
+import lombok.ast.StringLiteral;
+import lombok.ast.UnaryExpression;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Evaluates the types of nodes. This goes deeper than
+ * {@link JavaContext#getType(Node)} in that it analyzes the
+ * flow and for example figures out that if you ask for the type of {@code var}
+ * in this code snippet:
+ * <pre>
+ * Object o = new StringBuilder();
+ * Object var = o;
+ * </pre>
+ * it will return "java.lang.StringBuilder".
+ * <p>
+ * <b>NOTE:</b> This type evaluator does not (yet) compute the correct
+ * types when involving implicit type conversions, so be careful
+ * if using this for primitives; e.g. for "int * long" it might return
+ * the type "int".
+ */
+public class TypeEvaluator {
+ private final JavaContext mContext;
+
+ /**
+ * Creates a new constant evaluator
+ *
+ * @param context the context to use to resolve field references, if any
+ */
+ public TypeEvaluator(@Nullable JavaContext context) {
+ mContext = context;
+ }
+
+
+ /**
+ * Returns true if the node evaluates to an instance of type SecureRandom
+ */
+ @Nullable
+ public TypeDescriptor evaluate(@NonNull Node node) {
+ ResolvedNode resolved = null;
+ if (mContext != null) {
+ resolved = mContext.resolve(node);
+ }
+ if (resolved instanceof ResolvedMethod) {
+ TypeDescriptor type;
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.isConstructor()) {
+ ResolvedClass containingClass = method.getContainingClass();
+ type = containingClass.getType();
+ } else {
+ type = method.getReturnType();
+ }
+ return type;
+ }
+ if (resolved instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) resolved;
+ Node astNode = field.findAstNode();
+ if (astNode instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration)astNode;
+ VariableDefinition definition = declaration.astDefinition();
+ if (definition != null) {
+ VariableDefinitionEntry first = definition.astVariables().first();
+ if (first != null) {
+ Expression initializer = first.astInitializer();
+ if (initializer != null) {
+ TypeDescriptor type = evaluate(initializer);
+ if (type != null) {
+ return type;
+ }
+ }
+ }
+ }
+ }
+ return field.getType();
+ }
+
+ if (node instanceof VariableReference) {
+ Statement statement = getParentOfType(node, Statement.class, false);
+ if (statement != null) {
+ ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next() == statement) {
+ if (iterator.hasPrevious()) { // should always be true
+ iterator.previous();
+ }
+ break;
+ }
+ }
+
+ String targetName = ((VariableReference) node).astIdentifier().astValue();
+ while (iterator.hasPrevious()) {
+ Node previous = iterator.previous();
+ if (previous instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) previous;
+ VariableDefinition definition = declaration.astDefinition();
+ for (VariableDefinitionEntry entry : definition.astVariables()) {
+ if (entry.astInitializer() != null && entry.astName().astValue()
+ .equals(targetName)) {
+ return evaluate(entry.astInitializer());
+ }
+ }
+ } else if (previous instanceof ExpressionStatement) {
+ ExpressionStatement expressionStatement = (ExpressionStatement) previous;
+ Expression expression = expressionStatement.astExpression();
+ if (expression instanceof BinaryExpression &&
+ ((BinaryExpression) expression).astOperator()
+ == BinaryOperator.ASSIGN) {
+ BinaryExpression binaryExpression = (BinaryExpression) expression;
+ if (targetName.equals(binaryExpression.astLeft().toString())) {
+ return evaluate(binaryExpression.astRight());
+ }
+ }
+ }
+ }
+ }
+ } else if (node instanceof Cast) {
+ Cast cast = (Cast) node;
+ if (mContext != null) {
+ ResolvedNode typeReference = mContext.resolve(cast.astTypeReference());
+ if (typeReference instanceof ResolvedClass) {
+ return ((ResolvedClass) typeReference).getType();
+ }
+ }
+ TypeDescriptor viewType = evaluate(cast.astOperand());
+ if (viewType != null) {
+ return viewType;
+ }
+ } else if (node instanceof Literal) {
+ if (node instanceof NullLiteral) {
+ return null;
+ } else if (node instanceof BooleanLiteral) {
+ return new DefaultTypeDescriptor(TYPE_BOOLEAN);
+ } else if (node instanceof StringLiteral) {
+ return new DefaultTypeDescriptor(TYPE_STRING);
+ } else if (node instanceof CharLiteral) {
+ return new DefaultTypeDescriptor(TYPE_CHAR);
+ } else if (node instanceof IntegralLiteral) {
+ IntegralLiteral literal = (IntegralLiteral) node;
+ // Don't combine to ?: since that will promote astIntValue to a long
+ if (literal.astMarkedAsLong()) {
+ return new DefaultTypeDescriptor(TYPE_LONG);
+ } else {
+ return new DefaultTypeDescriptor(TYPE_INT);
+ }
+ } else if (node instanceof FloatingPointLiteral) {
+ FloatingPointLiteral literal = (FloatingPointLiteral) node;
+ // Don't combine to ?: since that will promote astFloatValue to a double
+ if (literal.astMarkedAsFloat()) {
+ return new DefaultTypeDescriptor(TYPE_FLOAT);
+ } else {
+ return new DefaultTypeDescriptor(TYPE_DOUBLE);
+ }
+ }
+ } else if (node instanceof UnaryExpression) {
+ return evaluate(((UnaryExpression) node).astOperand());
+ } else if (node instanceof InlineIfExpression) {
+ InlineIfExpression expression = (InlineIfExpression) node;
+ if (expression.astIfTrue() != null) {
+ return evaluate(expression.astIfTrue());
+ } else if (expression.astIfFalse() != null) {
+ return evaluate(expression.astIfFalse());
+ }
+ } else if (node instanceof BinaryExpression) {
+ BinaryExpression expression = (BinaryExpression) node;
+ BinaryOperator operator = expression.astOperator();
+ switch (operator) {
+ case LOGICAL_OR:
+ case LOGICAL_AND:
+ case EQUALS:
+ case NOT_EQUALS:
+ case GREATER:
+ case GREATER_OR_EQUAL:
+ case LESS:
+ case LESS_OR_EQUAL:
+ return new DefaultTypeDescriptor(TYPE_BOOLEAN);
+ }
+
+ TypeDescriptor type = evaluate(expression.astLeft());
+ if (type != null) {
+ return type;
+ }
+ return evaluate(expression.astRight());
+ }
+
+ if (resolved instanceof ResolvedVariable) {
+ ResolvedVariable variable = (ResolvedVariable) resolved;
+ return variable.getType();
+ }
+
+ return null;
+ }
+
+ /**
+ * Evaluates the given node and returns the likely type of the instance. Convenience
+ * wrapper which creates a new {@linkplain TypeEvaluator}, evaluates the node and returns
+ * the result.
+ *
+ * @param context the context to use to resolve field references, if any
+ * @param node the node to compute the type for
+ * @return the corresponding type descriptor, if found
+ */
+ @Nullable
+ public static TypeDescriptor evaluate(@NonNull JavaContext context, @NonNull Node node) {
+ return new TypeEvaluator(context).evaluate(node);
+ }
+}
diff --git a/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
similarity index 100%
rename from base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
rename to lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
diff --git a/base/lint/libs/lint-checks/.classpath b/lint/libs/lint-checks/.classpath
similarity index 100%
rename from base/lint/libs/lint-checks/.classpath
rename to lint/libs/lint-checks/.classpath
diff --git a/base/lint/libs/lint-checks/.project b/lint/libs/lint-checks/.project
similarity index 100%
rename from base/lint/libs/lint-checks/.project
rename to lint/libs/lint-checks/.project
diff --git a/base/lint/libs/lint-api/.settings/org.eclipse.jdt.core.prefs b/lint/libs/lint-checks/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/lint/libs/lint-api/.settings/org.eclipse.jdt.core.prefs
rename to lint/libs/lint-checks/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/lint/libs/lint-checks/.settings/org.moreunit.prefs b/lint/libs/lint-checks/.settings/org.moreunit.prefs
similarity index 100%
rename from base/lint/libs/lint-checks/.settings/org.moreunit.prefs
rename to lint/libs/lint-checks/.settings/org.moreunit.prefs
diff --git a/base/lint/libs/lint-checks/NOTICE b/lint/libs/lint-checks/NOTICE
similarity index 100%
rename from base/lint/libs/lint-checks/NOTICE
rename to lint/libs/lint-checks/NOTICE
diff --git a/base/lint/libs/lint-checks/build.gradle b/lint/libs/lint-checks/build.gradle
similarity index 100%
rename from base/lint/libs/lint-checks/build.gradle
rename to lint/libs/lint-checks/build.gradle
diff --git a/base/lint/libs/lint-checks/lint-checks-base.iml b/lint/libs/lint-checks/lint-checks-base.iml
similarity index 100%
rename from base/lint/libs/lint-checks/lint-checks-base.iml
rename to lint/libs/lint-checks/lint-checks-base.iml
diff --git a/base/lint/libs/lint-checks/lint-checks.iml b/lint/libs/lint-checks/lint-checks.iml
similarity index 100%
rename from base/lint/libs/lint-checks/lint-checks.iml
rename to lint/libs/lint-checks/lint-checks.iml
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlarmDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlarmDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlarmDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlarmDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetector.java
new file mode 100644
index 0000000..e11a04f
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetector.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Identifier;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
+public class AllowAllHostnameVerifierDetector extends Detector implements JavaScanner {
+
+ @SuppressWarnings("unchecked")
+ private static final Implementation IMPLEMENTATION =
+ new Implementation(AllowAllHostnameVerifierDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ public static final Issue ISSUE = Issue.create("AllowAllHostnameVerifier",
+ "Insecure HostnameVerifier",
+ "This check looks for use of HostnameVerifier implementations " +
+ "whose `verify` method always returns true (thus trusting any hostname) " +
+ "which could result in insecure network traffic caused by trusting arbitrary " +
+ "hostnames in TLS/SSL certificates presented by peers.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ @Nullable @SuppressWarnings("javadoc")
+ public List<String> getApplicableConstructorTypes() {
+ return Collections.singletonList("org.apache.http.conn.ssl.AllowAllHostnameVerifier");
+ }
+
+ @Override
+ public void visitConstructor(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull ConstructorInvocation node,
+ @NonNull ResolvedMethod constructor) {
+ Location location = context.getLocation(node);
+ context.report(ISSUE, node, location,
+ "Using the AllowAllHostnameVerifier HostnameVerifier is unsafe " +
+ "because it always returns true, which could cause insecure network " +
+ "traffic due to trusting TLS/SSL server certificates for wrong " +
+ "hostnames");
+ }
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList("setHostnameVerifier", "setDefaultHostnameVerifier");
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getArgumentCount() == 1 &&
+ node.astArguments().size() == 1 &&
+ method.getArgumentType(0).matchesName("javax.net.ssl.HostnameVerifier")) {
+ Expression argument = node.astArguments().first();
+ ResolvedNode resolvedArgument = context.resolve(argument);
+ if (resolvedArgument instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) resolvedArgument;
+ if (field.getName().equals("ALLOW_ALL_HOSTNAME_VERIFIER")) {
+ Location location = context.getLocation(argument);
+ String message = "Using the ALLOW_ALL_HOSTNAME_VERIFIER HostnameVerifier "
+ + "is unsafe because it always returns true, which could cause "
+ + "insecure network traffic due to trusting TLS/SSL server "
+ + "certificates for wrong hostnames";
+ context.report(ISSUE, argument, location, message);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidAutoDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidAutoDetector.java
new file mode 100644
index 0000000..af50f3e
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidAutoDetector.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.TAG_INTENT_FILTER;
+import static com.android.SdkConstants.TAG_SERVICE;
+import static com.android.xml.AndroidManifest.NODE_ACTION;
+import static com.android.xml.AndroidManifest.NODE_APPLICATION;
+import static com.android.xml.AndroidManifest.NODE_METADATA;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+
+/**
+ * Detector for Android Auto issues.
+ * <p> Uses a {@code <meta-data>} tag with a {@code name="com.google.android.gms.car.application"}
+ * as a trigger for validating Automotive specific issues.
+ */
+public class AndroidAutoDetector extends ResourceXmlDetector
+ implements Detector.XmlScanner, Detector.JavaScanner {
+
+ public static final Implementation IMPL = new Implementation(
+ AndroidAutoDetector.class,
+ EnumSet.of(Scope.RESOURCE_FILE, Scope.MANIFEST, Scope.JAVA_FILE),
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** Invalid attribute for uses tag.*/
+ public static final Issue INVALID_USES_TAG_ISSUE = Issue.create(
+ "InvalidUsesTagAttribute", //$NON-NLS-1$
+ "Invalid `name` attribute for `uses` element.",
+ "The <uses> element in `<automotiveApp>` should contain a " +
+ "valid value for the `name` attribute.\n" +
+ "Valid values are `media` or `notification`.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPL).addMoreInfo(
+ "https://developer.android.com/training/auto/start/index.html#auto-metadata");
+
+ /** Missing MediaBrowserService action */
+ public static final Issue MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE = Issue.create(
+ "MissingMediaBrowserServiceIntentFilter", //$NON-NLS-1$
+ "Missing intent-filter with action `android.media.browse.MediaBrowserService`.",
+ "An Automotive Media App requires an exported service that extends " +
+ "`android.service.media.MediaBrowserService` with an " +
+ "`intent-filter` for the action `android.media.browse.MediaBrowserService` " +
+ "to be able to browse and play media.\n" +
+ "To do this, add\n" +
+ "`<intent-filter>`\n" +
+ " `<action android:name=\"android.media.browse.MediaBrowserService\" />`\n" +
+ "`</intent-filter>`\n to the service that extends " +
+ "`android.service.media.MediaBrowserService`",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPL).addMoreInfo(
+ "https://developer.android.com/training/auto/audio/index.html#config_manifest");
+
+ /** Missing intent-filter for Media Search. */
+ public static final Issue MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH = Issue.create(
+ "MissingIntentFilterForMediaSearch", //$NON-NLS-1$
+ "Missing intent-filter with action `android.media.action.MEDIA_PLAY_FROM_SEARCH`",
+ "To support voice searches on Android Auto, you should also register an " +
+ "`intent-filter` for the action `android.media.action.MEDIA_PLAY_FROM_SEARCH`" +
+ ".\nTo do this, add\n" +
+ "`<intent-filter>`\n" +
+ " `<action android:name=\"android.media.action.MEDIA_PLAY_FROM_SEARCH\" />`\n" +
+ "`</intent-filter>`\n" +
+ "to your `<activity>` or `<service>`.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPL).addMoreInfo(
+ "https://developer.android.com/training/auto/audio/index.html#support_voice");
+
+ /** Missing implementation of MediaSession.Callback#onPlayFromSearch*/
+ public static final Issue MISSING_ON_PLAY_FROM_SEARCH = Issue.create(
+ "MissingOnPlayFromSearch", //$NON-NLS-1$
+ "Missing `onPlayFromSearch`.",
+ "To support voice searches on Android Auto, in addition to adding an " +
+ "`intent-filter` for the action `onPlayFromSearch`," +
+ " you also need to override and implement " +
+ "`onPlayFromSearch(String query, Bundle bundle)`",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPL).addMoreInfo(
+ "https://developer.android.com/training/auto/audio/index.html#support_voice");
+
+ private static final String CAR_APPLICATION_METADATA_NAME =
+ "com.google.android.gms.car.application"; //$NON-NLS-1$
+ private static final String VAL_NAME_MEDIA = "media"; //$NON-NLS-1$
+ private static final String VAL_NAME_NOTIFICATION = "notification"; //$NON-NLS-1$
+ private static final String TAG_AUTOMOTIVE_APP = "automotiveApp"; //$NON-NLS-1$
+ private static final String ATTR_RESOURCE = "resource"; //$NON-NLS-1$
+ private static final String TAG_USES = "uses"; //$NON-NLS-1$
+ private static final String ACTION_MEDIA_BROWSER_SERVICE =
+ "android.media.browse.MediaBrowserService"; //$NON-NLS-1$
+ private static final String ACTION_MEDIA_PLAY_FROM_SEARCH =
+ "android.media.action.MEDIA_PLAY_FROM_SEARCH"; //$NON-NLS-1$
+ private static final String CLASS_MEDIA_SESSION_CALLBACK =
+ "android.media.session.MediaSession.Callback"; //$NON-NLS-1$
+ private static final String CLASS_V4MEDIA_SESSION_COMPAT_CALLBACK =
+ "android.support.v4.media.session.MediaSessionCompat.Callback"; //$NON-NLS-1$
+ private static final String METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH =
+ "onPlayFromSearch"; //$NON-NLS-1$
+ private static final String STRING_ARG = "java.lang.String"; //$NON-NLS-1$
+ private static final String BUNDLE_ARG = "android.os.Bundle"; //$NON-NLS-1$
+
+ /**
+ * Indicates whether we identified that the current app is an automotive app and
+ * that we should validate all the automotive specific issues.
+ */
+ private boolean mDoAutomotiveAppCheck;
+
+ /** Indicates that a {@link #ACTION_MEDIA_BROWSER_SERVICE} intent-filter action was found. */
+ private boolean mMediaIntentFilterFound;
+
+ /** Indicates that a {@link #ACTION_MEDIA_PLAY_FROM_SEARCH} intent-filter action was found. */
+ private boolean mMediaSearchIntentFilterFound;
+
+ /** The resource file name deduced by the meta-data resource value */
+ private String mAutomotiveResourceFileName;
+
+ /** Indicates whether this app is an automotive Media App. */
+ private boolean mIsAutomotiveMediaApp;
+
+ /** {@link Location.Handle} to the application element */
+ private Location.Handle mMainApplicationHandle;
+
+ /** Constructs a new {@link AndroidAutoDetector} check */
+ public AndroidAutoDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ // We only need to check the meta data resource file in res/xml if any.
+ return folderType == ResourceFolderType.XML;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ TAG_AUTOMOTIVE_APP, // Root element of a declared automotive descriptor.
+ NODE_METADATA, // meta-data from AndroidManifest.xml
+ TAG_SERVICE, // service from AndroidManifest.xml
+ TAG_INTENT_FILTER, // Any declared intent-filter from AndroidManifest.xml
+ NODE_APPLICATION // Used for storing the application element/location.
+ );
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ mIsAutomotiveMediaApp = false;
+ mAutomotiveResourceFileName = null;
+ mMediaIntentFilterFound = false;
+ mMediaSearchIntentFilterFound = false;
+ }
+
+ @Override
+ @NonNull
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String tagName = element.getTagName();
+ if (NODE_METADATA.equals(tagName) && !mDoAutomotiveAppCheck) {
+ checkAutoMetadataTag(element);
+ } else if (TAG_AUTOMOTIVE_APP.equals(tagName)) {
+ checkAutomotiveAppElement(context, element);
+ } else if (NODE_APPLICATION.equals(tagName)) {
+ // Disable reporting the error if the Issue was suppressed at
+ // the application level.
+ if (context.getMainProject() == context.getProject()
+ && !context.getProject().isLibrary()) {
+ mMainApplicationHandle = context.createLocationHandle(element);
+ mMainApplicationHandle.setClientData(element);
+ }
+ } else if (TAG_SERVICE.equals(tagName)) {
+ checkServiceForBrowserServiceIntentFilter(element);
+ } else if (TAG_INTENT_FILTER.equals(tagName)) {
+ checkForMediaSearchIntentFilter(element);
+ }
+ }
+
+ private void checkAutoMetadataTag(Element element) {
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+
+ if (CAR_APPLICATION_METADATA_NAME.equals(name)) {
+ String autoFileName = element.getAttributeNS(ANDROID_URI, ATTR_RESOURCE);
+
+ if (autoFileName != null && autoFileName.startsWith("@xml/")) { //$NON-NLS-1$
+ // Store the fact that we need to check all the auto issues.
+ mDoAutomotiveAppCheck = true;
+ mAutomotiveResourceFileName =
+ autoFileName.substring("@xml/".length()) + DOT_XML; //$NON-NLS-1$
+ }
+ }
+ }
+
+ private void checkAutomotiveAppElement(XmlContext context, Element element) {
+ // Indicates whether the current file matches the resource that was registered
+ // in AndroidManifest.xml.
+ boolean isMetadataResource =
+ mAutomotiveResourceFileName != null
+ && mAutomotiveResourceFileName.equals(context.file.getName());
+
+ for (Element child : LintUtils.getChildren(element)) {
+
+ if (TAG_USES.equals(child.getTagName())) {
+ String attrValue = child.getAttribute(ATTR_NAME);
+ if (VAL_NAME_MEDIA.equals(attrValue)) {
+ mIsAutomotiveMediaApp |= isMetadataResource;
+ } else if (!VAL_NAME_NOTIFICATION.equals(attrValue)
+ && context.isEnabled(INVALID_USES_TAG_ISSUE)) {
+ // Error invalid value for attribute.
+ Attr node = child.getAttributeNode(ATTR_NAME);
+ if (node == null) {
+ // no name specified
+ continue;
+ }
+ context.report(INVALID_USES_TAG_ISSUE, node,
+ context.getLocation(node),
+ "Expecting one of `" + VAL_NAME_MEDIA + "` or `" +
+ VAL_NAME_NOTIFICATION + "` for the name " +
+ "attribute in " + TAG_USES + " tag.");
+ }
+ }
+ }
+ // Report any errors that we have collected that can be shown to the user
+ // once we determine that this is an Automotive Media App.
+ if (mIsAutomotiveMediaApp
+ && !context.getProject().isLibrary()
+ && mMainApplicationHandle != null
+ && mDoAutomotiveAppCheck) {
+
+ Element node = (Element) mMainApplicationHandle.getClientData();
+
+ if (!mMediaIntentFilterFound
+ && context.isEnabled(MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE)) {
+ context.report(MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE, node,
+ mMainApplicationHandle.resolve(),
+ "Missing `intent-filter` for action " +
+ "`android.media.browse.MediaBrowserService` that is required for " +
+ "android auto support");
+ }
+ if (!mMediaSearchIntentFilterFound
+ && context.isEnabled(MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH)) {
+ context.report(MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH, node,
+ mMainApplicationHandle.resolve(),
+ "Missing `intent-filter` for action " +
+ "`android.media.action.MEDIA_PLAY_FROM_SEARCH`.");
+ }
+ }
+ }
+
+ private void checkServiceForBrowserServiceIntentFilter(Element element) {
+ if (TAG_SERVICE.equals(element.getTagName())
+ && !mMediaIntentFilterFound) {
+
+ for (Element child : LintUtils.getChildren(element)) {
+ String tagName = child.getTagName();
+ if (TAG_INTENT_FILTER.equals(tagName)) {
+ for (Element filterChild : LintUtils.getChildren(child)) {
+ if (NODE_ACTION.equals(filterChild.getTagName())) {
+ String actionValue = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (ACTION_MEDIA_BROWSER_SERVICE.equals(actionValue)) {
+ mMediaIntentFilterFound = true;
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void checkForMediaSearchIntentFilter(Element element) {
+ if (!mMediaSearchIntentFilterFound) {
+
+ for (Element filterChild : LintUtils.getChildren(element)) {
+ if (NODE_ACTION.equals(filterChild.getTagName())) {
+ String actionValue = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (ACTION_MEDIA_PLAY_FROM_SEARCH.equals(actionValue)) {
+ mMediaSearchIntentFilterFound = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Implementation of the JavaScanner
+
+ @Override
+ @Nullable
+ public List<String> applicableSuperClasses() {
+ // We currently enable scanning only for media apps.
+ return mIsAutomotiveMediaApp ?
+ Arrays.asList(CLASS_MEDIA_SESSION_CALLBACK,
+ CLASS_V4MEDIA_SESSION_COMPAT_CALLBACK)
+ : null;
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
+ @NonNull Node node, @NonNull JavaParser.ResolvedClass resolvedClass) {
+ // Only check classes that are not declared abstract.
+ if (declaration != null && (resolvedClass.getModifiers() & Modifier.ABSTRACT) == 0) {
+ MediaSessionCallbackVisitor visitor = new MediaSessionCallbackVisitor(context);
+ declaration.accept(visitor);
+ if (!visitor.isPlayFromSearchMethodFound()
+ && context.isEnabled(MISSING_ON_PLAY_FROM_SEARCH)) {
+
+ context.report(MISSING_ON_PLAY_FROM_SEARCH, declaration.astName(),
+ context.getLocation(declaration.astName()),
+ "This class does not override `" +
+ METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH + "` from `MediaSession.Callback`" +
+ " The method should be overridden and implemented to support " +
+ "Voice search on Android Auto.");
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList(METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH);
+ }
+
+ /**
+ * A Visitor class to search for {@code MediaSession.Callback#onPlayFromSearch(..)}
+ * method declaration.
+ */
+ private static class MediaSessionCallbackVisitor extends ForwardingAstVisitor {
+
+ private final JavaContext mContext;
+
+ private boolean mOnPlayFromSearchFound;
+
+ public MediaSessionCallbackVisitor(JavaContext context) {
+ this.mContext = context;
+ }
+
+ public boolean isPlayFromSearchMethodFound() {
+ return mOnPlayFromSearchFound;
+ }
+
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ JavaParser.ResolvedNode result = mContext.resolve(node);
+ if (result != null
+ && METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH.equals(result.getName())
+ && result instanceof JavaParser.ResolvedMethod) {
+ JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) result;
+ if (method.getArgumentCount() == 2) {
+ JavaParser.TypeDescriptor firstArg = method.getArgumentType(0);
+ JavaParser.TypeDescriptor secondArg = method.getArgumentType(1);
+ if (firstArg.getTypeClass() != null
+ && firstArg.getTypeClass().matches(STRING_ARG)
+ && secondArg.getTypeClass() != null
+ && secondArg.getTypeClass().matches(BUNDLE_ARG)) {
+ mOnPlayFromSearchFound = true;
+ }
+ }
+ }
+ return super.visitMethodDeclaration(node);
+ }
+ }
+
+ // Used by AS to show errors.
+ @NonNull
+ public static String[] getAllowedAutomotiveAppTypes() {
+ return new String[]{VAL_NAME_MEDIA, VAL_NAME_NOTIFICATION};
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidTvDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidTvDetector.java
new file mode 100644
index 0000000..a1d175b
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AndroidTvDetector.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.TAG_USES_FEATURE;
+import static com.android.SdkConstants.TAG_USES_PERMISSION;
+import static com.android.tools.lint.detector.api.TextFormat.RAW;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_REQUIRED;
+import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
+import static com.android.xml.AndroidManifest.NODE_APPLICATION;
+import static com.android.xml.AndroidManifest.NODE_CATEGORY;
+import static com.android.xml.AndroidManifest.NODE_INTENT;
+import static com.android.xml.AndroidManifest.NODE_USES_FEATURE;
+import static com.android.xml.AndroidManifest.NODE_USES_PERMISSION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Detects various issues for Android TV.
+ */
+public class AndroidTvDetector extends Detector implements Detector.XmlScanner {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ AndroidTvDetector.class,
+ Scope.MANIFEST_SCOPE
+ );
+
+ /** Using hardware unsupported by TV */
+ public static final Issue UNSUPPORTED_TV_HARDWARE = Issue.create(
+ "UnsupportedTvHardware", //$NON-NLS-1$
+ "Unsupported TV Hardware Feature",
+ "The <uses-feature> element should not require this unsupported TV hardware feature. " +
+ "Any uses-feature not explicitly marked with required=\"false\" is necessary on the " +
+ "device to be installed on. " +
+ "Ensure that any features that might prevent it from being installed on a TV device " +
+ "are reviewed and marked as not required in the manifest.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPLEMENTATION).addMoreInfo(
+ "https://developer.android.com/training/tv/start/hardware.html#unsupported-features");
+
+ /** Missing leanback launcher intent filter */
+ public static final Issue MISSING_LEANBACK_LAUNCHER = Issue.create(
+ "MissingLeanbackLauncher", //$NON-NLS-1$
+ "Missing Leanback Launcher Intent Filter.",
+ "An application intended to run on TV devices must declare a launcher activity " +
+ "for TV in its manifest using a `android.intent.category.LEANBACK_LAUNCHER` " +
+ "intent filter.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION)
+ .addMoreInfo("https://developer.android.com/training/tv/start/start.html#tv-activity");
+
+ /** Missing leanback support */
+ public static final Issue MISSING_LEANBACK_SUPPORT = Issue.create(
+ "MissingLeanbackSupport", //$NON-NLS-1$
+ "Missing Leanback Support.",
+ "The manifest should declare the use of the Leanback user interface required " +
+ "by Android TV.\n" +
+ "To fix this, add\n" +
+ "`<uses-feature android:name=\"android.software.leanback\" " +
+ " android:required=\"false\" />`\n" +
+ "to your manifest.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPLEMENTATION)
+ .addMoreInfo("https://developer.android.com/training/tv/start/start.html#leanback-req");
+
+ /** Permission implies required hardware unsupported by TV */
+ public static final Issue PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE = Issue.create(
+ "PermissionImpliesUnsupportedHardware", //$NON-NLS-1$
+ "Permission Implies Unsupported Hardware",
+
+ "The <uses-permission> element should not require a permission that implies an " +
+ "unsupported TV hardware feature. Google Play assumes that certain hardware related " +
+ "permissions indicate that the underlying hardware features are required by default. " +
+ "To fix the issue, consider declaring the corresponding uses-feature element with " +
+ "required=\"false\" attribute.",
+ Category.CORRECTNESS,
+ 3,
+ Severity.WARNING,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions");
+
+ /** Missing banner attibute */
+ public static final Issue MISSING_BANNER = Issue.create(
+ "MissingTvBanner", //$NON-NLS-1$
+ "TV Missing Banner",
+ "A TV application must provide a home screen banner for each localization if it " +
+ "includes a Leanback launcher intent filter. The banner is the app launch point that " +
+ "appears on the home screen in the apps and games rows.",
+ Category.CORRECTNESS,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo("http://developer.android.com/training/tv/start/start.html#banner");
+
+ public static final String SOFTWARE_FEATURE_LEANBACK =
+ "android.software.leanback"; //$NON-NLS-1$
+
+ private static final String LEANBACK_LIB_ARTIFACT =
+ "com.android.support:leanback-v17"; //$NON-NLS-1$
+
+ private static final String CATEGORY_LEANBACK_LAUNCHER =
+ "android.intent.category.LEANBACK_LAUNCHER"; //$NON-NLS-1$
+
+ private static final String HARDWARE_FEATURE_CAMERA = "android.hardware.camera"; //$NON-NLS-1$
+
+ private static final String HARDWARE_FEATURE_LOCATION_GPS =
+ "android.hardware.location.gps"; //$NON-NLS-1$
+
+ private static final String ANDROID_HARDWARE_TELEPHONY =
+ "android.hardware.telephony"; //$NON-NLS-1$
+
+ private static final String ANDROID_HARDWARE_BLUETOOTH =
+ "android.hardware.bluetooth"; //$NON-NLS-1$
+
+ private static final String ATTR_BANNER = "banner"; //$NON-NLS-1$
+
+ private static final String ANDROID_HARDWARE_MICROPHONE =
+ "android.hardware.microphone"; //$NON-NLS-1$
+
+ // https://developer.android.com/training/tv/start/hardware.html
+ private static final Set<String> UNSUPPORTED_HARDWARE_FEATURES =
+ ImmutableSet.<String>builder()
+ .add("android.hardware.touchscreen") //$NON-NLS-1$
+ .add("android.hardware.faketouch") //$NON-NLS-1$
+ .add(ANDROID_HARDWARE_TELEPHONY)
+ .add(HARDWARE_FEATURE_CAMERA)
+ .add(ANDROID_HARDWARE_BLUETOOTH)
+ .add("android.hardware.nfc") //$NON-NLS-1$
+ .add(HARDWARE_FEATURE_LOCATION_GPS) //$NON-NLS-1$
+ .add(ANDROID_HARDWARE_MICROPHONE) //$NON-NLS-1$
+ .add("android.hardware.sensors") //$NON-NLS-1$
+ .build();
+
+ private static final Map<String, String> PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE =
+ ImmutableMap.<String, String>builder()
+ .put("android.permission.BLUETOOTH", //$NON-NLS-1$
+ ANDROID_HARDWARE_BLUETOOTH)
+ .put("android.permission.BLUETOOTH_ADMIN", //$NON-NLS-1$
+ ANDROID_HARDWARE_BLUETOOTH)
+ .put("android.permission.CAMERA", //$NON-NLS-1$
+ HARDWARE_FEATURE_CAMERA)
+ .put("android.permission.RECORD_AUDIO", //$NON-NLS-1$
+ ANDROID_HARDWARE_MICROPHONE)
+ .put("android.permission.ACCESS_FINE_LOCATION", //$NON-NLS-1$
+ HARDWARE_FEATURE_LOCATION_GPS)
+ .put("android.permission.CALL_PHONE", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.CALL_PRIVILEGED", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.PROCESS_OUTGOING_CALLS", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.READ_SMS", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.RECEIVE_SMS", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.RECEIVE_MMS", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.RECEIVE_WAP_PUSH", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.SEND_SMS", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.WRITE_APN_SETTINGS", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .put("android.permission.WRITE_SMS", //$NON-NLS-1$
+ ANDROID_HARDWARE_TELEPHONY)
+ .build();
+
+ /**
+ * If you change number of parameters or order, update
+ * {@link #getHardwareFeature(String, TextFormat)}
+ */
+ private static final String USES_HARDWARE_ERROR_MESSAGE_FORMAT =
+ "Permission exists without corresponding hardware `<uses-feature "
+ + "android:name=\"%1$s\" required=\"false\">` tag.";
+
+ /** Constructs a new {@link AndroidTvDetector} check */
+ public AndroidTvDetector() {
+ }
+
+ /** Used for {@link #MISSING_LEANBACK_LAUNCHER} */
+ private boolean mHasLeanbackLauncherActivity;
+
+ /** Used for {@link #MISSING_LEANBACK_SUPPORT} */
+ private boolean mHasLeanbackSupport;
+
+ /** Whether the app has a leanback-v7 dependency */
+ private boolean mHasLeanbackDependency;
+
+ /** Used for {@link #MISSING_BANNER} */
+ private boolean mHasApplicationBanner;
+
+ /** No. of activities that have the leanback intent but
+ * dont declare banners */
+ private int mLeanbackActivitiesWithoutBanners;
+
+ /** All permissions that imply unsupported tv hardware. */
+ private List<String> mUnsupportedHardwareImpliedPermissions;
+
+ /** All Unsupported TV uses features in use by the current manifest.*/
+ private Set<String> mAllUnsupportedTvUsesFeatures;
+
+ /** Set containing unsupported TV uses-features elements without required="false" */
+ private Set<String> mUnsupportedTvUsesFeatures;
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ NODE_APPLICATION,
+ NODE_ACTIVITY,
+ NODE_USES_FEATURE,
+ NODE_USES_PERMISSION
+ );
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ mHasLeanbackLauncherActivity = false;
+ mHasLeanbackSupport = false;
+ mHasApplicationBanner = false;
+ mLeanbackActivitiesWithoutBanners = 0;
+ mUnsupportedHardwareImpliedPermissions = Lists.newArrayListWithExpectedSize(2);
+ mUnsupportedTvUsesFeatures = Sets.newHashSetWithExpectedSize(2);
+ mAllUnsupportedTvUsesFeatures = Sets.newHashSetWithExpectedSize(2);
+
+ // Check gradle dependency
+ Project mainProject = context.getMainProject();
+ mHasLeanbackDependency = (mainProject.isGradleProject()
+ && Boolean.TRUE.equals(mainProject.dependsOn(LEANBACK_LIB_ARTIFACT)));
+ }
+
+ @Override
+ public void afterCheckFile(@NonNull Context context) {
+ boolean isTvApp = mHasLeanbackSupport
+ || mHasLeanbackDependency
+ || mHasLeanbackLauncherActivity;
+
+ if (!context.getMainProject().isLibrary() && isTvApp) {
+ XmlContext xmlContext = (XmlContext) context;
+ // Report an error if there's not at least one leanback launcher intent filter activity
+ if (!mHasLeanbackLauncherActivity
+ && xmlContext.isEnabled(MISSING_LEANBACK_LAUNCHER)) {
+ // No launch activity
+ Node manifestNode = xmlContext.document.getDocumentElement();
+ if (manifestNode != null) {
+ xmlContext.report(MISSING_LEANBACK_LAUNCHER, manifestNode,
+ xmlContext.getLocation(manifestNode),
+ "Expecting an activity to have `" + CATEGORY_LEANBACK_LAUNCHER +
+ "` intent filter.");
+ }
+ }
+
+ // Report an issue if there is no leanback <uses-feature> tag.
+ if (!mHasLeanbackSupport
+ && xmlContext.isEnabled(MISSING_LEANBACK_SUPPORT)) {
+ Node manifestNode = xmlContext.document.getDocumentElement();
+ if (manifestNode != null) {
+ xmlContext.report(MISSING_LEANBACK_SUPPORT, manifestNode,
+ xmlContext.getLocation(manifestNode),
+ "Expecting <uses-feature android:name=\"android.software.leanback\" " +
+ "android:required=\"false\" /> tag.");
+ }
+ }
+
+ // Report missing banners
+ if (!mHasApplicationBanner // no application banner
+ && mLeanbackActivitiesWithoutBanners > 0 // leanback activity without banner
+ && xmlContext.isEnabled(MISSING_BANNER)) {
+ Node applicationElement = getApplicationElement(xmlContext.document);
+ if (applicationElement != null) {
+ xmlContext.report(MISSING_BANNER, applicationElement,
+ xmlContext.getLocation(applicationElement),
+ "Expecting `android:banner` with the `<application>` tag or each "
+ + "Leanback launcher activity.");
+ }
+ }
+
+ // Report all unsupported TV hardware uses-feature.
+ // These point to all unsupported tv uses features that have not be marked
+ // required = false;
+ if (!mUnsupportedTvUsesFeatures.isEmpty()
+ && xmlContext.isEnabled(UNSUPPORTED_TV_HARDWARE)) {
+ List<Element> usesFeatureElements =
+ findUsesFeatureElements(mUnsupportedTvUsesFeatures, xmlContext.document);
+ for (Element element : usesFeatureElements) {
+ Attr attrRequired = element.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_REQUIRED);
+ Node location = attrRequired == null ? element : attrRequired;
+ xmlContext.report(UNSUPPORTED_TV_HARDWARE, location,
+ xmlContext.getLocation(location),
+ "Expecting `android:required=\"false\"` for this hardware "
+ + "feature that may not be supported by all Android TVs.");
+ }
+ }
+
+ // Report permissions implying unsupported hardware
+ if (!mUnsupportedHardwareImpliedPermissions.isEmpty()
+ && xmlContext.isEnabled(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE)) {
+
+ Collection<String> filteredPermissions = Collections2.filter(
+ mUnsupportedHardwareImpliedPermissions,
+ new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ // Filter out all permissions that already have their
+ // corresponding implied hardware declared in
+ // the AndroidManifest.xml
+ String usesFeature =
+ PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE.get(input);
+ return usesFeature != null
+ && !mAllUnsupportedTvUsesFeatures.contains(usesFeature);
+ }
+ });
+
+ List<Element> permissionsWithoutUsesFeatures =
+ findPermissionElements(filteredPermissions, xmlContext.document);
+
+ for (Element permissionElement : permissionsWithoutUsesFeatures) {
+ String name = permissionElement.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ String unsupportedHardwareName =
+ PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE.get(name);
+
+ if (unsupportedHardwareName != null) {
+ String message = String.format(
+ USES_HARDWARE_ERROR_MESSAGE_FORMAT, unsupportedHardwareName);
+ xmlContext.report(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE,
+ permissionElement,
+ xmlContext.getLocation(permissionElement), message);
+ }
+ }
+ }
+ }
+ }
+
+ private static List<Element> findPermissionElements(Collection<String> permissions,
+ Document document) {
+ Node manifestElement = document.getDocumentElement();
+ if (manifestElement == null) {
+ return Collections.emptyList();
+ }
+ List<Element> nodes = new ArrayList<Element>(permissions.size());
+ for (Element child : LintUtils.getChildren(manifestElement)) {
+ if (TAG_USES_PERMISSION.equals(child.getTagName())
+ && permissions.contains(child.getAttributeNS(ANDROID_URI, ATTR_NAME))) {
+ nodes.add(child);
+ }
+ }
+ return nodes;
+ }
+
+ /**
+ * Method to find all matching uses-feature elements in one go.
+ * Rather than iterating over the entire list of child nodes only to return the one that
+ * match a particular featureName, we use this method to iterate and return all the
+ * uses-feature elements of interest in a single iteration of the manifest element's children.
+ *
+ * @param featureNames The set of all features to look for inside the
+ * <code><manifest></code> node of the document.
+ * @param document The document/root node to use for iterating.
+ * @return A list of all <code><uses-feature></code> elements that match the featureNames.
+ */
+ private static List<Element> findUsesFeatureElements(@NonNull Set<String> featureNames,
+ @NonNull Document document) {
+ Node manifestElement = document.getDocumentElement();
+ if (manifestElement == null) {
+ return Collections.emptyList();
+ }
+ List<Element> nodes = new ArrayList<Element>(featureNames.size());
+ for (Element child : LintUtils.getChildren(manifestElement)) {
+ if (TAG_USES_FEATURE.equals(child.getTagName())
+ && featureNames.contains(child.getAttributeNS(ANDROID_URI, ATTR_NAME))) {
+ nodes.add(child);
+ }
+ }
+ return nodes;
+ }
+
+ /**
+ * @param document The root of the document.
+ * @return The Node pointing to the {@link com.android.xml.AndroidManifest#NODE_APPLICATION}
+ * of the document.
+ */
+ private static Node getApplicationElement(Document document) {
+ Node manifestNode = document.getDocumentElement();
+ if (manifestNode != null) {
+ return getElementWithTagName(NODE_APPLICATION, manifestNode);
+ }
+ return null;
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String elementName = element.getTagName();
+
+ if (NODE_APPLICATION.equals(elementName)) {
+ mHasApplicationBanner = element.hasAttributeNS(ANDROID_URI, ATTR_BANNER);
+ } else if (NODE_USES_FEATURE.equals(elementName)) {
+ // Ensures that unsupported hardware features aren't required.
+ Attr name = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (name != null) {
+ String featureName = name.getValue();
+ if (isUnsupportedHardwareFeature(featureName)) {
+ mAllUnsupportedTvUsesFeatures.add(featureName);
+ Attr required =
+ element.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_REQUIRED);
+ if (required == null || Boolean.parseBoolean(required.getValue())) {
+ mUnsupportedTvUsesFeatures.add(featureName);
+ }
+ }
+ }
+
+ if (!mHasLeanbackSupport && hasLeanbackSupport(element)) {
+ mHasLeanbackSupport = true;
+ }
+ } else if (NODE_ACTIVITY.equals(elementName) && hasLeanbackIntentFilter(element)) {
+ mHasLeanbackLauncherActivity = true;
+ // Since this activity has a leanback launcher intent filter,
+ // Make sure it has a home screen banner
+ if (!element.hasAttributeNS(ANDROID_URI, ATTR_BANNER)) {
+ mLeanbackActivitiesWithoutBanners++;
+ }
+ } else if (NODE_USES_PERMISSION.equals(elementName)) {
+
+ // Store all <uses-permission> tags that imply unsupported hardware)
+ String permissionName = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (PERMISSIONS_TO_IMPLIED_UNSUPPORTED_HARDWARE.containsKey(permissionName)) {
+ mUnsupportedHardwareImpliedPermissions.add(permissionName);
+ }
+ }
+ }
+
+ private static boolean hasLeanbackSupport(Element element) {
+ assert NODE_USES_FEATURE.equals(element.getTagName()) : element.getTagName();
+ return SOFTWARE_FEATURE_LEANBACK.equals(element.getAttributeNS(ANDROID_URI, ATTR_NAME));
+ }
+
+ private static boolean isUnsupportedHardwareFeature(@NonNull String featureName) {
+ for (String prefix : UNSUPPORTED_HARDWARE_FEATURES) {
+ if (featureName.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasLeanbackIntentFilter(@NonNull Node activityNode) {
+ assert NODE_ACTIVITY.equals(activityNode.getNodeName()) : activityNode.getNodeName();
+ // Visit every intent filter
+ for (Element activityChild : LintUtils.getChildren(activityNode)) {
+ if (NODE_INTENT.equals(activityChild.getNodeName())) {
+ for (Element intentFilterChild : LintUtils.getChildren(activityChild)) {
+ // Check to see if the category is the leanback launcher
+ String attrName = intentFilterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (NODE_CATEGORY.equals(intentFilterChild.getNodeName())
+ && CATEGORY_LEANBACK_LAUNCHER.equals(attrName)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Assumes that the node is a direct child of the given Node.
+ */
+ private static Node getElementWithTagName(@NonNull String tagName, @NonNull Node node) {
+ for (Element child : LintUtils.getChildren(node)) {
+ if (tagName.equals(child.getTagName())) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Given an error message created by this lint check, return the corresponding featureName
+ * that it suggests should be added.
+ * (Intended to support quickfix implementations for this lint check.)
+ *
+ * @param errorMessage The error message originally produced by this detector.
+ * @param format The format of the error message.
+ * @return the corresponding featureName, or null if not recognized
+ */
+ @Nullable
+ public static String getHardwareFeature(@NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ List<String> parameters = LintUtils.getFormattedParameters(
+ RAW.convertTo(USES_HARDWARE_ERROR_MESSAGE_FORMAT, format),
+ errorMessage);
+ if (parameters.size() == 1) {
+ return parameters.get(0);
+ }
+ return null;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
new file mode 100644
index 0000000..6d418e4
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ATTR_VALUE;
+import static com.android.SdkConstants.FQCN_SUPPRESS_LINT;
+import static com.android.SdkConstants.INT_DEF_ANNOTATION;
+import static com.android.SdkConstants.SUPPRESS_LINT;
+import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.filterRelevantAnnotations;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.detector.api.JavaContext.findSurroundingClass;
+import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
+import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.Annotation;
+import lombok.ast.AnnotationDeclaration;
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationValue;
+import lombok.ast.ArrayInitializer;
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.Block;
+import lombok.ast.Case;
+import lombok.ast.Cast;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Modifiers;
+import lombok.ast.Node;
+import lombok.ast.Select;
+import lombok.ast.Statement;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+import lombok.ast.Switch;
+import lombok.ast.TypeBody;
+import lombok.ast.TypeMember;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Checks annotations to make sure they are valid
+ */
+public class AnnotationDetector extends Detector implements Detector.JavaScanner {
+
+ public static final Implementation IMPLEMENTATION = new Implementation(
+ AnnotationDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Placing SuppressLint on a local variable doesn't work for class-file based checks */
+ public static final Issue INSIDE_METHOD = Issue.create(
+ "LocalSuppress", //$NON-NLS-1$
+ "@SuppressLint on invalid element",
+
+ "The `@SuppressAnnotation` is used to suppress Lint warnings in Java files. However, " +
+ "while many lint checks analyzes the Java source code, where they can find " +
+ "annotations on (for example) local variables, some checks are analyzing the " +
+ "`.class` files. And in class files, annotations only appear on classes, fields " +
+ "and methods. Annotations placed on local variables disappear. If you attempt " +
+ "to suppress a lint error for a class-file based lint check, the suppress " +
+ "annotation not work. You must move the annotation out to the surrounding method.",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** IntDef annotations should be unique */
+ public static final Issue UNIQUE = Issue.create(
+ "UniqueConstants", //$NON-NLS-1$
+ "Overlapping Enumeration Constants",
+
+ "The `@IntDef` annotation allows you to " +
+ "create a light-weight \"enum\" or type definition. However, it's possible to " +
+ "accidentally specify the same value for two or more of the values, which can " +
+ "lead to hard-to-detect bugs. This check looks for this scenario and flags any " +
+ "repeated constants.\n" +
+ "\n" +
+ "In some cases, the repeated constant is intentional (for example, renaming a " +
+ "constant to a more intuitive name, and leaving the old name in place for " +
+ "compatibility purposes.) In that case, simply suppress this check by adding a " +
+ "`@SuppressLint(\"UniqueConstants\")` annotation.",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Flags should typically be specified as bit shifts */
+ public static final Issue FLAG_STYLE = Issue.create(
+ "ShiftFlags", //$NON-NLS-1$
+ "Dangerous Flag Constant Declaration",
+
+ "When defining multiple constants for use in flags, the recommended style is " +
+ "to use the form `1 << 2`, `1 << 3`, `1 << 4` and so on to ensure that the " +
+ "constants are unique and non-overlapping.",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** All IntDef constants should be included in switch */
+ public static final Issue SWITCH_TYPE_DEF = Issue.create(
+ "SwitchIntDef", //$NON-NLS-1$
+ "Missing @IntDef in Switch",
+
+ "This check warns if a `switch` statement does not explicitly include all " +
+ "the values declared by the typedef `@IntDef` declaration.",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Constructs a new {@link AnnotationDetector} check */
+ public AnnotationDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ //noinspection unchecked
+ return Arrays.<Class<? extends Node>>asList(Annotation.class, Switch.class);
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ return new AnnotationChecker(context);
+ }
+
+ private static class AnnotationChecker extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public AnnotationChecker(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitAnnotation(Annotation node) {
+ String type = node.astAnnotationTypeReference().getTypeName();
+ if (SUPPRESS_LINT.equals(type) || FQCN_SUPPRESS_LINT.equals(type)) {
+ Node parent = node.getParent();
+ if (parent instanceof Modifiers) {
+ parent = parent.getParent();
+ if (parent instanceof VariableDefinition) {
+ for (AnnotationElement element : node.astElements()) {
+ AnnotationValue valueNode = element.astValue();
+ if (valueNode == null) {
+ continue;
+ }
+ if (valueNode instanceof StringLiteral) {
+ StringLiteral literal = (StringLiteral) valueNode;
+ String id = literal.astValue();
+ if (!checkId(node, id)) {
+ return super.visitAnnotation(node);
+ }
+ } else if (valueNode instanceof ArrayInitializer) {
+ ArrayInitializer array = (ArrayInitializer) valueNode;
+ StrictListAccessor<Expression, ArrayInitializer> expressions =
+ array.astExpressions();
+ if (expressions == null) {
+ continue;
+ }
+ for (Expression arrayElement : expressions) {
+ if (arrayElement instanceof StringLiteral) {
+ String id = ((StringLiteral) arrayElement).astValue();
+ if (!checkId(node, id)) {
+ return super.visitAnnotation(node);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (INT_DEF_ANNOTATION.equals(type) || "IntDef".equals(type)) {
+ // Make sure that all the constants are unique
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedAnnotation) {
+ ensureUniqueValues(((ResolvedAnnotation)resolved), node);
+ }
+ }
+
+ return super.visitAnnotation(node);
+ }
+
+ @Override
+ public boolean visitSwitch(Switch node) {
+ Expression condition = node.astCondition();
+ TypeDescriptor type = mContext.getType(condition);
+ if (type != null && type.matchesName(TYPE_INT)) {
+ ResolvedAnnotation annotation = findIntDef(condition);
+ if (annotation != null) {
+ checkSwitch(node, annotation);
+ }
+ }
+
+ return super.visitSwitch(node);
+ }
+
+ /**
+ * Searches for the corresponding @IntDef annotation definition associated
+ * with a given node
+ */
+ @Nullable
+ private ResolvedAnnotation findIntDef(@NonNull Node node) {
+ if ((node instanceof VariableReference || node instanceof Select)) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved == null) {
+ return null;
+ }
+
+ ResolvedAnnotation annotation = SupportAnnotationDetector.findIntDef(
+ filterRelevantAnnotations(resolved.getAnnotations()));
+ if (annotation != null) {
+ return annotation;
+ }
+
+ if (node instanceof VariableReference) {
+ Statement statement = getParentOfType(node, Statement.class, false);
+ if (statement != null) {
+ ListIterator<Node> iterator =
+ statement.getParent().getChildren().listIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next() == statement) {
+ if (iterator.hasPrevious()) { // should always be true
+ iterator.previous();
+ }
+ break;
+ }
+ }
+
+ String targetName = ((VariableReference) node).astIdentifier().astValue();
+ while (iterator.hasPrevious()) {
+ Node previous = iterator.previous();
+ if (previous instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) previous;
+ VariableDefinition definition = declaration.astDefinition();
+ for (VariableDefinitionEntry entry : definition
+ .astVariables()) {
+ if (entry.astInitializer() != null
+ && entry.astName().astValue().equals(targetName)) {
+ return findIntDef(entry.astInitializer());
+ }
+ }
+ } else if (previous instanceof ExpressionStatement) {
+ ExpressionStatement expressionStatement =
+ (ExpressionStatement) previous;
+ Expression expression = expressionStatement.astExpression();
+ if (expression instanceof BinaryExpression &&
+ ((BinaryExpression) expression).astOperator()
+ == BinaryOperator.ASSIGN) {
+ BinaryExpression binaryExpression
+ = (BinaryExpression) expression;
+ if (targetName.equals(binaryExpression.astLeft().toString())) {
+ return findIntDef(binaryExpression.astRight());
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (node instanceof MethodInvocation) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved != null) {
+ ResolvedAnnotation annotation = SupportAnnotationDetector
+ .findIntDef(filterRelevantAnnotations(resolved.getAnnotations()));
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+ } else if (node instanceof InlineIfExpression) {
+ InlineIfExpression expression = (InlineIfExpression) node;
+ if (expression.astIfTrue() != null) {
+ ResolvedAnnotation result = findIntDef(expression.astIfTrue());
+ if (result != null) {
+ return result;
+ }
+ }
+ if (expression.astIfFalse() != null) {
+ ResolvedAnnotation result = findIntDef(expression.astIfFalse());
+ if (result != null) {
+ return result;
+ }
+ }
+ } else if (node instanceof Cast) {
+ Cast cast = (Cast) node;
+ return findIntDef(cast.astOperand());
+ }
+
+ return null;
+ }
+
+ private void checkSwitch(@NonNull Switch node, @NonNull ResolvedAnnotation annotation) {
+ Block block = node.astBody();
+ if (block == null) {
+ return;
+ }
+
+ Object allowed = annotation.getValue();
+ if (!(allowed instanceof Object[])) {
+ return;
+ }
+ Object[] allowedValues = (Object[]) allowed;
+ List<ResolvedField> fields = Lists.newArrayListWithCapacity(allowedValues.length);
+ for (Object o : allowedValues) {
+ if (o instanceof ResolvedField) {
+ fields.add((ResolvedField) o);
+ }
+ }
+
+ // Empty switch: arguably we could skip these (since the IDE already warns about
+ // empty switches) but it's useful since the quickfix will kick in and offer all
+ // the missing ones when you're editing.
+ // if (block.astContents().isEmpty()) { return; }
+
+ for (Statement statement : block.astContents()) {
+ if (statement instanceof Case) {
+ Case caseStatement = (Case) statement;
+ Expression expression = caseStatement.astCondition();
+ if (expression instanceof IntegralLiteral) {
+ // Report warnings if you specify hardcoded constants.
+ // It's the wrong thing to do.
+ List<String> list = computeFieldNames(node, Arrays.asList(allowedValues));
+ // Keep error message in sync with {@link #getMissingCases}
+ String message = "Don't use a constant here; expected one of: " + Joiner
+ .on(", ").join(list);
+ mContext.report(SWITCH_TYPE_DEF, expression,
+ mContext.getLocation(expression), message);
+ return; // Don't look for other missing typedef constants since you might
+ // have aliased with value
+ } else if (expression != null) { // default case can have null expression
+ ResolvedNode resolved = mContext.resolve(expression);
+ if (resolved == null) {
+ // If there are compilation issues (e.g. user is editing code) we
+ // can't be certain, so don't flag anything.
+ return;
+ }
+ if (resolved instanceof ResolvedField) {
+ // We can't just do
+ // fields.remove(resolved);
+ // since the fields list contains instances of potentially
+ // different types with different hash codes. The
+ // equals method on ResolvedExternalField deliberately handles
+ // this (but it can't make its hash code match what
+ // the ECJ fields do, which is tied to the ECJ binding hash code.)
+ // So instead, manually check for equals. These lists tend to
+ // be very short anyway.
+ ListIterator<ResolvedField> iterator = fields.listIterator();
+ while (iterator.hasNext()) {
+ ResolvedField field = iterator.next();
+ if (field.equals(resolved)) {
+ iterator.remove();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!fields.isEmpty()) {
+ List<String> list = computeFieldNames(node, fields);
+ // Keep error message in sync with {@link #getMissingCases}
+ String message = "Switch statement on an `int` with known associated constant "
+ + "missing case " + Joiner.on(", ").join(list);
+ Location location = mContext.getNameLocation(node);
+ mContext.report(SWITCH_TYPE_DEF, node, location, message);
+ }
+ }
+
+ private void ensureUniqueValues(@NonNull ResolvedAnnotation annotation,
+ @NonNull Annotation node) {
+ Object allowed = annotation.getValue();
+ if (allowed instanceof Object[]) {
+ Object[] allowedValues = (Object[]) allowed;
+ Map<Number,Integer> valueToIndex =
+ Maps.newHashMapWithExpectedSize(allowedValues.length);
+
+ List<Node> constants = null;
+ for (AnnotationElement element : node.astElements()) {
+ if (element.astName() == null
+ || ATTR_VALUE.equals(element.astName().astValue())) {
+ AnnotationValue value = element.astValue();
+ if (value instanceof ArrayInitializer) {
+ ArrayInitializer initializer = (ArrayInitializer)value;
+ constants = Lists.newArrayListWithExpectedSize(allowedValues.length);
+ for (Expression expression : initializer.astExpressions()) {
+ constants.add(expression);
+ }
+ }
+ break;
+ }
+ }
+ if (constants != null) {
+ if (constants.size() != allowedValues.length) {
+ constants = null;
+ } else {
+ boolean flag = annotation.getValue(TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE;
+ if (flag) {
+ ensureUsingFlagStyle(constants);
+ }
+ }
+ }
+
+ for (int index = 0; index < allowedValues.length; index++) {
+ Object o = allowedValues[index];
+ if (o instanceof Number) {
+ Number number = (Number)o;
+ if (valueToIndex.containsKey(number)) {
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ Number repeatedValue = number;
+
+ Location location;
+ String message;
+ if (constants != null) {
+ Node constant = constants.get(index);
+ int prevIndex = valueToIndex.get(number);
+ Node prevConstant = constants.get(prevIndex);
+ message = String.format(
+ "Constants `%1$s` and `%2$s` specify the same exact "
+ + "value (%3$s); this is usually a cut & paste or "
+ + "merge error",
+ constant.toString(), prevConstant.toString(),
+ repeatedValue.toString());
+ location = mContext.getLocation(constant);
+ Location secondary = mContext.getLocation(prevConstant);
+ secondary.setMessage("Previous same value");
+ location.setSecondary(secondary);
+ } else {
+ message = String.format(
+ "More than one constant specifies the same exact "
+ + "value (%1$s); this is usually a cut & paste or"
+ + "merge error",
+ repeatedValue.toString());
+ location = mContext.getLocation(node);
+ }
+ Node scope = getAnnotationScope(node);
+ mContext.report(UNIQUE, scope, location, message);
+ break;
+ }
+ valueToIndex.put(number, index);
+ }
+ }
+ }
+ }
+
+ @NonNull
+ private static List<VariableDefinitionEntry> findDeclarations(
+ @Nullable ClassDeclaration cls,
+ @NonNull List<VariableReference> references) {
+ if (cls == null) {
+ return Collections.emptyList();
+ }
+ Map<String, VariableReference> referenceMap = Maps.newHashMap();
+ for (VariableReference reference : references) {
+ String name = reference.astIdentifier().astValue();
+ referenceMap.put(name, reference);
+ }
+ List<VariableDefinitionEntry> declarations = Lists.newArrayList();
+ for (TypeMember member : cls.astBody().astMembers()) {
+ if (member instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration)member;
+ VariableDefinitionEntry field = declaration.astDefinition().astVariables()
+ .first();
+ String name = field.astName().astValue();
+ if (referenceMap.containsKey(name)) {
+ // TODO: When the Lombok ECJ bridge properly handles resolving variable
+ // definitions into ECJ bindings this code should check that
+ // mContext.resolve(field) == mContext.resolve(referenceMap.get(name)) !
+ declarations.add(field);
+ }
+ }
+ }
+
+ return declarations;
+ }
+
+ private void ensureUsingFlagStyle(@NonNull List<Node> constants) {
+ if (constants.size() < 3) {
+ return;
+ }
+
+ List<VariableReference> references =
+ Lists.newArrayListWithExpectedSize(constants.size());
+ for (Node constant : constants) {
+ if (constant instanceof VariableReference) {
+ references.add((VariableReference) constant);
+ }
+ }
+ List<VariableDefinitionEntry> entries = findDeclarations(
+ findSurroundingClass(constants.get(0)), references);
+ for (VariableDefinitionEntry entry : entries) {
+ Expression declaration = entry.astInitializer();
+ if (declaration == null) {
+ continue;
+ }
+ if (declaration instanceof IntegralLiteral) {
+ IntegralLiteral literal = (IntegralLiteral) declaration;
+ // Allow -1, 0 and 1. You can write 1 as "1 << 0" but IntelliJ for
+ // example warns that that's a redundant shift.
+ long value = literal.astLongValue();
+ if (Math.abs(value) <= 1) {
+ continue;
+ }
+ // Only warn if we're setting a specific bit
+ if (Long.bitCount(value) != 1) {
+ continue;
+ }
+ int shift = Long.numberOfTrailingZeros(value);
+ String message = String.format(
+ "Consider declaring this constant using 1 << %1$d instead",
+ shift);
+ mContext.report(FLAG_STYLE, declaration, mContext.getLocation(declaration),
+ message);
+ }
+ }
+ }
+
+ private boolean checkId(Annotation node, String id) {
+ IssueRegistry registry = mContext.getDriver().getRegistry();
+ Issue issue = registry.getIssue(id);
+ // Special-case the ApiDetector issue, since it does both source file analysis
+ // only on field references, and class file analysis on the rest, so we allow
+ // annotations outside of methods only on fields
+ if (issue != null && !issue.getImplementation().getScope().contains(Scope.JAVA_FILE)
+ || issue == ApiDetector.UNSUPPORTED) {
+ // Ensure that this isn't a field
+ Node parent = node.getParent();
+ while (parent != null) {
+ if (parent instanceof MethodDeclaration
+ || parent instanceof ConstructorDeclaration
+ || parent instanceof Block) {
+ break;
+ } else if (parent instanceof TypeBody) { // It's a field
+ return true;
+ } else if (issue == ApiDetector.UNSUPPORTED
+ && parent instanceof VariableDefinition) {
+ VariableDefinition definition = (VariableDefinition) parent;
+ for (VariableDefinitionEntry entry : definition.astVariables()) {
+ Expression initializer = entry.astInitializer();
+ if (initializer instanceof Select) {
+ return true;
+ }
+ }
+ }
+ parent = parent.getParent();
+ if (parent == null) {
+ return true;
+ }
+ }
+
+ // This issue doesn't have AST access: annotations are not
+ // available for local variables or parameters
+ Node scope = getAnnotationScope(node);
+ mContext.report(INSIDE_METHOD, scope, mContext.getLocation(node), String.format(
+ "The `@SuppressLint` annotation cannot be used on a local " +
+ "variable with the lint check '%1$s': move out to the " +
+ "surrounding method", id));
+ return false;
+ }
+
+ return true;
+ }
+
+ @NonNull
+ private List<String> computeFieldNames(@NonNull Switch node, Iterable allowedValues) {
+ List<String> list = Lists.newArrayList();
+ for (Object o : allowedValues) {
+ if (o instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) o;
+ // Only include class name if necessary
+ String name = field.getName();
+ ClassDeclaration clz = findSurroundingClass(node);
+ if (clz != null) {
+ ResolvedNode resolved = mContext.resolve(clz);
+ ResolvedClass containingClass = field.getContainingClass();
+ if (containingClass != null && !containingClass.equals(resolved)
+ && resolved instanceof ResolvedClass) {
+ if (Objects.equal(containingClass.getPackage(),
+ ((ResolvedClass) resolved).getPackage())) {
+ name = containingClass.getSimpleName() + '.' + field.getName();
+ } else {
+ name = containingClass.getName() + '.' + field.getName();
+ }
+ }
+ }
+ list.add('`' + name + '`');
+ }
+ }
+ Collections.sort(list);
+ return list;
+ }
+ }
+
+ /**
+ * Given an error message produced by this lint detector for the {@link #SWITCH_TYPE_DEF} issue
+ * type, returns the list of missing enum cases. <p> Intended for IDE quickfix implementations.
+ *
+ * @param errorMessage the error message associated with the error
+ * @param format the format of the error message
+ * @return the list of enum cases, or null if not recognized
+ */
+ @Nullable
+ public static List<String> getMissingCases(@NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+
+ String substring = findSubstring(errorMessage, " missing case ", null);
+ if (substring == null) {
+ substring = findSubstring(errorMessage, "expected one of: ", null);
+ }
+ if (substring != null) {
+ return Splitter.on(",").trimResults().splitToList(substring);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the node to use as the scope for the given annotation node.
+ * You can't annotate an annotation itself (with {@code @SuppressLint}), but
+ * you should be able to place an annotation next to it, as a sibling, to only
+ * suppress the error on this annotated element, not the whole surrounding class.
+ */
+ @NonNull
+ private static Node getAnnotationScope(@NonNull Annotation node) {
+ Node scope = getParentOfType(node,
+ AnnotationDeclaration.class, true);
+ if (scope == null) {
+ scope = node;
+ }
+ return scope;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java
new file mode 100644
index 0000000..8b08290
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Api.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+
+import com.android.annotations.NonNull;
+
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Main entry point for API description.
+ *
+ * To create the {@link Api}, use {@link #parseApi(File)}
+ *
+ */
+public class Api {
+
+ /**
+ * Parses simplified API file.
+ * @param apiFile the file to read
+ * @return a new ApiInfo
+ */
+ public static Api parseApi(File apiFile) {
+ InputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(apiFile);
+ SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+ SAXParser parser = parserFactory.newSAXParser();
+ ApiParser apiParser = new ApiParser();
+ parser.parse(inputStream, apiParser);
+ inputStream.close();
+
+ // Also read in API (unless regenerating the map for newer libraries)
+ //noinspection PointlessBooleanExpression
+ if (!ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+ inputStream = Api.class.getResourceAsStream("api-versions-support-library.xml");
+ if (inputStream != null) {
+ parser.parse(inputStream, apiParser);
+ }
+ }
+
+ return new Api(apiParser.getClasses(), apiParser.getPackages());
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } catch (SAXException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private final Map<String, ApiClass> mClasses;
+ private final Map<String, ApiPackage> mPackages;
+
+ private Api(
+ @NonNull Map<String, ApiClass> classes,
+ @NonNull Map<String, ApiPackage> packages) {
+ mClasses = new HashMap<String, ApiClass>(classes);
+ mPackages = new HashMap<String, ApiPackage>(packages);
+ }
+
+ ApiClass getClass(String fqcn) {
+ return mClasses.get(fqcn);
+ }
+
+ Map<String, ApiClass> getClasses() {
+ return Collections.unmodifiableMap(mClasses);
+ }
+
+ Map<String, ApiPackage> getPackages() {
+ return Collections.unmodifiableMap(mPackages);
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
new file mode 100644
index 0000000..64478f2
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a class and its methods/fields.
+ *
+ * {@link #getSince()} gives the API level it was introduced.
+ *
+ * {@link #getMethod} returns when the method was introduced.
+ * {@link #getField} returns when the field was introduced.
+ */
+public class ApiClass implements Comparable<ApiClass> {
+ private final String mName;
+ private final int mSince;
+ private int mDeprecatedIn;
+
+ private final List<Pair<String, Integer>> mSuperClasses = Lists.newArrayList();
+ private final List<Pair<String, Integer>> mInterfaces = Lists.newArrayList();
+
+ private final Map<String, Integer> mFields = new HashMap<String, Integer>();
+ private final Map<String, Integer> mMethods = new HashMap<String, Integer>();
+ private final Map<String, Integer> mDeprecatedMembersIn = new HashMap<String, Integer>();
+
+ // Persistence data: Used when writing out binary data in ApiLookup
+ List<String> members;
+ int index; // class number, e.g. entry in index where the pointer can be found
+ int indexOffset; // offset of the class entry
+ int memberOffsetBegin; // offset of the first member entry in the class
+ int memberOffsetEnd; // offset after the last member entry in the class
+ int memberIndexStart; // entry in index for first member
+ int memberIndexLength; // number of entries
+
+ ApiClass(String name, int since, int deprecatedIn) {
+ mName = name;
+ mSince = since;
+ mDeprecatedIn = deprecatedIn;
+ }
+
+ /**
+ * Returns the name of the class.
+ * @return the name of the class
+ */
+ String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns when the class was introduced.
+ * @return the api level the class was introduced.
+ */
+ int getSince() {
+ return mSince;
+ }
+
+ /**
+ * Returns the API level a method was deprecated in, or 0 if the method is not deprecated
+ *
+ * @return the API level a method was deprecated in, or 0 if the method is not deprecated
+ */
+ int getDeprecatedIn() {
+ return mDeprecatedIn;
+ }
+
+ /**
+ * Returns when a field was added, or Integer.MAX_VALUE if it doesn't exist.
+ * @param name the name of the field.
+ * @param info the corresponding info
+ */
+ int getField(String name, Api info) {
+ // The field can come from this class or from a super class or an interface
+ // The value can never be lower than this introduction of this class.
+ // When looking at super classes and interfaces, it can never be lower than when the
+ // super class or interface was added as a super class or interface to this class.
+ // Look at all the values and take the lowest.
+ // For instance:
+ // This class A is introduced in 5 with super class B.
+ // In 10, the interface C was added.
+ // Looking for SOME_FIELD we get the following:
+ // Present in A in API 15
+ // Present in B in API 11
+ // Present in C in API 7.
+ // The answer is 10, which is when C became an interface
+ int min = Integer.MAX_VALUE;
+ Integer i = mFields.get(name);
+ if (i != null) {
+ min = i;
+ }
+
+ // now look at the super classes
+ for (Pair<String, Integer> superClassPair : mSuperClasses) {
+ ApiClass superClass = info.getClass(superClassPair.getFirst());
+ if (superClass != null) {
+ i = superClass.getField(name, info);
+ int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+ if (tmp < min) {
+ min = tmp;
+ }
+ }
+ }
+
+ // now look at the interfaces
+ for (Pair<String, Integer> superClassPair : mInterfaces) {
+ ApiClass superClass = info.getClass(superClassPair.getFirst());
+ if (superClass != null) {
+ i = superClass.getField(name, info);
+ int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+ if (tmp < min) {
+ min = tmp;
+ }
+ }
+ }
+
+ return min;
+ }
+
+ /**
+ * Returns when a field was deprecated, or 0 if it's not deprecated
+ *
+ * @param name the name of the field.
+ * @param info the corresponding info
+ */
+ int getMemberDeprecatedIn(String name, Api info) {
+ int deprecatedIn = findMemberDeprecatedIn(name, info);
+ return deprecatedIn < Integer.MAX_VALUE ? deprecatedIn : 0;
+ }
+
+ private int findMemberDeprecatedIn(String name, Api info) {
+ // This follows the same logic as getField/getMethod.
+ // However, it also incorporates deprecation versions from the class.
+ int min = Integer.MAX_VALUE;
+ Integer i = mDeprecatedMembersIn.get(name);
+ if (i != null) {
+ min = i;
+ }
+
+ // now look at the super classes
+ for (Pair<String, Integer> superClassPair : mSuperClasses) {
+ ApiClass superClass = info.getClass(superClassPair.getFirst());
+ if (superClass != null) {
+ i = superClass.findMemberDeprecatedIn(name, info);
+ int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+ if (tmp < min) {
+ min = tmp;
+ }
+ }
+ }
+
+ // now look at the interfaces
+ for (Pair<String, Integer> superClassPair : mInterfaces) {
+ ApiClass superClass = info.getClass(superClassPair.getFirst());
+ if (superClass != null) {
+ i = superClass.findMemberDeprecatedIn(name, info);
+ int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+ if (tmp < min) {
+ min = tmp;
+ }
+ }
+ }
+
+ return min;
+ }
+
+ /**
+ * Returns when a method was added, or Integer.MAX_VALUE if it doesn't exist.
+ * This goes through the super class to find method only present there.
+ * @param methodSignature the method signature
+ */
+ int getMethod(String methodSignature, Api info) {
+ // The method can come from this class or from a super class.
+ // The value can never be lower than this introduction of this class.
+ // When looking at super classes, it can never be lower than when the super class became
+ // a super class of this class.
+ // Look at all the values and take the lowest.
+ // For instance:
+ // This class A is introduced in 5 with super class B.
+ // In 10, the super class changes to C.
+ // Looking for foo() we get the following:
+ // Present in A in API 15
+ // Present in B in API 11
+ // Present in C in API 7.
+ // The answer is 10, which is when C became the super class.
+ int min = Integer.MAX_VALUE;
+ Integer i = mMethods.get(methodSignature);
+ if (i != null) {
+ min = i;
+
+ // Constructors aren't inherited
+ if (methodSignature.startsWith(CONSTRUCTOR_NAME)) {
+ return i;
+ }
+ }
+
+ // now look at the super classes
+ for (Pair<String, Integer> superClassPair : mSuperClasses) {
+ ApiClass superClass = info.getClass(superClassPair.getFirst());
+ if (superClass != null) {
+ i = superClass.getMethod(methodSignature, info);
+ int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
+ if (tmp < min) {
+ min = tmp;
+ }
+ }
+ }
+
+ // now look at the interfaces classes
+ for (Pair<String, Integer> interfacePair : mInterfaces) {
+ ApiClass superClass = info.getClass(interfacePair.getFirst());
+ if (superClass != null) {
+ i = superClass.getMethod(methodSignature, info);
+ int tmp = interfacePair.getSecond() > i ? interfacePair.getSecond() : i;
+ if (tmp < min) {
+ min = tmp;
+ }
+ }
+ }
+
+ return min;
+ }
+
+ void addField(String name, int since, int deprecatedIn) {
+ Integer i = mFields.get(name);
+ assert i == null;
+ mFields.put(name, since);
+ if (deprecatedIn > 0) {
+ mDeprecatedMembersIn.put(name, deprecatedIn);
+ }
+ }
+
+ void addMethod(String name, int since, int deprecatedIn) {
+ // Strip off the method type at the end to ensure that the code which
+ // produces inherited methods doesn't get confused and end up multiple entries.
+ // For example, java/nio/Buffer has the method "array()Ljava/lang/Object;",
+ // and the subclass java/nio/ByteBuffer has the method "array()[B". We want
+ // the lookup on mMethods to associate the ByteBuffer array method to be
+ // considered overriding the Buffer method.
+ int index = name.indexOf(')');
+ if (index != -1) {
+ name = name.substring(0, index + 1);
+ }
+
+ Integer i = mMethods.get(name);
+ assert i == null || i == since : i;
+ mMethods.put(name, since);
+ if (deprecatedIn > 0) {
+ mDeprecatedMembersIn.put(name, deprecatedIn);
+ }
+ }
+
+ void addSuperClass(String superClass, int since) {
+ addToArray(mSuperClasses, superClass, since);
+ }
+
+ void addInterface(String interfaceClass, int since) {
+ addToArray(mInterfaces, interfaceClass, since);
+ }
+
+ static void addToArray(List<Pair<String, Integer>> list, String name, int value) {
+ // check if we already have that name (at a lower level)
+ for (Pair<String, Integer> pair : list) {
+ if (name.equals(pair.getFirst())) {
+ assert false;
+ return;
+ }
+ }
+
+ list.add(Pair.of(name, value));
+
+ }
+
+ @Nullable
+ public String getPackage() {
+ int index = mName.lastIndexOf('/');
+ if (index != -1) {
+ return mName.substring(0, index);
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public String getSimpleName() {
+ int index = mName.lastIndexOf('/');
+ if (index != -1) {
+ return mName.substring(index + 1);
+ }
+
+ return mName;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+
+ /**
+ * Returns the set of all methods, including inherited
+ * ones.
+ *
+ * @param info the api to look up super classes from
+ * @return a set containing all the members fields
+ */
+ Set<String> getAllMethods(Api info) {
+ Set<String> members = new HashSet<String>(100);
+ addAllMethods(info, members, true /*includeConstructors*/);
+
+ return members;
+ }
+
+ List<Pair<String, Integer>> getInterfaces() {
+ return mInterfaces;
+ }
+
+ List<Pair<String, Integer>> getSuperClasses() {
+ return mSuperClasses;
+ }
+
+ private void addAllMethods(Api info, Set<String> set, boolean includeConstructors) {
+ if (!includeConstructors) {
+ for (String method : mMethods.keySet()) {
+ if (!method.startsWith(CONSTRUCTOR_NAME)) {
+ set.add(method);
+ }
+ }
+ } else {
+ for (String method : mMethods.keySet()) {
+ set.add(method);
+ }
+ }
+
+ for (Pair<String, Integer> superClass : mSuperClasses) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ if (clz != null) {
+ clz.addAllMethods(info, set, false);
+ }
+ }
+
+ // Get methods from implemented interfaces as well;
+ for (Pair<String, Integer> superClass : mInterfaces) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ if (clz != null) {
+ clz.addAllMethods(info, set, false);
+ }
+ }
+ }
+
+ /**
+ * Returns the set of all fields, including inherited
+ * ones.
+ *
+ * @param info the api to look up super classes from
+ * @return a set containing all the fields
+ */
+ Set<String> getAllFields(Api info) {
+ Set<String> members = new HashSet<String>(100);
+ addAllFields(info, members);
+
+ return members;
+ }
+
+ private void addAllFields(Api info, Set<String> set) {
+ for (String field : mFields.keySet()) {
+ set.add(field);
+ }
+
+ for (Pair<String, Integer> superClass : mSuperClasses) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ clz.addAllFields(info, set);
+ }
+
+ // Get methods from implemented interfaces as well;
+ for (Pair<String, Integer> superClass : mInterfaces) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ clz.addAllFields(info, set);
+ }
+ }
+
+ @Override
+ public int compareTo(@NonNull ApiClass other) {
+ return mName.compareTo(other.mName);
+ }
+
+ /* This code can be used to scan through all the fields and look for fields
+ that have moved to a higher class:
+ Field android/view/MotionEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
+ Field android/provider/ContactsContract$CommonDataKinds$Organization#PHONETIC_NAME has api=5 but parent android/provider/ContactsContract$ContactNameColumns provides it as 11
+ Field android/widget/ListView#CHOICE_MODE_MULTIPLE has api=1 but parent android/widget/AbsListView provides it as 11
+ Field android/widget/ListView#CHOICE_MODE_NONE has api=1 but parent android/widget/AbsListView provides it as 11
+ Field android/widget/ListView#CHOICE_MODE_SINGLE has api=1 but parent android/widget/AbsListView provides it as 11
+ Field android/view/KeyEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
+ This is used for example in the ApiDetector to filter out warnings which result
+ when people follow Eclipse's advice to replace
+ ListView.CHOICE_MODE_MULTIPLE
+ references with
+ AbsListView.CHOICE_MODE_MULTIPLE
+ since the latter has API=11 and the former has API=1; since the constant is unchanged
+ between the two, and the literal is copied into the class, using the AbsListView
+ reference works.
+ public void checkFields(Api info) {
+ fieldLoop:
+ for (String field : mFields.keySet()) {
+ Integer since = getField(field, info);
+ if (since == null || since == Integer.MAX_VALUE) {
+ continue;
+ }
+
+ for (Pair<String, Integer> superClass : mSuperClasses) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ Integer superSince = clz.getField(field, info);
+ if (superSince == Integer.MAX_VALUE) {
+ continue;
+ }
+
+ if (superSince != null && superSince > since) {
+ String declaredIn = clz.findFieldDeclaration(info, field);
+ System.out.println("Field " + getName() + "#" + field + " has api="
+ + since + " but parent " + declaredIn + " provides it as "
+ + superSince);
+ continue fieldLoop;
+ }
+ }
+ }
+
+ // Get methods from implemented interfaces as well;
+ for (Pair<String, Integer> superClass : mInterfaces) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ Integer superSince = clz.getField(field, info);
+ if (superSince == Integer.MAX_VALUE) {
+ continue;
+ }
+ if (superSince != null && superSince > since) {
+ String declaredIn = clz.findFieldDeclaration(info, field);
+ System.out.println("Field " + getName() + "#" + field + " has api="
+ + since + " but parent " + declaredIn + " provides it as "
+ + superSince);
+ continue fieldLoop;
+ }
+ }
+ }
+ }
+ }
+
+ private String findFieldDeclaration(Api info, String name) {
+ if (mFields.containsKey(name)) {
+ return getName();
+ }
+ for (Pair<String, Integer> superClass : mSuperClasses) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ String declaredIn = clz.findFieldDeclaration(info, name);
+ if (declaredIn != null) {
+ return declaredIn;
+ }
+ }
+ }
+
+ // Get methods from implemented interfaces as well;
+ for (Pair<String, Integer> superClass : mInterfaces) {
+ ApiClass clz = info.getClass(superClass.getFirst());
+ assert clz != null : superClass.getSecond();
+ if (clz != null) {
+ String declaredIn = clz.findFieldDeclaration(info, name);
+ if (declaredIn != null) {
+ return declaredIn;
+ }
+ }
+ }
+
+ return null;
+ }
+ */
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
new file mode 100644
index 0000000..cf88a7d
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
@@ -0,0 +1,2442 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_FULL_BACKUP_CONTENT;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LABEL_FOR;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PADDING_START;
+import static com.android.SdkConstants.ATTR_PARENT;
+import static com.android.SdkConstants.ATTR_TARGET_API;
+import static com.android.SdkConstants.ATTR_TEXT_IS_SELECTABLE;
+import static com.android.SdkConstants.BUTTON;
+import static com.android.SdkConstants.CHECK_BOX;
+import static com.android.SdkConstants.CLASS_CONSTRUCTOR;
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.SdkConstants.PREFIX_ANDROID;
+import static com.android.SdkConstants.R_CLASS;
+import static com.android.SdkConstants.SWITCH;
+import static com.android.SdkConstants.TAG;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.TARGET_API;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VIEW_TAG;
+import static com.android.tools.lint.checks.RtlDetector.ATTR_SUPPORTS_RTL;
+import static com.android.tools.lint.detector.api.ClassContext.getFqcn;
+import static com.android.tools.lint.detector.api.ClassContext.getInternalName;
+import static com.android.tools.lint.detector.api.LintUtils.getNextInstruction;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.EOL_NEAREST;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
+import static com.android.tools.lint.detector.api.Location.SearchDirection.NEAREST;
+import static com.android.utils.SdkUtils.getResourceFieldName;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.repository.Revision;
+import com.android.repository.api.LocalPackage;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Location.SearchHints;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.IntInsnNode;
+import org.objectweb.asm.tree.JumpInsnNode;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.LocalVariableNode;
+import org.objectweb.asm.tree.LookupSwitchInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import lombok.ast.Annotation;
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
+import lombok.ast.AnnotationValue;
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.Case;
+import lombok.ast.Cast;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.If;
+import lombok.ast.ImportDeclaration;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Modifiers;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+import lombok.ast.SuperConstructorInvocation;
+import lombok.ast.Switch;
+import lombok.ast.Try;
+import lombok.ast.TypeDeclaration;
+import lombok.ast.TypeReference;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Looks for usages of APIs that are not supported in all the versions targeted
+ * by this application (according to its minimum API requirement in the manifest).
+ */
+public class ApiDetector extends ResourceXmlDetector
+ implements Detector.ClassScanner, Detector.JavaScanner {
+
+ /**
+ * Whether we flag variable, field, parameter and return type declarations of a type
+ * not yet available. It appears Dalvik is very forgiving and doesn't try to preload
+ * classes until actually needed, so there is no need to flag these, and in fact,
+ * patterns used for supporting new and old versions sometimes declares these methods
+ * and only conditionally end up actually accessing methods and fields, so only check
+ * method and field accesses.
+ */
+ private static final boolean CHECK_DECLARATIONS = false;
+
+ private static final boolean AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null; //$NON-NLS-1$
+
+ /** Accessing an unsupported API */
+ @SuppressWarnings("unchecked")
+ public static final Issue UNSUPPORTED = Issue.create(
+ "NewApi", //$NON-NLS-1$
+ "Calling new methods on older versions",
+
+ "This check scans through all the Android API calls in the application and " +
+ "warns about any calls that are not available on *all* versions targeted " +
+ "by this application (according to its minimum SDK attribute in the manifest).\n" +
+ "\n" +
+ "If you really want to use this API and don't need to support older devices just " +
+ "set the `minSdkVersion` in your `build.gradle` or `AndroidManifest.xml` files.\n" +
+ "\n" +
+ "If your code is *deliberately* accessing newer APIs, and you have ensured " +
+ "(e.g. with conditional execution) that this code will only ever be called on a " +
+ "supported platform, then you can annotate your class or method with the " +
+ "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
+ "`@TargetApi(11)`, such that this check considers 11 rather than your manifest " +
+ "file's minimum SDK as the required API level.\n" +
+ "\n" +
+ "If you are deliberately setting `android:` attributes in style definitions, " +
+ "make sure you place this in a `values-vNN` folder in order to avoid running " +
+ "into runtime conflicts on certain devices where manufacturers have added " +
+ "custom attributes whose ids conflict with the new ones on later platforms.\n" +
+ "\n" +
+ "Similarly, you can use tools:targetApi=\"11\" in an XML file to indicate that " +
+ "the element will only be inflated in an adequate context.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ new Implementation(
+ ApiDetector.class,
+ EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST),
+ Scope.RESOURCE_FILE_SCOPE,
+ Scope.CLASS_FILE_SCOPE,
+ Scope.MANIFEST_SCOPE));
+
+ /** Accessing an inlined API on older platforms */
+ public static final Issue INLINED = Issue.create(
+ "InlinedApi", //$NON-NLS-1$
+ "Using inlined constants on older versions",
+
+ "This check scans through all the Android API field references in the application " +
+ "and flags certain constants, such as static final integers and Strings, " +
+ "which were introduced in later versions. These will actually be copied " +
+ "into the class files rather than being referenced, which means that " +
+ "the value is available even when running on older devices. In some " +
+ "cases that's fine, and in other cases it can result in a runtime " +
+ "crash or incorrect behavior. It depends on the context, so consider " +
+ "the code carefully and device whether it's safe and can be suppressed " +
+ "or whether the code needs tbe guarded.\n" +
+ "\n" +
+ "If you really want to use this API and don't need to support older devices just " +
+ "set the `minSdkVersion` in your `build.gradle` or `AndroidManifest.xml` files." +
+ "\n" +
+ "If your code is *deliberately* accessing newer APIs, and you have ensured " +
+ "(e.g. with conditional execution) that this code will only ever be called on a " +
+ "supported platform, then you can annotate your class or method with the " +
+ "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
+ "`@TargetApi(11)`, such that this check considers 11 rather than your manifest " +
+ "file's minimum SDK as the required API level.\n",
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ ApiDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ /** Accessing an unsupported API */
+ public static final Issue OVERRIDE = Issue.create(
+ "Override", //$NON-NLS-1$
+ "Method conflicts with new inherited method",
+
+ "Suppose you are building against Android API 8, and you've subclassed Activity. " +
+ "In your subclass you add a new method called `isDestroyed`(). At some later point, " +
+ "a method of the same name and signature is added to Android. Your method will " +
+ "now override the Android method, and possibly break its contract. Your method " +
+ "is not calling `super.isDestroyed()`, since your compilation target doesn't " +
+ "know about the method.\n" +
+ "\n" +
+ "The above scenario is what this lint detector looks for. The above example is " +
+ "real, since `isDestroyed()` was added in API 17, but it will be true for *any* " +
+ "method you have added to a subclass of an Android class where your build target " +
+ "is lower than the version the method was introduced in.\n" +
+ "\n" +
+ "To fix this, either rename your method, or if you are really trying to augment " +
+ "the builtin method if available, switch to a higher build target where you can " +
+ "deliberately add `@Override` on your overriding method, and call `super` if " +
+ "appropriate etc.\n",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ new Implementation(
+ ApiDetector.class,
+ Scope.CLASS_FILE_SCOPE));
+
+ /** Accessing an inlined API on older platforms */
+ public static final Issue UNUSED = Issue.create(
+ "UnusedAttribute", //$NON-NLS-1$
+ "Attribute unused on older versions",
+
+ "This check finds attributes set in XML files that were introduced in a version " +
+ "newer than the oldest version targeted by your application (with the " +
+ "`minSdkVersion` attribute).\n" +
+ "\n" +
+ "This is not an error; the application will simply ignore the attribute. However, " +
+ "if the attribute is important to the appearance of functionality of your " +
+ "application, you should consider finding an alternative way to achieve the " +
+ "same result with only available attributes, and then you can optionally create " +
+ "a copy of the layout in a layout-vNN folder which will be used on API NN or " +
+ "higher where you can take advantage of the newer attribute.\n" +
+ "\n" +
+ "Note: This check does not only apply to attributes. For example, some tags can be " +
+ "unused too, such as the new `<tag>` element in layouts introduced in API 21.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ ApiDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ private static final String TARGET_API_VMSIG = '/' + TARGET_API + ';';
+ private static final String SWITCH_TABLE_PREFIX = "$SWITCH_TABLE$"; //$NON-NLS-1$
+ private static final String ORDINAL_METHOD = "ordinal"; //$NON-NLS-1$
+ public static final String ENUM_SWITCH_PREFIX = "$SwitchMap$"; //$NON-NLS-1$
+
+ private static final String TAG_RIPPLE = "ripple";
+ private static final String TAG_VECTOR = "vector";
+ private static final String TAG_ANIMATED_VECTOR = "animated-vector";
+ private static final String TAG_ANIMATED_SELECTOR = "animated-selector";
+
+ private static final String SDK_INT = "SDK_INT";
+ private static final String ANDROID_OS_BUILD_VERSION = "android/os/Build$VERSION";
+
+ protected ApiLookup mApiDatabase;
+ private boolean mWarnedMissingDb;
+ private int mMinApi = -1;
+ private Map<String, List<Pair<String, Location>>> mPendingFields;
+
+ /** Constructs a new API check */
+ public ApiDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.SLOW;
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ if (mApiDatabase == null) {
+ mApiDatabase = ApiLookup.get(context.getClient());
+ // We can't look up the minimum API required by the project here:
+ // The manifest file hasn't been processed yet in the -before- project hook.
+ // For now it's initialized lazily in getMinSdk(Context), but the
+ // lint infrastructure should be fixed to parse manifest file up front.
+
+ if (mApiDatabase == null && !mWarnedMissingDb) {
+ mWarnedMissingDb = true;
+ context.report(IssueRegistry.LINT_ERROR, Location.create(context.file),
+ "Can't find API database; API check not performed");
+ } else {
+ // See if you don't have at least version 23.0.1 of platform tools installed
+ AndroidSdkHandler sdk = context.getClient().getSdk();
+ if (sdk == null) {
+ return;
+ }
+ LocalPackage pkgInfo = sdk.getLocalPackage(SdkConstants.FD_PLATFORM_TOOLS,
+ context.getClient().getRepositoryLogger());
+ if (pkgInfo == null) {
+ return;
+ }
+ Revision revision = pkgInfo.getVersion();
+
+ // The platform tools must be at at least the same revision
+ // as the compileSdkVersion!
+ // And as a special case, for 23, they must be at 23.0.1
+ // because 23.0.0 accidentally shipped without Android M APIs.
+ int compileSdkVersion = context.getProject().getBuildSdk();
+ if (compileSdkVersion == 23) {
+ if (revision.getMajor() > 23 || revision.getMajor() == 23
+ && (revision.getMinor() > 0 || revision.getMicro() > 0)) {
+ return;
+ }
+ } else if (compileSdkVersion <= revision.getMajor()) {
+ return;
+ }
+
+ // Pick a location: when incrementally linting in the IDE, tie
+ // it to the current file
+ List<File> currentFiles = context.getProject().getSubset();
+ Location location;
+ if (currentFiles != null && currentFiles.size() == 1) {
+ File file = currentFiles.get(0);
+ String contents = context.getClient().readFile(file);
+ int firstLineEnd = contents.indexOf('\n');
+ if (firstLineEnd == -1) {
+ firstLineEnd = contents.length();
+ }
+ location = Location.create(file,
+ new DefaultPosition(0, 0, 0), new
+ DefaultPosition(0, firstLineEnd, firstLineEnd));
+ } else {
+ location = Location.create(context.file);
+ }
+ context.report(UNSUPPORTED,
+ location,
+ String.format("The SDK platform-tools version (%1$s) is too old "
+ + " to check APIs compiled with API %2$d; please update",
+ revision.toShortString(),
+ compileSdkVersion));
+ }
+ }
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return true;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return ALL;
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return ALL;
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ if (mApiDatabase == null) {
+ return;
+ }
+
+ int attributeApiLevel = -1;
+ if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ String name = attribute.getLocalName();
+ if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) &&
+ !(name.equals(ATTR_ID)))) {
+ String owner = "android/R$attr"; //$NON-NLS-1$
+ attributeApiLevel = mApiDatabase.getFieldVersion(owner, name);
+ int minSdk = getMinSdk(context);
+ if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion()
+ && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement())
+ && !isBenignUnusedAttribute(name)
+ && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) {
+ if (RtlDetector.isRtlAttributeName(name) || ATTR_SUPPORTS_RTL.equals(name)) {
+ // No need to warn for example that
+ // "layout_alignParentEnd will only be used in API level 17 and higher"
+ // since we have a dedicated RTL lint rule dealing with those attributes
+
+ // However, paddingStart in particular is known to cause crashes
+ // when used on TextViews (and subclasses of TextViews), on some
+ // devices, because vendor specific attributes conflict with the
+ // later-added framework resources, and these are apparently read
+ // by the text views.
+ //
+ // However, as of build tools 23.0.1 aapt works around this by packaging
+ // the resources differently.
+
+ BuildToolInfo buildToolInfo = context.getProject().getBuildTools();
+ Revision buildTools = buildToolInfo != null
+ ? buildToolInfo.getRevision() : null;
+ boolean isOldBuildTools = buildTools != null &&
+ (buildTools.getMajor() < 23 || buildTools.getMajor() == 23
+ && buildTools.getMinor() == 0 && buildTools.getMicro() == 0);
+ if (name.equals(ATTR_PADDING_START) &&
+ (buildTools == null || isOldBuildTools) &&
+ viewMayExtendTextView(attribute.getOwnerElement())) {
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "Attribute `%1$s` referenced here can result in a crash on "
+ + "some specific devices older than API %2$d "
+ + "(current min is %3$d)",
+ attribute.getLocalName(), attributeApiLevel, minSdk);
+ //noinspection VariableNotUsedInsideIf
+ if (buildTools != null) {
+ message = String.format("Upgrade `buildToolsVersion` from "
+ + "`%1$s` to at least `23.0.1`; if not, ",
+ buildTools.toShortString())
+ + Character.toLowerCase(message.charAt(0))
+ + message.substring(1);
+ }
+ context.report(UNSUPPORTED, attribute, location, message);
+ }
+ } else {
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "Attribute `%1$s` is only used in API level %2$d and higher "
+ + "(current min is %3$d)",
+ attribute.getLocalName(), attributeApiLevel, minSdk);
+ context.report(UNUSED, attribute, location, message);
+ }
+ }
+ }
+
+ // Special case:
+ // the dividers attribute is present in API 1, but it won't be read on older
+ // versions, so don't flag the common pattern
+ // android:divider="?android:attr/dividerHorizontal"
+ // since this will work just fine. See issue 67440 for more.
+ if (name.equals("divider")) {
+ return;
+ }
+ }
+
+ String value = attribute.getValue();
+ String owner = null;
+ String name = null;
+ String prefix;
+ if (value.startsWith(ANDROID_PREFIX)) {
+ prefix = ANDROID_PREFIX;
+ } else if (value.startsWith(ANDROID_THEME_PREFIX)) {
+ prefix = ANDROID_THEME_PREFIX;
+ } else if (value.startsWith(PREFIX_ANDROID) && ATTR_NAME.equals(attribute.getName())
+ && TAG_ITEM.equals(attribute.getOwnerElement().getTagName())
+ && attribute.getOwnerElement().getParentNode() != null
+ && TAG_STYLE.equals(attribute.getOwnerElement().getParentNode().getNodeName())) {
+ owner = "android/R$attr"; //$NON-NLS-1$
+ name = value.substring(PREFIX_ANDROID.length());
+ prefix = null;
+ } else if (value.startsWith(PREFIX_ANDROID) && ATTR_PARENT.equals(attribute.getName())
+ && TAG_STYLE.equals(attribute.getOwnerElement().getTagName())) {
+ owner = "android/R$style"; //$NON-NLS-1$
+ name = getResourceFieldName(value.substring(PREFIX_ANDROID.length()));
+ prefix = null;
+ } else {
+ return;
+ }
+
+ if (owner == null) {
+ // Convert @android:type/foo into android/R$type and "foo"
+ int index = value.indexOf('/', prefix.length());
+ if (index != -1) {
+ owner = "android/R$" //$NON-NLS-1$
+ + value.substring(prefix.length(), index);
+ name = getResourceFieldName(value.substring(index + 1));
+ } else if (value.startsWith(ANDROID_THEME_PREFIX)) {
+ owner = "android/R$attr"; //$NON-NLS-1$
+ name = value.substring(ANDROID_THEME_PREFIX.length());
+ } else {
+ return;
+ }
+ }
+ int api = mApiDatabase.getFieldVersion(owner, name);
+ int minSdk = getMinSdk(context);
+ if (api > minSdk && api > context.getFolderVersion()
+ && api > getLocalMinSdk(attribute.getOwnerElement())) {
+ // Don't complain about resource references in the tools namespace,
+ // such as for example "tools:layout="@android:layout/list_content",
+ // used only for designtime previews
+ if (TOOLS_URI.equals(attribute.getNamespaceURI())) {
+ return;
+ }
+
+ //noinspection StatementWithEmptyBody
+ if (attributeApiLevel >= api) {
+ // The attribute will only be *read* on platforms >= attributeApiLevel.
+ // If this isn't lower than the attribute reference's API level, it
+ // won't be a problem
+ } else if (attributeApiLevel > minSdk) {
+ String attributeName = attribute.getLocalName();
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "`%1$s` requires API level %2$d (current min is %3$d), but note "
+ + "that attribute `%4$s` is only used in API level %5$d "
+ + "and higher",
+ name, api, minSdk, attributeName, attributeApiLevel);
+ context.report(UNSUPPORTED, attribute, location, message);
+ } else {
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "`%1$s` requires API level %2$d (current min is %3$d)",
+ value, api, minSdk);
+ context.report(UNSUPPORTED, attribute, location, message);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the view tag is possibly a text view. It may not be certain,
+ * but will err on the side of caution (for example, any custom view is considered
+ * to be a potential text view.)
+ */
+ private static boolean viewMayExtendTextView(@NonNull Element element) {
+ String tag = element.getTagName();
+ if (tag.equals(VIEW_TAG)) {
+ tag = element.getAttribute(ATTR_CLASS);
+ if (tag == null || tag.isEmpty()) {
+ return false;
+ }
+ }
+
+ //noinspection SimplifiableIfStatement
+ if (tag.indexOf('.') != -1) {
+ // Custom views: not sure. Err on the side of caution.
+ return true;
+
+ }
+
+ return tag.contains("Text") // TextView, EditText, etc
+ || tag.contains(BUTTON) // Button, ToggleButton, etc
+ || tag.equals("DigitalClock")
+ || tag.equals("Chronometer")
+ || tag.equals(CHECK_BOX)
+ || tag.equals(SWITCH);
+ }
+
+ /**
+ * Returns true if this attribute is in a drawable document with one of the
+ * root tags that require API 21
+ */
+ private static boolean isAlreadyWarnedDrawableFile(@NonNull XmlContext context,
+ @NonNull Attr attribute, int attributeApiLevel) {
+ // Don't complain if it's in a drawable file where we've already
+ // flagged the root drawable type as being unsupported
+ if (context.getResourceFolderType() == ResourceFolderType.DRAWABLE
+ && attributeApiLevel == 21) {
+ String root = attribute.getOwnerDocument().getDocumentElement().getTagName();
+ if (TAG_RIPPLE.equals(root)
+ || TAG_VECTOR.equals(root)
+ || TAG_ANIMATED_VECTOR.equals(root)
+ || TAG_ANIMATED_SELECTOR.equals(root)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Is the given attribute a "benign" unused attribute, one we probably don't need to
+ * flag to the user as not applicable on all versions? These are typically attributes
+ * which add some nice platform behavior when available, but that are not critical
+ * and developers would not typically need to be aware of to try to implement workarounds
+ * on older platforms.
+ */
+ public static boolean isBenignUnusedAttribute(@NonNull String name) {
+ return ATTR_LABEL_FOR.equals(name)
+ || ATTR_TEXT_IS_SELECTABLE.equals(name)
+ || ATTR_FULL_BACKUP_CONTENT.equals(name);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (mApiDatabase == null) {
+ return;
+ }
+
+ String tag = element.getTagName();
+
+ ResourceFolderType folderType = context.getResourceFolderType();
+ if (folderType != ResourceFolderType.LAYOUT) {
+ if (folderType == ResourceFolderType.DRAWABLE) {
+ checkElement(context, element, TAG_VECTOR, 21, "1.4", UNSUPPORTED);
+ checkElement(context, element, TAG_RIPPLE, 21, null, UNSUPPORTED);
+ checkElement(context, element, TAG_ANIMATED_SELECTOR, 21, null, UNSUPPORTED);
+ checkElement(context, element, TAG_ANIMATED_VECTOR, 21, null, UNSUPPORTED);
+ }
+ if (element.getParentNode().getNodeType() != Node.ELEMENT_NODE) {
+ // Root node
+ return;
+ }
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node textNode = childNodes.item(i);
+ if (textNode.getNodeType() == Node.TEXT_NODE) {
+ String text = textNode.getNodeValue();
+ if (text.contains(ANDROID_PREFIX)) {
+ text = text.trim();
+ // Convert @android:type/foo into android/R$type and "foo"
+ int index = text.indexOf('/', ANDROID_PREFIX.length());
+ if (index != -1) {
+ String typeString = text.substring(ANDROID_PREFIX.length(), index);
+ if (ResourceType.getEnum(typeString) != null) {
+ String owner = "android/R$" + typeString;
+ String name = getResourceFieldName(text.substring(index + 1));
+ int api = mApiDatabase.getFieldVersion(owner, name);
+ int minSdk = getMinSdk(context);
+ if (api > minSdk && api > context.getFolderVersion()
+ && api > getLocalMinSdk(element)) {
+ Location location = context.getLocation(textNode);
+ String message = String.format(
+ "`%1$s` requires API level %2$d (current min is %3$d)",
+ text, api, minSdk);
+ context.report(UNSUPPORTED, element, location, message);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ if (VIEW_TAG.equals(tag)) {
+ tag = element.getAttribute(ATTR_CLASS);
+ if (tag == null || tag.isEmpty()) {
+ return;
+ }
+ } else {
+ // TODO: Complain if <tag> is used at the root level!
+ checkElement(context, element, TAG, 21, null, UNUSED);
+ }
+
+ // Check widgets to make sure they're available in this version of the SDK.
+ if (tag.indexOf('.') != -1) {
+ // Custom views aren't in the index
+ return;
+ }
+ String fqn = "android/widget/" + tag; //$NON-NLS-1$
+ if (tag.equals("TextureView")) { //$NON-NLS-1$
+ fqn = "android/view/TextureView"; //$NON-NLS-1$
+ }
+ // TODO: Consider other widgets outside of android.widget.*
+ int api = mApiDatabase.getClassVersion(fqn);
+ int minSdk = getMinSdk(context);
+ if (api > minSdk && api > context.getFolderVersion()
+ && api > getLocalMinSdk(element)) {
+ Location location = context.getLocation(element);
+ String message = String.format(
+ "View requires API level %1$d (current min is %2$d): `<%3$s>`",
+ api, minSdk, tag);
+ context.report(UNSUPPORTED, element, location, message);
+ }
+ }
+ }
+
+ /** Checks whether the given element is the given tag, and if so, whether it satisfied
+ * the minimum version that the given tag is supported in */
+ private void checkElement(@NonNull XmlContext context, @NonNull Element element,
+ @NonNull String tag, int api, @Nullable String gradleVersion, @NonNull Issue issue) {
+ if (tag.equals(element.getTagName())) {
+ int minSdk = getMinSdk(context);
+ if (api > minSdk
+ && api > context.getFolderVersion()
+ && api > getLocalMinSdk(element)
+ && !featureProvidedByGradle(context, gradleVersion)) {
+ Location location = context.getLocation(element);
+ String message;
+ if (issue == UNSUPPORTED) {
+ message = String.format(
+ "`<%1$s>` requires API level %2$d (current min is %3$d)", tag, api,
+ minSdk);
+ if (gradleVersion != null) {
+ message += String.format(
+ " or building with Android Gradle plugin %1$s or higher",
+ gradleVersion);
+ }
+ } else {
+ assert issue == UNUSED : issue;
+ message = String.format(
+ "`<%1$s>` is only used in API level %2$d and higher "
+ + "(current min is %3$d)", tag, api, minSdk);
+ }
+ context.report(issue, element, location, message);
+ }
+ }
+ }
+
+ protected int getMinSdk(Context context) {
+ if (mMinApi == -1) {
+ AndroidVersion minSdkVersion = context.getMainProject().getMinSdkVersion();
+ mMinApi = minSdkVersion.getFeatureLevel();
+ }
+
+ return mMinApi;
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @SuppressWarnings("rawtypes") // ASM API
+ @Override
+ public void checkClass(@NonNull final ClassContext context, @NonNull ClassNode classNode) {
+ if (mApiDatabase == null) {
+ return;
+ }
+
+ if (AOSP_BUILD && classNode.name.startsWith("android/support/")) { //$NON-NLS-1$
+ return;
+ }
+
+ // Requires util package (add prebuilts/tools/common/asm-tools/asm-debug-all-4.0.jar)
+ //classNode.accept(new TraceClassVisitor(new PrintWriter(System.out)));
+
+ int classMinSdk = getClassMinSdk(context, classNode);
+ if (classMinSdk == -1) {
+ classMinSdk = getMinSdk(context);
+ }
+
+ List methodList = classNode.methods;
+ if (methodList.isEmpty()) {
+ return;
+ }
+
+ boolean checkCalls = context.isEnabled(UNSUPPORTED)
+ || context.isEnabled(INLINED);
+ boolean checkMethods = context.isEnabled(OVERRIDE)
+ && context.getMainProject().getBuildSdk() >= 1;
+ String frameworkParent = null;
+ if (checkMethods) {
+ LintDriver driver = context.getDriver();
+ String owner = classNode.superName;
+ while (owner != null) {
+ // For virtual dispatch, walk up the inheritance chain checking
+ // each inherited method
+ if ((owner.startsWith("android/") //$NON-NLS-1$
+ && !owner.startsWith("android/support/")) //$NON-NLS-1$
+ || owner.startsWith("java/") //$NON-NLS-1$
+ || owner.startsWith("javax/")) { //$NON-NLS-1$
+ frameworkParent = owner;
+ break;
+ }
+ owner = driver.getSuperClass(owner);
+ }
+ if (frameworkParent == null) {
+ checkMethods = false;
+ }
+ }
+
+ if (checkCalls) { // Check implements/extends
+ if (classNode.superName != null) {
+ String signature = classNode.superName;
+ checkExtendsClass(context, classNode, classMinSdk, signature);
+ }
+ if (classNode.interfaces != null) {
+ @SuppressWarnings("unchecked") // ASM API
+ List<String> interfaceList = classNode.interfaces;
+ for (String signature : interfaceList) {
+ checkExtendsClass(context, classNode, classMinSdk, signature);
+ }
+ }
+ }
+
+ for (Object m : methodList) {
+ MethodNode method = (MethodNode) m;
+
+ int minSdk = getLocalMinSdk(method.invisibleAnnotations);
+ if (minSdk == -1) {
+ minSdk = classMinSdk;
+ }
+
+ InsnList nodes = method.instructions;
+
+ if (checkMethods && Character.isJavaIdentifierStart(method.name.charAt(0))) {
+ int buildSdk = context.getMainProject().getBuildSdk();
+ String name = method.name;
+ assert frameworkParent != null;
+ int api = mApiDatabase.getCallVersion(frameworkParent, name, method.desc);
+ if (api > buildSdk && buildSdk != -1) {
+ // TODO: Don't complain if it's annotated with @Override; that means
+ // somehow the build target isn't correct.
+ String fqcn;
+ String owner = classNode.name;
+ if (CONSTRUCTOR_NAME.equals(name)) {
+ fqcn = "new " + getFqcn(owner); //$NON-NLS-1$
+ } else {
+ fqcn = getFqcn(owner) + '#' + name;
+ }
+ String message = String.format(
+ "This method is not overriding anything with the current build " +
+ "target, but will in API level %1$d (current target is %2$d): `%3$s`",
+ api, buildSdk, fqcn);
+
+ Location location = context.getLocation(method, classNode);
+ context.report(OVERRIDE, method, null, location, message);
+ }
+ }
+
+ if (!checkCalls) {
+ continue;
+ }
+
+ List tryCatchBlocks = method.tryCatchBlocks;
+ if (!tryCatchBlocks.isEmpty()) {
+ List<String> checked = Lists.newArrayList();
+ for (Object o : tryCatchBlocks) {
+ TryCatchBlockNode tryCatchBlock = (TryCatchBlockNode) o;
+ String className = tryCatchBlock.type;
+ if (className == null || checked.contains(className)) {
+ continue;
+ }
+
+ int api = mApiDatabase.getClassVersion(className);
+ if (api > minSdk) {
+ // Find instruction node
+ LabelNode label = tryCatchBlock.handler;
+ String fqcn = getFqcn(className);
+ String message = String.format(
+ "Class requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+ report(context, message, label, method,
+ className.substring(className.lastIndexOf('/') + 1), null,
+ SearchHints.create(EOL_NEAREST).matchJavaSymbol());
+ }
+ }
+ }
+
+
+ if (CHECK_DECLARATIONS) {
+ // Check types in parameter list and types of local variables
+ List localVariables = method.localVariables;
+ if (localVariables != null) {
+ for (Object v : localVariables) {
+ LocalVariableNode var = (LocalVariableNode) v;
+ String desc = var.desc;
+ if (desc.charAt(0) == 'L') {
+ // "Lpackage/Class;" => "package/Bar"
+ String className = desc.substring(1, desc.length() - 1);
+ int api = mApiDatabase.getClassVersion(className);
+ if (api > minSdk) {
+ String fqcn = getFqcn(className);
+ String message = String.format(
+ "Class requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+ report(context, message, var.start, method,
+ className.substring(className.lastIndexOf('/') + 1), null,
+ SearchHints.create(NEAREST).matchJavaSymbol());
+ }
+ }
+ }
+ }
+
+ // Check return type
+ // The parameter types are already handled as local variables so we can skip
+ // right to the return type.
+ // Check types in parameter list
+ String signature = method.desc;
+ if (signature != null) {
+ int args = signature.indexOf(')');
+ if (args != -1 && signature.charAt(args + 1) == 'L') {
+ String type = signature.substring(args + 2, signature.length() - 1);
+ int api = mApiDatabase.getClassVersion(type);
+ if (api > minSdk) {
+ String fqcn = getFqcn(type);
+ String message = String.format(
+ "Class requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+ AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
+ report(context, message, first, method, method.name, null,
+ SearchHints.create(BACKWARD).matchJavaSymbol());
+ }
+ }
+ }
+ }
+
+ for (int i = 0, n = nodes.size(); i < n; i++) {
+ AbstractInsnNode instruction = nodes.get(i);
+ int type = instruction.getType();
+ if (type == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode node = (MethodInsnNode) instruction;
+ String name = node.name;
+ String owner = node.owner;
+ String desc = node.desc;
+
+ // No need to check methods in this local class; we know they
+ // won't be an API match
+ if (node.getOpcode() == Opcodes.INVOKEVIRTUAL
+ && owner.equals(classNode.name)) {
+ owner = classNode.superName;
+ }
+
+ boolean checkingSuperClass = false;
+ while (owner != null) {
+ int api = mApiDatabase.getCallVersion(owner, name, desc);
+ if (api > minSdk) {
+ if (method.name.startsWith(SWITCH_TABLE_PREFIX)) {
+ // We're in a compiler-generated method to generate an
+ // array indexed by enum ordinal values to enum values. The enum
+ // itself must be requiring a higher API number than is
+ // currently used, but the call site for the switch statement
+ // will also be referencing it, so no need to report these
+ // calls.
+ break;
+ }
+
+ if (!checkingSuperClass
+ && node.getOpcode() == Opcodes.INVOKEVIRTUAL
+ && methodDefinedLocally(classNode, name, desc)) {
+ break;
+ }
+
+ String fqcn;
+ if (CONSTRUCTOR_NAME.equals(name)) {
+ fqcn = "new " + getFqcn(owner); //$NON-NLS-1$
+ } else {
+ fqcn = getFqcn(owner) + '#' + name;
+ }
+ String message = String.format(
+ "Call requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+
+ if (name.equals(ORDINAL_METHOD)
+ && instruction.getNext() != null
+ && instruction.getNext().getNext() != null
+ && instruction.getNext().getOpcode() == Opcodes.IALOAD
+ && instruction.getNext().getNext().getOpcode()
+ == Opcodes.TABLESWITCH) {
+ message = String.format(
+ "Enum for switch requires API level %1$d " +
+ "(current min is %2$d): `%3$s`",
+ api, minSdk, getFqcn(owner));
+ }
+
+ // If you're simply calling super.X from method X, even if method X
+ // is in a higher API level than the minSdk, we're generally safe;
+ // that method should only be called by the framework on the right
+ // API levels. (There is a danger of somebody calling that method
+ // locally in other contexts, but this is hopefully unlikely.)
+ if (instruction.getOpcode() == Opcodes.INVOKESPECIAL &&
+ name.equals(method.name) && desc.equals(method.desc) &&
+ // We specifically exclude constructors from this check,
+ // because we do want to flag constructors requiring the
+ // new API level; it's highly likely that the constructor
+ // is called by local code so you should specifically
+ // investigate this as a developer
+ !name.equals(CONSTRUCTOR_NAME)) {
+ break;
+ }
+
+ if (isWithinSdkConditional(context, classNode, method, instruction,
+ api)) {
+ break;
+ }
+
+ if (api == 19
+ && owner.equals("java/lang/ReflectiveOperationException")
+ && !method.tryCatchBlocks.isEmpty()) {
+ boolean direct = false;
+ for (Object o : method.tryCatchBlocks) {
+ if (((TryCatchBlockNode)o).type.equals("java/lang/ReflectiveOperationException")) {
+ direct = true;
+ break;
+ }
+ }
+ if (!direct) {
+ message = String.format("Multi-catch with these reflection "
+ + "exceptions requires API level 19 (current min is"
+ + " %2$d) because they get compiled to the common but "
+ + "new super type `ReflectiveOperationException`. "
+ + "As a workaround either create individual catch "
+ + "statements, or catch `Exception`.",
+ api, minSdk);
+ }
+ }
+
+ report(context, message, node, method, name, null,
+ SearchHints.create(FORWARD).matchJavaSymbol());
+ break;
+ }
+
+ // For virtual dispatch, walk up the inheritance chain checking
+ // each inherited method
+ if (owner.startsWith("android/") //$NON-NLS-1$
+ || owner.startsWith("javax/")) { //$NON-NLS-1$
+ // The API map has already inlined all inherited methods
+ // so no need to keep checking up the chain
+ // -- unless it's the support library which is also in
+ // the android/ namespace:
+ if (owner.startsWith("android/support/") && api == -1) { //$NON-NLS-1$
+ owner = context.getDriver().getSuperClass(owner);
+ } else {
+ owner = null;
+ }
+ } else if (owner.startsWith("java/")) { //$NON-NLS-1$
+ if (owner.equals("java/text/SimpleDateFormat")) {
+ checkSimpleDateFormat(context, method, node, minSdk);
+ }
+ // Already inlined; see comment above
+ owner = null;
+ } else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
+ owner = context.getDriver().getSuperClass(owner);
+ } else if (node.getOpcode() == Opcodes.INVOKESTATIC && api == -1) {
+ // Inherit through static classes as well
+ owner = context.getDriver().getSuperClass(owner);
+ } else {
+ owner = null;
+ }
+
+ checkingSuperClass = true;
+ }
+ } else if (type == AbstractInsnNode.FIELD_INSN) {
+ FieldInsnNode node = (FieldInsnNode) instruction;
+ String name = node.name;
+ String owner = node.owner;
+ int api = mApiDatabase.getFieldVersion(owner, name);
+ if (api > minSdk) {
+ if (method.name.startsWith(SWITCH_TABLE_PREFIX)) {
+ checkSwitchBlock(context, classNode, node, method, name, owner,
+ api, minSdk);
+ continue;
+ }
+
+ if (isSkippedEnumSwitch(context, classNode, method, node, owner, api)) {
+ continue;
+ }
+
+ if (isWithinSdkConditional(context, classNode, method, instruction, api)) {
+ continue;
+ }
+
+ String fqcn = getFqcn(owner) + '#' + name;
+ if (mPendingFields != null) {
+ mPendingFields.remove(fqcn);
+ }
+ String message = String.format(
+ "Field requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+ report(context, message, node, method, name, null,
+ SearchHints.create(FORWARD).matchJavaSymbol());
+ }
+ } else if (type == AbstractInsnNode.LDC_INSN) {
+ LdcInsnNode node = (LdcInsnNode) instruction;
+ if (node.cst instanceof Type) {
+ Type t = (Type) node.cst;
+ String className = t.getInternalName();
+
+ int api = mApiDatabase.getClassVersion(className);
+ if (api > minSdk) {
+ String fqcn = getFqcn(className);
+ String message = String.format(
+ "Class requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+ report(context, message, node, method,
+ className.substring(className.lastIndexOf('/') + 1), null,
+ SearchHints.create(FORWARD).matchJavaSymbol());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void checkExtendsClass(ClassContext context, ClassNode classNode, int classMinSdk,
+ String signature) {
+ int api = mApiDatabase.getClassVersion(signature);
+ if (api > classMinSdk) {
+ String fqcn = getFqcn(signature);
+ String message = String.format(
+ "Class requires API level %1$d (current min is %2$d): `%3$s`",
+ api, classMinSdk, fqcn);
+
+ String name = signature.substring(signature.lastIndexOf('/') + 1);
+ name = name.substring(name.lastIndexOf('$') + 1);
+ SearchHints hints = SearchHints.create(BACKWARD).matchJavaSymbol();
+ int lineNumber = ClassContext.findLineNumber(classNode);
+ Location location = context.getLocationForLine(lineNumber, name, null,
+ hints);
+ context.report(UNSUPPORTED, location, message);
+ }
+ }
+
+ private static void checkSimpleDateFormat(ClassContext context, MethodNode method,
+ MethodInsnNode node, int minSdk) {
+ if (minSdk >= 9) {
+ // Already OK
+ return;
+ }
+ if (node.name.equals(CONSTRUCTOR_NAME) && !node.desc.equals("()V")) { //$NON-NLS-1$
+ // Check first argument
+ AbstractInsnNode prev = LintUtils.getPrevInstruction(node);
+ if (prev != null && !node.desc.equals("(Ljava/lang/String;)V")) { //$NON-NLS-1$
+ prev = LintUtils.getPrevInstruction(prev);
+ }
+ if (prev != null && prev.getOpcode() == Opcodes.LDC) {
+ LdcInsnNode ldc = (LdcInsnNode) prev;
+ Object cst = ldc.cst;
+ if (cst instanceof String) {
+ String pattern = (String) cst;
+ boolean isEscaped = false;
+ for (int i = 0; i < pattern.length(); i++) {
+ char c = pattern.charAt(i);
+ if (c == '\'') {
+ isEscaped = !isEscaped;
+ } else if (!isEscaped && (c == 'L' || c == 'c')) {
+ String message = String.format(
+ "The pattern character '%1$c' requires API level 9 (current " +
+ "min is %2$d) : \"`%3$s`\"", c, minSdk, pattern);
+ report(context, message, node, method, pattern, null,
+ SearchHints.create(FORWARD));
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("rawtypes") // ASM API
+ private static boolean methodDefinedLocally(ClassNode classNode, String name, String desc) {
+ List methodList = classNode.methods;
+ for (Object m : methodList) {
+ MethodNode method = (MethodNode) m;
+ if (name.equals(method.name) && desc.equals(method.desc)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings("rawtypes") // ASM API
+ private static void checkSwitchBlock(ClassContext context, ClassNode classNode,
+ FieldInsnNode field, MethodNode method, String name, String owner, int api,
+ int minSdk) {
+ // Switch statements on enums are tricky. The compiler will generate a method
+ // which returns an array of the enum constants, indexed by their ordinal() values.
+ // However, we only want to complain if the code is actually referencing one of
+ // the non-available enum fields.
+ //
+ // For the android.graphics.PorterDuff.Mode enum for example, the first few items
+ // in the array are populated like this:
+ //
+ // L0
+ // ALOAD 0
+ // GETSTATIC android/graphics/PorterDuff$Mode.ADD : Landroid/graphics/PorterDuff$Mode;
+ // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
+ // ICONST_1
+ // IASTORE
+ // L1
+ // GOTO L3
+ // L2
+ // FRAME FULL [[I] [java/lang/NoSuchFieldError]
+ // POP
+ // L3
+ // FRAME SAME
+ // ALOAD 0
+ // GETSTATIC android/graphics/PorterDuff$Mode.CLEAR : Landroid/graphics/PorterDuff$Mode;
+ // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
+ // ICONST_2
+ // IASTORE
+ // ...
+ // So if we for example find that the "ADD" field isn't accessible, since it requires
+ // API 11, we need to
+ // (1) First find out what its ordinal number is. We can look at the following
+ // instructions to discover this; it's the "ICONST_1" and "IASTORE" instructions.
+ // (After ICONST_5 it moves on to BIPUSH 6, BIPUSH 7, etc.)
+ // (2) Find the corresponding *usage* of this switch method. For the above enum,
+ // the switch ordinal lookup method will be called
+ // "$SWITCH_TABLE$android$graphics$PorterDuff$Mode" with desc "()[I".
+ // This means we will be looking for an invocation in some other method which looks
+ // like this:
+ // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I
+ // (obviously, it can be invoked more than once)
+ // Note that it can be used more than once in this class and all sites should be
+ // checked!
+ // (3) Look up the corresponding table switch, which should look something like this:
+ // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I
+ // ALOAD 0
+ // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
+ // IALOAD
+ // LOOKUPSWITCH
+ // 2: L1
+ // 11: L2
+ // default: L3
+ // Here we need to see if the LOOKUPSWITCH instruction is referencing our target
+ // case. Above we were looking for the "ADD" case which had ordinal 1. Since this
+ // isn't explicitly referenced, we can ignore this field reference.
+ AbstractInsnNode next = field.getNext();
+ if (next == null || next.getOpcode() != Opcodes.INVOKEVIRTUAL) {
+ return;
+ }
+ next = next.getNext();
+ if (next == null) {
+ return;
+ }
+ int ordinal;
+ switch (next.getOpcode()) {
+ case Opcodes.ICONST_0: ordinal = 0; break;
+ case Opcodes.ICONST_1: ordinal = 1; break;
+ case Opcodes.ICONST_2: ordinal = 2; break;
+ case Opcodes.ICONST_3: ordinal = 3; break;
+ case Opcodes.ICONST_4: ordinal = 4; break;
+ case Opcodes.ICONST_5: ordinal = 5; break;
+ case Opcodes.BIPUSH: {
+ IntInsnNode iin = (IntInsnNode) next;
+ ordinal = iin.operand;
+ break;
+ }
+ default:
+ return;
+ }
+
+ // Find usages of this call site
+ List methodList = classNode.methods;
+ for (Object m : methodList) {
+ InsnList nodes = ((MethodNode) m).instructions;
+ for (int i = 0, n = nodes.size(); i < n; i++) {
+ AbstractInsnNode instruction = nodes.get(i);
+ if (instruction.getOpcode() != Opcodes.INVOKESTATIC){
+ continue;
+ }
+ MethodInsnNode node = (MethodInsnNode) instruction;
+ if (node.name.equals(method.name)
+ && node.desc.equals(method.desc)
+ && node.owner.equals(classNode.name)) {
+ // Find lookup switch
+ AbstractInsnNode target = getNextInstruction(node);
+ while (target != null) {
+ if (target.getOpcode() == Opcodes.LOOKUPSWITCH) {
+ LookupSwitchInsnNode lookup = (LookupSwitchInsnNode) target;
+ @SuppressWarnings("unchecked") // ASM API
+ List<Integer> keys = lookup.keys;
+ if (keys != null && keys.contains(ordinal)) {
+ String fqcn = getFqcn(owner) + '#' + name;
+ String message = String.format(
+ "Enum value requires API level %1$d " +
+ "(current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+ report(context, message, lookup, (MethodNode) m, name, null,
+ SearchHints.create(FORWARD).matchJavaSymbol());
+
+ // Break out of the inner target search only; the switch
+ // statement could be used in other places in this class as
+ // well and we want to report all problematic usages.
+ break;
+ }
+ }
+ target = getNextInstruction(target);
+ }
+ }
+ }
+ }
+ }
+
+ private static boolean isEnumSwitchInitializer(ClassNode classNode) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List fieldList = classNode.fields;
+ for (Object f : fieldList) {
+ FieldNode field = (FieldNode) f;
+ if (field.name.startsWith(ENUM_SWITCH_PREFIX)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static MethodNode findEnumSwitchUsage(ClassNode classNode, String owner) {
+ String target = ENUM_SWITCH_PREFIX + owner.replace('/', '$');
+ @SuppressWarnings("rawtypes") // ASM API
+ List methodList = classNode.methods;
+ for (Object f : methodList) {
+ MethodNode method = (MethodNode) f;
+ InsnList nodes = method.instructions;
+ for (int i = 0, n = nodes.size(); i < n; i++) {
+ AbstractInsnNode instruction = nodes.get(i);
+ if (instruction.getOpcode() == Opcodes.GETSTATIC) {
+ FieldInsnNode field = (FieldInsnNode) instruction;
+ if (field.name.equals(target)) {
+ return method;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static boolean isSkippedEnumSwitch(ClassContext context, ClassNode classNode,
+ MethodNode method, FieldInsnNode node, String owner, int api) {
+ // Enum-style switches are handled in a different way: it generates
+ // an innerclass where the class initializer creates a mapping from
+ // the ordinals to the corresponding values.
+ // Here we need to check to see if the call site which *used* the
+ // table switch had a suppress node on it (or up that node's parent
+ // chain
+ AbstractInsnNode next = getNextInstruction(node);
+ if (next != null && next.getOpcode() == Opcodes.INVOKEVIRTUAL
+ && CLASS_CONSTRUCTOR.equals(method.name)
+ && ORDINAL_METHOD.equals(((MethodInsnNode) next).name)
+ && classNode.outerClass != null
+ && isEnumSwitchInitializer(classNode)) {
+ LintDriver driver = context.getDriver();
+ ClassNode outer = driver.getOuterClassNode(classNode);
+ if (outer != null) {
+ MethodNode switchUser = findEnumSwitchUsage(outer, owner);
+ if (switchUser != null) {
+ // Is the API check suppressed at the call site?
+ if (driver.isSuppressed(UNSUPPORTED, outer, switchUser,
+ null)) {
+ return true;
+ }
+ // Is there a @TargetAPI annotation on the method or
+ // class referencing this switch map class?
+ if (getLocalMinSdk(switchUser.invisibleAnnotations) >= api
+ || getLocalMinSdk(outer.invisibleAnnotations) >= api) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the {@code @TargetApi} level to use for the given {@code classNode};
+ * this will be the {@code @TargetApi} annotation on the class, or any outer
+ * methods (for anonymous inner classes) or outer classes (for inner classes)
+ * of the given class.
+ */
+ private static int getClassMinSdk(ClassContext context, ClassNode classNode) {
+ int classMinSdk = getLocalMinSdk(classNode.invisibleAnnotations);
+ if (classMinSdk != -1) {
+ return classMinSdk;
+ }
+
+ LintDriver driver = context.getDriver();
+ while (classNode != null) {
+ ClassNode prev = classNode;
+ classNode = driver.getOuterClassNode(classNode);
+ if (classNode != null) {
+ // TODO: Should this be "curr" instead?
+ if (prev.outerMethod != null) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List methods = classNode.methods;
+ for (Object m : methods) {
+ MethodNode method = (MethodNode) m;
+ if (method.name.equals(prev.outerMethod)
+ && method.desc.equals(prev.outerMethodDesc)) {
+ // Found the outer method for this anonymous class; check method
+ // annotations on it, then continue up the class hierarchy
+ int methodMinSdk = getLocalMinSdk(method.invisibleAnnotations);
+ if (methodMinSdk != -1) {
+ return methodMinSdk;
+ }
+
+ break;
+ }
+ }
+ }
+
+ classMinSdk = getLocalMinSdk(classNode.invisibleAnnotations);
+ if (classMinSdk != -1) {
+ return classMinSdk;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the minimum SDK to use according to the given annotation list, or
+ * -1 if no annotation was found.
+ *
+ * @param annotations a list of annotation nodes from ASM
+ * @return the API level to use for this node, or -1
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static int getLocalMinSdk(List annotations) {
+ if (annotations != null) {
+ for (AnnotationNode annotation : (List<AnnotationNode>)annotations) {
+ String desc = annotation.desc;
+ if (desc.endsWith(TARGET_API_VMSIG)) {
+ if (annotation.values != null) {
+ for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
+ String key = (String) annotation.values.get(i);
+ if (key.equals("value")) { //$NON-NLS-1$
+ Object value = annotation.values.get(i + 1);
+ if (value instanceof Integer) {
+ return (Integer) value;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the minimum SDK to use in the given element context, or -1 if no
+ * {@code tools:targetApi} attribute was found.
+ *
+ * @param element the element to look at, including parents
+ * @return the API level to use for this element, or -1
+ */
+ private static int getLocalMinSdk(@NonNull Element element) {
+ //noinspection ConstantConditions
+ while (element != null) {
+ String targetApi = element.getAttributeNS(TOOLS_URI, ATTR_TARGET_API);
+ if (targetApi != null && !targetApi.isEmpty()) {
+ if (Character.isDigit(targetApi.charAt(0))) {
+ try {
+ return Integer.parseInt(targetApi);
+ } catch (NumberFormatException e) {
+ break;
+ }
+ } else {
+ return SdkVersionInfo.getApiByBuildCode(targetApi, true);
+ }
+ }
+
+ Node parent = element.getParentNode();
+ if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
+ element = (Element) parent;
+ } else {
+ break;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Checks if the current project supports features added in {@code minGradleVersion} version of the
+ * Android gradle plugin.
+ *
+ * @param context Current context.
+ * @param minGradleVersion Version in which support for a given feature was added, or null if it's
+ * not supported at build time.
+ */
+ private static boolean featureProvidedByGradle(@NonNull XmlContext context,
+ @Nullable String minGradleVersion) {
+ if (minGradleVersion == null) {
+ return false;
+ }
+
+ AndroidProject gradleModel = context.getProject().getGradleProjectModel();
+ if (gradleModel != null) {
+ Revision gradleModelVersion =
+ Revision.parseRevision(gradleModel.getModelVersion());
+ if (gradleModelVersion.compareTo(
+ Revision.parseRevision(minGradleVersion),
+ Revision.PreviewComparison.IGNORE) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void report(final ClassContext context, String message, AbstractInsnNode node,
+ MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
+ int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
+
+ // If looking for a constructor, the string we'll see in the source is not the
+ // method name (<init>) but the class name
+ if (patternStart != null && patternStart.equals(CONSTRUCTOR_NAME)
+ && node instanceof MethodInsnNode) {
+ if (hints != null) {
+ hints = hints.matchConstructor();
+ }
+ patternStart = ((MethodInsnNode) node).owner;
+ }
+
+ if (patternStart != null) {
+ int index = patternStart.lastIndexOf('$');
+ if (index != -1) {
+ patternStart = patternStart.substring(index + 1);
+ }
+ index = patternStart.lastIndexOf('/');
+ if (index != -1) {
+ patternStart = patternStart.substring(index + 1);
+ }
+ }
+
+ Location location = context.getLocationForLine(lineNumber, patternStart, patternEnd,
+ hints);
+ context.report(UNSUPPORTED, method, node, location, message);
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mPendingFields != null) {
+ for (List<Pair<String, Location>> list : mPendingFields.values()) {
+ for (Pair<String, Location> pair : list) {
+ String message = pair.getFirst();
+ Location location = pair.getSecond();
+ context.report(INLINED, location, message);
+ }
+ }
+ }
+
+ super.afterCheckProject(context);
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ if (mApiDatabase == null) {
+ return new ForwardingAstVisitor() {
+ };
+ }
+ return new ApiVisitor(context);
+ }
+
+ @Nullable
+ @Override
+ public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
+ List<Class<? extends lombok.ast.Node>> types =
+ new ArrayList<Class<? extends lombok.ast.Node>>(10);
+ types.add(ImportDeclaration.class);
+ types.add(Select.class);
+ types.add(MethodDeclaration.class);
+ types.add(ConstructorDeclaration.class);
+ types.add(VariableDefinitionEntry.class);
+ types.add(VariableReference.class);
+ types.add(Try.class);
+ types.add(Cast.class);
+ types.add(BinaryExpression.class);
+ types.add(MethodInvocation.class);
+ return types;
+ }
+
+ /**
+ * Checks whether the given instruction is a benign usage of a constant defined in
+ * a later version of Android than the application's {@code minSdkVersion}.
+ *
+ * @param node the instruction to check
+ * @param name the name of the constant
+ * @param owner the field owner
+ * @return true if the given usage is safe on older versions than the introduction
+ * level of the constant
+ */
+ public static boolean isBenignConstantUsage(
+ @Nullable lombok.ast.Node node,
+ @NonNull String name,
+ @NonNull String owner) {
+ if (owner.equals("android/os/Build$VERSION_CODES")) { //$NON-NLS-1$
+ // These constants are required for compilation, not execution
+ // and valid code checks it even on older platforms
+ return true;
+ }
+ if (owner.equals("android/view/ViewGroup$LayoutParams") //$NON-NLS-1$
+ && name.equals("MATCH_PARENT")) { //$NON-NLS-1$
+ return true;
+ }
+ if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$
+ && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$
+ || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$
+ || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$
+ // android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1,
+ // but in API 11 it was moved up to the parent class AbsListView.
+ // Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11,
+ // but the constant is the same as the older version, so accept this without
+ // warning.
+ return true;
+ }
+
+ // Gravity#START and Gravity#END are okay; these were specifically written to
+ // be backwards compatible (by using the same lower bits for START as LEFT and
+ // for END as RIGHT)
+ if ("android/view/Gravity".equals(owner) //$NON-NLS-1$
+ && ("START".equals(name) || "END".equals(name))) { //$NON-NLS-1$ //$NON-NLS-2$
+ return true;
+ }
+
+ if (node == null) {
+ return false;
+ }
+
+ // It's okay to reference the constant as a case constant (since that
+ // code path won't be taken) or in a condition of an if statement
+ lombok.ast.Node curr = node.getParent();
+ while (curr != null) {
+ Class<? extends lombok.ast.Node> nodeType = curr.getClass();
+ if (nodeType == Case.class) {
+ Case caseStatement = (Case) curr;
+ Expression condition = caseStatement.astCondition();
+ return condition != null && isAncestor(condition, node);
+ } else if (nodeType == If.class) {
+ If ifStatement = (If) curr;
+ Expression condition = ifStatement.astCondition();
+ return condition != null && isAncestor(condition, node);
+ } else if (nodeType == InlineIfExpression.class) {
+ InlineIfExpression ifStatement = (InlineIfExpression) curr;
+ Expression condition = ifStatement.astCondition();
+ return condition != null && isAncestor(condition, node);
+ }
+ curr = curr.getParent();
+ }
+
+ return false;
+ }
+
+ private static boolean isAncestor(
+ @NonNull lombok.ast.Node ancestor,
+ @Nullable lombok.ast.Node node) {
+ while (node != null) {
+ if (node == ancestor) {
+ return true;
+ }
+ node = node.getParent();
+ }
+
+ return false;
+ }
+
+ private final class ApiVisitor extends ForwardingAstVisitor {
+ private JavaContext mContext;
+ private Map<String, String> mClassToImport = Maps.newHashMap();
+ private List<String> mStarImports;
+ private Set<String> mLocalVars;
+ private lombok.ast.Node mCurrentMethod;
+ private Set<String> mFields;
+ private List<String> mStaticStarImports;
+
+ private ApiVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitImportDeclaration(ImportDeclaration node) {
+ if (node.astStarImport()) {
+ // Similarly, if you're inheriting from a constants class, figure out
+ // how that works... :=(
+ String fqcn = node.asFullyQualifiedName();
+ int strip = fqcn.lastIndexOf('*');
+ if (strip != -1) {
+ strip = fqcn.lastIndexOf('.', strip);
+ if (strip != -1) {
+ String pkgName = getInternalName(fqcn.substring(0, strip));
+ if (ApiLookup.isRelevantOwner(pkgName)) {
+ if (node.astStaticImport()) {
+ if (mStaticStarImports == null) {
+ mStaticStarImports = Lists.newArrayList();
+ }
+ mStaticStarImports.add(pkgName);
+ } else {
+ if (mStarImports == null) {
+ mStarImports = Lists.newArrayList();
+ }
+ mStarImports.add(pkgName);
+ }
+ }
+ }
+ }
+ } else if (node.astStaticImport()) {
+ String fqcn = node.asFullyQualifiedName();
+ String fieldName = getInternalName(fqcn);
+ int index = fieldName.lastIndexOf('$');
+ if (index != -1) {
+ String owner = fieldName.substring(0, index);
+ String name = fieldName.substring(index + 1);
+ checkField(node, name, owner);
+ }
+ } else {
+ // Store in map -- if it's "one of ours"
+ // Use override detector's map for that purpose
+ String fqcn = node.asFullyQualifiedName();
+
+ int last = fqcn.lastIndexOf('.');
+ if (last != -1) {
+ String className = fqcn.substring(last + 1);
+ mClassToImport.put(className, fqcn);
+ }
+ }
+
+ return super.visitImportDeclaration(node);
+ }
+
+ @Override
+ public boolean visitSelect(Select node) {
+ boolean result = super.visitSelect(node);
+
+ if (node.getParent() instanceof Select) {
+ // We only want to look at the leaf expressions; e.g. if you have
+ // "foo.bar.baz" we only care about the select foo.bar.baz, not foo.bar
+ return result;
+ }
+
+ // See if this corresponds to a field reference. We assume it's a field if
+ // it's a select (x.y) and either the identifier y is capitalized (e.g.
+ // foo.VIEW_MASK) or if it's a member of an R class (R.id.foo).
+ String name = node.astIdentifier().astValue();
+ boolean isField = Character.isUpperCase(name.charAt(0));
+ if (!isField) {
+ // See if there's an R class
+ Select current = node;
+ //noinspection ConstantConditions
+ while (current != null) {
+ Expression operand = current.astOperand();
+ if (operand instanceof Select) {
+ current = (Select) operand;
+ if (R_CLASS.equals(current.astIdentifier().astValue())) {
+ isField = true;
+ break;
+ }
+ } else if (operand instanceof VariableReference) {
+ VariableReference reference = (VariableReference) operand;
+ if (R_CLASS.equals(reference.astIdentifier().astValue())) {
+ isField = true;
+ }
+ break;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (isField) {
+ Expression operand = node.astOperand();
+ if (operand.getClass() == Select.class) {
+ // Possibly a fully qualified name in place
+ String cls = operand.toString();
+
+ // See if it's an imported class with an inner class
+ // (e.g. Manifest.permission.FIELD)
+ if (Character.isUpperCase(cls.charAt(0))) {
+ int firstDot = cls.indexOf('.');
+ if (firstDot != -1) {
+ String base = cls.substring(0, firstDot);
+ String fqcn = mClassToImport.get(base);
+ if (fqcn != null) {
+ // Yes imported
+ String owner = getInternalName(fqcn + cls.substring(firstDot));
+ checkField(node, name, owner);
+ return result;
+ }
+
+ // Might be a star import: have to iterate and check here
+ if (mStarImports != null) {
+ for (String packagePrefix : mStarImports) {
+ String owner = getInternalName(packagePrefix + '/' + cls);
+ if (checkField(node, name, owner)) {
+ mClassToImport.put(name, owner);
+ return result;
+ }
+ }
+ }
+ }
+ }
+
+ // See if it's a fully qualified reference in place
+ String owner = getInternalName(cls);
+ checkField(node, name, owner);
+ return result;
+ } else if (operand.getClass() == VariableReference.class) {
+ String className = ((VariableReference) operand).astIdentifier().astValue();
+ // Not a FQCN that we care about: look in imports
+ String fqcn = mClassToImport.get(className);
+ if (fqcn != null) {
+ // Yes imported
+ String owner = getInternalName(fqcn);
+ checkField(node, name, owner);
+ return result;
+ }
+
+ if (Character.isUpperCase(className.charAt(0))) {
+ // Might be a star import: have to iterate and check here
+ if (mStarImports != null) {
+ for (String packagePrefix : mStarImports) {
+ String owner = getInternalName(packagePrefix) + '/' + className;
+ if (checkField(node, name, owner)) {
+ mClassToImport.put(name, owner);
+ return result;
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean visitVariableReference(VariableReference node) {
+ boolean result = super.visitVariableReference(node);
+
+ if (node.getParent() != null) {
+ lombok.ast.Node parent = node.getParent();
+ Class<? extends lombok.ast.Node> parentClass = parent.getClass();
+ if (parentClass == Select.class
+ || parentClass == Switch.class // look up on the switch expression type
+ || parentClass == Case.class
+ || parentClass == ConstructorInvocation.class
+ || parentClass == SuperConstructorInvocation.class
+ || parentClass == AnnotationElement.class) {
+ return result;
+ }
+
+ if (parent instanceof MethodInvocation &&
+ ((MethodInvocation) parent).astOperand() == node) {
+ return result;
+ } else if (parent instanceof BinaryExpression) {
+ BinaryExpression expression = (BinaryExpression) parent;
+ if (expression.astLeft() == node) {
+ return result;
+ }
+ }
+ }
+
+ String name = node.astIdentifier().astValue();
+ if (Character.isUpperCase(name.charAt(0))
+ && (mLocalVars == null || !mLocalVars.contains(name))
+ && (mFields == null || !mFields.contains(name))) {
+ // Potential field reference: check it
+ if (mStaticStarImports != null) {
+ for (String owner : mStaticStarImports) {
+ if (checkField(node, name, owner)) {
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ //noinspection VariableNotUsedInsideIf
+ if (mCurrentMethod != null) {
+ if (mLocalVars == null) {
+ mLocalVars = Sets.newHashSet();
+ }
+ mLocalVars.add(node.astName().astValue());
+ } else {
+ if (mFields == null) {
+ mFields = Sets.newHashSet();
+ }
+ mFields.add(node.astName().astValue());
+ }
+
+ Expression initializer = node.astInitializer();
+ if (initializer != null
+ // Checking cast expressions is handled already; prevent duplicate errors
+ && !(initializer instanceof Cast)) {
+ TypeDescriptor classType = mContext.getType(initializer);
+ if (classType != null && !classType.isPrimitive()) {
+ String classOwner = classType.getInternalName();
+ if (mApiDatabase.isKnownClass(classOwner)) {
+ TypeDescriptor interfaceType = mContext.getType(node);
+ if (interfaceType != null) {
+ checkCast(initializer, classOwner, classType, interfaceType);
+ }
+ }
+ }
+ }
+
+ return super.visitVariableDefinitionEntry(node);
+ }
+
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ mLocalVars = null;
+ mCurrentMethod = node;
+ return super.visitMethodDeclaration(node);
+ }
+
+ @Override
+ public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
+ mLocalVars = null;
+ mCurrentMethod = node;
+ return super.visitConstructorDeclaration(node);
+ }
+
+ @Override
+ public boolean visitCast(Cast node) {
+ TypeDescriptor classType = mContext.getType(node.astOperand());
+ if (classType != null && !classType.isPrimitive()) {
+ TypeDescriptor interfaceType = mContext.getType(node);
+ checkCast(node, classType.getInternalName(), classType, interfaceType);
+ }
+
+ return super.visitCast(node);
+ }
+
+ @Override
+ public boolean visitBinaryExpression(BinaryExpression node) {
+ if (node.astOperator() == BinaryOperator.ASSIGN &&
+ // Checking cast expressions is handled already; prevent duplicate errors
+ !(node.astRight() instanceof Cast)) {
+ TypeDescriptor classType = mContext.getType(node.astRight());
+ if (classType != null && !classType.isPrimitive()) {
+ String classOwner = classType.getInternalName();
+ if (mApiDatabase.isKnownClass(classOwner)) {
+ TypeDescriptor interfaceType = mContext.getType(node.astLeft());
+ if (interfaceType != null && !interfaceType.isPrimitive()) {
+ checkCast(node, classOwner, classType, interfaceType);
+ }
+ }
+ }
+ }
+ return super.visitBinaryExpression(node);
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ ResolvedMethod method = null;
+ int parameterIndex = 0;
+ for (Expression argument : node.astArguments()) {
+ TypeDescriptor argumentType = mContext.getType(argument);
+ if (argumentType != null && !argumentType.isPrimitive()) {
+ if (method == null) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ method = (ResolvedMethod) resolved;
+ } else {
+ break;
+ }
+ }
+ if (parameterIndex >= method.getArgumentCount()) {
+ // We can end up with more arguments than parameters when
+ // there is a varargs call.
+ break;
+ }
+ TypeDescriptor parameterType = method.getArgumentType(parameterIndex);
+ String argumentOwner = argumentType.getInternalName();
+ if (mApiDatabase.isKnownClass(argumentOwner)) {
+ checkCast(argument, argumentOwner, argumentType,
+ parameterType);
+ }
+ }
+ parameterIndex++;
+ }
+
+ return super.visitMethodInvocation(node);
+ }
+
+ private void checkCast(
+ @NonNull lombok.ast.Node node,
+ @NonNull String classOwner,
+ @NonNull TypeDescriptor classType,
+ @Nullable TypeDescriptor interfaceType) {
+ if (interfaceType != null && !interfaceType.equals(classType)) {
+ String interfaceInternalName = interfaceType.getInternalName();
+ int api = mApiDatabase.getValidCastVersion(classOwner, interfaceInternalName);
+ if (api != -1 && !"java/lang/Object".equals(interfaceInternalName)) {
+ int minSdk = getMinSdk(mContext);
+ if (api > minSdk && api > getLocalMinSdk(node)) {
+ LintDriver driver = mContext.getDriver();
+ if (!driver.isSuppressed(mContext, UNSUPPORTED, node)) {
+ Location location = mContext.getLocation(node);
+ String message = String.format(
+ "Cast from %1$s to %2$s requires API level %3$d (current min is %4$d)",
+ classType.getSimpleName(), interfaceType.getSimpleName(),
+ api, minSdk);
+ mContext.report(UNSUPPORTED, node, location, message);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean visitTry(Try node) {
+ Object nativeNode = node.getNativeNode();
+ if (nativeNode != null && nativeNode.getClass().getName().equals(
+ "org.eclipse.jdt.internal.compiler.ast.TryStatement")) {
+ boolean isTryWithResources = false;
+ try {
+ Field field = nativeNode.getClass().getDeclaredField("resources");
+ Object value = field.get(nativeNode);
+ if (value instanceof Object[]) {
+ Object[] resources = (Object[]) value;
+ isTryWithResources = resources.length > 0;
+ }
+ } catch (NoSuchFieldException e) {
+ // Unexpected: ECJ parser internals have changed; can't detect try block
+ } catch (IllegalAccessException e) {
+ // Unexpected: ECJ parser internals have changed; can't detect try block
+ }
+ if (isTryWithResources) {
+ int minSdk = getMinSdk(mContext);
+ int api = 19; // minSdk for try with resources
+ if (api > minSdk && api > getLocalMinSdk(node)) {
+ Location location = mContext.getLocation(node);
+ String message = String.format("Try-with-resources requires "
+ + "API level %1$d (current min is %2$d)", api, minSdk);
+ LintDriver driver = mContext.getDriver();
+ if (!driver.isSuppressed(mContext, UNSUPPORTED, node)) {
+ mContext.report(UNSUPPORTED, node, location, message);
+ }
+ }
+ }
+ }
+
+ return super.visitTry(node);
+ }
+
+ @Override
+ public void endVisit(lombok.ast.Node node) {
+ if (node == mCurrentMethod) {
+ mCurrentMethod = null;
+ }
+ super.endVisit(node);
+ }
+
+ /**
+ * Checks a Java source field reference. Returns true if the field is known
+ * regardless of whether it's an invalid field or not
+ */
+ private boolean checkField(
+ @NonNull lombok.ast.Node node,
+ @NonNull String name,
+ @NonNull String owner) {
+ int api = mApiDatabase.getFieldVersion(owner, name);
+ if (api != -1) {
+ int minSdk = getMinSdk(mContext);
+ if (api > minSdk
+ && api > getLocalMinSdk(node)) {
+ if (isBenignConstantUsage(node, name, owner)) {
+ return true;
+ }
+
+ Location location = mContext.getLocation(node);
+ String fqcn = getFqcn(owner) + '#' + name;
+
+ if (node instanceof ImportDeclaration) {
+ // Replace import statement location range with just
+ // the identifier part
+ ImportDeclaration d = (ImportDeclaration) node;
+ int startOffset = d.astParts().first().getPosition().getStart();
+ Position start = location.getStart();
+ assert start != null : location;
+ int startColumn = start.getColumn();
+ int startLine = start.getLine();
+ start = new DefaultPosition(startLine,
+ startColumn + startOffset - start.getOffset(), startOffset);
+ int fqcnLength = fqcn.length();
+ Position end = new DefaultPosition(startLine,
+ start.getColumn() + fqcnLength,
+ start.getOffset() + fqcnLength);
+ location = Location.create(location.getFile(), start, end);
+ }
+
+ String message = String.format(
+ "Field requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
+
+ LintDriver driver = mContext.getDriver();
+ if (driver.isSuppressed(mContext, INLINED, node)) {
+ return true;
+ }
+
+ // Also allow to suppress these issues with NewApi, since some
+ // fields used to get identified that way
+ if (driver.isSuppressed(mContext, UNSUPPORTED, node)) {
+ return true;
+ }
+
+ // We can't report the issue right away; we don't yet know if
+ // this is an actual inlined (static primitive or String) yet.
+ // So just make a note of it, and report these after the project
+ // checking has finished; any fields that aren't inlined will be
+ // cleared when they're noticed by the class check.
+ if (mPendingFields == null) {
+ mPendingFields = Maps.newHashMapWithExpectedSize(20);
+ }
+ List<Pair<String, Location>> list = mPendingFields.get(fqcn);
+ if (list == null) {
+ list = new ArrayList<Pair<String, Location>>();
+ mPendingFields.put(fqcn, list);
+ } else {
+ // See if this location already exists. This can happen if
+ // we have multiple references to an inlined field on the same
+ // line. Since the class file only gives us line information, we
+ // can't distinguish between these in the client as separate usages,
+ // so they end up being identical errors.
+ for (Pair<String, Location> pair : list) {
+ Location existingLocation = pair.getSecond();
+ //noinspection FileEqualsUsage
+ if (location.getFile().equals(existingLocation.getFile())) {
+ Position start = location.getStart();
+ Position existingStart = existingLocation.getStart();
+ if (start != null && existingStart != null
+ && start.getLine() == existingStart.getLine()) {
+ return true;
+ }
+ }
+ }
+ }
+ list.add(Pair.of(message, location));
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the minimum SDK to use according to the given AST node, or null
+ * if no {@code TargetApi} annotations were found
+ *
+ * @return the API level to use for this node, or -1
+ */
+ public int getLocalMinSdk(@Nullable lombok.ast.Node scope) {
+ while (scope != null) {
+ Class<? extends lombok.ast.Node> type = scope.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == VariableDefinition.class) {
+ // Variable
+ VariableDefinition declaration = (VariableDefinition) scope;
+ int targetApi = getTargetApi(declaration.astModifiers());
+ if (targetApi != -1) {
+ return targetApi;
+ }
+ } else if (type == MethodDeclaration.class) {
+ // Method
+ // Look for annotations on the method
+ MethodDeclaration declaration = (MethodDeclaration) scope;
+ int targetApi = getTargetApi(declaration.astModifiers());
+ if (targetApi != -1) {
+ return targetApi;
+ }
+ } else if (type == ConstructorDeclaration.class) {
+ // Constructor
+ // Look for annotations on the method
+ ConstructorDeclaration declaration = (ConstructorDeclaration) scope;
+ int targetApi = getTargetApi(declaration.astModifiers());
+ if (targetApi != -1) {
+ return targetApi;
+ }
+ } else if (TypeDeclaration.class.isAssignableFrom(type)) {
+ // Class, annotation, enum, interface
+ TypeDeclaration declaration = (TypeDeclaration) scope;
+ int targetApi = getTargetApi(declaration.astModifiers());
+ if (targetApi != -1) {
+ return targetApi;
+ }
+ } else if (type == AnnotationMethodDeclaration.class) {
+ // Look for annotations on the method
+ AnnotationMethodDeclaration declaration = (AnnotationMethodDeclaration) scope;
+ int targetApi = getTargetApi(declaration.astModifiers());
+ if (targetApi != -1) {
+ return targetApi;
+ }
+ }
+
+ scope = scope.getParent();
+ }
+
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the API level for the given AST node if specified with
+ * an {@code @TargetApi} annotation.
+ *
+ * @param modifiers the modifier to check
+ * @return the target API level, or -1 if not specified
+ */
+ public static int getTargetApi(@Nullable Modifiers modifiers) {
+ if (modifiers == null) {
+ return -1;
+ }
+ StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations();
+ if (annotations == null) {
+ return -1;
+ }
+
+ for (Annotation annotation : annotations) {
+ TypeReference t = annotation.astAnnotationTypeReference();
+ String typeName = t.getTypeName();
+ if (typeName.endsWith(TARGET_API)) {
+ StrictListAccessor<AnnotationElement, Annotation> values =
+ annotation.astElements();
+ if (values != null) {
+ for (AnnotationElement element : values) {
+ AnnotationValue valueNode = element.astValue();
+ if (valueNode == null) {
+ continue;
+ }
+ if (valueNode instanceof IntegralLiteral) {
+ IntegralLiteral literal = (IntegralLiteral) valueNode;
+ return literal.astIntValue();
+ } else if (valueNode instanceof StringLiteral) {
+ String value = ((StringLiteral) valueNode).astValue();
+ return SdkVersionInfo.getApiByBuildCode(value, true);
+ } else if (valueNode instanceof Select) {
+ Select select = (Select) valueNode;
+ String codename = select.astIdentifier().astValue();
+ return SdkVersionInfo.getApiByBuildCode(codename, true);
+ } else if (valueNode instanceof VariableReference) {
+ VariableReference reference = (VariableReference) valueNode;
+ String codename = reference.astIdentifier().astValue();
+ return SdkVersionInfo.getApiByBuildCode(codename, true);
+ }
+ }
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ public static int getRequiredVersion(@NonNull Issue issue, @NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+
+ if (issue == UNSUPPORTED || issue == INLINED) {
+ Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); //$NON-NLS-1$
+ Matcher matcher = pattern.matcher(errorMessage);
+ if (matcher.find()) {
+ return Integer.parseInt(matcher.group(1));
+ }
+ }
+
+ return -1;
+ }
+
+ private static boolean isWithinSdkConditional(
+ @NonNull ClassContext context,
+ @NonNull ClassNode classNode,
+ @NonNull MethodNode method,
+ @NonNull AbstractInsnNode call,
+ int requiredApi) {
+ assert requiredApi != -1;
+
+ if (!containsSimpleSdkCheck(method)) {
+ return false;
+ }
+
+ try {
+ // Search in the control graph, from beginning, up to the target call
+ // node, to see if it's reachable. The call graph is constructed in a
+ // special way: we include all control flow edges, *except* those that
+ // are satisfied by a SDK_INT version check (where the operand is a version
+ // that is at least as high as the one needed for the given call).
+ //
+ // If we can reach the call, that means that there is a way this call
+ // can be reached on some versions, and lint should flag the call/field lookup.
+ //
+ //
+ // Let's say you have code like this:
+ // if (SDK_INT >= LOLLIPOP) {
+ // // Call
+ // return property.hasAdjacentMapping();
+ // }
+ // ...
+ //
+ // The compiler will turn this into the following byte code:
+ //
+ // 0: getstatic #3; //Field android/os/Build$VERSION.SDK_INT:I
+ // 3: bipush 21
+ // 5: if_icmple 17
+ // 8: aload_1
+ // 9: invokeinterface #4, 1; //InterfaceMethod
+ // android/view/ViewDebug$ExportedProperty.hasAdjacentMapping:()Z
+ // 14: ifeq 17
+ // 17: ... code after if loop
+ //
+ // When the call graph is constructed, for an if branch we're called twice; once
+ // where the target is the next instruction (the one taken if byte code check is false)
+ // and one to the jump label (the one taken if the byte code condition is true).
+ //
+ // Notice how at the byte code level, the logic is reversed: the >= instruction
+ // is turned into "<" and we jump to the code *after* the if clause; otherwise
+ // it will just fall through. Therefore, if we take a byte code branch, that means
+ // that the SDK check was *not* satisfied, and conversely, the target call is reachable
+ // if we don't take the branch.
+ //
+ // Therefore, when we build the call graph, we will add call graph nodes for an
+ // if check if :
+ // (1) it is some other comparison than <, <= or !=.
+ // (2) if the byte code comparison check is *not* satisfied, this means that the the
+ // SDK check was successful and that the call graph should only include
+ // the jump edge
+ // (3) all other edges are added
+ //
+ // With a flow control graph like that, we can determine whether a target call
+ // is guarded by a given SDK check: that will be the case if we cannot reach
+ // the target call in the call graph
+
+ ApiCheckGraph graph = new ApiCheckGraph(requiredApi);
+ ControlFlowGraph.create(graph, classNode, method);
+
+ // Note: To debug unit tests, you may want to for example do
+ // ControlFlowGraph.Node callNode = graph.getNode(call);
+ // Set<ControlFlowGraph.Node> highlight = Sets.newHashSet(callNode);
+ // Files.write(graph.toDot(highlight), new File("/tmp/graph.gv"), Charsets.UTF_8);
+ // This will generate a graphviz file you can visualize with the "dot" utility
+ AbstractInsnNode first = method.instructions.get(0);
+ return !graph.isConnected(first, call);
+ } catch (AnalyzerException e) {
+ context.log(e, null);
+ }
+
+ return false;
+ }
+
+ private static boolean containsSimpleSdkCheck(@NonNull MethodNode method) {
+ // Look for a compiled version of "if (Build.VERSION.SDK_INT op N) {"
+ InsnList nodes = method.instructions;
+ for (int i = 0, n = nodes.size(); i < n; i++) {
+ AbstractInsnNode instruction = nodes.get(i);
+ if (isSdkVersionLookup(instruction)) {
+ AbstractInsnNode bipush = getNextInstruction(instruction);
+ if (bipush != null && bipush.getOpcode() == Opcodes.BIPUSH) {
+ AbstractInsnNode ifNode = getNextInstruction(bipush);
+ if (ifNode != null && ifNode.getType() == AbstractInsnNode.JUMP_INSN) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isSdkVersionLookup(@NonNull AbstractInsnNode instruction) {
+ if (instruction.getOpcode() == Opcodes.GETSTATIC) {
+ FieldInsnNode fieldNode = (FieldInsnNode) instruction;
+ return (SDK_INT.equals(fieldNode.name)
+ && ANDROID_OS_BUILD_VERSION.equals(fieldNode.owner));
+ }
+ return false;
+ }
+
+ /**
+ * Control flow graph which skips control flow edges that check
+ * a given SDK_VERSION requirement that is not met by a given call
+ */
+ private static class ApiCheckGraph extends ControlFlowGraph {
+ private final int mRequiredApi;
+
+ public ApiCheckGraph(int requiredApi) {
+ mRequiredApi = requiredApi;
+ }
+
+ @Override
+ protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
+ if (from.getType() == AbstractInsnNode.JUMP_INSN &&
+ from.getPrevious() != null &&
+ from.getPrevious().getType() == AbstractInsnNode.INT_INSN) {
+ IntInsnNode intNode = (IntInsnNode) from.getPrevious();
+ if (intNode.getPrevious() != null && isSdkVersionLookup(intNode.getPrevious())) {
+ JumpInsnNode jumpNode = (JumpInsnNode) from;
+ int api = intNode.operand;
+ boolean isJumpEdge = to == jumpNode.label;
+ boolean includeEdge;
+ switch (from.getOpcode()) {
+ case Opcodes.IF_ICMPNE:
+ includeEdge = api < mRequiredApi || isJumpEdge;
+ break;
+ case Opcodes.IF_ICMPLE:
+ includeEdge = api < mRequiredApi - 1 || isJumpEdge;
+ break;
+ case Opcodes.IF_ICMPLT:
+ includeEdge = api < mRequiredApi || isJumpEdge;
+ break;
+
+ case Opcodes.IF_ICMPGE:
+ includeEdge = api < mRequiredApi || !isJumpEdge;
+ break;
+ case Opcodes.IF_ICMPGT:
+ includeEdge = api < mRequiredApi - 1 || !isJumpEdge;
+ break;
+ default:
+ // unexpected comparison for int API level
+ includeEdge = true;
+ }
+ if (!includeEdge) {
+ return;
+ }
+ }
+ }
+
+ super.add(from, to);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
new file mode 100644
index 0000000..45e7581
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
@@ -0,0 +1,1351 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_PKG;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.repository.api.LocalPackage;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.utils.Pair;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteSink;
+import com.google.common.io.Files;
+import com.google.common.primitives.UnsignedBytes;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Database for API checking: Allows quick lookup of a given class, method or field
+ * to see which API level it was introduced in.
+ * <p>
+ * This class is optimized for quick bytecode lookup used in conjunction with the
+ * ASM library: It has lookup methods that take internal JVM signatures, and for a method
+ * call for example it processes the owner, name and description parameters separately
+ * the way they are provided from ASM.
+ * <p>
+ * The {@link Api} class provides access to the full Android API along with version
+ * information, initialized from an XML file. This lookup class adds a binary cache around
+ * the API to make initialization faster and to require fewer objects. It creates
+ * a binary cache data structure, which fits in a single byte array, which means that
+ * to open the database you can just read in the byte array and go. On one particular
+ * machine, this takes about 30-50 ms versus 600-800ms for the full parse. It also
+ * helps memory by placing everything in a compact byte array instead of needing separate
+ * strings (2 bytes per character in a char[] for the 25k method entries, 11k field entries
+ * and 6k class entries) - and it also avoids the same number of Map.Entry objects.
+ * When creating the memory data structure it performs a few other steps to help memory:
+ * <ul>
+ * <li> It stores the strings as single bytes, since all the JVM signatures are in ASCII
+ * <li> It strips out the method return types (which takes the binary size down from
+ * about 4.7M to 4.0M)
+ * <li> It strips out all APIs that have since=1, since the lookup only needs to find
+ * classes, methods and fields that have an API level *higher* than 1. This drops
+ * the memory use down from 4.0M to 1.7M.
+ * </ul>
+ */
+public class ApiLookup {
+ /** Relative path to the api-versions.xml database file within the Lint installation */
+ private static final String XML_FILE_PATH = "platform-tools/api/api-versions.xml"; //$NON-NLS-1$
+ private static final String FILE_HEADER = "API database used by Android lint\000";
+ private static final int BINARY_FORMAT_VERSION = 8;
+ private static final boolean DEBUG_SEARCH = false;
+ private static final boolean WRITE_STATS = false;
+
+ private static final int CLASS_HEADER_MEMBER_OFFSETS = 1;
+ private static final int CLASS_HEADER_API = 2;
+ private static final int CLASS_HEADER_DEPRECATED = 3;
+ private static final int CLASS_HEADER_INTERFACES = 4;
+ private static final int HAS_DEPRECATION_BYTE_FLAG = 1 << 7;
+ private static final int API_MASK = ~HAS_DEPRECATION_BYTE_FLAG;
+
+ @VisibleForTesting
+ static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
+
+ private final Api mInfo;
+ private byte[] mData;
+ private int[] mIndices;
+
+ private static WeakReference<ApiLookup> sInstance = new WeakReference<ApiLookup>(null);
+
+ private int mPackageCount;
+
+ /**
+ * Returns an instance of the API database
+ *
+ * @param client the client to associate with this database - used only for
+ * logging. The database object may be shared among repeated invocations,
+ * and in that case client used will be the one originally passed in.
+ * In other words, this parameter may be ignored if the client created
+ * is not new.
+ * @return a (possibly shared) instance of the API database, or null
+ * if its data can't be found
+ */
+ @Nullable
+ public static ApiLookup get(@NonNull LintClient client) {
+ synchronized (ApiLookup.class) {
+ ApiLookup db = sInstance.get();
+ if (db == null) {
+ File file = client.findResource(XML_FILE_PATH);
+ if (file == null) {
+ // AOSP build environment?
+ String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
+ if (build != null) {
+ file = new File(build, "development/sdk/api-versions.xml" //$NON-NLS-1$
+ .replace('/', File.separatorChar));
+ }
+ }
+
+ if (file == null || !file.exists()) {
+ return null;
+ } else {
+ db = get(client, file);
+ }
+ sInstance = new WeakReference<ApiLookup>(db);
+ }
+
+ return db;
+ }
+ }
+
+ @VisibleForTesting
+ @Nullable
+ static String getPlatformVersion(@NonNull LintClient client) {
+ AndroidSdkHandler sdk = client.getSdk();
+ if (sdk != null) {
+ LocalPackage pkgInfo = sdk
+ .getLocalPackage(SdkConstants.FD_PLATFORM_TOOLS, client.getRepositoryLogger());
+ if (pkgInfo != null) {
+ return pkgInfo.getVersion().toShortString();
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ static String getCacheFileName(@NonNull String xmlFileName, @Nullable String platformVersion) {
+ if (LintUtils.endsWith(xmlFileName, DOT_XML)) {
+ xmlFileName = xmlFileName.substring(0, xmlFileName.length() - DOT_XML.length());
+ }
+
+ StringBuilder sb = new StringBuilder(100);
+ sb.append(xmlFileName);
+
+ // Incorporate version number in the filename to avoid upgrade filename
+ // conflicts on Windows (such as issue #26663)
+ sb.append('-').append(BINARY_FORMAT_VERSION);
+
+ if (platformVersion != null) {
+ sb.append('-').append(platformVersion);
+ }
+
+ sb.append(".bin"); //$NON-NLS-1$
+ return sb.toString();
+ }
+
+ /**
+ * Returns an instance of the API database
+ *
+ * @param client the client to associate with this database - used only for
+ * logging
+ * @param xmlFile the XML file containing configuration data to use for this
+ * database
+ * @return a (possibly shared) instance of the API database, or null
+ * if its data can't be found
+ */
+ private static ApiLookup get(LintClient client, File xmlFile) {
+ if (!xmlFile.exists()) {
+ client.log(null, "The API database file %1$s does not exist", xmlFile);
+ return null;
+ }
+
+ File cacheDir = client.getCacheDir(true/*create*/);
+ if (cacheDir == null) {
+ cacheDir = xmlFile.getParentFile();
+ }
+
+ String platformVersion = getPlatformVersion(client);
+ File binaryData = new File(cacheDir, getCacheFileName(xmlFile.getName(), platformVersion));
+
+ if (DEBUG_FORCE_REGENERATE_BINARY) {
+ System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom "
+ + xmlFile + "\nto " + binaryData);
+ if (!createCache(client, xmlFile, binaryData)) {
+ return null;
+ }
+ } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified()
+ || binaryData.length() == 0) {
+ if (!createCache(client, xmlFile, binaryData)) {
+ return null;
+ }
+ }
+
+ if (!binaryData.exists()) {
+ client.log(null, "The API database file %1$s does not exist", binaryData);
+ return null;
+ }
+
+ return new ApiLookup(client, xmlFile, binaryData, null);
+ }
+
+ private static boolean createCache(LintClient client, File xmlFile, File binaryData) {
+ long begin = 0;
+ if (WRITE_STATS) {
+ begin = System.currentTimeMillis();
+ }
+
+ Api info = Api.parseApi(xmlFile);
+
+ if (WRITE_STATS) {
+ long end = System.currentTimeMillis();
+ System.out.println("Reading XML data structures took " + (end - begin) + " ms)");
+ }
+
+ if (info != null) {
+ try {
+ writeDatabase(binaryData, info);
+ return true;
+ } catch (IOException ioe) {
+ client.log(ioe, "Can't write API cache file");
+ }
+ }
+
+ return false;
+ }
+
+ /** Use one of the {@link #get} factory methods instead */
+ private ApiLookup(
+ @NonNull LintClient client,
+ @NonNull File xmlFile,
+ @Nullable File binaryFile,
+ @Nullable Api info) {
+ mInfo = info;
+
+ if (binaryFile != null) {
+ readData(client, xmlFile, binaryFile);
+ }
+ }
+
+ /**
+ * Database format:
+ * <pre>
+ * (Note: all numbers are big endian; the format uses 1, 2, 3 and 4 byte integers.)
+ *
+ *
+ * 1. A file header, which is the exact contents of {@link #FILE_HEADER} encoded
+ * as ASCII characters. The purpose of the header is to identify what the file
+ * is for, for anyone attempting to open the file.
+ * 2. A file version number. If the binary file does not match the reader's expected
+ * version, it can ignore it (and regenerate the cache from XML).
+ *
+ * 3. The index table. When the data file is read, this is used to initialize the
+ * {@link #mIndices} array. The index table is built up like this:
+ * a. The number of index entries (e.g. number of elements in the {@link #mIndices} array)
+ * [1 4-byte int]
+ * b. The number of java/javax packages [1 4 byte int]
+ * c. Offsets to the package entries, one for each package, and each offset is 4 bytes.
+ * d. Offsets to the class entries, one for each class, and each offset is 4 bytes.
+ * e. Offsets to the member entries, one for each member, and each offset is 4 bytes.
+ *
+ * 4. The member entries -- one for each member. A given class entry will point to the
+ * first and last members in the index table above, and the offset of a given member
+ * is pointing to the offset of these entries.
+ * a. The name and description (except for the return value) of the member, in JVM format
+ * (e.g. for toLowerCase(char) we'd have "toLowerCase(C)". This is converted into
+ * UTF_8 representation as bytes [n bytes, the length of the byte representation of
+ * the description).
+ * b. A terminating 0 byte [1 byte].
+ * c. The API level the member was introduced in [1 byte], BUT with the
+ * top bit ({@link #HAS_DEPRECATION_BYTE_FLAG}) set if the member is deprecated.
+ * d. IF the member is deprecated, the API level the member was deprecated in [1 byte].
+ * Note that this byte does not appear if the bit indicated in (c) is not set.
+ *
+ * 5. The class entries -- one for each class.
+ * a. The index within this class entry where the metadata (other than the name)
+ * can be found. [1 byte]. This means that if you know a class by its number,
+ * you can quickly jump to its metadata without scanning through the string to
+ * find the end of it, by just adding this byte to the current offset and
+ * then you're at the data described below for (d).
+ * b. The name of the class (just the base name, not the package), as encoded as a
+ * UTF-8 string. [n bytes]
+ * c. A terminating 0 [1 byte].
+ * d. The index in the index table (3) of the first member in the class [a 3 byte integer.]
+ * e. The number of members in the class [a 2 byte integer].
+ * f. The API level the class was introduced in [1 byte], BUT with the
+ * top bit ({@link #HAS_DEPRECATION_BYTE_FLAG}) set if the class is deprecated.
+ * g. IF the class is deprecated, the API level the class was deprecated in [1 byte].
+ * Note that this byte does not appear if the bit indicated in (f) is not set.
+ * h. The number of new super classes and interfaces [1 byte]. This counts only
+ * super classes and interfaces added after the original API level of the class.
+ * i. For each super class or interface counted in h,
+ * I. The index of the class [a 3 byte integer]
+ * II. The API level the class/interface was added [1 byte]
+ *
+ * 6. The package entries -- one for each package.
+ * a. The name of the package as encoded as a UTF-8 string. [n bytes]
+ * b. A terminating 0 [1 byte].
+ * c. The index in the index table (3) of the first class in the package [a 3 byte integer.]
+ * d. The number of classes in the package [a 2 byte integer].
+ * </pre>
+ */
+ private void readData(@NonNull LintClient client, @NonNull File xmlFile,
+ @NonNull File binaryFile) {
+ if (!binaryFile.exists()) {
+ client.log(null, "%1$s does not exist", binaryFile);
+ return;
+ }
+ long start = System.currentTimeMillis();
+ try {
+ byte[] b = Files.toByteArray(binaryFile);
+
+ // First skip the header
+ int offset = 0;
+ byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII);
+ for (byte anExpectedHeader : expectedHeader) {
+ if (anExpectedHeader != b[offset++]) {
+ client.log(null, "Incorrect file header: not an API database cache " +
+ "file, or a corrupt cache file");
+ return;
+ }
+ }
+
+ // Read in the format number
+ if (b[offset++] != BINARY_FORMAT_VERSION) {
+ // Force regeneration of new binary data with up to date format
+ if (createCache(client, xmlFile, binaryFile)) {
+ readData(client, xmlFile, binaryFile); // Recurse
+ }
+
+ return;
+ }
+
+ int indexCount = get4ByteInt(b, offset);
+ offset += 4;
+ mPackageCount = get4ByteInt(b, offset);
+ offset += 4;
+
+ mIndices = new int[indexCount];
+ for (int i = 0; i < indexCount; i++) {
+ // TODO: Pack the offsets: They increase by a small amount for each entry, so
+ // no need to spend 4 bytes on each. These will need to be processed when read
+ // back in anyway, so consider storing the offset -deltas- as single bytes and
+ // adding them up cumulatively in readData().
+ mIndices[i] = get4ByteInt(b, offset);
+ offset += 4;
+ }
+ mData = b;
+ // TODO: We only need to keep the data portion here since we've initialized
+ // the offset array separately.
+ // TODO: Investigate (profile) accessing the byte buffer directly instead of
+ // accessing a byte array.
+ } catch (Throwable e) {
+ client.log(null, "Failure reading binary cache file %1$s", binaryFile.getPath());
+ client.log(null, "Please delete the file and restart the IDE/lint: %1$s",
+ binaryFile.getPath());
+ client.log(e, null);
+ }
+ if (WRITE_STATS) {
+ long end = System.currentTimeMillis();
+ System.out.println("\nRead API database in " + (end - start)
+ + " milliseconds.");
+ System.out.println("Size of data table: " + mData.length + " bytes ("
+ + Integer.toString(mData.length / 1024) + "k)\n");
+ }
+ }
+
+ /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */
+ private static void writeDatabase(File file, Api info) throws IOException {
+ Map<String, ApiClass> classMap = info.getClasses();
+
+ List<ApiPackage> packages = Lists.newArrayList(info.getPackages().values());
+ Collections.sort(packages);
+
+ // Compute members of each class that must be included in the database; we can
+ // skip those that have the same since-level as the containing class. And we
+ // also need to keep those entries that are marked deprecated.
+ int estimatedSize = 0;
+ for (ApiPackage pkg : packages) {
+ estimatedSize += 4; // offset entry
+ estimatedSize += pkg.getName().length() + 20; // package entry
+
+ if (assertionsEnabled() && !isRelevantOwner(pkg.getName() + "/") &&
+ !pkg.getName().startsWith("android/support")) {
+ System.out.println("Warning: isRelevantOwner fails for " + pkg.getName() + "/");
+ }
+
+ for (ApiClass apiClass : pkg.getClasses()) {
+ estimatedSize += 4; // offset entry
+ estimatedSize += apiClass.getName().length() + 20; // class entry
+
+ Set<String> allMethods = apiClass.getAllMethods(info);
+ Set<String> allFields = apiClass.getAllFields(info);
+ // Strip out all members that have been supported since version 1.
+ // This makes the database *much* leaner (down from about 4M to about
+ // 1.7M), and this just fills the table with entries that ultimately
+ // don't help the API checker since it just needs to know if something
+ // requires a version *higher* than the minimum. If in the future the
+ // database needs to answer queries about whether a method is public
+ // or not, then we'd need to put this data back in.
+ int clsSince = apiClass.getSince();
+ List<String> members = new ArrayList<String>(allMethods.size() + allFields.size());
+ for (String member : allMethods) {
+ if (apiClass.getMethod(member, info) != clsSince
+ || apiClass.getMemberDeprecatedIn(member, info) > 0) {
+ members.add(member);
+ }
+ }
+ for (String member : allFields) {
+ if (apiClass.getField(member, info) != clsSince
+ || apiClass.getMemberDeprecatedIn(member, info) > 0) {
+ members.add(member);
+ }
+ }
+
+ estimatedSize += 2 + 4 * (apiClass.getInterfaces().size());
+ if (apiClass.getSuperClasses().size() > 1) {
+ estimatedSize += 2 + 4 * (apiClass.getSuperClasses().size());
+ }
+
+ // Only include classes that have one or more members requiring version 2 or higher:
+ Collections.sort(members);
+ apiClass.members = members;
+ for (String member : members) {
+ estimatedSize += member.length();
+ estimatedSize += 16;
+ }
+ }
+
+ // Ensure the classes are sorted
+ Collections.sort(pkg.getClasses());
+ }
+
+ // Write header
+ ByteBuffer buffer = ByteBuffer.allocate(estimatedSize);
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ buffer.put(FILE_HEADER.getBytes(Charsets.US_ASCII));
+ buffer.put((byte) BINARY_FORMAT_VERSION);
+
+ int indexCountOffset = buffer.position();
+ int indexCount = 0;
+
+ buffer.putInt(0); // placeholder
+
+ // Write the number of packages in the package index
+ buffer.putInt(packages.size());
+
+ // Write package index
+ int newIndex = buffer.position();
+ for (ApiPackage pkg : packages) {
+ pkg.indexOffset = newIndex;
+ newIndex += 4;
+ indexCount++;
+ }
+
+ // Write class index
+ for (ApiPackage pkg : packages) {
+ for (ApiClass cls : pkg.getClasses()) {
+ cls.indexOffset = newIndex;
+ cls.index = indexCount;
+ newIndex += 4;
+ indexCount++;
+ }
+ }
+
+ // Write member indices
+ for (ApiPackage pkg : packages) {
+ for (ApiClass cls : pkg.getClasses()) {
+ if (cls.members != null && !cls.members.isEmpty()) {
+ cls.memberOffsetBegin = newIndex;
+ cls.memberIndexStart = indexCount;
+ for (String ignored : cls.members) {
+ newIndex += 4;
+ indexCount++;
+ }
+ cls.memberOffsetEnd = newIndex;
+ cls.memberIndexLength = indexCount - cls.memberIndexStart;
+ } else {
+ cls.memberOffsetBegin = -1;
+ cls.memberOffsetEnd = -1;
+ cls.memberIndexStart = -1;
+ cls.memberIndexLength = 0;
+ }
+ }
+ }
+
+ // Fill in the earlier index count
+ buffer.position(indexCountOffset);
+ buffer.putInt(indexCount);
+ buffer.position(newIndex);
+
+ // Write member entries
+ for (ApiPackage pkg : packages) {
+ for (ApiClass apiClass : pkg.getClasses()) {
+ String clz = apiClass.getName();
+ int index = apiClass.memberOffsetBegin;
+ for (String member : apiClass.members) {
+ // Update member offset to point to this entry
+ int start = buffer.position();
+ buffer.position(index);
+ buffer.putInt(start);
+ index = buffer.position();
+ buffer.position(start);
+
+ int since;
+ if (member.indexOf('(') != -1) {
+ since = apiClass.getMethod(member, info);
+ } else {
+ since = apiClass.getField(member, info);
+ }
+ if (since == Integer.MAX_VALUE) {
+ assert false : clz + ':' + member;
+ since = 1;
+ }
+
+ int deprecatedIn = apiClass.getMemberDeprecatedIn(member, info);
+ if (deprecatedIn != 0) {
+ assert deprecatedIn != -1 : deprecatedIn + " for " + member;
+ }
+
+ byte[] signature = member.getBytes(Charsets.UTF_8);
+ for (byte b : signature) {
+ // Make sure all signatures are really just simple ASCII
+ assert b == (b & 0x7f) : member;
+ buffer.put(b);
+ // Skip types on methods
+ if (b == (byte) ')') {
+ break;
+ }
+ }
+ buffer.put((byte) 0);
+ int api = since;
+ assert api == UnsignedBytes.toInt((byte) api);
+ assert api >= 1 && api < 0xFF; // max that fits in a byte
+
+ boolean isDeprecated = deprecatedIn > 0;
+ if (isDeprecated) {
+ api |= HAS_DEPRECATION_BYTE_FLAG;
+ }
+
+ buffer.put((byte) api);
+
+ if (isDeprecated) {
+ assert deprecatedIn == UnsignedBytes.toInt((byte) deprecatedIn);
+ buffer.put((byte) deprecatedIn);
+ }
+ }
+ assert index == apiClass.memberOffsetEnd : apiClass.memberOffsetEnd;
+ }
+ }
+
+ // Write class entries. These are written together, rather than
+ // being spread out among the member entries, in order to have
+ // reference locality (search that a binary search through the classes
+ // are likely to look at entries near each other.)
+ for (ApiPackage pkg : packages) {
+ List<ApiClass> classes = pkg.getClasses();
+ for (ApiClass cls : classes) {
+ int index = buffer.position();
+ buffer.position(cls.indexOffset);
+ buffer.putInt(index);
+ buffer.position(index);
+ String name = cls.getSimpleName();
+
+ byte[] nameBytes = name.getBytes(Charsets.UTF_8);
+ assert nameBytes.length < 254 : name;
+ buffer.put((byte)(nameBytes.length + 2)); // 2: terminating 0, and this byte itself
+ buffer.put(nameBytes);
+ buffer.put((byte) 0);
+
+ // 3 bytes for beginning, 2 bytes for *length*
+ put3ByteInt(buffer, cls.memberIndexStart);
+ put2ByteInt(buffer, cls.memberIndexLength);
+
+ ApiClass apiClass = classMap.get(cls.getName());
+ assert apiClass != null : cls.getName();
+ int since = apiClass.getSince();
+ assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits
+ int deprecatedIn = apiClass.getDeprecatedIn();
+ boolean isDeprecated = deprecatedIn > 0;
+ // The first byte is deprecated in
+ if (isDeprecated) {
+ since |= HAS_DEPRECATION_BYTE_FLAG;
+ assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits
+ }
+ buffer.put((byte) since);
+ if (isDeprecated) {
+ assert deprecatedIn == UnsignedBytes.toInt((byte) deprecatedIn) : deprecatedIn;
+ buffer.put((byte) deprecatedIn);
+ }
+
+ List<Pair<String, Integer>> interfaces = apiClass.getInterfaces();
+ int count = 0;
+ if (interfaces != null && !interfaces.isEmpty()) {
+ for (Pair<String, Integer> pair : interfaces) {
+ int api = pair.getSecond();
+ if (api > apiClass.getSince()) {
+ count++;
+ }
+ }
+ }
+ List<Pair<String, Integer>> supers = apiClass.getSuperClasses();
+ if (supers != null && !supers.isEmpty()) {
+ for (Pair<String, Integer> pair : supers) {
+ int api = pair.getSecond();
+ if (api > apiClass.getSince()) {
+ count++;
+ }
+ }
+ }
+ buffer.put((byte)count);
+ if (count > 0) {
+ if (supers != null) {
+ for (Pair<String, Integer> pair : supers) {
+ int api = pair.getSecond();
+ if (api > apiClass.getSince()) {
+ ApiClass superClass = classMap.get(pair.getFirst());
+ assert superClass != null : cls;
+ put3ByteInt(buffer, superClass.index);
+ buffer.put((byte) api);
+ }
+ }
+ }
+ if (interfaces != null) {
+ for (Pair<String, Integer> pair : interfaces) {
+ int api = pair.getSecond();
+ if (api > apiClass.getSince()) {
+ ApiClass interfaceClass = classMap.get(pair.getFirst());
+ assert interfaceClass != null : cls;
+ put3ByteInt(buffer, interfaceClass.index);
+ buffer.put((byte) api);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (ApiPackage pkg : packages) {
+ int index = buffer.position();
+ buffer.position(pkg.indexOffset);
+ buffer.putInt(index);
+ buffer.position(index);
+
+ byte[] bytes = pkg.getName().getBytes(Charsets.UTF_8);
+ buffer.put(bytes);
+ buffer.put((byte)0);
+
+ List<ApiClass> classes = pkg.getClasses();
+ if (classes.isEmpty()) {
+ put3ByteInt(buffer, 0);
+ put2ByteInt(buffer, 0);
+ } else {
+ // 3 bytes for beginning, 2 bytes for *length*
+ int firstClassIndex = classes.get(0).index;
+ int classCount = classes.get(classes.size() - 1).index - firstClassIndex + 1;
+ put3ByteInt(buffer, firstClassIndex);
+ put2ByteInt(buffer, classCount);
+ }
+ }
+
+ int size = buffer.position();
+ assert size <= buffer.limit();
+ buffer.mark();
+
+ if (WRITE_STATS) {
+ System.out.print("Actual binary size: " + size + " bytes");
+ System.out.println(String.format(" (%.1fM)", size/(1024*1024.f)));
+ }
+
+ // Now dump this out as a file
+ // There's probably an API to do this more efficiently; TODO: Look into this.
+ byte[] b = new byte[size];
+ buffer.rewind();
+ buffer.get(b);
+ if (file.exists()) {
+ boolean deleted = file.delete();
+ assert deleted : file;
+ }
+ ByteSink sink = Files.asByteSink(file);
+ sink.write(b);
+ }
+
+ // For debugging only
+ private String dumpEntry(int offset) {
+ if (DEBUG_SEARCH) {
+ StringBuilder sb = new StringBuilder(200);
+ for (int i = offset; i < mData.length; i++) {
+ if (mData[i] == 0) {
+ break;
+ }
+ char c = (char) UnsignedBytes.toInt(mData[i]);
+ sb.append(c);
+ }
+
+ return sb.toString();
+ } else {
+ return "<disabled>"; //$NON-NLS-1$
+ }
+ }
+
+ private static int compare(byte[] data, int offset, byte terminator, String s, int sOffset,
+ int max) {
+ int i = offset;
+ int j = sOffset;
+ for (; j < max; i++, j++) {
+ byte b = data[i];
+ char c = s.charAt(j);
+ // TODO: Check somewhere that the strings are purely in the ASCII range; if not
+ // they're not a match in the database
+ byte cb = (byte) c;
+ int delta = b - cb;
+ if (delta != 0) {
+ return delta;
+ }
+ }
+
+ return data[i] - terminator;
+ }
+
+ /**
+ * Returns the API version required by the given class reference,
+ * or -1 if this is not a known API class. Note that it may return -1
+ * for classes introduced in version 1; internally the database only
+ * stores version data for version 2 and up.
+ *
+ * @param className the internal name of the class, e.g. its
+ * fully qualified name (as returned by Class.getName(), but with
+ * '.' replaced by '/'.
+ * @return the minimum API version the method is supported for, or -1 if
+ * it's unknown <b>or version 1</b>.
+ */
+ public int getClassVersion(@NonNull String className) {
+ //noinspection VariableNotUsedInsideIf
+ if (mData != null) {
+ return getClassVersion(findClass(className));
+ } else {
+ assert mInfo != null;
+ ApiClass clz = mInfo.getClass(className);
+ if (clz != null) {
+ int since = clz.getSince();
+ if (since == Integer.MAX_VALUE) {
+ since = -1;
+ }
+ return since;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns true if the given owner class is known in the API database.
+ *
+ * @param className the internal name of the class, e.g. its fully qualified name (as returned
+ * by Class.getName(), but with '.' replaced by '/' (and '$' for inner
+ * classes)
+ * @return true if this is a class found in the API database
+ */
+ public boolean isKnownClass(@NonNull String className) {
+ return findClass(className) != -1;
+ }
+
+ private int getClassVersion(int classNumber) {
+ if (classNumber != -1) {
+ int offset = seekClassData(classNumber, CLASS_HEADER_API);
+ int api = UnsignedBytes.toInt(mData[offset]) & API_MASK;
+ return api > 1 ? api : -1;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the API version required to perform the given cast, or -1 if this is valid for all
+ * versions of the class (or, if these are not known classes or if the cast is not valid at
+ * all.) <p> Note also that this method should only be called for interfaces that are actually
+ * implemented by this class or extending the given super class (check elsewhere); it doesn't
+ * distinguish between interfaces implemented in the initial version of the class and interfaces
+ * not implemented at all.
+ *
+ * @param sourceClass the internal name of the class, e.g. its fully qualified name (as
+ * returned by Class.getName(), but with '.' replaced by '/'.
+ * @param destinationClass the class to cast the sourceClass to
+ * @return the minimum API version the method is supported for, or 1 or -1 if it's unknown.
+ */
+ public int getValidCastVersion(@NonNull String sourceClass,
+ @NonNull String destinationClass) {
+ if (mData != null) {
+ int classNumber = findClass(sourceClass);
+ if (classNumber != -1) {
+ int interfaceNumber = findClass(destinationClass);
+ if (interfaceNumber != -1) {
+ int offset = seekClassData(classNumber, CLASS_HEADER_INTERFACES);
+ int interfaceCount = mData[offset++];
+ for (int i = 0; i < interfaceCount; i++) {
+ int clsNumber = get3ByteInt(mData, offset);
+ offset += 3;
+ int api = mData[offset++];
+ if (clsNumber == interfaceNumber) {
+ return api;
+ }
+ }
+ return getClassVersion(classNumber);
+ }
+ }
+ } else {
+ assert mInfo != null;
+ ApiClass clz = mInfo.getClass(sourceClass);
+ if (clz != null) {
+ List<Pair<String, Integer>> interfaces = clz.getInterfaces();
+ for (Pair<String,Integer> pair : interfaces) {
+ String interfaceName = pair.getFirst();
+ if (interfaceName.equals(destinationClass)) {
+ return pair.getSecond();
+ }
+ }
+ }
+ }
+
+ return -1;
+ }
+ /**
+ * Returns the API version the given class was deprecated in, or -1 if the class
+ * is not deprecated.
+ *
+ * @param className the internal name of the method's owner class, e.g. its
+ * fully qualified name (as returned by Class.getName(), but with
+ * '.' replaced by '/'.
+ * @return the API version the API was deprecated in, or -1 if
+ * it's unknown <b>or version 0</b>.
+ */
+ public int getClassDeprecatedIn(@NonNull String className) {
+ if (mData != null) {
+ int classNumber = findClass(className);
+ if (classNumber != -1) {
+ int offset = seekClassData(classNumber, CLASS_HEADER_DEPRECATED);
+ if (offset == -1) {
+ // Not deprecated
+ return -1;
+ }
+ int deprecatedIn = UnsignedBytes.toInt(mData[offset]);
+ return deprecatedIn != 0 ? deprecatedIn : -1;
+ }
+ } else {
+ assert mInfo != null;
+ ApiClass clz = mInfo.getClass(className);
+ if (clz != null) {
+ int deprecatedIn = clz.getDeprecatedIn();
+ if (deprecatedIn == Integer.MAX_VALUE) {
+ deprecatedIn = -1;
+ }
+ return deprecatedIn;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the API version required by the given method call. The method is
+ * referred to by its {@code owner}, {@code name} and {@code desc} fields.
+ * If the method is unknown it returns -1. Note that it may return -1 for
+ * classes introduced in version 1; internally the database only stores
+ * version data for version 2 and up.
+ *
+ * @param owner the internal name of the method's owner class, e.g. its
+ * fully qualified name (as returned by Class.getName(), but with
+ * '.' replaced by '/'.
+ * @param name the method's name
+ * @param desc the method's descriptor - see {@link org.objectweb.asm.Type}
+ * @return the minimum API version the method is supported for, or 1 or -1 if
+ * it's unknown.
+ */
+ public int getCallVersion(
+ @NonNull String owner,
+ @NonNull String name,
+ @NonNull String desc) {
+ //noinspection VariableNotUsedInsideIf
+ if (mData != null) {
+ int classNumber = findClass(owner);
+ if (classNumber != -1) {
+ int api = findMember(classNumber, name, desc);
+ if (api == -1) {
+ return getClassVersion(classNumber);
+ }
+ return api;
+ }
+ } else {
+ assert mInfo != null;
+ ApiClass clz = mInfo.getClass(owner);
+ if (clz != null) {
+ String signature = name + desc;
+ int since = clz.getMethod(signature, mInfo);
+ if (since == Integer.MAX_VALUE) {
+ since = -1;
+ }
+ return since;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the API version the given call was deprecated in, or -1 if the method
+ * is not deprecated.
+ *
+ * @param owner the internal name of the method's owner class, e.g. its
+ * fully qualified name (as returned by Class.getName(), but with
+ * '.' replaced by '/'.
+ * @param name the method's name
+ * @param desc the method's descriptor - see {@link org.objectweb.asm.Type}
+ * @return the API version the API was deprecated in, or 1 or -1 if
+ * it's unknown.
+ */
+ public int getCallDeprecatedIn(
+ @NonNull String owner,
+ @NonNull String name,
+ @NonNull String desc) {
+ //noinspection VariableNotUsedInsideIf
+ if (mData != null) {
+ int classNumber = findClass(owner);
+ if (classNumber != -1) {
+ int deprecatedIn = findMemberDeprecatedIn(classNumber, name, desc);
+ return deprecatedIn != 0 ? deprecatedIn : -1;
+ }
+ } else {
+ assert mInfo != null;
+ ApiClass clz = mInfo.getClass(owner);
+ if (clz != null) {
+ String signature = name + desc;
+ int deprecatedIn = clz.getMemberDeprecatedIn(signature, mInfo);
+ if (deprecatedIn == Integer.MAX_VALUE) {
+ deprecatedIn = -1;
+ }
+ return deprecatedIn;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the API version required to access the given field, or -1 if this
+ * is not a known API method. Note that it may return -1 for classes
+ * introduced in version 1; internally the database only stores version data
+ * for version 2 and up.
+ *
+ * @param owner the internal name of the method's owner class, e.g. its
+ * fully qualified name (as returned by Class.getName(), but with
+ * '.' replaced by '/'.
+ * @param name the method's name
+ * @return the minimum API version the method is supported for, or 1 or -1 if
+ * it's unknown.
+ */
+ public int getFieldVersion(
+ @NonNull String owner,
+ @NonNull String name) {
+ //noinspection VariableNotUsedInsideIf
+ if (mData != null) {
+ int classNumber = findClass(owner);
+ if (classNumber != -1) {
+ int api = findMember(classNumber, name, null);
+ if (api == -1) {
+ return getClassVersion(classNumber);
+ }
+ return api;
+ }
+ } else {
+ assert mInfo != null;
+ ApiClass clz = mInfo.getClass(owner);
+ if (clz != null) {
+ int since = clz.getField(name, mInfo);
+ if (since == Integer.MAX_VALUE) {
+ since = -1;
+ }
+ return since;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the API version the given field was deprecated in, or -1 if the field
+ * is not deprecated.
+ *
+ * @param owner the internal name of the method's owner class, e.g. its
+ * fully qualified name (as returned by Class.getName(), but with
+ * '.' replaced by '/'.
+ * @param name the method's name
+ * @return the API version the API was deprecated in, or 1 or -1 if
+ * it's unknown.
+ */
+ public int getFieldDeprecatedIn(
+ @NonNull String owner,
+ @NonNull String name) {
+ //noinspection VariableNotUsedInsideIf
+ if (mData != null) {
+ int classNumber = findClass(owner);
+ if (classNumber != -1) {
+ int deprecatedIn = findMemberDeprecatedIn(classNumber, name, null);
+ return deprecatedIn != 0 ? deprecatedIn : -1;
+ }
+ } else {
+ assert mInfo != null;
+ ApiClass clz = mInfo.getClass(owner);
+ if (clz != null) {
+ int deprecatedIn = clz.getMemberDeprecatedIn(name, mInfo);
+ if (deprecatedIn == Integer.MAX_VALUE) {
+ deprecatedIn = -1;
+ }
+ return deprecatedIn;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns true if the given owner (in VM format) is relevant to the database.
+ * This allows quick filtering out of owners that won't return any data
+ * for the various {@code #getFieldVersion} etc methods.
+ *
+ * @param owner the owner to look up
+ * @return true if the owner might be relevant to the API database
+ */
+ public static boolean isRelevantOwner(@NonNull String owner) {
+ if (owner.startsWith("java")) { //$NON-NLS-1$ // includes javax/
+ return true;
+ }
+ if (owner.startsWith(ANDROID_PKG)) {
+ return !owner.startsWith("/support/", 7);
+ } else if (owner.startsWith("org/")) { //$NON-NLS-1$
+ if (owner.startsWith("xml", 4) //$NON-NLS-1$
+ || owner.startsWith("w3c/", 4) //$NON-NLS-1$
+ || owner.startsWith("json/", 4) //$NON-NLS-1$
+ || owner.startsWith("apache/", 4)) { //$NON-NLS-1$
+ return true;
+ }
+ } else if (owner.startsWith("com/")) { //$NON-NLS-1$
+ if (owner.startsWith("google/", 4) //$NON-NLS-1$
+ || owner.startsWith("android/", 4)) { //$NON-NLS-1$
+ return true;
+ }
+ } else if (owner.startsWith("junit") //$NON-NLS-1$
+ || owner.startsWith("dalvik")) { //$NON-NLS-1$
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the given owner (in VM format) is a valid Java package supported
+ * in any version of Android.
+ *
+ * @param owner the package, in VM format
+ * @return true if the package is included in one or more versions of Android
+ */
+ public boolean isValidJavaPackage(@NonNull String owner) {
+ return findPackage(owner) != -1;
+ }
+
+ /** Returns the package index of the given class, or -1 if it is unknown */
+ private int findPackage(@NonNull String owner) {
+ assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner;
+
+ // The index array contains class indexes from 0 to classCount and
+ // member indices from classCount to mIndices.length.
+ int low = 0;
+ int high = mPackageCount - 1;
+ // Compare the api info at the given index.
+ int classNameLength = owner.lastIndexOf('/');
+ while (low <= high) {
+ int middle = (low + high) >>> 1;
+ int offset = mIndices[middle];
+
+ if (DEBUG_SEARCH) {
+ System.out.println("Comparing string " + owner.substring(0, classNameLength)
+ + " with entry at " + offset + ": " + dumpEntry(offset));
+ }
+
+ int compare = compare(mData, offset, (byte) 0, owner, 0, classNameLength);
+ if (compare == 0) {
+ if (DEBUG_SEARCH) {
+ System.out.println("Found " + dumpEntry(offset));
+ }
+ return middle;
+ }
+
+ if (compare < 0) {
+ low = middle + 1;
+ } else if (compare > 0) {
+ high = middle - 1;
+ } else {
+ assert false; // compare == 0 already handled above
+ return -1;
+ }
+ }
+
+ return -1;
+ }
+
+ private static int get4ByteInt(@NonNull byte[] data, int offset) {
+ byte b1 = data[offset++];
+ byte b2 = data[offset++];
+ byte b3 = data[offset++];
+ byte b4 = data[offset];
+ // The byte data is always big endian.
+ return (b1 & 0xFF) << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF);
+ }
+
+ private static void put3ByteInt(@NonNull ByteBuffer buffer, int value) {
+ // Big endian
+ byte b3 = (byte) (value & 0xFF);
+ value >>>= 8;
+ byte b2 = (byte) (value & 0xFF);
+ value >>>= 8;
+ byte b1 = (byte) (value & 0xFF);
+ buffer.put(b1);
+ buffer.put(b2);
+ buffer.put(b3);
+ }
+
+ private static void put2ByteInt(@NonNull ByteBuffer buffer, int value) {
+ // Big endian
+ byte b2 = (byte) (value & 0xFF);
+ value >>>= 8;
+ byte b1 = (byte) (value & 0xFF);
+ buffer.put(b1);
+ buffer.put(b2);
+ }
+
+ private static int get3ByteInt(@NonNull byte[] mData, int offset) {
+ byte b1 = mData[offset++];
+ byte b2 = mData[offset++];
+ byte b3 = mData[offset];
+ // The byte data is always big endian.
+ return (b1 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b3 & 0xFF);
+ }
+
+ private static int get2ByteInt(@NonNull byte[] data, int offset) {
+ byte b1 = data[offset++];
+ byte b2 = data[offset];
+ // The byte data is always big endian.
+ return (b1 & 0xFF) << 8 | (b2 & 0xFF);
+ }
+
+ /** Returns the class number of the given class, or -1 if it is unknown */
+ private int findClass(@NonNull String owner) {
+ assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner;
+
+ int packageNumber = findPackage(owner);
+ if (packageNumber == -1) {
+ return -1;
+ }
+ int curr = mIndices[packageNumber];
+ while (mData[curr] != 0) {
+ curr++;
+ }
+ curr++;
+
+ // 3 bytes for first offset
+ int low = get3ByteInt(mData, curr);
+ curr += 3;
+
+ int length = get2ByteInt(mData, curr);
+ if (length == 0) {
+ return -1;
+ }
+ int high = low + length - 1;
+ int index = owner.lastIndexOf('/');
+ int classNameLength = owner.length();
+ while (low <= high) {
+ int middle = (low + high) >>> 1;
+ int offset = mIndices[middle];
+ offset++; // skip the byte which points to the metadata after the name
+
+ if (DEBUG_SEARCH) {
+ System.out.println("Comparing string " + owner.substring(0, classNameLength)
+ + " with entry at " + offset + ": " + dumpEntry(offset));
+ }
+
+ int compare = compare(mData, offset, (byte) 0, owner, index + 1, classNameLength);
+ if (compare == 0) {
+ if (DEBUG_SEARCH) {
+ System.out.println("Found " + dumpEntry(offset));
+ }
+ return middle;
+ }
+
+ if (compare < 0) {
+ low = middle + 1;
+ } else if (compare > 0) {
+ high = middle - 1;
+ } else {
+ assert false; // compare == 0 already handled above
+ return -1;
+ }
+ }
+
+ return -1;
+ }
+
+ private int findMember(int classNumber, @NonNull String name, @Nullable String desc) {
+ return findMember(classNumber, name, desc, false);
+ }
+
+ private int findMemberDeprecatedIn(int classNumber, @NonNull String name,
+ @Nullable String desc) {
+ return findMember(classNumber, name, desc, true);
+ }
+
+ private int seekClassData(int classNumber, int field) {
+ int offset = mIndices[classNumber];
+ offset += mData[offset] & 0xFF;
+ if (field == CLASS_HEADER_MEMBER_OFFSETS) {
+ return offset;
+ }
+ offset += 5; // 3 bytes for start, 2 bytes for length
+ if (field == CLASS_HEADER_API) {
+ return offset;
+ }
+ boolean hasDeprecation = (mData[offset] & HAS_DEPRECATION_BYTE_FLAG) != 0;
+ offset++;
+ if (field == CLASS_HEADER_DEPRECATED) {
+ return hasDeprecation ? offset : -1;
+ } else if (hasDeprecation) {
+ offset++;
+ }
+ assert field == CLASS_HEADER_INTERFACES;
+ return offset;
+ }
+
+ private int findMember(int classNumber, @NonNull String name, @Nullable String desc,
+ boolean deprecation) {
+ int curr = seekClassData(classNumber, CLASS_HEADER_MEMBER_OFFSETS);
+
+ // 3 bytes for first offset
+ int low = get3ByteInt(mData, curr);
+ curr += 3;
+
+ int length = get2ByteInt(mData, curr);
+ if (length == 0) {
+ return -1;
+ }
+ int high = low + length - 1;
+
+ while (low <= high) {
+ int middle = (low + high) >>> 1;
+ int offset = mIndices[middle];
+
+ if (DEBUG_SEARCH) {
+ System.out.println("Comparing string " + (name + ';' + desc) +
+ " with entry at " + offset + ": " + dumpEntry(offset));
+ }
+
+ int compare;
+ if (desc != null) {
+ // Method
+ int nameLength = name.length();
+ compare = compare(mData, offset, (byte) '(', name, 0, nameLength);
+ if (compare == 0) {
+ offset += nameLength;
+ int argsEnd = desc.indexOf(')');
+ // Only compare up to the ) -- after that we have a return value in the
+ // input description, which isn't there in the database
+ compare = compare(mData, offset, (byte) ')', desc, 0, argsEnd);
+ if (compare == 0) {
+ if (DEBUG_SEARCH) {
+ System.out.println("Found " + dumpEntry(offset));
+ }
+
+ offset += argsEnd + 1;
+
+ if (mData[offset++] == 0) {
+ // Yes, terminated argument list: get the API level
+ int api = UnsignedBytes.toInt(mData[offset]);
+ if (deprecation) {
+ if ((api & HAS_DEPRECATION_BYTE_FLAG) != 0) {
+ return UnsignedBytes.toInt(mData[offset + 1]);
+ } else {
+ return -1;
+ }
+ } else {
+ return api & API_MASK;
+ }
+ }
+ }
+ }
+ } else {
+ // Field
+ int nameLength = name.length();
+ compare = compare(mData, offset, (byte) 0, name, 0, nameLength);
+ if (compare == 0) {
+ offset += nameLength;
+ if (mData[offset++] == 0) {
+ // Yes, terminated argument list: get the API level
+ int api = UnsignedBytes.toInt(mData[offset]);
+ if (deprecation) {
+ if ((api & HAS_DEPRECATION_BYTE_FLAG) != 0) {
+ return UnsignedBytes.toInt(mData[offset + 1]);
+ } else {
+ return -1;
+ }
+ } else {
+ return api & API_MASK;
+ }
+ }
+ }
+ }
+
+ if (compare < 0) {
+ low = middle + 1;
+ } else if (compare > 0) {
+ high = middle - 1;
+ } else {
+ assert false; // compare == 0 already handled above
+ return -1;
+ }
+ }
+
+ return -1;
+ }
+
+ /** Clears out any existing lookup instances */
+ @VisibleForTesting
+ static void dispose() {
+ sInstance.clear();
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiPackage.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiPackage.java
new file mode 100644
index 0000000..8c0e058
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiPackage.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Represents a package and its classes
+ */
+public class ApiPackage implements Comparable<ApiPackage> {
+ private final String mName;
+ private final List<ApiClass> mClasses = Lists.newArrayListWithExpectedSize(100);
+
+ // Persistence data: Used when writing out binary data in ApiLookup
+ int indexOffset; // offset of the package entry
+
+ ApiPackage(@NonNull String name) {
+ mName = name;
+ }
+
+ /**
+ * Returns the name of the class (fully qualified name)
+ * @return the name of the class
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the classes in this package
+ * @return the classes in this package
+ */
+ @NonNull
+ public List<ApiClass> getClasses() {
+ return mClasses;
+ }
+
+ void addClass(@NonNull ApiClass clz) {
+ mClasses.add(clz);
+ }
+
+ @Override
+ public int compareTo(@NonNull ApiPackage other) {
+ return mName.compareTo(other.mName);
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
new file mode 100644
index 0000000..1698961
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parser for the simplified XML API format version 1.
+ */
+public class ApiParser extends DefaultHandler {
+
+ private static final String NODE_API = "api";
+ private static final String NODE_CLASS = "class";
+ private static final String NODE_FIELD = "field";
+ private static final String NODE_METHOD = "method";
+ private static final String NODE_EXTENDS = "extends";
+ private static final String NODE_IMPLEMENTS = "implements";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_SINCE = "since";
+ private static final String ATTR_DEPRECATED = "deprecated";
+
+ private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>(1000);
+ private final Map<String, ApiPackage> mPackages = new HashMap<String, ApiPackage>();
+
+ private ApiClass mCurrentClass;
+
+ ApiParser() {
+ }
+
+ Map<String, ApiClass> getClasses() {
+ return mClasses;
+ }
+ Map<String, ApiPackage> getPackages() { return mPackages; }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+
+ if (localName == null || localName.isEmpty()) {
+ localName = qName;
+ }
+
+ try {
+ //noinspection StatementWithEmptyBody
+ if (NODE_API.equals(localName)) {
+ // do nothing.
+ } else if (NODE_CLASS.equals(localName)) {
+ String name = attributes.getValue(ATTR_NAME);
+ int since = Integer.parseInt(attributes.getValue(ATTR_SINCE));
+
+ String deprecatedAttr = attributes.getValue(ATTR_DEPRECATED);
+ int deprecatedIn;
+ if (deprecatedAttr != null) {
+ deprecatedIn = Integer.parseInt(deprecatedAttr);
+ } else {
+ deprecatedIn = 0;
+ }
+ mCurrentClass = addClass(name, since, deprecatedIn);
+
+ } else if (NODE_EXTENDS.equals(localName)) {
+ String name = attributes.getValue(ATTR_NAME);
+ int since = getSince(attributes);
+
+ mCurrentClass.addSuperClass(name, since);
+
+ } else if (NODE_IMPLEMENTS.equals(localName)) {
+ String name = attributes.getValue(ATTR_NAME);
+ int since = getSince(attributes);
+
+ mCurrentClass.addInterface(name, since);
+
+ } else if (NODE_METHOD.equals(localName)) {
+ String name = attributes.getValue(ATTR_NAME);
+ int since = getSince(attributes);
+ int deprecatedIn = getDeprecatedIn(attributes);
+ mCurrentClass.addMethod(name, since, deprecatedIn);
+
+ } else if (NODE_FIELD.equals(localName)) {
+ String name = attributes.getValue(ATTR_NAME);
+ int since = getSince(attributes);
+ int deprecatedIn = getDeprecatedIn(attributes);
+
+ mCurrentClass.addField(name, since, deprecatedIn);
+
+ }
+
+ } finally {
+ super.startElement(uri, localName, qName, attributes);
+ }
+ }
+
+ private ApiClass addClass(String name, int apiLevel, int deprecatedIn) {
+ // There should not be any duplicates
+ ApiClass theClass = mClasses.get(name);
+ assert theClass == null;
+ theClass = new ApiClass(name, apiLevel, deprecatedIn);
+ mClasses.put(name, theClass);
+
+ String pkg = theClass.getPackage();
+ if (pkg != null) {
+ ApiPackage apiPackage = mPackages.get(pkg);
+ if (apiPackage == null) {
+ apiPackage = new ApiPackage(pkg);
+ mPackages.put(pkg, apiPackage);
+ }
+ apiPackage.addClass(theClass);
+ }
+
+ return theClass;
+ }
+
+ private int getSince(Attributes attributes) {
+ int since = mCurrentClass.getSince();
+ String sinceAttr = attributes.getValue(ATTR_SINCE);
+
+ if (sinceAttr != null) {
+ since = Integer.parseInt(sinceAttr);
+ }
+
+ return since;
+ }
+
+ private int getDeprecatedIn(Attributes attributes) {
+ int deprecatedIn = mCurrentClass.getDeprecatedIn();
+ String deprecatedAttr = attributes.getValue(ATTR_DEPRECATED);
+
+ if (deprecatedAttr != null) {
+ deprecatedIn = Integer.parseInt(deprecatedAttr);
+ }
+
+ return deprecatedIn;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
new file mode 100644
index 0000000..91ed21f
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatResourceDetector.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_SHOW_AS_ACTION;
+
+import com.android.annotations.NonNull;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Check that the right namespace is used for app compat menu items
+ *
+ * Using app:showAsAction instead of android:showAsAction leads to problems, but
+ * isn't caught by the API Detector since it's not in the Android namespace.
+ */
+public class AppCompatResourceDetector extends ResourceXmlDetector implements JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "AppCompatResource", //$NON-NLS-1$
+ "Menu namespace",
+
+ "When using the appcompat library, menu resources should refer to the " +
+ "`showAsAction` in the `app:` namespace, not the `android:` namespace.\n" +
+ "\n" +
+ "Similarly, when *not* using the appcompat library, you should be using " +
+ "the `android:showAsAction` attribute.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.ERROR,
+ new Implementation(
+ AppCompatResourceDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Constructs a new {@link com.android.tools.lint.checks.AppCompatResourceDetector} */
+ public AppCompatResourceDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.MENU;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(ATTR_SHOW_AS_ACTION);
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ Project mainProject = context.getMainProject();
+ if (mainProject.isGradleProject()) {
+ Boolean appCompat = mainProject.dependsOn("com.android.support:appcompat-v7");
+ if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ if (context.getFolderVersion() >= 14) {
+ return;
+ }
+ if (appCompat == Boolean.TRUE) {
+ context.report(ISSUE, attribute,
+ context.getLocation(attribute),
+ "Should use `app:showAsAction` with the appcompat library with "
+ + "`xmlns:app=\"http://schemas.android.com/apk/res-auto\"`");
+ }
+ } else {
+ if (appCompat == Boolean.FALSE) {
+ context.report(ISSUE, attribute,
+ context.getLocation(attribute),
+ "Should use `android:showAsAction` when not using the appcompat library");
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java
new file mode 100644
index 0000000..bb9781c
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppIndexingApiDetector.java
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_EXPORTED;
+import static com.android.SdkConstants.ATTR_HOST;
+import static com.android.SdkConstants.ATTR_PATH;
+import static com.android.SdkConstants.ATTR_PATH_PREFIX;
+import static com.android.SdkConstants.ATTR_SCHEME;
+import static com.android.SdkConstants.CLASS_ACTIVITY;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_MIME_TYPE;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_PORT;
+import static com.android.xml.AndroidManifest.NODE_ACTION;
+import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
+import static com.android.xml.AndroidManifest.NODE_APPLICATION;
+import static com.android.xml.AndroidManifest.NODE_CATEGORY;
+import static com.android.xml.AndroidManifest.NODE_DATA;
+import static com.android.xml.AndroidManifest.NODE_INTENT;
+import static com.android.xml.AndroidManifest.NODE_MANIFEST;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.client.api.XmlParser;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodInvocation;
+
+
+/**
+ * Check if the usage of App Indexing is correct.
+ */
+public class AppIndexingApiDetector extends Detector
+ implements Detector.XmlScanner, Detector.JavaScanner {
+
+ private static final Implementation URL_IMPLEMENTATION = new Implementation(
+ AppIndexingApiDetector.class, Scope.MANIFEST_SCOPE);
+
+ private static final Implementation APP_INDEXING_API_IMPLEMENTATION =
+ new Implementation(
+ AppIndexingApiDetector.class,
+ EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST),
+ Scope.JAVA_FILE_SCOPE, Scope.MANIFEST_SCOPE);
+
+ public static final Issue ISSUE_URL_ERROR = Issue.create(
+ "GoogleAppIndexingUrlError", //$NON-NLS-1$
+ "URL not supported by app for Google App Indexing",
+ "Ensure the URL is supported by your app, to get installs and traffic to your"
+ + " app from Google Search.",
+ Category.USABILITY, 5, Severity.ERROR, URL_IMPLEMENTATION)
+ .addMoreInfo("https://g.co/AppIndexing/AndroidStudio");
+
+ public static final Issue ISSUE_APP_INDEXING =
+ Issue.create(
+ "GoogleAppIndexingWarning", //$NON-NLS-1$
+ "Missing support for Google App Indexing",
+ "Adds URLs to get your app into the Google index, to get installs"
+ + " and traffic to your app from Google Search.",
+ Category.USABILITY, 5, Severity.WARNING, URL_IMPLEMENTATION)
+ .addMoreInfo("https://g.co/AppIndexing/AndroidStudio");
+
+ public static final Issue ISSUE_APP_INDEXING_API =
+ Issue.create(
+ "GoogleAppIndexingApiWarning", //$NON-NLS-1$
+ "Missing support for Google App Indexing Api",
+ "Adds URLs to get your app into the Google index, to get installs"
+ + " and traffic to your app from Google Search.",
+ Category.USABILITY, 5, Severity.WARNING, APP_INDEXING_API_IMPLEMENTATION)
+ .addMoreInfo("https://g.co/AppIndexing/AndroidStudio")
+ .setEnabledByDefault(false);
+
+ private static final String[] PATH_ATTR_LIST = new String[]{ATTR_PATH_PREFIX, ATTR_PATH};
+ private static final String SCHEME_MISSING = "android:scheme is missing";
+ private static final String HOST_MISSING = "android:host is missing";
+ private static final String DATA_MISSING = "Missing data element";
+ private static final String URL_MISSING = "Missing URL for the intent filter";
+ private static final String NOT_BROWSABLE
+ = "Activity supporting ACTION_VIEW is not set as BROWSABLE";
+ private static final String ILLEGAL_NUMBER = "android:port is not a legal number";
+
+ private static final String APP_INDEX_START = "start"; //$NON-NLS-1$
+ private static final String APP_INDEX_END = "end"; //$NON-NLS-1$
+ private static final String APP_INDEX_VIEW = "view"; //$NON-NLS-1$
+ private static final String APP_INDEX_VIEW_END = "viewEnd"; //$NON-NLS-1$
+ private static final String CLIENT_CONNECT = "connect"; //$NON-NLS-1$
+ private static final String CLIENT_DISCONNECT = "disconnect"; //$NON-NLS-1$
+ private static final String ADD_API = "addApi"; //$NON-NLS-1$
+
+ private static final String APP_INDEXING_API_CLASS
+ = "com.google.android.gms.appindexing.AppIndexApi";
+ private static final String GOOGLE_API_CLIENT_CLASS
+ = "com.google.android.gms.common.api.GoogleApiClient";
+ private static final String GOOGLE_API_CLIENT_BUILDER_CLASS
+ = "com.google.android.gms.common.api.GoogleApiClient.Builder";
+ private static final String API_CLASS = "com.google.android.gms.appindexing.AppIndex";
+
+ public enum IssueType {
+ SCHEME_MISSING(AppIndexingApiDetector.SCHEME_MISSING),
+ HOST_MISSING(AppIndexingApiDetector.HOST_MISSING),
+ DATA_MISSING(AppIndexingApiDetector.DATA_MISSING),
+ URL_MISSING(AppIndexingApiDetector.URL_MISSING),
+ NOT_BROWSABLE(AppIndexingApiDetector.NOT_BROWSABLE),
+ ILLEGAL_NUMBER(AppIndexingApiDetector.ILLEGAL_NUMBER),
+ EMPTY_FIELD("cannot be empty"),
+ MISSING_SLASH("attribute should start with '/'"),
+ UNKNOWN("unknown error type");
+
+ private String message;
+
+ IssueType(String str) {
+ this.message = str;
+ }
+
+ public static IssueType parse(String str) {
+ for (IssueType type : IssueType.values()) {
+ if (str.contains(type.message)) {
+ return type;
+ }
+ }
+ return UNKNOWN;
+ }
+ }
+
+ // ---- Implements XmlScanner ----
+ @Override
+ @Nullable
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(NODE_APPLICATION);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element application) {
+ List<Element> activities = extractChildrenByName(application, NODE_ACTIVITY);
+ boolean applicationHasActionView = false;
+ for (Element activity : activities) {
+ List<Element> intents = extractChildrenByName(activity, NODE_INTENT);
+ boolean activityHasActionView = false;
+ for (Element intent : intents) {
+ boolean actionView = hasActionView(intent);
+ if (actionView) {
+ activityHasActionView = true;
+ }
+ visitIntent(context, intent);
+ }
+ if (activityHasActionView) {
+ applicationHasActionView = true;
+ if (activity.hasAttributeNS(ANDROID_URI, ATTR_EXPORTED)) {
+ Attr exported = activity.getAttributeNodeNS(ANDROID_URI, ATTR_EXPORTED);
+ if (!exported.getValue().equals("true")) {
+ // Report error if the activity supporting action view is not exported.
+ context.report(ISSUE_URL_ERROR, activity,
+ context.getLocation(activity),
+ "Activity supporting ACTION_VIEW is not exported");
+ }
+ }
+ }
+ }
+ if (!applicationHasActionView && !context.getProject().isLibrary()) {
+ // Report warning if there is no activity that supports action view.
+ context.report(ISSUE_APP_INDEXING, application, context.getLocation(application),
+ // This error message is more verbose than the other app indexing lint warnings, because it
+ // shows up on a blank project, and we want to make it obvious by just looking at the error
+ // message what this is
+ "App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW " +
+ "intent-filler. See issue explanation for more details.");
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Collections.singletonList(CLASS_ACTIVITY);
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext javaContext,
+ @Nullable ClassDeclaration node,
+ @NonNull lombok.ast.Node declarationOrAnonymous,
+ @NonNull JavaParser.ResolvedClass cls) {
+ if (node == null) {
+ return;
+ }
+
+ // In case linting the base class itself.
+ if (!cls.isInheritingFrom(CLASS_ACTIVITY, true)) {
+ return;
+ }
+
+ node.accept(new MethodVisitor(javaContext));
+ }
+
+ static class MethodVisitor extends ForwardingAstVisitor {
+ private final JavaContext mJavaContext;
+ private List<MethodInvocation> mStartMethods;
+ private List<MethodInvocation> mEndMethods;
+ private List<MethodInvocation> mConnectMethods;
+ private List<MethodInvocation> mDisconnectMethods;
+ private boolean mHasAddAppIndexApi;
+
+ MethodVisitor(JavaContext javaContext) {
+ mJavaContext = javaContext;
+ mStartMethods = Lists.newArrayListWithExpectedSize(2);
+ mEndMethods = Lists.newArrayListWithExpectedSize(2);
+ mConnectMethods = Lists.newArrayListWithExpectedSize(2);
+ mDisconnectMethods = Lists.newArrayListWithExpectedSize(2);
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ JavaParser.ResolvedNode resolved = mJavaContext.resolve(node);
+ if (!(resolved instanceof JavaParser.ResolvedMethod)) {
+ return super.visitMethodInvocation(node);
+ }
+ JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
+ String methodName = node.astName().astValue();
+
+ if (methodName.equals(APP_INDEX_START)) {
+ if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+ mStartMethods.add(node);
+ }
+ } else if (methodName.equals(APP_INDEX_END)) {
+ if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+ mEndMethods.add(node);
+ }
+ } else if (methodName.equals(APP_INDEX_VIEW)) {
+ if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+ mStartMethods.add(node);
+ }
+ } else if (methodName.equals(APP_INDEX_VIEW_END)) {
+ if (method.getContainingClass().getName().equals(APP_INDEXING_API_CLASS)) {
+ mEndMethods.add(node);
+ }
+ } else if (methodName.equals(CLIENT_CONNECT)) {
+ if (method.getContainingClass().getName().equals(GOOGLE_API_CLIENT_CLASS)) {
+ mConnectMethods.add(node);
+ }
+ } else if (methodName.equals(CLIENT_DISCONNECT)) {
+ if (method.getContainingClass().getName().equals(GOOGLE_API_CLIENT_CLASS)) {
+ mDisconnectMethods.add(node);
+ }
+ } else if (methodName.equals(ADD_API)) {
+ if (method.getContainingClass().getName().equals(GOOGLE_API_CLIENT_BUILDER_CLASS)) {
+ JavaParser.ResolvedNode arg0 = mJavaContext
+ .resolve(node.astArguments().first());
+ if (arg0 instanceof JavaParser.ResolvedField) {
+ JavaParser.ResolvedField resolvedArg0 = (JavaParser.ResolvedField) arg0;
+ JavaParser.ResolvedClass cls = resolvedArg0.getContainingClass();
+ if (cls != null && cls.getName().equals(API_CLASS)) {
+ mHasAddAppIndexApi = true;
+ }
+ }
+ }
+ }
+ return super.visitMethodInvocation(node);
+ }
+
+ @Override
+ public void endVisit(lombok.ast.Node root){
+ if (!(root instanceof ClassDeclaration)) {
+ return;
+ }
+ ClassDeclaration node = (ClassDeclaration)root;
+ JavaParser.ResolvedNode resolvedNode = mJavaContext.resolve(node);
+ if (resolvedNode == null || !(resolvedNode instanceof JavaParser.ResolvedClass)) {
+ return;
+ }
+
+ if (!((JavaParser.ResolvedClass)resolvedNode).isInheritingFrom(CLASS_ACTIVITY, true)) {
+ return;
+ }
+
+ // finds the activity classes that need app activity annotation
+ Set<String> activitiesToCheck = getActivitiesToCheck(mJavaContext);
+
+ // app indexing API used but no support in manifest
+ boolean hasIntent = activitiesToCheck.contains(resolvedNode.getName());
+ if (!hasIntent) {
+ for (MethodInvocation method : mStartMethods) {
+ mJavaContext.report(ISSUE_APP_INDEXING_API, method,
+ mJavaContext.getLocation(method.astName()),
+ "Missing support for Google App Indexing in the manifest");
+ }
+ for (MethodInvocation method : mEndMethods) {
+ mJavaContext.report(ISSUE_APP_INDEXING_API, method,
+ mJavaContext.getLocation(method.astName()),
+ "Missing support for Google App Indexing in the manifest");
+ }
+ return;
+ }
+
+ // `AppIndex.AppIndexApi.start / end / view / viewEnd` should exist
+ if (mStartMethods.isEmpty() && mEndMethods.isEmpty()) {
+ mJavaContext.report(ISSUE_APP_INDEXING_API, node,
+ mJavaContext.getLocation(node.astName()),
+ "Missing support for Google App Indexing API");
+ return;
+ }
+
+ for (MethodInvocation startNode : mStartMethods) {
+ Expression startClient = startNode.astArguments().first();
+
+ // GoogleApiClient should `addApi(AppIndex.APP_INDEX_API)`
+ if (!mHasAddAppIndexApi) {
+ String message = String.format(
+ "GoogleApiClient `%1$s` has not added support for App Indexing API",
+ startClient.toString());
+ mJavaContext.report(ISSUE_APP_INDEXING_API, startClient,
+ mJavaContext.getLocation(startClient), message);
+ }
+
+ // GoogleApiClient `connect` should exist
+ if (!hasOperand(startClient, mConnectMethods)) {
+ String message = String
+ .format("GoogleApiClient `%1$s` is not connected", startClient.toString());
+ mJavaContext.report(ISSUE_APP_INDEXING_API, startClient,
+ mJavaContext.getLocation(startClient), message);
+ }
+
+ // `AppIndex.AppIndexApi.end` should pair with `AppIndex.AppIndexApi.start`
+ if (!hasFirstArgument(startClient, mEndMethods)) {
+ mJavaContext.report(ISSUE_APP_INDEXING_API, startNode,
+ mJavaContext.getLocation(startNode.astName()),
+ "Missing corresponding `AppIndex.AppIndexApi.end` method");
+ }
+ }
+
+ for (MethodInvocation endNode : mEndMethods) {
+ Expression endClient = endNode.astArguments().first();
+
+ // GoogleApiClient should `addApi(AppIndex.APP_INDEX_API)`
+ if (!mHasAddAppIndexApi) {
+ String message = String.format(
+ "GoogleApiClient `%1$s` has not added support for App Indexing API",
+ endClient.toString());
+ mJavaContext.report(ISSUE_APP_INDEXING_API, endClient,
+ mJavaContext.getLocation(endClient), message);
+ }
+
+ // GoogleApiClient `disconnect` should exist
+ if (!hasOperand(endClient, mDisconnectMethods)) {
+ String message = String.format("GoogleApiClient `%1$s`"
+ + " is not disconnected", endClient.toString());
+ mJavaContext.report(ISSUE_APP_INDEXING_API, endClient,
+ mJavaContext.getLocation(endClient), message);
+ }
+
+ // `AppIndex.AppIndexApi.start` should pair with `AppIndex.AppIndexApi.end`
+ if (!hasFirstArgument(endClient, mStartMethods)) {
+ mJavaContext.report(ISSUE_APP_INDEXING_API, endNode,
+ mJavaContext.getLocation(endNode.astName()),
+ "Missing corresponding `AppIndex.AppIndexApi.start` method");
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets names of activities which needs app indexing. i.e. the activities have data tag in their
+ * intent filters.
+ * TODO: Cache the activities to speed up batch lint.
+ *
+ * @param context The context to check in.
+ */
+ private static Set<String> getActivitiesToCheck(Context context) {
+ Set<String> activitiesToCheck = Sets.newHashSet();
+ List<File> manifestFiles = context.getProject().getManifestFiles();
+ XmlParser xmlParser = context.getDriver().getClient().getXmlParser();
+ if (xmlParser != null) {
+ // TODO: Avoid visit all manifest files before enable this check by default.
+ for (File manifest : manifestFiles) {
+ XmlContext xmlContext =
+ new XmlContext(context.getDriver(), context.getProject(),
+ null, manifest, null, xmlParser);
+ Document doc = xmlParser.parseXml(xmlContext);
+ if (doc != null) {
+ List<Element> children = LintUtils.getChildren(doc);
+ for (Element child : children) {
+ if (child.getNodeName().equals(NODE_MANIFEST)) {
+ List<Element> apps = extractChildrenByName(child, NODE_APPLICATION);
+ for (Element app : apps) {
+ List<Element> acts = extractChildrenByName(app, NODE_ACTIVITY);
+ for (Element act : acts) {
+ List<Element> intents = extractChildrenByName(act, NODE_INTENT);
+ for (Element intent : intents) {
+ List<Element> data = extractChildrenByName(intent,
+ NODE_DATA);
+ if (!data.isEmpty() && act.hasAttributeNS(
+ ANDROID_URI, ATTRIBUTE_NAME)) {
+ Attr attr = act.getAttributeNodeNS(
+ ANDROID_URI, ATTRIBUTE_NAME);
+ String activityName = attr.getValue();
+ int dotIndex = activityName.indexOf('.');
+ if (dotIndex <= 0) {
+ String pkg = context.getMainProject().getPackage();
+ if (pkg != null) {
+ if (dotIndex == 0) {
+ activityName = pkg + activityName;
+ }
+ else {
+ activityName = pkg + '.' + activityName;
+ }
+ }
+ }
+ activitiesToCheck.add(activityName);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return activitiesToCheck;
+ }
+
+ private static void visitIntent(@NonNull XmlContext context, @NonNull Element intent) {
+ boolean actionView = hasActionView(intent);
+ boolean browsable = isBrowsable(intent);
+ boolean isHttp = false;
+ boolean hasScheme = false;
+ boolean hasHost = false;
+ boolean hasPort = false;
+ boolean hasPath = false;
+ boolean hasMimeType = false;
+ Element firstData = null;
+ List<Element> children = extractChildrenByName(intent, NODE_DATA);
+ for (Element data : children) {
+ if (firstData == null) {
+ firstData = data;
+ }
+ if (isHttpSchema(data)) {
+ isHttp = true;
+ }
+ checkSingleData(context, data);
+
+ for (String name : PATH_ATTR_LIST) {
+ if (data.hasAttributeNS(ANDROID_URI, name)) {
+ hasPath = true;
+ }
+ }
+
+ if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) {
+ hasScheme = true;
+ }
+
+ if (data.hasAttributeNS(ANDROID_URI, ATTR_HOST)) {
+ hasHost = true;
+ }
+
+ if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) {
+ hasPort = true;
+ }
+
+ if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_MIME_TYPE)) {
+ hasMimeType = true;
+ }
+ }
+
+ // In data field, a URL is consisted by
+ // <scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]
+ // Each part of the URL should not have illegal character.
+ if ((hasPath || hasHost || hasPort) && !hasScheme) {
+ context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData),
+ SCHEME_MISSING);
+ }
+
+ if ((hasPath || hasPort) && !hasHost) {
+ context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData),
+ HOST_MISSING);
+ }
+
+ if (actionView && browsable) {
+ if (firstData == null) {
+ // If this activity is an ACTION_VIEW action with category BROWSABLE, but doesn't
+ // have data node, it may be a mistake and we will report error.
+ context.report(ISSUE_URL_ERROR, intent, context.getLocation(intent),
+ DATA_MISSING);
+ } else if (!hasScheme && !hasMimeType) {
+ // If this activity is an action view, is browsable, but has neither a
+ // URL nor mimeType, it may be a mistake and we will report error.
+ context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData),
+ URL_MISSING);
+ }
+ }
+
+ // If this activity is an ACTION_VIEW action, has a http URL but doesn't have
+ // BROWSABLE, it may be a mistake and and we will report warning.
+ if (actionView && isHttp && !browsable) {
+ context.report(ISSUE_APP_INDEXING, intent, context.getLocation(intent),
+ NOT_BROWSABLE);
+ }
+
+ if (actionView && !hasScheme) {
+ context.report(ISSUE_APP_INDEXING, intent, context.getLocation(intent),
+ "Missing URL");
+ }
+ }
+
+ /**
+ * Check if the intent filter supports action view.
+ *
+ * @param intent the intent filter
+ * @return true if it does
+ */
+ private static boolean hasActionView(@NonNull Element intent) {
+ List<Element> children = extractChildrenByName(intent, NODE_ACTION);
+ for (Element action : children) {
+ if (action.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
+ Attr attr = action.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
+ if (attr.getValue().equals("android.intent.action.VIEW")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if the intent filter is browsable.
+ *
+ * @param intent the intent filter
+ * @return true if it does
+ */
+ private static boolean isBrowsable(@NonNull Element intent) {
+ List<Element> children = extractChildrenByName(intent, NODE_CATEGORY);
+ for (Element e : children) {
+ if (e.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) {
+ Attr attr = e.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME);
+ if (attr.getNodeValue().equals("android.intent.category.BROWSABLE")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if the data node contains http schema
+ *
+ * @param data the data node
+ * @return true if it does
+ */
+ private static boolean isHttpSchema(@NonNull Element data) {
+ if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) {
+ String value = data.getAttributeNodeNS(ANDROID_URI, ATTR_SCHEME).getValue();
+ if (value.equalsIgnoreCase("http") || value.equalsIgnoreCase("https")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void checkSingleData(@NonNull XmlContext context, @NonNull Element data) {
+ // path, pathPrefix and pathPattern should starts with /.
+ for (String name : PATH_ATTR_LIST) {
+ if (data.hasAttributeNS(ANDROID_URI, name)) {
+ Attr attr = data.getAttributeNodeNS(ANDROID_URI, name);
+ String path = replaceUrlWithValue(context, attr.getValue());
+ if (!path.startsWith("/") && !path.startsWith(SdkConstants.PREFIX_RESOURCE_REF)) {
+ context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr),
+ "android:" + name + " attribute should start with '/', but it is : "
+ + path);
+ }
+ }
+ }
+
+ // port should be a legal number.
+ if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) {
+ Attr attr = data.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_PORT);
+ try {
+ String port = replaceUrlWithValue(context, attr.getValue());
+ Integer.parseInt(port);
+ } catch (NumberFormatException e) {
+ context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr),
+ ILLEGAL_NUMBER);
+ }
+ }
+
+ // Each field should be non empty.
+ NamedNodeMap attrs = data.getAttributes();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Node item = attrs.item(i);
+ if (item.getNodeType() == Node.ATTRIBUTE_NODE) {
+ Attr attr = (Attr) attrs.item(i);
+ if (attr.getValue().isEmpty()) {
+ context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr),
+ attr.getName() + " cannot be empty");
+ }
+ }
+ }
+ }
+
+ private static String replaceUrlWithValue(@NonNull XmlContext context,
+ @NonNull String str) {
+ Project project = context.getProject();
+ LintClient client = context.getClient();
+ if (!client.supportsProjectResources()) {
+ return str;
+ }
+ ResourceUrl style = ResourceUrl.parse(str);
+ if (style == null || style.type != ResourceType.STRING || style.framework) {
+ return str;
+ }
+ AbstractResourceRepository resources = client.getProjectResources(project, true);
+ if (resources == null) {
+ return str;
+ }
+ List<ResourceItem> items = resources.getResourceItem(ResourceType.STRING, style.name);
+ if (items == null || items.isEmpty()) {
+ return str;
+ }
+ ResourceValue resourceValue = items.get(0).getResourceValue(false);
+ if (resourceValue == null) {
+ return str;
+ }
+ return resourceValue.getValue() == null ? str : resourceValue.getValue();
+ }
+
+ /**
+ * If a method with a certain argument exists in the list of methods.
+ *
+ * @param argument The first argument of the method.
+ * @param list The methods list.
+ * @return If such a method exists in the list.
+ */
+ private static boolean hasFirstArgument(Expression argument, List<MethodInvocation> list) {
+ for (MethodInvocation method : list) {
+ Expression argument1 = method.astArguments().first();
+ if (argument.toString().equals(argument1.toString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If a method with a certain operand exists in the list of methods.
+ *
+ * @param operand The operand of the method.
+ * @param list The methods list.
+ * @return If such a method exists in the list.
+ */
+ private static boolean hasOperand(Expression operand, List<MethodInvocation> list) {
+ for (MethodInvocation method : list) {
+ Expression operand1 = method.astOperand();
+ if (operand.toString().equals(operand1.toString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static List<Element> extractChildrenByName(@NonNull Element node,
+ @NonNull String name) {
+ List<Element> result = Lists.newArrayList();
+ List<Element> children = LintUtils.getChildren(node);
+ for (Element child : children) {
+ if (child.getNodeName().equals(name)) {
+ result.add(child);
+ }
+ }
+ return result;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetector.java
new file mode 100644
index 0000000..7e29756
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetector.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_HOST;
+import static com.android.SdkConstants.ATTR_SCHEME;
+import static com.android.SdkConstants.UTF_8;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
+import static com.android.xml.AndroidManifest.NODE_ACTION;
+import static com.android.xml.AndroidManifest.NODE_CATEGORY;
+import static com.android.xml.AndroidManifest.NODE_DATA;
+import static com.android.xml.AndroidManifest.NODE_INTENT;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Check if the App Link which needs auto verification is correctly set.
+ */
+public class AppLinksAutoVerifyDetector extends Detector implements Detector.XmlScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ AppLinksAutoVerifyDetector.class, Scope.MANIFEST_SCOPE);
+
+ public static final Issue ISSUE_ERROR = Issue.create(
+ "AppLinksAutoVerifyError", //$NON-NLS-1$
+ "App Links Auto Verification Failure",
+ "Ensures that app links are correctly set and associated with website.",
+ Category.CORRECTNESS, 5, Severity.ERROR, IMPLEMENTATION)
+ .addMoreInfo("https://g.co/appindexing/applinks").setEnabledByDefault(false);
+
+ public static final Issue ISSUE_WARNING = Issue.create(
+ "AppLinksAutoVerifyWarning", //$NON-NLS-1$
+ "Potential App Links Auto Verification Failure",
+ "Ensures that app links are correctly set and associated with website.",
+ Category.CORRECTNESS, 5, Severity.WARNING, IMPLEMENTATION)
+ .addMoreInfo("https://g.co/appindexing/applinks").setEnabledByDefault(false);
+
+ private static final String ATTRIBUTE_AUTO_VERIFY = "autoVerify";
+ private static final String JSON_RELATIVE_PATH = "/.well-known/assetlinks.json";
+
+ @VisibleForTesting
+ static final int STATUS_HTTP_CONNECT_FAIL = -1;
+ @VisibleForTesting
+ static final int STATUS_MALFORMED_URL = -2;
+ @VisibleForTesting
+ static final int STATUS_UNKNOWN_HOST = -3;
+ @VisibleForTesting
+ static final int STATUS_NOT_FOUND = -4;
+ @VisibleForTesting
+ static final int STATUS_WRONG_JSON_SYNTAX = -5;
+ @VisibleForTesting
+ static final int STATUS_JSON_PARSE_FAIL = -6;
+ @VisibleForTesting
+ static final int STATUS_HTTP_OK = 200;
+
+ /* Maps website host url to a future task which will send HTTP request to fetch the JSON file
+ * and also return the status code during the fetching process. */
+ private Map<String, Future<HttpResult>> mFutures = Maps.newHashMap();
+
+ /* Maps website host url to host attribute in AndroidManifest.xml. */
+ private Map<String, Attr> mJsonHost = Maps.newHashMap();
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+
+ // This check sends http request. Only done in batch mode.
+ if (!context.getScope().contains(Scope.ALL_JAVA_FILES)) {
+ return;
+ }
+
+ if (document.getDocumentElement() != null) {
+ List<Element> intents = getTags(document.getDocumentElement(), NODE_INTENT);
+ if (!needAutoVerification(intents)) {
+ return;
+ }
+
+ for (Element intent : intents) {
+ boolean actionView = hasNamedSubTag(intent, NODE_ACTION,
+ "android.intent.action.VIEW");
+ boolean browsableCategory = hasNamedSubTag(intent, NODE_CATEGORY,
+ "android.intent.category.BROWSABLE");
+ if (!actionView || !browsableCategory) {
+ continue;
+ }
+ mJsonHost.putAll(getJsonUrl(intent));
+ }
+ }
+
+ Map<String, HttpResult> results = getJsonFileAsync();
+
+ String packageName = context.getProject().getPackage();
+ for (Map.Entry<String, HttpResult> result : results.entrySet()) {
+ if (result.getValue() == null) {
+ continue;
+ }
+ Attr host = mJsonHost.get(result.getKey());
+ String jsonPath = result.getKey() + JSON_RELATIVE_PATH;
+ switch (result.getValue().mStatus) {
+ case STATUS_HTTP_OK:
+ List<String> packageNames = getPackageNameFromJson(
+ result.getValue().mJsonFile);
+ if (!packageNames.contains(packageName)) {
+ context.report(ISSUE_ERROR, host, context.getLocation(host), String.format(
+ "This host does not support app links to your app. Checks the Digital Asset Links JSON file: %s",
+ jsonPath));
+ }
+ break;
+ case STATUS_HTTP_CONNECT_FAIL:
+ context.report(ISSUE_WARNING, host, context.getLocation(host),
+ String.format("Connection to Digital Asset Links JSON file %s fails",
+ jsonPath));
+ break;
+ case STATUS_MALFORMED_URL:
+ context.report(ISSUE_ERROR, host, context.getLocation(host), String.format(
+ "Malformed URL of Digital Asset Links JSON file: %s. An unknown protocol is specified",
+ jsonPath));
+ break;
+ case STATUS_UNKNOWN_HOST:
+ context.report(ISSUE_WARNING, host, context.getLocation(host), String.format(
+ "Unknown host: %s. Check if the host exists, and check your network connection",
+ result.getKey()));
+ break;
+ case STATUS_NOT_FOUND:
+ context.report(ISSUE_ERROR, host, context.getLocation(host), String.format(
+ "Digital Asset Links JSON file %s is not found on the host", jsonPath));
+ break;
+ case STATUS_WRONG_JSON_SYNTAX:
+ context.report(ISSUE_ERROR, host, context.getLocation(host),
+ String.format("%s has incorrect JSON syntax", jsonPath));
+ break;
+ case STATUS_JSON_PARSE_FAIL:
+ context.report(ISSUE_ERROR, host, context.getLocation(host),
+ String.format("Parsing JSON file %s fails", jsonPath));
+ break;
+ default:
+ context.report(ISSUE_WARNING, host, context.getLocation(host), String.format(
+ "HTTP request for Digital Asset Links JSON file %1$s fails. HTTP response code: %2$s",
+ jsonPath, result.getValue().mStatus));
+ }
+ }
+ }
+
+ /**
+ * Gets all the tag elements with a specific tag name, within a parent tag element.
+ *
+ * @param element The parent tag element.
+ * @return List of tag elements found.
+ */
+ @NonNull
+ private static List<Element> getTags(@NonNull Element element, @NonNull String tagName) {
+ List<Element> tagList = Lists.newArrayList();
+ if (element.getTagName().equalsIgnoreCase(tagName)) {
+ tagList.add(element);
+ } else {
+ NodeList children = element.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ Node child = children.item(i);
+ if (child instanceof Element) {
+ tagList.addAll(getTags((Element) child, tagName));
+ }
+ }
+ }
+ return tagList;
+ }
+
+ /**
+ * Checks if auto verification is needed. i.e. any intent tag element's autoVerify attribute is
+ * set to true.
+ *
+ * @param intents The intent tag elements.
+ * @return true if auto verification is needed.
+ */
+ private static boolean needAutoVerification(@NonNull List<Element> intents) {
+ for (Element intent : intents) {
+ if (intent.getAttributeNS(ANDROID_URI, ATTRIBUTE_AUTO_VERIFY).equals(SdkConstants.VALUE_TRUE)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the element has a sub tag with specific name and specific name attribute.
+ *
+ * @param element The tag element.
+ * @param tagName The name of the sub tag.
+ * @param nameAttrValue The value of the name attribute.
+ * @return If the element has such a sub tag.
+ */
+ private static boolean hasNamedSubTag(@NonNull Element element, @NonNull String tagName,
+ @NonNull String nameAttrValue) {
+ NodeList children = element.getElementsByTagName(tagName);
+ for (int i = 0; i < children.getLength(); i++) {
+ Element e = (Element) children.item(i);
+ if (e.getAttributeNS(ANDROID_URI, ATTRIBUTE_NAME).equals(nameAttrValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the urls of all the host from which Digital Asset Links JSON files will be fetched.
+ *
+ * @param intent The intent tag element.
+ * @return List of JSON file urls.
+ */
+ @NonNull
+ private static Map<String, Attr> getJsonUrl(@NonNull Element intent) {
+ List<String> schemes = Lists.newArrayList();
+ List<Attr> hosts = Lists.newArrayList();
+ NodeList dataTags = intent.getElementsByTagName(NODE_DATA);
+ for (int k = 0; k < dataTags.getLength(); k++) {
+ Element dataTag = (Element) dataTags.item(k);
+ String scheme = dataTag.getAttributeNS(ANDROID_URI, ATTR_SCHEME);
+ if (scheme.equals("http") || scheme.equals("https")) {
+ schemes.add(scheme);
+ }
+ if (dataTag.hasAttributeNS(ANDROID_URI, ATTR_HOST)) {
+ hosts.add(dataTag.getAttributeNodeNS(ANDROID_URI, ATTR_HOST));
+ }
+ }
+ Map<String, Attr> urls = Maps.newHashMap();
+ for (String scheme : schemes) {
+ for (Attr host : hosts) {
+ urls.put(scheme + "://" + host.getValue(), host);
+ }
+ }
+ return urls;
+ }
+
+ /* Normally null. Used for testing. */
+ @Nullable
+ @VisibleForTesting
+ static Map<String, HttpResult> sMockData;
+
+ /**
+ * Gets all the Digital Asset Links JSON file asynchronously.
+ *
+ * @return The map between the host url and the HTTP result.
+ */
+ private Map<String, HttpResult> getJsonFileAsync() {
+ if (sMockData != null) {
+ return sMockData;
+ }
+
+ ExecutorService executorService = Executors.newCachedThreadPool();
+ for (final Map.Entry<String, Attr> url : mJsonHost.entrySet()) {
+ Future<HttpResult> future = executorService.submit(new Callable<HttpResult>() {
+ @Override
+ public HttpResult call() {
+ return getJson(url.getKey() + JSON_RELATIVE_PATH);
+ }
+ });
+ mFutures.put(url.getKey(), future);
+ }
+ executorService.shutdown();
+
+ Map<String, HttpResult> jsons = Maps.newHashMap();
+ for (Map.Entry<String, Future<HttpResult>> future : mFutures.entrySet()) {
+ try {
+ jsons.put(future.getKey(), future.getValue().get());
+ } catch (Exception e) {
+ jsons.put(future.getKey(), null);
+ }
+ }
+ return jsons;
+ }
+
+ /**
+ * Gets the Digital Asset Links JSON file on the website host.
+ *
+ * @param url The URL of the host on which JSON file will be fetched.
+ */
+ @NonNull
+ private static HttpResult getJson(@NonNull String url) {
+ try {
+ URL urlObj = new URL(url);
+ HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
+ if (connection == null) {
+ return new HttpResult(STATUS_HTTP_CONNECT_FAIL, null);
+ }
+ try {
+ InputStream inputStream = connection.getInputStream();
+ if (inputStream == null) {
+ return new HttpResult(connection.getResponseCode(), null);
+ }
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(inputStream, UTF_8));
+ try {
+ String line;
+ StringBuilder response = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ response.append(line);
+ response.append('\n');
+ }
+
+ try {
+ JsonElement jsonFile = new JsonParser().parse(response.toString());
+ return new HttpResult(connection.getResponseCode(), jsonFile);
+ } catch (JsonSyntaxException e) {
+ return new HttpResult(STATUS_WRONG_JSON_SYNTAX, null);
+ } catch (RuntimeException e) {
+ return new HttpResult(STATUS_JSON_PARSE_FAIL, null);
+ }
+ } finally {
+ reader.close();
+ }
+ } finally {
+ connection.disconnect();
+ }
+ } catch (MalformedURLException e) {
+ return new HttpResult(STATUS_MALFORMED_URL, null);
+ } catch (UnknownHostException e) {
+ return new HttpResult(STATUS_UNKNOWN_HOST, null);
+ } catch (FileNotFoundException e) {
+ return new HttpResult(STATUS_NOT_FOUND, null);
+ } catch (IOException e) {
+ return new HttpResult(STATUS_HTTP_CONNECT_FAIL, null);
+ }
+ }
+
+ /**
+ * Gets the package names of all the apps from the Digital Asset Links JSON file.
+ *
+ * @param element The JsonElement of the json file.
+ * @return All the package names.
+ */
+ private static List<String> getPackageNameFromJson(JsonElement element) {
+ List<String> packageNames = Lists.newArrayList();
+ if (element instanceof JsonArray) {
+ JsonArray jsonArray = (JsonArray) element;
+ for (int i = 0; i < jsonArray.size(); i++) {
+ JsonElement app = jsonArray.get(i);
+ if (app instanceof JsonObject) {
+ JsonObject target = ((JsonObject) app).getAsJsonObject("target");
+ if (target != null) {
+ // Checks namespace to ensure it is an app statement.
+ JsonElement namespace = target.get("namespace");
+ JsonElement packageName = target.get("package_name");
+ if (namespace != null && namespace.getAsString().equals("android_app")
+ && packageName != null) {
+ packageNames.add(packageName.getAsString());
+ }
+ }
+ }
+ }
+ }
+ return packageNames;
+ }
+
+ /* For storing the result of getting Digital Asset Links Json File */
+ @VisibleForTesting
+ static final class HttpResult {
+
+ /* HTTP response code or others errors related to HTTP connection, JSON file parsing. */
+ private int mStatus;
+ private JsonElement mJsonFile;
+
+ @VisibleForTesting
+ HttpResult(int status, JsonElement jsonFile) {
+ mStatus = status;
+ mJsonFile = jsonFile;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AssertDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AssertDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AssertDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AssertDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BadHostnameVerifierDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BadHostnameVerifierDetector.java
new file mode 100644
index 0000000..2128896
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BadHostnameVerifierDetector.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.BooleanLiteral;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.Return;
+import lombok.ast.Throw;
+
+public class BadHostnameVerifierDetector extends Detector implements JavaScanner {
+
+ @SuppressWarnings("unchecked")
+ private static final Implementation IMPLEMENTATION =
+ new Implementation(BadHostnameVerifierDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ public static final Issue ISSUE = Issue.create("BadHostnameVerifier",
+ "Insecure HostnameVerifier",
+ "This check looks for implementations of `HostnameVerifier` " +
+ "whose `verify` method always returns true (thus trusting any hostname) " +
+ "which could result in insecure network traffic caused by trusting arbitrary " +
+ "hostnames in TLS/SSL certificates presented by peers.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Collections.singletonList("javax.net.ssl.HostnameVerifier");
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+ @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+ NormalTypeBody body;
+ if (declarationOrAnonymous instanceof NormalTypeBody) {
+ body = (NormalTypeBody) declarationOrAnonymous;
+ } else if (node != null) {
+ body = node.astBody();
+ } else {
+ return;
+ }
+
+ for (Node member : body.astMembers()) {
+ if (member instanceof MethodDeclaration) {
+ MethodDeclaration declaration = (MethodDeclaration)member;
+ if ("verify".equals(declaration.astMethodName().astValue())
+ && declaration.astParameters().size() == 2) {
+ ResolvedNode resolved = context.resolve(declaration);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getArgumentCount() == 2
+ && method.getArgumentType(0).matchesName(TYPE_STRING)
+ && method.getArgumentType(1).matchesName("javax.net.ssl.SSLSession")) {
+
+ ComplexVisitor visitor = new ComplexVisitor(context);
+ declaration.accept(visitor);
+ if (visitor.isComplex()) {
+ return;
+ }
+
+ Location location = context.getNameLocation(declaration);
+ String message = String.format("`%1$s` always returns `true`, which " +
+ "could cause insecure network traffic due to trusting "
+ + "TLS/SSL server certificates for wrong hostnames",
+ method.getName());
+ context.report(ISSUE, location, message);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static class ComplexVisitor extends ForwardingAstVisitor {
+ @SuppressWarnings("unused")
+ private final JavaContext mContext;
+ private boolean mComplex;
+
+ public ComplexVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitThrow(Throw node) {
+ mComplex = true;
+ return super.visitThrow(node);
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ // TODO: Ignore certain known safe methods, e.g. Logging etc
+ mComplex = true;
+ return super.visitMethodInvocation(node);
+ }
+
+ @Override
+ public boolean visitReturn(Return node) {
+ Expression argument = node.astValue();
+ if (argument != null) {
+ // TODO: Only do this if certain that there isn't some intermediate
+ // assignment, as exposed by the unit test
+ //Object value = ConstantEvaluator.evaluate(mContext, argument);
+ //if (Boolean.TRUE.equals(value)) {
+ if (argument instanceof BooleanLiteral &&
+ Boolean.TRUE.equals(((BooleanLiteral)argument).astValue())) {
+ mComplex = false;
+ } else {
+ mComplex = true; // "return false" or some complicated logic
+ }
+ }
+ return super.visitReturn(node);
+ }
+
+ public boolean isComplex() {
+ return mComplex;
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
new file mode 100644
index 0000000..039b70a
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+/** Registry which provides a list of checks to be performed on an Android project */
+public class BuiltinIssueRegistry extends IssueRegistry {
+ private static final List<Issue> sIssues;
+
+ static final int INITIAL_CAPACITY = 253;
+
+ static {
+ List<Issue> issues = new ArrayList<Issue>(INITIAL_CAPACITY);
+
+ issues.add(AccessibilityDetector.ISSUE);
+ issues.add(AddJavascriptInterfaceDetector.ISSUE);
+ issues.add(AlarmDetector.ISSUE);
+ issues.add(AllowAllHostnameVerifierDetector.ISSUE);
+ issues.add(AlwaysShowActionDetector.ISSUE);
+ issues.add(AndroidAutoDetector.INVALID_USES_TAG_ISSUE);
+ issues.add(AndroidAutoDetector.MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH);
+ issues.add(AndroidAutoDetector.MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE);
+ issues.add(AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH);
+ issues.add(AnnotationDetector.FLAG_STYLE);
+ issues.add(AnnotationDetector.INSIDE_METHOD);
+ issues.add(AnnotationDetector.SWITCH_TYPE_DEF);
+ issues.add(AnnotationDetector.UNIQUE);
+ issues.add(ApiDetector.INLINED);
+ issues.add(ApiDetector.OVERRIDE);
+ issues.add(ApiDetector.UNSUPPORTED);
+ issues.add(ApiDetector.UNUSED);
+ issues.add(AppCompatCallDetector.ISSUE);
+ issues.add(AppCompatResourceDetector.ISSUE);
+ issues.add(AppIndexingApiDetector.ISSUE_APP_INDEXING_API);
+ issues.add(AppIndexingApiDetector.ISSUE_URL_ERROR);
+ issues.add(AppIndexingApiDetector.ISSUE_APP_INDEXING);
+ issues.add(AppLinksAutoVerifyDetector.ISSUE_ERROR);
+ issues.add(AppLinksAutoVerifyDetector.ISSUE_WARNING);
+ issues.add(ArraySizeDetector.INCONSISTENT);
+ issues.add(AssertDetector.ISSUE);
+ issues.add(BadHostnameVerifierDetector.ISSUE);
+ issues.add(ButtonDetector.BACK_BUTTON);
+ issues.add(ButtonDetector.CASE);
+ issues.add(ButtonDetector.ORDER);
+ issues.add(ButtonDetector.STYLE);
+ issues.add(ByteOrderMarkDetector.BOM);
+ issues.add(CallSuperDetector.ISSUE);
+ issues.add(ChildCountDetector.ADAPTER_VIEW_ISSUE);
+ issues.add(ChildCountDetector.SCROLLVIEW_ISSUE);
+ issues.add(CipherGetInstanceDetector.ISSUE);
+ issues.add(CleanupDetector.COMMIT_FRAGMENT);
+ issues.add(CleanupDetector.RECYCLE_RESOURCE);
+ issues.add(ClickableViewAccessibilityDetector.ISSUE);
+ issues.add(CommentDetector.EASTER_EGG);
+ issues.add(CommentDetector.STOP_SHIP);
+ issues.add(CustomViewDetector.ISSUE);
+ issues.add(CutPasteDetector.ISSUE);
+ issues.add(DateFormatDetector.DATE_FORMAT);
+ issues.add(SetTextDetector.SET_TEXT_I18N);
+ issues.add(DeprecationDetector.ISSUE);
+ issues.add(DetectMissingPrefix.MISSING_NAMESPACE);
+ issues.add(DosLineEndingDetector.ISSUE);
+ issues.add(DuplicateIdDetector.CROSS_LAYOUT);
+ issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
+ issues.add(DuplicateResourceDetector.ISSUE);
+ issues.add(DuplicateResourceDetector.TYPE_MISMATCH);
+ issues.add(UnsafeNativeCodeDetector.LOAD);
+ issues.add(UnsafeNativeCodeDetector.UNSAFE_NATIVE_CODE_LOCATION);
+ issues.add(ExtraTextDetector.ISSUE);
+ issues.add(FieldGetterDetector.ISSUE);
+ issues.add(FullBackupContentDetector.ISSUE);
+ issues.add(FragmentDetector.ISSUE);
+ issues.add(GetSignaturesDetector.ISSUE);
+ issues.add(GradleDetector.COMPATIBILITY);
+ issues.add(GradleDetector.GRADLE_PLUGIN_COMPATIBILITY);
+ issues.add(GradleDetector.DEPENDENCY);
+ issues.add(GradleDetector.DEPRECATED);
+ issues.add(GradleDetector.GRADLE_GETTER);
+ issues.add(GradleDetector.IDE_SUPPORT);
+ issues.add(GradleDetector.NOT_INTERPOLATED);
+ issues.add(GradleDetector.PATH);
+ issues.add(GradleDetector.PLUS);
+ issues.add(GradleDetector.STRING_INTEGER);
+ issues.add(GradleDetector.REMOTE_VERSION);
+ issues.add(GradleDetector.ACCIDENTAL_OCTAL);
+ issues.add(GridLayoutDetector.ISSUE);
+ issues.add(HandlerDetector.ISSUE);
+ issues.add(HardcodedDebugModeDetector.ISSUE);
+ issues.add(HardcodedValuesDetector.ISSUE);
+ issues.add(IconDetector.DUPLICATES_CONFIGURATIONS);
+ issues.add(IconDetector.DUPLICATES_NAMES);
+ issues.add(IconDetector.GIF_USAGE);
+ issues.add(IconDetector.ICON_COLORS);
+ issues.add(IconDetector.ICON_DENSITIES);
+ issues.add(IconDetector.ICON_DIP_SIZE);
+ issues.add(IconDetector.ICON_EXPECTED_SIZE);
+ issues.add(IconDetector.ICON_EXTENSION);
+ issues.add(IconDetector.ICON_LAUNCHER_SHAPE);
+ issues.add(IconDetector.ICON_LOCATION);
+ issues.add(IconDetector.ICON_MISSING_FOLDER);
+ issues.add(IconDetector.ICON_MIX_9PNG);
+ issues.add(IconDetector.ICON_NODPI);
+ issues.add(IconDetector.ICON_XML_AND_PNG);
+ issues.add(IncludeDetector.ISSUE);
+ issues.add(InefficientWeightDetector.BASELINE_WEIGHTS);
+ issues.add(InefficientWeightDetector.INEFFICIENT_WEIGHT);
+ issues.add(InefficientWeightDetector.NESTED_WEIGHTS);
+ issues.add(InefficientWeightDetector.ORIENTATION);
+ issues.add(InefficientWeightDetector.WRONG_0DP);
+ issues.add(TrustAllX509TrustManagerDetector.ISSUE);
+ issues.add(InvalidPackageDetector.ISSUE);
+ issues.add(JavaPerformanceDetector.PAINT_ALLOC);
+ issues.add(JavaPerformanceDetector.USE_SPARSE_ARRAY);
+ issues.add(JavaPerformanceDetector.USE_VALUE_OF);
+ issues.add(JavaScriptInterfaceDetector.ISSUE);
+ issues.add(LabelForDetector.ISSUE);
+ issues.add(LayoutConsistencyDetector.INCONSISTENT_IDS);
+ issues.add(LayoutInflationDetector.ISSUE);
+ issues.add(LocaleDetector.STRING_LOCALE);
+ issues.add(LocaleFolderDetector.DEPRECATED_CODE);
+ issues.add(LocaleFolderDetector.INVALID_FOLDER);
+ issues.add(LocaleFolderDetector.WRONG_REGION);
+ issues.add(LocaleFolderDetector.USE_ALPHA_2);
+ issues.add(LogDetector.CONDITIONAL);
+ issues.add(LogDetector.LONG_TAG);
+ issues.add(LogDetector.WRONG_TAG);
+ issues.add(ManifestDetector.ALLOW_BACKUP);
+ issues.add(ManifestDetector.APPLICATION_ICON);
+ issues.add(ManifestDetector.DEVICE_ADMIN);
+ issues.add(ManifestDetector.DUPLICATE_ACTIVITY);
+ issues.add(ManifestDetector.DUPLICATE_USES_FEATURE);
+ issues.add(ManifestDetector.GRADLE_OVERRIDES);
+ issues.add(ManifestDetector.ILLEGAL_REFERENCE);
+ issues.add(ManifestDetector.MIPMAP);
+ issues.add(ManifestDetector.MOCK_LOCATION);
+ issues.add(ManifestDetector.MULTIPLE_USES_SDK);
+ issues.add(ManifestDetector.ORDER);
+ issues.add(ManifestDetector.SET_VERSION);
+ issues.add(ManifestDetector.TARGET_NEWER);
+ issues.add(ManifestDetector.UNIQUE_PERMISSION);
+ issues.add(ManifestDetector.USES_SDK);
+ issues.add(ManifestDetector.WRONG_PARENT);
+ issues.add(ManifestResourceDetector.ISSUE);
+ issues.add(ManifestTypoDetector.ISSUE);
+ issues.add(MathDetector.ISSUE);
+ issues.add(MergeRootFrameLayoutDetector.ISSUE);
+ issues.add(MissingClassDetector.INNERCLASS);
+ issues.add(MissingClassDetector.INSTANTIATABLE);
+ issues.add(MissingClassDetector.MISSING);
+ issues.add(MissingIdDetector.ISSUE);
+ issues.add(NamespaceDetector.CUSTOM_VIEW);
+ issues.add(NamespaceDetector.RES_AUTO);
+ issues.add(NamespaceDetector.TYPO);
+ issues.add(NamespaceDetector.UNUSED);
+ issues.add(NegativeMarginDetector.ISSUE);
+ issues.add(NestedScrollingWidgetDetector.ISSUE);
+ issues.add(NfcTechListDetector.ISSUE);
+ issues.add(NonInternationalizedSmsDetector.ISSUE);
+ issues.add(ObsoleteLayoutParamsDetector.ISSUE);
+ issues.add(OnClickDetector.ISSUE);
+ issues.add(OverdrawDetector.ISSUE);
+ issues.add(OverrideDetector.ISSUE);
+ issues.add(OverrideConcreteDetector.ISSUE);
+ issues.add(ParcelDetector.ISSUE);
+ issues.add(PluralsDetector.EXTRA);
+ issues.add(PluralsDetector.MISSING);
+ issues.add(PluralsDetector.IMPLIED_QUANTITY);
+ issues.add(PreferenceActivityDetector.ISSUE);
+ issues.add(PrivateKeyDetector.ISSUE);
+ issues.add(PrivateResourceDetector.ISSUE);
+ issues.add(ProguardDetector.SPLIT_CONFIG);
+ issues.add(ProguardDetector.WRONG_KEEP);
+ issues.add(PropertyFileDetector.ESCAPE);
+ issues.add(PropertyFileDetector.HTTP);
+ issues.add(PxUsageDetector.DP_ISSUE);
+ issues.add(PxUsageDetector.IN_MM_ISSUE);
+ issues.add(PxUsageDetector.PX_ISSUE);
+ issues.add(PxUsageDetector.SMALL_SP_ISSUE);
+ issues.add(ReadParcelableDetector.ISSUE);
+ issues.add(RecyclerViewDetector.ISSUE);
+ issues.add(RegistrationDetector.ISSUE);
+ issues.add(RelativeOverlapDetector.ISSUE);
+ issues.add(RequiredAttributeDetector.ISSUE);
+ issues.add(ResourceCycleDetector.CRASH);
+ issues.add(ResourceCycleDetector.CYCLE);
+ issues.add(ResourcePrefixDetector.ISSUE);
+ issues.add(RestrictionsDetector.ISSUE);
+ issues.add(RtlDetector.COMPAT);
+ issues.add(RtlDetector.ENABLED);
+ issues.add(RtlDetector.SYMMETRY);
+ issues.add(RtlDetector.USE_START);
+ issues.add(ScrollViewChildDetector.ISSUE);
+ issues.add(SdCardDetector.ISSUE);
+ issues.add(SecureRandomDetector.ISSUE);
+ issues.add(SecureRandomGeneratorDetector.ISSUE);
+ issues.add(SecurityDetector.EXPORTED_PROVIDER);
+ issues.add(SecurityDetector.EXPORTED_RECEIVER);
+ issues.add(SecurityDetector.EXPORTED_SERVICE);
+ issues.add(SecurityDetector.SET_READABLE);
+ issues.add(SecurityDetector.SET_WRITABLE);
+ issues.add(SecurityDetector.OPEN_PROVIDER);
+ issues.add(SecurityDetector.WORLD_READABLE);
+ issues.add(SecurityDetector.WORLD_WRITEABLE);
+ issues.add(ServiceCastDetector.ISSUE);
+ issues.add(SetJavaScriptEnabledDetector.ISSUE);
+ issues.add(SharedPrefsDetector.ISSUE);
+ issues.add(SignatureOrSystemDetector.ISSUE);
+ issues.add(SQLiteDetector.ISSUE);
+ issues.add(SslCertificateSocketFactoryDetector.CREATE_SOCKET);
+ issues.add(SslCertificateSocketFactoryDetector.GET_INSECURE);
+ issues.add(StateListDetector.ISSUE);
+ issues.add(StringFormatDetector.ARG_COUNT);
+ issues.add(StringFormatDetector.ARG_TYPES);
+ issues.add(StringFormatDetector.INVALID);
+ issues.add(StringFormatDetector.POTENTIAL_PLURAL);
+ issues.add(SupportAnnotationDetector.CHECK_PERMISSION);
+ issues.add(SupportAnnotationDetector.CHECK_RESULT);
+ issues.add(SupportAnnotationDetector.COLOR_USAGE);
+ issues.add(SupportAnnotationDetector.MISSING_PERMISSION);
+ issues.add(SupportAnnotationDetector.RANGE);
+ issues.add(SupportAnnotationDetector.RESOURCE_TYPE);
+ issues.add(SupportAnnotationDetector.THREAD);
+ issues.add(SupportAnnotationDetector.TYPE_DEF);
+ issues.add(SystemPermissionsDetector.ISSUE);
+ issues.add(TextFieldDetector.ISSUE);
+ issues.add(TextViewDetector.ISSUE);
+ issues.add(TextViewDetector.SELECTABLE);
+ issues.add(TitleDetector.ISSUE);
+ issues.add(ToastDetector.ISSUE);
+ issues.add(TooManyViewsDetector.TOO_DEEP);
+ issues.add(TooManyViewsDetector.TOO_MANY);
+ issues.add(TranslationDetector.EXTRA);
+ issues.add(TranslationDetector.MISSING);
+ issues.add(AndroidTvDetector.MISSING_LEANBACK_LAUNCHER);
+ issues.add(AndroidTvDetector.MISSING_LEANBACK_SUPPORT);
+ issues.add(AndroidTvDetector.PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+ issues.add(AndroidTvDetector.UNSUPPORTED_TV_HARDWARE);
+ issues.add(AndroidTvDetector.MISSING_BANNER);
+ issues.add(TypoDetector.ISSUE);
+ issues.add(TypographyDetector.DASHES);
+ issues.add(TypographyDetector.ELLIPSIS);
+ issues.add(TypographyDetector.FRACTIONS);
+ issues.add(TypographyDetector.OTHER);
+ issues.add(TypographyDetector.QUOTES);
+ issues.add(UnsafeBroadcastReceiverDetector.ACTION_STRING);
+ issues.add(UnsafeBroadcastReceiverDetector.BROADCAST_SMS);
+ issues.add(UnusedResourceDetector.ISSUE);
+ issues.add(UnusedResourceDetector.ISSUE_IDS);
+ issues.add(UseCompoundDrawableDetector.ISSUE);
+ issues.add(UselessViewDetector.USELESS_LEAF);
+ issues.add(UselessViewDetector.USELESS_PARENT);
+ issues.add(Utf8Detector.ISSUE);
+ issues.add(VectorDetector.ISSUE);
+ issues.add(ViewConstructorDetector.ISSUE);
+ issues.add(ViewHolderDetector.ISSUE);
+ issues.add(ViewTagDetector.ISSUE);
+ issues.add(ViewTypeDetector.ISSUE);
+ issues.add(WakelockDetector.ISSUE);
+ issues.add(WebViewDetector.ISSUE);
+ issues.add(WrongCallDetector.ISSUE);
+ issues.add(WrongCaseDetector.WRONG_CASE);
+ issues.add(WrongIdDetector.INVALID);
+ issues.add(WrongIdDetector.NOT_SIBLING);
+ issues.add(WrongIdDetector.UNKNOWN_ID);
+ issues.add(WrongIdDetector.UNKNOWN_ID_LAYOUT);
+ issues.add(WrongImportDetector.ISSUE);
+ issues.add(WrongLocationDetector.ISSUE);
+
+ sIssues = Collections.unmodifiableList(issues);
+ }
+
+ /**
+ * Constructs a new {@link BuiltinIssueRegistry}
+ */
+ public BuiltinIssueRegistry() {
+ }
+
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return sIssues;
+ }
+
+ @Override
+ protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) {
+ if (scope.equals(Scope.ALL)) {
+ return getIssues().size();
+ } else {
+ int initialSize = 12;
+ if (scope.contains(Scope.RESOURCE_FILE)) {
+ initialSize += 75;
+ } else if (scope.contains(Scope.ALL_RESOURCE_FILES)) {
+ initialSize += 10;
+ }
+
+ if (scope.contains(Scope.JAVA_FILE)) {
+ initialSize += 72;
+ } else if (scope.contains(Scope.CLASS_FILE)) {
+ initialSize += 15;
+ } else if (scope.contains(Scope.MANIFEST)) {
+ initialSize += 37;
+ } else if (scope.contains(Scope.GRADLE_FILE)) {
+ initialSize += 5;
+ }
+ return initialSize;
+ }
+ }
+
+ /**
+ * Reset the registry such that it recomputes its available issues.
+ * <p>
+ * NOTE: This is only intended for testing purposes.
+ */
+ @VisibleForTesting
+ public static void reset() {
+ IssueRegistry.reset();
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
new file mode 100644
index 0000000..0b7c1e4
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_BACKGROUND;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_ORIENTATION;
+import static com.android.SdkConstants.ATTR_STYLE;
+import static com.android.SdkConstants.ATTR_TEXT;
+import static com.android.SdkConstants.BUTTON;
+import static com.android.SdkConstants.LINEAR_LAYOUT;
+import static com.android.SdkConstants.RELATIVE_LAYOUT;
+import static com.android.SdkConstants.STRING_PREFIX;
+import static com.android.SdkConstants.TABLE_ROW;
+import static com.android.SdkConstants.TAG_STRING;
+import static com.android.SdkConstants.VALUE_SELECTABLE_ITEM_BACKGROUND;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.SdkConstants.VALUE_VERTICAL;
+import static com.android.tools.lint.checks.RequiredAttributeDetector.PERCENT_RELATIVE_LAYOUT;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Check which looks at the order of buttons in dialogs and makes sure that
+ * "the dismissive action of a dialog is always on the left whereas the affirmative actions
+ * are on the right."
+ * <p>
+ * This only looks for the affirmative and dismissive actions named "OK" and "Cancel";
+ * "Cancel" usually works, but the affirmative action often has many other names -- "Done",
+ * "Send", "Go", etc.
+ * <p>
+ * TODO: Perhaps we should look for Yes/No dialogs and suggested they be rephrased as
+ * Cancel/OK dialogs? Similarly, consider "Abort" a synonym for "Cancel" ?
+ */
+public class ButtonDetector extends ResourceXmlDetector {
+ /** Name of cancel value ("Cancel") */
+ private static final String CANCEL_LABEL = "Cancel";
+ /** Name of OK value ("Cancel") */
+ private static final String OK_LABEL = "OK";
+ /** Name of Back value ("Back") */
+ private static final String BACK_LABEL = "Back";
+ /** Yes */
+ private static final String YES_LABEL = "Yes";
+ /** No */
+ private static final String NO_LABEL = "No";
+
+ /** Layout text attribute reference to {@code @android:string/ok} */
+ private static final String ANDROID_OK_RESOURCE =
+ ANDROID_STRING_PREFIX + "ok"; //$NON-NLS-1$
+ /** Layout text attribute reference to {@code @android:string/cancel} */
+ private static final String ANDROID_CANCEL_RESOURCE =
+ ANDROID_STRING_PREFIX + "cancel"; //$NON-NLS-1$
+ /** Layout text attribute reference to {@code @android:string/yes} */
+ private static final String ANDROID_YES_RESOURCE =
+ ANDROID_STRING_PREFIX + "yes"; //$NON-NLS-1$
+ /** Layout text attribute reference to {@code @android:string/no} */
+ private static final String ANDROID_NO_RESOURCE =
+ ANDROID_STRING_PREFIX + "no"; //$NON-NLS-1$
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ ButtonDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** The main issue discovered by this detector */
+ public static final Issue ORDER = Issue.create(
+ "ButtonOrder", //$NON-NLS-1$
+ "Button order",
+
+ "According to the Android Design Guide,\n" +
+ "\n" +
+ "\"Action buttons are typically Cancel and/or OK, with OK indicating the preferred " +
+ "or most likely action. However, if the options consist of specific actions such " +
+ "as Close or Wait rather than a confirmation or cancellation of the action " +
+ "described in the content, then all the buttons should be active verbs. As a rule, " +
+ "the dismissive action of a dialog is always on the left whereas the affirmative " +
+ "actions are on the right.\"\n" +
+ "\n" +
+ "This check looks for button bars and buttons which look like cancel buttons, " +
+ "and makes sure that these are on the left.",
+
+ Category.USABILITY,
+ 8,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo(
+ "http://developer.android.com/design/building-blocks/dialogs.html"); //$NON-NLS-1$
+
+ /** The main issue discovered by this detector */
+ public static final Issue STYLE = Issue.create(
+ "ButtonStyle", //$NON-NLS-1$
+ "Button should be borderless",
+
+ "Button bars typically use a borderless style for the buttons. Set the " +
+ "`style=\"?android:attr/buttonBarButtonStyle\"` attribute " +
+ "on each of the buttons, and set `style=\"?android:attr/buttonBarStyle\"` on " +
+ "the parent layout",
+
+ Category.USABILITY,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo(
+ "http://developer.android.com/design/building-blocks/buttons.html"); //$NON-NLS-1$
+
+ /** The main issue discovered by this detector */
+ public static final Issue BACK_BUTTON = Issue.create(
+ "BackButton", //$NON-NLS-1$
+ "Back button",
+ // TODO: Look for ">" as label suffixes as well
+
+ "According to the Android Design Guide,\n" +
+ "\n" +
+ "\"Other platforms use an explicit back button with label to allow the user " +
+ "to navigate up the application's hierarchy. Instead, Android uses the main " +
+ "action bar's app icon for hierarchical navigation and the navigation bar's " +
+ "back button for temporal navigation.\"" +
+ "\n" +
+ "This check is not very sophisticated (it just looks for buttons with the " +
+ "label \"Back\"), so it is disabled by default to not trigger on common " +
+ "scenarios like pairs of Back/Next buttons to paginate through screens.",
+
+ Category.USABILITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .setEnabledByDefault(false)
+ .addMoreInfo(
+ "http://developer.android.com/design/patterns/pure-android.html"); //$NON-NLS-1$
+
+ /** The main issue discovered by this detector */
+ public static final Issue CASE = Issue.create(
+ "ButtonCase", //$NON-NLS-1$
+ "Cancel/OK dialog button capitalization",
+
+ "The standard capitalization for OK/Cancel dialogs is \"OK\" and \"Cancel\". " +
+ "To ensure that your dialogs use the standard strings, you can use " +
+ "the resource strings @android:string/ok and @android:string/cancel.",
+
+ Category.USABILITY,
+ 2,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Set of resource names whose value was either OK or Cancel */
+ private Set<String> mApplicableResources;
+
+ /**
+ * Map of resource names we'd like resolved into strings in phase 2. The
+ * values should be filled in with the actual string contents.
+ */
+ private Map<String, String> mKeyToLabel;
+
+ /**
+ * Set of elements we've already warned about. If we've already complained
+ * about a cancel button, don't also report the OK button (since it's listed
+ * for the warnings on OK buttons).
+ */
+ private Set<Element> mIgnore;
+
+ /** Constructs a new {@link ButtonDetector} */
+ public ButtonDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(BUTTON, TAG_STRING);
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ int phase = context.getPhase();
+ if (phase == 1 && mApplicableResources != null) {
+ // We found resources for the string "Cancel"; perform a second pass
+ // where we check layout text attributes against these strings.
+ context.getDriver().requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
+ }
+ }
+
+ private static String stripLabel(String text) {
+ text = text.trim();
+ if (text.length() > 2
+ && (text.charAt(0) == '"' || text.charAt(0) == '\'')
+ && (text.charAt(0) == text.charAt(text.length() - 1))) {
+ text = text.substring(1, text.length() - 1);
+ }
+
+ return text;
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ // This detector works in two passes.
+ // In pass 1, it looks in layout files for hardcoded strings of "Cancel", or
+ // references to @string/cancel or @android:string/cancel.
+ // It also looks in values/ files for strings whose value is "Cancel",
+ // and if found, stores the corresponding keys in a map. (This is necessary
+ // since value files are processed after layout files).
+ // Then, if at the end of phase 1 any "Cancel" string resources were
+ // found in the value files, then it requests a *second* phase,
+ // where it looks only for <Button>'s whose text matches one of the
+ // cancel string resources.
+ int phase = context.getPhase();
+ String tagName = element.getTagName();
+ if (phase == 1 && tagName.equals(TAG_STRING)) {
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+ for (int j = 0, len = text.length(); j < len; j++) {
+ char c = text.charAt(j);
+ if (!Character.isWhitespace(c)) {
+ if (c == '"' || c == '\'') {
+ continue;
+ }
+ if (LintUtils.startsWith(text, CANCEL_LABEL, j)) {
+ String label = stripLabel(text);
+ if (label.equalsIgnoreCase(CANCEL_LABEL)) {
+ String name = element.getAttribute(ATTR_NAME);
+ foundResource(context, name, element);
+
+ if (!label.equals(CANCEL_LABEL)
+ && LintUtils.isEnglishResource(context, true)
+ && context.isEnabled(CASE)) {
+ assert label.trim().equalsIgnoreCase(CANCEL_LABEL) : label;
+ context.report(CASE, child, context.getLocation(child),
+ String.format(
+ "The standard Android way to capitalize %1$s " +
+ "is \"Cancel\" (tip: use `@android:string/cancel` instead)",
+ label));
+ }
+ }
+ } else if (LintUtils.startsWith(text, OK_LABEL, j)) {
+ String label = stripLabel(text);
+ if (label.equalsIgnoreCase(OK_LABEL)) {
+ String name = element.getAttribute(ATTR_NAME);
+ foundResource(context, name, element);
+
+ if (!label.equals(OK_LABEL)
+ && LintUtils.isEnglishResource(context, true)
+ && context.isEnabled(CASE)) {
+ assert label.trim().equalsIgnoreCase(OK_LABEL) : label;
+ context.report(CASE, child, context.getLocation(child),
+ String.format(
+ "The standard Android way to capitalize %1$s " +
+ "is \"OK\" (tip: use `@android:string/ok` instead)",
+ label));
+ }
+ }
+ } else if (LintUtils.startsWith(text, BACK_LABEL, j) &&
+ stripLabel(text).equalsIgnoreCase(BACK_LABEL)) {
+ String name = element.getAttribute(ATTR_NAME);
+ foundResource(context, name, element);
+ }
+ break;
+ }
+ }
+ }
+ }
+ } else if (tagName.equals(BUTTON)) {
+ if (phase == 1) {
+ if (isInButtonBar(element)
+ && !element.hasAttribute(ATTR_STYLE)
+ && !VALUE_SELECTABLE_ITEM_BACKGROUND.equals(
+ element.getAttributeNS(ANDROID_URI, ATTR_BACKGROUND))
+ && (context.getProject().getMinSdk() >= 11
+ || context.getFolderVersion() >= 11)
+ && context.isEnabled(STYLE)
+ && !parentDefinesSelectableItem(element)) {
+ context.report(STYLE, element, context.getLocation(element),
+ "Buttons in button bars should be borderless; use " +
+ "`style=\"?android:attr/buttonBarButtonStyle\"` (and " +
+ "`?android:attr/buttonBarStyle` on the parent)");
+ }
+ }
+
+ String text = element.getAttributeNS(ANDROID_URI, ATTR_TEXT);
+ if (phase == 2) {
+ if (mApplicableResources.contains(text)) {
+ String key = text;
+ if (key.startsWith(STRING_PREFIX)) {
+ key = key.substring(STRING_PREFIX.length());
+ }
+ String label = mKeyToLabel.get(key);
+ boolean isCancel = CANCEL_LABEL.equalsIgnoreCase(label);
+ if (isCancel) {
+ if (isWrongCancelPosition(element)) {
+ reportCancelPosition(context, element);
+ }
+ } else if (OK_LABEL.equalsIgnoreCase(label)) {
+ if (isWrongOkPosition(element)) {
+ reportOkPosition(context, element);
+ }
+ } else {
+ assert BACK_LABEL.equalsIgnoreCase(label) : label + ':' + context.file;
+ Location location = context.getLocation(element);
+ if (context.isEnabled(BACK_BUTTON)) {
+ context.report(BACK_BUTTON, element, location,
+ "Back buttons are not standard on Android; see design guide's " +
+ "navigation section");
+ }
+ }
+ }
+ } else if (text.equals(CANCEL_LABEL) || text.equals(ANDROID_CANCEL_RESOURCE)) {
+ if (isWrongCancelPosition(element)) {
+ reportCancelPosition(context, element);
+ }
+ } else if (text.equals(OK_LABEL) || text.equals(ANDROID_OK_RESOURCE)) {
+ if (isWrongOkPosition(element)) {
+ reportOkPosition(context, element);
+ }
+ } else {
+ boolean isYes = text.equals(ANDROID_YES_RESOURCE);
+ if (isYes || text.equals(ANDROID_NO_RESOURCE)) {
+ Attr attribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_TEXT);
+ Location location = context.getLocation(attribute);
+ String message = String.format("%1$s actually returns \"%2$s\", not \"%3$s\"; "
+ + "use %4$s instead or create a local string resource for %5$s",
+ text,
+ isYes ? OK_LABEL : CANCEL_LABEL,
+ isYes ? YES_LABEL : NO_LABEL,
+ isYes ? ANDROID_OK_RESOURCE : ANDROID_CANCEL_RESOURCE,
+ isYes ? YES_LABEL : NO_LABEL);
+ context.report(CASE, element, location, message);
+ }
+ }
+ }
+ }
+
+ private static boolean parentDefinesSelectableItem(Element element) {
+ String background = element.getAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
+ if (VALUE_SELECTABLE_ITEM_BACKGROUND.equals(background)) {
+ return true;
+ }
+
+ Node parent = element.getParentNode();
+ if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
+ return parentDefinesSelectableItem((Element) parent);
+ }
+
+ return false;
+ }
+
+ /** Report the given OK button as being in the wrong position */
+ private void reportOkPosition(XmlContext context, Element element) {
+ report(context, element, false /*isCancel*/);
+ }
+
+ /** Report the given Cancel button as being in the wrong position */
+ private void reportCancelPosition(XmlContext context, Element element) {
+ report(context, element, true /*isCancel*/);
+ }
+
+ /**
+ * We've found a resource reference to some label we're interested in ("OK",
+ * "Cancel", "Back", ...). Record the corresponding name such that in the
+ * next pass through the layouts we can check the context (for OK/Cancel the
+ * button order etc).
+ */
+ private void foundResource(XmlContext context, String name, Element element) {
+ if (!LintUtils.isEnglishResource(context, true)) {
+ return;
+ }
+
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ if (mApplicableResources == null) {
+ mApplicableResources = new HashSet<String>();
+ }
+
+ mApplicableResources.add(STRING_PREFIX + name);
+
+ // ALSO record all the other string resources in this file to pick up other
+ // labels. If you define "OK" in one resource file and "Cancel" in another
+ // this won't work, but that's probably not common and has lower overhead.
+ Node parentNode = element.getParentNode();
+
+ List<Element> items = LintUtils.getChildren(parentNode);
+ if (mKeyToLabel == null) {
+ mKeyToLabel = new HashMap<String, String>(items.size());
+ }
+ for (Element item : items) {
+ String itemName = item.getAttribute(ATTR_NAME);
+ NodeList childNodes = item.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ String text = stripLabel(child.getNodeValue());
+ if (!text.isEmpty()) {
+ mKeyToLabel.put(itemName, text);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /** Report the given OK/Cancel button as being in the wrong position */
+ private void report(XmlContext context, Element element, boolean isCancel) {
+ if (!context.isEnabled(ORDER)) {
+ return;
+ }
+
+ if (mIgnore != null && mIgnore.contains(element)) {
+ return;
+ }
+
+ int target = context.getProject().getTargetSdk();
+ if (target < 14) {
+ // If you're only targeting pre-ICS UI's, this is not an issue
+ return;
+ }
+
+ boolean mustCreateIcsLayout = false;
+ if (context.getProject().getMinSdk() < 14) {
+ // If you're *also* targeting pre-ICS UIs, then this reverse button
+ // order is correct for layouts intended for pre-ICS and incorrect for
+ // ICS layouts.
+ //
+ // Therefore, we need to know if this layout is an ICS layout or
+ // a pre-ICS layout.
+ boolean isIcsLayout = context.getFolderVersion() >= 14;
+ if (!isIcsLayout) {
+ // This layout is not an ICS layout. However, there *must* also be
+ // an ICS layout here, or this button order will be wrong:
+ File res = context.file.getParentFile().getParentFile();
+ File[] resFolders = res.listFiles();
+ String fileName = context.file.getName();
+ if (resFolders != null) {
+ for (File folder : resFolders) {
+ String folderName = folder.getName();
+ if (folderName.startsWith(SdkConstants.FD_RES_LAYOUT)
+ && folderName.contains("-v14")) { //$NON-NLS-1$
+ File layout = new File(folder, fileName);
+ if (layout.exists()) {
+ // Yes, a v14 specific layout is available so this pre-ICS
+ // layout order is not a problem
+ return;
+ }
+ }
+ }
+ }
+ mustCreateIcsLayout = true;
+ }
+ }
+
+ List<Element> buttons = LintUtils.getChildren(element.getParentNode());
+
+ if (mIgnore == null) {
+ mIgnore = new HashSet<Element>();
+ }
+ for (Element button : buttons) {
+ // Mark all the siblings in the ignore list to ensure that we don't
+ // report *both* the Cancel and the OK button in "OK | Cancel"
+ mIgnore.add(button);
+ }
+
+ String message;
+ if (isCancel) {
+ message = "Cancel button should be on the left";
+ } else {
+ message = "OK button should be on the right";
+ }
+
+ if (mustCreateIcsLayout) {
+ message = String.format(
+ "Layout uses the wrong button order for API >= 14: Create a " +
+ "`layout-v14/%1$s` file with opposite order: %2$s",
+ context.file.getName(), message);
+ }
+
+ // Show existing button order? We can only do that for LinearLayouts
+ // since in for example a RelativeLayout the order of the elements may
+ // not be the same as the visual order
+ String layout = element.getParentNode().getNodeName();
+ if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
+ List<String> labelList = getLabelList(buttons);
+ String wrong = describeButtons(labelList);
+ sortButtons(labelList);
+ String right = describeButtons(labelList);
+ message += String.format(" (was \"%1$s\", should be \"%2$s\")", wrong, right);
+ }
+
+ Location location = context.getLocation(element);
+ context.report(ORDER, element, location, message);
+ }
+
+ /**
+ * Sort a list of label buttons into the expected order (Cancel on the left,
+ * OK on the right
+ */
+ private static void sortButtons(List<String> labelList) {
+ for (int i = 0, n = labelList.size(); i < n; i++) {
+ String label = labelList.get(i);
+ if (label.equalsIgnoreCase(CANCEL_LABEL) && i > 0) {
+ swap(labelList, 0, i);
+ } else if (label.equalsIgnoreCase(OK_LABEL) && i < n - 1) {
+ swap(labelList, n - 1, i);
+ }
+ }
+ }
+
+ /** Swaps the strings at positions i and j */
+ private static void swap(List<String> strings, int i, int j) {
+ if (i != j) {
+ String temp = strings.get(i);
+ strings.set(i, strings.get(j));
+ strings.set(j, temp);
+ }
+ }
+
+ /** Creates a display string for a list of button labels, such as "Cancel | OK" */
+ private static String describeButtons(List<String> labelList) {
+ StringBuilder sb = new StringBuilder(80);
+ for (String label : labelList) {
+ if (sb.length() > 0) {
+ sb.append(" | "); //$NON-NLS-1$
+ }
+ sb.append(label);
+ }
+
+ return sb.toString();
+ }
+
+ /** Returns the ordered list of button labels */
+ private List<String> getLabelList(List<Element> views) {
+ List<String> labels = new ArrayList<String>();
+
+ if (mIgnore == null) {
+ mIgnore = new HashSet<Element>();
+ }
+
+ for (Element view : views) {
+ if (view.getTagName().equals(BUTTON)) {
+ String text = view.getAttributeNS(ANDROID_URI, ATTR_TEXT);
+ String label = getLabel(text);
+ labels.add(label);
+
+ // Mark all the siblings in the ignore list to ensure that we don't
+ // report *both* the Cancel and the OK button in "OK | Cancel"
+ mIgnore.add(view);
+ }
+ }
+
+ return labels;
+ }
+
+ private String getLabel(String key) {
+ String label = null;
+ if (key.startsWith(ANDROID_STRING_PREFIX)) {
+ if (key.equals(ANDROID_OK_RESOURCE)) {
+ label = OK_LABEL;
+ } else if (key.equals(ANDROID_CANCEL_RESOURCE)) {
+ label = CANCEL_LABEL;
+ }
+ } else if (mKeyToLabel != null) {
+ if (key.startsWith(STRING_PREFIX)) {
+ label = mKeyToLabel.get(key.substring(STRING_PREFIX.length()));
+ }
+ }
+
+ if (label == null) {
+ label = key;
+ }
+
+ if (label.indexOf(' ') != -1 && label.indexOf('"') == -1) {
+ label = '"' + label + '"';
+ }
+
+ return label;
+ }
+
+ /** Is the cancel button in the wrong position? It has to be on the left. */
+ private static boolean isWrongCancelPosition(Element element) {
+ return isWrongPosition(element, true /*isCancel*/);
+ }
+
+ /** Is the OK button in the wrong position? It has to be on the right. */
+ private static boolean isWrongOkPosition(Element element) {
+ return isWrongPosition(element, false /*isCancel*/);
+ }
+
+ private static boolean isInButtonBar(Element element) {
+ assert element.getTagName().equals(BUTTON) : element.getTagName();
+ Node parentNode = element.getParentNode();
+ if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
+ return false;
+ }
+ Element parent = (Element) parentNode;
+
+ String style = parent.getAttribute(ATTR_STYLE);
+ if (style != null && style.contains("buttonBarStyle")) { //$NON-NLS-1$
+ return true;
+ }
+
+ // Don't warn about single Cancel / OK buttons
+ if (LintUtils.getChildCount(parent) < 2) {
+ return false;
+ }
+
+ String layout = parent.getTagName();
+ if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
+ String orientation = parent.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
+ if (VALUE_VERTICAL.equals(orientation)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // Ensure that all the children are buttons
+ Node n = parent.getFirstChild();
+ while (n != null) {
+ if (n.getNodeType() == Node.ELEMENT_NODE) {
+ if (!BUTTON.equals(n.getNodeName())) {
+ return false;
+ }
+ }
+ n = n.getNextSibling();
+ }
+
+ return true;
+ }
+
+ /** Is the given button in the wrong position? */
+ private static boolean isWrongPosition(Element element, boolean isCancel) {
+ Node parentNode = element.getParentNode();
+ if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
+ return false;
+ }
+ Element parent = (Element) parentNode;
+
+ // Don't warn about single Cancel / OK buttons
+ if (LintUtils.getChildCount(parent) < 2) {
+ return false;
+ }
+
+ String layout = parent.getTagName();
+ if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
+ String orientation = parent.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
+ if (VALUE_VERTICAL.equals(orientation)) {
+ return false;
+ }
+
+ if (isCancel) {
+ Node n = element.getPreviousSibling();
+ while (n != null) {
+ if (n.getNodeType() == Node.ELEMENT_NODE) {
+ return true;
+ }
+ n = n.getPreviousSibling();
+ }
+ } else {
+ Node n = element.getNextSibling();
+ while (n != null) {
+ if (n.getNodeType() == Node.ELEMENT_NODE) {
+ return true;
+ }
+ n = n.getNextSibling();
+ }
+ }
+
+ return false;
+ } else if (layout.equals(RELATIVE_LAYOUT) || layout.equals(PERCENT_RELATIVE_LAYOUT)) {
+ // In RelativeLayouts, look for attachments which look like a clear sign
+ // that the OK or Cancel buttons are out of order:
+ // -- a left attachment on a Cancel button (where the left attachment
+ // is a button; we don't want to complain if it's pointing to a spacer
+ // or image or progress indicator etc)
+ // -- a right-side parent attachment on a Cancel button (unless it's also
+ // attached on the left, e.g. a cancel button stretching across the
+ // layout)
+ // etc.
+ if (isCancel) {
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_TO_RIGHT_OF)
+ && isButtonId(parent, element.getAttributeNS(ANDROID_URI,
+ ATTR_LAYOUT_TO_RIGHT_OF))) {
+ return true;
+ }
+ if (isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_RIGHT) &&
+ !isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_LEFT)) {
+ return true;
+ }
+ } else {
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_TO_LEFT_OF)
+ && isButtonId(parent, element.getAttributeNS(ANDROID_URI,
+ ATTR_LAYOUT_TO_RIGHT_OF))) {
+ return true;
+ }
+ if (isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_LEFT) &&
+ !isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) {
+ return true;
+ }
+ }
+
+ return false;
+ } else {
+ // TODO: Consider other button layouts - GridLayouts, custom views extending
+ // LinearLayout etc?
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the given attribute (in the Android namespace) is set to
+ * true on the given element
+ */
+ private static boolean isTrue(Element element, String attribute) {
+ return VALUE_TRUE.equals(element.getAttributeNS(ANDROID_URI, attribute));
+ }
+
+ /** Is the given target id the id of a {@code <Button>} within this RelativeLayout? */
+ private static boolean isButtonId(Element parent, String targetId) {
+ for (Element child : LintUtils.getChildren(parent)) {
+ String id = child.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (LintUtils.idReferencesMatch(id, targetId)) {
+ return child.getTagName().equals(BUTTON);
+ }
+ }
+ return false;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ByteOrderMarkDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ByteOrderMarkDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ByteOrderMarkDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ByteOrderMarkDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
new file mode 100644
index 0000000..42df6d9
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CLASS_VIEW;
+import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.filterRelevantAnnotations;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Super;
+
+/**
+ * Makes sure that methods call super when overriding methods.
+ */
+public class CallSuperDetector extends Detector implements Detector.JavaScanner {
+ private static final String CALL_SUPER_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "CallSuper"; //$NON-NLS-1$
+ private static final String ON_DETACHED_FROM_WINDOW = "onDetachedFromWindow"; //$NON-NLS-1$
+ private static final String ON_VISIBILITY_CHANGED = "onVisibilityChanged"; //$NON-NLS-1$
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ CallSuperDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Missing call to super */
+ public static final Issue ISSUE = Issue.create(
+ "MissingSuperCall", //$NON-NLS-1$
+ "Missing Super Call",
+
+ "Some methods, such as `View#onDetachedFromWindow`, require that you also " +
+ "call the super implementation as part of your method.",
+
+ Category.CORRECTNESS,
+ 9,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Constructs a new {@link CallSuperDetector} check */
+ public CallSuperDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends Node>>singletonList(MethodDeclaration.class);
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull final JavaContext context) {
+ return new ForwardingAstVisitor() {
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ checkCallSuper(context, node, method);
+ }
+
+ return false;
+ }
+ };
+ }
+
+ private static void checkCallSuper(@NonNull JavaContext context,
+ @NonNull MethodDeclaration declaration,
+ @NonNull ResolvedMethod method) {
+
+ ResolvedMethod superMethod = getRequiredSuperMethod(method);
+ if (superMethod != null) {
+ if (!SuperCallVisitor.callsSuper(context, declaration, superMethod)) {
+ String methodName = method.getName();
+ String message = "Overriding method should call `super."
+ + methodName + "`";
+ Location location = context.getNameLocation(declaration);
+ context.report(ISSUE, declaration, location, message);
+ }
+ }
+ }
+
+ /**
+ * Checks whether the given method overrides a method which requires the super method
+ * to be invoked, and if so, returns it (otherwise returns null)
+ */
+ @Nullable
+ private static ResolvedMethod getRequiredSuperMethod(
+ @NonNull ResolvedMethod method) {
+
+ String name = method.getName();
+ if (ON_DETACHED_FROM_WINDOW.equals(name)) {
+ // No longer annotated on the framework method since it's
+ // now handled via onDetachedFromWindowInternal, but overriding
+ // is still dangerous if supporting older versions so flag
+ // this for now (should make annotation carry metadata like
+ // compileSdkVersion >= N).
+ if (!method.getContainingClass().isSubclassOf(CLASS_VIEW, false)) {
+ return null;
+ }
+ return method.getSuperMethod();
+ } else if (ON_VISIBILITY_CHANGED.equals(name)) {
+ // From Android Wear API; doesn't yet have an annotation
+ // but we want to enforce this right away until the AAR
+ // is updated to supply it once @CallSuper is available in
+ // the support library
+ if (!method.getContainingClass().isSubclassOf(
+ "android.support.wearable.watchface.WatchFaceService.Engine", false)) {
+ return null;
+ }
+ return method.getSuperMethod();
+ }
+
+ // Look up annotations metadata
+ ResolvedMethod directSuper = method.getSuperMethod();
+ ResolvedMethod superMethod = directSuper;
+ while (superMethod != null) {
+ Iterable<ResolvedAnnotation> annotations = superMethod.getAnnotations();
+ annotations = filterRelevantAnnotations(annotations);
+ for (ResolvedAnnotation annotation : annotations) {
+ String signature = annotation.getSignature();
+ if (CALL_SUPER_ANNOTATION.equals(signature)) {
+ return directSuper;
+ } else if (signature.endsWith(".OverrideMustInvoke")) {
+ // Handle findbugs annotation on the fly too
+ return directSuper;
+ }
+ }
+ superMethod = superMethod.getSuperMethod();
+ }
+
+ return null;
+ }
+
+ /** Visits a method and determines whether the method calls its super method */
+ private static class SuperCallVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+ private final ResolvedMethod mMethod;
+ private boolean mCallsSuper;
+
+ public static boolean callsSuper(
+ @NonNull JavaContext context,
+ @NonNull MethodDeclaration methodDeclaration,
+ @NonNull ResolvedMethod method) {
+ SuperCallVisitor visitor = new SuperCallVisitor(context, method);
+ methodDeclaration.accept(visitor);
+ return visitor.mCallsSuper;
+ }
+
+ private SuperCallVisitor(@NonNull JavaContext context, @NonNull ResolvedMethod method) {
+ mContext = context;
+ mMethod = method;
+ }
+
+ @Override
+ public boolean visitSuper(Super node) {
+ ResolvedNode resolved = null;
+ if (node.getParent() instanceof MethodInvocation) {
+ resolved = mContext.resolve(node.getParent());
+ }
+ if (resolved == null) {
+ resolved = mContext.resolve(node);
+ }
+ if (mMethod.equals(resolved)) {
+ mCallsSuper = true;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitNode(Node node) {
+ return mCallsSuper || super.visitNode(node);
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
new file mode 100644
index 0000000..5eb91cd
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CipherGetInstanceDetector.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+
+/**
+ * Ensures that Cipher.getInstance is not called with AES as the parameter.
+ */
+public class CipherGetInstanceDetector extends Detector implements Detector.JavaScanner {
+ public static final Issue ISSUE = Issue.create(
+ "GetInstance", //$NON-NLS-1$
+ "Cipher.getInstance with ECB",
+ "`Cipher#getInstance` should not be called with ECB as the cipher mode or without "
+ + "setting the cipher mode because the default mode on android is ECB, which "
+ + "is insecure.",
+ Category.SECURITY,
+ 9,
+ Severity.WARNING,
+ new Implementation(
+ CipherGetInstanceDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ private static final String CIPHER = "javax.crypto.Cipher"; //$NON-NLS-1$
+ private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
+ private static final Set<String> ALGORITHM_ONLY =
+ Sets.newHashSet("AES", "DES", "DESede"); //$NON-NLS-1$
+ private static final String ECB = "ECB"; //$NON-NLS-1$
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList(GET_INSTANCE);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ // Ignore if the method doesn't fit our description.
+ ResolvedNode resolved = context.resolve(node);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return;
+ }
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (!method.getContainingClass().isSubclassOf(CIPHER, false)) {
+ return;
+ }
+ StrictListAccessor<Expression, MethodInvocation> argumentList = node.astArguments();
+ if (argumentList != null && argumentList.size() == 1) {
+ Expression expression = argumentList.first();
+ if (expression instanceof StringLiteral) {
+ StringLiteral argument = (StringLiteral)expression;
+ String parameter = argument.astValue();
+ checkParameter(context, node, argument, parameter, false);
+ } else {
+ ResolvedNode resolve = context.resolve(expression);
+ if (resolve instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) resolve;
+ Object value = field.getValue();
+ if (value instanceof String) {
+ checkParameter(context, node, expression, (String)value, true);
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkParameter(@NonNull JavaContext context,
+ @NonNull MethodInvocation call, @NonNull Node node, @NonNull String value,
+ boolean includeValue) {
+ if (ALGORITHM_ONLY.contains(value)) {
+ String message = "`Cipher.getInstance` should not be called without setting the"
+ + " encryption mode and padding";
+ context.report(ISSUE, call, context.getLocation(node), message);
+ } else if (value.contains(ECB)) {
+ String message = "ECB encryption mode should not be used";
+ if (includeValue) {
+ message += " (was \"" + value + "\")";
+ }
+ context.report(ISSUE, call, context.getLocation(node), message);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
new file mode 100644
index 0000000..ba32081
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CLASS_CONTEXT;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.Lists;
+
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Return;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Checks for missing {@code recycle} calls on resources that encourage it, and
+ * for missing {@code commit} calls on FragmentTransactions, etc.
+ */
+public class CleanupDetector extends Detector implements JavaScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ CleanupDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Problems with missing recycle calls */
+ public static final Issue RECYCLE_RESOURCE = Issue.create(
+ "Recycle", //$NON-NLS-1$
+ "Missing `recycle()` calls",
+
+ "Many resources, such as TypedArrays, VelocityTrackers, etc., " +
+ "should be recycled (with a `recycle()` call) after use. This lint check looks " +
+ "for missing `recycle()` calls.",
+
+ Category.PERFORMANCE,
+ 7,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Problems with missing commit calls. */
+ public static final Issue COMMIT_FRAGMENT = Issue.create(
+ "CommitTransaction", //$NON-NLS-1$
+ "Missing `commit()` calls",
+
+ "After creating a `FragmentTransaction`, you typically need to commit it as well",
+
+ Category.CORRECTNESS,
+ 7,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ // Target method names
+ private static final String RECYCLE = "recycle"; //$NON-NLS-1$
+ private static final String RELEASE = "release"; //$NON-NLS-1$
+ private static final String OBTAIN = "obtain"; //$NON-NLS-1$
+ private static final String SHOW = "show"; //$NON-NLS-1$
+ private static final String ACQUIRE_CPC = "acquireContentProviderClient"; //$NON-NLS-1$
+ private static final String OBTAIN_NO_HISTORY = "obtainNoHistory"; //$NON-NLS-1$
+ private static final String OBTAIN_ATTRIBUTES = "obtainAttributes"; //$NON-NLS-1$
+ private static final String OBTAIN_TYPED_ARRAY = "obtainTypedArray"; //$NON-NLS-1$
+ private static final String OBTAIN_STYLED_ATTRIBUTES = "obtainStyledAttributes"; //$NON-NLS-1$
+ private static final String BEGIN_TRANSACTION = "beginTransaction"; //$NON-NLS-1$
+ private static final String COMMIT = "commit"; //$NON-NLS-1$
+ private static final String COMMIT_ALLOWING_LOSS = "commitAllowingStateLoss"; //$NON-NLS-1$
+ private static final String QUERY = "query"; //$NON-NLS-1$
+ private static final String RAW_QUERY = "rawQuery"; //$NON-NLS-1$
+ private static final String QUERY_WITH_FACTORY = "queryWithFactory"; //$NON-NLS-1$
+ private static final String RAW_QUERY_WITH_FACTORY = "rawQueryWithFactory"; //$NON-NLS-1$
+ private static final String CLOSE = "close"; //$NON-NLS-1$
+
+ private static final String MOTION_EVENT_CLS = "android.view.MotionEvent"; //$NON-NLS-1$
+ private static final String PARCEL_CLS = "android.os.Parcel"; //$NON-NLS-1$
+ private static final String TYPED_ARRAY_CLS = "android.content.res.TypedArray"; //$NON-NLS-1$
+ private static final String VELOCITY_TRACKER_CLS = "android.view.VelocityTracker";//$NON-NLS-1$
+ private static final String DIALOG_FRAGMENT = "android.app.DialogFragment"; //$NON-NLS-1$
+ private static final String DIALOG_V4_FRAGMENT =
+ "android.support.v4.app.DialogFragment"; //$NON-NLS-1$
+ private static final String FRAGMENT_MANAGER_CLS = "android.app.FragmentManager"; //$NON-NLS-1$
+ private static final String FRAGMENT_MANAGER_V4_CLS =
+ "android.support.v4.app.FragmentManager"; //$NON-NLS-1$
+ private static final String FRAGMENT_TRANSACTION_CLS =
+ "android.app.FragmentTransaction"; //$NON-NLS-1$
+ private static final String FRAGMENT_TRANSACTION_V4_CLS =
+ "android.support.v4.app.FragmentTransaction"; //$NON-NLS-1$
+
+ public static final String SURFACE_CLS = "android.view.Surface";
+ public static final String SURFACE_TEXTURE_CLS = "android.graphics.SurfaceTexture";
+
+ public static final String CONTENT_PROVIDER_CLIENT_CLS
+ = "android.content.ContentProviderClient";
+
+ public static final String CONTENT_RESOLVER_CLS = "android.content.ContentResolver";
+ public static final String CONTENT_PROVIDER_CLS = "android.content.ContentProvider";
+ @SuppressWarnings("SpellCheckingInspection")
+ public static final String SQLITE_DATABASE_CLS = "android.database.sqlite.SQLiteDatabase";
+ public static final String CURSOR_CLS = "android.database.Cursor";
+
+ /** Constructs a new {@link CleanupDetector} */
+ public CleanupDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(
+ // FragmentManager commit check
+ BEGIN_TRANSACTION,
+
+ // Recycle check
+ OBTAIN, OBTAIN_NO_HISTORY,
+ OBTAIN_STYLED_ATTRIBUTES,
+ OBTAIN_ATTRIBUTES,
+ OBTAIN_TYPED_ARRAY,
+
+ // Release check
+ ACQUIRE_CPC,
+
+ // Cursor close check
+ QUERY, RAW_QUERY, QUERY_WITH_FACTORY, RAW_QUERY_WITH_FACTORY
+ );
+ }
+
+ @Nullable
+ @Override
+ public List<String> getApplicableConstructorTypes() {
+ return Arrays.asList(SURFACE_TEXTURE_CLS, SURFACE_CLS);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+
+ String name = node.astName().astValue();
+ if (BEGIN_TRANSACTION.equals(name)) {
+ checkTransactionCommits(context, node);
+ } else {
+ checkResourceRecycled(context, node, name);
+ }
+ }
+
+ @Override
+ public void visitConstructor(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull ConstructorInvocation node, @NonNull ResolvedMethod constructor) {
+ checkRecycled(context, node, constructor.getContainingClass().getSignature(), RELEASE);
+ }
+
+ private static void checkResourceRecycled(@NonNull JavaContext context,
+ @NonNull MethodInvocation node, @NonNull String name) {
+ // Recycle detector
+ ResolvedNode resolved = context.resolve(node);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return;
+ }
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ ResolvedClass containingClass = method.getContainingClass();
+ if ((OBTAIN.equals(name) || OBTAIN_NO_HISTORY.equals(name)) &&
+ containingClass.isSubclassOf(MOTION_EVENT_CLS, false)) {
+ checkRecycled(context, node, MOTION_EVENT_CLS, RECYCLE);
+ } else if (OBTAIN.equals(name) && containingClass.isSubclassOf(PARCEL_CLS, false)) {
+ checkRecycled(context, node, PARCEL_CLS, RECYCLE);
+ } else if (OBTAIN.equals(name) &&
+ containingClass.isSubclassOf(VELOCITY_TRACKER_CLS, false)) {
+ checkRecycled(context, node, VELOCITY_TRACKER_CLS, RECYCLE);
+ } else if ((OBTAIN_STYLED_ATTRIBUTES.equals(name)
+ || OBTAIN_ATTRIBUTES.equals(name)
+ || OBTAIN_TYPED_ARRAY.equals(name)) &&
+ (containingClass.isSubclassOf(CLASS_CONTEXT, false) ||
+ containingClass.isSubclassOf(SdkConstants.CLASS_RESOURCES, false))) {
+ TypeDescriptor returnType = method.getReturnType();
+ if (returnType != null && returnType.matchesSignature(TYPED_ARRAY_CLS)) {
+ checkRecycled(context, node, TYPED_ARRAY_CLS, RECYCLE);
+ }
+ } else if (ACQUIRE_CPC.equals(name) && containingClass.isSubclassOf(
+ CONTENT_RESOLVER_CLS, false)) {
+ checkRecycled(context, node, CONTENT_PROVIDER_CLIENT_CLS, RELEASE);
+ } else if ((QUERY.equals(name)
+ || RAW_QUERY.equals(name)
+ || QUERY_WITH_FACTORY.equals(name)
+ || RAW_QUERY_WITH_FACTORY.equals(name))
+ && (containingClass.isSubclassOf(SQLITE_DATABASE_CLS, false) ||
+ containingClass.isSubclassOf(CONTENT_RESOLVER_CLS, false) ||
+ containingClass.isSubclassOf(CONTENT_PROVIDER_CLS, false) ||
+ containingClass.isSubclassOf(CONTENT_PROVIDER_CLIENT_CLS, false))) {
+ // Other potential cursors-returning methods that should be tracked:
+ // android.app.DownloadManager#query
+ // android.content.ContentProviderClient#query
+ // android.content.ContentResolver#query
+ // android.database.sqlite.SQLiteQueryBuilder#query
+ // android.provider.Browser#getAllBookmarks
+ // android.provider.Browser#getAllVisitedUrls
+ // android.provider.DocumentsProvider#queryChildDocuments
+ // android.provider.DocumentsProvider#qqueryDocument
+ // android.provider.DocumentsProvider#queryRecentDocuments
+ // android.provider.DocumentsProvider#queryRoots
+ // android.provider.DocumentsProvider#querySearchDocuments
+ // android.provider.MediaStore$Images$Media#query
+ // android.widget.FilterQueryProvider#runQuery
+ checkRecycled(context, node, CURSOR_CLS, CLOSE);
+ }
+ }
+
+ private static void checkRecycled(@NonNull final JavaContext context, @NonNull Node node,
+ @NonNull final String recycleType, @NonNull final String recycleName) {
+ Node variableNode = getVariableNode(node);
+ if (variableNode == null) {
+ return;
+ }
+ ResolvedVariable boundVariable = getResolvedVariable(context, variableNode);
+ if (boundVariable == null) {
+ return;
+ }
+
+ Node method = JavaContext.findSurroundingMethod(node);
+ if (method == null) {
+ return;
+ }
+ FinishVisitor visitor = new FinishVisitor(context, variableNode, boundVariable) {
+ @Override
+ protected boolean isCleanupCall(@NonNull MethodInvocation call) {
+ String methodName = call.astName().astValue();
+ if (!recycleName.equals(methodName)) {
+ return false;
+ }
+ ResolvedNode resolved = mContext.resolve(call);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedClass containingClass = ((ResolvedMethod) resolved).getContainingClass();
+ if (containingClass.isSubclassOf(recycleType, false)) {
+ // Yes, called the right recycle() method; now make sure
+ // we're calling it on the right variable
+ Expression operand = call.astOperand();
+ if (operand != null) {
+ resolved = mContext.resolve(operand);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ };
+
+ method.accept(visitor);
+ if (visitor.isCleanedUp() || visitor.variableEscapes()) {
+ return;
+ }
+
+ String className = recycleType.substring(recycleType.lastIndexOf('.') + 1);
+ String message;
+ if (RECYCLE.equals(recycleName)) {
+ message = String.format(
+ "This `%1$s` should be recycled after use with `#recycle()`", className);
+ } else {
+ message = String.format(
+ "This `%1$s` should be freed up after use with `#%2$s()`", className,
+ recycleName);
+ }
+ Node locationNode = node instanceof MethodInvocation ?
+ ((MethodInvocation) node).astName() : node;
+ Location location = context.getLocation(locationNode);
+ context.report(RECYCLE_RESOURCE, node, location, message);
+ }
+
+ private static boolean checkTransactionCommits(@NonNull JavaContext context,
+ @NonNull MethodInvocation node) {
+ if (isBeginTransaction(context, node)) {
+ Node variableNode = getVariableNode(node);
+ ResolvedVariable boundVariable = variableNode != null
+ ? getResolvedVariable(context, variableNode) : null;
+ if (boundVariable == null && isCommittedInChainedCalls(context, node)) {
+ return true;
+ }
+
+ if (boundVariable != null) {
+ Node method = JavaContext.findSurroundingMethod(node);
+ if (method == null) {
+ return true;
+ }
+
+ FinishVisitor commitVisitor = new FinishVisitor(context, variableNode,
+ boundVariable) {
+ @Override
+ protected boolean isCleanupCall(@NonNull MethodInvocation call) {
+ if (isTransactionCommitMethodCall(mContext, call)) {
+ Expression operand = call.astOperand();
+ if (operand != null) {
+ ResolvedNode resolved = mContext.resolve(operand);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ return true;
+ } else if (resolved instanceof ResolvedMethod
+ && operand instanceof MethodInvocation
+ && isCommittedInChainedCalls(mContext,(MethodInvocation) operand)) {
+ // Check that the target of the committed chains is the
+ // right variable!
+ while (operand instanceof MethodInvocation) {
+ operand = ((MethodInvocation)operand).astOperand();
+ }
+ if (operand instanceof VariableReference) {
+ resolved = mContext.resolve(operand);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ return true;
+ }
+ }
+ }
+ }
+ } else if (isShowFragmentMethodCall(mContext, call)) {
+ StrictListAccessor<Expression, MethodInvocation> arguments =
+ call.astArguments();
+ if (arguments.size() == 2) {
+ Expression first = arguments.first();
+ ResolvedNode resolved = mContext.resolve(first);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ };
+
+ method.accept(commitVisitor);
+ if (commitVisitor.isCleanedUp() || commitVisitor.variableEscapes()) {
+ return true;
+ }
+ }
+
+ String message = "This transaction should be completed with a `commit()` call";
+ context.report(COMMIT_FRAGMENT, node, context.getLocation(node.astName()),
+ message);
+ }
+ return false;
+ }
+
+ private static boolean isCommittedInChainedCalls(@NonNull JavaContext context,
+ @NonNull MethodInvocation node) {
+ // Look for chained calls since the FragmentManager methods all return "this"
+ // to allow constructor chaining, e.g.
+ // getFragmentManager().beginTransaction().addToBackStack("test")
+ // .disallowAddToBackStack().hide(mFragment2).setBreadCrumbShortTitle("test")
+ // .show(mFragment2).setCustomAnimations(0, 0).commit();
+ Node parent = node.getParent();
+ while (parent instanceof MethodInvocation) {
+ MethodInvocation methodInvocation = (MethodInvocation) parent;
+ if (isTransactionCommitMethodCall(context, methodInvocation)
+ || isShowFragmentMethodCall(context, methodInvocation)) {
+ return true;
+ }
+
+ parent = parent.getParent();
+ }
+
+ return false;
+ }
+
+ private static boolean isTransactionCommitMethodCall(@NonNull JavaContext context,
+ @NonNull MethodInvocation call) {
+
+ String methodName = call.astName().astValue();
+ return (COMMIT.equals(methodName) || COMMIT_ALLOWING_LOSS.equals(methodName)) &&
+ isMethodOnFragmentClass(context, call,
+ FRAGMENT_TRANSACTION_CLS,
+ FRAGMENT_TRANSACTION_V4_CLS,
+ true);
+ }
+
+ private static boolean isShowFragmentMethodCall(@NonNull JavaContext context,
+ @NonNull MethodInvocation call) {
+ String methodName = call.astName().astValue();
+ return SHOW.equals(methodName)
+ && isMethodOnFragmentClass(context, call,
+ DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT, true);
+ }
+
+ private static boolean isMethodOnFragmentClass(
+ @NonNull JavaContext context,
+ @NonNull MethodInvocation call,
+ @NonNull String fragmentClass,
+ @NonNull String v4FragmentClass,
+ boolean returnForUnresolved) {
+ ResolvedNode resolved = context.resolve(call);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedClass containingClass = ((ResolvedMethod) resolved).getContainingClass();
+ return containingClass.isSubclassOf(fragmentClass, false) ||
+ containingClass.isSubclassOf(v4FragmentClass, false);
+ } else if (resolved == null) {
+ // If we *can't* resolve the method call, caller can decide
+ // whether to consider the method called or not
+ return returnForUnresolved;
+ }
+
+ return false;
+ }
+
+ @Nullable
+ public static Node getVariableNode(@NonNull Node expression) {
+ Node parent = expression.getParent();
+ if (parent instanceof BinaryExpression) {
+ BinaryExpression binaryExpression = (BinaryExpression) parent;
+ if (binaryExpression.astOperator() == BinaryOperator.ASSIGN) {
+ return binaryExpression.astLeft();
+ }
+ } else if (parent instanceof VariableDefinitionEntry) {
+ return parent;
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public static ResolvedVariable getResolvedVariable(@NonNull JavaContext context,
+ @NonNull Node variable) {
+ ResolvedNode resolved = context.resolve(variable);
+ if (resolved instanceof ResolvedVariable) {
+ return (ResolvedVariable) resolved;
+ }
+
+ return null;
+ }
+
+ private static boolean isBeginTransaction(@NonNull JavaContext context,
+ @NonNull MethodInvocation node) {
+ String methodName = node.astName().astValue();
+ assert methodName.equals(BEGIN_TRANSACTION) : methodName;
+ if (BEGIN_TRANSACTION.equals(methodName)) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ ResolvedClass containingClass = method.getContainingClass();
+ if (containingClass.isSubclassOf(FRAGMENT_MANAGER_CLS, false)
+ || containingClass.isSubclassOf(FRAGMENT_MANAGER_V4_CLS,
+ false)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Visitor which checks whether an operation is "finished"; in the case
+ * of a FragmentTransaction we're looking for a "commit" call; in the
+ * case of a TypedArray we're looking for a "recycle", call, in the
+ * case of a database cursor we're looking for a "close" call, etc.
+ */
+ private abstract static class FinishVisitor extends ForwardingAstVisitor {
+ protected final JavaContext mContext;
+ protected final List<ResolvedVariable> mVariables;
+ private final Node mOriginalVariableNode;
+
+ private boolean mContainsCleanup;
+ private boolean mEscapes;
+
+ public FinishVisitor(JavaContext context, @NonNull Node variableNode,
+ @NonNull ResolvedVariable variable) {
+ mContext = context;
+ mOriginalVariableNode = variableNode;
+ mVariables = Lists.newArrayList(variable);
+ }
+
+ public boolean isCleanedUp() {
+ return mContainsCleanup;
+ }
+
+ public boolean variableEscapes() {
+ return mEscapes;
+ }
+
+ @Override
+ public boolean visitNode(Node node) {
+ return mContainsCleanup || super.visitNode(node);
+ }
+
+ protected abstract boolean isCleanupCall(@NonNull MethodInvocation call);
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation call) {
+ if (mContainsCleanup) {
+ return true;
+ }
+
+ super.visitMethodInvocation(call);
+
+ // Look for escapes
+ if (!mEscapes) {
+ for (Expression expression : call.astArguments()) {
+ if (expression instanceof VariableReference) {
+ ResolvedNode resolved = mContext.resolve(expression);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ mEscapes = true;
+
+ // Special case: MotionEvent.obtain(MotionEvent): passing in an
+ // event here does not recycle the event, and we also know it
+ // doesn't escape
+ if (OBTAIN.equals(call.astName().astValue())) {
+ ResolvedNode r = mContext.resolve(call);
+ if (r instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) r;
+ ResolvedClass cls = method.getContainingClass();
+ if (cls.matches(MOTION_EVENT_CLS)) {
+ mEscapes = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (isCleanupCall(call)) {
+ mContainsCleanup = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ Expression initializer = node.astInitializer();
+ if (initializer instanceof VariableReference) {
+ ResolvedNode resolved = mContext.resolve(initializer);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ ResolvedNode resolvedVariable = mContext.resolve(node);
+ if (resolvedVariable instanceof ResolvedVariable) {
+ ResolvedVariable variable = (ResolvedVariable) resolvedVariable;
+ mVariables.add(variable);
+ } else if (resolvedVariable instanceof ResolvedField) {
+ mEscapes = true;
+ }
+ }
+ }
+ return super.visitVariableDefinitionEntry(node);
+ }
+
+ @Override
+ public boolean visitBinaryExpression(BinaryExpression node) {
+ if (node.astOperator() == BinaryOperator.ASSIGN) {
+ Expression rhs = node.astRight();
+ // TEMPORARILY DISABLED; see testDatabaseCursorReassignment
+ // This can result in some false positives right now. Play it
+ // safe instead.
+ boolean clearLhs = false;
+ if (rhs instanceof VariableReference) {
+ ResolvedNode resolved = mContext.resolve(rhs);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ clearLhs = false;
+ ResolvedNode resolvedLhs = mContext.resolve(node.astLeft());
+ if (resolvedLhs instanceof ResolvedVariable) {
+ ResolvedVariable variable = (ResolvedVariable) resolvedLhs;
+ mVariables.add(variable);
+ } else if (resolvedLhs instanceof ResolvedField) {
+ mEscapes = true;
+ }
+ }
+ }
+
+ if (clearLhs) {
+ // If we reassign one of the variables, clear it out
+ Expression lhs = node.astLeft();
+ ResolvedNode resolved = mContext.resolve(lhs);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && !lhs.equals(mOriginalVariableNode)
+ && mVariables.contains(resolved)) {
+ //noinspection SuspiciousMethodCalls
+ mVariables.remove(resolved);
+ }
+ }
+ }
+ return super.visitBinaryExpression(node);
+ }
+
+ @Override
+ public boolean visitReturn(Return node) {
+ Expression value = node.astValue();
+ if (value instanceof VariableReference) {
+ ResolvedNode resolved = mContext.resolve(value);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ mEscapes = true;
+ }
+ }
+
+ return super.visitReturn(node);
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
new file mode 100644
index 0000000..3a907de
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FrameNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.IntInsnNode;
+import org.objectweb.asm.tree.JumpInsnNode;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.LineNumberNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+import org.objectweb.asm.tree.TypeInsnNode;
+import org.objectweb.asm.tree.analysis.Analyzer;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.objectweb.asm.tree.analysis.BasicInterpreter;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+//import org.objectweb.asm.util.Printer;
+
+/**
+ * A {@linkplain ControlFlowGraph} is a graph containing a node for each
+ * instruction in a method, and an edge for each possible control flow; usually
+ * just "next" for the instruction following the current instruction, but in the
+ * case of a branch such as an "if", multiple edges to each successive location,
+ * or with a "goto", a single edge to the jumped-to instruction.
+ * <p>
+ * It also adds edges for abnormal control flow, such as the possibility of a
+ * method call throwing a runtime exception.
+ */
+public class ControlFlowGraph {
+ /** Map from instructions to nodes */
+ private Map<AbstractInsnNode, Node> mNodeMap;
+ private MethodNode mMethod;
+
+ /**
+ * Creates a new {@link ControlFlowGraph} and populates it with the flow
+ * control for the given method. If the optional {@code initial} parameter is
+ * provided with an existing graph, then the graph is simply populated, not
+ * created. This allows subclassing of the graph instance, if necessary.
+ *
+ * @param initial usually null, but can point to an existing instance of a
+ * {@link ControlFlowGraph} in which that graph is reused (but
+ * populated with new edges)
+ * @param classNode the class containing the method to be analyzed
+ * @param method the method to be analyzed
+ * @return a {@link ControlFlowGraph} with nodes for the control flow in the
+ * given method
+ * @throws AnalyzerException if the underlying bytecode library is unable to
+ * analyze the method bytecode
+ */
+ @NonNull
+ public static ControlFlowGraph create(
+ @Nullable ControlFlowGraph initial,
+ @NonNull ClassNode classNode,
+ @NonNull MethodNode method) throws AnalyzerException {
+ final ControlFlowGraph graph = initial != null ? initial : new ControlFlowGraph();
+ final InsnList instructions = method.instructions;
+ graph.mNodeMap = Maps.newHashMapWithExpectedSize(instructions.size());
+ graph.mMethod = method;
+
+ // Create a flow control graph using ASM5's analyzer. According to the ASM 4 guide
+ // (download.forge.objectweb.org/asm/asm4-guide.pdf) there are faster ways to construct
+ // it, but those require a lot more code.
+ Analyzer analyzer = new Analyzer(new BasicInterpreter()) {
+ @Override
+ protected void newControlFlowEdge(int insn, int successor) {
+ // Update the information as of whether the this object has been
+ // initialized at the given instruction.
+ AbstractInsnNode from = instructions.get(insn);
+ AbstractInsnNode to = instructions.get(successor);
+ graph.add(from, to);
+ }
+
+ @Override
+ protected boolean newControlFlowExceptionEdge(int insn, TryCatchBlockNode tcb) {
+ AbstractInsnNode from = instructions.get(insn);
+ graph.exception(from, tcb);
+ return super.newControlFlowExceptionEdge(insn, tcb);
+ }
+
+ @Override
+ protected boolean newControlFlowExceptionEdge(int insn, int successor) {
+ AbstractInsnNode from = instructions.get(insn);
+ AbstractInsnNode to = instructions.get(successor);
+ graph.exception(from, to);
+ return super.newControlFlowExceptionEdge(insn, successor);
+ }
+ };
+
+ analyzer.analyze(classNode.name, method);
+ return graph;
+ }
+
+ /**
+ * Checks whether there is a path from the given source node to the given
+ * destination node
+ */
+ @SuppressWarnings("MethodMayBeStatic")
+ private boolean isConnected(@NonNull Node from,
+ @NonNull Node to, @NonNull Set<Node> seen) {
+ if (from == to) {
+ return true;
+ } else if (seen.contains(from)) {
+ return false;
+ }
+ seen.add(from);
+
+ List<Node> successors = from.successors;
+ List<Node> exceptions = from.exceptions;
+ if (exceptions != null) {
+ for (Node successor : exceptions) {
+ if (isConnected(successor, to, seen)) {
+ return true;
+ }
+ }
+ }
+
+ if (successors != null) {
+ for (Node successor : successors) {
+ if (isConnected(successor, to, seen)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether there is a path from the given source node to the given
+ * destination node
+ */
+ public boolean isConnected(@NonNull Node from, @NonNull Node to) {
+ return isConnected(from, to, Sets.<Node>newIdentityHashSet());
+ }
+
+ /**
+ * Checks whether there is a path from the given instruction to the given
+ * instruction node
+ */
+ public boolean isConnected(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
+ return isConnected(getNode(from), getNode(to));
+ }
+
+ /** A {@link Node} is a node in the control flow graph for a method, pointing to
+ * the instruction and its possible successors */
+ public static class Node {
+ /** The instruction */
+ public final AbstractInsnNode instruction;
+ /** Any normal successors (e.g. following instruction, or goto or conditional flow) */
+ public final List<Node> successors = new ArrayList<Node>(2);
+ /** Any abnormal successors (e.g. the handler to go to following an exception) */
+ public final List<Node> exceptions = new ArrayList<Node>(1);
+
+ /** A tag for use during depth-first-search iteration of the graph etc */
+ public int visit;
+
+ /**
+ * Constructs a new control graph node
+ *
+ * @param instruction the instruction to associate with this node
+ */
+ public Node(@NonNull AbstractInsnNode instruction) {
+ this.instruction = instruction;
+ }
+
+ void addSuccessor(@NonNull Node node) {
+ if (!successors.contains(node)) {
+ successors.add(node);
+ }
+ }
+
+ void addExceptionPath(@NonNull Node node) {
+ if (!exceptions.contains(node)) {
+ exceptions.add(node);
+ }
+ }
+
+ /**
+ * Represents this instruction as a string, for debugging purposes
+ *
+ * @param includeAdjacent whether it should include a display of
+ * adjacent nodes as well
+ * @return a string representation
+ */
+ @NonNull
+ public String toString(boolean includeAdjacent) {
+ StringBuilder sb = new StringBuilder(100);
+
+ sb.append(getId(instruction));
+ sb.append(':');
+
+ if (instruction instanceof LabelNode) {
+ //LabelNode l = (LabelNode) instruction;
+ //sb.append('L' + l.getLabel().getOffset() + ":");
+ //sb.append('L' + l.getLabel().info + ":");
+ sb.append("LABEL");
+ } else if (instruction instanceof LineNumberNode) {
+ sb.append("LINENUMBER ").append(((LineNumberNode)instruction).line);
+ } else if (instruction instanceof FrameNode) {
+ sb.append("FRAME");
+ } else {
+ int opcode = instruction.getOpcode();
+ String opcodeName = getOpcodeName(opcode);
+ sb.append(opcodeName);
+ if (instruction.getType() == AbstractInsnNode.METHOD_INSN) {
+ sb.append('(').append(((MethodInsnNode)instruction).name).append(')');
+ }
+ }
+
+ if (includeAdjacent) {
+ if (successors != null && !successors.isEmpty()) {
+ sb.append(" Next:");
+ for (Node successor : successors) {
+ sb.append(' ');
+ sb.append(successor.toString(false));
+ }
+ }
+
+ if (exceptions != null && !exceptions.isEmpty()) {
+ sb.append(" Exceptions:");
+ for (Node exception : exceptions) {
+ sb.append(' ');
+ sb.append(exception.toString(false));
+ }
+ }
+ sb.append('\n');
+ }
+
+ return sb.toString();
+ }
+ }
+
+ /** Adds an exception flow to this graph */
+ protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
+ getNode(from).addSuccessor(getNode(to));
+ }
+
+ /** Adds an exception flow to this graph */
+ protected void exception(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
+ // For now, these edges appear useless; we also get more specific
+ // information via the TryCatchBlockNode which we use instead.
+ //getNode(from).addExceptionPath(getNode(to));
+ }
+
+ /** Adds an exception try block node to this graph */
+ protected void exception(@NonNull AbstractInsnNode from, @NonNull TryCatchBlockNode tcb) {
+ // Add tcb's to all instructions in the range
+ LabelNode start = tcb.start;
+ LabelNode end = tcb.end; // exclusive
+
+ // Add exception edges for all method calls in the range
+ AbstractInsnNode curr = start;
+ Node handlerNode = getNode(tcb.handler);
+ while (curr != end && curr != null) {
+ // A method can throw can exception, or a throw instruction directly
+ if (curr.getType() == AbstractInsnNode.METHOD_INSN
+ || (curr.getType() == AbstractInsnNode.INSN
+ && curr.getOpcode() == Opcodes.ATHROW)) {
+ // Method call; add exception edge to handler
+ if (tcb.type == null) {
+ // finally block: not an exception path
+ getNode(curr).addSuccessor(handlerNode);
+ }
+ getNode(curr).addExceptionPath(handlerNode);
+ }
+ curr = curr.getNext();
+ }
+ }
+
+ /**
+ * Looks up (and if necessary) creates a graph node for the given instruction
+ *
+ * @param instruction the instruction
+ * @return the control flow graph node corresponding to the given
+ * instruction
+ */
+ @NonNull
+ public Node getNode(@NonNull AbstractInsnNode instruction) {
+ Node node = mNodeMap.get(instruction);
+ if (node == null) {
+ node = new Node(instruction);
+ mNodeMap.put(instruction, node);
+ }
+
+ return node;
+ }
+
+ /**
+ * Creates a human readable version of the graph
+ *
+ * @param start the starting instruction, or null if not known or to use the
+ * first instruction
+ * @return a string version of the graph
+ */
+ @NonNull
+ public String toString(@Nullable Node start) {
+ StringBuilder sb = new StringBuilder(400);
+
+ AbstractInsnNode curr;
+ if (start != null) {
+ curr = start.instruction;
+ } else {
+ if (mNodeMap.isEmpty()) {
+ return "<empty>";
+ } else {
+ curr = mNodeMap.keySet().iterator().next();
+ while (curr.getPrevious() != null) {
+ curr = curr.getPrevious();
+ }
+ }
+ }
+
+ while (curr != null) {
+ Node node = mNodeMap.get(curr);
+ if (node != null) {
+ sb.append(node.toString(true));
+ }
+ curr = curr.getNext();
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toString(null);
+ }
+
+ // ---- For debugging only ----
+
+ private static Map<Object, String> sIds = null;
+ private static int sNextId = 1;
+ private static String getId(Object object) {
+ if (sIds == null) {
+ sIds = Maps.newHashMap();
+ }
+ String id = sIds.get(object);
+ if (id == null) {
+ id = Integer.toString(sNextId++);
+ sIds.put(object, id);
+ }
+ return id;
+ }
+
+ /**
+ * Generates dot output of the graph. This can be used with
+ * graphwiz to visualize the graph. For example, if you
+ * save the output as graph1.gv you can run
+ * <pre>
+ * $ dot -Tps graph1.gv -o graph1.ps
+ * </pre>
+ * to generate a postscript file, which you can then view
+ * with "gv graph1.ps".
+ *
+ * (There are also some online web sites where you can
+ * paste in dot graphs and see the visualization right
+ * there in the browser.)
+ *
+ * @return a dot description of this control flow graph,
+ * useful for debugging
+ */
+ public String toDot(@Nullable Set<Node> highlight) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph G {\n");
+
+
+ AbstractInsnNode instruction = mMethod.instructions.getFirst();
+
+ // Special start node
+ sb.append(" start -> ").append(getId(mNodeMap.get(instruction))).append(";\n");
+ sb.append(" start [shape=plaintext];\n");
+
+ while (instruction != null) {
+ Node node = mNodeMap.get(instruction);
+ if (node != null) {
+ if (node.successors != null) {
+ for (Node to : node.successors) {
+ sb.append(" ").append(getId(node)).append(" -> ").append(getId(to));
+ if (node.instruction instanceof JumpInsnNode) {
+ sb.append(" [label=\"");
+ if (((JumpInsnNode)node.instruction).label == to.instruction) {
+ sb.append("yes");
+ } else {
+ sb.append("no");
+ }
+ sb.append("\"]");
+ }
+ sb.append(";\n");
+ }
+ }
+ if (node.exceptions != null) {
+ for (Node to : node.exceptions) {
+ sb.append(getId(node)).append(" -> ").append(getId(to));
+ sb.append(" [label=\"exception\"];\n");
+ }
+ }
+ }
+
+ instruction = instruction.getNext();
+ }
+
+
+ // Labels
+ sb.append("\n");
+ for (Node node : mNodeMap.values()) {
+ instruction = node.instruction;
+ sb.append(" ").append(getId(node)).append(" ");
+ sb.append("[label=\"").append(dotDescribe(node)).append("\"");
+ if (highlight != null && highlight.contains(node)) {
+ sb.append(",shape=box,style=filled");
+ } else if (instruction instanceof LineNumberNode ||
+ instruction instanceof LabelNode ||
+ instruction instanceof FrameNode) {
+ sb.append(",shape=oval,style=dotted");
+ } else {
+ sb.append(",shape=box");
+ }
+ sb.append("];\n");
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String dotDescribe(Node node) {
+ AbstractInsnNode instruction = node.instruction;
+ if (instruction instanceof LabelNode) {
+ return "Label";
+ } else if (instruction instanceof LineNumberNode) {
+ LineNumberNode lineNode = (LineNumberNode)instruction;
+ return "Line " + lineNode.line;
+ } else if (instruction instanceof FrameNode) {
+ return "Stack Frame";
+ } else if (instruction instanceof MethodInsnNode) {
+ MethodInsnNode method = (MethodInsnNode)instruction;
+ String cls = method.owner.substring(method.owner.lastIndexOf('/') + 1);
+ cls = cls.replace('$','.');
+ return "Call " + cls + "#" + method.name;
+ } else if (instruction instanceof FieldInsnNode) {
+ FieldInsnNode field = (FieldInsnNode) instruction;
+ String cls = field.owner.substring(field.owner.lastIndexOf('/') + 1);
+ cls = cls.replace('$','.');
+ return "Field " + cls + "#" + field.name;
+ } else if (instruction instanceof TypeInsnNode && instruction.getOpcode() == Opcodes.NEW) {
+ return "New " + ((TypeInsnNode)instruction).desc;
+ }
+ StringBuilder sb = new StringBuilder();
+ String opcodeName = getOpcodeName(instruction.getOpcode());
+ sb.append(opcodeName);
+
+ if (instruction instanceof IntInsnNode) {
+ IntInsnNode in = (IntInsnNode) instruction;
+ sb.append(" ").append(Integer.toString(in.operand));
+ } else if (instruction instanceof LdcInsnNode) {
+ LdcInsnNode ldc = (LdcInsnNode) instruction;
+ sb.append(" ");
+ if (ldc.cst instanceof String) {
+ sb.append("\\\"");
+ }
+ sb.append(ldc.cst);
+ if (ldc.cst instanceof String) {
+ sb.append("\\\"");
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String getOpcodeName(int opcode) {
+ if (sOpcodeNames == null) {
+ sOpcodeNames = new String[255];
+ try {
+ Field[] fields = Opcodes.class.getDeclaredFields();
+ for (Field field : fields) {
+ if (field.getType() == int.class) {
+ String name = field.getName();
+ if (name.startsWith("ASM") || name.startsWith("V1_") ||
+ name.startsWith("ACC_") || name.startsWith("T_") ||
+ name.startsWith("H_") || name.startsWith("F_")) {
+ continue;
+ }
+ int val = field.getInt(null);
+ if (val >= 0 && val < sOpcodeNames.length) {
+ sOpcodeNames[val] = field.getName();
+ }
+
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ if (opcode >= 0 && opcode < sOpcodeNames.length) {
+ String name = sOpcodeNames[opcode];
+ if (name != null) {
+ return name;
+ }
+ }
+
+ return Integer.toString(opcode);
+ }
+
+ private static String[] sOpcodeNames;
+}
+
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CustomViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CustomViewDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CustomViewDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CustomViewDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DateFormatDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DateFormatDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DateFormatDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DateFormatDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
new file mode 100644
index 0000000..299cc68
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ABSOLUTE_LAYOUT;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_AUTO_TEXT;
+import static com.android.SdkConstants.ATTR_CAPITALIZE;
+import static com.android.SdkConstants.ATTR_EDITABLE;
+import static com.android.SdkConstants.ATTR_ENABLED;
+import static com.android.SdkConstants.ATTR_INPUT_METHOD;
+import static com.android.SdkConstants.ATTR_NUMERIC;
+import static com.android.SdkConstants.ATTR_PASSWORD;
+import static com.android.SdkConstants.ATTR_PHONE_NUMBER;
+import static com.android.SdkConstants.ATTR_SINGLE_LINE;
+import static com.android.SdkConstants.EDIT_TEXT;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_23;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_M;
+import static com.android.SdkConstants.VALUE_TRUE;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Check which looks for usage of deprecated tags, attributes, etc.
+ */
+public class DeprecationDetector extends LayoutDetector {
+ /** Usage of deprecated views or attributes */
+ @SuppressWarnings("unchecked")
+ public static final Issue ISSUE = Issue.create(
+ "Deprecated", //$NON-NLS-1$
+ "Using deprecated resources",
+ "Deprecated views, attributes and so on are deprecated because there " +
+ "is a better way to do something. Do it that new way. You've been warned.",
+ Category.CORRECTNESS,
+ 2,
+ Severity.WARNING,
+ new Implementation(
+ DeprecationDetector.class,
+ Scope.MANIFEST_AND_RESOURCE_SCOPE,
+ Scope.MANIFEST_SCOPE,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Constructs a new {@link DeprecationDetector} */
+ public DeprecationDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ ABSOLUTE_LAYOUT,
+ TAG_USES_PERMISSION_SDK_M
+ );
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Arrays.asList(
+ // TODO: fill_parent is deprecated as of API 8.
+ // We could warn about it, but it will probably be very noisy
+ // and make people disable the deprecation check; let's focus on
+ // some older flags for now
+ //"fill_parent",
+
+ ATTR_EDITABLE,
+ ATTR_INPUT_METHOD,
+ ATTR_AUTO_TEXT,
+ ATTR_CAPITALIZE,
+
+ // This flag is still used a lot and is still properly handled by TextView
+ // so in the interest of not being too noisy and make people ignore all the
+ // output, keep quiet about this one -for now-.
+ //ATTR_SINGLE_LINE,
+
+ // This attribute is marked deprecated in android.R.attr but apparently
+ // using the suggested replacement of state_enabled doesn't work, see issue 27613
+ //ATTR_ENABLED,
+
+ ATTR_NUMERIC,
+ ATTR_PHONE_NUMBER,
+ ATTR_PASSWORD
+
+ // These attributes are also deprecated; not yet enabled until we
+ // know the API level to apply the deprecation for:
+
+ // "ignored as of ICS (but deprecated earlier)"
+ //"fadingEdge",
+
+ // "This attribute is not used by the Android operating system."
+ //"restoreNeedsApplication",
+
+ // "This will create a non-standard UI appearance, because the search bar UI is
+ // changing to use only icons for its buttons."
+ //"searchButtonText",
+
+ );
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String tagName = element.getTagName();
+ String message = String.format("`%1$s` is deprecated", tagName);
+ if (TAG_USES_PERMISSION_SDK_M.equals(tagName)) {
+ message += ": Use `" + TAG_USES_PERMISSION_SDK_23 + " instead";
+ }
+ context.report(ISSUE, element, context.getLocation(element),
+ message);
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ return;
+ }
+
+ String name = attribute.getLocalName();
+ String fix;
+ int minSdk = 1;
+ if (name.equals(ATTR_EDITABLE)) {
+ if (!EDIT_TEXT.equals(attribute.getOwnerElement().getTagName())) {
+ fix = "Use an `<EditText>` to make it editable";
+ } else {
+ if (VALUE_TRUE.equals(attribute.getValue())) {
+ fix = "`<EditText>` is already editable";
+ } else {
+ fix = "Use `inputType` instead";
+ }
+ }
+ } else if (name.equals(ATTR_ENABLED)) {
+ fix = "Use `state_enabled` instead";
+ } else if (name.equals(ATTR_SINGLE_LINE)) {
+ fix = "Use `maxLines=\"1\"` instead";
+ } else {
+ assert name.equals(ATTR_INPUT_METHOD)
+ || name.equals(ATTR_CAPITALIZE)
+ || name.equals(ATTR_NUMERIC)
+ || name.equals(ATTR_PHONE_NUMBER)
+ || name.equals(ATTR_PASSWORD)
+ || name.equals(ATTR_AUTO_TEXT);
+ fix = "Use `inputType` instead";
+ // The inputType attribute was introduced in API 3 so don't warn about
+ // deprecation if targeting older platforms
+ minSdk = 3;
+ }
+
+ if (context.getProject().getMinSdk() < minSdk) {
+ return;
+ }
+
+ context.report(ISSUE, attribute, context.getLocation(attribute),
+ String.format("`%1$s` is deprecated: %2$s",
+ attribute.getName(), fix));
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
new file mode 100644
index 0000000..b0c83fc
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Document;
+
+/**
+ * Checks that the line endings in DOS files are consistent
+ */
+public class DosLineEndingDetector extends LayoutDetector {
+ /** Detects mangled DOS line ending documents */
+ public static final Issue ISSUE = Issue.create(
+ "MangledCRLF", //$NON-NLS-1$
+ "Mangled file line endings",
+
+ "On Windows, line endings are typically recorded as carriage return plus " +
+ "newline: \\r\\n.\n" +
+ "\n" +
+ "This detector looks for invalid line endings with repeated carriage return " +
+ "characters (without newlines). Previous versions of the ADT plugin could " +
+ "accidentally introduce these into the file, and when editing the file, the " +
+ "editor could produce confusing visual artifacts.",
+
+ Category.CORRECTNESS,
+ 2,
+ Severity.ERROR,
+ new Implementation(
+ DosLineEndingDetector.class,
+ Scope.RESOURCE_FILE_SCOPE))
+ .setEnabledByDefault(false) // This check is probably not relevant for most users anymore
+ .addMoreInfo("https://bugs.eclipse.org/bugs/show_bug.cgi?id=375421"); //$NON-NLS-1$
+
+ /** Constructs a new {@link DosLineEndingDetector} */
+ public DosLineEndingDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ String contents = context.getContents();
+ if (contents == null) {
+ return;
+ }
+
+ // We could look for *consistency* and complain if you mix \n and \r\n too,
+ // but that isn't really a problem (most editors handle it) so let's
+ // not complain needlessly.
+
+ char prev = 0;
+ for (int i = 0, n = contents.length(); i < n; i++) {
+ char c = contents.charAt(i);
+ if (c == '\r' && prev == '\r') {
+ String message = "Incorrect line ending: found carriage return (`\\r`) without " +
+ "corresponding newline (`\\n`)";
+
+ // Mark the whole line as the error range, since pointing just to the
+ // line ending makes the error invisible in IDEs and error reports etc
+ // Find the most recent non-blank line
+ boolean blankLine = true;
+ for (int index = i - 2; index < i; index++) {
+ char d = contents.charAt(index);
+ if (!Character.isWhitespace(d)) {
+ blankLine = false;
+ }
+ }
+
+ int lineBegin = i;
+ for (int index = i - 2; index >= 0; index--) {
+ char d = contents.charAt(index);
+ if (d == '\n') {
+ lineBegin = index + 1;
+ if (!blankLine) {
+ break;
+ }
+ } else if (!Character.isWhitespace(d)) {
+ blankLine = false;
+ }
+ }
+
+ int lineEnd = Math.min(contents.length(), i + 1);
+ Location location = Location.create(context.file, contents, lineBegin, lineEnd);
+ context.report(ISSUE, document.getDocumentElement(), location, message);
+ return;
+ }
+ prev = c;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
new file mode 100644
index 0000000..fcbf9e3
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.utils.SdkUtils.getResourceFieldName;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Location.Handle;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This detector identifies cases where a resource is defined multiple times in the
+ * same resource folder
+ */
+public class DuplicateResourceDetector extends ResourceXmlDetector {
+
+ /** The main issue discovered by this detector */
+ @SuppressWarnings("unchecked")
+ public static final Issue ISSUE = Issue.create(
+ "DuplicateDefinition", //$NON-NLS-1$
+ "Duplicate definitions of resources",
+
+ "You can define a resource multiple times in different resource folders; that's how " +
+ "string translations are done, for example. However, defining the same resource " +
+ "more than once in the same resource folder is likely an error, for example " +
+ "attempting to add a new resource without realizing that the name is already used, " +
+ "and so on.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ new Implementation(
+ DuplicateResourceDetector.class,
+ // We should be able to do this incrementally!
+ Scope.ALL_RESOURCES_SCOPE,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Wrong resource value type */
+ public static final Issue TYPE_MISMATCH = Issue.create(
+ "ReferenceType", //$NON-NLS-1$
+ "Incorrect reference types",
+ "When you generate a resource alias, the resource you are pointing to must be " +
+ "of the same type as the alias",
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ new Implementation(
+ DuplicateResourceDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+
+ private static final String PRODUCT = "product"; //$NON-NLS-1$
+ private Map<ResourceType, Set<String>> mTypeMap;
+ private Map<ResourceType, List<Pair<String, Location.Handle>>> mLocations;
+ private File mParent;
+
+ /** Constructs a new {@link DuplicateResourceDetector} */
+ public DuplicateResourceDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ @Override
+ @Nullable
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(ATTR_NAME);
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.VALUES;
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ File parent = context.file.getParentFile();
+ if (!parent.equals(mParent)) {
+ mParent = parent;
+ mTypeMap = Maps.newEnumMap(ResourceType.class);
+ mLocations = Maps.newEnumMap(ResourceType.class);
+ }
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ Element element = attribute.getOwnerElement();
+
+ if (element.hasAttribute(PRODUCT)) {
+ return;
+ }
+
+ String tag = element.getTagName();
+ String typeString = tag;
+ if (tag.equals(TAG_ITEM)) {
+ typeString = element.getAttribute(ATTR_TYPE);
+ if (typeString == null || typeString.isEmpty()) {
+ if (element.getParentNode().getNodeName().equals(
+ ResourceType.STYLE.getName()) && isFirstElementChild(element)) {
+ checkUniqueNames(context, (Element) element.getParentNode());
+ }
+ return;
+ }
+ }
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type == null) {
+ return;
+ } else if (type == ResourceType.PUBLIC) {
+ // We can't easily check public declarations since it's not as simple as
+ // just looking up the type attribute and switching the ResourceType to it;
+ // that would treat <dimen name="foo"> and <public name="foo" type="dimen">
+ // as an actual duplicate name. A simple way to do it would be to change the
+ // name, e.g. by prefixing "public-" to it, but that would require restructuring
+ // the code a bit, and we'd need to remove it when displaying the conflicts --
+ // and most importantly, there really isn't a good reason to do it; a public
+ // declaration has no value, so there's no chance of creating conflicting
+ // definitions.
+ return;
+ }
+
+ if (type == ResourceType.ATTR
+ && element.getParentNode().getNodeName().equals(
+ ResourceType.DECLARE_STYLEABLE.getName())) {
+ if (isFirstElementChild(element)) {
+ checkUniqueNames(context, (Element) element.getParentNode());
+ }
+ return;
+ }
+
+ NodeList children = element.getChildNodes();
+ int childCount = children.getLength();
+ for (int i = 0; i < childCount; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+ for (int j = 0, length = text.length(); j < length; j++) {
+ char c = text.charAt(j);
+ if (c == '@') {
+ if (!text.regionMatches(false, j + 1, typeString, 0,
+ typeString.length()) && context.isEnabled(TYPE_MISMATCH)) {
+ ResourceUrl url = ResourceUrl.parse(text.trim());
+ if (url != null && url.type != type &&
+ // colors and mipmaps can apparently be used as drawables
+ !(type == ResourceType.DRAWABLE
+ && (url.type == ResourceType.COLOR
+ || url.type == ResourceType.MIPMAP))) {
+ String message = "Unexpected resource reference type; "
+ + "expected value of type `@" + type + "/`";
+ context.report(TYPE_MISMATCH, element,
+ context.getLocation(child),
+ message);
+ }
+ }
+ break;
+ } else if (!Character.isWhitespace(c)) {
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ Set<String> names = mTypeMap.get(type);
+ if (names == null) {
+ names = Sets.newHashSetWithExpectedSize(40);
+ mTypeMap.put(type, names);
+ }
+
+ String name = attribute.getValue();
+ String originalName = name;
+ // AAPT will flatten the namespace, turning dots, dashes and colons into _
+ name = getResourceFieldName(name);
+
+ if (names.contains(name)) {
+ String message = String.format("`%1$s` has already been defined in this folder", name);
+ if (!name.equals(originalName)) {
+ message += " (`" + name + "` is equivalent to `" + originalName + "`)";
+ }
+ Location location = context.getLocation(attribute);
+ List<Pair<String, Handle>> list = mLocations.get(type);
+ for (Pair<String, Handle> pair : list) {
+ if (name.equals(pair.getFirst())) {
+ Location secondary = pair.getSecond().resolve();
+ secondary.setMessage("Previously defined here");
+ location.setSecondary(secondary);
+ }
+ }
+ context.report(ISSUE, attribute, location, message);
+ } else {
+ names.add(name);
+ List<Pair<String, Handle>> list = mLocations.get(type);
+ if (list == null) {
+ list = Lists.newArrayList();
+ mLocations.put(type, list);
+ }
+ Location.Handle handle = context.createLocationHandle(attribute);
+ list.add(Pair.of(name, handle));
+ }
+ }
+
+ private static void checkUniqueNames(XmlContext context, Element parent) {
+ List<Element> items = LintUtils.getChildren(parent);
+ if (items.size() > 1) {
+ Set<String> names = Sets.newHashSet();
+ for (Element item : items) {
+ Attr nameNode = item.getAttributeNode(ATTR_NAME);
+ if (nameNode != null) {
+ String name = nameNode.getValue();
+ if (names.contains(name) && context.isEnabled(ISSUE)) {
+ Location location = context.getLocation(nameNode);
+ for (Element prevItem : items) {
+ Attr attribute = item.getAttributeNode(ATTR_NAME);
+ if (attribute != null && name.equals(attribute.getValue())) {
+ assert prevItem != item;
+ Location prev = context.getLocation(prevItem);
+ prev.setMessage("Previously defined here");
+ location.setSecondary(prev);
+ break;
+ }
+ }
+ String message = String.format(
+ "`%1$s` has already been defined in this `<%2$s>`",
+ name, parent.getTagName());
+ context.report(ISSUE, nameNode, location, message);
+ }
+ names.add(name);
+ }
+ }
+ }
+ }
+
+ private static boolean isFirstElementChild(Node node) {
+ node = node.getPreviousSibling();
+ while (node != null) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ return false;
+ }
+ node = node.getPreviousSibling();
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the resource type expected for a {@link #TYPE_MISMATCH} error reported by
+ * this lint detector. Intended for IDE quickfix implementations.
+ *
+ * @param message the error message created by this lint detector
+ * @param format the format of the error message
+ */
+ public static String getExpectedType(@NonNull String message, @NonNull TextFormat format) {
+ return LintUtils.findSubstring(format.toText(message), "value of type @", "/");
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
new file mode 100644
index 0000000..bd01fbb
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CLASS_FRAGMENT;
+import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.TypeMember;
+
+/**
+ * Checks that Fragment subclasses can be instantiated via
+ * {link {@link Class#newInstance()}}: the class is public, static, and has
+ * a public null constructor.
+ * <p>
+ * This helps track down issues like
+ * http://stackoverflow.com/questions/8058809/fragment-activity-crashes-on-screen-rotate
+ * (and countless duplicates)
+ */
+public class FragmentDetector extends Detector implements JavaScanner {
+ /** Are fragment subclasses instantiatable? */
+ public static final Issue ISSUE = Issue.create(
+ "ValidFragment", //$NON-NLS-1$
+ "Fragment not instantiatable",
+
+ "From the Fragment documentation:\n" +
+ "*Every* fragment must have an empty constructor, so it can be instantiated when " +
+ "restoring its activity's state. It is strongly recommended that subclasses do not " +
+ "have other constructors with parameters, since these constructors will not be " +
+ "called when the fragment is re-instantiated; instead, arguments can be supplied " +
+ "by the caller with `setArguments(Bundle)` and later retrieved by the Fragment " +
+ "with `getArguments()`.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ new Implementation(
+ FragmentDetector.class,
+ Scope.JAVA_FILE_SCOPE)
+ ).addMoreInfo(
+ "http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$
+
+
+ /** Constructs a new {@link FragmentDetector} */
+ public FragmentDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Arrays.asList(CLASS_FRAGMENT, CLASS_V4_FRAGMENT);
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+ @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+ if (node == null) {
+ String message = "Fragments should be static such that they can be re-instantiated by " +
+ "the system, and anonymous classes are not static";
+ Node locationNode = declarationOrAnonymous;
+ if (locationNode.getParent() instanceof ConstructorInvocation) {
+ ConstructorInvocation constructor = (ConstructorInvocation)locationNode.getParent();
+ if (constructor.astTypeReference() != null) {
+ locationNode = constructor.astTypeReference();
+ }
+ }
+ context.report(ISSUE, declarationOrAnonymous, context.getLocation(locationNode), message);
+ return;
+ }
+
+ int flags = node.astModifiers().getEffectiveModifierFlags();
+ if ((flags & Modifier.ABSTRACT) != 0) {
+ return;
+ }
+
+ if ((flags & Modifier.PUBLIC) == 0) {
+ String message = String.format("This fragment class should be public (%1$s)",
+ cls.getName());
+ context.report(ISSUE, node, context.getLocation(node.astName()), message);
+ return;
+ }
+
+ if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) {
+ String message = String.format(
+ "This fragment inner class should be static (%1$s)", cls.getName());
+ context.report(ISSUE, node, context.getLocation(node.astName()), message);
+ return;
+ }
+
+ boolean hasDefaultConstructor = false;
+ boolean hasConstructor = false;
+ NormalTypeBody body = node.astBody();
+ if (body != null) {
+ for (TypeMember member : body.astMembers()) {
+ if (member instanceof ConstructorDeclaration) {
+ hasConstructor = true;
+ ConstructorDeclaration constructor = (ConstructorDeclaration) member;
+ if (constructor.astParameters().isEmpty()) {
+ // The constructor must be public
+ if (constructor.astModifiers().isPublic()) {
+ hasDefaultConstructor = true;
+ } else {
+ Location location = context.getLocation(
+ constructor.astTypeName());
+ context.report(ISSUE, constructor, location,
+ "The default constructor must be public");
+ // Also mark that we have a constructor so we don't complain again
+ // below since we've already emitted a more specific error related
+ // to the default constructor
+ hasDefaultConstructor = true;
+ }
+ } else {
+ Location location = context.getLocation(constructor.astTypeName());
+ // TODO: Use separate issue for this which isn't an error
+ String message = "Avoid non-default constructors in fragments: "
+ + "use a default constructor plus "
+ + "`Fragment#setArguments(Bundle)` instead";
+ context.report(ISSUE, constructor, location, message);
+ }
+ }
+ }
+ }
+
+ if (!hasDefaultConstructor && hasConstructor) {
+ String message = String.format(
+ "This fragment should provide a default constructor (a public " +
+ "constructor with no arguments) (`%1$s`)",
+ cls.getName());
+ context.report(ISSUE, node, context.getLocation(node.astName()), message);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java
new file mode 100644
index 0000000..6780de7
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FullBackupContentDetector.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Check which makes sure that a full-backup-content descriptor file is valid and logical
+ */
+public class FullBackupContentDetector extends ResourceXmlDetector implements JavaScanner {
+ /**
+ * Validation of {@code <full-backup-content>} XML elements
+ */
+ public static final Issue ISSUE = Issue.create(
+ "FullBackupContent", //$NON-NLS-1$
+ "Valid Full Backup Content File",
+
+ "Ensures that a `<full-backup-content>` file, which is pointed to by a " +
+ "`android:fullBackupContent attribute` in the manifest file, is valid",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.FATAL,
+ new Implementation(
+ FullBackupContentDetector.class,
+ Scope.RESOURCE_FILE_SCOPE))
+ .addMoreInfo("http://android-developers.blogspot.com/2015/07/auto-backup-for-apps-made-simple.html");
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private static final String DOMAIN_SHARED_PREF = "sharedpref";
+ private static final String DOMAIN_ROOT = "root";
+ private static final String DOMAIN_FILE = "file";
+ private static final String DOMAIN_DATABASE = "database";
+ private static final String DOMAIN_EXTERNAL = "external";
+ private static final String TAG_EXCLUDE = "exclude";
+ private static final String TAG_INCLUDE = "include";
+ private static final String TAG_FULL_BACKUP_CONTENT = "full-backup-content";
+ private static final String ATTR_PATH = "path";
+ private static final String ATTR_DOMAIN = "domain";
+
+ /**
+ * Constructs a new {@link FullBackupContentDetector}
+ */
+ public FullBackupContentDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.XML;
+ }
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ Element root = document.getDocumentElement();
+ if (root == null) {
+ return;
+ }
+ if (!TAG_FULL_BACKUP_CONTENT.equals(root.getTagName())) {
+ return;
+ }
+
+ List<Element> includes = Lists.newArrayList();
+ List<Element> excludes = Lists.newArrayList();
+ NodeList children = root.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) child;
+ String tag = element.getTagName();
+ if (TAG_INCLUDE.equals(tag)) {
+ includes.add(element);
+ } else if (TAG_EXCLUDE.equals(tag)) {
+ excludes.add(element);
+ } else {
+ // See FullBackup#validateInnerTagContents
+ context.report(ISSUE, element, context.getNameLocation(element),
+ String.format("Unexpected element `<%1$s>`", tag));
+ }
+ }
+ }
+
+ Multimap<String, String> includePaths = ArrayListMultimap.create(includes.size(), 4);
+ for (Element include : includes) {
+ String domain = validateDomain(context, include);
+ String path = validatePath(context, include);
+ if (domain == null) {
+ continue;
+ }
+ includePaths.put(domain, path);
+ }
+
+ for (Element exclude : excludes) {
+ String excludePath = validatePath(context, exclude);
+ if (excludePath.isEmpty()) {
+ continue;
+ }
+ String domain = validateDomain(context, exclude);
+ if (domain == null) {
+ continue;
+ }
+ if (includePaths.isEmpty()) {
+ // There is no <include> anywhere: that means that everything
+ // is considered included and there's no potential prefix mismatch
+ continue;
+ }
+
+ boolean hasPrefix = false;
+ Collection<String> included = includePaths.get(domain);
+ if (included == null) {
+ continue;
+ }
+ for (String includePath : included) {
+ if (excludePath.startsWith(includePath)) {
+ if (excludePath.equals(includePath)) {
+ Attr pathNode = exclude.getAttributeNode(ATTR_PATH);
+ assert pathNode != null;
+ Location location = context.getValueLocation(pathNode);
+ // Find corresponding include path so we can link to it in the
+ // chained location list
+ for (Element include : includes) {
+ Attr includePathNode = include.getAttributeNode(ATTR_PATH);
+ String includeDomain = include.getAttribute(ATTR_DOMAIN);
+ if (includePathNode != null
+ && excludePath.equals(includePathNode.getValue())
+ && domain.equals(includeDomain)) {
+ Location earlier = context.getLocation(includePathNode);
+ earlier.setMessage("Unnecessary/conflicting <include>");
+ location.setSecondary(earlier);
+ }
+ }
+ context.report(ISSUE, exclude, location,
+ String.format("Include `%1$s` is also excluded", excludePath));
+ }
+ hasPrefix = true;
+ break;
+ }
+ }
+ if (!hasPrefix) {
+ Attr pathNode = exclude.getAttributeNode(ATTR_PATH);
+ assert pathNode != null;
+ context.report(ISSUE, exclude, context.getValueLocation(pathNode),
+ String.format("`%1$s` is not in an included path", excludePath));
+ }
+ }
+ }
+
+ @NonNull
+ private static String validatePath(@NonNull XmlContext context, @NonNull Element element) {
+ Attr pathNode = element.getAttributeNode(ATTR_PATH);
+ if (pathNode == null) {
+ return "";
+ }
+ String value = pathNode.getValue();
+ if (value.contains("//")) {
+ context.report(ISSUE, element, context.getValueLocation(pathNode),
+ "Paths are not allowed to contain `//`");
+ } else if (value.contains("..")) {
+ context.report(ISSUE, element, context.getValueLocation(pathNode),
+ "Paths are not allowed to contain `..`");
+ } else if (value.contains("/")) {
+ String domain = element.getAttribute(ATTR_DOMAIN);
+ if (DOMAIN_SHARED_PREF.equals(domain) || DOMAIN_DATABASE.equals(domain)) {
+ context.report(ISSUE, element, context.getValueLocation(pathNode),
+ String.format("Subdirectories are not allowed for domain `%1$s`",
+ domain));
+ }
+ }
+ return value;
+ }
+
+ @Nullable
+ private static String validateDomain(@NonNull XmlContext context, @NonNull Element element) {
+ Attr domainNode = element.getAttributeNode(ATTR_DOMAIN);
+ if (domainNode == null) {
+ context.report(ISSUE, element, context.getLocation(element),
+ String.format("Missing domain attribute, expected one of %1$s",
+ Joiner.on(", ").join(VALID_DOMAINS)));
+ return null;
+ }
+ String domain = domainNode.getValue();
+ for (String availableDomain : VALID_DOMAINS) {
+ if (availableDomain.equals(domain)) {
+ return domain;
+ }
+ }
+ context.report(ISSUE, element, context.getValueLocation(domainNode),
+ String.format("Unexpected domain `%1$s`, expected one of %2$s", domain,
+ Joiner.on(", ").join(VALID_DOMAINS)));
+
+ return domain;
+ }
+
+ /** Valid domains; see FullBackup#getTokenForXmlDomain for authoritative list */
+ private static final String[] VALID_DOMAINS = new String[] {
+ DOMAIN_ROOT, DOMAIN_FILE, DOMAIN_DATABASE, DOMAIN_SHARED_PREF, DOMAIN_EXTERNAL
+ };
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GetSignaturesDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GetSignaturesDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GetSignaturesDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GetSignaturesDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
new file mode 100644
index 0000000..0c89a2e
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GradleDetector.java
@@ -0,0 +1,1305 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.FD_BUILD_TOOLS;
+import static com.android.SdkConstants.GRADLE_PLUGIN_MINIMUM_VERSION;
+import static com.android.SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION;
+import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
+import static com.android.tools.lint.checks.ManifestDetector.TARGET_NEWER;
+import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
+import static com.android.tools.lint.detector.api.LintUtils.guessGradleLocation;
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.Variant;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.ide.common.repository.GradleCoordinate.RevisionComponent;
+import com.android.ide.common.repository.SdkMavenRepository;
+import com.android.repository.Revision;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Checks Gradle files for potential errors
+ */
+public class GradleDetector extends Detector implements Detector.GradleScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ GradleDetector.class,
+ Scope.GRADLE_SCOPE);
+
+ /** Obsolete dependencies */
+ public static final Issue DEPENDENCY = Issue.create(
+ "GradleDependency", //$NON-NLS-1$
+ "Obsolete Gradle Dependency",
+ "This detector looks for usages of libraries where the version you are using " +
+ "is not the current stable release. Using older versions is fine, and there are " +
+ "cases where you deliberately want to stick with an older version. However, " +
+ "you may simply not be aware that a more recent version is available, and that is " +
+ "what this lint check helps find.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Deprecated Gradle constructs */
+ public static final Issue DEPRECATED = Issue.create(
+ "GradleDeprecated", //$NON-NLS-1$
+ "Deprecated Gradle Construct",
+ "This detector looks for deprecated Gradle constructs which currently work but " +
+ "will likely stop working in a future update.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Incompatible Android Gradle plugin */
+ public static final Issue GRADLE_PLUGIN_COMPATIBILITY = Issue.create(
+ "GradlePluginVersion", //$NON-NLS-1$
+ "Incompatible Android Gradle Plugin",
+ "Not all versions of the Android Gradle plugin are compatible with all versions " +
+ "of the SDK. If you update your tools, or if you are trying to open a project that " +
+ "was built with an old version of the tools, you may need to update your plugin " +
+ "version number.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Invalid or dangerous paths */
+ public static final Issue PATH = Issue.create(
+ "GradlePath", //$NON-NLS-1$
+ "Gradle Path Issues",
+ "Gradle build scripts are meant to be cross platform, so file paths use " +
+ "Unix-style path separators (a forward slash) rather than Windows path separators " +
+ "(a backslash). Similarly, to keep projects portable and repeatable, avoid " +
+ "using absolute paths on the system; keep files within the project instead. To " +
+ "share code between projects, consider creating an android-library and an AAR " +
+ "dependency",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Constructs the IDE support struggles with */
+ public static final Issue IDE_SUPPORT = Issue.create(
+ "GradleIdeError", //$NON-NLS-1$
+ "Gradle IDE Support Issues",
+ "Gradle is highly flexible, and there are things you can do in Gradle files which " +
+ "can make it hard or impossible for IDEs to properly handle the project. This lint " +
+ "check looks for constructs that potentially break IDE support.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Using + in versions */
+ public static final Issue PLUS = Issue.create(
+ "GradleDynamicVersion", //$NON-NLS-1$
+ "Gradle Dynamic Version",
+ "Using `+` in dependencies lets you automatically pick up the latest available " +
+ "version rather than a specific, named version. However, this is not recommended; " +
+ "your builds are not repeatable; you may have tested with a slightly different " +
+ "version than what the build server used. (Using a dynamic version as the major " +
+ "version number is more problematic than using it in the minor version position.)",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Accidentally calling a getter instead of your own methods */
+ public static final Issue GRADLE_GETTER = Issue.create(
+ "GradleGetter", //$NON-NLS-1$
+ "Gradle Implicit Getter Call",
+ "Gradle will let you replace specific constants in your build scripts with method " +
+ "calls, so you can for example dynamically compute a version string based on your " +
+ "current version control revision number, rather than hardcoding a number.\n" +
+ "\n" +
+ "When computing a version name, it's tempting to for example call the method to do " +
+ "that `getVersionName`. However, when you put that method call inside the " +
+ "`defaultConfig` block, you will actually be calling the Groovy getter for the " +
+ "`versionName` property instead. Therefore, you need to name your method something " +
+ "which does not conflict with the existing implicit getters. Consider using " +
+ "`compute` as a prefix instead of `get`.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Using incompatible versions */
+ public static final Issue COMPATIBILITY = Issue.create(
+ "GradleCompatible", //$NON-NLS-1$
+ "Incompatible Gradle Versions",
+
+ "There are some combinations of libraries, or tools and libraries, that are " +
+ "incompatible, or can lead to bugs. One such incompatibility is compiling with " +
+ "a version of the Android support libraries that is not the latest version (or in " +
+ "particular, a version lower than your `targetSdkVersion`.)",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Using a string where an integer is expected */
+ public static final Issue STRING_INTEGER = Issue.create(
+ "StringShouldBeInt", //$NON-NLS-1$
+ "String should be int",
+
+ "The properties `compileSdkVersion`, `minSdkVersion` and `targetSdkVersion` are " +
+ "usually numbers, but can be strings when you are using an add-on (in the case " +
+ "of `compileSdkVersion`) or a preview platform (for the other two properties).\n" +
+ "\n" +
+ "However, you can not use a number as a string (e.g. \"19\" instead of 19); that " +
+ "will result in a platform not found error message at build/sync time.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Attempting to use substitution with single quotes */
+ public static final Issue NOT_INTERPOLATED = Issue.create(
+ "NotInterpolated", //$NON-NLS-1$
+ "Incorrect Interpolation",
+
+ "To insert the value of a variable, you can use `${variable}` inside " +
+ "a string literal, but *only* if you are using double quotes!",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION)
+ .addMoreInfo("http://www.groovy-lang.org/syntax.html#_string_interpolation");
+
+ /** A newer version is available on a remote server */
+ public static final Issue REMOTE_VERSION = Issue.create(
+ "NewerVersionAvailable", //$NON-NLS-1$
+ "Newer Library Versions Available",
+ "This detector checks with a central repository to see if there are newer versions " +
+ "available for the dependencies used by this project. " +
+ "This is similar to the `GradleDependency` check, which checks for newer versions " +
+ "available in the Android SDK tools and libraries, but this works with any " +
+ "MavenCentral dependency, and connects to the library every time, which makes " +
+ "it more flexible but also *much* slower.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION).setEnabledByDefault(false);
+
+ /** Accidentally using octal numbers */
+ public static final Issue ACCIDENTAL_OCTAL = Issue.create(
+ "AccidentalOctal", //$NON-NLS-1$
+ "Accidental Octal",
+
+ "In Groovy, an integer literal that starts with a leading 0 will be interpreted " +
+ "as an octal number. That is usually (always?) an accident and can lead to " +
+ "subtle bugs, for example when used in the `versionCode` of an app.",
+
+ Category.CORRECTNESS,
+ 2,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** The Gradle plugin ID for Android applications */
+ public static final String APP_PLUGIN_ID = "com.android.application";
+ /** The Gradle plugin ID for Android libraries */
+ public static final String LIB_PLUGIN_ID = "com.android.library";
+
+ /** Previous plugin id for applications */
+ public static final String OLD_APP_PLUGIN_ID = "android";
+ /** Previous plugin id for libraries */
+ public static final String OLD_LIB_PLUGIN_ID = "android-library";
+
+ /** Group ID for GMS */
+ public static final String GMS_GROUP_ID = "com.google.android.gms";
+
+ private int mMinSdkVersion;
+ private int mCompileSdkVersion;
+ private int mTargetSdkVersion;
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @Override
+ @NonNull
+ public Speed getSpeed(@SuppressWarnings("UnusedParameters") @NonNull Issue issue) {
+ return issue == REMOTE_VERSION ? Speed.REALLY_SLOW : Speed.NORMAL;
+ }
+
+ // ---- Implements Detector.GradleScanner ----
+
+ @Override
+ public void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData) {
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ protected static boolean isInterestingBlock(
+ @NonNull String parent,
+ @Nullable String parentParent) {
+ return parent.equals("defaultConfig")
+ || parent.equals("android")
+ || parent.equals("dependencies")
+ || parent.equals("repositories")
+ || parentParent != null && parentParent.equals("buildTypes");
+ }
+
+ protected static boolean isInterestingStatement(
+ @NonNull String statement,
+ @Nullable String parent) {
+ return parent == null && statement.equals("apply");
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ protected static boolean isInterestingProperty(
+ @NonNull String property,
+ @SuppressWarnings("UnusedParameters")
+ @NonNull String parent,
+ @Nullable String parentParent) {
+ return property.equals("targetSdkVersion")
+ || property.equals("buildToolsVersion")
+ || property.equals("versionName")
+ || property.equals("versionCode")
+ || property.equals("compileSdkVersion")
+ || property.equals("minSdkVersion")
+ || property.equals("applicationIdSuffix")
+ || property.equals("packageName")
+ || property.equals("packageNameSuffix")
+ || parent.equals("dependencies");
+ }
+
+ protected void checkOctal(
+ @NonNull Context context,
+ @NonNull String value,
+ @NonNull Object cookie) {
+ if (value.length() >= 2
+ && value.charAt(0) == '0'
+ && (value.length() > 2 || value.charAt(1) >= '8'
+ && isInteger(value))
+ && context.isEnabled(ACCIDENTAL_OCTAL)) {
+ String message = "The leading 0 turns this number into octal which is probably "
+ + "not what was intended";
+ try {
+ long numericValue = Long.decode(value);
+ message += " (interpreted as " + numericValue + ")";
+ } catch (NumberFormatException nufe) {
+ message += " (and it is not a valid octal number)";
+ }
+ report(context, cookie, ACCIDENTAL_OCTAL, message);
+ }
+ }
+
+ /** Called with for example "android", "defaultConfig", "minSdkVersion", "7" */
+ @SuppressWarnings("UnusedDeclaration")
+ protected void checkDslPropertyAssignment(
+ @NonNull Context context,
+ @NonNull String property,
+ @NonNull String value,
+ @NonNull String parent,
+ @Nullable String parentParent,
+ @NonNull Object valueCookie,
+ @NonNull Object statementCookie) {
+ if (parent.equals("defaultConfig")) {
+ if (property.equals("targetSdkVersion")) {
+ int version = getIntLiteralValue(value, -1);
+ if (version > 0 && version < context.getClient().getHighestKnownApiLevel()) {
+ String message =
+ "Not targeting the latest versions of Android; compatibility " +
+ "modes apply. Consider testing and updating this version. " +
+ "Consult the android.os.Build.VERSION_CODES javadoc for details.";
+ report(context, valueCookie, TARGET_NEWER, message);
+ }
+ if (version > 0) {
+ mTargetSdkVersion = version;
+ checkTargetCompatibility(context, valueCookie);
+ } else {
+ checkIntegerAsString(context, value, valueCookie);
+ }
+ } else if (property.equals("minSdkVersion")) {
+ int version = getIntLiteralValue(value, -1);
+ if (version > 0) {
+ mMinSdkVersion = version;
+ } else {
+ checkIntegerAsString(context, value, valueCookie);
+ }
+ }
+
+ if (value.startsWith("0")) {
+ checkOctal(context, value, valueCookie);
+ }
+
+ if (property.equals("versionName") || property.equals("versionCode") &&
+ !isInteger(value) || !isStringLiteral(value)) {
+ // Method call -- make sure it does not match one of the getters in the
+ // configuration!
+ if ((value.equals("getVersionCode") ||
+ value.equals("getVersionName"))) {
+ String message = "Bad method name: pick a unique method name which does not "
+ + "conflict with the implicit getters for the defaultConfig "
+ + "properties. For example, try using the prefix compute- "
+ + "instead of get-.";
+ report(context, valueCookie, GRADLE_GETTER, message);
+ }
+ } else if (property.equals("packageName")) {
+ if (isModelOlderThan011(context)) {
+ return;
+ }
+ String message = "Deprecated: Replace 'packageName' with 'applicationId'";
+ report(context, getPropertyKeyCookie(valueCookie), DEPRECATED, message);
+ }
+ } else if (property.equals("compileSdkVersion") && parent.equals("android")) {
+ int version = getIntLiteralValue(value, -1);
+ if (version > 0) {
+ mCompileSdkVersion = version;
+ checkTargetCompatibility(context, valueCookie);
+ } else {
+ checkIntegerAsString(context, value, valueCookie);
+ }
+ } else if (property.equals("buildToolsVersion") && parent.equals("android")) {
+ String versionString = getStringLiteralValue(value);
+ if (versionString != null) {
+ Revision version = parseRevisionSilently(versionString);
+ if (version != null) {
+ Revision recommended = getLatestBuildTools(context.getClient(),
+ version.getMajor());
+ if (recommended != null && version.compareTo(recommended) < 0) {
+ // Keep in sync with {@link #getOldValue} and {@link #getNewValue}
+ String message = "Old buildToolsVersion " + version +
+ "; recommended version is " + recommended + " or later";
+ report(context, valueCookie, DEPENDENCY, message);
+ }
+ }
+ }
+ } else if (parent.equals("dependencies")) {
+ if (value.startsWith("files('") && value.endsWith("')")) {
+ String path = value.substring("files('".length(), value.length() - 2);
+ if (path.contains("\\\\")) {
+ String message = "Do not use Windows file separators in .gradle files; "
+ + "use / instead";
+ report(context, valueCookie, PATH, message);
+
+ } else if (new File(path.replace('/', File.separatorChar)).isAbsolute()) {
+ String message = "Avoid using absolute paths in .gradle files";
+ report(context, valueCookie, PATH, message);
+ }
+ } else {
+ String dependency = getStringLiteralValue(value);
+ if (dependency == null) {
+ dependency = getNamedDependency(value);
+ }
+ // If the dependency is a GString (i.e. it uses Groovy variable substitution,
+ // with a $variable_name syntax) then don't try to parse it.
+ if (dependency != null) {
+ GradleCoordinate gc = GradleCoordinate.parseCoordinateString(dependency);
+ if (gc != null && dependency.contains("$")) {
+ if (value.startsWith("'") && value.endsWith("'") &&
+ context.isEnabled(NOT_INTERPOLATED)) {
+ String message = "It looks like you are trying to substitute a "
+ + "version variable, but using single quotes ('). For Groovy "
+ + "string interpolation you must use double quotes (\").";
+ report(context, statementCookie, NOT_INTERPOLATED, message);
+ }
+
+ gc = resolveCoordinate(context, gc);
+ }
+ if (gc != null) {
+ if (gc.acceptsGreaterRevisions()) {
+ String message = "Avoid using + in version numbers; can lead "
+ + "to unpredictable and unrepeatable builds (" + dependency + ")";
+ report(context, valueCookie, PLUS, message);
+ }
+ if (!dependency.startsWith(SdkConstants.GRADLE_PLUGIN_NAME) ||
+ !checkGradlePluginDependency(context, gc, valueCookie)) {
+ checkDependency(context, gc, valueCookie);
+ }
+ }
+ }
+ }
+ } else if (property.equals("packageNameSuffix")) {
+ if (isModelOlderThan011(context)) {
+ return;
+ }
+ String message = "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'";
+ report(context, getPropertyKeyCookie(valueCookie), DEPRECATED, message);
+ } else if (property.equals("applicationIdSuffix")) {
+ String suffix = getStringLiteralValue(value);
+ if (suffix != null && !suffix.startsWith(".")) {
+ String message = "Package suffix should probably start with a \".\"";
+ report(context, valueCookie, PATH, message);
+ }
+ }
+ }
+
+ @Nullable
+ private static GradleCoordinate resolveCoordinate(@NonNull Context context,
+ @NonNull GradleCoordinate gc) {
+ assert gc.getRevision().contains("$") : gc.getRevision();
+ Variant variant = context.getProject().getCurrentVariant();
+ if (variant != null) {
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+ for (AndroidLibrary library : dependencies.getLibraries()) {
+ MavenCoordinates mc = library.getResolvedCoordinates();
+ if (mc != null
+ && mc.getGroupId().equals(gc.getGroupId())
+ && mc.getArtifactId().equals(gc.getArtifactId())) {
+ List<RevisionComponent> revisions =
+ GradleCoordinate.parseRevisionNumber(mc.getVersion());
+ if (!revisions.isEmpty()) {
+ return new GradleCoordinate(mc.getGroupId(), mc.getArtifactId(),
+ revisions, null);
+ }
+ break;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // Convert a long-hand dependency, like
+ // group: 'com.android.support', name: 'support-v4', version: '21.0.+'
+ // into an equivalent short-hand dependency, like
+ // com.android.support:support-v4:21.0.+
+ @VisibleForTesting
+ @Nullable
+ static String getNamedDependency(@NonNull String expression) {
+ //if (value.startsWith("group: 'com.android.support', name: 'support-v4', version: '21.0.+'"))
+ if (expression.indexOf(',') != -1 && expression.contains("version:")) {
+ String artifact = null;
+ String group = null;
+ String version = null;
+ Splitter splitter = Splitter.on(',').omitEmptyStrings().trimResults();
+ for (String property : splitter.split(expression)) {
+ int colon = property.indexOf(':');
+ if (colon == -1) {
+ return null;
+ }
+ char quote = '\'';
+ int valueStart = property.indexOf(quote, colon + 1);
+ if (valueStart == -1) {
+ quote = '"';
+ valueStart = property.indexOf(quote, colon + 1);
+ }
+ if (valueStart == -1) {
+ // For example, "transitive: false"
+ continue;
+ }
+ valueStart++;
+ int valueEnd = property.indexOf(quote, valueStart);
+ if (valueEnd == -1) {
+ return null;
+ }
+ String value = property.substring(valueStart, valueEnd);
+ if (property.startsWith("group:")) {
+ group = value;
+ } else if (property.startsWith("name:")) {
+ artifact = value;
+ } else if (property.startsWith("version:")) {
+ version = value;
+ }
+ }
+
+ if (artifact != null && group != null && version != null) {
+ return group + ':' + artifact + ':' + version;
+ }
+ }
+
+ return null;
+ }
+
+ private void checkIntegerAsString(Context context, String value, Object valueCookie) {
+ // When done developing with a preview platform you might be tempted to switch from
+ // compileSdkVersion 'android-G'
+ // to
+ // compileSdkVersion '19'
+ // but that won't work; it needs to be
+ // compileSdkVersion 19
+ String string = getStringLiteralValue(value);
+ if (isNumberString(string)) {
+ String quote = Character.toString(value.charAt(0));
+ String message = String.format("Use an integer rather than a string here "
+ + "(replace %1$s%2$s%1$s with just %2$s)", quote, string);
+ report(context, valueCookie, STRING_INTEGER, message);
+ }
+ }
+
+ /**
+ * Given an error message produced by this lint detector for the given issue type,
+ * returns the old value to be replaced in the source code.
+ * <p>
+ * Intended for IDE quickfix implementations.
+ *
+ * @param issue the corresponding issue
+ * @param errorMessage the error message associated with the error
+ * @param format the format of the error message
+ * @return the corresponding old value, or null if not recognized
+ */
+ @Nullable
+ public static String getOldValue(@NonNull Issue issue, @NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+
+ // Consider extracting all the error strings as constants and handling this
+ // using the LintUtils#getFormattedParameters() method to pull back out the information
+ if (issue == DEPENDENCY) {
+ // "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0"
+ if (errorMessage.startsWith("A newer ")) {
+ return findSubstring(errorMessage, " than ", " ");
+ }
+ if (errorMessage.startsWith("Old buildToolsVersion ")) {
+ return findSubstring(errorMessage, "Old buildToolsVersion ", ";");
+ }
+ // "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)"
+ return findSubstring(errorMessage, "targetSdkVersion (", ")");
+ } else if (issue == STRING_INTEGER) {
+ return findSubstring(errorMessage, "replace ", " with ");
+ } else if (issue == DEPRECATED) {
+ if (errorMessage.contains(APP_PLUGIN_ID) &&
+ errorMessage.contains(OLD_APP_PLUGIN_ID)) {
+ return OLD_APP_PLUGIN_ID;
+ } else if (errorMessage.contains(LIB_PLUGIN_ID) &&
+ errorMessage.contains(OLD_LIB_PLUGIN_ID)) {
+ return OLD_LIB_PLUGIN_ID;
+ }
+ // "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'"
+ return findSubstring(errorMessage, "Replace '", "'");
+ } else if (issue == PLUS) {
+ return findSubstring(errorMessage, "(", ")");
+ } else if (issue == COMPATIBILITY) {
+ if (errorMessage.startsWith("Version 5.2.08")) {
+ return "5.2.08";
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Given an error message produced by this lint detector for the given issue type,
+ * returns the new value to be put into the source code.
+ * <p>
+ * Intended for IDE quickfix implementations.
+ *
+ * @param issue the corresponding issue
+ * @param errorMessage the error message associated with the error
+ * @param format the format of the error message
+ * @return the corresponding new value, or null if not recognized
+ */
+ @Nullable
+ public static String getNewValue(@NonNull Issue issue, @NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+
+ if (issue == DEPENDENCY) {
+ // "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0"
+ if (errorMessage.startsWith("A newer ")) {
+ return findSubstring(errorMessage, " is available: ", null);
+ }
+ if (errorMessage.startsWith("Old buildToolsVersion ")) {
+ return findSubstring(errorMessage, " version is ", " ");
+ }
+ // "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)"
+ return findSubstring(errorMessage, "compileSdkVersion (", ")");
+ } else if (issue == STRING_INTEGER) {
+ return findSubstring(errorMessage, " just ", ")");
+ } else if (issue == DEPRECATED) {
+ if (errorMessage.contains(APP_PLUGIN_ID) &&
+ errorMessage.contains(OLD_APP_PLUGIN_ID)) {
+ return APP_PLUGIN_ID;
+ } else if (errorMessage.contains(LIB_PLUGIN_ID) &&
+ errorMessage.contains(OLD_LIB_PLUGIN_ID)) {
+ return LIB_PLUGIN_ID;
+ }
+ // "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'"
+ return findSubstring(errorMessage, " with '", "'");
+ } else if (issue == COMPATIBILITY) {
+ if (errorMessage.startsWith("Version 5.2.08")) {
+ return findSubstring(errorMessage, "Use version ", " ");
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean isNumberString(@Nullable String s) {
+ if (s == null || s.isEmpty()) {
+ return false;
+ }
+ for (int i = 0, n = s.length(); i < n; i++) {
+ if (!Character.isDigit(s.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected void checkMethodCall(
+ @NonNull Context context,
+ @NonNull String statement,
+ @Nullable String parent,
+ @NonNull Map<String, String> namedArguments,
+ @SuppressWarnings("UnusedParameters")
+ @NonNull List<String> unnamedArguments,
+ @NonNull Object cookie) {
+ String plugin = namedArguments.get("plugin");
+ if (statement.equals("apply") && parent == null) {
+ boolean isOldAppPlugin = OLD_APP_PLUGIN_ID.equals(plugin);
+ if (isOldAppPlugin || OLD_LIB_PLUGIN_ID.equals(plugin)) {
+ String replaceWith = isOldAppPlugin ? APP_PLUGIN_ID : LIB_PLUGIN_ID;
+ String message = String.format("'%1$s' is deprecated; use '%2$s' instead", plugin,
+ replaceWith);
+ report(context, cookie, DEPRECATED, message);
+ }
+ }
+ }
+
+ @Nullable
+ private static Revision parseRevisionSilently(String versionString) {
+ try {
+ return Revision.parseRevision(versionString);
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ private static boolean isModelOlderThan011(@NonNull Context context) {
+ return LintUtils.isModelOlderThan(context.getProject().getGradleProjectModel(), 0, 11, 0);
+ }
+
+ private static int sMajorBuildTools;
+ private static Revision sLatestBuildTools;
+
+ /** Returns the latest build tools installed for the given major version.
+ * We just cache this once; we don't need to be accurate in the sense that if the
+ * user opens the SDK manager and installs a more recent version, we capture this in
+ * the same IDE session.
+ *
+ * @param client the associated client
+ * @param major the major version of build tools to look up (e.g. typically 18, 19, ...)
+ * @return the corresponding highest known revision
+ */
+ @Nullable
+ private static Revision getLatestBuildTools(@NonNull LintClient client, int major) {
+ if (major != sMajorBuildTools) {
+ sMajorBuildTools = major;
+
+ List<Revision> revisions = Lists.newArrayList();
+ if (major == 23) {
+ revisions.add(new Revision(23, 0, 1));
+ } else if (major == 22) {
+ revisions.add(new Revision(22, 0, 1));
+ } else if (major == 21) {
+ revisions.add(new Revision(21, 1, 2));
+ } else if (major == 20) {
+ revisions.add(new Revision(20));
+ } else if (major == 19) {
+ revisions.add(new Revision(19, 1));
+ } else if (major == 18) {
+ revisions.add(new Revision(18, 1, 1));
+ }
+ // The above versions can go stale.
+ // Check if a more recent one is installed. (The above are still useful for
+ // people who haven't updated with the SDK manager recently.)
+ File sdkHome = client.getSdkHome();
+ if (sdkHome != null) {
+ File[] dirs = new File(sdkHome, FD_BUILD_TOOLS).listFiles();
+ if (dirs != null) {
+ for (File dir : dirs) {
+ String name = dir.getName();
+ if (!dir.isDirectory() || !Character.isDigit(name.charAt(0))) {
+ continue;
+ }
+ Revision v = parseRevisionSilently(name);
+ if (v != null && v.getMajor() == major) {
+ revisions.add(v);
+ }
+ }
+ }
+ }
+
+ if (!revisions.isEmpty()) {
+ sLatestBuildTools = Collections.max(revisions);
+ }
+ }
+
+ return sLatestBuildTools;
+ }
+
+ private void checkTargetCompatibility(Context context, Object cookie) {
+ if (mCompileSdkVersion > 0 && mTargetSdkVersion > 0
+ && mTargetSdkVersion > mCompileSdkVersion) {
+ // NOTE: Keep this in sync with {@link #getOldValue} and {@link #getNewValue}
+ String message = "The targetSdkVersion (" + mTargetSdkVersion
+ + ") should not be higher than the compileSdkVersion ("
+ + mCompileSdkVersion + ")";
+ report(context, cookie, DEPENDENCY, message);
+ }
+ }
+
+ @Nullable
+ private static String getStringLiteralValue(@NonNull String value) {
+ if (value.length() > 2 && (value.startsWith("'") && value.endsWith("'") ||
+ value.startsWith("\"") && value.endsWith("\""))) {
+ return value.substring(1, value.length() - 1);
+ }
+
+ return null;
+ }
+
+ private static int getIntLiteralValue(@NonNull String value, int defaultValue) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ private static boolean isInteger(String token) {
+ return token.matches("\\d+");
+ }
+
+ private static boolean isStringLiteral(String token) {
+ return token.startsWith("\"") && token.endsWith("\"") ||
+ token.startsWith("'") && token.endsWith("'");
+ }
+
+ private void checkDependency(
+ @NonNull Context context,
+ @NonNull GradleCoordinate dependency,
+ @NonNull Object cookie) {
+ if ("com.android.support".equals(dependency.getGroupId())) {
+ checkSupportLibraries(context, dependency, cookie);
+ if (mMinSdkVersion >= 14 && "appcompat-v7".equals(dependency.getArtifactId())
+ && mCompileSdkVersion >= 1 && mCompileSdkVersion < 21) {
+ report(context, cookie, DEPENDENCY,
+ "Using the appcompat library when minSdkVersion >= 14 and "
+ + "compileSdkVersion < 21 is not necessary");
+ }
+ return;
+ } else if (GMS_GROUP_ID.equals(dependency.getGroupId())
+ && dependency.getArtifactId() != null) {
+
+ // 5.2.08 is not supported; special case and warn about this
+ if ("5.2.08".equals(dependency.getRevision()) && context.isEnabled(COMPATIBILITY)) {
+ // This specific version is actually a preview version which should
+ // not be used (https://code.google.com/p/android/issues/detail?id=75292)
+ String version = "6.1.11";
+ // Try to find a more recent available version, if one is available
+ File sdkHome = context.getClient().getSdkHome();
+ File repository = SdkMavenRepository.GOOGLE.getRepositoryLocation(sdkHome, true);
+ if (repository != null) {
+ GradleCoordinate max = SdkMavenRepository.getHighestInstalledVersion(
+ dependency.getGroupId(), dependency.getArtifactId(), repository,
+ null, false);
+ if (max != null) {
+ if (COMPARE_PLUS_HIGHER.compare(dependency, max) < 0) {
+ version = max.getRevision();
+ }
+ }
+ }
+ String message = String.format("Version `5.2.08` should not be used; the app "
+ + "can not be published with this version. Use version `%1$s` "
+ + "instead.", version);
+ report(context, cookie, COMPATIBILITY, message);
+ }
+
+ checkPlayServices(context, dependency, cookie);
+ return;
+ }
+
+ Revision version = null;
+ Issue issue = DEPENDENCY;
+ if ("com.android.tools.build".equals(dependency.getGroupId()) &&
+ "gradle".equals(dependency.getArtifactId())) {
+ try {
+ Revision v =
+ Revision.parseRevision(GRADLE_PLUGIN_RECOMMENDED_VERSION);
+ if (!v.isPreview()) {
+ version = getNewerRevision(dependency, v);
+ }
+ } catch (NumberFormatException e) {
+ context.log(e, null);
+ }
+ } else if ("com.google.guava".equals(dependency.getGroupId()) &&
+ "guava".equals(dependency.getArtifactId())) {
+ version = getNewerRevision(dependency, new Revision(18, 0));
+ } else if ("com.google.code.gson".equals(dependency.getGroupId()) &&
+ "gson".equals(dependency.getArtifactId())) {
+ version = getNewerRevision(dependency, new Revision(2, 4));
+ } else if ("org.apache.httpcomponents".equals(dependency.getGroupId()) &&
+ "httpclient".equals(dependency.getArtifactId())) {
+ version = getNewerRevision(dependency, new Revision(4, 3, 5));
+ }
+
+ // Network check for really up to date libraries? Only done in batch mode
+ if (context.getScope().size() > 1 && context.isEnabled(REMOTE_VERSION)) {
+ Revision latest = getLatestVersionFromRemoteRepo(context.getClient(), dependency,
+ dependency.isPreview());
+ if (latest != null && isOlderThan(dependency, latest.getMajor(), latest.getMinor(),
+ latest.getMicro())) {
+ version = latest;
+ issue = REMOTE_VERSION;
+ }
+ }
+
+ if (version != null) {
+ String message = getNewerVersionAvailableMessage(dependency, version);
+ report(context, cookie, issue, message);
+ }
+ }
+
+ private static String getNewerVersionAvailableMessage(GradleCoordinate dependency,
+ Revision version) {
+ return getNewerVersionAvailableMessage(dependency, version.toString());
+ }
+
+ private static String getNewerVersionAvailableMessage(GradleCoordinate dependency,
+ String version) {
+ // NOTE: Keep this in sync with {@link #getOldValue} and {@link #getNewValue}
+ return "A newer version of " + dependency.getGroupId() + ":" +
+ dependency.getArtifactId() + " than " + dependency.getRevision() +
+ " is available: " + version;
+ }
+
+ /** TODO: Cache these results somewhere! */
+ @Nullable
+ public static Revision getLatestVersionFromRemoteRepo(@NonNull LintClient client,
+ @NonNull GradleCoordinate dependency, boolean allowPreview) {
+ return getLatestVersionFromRemoteRepo(client, dependency, true, allowPreview);
+ }
+
+ @Nullable
+ private static Revision getLatestVersionFromRemoteRepo(@NonNull LintClient client,
+ @NonNull GradleCoordinate dependency, boolean firstRowOnly, boolean allowPreview) {
+ String groupId = dependency.getGroupId();
+ String artifactId = dependency.getArtifactId();
+ if (groupId == null || artifactId == null) {
+ return null;
+ }
+ StringBuilder query = new StringBuilder();
+ String encoding = UTF_8.name();
+ try {
+ query.append("http://search.maven.org/solrsearch/select?q=g:%22");
+ query.append(URLEncoder.encode(groupId, encoding));
+ query.append("%22+AND+a:%22");
+ query.append(URLEncoder.encode(artifactId, encoding));
+ } catch (UnsupportedEncodingException ee) {
+ return null;
+ }
+ query.append("%22&core=gav");
+ if (firstRowOnly) {
+ query.append("&rows=1");
+ }
+ query.append("&wt=json");
+
+ String response = readUrlData(client, dependency, query.toString());
+ if (response == null) {
+ return null;
+ }
+
+ // Sample response:
+ // {
+ // "responseHeader": {
+ // "status": 0,
+ // "QTime": 0,
+ // "params": {
+ // "fl": "id,g,a,v,p,ec,timestamp,tags",
+ // "sort": "score desc,timestamp desc,g asc,a asc,v desc",
+ // "indent": "off",
+ // "q": "g:\"com.google.guava\" AND a:\"guava\"",
+ // "core": "gav",
+ // "wt": "json",
+ // "rows": "1",
+ // "version": "2.2"
+ // }
+ // },
+ // "response": {
+ // "numFound": 37,
+ // "start": 0,
+ // "docs": [{
+ // "id": "com.google.guava:guava:17.0",
+ // "g": "com.google.guava",
+ // "a": "guava",
+ // "v": "17.0",
+ // "p": "bundle",
+ // "timestamp": 1398199666000,
+ // "tags": ["spec", "libraries", "classes", "google", "code"],
+ // "ec": ["-javadoc.jar", "-sources.jar", ".jar", "-site.jar", ".pom"]
+ // }]
+ // }
+ // }
+
+ // Look for version info: This is just a cheap skim of the above JSON results
+ boolean foundPreview = false;
+ int index = response.indexOf("\"response\""); //$NON-NLS-1$
+ while (index != -1) {
+ index = response.indexOf("\"v\":", index); //$NON-NLS-1$
+ if (index != -1) {
+ index += 4;
+ int start = response.indexOf('"', index) + 1;
+ int end = response.indexOf('"', start + 1);
+ if (end > start && start >= 0) {
+ Revision revision = parseRevisionSilently(response.substring(start, end));
+ if (revision != null) {
+ foundPreview = revision.isPreview();
+ if (allowPreview || !foundPreview) {
+ return revision;
+ }
+ }
+ }
+ }
+ }
+
+ if (!allowPreview && foundPreview && firstRowOnly) {
+ // Recurse: search more than the first row this time to see if we can find a
+ // non-preview version
+ return getLatestVersionFromRemoteRepo(client, dependency, false, false);
+ }
+
+ return null;
+ }
+
+ /** Normally null; used for testing */
+ @Nullable
+ @VisibleForTesting
+ static Map<String,String> sMockData;
+
+ @Nullable
+ private static String readUrlData(
+ @NonNull LintClient client,
+ @NonNull GradleCoordinate dependency,
+ @NonNull String query) {
+ // For unit testing: avoid network as well as unexpected new versions
+ if (sMockData != null) {
+ String value = sMockData.get(query);
+ assert value != null : query;
+ return value;
+ }
+
+ try {
+ URL url = new URL(query);
+
+ URLConnection connection = client.openConnection(url);
+ if (connection == null) {
+ return null;
+ }
+ try {
+ InputStream is = connection.getInputStream();
+ if (is == null) {
+ return null;
+ }
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8));
+ try {
+ StringBuilder sb = new StringBuilder(500);
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ sb.append('\n');
+ }
+
+ return sb.toString();
+ } finally {
+ reader.close();
+ }
+ } finally {
+ client.closeConnection(connection);
+ }
+ } catch (IOException ioe) {
+ client.log(ioe, "Could not connect to maven central to look up the " +
+ "latest available version for %1$s", dependency);
+ return null;
+ }
+ }
+
+ private boolean checkGradlePluginDependency(Context context, GradleCoordinate dependency,
+ Object cookie) {
+ GradleCoordinate latestPlugin = GradleCoordinate.parseCoordinateString(
+ SdkConstants.GRADLE_PLUGIN_NAME +
+ GRADLE_PLUGIN_MINIMUM_VERSION);
+ if (GradleCoordinate.COMPARE_PLUS_HIGHER.compare(dependency, latestPlugin) < 0) {
+ String message = "You must use a newer version of the Android Gradle plugin. The "
+ + "minimum supported version is " + GRADLE_PLUGIN_MINIMUM_VERSION +
+ " and the recommended version is " + GRADLE_PLUGIN_RECOMMENDED_VERSION;
+ report(context, cookie, GRADLE_PLUGIN_COMPATIBILITY, message);
+ return true;
+ }
+ return false;
+ }
+
+ private void checkSupportLibraries(Context context, GradleCoordinate dependency,
+ Object cookie) {
+ String groupId = dependency.getGroupId();
+ String artifactId = dependency.getArtifactId();
+ assert groupId != null && artifactId != null;
+
+ if (mCompileSdkVersion >= 18 && dependency.getMajorVersion() != mCompileSdkVersion &&
+ dependency.getMajorVersion() != GradleCoordinate.PLUS_REV_VALUE &&
+ // The multidex library doesn't follow normal supportlib numbering scheme
+ !dependency.getArtifactId().startsWith("multidex") &&
+ context.isEnabled(COMPATIBILITY)) {
+ String message = "This support library should not use a different version ("
+ + dependency.getMajorVersion() + ") than the `compileSdkVersion` ("
+ + mCompileSdkVersion + ")";
+ report(context, cookie, COMPATIBILITY, message);
+ } else if (mTargetSdkVersion > 0 && dependency.getMajorVersion() < mTargetSdkVersion &&
+ dependency.getMajorVersion() != GradleCoordinate.PLUS_REV_VALUE &&
+ // The multidex library doesn't follow normal supportlib numbering scheme
+ !dependency.getArtifactId().startsWith("multidex") &&
+ context.isEnabled(COMPATIBILITY)) {
+ String message = "This support library should not use a lower version ("
+ + dependency.getMajorVersion() + ") than the `targetSdkVersion` ("
+ + mTargetSdkVersion + ")";
+ report(context, cookie, COMPATIBILITY, message);
+ }
+
+ // Check to make sure you have the Android support repository installed
+ File sdkHome = context.getClient().getSdkHome();
+ File repository = SdkMavenRepository.ANDROID.getRepositoryLocation(sdkHome, true);
+ if (repository == null) {
+ report(context, cookie, DEPENDENCY,
+ "Dependency on a support library, but the SDK installation does not "
+ + "have the \"Extras > Android Support Repository\" installed. "
+ + "Open the SDK manager and install it.");
+ } else {
+ checkLocalMavenVersions(context, dependency, cookie, groupId, artifactId,
+ repository);
+ }
+ }
+
+ /**
+ * If incrementally editing a single build.gradle file, tracks whether we've already
+ * transitively checked GMS versions such that we don't flag the same error on every
+ * single dependency declaration
+ */
+ private boolean mCheckedGms;
+
+ private void checkPlayServices(Context context, GradleCoordinate dependency, Object cookie) {
+ String groupId = dependency.getGroupId();
+ String artifactId = dependency.getArtifactId();
+ assert groupId != null && artifactId != null;
+
+ File sdkHome = context.getClient().getSdkHome();
+ File repository = SdkMavenRepository.GOOGLE.getRepositoryLocation(sdkHome, true);
+ if (repository == null) {
+ report(context, cookie, DEPENDENCY,
+ "Dependency on Play Services, but the SDK installation does not "
+ + "have the \"Extras > Google Repository\" installed. "
+ + "Open the SDK manager and install it.");
+ } else {
+ checkLocalMavenVersions(context, dependency, cookie, groupId, artifactId,
+ repository);
+ }
+
+ if (!mCheckedGms) {
+ mCheckedGms = true;
+ // Incremental analysis only? If so, tie the check to
+ // a specific GMS play dependency if only, such that it's highlighted
+ // in the editor
+ if (!context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+ // Incremental editing: try flagging them in this file!
+ checkConsistentPlayServices(context, cookie);
+ }
+ }
+ }
+
+ private void checkConsistentPlayServices(@NonNull Context context,
+ @Nullable Object cookie) {
+ // Make sure we're using a consistent version across all play services libraries
+ // (b/22709708)
+
+ Project project = context.getMainProject();
+ if (!project.isGradleProject()) {
+ return;
+ }
+ Variant variant = project.getCurrentVariant();
+ if (variant == null) {
+ return;
+ }
+ AndroidArtifact artifact = variant.getMainArtifact();
+ Collection<AndroidLibrary> libraries = artifact.getDependencies().getLibraries();
+ Multimap<String, MavenCoordinates> versionToCoordinate = ArrayListMultimap.create();
+ for (AndroidLibrary library : libraries) {
+ addGmsLibraryVersions(versionToCoordinate, library);
+ }
+ Set<String> versions = versionToCoordinate.keySet();
+ if (versions.size() > 1) {
+ List<String> sortedVersions = Lists.newArrayList(versions);
+ Collections.sort(sortedVersions, Collections.reverseOrder());
+ MavenCoordinates c1 = versionToCoordinate.get(sortedVersions.get(0)).iterator().next();
+ MavenCoordinates c2 = versionToCoordinate.get(sortedVersions.get(1)).iterator().next();
+ // Not using toString because in the IDE, these are model proxies which display garbage output
+ String example1 = c1.getGroupId() + ":" + c1.getArtifactId() + ":" + c1 .getVersion();
+ String example2 = c2.getGroupId() + ":" + c2.getArtifactId() + ":" + c2 .getVersion();
+ String message = "All com.google.android.gms libraries must use the exact same "
+ + "version specification (mixing versions can lead to runtime crashes). "
+ + "Found versions " + Joiner.on(", ").join(sortedVersions) + ". "
+ + "Examples include " + example1 + " and " + example2;
+
+ if (cookie != null) {
+ report(context, cookie, COMPATIBILITY, message);
+ } else {
+ context.report(COMPATIBILITY, guessGradleLocation(context.getProject()), message);
+ }
+ }
+ }
+
+ private static void addGmsLibraryVersions(@NonNull Multimap<String, MavenCoordinates> versions,
+ @NonNull AndroidLibrary library) {
+ MavenCoordinates coordinates = library.getResolvedCoordinates();
+ if (coordinates != null && coordinates.getGroupId().equals(GMS_GROUP_ID)) {
+ versions.put(coordinates.getVersion(), coordinates);
+ }
+
+ for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+ addGmsLibraryVersions(versions, dependency);
+ }
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (context.getProject() == context.getMainProject() &&
+ // Full analysis? Don't tie check to any specific Gradle DSL element
+ context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+ checkConsistentPlayServices(context, null);
+ }
+ }
+
+ private void checkLocalMavenVersions(Context context, GradleCoordinate dependency,
+ Object cookie, String groupId, String artifactId, File repository) {
+ GradleCoordinate max = SdkMavenRepository.getHighestInstalledVersion(groupId, artifactId,
+ repository, null, false);
+ if (max != null) {
+ if (COMPARE_PLUS_HIGHER.compare(dependency, max) < 0
+ && context.isEnabled(DEPENDENCY)) {
+ String message = getNewerVersionAvailableMessage(dependency, max.getRevision());
+ report(context, cookie, DEPENDENCY, message);
+ }
+ }
+ }
+
+ private static Revision getNewerRevision(@NonNull GradleCoordinate dependency,
+ @NonNull Revision revision) {
+ assert dependency.getGroupId() != null;
+ assert dependency.getArtifactId() != null;
+ GradleCoordinate coordinate;
+ if (revision.isPreview()) {
+ String coordinateString = dependency.getGroupId()
+ + ":" + dependency.getArtifactId()
+ + ":" + revision.toString();
+ coordinate = GradleCoordinate.parseCoordinateString(coordinateString);
+ } else {
+ coordinate = new GradleCoordinate(dependency.getGroupId(), dependency.getArtifactId(),
+ revision.getMajor(), revision.getMinor(), revision.getMicro());
+ }
+ if (COMPARE_PLUS_HIGHER.compare(dependency, coordinate) < 0) {
+ return revision;
+ } else {
+ return null;
+ }
+ }
+
+ private static boolean isOlderThan(@NonNull GradleCoordinate dependency, int major, int minor,
+ int micro) {
+ assert dependency.getGroupId() != null;
+ assert dependency.getArtifactId() != null;
+ return COMPARE_PLUS_HIGHER.compare(dependency,
+ new GradleCoordinate(dependency.getGroupId(),
+ dependency.getArtifactId(), major, minor, micro)) < 0;
+ }
+
+ private void report(@NonNull Context context, @NonNull Object cookie, @NonNull Issue issue,
+ @NonNull String message) {
+ if (context.isEnabled(issue)) {
+ // Suppressed?
+ // Temporarily unconditionally checking for suppress comments in Gradle files
+ // since Studio insists on an AndroidLint id prefix
+ boolean checkComments = /*context.getClient().checkForSuppressComments()
+ &&*/ context.containsCommentSuppress();
+ if (checkComments) {
+ int startOffset = getStartOffset(context, cookie);
+ if (startOffset >= 0 && context.isSuppressedWithComment(startOffset, issue)) {
+ return;
+ }
+ }
+
+ context.report(issue, createLocation(context, cookie), message);
+ }
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ @NonNull
+ protected Object getPropertyKeyCookie(@NonNull Object cookie) {
+ return cookie;
+ }
+
+ @SuppressWarnings({"MethodMayBeStatic", "UnusedDeclaration"})
+ @NonNull
+ protected Object getPropertyPairCookie(@NonNull Object cookie) {
+ return cookie;
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ protected int getStartOffset(@NonNull Context context, @NonNull Object cookie) {
+ return -1;
+ }
+
+ @SuppressWarnings({"MethodMayBeStatic", "UnusedParameters"})
+ protected Location createLocation(@NonNull Context context, @NonNull Object cookie) {
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
new file mode 100644
index 0000000..3b8a41c
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+
+/**
+ * Checks that Handler implementations are top level classes or static.
+ * See the corresponding check in the android.os.Handler source code.
+ */
+public class HandlerDetector extends Detector implements Detector.JavaScanner {
+
+ /** Potentially leaking handlers */
+ public static final Issue ISSUE = Issue.create(
+ "HandlerLeak", //$NON-NLS-1$
+ "Handler reference leaks",
+
+ "Since this Handler is declared as an inner class, it may prevent the outer " +
+ "class from being garbage collected. If the Handler is using a Looper or " +
+ "MessageQueue for a thread other than the main thread, then there is no issue. " +
+ "If the Handler is using the Looper or MessageQueue of the main thread, you " +
+ "need to fix your Handler declaration, as follows: Declare the Handler as a " +
+ "static class; In the outer class, instantiate a WeakReference to the outer " +
+ "class and pass this object to your Handler when you instantiate the Handler; " +
+ "Make all references to members of the outer class using the WeakReference object.",
+
+ Category.PERFORMANCE,
+ 4,
+ Severity.WARNING,
+ new Implementation(
+ HandlerDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ private static final String LOOPER_CLS = "android.os.Looper";
+ private static final String HANDLER_CLS = "android.os.Handler";
+
+ /** Constructs a new {@link HandlerDetector} */
+ public HandlerDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Collections.singletonList(HANDLER_CLS);
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
+ @NonNull Node node, @NonNull ResolvedClass cls) {
+ if (!isInnerClass(declaration)) {
+ return;
+ }
+
+ if (isStaticClass(declaration)) {
+ return;
+ }
+
+ // Only flag handlers using the default looper
+ ConstructorInvocation invocation = null;
+ Node current = node;
+ while (current != null) {
+ if (current instanceof ConstructorInvocation) {
+ invocation = (ConstructorInvocation) current;
+ break;
+ } else if (current instanceof MethodDeclaration ||
+ current instanceof ClassDeclaration) {
+ break;
+ }
+ current = current.getParent();
+ }
+
+ if (invocation != null) {
+ for (Expression expression : invocation.astArguments()) {
+ TypeDescriptor type = context.getType(expression);
+ if (type != null && type.matchesName(LOOPER_CLS)) {
+ return;
+ }
+ }
+ } else if (hasLooperConstructorParameter(cls)) {
+ // This is an inner class which takes a Looper parameter:
+ // possibly used correctly from elsewhere
+ return;
+ }
+
+ Location location;
+ Node locationNode;
+ if (node instanceof ClassDeclaration) {
+ locationNode = node;
+ location = context.getLocation(((ClassDeclaration) node).astName());
+ } else if (node instanceof NormalTypeBody
+ && node.getParent() instanceof ConstructorInvocation) {
+ ConstructorInvocation parent = (ConstructorInvocation)node.getParent();
+ locationNode = parent;
+ location = context.getRangeLocation(parent, 0, parent.astTypeReference(), 0);
+ } else {
+ locationNode = node;
+ location = context.getLocation(node);;
+ }
+
+ //noinspection VariableNotUsedInsideIf
+ context.report(ISSUE, locationNode, location, String.format(
+ "This Handler class should be static or leaks might occur (%1$s)",
+ declaration == null ? "anonymous " + cls.getName() : cls.getName()));
+ }
+
+ private static boolean isInnerClass(@Nullable ClassDeclaration node) {
+ return node == null || // null class declarations means anonymous inner class
+ JavaContext.getParentOfType(node, ClassDeclaration.class, true) != null;
+ }
+
+ private static boolean isStaticClass(@Nullable ClassDeclaration node) {
+ if (node == null) {
+ // A null class declaration means anonymous inner class, and these can't be static
+ return false;
+ }
+
+ int flags = node.astModifiers().getEffectiveModifierFlags();
+ return (flags & Modifier.STATIC) != 0;
+ }
+
+ private static boolean hasLooperConstructorParameter(@NonNull ResolvedClass cls) {
+ for (ResolvedMethod constructor : cls.getConstructors()) {
+ for (int i = 0, n = constructor.getArgumentCount(); i < n; i++) {
+ TypeDescriptor type = constructor.getArgumentType(i);
+ if (type.matchesSignature(LOOPER_CLS)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
new file mode 100644
index 0000000..6c2e3b4
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CONTENT_DESCRIPTION;
+import static com.android.SdkConstants.ATTR_HINT;
+import static com.android.SdkConstants.ATTR_LABEL;
+import static com.android.SdkConstants.ATTR_PROMPT;
+import static com.android.SdkConstants.ATTR_TEXT;
+import static com.android.SdkConstants.ATTR_TITLE;
+import static com.android.tools.lint.checks.RestrictionsDetector.ATTR_DESCRIPTION;
+import static com.android.tools.lint.checks.RestrictionsDetector.TAG_RESTRICTIONS;
+
+import com.android.annotations.NonNull;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Check which looks at the children of ScrollViews and ensures that they fill/match
+ * the parent width instead of setting wrap_content.
+ */
+public class HardcodedValuesDetector extends LayoutDetector {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "HardcodedText", //$NON-NLS-1$
+ "Hardcoded text",
+
+ "Hardcoding text attributes directly in layout files is bad for several reasons:\n" +
+ "\n" +
+ "* When creating configuration variations (for example for landscape or portrait)" +
+ "you have to repeat the actual text (and keep it up to date when making changes)\n" +
+ "\n" +
+ "* The application cannot be translated to other languages by just adding new " +
+ "translations for existing string resources.\n" +
+ "\n" +
+ "In Android Studio and Eclipse there are quickfixes to automatically extract this " +
+ "hardcoded string into a resource lookup.",
+
+ Category.I18N,
+ 5,
+ Severity.WARNING,
+ new Implementation(
+ HardcodedValuesDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ // TODO: Add additional issues here, such as hardcoded colors, hardcoded sizes, etc
+
+ /** Constructs a new {@link HardcodedValuesDetector} */
+ public HardcodedValuesDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Arrays.asList(
+ // Layouts
+ ATTR_TEXT,
+ ATTR_CONTENT_DESCRIPTION,
+ ATTR_HINT,
+ ATTR_LABEL,
+ ATTR_PROMPT,
+
+ // Menus
+ ATTR_TITLE,
+
+ // App restrictions
+ ATTR_DESCRIPTION
+ );
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.LAYOUT
+ || folderType == ResourceFolderType.MENU
+ || folderType == ResourceFolderType.XML;
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ String value = attribute.getValue();
+ if (!value.isEmpty() && (value.charAt(0) != '@' && value.charAt(0) != '?')) {
+ // Make sure this is really one of the android: attributes
+ if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ return;
+ }
+
+ // Filter out a few special cases:
+
+ if (value.equals("Hello World!")) {
+ // This is the default text in new templates. Users are unlikely to
+ // leave this in, so let's not add warnings in the editor as their
+ // welcome to Android development greeting.
+ return;
+ }
+ if (value.equals("Large Text") || value.equals("Medium Text") ||
+ value.equals("Small Text") || value.startsWith("New ") &&
+ (value.equals("New Text")
+ || value.equals("New " + attribute.getOwnerElement().getTagName()))) {
+ // The layout editor initially places the label "New Button", "New TextView",
+ // etc on widgets dropped on the layout editor. Again, users are unlikely
+ // to leave it that way, so let's not flag it until they change it.
+ return;
+ }
+
+ // In XML folders, currently only checking application restriction files
+ // (since in general the res/xml folder can contain arbitrary XML content
+ // interpreted by the app)
+ if (context.getResourceFolderType() == ResourceFolderType.XML) {
+ String tagName = attribute.getOwnerDocument().getDocumentElement().getTagName();
+ if (!tagName.equals(TAG_RESTRICTIONS)) {
+ return;
+ }
+ }
+
+ context.report(ISSUE, attribute, context.getLocation(attribute),
+ String.format("[I18N] Hardcoded string \"%1$s\", should use `@string` resource",
+ value));
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IncludeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IncludeDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IncludeDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/IncludeDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutInflationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutInflationDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutInflationDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutInflationDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
new file mode 100644
index 0000000..1b16852
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.FORMAT_METHOD;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
+/**
+ * Checks for errors related to locale handling
+ */
+public class LocaleDetector extends Detector implements JavaScanner {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ LocaleDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Calling risky convenience methods */
+ public static final Issue STRING_LOCALE = Issue.create(
+ "DefaultLocale", //$NON-NLS-1$
+ "Implied default locale in case conversion",
+
+ "Calling `String#toLowerCase()` or `#toUpperCase()` *without specifying an " +
+ "explicit locale* is a common source of bugs. The reason for that is that those " +
+ "methods will use the current locale on the user's device, and even though the " +
+ "code appears to work correctly when you are developing the app, it will fail " +
+ "in some locales. For example, in the Turkish locale, the uppercase replacement " +
+ "for `i` is *not* `I`.\n" +
+ "\n" +
+ "If you want the methods to just perform ASCII replacement, for example to convert " +
+ "an enum name, call `String#toUpperCase(Locale.US)` instead. If you really want to " +
+ "use the current locale, call `String#toUpperCase(Locale.getDefault())` instead.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo(
+ "http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$
+
+ /** Constructs a new {@link LocaleDetector} */
+ public LocaleDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ if (LintClient.isStudio()) {
+ // In the IDE, don't flag toUpperCase/toLowerCase; these
+ // are already flagged by built-in IDE inspections, so we don't
+ // want duplicate warnings.
+ return Collections.singletonList(FORMAT_METHOD);
+ } else {
+ return Arrays.asList(
+ // Only when not running in the IDE
+ "toLowerCase", //$NON-NLS-1$
+ "toUpperCase", //$NON-NLS-1$
+ FORMAT_METHOD
+ );
+ }
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation call) {
+ ResolvedNode resolved = context.resolve(call);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getContainingClass().matches(TYPE_STRING)) {
+ String name = method.getName();
+ if (name.equals(FORMAT_METHOD)) {
+ checkFormat(context, method, call);
+ } else if (method.getArgumentCount() == 0) {
+ Location location = context.getNameLocation(call);
+ String message = String.format(
+ "Implicitly using the default locale is a common source of bugs: " +
+ "Use `%1$s(Locale)` instead", name);
+ context.report(STRING_LOCALE, call, location, message);
+ }
+ }
+ }
+ }
+
+ /** Returns true if the given node is a parameter to a Logging call */
+ private static boolean isLoggingParameter(
+ @NonNull JavaContext context,
+ @NonNull MethodInvocation node) {
+ Node parent = node.getParent();
+ if (parent instanceof MethodInvocation) {
+ MethodInvocation call = (MethodInvocation)parent;
+ String name = call.astName().astValue();
+ if (name.length() == 1) { // "d", "i", "e" etc in Log
+ ResolvedNode resolved = context.resolve(call);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getContainingClass().matches(LogDetector.LOG_CLS)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void checkFormat(
+ @NonNull JavaContext context,
+ @NonNull ResolvedMethod method,
+ @NonNull MethodInvocation call) {
+ // Only check the non-locale version of String.format
+ if (method.getArgumentCount() == 0
+ || !method.getArgumentType(0).matchesName(TYPE_STRING)
+ || call.astArguments().isEmpty()) {
+ return;
+ }
+
+ // Find the formatting string
+ Expression first = call.astArguments().first();
+ Object value = ConstantEvaluator.evaluate(context, first);
+ if (!(value instanceof String)) {
+ return;
+ }
+
+ String format = (String) value;
+ if (StringFormatDetector.isLocaleSpecific(format)) {
+ if (isLoggingParameter(context, call)) {
+ return;
+ }
+ Location location = context.getLocation(call);
+ String message =
+ "Implicitly using the default locale is a common source of bugs: " +
+ "Use `String.format(Locale, ...)` instead";
+ context.report(STRING_LOCALE, call, location, message);
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleFolderDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleFolderDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleFolderDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleFolderDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java
new file mode 100644
index 0000000..0530b4d
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LogDetector.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Expression;
+import lombok.ast.If;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Select;
+import lombok.ast.StringLiteral;
+import lombok.ast.VariableReference;
+
+/**
+ * Detector for finding inefficiencies and errors in logging calls.
+ */
+public class LogDetector extends Detector implements Detector.JavaScanner {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ LogDetector.class, Scope.JAVA_FILE_SCOPE);
+
+
+ /** Log call missing surrounding if */
+ public static final Issue CONDITIONAL = Issue.create(
+ "LogConditional", //$NON-NLS-1$
+ "Unconditional Logging Calls",
+ "The BuildConfig class (available in Tools 17) provides a constant, \"DEBUG\", " +
+ "which indicates whether the code is being built in release mode or in debug " +
+ "mode. In release mode, you typically want to strip out all the logging calls. " +
+ "Since the compiler will automatically remove all code which is inside a " +
+ "\"if (false)\" check, surrounding your logging calls with a check for " +
+ "BuildConfig.DEBUG is a good idea.\n" +
+ "\n" +
+ "If you *really* intend for the logging to be present in release mode, you can " +
+ "suppress this warning with a @SuppressLint annotation for the intentional " +
+ "logging calls.",
+
+ Category.PERFORMANCE,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION).setEnabledByDefault(false);
+
+ /** Mismatched tags between isLogging and log calls within it */
+ public static final Issue WRONG_TAG = Issue.create(
+ "LogTagMismatch", //$NON-NLS-1$
+ "Mismatched Log Tags",
+ "When guarding a `Log.v(tag, ...)` call with `Log.isLoggable(tag)`, the " +
+ "tag passed to both calls should be the same. Similarly, the level passed " +
+ "in to `Log.isLoggable` should typically match the type of `Log` call, e.g. " +
+ "if checking level `Log.DEBUG`, the corresponding `Log` call should be `Log.d`, " +
+ "not `Log.i`.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Log tag is too long */
+ public static final Issue LONG_TAG = Issue.create(
+ "LongLogTag", //$NON-NLS-1$
+ "Too Long Log Tags",
+ "Log tags are only allowed to be at most 23 tag characters long.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private static final String IS_LOGGABLE = "isLoggable"; //$NON-NLS-1$
+ public static final String LOG_CLS = "android.util.Log"; //$NON-NLS-1$
+ private static final String PRINTLN = "println"; //$NON-NLS-1$
+
+ // ---- Implements Detector.JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(
+ "d", //$NON-NLS-1$
+ "e", //$NON-NLS-1$
+ "i", //$NON-NLS-1$
+ "v", //$NON-NLS-1$
+ "w", //$NON-NLS-1$
+ PRINTLN,
+ IS_LOGGABLE);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, @NonNull MethodInvocation node) {
+ ResolvedNode resolved = context.resolve(node);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return;
+ }
+
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (!method.getContainingClass().matches(LOG_CLS)) {
+ return;
+ }
+
+ String name = node.astName().astValue();
+ boolean withinConditional = IS_LOGGABLE.equals(name) ||
+ checkWithinConditional(context, node.getParent(), node);
+
+ // See if it's surrounded by an if statement (and it's one of the non-error, spammy
+ // log methods (info, verbose, etc))
+ if (("i".equals(name) || "d".equals(name) || "v".equals(name) || PRINTLN.equals(name))
+ && !withinConditional
+ && performsWork(context, node)
+ && context.isEnabled(CONDITIONAL)) {
+ String message = String.format("The log call Log.%1$s(...) should be " +
+ "conditional: surround with `if (Log.isLoggable(...))` or " +
+ "`if (BuildConfig.DEBUG) { ... }`",
+ node.astName().toString());
+ context.report(CONDITIONAL, node, context.getLocation(node), message);
+ }
+
+ // Check tag length
+ if (context.isEnabled(LONG_TAG)) {
+ int tagArgumentIndex = PRINTLN.equals(name) ? 1 : 0;
+ if (method.getArgumentCount() > tagArgumentIndex
+ && method.getArgumentType(tagArgumentIndex).matchesSignature(TYPE_STRING)
+ && node.astArguments().size() == method.getArgumentCount()) {
+ Iterator<Expression> iterator = node.astArguments().iterator();
+ if (tagArgumentIndex == 1) {
+ iterator.next();
+ }
+ Node argument = iterator.next();
+ String tag = ConstantEvaluator.evaluateString(context, argument, true);
+ if (tag != null && tag.length() > 23) {
+ String message = String.format(
+ "The logging tag can be at most 23 characters, was %1$d (%2$s)",
+ tag.length(), tag);
+ context.report(LONG_TAG, node, context.getLocation(node), message);
+ }
+ }
+ }
+ }
+
+ /** Returns true if the given logging call performs "work" to compute the message */
+ private static boolean performsWork(
+ @NonNull JavaContext context,
+ @NonNull MethodInvocation node) {
+ int messageArgumentIndex = PRINTLN.equals(node.astName().astValue()) ? 2 : 1;
+ if (node.astArguments().size() >= messageArgumentIndex) {
+ Iterator<Expression> iterator = node.astArguments().iterator();
+ Node argument = null;
+ for (int i = 0; i <= messageArgumentIndex; i++) {
+ argument = iterator.next();
+ }
+ if (argument == null) {
+ return false;
+ }
+ if (argument instanceof StringLiteral || argument instanceof VariableReference) {
+ return false;
+ }
+ if (argument instanceof BinaryExpression) {
+ String string = ConstantEvaluator.evaluateString(context, argument, false);
+ //noinspection VariableNotUsedInsideIf
+ if (string != null) { // does it resolve to a constant?
+ return false;
+ }
+ } else if (argument instanceof Select) {
+ String string = ConstantEvaluator.evaluateString(context, argument, false);
+ //noinspection VariableNotUsedInsideIf
+ if (string != null) {
+ return false;
+ }
+ }
+
+ // Method invocations etc
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean checkWithinConditional(
+ @NonNull JavaContext context,
+ @Nullable Node curr,
+ @NonNull MethodInvocation logCall) {
+ while (curr != null) {
+ if (curr instanceof If) {
+ If ifNode = (If) curr;
+ if (ifNode.astCondition() instanceof MethodInvocation) {
+ MethodInvocation call = (MethodInvocation) ifNode.astCondition();
+ if (IS_LOGGABLE.equals(call.astName().astValue())) {
+ checkTagConsistent(context, logCall, call);
+ }
+ }
+
+ return true;
+ } else if (curr instanceof MethodInvocation
+ || curr instanceof ClassDeclaration) { // static block
+ break;
+ }
+ curr = curr.getParent();
+ }
+ return false;
+ }
+
+ /** Checks that the tag passed to Log.s and Log.isLoggable match */
+ private static void checkTagConsistent(JavaContext context, MethodInvocation logCall,
+ MethodInvocation call) {
+ Iterator<Expression> isLogIterator = call.astArguments().iterator();
+ Iterator<Expression> logIterator = logCall.astArguments().iterator();
+ if (!isLogIterator.hasNext() || !logIterator.hasNext()) {
+ return;
+ }
+ Expression isLoggableTag = isLogIterator.next();
+ Expression logTag = logIterator.next();
+
+ //String callName = logCall.astName().astValue();
+ String logCallName = logCall.astName().astValue();
+ boolean isPrintln = PRINTLN.equals(logCallName);
+ if (isPrintln) {
+ if (!logIterator.hasNext()) {
+ return;
+ }
+ logTag = logIterator.next();
+ }
+
+ if (logTag != null) {
+ if (!isLoggableTag.toString().equals(logTag.toString())) {
+ ResolvedNode resolved1 = context.resolve(isLoggableTag);
+ ResolvedNode resolved2 = context.resolve(logTag);
+ if ((resolved1 == null || resolved2 == null || !resolved1.equals(resolved2))
+ && context.isEnabled(WRONG_TAG)) {
+ Location location = context.getLocation(logTag);
+ Location alternate = context.getLocation(isLoggableTag);
+ alternate.setMessage("Conflicting tag");
+ location.setSecondary(alternate);
+ String isLoggableDescription = resolved1 != null ? resolved1
+ .getName()
+ : isLoggableTag.toString();
+ String logCallDescription = resolved2 != null ? resolved2.getName()
+ : logTag.toString();
+ String message = String.format(
+ "Mismatched tags: the `%1$s()` and `isLoggable()` calls typically " +
+ "should pass the same tag: `%2$s` versus `%3$s`",
+ logCallName,
+ isLoggableDescription,
+ logCallDescription);
+ context.report(WRONG_TAG, call, location, message);
+ }
+ }
+ }
+
+ // Check log level versus the actual log call type (e.g. flag
+ // if (Log.isLoggable(TAG, Log.DEBUG) Log.info(TAG, "something")
+
+ if (logCallName.length() != 1 || !isLogIterator.hasNext()) { // e.g. println
+ return;
+ }
+ Expression isLoggableLevel = isLogIterator.next();
+ if (isLoggableLevel == null) {
+ return;
+ }
+ String levelString = isLoggableLevel.toString();
+ if (isLoggableLevel instanceof Select) {
+ levelString = ((Select)isLoggableLevel).astIdentifier().astValue();
+ }
+ if (levelString.isEmpty()) {
+ return;
+ }
+ char levelChar = Character.toLowerCase(levelString.charAt(0));
+ if (logCallName.charAt(0) == levelChar || !context.isEnabled(WRONG_TAG)) {
+ return;
+ }
+ switch (levelChar) {
+ case 'd':
+ case 'e':
+ case 'i':
+ case 'v':
+ case 'w':
+ break;
+ default:
+ // Some other char; e.g. user passed in a literal value or some
+ // local constant or variable alias
+ return;
+ }
+ String expectedCall = String.valueOf(levelChar);
+ String message = String.format(
+ "Mismatched logging levels: when checking `isLoggable` level `%1$s`, the " +
+ "corresponding log call should be `Log.%2$s`, not `Log.%3$s`",
+ levelString, expectedCall, logCallName);
+ Location location = context.getLocation(logCall.astName());
+ Location alternate = context.getLocation(isLoggableLevel);
+ alternate.setMessage("Conflicting tag");
+ location.setSecondary(alternate);
+ context.report(WRONG_TAG, call, location, message);
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
new file mode 100644
index 0000000..8cd9f7a
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
@@ -0,0 +1,1071 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ALLOW_BACKUP;
+import static com.android.SdkConstants.ATTR_FULL_BACKUP_CONTENT;
+import static com.android.SdkConstants.ATTR_ICON;
+import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PACKAGE;
+import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
+import static com.android.SdkConstants.ATTR_VERSION_CODE;
+import static com.android.SdkConstants.ATTR_VERSION_NAME;
+import static com.android.SdkConstants.DRAWABLE_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.TAG_ACTIVITY;
+import static com.android.SdkConstants.TAG_APPLICATION;
+import static com.android.SdkConstants.TAG_INTENT_FILTER;
+import static com.android.SdkConstants.TAG_PERMISSION;
+import static com.android.SdkConstants.TAG_PROVIDER;
+import static com.android.SdkConstants.TAG_RECEIVER;
+import static com.android.SdkConstants.TAG_SERVICE;
+import static com.android.SdkConstants.TAG_USES_FEATURE;
+import static com.android.SdkConstants.TAG_USES_LIBRARY;
+import static com.android.SdkConstants.TAG_USES_PERMISSION;
+import static com.android.SdkConstants.TAG_USES_SDK;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VALUE_FALSE;
+import static com.android.xml.AndroidManifest.NODE_ACTION;
+import static com.android.xml.AndroidManifest.NODE_DATA;
+import static com.android.xml.AndroidManifest.NODE_METADATA;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.builder.model.Variant;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Maps;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Checks for issues in AndroidManifest files such as declaring elements in the
+ * wrong order.
+ */
+public class ManifestDetector extends Detector implements Detector.XmlScanner {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ ManifestDetector.class,
+ Scope.MANIFEST_SCOPE
+ );
+
+ /** Wrong order of elements in the manifest */
+ public static final Issue ORDER = Issue.create(
+ "ManifestOrder", //$NON-NLS-1$
+ "Incorrect order of elements in manifest",
+ "The <application> tag should appear after the elements which declare " +
+ "which version you need, which features you need, which libraries you " +
+ "need, and so on. In the past there have been subtle bugs (such as " +
+ "themes not getting applied correctly) when the `<application>` tag appears " +
+ "before some of these other elements, so it's best to order your " +
+ "manifest in the logical dependency order.",
+ Category.CORRECTNESS,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Missing a {@code <uses-sdk>} element */
+ public static final Issue USES_SDK = Issue.create(
+ "UsesMinSdkAttributes", //$NON-NLS-1$
+ "Minimum SDK and target SDK attributes not defined",
+
+ "The manifest should contain a `<uses-sdk>` element which defines the " +
+ "minimum API Level required for the application to run, " +
+ "as well as the target version (the highest API level you have tested " +
+ "the version for.)",
+
+ Category.CORRECTNESS,
+ 9,
+ Severity.WARNING,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
+
+ /** Using a targetSdkVersion that isn't recent */
+ public static final Issue TARGET_NEWER = Issue.create(
+ "OldTargetApi", //$NON-NLS-1$
+ "Target SDK attribute is not targeting latest version",
+
+ "When your application runs on a version of Android that is more recent than your " +
+ "`targetSdkVersion` specifies that it has been tested with, various compatibility " +
+ "modes kick in. This ensures that your application continues to work, but it may " +
+ "look out of place. For example, if the `targetSdkVersion` is less than 14, your " +
+ "app may get an option button in the UI.\n" +
+ "\n" +
+ "To fix this issue, set the `targetSdkVersion` to the highest available value. Then " +
+ "test your app to make sure everything works correctly. You may want to consult " +
+ "the compatibility notes to see what changes apply to each version you are adding " +
+ "support for: " +
+ "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html"); //$NON-NLS-1$
+
+ /** Using multiple {@code <uses-sdk>} elements */
+ public static final Issue MULTIPLE_USES_SDK = Issue.create(
+ "MultipleUsesSdk", //$NON-NLS-1$
+ "Multiple `<uses-sdk>` elements in the manifest",
+
+ "The `<uses-sdk>` element should appear just once; the tools will *not* merge the " +
+ "contents of all the elements so if you split up the attributes across multiple " +
+ "elements, only one of them will take effect. To fix this, just merge all the " +
+ "attributes from the various elements into a single <uses-sdk> element.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
+
+ /** Missing a {@code <uses-sdk>} element */
+ public static final Issue WRONG_PARENT = Issue.create(
+ "WrongManifestParent", //$NON-NLS-1$
+ "Wrong manifest parent",
+
+ "The `<uses-library>` element should be defined as a direct child of the " +
+ "`<application>` tag, not the `<manifest>` tag or an `<activity>` tag. Similarly, " +
+ "a `<uses-sdk>` tag must be declared at the root level, and so on. This check " +
+ "looks for incorrect declaration locations in the manifest, and complains " +
+ "if an element is found in the wrong place.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
+
+ /** Missing a {@code <uses-sdk>} element */
+ public static final Issue DUPLICATE_ACTIVITY = Issue.create(
+ "DuplicateActivity", //$NON-NLS-1$
+ "Activity registered more than once",
+
+ "An activity should only be registered once in the manifest. If it is " +
+ "accidentally registered more than once, then subtle errors can occur, " +
+ "since attribute declarations from the two elements are not merged, so " +
+ "you may accidentally remove previous declarations.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /**
+ * Documentation URL for app backup.
+ * <p>
+ * TODO: Replace with stable API doc reference once this moves out of preview
+ * (tracked in https://code.google.com/p/android/issues/detail?id=182113)
+ */
+ private static final String BACKUP_DOCUMENTATION_URL
+ = "https://developer.android.com/preview/backup/index.html";
+
+ /** Not explicitly defining allowBackup */
+ public static final Issue ALLOW_BACKUP = Issue.create(
+ "AllowBackup", //$NON-NLS-1$
+ "AllowBackup/FullBackupContent Problems",
+
+ "The `allowBackup` attribute determines if an application's data can be backed up " +
+ "and restored. It is documented at " +
+ "http://developer.android.com/reference/android/R.attr.html#allowBackup\n" +
+ "\n" +
+ "By default, this flag is set to `true`. When this flag is set to `true`, " +
+ "application data can be backed up and restored by the user using `adb backup` " +
+ "and `adb restore`.\n" +
+ "\n" +
+ "This may have security consequences for an application. `adb backup` allows " +
+ "users who have enabled USB debugging to copy application data off of the " +
+ "device. Once backed up, all application data can be read by the user. " +
+ "`adb restore` allows creation of application data from a source specified " +
+ "by the user. Following a restore, applications should not assume that the " +
+ "data, file permissions, and directory permissions were created by the " +
+ "application itself.\n" +
+ "\n" +
+ "Setting `allowBackup=\"false\"` opts an application out of both backup and " +
+ "restore.\n" +
+ "\n" +
+ "To fix this warning, decide whether your application should support backup, " +
+ "and explicitly set `android:allowBackup=(true|false)\"`.\n" +
+ "\n" +
+ "If not set to false, and if targeting API 23 or later, lint will also warn " +
+ "that you should set `android:fullBackupContent` to configure auto backup.",
+
+ Category.SECURITY,
+ 3,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo(BACKUP_DOCUMENTATION_URL)
+ .addMoreInfo("http://developer.android.com/reference/android/R.attr.html#allowBackup");
+
+ /** Conflicting permission names */
+ public static final Issue UNIQUE_PERMISSION = Issue.create(
+ "UniquePermission", //$NON-NLS-1$
+ "Permission names are not unique",
+
+ "The unqualified names or your permissions must be unique. The reason for this " +
+ "is that at build time, the `aapt` tool will generate a class named `Manifest` " +
+ "which contains a field for each of your permissions. These fields are named " +
+ "using your permission unqualified names (i.e. the name portion after the last " +
+ "dot).\n" +
+ "\n" +
+ "If more than one permission maps to the same field name, that field will " +
+ "arbitrarily name just one of them.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /** Using a resource for attributes that do not allow it */
+ public static final Issue SET_VERSION = Issue.create(
+ "MissingVersion", //$NON-NLS-1$
+ "Missing application name/version",
+
+ "You should define the version information for your application.\n" +
+ "`android:versionCode`: An integer value that represents the version of the " +
+ "application code, relative to other versions.\n" +
+ "\n" +
+ "`android:versionName`: A string value that represents the release version of " +
+ "the application code, as it should be shown to users.",
+
+ Category.CORRECTNESS,
+ 2,
+ Severity.WARNING,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/tools/publishing/versioning.html#appversioning");
+
+ /** Using a resource for attributes that do not allow it */
+ public static final Issue ILLEGAL_REFERENCE = Issue.create(
+ "IllegalResourceRef", //$NON-NLS-1$
+ "Name and version must be integer or string, not resource",
+
+ "For the `versionCode` attribute, you have to specify an actual integer " +
+ "literal; you cannot use an indirection with a `@dimen/name` resource. " +
+ "Similarly, the `versionName` attribute should be an actual string, not " +
+ "a string resource url.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Declaring a uses-feature multiple time */
+ public static final Issue DUPLICATE_USES_FEATURE = Issue.create(
+ "DuplicateUsesFeature", //$NON-NLS-1$
+ "Feature declared more than once",
+
+ "A given feature should only be declared once in the manifest.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Not explicitly defining application icon */
+ public static final Issue APPLICATION_ICON = Issue.create(
+ "MissingApplicationIcon", //$NON-NLS-1$
+ "Missing application icon",
+
+ "You should set an icon for the application as whole because there is no " +
+ "default. This attribute must be set as a reference to a drawable resource " +
+ "containing the image (for example `@drawable/icon`).",
+
+ Category.ICONS,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION).addMoreInfo(
+ "http://developer.android.com/tools/publishing/preparing.html#publishing-configure"); //$NON-NLS-1$
+
+ /** Malformed Device Admin */
+ public static final Issue DEVICE_ADMIN = Issue.create(
+ "DeviceAdmin", //$NON-NLS-1$
+ "Malformed Device Admin",
+ "If you register a broadcast receiver which acts as a device admin, you must also " +
+ "register an `<intent-filter>` for the action " +
+ "`android.app.action.DEVICE_ADMIN_ENABLED`, without any `<data>`, such that the " +
+ "device admin can be activated/deactivated.\n" +
+ "\n" +
+ "To do this, add\n" +
+ "`<intent-filter>`\n" +
+ " `<action android:name=\"android.app.action.DEVICE_ADMIN_ENABLED\" />`\n" +
+ "`</intent-filter>`\n" +
+ "to your `<receiver>`.",
+ Category.CORRECTNESS,
+ 7,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Using a mock location in a non-debug-specific manifest file */
+ public static final Issue MOCK_LOCATION = Issue.create(
+ "MockLocation", //$NON-NLS-1$
+ "Using mock location provider in production",
+
+ "Using a mock location provider (by requiring the permission " +
+ "`android.permission.ACCESS_MOCK_LOCATION`) should *only* be done " +
+ "in debug builds (or from tests). In Gradle projects, that means you should only " +
+ "request this permission in a test or debug source set specific manifest file.\n" +
+ "\n" +
+ "To fix this, create a new manifest file in the debug folder and move " +
+ "the `<uses-permission>` element there. A typical path to a debug manifest " +
+ "override file in a Gradle project is src/debug/AndroidManifest.xml.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /** Defining a value that is overridden by Gradle */
+ public static final Issue GRADLE_OVERRIDES = Issue.create(
+ "GradleOverrides", //$NON-NLS-1$
+ "Value overridden by Gradle build script",
+
+ "The value of (for example) `minSdkVersion` is only used if it is not specified in " +
+ "the `build.gradle` build scripts. When specified in the Gradle build scripts, " +
+ "the manifest value is ignored and can be misleading, so should be removed to " +
+ "avoid ambiguity.",
+
+ Category.CORRECTNESS,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Using drawable rather than mipmap launcher icons */
+ public static final Issue MIPMAP = Issue.create(
+ "MipmapIcons", //$NON-NLS-1$
+ "Use Mipmap Launcher Icons",
+
+ "Launcher icons should be provided in the `mipmap` resource directory. " +
+ "This is the same as the `drawable` resource directory, except resources in " +
+ "the `mipmap` directory will not get stripped out when creating density-specific " +
+ "APKs.\n" +
+ "\n" +
+ "In certain cases, the Launcher app may use a higher resolution asset (than " +
+ "would normally be computed for the device) to display large app shortcuts. " +
+ "If drawables for densities other than the device's resolution have been " +
+ "stripped out, then the app shortcut could appear blurry.\n" +
+ "\n" +
+ "To fix this, move your launcher icons from `drawable-`dpi to `mipmap-`dpi " +
+ "and change references from @drawable/ and R.drawable to @mipmap/ and R.mipmap.\n" +
+ "In Android Studio this lint warning has a quickfix to perform this automatically.",
+ Category.ICONS,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Permission name of mock location permission */
+ public static final String MOCK_LOCATION_PERMISSION =
+ "android.permission.ACCESS_MOCK_LOCATION"; //$NON-NLS-1$
+
+ /** Constructs a new {@link ManifestDetector} check */
+ public ManifestDetector() {
+ }
+
+ private boolean mSeenApplication;
+
+ /** Number of times we've seen the <uses-sdk> element */
+ private int mSeenUsesSdk;
+
+ /** Activities we've encountered */
+ private Set<String> mActivities;
+
+ /** Features we've encountered */
+ private Set<String> mUsesFeatures;
+
+ /** Permission basenames */
+ private Map<String, String> mPermissionNames;
+
+ /** Handle to the {@code <application>} tag */
+ private Location.Handle mApplicationTagHandle;
+
+ /** Whether we've seen an application icon definition in any of the manifest files (or
+ * if a manifest tag warning for this has been explicitly disabled) */
+ private boolean mSeenAppIcon;
+
+ /** Whether we've seen an allow backup definition in any of the manifest files (or
+ * if a manifest tag warning for this has been explicitly disabled) */
+ private boolean mSeenAllowBackup;
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return file.getName().equals(ANDROID_MANIFEST_XML);
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ mSeenApplication = false;
+ mSeenUsesSdk = 0;
+ mActivities = new HashSet<String>();
+ mUsesFeatures = new HashSet<String>();
+ }
+
+ @Override
+ public void afterCheckFile(@NonNull Context context) {
+ XmlContext xmlContext = (XmlContext) context;
+ Element element = xmlContext.document.getDocumentElement();
+ if (element != null) {
+ checkDocumentElement(xmlContext, element);
+ }
+
+ if (mSeenUsesSdk == 0 && context.isEnabled(USES_SDK)
+ // Not required in Gradle projects; typically defined in build.gradle instead
+ // and inserted at build time
+ && !context.getMainProject().isGradleProject()) {
+ context.report(USES_SDK, Location.create(context.file),
+ "Manifest should specify a minimum API level with " +
+ "`<uses-sdk android:minSdkVersion=\"?\" />`; if it really supports " +
+ "all versions of Android set it to 1.");
+ }
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (!mSeenAllowBackup && context.isEnabled(ALLOW_BACKUP)
+ && !context.getProject().isLibrary()
+ && context.getMainProject().getMinSdk() >= 4) {
+ Location location = getMainApplicationTagLocation(context);
+ context.report(ALLOW_BACKUP, location,
+ "Should explicitly set `android:allowBackup` to `true` or " +
+ "`false` (it's `true` by default, and that can have some security " +
+ "implications for the application's data)");
+ }
+
+ if (!context.getMainProject().isLibrary()
+ && !mSeenAppIcon && context.isEnabled(APPLICATION_ICON)) {
+ Location location = getMainApplicationTagLocation(context);
+ context.report(APPLICATION_ICON, location,
+ "Should explicitly set `android:icon`, there is no default");
+ }
+ }
+
+ @Nullable
+ private Location getMainApplicationTagLocation(@NonNull Context context) {
+ if (mApplicationTagHandle != null) {
+ return mApplicationTagHandle.resolve();
+ }
+
+ List<File> manifestFiles = context.getMainProject().getManifestFiles();
+ if (!manifestFiles.isEmpty()) {
+ return Location.create(manifestFiles.get(0));
+ }
+
+ return null;
+ }
+
+ private static void checkDocumentElement(XmlContext context, Element element) {
+ Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_VERSION_CODE);
+ if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
+ && context.isEnabled(ILLEGAL_REFERENCE)) {
+ context.report(ILLEGAL_REFERENCE, element, context.getLocation(codeNode),
+ "The `android:versionCode` cannot be a resource url, it must be "
+ + "a literal integer");
+ } else if (codeNode == null && context.isEnabled(SET_VERSION)
+ // Not required in Gradle projects; typically defined in build.gradle instead
+ // and inserted at build time
+ && !context.getMainProject().isGradleProject()) {
+ context.report(SET_VERSION, element, context.getLocation(element),
+ "Should set `android:versionCode` to specify the application version");
+ }
+ Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_VERSION_NAME);
+ if (nameNode == null && context.isEnabled(SET_VERSION)
+ // Not required in Gradle projects; typically defined in build.gradle instead
+ // and inserted at build time
+ && !context.getMainProject().isGradleProject()) {
+ context.report(SET_VERSION, element, context.getLocation(element),
+ "Should set `android:versionName` to specify the application version");
+ }
+
+ checkOverride(context, element, ATTR_VERSION_CODE);
+ checkOverride(context, element, ATTR_VERSION_NAME);
+
+ Attr pkgNode = element.getAttributeNode(ATTR_PACKAGE);
+ if (pkgNode != null) {
+ String pkg = pkgNode.getValue();
+ if (pkg.contains("${") && context.getMainProject().isGradleProject()) {
+ context.report(GRADLE_OVERRIDES, pkgNode, context.getLocation(pkgNode),
+ "Cannot use placeholder for the package in the manifest; "
+ + "set `applicationId` in `build.gradle` instead");
+ }
+ }
+ }
+
+ private static void checkOverride(XmlContext context, Element element, String attributeName) {
+ Project project = context.getProject();
+ Attr attribute = element.getAttributeNodeNS(ANDROID_URI, attributeName);
+ if (project.isGradleProject() && attribute != null && context.isEnabled(GRADLE_OVERRIDES)) {
+ Variant variant = project.getCurrentVariant();
+ if (variant != null) {
+ ProductFlavor flavor = variant.getMergedFlavor();
+ String gradleValue = null;
+ if (ATTR_MIN_SDK_VERSION.equals(attributeName)) {
+ if (element.hasAttributeNS(TOOLS_URI, "overrideLibrary")) {
+ // The manifest may be setting a minSdkVersion here to deliberately
+ // let the manifest merger know that a library dependency's manifest
+ // with a higher value is okay: this value wins. The manifest merger
+ // should really be taking the Gradle file into account instead,
+ // but for now we filter these out; http://b.android.com/186762
+ return;
+ }
+ ApiVersion minSdkVersion = flavor.getMinSdkVersion();
+ gradleValue = minSdkVersion != null ? minSdkVersion.getApiString() : null;
+ } else if (ATTR_TARGET_SDK_VERSION.equals(attributeName)) {
+ ApiVersion targetSdkVersion = flavor.getTargetSdkVersion();
+ gradleValue = targetSdkVersion != null ? targetSdkVersion.getApiString() : null;
+ } else if (ATTR_VERSION_CODE.equals(attributeName)) {
+ Integer versionCode = flavor.getVersionCode();
+ if (versionCode != null) {
+ gradleValue = versionCode.toString();
+ }
+ } else if (ATTR_VERSION_NAME.equals(attributeName)) {
+ gradleValue = flavor.getVersionName();
+ } else {
+ assert false : attributeName;
+ return;
+ }
+
+ if (gradleValue != null) {
+ String manifestValue = attribute.getValue();
+
+ String message = String.format("This `%1$s` value (`%2$s`) is not used; it is "
+ + "always overridden by the value specified in the Gradle build "
+ + "script (`%3$s`)", attributeName, manifestValue, gradleValue);
+ context.report(GRADLE_OVERRIDES, attribute, context.getLocation(attribute),
+ message);
+ }
+ }
+ }
+ }
+
+ // ---- Implements Detector.XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ TAG_APPLICATION,
+ TAG_USES_PERMISSION,
+ TAG_PERMISSION,
+ "permission-tree", //$NON-NLS-1$
+ "permission-group", //$NON-NLS-1$
+ TAG_USES_SDK,
+ "uses-configuration", //$NON-NLS-1$
+ TAG_USES_FEATURE,
+ "supports-screens", //$NON-NLS-1$
+ "compatible-screens", //$NON-NLS-1$
+ "supports-gl-texture", //$NON-NLS-1$
+ TAG_USES_LIBRARY,
+ TAG_ACTIVITY,
+ TAG_SERVICE,
+ TAG_PROVIDER,
+ TAG_RECEIVER
+ );
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String tag = element.getTagName();
+ Node parentNode = element.getParentNode();
+
+ boolean isReceiver = tag.equals(TAG_RECEIVER);
+ if (isReceiver) {
+ checkDeviceAdmin(context, element);
+ }
+
+ if (tag.equals(TAG_USES_LIBRARY) || tag.equals(TAG_ACTIVITY) || tag.equals(TAG_SERVICE)
+ || tag.equals(TAG_PROVIDER) || isReceiver) {
+ if (!TAG_APPLICATION.equals(parentNode.getNodeName())
+ && context.isEnabled(WRONG_PARENT)) {
+ context.report(WRONG_PARENT, element, context.getLocation(element),
+ String.format(
+ "The `<%1$s>` element must be a direct child of the <application> element",
+ tag));
+ }
+
+ if (tag.equals(TAG_ACTIVITY)) {
+ Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (nameNode != null) {
+ String name = nameNode.getValue();
+ if (!name.isEmpty()) {
+ String pkg = context.getMainProject().getPackage();
+ if (name.charAt(0) == '.') {
+ name = pkg + name;
+ } else if (name.indexOf('.') == -1) {
+ name = pkg + '.' + name;
+ }
+ if (mActivities.contains(name)) {
+ String message = String.format(
+ "Duplicate registration for activity `%1$s`", name);
+ context.report(DUPLICATE_ACTIVITY, element,
+ context.getLocation(nameNode), message);
+ } else {
+ mActivities.add(name);
+ }
+ }
+ }
+
+ checkMipmapIcon(context, element);
+ }
+
+ return;
+ }
+
+ if (parentNode != element.getOwnerDocument().getDocumentElement()
+ && context.isEnabled(WRONG_PARENT)) {
+ context.report(WRONG_PARENT, element, context.getLocation(element),
+ String.format(
+ "The `<%1$s>` element must be a direct child of the " +
+ "`<manifest>` root element", tag));
+ }
+
+ if (tag.equals(TAG_USES_SDK)) {
+ mSeenUsesSdk++;
+
+ if (mSeenUsesSdk == 2) { // Only warn when we encounter the first one
+ Location location = context.getLocation(element);
+
+ // Link up *all* encountered locations in the document
+ NodeList elements = element.getOwnerDocument().getElementsByTagName(TAG_USES_SDK);
+ Location secondary = null;
+ for (int i = elements.getLength() - 1; i >= 0; i--) {
+ Element e = (Element) elements.item(i);
+ if (e != element) {
+ Location l = context.getLocation(e);
+ l.setSecondary(secondary);
+ l.setMessage("Also appears here");
+ secondary = l;
+ }
+ }
+ location.setSecondary(secondary);
+
+ if (context.isEnabled(MULTIPLE_USES_SDK)) {
+ context.report(MULTIPLE_USES_SDK, element, location,
+ "There should only be a single `<uses-sdk>` element in the manifest:" +
+ " merge these together");
+ }
+ return;
+ }
+
+ if (!element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
+ if (context.isEnabled(USES_SDK) && !context.getMainProject().isGradleProject()) {
+ context.report(USES_SDK, element, context.getLocation(element),
+ "`<uses-sdk>` tag should specify a minimum API level with " +
+ "`android:minSdkVersion=\"?\"`");
+ }
+ } else {
+ Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
+ if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
+ && context.isEnabled(ILLEGAL_REFERENCE)) {
+ context.report(ILLEGAL_REFERENCE, element, context.getLocation(codeNode),
+ "The `android:minSdkVersion` cannot be a resource url, it must be "
+ + "a literal integer (or string if a preview codename)");
+ }
+
+ checkOverride(context, element, ATTR_MIN_SDK_VERSION);
+ }
+
+ if (!element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
+ // Warn if not setting target SDK -- but only if the min SDK is somewhat
+ // old so there's some compatibility stuff kicking in (such as the menu
+ // button etc)
+ if (context.isEnabled(USES_SDK) && !context.getMainProject().isGradleProject()) {
+ context.report(USES_SDK, element, context.getLocation(element),
+ "`<uses-sdk>` tag should specify a target API level (the " +
+ "highest verified version; when running on later versions, " +
+ "compatibility behaviors may be enabled) with " +
+ "`android:targetSdkVersion=\"?\"`");
+ }
+ } else {
+ checkOverride(context, element, ATTR_TARGET_SDK_VERSION);
+
+ if (context.isEnabled(TARGET_NEWER)) {
+ Attr targetSdkVersionNode = element.getAttributeNodeNS(ANDROID_URI,
+ ATTR_TARGET_SDK_VERSION);
+ if (targetSdkVersionNode != null) {
+ String target = targetSdkVersionNode.getValue();
+ try {
+ int api = Integer.parseInt(target);
+ if (api < context.getClient().getHighestKnownApiLevel()) {
+ context.report(TARGET_NEWER, element,
+ context.getLocation(targetSdkVersionNode),
+ "Not targeting the latest versions of Android; compatibility " +
+ "modes apply. Consider testing and updating this version. " +
+ "Consult the `android.os.Build.VERSION_CODES` javadoc for details.");
+ }
+ } catch (NumberFormatException nufe) {
+ // Ignore: AAPT will enforce this.
+ }
+ }
+ }
+ }
+
+ Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
+ if (nameNode != null && nameNode.getValue().startsWith(PREFIX_RESOURCE_REF)
+ && context.isEnabled(ILLEGAL_REFERENCE)) {
+ context.report(ILLEGAL_REFERENCE, element, context.getLocation(nameNode),
+ "The `android:targetSdkVersion` cannot be a resource url, it must be "
+ + "a literal integer (or string if a preview codename)");
+ }
+ }
+ if (tag.equals(TAG_PERMISSION)) {
+ Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (nameNode != null) {
+ String name = nameNode.getValue();
+ String base = name.substring(name.lastIndexOf('.') + 1);
+ if (mPermissionNames == null) {
+ mPermissionNames = Maps.newHashMap();
+ } else if (mPermissionNames.containsKey(base)) {
+ String prevName = mPermissionNames.get(base);
+ Location location = context.getLocation(nameNode);
+ NodeList siblings = element.getParentNode().getChildNodes();
+ for (int i = 0, n = siblings.getLength(); i < n; i++) {
+ Node node = siblings.item(i);
+ if (node == element) {
+ break;
+ } else if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element sibling = (Element) node;
+ String suffix = '.' + base;
+ if (sibling.getTagName().equals(TAG_PERMISSION)) {
+ String b = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (b.endsWith(suffix)) {
+ Location prevLocation = context.getLocation(node);
+ prevLocation.setMessage("Previous permission here");
+ location.setSecondary(prevLocation);
+ break;
+ }
+
+ }
+ }
+ }
+
+ String message = String.format("Permission name `%1$s` is not unique " +
+ "(appears in both `%2$s` and `%3$s`)", base, prevName, name);
+ context.report(UNIQUE_PERMISSION, element, location, message);
+ }
+
+ mPermissionNames.put(base, name);
+ }
+ }
+
+ if (tag.equals(TAG_USES_PERMISSION)) {
+ Attr name = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (name != null && name.getValue().equals(MOCK_LOCATION_PERMISSION)
+ && context.getMainProject().isGradleProject()
+ && !isDebugOrTestManifest(context, context.file)
+ && context.isEnabled(MOCK_LOCATION)) {
+ String message = "Mock locations should only be requested in a test or " +
+ "debug-specific manifest file (typically `src/debug/AndroidManifest.xml`)";
+ Location location = context.getLocation(name);
+ context.report(MOCK_LOCATION, element, location, message);
+ }
+ }
+
+ if (tag.equals(TAG_APPLICATION)) {
+ mSeenApplication = true;
+ boolean recordLocation = false;
+ String allowBackup = element.getAttributeNS(ANDROID_URI, ATTR_ALLOW_BACKUP);
+ if (allowBackup != null && !allowBackup.isEmpty()
+ || context.getDriver().isSuppressed(context, ALLOW_BACKUP, element)) {
+ mSeenAllowBackup = true;
+ } else {
+ recordLocation = true;
+ }
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_ICON)
+ || context.getDriver().isSuppressed(context, APPLICATION_ICON, element)) {
+ checkMipmapIcon(context, element);
+ mSeenAppIcon = true;
+ } else {
+ recordLocation = true;
+ }
+ if (recordLocation && !context.getProject().isLibrary() &&
+ (mApplicationTagHandle == null || isMainManifest(context, context.file))) {
+ mApplicationTagHandle = context.createLocationHandle(element);
+ }
+ Attr fullBackupNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_FULL_BACKUP_CONTENT);
+ if (fullBackupNode != null &&
+ fullBackupNode.getValue().startsWith(PREFIX_RESOURCE_REF) &&
+ context.getClient().supportsProjectResources()) {
+ AbstractResourceRepository resources = context.getClient()
+ .getProjectResources(context.getProject(), true);
+ ResourceUrl url = ResourceUrl.parse(fullBackupNode.getValue());
+ if (url != null && !url.framework
+ && resources != null
+ && !resources.hasResourceItem(url.type, url.name)) {
+ Location location = context.getValueLocation(fullBackupNode);
+ context.report(ALLOW_BACKUP, fullBackupNode, location,
+ "Missing `<full-backup-content>` resource");
+ }
+ } else if (fullBackupNode == null && !VALUE_FALSE.equals(allowBackup)
+ && !context.getProject().isLibrary()
+ && context.getMainProject().getTargetSdk() >= 23) {
+ if (hasGcmReceiver(element)) {
+ Location location = context.getLocation(element);
+ context.report(ALLOW_BACKUP, element, location, ""
+ + "On SDK version 23 and up, your app data will be automatically "
+ + "backed up, and restored on app install. Your GCM regid will not "
+ + "work across restores, so you must ensure that it is excluded "
+ + "from the back-up set. Use the attribute "
+ + "`android:fullBackupContent` to specify an `@xml` resource which "
+ + "configures which files to backup. More info: "
+ + BACKUP_DOCUMENTATION_URL);
+ } else {
+ Location location = context.getLocation(element);
+ context.report(ALLOW_BACKUP, element, location, ""
+ + "On SDK version 23 and up, your app data will be automatically "
+ + "backed up and restored on app install. Consider adding the "
+ + "attribute `android:fullBackupContent` to specify an `@xml` "
+ + "resource which configures which files to backup. More info: "
+ + BACKUP_DOCUMENTATION_URL);
+ }
+ }
+ } else if (mSeenApplication) {
+ if (context.isEnabled(ORDER)) {
+ context.report(ORDER, element, context.getLocation(element),
+ String.format("`<%1$s>` tag appears after `<application>` tag", tag));
+ }
+
+ // Don't complain for *every* element following the <application> tag
+ mSeenApplication = false;
+ }
+
+ if (tag.equals(TAG_USES_FEATURE)) {
+ Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (nameNode != null) {
+ String name = nameNode.getValue();
+ if (!name.isEmpty()) {
+ if (mUsesFeatures.contains(name)) {
+ String message = String.format(
+ "Duplicate declaration of uses-feature `%1$s`", name);
+ context.report(DUPLICATE_USES_FEATURE, element,
+ context.getLocation(nameNode), message);
+ } else {
+ mUsesFeatures.add(name);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given application element has a receiver with an intent filter
+ * action for GCM receive
+ */
+ private static boolean hasGcmReceiver(@NonNull Element application) {
+ NodeList applicationChildren = application.getChildNodes();
+ for (int i1 = 0, n1 = applicationChildren.getLength(); i1 < n1; i1++) {
+ Node applicationChild = applicationChildren.item(i1);
+ if (applicationChild.getNodeType() == Node.ELEMENT_NODE
+ && TAG_RECEIVER.equals(applicationChild.getNodeName())) {
+ NodeList receiverChildren = applicationChild.getChildNodes();
+ for (int i2 = 0, n2 = receiverChildren.getLength(); i2 < n2; i2++) {
+ Node receiverChild = receiverChildren.item(i2);
+ if (receiverChild.getNodeType() == Node.ELEMENT_NODE
+ && TAG_INTENT_FILTER.equals(receiverChild.getNodeName())) {
+ NodeList filterChildren = receiverChild.getChildNodes();
+ for (int i3 = 0, n3 = filterChildren.getLength(); i3 < n3; i3++) {
+ Node filterChild = filterChildren.item(i3);
+ if (filterChild.getNodeType() == Node.ELEMENT_NODE
+ && NODE_ACTION.equals(filterChild.getNodeName())) {
+ Element action = (Element) filterChild;
+ String name = action.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if ("com.google.android.c2dm.intent.RECEIVE".equals(name)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void checkMipmapIcon(@NonNull XmlContext context, @NonNull Element element) {
+ Attr attribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_ICON);
+ if (attribute == null) {
+ return;
+ }
+ String icon = attribute.getValue();
+ if (icon.startsWith(DRAWABLE_PREFIX)) {
+ if (TAG_ACTIVITY.equals(element.getTagName()) && !isLaunchableActivity(element)) {
+ return;
+ }
+
+ if (context.isEnabled(MIPMAP)
+ // Only complain if this app is skipping some densities
+ && context.getProject().getApplicableDensities() != null) {
+ context.report(MIPMAP, element, context.getLocation(attribute),
+ "Should use `@mipmap` instead of `@drawable` for launcher icons");
+ }
+ }
+ }
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private static boolean isLaunchableActivity(@NonNull Element element) {
+ if (!TAG_ACTIVITY.equals(element.getTagName())) {
+ return false;
+ }
+
+ for (Element child : LintUtils.getChildren(element)) {
+ if (child.getTagName().equals(TAG_INTENT_FILTER)) {
+ for (Element innerChild : LintUtils.getChildren(child)) {
+ if (innerChild.getTagName().equals("category")) { //$NON-NLS-1$
+ String categoryString = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ return "android.intent.category.LAUNCHER".equals(categoryString);
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /** Returns true iff the given manifest file is the main manifest file */
+ private static boolean isMainManifest(XmlContext context, File manifestFile) {
+ if (!context.getProject().isGradleProject()) {
+ // In non-gradle projects, just one manifest per project
+ return true;
+ }
+
+ AndroidProject model = context.getProject().getGradleProjectModel();
+ return model == null || manifestFile
+ .equals(model.getDefaultConfig().getSourceProvider().getManifestFile());
+ }
+
+ /**
+ * Returns true iff the given manifest file is in a debug-specific source set,
+ * or a test source set
+ */
+ private static boolean isDebugOrTestManifest(
+ @NonNull XmlContext context,
+ @NonNull File manifestFile) {
+ AndroidProject model = context.getProject().getGradleProjectModel();
+ if (model != null) {
+ // Quickly check if it's the main manifest first; that's the most likely scenario
+ if (manifestFile.equals(model.getDefaultConfig().getSourceProvider().getManifestFile())) {
+ return false;
+ }
+
+ // Debug build type?
+ for (BuildTypeContainer container : model.getBuildTypes()) {
+ if (container.getBuildType().isDebuggable()) {
+ if (manifestFile.equals(container.getSourceProvider().getManifestFile())) {
+ return true;
+ }
+ }
+ }
+
+ // Test source set?
+ for (SourceProviderContainer extra : model.getDefaultConfig().getExtraSourceProviders()) {
+ String artifactName = extra.getArtifactName();
+ if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)
+ && manifestFile.equals(extra.getSourceProvider().getManifestFile())) {
+ return true;
+ }
+ }
+ for (ProductFlavorContainer container : model.getProductFlavors()) {
+ for (SourceProviderContainer extra : container.getExtraSourceProviders()) {
+ String artifactName = extra.getArtifactName();
+ if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)
+ && manifestFile.equals(extra.getSourceProvider().getManifestFile())) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void checkDeviceAdmin(XmlContext context, Element element) {
+ List<Element> children = LintUtils.getChildren(element);
+ boolean requiredIntentFilterFound = false;
+ boolean deviceAdmin = false;
+ Attr locationNode = null;
+ for (Element child : children) {
+ String tagName = child.getTagName();
+ if (tagName.equals(TAG_INTENT_FILTER) && !requiredIntentFilterFound) {
+ boolean dataFound = false;
+ boolean actionFound = false;
+ for (Element filterChild : LintUtils.getChildren(child)) {
+ String filterTag = filterChild.getTagName();
+ if (filterTag.equals(NODE_ACTION)) {
+ String name = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if ("android.app.action.DEVICE_ADMIN_ENABLED".equals(name)) { //$NON-NLS-1$
+ actionFound = true;
+ }
+ } else if (filterTag.equals(NODE_DATA)) {
+ dataFound = true;
+ }
+ }
+ if (actionFound && !dataFound) {
+ requiredIntentFilterFound = true;
+ }
+ } else if (tagName.equals(NODE_METADATA)) {
+ Attr valueNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (valueNode != null) {
+ String name = valueNode.getValue();
+ if ("android.app.device_admin".equals(name)) { //$NON-NLS-1$
+ deviceAdmin = true;
+ locationNode = valueNode;
+ }
+ }
+ }
+ }
+
+ if (deviceAdmin && !requiredIntentFilterFound && context.isEnabled(DEVICE_ADMIN)) {
+ context.report(DEVICE_ADMIN, locationNode, context.getLocation(locationNode),
+ "You must have an intent filter for action "
+ + "`android.app.action.DEVICE_ADMIN_ENABLED`");
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestResourceDetector.java
new file mode 100644
index 0000000..63adda0
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestResourceDetector.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ICON;
+import static com.android.SdkConstants.ATTR_LABEL;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_THEME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.ide.common.resources.configuration.VersionQualifier;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.xml.AndroidManifest;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Detects references to resources in the manifest that vary by configuration
+ */
+public class ManifestResourceDetector extends ResourceXmlDetector {
+ /** Using resources in the manifest that vary by configuration */
+ @SuppressWarnings("unchecked")
+ public static final Issue ISSUE = Issue.create(
+ "ManifestResource", //$NON-NLS-1$
+ "Manifest Resource References",
+ "Elements in the manifest can reference resources, but those resources cannot " +
+ "vary across configurations (except as a special case, by version, and except " +
+ "for a few specific package attributes such as the application title and icon.)",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ new Implementation(
+ ManifestResourceDetector.class,
+ Scope.MANIFEST_AND_RESOURCE_SCOPE,
+ Scope.MANIFEST_SCOPE));
+
+ /**
+ * Map from resource name to resource type to manifest location; used
+ * in batch mode to report errors when resource overrides are found
+ */
+ private Map<String, Multimap<ResourceType, Location>> mManifestLocations;
+
+ /** Constructs a new {@link ManifestResourceDetector} */
+ public ManifestResourceDetector() {
+ }
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ if (endsWithIgnoreCase(context.file.getPath(), ANDROID_MANIFEST_XML)) {
+ checkManifest(context, document);
+ } else {
+ //noinspection VariableNotUsedInsideIf
+ if (mManifestLocations != null) {
+ checkResourceFile(context, document);
+ }
+ }
+ }
+
+ private void checkManifest(@NonNull XmlContext context, @NonNull Document document) {
+ LintClient client = context.getClient();
+ Project project = context.getProject();
+ AbstractResourceRepository repository = null;
+ if (client.supportsProjectResources()) {
+ repository = client.getProjectResources(project, true);
+ }
+ if (repository == null && !context.getScope().contains(Scope.RESOURCE_FILE)) {
+ // Can't perform incremental analysis without a resource repository
+ return;
+ }
+
+ Element root = document.getDocumentElement();
+ if (root != null) {
+ visit(context, root, repository);
+ }
+ }
+
+ private void visit(@NonNull XmlContext context, @NonNull Element element,
+ @Nullable AbstractResourceRepository repository) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Node node = attributes.item(i);
+ String value = node.getNodeValue();
+ if (value.startsWith(PREFIX_RESOURCE_REF)) {
+ Attr attribute = (Attr) node;
+ if (!isAllowedToVary(attribute)) {
+ checkReference(context, attribute, value, repository);
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ visit(context, ((Element)child), repository);
+ }
+ }
+ }
+
+ /**
+ * Is the given attribute allowed to reference a resource that has different
+ * values across configurations (other than with version qualifiers) ?
+ * <p>
+ * When the manifest is read, it has a fixed configuration with only the API level set.
+ * When strings are read, we can either read the actual string, or a resource reference.
+ * For labels and icons, we only read the resource reference -- that is the package manager
+ * doesn't need the actual string (like it would need for, say, the name of an activity),
+ * but just gets the resource ID, and then clients if they need the actual resource value can
+ * load it at that point using their current configuration.
+ * <p>
+ * To see which specific attributes in the manifest are processed this way, look at
+ * android.content.pm.PackageItemInfo to see what pieces of data are kept as raw resource
+ * IDs instead of loading their value. (For label resources we also keep the non localized
+ * label resource to allow people to specify hardcoded strings instead of a resource reference.)
+ *
+ * @param attribute the attribute node to look up
+ * @return true if this resource is allowed to have delayed configuration values
+ */
+ private static boolean isAllowedToVary(@NonNull Attr attribute) {
+ // This corresponds to the getResourceId() calls in PackageParser
+ // where we store the actual resource id such that they can be
+ // resolved later
+ String name = attribute.getLocalName();
+ if (ATTR_LABEL.equals(name)
+ || ATTR_ICON.equals(name)
+ || ATTR_THEME.equals(name)
+ || "description".equals(name)
+ || "logo".equals(name)
+ || "banner".equals(name)
+ || "sharedUserLabel".equals(name)) {
+ return ANDROID_URI.equals(attribute.getNamespaceURI());
+ }
+
+ return false;
+ }
+
+ private void checkReference(
+ @NonNull XmlContext context,
+ @NonNull Attr attribute,
+ @NonNull String value,
+ @Nullable AbstractResourceRepository repository) {
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (url != null && !url.framework) {
+ if (repository != null) {
+ List<ResourceItem> items = repository.getResourceItem(url.type, url.name);
+ if (items != null && items.size() > 1) {
+ List<String> list = Lists.newArrayListWithExpectedSize(5);
+ for (ResourceItem item : items) {
+ String qualifiers = item.getQualifiers();
+ // Default folder is okay
+ if (qualifiers.isEmpty()) {
+ continue;
+ }
+
+ // Version qualifier is okay
+ if (VersionQualifier.getQualifier(qualifiers) != null) {
+ continue;
+ }
+
+ list.add(qualifiers);
+ }
+ if (!list.isEmpty()) {
+ Collections.sort(list);
+ String message = getErrorMessage(Joiner.on(", ").join(list));
+ context.report(ISSUE, attribute, context.getValueLocation(attribute),
+ message);
+ }
+ }
+ } else if (!context.getDriver().isSuppressed(context, ISSUE, attribute)) {
+ // Don't have a resource repository; need to check resource files during batch
+ // run
+ if (mManifestLocations == null) {
+ mManifestLocations = Maps.newHashMap();
+ }
+ Multimap<ResourceType, Location> typeMap = mManifestLocations.get(url.name);
+ if (typeMap == null) {
+ typeMap = ArrayListMultimap.create();
+ mManifestLocations.put(url.name, typeMap);
+ }
+ typeMap.put(url.type, context.getValueLocation(attribute));
+ }
+ }
+ }
+
+ private void checkResourceFile(
+ @NonNull XmlContext context,
+ @NonNull Document document) {
+ File parentFile = context.file.getParentFile();
+ if (parentFile == null) {
+ return;
+ }
+ String parentName = parentFile.getName();
+ // Base folders are okay
+ int index = parentName.indexOf('-');
+ if (index == -1) {
+ return;
+ }
+
+ // Version qualifier is okay
+ String qualifiers = parentName.substring(index + 1);
+ if (VersionQualifier.getQualifier(qualifiers) != null) {
+ return;
+ }
+
+ ResourceFolderType folderType = context.getResourceFolderType();
+ if (folderType == ResourceFolderType.VALUES) {
+ Element root = document.getDocumentElement();
+ if (root != null) {
+ NodeList children = root.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element item = (Element)child;
+ String name = item.getAttribute(ATTR_NAME);
+ if (name != null && mManifestLocations.containsKey(name)) {
+ String tag = item.getTagName();
+ String typeString = tag;
+ if (tag.equals(TAG_ITEM)) {
+ typeString = item.getAttribute(ATTR_TYPE);
+ }
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type != null) {
+ reportIfFound(context, qualifiers, name, type, item);
+ }
+ }
+ }
+ }
+
+ }
+ } else if (folderType != null) {
+ String name = LintUtils.getBaseName(context.file.getName());
+ if (mManifestLocations.containsKey(name)) {
+ List<ResourceType> types =
+ FolderTypeRelationship.getRelatedResourceTypes(folderType);
+ for (ResourceType type : types) {
+ reportIfFound(context, qualifiers, name, type, document.getDocumentElement());
+ }
+ }
+ }
+ }
+
+ private void reportIfFound(@NonNull XmlContext context, @NonNull String qualifiers,
+ @NonNull String name, @NonNull ResourceType type, @Nullable Node secondary) {
+ Multimap<ResourceType, Location> typeMap = mManifestLocations.get(name);
+ if (typeMap != null) {
+ Collection<Location> locations = typeMap.get(type);
+ if (locations != null) {
+ for (Location location : locations) {
+ String message = getErrorMessage(qualifiers);
+ if (secondary != null) {
+ Location secondaryLocation = context.getLocation(secondary);
+ secondaryLocation.setSecondary(location.getSecondary());
+ secondaryLocation.setMessage("This value will not be used");
+ location.setSecondary(secondaryLocation);
+ }
+ context.report(ISSUE, location, message);
+ }
+ }
+ }
+ }
+
+ @NonNull
+ private static String getErrorMessage(@NonNull String qualifiers) {
+ return "Resources referenced from the manifest cannot vary by configuration "
+ + "(except for version qualifiers, e.g. `-v21`.) Found variation in " + qualifiers;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
new file mode 100644
index 0000000..6f627a0
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+
+/**
+ * Looks for usages of {@link java.lang.Math} methods which can be replaced with
+ * {@code android.util.FloatMath} methods to avoid casting.
+ */
+public class MathDetector extends Detector implements Detector.JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "FloatMath", //$NON-NLS-1$
+ "Using `FloatMath` instead of `Math`",
+
+ "In older versions of Android, using `android.util.FloatMath` was recommended " +
+ "for performance reasons when operating on floats. However, on modern hardware " +
+ "doubles are just as fast as float (though they take more memory), and in " +
+ "recent versions of Android, `FloatMath` is actually slower than using `java.lang.Math` " +
+ "due to the way the JIT optimizes `java.lang.Math`. Therefore, you should use " +
+ "`Math` instead of `FloatMath` if you are only targeting Froyo and above.",
+
+ Category.PERFORMANCE,
+ 3,
+ Severity.WARNING,
+ new Implementation(
+ MathDetector.class,
+ Scope.JAVA_FILE_SCOPE))
+ .addMoreInfo(
+ "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
+
+ /** Constructs a new {@link MathDetector} check */
+ public MathDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(
+ "sin", //$NON-NLS-1$
+ "cos", //$NON-NLS-1$
+ "ceil", //$NON-NLS-1$
+ "sqrt", //$NON-NLS-1$
+ "floor" //$NON-NLS-1$
+ );
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation call) {
+ Expression operand = call.astOperand();
+ if (operand == null || !operand.toString().equals("Math")) {
+ ResolvedNode resolved = context.resolve(call);
+ if (resolved instanceof ResolvedMethod &&
+ ((ResolvedMethod)resolved).getContainingClass().matches("android.util.FloatMath")
+ && context.getProject().getMinSdk() >= 8) {
+ String message = String.format(
+ "Use `java.lang.Math#%1$s` instead of `android.util.FloatMath#%1$s()` " +
+ "since it is faster as of API 8", call.astName().astValue());
+ Location location = operand != null
+ ? context.getRangeLocation(operand, 0, call.astName(), 0)
+ : context.getLocation(call);
+ context.report(ISSUE, call, location, message);
+ }
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
new file mode 100644
index 0000000..bcb275a
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_FRAGMENT;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.SdkConstants.TAG_ACTIVITY;
+import static com.android.SdkConstants.TAG_APPLICATION;
+import static com.android.SdkConstants.TAG_HEADER;
+import static com.android.SdkConstants.TAG_PROVIDER;
+import static com.android.SdkConstants.TAG_RECEIVER;
+import static com.android.SdkConstants.TAG_SERVICE;
+import static com.android.SdkConstants.TAG_STRING;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.SdkConstants.VIEW_TAG;
+import static com.android.resources.ResourceFolderType.LAYOUT;
+import static com.android.resources.ResourceFolderType.VALUES;
+import static com.android.resources.ResourceFolderType.XML;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Location.Handle;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.SdkUtils;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Checks to ensure that classes referenced in the manifest actually exist and are included
+ *
+ */
+public class MissingClassDetector extends LayoutDetector implements ClassScanner {
+ /** Manifest-referenced classes missing from the project or libraries */
+ public static final Issue MISSING = Issue.create(
+ "MissingRegistered", //$NON-NLS-1$
+ "Missing registered class",
+
+ "If a class is referenced in the manifest, it must also exist in the project (or in one " +
+ "of the libraries included by the project. This check helps uncover typos in " +
+ "registration names, or attempts to rename or move classes without updating the " +
+ "manifest file properly.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ new Implementation(
+ MissingClassDetector.class,
+ EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE,
+ Scope.JAVA_LIBRARIES, Scope.RESOURCE_FILE)))
+ .addMoreInfo("http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
+
+ /** Are activity, service, receiver etc subclasses instantiatable? */
+ public static final Issue INSTANTIATABLE = Issue.create(
+ "Instantiatable", //$NON-NLS-1$
+ "Registered class is not instantiatable",
+
+ "Activities, services, broadcast receivers etc. registered in the manifest file " +
+ "(or for custom views, in a layout file) " +
+ "must be \"instantiatable\" by the system, which means that the class must be " +
+ "public, it must have an empty public constructor, and if it's an inner class, " +
+ "it must be a static inner class.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ new Implementation(
+ MissingClassDetector.class,
+ Scope.CLASS_FILE_SCOPE));
+
+ /** Is the right character used for inner class separators? */
+ public static final Issue INNERCLASS = Issue.create(
+ "InnerclassSeparator", //$NON-NLS-1$
+ "Inner classes should use `$` rather than `.`",
+
+ "When you reference an inner class in a manifest file, you must use '$' instead of '.' as the separator character, i.e. Outer$Inner instead of Outer.Inner.\n" +
+ "\n" +
+ "(If you get this warning for a class which is not actually an inner class, it's because you are using uppercase characters in your package name, which is not conventional.)",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.WARNING,
+ new Implementation(
+ MissingClassDetector.class,
+ Scope.MANIFEST_SCOPE));
+
+ private Map<String, Location.Handle> mReferencedClasses;
+ private Set<String> mCustomViews;
+ private boolean mHaveClasses;
+
+ /** Constructs a new {@link MissingClassDetector} */
+ public MissingClassDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return ALL;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == VALUES || folderType == LAYOUT || folderType == XML;
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String pkg = null;
+ Node classNameNode;
+ String className;
+ String tag = element.getTagName();
+ ResourceFolderType folderType = context.getResourceFolderType();
+ if (folderType == VALUES) {
+ if (!tag.equals(TAG_STRING)) {
+ return;
+ }
+ Attr attr = element.getAttributeNode(ATTR_NAME);
+ if (attr == null) {
+ return;
+ }
+ className = attr.getValue();
+ classNameNode = attr;
+ } else if (folderType == LAYOUT) {
+ if (tag.indexOf('.') > 0) {
+ className = tag;
+ classNameNode = element;
+ } else if (tag.equals(VIEW_FRAGMENT) || tag.equals(VIEW_TAG)) {
+ Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (attr == null) {
+ attr = element.getAttributeNode(ATTR_CLASS);
+ }
+ if (attr == null) {
+ return;
+ }
+ className = attr.getValue();
+ classNameNode = attr;
+ } else {
+ return;
+ }
+ } else if (folderType == XML) {
+ if (!tag.equals(TAG_HEADER)) {
+ return;
+ }
+ Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_FRAGMENT);
+ if (attr == null) {
+ return;
+ }
+ className = attr.getValue();
+ classNameNode = attr;
+ } else {
+ // Manifest file
+ if (TAG_APPLICATION.equals(tag)
+ || TAG_ACTIVITY.equals(tag)
+ || TAG_SERVICE.equals(tag)
+ || TAG_RECEIVER.equals(tag)
+ || TAG_PROVIDER.equals(tag)) {
+ Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (attr == null) {
+ return;
+ }
+ className = attr.getValue();
+ classNameNode = attr;
+ pkg = context.getMainProject().getPackage();
+ } else {
+ return;
+ }
+ }
+ if (className.isEmpty()) {
+ return;
+ }
+
+ String fqcn;
+ int dotIndex = className.indexOf('.');
+ if (dotIndex <= 0) {
+ if (pkg == null) {
+ return; // value file
+ }
+ if (dotIndex == 0) {
+ fqcn = pkg + className;
+ } else {
+ // According to the <activity> manifest element documentation, this is not
+ // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
+ // but it appears in manifest files and appears to be supported by the runtime
+ // so handle this in code as well:
+ fqcn = pkg + '.' + className;
+ }
+ } else { // else: the class name is already a fully qualified class name
+ fqcn = className;
+ // Only look for fully qualified tracker names in analytics files
+ if (folderType == VALUES
+ && !SdkUtils.endsWith(context.file.getPath(), "analytics.xml")) { //$NON-NLS-1$
+ return;
+ }
+ }
+
+ String signature = ClassContext.getInternalName(fqcn);
+ if (signature.isEmpty() || signature.startsWith(ANDROID_PKG_PREFIX)) {
+ return;
+ }
+
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ Handle handle = null;
+ if (!context.getDriver().isSuppressed(context, MISSING, element)) {
+ if (mReferencedClasses == null) {
+ mReferencedClasses = Maps.newHashMapWithExpectedSize(16);
+ mCustomViews = Sets.newHashSetWithExpectedSize(8);
+ }
+
+ handle = context.createLocationHandle(element);
+ mReferencedClasses.put(signature, handle);
+ if (folderType == LAYOUT && !tag.equals(VIEW_FRAGMENT)) {
+ mCustomViews.add(ClassContext.getInternalName(className));
+ }
+ }
+
+ if (signature.indexOf('$') != -1) {
+ checkInnerClass(context, element, pkg, classNameNode, className);
+
+ // The internal name contains a $ which means it's an inner class.
+ // The conversion from fqcn to internal name is a bit ambiguous:
+ // "a.b.C.D" usually means "inner class D in class C in package a.b".
+ // However, it can (see issue 31592) also mean class D in package "a.b.C".
+ // To make sure we don't falsely complain that foo/Bar$Baz doesn't exist,
+ // in case the user has actually created a package named foo/Bar and a proper
+ // class named Baz, we register *both* into the reference map.
+ // When generating errors we'll look for these an rip them back out if
+ // it looks like one of the two variations have been seen.
+ if (handle != null) {
+ // Assume that each successive $ is really a capitalized package name
+ // instead. In other words, for A$B$C$D (assumed to be class A with
+ // inner classes A.B, A.B.C and A.B.C.D) generate the following possible
+ // referenced classes A/B$C$D (class B in package A with inner classes C and C.D),
+ // A/B/C$D and A/B/C/D
+ while (true) {
+ int index = signature.indexOf('$');
+ if (index == -1) {
+ break;
+ }
+ signature = signature.substring(0, index) + '/'
+ + signature.substring(index + 1);
+ mReferencedClasses.put(signature, handle);
+ if (folderType == LAYOUT && !tag.equals(VIEW_FRAGMENT)) {
+ mCustomViews.add(signature);
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkInnerClass(XmlContext context, Element element, String pkg,
+ Node classNameNode, String className) {
+ if (pkg != null && className.indexOf('$') == -1 && className.indexOf('.', 1) > 0) {
+ boolean haveUpperCase = false;
+ for (int i = 0, n = pkg.length(); i < n; i++) {
+ if (Character.isUpperCase(pkg.charAt(i))) {
+ haveUpperCase = true;
+ break;
+ }
+ }
+ if (!haveUpperCase) {
+ String fixed = className.charAt(0) + className.substring(1).replace('.','$');
+ String message = "Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \"" +
+ className + "\" with \"" + fixed + "\"";
+ Location location = context.getLocation(classNameNode);
+ context.report(INNERCLASS, element, location, message);
+ }
+ }
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (context.getProject() == context.getMainProject() && mHaveClasses
+ && mReferencedClasses != null && !mReferencedClasses.isEmpty()
+ && context.getDriver().getScope().contains(Scope.CLASS_FILE)) {
+ List<String> classes = new ArrayList<String>(mReferencedClasses.keySet());
+ Collections.sort(classes);
+ for (String owner : classes) {
+ Location.Handle handle = mReferencedClasses.get(owner);
+ String fqcn = ClassContext.getFqcn(owner);
+
+ String signature = ClassContext.getInternalName(fqcn);
+ if (!signature.equals(owner)) {
+ if (!mReferencedClasses.containsKey(signature)) {
+ continue;
+ }
+ } else if (signature.indexOf('$') != -1) {
+ signature = signature.replace('$', '/');
+ if (!mReferencedClasses.containsKey(signature)) {
+ continue;
+ }
+ }
+ mReferencedClasses.remove(owner);
+
+ // Ignore usages of platform libraries
+ if (owner.startsWith("android/")) { //$NON-NLS-1$
+ continue;
+ }
+
+ String message = String.format(
+ "Class referenced in the manifest, `%1$s`, was not found in the project or the libraries",
+ fqcn);
+ Location location = handle.resolve();
+ File parentFile = location.getFile().getParentFile();
+ if (parentFile != null) {
+ String parent = parentFile.getName();
+ ResourceFolderType type = ResourceFolderType.getFolderType(parent);
+ if (type == LAYOUT) {
+ message = String.format(
+ "Class referenced in the layout file, `%1$s`, was not found in the project or the libraries",
+ fqcn);
+ } else if (type == XML) {
+ message = String.format(
+ "Class referenced in the preference header file, `%1$s`, was not found in the project or the libraries",
+ fqcn);
+
+ } else if (type == VALUES) {
+ message = String.format(
+ "Class referenced in the analytics file, `%1$s`, was not found in the project or the libraries",
+ fqcn);
+ }
+ }
+
+ context.report(MISSING, location, message);
+ }
+ }
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @Override
+ public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
+ if (!mHaveClasses && !context.isFromClassLibrary()
+ && context.getProject() == context.getMainProject()) {
+ mHaveClasses = true;
+ }
+ String curr = classNode.name;
+ if (mReferencedClasses != null && mReferencedClasses.containsKey(curr)) {
+ boolean isCustomView = mCustomViews.contains(curr);
+ removeReferences(curr);
+
+ // Ensure that the class is public, non static and has a null constructor!
+
+ if ((classNode.access & Opcodes.ACC_PUBLIC) == 0) {
+ context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
+ "This class should be public (%1$s)",
+ ClassContext.createSignature(classNode.name, null, null)));
+ return;
+ }
+
+ if (classNode.name.indexOf('$') != -1 && !LintUtils.isStaticInnerClass(classNode)) {
+ context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
+ "This inner class should be static (%1$s)",
+ ClassContext.createSignature(classNode.name, null, null)));
+ return;
+ }
+
+ boolean hasDefaultConstructor = false;
+ @SuppressWarnings("rawtypes") // ASM API
+ List methodList = classNode.methods;
+ for (Object m : methodList) {
+ MethodNode method = (MethodNode) m;
+ if (method.name.equals(CONSTRUCTOR_NAME)) {
+ if (method.desc.equals("()V")) { //$NON-NLS-1$
+ // The constructor must be public
+ if ((method.access & Opcodes.ACC_PUBLIC) != 0) {
+ hasDefaultConstructor = true;
+ } else {
+ context.report(INSTANTIATABLE, context.getLocation(method, classNode),
+ "The default constructor must be public");
+ // Also mark that we have a constructor so we don't complain again
+ // below since we've already emitted a more specific error related
+ // to the default constructor
+ hasDefaultConstructor = true;
+ }
+ }
+ }
+ }
+
+ if (!hasDefaultConstructor && !isCustomView && !context.isFromClassLibrary()
+ && context.getProject().getReportIssues()) {
+ context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
+ "This class should provide a default constructor (a public constructor with no arguments) (%1$s)",
+ ClassContext.createSignature(classNode.name, null, null)));
+ }
+ }
+ }
+
+ private void removeReferences(String curr) {
+ mReferencedClasses.remove(curr);
+
+ // Since "A.B.C" is ambiguous whether it's referencing a class in package A.B or
+ // an inner class C in package A, we insert multiple possible references when we
+ // encounter the A.B.C reference; now that we've seen the actual class we need to
+ // remove all the possible permutations we've added such that the permutations
+ // don't count as unreferenced classes.
+ int index = curr.lastIndexOf('/');
+ if (index == -1) {
+ return;
+ }
+ boolean hasCapitalizedPackageName = false;
+ for (int i = index - 1; i >= 0; i--) {
+ char c = curr.charAt(i);
+ if (Character.isUpperCase(c)) {
+ hasCapitalizedPackageName = true;
+ break;
+ }
+ }
+ if (!hasCapitalizedPackageName) {
+ // No path ambiguity
+ return;
+ }
+
+ while (true) {
+ index = curr.lastIndexOf('/');
+ if (index == -1) {
+ break;
+ }
+ curr = curr.substring(0, index) + '$' + curr.substring(index + 1);
+ mReferencedClasses.remove(curr);
+ }
+ }
+
+ /**
+ * Given an error message produced by this lint detector for the given issue type,
+ * returns the old value to be replaced in the source code.
+ * <p>
+ * Intended for IDE quickfix implementations.
+ *
+ * @param issue the corresponding issue
+ * @param errorMessage the error message associated with the error
+ * @param format the format of the error message
+ * @return the corresponding old value, or null if not recognized
+ */
+ @Nullable
+ public static String getOldValue(@NonNull Issue issue, @NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ if (issue == INNERCLASS) {
+ errorMessage = format.toText(errorMessage);
+ return LintUtils.findSubstring(errorMessage, " replace \"", "\"");
+ }
+
+ return null;
+ }
+
+ /**
+ * Given an error message produced by this lint detector for the given issue type,
+ * returns the new value to be put into the source code.
+ * <p>
+ * Intended for IDE quickfix implementations.
+ *
+ * @param issue the corresponding issue
+ * @param errorMessage the error message associated with the error
+ * @param format the format of the error message
+ * @return the corresponding new value, or null if not recognized
+ */
+ @Nullable
+ public static String getNewValue(@NonNull Issue issue, @NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ if (issue == INNERCLASS) {
+ errorMessage = format.toText(errorMessage);
+ return LintUtils.findSubstring(errorMessage, " with \"", "\"");
+ }
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NegativeMarginDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NegativeMarginDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NegativeMarginDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NegativeMarginDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NfcTechListDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NfcTechListDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NfcTechListDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NfcTechListDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideConcreteDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideConcreteDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideConcreteDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideConcreteDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
new file mode 100644
index 0000000..f6fbb13
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedField;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.objectweb.asm.Opcodes;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Node;
+
+/**
+ * Looks for Parcelable classes that are missing a CREATOR field
+ */
+public class ParcelDetector extends Detector implements Detector.JavaScanner {
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "ParcelCreator", //$NON-NLS-1$
+ "Missing Parcelable `CREATOR` field",
+
+ "According to the `Parcelable` interface documentation, " +
+ "\"Classes implementing the Parcelable interface must also have a " +
+ "static field called `CREATOR`, which is an object implementing the " +
+ "`Parcelable.Creator` interface.\"",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.ERROR,
+ new Implementation(
+ ParcelDetector.class,
+ Scope.JAVA_FILE_SCOPE))
+ .addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html");
+
+ /** Constructs a new {@link com.android.tools.lint.checks.ParcelDetector} check */
+ public ParcelDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Collections.singletonList("android.os.Parcelable");
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+ @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+ if (node == null) {
+ // Anonymous classes aren't parcelable
+ return;
+ }
+ // Only applies to concrete classes
+ int flags = node.astModifiers().getExplicitModifierFlags();
+ if ((flags & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0) {
+ return;
+ }
+
+ // Parceling spans is handled in TextUtils#CHAR_SEQUENCE_CREATOR
+ if (!cls.isImplementing("android.text.ParcelableSpan", false)) {
+ ResolvedField field = cls.getField("CREATOR", false);
+ if (field == null) {
+ Location location = context.getLocation(node.astName());
+ context.report(ISSUE, node, location,
+ "This class implements `Parcelable` but does not "
+ + "provide a `CREATOR` field");
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java
new file mode 100644
index 0000000..439f0e5
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionFinder.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CLASS_INTENT;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_READ;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_WRITE;
+import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.JavaContext;
+
+import java.util.ListIterator;
+
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.Cast;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.Node;
+import lombok.ast.NullLiteral;
+import lombok.ast.Select;
+import lombok.ast.Statement;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Utility for locating permissions required by an intent or content resolver
+ */
+public class PermissionFinder {
+ /**
+ * Operation that has a permission requirement -- such as a method call,
+ * a content resolver read or write operation, an intent, etc.
+ */
+ public enum Operation {
+ CALL, ACTION, READ, WRITE;
+
+ /** Prefix to use when describing a name with a permission requirement */
+ public String prefix() {
+ switch (this) {
+ case ACTION:
+ return "by intent";
+ case READ:
+ return "to read";
+ case WRITE:
+ return "to write";
+ case CALL:
+ default:
+ return "by";
+ }
+ }
+ }
+
+ /** A permission requirement given a name and operation */
+ public static class Result {
+ @NonNull public final PermissionRequirement requirement;
+ @NonNull public final String name;
+ @NonNull public final Operation operation;
+
+ public Result(
+ @NonNull Operation operation,
+ @NonNull PermissionRequirement requirement,
+ @NonNull String name) {
+ this.operation = operation;
+ this.requirement = requirement;
+ this.name = name;
+ }
+ }
+
+ /**
+ * Searches for a permission requirement for the given parameter in the given call
+ *
+ * @param operation the operation to look up
+ * @param context the context to use for lookup
+ * @param parameter the parameter which contains the value which implies the permission
+ * @return the result with the permission requirement, or null if nothing is found
+ */
+ @Nullable
+ public static Result findRequiredPermissions(
+ @NonNull Operation operation,
+ @NonNull JavaContext context,
+ @NonNull Node parameter) {
+
+ // To find the permission required by an intent, we proceed in 3 steps:
+ // (1) Locate the parameter in the start call that corresponds to
+ // the Intent
+ //
+ // (2) Find the place where the intent is initialized, and figure
+ // out the action name being passed to it.
+ //
+ // (3) Find the place where the action is defined, and look for permission
+ // annotations on that action declaration!
+
+ return new PermissionFinder(context, operation).search(parameter);
+ }
+
+ private PermissionFinder(@NonNull JavaContext context, @NonNull Operation operation) {
+ mContext = context;
+ mOperation = operation;
+ }
+
+ @NonNull private final JavaContext mContext;
+ @NonNull private final Operation mOperation;
+
+ @Nullable
+ public Result search(@NonNull Node node) {
+ if (node instanceof NullLiteral) {
+ return null;
+ } else if (node instanceof InlineIfExpression) {
+ InlineIfExpression expression = (InlineIfExpression) node;
+ if (expression.astIfTrue() != null) {
+ Result result = search(expression.astIfTrue());
+ if (result != null) {
+ return result;
+ }
+ }
+ if (expression.astIfFalse() != null) {
+ Result result = search(expression.astIfFalse());
+ if (result != null) {
+ return result;
+ }
+ }
+ } else if (node instanceof Cast) {
+ Cast cast = (Cast) node;
+ return search(cast.astOperand());
+ } else if (node instanceof ConstructorInvocation && mOperation == Operation.ACTION) {
+ // Identifies "new Intent(argument)" calls and, if found, continues
+ // resolving the argument instead looking for the action definition
+ ConstructorInvocation call = (ConstructorInvocation) node;
+ String type = call.astTypeReference().getTypeName();
+ if (type.equals("Intent") || type.equals(CLASS_INTENT)) {
+ Expression action = call.astArguments().first();
+ if (action != null) {
+ return search(action);
+ }
+ }
+ return null;
+ } else if ((node instanceof VariableReference || node instanceof Select)) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) resolved;
+ if (mOperation == Operation.ACTION) {
+ ResolvedAnnotation annotation = field.getAnnotation(PERMISSION_ANNOTATION);
+ if (annotation != null) {
+ return getPermissionRequirement(field, annotation);
+ }
+ } else if (mOperation == Operation.READ || mOperation == Operation.WRITE) {
+ String fqn = mOperation == Operation.READ
+ ? PERMISSION_ANNOTATION_READ : PERMISSION_ANNOTATION_WRITE;
+ ResolvedAnnotation annotation = field.getAnnotation(fqn);
+ if (annotation != null) {
+ Object o = annotation.getValue();
+ if (o instanceof ResolvedAnnotation) {
+ annotation = (ResolvedAnnotation) o;
+ if (annotation.matches(PERMISSION_ANNOTATION)) {
+ return getPermissionRequirement(field, annotation);
+ }
+ } else {
+ // The complex annotations used for read/write cannot be
+ // expressed in the external annotations format, so they're inlined.
+ // (See Extractor.AnnotationData#write).
+ //
+ // Instead we've inlined the fields of the annotation on the
+ // outer one:
+ return getPermissionRequirement(field, annotation);
+ }
+ }
+ } else {
+ assert false : mOperation;
+ }
+ } else if (node instanceof VariableReference) {
+ Statement statement = getParentOfType(node, Statement.class, false);
+ if (statement != null) {
+ ListIterator<Node> iterator =
+ statement.getParent().getChildren().listIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next() == statement) {
+ if (iterator.hasPrevious()) { // should always be true
+ iterator.previous();
+ }
+ break;
+ }
+ }
+
+ String targetName = ((VariableReference)node).astIdentifier().astValue();
+ while (iterator.hasPrevious()) {
+ Node previous = iterator.previous();
+ if (previous instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) previous;
+ VariableDefinition definition = declaration.astDefinition();
+ for (VariableDefinitionEntry entry : definition
+ .astVariables()) {
+ if (entry.astInitializer() != null
+ && entry.astName().astValue().equals(targetName)) {
+ return search(entry.astInitializer());
+ }
+ }
+ } else if (previous instanceof ExpressionStatement) {
+ ExpressionStatement expressionStatement =
+ (ExpressionStatement) previous;
+ Expression expression = expressionStatement.astExpression();
+ if (expression instanceof BinaryExpression &&
+ ((BinaryExpression) expression).astOperator()
+ == BinaryOperator.ASSIGN) {
+ BinaryExpression binaryExpression = (BinaryExpression) expression;
+ if (targetName.equals(binaryExpression.astLeft().toString())) {
+ return search(binaryExpression.astRight());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @NonNull
+ private Result getPermissionRequirement(
+ @NonNull ResolvedField field,
+ @NonNull ResolvedAnnotation annotation) {
+ PermissionRequirement requirement = PermissionRequirement.create(mContext, annotation);
+ ResolvedClass containingClass = field.getContainingClass();
+ String name = containingClass != null
+ ? containingClass.getSimpleName() + "." + field.getName()
+ : field.getName();
+ return new Result(mOperation, requirement, name);
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionHolder.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionHolder.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionHolder.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionHolder.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java
new file mode 100644
index 0000000..3ba9e18
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PermissionRequirement.java
@@ -0,0 +1,789 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+
+import static com.android.SdkConstants.ATTR_VALUE;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.ATTR_ALL_OF;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.ATTR_ANY_OF;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.ATTR_CONDITIONAL;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Node;
+import lombok.ast.Select;
+import lombok.ast.VariableDefinitionEntry;
+
+/**
+ * A permission requirement is a boolean expression of permission names that a
+ * caller must satisfy for a given Android API.
+ */
+public abstract class PermissionRequirement {
+ public static final String ATTR_PROTECTION_LEVEL = "protectionLevel"; //$NON-NLS-1$
+ public static final String VALUE_DANGEROUS = "dangerous"; //$NON-NLS-1$
+
+ protected final ResolvedAnnotation annotation;
+ private int firstApi;
+ private int lastApi;
+
+ @SuppressWarnings("ConstantConditions")
+ public static final PermissionRequirement NONE = new PermissionRequirement(null) {
+ @Override
+ public boolean isSatisfied(@NonNull PermissionHolder available) {
+ return true;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull PermissionHolder available) {
+ return false;
+ }
+
+ @Override
+ public boolean isConditional() {
+ return false;
+ }
+
+ @Override
+ public boolean isRevocable(@NonNull PermissionHolder revocable) {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "None";
+ }
+
+ @Override
+ protected void addMissingPermissions(@NonNull PermissionHolder available,
+ @NonNull Set<String> result) {
+ }
+
+ @Override
+ protected void addRevocablePermissions(@NonNull Set<String> result,
+ @NonNull PermissionHolder revocable) {
+ }
+
+ @Nullable
+ @Override
+ public BinaryOperator getOperator() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<PermissionRequirement> getChildren() {
+ return Collections.emptyList();
+ }
+ };
+
+ private PermissionRequirement(@NonNull ResolvedAnnotation annotation) {
+ this.annotation = annotation;
+ }
+
+ @NonNull
+ public static PermissionRequirement create(
+ @Nullable Context context,
+ @NonNull ResolvedAnnotation annotation) {
+ String value = (String)annotation.getValue(ATTR_VALUE);
+ if (value != null && !value.isEmpty()) {
+ for (int i = 0, n = value.length(); i < n; i++) {
+ char c = value.charAt(i);
+ // See if it's a complex expression and if so build it up
+ if (c == '&' || c == '|' || c == '^') {
+ return Complex.parse(annotation, context, value);
+ }
+ }
+
+ return new Single(annotation, value);
+ }
+
+ Object v = annotation.getValue(ATTR_ANY_OF);
+ String[] anyOf = getAnnotationStrings(v);
+ if (anyOf != null) {
+ if (anyOf.length > 1) {
+ return new Many(annotation, BinaryOperator.LOGICAL_OR, anyOf);
+ } else if (anyOf.length == 1) {
+ return new Single(annotation, anyOf[0]);
+ }
+ }
+
+ v = annotation.getValue(ATTR_ALL_OF);
+ String[] allOf = getAnnotationStrings(v);
+ if (allOf != null) {
+ if (allOf.length > 1) {
+ return new Many(annotation, BinaryOperator.LOGICAL_AND, allOf);
+ } else if (allOf.length == 1) {
+ return new Single(annotation, allOf[0]);
+ }
+ }
+
+ return NONE;
+ }
+
+ @Nullable
+ private static String[] getAnnotationStrings(@Nullable Object v) {
+ if (v != null) {
+ if (v instanceof String[]) {
+ return (String[])v;
+ } else if (v instanceof String) {
+ return new String[] { (String)v };
+ } else if (v instanceof Object[]) {
+ List<String> strings = Lists.newArrayList();
+ for (Object o : (Object[])v) {
+ if (o instanceof ResolvedField) {
+ Object vs = ((ResolvedField)o).getValue();
+ if (vs instanceof String) {
+ strings.add((String)vs);
+ }
+ } else if (o instanceof String) {
+ strings.add((String)o);
+ }
+ }
+ return strings.toArray(new String[strings.size()]);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns false if this permission does not apply given the specified minimum and
+ * target sdk versions
+ *
+ * @param minSdkVersion the minimum SDK version
+ * @param targetSdkVersion the target SDK version
+ * @return true if this permission requirement applies for the given versions
+ */
+ /**
+ * Returns false if this permission does not apply given the specified minimum and target
+ * sdk versions
+ *
+ * @param available the permission holder which also knows the min and target versions
+ * @return true if this permission requirement applies for the given versions
+ */
+ protected boolean appliesTo(@NonNull PermissionHolder available) {
+ if (firstApi == 0) { // initialized?
+ firstApi = -1; // initialized, not specified
+
+ // Not initialized
+ Object o = annotation.getValue("apis");
+ if (o instanceof String) {
+ String range = (String)o;
+ // Currently only support the syntax "a..b" where a and b are inclusive end points
+ // and where "a" and "b" are optional
+ int index = range.indexOf("..");
+ if (index != -1) {
+ try {
+ if (index > 0) {
+ firstApi = Integer.parseInt(range.substring(0, index));
+ } else {
+ firstApi = 1;
+ }
+ if (index + 2 < range.length()) {
+ lastApi = Integer.parseInt(range.substring(index + 2));
+ } else {
+ lastApi = Integer.MAX_VALUE;
+ }
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ }
+ }
+
+ if (firstApi != -1) {
+ AndroidVersion minSdkVersion = available.getMinSdkVersion();
+ if (minSdkVersion.getFeatureLevel() > lastApi) {
+ return false;
+ }
+
+ AndroidVersion targetSdkVersion = available.getTargetSdkVersion();
+ if (targetSdkVersion.getFeatureLevel() < firstApi) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether this requirement is conditional, meaning that there are
+ * some circumstances in which the requirement is not necessary. For
+ * example, consider
+ * {@code android.app.backup.BackupManager.dataChanged(java.lang.String)} .
+ * Here the {@code android.permission.BACKUP} is required but only if the
+ * argument is not your own package.
+ * <p>
+ * This is used to handle permissions differently between the "missing" and
+ * "unused" checks. When checking for missing permissions, we err on the
+ * side of caution: if you are missing a permission, but the permission is
+ * conditional, you may not need it so we may not want to complain. However,
+ * when looking for unused permissions, we don't want to flag the
+ * conditional permissions as unused since they may be required.
+ *
+ * @return true if this requirement is conditional
+ */
+ public boolean isConditional() {
+ Object o = annotation.getValue(ATTR_CONDITIONAL);
+ if (o instanceof Boolean) {
+ return (Boolean)o;
+ } else if (o instanceof ResolvedField) {
+ o = ((ResolvedField)o).getValue();
+ if (o instanceof Boolean) {
+ return (Boolean)o;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether this requirement is for a single permission (rather than
+ * a boolean expression such as one permission or another.)
+ *
+ * @return true if this requirement is just a simple permission name
+ */
+ public boolean isSingle() {
+ return true;
+ }
+
+ /**
+ * Whether the permission requirement is satisfied given the set of granted permissions
+ *
+ * @param available the available permissions
+ * @return true if all permissions specified by this requirement are available
+ */
+ public abstract boolean isSatisfied(@NonNull PermissionHolder available);
+
+ /** Describes the missing permissions (e.g. "P1, P2 and P3") */
+ public String describeMissingPermissions(@NonNull PermissionHolder available) {
+ return "";
+ }
+
+ /** Returns the missing permissions (e.g. {"P1", "P2", "P3"} */
+ public Set<String> getMissingPermissions(@NonNull PermissionHolder available) {
+ Set<String> result = Sets.newHashSet();
+ addMissingPermissions(available, result);
+ return result;
+ }
+
+ protected abstract void addMissingPermissions(@NonNull PermissionHolder available,
+ @NonNull Set<String> result);
+
+ /** Returns the permissions in the requirement that are revocable */
+ public Set<String> getRevocablePermissions(@NonNull PermissionHolder revocable) {
+ Set<String> result = Sets.newHashSet();
+ addRevocablePermissions(result, revocable);
+ return result;
+ }
+
+ protected abstract void addRevocablePermissions(@NonNull Set<String> result,
+ @NonNull PermissionHolder revocable);
+
+ /**
+ * Returns whether this permission is revocable
+ *
+ * @param revocable the set of revocable permissions
+ * @return true if a user can revoke the permission
+ */
+ public abstract boolean isRevocable(@NonNull PermissionHolder revocable);
+
+ /**
+ * For permission requirements that combine children, the operator to combine them with; null
+ * for leaf nodes
+ */
+ @Nullable
+ public abstract BinaryOperator getOperator();
+
+ /**
+ * Returns nested requirements, combined via {@link #getOperator()}
+ */
+ @NonNull
+ public abstract Iterable<PermissionRequirement> getChildren();
+
+ /** Require a single permission */
+ private static class Single extends PermissionRequirement {
+ public final String name;
+
+ public Single(@NonNull ResolvedAnnotation annotation, @NonNull String name) {
+ super(annotation);
+ this.name = name;
+ }
+
+ @Override
+ public boolean isRevocable(@NonNull PermissionHolder revocable) {
+ return revocable.isRevocable(name) || isRevocableSystemPermission(name);
+ }
+
+ @Nullable
+ @Override
+ public BinaryOperator getOperator() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<PermissionRequirement> getChildren() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean isSingle() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public boolean isSatisfied(@NonNull PermissionHolder available) {
+ return available.hasPermission(name) || !appliesTo(available);
+ }
+
+ @Override
+ public String describeMissingPermissions(@NonNull PermissionHolder available) {
+ return isSatisfied(available) ? "" : name;
+ }
+
+ @Override
+ protected void addMissingPermissions(@NonNull PermissionHolder available,
+ @NonNull Set<String> missing) {
+ if (!isSatisfied(available)) {
+ missing.add(name);
+ }
+ }
+
+ @Override
+ protected void addRevocablePermissions(@NonNull Set<String> result,
+ @NonNull PermissionHolder revocable) {
+ if (isRevocable(revocable)) {
+ result.add(name);
+ }
+ }
+ }
+
+ protected static void appendOperator(StringBuilder sb, BinaryOperator operator) {
+ sb.append(' ');
+ if (operator == BinaryOperator.LOGICAL_AND) {
+ sb.append("and");
+ } else if (operator == BinaryOperator.LOGICAL_OR) {
+ sb.append("or");
+ } else {
+ assert operator == BinaryOperator.BITWISE_XOR : operator;
+ sb.append("xor");
+ }
+ sb.append(' ');
+ }
+
+ /**
+ * Require a series of permissions, all with the same operator.
+ */
+ private static class Many extends PermissionRequirement {
+ public final BinaryOperator operator;
+ public final List<PermissionRequirement> permissions;
+
+ public Many(
+ @NonNull ResolvedAnnotation annotation,
+ BinaryOperator operator,
+ String[] names) {
+ super(annotation);
+ assert operator == BinaryOperator.LOGICAL_OR
+ || operator == BinaryOperator.LOGICAL_AND : operator;
+ assert names.length >= 2;
+ this.operator = operator;
+ this.permissions = Lists.newArrayListWithExpectedSize(names.length);
+ for (String name : names) {
+ permissions.add(new Single(annotation, name));
+ }
+ }
+
+ @Override
+ public boolean isSingle() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(permissions.get(0));
+
+ for (int i = 1; i < permissions.size(); i++) {
+ appendOperator(sb, operator);
+ sb.append(permissions.get(i));
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public boolean isSatisfied(@NonNull PermissionHolder available) {
+ if (operator == BinaryOperator.LOGICAL_AND) {
+ for (PermissionRequirement requirement : permissions) {
+ if (!requirement.isSatisfied(available) && requirement.appliesTo(available)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ assert operator == BinaryOperator.LOGICAL_OR : operator;
+ for (PermissionRequirement requirement : permissions) {
+ if (requirement.isSatisfied(available) || !requirement.appliesTo(available)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public String describeMissingPermissions(@NonNull PermissionHolder available) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (PermissionRequirement requirement : permissions) {
+ if (!requirement.isSatisfied(available)) {
+ if (first) {
+ first = false;
+ } else {
+ appendOperator(sb, operator);
+ }
+ sb.append(requirement.describeMissingPermissions(available));
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ protected void addMissingPermissions(@NonNull PermissionHolder available,
+ @NonNull Set<String> missing) {
+ for (PermissionRequirement requirement : permissions) {
+ if (!requirement.isSatisfied(available)) {
+ requirement.addMissingPermissions(available, missing);
+ }
+ }
+ }
+
+ @Override
+ protected void addRevocablePermissions(@NonNull Set<String> result,
+ @NonNull PermissionHolder revocable) {
+ for (PermissionRequirement requirement : permissions) {
+ requirement.addRevocablePermissions(result, revocable);
+ }
+ }
+
+ @Override
+ public boolean isRevocable(@NonNull PermissionHolder revocable) {
+ // TODO: Pass in the available set of permissions here, and if
+ // the operator is BinaryOperator.LOGICAL_OR, only return revocable=true
+ // if an unsatisfied permission is also revocable. In other words,
+ // if multiple permissions are allowed, and some of them are satisfied and
+ // not revocable the overall permission requirement is not revocable.
+ for (PermissionRequirement requirement : permissions) {
+ if (requirement.isRevocable(revocable)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public BinaryOperator getOperator() {
+ return operator;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<PermissionRequirement> getChildren() {
+ return permissions;
+ }
+ }
+
+ /**
+ * Require multiple permissions. This is a group of permissions with some
+ * associated boolean logic, such as "B or (C and (D or E))".
+ */
+ private static class Complex extends PermissionRequirement {
+ public final BinaryOperator operator;
+ public final PermissionRequirement left;
+ public final PermissionRequirement right;
+
+ public Complex(
+ @NonNull ResolvedAnnotation annotation,
+ BinaryOperator operator,
+ PermissionRequirement left,
+ PermissionRequirement right) {
+ super(annotation);
+ this.operator = operator;
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public boolean isSingle() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ boolean needsParentheses = left instanceof Complex &&
+ ((Complex) left).operator != BinaryOperator.LOGICAL_AND;
+ if (needsParentheses) {
+ sb.append('(');
+ }
+ sb.append(left.toString());
+ if (needsParentheses) {
+ sb.append(')');
+ }
+
+ appendOperator(sb, operator);
+
+ needsParentheses = right instanceof Complex &&
+ ((Complex) right).operator != BinaryOperator.LOGICAL_AND;
+ if (needsParentheses) {
+ sb.append('(');
+ }
+ sb.append(right.toString());
+ if (needsParentheses) {
+ sb.append(')');
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public boolean isSatisfied(@NonNull PermissionHolder available) {
+ boolean satisfiedLeft = left.isSatisfied(available) || !left.appliesTo(available);
+ boolean satisfiedRight = right.isSatisfied(available) || !right.appliesTo(available);
+ if (operator == BinaryOperator.LOGICAL_AND) {
+ return satisfiedLeft && satisfiedRight;
+ } else if (operator == BinaryOperator.LOGICAL_OR) {
+ return satisfiedLeft || satisfiedRight;
+ } else {
+ assert operator == BinaryOperator.BITWISE_XOR : operator;
+ return satisfiedLeft ^ satisfiedRight;
+ }
+ }
+
+ @Override
+ public String describeMissingPermissions(@NonNull PermissionHolder available) {
+ boolean satisfiedLeft = left.isSatisfied(available);
+ boolean satisfiedRight = right.isSatisfied(available);
+ if (operator == BinaryOperator.LOGICAL_AND || operator == BinaryOperator.LOGICAL_OR) {
+ if (satisfiedLeft) {
+ if (satisfiedRight) {
+ return "";
+ }
+ return right.describeMissingPermissions(available);
+ } else if (satisfiedRight) {
+ return left.describeMissingPermissions(available);
+ } else {
+ StringBuilder sb = new StringBuilder();
+ sb.append(left.describeMissingPermissions(available));
+ appendOperator(sb, operator);
+ sb.append(right.describeMissingPermissions(available));
+ return sb.toString();
+ }
+ } else {
+ assert operator == BinaryOperator.BITWISE_XOR : operator;
+ return toString();
+ }
+ }
+
+ @Override
+ protected void addMissingPermissions(@NonNull PermissionHolder available,
+ @NonNull Set<String> missing) {
+ boolean satisfiedLeft = left.isSatisfied(available);
+ boolean satisfiedRight = right.isSatisfied(available);
+ if (operator == BinaryOperator.LOGICAL_AND || operator == BinaryOperator.LOGICAL_OR) {
+ if (satisfiedLeft) {
+ if (satisfiedRight) {
+ return;
+ }
+ right.addMissingPermissions(available, missing);
+ } else if (satisfiedRight) {
+ left.addMissingPermissions(available, missing);
+ } else {
+ left.addMissingPermissions(available, missing);
+ right.addMissingPermissions(available, missing);
+ }
+ } else {
+ assert operator == BinaryOperator.BITWISE_XOR : operator;
+ left.addMissingPermissions(available, missing);
+ right.addMissingPermissions(available, missing);
+ }
+ }
+
+ @Override
+ protected void addRevocablePermissions(@NonNull Set<String> result,
+ @NonNull PermissionHolder revocable) {
+ left.addRevocablePermissions(result, revocable);
+ right.addRevocablePermissions(result, revocable);
+ }
+
+ @Override
+ public boolean isRevocable(@NonNull PermissionHolder revocable) {
+ // TODO: If operator == BinaryOperator.LOGICAL_OR only return
+ // revocable the there isn't a non-revocable term which is also satisfied.
+ return left.isRevocable(revocable) || right.isRevocable(revocable);
+ }
+
+ @NonNull
+ public static PermissionRequirement parse(@NonNull ResolvedAnnotation annotation,
+ @Nullable Context context, @NonNull final String value) {
+ // Parse an expression of the form (A op1 B op2 C) op3 (D op4 E) etc.
+ // We'll just use the Java parser to handle this to ensure that operator
+ // precedence etc is correct.
+ if (context == null) {
+ return NONE;
+ }
+ JavaParser javaParser = context.getClient().getJavaParser(null);
+ if (javaParser == null) {
+ return NONE;
+ }
+ try {
+ JavaContext javaContext = new JavaContext(context.getDriver(),
+ context.getProject(), context.getMainProject(), context.file,
+ javaParser) {
+ @Nullable
+ @Override
+ public String getContents() {
+ return ""
+ + "class Test { void test() {\n"
+ + "boolean result=" + value
+ + ";\n}\n}";
+ }
+ };
+ Node node = javaParser.parseJava(javaContext);
+ if (node != null) {
+ final AtomicReference<Expression> reference = new AtomicReference<Expression>();
+ node.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ reference.set(node.astInitializer());
+ return true;
+ }
+ });
+ Expression expression = reference.get();
+ if (expression != null) {
+ return parse(annotation, expression);
+ }
+ }
+
+ return NONE;
+ } finally {
+ javaParser.dispose();
+ }
+ }
+
+ private static PermissionRequirement parse(
+ @NonNull ResolvedAnnotation annotation,
+ @NonNull Expression expression) {
+ if (expression instanceof Select) {
+ return new Single(annotation, expression.toString());
+ } else if (expression instanceof BinaryExpression) {
+ BinaryExpression binaryExpression = (BinaryExpression) expression;
+ BinaryOperator operator = binaryExpression.astOperator();
+ if (operator == BinaryOperator.LOGICAL_AND
+ || operator == BinaryOperator.LOGICAL_OR
+ || operator == BinaryOperator.BITWISE_XOR) {
+ PermissionRequirement left = parse(annotation, binaryExpression.astLeft());
+ PermissionRequirement right = parse(annotation, binaryExpression.astRight());
+ return new Complex(annotation, operator, left, right);
+ }
+ }
+ return NONE;
+ }
+
+ @Nullable
+ @Override
+ public BinaryOperator getOperator() {
+ return operator;
+ }
+
+ @NonNull
+ @Override
+ public Iterable<PermissionRequirement> getChildren() {
+ return Arrays.asList(left, right);
+ }
+ }
+
+ /**
+ * Returns true if the given permission name is a revocable permission for
+ * targetSdkVersion >= 23
+ *
+ * @param name permission name
+ * @return true if this is a revocable permission
+ */
+ public static boolean isRevocableSystemPermission(@NonNull String name) {
+ return Arrays.binarySearch(REVOCABLE_PERMISSION_NAMES, name) >= 0;
+ }
+
+ @VisibleForTesting
+ static final String[] REVOCABLE_PERMISSION_NAMES = new String[] {
+ "android.permission.ACCESS_COARSE_LOCATION",
+ "android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.BODY_SENSORS",
+ "android.permission.CALL_PHONE",
+ "android.permission.CAMERA",
+ "android.permission.PROCESS_OUTGOING_CALLS",
+ "android.permission.READ_CALENDAR",
+ "android.permission.READ_CALL_LOG",
+ "android.permission.READ_CELL_BROADCASTS",
+ "android.permission.READ_CONTACTS",
+ "android.permission.READ_EXTERNAL_STORAGE",
+ "android.permission.READ_PHONE_STATE",
+ "android.permission.READ_PROFILE",
+ "android.permission.READ_SMS",
+ "android.permission.READ_SOCIAL_STREAM",
+ "android.permission.RECEIVE_MMS",
+ "android.permission.RECEIVE_SMS",
+ "android.permission.RECEIVE_WAP_PUSH",
+ "android.permission.RECORD_AUDIO",
+ "android.permission.SEND_SMS",
+ "android.permission.USE_FINGERPRINT",
+ "android.permission.USE_SIP",
+ "android.permission.WRITE_CALENDAR",
+ "android.permission.WRITE_CALL_LOG",
+ "android.permission.WRITE_CONTACTS",
+ "android.permission.WRITE_EXTERNAL_STORAGE",
+ "android.permission.WRITE_SETTINGS",
+ "android.permission.WRITE_PROFILE",
+ "android.permission.WRITE_SOCIAL_STREAM",
+ "com.android.voicemail.permission.ADD_VOICEMAIL",
+ };
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
new file mode 100644
index 0000000..cb40ad7
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDatabase.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.few;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.many;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.one;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.two;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.zero;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.google.common.collect.Maps;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Database used by the {@link com.android.tools.lint.checks.PluralsDetector} to get information
+ * about plural forms for a given language
+ */
+public class PluralsDatabase {
+ private static final EnumSet<Quantity> NONE = EnumSet.noneOf(Quantity.class);
+
+ private static final PluralsDatabase sInstance = new PluralsDatabase();
+ private Map<String, EnumSet<Quantity>> mPlurals = Maps.newHashMap();
+
+ /** Bit set if this language uses quantity zero */
+ @SuppressWarnings("PointlessBitwiseExpression")
+ static final int FLAG_ZERO = 1 << 0;
+ /** Bit set if this language uses quantity one */
+ static final int FLAG_ONE = 1 << 1;
+ /** Bit set if this language uses quantity two */
+ static final int FLAG_TWO = 1 << 2;
+ /** Bit set if this language uses quantity few */
+ static final int FLAG_FEW = 1 << 3;
+ /** Bit set if this language uses quantity many */
+ static final int FLAG_MANY = 1 << 4;
+ /** Bit set if this language has multiple values that match quantity zero */
+ static final int FLAG_MULTIPLE_ZERO = 1 << 5;
+ /** Bit set if this language has multiple values that match quantity one */
+ static final int FLAG_MULTIPLE_ONE = 1 << 6;
+ /** Bit set if this language has multiple values that match quantity two */
+ static final int FLAG_MULTIPLE_TWO = 1 << 7;
+
+ @NonNull
+ public static PluralsDatabase get() {
+ return sInstance;
+ }
+
+ private static int getFlags(@NonNull String language) {
+ int index = getLanguageIndex(language);
+ if (index != -1) {
+ return FLAGS[index];
+ }
+ return 0;
+ }
+
+ private static int getLanguageIndex(@NonNull String language) {
+ int index = Arrays.binarySearch(LANGUAGE_CODES, language);
+ if (index >= 0) {
+ assert LANGUAGE_CODES[index].equals(language);
+ return index;
+ } else {
+ return -1;
+ }
+ }
+
+ @Nullable
+ public EnumSet<Quantity> getRelevant(@NonNull String language) {
+ EnumSet<Quantity> set = mPlurals.get(language);
+ if (set == null) {
+ int index = getLanguageIndex(language);
+ if (index == -1) {
+ mPlurals.put(language, NONE);
+ return null;
+ }
+
+ // Process each item and look for relevance
+ int flag = FLAGS[index];
+
+ set = EnumSet.noneOf(Quantity.class);
+ if ((flag & FLAG_ZERO) != 0) {
+ set.add(zero);
+ }
+ if ((flag & FLAG_ONE) != 0) {
+ set.add(one);
+ }
+ if ((flag & FLAG_TWO) != 0) {
+ set.add(two);
+ }
+ if ((flag & FLAG_FEW) != 0) {
+ set.add(few);
+ }
+ if ((flag & FLAG_MANY) != 0) {
+ set.add(many);
+ }
+
+ mPlurals.put(language, set);
+ }
+ return set == NONE ? null : set;
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ public boolean hasMultipleValuesForQuantity(
+ @NonNull String language,
+ @NonNull Quantity quantity) {
+ if (quantity == one) {
+ return (getFlags(language) & FLAG_MULTIPLE_ONE) != 0;
+ } else if (quantity == two) {
+ return (getFlags(language) & FLAG_MULTIPLE_TWO) != 0;
+ } else {
+ return quantity == zero && (getFlags(language) & FLAG_MULTIPLE_ZERO) != 0;
+ }
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ @Nullable
+ public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) {
+ if (quantity == one) {
+ return getExampleForQuantityOne(language);
+ } else if (quantity == two) {
+ return getExampleForQuantityTwo(language);
+ } else if (quantity == zero) {
+ return getExampleForQuantityZero(language);
+ } else {
+ return null;
+ }
+ }
+
+ public enum Quantity {
+ // deliberately lower case to match attribute names
+ few, many, one, two, zero, other;
+
+ @Nullable
+ public static Quantity get(@NonNull String name) {
+ for (Quantity quantity : values()) {
+ if (name.equals(quantity.name())) {
+ return quantity;
+ }
+ }
+
+ return null;
+ }
+
+ public static String formatSet(@NonNull EnumSet<Quantity> set) {
+ List<String> list = new ArrayList<String>(set.size());
+ for (Quantity quantity : set) {
+ list.add('`' + quantity.name() + '`');
+ }
+ return LintUtils.formatList(list, Integer.MAX_VALUE);
+ }
+ }
+
+ // GENERATED DATA.
+ // This data is generated by the #testDatabaseAccurate method in PluralsDatabaseTest
+ // which will generate the following if it can find an ICU plurals database file
+ // in the unit test data folder.
+
+ /** Set of language codes relevant to plurals data */
+ private static final String[] LANGUAGE_CODES = new String[] {
+ "af", "ak", "am", "ar", "as", "az", "be", "bg", "bh", "bm",
+ "bn", "bo", "br", "bs", "ca", "ce", "cs", "cy", "da", "de",
+ "dv", "dz", "ee", "el", "en", "eo", "es", "et", "eu", "fa",
+ "ff", "fi", "fo", "fr", "fy", "ga", "gd", "gl", "gu", "gv",
+ "ha", "he", "hi", "hr", "hu", "hy", "id", "ig", "ii", "in",
+ "is", "it", "iu", "iw", "ja", "ji", "jv", "ka", "kk", "kl",
+ "km", "kn", "ko", "ks", "ku", "kw", "ky", "lb", "lg", "ln",
+ "lo", "lt", "lv", "mg", "mk", "ml", "mn", "mr", "ms", "mt",
+ "my", "nb", "nd", "ne", "nl", "nn", "no", "nr", "ny", "om",
+ "or", "os", "pa", "pl", "ps", "pt", "rm", "ro", "ru", "se",
+ "sg", "si", "sk", "sl", "sn", "so", "sq", "sr", "ss", "st",
+ "sv", "sw", "ta", "te", "th", "ti", "tk", "tl", "tn", "to",
+ "tr", "ts", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa",
+ "wo", "xh", "yi", "yo", "zh", "zu"
+ };
+
+ /**
+ * Relevant flags for each language (corresponding to each language listed
+ * in the same position in {@link #LANGUAGE_CODES})
+ */
+ private static final int[] FLAGS = new int[] {
+ 0x0002, 0x0042, 0x0042, 0x001f, 0x0042, 0x0002, 0x005a, 0x0002,
+ 0x0042, 0x0000, 0x0042, 0x0000, 0x00de, 0x004a, 0x0002, 0x0002,
+ 0x000a, 0x001f, 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0002,
+ 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0042, 0x0042, 0x0002,
+ 0x0002, 0x0042, 0x0002, 0x001e, 0x00ce, 0x0002, 0x0042, 0x00ce,
+ 0x0002, 0x0016, 0x0042, 0x004a, 0x0002, 0x0042, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0042, 0x0002, 0x0006, 0x0016, 0x0000, 0x0002,
+ 0x0000, 0x0002, 0x0002, 0x0002, 0x0000, 0x0042, 0x0000, 0x0002,
+ 0x0002, 0x0006, 0x0002, 0x0002, 0x0002, 0x0042, 0x0000, 0x004a,
+ 0x0063, 0x0042, 0x0042, 0x0002, 0x0002, 0x0042, 0x0000, 0x001a,
+ 0x0000, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
+ 0x0002, 0x0002, 0x0002, 0x0002, 0x0042, 0x001a, 0x0002, 0x0042,
+ 0x0002, 0x000a, 0x005a, 0x0006, 0x0000, 0x0042, 0x000a, 0x00ce,
+ 0x0002, 0x0002, 0x0002, 0x004a, 0x0002, 0x0002, 0x0002, 0x0002,
+ 0x0002, 0x0002, 0x0000, 0x0042, 0x0002, 0x0042, 0x0002, 0x0000,
+ 0x0002, 0x0002, 0x0002, 0x005a, 0x0002, 0x0002, 0x0002, 0x0000,
+ 0x0002, 0x0042, 0x0000, 0x0002, 0x0002, 0x0000, 0x0000, 0x0042
+ };
+
+ @Nullable
+ private static String getExampleForQuantityZero(@NonNull String language) {
+ int index = getLanguageIndex(language);
+ switch (index) {
+ // set14
+ case 72: // lv
+ return "0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, \u2026";
+ case -1:
+ default:
+ return null;
+ }
+ }
+
+ @Nullable
+ private static String getExampleForQuantityOne(@NonNull String language) {
+ int index = getLanguageIndex(language);
+ switch (index) {
+ // set1
+ case 2: // am
+ case 4: // as
+ case 10: // bn
+ case 29: // fa
+ case 38: // gu
+ case 42: // hi
+ case 61: // kn
+ case 77: // mr
+ case 135: // zu
+ return "0, 1";
+ // set11
+ case 50: // is
+ return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
+ // set12
+ case 74: // mk
+ return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
+ // set13
+ case 117: // tl
+ return "0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, \u2026";
+ // set14
+ case 72: // lv
+ return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
+ // set2
+ case 30: // ff
+ case 33: // fr
+ case 45: // hy
+ return "0, 1";
+ // set20
+ case 13: // bs
+ case 43: // hr
+ case 107: // sr
+ return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
+ // set21
+ case 36: // gd
+ return "1, 11";
+ // set22
+ case 103: // sl
+ return "1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026";
+ // set27
+ case 6: // be
+ return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
+ // set28
+ case 71: // lt
+ return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
+ // set30
+ case 98: // ru
+ case 123: // uk
+ return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
+ // set31
+ case 12: // br
+ return "1, 21, 31, 41, 51, 61, 81, 101, 1001, \u2026";
+ // set33
+ case 39: // gv
+ return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
+ // set4
+ case 101: // si
+ return "0, 1";
+ // set5
+ case 1: // ak
+ case 8: // bh
+ case 69: // ln
+ case 73: // mg
+ case 92: // pa
+ case 115: // ti
+ case 129: // wa
+ return "0, 1";
+ // set7
+ case 95: // pt
+ return "0, 1";
+ case -1:
+ default:
+ return null;
+ }
+ }
+
+ @Nullable
+ private static String getExampleForQuantityTwo(@NonNull String language) {
+ int index = getLanguageIndex(language);
+ switch (index) {
+ // set21
+ case 36: // gd
+ return "2, 12";
+ // set22
+ case 103: // sl
+ return "2, 102, 202, 302, 402, 502, 602, 702, 1002, \u2026";
+ // set31
+ case 12: // br
+ return "2, 22, 32, 42, 52, 62, 82, 102, 1002, \u2026";
+ // set33
+ case 39: // gv
+ return "2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, \u2026";
+ case -1:
+ default:
+ return null;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PreferenceActivityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PreferenceActivityDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PreferenceActivityDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PreferenceActivityDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
new file mode 100644
index 0000000..a4a8bab
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_REF_PREFIX;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.FD_RES_VALUES;
+import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE;
+import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY;
+import static com.android.SdkConstants.RESOURCE_CLZ_ID;
+import static com.android.SdkConstants.TAG_ARRAY;
+import static com.android.SdkConstants.TAG_INTEGER_ARRAY;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_PLURALS;
+import static com.android.SdkConstants.TAG_RESOURCES;
+import static com.android.SdkConstants.TAG_STRING_ARRAY;
+import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.tools.lint.detector.api.LintUtils.getBaseName;
+import static com.android.utils.SdkUtils.getResourceFieldName;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.MavenCoordinates;
+import com.android.ide.common.repository.ResourceVisibilityLookup;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Node;
+
+/**
+ * Check which looks for access of private resources.
+ */
+public class PrivateResourceDetector extends ResourceXmlDetector implements JavaScanner {
+ /** Attribute for overriding a resource */
+ private static final String ATTR_OVERRIDE = "override";
+
+ @SuppressWarnings("unchecked")
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ PrivateResourceDetector.class,
+ Scope.JAVA_AND_RESOURCE_FILES,
+ Scope.JAVA_FILE_SCOPE,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "PrivateResource", //$NON-NLS-1$
+ "Using private resources",
+
+ "Private resources should not be referenced; the may not be present everywhere, and " +
+ "even where they are they may disappear without notice.\n" +
+ "\n" +
+ "To fix this, copy the resource into your own project instead.",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Constructs a new detector */
+ public PrivateResourceDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public boolean appliesToResourceRefs() {
+ return true;
+ }
+
+ @Override
+ public void visitResourceReference(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull Node node,
+ @NonNull String type,
+ @NonNull String name,
+ boolean isFramework) {
+ if (context.getProject().isGradleProject() && !isFramework) {
+ Project project = context.getProject();
+ if (project.getGradleProjectModel() != null && project.getCurrentVariant() != null) {
+ ResourceType resourceType = ResourceType.getEnum(type);
+ if (resourceType != null && isPrivate(context, resourceType, name)) {
+ String message = createUsageErrorMessage(context, resourceType, name);
+ context.report(ISSUE, node, context.getLocation(node), message);
+ }
+ }
+ }
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return ALL;
+ }
+
+ /** Check resource references: accessing a private resource from an upstream library? */
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ String value = attribute.getNodeValue();
+ if (context.getProject().isGradleProject()) {
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (isPrivate(context, url)) {
+ String message = createUsageErrorMessage(context, url.type, url.name);
+ context.report(ISSUE, attribute, context.getValueLocation(attribute), message);
+ }
+ }
+ }
+
+ /** Check resource definitions: overriding a private resource from an upstream library? */
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ TAG_STYLE,
+ TAG_RESOURCES,
+ TAG_ARRAY,
+ TAG_STRING_ARRAY,
+ TAG_INTEGER_ARRAY,
+ TAG_PLURALS
+ );
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (TAG_RESOURCES.equals(element.getTagName())) {
+ for (Element item : LintUtils.getChildren(element)) {
+ Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
+ if (nameAttribute != null) {
+ String name = getResourceFieldName(nameAttribute.getValue());
+ String type = item.getTagName();
+ if (type.equals(TAG_ITEM)) {
+ type = item.getAttribute(ATTR_TYPE);
+ if (type == null || type.isEmpty()) {
+ type = RESOURCE_CLZ_ID;
+ }
+ } else if (type.equals("declare-styleable")) { //$NON-NLS-1$
+ type = RESOURCE_CLR_STYLEABLE;
+ } else if (type.contains("array")) { //$NON-NLS-1$
+ // <string-array> etc
+ type = RESOURCE_CLZ_ARRAY;
+ }
+ ResourceType t = ResourceType.getEnum(type);
+ if (t != null && isPrivate(context, t, name) &&
+ !VALUE_TRUE.equals(item.getAttributeNS(TOOLS_URI, ATTR_OVERRIDE))) {
+ String message = createOverrideErrorMessage(context, t, name);
+ Location location = context.getValueLocation(nameAttribute);
+ context.report(ISSUE, nameAttribute, location, message);
+ }
+ }
+ }
+ } else {
+ assert TAG_STYLE.equals(element.getTagName())
+ || TAG_ARRAY.equals(element.getTagName())
+ || TAG_PLURALS.equals(element.getTagName())
+ || TAG_INTEGER_ARRAY.equals(element.getTagName())
+ || TAG_STRING_ARRAY.equals(element.getTagName());
+ for (Element item : LintUtils.getChildren(element)) {
+ checkChildRefs(context, item);
+ }
+ }
+ }
+
+ private static boolean isPrivate(Context context, ResourceType type, String name) {
+ if (type == ResourceType.ID) {
+ // No need to complain about "overriding" id's. There's no harm
+ // in doing so. (This avoids warning about cases like for example
+ // appcompat's (private) @id/title resource, which would otherwise
+ // flag any attempt to create a resource named title in the user's
+ // project.
+ return false;
+ }
+
+ if (context.getProject().isGradleProject()) {
+ ResourceVisibilityLookup lookup = context.getProject().getResourceVisibility();
+ return lookup.isPrivate(type, name);
+ }
+
+ return false;
+ }
+
+ private static boolean isPrivate(@NonNull Context context, @Nullable ResourceUrl url) {
+ return url != null && !url.framework && isPrivate(context, url.type, url.name);
+ }
+
+ private static void checkChildRefs(@NonNull XmlContext context, Element item) {
+ // Look for ?attr/ and @dimen/foo etc references in the item children
+ NodeList childNodes = item.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ org.w3c.dom.Node child = childNodes.item(i);
+ if (child.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
+ String text = child.getNodeValue();
+
+ int index = text.indexOf(ATTR_REF_PREFIX);
+ if (index != -1) {
+ String name = text.substring(index + ATTR_REF_PREFIX.length()).trim();
+ if (isPrivate(context, ResourceType.ATTR, name)) {
+ String message = createUsageErrorMessage(context, ResourceType.ATTR, name);
+ context.report(ISSUE, item, context.getLocation(child), message);
+ }
+ } else {
+ for (int j = 0, m = text.length(); j < m; j++) {
+ char c = text.charAt(j);
+ if (c == '@') {
+ ResourceUrl url = ResourceUrl.parse(text.trim());
+ if (isPrivate(context, url)) {
+ String message = createUsageErrorMessage(context, url.type,
+ url.name);
+ context.report(ISSUE, item, context.getLocation(child), message);
+ }
+ break;
+ } else if (!Character.isWhitespace(c)) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ File file = context.file;
+ boolean isXmlFile = LintUtils.isXmlFile(file);
+ if (!isXmlFile && !LintUtils.isBitmapFile(file)) {
+ return;
+ }
+ String parentName = file.getParentFile().getName();
+ int dash = parentName.indexOf('-');
+ if (dash != -1 || FD_RES_VALUES.equals(parentName)) {
+ return;
+ }
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName);
+ if (folderType == null) {
+ return;
+ }
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType);
+ if (types.isEmpty()) {
+ return;
+ }
+ ResourceType type = types.get(0);
+ String resourceName = getResourceFieldName(getBaseName(file.getName()));
+ if (isPrivate(context, type, resourceName)) {
+ String message = createOverrideErrorMessage(context, type, resourceName);
+ Location location = Location.create(file);
+ context.report(ISSUE, location, message);
+ }
+ }
+
+ private static String createOverrideErrorMessage(@NonNull Context context,
+ @NonNull ResourceType type, @NonNull String name) {
+ String libraryName = getLibraryName(context, type, name);
+ return String.format("Overriding `@%1$s/%2$s` which is marked as private in %3$s. If "
+ + "deliberate, use tools:override=\"true\", otherwise pick a "
+ + "different name.", type, name, libraryName);
+ }
+
+ private static String createUsageErrorMessage(@NonNull Context context,
+ @NonNull ResourceType type, @NonNull String name) {
+ String libraryName = getLibraryName(context, type, name);
+ return String.format("The resource `@%1$s/%2$s` is marked as private in %3$s", type,
+ name, libraryName);
+ }
+
+ /** Pick a suitable name to describe the library defining the private resource */
+ @Nullable
+ private static String getLibraryName(@NonNull Context context, @NonNull ResourceType type,
+ @NonNull String name) {
+ ResourceVisibilityLookup lookup = context.getProject().getResourceVisibility();
+ AndroidLibrary library = lookup.getPrivateIn(type, name);
+ if (library != null) {
+ String libraryName = library.getProject();
+ if (libraryName != null) {
+ return libraryName;
+ }
+ MavenCoordinates coordinates = library.getResolvedCoordinates();
+ if (coordinates != null) {
+ return coordinates.getGroupId() + ':' + coordinates.getArtifactId();
+ }
+ }
+ return "the library";
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
new file mode 100644
index 0000000..9c8408d
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PropertyFileDetector.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.DOT_PROPERTIES;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.SdkUtils;
+import com.google.common.base.Splitter;
+
+import java.io.File;
+import java.util.Iterator;
+
+/**
+ * Check for errors in .property files
+ * <p>
+ * TODO: Warn about bad paths like sdk properties with ' in the path, or suffix of " " etc
+ */
+public class PropertyFileDetector extends Detector {
+ /** Property file not escaped */
+ public static final Issue ESCAPE = Issue.create(
+ "PropertyEscape", //$NON-NLS-1$
+ "Incorrect property escapes",
+ "All backslashes and colons in .property files must be escaped with " +
+ "a backslash (\\). This means that when writing a Windows path, you " +
+ "must escape the file separators, so the path \\My\\Files should be " +
+ "written as `key=\\\\My\\\\Files.`",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ new Implementation(
+ PropertyFileDetector.class,
+ Scope.PROPERTY_SCOPE));
+
+ /** Using HTTP instead of HTTPS for the wrapper */
+ public static final Issue HTTP = Issue.create(
+ "UsingHttp", //$NON-NLS-1$
+ "Using HTTP instead of HTTPS",
+ "The Gradle Wrapper is available both via HTTP and HTTPS. HTTPS is more " +
+ "secure since it protects against man-in-the-middle attacks etc. Older " +
+ "projects created in Android Studio used HTTP but we now default to HTTPS " +
+ "and recommend upgrading existing projects.",
+
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ PropertyFileDetector.class,
+ Scope.PROPERTY_SCOPE));
+
+ /** Constructs a new {@link PropertyFileDetector} */
+ public PropertyFileDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return file.getPath().endsWith(DOT_PROPERTIES);
+ }
+
+ @Override
+ public void run(@NonNull Context context) {
+ String contents = context.getContents();
+ if (contents == null) {
+ return;
+ }
+ int offset = 0;
+ Iterator<String> iterator = Splitter.on('\n').split(contents).iterator();
+ String line;
+ for (; iterator.hasNext(); offset += line.length() + 1) {
+ line = iterator.next();
+ if (line.startsWith("#") || line.startsWith(" ")) {
+ continue;
+ }
+ if (line.indexOf('\\') == -1 && line.indexOf(':') == -1) {
+ continue;
+ }
+ int valueStart = line.indexOf('=') + 1;
+ if (valueStart == 0) {
+ continue;
+ }
+ checkLine(context, contents, line, offset, valueStart);
+ }
+ }
+
+ private static void checkLine(@NonNull Context context, @NonNull String contents,
+ @NonNull String line, int offset, int valueStart) {
+ String prefix = "distributionUrl=http\\";
+ if (line.startsWith(prefix)) {
+ String https = "https" + line.substring(prefix.length() - 1);
+ String message = String.format("Replace HTTP with HTTPS for better security; use %1$s",
+ https);
+ int startOffset = offset + valueStart;
+ int endOffset = startOffset + 4; // 4: "http".length()
+ Location location = Location.create(context.file, contents, startOffset, endOffset);
+ context.report(HTTP, location, message);
+ }
+
+ boolean escaped = false;
+ boolean hadNonPathEscape = false;
+ int errorStart = -1;
+ int errorEnd = -1;
+ StringBuilder path = new StringBuilder();
+ for (int i = valueStart; i < line.length(); i++) {
+ char c = line.charAt(i);
+ if (c == '\\') {
+ escaped = !escaped;
+ if (escaped) {
+ path.append(c);
+ }
+ } else if (c == ':') {
+ if (!escaped) {
+ hadNonPathEscape = true;
+ if (errorStart < 0) {
+ errorStart = i;
+ }
+ errorEnd = i;
+ } else {
+ escaped = false;
+ }
+ path.append(c);
+ } else {
+ if (escaped) {
+ hadNonPathEscape = true;
+ if (errorStart < 0) {
+ errorStart = i;
+ }
+ errorEnd = i;
+ }
+ escaped = false;
+ path.append(c);
+ }
+ }
+ String pathString = path.toString();
+ String key = line.substring(0, valueStart);
+ if (hadNonPathEscape && key.endsWith(".dir=") || new File(pathString).exists()) {
+ String escapedPath = suggestEscapes(line.substring(valueStart, line.length()));
+
+ // NOTE: Keep in sync with {@link #getSuggestedEscape} below
+ String message = "Windows file separators (`\\`) and drive letter "
+ + "separators (':') must be escaped (`\\\\`) in property files; use "
+ + escapedPath;
+ int startOffset = offset + errorStart;
+ int endOffset = offset + errorEnd + 1;
+ Location location = Location.create(context.file, contents, startOffset,
+ endOffset);
+ context.report(ESCAPE, location, message);
+ }
+ }
+
+ @NonNull
+ static String suggestEscapes(@NonNull String value) {
+ value = value.replace("\\:", ":").replace("\\\\", "\\");
+ return SdkUtils.escapePropertyValue(value);
+ }
+
+ /**
+ * Returns the escaped string value suggested by the error message which should have
+ * been computed by this lint detector.
+ *
+ * @param message the error message created by this lint detector
+ * @param format the format of the error message
+ * @return the suggested escaped value
+ */
+ @Nullable
+ public static String getSuggestedEscape(@NonNull String message, @NonNull TextFormat format) {
+ return LintUtils.findSubstring(format.toText(message), "; use ", null);
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ReadParcelableDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ReadParcelableDetector.java
new file mode 100644
index 0000000..c6884af
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ReadParcelableDetector.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.Identifier;
+import lombok.ast.MethodInvocation;
+import lombok.ast.NullLiteral;
+
+/**
+ * Looks for Parcelable classes that are missing a CREATOR field
+ */
+public class ReadParcelableDetector extends Detector implements Detector.JavaScanner {
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "ParcelClassLoader", //$NON-NLS-1$
+ "Default Parcel Class Loader",
+
+ "The documentation for `Parcel#readParcelable(ClassLoader)` (and its variations) " +
+ "says that you can pass in `null` to pick up the default class loader. However, " +
+ "that ClassLoader is a system class loader and is not able to find classes in " +
+ "your own application.\n" +
+ "\n" +
+ "If you are writing your own classes into the `Parcel` (not just SDK classes like " +
+ "`String` and so on), then you should supply a `ClassLoader` for your application " +
+ "instead; a simple way to obtain one is to just call `getClass().getClassLoader()` " +
+ "from your own class.",
+
+ Category.CORRECTNESS,
+ 3,
+ Severity.WARNING,
+ new Implementation(
+ ReadParcelableDetector.class,
+ Scope.JAVA_FILE_SCOPE))
+ .addMoreInfo("http://developer.android.com/reference/android/os/Parcel.html");
+
+ /** Constructs a new {@link ReadParcelableDetector} check */
+ public ReadParcelableDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(
+ "readParcelable",
+ "readParcelableArray",
+ "readBundle",
+ "readArray",
+ "readSparseArray",
+ "readValue",
+ "readPersistableBundle"
+ );
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getContainingClass().matches("android.os.Parcel")) {
+ int argumentCount = method.getArgumentCount();
+ if (argumentCount == 0) {
+ Identifier name = node.astName();
+ String message = String.format("Using the default class loader "
+ + "will not work if you are restoring your own classes. Consider "
+ + "using for example `%1$s(getClass().getClassLoader())` instead.",
+ name.astValue());
+ Location location = context.getRangeLocation(name, 0, name, 2);
+ context.report(ISSUE, node, location, message);
+ } else if (argumentCount == 1) {
+ Expression parameter = node.astArguments().first();
+ if (parameter instanceof NullLiteral) {
+ String message = "Passing null here (to use the default class loader) "
+ + "will not work if you are restoring your own classes. Consider "
+ + "using for example `getClass().getClassLoader()` instead.";
+ Location location = context.getRangeLocation(node.astName(), 0, parameter, 1);
+ context.report(ISSUE, node, location, message);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RecyclerViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RecyclerViewDetector.java
new file mode 100644
index 0000000..a4c5461
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RecyclerViewDetector.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+
+import static com.android.tools.lint.detector.api.JavaContext.findSurroundingClass;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.Lists;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ClassLiteral;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Checks related to RecyclerView usage.
+ * // https://code.google.com/p/android/issues/detail?id=172335
+ */
+public class RecyclerViewDetector extends Detector implements Detector.JavaScanner {
+ public static final Issue ISSUE = Issue.create(
+ "RecyclerView", //$NON-NLS-1$
+ "RecyclerView Problems",
+ "`RecyclerView` will *not* call `onBindViewHolder` again when the position of " +
+ "the item changes in the data set unless the item itself is " +
+ "invalidated or the new position cannot be determined.\n" +
+ "\n" +
+ "For this reason, you should *only* use the position parameter " +
+ "while acquiring the related data item inside this method, and " +
+ "should *not* keep a copy of it.\n" +
+ "\n" +
+ "If you need the position of an item later on (e.g. in a click " +
+ "listener), use `getAdapterPosition()` which will have the updated " +
+ "adapter position.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.WARNING,
+ new Implementation(
+ RecyclerViewDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ private static final String VIEW_ADAPTER = "android.support.v7.widget.RecyclerView.Adapter"; //$NON-NLS-1$
+ private static final String ON_BIND_VIEW_HOLDER = "onBindViewHolder"; //$NON-NLS-1$
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Collections.singletonList(VIEW_ADAPTER);
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration,
+ @NonNull Node node, @NonNull ResolvedClass resolvedClass) {
+ NormalTypeBody body;
+ if (declaration != null) {
+ body = declaration.astBody();
+ } else if (node instanceof NormalTypeBody) {
+ // anonymous inner class
+ body = (NormalTypeBody) node;
+ } else {
+ return;
+ }
+
+ for (Node child : body.astMembers()) {
+ if (child instanceof MethodDeclaration) {
+ MethodDeclaration method = (MethodDeclaration) child;
+ if (method.astMethodName().astValue().equals(ON_BIND_VIEW_HOLDER)) {
+ int size = method.astParameters().size();
+ if (size == 2 || size == 3) {
+ checkMethod(context, method);
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkMethod(@NonNull JavaContext context,
+ @NonNull MethodDeclaration declaration) {
+ Iterator<VariableDefinition> iterator = declaration.astParameters().iterator();
+ if (!iterator.hasNext()) {
+ return;
+ }
+ VariableDefinition viewHolder = iterator.next();
+ if (!iterator.hasNext()) {
+ return;
+ }
+ VariableDefinition parameter = iterator.next();
+ ResolvedNode reference = context.resolve(parameter);
+
+ if (reference instanceof ResolvedVariable) {
+ ParameterEscapesVisitor visitor = new ParameterEscapesVisitor(context, declaration,
+ (ResolvedVariable) reference);
+ declaration.accept(visitor);
+ if (visitor.variableEscapes()) {
+ reportError(context, viewHolder, parameter);
+ }
+ } else if (parameter.astModifiers().isFinal()) {
+ reportError(context, viewHolder, parameter);
+ }
+ }
+
+ private static void reportError(@NonNull JavaContext context, VariableDefinition viewHolder,
+ VariableDefinition parameter) {
+ String variablePrefix;
+ VariableDefinitionEntry first = viewHolder.astVariables().first();
+ if (first != null) {
+ variablePrefix = first.astName().astValue();
+ } else {
+ variablePrefix = "ViewHolder";
+ }
+ String message = String.format("Do not treat position as fixed; only use immediately "
+ + "and call `%1$s.getAdapterPosition()` to look it up later",
+ variablePrefix);
+ context.report(ISSUE, parameter, context.getLocation(parameter),
+ message);
+ }
+
+ /**
+ * Determines whether a given variable "escapes" either to a field or to a nested
+ * runnable. (We deliberately ignore variables that escape via method calls.)
+ */
+ private static class ParameterEscapesVisitor extends ForwardingAstVisitor {
+ protected final JavaContext mContext;
+ protected final List<ResolvedVariable> mVariables;
+ private final ClassDeclaration mBindClass;
+ private boolean mEscapes;
+ private boolean mFoundInnerClass;
+
+ public ParameterEscapesVisitor(JavaContext context,
+ @NonNull MethodDeclaration onBindMethod,
+ @NonNull ResolvedVariable variable) {
+ mContext = context;
+ mVariables = Lists.newArrayList(variable);
+ mBindClass = findSurroundingClass(onBindMethod);
+ }
+
+ public boolean variableEscapes() {
+ return mEscapes;
+ }
+
+ @Override
+ public boolean visitNode(Node node) {
+ return mEscapes || super.visitNode(node);
+ }
+
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ Expression initializer = node.astInitializer();
+ if (initializer instanceof VariableReference) {
+ ResolvedNode resolved = mContext.resolve(initializer);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ ResolvedNode resolvedVariable = mContext.resolve(node);
+ if (resolvedVariable instanceof ResolvedVariable) {
+ ResolvedVariable variable = (ResolvedVariable) resolvedVariable;
+ mVariables.add(variable);
+ } else if (resolvedVariable instanceof ResolvedField) {
+ mEscapes = true;
+ }
+ }
+ }
+ return super.visitVariableDefinitionEntry(node);
+ }
+
+ @Override
+ public boolean visitBinaryExpression(BinaryExpression node) {
+ if (node.astOperator() == BinaryOperator.ASSIGN) {
+ Expression rhs = node.astRight();
+ boolean clearLhs = true;
+ if (rhs instanceof VariableReference) {
+ ResolvedNode resolved = mContext.resolve(rhs);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ clearLhs = false;
+ ResolvedNode resolvedLhs = mContext.resolve(node.astLeft());
+ if (resolvedLhs instanceof ResolvedVariable) {
+ ResolvedVariable variable = (ResolvedVariable) resolvedLhs;
+ mVariables.add(variable);
+ } else if (resolvedLhs instanceof ResolvedField) {
+ mEscapes = true;
+ }
+ }
+ }
+ if (clearLhs) {
+ // If we reassign one of the variables, clear it out
+ ResolvedNode resolved = mContext.resolve(node.astLeft());
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ //noinspection SuspiciousMethodCalls
+ mVariables.remove(resolved);
+ }
+ }
+ }
+ return super.visitBinaryExpression(node);
+ }
+
+ @Override
+ public boolean visitVariableReference(VariableReference node) {
+ if (mFoundInnerClass) {
+ // Check to see if this reference is inside the same class as the original
+ // onBind (e.g. is this a reference from an inner class, or a reference
+ // to a variable assigned from there)
+ ResolvedNode resolved = mContext.resolve(node);
+ //noinspection SuspiciousMethodCalls
+ if (resolved != null && mVariables.contains(resolved)) {
+ Node scope = node.getParent();
+ while (scope != null) {
+ if (scope instanceof NormalTypeBody) {
+ if (scope != mBindClass.astBody()) {
+ mEscapes = true;
+ }
+ break;
+ }
+ scope = scope.getParent();
+ }
+ }
+ }
+ return super.visitVariableReference(node);
+ }
+
+ @Override
+ public boolean visitClassLiteral(ClassLiteral node) {
+ mFoundInnerClass = true;
+
+ return super.visitClassLiteral(node);
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ NormalTypeBody anonymous = node.astAnonymousClassBody();
+ if (anonymous != null) {
+ mFoundInnerClass = true;
+ }
+
+ return super.visitConstructorInvocation(node);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
new file mode 100644
index 0000000..a46ee0f
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.CLASS_ACTIVITY;
+import static com.android.SdkConstants.CLASS_APPLICATION;
+import static com.android.SdkConstants.CLASS_BROADCASTRECEIVER;
+import static com.android.SdkConstants.CLASS_CONTENTPROVIDER;
+import static com.android.SdkConstants.CLASS_SERVICE;
+import static com.android.SdkConstants.TAG_ACTIVITY;
+import static com.android.SdkConstants.TAG_APPLICATION;
+import static com.android.SdkConstants.TAG_PROVIDER;
+import static com.android.SdkConstants.TAG_RECEIVER;
+import static com.android.SdkConstants.TAG_SERVICE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.SdkUtils;
+import com.google.common.collect.Maps;
+
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.Modifiers;
+import lombok.ast.Node;
+
+/**
+ * Checks for missing manifest registrations for activities, services etc
+ * and also makes sure that they are registered with the correct tag
+ */
+public class RegistrationDetector extends LayoutDetector implements JavaScanner {
+ /** Unregistered activities and services */
+ public static final Issue ISSUE = Issue.create(
+ "Registered", //$NON-NLS-1$
+ "Class is not registered in the manifest",
+
+ "Activities, services and content providers should be registered in the " +
+ "`AndroidManifest.xml` file using `<activity>`, `<service>` and `<provider>` tags.\n" +
+ "\n" +
+ "If your activity is simply a parent class intended to be subclassed by other " +
+ "\"real\" activities, make it an abstract class.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ RegistrationDetector.class,
+ EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE)))
+ .addMoreInfo(
+ "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
+
+ protected Map<String, String> mManifestRegistrations;
+
+ /** Constructs a new {@link RegistrationDetector} */
+ public RegistrationDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(sTags);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (!element.hasAttributeNS(ANDROID_URI, ATTR_NAME)) {
+ // For example, application appears in manifest and doesn't always have a name
+ return;
+ }
+ String fqcn = getFqcn(context, element);
+ String tag = element.getTagName();
+ String frameworkClass = tagToClass(tag);
+ if (frameworkClass != null) {
+ String signature = fqcn;
+ if (mManifestRegistrations == null) {
+ mManifestRegistrations = Maps.newHashMap();
+ }
+ mManifestRegistrations.put(signature, frameworkClass);
+ if (signature.indexOf('$') != -1) {
+ signature = signature.replace('$', '.');
+ mManifestRegistrations.put(signature, frameworkClass);
+ }
+ }
+ }
+
+ /**
+ * Returns the fully qualified class name for a manifest entry element that
+ * specifies a name attribute
+ *
+ * @param context the query context providing the project
+ * @param element the element
+ * @return the fully qualified class name
+ */
+ @NonNull
+ private static String getFqcn(@NonNull XmlContext context, @NonNull Element element) {
+ String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (className.startsWith(".")) { //$NON-NLS-1$
+ return context.getProject().getPackage() + className;
+ } else if (className.indexOf('.') == -1) {
+ // According to the <activity> manifest element documentation, this is not
+ // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
+ // but it appears in manifest files and appears to be supported by the runtime
+ // so handle this in code as well:
+ return context.getProject().getPackage() + '.' + className;
+ } // else: the class name is already a fully qualified class name
+
+ return className;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Arrays.asList(
+ // Common super class for Activity, ContentProvider, Service, Application
+ // (as well as some other classes not registered in the manifest, such as
+ // Fragment and VoiceInteractionSession)
+ "android.content.ComponentCallbacks2",
+ CLASS_BROADCASTRECEIVER);
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+ @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+ if (node == null) {
+ // anonymous class; can't be registered
+ return;
+ }
+
+ Modifiers modifiers = node.astModifiers();
+ if (modifiers.isAbstract()) {
+ // Abstract classes do not need to be registered
+ return;
+ }
+
+ if (modifiers.isPrivate()) {
+ // Private classes are clearly not intended to be registered
+ return;
+ }
+
+ String rightTag = getTag(cls);
+ if (rightTag == null) {
+ // some non-registered Context, such as a BackupAgent
+ return;
+ }
+ String className = cls.getName();
+ if (mManifestRegistrations != null) {
+ String framework = mManifestRegistrations.get(className);
+ if (framework == null) {
+ reportMissing(context, node, className, rightTag);
+ } else if (!cls.isSubclassOf(framework, false)) {
+ reportWrongTag(context, node, rightTag, className, framework);
+ }
+ } else {
+ reportMissing(context, node, className, rightTag);
+ }
+ }
+
+ private static void reportWrongTag(
+ @NonNull JavaContext context,
+ @NonNull ClassDeclaration node,
+ @NonNull String rightTag,
+ @NonNull String className,
+ @NonNull String framework) {
+ String wrongTag = classToTag(framework);
+ if (wrongTag == null) {
+ return;
+ }
+ Location location = context.getNameLocation(node);
+ String message = String.format("`%1$s` is %2$s but is registered "
+ + "in the manifest as %3$s", className, describeTag(rightTag),
+ describeTag(wrongTag));
+ context.report(ISSUE, location, message);
+ }
+
+ private static String describeTag(@NonNull String tag) {
+ String article = tag.startsWith("a") ? "an" : "a"; // an for activity and application
+ return String.format("%1$s `<%2$s>`", article, tag);
+ }
+
+ private static void reportMissing(
+ @NonNull JavaContext context,
+ @NonNull ClassDeclaration node,
+ @NonNull String className,
+ @NonNull String tag) {
+ if (tag.equals(TAG_RECEIVER)) {
+ // Receivers can be registered in code; don't flag these.
+ return;
+ }
+
+ // Don't flag activities registered in test source sets
+ if (context.getProject().isGradleProject()) {
+ AndroidProject model = context.getProject().getGradleProjectModel();
+ if (model != null) {
+ String javaSource = context.file.getPath();
+ // Test source set?
+
+ for (SourceProviderContainer extra : model.getDefaultConfig().getExtraSourceProviders()) {
+ String artifactName = extra.getArtifactName();
+ if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
+ for (File file : extra.getSourceProvider().getJavaDirectories()) {
+ if (SdkUtils.startsWithIgnoreCase(javaSource, file.getPath())) {
+ return;
+ }
+ }
+ }
+ }
+
+ for (ProductFlavorContainer container : model.getProductFlavors()) {
+ for (SourceProviderContainer extra : container.getExtraSourceProviders()) {
+ String artifactName = extra.getArtifactName();
+ if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) {
+ for (File file : extra.getSourceProvider().getJavaDirectories()) {
+ if (SdkUtils.startsWithIgnoreCase(javaSource, file.getPath())) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Location location = context.getNameLocation(node);
+ String message = String.format("The `<%1$s> %2$s` is not registered in the manifest",
+ tag, className);
+ context.report(ISSUE, location, message);
+ }
+
+ private static String getTag(@NonNull ResolvedClass cls) {
+ String tag = null;
+ for (String s : sClasses) {
+ if (cls.isSubclassOf(s, false)) {
+ tag = classToTag(s);
+ break;
+ }
+ }
+ return tag;
+ }
+
+ /** The manifest tags we care about */
+ private static final String[] sTags = new String[] {
+ TAG_ACTIVITY,
+ TAG_SERVICE,
+ TAG_RECEIVER,
+ TAG_PROVIDER,
+ TAG_APPLICATION
+ // Keep synchronized with {@link #sClasses}
+ };
+
+ /** The corresponding framework classes that the tags in {@link #sTags} should extend */
+ private static final String[] sClasses = new String[] {
+ CLASS_ACTIVITY,
+ CLASS_SERVICE,
+ CLASS_BROADCASTRECEIVER,
+ CLASS_CONTENTPROVIDER,
+ CLASS_APPLICATION
+ // Keep synchronized with {@link #sTags}
+ };
+
+ /** Looks up the corresponding framework class a given manifest tag's class should extend */
+ private static String tagToClass(String tag) {
+ for (int i = 0, n = sTags.length; i < n; i++) {
+ if (sTags[i].equals(tag)) {
+ return sClasses[i];
+ }
+ }
+
+ return null;
+ }
+
+ /** Looks up the tag a given framework class should be registered with */
+ protected static String classToTag(String className) {
+ for (int i = 0, n = sClasses.length; i < n; i++) {
+ if (sClasses[i].equals(className)) {
+ return sTags[i];
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java
new file mode 100644
index 0000000..caab887
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RelativeOverlapDetector.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_END;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_START;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP;
+import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_END_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_START_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_TEXT;
+import static com.android.SdkConstants.ATTR_VISIBILITY;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.RELATIVE_LAYOUT;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
+import static com.android.SdkConstants.VIEW;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+import static com.android.tools.lint.checks.RequiredAttributeDetector.PERCENT_RELATIVE_LAYOUT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Check for potential item overlaps in a RelativeLayout when left- and
+ * right-aligned text items are used.
+ */
+public class RelativeOverlapDetector extends LayoutDetector {
+ public static final Issue ISSUE = Issue.create(
+ "RelativeOverlap",
+ "Overlapping items in RelativeLayout",
+ "If relative layout has text or button items aligned to left and right " +
+ "sides they can overlap each other due to localized text expansion " +
+ "unless they have mutual constraints like `toEndOf`/`toStartOf`.",
+ Category.I18N, 3, Severity.WARNING,
+ new Implementation(RelativeOverlapDetector.class, Scope.RESOURCE_FILE_SCOPE));
+
+ private static class LayoutNode {
+ private enum Bucket {
+ TOP, BOTTOM, SKIP
+ }
+
+ private int mIndex;
+ private boolean mProcessed;
+ private Element mNode;
+ private Bucket mBucket;
+ private LayoutNode mToLeft;
+ private LayoutNode mToRight;
+ private boolean mLastLeft;
+ private boolean mLastRight;
+
+ public LayoutNode(@NonNull Element node, int index) {
+ mNode = node;
+ mIndex = index;
+ mProcessed = false;
+ mLastLeft = true;
+ mLastRight = true;
+ }
+
+ @NonNull
+ public String getNodeId() {
+ String nodeid = mNode.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (nodeid.isEmpty()) {
+ return String.format("%1$s-%2$d", mNode.getTagName(), mIndex);
+ } else {
+ return uniformId(nodeid);
+ }
+ }
+
+ @NonNull
+ public String getNodeTextId() {
+ String text = mNode.getAttributeNS(ANDROID_URI, ATTR_TEXT);
+ if (text.isEmpty()) {
+ return getNodeId();
+ } else {
+ return uniformId(text);
+ }
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return getNodeTextId();
+ }
+
+ public boolean isInvisible() {
+ String visibility = mNode.getAttributeNS(ANDROID_URI,
+ ATTR_VISIBILITY);
+ return visibility.equals("gone") || visibility.equals("invisible");
+ }
+
+ /**
+ * Determine if not can grow due to localization or not.
+ */
+ public boolean fixedWidth() {
+ String width = mNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
+ if (width.equals(VALUE_WRAP_CONTENT)) {
+ // First check child nodes. If at least one of them is not
+ // fixed-width,
+ // treat whole layout as non-fixed-width
+ NodeList childNodes = mNode.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ LayoutNode childLayout = new LayoutNode((Element) child,
+ i);
+ if (!childLayout.fixedWidth()) {
+ return false;
+ }
+ }
+ }
+ // If node contains text attribute, consider it fixed-width if
+ // text is hard-coded, otherwise it is not fixed-width.
+ String text = mNode.getAttributeNS(ANDROID_URI, ATTR_TEXT);
+ if (!text.isEmpty()) {
+ return !text.startsWith(PREFIX_RESOURCE_REF)
+ && !text.startsWith(PREFIX_THEME_REF);
+ }
+
+ String nodeName = mNode.getTagName();
+ if (nodeName.contains("Image") || nodeName.contains("Progress")
+ || nodeName.contains("Radio")) {
+ return true;
+ } else if (nodeName.contains("Button")
+ || nodeName.contains("Text")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @NonNull
+ public Element getNode() {
+ return mNode;
+ }
+
+ /**
+ * Process a node of a layout. Put it into one of three processing
+ * units and determine its right and left neighbours.
+ */
+ public void processNode(@NonNull Map<String, LayoutNode> nodes) {
+ if (mProcessed) {
+ return;
+ }
+ mProcessed = true;
+
+ if (isInvisible() ||
+ hasAttr(ATTR_LAYOUT_ALIGN_RIGHT) ||
+ hasAttr(ATTR_LAYOUT_ALIGN_END) ||
+ hasAttr(ATTR_LAYOUT_ALIGN_LEFT) ||
+ hasAttr(ATTR_LAYOUT_ALIGN_START)) {
+ mBucket = Bucket.SKIP;
+ } else if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_TOP)) {
+ mBucket = Bucket.TOP;
+ } else if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM)) {
+ mBucket = Bucket.BOTTOM;
+ } else {
+ if (hasAttr(ATTR_LAYOUT_ABOVE) || hasAttr(ATTR_LAYOUT_BELOW)) {
+ mBucket = Bucket.SKIP;
+ } else {
+ String[] checkAlignment = { ATTR_LAYOUT_ALIGN_TOP,
+ ATTR_LAYOUT_ALIGN_BOTTOM,
+ ATTR_LAYOUT_ALIGN_BASELINE };
+ for (String alignment : checkAlignment) {
+ String value = mNode.getAttributeNS(ANDROID_URI,
+ alignment);
+ if (!value.isEmpty()) {
+ LayoutNode otherNode = nodes.get(uniformId(value));
+ if (otherNode != null) {
+ otherNode.processNode(nodes);
+ mBucket = otherNode.mBucket;
+ }
+ }
+ }
+ }
+ }
+ if (mBucket == null) {
+ mBucket = Bucket.TOP;
+ }
+
+ // Check relative placement
+ boolean positioned = false;
+ mToLeft = findNodeByAttr(nodes, ATTR_LAYOUT_TO_START_OF);
+ if (mToLeft == null) {
+ mToLeft = findNodeByAttr(nodes, ATTR_LAYOUT_TO_LEFT_OF);
+ }
+ // Avoid circular dependency
+ for (LayoutNode n = mToLeft; n != null; n = n.mToLeft) {
+ if (n.equals(this)) {
+ mToLeft = null;
+ mBucket = Bucket.SKIP;
+ break;
+ }
+ }
+ if (mToLeft != null) {
+ mToLeft.mLastLeft = false;
+ mLastRight = false;
+ positioned = true;
+ }
+ mToRight = findNodeByAttr(nodes, ATTR_LAYOUT_TO_END_OF);
+ if (mToRight == null) {
+ mToRight = findNodeByAttr(nodes, ATTR_LAYOUT_TO_RIGHT_OF);
+ }
+ // Avoid circular dependency
+ for (LayoutNode n = mToRight; n != null; n = n.mToRight) {
+ if (n.equals(this)) {
+ mToRight = null;
+ mBucket = Bucket.SKIP;
+ break;
+ }
+ }
+ if (mToRight != null) {
+ mToRight.mLastRight = false;
+ mLastLeft = false;
+ positioned = true;
+ }
+
+ if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_END)
+ || hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) {
+ mLastRight = false;
+ positioned = true;
+ }
+ if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_START)
+ || hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_LEFT)) {
+ mLastLeft = false;
+ positioned = true;
+ }
+ // Treat any node that does not have explicit relative placement
+ // same as if it has layout_alignParentStart = true;
+ if (!positioned) {
+ mLastLeft = false;
+ }
+ }
+
+ @NonNull
+ public Set<LayoutNode> canGrowLeft() {
+ Set<LayoutNode> nodes;
+ if (mToRight != null) {
+ nodes = mToRight.canGrowLeft();
+ } else {
+ nodes = new LinkedHashSet<LayoutNode>();
+ }
+ if (!fixedWidth()) {
+ nodes.add(this);
+ }
+ return nodes;
+ }
+
+ @NonNull
+ public Set<LayoutNode> canGrowRight() {
+ Set<LayoutNode> nodes;
+ if (mToLeft != null) {
+ nodes = mToLeft.canGrowRight();
+ } else {
+ nodes = new LinkedHashSet<LayoutNode>();
+ }
+ if (!fixedWidth()) {
+ nodes.add(this);
+ }
+ return nodes;
+ }
+
+ /**
+ * Determines if not should be skipped from checking.
+ */
+ public boolean skip() {
+ if (mBucket == Bucket.SKIP) {
+ return true;
+ }
+
+ // Skip all includes and Views
+ return mNode.getTagName().equals(VIEW_INCLUDE)
+ || mNode.getTagName().equals(VIEW);
+ }
+
+ public boolean sameBucket(@NonNull LayoutNode node) {
+ return mBucket == node.mBucket;
+ }
+
+ @Nullable
+ private LayoutNode findNodeByAttr(
+ @NonNull Map<String, LayoutNode> nodes,
+ @NonNull String attrName) {
+ String value = mNode.getAttributeNS(ANDROID_URI, attrName);
+ if (!value.isEmpty()) {
+ return nodes.get(uniformId(value));
+ } else {
+ return null;
+ }
+ }
+
+ private boolean hasAttr(@NonNull String key) {
+ return mNode.hasAttributeNS(ANDROID_URI, key);
+ }
+
+ private boolean hasTrueAttr(@NonNull String key) {
+ return mNode.getAttributeNS(ANDROID_URI, key).equals(VALUE_TRUE);
+ }
+
+ @NonNull
+ private static String uniformId(@NonNull String value) {
+ return value.replaceFirst("@\\+", "@");
+ }
+ }
+
+ public RelativeOverlapDetector() {
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(RELATIVE_LAYOUT, PERCENT_RELATIVE_LAYOUT);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ // Traverse all child elements
+ NodeList childNodes = element.getChildNodes();
+ int count = childNodes.getLength();
+ Map<String, LayoutNode> nodes = Maps.newHashMap();
+ for (int i = 0; i < count; i++) {
+ Node node = childNodes.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ LayoutNode ln = new LayoutNode((Element) node, i);
+ nodes.put(ln.getNodeId(), ln);
+ }
+ }
+
+ // Node map is populated, recalculate nodes sizes
+ for (LayoutNode ln : nodes.values()) {
+ ln.processNode(nodes);
+ }
+ for (LayoutNode right : nodes.values()) {
+ if (!right.mLastLeft || right.skip()) {
+ continue;
+ }
+ Set<LayoutNode> canGrowLeft = right.canGrowLeft();
+ for (LayoutNode left : nodes.values()) {
+ if (left == right || !left.mLastRight || left.skip()
+ || !left.sameBucket(right)) {
+ continue;
+ }
+ Set<LayoutNode> canGrowRight = left.canGrowRight();
+ if (!canGrowLeft.isEmpty() || !canGrowRight.isEmpty()) {
+ canGrowRight.addAll(canGrowLeft);
+ LayoutNode nodeToBlame = right;
+ LayoutNode otherNode = left;
+ if (!canGrowRight.contains(right)
+ && canGrowRight.contains(left)) {
+ nodeToBlame = left;
+ otherNode = right;
+ }
+ context.report(ISSUE, nodeToBlame.getNode(),
+ context.getLocation(nodeToBlame.getNode()),
+ String.format(
+ "`%1$s` can overlap `%2$s` if %3$s %4$s due to localized text expansion",
+ nodeToBlame.getNodeId(), otherNode.getNodeId(),
+ Joiner.on(", ").join(canGrowRight),
+ canGrowRight.size() > 1 ? "grow" : "grows"));
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
new file mode 100644
index 0000000..71f1e24
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_LAYOUT;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PARENT;
+import static com.android.SdkConstants.ATTR_STYLE;
+import static com.android.SdkConstants.AUTO_URI;
+import static com.android.SdkConstants.FD_RES_LAYOUT;
+import static com.android.SdkConstants.FN_RESOURCE_BASE;
+import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7;
+import static com.android.SdkConstants.GRID_LAYOUT;
+import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.REQUEST_FOCUS;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.TABLE_LAYOUT;
+import static com.android.SdkConstants.TABLE_ROW;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+import static com.android.SdkConstants.VIEW_MERGE;
+import static com.android.resources.ResourceFolderType.LAYOUT;
+import static com.android.resources.ResourceFolderType.VALUES;
+import static com.android.tools.lint.detector.api.LintUtils.getLayoutName;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.NullLiteral;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.VariableReference;
+
+/**
+ * Ensures that layout width and height attributes are specified
+ */
+public class RequiredAttributeDetector extends LayoutDetector implements Detector.JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "RequiredSize", //$NON-NLS-1$
+ "Missing `layout_width` or `layout_height` attributes",
+
+ "All views must specify an explicit `layout_width` and `layout_height` attribute. " +
+ "There is a runtime check for this, so if you fail to specify a size, an exception " +
+ "is thrown at runtime.\n" +
+ "\n" +
+ "It's possible to specify these widths via styles as well. GridLayout, as a special " +
+ "case, does not require you to specify a size.",
+ Category.CORRECTNESS,
+ 4,
+ Severity.ERROR,
+ new Implementation(
+ RequiredAttributeDetector.class,
+ EnumSet.of(Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES)));
+
+ public static final String PERCENT_RELATIVE_LAYOUT
+ = "android.support.percent.PercentRelativeLayout";
+ public static final String ATTR_LAYOUT_WIDTH_PERCENT = "layout_widthPercent";
+ public static final String ATTR_LAYOUT_HEIGHT_PERCENT = "layout_heightPercent";
+
+ /** Map from each style name to parent style */
+ @Nullable private Map<String, String> mStyleParents;
+
+ /** Set of style names where the style sets the layout width */
+ @Nullable private Set<String> mWidthStyles;
+
+ /** Set of style names where the style sets the layout height */
+ @Nullable private Set<String> mHeightStyles;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the width is set on the include */
+ @Nullable private Set<String> mIncludedWidths;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the height is set on the include */
+ @Nullable private Set<String> mIncludedHeights;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the width is <b>not</b> set on the include */
+ @Nullable private Set<String> mNotIncludedWidths;
+
+ /** Set of layout names for layouts that are included by an {@code <include>} tag
+ * where the height is <b>not</b> set on the include */
+ @Nullable private Set<String> mNotIncludedHeights;
+
+ /** Whether the width was set in a theme definition */
+ private boolean mSetWidthInTheme;
+
+ /** Whether the height was set in a theme definition */
+ private boolean mSetHeightInTheme;
+
+ /** Constructs a new {@link RequiredAttributeDetector} */
+ public RequiredAttributeDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == LAYOUT || folderType == VALUES;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ // Process checks in two phases:
+ // Phase 1: Gather styles and includes (styles are encountered after the layouts
+ // so we can't do it in a single phase, and includes can be affected by includes from
+ // layouts we haven't seen yet)
+ // Phase 2: Process layouts, using gathered style and include data, and mark layouts
+ // not known.
+ //
+ if (context.getPhase() == 1) {
+ checkSizeSetInTheme();
+
+ context.requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
+ }
+ }
+
+ private boolean isWidthStyle(String style) {
+ return isSizeStyle(style, mWidthStyles);
+ }
+
+ private boolean isHeightStyle(String style) {
+ return isSizeStyle(style, mHeightStyles);
+ }
+
+ private boolean isSizeStyle(String style, Set<String> sizeStyles) {
+ if (isFrameworkSizeStyle(style)) {
+ return true;
+ }
+ if (sizeStyles == null) {
+ return false;
+ }
+ return isSizeStyle(stripStylePrefix(style), sizeStyles, 0);
+ }
+
+ private static boolean isFrameworkSizeStyle(String style) {
+ // The styles Widget.TextView.ListSeparator (and several theme variations, such as
+ // Widget.Holo.TextView.ListSeparator, Widget.Holo.Light.TextView.ListSeparator, etc)
+ // define layout_width and layout_height.
+ // These are exposed through the listSeparatorTextViewStyle style.
+ if (style.equals("?android:attr/listSeparatorTextViewStyle") //$NON-NLS-1$
+ || style.equals("?android/listSeparatorTextViewStyle")) { //$NON-NLS-1$
+ return true;
+ }
+
+ // It's also set on Widget.QuickContactBadge and Widget.QuickContactBadgeSmall
+ // These are exposed via a handful of attributes with a common prefix
+ if (style.startsWith("?android:attr/quickContactBadgeStyle")) { //$NON-NLS-1$
+ return true;
+ }
+
+ // Finally, the styles are set on MediaButton and Widget.Holo.Tab (and
+ // Widget.Holo.Light.Tab) but these are not exposed via attributes.
+
+ return false;
+ }
+
+ private boolean isSizeStyle(
+ @NonNull String style,
+ @NonNull Set<String> sizeStyles, int depth) {
+ if (depth == 30) {
+ // Cycle between local and framework attribute style missed
+ // by the fact that we're stripping the distinction between framework
+ // and local styles here
+ return false;
+ }
+
+ assert !style.startsWith(STYLE_RESOURCE_PREFIX)
+ && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
+
+ if (sizeStyles.contains(style)) {
+ return true;
+ }
+
+ if (mStyleParents != null) {
+ String parentStyle = mStyleParents.get(style);
+ if (parentStyle != null) {
+ parentStyle = stripStylePrefix(parentStyle);
+ if (isSizeStyle(parentStyle, sizeStyles, depth + 1)) {
+ return true;
+ }
+ }
+ }
+
+ int index = style.lastIndexOf('.');
+ if (index > 0) {
+ return isSizeStyle(style.substring(0, index), sizeStyles, depth + 1);
+ }
+
+ return false;
+ }
+
+ private void checkSizeSetInTheme() {
+ // Look through the styles and determine whether each style is a theme
+ if (mStyleParents == null) {
+ return;
+ }
+
+ Map<String, Boolean> isTheme = Maps.newHashMap();
+ for (String style : mStyleParents.keySet()) {
+ if (isTheme(stripStylePrefix(style), isTheme, 0)) {
+ mSetWidthInTheme = true;
+ mSetHeightInTheme = true;
+ break;
+ }
+ }
+ }
+
+ private boolean isTheme(String style, Map<String, Boolean> isTheme, int depth) {
+ if (depth == 30) {
+ // Cycle between local and framework attribute style missed
+ // by the fact that we're stripping the distinction between framework
+ // and local styles here
+ return false;
+ }
+
+ assert !style.startsWith(STYLE_RESOURCE_PREFIX)
+ && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
+
+ Boolean known = isTheme.get(style);
+ if (known != null) {
+ return known;
+ }
+
+ if (style.contains("Theme")) { //$NON-NLS-1$
+ isTheme.put(style, true);
+ return true;
+ }
+
+ if (mStyleParents != null) {
+ String parentStyle = mStyleParents.get(style);
+ if (parentStyle != null) {
+ parentStyle = stripStylePrefix(parentStyle);
+ if (isTheme(parentStyle, isTheme, depth + 1)) {
+ isTheme.put(style, true);
+ return true;
+ }
+ }
+ }
+
+ int index = style.lastIndexOf('.');
+ if (index > 0) {
+ String parentStyle = style.substring(0, index);
+ boolean result = isTheme(parentStyle, isTheme, depth + 1);
+ isTheme.put(style, result);
+ return result;
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ static boolean hasLayoutVariations(File file) {
+ File parent = file.getParentFile();
+ if (parent == null) {
+ return false;
+ }
+ File res = parent.getParentFile();
+ if (res == null) {
+ return false;
+ }
+ String name = file.getName();
+ File[] folders = res.listFiles();
+ if (folders == null) {
+ return false;
+ }
+ for (File folder : folders) {
+ if (!folder.getName().startsWith(FD_RES_LAYOUT)) {
+ continue;
+ }
+ if (folder.equals(parent)) {
+ continue;
+ }
+ File other = new File(folder, name);
+ if (other.exists()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static String stripStylePrefix(@NonNull String style) {
+ if (style.startsWith(STYLE_RESOURCE_PREFIX)) {
+ style = style.substring(STYLE_RESOURCE_PREFIX.length());
+ } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
+ style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length());
+ }
+
+ return style;
+ }
+
+ private static boolean isRootElement(@NonNull Node node) {
+ return node == node.getOwnerDocument().getDocumentElement();
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return ALL;
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ ResourceFolderType folderType = context.getResourceFolderType();
+ int phase = context.getPhase();
+ if (phase == 1 && folderType == VALUES) {
+ String tag = element.getTagName();
+ if (TAG_STYLE.equals(tag)) {
+ String parent = element.getAttribute(ATTR_PARENT);
+ if (parent != null && !parent.isEmpty()) {
+ String name = element.getAttribute(ATTR_NAME);
+ if (name != null && !name.isEmpty()) {
+ if (mStyleParents == null) {
+ mStyleParents = Maps.newHashMap();
+ }
+ mStyleParents.put(name, parent);
+ }
+ }
+ } else if (TAG_ITEM.equals(tag)
+ && TAG_STYLE.equals(element.getParentNode().getNodeName())) {
+ String name = element.getAttribute(ATTR_NAME);
+ if (name.endsWith(ATTR_LAYOUT_WIDTH) &&
+ name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_WIDTH)) {
+ if (mWidthStyles == null) {
+ mWidthStyles = Sets.newHashSet();
+ }
+ String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
+ mWidthStyles.add(styleName);
+ }
+ if (name.endsWith(ATTR_LAYOUT_HEIGHT) &&
+ name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_HEIGHT)) {
+ if (mHeightStyles == null) {
+ mHeightStyles = Sets.newHashSet();
+ }
+ String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
+ mHeightStyles.add(styleName);
+ }
+ }
+ } else if (folderType == LAYOUT) {
+ if (phase == 1) {
+ // Gather includes
+ if (element.getTagName().equals(VIEW_INCLUDE)) {
+ String layout = element.getAttribute(ATTR_LAYOUT);
+ if (layout != null && !layout.isEmpty()) {
+ recordIncludeWidth(layout,
+ element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH));
+ recordIncludeHeight(layout,
+ element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT));
+ }
+ }
+ } else {
+ assert phase == 2; // Check everything using style data and include data
+ boolean hasWidth = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
+ boolean hasHeight = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
+
+ if (mSetWidthInTheme) {
+ hasWidth = true;
+ }
+
+ if (mSetHeightInTheme) {
+ hasHeight = true;
+ }
+
+ if (hasWidth && hasHeight) {
+ return;
+ }
+
+ String tag = element.getTagName();
+ if (VIEW_MERGE.equals(tag)
+ || VIEW_INCLUDE.equals(tag)
+ || REQUEST_FOCUS.equals(tag)) {
+ return;
+ }
+
+ String parentTag = element.getParentNode() != null
+ ? element.getParentNode().getNodeName() : "";
+ if (TABLE_LAYOUT.equals(parentTag)
+ || TABLE_ROW.equals(parentTag)
+ || GRID_LAYOUT.equals(parentTag)
+ || FQCN_GRID_LAYOUT_V7.equals(parentTag)) {
+ return;
+ }
+
+ // PercentRelativeLayout or PercentFrameLayout?
+ boolean isPercent = parentTag.startsWith("android.support.percent.Percent");
+ if (isPercent) {
+ hasWidth |= element.hasAttributeNS(AUTO_URI, ATTR_LAYOUT_WIDTH_PERCENT);
+ hasHeight |= element.hasAttributeNS(AUTO_URI, ATTR_LAYOUT_HEIGHT_PERCENT);
+ if (hasWidth && hasHeight) {
+ return;
+ }
+ }
+
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ boolean certain = true;
+ boolean isRoot = isRootElement(element);
+ if (isRoot || isRootElement(element.getParentNode())
+ && VIEW_MERGE.equals(parentTag)) {
+ String name = LAYOUT_RESOURCE_PREFIX + getLayoutName(context.file);
+ if (!hasWidth && mIncludedWidths != null) {
+ hasWidth = mIncludedWidths.contains(name);
+ // If the layout is *also* included in a context where the width
+ // was not set, we're not certain; it's possible that
+ if (mNotIncludedWidths != null && mNotIncludedWidths.contains(name)) {
+ hasWidth = false;
+ // If we only have a single layout we know that this layout isn't
+ // always included with layout_width or layout_height set, but
+ // if there are multiple layouts, it's possible that at runtime
+ // we only load the size-less layout by the tag which includes
+ // the size
+ certain = !hasLayoutVariations(context.file);
+ }
+ }
+ if (!hasHeight && mIncludedHeights != null) {
+ hasHeight = mIncludedHeights.contains(name);
+ if (mNotIncludedHeights != null && mNotIncludedHeights.contains(name)) {
+ hasHeight = false;
+ certain = !hasLayoutVariations(context.file);
+ }
+ }
+ if (hasWidth && hasHeight) {
+ return;
+ }
+ }
+
+ if (!hasWidth || !hasHeight) {
+ String style = element.getAttribute(ATTR_STYLE);
+ if (style != null && !style.isEmpty()) {
+ if (!hasWidth) {
+ hasWidth = isWidthStyle(style);
+ }
+ if (!hasHeight) {
+ hasHeight = isHeightStyle(style);
+ }
+ }
+ if (hasWidth && hasHeight) {
+ return;
+ }
+ }
+
+ String message;
+ if (!(hasWidth || hasHeight)) {
+ if (certain) {
+ message = "The required `layout_width` and `layout_height` attributes " +
+ "are missing";
+ } else {
+ message = "The required `layout_width` and `layout_height` attributes " +
+ "*may* be missing";
+ }
+ } else {
+ String attribute = hasWidth ? ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH;
+ if (certain) {
+ message = String.format("The required `%1$s` attribute is missing",
+ attribute);
+ } else {
+ message = String.format("The required `%1$s` attribute *may* be missing",
+ attribute);
+ }
+ }
+ if (isPercent) {
+ String escapedLayoutWidth = '`' + ATTR_LAYOUT_WIDTH + '`';
+ String escapedLayoutHeight = '`' + ATTR_LAYOUT_HEIGHT + '`';
+ String escapedLayoutWidthPercent = '`' + ATTR_LAYOUT_WIDTH_PERCENT + '`';
+ String escapedLayoutHeightPercent = '`' + ATTR_LAYOUT_HEIGHT_PERCENT + '`';
+ message = message.replace(escapedLayoutWidth, escapedLayoutWidth + " or "
+ + escapedLayoutWidthPercent).replace(escapedLayoutHeight,
+ escapedLayoutHeight + " or " + escapedLayoutHeightPercent);
+ }
+ context.report(ISSUE, element, context.getLocation(element),
+ message);
+ }
+ }
+ }
+
+ private void recordIncludeWidth(String layout, boolean providesWidth) {
+ if (providesWidth) {
+ if (mIncludedWidths == null) {
+ mIncludedWidths = Sets.newHashSet();
+ }
+ mIncludedWidths.add(layout);
+ } else {
+ if (mNotIncludedWidths == null) {
+ mNotIncludedWidths = Sets.newHashSet();
+ }
+ mNotIncludedWidths.add(layout);
+ }
+ }
+
+ private void recordIncludeHeight(String layout, boolean providesHeight) {
+ if (providesHeight) {
+ if (mIncludedHeights == null) {
+ mIncludedHeights = Sets.newHashSet();
+ }
+ mIncludedHeights.add(layout);
+ } else {
+ if (mNotIncludedHeights == null) {
+ mNotIncludedHeights = Sets.newHashSet();
+ }
+ mNotIncludedHeights.add(layout);
+ }
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ @Nullable
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList("inflate"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void visitMethod(
+ @NonNull JavaContext context,
+ @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation call) {
+ // Handle
+ // View#inflate(Context context, int resource, ViewGroup root)
+ // LayoutInflater#inflate(int resource, ViewGroup root)
+ // LayoutInflater#inflate(int resource, ViewGroup root, boolean attachToRoot)
+ StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
+
+ String layout = null;
+ int index = 0;
+ for (Iterator<Expression> iterator = args.iterator(); iterator.hasNext(); index++) {
+ Expression expression = iterator.next();
+ if (expression instanceof Select) {
+ Select outer = (Select) expression;
+ Expression operand = outer.astOperand();
+ if (operand instanceof Select) {
+ Select inner = (Select) operand;
+ if (inner.astOperand() instanceof VariableReference) {
+ VariableReference reference = (VariableReference) inner.astOperand();
+ if (FN_RESOURCE_BASE.equals(reference.astIdentifier().astValue())
+ // TODO: constant
+ && "layout".equals(inner.astIdentifier().astValue())) {
+ layout = LAYOUT_RESOURCE_PREFIX + outer.astIdentifier().astValue();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (layout == null) {
+ lombok.ast.Node method = StringFormatDetector.getParentMethod(call);
+ if (method != null) {
+ // Must track local types
+ index = 0;
+ String name = StringFormatDetector.getResourceArg(method, call, index);
+ if (name == null) {
+ index = 1;
+ name = StringFormatDetector.getResourceArg(method, call, index);
+ }
+ if (name != null) {
+ layout = LAYOUT_RESOURCE_PREFIX + name;
+ }
+ }
+ if (layout == null) {
+ // Flow analysis didn't succeed
+ return;
+ }
+ }
+
+ // In all the applicable signatures, the view root argument is immediately after
+ // the layout resource id.
+ int viewRootPos = index + 1;
+ if (viewRootPos < args.size()) {
+ int i = 0;
+ Iterator<Expression> iterator = args.iterator();
+ while (iterator.hasNext() && i < viewRootPos) {
+ iterator.next();
+ i++;
+ }
+ if (iterator.hasNext()) {
+ Expression viewRoot = iterator.next();
+ if (viewRoot instanceof NullLiteral) {
+ // Yep, this one inflates the given view with a null parent:
+ // Tag it as such. For now just use the include data structure since
+ // it has the same net effect
+ recordIncludeWidth(layout, true);
+ recordIncludeHeight(layout, true);
+ }
+ }
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceCycleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceCycleDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceCycleDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceCycleDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
new file mode 100644
index 0000000..bac39af
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourcePrefixDetector.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.TAG_DECLARE_STYLEABLE;
+import static com.android.SdkConstants.TAG_RESOURCES;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceContext;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+
+/**
+ * Ensure that resources in Gradle projects which specify a resource prefix
+ * conform to the given name
+ *
+ * TODO: What about id's?
+ */
+public class ResourcePrefixDetector extends ResourceXmlDetector implements
+ Detector.BinaryResourceScanner {
+ /** The main issue discovered by this detector */
+ @SuppressWarnings("unchecked")
+ public static final Issue ISSUE = Issue.create(
+ "ResourceName", //$NON-NLS-1$
+ "Resource with Wrong Prefix",
+ "In Gradle projects you can specify a resource prefix that all resources " +
+ "in the project must conform to. This makes it easier to ensure that you don't " +
+ "accidentally combine resources from different libraries, since they all end " +
+ "up in the same shared app namespace.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ new Implementation(
+ ResourcePrefixDetector.class,
+ EnumSet.of(Scope.RESOURCE_FILE, Scope.BINARY_RESOURCE_FILE),
+ Scope.RESOURCE_FILE_SCOPE,
+ Scope.BINARY_RESOURCE_FILE_SCOPE));
+
+ /** Constructs a new {@link com.android.tools.lint.checks.ResourcePrefixDetector} */
+ public ResourcePrefixDetector() {
+ }
+
+ private String mPrefix;
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(TAG_RESOURCES, TAG_DECLARE_STYLEABLE);
+ }
+
+ @Nullable
+ private static String computeResourcePrefix(@NonNull Project project) {
+ if (project.isGradleProject()) {
+ return LintUtils.computeResourcePrefix(project.getGradleProjectModel());
+ }
+
+ return null;
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ mPrefix = computeResourcePrefix(context.getProject());
+ }
+
+ @Override
+ public void beforeCheckLibraryProject(@NonNull Context context) {
+ // TODO: Make sure this doesn't wipe out the prefix for the remaining projects
+ mPrefix = computeResourcePrefix(context.getProject());
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ mPrefix = null;
+ }
+
+ @Override
+ public void afterCheckLibraryProject(@NonNull Context context) {
+ mPrefix = null;
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ if (mPrefix != null && context instanceof XmlContext) {
+ XmlContext xmlContext = (XmlContext) context;
+ ResourceFolderType folderType = xmlContext.getResourceFolderType();
+ if (folderType != null && folderType != ResourceFolderType.VALUES) {
+ String name = LintUtils.getBaseName(context.file.getName());
+ if (!name.startsWith(mPrefix)) {
+ // Attempt to report the error on the root tag of the associated
+ // document to make suppressing the error with a tools:suppress
+ // attribute etc possible
+ if (xmlContext.document != null) {
+ Element root = xmlContext.document.getDocumentElement();
+ if (root != null) {
+ xmlContext.report(ISSUE, root, xmlContext.getLocation(root),
+ getErrorMessage(name));
+ return;
+ }
+ }
+ context.report(ISSUE, Location.create(context.file),
+ getErrorMessage(name));
+ }
+ }
+ }
+ }
+
+ private String getErrorMessage(String name) {
+ assert mPrefix != null && !name.startsWith(mPrefix);
+ return String.format("Resource named '`%1$s`' does not start "
+ + "with the project's resource prefix '`%2$s`'; rename to '`%3$s`' ?",
+ name, mPrefix, LintUtils.computeResourceName(mPrefix, name));
+ }
+
+ // --- Implements XmlScanner ----
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (mPrefix == null || context.getResourceFolderType() != ResourceFolderType.VALUES) {
+ return;
+ }
+
+ for (Element item : LintUtils.getChildren(element)) {
+ Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
+ if (nameAttribute != null) {
+ String name = nameAttribute.getValue();
+ if (!name.startsWith(mPrefix)) {
+ String message = getErrorMessage(name);
+ context.report(ISSUE, nameAttribute, context.getLocation(nameAttribute),
+ message);
+ }
+ }
+ }
+ }
+
+ // ---- Implements BinaryResourceScanner ---
+
+ @Override
+ public void checkBinaryResource(@NonNull ResourceContext context) {
+ if (mPrefix != null) {
+ ResourceFolderType folderType = context.getResourceFolderType();
+ if (folderType != null && folderType != ResourceFolderType.VALUES) {
+ String name = LintUtils.getBaseName(context.file.getName());
+ if (!name.startsWith(mPrefix)) {
+ // Turns out the Gradle plugin will generate raw resources
+ // for renderscript. We don't want to flag these.
+ // We don't have a good way to recognize them today.
+ String path = context.file.getPath();
+ if (path.endsWith(".bc") && folderType == ResourceFolderType.RAW) {
+ return;
+ }
+
+ Location location = Location.create(context.file);
+ context.report(ISSUE, location, getErrorMessage(name));
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceUsageModel.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceUsageModel.java
new file mode 100644
index 0000000..9c5416f
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceUsageModel.java
@@ -0,0 +1,1655 @@
+/*
+ * 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.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_DISCARD;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_KEEP;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PARENT;
+import static com.android.SdkConstants.ATTR_SHRINK_MODE;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.PREFIX_ANDROID;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VALUE_SAFE;
+import static com.android.SdkConstants.VALUE_STRICT;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.DefaultConfiguration;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A model for Android resource declarations and usages
+ */
+public class ResourceUsageModel {
+ private static final int TYPICAL_RESOURCE_COUNT = 200;
+
+ /** List of all known resources (parsed from R.java) */
+ private List<Resource> mResources = Lists.newArrayListWithExpectedSize(TYPICAL_RESOURCE_COUNT);
+ /** Map from resource type to map from resource name to resource object */
+ private Map<ResourceType, Map<String, Resource>> mTypeToName =
+ Maps.newEnumMap(ResourceType.class);
+ /** Map from R field value to corresponding resource */
+ private Map<Integer, Resource> mValueToResource =
+ Maps.newHashMapWithExpectedSize(TYPICAL_RESOURCE_COUNT);
+
+ public static String getFieldName(Element element) {
+ return LintUtils.getFieldName(element.getAttribute(ATTR_NAME));
+ }
+
+ public static ResourceType getResourceType(Element element) {
+ String tagName = element.getTagName();
+ if (tagName.equals(TAG_ITEM)) {
+ String typeName = element.getAttribute(ATTR_TYPE);
+ if (!typeName.isEmpty()) {
+ return ResourceType.getEnum(typeName);
+ }
+ } else if ("string-array".equals(tagName) || "integer-array".equals(tagName)) {
+ return ResourceType.ARRAY;
+ } else {
+ return ResourceType.getEnum(tagName);
+ }
+ return null;
+ }
+
+ @Nullable
+ public Resource getResource(Element element) {
+ return getResource(element, false);
+ }
+
+ public Resource getResource(Element element, boolean declare) {
+ ResourceType type = getResourceType(element);
+ if (type != null) {
+ String name = getFieldName(element);
+ Resource resource = getResource(type, name);
+ if (resource == null && declare) {
+ resource = addResource(type, name, null);
+ resource.setDeclared(true);
+ }
+ return resource;
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unused") // Used by (temporary) copy in Gradle resource shrinker
+ @Nullable
+ public Resource getResource(@NonNull Integer value) {
+ return mValueToResource.get(value);
+ }
+
+ @Nullable
+ public Resource getResource(@NonNull ResourceType type, @NonNull String name) {
+ Map<String, Resource> nameMap = mTypeToName.get(type);
+ if (nameMap != null) {
+ return nameMap.get(LintUtils.getFieldName(name));
+ }
+ return null;
+ }
+
+ @Nullable
+ Resource getResourceFromUrl(@NonNull String possibleUrlReference) {
+ ResourceUrl url = ResourceUrl.parse(possibleUrlReference);
+ if (url != null && !url.framework) {
+ return addResource(url.type, LintUtils.getFieldName(url.name), null);
+ }
+
+ return null;
+ }
+
+ private static final String ANDROID_RES = "android_res/";
+
+ @Nullable
+ public Resource getResourceFromFilePath(@NonNull String url) {
+ int nameSlash = url.lastIndexOf('/');
+ if (nameSlash == -1) {
+ return null;
+ }
+
+ // Look for
+ // (1) a full resource URL: /android_res/type/name.ext
+ // (2) a partial URL that uniquely identifies a given resource: drawable/name.ext
+ // e.g. file:///android_res/drawable/bar.png
+ int androidRes = url.indexOf(ANDROID_RES);
+ if (androidRes != -1) {
+ androidRes += ANDROID_RES.length();
+ int slash = url.indexOf('/', androidRes);
+ if (slash != -1) {
+ String folderName = url.substring(androidRes, slash);
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
+ if (folderType != null) {
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
+ folderType);
+ if (!types.isEmpty()) {
+ ResourceType type = types.get(0);
+ int nameBegin = slash + 1;
+ int dot = url.indexOf('.', nameBegin);
+ String name = url.substring(nameBegin, dot != -1 ? dot : url.length());
+ return getResource(type, name);
+ }
+ }
+ }
+ }
+
+ // Some other relative path. Just look from the end:
+ int typeSlash = url.lastIndexOf('/', nameSlash - 1);
+ ResourceType type = ResourceType.getEnum(url.substring(typeSlash + 1, nameSlash));
+ if (type != null) {
+ int nameBegin = nameSlash + 1;
+ int dot = url.indexOf('.', nameBegin);
+ String name = url.substring(nameBegin, dot != -1 ? dot : url.length());
+ return getResource(type, name);
+ }
+
+ return null;
+ }
+
+ /**
+ * Marks the given resource (if non-null) as reachable, and returns true if
+ * this is the first time the resource is marked reachable
+ */
+ public static boolean markReachable(@Nullable Resource resource) {
+ if (resource != null) {
+ boolean wasReachable = resource.isReachable();
+ resource.setReachable(true);
+ return !wasReachable;
+ }
+
+ return false;
+ }
+
+ private static void markUnreachable(@Nullable Resource resource) {
+ if (resource != null) {
+ resource.setReachable(false);
+ }
+ }
+
+
+ public void recordManifestUsages(Node node) {
+ short nodeType = node.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr) attributes.item(i);
+ markReachable(getResourceFromUrl(attr.getValue()));
+ }
+ } else if (nodeType == Node.TEXT_NODE) {
+ // Does this apply to any manifests??
+ String text = node.getNodeValue().trim();
+ markReachable(getResourceFromUrl(text));
+ }
+
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ recordManifestUsages(child);
+ }
+ }
+
+ private static final int RESOURCE_DECLARED = 1 << 1;
+ private static final int RESOURCE_PUBLIC = 1 << 2;
+ private static final int RESOURCE_KEEP = 1 << 3;
+ private static final int RESOURCE_DISCARD = 1 << 4;
+ private static final int RESOURCE_REACHABLE = 1 << 5;
+
+ public static class Resource implements Comparable<Resource> {
+ private int mFlags;
+
+ /** Type of resource */
+ public final ResourceType type;
+ /** Name of resource */
+ public final String name;
+ /** Integer id location */
+ public int value;
+
+ /** Resources this resource references. For example, a layout can reference another via
+ * an include; a style reference in a layout references that layout style, and so on. */
+ public List<Resource> references;
+
+ /** Chained list of declaration locations */
+ public Location locations;
+ public List<File> declarations;
+
+ /** Whether we found a declaration for this resource (otherwise we might have seen
+ * a reference to this before we came across its potential declaration, so we added it
+ * to the map, but we don't want to report unused resources for invalid resource
+ * references */
+ public boolean isDeclared() {
+ return (mFlags & RESOURCE_DECLARED) != 0;
+ }
+
+ /** Whether we found a declaration for this resource (otherwise we might have seen
+ * a reference to this before we came across its potential declaration, so we added it
+ * to the map, but we don't want to report unused resources for invalid resource
+ * references */
+ public void setDeclared(boolean on) {
+ mFlags = on ? (mFlags | RESOURCE_DECLARED) : (mFlags & ~RESOURCE_DECLARED);
+ }
+
+ /** This resource is marked as public */
+ public boolean isPublic() {
+ return (mFlags & RESOURCE_PUBLIC) != 0;
+ }
+
+ /** This resource is marked as public */
+ public void setPublic(boolean on) {
+ mFlags = on ? (mFlags | RESOURCE_PUBLIC) : (mFlags & ~RESOURCE_PUBLIC);
+ }
+
+ /** This resource is marked as to be ignored for usage analysis, regardless of
+ * references */
+ public boolean isKeep() {
+ return (mFlags & RESOURCE_KEEP) != 0;
+ }
+
+ /** This resource is marked as to be ignored for usage analysis, regardless of
+ * references */
+ public void setKeep(boolean on) {
+ mFlags = on ? (mFlags | RESOURCE_KEEP) : (mFlags & ~RESOURCE_KEEP);
+ }
+
+ /** This resource is marked as to be ignored for usage analysis, regardless of lack of
+ * references */
+ public boolean isDiscard() {
+ return (mFlags & RESOURCE_DISCARD) != 0;
+ }
+
+ /** This resource is marked as to be ignored for usage analysis, regardless of lack of
+ * references */
+ public void setDiscard(boolean on) {
+ mFlags = on ? (mFlags | RESOURCE_DISCARD) : (mFlags & ~RESOURCE_DISCARD);
+ }
+
+ public boolean isReachable() {
+ return (mFlags & RESOURCE_REACHABLE) != 0;
+ }
+
+ public void setReachable(boolean on) {
+ mFlags = on ? (mFlags | RESOURCE_REACHABLE) : (mFlags & ~RESOURCE_REACHABLE);
+ }
+
+ public Resource(ResourceType type, String name, int value) {
+ this.type = type;
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return type + ":" + name + ":" + value;
+ }
+
+ @SuppressWarnings("RedundantIfStatement") // Generated by IDE
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Resource resource = (Resource) o;
+
+ if (name != null ? !name.equals(resource.name) : resource.name != null) {
+ return false;
+ }
+ if (type != resource.type) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ return result;
+ }
+
+ public void addLocation(@NonNull File file) {
+ if (declarations == null) {
+ declarations = Lists.newArrayList();
+ }
+ declarations.add(file);
+ }
+
+ public void recordLocation(@NonNull Location location) {
+ Location oldLocation = this.locations;
+ if (oldLocation != null) {
+ location.setSecondary(oldLocation);
+ }
+ this.locations = location;
+ }
+
+ public void addReference(@Nullable Resource resource) {
+ if (resource != null) {
+ if (references == null) {
+ references = Lists.newArrayList();
+ } else if (references.contains(resource)) {
+ return;
+ }
+ references.add(resource);
+ }
+ }
+
+ public String getUrl() {
+ return '@' + type.getName() + '/' + name;
+ }
+
+ public String getField() {
+ return "R." + type.getName() + '.' + name;
+ }
+
+ @Override
+ public int compareTo(@NonNull Resource other) {
+ if (type != other.type) {
+ return type.compareTo(other.type);
+ }
+
+ return name.compareTo(other.name);
+ }
+ }
+
+ public List<Resource> findUnused() {
+ return findUnused(mResources);
+ }
+
+ public String dumpReferences() {
+ StringBuilder sb = new StringBuilder(1000);
+ sb.append("Resource Reference Graph:\n");
+ for (Resource resource : mResources) {
+ if (resource.references != null) {
+ sb.append(resource).append(" => ").append(resource.references).append('\n');
+ }
+ }
+ return sb.toString();
+ }
+
+
+ public String dumpResourceModel() {
+ StringBuilder sb = new StringBuilder(1000);
+ Collections.sort(mResources, new Comparator<Resource>() {
+ @Override
+ public int compare(Resource resource1,
+ Resource resource2) {
+ int delta = resource1.type.compareTo(resource2.type);
+ if (delta != 0) {
+ return delta;
+ }
+ return resource1.name.compareTo(resource2.name);
+ }
+ });
+
+ for (Resource resource : mResources) {
+ sb.append(resource.getUrl()).append(" : reachable=").append(resource.isReachable());
+ sb.append("\n");
+ if (resource.references != null) {
+ for (Resource referenced : resource.references) {
+ sb.append(" ");
+ sb.append(referenced.getUrl());
+ sb.append("\n");
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public List<Resource> findUnused(List<Resource> resources) {
+ List<Resource> roots = findRoots(resources);
+
+ Map<Resource,Boolean> seen = new IdentityHashMap<Resource,Boolean>(resources.size());
+ for (Resource root : roots) {
+ visit(root, seen);
+ }
+
+ List<Resource> unused = Lists.newArrayListWithExpectedSize(resources.size());
+ for (Resource resource : resources) {
+ if (!resource.isReachable()
+ // Styles not yet handled correctly: don't mark as unused
+ && resource.type != ResourceType.ATTR
+ && resource.type != ResourceType.DECLARE_STYLEABLE
+ && resource.type != ResourceType.STYLEABLE
+ // Don't flag known service keys read by library
+ && !TranslationDetector.isServiceKey(resource.name)) {
+ unused.add(resource);
+ }
+ }
+
+ return unused;
+ }
+
+ @SuppressWarnings("MethodMayBeStatic")
+ @NonNull
+ protected List<Resource> findRoots(@NonNull List<Resource> resources) {
+ List<Resource> roots = Lists.newArrayList();
+
+ for (Resource resource : resources) {
+ if (resource.isReachable() || resource.isKeep()) {
+ roots.add(resource);
+ }
+ }
+ return roots;
+ }
+
+ private static void visit(Resource root, Map<Resource, Boolean> seen) {
+ if (seen.containsKey(root)) {
+ return;
+ }
+ seen.put(root, Boolean.TRUE);
+ root.setReachable(true);
+ if (root.references != null) {
+ for (Resource referenced : root.references) {
+ visit(referenced, seen);
+ }
+ }
+ }
+
+ @NonNull
+ public Resource addDeclaredResource(@NonNull ResourceType type, @NonNull String name,
+ @Nullable String value, boolean declared) {
+ Resource resource = addResource(type, name, value);
+ if (declared) {
+ resource.setDeclared(true);
+ }
+ return resource;
+ }
+
+ @NonNull
+ public Resource addResource(@NonNull ResourceType type, @NonNull String name,
+ @Nullable String value) {
+ int realValue = value != null ? Integer.decode(value) : -1;
+ Resource resource = getResource(type, name);
+ if (resource != null) {
+ //noinspection VariableNotUsedInsideIf
+ if (value != null) {
+ if (resource.value == -1) {
+ resource.value = realValue;
+ } else {
+ assert realValue == resource.value;
+ }
+ }
+ return resource;
+ }
+
+ resource = new Resource(type, name, realValue);
+ mResources.add(resource);
+ if (realValue != -1) {
+ mValueToResource.put(realValue, resource);
+ }
+ Map<String, Resource> nameMap = mTypeToName.get(type);
+ if (nameMap == null) {
+ nameMap = Maps.newHashMapWithExpectedSize(30);
+ mTypeToName.put(type, nameMap);
+ }
+ nameMap.put(name, resource);
+
+ // TODO: Assert that we don't set the same resource multiple times to different values.
+ // Could happen if you pass in stale data!
+
+ return resource;
+ }
+
+ /**
+ * Called for a tools:keep attribute containing a resource URL where that resource name
+ * is not referencing a known resource
+ *
+ * @param value The keep value
+ */
+ private void processKeepAttributes(@NonNull String value) {
+ // TODO: When nothing matches one of these attributes, mark it as unused too!
+ // Handle comma separated lists of URLs and globs
+ if (value.indexOf(',') != -1) {
+ for (String portion : Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
+ processKeepAttributes(portion);
+ }
+ return;
+ }
+
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (url == null || url.framework) {
+ return;
+ }
+
+ Resource resource = getResource(url.type, url.name);
+ if (resource != null) {
+ markReachable(resource);
+ } else if (url.name.contains("*") || url.name.contains("?")) {
+ // Look for globbing patterns
+ String regexp = DefaultConfiguration.globToRegexp(LintUtils.getFieldName(url.name));
+ try {
+ Pattern pattern = Pattern.compile(regexp);
+ Map<String, Resource> nameMap = mTypeToName.get(url.type);
+ if (nameMap != null) {
+ for (Resource r : nameMap.values()) {
+ if (pattern.matcher(r.name).matches()) {
+ markReachable(r);
+ }
+ }
+ }
+ } catch (PatternSyntaxException ignored) {
+ }
+ }
+ }
+
+ private void processDiscardAttributes(@NonNull String value) {
+ // Handle comma separated lists of URLs and globs
+ if (value.indexOf(',') != -1) {
+ for (String portion : Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
+ processDiscardAttributes(portion);
+ }
+ return;
+ }
+
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (url == null || url.framework) {
+ return;
+ }
+
+ Resource resource = getResource(url.type, url.name);
+ if (resource != null) {
+ markUnreachable(resource);
+ } else if (url.name.contains("*") || url.name.contains("?")) {
+ // Look for globbing patterns
+ String regexp = DefaultConfiguration.globToRegexp(LintUtils.getFieldName(url.name));
+ try {
+ Pattern pattern = Pattern.compile(regexp);
+ Map<String, Resource> nameMap = mTypeToName.get(url.type);
+ if (nameMap != null) {
+ for (Resource r : nameMap.values()) {
+ if (pattern.matcher(r.name).matches()) {
+ markUnreachable(r);
+ }
+ }
+ }
+ } catch (PatternSyntaxException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Recorded list of keep attributes: these can contain wildcards,
+ * so they can't be applied immediately; we have to apply them after
+ * scanning through all resources (done by {@link #processToolsAttributes()}
+ */
+ private List<String> mKeepAttributes;
+
+ /**
+ * Recorded list of discard attributes: these can contain wildcards,
+ * so they can't be applied immediately; we have to apply them after
+ * scanning through all resources (done by {@link #processToolsAttributes()}
+ */
+ private List<String> mDiscardAttributes;
+
+ private boolean mSafeMode = true;
+
+ /**
+ * Whether we should attempt to guess resources that should be kept based on looking
+ * at the string pool and assuming some of the strings can be used to dynamically construct
+ * the resource names. Can be turned off via {@code tools:shrinkMode="strict"}.
+ */
+ public boolean isSafeMode() {
+ return mSafeMode;
+ }
+
+ public void processToolsAttributes() {
+ if (mKeepAttributes != null) {
+ for (String keep : mKeepAttributes) {
+ processKeepAttributes(keep);
+ }
+ }
+ if (mDiscardAttributes != null) {
+ for (String discard : mDiscardAttributes) {
+ processDiscardAttributes(discard);
+ }
+ }
+ }
+
+ public void recordToolsAttributes(@Nullable Attr attr) {
+ if (attr == null) {
+ return;
+ }
+ String localName = attr.getLocalName();
+ String value = attr.getValue();
+ if (ATTR_KEEP.equals(localName)) {
+ if (mKeepAttributes == null) {
+ mKeepAttributes = Lists.newArrayList();
+ }
+ mKeepAttributes.add(value);
+ } else if (ATTR_DISCARD.equals(localName)) {
+ if (mDiscardAttributes == null) {
+ mDiscardAttributes = Lists.newArrayList();
+ }
+ mDiscardAttributes.add(value);
+ } else if (ATTR_SHRINK_MODE.equals(localName)) {
+ if (VALUE_STRICT.equals(value)) {
+ mSafeMode = false;
+ } else if (VALUE_SAFE.equals(value)) {
+ mSafeMode = true;
+ }
+ }
+ }
+
+ protected Resource declareResource(ResourceType type, String name, Node node) {
+ return addDeclaredResource(type, name, null, true);
+ }
+
+ @NonNull
+ protected String readText(@NonNull File file) {
+ try {
+ return Files.toString(file, UTF_8);
+ } catch (IOException ignore) {
+ return "";
+ }
+ }
+
+ public void visitBinaryResource(
+ @Nullable ResourceFolderType folderType,
+ @NonNull File file) {
+ Resource from = null;
+ if (folderType != ResourceFolderType.VALUES) {
+ // Record resource for the whole file
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
+ folderType);
+ ResourceType type = types.get(0);
+ assert type != ResourceType.ID : folderType;
+ String name = LintUtils.getBaseName(file.getName());
+ from = declareResource(type, name, null);
+ }
+
+ if (folderType == ResourceFolderType.RAW) {
+ // Is this an HTML, CSS or JavaScript document bundled with the app?
+ // If so tokenize and look for resource references.
+ String path = file.getPath();
+ if (endsWithIgnoreCase(path, ".html") || endsWithIgnoreCase(path, ".htm")) {
+ tokenizeHtml(from, readText(file));
+ } else if (endsWithIgnoreCase(path, ".css")) {
+ tokenizeCss(from, readText(file));
+ } else if (endsWithIgnoreCase(path, ".js")) {
+ tokenizeJs(from, readText(file));
+ } else if (file.isFile() && !LintUtils.isBitmapFile(file)) {
+ tokenizeUnknownBinary(from, file);
+ }
+ }
+ }
+
+ public void visitXmlDocument(
+ @NonNull File file,
+ @Nullable ResourceFolderType folderType,
+ @NonNull Document document) {
+ if (folderType == null) {
+ // Manifest file
+ recordManifestUsages(document.getDocumentElement());
+ return;
+ }
+ Resource from = null;
+ if (folderType != ResourceFolderType.VALUES) {
+ // Record resource for the whole file
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(
+ folderType);
+ ResourceType type = types.get(0);
+ assert type != ResourceType.ID : folderType;
+ String name = LintUtils.getBaseName(file.getName());
+
+ from = declareResource(type, name, document.getDocumentElement());
+ } else if (isAnalyticsFile(file)) {
+ return;
+ }
+
+ // For value files, and drawables and colors etc also pull in resource
+ // references inside the context.file
+ recordResourceReferences(folderType, document.getDocumentElement(), from);
+
+ if (folderType == ResourceFolderType.XML) {
+ tokenizeUnknownText(readText(file));
+ }
+ }
+
+ private static final String ANALYTICS_FILE = "analytics.xml"; //$NON-NLS-1$
+
+ /**
+ * Returns true if this XML file corresponds to an Analytics configuration file;
+ * these contain some attributes read by the library which won't be flagged as
+ * used by the application
+ *
+ * @param file the file in question
+ * @return true if the file represents an analytics file
+ */
+ public static boolean isAnalyticsFile(File file) {
+ return file.getPath().endsWith(ANALYTICS_FILE) && file.getName().equals(ANALYTICS_FILE);
+ }
+
+ /**
+ * Records resource declarations and usages within an XML resource file
+ * @param folderType the type of resource file
+ * @param node the root node to start the recursive search from
+ * @param from a referencing context, if any.
+ */
+ public void recordResourceReferences(
+ @NonNull ResourceFolderType folderType,
+ @NonNull Node node,
+ @Nullable Resource from) {
+ short nodeType = node.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ if (from != null) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr) attributes.item(i);
+
+ // Ignore tools: namespace attributes, unless it's
+ // a keep attribute
+ if (TOOLS_URI.equals(attr.getNamespaceURI())) {
+ recordToolsAttributes(attr);
+ // Skip all other tools: attributes
+ continue;
+ }
+
+ String value = attr.getValue();
+ if (!(value.startsWith(PREFIX_RESOURCE_REF) || value.startsWith(PREFIX_THEME_REF))) {
+ continue;
+ }
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (url != null && !url.framework) {
+ Resource resource;
+ if (url.create) {
+ resource = declareResource(url.type, url.name, attr);
+ if (!ATTR_ID.equals(attr.getLocalName()) || !ANDROID_URI.equals(attr.getNamespaceURI())) {
+ // Declaring an id is not a reference to that id
+ from.addReference(resource);
+ }
+ } else {
+ resource = addResource(url.type, url.name, null);
+ from.addReference(resource);
+ }
+ } else if (value.startsWith("@{")) {
+ // Data binding expression: there could be multiple references here
+ int length = value.length();
+ int index = 2; // skip @{
+ while (true) {
+ index = value.indexOf('@', index);
+ if (index == -1) {
+ break;
+ }
+ // Find end of (potential) resource URL: first non resource URL character
+ int end = index + 1;
+ while (end < length) {
+ char c = value.charAt(end);
+ if (!(Character.isJavaIdentifierPart(c) ||
+ c == '_' ||
+ c == '.' ||
+ c == '/' ||
+ c == '+')) {
+ break;
+ }
+ end++;
+ }
+ url = ResourceUrl.parse(value.substring(index, end));
+ if (url != null && !url.framework) {
+ Resource resource;
+ if (url.create) {
+ resource = declareResource(url.type, url.name, attr);
+ } else {
+ resource = addResource(url.type, url.name, null);
+ }
+ from.addReference(resource);
+ }
+
+ index = end;
+ }
+ }
+ }
+
+ // Android Wear. We *could* limit ourselves to only doing this in files
+ // referenced from a manifest meta-data element, e.g.
+ // <meta-data android:name="com.google.android.wearable.beta.app"
+ // android:resource="@xml/wearable_app_desc"/>
+ // but given that that property has "beta" in the name, it seems likely
+ // to change and therefore hardcoding it for that key risks breakage
+ // in the future.
+ if ("rawPathResId".equals(element.getTagName())) {
+ StringBuilder sb = new StringBuilder();
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Element.TEXT_NODE
+ || child.getNodeType() == Element.CDATA_SECTION_NODE) {
+ sb.append(child.getNodeValue());
+ }
+ }
+ if (sb.length() > 0) {
+ Resource resource = getResource(ResourceType.RAW, sb.toString().trim());
+ from.addReference(resource);
+ }
+ }
+ } else {
+ // Look for keep attributes everywhere else since they don't require a source
+ recordToolsAttributes(element.getAttributeNodeNS(TOOLS_URI, ATTR_KEEP));
+ recordToolsAttributes(element.getAttributeNodeNS(TOOLS_URI, ATTR_DISCARD));
+ recordToolsAttributes(element.getAttributeNodeNS(TOOLS_URI, ATTR_SHRINK_MODE));
+ }
+
+ if (folderType == ResourceFolderType.VALUES) {
+
+ Resource definition = null;
+ ResourceType type = getResourceType(element);
+ if (type != null) {
+ String name = getFieldName(element);
+ if (type == ResourceType.PUBLIC) {
+ String typeName = element.getAttribute(ATTR_TYPE);
+ if (!typeName.isEmpty()) {
+ type = ResourceType.getEnum(typeName);
+ if (type != null) {
+ definition = declareResource(type, name, element);
+ definition.setPublic(true);
+ }
+ }
+ } else {
+ definition = declareResource(type, name, element);
+ }
+ }
+ if (definition != null) {
+ from = definition;
+ }
+
+ String tagName = element.getTagName();
+ if (TAG_STYLE.equals(tagName)) {
+ if (element.hasAttribute(ATTR_PARENT)) {
+ String parent = element.getAttribute(ATTR_PARENT);
+ if (!parent.isEmpty() && !parent.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)
+ && !parent.startsWith(PREFIX_ANDROID)) {
+ String parentStyle = parent;
+ if (!parentStyle.startsWith(STYLE_RESOURCE_PREFIX)) {
+ parentStyle = STYLE_RESOURCE_PREFIX + parentStyle;
+ }
+ Resource ps = getResourceFromUrl(
+ LintUtils.getFieldName(parentStyle));
+ if (ps != null && definition != null) {
+ ps.addReference(definition);
+ definition.addReference(ps);
+ }
+ } else if (definition != null) {
+ // Extending a builtin theme: treat these as used
+ markReachable(definition);
+ }
+ } else {
+ // Implicit parent styles by name
+ String name = getFieldName(element);
+ while (true) {
+ int index = name.lastIndexOf('_');
+ if (index != -1) {
+ name = name.substring(0, index);
+ Resource ps = getResourceFromUrl(
+ STYLE_RESOURCE_PREFIX + LintUtils.getFieldName(name));
+ if (ps != null && definition != null) {
+ ps.addReference(definition);
+ definition.addReference(ps);
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ if (TAG_ITEM.equals(tagName)) {
+ // In style? If so the name: attribute can be a reference
+ if (element.getParentNode() != null
+ && element.getParentNode().getNodeName().equals(TAG_STYLE)) {
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (!name.isEmpty() && !name.startsWith("android:")) {
+ Resource resource = getResource(ResourceType.ATTR, name);
+ if (definition == null) {
+ Element style = (Element) element.getParentNode();
+ definition = getResource(style);
+ if (definition != null) {
+ from = definition;
+ definition.addReference(resource);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
+ String text = node.getNodeValue().trim();
+ // Why are we calling getFieldName here? That doesn't make sense! for styles I guess
+ Resource textResource = getResourceFromUrl(
+ LintUtils.getFieldName(text));
+ if (textResource != null && from != null) {
+ from.addReference(textResource);
+ }
+ }
+
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ recordResourceReferences(folderType, child, from);
+ }
+ }
+
+ public void tokenizeHtml(@Nullable Resource from, @NonNull String html) {
+ // Look for
+ // (1) URLs of the form /android_res/drawable/foo.ext
+ // which we will use to keep R.drawable.foo
+ // and
+ // (2) Filenames. If the web content is loaded with something like
+ // WebView.loadDataWithBaseURL("file:///android_res/drawable/", ...)
+ // this is similar to Resources#getIdentifier handling where all
+ // *potentially* aliased filenames are kept to play it safe.
+
+ // Simple HTML tokenizer
+ int length = html.length();
+ final int STATE_TEXT = 1;
+ final int STATE_SLASH = 2;
+ final int STATE_ATTRIBUTE_NAME = 3;
+ final int STATE_BEFORE_TAG = 4;
+ final int STATE_IN_TAG = 5;
+ final int STATE_BEFORE_ATTRIBUTE = 6;
+ final int STATE_ATTRIBUTE_BEFORE_EQUALS = 7;
+ final int STATE_ATTRIBUTE_AFTER_EQUALS = 8;
+ final int STATE_ATTRIBUTE_VALUE_NONE = 9;
+ final int STATE_ATTRIBUTE_VALUE_SINGLE = 10;
+ final int STATE_ATTRIBUTE_VALUE_DOUBLE = 11;
+ final int STATE_CLOSE_TAG = 12;
+
+ int state = STATE_TEXT;
+ int offset = 0;
+ int valueStart = 0;
+ int tagStart = 0;
+ String tag = null;
+ String attribute = null;
+ int attributeStart = 0;
+ int prev = -1;
+ while (offset < length) {
+ if (offset == prev) {
+ // Purely here to prevent potential bugs in the state machine from looping
+ // infinitely
+ offset++;
+ }
+ prev = offset;
+
+
+ char c = html.charAt(offset);
+
+ // MAke sure I handle doctypes properly.
+ // Make sure I handle cdata properly.
+ // Oh and what about <style> tags? tokenize everything inside as CSS!
+ // ANd <script> tag content as js!
+ switch (state) {
+ case STATE_TEXT: {
+ if (c == '<') {
+ state = STATE_SLASH;
+ offset++;
+ continue;
+ }
+
+ // Other text is just ignored
+ offset++;
+ break;
+ }
+
+ case STATE_SLASH: {
+ if (c == '!') {
+ if (html.startsWith("!--", offset)) {
+ // Comment
+ int end = html.indexOf("-->", offset + 3);
+ if (end == -1) {
+ offset = length;
+ break;
+ }
+ offset = end + 3;
+ continue;
+ } else if (html.startsWith("![CDATA[", offset)) {
+ // Skip CDATA text content; HTML text is irrelevant to this tokenizer
+ // anyway
+ int end = html.indexOf("]]>", offset + 8);
+ if (end == -1) {
+ offset = length;
+ break;
+ }
+ offset = end + 3;
+ continue;
+ }
+ } else if (c == '/') {
+ state = STATE_CLOSE_TAG;
+ offset++;
+ continue;
+ } else if (c == '?') {
+ // XML Prologue
+ int end = html.indexOf('>', offset + 2);
+ if (end == -1) {
+ offset = length;
+ break;
+ }
+ offset = end + 1;
+ continue;
+ }
+ state = STATE_IN_TAG;
+ tagStart = offset;
+ break;
+ }
+
+ case STATE_CLOSE_TAG: {
+ if (c == '>') {
+ state = STATE_TEXT;
+ }
+ offset++;
+ break;
+ }
+
+ case STATE_BEFORE_TAG: {
+ if (!Character.isWhitespace(c)) {
+ state = STATE_IN_TAG;
+ tagStart = offset;
+ }
+ // (For an end tag we'll include / in the tag name here)
+ offset++;
+ break;
+ }
+ case STATE_IN_TAG: {
+ if (Character.isWhitespace(c)) {
+ state = STATE_BEFORE_ATTRIBUTE;
+ tag = html.substring(tagStart, offset).trim();
+ } else if (c == '>') {
+ tag = html.substring(tagStart, offset).trim();
+ endHtmlTag(from, html, offset, tag);
+ state = STATE_TEXT;
+ }
+ offset++;
+ break;
+ }
+ case STATE_BEFORE_ATTRIBUTE: {
+ if (c == '>') {
+ endHtmlTag(from, html, offset, tag);
+ state = STATE_TEXT;
+ } else //noinspection StatementWithEmptyBody
+ if (c == '/') {
+ // we expect an '>' next to close the tag
+ } else if (!Character.isWhitespace(c)) {
+ state = STATE_ATTRIBUTE_NAME;
+ attributeStart = offset;
+ }
+ offset++;
+ break;
+ }
+ case STATE_ATTRIBUTE_NAME: {
+ if (c == '>') {
+ endHtmlTag(from, html, offset, tag);
+ state = STATE_TEXT;
+ } else if (c == '=') {
+ attribute = html.substring(attributeStart, offset);
+ state = STATE_ATTRIBUTE_AFTER_EQUALS;
+ } else if (Character.isWhitespace(c)) {
+ attribute = html.substring(attributeStart, offset);
+ state = STATE_ATTRIBUTE_BEFORE_EQUALS;
+ }
+ offset++;
+ break;
+ }
+ case STATE_ATTRIBUTE_BEFORE_EQUALS: {
+ if (c == '=') {
+ state = STATE_ATTRIBUTE_AFTER_EQUALS;
+ } else if (c == '>') {
+ endHtmlTag(from, html, offset, tag);
+ state = STATE_TEXT;
+ } else if (!Character.isWhitespace(c)) {
+ // Attribute value not specified (used for some boolean attributes)
+ state = STATE_ATTRIBUTE_NAME;
+ attributeStart = offset;
+ }
+ offset++;
+ break;
+ }
+
+ case STATE_ATTRIBUTE_AFTER_EQUALS: {
+ if (c == '\'') {
+ // a='b'
+ state = STATE_ATTRIBUTE_VALUE_SINGLE;
+ valueStart = offset + 1;
+ } else if (c == '"') {
+ // a="b"
+ state = STATE_ATTRIBUTE_VALUE_DOUBLE;
+ valueStart = offset + 1;
+ } else if (!Character.isWhitespace(c)) {
+ // a=b
+ state = STATE_ATTRIBUTE_VALUE_NONE;
+ valueStart = offset + 1;
+ }
+ offset++;
+ break;
+ }
+
+ case STATE_ATTRIBUTE_VALUE_SINGLE: {
+ if (c == '\'') {
+ state = STATE_BEFORE_ATTRIBUTE;
+ recordHtmlAttributeValue(from, tag, attribute,
+ html.substring(valueStart, offset));
+ }
+ offset++;
+ break;
+ }
+ case STATE_ATTRIBUTE_VALUE_DOUBLE: {
+ if (c == '"') {
+ state = STATE_BEFORE_ATTRIBUTE;
+ recordHtmlAttributeValue(from, tag, attribute,
+ html.substring(valueStart, offset));
+ }
+ offset++;
+ break;
+ }
+ case STATE_ATTRIBUTE_VALUE_NONE: {
+ if (c == '>') {
+ recordHtmlAttributeValue(from, tag, attribute,
+ html.substring(valueStart, offset));
+ endHtmlTag(from, html, offset, tag);
+ state = STATE_TEXT;
+ } else if (Character.isWhitespace(c)) {
+ state = STATE_BEFORE_ATTRIBUTE;
+ recordHtmlAttributeValue(from, tag, attribute,
+ html.substring(valueStart, offset));
+ }
+ offset++;
+ break;
+ }
+ default:
+ assert false : state;
+ }
+ }
+ }
+
+ private void endHtmlTag(@Nullable Resource from, @NonNull String html, int offset,
+ @Nullable String tag) {
+ if ("script".equals(tag)) {
+ int end = html.indexOf("</script>", offset + 1);
+ if (end != -1) {
+ // Attempt to tokenize the text as JavaScript
+ String js = html.substring(offset + 1, end);
+ tokenizeJs(from, js);
+ }
+ } else if ("style".equals(tag)) {
+ int end = html.indexOf("</style>", offset + 1);
+ if (end != -1) {
+ // Attempt to tokenize the text as CSS
+ String css = html.substring(offset + 1, end);
+ tokenizeCss(from, css);
+ }
+ }
+ }
+
+ public void tokenizeJs(@Nullable Resource from, @NonNull String js) {
+ // Simple JavaScript tokenizer: only looks for literal strings,
+ // and records those as string references
+ int length = js.length();
+ final int STATE_INIT = 1;
+ final int STATE_SLASH = 2;
+ final int STATE_STRING_DOUBLE = 3;
+ final int STATE_STRING_DOUBLE_QUOTED = 4;
+ final int STATE_STRING_SINGLE = 5;
+ final int STATE_STRING_SINGLE_QUOTED = 6;
+
+ int state = STATE_INIT;
+ int offset = 0;
+ int stringStart = 0;
+ int prev = -1;
+ while (offset < length) {
+ if (offset == prev) {
+ // Purely here to prevent potential bugs in the state machine from looping
+ // infinitely
+ offset++;
+ }
+ prev = offset;
+
+ char c = js.charAt(offset);
+ switch (state) {
+ case STATE_INIT: {
+ if (c == '/') {
+ state = STATE_SLASH;
+ } else if (c == '"') {
+ stringStart = offset + 1;
+ state = STATE_STRING_DOUBLE;
+ } else if (c == '\'') {
+ stringStart = offset + 1;
+ state = STATE_STRING_SINGLE;
+ }
+ offset++;
+ break;
+ }
+ case STATE_SLASH: {
+ if (c == '*') {
+ // Comment block
+ state = STATE_INIT;
+ int end = js.indexOf("*/", offset + 1);
+ if (end == -1) {
+ offset = length; // unterminated
+ break;
+ }
+ offset = end + 2;
+ continue;
+ } else if (c == '/') {
+ // Line comment
+ state = STATE_INIT;
+ int end = js.indexOf('\n', offset + 1);
+ if (end == -1) {
+ offset = length;
+ break;
+ }
+ offset = end + 1;
+ continue;
+ } else {
+ // division - just continue
+ state = STATE_INIT;
+ offset++;
+ break;
+ }
+ }
+ case STATE_STRING_DOUBLE: {
+ if (c == '"') {
+ recordJsString(js.substring(stringStart, offset));
+ state = STATE_INIT;
+ } else if (c == '\\') {
+ state = STATE_STRING_DOUBLE_QUOTED;
+ }
+ offset++;
+ break;
+ }
+ case STATE_STRING_DOUBLE_QUOTED: {
+ state = STATE_STRING_DOUBLE;
+ offset++;
+ break;
+ }
+ case STATE_STRING_SINGLE: {
+ if (c == '\'') {
+ recordJsString(js.substring(stringStart, offset));
+ state = STATE_INIT;
+ } else if (c == '\\') {
+ state = STATE_STRING_SINGLE_QUOTED;
+ }
+ offset++;
+ break;
+ }
+ case STATE_STRING_SINGLE_QUOTED: {
+ state = STATE_STRING_SINGLE;
+ offset++;
+ break;
+ }
+ default:
+ assert false : state;
+ }
+ }
+ }
+
+ public void tokenizeCss(@Nullable Resource from, @NonNull String css) {
+ // Simple CSS tokenizer: Only looks for URL references, and records those
+ // filenames. Skips everything else (unrelated to images).
+ int length = css.length();
+ final int STATE_INIT = 1;
+ final int STATE_SLASH = 2;
+ int state = STATE_INIT;
+ int offset = 0;
+ int prev = -1;
+ while (offset < length) {
+ if (offset == prev) {
+ // Purely here to prevent potential bugs in the state machine from looping
+ // infinitely
+ offset++;
+ }
+ prev = offset;
+
+ char c = css.charAt(offset);
+ switch (state) {
+ case STATE_INIT: {
+ if (c == '/') {
+ state = STATE_SLASH;
+ } else if (c == 'u' && css.startsWith("url(", offset) && offset > 0) {
+ char prevChar = css.charAt(offset-1);
+ if (Character.isWhitespace(prevChar) || prevChar == ':') {
+ int end = css.indexOf(')', offset);
+ offset += 4; // skip url(
+ while (offset < length && Character.isWhitespace(css.charAt(offset))) {
+ offset++;
+ }
+ if (end != -1 && end > offset + 1) {
+ while (end > offset
+ && Character.isWhitespace(css.charAt(end - 1))) {
+ end--;
+ }
+ if ((css.charAt(offset) == '"'
+ && css.charAt(end - 1) == '"')
+ || (css.charAt(offset) == '\''
+ && css.charAt(end - 1) == '\'')) {
+ // Strip " or '
+ offset++;
+ end--;
+ }
+ recordCssUrl(from, css.substring(offset, end).trim());
+ }
+ offset = end + 1;
+ continue;
+ }
+
+ }
+ offset++;
+ break;
+ }
+ case STATE_SLASH: {
+ if (c == '*') {
+ // CSS comment? Skip the whole block rather than staying within the
+ // character tokenizer.
+ int end = css.indexOf("*/", offset + 1);
+ if (end == -1) {
+ offset = length;
+ break;
+ }
+ offset = end + 2;
+ continue;
+ }
+ state = STATE_INIT;
+ offset++;
+ break;
+ }
+ default:
+ assert false : state;
+ }
+ }
+ }
+
+ private static byte[] sAndroidResBytes;
+
+ /** Look through binary/unknown files looking for resource URLs */
+ public void tokenizeUnknownBinary(@Nullable Resource from, @NonNull File file) {
+ try {
+ if (sAndroidResBytes == null) {
+ sAndroidResBytes = ANDROID_RES.getBytes(SdkConstants.UTF_8);
+ }
+ byte[] bytes = Files.toByteArray(file);
+ int index = 0;
+ while (index != -1) {
+ index = indexOf(bytes, sAndroidResBytes, index);
+ if (index != -1) {
+ index += sAndroidResBytes.length;
+
+ // Find the end of the URL
+ int begin = index;
+ int end = begin;
+ for (; end < bytes.length; end++) {
+ byte c = bytes[end];
+ if (c != '/' && !Character.isJavaIdentifierPart((char)c)) {
+ // android_res/raw/my_drawable.png => @raw/my_drawable
+ String url = "@" + new String(bytes, begin, end - begin, UTF_8);
+ Resource resource = getResourceFromUrl(url);
+ if (resource != null) {
+ if (from != null) {
+ from.addReference(resource);
+ } else {
+ markReachable(resource);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Returns the index of the given target array in the first array, looking from the given
+ * index
+ */
+ private static int indexOf(byte[] array, byte[] target, int fromIndex) {
+ outer:
+ for (int i = fromIndex; i < array.length - target.length + 1; i++) {
+ for (int j = 0; j < target.length; j++) {
+ if (array[i + j] != target[j]) {
+ continue outer;
+ }
+ }
+ return i;
+ }
+ return -1;
+ }
+
+ /** Look through text files of unknown structure looking for resource URLs */
+ private void tokenizeUnknownText(@NonNull String text) {
+ int index = 0;
+ while (index != -1) {
+ index = text.indexOf(ANDROID_RES, index);
+ if (index != -1) {
+ index += ANDROID_RES.length();
+
+ // Find the end of the URL
+ int begin = index;
+ int end = begin;
+ int length = text.length();
+ for (; end < length; end++) {
+ char c = text.charAt(end);
+ if (c != '/' && !Character.isJavaIdentifierPart(c)) {
+ // android_res/raw/my_drawable.png => @raw/my_drawable
+ markReachable(getResourceFromUrl("@" + text.substring(begin, end)));
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /** Adds the resource identifiers found in the given Java source code into the reference map */
+ public void tokenizeJavaCode(@NonNull String s) {
+ if (s.length() <= 2) {
+ return;
+ }
+
+ // Scan looking for R.{type}.name identifiers
+ // Extremely simple state machine which just avoids comments, line comments
+ // and strings, and outside of that records any R. identifiers it finds
+ int index = 0;
+ int length = s.length();
+
+ char c;
+ char next;
+ for (; index < length; index++) {
+ c = s.charAt(index);
+ if (index == length - 1) {
+ break;
+ }
+ next = s.charAt(index + 1);
+ if (Character.isWhitespace(c)) {
+ continue;
+ }
+ if (c == '/') {
+ if (next == '*') {
+ // Block comment
+ while (index < length - 2) {
+ if (s.charAt(index) == '*' && s.charAt(index + 1) == '/') {
+ break;
+ }
+ index++;
+ }
+ index++;
+ } else if (next == '/') {
+ // Line comment
+ while (index < length && s.charAt(index) != '\n') {
+ index++;
+ }
+ }
+ } else if (c == '\'') {
+ // Character
+ if (next == '\\') {
+ // Skip '\c'
+ index += 2;
+ } else {
+ // Skip 'c'
+ index++;
+ }
+ } else if (c == '\"') {
+ // String: Skip to end
+ index++;
+ while (index < length - 1) {
+ char t = s.charAt(index);
+ if (t == '\\') {
+ index++;
+ } else if (t == '"') {
+ break;
+ }
+ index++;
+ }
+ } else if (c == 'R' && next == '.') {
+ // This might be a pattern
+ int begin = index;
+ index += 2;
+ while (index < length) {
+ char t = s.charAt(index);
+ if (t == '.') {
+ String typeName = s.substring(begin + 2, index);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type != null) {
+ index++;
+ begin = index;
+ while (index < length &&
+ Character.isJavaIdentifierPart(s.charAt(index))) {
+ index++;
+ }
+ if (index > begin) {
+ String name = s.substring(begin, index);
+ Resource resource = addResource(type, name, null);
+ ResourceUsageModel.markReachable(resource);
+ }
+ }
+ index--;
+ break;
+ } else if (!Character.isJavaIdentifierStart(t)) {
+ break;
+ }
+ index++;
+ }
+ } else if (Character.isJavaIdentifierPart(c)) {
+ // Skip to the end of the identifier
+ while (index < length && Character.isJavaIdentifierPart(s.charAt(index))) {
+ index++;
+ }
+ // Back up so the next character can be checked to see if it's a " etc
+ index--;
+ } // else just punctuation/operators ( ) ; etc
+ }
+ }
+
+ protected void referencedString(@NonNull String string) {
+ }
+
+ private void recordCssUrl(@Nullable Resource from, @NonNull String value) {
+ if (!referencedUrl(from, value)) {
+ referencedString(value);
+ }
+ }
+
+ /**
+ * See if the given URL is a URL that we can resolve to a specific resource; if so,
+ * record it and return true, otherwise returns false.
+ */
+ private boolean referencedUrl(@Nullable Resource from, @NonNull String url) {
+ Resource resource = getResourceFromFilePath(url);
+ if (resource == null && url.indexOf('/') == -1) {
+ // URLs are often within the raw folder
+ resource = getResource(ResourceType.RAW,
+ LintUtils.getFieldName(LintUtils.getBaseName(url)));
+ }
+ if (resource != null) {
+ if (from != null) {
+ from.addReference(resource);
+ } else {
+ // We don't have an inclusion context, so just assume this resource is reachable
+ markReachable(resource);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private void recordHtmlAttributeValue(@Nullable Resource from, @Nullable String tagName,
+ @Nullable String attribute, @NonNull String value) {
+ if ("href".equals(attribute) || "src".equals(attribute)) {
+ // In general we'd need to unescape the HTML here (e.g. remove entities) but
+ // those wouldn't be valid characters in the resource name anyway
+ if (!referencedUrl(from, value)) {
+ referencedString(value);
+ }
+
+ // If this document includes another, record the reachability of that script/resource
+ if (from != null) {
+ from.addReference(getResourceFromFilePath(attribute));
+ }
+ }
+ }
+
+ private void recordJsString(@NonNull String string) {
+ referencedString(string);
+ }
+
+ public List<Resource> getResources() {
+ return mResources;
+ }
+
+ @NonNull
+ public Collection<Map<String, Resource>> getResourceMaps() {
+ return mTypeToName.values();
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RestrictionsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RestrictionsDetector.java
new file mode 100644
index 0000000..381e7c4
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RestrictionsDetector.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_TITLE;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.STRING_PREFIX;
+import static com.android.SdkConstants.XMLNS_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Maps;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Check which makes sure that an application restrictions file is correct.
+ * The rules are specified in
+ * https://developer.android.com/reference/android/content/RestrictionsManager.html
+ */
+public class RestrictionsDetector extends ResourceXmlDetector implements JavaScanner {
+
+ // Copied from Google Play store's AppRestrictionBuilder
+ @VisibleForTesting static final int MAX_NESTING_DEPTH = 20;
+ // Copied from Google Play store's AppRestrictionBuilder
+ @VisibleForTesting static final int MAX_NUMBER_OF_NESTED_RESTRICTIONS = 1000;
+ /**
+ * Validation of {@code <restrictions>} XML elements
+ */
+ public static final Issue ISSUE = Issue.create(
+ "ValidRestrictions", //$NON-NLS-1$
+ "Invalid Restrictions Descriptor",
+
+ "Ensures that an applications restrictions XML file is properly formed",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.FATAL,
+ new Implementation(
+ RestrictionsDetector.class,
+ Scope.RESOURCE_FILE_SCOPE))
+ .addMoreInfo("https://developer.android.com/reference/android/content/RestrictionsManager.html");
+
+ static final String TAG_RESTRICTIONS = "restrictions";
+ static final String TAG_RESTRICTION = "restriction";
+ static final String ATTR_RESTRICTION_TYPE = "restrictionType";
+ static final String ATTR_KEY = "key";
+ static final String ATTR_DESCRIPTION = "description";
+ static final String VALUE_BUNDLE = "bundle";
+ static final String VALUE_BUNDLE_ARRAY = "bundle_array";
+ static final String VALUE_CHOICE = "choice";
+ static final String VALUE_MULTI_SELECT = "multi-select";
+ static final String VALUE_ENTRIES = "entries";
+ static final String VALUE_ENTRY_VALUES = "entryValues";
+ static final String VALUE_HIDDEN = "hidden";
+ static final String VALUE_DEFAULT_VALUE = "defaultValue";
+ static final String VALUE_INTEGER = "integer";
+
+ /**
+ * Constructs a new {@link RestrictionsDetector}
+ */
+ public RestrictionsDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.XML;
+ }
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ Element root = document.getDocumentElement();
+ if (root == null) {
+ return;
+ }
+ if (!TAG_RESTRICTIONS.equals(root.getTagName())) {
+ return;
+ }
+
+ Map<String, Element> keys = Maps.newHashMap();
+ validateNestedRestrictions(context, root, null, keys, 0);
+ }
+
+ /** Validates the {@code <restriction>} <b>children</b> of the given element */
+ private static void validateNestedRestrictions(
+ @NonNull XmlContext context,
+ @NonNull Element element,
+ @Nullable String restrictionType,
+ @NonNull Map<String, Element> keys,
+ int depth) {
+ assert depth == 0 || restrictionType != null;
+
+ List<Element> children = LintUtils.getChildren(element);
+
+ // Only restrictions of type bundle and bundle_array can have one or multiple nested
+ // restriction elements.
+ if (depth == 0
+ || restrictionType.equals(VALUE_BUNDLE)
+ || restrictionType.equals(VALUE_BUNDLE_ARRAY)) {
+ // Bundle and bundle array should not have a default value
+ Attr defaultValue = element.getAttributeNodeNS(ANDROID_URI, VALUE_DEFAULT_VALUE);
+ if (defaultValue != null) {
+ context.report(ISSUE, element, context.getLocation(defaultValue),
+ String.format("Restriction type `%1$s` should not have a default value",
+ restrictionType));
+ }
+ for (Element child : children) {
+ if (verifyRestrictionTagName(context, child)) {
+ validateRestriction(context, child, depth + 1, keys);
+ }
+ }
+
+ //noinspection StatementWithEmptyBody
+ if (depth == 0) {
+ // It's okay to have <restrictions />
+ } else if (restrictionType.equals(VALUE_BUNDLE_ARRAY)) {
+ if (children.size() != 1) {
+ context.report(ISSUE, element, context.getLocation(element),
+ "Expected exactly one child for restriction of type `bundle_array`");
+ }
+ } else {
+ assert restrictionType.equals(VALUE_BUNDLE);
+ if (children.isEmpty()) {
+ context.report(ISSUE, element, context.getLocation(element),
+ "Restriction type `bundle` should have at least one nested "
+ + "restriction");
+
+ }
+ }
+
+ if (children.size() > MAX_NUMBER_OF_NESTED_RESTRICTIONS) {
+ context.report(ISSUE, element, context.getLocation(element), String.format(
+ // TODO: Reference Google Play store restriction here in error message,
+ // e.g. that violating this will cause APK to be rejected?
+ "Invalid nested restriction: too many nested restrictions "
+ + "(was %1$d, max %2$d)",
+ children.size(), MAX_NUMBER_OF_NESTED_RESTRICTIONS));
+ } else if (depth > MAX_NESTING_DEPTH) {
+ // Same comment as for MAX_NUMBER_OF_NESTED_RESTRICTIONS: include source?
+ context.report(ISSUE, element, context.getLocation(element), String.format(
+ "Invalid nested restriction: nesting depth %1$d too large (max %2$d",
+ depth, MAX_NESTING_DEPTH));
+ }
+ } else if (!children.isEmpty()) {
+ context.report(ISSUE, element, context.getNameLocation(element),
+ "Only restrictions of type `bundle` and `bundle_array` can have "
+ + "one or multiple nested restriction elements");
+ }
+ }
+
+ /** Validates a {@code <restriction>} element (and recurses to validate the children) */
+ private static void validateRestriction(@NonNull XmlContext context, Node node, int depth,
+ Map<String, Element> keys) {
+
+ if (node.getNodeType() != Node.ELEMENT_NODE) {
+ return;
+ }
+ Element element = (Element)node;
+
+ // key, title and restrictionType are mandatory.
+ String restrictionType = checkRequiredAttribute(context, element, ATTR_RESTRICTION_TYPE);
+ String key = checkRequiredAttribute(context, element, ATTR_KEY);
+ String title = checkRequiredAttribute(context, element, ATTR_TITLE);
+ if (restrictionType == null || key == null || title == null) {
+ return;
+ }
+
+ // You use each restriction's android:key attribute to read its value from a
+ // restrictions bundle. For this reason, each restriction must have a unique key string,
+ // and the string cannot be localized. It must be specified with a string literal.
+ if (key.startsWith(STRING_PREFIX)) {
+ Attr attribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_KEY);
+ Location valueLocation = context.getValueLocation(attribute);
+ context.report(ISSUE, element, valueLocation,
+ "Keys cannot be localized, they should be specified with a string literal");
+ } else if (keys.containsKey(key)) {
+ Attr thisAttribute = element.getAttributeNodeNS(ANDROID_URI, ATTR_KEY);
+ Location location = context.getValueLocation(
+ thisAttribute);
+ Element prev = keys.get(key);
+ Attr prevAttribute = prev.getAttributeNodeNS(ANDROID_URI, ATTR_KEY);
+ Location previousLocation = context.getValueLocation(prevAttribute);
+ previousLocation.setMessage("Previous use of key here");
+ location.setSecondary(previousLocation);
+ context.report(ISSUE, element, location, String.format("Duplicate key `%1$s`", key));
+ } else {
+ keys.put(key, element);
+ }
+
+ if (restrictionType.equals(VALUE_CHOICE) || restrictionType.equals(VALUE_MULTI_SELECT)) {
+ // entries and entryValues are required if restrictionType is choice or multi-select.
+ //noinspection unused
+ boolean ok = // deliberate short circuit evaluation
+ checkRequiredAttribute(context, element, VALUE_ENTRIES) != null
+ || checkRequiredAttribute(context, element, VALUE_ENTRY_VALUES) != null;
+ } else if (restrictionType.equals(VALUE_HIDDEN)) {
+ // hidden type must have a defaultValue
+ checkRequiredAttribute(context, element, VALUE_DEFAULT_VALUE);
+ } else if (restrictionType.equals(VALUE_INTEGER)) {
+ Attr defaultValue = element.getAttributeNodeNS(ANDROID_URI, VALUE_DEFAULT_VALUE);
+ if (defaultValue != null && !defaultValue.getValue().startsWith(PREFIX_RESOURCE_REF)) {
+ try {
+ //noinspection ResultOfMethodCallIgnored
+ Integer.decode(defaultValue.getValue());
+ } catch (NumberFormatException e) {
+ context.report(ISSUE, element, context.getValueLocation(defaultValue),
+ "Invalid number");
+ }
+ }
+ }
+
+ validateNestedRestrictions(context, element, restrictionType, keys, depth);
+ }
+
+ /**
+ * Makes sure that the given element corresponds to a restriction tag, and if not, reports
+ * it and return false */
+ private static boolean verifyRestrictionTagName(@NonNull XmlContext context, Element element) {
+ String tagName = element.getTagName();
+ if (!tagName.equals(TAG_RESTRICTION)) {
+ context.report(ISSUE, element, context.getNameLocation(element),
+ String.format("Unexpected tag `<%1$s>`, expected `<%2$s>`",
+ tagName, TAG_RESTRICTION));
+ return false;
+ }
+ return true;
+ }
+
+ private static String checkRequiredAttribute(@NonNull XmlContext context, Element element,
+ String attribute) {
+ if (!element.hasAttributeNS(ANDROID_URI, attribute)) {
+ String prefix = element.getOwnerDocument().lookupNamespaceURI(ANDROID_URI);
+ if (prefix == null) {
+ Element root = element.getOwnerDocument().getDocumentElement();
+ NamedNodeMap attributes = root.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr a = (Attr)attributes.item(i);
+ if (a.getName().startsWith(XMLNS_PREFIX) &&
+ ANDROID_URI.equals(a.getValue())) {
+ prefix = a.getName().substring(XMLNS_PREFIX.length());
+ break;
+ }
+ }
+ }
+ if (prefix != null) {
+ attribute = prefix + ':' + attribute;
+ }
+ context.report(ISSUE, element, context.getLocation(element),
+ // TODO: Include namespace prefix?
+ String.format("Missing required attribute `%1$s`", attribute));
+ return null;
+ }
+ return element.getAttributeNS(ANDROID_URI, attribute);
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
new file mode 100644
index 0000000..a004cd0
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_DRAWABLE_END;
+import static com.android.SdkConstants.ATTR_DRAWABLE_LEFT;
+import static com.android.SdkConstants.ATTR_DRAWABLE_RIGHT;
+import static com.android.SdkConstants.ATTR_DRAWABLE_START;
+import static com.android.SdkConstants.ATTR_GRAVITY;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_END;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_START;
+import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_START;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_END_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
+import static com.android.SdkConstants.ATTR_LAYOUT_TO_START_OF;
+import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_END;
+import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT;
+import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT;
+import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_START;
+import static com.android.SdkConstants.ATTR_PADDING;
+import static com.android.SdkConstants.ATTR_PADDING_END;
+import static com.android.SdkConstants.ATTR_PADDING_LEFT;
+import static com.android.SdkConstants.ATTR_PADDING_RIGHT;
+import static com.android.SdkConstants.ATTR_PADDING_START;
+import static com.android.SdkConstants.GRAVITY_VALUE_END;
+import static com.android.SdkConstants.GRAVITY_VALUE_LEFT;
+import static com.android.SdkConstants.GRAVITY_VALUE_RIGHT;
+import static com.android.SdkConstants.GRAVITY_VALUE_START;
+import static com.android.SdkConstants.TAG_APPLICATION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.EnumConstant;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Identifier;
+import lombok.ast.ImportDeclaration;
+import lombok.ast.Node;
+import lombok.ast.Select;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Check which looks for RTL issues (right-to-left support) in layouts
+ */
+public class RtlDetector extends LayoutDetector implements Detector.JavaScanner {
+
+ @SuppressWarnings("unchecked")
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ RtlDetector.class,
+ EnumSet.of(Scope.RESOURCE_FILE, Scope.JAVA_FILE, Scope.MANIFEST),
+ Scope.RESOURCE_FILE_SCOPE,
+ Scope.JAVA_FILE_SCOPE,
+ Scope.MANIFEST_SCOPE
+ );
+
+ public static final Issue USE_START = Issue.create(
+ "RtlHardcoded", //$NON-NLS-1$
+ "Using left/right instead of start/end attributes",
+
+ "Using `Gravity#LEFT` and `Gravity#RIGHT` can lead to problems when a layout is " +
+ "rendered in locales where text flows from right to left. Use `Gravity#START` " +
+ "and `Gravity#END` instead. Similarly, in XML `gravity` and `layout_gravity` " +
+ "attributes, use `start` rather than `left`.\n" +
+ "\n" +
+ "For XML attributes such as paddingLeft and `layout_marginLeft`, use `paddingStart` " +
+ "and `layout_marginStart`. *NOTE*: If your `minSdkVersion` is less than 17, you should " +
+ "add *both* the older left/right attributes *as well as* the new start/right " +
+ "attributes. On older platforms, where RTL is not supported and the start/right " +
+ "attributes are unknown and therefore ignored, you need the older left/right " +
+ "attributes. There is a separate lint check which catches that type of error.\n" +
+ "\n" +
+ "(Note: For `Gravity#LEFT` and `Gravity#START`, you can use these constants even " +
+ "when targeting older platforms, because the `start` bitmask is a superset of the " +
+ "`left` bitmask. Therefore, you can use `gravity=\"start\"` rather than " +
+ "`gravity=\"left|start\"`.)",
+
+ Category.RTL, 5, Severity.WARNING, IMPLEMENTATION);
+
+ public static final Issue COMPAT = Issue.create(
+ "RtlCompat", //$NON-NLS-1$
+ "Right-to-left text compatibility issues",
+
+ "API 17 adds a `textAlignment` attribute to specify text alignment. However, " +
+ "if you are supporting older versions than API 17, you must *also* specify a " +
+ "gravity or layout_gravity attribute, since older platforms will ignore the " +
+ "`textAlignment` attribute.",
+
+ Category.RTL, 6, Severity.ERROR, IMPLEMENTATION);
+
+ public static final Issue SYMMETRY = Issue.create(
+ "RtlSymmetry", //$NON-NLS-1$
+ "Padding and margin symmetry",
+
+ "If you specify padding or margin on the left side of a layout, you should " +
+ "probably also specify padding on the right side (and vice versa) for " +
+ "right-to-left layout symmetry.",
+
+ Category.RTL, 6, Severity.WARNING, IMPLEMENTATION);
+
+
+ public static final Issue ENABLED = Issue.create(
+ "RtlEnabled", //$NON-NLS-1$
+ "Using RTL attributes without enabling RTL support",
+
+ "To enable right-to-left support, when running on API 17 and higher, you must " +
+ "set the `android:supportsRtl` attribute in the manifest `<application>` element.\n" +
+ "\n" +
+ "If you have started adding RTL attributes, but have not yet finished the " +
+ "migration, you can set the attribute to false to satisfy this lint check.",
+
+ Category.RTL, 3, Severity.WARNING, IMPLEMENTATION);
+
+ /* TODO:
+ public static final Issue FIELD = Issue.create(
+ "RtlFieldAccess", //$NON-NLS-1$
+ "Accessing margin and padding fields directly",
+
+ "Modifying the padding and margin constants in view objects directly is " +
+ "problematic when using RTL support, since it can lead to inconsistent states. You " +
+ "*must* use the corresponding setter methods instead (`View#setPadding` etc).",
+
+ Category.RTL, 3, Severity.WARNING, IMPLEMENTATION).setEnabledByDefault(false);
+
+ public static final Issue AWARE = Issue.create(
+ "RtlAware", //$NON-NLS-1$
+ "View code not aware of RTL APIs",
+
+ "When manipulating views, and especially when implementing custom layouts, " +
+ "the code may need to be aware of RTL APIs. This lint check looks for usages of " +
+ "APIs that frequently require adjustments for right-to-left text, and warns if it " +
+ "does not also see text direction look-ups indicating that the code has already " +
+ "been updated to handle RTL layouts.",
+
+ Category.RTL, 3, Severity.WARNING, IMPLEMENTATION).setEnabledByDefault(false);
+ */
+
+ private static final String RIGHT_FIELD = "RIGHT"; //$NON-NLS-1$
+ private static final String LEFT_FIELD = "LEFT"; //$NON-NLS-1$
+ private static final String GRAVITY_CLASS = "Gravity"; //$NON-NLS-1$
+ private static final String FQCN_GRAVITY = "android.view.Gravity"; //$NON-NLS-1$
+ private static final String FQCN_GRAVITY_PREFIX = "android.view.Gravity."; //$NON-NLS-1$
+ private static final String ATTR_TEXT_ALIGNMENT = "textAlignment"; //$NON-NLS-1$
+ static final String ATTR_SUPPORTS_RTL = "supportsRtl"; //$NON-NLS-1$
+
+ /** API version in which RTL support was added */
+ private static final int RTL_API = 17;
+
+ private static final String LEFT = "Left";
+ private static final String START = "Start";
+ private static final String RIGHT = "Right";
+ private static final String END = "End";
+
+ private Boolean mEnabledRtlSupport;
+ private boolean mUsesRtlAttributes;
+
+ /** Constructs a new {@link RtlDetector} */
+ public RtlDetector() {
+ }
+
+ @Override
+ @NonNull
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ private boolean rtlApplies(@NonNull Context context) {
+ Project project = context.getMainProject();
+ if (project.getTargetSdk() < RTL_API) {
+ return false;
+ }
+
+ int buildTarget = project.getBuildSdk();
+ if (buildTarget != -1 && buildTarget < RTL_API) {
+ return false;
+ }
+
+ //noinspection RedundantIfStatement
+ if (mEnabledRtlSupport != null && !mEnabledRtlSupport) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mUsesRtlAttributes && mEnabledRtlSupport == null && rtlApplies(context)) {
+ List<File> manifestFile = context.getMainProject().getManifestFiles();
+ if (!manifestFile.isEmpty()) {
+ Location location = Location.create(manifestFile.get(0));
+ context.report(ENABLED, location,
+ "The project references RTL attributes, but does not explicitly enable " +
+ "or disable RTL support with `android:supportsRtl` in the manifest");
+ }
+ }
+ }
+
+ // ---- Implements XmlDetector ----
+
+ @VisibleForTesting
+ static final String[] ATTRIBUTES = new String[] {
+ // Pairs, from left/right constants to corresponding start/end constants
+ ATTR_LAYOUT_ALIGN_PARENT_LEFT, ATTR_LAYOUT_ALIGN_PARENT_START,
+ ATTR_LAYOUT_ALIGN_PARENT_RIGHT, ATTR_LAYOUT_ALIGN_PARENT_END,
+ ATTR_LAYOUT_MARGIN_LEFT, ATTR_LAYOUT_MARGIN_START,
+ ATTR_LAYOUT_MARGIN_RIGHT, ATTR_LAYOUT_MARGIN_END,
+ ATTR_PADDING_LEFT, ATTR_PADDING_START,
+ ATTR_PADDING_RIGHT, ATTR_PADDING_END,
+ ATTR_DRAWABLE_LEFT, ATTR_DRAWABLE_START,
+ ATTR_DRAWABLE_RIGHT, ATTR_DRAWABLE_END,
+ ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT, ATTR_LIST_PREFERRED_ITEM_PADDING_START,
+ ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT, ATTR_LIST_PREFERRED_ITEM_PADDING_END,
+
+ // RelativeLayout
+ ATTR_LAYOUT_TO_LEFT_OF, ATTR_LAYOUT_TO_START_OF,
+ ATTR_LAYOUT_TO_RIGHT_OF, ATTR_LAYOUT_TO_END_OF,
+ ATTR_LAYOUT_ALIGN_LEFT, ATTR_LAYOUT_ALIGN_START,
+ ATTR_LAYOUT_ALIGN_RIGHT, ATTR_LAYOUT_ALIGN_END,
+ };
+ static {
+ if (LintUtils.assertionsEnabled()) {
+ for (int i = 0; i < ATTRIBUTES.length; i += 2) {
+ String replace = ATTRIBUTES[i];
+ String with = ATTRIBUTES[i + 1];
+ assert with.equals(convertOldToNew(replace));
+ assert replace.equals(convertNewToOld(with));
+ }
+ }
+ }
+
+ public static boolean isRtlAttributeName(@NonNull String attribute) {
+ for (int i = 1; i < ATTRIBUTES.length; i += 2) {
+ if (attribute.equals(ATTRIBUTES[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ static String convertOldToNew(String attribute) {
+ if (attribute.contains(LEFT)) {
+ return attribute.replace(LEFT, START);
+ } else {
+ return attribute.replace(RIGHT, END);
+ }
+ }
+
+ @VisibleForTesting
+ static String convertNewToOld(String attribute) {
+ if (attribute.contains(START)) {
+ return attribute.replace(START, LEFT);
+ } else {
+ return attribute.replace(END, RIGHT);
+ }
+ }
+
+ @VisibleForTesting
+ static String convertToOppositeDirection(String attribute) {
+ if (attribute.contains(LEFT)) {
+ return attribute.replace(LEFT, RIGHT);
+ } else if (attribute.contains(RIGHT)) {
+ return attribute.replace(RIGHT, LEFT);
+ } else if (attribute.contains(START)) {
+ return attribute.replace(START, END);
+ } else {
+ return attribute.replace(END, START);
+ }
+ }
+
+ @Nullable
+ static String getTextAlignmentToGravity(String attribute) {
+ if (attribute.endsWith(START)) { // textStart, viewStart, ...
+ return GRAVITY_VALUE_START;
+ } else if (attribute.endsWith(END)) { // textEnd, viewEnd, ...
+ return GRAVITY_VALUE_END;
+ } else {
+ return null; // inherit, others
+ }
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ int size = ATTRIBUTES.length + 4;
+ List<String> attributes = new ArrayList<String>(size);
+
+ // For detecting whether RTL support is enabled
+ attributes.add(ATTR_SUPPORTS_RTL);
+
+ // For detecting left/right attributes which should probably be
+ // migrated to start/end
+ attributes.add(ATTR_GRAVITY);
+ attributes.add(ATTR_LAYOUT_GRAVITY);
+
+ // For detecting existing attributes which indicate an attempt to
+ // use RTL
+ attributes.add(ATTR_TEXT_ALIGNMENT);
+
+ // Add conversion attributes: left/right attributes to nominate
+ // attributes that should be added as start/end, and start/end
+ // attributes to use to look up elements that should have compatibility
+ // left/right ones as well
+ Collections.addAll(attributes, ATTRIBUTES);
+
+ assert attributes.size() == size : attributes.size();
+
+ return attributes;
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ Project project = context.getMainProject();
+ String value = attribute.getValue();
+
+ if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ // Layout attribute not in the Android namespace (or a custom namespace).
+ // This is likely an application error (which should get caught by
+ // the MissingPrefixDetector)
+ return;
+ }
+
+ String name = attribute.getLocalName();
+ assert name != null : attribute.getName();
+
+ if (name.equals(ATTR_SUPPORTS_RTL)) {
+ mEnabledRtlSupport = Boolean.valueOf(value);
+ if (!attribute.getOwnerElement().getTagName().equals(TAG_APPLICATION)) {
+ context.report(ENABLED, attribute, context.getLocation(attribute), String.format(
+ "Wrong declaration: `%1$s` should be defined on the `<application>` element",
+ attribute.getName()));
+ }
+ int targetSdk = project.getTargetSdk();
+ if (mEnabledRtlSupport && targetSdk < RTL_API) {
+ String message = String.format(
+ "You must set `android:targetSdkVersion` to at least %1$d when "
+ + "enabling RTL support (is %2$d)",
+ RTL_API, project.getTargetSdk());
+ context.report(ENABLED, attribute, context.getValueLocation(attribute), message);
+ }
+ return;
+ }
+
+ if (!rtlApplies(context)) {
+ return;
+ }
+
+ if (name.equals(ATTR_TEXT_ALIGNMENT)) {
+ if (context.getProject().getReportIssues()) {
+ mUsesRtlAttributes = true;
+ }
+
+ Element element = attribute.getOwnerElement();
+ final String gravity;
+ final Attr gravityNode;
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_GRAVITY)) {
+ gravityNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_GRAVITY);
+ gravity = gravityNode.getValue();
+ } else if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY)) {
+ gravityNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY);
+ gravity = gravityNode.getValue();
+ } else if (project.getMinSdk() < RTL_API) {
+ int folderVersion = context.getFolderVersion();
+ if (folderVersion < RTL_API && context.isEnabled(COMPAT)) {
+ String expectedGravity = getTextAlignmentToGravity(value);
+ if (expectedGravity != null) {
+ String message = String.format(
+ "To support older versions than API 17 (project specifies %1$d) "
+ + "you must *also* specify `gravity` or `layout_gravity=\"%2$s\"`",
+ project.getMinSdk(), expectedGravity);
+ context.report(COMPAT, attribute,
+ context.getNameLocation(attribute), message);
+ }
+ }
+ return;
+ } else {
+ return;
+ }
+
+ String expectedGravity = getTextAlignmentToGravity(value);
+ if (expectedGravity != null && !gravity.contains(expectedGravity)
+ && context.isEnabled(COMPAT)) {
+ String message = String.format("Inconsistent alignment specification between "
+ + "`textAlignment` and `gravity` attributes: was `%1$s`, expected `%2$s`",
+ gravity, expectedGravity);
+ Location location = context.getValueLocation(attribute);
+ context.report(COMPAT, attribute, location, message);
+ Location secondary = context.getValueLocation(gravityNode);
+ secondary.setMessage("Incompatible direction here");
+ location.setSecondary(secondary);
+ }
+ return;
+ }
+
+ if (name.equals(ATTR_GRAVITY) || name.equals(ATTR_LAYOUT_GRAVITY)) {
+ boolean isLeft = value.contains(GRAVITY_VALUE_LEFT);
+ boolean isRight = value.contains(GRAVITY_VALUE_RIGHT);
+ if (!isLeft && !isRight) {
+ if ((value.contains(GRAVITY_VALUE_START) || value.contains(GRAVITY_VALUE_END))
+ && context.getProject().getReportIssues()) {
+ mUsesRtlAttributes = true;
+ }
+ return;
+ }
+ String message = String.format(
+ "Use \"`%1$s`\" instead of \"`%2$s`\" to ensure correct behavior in "
+ + "right-to-left locales",
+ isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END,
+ isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT);
+ if (context.isEnabled(USE_START)) {
+ context.report(USE_START, attribute, context.getValueLocation(attribute), message);
+ }
+
+ return;
+ }
+
+ // Some other left/right/start/end attribute
+ int targetSdk = project.getTargetSdk();
+
+ // TODO: If attribute is drawableLeft or drawableRight, add note that you might
+ // want to consider adding a specialized image in the -ldrtl folder as well
+
+ Element element = attribute.getOwnerElement();
+ boolean isPaddingAttribute = isPaddingAttribute(name);
+ if (isPaddingAttribute || isMarginAttribute(name)) {
+ String opposite = convertToOppositeDirection(name);
+ if (element.hasAttributeNS(ANDROID_URI, opposite)) {
+ String oldValue = element.getAttributeNS(ANDROID_URI, opposite);
+ if (value.equals(oldValue)) {
+ return;
+ }
+ } else if (isPaddingAttribute
+ && !element.hasAttributeNS(ANDROID_URI,
+ isOldAttribute(opposite) ? convertOldToNew(opposite)
+ : convertNewToOld(opposite)) && context.isEnabled(SYMMETRY)) {
+ String message = String.format(
+ "When you define `%1$s` you should probably also define `%2$s` for "
+ + "right-to-left symmetry", name, opposite);
+ context.report(SYMMETRY, attribute, context.getNameLocation(attribute), message);
+ }
+ }
+
+ boolean isOld = isOldAttribute(name);
+ if (isOld) {
+ if (!context.isEnabled(USE_START)) {
+ return;
+ }
+ String rtl = convertOldToNew(name);
+ if (element.hasAttributeNS(ANDROID_URI, rtl)) {
+ if (project.getMinSdk() >= RTL_API || context.getFolderVersion() >= RTL_API) {
+ // Warn that left/right isn't needed
+ String message = String.format(
+ "Redundant attribute `%1$s`; already defining `%2$s` with "
+ + "`targetSdkVersion` %3$s",
+ name, rtl, targetSdk);
+ context.report(USE_START, attribute,
+ context.getNameLocation(attribute), message);
+ }
+ } else {
+ String message;
+ if (project.getMinSdk() >= RTL_API || context.getFolderVersion() >= RTL_API) {
+ message = String.format(
+ "Consider replacing `%1$s` with `%2$s:%3$s=\"%4$s\"` to better support "
+ + "right-to-left layouts",
+ attribute.getName(), attribute.getPrefix(), rtl, value);
+ } else {
+ message = String.format(
+ "Consider adding `%1$s:%2$s=\"%3$s\"` to better support "
+ + "right-to-left layouts",
+ attribute.getPrefix(), rtl, value);
+ }
+ context.report(USE_START, attribute,
+ context.getNameLocation(attribute), message);
+ }
+ } else {
+ if (project.getMinSdk() >= RTL_API || !context.isEnabled(COMPAT)) {
+ // Only supporting 17+: no need to define older attributes
+ return;
+ }
+ int folderVersion = context.getFolderVersion();
+ if (folderVersion >= RTL_API) {
+ // In a -v17 folder or higher: no need to define older attributes
+ return;
+ }
+ String old = convertNewToOld(name);
+ if (element.hasAttributeNS(ANDROID_URI, old)) {
+ return;
+ }
+ String message = String.format(
+ "To support older versions than API 17 (project specifies %1$d) "
+ + "you should *also* add `%2$s:%3$s=\"%4$s\"`",
+ project.getMinSdk(), attribute.getPrefix(), old,
+ convertNewToOld(value));
+ context.report(COMPAT, attribute, context.getNameLocation(attribute), message);
+ }
+ }
+
+ private static boolean isOldAttribute(String name) {
+ return name.contains(LEFT) || name.contains(RIGHT);
+ }
+
+ private static boolean isMarginAttribute(@NonNull String name) {
+ return name.startsWith(ATTR_LAYOUT_MARGIN);
+ }
+
+ private static boolean isPaddingAttribute(@NonNull String name) {
+ return name.startsWith(ATTR_PADDING);
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @Override
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends Node>>singletonList(Identifier.class);
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ if (rtlApplies(context)) {
+ return new IdentifierChecker(context);
+ }
+
+ return new ForwardingAstVisitor() { };
+ }
+
+ private static class IdentifierChecker extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public IdentifierChecker(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitIdentifier(Identifier node) {
+ String identifier = node.astValue();
+ boolean isLeft = LEFT_FIELD.equals(identifier);
+ boolean isRight = RIGHT_FIELD.equals(identifier);
+ if (!isLeft && !isRight) {
+ return false;
+ }
+
+ Node parent = node.getParent();
+ if (parent instanceof ImportDeclaration || parent instanceof EnumConstant
+ || parent instanceof VariableDefinitionEntry) {
+ return false;
+ }
+
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved != null) {
+ if (!(resolved instanceof ResolvedField)) {
+ return false;
+ } else {
+ ResolvedField field = (ResolvedField) resolved;
+ ResolvedClass containingClass = field.getContainingClass();
+ if (containingClass == null || !containingClass.matches(FQCN_GRAVITY)) {
+ return false;
+ }
+ }
+ } else {
+ // Can't resolve types (for example while editing code with errors):
+ // rely on heuristics like import statements and class qualifiers
+ if (parent instanceof Select &&
+ !(GRAVITY_CLASS.equals(((Select) parent).astOperand().toString()))) {
+ return false;
+ }
+ if (parent instanceof VariableReference) {
+ // No operand: make sure it's statically imported
+ if (!LintUtils.isImported(mContext.getCompilationUnit(),
+ FQCN_GRAVITY_PREFIX + identifier)) {
+ return false;
+ }
+ }
+ }
+
+ String message = String.format(
+ "Use \"`Gravity.%1$s`\" instead of \"`Gravity.%2$s`\" to ensure correct "
+ + "behavior in right-to-left locales",
+ (isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END).toUpperCase(Locale.US),
+ (isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT).toUpperCase(Locale.US));
+ Location location = mContext.getLocation(node);
+ mContext.report(USE_START, node, location, message);
+
+ return true;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SQLiteDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SQLiteDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SQLiteDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SQLiteDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
new file mode 100644
index 0000000..7a0b729
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TypeEvaluator;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
+/**
+ * Checks for hardcoded seeds with random numbers.
+ */
+public class SecureRandomDetector extends Detector implements JavaScanner {
+ /** Unregistered activities and services */
+ public static final Issue ISSUE = Issue.create(
+ "SecureRandom", //$NON-NLS-1$
+ "Using a fixed seed with `SecureRandom`",
+
+ "Specifying a fixed seed will cause the instance to return a predictable sequence " +
+ "of numbers. This may be useful for testing but it is not appropriate for secure use.",
+
+ Category.SECURITY,
+ 9,
+ Severity.WARNING,
+ new Implementation(
+ SecureRandomDetector.class,
+ Scope.JAVA_FILE_SCOPE))
+ .addMoreInfo("http://developer.android.com/reference/java/security/SecureRandom.html");
+
+ private static final String SET_SEED = "setSeed"; //$NON-NLS-1$
+ public static final String JAVA_SECURITY_SECURE_RANDOM = "java.security.SecureRandom";
+ public static final String JAVA_UTIL_RANDOM = "java.util.Random";
+
+ /** Constructs a new {@link SecureRandomDetector} */
+ public SecureRandomDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList(SET_SEED);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation call) {
+ ResolvedNode resolved = context.resolve(call);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return;
+ }
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ Expression seedArgument = call.astArguments().first();
+ if (seedArgument == null) {
+ return;
+ }
+ ResolvedClass containingClass = method.getContainingClass();
+ if (containingClass.matches(JAVA_SECURITY_SECURE_RANDOM) ||
+ containingClass.isSubclassOf(JAVA_UTIL_RANDOM, false)
+ && isSecureRandomReceiver(context, call)) {
+ // Called with a fixed seed?
+ Object seed = ConstantEvaluator.evaluate(context, seedArgument);
+ //noinspection VariableNotUsedInsideIf
+ if (seed != null) {
+ context.report(ISSUE, call, context.getLocation(call),
+ "Do not call `setSeed()` on a `SecureRandom` with a fixed seed: " +
+ "it is not secure. Use `getSeed()`.");
+ } else {
+ // Called with a simple System.currentTimeMillis() seed or something like that?
+ ResolvedNode resolvedArgument = context.resolve(seedArgument);
+ if (resolvedArgument instanceof ResolvedMethod) {
+ ResolvedMethod seedMethod = (ResolvedMethod) resolvedArgument;
+ String methodName = seedMethod.getName();
+ if (methodName.equals("currentTimeMillis") || methodName
+ .equals("nanoTime")) {
+ context.report(ISSUE, call, context.getLocation(call),
+ "It is dangerous to seed `SecureRandom` with the current "
+ + "time because that value is more predictable to "
+ + "an attacker than the default seed.");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given invocation is assigned a SecureRandom type
+ */
+ private static boolean isSecureRandomReceiver(@NonNull JavaContext context,
+ @NonNull MethodInvocation call) {
+ Expression operand = call.astOperand();
+ return operand != null && isSecureRandomType(context, operand);
+ }
+
+ /**
+ * Returns true if the node evaluates to an instance of type SecureRandom
+ */
+ private static boolean isSecureRandomType(@NonNull JavaContext context, @NonNull Node node) {
+ TypeDescriptor type = TypeEvaluator.evaluate(context, node);
+ return type != null && type.matchesName(JAVA_SECURITY_SECURE_RANDOM);
+
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
new file mode 100644
index 0000000..cb6504a
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Checks for pseudo random number generator initialization issues
+ */
+public class SecureRandomGeneratorDetector extends Detector implements ClassScanner {
+ private static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; //$NON-NLS-1$
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private static final String BLOG_URL
+ = "https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html";
+
+ /** Whether the random number generator is initialized correctly */
+ public static final Issue ISSUE = Issue.create(
+ "TrulyRandom", //$NON-NLS-1$
+ "Weak RNG",
+ "Key generation, signing, encryption, and random number generation may not " +
+ "receive cryptographically strong values due to improper initialization of " +
+ "the underlying PRNG on Android 4.3 and below.\n" +
+ "\n" +
+ "If your application relies on cryptographically secure random number generation " +
+ "you should apply the workaround described in " + BLOG_URL + " .\n" +
+ "\n" +
+ "This lint rule is mostly informational; it does not accurately detect whether " +
+ "cryptographically secure RNG is required, or whether the workaround has already " +
+ "been applied. After reading the blog entry and updating your code if necessary, " +
+ "you can disable this lint issue.",
+
+ Category.SECURITY,
+ 9,
+ Severity.WARNING,
+ new Implementation(
+ SecureRandomGeneratorDetector.class,
+ Scope.CLASS_FILE_SCOPE))
+ .addMoreInfo(BLOG_URL);
+
+ private static final String WRAP = "wrap"; //$NON-NLS-1$
+ private static final String UNWRAP = "unwrap"; //$NON-NLS-1$
+ private static final String INIT = "init"; //$NON-NLS-1$
+ private static final String INIT_SIGN = "initSign"; //$NON-NLS-1$
+ private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
+ private static final String FOR_NAME = "forName"; //$NON-NLS-1$
+ private static final String JAVA_LANG_CLASS = "java/lang/Class"; //$NON-NLS-1$
+ private static final String JAVAX_CRYPTO_KEY_GENERATOR = "javax/crypto/KeyGenerator";
+ private static final String JAVAX_CRYPTO_KEY_AGREEMENT = "javax/crypto/KeyAgreement";
+ private static final String JAVA_SECURITY_KEY_PAIR_GENERATOR =
+ "java/security/KeyPairGenerator";
+ private static final String JAVAX_CRYPTO_SIGNATURE = "javax/crypto/Signature";
+ private static final String JAVAX_CRYPTO_CIPHER = "javax/crypto/Cipher";
+ private static final String JAVAX_NET_SSL_SSLENGINE = "javax/net/ssl/SSLEngine";
+
+ /** Constructs a new {@link SecureRandomGeneratorDetector} */
+ public SecureRandomGeneratorDetector() {
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableCallOwners() {
+ return Arrays.asList(
+ JAVAX_CRYPTO_KEY_GENERATOR,
+ JAVA_SECURITY_KEY_PAIR_GENERATOR,
+ JAVAX_CRYPTO_KEY_AGREEMENT,
+ OWNER_SECURE_RANDOM,
+ JAVAX_NET_SSL_SSLENGINE,
+ JAVAX_CRYPTO_SIGNATURE,
+ JAVAX_CRYPTO_CIPHER
+ );
+ }
+
+ @Nullable
+ @Override
+ public List<String> getApplicableCallNames() {
+ return Collections.singletonList(FOR_NAME);
+ }
+
+ /** Location of first call to key generator (etc), if any */
+ private Location mLocation;
+
+ /** Whether the issue should be ignored (because we have a workaround, or because
+ * we're only targeting correct implementations, etc */
+ private boolean mIgnore;
+
+ @Override
+ public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull MethodInsnNode call) {
+ if (mIgnore) {
+ return;
+ }
+
+ String owner = call.owner;
+ String name = call.name;
+
+ // Look for the workaround code: if we see a Class.forName on the harmony NativeCrypto,
+ // we'll consider that a sign.
+
+ if (name.equals(FOR_NAME)) {
+ if (call.getOpcode() != Opcodes.INVOKESTATIC ||
+ !owner.equals(JAVA_LANG_CLASS)) {
+ return;
+ }
+ AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
+ if (prev instanceof LdcInsnNode) {
+ Object cst = ((LdcInsnNode)prev).cst;
+ //noinspection SpellCheckingInspection
+ if (cst instanceof String &&
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto".equals(cst)) {
+ mIgnore = true;
+ }
+ }
+ return;
+ }
+
+ // Look for calls that probably require a properly initialized random number generator.
+ assert owner.equals(JAVAX_CRYPTO_KEY_GENERATOR)
+ || owner.equals(JAVA_SECURITY_KEY_PAIR_GENERATOR)
+ || owner.equals(JAVAX_CRYPTO_KEY_AGREEMENT)
+ || owner.equals(OWNER_SECURE_RANDOM)
+ || owner.equals(JAVAX_CRYPTO_CIPHER)
+ || owner.equals(JAVAX_CRYPTO_SIGNATURE)
+ || owner.equals(JAVAX_NET_SSL_SSLENGINE) : owner;
+ boolean warn = false;
+
+ if (owner.equals(JAVAX_CRYPTO_SIGNATURE)) {
+ warn = name.equals(INIT_SIGN);
+ } else if (owner.equals(JAVAX_CRYPTO_CIPHER)) {
+ if (name.equals(INIT)) {
+ int arity = getDescArity(call.desc);
+ AbstractInsnNode node = call;
+ for (int i = 0; i < arity; i++) {
+ node = LintUtils.getPrevInstruction(node);
+ if (node == null) {
+ break;
+ }
+ }
+ if (node != null) {
+ int opcode = node.getOpcode();
+ if (opcode == Opcodes.ICONST_3 || // Cipher.WRAP_MODE
+ opcode == Opcodes.ICONST_1) { // Cipher.ENCRYPT_MODE
+ warn = true;
+ }
+ }
+ }
+ } else if (name.equals(GET_INSTANCE) || name.equals(CONSTRUCTOR_NAME)
+ || name.equals(WRAP) || name.equals(UNWRAP)) { // For SSLEngine
+ warn = true;
+ }
+
+ if (warn) {
+ if (mLocation != null) {
+ return;
+ }
+ if (context.getMainProject().getMinSdk() > 18) {
+ // Fix no longer needed
+ mIgnore = true;
+ return;
+ }
+
+ if (context.getDriver().isSuppressed(ISSUE, classNode, method, call)) {
+ mIgnore = true;
+ } else {
+ mLocation = context.getLocation(call);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static int getDescArity(String desc) {
+ int arity = 0;
+ // For example, (ILjava/security/Key;)V => 2
+ for (int i = 1, max = desc.length(); i < max; i++) {
+ char c = desc.charAt(i);
+ if (c == ')') {
+ break;
+ } else if (c == 'L') {
+ arity++;
+ i = desc.indexOf(';', i);
+ assert i != -1 : desc;
+ } else {
+ arity++;
+ }
+ }
+
+ return arity;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mLocation != null && !mIgnore) {
+ String message = "Potentially insecure random numbers on Android 4.3 and older. "
+ + "Read " + BLOG_URL + " for more info.";
+ context.report(ISSUE, mLocation, message);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
new file mode 100644
index 0000000..35a5334
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_EXPORTED;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PATH;
+import static com.android.SdkConstants.ATTR_PATH_PATTERN;
+import static com.android.SdkConstants.ATTR_PATH_PREFIX;
+import static com.android.SdkConstants.ATTR_PERMISSION;
+import static com.android.SdkConstants.ATTR_READ_PERMISSION;
+import static com.android.SdkConstants.ATTR_WRITE_PERMISSION;
+import static com.android.SdkConstants.TAG_ACTIVITY;
+import static com.android.SdkConstants.TAG_APPLICATION;
+import static com.android.SdkConstants.TAG_GRANT_PERMISSION;
+import static com.android.SdkConstants.TAG_INTENT_FILTER;
+import static com.android.SdkConstants.TAG_PATH_PERMISSION;
+import static com.android.SdkConstants.TAG_PROVIDER;
+import static com.android.SdkConstants.TAG_RECEIVER;
+import static com.android.SdkConstants.TAG_SERVICE;
+import static com.android.xml.AndroidManifest.NODE_ACTION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Identifier;
+import lombok.ast.MethodInvocation;
+import lombok.ast.StrictListAccessor;
+
+/**
+ * Checks that exported services request a permission.
+ */
+public class SecurityDetector extends Detector implements Detector.XmlScanner,
+ Detector.JavaScanner {
+
+ private static final Implementation IMPLEMENTATION_MANIFEST = new Implementation(
+ SecurityDetector.class,
+ Scope.MANIFEST_SCOPE);
+
+ private static final Implementation IMPLEMENTATION_JAVA = new Implementation(
+ SecurityDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Exported services */
+ public static final Issue EXPORTED_SERVICE = Issue.create(
+ "ExportedService", //$NON-NLS-1$
+ "Exported service does not require permission",
+ "Exported services (services which either set `exported=true` or contain " +
+ "an intent-filter and do not specify `exported=false`) should define a " +
+ "permission that an entity must have in order to launch the service " +
+ "or bind to it. Without this, any application can use this service.",
+ Category.SECURITY,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION_MANIFEST);
+
+ /** Exported content providers */
+ public static final Issue EXPORTED_PROVIDER = Issue.create(
+ "ExportedContentProvider", //$NON-NLS-1$
+ "Content provider does not require permission",
+ "Content providers are exported by default and any application on the " +
+ "system can potentially use them to read and write data. If the content " +
+ "provider provides access to sensitive data, it should be protected by " +
+ "specifying `export=false` in the manifest or by protecting it with a " +
+ "permission that can be granted to other applications.",
+ Category.SECURITY,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION_MANIFEST);
+
+ /** Exported receivers */
+ public static final Issue EXPORTED_RECEIVER = Issue.create(
+ "ExportedReceiver", //$NON-NLS-1$
+ "Receiver does not require permission",
+ "Exported receivers (receivers which either set `exported=true` or contain " +
+ "an intent-filter and do not specify `exported=false`) should define a " +
+ "permission that an entity must have in order to launch the receiver " +
+ "or bind to it. Without this, any application can use this receiver.",
+ Category.SECURITY,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION_MANIFEST);
+
+ /** Content provides which grant all URIs access */
+ public static final Issue OPEN_PROVIDER = Issue.create(
+ "GrantAllUris", //$NON-NLS-1$
+ "Content provider shares everything",
+ "The `<grant-uri-permission>` element allows specific paths to be shared. " +
+ "This detector checks for a path URL of just '/' (everything), which is " +
+ "probably not what you want; you should limit access to a subset.",
+ Category.SECURITY,
+ 7,
+ Severity.WARNING,
+ IMPLEMENTATION_MANIFEST);
+
+ /** Using java.io.File.setReadable(true, false) to set file world-readable */
+ public static final Issue SET_READABLE = Issue.create(
+ "SetWorldReadable",
+ "`File.setReadable()` used to make file world-readable",
+ "Setting files world-readable is very dangerous, and likely to cause security " +
+ "holes in applications. It is strongly discouraged; instead, applications should " +
+ "use more formal mechanisms for interactions such as `ContentProvider`, " +
+ "`BroadcastReceiver`, and `Service`.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION_JAVA);
+
+ /** Using java.io.File.setWritable(true, false) to set file world-writable */
+ public static final Issue SET_WRITABLE = Issue.create(
+ "SetWorldWritable",
+ "`File.setWritable()` used to make file world-writable",
+ "Setting files world-writable is very dangerous, and likely to cause security " +
+ "holes in applications. It is strongly discouraged; instead, applications should " +
+ "use more formal mechanisms for interactions such as `ContentProvider`, " +
+ "`BroadcastReceiver`, and `Service`.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION_JAVA);
+
+ /** Using the world-writable flag */
+ public static final Issue WORLD_WRITEABLE = Issue.create(
+ "WorldWriteableFiles", //$NON-NLS-1$
+ "`openFileOutput()` or similar call passing `MODE_WORLD_WRITEABLE`",
+ "There are cases where it is appropriate for an application to write " +
+ "world writeable files, but these should be reviewed carefully to " +
+ "ensure that they contain no private data, and that if the file is " +
+ "modified by a malicious application it does not trick or compromise " +
+ "your application.",
+ Category.SECURITY,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION_JAVA);
+
+
+ /** Using the world-readable flag */
+ public static final Issue WORLD_READABLE = Issue.create(
+ "WorldReadableFiles", //$NON-NLS-1$
+ "`openFileOutput()` or similar call passing `MODE_WORLD_READABLE`",
+ "There are cases where it is appropriate for an application to write " +
+ "world readable files, but these should be reviewed carefully to " +
+ "ensure that they contain no private data that is leaked to other " +
+ "applications.",
+ Category.SECURITY,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION_JAVA);
+
+ private static final String FILE_CLASS = "java.io.File"; //$NON-NLS-1$
+
+ /** Constructs a new {@link SecurityDetector} check */
+ public SecurityDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return file.getName().equals(ANDROID_MANIFEST_XML);
+ }
+
+ // ---- Implements Detector.XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ TAG_SERVICE,
+ TAG_GRANT_PERMISSION,
+ TAG_PROVIDER,
+ TAG_ACTIVITY,
+ TAG_RECEIVER
+ );
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String tag = element.getTagName();
+ if (tag.equals(TAG_SERVICE)) {
+ checkService(context, element);
+ } else if (tag.equals(TAG_GRANT_PERMISSION)) {
+ checkGrantPermission(context, element);
+ } else if (tag.equals(TAG_PROVIDER)) {
+ checkProvider(context, element);
+ } else if (tag.equals(TAG_RECEIVER)) {
+ checkReceiver(context, element);
+ }
+ }
+
+ public static boolean getExported(Element element) {
+ // Used to check whether an activity, service or broadcast receiver is exported.
+ String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
+ if (exportValue != null && !exportValue.isEmpty()) {
+ return Boolean.valueOf(exportValue);
+ } else {
+ for (Element child : LintUtils.getChildren(element)) {
+ if (child.getTagName().equals(TAG_INTENT_FILTER)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isUnprotectedByPermission(Element element) {
+ // Used to check whether an activity, service or broadcast receiver are
+ // protected by a permission.
+ String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
+ if (permission == null || permission.isEmpty()) {
+ Node parent = element.getParentNode();
+ if (parent.getNodeType() == Node.ELEMENT_NODE
+ && parent.getNodeName().equals(TAG_APPLICATION)) {
+ Element application = (Element) parent;
+ permission = application.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
+ return permission == null || permission.isEmpty();
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isWearableBindListener(@NonNull Element element) {
+ // Checks whether a service has an Android Wear bind listener
+ for (Element child : LintUtils.getChildren(element)) {
+ if (child.getTagName().equals(TAG_INTENT_FILTER)) {
+ for (Element innerChild : LintUtils.getChildren(child)) {
+ if (innerChild.getTagName().equals(NODE_ACTION)) {
+ String name = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if ("com.google.android.gms.wearable.BIND_LISTENER".equals(name)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isStandardReceiver(Element element) {
+ // Play Services also the following receiver which we'll consider standard
+ // in the sense that it doesn't require a separate permission
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if ("com.google.android.gms.tagmanager.InstallReferrerReceiver".equals(name)) {
+ return true;
+ }
+
+ // Checks whether a broadcast receiver receives a standard Android action
+ for (Element child : LintUtils.getChildren(element)) {
+ if (child.getTagName().equals(TAG_INTENT_FILTER)) {
+ for (Element innerChild : LintUtils.getChildren(child)) {
+ if (innerChild.getTagName().equals(NODE_ACTION)) {
+ String categoryString = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ return categoryString.startsWith("android."); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void checkReceiver(XmlContext context, Element element) {
+ if (getExported(element) && isUnprotectedByPermission(element) &&
+ !isStandardReceiver(element)) {
+ // No declared permission for this exported receiver: complain
+ context.report(EXPORTED_RECEIVER, element, context.getLocation(element),
+ "Exported receiver does not require permission");
+ }
+ }
+
+ private static void checkService(XmlContext context, Element element) {
+ if (getExported(element) && isUnprotectedByPermission(element)
+ && !isWearableBindListener(element)) {
+ // No declared permission for this exported service: complain
+ context.report(EXPORTED_SERVICE, element, context.getLocation(element),
+ "Exported service does not require permission");
+ }
+ }
+
+ private static void checkGrantPermission(XmlContext context, Element element) {
+ Attr path = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH);
+ Attr prefix = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PREFIX);
+ Attr pattern = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PATTERN);
+
+ String msg = "Content provider shares everything; this is potentially dangerous.";
+ if (path != null && path.getValue().equals("/")) { //$NON-NLS-1$
+ context.report(OPEN_PROVIDER, path, context.getLocation(path), msg);
+ }
+ if (prefix != null && prefix.getValue().equals("/")) { //$NON-NLS-1$
+ context.report(OPEN_PROVIDER, prefix, context.getLocation(prefix), msg);
+ }
+ if (pattern != null && (pattern.getValue().equals("/") //$NON-NLS-1$
+ /* || pattern.getValue().equals(".*")*/)) {
+ context.report(OPEN_PROVIDER, pattern, context.getLocation(pattern), msg);
+ }
+ }
+
+ private static void checkProvider(XmlContext context, Element element) {
+ String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
+ // Content providers are exported by default
+ boolean exported = true;
+ if (exportValue != null && !exportValue.isEmpty()) {
+ exported = Boolean.valueOf(exportValue);
+ }
+
+ if (exported) {
+ // Just check for some use of permissions. Other Lint checks can check the saneness
+ // of the permissions. We'll accept the permission, readPermission, or writePermission
+ // attributes on the provider element, or a path-permission element.
+ String permission = element.getAttributeNS(ANDROID_URI, ATTR_READ_PERMISSION);
+ if (permission == null || permission.isEmpty()) {
+ permission = element.getAttributeNS(ANDROID_URI, ATTR_WRITE_PERMISSION);
+ if (permission == null || permission.isEmpty()) {
+ permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
+ if (permission == null || permission.isEmpty()) {
+ // No permission attributes? Check for path-permission.
+
+ // TODO: Add a Lint check to ensure the path-permission is good, similar to
+ // the grant-uri-permission check.
+ boolean hasPermission = false;
+ for (Element child : LintUtils.getChildren(element)) {
+ String tag = child.getTagName();
+ if (tag.equals(TAG_PATH_PERMISSION)) {
+ hasPermission = true;
+ break;
+ }
+ }
+
+ if (!hasPermission) {
+ context.report(EXPORTED_PROVIDER, element,
+ context.getLocation(element),
+ "Exported content providers can provide access to " +
+ "potentially sensitive data");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // ---- Implements Detector.JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ // These are the API calls that can accept a MODE_WORLD_READABLE/MODE_WORLD_WRITEABLE
+ // argument.
+ List<String> values = new ArrayList<String>(3);
+ values.add("openFileOutput"); //$NON-NLS-1$
+ values.add("getSharedPreferences"); //$NON-NLS-1$
+ values.add("getDir"); //$NON-NLS-1$
+ // These API calls can be used to set files world-readable or world-writable
+ values.add("setReadable"); //$NON-NLS-1$
+ values.add("setWritable"); //$NON-NLS-1$
+ return values;
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedClass resolvedClass = ((ResolvedMethod) resolved).getContainingClass();
+ String methodName = node.astName().astValue();
+ if (resolvedClass.isSubclassOf(FILE_CLASS, false)) {
+ // Report calls to java.io.File.setReadable(true, false) or
+ // java.io.File.setWritable(true, false)
+ if ("setReadable".equals(methodName)) {
+ if (node.astArguments().size() == 2 &&
+ Boolean.TRUE.equals(ConstantEvaluator.evaluate(context,
+ node.astArguments().first())) &&
+ Boolean.FALSE.equals(ConstantEvaluator.evaluate(context,
+ node.astArguments().last()))) {
+ context.report(SET_READABLE, node, context.getLocation(node),
+ "Setting file permissions to world-readable can be " +
+ "risky, review carefully");
+ }
+ return;
+ } else if ("setWritable".equals(methodName)) {
+ if (node.astArguments().size() == 2 &&
+ Boolean.TRUE.equals(ConstantEvaluator.evaluate(context,
+ node.astArguments().first())) &&
+ Boolean.FALSE.equals(ConstantEvaluator.evaluate(context,
+ node.astArguments().last()))) {
+ context.report(SET_WRITABLE, node, context.getLocation(node),
+ "Setting file permissions to world-writable can be " +
+ "risky, review carefully");
+ }
+ return;
+ }
+ }
+ }
+ StrictListAccessor<Expression,MethodInvocation> args = node.astArguments();
+ for (Expression arg : args) {
+ arg.accept(visitor);
+ }
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ return new IdentifierVisitor(context);
+ }
+
+ private static class IdentifierVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public IdentifierVisitor(JavaContext context) {
+ super();
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitIdentifier(Identifier node) {
+ if ("MODE_WORLD_WRITEABLE".equals(node.astValue())) { //$NON-NLS-1$
+ Location location = mContext.getLocation(node);
+ mContext.report(WORLD_WRITEABLE, node, location,
+ "Using `MODE_WORLD_WRITEABLE` when creating files can be " +
+ "risky, review carefully");
+ } else if ("MODE_WORLD_READABLE".equals(node.astValue())) { //$NON-NLS-1$
+ Location location = mContext.getLocation(node);
+ mContext.report(WORLD_READABLE, node, location,
+ "Using `MODE_WORLD_READABLE` when creating files can be " +
+ "risky, review carefully");
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
new file mode 100644
index 0000000..71541e8
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Cast;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.StrictListAccessor;
+
+/**
+ * Detector looking for casts on th result of context.getSystemService which are suspect
+ */
+public class ServiceCastDetector extends Detector implements Detector.JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "ServiceCast", //$NON-NLS-1$
+ "Wrong system service casts",
+
+ "When you call `Context#getSystemService()`, the result is typically cast to " +
+ "a specific interface. This lint check ensures that the cast is compatible with " +
+ "the expected type of the return value.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ new Implementation(
+ ServiceCastDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ /** Constructs a new {@link ServiceCastDetector} check */
+ public ServiceCastDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList("getSystemService"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ if (node.getParent() instanceof Cast) {
+ Cast cast = (Cast) node.getParent();
+ StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
+ if (args.size() == 1) {
+ String name = stripPackage(args.first().toString());
+ String expectedClass = getExpectedType(name);
+ if (expectedClass != null) {
+ String castType = cast.astTypeReference().getTypeName();
+ if (castType.indexOf('.') == -1) {
+ expectedClass = stripPackage(expectedClass);
+ }
+ if (!castType.equals(expectedClass)) {
+ // It's okay to mix and match
+ // android.content.ClipboardManager and android.text.ClipboardManager
+ if (isClipboard(castType) && isClipboard(expectedClass)) {
+ return;
+ }
+
+ String message = String.format(
+ "Suspicious cast to `%1$s` for a `%2$s`: expected `%3$s`",
+ stripPackage(castType), name, stripPackage(expectedClass));
+ context.report(ISSUE, node, context.getLocation(cast), message);
+ }
+ }
+
+ }
+ }
+ }
+
+ private static boolean isClipboard(String cls) {
+ return cls.equals("android.content.ClipboardManager") //$NON-NLS-1$
+ || cls.equals("android.text.ClipboardManager"); //$NON-NLS-1$
+ }
+
+ private static String stripPackage(String fqcn) {
+ int index = fqcn.lastIndexOf('.');
+ if (index != -1) {
+ fqcn = fqcn.substring(index + 1);
+ }
+
+ return fqcn;
+ }
+
+ @Nullable
+ private static String getExpectedType(@NonNull String value) {
+ return getServiceMap().get(value);
+ }
+
+ @NonNull
+ private static Map<String, String> getServiceMap() {
+ if (sServiceMap == null) {
+ final int EXPECTED_SIZE = 55;
+ sServiceMap = Maps.newHashMapWithExpectedSize(EXPECTED_SIZE);
+
+ sServiceMap.put("ACCESSIBILITY_SERVICE", "android.view.accessibility.AccessibilityManager");
+ sServiceMap.put("ACCOUNT_SERVICE", "android.accounts.AccountManager");
+ sServiceMap.put("ACTIVITY_SERVICE", "android.app.ActivityManager");
+ sServiceMap.put("ALARM_SERVICE", "android.app.AlarmManager");
+ sServiceMap.put("APPWIDGET_SERVICE", "android.appwidget.AppWidgetManager");
+ sServiceMap.put("APP_OPS_SERVICE", "android.app.AppOpsManager");
+ sServiceMap.put("AUDIO_SERVICE", "android.media.AudioManager");
+ sServiceMap.put("BATTERY_SERVICE", "android.os.BatteryManager");
+ sServiceMap.put("BLUETOOTH_SERVICE", "android.bluetooth.BluetoothManager");
+ sServiceMap.put("CAMERA_SERVICE", "android.hardware.camera2.CameraManager");
+ sServiceMap.put("CAPTIONING_SERVICE", "android.view.accessibility.CaptioningManager");
+ sServiceMap.put("CARRIER_CONFIG_SERVICE", "android.telephony.CarrierConfigManager");
+ sServiceMap.put("CLIPBOARD_SERVICE", "android.text.ClipboardManager"); // also allow @Deprecated android.content.ClipboardManager
+ sServiceMap.put("CONNECTIVITY_SERVICE", "android.net.ConnectivityManager");
+ sServiceMap.put("CONSUMER_IR_SERVICE", "android.hardware.ConsumerIrManager");
+ sServiceMap.put("DEVICE_POLICY_SERVICE", "android.app.admin.DevicePolicyManager");
+ sServiceMap.put("DISPLAY_SERVICE", "android.hardware.display.DisplayManager");
+ sServiceMap.put("DOWNLOAD_SERVICE", "android.app.DownloadManager");
+ sServiceMap.put("DROPBOX_SERVICE", "android.os.DropBoxManager");
+ sServiceMap.put("FINGERPRINT_SERVICE", "android.hardware.fingerprint.FingerprintManager");
+ sServiceMap.put("INPUT_METHOD_SERVICE", "android.view.inputmethod.InputMethodManager");
+ sServiceMap.put("INPUT_SERVICE", "android.hardware.input.InputManager");
+ sServiceMap.put("JOB_SCHEDULER_SERVICE", "android.app.job.JobScheduler");
+ sServiceMap.put("KEYGUARD_SERVICE", "android.app.KeyguardManager");
+ sServiceMap.put("LAUNCHER_APPS_SERVICE", "android.content.pm.LauncherApps");
+ sServiceMap.put("LAYOUT_INFLATER_SERVICE", "android.view.LayoutInflater");
+ sServiceMap.put("LOCATION_SERVICE", "android.location.LocationManager");
+ sServiceMap.put("MEDIA_PROJECTION_SERVICE", "android.media.projection.MediaProjectionManager");
+ sServiceMap.put("MEDIA_ROUTER_SERVICE", "android.media.MediaRouter");
+ sServiceMap.put("MEDIA_SESSION_SERVICE", "android.media.session.MediaSessionManager");
+ sServiceMap.put("MIDI_SERVICE", "android.media.midi.MidiManager");
+ sServiceMap.put("NETWORK_STATS_SERVICE", "android.app.usage.NetworkStatsManager");
+ sServiceMap.put("NFC_SERVICE", "android.nfc.NfcManager");
+ sServiceMap.put("NOTIFICATION_SERVICE", "android.app.NotificationManager");
+ sServiceMap.put("NSD_SERVICE", "android.net.nsd.NsdManager");
+ sServiceMap.put("POWER_SERVICE", "android.os.PowerManager");
+ sServiceMap.put("PRINT_SERVICE", "android.print.PrintManager");
+ sServiceMap.put("RESTRICTIONS_SERVICE", "android.content.RestrictionsManager");
+ sServiceMap.put("SEARCH_SERVICE", "android.app.SearchManager");
+ sServiceMap.put("SENSOR_SERVICE", "android.hardware.SensorManager");
+ sServiceMap.put("STORAGE_SERVICE", "android.os.storage.StorageManager");
+ sServiceMap.put("TELECOM_SERVICE", "android.telecom.TelecomManager");
+ sServiceMap.put("TELEPHONY_SERVICE", "android.telephony.TelephonyManager");
+ sServiceMap.put("TELEPHONY_SUBSCRIPTION_SERVICE", "android.telephony.SubscriptionManager");
+ sServiceMap.put("TEXT_SERVICES_MANAGER_SERVICE", "android.view.textservice.TextServicesManager");
+ sServiceMap.put("TV_INPUT_SERVICE", "android.media.tv.TvInputManager");
+ sServiceMap.put("UI_MODE_SERVICE", "android.app.UiModeManager");
+ sServiceMap.put("USAGE_STATS_SERVICE", "android.app.usage.UsageStatsManager");
+ sServiceMap.put("USB_SERVICE", "android.hardware.usb.UsbManager");
+ sServiceMap.put("USER_SERVICE", "android.os.UserManager");
+ sServiceMap.put("VIBRATOR_SERVICE", "android.os.Vibrator");
+ sServiceMap.put("WALLPAPER_SERVICE", "com.android.server.WallpaperService");
+ sServiceMap.put("WIFI_P2P_SERVICE", "android.net.wifi.p2p.WifiP2pManager");
+ sServiceMap.put("WIFI_SERVICE", "android.net.wifi.WifiManager");
+ sServiceMap.put("WINDOW_SERVICE", "android.view.WindowManager");
+
+ assert sServiceMap.size() == EXPECTED_SIZE : sServiceMap.size();
+ }
+
+ return sServiceMap;
+ }
+
+ private static Map<String, String> sServiceMap;
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetTextDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetTextDetector.java
new file mode 100644
index 0000000..fc55166
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SetTextDetector.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.StringLiteral;
+
+/**
+ * Checks for errors related to TextView#setText and internationalization
+ */
+public class SetTextDetector extends Detector implements JavaScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ SetTextDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Constructing SimpleDateFormat without an explicit locale */
+ public static final Issue SET_TEXT_I18N = Issue.create(
+ "SetTextI18n", //$NON-NLS-1$
+ "TextView Internationalization",
+
+ "When calling `TextView#setText`\n" +
+ "* Never call `Number#toString()` to format numbers; it will not handle fraction " +
+ "separators and locale-specific digits properly. Consider using `String#format` " +
+ "with proper format specifications (`%d` or `%f`) instead.\n" +
+ "* Do not pass a string literal (e.g. \"Hello\") to display text. Hardcoded " +
+ "text can not be properly translated to other languages. Consider using Android " +
+ "resource strings instead.\n" +
+ "* Do not build messages by concatenating text chunks. Such messages can not be " +
+ "properly translated.",
+
+ Category.I18N,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .addMoreInfo("http://developer.android.com/guide/topics/resources/localization.html");
+
+
+ private static final String METHOD_NAME = "setText";
+ private static final String TO_STRING_NAME = "toString";
+ private static final String CHAR_SEQUENCE_CLS = "java.lang.CharSequence";
+ private static final String NUMBER_CLS = "java.lang.Number";
+ private static final String TEXT_VIEW_CLS = "android.widget.TextView";
+
+ // Pattern to match string literal that require translation. These are those having word
+ // characters in it.
+ private static final String WORD_PATTERN = ".*\\w{2,}.*";
+
+ /** Constructs a new {@link SetTextDetector} */
+ public SetTextDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList(METHOD_NAME);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation call) {
+ ResolvedMethod method = (ResolvedMethod) context.resolve(call);
+ if (method != null && method.getContainingClass().matches(TEXT_VIEW_CLS)
+ && method.matches(METHOD_NAME)
+ && method.getArgumentCount() > 0
+ && method.getArgumentType(0).matchesSignature(CHAR_SEQUENCE_CLS)) {
+ checkNode(context, call.astArguments().first());
+ }
+ }
+
+ private static void checkNode(JavaContext context, Node node) {
+ if (node instanceof StringLiteral) {
+ if (((StringLiteral) node).astValue().matches(WORD_PATTERN)) {
+ context.report(SET_TEXT_I18N, node, context.getLocation(node),
+ "String literal in `setText` can not be translated. Use Android "
+ + "resources instead.");
+ }
+ } else if (node instanceof MethodInvocation) {
+ ResolvedMethod rm = (ResolvedMethod) context.resolve(node);
+ if (rm != null && rm.getName().matches(TO_STRING_NAME)) {
+ ResolvedClass superClass = rm.getContainingClass().getSuperClass();
+ if (superClass != null && superClass.matches(NUMBER_CLS)) {
+ context.report(SET_TEXT_I18N, node, context.getLocation(node),
+ "Number formatting does not take into account locale settings. " +
+ "Consider using `String.format` instead.");
+ }
+ }
+ } else if (node instanceof BinaryExpression) {
+ BinaryExpression expression = (BinaryExpression) node;
+ if (expression.astOperator() == BinaryOperator.PLUS) {
+ context.report(SET_TEXT_I18N, node, context.getLocation(node),
+ "Do not concatenate text displayed with `setText`. "
+ + "Use resource string with placeholders.");
+ }
+ checkNode(context, expression.astLeft());
+ checkNode(context, expression.astRight());
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
new file mode 100644
index 0000000..3b0bd09
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.Assert;
+import lombok.ast.AstVisitor;
+import lombok.ast.Block;
+import lombok.ast.Case;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.DoWhile;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.For;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.If;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.Return;
+import lombok.ast.Statement;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableReference;
+import lombok.ast.While;
+
+/**
+ * Detector looking for SharedPreferences.edit() calls without a corresponding
+ * commit() or apply() call
+ */
+public class SharedPrefsDetector extends Detector implements Detector.JavaScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "CommitPrefEdits", //$NON-NLS-1$
+ "Missing `commit()` on `SharedPreference` editor",
+
+ "After calling `edit()` on a `SharedPreference`, you must call `commit()` " +
+ "or `apply()` on the editor to save the results.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ SharedPrefsDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ public static final String ANDROID_CONTENT_SHARED_PREFERENCES =
+ "android.content.SharedPreferences"; //$NON-NLS-1$
+ private static final String ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR =
+ "android.content.SharedPreferences.Editor"; //$NON-NLS-1$
+
+ /** Constructs a new {@link SharedPrefsDetector} check */
+ public SharedPrefsDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList("edit"); //$NON-NLS-1$
+ }
+
+ @Nullable
+ private static NormalTypeBody findSurroundingTypeBody(Node scope) {
+ while (scope != null) {
+ Class<? extends Node> type = scope.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == NormalTypeBody.class) {
+ return (NormalTypeBody) scope;
+ }
+
+ scope = scope.getParent();
+ }
+
+ return null;
+ }
+
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ assert node.astName().astValue().equals("edit");
+
+ boolean verifiedType = false;
+ ResolvedNode resolve = context.resolve(node);
+ if (resolve instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolve;
+ TypeDescriptor returnType = method.getReturnType();
+ if (returnType == null ||
+ !returnType.matchesName(ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR)) {
+ return;
+ }
+ verifiedType = true;
+ }
+
+ Expression operand = node.astOperand();
+ if (operand == null) {
+ return;
+ }
+
+ // Looking for the specific pattern where you assign the edit() result
+ // to a local variable; this means we won't recognize some other usages
+ // of the API (e.g. assigning it to a previously declared variable) but
+ // is needed until we have type attribution in the AST itself.
+ Node parent = node.getParent();
+
+ VariableDefinition definition = getLhs(parent);
+ boolean allowCommitBeforeTarget;
+ if (definition == null) {
+ if (operand instanceof VariableReference) {
+ if (!verifiedType) {
+ NormalTypeBody body = findSurroundingTypeBody(parent);
+ if (body == null) {
+ return;
+ }
+ String variableName = ((VariableReference) operand).astIdentifier().astValue();
+ String type = getFieldType(body, variableName);
+ if (type == null || !type.equals("SharedPreferences")) { //$NON-NLS-1$
+ return;
+ }
+ }
+ allowCommitBeforeTarget = true;
+ } else {
+ allowCommitBeforeTarget = false;
+ }
+ } else {
+ if (!verifiedType) {
+ String type = definition.astTypeReference().toString();
+ if (!type.endsWith("SharedPreferences.Editor")) { //$NON-NLS-1$
+ if (!type.equals("Editor") || //$NON-NLS-1$
+ !LintUtils.isImported(context.getCompilationUnit(),
+ ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR)) {
+ return;
+ }
+ }
+ }
+ allowCommitBeforeTarget = false;
+ }
+
+ Node method = JavaContext.findSurroundingMethod(parent);
+ if (method == null) {
+ return;
+ }
+
+ CommitFinder finder = new CommitFinder(context, node, allowCommitBeforeTarget);
+ method.accept(finder);
+ if (!finder.isCommitCalled()) {
+ context.report(ISSUE, method, context.getLocation(node),
+ "`SharedPreferences.edit()` without a corresponding `commit()` or `apply()` call");
+ }
+ }
+
+ @Nullable
+ private static String getFieldType(@NonNull NormalTypeBody cls, @NonNull String name) {
+ List<Node> children = cls.getChildren();
+ for (Node child : children) {
+ if (child.getClass() == VariableDeclaration.class) {
+ VariableDeclaration declaration = (VariableDeclaration) child;
+ VariableDefinition definition = declaration.astDefinition();
+ return definition.astTypeReference().toString();
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static VariableDefinition getLhs(@NonNull Node node) {
+ while (node != null) {
+ Class<? extends Node> type = node.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) {
+ return null;
+ }
+ if (type == VariableDefinition.class) {
+ return (VariableDefinition) node;
+ }
+
+ node = node.getParent();
+ }
+
+ return null;
+ }
+
+ private static class CommitFinder extends ForwardingAstVisitor {
+ /** The target edit call */
+ private final MethodInvocation mTarget;
+ /** whether it allows the commit call to be seen before the target node */
+ private final boolean mAllowCommitBeforeTarget;
+
+ private final JavaContext mContext;
+
+ /** Whether we've found one of the commit/cancel methods */
+ private boolean mFound;
+ /** Whether we've seen the target edit node yet */
+ private boolean mSeenTarget;
+
+ private CommitFinder(JavaContext context, MethodInvocation target,
+ boolean allowCommitBeforeTarget) {
+ mContext = context;
+ mTarget = target;
+ mAllowCommitBeforeTarget = allowCommitBeforeTarget;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node == mTarget) {
+ mSeenTarget = true;
+
+ // Look for chained calls on the target node
+ Node parent = node.getParent();
+ while (parent instanceof MethodInvocation) {
+ MethodInvocation methodInvocation = (MethodInvocation) parent;
+ String name = methodInvocation.astName().astValue();
+ boolean isCommit = "commit".equals(name);
+ if (isCommit || "apply".equals(name)) {
+ mFound = true;
+ if (isCommit) {
+ suggestApplyIfApplicable(node);
+ }
+ break;
+ }
+
+ parent = parent.getParent();
+ }
+
+ } else if (mAllowCommitBeforeTarget || mSeenTarget || node.astOperand() == mTarget) {
+ String name = node.astName().astValue();
+ boolean isCommit = "commit".equals(name);
+ if (isCommit || "apply".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
+ // TODO: Do more flow analysis to see whether we're really calling commit/apply
+ // on the right type of object?
+ mFound = true;
+
+ if (isCommit) {
+ suggestApplyIfApplicable(node);
+ }
+ }
+ }
+
+ return super.visitMethodInvocation(node);
+ }
+
+ private void suggestApplyIfApplicable(MethodInvocation node) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ ResolvedClass clz = method.getContainingClass();
+ if (clz.isSubclassOf("android.content.SharedPreferences.Editor", false)
+ && mContext.getProject().getMinSdkVersion().getApiLevel() >= 9) {
+ // See if the return value is read: can only replace commit with
+ // apply if the return value is not considered
+ Node parent = node.getParent();
+ boolean returnValueIgnored = false;
+ if (parent instanceof MethodDeclaration ||
+ parent instanceof ConstructorDeclaration ||
+ parent instanceof ClassDeclaration ||
+ parent instanceof Block ||
+ parent instanceof ExpressionStatement) {
+ returnValueIgnored = true;
+ } else if (parent instanceof Statement) {
+ if (parent instanceof If) {
+ returnValueIgnored = ((If) parent).astCondition() != node;
+ } else if (parent instanceof Return) {
+ returnValueIgnored = false;
+ } else if (parent instanceof VariableDeclaration) {
+ returnValueIgnored = false;
+ } else if (parent instanceof For) {
+ returnValueIgnored = ((For) parent).astCondition() != node;
+ } else if (parent instanceof While) {
+ returnValueIgnored = ((While) parent).astCondition() != node;
+ } else if (parent instanceof DoWhile) {
+ returnValueIgnored = ((DoWhile) parent).astCondition() != node;
+ } else if (parent instanceof Case) {
+ returnValueIgnored = ((Case) parent).astCondition() != node;
+ } else if (parent instanceof Assert) {
+ returnValueIgnored = ((Assert) parent).astAssertion() != node;
+ } else {
+ returnValueIgnored = true;
+ }
+ }
+ if (returnValueIgnored) {
+ String message = "Consider using `apply()` instead; `commit` writes "
+ + "its data to persistent storage immediately, whereas "
+ + "`apply` will handle it in the background";
+ mContext.report(ISSUE, node, mContext.getLocation(node), message);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean visitReturn(Return node) {
+ if (node.astValue() == mTarget) {
+ // If you just do "return editor.commit() don't warn
+ mFound = true;
+ }
+ return super.visitReturn(node);
+ }
+
+ boolean isCommitCalled() {
+ return mFound;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SignatureOrSystemDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SignatureOrSystemDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SignatureOrSystemDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SignatureOrSystemDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetector.java
new file mode 100644
index 0000000..393458a
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetector.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.StrictListAccessor;
+
+public class SslCertificateSocketFactoryDetector extends Detector
+ implements Detector.JavaScanner {
+
+ private static final Implementation IMPLEMENTATION_JAVA = new Implementation(
+ SslCertificateSocketFactoryDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ public static final Issue CREATE_SOCKET = Issue.create(
+ "SSLCertificateSocketFactoryCreateSocket", //$NON-NLS-1$
+ "Insecure call to `SSLCertificateSocketFactory.createSocket()`",
+ "When `SSLCertificateSocketFactory.createSocket()` is called with an `InetAddress` " +
+ "as the first parameter, TLS/SSL hostname verification is not performed, which " +
+ "could result in insecure network traffic caused by trusting arbitrary " +
+ "hostnames in TLS/SSL certificates presented by peers. In this case, developers " +
+ "must ensure that the `InetAddress` is explicitly verified against the certificate " +
+ "through other means, such as by calling " +
+ "`SSLCertificateSocketFactory.getDefaultHostnameVerifier() to get a " +
+ "`HostnameVerifier` and calling `HostnameVerifier.verify()`.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION_JAVA);
+
+ public static final Issue GET_INSECURE = Issue.create(
+ "SSLCertificateSocketFactoryGetInsecure", //$NON-NLS-1$
+ "Call to `SSLCertificateSocketFactory.getInsecure()`",
+ "The `SSLCertificateSocketFactory.getInsecure()` method returns " +
+ "an SSLSocketFactory with all TLS/SSL security checks disabled, which " +
+ "could result in insecure network traffic caused by trusting arbitrary " +
+ "TLS/SSL certificates presented by peers. This method should be " +
+ "avoided unless needed for a special circumstance such as " +
+ "debugging. Instead, `SSLCertificateSocketFactory.getDefault()` " +
+ "should be used.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION_JAVA);
+
+ private static final String INET_ADDRESS_CLASS =
+ "java.net.InetAddress";
+
+ private static final String SSL_CERTIFICATE_SOCKET_FACTORY_CLASS =
+ "android.net.SSLCertificateSocketFactory";
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements Detector.JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ // Detect calls to potentially insecure SSLCertificateSocketFactory methods
+ return Arrays.asList("createSocket", "getInsecure");
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ String methodName = node.astName().astValue();
+ ResolvedClass resolvedClass = ((ResolvedMethod) resolved).getContainingClass();
+ if (resolvedClass.isSubclassOf(SSL_CERTIFICATE_SOCKET_FACTORY_CLASS, false)) {
+ if ("createSocket".equals(methodName)) {
+ StrictListAccessor<Expression, MethodInvocation> argumentList =
+ node.astArguments();
+ if (argumentList != null) {
+ TypeDescriptor firstParameterType = context.getType(argumentList.first());
+ if (firstParameterType != null) {
+ ResolvedClass firstParameterClass = firstParameterType.getTypeClass();
+ if (firstParameterClass != null &&
+ firstParameterClass.isSubclassOf(INET_ADDRESS_CLASS, false)) {
+ context.report(CREATE_SOCKET, node, context.getLocation(node),
+ "Use of `SSLCertificateSocketFactory.createSocket()` " +
+ "with an InetAddress parameter can cause insecure " +
+ "network traffic due to trusting arbitrary hostnames in " +
+ "TLS/SSL certificates presented by peers");
+ }
+ }
+ }
+ } else if ("getInsecure".equals(methodName)) {
+ context.report(GET_INSECURE, node, context.getLocation(node),
+ "Use of `SSLCertificateSocketFactory.getInsecure()` can cause " +
+ "insecure network traffic due to trusting arbitrary TLS/SSL " +
+ "certificates presented by peers");
+ }
+ }
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
new file mode 100644
index 0000000..6031729
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
@@ -0,0 +1,1774 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.CLASS_CONTEXT;
+import static com.android.SdkConstants.CLASS_FRAGMENT;
+import static com.android.SdkConstants.CLASS_RESOURCES;
+import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.FORMAT_METHOD;
+import static com.android.SdkConstants.GET_STRING_METHOD;
+import static com.android.SdkConstants.R_CLASS;
+import static com.android.SdkConstants.TAG_STRING;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BOOLEAN;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_BYTE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_CHAR;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_DOUBLE;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_FLOAT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_NULL;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_OBJECT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_SHORT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_STRING;
+import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Location.Handle;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import lombok.ast.ArrayCreation;
+import lombok.ast.ArrayDimension;
+import lombok.ast.ArrayInitializer;
+import lombok.ast.AstVisitor;
+import lombok.ast.BooleanLiteral;
+import lombok.ast.CharLiteral;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Expression;
+import lombok.ast.FloatingPointLiteral;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.NullLiteral;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.StringLiteral;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Check which looks for problems with formatting strings such as inconsistencies between
+ * translations or between string declaration and string usage in Java.
+ * <p>
+ * TODO: Verify booleans!
+ * TODO: Handle Resources.getQuantityString as well
+ */
+public class StringFormatDetector extends ResourceXmlDetector implements Detector.JavaScanner {
+ private static final Implementation IMPLEMENTATION_XML = new Implementation(
+ StringFormatDetector.class,
+ Scope.ALL_RESOURCES_SCOPE);
+
+ @SuppressWarnings("unchecked")
+ private static final Implementation IMPLEMENTATION_XML_AND_JAVA = new Implementation(
+ StringFormatDetector.class,
+ EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE),
+ Scope.JAVA_FILE_SCOPE);
+
+
+ /** Whether formatting strings are invalid */
+ public static final Issue INVALID = Issue.create(
+ "StringFormatInvalid", //$NON-NLS-1$
+ "Invalid format string",
+
+ "If a string contains a '%' character, then the string may be a formatting string " +
+ "which will be passed to `String.format` from Java code to replace each '%' " +
+ "occurrence with specific values.\n" +
+ "\n" +
+ "This lint warning checks for two related problems:\n" +
+ "(1) Formatting strings that are invalid, meaning that `String.format` will throw " +
+ "exceptions at runtime when attempting to use the format string.\n" +
+ "(2) Strings containing '%' that are not formatting strings getting passed to " +
+ "a `String.format` call. In this case the '%' will need to be escaped as '%%'.\n" +
+ "\n" +
+ "NOTE: Not all Strings which look like formatting strings are intended for " +
+ "use by `String.format`; for example, they may contain date formats intended " +
+ "for `android.text.format.Time#format()`. Lint cannot always figure out that " +
+ "a String is a date format, so you may get false warnings in those scenarios. " +
+ "See the suppress help topic for information on how to suppress errors in " +
+ "that case.",
+
+ Category.MESSAGES,
+ 9,
+ Severity.ERROR,
+ IMPLEMENTATION_XML);
+
+ /** Whether formatting argument types are consistent across translations */
+ public static final Issue ARG_COUNT = Issue.create(
+ "StringFormatCount", //$NON-NLS-1$
+ "Formatting argument types incomplete or inconsistent",
+
+ "When a formatted string takes arguments, it usually needs to reference the " +
+ "same arguments in all translations (or all arguments if there are no " +
+ "translations.\n" +
+ "\n" +
+ "There are cases where this is not the case, so this issue is a warning rather " +
+ "than an error by default. However, this usually happens when a language is not " +
+ "translated or updated correctly.",
+ Category.MESSAGES,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION_XML);
+
+ /** Whether the string format supplied in a call to String.format matches the format string */
+ public static final Issue ARG_TYPES = Issue.create(
+ "StringFormatMatches", //$NON-NLS-1$
+ "`String.format` string doesn't match the XML format string",
+
+ "This lint check ensures the following:\n" +
+ "(1) If there are multiple translations of the format string, then all translations " +
+ "use the same type for the same numbered arguments\n" +
+ "(2) The usage of the format string in Java is consistent with the format string, " +
+ "meaning that the parameter types passed to String.format matches those in the " +
+ "format string.",
+ Category.MESSAGES,
+ 9,
+ Severity.ERROR,
+ IMPLEMENTATION_XML_AND_JAVA);
+
+ /** This plural does not use the quantity value */
+ public static final Issue POTENTIAL_PLURAL = Issue.create(
+ "PluralsCandidate", //$NON-NLS-1$
+ "Potential Plurals",
+
+ "This lint check looks for potential errors in internationalization where you have " +
+ "translated a message which involves a quantity and it looks like other parts of " +
+ "the string may need grammatical changes.\n" +
+ "\n" +
+ "For example, rather than something like this:\n" +
+ " <string name=\"try_again\">Try again in %d seconds.</string>\n" +
+ "you should be using a plural:\n" +
+ " <plurals name=\"try_again\">\n" +
+ " <item quantity=\"one\">Try again in %d second</item>\n" +
+ " <item quantity=\"other\">Try again in %d seconds</item>\n" +
+ " </plurals>\n" +
+ "This will ensure that in other languages the right set of translations are " +
+ "provided for the different quantity classes.\n" +
+ "\n" +
+ "(This check depends on some heuristics, so it may not accurately determine whether " +
+ "a string really should be a quantity. You can use tools:ignore to filter out false " +
+ "positives.",
+
+ Category.MESSAGES,
+ 5,
+ Severity.WARNING,
+ IMPLEMENTATION_XML).addMoreInfo(
+ "http://developer.android.com/guide/topics/resources/string-resource.html#Plurals");
+
+ /**
+ * Map from a format string name to a list of declaration file and actual
+ * formatting string content. We're using a list since a format string can be
+ * defined multiple times, usually for different translations.
+ */
+ private Map<String, List<Pair<Handle, String>>> mFormatStrings;
+
+ /**
+ * Map of strings that do not contain any formatting.
+ */
+ private final Map<String, Handle> mNotFormatStrings = new HashMap<String, Handle>();
+
+ /**
+ * Set of strings that have an unknown format such as date formatting; we should not
+ * flag these as invalid when used from a String#format call
+ */
+ private Set<String> mIgnoreStrings;
+
+ /** Constructs a new {@link StringFormatDetector} check */
+ public StringFormatDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.VALUES;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ if (LintUtils.endsWith(file.getName(), DOT_JAVA)) {
+ return mFormatStrings != null;
+ }
+
+ return super.appliesTo(context, file);
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(TAG_STRING);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ NodeList childNodes = element.getChildNodes();
+ if (childNodes.getLength() > 0) {
+ if (childNodes.getLength() == 1) {
+ Node child = childNodes.item(0);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ checkTextNode(context, element, strip(child.getNodeValue()));
+ }
+ } else {
+ // Concatenate children and build up a plain string.
+ // This is needed to handle xliff localization documents,
+ // but this needs more work so ignore compound XML documents as
+ // string values for now:
+ StringBuilder sb = new StringBuilder();
+ addText(sb, element);
+ if (sb.length() > 0) {
+ checkTextNode(context, element, sb.toString());
+ }
+ }
+ }
+ }
+
+ private static void addText(StringBuilder sb, Node node) {
+ if (node.getNodeType() == Node.TEXT_NODE) {
+ sb.append(strip(node.getNodeValue().trim()));
+ } else {
+ NodeList childNodes = node.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ addText(sb, childNodes.item(i));
+ }
+ }
+ }
+
+ private static String strip(String s) {
+ if (s.length() < 2) {
+ return s;
+ }
+ char first = s.charAt(0);
+ char last = s.charAt(s.length() - 1);
+ if (first == last && (first == '\'' || first == '"')) {
+ return s.substring(1, s.length() - 1);
+ }
+
+ return s;
+ }
+
+ private void checkTextNode(XmlContext context, Element element, String text) {
+ String name = element.getAttribute(ATTR_NAME);
+ boolean found = false;
+ boolean foundPlural = false;
+
+ // Look at the String and see if it's a format string (contains
+ // positional %'s)
+ for (int j = 0, m = text.length(); j < m; j++) {
+ char c = text.charAt(j);
+ if (c == '\\') {
+ j++;
+ }
+ if (c == '%') {
+ // Also make sure this String isn't an unformatted String
+ String formatted = element.getAttribute("formatted"); //$NON-NLS-1$
+ if (!formatted.isEmpty() && !Boolean.parseBoolean(formatted)) {
+ if (!mNotFormatStrings.containsKey(name)) {
+ Handle handle = context.createLocationHandle(element);
+ handle.setClientData(element);
+ mNotFormatStrings.put(name, handle);
+ }
+ return;
+ }
+
+ // See if it's not a format string, e.g. "Battery charge is 100%!".
+ // If so we want to record this name in a special list such that we can
+ // make sure you don't attempt to reference this string from a String.format
+ // call.
+ Matcher matcher = FORMAT.matcher(text);
+ if (!matcher.find(j)) {
+ if (!mNotFormatStrings.containsKey(name)) {
+ Handle handle = context.createLocationHandle(element);
+ handle.setClientData(element);
+ mNotFormatStrings.put(name, handle);
+ }
+ return;
+ }
+
+ String conversion = matcher.group(6);
+ int conversionClass = getConversionClass(conversion.charAt(0));
+ if (conversionClass == CONVERSION_CLASS_UNKNOWN || matcher.group(5) != null) {
+ if (mIgnoreStrings == null) {
+ mIgnoreStrings = new HashSet<String>();
+ }
+ mIgnoreStrings.add(name);
+
+ // Don't process any other strings here; some of them could
+ // accidentally look like a string, e.g. "%H" is a hash code conversion
+ // in String.format (and hour in Time formatting).
+ return;
+ }
+
+ if (conversionClass == CONVERSION_CLASS_INTEGER && !foundPlural) {
+ // See if there appears to be further text content here.
+ // Look for whitespace followed by a letter, with no punctuation in between
+ for (int k = matcher.end(); k < m; k++) {
+ char nc = text.charAt(k);
+ if (!Character.isWhitespace(nc)) {
+ if (Character.isLetter(nc)) {
+ foundPlural = checkPotentialPlural(context, element, text, k);
+ }
+ break;
+ }
+ }
+ }
+
+ found = true;
+ j++; // Ensure that when we process a "%%" we don't separately check the second %
+ }
+ }
+
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ if (name != null) {
+ Handle handle = context.createLocationHandle(element);
+ handle.setClientData(element);
+ if (found) {
+ // Record it for analysis when seen in Java code
+ if (mFormatStrings == null) {
+ mFormatStrings = new HashMap<String, List<Pair<Handle, String>>>();
+ }
+
+ List<Pair<Handle, String>> list = mFormatStrings.get(name);
+ if (list == null) {
+ list = new ArrayList<Pair<Handle, String>>();
+ mFormatStrings.put(name, list);
+ }
+ list.add(Pair.of(handle, text));
+ } else {
+ mNotFormatStrings.put(name, handle);
+ }
+ }
+ }
+
+ /**
+ * Checks whether the text begins with a non-unit word, pointing to a string
+ * that should probably be a plural instead. This
+ */
+ private static boolean checkPotentialPlural(XmlContext context, Element element, String text,
+ int wordBegin) {
+ // This method should only be called if the text is known to start with a word
+ assert Character.isLetter(text.charAt(wordBegin));
+
+ int wordEnd = wordBegin;
+ while (wordEnd < text.length()) {
+ if (!Character.isLetter(text.charAt(wordEnd))) {
+ break;
+ }
+ wordEnd++;
+ }
+
+ // Eliminate units, since those are not sentences you need to use plurals for, e.g.
+ // "Elevation gain: %1$d m (%2$d ft)"
+ // We'll determine whether something is a unit by looking for
+ // (1) Multiple uppercase characters (e.g. KB, or MiB), or better yet, uppercase characters
+ // anywhere but as the first letter
+ // (2) No vowels (e.g. ft)
+ // (3) Adjacent consonants (e.g. ft); this one can eliminate some legitimate
+ // English words as well (e.g. "the") so we should really limit this to
+ // letter pairs that are not common in English. This is probably overkill
+ // so not handled yet. Instead we use a simpler heuristic:
+ // (4) Very short "words" (1-2 letters)
+ if (wordEnd - wordBegin <= 2) {
+ // Very short word (1-2 chars): possible unit, e.g. "m", "ft", "kb", etc
+ return false;
+ }
+ boolean hasVowel = false;
+ for (int i = wordBegin; i < wordEnd; i++) {
+ // Uppercase character anywhere but first character: probably a unit (e.g. KB)
+ char c = text.charAt(i);
+ if (i > wordBegin && Character.isUpperCase(c)) {
+ return false;
+ }
+ if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'y') {
+ hasVowel = true;
+ }
+ }
+ if (!hasVowel) {
+ // No vowels: likely unit
+ return false;
+ }
+
+ String word = text.substring(wordBegin, wordEnd);
+
+ // Some other known abbreviations that we don't want to count:
+ if (word.equals("min")) {
+ return false;
+ }
+
+ // This heuristic only works in English!
+ if (LintUtils.isEnglishResource(context, true)) {
+ String message = String.format("Formatting %%d followed by words (\"%1$s\"): "
+ + "This should probably be a plural rather than a string", word);
+ context.report(POTENTIAL_PLURAL, element,
+ context.getLocation(element),
+ message);
+ // Avoid reporting multiple errors on the same string
+ // (if it contains more than one %d)
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mFormatStrings != null) {
+ boolean checkCount = context.isEnabled(ARG_COUNT);
+ boolean checkValid = context.isEnabled(INVALID);
+ boolean checkTypes = context.isEnabled(ARG_TYPES);
+
+ // Ensure that all the format strings are consistent with respect to each other;
+ // e.g. they all have the same number of arguments, they all use all the
+ // arguments, and they all use the same types for all the numbered arguments
+ for (Map.Entry<String, List<Pair<Handle, String>>> entry : mFormatStrings.entrySet()) {
+ String name = entry.getKey();
+ List<Pair<Handle, String>> list = entry.getValue();
+
+ // Check argument counts
+ if (checkCount) {
+ Handle notFormatted = mNotFormatStrings.get(name);
+ if (notFormatted != null) {
+ list = ImmutableList.<Pair<Handle, String>>builder()
+ .add(Pair.of(notFormatted, name)).addAll(list).build();
+ }
+ checkArity(context, name, list);
+ }
+
+ // Check argument types (and also make sure that the formatting strings are valid)
+ if (checkValid || checkTypes) {
+ checkTypes(context, checkValid, checkTypes, name, list);
+ }
+ }
+ }
+ }
+
+ private static void checkTypes(Context context, boolean checkValid,
+ boolean checkTypes, String name, List<Pair<Handle, String>> list) {
+ Map<Integer, String> types = new HashMap<Integer, String>();
+ Map<Integer, Handle> typeDefinition = new HashMap<Integer, Handle>();
+ for (Pair<Handle, String> pair : list) {
+ Handle handle = pair.getFirst();
+ String formatString = pair.getSecond();
+
+ //boolean warned = false;
+ Matcher matcher = FORMAT.matcher(formatString);
+ int index = 0;
+ int prevIndex = 0;
+ int nextNumber = 1;
+ while (true) {
+ if (matcher.find(index)) {
+ int matchStart = matcher.start();
+ // Make sure this is not an escaped '%'
+ for (; prevIndex < matchStart; prevIndex++) {
+ char c = formatString.charAt(prevIndex);
+ if (c == '\\') {
+ prevIndex++;
+ }
+ }
+ if (prevIndex > matchStart) {
+ // We're in an escape, ignore this result
+ index = prevIndex;
+ continue;
+ }
+
+ index = matcher.end(); // Ensure loop proceeds
+ String str = formatString.substring(matchStart, matcher.end());
+ if (str.equals("%%") || str.equals("%n")) { //$NON-NLS-1$ //$NON-NLS-2$
+ // Just an escaped %
+ continue;
+ }
+
+ if (checkValid) {
+ // Make sure it's a valid format string
+ if (str.length() > 2 && str.charAt(str.length() - 2) == ' ') {
+ char last = str.charAt(str.length() - 1);
+ // If you forget to include the conversion character, e.g.
+ // "Weight=%1$ g" instead of "Weight=%1$d g", then
+ // you're going to end up with a format string interpreted as
+ // "%1$ g". This means that the space character is interpreted
+ // as a flag character, but it can only be a flag character
+ // when used in conjunction with the numeric conversion
+ // formats (d, o, x, X). If that's not the case, make a
+ // dedicated error message
+ if (last != 'd' && last != 'o' && last != 'x' && last != 'X') {
+ Object clientData = handle.getClientData();
+ if (clientData instanceof Node) {
+ if (context.getDriver().isSuppressed(null, INVALID,
+ (Node) clientData)) {
+ return;
+ }
+ }
+
+ Location location = handle.resolve();
+ String message = String.format(
+ "Incorrect formatting string `%1$s`; missing conversion " +
+ "character in '`%2$s`' ?", name, str);
+ context.report(INVALID, location, message);
+ //warned = true;
+ continue;
+ }
+ }
+ }
+
+ if (!checkTypes) {
+ continue;
+ }
+
+ // Shouldn't throw a number format exception since we've already
+ // matched the pattern in the regexp
+ int number;
+ String numberString = matcher.group(1);
+ if (numberString != null) {
+ // Strip off trailing $
+ numberString = numberString.substring(0, numberString.length() - 1);
+ number = Integer.parseInt(numberString);
+ nextNumber = number + 1;
+ } else {
+ number = nextNumber++;
+ }
+ String format = matcher.group(6);
+ String currentFormat = types.get(number);
+ if (currentFormat == null) {
+ types.put(number, format);
+ typeDefinition.put(number, handle);
+ } else if (!currentFormat.equals(format)
+ && isIncompatible(currentFormat.charAt(0), format.charAt(0))) {
+
+ Object clientData = handle.getClientData();
+ if (clientData instanceof Node) {
+ if (context.getDriver().isSuppressed(null, ARG_TYPES,
+ (Node) clientData)) {
+ return;
+ }
+ }
+
+ Location location = handle.resolve();
+ // Attempt to limit the location range to just the formatting
+ // string in question
+ location = refineLocation(context, location, formatString,
+ matcher.start(), matcher.end());
+ Location otherLocation = typeDefinition.get(number).resolve();
+ otherLocation.setMessage("Conflicting argument type here");
+ location.setSecondary(otherLocation);
+ File f = otherLocation.getFile();
+ String message = String.format(
+ "Inconsistent formatting types for argument #%1$d in " +
+ "format string `%2$s` ('%3$s'): Found both '`%4$s`' and '`%5$s`' " +
+ "(in %6$s)",
+ number, name,
+ str,
+ currentFormat, format,
+ f.getParentFile().getName() + File.separator + f.getName());
+ //warned = true;
+ context.report(ARG_TYPES, location, message);
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Check that the format string is valid by actually attempting to instantiate
+ // it. We only do this if we haven't already complained about this string
+ // for other reasons.
+ /* Check disabled for now: it had many false reports due to conversion
+ * errors (which is expected since we just pass in strings), but once those
+ * are eliminated there aren't really any other valid error messages returned
+ * (for example, calling the formatter with bogus formatting flags always just
+ * returns a "conversion" error. It looks like we'd need to actually pass compatible
+ * arguments to trigger other types of formatting errors such as precision errors.
+ if (!warned && checkValid) {
+ try {
+ formatter.format(formatString, "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "");
+
+ } catch (IllegalFormatException t) { // TODO: UnknownFormatConversionException
+ if (!t.getLocalizedMessage().contains(" != ")
+ && !t.getLocalizedMessage().contains("Conversion")) {
+ Location location = handle.resolve();
+ context.report(INVALID, location,
+ String.format("Wrong format for %1$s: %2$s",
+ name, t.getLocalizedMessage()), null);
+ }
+ }
+ }
+ */
+ }
+ }
+
+ /**
+ * Returns true if two String.format conversions are "incompatible" (meaning
+ * that using these two for the same argument across different translations
+ * is more likely an error than intentional. Some conversions are
+ * incompatible, e.g. "d" and "s" where one is a number and string, whereas
+ * others may work (e.g. float versus integer) but are probably not
+ * intentional.
+ */
+ private static boolean isIncompatible(char conversion1, char conversion2) {
+ int class1 = getConversionClass(conversion1);
+ int class2 = getConversionClass(conversion2);
+ return class1 != class2
+ && class1 != CONVERSION_CLASS_UNKNOWN
+ && class2 != CONVERSION_CLASS_UNKNOWN;
+ }
+
+ private static final int CONVERSION_CLASS_UNKNOWN = 0;
+ private static final int CONVERSION_CLASS_STRING = 1;
+ private static final int CONVERSION_CLASS_CHARACTER = 2;
+ private static final int CONVERSION_CLASS_INTEGER = 3;
+ private static final int CONVERSION_CLASS_FLOAT = 4;
+ private static final int CONVERSION_CLASS_BOOLEAN = 5;
+ private static final int CONVERSION_CLASS_HASHCODE = 6;
+ private static final int CONVERSION_CLASS_PERCENT = 7;
+ private static final int CONVERSION_CLASS_NEWLINE = 8;
+ private static final int CONVERSION_CLASS_DATETIME = 9;
+
+ private static int getConversionClass(char conversion) {
+ // See http://developer.android.com/reference/java/util/Formatter.html
+ switch (conversion) {
+ case 't': // Time/date conversion
+ case 'T':
+ return CONVERSION_CLASS_DATETIME;
+ case 's': // string
+ case 'S': // Uppercase string
+ return CONVERSION_CLASS_STRING;
+ case 'c': // character
+ case 'C': // Uppercase character
+ return CONVERSION_CLASS_CHARACTER;
+ case 'd': // decimal
+ case 'o': // octal
+ case 'x': // hex
+ case 'X':
+ return CONVERSION_CLASS_INTEGER;
+ case 'f': // decimal float
+ case 'e': // exponential float
+ case 'E':
+ case 'g': // decimal or exponential depending on size
+ case 'G':
+ case 'a': // hex float
+ case 'A':
+ return CONVERSION_CLASS_FLOAT;
+ case 'b': // boolean
+ case 'B':
+ return CONVERSION_CLASS_BOOLEAN;
+ case 'h': // boolean
+ case 'H':
+ return CONVERSION_CLASS_HASHCODE;
+ case '%': // literal
+ return CONVERSION_CLASS_PERCENT;
+ case 'n': // literal
+ return CONVERSION_CLASS_NEWLINE;
+ }
+
+ return CONVERSION_CLASS_UNKNOWN;
+ }
+
+ private static Location refineLocation(Context context, Location location,
+ String formatString, int substringStart, int substringEnd) {
+ Position startLocation = location.getStart();
+ Position endLocation = location.getEnd();
+ if (startLocation != null && endLocation != null) {
+ int startOffset = startLocation.getOffset();
+ int endOffset = endLocation.getOffset();
+ if (startOffset >= 0) {
+ String contents = context.getClient().readFile(location.getFile());
+ if (endOffset <= contents.length() && startOffset < endOffset) {
+ int formatOffset = contents.indexOf(formatString, startOffset);
+ if (formatOffset != -1 && formatOffset <= endOffset) {
+ return Location.create(location.getFile(), contents,
+ formatOffset + substringStart, formatOffset + substringEnd);
+ }
+ }
+ }
+ }
+
+ return location;
+ }
+
+ /**
+ * Check that the number of arguments in the format string is consistent
+ * across translations, and that all arguments are used
+ */
+ private static void checkArity(Context context, String name, List<Pair<Handle, String>> list) {
+ // Check to make sure that the argument counts and types are consistent
+ int prevCount = -1;
+ for (Pair<Handle, String> pair : list) {
+ Set<Integer> indices = new HashSet<Integer>();
+ int count = getFormatArgumentCount(pair.getSecond(), indices);
+ Handle handle = pair.getFirst();
+ if (prevCount != -1 && prevCount != count) {
+ Object clientData = handle.getClientData();
+ if (clientData instanceof Node) {
+ if (context.getDriver().isSuppressed(null, ARG_COUNT, (Node) clientData)) {
+ return;
+ }
+ }
+ Location location = handle.resolve();
+ Location secondary = list.get(0).getFirst().resolve();
+ secondary.setMessage("Conflicting number of arguments here");
+ location.setSecondary(secondary);
+ String message = String.format(
+ "Inconsistent number of arguments in formatting string `%1$s`; " +
+ "found both %2$d and %3$d", name, prevCount, count);
+ context.report(ARG_COUNT, location, message);
+ break;
+ }
+
+ for (int i = 1; i <= count; i++) {
+ if (!indices.contains(i)) {
+ Object clientData = handle.getClientData();
+ if (clientData instanceof Node) {
+ if (context.getDriver().isSuppressed(null, ARG_COUNT,
+ (Node) clientData)) {
+ return;
+ }
+ }
+
+ Set<Integer> all = new HashSet<Integer>();
+ for (int j = 1; j < count; j++) {
+ all.add(j);
+ }
+ all.removeAll(indices);
+ List<Integer> sorted = new ArrayList<Integer>(all);
+ Collections.sort(sorted);
+ Location location = handle.resolve();
+ String message = String.format(
+ "Formatting string '`%1$s`' is not referencing numbered arguments %2$s",
+ name, sorted);
+ context.report(ARG_COUNT, location, message);
+ break;
+ }
+ }
+
+ prevCount = count;
+ }
+ }
+
+ // See java.util.Formatter docs
+ public static final Pattern FORMAT = Pattern.compile(
+ // Generic format:
+ // %[argument_index$][flags][width][.precision]conversion
+ //
+ "%" + //$NON-NLS-1$
+ // Argument Index
+ "(\\d+\\$)?" + //$NON-NLS-1$
+ // Flags
+ "([-+#, 0(\\<]*)?" + //$NON-NLS-1$
+ // Width
+ "(\\d+)?" + //$NON-NLS-1$
+ // Precision
+ "(\\.\\d+)?" + //$NON-NLS-1$
+ // Conversion. These are all a single character, except date/time conversions
+ // which take a prefix of t/T:
+ "([tT])?" + //$NON-NLS-1$
+ // The current set of conversion characters are
+ // b,h,s,c,d,o,x,e,f,g,a,t (as well as all those as upper-case characters), plus
+ // n for newlines and % as a literal %. And then there are all the time/date
+ // characters: HIKLm etc. Just match on all characters here since there should
+ // be at least one.
+ "([a-zA-Z%])"); //$NON-NLS-1$
+
+ /** Given a format string returns the format type of the given argument */
+ @VisibleForTesting
+ @Nullable
+ static String getFormatArgumentType(String s, int argument) {
+ Matcher matcher = FORMAT.matcher(s);
+ int index = 0;
+ int prevIndex = 0;
+ int nextNumber = 1;
+ while (true) {
+ if (matcher.find(index)) {
+ String value = matcher.group(6);
+ if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$
+ index = matcher.end();
+ continue;
+ }
+ int matchStart = matcher.start();
+ // Make sure this is not an escaped '%'
+ for (; prevIndex < matchStart; prevIndex++) {
+ char c = s.charAt(prevIndex);
+ if (c == '\\') {
+ prevIndex++;
+ }
+ }
+ if (prevIndex > matchStart) {
+ // We're in an escape, ignore this result
+ index = prevIndex;
+ continue;
+ }
+
+ // Shouldn't throw a number format exception since we've already
+ // matched the pattern in the regexp
+ int number;
+ String numberString = matcher.group(1);
+ if (numberString != null) {
+ // Strip off trailing $
+ numberString = numberString.substring(0, numberString.length() - 1);
+ number = Integer.parseInt(numberString);
+ nextNumber = number + 1;
+ } else {
+ number = nextNumber++;
+ }
+
+ if (number == argument) {
+ return matcher.group(6);
+ }
+ index = matcher.end();
+ } else {
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Given a format string returns the number of required arguments. If the
+ * {@code seenArguments} parameter is not null, put the indices of any
+ * observed arguments into it.
+ */
+ @VisibleForTesting
+ static int getFormatArgumentCount(@NonNull String s, @Nullable Set<Integer> seenArguments) {
+ Matcher matcher = FORMAT.matcher(s);
+ int index = 0;
+ int prevIndex = 0;
+ int nextNumber = 1;
+ int max = 0;
+ while (true) {
+ if (matcher.find(index)) {
+ String value = matcher.group(6);
+ if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$
+ index = matcher.end();
+ continue;
+ }
+ int matchStart = matcher.start();
+ // Make sure this is not an escaped '%'
+ for (; prevIndex < matchStart; prevIndex++) {
+ char c = s.charAt(prevIndex);
+ if (c == '\\') {
+ prevIndex++;
+ }
+ }
+ if (prevIndex > matchStart) {
+ // We're in an escape, ignore this result
+ index = prevIndex;
+ continue;
+ }
+
+ // Shouldn't throw a number format exception since we've already
+ // matched the pattern in the regexp
+ int number;
+ String numberString = matcher.group(1);
+ if (numberString != null) {
+ // Strip off trailing $
+ numberString = numberString.substring(0, numberString.length() - 1);
+ number = Integer.parseInt(numberString);
+ nextNumber = number + 1;
+ } else {
+ number = nextNumber++;
+ }
+
+ if (number > max) {
+ max = number;
+ }
+ if (seenArguments != null) {
+ seenArguments.add(number);
+ }
+
+ index = matcher.end();
+ } else {
+ break;
+ }
+ }
+
+ return max;
+ }
+
+ /**
+ * Determines whether the given {@link String#format(String, Object...)}
+ * formatting string is "locale dependent", meaning that its output depends
+ * on the locale. This is the case if it for example references decimal
+ * numbers of dates and times.
+ *
+ * @param format the format string
+ * @return true if the format is locale sensitive, false otherwise
+ */
+ public static boolean isLocaleSpecific(@NonNull String format) {
+ if (format.indexOf('%') == -1) {
+ return false;
+ }
+
+ Matcher matcher = FORMAT.matcher(format);
+ int index = 0;
+ int prevIndex = 0;
+ while (true) {
+ if (matcher.find(index)) {
+ int matchStart = matcher.start();
+ // Make sure this is not an escaped '%'
+ for (; prevIndex < matchStart; prevIndex++) {
+ char c = format.charAt(prevIndex);
+ if (c == '\\') {
+ prevIndex++;
+ }
+ }
+ if (prevIndex > matchStart) {
+ // We're in an escape, ignore this result
+ index = prevIndex;
+ continue;
+ }
+
+ String type = matcher.group(6);
+ if (!type.isEmpty()) {
+ char t = type.charAt(0);
+
+ // The following formatting characters are locale sensitive:
+ switch (t) {
+ case 'd': // decimal integer
+ case 'e': // scientific
+ case 'E':
+ case 'f': // decimal float
+ case 'g': // general
+ case 'G':
+ case 't': // date/time
+ case 'T':
+ return true;
+ }
+ }
+ index = matcher.end();
+ } else {
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(FORMAT_METHOD, GET_STRING_METHOD);
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ if (mFormatStrings == null && !context.getClient().supportsProjectResources()) {
+ return;
+ }
+
+ ResolvedNode resolved = context.resolve(node);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return;
+ }
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ String methodName = node.astName().astValue();
+ if (methodName.equals(FORMAT_METHOD)) {
+ if (method.getContainingClass().matches(TYPE_STRING)) {
+ // Check formatting parameters for
+ // java.lang.String#format(String format, Object... formatArgs)
+ // java.lang.String#format(Locale locale, String format, Object... formatArgs)
+ checkFormatCall(context, node, method.getArgumentCount() == 3);
+
+ // TODO: Consider also enforcing
+ // java.util.Formatter#format(String string, Object... formatArgs)
+ }
+ } else {
+ // Look up any of these string formatting methods:
+ // android.content.res.Resources#getString(@StringRes int resId, Object... formatArgs)
+ // android.content.Context#getString(@StringRes int resId, Object... formatArgs)
+ // android.app.Fragment#getString(@StringRes int resId, Object... formatArgs)
+ // android.support.v4.app.Fragment#getString(@StringRes int resId, Object... formatArgs)
+
+ // Many of these also define a plain getString method:
+ // android.content.res.Resources#getString(@StringRes int resId)
+ // However, while it's possible that these contain formatting strings) it's
+ // also possible that they're looking up strings that are not intended to be used
+ // for formatting so while we may want to warn about this it's not necessarily
+ // an error.
+ if (method.getArgumentCount() < 2) {
+ return;
+ }
+
+ ResolvedClass containingClass = method.getContainingClass();
+ if (containingClass.isSubclassOf(CLASS_RESOURCES, false) ||
+ containingClass.isSubclassOf(CLASS_CONTEXT, false) ||
+ containingClass.isSubclassOf(CLASS_FRAGMENT, false) ||
+ containingClass.isSubclassOf(CLASS_V4_FRAGMENT, false)) {
+ checkFormatCall(context, node, false);
+ }
+
+ // TODO: Consider also looking up
+ // android.content.res.Resources#getQuantityString(@PluralsRes int id, int quantity,
+ // Object... formatArgs)
+ // though this will require being smarter about cross referencing formatting
+ // strings since we'll need to go via the quantity string definitions
+ }
+ }
+
+ private void checkFormatCall(JavaContext context, MethodInvocation node,
+ boolean specifiesLocale) {
+ lombok.ast.Node current = getParentMethod(node);
+ if (current != null) {
+ checkStringFormatCall(context, current, node, specifiesLocale);
+ }
+ }
+
+ /**
+ * Checks a String.format call that is using a string that doesn't contain format placeholders.
+ * @param context the context to report errors to
+ * @param call the AST node for the {@link String#format}
+ * @param name the string name
+ * @param handle the string location
+ */
+ private static void checkNotFormattedHandle(
+ JavaContext context,
+ MethodInvocation call,
+ String name,
+ Handle handle) {
+ Object clientData = handle.getClientData();
+ if (clientData instanceof Node) {
+ if (context.getDriver().isSuppressed(null, INVALID, (Node) clientData)) {
+ return;
+ }
+ }
+ Location location = context.getLocation(call);
+ Location secondary = handle.resolve();
+ secondary.setMessage("This definition does not require arguments");
+ location.setSecondary(secondary);
+ String message = String.format(
+ "Format string '`%1$s`' is not a valid format string so it should not be " +
+ "passed to `String.format`",
+ name);
+ context.report(INVALID, call, location, message);
+ }
+
+ /**
+ * Check the given String.format call (with the given arguments) to see if the string format is
+ * being used correctly
+ *
+ * @param context the context to report errors to
+ * @param method the method containing the {@link String#format} call
+ * @param call the AST node for the {@link String#format}
+ * @param specifiesLocale whether the first parameter is a locale string, shifting the
+ * formatting string to the second argument
+ */
+ private void checkStringFormatCall(
+ JavaContext context,
+ lombok.ast.Node method,
+ MethodInvocation call,
+ boolean specifiesLocale) {
+
+ StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
+ if (args.isEmpty()) {
+ return;
+ }
+
+ StringTracker tracker = new StringTracker(context, method, call, specifiesLocale ? 1 : 0);
+ method.accept(tracker);
+ String name = tracker.getFormatStringName();
+ if (name == null) {
+ return;
+ }
+
+ if (mIgnoreStrings != null && mIgnoreStrings.contains(name)) {
+ return;
+ }
+
+ boolean passingVarArgsArray = false;
+ int callCount = args.size() - 1 - (specifiesLocale ? 1 : 0);
+ if (callCount == 1) {
+ // If instead of a varargs call like
+ // getString(R.string.foo, arg1, arg2, arg3)
+ // the code is calling the varargs method with a packed Object array, as in
+ // getString(R.string.foo, new Object[] { arg1, arg2, arg3 })
+ // we'll need to handle that such that we don't think this is a single
+ // argument
+ TypeDescriptor type = context.getType(args.last());
+ if (type != null && type.isArray() && !type.isPrimitive()) {
+ boolean knownArity = false;
+ if (args.last() instanceof ArrayCreation) {
+ ArrayCreation creation = (ArrayCreation) args.last();
+ ArrayInitializer initializer = creation.astInitializer();
+ if (initializer != null) {
+ callCount = initializer.astExpressions().size();
+ knownArity = true;
+ } else if (creation.astDimensions() != null
+ && creation.astDimensions().size() == 1) {
+ ArrayDimension first = creation.astDimensions().first();
+ Expression expression = first.astDimension();
+ if (expression instanceof IntegralLiteral) {
+ callCount = ((IntegralLiteral) expression).astIntValue();
+ knownArity = true;
+ }
+ }
+ }
+ if (!knownArity) {
+ return;
+ }
+ passingVarArgsArray = true;
+ }
+ }
+
+ if (callCount > 0 && mNotFormatStrings.containsKey(name)) {
+ checkNotFormattedHandle(context, call, name, mNotFormatStrings.get(name));
+ return;
+ }
+
+ List<Pair<Handle, String>> list = mFormatStrings != null ? mFormatStrings.get(name) : null;
+ if (list == null) {
+ LintClient client = context.getClient();
+ if (client.supportsProjectResources() &&
+ !context.getScope().contains(Scope.RESOURCE_FILE)) {
+ AbstractResourceRepository resources = client
+ .getProjectResources(context.getMainProject(), true);
+ List<ResourceItem> items;
+ if (resources != null) {
+ items = resources.getResourceItem(ResourceType.STRING, name);
+ } else {
+ // Must be a non-Android module
+ items = null;
+ }
+ if (items != null) {
+ for (final ResourceItem item : items) {
+ ResourceValue v = item.getResourceValue(false);
+ if (v != null) {
+ String value = v.getRawXmlValue();
+ if (value != null) {
+ // Make sure it's really a formatting string,
+ // not for example "Battery remaining: 90%"
+ boolean isFormattingString = value.indexOf('%') != -1;
+ for (int j = 0, m = value.length();
+ j < m && isFormattingString;
+ j++) {
+ char c = value.charAt(j);
+ if (c == '\\') {
+ j++;
+ } else if (c == '%') {
+ Matcher matcher = FORMAT.matcher(value);
+ if (!matcher.find(j)) {
+ isFormattingString = false;
+ } else {
+ String conversion = matcher.group(6);
+ int conversionClass = getConversionClass(
+ conversion.charAt(0));
+ if (conversionClass == CONVERSION_CLASS_UNKNOWN
+ || matcher.group(5) != null) {
+ // Some date format etc - don't process
+ return;
+ }
+ }
+ j++; // Don't process second % in a %%
+ }
+ // If the user marked the string with
+ }
+
+ Handle handle = client.createResourceItemHandle(item);
+ if (isFormattingString) {
+ if (list == null) {
+ list = Lists.newArrayList();
+ if (mFormatStrings == null) {
+ mFormatStrings = Maps.newHashMap();
+ }
+ mFormatStrings.put(name, list);
+ }
+ list.add(Pair.of(handle, value));
+ } else if (callCount > 0) {
+ checkNotFormattedHandle(context, call, name, handle);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ return;
+ }
+ }
+
+ if (list != null) {
+ Set<String> reported = null;
+ for (Pair<Handle, String> pair : list) {
+ String s = pair.getSecond();
+ if (reported != null && reported.contains(s)) {
+ continue;
+ }
+ int count = getFormatArgumentCount(s, null);
+ Handle handle = pair.getFirst();
+ if (count != callCount) {
+ Location location = context.getLocation(call);
+ Location secondary = handle.resolve();
+ secondary.setMessage(String.format("This definition requires %1$d arguments",
+ count));
+ location.setSecondary(secondary);
+ String message = String.format(
+ "Wrong argument count, format string `%1$s` requires `%2$d` but format " +
+ "call supplies `%3$d`",
+ name, count, callCount);
+ context.report(ARG_TYPES, method, location, message);
+ if (reported == null) {
+ reported = Sets.newHashSet();
+ }
+ reported.add(s);
+ } else {
+ if (passingVarArgsArray) {
+ // Can't currently check these: make sure we don't incorrectly
+ // flag parameters on the Object[] instead of the wrapped parameters
+ return;
+ }
+ for (int i = 1; i <= count; i++) {
+ int argumentIndex = i + (specifiesLocale ? 1 : 0);
+ Class<?> type = tracker.getArgumentType(argumentIndex);
+ if (type != null) {
+ boolean valid = true;
+ String formatType = getFormatArgumentType(s, i);
+ if (formatType == null) {
+ continue;
+ }
+ char last = formatType.charAt(formatType.length() - 1);
+ if (formatType.length() >= 2 &&
+ Character.toLowerCase(
+ formatType.charAt(formatType.length() - 2)) == 't') {
+ // Date time conversion.
+ // TODO
+ continue;
+ }
+ switch (last) {
+ // Booleans. It's okay to pass objects to these;
+ // it will print "true" if non-null, but it's
+ // unusual and probably not intended.
+ case 'b':
+ case 'B':
+ valid = type == Boolean.TYPE;
+ break;
+
+ // Numeric: integer and floats in various formats
+ case 'x':
+ case 'X':
+ case 'd':
+ case 'o':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ case 'a':
+ case 'A':
+ valid = type == Integer.TYPE
+ || type == Float.TYPE
+ || type == Double.TYPE
+ || type == Long.TYPE
+ || type == Byte.TYPE
+ || type == Short.TYPE;
+ break;
+ case 'c':
+ case 'C':
+ // Unicode character
+ valid = type == Character.TYPE;
+ break;
+ case 'h':
+ case 'H': // Hex print of hash code of objects
+ case 's':
+ case 'S':
+ // String. Can pass anything, but warn about
+ // numbers since you may have meant more
+ // specific formatting. Use special issue
+ // explanation for this?
+ valid = type != Boolean.TYPE &&
+ !Number.class.isAssignableFrom(type);
+ break;
+ }
+
+ if (!valid) {
+ Expression argument = tracker.getArgument(argumentIndex);
+ Location location = context.getLocation(argument);
+ Location secondary = handle.resolve();
+ secondary.setMessage("Conflicting argument declaration here");
+ location.setSecondary(secondary);
+
+ String message = String.format(
+ "Wrong argument type for formatting argument '#%1$d' " +
+ "in `%2$s`: conversion is '`%3$s`', received `%4$s` " +
+ "(argument #%5$d in method call)",
+ i, name, formatType, type.getSimpleName(),
+ argumentIndex + 1);
+ context.report(ARG_TYPES, method, location, message);
+ if (reported == null) {
+ reported = Sets.newHashSet();
+ }
+ reported.add(s);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static boolean isLocaleReference(@Nullable TypeDescriptor reference) {
+ return reference != null && isLocaleReference(reference.getName());
+ }
+
+ private static boolean isLocaleReference(@Nullable String typeName) {
+ return typeName != null && (typeName.equals("Locale") //$NON-NLS-1$
+ || typeName.equals("java.util.Locale")); //$NON-NLS-1$
+ }
+
+ /** Returns the parent method of the given AST node */
+ @Nullable
+ public static lombok.ast.Node getParentMethod(@NonNull lombok.ast.Node node) {
+ lombok.ast.Node current = node.getParent();
+ while (current != null
+ && !(current instanceof MethodDeclaration)
+ && !(current instanceof ConstructorDeclaration)) {
+ current = current.getParent();
+ }
+
+ return current;
+ }
+
+ /** Returns the resource name corresponding to the first argument in the given call */
+ @Nullable
+ public static String getResourceForFirstArg(
+ @NonNull lombok.ast.Node method,
+ @NonNull lombok.ast.Node call) {
+ assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
+ StringTracker tracker = new StringTracker(null, method, call, 0);
+ method.accept(tracker);
+
+ return tracker.getFormatStringName();
+ }
+
+ /** Returns the resource name corresponding to the given argument in the given call */
+ @Nullable
+ public static String getResourceArg(
+ @NonNull lombok.ast.Node method,
+ @NonNull lombok.ast.Node call,
+ int argIndex) {
+ assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
+ StringTracker tracker = new StringTracker(null, method, call, argIndex);
+ method.accept(tracker);
+
+ return tracker.getFormatStringName();
+ }
+
+ /**
+ * Given a variable reference, finds the original R.string value corresponding to it.
+ * For example:
+ * <pre>
+ * {@code
+ * String target = "World";
+ * String hello = getResources().getString(R.string.hello);
+ * String output = String.format(hello, target);
+ * }
+ * </pre>
+ *
+ * Given the {@code String.format} call, we want to find out what R.string resource
+ * corresponds to the first argument, in this case {@code R.string.hello}.
+ * To do this, we look for R.string references, and track those through assignments
+ * until we reach the target node.
+ * <p>
+ * In addition, it also does some primitive type tracking such that it (in some cases)
+ * can answer questions about the types of variables. This allows it to check whether
+ * certain argument types are valid. Note however that it does not do full-blown
+ * type analysis by checking method call signatures and so on.
+ */
+ private static class StringTracker extends ForwardingAstVisitor {
+ /** Method we're searching within */
+ private final lombok.ast.Node mTop;
+ /** The argument index in the method we're targeting */
+ private final int mArgIndex;
+ /** Map from variable name to corresponding string resource name */
+ private final Map<String, String> mMap = new HashMap<String, String>();
+ /** Map from variable name to corresponding type */
+ private final Map<String, Class<?>> mTypes = new HashMap<String, Class<?>>();
+ /** The AST node for the String.format we're interested in */
+ private final lombok.ast.Node mTargetNode;
+ private boolean mDone;
+ @Nullable
+ private JavaContext mContext;
+
+ /**
+ * Result: the name of the string resource being passed to the
+ * String.format, if any
+ */
+ private String mName;
+
+ public StringTracker(@Nullable JavaContext context, lombok.ast.Node top, lombok.ast.Node targetNode, int argIndex) {
+ mContext = context;
+ mTop = top;
+ mArgIndex = argIndex;
+ mTargetNode = targetNode;
+ }
+
+ public String getFormatStringName() {
+ return mName;
+ }
+
+ /** Returns the argument type of the given formatting argument of the
+ * target node. Note: This is in the formatting string, which is one higher
+ * than the String.format parameter number, since the first argument is the
+ * formatting string itself.
+ *
+ * @param argument the argument number
+ * @return the class (such as {@link Integer#TYPE} etc) or null if not known
+ */
+ public Class<?> getArgumentType(int argument) {
+ Expression arg = getArgument(argument);
+ if (arg != null) {
+ // Look up type based on the source code literals
+ Class<?> type = getType(arg);
+ if (type != null) {
+ return type;
+ }
+
+ // If the AST supports type resolution, use that for other types
+ // of expressions
+ if (mContext != null) {
+ return getTypeClass(mContext.getType(arg));
+ }
+ }
+
+ return null;
+ }
+
+ private static Class<?> getTypeClass(@Nullable TypeDescriptor type) {
+ if (type != null) {
+ return getTypeClass(type.getName());
+ }
+ return null;
+ }
+
+ private static Class<?> getTypeClass(@Nullable String fqcn) {
+ if (fqcn == null) {
+ return null;
+ } else if (fqcn.equals(TYPE_STRING) || fqcn.equals("String")) { //$NON-NLS-1$
+ return String.class;
+ } else if (fqcn.equals(TYPE_INT)) {
+ return Integer.TYPE;
+ } else if (fqcn.equals(TYPE_BOOLEAN)) {
+ return Boolean.TYPE;
+ } else if (fqcn.equals(TYPE_NULL)) {
+ return Object.class;
+ } else if (fqcn.equals(TYPE_LONG)) {
+ return Long.TYPE;
+ } else if (fqcn.equals(TYPE_FLOAT)) {
+ return Float.TYPE;
+ } else if (fqcn.equals(TYPE_DOUBLE)) {
+ return Double.TYPE;
+ } else if (fqcn.equals(TYPE_CHAR)) {
+ return Character.TYPE;
+ } else if (fqcn.equals("BigDecimal") //$NON-NLS-1$
+ || fqcn.equals("java.math.BigDecimal")) { //$NON-NLS-1$
+ return Float.TYPE;
+ } else if (fqcn.equals("BigInteger") //$NON-NLS-1$
+ || fqcn.equals("java.math.BigInteger")) { //$NON-NLS-1$
+ return Integer.TYPE;
+ } else if (fqcn.equals(TYPE_OBJECT)) {
+ return null;
+ } else if (fqcn.startsWith("java.lang.")) {
+ if (fqcn.equals("java.lang.Integer")
+ || fqcn.equals("java.lang.Short")
+ || fqcn.equals("java.lang.Byte")
+ || fqcn.equals("java.lang.Long")) {
+ return Integer.TYPE;
+ } else if (fqcn.equals("java.lang.Float")
+ || fqcn.equals("java.lang.Double")) {
+ return Float.TYPE;
+ } else {
+ return null;
+ }
+ } else if (fqcn.equals(TYPE_BYTE)) {
+ return Byte.TYPE;
+ } else if (fqcn.equals(TYPE_SHORT)) {
+ return Short.TYPE;
+ } else {
+ return null;
+ }
+ }
+
+ public Expression getArgument(int argument) {
+ if (!(mTargetNode instanceof MethodInvocation)) {
+ return null;
+ }
+ MethodInvocation call = (MethodInvocation) mTargetNode;
+ StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
+ if (argument >= args.size()) {
+ return null;
+ }
+
+ Iterator<Expression> iterator = args.iterator();
+ int index = 0;
+ while (iterator.hasNext()) {
+ Expression arg = iterator.next();
+ if (index++ == argument) {
+ return arg;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean visitNode(lombok.ast.Node node) {
+ return mDone || super.visitNode(node);
+
+ }
+
+ @Override
+ public boolean visitVariableReference(VariableReference node) {
+ if (node.astIdentifier().astValue().equals(R_CLASS) && //$NON-NLS-1$
+ node.getParent() instanceof Select &&
+ node.getParent().getParent() instanceof Select) {
+
+ // See if we're on the right hand side of an assignment
+ lombok.ast.Node current = node.getParent().getParent();
+ String reference = ((Select) current).astIdentifier().astValue();
+ lombok.ast.Node prev = current;
+ while (current != mTop && !(current instanceof VariableDefinitionEntry)) {
+ if (current == mTargetNode) {
+ // Make sure the reference we found was part of the
+ // target parameter (e.g. for a string format check,
+ // the actual formatting string, not one of the arguments
+ // supplied to the formatting string)
+ boolean isParameterArg = false;
+ Iterator<Expression> iterator = null;
+ if (mTargetNode instanceof MethodInvocation) {
+ MethodInvocation call = (MethodInvocation) mTargetNode;
+ iterator = call.astArguments().iterator();
+ } else if (mTargetNode instanceof ConstructorInvocation) {
+ ConstructorInvocation call = (ConstructorInvocation) mTargetNode;
+ iterator = call.astArguments().iterator();
+ }
+ if (iterator != null) {
+ Expression arg = null;
+ for (int i = 0; i <= mArgIndex; i++) {
+ if (iterator.hasNext()) {
+ arg = iterator.next();
+ } else {
+ arg = null;
+ }
+ }
+ if (arg == prev) {
+ isParameterArg = true;
+ }
+ } else {
+ // Constructor?
+ isParameterArg = true;
+ }
+ if (isParameterArg) {
+ mName = reference;
+ mDone = true;
+ return false;
+ }
+ }
+ prev = current;
+ current = current.getParent();
+ }
+ if (current instanceof VariableDefinitionEntry) {
+ VariableDefinitionEntry entry = (VariableDefinitionEntry) current;
+ String variable = entry.astName().astValue();
+ mMap.put(variable, reference);
+ }
+ }
+
+ return false;
+ }
+
+ @Nullable
+ private Expression getTargetArgument() {
+ Iterator<Expression> iterator;
+ if (mTargetNode instanceof MethodInvocation) {
+ iterator = ((MethodInvocation) mTargetNode).astArguments().iterator();
+ } else if (mTargetNode instanceof ConstructorInvocation) {
+ iterator = ((ConstructorInvocation) mTargetNode).astArguments().iterator();
+ } else {
+ return null;
+ }
+ int i = 0;
+ while (i < mArgIndex && iterator.hasNext()) {
+ iterator.next();
+ i++;
+ }
+ if (iterator.hasNext()) {
+ Expression next = iterator.next();
+ if (next != null && mContext != null && iterator.hasNext()) {
+ TypeDescriptor type = mContext.getType(next);
+ if (isLocaleReference(type)) {
+ next = iterator.next();
+ } else if (type == null
+ && next.toString().startsWith("Locale.")) { //$NON-NLS-1$
+ next = iterator.next();
+ }
+ }
+ return next;
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node == mTargetNode) {
+ Expression arg = getTargetArgument();
+ if (arg instanceof VariableReference) {
+ VariableReference reference = (VariableReference) arg;
+ String variable = reference.astIdentifier().astValue();
+ mName = mMap.get(variable);
+ mDone = true;
+ return true;
+ }
+ }
+
+ // Is this a getString() call? On a resource object? If so,
+ // promote the resource argument up to the left hand side
+ return super.visitMethodInvocation(node);
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ if (node == mTargetNode) {
+ Expression arg = getTargetArgument();
+ if (arg instanceof VariableReference) {
+ VariableReference reference = (VariableReference) arg;
+ String variable = reference.astIdentifier().astValue();
+ mName = mMap.get(variable);
+ mDone = true;
+ return true;
+ }
+ }
+
+ // Is this a getString() call? On a resource object? If so,
+ // promote the resource argument up to the left hand side
+ return super.visitConstructorInvocation(node);
+ }
+
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ String name = node.astName().astValue();
+ Expression rhs = node.astInitializer();
+ Class<?> type = getType(rhs);
+ if (type != null) {
+ mTypes.put(name, type);
+ } else {
+ // Make sure we're not visiting the String.format node itself. If you have
+ // msg = String.format("%1$s", msg)
+ // then we'd be wiping out the type of "msg" before visiting the
+ // String.format call!
+ if (rhs != mTargetNode) {
+ mTypes.remove(name);
+ }
+ }
+
+ return super.visitVariableDefinitionEntry(node);
+ }
+
+ private Class<?> getType(Expression expression) {
+ if (expression == null) {
+ return null;
+ }
+
+ if (expression instanceof VariableReference) {
+ VariableReference reference = (VariableReference) expression;
+ String variable = reference.astIdentifier().astValue();
+ Class<?> type = mTypes.get(variable);
+ if (type != null) {
+ return type;
+ }
+ } else if (expression instanceof MethodInvocation) {
+ MethodInvocation method = (MethodInvocation) expression;
+ String methodName = method.astName().astValue();
+ if (methodName.equals(GET_STRING_METHOD)) {
+ return String.class;
+ }
+ } else if (expression instanceof StringLiteral) {
+ return String.class;
+ } else if (expression instanceof IntegralLiteral) {
+ return Integer.TYPE;
+ } else if (expression instanceof FloatingPointLiteral) {
+ return Float.TYPE;
+ } else if (expression instanceof CharLiteral) {
+ return Character.TYPE;
+ } else if (expression instanceof BooleanLiteral) {
+ return Boolean.TYPE;
+ } else if (expression instanceof NullLiteral) {
+ return Object.class;
+ }
+
+ if (mContext != null) {
+ TypeDescriptor type = mContext.getType(expression);
+ if (type != null) {
+ Class<?> typeClass = getTypeClass(type);
+ if (typeClass != null) {
+ return typeClass;
+ } else {
+ return Object.class;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
new file mode 100644
index 0000000..672c637
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
@@ -0,0 +1,1719 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_VALUE;
+import static com.android.SdkConstants.CLASS_INTENT;
+import static com.android.SdkConstants.INT_DEF_ANNOTATION;
+import static com.android.SdkConstants.R_CLASS;
+import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
+import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
+import static com.android.SdkConstants.TAG_PERMISSION;
+import static com.android.SdkConstants.TAG_USES_PERMISSION;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_23;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_M;
+import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
+import static com.android.resources.ResourceType.COLOR;
+import static com.android.resources.ResourceType.DRAWABLE;
+import static com.android.resources.ResourceType.MIPMAP;
+import static com.android.tools.lint.checks.PermissionFinder.Operation.ACTION;
+import static com.android.tools.lint.checks.PermissionFinder.Operation.READ;
+import static com.android.tools.lint.checks.PermissionFinder.Operation.WRITE;
+import static com.android.tools.lint.checks.PermissionRequirement.ATTR_PROTECTION_LEVEL;
+import static com.android.tools.lint.checks.PermissionRequirement.VALUE_DANGEROUS;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_INT;
+import static com.android.tools.lint.client.api.JavaParser.TYPE_LONG;
+import static com.android.tools.lint.detector.api.JavaContext.findSurroundingMethod;
+import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceType;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.lint.checks.PermissionFinder.Operation;
+import com.android.tools.lint.checks.PermissionFinder.Result;
+import com.android.tools.lint.checks.PermissionHolder.SetPermissionLookup;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ConstantEvaluator;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Set;
+
+import lombok.ast.ArrayCreation;
+import lombok.ast.ArrayInitializer;
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.BinaryOperator;
+import lombok.ast.Catch;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.EnumConstant;
+import lombok.ast.Expression;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.FloatingPointLiteral;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.NullLiteral;
+import lombok.ast.Select;
+import lombok.ast.Statement;
+import lombok.ast.StringLiteral;
+import lombok.ast.Try;
+import lombok.ast.TypeReference;
+import lombok.ast.UnaryExpression;
+import lombok.ast.UnaryOperator;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+
+/**
+ * Looks up annotations on method calls and enforces the various things they
+ * express, e.g. for {@code @CheckReturn} it makes sure the return value is used,
+ * for {@code ColorInt} it ensures that a proper color integer is passed in, etc.
+ *
+ * TODO: Throw in some annotation usage checks here too; e.g. specifying @Size without parameters,
+ * specifying toInclusive without setting to, combining @ColorInt with any @ResourceTypeRes,
+ * using @CheckResult on a void method, etc.
+ */
+public class SupportAnnotationDetector extends Detector implements Detector.JavaScanner {
+
+ public static final Implementation IMPLEMENTATION
+ = new Implementation(SupportAnnotationDetector.class, Scope.JAVA_FILE_SCOPE);
+
+ /** Method result should be used */
+ public static final Issue RANGE = Issue.create(
+ "Range", //$NON-NLS-1$
+ "Outside Range",
+
+ "Some parameters are required to in a particular numerical range; this check " +
+ "makes sure that arguments passed fall within the range. For arrays, Strings " +
+ "and collections this refers to the size or length.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /**
+ * Attempting to set a resource id as a color
+ */
+ public static final Issue RESOURCE_TYPE = Issue.create(
+ "ResourceType", //$NON-NLS-1$
+ "Wrong Resource Type",
+
+ "Ensures that resource id's passed to APIs are of the right type; for example, " +
+ "calling `Resources.getColor(R.string.name)` is wrong.",
+
+ Category.CORRECTNESS,
+ 7,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /** Attempting to set a resource id as a color */
+ public static final Issue COLOR_USAGE = Issue.create(
+ "ResourceAsColor", //$NON-NLS-1$
+ "Should pass resolved color instead of resource id",
+
+ "Methods that take a color in the form of an integer should be passed " +
+ "an RGB triple, not the actual color resource id. You must call " +
+ "`getResources().getColor(resource)` to resolve the actual color value first.",
+
+ Category.CORRECTNESS,
+ 7,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Passing the wrong constant to an int or String method */
+ public static final Issue TYPE_DEF = Issue.create(
+ "WrongConstant", //$NON-NLS-1$
+ "Incorrect constant",
+
+ "Ensures that when parameter in a method only allows a specific set " +
+ "of constants, calls obey those rules.",
+
+ Category.SECURITY,
+ 6,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Method result should be used */
+ public static final Issue CHECK_RESULT = Issue.create(
+ "CheckResult", //$NON-NLS-1$
+ "Ignoring results",
+
+ "Some methods have no side effects, an calling them without doing something " +
+ "without the result is suspicious. ",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Failing to enforce security by just calling check permission */
+ public static final Issue CHECK_PERMISSION = Issue.create(
+ "UseCheckPermission", //$NON-NLS-1$
+ "Using the result of check permission calls",
+
+ "You normally want to use the result of checking a permission; these methods " +
+ "return whether the permission is held; they do not throw an error if the permission " +
+ "is not granted. Code which does not do anything with the return value probably " +
+ "meant to be calling the enforce methods instead, e.g. rather than " +
+ "`Context#checkCallingPermission` it should call `Context#enforceCallingPermission`.",
+
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Method result should be used */
+ public static final Issue MISSING_PERMISSION = Issue.create(
+ "MissingPermission", //$NON-NLS-1$
+ "Missing Permissions",
+
+ "This check scans through your code and libraries and looks at the APIs being used, " +
+ "and checks this against the set of permissions required to access those APIs. If " +
+ "the code using those APIs is called at runtime, then the program will crash.\n" +
+ "\n" +
+ "Furthermore, for permissions that are revocable (with targetSdkVersion 23), client " +
+ "code must also be prepared to handle the calls throwing an exception if the user " +
+ "rejects the request for permission at runtime.",
+
+ Category.CORRECTNESS,
+ 9,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Passing the wrong constant to an int or String method */
+ public static final Issue THREAD = Issue.create(
+ "WrongThread", //$NON-NLS-1$
+ "Wrong Thread",
+
+ "Ensures that a method which expects to be called on a specific thread, is actually " +
+ "called from that thread. For example, calls on methods in widgets should always " +
+ "be made on the UI thread.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ IMPLEMENTATION)
+ .addMoreInfo(
+ "http://developer.android.com/guide/components/processes-and-threads.html#Threads");
+
+ public static final String CHECK_RESULT_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "CheckResult"; //$NON-NLS-1$
+ public static final String COLOR_INT_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "ColorInt"; //$NON-NLS-1$
+ public static final String INT_RANGE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntRange"; //$NON-NLS-1$
+ public static final String FLOAT_RANGE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "FloatRange"; //$NON-NLS-1$
+ public static final String SIZE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "Size"; //$NON-NLS-1$
+ public static final String PERMISSION_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "RequiresPermission"; //$NON-NLS-1$
+ public static final String UI_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "UiThread"; //$NON-NLS-1$
+ public static final String MAIN_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "MainThread"; //$NON-NLS-1$
+ public static final String WORKER_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "WorkerThread"; //$NON-NLS-1$
+ public static final String BINDER_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "BinderThread"; //$NON-NLS-1$
+ public static final String PERMISSION_ANNOTATION_READ = PERMISSION_ANNOTATION + ".Read"; //$NON-NLS-1$
+ public static final String PERMISSION_ANNOTATION_WRITE = PERMISSION_ANNOTATION + ".Write"; //$NON-NLS-1$
+
+ public static final String RES_SUFFIX = "Res";
+ public static final String THREAD_SUFFIX = "Thread";
+ public static final String ATTR_SUGGEST = "suggest";
+ public static final String ATTR_TO = "to";
+ public static final String ATTR_FROM = "from";
+ public static final String ATTR_FROM_INCLUSIVE = "fromInclusive";
+ public static final String ATTR_TO_INCLUSIVE = "toInclusive";
+ public static final String ATTR_MULTIPLE = "multiple";
+ public static final String ATTR_MIN = "min";
+ public static final String ATTR_MAX = "max";
+ public static final String ATTR_ALL_OF = "allOf";
+ public static final String ATTR_ANY_OF = "anyOf";
+ public static final String ATTR_CONDITIONAL = "conditional";
+
+ /**
+ * Marker ResourceType used to signify that an expression is of type {@code @ColorInt},
+ * which isn't actually a ResourceType but one we want to specifically compare with.
+ * We're using {@link ResourceType#PUBLIC} because that one won't appear in the R
+ * class (and ResourceType is an enum we can't just create new constants for.)
+ */
+ public static final ResourceType COLOR_INT_MARKER_TYPE = ResourceType.PUBLIC;
+
+ /**
+ * Constructs a new {@link SupportAnnotationDetector} check
+ */
+ public SupportAnnotationDetector() {
+ }
+
+ private void checkMethodAnnotation(
+ @NonNull JavaContext context,
+ @NonNull ResolvedMethod method,
+ @NonNull Node node,
+ @NonNull ResolvedAnnotation annotation) {
+ String signature = annotation.getSignature();
+ if (CHECK_RESULT_ANNOTATION.equals(signature)
+ || signature.endsWith(".CheckReturnValue")) { // support findbugs annotation too
+ checkResult(context, node, annotation);
+ } else if (signature.equals(PERMISSION_ANNOTATION)) {
+ PermissionRequirement requirement = PermissionRequirement.create(context, annotation);
+ checkPermission(context, node, method, null, requirement);
+ } else if (signature.endsWith(THREAD_SUFFIX)
+ && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
+ checkThreading(context, node, method, signature);
+ }
+ }
+
+ private void checkParameterAnnotations(
+ @NonNull JavaContext context,
+ @NonNull Node argument,
+ @NonNull Node call,
+ @NonNull ResolvedMethod method,
+ @NonNull Iterable<ResolvedAnnotation> annotations) {
+ boolean handledResourceTypes = false;
+ for (ResolvedAnnotation annotation : annotations) {
+ String signature = annotation.getSignature();
+
+ if (COLOR_INT_ANNOTATION.equals(signature)) {
+ checkColor(context, argument);
+ } else if (signature.equals(INT_RANGE_ANNOTATION)) {
+ checkIntRange(context, annotation, argument, annotations);
+ } else if (signature.equals(FLOAT_RANGE_ANNOTATION)) {
+ checkFloatRange(context, annotation, argument);
+ } else if (signature.equals(SIZE_ANNOTATION)) {
+ checkSize(context, annotation, argument);
+ } else if (signature.startsWith(PERMISSION_ANNOTATION)) {
+ // PERMISSION_ANNOTATION, PERMISSION_ANNOTATION_READ, PERMISSION_ANNOTATION_WRITE
+ // When specified on a parameter, that indicates that we're dealing with
+ // a permission requirement on this *method* which depends on the value
+ // supplied by this parameter
+ checkParameterPermission(context, signature, call, method, argument);
+ } else {
+ // We only run @IntDef, @StringDef and @<Type>Res checks if we're not
+ // running inside Android Studio / IntelliJ where there are already inspections
+ // covering the same warnings (using IntelliJ's own data flow analysis); we
+ // don't want to (a) create redundant warnings or (b) work harder than we
+ // have to
+ if (signature.equals(INT_DEF_ANNOTATION)) {
+ boolean flag = annotation.getValue(TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE;
+ checkTypeDefConstant(context, annotation, argument, null, flag,
+ annotations);
+ } else if (signature.equals(STRING_DEF_ANNOTATION)) {
+ checkTypeDefConstant(context, annotation, argument, null, false,
+ annotations);
+ } else if (signature.endsWith(RES_SUFFIX)) {
+ if (handledResourceTypes) {
+ continue;
+ }
+ handledResourceTypes = true;
+ EnumSet<ResourceType> types = null;
+ // Handle all resource type annotations in one go: there could be multiple
+ // resource type annotations specified on the same element; we need to
+ // know about them all up front.
+ for (ResolvedAnnotation a : annotations) {
+ String s = a.getSignature();
+ if (s.endsWith(RES_SUFFIX)) {
+ String typeString = s.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
+ s.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type != null) {
+ if (types == null) {
+ types = EnumSet.of(type);
+ } else {
+ types.add(type);
+ }
+ } else if (typeString.equals("any")) { // @AnyRes
+ types = getAnyRes();
+ break;
+ }
+ }
+ }
+
+ if (types != null) {
+ checkResourceType(context, argument, types);
+ }
+ }
+ }
+ }
+ }
+
+ private static EnumSet<ResourceType> getAnyRes() {
+ EnumSet<ResourceType> types = EnumSet.allOf(ResourceType.class);
+ types.remove(COLOR_INT_MARKER_TYPE);
+ return types;
+ }
+
+ private void checkParameterPermission(
+ @NonNull JavaContext context,
+ @NonNull String signature,
+ @NonNull Node call,
+ @NonNull ResolvedMethod method,
+ @NonNull Node argument) {
+ Operation operation = null;
+ if (signature.equals(PERMISSION_ANNOTATION_READ)) {
+ operation = READ;
+ } else if (signature.equals(PERMISSION_ANNOTATION_WRITE)) {
+ operation = WRITE;
+ } else {
+ TypeDescriptor type = context.getType(argument);
+ if (type == null) {
+ return;
+ }
+ if (type.matchesSignature(CLASS_INTENT)) {
+ operation = ACTION;
+ }
+ }
+ if (operation == null) {
+ return;
+ }
+ Result result = PermissionFinder.findRequiredPermissions(operation, context, argument);
+ if (result != null) {
+ checkPermission(context, call, method, result, result.requirement);
+ }
+ }
+
+ private static void checkColor(@NonNull JavaContext context, @NonNull Node argument) {
+ if (argument instanceof InlineIfExpression) {
+ InlineIfExpression expression = (InlineIfExpression) argument;
+ checkColor(context, expression.astIfTrue());
+ checkColor(context, expression.astIfFalse());
+ return;
+ }
+
+ EnumSet<ResourceType> types = getResourceTypes(context, argument);
+
+ if (types != null && types.contains(ResourceType.COLOR)
+ && !isIgnoredInIde(COLOR_USAGE, context, argument)) {
+ String message = String.format(
+ "Should pass resolved color instead of resource id here: " +
+ "`getResources().getColor(%1$s)`", argument.toString());
+ context.report(COLOR_USAGE, argument, context.getLocation(argument), message);
+ }
+ }
+
+ private static boolean isIgnoredInIde(@NonNull Issue issue, @NonNull JavaContext context,
+ @NonNull Node node) {
+ // Historically, the IDE would treat *all* support annotation warnings as
+ // handled by the id "ResourceType", so look for that id too for issues
+ // deliberately suppressed prior to Android Studio 2.0.
+ Issue synonym = Issue.create("ResourceType", issue.getBriefDescription(TextFormat.RAW),
+ issue.getExplanation(TextFormat.RAW), issue.getCategory(), issue.getPriority(),
+ issue.getDefaultSeverity(), issue.getImplementation());
+ return context.getDriver().isSuppressed(context, synonym, node);
+ }
+
+ private void checkPermission(
+ @NonNull JavaContext context,
+ @NonNull Node node,
+ @Nullable ResolvedMethod method,
+ @Nullable Result result,
+ @NonNull PermissionRequirement requirement) {
+ if (requirement.isConditional()) {
+ return;
+ }
+ PermissionHolder permissions = getPermissions(context);
+ if (!requirement.isSatisfied(permissions)) {
+ // See if it looks like we're holding the permission implicitly by @RequirePermission
+ // annotations in the surrounding context
+ permissions = addLocalPermissions(context, permissions, node);
+ if (!requirement.isSatisfied(permissions)) {
+ if (isIgnoredInIde(MISSING_PERMISSION, context, node)) {
+ return;
+ }
+ Operation operation;
+ String name;
+ if (result != null) {
+ name = result.name;
+ operation = result.operation;
+ } else {
+ assert method != null;
+ name = method.getContainingClass().getSimpleName() + "." + method.getName();
+ operation = Operation.CALL;
+ }
+ String message = getMissingPermissionMessage(requirement, name, permissions,
+ operation);
+ context.report(MISSING_PERMISSION, node, context.getLocation(node), message);
+ }
+ } else if (requirement.isRevocable(permissions) &&
+ context.getMainProject().getTargetSdkVersion().getFeatureLevel() >= 23) {
+ // Ensure that the caller is handling a security exception
+ // First check to see if we're inside a try/catch which catches a SecurityException
+ // (or some wider exception than that). Check for nested try/catches too.
+ boolean handlesMissingPermission = false;
+ Node parent = node;
+ while (true) {
+ Try tryCatch = getParentOfType(parent, Try.class);
+ if (tryCatch == null) {
+ break;
+ } else {
+ JavaParser parser = context.getParser();
+ for (Catch aCatch : tryCatch.astCatches()) {
+ for (TypeDescriptor catchType : parser.getCatchTypes(context, aCatch)) {
+ if (isSecurityException(context,
+ catchType)) {
+ handlesMissingPermission = true;
+ break;
+ }
+ }
+ }
+ parent = tryCatch;
+ }
+ }
+
+ // If not, check to see if the method itself declares that it throws a
+ // SecurityException or something wider.
+ if (!handlesMissingPermission) {
+ MethodDeclaration declaration = getParentOfType(parent, MethodDeclaration.class);
+ if (declaration != null) {
+ for (TypeReference typeReference : declaration.astThrownTypeReferences()) {
+ if (isSecurityException(context, context.getType(typeReference))) {
+ handlesMissingPermission = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // If not, check to see if the code is deliberately checking to see if the
+ // given permission is available.
+ if (!handlesMissingPermission) {
+ Node methodNode = JavaContext.findSurroundingMethod(node);
+ if (methodNode != null) {
+ CheckPermissionVisitor visitor = new CheckPermissionVisitor(node);
+ methodNode.accept(visitor);
+ handlesMissingPermission = visitor.checksPermission();
+ }
+ }
+
+ if (!handlesMissingPermission && !isIgnoredInIde(MISSING_PERMISSION, context, node)) {
+ String message = getUnhandledPermissionMessage();
+ context.report(MISSING_PERMISSION, node, context.getLocation(node), message);
+ }
+ }
+ }
+
+ @NonNull
+ private static PermissionHolder addLocalPermissions(
+ @NonNull JavaContext context,
+ @NonNull PermissionHolder permissions,
+ @NonNull Node node) {
+ // Accumulate @RequirePermissions available in the local context
+ Node methodNode = JavaContext.findSurroundingMethod(node);
+ if (methodNode == null) {
+ return permissions;
+ }
+ ResolvedNode resolved = context.resolve(methodNode);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return permissions;
+ }
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ ResolvedAnnotation annotation = method.getAnnotation(PERMISSION_ANNOTATION);
+ permissions = mergeAnnotationPermissions(context, permissions, annotation);
+ annotation = method.getContainingClass().getAnnotation(PERMISSION_ANNOTATION);
+ permissions = mergeAnnotationPermissions(context, permissions, annotation);
+ return permissions;
+ }
+
+ @NonNull
+ private static PermissionHolder mergeAnnotationPermissions(
+ @NonNull JavaContext context,
+ @NonNull PermissionHolder permissions,
+ @Nullable ResolvedAnnotation annotation) {
+ if (annotation != null) {
+ PermissionRequirement requirement = PermissionRequirement.create(context, annotation);
+ permissions = SetPermissionLookup.join(permissions, requirement);
+ }
+
+ return permissions;
+ }
+
+ /** Returns the error message shown when a given call is missing one or more permissions */
+ public static String getMissingPermissionMessage(@NonNull PermissionRequirement requirement,
+ @NonNull String callName, @NonNull PermissionHolder permissions,
+ @NonNull Operation operation) {
+ return String.format("Missing permissions required %1$s %2$s: %3$s", operation.prefix(),
+ callName, requirement.describeMissingPermissions(permissions));
+ }
+
+ /** Returns the error message shown when a revocable permission call is not properly handled */
+ public static String getUnhandledPermissionMessage() {
+ return "Call requires permission which may be rejected by user: code should explicitly "
+ + "check to see if permission is available (with `checkPermission`) or explicitly "
+ + "handle a potential `SecurityException`";
+ }
+
+ /**
+ * Visitor which looks through a method, up to a given call (the one requiring a
+ * permission) and checks whether it's preceeded by a call to checkPermission or
+ * checkCallingPermission or enforcePermission etc.
+ * <p>
+ * Currently it only looks for the presence of this check; it does not perform
+ * flow analysis to determine whether the check actually affects program flow
+ * up to the permission call, or whether the check permission is checking for
+ * permissions sufficient to satisfy the permission requirement of the target call,
+ * or whether the check return value (== PERMISSION_GRANTED vs != PERMISSION_GRANTED)
+ * is handled correctly, etc.
+ */
+ private static class CheckPermissionVisitor extends ForwardingAstVisitor {
+ private boolean mChecksPermission;
+ private boolean mDone;
+ private final Node mTarget;
+
+ public CheckPermissionVisitor(@NonNull Node target) {
+ mTarget = target;
+ }
+
+ @Override
+ public boolean visitNode(Node node) {
+ return mDone;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node == mTarget) {
+ mDone = true;
+ }
+
+ String name = node.astName().astValue();
+ if ((name.startsWith("check") || name.startsWith("enforce"))
+ && name.endsWith("Permission")) {
+ mChecksPermission = true;
+ mDone = true;
+ }
+ return super.visitMethodInvocation(node);
+ }
+
+ public boolean checksPermission() {
+ return mChecksPermission;
+ }
+ }
+
+ private static boolean isSecurityException(
+ @NonNull JavaContext context,
+ @Nullable TypeDescriptor type) {
+ // In earlier versions we checked not just for java.lang.SecurityException but
+ // any super type as well, however that probably hides warnings in cases where
+ // users don't want that; see http://b.android.com/182165
+ return type != null && type.matchesSignature("java.lang.SecurityException");
+ }
+
+ private PermissionHolder mPermissions;
+
+ private PermissionHolder getPermissions(
+ @NonNull JavaContext context) {
+ if (mPermissions == null) {
+ Set<String> permissions = Sets.newHashSetWithExpectedSize(30);
+ Set<String> revocable = Sets.newHashSetWithExpectedSize(4);
+ LintClient client = context.getClient();
+ // Gather permissions from all projects that contribute to the
+ // main project.
+ Project mainProject = context.getMainProject();
+ for (File manifest : mainProject.getManifestFiles()) {
+ addPermissions(client, permissions, revocable, manifest);
+ }
+ for (Project library : mainProject.getAllLibraries()) {
+ for (File manifest : library.getManifestFiles()) {
+ addPermissions(client, permissions, revocable, manifest);
+ }
+ }
+
+ AndroidVersion minSdkVersion = mainProject.getMinSdkVersion();
+ AndroidVersion targetSdkVersion = mainProject.getTargetSdkVersion();
+ mPermissions = new SetPermissionLookup(permissions, revocable, minSdkVersion,
+ targetSdkVersion);
+ }
+
+ return mPermissions;
+ }
+
+ private static void addPermissions(@NonNull LintClient client,
+ @NonNull Set<String> permissions,
+ @NonNull Set<String> revocable,
+ @NonNull File manifest) {
+ Document document = XmlUtils.parseDocumentSilently(client.readFile(manifest), true);
+ if (document == null) {
+ return;
+ }
+ Element root = document.getDocumentElement();
+ if (root == null) {
+ return;
+ }
+ NodeList children = root.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ org.w3c.dom.Node item = children.item(i);
+ if (item.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE) {
+ continue;
+ }
+ String nodeName = item.getNodeName();
+ if (nodeName.equals(TAG_USES_PERMISSION)
+ || nodeName.equals(TAG_USES_PERMISSION_SDK_23)
+ || nodeName.equals(TAG_USES_PERMISSION_SDK_M)) {
+ Element element = (Element)item;
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (!name.isEmpty()) {
+ permissions.add(name);
+ }
+ } else if (nodeName.equals(TAG_PERMISSION)) {
+ Element element = (Element)item;
+ String protectionLevel = element.getAttributeNS(ANDROID_URI,
+ ATTR_PROTECTION_LEVEL);
+ if (VALUE_DANGEROUS.equals(protectionLevel)) {
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (!name.isEmpty()) {
+ revocable.add(name);
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkResult(@NonNull JavaContext context, @NonNull Node node,
+ @NonNull ResolvedAnnotation annotation) {
+ if (node.getParent() instanceof ExpressionStatement) {
+ String methodName = JavaContext.getMethodName(node);
+ Object suggested = annotation.getValue(ATTR_SUGGEST);
+
+ // Failing to check permissions is a potential security issue (and had an existing
+ // dedicated issue id before which people may already have configured with a
+ // custom severity in their LintOptions etc) so continue to use that issue
+ // (which also has category Security rather than Correctness) for these:
+ Issue issue = CHECK_RESULT;
+ if (methodName != null && methodName.startsWith("check")
+ && methodName.contains("Permission")) {
+ issue = CHECK_PERMISSION;
+ }
+
+ if (isIgnoredInIde(issue, context, node)) {
+ return;
+ }
+
+ String message = String.format("The result of `%1$s` is not used",
+ methodName);
+ if (suggested != null) {
+ // TODO: Resolve suggest attribute (e.g. prefix annotation class if it starts
+ // with "#" etc?
+ message = String.format(
+ "The result of `%1$s` is not used; did you mean to call `%2$s`?",
+ methodName, suggested.toString());
+ }
+ context.report(issue, node, context.getLocation(node), message);
+ }
+ }
+
+ private static void checkThreading(
+ @NonNull JavaContext context,
+ @NonNull Node node,
+ @NonNull ResolvedMethod method,
+ @NonNull String annotation) {
+ String threadContext = getThreadContext(context, node);
+ if (threadContext != null && !isCompatibleThread(threadContext, annotation)
+ && !isIgnoredInIde(THREAD, context, node)) {
+ String message = String.format("Method %1$s must be called from the `%2$s` thread, currently inferred thread is `%3$s` thread",
+ method.getName(), describeThread(annotation), describeThread(threadContext));
+ context.report(THREAD, node, context.getLocation(node), message);
+ }
+ }
+
+ @NonNull
+ public static String describeThread(@NonNull String annotation) {
+ if (UI_THREAD_ANNOTATION.equals(annotation)) {
+ return "UI";
+ }
+ else if (MAIN_THREAD_ANNOTATION.equals(annotation)) {
+ return "main";
+ }
+ else if (BINDER_THREAD_ANNOTATION.equals(annotation)) {
+ return "binder";
+ }
+ else if (WORKER_THREAD_ANNOTATION.equals(annotation)) {
+ return "worker";
+ } else {
+ return "other";
+ }
+ }
+
+ /** returns true if the two threads are compatible */
+ public static boolean isCompatibleThread(@NonNull String thread1, @NonNull String thread2) {
+ if (thread1.equals(thread2)) {
+ return true;
+ }
+
+ // Allow @UiThread and @MainThread to be combined
+ if (thread1.equals(UI_THREAD_ANNOTATION)) {
+ if (thread2.equals(MAIN_THREAD_ANNOTATION)) {
+ return true;
+ }
+ } else if (thread1.equals(MAIN_THREAD_ANNOTATION)) {
+ if (thread2.equals(UI_THREAD_ANNOTATION)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** Attempts to infer the current thread context at the site of the given method call */
+ @Nullable
+ private static String getThreadContext(@NonNull JavaContext context,
+ @NonNull Node methodCall) {
+ Node node = findSurroundingMethod(methodCall);
+ if (node != null) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ ResolvedClass cls = method.getContainingClass();
+
+ while (method != null) {
+ for (ResolvedAnnotation annotation : method.getAnnotations()) {
+ String name = annotation.getSignature();
+ if (name.startsWith(SUPPORT_ANNOTATIONS_PREFIX)
+ && name.endsWith(THREAD_SUFFIX)) {
+ return name;
+ }
+ }
+ method = method.getSuperMethod();
+ }
+
+ // See if we're extending a class with a known threading context
+ while (cls != null) {
+ for (ResolvedAnnotation annotation : cls.getAnnotations()) {
+ String name = annotation.getSignature();
+ if (name.startsWith(SUPPORT_ANNOTATIONS_PREFIX)
+ && name.endsWith(THREAD_SUFFIX)) {
+ return name;
+ }
+ }
+ cls = cls.getSuperClass();
+ }
+ }
+ }
+
+ // In the future, we could also try to infer the threading context using
+ // other heuristics. For example, if we're in a method with unknown threading
+ // context, but we see that the method is called by another method with a known
+ // threading context, we can infer that that threading context is the context for
+ // this thread too (assuming the call is direct).
+
+ return null;
+ }
+
+ private static boolean isNumber(@NonNull Node argument) {
+ return argument instanceof IntegralLiteral || argument instanceof UnaryExpression
+ && ((UnaryExpression) argument).astOperator() == UnaryOperator.UNARY_MINUS
+ && ((UnaryExpression) argument).astOperand() instanceof IntegralLiteral;
+ }
+
+ private static boolean isZero(@NonNull Node argument) {
+ return argument instanceof IntegralLiteral
+ && ((IntegralLiteral) argument).astIntValue() == 0;
+ }
+
+ private static boolean isMinusOne(@NonNull Node argument) {
+ return argument instanceof UnaryExpression
+ && ((UnaryExpression) argument).astOperator() == UnaryOperator.UNARY_MINUS
+ && ((UnaryExpression) argument).astOperand() instanceof IntegralLiteral
+ && ((IntegralLiteral) ((UnaryExpression) argument).astOperand()).astIntValue()
+ == 1;
+ }
+
+ private static void checkResourceType(
+ @NonNull JavaContext context,
+ @NonNull Node argument,
+ @NonNull EnumSet<ResourceType> expectedType) {
+ EnumSet<ResourceType> actual = getResourceTypes(context, argument);
+ if (actual == null && (!isNumber(argument) || isZero(argument) || isMinusOne(argument)) ) {
+ return;
+ } else if (actual != null && (!Sets.intersection(actual, expectedType).isEmpty()
+ || expectedType.contains(DRAWABLE)
+ && (actual.contains(COLOR) || actual.contains(MIPMAP)))) {
+ return;
+ }
+
+ if (isIgnoredInIde(RESOURCE_TYPE, context, argument)) {
+ return;
+ }
+
+ String message;
+ if (actual != null && actual.size() == 1 && actual.contains(COLOR_INT_MARKER_TYPE)) {
+ message = "Expected a color resource id (`R.color.`) but received an RGB integer";
+ } else if (expectedType.contains(COLOR_INT_MARKER_TYPE)) {
+ message = String.format("Should pass resolved color instead of resource id here: " +
+ "`getResources().getColor(%1$s)`", argument.toString());
+ } else if (expectedType.size() < ResourceType.getNames().length - 1) {
+ message = String.format("Expected resource of type %1$s",
+ Joiner.on(" or ").join(expectedType));
+ } else {
+ message = "Expected resource identifier (`R`.type.`name`)";
+ }
+ context.report(RESOURCE_TYPE, argument, context.getLocation(argument), message);
+ }
+
+ @Nullable
+ private static EnumSet<ResourceType> getResourceTypes(@NonNull JavaContext context,
+ @NonNull Node argument) {
+ if (argument instanceof Select) {
+ Select node = (Select) argument;
+ if (node.astOperand() instanceof Select) {
+ Select select = (Select) node.astOperand();
+ if (select.astOperand() instanceof Select) { // android.R....
+ Select innerSelect = (Select) select.astOperand();
+ if (innerSelect.astIdentifier().astValue().equals(R_CLASS)) {
+ String typeName = select.astIdentifier().astValue();
+ ResourceType type = ResourceType.getEnum(typeName);
+ return type != null ? EnumSet.of(type) : null;
+ }
+ }
+ if (select.astOperand() instanceof VariableReference) {
+ VariableReference reference = (VariableReference) select.astOperand();
+ if (reference.astIdentifier().astValue().equals(R_CLASS)) {
+ String typeName = select.astIdentifier().astValue();
+ ResourceType type = ResourceType.getEnum(typeName);
+ return type != null ? EnumSet.of(type) : null;
+ }
+ }
+ }
+
+ // Arbitrary packages -- android.R.type.name, foo.bar.R.type.name
+ if (node.astIdentifier().astValue().equals(R_CLASS)) {
+ Node parent = node.getParent();
+ if (parent instanceof Select) {
+ Node grandParent = parent.getParent();
+ if (grandParent instanceof Select) {
+ Select select = (Select) grandParent;
+ Expression typeOperand = select.astOperand();
+ if (typeOperand instanceof Select) {
+ Select typeSelect = (Select) typeOperand;
+ String typeName = typeSelect.astIdentifier().astValue();
+ ResourceType type = ResourceType.getEnum(typeName);
+ return type != null ? EnumSet.of(type) : null;
+ }
+ }
+ }
+ }
+ } else if (argument instanceof VariableReference) {
+ Statement statement = getParentOfType(argument, Statement.class, false);
+ if (statement != null) {
+ ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next() == statement) {
+ if (iterator.hasPrevious()) { // should always be true
+ iterator.previous();
+ }
+ break;
+ }
+ }
+
+ String targetName = ((VariableReference)argument).astIdentifier().astValue();
+ while (iterator.hasPrevious()) {
+ Node previous = iterator.previous();
+ if (previous instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) previous;
+ VariableDefinition definition = declaration.astDefinition();
+ for (VariableDefinitionEntry entry : definition
+ .astVariables()) {
+ if (entry.astInitializer() != null
+ && entry.astName().astValue().equals(targetName)) {
+ return getResourceTypes(context, entry.astInitializer());
+ }
+ }
+ } else if (previous instanceof ExpressionStatement) {
+ ExpressionStatement expressionStatement = (ExpressionStatement) previous;
+ Expression expression = expressionStatement.astExpression();
+ if (expression instanceof BinaryExpression &&
+ ((BinaryExpression) expression).astOperator()
+ == BinaryOperator.ASSIGN) {
+ BinaryExpression binaryExpression = (BinaryExpression) expression;
+ if (targetName.equals(binaryExpression.astLeft().toString())) {
+ return getResourceTypes(context, binaryExpression.astRight());
+ }
+ }
+ }
+ }
+ }
+ } else if (argument instanceof MethodInvocation) {
+ ResolvedNode resolved = context.resolve(argument);
+ if (resolved != null) {
+ for (ResolvedAnnotation annotation : resolved.getAnnotations()) {
+ String signature = annotation.getSignature();
+ if (signature.equals(COLOR_INT_ANNOTATION)) {
+ return EnumSet.of(COLOR_INT_MARKER_TYPE);
+ }
+ if (signature.endsWith(RES_SUFFIX)
+ && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
+ String typeString = signature.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
+ signature.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type != null) {
+ return EnumSet.of(type);
+ } else if (typeString.equals("any")) { // @AnyRes
+ return getAnyRes();
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static void checkIntRange(
+ @NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation,
+ @NonNull Node argument,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
+ String message = getIntRangeError(context, annotation, argument);
+ if (message != null) {
+ if (findIntDef(allAnnotations) != null) {
+ // Don't flag int range errors if there is an int def annotation there too;
+ // there could be a valid @IntDef constant. (The @IntDef check will
+ // perform range validation by calling getIntRange.)
+ return;
+ }
+
+ if (isIgnoredInIde(RANGE, context, argument)) {
+ return;
+ }
+
+ context.report(RANGE, argument, context.getLocation(argument), message);
+ }
+ }
+
+ @Nullable
+ private static String getIntRangeError(
+ @NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation,
+ @NonNull Node argument) {
+ if (argument instanceof ArrayCreation) {
+ ArrayCreation creation = (ArrayCreation)argument;
+ ArrayInitializer initializer = creation.astInitializer();
+ if (initializer != null) {
+ for (Expression expression : initializer.astExpressions()) {
+ String error = getIntRangeError(context, annotation, expression);
+ if (error != null) {
+ return error;
+ }
+ }
+ }
+
+ return null;
+ }
+ Object object = ConstantEvaluator.evaluate(context, argument);
+ if (!(object instanceof Number)) {
+ return null;
+ }
+ long value = ((Number)object).longValue();
+ long from = getLongAttribute(annotation, ATTR_FROM, Long.MIN_VALUE);
+ long to = getLongAttribute(annotation, ATTR_TO, Long.MAX_VALUE);
+
+ return getIntRangeError(value, from, to);
+ }
+
+ /**
+ * Checks whether a given integer value is in the allowed range, and if so returns
+ * null; otherwise returns a suitable error message.
+ */
+ private static String getIntRangeError(long value, long from, long to) {
+ String message = null;
+ if (value < from || value > to) {
+ StringBuilder sb = new StringBuilder(20);
+ if (value < from) {
+ sb.append("Value must be \u2265 ");
+ sb.append(Long.toString(from));
+ } else {
+ assert value > to;
+ sb.append("Value must be \u2264 ");
+ sb.append(Long.toString(to));
+ }
+ sb.append(" (was ").append(value).append(')');
+ message = sb.toString();
+ }
+ return message;
+ }
+
+ private static void checkFloatRange(
+ @NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation,
+ @NonNull Node argument) {
+ Object object = ConstantEvaluator.evaluate(context, argument);
+ if (!(object instanceof Number)) {
+ return;
+ }
+ double value = ((Number)object).doubleValue();
+ double from = getDoubleAttribute(annotation, ATTR_FROM, Double.NEGATIVE_INFINITY);
+ double to = getDoubleAttribute(annotation, ATTR_TO, Double.POSITIVE_INFINITY);
+ boolean fromInclusive = getBoolean(annotation, ATTR_FROM_INCLUSIVE, true);
+ boolean toInclusive = getBoolean(annotation, ATTR_TO_INCLUSIVE, true);
+
+ String message = getFloatRangeError(value, from, to, fromInclusive, toInclusive, argument);
+ if (message != null && !isIgnoredInIde(RANGE, context, argument)) {
+ context.report(RANGE, argument, context.getLocation(argument), message);
+ }
+ }
+
+ /**
+ * Checks whether a given floating point value is in the allowed range, and if so returns
+ * null; otherwise returns a suitable error message.
+ */
+ @Nullable
+ private static String getFloatRangeError(double value, double from, double to,
+ boolean fromInclusive, boolean toInclusive, @NonNull Node node) {
+ if (!((fromInclusive && value >= from || !fromInclusive && value > from) &&
+ (toInclusive && value <= to || !toInclusive && value < to))) {
+ StringBuilder sb = new StringBuilder(20);
+ if (from != Double.NEGATIVE_INFINITY) {
+ if (to != Double.POSITIVE_INFINITY) {
+ if (fromInclusive && value < from || !fromInclusive && value <= from) {
+ sb.append("Value must be ");
+ if (fromInclusive) {
+ sb.append('\u2265'); // >= sign
+ } else {
+ sb.append('>');
+ }
+ sb.append(' ');
+ sb.append(Double.toString(from));
+ } else {
+ assert toInclusive && value > to || !toInclusive && value >= to;
+ sb.append("Value must be ");
+ if (toInclusive) {
+ sb.append('\u2264'); // <= sign
+ } else {
+ sb.append('<');
+ }
+ sb.append(' ');
+ sb.append(Double.toString(to));
+ }
+ } else {
+ sb.append("Value must be ");
+ if (fromInclusive) {
+ sb.append('\u2265'); // >= sign
+ } else {
+ sb.append('>');
+ }
+ sb.append(' ');
+ sb.append(Double.toString(from));
+ }
+ } else if (to != Double.POSITIVE_INFINITY) {
+ sb.append("Value must be ");
+ if (toInclusive) {
+ sb.append('\u2264'); // <= sign
+ } else {
+ sb.append('<');
+ }
+ sb.append(' ');
+ sb.append(Double.toString(to));
+ }
+ sb.append(" (was ");
+ if (node instanceof FloatingPointLiteral || node instanceof IntegralLiteral) {
+ // Use source text instead to avoid rounding errors involved in conversion, e.g
+ // Error: Value must be > 2.5 (was 2.490000009536743) [Range]
+ // printAtLeastExclusive(2.49f); // ERROR
+ // ~~~~~
+ String str = node.toString();
+ if (str.endsWith("f") || str.endsWith("F")) {
+ str = str.substring(0, str.length() - 1);
+ }
+ sb.append(str);
+ } else {
+ sb.append(value);
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+ return null;
+ }
+
+ private static void checkSize(
+ @NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation,
+ @NonNull Node argument) {
+ int actual;
+ if (argument instanceof StringLiteral) {
+ // Check string length
+ StringLiteral literal = (StringLiteral) argument;
+ String s = literal.astValue();
+ actual = s.length();
+ } else if (argument instanceof ArrayCreation) {
+ ArrayCreation literal = (ArrayCreation) argument;
+ ArrayInitializer initializer = literal.astInitializer();
+ if (initializer == null) {
+ return;
+ }
+ actual = initializer.astExpressions().size();
+ } else {
+ // TODO: Collections syntax, e.g. Arrays.asList => param count, emptyList=0, singleton=1, etc
+ // TODO: Flow analysis
+ // No flow analysis for this check yet, only checking literals passed in as parameters
+ return;
+ }
+ long exact = getLongAttribute(annotation, ATTR_VALUE, -1);
+ long min = getLongAttribute(annotation, ATTR_MIN, Long.MIN_VALUE);
+ long max = getLongAttribute(annotation, ATTR_MAX, Long.MAX_VALUE);
+ long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1);
+
+ String unit;
+ boolean isString = argument instanceof StringLiteral;
+ if (isString) {
+ unit = "length";
+ } else {
+ unit = "size";
+ }
+ String message = getSizeError(actual, exact, min, max, multiple, unit);
+ if (message != null && !isIgnoredInIde(RANGE, context, argument)) {
+ context.report(RANGE, argument, context.getLocation(argument), message);
+ }
+ }
+
+ /**
+ * Checks whether a given size follows the given constraints, and if so returns
+ * null; otherwise returns a suitable error message.
+ */
+ private static String getSizeError(long actual, long exact, long min, long max, long multiple,
+ @NonNull String unit) {
+ String message = null;
+ if (exact != -1) {
+ if (exact != actual) {
+ message = String.format("Expected %1$s %2$d (was %3$d)",
+ unit, exact, actual);
+ }
+ } else if (actual < min || actual > max) {
+ StringBuilder sb = new StringBuilder(20);
+ if (actual < min) {
+ sb.append("Expected ").append(unit).append(" \u2265 ");
+ sb.append(Long.toString(min));
+ } else {
+ assert actual > max;
+ sb.append("Expected ").append(unit).append(" \u2264 ");
+ sb.append(Long.toString(max));
+ }
+ sb.append(" (was ").append(actual).append(')');
+ message = sb.toString();
+ } else if (actual % multiple != 0) {
+ message = String.format("Expected %1$s to be a multiple of %2$d (was %3$d "
+ + "and should be either %4$d or %5$d)",
+ unit, multiple, actual, (actual / multiple) * multiple,
+ (actual / multiple + 1) * multiple);
+ }
+ return message;
+ }
+
+ @Nullable
+ private static ResolvedAnnotation findIntRange(
+ @NonNull Iterable<ResolvedAnnotation> annotations) {
+ for (ResolvedAnnotation annotation : annotations) {
+ if (INT_RANGE_ANNOTATION.equals(annotation.getName())) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ static ResolvedAnnotation findIntDef(
+ @NonNull Iterable<ResolvedAnnotation> annotations) {
+ for (ResolvedAnnotation annotation : annotations) {
+ if (INT_DEF_ANNOTATION.equals(annotation.getName())) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ private static void checkTypeDefConstant(
+ @NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation,
+ @NonNull Node argument,
+ @Nullable Node errorNode,
+ boolean flag,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
+ if (argument instanceof NullLiteral) {
+ // Accepted for @StringDef
+ return;
+ }
+
+ if (argument instanceof StringLiteral) {
+ StringLiteral string = (StringLiteral) argument;
+ checkTypeDefConstant(context, annotation, argument, errorNode, false, string.astValue(),
+ allAnnotations);
+ } else if (argument instanceof IntegralLiteral) {
+ IntegralLiteral literal = (IntegralLiteral) argument;
+ int value = literal.astIntValue();
+ if (flag && value == 0) {
+ // Accepted for a flag @IntDef
+ return;
+ }
+
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ if (getIntRangeError(context, rangeAnnotation, literal) == null) {
+ return;
+ }
+ }
+
+ checkTypeDefConstant(context, annotation, argument, errorNode, flag, value,
+ allAnnotations);
+ } else if (isMinusOne(argument)) {
+ // -1 is accepted unconditionally for flags
+ if (!flag) {
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ if (getIntRangeError(context, rangeAnnotation, argument) == null) {
+ return;
+ }
+ }
+
+ reportTypeDef(context, annotation, argument, errorNode, allAnnotations);
+ }
+ } else if (argument instanceof InlineIfExpression) {
+ InlineIfExpression expression = (InlineIfExpression) argument;
+ if (expression.astIfTrue() != null) {
+ checkTypeDefConstant(context, annotation, expression.astIfTrue(), errorNode, flag,
+ allAnnotations);
+ }
+ if (expression.astIfFalse() != null) {
+ checkTypeDefConstant(context, annotation, expression.astIfFalse(), errorNode, flag,
+ allAnnotations);
+ }
+ } else if (argument instanceof UnaryExpression) {
+ UnaryExpression expression = (UnaryExpression) argument;
+ UnaryOperator operator = expression.astOperator();
+ if (flag) {
+ checkTypeDefConstant(context, annotation, expression.astOperand(), errorNode, true,
+ allAnnotations);
+ } else if (operator == UnaryOperator.BINARY_NOT) {
+ if (isIgnoredInIde(TYPE_DEF, context, expression)) {
+ return;
+ }
+ context.report(TYPE_DEF, expression, context.getLocation(expression),
+ "Flag not allowed here");
+ } else if (operator == UnaryOperator.UNARY_MINUS) {
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ if (getIntRangeError(context, rangeAnnotation, argument) == null) {
+ return;
+ }
+ }
+
+ reportTypeDef(context, annotation, argument, errorNode, allAnnotations);
+ }
+ } else if (argument instanceof BinaryExpression) {
+ // If it's ?: then check both the if and else clauses
+ BinaryExpression expression = (BinaryExpression) argument;
+ if (flag) {
+ checkTypeDefConstant(context, annotation, expression.astLeft(), errorNode, true,
+ allAnnotations);
+ checkTypeDefConstant(context, annotation, expression.astRight(), errorNode, true,
+ allAnnotations);
+ } else {
+ BinaryOperator operator = expression.astOperator();
+ if (operator == BinaryOperator.BITWISE_AND
+ || operator == BinaryOperator.BITWISE_OR
+ || operator == BinaryOperator.BITWISE_XOR) {
+ if (isIgnoredInIde(TYPE_DEF, context, expression)) {
+ return;
+ }
+ context.report(TYPE_DEF, expression, context.getLocation(expression),
+ "Flag not allowed here");
+ }
+ }
+ } else if (argument instanceof ArrayCreation) {
+ ArrayCreation creation = (ArrayCreation) argument;
+ TypeReference typeReference = creation.astComponentTypeReference();
+ ArrayInitializer initializer = creation.astInitializer();
+ if (initializer != null && (TYPE_INT.equals(typeReference.getTypeName())
+ || TYPE_LONG.equals(typeReference.getTypeName()))) {
+ for (Expression expression : initializer.astExpressions()) {
+ checkTypeDefConstant(context, annotation, expression, errorNode, flag,
+ allAnnotations);
+ }
+ }
+ } else {
+ ResolvedNode resolved = context.resolve(argument);
+ if (resolved instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) resolved;
+ if (field.getType().isArray()) {
+ // It's pointing to an array reference; we can't check these individual
+ // elements (because we can't jump from ResolvedNodes to AST elements; this
+ // is part of the motivation for the PSI change in lint 2.0), but we also
+ // don't want to flag it as invalid.
+ return;
+ }
+ int modifiers = field.getModifiers();
+ // If it's a constant (static/final) check that it's one of the allowed ones
+ if ((modifiers & (Modifier.FINAL|Modifier.STATIC))
+ == (Modifier.FINAL|Modifier.STATIC)) {
+ checkTypeDefConstant(context, annotation, argument, errorNode, flag, resolved,
+ allAnnotations);
+ }
+ } else if (argument instanceof VariableReference) {
+ Statement statement = getParentOfType(argument, Statement.class, false);
+ if (statement != null) {
+ ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next() == statement) {
+ if (iterator.hasPrevious()) { // should always be true
+ iterator.previous();
+ }
+ break;
+ }
+ }
+
+ String targetName = ((VariableReference)argument).astIdentifier().astValue();
+ while (iterator.hasPrevious()) {
+ Node previous = iterator.previous();
+ if (previous instanceof VariableDeclaration) {
+ VariableDeclaration declaration = (VariableDeclaration) previous;
+ VariableDefinition definition = declaration.astDefinition();
+ for (VariableDefinitionEntry entry : definition
+ .astVariables()) {
+ if (entry.astInitializer() != null
+ && entry.astName().astValue().equals(targetName)) {
+ checkTypeDefConstant(context, annotation,
+ entry.astInitializer(),
+ errorNode != null ? errorNode : argument, flag,
+ allAnnotations);
+ return;
+ }
+ }
+ } else if (previous instanceof ExpressionStatement) {
+ ExpressionStatement expressionStatement = (ExpressionStatement) previous;
+ Expression expression = expressionStatement.astExpression();
+ if (expression instanceof BinaryExpression &&
+ ((BinaryExpression) expression).astOperator()
+ == BinaryOperator.ASSIGN) {
+ BinaryExpression binaryExpression = (BinaryExpression) expression;
+ if (targetName.equals(binaryExpression.astLeft().toString())) {
+ checkTypeDefConstant(context, annotation,
+ binaryExpression.astRight(),
+ errorNode != null ? errorNode : argument, flag,
+ allAnnotations);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkTypeDefConstant(@NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation, @NonNull Node argument,
+ @Nullable Node errorNode, boolean flag, Object value,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
+ Object allowed = annotation.getValue();
+ if (allowed instanceof Object[]) {
+ Object[] allowedValues = (Object[]) allowed;
+ for (Object o : allowedValues) {
+ if (o.equals(value)) {
+ return;
+ }
+ }
+ reportTypeDef(context, argument, errorNode, flag, allowedValues, allAnnotations);
+ }
+ }
+
+ private static void reportTypeDef(@NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation, @NonNull Node argument,
+ @Nullable Node errorNode, @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
+ Object allowed = annotation.getValue();
+ if (allowed instanceof Object[]) {
+ Object[] allowedValues = (Object[]) allowed;
+ reportTypeDef(context, argument, errorNode, false, allowedValues, allAnnotations);
+ }
+ }
+
+ private static void reportTypeDef(@NonNull JavaContext context, @NonNull Node node,
+ @Nullable Node errorNode, boolean flag, @NonNull Object[] allowedValues,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
+ if (errorNode == null) {
+ errorNode = node;
+ }
+ if (isIgnoredInIde(TYPE_DEF, context, errorNode)) {
+ return;
+ }
+
+ String values = listAllowedValues(allowedValues);
+ String message;
+ if (flag) {
+ message = "Must be one or more of: " + values;
+ } else {
+ message = "Must be one of: " + values;
+ }
+
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ String rangeError = getIntRangeError(context, rangeAnnotation, node);
+ if (rangeError != null && !rangeError.isEmpty()) {
+ message += " or " + Character.toLowerCase(rangeError.charAt(0))
+ + rangeError.substring(1);
+ }
+ }
+
+ context.report(TYPE_DEF, errorNode, context.getLocation(errorNode), message);
+ }
+
+ private static String listAllowedValues(@NonNull Object[] allowedValues) {
+ StringBuilder sb = new StringBuilder();
+ for (Object allowedValue : allowedValues) {
+ String s;
+ if (allowedValue instanceof Integer) {
+ s = allowedValue.toString();
+ } else if (allowedValue instanceof ResolvedNode) {
+ ResolvedNode node = (ResolvedNode) allowedValue;
+ if (node instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) node;
+ String containingClassName = field.getContainingClassName();
+ if (containingClassName == null) {
+ continue;
+ }
+ containingClassName = containingClassName.substring(containingClassName.lastIndexOf('.') + 1);
+ s = containingClassName + "." + field.getName();
+ } else {
+ s = node.getSignature();
+ }
+ } else {
+ continue;
+ }
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+ private static double getDoubleAttribute(@NonNull ResolvedAnnotation annotation,
+ @NonNull String name, double defaultValue) {
+ Object value = annotation.getValue(name);
+ if (value instanceof Number) {
+ return ((Number) value).doubleValue();
+ }
+
+ return defaultValue;
+ }
+
+ private static long getLongAttribute(@NonNull ResolvedAnnotation annotation,
+ @NonNull String name, long defaultValue) {
+ Object value = annotation.getValue(name);
+ if (value instanceof Number) {
+ return ((Number) value).longValue();
+ }
+
+ return defaultValue;
+ }
+
+ private static boolean getBoolean(@NonNull ResolvedAnnotation annotation,
+ @NonNull String name, boolean defaultValue) {
+ Object value = annotation.getValue(name);
+ if (value instanceof Boolean) {
+ return ((Boolean) value);
+ }
+
+ return defaultValue;
+ }
+
+ @NonNull
+ static Iterable<ResolvedAnnotation> filterRelevantAnnotations(
+ @NonNull Iterable<ResolvedAnnotation> annotations) {
+ List<ResolvedAnnotation> result = null;
+ Iterator<ResolvedAnnotation> iterator = annotations.iterator();
+ int index = 0;
+ while (iterator.hasNext()) {
+ ResolvedAnnotation annotation = iterator.next();
+ index++;
+
+ String signature = annotation.getSignature();
+ if (signature.startsWith("java.")) {
+ // @Override, @SuppressWarnings etc. Ignore
+ continue;
+ }
+
+ if (signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
+ // Bail on the nullness annotations early since they're the most commonly
+ // defined ones. They're not analyzed in lint yet.
+ if (signature.endsWith(".Nullable") || signature.endsWith(".NonNull")) {
+ continue;
+ }
+
+ // Common case: there's just one annotation; no need to create a list copy
+ if (!iterator.hasNext() && index == 1) {
+ return annotations;
+ }
+ if (result == null) {
+ result = new ArrayList<ResolvedAnnotation>(2);
+ }
+ result.add(annotation);
+ }
+
+ // Special case @IntDef and @StringDef: These are used on annotations
+ // themselves. For example, you create a new annotation named @foo.bar.Baz,
+ // annotate it with @IntDef, and then use @foo.bar.Baz in your signatures.
+ // Here we want to map from @foo.bar.Baz to the corresponding int def.
+ // Don't need to compute this if performing @IntDef or @StringDef lookup
+ ResolvedClass type = annotation.getClassType();
+ if (type != null) {
+ Iterable<ResolvedAnnotation> innerAnnotations = type.getAnnotations();
+ Iterator<ResolvedAnnotation> iterator2 = innerAnnotations.iterator();
+ while (iterator2.hasNext()) {
+ ResolvedAnnotation inner = iterator2.next();
+ if (inner.matches(INT_DEF_ANNOTATION)
+ || inner.matches(PERMISSION_ANNOTATION)
+ || inner.matches(INT_RANGE_ANNOTATION)
+ || inner.matches(STRING_DEF_ANNOTATION)) {
+ if (!iterator.hasNext() && !iterator2.hasNext() && index == 1) {
+ return innerAnnotations;
+ }
+ if (result == null) {
+ result = new ArrayList<ResolvedAnnotation>(2);
+ }
+ result.add(inner);
+ }
+ }
+ }
+ }
+
+ return result != null ? result : Collections.<ResolvedAnnotation>emptyList();
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public
+ List<Class<? extends Node>> getApplicableNodeTypes() {
+ //noinspection unchecked
+ return Arrays.<Class<? extends Node>>asList(
+ MethodInvocation.class,
+ ConstructorInvocation.class,
+ EnumConstant.class);
+ }
+
+ @Nullable
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ return new CallVisitor(context);
+ }
+
+ private class CallVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public CallVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(@NonNull MethodInvocation call) {
+ ResolvedNode resolved = mContext.resolve(call);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ checkCall(call, method);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(@NonNull ConstructorInvocation call) {
+ ResolvedNode resolved = mContext.resolve(call);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ checkCall(call, method);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visitEnumConstant(EnumConstant node) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ checkCall(node, method);
+ }
+
+ return false;
+ }
+
+ private void checkCall(@NonNull Node call, ResolvedMethod method) {
+ Iterable<ResolvedAnnotation> annotations = method.getAnnotations();
+ annotations = filterRelevantAnnotations(annotations);
+ for (ResolvedAnnotation annotation : annotations) {
+ checkMethodAnnotation(mContext, method, call, annotation);
+ }
+
+ // Look for annotations on the class as well: these trickle
+ // down to all the methods in the class
+ ResolvedClass containingClass = method.getContainingClass();
+ annotations = containingClass.getAnnotations();
+ annotations = filterRelevantAnnotations(annotations);
+ for (ResolvedAnnotation annotation : annotations) {
+ checkMethodAnnotation(mContext, method, call, annotation);
+ }
+
+ Iterator<Expression> arguments = JavaContext.getParameters(call);
+ for (int i = 0, n = method.getArgumentCount();
+ i < n && arguments.hasNext();
+ i++) {
+ Expression argument = arguments.next();
+
+ annotations = method.getParameterAnnotations(i);
+ annotations = filterRelevantAnnotations(annotations);
+ checkParameterAnnotations(mContext, argument, call, method, annotations);
+ }
+ while (arguments.hasNext()) { // last parameter is varargs (same parameter annotations)
+ Expression argument = arguments.next();
+ checkParameterAnnotations(mContext, argument, call, method, annotations);
+ }
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
new file mode 100644
index 0000000..23b7c57
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
@@ -0,0 +1,736 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ATTR_LOCALE;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TRANSLATABLE;
+import static com.android.SdkConstants.FD_RES_VALUES;
+import static com.android.SdkConstants.STRING_PREFIX;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TAG_STRING;
+import static com.android.SdkConstants.TAG_STRING_ARRAY;
+import static com.android.SdkConstants.TOOLS_URI;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.Variant;
+import com.android.ide.common.resources.LocaleManager;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LocaleQualifier;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Checks for incomplete translations - e.g. keys that are only present in some
+ * locales but not all.
+ */
+public class TranslationDetector extends ResourceXmlDetector {
+ @VisibleForTesting
+ static boolean sCompleteRegions =
+ System.getenv("ANDROID_LINT_COMPLETE_REGIONS") != null; //$NON-NLS-1$
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ TranslationDetector.class,
+ Scope.ALL_RESOURCES_SCOPE);
+
+ /** Are all translations complete? */
+ public static final Issue MISSING = Issue.create(
+ "MissingTranslation", //$NON-NLS-1$
+ "Incomplete translation",
+ "If an application has more than one locale, then all the strings declared in " +
+ "one language should also be translated in all other languages.\n" +
+ "\n" +
+ "If the string should *not* be translated, you can add the attribute " +
+ "`translatable=\"false\"` on the `<string>` element, or you can define all " +
+ "your non-translatable strings in a resource file called `donottranslate.xml`. " +
+ "Or, you can ignore the issue with a `tools:ignore=\"MissingTranslation\"` " +
+ "attribute.\n" +
+ "\n" +
+ "By default this detector allows regions of a language to just provide a " +
+ "subset of the strings and fall back to the standard language strings. " +
+ "You can require all regions to provide a full translation by setting the " +
+ "environment variable `ANDROID_LINT_COMPLETE_REGIONS`.\n" +
+ "\n" +
+ "You can tell lint (and other tools) which language is the default language " +
+ "in your `res/values/` folder by specifying `tools:locale=\"languageCode\"` for " +
+ "the root `<resources>` element in your resource file. (The `tools` prefix refers " +
+ "to the namespace declaration `http://schemas.android.com/tools`.)",
+ Category.MESSAGES,
+ 8,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /** Are there extra translations that are "unused" (appear only in specific languages) ? */
+ public static final Issue EXTRA = Issue.create(
+ "ExtraTranslation", //$NON-NLS-1$
+ "Extra translation",
+ "If a string appears in a specific language translation file, but there is " +
+ "no corresponding string in the default locale, then this string is probably " +
+ "unused. (It's technically possible that your application is only intended to " +
+ "run in a specific locale, but it's still a good idea to provide a fallback.).\n" +
+ "\n" +
+ "Note that these strings can lead to crashes if the string is looked up on any " +
+ "locale not providing a translation, so it's important to clean them up.",
+ Category.MESSAGES,
+ 6,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ private Set<String> mNames;
+ private Set<String> mTranslatedArrays;
+ private Set<String> mNonTranslatable;
+ private boolean mIgnoreFile;
+ private Map<File, Set<String>> mFileToNames;
+ private Map<File, String> mFileToLocale;
+
+ /** Locations for each untranslated string name. Populated during phase 2, if necessary */
+ private Map<String, Location> mMissingLocations;
+
+ /** Locations for each extra translated string name. Populated during phase 2, if necessary */
+ private Map<String, Location> mExtraLocations;
+
+ /** Error messages for each untranslated string name. Populated during phase 2, if necessary */
+ private Map<String, String> mDescriptions;
+
+ /** Constructs a new {@link TranslationDetector} */
+ public TranslationDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.VALUES;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ TAG_STRING,
+ TAG_STRING_ARRAY
+ );
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ if (context.getDriver().getPhase() == 1) {
+ mFileToNames = new HashMap<File, Set<String>>();
+ }
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ if (context.getPhase() == 1) {
+ mNames = new HashSet<String>();
+ }
+
+ // Convention seen in various projects
+ mIgnoreFile = context.file.getName().startsWith("donottranslate") //$NON-NLS-1$
+ || ResourceUsageModel.isAnalyticsFile(context.file);
+
+ if (!context.getProject().getReportIssues()) {
+ mIgnoreFile = true;
+ }
+ }
+
+ @Override
+ public void afterCheckFile(@NonNull Context context) {
+ if (context.getPhase() == 1) {
+ // Store this layout's set of ids for full project analysis in afterCheckProject
+ if (context.getProject().getReportIssues() && mNames != null && !mNames.isEmpty()) {
+ mFileToNames.put(context.file, mNames);
+
+ Element root = ((XmlContext) context).document.getDocumentElement();
+ if (root != null) {
+ String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
+ if (locale != null && !locale.isEmpty()) {
+ if (mFileToLocale == null) {
+ mFileToLocale = Maps.newHashMap();
+ }
+ mFileToLocale.put(context.file, locale);
+ }
+ // Add in English here if not specified? Worry about false positives listing "en" explicitly
+ }
+ }
+
+ mNames = null;
+ }
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (context.getPhase() == 1) {
+ // NOTE - this will look for the presence of translation strings.
+ // If you create a resource folder but don't actually place a file in it
+ // we won't detect that, but it seems like a smaller problem.
+
+ checkTranslations(context);
+
+ mFileToNames = null;
+
+ if (mMissingLocations != null || mExtraLocations != null) {
+ context.getDriver().requestRepeat(this, Scope.ALL_RESOURCES_SCOPE);
+ }
+ } else {
+ assert context.getPhase() == 2;
+
+ reportMap(context, MISSING, mMissingLocations);
+ reportMap(context, EXTRA, mExtraLocations);
+ mMissingLocations = null;
+ mExtraLocations = null;
+ mDescriptions = null;
+ }
+ }
+
+ private void reportMap(Context context, Issue issue, Map<String, Location> map) {
+ if (map != null) {
+ for (Map.Entry<String, Location> entry : map.entrySet()) {
+ Location location = entry.getValue();
+ String name = entry.getKey();
+ String message = mDescriptions.get(name);
+
+ if (location == null) {
+ location = Location.create(context.getProject().getDir());
+ }
+
+ // We were prepending locations, but we want to prefer the base folders
+ location = Location.reverse(location);
+
+ context.report(issue, location, message);
+ }
+ }
+ }
+
+ private void checkTranslations(Context context) {
+ // Only one file defining strings? If so, no problems.
+ Set<File> files = mFileToNames.keySet();
+ Set<File> parentFolders = new HashSet<File>();
+ for (File file : files) {
+ parentFolders.add(file.getParentFile());
+ }
+ if (parentFolders.size() == 1
+ && FD_RES_VALUES.equals(parentFolders.iterator().next().getName())) {
+ // Only one language - no problems.
+ return;
+ }
+
+ boolean reportMissing = context.isEnabled(MISSING);
+ boolean reportExtra = context.isEnabled(EXTRA);
+
+ // res/strings.xml etc
+ String defaultLanguage = "Default";
+
+ Map<File, String> parentFolderToLanguage = new HashMap<File, String>();
+ for (File parent : parentFolders) {
+ String name = parent.getName();
+
+ // Look up the language for this folder.
+ String language = getLanguageTag(name);
+ if (language == null) {
+ language = defaultLanguage;
+ }
+
+ parentFolderToLanguage.put(parent, language);
+ }
+
+ int languageCount = parentFolderToLanguage.values().size();
+ if (languageCount == 0 || languageCount == 1 && defaultLanguage.equals(
+ parentFolderToLanguage.values().iterator().next())) {
+ // At most one language -- no problems.
+ return;
+ }
+
+ // Merge together the various files building up the translations for each language
+ Map<String, Set<String>> languageToStrings =
+ new HashMap<String, Set<String>>(languageCount);
+ Set<String> allStrings = new HashSet<String>(200);
+ for (File file : files) {
+ String language = null;
+ if (mFileToLocale != null) {
+ String locale = mFileToLocale.get(file);
+ if (locale != null) {
+ int index = locale.indexOf('-');
+ if (index != -1) {
+ locale = locale.substring(0, index);
+ }
+ language = locale;
+ }
+ }
+ if (language == null) {
+ language = parentFolderToLanguage.get(file.getParentFile());
+ }
+ assert language != null : file.getParent();
+ Set<String> fileStrings = mFileToNames.get(file);
+
+ Set<String> languageStrings = languageToStrings.get(language);
+ if (languageStrings == null) {
+ // We don't need a copy; we're done with the string tables now so we
+ // can modify them
+ languageToStrings.put(language, fileStrings);
+ } else {
+ languageStrings.addAll(fileStrings);
+ }
+ allStrings.addAll(fileStrings);
+ }
+
+ Set<String> defaultStrings = languageToStrings.get(defaultLanguage);
+ if (defaultStrings == null) {
+ defaultStrings = new HashSet<String>();
+ }
+
+ // See if it looks like the user has named a specific locale as the base language
+ // (this impacts whether we report strings as "extra" or "missing")
+ if (mFileToLocale != null) {
+ Set<String> specifiedLocales = Sets.newHashSet();
+ for (Map.Entry<File, String> entry : mFileToLocale.entrySet()) {
+ String locale = entry.getValue();
+ int index = locale.indexOf('-');
+ if (index != -1) {
+ locale = locale.substring(0, index);
+ }
+ specifiedLocales.add(locale);
+ }
+ if (specifiedLocales.size() == 1) {
+ String first = specifiedLocales.iterator().next();
+ Set<String> languageStrings = languageToStrings.get(first);
+ assert languageStrings != null;
+ defaultStrings.addAll(languageStrings);
+ }
+ }
+
+ int stringCount = allStrings.size();
+
+ // Treat English is the default language if not explicitly specified
+ if (!sCompleteRegions && !languageToStrings.containsKey("en")
+ && mFileToLocale == null) { //$NON-NLS-1$
+ // But only if we have an actual region
+ for (String l : languageToStrings.keySet()) {
+ if (l.startsWith("en-")) { //$NON-NLS-1$
+ languageToStrings.put("en", defaultStrings); //$NON-NLS-1$
+ break;
+ }
+ }
+ }
+
+ List<String> resConfigLanguages = getResConfigLanguages(context.getMainProject());
+ if (resConfigLanguages != null) {
+ List<String> keys = Lists.newArrayList(languageToStrings.keySet());
+ for (String locale : keys) {
+ if (defaultLanguage.equals(locale)) {
+ continue;
+ }
+ String language = locale;
+ int index = language.indexOf('-');
+ if (index != -1) {
+ // Strip off region
+ language = language.substring(0, index);
+ }
+ if (!resConfigLanguages.contains(language)) {
+ languageToStrings.remove(locale);
+ }
+ }
+ }
+
+ // Do we need to resolve fallback strings for regions that only define a subset
+ // of the strings in the language and fall back on the main language for the rest?
+ if (!sCompleteRegions) {
+ for (String l : languageToStrings.keySet()) {
+ if (l.indexOf('-') != -1) {
+ // Yes, we have regions. Merge all base language string names into each region.
+ for (Map.Entry<String, Set<String>> entry : languageToStrings.entrySet()) {
+ Set<String> strings = entry.getValue();
+ if (stringCount != strings.size()) {
+ String languageRegion = entry.getKey();
+ int regionIndex = languageRegion.indexOf('-');
+ if (regionIndex != -1) {
+ String language = languageRegion.substring(0, regionIndex);
+ Set<String> fallback = languageToStrings.get(language);
+ if (fallback != null) {
+ strings.addAll(fallback);
+ }
+ }
+ }
+ }
+ // We only need to do this once; when we see the first region we know
+ // we need to do it; once merged we can bail
+ break;
+ }
+ }
+ }
+
+ // Fast check to see if there's no problem: if the default locale set is the
+ // same as the all set (meaning there are no extra strings in the other languages)
+ // then we can quickly determine if everything is okay by just making sure that
+ // each language defines everything. If that's the case they will all have the same
+ // string count.
+ if (stringCount == defaultStrings.size()) {
+ boolean haveError = false;
+ for (Map.Entry<String, Set<String>> entry : languageToStrings.entrySet()) {
+ Set<String> strings = entry.getValue();
+ if (stringCount != strings.size()) {
+ haveError = true;
+ break;
+ }
+ }
+ if (!haveError) {
+ return;
+ }
+ }
+
+ List<String> languages = new ArrayList<String>(languageToStrings.keySet());
+ Collections.sort(languages);
+ for (String language : languages) {
+ Set<String> strings = languageToStrings.get(language);
+ if (defaultLanguage.equals(language)) {
+ continue;
+ }
+
+ // if strings.size() == stringCount, then this language is defining everything,
+ // both all the default language strings and the union of all extra strings
+ // defined in other languages, so there's no problem.
+ if (stringCount != strings.size()) {
+ if (reportMissing) {
+ Set<String> difference = Sets.difference(defaultStrings, strings);
+ if (!difference.isEmpty()) {
+ if (mMissingLocations == null) {
+ mMissingLocations = new HashMap<String, Location>();
+ }
+ if (mDescriptions == null) {
+ mDescriptions = new HashMap<String, String>();
+ }
+
+ for (String s : difference) {
+ mMissingLocations.put(s, null);
+ String message = mDescriptions.get(s);
+ if (message == null) {
+ message = String.format("\"`%1$s`\" is not translated in %2$s",
+ s, getLanguageDescription(language));
+ } else {
+ message = message + ", " + getLanguageDescription(language);
+ }
+ mDescriptions.put(s, message);
+ }
+ }
+ }
+ }
+
+ if (stringCount != defaultStrings.size()) {
+ if (reportExtra) {
+ Set<String> difference = Sets.difference(strings, defaultStrings);
+ if (!difference.isEmpty()) {
+ if (mExtraLocations == null) {
+ mExtraLocations = new HashMap<String, Location>();
+ }
+ if (mDescriptions == null) {
+ mDescriptions = new HashMap<String, String>();
+ }
+
+ for (String s : difference) {
+ if (mTranslatedArrays != null && mTranslatedArrays.contains(s)) {
+ continue;
+ }
+ if (mNonTranslatable != null && mNonTranslatable.contains(s)) {
+ continue;
+ }
+
+ mExtraLocations.put(s, null);
+ String message = String.format(
+ "\"`%1$s`\" is translated here but not found in default locale", s);
+ mDescriptions.put(s, message);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static String getLanguageDescription(@NonNull String locale) {
+ int index = locale.indexOf('-');
+ String regionCode = null;
+ String languageCode = locale;
+ if (index != -1) {
+ regionCode = locale.substring(index + 1).toUpperCase(Locale.US);
+ languageCode = locale.substring(0, index).toLowerCase(Locale.US);
+ }
+
+ String languageName = LocaleManager.getLanguageName(languageCode);
+ if (languageName != null) {
+ if (regionCode != null) {
+ String regionName = LocaleManager.getRegionName(regionCode);
+ if (regionName != null) {
+ languageName = languageName + ": " + regionName;
+ }
+ }
+
+ return String.format("\"%1$s\" (%2$s)", locale, languageName);
+ } else {
+ return '"' + locale + '"';
+ }
+ }
+
+
+ /** Look up the language for the given folder name */
+ private static String getLanguageTag(String name) {
+ if (FD_RES_VALUES.equals(name)) {
+ return null;
+ }
+
+ FolderConfiguration configuration = FolderConfiguration.getConfigForFolder(name);
+ if (configuration != null) {
+ LocaleQualifier locale = configuration.getLocaleQualifier();
+ if (locale != null && !locale.hasFakeValue()) {
+ return locale.getTag();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (mIgnoreFile) {
+ return;
+ }
+
+ Attr attribute = element.getAttributeNode(ATTR_NAME);
+
+ if (context.getPhase() == 2) {
+ // Just locating names requested in the {@link #mLocations} map
+ if (attribute == null) {
+ return;
+ }
+ String name = attribute.getValue();
+ if (mMissingLocations != null && mMissingLocations.containsKey(name)) {
+ String language = getLanguageTag(context.file.getParentFile().getName());
+ if (language == null) {
+ if (context.getDriver().isSuppressed(context, MISSING, element)) {
+ mMissingLocations.remove(name);
+ return;
+ }
+
+ Location location = context.getLocation(attribute);
+ location.setClientData(element);
+ location.setSecondary(mMissingLocations.get(name));
+ mMissingLocations.put(name, location);
+ }
+ }
+ if (mExtraLocations != null && mExtraLocations.containsKey(name)) {
+ String language = getLanguageTag(context.file.getParentFile().getName());
+ if (language != null) {
+ if (context.getDriver().isSuppressed(context, EXTRA, element)) {
+ mExtraLocations.remove(name);
+ return;
+ }
+ Location location = context.getLocation(attribute);
+ location.setClientData(element);
+ location.setMessage("Also translated here");
+ location.setSecondary(mExtraLocations.get(name));
+ mExtraLocations.put(name, location);
+ }
+ }
+ return;
+ }
+
+ assert context.getPhase() == 1;
+ if (attribute == null || attribute.getValue().isEmpty()) {
+ context.report(MISSING, element, context.getLocation(element),
+ "Missing `name` attribute in `<string>` declaration");
+ } else {
+ String name = attribute.getValue();
+
+ Attr translatable = element.getAttributeNode(ATTR_TRANSLATABLE);
+ if (translatable != null && !Boolean.valueOf(translatable.getValue())) {
+ String l = LintUtils.getLocaleAndRegion(context.file.getParentFile().getName());
+ //noinspection VariableNotUsedInsideIf
+ if (l != null) {
+ context.report(EXTRA, translatable, context.getLocation(translatable),
+ "Non-translatable resources should only be defined in the base " +
+ "`values/` folder");
+ } else {
+ if (mNonTranslatable == null) {
+ mNonTranslatable = new HashSet<String>();
+ }
+ mNonTranslatable.add(name);
+ }
+ return;
+ } else if (isServiceKey(name)
+ // Older versions of the templates shipped with these not marked as
+ // non-translatable; don't flag them
+ || name.equals("google_maps_key") //$NON-NLS-1$
+ || name.equals("google_maps_key_instructions")) { //$NON-NLS-1$
+ if (mNonTranslatable == null) {
+ mNonTranslatable = new HashSet<String>();
+ }
+ mNonTranslatable.add(name);
+ return;
+ }
+
+ if (element.getTagName().equals(TAG_STRING_ARRAY) &&
+ allItemsAreReferences(element)) {
+ // No need to provide translations for string arrays where all
+ // the children items are defined as translated string resources,
+ // e.g.
+ // <string-array name="foo">
+ // <item>@string/item1</item>
+ // <item>@string/item2</item>
+ // </string-array>
+ // However, we need to remember these names such that we don't consider
+ // these arrays "extra" if one of the *translated* versions of the array
+ // perform an inline translation of an array item
+ if (mTranslatedArrays == null) {
+ mTranslatedArrays = new HashSet<String>();
+ }
+ mTranslatedArrays.add(name);
+ return;
+ }
+
+ // Check for duplicate name definitions? No, because there can be
+ // additional customizations like product=
+ //if (mNames.contains(name)) {
+ // context.mClient.report(ISSUE, context.getLocation(attribute),
+ // String.format("Duplicate name %1$s, already defined earlier in this file",
+ // name));
+ //}
+
+ mNames.add(name);
+
+ if (mNonTranslatable != null && mNonTranslatable.contains(name)) {
+ String message = String.format("The resource string \"`%1$s`\" has been marked as " +
+ "`translatable=\"false\"`", name);
+ context.report(EXTRA, attribute, context.getLocation(attribute), message);
+ }
+
+ // TBD: Also make sure that the strings are not empty or placeholders?
+ }
+ }
+
+ public static boolean isServiceKey(@NonNull String name) {
+ // These are keys used by misc developer services.
+ // Configuration files provided by for example
+ // https://developers.google.com/cloud-messaging/android/client
+ // in earlier versions would omit translatable="false", which meant users
+ // would run into fatal translation errors at build time.
+ // See for example
+ // https://code.google.com/p/android/issues/detail?id=195824
+ return name.equals("gcm_defaultSenderId")
+ || name.equals("google_app_id")
+ || name.equals("ga_trackingID");
+ }
+
+ private static boolean allItemsAreReferences(Element element) {
+ assert element.getTagName().equals(TAG_STRING_ARRAY);
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node item = childNodes.item(i);
+ if (item.getNodeType() == Node.ELEMENT_NODE &&
+ TAG_ITEM.equals(item.getNodeName())) {
+ NodeList itemChildren = item.getChildNodes();
+ for (int j = 0, m = itemChildren.getLength(); j < m; j++) {
+ Node valueNode = itemChildren.item(j);
+ if (valueNode.getNodeType() == Node.TEXT_NODE) {
+ String value = valueNode.getNodeValue().trim();
+ if (!value.startsWith(ANDROID_PREFIX)
+ && !value.startsWith(STRING_PREFIX)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Nullable
+ private static List<String> getResConfigLanguages(@NonNull Project project) {
+ if (project.isGradleProject() && project.getGradleProjectModel() != null &&
+ project.getCurrentVariant() != null) {
+ Set<String> relevantDensities = Sets.newHashSet();
+ Variant variant = project.getCurrentVariant();
+ List<String> variantFlavors = variant.getProductFlavors();
+ AndroidProject gradleProjectModel = project.getGradleProjectModel();
+
+ addResConfigsFromFlavor(relevantDensities, null,
+ project.getGradleProjectModel().getDefaultConfig());
+ for (ProductFlavorContainer container : gradleProjectModel.getProductFlavors()) {
+ addResConfigsFromFlavor(relevantDensities, variantFlavors, container);
+ }
+ if (!relevantDensities.isEmpty()) {
+ ArrayList<String> strings = Lists.newArrayList(relevantDensities);
+ Collections.sort(strings);
+ return strings;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds in the resConfig values specified by the given flavor container, assuming
+ * it's in one of the relevant variantFlavors, into the given set
+ */
+ private static void addResConfigsFromFlavor(@NonNull Set<String> relevantLanguages,
+ @Nullable List<String> variantFlavors,
+ @NonNull ProductFlavorContainer container) {
+ ProductFlavor flavor = container.getProductFlavor();
+ if (variantFlavors == null || variantFlavors.contains(flavor.getName())) {
+ if (!flavor.getResourceConfigurations().isEmpty()) {
+ for (String resConfig : flavor.getResourceConfigurations()) {
+ // Look for languages; these are of length 2. (ResConfigs
+ // can also refer to densities, etc.)
+ if (resConfig.length() == 2) {
+ relevantLanguages.add(resConfig);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetector.java
new file mode 100644
index 0000000..7e92f89
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetector.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.Return;
+import lombok.ast.Statement;
+
+public class TrustAllX509TrustManagerDetector extends Detector implements JavaScanner,
+ ClassScanner {
+
+ @SuppressWarnings("unchecked")
+ private static final Implementation IMPLEMENTATION =
+ new Implementation(TrustAllX509TrustManagerDetector.class,
+ EnumSet.of(Scope.JAVA_LIBRARIES, Scope.JAVA_FILE),
+ Scope.JAVA_FILE_SCOPE);
+
+ public static final Issue ISSUE = Issue.create("TrustAllX509TrustManager",
+ "Insecure TLS/SSL trust manager",
+ "This check looks for X509TrustManager implementations whose `checkServerTrusted` or " +
+ "`checkClientTrusted` methods do nothing (thus trusting any certificate chain) " +
+ "which could result in insecure network traffic caused by trusting arbitrary " +
+ "TLS/SSL certificates presented by peers.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ public TrustAllX509TrustManagerDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return Collections.singletonList("javax.net.ssl.X509TrustManager");
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+ @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+ NormalTypeBody body;
+ if (declarationOrAnonymous instanceof NormalTypeBody) {
+ body = (NormalTypeBody) declarationOrAnonymous;
+ } else if (node != null) {
+ body = node.astBody();
+ } else {
+ return;
+ }
+
+ for (Node member : body.astMembers()) {
+ if (member instanceof MethodDeclaration) {
+ MethodDeclaration declaration = (MethodDeclaration)member;
+ String methodName = declaration.astMethodName().astValue();
+ if ("checkServerTrusted".equals(methodName)
+ || "checkClientTrusted".equals(methodName)) {
+
+ // For now very simple; only checks if nothing is done.
+ // Future work: Improve this check to be less sensitive to irrelevant
+ // instructions/statements/invocations (e.g. System.out.println) by
+ // looking for calls that could lead to a CertificateException being
+ // thrown, e.g. throw statement within the method itself or invocation
+ // of another method that may throw a CertificateException, and only
+ // reporting an issue if none of these calls are found. ControlFlowGraph
+ // may be useful here.
+
+ boolean complex = false;
+ for (Statement statement : declaration.astBody().astContents()) {
+ if (!(statement instanceof Return)) {
+ complex = true;
+ break;
+ }
+ }
+
+ if (!complex) {
+ Location location = context.getNameLocation(declaration);
+ String message = getErrorMessage(methodName);
+ context.report(ISSUE, declaration, location, message);
+ }
+ }
+ }
+ }
+ }
+
+ @NonNull
+ private static String getErrorMessage(String methodName) {
+ return "`" + methodName + "` is empty, which could cause " +
+ "insecure network traffic due to trusting arbitrary TLS/SSL " +
+ "certificates presented by peers";
+ }
+
+ // ---- Implements ClassScanner ----
+ // Only used for libraries where we have to analyze bytecode
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public void checkClass(@NonNull final ClassContext context,
+ @NonNull ClassNode classNode) {
+ if (!context.isFromClassLibrary()) {
+ // Non-library code checked at the AST level
+ return;
+ }
+ if (!classNode.interfaces.contains("javax/net/ssl/X509TrustManager")) {
+ return;
+ }
+ List methodList = classNode.methods;
+ for (Object m : methodList) {
+ MethodNode method = (MethodNode) m;
+ if ("checkServerTrusted".equals(method.name) ||
+ "checkClientTrusted".equals(method.name)) {
+ InsnList nodes = method.instructions;
+ boolean emptyMethod = true; // Stays true if method doesn't perform any "real"
+ // operations
+ for (int i = 0, n = nodes.size(); i < n; i++) {
+ // Future work: Improve this check to be less sensitive to irrelevant
+ // instructions/statements/invocations (e.g. System.out.println) by
+ // looking for calls that could lead to a CertificateException being
+ // thrown, e.g. throw statement within the method itself or invocation
+ // of another method that may throw a CertificateException, and only
+ // reporting an issue if none of these calls are found. ControlFlowGraph
+ // may be useful here.
+ AbstractInsnNode instruction = nodes.get(i);
+ int type = instruction.getType();
+ if (type != AbstractInsnNode.LABEL && type != AbstractInsnNode.LINE &&
+ !(type == AbstractInsnNode.INSN &&
+ instruction.getOpcode() == Opcodes.RETURN)) {
+ emptyMethod = false;
+ break;
+ }
+ }
+ if (emptyMethod) {
+ Location location = context.getLocation(method, classNode);
+ context.report(ISSUE, location, getErrorMessage(method.name));
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
new file mode 100644
index 0000000..fbb6dee
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ATTR_LOCALE;
+import static com.android.SdkConstants.ATTR_TRANSLATABLE;
+import static com.android.SdkConstants.FD_RES_VALUES;
+import static com.android.SdkConstants.TAG_PLURALS;
+import static com.android.SdkConstants.TAG_STRING;
+import static com.android.SdkConstants.TAG_STRING_ARRAY;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.tools.lint.checks.TypoLookup.isLetter;
+import static com.google.common.base.Objects.equal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.configuration.LocaleQualifier;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.base.Charsets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Check which looks for likely typos in Strings.
+ * <p>
+ * TODO:
+ * <ul>
+ * <li> Add check of Java String literals too!
+ * <li> Add support for <b>additional</b> languages. The typo detector is now
+ * multilingual and looks for typos-*locale*.txt files to use. However,
+ * we need to seed it with additional typo databases. I did some searching
+ * and came up with some alternatives. Here's the strategy I used:
+ * Used Google Translate to translate "Wikipedia Common Misspellings", and
+ * then I went to google.no, google.fr etc searching with that translation, and
+ * came up with what looks like wikipedia language local lists of typos.
+ * This is how I found the Norwegian one for example:
+ * <br>
+ * http://no.wikipedia.org/wiki/Wikipedia:Liste_over_alminnelige_stavefeil/Maskinform
+ * <br>
+ * Here are some additional possibilities not yet processed:
+ * <ul>
+ * <li> French: http://fr.wikipedia.org/wiki/Wikip%C3%A9dia:Liste_de_fautes_d'orthographe_courantes
+ * (couldn't find a machine-readable version there?)
+ * <li> Swedish:
+ * http://sv.wikipedia.org/wiki/Wikipedia:Lista_%C3%B6ver_vanliga_spr%C3%A5kfel
+ * (couldn't find a machine-readable version there?)
+ * <li> German
+ * http://de.wikipedia.org/wiki/Wikipedia:Liste_von_Tippfehlern/F%C3%BCr_Maschinen
+ * </ul>
+ * <li> Consider also digesting files like
+ * http://sv.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/Typos
+ * See http://en.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/User_manual.
+ * </ul>
+ */
+public class TypoDetector extends ResourceXmlDetector {
+ @Nullable private TypoLookup mLookup;
+ @Nullable private String mLastLanguage;
+ @Nullable private String mLastRegion;
+ @Nullable private String mLanguage;
+ @Nullable private String mRegion;
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "Typos", //$NON-NLS-1$
+ "Spelling error",
+
+ "This check looks through the string definitions, and if it finds any words " +
+ "that look like likely misspellings, they are flagged.",
+ Category.MESSAGES,
+ 7,
+ Severity.WARNING,
+ new Implementation(
+ TypoDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Constructs a new detector */
+ public TypoDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.VALUES;
+ }
+
+ /** Look up the locale and region from the given parent folder name and store it
+ * in {@link #mLanguage} and {@link #mRegion} */
+ private void initLocale(@NonNull String parent) {
+ mLanguage = null;
+ mRegion = null;
+
+ if (parent.equals(FD_RES_VALUES)) {
+ return;
+ }
+
+ LocaleQualifier locale = LintUtils.getLocale(parent);
+ if (locale != null) {
+ mLanguage = locale.getLanguage();
+ mRegion = locale.hasRegion() ? locale.getRegion() : null;
+ }
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ initLocale(context.file.getParentFile().getName());
+ if (mLanguage == null) {
+ // Check to see if the user has specified the language for this folder
+ // using a tools:locale attribute
+ if (context instanceof XmlContext) {
+ Element root = ((XmlContext) context).document.getDocumentElement();
+ if (root != null) {
+ String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
+ if (locale != null && !locale.isEmpty()) {
+ initLocale(FD_RES_VALUES + '-' + locale);
+ }
+ }
+ }
+
+ if (mLanguage == null) {
+ mLanguage = "en"; //$NON-NLS-1$
+ }
+ }
+
+ if (!equal(mLastLanguage, mLanguage) || !equal(mLastRegion, mRegion)) {
+ mLookup = TypoLookup.get(context.getClient(), mLanguage, mRegion);
+ mLastLanguage = mLanguage;
+ mLastRegion = mRegion;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(
+ TAG_STRING,
+ TAG_STRING_ARRAY,
+ TAG_PLURALS
+ );
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ if (mLookup == null) {
+ return;
+ }
+
+ visit(context, element, element);
+ }
+
+ private void visit(XmlContext context, Element parent, Node node) {
+ if (node.getNodeType() == Node.TEXT_NODE) {
+ // TODO: Figure out how to deal with entities
+ check(context, parent, node, node.getNodeValue());
+ } else {
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ visit(context, parent, children.item(i));
+ }
+ }
+ }
+
+ private void check(XmlContext context, Element element, Node node, String text) {
+ int max = text.length();
+ int index = 0;
+ int lastWordBegin = -1;
+ int lastWordEnd = -1;
+ boolean checkedTypos = false;
+
+ for (; index < max; index++) {
+ char c = text.charAt(index);
+ if (!Character.isWhitespace(c)) {
+ if (c == '@' || (c == '?')) {
+ // Don't look for typos in resource references; they are not
+ // user visible anyway
+ return;
+ }
+ break;
+ }
+ }
+
+ while (index < max) {
+ for (; index < max; index++) {
+ char c = text.charAt(index);
+ if (c == '\\') {
+ index++;
+ } else if (Character.isLetter(c)) {
+ break;
+ }
+ }
+ if (index >= max) {
+ return;
+ }
+ int begin = index;
+ for (; index < max; index++) {
+ char c = text.charAt(index);
+ if (c == '\\') {
+ index++;
+ break;
+ } else if (!Character.isLetter(c)) {
+ break;
+ } else if (text.charAt(index) >= 0x80) {
+ // Switch to UTF-8 handling for this string
+ if (checkedTypos) {
+ // If we've already checked words we may have reported typos
+ // so create a substring from the current word and on.
+ byte[] utf8Text = text.substring(begin).getBytes(Charsets.UTF_8);
+ check(context, element, node, utf8Text, 0, utf8Text.length, text, begin);
+ } else {
+ // If all we've done so far is skip whitespace (common scenario)
+ // then no need to substring the text, just re-search with the
+ // UTF-8 routines
+ byte[] utf8Text = text.getBytes(Charsets.UTF_8);
+ check(context, element, node, utf8Text, 0, utf8Text.length, text, 0);
+ }
+ return;
+ }
+ }
+
+ int end = index;
+ checkedTypos = true;
+ assert mLookup != null;
+ List<String> replacements = mLookup.getTypos(text, begin, end);
+ if (replacements != null && isTranslatable(element)) {
+ reportTypo(context, node, text, begin, replacements);
+ }
+
+ checkRepeatedWords(context, element, node, text, lastWordBegin, lastWordEnd, begin,
+ end);
+
+ lastWordBegin = begin;
+ lastWordEnd = end;
+ index = end + 1;
+ }
+ }
+
+ private static void checkRepeatedWords(XmlContext context, Element element, Node node,
+ String text, int lastWordBegin, int lastWordEnd, int begin, int end) {
+ if (lastWordBegin != -1 && end - begin == lastWordEnd - lastWordBegin
+ && end - begin > 1) {
+ // See whether we have a repeated word
+ boolean different = false;
+ for (int i = lastWordBegin, j = begin; i < lastWordEnd; i++, j++) {
+ if (text.charAt(i) != text.charAt(j)) {
+ different = true;
+ break;
+ }
+ }
+ if (!different && onlySpace(text, lastWordEnd, begin) && isTranslatable(element)) {
+ reportRepeatedWord(context, node, text, lastWordBegin, begin, end);
+ }
+ }
+ }
+
+ private static boolean onlySpace(String text, int fromInclusive, int toExclusive) {
+ for (int i = fromInclusive; i < toExclusive; i++) {
+ if (!Character.isWhitespace(text.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void check(XmlContext context, Element element, Node node, byte[] utf8Text,
+ int byteStart, int byteEnd, String text, int charStart) {
+ int lastWordBegin = -1;
+ int lastWordEnd = -1;
+ int index = byteStart;
+ while (index < byteEnd) {
+ // Find beginning of word
+ while (index < byteEnd) {
+ byte b = utf8Text[index];
+ if (b == '\\') {
+ index++;
+ charStart++;
+ if (index < byteEnd) {
+ b = utf8Text[index];
+ }
+ } else if (isLetter(b)) {
+ break;
+ }
+ index++;
+ if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
+ // First characters in UTF-8 are always ASCII (0 high bit) or 11XXXXXX
+ charStart++;
+ }
+ }
+
+ if (index >= byteEnd) {
+ return;
+ }
+ int charEnd = charStart;
+ int begin = index;
+
+ // Find end of word. Unicode has the nice property that even 2nd, 3rd and 4th
+ // bytes won't match these ASCII characters (because the high bit must be set there)
+ while (index < byteEnd) {
+ byte b = utf8Text[index];
+ if (b == '\\') {
+ index++;
+ charEnd++;
+ if (index < byteEnd) {
+ b = utf8Text[index++];
+ if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
+ charEnd++;
+ }
+ }
+ break;
+ } else if (!isLetter(b)) {
+ break;
+ }
+ index++;
+ if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
+ // First characters in UTF-8 are always ASCII (0 high bit) or 11XXXXXX
+ charEnd++;
+ }
+ }
+
+ int end = index;
+ List<String> replacements = mLookup.getTypos(utf8Text, begin, end);
+ if (replacements != null && isTranslatable(element)) {
+ reportTypo(context, node, text, charStart, replacements);
+ }
+
+ checkRepeatedWords(context, element, node, text, lastWordBegin, lastWordEnd, charStart,
+ charEnd);
+
+ lastWordBegin = charStart;
+ lastWordEnd = charEnd;
+ charStart = charEnd;
+ }
+ }
+
+ private static boolean isTranslatable(Element element) {
+ Attr translatable = element.getAttributeNode(ATTR_TRANSLATABLE);
+ return translatable == null || Boolean.valueOf(translatable.getValue());
+ }
+
+ /** Report the typo found at the given offset and suggest the given replacements */
+ private static void reportTypo(XmlContext context, Node node, String text, int begin,
+ List<String> replacements) {
+ if (replacements.size() < 2) {
+ return;
+ }
+
+ String typo = replacements.get(0);
+ String word = text.substring(begin, begin + typo.length());
+
+ String first = null;
+ String message;
+
+ boolean isCapitalized = Character.isUpperCase(word.charAt(0));
+ StringBuilder sb = new StringBuilder(40);
+ for (int i = 1, n = replacements.size(); i < n; i++) {
+ String replacement = replacements.get(i);
+ if (first == null) {
+ first = replacement;
+ }
+ if (sb.length() > 0) {
+ sb.append(" or ");
+ }
+ sb.append('"');
+ if (isCapitalized) {
+ sb.append(Character.toUpperCase(replacement.charAt(0)));
+ sb.append(replacement.substring(1));
+ } else {
+ sb.append(replacement);
+ }
+ sb.append('"');
+ }
+
+ if (first != null && first.equalsIgnoreCase(word)) {
+ if (first.equals(word)) {
+ return;
+ }
+ message = String.format(
+ "\"%1$s\" is usually capitalized as \"%2$s\"",
+ word, first);
+ } else {
+ message = String.format(
+ "\"%1$s\" is a common misspelling; did you mean %2$s ?",
+ word, sb.toString());
+ }
+
+ int end = begin + word.length();
+ context.report(ISSUE, node, context.getLocation(node, begin, end), message);
+ }
+
+ /** Reports a repeated word */
+ private static void reportRepeatedWord(XmlContext context, Node node, String text,
+ int lastWordBegin,
+ int begin, int end) {
+ String message = String.format(
+ "Repeated word \"%1$s\" in message: possible typo",
+ text.substring(begin, end));
+ Location location = context.getLocation(node, lastWordBegin, end);
+ context.report(ISSUE, node, location, message);
+ }
+
+ /** Simple data holder used to return several values from
+ * {@link #getSuggestions(String, TextFormat)}
+ */
+ public static class TypoSuggestionInfo {
+ @NonNull
+ private final String mOriginal;
+ @NonNull
+ private final List<String> mReplacements;
+
+ public TypoSuggestionInfo(@NonNull String original, @NonNull List<String> replacements) {
+ mOriginal = original;
+ mReplacements = replacements;
+ }
+
+ @NonNull
+ public String getOriginal() {
+ return mOriginal;
+ }
+
+ @NonNull
+ public List<String> getReplacements() {
+ return mReplacements;
+ }
+ }
+
+ /** Returns the suggested replacements and original string, for the given typo.
+ * The error message <b>must</b> be one supplied by lint.
+ *
+ * @param errorMessage the error message
+ * @param format the format of the error message
+ * @return {@link TypoSuggestionInfo}
+ */
+ @NonNull
+ public static TypoSuggestionInfo getSuggestions(@NonNull String errorMessage,
+ @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+
+ // The words are all in quotes; the first word is the misspelling,
+ // the other words are the suggested replacements
+ List<String> replacements = new ArrayList<String>();
+ // Skip the typo
+ int index = errorMessage.indexOf('"');
+ int originalEndIndex = errorMessage.indexOf('"', index + 1);
+ String original = errorMessage.substring(index + 1, originalEndIndex);
+
+ index = originalEndIndex + 1;
+
+ while (true) {
+ index = errorMessage.indexOf('"', index);
+ if (index == -1) {
+ break;
+ }
+ index++;
+ int start = index;
+ index = errorMessage.indexOf('"', index);
+ if (index == -1) {
+ index = errorMessage.length();
+ }
+ replacements.add(errorMessage.substring(start, index));
+ index++;
+ }
+
+ return new TypoSuggestionInfo(original, replacements);
+ }
+
+ /**
+ * Returns the typo word in the error message from this detector
+ *
+ * @param errorMessage the error message produced earlier by this detector
+ * @param format the format of the error message
+ * @return the typo
+ */
+ @Nullable
+ public static String getTypo(@NonNull String errorMessage, @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+ // The words are all in quotes
+ int index = errorMessage.indexOf('"');
+ int start = index + 1;
+ index = errorMessage.indexOf('"', start);
+ if (index != -1) {
+ return errorMessage.substring(start, index);
+ }
+
+ return null;
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
new file mode 100644
index 0000000..f9da908
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.io.ByteSink;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.WeakHashMap;
+
+/**
+ * Database of common typos / misspellings.
+ */
+public class TypoLookup {
+ private static final TypoLookup NONE = new TypoLookup();
+
+ /** String separating misspellings and suggested replacements in the text file */
+ private static final String WORD_SEPARATOR = "->"; //$NON-NLS-1$
+
+ /** Relative path to the typos database file within the Lint installation */
+ private static final String XML_FILE_PATH = "tools/support/typos-%1$s.txt"; //$NON-NLS-1$
+ private static final String FILE_HEADER = "Typo database used by Android lint\000";
+ private static final int BINARY_FORMAT_VERSION = 2;
+ private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
+ private static final boolean DEBUG_SEARCH = false;
+ private static final boolean WRITE_STATS = false;
+ /** Default size to reserve for each API entry when creating byte buffer to build up data */
+ private static final int BYTES_PER_ENTRY = 28;
+
+ private byte[] mData;
+ private int[] mIndices;
+ private int mWordCount;
+
+ private static final WeakHashMap<String, TypoLookup> sInstanceMap =
+ new WeakHashMap<String, TypoLookup>();
+
+ /**
+ * Returns an instance of the Typo database for the given locale
+ *
+ * @param client the client to associate with this database - used only for
+ * logging. The database object may be shared among repeated
+ * invocations, and in that case client used will be the one
+ * originally passed in. In other words, this parameter may be
+ * ignored if the client created is not new.
+ * @param locale the locale to look up a typo database for (should be a
+ * language code (ISO 639-1, two lowercase character names)
+ * @param region the region to look up a typo database for (should be a two
+ * letter ISO 3166-1 alpha-2 country code in upper case) language
+ * code
+ * @return a (possibly shared) instance of the typo database, or null if its
+ * data can't be found
+ */
+ @Nullable
+ public static TypoLookup get(@NonNull LintClient client, @NonNull String locale,
+ @Nullable String region) {
+ synchronized (TypoLookup.class) {
+ String key = locale;
+
+ if (region != null && region.length() == 2) { // skip BCP-47 regions
+ // Allow for region-specific dictionaries. See for example
+ // http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences
+ assert region.length() == 2
+ && Character.isUpperCase(region.charAt(0))
+ && Character.isUpperCase(region.charAt(1)) : region;
+ // Look for typos-en-rUS.txt etc
+ key = locale + 'r' + region;
+ }
+
+ TypoLookup db = sInstanceMap.get(key);
+ if (db == null) {
+ String path = String.format(XML_FILE_PATH, key);
+ File file = client.findResource(path);
+ if (file == null) {
+ // AOSP build environment?
+ String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
+ if (build != null) {
+ file = new File(build, ("sdk/files/" //$NON-NLS-1$
+ + path.substring(path.lastIndexOf('/') + 1))
+ .replace('/', File.separatorChar));
+ }
+ }
+
+ if (file == null || !file.exists()) {
+ //noinspection VariableNotUsedInsideIf
+ if (region != null) {
+ // Fall back to the generic locale (non-region-specific) database
+ return get(client, locale, null);
+ }
+ db = NONE;
+ } else {
+ db = get(client, file);
+ assert db != null : file;
+ }
+ sInstanceMap.put(key, db);
+ }
+
+ if (db == NONE) {
+ return null;
+ } else {
+ return db;
+ }
+ }
+ }
+
+ /**
+ * Returns an instance of the typo database
+ *
+ * @param client the client to associate with this database - used only for
+ * logging
+ * @param xmlFile the XML file containing configuration data to use for this
+ * database
+ * @return a (possibly shared) instance of the typo database, or null
+ * if its data can't be found
+ */
+ @Nullable
+ private static TypoLookup get(LintClient client, File xmlFile) {
+ if (!xmlFile.exists()) {
+ client.log(null, "The typo database file %1$s does not exist", xmlFile);
+ return null;
+ }
+
+ String name = xmlFile.getName();
+ if (LintUtils.endsWith(name, DOT_XML)) {
+ name = name.substring(0, name.length() - DOT_XML.length());
+ }
+ File cacheDir = client.getCacheDir(true/*create*/);
+ if (cacheDir == null) {
+ cacheDir = xmlFile.getParentFile();
+ }
+
+ File binaryData = new File(cacheDir, name
+ // Incorporate version number in the filename to avoid upgrade filename
+ // conflicts on Windows (such as issue #26663)
+ + '-' + BINARY_FORMAT_VERSION + ".bin"); //$NON-NLS-1$
+
+ if (DEBUG_FORCE_REGENERATE_BINARY) {
+ System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom "
+ + xmlFile + "\nto " + binaryData);
+ if (!createCache(client, xmlFile, binaryData)) {
+ return null;
+ }
+ } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified()) {
+ if (!createCache(client, xmlFile, binaryData)) {
+ return null;
+ }
+ }
+
+ if (!binaryData.exists()) {
+ client.log(null, "The typo database file %1$s does not exist", binaryData);
+ return null;
+ }
+
+ return new TypoLookup(client, xmlFile, binaryData);
+ }
+
+ private static boolean createCache(LintClient client, File xmlFile, File binaryData) {
+ long begin = 0;
+ if (WRITE_STATS) {
+ begin = System.currentTimeMillis();
+ }
+
+ // Read in data
+ List<String> lines;
+ try {
+ lines = Files.readLines(xmlFile, Charsets.UTF_8);
+ } catch (IOException e) {
+ client.log(e, "Can't read typo database file");
+ return false;
+ }
+
+ if (WRITE_STATS) {
+ long end = System.currentTimeMillis();
+ System.out.println("Reading data structures took " + (end - begin) + " ms)");
+ }
+
+ try {
+ writeDatabase(binaryData, lines);
+ return true;
+ } catch (IOException ioe) {
+ client.log(ioe, "Can't write typo cache file");
+ }
+
+ return false;
+ }
+
+ /** Use one of the {@link #get} factory methods instead */
+ private TypoLookup(
+ @NonNull LintClient client,
+ @NonNull File xmlFile,
+ @Nullable File binaryFile) {
+ if (binaryFile != null) {
+ readData(client, xmlFile, binaryFile);
+ }
+ }
+
+ private TypoLookup() {
+ }
+
+ private void readData(@NonNull LintClient client, @NonNull File xmlFile,
+ @NonNull File binaryFile) {
+ if (!binaryFile.exists()) {
+ client.log(null, "%1$s does not exist", binaryFile);
+ return;
+ }
+ long start = System.currentTimeMillis();
+ try {
+ MappedByteBuffer buffer = Files.map(binaryFile, MapMode.READ_ONLY);
+ assert buffer.order() == ByteOrder.BIG_ENDIAN;
+
+ // First skip the header
+ byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII);
+ buffer.rewind();
+ for (int offset = 0; offset < expectedHeader.length; offset++) {
+ if (expectedHeader[offset] != buffer.get()) {
+ client.log(null, "Incorrect file header: not an typo database cache " +
+ "file, or a corrupt cache file");
+ return;
+ }
+ }
+
+ // Read in the format number
+ if (buffer.get() != BINARY_FORMAT_VERSION) {
+ // Force regeneration of new binary data with up to date format
+ if (createCache(client, xmlFile, binaryFile)) {
+ readData(client, xmlFile, binaryFile); // Recurse
+ }
+
+ return;
+ }
+
+ mWordCount = buffer.getInt();
+
+ // Read in the word table indices;
+ int count = mWordCount;
+ int[] offsets = new int[count];
+
+ // Another idea: I can just store the DELTAS in the file (and add them up
+ // when reading back in) such that it takes just ONE byte instead of four!
+
+ for (int i = 0; i < count; i++) {
+ offsets[i] = buffer.getInt();
+ }
+
+ // No need to read in the rest -- we'll just keep the whole byte array in memory
+ // TODO: Make this code smarter/more efficient.
+ int size = buffer.limit();
+ byte[] b = new byte[size];
+ buffer.rewind();
+ buffer.get(b);
+ mData = b;
+ mIndices = offsets;
+
+ // TODO: We only need to keep the data portion here since we've initialized
+ // the offset array separately.
+ // TODO: Investigate (profile) accessing the byte buffer directly instead of
+ // accessing a byte array.
+ } catch (IOException e) {
+ client.log(e, null);
+ }
+ if (WRITE_STATS) {
+ long end = System.currentTimeMillis();
+ System.out.println("\nRead typo database in " + (end - start)
+ + " milliseconds.");
+ System.out.println("Size of data table: " + mData.length + " bytes ("
+ + Integer.toString(mData.length/1024) + "k)\n");
+ }
+ }
+
+ /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */
+ private static void writeDatabase(File file, List<String> lines) throws IOException {
+ /*
+ * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
+ * as ASCII characters. The purpose of the header is to identify what the file
+ * is for, for anyone attempting to open the file.
+ * 2. A file version number. If the binary file does not match the reader's expected
+ * version, it can ignore it (and regenerate the cache from XML).
+ */
+
+ // Drop comments etc
+ List<String> words = new ArrayList<String>(lines.size());
+ for (String line : lines) {
+ if (!line.isEmpty() && Character.isLetter(line.charAt(0))) {
+ int end = line.indexOf(WORD_SEPARATOR);
+ if (end == -1) {
+ end = line.trim().length();
+ }
+ String typo = line.substring(0, end).trim();
+ String replacements = line.substring(end + WORD_SEPARATOR.length()).trim();
+ if (replacements.isEmpty()) {
+ // We don't support empty replacements
+ continue;
+ }
+ String combined = typo + (char) 0 + replacements;
+
+ words.add(combined);
+ }
+ }
+
+ byte[][] wordArrays = new byte[words.size()][];
+ for (int i = 0, n = words.size(); i < n; i++) {
+ String word = words.get(i);
+ wordArrays[i] = word.getBytes(Charsets.UTF_8);
+ }
+ // Sort words, using our own comparator to ensure that it matches the
+ // binary search in getTypos()
+ Comparator<byte[]> comparator = new Comparator<byte[]>() {
+ @Override
+ public int compare(byte[] o1, byte[] o2) {
+ return TypoLookup.compare(o1, 0, (byte) 0, o2, 0, o2.length);
+ }
+ };
+ Arrays.sort(wordArrays, comparator);
+
+ byte[] headerBytes = FILE_HEADER.getBytes(Charsets.US_ASCII);
+ int entryCount = wordArrays.length;
+ int capacity = entryCount * BYTES_PER_ENTRY + headerBytes.length + 5;
+ ByteBuffer buffer = ByteBuffer.allocate(capacity);
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ // 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
+ // as ASCII characters. The purpose of the header is to identify what the file
+ // is for, for anyone attempting to open the file.
+ buffer.put(headerBytes);
+
+ // 2. A file version number. If the binary file does not match the reader's expected
+ // version, it can ignore it (and regenerate the cache from XML).
+ buffer.put((byte) BINARY_FORMAT_VERSION);
+
+ // 3. The number of words [1 int]
+ buffer.putInt(entryCount);
+
+ // 4. Word offset table (one integer per word, pointing to the byte offset in the
+ // file (relative to the beginning of the file) where each word begins.
+ // The words are always sorted alphabetically.
+ int wordOffsetTable = buffer.position();
+
+ // Reserve enough room for the offset table here: we will backfill it with pointers
+ // as we're writing out the data structures below
+ for (int i = 0, n = entryCount; i < n; i++) {
+ buffer.putInt(0);
+ }
+
+ int nextEntry = buffer.position();
+ int nextOffset = wordOffsetTable;
+
+ // 7. Word entry table. Each word entry consists of the word, followed by the byte 0
+ // as a terminator, followed by a comma separated list of suggestions (which
+ // may be empty), or a final 0.
+ for (int i = 0; i < entryCount; i++) {
+ byte[] word = wordArrays[i];
+ buffer.position(nextOffset);
+ buffer.putInt(nextEntry);
+ nextOffset = buffer.position();
+ buffer.position(nextEntry);
+
+ buffer.put(word); // already embeds 0 to separate typo from words
+ buffer.put((byte) 0);
+
+ nextEntry = buffer.position();
+ }
+
+ int size = buffer.position();
+ assert size <= buffer.limit();
+ buffer.mark();
+
+ if (WRITE_STATS) {
+ System.out.println("Wrote " + words.size() + " word entries");
+ System.out.print("Actual binary size: " + size + " bytes");
+ System.out.println(String.format(" (%.1fM)", size/(1024*1024.f)));
+
+ System.out.println("Allocated size: " + (entryCount * BYTES_PER_ENTRY) + " bytes");
+ System.out.println("Required bytes per entry: " + (size/ entryCount) + " bytes");
+ }
+
+ // Now dump this out as a file
+ // There's probably an API to do this more efficiently; TODO: Look into this.
+ byte[] b = new byte[size];
+ buffer.rewind();
+ buffer.get(b);
+ ByteSink sink = Files.asByteSink(file);
+ sink.write(b);
+ }
+
+ // For debugging only
+ private String dumpEntry(int offset) {
+ if (DEBUG_SEARCH) {
+ int end = offset;
+ while (mData[end] != 0) {
+ end++;
+ }
+ return new String(mData, offset, end - offset, Charsets.UTF_8);
+ } else {
+ return "<disabled>"; //$NON-NLS-1$
+ }
+ }
+
+ /** Comparison function: *only* used for ASCII strings */
+ @VisibleForTesting
+ static int compare(byte[] data, int offset, byte terminator, CharSequence s,
+ int begin, int end) {
+ int i = offset;
+ int j = begin;
+ for (; ; i++, j++) {
+ byte b = data[i];
+ if (b == ' ') {
+ // We've matched up to the space in a split-word typo, such as
+ // in German all zu=>allzu; here we've matched just past "all".
+ // Rather than terminating, attempt to continue in the buffer.
+ if (j == end) {
+ int max = s.length();
+ if (end < max && s.charAt(end) == ' ') {
+ // Find next word
+ for (; end < max; end++) {
+ char c = s.charAt(end);
+ if (!Character.isLetter(c)) {
+ if (c == ' ' && end == j) {
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (j == end) {
+ break;
+ }
+
+ if (b == '*') {
+ // Glob match (only supported at the end)
+ return 0;
+ }
+ char c = s.charAt(j);
+ byte cb = (byte) c;
+ int delta = b - cb;
+ if (delta != 0) {
+ cb = (byte) Character.toLowerCase(c);
+ if (b != cb) {
+ // Ensure that it has the right sign
+ b = (byte) Character.toLowerCase(b);
+ delta = b - cb;
+ if (delta != 0) {
+ return delta;
+ }
+ }
+ }
+ }
+
+ return data[i] - terminator;
+ }
+
+ /** Comparison function used for general UTF-8 encoded strings */
+ @VisibleForTesting
+ static int compare(byte[] data, int offset, byte terminator, byte[] s,
+ int begin, int end) {
+ int i = offset;
+ int j = begin;
+ for (; ; i++, j++) {
+ byte b = data[i];
+ if (b == ' ') {
+ // We've matched up to the space in a split-word typo, such as
+ // in German all zu=>allzu; here we've matched just past "all".
+ // Rather than terminating, attempt to continue in the buffer.
+ // We've matched up to the space in a split-word typo, such as
+ // in German all zu=>allzu; here we've matched just past "all".
+ // Rather than terminating, attempt to continue in the buffer.
+ if (j == end) {
+ int max = s.length;
+ if (end < max && s[end] == ' ') {
+ // Find next word
+ for (; end < max; end++) {
+ byte cb = s[end];
+ if (!isLetter(cb)) {
+ if (cb == ' ' && end == j) {
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (j == end) {
+ break;
+ }
+ if (b == '*') {
+ // Glob match (only supported at the end)
+ return 0;
+ }
+ byte cb = s[j];
+ int delta = b - cb;
+ if (delta != 0) {
+ cb = toLowerCase(cb);
+ b = toLowerCase(b);
+ delta = b - cb;
+ if (delta != 0) {
+ return delta;
+ }
+ }
+
+ if (b == terminator || cb == terminator) {
+ return delta;
+ }
+ }
+
+ return data[i] - terminator;
+ }
+
+ /**
+ * Look up whether this word is a typo, and if so, return the typo itself
+ * and one or more likely meanings
+ *
+ * @param text the string containing the word
+ * @param begin the index of the first character in the word
+ * @param end the index of the first character after the word. Note that the
+ * search may extend <b>beyond</b> this index, if for example the
+ * word matches a multi-word typo in the dictionary
+ * @return a list of the typo itself followed by the replacement strings if
+ * the word represents a typo, and null otherwise
+ */
+ @Nullable
+ public List<String> getTypos(@NonNull CharSequence text, int begin, int end) {
+ assert end <= text.length();
+
+ if (assertionsEnabled()) {
+ for (int i = begin; i < end; i++) {
+ char c = text.charAt(i);
+ if (c >= 128) {
+ assert false : "Call the UTF-8 version of this method instead";
+ return null;
+ }
+ }
+ }
+
+ int low = 0;
+ int high = mWordCount - 1;
+ while (low <= high) {
+ int middle = (low + high) >>> 1;
+ int offset = mIndices[middle];
+
+ if (DEBUG_SEARCH) {
+ System.out.println("Comparing string " + text +" with entry at " + offset
+ + ": " + dumpEntry(offset));
+ }
+
+ // Compare the word at the given index.
+ int compare = compare(mData, offset, (byte) 0, text, begin, end);
+
+ if (compare == 0) {
+ offset = mIndices[middle];
+
+ // Don't allow matching uncapitalized words, such as "enlish", when
+ // the dictionary word is capitalized, "Enlish".
+ if (mData[offset] != text.charAt(begin)
+ && Character.isLowerCase(text.charAt(begin))) {
+ return null;
+ }
+
+ // Make sure there is a case match; we only want to allow
+ // matching capitalized words to capitalized typos or uncapitalized typos
+ // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized
+ // typos (e.g. "enlish" to "Enlish").
+ String glob = null;
+ for (int i = begin; ; i++) {
+ byte b = mData[offset++];
+ if (b == 0) {
+ offset--;
+ break;
+ } else if (b == '*') {
+ int globEnd = i;
+ while (globEnd < text.length()
+ && Character.isLetter(text.charAt(globEnd))) {
+ globEnd++;
+ }
+ glob = text.subSequence(i, globEnd).toString();
+ break;
+ }
+ char c = text.charAt(i);
+ byte cb = (byte) c;
+ if (b != cb && i > begin) {
+ return null;
+ }
+ }
+
+ return computeSuggestions(mIndices[middle], offset, glob);
+ }
+
+ if (compare < 0) {
+ low = middle + 1;
+ } else if (compare > 0) {
+ high = middle - 1;
+ } else {
+ assert false; // compare == 0 already handled above
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Look up whether this word is a typo, and if so, return the typo itself
+ * and one or more likely meanings
+ *
+ * @param utf8Text the string containing the word, encoded as UTF-8
+ * @param begin the index of the first character in the word
+ * @param end the index of the first character after the word. Note that the
+ * search may extend <b>beyond</b> this index, if for example the
+ * word matches a multi-word typo in the dictionary
+ * @return a list of the typo itself followed by the replacement strings if
+ * the word represents a typo, and null otherwise
+ */
+ @Nullable
+ public List<String> getTypos(@NonNull byte[] utf8Text, int begin, int end) {
+ assert end <= utf8Text.length;
+
+ int low = 0;
+ int high = mWordCount - 1;
+ while (low <= high) {
+ int middle = (low + high) >>> 1;
+ int offset = mIndices[middle];
+
+ if (DEBUG_SEARCH) {
+ String s = new String(Arrays.copyOfRange(utf8Text, begin, end), Charsets.UTF_8);
+ System.out.println("Comparing string " + s +" with entry at " + offset
+ + ": " + dumpEntry(offset));
+ System.out.println(" middle=" + middle + ", low=" + low + ", high=" + high);
+ }
+
+ // Compare the word at the given index.
+ int compare = compare(mData, offset, (byte) 0, utf8Text, begin, end);
+
+ if (DEBUG_SEARCH) {
+ System.out.println(" signum=" + (int)Math.signum(compare) + ", delta=" + compare);
+ }
+
+ if (compare == 0) {
+ offset = mIndices[middle];
+
+ // Don't allow matching uncapitalized words, such as "enlish", when
+ // the dictionary word is capitalized, "Enlish".
+ if (mData[offset] != utf8Text[begin] && isUpperCase(mData[offset])) {
+ return null;
+ }
+
+ // Make sure there is a case match; we only want to allow
+ // matching capitalized words to capitalized typos or uncapitalized typos
+ // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized
+ // typos (e.g. "enlish" to "Enlish").
+ String glob = null;
+ for (int i = begin; ; i++) {
+ byte b = mData[offset++];
+ if (b == 0) {
+ offset--;
+ break;
+ } else if (b == '*') {
+ int globEnd = i;
+ while (globEnd < utf8Text.length && isLetter(utf8Text[globEnd])) {
+ globEnd++;
+ }
+ glob = new String(utf8Text, i, globEnd - i, Charsets.UTF_8);
+ break;
+ }
+ byte cb = utf8Text[i];
+ if (b != cb && i > begin) {
+ return null;
+ }
+ }
+
+ return computeSuggestions(mIndices[middle], offset, glob);
+ }
+
+ if (compare < 0) {
+ low = middle + 1;
+ } else if (compare > 0) {
+ high = middle - 1;
+ } else {
+ assert false; // compare == 0 already handled above
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ private List<String> computeSuggestions(int begin, int offset, String glob) {
+ String typo = new String(mData, begin, offset - begin, Charsets.UTF_8);
+
+ if (glob != null) {
+ typo = typo.replaceAll("\\*", glob); //$NON-NLS-1$
+ }
+
+ assert mData[offset] == 0;
+ offset++;
+ int replacementEnd = offset;
+ while (mData[replacementEnd] != 0) {
+ replacementEnd++;
+ }
+ String replacements = new String(mData, offset, replacementEnd - offset, Charsets.UTF_8);
+ List<String> words = new ArrayList<String>();
+ words.add(typo);
+
+ // The first entry should be the typo itself. We need to pass this back since due
+ // to multi-match words and globbing it could extend beyond the initial word range
+
+ for (String s : Splitter.on(',').omitEmptyStrings().trimResults().split(replacements)) {
+ if (glob != null) {
+ // Need to append the glob string to each result
+ words.add(s.replaceAll("\\*", glob)); //$NON-NLS-1$
+ } else {
+ words.add(s);
+ }
+ }
+
+ return words;
+ }
+
+ // "Character" handling for bytes. This assumes that the bytes correspond to Unicode
+ // characters in the ISO 8859-1 range, which is are encoded the same way in UTF-8.
+ // This obviously won't work to for example uppercase to lowercase conversions for
+ // multi byte characters, which means we simply won't catch typos if the dictionaries
+ // contain these. None of the currently included dictionaries do. However, it does
+ // help us properly deal with punctuation and spacing characters.
+
+ static boolean isUpperCase(byte b) {
+ return Character.isUpperCase((char) b);
+ }
+
+ static byte toLowerCase(byte b) {
+ return (byte) Character.toLowerCase((char) b);
+ }
+
+ static boolean isSpace(byte b) {
+ return Character.isWhitespace((char) b);
+ }
+
+ static boolean isLetter(byte b) {
+ // Assume that multi byte characters represent letters in other languages.
+ // Obviously, it could be unusual punctuation etc but letters are more likely
+ // in this context.
+ return Character.isLetter((char) b) || (b & 0x80) != 0;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetector.java
new file mode 100644
index 0000000..1287465
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetector.java
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PERMISSION;
+import static com.android.SdkConstants.CLASS_BROADCASTRECEIVER;
+import static com.android.SdkConstants.CLASS_CONTEXT;
+import static com.android.SdkConstants.CLASS_INTENT;
+import static com.android.SdkConstants.TAG_INTENT_FILTER;
+import static com.android.SdkConstants.TAG_RECEIVER;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Detector.XmlScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.VariableReference;
+
+public class UnsafeBroadcastReceiverDetector extends Detector
+ implements JavaScanner, XmlScanner {
+
+ /* Description of check implementations:
+ *
+ * UnsafeProtectedBroadcastReceiver check
+ *
+ * If a receiver is declared in the application manifest that has an intent-filter
+ * with an action string that matches a protected-broadcast action string,
+ * then if that receiver has an onReceive method, ensure that the method calls
+ * getAction at least once.
+ *
+ * With this check alone, false positives will occur if the onReceive method
+ * passes the received intent to another method that calls getAction.
+ * We look for any calls to aload_2 within the method bytecode, which could
+ * indicate loading the inputted intent onto the stack to use in a call
+ * to another method. In those cases, still report the issue, but
+ * report in the description that the finding may be a false positive.
+ * An alternative implementation option would be to omit reporting the issue
+ * at all when a call to aload_2 exists.
+ *
+ * UnprotectedSMSBroadcastReceiver check
+ *
+ * If a receiver is declared in AndroidManifest that has an intent-filter
+ * with action string SMS_DELIVER or SMS_RECEIVED, ensure that the
+ * receiver requires callers to have the BROADCAST_SMS permission.
+ *
+ * It is possible that the receiver may check the sender's permission by
+ * calling checkCallingPermission, which could cause a false positive.
+ * However, application developers should still be encouraged to declare
+ * the permission requirement in the manifest where it can be easily
+ * audited.
+ *
+ * Future work: Add checks for other action strings that should require
+ * particular permissions be checked, such as
+ * android.provider.Telephony.WAP_PUSH_DELIVER
+ *
+ * Note that neither of these checks address receivers dynamically created at runtime,
+ * only ones that are declared in the application manifest.
+ */
+
+ public static final Issue ACTION_STRING = Issue.create(
+ "UnsafeProtectedBroadcastReceiver",
+ "Unsafe Protected BroadcastReceiver",
+ "BroadcastReceivers that declare an intent-filter for a protected-broadcast action " +
+ "string must check that the received intent's action string matches the expected " +
+ "value, otherwise it is possible for malicious actors to spoof intents.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ new Implementation(UnsafeBroadcastReceiverDetector.class,
+ EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE)));
+
+ public static final Issue BROADCAST_SMS = Issue.create(
+ "UnprotectedSMSBroadcastReceiver",
+ "Unprotected SMS BroadcastReceiver",
+ "BroadcastReceivers that declare an intent-filter for SMS_DELIVER or " +
+ "SMS_RECEIVED must ensure that the caller has the BROADCAST_SMS permission, " +
+ "otherwise it is possible for malicious actors to spoof intents.",
+ Category.SECURITY,
+ 6,
+ Severity.WARNING,
+ new Implementation(UnsafeBroadcastReceiverDetector.class,
+ Scope.MANIFEST_SCOPE));
+
+ /* List of protected broadcast strings. This list must be sorted alphabetically.
+ * Protected broadcast strings are defined by <protected-broadcast> entries in the
+ * manifest of system-level components or applications.
+ * The below list is copied from frameworks/base/core/res/AndroidManifest.xml
+ * and packages/services/Telephony/AndroidManifest.xml .
+ * It should be periodically updated. This list will likely not be complete, since
+ * protected-broadcast entries can be defined elsewhere, but should address
+ * most situations.
+ */
+ @VisibleForTesting
+ static final String[] PROTECTED_BROADCASTS = new String[] {
+ "android.app.action.DEVICE_OWNER_CHANGED",
+ "android.app.action.ENTER_CAR_MODE",
+ "android.app.action.ENTER_DESK_MODE",
+ "android.app.action.EXIT_CAR_MODE",
+ "android.app.action.EXIT_DESK_MODE",
+ "android.app.action.NEXT_ALARM_CLOCK_CHANGED",
+ "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED",
+ "android.appwidget.action.APPWIDGET_DELETED",
+ "android.appwidget.action.APPWIDGET_DISABLED",
+ "android.appwidget.action.APPWIDGET_ENABLED",
+ "android.appwidget.action.APPWIDGET_HOST_RESTORED",
+ "android.appwidget.action.APPWIDGET_RESTORED",
+ "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS",
+ "android.backup.intent.CLEAR",
+ "android.backup.intent.INIT",
+ "android.backup.intent.RUN",
+ "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED",
+ "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED",
+ "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED",
+ "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.adapter.action.DISCOVERY_FINISHED",
+ "android.bluetooth.adapter.action.DISCOVERY_STARTED",
+ "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED",
+ "android.bluetooth.adapter.action.SCAN_MODE_CHANGED",
+ "android.bluetooth.adapter.action.STATE_CHANGED",
+ "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.device.action.ACL_CONNECTED",
+ "android.bluetooth.device.action.ACL_DISCONNECTED",
+ "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED",
+ "android.bluetooth.device.action.ALIAS_CHANGED",
+ "android.bluetooth.device.action.BOND_STATE_CHANGED",
+ "android.bluetooth.device.action.CLASS_CHANGED",
+ "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL",
+ "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY",
+ "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST",
+ "android.bluetooth.device.action.DISAPPEARED",
+ "android.bluetooth.device.action.FOUND",
+ "android.bluetooth.device.action.MAS_INSTANCE",
+ "android.bluetooth.device.action.NAME_CHANGED",
+ "android.bluetooth.device.action.NAME_FAILED",
+ "android.bluetooth.device.action.PAIRING_CANCEL",
+ "android.bluetooth.device.action.PAIRING_REQUEST",
+ "android.bluetooth.device.action.UUID",
+ "android.bluetooth.devicepicker.action.DEVICE_SELECTED",
+ "android.bluetooth.devicepicker.action.LAUNCH",
+ "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT",
+ "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED",
+ "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED",
+ "android.bluetooth.headsetclient.profile.action.AG_EVENT",
+ "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED",
+ "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.headsetclient.profile.action.LAST_VTAG",
+ "android.bluetooth.headsetclient.profile.action.RESULT",
+ "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED",
+ "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS",
+ "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED",
+ "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED",
+ "android.btopp.intent.action.CONFIRM",
+ "android.btopp.intent.action.HIDE",
+ "android.btopp.intent.action.HIDE_COMPLETE",
+ "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION",
+ "android.btopp.intent.action.LIST",
+ "android.btopp.intent.action.OPEN",
+ "android.btopp.intent.action.OPEN_INBOUND",
+ "android.btopp.intent.action.OPEN_OUTBOUND",
+ "android.btopp.intent.action.RETRY",
+ "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT",
+ "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED",
+ "android.hardware.usb.action.USB_ACCESSORY_ATTACHED",
+ "android.hardware.usb.action.USB_ACCESSORY_DETACHED",
+ "android.hardware.usb.action.USB_DEVICE_ATTACHED",
+ "android.hardware.usb.action.USB_DEVICE_DETACHED",
+ "android.hardware.usb.action.USB_PORT_CHANGED",
+ "android.hardware.usb.action.USB_STATE",
+ "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED",
+ "android.intent.action.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED",
+ "android.intent.action.ACTION_DEFAULT_SUBSCRIPTION_CHANGED",
+ "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED",
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_END",
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_START",
+ "android.intent.action.ACTION_POWER_CONNECTED",
+ "android.intent.action.ACTION_POWER_DISCONNECTED",
+ "android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE",
+ "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED",
+ "android.intent.action.ACTION_SHUTDOWN",
+ "android.intent.action.ACTION_SUBINFO_CONTENT_CHANGE",
+ "android.intent.action.ACTION_SUBINFO_RECORD_UPDATED",
+ "android.intent.action.ADVANCED_SETTINGS",
+ "android.intent.action.AIRPLANE_MODE",
+ "android.intent.action.ANY_DATA_STATE",
+ "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED",
+ "android.intent.action.BATTERY_CHANGED",
+ "android.intent.action.BATTERY_LOW",
+ "android.intent.action.BATTERY_OKAY",
+ "android.intent.action.BOOT_COMPLETED",
+ "android.intent.action.BUGREPORT_FINISHED",
+ "android.intent.action.CHARGING",
+ "android.intent.action.CLEAR_DNS_CACHE",
+ "android.intent.action.CONFIGURATION_CHANGED",
+ "android.intent.action.DATE_CHANGED",
+ "android.intent.action.DEVICE_STORAGE_FULL",
+ "android.intent.action.DEVICE_STORAGE_LOW",
+ "android.intent.action.DEVICE_STORAGE_NOT_FULL",
+ "android.intent.action.DEVICE_STORAGE_OK",
+ "android.intent.action.DISCHARGING",
+ "android.intent.action.DOCK_EVENT",
+ "android.intent.action.DREAMING_STARTED",
+ "android.intent.action.DREAMING_STOPPED",
+ "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE",
+ "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE",
+ "android.intent.action.HDMI_PLUGGED",
+ "android.intent.action.HEADSET_PLUG",
+ "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION",
+ "android.intent.action.LOCALE_CHANGED",
+ "android.intent.action.MASTER_CLEAR_NOTIFICATION",
+ "android.intent.action.MEDIA_BAD_REMOVAL",
+ "android.intent.action.MEDIA_CHECKING",
+ "android.intent.action.MEDIA_EJECT",
+ "android.intent.action.MEDIA_MOUNTED",
+ "android.intent.action.MEDIA_NOFS",
+ "android.intent.action.MEDIA_REMOVED",
+ "android.intent.action.MEDIA_SHARED",
+ "android.intent.action.MEDIA_UNMOUNTABLE",
+ "android.intent.action.MEDIA_UNMOUNTED",
+ "android.intent.action.MEDIA_UNSHARED",
+ "android.intent.action.MY_PACKAGE_REPLACED",
+ "android.intent.action.NEW_OUTGOING_CALL",
+ "android.intent.action.PACKAGE_ADDED",
+ "android.intent.action.PACKAGE_CHANGED",
+ "android.intent.action.PACKAGE_DATA_CLEARED",
+ "android.intent.action.PACKAGE_FIRST_LAUNCH",
+ "android.intent.action.PACKAGE_FULLY_REMOVED",
+ "android.intent.action.PACKAGE_INSTALL",
+ "android.intent.action.PACKAGE_NEEDS_VERIFICATION",
+ "android.intent.action.PACKAGE_REMOVED",
+ "android.intent.action.PACKAGE_REPLACED",
+ "android.intent.action.PACKAGE_RESTARTED",
+ "android.intent.action.PACKAGE_VERIFIED",
+ "android.intent.action.PERMISSION_RESPONSE_RECEIVED",
+ "android.intent.action.PHONE_STATE",
+ "android.intent.action.PROXY_CHANGE",
+ "android.intent.action.QUERY_PACKAGE_RESTART",
+ "android.intent.action.REBOOT",
+ "android.intent.action.REQUEST_PERMISSION",
+ "android.intent.action.SCREEN_OFF",
+ "android.intent.action.SCREEN_ON",
+ "android.intent.action.SUB_DEFAULT_CHANGED",
+ "android.intent.action.THERMAL_EVENT",
+ "android.intent.action.TIMEZONE_CHANGED",
+ "android.intent.action.TIME_SET",
+ "android.intent.action.TIME_TICK",
+ "android.intent.action.UID_REMOVED",
+ "android.intent.action.USER_ADDED",
+ "android.intent.action.USER_BACKGROUND",
+ "android.intent.action.USER_FOREGROUND",
+ "android.intent.action.USER_PRESENT",
+ "android.intent.action.USER_REMOVED",
+ "android.intent.action.USER_STARTED",
+ "android.intent.action.USER_STARTING",
+ "android.intent.action.USER_STOPPED",
+ "android.intent.action.USER_STOPPING",
+ "android.intent.action.USER_SWITCHED",
+ "android.internal.policy.action.BURN_IN_PROTECTION",
+ "android.location.GPS_ENABLED_CHANGE",
+ "android.location.GPS_FIX_CHANGE",
+ "android.location.MODE_CHANGED",
+ "android.location.PROVIDERS_CHANGED",
+ "android.media.ACTION_SCO_AUDIO_STATE_UPDATED",
+ "android.media.AUDIO_BECOMING_NOISY",
+ "android.media.MASTER_MUTE_CHANGED_ACTION",
+ "android.media.MASTER_VOLUME_CHANGED_ACTION",
+ "android.media.RINGER_MODE_CHANGED",
+ "android.media.SCO_AUDIO_STATE_CHANGED",
+ "android.media.VIBRATE_SETTING_CHANGED",
+ "android.media.VOLUME_CHANGED_ACTION",
+ "android.media.action.HDMI_AUDIO_PLUG",
+ "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED",
+ "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED",
+ "android.net.conn.CAPTIVE_PORTAL",
+ "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
+ "android.net.conn.CONNECTIVITY_CHANGE",
+ "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE",
+ "android.net.conn.DATA_ACTIVITY_CHANGE",
+ "android.net.conn.INET_CONDITION_ACTION",
+ "android.net.conn.NETWORK_CONDITIONS_MEASURED",
+ "android.net.conn.TETHER_STATE_CHANGED",
+ "android.net.nsd.STATE_CHANGED",
+ "android.net.proxy.PAC_REFRESH",
+ "android.net.scoring.SCORER_CHANGED",
+ "android.net.scoring.SCORE_NETWORKS",
+ "android.net.wifi.CONFIGURED_NETWORKS_CHANGE",
+ "android.net.wifi.LINK_CONFIGURATION_CHANGED",
+ "android.net.wifi.RSSI_CHANGED",
+ "android.net.wifi.SCAN_RESULTS",
+ "android.net.wifi.STATE_CHANGE",
+ "android.net.wifi.WIFI_AP_STATE_CHANGED",
+ "android.net.wifi.WIFI_CREDENTIAL_CHANGED",
+ "android.net.wifi.WIFI_SCAN_AVAILABLE",
+ "android.net.wifi.WIFI_STATE_CHANGED",
+ "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
+ "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE",
+ "android.net.wifi.p2p.PEERS_CHANGED",
+ "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED",
+ "android.net.wifi.p2p.STATE_CHANGED",
+ "android.net.wifi.p2p.THIS_DEVICE_CHANGED",
+ "android.net.wifi.supplicant.CONNECTION_CHANGE",
+ "android.net.wifi.supplicant.STATE_CHANGE",
+ "android.nfc.action.LLCP_LINK_STATE_CHANGED",
+ "android.nfc.action.TRANSACTION_DETECTED",
+ "android.nfc.handover.intent.action.HANDOVER_STARTED",
+ "android.nfc.handover.intent.action.TRANSFER_DONE",
+ "android.nfc.handover.intent.action.TRANSFER_PROGRESS",
+ "android.os.UpdateLock.UPDATE_LOCK_CHANGED",
+ "android.os.action.DEVICE_IDLE_MODE_CHANGED",
+ "android.os.action.POWER_SAVE_MODE_CHANGED",
+ "android.os.action.POWER_SAVE_MODE_CHANGING",
+ "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED",
+ "android.os.action.POWER_SAVE_WHITELIST_CHANGED",
+ "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED",
+ "android.os.action.SETTING_RESTORED",
+ "android.telecom.action.DEFAULT_DIALER_CHANGED",
+ "com.android.bluetooth.pbap.authcancelled",
+ "com.android.bluetooth.pbap.authchall",
+ "com.android.bluetooth.pbap.authresponse",
+ "com.android.bluetooth.pbap.userconfirmtimeout",
+ "com.android.nfc_extras.action.AID_SELECTED",
+ "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED",
+ "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED",
+ "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP",
+ "com.android.server.WifiManager.action.START_PNO",
+ "com.android.server.WifiManager.action.START_SCAN",
+ "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION",
+ };
+
+ private static final Set<String> PROTECTED_BROADCAST_SET =
+ Sets.newHashSet(PROTECTED_BROADCASTS);
+
+ private Set<String> mReceiversWithProtectedBroadcastIntentFilter = new HashSet<String>();
+
+ public UnsafeBroadcastReceiverDetector() {
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(TAG_RECEIVER);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context,
+ @NonNull Element element) {
+ String tag = element.getTagName();
+ if (TAG_RECEIVER.equals(tag)) {
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
+ // If no permission attribute, then if any exists at the application
+ // element, it applies
+ if (permission == null || permission.isEmpty()) {
+ Element parent = (Element) element.getParentNode();
+ permission = parent.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
+ }
+ List<Element> children = LintUtils.getChildren(element);
+ for (Element child : children) {
+ String tagName = child.getTagName();
+ if (TAG_INTENT_FILTER.equals(tagName)) {
+ if (name.startsWith(".")) {
+ name = context.getProject().getPackage() + name;
+ }
+ name = name.replace('$', '.');
+ List<Element> children2 = LintUtils.getChildren(child);
+ for (Element child2 : children2) {
+ if ("action".equals(child2.getTagName())) {
+ String actionName = child2.getAttributeNS(
+ ANDROID_URI, ATTR_NAME);
+ if (("android.provider.Telephony.SMS_DELIVER".equals(actionName) ||
+ "android.provider.Telephony.SMS_RECEIVED".
+ equals(actionName)) &&
+ !"android.permission.BROADCAST_SMS".equals(permission)) {
+ context.report(
+ BROADCAST_SMS,
+ element,
+ context.getLocation(element),
+ "BroadcastReceivers that declare an intent-filter for " +
+ "SMS_DELIVER or SMS_RECEIVED must ensure that the " +
+ "caller has the BROADCAST_SMS permission, otherwise it " +
+ "is possible for malicious actors to spoof intents.");
+ }
+ else if (PROTECTED_BROADCAST_SET.contains(actionName)) {
+ mReceiversWithProtectedBroadcastIntentFilter.add(name);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> applicableSuperClasses() {
+ return mReceiversWithProtectedBroadcastIntentFilter.isEmpty()
+ ? null : Collections.singletonList(CLASS_BROADCASTRECEIVER);
+ }
+
+ @Override
+ public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration node,
+ @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
+ if (node == null) { // anonymous classes can't be the ones referenced in the manifest
+ return;
+ }
+ String name = cls.getName();
+ if (!mReceiversWithProtectedBroadcastIntentFilter.contains(name)) {
+ return;
+ }
+ for (Node member : node.astBody().astMembers()) {
+ if (member instanceof MethodDeclaration) {
+ MethodDeclaration declaration = (MethodDeclaration)member;
+ if ("onReceive".equals(declaration.astMethodName().astValue())
+ && declaration.astParameters().size() == 2) {
+ ResolvedNode resolved = context.resolve(declaration);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getArgumentCount() == 2
+ && method.getArgumentType(0).matchesName(CLASS_CONTEXT)
+ && method.getArgumentType(1).matchesName(CLASS_INTENT)) {
+ checkOnReceive(context, declaration, method);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void checkOnReceive(@NonNull JavaContext context,
+ @NonNull MethodDeclaration declaration,
+ @NonNull ResolvedMethod method) {
+ // Search for call to getAction but also search for references to aload_2,
+ // which indicates that the method is making use of the received intent in
+ // some way.
+ //
+ // If the onReceive method doesn't call getAction but does make use of
+ // the received intent, it is possible that it is passing it to another
+ // method that might be performing the getAction check, so we warn that the
+ // finding may be a false positive. (An alternative option would be to not
+ // report a finding at all in this case.)
+ assert method.getArgumentCount() == 2;
+ ResolvedVariable parameter = null;
+ if (declaration.astParameters().size() == 2) {
+ ResolvedNode resolved = context.resolve(declaration.astParameters().last());
+ if (resolved instanceof ResolvedVariable) {
+ parameter = (ResolvedVariable) resolved;
+ }
+ }
+ OnReceiveVisitor visitor = new OnReceiveVisitor(context, parameter);
+ declaration.accept(visitor);
+ if (!visitor.getCallsGetAction()) {
+ String report;
+ if (!visitor.getUsesIntent()) {
+ report = "This broadcast receiver declares an intent-filter for a protected " +
+ "broadcast action string, which can only be sent by the system, " +
+ "not third-party applications. However, the receiver's onReceive " +
+ "method does not appear to call getAction to ensure that the " +
+ "received Intent's action string matches the expected value, " +
+ "potentially making it possible for another actor to send a " +
+ "spoofed intent with no action string or a different action " +
+ "string and cause undesired behavior.";
+ } else {
+ // An alternative implementation option is to not report a finding at all in
+ // this case, if we are worried about false positives causing confusion or
+ // resulting in developers ignoring other lint warnings.
+ report = "This broadcast receiver declares an intent-filter for a protected " +
+ "broadcast action string, which can only be sent by the system, " +
+ "not third-party applications. However, the receiver's onReceive " +
+ "method does not appear to call getAction to ensure that the " +
+ "received Intent's action string matches the expected value, " +
+ "potentially making it possible for another actor to send a " +
+ "spoofed intent with no action string or a different action " +
+ "string and cause undesired behavior. In this case, it is " +
+ "possible that the onReceive method passed the received Intent " +
+ "to another method that checked the action string. If so, this " +
+ "finding can safely be ignored.";
+ }
+ Location location = context.getNameLocation(declaration);
+ context.report(ACTION_STRING, declaration, location, report);
+ }
+ }
+
+ private static class OnReceiveVisitor extends ForwardingAstVisitor {
+ @NonNull private final JavaContext mContext;
+ @Nullable private final ResolvedVariable mParameter;
+ private boolean mCallsGetAction;
+ private boolean mUsesIntent;
+
+ public OnReceiveVisitor(@NonNull JavaContext context, @Nullable ResolvedVariable parameter) {
+ mContext = context;
+ mParameter = parameter;
+ }
+
+ public boolean getCallsGetAction() {
+ return mCallsGetAction;
+ }
+
+ public boolean getUsesIntent() {
+ return mUsesIntent;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(@NonNull MethodInvocation node) {
+ if (!mCallsGetAction) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getName().equals("getAction") &&
+ method.getContainingClass().isSubclassOf(CLASS_INTENT, false)) {
+ mCallsGetAction = true;
+ }
+ }
+ }
+ return super.visitMethodInvocation(node);
+ }
+
+ @Override
+ public boolean visitVariableReference(@NonNull VariableReference node) {
+ if (!mUsesIntent && mParameter != null) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (mParameter.equals(resolved)) {
+ mUsesIntent = true;
+ }
+ }
+
+ return super.visitVariableReference(node);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeNativeCodeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeNativeCodeDetector.java
new file mode 100644
index 0000000..6033693
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnsafeNativeCodeDetector.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.DOT_NATIVE_LIBS;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.MethodInvocation;
+
+public class UnsafeNativeCodeDetector extends Detector
+ implements Detector.JavaScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ UnsafeNativeCodeDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ public static final Issue LOAD = Issue.create(
+ "UnsafeDynamicallyLoadedCode",
+ "`load` used to dynamically load code",
+ "Dynamically loading code from locations other than the application's library " +
+ "directory or the Android platform's built-in library directories is dangerous, " +
+ "as there is an increased risk that the code could have been tampered with. " +
+ "Applications should use `loadLibrary` when possible, which provides increased " +
+ "assurance that libraries are loaded from one of these safer locations. " +
+ "Application developers should use the features of their development " +
+ "environment to place application native libraries into the lib directory " +
+ "of their compiled APKs.",
+ Category.SECURITY,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ public static final Issue UNSAFE_NATIVE_CODE_LOCATION = Issue.create(
+ "UnsafeNativeCodeLocation", //$NON-NLS-1$
+ "Native code outside library directory",
+ "In general, application native code should only be placed in the application's " +
+ "library directory, not in other locations such as the res or assets directories. " +
+ "Placing the code in the library directory provides increased assurance that the " +
+ "code will not be tampered with after application installation. Application " +
+ "developers should use the features of their development environment to place " +
+ "application native libraries into the lib directory of their compiled " +
+ "APKs. Embedding non-shared library native executables into applications should " +
+ "be avoided when possible.",
+ Category.SECURITY,
+ 4,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ private static final String RUNTIME_CLASS = "java.lang.Runtime"; //$NON-NLS-1$
+ private static final String SYSTEM_CLASS = "java.lang.System"; //$NON-NLS-1$
+
+ private static final byte[] ELF_MAGIC_VALUE = { (byte) 0x7F, (byte) 0x45, (byte) 0x4C, (byte) 0x46 };
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.NORMAL;
+ }
+
+ // ---- Implements Detector.JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ // Identify calls to Runtime.load() and System.load()
+ return Collections.singletonList("load");
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ String methodName = node.astName().astValue();
+ ResolvedClass resolvedClass = ((ResolvedMethod) resolved).getContainingClass();
+ if ((resolvedClass.isSubclassOf(RUNTIME_CLASS, false)) ||
+ (resolvedClass.matches(SYSTEM_CLASS))) {
+ // Report calls to Runtime.load() and System.load()
+ if ("load".equals(methodName)) {
+ context.report(LOAD, node, context.getLocation(node),
+ "Dynamically loading code using `load` is risky, please use " +
+ "`loadLibrary` instead when possible");
+ }
+ }
+ }
+ }
+
+ // ---- Look for code in resource and asset directories ----
+
+ @Override
+ public void afterCheckLibraryProject(@NonNull Context context) {
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ checkResourceFolders(context, context.getProject());
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ checkResourceFolders(context, context.getProject());
+ }
+
+ private static boolean isNativeCode(File file) {
+ if (!file.isFile()) {
+ return false;
+ }
+
+ try {
+ FileInputStream fis = new FileInputStream(file);
+ try {
+ byte[] bytes = new byte[4];
+ int length = fis.read(bytes);
+ return (length == 4) && (Arrays.equals(ELF_MAGIC_VALUE, bytes));
+ } finally {
+ fis.close();
+ }
+ } catch (IOException ex) {
+ return false;
+ }
+ }
+
+ private static void checkResourceFolders(Context context, @NonNull Project project) {
+ if (!context.getScope().contains(Scope.RESOURCE_FOLDER)) {
+ // Don't do work when doing in-editor analysis of Java files
+ return;
+ }
+ List<File> resourceFolders = project.getResourceFolders();
+ for (File res : resourceFolders) {
+ File[] folders = res.listFiles();
+ if (folders != null) {
+ for (File typeFolder : folders) {
+ if (typeFolder.getName().startsWith(SdkConstants.FD_RES_RAW)) {
+ File[] rawFiles = typeFolder.listFiles();
+ if (rawFiles != null) {
+ for (File rawFile : rawFiles) {
+ checkFile(context, rawFile);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ List<File> assetFolders = project.getAssetFolders();
+ for (File assetFolder : assetFolders) {
+ File[] assets = assetFolder.listFiles();
+ if (assets != null) {
+ for (File asset : assets) {
+ checkFile(context, asset);
+ }
+ }
+ }
+ }
+
+ private static void checkFile(@NonNull Context context, @NonNull File file) {
+ if (isNativeCode(file)) {
+ if (LintUtils.endsWith(file.getPath(), DOT_NATIVE_LIBS)) {
+ context.report(UNSAFE_NATIVE_CODE_LOCATION, Location.create(file),
+ "Shared libraries should not be placed in the res or assets " +
+ "directories. Please use the features of your development " +
+ "environment to place shared libraries in the lib directory of " +
+ "the compiled APK.");
+ } else {
+ context.report(UNSAFE_NATIVE_CODE_LOCATION, Location.create(file),
+ "Embedding non-shared library native executables into applications " +
+ "should be avoided when possible, as there is an increased risk that " +
+ "the executables could be tampered with after installation. Instead, " +
+ "native code should be placed in a shared library, and the features of " +
+ "the development environment should be used to place the shared library " +
+ "in the lib directory of the compiled APK.");
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
new file mode 100644
index 0000000..e5b37a1
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
@@ -0,0 +1,711 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.R_CLASS;
+import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.Variant;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.checks.ResourceUsageModel.Resource;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector.BinaryResourceScanner;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Detector.XmlScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceContext;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.XmlUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.CompilationUnit;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Identifier;
+import lombok.ast.ImportDeclaration;
+import lombok.ast.VariableReference;
+
+/**
+ * Finds unused resources.
+ */
+public class UnusedResourceDetector extends ResourceXmlDetector implements JavaScanner,
+ BinaryResourceScanner, XmlScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ UnusedResourceDetector.class,
+ EnumSet.of(Scope.MANIFEST, Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES,
+ Scope.BINARY_RESOURCE_FILE, Scope.TEST_SOURCES));
+
+ /** Unused resources (other than ids). */
+ public static final Issue ISSUE = Issue.create(
+ "UnusedResources", //$NON-NLS-1$
+ "Unused resources",
+ "Unused resources make applications larger and slow down builds.",
+ Category.PERFORMANCE,
+ 3,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ /** Unused id's */
+ public static final Issue ISSUE_IDS = Issue.create(
+ "UnusedIds", //$NON-NLS-1$
+ "Unused id",
+ "This resource id definition appears not to be needed since it is not referenced " +
+ "from anywhere. Having id definitions, even if unused, is not necessarily a bad " +
+ "idea since they make working on layouts and menus easier, so there is not a " +
+ "strong reason to delete these.",
+ Category.PERFORMANCE,
+ 1,
+ Severity.WARNING,
+ IMPLEMENTATION)
+ .setEnabledByDefault(false);
+
+ private final UnusedResourceDetectorUsageModel mModel =
+ new UnusedResourceDetectorUsageModel();
+
+ /** Whether the resource detector will look for inactive resources (e.g. resource and code references
+ * in source sets that are not the primary/active variant) */
+ public static boolean sIncludeInactiveReferences = true;
+
+ /**
+ * Constructs a new {@link UnusedResourceDetector}
+ */
+ public UnusedResourceDetector() {
+ }
+
+ private void addDynamicResources(
+ @NonNull Context context) {
+ Project project = context.getProject();
+ AndroidProject model = project.getGradleProjectModel();
+ if (model != null) {
+ Variant selectedVariant = project.getCurrentVariant();
+ if (selectedVariant != null) {
+ for (BuildTypeContainer container : model.getBuildTypes()) {
+ if (selectedVariant.getBuildType().equals(container.getBuildType().getName())) {
+ addDynamicResources(project, container.getBuildType().getResValues());
+ }
+ }
+ }
+ ProductFlavor flavor = model.getDefaultConfig().getProductFlavor();
+ addDynamicResources(project, flavor.getResValues());
+ }
+ }
+
+ private void addDynamicResources(@NonNull Project project,
+ @NonNull Map<String, ClassField> resValues) {
+ Set<String> keys = resValues.keySet();
+ if (!keys.isEmpty()) {
+ Location location = LintUtils.guessGradleLocation(project);
+ for (String name : keys) {
+ ClassField field = resValues.get(name);
+ ResourceType type = ResourceType.getEnum(field.getType());
+ if (type == null) {
+ // Highly unlikely. This would happen if in the future we add
+ // some new ResourceType, that the Gradle plugin (and the user's
+ // Gradle file is creating) and it's an older version of Studio which
+ // doesn't yet have this ResourceType in its enum.
+ continue;
+ }
+ Resource resource = mModel.declareResource(type, name, null);
+ resource.recordLocation(location);
+ }
+ }
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (context.getPhase() == 1) {
+ Project project = context.getProject();
+
+ // Look for source sets that aren't part of the active variant;
+ // we need to make sure we find references in those source sets as well
+ // such that we don't incorrectly remove resources that are
+ // used by some other source set.
+ if (sIncludeInactiveReferences && project.isGradleProject() && !project.isLibrary()) {
+ AndroidProject model = project.getGradleProjectModel();
+ Variant variant = project.getCurrentVariant();
+ if (model != null && variant != null) {
+ addInactiveReferences(model, variant);
+ }
+ }
+
+ addDynamicResources(context);
+ mModel.processToolsAttributes();
+
+ List<Resource> unusedResources = mModel.findUnused();
+ Set<Resource> unused = Sets.newHashSetWithExpectedSize(unusedResources.size());
+ for (Resource resource : unusedResources) {
+ if (resource.isDeclared()
+ && !resource.isPublic()
+ && resource.type != ResourceType.PUBLIC) {
+ unused.add(resource);
+ }
+ }
+
+ // Remove id's if the user has disabled reporting issue ids
+ if (!unused.isEmpty() && !context.isEnabled(ISSUE_IDS)) {
+ // Remove all R.id references
+ List<Resource> ids = Lists.newArrayList();
+ for (Resource resource : unused) {
+ if (resource.type == ResourceType.ID) {
+ ids.add(resource);
+ }
+ }
+ unused.removeAll(ids);
+ }
+
+ if (!unused.isEmpty() && !context.getDriver().hasParserErrors()) {
+ mModel.unused = unused;
+
+ // Request another pass, and in the second pass we'll gather location
+ // information for all declaration locations we've found
+ context.requestRepeat(this, Scope.ALL_RESOURCES_SCOPE);
+ }
+ } else {
+ assert context.getPhase() == 2;
+
+ // Report any resources that we (for some reason) could not find a declaration
+ // location for
+ Collection<Resource> unused = mModel.unused;
+ if (!unused.isEmpty()) {
+ // Final pass: we may have marked a few resource declarations with
+ // tools:ignore; we don't check that on every single element, only those
+ // first thought to be unused. We don't just remove the elements explicitly
+ // marked as unused, we revisit everything transitively such that resources
+ // referenced from the ignored/kept resource are also kept.
+ unused = mModel.findUnused(Lists.newArrayList(unused));
+ if (unused.isEmpty()) {
+ return;
+ }
+
+ // Fill in locations for files that we didn't encounter in other ways
+ for (Resource resource : unused) {
+ Location location = resource.locations;
+ //noinspection VariableNotUsedInsideIf
+ if (location != null) {
+ continue;
+ }
+
+ // Try to figure out the file if it's a file based resource (such as R.layout) --
+ // in that case we can figure out the filename since it has a simple mapping
+ // from the resource name (though the presence of qualifiers like -land etc
+ // makes it a little tricky if there's no base file provided)
+ ResourceType type = resource.type;
+ if (type != null && LintUtils.isFileBasedResourceType(type)) {
+ String name = resource.name;
+
+ List<File> folders = Lists.newArrayList();
+ List<File> resourceFolders = context.getProject().getResourceFolders();
+ for (File res : resourceFolders) {
+ File[] f = res.listFiles();
+ if (f != null) {
+ folders.addAll(Arrays.asList(f));
+ }
+ }
+ if (!folders.isEmpty()) {
+ // Process folders in alphabetical order such that we process
+ // based folders first: we want the locations in base folder
+ // order
+ Collections.sort(folders, new Comparator<File>() {
+ @Override
+ public int compare(File file1, File file2) {
+ return file1.getName().compareTo(file2.getName());
+ }
+ });
+ for (File folder : folders) {
+ if (folder.getName().startsWith(type.getName())) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ Arrays.sort(files);
+ for (File file : files) {
+ String fileName = file.getName();
+ if (fileName.startsWith(name)
+ && fileName.startsWith(".", //$NON-NLS-1$
+ name.length())) {
+ resource.recordLocation(Location.create(file));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ List<Resource> sorted = Lists.newArrayList(unused);
+ Collections.sort(sorted);
+
+ Boolean skippedLibraries = null;
+
+ for (Resource resource : sorted) {
+ Location location = resource.locations;
+ if (location != null) {
+ // We were prepending locations, but we want to prefer the base folders
+ location = Location.reverse(location);
+ }
+
+ if (location == null) {
+ if (skippedLibraries == null) {
+ skippedLibraries = false;
+ for (Project project : context.getDriver().getProjects()) {
+ if (!project.getReportIssues()) {
+ skippedLibraries = true;
+ break;
+ }
+ }
+ }
+ if (skippedLibraries) {
+ // Skip this resource if we don't have a location, and one or
+ // more library projects were skipped; the resource was very
+ // probably defined in that library project and only encountered
+ // in the main project's java R file
+ continue;
+ }
+ }
+
+ // Keep in sync with getUnusedResource() below
+ String message = String.format("The resource `%1$s` appears to be unused",
+ resource.getField());
+ context.report(getIssue(resource), location, message);
+ }
+ }
+ }
+ }
+
+ /** Returns source providers that are <b>not</b> part of the given variant */
+ @NonNull
+ private static List<SourceProvider> getInactiveSourceProviders(
+ @NonNull AndroidProject project,
+ @NonNull Variant variant) {
+ Collection<Variant> variants = project.getVariants();
+ List<SourceProvider> providers = Lists.newArrayList();
+
+ // Add other flavors
+ Collection<ProductFlavorContainer> flavors = project.getProductFlavors();
+ for (ProductFlavorContainer pfc : flavors) {
+ if (variant.getProductFlavors().contains(pfc.getProductFlavor().getName())) {
+ continue;
+ }
+ providers.add(pfc.getSourceProvider());
+ }
+
+ // Add other multi-flavor source providers
+ for (Variant v : variants) {
+ if (variant.getName().equals(v.getName())) {
+ continue;
+ }
+ SourceProvider provider = v.getMainArtifact().getMultiFlavorSourceProvider();
+ if (provider != null) {
+ providers.add(provider);
+ }
+ }
+
+ // Add other the build types
+ Collection<BuildTypeContainer> buildTypes = project.getBuildTypes();
+ for (BuildTypeContainer btc : buildTypes) {
+ if (variant.getBuildType().equals(btc.getBuildType().getName())) {
+ continue;
+ }
+ providers.add(btc.getSourceProvider());
+ }
+
+ // Add other the other variant source providers
+ for (Variant v : variants) {
+ if (variant.getName().equals(v.getName())) {
+ continue;
+ }
+ SourceProvider provider = v.getMainArtifact().getVariantSourceProvider();
+ if (provider != null) {
+ providers.add(provider);
+ }
+ }
+
+ return providers;
+ }
+
+ private void recordInactiveJavaReferences(@NonNull File resDir) {
+ File[] files = resDir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ recordInactiveJavaReferences(file);
+ } else if (file.getName().endsWith(DOT_JAVA)) {
+ try {
+ String java = Files.toString(file, UTF_8);
+ mModel.tokenizeJavaCode(java);
+ } catch (Throwable ignore) {
+ // Tolerate parsing errors etc in these files; they're user
+ // sources, and this is even for inactive source sets.
+ }
+ }
+ }
+ }
+ }
+
+ private void recordInactiveXmlResources(@NonNull File resDir) {
+ File[] resourceFolders = resDir.listFiles();
+ if (resourceFolders != null) {
+ for (File folder : resourceFolders) {
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folder.getName());
+ if (folderType != null) {
+ recordInactiveXmlResources(folderType, folder);
+ }
+ }
+ }
+ }
+
+ // Used for traversing resource folders *outside* of the normal Gradle variant
+ // folders: these are not necessarily on the project path, so we don't have PSI files
+ // for them
+ private void recordInactiveXmlResources(@NonNull ResourceFolderType folderType,
+ @NonNull File folder) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ String path = file.getPath();
+ boolean isXml = endsWithIgnoreCase(path, DOT_XML);
+ try {
+ if (isXml) {
+ String xml = Files.toString(file, UTF_8);
+ Document document = XmlUtils.parseDocument(xml, true);
+ mModel.visitXmlDocument(file, folderType, document);
+ } else {
+ mModel.visitBinaryResource(folderType, file);
+ }
+ } catch (Throwable ignore) {
+ // Tolerate parsing errors etc in these files; they're user
+ // sources, and this is even for inactive source sets.
+ }
+ }
+ }
+ }
+
+ private void addInactiveReferences(@NonNull AndroidProject model,
+ @NonNull Variant variant) {
+ for (SourceProvider provider : getInactiveSourceProviders(model, variant)) {
+ for (File res : provider.getResDirectories()) {
+ // Scan resource directory
+ if (res.isDirectory()) {
+ recordInactiveXmlResources(res);
+ }
+ }
+ for (File file : provider.getJavaDirectories()) {
+ // Scan Java directory
+ if (file.isDirectory()) {
+ recordInactiveJavaReferences(file);
+ }
+ }
+ }
+ }
+
+ /**
+ * Given an error message created by this lint check, return the corresponding
+ * resource field name for the resource that is described as unused.
+ * (Intended to support quickfix implementations for this lint check.)
+ *
+ * @param errorMessage the error message originally produced by this detector
+ * @param format the format of the error message
+ * @return the corresponding resource field name, e.g. {@code R.string.foo}
+ */
+ @Nullable
+ public static String getUnusedResource(@NonNull String errorMessage, @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+ return findSubstring(errorMessage, "The resource ", " appears ");
+ }
+
+ private static Issue getIssue(@NonNull Resource resource) {
+ return resource.type != ResourceType.ID ? ISSUE : ISSUE_IDS;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return true;
+ }
+
+ // ---- Implements BinaryResourceScanner ----
+
+ @Override
+ public void checkBinaryResource(@NonNull ResourceContext context) {
+ mModel.context = context;
+ try {
+ mModel.visitBinaryResource(context.getResourceFolderType(), context.file);
+ } finally {
+ mModel.context = null;
+ }
+ }
+
+ // ---- Implements XmlScanner ----
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ mModel.context = mModel.xmlContext = context;
+ try {
+ mModel.visitXmlDocument(context.file, context.getResourceFolderType(),
+ document);
+ } finally {
+ mModel.context = mModel.xmlContext = null;
+ }
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
+ //noinspection unchecked
+ return Arrays.<Class<? extends lombok.ast.Node>>asList(
+ ClassDeclaration.class,
+ ImportDeclaration.class);
+ }
+
+ @Override
+ public boolean appliesToResourceRefs() {
+ return true;
+ }
+
+ @Override
+ public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull lombok.ast.Node node, @NonNull String type, @NonNull String name,
+ boolean isFramework) {
+ if (!isFramework) {
+ ResourceType t = ResourceType.getEnum(type);
+ assert t != null : type;
+ ResourceUsageModel.markReachable(mModel.addResource(t, name, null));
+ }
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ if (context.getDriver().getPhase() == 1) {
+ return new UnusedResourceVisitor(context);
+ } else {
+ // Second pass, computing resource declaration locations: No need to look at Java
+ return null;
+ }
+ }
+
+ // Look for references and declarations
+ private class UnusedResourceVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public UnusedResourceVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ // Look for declarations of R class fields and record them as declarations
+ String description = node.astName().astValue();
+ if (description.equals(R_CLASS)) {
+ // Don't visit R class declarations; we don't need to look at R declarations
+ // since we now look for all the same resource declarations that the R
+ // class itself was derived from.
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visitImportDeclaration(ImportDeclaration node) {
+ if (node.astStaticImport()) {
+ // import static pkg.R.type.name;
+ // or
+ // import static pkg.R.type.*;
+ Iterator<Identifier> iterator = node.astParts().iterator();
+ while (iterator.hasNext()) {
+ String identifier = iterator.next().astValue();
+ if (identifier.equals(R_CLASS)) {
+ if (iterator.hasNext()) {
+ String typeString = iterator.next().astValue();
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type != null) {
+ if (iterator.hasNext()) {
+ // import static pkg.R.type.name;
+ String name = iterator.next().astValue();
+ Resource resource = mModel.addResource(type, name, null);
+ ResourceUsageModel.markReachable(resource);
+ } else if (!mScannedForStaticImports) {
+ // wildcard import of whole type:
+ // import static pkg.R.type.*;
+ // We have to do a more expensive analysis here to
+ // for example recognize "x" as a reference to R.string.x
+ mScannedForStaticImports = true;
+ CompilationUnit unit = node.upToCompilationUnit();
+ scanForStaticImportReferences(unit);
+ }
+ }
+ }
+ break;
+ }
+ }
+ } else if (!mScannedForStaticImports) {
+ String last = node.astParts().last().astValue();
+ if (Character.isLowerCase(last.charAt(0))) {
+ Iterator<Identifier> iterator = node.astParts().iterator();
+ while (iterator.hasNext()) {
+ String identifier = iterator.next().astValue();
+ if (identifier.equals(R_CLASS)) {
+ if (iterator.hasNext()) {
+ String typeString = iterator.next().astValue();
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type != null && !iterator.hasNext()) {
+ // Import a type as a class:
+ // import pkg.R.type;
+ // We have to do a more expensive analysis here to
+ // for example recognize "string.x" as a reference to R.string.x
+ mScannedForStaticImports = true;
+ CompilationUnit unit = node.upToCompilationUnit();
+ scanForStaticImportReferences(unit);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void scanForStaticImportReferences(@Nullable CompilationUnit unit) {
+ if (unit == null) {
+ return;
+ }
+ unit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitVariableReference(
+ VariableReference node) {
+ ResolvedNode resolved = mContext.resolve(node);
+ if (resolved instanceof ResolvedField) {
+ ResolvedField field = (ResolvedField) resolved;
+ ResolvedClass typeClass = field.getContainingClass();
+ if (typeClass != null) {
+ ResolvedClass rClass = typeClass.getContainingClass();
+ if (rClass != null && R_CLASS.equals(rClass.getSimpleName())) {
+ ResourceType type = ResourceType.getEnum(typeClass.getSimpleName());
+ if (type != null) {
+ Resource resource = mModel.addResource(type, field.getName(),
+ null);
+ ResourceUsageModel.markReachable(resource);
+ }
+ }
+ }
+
+ }
+ return super.visitVariableReference(node);
+ }
+ });
+ }
+
+ private boolean mScannedForStaticImports;
+ }
+
+ private static class UnusedResourceDetectorUsageModel extends ResourceUsageModel {
+ public XmlContext xmlContext;
+ public Context context;
+ public Set<Resource> unused = Sets.newHashSet();
+
+ @NonNull
+ @Override
+ protected String readText(@NonNull File file) {
+ if (context != null) {
+ return context.getClient().readFile(file);
+ }
+ try {
+ return Files.toString(file, UTF_8);
+ } catch (IOException e) {
+ return ""; // Lint API
+ }
+ }
+
+ @Override
+ protected Resource declareResource(ResourceType type, String name, Node node) {
+ Resource resource = super.declareResource(type, name, node);
+ if (context != null) {
+ resource.setDeclared(context.getProject().getReportIssues());
+ if (context.getPhase() == 2 && unused.contains(resource)) {
+ if (xmlContext != null && xmlContext.getDriver().isSuppressed(xmlContext,
+ getIssue(resource), node)) {
+ resource.setKeep(true);
+ } else {
+ // For positions we try to use the name node rather than the
+ // whole declaration element
+ if (node == null || xmlContext == null) {
+ resource.recordLocation(Location.create(context.file));
+ } else {
+ if (node instanceof Element) {
+ Node attribute = ((Element) node).getAttributeNode(ATTR_NAME);
+ if (attribute != null) {
+ node = attribute;
+ }
+ }
+ resource.recordLocation(xmlContext.getLocation(node));
+ }
+ }
+ }
+ }
+
+ return resource;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/VectorDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/VectorDetector.java
new file mode 100644
index 0000000..d19c3de
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/VectorDetector.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidProject;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Checks for unreachable states in an Android state list definition
+ */
+public class VectorDetector extends ResourceXmlDetector {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "VectorRaster", //$NON-NLS-1$
+ "Vector Image Generation",
+ "Vector icons require API 21, but when using Android Gradle plugin 1.4 or higher, " +
+ "vectors placed in the `drawable` folder are automatically moved to `drawable-*dpi-v21` " +
+ "and a bitmap image is generated each `drawable-*dpi` folder instead, for backwards " +
+ "compatibility (provided `minSdkVersion` is less than 21.).\n" +
+ "\n" +
+ "However, there are some limitations to this vector image generation, and this " +
+ "lint check flags elements and attributes that are not fully supported. " +
+ "You should manually check whether the generated output is acceptable for those " +
+ "older devices.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.WARNING,
+ new Implementation(
+ VectorDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Constructs a new {@link VectorDetector} */
+ public VectorDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.DRAWABLE;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+
+ /**
+ * Returns true if the given Gradle project model supports vector image generation
+ *
+ * @param project the project to check
+ * @return true if the plugin supports vector image generation
+ */
+ public static boolean isVectorGenerationSupported(@NonNull AndroidProject project) {
+ String modelVersion = project.getModelVersion();
+
+ // Requires 1.4.x or higher. Rather than doing string => x.y.z decomposition and then
+ // checking higher than 1.4.0, we'll just exclude the 4 possible prefixes that don't satisfy
+ // this requirement.
+ return !(modelVersion.startsWith("1.0")
+ || modelVersion.startsWith("1.1")
+ || modelVersion.startsWith("1.2")
+ || modelVersion.startsWith("1.3"));
+ }
+
+
+ @Override
+ public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
+ // If minSdkVersion >= 21, we're not generating compatibility vector icons
+ Project project = context.getMainProject();
+ if (project.getMinSdkVersion().getFeatureLevel() >= 21) {
+ return;
+ }
+
+ // Vector generation is only done for Gradle projects
+ if (!project.isGradleProject()) {
+ return;
+ }
+
+ // Not using a plugin that supports vector image generation?
+ AndroidProject model = project.getGradleProjectModel();
+ if (model == null || !isVectorGenerationSupported(model)) {
+ return;
+ }
+
+ Element root = document.getDocumentElement();
+ // If this is not actually a vector icon, nothing to do in this detector
+ if (root == null || !root.getTagName().equals("vector")) { //$NON-NLS-1$
+ return;
+ }
+
+ // If this vector asset is in a -v21 folder, we're not generating vector assets
+ if (context.getFolderVersion() >= 21) {
+ return;
+ }
+
+ // TODO: Check to see if there already is a -?dpi version of the file; if so,
+ // we also won't be generating a vector image
+
+
+ checkSupported(context, root);
+ }
+
+ /** Recursive element check for unsupported attributes and tags */
+ private static void checkSupported(@NonNull XmlContext context, @NonNull Element element) {
+ // Unsupported tags
+ String tag = element.getTagName();
+ if ("clip-path".equals(tag)) {
+ String message = "This tag is not supported in images generated from this "
+ + "vector icon for API < 21; check generated icon to make sure it looks "
+ + "acceptable";
+ context.report(ISSUE, element, context.getLocation(element), message);
+ } else if ("group".equals(tag)) {
+ AndroidProject model = context.getMainProject().getGradleProjectModel();
+ if (model != null && model.getModelVersion().startsWith("1.4.")) {
+ String message = "Update Gradle plugin version to 1.5+ to correctly handle "
+ + "`<group>` tags in generated bitmaps";
+ context.report(ISSUE, element, context.getLocation(element), message);
+ }
+ }
+
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr)attributes.item(i);
+ String name = attr.getLocalName();
+ if (("autoMirrored".equals(name)
+ || "trimPathStart".equals(name)
+ || "trimPathEnd".equals(name)
+ || "trimPathOffset".equals(name))
+ && ANDROID_URI.equals(attr.getNamespaceURI())) {
+ String message = "This attribute is not supported in images generated from this "
+ + "vector icon for API < 21; check generated icon to make sure it looks "
+ + "acceptable";
+ context.report(ISSUE, attr, context.getNameLocation(attr), message);
+ }
+
+ String value = attr.getValue();
+ if (ResourceUrl.parse(value) != null) {
+ String message = "Resource references will not work correctly in images generated "
+ + "for this vector icon for API < 21; check generated icon to make sure "
+ + "it looks acceptable";
+ context.report(ISSUE, attr, context.getValueLocation(attr), message);
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ checkSupported(context, ((Element) child));
+ }
+ }
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewHolderDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewHolderDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewHolderDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewHolderDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
new file mode 100644
index 0000000..f4a15c7
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CLASS_VIEW;
+import static com.android.tools.lint.checks.CleanupDetector.CURSOR_CLS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TypeEvaluator;
+
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+
+/**
+ * Checks for missing view tag detectors
+ */
+public class ViewTagDetector extends Detector implements Detector.JavaScanner {
+ /** Using setTag and leaking memory */
+ public static final Issue ISSUE = Issue.create(
+ "ViewTag", //$NON-NLS-1$
+ "Tagged object leaks",
+
+ "Prior to Android 4.0, the implementation of `View.setTag(int, Object)` would " +
+ "store the objects in a static map, where the values were strongly referenced. " +
+ "This means that if the object contains any references pointing back to the " +
+ "context, the context (which points to pretty much everything else) will leak. " +
+ "If you pass a view, the view provides a reference to the context " +
+ "that created it. Similarly, view holders typically contain a view, and cursors " +
+ "are sometimes also associated with views.",
+
+ Category.PERFORMANCE,
+ 6,
+ Severity.WARNING,
+ new Implementation(
+ ViewTagDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ /** Constructs a new {@link ViewTagDetector} */
+ public ViewTagDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList("setTag");
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation call) {
+ // The leak behavior is fixed in ICS:
+ // http://code.google.com/p/android/issues/detail?id=18273
+ if (context.getMainProject().getMinSdk() >= 14) {
+ return;
+ }
+
+ ResolvedNode resolved = context.resolve(call);
+ if (!(resolved instanceof ResolvedMethod)) {
+ return;
+ }
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ if (method.getArgumentCount() != 2) {
+ return;
+ }
+ Expression tagArgument = call.astArguments().last();
+ if (tagArgument == null) {
+ return;
+ }
+ ResolvedClass containingClass = method.getContainingClass();
+ if (!containingClass.matches(CLASS_VIEW)) {
+ return;
+ }
+
+ TypeDescriptor type = TypeEvaluator.evaluate(context, tagArgument);
+ if (type == null) {
+ return;
+ }
+ ResolvedClass typeClass = type.getTypeClass();
+ if (typeClass == null) {
+ return;
+ }
+
+ String objectType;
+ if (typeClass.isSubclassOf(CLASS_VIEW, false)) {
+ objectType = "views";
+ } else if (typeClass.isImplementing(CURSOR_CLS, false)) {
+ objectType = "cursors";
+ } else if (typeClass.getSimpleName().endsWith("ViewHolder")) {
+ objectType = "view holders";
+ } else {
+ return;
+ }
+ String message = String.format("Avoid setting %1$s as values for `setTag`: " +
+ "Can lead to memory leaks in versions older than Android 4.0",
+ objectType);
+
+ context.report(ISSUE, call, context.getLocation(tagArgument), message);
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
new file mode 100644
index 0000000..6718c43
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.ID_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.VIEW_TAG;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Cast;
+import lombok.ast.Expression;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+
+/** Detector for finding inconsistent usage of views and casts
+ * <p>
+ * TODO: Check findFragmentById
+ * <pre>
+ * ((ItemListFragment) getSupportFragmentManager()
+ * .findFragmentById(R.id.item_list))
+ * .setActivateOnItemClick(true);
+ * </pre>
+ * Here we should check the {@code <fragment>} tag pointed to by the id, and
+ * check its name or class attributes to make sure the cast is compatible with
+ * the named fragment class!
+ */
+public class ViewTypeDetector extends ResourceXmlDetector implements JavaScanner {
+ /** Mismatched view types */
+ @SuppressWarnings("unchecked")
+ public static final Issue ISSUE = Issue.create(
+ "WrongViewCast", //$NON-NLS-1$
+ "Mismatched view type",
+ "Keeps track of the view types associated with ids and if it finds a usage of " +
+ "the id in the Java code it ensures that it is treated as the same type.",
+ Category.CORRECTNESS,
+ 9,
+ Severity.FATAL,
+ new Implementation(
+ ViewTypeDetector.class,
+ EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES),
+ Scope.JAVA_FILE_SCOPE));
+
+ /** Flag used to do no work if we're running in incremental mode in a .java file without
+ * a client supporting project resources */
+ private Boolean mIgnore = null;
+
+ private final Map<String, Object> mIdToViewTag = new HashMap<String, Object>(50);
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.SLOW;
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.LAYOUT;
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(ATTR_ID);
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ String view = attribute.getOwnerElement().getTagName();
+ String value = attribute.getValue();
+ String id = null;
+ if (value.startsWith(ID_PREFIX)) {
+ id = value.substring(ID_PREFIX.length());
+ } else if (value.startsWith(NEW_ID_PREFIX)) {
+ id = value.substring(NEW_ID_PREFIX.length());
+ } // else: could be @android id
+
+ if (id != null) {
+ if (view.equals(VIEW_TAG)) {
+ view = attribute.getOwnerElement().getAttribute(ATTR_CLASS);
+ }
+
+ Object existing = mIdToViewTag.get(id);
+ if (existing == null) {
+ mIdToViewTag.put(id, view);
+ } else if (existing instanceof String) {
+ String existingString = (String) existing;
+ if (!existingString.equals(view)) {
+ // Convert to list
+ List<String> list = new ArrayList<String>(2);
+ list.add((String) existing);
+ list.add(view);
+ mIdToViewTag.put(id, list);
+ }
+ } else if (existing instanceof List<?>) {
+ @SuppressWarnings("unchecked")
+ List<String> list = (List<String>) existing;
+ if (!list.contains(view)) {
+ list.add(view);
+ }
+ }
+ }
+ }
+
+ // ---- Implements Detector.JavaScanner ----
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Collections.singletonList("findViewById"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ LintClient client = context.getClient();
+ if (mIgnore == Boolean.TRUE) {
+ return;
+ } else if (mIgnore == null) {
+ mIgnore = !context.getScope().contains(Scope.ALL_RESOURCE_FILES) &&
+ !client.supportsProjectResources();
+ if (mIgnore) {
+ return;
+ }
+ }
+ assert node.astName().astValue().equals("findViewById");
+ if (node.getParent() instanceof Cast) {
+ Cast cast = (Cast) node.getParent();
+ String castType = cast.astTypeReference().getTypeName();
+ StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
+ if (args.size() == 1) {
+ Expression first = args.first();
+ // TODO: Do flow analysis as in the StringFormatDetector in order
+ // to handle variable references too
+ if (first instanceof Select) {
+ String resource = first.toString();
+ if (resource.startsWith("R.id.")) { //$NON-NLS-1$
+ String id = ((Select) first).astIdentifier().astValue();
+
+ if (client.supportsProjectResources()) {
+ AbstractResourceRepository resources = client
+ .getProjectResources(context.getMainProject(), true);
+ if (resources == null) {
+ return;
+ }
+
+ List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
+ id);
+ if (items != null && !items.isEmpty()) {
+ Set<String> compatible = Sets.newHashSet();
+ for (ResourceItem item : items) {
+ Collection<String> tags = getViewTags(context, item);
+ if (tags != null) {
+ compatible.addAll(tags);
+ }
+ }
+ if (!compatible.isEmpty()) {
+ ArrayList<String> layoutTypes = Lists.newArrayList(compatible);
+ checkCompatible(context, castType, null, layoutTypes, cast);
+ }
+ }
+ } else {
+ Object types = mIdToViewTag.get(id);
+ if (types instanceof String) {
+ String layoutType = (String) types;
+ checkCompatible(context, castType, layoutType, null, cast);
+ } else if (types instanceof List<?>) {
+ @SuppressWarnings("unchecked")
+ List<String> layoutTypes = (List<String>) types;
+ checkCompatible(context, castType, null, layoutTypes, cast);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Nullable
+ protected Collection<String> getViewTags(
+ @NonNull Context context,
+ @NonNull ResourceItem item) {
+ // Check view tag in this file. Can I do it cheaply? Try with
+ // an XML pull parser. Or DOM if we have multiple resources looked
+ // up?
+ ResourceFile source = item.getSource();
+ if (source != null) {
+ File file = source.getFile();
+ Multimap<String,String> map = getIdToTagsIn(context, file);
+ if (map != null) {
+ return map.get(item.getName());
+ }
+ }
+
+ return null;
+ }
+
+
+ private Map<File, Multimap<String, String>> mFileIdMap;
+
+ @Nullable
+ private Multimap<String, String> getIdToTagsIn(@NonNull Context context, @NonNull File file) {
+ if (!file.getPath().endsWith(DOT_XML)) {
+ return null;
+ }
+ if (mFileIdMap == null) {
+ mFileIdMap = Maps.newHashMap();
+ }
+ Multimap<String, String> map = mFileIdMap.get(file);
+ if (map == null) {
+ map = ArrayListMultimap.create();
+ mFileIdMap.put(file, map);
+
+ String xml = context.getClient().readFile(file);
+ // TODO: Use pull parser instead for better performance!
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ if (document != null && document.getDocumentElement() != null) {
+ addViewTags(map, document.getDocumentElement());
+ }
+ }
+ return map;
+ }
+
+ private static void addViewTags(Multimap<String, String> map, Element element) {
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (id != null && !id.isEmpty()) {
+ id = LintUtils.stripIdPrefix(id);
+ if (!map.containsEntry(id, element.getTagName())) {
+ map.put(id, element.getTagName());
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addViewTags(map, (Element) child);
+ }
+ }
+ }
+
+ /** Check if the view and cast type are compatible */
+ private static void checkCompatible(JavaContext context, String castType, String layoutType,
+ List<String> layoutTypes, Cast node) {
+ assert layoutType == null || layoutTypes == null; // Should only specify one or the other
+ boolean compatible = true;
+ if (layoutType != null) {
+ if (!layoutType.equals(castType)
+ && !context.getSdkInfo().isSubViewOf(castType, layoutType)) {
+ compatible = false;
+ }
+ } else {
+ compatible = false;
+ assert layoutTypes != null;
+ for (String type : layoutTypes) {
+ if (type.equals(castType)
+ || context.getSdkInfo().isSubViewOf(castType, type)) {
+ compatible = true;
+ break;
+ }
+ }
+ }
+
+ if (!compatible) {
+ if (layoutType == null) {
+ layoutType = Joiner.on("|").join(layoutTypes);
+ }
+ String message = String.format(
+ "Unexpected cast to `%1$s`: layout tag was `%2$s`",
+ castType, layoutType);
+ context.report(ISSUE, node, context.getLocation(node), message);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
new file mode 100644
index 0000000..0fb736c
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.checks.ControlFlowGraph.Node;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.JumpInsnNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Checks for problems with wakelocks (such as failing to release them)
+ * which can lead to unnecessary battery usage.
+ */
+public class WakelockDetector extends Detector implements ClassScanner {
+ public static final String ANDROID_APP_ACTIVITY = "android/app/Activity"; //$NON-NLS-1$
+
+ /** Problems using wakelocks */
+ public static final Issue ISSUE = Issue.create(
+ "Wakelock", //$NON-NLS-1$
+ "Incorrect `WakeLock` usage",
+
+ "Failing to release a wakelock properly can keep the Android device in " +
+ "a high power mode, which reduces battery life. There are several causes " +
+ "of this, such as releasing the wake lock in `onDestroy()` instead of in " +
+ "`onPause()`, failing to call `release()` in all possible code paths after " +
+ "an `acquire()`, and so on.\n" +
+ "\n" +
+ "NOTE: If you are using the lock just to keep the screen on, you should " +
+ "strongly consider using `FLAG_KEEP_SCREEN_ON` instead. This window flag " +
+ "will be correctly managed by the platform as the user moves between " +
+ "applications and doesn't require a special permission. See " +
+ "http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON.",
+
+ Category.PERFORMANCE,
+ 9,
+ Severity.WARNING,
+ new Implementation(
+ WakelockDetector.class,
+ Scope.CLASS_FILE_SCOPE));
+
+ private static final String WAKELOCK_OWNER = "android/os/PowerManager$WakeLock"; //$NON-NLS-1$
+ private static final String RELEASE_METHOD = "release"; //$NON-NLS-1$
+ private static final String ACQUIRE_METHOD = "acquire"; //$NON-NLS-1$
+ private static final String IS_HELD_METHOD = "isHeld"; //$NON-NLS-1$
+ private static final String POWER_MANAGER = "android/os/PowerManager"; //$NON-NLS-1$
+ private static final String NEW_WAKE_LOCK_METHOD = "newWakeLock"; //$NON-NLS-1$
+
+ /** Print diagnostics during analysis (display flow control graph etc).
+ * Make sure you add the asm-debug or asm-util jars to the runtime classpath
+ * as well since the opcode integer to string mapping display routine looks for
+ * it via reflection. */
+ private static final boolean DEBUG = false;
+
+ /** Constructs a new {@link WakelockDetector} */
+ public WakelockDetector() {
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mHasAcquire && !mHasRelease && context.getDriver().getPhase() == 1) {
+ // Gather positions of the acquire calls
+ context.getDriver().requestRepeat(this, Scope.CLASS_FILE_SCOPE);
+ }
+ }
+
+ // ---- Implements ClassScanner ----
+
+ /** Whether any {@code acquire()} calls have been encountered */
+ private boolean mHasAcquire;
+
+ /** Whether any {@code release()} calls have been encountered */
+ private boolean mHasRelease;
+
+ @Override
+ @Nullable
+ public List<String> getApplicableCallNames() {
+ return Arrays.asList(ACQUIRE_METHOD, RELEASE_METHOD, NEW_WAKE_LOCK_METHOD);
+ }
+
+ @Override
+ public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull MethodInsnNode call) {
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ if (call.owner.equals(WAKELOCK_OWNER)) {
+ String name = call.name;
+ if (name.equals(ACQUIRE_METHOD)) {
+ if (call.desc.equals("(J)V")) { // acquire(long timeout) does not require a corresponding release
+ return;
+ }
+ mHasAcquire = true;
+
+ if (context.getDriver().getPhase() == 2) {
+ assert !mHasRelease;
+ context.report(ISSUE, method, call, context.getLocation(call),
+ "Found a wakelock `acquire()` but no `release()` calls anywhere");
+ } else {
+ assert context.getDriver().getPhase() == 1;
+ // Perform flow analysis in this method to see if we're
+ // performing an acquire/release block, where there are code paths
+ // between the acquire and release which can result in the
+ // release call not getting reached.
+ checkFlow(context, classNode, method, call);
+ }
+ } else if (name.equals(RELEASE_METHOD)) {
+ mHasRelease = true;
+
+ // See if the release is happening in an onDestroy method, in an
+ // activity.
+ if ("onDestroy".equals(method.name) //$NON-NLS-1$
+ && context.getDriver().isSubclassOf(
+ classNode, ANDROID_APP_ACTIVITY)) {
+ context.report(ISSUE, method, call, context.getLocation(call),
+ "Wakelocks should be released in `onPause`, not `onDestroy`");
+ }
+ }
+ } else if (call.owner.equals(POWER_MANAGER)) {
+ if (call.name.equals(NEW_WAKE_LOCK_METHOD)) {
+ AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
+ if (prev == null) {
+ return;
+ }
+ prev = LintUtils.getPrevInstruction(prev);
+ if (prev == null || prev.getOpcode() != Opcodes.LDC) {
+ return;
+ }
+ LdcInsnNode ldc = (LdcInsnNode) prev;
+ Object constant = ldc.cst;
+ if (constant instanceof Integer) {
+ int flag = ((Integer) constant).intValue();
+ // Constant values are copied into the bytecode so we have to compare
+ // values; however, that means the values are part of the API
+ final int PARTIAL_WAKE_LOCK = 0x00000001;
+ final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
+ final int both = PARTIAL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP;
+ if ((flag & both) == both) {
+ context.report(ISSUE, method, call, context.getLocation(call),
+ "Should not set both `PARTIAL_WAKE_LOCK` and `ACQUIRE_CAUSES_WAKEUP`. "
+ + "If you do not want the screen to turn on, get rid of "
+ + "`ACQUIRE_CAUSES_WAKEUP`");
+ }
+ }
+
+ }
+ }
+ }
+
+ private static void checkFlow(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull MethodInsnNode acquire) {
+ final InsnList instructions = method.instructions;
+ MethodInsnNode release = null;
+
+ // Find release call
+ for (int i = 0, n = instructions.size(); i < n; i++) {
+ AbstractInsnNode instruction = instructions.get(i);
+ int type = instruction.getType();
+ if (type == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode call = (MethodInsnNode) instruction;
+ if (call.name.equals(RELEASE_METHOD) &&
+ call.owner.equals(WAKELOCK_OWNER)) {
+ release = call;
+ break;
+ }
+ }
+ }
+
+ if (release == null) {
+ // Didn't find both acquire and release in this method; no point in doing
+ // local flow analysis
+ return;
+ }
+
+ try {
+ MyGraph graph = new MyGraph();
+ ControlFlowGraph.create(graph, classNode, method);
+
+ if (DEBUG) {
+ // Requires util package
+ //ClassNode clazz = classNode;
+ //clazz.accept(new TraceClassVisitor(new PrintWriter(System.out)));
+ System.out.println(graph.toString(graph.getNode(acquire)));
+ }
+
+ int status = dfs(graph.getNode(acquire));
+ if ((status & SEEN_RETURN) != 0) {
+ String message;
+ if ((status & SEEN_EXCEPTION) != 0) {
+ message = "The `release()` call is not always reached (via exceptional flow)";
+ } else {
+ message = "The `release()` call is not always reached";
+ }
+
+ context.report(ISSUE, method, acquire,
+ context.getLocation(release), message);
+ }
+ } catch (AnalyzerException e) {
+ context.log(e, null);
+ }
+ }
+
+ private static final int SEEN_TARGET = 1;
+ private static final int SEEN_BRANCH = 2;
+ private static final int SEEN_EXCEPTION = 4;
+ private static final int SEEN_RETURN = 8;
+
+ /** TODO RENAME */
+ private static class MyGraph extends ControlFlowGraph {
+ @Override
+ protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
+ if (from.getOpcode() == Opcodes.IFNULL) {
+ JumpInsnNode jump = (JumpInsnNode) from;
+ if (jump.label == to) {
+ // Skip jump targets on null if it's surrounding the release call
+ //
+ // if (lock != null) {
+ // lock.release();
+ // }
+ //
+ // The above shouldn't be considered a scenario where release() may not
+ // be called.
+ AbstractInsnNode next = LintUtils.getNextInstruction(from);
+ if (next != null && next.getType() == AbstractInsnNode.VAR_INSN) {
+ next = LintUtils.getNextInstruction(next);
+ if (next != null && next.getType() == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode method = (MethodInsnNode) next;
+ if (method.name.equals(RELEASE_METHOD) &&
+ method.owner.equals(WAKELOCK_OWNER)) {
+ // This isn't entirely correct; this will also trigger
+ // for "if (lock == null) { lock.release(); }" but that's
+ // not likely (and caught by other null checking in tools)
+ return;
+ }
+ }
+ }
+ }
+ } else if (from.getOpcode() == Opcodes.IFEQ) {
+ JumpInsnNode jump = (JumpInsnNode) from;
+ if (jump.label == to) {
+ AbstractInsnNode prev = LintUtils.getPrevInstruction(from);
+ if (prev != null && prev.getType() == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode method = (MethodInsnNode) prev;
+ if (method.name.equals(IS_HELD_METHOD) &&
+ method.owner.equals(WAKELOCK_OWNER)) {
+ AbstractInsnNode next = LintUtils.getNextInstruction(from);
+ if (next != null) {
+ super.add(from, next);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ super.add(from, to);
+ }
+ }
+
+ /** Search from the given node towards the target; return false if we reach
+ * an exit point such as a return or a call on the way there that is not within
+ * a try/catch clause.
+ *
+ * @param node the current node
+ * @return true if the target was reached
+ * XXX RETURN VALUES ARE WRONG AS OF RIGHT NOW
+ */
+ protected static int dfs(ControlFlowGraph.Node node) {
+ AbstractInsnNode instruction = node.instruction;
+ if (instruction.getType() == AbstractInsnNode.JUMP_INSN) {
+ int opcode = instruction.getOpcode();
+ if (opcode == Opcodes.RETURN || opcode == Opcodes.ARETURN
+ || opcode == Opcodes.LRETURN || opcode == Opcodes.IRETURN
+ || opcode == Opcodes.DRETURN || opcode == Opcodes.FRETURN
+ || opcode == Opcodes.ATHROW) {
+ if (DEBUG) {
+ System.out.println("Found exit via explicit return: " //$NON-NLS-1$
+ + node.toString(false));
+ }
+ return SEEN_RETURN;
+ }
+ }
+
+ if (!DEBUG) {
+ // There are no cycles, so no *NEED* for this, though it does avoid
+ // researching shared labels. However, it makes debugging harder (no re-entry)
+ // so this is only done when debugging is off
+ if (node.visit != 0) {
+ return 0;
+ }
+ node.visit = 1;
+ }
+
+ // Look for the target. This is any method call node which is a release on the
+ // lock (later also check it's the same instance, though that's harder).
+ // This is because finally blocks tend to be inlined so from a single try/catch/finally
+ // with a release() in the finally, the bytecode can contain multiple repeated
+ // (inlined) release() calls.
+ if (instruction.getType() == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode method = (MethodInsnNode) instruction;
+ if (method.name.equals(RELEASE_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
+ return SEEN_TARGET;
+ } else if (method.name.equals(ACQUIRE_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
+ // OK
+ } else if (method.name.equals(IS_HELD_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
+ // OK
+ } else {
+ // Some non acquire/release method call: if this is not associated with a
+ // try-catch block, it would mean the exception would exit the method,
+ // which would be an error
+ if (node.exceptions == null || node.exceptions.isEmpty()) {
+ // Look up the corresponding frame, if any
+ AbstractInsnNode curr = method.getPrevious();
+ boolean foundFrame = false;
+ while (curr != null) {
+ if (curr.getType() == AbstractInsnNode.FRAME) {
+ foundFrame = true;
+ break;
+ }
+ curr = curr.getPrevious();
+ }
+
+ if (!foundFrame) {
+ if (DEBUG) {
+ System.out.println("Found exit via unguarded method call: " //$NON-NLS-1$
+ + node.toString(false));
+ }
+ return SEEN_RETURN;
+ }
+ }
+ }
+ }
+
+ // if (node.instruction is a call, and the call is not caught by
+ // a try/catch block (provided the release is not inside the try/catch block)
+ // then return false
+ int status = 0;
+
+ boolean implicitReturn = true;
+ List<Node> successors = node.successors;
+ List<Node> exceptions = node.exceptions;
+ if (exceptions != null) {
+ if (!exceptions.isEmpty()) {
+ implicitReturn = false;
+ }
+ for (Node successor : exceptions) {
+ status = dfs(successor) | status;
+ if ((status & SEEN_RETURN) != 0) {
+ if (DEBUG) {
+ System.out.println("Found exit via exception: " //$NON-NLS-1$
+ + node.toString(false));
+ }
+ return status;
+ }
+ }
+
+ if (status != 0) {
+ status |= SEEN_EXCEPTION;
+ }
+ }
+
+ if (successors != null) {
+ if (!successors.isEmpty()) {
+ implicitReturn = false;
+ if (successors.size() > 1) {
+ status |= SEEN_BRANCH;
+ }
+ }
+ for (Node successor : successors) {
+ status = dfs(successor) | status;
+ if ((status & SEEN_RETURN) != 0) {
+ if (DEBUG) {
+ System.out.println("Found exit via branches: " //$NON-NLS-1$
+ + node.toString(false));
+ }
+ return status;
+ }
+ }
+ }
+
+ if (implicitReturn) {
+ status |= SEEN_RETURN;
+ if (DEBUG) {
+ System.out.println("Found exit: via implicit return: " //$NON-NLS-1$
+ + node.toString(false));
+ }
+ }
+
+ return status;
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WebViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WebViewDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WebViewDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WebViewDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
new file mode 100644
index 0000000..77acff3
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CLASS_VIEW;
+import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_DRAW;
+import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_LAYOUT;
+import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_MEASURE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.TextFormat;
+
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.Expression;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Super;
+
+/**
+ * Checks for cases where the wrong call is being made
+ */
+public class WrongCallDetector extends Detector implements Detector.JavaScanner {
+ /** Calling the wrong method */
+ public static final Issue ISSUE = Issue.create(
+ "WrongCall", //$NON-NLS-1$
+ "Using wrong draw/layout method",
+
+ "Custom views typically need to call `measure()` on their children, not `onMeasure`. " +
+ "Ditto for onDraw, onLayout, etc.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ new Implementation(
+ WrongCallDetector.class,
+ Scope.JAVA_FILE_SCOPE));
+
+ /** Constructs a new {@link WrongCallDetector} */
+ public WrongCallDetector() {
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ @Nullable
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(
+ ON_DRAW,
+ ON_MEASURE,
+ ON_LAYOUT
+ );
+ }
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+
+ // Call is only allowed if it is both only called on the super class (invoke special)
+ // as well as within the same overriding method (e.g. you can't call super.onLayout
+ // from the onMeasure method)
+ Expression operand = node.astOperand();
+ if (!(operand instanceof Super)) {
+ report(context, node);
+ return;
+ }
+
+ Node method = StringFormatDetector.getParentMethod(node);
+ if (!(method instanceof MethodDeclaration) ||
+ !((MethodDeclaration)method).astMethodName().astValue().equals(
+ node.astName().astValue())) {
+ report(context, node);
+ }
+ }
+
+ private static void report(JavaContext context, MethodInvocation node) {
+ // Make sure the call is on a view
+ ResolvedNode resolved = context.resolve(node);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ ResolvedClass containingClass = method.getContainingClass();
+ if (!containingClass.isSubclassOf(CLASS_VIEW, false)) {
+ return;
+ }
+ }
+
+ String name = node.astName().astValue();
+ String suggestion = Character.toLowerCase(name.charAt(2)) + name.substring(3);
+ String message = String.format(
+ // Keep in sync with {@link #getOldValue} and {@link #getNewValue} below!
+ "Suspicious method call; should probably call \"`%1$s`\" rather than \"`%2$s`\"",
+ suggestion, name);
+ context.report(ISSUE, node, context.getLocation(node.astName()), message);
+ }
+
+ /**
+ * Given an error message produced by this lint detector for the given issue type,
+ * returns the old value to be replaced in the source code.
+ * <p>
+ * Intended for IDE quickfix implementations.
+ *
+ * @param errorMessage the error message associated with the error
+ * @param format the format of the error message
+ * @return the corresponding old value, or null if not recognized
+ */
+ @Nullable
+ public static String getOldValue(@NonNull String errorMessage, @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+ return LintUtils.findSubstring(errorMessage, "than \"", "\"");
+ }
+
+ /**
+ * Given an error message produced by this lint detector for the given issue type,
+ * returns the new value to be put into the source code.
+ * <p>
+ * Intended for IDE quickfix implementations.
+ *
+ * @param errorMessage the error message associated with the error
+ * @param format the format of the error message
+ * @return the corresponding new value, or null if not recognized
+ */
+ @Nullable
+ public static String getNewValue(@NonNull String errorMessage, @NonNull TextFormat format) {
+ errorMessage = format.toText(errorMessage);
+ return LintUtils.findSubstring(errorMessage, "call \"", "\"");
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
new file mode 100644
index 0000000..c02bc08
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.FD_RES_VALUES;
+import static com.android.SdkConstants.ID_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.RELATIVE_LAYOUT;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.VALUE_ID;
+import static com.android.tools.lint.checks.RequiredAttributeDetector.PERCENT_RELATIVE_LAYOUT;
+import static com.android.tools.lint.detector.api.LintUtils.editDistance;
+import static com.android.tools.lint.detector.api.LintUtils.getChildren;
+import static com.android.tools.lint.detector.api.LintUtils.isSameResourceFile;
+import static com.android.tools.lint.detector.api.LintUtils.stripIdPrefix;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Location.Handle;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.android.utils.Pair;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Checks for duplicate ids within a layout and within an included layout
+ */
+public class WrongIdDetector extends LayoutDetector {
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ WrongIdDetector.class,
+ Scope.RESOURCE_FILE_SCOPE);
+
+ /** Ids bound to widgets in any of the layout files */
+ private final Set<String> mGlobalIds = new HashSet<String>(100);
+
+ /** Ids bound to widgets in the current layout file */
+ private Set<String> mFileIds;
+
+ /** Ids declared in a value's file, e.g. {@code <item type="id" name="foo"/>} */
+ private Set<String> mDeclaredIds;
+
+ /**
+ * Location handles for the various id references that were not found as
+ * defined in the same layout, to be checked after the whole project has
+ * been scanned
+ */
+ private List<Pair<String, Location.Handle>> mHandles;
+
+ /** List of RelativeLayout elements in the current layout */
+ private List<Element> mRelativeLayouts;
+
+ /** Reference to an unknown id */
+ @SuppressWarnings("unchecked")
+ public static final Issue UNKNOWN_ID = Issue.create(
+ "UnknownId", //$NON-NLS-1$
+ "Reference to an unknown id",
+ "The `@+id/` syntax refers to an existing id, or creates a new one if it has " +
+ "not already been defined elsewhere. However, this means that if you have a " +
+ "typo in your reference, or if the referred view no longer exists, you do not " +
+ "get a warning since the id will be created on demand. This check catches " +
+ "errors where you have renamed an id without updating all of the references to " +
+ "it.",
+ Category.CORRECTNESS,
+ 8,
+ Severity.FATAL,
+ new Implementation(
+ WrongIdDetector.class,
+ Scope.ALL_RESOURCES_SCOPE,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Reference to an id that is not a sibling */
+ public static final Issue NOT_SIBLING = Issue.create(
+ "NotSibling", //$NON-NLS-1$
+ "RelativeLayout Invalid Constraints",
+ "Layout constraints in a given `RelativeLayout` should reference other views " +
+ "within the same relative layout (but not itself!)",
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /** An ID declaration which is not valid */
+ public static final Issue INVALID = Issue.create(
+ "InvalidId", //$NON-NLS-1$
+ "Invalid ID declaration",
+ "An id definition *must* be of the form `@+id/yourname`. The tools have not " +
+ "rejected strings of the form `@+foo/bar` in the past, but that was an error, " +
+ "and could lead to tricky errors because of the way the id integers are assigned.\n" +
+ "\n" +
+ "If you really want to have different \"scopes\" for your id's, use prefixes " +
+ "instead, such as `login_button1` and `login_button2`.",
+ Category.CORRECTNESS,
+ 6,
+ Severity.FATAL,
+ IMPLEMENTATION);
+
+ /** Reference to an id that is not in the current layout */
+ public static final Issue UNKNOWN_ID_LAYOUT = Issue.create(
+ "UnknownIdInLayout", //$NON-NLS-1$
+ "Reference to an id that is not in the current layout",
+
+ "The `@+id/` syntax refers to an existing id, or creates a new one if it has " +
+ "not already been defined elsewhere. However, this means that if you have a " +
+ "typo in your reference, or if the referred view no longer exists, you do not " +
+ "get a warning since the id will be created on demand.\n" +
+ "\n" +
+ "This is sometimes intentional, for example where you are referring to a view " +
+ "which is provided in a different layout via an include. However, it is usually " +
+ "an accident where you have a typo or you have renamed a view without updating " +
+ "all the references to it.",
+
+ Category.CORRECTNESS,
+ 5,
+ Severity.WARNING,
+ new Implementation(
+ WrongIdDetector.class,
+ Scope.RESOURCE_FILE_SCOPE));
+
+ /** Constructs a duplicate id check */
+ public WrongIdDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(ATTR_ID);
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(RELATIVE_LAYOUT, TAG_ITEM, PERCENT_RELATIVE_LAYOUT);
+ }
+
+ @Override
+ public void beforeCheckFile(@NonNull Context context) {
+ mFileIds = new HashSet<String>();
+ mRelativeLayouts = null;
+ }
+
+ @Override
+ public void afterCheckFile(@NonNull Context context) {
+ if (mRelativeLayouts != null) {
+ if (!context.getProject().getReportIssues()) {
+ // If this is a library project not being analyzed, ignore it
+ return;
+ }
+
+ for (Element layout : mRelativeLayouts) {
+ List<Element> children = getChildren(layout);
+ Set<String> ids = Sets.newHashSetWithExpectedSize(children.size());
+ for (Element child : children) {
+ String id = child.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (id != null && !id.isEmpty()) {
+ ids.add(id);
+ }
+ }
+
+ for (Element element : children) {
+ String selfId = stripIdPrefix(element.getAttributeNS(ANDROID_URI, ATTR_ID));
+
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr) attributes.item(i);
+ String value = attr.getValue();
+ if ((value.startsWith(NEW_ID_PREFIX) ||
+ value.startsWith(ID_PREFIX))
+ && ANDROID_URI.equals(attr.getNamespaceURI())
+ && attr.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
+ if (!idDefined(mFileIds, value)) {
+ // Stash a reference to this id and location such that
+ // we can check after the *whole* layout has been processed,
+ // since it's too early to conclude here that the id does
+ // not exist (you are allowed to have forward references)
+ XmlContext xmlContext = (XmlContext) context;
+ Handle handle = xmlContext.createLocationHandle(attr);
+ handle.setClientData(attr);
+
+ if (mHandles == null) {
+ mHandles = new ArrayList<Pair<String,Handle>>();
+ }
+ mHandles.add(Pair.of(value, handle));
+ } else {
+ // Check siblings. TODO: Look for cycles!
+ if (ids.contains(value)) {
+ // Make sure it's not pointing to self
+ if (!ATTR_ID.equals(attr.getLocalName())
+ && !selfId.isEmpty()
+ && value.endsWith(selfId)
+ && stripIdPrefix(value).equals(selfId)) {
+ XmlContext xmlContext = (XmlContext) context;
+ String message = String.format(
+ "Cannot be relative to self: id=%1$s, %2$s=%3$s",
+ selfId, attr.getLocalName(), selfId);
+ Location location = xmlContext.getLocation(attr);
+ xmlContext.report(NOT_SIBLING, attr, location, message);
+ }
+
+ continue;
+ }
+ if (value.startsWith(NEW_ID_PREFIX)) {
+ if (ids.contains(ID_PREFIX + stripIdPrefix(value))) {
+ continue;
+ }
+ } else {
+ assert value.startsWith(ID_PREFIX) : value;
+ if (ids.contains(NEW_ID_PREFIX + stripIdPrefix(value))) {
+ continue;
+ }
+ }
+ if (context.isEnabled(NOT_SIBLING)) {
+ XmlContext xmlContext = (XmlContext) context;
+ String message = String.format(
+ "`%1$s` is not a sibling in the same `RelativeLayout`",
+ value);
+ Location location = xmlContext.getLocation(attr);
+ xmlContext.report(NOT_SIBLING, attr, location, message);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mFileIds = null;
+
+ if (!context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+ checkHandles(context);
+ }
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
+ checkHandles(context);
+ }
+ }
+
+ private void checkHandles(@NonNull Context context) {
+ if (mHandles != null) {
+ boolean checkSameLayout = context.isEnabled(UNKNOWN_ID_LAYOUT);
+ boolean checkExists = context.isEnabled(UNKNOWN_ID);
+ boolean projectScope = context.getScope().contains(Scope.ALL_RESOURCE_FILES);
+ for (Pair<String, Handle> pair : mHandles) {
+ String id = pair.getFirst();
+ boolean isBound = projectScope ? idDefined(mGlobalIds, id) :
+ idDefined(context, id, context.file);
+ LintClient client = context.getClient();
+ if (!isBound && checkExists
+ && (projectScope || client.supportsProjectResources())) {
+ Handle handle = pair.getSecond();
+ boolean isDeclared = idDefined(mDeclaredIds, id);
+ id = stripIdPrefix(id);
+ String suggestionMessage;
+ Set<String> spellingDictionary = mGlobalIds;
+ if (!projectScope && client.supportsProjectResources()) {
+ AbstractResourceRepository resources =
+ client.getProjectResources(context.getProject(), true);
+ if (resources != null) {
+ spellingDictionary = Sets.newHashSet(
+ resources.getItemsOfType(ResourceType.ID));
+ spellingDictionary.remove(id);
+ }
+ }
+ List<String> suggestions = getSpellingSuggestions(id, spellingDictionary);
+ if (suggestions.size() > 1) {
+ suggestionMessage = String.format(" Did you mean one of {%2$s} ?",
+ id, Joiner.on(", ").join(suggestions));
+ } else if (!suggestions.isEmpty()) {
+ suggestionMessage = String.format(" Did you mean %2$s ?",
+ id, suggestions.get(0));
+ } else {
+ suggestionMessage = "";
+ }
+ String message;
+ if (isDeclared) {
+ message = String.format(
+ "The id \"`%1$s`\" is defined but not assigned to any views.%2$s",
+ id, suggestionMessage);
+ } else {
+ message = String.format(
+ "The id \"`%1$s`\" is not defined anywhere.%2$s",
+ id, suggestionMessage);
+ }
+ report(context, UNKNOWN_ID, handle, message);
+ } else if (checkSameLayout && (!projectScope || isBound)
+ && id.startsWith(NEW_ID_PREFIX)) {
+ // The id was defined, but in a different layout. Usually not intentional
+ // (might be referring to a random other view that happens to have the same
+ // name.)
+ Handle handle = pair.getSecond();
+ report(context, UNKNOWN_ID_LAYOUT, handle,
+ String.format(
+ "The id \"`%1$s`\" is not referring to any views in this layout",
+ stripIdPrefix(id)));
+ }
+ }
+ }
+ }
+
+ private static void report(Context context, Issue issue, Handle handle, String message) {
+ Location location = handle.resolve();
+ Object clientData = handle.getClientData();
+ if (clientData instanceof Node) {
+ if (context.getDriver().isSuppressed(null, issue, (Node) clientData)) {
+ return;
+ }
+ }
+
+ context.report(issue, location, message);
+ }
+
+ @Override
+ public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+ String tagName = element.getTagName();
+ if (tagName.equals(TAG_ITEM)) {
+ String type = element.getAttribute(ATTR_TYPE);
+ if (VALUE_ID.equals(type)) {
+ String name = element.getAttribute(ATTR_NAME);
+ if (!name.isEmpty()) {
+ if (mDeclaredIds == null) {
+ mDeclaredIds = Sets.newHashSet();
+ }
+ mDeclaredIds.add(ID_PREFIX + name);
+ }
+ }
+ } else {
+ assert tagName.equals(RELATIVE_LAYOUT) || tagName.equals(
+ PERCENT_RELATIVE_LAYOUT);
+ if (mRelativeLayouts == null) {
+ mRelativeLayouts = new ArrayList<Element>();
+ }
+ mRelativeLayouts.add(element);
+ }
+ }
+
+ @Override
+ public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
+ assert attribute.getName().equals(ATTR_ID) || attribute.getLocalName().equals(ATTR_ID);
+ String id = attribute.getValue();
+ mFileIds.add(id);
+ mGlobalIds.add(id);
+
+ if (id.equals(NEW_ID_PREFIX) || id.equals(ID_PREFIX) || "@+id".equals(ID_PREFIX)) {
+ String message = "Invalid id: missing value";
+ context.report(INVALID, attribute, context.getLocation(attribute), message);
+ } else if (id.startsWith("@+") && !id.startsWith(NEW_ID_PREFIX) //$NON-NLS-1$
+ && !id.startsWith("@+android:id/") //$NON-NLS-1$
+ || id.startsWith(NEW_ID_PREFIX)
+ && id.indexOf('/', NEW_ID_PREFIX.length()) != -1) {
+ int nameStart = id.startsWith(NEW_ID_PREFIX) ? NEW_ID_PREFIX.length() : 2;
+ String suggested = NEW_ID_PREFIX + id.substring(nameStart).replace('/', '_');
+ String message = String.format(
+ "ID definitions *must* be of the form `@+id/name`; try using `%1$s`", suggested);
+ context.report(INVALID, attribute, context.getLocation(attribute), message);
+ }
+ }
+
+ private static boolean idDefined(Set<String> ids, String id) {
+ if (ids == null) {
+ return false;
+ }
+ boolean definedLocally = ids.contains(id);
+ if (!definedLocally) {
+ if (id.startsWith(NEW_ID_PREFIX)) {
+ definedLocally = ids.contains(ID_PREFIX +
+ id.substring(NEW_ID_PREFIX.length()));
+ } else if (id.startsWith(ID_PREFIX)) {
+ definedLocally = ids.contains(NEW_ID_PREFIX +
+ id.substring(ID_PREFIX.length()));
+ }
+ }
+
+ return definedLocally;
+ }
+
+ private boolean idDefined(@NonNull Context context, @NonNull String id,
+ @Nullable File notIn) {
+ AbstractResourceRepository resources =
+ context.getClient().getProjectResources(context.getProject(), true);
+ if (resources != null) {
+ List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
+ stripIdPrefix(id));
+ if (items == null || items.isEmpty()) {
+ return false;
+ }
+ for (ResourceItem item : items) {
+ ResourceFile source = item.getSource();
+ if (source != null) {
+ File file = source.getFile();
+ if (file.getParentFile().getName().startsWith(FD_RES_VALUES)) {
+ if (mDeclaredIds == null) {
+ mDeclaredIds = Sets.newHashSet();
+ }
+ mDeclaredIds.add(id);
+ continue;
+ }
+
+ // Ignore definitions in the given file. This is used to ignore
+ // matches in the same file as the reference, since the reference
+ // is often expressed as a definition
+ if (!isSameResourceFile(file, notIn)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static List<String> getSpellingSuggestions(String id, Collection<String> ids) {
+ int maxDistance = id.length() >= 4 ? 2 : 1;
+
+ // Look for typos and try to match with custom views and android views
+ Multimap<Integer, String> matches = ArrayListMultimap.create(2, 10);
+ int count = 0;
+ if (!ids.isEmpty()) {
+ for (String matchWith : ids) {
+ matchWith = stripIdPrefix(matchWith);
+ if (Math.abs(id.length() - matchWith.length()) > maxDistance) {
+ // The string lengths differ more than the allowed edit distance;
+ // no point in even attempting to compute the edit distance (requires
+ // O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
+ continue;
+ }
+ int distance = editDistance(id, matchWith);
+ if (distance <= maxDistance) {
+ matches.put(distance, matchWith);
+ }
+
+ if (count++ > 100) {
+ // Make sure that for huge projects we don't completely grind to a halt
+ break;
+ }
+ }
+ }
+
+ for (int i = 0; i < maxDistance; i++) {
+ Collection<String> strings = matches.get(i);
+ if (strings != null && !strings.isEmpty()) {
+ List<String> suggestions = new ArrayList<String>(strings);
+ Collections.sort(suggestions);
+ return suggestions;
+ }
+ }
+
+ return Collections.emptyList();
+ }
+}
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java
diff --git a/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
similarity index 100%
rename from base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
rename to lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/api-versions-support-library.xml b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/api-versions-support-library.xml
new file mode 100644
index 0000000..faa8970
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/api-versions-support-library.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<!-- This file is generated by ApiLookupTest#generateSupportLibraryFile() -->
+<api version="2">
+ <class name="android/support/design/internal/ForegroundLinearLayout" since="7">
+ <extends name="android/support/v7/widget/LinearLayoutCompat" />
+ <method name="drawableHotspotChanged(FF)V" since="7" />
+ <method name="getForeground()Landroid/graphics/drawable/Drawable;" since="7" />
+ <method name="getForegroundGravity()I" since="7" />
+ <method name="jumpDrawablesToCurrentState()V" since="7" />
+ <method name="setForeground(Landroid/graphics/drawable/Drawable;)V" since="7" />
+ <method name="setForegroundGravity(I)V" since="7" />
+ </class>
+ <class name="android/support/design/widget/CoordinatorLayout" since="7">
+ <extends name="android/view/ViewGroup" />
+ <method name="getNestedScrollAxes()I" since="7" />
+ <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="7" />
+ <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="7" />
+ <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="7" />
+ <method name="onNestedScroll(Landroid/view/View;IIII)V" since="7" />
+ <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="7" />
+ <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="7" />
+ <method name="onStopNestedScroll(Landroid/view/View;)V" since="7" />
+ </class>
+ <class name="android/support/design/widget/FloatingActionButton" since="7">
+ <extends name="android/widget/ImageButton" />
+ <method name="getBackgroundTintList()Landroid/content/res/ColorStateList;" since="7" />
+ <method name="getBackgroundTintMode()Landroid/graphics/PorterDuff$Mode;" since="7" />
+ <method name="jumpDrawablesToCurrentState()V" since="7" />
+ <method name="setBackgroundTintList(Landroid/content/res/ColorStateList;)V" since="7" />
+ <method name="setBackgroundTintMode(Landroid/graphics/PorterDuff$Mode;)V" since="7" />
+ </class>
+ <class name="android/support/design/widget/TextInputLayout" since="7">
+ <extends name="android/widget/LinearLayout" />
+ <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" since="7" />
+ </class>
+ <class name="android/support/v17/leanback/transition/FadeAndShortSlide" since="17">
+ <extends name="android/transition/Visibility" />
+ <method name="<init>()V" since="17" />
+ <method name="addListener(Landroid/transition/Transition$TransitionListener;)Landroid/transition/Transition;" since="17" />
+ <method name="captureEndValues(Landroid/transition/TransitionValues;)V" since="17" />
+ <method name="captureStartValues(Landroid/transition/TransitionValues;)V" since="17" />
+ <method name="clone()Landroid/transition/Transition;" since="17" />
+ <method name="clone()Ljava/lang/Object;" since="17" />
+ <method name="onAppear(Landroid/view/ViewGroup;Landroid/view/View;Landroid/transition/TransitionValues;Landroid/transition/TransitionValues;)Landroid/animation/Animator;" since="17" />
+ <method name="onDisappear(Landroid/view/ViewGroup;Landroid/view/View;Landroid/transition/TransitionValues;Landroid/transition/TransitionValues;)Landroid/animation/Animator;" since="17" />
+ <method name="removeListener(Landroid/transition/Transition$TransitionListener;)Landroid/transition/Transition;" since="17" />
+ <method name="setEpicenterCallback(Landroid/transition/Transition$EpicenterCallback;)V" since="17" />
+ </class>
+ <class name="android/support/v17/leanback/transition/SlideNoPropagation" since="17">
+ <extends name="android/transition/Slide" />
+ <method name="<init>()V" since="17" />
+ <method name="<init>(I)V" since="17" />
+ <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;)V" since="17" />
+ <method name="setSlideEdge(I)V" since="17" />
+ </class>
+ <class name="android/support/v4/view/ViewPager" since="4">
+ <extends name="android/view/ViewGroup" />
+ <method name="canScrollHorizontally(I)Z" since="4" />
+ </class>
+ <class name="android/support/v4/widget/DrawerLayout" since="4">
+ <extends name="android/view/ViewGroup" />
+ <method name="onRtlPropertiesChanged(I)V" since="4" />
+ </class>
+ <class name="android/support/v4/widget/NestedScrollView" since="4">
+ <extends name="android/widget/FrameLayout" />
+ <method name="dispatchNestedFling(FFZ)Z" since="4" />
+ <method name="dispatchNestedPreFling(FF)Z" since="4" />
+ <method name="dispatchNestedPreScroll(II[I[I)Z" since="4" />
+ <method name="dispatchNestedScroll(IIII[I)Z" since="4" />
+ <method name="getNestedScrollAxes()I" since="4" />
+ <method name="hasNestedScrollingParent()Z" since="4" />
+ <method name="isNestedScrollingEnabled()Z" since="4" />
+ <method name="onGenericMotionEvent(Landroid/view/MotionEvent;)Z" since="4" />
+ <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="4" />
+ <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="4" />
+ <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="4" />
+ <method name="onNestedScroll(Landroid/view/View;IIII)V" since="4" />
+ <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="4" />
+ <method name="onOverScrolled(IIZZ)V" since="4" />
+ <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="4" />
+ <method name="onStopNestedScroll(Landroid/view/View;)V" since="4" />
+ <method name="setNestedScrollingEnabled(Z)V" since="4" />
+ <method name="shouldDelayChildPressedState()Z" since="4" />
+ <method name="startNestedScroll(I)Z" since="4" />
+ <method name="stopNestedScroll()V" since="4" />
+ </class>
+ <class name="android/support/v4/widget/SwipeRefreshLayout" since="4">
+ <extends name="android/view/ViewGroup" />
+ <method name="dispatchNestedFling(FFZ)Z" since="4" />
+ <method name="dispatchNestedPreFling(FF)Z" since="4" />
+ <method name="dispatchNestedPreScroll(II[I[I)Z" since="4" />
+ <method name="dispatchNestedScroll(IIII[I)Z" since="4" />
+ <method name="getNestedScrollAxes()I" since="4" />
+ <method name="hasNestedScrollingParent()Z" since="4" />
+ <method name="isNestedScrollingEnabled()Z" since="4" />
+ <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="4" />
+ <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="4" />
+ <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="4" />
+ <method name="onNestedScroll(Landroid/view/View;IIII)V" since="4" />
+ <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="4" />
+ <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="4" />
+ <method name="onStopNestedScroll(Landroid/view/View;)V" since="4" />
+ <method name="setNestedScrollingEnabled(Z)V" since="4" />
+ <method name="startNestedScroll(I)Z" since="4" />
+ <method name="stopNestedScroll()V" since="4" />
+ </class>
+ <class name="android/support/v7/app/AppCompatDialog" since="7">
+ <extends name="android/app/Dialog" />
+ <method name="invalidateOptionsMenu()V" since="7" />
+ </class>
+ <class name="android/support/v7/app/MediaRouteButton" since="7">
+ <extends name="android/view/View" />
+ <method name="jumpDrawablesToCurrentState()V" since="7" />
+ </class>
+ <class name="android/support/v7/graphics/drawable/DrawableWrapper" since="7">
+ <extends name="android/graphics/drawable/Drawable" />
+ <method name="isAutoMirrored()Z" since="7" />
+ <method name="jumpToCurrentState()V" since="7" />
+ <method name="setAutoMirrored(Z)V" since="7" />
+ <method name="setHotspot(FF)V" since="7" />
+ <method name="setHotspotBounds(IIII)V" since="7" />
+ <method name="setTint(I)V" since="7" />
+ <method name="setTintList(Landroid/content/res/ColorStateList;)V" since="7" />
+ <method name="setTintMode(Landroid/graphics/PorterDuff$Mode;)V" since="7" />
+ </class>
+ <class name="android/support/v7/internal/widget/PreferenceImageView" since="7">
+ <extends name="android/widget/ImageView" />
+ <method name="getMaxHeight()I" since="7" />
+ <method name="getMaxWidth()I" since="7" />
+ </class>
+ <class name="android/support/v7/view/SupportActionModeWrapper" since="7">
+ <extends name="android/view/ActionMode" />
+ <method name="finish()V" since="7" />
+ <method name="getCustomView()Landroid/view/View;" since="7" />
+ <method name="getMenu()Landroid/view/Menu;" since="7" />
+ <method name="getMenuInflater()Landroid/view/MenuInflater;" since="7" />
+ <method name="getSubtitle()Ljava/lang/CharSequence;" since="7" />
+ <method name="getTag()Ljava/lang/Object;" since="7" />
+ <method name="getTitle()Ljava/lang/CharSequence;" since="7" />
+ <method name="getTitleOptionalHint()Z" since="7" />
+ <method name="invalidate()V" since="7" />
+ <method name="isTitleOptional()Z" since="7" />
+ <method name="setCustomView(Landroid/view/View;)V" since="7" />
+ <method name="setSubtitle(I)V" since="7" />
+ <method name="setSubtitle(Ljava/lang/CharSequence;)V" since="7" />
+ <method name="setTag(Ljava/lang/Object;)V" since="7" />
+ <method name="setTitle(I)V" since="7" />
+ <method name="setTitle(Ljava/lang/CharSequence;)V" since="7" />
+ <method name="setTitleOptionalHint(Z)V" since="7" />
+ </class>
+ <class name="android/support/v7/view/menu/ActionMenuItemView" since="7">
+ <extends name="android/support/v7/widget/AppCompatTextView" />
+ <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+ </class>
+ <class name="android/support/v7/view/menu/ListMenuItemView" since="7">
+ <extends name="android/widget/LinearLayout" />
+ <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/ActionBarContainer" since="7">
+ <extends name="android/widget/FrameLayout" />
+ <method name="jumpDrawablesToCurrentState()V" since="7" />
+ <method name="startActionModeForChild(Landroid/view/View;Landroid/view/ActionMode$Callback;)Landroid/view/ActionMode;" since="7" />
+ </class>
+ <class name="android/support/v7/widget/ActionBarContextView" since="7">
+ <extends name="android/support/v7/widget/AbsActionBarView" />
+ <method name="onHoverEvent(Landroid/view/MotionEvent;)Z" since="7" />
+ <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+ <method name="shouldDelayChildPressedState()Z" since="7" />
+ </class>
+ <class name="android/support/v7/widget/ActionBarOverlayLayout" since="7">
+ <extends name="android/view/ViewGroup" />
+ <method name="getNestedScrollAxes()I" since="7" />
+ <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+ <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="7" />
+ <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="7" />
+ <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="7" />
+ <method name="onNestedScroll(Landroid/view/View;IIII)V" since="7" />
+ <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="7" />
+ <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="7" />
+ <method name="onStopNestedScroll(Landroid/view/View;)V" since="7" />
+ <method name="onWindowSystemUiVisibilityChanged(I)V" since="7" />
+ <method name="shouldDelayChildPressedState()Z" since="7" />
+ </class>
+ <class name="android/support/v7/widget/ActionMenuView" since="7">
+ <extends name="android/support/v7/widget/LinearLayoutCompat" />
+ <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/AppCompatButton" since="7">
+ <extends name="android/widget/Button" />
+ <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+ <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/AppCompatPopupWindow" since="7">
+ <extends name="android/widget/PopupWindow" />
+ <method name="showAsDropDown(Landroid/view/View;III)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/AppCompatSpinner" since="7">
+ <extends name="android/widget/Spinner" />
+ <method name="<init>(Landroid/content/Context;I)V" since="7" />
+ <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;II)V" since="7" />
+ <method name="getDropDownHorizontalOffset()I" since="7" />
+ <method name="getDropDownVerticalOffset()I" since="7" />
+ <method name="getDropDownWidth()I" since="7" />
+ <method name="getPopupBackground()Landroid/graphics/drawable/Drawable;" since="7" />
+ <method name="getPopupContext()Landroid/content/Context;" since="7" />
+ <method name="setDropDownHorizontalOffset(I)V" since="7" />
+ <method name="setDropDownVerticalOffset(I)V" since="7" />
+ <method name="setDropDownWidth(I)V" since="7" />
+ <method name="setPopupBackgroundDrawable(Landroid/graphics/drawable/Drawable;)V" since="7" />
+ <method name="setPopupBackgroundResource(I)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/CardView" since="7">
+ <extends name="android/widget/FrameLayout" />
+ <method name="setPaddingRelative(IIII)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/LinearLayoutCompat" since="7">
+ <extends name="android/view/ViewGroup" />
+ <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+ <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" />
+ <method name="shouldDelayChildPressedState()Z" since="7" />
+ </class>
+ <class name="android/support/v7/widget/RecyclerView" since="7">
+ <extends name="android/view/ViewGroup" />
+ <method name="dispatchNestedFling(FFZ)Z" since="7" />
+ <method name="dispatchNestedPreFling(FF)Z" since="7" />
+ <method name="dispatchNestedPreScroll(II[I[I)Z" since="7" />
+ <method name="dispatchNestedScroll(IIII[I)Z" since="7" />
+ <method name="hasNestedScrollingParent()Z" since="7" />
+ <method name="isAttachedToWindow()Z" since="7" />
+ <method name="isNestedScrollingEnabled()Z" since="7" />
+ <method name="onGenericMotionEvent(Landroid/view/MotionEvent;)Z" since="7" />
+ <method name="setNestedScrollingEnabled(Z)V" since="7" />
+ <method name="startNestedScroll(I)Z" since="7" />
+ <method name="stopNestedScroll()V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/ScrollingTabContainerView" since="7">
+ <extends name="android/widget/HorizontalScrollView" />
+ <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/SwitchCompat" since="7">
+ <extends name="android/widget/CompoundButton" />
+ <method name="drawableHotspotChanged(FF)V" since="7" />
+ <method name="jumpDrawablesToCurrentState()V" since="7" />
+ <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+ <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" />
+ <method name="onPopulateAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" />
+ </class>
+ <class name="android/support/v7/widget/Toolbar" since="7">
+ <extends name="android/view/ViewGroup" />
+ <method name="onHoverEvent(Landroid/view/MotionEvent;)Z" since="7" />
+ <method name="onRtlPropertiesChanged(I)V" since="7" />
+ </class>
+ <!-- Referenced Super Classes -->
+ <class name="android/support/v7/widget/AbsActionBarView" since="7">
+ <extends name="android/view/ViewGroup" />
+ </class>
+ <class name="android/support/v7/widget/AppCompatTextView" since="7">
+ <extends name="android/widget/TextView" />
+ </class>
+</api>
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/NOTICE b/lint/libs/lint-tests/NOTICE
similarity index 100%
rename from base/lint/libs/lint-tests/NOTICE
rename to lint/libs/lint-tests/NOTICE
diff --git a/base/lint/libs/lint-tests/build.gradle b/lint/libs/lint-tests/build.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/build.gradle
rename to lint/libs/lint-tests/build.gradle
diff --git a/lint/libs/lint-tests/lint-tests.iml b/lint/libs/lint-tests/lint-tests.iml
new file mode 100644
index 0000000..be909e6
--- /dev/null
+++ b/lint/libs/lint-tests/lint-tests.iml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ <excludeFolder url="file://$MODULE_DIR$/src/test/.settings" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="module" module-name="common" exported="" />
+ <orderEntry type="module" module-name="integration-test" />
+ <orderEntry type="module" module-name="lint-checks-base" exported="" />
+ <orderEntry type="module" module-name="lint-cli" exported="" />
+ <orderEntry type="module" module-name="testutils" exported="" />
+ <orderEntry type="module" module-name="lint-api-base" exported="" />
+ <orderEntry type="library" exported="" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="layoutlib-api-base" exported="" />
+ <orderEntry type="library" exported="" name="intellij-annotations" level="project" />
+ <orderEntry type="library" scope="TEST" name="groovy" level="project" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="library" name="ecj" level="project" />
+ <orderEntry type="module" module-name="sdk-common-base" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java b/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java
new file mode 100644
index 0000000..3087c76
--- /dev/null
+++ b/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java
@@ -0,0 +1,1363 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks.infrastructure;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.utils.SdkUtils.escapePropertyValue;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.res2.DuplicateDataException;
+import com.android.ide.common.res2.MergingException;
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.ide.common.res2.ResourceMerger;
+import com.android.ide.common.res2.ResourceRepository;
+import com.android.ide.common.res2.ResourceSet;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.sdklib.IAndroidTarget;
+import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.EcjParser;
+import com.android.tools.lint.ExternalAnnotationRepository;
+import com.android.tools.lint.LintCliClient;
+import com.android.tools.lint.LintCliFlags;
+import com.android.tools.lint.Reporter;
+import com.android.tools.lint.TextReporter;
+import com.android.tools.lint.Warning;
+import com.android.tools.lint.checks.BuiltinIssueRegistry;
+import com.android.tools.lint.client.api.Configuration;
+import com.android.tools.lint.client.api.DefaultConfiguration;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaVisitor;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.utils.FileUtils;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.android.utils.StdLogger;
+import com.android.utils.XmlUtils;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.intellij.lang.annotations.Language;
+import org.objectweb.asm.Opcodes;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+
+import javax.xml.bind.DatatypeConverter;
+
+/**
+ * Test case for lint detectors.
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+ at Beta
+ at SuppressWarnings("javadoc")
+public abstract class LintDetectorTest extends SdkTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ BuiltinIssueRegistry.reset();
+ JavaVisitor.clearCrashCount();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ List<Issue> issues;
+ try {
+ // Some detectors extend LintDetectorTest but don't actually
+ // provide issues and assert instead; gracefully ignore those
+ // here
+ issues = getIssues();
+ } catch (Throwable t) {
+ issues = Collections.emptyList();
+ }
+ for (Issue issue : issues) {
+ if (issue.getImplementation().getScope().contains(Scope.JAVA_FILE)) {
+ assertEquals(0, JavaVisitor.getCrashCount());
+ break;
+ }
+ }
+ }
+
+ protected abstract Detector getDetector();
+
+ private Detector mDetector;
+
+ protected final Detector getDetectorInstance() {
+ if (mDetector == null) {
+ mDetector = getDetector();
+ }
+
+ return mDetector;
+ }
+
+ protected boolean allowCompilationErrors() {
+ return false;
+ }
+
+ protected abstract List<Issue> getIssues();
+
+ public class CustomIssueRegistry extends IssueRegistry {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return LintDetectorTest.this.getIssues();
+ }
+ }
+
+ protected String lintFiles(String... relativePaths) throws Exception {
+ List<File> files = new ArrayList<File>();
+ File targetDir = getTargetDir();
+ for (String relativePath : relativePaths) {
+ File file = getTestfile(targetDir, relativePath);
+ assertNotNull(file);
+ files.add(file);
+ }
+
+ Collections.sort(files, new Comparator<File>() {
+ @Override
+ public int compare(File file1, File file2) {
+ ResourceFolderType folder1 = ResourceFolderType.getFolderType(
+ file1.getParentFile().getName());
+ ResourceFolderType folder2 = ResourceFolderType.getFolderType(
+ file2.getParentFile().getName());
+ if (folder1 != null && folder2 != null && folder1 != folder2) {
+ return folder1.compareTo(folder2);
+ }
+ return file1.compareTo(file2);
+ }
+ });
+
+ addManifestFile(targetDir);
+
+ return checkLint(files);
+ }
+
+ protected String checkLint(List<File> files) throws Exception {
+ TestLintClient lintClient = createClient();
+ return checkLint(lintClient, files);
+ }
+
+ protected String checkLint(TestLintClient lintClient, List<File> files) throws Exception {
+ if (System.getenv("ANDROID_BUILD_TOP") != null) {
+ fail("Don't run the lint tests with $ANDROID_BUILD_TOP set; that enables lint's "
+ + "special support for detecting AOSP projects (looking for .class "
+ + "files in $ANDROID_HOST_OUT etc), and this confuses lint.");
+ }
+
+ mOutput = new StringBuilder();
+ String result = lintClient.analyze(files);
+
+ // The output typically contains a few directory/filenames.
+ // On Windows we need to change the separators to the unix-style
+ // forward slash to make the test as OS-agnostic as possible.
+ if (File.separatorChar != '/') {
+ result = result.replace(File.separatorChar, '/');
+ }
+
+ for (File f : files) {
+ deleteFile(f);
+ }
+
+ return result;
+ }
+
+ protected void checkReportedError(
+ @NonNull Context context,
+ @NonNull Issue issue,
+ @NonNull Severity severity,
+ @Nullable Location location,
+ @NonNull String message) {
+ }
+
+ protected TestLintClient createClient() {
+ return new TestLintClient();
+ }
+
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null);
+ }
+
+ protected void configureDriver(LintDriver driver) {
+ }
+
+ /**
+ * Run lint on the given files when constructed as a separate project
+ *
+ * @return The output of the lint check. On Windows, this transforms all directory separators to
+ * the unix-style forward slash.
+ */
+ protected String lintProject(String... relativePaths) throws Exception {
+ File projectDir = getProjectDir(null, relativePaths);
+ return checkLint(Collections.singletonList(projectDir));
+ }
+
+ protected String lintProjectIncrementally(String currentFile, String... relativePaths)
+ throws Exception {
+ File projectDir = getProjectDir(null, relativePaths);
+ File current = new File(projectDir, currentFile.replace('/', File.separatorChar));
+ assertTrue(current.exists());
+ TestLintClient client = createClient();
+ client.setIncremental(current);
+ return checkLint(client, Collections.singletonList(projectDir));
+ }
+
+ protected String lintProjectIncrementally(String currentFile, TestFile... files)
+ throws Exception {
+ File projectDir = getProjectDir(null, files);
+ File current = new File(projectDir, currentFile.replace('/', File.separatorChar));
+ assertTrue(current.exists());
+ TestLintClient client = createClient();
+ client.setIncremental(current);
+ return checkLint(client, Collections.singletonList(projectDir));
+ }
+
+ /**
+ * Run lint on the given files when constructed as a separate project
+ * @return The output of the lint check. On Windows, this transforms all directory
+ * separators to the unix-style forward slash.
+ */
+ protected String lintProject(TestFile... files) throws Exception {
+ File projectDir = getProjectDir(null, files);
+ return checkLint(Collections.singletonList(projectDir));
+ }
+
+ @Override
+ protected File getTargetDir() {
+ File targetDir = new File(getTempDir(), getClass().getSimpleName() + "_" + getName());
+ addCleanupDir(targetDir);
+ return targetDir;
+ }
+
+ @NonNull
+ public TestFile file() {
+ return new TestFile();
+ }
+
+ @NonNull
+ public TestFile source(@NonNull String to, @NonNull String source) {
+ return file().to(to).withSource(source);
+ }
+
+ @NonNull
+ public TestFile java(@NonNull String to, @NonNull @Language("JAVA") String source) {
+ return file().to(to).withSource(source);
+ }
+
+ private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+(.*)\\s*;");
+ private static final Pattern CLASS_PATTERN = Pattern
+ .compile("\\s*(\\S+)\\s*(extends.*)?(implements.*)?\\{");
+
+ @NonNull
+ public TestFile java(@NonNull @Language("JAVA") String source) {
+ // Figure out the "to" path: the package plus class name + java in the src/ folder
+ Matcher matcher = PACKAGE_PATTERN.matcher(source);
+ assertTrue("Couldn't find package declaration in source", matcher.find());
+ String pkg = matcher.group(1).trim();
+ matcher = CLASS_PATTERN.matcher(source);
+ assertTrue("Couldn't find class declaration in source", matcher.find());
+ String cls = matcher.group(1).trim();
+ String to = "src/" + pkg.replace('.', '/') + '/' + cls + DOT_JAVA;
+
+ return file().to(to).withSource(source);
+ }
+
+ @NonNull
+ public TestFile xml(@NonNull String to, @NonNull @Language("XML") String source) {
+ return file().to(to).withSource(source);
+ }
+
+ @NonNull
+ public TestFile copy(@NonNull String from, @NonNull String to) {
+ return file().from(from).to(to);
+ }
+
+ @NonNull
+ public ManifestTestFile manifest() {
+ return new ManifestTestFile();
+ }
+
+ public class ManifestTestFile extends TestFile {
+ public String pkg = "test.pkg";
+ public int minSdk;
+ public int targetSdk;
+ public String[] permissions;
+
+ public ManifestTestFile() {
+ to(FN_ANDROID_MANIFEST_XML);
+ }
+
+ public ManifestTestFile minSdk(int min) {
+ minSdk = min;
+ return this;
+ }
+
+ public ManifestTestFile targetSdk(int target) {
+ targetSdk = target;
+ return this;
+ }
+
+ public ManifestTestFile permissions(String... permissions) {
+ this.permissions = permissions;
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getContents() {
+ if (contents == null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+ sb.append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n");
+ sb.append(" package=\"").append(pkg).append("\"\n");
+ sb.append(" android:versionCode=\"1\"\n");
+ sb.append(" android:versionName=\"1.0\" >\n");
+ if (minSdk > 0 || targetSdk > 0) {
+ sb.append(" <uses-sdk ");
+ if (minSdk > 0) {
+ sb.append(" android:minSdkVersion=\"").append(Integer.toString(minSdk))
+ .append("\"");
+ }
+ if (targetSdk > 0) {
+ sb.append(" android:targetSdkVersion=\"")
+ .append(Integer.toString(targetSdk))
+ .append("\"");
+ }
+ sb.append(" />\n");
+ }
+ if (permissions != null && permissions.length > 0) {
+ StringBuilder permissionBlock = new StringBuilder();
+ for (String permission : permissions) {
+ permissionBlock
+ .append(" <uses-permission android:name=\"")
+ .append(permission)
+ .append("\" />\n");
+ }
+ sb.append(permissionBlock);
+ sb.append("\n");
+ }
+
+ sb.append(""
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>");
+ contents = sb.toString();
+ }
+
+ return contents;
+ }
+
+ @NonNull
+ @Override
+ public File createFile(@NonNull File targetDir) throws IOException {
+ getContents(); // lazy init
+ return super.createFile(targetDir);
+ }
+ }
+
+ @NonNull
+ public PropertyTestFile projectProperties() {
+ return new PropertyTestFile();
+ }
+
+ public class PropertyTestFile extends TestFile {
+ @SuppressWarnings("StringBufferField")
+ private StringBuilder mStringBuilder = new StringBuilder();
+
+ private int mNextLibraryIndex = 1;
+
+ public PropertyTestFile() {
+ to("project.properties");
+ }
+
+ public PropertyTestFile property(String key, String value) {
+ String escapedValue = escapePropertyValue(value);
+ mStringBuilder.append(key).append('=').append(escapedValue).append('\n');
+ return this;
+ }
+
+ public PropertyTestFile compileSdk(int target) {
+ mStringBuilder.append("target=android-").append(Integer.toString(target)).append('\n');
+ return this;
+ }
+
+ public PropertyTestFile library(boolean isLibrary) {
+ mStringBuilder.append("android.library=").append(Boolean.toString(isLibrary))
+ .append('\n');
+ return this;
+ }
+
+ public PropertyTestFile dependsOn(String relative) {
+ assertTrue(relative.startsWith("../"));
+ mStringBuilder.append("android.library.reference.")
+ .append(Integer.toString(mNextLibraryIndex++))
+ .append("=").append(escapePropertyValue(relative))
+ .append('\n');
+ return this;
+ }
+
+ @Override
+ public TestFile withSource(@NonNull String source) {
+ fail("Don't call withSource on " + this.getClass());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getContents() {
+ contents = mStringBuilder.toString();
+ return contents;
+ }
+
+ @NonNull
+ @Override
+ public File createFile(@NonNull File targetDir) throws IOException {
+ getContents(); // lazy init
+ return super.createFile(targetDir);
+ }
+ }
+
+ /** Produces byte arrays, for use with {@link BinaryTestFile} */
+ public static class ByteProducer {
+ @SuppressWarnings("MethodMayBeStatic") // intended for override
+ @NonNull
+ public byte[] produce() {
+ return new byte[0];
+ }
+ }
+
+ public static class BytecodeProducer extends ByteProducer implements Opcodes {
+ /**
+ * Typically generated by first getting asm output like this:
+ * <pre>
+ * assertEquals("", asmify(new File("/full/path/to/my/file.class")));
+ * </pre>
+ * ...and when the test fails, the actual test output is the necessary assembly
+ *
+ */
+ @Override
+ @SuppressWarnings("MethodMayBeStatic") // intended for override
+ @NonNull
+ public byte[] produce() {
+ return new byte[0];
+ }
+ }
+
+ @NonNull
+ public BinaryTestFile bytecode(@NonNull String to, @NonNull BytecodeProducer producer) {
+ return new BinaryTestFile(to, producer);
+ }
+
+ public static String toBase64(@NonNull byte[] bytes) {
+ String base64 = DatatypeConverter.printBase64Binary(bytes);
+ return Joiner.on("").join(Splitter.fixedLength(60).split(base64));
+ }
+
+ public static String toBase64(@NonNull File file) throws IOException {
+ return toBase64(Files.toByteArray(file));
+ }
+
+ /**
+ * Creates a test file from the given base64 data. To create this data, use {@link
+ * #toBase64(File)} or {@link #toBase64(byte[])}, for example via {@code assertEquals("",
+ * uuencode(new File("path/to/your.class")));} </pre>
+ *
+ * @param to the file to write as
+ * @param encoded the encoded data
+ * @return the new test file
+ */
+ public BinaryTestFile base64(@NonNull String to, @NonNull String encoded) {
+ encoded = encoded.replaceAll("\n", "");
+ final byte[] bytes = DatatypeConverter.parseBase64Binary(encoded);
+ return new BinaryTestFile(to, new BytecodeProducer() {
+ @NonNull
+ @Override
+ public byte[] produce() {
+ return bytes;
+ }
+ });
+ }
+
+ public class BinaryTestFile extends TestFile {
+ private final BytecodeProducer mProducer;
+
+ public BinaryTestFile(@NonNull String to, @NonNull BytecodeProducer producer) {
+ to(to);
+ mProducer = producer;
+ }
+
+ @Override
+ public TestFile withSource(@NonNull String source) {
+ fail("Don't call withSource on " + this.getClass());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getContents() {
+ fail("Don't call getContents on binary " + this.getClass());
+ return null;
+ }
+
+ public byte[] getBinaryContents() {
+ return mProducer.produce();
+ }
+
+ @NonNull
+ @Override
+ public File createFile(@NonNull File targetDir) throws IOException {
+ int index = targetRelativePath.lastIndexOf('/');
+ String relative = null;
+ String name = targetRelativePath;
+ if (index != -1) {
+ name = targetRelativePath.substring(index + 1);
+ relative = targetRelativePath.substring(0, index);
+ }
+ InputStream stream = new ByteArrayInputStream(getBinaryContents());
+ return makeTestFile(targetDir, name, relative, stream);
+ }
+ }
+
+ @NonNull
+ public JarTestFile jar(@NonNull String to) {
+ return new JarTestFile(to);
+ }
+
+ @NonNull
+ public JarTestFile jar(@NonNull String to, @NonNull TestFile... files) {
+ JarTestFile jar = new JarTestFile(to);
+ jar.files(files);
+ return jar;
+ }
+
+ public class JarTestFile extends TestFile {
+ private List<TestFile> mFiles = Lists.newArrayList();
+ private Map<TestFile, String> mPath = Maps.newHashMap();
+
+ public JarTestFile(@NonNull String to) {
+ to(to);
+ }
+
+ public JarTestFile files(@NonNull TestFile... files) {
+ mFiles.addAll(Arrays.asList(files));
+ return this;
+ }
+
+ public JarTestFile add(@NonNull TestFile file, @NonNull String path) {
+ add(file);
+ mPath.put(file, path);
+ return this;
+ }
+
+ public JarTestFile add(@NonNull TestFile file) {
+ mFiles.add(file);
+ mPath.put(file, null);
+ return this;
+ }
+
+ @Override
+ public TestFile withSource(@NonNull String source) {
+ fail("Don't call withSource on " + this.getClass());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getContents() {
+ fail("Don't call getContents on binary " + this.getClass());
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public File createFile(@NonNull File targetDir) throws IOException {
+ int index = targetRelativePath.lastIndexOf('/');
+ String relative = null;
+ String name = targetRelativePath;
+ if (index != -1) {
+ name = targetRelativePath.substring(index + 1);
+ relative = targetRelativePath.substring(0, index);
+ }
+
+ File dir = targetDir;
+ if (relative != null) {
+ dir = new File(dir, relative);
+ if (!dir.exists()) {
+ boolean mkdir = dir.mkdirs();
+ assertTrue(dir.getPath(), mkdir);
+ }
+ } else if (!dir.exists()) {
+ boolean mkdir = dir.mkdirs();
+ assertTrue(dir.getPath(), mkdir);
+ }
+ File tempFile = new File(dir, name);
+ if (tempFile.exists()) {
+ boolean deleted = tempFile.delete();
+ assertTrue(tempFile.getPath(), deleted);
+ }
+
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ JarOutputStream jarOutputStream = new JarOutputStream(
+ new BufferedOutputStream(new FileOutputStream(tempFile)), manifest);
+
+ try {
+ for (TestFile file : mFiles) {
+ String path = mPath.get(file);
+ if (path == null) {
+ path = file.targetRelativePath;
+ }
+ jarOutputStream.putNextEntry(new ZipEntry(path));
+ if (file instanceof BinaryTestFile) {
+ byte[] bytes = ((BinaryTestFile)file).getBinaryContents();
+ assertNotNull(file.targetRelativePath, bytes);
+ ByteStreams.copy(new ByteArrayInputStream(bytes), jarOutputStream);
+ } else {
+ String contents = file.getContents();
+ assertNotNull(file.targetRelativePath, contents);
+ byte[] bytes = contents.getBytes(Charsets.UTF_8);
+ ByteStreams.copy(new ByteArrayInputStream(bytes), jarOutputStream);
+ }
+ jarOutputStream.closeEntry();
+ }
+ } finally {
+ jarOutputStream.close();
+ }
+
+ return tempFile;
+ }
+ }
+
+ @NonNull
+ public TestFile copy(@NonNull String from) {
+ return file().from(from).to(from);
+ }
+
+ /** Creates a project directory structure from the given files */
+ protected File getProjectDir(String name, String ...relativePaths) throws Exception {
+ assertFalse("getTargetDir must be overridden to make a unique directory",
+ getTargetDir().equals(getTempDir()));
+
+ List<TestFile> testFiles = Lists.newArrayList();
+ for (String relativePath : relativePaths) {
+ testFiles.add(file().copy(relativePath));
+ }
+ return getProjectDir(name, testFiles.toArray(new TestFile[testFiles.size()]));
+ }
+
+ /** Creates a project directory structure from the given files */
+ protected File getProjectDir(String name, TestFile... testFiles) throws Exception {
+ assertFalse("getTargetDir must be overridden to make a unique directory",
+ getTargetDir().equals(getTempDir()));
+
+ File projectDir = getTargetDir();
+ if (name != null) {
+ projectDir = new File(projectDir, name);
+ }
+ if (!projectDir.exists()) {
+ assertTrue(projectDir.getPath(), projectDir.mkdirs());
+ }
+
+ for (TestFile fp : testFiles) {
+ File file = fp.createFile(projectDir);
+ assertNotNull(file);
+ }
+
+ addManifestFile(projectDir);
+ return projectDir;
+ }
+
+ private static void addManifestFile(File projectDir) throws IOException {
+ // Ensure that there is at least a manifest file there to make it a valid project
+ // as far as Lint is concerned:
+ if (!new File(projectDir, "AndroidManifest.xml").exists()) {
+ File manifest = new File(projectDir, "AndroidManifest.xml");
+ FileWriter fw = new FileWriter(manifest);
+ fw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " package=\"foo.bar2\"\n" +
+ " android:versionCode=\"1\"\n" +
+ " android:versionName=\"1.0\" >\n" +
+ "</manifest>\n");
+ fw.close();
+ }
+ }
+
+ private StringBuilder mOutput = null;
+
+ @Override
+ protected InputStream getTestResource(String relativePath, boolean expectExists) {
+ String path = "data" + File.separator + relativePath; //$NON-NLS-1$
+ InputStream stream = getClass().getResourceAsStream(path);
+ if (!expectExists && stream == null) {
+ return null;
+ }
+ return stream;
+ }
+
+ protected boolean isEnabled(Issue issue) {
+ Class<? extends Detector> detectorClass = getDetectorInstance().getClass();
+ if (issue.getImplementation().getDetectorClass() == detectorClass) {
+ return true;
+ }
+
+ if (issue == IssueRegistry.LINT_ERROR) {
+ return !ignoreSystemErrors();
+ } else if (issue == IssueRegistry.PARSER_ERROR) {
+ return !allowCompilationErrors();
+ }
+
+ return false;
+ }
+
+ protected boolean includeParentPath() {
+ return false;
+ }
+
+ protected EnumSet<Scope> getLintScope(List<File> file) {
+ return null;
+ }
+
+ public String getSuperClass(Project project, String name) {
+ return null;
+ }
+
+ protected boolean ignoreSystemErrors() {
+ return true;
+ }
+
+ public class TestLintClient extends LintCliClient {
+ private StringWriter mWriter = new StringWriter();
+ private File mIncrementalCheck;
+
+ public TestLintClient() {
+ super(new LintCliFlags(), "test");
+ mFlags.getReporters().add(new TextReporter(this, mFlags, mWriter, false));
+ }
+
+ @Override
+ public String getSuperClass(@NonNull Project project, @NonNull String name) {
+ String superClass = LintDetectorTest.this.getSuperClass(project, name);
+ if (superClass != null) {
+ return superClass;
+ }
+
+ return super.getSuperClass(project, name);
+ }
+
+ public String analyze(List<File> files) throws Exception {
+ mDriver = new LintDriver(new CustomIssueRegistry(), this);
+ configureDriver(mDriver);
+ LintRequest request = new LintRequest(this, files);
+ if (mIncrementalCheck != null) {
+ assertEquals(1, files.size());
+ File projectDir = files.get(0);
+ assertTrue(isProjectDirectory(projectDir));
+ Project project = createProject(projectDir, projectDir);
+ project.addFile(mIncrementalCheck);
+ List<Project> projects = Collections.singletonList(project);
+ request.setProjects(projects);
+ }
+
+ mDriver.analyze(request.setScope(getLintScope(files)));
+
+ // Check compare contract
+ Warning prev = null;
+ for (Warning warning : mWarnings) {
+ if (prev != null) {
+ boolean equals = warning.equals(prev);
+ assertEquals(equals, prev.equals(warning));
+ int compare = warning.compareTo(prev);
+ assertEquals(equals, compare == 0);
+ assertEquals(-compare, prev.compareTo(warning));
+ }
+ prev = warning;
+ }
+
+ Collections.sort(mWarnings);
+
+ // Check compare contract & transitivity
+ Warning prev2 = prev;
+ prev = null;
+ for (Warning warning : mWarnings) {
+ if (prev != null && prev2 != null) {
+ assertTrue(warning.compareTo(prev) >= 0);
+ assertTrue(prev.compareTo(prev2) >= 0);
+ assertTrue(warning.compareTo(prev2) >= 0);
+
+ assertTrue(prev.compareTo(warning) <= 0);
+ assertTrue(prev2.compareTo(prev) <= 0);
+ assertTrue(prev2.compareTo(warning) <= 0);
+ }
+ prev2 = prev;
+ prev = warning;
+ }
+
+ for (Reporter reporter : mFlags.getReporters()) {
+ reporter.write(mErrorCount, mWarningCount, mWarnings);
+ }
+
+ mOutput.append(mWriter.toString());
+
+ if (mOutput.length() == 0) {
+ mOutput.append("No warnings.");
+ }
+
+ String result = mOutput.toString();
+ if (result.equals("No issues found.\n")) {
+ result = "No warnings.";
+ }
+
+ result = cleanup(result);
+
+ return result;
+ }
+
+ public String getErrors() throws Exception {
+ return mWriter.toString();
+ }
+
+ @Override
+ public JavaParser getJavaParser(@Nullable Project project) {
+ return new EcjParser(this, project) {
+ @Override
+ public void prepareJavaParse(@NonNull List<JavaContext> contexts) {
+ super.prepareJavaParse(contexts);
+ if (!allowCompilationErrors() && mEcjResult != null) {
+ StringBuilder sb = new StringBuilder();
+ for (CompilationUnitDeclaration unit : mEcjResult.getCompilationUnits()) {
+ // so maybe I don't need my map!!
+ CategorizedProblem[] problems = unit.compilationResult()
+ .getAllProblems();
+ if (problems != null) {
+ for (IProblem problem : problems) {
+ if (problem == null || !problem.isError()) {
+ continue;
+ }
+ String filename = new File(new String(
+ problem.getOriginatingFileName())).getName();
+ sb.append(filename)
+ .append(":")
+ .append(problem.isError() ? "Error" : "Warning")
+ .append(": ").append(problem.getSourceLineNumber())
+ .append(": ").append(problem.getMessage())
+ .append('\n');
+ }
+ }
+ }
+ if (sb.length() > 0) {
+ fail("Found compilation problems in lint test not overriding "
+ + "allowCompilationErrors():\n" + sb);
+ }
+
+ }
+ }
+ };
+ }
+
+ @Override
+ public void report(
+ @NonNull Context context,
+ @NonNull Issue issue,
+ @NonNull Severity severity,
+ @Nullable Location location,
+ @NonNull String message,
+ @NonNull TextFormat format) {
+ if (ignoreSystemErrors() && (issue == IssueRegistry.LINT_ERROR)) {
+ return;
+ }
+
+ // Use plain ascii in the test golden files for now. (This also ensures
+ // that the markup is well-formed, e.g. if we have a ` without a matching
+ // closing `, the ` would show up in the plain text.)
+ message = format.convertTo(message, TextFormat.TEXT);
+
+ checkReportedError(context, issue, severity, location, message);
+
+ if (severity == Severity.FATAL) {
+ // Treat fatal errors like errors in the golden files.
+ severity = Severity.ERROR;
+ }
+
+ // For messages into all secondary locations to ensure they get
+ // specifically included in the text report
+ if (location != null && location.getSecondary() != null) {
+ Location l = location.getSecondary();
+ if (l == location) {
+ fail("Location link cycle");
+ }
+ while (l != null) {
+ if (l.getMessage() == null) {
+ l.setMessage("<No location-specific message");
+ }
+ if (l == l.getSecondary()) {
+ fail("Location link cycle");
+ }
+ l = l.getSecondary();
+ }
+ }
+
+ super.report(context, issue, severity, location, message, format);
+
+ // Make sure errors are unique!
+ Warning prev = null;
+ for (Warning warning : mWarnings) {
+ assertNotSame(warning, prev);
+ assert prev == null || !warning.equals(prev) : warning;
+ prev = warning;
+ }
+ }
+
+ @Override
+ public void log(Throwable exception, String format, Object... args) {
+ if (exception != null) {
+ exception.printStackTrace();
+ }
+ StringBuilder sb = new StringBuilder();
+ if (format != null) {
+ sb.append(String.format(format, args));
+ }
+ if (exception != null) {
+ sb.append(exception.toString());
+ }
+ System.err.println(sb);
+
+ if (exception != null) {
+ fail(exception.toString());
+ }
+ }
+
+ @NonNull
+ @Override
+ public Configuration getConfiguration(@NonNull Project project,
+ @Nullable LintDriver driver) {
+ return LintDetectorTest.this.getConfiguration(this, project);
+ }
+
+ @Override
+ public File findResource(@NonNull String relativePath) {
+ if (relativePath.equals("platform-tools/api/api-versions.xml")) {
+ // Look in the current Git repository and try to find it there
+ File rootDir = getRootDir();
+ if (rootDir != null) {
+ File file = new File(rootDir, "development" + File.separator + "sdk"
+ + File.separator + "api-versions.xml");
+ if (file.exists()) {
+ return file;
+ }
+ }
+ // Look in an SDK install, if found
+ File home = getSdkHome();
+ if (home != null) {
+ return new File(home, relativePath);
+ }
+ } else if (relativePath.equals(ExternalAnnotationRepository.SDK_ANNOTATIONS_PATH)) {
+ // Look in the current Git repository and try to find it there
+ File rootDir = getRootDir();
+ if (rootDir != null) {
+ File file = new File(rootDir,
+ "tools" + File.separator
+ + "adt" + File.separator
+ + "idea" + File.separator
+ + "android" + File.separator
+ + "annotations");
+ if (file.exists()) {
+ return file;
+ }
+ }
+ // Look in an SDK install, if found
+ File home = getSdkHome();
+ if (home != null) {
+ File file = new File(home, relativePath);
+ return file.exists() ? file : null;
+ }
+ } else if (relativePath.startsWith("tools/support/")) {
+ // Look in the current Git repository and try to find it there
+ String base = relativePath.substring("tools/support/".length());
+ File rootDir = getRootDir();
+ if (rootDir != null) {
+ File file = new File(rootDir, "tools"
+ + File.separator + "base"
+ + File.separator + "files"
+ + File.separator + "typos"
+ + File.separator + base);
+ if (file.exists()) {
+ return file;
+ }
+ }
+ // Look in an SDK install, if found
+ File home = getSdkHome();
+ if (home != null) {
+ return new File(home, relativePath);
+ }
+ } else {
+ fail("Unit tests don't support arbitrary resource lookup yet.");
+ }
+
+ return super.findResource(relativePath);
+ }
+
+ @NonNull
+ @Override
+ public List<File> findGlobalRuleJars() {
+ // Don't pick up random custom rules in ~/.android/lint when running unit tests
+ return Collections.emptyList();
+ }
+
+ public void setIncremental(File currentFile) {
+ mIncrementalCheck = currentFile;
+ }
+
+ @Override
+ public boolean supportsProjectResources() {
+ return mIncrementalCheck != null;
+ }
+
+ @Nullable
+ @Override
+ public AbstractResourceRepository getProjectResources(Project project,
+ boolean includeDependencies) {
+ if (mIncrementalCheck == null) {
+ return null;
+ }
+
+ ResourceRepository repository = new ResourceRepository(false);
+ ILogger logger = new StdLogger(StdLogger.Level.INFO);
+ ResourceMerger merger = new ResourceMerger(0);
+
+ ResourceSet resourceSet = new ResourceSet(getName()) {
+ @Override
+ protected void checkItems() throws DuplicateDataException {
+ // No checking in ProjectResources; duplicates can happen, but
+ // the project resources shouldn't abort initialization
+ }
+ };
+ // Only support 1 resource folder in test setup right now
+ int size = project.getResourceFolders().size();
+ assertTrue("Found " + size + " test resources folders", size <= 1);
+ if (size == 1) {
+ resourceSet.addSource(project.getResourceFolders().get(0));
+ }
+ try {
+ resourceSet.loadFromFiles(logger);
+ merger.addDataSet(resourceSet);
+ merger.mergeData(repository.createMergeConsumer(), true);
+
+ // Make tests stable: sort the item lists!
+ Map<ResourceType, ListMultimap<String, ResourceItem>> map = repository.getItems();
+ for (Map.Entry<ResourceType, ListMultimap<String, ResourceItem>> entry : map.entrySet()) {
+ Map<String, List<ResourceItem>> m = Maps.newHashMap();
+ ListMultimap<String, ResourceItem> value = entry.getValue();
+ List<List<ResourceItem>> lists = Lists.newArrayList();
+ for (Map.Entry<String, ResourceItem> e : value.entries()) {
+ String key = e.getKey();
+ ResourceItem item = e.getValue();
+
+ List<ResourceItem> list = m.get(key);
+ if (list == null) {
+ list = Lists.newArrayList();
+ lists.add(list);
+ m.put(key, list);
+ }
+ list.add(item);
+ }
+
+ for (List<ResourceItem> list : lists) {
+ Collections.sort(list, new Comparator<ResourceItem>() {
+ @Override
+ public int compare(ResourceItem o1, ResourceItem o2) {
+ return o1.getKey().compareTo(o2.getKey());
+ }
+ });
+ }
+
+ // Store back in list multi map in new sorted order
+ value.clear();
+ for (Map.Entry<String, List<ResourceItem>> e : m.entrySet()) {
+ String key = e.getKey();
+ List<ResourceItem> list = e.getValue();
+ for (ResourceItem item : list) {
+ value.put(key, item);
+ }
+ }
+ }
+
+ // Workaround: The repository does not insert ids from layouts! We need
+ // to do that here.
+ Map<ResourceType,ListMultimap<String,ResourceItem>> items = repository.getItems();
+ ListMultimap<String, ResourceItem> layouts = items
+ .get(ResourceType.LAYOUT);
+ if (layouts != null) {
+ for (ResourceItem item : layouts.values()) {
+ ResourceFile source = item.getSource();
+ if (source == null) {
+ continue;
+ }
+ File file = source.getFile();
+ try {
+ String xml = Files.toString(file, Charsets.UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ assertNotNull(document);
+ Set<String> ids = Sets.newHashSet();
+ addIds(ids, document); // TODO: pull parser
+ if (!ids.isEmpty()) {
+ ListMultimap<String, ResourceItem> idMap =
+ items.get(ResourceType.ID);
+ if (idMap == null) {
+ idMap = ArrayListMultimap.create();
+ items.put(ResourceType.ID, idMap);
+ }
+ for (String id : ids) {
+ ResourceItem idItem = new ResourceItem(id, ResourceType.ID,
+ null);
+ String qualifiers = file.getParentFile().getName();
+ if (qualifiers.startsWith("layout-")) {
+ qualifiers = qualifiers.substring("layout-".length());
+ } else if (qualifiers.equals("layout")) {
+ qualifiers = "";
+ }
+
+ // Creating the resource file will set the source of
+ // idItem.
+ new ResourceFile(file, idItem, qualifiers);
+ idMap.put(id, idItem);
+ }
+ }
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ }
+ }
+ }
+ catch (MergingException e) {
+ fail(e.getMessage());
+ }
+
+ return repository;
+ }
+
+ private void addIds(Set<String> ids, Node node) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (id != null && !id.isEmpty()) {
+ ids.add(LintUtils.stripIdPrefix(id));
+ }
+
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String value = attribute.getValue();
+ if (value.startsWith(NEW_ID_PREFIX)) {
+ ids.add(value.substring(NEW_ID_PREFIX.length()));
+ }
+ }
+ }
+
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ addIds(ids, child);
+ }
+ }
+
+ @Nullable
+ @Override
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ IAndroidTarget compileTarget = super.getCompileTarget(project);
+ if (compileTarget == null) {
+ IAndroidTarget[] targets = getTargets();
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform()) {
+ return target;
+ }
+ }
+ }
+
+ return compileTarget;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getTestSourceFolders(@NonNull Project project) {
+ List<File> testSourceFolders = super.getTestSourceFolders(project);
+
+ //List<File> tests = new ArrayList<File>();
+ File tests = new File(project.getDir(), "test");
+ if (tests.exists()) {
+ List<File> all = Lists.newArrayList(testSourceFolders);
+ all.add(tests);
+ testSourceFolders = all;
+ }
+
+ return testSourceFolders;
+ }
+ }
+
+ /**
+ * Returns the Android source tree root dir.
+ * @return the root dir or null if it couldn't be computed.
+ */
+ protected File getRootDir() {
+ CodeSource source = getClass().getProtectionDomain().getCodeSource();
+ if (source != null) {
+ URL location = source.getLocation();
+ try {
+ File dir = SdkUtils.urlToFile(location);
+ assertTrue(dir.getPath(), dir.exists());
+ while (dir != null) {
+ File settingsGradle = new File(dir, "settings.gradle"); //$NON-NLS-1$
+ if (settingsGradle.exists()) {
+ return dir.getParentFile().getParentFile();
+ }
+ File lint = new File(dir, "lint"); //$NON-NLS-1$
+ if (lint.exists() && new File(lint, "cli").exists()) { //$NON-NLS-1$
+ return dir.getParentFile().getParentFile();
+ }
+
+ File tools = new File(dir, "tools");
+ if (tools.exists() && FileUtils.join(tools, "base", "lint", "cli").exists()) {
+ return dir;
+ }
+ dir = dir.getParentFile();
+ }
+
+ return null;
+ } catch (MalformedURLException e) {
+ fail(e.getLocalizedMessage());
+ }
+ }
+
+ return null;
+ }
+
+ public class TestConfiguration extends DefaultConfiguration {
+ protected TestConfiguration(
+ @NonNull LintClient client,
+ @NonNull Project project,
+ @Nullable Configuration parent) {
+ super(client, project, parent);
+ }
+
+ public TestConfiguration(
+ @NonNull LintClient client,
+ @Nullable Project project,
+ @Nullable Configuration parent,
+ @NonNull File configFile) {
+ super(client, project, parent, configFile);
+ }
+
+ @Override
+ @NonNull
+ protected Severity getDefaultSeverity(@NonNull Issue issue) {
+ // In unit tests, include issues that are ignored by default
+ Severity severity = super.getDefaultSeverity(issue);
+ if (severity == Severity.IGNORE) {
+ if (issue.getDefaultSeverity() != Severity.IGNORE) {
+ return issue.getDefaultSeverity();
+ }
+ return Severity.WARNING;
+ }
+ return severity;
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return LintDetectorTest.this.isEnabled(issue);
+ }
+
+ @Override
+ public void ignore(@NonNull Context context, @NonNull Issue issue,
+ @Nullable Location location, @NonNull String message) {
+ fail("Not supported in tests.");
+ }
+
+ @Override
+ public void setSeverity(@NonNull Issue issue, @Nullable Severity severity) {
+ fail("Not supported in tests.");
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/.settings/org.eclipse.core.resources.prefs b/lint/libs/lint-tests/src/test/.settings/org.eclipse.core.resources.prefs
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/.settings/org.eclipse.core.resources.prefs
rename to lint/libs/lint-tests/src/test/.settings/org.eclipse.core.resources.prefs
diff --git a/base/lint/libs/lint-tests/src/test/.settings/org.moreunit.prefs b/lint/libs/lint-tests/src/test/.settings/org.moreunit.prefs
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/.settings/org.moreunit.prefs
rename to lint/libs/lint-tests/src/test/.settings/org.moreunit.prefs
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java
new file mode 100644
index 0000000..2b72080
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/EcjParserTest.java
@@ -0,0 +1,1167 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.tools.lint.EcjParser.equalsCompound;
+import static com.android.tools.lint.EcjParser.startsWithCompound;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import static com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.SdCardDetector;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtilsTest;
+import com.android.tools.lint.detector.api.Project;
+import com.google.common.collect.Lists;
+
+import org.intellij.lang.annotations.Language;
+import org.junit.Assert;
+
+import java.io.File;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import lombok.ast.AnnotationElement;
+import lombok.ast.BinaryExpression;
+import lombok.ast.Block;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.DescribedNode;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.Identifier;
+import lombok.ast.KeywordModifier;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Modifiers;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.Select;
+import lombok.ast.TypeReferencePart;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableReference;
+import lombok.ast.printer.SourceFormatter;
+import lombok.ast.printer.SourcePrinter;
+import lombok.ast.printer.TextFormatter;
+
+// Disable code warnings that are applied to injected languages in ECJ sample code: these
+// are deliberately doing dodgy things to test parser scenarios
+ at SuppressWarnings({"ClassNameDiffersFromFileName", "MethodMayBeStatic", "ImplicitArrayToString",
+ "UnnecessaryLocalVariable", "UnnecessarySemicolon"})
+public class EcjParserTest extends AbstractCheckTest {
+ public void testTryCatchHang() throws Exception {
+ // Ensure that we're really using this parser
+ JavaParser javaParser = createClient().getJavaParser(null);
+ assertNotNull(javaParser);
+ assertTrue(javaParser.getClass().getName(), javaParser instanceof EcjParser);
+
+ // See https://code.google.com/p/projectlombok/issues/detail?id=573#c6
+ // With lombok.ast 0.2.1 and the parboiled-based Java parser this test will hang forever.
+ assertEquals(
+ "No warnings.",
+
+ lintProject("src/test/pkg/TryCatchHang.java.txt=>src/test/pkg/TryCatchHang.java"));
+ }
+
+ public void testKitKatLanguageFeatures() throws Exception {
+ @Language("JAVA")
+ String testClass = "" +
+ "package test.pkg;\n" +
+ "\n" +
+ "import java.io.BufferedReader;\n" +
+ "import java.io.FileReader;\n" +
+ "import java.io.IOException;\n" +
+ "import java.lang.reflect.InvocationTargetException;\n" +
+ "import java.util.List;\n" +
+ "import java.util.Map;\n" +
+ "import java.util.TreeMap;\n" +
+ "\n" +
+ "public class Java7LanguageFeatureTest {\n" +
+ " public void testDiamondOperator() {\n" +
+ " Map<String, List<Integer>> map = new TreeMap<>();\n" +
+ " }\n" +
+ "\n" +
+ " public int testStringSwitches(String value) {\n" +
+ " final String first = \"first\";\n" +
+ " final String second = \"second\";\n" +
+ "\n" +
+ " switch (value) {\n" +
+ " case first:\n" +
+ " return 41;\n" +
+ " case second:\n" +
+ " return 42;\n" +
+ " default:\n" +
+ " return 0;\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " public String testTryWithResources(String path) throws IOException {\n" +
+ " try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n" +
+ " return br.readLine();\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " public void testNumericLiterals() {\n" +
+ " int thousand = 1_000;\n" +
+ " int million = 1_000_000;\n" +
+ " int binary = 0B01010101;\n" +
+ " }\n" +
+ "\n" +
+ " public void testMultiCatch() {\n" +
+ "\n" +
+ " try {\n" +
+ " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n" +
+ " } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n" +
+ " e.printStackTrace();\n" +
+ " } catch (ClassNotFoundException e) {\n" +
+ " // TODO: Logging here\n" +
+ " }\n" +
+ " }\n" +
+ "}\n";
+
+ Node unit = LintUtilsTest.getCompilationUnit(testClass);
+ assertNotNull(unit);
+
+ // Now print the AST back and make sure that it contains at least the essence of the AST
+ TextFormatter formatter = new TextFormatter();
+ unit.accept(new SourcePrinter(formatter));
+ String actual = formatter.finish();
+ assertEquals(""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import java.io.BufferedReader;\n"
+ + "import java.io.FileReader;\n"
+ + "import java.io.IOException;\n"
+ + "import java.lang.reflect.InvocationTargetException;\n"
+ + "import java.util.List;\n"
+ + "import java.util.Map;\n"
+ + "import java.util.TreeMap;\n"
+ + "\n"
+ + "public class Java7LanguageFeatureTest {\n"
+ + " public void testDiamondOperator() {\n"
+ + " Map<String, List<Integer>> map = new TreeMap();\n" // missing types on rhs
+ + " }\n"
+ + " \n"
+ + " public int testStringSwitches(String value) {\n"
+ + " final String first = \"first\";\n"
+ + " final String second = \"second\";\n"
+ + " switch (value) {\n"
+ + " case first:\n"
+ + " return 41;\n"
+ + " case second:\n"
+ + " return 42;\n"
+ + " default:\n"
+ + " return 0;\n"
+ + " }\n"
+ + " }\n"
+ + " \n"
+ + " public String testTryWithResources(String path) throws IOException {\n"
+ + " try {\n" // Note how the initialization clause is gone here
+ + " return br.readLine();\n"
+ + " }\n"
+ + " }\n"
+ + " \n"
+ + " public void testNumericLiterals() {\n"
+ + " int thousand = 1_000;\n"
+ + " int million = 1_000_000;\n"
+ + " int binary = 0B01010101;\n"
+ + " }\n"
+ + " \n"
+ + " public void testMultiCatch() {\n"
+ + " try {\n"
+ + " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n"
+ + " } catch (IllegalAccessException e) {\n" // Note: missing other union types
+ + " e.printStackTrace();\n"
+ + " } catch (ClassNotFoundException e) {\n"
+ + " }\n"
+ + " }\n"
+ + "}",
+ actual);
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testGetFields() throws Exception {
+ @Language("JAVA")
+ String source = ""
+ + "public class FieldTest {\n"
+ + " public int field1 = 1;\n"
+ + " public int field2 = 3;\n"
+ + " public int field3 = 5;\n"
+ + " \n"
+ + " public static class Inner extends FieldTest {\n"
+ + " public int field2 = 5;\n"
+ + " }\n"
+ + "}\n";
+
+ final JavaContext context = LintUtilsTest.parse(source,
+ new File("src/test/pkg/FieldTest.java"));
+ assertNotNull(context);
+
+ Node compilationUnit = context.getCompilationUnit();
+ assertNotNull(compilationUnit);
+ final AtomicBoolean found = new AtomicBoolean();
+ compilationUnit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ if (node.astName().astValue().equals("Inner")) {
+ found.set(true);
+ ResolvedNode resolved = context.resolve(node);
+ assertNotNull(resolved);
+ ResolvedClass cls = (ResolvedClass) resolved;
+ List<ResolvedField> declaredFields = Lists.newArrayList(cls.getFields(false));
+ assertEquals(1, declaredFields.size());
+ assertEquals("field2", declaredFields.get(0).getName());
+
+ declaredFields = Lists.newArrayList(cls.getFields(true));
+ assertEquals(3, declaredFields.size());
+ assertEquals("field2", declaredFields.get(0).getName());
+ assertEquals("FieldTest.Inner", declaredFields.get(0).getContainingClassName());
+ assertEquals("field1", declaredFields.get(1).getName());
+ assertEquals("FieldTest", declaredFields.get(1).getContainingClassName());
+ assertEquals("field3", declaredFields.get(2).getName());
+ assertEquals("FieldTest", declaredFields.get(2).getContainingClassName());
+ }
+
+ return super.visitClassDeclaration(node);
+ }
+ });
+ assertTrue(found.get());
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testRemoveDuplicates() throws Exception {
+ @SuppressWarnings("override")
+ @Language("JAVA")
+ String source = ""
+ + "package test.pkg;\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "@SuppressWarnings(\"unused\")\n"
+ + "public class AnnotationTest {\n"
+ + " @Retention(RetentionPolicy.CLASS)\n"
+ + " public @interface Annotation1 {};\n"
+ + " @Retention(RetentionPolicy.CLASS)\n"
+ + " public @interface Annotation2 {};\n"
+ + " @Retention(RetentionPolicy.CLASS)\n"
+ + " public @interface Annotation3 {};\n"
+ + "\n"
+ + " public static void method(ChildClass child) {\n"
+ + " child.method(0xff0000);\n"
+ + " }\n"
+ + "\n"
+ + " public static class ParentClass {\n"
+ + " @Annotation1\n"
+ + " void method(@Annotation2 int rgb) {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static class ChildClass extends ParentClass {\n"
+ + " @Annotation1\n"
+ + " void method(@Annotation2 @Annotation3 int rgb) {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
+
+ final JavaContext context = LintUtilsTest.parse(source,
+ new File("src/test/pkg/AnnotationTest.java"));
+ assertNotNull(context);
+
+ Node compilationUnit = context.getCompilationUnit();
+ assertNotNull(compilationUnit);
+ final AtomicBoolean found = new AtomicBoolean();
+ compilationUnit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node.astName().astValue().equals("method")) {
+ found.set(true);
+ ResolvedNode resolved = context.resolve(node);
+ assertNotNull(resolved);
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ List<ResolvedAnnotation> methodAnnotations =
+ Lists.newArrayList(method.getAnnotations());
+ // This is the point of this test: make sure there is only *one* occurrence
+ // of this annotation (prior to this fix, we'd accumulate up the hierarchy)
+ assertEquals(1, methodAnnotations.size());
+ assertEquals("test.pkg.AnnotationTest.Annotation1",
+ methodAnnotations.get(0).getName());
+ assertEquals(methodAnnotations.get(0).getName(),
+ methodAnnotations.get(0).getSignature());
+
+ List<ResolvedAnnotation> annotations =
+ Lists.newArrayList(method.getParameterAnnotations(0));
+ assertEquals(2, annotations.size());
+ String first = annotations.get(0).getName();
+ String second = annotations.get(1).getName();
+ if (first.compareTo(second) > 0) {
+ String temp = first;
+ first = second;
+ second = temp;
+ }
+ assertEquals("test.pkg.AnnotationTest.Annotation2", first);
+ assertEquals("test.pkg.AnnotationTest.Annotation3", second);
+ }
+
+ return super.visitMethodInvocation(node);
+ }
+ });
+ assertTrue(found.get());
+ }
+
+ public void testResolution() throws Exception {
+ @Language("JAVA")
+ String source =
+ "package test.pkg;\n" +
+ "\n" +
+ "import java.io.File;\n" +
+ "\n" +
+ "public class TypeResolutionTest {\n" +
+ " public static class Inner extends File {\n" +
+ " public float myField = 5f;\n" +
+ " public int[] myInts;\n" +
+ "\n" +
+ " public Inner(File dir, String name) {\n" +
+ " super(dir, name);\n" +
+ " }\n" +
+ "\n" +
+ " public void call(int arg1, double arg2) {\n" +
+ " boolean x = super.canRead();\n" +
+ " System.out.println(x);\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " @SuppressWarnings(\"all\")\n" +
+ " public static class Other {\n" +
+ " private void client(int z) {\n" +
+ " int x = z;\n" +
+ " int y = x + 5;\n" +
+ " {\n" +
+ " int w = 5;\n" +
+ " }\n" +
+ " Inner inner = new Inner(null, null);\n" +
+ " inner.myField = 6;\n" +
+ " System.out.println(inner.myInts);\n" +
+ " }\n" +
+ " }\n" +
+ "}\n";
+
+ Node unit = LintUtilsTest.getCompilationUnit(source,
+ new File("src/test/pkg/TypeResolutionTest.java"));
+
+ // Visit all nodes and assert nativeNode != null unless I expect it!
+ unit.accept(new ForwardingAstVisitor() {
+ @SuppressWarnings("Contract")
+ @Override
+ public boolean visitNode(Node node) {
+ if (node.getNativeNode() == null && requiresNativeNode(node)) {
+ fail("Expected native node on node of type " +
+ node.getClass().getSimpleName());
+ }
+ return super.visitNode(node);
+ }
+
+ private boolean requiresNativeNode(Node node) {
+ if (node instanceof TypeReferencePart &&
+ node.getParent().getNativeNode() != null) {
+ return false;
+ }
+
+ if (node instanceof Identifier
+ || node instanceof NormalTypeBody
+ || node instanceof Block
+ || node instanceof VariableDeclaration
+ || node instanceof VariableDefinition
+ || node instanceof AnnotationElement
+ || node instanceof BinaryExpression
+ || node instanceof Modifiers
+ || node instanceof KeywordModifier) {
+ return false;
+ }
+
+ if (node instanceof VariableReference) {
+ VariableReference reference = (VariableReference)node;
+ if (reference.getParent() instanceof Select) {
+ return false;
+ }
+ } else if (node instanceof MethodInvocation) {
+ Node parent = node.getParent();
+ if (parent instanceof ExpressionStatement &&
+ parent.getNativeNode() != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ });
+
+ JavaParser parser = new EcjParser(new LintCliClient(), null);
+ AstPrettyPrinter astPrettyPrinter = new AstPrettyPrinter(parser);
+ unit.accept(new SourcePrinter(astPrettyPrinter));
+ String actual = astPrettyPrinter.finish();
+ assertEquals(
+ "[CompilationUnit]\n" +
+ " [PackageDeclaration]\n" +
+ " [Identifier test]\n" +
+ " PROPERTY: name = test\n" +
+ " [Identifier pkg]\n" +
+ " PROPERTY: name = pkg\n" +
+ " [ImportDeclaration]\n" +
+ " PROPERTY: static = false\n" +
+ " PROPERTY: star = false\n" +
+ " [Identifier java]\n" +
+ " PROPERTY: name = java\n" +
+ " [Identifier io]\n" +
+ " PROPERTY: name = io\n" +
+ " [Identifier File]\n" +
+ " PROPERTY: name = File\n" +
+ " [ClassDeclaration TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " [Modifiers], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " typeName: [Identifier TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " PROPERTY: name = TypeResolutionTest\n" +
+ " [NormalTypeBody], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
+ " [ClassDeclaration Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [Modifiers], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " [KeywordModifier static]\n" +
+ " PROPERTY: modifier = static\n" +
+ " typeName: [Identifier Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " PROPERTY: name = Inner\n" +
+ " extends: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
+ " [Identifier File]\n" +
+ " PROPERTY: name = File\n" +
+ " [NormalTypeBody], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [VariableDeclaration], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
+ " [VariableDefinition], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " type: [TypeReference float], type: float, resolved class: float \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: float, resolved class: float \n" +
+ " [Identifier float]\n" +
+ " PROPERTY: name = float\n" +
+ " [VariableDefinitionEntry], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier myField], type: float, resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = myField\n" +
+ " [FloatingPointLiteral 5.0], type: float\n" +
+ " PROPERTY: value = 5f\n" +
+ " [VariableDeclaration], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
+ " [VariableDefinition], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " type: [TypeReference int[]], type: int[], resolved class: int[] \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 1\n" +
+ " [TypeReferencePart], type: int[], resolved class: int[] \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier myInts], type: int[], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = myInts\n" +
+ " [ConstructorDeclaration], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [Modifiers], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " typeName: [Identifier Inner], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = Inner\n" +
+ " parameter: [VariableDefinition], type: java.io.File, resolved variable: dir java.io.File\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
+ " [Identifier File]\n" +
+ " PROPERTY: name = File\n" +
+ " [VariableDefinitionEntry], type: java.io.File, resolved variable: dir java.io.File\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier dir], type: java.io.File, resolved variable: dir java.io.File\n" +
+ " PROPERTY: name = dir\n" +
+ " parameter: [VariableDefinition], type: java.lang.String, resolved variable: name java.lang.String\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference String], type: java.lang.String, resolved class: java.lang.String \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.lang.String, resolved class: java.lang.String \n" +
+ " [Identifier String]\n" +
+ " PROPERTY: name = String\n" +
+ " [VariableDefinitionEntry], type: java.lang.String, resolved variable: name java.lang.String\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier name], type: java.lang.String, resolved variable: name java.lang.String\n" +
+ " PROPERTY: name = name\n" +
+ " [Block], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [SuperConstructorInvocation], resolved method: java.io.File java.io.File\n" +
+ " [VariableReference], type: java.io.File, resolved variable: dir java.io.File\n" +
+ " [Identifier dir], type: java.io.File, resolved variable: dir java.io.File\n" +
+ " PROPERTY: name = dir\n" +
+ " [VariableReference], type: java.lang.String, resolved variable: name java.lang.String\n" +
+ " [Identifier name], type: java.lang.String, resolved variable: name java.lang.String\n" +
+ " PROPERTY: name = name\n" +
+ " [MethodDeclaration call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " [Modifiers], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " returnType: [TypeReference void], type: void, resolved class: void \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: void, resolved class: void \n" +
+ " [Identifier void]\n" +
+ " PROPERTY: name = void\n" +
+ " methodName: [Identifier call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = call\n" +
+ " parameter: [VariableDefinition], type: int, resolved variable: arg1 int\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry], type: int, resolved variable: arg1 int\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier arg1], type: int, resolved variable: arg1 int\n" +
+ " PROPERTY: name = arg1\n" +
+ " parameter: [VariableDefinition], type: double, resolved variable: arg2 double\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference double], type: double, resolved class: double \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: double, resolved class: double \n" +
+ " [Identifier double]\n" +
+ " PROPERTY: name = double\n" +
+ " [VariableDefinitionEntry], type: double, resolved variable: arg2 double\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier arg2], type: double, resolved variable: arg2 double\n" +
+ " PROPERTY: name = arg2\n" +
+ " [Block], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
+ " [VariableDeclaration], type: boolean, resolved variable: x boolean\n" +
+ " [VariableDefinition], type: boolean, resolved variable: x boolean\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference boolean], type: boolean, resolved class: boolean \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: boolean, resolved class: boolean \n" +
+ " [Identifier boolean]\n" +
+ " PROPERTY: name = boolean\n" +
+ " [VariableDefinitionEntry], type: boolean, resolved variable: x boolean\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier x], type: boolean, resolved variable: x boolean\n" +
+ " PROPERTY: name = x\n" +
+ " [MethodInvocation canRead], type: boolean, resolved method: canRead java.io.File\n" +
+ " operand: [Super], type: java.io.File\n" +
+ " methodName: [Identifier canRead], type: boolean, resolved method: canRead java.io.File\n" +
+ " PROPERTY: name = canRead\n" +
+ " [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
+ " [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
+ " operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " [Identifier System]\n" +
+ " PROPERTY: name = System\n" +
+ " selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " PROPERTY: name = out\n" +
+ " methodName: [Identifier println]\n" +
+ " PROPERTY: name = println\n" +
+ " [VariableReference], type: boolean, resolved variable: x boolean\n" +
+ " [Identifier x], type: boolean, resolved variable: x boolean\n" +
+ " PROPERTY: name = x\n" +
+ " [ClassDeclaration Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " [Modifiers], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " [Annotation SuppressWarnings], type: java.lang.SuppressWarnings, resolved annotation: java.lang.SuppressWarnings \n" +
+ " [TypeReference SuppressWarnings], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
+ " [Identifier SuppressWarnings]\n" +
+ " PROPERTY: name = SuppressWarnings\n" +
+ " [AnnotationElement null], type: java.lang.SuppressWarnings, resolved annotation: java.lang.SuppressWarnings \n" +
+ " [StringLiteral all], type: java.lang.String\n" +
+ " PROPERTY: value = \"all\"\n" +
+ " [KeywordModifier public]\n" +
+ " PROPERTY: modifier = public\n" +
+ " [KeywordModifier static]\n" +
+ " PROPERTY: modifier = static\n" +
+ " typeName: [Identifier Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " PROPERTY: name = Other\n" +
+ " [NormalTypeBody], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
+ " [MethodDeclaration client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " [Modifiers], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " [KeywordModifier private]\n" +
+ " PROPERTY: modifier = private\n" +
+ " returnType: [TypeReference void], type: void, resolved class: void \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: void, resolved class: void \n" +
+ " [Identifier void]\n" +
+ " PROPERTY: name = void\n" +
+ " methodName: [Identifier client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " PROPERTY: name = client\n" +
+ " parameter: [VariableDefinition], type: int, resolved variable: z int\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry], type: int, resolved variable: z int\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier z], type: int, resolved variable: z int\n" +
+ " PROPERTY: name = z\n" +
+ " [Block], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
+ " [VariableDeclaration], type: int, resolved variable: x int\n" +
+ " [VariableDefinition], type: int, resolved variable: x int\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry], type: int, resolved variable: x int\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier x], type: int, resolved variable: x int\n" +
+ " PROPERTY: name = x\n" +
+ " [VariableReference], type: int, resolved variable: z int\n" +
+ " [Identifier z], type: int, resolved variable: z int\n" +
+ " PROPERTY: name = z\n" +
+ " [VariableDeclaration], type: int, resolved variable: y int\n" +
+ " [VariableDefinition], type: int, resolved variable: y int\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry], type: int, resolved variable: y int\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier y], type: int, resolved variable: y int\n" +
+ " PROPERTY: name = y\n" +
+ " [BinaryExpression +], type: int\n" +
+ " PROPERTY: operator = +\n" +
+ " left: [VariableReference], type: int, resolved variable: x int\n" +
+ " [Identifier x], type: int, resolved variable: x int\n" +
+ " PROPERTY: name = x\n" +
+ " right: [IntegralLiteral 5], type: int\n" +
+ " PROPERTY: value = 5\n" +
+ " [Block]\n" +
+ " [VariableDeclaration], type: int, resolved variable: w int\n" +
+ " [VariableDefinition], type: int, resolved variable: w int\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference int], type: int, resolved class: int \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: int, resolved class: int \n" +
+ " [Identifier int]\n" +
+ " PROPERTY: name = int\n" +
+ " [VariableDefinitionEntry], type: int, resolved variable: w int\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier w], type: int, resolved variable: w int\n" +
+ " PROPERTY: name = w\n" +
+ " [IntegralLiteral 5], type: int\n" +
+ " PROPERTY: value = 5\n" +
+ " [VariableDeclaration], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [VariableDefinition], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: varargs = false\n" +
+ " [Modifiers]\n" +
+ " type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [Identifier Inner]\n" +
+ " PROPERTY: name = Inner\n" +
+ " [VariableDefinitionEntry], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " varName: [Identifier inner], type: test.pkg.TypeResolutionTest.Inner, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = inner\n" +
+ " [ConstructorInvocation Inner], type: test.pkg.TypeResolutionTest.Inner, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
+ " type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " PROPERTY: WildcardKind = NONE\n" +
+ " PROPERTY: arrayDimensions = 0\n" +
+ " [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
+ " [Identifier Inner]\n" +
+ " PROPERTY: name = Inner\n" +
+ " [NullLiteral], type: null\n" +
+ " [NullLiteral], type: null\n" +
+ " [ExpressionStatement], type: float\n" +
+ " [BinaryExpression =], type: float\n" +
+ " PROPERTY: operator = =\n" +
+ " left: [Select], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " operand: [VariableReference], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [Identifier inner]\n" +
+ " PROPERTY: name = inner\n" +
+ " selected: [Identifier myField], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = myField\n" +
+ " right: [IntegralLiteral 6], type: int\n" +
+ " PROPERTY: value = 6\n" +
+ " [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
+ " [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
+ " operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " [Identifier System]\n" +
+ " PROPERTY: name = System\n" +
+ " selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
+ " PROPERTY: name = out\n" +
+ " methodName: [Identifier println]\n" +
+ " PROPERTY: name = println\n" +
+ " [Select], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " operand: [VariableReference], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " [Identifier inner]\n" +
+ " PROPERTY: name = inner\n" +
+ " selected: [Identifier myInts], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
+ " PROPERTY: name = myInts\n",
+ actual);
+ }
+
+ public void testStartsWithCompound() throws Exception {
+ assertTrue(startsWithCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray()
+ }));
+
+ assertTrue(startsWithCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray(),
+ "other".toCharArray(),
+ }));
+
+ assertFalse(startsWithCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "other".toCharArray()
+ }));
+
+ // Corner cases
+ assertFalse(startsWithCompound("test.pk",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray(),
+ }));
+ assertFalse(startsWithCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray()
+ }));
+ assertTrue(startsWithCompound("test.",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray()
+ }));
+ assertFalse(startsWithCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "pk".toCharArray()
+ }));
+ }
+
+ public void testEqualsWithCompound() throws Exception {
+ assertTrue(equalsCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray()
+ }));
+
+ assertFalse(equalsCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray(),
+ "other".toCharArray(),
+ }));
+
+ assertFalse(equalsCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "other".toCharArray()
+ }));
+
+ // Corner cases
+ assertFalse(equalsCompound("test.pk",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray(),
+ }));
+ assertFalse(equalsCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray()
+ }));
+ assertFalse(equalsCompound("test.",
+ new char[][]{
+ "test".toCharArray(),
+ "pkg".toCharArray()
+ }));
+ assertFalse(equalsCompound("test.pkg",
+ new char[][]{
+ "test".toCharArray(),
+ "pk".toCharArray()
+ }));
+ }
+
+ // Regression test for https://code.google.com/p/android/issues/detail?id=172268
+ // This is a reduced version of the in-the-wild occurrence that happened when lint processed
+ // Chromium for Android sources.
+ public void testTypeInformationIsNotClearedAfterCompilation() throws Exception {
+ // We need to generate lots of classes to overflow default Compiler's TypeSystem internal array
+ // of types to trigger ArrayIndexOutOfBoundsException. This array initially has 256 entries.
+ int innerClassesNumber = 256;
+ String innerClassWithFieldTemplate = ""
+ + " private static class Inner%1$d {}\n"
+ + " private final HashMap<Inner%1$d, Integer> mMap%1$d;";
+
+ StringBuilder innerClasses = new StringBuilder();
+ for (int i = 0; i < innerClassesNumber; ++i) {
+ innerClasses.append(String.format(Locale.US, innerClassWithFieldTemplate, i));
+ innerClasses.append('\n');
+ }
+
+ String accessorTemplate = ""
+ + " for (Map.Entry<Inner%1$d, Integer> entry : mMap%1$d.entrySet()) {}";
+ StringBuilder accessors = new StringBuilder();
+ for (int i = 0; i < innerClassesNumber; ++i) {
+ accessors.append(String.format(Locale.US, accessorTemplate, i)).append('\n');
+ }
+ @SuppressWarnings({"OnDemandImport", "ClassNameDiffersFromFileName"})
+ @Language("JAVA")
+ String source = ""
+ + "import java.util.*;\n"
+ + "public class TypeInfoTest {\n"
+ + innerClasses.toString()
+ + " public void doSomething() {\n"
+ + accessors.toString()
+ + " }"
+ + "}\n";
+
+ final JavaContext context = LintUtilsTest.parse(source,
+ new File("src/test/pkg/TypeInfoTest.java"));
+ assertNotNull(context);
+
+ Node compilationUnit = context.getCompilationUnit();
+ assertNotNull(compilationUnit);
+
+ // null means OK
+ final AtomicReference<String> result = new AtomicReference<String>();
+
+ compilationUnit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation call) {
+ // Snapshot of SupportAnnotationDetector
+ try {
+ ResolvedNode resolved = context.resolve(call);
+ if (resolved instanceof ResolvedMethod) {
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ method.getAnnotations();
+ method.getContainingClass().getAnnotations();
+ } else {
+ // Most likely target SDK isn't supplied (via ANDROID_HOME or by some other
+ // means). The test is meaningless then.
+ result.compareAndSet(null,
+ "Cannot get resolved method, is test setup correct?");
+ }
+ } catch (RuntimeException e) {
+ // Exception can be swallowed by some upper-level code so we cannot rely on
+ // compilationUnit.accept to fail test
+ result.compareAndSet(null, "Got an exception " + e);
+ }
+ return false;
+ }
+ });
+
+ String failMessage = result.get();
+ assertNull(failMessage, result.get());
+ }
+
+ public void testGetClassNamesWithoutSignatures() {
+ @SuppressWarnings("TypeParameterExplicitlyExtendsObject")
+ @Language("JAVA")
+ String source = ""
+ + "package test.pkg;\n"
+ + "public abstract class Adapter<VH extends Object> {\n"
+ + " public abstract void onBindViewHolder(VH holder, int position);\n"
+ + "}\n";
+ final JavaContext context = LintUtilsTest.parse(source,
+ new File("src/test/pkg/Adapter.java"));
+ assertNotNull(context);
+
+ Node compilationUnit = context.getCompilationUnit();
+ assertNotNull(compilationUnit);
+ final AtomicBoolean found = new AtomicBoolean();
+ compilationUnit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ if (node.astName().astValue().startsWith("Adapter")) {
+ found.set(true);
+ ResolvedNode resolved = context.resolve(node);
+ assertNotNull(resolved);
+ ResolvedClass cls = (ResolvedClass) resolved;
+ assertEquals("test.pkg.Adapter", cls.getName());
+ assertEquals("Adapter", cls.getSimpleName());
+ }
+ return super.visitClassDeclaration(node);
+ }
+ });
+ assertTrue(found.get());
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new SdCardDetector();
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected ClassPathInfo getClassPath(@NonNull Project project) {
+ ClassPathInfo classPath = super.getClassPath(project);
+ // Insert fake classpath entries (non existent directories) to
+ // make sure the parser handles that gracefully. See issue 87740.
+ classPath.getLibraries(true).add(new File("nonexistent path"));
+ return classPath;
+ }
+ };
+ }
+
+ public void testEnsureUnique() {
+ ResolvedAnnotation foo1 = createTestAnnotation("foo");
+ ResolvedAnnotation foo2 = createTestAnnotation("foo");
+ ResolvedAnnotation foo3 = createTestAnnotation("foo");
+ ResolvedAnnotation bar1 = createTestAnnotation("bar");
+ ResolvedAnnotation bar2 = createTestAnnotation("bar");
+
+ assertEquals("[]",
+ EcjParser.ensureUnique(Lists.<ResolvedAnnotation>newArrayList()).toString());
+ assertEquals("[foo, bar]",
+ EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2, foo3, bar1)).toString());
+ assertEquals("[foo]",
+ EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2)).toString());
+ assertEquals("[foo]",
+ EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2, foo3)).toString());
+ assertEquals("[foo, bar]",
+ EcjParser.ensureUnique(Lists.newArrayList(foo1, foo2, foo3, bar1)).toString());
+ assertEquals("[bar, foo]",
+ EcjParser.ensureUnique(Lists.newArrayList(bar1, foo2, foo3)).toString());
+ assertEquals("[bar, foo]",
+ EcjParser.ensureUnique(Lists.newArrayList(bar1, bar2, foo2, foo3)).toString());
+ }
+
+ private ResolvedAnnotation createTestAnnotation(String name) {
+ ResolvedAnnotation annotation = mock(ResolvedAnnotation.class);
+ when(annotation.getName()).thenReturn(name);
+ when(annotation.toString()).thenReturn(name);
+ return annotation;
+ }
+
+ public static class AstPrettyPrinter implements SourceFormatter {
+
+ @SuppressWarnings("StringBufferField") // Don't care about performance here
+ private final StringBuilder mOutput = new StringBuilder(1000);
+
+ private final JavaParser mResolver;
+
+ private int mIndent;
+
+ private String mName;
+
+ public AstPrettyPrinter(JavaParser resolver) {
+ mResolver = resolver;
+ }
+
+ private void add(String in, Object... args) {
+ for (int i = 0; i < mIndent; i++) {
+ mOutput.append(" ");
+ }
+ if (mName != null) {
+ mOutput.append(mName).append(": ");
+ mName = null;
+ }
+ if (args.length == 0) {
+ mOutput.append(in);
+ } else {
+ mOutput.append(String.format(in, args));
+ }
+ }
+
+ @Override
+ public void buildInline(Node node) {
+ buildNode(node);
+ }
+
+ @Override
+ public void buildBlock(Node node) {
+ buildNode(node);
+ }
+
+ private void buildNode(Node node) {
+ if (node == null) {
+ mIndent++;
+ return;
+ }
+ String name = node.getClass().getSimpleName();
+ String description = "";
+ if (node instanceof DescribedNode) {
+ description = " " + ((DescribedNode) node).getDescription();
+ }
+
+ String typeDescription = "";
+ String resolutionDescription = "";
+ @SuppressWarnings("ConstantConditions") // Hack for test pretty printer only
+ JavaParser.TypeDescriptor t = mResolver.getType(null, node);
+ if (t != null) {
+ typeDescription = ", type: " + t.getName();
+ }
+ @SuppressWarnings("ConstantConditions") // Hack for test pretty printer only
+ ResolvedNode resolved = mResolver.resolve(null, node);
+ if (resolved != null) {
+ String c = "unknown";
+ String extra = "";
+ if (resolved instanceof ResolvedClass) {
+ c = "class";
+ } else if (resolved instanceof ResolvedMethod) {
+ c = "method";
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ extra = method.getContainingClass().getName();
+ } else if (resolved instanceof ResolvedField) {
+ c = "field";
+ ResolvedField field = (ResolvedField) resolved;
+ ResolvedClass containingClass = field.getContainingClass();
+ if (containingClass != null) {
+ extra = containingClass.getName();
+ }
+ } else if (resolved instanceof ResolvedVariable) {
+ c = "variable";
+ ResolvedVariable variable = (ResolvedVariable) resolved;
+ extra = variable.getType().getName();
+ } else if (resolved instanceof ResolvedAnnotation) {
+ c = "annotation";
+ }
+ resolutionDescription = String.format(", resolved %1$s: %2$s %3$s",
+ c, resolved.getName(), extra);
+ }
+
+ add("[%1$s%2$s]%3$s%4$s\n", name, description, typeDescription, resolutionDescription);
+
+ mIndent++;
+ }
+
+ @Override
+ public void fail(String fail) {
+ Assert.fail(fail);
+ }
+
+ @Override
+ public void property(String name, Object value) {
+ add("PROPERTY: %s = %s\n", name, value);
+ }
+
+ @Override
+ public void keyword(String text) {
+ }
+
+ @Override
+ public void operator(String text) {
+ }
+
+ @Override
+ public void verticalSpace() {
+ }
+
+ @Override
+ public void space() {
+ }
+
+ @Override
+ public void append(String text) {
+ }
+
+ @Override
+ public void startSuppressBlock() {
+ }
+
+ @Override
+ public void endSuppressBlock() {
+ }
+
+ @Override
+ public void startSuppressIndent() {
+ }
+
+ @Override
+ public void endSuppressIndent() {
+ }
+
+ @Override
+ public void closeInline() {
+ mIndent--;
+ }
+
+ @Override
+ public void closeBlock() {
+ mIndent--;
+ }
+
+ @Override
+ public void addError(int start, int end, String message) {
+ fail(message);
+ }
+
+ @Override
+ public String finish() {
+ return mOutput.toString();
+ }
+
+ @Override
+ public void setTimeTaken(long taken) {
+ }
+
+ @Override
+ public void nameNextElement(String name) {
+ mName = name;
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java
new file mode 100644
index 0000000..47eb31f
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ExternalAnnotationRepositoryTest.java
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import static com.android.tools.lint.ExternalAnnotationRepository.FN_ANNOTATIONS_XML;
+import static com.google.common.base.Charsets.UTF_8;
+import static java.io.File.separatorChar;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.client.api.JavaParser.DefaultTypeDescriptor;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
+import com.android.tools.lint.client.api.JavaParser.ResolvedField;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
+import com.android.tools.lint.client.api.JavaParser.ResolvedPackage;
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.LintUtilsTest;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+
+public class ExternalAnnotationRepositoryTest extends SdkTestCase {
+
+ @Nullable
+ private ExternalAnnotationRepository getSdkAnnotations() {
+ File annotations = findSrcRelativeDir("tools/adt/idea/android/annotations");
+ if (annotations != null) {
+ List<File> files = Collections.singletonList(annotations);
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.create(null, files);
+ assertNotNull(manager);
+ return manager;
+ } else {
+ // Can't find it when running from Gradle; ignore for now
+ //fail("Could not find annotations database");
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private ExternalAnnotationRepository getExternalAnnotations(@NonNull String pkg,
+ @NonNull String contents) throws IOException {
+ File dir = Files.createTempDir();
+ try {
+ File pkgDir = new File(dir, pkg.replace('.', separatorChar));
+ boolean mkdirs = pkgDir.mkdirs();
+ assertTrue(mkdirs);
+ Files.write(contents, new File(pkgDir, FN_ANNOTATIONS_XML), UTF_8);
+
+ List<File> files = Collections.singletonList(dir);
+ ExternalAnnotationRepository manager = ExternalAnnotationRepository.create(null, files);
+ assertNotNull(manager);
+ return manager;
+
+ } finally {
+ deleteFile(dir);
+ }
+ }
+
+ public void testFields() throws Exception {
+ ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"android.graphics.Color\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation1\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Color BLUE\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Color TRANSPARENT\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ ResolvedClass cls = createClass("android.graphics.Color");
+ assertNotNull(manager.getAnnotation(cls, "android.support.annotation.Annotation1"));
+ ResolvedField blueField = createField("android.graphics.Color", "BLUE");
+ ResolvedField transparentField = createField("android.graphics.Color", "TRANSPARENT");
+ assertNotNull(manager.getAnnotation(blueField, "android.support.annotation.Annotation3"));
+ assertNull(manager.getAnnotation(blueField, "android.support.annotation.Annotation4"));
+ assertNull(manager.getAnnotation(blueField, "android.support.annotation.Annotation5"));
+
+ assertNotNull(manager.getAnnotation(transparentField, "android.support.annotation.Annotation5"));
+ assertNull(manager.getAnnotation(transparentField, "android.support.annotation.Annotation3"));
+ assertNull(manager.getAnnotation(transparentField, "android.support.annotation.Annotation4"));
+ }
+
+ public void testMethods1() throws Exception {
+ ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"android.graphics.Color int HSVToColor(float[]) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Color int HSVToColor(int, float[]) 1\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation7\">\n"
+ + " <val name=\"value\" val=\"3\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Color int alpha(int)\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Color int argb(int, int, int, int)\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.RadialGradient RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode) 4\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.ArrayAdapter ArrayAdapter(android.content.Context, int, int, java.util.List<T>) 3\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
+ + " </item>"
+ + "</root>\n");
+ assertNotNull(manager);
+ ResolvedMethod method1 = createMethod("android.graphics.Color", "int", "HSVToColor",
+ "float[]");
+ assertNotNull(manager.getAnnotation(method1, 0, "android.support.annotation.Annotation5"));
+
+ // Generic types
+ ResolvedMethod method2 = createConstructor("android.graphics.ArrayAdapter", "ArrayAdapter",
+ "android.content.Context, int, int, java.util.List<T>");
+ assertNotNull(manager.getAnnotation(method2, 3, "android.support.annotation.Annotation4"));
+
+ // Raw types
+ method2 = createConstructor("android.graphics.ArrayAdapter", "ArrayAdapter",
+ "android.content.Context, int, int, java.util.List");
+ assertNotNull(manager.getAnnotation(method2, 3, "android.support.annotation.Annotation4"));
+ }
+
+ public void testMethods2() throws Exception {
+ ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"test.pkg.Test java.lang.Object myMethod()\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " <annotation name=\"android.support.annotation.Annotation6\">\n"
+ + " <val name=\"suggest\" val=\""#other(String,String)"\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test java.lang.Object myMethod(int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation7\">\n"
+ + " <val name=\"value\" val=\"3\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test java.lang.Object myMethod(int[]) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test java.lang.Object myMethod(int,java.lang.Object) 1\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test java.lang.Object myMethod(android.content.Context, int, int, java.util.List<T>) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test java.lang.Object myMethod(android.content.Context, int, int, java.util.List<T>) 1\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test java.lang.Object myMethod(java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.String>>,int) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ ResolvedMethod method;
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod", "");
+ assertNotNull(manager.getAnnotation(method, "android.support.annotation.Annotation5"));
+ assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
+ assertNotNull(manager.getAnnotation(method, "android.support.annotation.Annotation6"));
+
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod", "int");
+ assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation7"));
+
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod", "int[]");
+ assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
+ assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation3"));
+ assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation3"));
+
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
+ "int,java.lang.Object");
+ assertNotNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation5"));
+
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
+ "android.content.Context, int, int, java.util.List<T>");
+ assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation4"));
+ assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation5"));
+ assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
+ assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation3"));
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation5"));
+ assertNotNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation3"));
+ assertNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation5"));
+
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
+ "android.content.Context, int, int, java.util.List");
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation5"));
+ assertNotNull(manager.getAnnotation(method, 1, "android.support.annotation.Annotation3"));
+
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
+ Arrays.asList("java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.String>>",
+ "int"), false);
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
+ method = createMethod("test.pkg.Test", "java.lang.Object", "myMethod",
+ "java.util.Map,int");
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
+ }
+
+ // test intdef!
+
+
+ public void testAnnotationAttributes() throws Exception {
+ ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"android.graphics.Color int HSVToColor(float[]) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation7\">\n"
+ + " <val name=\"value\" val=\"3\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Color int HSVToColor(int, float[]) 1\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation7\">\n"
+ + " <val name=\"value\" val=\"3\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Canvas void drawLines(float[], android.graphics.Paint) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation7\">\n"
+ + " <val name=\"min\" val=\"4\" />\n"
+ + " <val name=\"multiple\" val=\"2\" />\n"
+ + " </annotation>\n"
+ + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Canvas int saveLayer(android.graphics.RectF, android.graphics.Paint, int) 2\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation8\">\n"
+ + " <val name=\"value\" val=\"{android.graphics.Canvas.MATRIX_SAVE_FLAG, android.graphics.Canvas.CLIP_SAVE_FLAG, android.graphics.Canvas.HAS_ALPHA_LAYER_SAVE_FLAG, android.graphics.Canvas.FULL_COLOR_LAYER_SAVE_FLAG, android.graphics.Canvas.CLIP_TO_LAYER_SAVE_FLAG, android.graphics.Canvas.ALL_SAVE_FLAG}\" />\n"
+ + " <val name=\"flag\" val=\"true\" />\n"
+ + " </annotation>\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ ResolvedMethod method;
+ ResolvedAnnotation annotation;
+
+ // Size 1
+ method = createMethod("android.graphics.Color", "int", "HSVToColor", "int, float[]");
+ assertNull(manager.getAnnotation(method, "android.support.annotation.Annotation7"));
+ assertNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation7"));
+ annotation = manager.getAnnotation(method, 1, "android.support.annotation.Annotation7");
+ assertNotNull(annotation);
+ assertEquals(3L, annotation.getValue());
+ assertEquals(3L, annotation.getValue("value"));
+ //noinspection ConstantConditions
+ assertEquals(3, ((Number)annotation.getValue("value")).intValue());
+ assertNotNull(annotation);
+
+ // Size 2
+ method = createMethod("android.graphics.Canvas", "void", "drawLines", "float[], android.graphics.Paint");
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.Annotation4"));
+ annotation = manager.getAnnotation(method, 0, "android.support.annotation.Annotation7");
+ assertNotNull(annotation);
+ assertEquals(4L, annotation.getValue("min"));
+ assertEquals(2L, annotation.getValue("multiple"));
+ assertNotNull(annotation);
+
+ // Intdef
+ method = createMethod("android.graphics.Canvas", "int", "saveLayer",
+ "android.graphics.RectF, android.graphics.Paint, int");
+ annotation = manager.getAnnotation(method, 2, "android.support.annotation.Annotation8");
+ assertNotNull(annotation);
+ assertEquals(true, annotation.getValue("flag"));
+ Object[] values = (Object[]) annotation.getValue("value");
+ assertNotNull(values);
+ assertEquals(6, values.length);
+ assertTrue(values[0] instanceof ResolvedField);
+ assertFalse(values[0].equals(createField("android.graphics.Canvas", "WRONG_NAME")));
+ assertEquals(values[0], createField("android.graphics.Canvas", "MATRIX_SAVE_FLAG"));
+ assertEquals(values[1], createField("android.graphics.Canvas", "CLIP_SAVE_FLAG"));
+ assertEquals(values[2], createField("android.graphics.Canvas", "HAS_ALPHA_LAYER_SAVE_FLAG"));
+ assertEquals(values[3], createField("android.graphics.Canvas", "FULL_COLOR_LAYER_SAVE_FLAG"));
+ assertEquals(values[4], createField("android.graphics.Canvas", "CLIP_TO_LAYER_SAVE_FLAG"));
+ assertEquals(values[5], createField("android.graphics.Canvas", "ALL_SAVE_FLAG"));
+
+ ResolvedField field = (ResolvedField)values[0];
+ assertEquals("android.graphics.Canvas.MATRIX_SAVE_FLAG", field.getSignature());
+ assertEquals("android.graphics.Canvas", field.getContainingClassName());
+ assertEquals("MATRIX_SAVE_FLAG", field.getName());
+ }
+
+ public void testConstructors() throws Exception {
+ ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"android.graphics.RadialGradient RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode) 4\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ ResolvedMethod method = createConstructor("android.graphics.RadialGradient",
+ "RadialGradient",
+ "float, float, float, int[], float[], android.graphics.Shader.TileMode");
+ assertNull(manager.getAnnotation(method, 4, "android.support.annotation.Annotation4"));
+ assertNotNull(manager.getAnnotation(method, 4, "android.support.annotation.Annotation5"));
+ }
+
+ public void testVarArgs() throws Exception {
+ ExternalAnnotationRepository manager = getExternalAnnotations("android.graphics", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"android.graphics.RadialGradient RadialGradient(float, float, float, int...) 3\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + " <item name=\"android.graphics.Bitmap android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]) 2\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ // Match "..." in external annotation with ... in code lookup
+ ResolvedMethod method = createConstructor("android.graphics.RadialGradient",
+ "RadialGradient",
+ "float, float, float, int...");
+ assertNotNull(manager.getAnnotation(method, 3, "android.support.annotation.Annotation5"));
+ // Match "..." in external annotation with [] in code lookup
+ method = createConstructor("android.graphics.RadialGradient",
+ "RadialGradient",
+ "float, float, float, int[]");
+ assertNotNull(manager.getAnnotation(method, 3, "android.support.annotation.Annotation5"));
+
+ // Match "[]" in external annotation with [] in code lookup
+ method = createMethod("android.graphics.Bitmap",
+ "android.graphics.Bitmap",
+ "extractAlpha",
+ "android.graphics.Paint, int[]");
+ assertNotNull(manager.getAnnotation(method, 2, "android.support.annotation.Annotation5"));
+
+ // Match "[]" in external annotation with ... in code lookup
+ method = createMethod("android.graphics.Bitmap",
+ "android.graphics.Bitmap",
+ "extractAlpha",
+ "android.graphics.Paint, int...");
+ assertNotNull(manager.getAnnotation(method, 2, "android.support.annotation.Annotation5"));
+ }
+
+ public void testPackage() throws Exception {
+ ExternalAnnotationRepository manager = getExternalAnnotations("foo.bar.baz", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"foo.bar.baz.package-info\">\n"
+ + " <annotation name=\"my.pkg.MyAnnotation\"/>\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ ResolvedClass cls = createClass("foo.bar.baz.AdView");
+ ResolvedPackage pkg = cls.getPackage();
+ assertNotNull(pkg);
+ assertNull(manager.getAnnotation(pkg, "foo.bar.Baz"));
+ assertNotNull(manager.getAnnotation(pkg, "my.pkg.MyAnnotation"));
+ }
+
+ public void testMatchWithEcj() throws Exception {
+ try {
+ ExternalAnnotationRepository manager = getExternalAnnotations("test.pkg", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"test.pkg.Test\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation1\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test.Inner\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation2\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test void foo(int, int[], int...)\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation6\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test void foo(int, int[], int...) 0\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test void foo(int, int[], int...) 1\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation3\" />\n"
+ + " <annotation name=\"android.support.annotation.Annotation4\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test void foo(int, int[], int...) 2\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation5\" />\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ ExternalAnnotationRepository.set(manager);
+
+ String source =
+ "package test.pkg;\n" +
+ "\n" +
+ "public class Test {\n" +
+ " public void foo(int a, int[] b, int... c) {\n" +
+ " }\n" +
+ " public static class Inner {\n" +
+ " }\n" +
+ "}\n";
+
+ final JavaContext context = LintUtilsTest.parse(source,
+ new File("src/test/pkg/Test.java"));
+ assertNotNull(context);
+ Node unit = context.getCompilationUnit();
+ assertNotNull(unit);
+ unit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ ResolvedNode resolved = context.resolve(node);
+ assertNotNull(resolved);
+ assertTrue(resolved.getClass().getName(), resolved instanceof ResolvedClass);
+ ResolvedClass cls = (ResolvedClass) resolved;
+ if (cls.getName().endsWith(".Inner")) {
+ assertNull(cls.getAnnotation("android.support.annotation.Annotation1"));
+ assertNotNull(cls.getAnnotation("android.support.annotation.Annotation2"));
+ } else {
+ assertNotNull(cls.getAnnotation("android.support.annotation.Annotation1"));
+ assertNull(cls.getAnnotation("android.support.annotation.Annotation2"));
+ }
+
+ return super.visitClassDeclaration(node);
+ }
+
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ ResolvedNode resolved = context.resolve(node);
+ assertNotNull(resolved);
+ assertTrue(resolved.getClass().getName(), resolved instanceof ResolvedMethod);
+ ResolvedMethod method = (ResolvedMethod) resolved;
+ assertNull(method.getAnnotation("android.support.annotation.Annotation5"));
+ assertNotNull(method.getAnnotation("android.support.annotation.Annotation6"));
+ assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation3", 0));
+ assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation4", 1));
+ assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation3", 1));
+ assertNotNull(method.getParameterAnnotation("android.support.annotation.Annotation5", 2));
+
+ return super.visitMethodDeclaration(node);
+ }
+ });
+ } finally {
+ ExternalAnnotationRepository.set(null);
+ }
+ }
+
+ public void testMergeParameters() throws Exception {
+ try {
+ ExternalAnnotationRepository manager = getExternalAnnotations("test.pkg", ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<root>\n"
+ + " <item name=\"test.pkg.Test.Parent void testMethod(int)\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation1\" />\n"
+ + " </item>\n"
+ + " <item name=\"test.pkg.Test.Child void testMethod(int)\">\n"
+ + " <annotation name=\"android.support.annotation.Annotation2\" />\n"
+ + " </item>\n"
+ + "</root>\n");
+ assertNotNull(manager);
+ ExternalAnnotationRepository.set(manager);
+
+ String source = ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "public class Test {\n"
+ + " public void test(Child child) {\n"
+ + " child.testMethod(5);\n"
+ + " }\n"
+ + "\n"
+ + " public static class Parent {\n"
+ + " public void testMethod(int parameter) {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static class Child extends Parent {\n"
+ + " @Override\n"
+ + " public void testMethod(int parameter) {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
+
+ final JavaContext context = LintUtilsTest.parse(source,
+ new File("src/test/pkg/Test.java"));
+ assertNotNull(context);
+ Node unit = context.getCompilationUnit();
+ assertNotNull(unit);
+ final AtomicBoolean foundMethod = new AtomicBoolean();
+ unit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ foundMethod.set(true);
+ assertEquals("testMethod", node.astName().astValue());
+ ResolvedNode resolved = context.resolve(node);
+ assertTrue(resolved instanceof ResolvedMethod);
+ ResolvedMethod method = (ResolvedMethod)resolved;
+ List<ResolvedAnnotation> annotations =
+ Lists.newArrayList(method.getAnnotations());
+ assertEquals(3, annotations.size());
+ Collections.sort(annotations,
+ new Comparator<ResolvedAnnotation>() {
+ @Override
+ public int compare(ResolvedAnnotation a1,
+ ResolvedAnnotation a2) {
+ return a1.getName().compareTo(a2.getName());
+ }
+ });
+ assertEquals("android.support.annotation.Annotation1", annotations.get(0).getName());
+ assertEquals("android.support.annotation.Annotation2", annotations.get(1).getName());
+ assertEquals("java.lang.Override", annotations.get(2).getName());
+ return super.visitMethodInvocation(node);
+ }
+ });
+ assertTrue(foundMethod.get());
+ } finally {
+ ExternalAnnotationRepository.set(null);
+ }
+ }
+
+ public void testSdkAnnotations() throws Exception {
+ ExternalAnnotationRepository manager = getSdkAnnotations();
+ if (manager == null) {
+ // Can't find it when running from Gradle; ignore for now
+ return;
+ }
+ ResolvedMethod method = createMethod("android.view.View", "void",
+ "dispatchVisibilityChanged", "android.view.View, int");
+ assertNotNull(manager.getAnnotation(method, 0, "android.support.annotation.NonNull"));
+ }
+
+ private static ResolvedClass createClass(String name) {
+ ResolvedClass mock = mock(ResolvedClass.class);
+ when(mock.getName()).thenReturn(name);
+ when(mock.getSignature()).thenReturn(name);
+ assertTrue(name, name.indexOf('.') != -1);
+ ResolvedPackage pkg = createPackage(name.substring(0, name.lastIndexOf('.')));
+ when(mock.getPackage()).thenReturn(pkg);
+ return mock;
+ }
+
+ private static ResolvedMethod createConstructor(String containingClass, String name,
+ String parameters) {
+ return createMethod(containingClass, null, name, parameters, true);
+ }
+
+ public static ResolvedMethod createMethod(String containingClass, String returnType,
+ String name, String parameters) {
+ return createMethod(containingClass, returnType, name, parameters, false);
+ }
+
+ public static ResolvedMethod createMethod(String containingClass, String returnType,
+ String name, String parameters, boolean isConstructor) {
+ return createMethod(containingClass, returnType, name,
+ Splitter.on(',').trimResults().split(parameters), isConstructor);
+ }
+
+ public static ResolvedMethod createMethod(String containingClass, String returnType,
+ String name, Iterable<String> parameters, boolean isConstructor) {
+ ResolvedMethod mock = mock(ResolvedMethod.class);
+ when(mock.isConstructor()).thenReturn(isConstructor);
+ when(mock.getName()).thenReturn(name);
+ if (!isConstructor) {
+ DefaultTypeDescriptor typeDescriptor = new DefaultTypeDescriptor(returnType);
+ when(mock.getReturnType()).thenReturn(typeDescriptor);
+ }
+ ResolvedClass cls = createClass(containingClass);
+ when(mock.getContainingClass()).thenReturn(cls);
+ int index = 0;
+ for (String argument : parameters) {
+ TypeDescriptor typeDescriptor = new DefaultTypeDescriptor(argument);
+ when(mock.getArgumentType(index)).thenReturn(typeDescriptor);
+ index++;
+ }
+ when(mock.getArgumentCount()).thenReturn(index);
+ return mock;
+ }
+
+ public static ResolvedField createField(String containingClass, String name) {
+ ResolvedField mock = mock(ResolvedField.class);
+ when(mock.getName()).thenReturn(name);
+ ResolvedClass cls = createClass(containingClass);
+ when(mock.getContainingClass()).thenReturn(cls);
+ return mock;
+ }
+
+ public static ResolvedPackage createPackage(String pkgName) {
+ ResolvedPackage mock = mock(ResolvedPackage.class);
+ when(mock.getName()).thenReturn(pkgName);
+ return mock;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/HtmlReporterTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/HtmlReporterTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/HtmlReporterTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/HtmlReporterTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/LintCliXmlParserTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MainTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MainTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MainTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/MainTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
new file mode 100644
index 0000000..7d00b3d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/MultiProjectHtmlReporterTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.HardcodedValuesDetector;
+import com.android.tools.lint.checks.ManifestDetector;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class MultiProjectHtmlReporterTest extends AbstractCheckTest {
+ public void test() throws Exception {
+ File dir = new File(getTargetDir(), "report");
+ try {
+ LintCliClient client = new LintCliClient() {
+ @Override
+ IssueRegistry getRegistry() {
+ if (mRegistry == null) {
+ mRegistry = new IssueRegistry() {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return Arrays.asList(
+ ManifestDetector.USES_SDK,
+ HardcodedValuesDetector.ISSUE,
+ // Not reported, but for the disabled-list
+ ManifestDetector.MOCK_LOCATION);
+ }
+ };
+ }
+ return mRegistry;
+ }
+ };
+
+ //noinspection ResultOfMethodCallIgnored
+ dir.mkdirs();
+ MultiProjectHtmlReporter reporter = new MultiProjectHtmlReporter(client, dir);
+ Project project = Project.create(client, new File("/foo/bar/Foo"),
+ new File("/foo/bar/Foo"));
+
+ Warning warning1 = new Warning(ManifestDetector.USES_SDK,
+ "<uses-sdk> tag should specify a target API level (the highest verified " +
+ "version; when running on later versions, compatibility behaviors may " +
+ "be enabled) with android:targetSdkVersion=\"?\"",
+ Severity.WARNING, project);
+ warning1.line = 6;
+ warning1.file = new File("/foo/bar/Foo/AndroidManifest.xml");
+ warning1.errorLine = " <uses-sdk android:minSdkVersion=\"8\" />\n ^\n";
+ warning1.path = "AndroidManifest.xml";
+ warning1.location = Location.create(warning1.file,
+ new DefaultPosition(6, 4, 198), new DefaultPosition(6, 42, 236));
+
+ Warning warning2 = new Warning(HardcodedValuesDetector.ISSUE,
+ "[I18N] Hardcoded string \"Fooo\", should use @string resource",
+ Severity.WARNING, project);
+ warning2.line = 11;
+ warning2.file = new File("/foo/bar/Foo/res/layout/main.xml");
+ warning2.errorLine = " (java.lang.String) android:text=\"Fooo\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n";
+ warning2.path = "res/layout/main.xml";
+ warning2.location = Location.create(warning2.file,
+ new DefaultPosition(11, 8, 377), new DefaultPosition(11, 27, 396));
+
+ List<Warning> warnings = new ArrayList<Warning>();
+ warnings.add(warning1);
+ warnings.add(warning2);
+
+ reporter.write(0, 2, warnings);
+
+ String report = Files.toString(new File(dir, "index.html"), Charsets.UTF_8);
+
+ // Replace the timestamp to make golden file comparison work
+ String timestampPrefix = "Check performed at ";
+ int begin = report.indexOf(timestampPrefix);
+ assertTrue(begin != -1);
+ begin += timestampPrefix.length();
+ int end = report.indexOf(".<br/>", begin);
+ assertTrue(end != -1);
+ report = report.substring(0, begin) + "$DATE" + report.substring(end);
+
+ // NOTE: If you change the output, please validate it manually in
+ // http://validator.w3.org/#validate_by_input
+ // before updating the following
+ assertEquals(""
+ + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ + "<head>\n"
+ + "<title>Lint Report</title>\n"
+ + "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://fonts.googleapis.com/css?family=Roboto\" />\n"
+ + "<link rel=\"stylesheet\" type=\"text/css\" href=\"index_files/hololike.css\" />\n"
+ + "</head>\n"
+ + "<body>\n"
+ + "<h1>Lint Report</h1>\n"
+ + "<div class=\"titleSeparator\"></div>\n"
+ + "Check performed at $DATE.<br/>\n"
+ + "0 errors and 2 warnings found:\n"
+ + "<br/><br/>\n"
+ + "<table class=\"overview\">\n"
+ + "<tr><th>Project</th><th class=\"countColumn\"><img border=\"0\" align=\"top\" src=\"index_files/lint-error.png\" alt=\"Error\" />\n"
+ + "Errors</th><th class=\"countColumn\"><img border=\"0\" align=\"top\" src=\"index_files/lint-warning.png\" alt=\"Warning\" />\n"
+ + "Warnings</th></tr>\n"
+ + "<tr><td><a href=\"Foo.html\">Foo</a></td><td class=\"countColumn\">0</td><td class=\"countColumn\">2</td></tr>\n"
+ + "</table>\n"
+ + "</body>\n"
+ + "</html>\n",
+ report);
+
+ assertTrue(new File(dir, "index_files" + File.separator + "hololike.css").exists());
+ assertTrue(new File(dir, "index_files" + File.separator + "lint-warning.png").exists());
+ assertTrue(new File(dir, "index_files" + File.separator + "lint-error.png").exists());
+ assertTrue(new File(dir, "Foo.html").exists());
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ dir.delete();
+ }
+ }
+
+ @Override
+ protected Detector getDetector() {
+ fail("Not used in this test");
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ReporterTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ReporterTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/ReporterTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/ReporterTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/TextReporterTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/TextReporterTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/TextReporterTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/TextReporterTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java
new file mode 100644
index 0000000..6466a01
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/WarningTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint;
+
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.UnusedResourceDetector;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class WarningTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new UnusedResourceDetector();
+ }
+
+ @Override
+ protected boolean isEnabled(Issue issue) {
+ return true;
+ }
+
+ public void testComparator() throws Exception {
+ File projectDir = getProjectDir(null, // Rename .txt files to .java
+ "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
+ "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
+ "AndroidManifest.xml",
+ "res/layout/accessibility.xml");
+
+ final AtomicReference<List<Warning>> warningsHolder = new AtomicReference<List<Warning>>();
+ TestLintClient lintClient = new TestLintClient() {
+ @Override
+ public String analyze(List<File> files) throws Exception {
+ //String analyze = super.analyze(files);
+ mDriver = new LintDriver(new CustomIssueRegistry(), this);
+ configureDriver(mDriver);
+ mDriver.analyze(new LintRequest(this, files).setScope(getLintScope(files)));
+ warningsHolder.set(mWarnings);
+ return null;
+ }
+ };
+ List<File> files = Collections.singletonList(projectDir);
+ lintClient.analyze(files);
+
+ List<Warning> warnings = warningsHolder.get();
+ Warning prev = null;
+ for (Warning warning : warnings) {
+ if (prev != null) {
+ boolean equals = warning.equals(prev);
+ assertEquals(equals, prev.equals(warning));
+ int compare = warning.compareTo(prev);
+ assertEquals(equals, compare == 0);
+ assertEquals(-compare, prev.compareTo(warning));
+ }
+ prev = warning;
+ }
+
+ Collections.sort(warnings);
+
+ Warning prev2 = prev;
+ prev = null;
+ for (Warning warning : warnings) {
+ if (prev != null && prev2 != null) {
+ assertTrue(warning.compareTo(prev) > 0);
+ assertTrue(prev.compareTo(prev2) > 0);
+ assertTrue(warning.compareTo(prev2) > 0);
+
+ assertTrue(prev.compareTo(warning) < 0);
+ assertTrue(prev2.compareTo(prev) < 0);
+ assertTrue(prev2.compareTo(warning) < 0);
+ }
+ prev2 = prev;
+ prev = warning;
+ }
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/XmlReporterTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/XmlReporterTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/XmlReporterTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/XmlReporterTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AccessibilityDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AccessibilityDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AccessibilityDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AccessibilityDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlarmDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlarmDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlarmDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlarmDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetectorTest.java
new file mode 100644
index 0000000..5531e3c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AllowAllHostnameVerifierDetectorTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "ImplicitArrayToString",
+ "MethodMayBeStatic"})
+public class AllowAllHostnameVerifierDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new AllowAllHostnameVerifierDetector();
+ }
+
+ public void testBroken() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/InsecureHostnameVerifier.java:22: Warning: Using the AllowAllHostnameVerifier HostnameVerifier is unsafe because it always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames [AllowAllHostnameVerifier]\n"
+ + " connection.setHostnameVerifier(new AllowAllHostnameVerifier());\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/InsecureHostnameVerifier.java:23: Warning: Using the ALLOW_ALL_HOSTNAME_VERIFIER HostnameVerifier is unsafe because it always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames [AllowAllHostnameVerifier]\n"
+ + " connection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <service\n"
+ + " android:name=\".InsecureHostnameVerifier\" >\n"
+ + " </service>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ java("src/test/pkg/InsecureHostnameVerifier.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "import java.io.IOException;\n"
+ + "import java.net.URL;\n"
+ + "import javax.net.ssl.HostnameVerifier;\n"
+ + "import javax.net.ssl.HttpsURLConnection;\n"
+ + "import javax.net.ssl.SSLContext;\n"
+ + "import javax.net.ssl.SSLSession;\n"
+ + "import javax.net.ssl.TrustManager;\n"
+ + "import javax.net.ssl.X509TrustManager;\n"
+ + "\n"
+ + "import org.apache.http.conn.ssl.SSLSocketFactory;\n"
+ + "import org.apache.http.conn.ssl.AllowAllHostnameVerifier;\n"
+ + "\n"
+ + "public class InsecureHostnameVerifier {\n"
+ + " protected void onHandleIntent(Intent intent) {\n"
+ + " try {\n"
+ + " URL url = new URL(\"https://www.google.com\");\n"
+ + " HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();\n"
+ + " connection.setHostnameVerifier(new AllowAllHostnameVerifier());\n"
+ + " connection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);\n"
+ + " } catch (IOException e) {\n"
+ + " System.out.println(e.getStackTrace());\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testCorrect() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <service\n"
+ + " android:name=\".ExampleHostnameVerifier\" >\n"
+ + " </service>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ java("src/test/pkg/ExampleHostnameVerifier.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "import java.io.IOException;\n"
+ + "import java.net.URL;\n"
+ + "import javax.net.ssl.HostnameVerifier;\n"
+ + "import javax.net.ssl.HttpsURLConnection;\n"
+ + "import javax.net.ssl.SSLContext;\n"
+ + "import javax.net.ssl.SSLSession;\n"
+ + "import javax.net.ssl.TrustManager;\n"
+ + "import javax.net.ssl.X509TrustManager;\n"
+ + "\n"
+ + "import org.apache.http.conn.ssl.SSLSocketFactory;\n"
+ + "import org.apache.http.conn.ssl.StrictHostnameVerifier;\n"
+ + "\n"
+ + "public class ExampleHostnameVerifier {\n"
+ + " protected void onHandleIntent(Intent intent) {\n"
+ + " try {\n"
+ + " URL url = new URL(\"https://www.google.com\");\n"
+ + " HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();\n"
+ + " connection.setHostnameVerifier(new StrictHostnameVerifier());\n"
+ + " connection.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);\n"
+ + " } catch (IOException e) {\n"
+ + " System.out.println(e.getStackTrace());\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlwaysShowActionDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlwaysShowActionDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlwaysShowActionDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AlwaysShowActionDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidAutoDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidAutoDetectorTest.java
new file mode 100644
index 0000000..b6424bd
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidAutoDetectorTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
+
+import com.android.annotations.NonNull;
+import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AndroidAutoDetectorTest extends AbstractCheckTest {
+
+ private final SdkTestCase.TestFile mValidAutomotiveDescriptor =
+ xml("res/xml/automotive_app_desc.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<automotiveApp>\n"
+ + " <uses name=\"media\"/>\n"
+ + "</automotiveApp>\n");
+
+ private SdkTestCase.TestFile mValidAutoAndroidXml = xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.android.uamp\">\n"
+ + "\n"
+ + " <application\n"
+ + " android:name=\".UAMPApplication\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/UAmpAppTheme\">\n"
+ + "\n"
+ + " <meta-data\n"
+ + " android:name=\"com.google.android.gms.car.application\"\n"
+ + " android:resource=\"@xml/automotive_app_desc\"/>\n"
+ + "\n"
+ + " <service\n"
+ + " android:name=\".MusicService\"\n"
+ + " android:exported=\"true\"\n"
+ + " tools:ignore=\"ExportedService\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.media.browse.MediaBrowserService\"/>\n"
+ + " </intent-filter>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.media.action.MEDIA_PLAY_FROM_SEARCH\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " </intent-filter>\n"
+ + " </service>\n"
+ + "\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n");
+
+ private Set<Issue> mEnabled = new HashSet<Issue>();
+
+ @Override
+ protected Detector getDetector() {
+ return new AndroidAutoDetector();
+ }
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && mEnabled.contains(issue);
+ }
+ };
+ }
+
+ public void testMissingIntentFilter() throws Exception {
+ mEnabled = Collections.singleton(
+ AndroidAutoDetector.MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE);
+ String expected = "AndroidManifest.xml:6: Error: Missing intent-filter for action android.media.browse.MediaBrowserService that is required for android auto support [MissingMediaBrowserServiceIntentFilter]\n"
+ + " <application\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n";
+ String result = lintProject(
+ xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.android.uamp\">\n"
+ + "\n"
+ + " <application\n"
+ + " android:name=\".UAMPApplication\"\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/UAmpAppTheme\"\n"
+ + " android:banner=\"@drawable/banner_tv\">\n"
+ + "\n"
+ + " <meta-data\n"
+ + " android:name=\"com.google.android.gms.car.application\"\n"
+ + " android:resource=\"@xml/automotive_app_desc\"/>\n"
+ + " <service\n"
+ + " android:name=\".MusicService\"\n"
+ + " android:exported=\"true\"\n"
+ + " tools:ignore=\"ExportedService\">\n"
+ + " </service>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ mValidAutomotiveDescriptor);
+
+ assertEquals(expected, result);
+ }
+
+ public void testInvalidUsesTagInMetadataFile() throws Exception {
+ mEnabled = Collections.singleton(AndroidAutoDetector.INVALID_USES_TAG_ISSUE);
+ String expected = "" +
+ "res/xml/automotive_app_desc.xml:3: Error: Expecting one of media or notification for the name attribute in uses tag. [InvalidUsesTagAttribute]\n"
+ + " <uses name=\"medias\"/>\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n";
+ String result = lintProject(
+ mValidAutoAndroidXml,
+ xml("res/xml/automotive_app_desc.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<automotiveApp>\n"
+ + " <uses name=\"medias\"/>\n"
+ + "</automotiveApp>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testMissingMediaSearchIntent() throws Exception {
+ mEnabled = Collections.singleton(
+ AndroidAutoDetector.MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH);
+ String expected = "AndroidManifest.xml:6: Error: Missing intent-filter for action android.media.action.MEDIA_PLAY_FROM_SEARCH. [MissingIntentFilterForMediaSearch]\n"
+ + " <application\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n";
+
+ String result = lintProject(
+ xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.android.uamp\">\n"
+ + "\n"
+ + " <application\n"
+ + " android:name=\".UAMPApplication\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/UAmpAppTheme\">\n"
+ + "\n"
+ + " <meta-data\n"
+ + " android:name=\"com.google.android.gms.car.application\"\n"
+ + " android:resource=\"@xml/automotive_app_desc\"/>\n"
+ + "\n"
+ + " <service\n"
+ + " android:name=\".MusicService\"\n"
+ + " android:exported=\"true\"\n"
+ + " tools:ignore=\"ExportedService\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.media.browse"
+ + ".MediaBrowserService\"/>\n"
+ + " </intent-filter>\n"
+ + " </service>\n"
+ + "\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ mValidAutomotiveDescriptor);
+ assertEquals(expected, result);
+ }
+
+ public void testMissingOnPlayFromSearch() throws Exception {
+ mEnabled = Collections.singleton(
+ AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH);
+
+ String expected = "src/com/example/android/uamp/MSessionCallback.java:5: Error: This class does not override onPlayFromSearch from MediaSession.Callback The method should be overridden and implemented to support Voice search on Android Auto. [MissingOnPlayFromSearch]\n"
+ + "public class MSessionCallback extends Callback {\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n";
+
+ String result = lintProject(
+ copy("bytecode/.classpath", ".classpath"),
+ mValidAutoAndroidXml,
+ mValidAutomotiveDescriptor,
+ java("src/com/example/android/uamp/MSessionCallback.java", ""
+ + "package com.example.android.uamp;\n"
+ + "\n"
+ + "import android.media.session.MediaSession.Callback;\n"
+ + "\n"
+ + "public class MSessionCallback extends Callback {\n"
+ + " @Override\n"
+ + " public void onPlay() {\n"
+ + " // custom impl\n"
+ + " }\n"
+ + "}\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testValidOnPlayFromSearch() throws Exception {
+ mEnabled = Collections.singleton(AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH);
+
+ String expected = "No warnings.";
+
+ String result = lintProject(
+ copy("bytecode/.classpath", ".classpath"),
+ mValidAutoAndroidXml,
+ mValidAutomotiveDescriptor,
+ java("src/com/example/android/uamp/MSessionCallback.java", ""
+ + "package com.example.android.uamp;\n"
+ + "\n"
+ + "import android.os.Bundle;\n"
+ + "\n"
+ + "import android.media.session.MediaSession.Callback;\n"
+ + "\n"
+ + "public class MSessionCallback extends Callback {\n"
+ + " @Override\n"
+ + " public void onPlayFromSearch(String query, Bundle bundle) {\n"
+ + " // custom impl\n"
+ + " }\n"
+ + "}\n"));
+ assertEquals(expected, result);
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidTvDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidTvDetectorTest.java
new file mode 100644
index 0000000..e5bcedf
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidTvDetectorTest.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML;
+import static com.android.tools.lint.checks.AndroidTvDetector.MISSING_BANNER;
+import static com.android.tools.lint.checks.AndroidTvDetector.MISSING_LEANBACK_LAUNCHER;
+import static com.android.tools.lint.checks.AndroidTvDetector.MISSING_LEANBACK_SUPPORT;
+import static com.android.tools.lint.checks.AndroidTvDetector.PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE;
+import static com.android.tools.lint.checks.AndroidTvDetector.UNSUPPORTED_TV_HARDWARE;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+ at SuppressWarnings("javadoc")
+public class AndroidTvDetectorTest extends AbstractCheckTest {
+
+ private Set<Issue> mEnabled = new HashSet<Issue>();
+
+ @Override
+ protected Detector getDetector() {
+ return new AndroidTvDetector();
+ }
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && mEnabled.contains(issue);
+ }
+ };
+ }
+
+ public void testInvalidNoLeanbackActivity() throws Exception {
+ mEnabled = Collections.singleton(MISSING_LEANBACK_LAUNCHER);
+ String expected = "AndroidManifest.xml:2: Error: Expecting an activity to have android.intent.category.LEANBACK_LAUNCHER intent filter. [MissingLeanbackLauncher]\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <application>\n"
+ + " <!-- Application contains an activity, but it isn't a leanback launcher activity -->\n"
+ + " <activity android:name=\"com.example.android.test.Activity\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.SEND\"/>\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\"/>\n"
+ + " <data android:mimeType=\"text/plain\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testValidLeanbackActivity() throws Exception {
+ mEnabled = Collections.singleton(MISSING_LEANBACK_LAUNCHER);
+ String expected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <application>\n"
+ + " <activity android:name=\"com.example.android.TvActivity\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testInvalidNoUsesFeatureLeanback() throws Exception {
+ mEnabled = Collections.singleton(MISSING_LEANBACK_SUPPORT);
+ String expected = "AndroidManifest.xml:2: Error: Expecting <uses-feature android:name=\"android.software.leanback\" android:required=\"false\" /> tag. [MissingLeanbackSupport]\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <application>\n"
+ + " <activity android:name=\"com.example.android.TvActivity\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testValidUsesFeatureLeanback() throws Exception {
+ mEnabled = Collections.singleton(MISSING_LEANBACK_SUPPORT);
+ String expected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\" android:required=\"false\" />\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testInvalidUnsupportedHardware() throws Exception {
+ mEnabled = Collections.singleton(UNSUPPORTED_TV_HARDWARE);
+ String expected = "AndroidManifest.xml:6: Error: Expecting android:required=\"false\" for this hardware feature that may not be supported by all Android TVs. [UnsupportedTvHardware]\n"
+ + " android:name=\"android.hardware.touchscreen\" android:required=\"true\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <uses-feature\n"
+ + " android:name=\"android.hardware.touchscreen\" android:required=\"true\"/>\n"
+ + "\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testValidUnsupportedHardware() throws Exception {
+ mEnabled = Collections.singleton(UNSUPPORTED_TV_HARDWARE);
+ String expected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature\n"
+ + " android:name=\"android.hardware.touchscreen\"\n"
+ + " android:required=\"false\" />\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testValidPermissionImpliesNotMissingUnsupportedHardware() throws Exception {
+ mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+ String expected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + " <uses-feature android:required=\"false\" android:name=\"android.hardware.telephony\"/>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testInvalidPermissionImpliesNotMissingUnsupportedHardware() throws Exception {
+ mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+ String expected = "AndroidManifest.xml:5: Warning: Permission exists without corresponding hardware <uses-feature android:name=\"android.hardware.telephony\" required=\"false\"> tag. [PermissionImpliesUnsupportedHardware]\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testInvalidPermissionImpliesMissingUnsupportedHardware() throws Exception {
+ mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+ String expected = "AndroidManifest.xml:5: Warning: Permission exists without corresponding hardware <uses-feature android:name=\"android.hardware.telephony\" required=\"false\"> tag. [PermissionImpliesUnsupportedHardware]\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testValidPermissionImpliesUnsupportedHardware() throws Exception {
+ mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+ String expected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testBannerMissingInApplicationTag() throws Exception {
+ mEnabled = Collections.singleton(MISSING_BANNER);
+ String expected = "AndroidManifest.xml:5: Warning: Expecting android:banner with the <application> tag or each Leanback launcher activity. [MissingTvBanner]\n"
+ + " <application>\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <application>\n"
+ + " <activity>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testBannerInLeanbackLauncherActivity() throws Exception {
+ mEnabled = Collections.singleton(MISSING_BANNER);
+ String expected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <application>\n"
+ + " <activity android:banner=\"@drawable/banner\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testBannerInApplicationTag() throws Exception {
+ mEnabled = Collections.singleton(MISSING_BANNER);
+ String expected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <application android:banner=\"@drawable/banner\">\n"
+ + " <activity>\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ // Implicit trigger tests
+
+ public void testLeanbackSupportTrigger() throws Exception {
+ // Expect some issue to be raised when there is the leanback support trigger.
+ mEnabled = Collections.singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+ String notExpected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-feature android:name=\"android.software.leanback\"/>\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + "</manifest>\n"));
+ assertNotSame(notExpected, result);
+
+ // Expect no warnings when there is no trigger.
+ mEnabled = Collections
+ .singleton(PERMISSION_IMPLIES_UNSUPPORTED_HARDWARE);
+ String expected = "No warnings.";
+ result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+
+ public void testLeanbackLauncherTrigger() throws Exception {
+ mEnabled = Collections
+ .singleton(MISSING_LEANBACK_SUPPORT);
+ String notExpected = "No warnings.";
+ String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <application>\n"
+ + " <activity android:name=\"com.example.android.TvActivity\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>\n"));
+ assertNotSame(notExpected, result);
+
+ // Expect no warnings when there is no trigger.
+ mEnabled = Collections.singleton(MISSING_LEANBACK_SUPPORT);
+ String expected = "No warnings.";
+ result = lintProject(xml(FN_ANDROID_MANIFEST_XML, ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + " <uses-permission android:name=\"android.permission.CALL_PHONE\"/>\n"
+ + "</manifest>\n"));
+ assertEquals(expected, result);
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java
new file mode 100644
index 0000000..e84db85
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AnnotationDetectorTest.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.AnnotationDetector.SWITCH_TYPE_DEF;
+import static com.android.tools.lint.checks.AnnotationDetector.getMissingCases;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.TextFormat;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "UnnecessaryLocalVariable",
+ "ConstantConditionalExpression", "StatementWithEmptyBody", "RedundantCast",
+ "MethodMayBeStatic"})
+public class AnnotationDetectorTest extends AbstractCheckTest {
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/WrongAnnotation.java:9: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
+ " public static void foobar(View view, @SuppressLint(\"NewApi\") int foo) { // Invalid: class-file check\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WrongAnnotation.java:10: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
+ " @SuppressLint(\"NewApi\") // Invalid\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WrongAnnotation.java:12: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
+ " @SuppressLint({\"SdCardPath\", \"NewApi\"}) // Invalid: class-file based check on local variable\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WrongAnnotation.java:14: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
+ " @android.annotation.SuppressLint({\"SdCardPath\", \"NewApi\"}) // Invalid (FQN)\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WrongAnnotation.java:28: Error: The @SuppressLint annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method [LocalSuppress]\n" +
+ " @SuppressLint(\"NewApi\")\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "5 errors, 0 warnings\n",
+
+ lintProject(
+ "src/test/pkg/WrongAnnotation.java.txt=>src/test/pkg/WrongAnnotation.java"
+ ));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testUniqueValues() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/IntDefTest.java:9: Error: Constants STYLE_NO_INPUT and STYLE_NO_FRAME specify the same exact value (2); this is usually a cut & paste or merge error [UniqueConstants]\n"
+ + " @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + " src/test/pkg/IntDefTest.java:9: Previous same value\n"
+ + "src/test/pkg/IntDefTest.java:28: Error: Constants FLAG3 and FLAG2 specify the same exact value (562949953421312); this is usually a cut & paste or merge error [UniqueConstants]\n"
+ + " @IntDef({FLAG2, FLAG3, FLAG1})\n"
+ + " ~~~~~\n"
+ + " src/test/pkg/IntDefTest.java:28: Previous same value\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/IntDefTest.java", ""
+ + "package test.pkg;\n"
+ + "import android.support.annotation.IntDef;\n"
+ + "import android.annotation.SuppressLint;\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "@SuppressLint(\"UnusedDeclaration\")\n"
+ + "public class IntDefTest {\n"
+ + " @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface DialogStyle {}\n"
+ + "\n"
+ + " public static final int STYLE_NORMAL = 0;\n"
+ + " public static final int STYLE_NO_TITLE = 1;\n"
+ + " public static final int STYLE_NO_FRAME = 2;\n"
+ + " public static final int STYLE_NO_INPUT = 2;\n"
+ + "\n"
+ + " @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})\n"
+ + " @SuppressWarnings(\"UniqueConstants\")\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface SuppressedDialogStyle {}\n"
+ + "\n"
+ + "\n"
+ + " public static final long FLAG1 = 0x100000000000L;\n"
+ + " public static final long FLAG2 = 0x0002000000000000L;\n"
+ + " public static final long FLAG3 = 0x2000000000000L;\n"
+ + "\n"
+ + " @IntDef({FLAG2, FLAG3, FLAG1})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface Flags {}\n"
+ + "\n"
+
+ + ""
+ + "}"),
+ copy("src/android/support/annotation/IntDef.java.txt",
+ "src/android/support/annotation/IntDef.java")));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testFlagStyle() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/IntDefTest.java:13: Warning: Consider declaring this constant using 1 << 44 instead [ShiftFlags]\n"
+ + " public static final long FLAG5 = 0x100000000000L;\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:14: Warning: Consider declaring this constant using 1 << 49 instead [ShiftFlags]\n"
+ + " public static final long FLAG6 = 0x0002000000000000L;\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:15: Warning: Consider declaring this constant using 1 << 3 instead [ShiftFlags]\n"
+ + " public static final long FLAG7 = 8L;\n"
+ + " ~~\n"
+ + "0 errors, 3 warnings\n",
+ lintProject(
+ java("src/test/pkg/IntDefTest.java", ""
+ + "package test.pkg;\n"
+ + "import android.support.annotation.IntDef;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "@SuppressWarnings(\"unused\")\n"
+ + "public class IntDefTest {\n"
+ + " public static final long FLAG1 = 1;\n"
+ + " public static final long FLAG2 = 2;\n"
+ + " public static final long FLAG3 = 1 << 2;\n"
+ + " public static final long FLAG4 = 1 << 3;\n"
+ + " public static final long FLAG5 = 0x100000000000L;\n"
+ + " public static final long FLAG6 = 0x0002000000000000L;\n"
+ + " public static final long FLAG7 = 8L;\n"
+ + " public static final long FLAG8 = 9L;\n"
+ + " public static final long FLAG9 = 0;\n"
+ + " public static final long FLAG10 = 1;\n"
+ + " public static final long FLAG11 = -1;\n"
+ + "\n"
+ // Not a flag (missing flag=true)
+ + " @IntDef({FLAG1, FLAG2, FLAG3})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface Flags1 {}\n"
+ + "\n"
+ // OK: Too few values
+ + " @IntDef(flag = true, value={FLAG1, FLAG2})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface Flags2 {}\n"
+ + "\n"
+ // OK: Allow 0, 1, -1
+ + " @IntDef(flag = true, value={FLAG9, FLAG10, FLAG11})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface Flags3 {}\n"
+ + "\n"
+ // OK: Already using shifts
+ + " @IntDef(flag = true, value={FLAG1, FLAG3, FLAG4})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface Flags4 {}\n"
+ + "\n"
+ // Wrong: should be flagged
+ + " @IntDef(flag = true, value={FLAG5, FLAG6, FLAG7, FLAG8})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " private @interface Flags5 {}\n"
+ + "}"),
+ copy("src/android/support/annotation/IntDef.java.txt",
+ "src/android/support/annotation/IntDef.java")));
+ }
+
+ public void testMissingIntDefSwitchConstants() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/X.java:40: Warning: Don't use a constant here; expected one of: LENGTH_INDEFINITE, LENGTH_LONG, LENGTH_SHORT [SwitchIntDef]\n"
+ + " case 5:\n"
+ + " ~\n"
+ + "src/test/pkg/X.java:47: Warning: Switch statement on an int with known associated constant missing case LENGTH_LONG [SwitchIntDef]\n"
+ + " switch (duration) {\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/X.java:56: Warning: Switch statement on an int with known associated constant missing case LENGTH_INDEFINITE, LENGTH_LONG, LENGTH_SHORT [SwitchIntDef]\n"
+ + " switch (duration) {\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/X.java:66: Warning: Switch statement on an int with known associated constant missing case LENGTH_SHORT [SwitchIntDef]\n"
+ + " switch (duration) {\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/X.java:75: Warning: Switch statement on an int with known associated constant missing case LENGTH_SHORT [SwitchIntDef]\n"
+ + " switch ((int)getDuration()) {\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/X.java:85: Warning: Switch statement on an int with known associated constant missing case LENGTH_SHORT [SwitchIntDef]\n"
+ + " switch (true ? getDuration() : 0) {\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/X.java:95: Warning: Switch statement on an int with known associated constant missing case X.LENGTH_SHORT [SwitchIntDef]\n"
+ + " switch (X.getDuration()) {\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/X.java:104: Warning: Switch statement on an int with known associated constant missing case LENGTH_INDEFINITE [SwitchIntDef]\n"
+ + " switch (duration) {\n"
+ + " ~~~~~~\n"
+ + "0 errors, 8 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/X.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.annotation.SuppressLint;\n"
+ + "import android.support.annotation.IntDef;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "@SuppressWarnings({\"UnusedParameters\", \"unused\", \"SpellCheckingInspection\", \"RedundantCast\"})\n"
+ + "public class X {\n"
+ + " @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " public @interface Duration {\n"
+ + " }\n"
+ + "\n"
+ + " public static final int LENGTH_INDEFINITE = -2;\n"
+ + " public static final int LENGTH_SHORT = -1;\n"
+ + " public static final int LENGTH_LONG = 0;\n"
+ + "\n"
+ + " public void setDuration(@Duration int duration) {\n"
+ + " }\n"
+ + "\n"
+ + " @Duration\n"
+ + " public static int getDuration() {\n"
+ + " return LENGTH_INDEFINITE;\n"
+ + " }\n"
+ + "\n"
+ + " public static void testOk(@Duration int duration) {\n"
+ + " switch (duration) {\n"
+ + " case LENGTH_SHORT:\n"
+ + " case LENGTH_LONG:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static void testLiteral(@Duration int duration) {\n"
+ + " switch (duration) {\n"
+ + " case LENGTH_SHORT:\n"
+ + " case 5:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static void testParameter(@Duration int duration) {\n"
+ + " switch (duration) {\n"
+ + " case LENGTH_SHORT:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static void testMissingAll(@Duration int duration) {\n"
+ + " // We don't flag these; let the IDE's normal \"empty switch\" check flag it\n"
+ + " switch (duration) {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+ + " public static void testLocalVariableFlow() {\n"
+ + " int intermediate = getDuration();\n"
+ + " int duration = intermediate;\n"
+ + "\n"
+ + " // Missing LENGTH_SHORT\n"
+ + " switch (duration) {\n"
+ + " case LENGTH_LONG:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static void testMethodCall() {\n"
+ + " // Missing LENGTH_SHORT\n"
+ + " switch ((int)getDuration()) {\n"
+ + " case LENGTH_LONG:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @SuppressWarnings(\"ConstantConditionalExpression\")\n"
+ + " public static void testInline() {\n"
+ + " // Missing LENGTH_SHORT\n"
+ + " switch (true ? getDuration() : 0) {\n"
+ + " case LENGTH_LONG:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " private static class SomeOtherClass {\n"
+ + " private void method() {\n"
+ + " // Missing LENGTH_SHORT\n"
+ + " switch (X.getDuration()) {\n"
+ + " case LENGTH_LONG:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static void testMissingWithDefault(@Duration int duration) {\n"
+ + " switch (duration) {\n"
+ + " case LENGTH_SHORT:\n"
+ + " case LENGTH_LONG:\n"
+ + " default:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @SuppressLint(\"SwitchIntDef\")\n"
+ + " public static void testSuppressAnnotation(@Duration int duration) {\n"
+ + " switch (duration) {\n"
+ + " case LENGTH_SHORT:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static void testSuppressComment(@Duration int duration) {\n"
+ + " //noinspection AndroidLintSwitchIntDef\n"
+ + " switch (duration) {\n"
+ + " case LENGTH_SHORT:\n"
+ + " case LENGTH_INDEFINITE:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ copy("src/android/support/annotation/IntDef.java.txt",
+ "src/android/support/annotation/IntDef.java")
+ ));
+ }
+
+
+ public void testMissingSwitchFailingIntDef() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/X.java:8: Warning: Switch statement on an int with known associated constant missing case EXACTLY, UNSPECIFIED [SwitchIntDef]\n"
+ + " switch (val) {\n"
+ + " ~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ java("src/test/pkg/X.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.view.View;"
+ + "\n"
+ + "public class X {\n"
+ + "\n"
+ + " public void measure(int mode) {\n"
+ + " int val = View.MeasureSpec.getMode(mode);\n"
+ + " switch (val) {\n"
+ + " case View.MeasureSpec.AT_MOST:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ copy("bytecode/.classpath", ".classpath")));
+ }
+
+ public void testGetEnumCases() {
+ assertEquals(
+ Arrays.asList("LENGTH_INDEFINITE", "LENGTH_SHORT", "LENGTH_LONG"),
+ getMissingCases("Don't use a constant here; expected one of: LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG",
+ TextFormat.TEXT));
+ assertEquals(
+ Collections.singletonList("LENGTH_SHORT"),
+ getMissingCases("Switch statement on an int with known associated constant missing case LENGTH_SHORT",
+ TextFormat.TEXT));
+ }
+
+ public void testMatchEcjAndExternalFieldNames() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(java("src/test/pkg/MissingEnum.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.net.wifi.WifiManager;\n"
+ + "\n"
+ + "public class MissingEnum {\n"
+ + " private WifiManager mWifiManager;\n"
+ + "\n"
+ + " private void updateAccessPoints() {\n"
+ + " final int wifiState = mWifiManager.getWifiState();\n"
+ + " switch (wifiState) {\n"
+ + " case WifiManager.WIFI_STATE_ENABLING:\n"
+ + " break;\n"
+ + " case WifiManager.WIFI_STATE_ENABLED:\n"
+ + " break;\n"
+ + " case WifiManager.WIFI_STATE_DISABLING:\n"
+ + " break;\n"
+ + " case WifiManager.WIFI_STATE_DISABLED:\n"
+ + " break;\n"
+ + " case WifiManager.WIFI_STATE_UNKNOWN:\n"
+ + " break;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")));
+ }
+
+ @Override
+ protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+ @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+ if (issue == SWITCH_TYPE_DEF) {
+ assertNotNull("Could not extract message tokens from " + message,
+ getMissingCases(message, TEXT));
+ }
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new AnnotationDetector();
+ }
+
+ @Override
+ protected List<Issue> getIssues() {
+ List<Issue> issues = super.getIssues();
+
+ // Need these issues on to be found by the registry as well to look up scope
+ // in id references (these ids are referenced in the unit test java file below)
+ issues.add(ApiDetector.UNSUPPORTED);
+ issues.add(SdCardDetector.ISSUE);
+
+ return issues;
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
new file mode 100644
index 0000000..a2b20e9
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
@@ -0,0 +1,2237 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.ApiDetector.INLINED;
+import static com.android.tools.lint.checks.ApiDetector.UNSUPPORTED;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.repository.Revision;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+ at SuppressWarnings("javadoc")
+public class ApiDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ApiDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void testXmlApi1() throws Exception {
+ assertEquals(
+ "res/color/colors.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
+ " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
+ " ^\n" +
+ "res/layout/layout.xml:9: Error: View requires API level 5 (current min is 1): <QuickContactBadge> [NewApi]\n" +
+ " <QuickContactBadge\n" +
+ " ^\n" +
+ "res/layout/layout.xml:15: Error: View requires API level 11 (current min is 1): <CalendarView> [NewApi]\n" +
+ " <CalendarView\n" +
+ " ^\n" +
+ "res/layout/layout.xml:21: Error: View requires API level 14 (current min is 1): <GridLayout> [NewApi]\n" +
+ " <GridLayout\n" +
+ " ^\n" +
+ "res/layout/layout.xml:22: Error: @android:attr/actionBarSplitStyle requires API level 14 (current min is 1) [NewApi]\n" +
+ " foo=\"@android:attr/actionBarSplitStyle\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout.xml:23: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
+ " bar=\"@android:color/holo_red_light\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values/themes.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
+ " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
+ " ^\n" +
+ "7 errors, 0 warnings\n" +
+ "",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml"
+ ));
+ }
+
+ public void testXmlApi2() throws Exception {
+ assertEquals(""
+ + "res/layout/textureview.xml:8: Error: View requires API level 14 (current min is 1): <TextureView> [NewApi]\n"
+ + " <TextureView\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "res/layout/textureview.xml=>res/layout/textureview.xml"
+ ));
+ }
+
+ public void testTag() throws Exception {
+ assertEquals(""
+ + "res/layout/tag.xml:12: Warning: <tag> is only used in API level 21 and higher (current min is 1) [UnusedAttribute]\n"
+ + " <tag id=\"@+id/test\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "res/layout/tag.xml=>res/layout/tag.xml"
+ ));
+ }
+
+ public void testAttrWithoutSlash() throws Exception {
+ assertEquals(""
+ + "res/layout/attribute.xml:4: Error: ?android:indicatorStart requires API level 18 (current min is 1) [NewApi]\n"
+ + " android:enabled=\"?android:indicatorStart\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/attribute.xml=>res/layout/attribute.xml"
+ ));
+ }
+
+ public void testUnusedAttributes() throws Exception {
+ assertEquals(""
+ + "res/layout/divider.xml:9: Warning: Attribute showDividers is only used in API level 11 and higher (current min is 4) [UnusedAttribute]\n"
+ + " android:showDividers=\"middle\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/layout/labelfor.xml",
+ "res/layout/edit_textview.xml",
+ "apicheck/divider.xml=>res/layout/divider.xml"
+ ));
+ }
+
+ public void testUnusedOnSomeVersions1() throws Exception {
+ assertEquals(""
+ + "res/layout/attribute2.xml:4: Error: switchTextAppearance requires API level 14 (current min is 1), but note that attribute editTextColor is only used in API level 11 and higher [NewApi]\n"
+ + " android:editTextColor=\"?android:switchTextAppearance\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/attribute2.xml:4: Warning: Attribute editTextColor is only used in API level 11 and higher (current min is 1) [UnusedAttribute]\n"
+ + " android:editTextColor=\"?android:switchTextAppearance\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/attribute2.xml=>res/layout/attribute2.xml"
+ ));
+ }
+
+ public void testRtlManifestAttribute() throws Exception {
+ // Treat the manifest RTL attribute in the same was as the layout start/end attributes:
+ // these are known to be benign on older platforms, so don't flag it.
+ assertEquals("No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.bytecode\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"1\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:supportsRtl='true'\n"
+
+ // Ditto for the fullBackupContent attribute. If you're targeting
+ // 23, you'll want to use it, but it's not an error that older
+ // platforms aren't looking at it.
+
+ + " android:fullBackupContent='false'\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")
+
+ )
+ );
+ }
+
+ public void testXmlApi() throws Exception {
+ assertEquals(""
+ + "res/layout/attribute2.xml:4: Error: ?android:switchTextAppearance requires API level 14 (current min is 11) [NewApi]\n"
+ + " android:editTextColor=\"?android:switchTextAppearance\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk11.xml=>AndroidManifest.xml",
+ "apicheck/attribute2.xml=>res/layout/attribute2.xml"
+ ));
+ }
+
+ public void testReportAttributeName() throws Exception {
+ assertEquals("res/layout/layout.xml:13: Warning: Attribute layout_row is only used in API level 14 and higher (current min is 4) [UnusedAttribute]\n"
+ + " android:layout_row=\"2\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/layoutattr.xml=>res/layout/layout.xml"
+ ));
+ }
+
+ public void testXmlApi14() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml"
+ ));
+ }
+
+ public void testXmlApiIceCreamSandwich() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minics.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml"
+ ));
+ }
+
+ public void testXmlApi1TargetApi() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/layout_targetapi.xml=>res/layout/layout.xml"
+ ));
+ }
+
+ public void testXmlApiFolderVersion11() throws Exception {
+ assertEquals(
+ "res/color-v11/colors.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
+ " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
+ " ^\n" +
+ "res/layout-v11/layout.xml:21: Error: View requires API level 14 (current min is 1): <GridLayout> [NewApi]\n" +
+ " <GridLayout\n" +
+ " ^\n" +
+ "res/layout-v11/layout.xml:22: Error: @android:attr/actionBarSplitStyle requires API level 14 (current min is 1) [NewApi]\n" +
+ " foo=\"@android:attr/actionBarSplitStyle\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout-v11/layout.xml:23: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
+ " bar=\"@android:color/holo_red_light\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values-v11/themes.xml:9: Error: @android:color/holo_red_light requires API level 14 (current min is 1) [NewApi]\n" +
+ " <item name=\"android:windowBackground\"> @android:color/holo_red_light </item>\n" +
+ " ^\n" +
+ "5 errors, 0 warnings\n" +
+ "",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout-v11/layout.xml",
+ "apicheck/themes.xml=>res/values-v11/themes.xml",
+ "apicheck/themes.xml=>res/color-v11/colors.xml"
+ ));
+ }
+
+ public void testXmlApiFolderVersion14() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout-v14/layout.xml",
+ "apicheck/themes.xml=>res/values-v14/themes.xml",
+ "apicheck/themes.xml=>res/color-v14/colors.xml"
+ ));
+ }
+
+ public void testThemeVersion() throws Exception {
+ assertEquals(""
+ + "res/values/themes3.xml:3: Error: android:Theme.Holo.Light.DarkActionBar requires API level 14 (current min is 4) [NewApi]\n"
+ + " <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/values/themes3.xml"
+ ));
+ }
+
+ public void testApi1() throws Exception {
+ assertEquals(
+ "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
+ " getActionBar(); // API 11\n" +
+ " ~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
+ " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:27: Error: Call requires API level 3 (current min is 1): android.widget.Chronometer#getOnChronometerTickListener [NewApi]\n" +
+ " chronometer.getOnChronometerTickListener(); // API 3 \n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 1): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
+ " chronometer.setTextIsSelectable(true); // API 11\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
+ " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
+ " ~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
+ " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
+ " ~~~~~~~~~~~\n" +
+ // Note: the above error range is wrong; should be pointing to the second
+ "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
+ " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
+ " ~~~~~~~\n" +
+ "7 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ ));
+ }
+
+ public void testApi2() throws Exception {
+ assertEquals(
+ "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 2): android.app.Activity#getActionBar [NewApi]\n" +
+ " getActionBar(); // API 11\n" +
+ " ~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 2): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
+ " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:27: Error: Call requires API level 3 (current min is 2): android.widget.Chronometer#getOnChronometerTickListener [NewApi]\n" +
+ " chronometer.getOnChronometerTickListener(); // API 3 \n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 2): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
+ " chronometer.setTextIsSelectable(true); // API 11\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 2): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
+ " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
+ " ~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 2): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
+ " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
+ " ~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 2): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
+ " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
+ " ~~~~~~~\n" +
+ "7 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk2.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ ));
+ }
+
+ public void testApi4() throws Exception {
+ assertEquals(
+ "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 4): android.app.Activity#getActionBar [NewApi]\n" +
+ " getActionBar(); // API 11\n" +
+ " ~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 4): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
+ " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 4): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
+ " chronometer.setTextIsSelectable(true); // API 11\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 4): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
+ " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
+ " ~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 4): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
+ " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
+ " ~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
+ " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
+ " ~~~~~~~\n" +
+ "6 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ ));
+ }
+
+ public void testApi10() throws Exception {
+ assertEquals(
+ "src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 10): android.app.Activity#getActionBar [NewApi]\n" +
+ " getActionBar(); // API 11\n" +
+ " ~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:30: Error: Call requires API level 11 (current min is 10): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
+ " chronometer.setTextIsSelectable(true); // API 11\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 10): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
+ " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
+ " ~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 10): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
+ " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
+ " ~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 10): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
+ " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
+ " ~~~~~~~\n" +
+ "5 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk10.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ ));
+ }
+
+ public void testApi14() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ ));
+ }
+
+ public void testInheritStatic() throws Exception {
+ assertEquals(
+ "src/foo/bar/ApiCallTest5.java:16: Error: Call requires API level 11 (current min is 2): android.view.View#resolveSizeAndState [NewApi]\n" +
+ " int measuredWidth = View.resolveSizeAndState(widthMeasureSpec,\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest5.java:18: Error: Call requires API level 11 (current min is 2): android.view.View#resolveSizeAndState [NewApi]\n" +
+ " int measuredHeight = resolveSizeAndState(heightMeasureSpec,\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest5.java:20: Error: Call requires API level 11 (current min is 2): android.view.View#combineMeasuredStates [NewApi]\n" +
+ " View.combineMeasuredStates(0, 0);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiCallTest5.java:21: Error: Call requires API level 11 (current min is 2): android.view.View#combineMeasuredStates [NewApi]\n" +
+ " ApiCallTest5.combineMeasuredStates(0, 0);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "4 errors, 0 warnings\n" +
+ "",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk2.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest5.java.txt=>src/foo/bar/ApiCallTest5.java",
+ "apicheck/ApiCallTest5.class.data=>bin/classes/foo/bar/ApiCallTest5.class"
+ ));
+ }
+
+ public void testInheritLocal() throws Exception {
+ // Test virtual dispatch in a local class which extends some other local class (which
+ // in turn extends an Android API)
+ assertEquals(
+ "src/test/pkg/ApiCallTest3.java:10: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
+ " getActionBar(); // API 11\n" +
+ " ~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n" +
+ "",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java",
+ "apicheck/ApiCallTest3.java.txt=>src/test/pkg/ApiCallTest3.java",
+ "apicheck/ApiCallTest3.class.data=>bin/classes/test/pkg/ApiCallTest3.class",
+ "apicheck/Intermediate.class.data=>bin/classes/test/pkg/Intermediate.class"
+ ));
+ }
+
+ public void testViewClassLayoutReference() throws Exception {
+ assertEquals(
+ "res/layout/view.xml:9: Error: View requires API level 5 (current min is 1): <QuickContactBadge> [NewApi]\n" +
+ " <view\n" +
+ " ^\n" +
+ "res/layout/view.xml:16: Error: View requires API level 11 (current min is 1): <CalendarView> [NewApi]\n" +
+ " <view\n" +
+ " ^\n" +
+ "res/layout/view.xml:24: Error: ?android:attr/dividerHorizontal requires API level 11 (current min is 1) [NewApi]\n" +
+ " unknown=\"?android:attr/dividerHorizontal\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/view.xml:25: Error: ?android:attr/textColorLinkInverse requires API level 11 (current min is 1) [NewApi]\n" +
+ " android:textColor=\"?android:attr/textColorLinkInverse\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "4 errors, 0 warnings\n" +
+ "",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/view.xml=>res/layout/view.xml"
+ ));
+ }
+
+ public void testIOException() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=35190
+ assertEquals(
+ "src/test/pkg/ApiCallTest6.java:8: Error: Call requires API level 9 (current min is 1): new java.io.IOException [NewApi]\n" +
+ " IOException ioException = new IOException(throwable);\n" +
+ " ~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java",
+ "apicheck/ApiCallTest6.java.txt=>src/test/pkg/ApiCallTest6.java",
+ "apicheck/ApiCallTest6.class.data=>bin/classes/test/pkg/ApiCallTest6.class"
+ ));
+ }
+
+ // Test suppressing errors -- on classes, methods etc.
+
+ public void testSuppress() throws Exception {
+ assertEquals(
+ // These errors are correctly -not- suppressed because they
+ // appear in method3 (line 74-98) which is annotated with a
+ // @SuppressLint annotation specifying only an unrelated issue id
+
+ "src/foo/bar/SuppressTest1.java:76: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
+ " getActionBar(); // API 11\n" +
+ " ~~~~~~~~~~~~\n" +
+ "src/foo/bar/SuppressTest1.java:80: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
+ " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/SuppressTest1.java:83: Error: Call requires API level 3 (current min is 1): android.widget.Chronometer#getOnChronometerTickListener [NewApi]\n" +
+ " chronometer.getOnChronometerTickListener(); // API 3\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/SuppressTest1.java:86: Error: Call requires API level 11 (current min is 1): android.widget.Chronometer#setTextIsSelectable [NewApi]\n" +
+ " chronometer.setTextIsSelectable(true); // API 11\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/SuppressTest1.java:89: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
+ " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
+ " ~~~~~~~~~~~~~\n" +
+ "src/foo/bar/SuppressTest1.java:94: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
+ " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
+ " ~~~~~~~~~~~\n" +
+ "src/foo/bar/SuppressTest1.java:97: Error: Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
+ " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
+ " ~~~~~~~\n" +
+
+ // Note: These annotations are within the methods, not ON the methods, so they have
+ // no effect (because they don't end up in the bytecode)
+
+
+ "src/foo/bar/SuppressTest4.java:19: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
+ " BatteryInfo batteryInfo = report.batteryInfo;\n" +
+ " ~~~~~~~~~~~\n" +
+ "8 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/SuppressTest1.java.txt=>src/foo/bar/SuppressTest1.java",
+ "apicheck/SuppressTest1.class.data=>bin/classes/foo/bar/SuppressTest1.class",
+ "apicheck/SuppressTest2.java.txt=>src/foo/bar/SuppressTest2.java",
+ "apicheck/SuppressTest2.class.data=>bin/classes/foo/bar/SuppressTest2.class",
+ "apicheck/SuppressTest3.java.txt=>src/foo/bar/SuppressTest3.java",
+ "apicheck/SuppressTest3.class.data=>bin/classes/foo/bar/SuppressTest3.class",
+ "apicheck/SuppressTest4.java.txt=>src/foo/bar/SuppressTest4.java",
+ "apicheck/SuppressTest4.class.data=>bin/classes/foo/bar/SuppressTest4.class"
+ ));
+ }
+
+ public void testSuppressInnerClasses() throws Exception {
+ assertEquals(
+ // These errors are correctly -not- suppressed because they
+ // appear outside the middle inner class suppressing its own errors
+ // and its child's errors
+ "src/test/pkg/ApiCallTest4.java:9: Error: Call requires API level 14 (current min is 1): new android.widget.GridLayout [NewApi]\n" +
+ " new GridLayout(null, null, 0);\n" +
+ " ~~~~~~~~~~\n" +
+ "src/test/pkg/ApiCallTest4.java:38: Error: Call requires API level 14 (current min is 1): new android.widget.GridLayout [NewApi]\n" +
+ " new GridLayout(null, null, 0);\n" +
+ " ~~~~~~~~~~\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest4.java.txt=>src/test/pkg/ApiCallTest4.java",
+ "apicheck/ApiCallTest4.class.data=>bin/classes/test/pkg/ApiCallTest4.class",
+ "apicheck/ApiCallTest4$1.class.data=>bin/classes/test/pkg/ApiCallTest4$1.class",
+ "apicheck/ApiCallTest4$InnerClass1.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass1.class",
+ "apicheck/ApiCallTest4$InnerClass2.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass2.class",
+ "apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass1$InnerInnerClass1.class"
+ ));
+ }
+
+ public void testApiTargetAnnotation() throws Exception {
+ assertEquals(
+ "src/foo/bar/ApiTargetTest.java:13: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
+ " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiTargetTest.java:25: Error: Class requires API level 8 (current min is 4): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
+ " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/foo/bar/ApiTargetTest.java:39: Error: Class requires API level 8 (current min is 7): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
+ " Class<?> clz = DOMErrorHandler.class; // API 8\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "3 errors, 0 warnings\n" +
+ "",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/ApiTargetTest.java.txt=>src/foo/bar/ApiTargetTest.java",
+ "apicheck/ApiTargetTest.class.data=>bin/classes/foo/bar/ApiTargetTest.class",
+ "apicheck/ApiTargetTest$LocalClass.class.data=>bin/classes/foo/bar/ApiTargetTest$LocalClass.class"
+ ));
+ }
+
+ public void testTargetAnnotationInner() throws Exception {
+ assertEquals(
+ "src/test/pkg/ApiTargetTest2.java:32: Error: Call requires API level 14 (current min is 3): new android.widget.GridLayout [NewApi]\n" +
+ " new GridLayout(null, null, 0);\n" +
+ " ~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/ApiTargetTest2.java.txt=>src/test/pkg/ApiTargetTest2.java",
+ "apicheck/ApiTargetTest2.class.data=>bin/classes/test/pkg/ApiTargetTest2.class",
+ "apicheck/ApiTargetTest2$1.class.data=>bin/classes/test/pkg/ApiTargetTest2$1.class",
+ "apicheck/ApiTargetTest2$1$2.class.data=>bin/classes/test/pkg/ApiTargetTest2$1$2.class",
+ "apicheck/ApiTargetTest2$1$1.class.data=>bin/classes/test/pkg/ApiTargetTest2$1$1.class"
+ ));
+ }
+
+ public void testSuper() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=36384
+ assertEquals(
+ "src/test/pkg/ApiCallTest7.java:8: Error: Call requires API level 9 (current min is 4): new java.io.IOException [NewApi]\n" +
+ " super(message, cause); // API 9\n" +
+ " ~~~~~\n" +
+ "src/test/pkg/ApiCallTest7.java:12: Error: Call requires API level 9 (current min is 4): new java.io.IOException [NewApi]\n" +
+ " super.toString(); throw new IOException((Throwable) null); // API 9\n" +
+ " ~~~~~~~~~~~\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest7.java.txt=>src/test/pkg/ApiCallTest7.java",
+ "apicheck/ApiCallTest7.class.data=>bin/classes/test/pkg/ApiCallTest7.class"
+ ));
+ }
+
+ public void testEnums() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=36951
+ assertEquals(
+ "src/test/pkg/TestEnum.java:26: Error: Enum value requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
+ " case OVERLAY: {\n" +
+ " ~~~~~~~\n" +
+ "src/test/pkg/TestEnum.java:37: Error: Enum value requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
+ " case OVERLAY: {\n" +
+ " ~~~~~~~\n" +
+ "src/test/pkg/TestEnum.java:61: Error: Enum for switch requires API level 11 (current min is 4): android.renderscript.Element.DataType [NewApi]\n" +
+ " switch (type) {\n" +
+ " ^\n" +
+ "3 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/TestEnum.java.txt=>src/test/pkg/TestEnum.java",
+ "apicheck/TestEnum.class.data=>bin/classes/test/pkg/TestEnum.class"
+ ));
+ }
+
+ @Override
+ public String getSuperClass(Project project, String name) {
+ // For testInterfaceInheritance
+ if (name.equals("android/database/sqlite/SQLiteStatement")) {
+ return "android/database/sqlite/SQLiteProgram";
+ } else if (name.equals("android/database/sqlite/SQLiteProgram")) {
+ return "android/database/sqlite/SQLiteClosable";
+ } else if (name.equals("android/database/sqlite/SQLiteClosable")) {
+ return "java/lang/Object";
+ }
+ return null;
+ }
+
+ public void testInterfaceInheritance() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=38004
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/CloseTest.java.txt=>src/test/pkg/CloseTest.java",
+ "apicheck/CloseTest.class.data=>bin/classes/test/pkg/CloseTest.class"
+ ));
+ }
+
+ public void testInnerClassPositions() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=38113
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest8.java.txt=>src/test/pkg/ApiCallTest8.java",
+ "apicheck/ApiCallTest8.class.data=>bin/classes/test/pkg/ApiCallTest8.class"
+ ));
+ }
+
+ public void testManifestReferences() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:15: Error: @android:style/Theme.Holo requires API level 11 (current min is 4) [NewApi]\n" +
+ " android:theme=\"@android:style/Theme.Holo\" >\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/holomanifest.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testSuppressFieldAnnotations() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=38626
+ assertEquals(
+ "src/test/pkg/ApiCallTest9.java:9: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n" +
+ " private GridLayout field1 = new GridLayout(null);\n" +
+ " ~~~~~~~~~~\n" +
+ "src/test/pkg/ApiCallTest9.java:12: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n" +
+ " private static GridLayout field2 = new GridLayout(null);\n" +
+ " ~~~~~~~~~~\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest9.java.txt=>src/test/pkg/ApiCallTest9.java",
+ "apicheck/ApiCallTest9.class.data=>bin/classes/test/pkg/ApiCallTest9.class"
+ ));
+ }
+
+ public void test38195() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=38195
+ assertEquals(
+ "bin/classes/TestLint.class: Error: Call requires API level 16 (current min is 4): new android.database.SQLException [NewApi]\n" +
+ "bin/classes/TestLint.class: Error: Call requires API level 9 (current min is 4): java.lang.String#isEmpty [NewApi]\n" +
+ "bin/classes/TestLint.class: Error: Call requires API level 9 (current min is 4): new java.sql.SQLException [NewApi]\n" +
+ "3 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ /*
+ Compiled from "TestLint.java"
+ public class test.pkg.TestLint extends java.lang.Object{
+ public test.pkg.TestLint();
+ Code:
+ 0: aload_0
+ 1: invokespecial #8; //Method java/lang/Object."<init>":()V
+ 4: return
+
+ public void test(java.lang.Exception) throws java.lang.Exception;
+ Code:
+ 0: ldc #19; //String
+ 2: invokevirtual #21; //Method java/lang/String.isEmpty:()Z
+ 5: istore_2
+ 6: new #27; //class java/sql/SQLException
+ 9: dup
+ 10: ldc #29; //String error on upgrade:
+ 12: aload_1
+ 13: invokespecial #31; //Method java/sql/SQLException."<init>":
+ (Ljava/lang/String;Ljava/lang/Throwable;)V
+ 16: athrow
+
+ public void test2(java.lang.Exception) throws java.lang.Exception;
+ Code:
+ 0: new #39; //class android/database/SQLException
+ 3: dup
+ 4: ldc #29; //String error on upgrade:
+ 6: aload_1
+ 7: invokespecial #41; //Method android/database/SQLException.
+ "<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+ 10: athrow
+ }
+ */
+ "apicheck/TestLint.class.data=>bin/classes/TestLint.class"
+ ));
+ }
+
+ public void testAllowLocalMethodsImplementingInaccessible() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=39030
+ assertEquals(
+ "src/test/pkg/ApiCallTest10.java:40: Error: Call requires API level 14 (current min is 4): android.view.View#dispatchHoverEvent [NewApi]\n" +
+ " dispatchHoverEvent(null);\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest10.java.txt=>src/test/pkg/ApiCallTest10.java",
+ "apicheck/ApiCallTest10.class.data=>bin/classes/test/pkg/ApiCallTest10.class"
+ ));
+ }
+
+ public void testOverrideUnknownTarget() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest11.java.txt=>src/test/pkg/ApiCallTest11.java",
+ "apicheck/ApiCallTest11.class.data=>bin/classes/test/pkg/ApiCallTest11.class"
+ ));
+ }
+
+ public void testOverride() throws Exception {
+ assertEquals(
+ "src/test/pkg/ApiCallTest11.java:13: Error: This method is not overriding anything with the current build target, but will in API level 11 (current target is 3): test.pkg.ApiCallTest11#getActionBar [Override]\n" +
+ " public ActionBar getActionBar() {\n" +
+ " ~~~~~~~~~~~~\n" +
+ "src/test/pkg/ApiCallTest11.java:17: Error: This method is not overriding anything with the current build target, but will in API level 17 (current target is 3): test.pkg.ApiCallTest11#isDestroyed [Override]\n" +
+ " public boolean isDestroyed() {\n" +
+ " ~~~~~~~~~~~\n" +
+ "src/test/pkg/ApiCallTest11.java:39: Error: This method is not overriding anything with the current build target, but will in API level 11 (current target is 3): test.pkg.ApiCallTest11.MyLinear#setDividerDrawable [Override]\n" +
+ " public void setDividerDrawable(Drawable dividerDrawable) {\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "3 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties1=>project.properties",
+ "apicheck/ApiCallTest11.java.txt=>src/test/pkg/ApiCallTest11.java",
+ "apicheck/ApiCallTest11.class.data=>bin/classes/test/pkg/ApiCallTest11.class",
+ "apicheck/ApiCallTest11$MyLinear.class.data=>bin/classes/test/pkg/ApiCallTest11$MyLinear.class",
+ "apicheck/ApiCallTest11$MyActivity.class.data=>bin/classes/test/pkg/ApiCallTest11$MyActivity.class"
+ ));
+ }
+
+ public void testDateFormat() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=40876
+ assertEquals(
+ "src/test/pkg/ApiCallTest12.java:18: Error: Call requires API level 9 (current min is 4): java.text.DateFormatSymbols#getInstance [NewApi]\n" +
+ " new SimpleDateFormat(\"yyyy-MM-dd\", DateFormatSymbols.getInstance());\n" +
+ " ~~~~~~~~~~~\n" +
+ "src/test/pkg/ApiCallTest12.java:23: Error: The pattern character 'L' requires API level 9 (current min is 4) : \"yyyy-MM-dd LL\" [NewApi]\n" +
+ " new SimpleDateFormat(\"yyyy-MM-dd LL\", Locale.US);\n" +
+ " ^\n" +
+ "src/test/pkg/ApiCallTest12.java:25: Error: The pattern character 'c' requires API level 9 (current min is 4) : \"cc yyyy-MM-dd\" [NewApi]\n" +
+ " SimpleDateFormat format = new SimpleDateFormat(\"cc yyyy-MM-dd\");\n" +
+ " ^\n" +
+ "3 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java",
+ "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class"
+ ));
+ }
+
+ public void testDateFormatOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk10.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java",
+ "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class"
+ ));
+ }
+
+ public void testJavaConstants() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ApiSourceCheck.java:5: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ + "import static android.view.View.MEASURED_STATE_MASK;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:30: Warning: Field requires API level 11 (current min is 1): android.widget.ZoomControls#MEASURED_STATE_MASK [InlinedApi]\n"
+ + " int x = MEASURED_STATE_MASK;\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:33: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ + " int y = android.view.View.MEASURED_STATE_MASK;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:36: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ + " int z = View.MEASURED_STATE_MASK;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:37: Warning: Field requires API level 14 (current min is 1): android.view.View#FIND_VIEWS_WITH_TEXT [InlinedApi]\n"
+ + " int find2 = View.FIND_VIEWS_WITH_TEXT; // requires API 14\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:40: Warning: Field requires API level 12 (current min is 1): android.app.ActivityManager#MOVE_TASK_NO_USER_ACTION [InlinedApi]\n"
+ + " int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:41: Warning: Field requires API level 14 (current min is 1): android.widget.ZoomButton#FIND_VIEWS_WITH_CONTENT_DESCRIPTION [InlinedApi]\n"
+ + " int find1 = ZoomButton.FIND_VIEWS_WITH_CONTENT_DESCRIPTION; // requires\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:44: Warning: Field requires API level 9 (current min is 1): android.widget.ZoomControls#OVER_SCROLL_ALWAYS [InlinedApi]\n"
+ + " int overScroll = OVER_SCROLL_ALWAYS; // requires API 9\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:47: Warning: Field requires API level 16 (current min is 1): android.widget.ZoomControls#IMPORTANT_FOR_ACCESSIBILITY_AUTO [InlinedApi]\n"
+ + " int auto = IMPORTANT_FOR_ACCESSIBILITY_AUTO; // requires API 16\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:54: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ + " return (child.getMeasuredWidth() & View.MEASURED_STATE_MASK)\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [InlinedApi]\n"
+ + " | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ + " | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:90: Warning: Field requires API level 8 (current min is 1): android.R.id#custom [InlinedApi]\n"
+ + " int custom = android.R.id.custom; // API 8\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:94: Warning: Field requires API level 19 (current min is 1): android.Manifest.permission#BLUETOOTH_PRIVILEGED [InlinedApi]\n"
+ + " String setPointerSpeed = permission.BLUETOOTH_PRIVILEGED;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:95: Warning: Field requires API level 19 (current min is 1): android.Manifest.permission#BLUETOOTH_PRIVILEGED [InlinedApi]\n"
+ + " String setPointerSpeed2 = Manifest.permission.BLUETOOTH_PRIVILEGED;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:120: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ + " int y = View.MEASURED_STATE_MASK; // Not OK\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:121: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ + " testBenignUsages(View.MEASURED_STATE_MASK); // Not OK\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck.java:51: Error: Field requires API level 14 (current min is 1): android.widget.ZoomButton#ROTATION_X [NewApi]\n"
+ + " Object rotationX = ZoomButton.ROTATION_X; // Requires API 14\n"
+ + " ~~~~~~~~~~\n"
+ + "1 errors, 17 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "apicheck/ApiSourceCheck.java.txt=>src/test/pkg/ApiSourceCheck.java",
+ "apicheck/ApiSourceCheck.class.data=>bin/classes/test/pkg/ApiSourceCheck.class"
+ ));
+ }
+
+ public void testStyleDeclaration() throws Exception {
+ assertEquals(""
+ + "res/values/styles2.xml:5: Error: android:actionBarStyle requires API level 11 (current min is 10) [NewApi]\n"
+ + " <item name=\"android:actionBarStyle\">...</item>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk10.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "res/values/styles2.xml"
+ ));
+ }
+
+ public void testStyleDeclarationInV9() throws Exception {
+ assertEquals(""
+ + "res/values-v9/styles2.xml:5: Error: android:actionBarStyle requires API level 11 (current min is 10) [NewApi]\n"
+ + " <item name=\"android:actionBarStyle\">...</item>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk10.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "res/values/styles2.xml=>res/values-v9/styles2.xml"
+ ));
+ }
+
+ public void testStyleDeclarationInV11() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk10.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "res/values/styles2.xml=>res/values-v11/styles2.xml"
+ ));
+ }
+
+ public void testStyleDeclarationInV14() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk10.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "res/values/styles2.xml=>res/values-v14/styles2.xml"
+ ));
+ }
+
+ public void testMovedConstants() throws Exception {
+ assertEquals(""
+ // These two constants were introduced in API 11; the other 3 were available
+ // on subclass ListView from API 1
+ + "src/test/pkg/ApiSourceCheck2.java:10: Warning: Field requires API level 11 (current min is 1): android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL [InlinedApi]\n"
+ + " int mode2 = AbsListView.CHOICE_MODE_MULTIPLE_MODAL;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiSourceCheck2.java:14: Warning: Field requires API level 11 (current min is 1): android.widget.ListView#CHOICE_MODE_MULTIPLE_MODAL [InlinedApi]\n"
+ + " int mode6 = ListView.CHOICE_MODE_MULTIPLE_MODAL;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "apicheck/ApiSourceCheck2.java.txt=>src/test/pkg/ApiSourceCheck2.java",
+ "apicheck/ApiSourceCheck2.class.data=>bin/classes/test/pkg/ApiSourceCheck2.class"
+ ));
+ }
+
+ public void testInheritCompatLibrary() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/MyActivityImpl.java:8: Error: Call requires API level 11 (current min is 1): android.app.Activity#isChangingConfigurations [NewApi]\n"
+ + " boolean isChanging = super.isChangingConfigurations();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/MyActivityImpl.java:12: Error: This method is not overriding anything with the current build target, but will in API level 11 (current target is 3): test.pkg.MyActivityImpl#isChangingConfigurations [Override]\n"
+ + " public boolean isChangingConfigurations() {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "project.properties1=>project.properties",
+ "apicheck/MyActivityImpl.java.txt=>src/test/pkg/MyActivityImpl.java",
+ "apicheck/MyActivityImpl.class.data=>bin/classes/test/pkg/MyActivityImpl.class",
+ "apicheck/android-support-v4.jar.data=>libs/android-support-v4.jar"
+ ));
+ }
+
+ public void testImplements() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ApiCallTest13.java:8: Error: Class requires API level 14 (current min is 4): android.widget.GridLayout [NewApi]\n"
+ + "public class ApiCallTest13 extends GridLayout implements\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/ApiCallTest13.java:9: Error: Class requires API level 11 (current min is 4): android.view.View.OnLayoutChangeListener [NewApi]\n"
+ + " View.OnSystemUiVisibilityChangeListener, OnLayoutChangeListener {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiCallTest13.java:9: Error: Class requires API level 11 (current min is 4): android.view.View.OnSystemUiVisibilityChangeListener [NewApi]\n"
+ + " View.OnSystemUiVisibilityChangeListener, OnLayoutChangeListener {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ApiCallTest13.java:12: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " super(context);\n"
+ + " ~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "apicheck/ApiCallTest13.java.txt=>src/test/pkg/ApiCallTest13.java",
+ "apicheck/ApiCallTest13.class.data=>bin/classes/test/pkg/ApiCallTest13.class"
+ ));
+ }
+
+ public void testFieldSuppress() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=52726
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest14.java.txt=>src/test/pkg/ApiCallTest14.java",
+ "apicheck/ApiCallTest14.class.data=>bin/classes/test/pkg/ApiCallTest14.class",
+ "apicheck/ApiCallTest14$1.class.data=>bin/classes/test/pkg/ApiCallTest14$1.class",
+ "apicheck/ApiCallTest14$2.class.data=>bin/classes/test/pkg/ApiCallTest14$2.class",
+ "apicheck/ApiCallTest14$3.class.data=>bin/classes/test/pkg/ApiCallTest14$3.class"
+ ));
+ }
+
+ public void testTryWithResources() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/TryWithResources.java:13: Error: Try-with-resources requires API level 19 (current min is 1) [NewApi]\n"
+ + " try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "src/test/pkg/TryWithResources.java.txt=>src/test/pkg/TryWithResources.java"
+ ));
+ }
+
+ public void testTryWithResourcesOk() throws Exception {
+ assertEquals(""
+ + "No warnings.",
+ lintProject(
+ "apicheck/minsdk19.xml=>AndroidManifest.xml",
+ "src/test/pkg/TryWithResources.java.txt=>src/test/pkg/TryWithResources.java"
+ ));
+ }
+
+ public void testReflectiveOperationException() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/Java7API.java:8: Error: Class requires API level 19 (current min is 1): java.lang.ReflectiveOperationException [NewApi]\n"
+ + " } catch (ReflectiveOperationException e) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/Java7API.java:9: Error: Call requires API level 19 (current min is 1): java.lang.ReflectiveOperationException#printStackTrace [NewApi]\n"
+ + " e.printStackTrace();\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "src/test/pkg/Java7API.java.txt=>src/test/pkg/Java7API.java",
+ "src/test/pkg/Java7API.class.data=>bin/classes/test/pkg/Java7API.class"
+ ));
+ }
+
+ public void testReflectiveOperationExceptionOk() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/minsdk19.xml=>AndroidManifest.xml",
+ "src/test/pkg/Java7API.java.txt=>src/test/pkg/Java7API.java"
+ ));
+ }
+
+ public void testMissingApiDatabase() throws Exception {
+ ApiLookup.dispose();
+ assertEquals(""
+ + "ApiDetectorTest_testMissingApiDatabase: Error: Can't find API database; API check not performed [LintError]\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "apicheck/classpath=>.classpath",
+ "apicheck/ApiCallTest.java.txt=>src/foo/bar/ApiCallTest.java",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ ));
+ }
+
+ public void testRipple() throws Exception {
+ assertEquals(""
+ + "res/drawable/ripple.xml:1: Error: <ripple> requires API level 21 (current min is 14) [NewApi]\n"
+ + "<ripple\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/ripple.xml=>res/drawable/ripple.xml"
+ ));
+ }
+
+ public void testRippleOk1() throws Exception {
+ // minSdkVersion satisfied
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/minsdk21.xml=>AndroidManifest.xml",
+ "apicheck/ripple.xml=>res/drawable/ripple.xml"
+ ));
+ }
+
+ public void testRippleOk2() throws Exception {
+ // -vNN location satisfied
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/ripple.xml=>res/drawable-v21/ripple.xml"
+ ));
+ }
+
+ public void testVector() throws Exception {
+ assertEquals(""
+ + "res/drawable/vector.xml:1: Error: <vector> requires API level 21 (current min is 4) or building with Android Gradle plugin 1.4 or higher [NewApi]\n"
+ + "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/vector.xml=>res/drawable/vector.xml"
+ ));
+ }
+
+ public void testVector_withGradleSupport() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/vector.xml=>res/drawable/vector.xml"
+ ));
+ }
+
+ public void testAnimatedSelector() throws Exception {
+ assertEquals(""
+ + "res/drawable/animated_selector.xml:1: Error: <animated-selector> requires API level 21 (current min is 14) [NewApi]\n"
+ + "<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/animated_selector.xml=>res/drawable/animated_selector.xml"
+ ));
+ }
+
+ public void testAnimatedVector() throws Exception {
+ assertEquals(""
+ + "res/drawable/animated_vector.xml:1: Error: <animated-vector> requires API level 21 (current min is 14) [NewApi]\n"
+ + "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/animated_vector.xml=>res/drawable/animated_vector.xml"
+ ));
+ }
+
+ public void testPaddingStart() throws Exception {
+ assertEquals(""
+ + "res/layout/padding_start.xml:14: Error: Attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+ + " android:paddingStart=\"20dp\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/padding_start.xml:21: Error: Attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+ + " android:paddingStart=\"20dp\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/padding_start.xml:28: Error: Attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+ + " android:paddingStart=\"20dp\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/padding_start.xml=>res/layout/padding_start.xml"
+ ));
+ }
+
+ public void testPaddingStartNotApplicable() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/padding_start.xml=>res/layout-v17/padding_start.xml"
+ ));
+ }
+
+ public void testPaddingStartWithOldBuildTools() throws Exception {
+ assertEquals(""
+ + "res/layout/padding_start.xml:14: Error: Upgrade buildToolsVersion from 22.2.1 to at least 23.0.1; if not, attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+ + " android:paddingStart=\"20dp\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/padding_start.xml:21: Error: Upgrade buildToolsVersion from 22.2.1 to at least 23.0.1; if not, attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+ + " android:paddingStart=\"20dp\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/padding_start.xml:28: Error: Upgrade buildToolsVersion from 22.2.1 to at least 23.0.1; if not, attribute paddingStart referenced here can result in a crash on some specific devices older than API 17 (current min is 4) [NewApi]\n"
+ + " android:paddingStart=\"20dp\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/padding_start.xml=>res/layout/padding_start.xml"
+ ));
+ }
+
+ public void testPaddingStartWithNewBuildTools() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/padding_start.xml=>res/layout/padding_start.xml"
+ ));
+ }
+
+ public void testSwitch() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/TargetApiTest.java.txt=>src/test/pkg/TargetApiTest.java",
+ "apicheck/TargetApiTest.class.data=>bin/classes/test/pkg/TargetApiTest.class",
+ "apicheck/TargetApiTest$1.class.data=>bin/classes/test/pkg/TargetApiTest$1.class"
+ ));
+ }
+
+ public void testGravity() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/GravityTest.java.txt=>src/test/pkg/GravityTest.java"
+ ));
+ }
+
+ public void testSuperCall() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SuperCallTest.java:20: Error: Call requires API level 21 (current min is 19): android.service.wallpaper.WallpaperService.Engine#onApplyWindowInsets [NewApi]\n"
+ + " super.onApplyWindowInsets(insets); // Error\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SuperCallTest.java:27: Error: Call requires API level 21 (current min is 19): android.service.wallpaper.WallpaperService.Engine#onApplyWindowInsets [NewApi]\n"
+ + " onApplyWindowInsets(insets); // Error: not overridden\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk19.xml=>AndroidManifest.xml",
+ "apicheck/SuperCallTest.java.txt=>src/test/pkg/SuperCallTest.java",
+ "apicheck/SuperCallTest.class.data=>bin/classes/test/pkg/SuperCallTest.class",
+ "apicheck/SuperCallTest$MyEngine2.class.data=>bin/classes/test/pkg/SuperCallTest$MyEngine2.class",
+ "apicheck/SuperCallTest$MyEngine1.class.data=>bin/classes/test/pkg/SuperCallTest$MyEngine1.class"
+ ));
+ }
+
+ public void testSuperClassInLibrary() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=97006
+ // 97006: Gradle lint does not recognize Context.getDrawable() as API 21+
+ assertEquals(
+ "src/test/pkg/MyFragment.java:10: Error: Call requires API level 21 (current min is 14): android.app.Activity#getDrawable [NewApi]\n" +
+ " getActivity().getDrawable(R.color.my_color);\n" +
+ " ~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java",
+ "apicheck/MyFragment.java.txt=>src/test/pkg/MyFragment.java",
+ "apicheck/MyFragment$R$color.class.data=>bin/classes/test/pkg/MyFragment$R$color.class",
+ "apicheck/MyFragment$R.class.data=>bin/classes/test/pkg/MyFragment$R.class",
+ "apicheck/MyFragment.class.data=>bin/classes/test/pkg/MyFragment.class",
+
+ // Library project
+ "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
+ "multiproject/library.properties=>../LibraryProject/project.properties",
+ "multiproject/LibraryCode.java.txt=>../LibraryProject/src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>../LibraryProject/res/values/strings.xml",
+ "apicheck/fragment_support.jar.data=>../LibraryProject/libs/fragment_support.jar"
+ ));
+ }
+
+ public void testConditionalApi0() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=137195
+ assertEquals(""
+ + "src/test/pkg/ConditionalApiTest.java:28: Error: Call requires API level 18 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
+ + " new RectEvaluator(); // ERROR\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ConditionalApiTest.java:37: Error: Call requires API level 21 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
+ + " new RectEvaluator(rect); // ERROR\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ConditionalApiTest.java:43: Error: Call requires API level 21 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
+ + " new RectEvaluator(rect); // ERROR\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ConditionalApiTest.java:45: Error: Call requires API level 21 (current min is 14): new android.animation.RectEvaluator [NewApi]\n"
+ + " new RectEvaluator(rect); // ERROR\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/ConditionalApiTest.java.txt=>src/test/pkg/ConditionalApiTest.java",
+ "apicheck/ConditionalApiTest.class.data=>bin/classes/test/pkg/ConditionalApiTest.class"
+ ));
+ }
+
+ public void testConditionalApi1() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=137195
+ assertEquals(""
+ + "src/test/pkg/VersionConditional1.java:18: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:18: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:24: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:24: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:30: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:30: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:36: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:36: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:40: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:40: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:48: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:48: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:54: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:54: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:60: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:60: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:62: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:62: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:65: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:65: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:76: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:84: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:90: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:94: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:94: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:96: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:102: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:108: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:114: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:118: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:126: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1.java:132: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "32 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/VersionConditional1.java.txt=>src/test/pkg/VersionConditional1.java",
+ "apicheck/VersionConditional1.class.data=>bin/classes/test/pkg/VersionConditional1.class"
+ ));
+ }
+
+ public void testConditionalApi1b() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=137195
+ // This is like testConditionalApi1, but with each logical lookup call extracted into
+ // a single method. This makes debugging through the control flow graph a lot easier.
+ assertEquals(""
+ + "src/test/pkg/VersionConditional1b.java:23: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:31: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:31: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:33: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:33: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:36: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:36: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:44: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:44: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:52: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:52: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:58: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:58: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:68: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:68: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:76: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:76: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:84: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:84: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:92: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:92: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:100: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:106: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:110: Error: Call requires API level 14 (current min is 4): android.widget.GridLayout#getOrientation [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:110: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null).getOrientation(); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:112: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:118: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:124: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:130: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:134: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:142: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional1b.java:148: Error: Call requires API level 14 (current min is 4): new android.widget.GridLayout [NewApi]\n"
+ + " new GridLayout(null); // Flagged\n"
+ + " ~~~~~~~~~~\n"
+ + "32 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/VersionConditional1b.java.txt=>src/test/pkg/VersionConditional1b.java",
+ "apicheck/VersionConditional1b.class.data=>bin/classes/test/pkg/VersionConditional1b.class"
+ ));
+ }
+
+ public void testConditionalApi2() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=137195
+ assertEquals(""
+ + "src/test/pkg/VersionConditional2.java:20: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:24: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:42: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:46: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:50: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:66: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:72: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:78: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:98: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:104: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:128: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:132: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2.java:136: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "13 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/VersionConditional2.java.txt=>src/test/pkg/VersionConditional2.java",
+ "apicheck/VersionConditional2.class.data=>bin/classes/test/pkg/VersionConditional2.class"
+ ));
+ }
+
+ public void testConditionalApi2b() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=137195
+ // This is like testConditionalApi2, but with each logical lookup call extracted into
+ // a single method. This makes debugging through the control flow graph a lot easier.
+ assertEquals(""
+ + "src/test/pkg/VersionConditional2b.java:17: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:23: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:47: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:53: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:59: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:79: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:87: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:95: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:119: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:127: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:157: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:163: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional2b.java:169: Error: Call requires API level 16 (current min is 4): android.view.View#setBackground [NewApi]\n"
+ + " root.setBackground(background); // Flagged\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "13 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/VersionConditional2b.java.txt=>src/test/pkg/VersionConditional2b.java",
+ "apicheck/VersionConditional2b.class.data=>bin/classes/test/pkg/VersionConditional2b.class"
+ ));
+ }
+
+ public void testConditionalApi3() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=137195
+ assertEquals(""
+ + "src/test/pkg/VersionConditional3.java:13: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:15: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > 19 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:24: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT >= 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:26: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT >= 19 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:28: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT >= 20 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:35: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT == 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:37: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT == 19 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:39: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT == 20 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:46: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT < 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:48: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT < 22 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:50: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT <= 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:52: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT <= 22 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:56: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:58: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > VERSION_CODES.KITKAT && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:66: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > 21 || property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3.java:83: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " property.hasAdjacentMapping() && // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "16 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/VersionConditional3.java.txt=>src/test/pkg/VersionConditional3.java",
+ "apicheck/VersionConditional3.class.data=>bin/classes/test/pkg/VersionConditional3.class"
+ ));
+ }
+
+ public void testConditionalApi3b() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=137195
+ // This is like testConditionalApi3, but with each logical lookup call extracted into
+ // a single method. This makes debugging through the control flow graph a lot easier.
+ assertEquals(""
+ + "src/test/pkg/VersionConditional3b.java:21: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " property.hasAdjacentMapping() && // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:44: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > 21 || property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:59: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > VERSION_CODES.KITKAT && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:64: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > VERSION_CODES.GINGERBREAD && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:69: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT <= 22 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:74: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT <= 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:79: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT < 22 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:84: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT < 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:99: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT == 20 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:104: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT == 19 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:109: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT == 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:124: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT >= 20 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:129: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT >= 19 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:134: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT >= 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:154: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > 19 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/VersionConditional3b.java:159: Error: Call requires API level 21 (current min is 4): android.view.ViewDebug.ExportedProperty#hasAdjacentMapping [NewApi]\n"
+ + " if (Build.VERSION.SDK_INT > 18 && property.hasAdjacentMapping()) { // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "16 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/VersionConditional3b.java.txt=>src/test/pkg/VersionConditional3b.java",
+ "apicheck/VersionConditional3b.class.data=>bin/classes/test/pkg/VersionConditional3b.class"
+ ));
+ }
+
+ public void testHigherCompileSdkVersionThanPlatformTools() throws Exception {
+ // Warn if the platform tools are too old on the system
+ assertTrue(Pattern.matches(""
+ + "ApiDetectorTest_testHigherCompileSdkVersionThanPlatformTools: Error: The SDK platform-tools version \\([^)]+\\) is too old to check APIs compiled with API 400; please update \\[NewApi\\]\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+ source("project.properties", "target=android-400"), // in the future
+ copy("apicheck/ApiCallTest12.java.txt", "src/test/pkg/ApiCallTest12.java"),
+ copy("apicheck/ApiCallTest12.class.data", "bin/classes/test/pkg/ApiCallTest12.class")
+ )));
+ }
+
+ public void testHigherCompileSdkVersionThanPlatformToolsInEditor() throws Exception {
+ // When editing a file we place the error on the first line of the file instead
+ assertTrue(Pattern.matches(""
+ + "src/test/pkg/ApiCallTest12.java:1: Error: The SDK platform-tools version \\([^)]+\\) is too old to check APIs compiled with API 400; please update \\[NewApi\\]\n"
+ + "package test.pkg;\n"
+ + "~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProjectIncrementally(
+ "src/test/pkg/ApiCallTest12.java",
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+ source("project.properties", "target=android-400"), // in the future
+ copy("apicheck/ApiCallTest12.java.txt", "src/test/pkg/ApiCallTest12.java"),
+ copy("apicheck/ApiCallTest12.class.data", "bin/classes/test/pkg/ApiCallTest12.class")
+ )));
+ }
+
+ @SuppressWarnings({"MethodMayBeStatic", "ConstantConditions", "ClassNameDiffersFromFileName"})
+ public void testCastChecks() throws Exception {
+ // When editing a file we place the error on the first line of the file instead
+ assertEquals(""
+ + "src/test/pkg/CastTest.java:15: Error: Cast from Cursor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+ + " Closeable closeable = (Closeable) cursor; // Requires 16\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/CastTest.java:21: Error: Cast from KeyCharacterMap to Parcelable requires API level 16 (current min is 14) [NewApi]\n"
+ + " Parcelable parcelable2 = (Parcelable)map; // Requires API 16\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/CastTest.java:27: Error: Cast from AnimatorListenerAdapter to Animator.AnimatorPauseListener requires API level 19 (current min is 14) [NewApi]\n"
+ + " AnimatorPauseListener listener = (AnimatorPauseListener)adapter;\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/CastTest.java", ""
+ + "import android.animation.Animator.AnimatorPauseListener;\n"
+ + "import android.animation.AnimatorListenerAdapter;\n"
+ + "import android.database.Cursor;\n"
+ + "import android.database.CursorWindow;\n"
+ + "import android.os.Parcelable;\n"
+ + "import android.view.KeyCharacterMap;\n"
+ + "\n"
+ + "import java.io.Closeable;\n"
+ + "import java.io.IOException;\n"
+ + "\n"
+ + "@SuppressWarnings({\"RedundantCast\", \"unused\"})\n"
+ + "public class CastTest {\n"
+ + " public void test(Cursor cursor) throws IOException {\n"
+ + " cursor.close();\n"
+ + " Closeable closeable = (Closeable) cursor; // Requires 16\n"
+ + " closeable.close();\n"
+ + " }\n"
+ + "\n"
+ + " public void test(CursorWindow window, KeyCharacterMap map) {\n"
+ + " Parcelable parcelable1 = (Parcelable)window; // OK\n"
+ + " Parcelable parcelable2 = (Parcelable)map; // Requires API 16\n"
+ + " }\n"
+ + "\n"
+ + " @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+ + " public void test(AnimatorListenerAdapter adapter) {\n"
+ + " // Uh oh - what if the cast isn't needed anymore\n"
+ + " AnimatorPauseListener listener = (AnimatorPauseListener)adapter;\n"
+ + " }\n"
+ + "}"),
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+ ));
+ }
+
+ @SuppressWarnings({"MethodMayBeStatic", "ConstantConditions", "ClassNameDiffersFromFileName",
+ "UnnecessaryLocalVariable"})
+ public void testImplicitCastTest() throws Exception {
+ // When editing a file we place the error on the first line of the file instead
+ assertEquals(""
+ + "src/test/pkg/ImplicitCastTest.java:14: Error: Cast from Cursor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+ + " Closeable closeable = c;\n"
+ + " ~\n"
+ + "src/test/pkg/ImplicitCastTest.java:26: Error: Cast from Cursor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+ + " closeable = c;\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ImplicitCastTest.java:36: Error: Cast from ParcelFileDescriptor to Closeable requires API level 16 (current min is 14) [NewApi]\n"
+ + " safeClose(pfd);\n"
+ + " ~~~\n"
+ + "src/test/pkg/ImplicitCastTest.java:47: Error: Cast from AccelerateDecelerateInterpolator to BaseInterpolator requires API level 22 (current min is 14) [NewApi]\n"
+ + " android.view.animation.BaseInterpolator base = interpolator;\n"
+ + " ~~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ImplicitCastTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.database.Cursor;\n"
+ + "import android.os.ParcelFileDescriptor;\n"
+ + "\n"
+ + "import java.io.Closeable;\n"
+ + "import java.io.IOException;\n"
+ + "\n"
+ + "@SuppressWarnings(\"unused\")\n"
+ + "public class ImplicitCastTest {\n"
+ + " // https://code.google.com/p/android/issues/detail?id=174535\n"
+ + " @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+ + " public void testImplicitCast(Cursor c) {\n"
+ + " Closeable closeable = c;\n"
+ + " try {\n"
+ + " closeable.close();\n"
+ + " } catch (IOException e) {\n"
+ + " e.printStackTrace();\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " // Like the above, but with assignment instead of initializer\n"
+ + " public void testImplicitCast2(Cursor c) {\n"
+ + " @SuppressWarnings(\"UnnecessaryLocalVariable\")\n"
+ + " Closeable closeable;\n"
+ + " closeable = c;\n"
+ + " try {\n"
+ + " closeable.close();\n"
+ + " } catch (IOException e) {\n"
+ + " e.printStackTrace();\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " // https://code.google.com/p/android/issues/detail?id=191120\n"
+ + " public void testImplicitCast(ParcelFileDescriptor pfd) {\n"
+ + " safeClose(pfd);\n"
+ + " }\n"
+ + "\n"
+ + " private static void safeClose(Closeable closeable) {\n"
+ + " try {\n"
+ + " closeable.close();\n"
+ + " } catch (IOException ignore) {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public void testImplicitCast(android.view.animation.AccelerateDecelerateInterpolator interpolator) {\n"
+ + " android.view.animation.BaseInterpolator base = interpolator;\n"
+ + " }\n"
+ + "\n"
+ + "}\n"),
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+ ));
+ }
+
+ public void testSupportLibraryCalls() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SupportLibraryApiTest.java:22: Error: Call requires API level 21 (current min is 14): android.widget.ImageButton#setBackgroundTintList [NewApi]\n"
+ + " button.setBackgroundTintList(colors); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+ copy("bytecode/SupportLibraryApiTest.java.txt",
+ "src/test/pkg/SupportLibraryApiTest.java"),
+ copy("bytecode/SupportLibraryApiTest.class.data",
+ "bin/classes/test/pkg/SupportLibraryApiTest.class"),
+ copy("bytecode/FloatingActionButton.java.txt",
+ "src/android/support/design/widget/FloatingActionButton.java"),
+ copy("bytecode/FloatingActionButton.class.data",
+ "bin/classes/android/support/design/widget/FloatingActionButton.class")
+ ));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ if (getName().equals("testMissingApiDatabase")) {
+ // Simulate an environment where there is no API database
+ return new TestLintClient() {
+ @Override
+ public File findResource(@NonNull String relativePath) {
+ return null;
+ }
+ };
+ }
+ if (getName().equals("testVector_withGradleSupport")) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ AndroidProject model = mock(AndroidProject.class);
+ when(model.getModelVersion()).thenReturn("1.4.0-alpha1");
+
+ Project fromSuper = super.createProject(dir, referenceDir);
+ Project spy = spy(fromSuper);
+ when(spy.getGradleProjectModel()).thenReturn(model);
+ return spy;
+ }
+ };
+ }
+ if (getName().equals("testPaddingStart")) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ Project fromSuper = super.createProject(dir, referenceDir);
+ Project spy = spy(fromSuper);
+ when(spy.getBuildTools()).thenReturn(null);
+ return spy;
+ }
+ };
+ }
+ if (getName().equals("testPaddingStartWithOldBuildTools")) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ Revision revision = new Revision(22, 2, 1);
+ BuildToolInfo info = new BuildToolInfo(revision, dir);
+
+ Project fromSuper = super.createProject(dir, referenceDir);
+ Project spy = spy(fromSuper);
+ when(spy.getBuildTools()).thenReturn(info);
+ return spy;
+ }
+ };
+ }
+ if (getName().equals("testPaddingStartWithNewBuildTools")) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ Revision revision = new Revision(23, 0, 2);
+ BuildToolInfo info = new BuildToolInfo(revision, dir);
+
+ Project fromSuper = super.createProject(dir, referenceDir);
+ Project spy = spy(fromSuper);
+ when(spy.getBuildTools()).thenReturn(info);
+ return spy;
+ }
+ };
+ }
+ return super.createClient();
+ }
+
+ // bug 198295: Add a test for a case that crashes ApiDetector due to an
+ // invalid parameterIndex causing by a varargs method invocation.
+ public void testMethodWithPrimitiveAndVarargs() throws Exception {
+ // In case of a crash, there is an assertion failure in tearDown()
+ //noinspection ClassNameDiffersFromFileName
+ assertEquals("No warnings.",
+ lintProject(
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml"),
+ java("src/test/pkg/LogHelper.java", "" +
+ "package test.pkg;\n"
+ + "\n"
+ + "public class LogHelper {\n"
+ + "\n"
+ + " public static void log(String tag, Object... args) {\n"
+ + " }\n"
+ + "}"),
+ java("src/test/pkg/Browser.java", "" +
+ "package test.pkg;\n"
+ + "\n"
+ + "public class Browser {\n"
+ + " \n"
+ + " public void onCreate() {\n"
+ + " LogHelper.log(\"TAG\", \"arg1\", \"arg2\", 1, \"arg4\", this /*non primitive*/);\n"
+ + " }\n"
+ + "}")
+ ));
+ }
+
+ public void testMethodInvocationWithGenericTypeArgs() throws Exception {
+ // Test case for https://code.google.com/p/android/issues/detail?id=198439
+ //noinspection ClassNameDiffersFromFileName
+ assertEquals("No warnings.",
+ lintProject(
+ java("src/test/pkg/Loader.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "public abstract class Loader<P> {\n"
+ + " private P mParam;\n"
+ + "\n"
+ + " public abstract void loadInBackground(P val);\n"
+ + "\n"
+ + " public void load() {\n"
+ + " // Invoke a method that takes a generic type.\n"
+ + " loadInBackground(mParam);\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testMultiCatch() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=198854
+ // Check disjointed exception types
+
+ //noinspection ClassNameDiffersFromFileName
+ assertEquals(""
+ + "src/test/pkg/MultiCatch.java:12: Error: Class requires API level 18 (current min is 1): android.media.UnsupportedSchemeException [NewApi]\n"
+ + " } catch (MediaDrm.MediaDrmStateException | UnsupportedSchemeException e) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/MultiCatch.java:12: Error: Class requires API level 21 (current min is 1): android.media.MediaDrm.MediaDrmStateException [NewApi]\n"
+ + " } catch (MediaDrm.MediaDrmStateException | UnsupportedSchemeException e) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/MultiCatch.java:18: Error: Class requires API level 21 (current min is 1): android.media.MediaDrm.MediaDrmStateException [NewApi]\n"
+ + " } catch (MediaDrm.MediaDrmStateException\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/MultiCatch.java:19: Error: Class requires API level 18 (current min is 1): android.media.UnsupportedSchemeException [NewApi]\n"
+ + " | UnsupportedSchemeException e) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/MultiCatch.java:26: Error: Multi-catch with these reflection exceptions requires API level 19 (current min is 1) because they get compiled to the common but new super type ReflectiveOperationException. As a workaround either create individual catch statements, or catch Exception. [NewApi]\n"
+ + " e.printStackTrace();\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "5 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/MultiCatch.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.media.MediaDrm;\n"
+ + "import android.media.UnsupportedSchemeException;\n"
+ + "\n"
+ + "import java.lang.reflect.InvocationTargetException;\n"
+ + "\n"
+ + "public class MultiCatch {\n"
+ + " public void test() {\n"
+ + " try {\n"
+ + " method1();\n"
+ + " } catch (MediaDrm.MediaDrmStateException | UnsupportedSchemeException e) {\n"
+ + " e.printStackTrace();\n"
+ + " }\n"
+ + "\n"
+ + " try {\n"
+ + " method2();\n"
+ + " } catch (MediaDrm.MediaDrmStateException\n"
+ + " | UnsupportedSchemeException e) {\n"
+ + " e.printStackTrace();\n"
+ + " }\n"
+ + "\n"
+ + " try {\n"
+ + " String.class.getMethod(\"trim\").invoke(\"\");\n"
+ + " } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n"
+ + " e.printStackTrace();\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public void method1() throws MediaDrm.MediaDrmStateException, UnsupportedSchemeException {\n"
+ + " }\n"
+ + " public void method2() throws MediaDrm.MediaDrmStateException, UnsupportedSchemeException {\n"
+ + " }\n"
+ + "}\n"),
+ base64("bin/classes/test/pkg/MultiCatch.class", ""
+ + "yv66vgAAADMARgoADAAmCgASACcHACkHACwKAC0ALgoAEgAvBwAwCAAxBwAy"
+ + "CgAJADMIADQHADUKADYANwcAOAcAOQcAOgoAOwAuBwA8AQAGPGluaXQ+AQAD"
+ + "KClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVU"
+ + "YWJsZQEABHRoaXMBABVMdGVzdC9wa2cvTXVsdGlDYXRjaDsBAAR0ZXN0AQAB"
+ + "ZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEAKExqYXZhL2xhbmcvUmVmbGVj"
+ + "dGl2ZU9wZXJhdGlvbkV4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwA9BwA+"
+ + "AQAHbWV0aG9kMQEACkV4Y2VwdGlvbnMBAAdtZXRob2QyAQAKU291cmNlRmls"
+ + "ZQEAD011bHRpQ2F0Y2guamF2YQwAEwAUDAAhABQHAD8BAC1hbmRyb2lkL21l"
+ + "ZGlhL01lZGlhRHJtJE1lZGlhRHJtU3RhdGVFeGNlcHRpb24BABZNZWRpYURy"
+ + "bVN0YXRlRXhjZXB0aW9uAQAMSW5uZXJDbGFzc2VzAQAoYW5kcm9pZC9tZWRp"
+ + "YS9VbnN1cHBvcnRlZFNjaGVtZUV4Y2VwdGlvbgcAPQwAQAAUDAAjABQBABBq"
+ + "YXZhL2xhbmcvU3RyaW5nAQAEdHJpbQEAD2phdmEvbGFuZy9DbGFzcwwAQQBC"
+ + "AQAAAQAQamF2YS9sYW5nL09iamVjdAcAQwwARABFAQAgamF2YS9sYW5nL0ls"
+ + "bGVnYWxBY2Nlc3NFeGNlcHRpb24BACtqYXZhL2xhbmcvcmVmbGVjdC9JbnZv"
+ + "Y2F0aW9uVGFyZ2V0RXhjZXB0aW9uAQAfamF2YS9sYW5nL05vU3VjaE1ldGhv"
+ + "ZEV4Y2VwdGlvbgcAPgEAE3Rlc3QvcGtnL011bHRpQ2F0Y2gBABNqYXZhL2xh"
+ + "bmcvRXhjZXB0aW9uAQAmamF2YS9sYW5nL1JlZmxlY3RpdmVPcGVyYXRpb25F"
+ + "eGNlcHRpb24BABZhbmRyb2lkL21lZGlhL01lZGlhRHJtAQAPcHJpbnRTdGFj"
+ + "a1RyYWNlAQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2"
+ + "YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAGGph"
+ + "dmEvbGFuZy9yZWZsZWN0L01ldGhvZAEABmludm9rZQEAOShMamF2YS9sYW5n"
+ + "L09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0"
+ + "OwAhABIADAAAAAAABAABABMAFAABABUAAAAvAAEAAQAAAAUqtwABsQAAAAIA"
+ + "FgAAAAYAAQAAAAgAFwAAAAwAAQAAAAUAGAAZAAAAAQAaABQAAQAVAAAA/QAD"
+ + "AAIAAAA2KrYAAqcACEwrtgAFKrYABqcACEwrtgAFEgcSCAO9AAm2AAoSCwO9"
+ + "AAy2AA1XpwAITCu2ABGxAAcAAAAEAAcAAwAAAAQABwAEAAwAEAATAAMADAAQ"
+ + "ABMABAAYAC0AMAAOABgALQAwAA8AGAAtADAAEAADABYAAAA2AA0AAAALAAQA"
+ + "DgAHAAwACAANAAwAEQAQABUAEwASABQAFAAYABgALQAbADAAGQAxABoANQAc"
+ + "ABcAAAAqAAQACAAEABsAHAABABQABAAbABwAAQAxAAQAGwAdAAEAAAA2ABgA"
+ + "GQAAAB4AAAARAAZHBwAfBEYHAB8EVwcAIAQAAQAhABQAAgAVAAAAKwAAAAEA"
+ + "AAABsQAAAAIAFgAAAAYAAQAAAB8AFwAAAAwAAQAAAAEAGAAZAAAAIgAAAAYA"
+ + "AgADAAQAAQAjABQAAgAVAAAAKwAAAAEAAAABsQAAAAIAFgAAAAYAAQAAACEA"
+ + "FwAAAAwAAQAAAAEAGAAZAAAAIgAAAAYAAgADAAQAAgAkAAAAAgAlACsAAAAK"
+ + "AAEAAwAoACoAGQ==")
+
+ ));
+ }
+
+
+
+ @Override
+ protected boolean ignoreSystemErrors() {
+ //noinspection SimplifiableIfStatement
+ if (getName().equals("testMissingApiDatabase")) {
+ return false;
+ }
+ return super.ignoreSystemErrors();
+ }
+
+ @Override
+ protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+ @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+ if (issue == UNSUPPORTED || issue == INLINED) {
+ if (message.startsWith("The SDK platform-tools version (")) {
+ return;
+ }
+ int requiredVersion = ApiDetector.getRequiredVersion(issue, message, TEXT);
+ assertTrue("Could not extract message tokens from \"" + message + "\"",
+ requiredVersion >= 1 && requiredVersion <= SdkVersionInfo.HIGHEST_KNOWN_API);
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
new file mode 100644
index 0000000..57303e4
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ApiLookupTest.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
+import static com.android.SdkConstants.DOT_AAR;
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.FN_CLASSES_JAR;
+import static com.android.SdkConstants.TAG_USES_SDK;
+import static com.android.ide.common.repository.SdkMavenRepository.ANDROID;
+import static com.android.tools.lint.detector.api.LintUtils.getChildren;
+import static com.google.common.base.Charsets.UTF_8;
+import static java.io.File.separatorChar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.ide.common.xml.XmlFormatPreferences;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.repository.io.FileOpUtils;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.Pair;
+import com.android.utils.XmlUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.io.StringWriter;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipEntry;
+
+ at SuppressWarnings({"javadoc", "ConstantConditions"})
+public class ApiLookupTest extends AbstractCheckTest {
+ private final ApiLookup mDb = ApiLookup.get(new TestLintClient());
+
+ public void test1() {
+ assertEquals(5, mDb.getFieldVersion("android/Manifest$permission", "AUTHENTICATE_ACCOUNTS"));
+ assertTrue(mDb.getFieldVersion("android/R$attr", "absListViewStyle") <= 1);
+ assertEquals(11, mDb.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
+ assertEquals(5, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
+ "<init>", "(Landroid/content/res/Resources;Ljava/lang/String;)V"));
+ assertEquals(4, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
+ "setTargetDensity", "(Landroid/util/DisplayMetrics;)V"));
+ assertEquals(7, mDb.getClassVersion("android/app/WallpaperInfo"));
+ assertEquals(11, mDb.getClassVersion("android/widget/StackView"));
+ assertTrue(mDb.getClassVersion("ava/text/ChoiceFormat") <= 1);
+
+ // Class lookup: Unknown class
+ assertEquals(-1, mDb.getClassVersion("foo/Bar"));
+ // Field lookup: Unknown class
+ assertEquals(-1, mDb.getFieldVersion("foo/Bar", "FOOBAR"));
+ // Field lookup: Unknown field
+ assertEquals(-1, mDb.getFieldVersion("android/Manifest$permission", "FOOBAR"));
+ // Method lookup: Unknown class
+ assertEquals(-1, mDb.getCallVersion("foo/Bar",
+ "<init>", "(Landroid/content/res/Resources;Ljava/lang/String;)V"));
+ // Method lookup: Unknown name
+ assertEquals(-1, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
+ "foo", "(Landroid/content/res/Resources;Ljava/lang/String;)V"));
+ // Method lookup: Unknown argument list
+ assertEquals(-1, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable",
+ "<init>", "(I)V"));
+ }
+
+ public void test2() {
+ // Regression test:
+ // This used to return 11 because of some wildcard syntax in the signature
+ assertTrue(mDb.getCallVersion("java/lang/Object", "getClass", "()") <= 1);
+ }
+
+ public void testIssue26467() {
+ assertTrue(mDb.getCallVersion("java/nio/ByteBuffer", "array", "()") <= 1);
+ assertEquals(9, mDb.getCallVersion("java/nio/Buffer", "array", "()"));
+ }
+
+ public void testNoInheritedConstructors() {
+ assertTrue(mDb.getCallVersion("java/util/zip/ZipOutputStream", "<init>", "()") <= 1);
+ assertTrue(mDb.getCallVersion("android/app/AliasActivity", "<init>", "(Landroid/content/Context;I)") <= 1);
+ }
+
+ public void testIssue35190() {
+ assertEquals(9, mDb.getCallVersion("java/io/IOException", "<init>",
+ "(Ljava/lang/Throwable;)V"));
+ }
+
+ public void testDeprecatedFields() {
+ // Not deprecated:
+ assertEquals(-1, mDb.getFieldDeprecatedIn("android/Manifest$permission", "GET_PACKAGE_SIZE"));
+ // Field only has since > 1, no deprecation
+ assertEquals(9, mDb.getFieldVersion("android/Manifest$permission", "NFC"));
+
+ // Deprecated
+ assertEquals(21, mDb.getFieldDeprecatedIn("android/Manifest$permission", "GET_TASKS"));
+ // Field both deprecated and since > 1
+ assertEquals(21, mDb.getFieldDeprecatedIn("android/Manifest$permission", "READ_SOCIAL_STREAM"));
+ assertEquals(15, mDb.getFieldVersion("android/Manifest$permission", "READ_SOCIAL_STREAM"));
+ }
+
+ public void testDeprecatedCalls() {
+ // Not deprecated:
+ //assertEquals(12, mDb.getCallVersion("android/app/Fragment", "onInflate",
+ // "(Landroid/app/Activity;Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+ assertEquals(23, mDb.getCallDeprecatedIn("android/app/Fragment", "onInflate",
+ "(Landroid/app/Activity;Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+ assertEquals(-1, mDb.getCallDeprecatedIn("android/app/Fragment", "onInflate",
+ "(Landroid/content/Context;Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+ // Deprecated
+ assertEquals(16, mDb.getCallDeprecatedIn("android/app/Service", "onStart", "(Landroid/content/Intent;I)V"));
+ assertEquals(16, mDb.getCallDeprecatedIn("android/app/Fragment", "onInflate", "(Landroid/util/AttributeSet;Landroid/os/Bundle;)V"));
+ }
+
+ public void testDeprecatedClasses() {
+ // Not deprecated:
+ assertEquals(-1, mDb.getClassDeprecatedIn("android/app/Fragment"));
+ // Deprecated
+ assertEquals(9, mDb.getClassDeprecatedIn("org/xml/sax/Parser"));
+ }
+
+ public void testInheritInterfaces() {
+ // The onPreferenceStartFragment is inherited via the
+ // android/preference/PreferenceFragment$OnPreferenceStartFragmentCallback
+ // interface
+ assertEquals(11, mDb.getCallVersion("android/preference/PreferenceActivity",
+ "onPreferenceStartFragment",
+ "(Landroid/preference/PreferenceFragment;Landroid/preference/Preference;)"));
+ }
+
+ public void testInterfaceApi() {
+ assertEquals(21, mDb.getClassVersion("android/animation/StateListAnimator"));
+ assertEquals(11, mDb.getValidCastVersion("android/animation/AnimatorListenerAdapter",
+ "android/animation/Animator$AnimatorListener"));
+ assertEquals(19, mDb.getValidCastVersion("android/animation/AnimatorListenerAdapter",
+ "android/animation/Animator$AnimatorPauseListener"));
+
+ assertEquals(11, mDb.getValidCastVersion("android/animation/Animator",
+ "java/lang/Cloneable"));
+ assertEquals(22, mDb.getValidCastVersion("android/animation/StateListAnimator",
+ "java/lang/Cloneable"));
+ }
+
+ public void testSuperClassCast() {
+ assertEquals(22, mDb.getValidCastVersion("android/view/animation/AccelerateDecelerateInterpolator",
+ "android/view/animation/BaseInterpolator"));
+ }
+
+ public void testIsValidPackage() {
+ assertTrue(mDb.isValidJavaPackage("java/lang/Integer"));
+ assertTrue(mDb.isValidJavaPackage("javax/crypto/Cipher"));
+ assertTrue(mDb.isValidJavaPackage("java/awt/font/NumericShaper"));
+
+ assertFalse(mDb.isValidJavaPackage("javax/swing/JButton"));
+ assertFalse(mDb.isValidJavaPackage("java/rmi/Naming"));
+ assertFalse(mDb.isValidJavaPackage("java/lang/instrument/Instrumentation"));
+ }
+
+ @Override
+ protected Detector getDetector() {
+ fail("This is not used in the ApiDatabase test");
+ return null;
+ }
+
+ private File mCacheDir;
+ @SuppressWarnings("StringBufferField")
+ private StringBuilder mLogBuffer = new StringBuilder();
+
+ @SuppressWarnings({"ConstantConditions", "IOResourceOpenedButNotSafelyClosed",
+ "ResultOfMethodCallIgnored"})
+ public void testCorruptedCacheHandling() throws Exception {
+ if (ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+ System.err.println("Skipping " + getName() + ": not valid while regenerating indices");
+ return;
+ }
+
+ ApiLookup lookup;
+
+ // Real cache:
+ mCacheDir = new TestLintClient().getCacheDir(true);
+ mLogBuffer.setLength(0);
+ lookup = ApiLookup.get(new LookupTestClient());
+ assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
+ assertNotNull(lookup);
+ assertEquals("", mLogBuffer.toString()); // No warnings
+ ApiLookup.dispose();
+
+ // Custom cache dir: should also work
+ mCacheDir = new File(getTempDir(), "test-cache");
+ mCacheDir.mkdirs();
+ mLogBuffer.setLength(0);
+ lookup = ApiLookup.get(new LookupTestClient());
+ assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
+ assertNotNull(lookup);
+ assertEquals("", mLogBuffer.toString()); // No warnings
+ ApiLookup.dispose();
+
+ // Now truncate cache file
+ File cacheFile = new File(mCacheDir,
+ ApiLookup.getCacheFileName("api-versions.xml",
+ ApiLookup.getPlatformVersion(new LookupTestClient()))); //$NON-NLS-1$
+ mLogBuffer.setLength(0);
+ assertTrue(cacheFile.exists());
+ RandomAccessFile raf = new RandomAccessFile(cacheFile, "rw");
+ // Truncate file in half
+ raf.setLength(100); // Broken header
+ raf.close();
+ ApiLookup.get(new LookupTestClient());
+ String message = mLogBuffer.toString();
+ // NOTE: This test is incompatible with the DEBUG_FORCE_REGENERATE_BINARY and WRITE_STATS
+ // flags in the ApiLookup class, so if the test fails during development and those are
+ // set, clear them.
+ assertTrue(message.contains("Please delete the file and restart the IDE/lint:"));
+ assertTrue(message.contains(mCacheDir.getPath()));
+ ApiLookup.dispose();
+
+ mLogBuffer.setLength(0);
+ assertTrue(cacheFile.exists());
+ raf = new RandomAccessFile(cacheFile, "rw");
+ // Truncate file in half in the data portion
+ raf.setLength(raf.length() / 2);
+ raf.close();
+ lookup = ApiLookup.get(new LookupTestClient());
+ // This data is now truncated: lookup returns the wrong size.
+ try {
+ assertNotNull(lookup);
+ lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance");
+ fail("Can't look up bogus data");
+ } catch (Throwable t) {
+ // Expected this: the database is corrupted.
+ }
+ assertTrue(message.contains("Please delete the file and restart the IDE/lint:"));
+ assertTrue(message.contains(mCacheDir.getPath()));
+ ApiLookup.dispose();
+
+ mLogBuffer.setLength(0);
+ assertTrue(cacheFile.exists());
+ raf = new RandomAccessFile(cacheFile, "rw");
+ // Truncate file to 0 bytes
+ raf.setLength(0);
+ raf.close();
+ lookup = ApiLookup.get(new LookupTestClient());
+ assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance"));
+ assertNotNull(lookup);
+ assertEquals("", mLogBuffer.toString()); // No warnings
+ ApiLookup.dispose();
+ }
+
+ private static final boolean CHECK_DEPRECATED = true;
+
+ private static void assertSameApi(String desc, int expected, int actual) {
+ // In the database we don't distinguish between 1 and -1 (to save diskspace)
+ if (expected <= 1) {
+ expected = -1;
+ }
+ if (actual <= 1) {
+ actual = -1;
+ }
+ assertEquals(desc, expected, actual);
+ }
+
+ public void testFindEverything() throws Exception {
+ // Load the API versions file and look up every single method/field/class in there
+ // (provided since != 1) and also check the deprecated calls.
+
+ File file = createClient().findResource("platform-tools/api/api-versions.xml");
+ if (file == null || !file.exists()) {
+ return;
+ }
+
+ Api info = Api.parseApi(file);
+ assertNotNull(info);
+ for (ApiClass cls : info.getClasses().values()) {
+ int classSince = cls.getSince();
+ String className = cls.getName();
+ if (className.startsWith("android/support/")) {
+ continue;
+ }
+ assertSameApi(className, classSince, mDb.getClassVersion(className));
+
+ for (String method : cls.getAllMethods(info)) {
+ int since = cls.getMethod(method, info);
+ int index = method.indexOf('(');
+ String name = method.substring(0, index);
+ String desc = method.substring(index);
+ assertSameApi(method, since, mDb.getCallVersion(className, name, desc));
+
+ }
+ for (String method : cls.getAllFields(info)) {
+ int since = cls.getField(method, info);
+ assertSameApi(method, since, mDb.getFieldVersion(className, method));
+ }
+
+ for (Pair<String, Integer> pair : cls.getInterfaces()) {
+ String interfaceName = pair.getFirst();
+ int api = pair.getSecond();
+ assertSameApi(interfaceName, api,
+ mDb.getValidCastVersion(className, interfaceName));
+ }
+ }
+
+ if (CHECK_DEPRECATED) {
+ for (ApiClass cls : info.getClasses().values()) {
+ int classDeprecatedIn = cls.getDeprecatedIn();
+ String className = cls.getName();
+ if (className.startsWith("android/support/")) {
+ continue;
+ }
+ if (classDeprecatedIn > 1) {
+ assertSameApi(className, classDeprecatedIn,
+ mDb.getClassDeprecatedIn(className));
+ } else {
+ assertSameApi(className, -1, mDb.getClassDeprecatedIn(className));
+ }
+
+ for (String method : cls.getAllMethods(info)) {
+ int deprecatedIn = cls.getMemberDeprecatedIn(method, info);
+ int index = method.indexOf('(');
+ String name = method.substring(0, index);
+ String desc = method.substring(index);
+ assertSameApi(method + " in " + className, deprecatedIn,
+ mDb.getCallDeprecatedIn(className, name, desc));
+ }
+ for (String method : cls.getAllFields(info)) {
+ int deprecatedIn = cls.getMemberDeprecatedIn(method, info);
+ assertSameApi(method, deprecatedIn, mDb.getFieldDeprecatedIn(className,
+ method));
+ }
+ }
+ }
+ }
+
+ public void testLookUpContractSettings() {
+ assertEquals(14, mDb.getFieldVersion("android/provider/ContactsContract$Settings", "DATA_SET"));
+ }
+
+ public void testIssue196925() {
+ if (ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+ // This test doesn't work when regenerating binaries: it's tied to data
+ // not included in api-versions.xml
+ return;
+ }
+ //196925: Incorrect Lint NewApi error on FloatingActionButton#setBackgroundTintList()
+ assertEquals(7, mDb.getCallVersion("android/support/design/widget/FloatingActionButton",
+ "getBackgroundTintList", "()"));
+ assertEquals(7, mDb.getCallVersion("android/support/design/widget/FloatingActionButton",
+ "setBackgroundTintList", "(Landroid/content/res/ColorStateList;)"));
+ }
+
+ private final class LookupTestClient extends TestLintClient {
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @Override
+ public File getCacheDir(boolean create) {
+ assertNotNull(mCacheDir);
+ if (create && !mCacheDir.exists()) {
+ mCacheDir.mkdirs();
+ }
+ return mCacheDir;
+ }
+
+ @Override
+ public void log(
+ @NonNull Severity severity,
+ @Nullable Throwable exception,
+ @Nullable String format,
+ @Nullable Object... args) {
+ if (format != null) {
+ mLogBuffer.append(String.format(format, args));
+ mLogBuffer.append('\n');
+ }
+ if (exception != null) {
+ StringWriter writer = new StringWriter();
+ exception.printStackTrace(new PrintWriter(writer));
+ mLogBuffer.append(writer.toString());
+ mLogBuffer.append('\n');
+ }
+ }
+
+ @Override
+ public void log(Throwable exception, String format, Object... args) {
+ log(Severity.WARNING, exception, format, args);
+ }
+ }
+
+ /**
+ * Finds the most recent version of the support/appcompat library, and for any
+ * classes that extend framework classes, creates a list of APIs that should
+ * <b>not</b> be flagged when called via the support library (since the support
+ * library provides a backport of the APIs).
+ * <p>
+ * Example: {@code FloatingActionButton#setBackgroundTintList()}
+ * This method is available on any version, yet it extends a method
+ * ({@code ImageButton#setBackgroundTintList} which has min api 21) so lint
+ * flags it.
+ */
+ public void testSupportLibraryMap() throws Exception {
+ if (ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+ generateSupportLibraryFile();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void generateSupportLibraryFile() throws Exception {
+ //noinspection PointlessBooleanExpression
+ if (!ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) {
+ System.out.println("Ignoring " + getName() + " since"
+ + " ApiLookup.DEBUG_FORCE_REGENERATE_BINARY is not set to true");
+ return;
+ }
+ File sdkHome = createClient().getSdkHome();
+ if (sdkHome == null) {
+ System.err.println("Ignoring " + getName() + ": no SDK home found");
+ return;
+ }
+
+ File root = ANDROID.getRepositoryLocation(sdkHome, true, FileOpUtils.create());
+ if (root == null) {
+ System.out.println("No android support repository installed in the SDK home");
+ return;
+ }
+
+ @SuppressWarnings("SpellCheckingInspection")
+ String[] artifacts = new String[] {
+ "appcompat-v7",
+ "cardview-v7",
+ "customtabs",
+ "design",
+ "gridlayout-v7",
+ "leanback-v17",
+ "mediarouter-v7",
+ "multidex",
+ "multidex-instrumentation",
+ "palette-v7",
+ "percent",
+ "preference-leanback-v17",
+ "preference-v14",
+ "preference-v7",
+ "recommendation",
+ "recyclerview-v7",
+ "support-annotations",
+ "support-v13",
+ "support-v4",
+// "test"
+ };
+ String groupId = "com.android.support";
+
+ Map<String, ClassNode> classes = Maps.newHashMapWithExpectedSize(1000);
+ Map<String, Integer> minSdkMap = Maps.newHashMapWithExpectedSize(1000);
+
+ for (String artifact : artifacts) {
+ GradleCoordinate version = ANDROID.getHighestInstalledVersion(sdkHome, groupId,
+ artifact, null, true, FileOpUtils.create());
+ String revision = version.getRevision();
+ File file = new File(root, groupId.replace('.', separatorChar) + separatorChar
+ + artifact + separatorChar + revision
+ + separatorChar + artifact + "-" + revision + DOT_AAR);
+ if (!file.exists()) {
+ String path = file.getPath();
+ path = path.substring(0, path.length() - DOT_AAR.length()) + DOT_JAR;
+ file = new File(path);
+ if (!file.exists()) {
+ System.err.println(
+ "Ignoring artifact " + artifact + ": couldn't find .aar/.jar file");
+ continue;
+ }
+ }
+
+ System.out.println("Analyzing file " + file);
+
+ byte[] bytes = Files.toByteArray(file);
+ String path = file.getPath();
+ if (path.endsWith(DOT_AAR)) {
+ analyzeAar(bytes, classes, minSdkMap);
+ } else {
+ assertTrue(path, path.endsWith(DOT_JAR));
+ analyzeJar(bytes, classes, minSdkMap, -1);
+ }
+ }
+
+ System.out.println("Found " + classes.size() + " classes (including innerclasses)");
+ File file = createClient().findResource("platform-tools/api/api-versions.xml");
+ if (file == null || !file.exists()) {
+ System.out.println("No API versions xml file found.");
+ return;
+ }
+
+ Api api = Api.parseApi(file);
+
+ Document document = XmlUtils.parseDocument(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<!--\n"
+ + " ~ Copyright (C) 2015 The Android Open Source Project\n"
+ + " ~\n"
+ + " ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+ + " ~ you may not use this file except in compliance with the License.\n"
+ + " ~ You may obtain a copy of the License at\n"
+ + " ~\n"
+ + " ~ http://www.apache.org/licenses/LICENSE-2.0\n"
+ + " ~\n"
+ + " ~ Unless required by applicable law or agreed to in writing, software\n"
+ + " ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+ + " ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+ + " ~ See the License for the specific language governing permissions and\n"
+ + " ~ limitations under the License.\n"
+ + " -->\n"
+ + "<!-- This file is generated by ApiLookupTest#generateSupportLibraryFile() -->\n"
+ + "<api version=\"2\"/>", false);
+ Element rootElement = document.getDocumentElement();
+
+ Set<ClassNode> referencedClasses = Sets.newHashSetWithExpectedSize(100);
+ Set<ClassNode> referencedSuperClasses = Sets.newHashSetWithExpectedSize(100);
+
+ // Walk through the various support classes, and walk up the inheritance chain
+ // to see if it extends a class from the support library, and if so, mark
+ // any methods as deliberately having a lower API level
+ for (ClassNode node : sorted(classes.values())) {
+ String name = node.name;
+ if (name.indexOf('$') != -1) {
+ // Ignore inner classes
+ continue;
+ }
+ if ((node.access & Modifier.PUBLIC) == 0) {
+ continue;
+ }
+ ApiClass apiClass = extendsKnownApi(api, node, classes);
+ if (apiClass != null && !apiClass.getName().equals("java/lang/Object")) {
+ @SuppressWarnings("unchecked") // ASM API
+ List<MethodNode> methodList = sorted((List<MethodNode>) node.methods);
+ if (methodList.isEmpty()) {
+ return;
+ }
+
+ int supportMin = getMinSdk(node.name, minSdkMap);
+ Element classNode = null;
+
+ for (MethodNode method : methodList) {
+ String signature = method.name + method.desc;
+ int end = signature.indexOf(')');
+ if (end != -1) {
+ signature = signature.substring(0, end + 1);
+ }
+ int methodSince = apiClass.getMethod(signature, api);
+ if (methodSince < Integer.MAX_VALUE) {
+ if (supportMin < methodSince) {
+ referencedClasses.add(node);
+ if (classNode == null) {
+ classNode = document.createElement("class");
+ rootElement.appendChild(classNode);
+ classNode.setAttribute("name", node.name);
+ classNode.setAttribute("since", Integer.toString(supportMin));
+ if (node.superName != null) {
+ Element extendsNode = document.createElement("extends");
+ classNode.appendChild(extendsNode);
+ extendsNode.setAttribute("name", node.superName);
+
+ ClassNode superClassNode = classes.get(node.superName);
+ while (superClassNode != null) {
+ referencedSuperClasses.add(superClassNode);
+ superClassNode = classes.get(superClassNode.superName);
+ }
+ }
+ }
+ Element methodNode = document.createElement("method");
+ classNode.appendChild(methodNode);
+ methodNode.setAttribute("name", method.name + method.desc);
+ methodNode.setAttribute("since", Integer.toString(supportMin));
+ }
+ }
+ }
+ }
+ }
+
+ // Also list any super classes referenced such that we ensure we have super-class
+ // references to them in the ApiClass info (such that it can correctly pull in
+ // methods from the framework to check their since-versions relative to the class'
+ // own since value)
+ referencedSuperClasses.removeAll(referencedClasses);
+ if (!referencedSuperClasses.isEmpty()) {
+ rootElement.appendChild(document.createTextNode("\n"));
+ rootElement.appendChild(document.createComment("Referenced Super Classes"));
+ for (ClassNode node : sorted(referencedSuperClasses)) {
+ int supportMin = getMinSdk(node.name, minSdkMap);
+ Element classNode = document.createElement("class");
+ rootElement.appendChild(classNode);
+ classNode.setAttribute("name", node.name);
+ classNode.setAttribute("since", Integer.toString(supportMin));
+ if (node.superName != null) {
+ Element extendsNode = document.createElement("extends");
+ classNode.appendChild(extendsNode);
+ extendsNode.setAttribute("name", node.superName);
+ }
+ }
+ }
+
+ String xml = XmlPrettyPrinter.prettyPrint(document, XmlFormatPreferences.defaults(),
+ XmlFormatStyle.RESOURCE, "\n", false);
+ xml = xml.replace("\n\n", "\n");
+
+ File xmlFile = findSrcDir();
+ if (xmlFile == null) {
+ System.out.println("Ignoring " + getName() + ": Should set $ANDROID_SRC to point "
+ + "to source dir to run this test");
+ return;
+ }
+ xmlFile = new File(xmlFile, ("tools/base/lint/libs/lint-checks/src/main/java/com/android/"
+ + "tools/lint/checks/api-versions-support-library.xml").replace('/', separatorChar));
+ assertTrue(xmlFile.getPath(), xmlFile.exists());
+ String prev = Files.toString(xmlFile, UTF_8);
+ assertEquals(prev, xml);
+ }
+
+ @NonNull
+ private static List<MethodNode> sorted(List<MethodNode> methods) {
+ List<MethodNode> sorted = Lists.newArrayList(methods);
+ Collections.sort(sorted, new Comparator<MethodNode>() {
+ @Override
+ public int compare(MethodNode node1, MethodNode node2) {
+ int delta = node1.name.compareTo(node2.name);
+ if (delta != 0) {
+ return delta;
+ }
+ return node1.desc.compareTo(node2.desc);
+ }
+ });
+ return sorted;
+ }
+
+ @NonNull
+ private static List<ClassNode> sorted(Collection<ClassNode> classes) {
+ List<ClassNode> sorted = Lists.newArrayList(classes);
+ Collections.sort(sorted, new Comparator<ClassNode>() {
+ @Override
+ public int compare(ClassNode node1, ClassNode node2) {
+ return node1.name.compareTo(node2.name);
+ }
+ });
+ return sorted;
+ }
+
+ private static int getMinSdk(@NonNull String name, @NonNull Map<String, Integer> minSdkMap) {
+ Integer min = minSdkMap.get(name);
+ if (min != null) {
+ return min;
+ }
+ String prefix = "android/support/v";
+ if (name.startsWith(prefix)) {
+ int endIndex = name.indexOf('/', prefix.length());
+ if (endIndex != -1) {
+ return Integer.parseInt(name.substring(prefix.length(), endIndex));
+ }
+ }
+
+ return 7;
+ }
+
+ @Nullable
+ private static ClassNode getSuperClass(@NonNull ClassNode node,
+ @NonNull Map<String, ClassNode> classes) {
+ if (node.superName != null) {
+ return classes.get(node.superName);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static ApiClass extendsKnownApi(@NonNull Api api, @Nullable ClassNode node,
+ @NonNull Map<String, ClassNode> classes) {
+ while (node != null) {
+ ApiClass cls = api.getClass(node.name);
+ if (cls != null) {
+ return cls;
+ }
+
+ ClassNode superClass = getSuperClass(node, classes);
+ if (superClass == null && node.superName != null) {
+ // Pointing up into android.jar, not in our class map?
+ return api.getClass(node.superName);
+ } else {
+ node = superClass;
+ }
+ }
+
+ return null;
+ }
+
+ private static void analyzeAar(@NonNull byte[] bytes, @NonNull Map<String, ClassNode> classes,
+ @NonNull Map<String, Integer> minSdkMap) throws Exception {
+ JarInputStream zis = null;
+ try {
+ InputStream fis = new ByteArrayInputStream(bytes);
+ try {
+ zis = new JarInputStream(fis);
+ ZipEntry entry = zis.getNextEntry();
+ int minSdk = -1;
+ while (entry != null) {
+ String name = entry.getName();
+ if (name.equals(ANDROID_MANIFEST_XML)) {
+ byte[] b = ByteStreams.toByteArray(zis);
+ assertNotNull(b);
+ String xml = new String(b, UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+ for (Element element : getChildren(document.getDocumentElement())) {
+ if (element.getTagName().equals(TAG_USES_SDK)) {
+ String min = element.getAttributeNS(ANDROID_URI,
+ ATTR_MIN_SDK_VERSION);
+ if (!min.isEmpty()) {
+ try {
+ minSdk = Integer.parseInt(min);
+ } catch (NumberFormatException e) {
+ fail(e.toString());
+ }
+ }
+ }
+ }
+ } else if (name.equals(FN_CLASSES_JAR)) {
+ // Bingo!
+ byte[] b = ByteStreams.toByteArray(zis);
+ assertNotNull(b);
+ analyzeJar(b, classes, minSdkMap, minSdk);
+ break;
+ }
+ entry = zis.getNextEntry();
+ }
+ } finally {
+ Closeables.close(fis, true);
+ }
+ } finally {
+ Closeables.close(zis, false);
+ }
+ }
+
+ private static void analyzeJar(@NonNull byte[] bytes, @NonNull Map<String, ClassNode> classes,
+ @NonNull Map<String, Integer> minSdkMap, int manifestMinSdk) throws Exception {
+ JarInputStream zis = null;
+ try {
+ InputStream fis = new ByteArrayInputStream(bytes);
+ try {
+ zis = new JarInputStream(fis);
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+ if (name.endsWith(DOT_CLASS)) {
+ // Bingo!
+ byte[] b = ByteStreams.toByteArray(zis);
+ if (b != null) {
+ analyzeClass(b, classes, minSdkMap, manifestMinSdk);
+ }
+ }
+ entry = zis.getNextEntry();
+ }
+ } finally {
+ Closeables.close(fis, true);
+ }
+ } finally {
+ Closeables.close(zis, false);
+ }
+ }
+
+ private static void analyzeClass(@NonNull byte[] bytes,
+ @NonNull Map<String, ClassNode> classes, @NonNull Map<String, Integer> minSdkMap,
+ int manifestMinSdk) {
+
+ ClassReader reader = new ClassReader(bytes);
+ ClassNode classNode = new ClassNode();
+ reader.accept(classNode, 0 /* flags */);
+
+ assertNull(classes.get(classNode.name));
+ classes.put(classNode.name, classNode);
+
+ int minSdk = manifestMinSdk != -1 ? manifestMinSdk : getMinSdk(classNode.name, minSdkMap);
+ minSdkMap.put(classNode.name, minSdk);
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatCallDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatCallDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatCallDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatCallDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatResourceDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatResourceDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppCompatResourceDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java
new file mode 100644
index 0000000..0fa1b47
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppIndexingApiDetectorTest.java
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class AppIndexingApiDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new AppIndexingApiDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ return true;
+ }
+
+ public void testOk() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testDataMissing() throws Exception {
+ assertEquals(AppIndexingApiDetector.IssueType.DATA_MISSING, AppIndexingApiDetector.IssueType.parse("Missing data element"));
+ assertEquals(""
+ + "AndroidManifest.xml:15: Error: Missing data element [GoogleAppIndexingUrlError]\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " ^\n"
+ + "AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " ^\n"
+ + "1 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testNoUrl() throws Exception {
+ assertEquals(AppIndexingApiDetector.IssueType.URL_MISSING, AppIndexingApiDetector.IssueType.parse("Missing URL for the intent filter"));
+ assertEquals(""
+ + "AndroidManifest.xml:17: Error: Missing URL for the intent filter [GoogleAppIndexingUrlError]\n"
+ + " <data />\n"
+ + " ~~~~~~~~\n"
+ + "AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " ^\n"
+ + "1 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMimeType() throws Exception {
+ assertEquals("AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:mimeType=\"mimetype\" /> "
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testNoActivity() throws Exception {
+ assertEquals(
+ ""
+ + "AndroidManifest.xml:5: Warning: App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW intent-filler. See issue explanation for more details. [GoogleAppIndexingWarning]\n"
+ + " <application\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testNoWarningInLibraries() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=194937
+ // 194937: App indexing lint check shouldn't apply to library projects
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ // Mark project as library
+ source("project.properties", "android.library=true\n")));
+ }
+
+ public void testNoActionView() throws Exception {
+ assertEquals(
+ ""
+ + "AndroidManifest.xml:5: Warning: App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW intent-filler. See issue explanation for more details. [GoogleAppIndexingWarning]\n"
+ + " <application\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".MainActivity\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testNotBrowsable() throws Exception {
+ assertEquals(AppIndexingApiDetector.IssueType.NOT_BROWSABLE, AppIndexingApiDetector.IssueType.parse("Activity supporting ACTION_VIEW is not set as BROWSABLE"));
+ assertEquals(""
+ + "AndroidManifest.xml:25: Warning: Activity supporting ACTION_VIEW is not set as BROWSABLE [GoogleAppIndexingWarning]\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".MainActivity\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + "\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testWrongPathPrefix() throws Exception {
+ assertEquals(AppIndexingApiDetector.IssueType.MISSING_SLASH, AppIndexingApiDetector.IssueType.parse("android:pathPrefix attribute should start with '/', but it is : gizmos"));
+ assertEquals(""
+ + "AndroidManifest.xml:19: Error: android:pathPrefix attribute should start with '/', but it is : gizmos [GoogleAppIndexingUrlError]\n"
+ + " android:pathPrefix=\"gizmos\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testWrongPort() throws Exception {
+ assertEquals(AppIndexingApiDetector.IssueType.ILLEGAL_NUMBER, AppIndexingApiDetector.IssueType.parse("android:port is not a legal number"));
+ assertEquals(""
+ + "AndroidManifest.xml:19: Error: android:port is not a legal number [GoogleAppIndexingUrlError]\n"
+ + " android:port=\"ABCD\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:port=\"ABCD\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testSchemeAndHostMissing() throws Exception {
+ assertEquals(AppIndexingApiDetector.IssueType.SCHEME_MISSING, AppIndexingApiDetector.IssueType.parse("android:scheme is missing"));
+ assertEquals(AppIndexingApiDetector.IssueType.HOST_MISSING, AppIndexingApiDetector.IssueType.parse("android:host is missing"));
+ assertEquals(""
+ + "AndroidManifest.xml:17: Error: Missing URL for the intent filter [GoogleAppIndexingUrlError]\n"
+ + " <data android:pathPrefix=\"/gizmos\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:17: Error: android:host is missing [GoogleAppIndexingUrlError]\n"
+ + " <data android:pathPrefix=\"/gizmos\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:17: Error: android:scheme is missing [GoogleAppIndexingUrlError]\n"
+ + " <data android:pathPrefix=\"/gizmos\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:15: Warning: Missing URL [GoogleAppIndexingWarning]\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " ^\n"
+ + "3 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMultiData() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\" />\n"
+ + " <data android:host=\"example.com\" />\n"
+ + " <data android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMultiIntent() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMultiIntentWithError() throws Exception {
+ assertEquals(""
+ + "AndroidManifest.xml:20: Error: android:host is missing [GoogleAppIndexingUrlError]\n"
+ + " <data android:scheme=\"http\"\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testNotExported() throws Exception {
+ assertEquals("AndroidManifest.xml:10: Error: Activity supporting ACTION_VIEW is not exported [GoogleAppIndexingUrlError]\n"
+ + " <activity android:exported=\"false\"\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " <activity android:exported=\"false\"\n"
+ + " android:name=\".FullscreenActivity\"\n"
+ + " android:configChanges=\"orientation|keyboardHidden|screenSize\"\n"
+ + " android:label=\"@string/title_activity_fullscreen\"\n"
+ + " android:theme=\"@style/FullscreenTheme\" >\n"
+ + " <intent-filter android:label=\"@string/title_activity_fullscreen\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <meta-data android:name=\"com.google.android.gms.version\" android:value=\"@integer/google_play_services_version\" />"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testOkWithResource() throws Exception {
+ assertEquals("No warnings.",
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ "appindexing_manifest.xml=>AndroidManifest.xml",
+ "res/values/appindexing_strings.xml"));
+ }
+
+ public void testWrongWithResource() throws Exception {
+ assertEquals("" + "AndroidManifest.xml:18: Error: android:pathPrefix attribute should start with '/', but it is : pathprefix [GoogleAppIndexingUrlError]\n"
+ + " android:pathPrefix=\"@string/path_prefix\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:19: Error: android:port is not a legal number [GoogleAppIndexingUrlError]\n"
+ + " android:port=\"@string/port\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ "appindexing_manifest.xml=>AndroidManifest.xml",
+ "res/values/appindexing_wrong_strings.xml"));
+ }
+
+ public void testJavaOk() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestOk.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testNoManifest() throws Exception {
+ assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:28: Warning: Missing support for Google App Indexing in the manifest [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.start(mClient, action);\n"
+ + " ~~~~~\n"
+ + "src/com/example/helloworld/AppIndexingApiTest.java:36: Warning: Missing support for Google App Indexing in the manifest [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.end(mClient, action);\n"
+ + " ~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestOk.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testNoStartEnd() throws Exception {
+ assertEquals(""
+ + "src/com/example/helloworld/AppIndexingApiTest.java:11: Warning: Missing support for Google App Indexing API [GoogleAppIndexingApiWarning]\n"
+ + "public class AppIndexingApiTest extends Activity {\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testStartMatch() throws Exception {
+ assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:27: Warning: GoogleApiClient mClient is not connected [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.start(mClient, action);\n"
+ + " ~~~~~~~\n"
+ + "src/com/example/helloworld/AppIndexingApiTest.java:27: Warning: Missing corresponding AppIndex.AppIndexApi.end method [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.start(mClient, action);\n"
+ + " ~~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testEndMatch() throws Exception {
+ assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:33: Warning: GoogleApiClient mClient is not disconnected [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.end(mClient, action);\n"
+ + " ~~~~~~~\n"
+ + "src/com/example/helloworld/AppIndexingApiTest.java:33: Warning: Missing corresponding AppIndex.AppIndexApi.start method [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.end(mClient, action);\n"
+ + " ~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testViewMatch() throws Exception {
+ assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:26: Warning: GoogleApiClient mClient is not connected [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.view(mClient, this, APP_URI, title, WEB_URL, null);\n"
+ + " ~~~~~~~\n"
+ + "src/com/example/helloworld/AppIndexingApiTest.java:26: Warning: Missing corresponding AppIndex.AppIndexApi.end method [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.view(mClient, this, APP_URI, title, WEB_URL, null);\n"
+ + " ~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testViewEndMatch() throws Exception {
+ assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:29: Warning: GoogleApiClient mClient is not disconnected [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.viewEnd(mClient, this, APP_URI);\n"
+ + " ~~~~~~~\n"
+ + "src/com/example/helloworld/AppIndexingApiTest.java:29: Warning: Missing corresponding AppIndex.AppIndexApi.start method [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.viewEnd(mClient, this, APP_URI);\n"
+ + " ~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testWrongOrder() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+ public void testGoogleApiClientAddApi() throws Exception {
+ assertEquals("" + "src/com/example/helloworld/AppIndexingApiTest.java:28: Warning: GoogleApiClient mClient has not added support for App Indexing API [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.start(mClient, action);\n"
+ + " ~~~~~~~\n"
+ + "src/com/example/helloworld/AppIndexingApiTest.java:36: Warning: GoogleApiClient mClient has not added support for App Indexing API [GoogleAppIndexingApiWarning]\n"
+ + " AppIndex.AppIndexApi.end(mClient, action);\n"
+ + " ~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ "src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt=>src/com/example/helloworld/AppIndexingApiTest.java",
+ "app_indexing_api_test.xml=>AndroidManifest.xml",
+ "src/com/appindexing/AppIndex.java.txt=>src/com/google/android/gms/appindexing/AppIndex.java",
+ "src/com/appindexing/AppIndexApi.java.txt=>src/com/google/android/gms/appindexing/AppIndexApi.java",
+ "src/com/appindexing/GoogleApiClient.java.txt=>src/com/google/android/gms/common/api/GoogleApiClient.java",
+ "src/com/appindexing/Activity.java.txt=>src/com/google/android/app/Activity.java",
+ "src/com/appindexing/Api.java.txt=>src/com/google/android/gms/common/api/Api.java"));
+ }
+
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetectorTest.java
new file mode 100644
index 0000000..674a6af
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AppLinksAutoVerifyDetectorTest.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+import com.google.common.collect.Maps;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import java.util.Map;
+
+ at SuppressWarnings("javadoc")
+public class AppLinksAutoVerifyDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new AppLinksAutoVerifyDetector();
+ }
+
+ public void testOk() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ JsonArray statementList = new JsonArray();
+ JsonObject statement = new JsonObject();
+ JsonArray relation = new JsonArray();
+ relation.add(new JsonPrimitive("delegate_permission/common.handle_all_urls"));
+ statement.add("relation", relation);
+ JsonObject target = new JsonObject();
+ target.addProperty("namespace", "android_app");
+ target.addProperty("package_name", "com.example.helloworld");
+ statement.add("target", target);
+ statementList.add(statement);
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_HTTP_OK, statementList));
+
+ assertEquals("No warnings.",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testInvalidPackage() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ JsonArray statementList = new JsonArray();
+ JsonObject statement = new JsonObject();
+ JsonArray relation = new JsonArray();
+ relation.add(new JsonPrimitive("delegate_permission/common.handle_all_urls"));
+ statement.add("relation", relation);
+ JsonObject target = new JsonObject();
+ target.addProperty("namespace", "android_app");
+ target.addProperty("package_name", "com.example");
+ statement.add("target", target);
+ statementList.add(statement);
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_HTTP_OK, statementList));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Error: This host does not support app links to your app. Checks the Digital Asset Links JSON file: http://example.com/.well-known/assetlinks.json [AppLinksAutoVerifyError]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testNotAppTarget() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ JsonArray statementList = new JsonArray();
+ JsonObject statement = new JsonObject();
+ JsonArray relation = new JsonArray();
+ relation.add(new JsonPrimitive("delegate_permission/common.handle_all_urls"));
+ statement.add("relation", relation);
+ JsonObject target = new JsonObject();
+ target.addProperty("namespace", "web");
+ target.addProperty("package_name", "com.example.helloworld");
+ statement.add("target", target);
+ statementList.add(statement);
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_HTTP_OK, statementList));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Error: This host does not support app links to your app. Checks the Digital Asset Links JSON file: http://example.com/.well-known/assetlinks.json [AppLinksAutoVerifyError]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testHttpResponseError() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(404, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: HTTP request for Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails. HTTP response code: 404 [AppLinksAutoVerifyWarning]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testFailedHttpConnection() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_HTTP_CONNECT_FAIL, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Connection to Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyWarning]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testMalformedUrl() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_MALFORMED_URL, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Error: Malformed URL of Digital Asset Links JSON file: http://example.com/.well-known/assetlinks.json. An unknown protocol is specified [AppLinksAutoVerifyError]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testUnknownHost() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_UNKNOWN_HOST, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Unknown host: http://example.com. Check if the host exists, and check your network connection [AppLinksAutoVerifyWarning]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testNotFound() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_NOT_FOUND, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Error: Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json is not found on the host [AppLinksAutoVerifyError]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testWrongJsonSyntax() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_WRONG_JSON_SYNTAX, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Error: http://example.com/.well-known/assetlinks.json has incorrect JSON syntax [AppLinksAutoVerifyError]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testFailedJsonParsing() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_JSON_PARSE_FAIL, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Error: Parsing JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyError]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testNoAutoVerify() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testNotAppLinkInIntents() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " </intent-filter>\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMultipleLinks() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_HTTP_CONNECT_FAIL, null));
+ data.put("https://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_NOT_FOUND, null));
+ data.put("http://www.example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_UNKNOWN_HOST, null));
+ data.put("https://www.example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_WRONG_JSON_SYNTAX, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Error: Digital Asset Links JSON file https://example.com/.well-known/assetlinks.json is not found on the host [AppLinksAutoVerifyError]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:15: Error: https://www.example.com/.well-known/assetlinks.json has incorrect JSON syntax [AppLinksAutoVerifyError]\n"
+ + " <data android:host=\"www.example.com\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:12: Warning: Connection to Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyWarning]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:15: Warning: Unknown host: http://www.example.com. Check if the host exists, and check your network connection [AppLinksAutoVerifyWarning]\n"
+ + " <data android:host=\"www.example.com\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 2 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <data android:scheme=\"https\" />\n"
+ + " <data android:host=\"www.example.com\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+
+ public void testMultipleIntents() throws Exception {
+ try {
+ Map<String, AppLinksAutoVerifyDetector.HttpResult> data = Maps.newHashMap();
+ AppLinksAutoVerifyDetector.sMockData = data;
+ data.put("http://example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_HTTP_CONNECT_FAIL, null));
+ data.put("http://www.example.com", new AppLinksAutoVerifyDetector.HttpResult(
+ AppLinksAutoVerifyDetector.STATUS_UNKNOWN_HOST, null));
+
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Unknown host: http://www.example.com. Check if the host exists, and check your network connection [AppLinksAutoVerifyWarning]\n"
+ + " android:host=\"www.example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:20: Warning: Connection to Digital Asset Links JSON file http://example.com/.well-known/assetlinks.json fails [AppLinksAutoVerifyWarning]\n"
+ + " android:host=\"example.com\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@mipmap/ic_launcher\" >\n"
+ + " <activity android:name=\".MainActivity\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"www.example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " <intent-filter android:autoVerify=\"true\">\n"
+ + " <action android:name=\"android.intent.action.VIEW\" />\n"
+ + " <data android:scheme=\"http\"\n"
+ + " android:host=\"example.com\"\n"
+ + " android:pathPrefix=\"/gizmos\" />\n"
+ + " <category android:name=\"android.intent.category.DEFAULT\" />\n"
+ + " <category android:name=\"android.intent.category.BROWSABLE\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ } finally {
+ AppLinksAutoVerifyDetector.sMockData = null;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ArraySizeDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AssertDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AssertDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AssertDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AssertDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BadHostnameVerifierDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BadHostnameVerifierDetectorTest.java
new file mode 100644
index 0000000..1dd48bc
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BadHostnameVerifierDetectorTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "ImplicitArrayToString",
+ "ConstantConditions", "ConstantIfStatement"})
+public class BadHostnameVerifierDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new BadHostnameVerifierDetector();
+ }
+
+ public void testBroken() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/InsecureHostnameVerifier.java:9: Warning: verify always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames [BadHostnameVerifier]\n"
+ + " public boolean verify(String hostname, SSLSession session) {\n"
+ + " ~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <service\n"
+ + " android:name=\".InsecureHostnameVerifier\" >\n"
+ + " </service>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ copy("res/values/strings.xml"),
+ java("src/test/pkg/InsecureHostnameVerifier.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import javax.net.ssl.HostnameVerifier;\n"
+ + "import javax.net.ssl.SSLSession;\n"
+ + "\n"
+ + "public abstract class InsecureHostnameVerifier {\n"
+ + " HostnameVerifier allowAll = new HostnameVerifier() {\n"
+ + " @Override\n"
+ + " public boolean verify(String hostname, SSLSession session) {\n"
+ + " return true;\n"
+ + " }\n"
+ + " };\n"
+ + "\n"
+ + " HostnameVerifier allowAll2 = new HostnameVerifier() {\n"
+ + " @Override\n"
+ + " public boolean verify(String hostname, SSLSession session) {\n"
+ + " boolean returnValue = true;\n"
+ + " if (true) {\n"
+ + " int irrelevant = 5;\n"
+ + " if (irrelevant > 6) {\n"
+ + " return returnValue;\n"
+ + " }\n"
+ + " }\n"
+ + " return returnValue;\n"
+ + " }\n"
+ + " };\n"
+ + "\n"
+ + " HostnameVerifier unknown = new HostnameVerifier() {\n"
+ + " @Override\n"
+ + " public boolean verify(String hostname, SSLSession session) {\n"
+ + " boolean returnValue = true;\n"
+ + " if (hostname.contains(\"something\")) {\n"
+ + " returnValue = false;\n"
+ + " }\n"
+ + " return returnValue;\n"
+ + " }\n"
+ + " };\n"
+ + "}\n"
+ + "\n")
+ ));
+ }
+
+ public void testCorrect() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <service\n"
+ + " android:name=\".ExampleHostnameVerifier\" >\n"
+ + " </service>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ copy("res/values/strings.xml"),
+ java("src/test/pkg/ExampleHostnameVerifier.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.Intent;\n"
+ + "import android.app.IntentService;\n"
+ + "\n"
+ + "import java.io.IOException;\n"
+ + "import java.net.URL;\n"
+ + "import javax.net.ssl.HostnameVerifier;\n"
+ + "import javax.net.ssl.HttpsURLConnection;\n"
+ + "import javax.net.ssl.SSLContext;\n"
+ + "import javax.net.ssl.SSLSession;\n"
+ + "import javax.net.ssl.TrustManager;\n"
+ + "import javax.net.ssl.X509TrustManager;\n"
+ + "\n"
+ + "import org.apache.http.conn.ssl.SSLSocketFactory;\n"
+ + "import org.apache.http.conn.ssl.StrictHostnameVerifier;\n"
+ + "\n"
+ + "public class ExampleHostnameVerifier extends IntentService {\n"
+ + " HostnameVerifier denyAll = new HostnameVerifier() {\n"
+ + " @Override\n"
+ + " public boolean verify(String hostname, SSLSession session) {\n"
+ + " return false;\n"
+ + " }\n"
+ + " };\n"
+ + "\n"
+ + " public ExampleHostnameVerifier() {\n"
+ + " super(\"ExampleHostnameVerifier\");\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " protected void onHandleIntent(Intent intent) {\n"
+ + " try {\n"
+ + " URL url = new URL(\"https://www.google.com\");\n"
+ + " HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();\n"
+ + " connection.setHostnameVerifier(denyAll);\n"
+ + " } catch (IOException e) {\n"
+ + " System.out.println(e.getStackTrace());\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
new file mode 100644
index 0000000..a7922be
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/BuiltinIssueRegistryTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
+
+import junit.framework.TestCase;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class BuiltinIssueRegistryTest extends TestCase {
+ public void testNoListResize() {
+ BuiltinIssueRegistry registry = new BuiltinIssueRegistry();
+ List<Issue> issues = registry.getIssues();
+ int issueCount = issues.size();
+ assertTrue(Integer.toString(issueCount),
+ BuiltinIssueRegistry.INITIAL_CAPACITY >= issueCount);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testCapacities() throws IllegalAccessException {
+ TestIssueRegistry registry = new TestIssueRegistry();
+ for (Scope scope : Scope.values()) {
+ EnumSet<Scope> scopeSet = EnumSet.of(scope);
+ checkCapacity(registry, scopeSet);
+ }
+
+ // Also check the commonly used combinations
+ for (Field field : Scope.class.getDeclaredFields()) {
+ if (field.getType().isAssignableFrom(EnumSet.class)) {
+ checkCapacity(registry, (EnumSet<Scope>) field.get(null));
+ }
+ }
+ }
+
+ public void testUnique() {
+ // Check that ids are unique
+ Set<String> ids = new HashSet<String>();
+ for (Issue issue : new BuiltinIssueRegistry().getIssues()) {
+ String id = issue.getId();
+ assertTrue("Duplicate id " + id, !ids.contains(id));
+ ids.add(id);
+ }
+ }
+
+ private static void checkCapacity(TestIssueRegistry registry,
+ EnumSet<Scope> scopeSet) {
+ List<Issue> issuesForScope = registry.getIssuesForScope(scopeSet);
+ int requiredSize = issuesForScope.size();
+ int capacity = registry.getIssueCapacity(scopeSet);
+ if (requiredSize > capacity) {
+ fail("For Scope set " + scopeSet + ": capacity " + capacity
+ + " < actual " + requiredSize);
+ }
+ }
+
+ public void testSimultaneousIssueMapInitialization() throws Exception {
+ // Regression test for b/27563821: bogus
+ // Error: Unknown issue id "UseCompoundDrawables"
+
+ final BuiltinIssueRegistry registry1 = new BuiltinIssueRegistry();
+ assertNotNull(registry1.getIssue(UseCompoundDrawableDetector.ISSUE.getId()));
+
+ // Step 1: Somebody resets the issue registry (usually the JarFileIssueRegistry
+ // because new custom rules have been loaded for some new library dependency
+ BuiltinIssueRegistry.reset();
+
+ // Step 2: Thread 1 calls issue registry getIssue(String id)
+ // and since the id to issue map is null (because of the above reset)
+ // it's computed in double checked locking code. It ends up calling into
+ // getIssues(). We'll make the code stall there with a barrier:
+
+ final CyclicBarrier barrier1 = new CyclicBarrier(2);
+ final CyclicBarrier barrier2 = new CyclicBarrier(2);
+ final BuiltinIssueRegistry registry2 = new BuiltinIssueRegistry() {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ final List<Issue> superList = super.getIssues();
+ // Special list constructed such that *iterating* through the list
+ // can be interrupted at the beginning. This lets us sequence timings
+ // in getIssue(String) such that we can pause the implementation
+ // right in the for loop in the middle (between the map construction
+ // and assigning the field. Prior to this bug fix, at this point
+ // the field would have been initialized and other threads could
+ // skip the whole locked region.
+ return new ArrayList<Issue>() {
+ @NotNull
+ @Override
+ public Iterator<Issue> iterator() {
+ try {
+ barrier1.await();
+
+ // With the bug, the second thread would immediately
+ // see the field (pointing to the empty map) and proceed.
+ // In that case the test fails immediately. However, when
+ // the code is working correctly, the second registry can't
+ // access the map while we're in the synchronized block - and
+ // if we wait forever on this barrier, the code will deadlock.
+ // Therefore, we only wait 3 seconds to simulate contention,
+ // and when the await finally times out it will exit the
+ // critical section, finish the map and let other registries
+ // access it.
+ barrier2.await(3, TimeUnit.SECONDS);
+ fail("Incorrect synchronization: other thread should have "
+ + "blocked in synchronized block and never reached barrier "
+ + "until timeout");
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ } catch (BrokenBarrierException ignore) {
+ // This is expected; see above
+ } catch (TimeoutException ignore) {
+ // This is expected; see above
+ }
+ return superList.listIterator();
+ }
+ };
+ }
+ };
+
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ // Trigger computation of the issue map (which will enter the
+ // synchronized section and blocking on barrier1 in getIssues()
+ registry2.getIssue(UseCompoundDrawableDetector.ISSUE.getId());
+ }
+ };
+ thread.start();
+
+ // Sync this thread with the issue thread such that we know it's inside the
+ // getIssue(String) method:
+ barrier1.await();
+
+ // Now thread 2 (the UI test thread) comes along and asks for the issue id's
+ // (while the other thread is busy computing the map - it's between barrier1 and
+ // barrier2 in getIssues() above)
+ assertNotNull(registry1.getIssue(UseCompoundDrawableDetector.ISSUE.getId()));
+
+ // All done
+ try {
+ barrier2.await();
+ fail("Incorrect synchronization: getIssue should have blocked until thread1 is done");
+ } catch (BrokenBarrierException ignore) {
+ // This is expected; see comment in iterator() above
+ }
+ thread.join();
+ }
+
+ private static class TestIssueRegistry extends BuiltinIssueRegistry {
+ // Override to make method accessible outside package
+ @NonNull
+ @Override
+ public List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
+ return super.getIssuesForScope(scope);
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ButtonDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ByteOrderMarkDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ByteOrderMarkDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ByteOrderMarkDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ByteOrderMarkDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
new file mode 100644
index 0000000..2c0a17b
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class CallSuperDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new CallSuperDetector();
+ }
+
+ public void testCallSuper() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/CallSuperTest.java:11: Error: Overriding method should call super.test1 [MissingSuperCall]\n"
+ + " protected void test1() { // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/CallSuperTest.java:14: Error: Overriding method should call super.test2 [MissingSuperCall]\n"
+ + " protected void test2() { // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/CallSuperTest.java:17: Error: Overriding method should call super.test3 [MissingSuperCall]\n"
+ + " protected void test3() { // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/CallSuperTest.java:20: Error: Overriding method should call super.test4 [MissingSuperCall]\n"
+ + " protected void test4(int arg) { // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/CallSuperTest.java:26: Error: Overriding method should call super.test5 [MissingSuperCall]\n"
+ + " protected void test5(int arg1, boolean arg2, Map<List<String>,?> arg3, // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/CallSuperTest.java:30: Error: Overriding method should call super.test5 [MissingSuperCall]\n"
+ + " protected void test5() { // ERROR\n"
+ + " ~~~~~\n"
+ + "6 errors, 0 warnings\n",
+
+ lintProject("src/test/pkg/CallSuperTest.java.txt=>src/test/pkg/CallSuperTest.java",
+ "src/android/support/annotation/CallSuper.java.txt=>src/android/support/annotation/CallSuper.java"));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testCallSuperIndirect() throws Exception {
+ // Ensure that when the @CallSuper is on an indirect super method,
+ // we correctly check that you call the direct super method, not the ancestor.
+ //
+ // Regression test for
+ // https://code.google.com/p/android/issues/detail?id=174964
+ assertEquals("No warnings.",
+ lintProject(
+ java("src/test/pkg/CallSuperTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.CallSuper;\n"
+ + "\n"
+ + "import java.util.List;\n"
+ + "import java.util.Map;\n"
+ + "\n"
+ + "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ + "public class CallSuperTest {\n"
+ + " private static class Child extends Parent {\n"
+ + " @Override\n"
+ + " protected void test1() {\n"
+ + " super.test1();\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " private static class Parent extends ParentParent {\n"
+ + " @Override\n"
+ + " protected void test1() {\n"
+ + " super.test1();\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " private static class ParentParent extends ParentParentParent {\n"
+ + " @CallSuper\n"
+ + " protected void test1() {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " private static class ParentParentParent {\n"
+ + "\n"
+ + " }\n"
+ + "}\n"),
+ copy("src/android/support/annotation/CallSuper.java.txt",
+ "src/android/support/annotation/CallSuper.java")));
+ }
+
+ public void testDetachFromWindow() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/DetachedFromWindow.java:7: Error: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
+ + " protected void onDetachedFromWindow() {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/DetachedFromWindow.java:26: Error: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
+ + " protected void onDetachedFromWindow() {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject("src/test/pkg/DetachedFromWindow.java.txt=>" +
+ "src/test/pkg/DetachedFromWindow.java"));
+ }
+
+ public void testWatchFaceVisibility() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/WatchFaceTest.java:9: Error: Overriding method should call super.onVisibilityChanged [MissingSuperCall]\n"
+ + " public void onVisibilityChanged(boolean visible) { // ERROR: Missing super call\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "src/test/pkg/WatchFaceTest.java.txt=>src/test/pkg/WatchFaceTest.java",
+ "stubs/WatchFaceService.java.txt=>src/android/support/wearable/watchface/WatchFaceService.java",
+ "stubs/CanvasWatchFaceService.java.txt=>src/android/support/wearable/watchface/CanvasWatchFaceService.java"
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ChildCountDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ChildCountDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ChildCountDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ChildCountDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CipherGetInstanceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CipherGetInstanceDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CipherGetInstanceDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CipherGetInstanceDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java
new file mode 100644
index 0000000..5d56a76
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CleanupDetectorTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class CleanupDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new CleanupDetector();
+ }
+
+ public void testRecycle() throws Exception {
+ assertEquals(
+ "src/test/pkg/RecycleTest.java:56: Warning: This TypedArray should be recycled after use with #recycle() [Recycle]\n" +
+ " final TypedArray a = getContext().obtainStyledAttributes(attrs,\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:63: Warning: This TypedArray should be recycled after use with #recycle() [Recycle]\n" +
+ " final TypedArray a = getContext().obtainStyledAttributes(new int[0]);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:79: Warning: This VelocityTracker should be recycled after use with #recycle() [Recycle]\n" +
+ " VelocityTracker tracker = VelocityTracker.obtain();\n" +
+ " ~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:92: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
+ " MotionEvent event1 = MotionEvent.obtain(null);\n" +
+ " ~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:93: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
+ " MotionEvent event2 = MotionEvent.obtainNoHistory(null);\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:98: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
+ " MotionEvent event2 = MotionEvent.obtainNoHistory(null); // Not recycled\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:103: Warning: This MotionEvent should be recycled after use with #recycle() [Recycle]\n" +
+ " MotionEvent event1 = MotionEvent.obtain(null); // Not recycled\n" +
+ " ~~~~~~\n" +
+ /* Not implemented in AST visitor; not a typical user error and easy to diagnose if it's done
+ "src/test/pkg/RecycleTest.java:113: Warning: This MotionEvent has already been recycled [Recycle]\n" +
+ " int contents2 = event1.describeContents(); // BAD, after recycle\n" +
+ " ~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:117: Warning: This TypedArray has already been recycled [Recycle]\n" +
+ " example = a.getString(R.styleable.MyView_exampleString); // BAD, after recycle\n" +
+ " ~~~~~~~~~\n" +
+ */
+ "src/test/pkg/RecycleTest.java:129: Warning: This Parcel should be recycled after use with #recycle() [Recycle]\n" +
+ " Parcel myparcel = Parcel.obtain();\n" +
+ " ~~~~~~\n" +
+ "src/test/pkg/RecycleTest.java:190: Warning: This TypedArray should be recycled after use with #recycle() [Recycle]\n" +
+ " final TypedArray a = getContext().obtainStyledAttributes(attrs, // Not recycled\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 9 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "bytecode/RecycleTest.java.txt=>src/test/pkg/RecycleTest.java",
+ "bytecode/RecycleTest.class.data=>bin/classes/test/pkg/RecycleTest.class"
+ ));
+ }
+
+ public void testCommit() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/CommitTest.java:25: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+ + " getFragmentManager().beginTransaction(); // Missing commit\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/CommitTest.java:30: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+ + " FragmentTransaction transaction2 = getFragmentManager().beginTransaction(); // Missing commit\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/CommitTest.java:39: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+ + " getFragmentManager().beginTransaction(); // Missing commit\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/CommitTest.java:65: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+ + " getSupportFragmentManager().beginTransaction();\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 4 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "bytecode/CommitTest.java.txt=>src/test/pkg/CommitTest.java",
+ "bytecode/CommitTest.class.data=>bin/classes/test/pkg/CommitTest.class",
+ // Stubs just to be able to do type resolution without needing the full appcompat jar
+ "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
+ "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
+ "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
+ "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
+ ));
+ }
+
+ public void testCommit2() throws Exception {
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "bytecode/DialogFragment.class.data=>bin/classes/test/pkg/DialogFragment.class",
+ // Stubs just to be able to do type resolution without needing the full appcompat jar
+ "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
+ "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
+ "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
+ "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
+ ));
+ }
+
+ public void testCommit3() throws Exception {
+ assertEquals("" +
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "bytecode/CommitTest2.java.txt=>src/test/pkg/CommitTest2.java",
+ "bytecode/CommitTest2$MyDialogFragment.class.data=>bin/classes/test/pkg/CommitTest2$MyDialogFragment.class",
+ "bytecode/CommitTest2.class.data=>bin/classes/test/pkg/CommitTest2.class",
+ // Stubs just to be able to do type resolution without needing the full appcompat jar
+ "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
+ "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
+ "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
+ "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
+ ));
+ }
+
+ public void testCommit4() throws Exception {
+ assertEquals("" +
+ "src/test/pkg/CommitTest3.java:35: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+ + " getCompatFragmentManager().beginTransaction();\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "bytecode/CommitTest3.java.txt=>src/test/pkg/CommitTest3.java",
+ "bytecode/CommitTest3.class.data=>bin/classes/test/pkg/CommitTest3.class",
+ "bytecode/CommitTest3$MyDialogFragment.class.data=>bin/classes/test/pkg/CommitTest3$MyDialogFragment.class",
+ "bytecode/CommitTest3$MyCompatDialogFragment.class.data=>bin/classes/test/pkg/CommitTest3$MyCompatDialogFragment.class",
+ // Stubs just to be able to do type resolution without needing the full appcompat jar
+ "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
+ "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
+ "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
+ "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
+ ));
+ }
+
+ public void testCommitChainedCalls() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=135204
+ assertEquals(""
+ + "src/test/pkg/TransactionTest.java:8: Warning: This transaction should be completed with a commit() call [CommitTransaction]\n"
+ + " android.app.FragmentTransaction transaction2 = getFragmentManager().beginTransaction();\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "src/test/pkg/TransactionTest.java.txt=>src/test/pkg/TransactionTest.java",
+ // Stubs just to be able to do type resolution without needing the full appcompat jar
+ "appcompat/Fragment.java.txt=>src/android/support/v4/app/Fragment.java",
+ "appcompat/DialogFragment.java.txt=>src/android/support/v4/app/DialogFragment.java",
+ "appcompat/FragmentTransaction.java.txt=>src/android/support/v4/app/FragmentTransaction.java",
+ "appcompat/FragmentManager.java.txt=>src/android/support/v4/app/FragmentManager.java"
+ ));
+ }
+
+ public void testSurfaceTexture() throws Exception {
+ assertEquals(
+ "src/test/pkg/SurfaceTextureTest.java:18: Warning: This SurfaceTexture should be freed up after use with #release() [Recycle]\n" +
+ " SurfaceTexture texture = new SurfaceTexture(1); // Warn: texture not released\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SurfaceTextureTest.java:25: Warning: This SurfaceTexture should be freed up after use with #release() [Recycle]\n" +
+ " SurfaceTexture texture = new SurfaceTexture(1); // Warn: texture not released\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SurfaceTextureTest.java:32: Warning: This Surface should be freed up after use with #release() [Recycle]\n" +
+ " Surface surface = new Surface(texture); // Warn: surface not released\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 3 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "src/test/pkg/SurfaceTextureTest.java.txt=>src/test/pkg/SurfaceTextureTest.java"
+ ));
+ }
+
+ public void testContentProviderClient() throws Exception {
+ assertEquals(
+ "src/test/pkg/ContentProviderClientTest.java:8: Warning: This ContentProviderClient should be freed up after use with #release() [Recycle]\n" +
+ " ContentProviderClient client = resolver.acquireContentProviderClient(\"test\"); // Warn\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "src/test/pkg/ContentProviderClientTest.java.txt=>src/test/pkg/ContentProviderClientTest.java"
+ ));
+ }
+
+ public void testDatabaseCursor() throws Exception {
+ assertEquals(
+ "src/test/pkg/CursorTest.java:14: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
+ " Cursor cursor = db.query(\"TABLE_TRIPS\",\n" +
+ " ~~~~~\n" +
+ "src/test/pkg/CursorTest.java:23: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
+ " Cursor cursor = db.query(\"TABLE_TRIPS\",\n" +
+ " ~~~~~\n" +
+ "src/test/pkg/CursorTest.java:74: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
+ " Cursor query = provider.query(uri, null, null, null, null);\n" +
+ " ~~~~~\n" +
+ "src/test/pkg/CursorTest.java:75: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
+ " Cursor query2 = resolver.query(uri, null, null, null, null);\n" +
+ " ~~~~~\n" +
+ "src/test/pkg/CursorTest.java:76: Warning: This Cursor should be freed up after use with #close() [Recycle]\n" +
+ " Cursor query3 = client.query(uri, null, null, null, null);\n" +
+ " ~~~~~\n" +
+ "0 errors, 5 warnings\n",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "project.properties19=>project.properties",
+ "src/test/pkg/CursorTest.java.txt=>src/test/pkg/CursorTest.java"
+ ));
+ }
+
+ public void testDatabaseCursorReassignment() throws Exception {
+ //noinspection ClassNameDiffersFromFileName,SpellCheckingInspection
+ assertEquals("No warnings.",
+ lintProject(java("src/test/pkg/CursorTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.Activity;\n"
+ + "import android.database.Cursor;\n"
+ + "import android.database.sqlite.SQLiteException;\n"
+ + "import android.net.Uri;\n"
+ + "\n"
+ + "public class CursorTest extends Activity {\n"
+ + " public void testSimple() {\n"
+ + " Cursor cursor;\n"
+ + " try {\n"
+ + " cursor = getContentResolver().query(Uri.parse(\"blahblah\"),\n"
+ + " new String[]{\"_id\", \"display_name\"}, null, null, null);\n"
+ + " } catch (SQLiteException e) {\n"
+ + " // Fallback\n"
+ + " cursor = getContentResolver().query(Uri.parse(\"blahblah\"),\n"
+ + " new String[]{\"_id2\", \"display_name\"}, null, null, null);\n"
+ + " }\n"
+ + " assert cursor != null;\n"
+ + " cursor.close();\n"
+ + " }\n"
+ + "}\n")));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ClickableViewAccessibilityDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CommentDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CommentDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CommentDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CommentDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CustomViewDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CustomViewDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CustomViewDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CustomViewDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java
new file mode 100644
index 0000000..314f146
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/CutPasteDetectorTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class CutPasteDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new CutPasteDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/PasteError.java:15: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
+ " View view2 = findViewById(R.id.textView1);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " src/test/pkg/PasteError.java:14: First usage here\n" +
+ "src/test/pkg/PasteError.java:71: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
+ " view2 = findViewById(R.id.textView1);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " src/test/pkg/PasteError.java:68: First usage here\n" +
+ "src/test/pkg/PasteError.java:78: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
+ " view2 = findViewById(R.id.textView1);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " src/test/pkg/PasteError.java:76: First usage here\n" +
+ "src/test/pkg/PasteError.java:86: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
+ " view2 = findViewById(R.id.textView1);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " src/test/pkg/PasteError.java:83: First usage here\n" +
+ "src/test/pkg/PasteError.java:95: Warning: The id R.id.textView1 has already been looked up in this method; possible cut & paste error? [CutPasteId]\n" +
+ " view2 = findViewById(R.id.textView1);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " src/test/pkg/PasteError.java:91: First usage here\n" +
+ "0 errors, 5 warnings\n",
+
+ lintProject("src/test/pkg/PasteError.java.txt=>" +
+ "src/test/pkg/PasteError.java"));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DateFormatDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DateFormatDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DateFormatDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DateFormatDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java
new file mode 100644
index 0000000..76ddf03
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DeprecationDetectorTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class DeprecationDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new DeprecationDetector();
+ }
+
+ public void testApi1() throws Exception {
+ assertEquals(
+ "res/layout/deprecation.xml:2: Warning: AbsoluteLayout is deprecated [Deprecated]\n" +
+ "<AbsoluteLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ "^\n" +
+ "res/layout/deprecation.xml:18: Warning: android:editable is deprecated: Use an <EditText> to make it editable [Deprecated]\n" +
+ " android:editable=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:26: Warning: android:editable is deprecated: <EditText> is already editable [Deprecated]\n" +
+ " <EditText android:editable=\"true\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:27: Warning: android:editable is deprecated: Use inputType instead [Deprecated]\n" +
+ " <EditText android:editable=\"false\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 4 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "res/layout/deprecation.xml"));
+ }
+
+ public void testApi4() throws Exception {
+ assertEquals(
+ "res/layout/deprecation.xml:2: Warning: AbsoluteLayout is deprecated [Deprecated]\n" +
+ "<AbsoluteLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ "^\n" +
+ "res/layout/deprecation.xml:16: Warning: android:autoText is deprecated: Use inputType instead [Deprecated]\n" +
+ " android:autoText=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:17: Warning: android:capitalize is deprecated: Use inputType instead [Deprecated]\n" +
+ " android:capitalize=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:18: Warning: android:editable is deprecated: Use an <EditText> to make it editable [Deprecated]\n" +
+ " android:editable=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:20: Warning: android:inputMethod is deprecated: Use inputType instead [Deprecated]\n" +
+ " android:inputMethod=\"@+id/foo\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:21: Warning: android:numeric is deprecated: Use inputType instead [Deprecated]\n" +
+ " android:numeric=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:22: Warning: android:password is deprecated: Use inputType instead [Deprecated]\n" +
+ " android:password=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:23: Warning: android:phoneNumber is deprecated: Use inputType instead [Deprecated]\n" +
+ " android:phoneNumber=\"true\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:26: Warning: android:editable is deprecated: <EditText> is already editable [Deprecated]\n" +
+ " <EditText android:editable=\"true\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/deprecation.xml:27: Warning: android:editable is deprecated: Use inputType instead [Deprecated]\n" +
+ " <EditText android:editable=\"false\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 10 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/layout/deprecation.xml"));
+ }
+
+ public void testUsesSdkM() throws Exception {
+ assertEquals(""
+ + "AndroidManifest.xml:8: Warning: uses-permission-sdk-m is deprecated: Use `uses-permission-sdk-23 instead [Deprecated]\n"
+ + " <uses-permission-sdk-m android:name=\"foo.bar.BAZ\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"4\" />\n"
+ + " <uses-permission android:name=\"foo.bar.BAZ\" />\n"
+ + " <uses-permission-sdk-23 android:name=\"foo.bar.BAZ\" />\n"
+ + " <uses-permission-sdk-m android:name=\"foo.bar.BAZ\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <activity\n"
+ + " android:name=\".BytecodeTestsActivity\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + "\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"
+ )
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DetectMissingPrefixTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DosLineEndingDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DosLineEndingDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DosLineEndingDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DosLineEndingDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
new file mode 100644
index 0000000..b51a436
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/DuplicateResourceDetectorTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.detector.api.TextFormat.RAW;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Severity;
+
+ at SuppressWarnings("javadoc")
+public class DuplicateResourceDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new DuplicateResourceDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "res/values/customattr2.xml:2: Error: ContentFrame has already been defined in this folder [DuplicateDefinition]\n" +
+ " <declare-styleable name=\"ContentFrame\">\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values/customattr.xml:2: Previously defined here\n" +
+ "res/values/strings2.xml:19: Error: wallpaper_instructions has already been defined in this folder [DuplicateDefinition]\n" +
+ " <string name=\"wallpaper_instructions\">Tap image to set landscape wallpaper</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values/strings.xml:29: Previously defined here\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/strings.xml",
+ "res/values-land/strings.xml=>res/values/strings2.xml",
+ "res/values-cs/strings.xml",
+ "res/values/customattr.xml",
+ "res/values/customattr.xml=>res/values/customattr2.xml"));
+ }
+
+ public void testDotAliases() throws Exception {
+ assertEquals(""
+ + "res/values/duplicate-strings2.xml:5: Error: app_name has already been defined in this folder (app_name is equivalent to app.name) [DuplicateDefinition]\n"
+ + " <string name=\"app.name\">App Name 1</string>\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + " res/values/duplicate-strings2.xml:4: Previously defined here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/duplicate-strings2.xml"));
+ }
+
+ public void testSameFile() throws Exception {
+ assertEquals(""
+ + "res/values/duplicate-strings.xml:6: Error: app_name has already been defined in this folder [DuplicateDefinition]\n"
+ + " <string name=\"app_name\">App Name 1</string>\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + " res/values/duplicate-strings.xml:4: Previously defined here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/duplicate-strings.xml"));
+ }
+
+ public void testStyleItems() throws Exception {
+ assertEquals(""
+ + "res/values/duplicate-items.xml:7: Error: android:textColor has already been defined in this <style> [DuplicateDefinition]\n"
+ + " <item name=\"android:textColor\">#ff0000</item>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/duplicate-items.xml:5: Previously defined here\n"
+ + "res/values/duplicate-items.xml:13: Error: contentId has already been defined in this <declare-styleable> [DuplicateDefinition]\n"
+ + " <attr name=\"contentId\" format=\"integer\" />\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + " res/values/duplicate-items.xml:11: Previously defined here\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/duplicate-items.xml"));
+ }
+
+ public void testOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/strings.xml",
+ "res/values-cs/strings.xml",
+ "res/values-de-rDE/strings.xml",
+ "res/values-es/strings.xml",
+ "res/values-es-rUS/strings.xml",
+ "res/values-land/strings.xml",
+ "res/values-cs/arrays.xml",
+ "res/values-es/donottranslate.xml",
+ "res/values-nl-rNL/strings.xml"));
+ }
+
+ public void testResourceAliases() throws Exception {
+ assertEquals(""
+ + "res/values/refs.xml:3: Error: Unexpected resource reference type; expected value of type @string/ [ReferenceType]\n"
+ + " <item name=\"invalid1\" type=\"string\">@layout/other</item>\n"
+ + " ^\n"
+ + "res/values/refs.xml:5: Error: Unexpected resource reference type; expected value of type @drawable/ [ReferenceType]\n"
+ + " @layout/other\n"
+ + " ^\n"
+ + "res/values/refs.xml:10: Error: Unexpected resource reference type; expected value of type @string/ [ReferenceType]\n"
+ + " <string name=\"invalid4\">@layout/indirect</string>\n"
+ + " ^\n"
+ + "res/values/refs.xml:15: Error: Unexpected resource reference type; expected value of type @color/ [ReferenceType]\n"
+ + " <item name=\"drawableAsColor\" type=\"color\">@drawable/my_drawable</item>\n"
+ + " ^\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject("res/values/refs.xml"));
+ }
+
+ public void testPublic() throws Exception {
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ xml("res/values/refs.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item type='dimen' name='largePadding'>20dp</item>\n"
+ + " <item type='dimen' name='smallPadding'>15dp</item>\n"
+ + " <public type='dimen' name='largePadding' />"
+ + " <public type='string' name='largePadding' />"
+ + " <public type='dimen' name='smallPadding' />"
+ + " <public type='dimen' name='smallPadding' />"
+ + "</resources>\n")));
+ }
+
+ public void testGetExpectedType() {
+ assertEquals("string", DuplicateResourceDetector.getExpectedType(
+ "Unexpected resource reference type; expected value of type `@string/`", RAW));
+ assertEquals("string", DuplicateResourceDetector.getExpectedType(
+ "Unexpected resource reference type; expected value of type @string/", TEXT));
+ }
+
+ public void testMipmapDrawable() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=109892
+ assertEquals("No warnings.",
+
+ lintProject("res/values/refs2.xml"));
+ }
+
+ @Override
+ protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+ @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+ if (issue == DuplicateResourceDetector.TYPE_MISMATCH) {
+ assertNotNull(message, DuplicateResourceDetector.getExpectedType(message, TEXT));
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ExtraTextDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ExtraTextDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ExtraTextDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ExtraTextDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FieldGetterDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FieldGetterDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FieldGetterDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FieldGetterDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
new file mode 100644
index 0000000..42e8e7a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FragmentDetectorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "MethodMayBeStatic"})
+public class FragmentDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new FragmentDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/FragmentTest.java:10: Error: This fragment class should be public (test.pkg.FragmentTest.Fragment1) [ValidFragment]\n"
+ + " private static class Fragment1 extends Fragment {\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:15: Error: This fragment inner class should be static (test.pkg.FragmentTest.Fragment2) [ValidFragment]\n"
+ + " public class Fragment2 extends Fragment {\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:21: Error: The default constructor must be public [ValidFragment]\n"
+ + " private Fragment3() {\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:26: Error: This fragment should provide a default constructor (a public constructor with no arguments) (test.pkg.FragmentTest.Fragment4) [ValidFragment]\n"
+ + " public static class Fragment4 extends Fragment {\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:27: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n"
+ + " private Fragment4(int dummy) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/FragmentTest.java:36: Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]\n"
+ + " public Fragment5(int dummy) {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "6 errors, 0 warnings\n",
+
+ lintProject(
+ "bytecode/FragmentTest$Fragment1.class.data=>bin/classes/test/pkg/FragmentTest$Fragment1.class",
+ "bytecode/FragmentTest$Fragment2.class.data=>bin/classes/test/pkg/FragmentTest$Fragment2.class",
+ "bytecode/FragmentTest$Fragment3.class.data=>bin/classes/test/pkg/FragmentTest$Fragment3.class",
+ "bytecode/FragmentTest$Fragment4.class.data=>bin/classes/test/pkg/FragmentTest$Fragment4.class",
+ "bytecode/FragmentTest$Fragment5.class.data=>bin/classes/test/pkg/FragmentTest$Fragment5.class",
+ "bytecode/FragmentTest$Fragment6.class.data=>bin/classes/test/pkg/FragmentTest$Fragment6.class",
+ "bytecode/FragmentTest$NotAFragment.class.data=>bin/classes/test/pkg/FragmentTest$NotAFragment.class",
+ "bytecode/FragmentTest.java.txt=>src/test/pkg/FragmentTest.java"));
+ }
+
+ public void testAnonymousInnerClass() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/Parent.java:7: Error: Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static [ValidFragment]\n"
+ + " return new Fragment() {\n"
+ + " ~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(java("src/test/pkg/Parent.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.Fragment;\n"
+ + "\n"
+ + "public class Parent {\n"
+ + " public Fragment method() {\n"
+ + " return new Fragment() {\n"
+ + " };\n"
+ + " }\n"
+ + "}\n")));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FullBackupContentDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FullBackupContentDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FullBackupContentDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/FullBackupContentDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java
new file mode 100644
index 0000000..8b3ffad
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GetSignaturesDetectorTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class GetSignaturesDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new GetSignaturesDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void testLintWarningOnSingleGetSignaturesFlag() throws Exception {
+ assertEquals(
+ "src/test/pkg/GetSignaturesSingleFlagTest.java:9: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
+ + " .getPackageInfo(\"some.pkg\", PackageManager.GET_SIGNATURES);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/GetSignaturesSingleFlagTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesSingleFlagTest.java"
+ ));
+ }
+
+ public void testLintWarningOnGetSignaturesFlagInBitwiseOrExpression() throws Exception {
+ assertEquals(
+ "src/test/pkg/GetSignaturesBitwiseOrTest.java:11: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
+ + " .getPackageInfo(\"some.pkg\", GET_GIDS | GET_SIGNATURES | GET_PROVIDERS);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/GetSignaturesBitwiseOrTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesBitwiseOrTest.java"
+ ));
+ }
+
+ public void testLintWarningOnGetSignaturesFlagInBitwiseXorExpression() throws Exception {
+ assertEquals(
+ "src/test/pkg/GetSignaturesBitwiseXorTest.java:8: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
+ + " getPackageManager().getPackageInfo(\"some.pkg\", PackageManager.GET_SIGNATURES ^ 0x0);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/GetSignaturesBitwiseXorTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesBitwiseXorTest.java"
+ ));
+ }
+
+ public void testLintWarningOnGetSignaturesFlagInBitwiseAndExpression() throws Exception {
+ assertEquals(
+ "src/test/pkg/GetSignaturesBitwiseAndTest.java:9: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
+ + " Integer.MAX_VALUE & PackageManager.GET_SIGNATURES);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/GetSignaturesBitwiseAndTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesBitwiseAndTest.java"
+ ));
+ }
+
+ public void testLintWarningOnFlagsInStaticField() throws Exception {
+ assertEquals(
+ "src/test/pkg/GetSignaturesStaticFieldTest.java:9: Information: Reading app signatures from getPackageInfo: The app signatures could be exploited if not validated properly; see issue explanation for details. [PackageManagerGetSignatures]\n"
+ + " getPackageManager().getPackageInfo(\"some.pkg\", FLAGS);\n"
+ + " ~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "src/test/pkg/GetSignaturesStaticFieldTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesStaticFieldTest.java"
+ ));
+ }
+
+ public void testNoLintWarningOnFlagsInLocalVariable() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "src/test/pkg/GetSignaturesLocalVariableTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesLocalVariableTest.java"
+ ));
+ }
+
+ public void testNoLintWarningOnGetSignaturesWithNoFlag() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "src/test/pkg/GetSignaturesNoFlagTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesNoFlagTest.java"
+ ));
+ }
+
+ public void testNoLintWarningOnGetPackageInfoOnNonPackageManagerClass() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "src/test/pkg/GetSignaturesNotPackageManagerTest.java.txt" +
+ "=>src/test/pkg/GetSignaturesNotPackageManagerTest.java"
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java
new file mode 100644
index 0000000..4343b3d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GradleDetectorTest.java
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.GRADLE_PLUGIN_MINIMUM_VERSION;
+import static com.android.SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION;
+import static com.android.tools.lint.checks.GradleDetector.ACCIDENTAL_OCTAL;
+import static com.android.tools.lint.checks.GradleDetector.COMPATIBILITY;
+import static com.android.tools.lint.checks.GradleDetector.DEPENDENCY;
+import static com.android.tools.lint.checks.GradleDetector.DEPRECATED;
+import static com.android.tools.lint.checks.GradleDetector.GRADLE_GETTER;
+import static com.android.tools.lint.checks.GradleDetector.GRADLE_PLUGIN_COMPATIBILITY;
+import static com.android.tools.lint.checks.GradleDetector.NOT_INTERPOLATED;
+import static com.android.tools.lint.checks.GradleDetector.PATH;
+import static com.android.tools.lint.checks.GradleDetector.PLUS;
+import static com.android.tools.lint.checks.GradleDetector.REMOTE_VERSION;
+import static com.android.tools.lint.checks.GradleDetector.STRING_INTEGER;
+import static com.android.tools.lint.checks.GradleDetector.getNamedDependency;
+import static com.android.tools.lint.checks.GradleDetector.getNewValue;
+import static com.android.tools.lint.checks.GradleDetector.getOldValue;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.DefaultPosition;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.Pair;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.GroovyCodeVisitor;
+import org.codehaus.groovy.ast.builder.AstBuilder;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * NOTE: Many of these tests are duplicated in the Android Studio plugin to
+ * test the custom GradleDetector subclass, IntellijGradleDetector, which
+ * customizes some behavior to be based on top of PSI rather than the Groovy parser.
+ */
+public class GradleDetectorTest extends AbstractCheckTest {
+
+ private File mSdkDir;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ if (mSdkDir != null) {
+ deleteFile(mSdkDir);
+ mSdkDir = null;
+ }
+ }
+
+ /** Creates a mock SDK installation structure, containing a fixed set of dependencies */
+ private File getMockSupportLibraryInstallation() {
+ if (mSdkDir == null) {
+ // Make fake SDK "installation" such that we can predict the set
+ // of Maven repositories discovered by this test
+ mSdkDir = Files.createTempDir();
+
+ String[] paths = new String[]{
+ // Android repository
+ "extras/android/m2repository/com/android/support/appcompat-v7/18.0.0/appcompat-v7-18.0.0.aar",
+ "extras/android/m2repository/com/android/support/appcompat-v7/19.0.0/appcompat-v7-19.0.0.aar",
+ "extras/android/m2repository/com/android/support/appcompat-v7/19.0.1/appcompat-v7-19.0.1.aar",
+ "extras/android/m2repository/com/android/support/appcompat-v7/19.1.0/appcompat-v7-19.1.0.aar",
+ "extras/android/m2repository/com/android/support/appcompat-v7/20.0.0/appcompat-v7-20.0.0.aar",
+ "extras/android/m2repository/com/android/support/appcompat-v7/21.0.0/appcompat-v7-21.0.0.aar",
+ "extras/android/m2repository/com/android/support/appcompat-v7/21.0.2/appcompat-v7-21.0.2.aar",
+ "extras/android/m2repository/com/android/support/cardview-v7/21.0.0/cardview-v7-21.0.0.aar",
+ "extras/android/m2repository/com/android/support/cardview-v7/21.0.2/cardview-v7-21.0.2.aar",
+ "extras/android/m2repository/com/android/support/support-v13/20.0.0/support-v13-20.0.0.aar",
+ "extras/android/m2repository/com/android/support/support-v13/21.0.0/support-v13-21.0.0.aar",
+ "extras/android/m2repository/com/android/support/support-v13/21.0.2/support-v13-21.0.2.aar",
+ "extras/android/m2repository/com/android/support/support-v4/20.0.0/support-v4-20.0.0.aar",
+ "extras/android/m2repository/com/android/support/support-v4/21.0.0/support-v4-21.0.0.aar",
+ "extras/android/m2repository/com/android/support/support-v4/21.0.2/support-v4-21.0.2.aar",
+
+ // Google repository
+ "extras/google/m2repository/com/google/android/gms/play-services/3.1.36/play-services-3.1.36.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/3.1.59/play-services-3.1.59.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/3.2.25/play-services-3.2.25.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/3.2.65/play-services-3.2.65.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/4.0.30/play-services-4.0.30.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/4.1.32/play-services-4.1.32.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/4.2.42/play-services-4.2.42.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/4.3.23/play-services-4.3.23.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/4.4.52/play-services-4.4.52.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/5.0.89/play-services-5.0.89.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/6.1.11/play-services-6.1.11.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services/6.1.71/play-services-6.1.71.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services-wearable/5.0.77/play-services-wearable-5.0.77.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services-wearable/6.1.11/play-services-wearable-6.1.11.aar",
+ "extras/google/m2repository/com/google/android/gms/play-services-wearable/6.1.71/play-services-wearable-6.1.71.aar",
+ "extras/google/m2repository/com/google/android/support/wearable/1.0.0/wearable-1.0.0.aar"
+ };
+
+ for (String path : paths) {
+ File file = new File(mSdkDir, path.replace('/', File.separatorChar));
+ File parent = file.getParentFile();
+ if (!parent.exists()) {
+ boolean ok = parent.mkdirs();
+ assertTrue(ok);
+ }
+ try {
+ boolean created = file.createNewFile();
+ assertTrue(created);
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ }
+ }
+
+ return mSdkDir;
+ }
+
+ public void testGetOldValue() {
+ assertEquals("11.0.2", getOldValue(DEPENDENCY,
+ "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0",
+ TEXT));
+ assertNull(getOldValue(DEPENDENCY, "Bogus", TEXT));
+ assertNull(getOldValue(DEPENDENCY, "bogus", TEXT));
+ // targetSdkVersion 20, compileSdkVersion 19: Should replace targetVersion 20 with 19
+ assertEquals("20", getOldValue(DEPENDENCY,
+ "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)",
+ TEXT));
+ assertEquals("'19'", getOldValue(STRING_INTEGER,
+ "Use an integer rather than a string here (replace '19' with just 19)", TEXT));
+ assertEquals("android", getOldValue(DEPRECATED,
+ "'android' is deprecated; use 'com.android.application' instead", TEXT));
+ assertEquals("android-library", getOldValue(DEPRECATED,
+ "'android-library' is deprecated; use 'com.android.library' instead", TEXT));
+ assertEquals("packageName", getOldValue(DEPRECATED,
+ "Deprecated: Replace 'packageName' with 'applicationId'", TEXT));
+ assertEquals("packageNameSuffix", getOldValue(DEPRECATED,
+ "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'", TEXT));
+ assertEquals("18.0.0", getOldValue(DEPENDENCY,
+ "Old buildToolsVersion 18.0.0; recommended version is 19.1 or later", TEXT));
+ }
+
+ public void testGetNewValue() {
+ assertEquals("17.0.0", getNewValue(DEPENDENCY,
+ "A newer version of com.google.guava:guava than 11.0.2 is available: 17.0.0",
+ TEXT));
+ assertNull(getNewValue(DEPENDENCY,
+ "A newer version of com.google.guava:guava than 11.0.2 is available", TEXT));
+ assertNull(getNewValue(DEPENDENCY, "bogus", TEXT));
+ // targetSdkVersion 20, compileSdkVersion 19: Should replace targetVersion 20 with 19
+ assertEquals("19", getNewValue(DEPENDENCY,
+ "The targetSdkVersion (20) should not be higher than the compileSdkVersion (19)",
+ TEXT));
+ assertEquals("19", getNewValue(STRING_INTEGER,
+ "Use an integer rather than a string here (replace '19' with just 19)", TEXT));
+ assertEquals("com.android.application", getNewValue(DEPRECATED,
+ "'android' is deprecated; use 'com.android.application' instead", TEXT));
+ assertEquals("com.android.library", getNewValue(DEPRECATED,
+ "'android-library' is deprecated; use 'com.android.library' instead", TEXT));
+ assertEquals("applicationId", getNewValue(DEPRECATED,
+ "Deprecated: Replace 'packageName' with 'applicationId'", TEXT));
+ assertEquals("applicationIdSuffix", getNewValue(DEPRECATED,
+ "Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix'", TEXT));
+ assertEquals("19.1", getNewValue(DEPENDENCY,
+ "Old buildToolsVersion 18.0.0; recommended version is 19.1 or later", TEXT));
+ }
+
+ public void test() throws Exception {
+ mEnabled = Sets.newHashSet(COMPATIBILITY, DEPRECATED, DEPENDENCY, PLUS);
+ assertEquals(""
+ + "build.gradle:25: Error: This support library should not use a different version (13) than the compileSdkVersion (19) [GradleCompatible]\n"
+ + " compile 'com.android.support:appcompat-v7:13.0.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:1: Warning: 'android' is deprecated; use 'com.android.application' instead [GradleDeprecated]\n"
+ + "apply plugin: 'android'\n"
+ + "~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:5: Warning: Old buildToolsVersion 19.0.0; recommended version is 19.1 or later [GradleDependency]\n"
+ + " buildToolsVersion \"19.0.0\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:24: Warning: A newer version of com.google.guava:guava than 11.0.2 is available: 18.0 [GradleDependency]\n"
+ + " freeCompile 'com.google.guava:guava:11.0.2'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:25: Warning: A newer version of com.android.support:appcompat-v7 than 13.0.0 is available: 21.0.2 [GradleDependency]\n"
+ + " compile 'com.android.support:appcompat-v7:13.0.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:23: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:appcompat-v7:+) [GradleDynamicVersion]\n"
+ + " compile 'com.android.support:appcompat-v7:+'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 5 warnings\n",
+
+ lintProject("gradle/Dependencies.gradle=>build.gradle"));
+ }
+
+ public void testCompatibility() throws Exception {
+ mEnabled = Collections.singleton(COMPATIBILITY);
+ assertEquals(""
+ + "build.gradle:16: Error: This support library should not use a lower version (18) than the targetSdkVersion (19) [GradleCompatible]\n"
+ + " compile 'com.android.support:support-v4:18.0.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("gradle/Compatibility.gradle=>build.gradle"));
+ }
+
+ public void testIncompatiblePlugin() throws Exception {
+ mEnabled = Collections.singleton(GRADLE_PLUGIN_COMPATIBILITY);
+ assertEquals(""
+ + "build.gradle:6: Error: You must use a newer version of the Android Gradle plugin. The minimum supported version is " + GRADLE_PLUGIN_MINIMUM_VERSION + " and the recommended version is " + GRADLE_PLUGIN_RECOMMENDED_VERSION + " [GradlePluginVersion]\n"
+ + " classpath 'com.android.tools.build:gradle:0.1.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("gradle/IncompatiblePlugin.gradle=>build.gradle"));
+ }
+
+ public void testSetter() throws Exception {
+ mEnabled = Collections.singleton(GRADLE_GETTER);
+ assertEquals(""
+ + "build.gradle:18: Error: Bad method name: pick a unique method name which does not conflict with the implicit getters for the defaultConfig properties. For example, try using the prefix compute- instead of get-. [GradleGetter]\n"
+ + " versionCode getVersionCode\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:19: Error: Bad method name: pick a unique method name which does not conflict with the implicit getters for the defaultConfig properties. For example, try using the prefix compute- instead of get-. [GradleGetter]\n"
+ + " versionName getVersionName\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject("gradle/Setter.gradle=>build.gradle"));
+ }
+
+ public void testDependencies() throws Exception {
+ mEnabled = Collections.singleton(DEPENDENCY);
+ assertEquals(""
+ + "build.gradle:5: Warning: Old buildToolsVersion 19.0.0; recommended version is 19.1 or later [GradleDependency]\n"
+ + " buildToolsVersion \"19.0.0\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:24: Warning: A newer version of com.google.guava:guava than 11.0.2 is available: 18.0 [GradleDependency]\n"
+ + " freeCompile 'com.google.guava:guava:11.0.2'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:25: Warning: A newer version of com.android.support:appcompat-v7 than 13.0.0 is available: 21.0.2 [GradleDependency]\n"
+ + " compile 'com.android.support:appcompat-v7:13.0.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 3 warnings\n",
+
+ lintProject("gradle/Dependencies.gradle=>build.gradle"));
+ }
+
+ public void testLongHandDependencies() throws Exception {
+ mEnabled = Collections.singleton(DEPENDENCY);
+ assertEquals(""
+ + "build.gradle:9: Warning: A newer version of com.android.support:support-v4 than 19.0 is available: 21.0.2 [GradleDependency]\n"
+ + " compile group: 'com.android.support', name: 'support-v4', version: '19.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("gradle/DependenciesProps.gradle=>build.gradle"));
+ }
+
+ public void testDependenciesMinSdkVersion() throws Exception {
+ mEnabled = Collections.singleton(DEPENDENCY);
+ assertEquals(""
+ + "build.gradle:13: Warning: Using the appcompat library when minSdkVersion >= 14 and compileSdkVersion < 21 is not necessary [GradleDependency]\n"
+ + " compile 'com.android.support:appcompat-v7:+'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("gradle/Dependencies14.gradle=>build.gradle"));
+ }
+
+ public void testDependenciesMinSdkVersionLollipop() throws Exception {
+ mEnabled = Collections.singleton(DEPENDENCY);
+ assertEquals("No warnings.",
+ lintProject("gradle/Dependencies14_21.gradle=>build.gradle"));
+ }
+
+ public void testDependenciesNoMicroVersion() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=77594
+ mEnabled = Collections.singleton(DEPENDENCY);
+ assertEquals(""
+ + "build.gradle:13: Warning: A newer version of com.google.code.gson:gson than 2.2 is available: 2.4 [GradleDependency]\n"
+ + " compile 'com.google.code.gson:gson:2.2'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("gradle/DependenciesGson.gradle=>build.gradle"));
+ }
+
+ public void testPaths() throws Exception {
+ mEnabled = Collections.singleton(PATH);
+ assertEquals(""
+ + "build.gradle:4: Warning: Do not use Windows file separators in .gradle files; use / instead [GradlePath]\n"
+ + " compile files('my\\\\libs\\\\http.jar')\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:5: Warning: Avoid using absolute paths in .gradle files [GradlePath]\n"
+ + " compile files('/libs/android-support-v4.jar')\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject("gradle/Paths.gradle=>build.gradle"));
+ }
+
+ public void testIdSuffix() throws Exception {
+ mEnabled = Collections.singleton(PATH);
+ assertEquals(""
+ + "build.gradle:6: Warning: Package suffix should probably start with a \".\" [GradlePath]\n"
+ + " applicationIdSuffix \"debug\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("gradle/IdSuffix.gradle=>build.gradle"));
+ }
+
+ public void testPackage() throws Exception {
+ mEnabled = Collections.singleton(DEPRECATED);
+ assertEquals(""
+ + "build.gradle:5: Warning: Deprecated: Replace 'packageName' with 'applicationId' [GradleDeprecated]\n"
+ + " packageName 'my.pkg'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:9: Warning: Deprecated: Replace 'packageNameSuffix' with 'applicationIdSuffix' [GradleDeprecated]\n"
+ + " packageNameSuffix \".debug\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject("gradle/Package.gradle=>build.gradle"));
+ }
+
+ public void testPlus() throws Exception {
+ mEnabled = Collections.singleton(PLUS);
+ assertEquals(""
+ + "build.gradle:9: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:appcompat-v7:+) [GradleDynamicVersion]\n"
+ + " compile 'com.android.support:appcompat-v7:+'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:10: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:support-v4:21.0.+) [GradleDynamicVersion]\n"
+ + " compile group: 'com.android.support', name: 'support-v4', version: '21.0.+'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:11: Warning: Avoid using + in version numbers; can lead to unpredictable and unrepeatable builds (com.android.support:appcompat-v7:+ at aar) [GradleDynamicVersion]\n"
+ + " compile 'com.android.support:appcompat-v7:+ at aar'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 3 warnings\n",
+
+
+ lintProject("gradle/Plus.gradle=>build.gradle"));
+ }
+
+ public void testStringInt() throws Exception {
+ mEnabled = Collections.singleton(STRING_INTEGER);
+ assertEquals(""
+ + "build.gradle:4: Error: Use an integer rather than a string here (replace '19' with just 19) [StringShouldBeInt]\n"
+ + " compileSdkVersion '19'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:7: Error: Use an integer rather than a string here (replace '8' with just 8) [StringShouldBeInt]\n"
+ + " minSdkVersion '8'\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:8: Error: Use an integer rather than a string here (replace '16' with just 16) [StringShouldBeInt]\n"
+ + " targetSdkVersion '16'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+
+ lintProject("gradle/StringInt.gradle=>build.gradle"));
+ }
+
+ public void testSuppressLine2() throws Exception {
+ mEnabled = null;
+ assertEquals("No warnings.",
+
+ lintProject("gradle/SuppressLine2.gradle=>build.gradle"));
+ }
+
+ public void testDeprecatedPluginId() throws Exception {
+ mEnabled = Sets.newHashSet(DEPRECATED);
+ assertEquals(""
+ + "build.gradle:4: Warning: 'android' is deprecated; use 'com.android.application' instead [GradleDeprecated]\n"
+ + "apply plugin: 'android'\n"
+ + "~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:5: Warning: 'android-library' is deprecated; use 'com.android.library' instead [GradleDeprecated]\n"
+ + "apply plugin: 'android-library'\n"
+ + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject("gradle/DeprecatedPluginId.gradle=>build.gradle"));
+ }
+
+ public void testIgnoresGStringsInDependencies() throws Exception {
+ mEnabled = null;
+ assertEquals("No warnings.",
+
+ lintProject("gradle/IgnoresGStringsInDependencies.gradle=>build.gradle"));
+ }
+
+ public void testAccidentalOctal() throws Exception {
+ mEnabled = Collections.singleton(ACCIDENTAL_OCTAL);
+ assertEquals(""
+ + "build.gradle:13: Error: The leading 0 turns this number into octal which is probably not what was intended (interpreted as 8) [AccidentalOctal]\n"
+ + " versionCode 010\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "build.gradle:16: Error: The leading 0 turns this number into octal which is probably not what was intended (and it is not a valid octal number) [AccidentalOctal]\n"
+ + " versionCode 01 // line suffix comments are not handled correctly\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject("gradle/AccidentalOctal.gradle=>build.gradle"));
+ }
+
+ public void testBadPlayServicesVersion() throws Exception {
+ mEnabled = Collections.singleton(COMPATIBILITY);
+ assertEquals(""
+ + "build.gradle:5: Error: Version 5.2.08 should not be used; the app can not be published with this version. Use version 6.1.71 instead. [GradleCompatible]\n"
+ + " compile 'com.google.android.gms:play-services:5.2.08'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("gradle/PlayServices.gradle=>build.gradle"));
+ }
+
+ public void testRemoteVersions() throws Exception {
+ mEnabled = Collections.singleton(REMOTE_VERSION);
+ try {
+ HashMap<String, String> data = Maps.newHashMap();
+ GradleDetector.sMockData = data;
+ data.put("http://search.maven.org/solrsearch/select?q=g:%22joda-time%22+AND+a:%22joda-time%22&core=gav&rows=1&wt=json",
+ "{\"responseHeader\":{\"status\":0,\"QTime\":1,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"joda-time\\\" AND a:\\\"joda-time\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"rows\":\"1\",\"version\":\"2.2\"}},\"response\":{\"numFound\":17,\"start\":0,\"docs\":[{\"id\":\"joda-time:joda-time:2.3\",\"g\":\"joda-time\",\"a\":\"joda-time\",\"v\":\"2.3\",\"p\":\"jar\",\"timestamp\":13 [...]
+ data.put("http://search.maven.org/solrsearch/select?q=g:%22com.squareup.dagger%22+AND+a:%22dagger%22&core=gav&rows=1&wt=json",
+ "{\"responseHeader\":{\"status\":0,\"QTime\":1,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"com.squareup.dagger\\\" AND a:\\\"dagger\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"rows\":\"1\",\"version\":\"2.2\"}},\"response\":{\"numFound\":5,\"start\":0,\"docs\":[{\"id\":\"com.squareup.dagger:dagger:1.2.1\",\"g\":\"com.squareup.dagger\",\"a\":\"dagger\",\"v\":\"1.2.1\",\"p\": [...]
+
+ assertEquals(""
+ + "build.gradle:9: Warning: A newer version of joda-time:joda-time than 2.1 is available: 2.3 [NewerVersionAvailable]\n"
+ + " compile 'joda-time:joda-time:2.1'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:10: Warning: A newer version of com.squareup.dagger:dagger than 1.2.0 is available: 1.2.1 [NewerVersionAvailable]\n"
+ + " compile 'com.squareup.dagger:dagger:1.2.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject("gradle/RemoteVersions.gradle=>build.gradle"));
+ } finally {
+ GradleDetector.sMockData = null;
+ }
+ }
+
+ public void testRemoteVersionsWithPreviews() throws Exception {
+ // If the most recent version is a rc version, query for all versions
+ mEnabled = Collections.singleton(REMOTE_VERSION);
+ try {
+ HashMap<String, String> data = Maps.newHashMap();
+ GradleDetector.sMockData = data;
+ data.put("http://search.maven.org/solrsearch/select?q=g:%22com.google.guava%22+AND+a:%22guava%22&core=gav&rows=1&wt=json",
+ "{\"responseHeader\":{\"status\":0,\"QTime\":0,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"com.google.guava\\\" AND a:\\\"guava\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"rows\":\"1\",\"version\":\"2.2\"}},\"response\":{\"numFound\":38,\"start\":0,\"docs\":[{\"id\":\"com.google.guava:guava:18.0-rc1\",\"g\":\"com.google.guava\",\"a\":\"guava\",\"v\":\"18.0-rc1\",\"p\":\"bun [...]
+ data.put("http://search.maven.org/solrsearch/select?q=g:%22com.google.guava%22+AND+a:%22guava%22&core=gav&wt=json",
+ "{\"responseHeader\":{\"status\":0,\"QTime\":1,\"params\":{\"fl\":\"id,g,a,v,p,ec,timestamp,tags\",\"sort\":\"score desc,timestamp desc,g asc,a asc,v desc\",\"indent\":\"off\",\"q\":\"g:\\\"com.google.guava\\\" AND a:\\\"guava\\\"\",\"core\":\"gav\",\"wt\":\"json\",\"version\":\"2.2\"}},\"response\":{\"numFound\":38,\"start\":0,\"docs\":[{\"id\":\"com.google.guava:guava:18.0-rc1\",\"g\":\"com.google.guava\",\"a\":\"guava\",\"v\":\"18.0-rc1\",\"p\":\"bundle\",\"timesta [...]
+
+ assertEquals(""
+ + "build.gradle:9: Warning: A newer version of com.google.guava:guava than 11.0.2 is available: 17.0 [NewerVersionAvailable]\n"
+ + " compile 'com.google.guava:guava:11.0.2'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "build.gradle:10: Warning: A newer version of com.google.guava:guava than 16.0-rc1 is available: 18.0.0-rc1 [NewerVersionAvailable]\n"
+ + " compile 'com.google.guava:guava:16.0-rc1'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject("gradle/RemoteVersions2.gradle=>build.gradle"));
+ } finally {
+ GradleDetector.sMockData = null;
+ }
+ }
+
+ public void testPreviewVersions() throws Exception {
+ mEnabled = Collections.singleton(DEPENDENCY);
+ // This test only works when SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION contains
+ // a preview string:
+ if (!GRADLE_PLUGIN_RECOMMENDED_VERSION.startsWith("1.0.0-rc")) {
+ return;
+ }
+ assertEquals(""
+ + "build.gradle:6: Warning: A newer version of com.android.tools.build:gradle than 1.0.0-rc0 is available: " + GRADLE_PLUGIN_RECOMMENDED_VERSION + " [GradleDependency]\n"
+ + " classpath 'com.android.tools.build:gradle:1.0.0-rc0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("gradle/PreviewDependencies.gradle=>build.gradle"));
+ }
+
+ public void testDependenciesInVariables() throws Exception {
+ mEnabled = Collections.singleton(DEPENDENCY);
+ assertEquals(""
+ + "build.gradle:10: Warning: A newer version of com.google.android.gms:play-services-wearable than 5.0.77 is available: 6.1.71 [GradleDependency]\n"
+ + " compile \"com.google.android.gms:play-services-wearable:${GPS_VERSION}\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("gradle/DependenciesVariable.gradle=>build.gradle"));
+ }
+
+ public void testPlayServiceConsistency() throws Exception {
+ // Requires custom model mocks
+ mEnabled = Collections.singleton(COMPATIBILITY);
+ assertEquals(""
+ + "build.gradle:4: Error: All com.google.android.gms libraries must use the "
+ + "exact same version specification (mixing versions can lead to runtime "
+ + "crashes). Found versions 7.5.0, 7.3.0. Examples include "
+ + "com.google.android.gms:play-services-wearable:7.5.0 and "
+ + "com.google.android.gms:play-services-location:7.3.0 [GradleCompatible]\n"
+ + " compile 'com.google.android.gms:play-services-wearable:7.5.0'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProjectIncrementally("build.gradle",
+ "gradle/PlayServices2.gradle=>build.gradle"));
+ }
+
+ public void testPlayServiceConsistencyNonIncremental() throws Exception {
+ // Requires custom model mocks
+ mEnabled = Collections.singleton(COMPATIBILITY);
+ assertEquals(""
+ + "build.gradle: Error: All com.google.android.gms libraries must use "
+ + "the exact same version specification (mixing versions can lead to "
+ + "runtime crashes). Found versions 7.5.0, 7.3.0. Examples include "
+ + "com.google.android.gms:play-services-wearable:7.5.0 and "
+ + "com.google.android.gms:play-services-location:7.3.0 [GradleCompatible]\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject("gradle/PlayServices2.gradle=>build.gradle"));
+ }
+
+ public void testWrongQuotes() throws Exception {
+ mEnabled = Collections.singleton(NOT_INTERPOLATED);
+ assertEquals(""
+ + "build.gradle:5: Error: It looks like you are trying to substitute a version variable, but using single quotes ('). For Groovy string interpolation you must use double quotes (\"). [NotInterpolated]\n"
+ + " compile 'com.android.support:design:${supportLibVersion}'\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(source("build.gradle", ""
+ + "ext {\n"
+ + " supportLibVersion = \"23.1.1\"\n"
+ + "}\n"
+ + "dependencies {\n"
+ + " compile 'com.android.support:design:${supportLibVersion}'\n"
+ + " compile \"com.android.support:appcompat-v7:${supportLibVersion}\"\n"
+ + "}\n")));
+ }
+
+ @Override
+ protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+ @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+ if (issue == DEPENDENCY && message.startsWith("Using the appcompat library when ")) {
+ // No data embedded in this specific message
+ return;
+ }
+
+ // Issues we're supporting getOldFrom
+ if (issue == DEPENDENCY
+ || issue == STRING_INTEGER
+ || issue == DEPRECATED
+ || issue == PLUS) {
+ assertNotNull("Could not extract message tokens from " + message,
+ GradleDetector.getOldValue(issue, message, TEXT));
+ }
+
+ if (issue == DEPENDENCY
+ || issue == STRING_INTEGER
+ || issue == DEPRECATED) {
+ assertNotNull("Could not extract message tokens from " + message,
+ GradleDetector.getNewValue(issue, message, TEXT));
+ }
+
+ if (issue == COMPATIBILITY) {
+ if (message.startsWith("Version ")) {
+ assertNotNull("Could not extract message tokens from " + message,
+ GradleDetector.getNewValue(issue, message, TEXT));
+ }
+ }
+ }
+
+ public void testGetNamedDependency() {
+ assertEquals("com.android.support:support-v4:21.0.+", getNamedDependency(
+ "group: 'com.android.support', name: 'support-v4', version: '21.0.+'"
+ ));
+ assertEquals("com.android.support:support-v4:21.0.+", getNamedDependency(
+ "name:'support-v4', group: \"com.android.support\", version: '21.0.+'"
+ ));
+ assertEquals("junit:junit:4.+", getNamedDependency(
+ "group: 'junit', name: 'junit', version: '4.+'"
+ ));
+ assertEquals("com.android.support:support-v4:19.0.+", getNamedDependency(
+ "group: 'com.android.support', name: 'support-v4', version: '19.0.+'"
+ ));
+ assertEquals("com.google.guava:guava:11.0.1", getNamedDependency(
+ "group: 'com.google.guava', name: 'guava', version: '11.0.1', transitive: false"
+ ));
+ assertEquals("com.google.api-client:google-api-client:1.6.0-beta", getNamedDependency(
+ "group: 'com.google.api-client', name: 'google-api-client', version: '1.6.0-beta', transitive: false"
+ ));
+ assertEquals("org.robolectric:robolectric:2.3-SNAPSHOT", getNamedDependency(
+ "group: 'org.robolectric', name: 'robolectric', version: '2.3-SNAPSHOT'"
+ ));
+ }
+
+ // -------------------------------------------------------------------------------------------
+ // Test infrastructure below here
+ // -------------------------------------------------------------------------------------------
+
+ static final Implementation IMPLEMENTATION = new Implementation(
+ GroovyGradleDetector.class,
+ Scope.GRADLE_SCOPE);
+ static {
+ for (Issue issue : new BuiltinIssueRegistry().getIssues()) {
+ if (issue.getImplementation().getDetectorClass() == GradleDetector.class) {
+ issue.setImplementation(IMPLEMENTATION);
+ }
+ }
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new GroovyGradleDetector();
+ }
+
+ private Set<Issue> mEnabled;
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && (mEnabled == null || mEnabled.contains(issue));
+ }
+ };
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @Nullable
+ @Override
+ public File getSdkHome() {
+ return getMockSupportLibraryInstallation();
+ }
+
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ if ("testDependenciesInVariables".equals(getName())) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ /*
+ Simulate variant which has an AndroidLibrary with
+ resolved coordinates
+
+ com.google.android.gms:play-services-wearable:5.0.77"
+ */
+ MavenCoordinates coordinates = mock(MavenCoordinates.class);
+ when(coordinates.getGroupId()).thenReturn("com.google.android.gms");
+ when(coordinates.getArtifactId()).thenReturn("play-services-wearable");
+ when(coordinates.getVersion()).thenReturn("5.0.77");
+
+ AndroidLibrary library = mock(AndroidLibrary.class);
+ when(library.getResolvedCoordinates()).thenReturn(coordinates);
+ List<AndroidLibrary> libraries = Collections.singletonList(library);
+
+ Dependencies dependencies = mock(Dependencies.class);
+ when(dependencies.getLibraries()).thenReturn(libraries);
+
+ AndroidArtifact artifact = mock(AndroidArtifact.class);
+ when(artifact.getDependencies()).thenReturn(dependencies);
+
+ Variant variant = mock(Variant.class);
+ when(variant.getMainArtifact()).thenReturn(artifact);
+ return variant;
+ }
+ };
+ }
+
+ if ("testPlayServiceConsistency".equals(getName())
+ || "testPlayServiceConsistencyNonIncremental".equals(getName())) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ /*
+ Simulate variant which has an AndroidLibrary with
+ resolved coordinates
+
+ b//22709708
+ com.google.android.gms:play-services-location:7.3.0
+ com.google.android.gms:play-services-wearable:7.5.0
+ */
+ MavenCoordinates coordinates1 = mock(MavenCoordinates.class);
+ when(coordinates1.getGroupId()).thenReturn("com.google.android.gms");
+ when(coordinates1.getArtifactId()).thenReturn("play-services-location");
+ when(coordinates1.getVersion()).thenReturn("7.3.0");
+ when(coordinates1.toString()).thenReturn("com.google.android.gms:play-services-location:7.3.0");
+
+ MavenCoordinates coordinates2 = mock(MavenCoordinates.class);
+ when(coordinates2.getGroupId()).thenReturn("com.google.android.gms");
+ when(coordinates2.getArtifactId()).thenReturn("play-services-wearable");
+ when(coordinates2.getVersion()).thenReturn("7.5.0");
+ when(coordinates2.toString()).thenReturn("com.google.android.gms:play-services-wearable:7.5.0");
+
+ AndroidLibrary library1 = mock(AndroidLibrary.class);
+ when(library1.getResolvedCoordinates()).thenReturn(coordinates1);
+ when(library1.getLintJar()).thenReturn(new File("lint.jar"));
+
+ AndroidLibrary library2 = mock(AndroidLibrary.class);
+ when(library2.getResolvedCoordinates()).thenReturn(coordinates2);
+ when(library2.getLintJar()).thenReturn(new File("lint.jar"));
+
+ List<AndroidLibrary> libraries = Arrays.asList(library1, library2);
+
+ Dependencies dependencies = mock(Dependencies.class);
+ when(dependencies.getLibraries()).thenReturn(libraries);
+
+ AndroidArtifact artifact = mock(AndroidArtifact.class);
+ when(artifact.getDependencies()).thenReturn(dependencies);
+
+ Variant variant = mock(Variant.class);
+ when(variant.getMainArtifact()).thenReturn(artifact);
+ return variant;
+ }
+ };
+ }
+
+ return super.createProject(dir, referenceDir);
+ }
+ };
+ }
+
+ // Copy of com.android.build.gradle.tasks.GroovyGradleDetector (with "static" added as
+ // a modifier, and the unused field IMPLEMENTATION removed, and with fail(t.toString())
+ // inserted into visitBuildScript's catch handler.
+ //
+ // THIS CODE DUPLICATION IS NOT AN IDEAL SITUATION! But, it's preferable to a lack of
+ // tests.
+ //
+ // A more proper fix would be to extract the groovy detector into a library shared by
+ // the testing framework and the gradle plugin.
+
+ public static class GroovyGradleDetector extends GradleDetector {
+ @Override
+ public void visitBuildScript(@NonNull final Context context, Map<String, Object> sharedData) {
+ try {
+ visitQuietly(context, sharedData);
+ } catch (Throwable t) {
+ // ignore
+ // Parsing the build script can involve class loading that we sometimes can't
+ // handle. This happens for example when running lint in build-system/tests/api/.
+ // This is a lint limitation rather than a user error, so don't complain
+ // about these. Consider reporting a Issue#LINT_ERROR.
+ fail(t.toString());
+ }
+ }
+
+ private void visitQuietly(@NonNull final Context context,
+ @SuppressWarnings("UnusedParameters") Map<String, Object> sharedData) {
+ String source = context.getContents();
+ if (source == null) {
+ return;
+ }
+
+ List<ASTNode> astNodes = new AstBuilder().buildFromString(source);
+ GroovyCodeVisitor visitor = new CodeVisitorSupport() {
+ private List<MethodCallExpression> mMethodCallStack = Lists.newArrayList();
+ @Override
+ public void visitMethodCallExpression(MethodCallExpression expression) {
+ mMethodCallStack.add(expression);
+ super.visitMethodCallExpression(expression);
+ Expression arguments = expression.getArguments();
+ String parent = expression.getMethodAsString();
+ String parentParent = getParentParent();
+ if (arguments instanceof ArgumentListExpression) {
+ ArgumentListExpression ale = (ArgumentListExpression)arguments;
+ List<Expression> expressions = ale.getExpressions();
+ if (expressions.size() == 1 &&
+ expressions.get(0) instanceof ClosureExpression) {
+ if (isInterestingBlock(parent, parentParent)) {
+ ClosureExpression closureExpression =
+ (ClosureExpression)expressions.get(0);
+ Statement block = closureExpression.getCode();
+ if (block instanceof BlockStatement) {
+ BlockStatement bs = (BlockStatement)block;
+ for (Statement statement : bs.getStatements()) {
+ if (statement instanceof ExpressionStatement) {
+ ExpressionStatement e = (ExpressionStatement)statement;
+ if (e.getExpression() instanceof MethodCallExpression) {
+ checkDslProperty(parent,
+ (MethodCallExpression)e.getExpression(),
+ parentParent);
+ }
+ } else if (statement instanceof ReturnStatement) {
+ // Single item in block
+ ReturnStatement e = (ReturnStatement)statement;
+ if (e.getExpression() instanceof MethodCallExpression) {
+ checkDslProperty(parent,
+ (MethodCallExpression)e.getExpression(),
+ parentParent);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (arguments instanceof TupleExpression) {
+ if (isInterestingStatement(parent, parentParent)) {
+ TupleExpression te = (TupleExpression) arguments;
+ Map<String, String> namedArguments = Maps.newHashMap();
+ List<String> unnamedArguments = Lists.newArrayList();
+ for (Expression subExpr : te.getExpressions()) {
+ if (subExpr instanceof NamedArgumentListExpression) {
+ NamedArgumentListExpression nale = (NamedArgumentListExpression) subExpr;
+ for (MapEntryExpression mae : nale.getMapEntryExpressions()) {
+ namedArguments.put(mae.getKeyExpression().getText(),
+ mae.getValueExpression().getText());
+ }
+ }
+ }
+ checkMethodCall(context, parent, parentParent, namedArguments, unnamedArguments, expression);
+ }
+ }
+ assert !mMethodCallStack.isEmpty();
+ assert mMethodCallStack.get(mMethodCallStack.size() - 1) == expression;
+ mMethodCallStack.remove(mMethodCallStack.size() - 1);
+ }
+
+ private String getParentParent() {
+ for (int i = mMethodCallStack.size() - 2; i >= 0; i--) {
+ MethodCallExpression expression = mMethodCallStack.get(i);
+ Expression arguments = expression.getArguments();
+ if (arguments instanceof ArgumentListExpression) {
+ ArgumentListExpression ale = (ArgumentListExpression)arguments;
+ List<Expression> expressions = ale.getExpressions();
+ if (expressions.size() == 1 &&
+ expressions.get(0) instanceof ClosureExpression) {
+ return expression.getMethodAsString();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void checkDslProperty(String parent, MethodCallExpression c,
+ String parentParent) {
+ String property = c.getMethodAsString();
+ if (isInterestingProperty(property, parent, getParentParent())) {
+ String value = getText(c.getArguments());
+ checkDslPropertyAssignment(context, property, value, parent, parentParent, c, c);
+ }
+ }
+
+ private String getText(ASTNode node) {
+ String source = context.getContents();
+ Pair<Integer, Integer> offsets = getOffsets(node, context);
+ return source.substring(offsets.getFirst(), offsets.getSecond());
+ }
+ };
+
+ for (ASTNode node : astNodes) {
+ node.visit(visitor);
+ }
+ }
+
+ @NonNull
+ private static Pair<Integer, Integer> getOffsets(ASTNode node, Context context) {
+ if (node.getLastLineNumber() == -1 && node instanceof TupleExpression) {
+ // Workaround: TupleExpressions yield bogus offsets, so use its
+ // children instead
+ TupleExpression exp = (TupleExpression) node;
+ List<Expression> expressions = exp.getExpressions();
+ if (!expressions.isEmpty()) {
+ return Pair.of(
+ getOffsets(expressions.get(0), context).getFirst(),
+ getOffsets(expressions.get(expressions.size() - 1), context).getSecond());
+ }
+ }
+ String source = context.getContents();
+ assert source != null; // because we successfully parsed
+ int start = 0;
+ int end = source.length();
+ int line = 1;
+ int startLine = node.getLineNumber();
+ int startColumn = node.getColumnNumber();
+ int endLine = node.getLastLineNumber();
+ int endColumn = node.getLastColumnNumber();
+ int column = 1;
+ for (int index = 0, len = end; index < len; index++) {
+ if (line == startLine && column == startColumn) {
+ start = index;
+ }
+ if (line == endLine && column == endColumn) {
+ end = index;
+ break;
+ }
+
+ char c = source.charAt(index);
+ if (c == '\n') {
+ line++;
+ column = 1;
+ } else {
+ column++;
+ }
+ }
+
+ return Pair.of(start, end);
+ }
+
+ @Override
+ protected int getStartOffset(@NonNull Context context, @NonNull Object cookie) {
+ ASTNode node = (ASTNode) cookie;
+ Pair<Integer, Integer> offsets = getOffsets(node, context);
+ return offsets.getFirst();
+ }
+
+ @Override
+ protected Location createLocation(@NonNull Context context, @NonNull Object cookie) {
+ ASTNode node = (ASTNode) cookie;
+ Pair<Integer, Integer> offsets = getOffsets(node, context);
+ int fromLine = node.getLineNumber() - 1;
+ int fromColumn = node.getColumnNumber() - 1;
+ int toLine = node.getLastLineNumber() - 1;
+ int toColumn = node.getLastColumnNumber() - 1;
+ return Location.create(context.file,
+ new DefaultPosition(fromLine, fromColumn, offsets.getFirst()),
+ new DefaultPosition(toLine, toColumn, offsets.getSecond()));
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GridLayoutDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
new file mode 100644
index 0000000..3211e5b
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class HandlerDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new HandlerDetector();
+ }
+
+ public void testRegistered() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/HandlerTest.java:12: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.Inner) [HandlerLeak]\n"
+ + " public class Inner extends Handler { // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/HandlerTest.java:18: Warning: This Handler class should be static or leaks might occur (anonymous android.os.Handler) [HandlerLeak]\n"
+ + " Handler anonymous = new Handler() { // ERROR\n"
+ + " ~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/HandlerTest.java", ""
+ + "package test.pkg;\n"
+ + "import android.os.Looper;\n"
+ + "import android.os.Handler;\n"
+ + "import android.os.Message;\n"
+ + "\n"
+ + "public class HandlerTest extends Handler { // OK\n"
+ + " public static class StaticInner extends Handler { // OK\n"
+ + " public void dispatchMessage(Message msg) {\n"
+ + " super.dispatchMessage(msg);\n"
+ + " };\n"
+ + " }\n"
+ + " public class Inner extends Handler { // ERROR\n"
+ + " public void dispatchMessage(Message msg) {\n"
+ + " super.dispatchMessage(msg);\n"
+ + " };\n"
+ + " }\n"
+ + " void method() {\n"
+ + " Handler anonymous = new Handler() { // ERROR\n"
+ + " public void dispatchMessage(Message msg) {\n"
+ + " super.dispatchMessage(msg);\n"
+ + " };\n"
+ + " };\n"
+ + "\n"
+ + " Looper looper = null;\n"
+ + " Handler anonymous2 = new Handler(looper) { // OK\n"
+ + " public void dispatchMessage(Message msg) {\n"
+ + " super.dispatchMessage(msg);\n"
+ + " };\n"
+ + " };\n"
+ + " }\n"
+ + "\n"
+ + " public class WithArbitraryLooper extends Handler {\n"
+ + " public WithArbitraryLooper(String unused, Looper looper) { // OK\n"
+ + " super(looper, null);\n"
+ + " }\n"
+ + "\n"
+ + " public void dispatchMessage(Message msg) {\n"
+ + " super.dispatchMessage(msg);\n"
+ + " };\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testSuppress() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ java("src/test/pkg/CheckActivity.java", ""
+ + "package test.pkg;\n"
+ + "import android.annotation.SuppressLint;\n"
+ + "import android.app.Activity;\n"
+ + "import android.os.Handler;\n"
+ + "import android.os.Message;\n"
+ + "\n"
+ + "public class CheckActivity extends Activity {\n"
+ + "\n"
+ + " @SuppressWarnings(\"unused\")\n"
+ + " @SuppressLint(\"HandlerLeak\")\n"
+ + " Handler handler = new Handler() {\n"
+ + "\n"
+ + " public void handleMessage(Message msg) {\n"
+ + "\n"
+ + " }\n"
+ + " };\n"
+ + "\n"
+ + "}")
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedDebugModeDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
new file mode 100644
index 0000000..e1d5be4
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class HardcodedValuesDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new HardcodedValuesDetector();
+ }
+
+ public void testStrings() throws Exception {
+ assertEquals(
+ "res/layout/accessibility.xml:3: Warning: [I18N] Hardcoded string \"Button\", should use @string resource [HardcodedText]\n" +
+ " <Button android:text=\"Button\" android:id=\"@+id/button1\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/accessibility.xml:6: Warning: [I18N] Hardcoded string \"Button\", should use @string resource [HardcodedText]\n" +
+ " <Button android:text=\"Button\" android:id=\"@+id/button2\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 2 warnings\n",
+
+ lintFiles("res/layout/accessibility.xml"));
+ }
+
+ public void testMenus() throws Exception {
+ assertEquals(
+ "res/menu/menu.xml:7: Warning: [I18N] Hardcoded string \"My title 1\", should use @string resource [HardcodedText]\n" +
+ " android:title=\"My title 1\">\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/menu/menu.xml:13: Warning: [I18N] Hardcoded string \"My title 2\", should use @string resource [HardcodedText]\n" +
+ " android:title=\"My title 2\">\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 2 warnings\n",
+
+ lintFiles("res/menu/menu.xml"));
+ }
+
+ public void testMenusOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintFiles("res/menu/titles.xml"));
+ }
+
+ public void testSuppress() throws Exception {
+ // All but one errors in the file contain ignore attributes - direct, inherited
+ // and lists
+ assertEquals(
+ "res/layout/ignores.xml:61: Warning: [I18N] Hardcoded string \"Hardcoded\", should use @string resource [HardcodedText]\n" +
+ " android:text=\"Hardcoded\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintFiles("res/layout/ignores.xml"));
+ }
+
+ public void testSuppressViaComment() throws Exception {
+ assertEquals(""
+ + "res/layout/ignores2.xml:51: Warning: [I18N] Hardcoded string \"Hardcoded\", should use @string resource [HardcodedText]\n"
+ + " android:text=\"Hardcoded\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintFiles("res/layout/ignores2.xml"));
+ }
+
+ public void testSkippingPlaceHolders() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ xml("res/layout/test.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\">\n"
+ + "\n"
+ + " <TextView\n"
+ + " android:id=\"@+id/textView\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"Hello World!\" />\n"
+ + "\n"
+ + " <Button\n"
+ + " android:id=\"@+id/button\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"New Button\" />\n"
+ + "\n"
+ + " <TextView\n"
+ + " android:id=\"@+id/textView2\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"Large Text\"\n"
+ + " android:textAppearance=\"?android:attr/textAppearanceLarge\" />\n"
+ + "\n"
+ + " <Button\n"
+ + " android:id=\"@+id/button2\"\n"
+ + " style=\"?android:attr/buttonStyleSmall\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"New Button\" />\n"
+ + "\n"
+ + " <CheckBox\n"
+ + " android:id=\"@+id/checkBox\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"New CheckBox\" />\n"
+ + "\n"
+ + " <TextView\n"
+ + " android:id=\"@+id/textView3\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"New Text\" />\n"
+ + "</LinearLayout>\n")));
+ }
+
+ public void testAppRestrictions() throws Exception {
+ // Sample from https://developer.android.com/samples/AppRestrictionSchema/index.html
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:12: Warning: [I18N] Hardcoded string \"Hardcoded description\", should use @string resource [HardcodedText]\n"
+ + " android:description=\"Hardcoded description\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/xml/app_restrictions.xml:15: Warning: [I18N] Hardcoded string \"Hardcoded title\", should use @string resource [HardcodedText]\n"
+ + " android:title=\"Hardcoded title\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@bool/default_can_say_hello\"\n"
+ + " android:description=\"@string/description_can_say_hello\"\n"
+ + " android:key=\"can_say_hello\"\n"
+ + " android:restrictionType=\"bool\"\n"
+ + " android:title=\"@string/title_can_say_hello\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"Hardcoded default value\"\n"
+ + " android:description=\"Hardcoded description\"\n"
+ + " android:key=\"message\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:title=\"Hardcoded title\"/>\n"
+ + " \n"
+ + "</restrictions>"),
+ xml("res/xml/random_file.xml", ""
+ + "<myRoot xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " \n"
+ + " <myElement\n"
+ + " android:description=\"Hardcoded description\"\n"
+ + " android:title=\"Hardcoded title\"/>\n"
+ + " \n"
+ + "</myRoot>")
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
new file mode 100644
index 0000000..dcad992
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IconDetectorTest.java
@@ -0,0 +1,819 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.FilterData;
+import com.android.build.OutputFile;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidArtifactOutput;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.mockito.stubbing.OngoingStubbing;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+ at SuppressWarnings("javadoc")
+public class IconDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new IconDetector();
+ }
+
+ private Set<Issue> mEnabled = new HashSet<Issue>();
+ private boolean mAbbreviate;
+
+ private static final Set<Issue> ALL = new HashSet<Issue>();
+ static {
+ ALL.add(IconDetector.DUPLICATES_CONFIGURATIONS);
+ ALL.add(IconDetector.DUPLICATES_NAMES);
+ ALL.add(IconDetector.GIF_USAGE);
+ ALL.add(IconDetector.ICON_DENSITIES);
+ ALL.add(IconDetector.ICON_DIP_SIZE);
+ ALL.add(IconDetector.ICON_EXTENSION);
+ ALL.add(IconDetector.ICON_LOCATION);
+ ALL.add(IconDetector.ICON_MISSING_FOLDER);
+ ALL.add(IconDetector.ICON_NODPI);
+ ALL.add(IconDetector.ICON_COLORS);
+ ALL.add(IconDetector.ICON_XML_AND_PNG);
+ ALL.add(IconDetector.ICON_LAUNCHER_SHAPE);
+ ALL.add(IconDetector.ICON_MIX_9PNG);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mAbbreviate = true;
+ }
+
+ @Override
+ protected void configureDriver(LintDriver driver) {
+ driver.setAbbreviating(mAbbreviate);
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && mEnabled.contains(issue);
+ }
+ };
+ }
+
+ public void test() throws Exception {
+ mEnabled = ALL;
+ assertEquals(
+ "res/drawable-mdpi/sample_icon.gif: Warning: Using the .gif format for bitmaps is discouraged [GifUsage]\n" +
+ "res/drawable/ic_launcher.png: Warning: The ic_launcher.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable [IconDuplicatesConfig]\n" +
+ " res/drawable-mdpi/ic_launcher.png: <No location-specific message\n" +
+ "res/drawable/ic_launcher.png: Warning: Found bitmap drawable res/drawable/ic_launcher.png in densityless folder [IconLocation]\n" +
+ "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: sample_icon.gif (found in drawable-mdpi) [IconDensities]\n" +
+ "res: Warning: Missing density variation folders in res: drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
+ "0 errors, 5 warnings\n" +
+ "",
+
+ lintProject(
+ // Use minSDK4 to ensure that we get warnings about missing drawables
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/drawable/ic_launcher.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher.png",
+ "res/drawable-mdpi/sample_icon.gif",
+ // Make a dummy file named .svn to make sure it doesn't get seen as
+ // an icon name
+ "res/drawable-mdpi/sample_icon.gif=>res/drawable-hdpi/.svn",
+ "res/drawable-hdpi/ic_launcher.png"));
+ }
+
+ public void testMixed() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_XML_AND_PNG);
+ assertEquals(
+ "res/drawable/background.xml: Warning: The following images appear both as density independent .xml files and as bitmap files: res/drawable-mdpi/background.png, res/drawable/background.xml [IconXmlAndPng]\n" +
+ " res/drawable-mdpi/background.png: <No location-specific message\n" +
+ "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "apicheck/minsdk4.xml=>res/drawable/background.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/background.png"));
+ }
+
+ public void testApi1() throws Exception {
+ mEnabled = ALL;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ // manifest file which specifies uses sdk = 2
+ "apicheck/minsdk2.xml=>AndroidManifest.xml",
+ "res/drawable/ic_launcher.png"));
+ }
+
+ public void test2() throws Exception {
+ mEnabled = ALL;
+ assertEquals(
+ "res/drawable-hdpi/other.9.png: Warning: The following unrelated icon files have identical contents: appwidget_bg.9.png, other.9.png [IconDuplicates]\n" +
+ " res/drawable-hdpi/appwidget_bg.9.png: <No location-specific message\n" +
+ "res/drawable-hdpi/unrelated.png: Warning: The following unrelated icon files have identical contents: ic_launcher.png, unrelated.png [IconDuplicates]\n" +
+ " res/drawable-hdpi/ic_launcher.png: <No location-specific message\n" +
+ "res: Warning: Missing density variation folders in res: drawable-mdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
+ "0 errors, 3 warnings\n",
+
+ lintProject(
+ "res/drawable-hdpi/unrelated.png",
+ "res/drawable-hdpi/appwidget_bg.9.png",
+ "res/drawable-hdpi/appwidget_bg_focus.9.png",
+ "res/drawable-hdpi/other.9.png",
+ "res/drawable-hdpi/ic_launcher.png"
+ ));
+ }
+
+ public void testNoDpi() throws Exception {
+ mEnabled = ALL;
+ assertEquals(
+ "res/drawable-mdpi/frame.png: Warning: The following images appear in both -nodpi and in a density folder: frame.png [IconNoDpi]\n" +
+ "res/drawable-xlarge-nodpi-v11/frame.png: Warning: The frame.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable-nodpi, drawable-xlarge-nodpi-v11 [IconDuplicatesConfig]\n" +
+ " res/drawable-nodpi/frame.png: <No location-specific message\n" +
+ " res/drawable-mdpi/frame.png: <No location-specific message\n" +
+ "res: Warning: Missing density variation folders in res: drawable-hdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
+ "0 errors, 3 warnings\n" +
+ "",
+
+ lintProject(
+ "res/drawable-mdpi/frame.png",
+ "res/drawable-nodpi/frame.png",
+ "res/drawable-xlarge-nodpi-v11/frame.png"));
+ }
+
+ public void testNoDpi2() throws Exception {
+ mEnabled = ALL;
+ // Having additional icon names in the no-dpi folder should not cause any complaints
+ assertEquals(
+ "res/drawable-xxxhdpi/frame.png: Warning: The image frame.png varies significantly in its density-independent (dip) size across the various density versions: drawable-ldpi/frame.png: 629x387 dp (472x290 px), drawable-mdpi/frame.png: 472x290 dp (472x290 px), drawable-hdpi/frame.png: 315x193 dp (472x290 px), drawable-xhdpi/frame.png: 236x145 dp (472x290 px), drawable-xxhdpi/frame.png: 157x97 dp (472x290 px), drawable-xxxhdpi/frame.png: 118x73 dp (472x290 px) [IconDipSize]\n" +
+ " res/drawable-xxhdpi/frame.png: <No location-specific message\n" +
+ " res/drawable-xhdpi/frame.png: <No location-specific message\n" +
+ " res/drawable-hdpi/frame.png: <No location-specific message\n" +
+ " res/drawable-mdpi/frame.png: <No location-specific message\n" +
+ " res/drawable-ldpi/frame.png: <No location-specific message\n" +
+ "res/drawable-xxxhdpi/frame.png: Warning: The following unrelated icon files have identical contents: frame.png, frame.png, frame.png, file1.png, file2.png, frame.png, frame.png, frame.png [IconDuplicates]\n" +
+ " res/drawable-xxhdpi/frame.png: <No location-specific message\n" +
+ " res/drawable-xhdpi/frame.png: <No location-specific message\n" +
+ " res/drawable-nodpi/file2.png: <No location-specific message\n" +
+ " res/drawable-nodpi/file1.png: <No location-specific message\n" +
+ " res/drawable-mdpi/frame.png: <No location-specific message\n" +
+ " res/drawable-ldpi/frame.png: <No location-specific message\n" +
+ " res/drawable-hdpi/frame.png: <No location-specific message\n" +
+ "0 errors, 2 warnings\n" +
+ "",
+
+ lintProject(
+ "res/drawable-mdpi/frame.png=>res/drawable-mdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-hdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-ldpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-xhdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-xxhdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-xxxhdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-nodpi/file1.png",
+ "res/drawable-mdpi/frame.png=>res/drawable-nodpi/file2.png"));
+ }
+
+ public void testNoDpiMix() throws Exception {
+ mEnabled = ALL;
+ assertEquals(
+ "res/drawable-mdpi/frame.xml: Warning: The following images appear in both -nodpi and in a density folder: frame.png, frame.xml [IconNoDpi]\n" +
+ " res/drawable-mdpi/frame.png: <No location-specific message\n" +
+ "res/drawable-nodpi/frame.xml: Warning: The following images appear both as density independent .xml files and as bitmap files: res/drawable-mdpi/frame.png, res/drawable-nodpi/frame.xml [IconXmlAndPng]\n" +
+ " res/drawable-mdpi/frame.png: <No location-specific message\n" +
+ "res: Warning: Missing density variation folders in res: drawable-hdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi [IconMissingDensityFolder]\n" +
+ "0 errors, 3 warnings\n",
+
+ lintProject(
+ "res/drawable-mdpi/frame.png",
+ "res/drawable/states.xml=>res/drawable-nodpi/frame.xml"));
+ }
+
+
+ public void testMixedFormat() throws Exception {
+ mEnabled = ALL;
+ // Test having a mixture of .xml and .png resources for the same name
+ // Make sure we don't get:
+ // drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: f.png (found in drawable-mdpi)
+ // drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: f.png (found in drawable-mdpi)
+ assertEquals(
+ "res/drawable-xxxhdpi/f.xml: Warning: The following images appear both as density independent .xml files and as bitmap files: res/drawable-hdpi/f.xml, res/drawable-mdpi/f.png [IconXmlAndPng]\n" +
+ " res/drawable-xxhdpi/f.xml: <No location-specific message\n" +
+ " res/drawable-xhdpi/f.xml: <No location-specific message\n" +
+ " res/drawable-mdpi/f.png: <No location-specific message\n" +
+ " res/drawable-hdpi/f.xml: <No location-specific message\n" +
+ "0 errors, 1 warnings\n",
+
+ lintProject(
+ "res/drawable-mdpi/frame.png=>res/drawable-mdpi/f.png",
+ "res/drawable/states.xml=>res/drawable-hdpi/f.xml",
+ "res/drawable/states.xml=>res/drawable-xhdpi/f.xml",
+ "res/drawable/states.xml=>res/drawable-xxhdpi/f.xml",
+ "res/drawable/states.xml=>res/drawable-xxxhdpi/f.xml"));
+ }
+
+ public void testMisleadingFileName() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_EXTENSION);
+ assertEquals(
+ "res/drawable-mdpi/frame.gif: Warning: Misleading file extension; named .gif but the file format is png [IconExtension]\n" +
+ "res/drawable-mdpi/frame.jpg: Warning: Misleading file extension; named .jpg but the file format is png [IconExtension]\n" +
+ "res/drawable-mdpi/myjpg.png: Warning: Misleading file extension; named .png but the file format is JPEG [IconExtension]\n" +
+ "res/drawable-mdpi/sample_icon.jpeg: Warning: Misleading file extension; named .jpeg but the file format is gif [IconExtension]\n" +
+ "res/drawable-mdpi/sample_icon.jpg: Warning: Misleading file extension; named .jpg but the file format is gif [IconExtension]\n" +
+ "res/drawable-mdpi/sample_icon.png: Warning: Misleading file extension; named .png but the file format is gif [IconExtension]\n" +
+ "0 errors, 6 warnings\n",
+
+ lintProject(
+ "res/drawable-mdpi/sample_icon.jpg=>res/drawable-mdpi/myjpg.jpg", // VALID
+ "res/drawable-mdpi/sample_icon.jpg=>res/drawable-mdpi/myjpg.jpeg", // VALID
+ "res/drawable-mdpi/frame.png=>res/drawable-mdpi/frame.gif",
+ "res/drawable-mdpi/frame.png=>res/drawable-mdpi/frame.jpg",
+ "res/drawable-mdpi/sample_icon.jpg=>res/drawable-mdpi/myjpg.png",
+ "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.jpg",
+ "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.jpeg",
+ "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/sample_icon.png"));
+ }
+
+ public void testColors() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "res/drawable-mdpi/ic_menu_my_action.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
+ "res/drawable-mdpi-v11/ic_stat_my_notification.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi-v9/ic_stat_my_notification2.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "0 errors, 3 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_menu_my_action.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi-v11/ic_stat_my_notification.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi-v9/ic_stat_my_notification2.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testNotActionBarIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "No warnings.",
+
+ // No Java code designates the menu as an action bar menu
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/menu/menu.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png", // Not action bar
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testActionBarIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "res/drawable-mdpi/icon1.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
+ "res/drawable-mdpi/icon2.png: Warning: Action Bar icons should use a single gray color (#333333 for light themes (with 60%/30% opacity for enabled/disabled), and #FFFFFF with opacity 80%/30% for dark themes [IconColors]\n" +
+ "0 errors, 2 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/menu/menu.xml",
+ "src/test/pkg/ActionBarTest.java.txt=>src/test/pkg/ActionBarTest.java",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png", // Not action bar
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testOkActionBarIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/menu/menu.xml",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png"));
+ }
+
+ public void testNotificationIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "res/drawable-mdpi/icon1.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon2.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon3.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon4.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "res/drawable-mdpi/icon5.png: Warning: Notification icons must be entirely white [IconColors]\n" +
+ "0 errors, 5 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon4.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon5.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon6.png", // not a notification
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon7.png", // ditto
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png")); // OK
+ }
+
+ public void testOkNotificationIcons() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon3.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon4.png",
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon5.png"));
+ }
+
+ public void testExpectedSize() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_EXPECTED_SIZE);
+ assertEquals(
+ "res/drawable-mdpi/ic_launcher.png: Warning: Incorrect icon size for drawable-mdpi/ic_launcher.png: expected 48x48, but was 24x24 [IconExpectedSize]\n" +
+ "res/drawable-mdpi/icon1.png: Warning: Incorrect icon size for drawable-mdpi/icon1.png: expected 32x32, but was 48x48 [IconExpectedSize]\n" +
+ "res/drawable-mdpi/icon3.png: Warning: Incorrect icon size for drawable-mdpi/icon3.png: expected 24x24, but was 48x48 [IconExpectedSize]\n" +
+ "0 errors, 3 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "src/test/pkg/NotificationTest.java.txt=>src/test/pkg/NotificationTest.java",
+ "res/menu/menu.xml",
+ "src/test/pkg/ActionBarTest.java.txt=>src/test/pkg/ActionBarTest.java",
+
+ // 3 wrong-sized icons:
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/icon3.png",
+ "res/drawable-mdpi/stat_notify_alarm.png=>res/drawable-mdpi/ic_launcher.png",
+
+ // OK sizes
+ "res/drawable-mdpi/ic_menu_add_clip_normal.png=>res/drawable-mdpi/icon2.png",
+ "res/drawable-mdpi/stat_notify_alarm.png=>res/drawable-mdpi/icon4.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png"
+ ));
+ }
+
+ public void testAbbreviate() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_DENSITIES);
+ assertEquals(
+ "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: " +
+ "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png, " +
+ "ic_launcher3.png... (6 more) [IconDensities]\n" +
+ "res/drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: " +
+ "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png, " +
+ "ic_launcher3.png... (6 more) [IconDensities]\n" +
+ "0 errors, 2 warnings\n",
+
+ lintProject(
+ // Use minSDK4 to ensure that we get warnings about missing drawables
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-hdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-xhdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher3.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher4.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher5.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher6.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher7.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher8.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher9.webp",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher10.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher11.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher12.png"
+ ));
+ }
+
+ public void testShowAll() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_DENSITIES);
+ mAbbreviate = false;
+ assertEquals(
+ "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: " +
+ "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png, " +
+ "ic_launcher3.png, ic_launcher4.png, ic_launcher5.png, ic_launcher6.png, " +
+ "ic_launcher7.png, ic_launcher8.png, ic_launcher9.png [IconDensities]\n" +
+ "res/drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: " +
+ "ic_launcher10.png, ic_launcher11.png, ic_launcher12.png, ic_launcher2.png," +
+ " ic_launcher3.png, ic_launcher4.png, ic_launcher5.png, ic_launcher6.png, " +
+ "ic_launcher7.png, ic_launcher8.png, ic_launcher9.png [IconDensities]\n" +
+ "0 errors, 2 warnings\n",
+
+ lintProject(
+ // Use minSDK4 to ensure that we get warnings about missing drawables
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-hdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-xhdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher3.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher4.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher5.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher6.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher7.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher8.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher9.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher10.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher11.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher12.png"
+ ));
+ }
+
+ public void testIgnoreMissingFolders() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_DENSITIES);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ // Use minSDK4 to ensure that we get warnings about missing drawables
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "ignoremissing.xml=>lint.xml",
+ "res/drawable/ic_launcher.png=>res/drawable-hdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher1.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher2.png"
+ ));
+ }
+
+ public void testSquareLauncher() throws Exception {
+ mEnabled = Collections.singleton(IconDetector.ICON_LAUNCHER_SHAPE);
+ assertEquals(
+ "res/drawable-hdpi/ic_launcher_filled.png: Warning: Launcher icons should not fill every pixel of their square region; see the design guide for details [IconLauncherShape]\n" +
+ "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/drawable-hdpi/filled.png=>res/drawable-hdpi/ic_launcher_filled.png",
+ "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/ic_launcher_2.gif"
+ ));
+ }
+
+ public void testMixNinePatch() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=43075
+ mEnabled = Collections.singleton(IconDetector.ICON_MIX_9PNG);
+ assertEquals(""
+ + "res/drawable-mdpi/ic_launcher_filled.png: Warning: The files ic_launcher_filled.png and ic_launcher_filled.9.png clash; both will map to @drawable/ic_launcher_filled [IconMixedNinePatch]\n"
+ + " res/drawable-hdpi/ic_launcher_filled.png: <No location-specific message\n"
+ + " res/drawable-hdpi/ic_launcher_filled.9.png: <No location-specific message\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/drawable-hdpi/filled.png=>res/drawable-mdpi/ic_launcher_filled.png",
+ "res/drawable-hdpi/filled.png=>res/drawable-hdpi/ic_launcher_filled.png",
+ "res/drawable-hdpi/filled.png=>res/drawable-hdpi/ic_launcher_filled.9.png",
+ "res/drawable-mdpi/sample_icon.gif=>res/drawable-mdpi/ic_launcher_2.gif"
+ ));
+ }
+
+ public void test67486() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=67486
+ mEnabled = Collections.singleton(IconDetector.ICON_COLORS);
+ assertEquals("No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/drawable-xhdpi/ic_stat_notify.png=>res/drawable-xhdpi/ic_stat_notify.png"
+ ));
+ }
+
+ public void testDuplicatesWithDpNames() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=74584
+ mEnabled = Collections.singleton(IconDetector.DUPLICATES_NAMES);
+ assertEquals("No warnings.",
+
+ lintProject(
+ "res/drawable-hdpi/unrelated.png=>res/drawable-mdpi/foo_72dp.png",
+ "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_36dp.png"
+ ));
+ }
+
+ public void testClaimedSize() throws Exception {
+ // Check that icons which declare a dp size actually correspond to that dp size
+ mEnabled = Collections.singleton(IconDetector.ICON_DIP_SIZE);
+ assertEquals(""
+ + "res/drawable-xhdpi/foo_30dp.png: Warning: Suspicious file name foo_30dp.png: The implied 30 dp size does not match the actual dp size (pixel size 72\u00d772 in a drawable-xhdpi folder computes to 36\u00d736 dp) [IconDipSize]\n"
+ + "res/drawable-mdpi/foo_80dp.png: Warning: Suspicious file name foo_80dp.png: The implied 80 dp size does not match the actual dp size (pixel size 72\u00d772 in a drawable-mdpi folder computes to 72\u00d772 dp) [IconDipSize]\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "res/drawable-hdpi/unrelated.png=>res/drawable-mdpi/foo_72dp.png", // ok
+ "res/drawable-hdpi/unrelated.png=>res/drawable-mdpi/foo_80dp.png", // wrong
+ "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_36dp.png", // ok
+ "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_35dp.png", // ~ok
+ "res/drawable-hdpi/unrelated.png=>res/drawable-xhdpi/foo_30dp.png" // wrong
+ ));
+ }
+
+ public void testResConfigs1() throws Exception {
+ // resConfigs in the Gradle model sets up the specific set of resource configs
+ // that are included in the packaging: we use this to limit the set of required
+ // densities
+ mEnabled = Sets.newHashSet(IconDetector.ICON_DENSITIES, IconDetector.ICON_MISSING_FOLDER);
+ assertEquals(""
+ + "res: Warning: Missing density variation folders in res: drawable-hdpi [IconMissingDensityFolder]\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "res/drawable-mdpi/frame.png",
+ "res/drawable-nodpi/frame.png",
+ "res/drawable-xlarge-nodpi-v11/frame.png"));
+ }
+
+ public void testResConfigs2() throws Exception {
+ mEnabled = Sets.newHashSet(IconDetector.ICON_DENSITIES, IconDetector.ICON_MISSING_FOLDER);
+ assertEquals(""
+ + "res/drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: sample_icon.gif (found in drawable-mdpi) [IconDensities]\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ // Use minSDK4 to ensure that we get warnings about missing drawables
+ "apicheck/minsdk4.xml=>AndroidManifest.xml",
+ "res/drawable/ic_launcher.png",
+ "res/drawable/ic_launcher.png=>res/drawable-mdpi/ic_launcher.png",
+ "res/drawable/ic_launcher.png=>res/drawable-xhdpi/ic_launcher.png",
+ "res/drawable-mdpi/sample_icon.gif",
+ "res/drawable-hdpi/ic_launcher.png"));
+ }
+
+ public void testSplits1() throws Exception {
+ // splits in the Gradle model sets up the specific set of resource configs
+ // that are included in the packaging: we use this to limit the set of required
+ // densities
+ mEnabled = Sets.newHashSet(IconDetector.ICON_DENSITIES, IconDetector.ICON_MISSING_FOLDER);
+ assertEquals(""
+ + "res: Warning: Missing density variation folders in res: drawable-hdpi [IconMissingDensityFolder]\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "res/drawable-mdpi/frame.png",
+ "res/drawable-nodpi/frame.png",
+ "res/drawable-xlarge-nodpi-v11/frame.png"));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ String testName = getName();
+ if (testName.startsWith("testResConfigs")) {
+ return createClientForTestResConfigs();
+ } else if (testName.startsWith("testSplits")) {
+ return createClientForTestSplits();
+ } else {
+ return super.createClient();
+ }
+ }
+
+ private TestLintClient createClientForTestResConfigs() {
+
+ // Set up a mock project model for the resource configuration test(s)
+ // where we provide a subset of densities to be included
+
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ /*
+ Simulate variant freeBetaDebug in this setup:
+ defaultConfig {
+ ...
+ resConfigs "mdpi"
+ }
+ flavorDimensions "pricing", "releaseType"
+ productFlavors {
+ beta {
+ flavorDimension "releaseType"
+ resConfig "en"
+ resConfigs "nodpi", "hdpi"
+ }
+ normal { flavorDimension "releaseType" }
+ free { flavorDimension "pricing" }
+ paid { flavorDimension "pricing" }
+ }
+ */
+ ProductFlavor flavorFree = mock(ProductFlavor.class);
+ when(flavorFree.getName()).thenReturn("free");
+ when(flavorFree.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorNormal = mock(ProductFlavor.class);
+ when(flavorNormal.getName()).thenReturn("normal");
+ when(flavorNormal.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorPaid = mock(ProductFlavor.class);
+ when(flavorPaid.getName()).thenReturn("paid");
+ when(flavorPaid.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorBeta = mock(ProductFlavor.class);
+ when(flavorBeta.getName()).thenReturn("beta");
+ List<String> resConfigs = Arrays.asList("hdpi", "en", "nodpi");
+ when(flavorBeta.getResourceConfigurations()).thenReturn(resConfigs);
+
+ ProductFlavor defaultFlavor = mock(ProductFlavor.class);
+ when(defaultFlavor.getName()).thenReturn("main");
+ when(defaultFlavor.getResourceConfigurations()).thenReturn(
+ Collections.singleton("mdpi"));
+
+ ProductFlavorContainer containerBeta =
+ mock(ProductFlavorContainer.class);
+ when(containerBeta.getProductFlavor()).thenReturn(flavorBeta);
+
+ ProductFlavorContainer containerFree =
+ mock(ProductFlavorContainer.class);
+ when(containerFree.getProductFlavor()).thenReturn(flavorFree);
+
+ ProductFlavorContainer containerPaid =
+ mock(ProductFlavorContainer.class);
+ when(containerPaid.getProductFlavor()).thenReturn(flavorPaid);
+
+ ProductFlavorContainer containerNormal =
+ mock(ProductFlavorContainer.class);
+ when(containerNormal.getProductFlavor()).thenReturn(flavorNormal);
+
+ ProductFlavorContainer defaultContainer =
+ mock(ProductFlavorContainer.class);
+ when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
+
+ List<ProductFlavorContainer> containers = Arrays.asList(
+ containerPaid, containerFree, containerNormal, containerBeta
+ );
+
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getProductFlavors()).thenReturn(containers);
+ when(project.getDefaultConfig()).thenReturn(defaultContainer);
+ return project;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ List<String> productFlavorNames = Arrays.asList("free", "beta");
+ Variant mock = mock(Variant.class);
+ when(mock.getProductFlavors()).thenReturn(productFlavorNames);
+ return mock;
+ }
+ };
+ }
+ };
+ }
+
+ private TestLintClient createClientForTestSplits() {
+
+ // Set up a mock project model for the resource configuration test(s)
+ // where we provide a subset of densities to be included
+
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ /*
+ Simulate variant debug in this setup:
+ splits {
+ density {
+ enable true
+ reset()
+ include "mdpi", "hdpi"
+ }
+ }
+ */
+
+ ProductFlavor defaultFlavor = mock(ProductFlavor.class);
+ when(defaultFlavor.getName()).thenReturn("main");
+ when(defaultFlavor.getResourceConfigurations()).thenReturn(
+ Collections.<String>emptyList());
+
+ ProductFlavorContainer defaultContainer =
+ mock(ProductFlavorContainer.class);
+ when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
+
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getProductFlavors()).thenReturn(
+ Collections.<ProductFlavorContainer>emptyList());
+ when(project.getDefaultConfig()).thenReturn(defaultContainer);
+ return project;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ Collection<AndroidArtifactOutput> outputs = Lists.newArrayList();
+
+ outputs.add(createAndroidArtifactOutput("", ""));
+ outputs.add(createAndroidArtifactOutput("DENSITY", "mdpi"));
+ outputs.add(createAndroidArtifactOutput("DENSITY", "hdpi"));
+
+ AndroidArtifact mainArtifact = mock(AndroidArtifact.class);
+ when(mainArtifact.getOutputs()).thenReturn(outputs);
+
+ List<String> productFlavorNames = Collections.emptyList();
+ Variant mock = mock(Variant.class);
+ when(mock.getProductFlavors()).thenReturn(productFlavorNames);
+ when(mock.getMainArtifact()).thenReturn(mainArtifact);
+ return mock;
+ }
+
+ private AndroidArtifactOutput createAndroidArtifactOutput(
+ @NonNull String filterType,
+ @NonNull String identifier) {
+ AndroidArtifactOutput artifactOutput = mock(
+ AndroidArtifactOutput.class);
+
+ OutputFile outputFile = mock(OutputFile.class);
+ if (filterType.isEmpty()) {
+ when(outputFile.getFilterTypes())
+ .thenReturn(Collections.<String>emptyList());
+ when(outputFile.getFilters())
+ .thenReturn(Collections.<FilterData>emptyList());
+ } else {
+ when(outputFile.getFilterTypes())
+ .thenReturn(Collections.singletonList(filterType));
+ List<FilterData> filters = Lists.newArrayList();
+ FilterData filter = mock(FilterData.class);
+ when(filter.getFilterType()).thenReturn(filterType);
+ when(filter.getIdentifier()).thenReturn(identifier);
+ filters.add(filter);
+ when(outputFile.getFilters()).thenReturn(filters);
+ }
+
+ // Work around wildcard capture
+ //when(artifactOutput.getOutputs()).thenReturn(outputFiles);
+ List<OutputFile> outputFiles = Collections.singletonList(outputFile);
+ OngoingStubbing<? extends Collection<? extends OutputFile>> when = when(
+ artifactOutput.getOutputs());
+ //noinspection unchecked,RedundantCast
+ ((OngoingStubbing<Collection<? extends OutputFile>>) (OngoingStubbing<?>) when)
+ .thenReturn(outputFiles);
+
+ return artifactOutput;
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IncludeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IncludeDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IncludeDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/IncludeDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
new file mode 100644
index 0000000..b3daf88
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+ at SuppressWarnings("javadoc")
+public class InvalidPackageDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new InvalidPackageDetector();
+ }
+
+ public void testUnsupportedJavaLibraryCode() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=39109
+ assertEquals(
+ "libs/unsupported.jar: Error: Invalid package reference in library; not included in Android: java.awt. Referenced from test.pkg.LibraryClass. [InvalidPackage]\n" +
+ "libs/unsupported.jar: Error: Invalid package reference in library; not included in Android: javax.swing. Referenced from test.pkg.LibraryClass. [InvalidPackage]\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "apicheck/unsupported.jar.data=>libs/unsupported.jar"
+ ));
+ }
+
+ public void testOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk2.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
+ "bytecode/GetterTest.jar.data=>libs/GetterTest.jar",
+ "bytecode/classes.jar=>libs/classes.jar"
+ ));
+ }
+
+ public void testLibraryInJavax() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "bytecode/javax.jar.data=>libs/javax.jar"
+ ));
+ }
+
+ public void testAnnotationProcessors1() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=64014
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "bytecode/butterknife-2.0.1.jar.data=>libs/butterknife-2.0.1.jar"
+ ));
+ }
+
+ public void testAnnotationProcessors2() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=64014
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "bytecode/dagger-compiler-1.2.1-subset.jar.data=>libs/dagger-compiler-1.2.1.jar"
+ ));
+ }
+
+ public void testSkipProvidedLibraries() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=187191
+ assertEquals("No warnings.",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "apicheck/unsupported.jar.data=>libs/unsupported.jar"
+ ));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ if ("testSkipProvidedLibraries".equals(getName())) {
+ // Set up a mock project model for the resource configuration test(s)
+ // where we provide a subset of densities to be included
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @NonNull
+ @Override
+ public List<File> getJavaLibraries(boolean includeProvided) {
+ if (!includeProvided) {
+ return Collections.emptyList();
+ }
+ return super.getJavaLibraries(true);
+ }
+ };
+ }
+ };
+ }
+
+ return super.createClient();
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
new file mode 100644
index 0000000..e7ba0ca
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class JavaPerformanceDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new JavaPerformanceDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/JavaPerformanceTest.java:28: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " new String(\"foo\");\n" +
+ " ~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:29: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " String s = new String(\"bar\");\n" +
+ " ~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:103: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " new String(\"flag me\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:109: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " new String(\"flag me\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:112: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " Bitmap.createBitmap(100, 100, null);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:113: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " android.graphics.Bitmap.createScaledBitmap(null, 100, 100, false);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:114: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " BitmapFactory.decodeFile(null);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:116: Warning: Avoid object allocations during draw operations: Use Canvas.getClipBounds(Rect) instead of Canvas.getClipBounds() which allocates a temporary Rect [DrawAllocation]\n" +
+ " canvas.getClipBounds(); // allocates on your behalf\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:140: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse instead) [DrawAllocation]\n" +
+ " new String(\"foo\");\n" +
+ " ~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:70: Warning: Use new SparseArray<String>(...) instead for better performance [UseSparseArrays]\n" +
+ " Map<Integer, String> myMap = new HashMap<Integer, String>();\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:72: Warning: Use new SparseBooleanArray(...) instead for better performance [UseSparseArrays]\n" +
+ " Map<Integer, Boolean> myBoolMap = new HashMap<Integer, Boolean>();\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:74: Warning: Use new SparseIntArray(...) instead for better performance [UseSparseArrays]\n" +
+ " Map<Integer, Integer> myIntMap = new java.util.HashMap<Integer, Integer>();\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:190: Warning: Use new SparseIntArray(...) instead for better performance [UseSparseArrays]\n" +
+ " new SparseArray<Integer>(); // Use SparseIntArray instead\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:192: Warning: Use new SparseBooleanArray(...) instead for better performance [UseSparseArrays]\n" +
+ " new SparseArray<Boolean>(); // Use SparseBooleanArray instead\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:201: Warning: Use new SparseArray<String>(...) instead for better performance [UseSparseArrays]\n" +
+ " Map<Byte, String> myByteMap = new HashMap<Byte, String>();\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:33: Warning: Use Integer.valueOf(5) instead [UseValueOf]\n" +
+ " Integer i = new Integer(5);\n" +
+ " ~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:145: Warning: Use Integer.valueOf(42) instead [UseValueOf]\n" +
+ " Integer i1 = new Integer(42);\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:146: Warning: Use Long.valueOf(42L) instead [UseValueOf]\n" +
+ " Long l1 = new Long(42L);\n" +
+ " ~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:147: Warning: Use Boolean.valueOf(true) instead [UseValueOf]\n" +
+ " Boolean b1 = new Boolean(true);\n" +
+ " ~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:148: Warning: Use Character.valueOf('c') instead [UseValueOf]\n" +
+ " Character c1 = new Character('c');\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:149: Warning: Use Float.valueOf(1.0f) instead [UseValueOf]\n" +
+ " Float f1 = new Float(1.0f);\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/JavaPerformanceTest.java:150: Warning: Use Double.valueOf(1.0) instead [UseValueOf]\n" +
+ " Double d1 = new Double(1.0);\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "0 errors, 22 warnings\n",
+
+ lintProject("src/test/pkg/JavaPerformanceTest.java.txt=>" +
+ "src/test/pkg/JavaPerformanceTest.java"));
+ }
+
+ public void testLongSparseArray() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/LongSparseArray.java:10: Warning: Use new LongSparseArray(...) instead for better performance [UseSparseArrays]\n"
+ + " Map<Long, String> myStringMap = new HashMap<Long, String>();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk17.xml=>AndroidManifest.xml",
+ "src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java"));
+ }
+
+ public void testLongSparseSupportLibArray() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/LongSparseArray.java:10: Warning: Use new android.support.v4.util.LongSparseArray(...) instead for better performance [UseSparseArrays]\n"
+ + " Map<Long, String> myStringMap = new HashMap<Long, String>();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java",
+ "bytecode/classes.jar=>libs/android-support-v4.jar"));
+ }
+
+ public void testNoLongSparseArray() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "src/test/pkg/LongSparseArray.java.txt=>src/test/pkg/LongSparseArray.java"));
+ }
+
+ public void testSparseLongArray1() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SparseLongArray.java:10: Warning: Use new SparseLongArray(...) instead for better performance [UseSparseArrays]\n"
+ + " Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk19.xml=>AndroidManifest.xml",
+ "src/test/pkg/SparseLongArray.java.txt=>src/test/pkg/SparseLongArray.java"));
+ }
+
+ public void testSparseLongArray2() throws Exception {
+ // Note -- it's offering a SparseArray, not a SparseLongArray!
+ assertEquals(""
+ + "src/test/pkg/SparseLongArray.java:10: Warning: Use new SparseArray<Long>(...) instead for better performance [UseSparseArrays]\n"
+ + " Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "src/test/pkg/SparseLongArray.java.txt=>src/test/pkg/SparseLongArray.java"));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaScriptInterfaceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaScriptInterfaceDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaScriptInterfaceDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaScriptInterfaceDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LabelForDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LabelForDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LabelForDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LabelForDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java
new file mode 100644
index 0000000..b42022e
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutConsistencyDetectorTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class LayoutConsistencyDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new LayoutConsistencyDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "res/layout/layout1.xml:11: Warning: The id \"button1\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout) [InconsistentLayout]\n"
+ + " android:id=\"@+id/button1\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/layout1.xml:38: Warning: The id \"button4\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout) [InconsistentLayout]\n"
+ + " android:id=\"@+id/button4\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
+ "wrongid/layout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
+ ));
+ }
+
+ public void testSuppress() throws Exception {
+ // Same as unit test above, but button1 is suppressed with tools:ignore; button4 is not
+ assertEquals(""
+ + "res/layout/layout1.xml:56: Warning: The id \"button4\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout) [InconsistentLayout]\n"
+ + " android:id=\"@+id/button4\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
+ "wrongid/layout1_ignore.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
+ ));
+ }
+
+ public void test2() throws Exception {
+ assertEquals(""
+ + "res/layout/layout1.xml:11: Warning: The id \"button1\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout, layout-sw600dp, layout-sw600dp-land) [InconsistentLayout]\n"
+ + " android:id=\"@+id/button1\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/layout-sw600dp/layout1.xml:11: Occurrence in layout-sw600dp\n"
+ + " res/layout-sw600dp-land/layout1.xml:11: Occurrence in layout-sw600dp-land\n"
+ + "res/layout/layout1.xml:38: Warning: The id \"button4\" in layout \"layout1\" is missing from the following layout configurations: layout-xlarge (present in layout, layout-sw600dp, layout-sw600dp-land) [InconsistentLayout]\n"
+ + " android:id=\"@+id/button4\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/layout-sw600dp/layout1.xml:38: Occurrence in layout-sw600dp\n"
+ + " res/layout-sw600dp-land/layout1.xml:38: Occurrence in layout-sw600dp-land\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
+ "wrongid/layout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout1.xml=>res/layout-sw600dp/layout1.xml",
+ "wrongid/layout1.xml=>res/layout-sw600dp-land/layout1.xml",
+ "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
+ ));
+ }
+
+ public void test3() throws Exception {
+ assertEquals(""
+ + "res/layout/layout1.xml:11: Warning: The id \"button1\" in layout \"layout1\" is only present in the following layout configurations: layout (missing from layout-sw600dp, layout-sw600dp-land, layout-xlarge) [InconsistentLayout]\n"
+ + " android:id=\"@+id/button1\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/layout1.xml:38: Warning: The id \"button4\" in layout \"layout1\" is only present in the following layout configurations: layout (missing from layout-sw600dp, layout-sw600dp-land, layout-xlarge) [InconsistentLayout]\n"
+ + " android:id=\"@+id/button4\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "wrongid/Foo.java.txt=>src/test/pkg/Foo.java",
+ "wrongid/layout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout-sw600dp/layout1.xml",
+ "wrongid/layout2.xml=>res/layout-sw600dp-land/layout1.xml",
+ "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
+ ));
+ }
+
+ public void testNoJavaRefs() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "wrongid/layout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout-xlarge/layout1.xml"
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
new file mode 100644
index 0000000..606c945
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LayoutInflationDetectorTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+public class LayoutInflationDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new LayoutInflationDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/LayoutInflationTest.java:13: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ + " convertView = mInflater.inflate(R.layout.your_layout, null);\n"
+ + " ~~~~\n"
+ + "src/test/pkg/LayoutInflationTest.java:14: Warning: Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) [InflateParams]\n"
+ + " convertView = mInflater.inflate(R.layout.your_layout, null, true);\n"
+ + " ~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "src/test/pkg/LayoutInflationTest.java.txt=>src/test/pkg/LayoutInflationTest.java",
+ "res/layout/textsize.xml=>res/layout/your_layout.xml",
+ "res/layout/listseparator.xml=>res/layout-port/your_layout.xml"));
+ }
+
+ public void testNoLayoutParams() throws Exception {
+ assertEquals("No warnings.",
+
+ lintProject(
+ "src/test/pkg/LayoutInflationTest.java.txt=>src/test/pkg/LayoutInflationTest.java",
+ "res/layout/listseparator.xml=>res/layout/your_layout.xml"));
+ }
+
+ public void testHasLayoutParams() throws IOException, XmlPullParserException {
+ assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader("")));
+ assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader("<LinearLayout/>")));
+ assertFalse(LayoutInflationDetector.hasLayoutParams(new StringReader(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:orientation=\"vertical\" >\n"
+ + "\n"
+ + " <include\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " layout=\"@layout/layoutcycle1\" />\n"
+ + "\n"
+ + "</LinearLayout>")));
+
+
+ assertTrue(LayoutInflationDetector.hasLayoutParams(new StringReader(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"\n"
+ + " android:orientation=\"vertical\" >\n"
+ + "\n"
+ + " <include\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " layout=\"@layout/layoutcycle1\" />\n"
+ + "\n"
+ + "</LinearLayout>")));
+ }
+
+ public void testSuppressed() throws Exception {
+ assertEquals("No warnings.",
+
+ lintProject(
+ "src/test/pkg/LayoutInflationTest_ignored.java.txt=>src/test/pkg/LayoutInflationTest.java",
+ "res/layout/textsize.xml=>res/layout/your_layout.xml",
+ "res/layout/listseparator.xml=>res/layout-port/your_layout.xml"));
+ }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java
new file mode 100644
index 0000000..dd5231a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleDetectorTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("ALL")
+public class LocaleDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new LocaleDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/LocaleTest.java:11: Warning: Implicitly using the default locale is a common source of bugs: Use toUpperCase(Locale) instead [DefaultLocale]\n"
+ + " System.out.println(\"WRONG\".toUpperCase());\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:16: Warning: Implicitly using the default locale is a common source of bugs: Use toLowerCase(Locale) instead [DefaultLocale]\n"
+ + " System.out.println(\"WRONG\".toLowerCase());\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:20: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+ + " String.format(\"WRONG: %f\", 1.0f); // Implies locale\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:21: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+ + " String.format(\"WRONG: %1$f\", 1.0f);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:22: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+ + " String.format(\"WRONG: %e\", 1.0f);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:23: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+ + " String.format(\"WRONG: %d\", 1.0f);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:24: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+ + " String.format(\"WRONG: %g\", 1.0f);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:25: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+ + " String.format(\"WRONG: %g\", 1.0f);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/LocaleTest.java:26: Warning: Implicitly using the default locale is a common source of bugs: Use String.format(Locale, ...) instead [DefaultLocale]\n"
+ + " String.format(\"WRONG: %1$tm %1$te,%1$tY\",\n"
+ + " ^\n"
+ + "0 errors, 9 warnings\n"
+,
+
+ lintProject(
+ java("src/test/pkg/LocaleTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import java.text.*;\n"
+ + "import java.util.*;\n"
+ + "\n"
+ + "public class LocaleTest {\n"
+ + " public void testStrings() {\n"
+ + " System.out.println(\"OK\".toUpperCase(Locale.getDefault()));\n"
+ + " System.out.println(\"OK\".toUpperCase(Locale.US));\n"
+ + " System.out.println(\"OK\".toUpperCase(Locale.CHINA));\n"
+ + " System.out.println(\"WRONG\".toUpperCase());\n"
+ + "\n"
+ + " System.out.println(\"OK\".toLowerCase(Locale.getDefault()));\n"
+ + " System.out.println(\"OK\".toLowerCase(Locale.US));\n"
+ + " System.out.println(\"OK\".toLowerCase(Locale.CHINA));\n"
+ + " System.out.println(\"WRONG\".toLowerCase());\n"
+ + "\n"
+ + " String.format(Locale.getDefault(), \"OK: %f\", 1.0f);\n"
+ + " String.format(\"OK: %x %A %c %b %B %h %n %%\", 1, 2, 'c', true, false, 5);\n"
+ + " String.format(\"WRONG: %f\", 1.0f); // Implies locale\n"
+ + " String.format(\"WRONG: %1$f\", 1.0f);\n"
+ + " String.format(\"WRONG: %e\", 1.0f);\n"
+ + " String.format(\"WRONG: %d\", 1.0f);\n"
+ + " String.format(\"WRONG: %g\", 1.0f);\n"
+ + " String.format(\"WRONG: %g\", 1.0f);\n"
+ + " String.format(\"WRONG: %1$tm %1$te,%1$tY\",\n"
+ + " new GregorianCalendar(2012, GregorianCalendar.AUGUST, 27));\n"
+ + " }\n"
+ + "\n"
+ + " @android.annotation.SuppressLint(\"NewApi\") // DateFormatSymbols requires API 9\n"
+ + " public void testSimpleDateFormat() {\n"
+ + " new SimpleDateFormat(); // WRONG\n"
+ + " new SimpleDateFormat(\"yyyy-MM-dd\"); // WRONG\n"
+ + " new SimpleDateFormat(\"yyyy-MM-dd\", DateFormatSymbols.getInstance()); // WRONG\n"
+ + " new SimpleDateFormat(\"yyyy-MM-dd\", Locale.US); // OK\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testIgnoreLoggingWithoutLocale() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ java("src/test/pkg/LogTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.util.Log;\n"
+ + "\n"
+ + "public class LogTest {\n"
+ + " private static final String TAG = \"mytag\";\n"
+ + "\n"
+ + " // Don't flag String.format inside logging calls\n"
+ + " public void test(String dataItemName, int eventStatus) {\n"
+ + " if (Log.isLoggable(TAG, Log.DEBUG)) {\n"
+ + " Log.d(TAG, String.format(\"CQS:Event=%s, keeping status=%d\", dataItemName,\n"
+ + " eventStatus));\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleFolderDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleFolderDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleFolderDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LocaleFolderDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java
new file mode 100644
index 0000000..48531fb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/LogDetectorTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class LogDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new LogDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/LogTest.java:33: Error: Mismatched tags: the d() and isLoggable() calls typically should pass the same tag: TAG1 versus TAG2 [LogTagMismatch]\n" +
+ " Log.d(TAG2, \"message\"); // warn: mismatched tags!\n" +
+ " ~~~~\n" +
+ " src/test/pkg/LogTest.java:32: Conflicting tag\n" +
+ "src/test/pkg/LogTest.java:36: Error: Mismatched tags: the d() and isLoggable() calls typically should pass the same tag: \"my_tag\" versus \"other_tag\" [LogTagMismatch]\n" +
+ " Log.d(\"other_tag\", \"message\"); // warn: mismatched tags!\n" +
+ " ~~~~~~~~~~~\n" +
+ " src/test/pkg/LogTest.java:35: Conflicting tag\n" +
+ "src/test/pkg/LogTest.java:80: Error: Mismatched logging levels: when checking isLoggable level DEBUG, the corresponding log call should be Log.d, not Log.v [LogTagMismatch]\n" +
+ " Log.v(TAG1, \"message\"); // warn: wrong level\n" +
+ " ~\n" +
+ " src/test/pkg/LogTest.java:79: Conflicting tag\n" +
+ "src/test/pkg/LogTest.java:83: Error: Mismatched logging levels: when checking isLoggable level DEBUG, the corresponding log call should be Log.d, not Log.v [LogTagMismatch]\n" +
+ " Log.v(TAG1, \"message\"); // warn: wrong level\n" +
+ " ~\n" +
+ " src/test/pkg/LogTest.java:82: Conflicting tag\n" +
+ "src/test/pkg/LogTest.java:86: Error: Mismatched logging levels: when checking isLoggable level VERBOSE, the corresponding log call should be Log.v, not Log.d [LogTagMismatch]\n" +
+ " Log.d(TAG1, \"message\"); // warn? verbose is a lower logging level, which includes debug\n" +
+ " ~\n" +
+ " src/test/pkg/LogTest.java:85: Conflicting tag\n" +
+ "src/test/pkg/LogTest.java:53: Error: The logging tag can be at most 23 characters, was 43 (really_really_really_really_really_long_tag) [LongLogTag]\n" +
+ " Log.d(\"really_really_really_really_really_long_tag\", \"message\"); // error: too long\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/LogTest.java:59: Error: The logging tag can be at most 23 characters, was 24 (123456789012345678901234) [LongLogTag]\n" +
+ " Log.d(TAG24, \"message\"); // error: too long\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/LogTest.java:60: Error: The logging tag can be at most 23 characters, was 39 (MyReallyReallyReallyReallyReallyLongTag) [LongLogTag]\n" +
+ " Log.d(LONG_TAG, \"message\"); // error: way too long\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/LogTest.java:64: Error: The logging tag can be at most 23 characters, was 39 (MyReallyReallyReallyReallyReallyLongTag) [LongLogTag]\n" +
+ " Log.d(LOCAL_TAG, \"message\"); // error: too long\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/LogTest.java:67: Error: The logging tag can be at most 23 characters, was 28 (1234567890123456789012MyTag1) [LongLogTag]\n" +
+ " Log.d(TAG22 + TAG1, \"message\"); // error: too long\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/LogTest.java:68: Error: The logging tag can be at most 23 characters, was 27 (1234567890123456789012MyTag) [LongLogTag]\n" +
+ " Log.d(TAG22 + \"MyTag\", \"message\"); // error: too long\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/LogTest.java:21: Warning: The log call Log.i(...) should be conditional: surround with if (Log.isLoggable(...)) or if (BuildConfig.DEBUG) { ... } [LogConditional]\n" +
+ " Log.i(TAG1, \"message\" + m); // error: unconditional w/ computation\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/LogTest.java:22: Warning: The log call Log.i(...) should be conditional: surround with if (Log.isLoggable(...)) or if (BuildConfig.DEBUG) { ... } [LogConditional]\n" +
+ " Log.i(TAG1, toString()); // error: unconditional w/ computation\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "11 errors, 2 warnings\n",
+
+ lintProject(
+ "src/test/pkg/LogTest.java.txt=>src/test/pkg/LogTest.java",
+ // stub for type resolution
+ "src/test/pkg/Log.java.txt=>src/android/util/Log.java"
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
new file mode 100644
index 0000000..ddd9050
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
@@ -0,0 +1,1097 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+ at SuppressWarnings("javadoc")
+public class ManifestDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ManifestDetector();
+ }
+
+ private Set<Issue> mEnabled = new HashSet<Issue>();
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && mEnabled.contains(issue);
+ }
+ };
+ }
+
+ public void testOrderOk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ORDER);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testBrokenOrder() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ORDER);
+ assertEquals(
+ "AndroidManifest.xml:16: Warning: <uses-sdk> tag appears after <application> tag [ManifestOrder]\n" +
+ " <uses-sdk android:minSdkVersion=\"Froyo\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintProject(
+ "broken-manifest.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testMissingUsesSdk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
+ assertEquals(
+ "AndroidManifest.xml: Warning: Manifest should specify a minimum API level with <uses-sdk android:minSdkVersion=\"?\" />; if it really supports all versions of Android set it to 1. [UsesMinSdkAttributes]\n" +
+ "0 errors, 1 warnings\n",
+ lintProject(
+ "missingusessdk.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testMissingUsesSdkInGradle() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
+ assertEquals(""
+ + "No warnings.",
+ lintProject("missingusessdk.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testMissingMinSdk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
+ assertEquals(
+ "AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a minimum API level with android:minSdkVersion=\"?\" [UsesMinSdkAttributes]\n" +
+ " <uses-sdk android:targetSdkVersion=\"10\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "missingmin.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testMissingTargetSdk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
+ assertEquals(
+ "AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\" [UsesMinSdkAttributes]\n" +
+ " <uses-sdk android:minSdkVersion=\"10\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n",
+ lintProject(
+ "missingtarget.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testOldTargetSdk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.TARGET_NEWER);
+ assertEquals(
+ "AndroidManifest.xml:7: Warning: Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details. [OldTargetApi]\n" +
+ " <uses-sdk android:minSdkVersion=\"10\" android:targetSdkVersion=\"14\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n",
+ lintProject(
+ "oldtarget.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testMultipleSdk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.MULTIPLE_USES_SDK);
+ assertEquals(
+ "AndroidManifest.xml:8: Error: There should only be a single <uses-sdk> element in the manifest: merge these together [MultipleUsesSdk]\n" +
+ " <uses-sdk android:targetSdkVersion=\"14\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " AndroidManifest.xml:7: Also appears here\n" +
+ " AndroidManifest.xml:9: Also appears here\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "multiplesdk.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testWrongLocation() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.WRONG_PARENT);
+ assertEquals(
+ "AndroidManifest.xml:8: Error: The <uses-sdk> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <uses-sdk android:minSdkVersion=\"Froyo\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:9: Error: The <uses-permission> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <uses-permission />\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:10: Error: The <permission> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <permission />\n" +
+ " ~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:11: Error: The <permission-tree> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <permission-tree />\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:12: Error: The <permission-group> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <permission-group />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:14: Error: The <uses-sdk> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <uses-sdk />\n" +
+ " ~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:15: Error: The <uses-configuration> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <uses-configuration />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:16: Error: The <uses-feature> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <uses-feature />\n" +
+ " ~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:17: Error: The <supports-screens> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <supports-screens />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:18: Error: The <compatible-screens> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <compatible-screens />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:19: Error: The <supports-gl-texture> element must be a direct child of the <manifest> root element [WrongManifestParent]\n" +
+ " <supports-gl-texture />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:24: Error: The <uses-library> element must be a direct child of the <application> element [WrongManifestParent]\n" +
+ " <uses-library />\n" +
+ " ~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:25: Error: The <activity> element must be a direct child of the <application> element [WrongManifestParent]\n" +
+ " <activity android:name=\".HelloWorld\"\n" +
+ " ^\n" +
+ "13 errors, 0 warnings\n" +
+ "",
+
+ lintProject("broken-manifest2.xml=>AndroidManifest.xml"));
+ }
+
+ public void testDuplicateActivity() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
+ assertEquals(
+ "AndroidManifest.xml:16: Error: Duplicate registration for activity com.example.helloworld.HelloWorld [DuplicateActivity]\n" +
+ " <activity android:name=\"com.example.helloworld.HelloWorld\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n" +
+ "",
+
+ lintProject(
+ "duplicate-manifest.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testDuplicateActivityAcrossSourceSets() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "AndroidManifest.xml=>AndroidManifest.xml",
+ "multiproject/main-merge.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "AndroidManifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>res/values/strings.xml"
+ );
+ assertEquals("No warnings.",
+ checkLint(Arrays.asList(master, library)));
+ }
+
+ public void testIgnoreDuplicateActivity() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_ACTIVITY);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "duplicate-manifest-ignore.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testAllowBackup() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(
+ "AndroidManifest.xml:9: Warning: Should explicitly set android:allowBackup to " +
+ "true or false (it's true by default, and that can have some security " +
+ "implications for the application's data) [AllowBackup]\n" +
+ " <application\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n",
+ lintProject(
+ "AndroidManifest.xml",
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testAllowBackupOk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "allowbackup.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testAllowBackupOk2() throws Exception {
+ // Requires build api >= 4
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testAllowBackupOk3() throws Exception {
+ // Not flagged in library projects
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "res/values/strings.xml"));
+ }
+
+ public void testAllowIgnore() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "allowbackup_ignore.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testDuplicatePermissions() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.UNIQUE_PERMISSION);
+ assertEquals(
+ "AndroidManifest.xml:12: Error: Permission name SEND_SMS is not unique (appears in both foo.permission.SEND_SMS and bar.permission.SEND_SMS) [UniquePermission]\n" +
+ " <permission android:name=\"bar.permission.SEND_SMS\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " AndroidManifest.xml:9: Previous permission here\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "duplicate_permissions1.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testDuplicatePermissionsMultiProject() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.UNIQUE_PERMISSION);
+
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "duplicate_permissions2.xml=>AndroidManifest.xml",
+ "multiproject/main-merge.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "duplicate_permissions3.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>res/values/strings.xml"
+ );
+ assertEquals(
+ "LibraryProject/AndroidManifest.xml:9: Error: Permission name SEND_SMS is not unique (appears in both foo.permission.SEND_SMS and bar.permission.SEND_SMS) [UniquePermission]\n" +
+ " <permission android:name=\"bar.permission.SEND_SMS\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ checkLint(Arrays.asList(master, library)));
+ }
+
+ public void testMissingVersion() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
+ assertEquals(""
+ + "AndroidManifest.xml:2: Warning: Should set android:versionCode to specify the application version [MissingVersion]\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "AndroidManifest.xml:2: Warning: Should set android:versionName to specify the application version [MissingVersion]\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "0 errors, 2 warnings\n",
+ lintProject("no_version.xml=>AndroidManifest.xml"));
+ }
+
+ public void testVersionNotMissingInGradleProjects() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
+ assertEquals(""
+ + "No warnings.",
+ lintProject("no_version.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testIllegalReference() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ILLEGAL_REFERENCE);
+ assertEquals(""
+ + "AndroidManifest.xml:4: Warning: The android:versionCode cannot be a resource url, it must be a literal integer [IllegalResourceRef]\n"
+ + " android:versionCode=\"@dimen/versionCode\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:7: Warning: The android:minSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
+ + " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:7: Warning: The android:targetSdkVersion cannot be a resource url, it must be a literal integer (or string if a preview codename) [IllegalResourceRef]\n"
+ + " <uses-sdk android:minSdkVersion=\"@dimen/minSdkVersion\" android:targetSdkVersion=\"@dimen/targetSdkVersion\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 3 warnings\n",
+
+ lintProject("illegal_version.xml=>AndroidManifest.xml"));
+ }
+
+ public void testDuplicateUsesFeature() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_USES_FEATURE);
+ assertEquals(
+ "AndroidManifest.xml:11: Warning: Duplicate declaration of uses-feature android.hardware.camera [DuplicateUsesFeature]\n" +
+ " <uses-feature android:name=\"android.hardware.camera\"/>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n",
+ lintProject(
+ "duplicate_uses_feature.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testDuplicateUsesFeatureOk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.DUPLICATE_USES_FEATURE);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "duplicate_uses_feature_ok.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testMissingApplicationIcon() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
+ assertEquals(
+ "AndroidManifest.xml:9: Warning: Should explicitly set android:icon, there is no default [MissingApplicationIcon]\n" +
+ " <application\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n",
+ lintProject(
+ "missing_application_icon.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testMissingApplicationIconInLibrary() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "missing_application_icon.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "res/values/strings.xml"));
+ }
+
+ public void testMissingApplicationIconOk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testDeviceAdmin() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.DEVICE_ADMIN);
+ assertEquals(""
+ + "AndroidManifest.xml:31: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
+ + " <meta-data android:name=\"android.app.device_admin\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:44: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
+ + " <meta-data android:name=\"android.app.device_admin\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:56: Warning: You must have an intent filter for action android.app.action.DEVICE_ADMIN_ENABLED [DeviceAdmin]\n"
+ + " <meta-data android:name=\"android.app.device_admin\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 3 warnings\n",
+ lintProject("deviceadmin.xml=>AndroidManifest.xml"));
+ }
+
+ public void testMockLocations() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
+ assertEquals(""
+ + "AndroidManifest.xml:9: Error: Mock locations should only be requested in a test or debug-specific manifest file (typically src/debug/AndroidManifest.xml) [MockLocation]\n"
+ + " <uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\" /> \n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "mock_location.xml=>AndroidManifest.xml",
+ "mock_location.xml=>debug/AndroidManifest.xml",
+ "mock_location.xml=>test/AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ // TODO: When we have an instantiatable gradle model, test with real model and verify
+ // that a manifest file in a debug build type does not get flagged.
+ }
+
+ public void testMockLocationsOk() throws Exception {
+ // Not a Gradle project
+ mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
+ assertEquals(""
+ + "No warnings.",
+ lintProject(
+ "mock_location.xml=>AndroidManifest.xml"));
+ }
+
+ public void testGradleOverrides() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
+ assertEquals(""
+ + "AndroidManifest.xml:4: Warning: This versionCode value (1) is not used; it is always overridden by the value specified in the Gradle build script (2) [GradleOverrides]\n"
+ + " android:versionCode=\"1\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:5: Warning: This versionName value (1.0) is not used; it is always overridden by the value specified in the Gradle build script (MyName) [GradleOverrides]\n"
+ + " android:versionName=\"1.0\" >\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:7: Warning: This minSdkVersion value (14) is not used; it is always overridden by the value specified in the Gradle build script (5) [GradleOverrides]\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:7: Warning: This targetSdkVersion value (17) is not used; it is always overridden by the value specified in the Gradle build script (16) [GradleOverrides]\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"17\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 4 warnings\n",
+ lintProject(
+ "gradle_override.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testGradleOverridesOk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
+ // (See custom logic in #createClient which returns -1/null for the merged flavor
+ // from this test, and not from testGradleOverrides)
+ assertEquals(""
+ + "No warnings.",
+ lintProject(
+ "gradle_override.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testGradleOverrideManifestMergerOverride() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=186762
+ mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
+ assertEquals("No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"test.pkg\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" tools:overrideLibrary=\"lib.pkg\" />\n"
+ + "\n"
+ + "</manifest>\n"),
+ copy("multiproject/library.properties", "build.gradle") // dummy; only name counts));
+ ));
+ }
+
+ public void testManifestPackagePlaceholder() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.GRADLE_OVERRIDES);
+ assertEquals(""
+ + "AndroidManifest.xml:3: Warning: Cannot use placeholder for the package in the manifest; set applicationId in build.gradle instead [GradleOverrides]\n"
+ + " package=\"${packageName}\" >\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "gradle_override_placeholder.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
+ public void testMipMap() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.MIPMAP);
+ assertEquals("No warnings.",
+
+ lintProject(
+ "mipmap.xml=>AndroidManifest.xml"));
+ }
+
+ public void testMipMapWithDensityFiltering() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.MIPMAP);
+ assertEquals(""
+ + "AndroidManifest.xml:9: Warning: Should use @mipmap instead of @drawable for launcher icons [MipmapIcons]\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:14: Warning: Should use @mipmap instead of @drawable for launcher icons [MipmapIcons]\n"
+ + " android:icon=\"@drawable/activity1\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "mipmap.xml=>AndroidManifest.xml"));
+ }
+
+ public void testFullBackupContentBoolean() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:fullBackupContent=\"true\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testFullBackupContentMissing() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(""
+ + "AndroidManifest.xml:7: Warning: Missing <full-backup-content> resource [AllowBackup]\n"
+ + " android:fullBackupContent=\"@xml/backup\"\n"
+ + " ~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:fullBackupContent=\"@xml/backup\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testFullBackupContentMissingInLibrary() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:fullBackupContent=\"@xml/backup\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ copy("multiproject/library.properties", "project.properties")));
+ }
+
+ public void testFullBackupContentOk() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + "\n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:fullBackupContent=\"@xml/backup\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ xml("res/xml/backup.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<full-backup-content>\n"
+ + " <include domain=\"file\" path=\"dd\"/>\n"
+ + " <exclude domain=\"file\" path=\"dd/fo3o.txt\"/>\n"
+ + " <exclude domain=\"file\" path=\"dd/ss/foo.txt\"/>\n"
+ + "</full-backup-content>")));
+ }
+
+ public void testHasBackupSpecifiedInTarget23() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"23\" />"
+ + "\n"
+ + " <application\n"
+ + " android:fullBackupContent=\"no\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMissingBackupInTarget23() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(""
+ + "AndroidManifest.xml:5: Warning: On SDK version 23 and up, your app data will "
+ + "be automatically backed up and restored on app install. Consider "
+ + "adding the attribute android:fullBackupContent to specify an @xml "
+ + "resource which configures which files to backup. More info: "
+ + "https://developer.android.com/preview/backup/index.html [AllowBackup]\n"
+ + " <application\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"23\" />"
+ + "\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMissingBackupInPreTarget23() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"21\" />"
+ + "\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMissingBackupWithoutGcmPreTarget23() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"21\" />"
+ + "\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMissingBackupWithoutGcmPostTarget23() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(""
+ + "AndroidManifest.xml:5: Warning: On SDK version 23 and up, your app "
+ + "data will be automatically backed up and restored on app install. "
+ + "Consider adding the attribute android:fullBackupContent to specify "
+ + "an @xml resource which configures which files to backup. "
+ + "More info: https://developer.android.com/preview/backup/index.html [AllowBackup]\n"
+ + " <application\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"23\" />"
+ + "\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMissingBackupWithGcmPreTarget23() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"21\" />"
+ + "\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >"
+ + " <receiver\n"
+ + " android:name=\".GcmBroadcastReceiver\"\n"
+ + " android:permission=\"com.google.android.c2dm.permission.SEND\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n"
+ + " <category android:name=\"com.example.gcm\" />\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testMissingBackupWithGcmPostTarget23() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals(""
+ + "AndroidManifest.xml:5: Warning: On SDK version 23 and up, your app "
+ + "data will be automatically backed up, and restored on app install. "
+ + "Your GCM regid will not work across restores, so you must ensure that "
+ + "it is excluded from the back-up set. Use the attribute "
+ + "android:fullBackupContent to specify an @xml resource which "
+ + "configures which files to backup. More info: "
+ + "https://developer.android.com/preview/backup/index.html [AllowBackup]\n"
+ + " <application\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"23\" />"
+ + "\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\" >"
+ + " <receiver\n"
+ + " android:name=\".GcmBroadcastReceiver\"\n"
+ + " android:permission=\"com.google.android.c2dm.permission.SEND\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n"
+ + " <category android:name=\"com.example.gcm\" />\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ public void testNoMissingFullBackupWithDoNotAllowBackup() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=181805
+ mEnabled = Collections.singleton(ManifestDetector.ALLOW_BACKUP);
+ assertEquals("No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.helloworld\" >\n"
+ + " <uses-sdk android:targetSdkVersion=\"21\" />"
+ + "\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:allowBackup=\"false\"\n"
+ + " android:theme=\"@style/AppTheme\" >"
+ + " <receiver\n"
+ + " android:name=\".GcmBroadcastReceiver\"\n"
+ + " android:permission=\"com.google.android.c2dm.permission.SEND\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n"
+ + " <category android:name=\"com.example.gcm\" />\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n")));
+ }
+
+ // Custom project which locates all manifest files in the project rather than just
+ // being hardcoded to the root level
+
+ @Override
+ protected TestLintClient createClient() {
+ if ("testMipMapWithDensityFiltering".equals(getName())) {
+ // Set up a mock project model for the resource configuration test(s)
+ // where we provide a subset of densities to be included
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ /*
+ Simulate variant freeBetaDebug in this setup:
+ defaultConfig {
+ ...
+ resConfigs "cs"
+ }
+ flavorDimensions "pricing", "releaseType"
+ productFlavors {
+ beta {
+ flavorDimension "releaseType"
+ resConfig "en", "de"
+ resConfigs "nodpi", "hdpi"
+ }
+ normal { flavorDimension "releaseType" }
+ free { flavorDimension "pricing" }
+ paid { flavorDimension "pricing" }
+ }
+ */
+ ProductFlavor flavorFree = mock(ProductFlavor.class);
+ when(flavorFree.getName()).thenReturn("free");
+ when(flavorFree.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorNormal = mock(ProductFlavor.class);
+ when(flavorNormal.getName()).thenReturn("normal");
+ when(flavorNormal.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorPaid = mock(ProductFlavor.class);
+ when(flavorPaid.getName()).thenReturn("paid");
+ when(flavorPaid.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorBeta = mock(ProductFlavor.class);
+ when(flavorBeta.getName()).thenReturn("beta");
+ List<String> resConfigs = Arrays.asList("hdpi", "en", "de", "nodpi");
+ when(flavorBeta.getResourceConfigurations()).thenReturn(resConfigs);
+
+ ProductFlavor defaultFlavor = mock(ProductFlavor.class);
+ when(defaultFlavor.getName()).thenReturn("main");
+ when(defaultFlavor.getResourceConfigurations()).thenReturn(
+ Collections.singleton("cs"));
+
+ ProductFlavorContainer containerBeta =
+ mock(ProductFlavorContainer.class);
+ when(containerBeta.getProductFlavor()).thenReturn(flavorBeta);
+
+ ProductFlavorContainer containerFree =
+ mock(ProductFlavorContainer.class);
+ when(containerFree.getProductFlavor()).thenReturn(flavorFree);
+
+ ProductFlavorContainer containerPaid =
+ mock(ProductFlavorContainer.class);
+ when(containerPaid.getProductFlavor()).thenReturn(flavorPaid);
+
+ ProductFlavorContainer containerNormal =
+ mock(ProductFlavorContainer.class);
+ when(containerNormal.getProductFlavor()).thenReturn(flavorNormal);
+
+ ProductFlavorContainer defaultContainer =
+ mock(ProductFlavorContainer.class);
+ when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
+
+ List<ProductFlavorContainer> containers = Arrays.asList(
+ containerPaid, containerFree, containerNormal, containerBeta
+ );
+
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getProductFlavors()).thenReturn(containers);
+ when(project.getDefaultConfig()).thenReturn(defaultContainer);
+ return project;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ List<String> productFlavorNames = Arrays.asList("free", "beta");
+ Variant mock = mock(Variant.class);
+ when(mock.getProductFlavors()).thenReturn(productFlavorNames);
+ return mock;
+ }
+ };
+ }
+ };
+ }
+ if (mEnabled.contains(ManifestDetector.MOCK_LOCATION)) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ mManifestFiles = Lists.newArrayList();
+ addManifestFiles(mDir);
+ }
+
+ return mManifestFiles;
+ }
+
+ private void addManifestFiles(File dir) {
+ if (dir.getName().equals(ANDROID_MANIFEST_XML)) {
+ mManifestFiles.add(dir);
+ } else if (dir.isDirectory()) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ addManifestFiles(file);
+ }
+ }
+ }
+ }
+
+ @NonNull SourceProvider createSourceProvider(File manifest) {
+ SourceProvider provider = mock(SourceProvider.class);
+ when(provider.getManifestFile()).thenReturn(manifest);
+ return provider;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ if (!isGradleProject()) {
+ return null;
+ }
+
+ File main = new File(mDir, ANDROID_MANIFEST_XML);
+ File debug = new File(mDir, "debug" + File.separator + ANDROID_MANIFEST_XML);
+ File test = new File(mDir, "test" + File.separator + ANDROID_MANIFEST_XML);
+
+ SourceProvider defaultSourceProvider = createSourceProvider(main);
+ SourceProvider debugSourceProvider = createSourceProvider(debug);
+ SourceProvider testSourceProvider = createSourceProvider(test);
+
+ ProductFlavorContainer defaultConfig = mock(ProductFlavorContainer.class);
+ when(defaultConfig.getSourceProvider()).thenReturn(defaultSourceProvider);
+
+ BuildType buildType = mock(BuildType.class);
+ when(buildType.isDebuggable()).thenReturn(true);
+
+ BuildTypeContainer buildTypeContainer = mock(BuildTypeContainer.class);
+ when(buildTypeContainer.getBuildType()).thenReturn(buildType);
+ when(buildTypeContainer.getSourceProvider()).thenReturn(debugSourceProvider);
+ List<BuildTypeContainer> buildTypes = Lists.newArrayList(buildTypeContainer);
+
+ SourceProviderContainer extraProvider = mock(SourceProviderContainer.class);
+ when(extraProvider.getArtifactName()).thenReturn(AndroidProject.ARTIFACT_ANDROID_TEST);
+ when(extraProvider.getSourceProvider()).thenReturn(testSourceProvider);
+ List<SourceProviderContainer> extraProviders = Lists.newArrayList(extraProvider);
+
+ ProductFlavorContainer productFlavorContainer = mock(ProductFlavorContainer.class);
+ when(productFlavorContainer.getExtraSourceProviders()).thenReturn(extraProviders);
+ List<ProductFlavorContainer> productFlavors = Lists.newArrayList(productFlavorContainer);
+
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getDefaultConfig()).thenReturn(defaultConfig);
+ when(project.getBuildTypes()).thenReturn(buildTypes);
+ when(project.getProductFlavors()).thenReturn(productFlavors);
+ return project;
+ }
+ };
+ }
+ };
+ } else if (mEnabled.contains(ManifestDetector.GRADLE_OVERRIDES)) {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ ProductFlavor flavor = mock(ProductFlavor.class);
+ if (getName().equals("ManifestDetectorTest_testGradleOverridesOk") ||
+ getName().equals("ManifestDetectorTest_testManifestPackagePlaceholder")) {
+ when(flavor.getMinSdkVersion()).thenReturn(null);
+ when(flavor.getTargetSdkVersion()).thenReturn(null);
+ when(flavor.getVersionCode()).thenReturn(null);
+ when(flavor.getVersionName()).thenReturn(null);
+ } else {
+ assertTrue(getName(), getName().equals("ManifestDetectorTest_testGradleOverrides") ||
+ getName().equals("ManifestDetectorTest_testGradleOverrideManifestMergerOverride"));
+
+ ApiVersion apiMock = mock(ApiVersion.class);
+ when(apiMock.getApiLevel()).thenReturn(5);
+ when(apiMock.getApiString()).thenReturn("5");
+ when(flavor.getMinSdkVersion()).thenReturn(apiMock);
+
+ apiMock = mock(ApiVersion.class);
+ when(apiMock.getApiLevel()).thenReturn(16);
+ when(apiMock.getApiString()).thenReturn("16");
+ when(flavor.getTargetSdkVersion()).thenReturn(apiMock);
+
+ when(flavor.getVersionCode()).thenReturn(2);
+ when(flavor.getVersionName()).thenReturn("MyName");
+ }
+
+ Variant mock = mock(Variant.class);
+ when(mock.getMergedFlavor()).thenReturn(flavor);
+ return mock;
+ }
+ };
+ }
+ };
+
+ }
+ return super.createClient();
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestResourceDetectorTest.java
new file mode 100644
index 0000000..9892512
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestResourceDetectorTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class ManifestResourceDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ManifestResourceDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals("No warnings.",
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"foo.bar2\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <receiver android:enabled=\"@bool/has_honeycomb\" android:name=\"com.google.android.apps.iosched.appwidget.ScheduleWidgetProvider\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\"/>\n"
+ + " </intent-filter>\n"
+ + " <!-- This specifies the widget provider info -->\n"
+ + " <meta-data android:name=\"android.appwidget.provider\" android:resource=\"@xml/widgetinfo\"/>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>"),
+ xml("res/values/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"app_name\">App Name (Default)</string>\n"
+ + " <bool name=\"has_honeycomb\">false</bool>"
+ + "</resources>"),
+ xml("res/values-v11/values.xml", ""
+ + "<resources>\n"
+ + " <bool name=\"has_honeycomb\">true</bool>\n"
+ + "</resources>"),
+ xml("res/values-en-rUS/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"app_name\">App Name (English)</string>\n"
+ + "</resources>"),
+ xml("res/values-xlarge/values.xml", ""
+ + "<resources>\n"
+ + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
+ + "</resources>")
+ ));
+ }
+
+ public void testInvalidManifestReference() throws Exception {
+ assertEquals(""
+ + "AndroidManifest.xml:6: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in mcc [ManifestResource]\n"
+ + " <application android:fullBackupContent=\"@xml/backup\">\n"
+ + " ~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:8: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in en-rUS [ManifestResource]\n"
+ + " android:process=\"@string/location_process\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "AndroidManifest.xml:9: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in watch-v20 [ManifestResource]\n"
+ + " android:enabled=\"@bool/enable_wearable_location_service\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\">\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application android:fullBackupContent=\"@xml/backup\">\n"
+ + " <service\n" // missing stuff here, not important for test
+ + " android:process=\"@string/location_process\"\n"
+ + " android:enabled=\"@bool/enable_wearable_location_service\">\n"
+ + " </service>"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>"),
+ xml("res/values/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"location_process\">Location Process</string>\n"
+ + "</resources>"),
+ xml("res/values/bools.xml", ""
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + " <bool name=\"enable_wearable_location_service\">true</bool>\n"
+ + "</resources>"),
+ xml("res/values-en-rUS/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"location_process\">Location Process (English)</string>\n"
+ + "</resources>"),
+ xml("res/values-watch/bools.xml", ""
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + " <bool name=\"enable_wearable_location_service\">false</bool>\n"
+ + "</resources>"),
+ xml("res/xml/backup.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<full-backup-content>\n"
+ + " <include domain=\"file\" path=\"dd\"/>\n"
+ + " <exclude domain=\"file\" path=\"dd/fo3o.txt\"/>\n"
+ + " <exclude domain=\"file\" path=\"dd/ss/foo.txt\"/>\n"
+ + "</full-backup-content>"),
+ xml("res/xml-mcc/backup.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<full-backup-content>\n"
+ + " <include domain=\"file\" path=\"mcc\"/>\n"
+ + "</full-backup-content>")
+ ));
+ }
+
+ public void testBatchAnalysis() throws Exception {
+ assertEquals(""
+ + "AndroidManifest.xml:11: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in mcc [ManifestResource]\n"
+ + " android:fullBackupContent=\"@xml/backup\"\n"
+ + " ~~~~~~~~~~~\n"
+ + " res/xml-mcc/backup.xml:2: This value will not be used\n"
+ + "AndroidManifest.xml:21: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in en-rUS [ManifestResource]\n"
+ + " android:process=\"@string/location_process\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values-en-rUS/values.xml:2: This value will not be used\n"
+ + "AndroidManifest.xml:22: Error: Resources referenced from the manifest cannot vary by configuration (except for version qualifiers, e.g. -v21.) Found variation in watch [ManifestResource]\n"
+ + " android:enabled=\"@bool/enable_wearable_location_service\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values-watch/bools.xml:2: This value will not be used\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"foo.bar2\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:fullBackupContent=\"@xml/backup\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <receiver android:enabled=\"@bool/has_honeycomb\" android:name=\"com.google.android.apps.iosched.appwidget.ScheduleWidgetProvider\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\"/>\n"
+ + " </intent-filter>\n"
+ + " <!-- This specifies the widget provider info -->\n"
+ + " <meta-data android:name=\"android.appwidget.provider\" android:resource=\"@xml/widgetinfo\"/>\n"
+ + " </receiver>\n"
+ + " <service\n"
+ + " android:process=\"@string/location_process\"\n"
+ + " android:enabled=\"@bool/enable_wearable_location_service\">\n"
+ + " </service>"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>"),
+ xml("res/values/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"location_process\">Location Process</string>\n"
+ + " <string name=\"app_name\">App Name (Default)</string>\n"
+ + " <bool name=\"has_honeycomb\">false</bool>"
+ + "</resources>"),
+ xml("res/values/bools.xml", ""
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + " <bool name=\"enable_wearable_location_service\">true</bool>\n"
+ + "</resources>"),
+ xml("res/values-en-rUS/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"location_process\">Location Process (English)</string>\n"
+ + " <string name=\"app_name\">App Name (English)</string>\n"
+ + "</resources>"),
+ xml("res/values-watch/bools.xml", ""
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + " <bool name=\"enable_wearable_location_service\">false</bool>\n"
+ + "</resources>"),
+ xml("res/values-v11/values.xml", ""
+ + "<resources>\n"
+ + " <bool name=\"has_honeycomb\">true</bool>\n"
+ + "</resources>"),
+ xml("res/values-xlarge/values.xml", ""
+ + "<resources>\n"
+ + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n"
+ + "</resources>"),
+ xml("res/xml/backup.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<full-backup-content>\n"
+ + " <include domain=\"file\" path=\"dd\"/>\n"
+ + " <exclude domain=\"file\" path=\"dd/fo3o.txt\"/>\n"
+ + " <exclude domain=\"file\" path=\"dd/ss/foo.txt\"/>\n"
+ + "</full-backup-content>"),
+ xml("res/xml-mcc/backup.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<full-backup-content>\n"
+ + " <include domain=\"file\" path=\"mcc\"/>\n"
+ + "</full-backup-content>")
+ ));
+ }
+
+ public void testAllowPermissionNameLocalizations() throws Exception {
+ assertEquals("No warnings.",
+ lintProjectIncrementally(
+ "AndroidManifest.xml",
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"foo.bar2\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <permission-group android:name=\"android.permission-group.CONTACTS\"\n"
+ + " android:icon=\"@drawable/perm_group_contacts\"\n"
+ + " android:label=\"@string/permgrouplab_contacts\"\n"
+ + " android:description=\"@string/permgroupdesc_contacts\"\n"
+ + " android:priority=\"100\" />\n"
+ + "\n"
+ + " <permission android:name=\"android.permission.READ_CONTACTS\"\n"
+ + " android:permissionGroup=\"android.permission-group.CONTACTS\"\n"
+ + " android:label=\"@string/permlab_readContacts\"\n"
+ + " android:description=\"@string/permdesc_readContacts\"\n"
+ + " android:protectionLevel=\"dangerous\" />"
+ + "</manifest>"),
+ xml("res/values/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"permgrouplab_contacts\">Contacts</string>\n"
+ + " <string name=\"permgroupdesc_contacts\">access your contacts</string>\n"
+ + " <string name=\"permlab_readContacts\">read your contacts</string>\n"
+ + " <string name=\"permdesc_readContacts\">Allows the app to...</string>"
+ + "</resources>"),
+ xml("res/values-nb/values.xml", ""
+ + "<resources>\n"
+ + " <string name=\"permgrouplab_contacts\">\"Kontakter\"</string>\n"
+ + " <string name=\"permgroupdesc_contacts\">\"se kontaktene dine\"</string>\n"
+ + " <string name=\"permlab_readContacts\">\"lese kontaktene dine\"</string>\n"
+ + " <string name=\"permdesc_readContacts\">\"Lar appen lese...</string>"
+ + "</resources>")
+ ));
+
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ManifestTypoDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java
new file mode 100644
index 0000000..f4453c7
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MathDetectorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class MathDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // FloatMath methods were removed in API 23; the test below attempts to
+ // use API 3, but if not available in test environment ensure test doesn't fail.
+ return true;
+ }
+
+ private TestFile mTestFile = java("src/test/bytecode/MathTest.java", ""
+ + "package test.bytecode;\n"
+ + "\n"
+ + "import android.util.FloatMath;\n"
+ + "import static android.util.FloatMath.sin;\n"
+ + "\n"
+ + "//Test data for the MathDetector\n"
+ + "public class MathTest {\n"
+ + " public float floatResult;\n"
+ + " public double doubleResult;\n"
+ + "\n"
+ + " public void floatToFloatTest(float x, double y, int z) {\n"
+ + " floatResult = FloatMath.cos(x);\n"
+ + " floatResult = FloatMath.sin((float) y);\n"
+ + " floatResult = android.util.FloatMath.ceil((float) y);\n"
+ + " System.out.println(FloatMath.floor(x));\n"
+ + " System.out.println(FloatMath.sqrt(z));\n"
+ + " floatResult = sin((float) y);\n"
+ + "\n"
+ + " // No warnings for plain math\n"
+ + " floatResult = (float) Math.cos(x);\n"
+ + " floatResult = (float) java.lang.Math.sin(x);\n"
+ + " }\n"
+ + "}\n");
+
+
+ @Override
+ protected Detector getDetector() {
+ return new MathDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/bytecode/MathTest.java:12: Warning: Use java.lang.Math#cos instead of android.util.FloatMath#cos() since it is faster as of API 8 [FloatMath]\n"
+ + " floatResult = FloatMath.cos(x);\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/bytecode/MathTest.java:13: Warning: Use java.lang.Math#sin instead of android.util.FloatMath#sin() since it is faster as of API 8 [FloatMath]\n"
+ + " floatResult = FloatMath.sin((float) y);\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/bytecode/MathTest.java:14: Warning: Use java.lang.Math#ceil instead of android.util.FloatMath#ceil() since it is faster as of API 8 [FloatMath]\n"
+ + " floatResult = android.util.FloatMath.ceil((float) y);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/bytecode/MathTest.java:15: Warning: Use java.lang.Math#floor instead of android.util.FloatMath#floor() since it is faster as of API 8 [FloatMath]\n"
+ + " System.out.println(FloatMath.floor(x));\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/bytecode/MathTest.java:16: Warning: Use java.lang.Math#sqrt instead of android.util.FloatMath#sqrt() since it is faster as of API 8 [FloatMath]\n"
+ + " System.out.println(FloatMath.sqrt(z));\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/bytecode/MathTest.java:17: Warning: Use java.lang.Math#sin instead of android.util.FloatMath#sin() since it is faster as of API 8 [FloatMath]\n"
+ + " floatResult = sin((float) y);\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "0 errors, 6 warnings\n",
+
+ lintProject(
+ mTestFile,
+ source("project.properties", "target=android-19"),
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml")));
+ }
+
+ public void testNoWarningsPreFroyo() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(mTestFile,
+ source("project.properties", "target=android-3"),
+ copy("apicheck/minsdk2.xml", "AndroidManifest.xml")));
+ }
+
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
new file mode 100644
index 0000000..391ae59
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class MergeRootFrameLayoutDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new MergeRootFrameLayoutDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void testMergeRefFromJava() throws Exception {
+ assertEquals(
+ "res/layout/simple.xml:3: Warning: This <FrameLayout> can be replaced with a <merge> tag [MergeRootFrame]\n" +
+ "<FrameLayout\n" +
+ "^\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "res/layout/simple.xml",
+ "src/test/pkg/ImportFrameActivity.java.txt=>src/test/pkg/ImportFrameActivity.java"
+ ));
+ }
+
+ public void testMergeRefFromInclude() throws Exception {
+ assertEquals(
+ "res/layout/simple.xml:3: Warning: This <FrameLayout> can be replaced with a <merge> tag [MergeRootFrame]\n" +
+ "<FrameLayout\n" +
+ "^\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "res/layout/simple.xml",
+ "res/layout/simpleinclude.xml"
+ ));
+ }
+
+ public void testMergeRefFromIncludeSuppressed() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "res/layout/simple_ignore.xml=>res/layout/simple.xml",
+ "res/layout/simpleinclude.xml"
+ ));
+ }
+
+ public void testNotIncluded() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject("res/layout/simple.xml"));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
new file mode 100644
index 0000000..2118db2
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.MissingClassDetector.INNERCLASS;
+import static com.android.tools.lint.checks.MissingClassDetector.INSTANTIATABLE;
+import static com.android.tools.lint.checks.MissingClassDetector.MISSING;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+ at SuppressWarnings("javadoc")
+public class MissingClassDetectorTest extends AbstractCheckTest {
+ private EnumSet<Scope> mScopes;
+ private Set<Issue> mEnabled = new HashSet<Issue>();
+
+ @Override
+ protected Detector getDetector() {
+ return new MissingClassDetector();
+ }
+
+ @Override
+ protected EnumSet<Scope> getLintScope(List<File> file) {
+ return mScopes;
+ }
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && mEnabled.contains(issue);
+ }
+ };
+ }
+
+ public void test() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING);
+ assertEquals(
+ "AndroidManifest.xml:13: Error: Class referenced in the manifest, test.pkg.TestProvider, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <activity android:name=\".TestProvider\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.TestProvider2, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <service android:name=\"test.pkg.TestProvider2\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:15: Error: Class referenced in the manifest, test.pkg.TestService, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <provider android:name=\".TestService\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:16: Error: Class referenced in the manifest, test.pkg.OnClickActivity, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <receiver android:name=\"OnClickActivity\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:17: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <service android:name=\"TestReceiver\" />\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "5 errors, 0 warnings\n",
+
+ lintProject(
+ "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
+ "bytecode/.classpath=>.classpath"
+ ));
+ }
+
+ public void testIncrementalInManifest() throws Exception {
+ mScopes = Scope.MANIFEST_SCOPE;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath"
+ ));
+ }
+
+ public void testNoWarningBeforeBuild() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath"
+ ));
+ }
+
+ public void testOkClasses() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+ "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
+ "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
+ "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
+ "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
+ "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
+ "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
+ "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
+ "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
+ "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class"
+ ));
+ }
+
+ public void testOkLibraries() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "bytecode/classes.jar=>libs/classes.jar"
+ ));
+ }
+
+ public void testLibraryProjects() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
+ "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
+ "bytecode/.classpath=>.classpath"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+ "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
+ "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
+ "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
+ "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
+ "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class"
+ // Missing TestReceiver: Test should complain about just that class
+ );
+ assertEquals(""
+ + "MasterProject/AndroidManifest.xml:32: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <receiver android:name=\"TestReceiver\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ checkLint(Arrays.asList(master, library)));
+ }
+
+ public void testIndirectLibraryProjects() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
+ "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
+ "bytecode/.classpath=>.classpath"
+ );
+ File library2 = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest2.xml=>AndroidManifest.xml",
+ "multiproject/library2.properties=>project.properties"
+ );
+ File library = getProjectDir("RealLibrary",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+ "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
+ "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
+ "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
+ "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
+ "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class"
+ // Missing TestReceiver: Test should complain about just that class
+ );
+ assertEquals(""
+ + "MasterProject/AndroidManifest.xml:32: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <receiver android:name=\"TestReceiver\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ checkLint(Arrays.asList(master, library2, library)));
+ }
+
+ public void testLibraryWithMissingClass() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING);
+ assertEquals("AndroidManifest.xml:11: Error: Class referenced in the manifest, test.pkg.TestService, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <service android:name=\".TestService\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <service android:name=\".TestService\" />\n"
+ + "\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>"),
+ // This is not the actual class that is present in AndroidManifest.xml
+ copy("bytecode/TestProvider2.class.data",
+ "bin/classes/test/pkg/TestProvider2.class"),
+ copy("bytecode/.classpath", ".classpath"),
+ // Note that the manifestmerger.enabled property is necessary for
+ // the manifest scoped lint detectors to run.
+ source("project.properties", ""
+ + "target=android-14\n"
+ + "android.library=true\n"
+ + "manifestmerger.enabled=true\n"))
+ );
+ }
+
+ public void testInnerClassStatic() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "src/test/pkg/Foo.java:8: Error: This inner class should be static (test.pkg.Foo.Baz) [Instantiatable]\n" +
+ " public class Baz extends Activity {\n" +
+ " ^\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "registration/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "registration/Foo.java.txt=>src/test/pkg/Foo.java",
+ "registration/Foo.class.data=>bin/classes/test/pkg/Foo.class",
+ "registration/Foo$Bar.class.data=>bin/classes/test/pkg/Foo$Bar.class",
+ "registration/Foo$Baz.class.data=>bin/classes/test/pkg/Foo$Baz.class"
+ ));
+ }
+
+ public void testInnerClassPublic() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "src/test/pkg/Foo/Bar.java:6: Error: The default constructor must be public [Instantiatable]\n" +
+ " private Bar() {\n" +
+ " ^\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "registration/AndroidManifestInner.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java",
+ "registration/Bar.class.data=>bin/classes/test/pkg/Foo/Bar.class"
+ ));
+ }
+
+ public void testInnerClass() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <activity\n" +
+ " ^\n" +
+ "AndroidManifest.xml:23: Error: Class referenced in the manifest, test.pkg.Foo.Baz, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <activity\n" +
+ " ^\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "registration/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
+ "registration/Foo.java.txt=>src/test/pkg/Foo.java"
+ ));
+ }
+
+ public void testInnerClass2() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <activity\n" +
+ " ^\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "registration/AndroidManifestInner.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
+ "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
+ ));
+ }
+
+ public void testWrongSeparator1() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <activity\n" +
+ " ^\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "registration/AndroidManifestWrong.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
+ "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
+ ));
+ }
+
+ public void testWrongSeparator2() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+ " <activity\n" +
+ " ^\n" +
+ "AndroidManifest.xml:15: Warning: Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \".Foo.Bar\" with \".Foo$Bar\" [InnerclassSeparator]\n" +
+ " android:name=\".Foo.Bar\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 1 warnings\n",
+
+ lintProject(
+ "registration/AndroidManifestWrong2.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
+ "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
+ ));
+ }
+
+ public void testNoClassesWithLibraries() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "bytecode/GetterTest.jar.data=>libs/foo.jar"
+ ));
+ }
+
+ public void testFragment() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "res/layout/fragment2.xml:7: Error: Class referenced in the layout file, my.app.Fragment, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <fragment\n"
+ + " ^\n"
+ + "res/layout/fragment2.xml:12: Error: Class referenced in the layout file, my.app.MyView, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <view\n"
+ + " ^\n"
+ + "res/layout/fragment2.xml:17: Error: Class referenced in the layout file, my.app.Fragment2, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <fragment\n"
+ + " ^\n"
+ + "src/test/pkg/Foo/Bar.java:6: Error: The default constructor must be public [Instantiatable]\n"
+ + " private Bar() {\n"
+ + " ^\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml",
+ "bytecode/.classpath=>.classpath",
+ "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+ "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
+ "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
+ "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
+ "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
+ "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
+ "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
+ "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
+ "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
+ "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class",
+ "registration/Foo.java.txt=>src/test/pkg/Foo.java",
+ "registration/Foo.class.data=>bin/classes/test/pkg/Foo.class",
+ "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java",
+ "registration/Bar.class.data=>bin/classes/test/pkg/Foo/Bar.class",
+
+ "res/layout/fragment2.xml"
+ ));
+ }
+
+ public void testAnalytics() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "res/values/analytics.xml:13: Error: Class referenced in the analytics file, com.example.app.BaseActivity, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <string name=\"com.example.app.BaseActivity\">Home</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/analytics.xml:14: Error: Class referenced in the analytics file, com.example.app.PrefsActivity, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <string name=\"com.example.app.PrefsActivity\">Preferences</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "res/values/analytics.xml",
+ "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+ "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class"
+ ));
+ }
+
+ public void testCustomView() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "res/layout/customview.xml:21: Error: Class referenced in the layout file, foo.bar.Baz, was not found in the project or the libraries [MissingRegistered]\n"
+ + " <foo.bar.Baz\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "res/layout/customview.xml",
+ "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+ "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class"
+ ));
+ }
+
+ public void testCustomViewInCapitalizedPackage() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "res/layout/customview3.xml",
+ "bytecode/CustomView3.java.txt=>src/test/bytecode/CustomView3.java",
+ "bytecode/CustomView3.class.data=>bin/classes/test/bytecode/CustomView3.class"
+ ));
+ }
+
+ public void testCustomViewNotReferenced() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/CustomView3.java.txt=>src/test/bytecode/CustomView3.java",
+ "bytecode/CustomView3.class.data=>bin/classes/test/bytecode/CustomView3.class"
+ ));
+ }
+
+
+ public void testMissingClass() throws Exception {
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/user_prefs_fragment.xml=>res/layout/user_prefs_fragment.xml",
+ "bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data=>bin/classes/course/examples/DataManagement/PreferenceActivity/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class"
+ ));
+ }
+
+ public void testFragments() throws Exception {
+ mScopes = Scope.MANIFEST_SCOPE;
+ mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS);
+
+ // Ensure that we don't do instantiation checks here since they are handled by
+ // the FragmentDetector
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/FragmentTest$Fragment1.class.data=>bin/classes/test/pkg/FragmentTest$Fragment1.class",
+ "bytecode/FragmentTest$Fragment2.class.data=>bin/classes/test/pkg/FragmentTest$Fragment2.class",
+ "bytecode/FragmentTest$Fragment3.class.data=>bin/classes/test/pkg/FragmentTest$Fragment3.class",
+ "bytecode/FragmentTest$Fragment4.class.data=>bin/classes/test/pkg/FragmentTest$Fragment4.class",
+ "bytecode/FragmentTest$Fragment5.class.data=>bin/classes/test/pkg/FragmentTest$Fragment5.class",
+ "bytecode/FragmentTest$Fragment6.class.data=>bin/classes/test/pkg/FragmentTest$Fragment6.class",
+ "bytecode/FragmentTest$NotAFragment.class.data=>bin/classes/test/pkg/FragmentTest$NotAFragment.class",
+ "bytecode/FragmentTest.java.txt=>src/test/pkg/FragmentTest.java"));
+ }
+
+ public void testHeaders() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=51851
+ mScopes = null;
+ mEnabled = Sets.newHashSet(MISSING, INNERCLASS);
+ assertEquals(""
+ + "res/xml/prefs_headers.xml:3: Error: Class referenced in the preference header file, foo.bar.MyFragment.Missing, was not found in the project or the libraries [MissingRegistered]\n"
+ + "<header\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "bytecode/FragmentTest$Fragment1.class.data=>bin/classes/test/pkg/FragmentTest$Fragment1.class",
+ "bytecode/FragmentTest$Fragment2.class.data=>bin/classes/test/pkg/FragmentTest$Fragment2.class",
+ "bytecode/FragmentTest$Fragment3.class.data=>bin/classes/test/pkg/FragmentTest$Fragment3.class",
+ "bytecode/FragmentTest$Fragment4.class.data=>bin/classes/test/pkg/FragmentTest$Fragment4.class",
+ "bytecode/FragmentTest$Fragment5.class.data=>bin/classes/test/pkg/FragmentTest$Fragment5.class",
+ "bytecode/FragmentTest$Fragment6.class.data=>bin/classes/test/pkg/FragmentTest$Fragment6.class",
+ "bytecode/FragmentTest$NotAFragment.class.data=>bin/classes/test/pkg/FragmentTest$NotAFragment.class",
+ "bytecode/FragmentTest.java.txt=>src/test/pkg/FragmentTest.java",
+ "bytecode/.classpath=>.classpath",
+ "res/xml/prefs_headers.xml"));
+ }
+
+
+ public void testGetOldValue() {
+ assertEquals(".Foo.Bar", MissingClassDetector.getOldValue(INNERCLASS,
+ "Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \".Foo.Bar\" with \".Foo$Bar\" [InnerclassSeparator]",
+ TEXT));
+ }
+
+ public void testGetNewValue() {
+ assertEquals(".Foo$Bar", MissingClassDetector.getNewValue(INNERCLASS,
+ "Use '$' instead of '.' for inner classes (or use only lowercase letters in package names); replace \".Foo.Bar\" with \".Foo$Bar\" [InnerclassSeparator]",
+ TEXT));
+ }
+
+ @Override
+ protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+ @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+ if (issue == INNERCLASS) {
+ assertNotNull(message, MissingClassDetector.getOldValue(issue, message, TEXT));
+ assertNotNull(message, MissingClassDetector.getNewValue(issue, message, TEXT));
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingIdDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingIdDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingIdDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/MissingIdDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NegativeMarginDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NegativeMarginDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NegativeMarginDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NegativeMarginDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NfcTechListDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NfcTechListDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NfcTechListDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NfcTechListDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NonInternationalizedSmsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NonInternationalizedSmsDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NonInternationalizedSmsDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/NonInternationalizedSmsDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OnClickDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OnClickDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OnClickDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OnClickDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverdrawDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverdrawDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverdrawDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverdrawDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideConcreteDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideConcreteDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideConcreteDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideConcreteDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/OverrideDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
new file mode 100644
index 0000000..c2be37d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "override", "MethodMayBeStatic"})
+public class ParcelDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ParcelDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/bytecode/MyParcelable1.java:6: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+ + "public class MyParcelable1 implements Parcelable {\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/MyParcelable1.java.txt=>src/test/bytecode/MyParcelable1.java",
+ "bytecode/MyParcelable2.java.txt=>src/test/bytecode/MyParcelable2.java",
+ "bytecode/MyParcelable3.java.txt=>src/test/bytecode/MyParcelable3.java",
+ "bytecode/MyParcelable4.java.txt=>src/test/bytecode/MyParcelable4.java",
+ "bytecode/MyParcelable5.java.txt=>src/test/bytecode/MyParcelable5.java",
+ "bytecode/MyParcelable1.class.data=>bin/classes/test/bytecode/MyParcelable1.class",
+ "bytecode/MyParcelable2.class.data=>bin/classes/test/bytecode/MyParcelable2.class",
+ "bytecode/MyParcelable2$1.class.data=>bin/classes/test/bytecode/MyParcelable2$1.class",
+ "bytecode/MyParcelable3.class.data=>bin/classes/test/bytecode/MyParcelable3.class",
+ "bytecode/MyParcelable4.class.data=>bin/classes/test/bytecode/MyParcelable4.class",
+ "bytecode/MyParcelable5.class.data=>bin/classes/test/bytecode/MyParcelable5.class"
+ ));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testInterfaceOnSuperClass() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=171522
+ assertEquals(""
+ + "src/test/pkg/ParcelableDemo.java:14: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+ + " private static class JustParcelable implements Parcelable {\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:19: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+ + " private static class JustParcelableSubclass extends JustParcelable {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:22: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+ + " private static class ParcelableThroughAbstractSuper extends AbstractParcelable {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:27: Error: This class implements Parcelable but does not provide a CREATOR field [ParcelCreator]\n"
+ + " private static class ParcelableThroughInterface implements MoreThanParcelable {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ParcelableDemo.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.os.Parcel;\n"
+ + "import android.os.Parcelable;\n"
+ + "\n"
+ + "public class ParcelableDemo {\n"
+ + " private interface MoreThanParcelable extends Parcelable {\n"
+ + " void somethingMore();\n"
+ + " }\n"
+ + "\n"
+ + " private abstract static class AbstractParcelable implements Parcelable {\n"
+ + " }\n"
+ + "\n"
+ + " private static class JustParcelable implements Parcelable {\n"
+ + " public int describeContents() {return 0;}\n"
+ + " public void writeToParcel(Parcel dest, int flags) {}\n"
+ + " }\n"
+ + "\n"
+ + " private static class JustParcelableSubclass extends JustParcelable {\n"
+ + " }\n"
+ + "\n"
+ + " private static class ParcelableThroughAbstractSuper extends AbstractParcelable {\n"
+ + " public int describeContents() {return 0;}\n"
+ + " public void writeToParcel(Parcel dest, int flags) {}\n"
+ + " }\n"
+ + "\n"
+ + " private static class ParcelableThroughInterface implements MoreThanParcelable {\n"
+ + " public int describeContents() {return 0;}\n"
+ + " public void writeToParcel(Parcel dest, int flags) {}\n"
+ + " public void somethingMore() {}\n"
+ + " }\n"
+ + "}")
+ ));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testSpans() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=192841
+ assertEquals("No warnings.",
+
+ lintProject(
+ java("src/test/pkg/TestSpan.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.text.TextPaint;\n"
+ + "import android.text.style.URLSpan;\n"
+ + "\n"
+ + "public class TestSpan extends URLSpan {\n"
+ + " public TestSpan(String url) {\n"
+ + " super(url);\n"
+ + " }\n"
+ + "}")
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionHolderTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionHolderTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionHolderTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionHolderTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java
new file mode 100644
index 0000000..4ce0d07
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PermissionRequirementTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.TAG_PERMISSION;
+import static com.android.tools.lint.checks.PermissionRequirement.REVOCABLE_PERMISSION_NAMES;
+import static com.android.tools.lint.checks.PermissionRequirement.isRevocableSystemPermission;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.checks.PermissionHolder.SetPermissionLookup;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import lombok.ast.BinaryOperator;
+
+public class PermissionRequirementTest extends TestCase {
+ private static ResolvedAnnotation createAnnotation(
+ @NonNull String name,
+ @NonNull ResolvedAnnotation.Value... values) {
+ ResolvedAnnotation annotation = mock(ResolvedAnnotation.class);
+ when(annotation.getName()).thenReturn(name);
+ when(annotation.getValues()).thenReturn(Arrays.asList(values));
+ for (ResolvedAnnotation.Value value : values) {
+ when(annotation.getValue(value.name)).thenReturn(value.value);
+ }
+ return annotation;
+ }
+
+ public void testSingle() {
+ ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("value",
+ "android.permission.ACCESS_FINE_LOCATION");
+ Set<String> emptySet = Collections.emptySet();
+ Set<String> fineSet = Collections.singleton("android.permission.ACCESS_FINE_LOCATION");
+ ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
+ PermissionRequirement req = PermissionRequirement.create(null, annotation);
+ assertTrue(req.isRevocable(new SetPermissionLookup(emptySet)));
+
+ assertFalse(req.isSatisfied(new SetPermissionLookup(emptySet)));
+ assertFalse(req.isSatisfied(new SetPermissionLookup(Collections.singleton(""))));
+ assertTrue(req.isSatisfied(new SetPermissionLookup(fineSet)));
+ assertEquals("android.permission.ACCESS_FINE_LOCATION",
+ req.describeMissingPermissions(new SetPermissionLookup(emptySet)));
+ assertEquals(fineSet, req.getMissingPermissions(new SetPermissionLookup(emptySet)));
+ assertEquals(emptySet, req.getMissingPermissions(new SetPermissionLookup(fineSet)));
+ assertEquals(fineSet, req.getRevocablePermissions(new SetPermissionLookup(emptySet)));
+ assertNull(req.getOperator());
+ assertFalse(req.getChildren().iterator().hasNext());
+ }
+
+ public void testAny() {
+ ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("anyOf",
+ new String[]{"android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.ACCESS_COARSE_LOCATION"});
+ Set<String> emptySet = Collections.emptySet();
+ Set<String> fineSet = Collections.singleton("android.permission.ACCESS_FINE_LOCATION");
+ Set<String> coarseSet = Collections.singleton("android.permission.ACCESS_COARSE_LOCATION");
+ Set<String> bothSet = Sets.newHashSet(
+ "android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.ACCESS_COARSE_LOCATION");
+
+ ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
+ PermissionRequirement req = PermissionRequirement.create(null, annotation);
+ assertTrue(req.isRevocable(new SetPermissionLookup(emptySet)));
+ assertFalse(req.isSatisfied(new SetPermissionLookup(emptySet)));
+ assertFalse(req.isSatisfied(new SetPermissionLookup(Collections.singleton(""))));
+ assertTrue(req.isSatisfied(new SetPermissionLookup(fineSet)));
+ assertTrue(req.isSatisfied(new SetPermissionLookup(coarseSet)));
+ assertEquals(
+ "android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION",
+ req.describeMissingPermissions(new SetPermissionLookup(emptySet)));
+ assertEquals(bothSet, req.getMissingPermissions(new SetPermissionLookup(emptySet)));
+ assertEquals(bothSet, req.getRevocablePermissions(new SetPermissionLookup(emptySet)));
+ assertSame(BinaryOperator.LOGICAL_OR, req.getOperator());
+ }
+
+ public void testAll() {
+ ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("allOf",
+ new String[]{"android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.ACCESS_COARSE_LOCATION"});
+ Set<String> emptySet = Collections.emptySet();
+ Set<String> fineSet = Collections.singleton("android.permission.ACCESS_FINE_LOCATION");
+ Set<String> coarseSet = Collections.singleton("android.permission.ACCESS_COARSE_LOCATION");
+ Set<String> bothSet = Sets.newHashSet(
+ "android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.ACCESS_COARSE_LOCATION");
+
+ ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
+ PermissionRequirement req = PermissionRequirement.create(null, annotation);
+ assertTrue(req.isRevocable(new SetPermissionLookup(emptySet)));
+ assertFalse(req.isSatisfied(new SetPermissionLookup(emptySet)));
+ assertFalse(req.isSatisfied(new SetPermissionLookup(Collections.singleton(""))));
+ assertFalse(req.isSatisfied(new SetPermissionLookup(fineSet)));
+ assertFalse(req.isSatisfied(new SetPermissionLookup(coarseSet)));
+ assertTrue(req.isSatisfied(new SetPermissionLookup(bothSet)));
+ assertEquals(
+ "android.permission.ACCESS_FINE_LOCATION and android.permission.ACCESS_COARSE_LOCATION",
+ req.describeMissingPermissions(new SetPermissionLookup(emptySet)));
+ assertEquals(bothSet, req.getMissingPermissions(new SetPermissionLookup(emptySet)));
+ assertEquals(
+ "android.permission.ACCESS_COARSE_LOCATION",
+ req.describeMissingPermissions(new SetPermissionLookup(fineSet)));
+ assertEquals(coarseSet, req.getMissingPermissions(new SetPermissionLookup(fineSet)));
+ assertEquals(
+ "android.permission.ACCESS_FINE_LOCATION",
+ req.describeMissingPermissions(new SetPermissionLookup(coarseSet)));
+ assertEquals(fineSet, req.getMissingPermissions(new SetPermissionLookup(coarseSet)));
+ assertEquals(bothSet, req.getRevocablePermissions(new SetPermissionLookup(emptySet)));
+ assertSame(BinaryOperator.LOGICAL_AND, req.getOperator());
+ }
+
+ public void testSingleAsArray() {
+ // Annotations let you supply a single string to an array method
+ ResolvedAnnotation.Value values = new ResolvedAnnotation.Value("allOf",
+ "android.permission.ACCESS_FINE_LOCATION");
+ ResolvedAnnotation annotation = createAnnotation(PERMISSION_ANNOTATION, values);
+ assertTrue(PermissionRequirement.create(null, annotation).isSingle());
+ }
+
+ public void testRevocable() {
+ assertTrue(isRevocableSystemPermission("android.permission.ACCESS_FINE_LOCATION"));
+ assertTrue(isRevocableSystemPermission("android.permission.ACCESS_COARSE_LOCATION"));
+ assertFalse(isRevocableSystemPermission("android.permission.UNKNOWN_PERMISSION_NAME"));
+ }
+
+ public void testRevocable2() {
+ assertTrue(new SetPermissionLookup(Collections.<String>emptySet(),
+ Sets.newHashSet("my.permission1", "my.permission2")).isRevocable("my.permission2"));
+ }
+
+ public void testAppliesTo() {
+ ResolvedAnnotation annotation;
+ PermissionRequirement req;
+
+ // No date range applies to permission
+ annotation = createAnnotation(PERMISSION_ANNOTATION,
+ new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"));
+ req = PermissionRequirement.create(null, annotation);
+ assertTrue(req.appliesTo(getHolder(15, 1)));
+ assertTrue(req.appliesTo(getHolder(15, 19)));
+ assertTrue(req.appliesTo(getHolder(15, 23)));
+ assertTrue(req.appliesTo(getHolder(22, 23)));
+ assertTrue(req.appliesTo(getHolder(23, 23)));
+
+ // Permission discontinued in API 23:
+ annotation = createAnnotation(PERMISSION_ANNOTATION,
+ new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"),
+ new ResolvedAnnotation.Value("apis", "..22"));
+ req = PermissionRequirement.create(null, annotation);
+ assertTrue(req.appliesTo(getHolder(15, 1)));
+ assertTrue(req.appliesTo(getHolder(15, 19)));
+ assertTrue(req.appliesTo(getHolder(15, 23)));
+ assertTrue(req.appliesTo(getHolder(22, 23)));
+ assertFalse(req.appliesTo(getHolder(23, 23)));
+
+ // Permission requirement started in API 23
+ annotation = createAnnotation(PERMISSION_ANNOTATION,
+ new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"),
+ new ResolvedAnnotation.Value("apis", "23.."));
+ req = PermissionRequirement.create(null, annotation);
+ assertFalse(req.appliesTo(getHolder(15, 1)));
+ assertFalse(req.appliesTo(getHolder(1, 19)));
+ assertFalse(req.appliesTo(getHolder(15, 22)));
+ assertTrue(req.appliesTo(getHolder(22, 23)));
+ assertTrue(req.appliesTo(getHolder(23, 30)));
+
+ // Permission requirement applied from API 14 through API 18
+ annotation = createAnnotation(PERMISSION_ANNOTATION,
+ new ResolvedAnnotation.Value("value", "android.permission.AUTHENTICATE_ACCOUNTS"),
+ new ResolvedAnnotation.Value("apis", "14..18"));
+ req = PermissionRequirement.create(null, annotation);
+ assertFalse(req.appliesTo(getHolder(1, 5)));
+ assertTrue(req.appliesTo(getHolder(15, 19)));
+ }
+
+ private static PermissionHolder getHolder(int min, int target) {
+ return new PermissionHolder.SetPermissionLookup(Collections.<String>emptySet(),
+ Collections.<String>emptySet(), new AndroidVersion(min, null),
+ new AndroidVersion(target, null));
+ }
+
+ public static void testDbUpToDate() throws Exception {
+ List<String> expected = getDangerousPermissions();
+ if (expected == null) {
+ return;
+ }
+ List<String> actual = Arrays.asList(REVOCABLE_PERMISSION_NAMES);
+ if (!expected.equals(actual)) {
+ System.out.println("Correct list of permissions:");
+ for (String name : expected) {
+ System.out.println(" \"" + name + "\",");
+ }
+ fail("List of revocable permissions has changed:\n" +
+ // Make the diff show what it take to bring the actual results into the
+ // expected results
+ SdkTestCase.getDiff(Joiner.on('\n').join(actual),
+ Joiner.on('\n').join(expected)));
+ }
+ }
+
+ @Nullable
+ private static List<String> getDangerousPermissions() throws IOException {
+ Pattern pattern = Pattern.compile("dangerous");
+ String top = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
+ if (top == null) {
+ top = "/Volumes/android/mnc-dev";
+ }
+
+ // TODO: We should ship this file with the SDK!
+ File file = new File(top, "frameworks/base/core/res/AndroidManifest.xml");
+ if (!file.exists()) {
+ System.out.println("Set $ANDROID_BUILD_TOP to point to the git repository");
+ return null;
+ }
+ boolean passedRuntimeHeader = false;
+ boolean passedInstallHeader = false;
+ String xml = Files.toString(file, Charsets.UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ Set<String> revocable = Sets.newHashSet();
+ if (document != null && document.getDocumentElement() != null) {
+ NodeList children = document.getDocumentElement().getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ short nodeType = child.getNodeType();
+ if (nodeType == Node.COMMENT_NODE) {
+ String comment = child.getNodeValue();
+ if (comment.contains("RUNTIME PERMISSIONS")) {
+ passedRuntimeHeader = true;
+ } else if (comment.contains("INSTALLTIME PERMISSIONS"))
+ passedInstallHeader = true;
+ } else if (passedRuntimeHeader
+ && !passedInstallHeader
+ && nodeType == Node.ELEMENT_NODE
+ && child.getNodeName().equals(TAG_PERMISSION)) {
+ Element element = (Element) child;
+ String protectionLevel = element.getAttributeNS(ANDROID_URI, "protectionLevel");
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (!name.isEmpty() && pattern.matcher(protectionLevel).find()) {
+ revocable.add(name);
+ }
+ }
+ }
+ }
+
+ List<String> expected = Lists.newArrayList(revocable);
+ Collections.sort(expected);
+ return expected;
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
new file mode 100644
index 0000000..e63e6f2
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDatabaseTest.java
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_FEW;
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MANY;
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MULTIPLE_ONE;
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MULTIPLE_TWO;
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_MULTIPLE_ZERO;
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_ONE;
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_TWO;
+import static com.android.tools.lint.checks.PluralsDatabase.FLAG_ZERO;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.few;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.many;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.one;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.two;
+import static com.android.tools.lint.checks.PluralsDatabase.Quantity.zero;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.LocaleManager;
+import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+public class PluralsDatabaseTest extends TestCase {
+ public void testGetRelevant() {
+ PluralsDatabase db = PluralsDatabase.get();
+ assertNull(db.getRelevant("unknown"));
+ EnumSet<Quantity> relevant = db.getRelevant("en");
+ assertNotNull(relevant);
+ assertEquals(1, relevant.size());
+ assertSame(Quantity.one, relevant.iterator().next());
+
+ relevant = db.getRelevant("cs");
+ assertNotNull(relevant);
+ assertEquals(EnumSet.of(Quantity.few, Quantity.one), relevant);
+ }
+
+ public void testFindExamples() {
+ PluralsDatabase db = PluralsDatabase.get();
+
+ //noinspection ConstantConditions
+ assertEquals("1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026",
+ db.findIntegerExamples("sl", Quantity.one));
+
+ assertEquals("1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026",
+ db.findIntegerExamples("ru", Quantity.one));
+ }
+
+ public void testHasMultiValue() {
+ PluralsDatabase db = PluralsDatabase.get();
+
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.one));
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.two));
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.few));
+ assertFalse(db.hasMultipleValuesForQuantity("en", Quantity.many));
+
+ assertTrue(db.hasMultipleValuesForQuantity("br", Quantity.two));
+ assertTrue(db.hasMultipleValuesForQuantity("mk", Quantity.one));
+ assertTrue(db.hasMultipleValuesForQuantity("lv", Quantity.zero));
+ }
+
+ /**
+ * If the lint unit test data/ folder contains a plurals.txt database file,
+ * this test will parse that file and ensure that our current database produces
+ * exactly the same results as those inferred from the file. If not, it will
+ * dump out updated data structures for the database.
+ */
+ public void testDatabaseAccurate() {
+ List<String> languages = new ArrayList<String>(LocaleManager.getLanguageCodes());
+ Collections.sort(languages);
+ PluralsTextDatabase db = PluralsTextDatabase.get();
+ db.ensureInitialized();
+
+ if (db.getSetName("en") == null) {
+ // plurals.txt not found
+ System.out.println("No plurals.txt database included; not checking consistency");
+ return;
+ }
+
+ // Ensure that the two databases (the plurals.txt backed one and our actual
+ // database) fully agree on everything
+ PluralsDatabase pdb = PluralsDatabase.get();
+ for (String language : languages) {
+ if (!Objects.equal(pdb.getRelevant(language), db.getRelevant(language))) {
+ dumpDatabaseTables();
+ assertEquals(language, pdb.getRelevant(language), db.getRelevant(language));
+ }
+ if (db.getSetName(language) == null) {
+ continue;
+ }
+ for (Quantity q : Quantity.values()) {
+ boolean mv1 = pdb.hasMultipleValuesForQuantity(language, q); // binary database
+ boolean mv2 = db.hasMultipleValuesForQuantity(language, q); // text database
+ if (mv1 != mv2) {
+ dumpDatabaseTables();
+ assertEquals(language + " with quantity " + q, mv1, mv2);
+ }
+ if (mv2) {
+ String e1 = pdb.findIntegerExamples(language, q);
+ String e2 = db.findIntegerExamples(language, q);
+ if (!Objects.equal(e1, e2)) {
+ dumpDatabaseTables();
+ assertEquals(language, e1, e2);
+ }
+ }
+ }
+ }
+ }
+
+ private static void dumpDatabaseTables() {
+ List<String> languages = new ArrayList<String>(LocaleManager.getLanguageCodes());
+ Collections.sort(languages);
+ PluralsTextDatabase db = PluralsTextDatabase.get();
+ db.ensureInitialized();
+
+ db.getRelevant("en"); // ensure initialized
+ Map<String,String> languageMap = Maps.newHashMap();
+ Map<String,EnumSet<Quantity>> setMap = Maps.newHashMap();
+ for (String language : languages) {
+ String set = db.getSetName(language);
+ if (set == null) {
+ continue;
+ }
+ EnumSet<Quantity> quantitySet = db.getRelevant(language);
+ if (quantitySet == null) {
+ // No plurals data for this language. For example, in ICU 52, no
+ // plurals data for the "nv" language (Navajo).
+ continue;
+ }
+ assertNotNull(language, quantitySet);
+ setMap.put(set, quantitySet);
+ languageMap.put(set, language); // Could be multiple
+ }
+
+ List<String> setNames = Lists.newArrayList(setMap.keySet());
+ Collections.sort(setNames);
+
+ // Compute uniqueness
+ Map<String,String> sameAs = Maps.newHashMap();
+ for (int i = 0, n = setNames.size(); i < n; i++) {
+ for (int j = i + 1; j < n; j++) {
+ String iSetName = setNames.get(i);
+ String jSetName = setNames.get(j);
+ assertNotNull(iSetName);
+ assertNotNull(jSetName);
+ EnumSet<Quantity> iSet = setMap.get(iSetName);
+ EnumSet<Quantity> jSet = setMap.get(jSetName);
+ assertNotNull(iSet);
+ assertNotNull(jSet);
+ if (iSet.equals(jSet)) {
+ String alias = sameAs.get(iSetName);
+ if (alias != null) {
+ iSetName = alias;
+ }
+ sameAs.put(jSetName, iSetName);
+ break;
+ }
+ }
+ }
+
+ final String indent = " ";
+ StringBuilder sb = new StringBuilder();
+
+ // Multi Value Set names
+ Set<String> sets = Sets.newHashSet();
+ for (String language : languages) {
+ String set = db.getSetName(language);
+ sets.add(set);
+ languageMap.put(set, language); // Could be multiple
+ }
+
+ Map<String,Integer> indices = Maps.newTreeMap();
+ int index = 0;
+ for (String set : setNames) {
+ indices.put(set, index++);
+ }
+
+ // Language indices
+ Map<String,Integer> languageIndices = Maps.newTreeMap();
+ index = 0;
+ for (String language : languages) {
+ String set = db.getSetName(language);
+ if (set == null) {
+ continue;
+ }
+
+ languageIndices.put(language, index++);
+ }
+
+ Map<String, String> zero = computeExamples(db, Quantity.zero, sets, languageMap);
+ Map<String, String> one = computeExamples(db, Quantity.one, sets, languageMap);
+ Map<String, String> two = computeExamples(db, Quantity.two, sets, languageMap);
+
+ // Language map
+ sb.setLength(0);
+ sb.append("/** Set of language codes relevant to plurals data */\n");
+ sb.append("private static final String[] LANGUAGE_CODES = new String[] {\n");
+ int column = 0;
+ index = 0;
+ sb.append(indent);
+ for (String language : languages) {
+ String set = db.getSetName(language);
+ if (set == null) {
+ continue;
+ }
+ sb.append('"').append(language).append("\", ");
+ column++;
+ if (column == 10) {
+ sb.append("\n");
+ sb.append(indent);
+ column = 0;
+ }
+ assertEquals((int)languageIndices.get(language), index);
+ index++;
+ }
+ stripLastComma(sb);
+ sb.append("\n};\n");
+ System.out.println(sb);
+
+ // Quantity map
+ sb.setLength(0);
+ sb.append("/**\n"
+ + " * Relevant flags for each language (corresponding to each language listed\n"
+ + " * in the same position in {@link #LANGUAGE_CODES})\n"
+ + " */\n");
+ sb.append("private static final int[] FLAGS = new int[] {\n");
+ column = 0;
+ sb.append(indent);
+ index = 0;
+ for (String language : languages) {
+ String setName = db.getSetName(language);
+ if (setName == null) {
+ continue;
+ }
+ assertEquals((int)languageIndices.get(language), index);
+
+ // Compute flag
+ int flag = 0;
+ EnumSet<Quantity> relevant = db.getRelevant(language);
+ assertNotNull(relevant);
+ if (relevant.contains(Quantity.zero)) {
+ flag |= FLAG_ZERO;
+ }
+ if (relevant.contains(Quantity.one)) {
+ flag |= FLAG_ONE;
+ }
+ if (relevant.contains(Quantity.two)) {
+ flag |= FLAG_TWO;
+ }
+ if (relevant.contains(Quantity.few)) {
+ flag |= FLAG_FEW;
+ }
+ if (relevant.contains(Quantity.many)) {
+ flag |= FLAG_MANY;
+ }
+ if (zero.containsKey(setName)) {
+ flag |= FLAG_MULTIPLE_ZERO;
+ }
+ if (one.containsKey(setName)) {
+ flag |= FLAG_MULTIPLE_ONE;
+ }
+ if (two.containsKey(setName)) {
+ flag |= FLAG_MULTIPLE_TWO;
+ }
+
+ sb.append(String.format(Locale.US, "0x%04x, ", flag));
+ column++;
+ if (column == 8) {
+ sb.append("\n");
+ sb.append(indent);
+ column = 0;
+ }
+
+ index++;
+ }
+ stripLastComma(sb);
+ sb.append("\n};\n");
+ System.out.println(sb);
+
+ // Switch statement methods for examples
+ printSwitch(db, Quantity.zero, languages, languageIndices, indices, zero);
+ printSwitch(db, Quantity.one, languages, languageIndices, indices, one);
+ printSwitch(db, Quantity.two, languages, languageIndices, indices, two);
+
+ }
+
+ private static String stripLastComma(String s) {
+ StringBuilder stringBuilder = new StringBuilder(s);
+ stripLastComma(stringBuilder);
+ return stringBuilder.toString();
+ }
+
+ private static void stripLastComma(@NonNull StringBuilder sb) {
+ for (int i = sb.length() - 1; i >= 1; i--) {
+ char c = sb.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ if (c == ',') {
+ sb.setLength(i);
+ }
+ break;
+ }
+ }
+ }
+
+ private static Map<String, String> computeExamples(PluralsTextDatabase db, Quantity quantity,
+ Set<String> sets, Map<String, String> languageMap) {
+
+ Map<String, String> setsWithExamples = Maps.newHashMap();
+ for (String set : sets) {
+ String language = languageMap.get(set);
+ String examples = db.findIntegerExamples(language, quantity);
+ if (examples != null && examples.indexOf(',') != -1) {
+ setsWithExamples.put(set, examples);
+ }
+ }
+
+ return setsWithExamples;
+ }
+
+
+ private static void printSwitch(
+ PluralsTextDatabase db,
+ Quantity quantity,
+ List<String> languages,
+ Map<String,Integer> languageIndices,
+ Map<String, Integer> indices,
+ Map<String, String> setsWithExamples) {
+
+ List<String> sorted = new ArrayList<String>(setsWithExamples.keySet());
+ Collections.sort(sorted);
+
+ StringBuilder sb = new StringBuilder();
+ String quantityName = quantity.name();
+ quantityName = Character.toUpperCase(quantityName.charAt(0)) + quantityName.substring(1);
+ sb.append(" @Nullable\n"
+ + " private static String getExampleForQuantity").append(quantityName)
+ .append("(@NonNull String language) {\n"
+ + " int index = getLanguageIndex(language);\n"
+ + " switch (index) {\n");
+
+ for (Map.Entry<String, Integer> entry : indices.entrySet()) {
+ String set = entry.getKey();
+ if (!setsWithExamples.containsKey(set)) {
+ continue;
+ }
+ String example = setsWithExamples.get(set);
+ example = example.replace("…", "\\u2026");
+ sb.append(" // ").append(set).append("\n");
+ for (String language : languages) {
+ String setName = db.getSetName(language);
+ if (set.equals(setName)) {
+ int languageIndex = languageIndices.get(language);
+ sb.append(" case ");
+ sb.append(languageIndex).append(": // ").append(language).append("\n");
+ }
+ }
+
+ sb.append(" return ");
+ sb.append("\"").append(example).append("\"");
+ sb.append(";\n");
+ }
+
+ sb.append(" case -1:\n"
+ + " default:\n"
+ + " return null;\n"
+ + " }\n"
+ + " }\n");
+
+ System.out.println(sb);
+ }
+
+ /**
+ * Plurals database backed by a plurals.txt file from ICU
+ */
+ private static class PluralsTextDatabase {
+ private static final boolean DEBUG = false;
+ private static final EnumSet<Quantity> NONE = EnumSet.noneOf(Quantity.class);
+
+ private static final PluralsTextDatabase sInstance = new PluralsTextDatabase();
+
+ private Map<String, EnumSet<Quantity>> mPlurals;
+ private Map<Quantity, Set<String>> mMultiValueSetNames = Maps.newEnumMap(Quantity.class);
+ private String mDescriptions;
+ private int mRuleSetOffset;
+ private Map<String,String> mSetNamePerLanguage;
+
+ @NonNull
+ public static PluralsTextDatabase get() {
+ return sInstance;
+ }
+
+ @Nullable
+ public EnumSet<Quantity> getRelevant(@NonNull String language) {
+ ensureInitialized();
+ EnumSet<Quantity> set = mPlurals.get(language);
+ if (set == null) {
+ String s = getLocaleData(language);
+ if (s == null) {
+ mPlurals.put(language, NONE);
+ return null;
+ }
+ // Process each item and look for relevance
+
+ set = EnumSet.noneOf(Quantity.class);
+ int length = s.length();
+ for (int offset = 0, end; offset < length; offset = end + 1) {
+ for (; offset < length; offset++) {
+ if (!Character.isWhitespace(s.charAt(offset))) {
+ break;
+ }
+ }
+
+ int begin = s.indexOf('{', offset);
+ if (begin == -1) {
+ break;
+ }
+ end = findBalancedEnd(s, begin);
+ if (end == -1) {
+ end = length;
+ }
+
+ if (s.startsWith("other{", offset)) {
+ // Not included
+ continue;
+ }
+
+ // Make sure the rule references applies to integers:
+ // Rule definition mentions n or i or @integer
+ //
+ // n absolute value of the source number (integer and decimals).
+ // i integer digits of n.
+ // v number of visible fraction digits in n, with trailing zeros.
+ // w number of visible fraction digits in n, without trailing zeros.
+ // f visible fractional digits in n, with trailing zeros.
+ // t visible fractional digits in n, without trailing zeros.
+ boolean appliesToIntegers = false;
+ boolean inQuotes = false;
+ for (int i = begin + 1; i < end - 1; i++) {
+ char c = s.charAt(i);
+ if (c == '"') {
+ inQuotes = !inQuotes;
+ } else if (inQuotes) {
+ if (c == '@') {
+ if (s.startsWith("@integer", i)) {
+ appliesToIntegers = true;
+ break;
+ } else {
+ // @decimal always comes after @integer
+ break;
+ }
+ } else if ((c == 'i' || c == 'n') && Character
+ .isWhitespace(s.charAt(i + 1))) {
+ appliesToIntegers = true;
+ break;
+ }
+ }
+ }
+
+ if (!appliesToIntegers) {
+ if (DEBUG) {
+ System.out.println("Skipping quantity " + s.substring(offset, begin)
+ + " in set for locale " + language + " (" + getSetName(language)
+ + ")");
+ }
+ continue;
+ }
+
+ if (s.startsWith("one{", offset)) {
+ set.add(one);
+ } else if (s.startsWith("few{", offset)) {
+ set.add(few);
+ } else if (s.startsWith("many{", offset)) {
+ set.add(many);
+ } else if (s.startsWith("two{", offset)) {
+ set.add(two);
+ } else if (s.startsWith("zero{", offset)) {
+ set.add(zero);
+ } else {
+ // Unexpected quantity: ignore
+ if (DEBUG) {
+ assert false : s.substring(offset, Math.min(offset + 10, length));
+ }
+ }
+ }
+
+ mPlurals.put(language, set);
+ }
+ return set == NONE ? null : set;
+ }
+
+ public boolean hasMultipleValuesForQuantity(
+ @NonNull String language,
+ @NonNull Quantity quantity) {
+ if (quantity == Quantity.one || quantity == Quantity.two || quantity == Quantity.zero) {
+ ensureInitialized();
+ String setName = getSetName(language);
+ if (setName != null) {
+ Set<String> names = mMultiValueSetNames.get(quantity);
+ assert names != null : quantity;
+ return names.contains(setName);
+ }
+ }
+
+ return false;
+ }
+
+ private void ensureInitialized() {
+ if (mPlurals == null) {
+ initialize();
+ }
+ }
+
+ @SuppressWarnings({"UnnecessaryLocalVariable", "UnusedDeclaration"})
+ private void initialize() {
+ // Sets where more than a single integer maps to the quantity. Take for example
+ // set 10:
+ // set10{
+ // one{
+ // "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81,"
+ // " 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0"
+ // ", 101.0, 1001.0, …"
+ // }
+ // }
+ // Here we see that both "1" and "21" will match the "one" category.
+ // Note that this only applies to integers (since getQuantityString only takes integer)
+ // whereas the plurals data also covers fractions. I was not sure what to do about
+ // set17:
+ // set17{
+ // one{"i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6"}
+ // }
+ // since it looks to me like this only differs from 1 in the fractional part.
+ mSetNamePerLanguage = Maps.newHashMapWithExpectedSize(20);
+ mMultiValueSetNames = Maps.newEnumMap(Quantity.class);
+ Quantity[] quantities = new Quantity[] { Quantity.zero, Quantity.one, Quantity.two };
+ for (Quantity quantity : quantities) {
+ mMultiValueSetNames.put(quantity, Sets.<String>newHashSet());
+ for (String language : LocaleManager.getLanguageCodes()) {
+ String examples = findIntegerExamples(language, quantity);
+ if (examples != null && examples.indexOf(',') != -1) {
+ String setName = getSetName(language);
+ assertNotNull(setName);
+ Set<String> set = mMultiValueSetNames.get(quantity);
+ assertNotNull(set);
+ set.add(setName);
+ }
+ }
+ }
+
+ mPlurals = Maps.newHashMapWithExpectedSize(20);
+ }
+
+ @Nullable
+ public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) {
+ String data = getQuantityData(language, quantity);
+ if (data != null) {
+ int index = data.indexOf("@integer");
+ if (index == -1) {
+ return null;
+ }
+ int start = index + "@integer".length();
+ int end = data.indexOf('@', start);
+ if (end == -1) {
+ end = data.length();
+ }
+ return data.substring(start, end).trim();
+ }
+
+ return null;
+ }
+
+ @NonNull
+ private String getPluralsDescriptions() {
+ if (mDescriptions == null) {
+ InputStream stream = PluralsDatabaseTest.class.getResourceAsStream("data/plurals.txt");
+ if (stream != null) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(stream);
+ mDescriptions = new String(bytes, Charsets.UTF_8);
+ mRuleSetOffset = mDescriptions.indexOf("rules{");
+ if (mRuleSetOffset == -1) {
+ if (DEBUG) {
+ assert false;
+ }
+ mDescriptions = "";
+ mRuleSetOffset = 0;
+ }
+
+ } catch (IOException e) {
+ try {
+ stream.close();
+ } catch (IOException e1) {
+ // Stupid API.
+ }
+ }
+ }
+ if (mDescriptions == null) {
+ mDescriptions = "";
+ }
+ }
+ return mDescriptions;
+ }
+
+ @Nullable
+ public String getQuantityData(@NonNull String language, @NonNull Quantity quantity) {
+ String data = getLocaleData(language);
+ if (data == null) {
+ return null;
+ }
+ String quantityDeclaration = quantity.name() + "{";
+ int quantityStart = data.indexOf(quantityDeclaration);
+ if (quantityStart == -1) {
+ return null;
+ }
+ int quantityEnd = findBalancedEnd(data, quantityStart);
+ if (quantityEnd == -1) {
+ return null;
+ }
+ //String s = data.substring(quantityStart + quantityDeclaration.length(), quantityEnd);
+ StringBuilder sb = new StringBuilder();
+ boolean inString = false;
+ for (int i = quantityStart + quantityDeclaration.length(); i < quantityEnd; i++) {
+ char c = data.charAt(i);
+ if (c == '"') {
+ inString = !inString;
+ } else if (inString) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ @Nullable
+ public String getSetName(@NonNull String language) {
+ String name = mSetNamePerLanguage.get(language);
+ if (name == null) {
+ name = findSetName(language);
+ if (name == null) {
+ name = ""; // Store "" instead of null so we remember search result
+ }
+ mSetNamePerLanguage.put(language, name);
+ }
+
+ return name.isEmpty() ? null : name;
+ }
+
+ @Nullable
+ private String findSetName(@NonNull String language) {
+ String data = getPluralsDescriptions();
+ int index = data.indexOf("locales{");
+ if (index == -1) {
+ return null;
+ }
+ int end = data.indexOf("locales_ordinals{", index + 1);
+ if (end == -1) {
+ return null;
+ }
+ String languageDeclaration = " " + language + "{\"";
+ index = data.indexOf(languageDeclaration);
+ if (index == -1 || index >= end) {
+ return null;
+ }
+ int setEnd = data.indexOf('\"', index + languageDeclaration.length());
+ if (setEnd == -1) {
+ return null;
+ }
+ return data.substring(index + languageDeclaration.length(), setEnd).trim();
+ }
+
+ @Nullable
+ public String getLocaleData(@NonNull String language) {
+ String set = getSetName(language);
+ if (set == null) {
+ return null;
+ }
+ String data = getPluralsDescriptions();
+ int setStart = data.indexOf(set + "{", mRuleSetOffset);
+ if (setStart == -1) {
+ return null;
+ }
+ int setEnd = findBalancedEnd(data, setStart);
+ if (setEnd == -1) {
+ return null;
+ }
+ return data.substring(setStart + set.length() + 1, setEnd);
+ }
+
+ private static int findBalancedEnd(String data, int offset) {
+ int balance = 0;
+ int length = data.length();
+ for (; offset < length; offset++) {
+ char c = data.charAt(offset);
+ if (c == '{') {
+ balance++;
+ } else if (c == '}') {
+ balance--;
+ if (balance == 0) {
+ return offset;
+ }
+ }
+ }
+
+ return -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PreferenceActivityDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PreferenceActivityDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PreferenceActivityDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PreferenceActivityDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateKeyDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java
new file mode 100644
index 0000000..de5d3e7
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PrivateResourceDetectorTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.FN_PUBLIC_TXT;
+import static com.android.SdkConstants.FN_RESOURCE_TEXT;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.Variant;
+import com.android.ide.common.repository.GradleCoordinate;
+import com.android.testutils.TestUtils;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.mockito.stubbing.OngoingStubbing;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+ at SuppressWarnings("javadoc")
+public class PrivateResourceDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new PrivateResourceDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void testPrivateInXml() throws Exception {
+ assertEquals(""
+ + "res/layout/private.xml:11: Warning: The resource @string/my_private_string is marked as private in com.android.tools:test-library [PrivateResource]\n"
+ + " android:text=\"@string/my_private_string\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("res/layout/private.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:id=\"@+id/newlinear\"\n"
+ + " android:orientation=\"vertical\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\">\n"
+ + "\n"
+ + " <TextView\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"@string/my_private_string\" />\n"
+ + "\n"
+ + " <TextView\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"@string/my_public_string\" />\n"
+ + "</LinearLayout>\n")));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testPrivateInJava() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/Private.java:3: Warning: The resource @string/my_private_string is marked as private in com.android.tools:test-library [PrivateResource]\n"
+ + " int x = R.string.my_private_string; // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(java("src/test/pkg/Private.java", ""
+ + "public class Private {\n"
+ + " void test() {\n"
+ + " int x = R.string.my_private_string; // ERROR\n"
+ + " int y = R.string.my_public_string; // OK\n"
+ + " int y = android.R.string.my_private_string; // OK (not in project namespace)\n"
+ + " }\n"
+ + "}\n")));
+ }
+
+ public void testOverride() throws Exception {
+ assertEquals(""
+ + "res/layout/my_private_layout.xml: Warning: Overriding @layout/my_private_layout which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ + "res/values/strings.xml:5: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ + " <string name=\"my_private_string\">String 1</string>\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "res/values/strings.xml:9: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ + " <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "res/values/strings.xml:12: Warning: Overriding @string/my_private_string which is marked as private in com.android.tools:test-library. If deliberate, use tools:override=\"true\", otherwise pick a different name. [PrivateResource]\n"
+ + " <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 4 warnings\n",
+ lintProject(xml("res/values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n"
+ + "\n"
+ + " <string name=\"app_name\">LibraryProject</string>\n"
+ + " <string name=\"my_private_string\">String 1</string>\n"
+ + " <string name=\"my_public_string\">String 2</string>\n"
+ + " <string name=\"string3\"> @my_private_string </string>\n"
+ + " <string name=\"string4\"> @my_public_string </string>\n"
+ + " <item type=\"string\" name=\"my_private_string\">String 1</item>\n"
+ + " <dimen name=\"my_private_string\">String 1</dimen>\n" // unrelated
+ + " <string tools:ignore=\"PrivateResource\" name=\"my_private_string\">String 2</string>\n"
+ + " <string tools:override=\"false\" name=\"my_private_string\">String 2</string>\n"
+ + " <string tools:override=\"true\" name=\"my_private_string\">String 2</string>\n"
+ + "\n"
+ + "</resources>\n"),
+ xml("res/layout/my_private_layout.xml", "<LinearLayout/>"),
+ xml("res/layout/my_public_layout.xml", "<LinearLayout/>")));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testIds() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=183851
+ assertEquals("No warnings.",
+ lintProject(
+ xml("res/layout/private.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:id=\"@+id/title\"\n"
+ + " android:orientation=\"vertical\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"/>\n"),
+ java("src/test/pkg/Private.java", ""
+ + "public class Private {\n"
+ + " void test() {\n"
+ + " int x = R.id.title; // ERROR\n"
+ + " }\n"
+ + "}\n")));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ // First version which supported private resources; this does not
+ // need to track later versions we release
+ return createMockProject("1.3.0-alpha2", 3);
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ try {
+ AndroidLibrary library = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int string my_private_string 0x7f040000\n"
+ + "int string my_public_string 0x7f040001\n"
+ + "int layout my_private_layout 0x7f040002\n"
+ + "int id title 0x7f040003\n",
+ ""
+ + ""
+ + "string my_public_string\n",
+ Collections.<AndroidLibrary>emptyList()
+ );
+ AndroidArtifact artifact = createMockArtifact(
+ Collections.singletonList(library));
+
+ Variant variant = mock(Variant.class);
+ when(variant.getMainArtifact()).thenReturn(artifact);
+ return variant;
+ } catch (Exception e) {
+ fail(e.toString());
+ return null;
+ }
+ }
+ };
+ }
+ };
+ }
+
+ public static AndroidProject createMockProject(String modelVersion, int apiVersion) {
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getApiVersion()).thenReturn(apiVersion);
+ when(project.getModelVersion()).thenReturn(modelVersion);
+
+ return project;
+ }
+
+ public static AndroidArtifact createMockArtifact(List<AndroidLibrary> libraries) {
+ Dependencies dependencies = mock(Dependencies.class);
+ when(dependencies.getLibraries()).thenReturn(libraries);
+
+ AndroidArtifact artifact = mock(AndroidArtifact.class);
+ when(artifact.getDependencies()).thenReturn(dependencies);
+
+ return artifact;
+ }
+
+ public static AndroidLibrary createMockLibrary(String name,
+ String allResources, String publicResources,
+ List<AndroidLibrary> dependencies)
+ throws IOException {
+ final File tempDir = TestUtils.createTempDirDeletedOnExit();
+
+ Files.write(allResources, new File(tempDir, FN_RESOURCE_TEXT), Charsets.UTF_8);
+ File publicTxtFile = new File(tempDir, FN_PUBLIC_TXT);
+ if (publicResources != null) {
+ Files.write(publicResources, publicTxtFile, Charsets.UTF_8);
+ }
+ AndroidLibrary library = mock(AndroidLibrary.class);
+ when(library.getPublicResources()).thenReturn(publicTxtFile);
+ GradleCoordinate c = GradleCoordinate.parseCoordinateString(name);
+ assertNotNull(c);
+ MavenCoordinates coordinates = mock(MavenCoordinates.class);
+ when(coordinates.getGroupId()).thenReturn(c.getGroupId());
+ when(coordinates.getArtifactId()).thenReturn(c.getArtifactId());
+ when(coordinates.getVersion()).thenReturn(c.getRevision());
+ when(library.getResolvedCoordinates()).thenReturn(coordinates);
+ when(library.getBundle()).thenReturn(new File("intermediates" + File.separator +
+ "exploded-aar" + File.separator + name));
+
+ // Work around wildcard capture
+ //when(library.getLibraryDependencies()).thenReturn(dependencies);
+ List libraryDependencies = library.getLibraryDependencies();
+ OngoingStubbing<List> setter = when(libraryDependencies);
+ setter.thenReturn(dependencies);
+ return library;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ProguardDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ProguardDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ProguardDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ProguardDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PropertyFileDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PropertyFileDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PropertyFileDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PropertyFileDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/PxUsageDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ReadParcelableDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ReadParcelableDetectorTest.java
new file mode 100644
index 0000000..611d505
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ReadParcelableDetectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "override", "MethodMayBeStatic"})
+public class ReadParcelableDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ReadParcelableDetector();
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void test() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=196457
+ assertEquals(""
+ + "src/test/pkg/ParcelableDemo.java:10: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+ + " Parcelable error1 = in.readParcelable(null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:11: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+ + " Parcelable[] error2 = in.readParcelableArray(null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:12: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+ + " Bundle error3 = in.readBundle(null);\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:13: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+ + " Object[] error4 = in.readArray(null);\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:14: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+ + " SparseArray error5 = in.readSparseArray(null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:15: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+ + " Object error6 = in.readValue(null);\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:16: Warning: Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example getClass().getClassLoader() instead. [ParcelClassLoader]\n"
+ + " Parcelable error7 = in.readPersistableBundle(null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:17: Warning: Using the default class loader will not work if you are restoring your own classes. Consider using for example readBundle(getClass().getClassLoader()) instead. [ParcelClassLoader]\n"
+ + " Bundle error8 = in.readBundle();\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/ParcelableDemo.java:18: Warning: Using the default class loader will not work if you are restoring your own classes. Consider using for example readPersistableBundle(getClass().getClassLoader()) instead. [ParcelClassLoader]\n"
+ + " Parcelable error9 = in.readPersistableBundle();\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 9 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ParcelableDemo.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.os.Bundle;\n"
+ + "import android.os.Parcel;\n"
+ + "import android.os.Parcelable;\n"
+ + "import android.util.SparseArray;\n"
+ + "\n"
+ + "public class ParcelableDemo {\n"
+ + " private void testParcelable(Parcel in) {\n"
+ + " Parcelable error1 = in.readParcelable(null);\n"
+ + " Parcelable[] error2 = in.readParcelableArray(null);\n"
+ + " Bundle error3 = in.readBundle(null);\n"
+ + " Object[] error4 = in.readArray(null);\n"
+ + " SparseArray error5 = in.readSparseArray(null);\n"
+ + " Object error6 = in.readValue(null);\n"
+ + " Parcelable error7 = in.readPersistableBundle(null);\n"
+ + " Bundle error8 = in.readBundle();\n"
+ + " Parcelable error9 = in.readPersistableBundle();\n"
+ + "\n"
+ + " Parcelable ok = in.readParcelable(getClass().getClassLoader());\n"
+ + " }\n"
+ + "}")
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RecyclerViewDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RecyclerViewDetectorTest.java
new file mode 100644
index 0000000..aead7dc
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RecyclerViewDetectorTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"ClassNameDiffersFromFileName", "MethodMayBeStatic", "SpellCheckingInspection"})
+public class RecyclerViewDetectorTest extends AbstractCheckTest {
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/RecyclerViewTest.java:69: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+ + " public void onBindViewHolder(ViewHolder holder, int position) {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/RecyclerViewTest.java:82: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+ + " public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RecyclerViewTest.java:102: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+ + " public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RecyclerViewTest.java:111: Warning: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later [RecyclerView]\n"
+ + " public void onBindViewHolder(ViewHolder holder, final int position, List<Object> payloads) {\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 4 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/RecyclerViewTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.v7.widget.RecyclerView;\n"
+ + "import android.view.View;\n"
+ + "import android.widget.TextView;\n"
+ + "\n"
+ + "import java.util.List;\n"
+ + "\n"
+ + "@SuppressWarnings({\"ClassNameDiffersFromFileName\", \"unused\"})\n"
+ + "public class RecyclerViewTest {\n"
+ + " // From https://developer.android.com/training/material/lists-cards.html\n"
+ + " public static class Test1 extends RecyclerView.Adapter<Test1.ViewHolder> {\n"
+ + " private String[] mDataset;\n"
+ + " public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+ + " public TextView mTextView;\n"
+ + " public ViewHolder(TextView v) {\n"
+ + " super(v);\n"
+ + " mTextView = v;\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public Test1(String[] myDataset) {\n"
+ + " mDataset = myDataset;\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onBindViewHolder(ViewHolder holder, int position) {\n"
+ + " holder.mTextView.setText(mDataset[position]); // OK\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static class Test2 extends RecyclerView.Adapter<Test2.ViewHolder> {\n"
+ + " public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+ + " public ViewHolder(View v) {\n"
+ + " super(v);\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onBindViewHolder(ViewHolder holder, int position) {\n"
+ + " // OK\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static class Test3 extends RecyclerView.Adapter<Test3.ViewHolder> {\n"
+ + " public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+ + " public ViewHolder(View v) {\n"
+ + " super(v);\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+ + " // OK - final, but not referenced\n\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static class Test4 extends RecyclerView.Adapter<Test4.ViewHolder> {\n"
+ + " private int myCachedPosition;\n"
+ + "\n"
+ + " public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+ + " public ViewHolder(View v) {\n"
+ + " super(v);\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onBindViewHolder(ViewHolder holder, int position) {\n"
+ + " myCachedPosition = position; // ERROR: escapes\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static class Test5 extends RecyclerView.Adapter<Test5.ViewHolder> {\n"
+ + " public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+ + " public ViewHolder(View v) {\n"
+ + " super(v);\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+ + " new Runnable() {\n"
+ + " @Override public void run() {\n"
+ + " System.out.println(position); // ERROR: escapes\n"
+ + " }\n"
+ + " }.run();\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " // https://code.google.com/p/android/issues/detail?id=172335\n"
+ + " public static class Test6 extends RecyclerView.Adapter<Test6.ViewHolder> {\n"
+ + " List<String> myData;\n"
+ + " public static class ViewHolder extends RecyclerView.ViewHolder {\n"
+ + " private View itemView;\n"
+ + " public ViewHolder(View v) {\n"
+ + " super(v);\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onBindViewHolder(ViewHolder holder, final int position) {\n"
+ + " holder.itemView.setOnClickListener(new View.OnClickListener() {\n"
+ + " public void onClick(View view) {\n"
+ + " myData.get(position); // ERROR\n"
+ + " }\n"
+ + " });\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onBindViewHolder(ViewHolder holder, final int position, List<Object> payloads) {\n"
+ + " holder.itemView.setOnClickListener(new View.OnClickListener() {\n"
+ + " public void onClick(View view) {\n"
+ + " myData.get(position); // ERROR\n"
+ + " }\n"
+ + " });\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ java("src/android/support/v7/widget/RecyclerView.java", ""
+ + "package android.support.v7.widget;\n"
+ + "\n"
+ + "import android.content.Context;\n"
+ + "import android.util.AttributeSet;\n"
+ + "import android.view.View;\n"
+ + "import java.util.List;\n"
+ + "\n"
+ + "// Just a stub for lint unit tests\n"
+ + "public class RecyclerView extends View {\n"
+ + " public RecyclerView(Context context, AttributeSet attrs) {\n"
+ + " super(context, attrs);\n"
+ + " }\n"
+ + "\n"
+ + " public abstract static class ViewHolder {\n"
+ + " public ViewHolder(View itemView) {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public abstract static class Adapter<VH extends ViewHolder> {\n"
+ + " public abstract void onBindViewHolder(VH holder, int position);\n"
+ + " public void onBindViewHolder(VH holder, int position, List<Object> payloads) {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"))
+ );
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new RecyclerViewDetector();
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java
new file mode 100644
index 0000000..48aa171
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RegistrationDetectorTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "MethodMayBeStatic"})
+public class RegistrationDetectorTest extends AbstractCheckTest {
+
+ public void testRegistered() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\">\n"
+ + " <application\n"
+ + " android:name=\".MyApplication\">\n"
+ + " <activity android:name=\".TestActivity\" />\n"
+ + " <service android:name=\".TestService\" />\n"
+ + " <provider android:name=\".TestProvider\" />\n"
+ + " <provider android:name=\".TestProvider2\" />\n"
+ + " <receiver android:name=\".TestReceiver\" />\n"
+ + " </application>\n"
+ + "</manifest>\n"),
+ mApplication,
+ mTestActivity,
+ mTestService,
+ mTestProvider,
+ mTestProvider2,
+ mTestReceiver));
+ }
+
+ public void testNotRegistered() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/MyApplication.java:5: Warning: The <application> test.pkg.MyApplication is not registered in the manifest [Registered]\n"
+ + "public class MyApplication extends Application {\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestActivity.java:3: Warning: The <activity> test.pkg.TestActivity is not registered in the manifest [Registered]\n"
+ + "public class TestActivity extends Activity {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestProvider.java:8: Warning: The <provider> test.pkg.TestProvider is not registered in the manifest [Registered]\n"
+ + "public class TestProvider extends ContentProvider {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestProvider2.java:3: Warning: The <provider> test.pkg.TestProvider2 is not registered in the manifest [Registered]\n"
+ + "public class TestProvider2 extends TestProvider {\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestService.java:7: Warning: The <service> test.pkg.TestService is not registered in the manifest [Registered]\n"
+ + "public class TestService extends Service {\n"
+ + " ~~~~~~~~~~~\n"
+ + "0 errors, 5 warnings\n",
+
+ lintProject(
+ // no manifest
+ mApplication,
+ mTestActivity,
+ mTestService,
+ mTestProvider,
+ mTestProvider2,
+ mTestReceiver));
+ }
+
+ public void testNoDot() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\">\n"
+ + " <application>\n"
+ + " <activity android:name=\"TestActivity\" />\n"
+ + " </application>\n"
+ + "</manifest>\n"),
+ mTestActivity));
+ }
+
+ public void testWrongRegistrations() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/MyApplication.java:5: Warning: test.pkg.MyApplication is an <application> but is registered in the manifest as a <service> [Registered]\n"
+ + "public class MyApplication extends Application {\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestActivity.java:3: Warning: test.pkg.TestActivity is an <activity> but is registered in the manifest as a <receiver> [Registered]\n"
+ + "public class TestActivity extends Activity {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestProvider.java:8: Warning: test.pkg.TestProvider is a <provider> but is registered in the manifest as an <activity> [Registered]\n"
+ + "public class TestProvider extends ContentProvider {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestProvider2.java:3: Warning: test.pkg.TestProvider2 is a <provider> but is registered in the manifest as a <service> [Registered]\n"
+ + "public class TestProvider2 extends TestProvider {\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestReceiver.java:7: Warning: test.pkg.TestReceiver is a <receiver> but is registered in the manifest as a <service> [Registered]\n"
+ + "public class TestReceiver extends BroadcastReceiver {\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/TestService.java:7: Warning: test.pkg.TestService is a <service> but is registered in the manifest as a <provider> [Registered]\n"
+ + "public class TestService extends Service {\n"
+ + " ~~~~~~~~~~~\n"
+ + "0 errors, 6 warnings\n",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\">\n"
+ + " <application\n"
+ + " android:name=\".TestActivity\">\n"
+ + " <!-- These registrations are bogus (wrong type) -->\n"
+ + " <activity android:name=\".TestProvider\" />\n"
+ + " <service android:name=\"test.pkg.TestProvider2\" />\n"
+ + " <provider android:name=\".TestService\" />\n"
+ + " <receiver android:name=\".TestActivity\" />\n"
+ + " <service android:name=\".TestReceiver\" />\n"
+ + " <service android:name=\".MyApplication\" />\n"
+ + " </application>\n"
+ + "</manifest>\n"),
+ mApplication,
+ mTestActivity,
+ mTestService,
+ mTestProvider,
+ mTestProvider2,
+ mTestReceiver));
+ }
+
+ public void testLibraryProjects() throws Exception {
+ // If a library project provides additional activities, it is not an error to
+ // not register all of those here
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ // Master project
+ source("project.properties", "android.library.reference.1=../LibraryProject2"),
+ // Library project
+ source("../LibraryProject2/project.properties", "android.library=true"),
+
+ java("../LibraryProject2/src/test/pkg/TestActivity.java", ""
+ + "package test.pkg;\n"
+ + "import android.app.Activity;\n"
+ + "public class TestActivity extends Activity {\n"
+ + "}\n")
+ ));
+ }
+
+ public void testSkipReceivers() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(java("src/test/pkg/MyReceiver.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.Activity;\n"
+ + "import android.content.BroadcastReceiver;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "public class MyReceiver extends BroadcastReceiver {\n"
+ + " @Override\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " }\n"
+ + "\n"
+ + " private static class MyActivity extends Activity {\n"
+ + " }\n"
+ + "}\n")));
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new RegistrationDetector();
+ }
+
+ private TestFile mTestActivity = java("src/test/pkg/TestActivity.java", ""
+ + "package test.pkg;\n"
+ + "import android.app.Activity;\n"
+ + "public class TestActivity extends Activity {\n"
+ + "}\n");
+
+ private TestFile mTestService = java("src/test/pkg/TestService.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.Service;\n"
+ + "import android.content.Intent;\n"
+ + "import android.os.IBinder;\n"
+ + "\n"
+ + "public class TestService extends Service {\n"
+ + "\n"
+ + " @Override\n"
+ + " public IBinder onBind(Intent intent) {\n"
+ + " return null;\n"
+ + " }\n"
+ + "\n"
+ + "}\n");
+
+ private TestFile mTestProvider = java("src/test/pkg/TestProvider.java", "package test.pkg;\n"
+ + "\n"
+ + "import android.content.ContentProvider;\n"
+ + "import android.content.ContentValues;\n"
+ + "import android.database.Cursor;\n"
+ + "import android.net.Uri;\n"
+ + "\n"
+ + "public class TestProvider extends ContentProvider {\n"
+ + " @Override\n"
+ + " public int delete(Uri uri, String selection, String[] selectionArgs) {\n"
+ + " return 0;\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public String getType(Uri uri) {\n"
+ + " return null;\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public Uri insert(Uri uri, ContentValues values) {\n"
+ + " return null;\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public boolean onCreate() {\n"
+ + " return false;\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public Cursor query(Uri uri, String[] projection, String selection,\n"
+ + " String[] selectionArgs, String sortOrder) {\n"
+ + " return null;\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public int update(Uri uri, ContentValues values, String selection,\n"
+ + " String[] selectionArgs) {\n"
+ + " return 0;\n"
+ + " }\n"
+ + "}\n");
+
+ private TestFile mTestProvider2 = java("src/test/pkg/TestProvider2.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "public class TestProvider2 extends TestProvider {\n"
+ + "}\n");
+
+ private TestFile mTestReceiver = java("src/test/pkg/TestReceiver.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.BroadcastReceiver;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "public class TestReceiver extends BroadcastReceiver {\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " }\n"
+ + "\n"
+ + " // Anonymous classes should NOT be counted as a must-register\n"
+ + " private BroadcastReceiver dummy() {\n"
+ + " return new BroadcastReceiver() {\n"
+ + " @Override\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " }\n"
+ + " };\n"
+ + " }\n"
+ + "}\n");
+
+ private TestFile mApplication = java("src/test/pkg/MyApplication.java", ""
+ + "package test.pkg;\n"
+ +"\n"
+ +"import android.app.Application;\n"
+ +"\n"
+ +"public class MyApplication extends Application {\n"
+ +"}\n");
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java
new file mode 100644
index 0000000..b1cb92c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RelativeOverlapDetectorTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class RelativeOverlapDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new RelativeOverlapDetector();
+ }
+
+ public void testOneOverlap() throws Exception {
+ assertEquals(
+ "res/layout/relative_overlap.xml:17: Warning: @id/label2 can overlap @id/label1 if @string/label1_text, @string/label2_text grow due to localized text expansion [RelativeOverlap]\n" +
+ " <TextView\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n",
+ lintFiles("res/layout/relative_overlap.xml"));
+ }
+
+ public void testOneOverlapPercent() throws Exception {
+ assertEquals(""
+ + "res/layout/relative_percent_overlap.xml:17: Warning: @id/label2 can overlap @id/label1 if @string/label1_text, @string/label2_text grow due to localized text expansion [RelativeOverlap]\n"
+ + " <TextView\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(xml("res/layout/relative_percent_overlap.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:id=\"@+id/container\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:orientation=\"vertical\">\n"
+ + " <android.support.percent.PercentRelativeLayout\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"wrap_content\">\n"
+ + " <TextView\n"
+ + " android:id=\"@+id/label1\"\n"
+ + " android:layout_alignParentLeft=\"true\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"@string/label1_text\"\n"
+ + " android:ellipsize=\"end\" />\n"
+ + " <TextView\n"
+ + " android:id=\"@+id/label2\"\n"
+ + " android:layout_alignParentRight=\"true\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"@string/label2_text\"\n"
+ + " android:ellipsize=\"end\" />\n"
+ + " <TextView\n"
+ + " android:id=\"@+id/circular1\"\n"
+ + " android:layout_alignParentBottom=\"true\"\n"
+ + " android:layout_toRightOf=\"@+id/circular2\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"@string/label1_text\"\n"
+ + " android:ellipsize=\"end\" />\n"
+ + " <TextView\n"
+ + " android:id=\"@id/circular2\"\n"
+ + " android:layout_alignParentBottom=\"true\"\n"
+ + " android:layout_toRightOf=\"@id/circular1\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"@string/label2_text\"\n"
+ + " android:ellipsize=\"end\" />\n"
+ + " <TextView\n"
+ + " android:id=\"@id/circular3\"\n"
+ + " android:layout_alignParentBottom=\"true\"\n"
+ + " android:layout_toRightOf=\"@id/circular1\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:text=\"@string/label2_text\"\n"
+ + " android:ellipsize=\"end\" />\n"
+ + " </android.support.percent.PercentRelativeLayout>\n"
+ + "</LinearLayout>\n")));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
new file mode 100644
index 0000000..0d8f9d5
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RequiredAttributeDetectorTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+import java.io.File;
+
+ at SuppressWarnings("javadoc")
+public class RequiredAttributeDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new RequiredAttributeDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void test() throws Exception {
+ // Simple: Only consider missing attributes in the layout xml file
+ // (though skip warnings on <merge> tags and under <GridLayout>
+ assertEquals(
+ "res/layout/size.xml:13: Error: The required layout_height attribute is missing [RequiredSize]\n" +
+ " <RadioButton\n" +
+ " ^\n" +
+ "res/layout/size.xml:18: Error: The required layout_width attribute is missing [RequiredSize]\n" +
+ " <EditText\n" +
+ " ^\n" +
+ "res/layout/size.xml:23: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
+ " <EditText\n" +
+ " ^\n" +
+ "3 errors, 0 warnings\n",
+
+ lintProject("res/layout/size.xml"));
+ }
+
+ public void test2() throws Exception {
+ // Consider styles (specifying sizes) and includes (providing sizes for the root tags)
+ assertEquals(
+ "res/layout/size2.xml:9: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
+ " <Button\n" +
+ " ^\n" +
+ "res/layout/size2.xml:18: Error: The required layout_height attribute is missing [RequiredSize]\n" +
+ " <Button\n" +
+ " ^\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/layout/size2.xml",
+ "res/layout/sizeincluded.xml",
+ "res/values/sizestyles.xml"
+ ));
+ }
+
+ public void testPercent() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=198432
+ // Don't flag missing layout_width in PercentFrameLayout or PercentRelativeLayout
+ assertEquals(""
+ + "res/layout/test.xml:28: Error: The required layout_width or layout_widthPercent and layout_height or layout_heightPercent attributes are missing [RequiredSize]\n"
+ + " <View />\n"
+ + " ~~~~~~~~\n"
+ + "res/layout/test.xml:30: Error: The required layout_width or layout_widthPercent attribute is missing [RequiredSize]\n"
+ + " <View\n"
+ + " ^\n"
+ + "res/layout/test.xml:34: Error: The required layout_height or layout_heightPercent attribute is missing [RequiredSize]\n"
+ + " <View\n"
+ + " ^\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(xml("res/layout/test.xml", ""
+ + "<merge xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ + " xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n"
+ + " <android.support.percent.PercentFrameLayout\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"\n"
+ + " >\n"
+ + " <View\n"
+ + " app:layout_widthPercent=\"50%\"\n"
+ + " app:layout_heightPercent=\"50%\"/>\n"
+ + " <View\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " app:layout_marginStartPercent=\"25%\"\n"
+ + " app:layout_marginEndPercent=\"25%\"/>\n"
+ + " <View\n"
+ + " android:id=\"@+id/textview2\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " app:layout_widthPercent=\"60%\"/>\n"
+ + " </android.support.percent.PercentFrameLayout>"
+ + "\n"
+ + " <android.support.percent.PercentRelativeLayout\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\">\n"
+ + " <View\n"
+ + " android:layout_gravity=\"center\"\n"
+ + " app:layout_widthPercent=\"50%\"\n"
+ + " app:layout_heightPercent=\"50%\"/>\n"
+ + " <!-- Errors -->\n"
+ + " <!-- Missing both -->\n"
+ + " <View />\n"
+ + " <!-- Missing width -->\n"
+ + " <View\n"
+ + " android:layout_gravity=\"center\"\n"
+ + " app:layout_heightPercent=\"50%\"/>\n"
+ + " <!-- Missing height -->\n"
+ + " <View\n"
+ + " android:layout_gravity=\"center\"\n"
+ + " app:layout_widthPercent=\"50%\"/>\n"
+ + "\n"
+ + " </android.support.percent.PercentRelativeLayout>\n"
+ + "\n"
+ + "</merge>")));
+ }
+
+ public void testInflaters() throws Exception {
+ // Consider java inflation
+ assertEquals(
+ "res/layout/size5.xml:2: Error: The required layout_width and layout_height attributes are missing [RequiredSize]\n" +
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ "^\n" +
+ "1 errors, 0 warnings\n",
+
+ lintProject(
+ "src/test/pkg/InflaterTest.java.txt=>src/test/pkg/InflaterTest.java",
+ "res/layout/sizeincluded.xml=>res/layout/size1.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size2.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size3.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size4.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size5.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size6.xml",
+ "res/layout/sizeincluded.xml=>res/layout/size7.xml"
+ ));
+ }
+
+ public void testRequestFocus() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=38700
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/layout/edit_type.xml"
+ ));
+ }
+
+ public void testFrameworkStyles() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=38958
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/layout/listseparator.xml"
+ ));
+ }
+
+ public void testThemeStyles() throws Exception {
+ // Check that we don't complain about cases where the size is defined in a theme
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/layout/size.xml",
+ "res/values/themes.xml"
+ ));
+ }
+
+ public void testThemeStyles2() throws Exception {
+ // Check that we don't complain about cases where the size is defined in a theme
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/layout/size.xml",
+ "res/values/themes2.xml"
+ ));
+ }
+
+ public void testHasLayoutVariations() throws Exception {
+ File projectDir = getProjectDir(null,
+ copy("res/layout/size.xml"),
+ copy("res/layout/size.xml", "res/layout-land/size.xml"),
+ copy("res/layout/size.xml", "res/layout/size2.xml"));
+ assertTrue(RequiredAttributeDetector.hasLayoutVariations(
+ new File(projectDir, "res/layout/size.xml".replace('/', File.separatorChar))));
+ assertTrue(RequiredAttributeDetector.hasLayoutVariations(
+ new File(projectDir, "res/layout-land/size.xml".replace('/', File.separatorChar))));
+ assertFalse(RequiredAttributeDetector.hasLayoutVariations(
+ new File(projectDir, "res/layout/size2.xml".replace('/', File.separatorChar))));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourceCycleDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourceCycleDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourceCycleDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourceCycleDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
new file mode 100644
index 0000000..e34c012
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ResourcePrefixDetectorTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+
+import java.io.File;
+import java.util.Arrays;
+
+public class ResourcePrefixDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new ResourcePrefixDetector();
+ }
+
+ public void testResourceFiles() throws Exception {
+ assertEquals(""
+ + "res/drawable-mdpi/frame.png: Error: Resource named 'frame' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_frame' ? [ResourceName]\n"
+ + "res/layout/layout1.xml:2: Error: Resource named 'layout1' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_layout1' ? [ResourceName]\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "res/menu/menu.xml:2: Error: Resource named 'menu' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_menu' ? [ResourceName]\n"
+ + "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
+ + "^\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ "res/layout/layout1.xml",
+ "res/menu/menu.xml",
+ "res/layout/layout1.xml=>res/layout/unit_test_prefix_ok.xml",
+ "res/drawable-mdpi/frame.png",
+ "res/drawable-mdpi/frame.png=>res/drawable/unit_test_prefix_ok1.png",
+ "res/drawable-mdpi/frame.png=>res/drawable/unit_test_prefix_ok2.9.png"
+ ));
+ }
+
+ public void testValues() throws Exception {
+ assertEquals(""
+ + "res/values/customattr.xml:2: Error: Resource named 'ContentFrame' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_ContentFrame' ? [ResourceName]\n"
+ + " <declare-styleable name=\"ContentFrame\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/customattr.xml:3: Error: Resource named 'content' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_content' ? [ResourceName]\n"
+ + " <attr name=\"content\" format=\"reference\" />\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "res/values/customattr.xml:4: Error: Resource named 'contentId' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_contentId' ? [ResourceName]\n"
+ + " <attr name=\"contentId\" format=\"reference\" />\n"
+ + " ~~~~~~~~~~~~~~~~\n"
+ + "res/layout/customattrlayout.xml:2: Error: Resource named 'customattrlayout' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_customattrlayout' ? [ResourceName]\n"
+ + "<foo.bar.ContentFrame\n"
+ + "^\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/customattr.xml",
+ "res/layout/customattrlayout.xml",
+ "unusedR.java.txt=>gen/my/pkg/R.java",
+ "AndroidManifest.xml"));
+ }
+
+ public void testMultiProject() throws Exception {
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>res/values/strings.xml"
+ );
+ assertEquals(""
+ + "LibraryProject/res/values/strings.xml:4: Error: Resource named 'app_name' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_app_name' ? [ResourceName]\n"
+ + " <string name=\"app_name\">LibraryProject</string>\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "LibraryProject/res/values/strings.xml:5: Error: Resource named 'string1' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string1' ? [ResourceName]\n"
+ + " <string name=\"string1\">String 1</string>\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "LibraryProject/res/values/strings.xml:6: Error: Resource named 'string2' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string2' ? [ResourceName]\n"
+ + " <string name=\"string2\">String 2</string>\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "LibraryProject/res/values/strings.xml:7: Error: Resource named 'string3' does not start with the project's resource prefix 'unit_test_prefix_'; rename to 'unit_test_prefix_string3' ? [ResourceName]\n"
+ + " <string name=\"string3\">String 3</string>\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ checkLint(Arrays.asList(master, library)).replace("/TESTROOT/",""));
+ }
+
+ public void testSuppressGeneratedRs() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ copy("res/layout/layout1.xml", "res/raw/blend.bc")
+ ));
+
+ }
+
+ // TODO: Test suppressing root level tag
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getResourcePrefix()).thenReturn("unit_test_prefix_");
+ return project;
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RestrictionsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RestrictionsDetectorTest.java
new file mode 100644
index 0000000..16a05c0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RestrictionsDetectorTest.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.RestrictionsDetector.MAX_NESTING_DEPTH;
+import static com.android.tools.lint.checks.RestrictionsDetector.MAX_NUMBER_OF_NESTED_RESTRICTIONS;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class RestrictionsDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new RestrictionsDetector();
+ }
+
+ public void testSample() throws Exception {
+ // Sample from https://developer.android.com/samples/AppRestrictionSchema/index.html
+ // We expect no warnings.
+ assertEquals("No warnings.",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.android.apprestrictionschema\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\">\n"
+ + " \n"
+ + " <!-- uses-sdk android:minSdkVersion=\"21\" android:targetSdkVersion=\"21\" /-->\n"
+ + " \n"
+ + " <application\n"
+ + " android:allowBackup=\"true\"\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:theme=\"@style/AppTheme\">\n"
+ + " \n"
+ + " <meta-data\n"
+ + " android:name=\"android.content.APP_RESTRICTIONS\"\n"
+ + " android:resource=\"@xml/app_restrictions\" />\n"
+ + " \n"
+ + " <activity\n"
+ + " android:name=\".MainActivity\"\n"
+ + " android:label=\"@string/app_name\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + " \n"
+ + " \n"
+ + "</manifest>"),
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " \n"
+ + " <!--\n"
+ + " Refer to the javadoc of RestrictionsManager for detail of this file.\n"
+ + " https://developer.android.com/reference/android/content/RestrictionsManager.html\n"
+ + " -->\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@bool/default_can_say_hello\"\n"
+ + " android:description=\"@string/description_can_say_hello\"\n"
+ + " android:key=\"can_say_hello\"\n"
+ + " android:restrictionType=\"bool\"\n"
+ + " android:title=\"@string/title_can_say_hello\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@string/default_message\"\n"
+ + " android:description=\"@string/description_message\"\n"
+ + " android:key=\"message\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:title=\"@string/title_message\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@integer/default_number\"\n"
+ + " android:description=\"@string/description_number\"\n"
+ + " android:key=\"number\"\n"
+ + " android:restrictionType=\"integer\"\n"
+ + " android:title=\"@string/title_number\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@string/default_rank\"\n"
+ + " android:description=\"@string/description_rank\"\n"
+ + " android:entries=\"@array/entries_rank\"\n"
+ + " android:entryValues=\"@array/entry_values_rank\"\n"
+ + " android:key=\"rank\"\n"
+ + " android:restrictionType=\"choice\"\n"
+ + " android:title=\"@string/title_rank\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@array/default_approvals\"\n"
+ + " android:description=\"@string/description_approvals\"\n"
+ + " android:entries=\"@array/entries_approvals\"\n"
+ + " android:entryValues=\"@array/entry_values_approvals\"\n"
+ + " android:key=\"approvals\"\n"
+ + " android:restrictionType=\"multi-select\"\n"
+ + " android:title=\"@string/title_approvals\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@string/default_secret_code\"\n"
+ + " android:description=\"@string/description_secret_code\"\n"
+ + " android:key=\"secret_code\"\n"
+ + " android:restrictionType=\"hidden\"\n"
+ + " android:title=\"@string/title_secret_code\"/>\n"
+ + " \n"
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testMissingRequiredAttributes() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:key [ValidRestrictions]\n"
+ + " <restriction />\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:restrictionType [ValidRestrictions]\n"
+ + " <restriction />\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:title [ValidRestrictions]\n"
+ + " <restriction />\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction />\n"
+ + "</restrictions>")
+ ));
+ }
+
+ public void testNewSample() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction android:key=\"key_bool\"\n"
+ + " android:restrictionType=\"bool\"\n"
+ + " android:title=\"@string/title_bool\"\n"
+ + " android:description=\"@string/desc_bool\"\n"
+ + " android:defaultValue=\"true\"\n"
+ + " />\n"
+ + " <restriction android:key=\"key_int\"\n"
+ + " android:restrictionType=\"integer\"\n"
+ + " android:title=\"@string/title_int\"\n"
+ + " android:defaultValue=\"15\"\n"
+ + " />\n"
+ + " <restriction android:key=\"key_string\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:defaultValue=\"@string/string_value\"\n"
+ + " android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+ + " />\n"
+ + " <restriction android:key=\"components\"\n"
+ + " android:restrictionType=\"bundle_array\"\n"
+ + " android:title=\"@string/title_bundle_array\"\n"
+ + " android:description=\"@string/desc_bundle_array\">\n"
+ + " <restriction android:restrictionType=\"bundle\"\n"
+ + " android:key=\"someKey\"\n"
+ + " android:title=\"@string/title_bundle_comp\"\n"
+ + " android:description=\"@string/desc_bundle_comp\">\n"
+ + " <restriction android:key=\"enabled\"\n"
+ + " android:restrictionType=\"bool\"\n"
+ + " android:defaultValue=\"true\"\n"
+ + " android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+ + " />\n"
+ + " <restriction android:key=\"name\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+ + " />\n"
+ + " </restriction>\n"
+ + "\n"
+ + " </restriction>\n"
+ + " <restriction android:key=\"connection_settings\"\n"
+ + " android:restrictionType=\"bundle\"\n"
+ + " android:title=\"@string/title_bundle\"\n"
+ + " android:description=\"@string/desc_bundle\">\n"
+ + " <restriction android:key=\"max_wait_time_ms\"\n"
+ + " android:restrictionType=\"integer\"\n"
+ + " android:title=\"@string/title_int\"\n"
+ + " android:defaultValue=\"1000\"\n"
+ + " />\n"
+ + " <restriction android:key=\"host\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:title=\"@string/missing_title\"\n" // MISSING IN SAMPLE!
+ + " />\n"
+ + " </restriction>\n"
+ + "</restrictions>\n")
+ ));
+ }
+
+ public void testMissingRequiredAttributesForChoice() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:entries [ValidRestrictions]\n"
+ + " <restriction\n"
+ + " ^\n"
+ + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:entryValues [ValidRestrictions]\n"
+ + " <restriction\n"
+ + " ^\n"
+ + "2 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:description=\"@string/description_number\"\n"
+ + " android:key=\"number\"\n"
+ + " android:restrictionType=\"choice\"\n"
+ + " android:title=\"@string/title_number\"/>\n"
+ + "</restrictions>")
+ ));
+ }
+
+ public void testMissingRequiredAttributesForHidden() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:2: Error: Missing required attribute android:defaultValue [ValidRestrictions]\n"
+ + " <restriction\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:description=\"@string/description_number\"\n"
+ + " android:key=\"number\"\n"
+ + " android:restrictionType=\"hidden\"\n"
+ + " android:title=\"@string/title_number\"/>\n"
+ + "</restrictions>")
+ ));
+ }
+
+ public void testValidNumber() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:3: Error: Invalid number [ValidRestrictions]\n"
+ + " android:defaultValue=\"abc\"\n"
+ + " ~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:defaultValue=\"abc\"\n" // ERROR
+ + " android:description=\"@string/description_number\"\n"
+ + " android:key=\"message1\"\n"
+ + " android:restrictionType=\"integer\"\n"
+ + " android:title=\"@string/title_number\"/>\n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@integer/default_number\"\n" // OK
+ + " android:description=\"@string/description_message\"\n"
+ + " android:key=\"message2\"\n"
+ + " android:restrictionType=\"integer\"\n"
+ + " android:title=\"@string/title_number2\"/>\n"
+ + " <restriction\n"
+ + " android:defaultValue=\"123\"\n" // OK
+ + " android:description=\"@string/description_message2\"\n"
+ + " android:key=\"message3\"\n"
+ + " android:restrictionType=\"integer\"\n"
+ + " android:title=\"@string/title_number3\"/>\n"
+ + "</restrictions>")
+ ));
+ }
+
+ public void testUnexpectedTag() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:3: Error: Unexpected tag <wrongtag>, expected <restriction> [ValidRestrictions]\n"
+ + " <wrongtag />\n"
+ + " ~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <!-- Comments are okay -->\n"
+ + " <wrongtag />\n"
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testLocalizedKey() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:5: Error: Keys cannot be localized, they should be specified with a string literal [ValidRestrictions]\n"
+ + " android:key=\"@string/can_say_hello\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@bool/default_can_say_hello\"\n"
+ + " android:description=\"@string/description_can_say_hello\"\n"
+ + " android:key=\"@string/can_say_hello\"\n"
+ + " android:restrictionType=\"bool\"\n"
+ + " android:title=\"@string/title_can_say_hello\"/>\n"
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testDuplicateKeys() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:19: Error: Duplicate key can_say_hello [ValidRestrictions]\n"
+ + " android:key=\"can_say_hello\"\n"
+ + " ~~~~~~~~~~~~~\n"
+ + " res/xml/app_restrictions.xml:5: Previous use of key here\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@bool/default_can_say_hello\"\n"
+ + " android:description=\"@string/description_can_say_hello\"\n"
+ + " android:key=\"can_say_hello\"\n"
+ + " android:restrictionType=\"bool\"\n"
+ + " android:title=\"@string/title_can_say_hello\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@string/default_message\"\n"
+ + " android:description=\"@string/description_message\"\n"
+ + " android:key=\"message\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:title=\"@string/title_message\"/>\n"
+ + " \n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@integer/default_number\"\n"
+ + " android:description=\"@string/description_number\"\n"
+ + " android:key=\"can_say_hello\"\n" // ERROR: Duplicate
+ + " android:restrictionType=\"integer\"\n"
+ + " android:title=\"@string/title_number\"/>\n"
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testNoDefaultValueForBundles() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:3: Error: Restriction type bundle_array should not have a default value [ValidRestrictions]\n"
+ + " android:defaultValue=\"@string/default_message\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@string/default_message\"\n"
+ + " android:description=\"@string/description_message\"\n"
+ + " android:key=\"message\"\n"
+ + " android:restrictionType=\"bundle_array\"\n"
+ + " android:title=\"@string/title_message\">\n"
+ + " <restriction\n"
+ + " android:defaultValue=\"@bool/default_can_say_hello\"\n"
+ + " android:description=\"@string/description_can_say_hello\"\n"
+ + " android:key=\"can_say_hello\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:title=\"@string/title_can_say_hello\"/>\n"
+ + " </restriction>"
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testNoChildrenForBundle() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:2: Error: Restriction type bundle should have at least one nested restriction [ValidRestrictions]\n"
+ + " <restriction\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:description=\"@string/description_message\"\n"
+ + " android:key=\"message\"\n"
+ + " android:restrictionType=\"bundle\"\n"
+ + " android:title=\"@string/title_message\"/>\n"
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testNoChildrenForBundleArray() throws Exception {
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:2: Error: Expected exactly one child for restriction of type bundle_array [ValidRestrictions]\n"
+ + " <restriction\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + " <restriction\n"
+ + " android:description=\"@string/description_message\"\n"
+ + " android:key=\"message\"\n"
+ + " android:restrictionType=\"bundle_array\"\n"
+ + " android:title=\"@string/title_message\"/>\n"
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testTooManyChildren() throws Exception {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < MAX_NUMBER_OF_NESTED_RESTRICTIONS + 2; i++) {
+ //noinspection StringConcatenationInsideStringBufferAppend
+ sb.append(""
+ + " <restriction\n"
+ + " android:defaultValue=\"@bool/default_can_say_hello" + i + "\"\n"
+ + " android:description=\"@string/description_can_say_hello" + i + "\"\n"
+ + " android:key=\"can_say_hello" + i + "\"\n"
+ + " android:restrictionType=\"bool\"\n"
+ + " android:title=\"@string/title_can_say_hello" + i + "\"/>\n"
+ + " \n");
+
+ }
+
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:1: Error: Invalid nested restriction: too many nested restrictions (was 1002, max 1000) [ValidRestrictions]\n"
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + "^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + sb.toString()
+ + "</restrictions>")
+
+ ));
+ }
+
+ public void testNestingTooDeep() throws Exception {
+ StringBuilder sb = new StringBuilder();
+ int maxDepth = MAX_NESTING_DEPTH + 1;
+ for (int i = 0; i < maxDepth; i++) {
+ //noinspection StringConcatenationInsideStringBufferAppend
+ sb.append(""
+ + " <restriction\n"
+ + " android:description=\"@string/description_can_say_hello" + i + "\"\n"
+ + " android:key=\"can_say_hello" + i + "\"\n"
+ + " android:restrictionType=\"bundle\"\n"
+ + " android:title=\"@string/title_can_say_hello" + i + "\">\n"
+ + " \n");
+ }
+ sb.append(""
+ + " <restriction\n"
+ + " android:defaultValue=\"@string/default_message\"\n"
+ + " android:description=\"@string/description_message\"\n"
+ + " android:key=\"message\"\n"
+ + " android:restrictionType=\"string\"\n"
+ + " android:title=\"@string/title_message\"/>\n"
+ + " \n");
+ for (int i = 0; i < maxDepth; i++) {
+ sb.append(" </restriction>\n");
+ }
+
+ assertEquals(""
+ + "res/xml/app_restrictions.xml:122: Error: Invalid nested restriction: nesting depth 21 too large (max 20 [ValidRestrictions]\n"
+ + " <restriction\n"
+ + " ^\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ xml("res/xml/app_restrictions.xml", ""
+ + "<restrictions xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
+ + sb.toString()
+ + "</restrictions>")
+
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
new file mode 100644
index 0000000..8d70bbb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/RtlDetectorTest.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ATTR_DRAWABLE_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START;
+import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.tools.lint.checks.RtlDetector.ATTRIBUTES;
+import static com.android.tools.lint.checks.RtlDetector.convertNewToOld;
+import static com.android.tools.lint.checks.RtlDetector.convertOldToNew;
+import static com.android.tools.lint.checks.RtlDetector.convertToOppositeDirection;
+import static com.android.tools.lint.checks.RtlDetector.isRtlAttributeName;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+ at SuppressWarnings("javadoc")
+public class RtlDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new RtlDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ private Set<Issue> mEnabled = new HashSet<Issue>();
+ private static final Set<Issue> ALL = new HashSet<Issue>();
+ static {
+ ALL.add(RtlDetector.USE_START);
+ ALL.add(RtlDetector.ENABLED);
+ ALL.add(RtlDetector.COMPAT);
+ ALL.add(RtlDetector.SYMMETRY);
+ }
+
+ public void testIsRtlAttributeName() {
+ assertTrue(isRtlAttributeName(ATTR_LAYOUT_ALIGN_PARENT_START));
+ assertTrue(isRtlAttributeName(ATTR_LAYOUT_MARGIN_END));
+ assertTrue(isRtlAttributeName(ATTR_LAYOUT_ALIGN_END));
+ assertFalse(isRtlAttributeName(ATTR_LAYOUT_ALIGN_PARENT_LEFT));
+ assertFalse(isRtlAttributeName(ATTR_DRAWABLE_RIGHT));
+ assertFalse(isRtlAttributeName(ATTR_LAYOUT_ALIGN_RIGHT));
+ assertFalse(isRtlAttributeName(ATTR_NAME));
+ }
+
+ @Override
+ protected TestConfiguration getConfiguration(LintClient client, Project project) {
+ return new TestConfiguration(client, project, null) {
+ @Override
+ public boolean isEnabled(@NonNull Issue issue) {
+ return super.isEnabled(issue) && mEnabled.contains(issue);
+ }
+ };
+ }
+
+ public void testTarget14WithRtl() throws Exception {
+ mEnabled = ALL;
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "minsdk5targetsdk14.xml=>AndroidManifest.xml",
+ "rtl/rtl.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testTarget17WithRtl() throws Exception {
+ mEnabled = ALL;
+ assertEquals(""
+ + "res/layout/rtl.xml:14: Warning: Use \"start\" instead of \"left\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:layout_gravity=\"left\"\n"
+ + " ~~~~\n"
+ + "res/layout/rtl.xml:22: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:layout_gravity=\"right\"\n"
+ + " ~~~~~\n"
+ + "res/layout/rtl.xml:30: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:gravity=\"right\"\n"
+ + " ~~~~~\n"
+ + "AndroidManifest.xml: Warning: The project references RTL attributes, but does not explicitly enable or disable RTL support with android:supportsRtl in the manifest [RtlEnabled]\n"
+ + "0 errors, 4 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/rtl.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testTarget14() throws Exception {
+ mEnabled = ALL;
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "minsdk5targetsdk14.xml=>AndroidManifest.xml"
+ ));
+ }
+
+ public void testOlderCompilationTarget() throws Exception {
+ mEnabled = ALL;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "rtl/project-api14.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/rtl.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testUseStart() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.USE_START);
+ assertEquals(""
+ + "res/layout/rtl.xml:14: Warning: Use \"start\" instead of \"left\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:layout_gravity=\"left\"\n"
+ + " ~~~~\n"
+ + "res/layout/rtl.xml:22: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:layout_gravity=\"right\"\n"
+ + " ~~~~~\n"
+ + "res/layout/rtl.xml:30: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:gravity=\"right\"\n"
+ + " ~~~~~\n"
+ + "0 errors, 3 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/rtl.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testTarget17Rtl() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.USE_START);
+ assertEquals(""
+ + "res/layout/rtl.xml:14: Warning: Use \"start\" instead of \"left\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:layout_gravity=\"left\"\n"
+ + " ~~~~\n"
+ + "res/layout/rtl.xml:22: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:layout_gravity=\"right\"\n"
+ + " ~~~~~\n"
+ + "res/layout/rtl.xml:30: Warning: Use \"end\" instead of \"right\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " android:gravity=\"right\"\n"
+ + " ~~~~~\n"
+ + "0 errors, 3 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/min17rtl.xml=>AndroidManifest.xml",
+ "rtl/rtl.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testRelativeLayoutInOld() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.USE_START);
+ assertEquals(""
+ + "res/layout/relative.xml:10: Warning: Consider adding android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignParentLeft=\"true\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:13: Warning: Consider adding android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_marginLeft=\"40dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:24: Warning: Consider adding android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_marginLeft=\"40dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:26: Warning: Consider adding android:layout_toEndOf=\"@id/loading_progress\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_toRightOf=\"@id/loading_progress\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:29: Warning: Consider adding android:paddingEnd=\"120dip\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:paddingRight=\"120dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:37: Warning: Consider adding android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignParentLeft=\"true\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:38: Warning: Consider adding android:layout_alignEnd=\"@id/text\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignRight=\"@id/text\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:47: Warning: Consider adding android:layout_alignStart=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignLeft=\"@id/cancel\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:48: Warning: Consider adding android:layout_alignEnd=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignRight=\"@id/cancel\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 9 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/relative.xml=>res/layout/relative.xml"
+ ));
+ }
+
+ public void testRelativeLayoutInNew() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.USE_START);
+ assertEquals(""
+ + "res/layout/relative.xml:10: Warning: Consider replacing android:layout_alignParentLeft with android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignParentLeft=\"true\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:13: Warning: Consider replacing android:layout_marginLeft with android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_marginLeft=\"40dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:24: Warning: Consider replacing android:layout_marginLeft with android:layout_marginStart=\"40dip\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_marginLeft=\"40dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:26: Warning: Consider replacing android:layout_toRightOf with android:layout_toEndOf=\"@id/loading_progress\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_toRightOf=\"@id/loading_progress\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:29: Warning: Consider replacing android:paddingRight with android:paddingEnd=\"120dip\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:paddingRight=\"120dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:37: Warning: Consider replacing android:layout_alignParentLeft with android:layout_alignParentStart=\"true\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignParentLeft=\"true\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:38: Warning: Consider replacing android:layout_alignRight with android:layout_alignEnd=\"@id/text\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignRight=\"@id/text\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:47: Warning: Consider replacing android:layout_alignLeft with android:layout_alignStart=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignLeft=\"@id/cancel\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:48: Warning: Consider replacing android:layout_alignRight with android:layout_alignEnd=\"@id/cancel\" to better support right-to-left layouts [RtlHardcoded]\n"
+ + " android:layout_alignRight=\"@id/cancel\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 9 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/min17rtl.xml=>AndroidManifest.xml",
+ "rtl/relative.xml=>res/layout/relative.xml"
+ ));
+ }
+
+ public void testRelativeLayoutCompat() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.COMPAT);
+ assertEquals(""
+ + "res/layout/relative.xml:10: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignParentLeft=\"true\" [RtlCompat]\n"
+ + " android:layout_alignParentStart=\"true\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:13: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_marginLeft=\"40dip\" [RtlCompat]\n"
+ + " android:layout_marginStart=\"40dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:24: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_marginLeft=\"40dip\" [RtlCompat]\n"
+ + " android:layout_marginStart=\"40dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:26: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_toRightOf=\"@id/loading_progress\" [RtlCompat]\n"
+ + " android:layout_toEndOf=\"@id/loading_progress\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:29: Error: To support older versions than API 17 (project specifies 5) you should also add android:paddingRight=\"120dip\" [RtlCompat]\n"
+ + " android:paddingEnd=\"120dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:37: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignParentLeft=\"true\" [RtlCompat]\n"
+ + " android:layout_alignParentStart=\"true\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:38: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignRight=\"@id/text\" [RtlCompat]\n"
+ + " android:layout_alignEnd=\"@id/text\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:47: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignLeft=\"@id/cancel\" [RtlCompat]\n"
+ + " android:layout_alignStart=\"@id/cancel\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/relative.xml:48: Error: To support older versions than API 17 (project specifies 5) you should also add android:layout_alignRight=\"@id/cancel\" [RtlCompat]\n"
+ + " android:layout_alignEnd=\"@id/cancel\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "9 errors, 0 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/relativeCompat.xml=>res/layout/relative.xml"
+ ));
+ }
+
+
+ public void testRelativeCompatOk() throws Exception {
+ mEnabled = ALL;
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/relativeOk.xml=>res/layout/relative.xml"
+ ));
+ }
+
+ public void testTarget17NoRtl() throws Exception {
+ mEnabled = ALL;
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/min17nortl.xml=>AndroidManifest.xml",
+ "rtl/rtl.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testJava() throws Exception {
+ mEnabled = ALL;
+ assertEquals(""
+ + "src/test/pkg/GravityTest.java:24: Warning: Use \"Gravity.START\" instead of \"Gravity.LEFT\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " t1.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);\n"
+ + " ~~~~\n"
+ + "src/test/pkg/GravityTest.java:30: Warning: Use \"Gravity.START\" instead of \"Gravity.LEFT\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " t1.setGravity(LEFT | RIGHT); // static imports\n"
+ + " ~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/GravityTest.java.txt=>src/test/pkg/GravityTest.java"
+ ));
+ }
+
+ public void testOk1() throws Exception {
+ mEnabled = ALL;
+ // targetSdkVersion < 17
+ assertEquals(
+ "No warnings.",
+
+ lintProject("rtl/rtl.xml=>res/layout/rtl.xml"));
+ }
+
+ public void testOk2() throws Exception {
+ mEnabled = ALL;
+ // build target < 14
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "overdraw/project.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/rtl.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testNullLocalName() throws Exception {
+ // Regression test for attribute with null local name
+ mEnabled = ALL;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "overdraw/project.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/rtl_noprefix.xml=>res/layout/rtl.xml"
+ ));
+ }
+
+ public void testSymmetry() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.SYMMETRY);
+ assertEquals(""
+ + "res/layout/relative.xml:29: Warning: When you define paddingRight you should probably also define paddingLeft for right-to-left symmetry [RtlSymmetry]\n"
+ + " android:paddingRight=\"120dip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/relative.xml=>res/layout/relative.xml"
+ ));
+ }
+
+ public void testCompatAttributeValueConversion() throws Exception {
+ // Ensure that when the RTL value contains a direction, we produce the
+ // compatibility version of it for the compatibility attribute, e.g. if the
+ // attribute for paddingEnd is ?listPreferredItemPaddingEnd, when we suggest
+ // also setting paddingRight we suggest ?listPreferredItemPaddingRight
+ mEnabled = Collections.singleton(RtlDetector.COMPAT);
+ assertEquals(""
+ + "res/layout/symmetry.xml:8: Error: To support older versions than API 17 (project specifies 5) you should also add android:paddingRight=\"?android:listPreferredItemPaddingRight\" [RtlCompat]\n"
+ + " android:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/symmetry.xml=>res/layout/symmetry.xml"
+ ));
+ }
+
+ public void testTextAlignment() throws Exception {
+ mEnabled = Collections.singleton(RtlDetector.COMPAT);
+ assertEquals(""
+ + "res/layout/spinner.xml:49: Error: Inconsistent alignment specification between textAlignment and gravity attributes: was end, expected start [RtlCompat]\n"
+ + " android:textAlignment=\"textStart\"/> <!-- ERROR -->\n"
+ + " ~~~~~~~~~\n"
+ + " res/layout/spinner.xml:46: Incompatible direction here\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/spinner.xml=>res/layout/spinner.xml"
+ ));
+ }
+
+ public void testConvertBetweenAttributes() {
+ assertEquals("alignParentStart", convertOldToNew("alignParentLeft"));
+ assertEquals("alignParentEnd", convertOldToNew("alignParentRight"));
+ assertEquals("paddingEnd", convertOldToNew("paddingRight"));
+ assertEquals("paddingStart", convertOldToNew("paddingLeft"));
+
+ assertEquals("alignParentLeft", convertNewToOld("alignParentStart"));
+ assertEquals("alignParentRight", convertNewToOld("alignParentEnd"));
+ assertEquals("paddingRight", convertNewToOld("paddingEnd"));
+ assertEquals("paddingLeft", convertNewToOld("paddingStart"));
+
+ for (int i = 0, n = ATTRIBUTES.length; i < n; i += 2) {
+ String oldAttribute = ATTRIBUTES[i];
+ String newAttribute = ATTRIBUTES[i + 1];
+ assertEquals(newAttribute, convertOldToNew(oldAttribute));
+ assertEquals(oldAttribute, convertNewToOld(newAttribute));
+ }
+ }
+
+ public void testConvertToOppositeDirection() {
+ assertEquals("alignParentRight", convertToOppositeDirection("alignParentLeft"));
+ assertEquals("alignParentLeft", convertToOppositeDirection("alignParentRight"));
+ assertEquals("alignParentStart", convertToOppositeDirection("alignParentEnd"));
+ assertEquals("alignParentEnd", convertToOppositeDirection("alignParentStart"));
+ assertEquals("paddingLeft", convertToOppositeDirection("paddingRight"));
+ assertEquals("paddingRight", convertToOppositeDirection("paddingLeft"));
+ assertEquals("paddingStart", convertToOppositeDirection("paddingEnd"));
+ assertEquals("paddingEnd", convertToOppositeDirection("paddingStart"));
+ }
+
+ public void testEnumConstants() throws Exception {
+ // Regression test for
+ // https://code.google.com/p/android/issues/detail?id=75480
+ // Also checks that static imports work correctly
+ mEnabled = ALL;
+ assertEquals(""
+ + "src/test/pkg/GravityTest2.java:19: Warning: Use \"Gravity.START\" instead of \"Gravity.LEFT\" to ensure correct behavior in right-to-left locales [RtlHardcoded]\n"
+ + " if (gravity == LEFT) { // ERROR\n"
+ + " ~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "rtl/project-api17.properties=>project.properties",
+ "rtl/minsdk5targetsdk17.xml=>AndroidManifest.xml",
+ "rtl/GravityTest2.java.txt=>src/test/pkg/GravityTest2.java"
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java
new file mode 100644
index 0000000..c500e1e
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SQLiteDetectorTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class SQLiteDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SQLiteDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SQLiteTest.java:25: Warning: Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example,strings that look like integers can drop leading zeroes. See issue explanation for details.) [SQLiteString]\n"
+ + " db.execSQL(\"CREATE TABLE \" + name + \"(\" + Tables.AppKeys.SCHEMA + \");\"); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SQLiteTest.java:30: Warning: Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example,strings that look like integers can drop leading zeroes. See issue explanation for details.) [SQLiteString]\n"
+ + " db.execSQL(TracksColumns.CREATE_TABLE); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "src/test/pkg/MyTracksProvider.java.txt=>src/test/pkg/TracksColumns.java",
+ "src/test/pkg/SQLiteTest.java.txt=>src/test/pkg/SQLiteTest.java",
+ // stub for type resolution
+ "src/test/pkg/SQLiteDatabase.java.txt=>src/android/database/sqlite/SQLiteDatabase.java"
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java
new file mode 100644
index 0000000..e34f509
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SdCardDetectorTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class SdCardDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SdCardDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/SdCardTest.java:13: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " private static final String SDCARD_TEST_HTML = \"/sdcard/test.html\";\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:14: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " public static final String SDCARD_ROOT = \"/sdcard\";\n" +
+ " ~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:15: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " public static final String PACKAGES_PATH = \"/sdcard/o/packages/\";\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:16: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " File deviceDir = new File(\"/sdcard/vr\");\n" +
+ " ~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:20: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " android.os.Debug.startMethodTracing(\"/sdcard/launcher\");\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:22: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " if (new File(\"/sdcard\").exists()) {\n" +
+ " ~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:24: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " String FilePath = \"/sdcard/\" + new File(\"test\");\n" +
+ " ~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:29: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " intent.setDataAndType(Uri.parse(\"file://sdcard/foo.json\"), \"application/bar-json\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:30: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " intent.putExtra(\"path-filter\", \"/sdcard(/.+)*\");\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:31: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " intent.putExtra(\"start-dir\", \"/sdcard\");\n" +
+ " ~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:32: Warning: Do not hardcode \"/data/\"; use Context.getFilesDir().getPath() instead [SdCardPath]\n" +
+ " String mypath = \"/data/data/foo\";\n" +
+ " ~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:33: Warning: Do not hardcode \"/data/\"; use Context.getFilesDir().getPath() instead [SdCardPath]\n" +
+ " String base = \"/data/data/foo.bar/test-profiling\";\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SdCardTest.java:34: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " String s = \"file://sdcard/foo\";\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 13 warnings\n",
+
+ lintProject("src/test/pkg/SdCardTest.java.txt=>src/test/pkg/SdCardTest.java"));
+ }
+
+ public void testSuppress() throws Exception {
+ assertEquals(
+ // The only reference in the file not covered by an annotation
+ "src/test/pkg/SuppressTest5.java:40: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " String notAnnotated = \"/sdcard/mypath\";\n" +
+ " ~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ // File with lots of /sdcard references, but with @SuppressLint warnings
+ // on fields, methods, variable declarations etc
+ lintProject("src/test/pkg/SuppressTest5.java.txt=>src/test/pkg/SuppressTest5.java"));
+ }
+
+ public void testUtf8Bom() throws Exception {
+ assertEquals(
+ "src/test/pkg/Utf8BomTest.java:4: Warning: Do not hardcode \"/sdcard/\"; use Environment.getExternalStorageDirectory().getPath() instead [SdCardPath]\n" +
+ " String s = \"/sdcard/mydir\";\n" +
+ " ~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintProject("src/test/pkg/Utf8BomTest.java.data=>src/test/pkg/Utf8BomTest.java"));
+ }
+
+ public void testSuppressInAnnotation() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(java("src/test/pkg/MyInterface.java", ""
+ + "package test.pkg;\n"
+ + "import android.annotation.SuppressLint;\n"
+ + "public @interface MyInterface {\n"
+ + " @SuppressLint(\"SdCardPath\")\n"
+ + " String engineer() default \"/sdcard/this/is/wrong\";\n"
+ + "}\n")));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java
new file mode 100644
index 0000000..22395bb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomDetectorTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "MethodMayBeStatic", "RedundantCast"})
+public class SecureRandomDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SecureRandomDetector();
+ }
+
+ public void test0() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SecureRandomTest.java:12: Warning: It is dangerous to seed SecureRandom with the current time because that value is more predictable to an attacker than the default seed. [SecureRandom]\n"
+ + " random1.setSeed(System.currentTimeMillis()); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:13: Warning: It is dangerous to seed SecureRandom with the current time because that value is more predictable to an attacker than the default seed. [SecureRandom]\n"
+ + " random1.setSeed(System.nanoTime()); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:15: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random1.setSeed(0); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:16: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random1.setSeed(1); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:17: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random1.setSeed((int)1023); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:18: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random1.setSeed(1023L); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:19: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random1.setSeed(FIXED_SEED); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:29: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:41: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random2.setSeed(seed); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/SecureRandomTest.java:47: Warning: Do not call setSeed() on a SecureRandom with a fixed seed: it is not secure. Use getSeed(). [SecureRandom]\n"
+ + " random2.setSeed(seedBytes); // Wrong\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 10 warnings\n",
+ // Error on line 55 is only found in the IDE where we can map from ResolvedNodes to AST nodes
+
+ lintProject(java("src/test/pkg/SecureRandomTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import java.security.SecureRandom;\n"
+ + "import java.util.Random;\n"
+ + "\n"
+ + "public class SecureRandomTest {\n"
+ + " private static final long FIXED_SEED = 1000L;\n"
+ + " protected int getDynamicSeed() { return 1; }\n"
+ + "\n"
+ + " public void testLiterals() {\n"
+ + " SecureRandom random1 = new SecureRandom();\n"
+ + " random1.setSeed(System.currentTimeMillis()); // Wrong\n"
+ + " random1.setSeed(System.nanoTime()); // Wrong\n"
+ + " random1.setSeed(getDynamicSeed()); // OK\n"
+ + " random1.setSeed(0); // Wrong\n"
+ + " random1.setSeed(1); // Wrong\n"
+ + " random1.setSeed((int)1023); // Wrong\n"
+ + " random1.setSeed(1023L); // Wrong\n"
+ + " random1.setSeed(FIXED_SEED); // Wrong\n"
+ + " }\n"
+ + "\n"
+ + " public static void testRandomTypeOk() {\n"
+ + " Random random2 = new Random();\n"
+ + " random2.setSeed(0); // OK\n"
+ + " }\n"
+ + "\n"
+ + " public static void testRandomTypeWrong() {\n"
+ + " Random random3 = new SecureRandom();\n"
+ + " random3.setSeed(0); // Wrong: owner is java/util/Random, but applied to SecureRandom object\n"
+ + " }\n"
+ + "\n"
+ + " public static void testBytesOk() {\n"
+ + " SecureRandom random1 = new SecureRandom();\n"
+ + " byte[] seed = random1.generateSeed(4);\n"
+ + " random1.setSeed(seed); // OK\n"
+ + " }\n"
+ + "\n"
+ + " public static void testBytesWrong() {\n"
+ + " SecureRandom random2 = new SecureRandom();\n"
+ + " byte[] seed = new byte[3];\n"
+ + " random2.setSeed(seed); // Wrong\n"
+ + " }\n"
+ + "\n"
+ + " public static void testFixedSeedBytes(byte something) {\n"
+ + " SecureRandom random2 = new SecureRandom();\n"
+ + " byte[] seedBytes = new byte[] { 1, 2, 3 };\n"
+ + " random2.setSeed(seedBytes); // Wrong\n"
+ + " byte[] seedBytes2 = new byte[] { 1, something, 3 };\n"
+ + " random2.setSeed(seedBytes2); // OK\n"
+ + " }\n"
+ + "\n"
+ + " private static final byte[] fixedSeed = new byte[] { 1, 2, 3 };\n"
+ + " public void testFixedSeedBytesField() {\n"
+ + " SecureRandom random2 = new SecureRandom();\n"
+ + " random2.setSeed(fixedSeed); // Wrong\n"
+ + " }\n"
+ + "\n"
+ + "}\n")));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomGeneratorDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomGeneratorDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomGeneratorDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecureRandomGeneratorDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java
new file mode 100644
index 0000000..30a58b3
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SecurityDetectorTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class SecurityDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SecurityDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void testBroken() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" +
+ " <service\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "exportservice1.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testBroken2() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" +
+ " <service\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "exportservice2.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testBroken3() throws Exception {
+ // Not defining exported, but have intent-filters
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" +
+ " <service\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "exportservice5.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testOk1() throws Exception {
+ // Defines a permission on the <service> element
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "exportservice3.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testOk2() throws Exception {
+ // Defines a permission on the parent <application> element
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "exportservice4.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testUri() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:25: Warning: Content provider shares everything; this is potentially dangerous. [GrantAllUris]\n" +
+ " <grant-uri-permission android:path=\"/\"/>\n" +
+ " ~~~~~~~~~~~~~~~~\n" +
+ "AndroidManifest.xml:26: Warning: Content provider shares everything; this is potentially dangerous. [GrantAllUris]\n" +
+ " <grant-uri-permission android:pathPrefix=\"/\"/>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 2 warnings\n" +
+ "",
+
+ lintProject(
+ "grantpermission.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ // exportprovider1.xml has two exported content providers with no permissions
+ public void testContentProvider1() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:14: Warning: Exported content providers can provide access to potentially sensitive data [ExportedContentProvider]\n" +
+ " <provider\n" +
+ " ^\n" +
+ "AndroidManifest.xml:20: Warning: Exported content providers can provide access to potentially sensitive data [ExportedContentProvider]\n" +
+ " <provider\n" +
+ " ^\n" +
+ "0 errors, 2 warnings\n" +
+ "",
+ lintProject(
+ "exportprovider1.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ // exportprovider2.xml has no un-permissioned exported content providers
+ public void testContentProvider2() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "exportprovider2.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testWorldWriteable() throws Exception {
+ assertEquals(
+ "src/test/pkg/WorldWriteableFile.java:41: Warning: Setting file permissions to world-readable can be risky, review carefully [SetWorldReadable]\n" +
+ " mFile.setReadable(true, false);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WorldWriteableFile.java:48: Warning: Setting file permissions to world-writable can be risky, review carefully [SetWorldWritable]\n" +
+ " mFile.setWritable(true, false);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WorldWriteableFile.java:27: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
+ " out = openFileOutput(mFile.getName(), MODE_WORLD_READABLE);\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WorldWriteableFile.java:32: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
+ " prefs = getSharedPreferences(mContext, MODE_WORLD_READABLE);\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WorldWriteableFile.java:36: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" +
+ " dir = getDir(mFile.getName(), MODE_WORLD_READABLE);\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WorldWriteableFile.java:26: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
+ " out = openFileOutput(mFile.getName(), MODE_WORLD_WRITEABLE);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WorldWriteableFile.java:31: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
+ " prefs = getSharedPreferences(mContext, MODE_WORLD_WRITEABLE);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/WorldWriteableFile.java:35: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" +
+ " dir = getDir(mFile.getName(), MODE_WORLD_WRITEABLE);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 8 warnings\n" +
+ "",
+
+ lintProject(
+ // Java files must be renamed in source tree
+ "src/test/pkg/WorldWriteableFile.java.txt=>src/test/pkg/WorldWriteableFile.java"));
+ }
+
+ public void testReceiver0() throws Exception {
+ // Activities that do not have intent-filters do not need warnings
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "exportreceiver0.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testReceiver1() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Exported receiver does not require permission [ExportedReceiver]\n" +
+ " <receiver\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "exportreceiver1.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testReceiver2() throws Exception {
+ // Defines a permission on the <activity> element
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "exportreceiver2.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testReceiver3() throws Exception {
+ // Defines a permission on the parent <application> element
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "exportreceiver3.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testReceiver4() throws Exception {
+ // Not defining exported, but have intent-filters
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: Exported receiver does not require permission [ExportedReceiver]\n" +
+ " <receiver\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+ lintProject(
+ "exportreceiver4.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testReceiver5() throws Exception {
+ // Intent filter for standard Android action
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "exportreceiver5.xml=>AndroidManifest.xml",
+ "res/values/strings.xml"));
+ }
+
+ public void testStandard() throws Exception {
+ // Various regression tests for http://code.google.com/p/android/issues/detail?id=33976
+ assertEquals(
+ "No warnings.",
+ lintProject("exportreceiver6.xml=>AndroidManifest.xml"));
+ }
+
+ public void testUsingInstallReferrerReceiver() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=73934
+ assertEquals(
+ "No warnings.",
+ lintProject("exportreceiver7.xml=>AndroidManifest.xml"));
+ }
+
+ public void testGmsWearable() throws Exception {
+ // As documented in
+ // https://developer.android.com/training/wearables/data-layer/events.html
+ // you shouldn't need a permission here.
+ assertEquals(
+ "No warnings.",
+ lintProject("exportreceiver8.xml=>AndroidManifest.xml"));
+
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ServiceCastDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ServiceCastDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ServiceCastDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ServiceCastDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetTextDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetTextDetectorTest.java
new file mode 100644
index 0000000..0ab937f
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SetTextDetectorTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class SetTextDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SetTextDetector();
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/CustomScreen.java:13: Warning: String literal in setText can not be translated. Use Android resources instead. [SetTextI18n]\n" +
+ " view.setText(\"Hardcoded\");\n" +
+ " ~~~~~~~~~~~\n" +
+ "src/test/pkg/CustomScreen.java:17: Warning: Do not concatenate text displayed with setText. Use resource string with placeholders. [SetTextI18n]\n" +
+ " view.setText(Integer.toString(50) + \"%\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/CustomScreen.java:17: Warning: Number formatting does not take into account locale settings. Consider using String.format instead. [SetTextI18n]\n" +
+ " view.setText(Integer.toString(50) + \"%\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/CustomScreen.java:18: Warning: Do not concatenate text displayed with setText. Use resource string with placeholders. [SetTextI18n]\n" +
+ " view.setText(Double.toString(12.5) + \" miles\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/CustomScreen.java:18: Warning: Number formatting does not take into account locale settings. Consider using String.format instead. [SetTextI18n]\n" +
+ " view.setText(Double.toString(12.5) + \" miles\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/CustomScreen.java:18: Warning: String literal in setText can not be translated. Use Android resources instead. [SetTextI18n]\n" +
+ " view.setText(Double.toString(12.5) + \" miles\");\n" +
+ " ~~~~~~~~\n" +
+ "src/test/pkg/CustomScreen.java:21: Warning: Do not concatenate text displayed with setText. Use resource string with placeholders. [SetTextI18n]\n" +
+ " btn.setText(\"User \" + getUserName());\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/CustomScreen.java:21: Warning: String literal in setText can not be translated. Use Android resources instead. [SetTextI18n]\n" +
+ " btn.setText(\"User \" + getUserName());\n" +
+ " ~~~~~~~\n" +
+ "0 errors, 8 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/CustomScreen.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.Context;\n"
+ + "import android.widget.Button;\n"
+ + "import android.widget.TextView;\n"
+ + "\n"
+ + "class CustomScreen {\n"
+ + "\n"
+ + " public CustomScreen(Context context) {\n"
+ + " TextView view = new TextView(context);\n"
+ + "\n"
+ + " // Should fail - hardcoded string\n"
+ + " view.setText(\"Hardcoded\");\n"
+ + " // Should pass - no letters\n"
+ + " view.setText(\"-\");\n"
+ + " // Should fail - concatenation and toString for numbers.\n"
+ + " view.setText(Integer.toString(50) + \"%\");\n"
+ + " view.setText(Double.toString(12.5) + \" miles\");\n"
+ + "\n"
+ + " Button btn = new Button(context);\n"
+ + " btn.setText(\"User \" + getUserName());\n"
+ + " btn.setText(String.format(\"%s of %s users\", Integer.toString(5), Integer.toString(10)));\n"
+ + " }\n"
+ + "\n"
+ + " private static String getUserName() {\n"
+ + " return \"stub\";\n"
+ + " }\n"
+ + "}\n")));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
new file mode 100644
index 0000000..ad778eb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class SharedPrefsDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SharedPrefsDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/SharedPrefsTest.java:54: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " SharedPreferences.Editor editor = preferences.edit();\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest.java:62: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " SharedPreferences.Editor editor = preferences.edit();\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 2 warnings\n" +
+ "",
+
+ lintProject("src/test/pkg/SharedPrefsTest.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest.java"));
+ }
+
+ public void test2() throws Exception {
+ // Regression test 1 for http://code.google.com/p/android/issues/detail?id=34322
+ assertEquals(
+ "src/test/pkg/SharedPrefsTest2.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " SharedPreferences.Editor editor = preferences.edit();\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest2.java:17: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " Editor editor = preferences.edit();\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 2 warnings\n",
+
+ lintProject("src/test/pkg/SharedPrefsTest2.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest2.java"));
+ }
+
+ public void test3() throws Exception {
+ // Regression test 2 for http://code.google.com/p/android/issues/detail?id=34322
+ assertEquals(
+ "src/test/pkg/SharedPrefsTest3.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " Editor editor = preferences.edit();\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n",
+
+ lintProject("src/test/pkg/SharedPrefsTest3.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest3.java"));
+ }
+
+ public void test4() throws Exception {
+ // Regression test 3 for http://code.google.com/p/android/issues/detail?id=34322
+ assertEquals(""
+ + "src/test/pkg/SharedPrefsTest4.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
+ + " Editor editor = preferences.edit();\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("src/test/pkg/SharedPrefsTest4.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest4.java"));
+ }
+
+ public void test5() throws Exception {
+ // Check fields too: http://code.google.com/p/android/issues/detail?id=39134
+ assertEquals(
+ "src/test/pkg/SharedPrefsTest5.java:16: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " mPreferences.edit().putString(PREF_FOO, \"bar\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest5.java:17: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " mPreferences.edit().remove(PREF_BAZ).remove(PREF_FOO);\n" +
+ " ~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest5.java:26: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " preferences.edit().putString(PREF_FOO, \"bar\");\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest5.java:27: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " preferences.edit().remove(PREF_BAZ).remove(PREF_FOO);\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest5.java:32: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " preferences.edit().putString(PREF_FOO, \"bar\");\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest5.java:33: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " preferences.edit().remove(PREF_BAZ).remove(PREF_FOO);\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SharedPrefsTest5.java:38: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n" +
+ " Editor editor = preferences.edit().putString(PREF_FOO, \"bar\");\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 7 warnings\n",
+
+ lintProject("src/test/pkg/SharedPrefsTest5.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest5.java"));
+ }
+
+ public void test6() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=68692
+ assertEquals(""
+ + "src/test/pkg/SharedPrefsTest7.java:13: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
+ + " settings.edit().putString(MY_PREF_KEY, myPrefValue);\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("src/test/pkg/SharedPrefsTest7.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest7.java"));
+ }
+
+ public void test7() throws Exception {
+ assertEquals("No warnings.", // minSdk < 9: no warnings
+
+ lintProject("src/test/pkg/SharedPrefsTest8.java.txt=>" +
+ "src/test/pkg/SharedPrefsTest8.java"));
+ }
+
+ public void test8() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/SharedPrefsTest8.java:11: Warning: Consider using apply() instead; commit writes its data to persistent storage immediately, whereas apply will handle it in the background [CommitPrefEdits]\n"
+ + " editor.commit();\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk11.xml=>AndroidManifest.xml",
+ "src/test/pkg/SharedPrefsTest8.java.txt=>src/test/pkg/SharedPrefsTest8.java"));
+ }
+
+ public void testChainedCalls() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/Chained.java:24: Warning: SharedPreferences.edit() without a corresponding commit() or apply() call [CommitPrefEdits]\n"
+ + " PreferenceManager\n"
+ + " ^\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(java("src/test/pkg/Chained.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.Context;\n"
+ + "import android.preference.PreferenceManager;\n"
+ + "\n"
+ + "public class Chained {\n"
+ + " private static void falsePositive(Context context) {\n"
+ + " PreferenceManager\n"
+ + " .getDefaultSharedPreferences(context)\n"
+ + " .edit()\n"
+ + " .putString(\"wat\", \"wat\")\n"
+ + " .commit();\n"
+ + " }\n"
+ + "\n"
+ + " private static void falsePositive2(Context context) {\n"
+ + " boolean var = PreferenceManager\n"
+ + " .getDefaultSharedPreferences(context)\n"
+ + " .edit()\n"
+ + " .putString(\"wat\", \"wat\")\n"
+ + " .commit();\n"
+ + " }\n"
+ + "\n"
+ + " private static void truePositive(Context context) {\n"
+ + " PreferenceManager\n"
+ + " .getDefaultSharedPreferences(context)\n"
+ + " .edit()\n"
+ + " .putString(\"wat\", \"wat\");\n"
+ + " }\n"
+ + "}\n")));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SignatureOrSystemDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SignatureOrSystemDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SignatureOrSystemDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SignatureOrSystemDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetectorTest.java
new file mode 100644
index 0000000..9d85889
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SslCertificateSocketFactoryDetectorTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class SslCertificateSocketFactoryDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new SslCertificateSocketFactoryDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/SSLCertificateSocketFactoryTest.java:21: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+ " sf.createSocket(inet, 80);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SSLCertificateSocketFactoryTest.java:22: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+ " sf.createSocket(inet4, 80);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SSLCertificateSocketFactoryTest.java:23: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+ " sf.createSocket(inet6, 80);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SSLCertificateSocketFactoryTest.java:24: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+ " sf.createSocket(inet, 80, inet, 2000);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SSLCertificateSocketFactoryTest.java:25: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+ " sf.createSocket(inet4, 80, inet, 2000);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SSLCertificateSocketFactoryTest.java:26: Warning: Use of SSLCertificateSocketFactory.createSocket() with an InetAddress parameter can cause insecure network traffic due to trusting arbitrary hostnames in TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryCreateSocket]\n" +
+ " sf.createSocket(inet6, 80, inet, 2000);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/SSLCertificateSocketFactoryTest.java:29: Warning: Use of SSLCertificateSocketFactory.getInsecure() can cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [SSLCertificateSocketFactoryGetInsecure]\n" +
+ " SSLCertificateSocketFactory.getInsecure(-1,null));\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 7 warnings\n",
+ lintProject(java("src/test/pkg/SSLCertificateSocketFactoryTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.net.SSLCertificateSocketFactory;\n"
+ + "import java.net.InetAddress;\n"
+ + "import java.net.Inet4Address;\n"
+ + "import java.net.Inet6Address;\n"
+ + "import javax.net.ssl.HttpsURLConnection;\n"
+ + "\n"
+ + "public class SSLCertificateSocketFactoryTest {\n"
+ + " public void foo() {\n"
+ + " byte[] ipv4 = new byte[4];\n"
+ + " byte[] ipv6 = new byte[16];\n"
+ + " InetAddress inet = Inet4Address.getByAddress(ipv4);\n"
+ + " Inet4Address inet4 = (Inet4Address) Inet4Address.getByAddress(ipv4);\n"
+ + " Inet6Address inet6 = Inet6Address.getByAddress(null, ipv6, 0);\n"
+ + "\n"
+ + " SSLCertificateSocketFactory sf = (SSLCertificateSocketFactory)\n"
+ + " SSLCertificateSocketFactory.getDefault(0);\n"
+ + " sf.createSocket(\"www.google.com\", 80); // ok\n"
+ + " sf.createSocket(\"www.google.com\", 80, inet, 2000); // ok\n"
+ + " sf.createSocket(inet, 80);\n"
+ + " sf.createSocket(inet4, 80);\n"
+ + " sf.createSocket(inet6, 80);\n"
+ + " sf.createSocket(inet, 80, inet, 2000);\n"
+ + " sf.createSocket(inet4, 80, inet, 2000);\n"
+ + " sf.createSocket(inet6, 80, inet, 2000);\n"
+ + "\n"
+ + " HttpsURLConnection.setDefaultSSLSocketFactory(\n"
+ + " SSLCertificateSocketFactory.getInsecure(-1,null));\n"
+ + " }\n"
+ + "}\n")));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StateListDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StateListDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StateListDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StateListDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
new file mode 100644
index 0000000..49ea0ce
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/StringFormatDetectorTest.java
@@ -0,0 +1,716 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.checks.StringFormatDetector.isLocaleSpecific;
+
+import com.android.tools.lint.detector.api.Detector;
+
+import java.util.HashSet;
+import java.util.Set;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class StringFormatDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new StringFormatDetector();
+ }
+
+ public void testAll() throws Exception {
+ assertEquals(
+ "src/test/pkg/StringFormatActivity.java:13: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output1 = String.format(hello, target);\n" +
+ " ~~~~~~\n" +
+ " res/values-es/formatstrings.xml:3: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:15: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " String output2 = String.format(hello2, target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:21: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output4 = String.format(score, true); // wrong\n" +
+ " ~~~~\n" +
+ " res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output = String.format(score, won); // wrong\n" +
+ " ~~~\n" +
+ " res/values/formatstrings.xml:6: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:26: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " getResources().getString(R.string.hello2, target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml:4: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:33: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output1 = String.format(hello, target);\n" +
+ " ~~~~~~\n" +
+ " res/values-es/formatstrings.xml:3: Conflicting argument declaration here\n" +
+ "res/values-es/formatstrings.xml:3: Error: Inconsistent formatting types for argument #1 in format string hello ('%1$d'): Found both 's' and 'd' (in values/formatstrings.xml) [StringFormatMatches]\n" +
+ " <string name=\"hello\">%1$d</string>\n" +
+ " ~~~~\n" +
+ " res/values/formatstrings.xml:3: Conflicting argument type here\n" +
+ "res/values-es/formatstrings.xml:4: Warning: Inconsistent number of arguments in formatting string hello2; found both 2 and 3 [StringFormatCount]\n" +
+ " <string name=\"hello2\">%3$d: %1$s, %2$s?</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values/formatstrings.xml:4: Conflicting number of arguments here\n" +
+ "res/values/formatstrings.xml:5: Warning: Formatting string 'missing' is not referencing numbered arguments [1, 2] [StringFormatCount]\n" +
+ " <string name=\"missing\">Hello %3$s World</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "8 errors, 2 warnings\n",
+
+ lintProject(
+ "res/values/formatstrings.xml",
+ "res/values-es/formatstrings.xml",
+ // Java files must be renamed in source tree
+ "src/test/pkg/StringFormatActivity.java.txt=>src/test/pkg/StringFormatActivity.java"
+ ));
+ }
+
+ public void testArgCount() {
+ assertEquals(0, StringFormatDetector.getFormatArgumentCount(
+ "%n%% ", null));
+ assertEquals(1, StringFormatDetector.getFormatArgumentCount(
+ "%n%% %s", null));
+ assertEquals(3, StringFormatDetector.getFormatArgumentCount(
+ "First: %1$s, Second %2$s, Third %3$s", null));
+ assertEquals(11, StringFormatDetector.getFormatArgumentCount(
+ "Skipping stuff: %11$s", null));
+ assertEquals(1, StringFormatDetector.getFormatArgumentCount(
+ "First: %1$s, Skip \\%2$s", null));
+ assertEquals(1, StringFormatDetector.getFormatArgumentCount(
+ "First: %s, Skip \\%s", null));
+
+ Set<Integer> indices = new HashSet<Integer>();
+ assertEquals(11, StringFormatDetector.getFormatArgumentCount(
+ "Skipping stuff: %2$d %11$s", indices));
+ assertEquals(2, indices.size());
+ assertTrue(indices.contains(2));
+ assertTrue(indices.contains(11));
+ }
+
+ public void testArgType() {
+ assertEquals("s", StringFormatDetector.getFormatArgumentType(
+ "First: %n%% %1$s, Second %2$s, Third %3$s", 1));
+ assertEquals("s", StringFormatDetector.getFormatArgumentType(
+ "First: %1$s, Second %2$s, Third %3$s", 1));
+ assertEquals("d", StringFormatDetector.getFormatArgumentType(
+ "First: %1$s, Second %2$-5d, Third %3$s", 2));
+ assertEquals("s", StringFormatDetector.getFormatArgumentType(
+ "Skipping stuff: %11$s",11));
+ assertEquals("d", StringFormatDetector.getFormatArgumentType(
+ "First: %1$s, Skip \\%2$s, Value=%2$d", 2));
+ }
+
+ public void testWrongSyntax() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/formatstrings2.xml"
+ ));
+ }
+
+ public void testDateStrings() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/formatstrings-version1.xml=>res/values-tl/donottranslate-cldr.xml",
+ "res/values/formatstrings-version2.xml=>res/values/donottranslate-cldr.xml"
+ ));
+ }
+
+ public void testUa() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/formatstrings-version1.xml=>res/values-tl/donottranslate-cldr.xml",
+ "src/test/pkg/StringFormat2.java.txt=>src/test/pkg/StringFormat2.java"
+ ));
+ }
+
+ public void testSuppressed() throws Exception {
+ //noinspection ClassNameDiffersFromFileName,ConstantConditions
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ copy("res/values/formatstrings_ignore.xml", "res/values/formatstrings.xml"),
+ copy("res/values-es/formatstrings_ignore.xml", "res/values-es/formatstrings.xml"),
+ java("src/test/pkg/StringFormatActivity_ignore.java.txt", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.annotation.SuppressLint;\n"
+ + "import android.app.Activity;\n"
+ + "import android.os.Bundle;\n"
+ + "\n"
+ + "public class StringFormatActivity extends Activity {\n"
+ + " /** Called when the activity is first created. */\n"
+ + " @SuppressLint(\"all\")\n"
+ + " @Override\n"
+ + " public void onCreate(Bundle savedInstanceState) {\n"
+ + " super.onCreate(savedInstanceState);\n"
+ + " String target = \"World\";\n"
+ + " String hello = getResources().getString(R.string.hello);\n"
+ + " String output1 = String.format(hello, target);\n"
+ + " String hello2 = getResources().getString(R.string.hello2);\n"
+ + " String output2 = String.format(hello2, target, \"How are you\");\n"
+ + " setContentView(R.layout.main);\n"
+ + " String score = getResources().getString(R.string.score);\n"
+ + " int points = 50;\n"
+ + " boolean won = true;\n"
+ + " String output3 = String.format(score, points);\n"
+ + " String output4 = String.format(score, true); // wrong\n"
+ + " String output = String.format(score, won); // wrong\n"
+ + " String output5 = String.format(score, 75);\n"
+ + " }\n"
+ + "\n"
+ + " private static class R {\n"
+ + " private static class string {\n"
+ + " public static final int hello = 1;\n"
+ + " public static final int hello2 = 2;\n"
+ + " public static final int score = 3;\n"
+ + " }\n"
+ + " private static class layout {\n"
+ + " public static final int main = 4;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testIssue27108() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject("res/values/formatstrings3.xml"));
+ }
+
+ public void testIssue39758() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/formatstrings4.xml",
+ "src/test/pkg/StringFormatActivity2.java.txt=>src/test/pkg/StringFormatActivity2.java"));
+ }
+
+ public void testIssue42798() throws Exception {
+ // http://code.google.com/p/android/issues/detail?id=42798
+ // String playsCount = String.format(Locale.FRANCE, this.context.getString(R.string.gridview_views_count), article.playsCount);
+ assertEquals(
+ "src/test/pkg/StringFormat3.java:12: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
+ " context.getString(R.string.gridview_views_count), article.playsCount);\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormat3.java:16: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
+ " context.getString(R.string.gridview_views_count), \"wrong\");\n" +
+ " ~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormat3.java:17: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String s4 = String.format(context.getString(R.string.gridview_views_count), \"wrong\");\n" +
+ " ~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormat3.java:22: Error: Wrong argument type for formatting argument '#1' in gridview_views_count: conversion is 'd', received String (argument #3 in method call) [StringFormatMatches]\n" +
+ " context.getString(R.string.gridview_views_count), \"string\");\n" +
+ " ~~~~~~~~\n" +
+ " res/values/formatstrings5.xml:3: Conflicting argument declaration here\n" +
+ "res/values/formatstrings5.xml:3: Warning: Formatting %d followed by words (\"vues\"): This should probably be a plural rather than a string [PluralsCandidate]\n" +
+ " <string name=\"gridview_views_count\">%d vues</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "4 errors, 1 warnings\n",
+
+ lintProject(
+ "res/values/formatstrings5.xml",
+ "src/test/pkg/StringFormat3.java.txt=>src/test/pkg/StringFormat3.java"));
+ }
+
+ public void testIsLocaleSpecific() throws Exception {
+ assertFalse(isLocaleSpecific(""));
+ assertFalse(isLocaleSpecific("Hello World!"));
+ assertFalse(isLocaleSpecific("%% %n"));
+ assertFalse(isLocaleSpecific(" %%f"));
+ assertFalse(isLocaleSpecific("%x %A %c %b %B %h %n %%"));
+ assertTrue(isLocaleSpecific("%f"));
+ assertTrue(isLocaleSpecific(" %1$f "));
+ assertTrue(isLocaleSpecific(" %5$e "));
+ assertTrue(isLocaleSpecific(" %E "));
+ assertTrue(isLocaleSpecific(" %g "));
+ assertTrue(isLocaleSpecific(" %1$tm %1$te,%1$tY "));
+ }
+
+ public void testGetStringAsParameter() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/StringFormat4.java:11: Error: Wrong argument count, format string error_and_source requires 2 but format call supplies 1 [StringFormatMatches]\n"
+ + " getString(R.string.error_and_source, getString(R.string.data_source)); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/formatstrings6.xml:24: This definition requires 2 arguments\n"
+ + "src/test/pkg/StringFormat4.java:13: Error: Wrong argument count, format string error_and_source requires 2 but format call supplies 1 [StringFormatMatches]\n"
+ + " getString(R.string.error_and_source, \"data source\"); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/formatstrings6.xml:24: This definition requires 2 arguments\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/formatstrings6.xml",
+ "src/test/pkg/StringFormat4.java.txt=>src/test/pkg/StringFormat4.java"));
+ }
+
+ public void testNotLocaleMethod() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=53238
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ "res/values/formatstrings7.xml",
+ "src/test/pkg/StringFormat5.java.txt=>src/test/pkg/StringFormat5.java"));
+ }
+
+ public void testNewlineChar() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=65692
+ assertEquals(""
+ + "src/test/pkg/StringFormat8.java:12: Error: Wrong argument count, format string amount_string requires 1 but format call supplies 0 [StringFormatMatches]\n"
+ + " String amount4 = String.format(getResources().getString(R.string.amount_string)); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/formatstrings8.xml:2: This definition requires 1 arguments\n"
+ + "src/test/pkg/StringFormat8.java:13: Error: Wrong argument count, format string amount_string requires 1 but format call supplies 2 [StringFormatMatches]\n"
+ + " String amount5 = getResources().getString(R.string.amount_string, amount, amount); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/formatstrings8.xml:2: This definition requires 1 arguments\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/formatstrings8.xml",
+ "src/test/pkg/StringFormat8.java.txt=>src/test/pkg/StringFormat8.java"));
+ }
+
+ public void testIncremental() throws Exception {
+ assertEquals(
+ "src/test/pkg/StringFormatActivity.java:13: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output1 = String.format(hello, target);\n" +
+ " ~~~~~~\n" +
+ " res/values-es/formatstrings.xml: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:15: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " String output2 = String.format(hello2, target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:21: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output4 = String.format(score, true); // wrong\n" +
+ " ~~~~\n" +
+ " res/values/formatstrings.xml: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:22: Error: Wrong argument type for formatting argument '#1' in score: conversion is 'd', received boolean (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output = String.format(score, won); // wrong\n" +
+ " ~~~\n" +
+ " res/values/formatstrings.xml: Conflicting argument declaration here\n" +
+ "src/test/pkg/StringFormatActivity.java:24: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " String.format(getResources().getString(R.string.hello2), target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:26: Error: Wrong argument count, format string hello2 requires 3 but format call supplies 2 [StringFormatMatches]\n" +
+ " getResources().getString(R.string.hello2, target, \"How are you\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/formatstrings.xml: This definition requires 3 arguments\n" +
+ "src/test/pkg/StringFormatActivity.java:33: Error: Wrong argument type for formatting argument '#1' in hello: conversion is 'd', received String (argument #2 in method call) [StringFormatMatches]\n" +
+ " String output1 = String.format(hello, target);\n" +
+ " ~~~~~~\n" +
+ " res/values-es/formatstrings.xml: Conflicting argument declaration here\n" +
+ "res/values/formatstrings.xml: Error: Inconsistent formatting types for argument #1 in format string hello ('%1$s'): Found both 'd' and 's' (in values-es/formatstrings.xml) [StringFormatMatches]\n" +
+ " res/values-es/formatstrings.xml: Conflicting argument type here\n" +
+ "res/values/formatstrings.xml: Warning: Inconsistent number of arguments in formatting string hello2; found both 3 and 2 [StringFormatCount]\n" +
+ " res/values-es/formatstrings.xml: Conflicting number of arguments here\n" +
+ "8 errors, 1 warnings\n",
+
+ lintProjectIncrementally(
+ "src/test/pkg/StringFormatActivity.java",
+ "res/values/formatstrings.xml",
+ "res/values-es/formatstrings.xml",
+ // Java files must be renamed in source tree
+ "src/test/pkg/StringFormatActivity.java.txt=>src/test/pkg/StringFormatActivity.java"
+ ));
+ }
+
+ public void testNotStringFormat() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=67597
+ assertEquals("No warnings.",
+
+ lintProject(
+ "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
+ "res/values/shared_prefs_keys.xml",
+ "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsFormat.java"));
+ }
+
+ public void testNotStringFormatIncrementally() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=67597
+ assertEquals("No warnings.",
+
+ lintProjectIncrementally(
+ "src/test/pkg/SharedPrefsFormat.java",
+
+ "res/values/formatstrings3.xml",//"res/values/formatstrings.xml",
+ "res/values/shared_prefs_keys.xml",
+ "src/test/pkg/SharedPrefsTest6.java.txt=>src/test/pkg/SharedPrefsFormat.java"));
+ }
+
+ public void testIncrementalNonMatch() throws Exception {
+ // Regression test for scenario where the below source files would crash during
+ // a string format check with
+ // java.lang.IllegalStateException: No match found
+ // at java.util.regex.Matcher.group(Matcher.java:468)
+ // at com.android.tools.lint.checks.StringFormatDetector.checkStringFormatCall(StringFormatDetector.java:1028)
+ // ...
+ assertEquals("No warnings.",
+
+ lintProjectIncrementally(
+ "src/test/pkg/StringFormatActivity3.java",
+ "res/values/formatstrings11.xml",
+ "res/values/formatstrings11.xml=>res/values-de/formatstrings11de.xml",
+ "src/test/pkg/StringFormatActivity3.java.txt=>src/test/pkg/StringFormatActivity3.java"));
+ }
+
+ public void testXliff() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/formatstrings9.xml",
+ "src/test/pkg/StringFormat9.java.txt=>src/test/pkg/StringFormat9.java"
+ ));
+ }
+
+ public void testXliffIncremental() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProjectIncrementally(
+ "src/test/pkg/StringFormat9.java",
+ "res/values/formatstrings9.xml",
+ "src/test/pkg/StringFormat9.java.txt=>src/test/pkg/StringFormat9.java"
+ ));
+ }
+
+ public void testBigDecimal() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=69527
+ assertEquals("No warnings.",
+
+ lintProject(
+ "res/values/formatstrings10.xml",
+ "src/test/pkg/StringFormat10.java.txt=>src/test/pkg/StringFormat10.java"
+ ));
+
+ }
+
+ public void testWrapperClasses() throws Exception {
+ assertEquals("No warnings.",
+
+ lintProject(
+ "res/values/formatstrings10.xml",
+ "src/test/pkg/StringFormat11.java.txt=>src/test/pkg/StringFormat11.java"
+ ));
+ }
+
+ public void testPluralsCandidates() throws Exception {
+ assertEquals(""
+ + "res/values/plurals_candidates.xml:4: Warning: Formatting %d followed by words (\"times\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
+ + " <string name=\"lockscreen_too_many_failed_attempts_dialog_message1\">\n"
+ + " ^\n"
+ + "res/values/plurals_candidates.xml:10: Warning: Formatting %d followed by words (\"times\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
+ + " <string name=\"lockscreen_too_many_failed_attempts_dialog_message2\">\n"
+ + " ^\n"
+ + "res/values/plurals_candidates.xml:14: Warning: Formatting %d followed by words (\"moves\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
+ + " <string name=\"win_dialog\">You won in %1$s and %2$d moves!</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/plurals_candidates.xml:15: Warning: Formatting %d followed by words (\"times\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
+ + " <string name=\"countdown_complete_sub\">Timer was paused %d times</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/plurals_candidates.xml:16: Warning: Formatting %d followed by words (\"satellites\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
+ + " <string name=\"service_gpsstatus\">Logging: %s (%s with %d satellites)</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/plurals_candidates.xml:17: Warning: Formatting %d followed by words (\"seconds\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
+ + " <string name=\"sync_log_clocks_unsynchronized\">The clock on your device is incorrect by %1$d seconds%2$s;</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/plurals_candidates.xml:18: Warning: Formatting %d followed by words (\"tasks\"): This should probably be a plural rather than a string [PluralsCandidate]\n"
+ + " <string name=\"EPr_manage_purge_deleted_status\">Purged %d tasks!</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 7 warnings\n",
+
+ lintProject(
+ "res/values/plurals_candidates.xml",
+ // Should not flag on anything but English strings
+ "res/values/plurals_candidates.xml=>res/values-de/plurals_candidates.xml"
+
+ ));
+ }
+
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testAdditionalGetStringMethods() throws Exception {
+ // Regression test for
+ // https://code.google.com/p/android/issues/detail?id=183643
+ // 183643: Lint format detector should apply to Context#getString
+ // It also checks that we handle Object[] properly
+ assertEquals(""
+ + "src/test/pkg/FormatCheck.java:11: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+ + " context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:4: This definition does not require arguments\n"
+ + "src/test/pkg/FormatCheck.java:13: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+ + " context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:4: This definition does not require arguments\n"
+ + "src/test/pkg/FormatCheck.java:17: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+ + " context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:5: This definition requires 1 arguments\n"
+ + "src/test/pkg/FormatCheck.java:18: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 0 [StringFormatMatches]\n"
+ + " context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:5: This definition requires 1 arguments\n"
+ + "src/test/pkg/FormatCheck.java:20: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+ + " context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:5: This definition requires 1 arguments\n"
+ + "src/test/pkg/FormatCheck.java:22: Error: Wrong argument count, format string two_args requires 2 but format call supplies 1 [StringFormatMatches]\n"
+ + " context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:6: This definition requires 2 arguments\n"
+ + "src/test/pkg/FormatCheck.java:24: Error: Wrong argument count, format string two_args requires 2 but format call supplies 0 [StringFormatMatches]\n"
+ + " context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:6: This definition requires 2 arguments\n"
+ + "src/test/pkg/FormatCheck.java:26: Error: Wrong argument count, format string two_args requires 2 but format call supplies 3 [StringFormatMatches]\n"
+ + " context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:6: This definition requires 2 arguments\n"
+ + "src/test/pkg/FormatCheck.java:36: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 3 [StringFormatMatches]\n"
+ + " fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml:5: This definition requires 1 arguments\n"
+ + "9 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/FormatCheck.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.Fragment;\n"
+ + "import android.content.Context;\n"
+ + "\n"
+ + "\n"
+ + "\n"
+ + "public class FormatCheck {\n"
+ + " public static void testContext(Context context) {\n"
+ + " context.getString(R.string.zero_args); // OK: Just looking up the string (includes %1$s)\n"
+ + " context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+ + " context.getString(R.string.zero_args, new Object[0]); // OK\n"
+ + " context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+ + "\n"
+ + " context.getString(R.string.one_arg); // OK: Just looking up the string (includes %1$s)\n"
+ + " context.getString(R.string.one_arg, \"first\"); // OK\n"
+ + " context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+ + " context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+ + " context.getString(R.string.one_arg, new Object[] { \"first\" }); // OK\n"
+ + " context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+ + " \n"
+ + " context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+ + " context.getString(R.string.two_args, \"first\", \"second\"); // OK\n"
+ + " context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+ + " context.getString(R.string.two_args, new Object[] { \"first\", \"second\" }); // OK\n"
+ + " context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+ + " String[] args2 = new String[] { \"first\", \"second\" };\n"
+ + " context.getString(R.string.two_args, args2); // OK\n"
+ + " String[] args3 = new String[] { \"first\", \"second\", \"third\" };\n"
+ + " context.getString(R.string.two_args, args3); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " public static void testFragment(Fragment fragment) {\n"
+ + " fragment.getString(R.string.one_arg); // OK: Just looking up the string\n"
+ + " fragment.getString(R.string.one_arg, \"\"); // OK: Not checking non-varargs version\n"
+ + " fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+ + " }\n"
+ + "\n"
+ + " public static void testArrayTypeConversions(Context context) {\n"
+ + " context.getString(R.string.one_arg, new Object[] { 5 }); // ERROR: Wrong type\n"
+ + " context.getString(R.string.two_args, new Object[] { 5, 5.0f }); // ERROR: Wrong type\n"
+ + " }\n"
+ + "\n"
+ + " public static final class R {\n"
+ + " public static final class string {\n"
+ + " public static final int hello = 0x7f0a0000;\n"
+ + " public static final int zero_args = 0x7f0a0001;\n"
+ + " public static final int one_arg = 0x7f0a0002;\n"
+ + " public static final int two_args = 0x7f0a0003;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ xml("res/values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"hello\">Hello %1$s</string>\n"
+ + " <string name=\"zero_args\">Hello</string>\n"
+ + " <string name=\"one_arg\">Hello %1$s</string>\n"
+ + " <string name=\"two_args\">Hello %1$s %2$s</string>\n"
+ + "</resources>\n"
+ + "\n")
+ ));
+ }
+
+ /**
+ * This test checks the same behaviour as {@link #testAdditionalGetStringMethods()} when lint
+ * is running incrementally.
+ */
+ @SuppressWarnings("ClassNameDiffersFromFileName")
+ public void testAdditionalGetStringMethodsIncrementally() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/FormatCheck.java:11: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+ + " context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition does not require arguments\n"
+ + "src/test/pkg/FormatCheck.java:13: Error: Format string 'zero_args' is not a valid format string so it should not be passed to String.format [StringFormatInvalid]\n"
+ + " context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition does not require arguments\n"
+ + "src/test/pkg/FormatCheck.java:17: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+ + " context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition requires 1 arguments\n"
+ + "src/test/pkg/FormatCheck.java:18: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 0 [StringFormatMatches]\n"
+ + " context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition requires 1 arguments\n"
+ + "src/test/pkg/FormatCheck.java:20: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 2 [StringFormatMatches]\n"
+ + " context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition requires 1 arguments\n"
+ + "src/test/pkg/FormatCheck.java:22: Error: Wrong argument count, format string two_args requires 2 but format call supplies 1 [StringFormatMatches]\n"
+ + " context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition requires 2 arguments\n"
+ + "src/test/pkg/FormatCheck.java:24: Error: Wrong argument count, format string two_args requires 2 but format call supplies 0 [StringFormatMatches]\n"
+ + " context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition requires 2 arguments\n"
+ + "src/test/pkg/FormatCheck.java:26: Error: Wrong argument count, format string two_args requires 2 but format call supplies 3 [StringFormatMatches]\n"
+ + " context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition requires 2 arguments\n"
+ + "src/test/pkg/FormatCheck.java:36: Error: Wrong argument count, format string one_arg requires 1 but format call supplies 3 [StringFormatMatches]\n"
+ + " fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values/strings.xml: This definition requires 1 arguments\n"
+ + "9 errors, 0 warnings\n",
+
+ lintProjectIncrementally(
+ "src/test/pkg/FormatCheck.java",
+ java("src/test/pkg/FormatCheck.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.Fragment;\n"
+ + "import android.content.Context;\n"
+ + "\n"
+ + "\n"
+ + "\n"
+ + "public class FormatCheck {\n"
+ + " public static void testContext(Context context) {\n"
+ + " context.getString(R.string.zero_args); // OK: Just looking up the string (includes %1$s)\n"
+ + " context.getString(R.string.zero_args, \"first\"); // ERROR\n"
+ + " context.getString(R.string.zero_args, new Object[0]); // OK\n"
+ + " context.getString(R.string.zero_args, new Object[] { \"first\" }); // ERROR\n"
+ + "\n"
+ + " context.getString(R.string.one_arg); // OK: Just looking up the string (includes %1$s)\n"
+ + " context.getString(R.string.one_arg, \"first\"); // OK\n"
+ + " context.getString(R.string.one_arg, \"too\", \"many\"); // ERROR: too many arguments\n"
+ + " context.getString(R.string.one_arg, new Object[0]); // ERROR: not enough arguments\n"
+ + " context.getString(R.string.one_arg, new Object[] { \"first\" }); // OK\n"
+ + " context.getString(R.string.one_arg, new Object[] { \"first\", \"second\" }); // ERROR\n"
+ + " \n"
+ + " context.getString(R.string.two_args, \"first\"); // ERROR: too few\n"
+ + " context.getString(R.string.two_args, \"first\", \"second\"); // OK\n"
+ + " context.getString(R.string.two_args, new Object[0]); // ERROR: not enough arguments\n"
+ + " context.getString(R.string.two_args, new Object[] { \"first\", \"second\" }); // OK\n"
+ + " context.getString(R.string.two_args, new Object[] { \"first\", \"second\", \"third\" }); // ERROR\n"
+ + " String[] args2 = new String[] { \"first\", \"second\" };\n"
+ + " context.getString(R.string.two_args, args2); // OK\n"
+ + " String[] args3 = new String[] { \"first\", \"second\", \"third\" };\n"
+ + " context.getString(R.string.two_args, args3); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " public static void testFragment(Fragment fragment) {\n"
+ + " fragment.getString(R.string.one_arg); // OK: Just looking up the string\n"
+ + " fragment.getString(R.string.one_arg, \"\"); // OK: Not checking non-varargs version\n"
+ + " fragment.getString(R.string.one_arg, \"too\", \"many\", \"args\"); // ERROR: not enough arguments\n"
+ + " }\n"
+ + "\n"
+ + " public static void testArrayTypeConversions(Context context) {\n"
+ + " context.getString(R.string.one_arg, new Object[] { 5 }); // ERROR: Wrong type\n"
+ + " context.getString(R.string.two_args, new Object[] { 5, 5.0f }); // ERROR: Wrong type\n"
+ + " }\n"
+ + "\n"
+ + " public static final class R {\n"
+ + " public static final class string {\n"
+ + " public static final int hello = 0x7f0a0000;\n"
+ + " public static final int zero_args = 0x7f0a0001;\n"
+ + " public static final int one_arg = 0x7f0a0002;\n"
+ + " public static final int two_args = 0x7f0a0003;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ xml("res/values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"hello\">Hello %1$s</string>\n"
+ + " <string name=\"zero_args\">Hello</string>\n"
+ + " <string name=\"one_arg\">Hello %1$s</string>\n"
+ + " <string name=\"two_args\">Hello %1$s %2$s</string>\n"
+ + "</resources>\n"
+ + "\n")
+ ));
+ }
+
+ public void testIssue197940() throws Exception {
+ // Regression test for
+ // https://code.google.com/p/android/issues/detail?id=197940
+ // 197940: The linter alerts about a wrong String.format format, but it's ok
+ assertEquals("No warnings.",
+
+ lintProject(
+ java("src/test/pkg/FormatCheck.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.res.Resources;\n"
+ + "\n"
+ + "public class FormatCheck {\n"
+
+ + " private static String test(Resources resources) {\n"
+ + " return String.format(\"%s\", resources.getString(R.string.a_b_c));\n"
+ + " }\n"
+ + "\n"
+ + " public static final class R {\n"
+ + " public static final class string {\n"
+ + " public static final int a_b_c = 0x7f0a0000;\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ xml("res/values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"a_b_c\">A b c </string>\n\n"
+ + " <string name=\"a_b_c_2\">A %1$s c </string>\n\n"
+ + "</resources>\n"
+ + "\n")
+ ));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
new file mode 100644
index 0000000..6e61d6e
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
@@ -0,0 +1,1701 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.TAG_USES_PERMISSION;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_23;
+import static com.android.SdkConstants.TAG_USES_PERMISSION_SDK_M;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
+
+import com.android.tools.lint.ExternalAnnotationRepository;
+import com.android.tools.lint.ExternalAnnotationRepositoryTest;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
+import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("ClassNameDiffersFromFileName") // For embedded unit tests
+public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ private static final boolean SDK_ANNOTATIONS_AVAILABLE =
+ new SupportAnnotationDetectorTest().createClient().findResource(
+ ExternalAnnotationRepository.SDK_ANNOTATIONS_PATH) != null;
+
+ @Override
+ protected Detector getDetector() {
+ return new SupportAnnotationDetector();
+ }
+
+ public void testRange() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/RangeTest.java:32: Error: Expected length 5 (was 4) [Range]\n"
+ + " printExact(\"1234\"); // ERROR\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:34: Error: Expected length 5 (was 6) [Range]\n"
+ + " printExact(\"123456\"); // ERROR\n"
+ + " ~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:36: Error: Expected length ≥ 5 (was 4) [Range]\n"
+ + " printMin(\"1234\"); // ERROR\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:43: Error: Expected length ≤ 8 (was 9) [Range]\n"
+ + " printMax(\"123456789\"); // ERROR\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:45: Error: Expected length ≥ 4 (was 3) [Range]\n"
+ + " printRange(\"123\"); // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/RangeTest.java:49: Error: Expected length ≤ 6 (was 7) [Range]\n"
+ + " printRange(\"1234567\"); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:53: Error: Expected size 5 (was 4) [Range]\n"
+ + " printExact(new int[]{1, 2, 3, 4}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:55: Error: Expected size 5 (was 6) [Range]\n"
+ + " printExact(new int[]{1, 2, 3, 4, 5, 6}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:57: Error: Expected size ≥ 5 (was 4) [Range]\n"
+ + " printMin(new int[]{1, 2, 3, 4}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:65: Error: Expected size ≤ 8 (was 9) [Range]\n"
+ + " printMax(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:67: Error: Expected size ≥ 4 (was 3) [Range]\n"
+ + " printRange(new int[] {1,2,3}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:71: Error: Expected size ≤ 6 (was 7) [Range]\n"
+ + " printRange(new int[] {1,2,3,4,5,6,7}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:74: Error: Expected size to be a multiple of 3 (was 4 and should be either 3 or 6) [Range]\n"
+ + " printMultiple(new int[] {1,2,3,4}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:75: Error: Expected size to be a multiple of 3 (was 5 and should be either 3 or 6) [Range]\n"
+ + " printMultiple(new int[] {1,2,3,4,5}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:77: Error: Expected size to be a multiple of 3 (was 7 and should be either 6 or 9) [Range]\n"
+ + " printMultiple(new int[] {1,2,3,4,5,6,7}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:80: Error: Expected size ≥ 4 (was 3) [Range]\n"
+ + " printMinMultiple(new int[]{1, 2, 3}); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:84: Error: Value must be ≥ 4 (was 3) [Range]\n"
+ + " printAtLeast(3); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/RangeTest.java:91: Error: Value must be ≤ 7 (was 8) [Range]\n"
+ + " printAtMost(8); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/RangeTest.java:93: Error: Value must be ≥ 4 (was 3) [Range]\n"
+ + " printBetween(3); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/RangeTest.java:98: Error: Value must be ≤ 7 (was 8) [Range]\n"
+ + " printBetween(8); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/RangeTest.java:102: Error: Value must be ≥ 2.5 (was 2.49) [Range]\n"
+ + " printAtLeastInclusive(2.49f); // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/RangeTest.java:106: Error: Value must be > 2.5 (was 2.49) [Range]\n"
+ + " printAtLeastExclusive(2.49f); // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/RangeTest.java:107: Error: Value must be > 2.5 (was 2.5) [Range]\n"
+ + " printAtLeastExclusive(2.5f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:113: Error: Value must be ≤ 7.0 (was 7.1) [Range]\n"
+ + " printAtMostInclusive(7.1f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:117: Error: Value must be < 7.0 (was 7.0) [Range]\n"
+ + " printAtMostExclusive(7.0f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:118: Error: Value must be < 7.0 (was 7.1) [Range]\n"
+ + " printAtMostExclusive(7.1f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:120: Error: Value must be ≥ 2.5 (was 2.4) [Range]\n"
+ + " printBetweenFromInclusiveToInclusive(2.4f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:124: Error: Value must be ≤ 5.0 (was 5.1) [Range]\n"
+ + " printBetweenFromInclusiveToInclusive(5.1f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:126: Error: Value must be > 2.5 (was 2.4) [Range]\n"
+ + " printBetweenFromExclusiveToInclusive(2.4f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:127: Error: Value must be > 2.5 (was 2.5) [Range]\n"
+ + " printBetweenFromExclusiveToInclusive(2.5f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:129: Error: Value must be ≤ 5.0 (was 5.1) [Range]\n"
+ + " printBetweenFromExclusiveToInclusive(5.1f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:131: Error: Value must be ≥ 2.5 (was 2.4) [Range]\n"
+ + " printBetweenFromInclusiveToExclusive(2.4f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:135: Error: Value must be < 5.0 (was 5.0) [Range]\n"
+ + " printBetweenFromInclusiveToExclusive(5.0f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:137: Error: Value must be > 2.5 (was 2.4) [Range]\n"
+ + " printBetweenFromExclusiveToExclusive(2.4f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:138: Error: Value must be > 2.5 (was 2.5) [Range]\n"
+ + " printBetweenFromExclusiveToExclusive(2.5f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:141: Error: Value must be < 5.0 (was 5.0) [Range]\n"
+ + " printBetweenFromExclusiveToExclusive(5.0f); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangeTest.java:145: Error: Value must be ≥ 4 (was -7) [Range]\n"
+ + " printBetween(-7); // ERROR\n"
+ + " ~~\n"
+ + "src/test/pkg/RangeTest.java:146: Error: Value must be > 2.5 (was -10.0) [Range]\n"
+ + " printAtLeastExclusive(-10.0f); // ERROR\n"
+ + " ~~~~~~\n"
+ + "src/test/pkg/RangeTest.java:156: Error: Value must be ≥ -1 (was -2) [Range]\n"
+ + " printIndirect(-2); // ERROR\n"
+ + " ~~\n"
+ + "src/test/pkg/RangeTest.java:157: Error: Value must be ≤ 42 (was 43) [Range]\n"
+ + " printIndirect(43); // ERROR\n"
+ + " ~~\n"
+ + "src/test/pkg/RangeTest.java:158: Error: Expected length 5 (was 7) [Range]\n"
+ + " printIndirectSize(\"1234567\"); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "41 errors, 0 warnings\n",
+
+ lintProject("src/test/pkg/RangeTest.java.txt=>src/test/pkg/RangeTest.java",
+ "src/android/support/annotation/Size.java.txt=>src/android/support/annotation/Size.java",
+ "src/android/support/annotation/IntRange.java.txt=>src/android/support/annotation/IntRange.java",
+ "src/android/support/annotation/FloatRange.java.txt=>src/android/support/annotation/FloatRange.java"
+ ));
+ }
+
+ public void testTypeDef() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/IntDefTest.java:31: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setStyle(0, 0); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/IntDefTest.java:32: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setStyle(-1, 0); // ERROR\n"
+ + " ~~\n"
+ + "src/test/pkg/IntDefTest.java:33: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setStyle(UNRELATED, 0); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:34: Error: Must be one of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setStyle(IntDefTest.UNRELATED, 0); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:35: Error: Flag not allowed here [WrongConstant]\n"
+ + " setStyle(IntDefTest.STYLE_NORMAL|STYLE_NO_FRAME, 0); // ERROR: Not a flag\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:36: Error: Flag not allowed here [WrongConstant]\n"
+ + " setStyle(~STYLE_NO_FRAME, 0); // ERROR: Not a flag\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:55: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setFlags(\"\", UNRELATED); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:56: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setFlags(\"\", UNRELATED|STYLE_NO_TITLE); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:57: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setFlags(\"\", STYLE_NORMAL|STYLE_NO_TITLE|UNRELATED); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:58: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setFlags(\"\", 1); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/IntDefTest.java:59: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setFlags(\"\", arg < 0 ? STYLE_NORMAL : UNRELATED); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:60: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setFlags(\"\", arg < 0 ? UNRELATED : STYLE_NORMAL); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:79: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
+ + " setTitle(\"\", UNRELATED_TYPE); // ERROR\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:80: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
+ + " setTitle(\"\", \"type2\"); // ERROR\n"
+ + " ~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:87: Error: Must be one of: IntDefTest.TYPE_1, IntDefTest.TYPE_2 [WrongConstant]\n"
+ + " setTitle(\"\", type); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/IntDefTest.java:92: Error: Must be one or more of: IntDefTest.STYLE_NORMAL, IntDefTest.STYLE_NO_TITLE, IntDefTest.STYLE_NO_FRAME, IntDefTest.STYLE_NO_INPUT [WrongConstant]\n"
+ + " setFlags(\"\", flag); // ERROR\n"
+ + " ~~~~\n"
+ + (SDK_ANNOTATIONS_AVAILABLE ?
+ "src/test/pkg/IntDefTest.java:99: Error: Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE [WrongConstant]\n"
+ + " view.setLayoutDirection(View.TEXT_DIRECTION_LTR); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:100: Error: Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE [WrongConstant]\n"
+ + " view.setLayoutDirection(0); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/IntDefTest.java:101: Error: Flag not allowed here [WrongConstant]\n"
+ + " view.setLayoutDirection(View.LAYOUT_DIRECTION_LTR|View.LAYOUT_DIRECTION_RTL); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/IntDefTest.java:102: Error: Must be one of: Context.POWER_SERVICE, Context.WINDOW_SERVICE, Context.LAYOUT_INFLATER_SERVICE, Context.ACCOUNT_SERVICE, Context.ACTIVITY_SERVICE, Context.ALARM_SERVICE, Context.NOTIFICATION_SERVICE, Context.ACCESSIBILITY_SERVICE, Context.CAPTIONING_SERVICE, Context.KEYGUARD_SERVICE, Context.LOCATION_SERVICE, Context.SEARCH_SERVICE, Context.SENSOR_SERVICE, Context.STORAGE_SERVICE, Context.WALLPAPER_SERVICE, Context.VIBRATOR_SERV [...]
+ + " context.getSystemService(TYPE_1); // ERROR\n"
+ + " ~~~~~~\n"
+ + "20 errors, 0 warnings\n" :
+ "16 errors, 0 warnings\n"),
+
+ lintProject("src/test/pkg/IntDefTest.java.txt=>src/test/pkg/IntDefTest.java",
+ "src/android/support/annotation/IntDef.java.txt=>src/android/support/annotation/IntDef.java",
+ "src/android/support/annotation/StringDef.java.txt=>src/android/support/annotation/StringDef.java"
+ ));
+ }
+
+ public void testColorInt() throws Exception {
+ // Needs updated annotations!
+ assertEquals((SDK_ANNOTATIONS_AVAILABLE ? ""
+ + "src/test/pkg/WrongColor.java:9: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
+ + " paint2.setColor(R.color.blue);\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/WrongColor.java:11: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.red) [ResourceAsColor]\n"
+ + " textView.setTextColor(R.color.red);\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/WrongColor.java:12: Error: Should pass resolved color instead of resource id here: getResources().getColor(android.R.color.black) [ResourceAsColor]\n"
+ + " textView.setTextColor(android.R.color.black);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/WrongColor.java:13: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
+ + " textView.setTextColor(foo > 0 ? R.color.green : R.color.blue);\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/WrongColor.java:13: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.green) [ResourceAsColor]\n"
+ + " textView.setTextColor(foo > 0 ? R.color.green : R.color.blue);\n"
+ + " ~~~~~~~~~~~~~\n" : "")
+ + "src/test/pkg/WrongColor.java:21: Error: Should pass resolved color instead of resource id here: getResources().getColor(R.color.blue) [ResourceAsColor]\n"
+ + " foo2(R.color.blue);\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/WrongColor.java:20: Error: Expected resource of type color [ResourceType]\n"
+ + " foo1(0xffff0000);\n"
+ + " ~~~~~~~~~~\n"
+ + (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "2 errors, 0 warnings\n"),
+
+ lintProject(
+ copy("src/test/pkg/WrongColor.java.txt", "src/test/pkg/WrongColor.java"),
+ copy("src/android/support/annotation/ColorInt.java.txt", "src/android/support/annotation/ColorInt.java"),
+ mColorResAnnotation
+ ));
+ }
+
+ public void testColorInt2() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ColorTest.java:23: Error: Should pass resolved color instead of resource id here: getResources().getColor(actualColor) [ResourceAsColor]\n"
+ + " setColor2(actualColor); // ERROR\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/ColorTest.java:24: Error: Should pass resolved color instead of resource id here: getResources().getColor(getColor2()) [ResourceAsColor]\n"
+ + " setColor2(getColor2()); // ERROR\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/ColorTest.java:17: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
+ + " setColor1(actualColor); // ERROR\n"
+ + " ~~~~~~~~~~~\n"
+ + "src/test/pkg/ColorTest.java:18: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
+ + " setColor1(getColor1()); // ERROR\n"
+ + " ~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ColorTest.java", ""
+ + "package test.pkg;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.res.Resources;\n"
+ + "import android.support.annotation.ColorInt;\n"
+ + "import android.support.annotation.ColorRes;\n"
+ + "\n"
+ + "public abstract class ColorTest {\n"
+ + " @ColorInt\n"
+ + " public abstract int getColor1();\n"
+ + " public abstract void setColor1(@ColorRes int color);\n"
+ + " @ColorRes\n"
+ + " public abstract int getColor2();\n"
+ + " public abstract void setColor2(@ColorInt int color);\n"
+ + "\n"
+ + " public void test1(Context context) {\n"
+ + " int actualColor = getColor1();\n"
+ + " setColor1(actualColor); // ERROR\n"
+ + " setColor1(getColor1()); // ERROR\n"
+ + " setColor1(getColor2()); // OK\n"
+ + " }\n"
+ + " public void test2(Context context) {\n"
+ + " int actualColor = getColor2();\n"
+ + " setColor2(actualColor); // ERROR\n"
+ + " setColor2(getColor2()); // ERROR\n"
+ + " setColor2(getColor1()); // OK\n"
+ + " }\n"
+ + "}\n"),
+ mColorResAnnotation,
+ mColorIntAnnotation
+ ));
+ }
+
+ public void testColorInt3() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=176321
+
+ if (!SDK_ANNOTATIONS_AVAILABLE) {
+ return;
+ }
+ assertEquals(""
+ + "src/test/pkg/ColorTest.java:11: Error: Expected a color resource id (R.color.) but received an RGB integer [ResourceType]\n"
+ + " setColor(actualColor);\n"
+ + " ~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ColorTest.java", ""
+ + "package test.pkg;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.res.Resources;\n"
+ + "import android.support.annotation.ColorRes;\n"
+ + "\n"
+ + "public abstract class ColorTest {\n"
+ + " public abstract void setColor(@ColorRes int color);\n"
+ + "\n"
+ + " public void test(Context context, @ColorRes int id) {\n"
+ + " int actualColor = context.getResources().getColor(id, null);\n"
+ + " setColor(actualColor);\n"
+ + " }\n"
+ + "}\n"),
+ mColorResAnnotation
+ ));
+ }
+ public void testResourceType() throws Exception {
+ assertEquals((SDK_ANNOTATIONS_AVAILABLE ? ""
+ + "src/p1/p2/Flow.java:13: Error: Expected resource of type drawable [ResourceType]\n"
+ + " resources.getDrawable(10); // ERROR\n"
+ + " ~~\n"
+ + "src/p1/p2/Flow.java:18: Error: Expected resource of type drawable [ResourceType]\n"
+ + " resources.getDrawable(R.string.my_string); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n" : "")
+ + "src/p1/p2/Flow.java:22: Error: Expected resource of type drawable [ResourceType]\n"
+ + " myMethod(R.string.my_string, null); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/p1/p2/Flow.java:26: Error: Expected resource of type drawable [ResourceType]\n"
+ + " resources.getDrawable(R.string.my_string); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/p1/p2/Flow.java:32: Error: Expected resource identifier (R.type.name) [ResourceType]\n"
+ + " myAnyResMethod(50); // ERROR\n"
+ + " ~~\n"
+ + (SDK_ANNOTATIONS_AVAILABLE ? "src/p1/p2/Flow.java:60: Error: Expected resource of type drawable [ResourceType]\n"
+ + " resources.getDrawable(MimeTypes.getAnnotatedString()); // Error\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" : "")
+ + "src/p1/p2/Flow.java:68: Error: Expected resource of type drawable [ResourceType]\n"
+ + " myMethod(z, null); // ERROR\n"
+ + " ~\n"
+ + (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "4 errors, 0 warnings\n"),
+
+ lintProject(
+ copy("src/p1/p2/Flow.java.txt", "src/p1/p2/Flow.java"),
+ copy("src/android/support/annotation/DrawableRes.java.txt", "src/android/support/annotation/DrawableRes.java"),
+ mStringResAnnotation,
+ mStyleResAnnotation,
+ mAnyResAnnotation
+ ));
+ }
+
+ public void testTypes2() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ActivityType.java:5: Error: Expected resource of type drawable [ResourceType]\n"
+ + " SKI(1),\n"
+ + " ~\n"
+ + "src/test/pkg/ActivityType.java:6: Error: Expected resource of type drawable [ResourceType]\n"
+ + " SNOWBOARD(2);\n"
+ + " ~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ActivityType.java", ""
+ + "import android.support.annotation.DrawableRes;\n"
+ + "\n"
+ + "enum ActivityType {\n"
+ + "\n"
+ + " SKI(1),\n"
+ + " SNOWBOARD(2);\n"
+ + "\n"
+ + " private final int mIconResId;\n"
+ + "\n"
+ + " ActivityType(@DrawableRes int iconResId) {\n"
+ + " mIconResId = iconResId;\n"
+ + " }\n"
+ + "}"),
+ copy("src/android/support/annotation/DrawableRes.java.txt",
+ "src/android/support/annotation/DrawableRes.java")));
+ }
+
+ // Temporarily disabled; TypedArray.getResourceId has now been annotated with @StyleRes
+ //public void testResourceTypesIssue182433() throws Exception {
+ // // Regression test for https://code.google.com/p/android/issues/detail?id=182433
+ // assertEquals("No warnings.",
+ // lintProject(
+ // java("src/test/pkg/ResourceTypeTest.java", ""
+ // + "package test.pkg;\n"
+ // + "import android.app.Activity;\n"
+ // + "import android.content.res.TypedArray;\n"
+ // + "\n"
+ // + "@SuppressWarnings(\"unused\")\n"
+ // + "public class ResourceTypeTest extends Activity {\n"
+ // + " public static void test(TypedArray typedArray) {\n"
+ // + " typedArray.getResourceId(2 /* index */, 0 /* invalid drawableRes */);\n"
+ // + " }\n"
+ // + "}\n"),
+ // mAnyResAnnotation
+ // ));
+ //}
+
+ @SuppressWarnings({"MethodMayBeStatic", "ResultOfObjectAllocationIgnored"})
+ public void testConstructor() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ConstructorTest.java:14: Error: Expected resource of type drawable [ResourceType]\n"
+ + " new ConstructorTest(1, 3);\n"
+ + " ~\n"
+ + "src/test/pkg/ConstructorTest.java:14: Error: Value must be ≥ 5 (was 3) [Range]\n"
+ + " new ConstructorTest(1, 3);\n"
+ + " ~\n"
+ + "src/test/pkg/ConstructorTest.java:19: Error: Method test.pkg.ConstructorTest must be called from the UI thread, currently inferred thread is worker thread [WrongThread]\n"
+ + " new ConstructorTest(res, range);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ConstructorTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.DrawableRes;\n"
+ + "import android.support.annotation.IntRange;\n"
+ + "import android.support.annotation.UiThread;\n"
+ + "import android.support.annotation.WorkerThread;\n"
+ + "\n"
+ + "public class ConstructorTest {\n"
+ + " @UiThread\n"
+ + " ConstructorTest(@DrawableRes int iconResId, @IntRange(from = 5) int start) {\n"
+ + " }\n"
+ + "\n"
+ + " public void testParameters() {\n"
+ + " new ConstructorTest(1, 3);\n"
+ + " }\n"
+ + "\n"
+ + " @WorkerThread\n"
+ + " public void testMethod(int res, int range) {\n"
+ + " new ConstructorTest(res, range);\n"
+ + " }\n"
+ + "}\n"),
+ mWorkerThreadPermission,
+ mUiThreadPermission,
+ copy("src/android/support/annotation/DrawableRes.java.txt",
+ "src/android/support/annotation/DrawableRes.java"),
+ copy("src/android/support/annotation/IntRange.java.txt",
+ "src/android/support/annotation/IntRange.java")
+ ));
+ }
+
+ public void testColorAsDrawable() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject("src/p1/p2/ColorAsDrawable.java.txt=>src/p1/p2/ColorAsDrawable.java"));
+ }
+
+ public void testCheckResult() throws Exception {
+ if (!SDK_ANNOTATIONS_AVAILABLE) {
+ // Currently only tests @CheckResult on SDK annotations
+ return;
+ }
+ assertEquals(""
+ + "src/test/pkg/CheckPermissions.java:22: Warning: The result of extractAlpha is not used [CheckResult]\n"
+ + " bitmap.extractAlpha(); // WARNING\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/CheckPermissions.java:10: Warning: The result of checkCallingOrSelfPermission is not used; did you mean to call #enforceCallingOrSelfPermission(String,String)? [UseCheckPermission]\n"
+ + " context.checkCallingOrSelfPermission(Manifest.permission.INTERNET); // WRONG\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/CheckPermissions.java:11: Warning: The result of checkPermission is not used; did you mean to call #enforcePermission(String,int,int,String)? [UseCheckPermission]\n"
+ + " context.checkPermission(Manifest.permission.INTERNET, 1, 1);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 3 warnings\n",
+
+ lintProject("src/test/pkg/CheckPermissions.java.txt=>src/test/pkg/CheckPermissions.java"));
+ }
+
+ private final TestFile mPermissionTest = java("src/test/pkg/PermissionTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.location.LocationManager;\n"
+ + "\n"
+ + "public class PermissionTest {\n"
+ + " public static void test(LocationManager locationManager, String provider) {\n"
+ + " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ + " }\n"
+ + "}\n");
+
+ private final TestFile mLocationManagerStub = java("src/android/location/LocationManager.java", ""
+ + "package android.location;\n"
+ + "\n"
+ + "import android.support.annotation.RequiresPermission;\n"
+ + "\n"
+ + "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
+ + "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
+ + "\n"
+ + "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ + "public abstract class LocationManager {\n"
+ + " @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})\n"
+ + " public abstract Location myMethod(String provider);\n"
+ + " public static class Location {\n"
+ + " }\n"
+ + "}\n");
+
+ private final TestFile mComplexLocationManagerStub = java("src/android/location/LocationManager.java", ""
+ + "package android.location;\n"
+ + "\n"
+ + "import android.support.annotation.RequiresPermission;\n"
+ + "\n"
+ + "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
+ + "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
+ + "import static android.Manifest.permission.BLUETOOTH;\n"
+ + "import static android.Manifest.permission.READ_SMS;\n"
+ + "\n"
+ + "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ + "public abstract class LocationManager {\n"
+ + " @RequiresPermission(\"(\" + ACCESS_FINE_LOCATION + \"|| \" + ACCESS_COARSE_LOCATION + \") && (\" + BLUETOOTH + \" ^ \" + READ_SMS + \")\")\n"
+ + " public abstract Location myMethod(String provider);\n"
+ + " public static class Location {\n"
+ + " }\n"
+ + "}\n");
+
+ private final TestFile mRequirePermissionAnnotation = java("src/android/support/annotation/RequiresPermission.java", ""
+ + "/*\n"
+ + " * Copyright (C) 2015 The Android Open Source Project\n"
+ + " *\n"
+ + " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+ + " * you may not use this file except in compliance with the License.\n"
+ + " * You may obtain a copy of the License at\n"
+ + " *\n"
+ + " * http://www.apache.org/licenses/LICENSE-2.0\n"
+ + " *\n"
+ + " * Unless required by applicable law or agreed to in writing, software\n"
+ + " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+ + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+ + " * See the License for the specific language governing permissions and\n"
+ + " * limitations under the License.\n"
+ + " */\n"
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.*;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({METHOD,CONSTRUCTOR,FIELD,PARAMETER,ANNOTATION_TYPE})\n"
+ + "public @interface RequiresPermission {\n"
+ + " String value() default \"\";\n"
+ + " String[] allOf() default {};\n"
+ + " String[] anyOf() default {};\n"
+ + " boolean conditional() default false;\n"
+ + " String notes() default \"\";\n"
+ + " @Target({FIELD,METHOD,PARAMETER})\n"
+ + " @interface Read {\n"
+ + " RequiresPermission value();\n"
+ + " }\n"
+ + " @Target({FIELD,METHOD,PARAMETER})\n"
+ + " @interface Write {\n"
+ + " RequiresPermission value();\n"
+ + " }\n"
+ + "}");
+
+ private final TestFile mUiThreadPermission = java("src/android/support/annotation/UiThread.java", ""
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
+ + "import static java.lang.annotation.ElementType.METHOD;\n"
+ + "import static java.lang.annotation.ElementType.TYPE;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
+ + "public @interface UiThread {\n"
+ + "}\n");
+
+ private final TestFile mMainThreadPermission = java("src/android/support/annotation/MainThread.java", ""
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
+ + "import static java.lang.annotation.ElementType.METHOD;\n"
+ + "import static java.lang.annotation.ElementType.TYPE;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
+ + "public @interface MainThread {\n"
+ + "}\n");
+
+ private final TestFile mWorkerThreadPermission = java("src/android/support/annotation/WorkerThread.java", ""
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.CONSTRUCTOR;\n"
+ + "import static java.lang.annotation.ElementType.METHOD;\n"
+ + "import static java.lang.annotation.ElementType.TYPE;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({METHOD,CONSTRUCTOR,TYPE})\n"
+ + "public @interface WorkerThread {\n"
+ + "}\n");
+
+ private TestFile createResAnnotation(String prefix) {
+ return java("src/android/support/annotation/" + prefix + "Res.java", ""
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.*;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
+ + "public @interface " + prefix + "Res {\n"
+ + "}\n");
+ }
+
+ private final TestFile mColorResAnnotation = createResAnnotation("Color");
+ private final TestFile mStringResAnnotation = createResAnnotation("String");
+ private final TestFile mStyleResAnnotation = createResAnnotation("Style");
+ private final TestFile mAnyResAnnotation = createResAnnotation("Any");
+
+ private final TestFile mColorIntAnnotation = java("src/android/support/annotation/ColorInt.java", ""
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.*;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
+ + "public @interface ColorInt {\n"
+ + "}\n");
+
+ private TestFile getManifestWithPermissions(int targetSdk, String... permissions) {
+ return getManifestWithPermissions(1, targetSdk, permissions);
+ }
+
+ private TestFile getManifestWithPermissions(int minSdk, int targetSdk, String... permissions) {
+ StringBuilder permissionBlock = new StringBuilder();
+ for (String permission : permissions) {
+ permissionBlock.append(" <uses-permission android:name=\"").append(permission)
+ .append("\" />\n");
+ }
+ return xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"foo.bar2\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"" + minSdk + "\" android:targetSdkVersion=\""
+ + targetSdk + "\" />\n"
+ + "\n"
+ + permissionBlock.toString()
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>");
+ }
+
+ private TestFile mRevokeTest = java("src/test/pkg/RevokeTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.Context;\n"
+ + "import android.content.pm.PackageManager;\n"
+ + "import android.location.LocationManager;\n"
+ + "import java.io.IOException;\n"
+ + "import java.security.AccessControlException;\n"
+ + "\n"
+ + "public class RevokeTest {\n"
+ + " public static void test1(LocationManager locationManager, String provider) {\n"
+ + " try {\n"
+ + " // Ok: Security exception caught in one of the branches\n"
+ + " locationManager.myMethod(provider); // OK\n"
+ + " } catch (IllegalArgumentException ignored) {\n"
+ + " } catch (SecurityException ignored) {\n"
+ + " }\n"
+ + "\n"
+ + " try {\n"
+ + " // You have to catch SecurityException explicitly, not parent\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " } catch (RuntimeException e) { // includes Security Exception\n"
+ + " }\n"
+ + "\n"
+ + " try {\n"
+ + " // Ok: Caught in outer statement\n"
+ + " try {\n"
+ + " locationManager.myMethod(provider); // OK\n"
+ + " } catch (IllegalArgumentException e) {\n"
+ + " // inner\n"
+ + " }\n"
+ + " } catch (SecurityException ignored) {\n"
+ + " }\n"
+ + "\n"
+ + " try {\n"
+ + " // You have to catch SecurityException explicitly, not parent\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " } catch (Exception e) { // includes Security Exception\n"
+ + " }\n"
+ + "\n"
+ + " // NOT OK: Catching security exception subclass (except for dedicated ones?)\n"
+ + "\n"
+ + " try {\n"
+ + " // Error: catching security exception, but not all of them\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " } catch (AccessControlException e) { // security exception but specific one\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static void test2(LocationManager locationManager, String provider) {\n"
+ + " locationManager.myMethod(provider); // ERROR: not caught\n"
+ + " }\n"
+ + "\n"
+ + " public static void test3(LocationManager locationManager, String provider)\n"
+ + " throws IllegalArgumentException {\n"
+ + " locationManager.myMethod(provider); // ERROR: not caught by right type\n"
+ + " }\n"
+ + "\n"
+ + " public static void test4(LocationManager locationManager, String provider)\n"
+ + " throws AccessControlException { // Security exception but specific one\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " public static void test5(LocationManager locationManager, String provider)\n"
+ + " throws SecurityException {\n"
+ + " locationManager.myMethod(provider); // OK\n"
+ + " }\n"
+ + "\n"
+ + " public static void test6(LocationManager locationManager, String provider)\n"
+ + " throws Exception { // includes Security Exception\n"
+ + " // You have to throw SecurityException explicitly, not parent\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " public static void test7(LocationManager locationManager, String provider, Context context)\n"
+ + " throws IllegalArgumentException {\n"
+ + " if (context.getPackageManager().checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, context.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n"
+ + " return;\n"
+ + " }\n"
+ + " locationManager.myMethod(provider); // OK: permission checked\n"
+ + " }\n"
+ + "\n"
+ + " public void test8(LocationManager locationManager, String provider) {\n"
+ + " // Regression test for http://b.android.com/187204\n"
+ + " try {\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " mightThrow();\n"
+ + " } catch (SecurityException | IOException se) { // OK: Checked in multi catch\n"
+ + " }\n"
+ + " try {\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " mightThrow();\n"
+ + " } catch (IOException | SecurityException se) { // OK: Checked in multi catch\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public void mightThrow() throws IOException {\n"
+ + " }\n"
+ + "\n"
+ + "}\n");
+
+ public void testMissingPermissions() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION [MissingPermission]\n"
+ + " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ getManifestWithPermissions(14),
+ mPermissionTest,
+ mLocationManagerStub,
+ mRequirePermissionAnnotation));
+ }
+
+ public void testHasPermission() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ getManifestWithPermissions(14, "android.permission.ACCESS_FINE_LOCATION"),
+ mPermissionTest,
+ mLocationManagerStub,
+ mRequirePermissionAnnotation));
+ }
+
+ public void testRevokePermissions() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/RevokeTest.java:20: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RevokeTest.java:36: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RevokeTest.java:44: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RevokeTest.java:50: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+ + " locationManager.myMethod(provider); // ERROR: not caught\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RevokeTest.java:55: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+ + " locationManager.myMethod(provider); // ERROR: not caught by right type\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RevokeTest.java:60: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/RevokeTest.java:71: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]\n"
+ + " locationManager.myMethod(provider); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "7 errors, 0 warnings\n",
+ lintProject(
+ getManifestWithPermissions(23, "android.permission.ACCESS_FINE_LOCATION"),
+ mLocationManagerStub,
+ mRequirePermissionAnnotation,
+ mRevokeTest
+ ));
+ }
+
+ public void testImpliedPermissions() throws Exception {
+ // Regression test for
+ // https://code.google.com/p/android/issues/detail?id=177381
+ assertEquals(""
+ + "src/test/pkg/PermissionTest2.java:11: Error: Missing permissions required by PermissionTest2.method1: my.permission.PERM2 [MissingPermission]\n"
+ + " method1(); // ERROR\n"
+ + " ~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ getManifestWithPermissions(14, 14, "android.permission.ACCESS_FINE_LOCATION"),
+ java("src/test/pkg/PermissionTest2.java", ""
+ + "package test.pkg;\n"
+ + "import android.support.annotation.RequiresPermission;\n"
+ + "\n"
+ + "public class PermissionTest2 {\n"
+ + " @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n"
+ + " public void method1() {\n"
+ + " }\n"
+ + "\n"
+ + " @RequiresPermission(\"my.permission.PERM1\")\n"
+ + " public void method2() {\n"
+ + " method1(); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n"
+ + " public void method3() {\n"
+ + " // The above @RequiresPermission implies that we are holding these\n"
+ + " // permissions here, so the call to method1() should not be flagged as\n"
+ + " // missing a permission!\n"
+ + " method1(); // OK\n"
+ + " }\n"
+ + "}\n"),
+ mRequirePermissionAnnotation
+ ));
+ }
+
+ public void testRevokePermissionsPre23() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ getManifestWithPermissions(14, "android.permission.ACCESS_FINE_LOCATION"),
+ mLocationManagerStub,
+ mRequirePermissionAnnotation,
+ mRevokeTest
+ ));
+ }
+
+ public void testComplexPermission1() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
+ + " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ getManifestWithPermissions(14,
+ "android.permission.ACCESS_FINE_LOCATION"),
+ mPermissionTest,
+ mComplexLocationManagerStub,
+ mRequirePermissionAnnotation));
+ }
+
+ public void testComplexPermission2() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ getManifestWithPermissions(14,
+ "android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.BLUETOOTH"),
+ mPermissionTest,
+ mComplexLocationManagerStub,
+ mRequirePermissionAnnotation));
+ }
+
+ public void testComplexPermission3() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
+ + " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ getManifestWithPermissions(14,
+ "android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.BLUETOOTH",
+ "android.permission.READ_SMS"),
+ mPermissionTest,
+ mComplexLocationManagerStub,
+ mRequirePermissionAnnotation));
+ }
+
+ public void testUsesPermissionSdk23() throws Exception {
+ TestFile manifest = getManifestWithPermissions(14,
+ "android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.BLUETOOTH");
+ String contents = manifest.getContents();
+ assertNotNull(contents);
+ String s = contents.replace(TAG_USES_PERMISSION, TAG_USES_PERMISSION_SDK_23);
+ manifest.withSource(s);
+ assertEquals("No warnings.",
+ lintProject(
+ manifest,
+ mPermissionTest,
+ mComplexLocationManagerStub,
+ mRequirePermissionAnnotation));
+ }
+
+ public void testUsesPermissionSdkM() throws Exception {
+ TestFile manifest = getManifestWithPermissions(14,
+ "android.permission.ACCESS_FINE_LOCATION",
+ "android.permission.BLUETOOTH");
+ String contents = manifest.getContents();
+ assertNotNull(contents);
+ String s = contents.replace(TAG_USES_PERMISSION, TAG_USES_PERMISSION_SDK_M);
+ manifest.withSource(s);
+ assertEquals("No warnings.",
+ lintProject(
+ manifest,
+ mPermissionTest,
+ mComplexLocationManagerStub,
+ mRequirePermissionAnnotation));
+ }
+
+ public void testPermissionAnnotation() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/LocationManager.java:24: Error: Missing permissions required by LocationManager.getLastKnownLocation: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION [MissingPermission]\n"
+ + " Location location = manager.getLastKnownLocation(\"provider\");\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ java("src/test/pkg/LocationManager.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.RequiresPermission;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
+ + "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
+ + "\n"
+ + "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ + "public abstract class LocationManager {\n"
+ + " @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " @interface AnyLocationPermission {\n"
+ + " }\n"
+ + "\n"
+ + " @AnyLocationPermission\n"
+ + " public abstract Location getLastKnownLocation(String provider);\n"
+ + " public static class Location {\n"
+ + " }\n"
+ + " \n"
+ + " public static void test(LocationManager manager) {\n"
+ + " Location location = manager.getLastKnownLocation(\"provider\");\n"
+ + " }\n"
+ + "}\n"),
+ mRequirePermissionAnnotation));
+ }
+
+ public void testThreading() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ThreadTest.java:15: Error: Method onPreExecute must be called from the main thread, currently inferred thread is worker thread [WrongThread]\n"
+ + " onPreExecute(); // ERROR\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ThreadTest.java:16: Error: Method paint must be called from the UI thread, currently inferred thread is worker thread [WrongThread]\n"
+ + " view.paint(); // ERROR\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/ThreadTest.java:22: Error: Method publishProgress must be called from the worker thread, currently inferred thread is main thread [WrongThread]\n"
+ + " publishProgress(); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ThreadTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.MainThread;\n"
+ + "import android.support.annotation.UiThread;\n"
+ + "import android.support.annotation.WorkerThread;\n"
+ + "\n"
+ + "public class ThreadTest {\n"
+ + " public static AsyncTask testTask() {\n"
+ + "\n"
+ + " return new AsyncTask() {\n"
+ + " final CustomView view = new CustomView();\n"
+ + "\n"
+ + " @Override\n"
+ + " protected void doInBackground(Object... params) {\n"
+ + " onPreExecute(); // ERROR\n"
+ + " view.paint(); // ERROR\n"
+ + " publishProgress(); // OK\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " protected void onPreExecute() {\n"
+ + " publishProgress(); // ERROR\n"
+ + " onProgressUpdate(); // OK\n"
+ + " // Suppressed via older Android Studio inspection id:\n"
+ + " //noinspection ResourceType\n"
+ + " publishProgress(); // SUPPRESSED\n"
+ + " // Suppressed via new lint id:\n"
+ + " //noinspection WrongThread\n"
+ + " publishProgress(); // SUPPRESSED\n"
+ + " // Suppressed via Studio inspection id:\n"
+ + " //noinspection AndroidLintWrongThread\n"
+ + " publishProgress(); // SUPPRESSED\n"
+ + " }\n"
+ + " };\n"
+ + " }\n"
+ + "\n"
+ + " @UiThread\n"
+ + " public static class View {\n"
+ + " public void paint() {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public static class CustomView extends View {\n"
+ + " @Override public void paint() {\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " public abstract static class AsyncTask {\n"
+ + " @WorkerThread\n"
+ + " protected abstract void doInBackground(Object... params);\n"
+ + "\n"
+ + " @MainThread\n"
+ + " protected void onPreExecute() {\n"
+ + " }\n"
+ + "\n"
+ + " @MainThread\n"
+ + " protected void onProgressUpdate(Object... values) {\n"
+ + " }\n"
+ + "\n"
+ + " @WorkerThread\n"
+ + " protected final void publishProgress(Object... values) {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ mUiThreadPermission,
+ mMainThreadPermission,
+ mWorkerThreadPermission));
+ }
+
+ public void testIntentPermission() throws Exception {
+ if (SDK_ANNOTATIONS_AVAILABLE) {
+ TestLintClient client = createClient();
+ ExternalAnnotationRepository repository = ExternalAnnotationRepository.get(client);
+ ResolvedMethod method = ExternalAnnotationRepositoryTest.createMethod(
+ "android.content.Context", "void", "startActivity",
+ "android.content.Intent");
+ ResolvedAnnotation a = repository.getAnnotation(method, 0, PERMISSION_ANNOTATION);
+ if (a == null) {
+ // Running tests from outside the IDE (where it can't find the
+ // bundled up to date annotations in tools/adt/idea/android/annotations)
+ // and we have the annotations.zip file available in platform-tools,
+ // but its contents are old (it's from Android M Preview 1, not including
+ // the new intent-annotation data); skip this test for now.
+ return;
+ }
+ }
+
+ assertEquals(!SDK_ANNOTATIONS_AVAILABLE ? "" // Most of the intent/content provider checks are based on framework annotations
+ + "src/test/pkg/ActionTest.java:86: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:87: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:88: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " myWriteResolverMethod(BOOKMARKS_URI);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n" : ""
+
+ + "src/test/pkg/ActionTest.java:36: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startActivity(intent);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:42: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startActivity(intent);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:43: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startActivity(intent, null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:44: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startActivityForResult(intent, 0);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:45: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startActivityFromChild(activity, intent, 0);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:46: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startActivityIfNeeded(intent, 0);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:47: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startActivityFromFragment(null, intent, 0);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:48: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " activity.startNextMatchingActivity(intent);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:54: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " context.sendBroadcast(intent);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:55: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " context.sendBroadcast(intent, \"\");\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:56: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " context.sendBroadcastAsUser(intent, null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:57: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " context.sendStickyBroadcast(intent);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:62: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " resolver.query(BOOKMARKS_URI, null, null, null, null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:65: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " resolver.insert(BOOKMARKS_URI, null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:66: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " resolver.delete(BOOKMARKS_URI, null, null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:67: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " resolver.update(BOOKMARKS_URI, null, null, null);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:86: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ + " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:87: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ActionTest.java:88: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ + " myWriteResolverMethod(BOOKMARKS_URI);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "19 errors, 0 warnings\n",
+
+ lintProject(
+ getManifestWithPermissions(14, 23),
+ java("src/test/pkg/ActionTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.Manifest;\n"
+ + "import android.app.Activity;\n"
+ + "import android.content.ContentResolver;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.Intent;\n"
+ + "import android.database.Cursor;\n"
+ + "import android.net.Uri;\n"
+ + "import android.support.annotation.RequiresPermission;\n"
+ + "\n"
+ //+ "import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;\n"
+ //+ "import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;\n"
+ + "\n"
+ + "@SuppressWarnings({\"deprecation\", \"unused\"})\n"
+ + "public class ActionTest {\n"
+ + " public static final String READ_HISTORY_BOOKMARKS=\"com.android.browser.permission.READ_HISTORY_BOOKMARKS\";\n"
+ + " public static final String WRITE_HISTORY_BOOKMARKS=\"com.android.browser.permission.WRITE_HISTORY_BOOKMARKS\";\n"
+ + " @RequiresPermission(Manifest.permission.CALL_PHONE)\n"
+ + " public static final String ACTION_CALL = \"android.intent.action.CALL\";\n"
+ + "\n"
+ + " @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))\n"
+ + " @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))\n"
+ + " public static final Uri BOOKMARKS_URI = Uri.parse(\"content://browser/bookmarks\");\n"
+ + "\n"
+ + " public static final Uri COMBINED_URI = Uri.withAppendedPath(BOOKMARKS_URI, \"bookmarks\");\n"
+ + " \n"
+ + " public static void activities1(Activity activity) {\n"
+ + " Intent intent = new Intent(Intent.ACTION_CALL);\n"
+ + " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
+ + " // This one will only be flagged if we have framework metadata on Intent.ACTION_CALL\n"
+ // Too flaky
+ + " //activity.startActivity(intent);\n"
+ + " }\n"
+ + "\n"
+ + " public static void activities2(Activity activity) {\n"
+ + " Intent intent = new Intent(ACTION_CALL);\n"
+ + " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
+ + " activity.startActivity(intent);\n"
+ + " }\n"
+ + " public static void activities3(Activity activity) {\n"
+ + " Intent intent;\n"
+ + " intent = new Intent(ACTION_CALL);\n"
+ + " intent.setData(Uri.parse(\"tel:1234567890\"));\n"
+ + " activity.startActivity(intent);\n"
+ + " activity.startActivity(intent, null);\n"
+ + " activity.startActivityForResult(intent, 0);\n"
+ + " activity.startActivityFromChild(activity, intent, 0);\n"
+ + " activity.startActivityIfNeeded(intent, 0);\n"
+ + " activity.startActivityFromFragment(null, intent, 0);\n"
+ + " activity.startNextMatchingActivity(intent);\n"
+ + " }\n"
+ + "\n"
+ + " public static void broadcasts(Context context) {\n"
+ + " Intent intent;\n"
+ + " intent = new Intent(ACTION_CALL);\n"
+ + " context.sendBroadcast(intent);\n"
+ + " context.sendBroadcast(intent, \"\");\n"
+ + " context.sendBroadcastAsUser(intent, null);\n"
+ + " context.sendStickyBroadcast(intent);\n"
+ + " }\n"
+ + "\n"
+ + " public static void contentResolvers(Context context, ContentResolver resolver) {\n"
+ + " // read\n"
+ + " resolver.query(BOOKMARKS_URI, null, null, null, null);\n"
+ + "\n"
+ + " // write\n"
+ + " resolver.insert(BOOKMARKS_URI, null);\n"
+ + " resolver.delete(BOOKMARKS_URI, null, null);\n"
+ + " resolver.update(BOOKMARKS_URI, null, null, null);\n"
+ + "\n"
+ + " // Framework (external) annotation\n"
+ + "//REMOVED resolver.query(android.provider.Browser.BOOKMARKS_URI, null, null, null, null);\n"
+ + "\n"
+ + " // TODO: Look for more complex URI manipulations\n"
+ + " }\n"
+ + "\n"
+ + " public static void myStartActivity(String s1, String s2, \n"
+ + " @RequiresPermission Intent intent) {\n"
+ + " }\n"
+ + "\n"
+ + " public static void myReadResolverMethod(String s1, @RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
+ + " }\n"
+ + "\n"
+ + " public static void myWriteResolverMethod(@RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
+ + " }\n"
+ + " \n"
+ + " public static void testCustomMethods() {\n"
+ + " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
+ + " myReadResolverMethod(\"\", BOOKMARKS_URI);\n"
+ + " myWriteResolverMethod(BOOKMARKS_URI);\n"
+ + " }\n"
+ + "}\n"),
+ mRequirePermissionAnnotation
+ ));
+ }
+
+ public void testCombinedIntDefAndIntRange() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/X.java:27: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG [WrongConstant]\n"
+ + " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/X.java:28: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was -5) [WrongConstant]\n"
+ + " setDuration(-5); // ERROR (not right int def or value\n"
+ + " ~~\n"
+ + "src/test/pkg/X.java:29: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was 8) [WrongConstant]\n"
+ + " setDuration(8); // ERROR (not matching number range)\n"
+ + " ~\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ getManifestWithPermissions(14, 23),
+ java("src/test/pkg/X.java", ""
+ + "\n"
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.IntDef;\n"
+ + "import android.support.annotation.IntRange;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "@SuppressWarnings({\"UnusedParameters\", \"unused\", \"SpellCheckingInspection\"})\n"
+ + "public class X {\n"
+ + "\n"
+ + " public static final int UNRELATED = 500;\n"
+ + "\n"
+ + " @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n"
+ + " @IntRange(from = 10)\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " public @interface Duration {}\n"
+ + "\n"
+ + " public static final int LENGTH_INDEFINITE = -2;\n"
+ + " public static final int LENGTH_SHORT = -1;\n"
+ + " public static final int LENGTH_LONG = 0;\n"
+ + " public void setDuration(@Duration int duration) {\n"
+ + " }\n"
+ + "\n"
+ + " public void test() {\n"
+ + " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
+ + " setDuration(-5); // ERROR (not right int def or value\n"
+ + " setDuration(8); // ERROR (not matching number range)\n"
+ + " setDuration(8000); // OK (@IntRange applies)\n"
+ + " setDuration(LENGTH_INDEFINITE); // OK (@IntDef)\n"
+ + " setDuration(LENGTH_LONG); // OK (@IntDef)\n"
+ + " setDuration(LENGTH_SHORT); // OK (@IntDef)\n"
+ + " }\n"
+ + "}\n"),
+ copy("src/android/support/annotation/IntDef.java.txt", "src/android/support/annotation/IntDef.java"),
+ copy("src/android/support/annotation/IntRange.java.txt", "src/android/support/annotation/IntRange.java")
+ ));
+ }
+
+ public void testMultipleProjects() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=182179
+ // 182179: Lint gives erroneous @StringDef errors in androidTests
+ assertEquals(""
+ + "src/test/zpkg/SomeClassTest.java:10: Error: Must be one of: SomeClass.MY_CONSTANT [WrongConstant]\n"
+ + " SomeClass.doSomething(\"error\");\n"
+ + " ~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ getManifestWithPermissions(14, 23),
+ java("src/test/pkg/SomeClass.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.StringDef;\n"
+ + "import android.util.Log;\n"
+ + "\n"
+ + "import java.lang.annotation.Documented;\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "public class SomeClass {\n"
+ + "\n"
+ + " public static final String MY_CONSTANT = \"foo\";\n"
+ + "\n"
+ + " public static void doSomething(@MyTypeDef final String myString) {\n"
+ + " Log.v(\"tag\", myString);\n"
+ + " }\n"
+ + "\n"
+ + "\n"
+ + " /**\n"
+ + " * Defines the possible values for state type.\n"
+ + " */\n"
+ + " @StringDef({MY_CONSTANT})\n"
+ + " @Documented\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " public @interface MyTypeDef {\n"
+ + "\n"
+ + " }\n"
+ + "}"),
+ // test.zpkg: alphabetically after test.pkg: We want to make sure
+ // that the SomeClass source unit is disposed before we try to
+ // process SomeClassTest and try to resolve its SomeClass.MY_CONSTANT
+ // @IntDef reference
+ java("src/test/zpkg/SomeClassTest.java", ""
+ + "package test.zpkg;\n"
+ + "\n"
+ + "import test.pkg.SomeClass;\n"
+ + "import junit.framework.TestCase;\n"
+ + "\n"
+ + "public class SomeClassTest extends TestCase {\n"
+ + "\n"
+ + " public void testDoSomething() {\n"
+ + " SomeClass.doSomething(SomeClass.MY_CONSTANT);\n"
+ + " SomeClass.doSomething(\"error\");\n"
+ + " }\n"
+ + "}"),
+ copy("src/android/support/annotation/StringDef.java.txt",
+ "src/android/support/annotation/StringDef.java")
+ ));
+ }
+
+ @SuppressWarnings({"InstantiationOfUtilityClass", "ResultOfObjectAllocationIgnored"})
+ public void testMultipleResourceTypes() throws Exception {
+ // Regression test for
+ // https://code.google.com/p/android/issues/detail?id=187181
+ // Make sure that parameters which specify multiple resource types are handled
+ // correctly.
+ assertEquals(""
+ + "src/test/pkg/ResourceTypeTest.java:14: Error: Expected resource of type drawable or string [ResourceType]\n"
+ + " new ResourceTypeTest(res, R.raw.my_raw_file); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/ResourceTypeTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.res.Resources;\n"
+ + "import android.support.annotation.DrawableRes;\n"
+ + "import android.support.annotation.StringRes;\n"
+ + "\n"
+ + "public class ResourceTypeTest {\n"
+ + " public ResourceTypeTest(Resources res, @DrawableRes @StringRes int id) {\n"
+ + " }\n"
+ + "\n"
+ + " public static void test(Resources res) {\n"
+ + " new ResourceTypeTest(res, R.drawable.ic_announcement_24dp); // OK\n"
+ + " new ResourceTypeTest(res, R.string.action_settings); // OK\n"
+ + " new ResourceTypeTest(res, R.raw.my_raw_file); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " public static final class R {\n"
+ + " public static final class drawable {\n"
+ + " public static final int ic_announcement_24dp = 0x7f0a0000;\n"
+ + " }\n"
+ + " public static final class string {\n"
+ + " public static final int action_settings = 0x7f0a0001;\n"
+ + " }\n"
+ + " public static final class raw {\n"
+ + " public static final int my_raw_file = 0x7f0a0002;\n"
+ + " }\n"
+ + " }"
+ + "}"),
+ copy("src/android/support/annotation/DrawableRes.java.txt", "src/android/support/annotation/DrawableRes.java"),
+ mStringResAnnotation
+ ));
+ }
+
+ @SuppressWarnings({"InstantiationOfUtilityClass", "ResultOfObjectAllocationIgnored"})
+ public void testAnyRes() throws Exception {
+ // Make sure error messages for @AnyRes are handled right since it's now an
+ // enum set containing all possible resource types
+ assertEquals(""
+ + "src/test/pkg/AnyResTest.java:14: Error: Expected resource identifier (R.type.name) [ResourceType]\n"
+ + " new AnyResTest(res, 52); // ERROR\n"
+ + " ~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ java("src/test/pkg/AnyResTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.res.Resources;\n"
+ + "import android.support.annotation.AnyRes;\n"
+ + "\n"
+ + "public class AnyResTest {\n"
+ + " public AnyResTest(Resources res, @AnyRes int id) {\n"
+ + " }\n"
+ + "\n"
+ + " public static void test(Resources res) {\n"
+ + " new AnyResTest(res, R.drawable.ic_announcement_24dp); // OK\n"
+ + " new AnyResTest(res, R.string.action_settings); // OK\n"
+ + " new AnyResTest(res, R.raw.my_raw_file); // OK\n"
+ + " new AnyResTest(res, 52); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " public static final class R {\n"
+ + " public static final class drawable {\n"
+ + " public static final int ic_announcement_24dp = 0x7f0a0000;\n"
+ + " }\n"
+ + " public static final class string {\n"
+ + " public static final int action_settings = 0x7f0a0001;\n"
+ + " }\n"
+ + " public static final class raw {\n"
+ + " public static final int my_raw_file = 0x7f0a0002;\n"
+ + " }\n"
+ + " }"
+ + "}"),
+ copy("src/android/support/annotation/AnyRes.java.txt", "src/android/support/annotation/AnyRes.java")
+ ));
+ }
+
+ /**
+ * Test @IntDef when applied to multiple elements like arrays or varargs.
+ */
+ public void testIntDefMultiple() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/IntDefMultiple.java:24: Error: Must be one of: IntDefMultiple.VALUE_A, IntDefMultiple.VALUE_B [WrongConstant]\n"
+ + " restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/new int[]{VALUE_A, 0, VALUE_B}/**/); // ERROR;\n"
+ + " ~\n"
+ + "src/test/pkg/IntDefMultiple.java:31: Error: Must be one of: IntDefMultiple.VALUE_A, IntDefMultiple.VALUE_B [WrongConstant]\n"
+ + " restrictedEllipsis(VALUE_A, /*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/, VALUE_B); // ERROR\n"
+ + " ~\n"
+ + "src/test/pkg/IntDefMultiple.java:32: Error: Must be one of: IntDefMultiple.VALUE_A, IntDefMultiple.VALUE_B [WrongConstant]\n"
+ + " restrictedEllipsis(/*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/); // ERROR\n"
+ + " ~\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ java("src/test/pkg/IntDefMultiple.java", ""
+ + "package test.pkg;\n"
+ + "import android.support.annotation.IntDef;\n"
+ + "\n"
+ + "public class IntDefMultiple {\n"
+ + " private static final int VALUE_A = 0;\n"
+ + " private static final int VALUE_B = 1;\n"
+ + "\n"
+ + " private static final int[] VALID_ARRAY = {VALUE_A, VALUE_B};\n"
+ + " private static final int[] INVALID_ARRAY = {VALUE_A, 0, VALUE_B};\n"
+ + " private static final int[] INVALID_ARRAY2 = {10};\n"
+ + "\n"
+ + " @IntDef({VALUE_A, VALUE_B})\n"
+ + " public @interface MyIntDef {}\n"
+ + "\n"
+ + " @MyIntDef\n"
+ + " public int a = 0;\n"
+ + "\n"
+ + " @MyIntDef\n"
+ + " public int[] b;\n"
+ + "\n"
+ + " public void testCall() {\n"
+ + " restrictedArray(new int[]{VALUE_A}); // OK\n"
+ + " restrictedArray(new int[]{VALUE_A, VALUE_B}); // OK\n"
+ + " restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/new int[]{VALUE_A, 0, VALUE_B}/**/); // ERROR;\n"
+ + " restrictedArray(VALID_ARRAY); // OK\n"
+ + " restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/INVALID_ARRAY/**/); // ERROR\n"
+ + " restrictedArray(/*Must be one of: X.VALUE_A, X.VALUE_B*/INVALID_ARRAY2/**/); // ERROR\n"
+ + "\n"
+ + " restrictedEllipsis(VALUE_A); // OK\n"
+ + " restrictedEllipsis(VALUE_A, VALUE_B); // OK\n"
+ + " restrictedEllipsis(VALUE_A, /*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/, VALUE_B); // ERROR\n"
+ + " restrictedEllipsis(/*Must be one of: X.VALUE_A, X.VALUE_B*/0/**/); // ERROR\n"
+ + " // Suppressed via older Android Studio inspection id:\n"
+ + " //noinspection ResourceType\n"
+ + " restrictedEllipsis(0); // SUPPRESSED\n"
+ + " }\n"
+ + "\n"
+ + " private void restrictedEllipsis(@MyIntDef int... test) {}\n"
+ + "\n"
+ + " private void restrictedArray(@MyIntDef int[] test) {}\n"
+ + "}"),
+ copy("src/android/support/annotation/IntDef.java.txt",
+ "src/android/support/annotation/IntDef.java")));
+ }
+
+ /**
+ * Test @IntRange and @FloatRange support annotation applied to arrays and vargs.
+ */
+ public void testRangesMultiple() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/RangesMultiple.java:22: Error: Value must be ≥ 10.0 (was 5.0) [Range]\n"
+ + " varargsFloat(15.0f, 10.0f, /*Value must be ≥ 10.0 and ≤ 15.0 (was 5.0f)*/5.0f/**/); // ERROR\n"
+ + " ~~~~\n"
+ + "src/test/pkg/RangesMultiple.java:32: Error: Value must be ≤ 500 (was 510) [Range]\n"
+ + " varargsInt(15, 10, /*Value must be ≥ 10 and ≤ 500 (was 510)*/510/**/); // ERROR\n"
+ + " ~~~\n"
+ + "src/test/pkg/RangesMultiple.java:36: Error: Value must be ≥ 10 (was 0) [Range]\n"
+ + " restrictedIntArray(/*Value must be ≥ 10 and ≤ 500*/new int[]{0, 500}/**/); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ java("src/test/pkg/RangesMultiple.java", ""
+ + "package test.pkg;\n"
+ + "import android.support.annotation.FloatRange;\n"
+ + "import android.support.annotation.IntRange;\n"
+ + "\n"
+ + "public class RangesMultiple {\n"
+ + " private static final float[] VALID_FLOAT_ARRAY = new float[] {10.0f, 12.0f, 15.0f};\n"
+ + " private static final float[] INVALID_FLOAT_ARRAY = new float[] {10.0f, 12.0f, 5.0f};\n"
+ + "\n"
+ + " private static final int[] VALID_INT_ARRAY = new int[] {15, 120, 500};\n"
+ + " private static final int[] INVALID_INT_ARRAY = new int[] {15, 120, 5};\n"
+ + "\n"
+ + " @FloatRange(from = 10.0, to = 15.0)\n"
+ + " public float[] a;\n"
+ + "\n"
+ + " @IntRange(from = 10, to = 500)\n"
+ + " public int[] b;\n"
+ + "\n"
+ + " public void testCall() {\n"
+ + " a = new float[2];\n"
+ + " a[0] = /*Value must be ≥ 10.0 and ≤ 15.0 (was 5f)*/5f/**/; // ERROR\n"
+ + " a[1] = 14f; // OK\n"
+ + " varargsFloat(15.0f, 10.0f, /*Value must be ≥ 10.0 and ≤ 15.0 (was 5.0f)*/5.0f/**/); // ERROR\n"
+ + " restrictedFloatArray(VALID_FLOAT_ARRAY); // OK\n"
+ + " restrictedFloatArray(/*Value must be ≥ 10.0 and ≤ 15.0*/INVALID_FLOAT_ARRAY/**/); // ERROR\n"
+ + " restrictedFloatArray(new float[]{10.5f, 14.5f}); // OK\n"
+ + " restrictedFloatArray(/*Value must be ≥ 10.0 and ≤ 15.0*/new float[]{12.0f, 500.0f}/**/); // ERROR\n"
+ + "\n"
+ + "\n"
+ + " b = new int[2];\n"
+ + " b[0] = /*Value must be ≥ 10 and ≤ 500 (was 5)*/5/**/; // ERROR\n"
+ + " b[1] = 100; // OK\n"
+ + " varargsInt(15, 10, /*Value must be ≥ 10 and ≤ 500 (was 510)*/510/**/); // ERROR\n"
+ + " restrictedIntArray(VALID_INT_ARRAY); // OK\n"
+ + " restrictedIntArray(/*Value must be ≥ 10 and ≤ 500*/INVALID_INT_ARRAY/**/); // ERROR\n"
+ + " restrictedIntArray(new int[]{50, 500}); // OK\n"
+ + " restrictedIntArray(/*Value must be ≥ 10 and ≤ 500*/new int[]{0, 500}/**/); // ERROR\n"
+ + " }\n"
+ + "\n"
+ + " public void restrictedIntArray(@IntRange(from = 10, to = 500) int[] a) {\n"
+ + " }\n"
+ + "\n"
+ + " public void varargsInt(@IntRange(from = 10, to = 500) int... a) {\n"
+ + " }\n"
+ + "\n"
+ + " public void varargsFloat(@FloatRange(from = 10.0, to = 15.0) float... a) {\n"
+ + " }\n"
+ + "\n"
+ + " public void restrictedFloatArray(@FloatRange(from = 10.0, to = 15.0) float[] a) {\n"
+ + " }\n"
+ + "}\n"
+ + "\n"),
+ copy("src/android/support/annotation/IntRange.java.txt", "src/android/support/annotation/IntRange.java"),
+ copy("src/android/support/annotation/FloatRange.java.txt", "src/android/support/annotation/FloatRange.java")));
+ }
+
+ public void testIntDefInBuilder() throws Exception {
+ // Ensure that we only check constants, not instance fields, when passing
+ // fields as arguments to typedef parameters.
+ assertEquals("No warnings.",
+ lintProject(
+ java("src/test/pkg/Product.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.IntDef;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "public class Product {\n"
+ + " @IntDef({\n"
+ + " STATUS_AVAILABLE, STATUS_BACK_ORDER, STATUS_UNAVAILABLE\n"
+ + " })\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " public @interface Status {\n"
+ + " }\n"
+ + " public static final int STATUS_AVAILABLE = 1;\n"
+ + " public static final int STATUS_BACK_ORDER = 2;\n"
+ + " public static final int STATUS_UNAVAILABLE = 3;\n"
+ + "\n"
+ + " @Status\n"
+ + " private final int mStatus;\n"
+ + " private final String mName;\n"
+ + "\n"
+ + " private Product(String name, @Status int status) {\n"
+ + " mName = name;\n"
+ + " mStatus = status;\n"
+ + " }\n"
+ + " public static class Builder {\n"
+ + " @Status\n"
+ + " private int mStatus;\n"
+ + " private final int mStatus2 = STATUS_AVAILABLE;\n"
+ + " private String mName;\n"
+ + "\n"
+ + " public Builder(String name, @Status int status) {\n"
+ + " mName = name;\n"
+ + " mStatus = status;\n"
+ + " }\n"
+ + "\n"
+ + " public Builder setStatus(@Status int status) {\n"
+ + " mStatus = status;\n"
+ + " return this;\n"
+ + " }\n"
+ + "\n"
+ + " public Product build() {\n"
+ + " return new Product(mName, mStatus);\n"
+ + " }\n"
+ + "\n"
+ + " public Product build2() {\n"
+ + " return new Product(mName, mStatus2);\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"),
+ copy("src/android/support/annotation/IntDef.java.txt", "src/android/support/annotation/IntDef.java"))
+ );
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SystemPermissionsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SystemPermissionsDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SystemPermissionsDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SystemPermissionsDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextFieldDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextViewDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextViewDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextViewDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TextViewDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TitleDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java
new file mode 100644
index 0000000..821e64f
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ToastDetectorTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class ToastDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ToastDetector();
+ }
+
+
+ public void test() throws Exception {
+ assertEquals(
+ "src/test/pkg/ToastTest.java:31: Warning: Toast created but not shown: did you forget to call show() ? [ShowToast]\n" +
+ " Toast.makeText(context, \"foo\", Toast.LENGTH_LONG);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/ToastTest.java:32: Warning: Expected duration Toast.LENGTH_SHORT or Toast.LENGTH_LONG, a custom duration value is not supported [ShowToast]\n" +
+ " Toast toast = Toast.makeText(context, R.string.app_name, 5000);\n" +
+ " ~~~~\n" +
+ "src/test/pkg/ToastTest.java:32: Warning: Toast created but not shown: did you forget to call show() ? [ShowToast]\n" +
+ " Toast toast = Toast.makeText(context, R.string.app_name, 5000);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/ToastTest.java:38: Warning: Toast created but not shown: did you forget to call show() ? [ShowToast]\n" +
+ " Toast.makeText(context, \"foo\", Toast.LENGTH_LONG);\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 4 warnings\n" +
+ "",
+
+ lintProject("src/test/pkg/ToastTest.java.txt=>src/test/pkg/ToastTest.java"));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TooManyViewsDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TooManyViewsDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TooManyViewsDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TooManyViewsDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
new file mode 100644
index 0000000..a2ca117
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+ at SuppressWarnings("javadoc")
+public class TranslationDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new TranslationDetector();
+ }
+
+ @Override
+ protected boolean includeParentPath() {
+ return true;
+ }
+
+ public void testTranslation() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(
+ // Sample files from the Home app
+ "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
+ " <string name=\"show_all_apps\">All</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
+ " <string name=\"menu_wallpaper\">Wallpaper</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-DE\" (German: Germany), \"es\" (Spanish), \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
+ " <string name=\"menu_settings\">Settings</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values-cs/arrays.xml:3: Error: \"security_questions\" is translated here but not found in default locale [ExtraTranslation]\n" +
+ " <string-array name=\"security_questions\">\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-es/strings.xml:12: Also translated here\n" +
+ "res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n" +
+ " <string name=\"continue_skip_label\">\"Weiter\"</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "5 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/strings.xml",
+ "res/values-cs/strings.xml",
+ "res/values-de-rDE/strings.xml",
+ "res/values-es/strings.xml",
+ "res/values-es-rUS/strings.xml",
+ "res/values-land/strings.xml",
+ "res/values-cs/arrays.xml",
+ "res/values-es/donottranslate.xml",
+ "res/values-nl-rNL/strings.xml"));
+ }
+
+ public void testTranslationWithCompleteRegions() throws Exception {
+ TranslationDetector.sCompleteRegions = true;
+ assertEquals(
+ // Sample files from the Home app
+ "res/values/strings.xml:19: Error: \"home_title\" is not translated in \"es-US\" (Spanish: United States) [MissingTranslation]\n" +
+ " <string name=\"home_title\">Home Sample</string>\n" +
+ " ~~~~~~~~~~~~~~~~~\n" +
+ "res/values/strings.xml:20: Error: \"show_all_apps\" is not translated in \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
+ " <string name=\"show_all_apps\">All</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values/strings.xml:23: Error: \"menu_wallpaper\" is not translated in \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
+ " <string name=\"menu_wallpaper\">Wallpaper</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-DE\" (German: Germany), \"es-US\" (Spanish: United States), \"nl-NL\" (Dutch: Netherlands) [MissingTranslation]\n" +
+ " <string name=\"menu_settings\">Settings</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/values/strings.xml:29: Error: \"wallpaper_instructions\" is not translated in \"es-US\" (Spanish: United States) [MissingTranslation]\n" +
+ " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ " res/values-land/strings.xml:19: <No location-specific message\n" +
+ "res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n" +
+ " <string name=\"continue_skip_label\">\"Weiter\"</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "6 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/strings.xml",
+ "res/values-cs/strings.xml",
+ "res/values-de-rDE/strings.xml",
+ "res/values-es-rUS/strings.xml",
+ "res/values-land/strings.xml",
+ "res/values-nl-rNL/strings.xml"));
+ }
+
+ public void testBcp47() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(""
+ + "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"tlh\" (Klingon; tlhIngan-Hol) [MissingTranslation]\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/strings.xml",
+ "res/values-cs/strings.xml=>res/values-b+tlh/strings.xml"));
+ }
+
+ public void testHandleBom() throws Exception {
+ // This isn't really testing translation detection; it's just making sure that the
+ // XML parser doesn't bomb on BOM bytes (byte order marker) at the beginning of
+ // the XML document
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "res/values-de/strings.xml=>res/values/strings.xml"
+ ));
+ }
+
+ public void testTranslatedArrays() throws Exception {
+ TranslationDetector.sCompleteRegions = true;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/translatedarrays.xml",
+ "res/values-cs/translatedarrays.xml"));
+ }
+
+ public void testTranslationSuppresss() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/strings_ignore.xml=>res/values/strings.xml",
+ "res/values-es/strings_ignore.xml=>res/values-es/strings.xml",
+ "res/values-nl-rNL/strings.xml=>res/values-nl-rNL/strings.xml"));
+ }
+
+ public void testMixedTranslationArrays() throws Exception {
+ // See issue http://code.google.com/p/android/issues/detail?id=29263
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/strings3.xml=>res/values/strings.xml",
+ "res/values-fr/strings.xml=>res/values-fr/strings.xml"));
+ }
+
+ public void testLibraryProjects() throws Exception {
+ // If a library project provides additional locales, that should not force
+ // the main project to include all those translations
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "res/values/strings2.xml",
+
+ // Library project
+ "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
+ "multiproject/library.properties=>../LibraryProject/project.properties",
+
+ "res/values/strings.xml=>../LibraryProject/res/values/strings.xml",
+ "res/values-cs/strings.xml=>../LibraryProject/res/values-cs/strings.xml",
+ "res/values-cs/strings.xml=>../LibraryProject/res/values-de/strings.xml",
+ "res/values-cs/strings.xml=>../LibraryProject/res/values-nl/strings.xml"
+ ));
+ }
+
+ public void testNonTranslatable1() throws Exception {
+ TranslationDetector.sCompleteRegions = true;
+ assertEquals(
+ "res/values-nb/nontranslatable.xml:3: Error: The resource string \"dummy\" has been marked as translatable=\"false\" [ExtraTranslation]\n" +
+ " <string name=\"dummy\">Ignore Me</string>\n" +
+ " ~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n" +
+ "",
+
+ lintProject("res/values/nontranslatable.xml",
+ "res/values/nontranslatable2.xml=>res/values-nb/nontranslatable.xml"));
+ }
+
+ public void testNonTranslatable2() throws Exception {
+ TranslationDetector.sCompleteRegions = true;
+ assertEquals(
+ "res/values-nb/nontranslatable.xml:3: Error: Non-translatable resources should only be defined in the base values/ folder [ExtraTranslation]\n" +
+ " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n" +
+ " ~~~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n" +
+ "",
+
+ lintProject("res/values/nontranslatable.xml=>res/values-nb/nontranslatable.xml"));
+ }
+
+ public void testNonTranslatable3() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=92861
+ // Don't treat "google_maps_key" or "google_maps_key_instructions" as translatable
+ TranslationDetector.sCompleteRegions = true;
+ assertEquals(
+ "No warnings.",
+
+ lintProject("res/values/google_maps_api.xml",
+ "res/values/strings2.xml",
+ "res/values/strings2.xml=>res/values-nb/strings2.xml"));
+ }
+
+ public void testSpecifiedLanguageOk() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values-es/strings.xml=>res/values/strings.xml",
+ "res/values-es/strings.xml=>res/values-es/strings.xml",
+ "res/values-es-rUS/strings.xml"));
+ }
+
+ public void testSpecifiedLanguage() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values-es/strings_locale.xml=>res/values/strings.xml",
+ "res/values-es-rUS/strings.xml"));
+ }
+
+ public void testAnalytics() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=43070
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/analytics.xml",
+ "res/values-es/donottranslate.xml" // to make app multilingual
+ ));
+ }
+
+ public void testIssue33845() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=33845
+ assertEquals(""
+ + "res/values/strings.xml:5: Error: \"dateTimeFormat\" is not translated in \"de\" (German) [MissingTranslation]\n"
+ + " <string name=\"dateTimeFormat\">MM/dd/yyyy - HH:mm</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "locale33845/.classpath=>.classpath",
+ "locale33845/AndroidManifest.xml=>AndroidManifest.xml",
+ "locale33845/project.properties=>project.properties",
+ "locale33845/res/values/strings.xml=>res/values/strings.xml",
+ "locale33845/res/values-de/strings.xml=>res/values-de/strings.xml",
+ "locale33845/res/values-en-rGB/strings.xml=>res/values-en-rGB/strings.xml"
+ ));
+ }
+
+ public void testIssue33845b() throws Exception {
+ // Similar to issue 33845, but with some variations to the test data
+ // See http://code.google.com/p/android/issues/detail?id=33845
+ assertEquals("No warnings.",
+
+ lintProject(
+ "locale33845/.classpath=>.classpath",
+ "locale33845/AndroidManifest.xml=>AndroidManifest.xml",
+ "locale33845/project.properties=>project.properties",
+ "locale33845/res/values/styles.xml=>res/values/styles.xml",
+ "locale33845/res/values/strings2.xml=>res/values/strings.xml",
+ "locale33845/res/values-en-rGB/strings2.xml=>res/values-en-rGB/strings.xml"
+ ));
+ }
+
+ public void testEnglishRegionAndValuesAsEnglish1() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ // tools:locale=en in base folder
+ // Regression test for https://code.google.com/p/android/issues/detail?id=75879
+ assertEquals("No warnings.",
+
+ lintProject(
+ "locale33845/res/values/strings3.xml=>res/values/strings.xml",
+ "locale33845/res/values-en-rGB/strings3.xml=>res/values-en-rGB/strings.xml"
+ ));
+ }
+
+ public void testEnglishRegionAndValuesAsEnglish2() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ // No tools:locale specified in the base folder: *assume* English
+ // Regression test for https://code.google.com/p/android/issues/detail?id=75879
+ assertEquals(""
+ + "res/values/strings.xml:5: Error: \"other\" is not translated in \"de-DE\" (German: Germany) [MissingTranslation]\n"
+ + " <string name=\"other\">other</string>\n"
+ + " ~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(
+ "locale33845/res/values/strings4.xml=>res/values/strings.xml",
+ // Flagged because it's not the default locale:
+ "locale33845/res/values-en-rGB/strings3.xml=>res/values-de-rDE/strings.xml",
+ // Not flagged because it's the implicit default locale
+ "locale33845/res/values-en-rGB/strings3.xml=>res/values-en-rGB/strings.xml"
+ ));
+ }
+
+ public void testEnglishRegionAndValuesAsEnglish3() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ // tools:locale=de in base folder
+ // Regression test for https://code.google.com/p/android/issues/detail?id=75879
+ assertEquals("No warnings.",
+
+ lintProject(
+ "locale33845/res/values/strings5.xml=>res/values/strings.xml",
+ "locale33845/res/values-en-rGB/strings3.xml=>res/values-de-rDE/strings.xml"
+ ));
+ }
+
+ public void testResConfigs() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(""
+ + "res/values/strings.xml:25: Error: \"menu_settings\" is not translated in \"cs\" (Czech), \"de-DE\" (German: Germany) [MissingTranslation]\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values-cs/arrays.xml:3: Error: \"security_questions\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string-array name=\"security_questions\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + " res/values-es/strings.xml:12: Also translated here\n"
+ + "res/values-de-rDE/strings.xml:11: Error: \"continue_skip_label\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"continue_skip_label\">\"Weiter\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "3 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values/strings.xml",
+ "res/values-cs/strings.xml",
+ "res/values-de-rDE/strings.xml",
+ "res/values-es/strings.xml",
+ "res/values-es-rUS/strings.xml",
+ "res/values-land/strings.xml",
+ "res/values-cs/arrays.xml",
+ "res/values-es/donottranslate.xml",
+ "res/values-nl-rNL/strings.xml"));
+ }
+
+ public void testMissingBaseCompletely() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(""
+ + "res/values-cs/strings.xml:4: Error: \"home_title\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"home_title\">\"Domů\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "res/values-cs/strings.xml:5: Error: \"show_all_apps\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"show_all_apps\">\"Vše\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values-cs/strings.xml:6: Error: \"menu_wallpaper\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"menu_wallpaper\">\"Tapeta\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values-cs/strings.xml:7: Error: \"menu_search\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"menu_search\">\"Hledat\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "res/values-cs/strings.xml:10: Error: \"wallpaper_instructions\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"wallpaper_instructions\">\"Klepnutím na obrázek nastavíte tapetu portrétu\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "5 errors, 0 warnings\n",
+
+ lintProject("res/values-cs/strings.xml"));
+ }
+
+ public void testMissingSomeBaseStrings() throws Exception {
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals(""
+ + "res/values-es/strings.xml:4: Error: \"home_title\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"home_title\">\"Casa\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "res/values-es/strings.xml:5: Error: \"show_all_apps\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"show_all_apps\">\"Todo\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values-es/strings.xml:6: Error: \"menu_wallpaper\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"menu_wallpaper\">\"Papel tapiz\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values-es/strings.xml:10: Error: \"wallpaper_instructions\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string name=\"wallpaper_instructions\">\"Puntee en la imagen para establecer papel tapiz vertical\"</string>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values-es/strings.xml:12: Error: \"security_questions\" is translated here but not found in default locale [ExtraTranslation]\n"
+ + " <string-array name=\"security_questions\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "5 errors, 0 warnings\n",
+
+ lintProject(
+ "res/values-es-rUS/strings.xml=>res/values/strings.xml",
+ "res/values-es/strings.xml=>res/values-es/strings.xml"
+
+ ));
+ }
+
+ public void testConfigKeys() throws Exception {
+ // Some developer services create config files merged with your project, but in some
+ // versions they were missing a translatable="false" entry. Since we know these aren't
+ // keys you normally want to translate, let's filter them for users.
+ TranslationDetector.sCompleteRegions = false;
+ assertEquals("No warnings.",
+ lintProject(
+ xml("res/values/config.xml", ""
+ + "<resources>\n"
+ + " <string name=\"gcm_defaultSenderId\">SENDER_ID</string>\n"
+ + " <string name=\"google_app_id\">App Id</string>\n"
+ + " <string name=\"ga_trackingID\">Analytics</string>\n"
+ + "</resources>\n"),
+ xml("res/values/strings.xml", ""
+ + "<resources>\n"
+ + " <string name=\"app_name\">My Application</string>\n"
+ + "</resources>\n"),
+ xml("res/values-nb/strings.xml", ""
+ + "<resources>\n"
+ + " <string name=\"app_name\">Min Applikasjon</string>\n"
+ + "</resources>\n")
+ ));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ if (!getName().startsWith("testResConfigs")) {
+ return super.createClient();
+ }
+
+ // Set up a mock project model for the resource configuration test(s)
+ // where we provide a subset of densities to be included
+
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ /*
+ Simulate variant freeBetaDebug in this setup:
+ defaultConfig {
+ ...
+ resConfigs "cs"
+ }
+ flavorDimensions "pricing", "releaseType"
+ productFlavors {
+ beta {
+ flavorDimension "releaseType"
+ resConfig "en", "de"
+ resConfigs "nodpi", "hdpi"
+ }
+ normal { flavorDimension "releaseType" }
+ free { flavorDimension "pricing" }
+ paid { flavorDimension "pricing" }
+ }
+ */
+ ProductFlavor flavorFree = mock(ProductFlavor.class);
+ when(flavorFree.getName()).thenReturn("free");
+ when(flavorFree.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorNormal = mock(ProductFlavor.class);
+ when(flavorNormal.getName()).thenReturn("normal");
+ when(flavorNormal.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorPaid = mock(ProductFlavor.class);
+ when(flavorPaid.getName()).thenReturn("paid");
+ when(flavorPaid.getResourceConfigurations())
+ .thenReturn(Collections.<String>emptyList());
+
+ ProductFlavor flavorBeta = mock(ProductFlavor.class);
+ when(flavorBeta.getName()).thenReturn("beta");
+ List<String> resConfigs = Arrays.asList("hdpi", "en", "de", "nodpi");
+ when(flavorBeta.getResourceConfigurations()).thenReturn(resConfigs);
+
+ ProductFlavor defaultFlavor = mock(ProductFlavor.class);
+ when(defaultFlavor.getName()).thenReturn("main");
+ when(defaultFlavor.getResourceConfigurations()).thenReturn(
+ Collections.singleton("cs"));
+
+ ProductFlavorContainer containerBeta =
+ mock(ProductFlavorContainer.class);
+ when(containerBeta.getProductFlavor()).thenReturn(flavorBeta);
+
+ ProductFlavorContainer containerFree =
+ mock(ProductFlavorContainer.class);
+ when(containerFree.getProductFlavor()).thenReturn(flavorFree);
+
+ ProductFlavorContainer containerPaid =
+ mock(ProductFlavorContainer.class);
+ when(containerPaid.getProductFlavor()).thenReturn(flavorPaid);
+
+ ProductFlavorContainer containerNormal =
+ mock(ProductFlavorContainer.class);
+ when(containerNormal.getProductFlavor()).thenReturn(flavorNormal);
+
+ ProductFlavorContainer defaultContainer =
+ mock(ProductFlavorContainer.class);
+ when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
+
+ List<ProductFlavorContainer> containers = Arrays.asList(
+ containerPaid, containerFree, containerNormal, containerBeta
+ );
+
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getProductFlavors()).thenReturn(containers);
+ when(project.getDefaultConfig()).thenReturn(defaultContainer);
+ return project;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ List<String> productFlavorNames = Arrays.asList("free", "beta");
+ Variant mock = mock(Variant.class);
+ when(mock.getProductFlavors()).thenReturn(productFlavorNames);
+ return mock;
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetectorTest.java
new file mode 100644
index 0000000..c71fe19
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TrustAllX509TrustManagerDetectorTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "ImplicitArrayToString"})
+public class TrustAllX509TrustManagerDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new TrustAllX509TrustManagerDetector();
+ }
+
+ public void testBroken() throws Exception {
+ assertEquals(
+ "src/test/pkg/InsecureTLSIntentService.java:22: Warning: checkClientTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [TrustAllX509TrustManager]\n" +
+ " public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/InsecureTLSIntentService.java:26: Warning: checkServerTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [TrustAllX509TrustManager]\n" +
+ " public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws CertificateException {\n" +
+ " ~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 2 warnings\n" +
+ "",
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <service\n"
+ + " android:name=\".InsecureTLSIntentService\" >\n"
+ + " </service>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"
+ + "\n"),
+ java("src/test/pkg/InsecureTLSIntentService.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.IntentService;\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "import java.security.GeneralSecurityException;\n"
+ + "import java.security.cert.CertificateException;\n"
+ + "\n"
+ + "import javax.net.ssl.HttpsURLConnection;\n"
+ + "import javax.net.ssl.SSLContext;\n"
+ + "import javax.net.ssl.TrustManager;\n"
+ + "import javax.net.ssl.X509TrustManager;\n"
+ + "\n"
+ + "public class InsecureTLSIntentService extends IntentService {\n"
+ + " TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() {\n"
+ + " @Override\n"
+ + " public java.security.cert.X509Certificate[] getAcceptedIssuers() {\n"
+ + " return null;\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws CertificateException {\n"
+ + " }\n"
+ + " }};\n"
+ + "\n"
+ + " public InsecureTLSIntentService() {\n"
+ + " super(\"InsecureTLSIntentService\");\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " protected void onHandleIntent(Intent intent) {\n"
+ + " try {\n"
+ + " SSLContext sc = SSLContext.getInstance(\"TLSv1.2\");\n"
+ + " sc.init(null, trustAllCerts, new java.security.SecureRandom());\n"
+ + " HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());\n"
+ + " } catch (GeneralSecurityException e) {\n"
+ + " System.out.println(e.getStackTrace());\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")));
+
+ // TODO: Test bytecode check via library jar?
+ //"bytecode/InsecureTLSIntentService.java.txt=>src/test/pkg/InsecureTLSIntentService.java",
+ //"bytecode/InsecureTLSIntentService.class.data=>bin/classes/test/pkg/InsecureTLSIntentService.class",
+ //"bytecode/InsecureTLSIntentService$1.class.data=>bin/classes/test/pkg/InsecureTLSIntentService$1.class"));
+ }
+
+ public void testCorrect() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <service\n"
+ + " android:name=\".ExampleTLSIntentService\" >\n"
+ + " </service>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"),
+ java("src/test/pkg/ExampleTLSIntentService.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.app.IntentService;\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "import java.io.BufferedInputStream;\n"
+ + "import java.io.FileInputStream;\n"
+ + "import java.security.GeneralSecurityException;\n"
+ + "import java.security.cert.CertificateException;\n"
+ + "import java.security.cert.CertificateFactory;\n"
+ + "import java.security.cert.X509Certificate;\n"
+ + "\n"
+ + "import javax.net.ssl.HttpsURLConnection;\n"
+ + "import javax.net.ssl.SSLContext;\n"
+ + "import javax.net.ssl.TrustManager;\n"
+ + "import javax.net.ssl.X509TrustManager;\n"
+ + "\n"
+ + "public class ExampleTLSIntentService extends IntentService {\n"
+ + " TrustManager[] trustManagerExample;\n"
+ + "\n"
+ + " {\n"
+ + " trustManagerExample = new TrustManager[]{new X509TrustManager() {\n"
+ + " @Override\n"
+ + " public X509Certificate[] getAcceptedIssuers() {\n"
+ + " try {\n"
+ + " FileInputStream fis = new FileInputStream(\"testcert.pem\");\n"
+ + " BufferedInputStream bis = new BufferedInputStream(fis);\n"
+ + " CertificateFactory cf = CertificateFactory.getInstance(\"X.509\");\n"
+ + " X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);\n"
+ + " return new X509Certificate[]{cert};\n"
+ + " } catch (Exception e) {\n"
+ + " throw new RuntimeException(\"Could not load cert\");\n"
+ + " }\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {\n"
+ + " throw new CertificateException(\"Not trusted\");\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {\n"
+ + " throw new CertificateException(\"Not trusted\");\n"
+ + " }\n"
+ + " }};\n"
+ + " }\n"
+ + "\n"
+ + " public ExampleTLSIntentService() {\n"
+ + " super(\"ExampleTLSIntentService\");\n"
+ + " }\n"
+ + "\n"
+ + " @Override\n"
+ + " protected void onHandleIntent(Intent intent) {\n"
+ + " try {\n"
+ + " SSLContext sc = SSLContext.getInstance(\"TLSv1.2\");\n"
+ + " sc.init(null, trustManagerExample, new java.security.SecureRandom());\n"
+ + " HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());\n"
+ + " } catch (GeneralSecurityException e) {\n"
+ + " System.out.println(e.getStackTrace());\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
new file mode 100644
index 0000000..5db1f2a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+
+import com.android.tools.lint.detector.api.Detector;
+
+import java.util.Arrays;
+
+ at SuppressWarnings("javadoc")
+public class TypoDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new TypoDetector();
+ }
+
+ public void testPlainValues() throws Exception {
+ assertEquals(
+ "res/values/strings.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
+ " <string name=\"s2\">Andriod activites!</string>\n" +
+ " ^\n" +
+ "res/values/strings.xml:6: Warning: \"activites\" is a common misspelling; did you mean \"activities\" ? [Typos]\n" +
+ " <string name=\"s2\">Andriod activites!</string>\n" +
+ " ^\n" +
+ "res/values/strings.xml:8: Warning: \"Cmoputer\" is a common misspelling; did you mean \"Computer\" ? [Typos]\n" +
+ " <string name=\"s3\"> (Cmoputer </string>\n" +
+ " ^\n" +
+ "res/values/strings.xml:10: Warning: \"throught\" is a common misspelling; did you mean \"thought\" or \"through\" or \"throughout\" ? [Typos]\n" +
+ " <string name=\"s4\"><b>throught</b></string>\n" +
+ " ^\n" +
+ "res/values/strings.xml:12: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n" +
+ " <string name=\"s5\">Seach</string>\n" +
+ " ^\n" +
+ "res/values/strings.xml:16: Warning: \"Tuscon\" is a common misspelling; did you mean \"Tucson\" ? [Typos]\n" +
+ " <string name=\"s7\">Tuscon tuscon</string>\n" +
+ " ^\n" +
+ "res/values/strings.xml:20: Warning: \"Ok\" is usually capitalized as \"OK\" [Typos]\n" +
+ " <string name=\"dlg_button_ok\">Ok</string>\n" +
+ " ^\n" +
+ "0 errors, 7 warnings\n" +
+ "",
+ lintProject("res/values/typos.xml=>res/values/strings.xml"));
+ }
+
+ public void testRepeatedWords() throws Exception {
+ assertEquals(
+ "res/values/strings.xml:5: Warning: Repeated word \"to\" in message: possible typo [Typos]\n" +
+ " extra location provider commands. This may allow the app to to interfere\n" +
+ " ^\n" +
+ "res/values/strings.xml:7: Warning: Repeated word \"zü\" in message: possible typo [Typos]\n" +
+ " <string name=\"other\">\"ü test\\n zü zü\"</string>\n" +
+ " ^\n" +
+ "0 errors, 2 warnings\n",
+ lintProject("res/values/repeated_words.xml=>res/values/strings.xml"));
+ }
+
+ public void testEnLanguage() throws Exception {
+ assertEquals(
+ "res/values-en-rUS/strings-en.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
+ " <string name=\"s2\">Andriod activites!</string>\n" +
+ " ^\n" +
+ "res/values-en-rUS/strings-en.xml:6: Warning: \"activites\" is a common misspelling; did you mean \"activities\" ? [Typos]\n" +
+ " <string name=\"s2\">Andriod activites!</string>\n" +
+ " ^\n" +
+ "res/values-en-rUS/strings-en.xml:8: Warning: \"Cmoputer\" is a common misspelling; did you mean \"Computer\" ? [Typos]\n" +
+ " <string name=\"s3\"> (Cmoputer </string>\n" +
+ " ^\n" +
+ "res/values-en-rUS/strings-en.xml:10: Warning: \"throught\" is a common misspelling; did you mean \"thought\" or \"through\" or \"throughout\" ? [Typos]\n" +
+ " <string name=\"s4\"><b>throught</b></string>\n" +
+ " ^\n" +
+ "res/values-en-rUS/strings-en.xml:12: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n" +
+ " <string name=\"s5\">Seach</string>\n" +
+ " ^\n" +
+ "res/values-en-rUS/strings-en.xml:16: Warning: \"Tuscon\" is a common misspelling; did you mean \"Tucson\" ? [Typos]\n" +
+ " <string name=\"s7\">Tuscon tuscon</string>\n" +
+ " ^\n" +
+ "res/values-en-rUS/strings-en.xml:20: Warning: \"Ok\" is usually capitalized as \"OK\" [Typos]\n" +
+ " <string name=\"dlg_button_ok\">Ok</string>\n" +
+ " ^\n" +
+ "0 errors, 7 warnings\n" +
+ "",
+ lintProject("res/values/typos.xml=>res/values-en-rUS/strings-en.xml"));
+ }
+
+ public void testEnLanguageBcp47() throws Exception {
+ // Check BCP-47 locale declaration for English
+ assertEquals(
+ "res/values-b+en+USA/strings-en.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
+ " <string name=\"s2\">Andriod activites!</string>\n" +
+ " ^\n" +
+ "res/values-b+en+USA/strings-en.xml:6: Warning: \"activites\" is a common misspelling; did you mean \"activities\" ? [Typos]\n" +
+ " <string name=\"s2\">Andriod activites!</string>\n" +
+ " ^\n" +
+ "res/values-b+en+USA/strings-en.xml:8: Warning: \"Cmoputer\" is a common misspelling; did you mean \"Computer\" ? [Typos]\n" +
+ " <string name=\"s3\"> (Cmoputer </string>\n" +
+ " ^\n" +
+ "res/values-b+en+USA/strings-en.xml:10: Warning: \"throught\" is a common misspelling; did you mean \"thought\" or \"through\" or \"throughout\" ? [Typos]\n" +
+ " <string name=\"s4\"><b>throught</b></string>\n" +
+ " ^\n" +
+ "res/values-b+en+USA/strings-en.xml:12: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n" +
+ " <string name=\"s5\">Seach</string>\n" +
+ " ^\n" +
+ "res/values-b+en+USA/strings-en.xml:16: Warning: \"Tuscon\" is a common misspelling; did you mean \"Tucson\" ? [Typos]\n" +
+ " <string name=\"s7\">Tuscon tuscon</string>\n" +
+ " ^\n" +
+ "res/values-b+en+USA/strings-en.xml:20: Warning: \"Ok\" is usually capitalized as \"OK\" [Typos]\n" +
+ " <string name=\"dlg_button_ok\">Ok</string>\n" +
+ " ^\n" +
+ "0 errors, 7 warnings\n",
+ lintProject("res/values/typos.xml=>res/values-b+en+USA/strings-en.xml"));
+ }
+
+ public void testNorwegian() throws Exception {
+ // UTF-8 handling
+ assertEquals(
+ "res/values-nb/typos.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
+ " <string name=\"s2\">Mer morro med Andriod</string>\n" +
+ " ^\n" +
+ "res/values-nb/typos.xml:6: Warning: \"morro\" is a common misspelling; did you mean \"moro\" ? [Typos]\n" +
+ " <string name=\"s2\">Mer morro med Andriod</string>\n" +
+ " ^\n" +
+ "res/values-nb/typos.xml:8: Warning: \"Parallel\" is a common misspelling; did you mean \"Parallell\" ? [Typos]\n" +
+ " <string name=\"s3\"> Parallel </string>\n" +
+ " ^\n" +
+ "res/values-nb/typos.xml:10: Warning: \"altid\" is a common misspelling; did you mean \"alltid\" ? [Typos]\n" +
+ " <string name=\"s4\"><b>altid</b></string>\n" +
+ " ^\n" +
+ "res/values-nb/typos.xml:12: Warning: \"Altid\" is a common misspelling; did you mean \"Alltid\" ? [Typos]\n" +
+ " <string name=\"s5\">Altid</string>\n" +
+ " ^\n" +
+ "res/values-nb/typos.xml:18: Warning: \"karriære\" is a common misspelling; did you mean \"karrière\" ? [Typos]\n" +
+ " <string name=\"s7\">Koding er en spennende karriære</string>\n" +
+ " ^\n" +
+ "0 errors, 6 warnings\n" +
+ "",
+ lintProject("res/values-nb/typos.xml"));
+ }
+
+ public void testGerman() throws Exception {
+ // Test globbing and multiple word matching
+ assertEquals(
+ "res/values-de/typos.xml:6: Warning: \"befindet eine\" is a common misspelling; did you mean \"befindet sich eine\" ? [Typos]\n" +
+ " wo befindet eine ip\n" +
+ " ^\n" +
+ "res/values-de/typos.xml:9: Warning: \"Authorisierungscode\" is a common misspelling; did you mean \"Autorisierungscode\" ? [Typos]\n" +
+ " <string name=\"s2\">(Authorisierungscode!)</string>\n" +
+ " ^\n" +
+ "res/values-de/typos.xml:10: Warning: \"zurück gefoobaren\" is a common misspelling; did you mean \"zurückgefoobaren\" ? [Typos]\n" +
+ " <string name=\"s3\"> zurück gefoobaren!</string>\n" +
+ " ^\n" +
+ "0 errors, 3 warnings\n" +
+ "",
+ lintProject("res/values-de/typos.xml"));
+ }
+
+ public void testOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject("res/values/typos.xml=>res/values-xy/strings.xml"));
+ }
+
+ public void testGetReplacements() {
+ String s = "\"throught\" is a common misspelling; did you mean \"thought\" or " +
+ "\"through\" or \"throughout\" ?\n";
+ assertEquals("throught", TypoDetector.getTypo(s, TEXT));
+ assertEquals(Arrays.asList("thought", "through", "throughout"),
+ TypoDetector.getSuggestions(s, TEXT).getReplacements());
+ }
+
+ public void testNorwegianDefault() throws Exception {
+ assertEquals(
+ "res/values/typos.xml:5: Warning: \"altid\" is a common misspelling; did you mean \"alltid\" ? [Typos]\n" +
+ " <string name=\"s4\"><b>altid</b></string>\n" +
+ " ^\n" +
+ "res/values/typos.xml:7: Warning: \"Altid\" is a common misspelling; did you mean \"Alltid\" ? [Typos]\n" +
+ " <string name=\"s5\">Altid</string>\n" +
+ " ^\n" +
+ "0 errors, 2 warnings\n",
+
+ lintProject("res/values-nb/typos_locale.xml=>res/values/typos.xml"));
+ }
+
+
+ public void testPluralsAndStringArrays() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=82588
+ assertEquals(""
+ + "res/values/plurals_typography.xml:8: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n"
+ + " <item quantity=\"other\">Andriod</item>\n"
+ + " ^\n"
+ + "res/values/plurals_typography.xml:13: Warning: \"Seach\" is a common misspelling; did you mean \"Search\" ? [Typos]\n"
+ + " <item>Seach</item>\n"
+ + " ^\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject("res/values/plurals_typography.xml"));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoLookupTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoLookupTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoLookupTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypoLookupTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetectorTest.java
new file mode 100644
index 0000000..8788497
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeBroadcastReceiverDetectorTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.tools.lint.checks.UnsafeBroadcastReceiverDetector.PROTECTED_BROADCASTS;
+
+import com.android.annotations.Nullable;
+import com.android.testutils.SdkTestCase;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName", "StatementWithEmptyBody",
+ "MethodMayBeStatic"})
+public class UnsafeBroadcastReceiverDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new UnsafeBroadcastReceiverDetector();
+ }
+
+ public void testBroken() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/TestReceiver.java:10: Warning: This broadcast receiver declares "
+ + "an intent-filter for a protected broadcast action string, which can only be "
+ + "sent by the system, not third-party applications. However, the receiver's "
+ + "onReceive method does not appear to call getAction to ensure that the "
+ + "received Intent's action string matches the expected value, potentially "
+ + "making it possible for another actor to send a spoofed intent with no "
+ + "action string or a different action string and cause undesired "
+ + "behavior. [UnsafeProtectedBroadcastReceiver]\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " ~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ copy("res/values/strings.xml"),
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <receiver\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:name=\".TestReceiver\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.BOOT_COMPLETED\"/>\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"
+ + "\n"),
+ java("src/test/pkg/TestReceiver.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.BroadcastReceiver;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "public class TestReceiver extends BroadcastReceiver {\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " }\n"
+ + "\n"
+ + " // Anonymous classes should NOT be counted as a must-register\n"
+ + " private static BroadcastReceiver dummy() {\n"
+ + " return new BroadcastReceiver() {\n"
+ + " @Override\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " }\n"
+ + " };\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testBroken2() throws Exception {
+ assertEquals(
+ "AndroidManifest.xml:12: Warning: BroadcastReceivers that declare an " +
+ "intent-filter for SMS_DELIVER or SMS_RECEIVED must ensure that the caller has " +
+ "the BROADCAST_SMS permission, otherwise it is possible for malicious actors to " +
+ "spoof intents. [UnprotectedSMSBroadcastReceiver]\n" +
+ " <receiver\n" +
+ " ^\n" +
+ "0 errors, 1 warnings\n",
+ lintProject(
+ copy("res/values/strings.xml"),
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <receiver\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:name=\".TestReceiver\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"
+ + "\n")
+ ));
+ }
+
+ public void testReferencesIntentVariable() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/TestReceiver.java:10: Warning: This broadcast receiver declares "
+ + "an intent-filter for a protected broadcast action string, which can only be "
+ + "sent by the system, not third-party applications. However, the receiver's "
+ + "onReceive method does not appear to call getAction to ensure that the "
+ + "received Intent's action string matches the expected value, potentially "
+ + "making it possible for another actor to send a spoofed intent with no action "
+ + "string or a different action string and cause undesired behavior. In this "
+ + "case, it is possible that the onReceive method passed the received Intent "
+ + "to another method that checked the action string. If so, this finding can "
+ + "safely be ignored. [UnsafeProtectedBroadcastReceiver]\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " ~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ copy("res/values/strings.xml"),
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <receiver\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:name=\".TestReceiver\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.BOOT_COMPLETED\"/>\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"
+ + "\n"),
+ java("src/test/pkg/TestReceiver.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.BroadcastReceiver;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "public class TestReceiver extends BroadcastReceiver {\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " System.out.println(intent);\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testCorrect() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <receiver\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:name=\".TestReceiver2\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.BOOT_COMPLETED\"/>\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"
+ + "\n"),
+ java("src/test/pkg/TestReceiver2.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.content.BroadcastReceiver;\n"
+ + "import android.content.Context;\n"
+ + "import android.content.Intent;\n"
+ + "\n"
+ + "public class TestReceiver2 extends BroadcastReceiver {\n"
+ + "\n"
+ + " @Override\n"
+ + " public void onReceive(Context context, Intent intent) {\n"
+ + " if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testCorrect2() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ xml("AndroidManifest.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"test.pkg\"\n"
+ + " android:versionCode=\"1\"\n"
+ + " android:versionName=\"1.0\" >\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"14\" />\n"
+ + "\n"
+ + " <application\n"
+ + " android:icon=\"@drawable/ic_launcher\"\n"
+ + " android:label=\"@string/app_name\" >\n"
+ + " <receiver\n"
+ + " android:label=\"@string/app_name\"\n"
+ + " android:name=\".TestReceiver\"\n"
+ + " android:permission=\"android.permission.BROADCAST_SMS\" >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.provider.Telephony.SMS_RECEIVED\"/>\n"
+ + " </intent-filter>\n"
+ + " </receiver>\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n"
+ + "\n")
+ ));
+ }
+
+ public static void testDbUpToDate() throws Exception {
+ List<String> expected = getProtectedBroadcasts();
+ if (expected == null) {
+ return;
+ }
+ List<String> actual = Arrays.asList(PROTECTED_BROADCASTS);
+ if (!expected.equals(actual)) {
+ System.out.println("Correct list of broadcasts:");
+ for (String name : expected) {
+ System.out.println(" \"" + name + "\",");
+ }
+ fail("List of protected broadcast names has changed:\n" +
+ // Make the diff show what it take to bring the actual results into the
+ // expected results
+ SdkTestCase.getDiff(Joiner.on('\n').join(actual),
+ Joiner.on('\n').join(expected)));
+ }
+ }
+
+ @Nullable
+ private static List<String> getProtectedBroadcasts() throws IOException {
+ String top = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
+ if (top == null) {
+ top = "/Users/tnorbye/dev/mnc-dev";
+ }
+
+ // TODO: We should ship this file with the SDK!
+ File file = new File(top, "frameworks/base/core/res/AndroidManifest.xml");
+ if (!file.exists()) {
+ System.out.println("Set $ANDROID_BUILD_TOP to point to the git repository");
+ return null;
+ }
+ String xml = Files.toString(file, Charsets.UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ Set<String> list = Sets.newHashSet();
+ if (document != null && document.getDocumentElement() != null) {
+ NodeList children = document.getDocumentElement().getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ short nodeType = child.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE
+ && child.getNodeName().equals("protected-broadcast")) {
+ Element element = (Element) child;
+ String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (!name.isEmpty()) {
+ list.add(name);
+ }
+ }
+ }
+ }
+
+ List<String> expected = Lists.newArrayList(list);
+ Collections.sort(expected);
+ return expected;
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeNativeCodeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeNativeCodeDetectorTest.java
new file mode 100644
index 0000000..0cdb63c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnsafeNativeCodeDetectorTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "JavaLangImport", "ClassNameDiffersFromFileName"})
+public class UnsafeNativeCodeDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new UnsafeNativeCodeDetector();
+ }
+
+ public void testLoad() throws Exception {
+ assertEquals(
+ "src/test/pkg/Load.java:12: Warning: Dynamically loading code using load is risky, please use loadLibrary instead when possible [UnsafeDynamicallyLoadedCode]\n" +
+ " Runtime.getRuntime().load(\"/data/data/test.pkg/files/libhello.so\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "src/test/pkg/Load.java:14: Warning: Dynamically loading code using load is risky, please use loadLibrary instead when possible [UnsafeDynamicallyLoadedCode]\n" +
+ " System.load(\"/data/data/test.pkg/files/libhello.so\");\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 2 warnings\n",
+ lintProject(java("src/test/pkg/Load.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import java.lang.NullPointerException;\n"
+ + "import java.lang.Runtime;\n"
+ + "import java.lang.SecurityException;\n"
+ + "import java.lang.System;\n"
+ + "import java.lang.UnsatisfiedLinkError;\n"
+ + "\n"
+ + "public class Load {\n"
+ + " public static void foo() {\n"
+ + " try {\n"
+ + " Runtime.getRuntime().load(\"/data/data/test.pkg/files/libhello.so\");\n"
+ + " Runtime.getRuntime().loadLibrary(\"hello\"); // ok\n"
+ + " System.load(\"/data/data/test.pkg/files/libhello.so\");\n"
+ + " System.loadLibrary(\"hello\"); // ok\n"
+ + " } catch (SecurityException ignore) {\n"
+ + " } catch (UnsatisfiedLinkError ignore) {\n"
+ + " } catch (NullPointerException ignore) {\n"
+ + " }\n"
+ + " }\n"
+ + "}\n")));
+ }
+
+ public void testNativeCode() throws Exception {
+ assertEquals(""
+ + "assets/hello: Warning: Embedding non-shared library native executables into applications should be avoided when possible, as there is an increased risk that the executables could be tampered with after installation. Instead, native code should be placed in a shared library, and the features of the development environment should be used to place the shared library in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+ + "res/raw/hello: Warning: Embedding non-shared library native executables into applications should be avoided when possible, as there is an increased risk that the executables could be tampered with after installation. Instead, native code should be placed in a shared library, and the features of the development environment should be used to place the shared library in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+ + "assets/libhello-jni.so: Warning: Shared libraries should not be placed in the res or assets directories. Please use the features of your development environment to place shared libraries in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+ + "res/raw/libhello-jni.so: Warning: Shared libraries should not be placed in the res or assets directories. Please use the features of your development environment to place shared libraries in the lib directory of the compiled APK. [UnsafeNativeCodeLocation]\n"
+ + "0 errors, 4 warnings\n",
+ lintProject(
+ copy("res/raw/hello"),
+ copy("res/raw/libhello-jni.so"),
+ copy("res/raw/hello", "assets/hello"),
+ copy("res/raw/libhello-jni.so", "assets/libhello-jni.so"),
+ copy("lib/armeabi/hello"),
+ copy("lib/armeabi/libhello-jni.so")));
+ }
+
+ public void testNoWorkInInteractiveMode() throws Exception {
+ // Make sure we don't scan through all resource folders when just incrementally
+ // editing a Java file
+ assertEquals(
+ "No warnings.",
+ lintProjectIncrementally(
+ "src/test/pkg/Load.java",
+ java("src/test/pkg/Load.java", ""
+ + "package test.pkg;\n"
+ + "public class Load { }\n"),
+ copy("res/raw/hello"),
+ copy("res/raw/libhello-jni.so"),
+ copy("res/raw/hello", "assets/hello"),
+ copy("res/raw/libhello-jni.so", "assets/libhello-jni.so"),
+ copy("lib/armeabi/hello"),
+ copy("lib/armeabi/libhello-jni.so")));
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
new file mode 100644
index 0000000..cd99a0b
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class UnusedResourceDetectorTest extends AbstractCheckTest {
+ private boolean mEnableIds = false;
+
+ @Override
+ protected Detector getDetector() {
+ return new UnusedResourceDetector();
+ }
+
+ @Override
+ protected boolean allowCompilationErrors() {
+ // Some of these unit tests are still relying on source code that references
+ // unresolved symbols etc.
+ return true;
+ }
+
+ @Override
+ protected boolean isEnabled(Issue issue) {
+ //noinspection SimplifiableIfStatement
+ if (issue == UnusedResourceDetector.ISSUE_IDS) {
+ return mEnableIds;
+ } else {
+ return true;
+ }
+ }
+
+ public void testUnused() throws Exception {
+ mEnableIds = false;
+ assertEquals(""
+ + "res/layout/accessibility.xml:2: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:id=\"@+id/newlinear\" android:orientation=\"vertical\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\">\n"
+ + "^\n"
+ + "res/layout/main.xml:2: Warning: The resource R.layout.main appears to be unused [UnusedResources]\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "res/layout/other.xml:2: Warning: The resource R.layout.other appears to be unused [UnusedResources]\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + "^\n"
+ + "res/values/strings2.xml:3: Warning: The resource R.string.hello appears to be unused [UnusedResources]\n"
+ + " <string name=\"hello\">Hello</string>\n"
+ + " ~~~~~~~~~~~~\n"
+ + "0 errors, 4 warnings\n",
+
+ lintProject(
+ "res/values/strings2.xml",
+ "res/layout/layout1.xml=>res/layout/main.xml",
+ "res/layout/layout1.xml=>res/layout/other.xml",
+
+ // Rename .txt files to .java
+ "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
+ "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
+ "AndroidManifest.xml",
+ "res/layout/accessibility.xml"));
+ }
+
+ public void testUnusedIds() throws Exception {
+ mEnableIds = true;
+
+ assertEquals(""
+ + "res/layout/accessibility.xml:2: Warning: The resource R.layout.accessibility appears to be unused [UnusedResources]\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:id=\"@+id/newlinear\" android:orientation=\"vertical\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\">\n"
+ + "^\n"
+ + "res/layout/accessibility.xml:2: Warning: The resource R.id.newlinear appears to be unused [UnusedIds]\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:id=\"@+id/newlinear\" android:orientation=\"vertical\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/accessibility.xml:3: Warning: The resource R.id.button1 appears to be unused [UnusedIds]\n"
+ + " <Button android:text=\"Button\" android:id=\"@+id/button1\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\"></Button>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/accessibility.xml:4: Warning: The resource R.id.android_logo appears to be unused [UnusedIds]\n"
+ + " <ImageView android:id=\"@+id/android_logo\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:src=\"@drawable/android_button\" android:focusable=\"false\" android:clickable=\"false\" android:layout_weight=\"1.0\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/accessibility.xml:5: Warning: The resource R.id.android_logo2 appears to be unused [UnusedIds]\n"
+ + " <ImageButton android:importantForAccessibility=\"yes\" android:id=\"@+id/android_logo2\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:src=\"@drawable/android_button\" android:focusable=\"false\" android:clickable=\"false\" android:layout_weight=\"1.0\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 5 warnings\n",
+
+ lintProject(
+ "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
+ "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
+ "AndroidManifest.xml",
+ "res/layout/accessibility.xml"));
+ }
+
+ public void testArrayReference() throws Exception {
+ assertEquals(""
+ // The string is unused, but only because the array referencing it is unused too.
+ + "res/values/arrayusage.xml:2: Warning: The resource R.string.my_item appears to be unused [UnusedResources]\n"
+ + "<string name=\"my_item\">An Item</string>\n"
+ + " ~~~~~~~~~~~~~~\n"
+ + "res/values/arrayusage.xml:3: Warning: The resource R.array.my_array appears to be unused [UnusedResources]\n"
+ + "<string-array name=\"my_array\">\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ xml("res/values/arrayusage.xml", ""
+ + "<resources>\n"
+ + "<string name=\"my_item\">An Item</string>\n"
+ + "<string-array name=\"my_array\">\n"
+ + " <item>@string/my_item</item>\n"
+ + "</string-array>\n"
+ + "</resources>\n")
+ ));
+ }
+
+ public void testArrayReferenceIncluded() throws Exception {
+ assertEquals("No warnings.",
+
+ lintProject(
+ xml("res/values/arrayusage.xml", ""
+ + "<resources xmlns:tools=\"http://schemas.android.com/tools\""
+ + " tools:keep=\"@array/my_array\">\n"
+ + "<string name=\"my_item\">An Item</string>\n"
+ + "<string-array name=\"my_array\">\n"
+ + " <item>@string/my_item</item>\n"
+ + "</string-array>\n"
+ + "</resources>\n")
+ ));
+ }
+
+ public void testAttrs() throws Exception {
+ assertEquals(""
+ + "res/layout/customattrlayout.xml:2: Warning: The resource R.layout.customattrlayout appears to be unused [UnusedResources]\n"
+ + "<foo.bar.ContentFrame\n"
+ + "^\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "res/values/customattr.xml",
+ "res/layout/customattrlayout.xml",
+ "unusedR.java.txt=>gen/my/pkg/R.java",
+ "AndroidManifest.xml"));
+ }
+
+ public void testMultiProjectIgnoreLibraries() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java",
+
+ // Library project
+ "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
+ "multiproject/library.properties=>../LibraryProject/project.properties",
+ "multiproject/LibraryCode.java.txt=>../LibraryProject/src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>../LibraryProject/res/values/strings.xml"
+ ));
+ }
+
+ public void testMultiProject() throws Exception {
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>res/values/strings.xml"
+ );
+ assertEquals(
+ // string1 is defined and used in the library project
+ // string2 is defined in the library project and used in the master project
+ // string3 is defined in the library project and not used anywhere
+ "LibraryProject/res/values/strings.xml:7: Warning: The resource R.string.string3 appears to be unused [UnusedResources]\n" +
+ " <string name=\"string3\">String 3</string>\n" +
+ " ~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n",
+
+ checkLint(Arrays.asList(master, library)).replace("/TESTROOT/", ""));
+ }
+
+ public void testFqcnReference() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/layout/layout1.xml=>res/layout/main.xml",
+ "src/test/pkg/UnusedReference.java.txt=>src/test/pkg/UnusedReference.java",
+ "AndroidManifest.xml"));
+ }
+
+ /* Not sure about this -- why would we ignore drawable XML?
+ public void testIgnoreXmlDrawable() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/drawable/ic_menu_help.xml",
+ "gen/my/pkg/R2.java.txt=>gen/my/pkg/R.java"
+ ));
+ }
+ */
+
+ public void testPlurals() throws Exception {
+ //noinspection ClassNameDiffersFromFileName
+ assertEquals("No warnings.",
+ lintProject(
+ copy("res/values/strings4.xml"),
+ copy("res/values/plurals.xml"),
+ copy("AndroidManifest.xml"),
+ java("src/test/pkg/Test.java", ""
+ + "package test.pkg;\n"
+ + "public class Test {\n"
+ + " public void test() {"
+ + " int used = R.plurals.my_plural;\n"
+ + " }"
+ + "}")
+ ));
+ }
+
+ public void testNoMerging() throws Exception {
+ // http://code.google.com/p/android/issues/detail?id=36952
+
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>res/values/strings.xml"
+ );
+ assertEquals(
+ // The strings are all referenced in the library project's manifest file
+ // which in this project is merged in
+ "LibraryProject/res/values/strings.xml:7: Warning: The resource R.string.string3 appears to be unused [UnusedResources]\n" +
+ " <string name=\"string3\">String 3</string>\n" +
+ " ~~~~~~~~~~~~~~\n" +
+ "0 errors, 1 warnings\n",
+
+ checkLint(Arrays.asList(master, library)).replace("/TESTROOT/", ""));
+ }
+
+ public void testLibraryMerging() throws Exception {
+ // http://code.google.com/p/android/issues/detail?id=36952
+ File master = getProjectDir("MasterProject",
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main-merge.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java"
+ );
+ File library = getProjectDir("LibraryProject",
+ // Library project
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "multiproject/LibraryCode.java.txt=>src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>res/values/strings.xml"
+ );
+ assertEquals(
+ // The strings are all referenced in the library project's manifest file
+ // which in this project is merged in
+ "No warnings.",
+
+ checkLint(Arrays.asList(master, library)));
+ }
+
+ public void testCornerCase() throws Exception {
+ // See http://code.google.com/p/projectlombok/issues/detail?id=415
+ mEnableIds = true;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "src/test/pkg/Foo.java.txt=>src/test/pkg/Foo.java",
+ "AndroidManifest.xml"));
+ }
+
+ public void testAnalytics() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=42565
+ mEnableIds = false;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/analytics.xml"
+ ));
+ }
+
+ public void testIntegers() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=53995
+ mEnableIds = true;
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "res/values/integers.xml",
+ "res/anim/slide_in_out.xml"
+ ));
+ }
+
+ public void testIntegerArrays() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=59761
+ mEnableIds = false;
+ assertEquals(
+ "No warnings.",
+ lintProject("res/values/integer_arrays.xml=>res/values/integer_arrays.xml"));
+ }
+
+ public void testUnitTestReferences() throws Exception {
+ // Make sure that we pick up references in unit tests as well
+ // Regression test for
+ // https://code.google.com/p/android/issues/detail?id=79066
+ mEnableIds = false;
+ //noinspection ClassNameDiffersFromFileName
+ assertEquals("No warnings.",
+
+ lintProject(
+ copy("res/values/strings2.xml"),
+ copy("res/layout/layout1.xml", "res/layout/main.xml"),
+ copy("res/layout/layout1.xml", "res/layout/other.xml"),
+
+ copy("src/my/pkg/Test.java.txt", "src/my/pkg/Test.java"),
+ copy("gen/my/pkg/R.java.txt", "gen/my/pkg/R.java"),
+ copy("AndroidManifest.xml"),
+ copy("res/layout/accessibility.xml"),
+
+ // Add unit test source which references resources which would otherwise
+ // be marked as unused
+ java("test/my/pkg/MyTest.java", ""
+ + "package my.pkg;\n"
+ + "class MyTest {\n"
+ + " public void test() {\n"
+ + " System.out.println(R.layout.accessibility);\n"
+ + " System.out.println(R.layout.main);\n"
+ + " System.out.println(R.layout.other);\n"
+ + " System.out.println(R.string.hello);\n"
+ + " }\n"
+ + "}\n")
+ ));
+ }
+
+ public void testDataBinding() throws Exception {
+ // Make sure that resources referenced only via a data binding expression
+ // are not counted as unused.
+ // Regression test for https://code.google.com/p/android/issues/detail?id=183934
+ mEnableIds = false;
+ assertEquals("No warnings.",
+
+ lintProject(
+ xml("res/values/resources.xml", ""
+ + "<resources>\n"
+ + " <item type='dimen' name='largePadding'>20dp</item>\n"
+ + " <item type='dimen' name='smallPadding'>15dp</item>\n"
+ + " <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+ + "</resources>"),
+
+ // Add unit test source which references resources which would otherwise
+ // be marked as unused
+ xml("res/layout/db.xml", ""
+ + "<layout xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ + " xmlns:tools=\"http://schemas.android.com/tools\" "
+ + " tools:keep=\"@layout/db\">\n"
+ + " <data>\n"
+ + " <variable name=\"user\" type=\"com.example.User\"/>\n"
+ + " </data>\n"
+ + " <LinearLayout\n"
+ + " android:orientation=\"vertical\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\"\n"
+ // Data binding expressions
+ + " android:padding=\"@{large? @dimen/largePadding : @dimen/smallPadding}\"\n"
+ + " android:text=\"@{@string/nameFormat(firstName, lastName)}\" />\n"
+ + "</layout>")
+ ));
+ }
+
+ public void testPublic() throws Exception {
+ // Resources marked as public should not be listed as potentially unused
+ mEnableIds = false;
+ assertEquals(""
+ + "res/values/resources.xml:4: Warning: The resource R.string.nameFormat appears to be unused [UnusedResources]\n"
+ + " <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+ + " ~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ xml("res/values/resources.xml", ""
+ + "<resources>\n"
+ + " <item type='dimen' name='largePadding'>20dp</item>\n"
+ + " <item type='dimen' name='smallPadding'>15dp</item>\n"
+ + " <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+ + " <public type='dimen' name='largePadding' />"
+ + " <public type='dimen' name='smallPadding' />"
+ + "</resources>")
+ ));
+ }
+
+ public void testDynamicResources() throws Exception {
+ assertEquals(""
+ + "UnusedResourceDetectorTest_testDynamicResources: Warning: The resource R.string.cat appears to be unused [UnusedResources]\n"
+ + "UnusedResourceDetectorTest_testDynamicResources: Warning: The resource R.string.dog appears to be unused [UnusedResources]\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject(
+ "res/layout/layout1.xml=>res/layout/main.xml",
+ "src/test/pkg/UnusedReferenceDynamic.java.txt=>src/test/pkg/UnusedReferenceDynamic.java",
+ "AndroidManifest.xml"));
+ }
+
+ public void testStaticImport() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=40293
+ // 40293: Lint reports resource as unused when referenced via "import static"
+ mEnableIds = false;
+ assertEquals(""
+ + "No warnings.",
+
+ lintProject(
+ xml("res/values/resources.xml", ""
+ + "<resources>\n"
+ + " <item type='dimen' name='largePadding'>20dp</item>\n"
+ + " <item type='dimen' name='smallPadding'>15dp</item>\n"
+ + " <item type='string' name='nameFormat'>%1$s %2$s</item>\n"
+ + "</resources>"),
+
+ // Add unit test source which references resources which would otherwise
+ // be marked as unused
+ java("src/test/pkg/TestCode.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import static test.pkg.R.dimen.*;\n"
+ + "import static test.pkg.R.string.nameFormat;\n"
+ + "import test.pkg.R.dimen;\n"
+ + "\n"
+ + "public class TestCode {\n"
+ + " public void test() {\n"
+ + " int x = dimen.smallPadding; // Qualified import\n"
+ + " int y = largePadding; // Static wildcard import\n"
+ + " int z = nameFormat; // Static explicit import\n"
+ + " }\n"
+ + "}\n"),
+ java("src/test/pkg/R.java", ""
+ + "package test.pkg;\n"
+ + "public class R {\n"
+ + " public static class dimen {\n"
+ + " public static final int largePadding = 1;\n"
+ + " public static final int smallPadding = 2;\n"
+ + " }\n"
+ + " public static class string {\n"
+ + " public static final int nameFormat = 3;\n"
+ + " }\n"
+ + "}")
+ ));
+ }
+
+ public void testStyles() throws Exception {
+ mEnableIds = false;
+ assertEquals(""
+ + "res/values/styles.xml:5: Warning: The resource R.style.UnusedStyle appears to be unused [UnusedResources]\n"
+ + " <style name=\"UnusedStyle\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/styles.xml:6: Warning: The resource R.style.UnusedStyle_Sub appears to be unused [UnusedResources]\n"
+ + " <style name=\"UnusedStyle.Sub\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/values/styles.xml:7: Warning: The resource R.style.UnusedStyle_Something_Sub appears to be unused [UnusedResources]\n"
+ + " <style name=\"UnusedStyle.Something.Sub\" parent=\"UnusedStyle\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 3 warnings\n",
+
+ lintProject(
+ xml("res/values/styles.xml", ""
+ + "<resources>\n"
+ + " <style name=\"UsedStyle\" parent=\"android:Theme\"/>\n"
+ + " <style name=\"UsedStyle.Sub\"/>\n"
+ + " <style name=\"UsedStyle.Something.Sub\" parent=\"UsedStyle\"/>\n"
+
+ + " <style name=\"UnusedStyle\"/>\n"
+ + " <style name=\"UnusedStyle.Sub\"/>\n"
+ + " <style name=\"UnusedStyle.Something.Sub\" parent=\"UnusedStyle\"/>\n"
+
+ + " <style name=\"ImplicitUsed\" parent=\"android:Widget.ActionBar\"/>\n"
+ + "</resources>")
+ ));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ if (!getName().startsWith("testDynamicResources")) {
+ return super.createClient();
+ }
+
+ // Set up a mock project model for the resource configuration test(s)
+ // where we provide a subset of densities to be included
+
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ /*
+ Simulate dynamic resources in this setup:
+ defaultConfig {
+ ...
+ resValue "string", "cat", "Some Data"
+ }
+ buildTypes {
+ debug {
+ ...
+ resValue "string", "foo", "Some Data"
+ }
+ release {
+ ...
+ resValue "string", "xyz", "Some Data"
+ resValue "string", "dog", "Some Data"
+ }
+ }
+ */
+ ClassField foo = mock(ClassField.class);
+ when(foo.getName()).thenReturn("foo");
+ when(foo.getType()).thenReturn("string");
+ ClassField xyz = mock(ClassField.class);
+ when(xyz.getName()).thenReturn("xyz");
+ when(xyz.getType()).thenReturn("string");
+ ClassField cat = mock(ClassField.class);
+ when(cat.getName()).thenReturn("cat");
+ when(cat.getType()).thenReturn("string");
+ ClassField dog = mock(ClassField.class);
+ when(dog.getName()).thenReturn("dog");
+ when(dog.getType()).thenReturn("string");
+
+ Map<String, ClassField> debugResValues = ImmutableMap.of("foo", foo);
+ BuildType type1 = mock(BuildType.class);
+ when(type1.getName()).thenReturn("debug");
+ when(type1.getResValues()).thenReturn(debugResValues);
+ Map<String, ClassField> releaseResValues =
+ ImmutableMap.of("xyz", xyz, "dog", dog);
+ BuildType type2 = mock(BuildType.class);
+ when(type2.getName()).thenReturn("release");
+ when(type2.getResValues()).thenReturn(releaseResValues);
+
+ BuildTypeContainer container1 = mock(BuildTypeContainer.class);
+ when(container1.getBuildType()).thenReturn(type1);
+ BuildTypeContainer container2 = mock(BuildTypeContainer.class);
+ when(container2.getBuildType()).thenReturn(type2);
+
+ SourceProvider debugProvider = mock(SourceProvider.class);
+ when(debugProvider.getResDirectories()).thenReturn(Collections.<File>emptyList());
+ when(debugProvider.getJavaDirectories()).thenReturn(Collections.<File>emptyList());
+ SourceProvider releaseProvider = mock(SourceProvider.class);
+ when(releaseProvider.getResDirectories()).thenReturn(Collections.<File>emptyList());
+ when(releaseProvider.getJavaDirectories()).thenReturn(Collections.<File>emptyList());
+
+ when(container1.getSourceProvider()).thenReturn(debugProvider);
+ when(container2.getSourceProvider()).thenReturn(releaseProvider);
+
+ Map<String, ClassField> defaultResValues = ImmutableMap.of("cat", cat);
+ ProductFlavor defaultFlavor = mock(ProductFlavor.class);
+ when(defaultFlavor.getResValues()).thenReturn(defaultResValues);
+
+ ProductFlavorContainer defaultContainer =
+ mock(ProductFlavorContainer.class);
+ when(defaultContainer.getProductFlavor()).thenReturn(defaultFlavor);
+
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getDefaultConfig()).thenReturn(defaultContainer);
+ when(project.getBuildTypes())
+ .thenReturn(Arrays.asList(container1, container2));
+ return project;
+ }
+
+ @Nullable
+ @Override
+ public Variant getCurrentVariant() {
+ Variant variant = mock(Variant.class);
+ when(variant.getBuildType()).thenReturn("release");
+ return variant;
+ }
+ };
+ }
+ };
+ }
+
+ @Override
+ protected void checkReportedError(@NonNull Context context, @NonNull Issue issue,
+ @NonNull Severity severity, @Nullable Location location, @NonNull String message) {
+ assertNotNull(message, UnusedResourceDetector.getUnusedResource(message, TEXT));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/UselessViewDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/Utf8DetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/VectorDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/VectorDetectorTest.java
new file mode 100644
index 0000000..2fe35c0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/VectorDetectorTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+
+import org.intellij.lang.annotations.Language;
+
+import java.io.File;
+
+ at SuppressWarnings("javadoc")
+public class VectorDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new VectorDetector();
+ }
+
+ @Language("XML")
+ private static final String VECTOR = ""
+ + "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:height=\"76dp\"\n"
+ + " android:width=\"76dp\"\n"
+ + " android:viewportHeight=\"48\"\n"
+ + " android:viewportWidth=\"48\"\n"
+ + " android:autoMirrored=\"true\"\n"
+ + " android:tint=\"?attr/colorControlActivated\">\n"
+ + "\n"
+ + " <clip-path />\n" // couldn't find any examples
+ + "\n"
+ + " <group\n"
+ + " android:name=\"root\"\n"
+ + " android:translateX=\"24.0\"\n"
+ + " android:translateY=\"24.0\" >\n"
+ + " <path\n"
+ + " android:name=\"progressBar\"\n"
+ + " android:fillColor=\"#00000000\"\n"
+ + " android:pathData=\"M0, 0 m 0, -19 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38\"\n"
+ + " android:strokeColor=\"@color/white\"\n"
+ + " android:strokeLineCap=\"square\"\n"
+ + " android:strokeLineJoin=\"miter\"\n"
+ + " android:strokeWidth=\"4\"\n"
+ + " android:trimPathEnd=\"0\"\n"
+ + " android:trimPathOffset=\"0\"\n"
+ + " android:trimPathStart=\"0\" />\n"
+ + " </group>\n"
+ + "\n"
+ + "</vector>";
+
+ public void testWarn() throws Exception {
+ assertEquals(""
+ + "res/drawable/foo.xml:6: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+ + " android:autoMirrored=\"true\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/drawable/foo.xml:7: Warning: Resource references will not work correctly in images generated for this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+ + " android:tint=\"?attr/colorControlActivated\">\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/drawable/foo.xml:9: Warning: This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+ + " <clip-path />\n"
+ + " ~~~~~~~~~~~~~\n"
+ + "res/drawable/foo.xml:11: Warning: Update Gradle plugin version to 1.5+ to correctly handle <group> tags in generated bitmaps [VectorRaster]\n"
+ + " <group\n"
+ + " ^\n"
+ + "res/drawable/foo.xml:19: Warning: Resource references will not work correctly in images generated for this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+ + " android:strokeColor=\"@color/white\"\n"
+ + " ~~~~~~~~~~~~\n"
+ + "res/drawable/foo.xml:23: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+ + " android:trimPathEnd=\"0\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~\n"
+ + "res/drawable/foo.xml:24: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+ + " android:trimPathOffset=\"0\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/drawable/foo.xml:25: Warning: This attribute is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable [VectorRaster]\n"
+ + " android:trimPathStart=\"0\" />\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 8 warnings\n",
+ lintProject(
+ xml("res/drawable/foo.xml", VECTOR),
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+ ));
+ }
+
+ public void testNoWarningsWithMinSdk21() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ xml("res/drawable/foo.xml", VECTOR),
+ copy("apicheck/minsdk21.xml", "AndroidManifest.xml")
+ ));
+ }
+
+ public void testNoWarningsInV21Folder() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ xml("res/drawable-v21/foo.xml", VECTOR),
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+ ));
+ }
+
+ public void testNoGroupWarningWithPlugin15() throws Exception {
+ assertEquals("No warnings.",
+ lintProject(
+ xml("res/drawable/foo.xml", ""
+ + "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:height=\"76dp\"\n"
+ + " android:width=\"76dp\"\n"
+ + " android:viewportHeight=\"48\"\n"
+ + " android:viewportWidth=\"48\">\n"
+ + "\n"
+ + " <group />"
+ + "\n"
+ + "</vector>"
+ + ""),
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml")
+ ));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new Project(this, dir, referenceDir) {
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ String modelVersion = "1.4.0-alpha2";
+ if (getName().equals("VectorDetectorTest_testNoGroupWarningWithPlugin15")) {
+ modelVersion = "1.5.0-alpha1";
+ }
+ return PrivateResourceDetectorTest.createMockProject(modelVersion, 3);
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewHolderDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewHolderDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewHolderDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewHolderDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
new file mode 100644
index 0000000..8145edb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTagDetectorTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings({"javadoc", "ClassNameDiffersFromFileName"})
+public class ViewTagDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ViewTagDetector();
+ }
+
+ private final TestFile mViewTagTest = java("src/test/pkg/ViewTagTest.java", ""
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.annotation.SuppressLint;\n"
+ + "import android.content.Context;\n"
+ + "import android.database.Cursor;\n"
+ + "import android.database.MatrixCursor;\n"
+ + "import android.view.LayoutInflater;\n"
+ + "import android.view.View;\n"
+ + "import android.view.ViewGroup;\n"
+ + "import android.widget.CursorAdapter;\n"
+ + "import android.widget.ImageView;\n"
+ + "import android.widget.TextView;\n"
+ + "\n"
+ + "@SuppressWarnings(\"unused\")\n"
+ + "public abstract class ViewTagTest {\n"
+ + " public View newView(Context context, ViewGroup group, Cursor cursor1,\n"
+ + " MatrixCursor cursor2) {\n"
+ + " LayoutInflater inflater = LayoutInflater.from(context);\n"
+ + " View view = inflater.inflate(android.R.layout.activity_list_item, null);\n"
+ + " view.setTag(android.R.id.background, \"Some random tag\"); // OK\n"
+ + " view.setTag(android.R.id.button1, group); // ERROR\n"
+ + " view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR\n"
+ + " view.setTag(android.R.id.icon1, cursor1); // ERROR\n"
+ + " view.setTag(android.R.id.icon2, cursor2); // ERROR\n"
+ + " view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR\n"
+ + " return view;\n"
+ + " }\n"
+ + "\n"
+ + " @SuppressLint(\"ViewTag\")\n"
+ + " public static void checkSuppress(Context context, View view) {\n"
+ + " view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon));\n"
+ + " }\n"
+ + "\n"
+ + " private class MyViewHolder {\n"
+ + " View view;\n"
+ + " }\n"
+ + "}\n");
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/ViewTagTest.java:21: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+ + " view.setTag(android.R.id.button1, group); // ERROR\n"
+ + " ~~~~~\n"
+ + "src/test/pkg/ViewTagTest.java:22: Warning: Avoid setting views as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+ + " view.setTag(android.R.id.icon, view.findViewById(android.R.id.icon)); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/ViewTagTest.java:23: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+ + " view.setTag(android.R.id.icon1, cursor1); // ERROR\n"
+ + " ~~~~~~~\n"
+ + "src/test/pkg/ViewTagTest.java:24: Warning: Avoid setting cursors as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+ + " view.setTag(android.R.id.icon2, cursor2); // ERROR\n"
+ + " ~~~~~~~\n"
+ + "src/test/pkg/ViewTagTest.java:25: Warning: Avoid setting view holders as values for setTag: Can lead to memory leaks in versions older than Android 4.0 [ViewTag]\n"
+ + " view.setTag(android.R.id.copy, new MyViewHolder()); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 5 warnings\n",
+
+ lintProject(mViewTagTest));
+ }
+
+ public void testICS() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ mViewTagTest,
+ copy("apicheck/minsdk14.xml", "AndroidManifest.xml")));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/ViewTypeDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
new file mode 100644
index 0000000..e5fdf3a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WakelockDetectorTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class WakelockDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new WakelockDetector();
+ }
+
+ public void test1() throws Exception {
+ assertEquals(
+ "src/test/pkg/WakelockActivity1.java:15: Warning: Found a wakelock acquire() but no release() calls anywhere [Wakelock]\n" +
+ " mWakeLock.acquire(); // Never released\n" +
+ " ~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "bytecode/WakelockActivity1.java.txt=>src/test/pkg/WakelockActivity1.java",
+ "bytecode/WakelockActivity1.class.data=>bin/classes/test/pkg/WakelockActivity1.class"
+ ));
+ }
+
+ public void test2() throws Exception {
+ assertEquals(
+ "src/test/pkg/WakelockActivity2.java:13: Warning: Wakelocks should be released in onPause, not onDestroy [Wakelock]\n" +
+ " mWakeLock.release(); // Should be done in onPause instead\n" +
+ " ~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "bytecode/WakelockActivity2.java.txt=>src/test/pkg/WakelockActivity2.java",
+ "bytecode/WakelockActivity2.class.data=>bin/classes/test/pkg/WakelockActivity2.class"
+ ));
+ }
+
+ public void test3() throws Exception {
+ assertEquals(
+ "src/test/pkg/WakelockActivity3.java:13: Warning: The release() call is not always reached [Wakelock]\n" +
+ " lock.release(); // Should be in finally block\n" +
+ " ~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "bytecode/WakelockActivity3.java.txt=>src/test/pkg/WakelockActivity3.java",
+ "bytecode/WakelockActivity3.class.data=>bin/classes/test/pkg/WakelockActivity3.class"
+ ));
+ }
+
+ public void test4() throws Exception {
+ assertEquals(
+ "src/test/pkg/WakelockActivity4.java:10: Warning: The release() call is not always reached [Wakelock]\n" +
+ " getLock().release(); // Should be in finally block\n" +
+ " ~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "bytecode/WakelockActivity4.java.txt=>src/test/pkg/WakelockActivity4.java",
+ "bytecode/WakelockActivity4.class.data=>bin/classes/test/pkg/WakelockActivity4.class"
+ ));
+ }
+
+ public void test5() throws Exception {
+ assertEquals(
+ "src/test/pkg/WakelockActivity5.java:13: Warning: The release() call is not always reached [Wakelock]\n" +
+ " lock.release(); // Should be in finally block\n" +
+ " ~~~~~~~\n" +
+ "0 errors, 1 warnings\n" +
+ "",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "bytecode/WakelockActivity5.java.txt=>src/test/pkg/WakelockActivity5.java",
+ "bytecode/WakelockActivity5.class.data=>bin/classes/test/pkg/WakelockActivity5.class"
+ ));
+ }
+
+ public void test6() throws Exception {
+ assertEquals(
+ "src/test/pkg/WakelockActivity6.java:19: Warning: The release() call is not always reached [Wakelock]\n" +
+ " lock.release(); // Wrong\n" +
+ " ~~~~~~~\n" +
+ "src/test/pkg/WakelockActivity6.java:28: Warning: The release() call is not always reached [Wakelock]\n" +
+ " lock.release(); // Wrong\n" +
+ " ~~~~~~~\n" +
+ "src/test/pkg/WakelockActivity6.java:65: Warning: The release() call is not always reached [Wakelock]\n" +
+ " lock.release(); // Wrong\n" +
+ " ~~~~~~~\n" +
+ "0 errors, 3 warnings\n" +
+ "",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "bytecode/WakelockActivity6.java.txt=>src/test/pkg/WakelockActivity6.java",
+ "bytecode/WakelockActivity6.class.data=>bin/classes/test/pkg/WakelockActivity6.class"
+ ));
+ }
+
+ public void test7() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "res/layout/onclick.xml=>res/layout/onclick.xml",
+ "bytecode/WakelockActivity7.java.txt=>src/test/pkg/WakelockActivity7.java",
+ "bytecode/WakelockActivity7.class.data=>bin/classes/test/pkg/WakelockActivity7.class"
+ ));
+ }
+
+ public void test8() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/WakelockActivity8.java.txt=>src/test/pkg/WakelockActivity8.java",
+ "bytecode/WakelockActivity8.class.data=>bin/classes/test/pkg/WakelockActivity8.class"
+ ));
+ }
+
+ public void test9() throws Exception {
+ // Regression test for 66040
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/WakelockActivity9.java.txt=>src/test/pkg/WakelockActivity9.java",
+ "bytecode/WakelockActivity9.class.data=>bin/classes/test/pkg/WakelockActivity9.class"
+ ));
+ }
+
+ public void test10() throws Exception {
+ // Regression test for 43212
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/WakelockActivity10.java.txt=>src/test/pkg/WakelockActivity10.java",
+ "bytecode/WakelockActivity10.class.data=>bin/classes/test/pkg/WakelockActivity10.class"
+ ));
+ }
+
+ public void testFlags() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/PowerManagerFlagTest.java:15: Warning: Should not set both PARTIAL_WAKE_LOCK and ACQUIRE_CAUSES_WAKEUP. If you do not want the screen to turn on, get rid of ACQUIRE_CAUSES_WAKEUP [Wakelock]\n"
+ + " pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|ACQUIRE_CAUSES_WAKEUP, \"Test\"); // Bad\n"
+ + " ~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/PowerManagerFlagTest.java.txt=>src/test/pkg/PowerManagerFlagTest.java",
+ "bytecode/PowerManagerFlagTest.class.data=>bin/classes/test/pkg/PowerManagerFlagTest.class"
+ ));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WebViewDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WebViewDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WebViewDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WebViewDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCallDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCallDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCallDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCallDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongCaseDetectorTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
new file mode 100644
index 0000000..7e0ea7b
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongIdDetectorTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+ at SuppressWarnings("javadoc")
+public class WrongIdDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new WrongIdDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "res/layout/layout1.xml:14: Error: The id \"button5\" is not defined anywhere. Did you mean one of {button1, button2, button3, button4} ? [UnknownId]\n" +
+ " android:layout_alignBottom=\"@+id/button5\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:17: Error: The id \"my_id3\" is not defined anywhere. Did you mean my_id2 ? [UnknownId]\n" +
+ " android:layout_alignRight=\"@+id/my_id3\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:18: Error: The id \"my_id1\" is defined but not assigned to any views. Did you mean my_id2 ? [UnknownId]\n" +
+ " android:layout_alignTop=\"@+id/my_id1\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:15: Warning: The id \"my_id2\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
+ " android:layout_alignLeft=\"@+id/my_id2\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "3 errors, 1 warnings\n" +
+ "",
+
+ lintProject(
+ "wrongid/layout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout/layout2.xml",
+ "wrongid/ids.xml=>res/values/ids.xml"
+ ));
+ }
+
+ public void testSingleFile() throws Exception {
+ assertEquals(
+ "res/layout/layout1.xml:14: Warning: The id \"button5\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
+ " android:layout_alignBottom=\"@+id/button5\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:15: Warning: The id \"my_id2\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
+ " android:layout_alignLeft=\"@+id/my_id2\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:17: Warning: The id \"my_id3\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
+ " android:layout_alignRight=\"@+id/my_id3\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:18: Warning: The id \"my_id1\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
+ " android:layout_alignTop=\"@+id/my_id1\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "0 errors, 4 warnings\n" +
+ "",
+
+ lintFiles("wrongid/layout1.xml=>res/layout/layout1.xml"));
+ }
+
+ public void testSuppressed() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "wrongid/ignorelayout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout/layout2.xml",
+ "wrongid/ids.xml=>res/values/ids.xml"
+ ));
+ }
+
+ public void testSuppressedSingleFile() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintFiles("wrongid/ignorelayout1.xml=>res/layout/layout1.xml"));
+ }
+
+ public void testNewIdPrefix() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintFiles("res/layout/default_item_badges.xml",
+ "res/layout/detailed_item.xml"));
+ }
+
+ public void testSiblings() throws Exception {
+ assertEquals(""
+ + "res/layout/siblings.xml:55: Error: @id/button5 is not a sibling in the same RelativeLayout [NotSibling]\n"
+ + " android:layout_alignTop=\"@id/button5\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/siblings.xml:56: Error: @id/button6 is not a sibling in the same RelativeLayout [NotSibling]\n"
+ + " android:layout_toRightOf=\"@id/button6\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/siblings.xml:63: Error: @+id/button5 is not a sibling in the same RelativeLayout [NotSibling]\n"
+ + " android:layout_alignTop=\"@+id/button5\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/siblings.xml:64: Error: @+id/button6 is not a sibling in the same RelativeLayout [NotSibling]\n"
+ + " android:layout_toRightOf=\"@+id/button6\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ lintFiles("res/layout/siblings.xml"));
+ }
+
+ public void testInvalidIds1() throws Exception {
+ // See https://code.google.com/p/android/issues/detail?id=56029
+ assertEquals(""
+ + "res/layout/invalid_ids.xml:23: Error: ID definitions must be of the form @+id/name; try using @+id/menu_Reload [InvalidId]\n"
+ + " android:id=\"@+menu/Reload\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/invalid_ids.xml:31: Error: ID definitions must be of the form @+id/name; try using @+id/_id_foo [InvalidId]\n"
+ + " android:id=\"@+/id_foo\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/invalid_ids.xml:37: Error: ID definitions must be of the form @+id/name; try using @+id/myid_button5 [InvalidId]\n"
+ + " android:id=\"@+myid/button5\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/invalid_ids.xml:43: Error: ID definitions must be of the form @+id/name; try using @+id/string_whatevs [InvalidId]\n"
+ + " android:id=\"@+string/whatevs\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "4 errors, 0 warnings\n",
+
+ lintFiles("res/layout/invalid_ids.xml"));
+ }
+
+ public void testInvalidIds2() throws Exception {
+ // https://code.google.com/p/android/issues/detail?id=65244
+ assertEquals(""
+ + "res/layout/invalid_ids2.xml:8: Error: ID definitions must be of the form @+id/name; try using @+id/btn_skip [InvalidId]\n"
+ + " android:id=\"@+id/btn/skip\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "res/layout/invalid_ids2.xml:16: Error: Invalid id: missing value [InvalidId]\n"
+ + " android:id=\"@+id/\"\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ + "2 errors, 0 warnings\n",
+
+ lintFiles("res/layout/invalid_ids2.xml"));
+ }
+
+ public void testIncremental() throws Exception {
+ assertEquals(
+ "res/layout/layout1.xml:14: Error: The id \"button5\" is not defined anywhere. Did you mean one of {button1, button2, button3, button4} ? [UnknownId]\n" +
+ " android:layout_alignBottom=\"@+id/button5\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:17: Error: The id \"my_id3\" is not defined anywhere. Did you mean one of {my_id1, my_id2} ? [UnknownId]\n" +
+ " android:layout_alignRight=\"@+id/my_id3\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:18: Error: The id \"my_id1\" is defined but not assigned to any views. Did you mean one of {my_id2, my_id3} ? [UnknownId]\n" +
+ " android:layout_alignTop=\"@+id/my_id1\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "res/layout/layout1.xml:15: Warning: The id \"my_id2\" is not referring to any views in this layout [UnknownIdInLayout]\n" +
+ " android:layout_alignLeft=\"@+id/my_id2\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "3 errors, 1 warnings\n",
+
+ lintProjectIncrementally(
+ "res/layout/layout1.xml",
+
+ "wrongid/layout1.xml=>res/layout/layout1.xml",
+ "wrongid/layout2.xml=>res/layout/layout2.xml",
+ "wrongid/ids.xml=>res/values/ids.xml"
+ ));
+ }
+
+ public void testSelfReference() throws Exception {
+ // Make sure we highlight direct references to self
+ // Regression test for https://code.google.com/p/android/issues/detail?id=136103
+ assertEquals(""
+ + "res/layout/layout3.xml:9: Error: Cannot be relative to self: id=tv_portfolio_title, layout_below=tv_portfolio_title [NotSibling]\n"
+ + " android:layout_below=\"@+id/tv_portfolio_title\"/>\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintFiles("wrongid/layout3.xml=>res/layout/layout3.xml"));
+ }
+
+ public void testPercent() throws Exception {
+ assertEquals(""
+ + "res/layout/test.xml:18: Error: The id \"textView1\" is not defined anywhere. Did you mean textview1 ? [UnknownId]\n"
+ + " android:layout_below=\"@id/textView1\"\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+
+ lintProject(xml("res/layout/test.xml", ""
+ + "<android.support.percent.PercentRelativeLayout "
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\""
+ + " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\">\n"
+ + " <View\n"
+ + " android:id=\"@+id/textview1\"\n"
+ + " android:layout_gravity=\"center\"\n"
+ + " app:layout_widthPercent=\"50%\"\n"
+ + " app:layout_heightPercent=\"50%\"/>\n"
+ + " <View\n"
+ + " android:id=\"@+id/textview2\"\n"
+ + " android:layout_below=\"@id/textview1\"\n" // OK
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " app:layout_marginStartPercent=\"25%\"\n"
+ + " app:layout_marginEndPercent=\"25%\"/>\n"
+ + " <View\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:layout_below=\"@id/textView1\"\n" // WRONG (case)
+ + " app:layout_widthPercent=\"60%\"/>\n"
+ + "\n"
+ + "</android.support.percent.PercentRelativeLayout>\n")));
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongImportDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongImportDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongImportDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongImportDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongLocationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongLocationDetectorTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongLocationDetectorTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/WrongLocationDetectorTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AbstractActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AbstractActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AbstractActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AbstractActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AndroidManifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AndroidManifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AndroidManifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/AndroidManifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/Dependencies.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/Dependencies.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/Dependencies.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/Dependencies.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/allowbackup_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/android/PreferenceActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/android/PreferenceActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/android/PreferenceActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/android/PreferenceActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest10.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyActivity.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyActivity.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyActivity.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyActivity.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyLinear.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyLinear.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyLinear.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11$MyLinear.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest11.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest13.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14$3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest14.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest5.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest6.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest7.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest8.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiCallTest9.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.class.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
new file mode 100644
index 0000000..9b70b21
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck.java.txt
@@ -0,0 +1,123 @@
+package test.pkg;
+
+import android.util.Property;
+import android.view.View;
+import static android.view.View.MEASURED_STATE_MASK;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import android.view.*;
+import android.annotation.*;
+import android.app.*;
+import android.widget.*;
+import static android.widget.ZoomControls.*;
+import android.Manifest.permission;
+import android.Manifest;
+
+/** Various tests for source-level checks */
+final class ApiSourceCheck extends LinearLayout {
+ public ApiSourceCheck(android.content.Context context) {
+ super(context);
+ }
+
+ /**
+ * Return only the state bits of {@link #getMeasuredWidthAndState()} and
+ * {@link #getMeasuredHeightAndState()}, combined into one integer. The
+ * width component is in the regular bits {@link #MEASURED_STATE_MASK} and
+ * the height component is at the shifted bits
+ * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
+ */
+ public static int m1(View child) {
+ // from static import of field
+ int x = MEASURED_STATE_MASK;
+
+ // fully qualified name field access
+ int y = android.view.View.MEASURED_STATE_MASK;
+
+ // from explicitly imported class
+ int z = View.MEASURED_STATE_MASK;
+ int find2 = View.FIND_VIEWS_WITH_TEXT; // requires API 14
+
+ // from wildcard import of package
+ int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+ int find1 = ZoomButton.FIND_VIEWS_WITH_CONTENT_DESCRIPTION; // requires
+ // API 14
+ // from static wildcard import
+ int overScroll = OVER_SCROLL_ALWAYS; // requires API 9
+
+ // Inherited field from ancestor class (View)
+ int auto = IMPORTANT_FOR_ACCESSIBILITY_AUTO; // requires API 16
+
+ // object field reference: ensure that we don't get two errors
+ // (one from source scan, the other from class scan)
+ Object rotationX = ZoomButton.ROTATION_X; // Requires API 14
+
+ // different type of expression than variable declaration
+ return (child.getMeasuredWidth() & View.MEASURED_STATE_MASK)
+ | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));
+ }
+
+ @SuppressLint("NewApi")
+ private void testSuppress1() {
+ // Checks suppress on surrounding method
+ int w = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+ }
+
+ private void testSuppress2() {
+ // Checks suppress on surrounding declaration statement
+ @SuppressLint("NewApi")
+ int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+ }
+
+ @TargetApi(17)
+ private void testTargetApi1() {
+ // Checks @TargetApi on surrounding method
+ int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+ }
+
+ @TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private void testTargetApi2() {
+ // Checks @TargetApi with codename
+ int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+ }
+
+ @TargetApi(JELLY_BEAN_MR1)
+ private void testTargetApi3() {
+ // Checks @TargetApi with codename
+ int w, z = ActivityManager.MOVE_TASK_NO_USER_ACTION;
+ }
+
+ private void checkOtherFields() {
+ // Look at fields that aren't capitalized
+ int custom = android.R.id.custom; // API 8
+ }
+
+ private void innerclass() {
+ String setPointerSpeed = permission.BLUETOOTH_PRIVILEGED;
+ String setPointerSpeed2 = Manifest.permission.BLUETOOTH_PRIVILEGED;
+ }
+
+ private void test() {
+ // Make sure that local variable references which look like fields,
+ // even imported ones, aren't taken as invalid references
+ int OVER_SCROLL_ALWAYS = 1, IMPORTANT_FOR_ACCESSIBILITY_AUTO = 2;
+ int x = OVER_SCROLL_ALWAYS;
+ int y = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ findViewById(IMPORTANT_FOR_ACCESSIBILITY_AUTO); // yes, nonsensical
+ }
+
+ private void testBenignUsages(int x) {
+ // Certain types of usages (such as switch/case constants) are okay
+ switch (x) {
+ case View.MEASURED_STATE_MASK: { // OK
+ break;
+ }
+ }
+ if (x == View.MEASURED_STATE_MASK) { // OK
+ }
+ if (false || x == View.MEASURED_STATE_MASK) { // OK
+ }
+ if (x >= View.MEASURED_STATE_MASK) { // OK
+ }
+ int y = View.MEASURED_STATE_MASK; // Not OK
+ testBenignUsages(View.MEASURED_STATE_MASK); // Not OK
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiSourceCheck2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest$LocalClass.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest$LocalClass.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest$LocalClass.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest$LocalClass.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1$2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ApiTargetTest2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/CloseTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ConditionalApiTest2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Fragment.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/FragmentActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/GravityTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/GravityTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/GravityTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/GravityTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate$IntermediateCustomV.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate$IntermediateCustomV.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate$IntermediateCustomV.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate$IntermediateCustomV.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/Intermediate.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyActivityImpl.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R$color.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R$color.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R$color.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R$color.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment$R.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/MyFragment.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest$MyEngine2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuperCallTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.class.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt
new file mode 100644
index 0000000..090688c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest1.java.txt
@@ -0,0 +1,137 @@
+package test.pkg;
+
+import org.w3c.dom.DOMError;
+import org.w3c.dom.DOMErrorHandler;
+import org.w3c.dom.DOMLocator;
+
+import android.view.ViewGroup.LayoutParams;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ApplicationErrorReport;
+import android.app.ApplicationErrorReport.BatteryInfo;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.widget.Chronometer;
+import android.widget.GridLayout;
+import dalvik.bytecode.OpcodeInfo;
+
+public class SuppressTest1 extends Activity {
+ @SuppressLint("all")
+ public void method1(Chronometer chronometer, DOMLocator locator) {
+ // Virtual call
+ getActionBar(); // API 11
+
+ // Class references (no call or field access)
+ DOMError error = null; // API 8
+ Class<?> clz = DOMErrorHandler.class; // API 8
+
+ // Method call
+ chronometer.getOnChronometerTickListener(); // API 3
+
+ // Inherited method call (from TextView
+ chronometer.setTextIsSelectable(true); // API 11
+
+ // Field access
+ int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
+ int fillParent = LayoutParams.FILL_PARENT; // API 1
+ // This is a final int, which means it gets inlined
+ int matchParent = LayoutParams.MATCH_PARENT; // API 8
+ // Field access: non final
+ BatteryInfo batteryInfo = getReport().batteryInfo;
+
+ // Enum access
+ Mode mode = PorterDuff.Mode.OVERLAY; // API 11
+ }
+
+ @SuppressLint("NewApi")
+ public void method2(Chronometer chronometer, DOMLocator locator) {
+ // Virtual call
+ getActionBar(); // API 11
+
+ // Class references (no call or field access)
+ DOMError error = null; // API 8
+ Class<?> clz = DOMErrorHandler.class; // API 8
+
+ // Method call
+ chronometer.getOnChronometerTickListener(); // API 3
+
+ // Inherited method call (from TextView
+ chronometer.setTextIsSelectable(true); // API 11
+
+ // Field access
+ int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
+ int fillParent = LayoutParams.FILL_PARENT; // API 1
+ // This is a final int, which means it gets inlined
+ int matchParent = LayoutParams.MATCH_PARENT; // API 8
+ // Field access: non final
+ BatteryInfo batteryInfo = getReport().batteryInfo;
+
+ // Enum access
+ Mode mode = PorterDuff.Mode.OVERLAY; // API 11
+ }
+
+ @SuppressLint("SomethingElse")
+ public void method3(Chronometer chronometer, DOMLocator locator) {
+ // Virtual call
+ getActionBar(); // API 11
+
+ // Class references (no call or field access)
+ DOMError error = null; // API 8
+ Class<?> clz = DOMErrorHandler.class; // API 8
+
+ // Method call
+ chronometer.getOnChronometerTickListener(); // API 3
+
+ // Inherited method call (from TextView
+ chronometer.setTextIsSelectable(true); // API 11
+
+ // Field access
+ int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
+ int fillParent = LayoutParams.FILL_PARENT; // API 1
+ // This is a final int, which means it gets inlined
+ int matchParent = LayoutParams.MATCH_PARENT; // API 8
+ // Field access: non final
+ BatteryInfo batteryInfo = getReport().batteryInfo;
+
+ // Enum access
+ Mode mode = PorterDuff.Mode.OVERLAY; // API 11
+ }
+
+ @SuppressLint({"SomethingElse", "NewApi"})
+ public void method4(Chronometer chronometer, DOMLocator locator) {
+ // Virtual call
+ getActionBar(); // API 11
+
+ // Class references (no call or field access)
+ DOMError error = null; // API 8
+ Class<?> clz = DOMErrorHandler.class; // API 8
+
+ // Method call
+ chronometer.getOnChronometerTickListener(); // API 3
+
+ // Inherited method call (from TextView
+ chronometer.setTextIsSelectable(true); // API 11
+
+ // Field access
+ int field = OpcodeInfo.MAXIMUM_VALUE; // API 11
+ int fillParent = LayoutParams.FILL_PARENT; // API 1
+ // This is a final int, which means it gets inlined
+ int matchParent = LayoutParams.MATCH_PARENT; // API 8
+ // Field access: non final
+ BatteryInfo batteryInfo = getReport().batteryInfo;
+
+ // Enum access
+ Mode mode = PorterDuff.Mode.OVERLAY; // API 11
+ }
+
+ // Return type
+ @SuppressLint("NewApi")
+ GridLayout getGridLayout() { // API 14
+ return null;
+ }
+
+ @SuppressLint("all")
+ private ApplicationErrorReport getReport() {
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.class.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt
new file mode 100644
index 0000000..1400224
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest2.java.txt
@@ -0,0 +1,25 @@
+package test.pkg;
+
+import org.w3c.dom.DOMLocator;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ApplicationErrorReport;
+import android.widget.Chronometer;
+import android.widget.GridLayout;
+
+ at SuppressLint("all")
+public class SuppressTest2 extends Activity {
+ public void method(Chronometer chronometer, DOMLocator locator) {
+ getActionBar(); // API 11
+ }
+
+ // Return type
+ GridLayout getGridLayout() { // API 14
+ return null;
+ }
+
+ private ApplicationErrorReport getReport() {
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.class.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt
new file mode 100644
index 0000000..0f17f4b
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest3.java.txt
@@ -0,0 +1,25 @@
+package test.pkg;
+
+import org.w3c.dom.DOMLocator;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ApplicationErrorReport;
+import android.widget.Chronometer;
+import android.widget.GridLayout;
+
+ at SuppressLint("NewApi")
+public class SuppressTest3 extends Activity {
+ public void method(Chronometer chronometer, DOMLocator locator) {
+ getActionBar(); // API 11
+ }
+
+ // Return type
+ GridLayout getGridLayout() { // API 14
+ return null;
+ }
+
+ private ApplicationErrorReport getReport() {
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.class.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt
new file mode 100644
index 0000000..94152f6
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/SuppressTest4.java.txt
@@ -0,0 +1,21 @@
+package test.pkg;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ApplicationErrorReport;
+import android.app.ApplicationErrorReport.BatteryInfo;
+
+public class SuppressTest4 extends Activity {
+ public void method() {
+
+ // These annotations within the method do not end up
+ // in the bytecode, so they have no effect. We need a
+ // lint annotation check to find these.
+
+ @SuppressLint("NewApi")
+ ApplicationErrorReport report = null;
+
+ @SuppressLint("NewApi")
+ BatteryInfo batteryInfo = report.batteryInfo;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TargetApiTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestEnum.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestLint.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestLint.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestLint.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/TestLint.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional1b.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional2b.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/VersionConditional3b.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_selector.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_selector.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_selector.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_selector.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_vector.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_vector.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_vector.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/animated_vector.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/attribute3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/classpath b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/classpath
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/classpath
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/classpath
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/colors.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/colors.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/colors.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/colors.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/divider.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/divider.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/divider.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/divider.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/holomanifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/holomanifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/holomanifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/holomanifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layout_targetapi.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layoutattr.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layoutattr.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layoutattr.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/layoutattr.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minics.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minics.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minics.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minics.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk10.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk10.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk10.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk10.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk11.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk11.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk11.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk11.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk14.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk14.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk14.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk14.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk17.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk17.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk17.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk17.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk19.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk19.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk19.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk19.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk21.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk21.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk21.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk21.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/minsdk4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/padding_start.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/padding_start.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/padding_start.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/padding_start.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ripple.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ripple.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ripple.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/ripple.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/themes.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/themes.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/themes.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/themes.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/vector.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/vector.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/vector.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/vector.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/view.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/view.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/view.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/apicheck/view.xml
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test.xml
new file mode 100644
index 0000000..69d5872
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test.xml
@@ -0,0 +1,28 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.helloworld">
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".AppIndexingApiTest"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:label="@string/title_activity_fullscreen"
+ android:theme="@style/FullscreenTheme" >
+ <intent-filter android:label="@string/title_activity_fullscreen">
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="http"
+ android:host="example.com"
+ android:pathPrefix="/gizmos" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ </intent-filter>
+ </activity>
+
+ <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
+ </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test_no_manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test_no_manifest.xml
new file mode 100644
index 0000000..4e8cb47
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/app_indexing_api_test_no_manifest.xml
@@ -0,0 +1,25 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.helloworld">
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".AppIndexingApiTest"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:label="@string/title_activity_fullscreen"
+ android:theme="@style/FullscreenTheme" >
+ <intent-filter android:label="@string/title_activity_fullscreen">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ </intent-filter>
+ </activity>
+
+ <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
+ </application>
+
+</manifest>
+
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionBarActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionBarActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionBarActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionBarActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionMode.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionMode.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionMode.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/ActionMode.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt
new file mode 100644
index 0000000..1a6a269
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatPrefTest.java.txt
@@ -0,0 +1,15 @@
+package test.pkg;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+public class AppCompatPrefTest extends PreferenceActivity {
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getActionBar(); // Should not generate a warning
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/AppCompatTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt
new file mode 100644
index 0000000..2274c5a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/DialogFragment.java.txt
@@ -0,0 +1,9 @@
+package android.support.v4.app;
+
+/** Stub to make unit tests able to resolve types without having a real dependency
+ * on the appcompat library */
+public abstract class DialogFragment extends Fragment {
+ public void show(FragmentManager manager, String tag) { }
+ public int show(FragmentTransaction transaction, String tag) { return 0; }
+ public void dismiss() { }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/Fragment.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/Fragment.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/Fragment.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/Fragment.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentManager.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentManager.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentManager.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentManager.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt
new file mode 100644
index 0000000..4545dcb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/FragmentTransaction.java.txt
@@ -0,0 +1,18 @@
+package android.support.v4.app;
+
+/** Stub to make unit tests able to resolve types without having a real dependency
+ * on the appcompat library */
+public abstract class FragmentTransaction {
+ public abstract int commit();
+ public abstract int commitAllowingStateLoss();
+ public abstract FragmentTransaction show(Fragment fragment);
+ public abstract FragmentTransaction hide(Fragment fragment);
+ public abstract FragmentTransaction attach(Fragment fragment);
+ public abstract FragmentTransaction detach(Fragment fragment);
+ public abstract FragmentTransaction add(int containerViewId, Fragment fragment);
+ public abstract FragmentTransaction add(Fragment fragment, String tag);
+ public abstract FragmentTransaction addToBackStack(String name);
+ public abstract FragmentTransaction disallowAddToBackStack();
+ public abstract FragmentTransaction setBreadCrumbShortTitle(int res);
+ public abstract FragmentTransaction setCustomAnimations(int enter, int exit);
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/IntermediateActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/IntermediateActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/IntermediateActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appcompat/IntermediateActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appindexing_manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appindexing_manifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appindexing_manifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/appindexing_manifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/broken-manifest2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/.classpath b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/.classpath
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/.classpath
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/.classpath
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnNonWebView.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebView.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$CallAddJavascriptInterfaceOnWebViewChild.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$NonWebView.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$NonWebView.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$NonWebView.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$NonWebView.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest$WebViewChild.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AddJavascriptInterfaceTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestMinSdk17.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestMinSdk17.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestMinSdk17.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestMinSdk17.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestReg.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestReg.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestReg.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestReg.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestRegs.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestRegs.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestRegs.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestRegs.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestTarget17.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestTarget17.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestTarget17.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestTarget17.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestWrongRegs.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestWrongRegs.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestWrongRegs.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestWrongRegs.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/AnnotatedObject.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1$Class4.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1$Class4.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1$Class4.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1$Class4.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2$Class3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2$Class3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2$Class3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2$Class3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/Class2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousInvalidOnTouchListener.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$AnonymousValidOnTouchListener.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClick.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$HasPerformClickOnTouchListenerSetter.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$InvalidOnTouchListener.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClick.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NoPerformClickOnTouchListenerSetter.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAView.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAView.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAView.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAView.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$NotAViewOnTouchListenerSetter.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$PerformClickDoesNotCallSuper.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidOnTouchListener.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidView.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidView.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidView.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ValidView.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewDoesNotCallPerformClick.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewOverridesOnTouchEventButNotPerformClick.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewSubclass.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentOnTouchEvent.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest$ViewWithDifferentPerformClick.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ClickableViewAccessibilityTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommentsActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.class.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt
new file mode 100644
index 0000000..59acc3a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest.java.txt
@@ -0,0 +1,137 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+
+ at SuppressWarnings("unused")
+public class CommitTest extends Activity {
+ public void ok1() {
+ getFragmentManager().beginTransaction().commit();
+ }
+
+ public void ok2() {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.commit();
+ }
+
+ public void ok3() {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.commitAllowingStateLoss();
+ }
+
+ public void error1() {
+ getFragmentManager().beginTransaction(); // Missing commit
+ }
+
+ public void error() {
+ FragmentTransaction transaction1 = getFragmentManager().beginTransaction();
+ FragmentTransaction transaction2 = getFragmentManager().beginTransaction(); // Missing commit
+ transaction1.commit();
+ }
+
+ public void error3_public() {
+ error3();
+ }
+
+ private void error3() {
+ getFragmentManager().beginTransaction(); // Missing commit
+ }
+
+ public void ok4(FragmentManager manager, String tag) {
+ FragmentTransaction ft = manager.beginTransaction();
+ ft.add(null, tag);
+ ft.commit();
+ }
+
+ // Support library
+
+ private android.support.v4.app.FragmentManager getSupportFragmentManager() {
+ return null;
+ }
+
+ public void ok5() {
+ getSupportFragmentManager().beginTransaction().commit();
+ }
+
+ public void ok6(android.support.v4.app.FragmentManager manager, String tag) {
+ android.support.v4.app.FragmentTransaction ft = manager.beginTransaction();
+ ft.add(null, tag);
+ ft.commit();
+ }
+
+ public void error4() {
+ getSupportFragmentManager().beginTransaction();
+ }
+
+ android.support.v4.app.Fragment mFragment1 = null;
+ Fragment mFragment2 = null;
+
+ public void ok7() {
+ getSupportFragmentManager().beginTransaction().add(android.R.id.content, mFragment1).commit();
+ }
+
+ public void ok8() {
+ getFragmentManager().beginTransaction().add(android.R.id.content, mFragment2).commit();
+ }
+
+ public void ok10() {
+ // Test chaining
+ FragmentManager fragmentManager = getFragmentManager();
+ fragmentManager.beginTransaction().addToBackStack("test").attach(mFragment2).detach(mFragment2)
+ .disallowAddToBackStack().hide(mFragment2).setBreadCrumbShortTitle("test")
+ .show(mFragment2).setCustomAnimations(0, 0).commit();
+ }
+
+ public void ok9() {
+ FragmentManager fragmentManager = getFragmentManager();
+ fragmentManager.beginTransaction().commit();
+ }
+
+ public void ok11() {
+ FragmentTransaction transaction;
+ // Comment in between variable declaration and assignment
+ transaction = getFragmentManager().beginTransaction();
+ transaction.commit();
+ }
+
+ public void ok12() {
+ FragmentTransaction transaction;
+ transaction = (getFragmentManager().beginTransaction());
+ transaction.commit();
+ }
+
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ public void ok13() {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ FragmentTransaction temp;
+ temp = transaction;
+ temp.commitAllowingStateLoss();
+ }
+
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ public void ok14() {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ FragmentTransaction temp = transaction;
+ temp.commitAllowingStateLoss();
+ }
+
+ public void error5(FragmentTransaction unrelated) {
+ FragmentTransaction transaction;
+ // Comment in between variable declaration and assignment
+ transaction = getFragmentManager().beginTransaction();
+ transaction = unrelated;
+ transaction.commit();
+ }
+
+ public void error6(FragmentTransaction unrelated) {
+ FragmentTransaction transaction;
+ FragmentTransaction transaction2;
+ // Comment in between variable declaration and assignment
+ transaction = getFragmentManager().beginTransaction();
+ transaction2 = transaction;
+ transaction2 = unrelated;
+ transaction2.commit();
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2$MyDialogFragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2$MyDialogFragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2$MyDialogFragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2$MyDialogFragment.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyCompatDialogFragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyCompatDialogFragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyCompatDialogFragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyCompatDialogFragment.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyDialogFragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyDialogFragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyDialogFragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3$MyDialogFragment.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CommitTest3.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView1.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomView3.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomViewTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomViewTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomViewTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/CustomViewTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/DialogFragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/DialogFragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/DialogFragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/DialogFragment.class.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleHostnameVerifier$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleHostnameVerifier$1.class.data
new file mode 100644
index 0000000..30f03ee
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleHostnameVerifier$1.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService$1.class.data
new file mode 100644
index 0000000..78b84b8
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService$1.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.class.data
new file mode 100644
index 0000000..7826fa0
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.java.txt
new file mode 100644
index 0000000..e6f8581
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ExampleTLSIntentService.java.txt
@@ -0,0 +1,62 @@
+package test.pkg;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class ExampleTLSIntentService extends IntentService {
+ TrustManager[] trustManagerExample;
+
+ {
+ trustManagerExample = new TrustManager[]{new X509TrustManager() {
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ try {
+ FileInputStream fis = new FileInputStream("testcert.pem");
+ BufferedInputStream bis = new BufferedInputStream(fis);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
+ return new X509Certificate[]{cert};
+ } catch (Exception e) {
+ throw new RuntimeException("Could not load cert");
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
+ throw new CertificateException("Not trusted");
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
+ throw new CertificateException("Not trusted");
+ }
+ }};
+ }
+
+ public ExampleTLSIntentService() {
+ super("ExampleTLSIntentService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ try {
+ SSLContext sc = SSLContext.getInstance("TLSv1.2");
+ sc.init(null, trustManagerExample, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ } catch (GeneralSecurityException e) {
+ System.out.println(e.getStackTrace());
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.class.data
new file mode 100644
index 0000000..f2bdf49
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.java.txt
new file mode 100644
index 0000000..2742ce7
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FloatingActionButton.java.txt
@@ -0,0 +1,20 @@
+package android.support.design.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+// JUST A UNIT TESTING STUB!
+public abstract class FloatingActionButton extends ImageButton {
+ @SuppressLint("NewApi")
+ public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setBackgroundTintList(ColorStateList tint) {
+ super.setBackgroundTintList(tint);
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment4.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment4.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment4.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment4.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment5.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment5.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment5.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment5.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment6.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment6.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment6.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$Fragment6.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$NotAFragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$NotAFragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$NotAFragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$NotAFragment.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$ValidFragment1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$ValidFragment1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$ValidFragment1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest$ValidFragment1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/FragmentTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InheritsFromAnnotated.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureHostnameVerifier$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureHostnameVerifier$1.class.data
new file mode 100644
index 0000000..2fe5b4e
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureHostnameVerifier$1.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService$1.class.data
new file mode 100644
index 0000000..f5562a9
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService$1.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.class.data
new file mode 100644
index 0000000..7dd0832
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.java.txt
new file mode 100644
index 0000000..b128037
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/InsecureTLSIntentService.java.txt
@@ -0,0 +1,44 @@
+package test.pkg;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class InsecureTLSIntentService extends IntentService {
+ TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+ @Override
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ @Override
+ public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws CertificateException {
+ }
+ }};
+
+ public InsecureTLSIntentService() {
+ super("InsecureTLSIntentService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ try {
+ SSLContext sc = SSLContext.getInstance("TLSv1.2");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ } catch (GeneralSecurityException e) {
+ System.out.println(e.getStackTrace());
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/JavaScriptTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LayoutTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/LocaleTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/META-INF/MANIFEST.MF b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/META-INF/MANIFEST.MF
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/META-INF/MANIFEST.MF
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/META-INF/MANIFEST.MF
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable1.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable3.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/NonAnnotatedObject.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/OnClickActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PowerManagerFlagTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/RecycleTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.class.data
new file mode 100644
index 0000000..8d1fa3b
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.java.txt
new file mode 100644
index 0000000..eca796d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/SupportLibraryApiTest.java.txt
@@ -0,0 +1,24 @@
+package test.pkg;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.design.widget.FloatingActionButton;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+public class SupportLibraryApiTest extends FloatingActionButton {
+ public SupportLibraryApiTest(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void test1(ColorStateList colors) {
+ setBackgroundTintList(colors); // OK: FAB overrides ImageButton with lower minSDK
+ this.setBackgroundTintList(colors); // OK: FAB overrides ImageButton with lower minSDK
+ }
+
+ public void test2(FloatingActionButton fab, ImageButton button,
+ ColorStateList colors) {
+ fab.setBackgroundTintList(colors); // OK: FAB overrides ImageButton with lower minSDK
+ button.setBackgroundTintList(colors); // ERROR
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestFieldGetter.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestProvider2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver$1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver$1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver$1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver$1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestReceiver.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/TestService.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/ViewAndUpdatePreferencesActivity$UserPreferenceFragment.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity1.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.class.data
new file mode 100644
index 0000000..6a26f14
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.class.data differ
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.java.txt
new file mode 100644
index 0000000..105d722
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity10.java.txt
@@ -0,0 +1,23 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PowerManager;
+
+public class WakelockActivity10 extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ PowerManager manager = (PowerManager) getSystemService(POWER_SERVICE);
+ PowerManager.WakeLock wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Test");
+
+ try {
+ wakeLock.acquire();
+ throw new Exception();
+ } catch (Exception e) {
+
+ } finally {
+ wakeLock.release();
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity3.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity4.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity5.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity6.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity7.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity8.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/WakelockActivity9.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-jar b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-jar
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-jar
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-jar
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-lib b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-lib
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-lib
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/classpath-lib
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/user_prefs_fragment.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/user_prefs_fragment.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/user_prefs_fragment.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/bytecode/user_prefs_fragment.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/debuggable.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/debuggable.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/debuggable.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/debuggable.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/deviceadmin.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/deviceadmin.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/deviceadmin.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/deviceadmin.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest-ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest-ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest-ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest-ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate-manifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_permissions3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature_ok.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature_ok.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature_ok.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/duplicate_uses_feature_ok.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/MacRoman.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/MacRoman.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/MacRoman.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/MacRoman.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom-implicit.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom-implicit.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom-implicit.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom-implicit.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-bom.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-nobom.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-nobom.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-nobom.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF-16-nobom.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-bom.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-bom.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-bom.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-bom.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-nobom.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-nobom.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-nobom.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32-nobom.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32LE-nobom.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32LE-nobom.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32LE-nobom.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/UTF_32LE-nobom.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/Windows-1252.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/Windows-1252.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/Windows-1252.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/encoding/Windows-1252.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_explicit.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_explicit.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_explicit.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_explicit.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_implicit.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_implicit.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_implicit.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_implicit.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_no_export.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_no_export.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_no_export.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_no_export.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_explicit.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_explicit.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_explicit.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_explicit.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_implicit.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_implicit.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_implicit.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_implicit.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_target_sdk_19.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_target_sdk_19.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_target_sdk_19.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_subclass_target_sdk_19.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_suppressed.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_suppressed.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_suppressed.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/export_preference_activity_suppressed.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity0.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity0.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity0.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity0.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportactivity4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportprovider2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver0.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver0.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver0.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver0.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver6.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver6.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver6.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver6.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver7.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver7.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver7.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver7.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver8.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver8.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver8.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportreceiver8.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/exportservice5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gen/my/pkg/R2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/AccidentalOctal.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/AccidentalOctal.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/AccidentalOctal.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/AccidentalOctal.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/BadDependenciesInBuildscriptBlock.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/BadDependenciesInBuildscriptBlock.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/BadDependenciesInBuildscriptBlock.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/BadDependenciesInBuildscriptBlock.gradle
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle
new file mode 100644
index 0000000..dba0e20
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Compatibility.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 18
+ buildToolsVersion "19.0.0"
+
+ defaultConfig {
+ minSdkVersion 7
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:18.0.0'
+ compile 'com.android.support.test:espresso:0.2'
+ compile 'com.android.support:multidex:1.0.1'
+ compile 'com.android.support:multidex-instrumentation:1.0.1'
+
+ // Suppressed:
+ //noinspection GradleCompatible
+ compile 'com.android.support:support-v4:18.0.0'
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14_21.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14_21.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14_21.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Dependencies14_21.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesGson.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesGson.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesGson.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesGson.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesInAndroidBlock.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesInAndroidBlock.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesInAndroidBlock.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesInAndroidBlock.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesProps.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesProps.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesProps.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesProps.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesVariable.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesVariable.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesVariable.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DependenciesVariable.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DeprecatedPluginId.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DeprecatedPluginId.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DeprecatedPluginId.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DeprecatedPluginId.gradle
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DynamicResources.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DynamicResources.gradle
new file mode 100644
index 0000000..8a50ea4
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/DynamicResources.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.1.0"
+
+ defaultConfig {
+ resValue "string", "cat", "Some Data"
+ minSdkVersion 15
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ debug {
+ resValue "string", "foo", "Some Data"
+ }
+ release {
+ resValue "string", "xyz", "Some Data"
+ resValue "string", "dog", "Some Data"
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IdSuffix.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IdSuffix.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IdSuffix.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IdSuffix.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IgnoresGStringsInDependencies.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IgnoresGStringsInDependencies.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IgnoresGStringsInDependencies.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IgnoresGStringsInDependencies.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IncompatiblePlugin.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IncompatiblePlugin.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IncompatiblePlugin.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/IncompatiblePlugin.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/MinSdkAssignment.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/MinSdkAssignment.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/MinSdkAssignment.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/MinSdkAssignment.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoAndroidStatement.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoAndroidStatement.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoAndroidStatement.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoAndroidStatement.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoApplyPlugin.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoApplyPlugin.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoApplyPlugin.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/NoApplyPlugin.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Package.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Package.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Package.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Package.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Paths.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Paths.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Paths.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Paths.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices.gradle
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices2.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices2.gradle
new file mode 100644
index 0000000..34f329d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PlayServices2.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android'
+
+dependencies {
+ compile 'com.google.android.gms:play-services-wearable:7.5.0'
+ compile 'com.google.android.gms:play-services-location:7.3.0'
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle
new file mode 100644
index 0000000..3ddf437
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Plus.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "19.0.1"
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:+'
+ compile group: 'com.android.support', name: 'support-v4', version: '21.0.+'
+ compile 'com.android.support:appcompat-v7:+ at aar'
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PreviewDependencies.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PreviewDependencies.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PreviewDependencies.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/PreviewDependencies.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions2.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions2.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions2.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RemoteVersions2.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RepositoriesInDependenciesBlock.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RepositoriesInDependenciesBlock.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RepositoriesInDependenciesBlock.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/RepositoriesInDependenciesBlock.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Setter.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Setter.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Setter.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/Setter.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/StringInt.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/StringInt.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/StringInt.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/StringInt.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/SuppressLine2.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/SuppressLine2.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/SuppressLine2.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/SuppressLine2.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelDependenciesBlock.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelDependenciesBlock.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelDependenciesBlock.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelDependenciesBlock.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelRepositoriesBlock.gradle b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelRepositoriesBlock.gradle
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelRepositoriesBlock.gradle
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle/TopLevelRepositoriesBlock.gradle
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_http.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_http.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_http.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_http.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override_placeholder.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override_placeholder.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override_placeholder.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/gradle_override_placeholder.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/grantpermission.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/grantpermission.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/grantpermission.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/grantpermission.xml
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier.xml
new file mode 100644
index 0000000..9e6acd6
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <service
+ android:name=".InsecureHostnameVerifier" >
+ </service>
+ </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier1.xml
new file mode 100644
index 0000000..55eebff
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/hostnameverifier1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <service
+ android:name=".ExampleHostnameVerifier" >
+ </service>
+ </application>
+
+</manifest>
+
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/https_namespace.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/https_namespace.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/https_namespace.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/https_namespace.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/ignoremissing.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/ignoremissing.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/ignoremissing.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/ignoremissing.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/illegal_version.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/illegal_version.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/illegal_version.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/illegal_version.xml
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/hello b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/hello
new file mode 100755
index 0000000..e84aac3
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/lib/armeabi/hello differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local2.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local2.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local2.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/local2.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/.classpath b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/.classpath
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/.classpath
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/.classpath
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/AndroidManifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/AndroidManifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/AndroidManifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/AndroidManifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/project.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/project.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/project.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/project.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-de/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-de/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-de/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-de/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values-en-rGB/strings3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/strings5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/styles.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/styles.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/styles.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/locale33845/res/values/styles.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk14.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk14.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk14.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk14.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk9.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk9.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk9.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/minsdk5targetsdk9.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mipmap.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mipmap.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mipmap.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mipmap.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missing_application_icon.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missing_application_icon.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missing_application_icon.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missing_application_icon.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingmin.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingmin.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingmin.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingmin.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingprefix.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingprefix.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingprefix.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingprefix.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingtarget.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingtarget.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingtarget.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingtarget.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingusessdk.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingusessdk.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingusessdk.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/missingusessdk.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mock_location.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mock_location.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mock_location.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/mock_location.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiplesdk.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiplesdk.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiplesdk.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiplesdk.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library-manifest2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library2.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library2.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library2.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/library2.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-manifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-manifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-manifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-merge.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-merge.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-merge.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main-merge.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/main.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/multiproject/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/no_version.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/no_version.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/no_version.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/no_version.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/oldtarget.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/oldtarget.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/oldtarget.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/oldtarget.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.classpath b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.classpath
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.classpath
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.classpath
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.project b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.project
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.project
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/.project
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/AndroidManifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/AndroidManifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/AndroidManifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/AndroidManifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/BuildConfig.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/BuildConfig.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/BuildConfig.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/BuildConfig.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/R.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/R.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/R.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/gen/test/pkg/R.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/project.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/project.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/project.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/project.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/ic_launcher.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/ic_launcher.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-hdpi/ic_launcher.png
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-ldpi/ic_launcher.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-ldpi/ic_launcher.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-ldpi/ic_launcher.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-mdpi/ic_launcher.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-mdpi/ic_launcher.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable-mdpi/ic_launcher.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/drawable/custombg2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fifth.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fifth.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fifth.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fifth.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/fourth_context2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/main_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/nullbackground.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/nullbackground.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/nullbackground.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/nullbackground.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/second.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/second.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/second.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/second.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/sixth.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/sixth.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/sixth.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/sixth.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/third.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/third.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/third.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/third.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/tools_background.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/tools_background.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/tools_background.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/layout/tools_background.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/styles.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/styles.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/styles.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/res/values/styles.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/FourthActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/FourthActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/FourthActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/FourthActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/OverdrawActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/OverdrawActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/OverdrawActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/OverdrawActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/SecondActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/SecondActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/SecondActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/SecondActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/ThirdActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/ThirdActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/ThirdActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/overdraw/src/test/pkg/ThirdActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.cfg b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.cfg
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.cfg
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.cfg
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.pro b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.pro
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.pro
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.pro
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/proguard.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties1 b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties1
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties1
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties1
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties19 b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties19
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties19
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties19
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties2 b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties2
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties2
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties2
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties3 b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties3
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties3
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties3
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties4 b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties4
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties4
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/project.properties4
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protectedpermissions2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_fail.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_fail.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_fail.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_fail.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_ok.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_ok.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_ok.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/protection_level_ok.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestInner.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestInner.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestInner.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestInner.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/AndroidManifestWrong2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Bar.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Bar.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Bar.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Bar.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Bar.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Baz.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Baz.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Baz.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo$Baz.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/registration/Foo.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/anim/slide_in_out.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/anim/slide_in_out.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/anim/slide_in_out.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/anim/slide_in_out.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/color/color1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/color/color1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/color/color1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/color/color1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg.9.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg.9.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg.9.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg.9.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg_focus.9.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg_focus.9.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg_focus.9.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/appwidget_bg_focus.9.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/filled.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/filled.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/filled.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/filled.png
diff --git a/base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-hdpi/ic_launcher.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/androidTestLibDep/src/main/res/drawable-hdpi/ic_launcher.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/ic_launcher.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/other.9.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/other.9.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/other.9.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/other.9.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/unrelated.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/unrelated.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/unrelated.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-hdpi/unrelated.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/frame.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/frame.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/frame.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/frame.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/ic_menu_add_clip_normal.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.gif b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.gif
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.gif
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.gif
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.jpg b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.jpg
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.jpg
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/sample_icon.jpg
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-mdpi/stat_notify_alarm.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-nodpi/frame.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-nodpi/frame.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-nodpi/frame.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-nodpi/frame.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xhdpi/ic_stat_notify.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xhdpi/ic_stat_notify.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xhdpi/ic_stat_notify.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xhdpi/ic_stat_notify.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xlarge-nodpi-v11/frame.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xlarge-nodpi-v11/frame.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xlarge-nodpi-v11/frame.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable-xlarge-nodpi-v11/frame.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/drawable1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/drawable1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/drawable1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/drawable1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_launcher.png b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_launcher.png
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_launcher.png
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_launcher.png
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_menu_help.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_menu_help.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_menu_help.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/ic_menu_help.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/drawable/states3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/accessibility2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/activity_item_two_pane.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/activity_item_two_pane.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/activity_item_two_pane.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/activity_item_two_pane.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/baseline_weights3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/breadcrumbs_in_fragment.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/breadcrumbs_in_fragment.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/breadcrumbs_in_fragment.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/breadcrumbs_in_fragment.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/broken.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/broken.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/broken.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/broken.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar_suppressed.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar_suppressed.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar_suppressed.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/buttonbar_suppressed.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/case.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/case.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/case.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/case.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/casts4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/compound3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/crcrlf_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customattrlayout.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customattrlayout.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customattrlayout.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customattrlayout.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/customview3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/default_item_badges.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/default_item_badges.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/default_item_badges.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/default_item_badges.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/deprecation.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/deprecation.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/deprecation.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/deprecation.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/detailed_item.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/detailed_item.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/detailed_item.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/detailed_item.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/duplicate.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/duplicate.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/duplicate.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/duplicate.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_textview.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_textview.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_textview.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_textview.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_type.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_type.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_type.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/edit_type.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/encoding2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/gridlayout4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/has_children2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/ignores2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/include_params.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/include_params.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/include_params.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/include_params.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/inefficient_weight3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/invalid_ids2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/labelfor_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout1_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4cycle.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4cycle.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4cycle.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layout4cycle.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layoutcycle1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layoutcycle1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layoutcycle1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/layoutcycle1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/listseparator.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/listseparator.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/listseparator.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/listseparator.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/message_edit_detail.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/message_edit_detail.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/message_edit_detail.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/message_edit_detail.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/negative_margins.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/negative_margins.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/negative_margins.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/negative_margins.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/note_edit2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/onclick.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/onclick.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/onclick.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/onclick.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/orientation2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/relative_overlap.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/relative_overlap.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/relative_overlap.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/relative_overlap.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/scrolling.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/scrolling.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/scrolling.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/scrolling.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/siblings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/siblings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/siblings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/siblings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simple_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simpleinclude.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simpleinclude.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simpleinclude.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/simpleinclude.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/size2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/sizeincluded.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/tag.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/tag.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/tag.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/tag.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textsize2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textureview.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textureview.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textureview.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/textureview.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_deep.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_deep.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_deep.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_deep.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_many.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_many.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_many.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/too_many.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/useless4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/webview3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong0dp.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong0dp.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong0dp.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong0dp.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_dimension.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_dimension.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_dimension.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_dimension.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrong_namespace5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams6.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/wrongparams_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/yesno.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/yesno.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/yesno.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/layout/yesno.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu-land/actions2_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/menu.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/menu.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/menu.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/menu.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/showAction2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/titles.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/titles.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/titles.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/menu/titles.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/private_key.pem b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/private_key.pem
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/private_key.pem
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/private_key.pem
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/hello b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/hello
new file mode 100755
index 0000000..e84aac3
Binary files /dev/null and b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/raw/hello differ
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/arrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/arrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/arrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/arrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/plurals3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/plurals3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/plurals3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/plurals3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/translatedarrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/translatedarrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/translatedarrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-cs/translatedarrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de-rDE/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de-rDE/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de-rDE/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de-rDE/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/typos.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/typos.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/typos.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-de/typos.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es-rUS/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es-rUS/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es-rUS/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es-rUS/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/donottranslate.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/donottranslate.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/donottranslate.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/donottranslate.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/formatstrings_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_locale.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_locale.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_locale.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-es/strings_locale.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-fr/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-fr/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-fr/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-fr/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/arrays_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-land/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos_locale.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos_locale.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos_locale.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nb/typos_locale.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/arrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/arrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/arrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/arrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-nl-rNL/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-pl/plurals2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-pl/plurals2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-pl/plurals2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-pl/plurals2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-ru/plurals6.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-ru/plurals6.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-ru/plurals6.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-ru/plurals6.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/bom.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/bom.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/bom.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/bom.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/plurals3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/plurals3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/plurals3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values-zh-rCN/plurals3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aaptcrash.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aaptcrash.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aaptcrash.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aaptcrash.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/aliases2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/analytics.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/analytics.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/analytics.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/analytics.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_wrong_strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_wrong_strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_wrong_strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/appindexing_wrong_strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrayusage.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrayusage.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrayusage.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/arrayusage.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/buttonbar-values.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/buttonbar-values.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/buttonbar-values.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/buttonbar-values.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/colorcycle5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/customattr.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/customattr.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/customattr.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/customattr.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/dimens.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/dimens.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/dimens.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/dimens.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-items.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-items.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-items.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-items.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/duplicate-strings2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings-version2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings10.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings10.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings10.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings10.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings11.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings11.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings11.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings11.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings6.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings6.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings6.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings6.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings7.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings7.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings7.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings7.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings8.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings8.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings8.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings8.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings9.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings9.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings9.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings9.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/formatstrings_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/google_maps_api.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/google_maps_api.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/google_maps_api.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/google_maps_api.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integer_arrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integer_arrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integer_arrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integer_arrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integers.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integers.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integers.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/integers.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/negative_margins.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/negative_margins.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/negative_margins.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/negative_margins.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/nontranslatable2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals5.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals5.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals5.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals5.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_candidates.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_candidates.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_candidates.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_candidates.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_typography.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_typography.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_typography.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/plurals_typography.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/pxsp.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/pxsp.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/pxsp.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/pxsp.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/refs2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/repeated_words.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/repeated_words.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/repeated_words.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/repeated_words.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/shared_prefs_keys.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/shared_prefs_keys.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/shared_prefs_keys.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/shared_prefs_keys.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/sizestyles.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/sizestyles.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/sizestyles.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/sizestyles.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stringarrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stringarrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stringarrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stringarrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings4.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings4.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings4.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings4.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/strings_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/stylecycle2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-inherited-orientation.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-inherited-orientation.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-inherited-orientation.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-inherited-orientation.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-orientation.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-orientation.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-orientation.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles-orientation.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/styles2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/themes3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/translatedarrays.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/translatedarrays.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/translatedarrays.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/translatedarrays.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list_formatted.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list_formatted.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list_formatted.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/nfc_tech_list_formatted.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/prefs_headers.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/prefs_headers.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/prefs_headers.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/res/xml/prefs_headers.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/GravityTest2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17nortl.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17nortl.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17nortl.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17nortl.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17rtl.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17rtl.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17rtl.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/min17rtl.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/minsdk5targetsdk17.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/minsdk5targetsdk17.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/minsdk5targetsdk17.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/minsdk5targetsdk17.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api14.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api14.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api14.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api14.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api17.properties b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api17.properties
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api17.properties
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/project-api17.properties
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relative.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeCompat.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeCompat.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeCompat.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeCompat.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/relativeOk.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl_noprefix.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl_noprefix.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl_noprefix.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/rtl_noprefix.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/spinner.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/spinner.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/spinner.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/spinner.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/symmetry.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/symmetry.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/symmetry.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rtl/symmetry.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/safereceiver1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/safereceiver1.xml
new file mode 100644
index 0000000..5004bee
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/safereceiver1.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <receiver
+ android:label="@string/app_name"
+ android:name=".TestReceiver"
+ android:permission="android.permission.BROADCAST_SMS" >
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt
new file mode 100644
index 0000000..53c6c15
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/AnyRes.java.txt
@@ -0,0 +1,15 @@
+package android.support.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ at Retention(CLASS)
+ at Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface AnyRes {
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CallSuper.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CallSuper.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CallSuper.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CallSuper.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CheckResult.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CheckResult.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CheckResult.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/CheckResult.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/ColorInt.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/ColorInt.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/ColorInt.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/ColorInt.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt
new file mode 100644
index 0000000..4393dfb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/DrawableRes.java.txt
@@ -0,0 +1,17 @@
+package android.support.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+ at Documented
+ at Retention(SOURCE)
+ at Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface DrawableRes {
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/FloatRange.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/FloatRange.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/FloatRange.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/FloatRange.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntDef.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntDef.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntDef.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntDef.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/Size.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/Size.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/Size.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/Size.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/StringDef.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/StringDef.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/StringDef.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/StringDef.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Activity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Activity.java.txt
new file mode 100644
index 0000000..80fc034
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Activity.java.txt
@@ -0,0 +1,6 @@
+package android.app;
+
+import android.content.Context
+
+// Stub class for testing.
+public class Activity extends Context {}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Api.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Api.java.txt
new file mode 100644
index 0000000..455a90d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/Api.java.txt
@@ -0,0 +1,8 @@
+package com.google.android.gms.common.api;
+
+public final class Api<O extends Api.ApiOptions> {
+ public interface ApiOptions {
+ public interface NotRequiredOptions extends Api.ApiOptions {}
+ public static final class NoOptions implements Api.ApiOptions.NotRequiredOptions {}
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndex.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndex.java.txt
new file mode 100644
index 0000000..7b1ca76
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndex.java.txt
@@ -0,0 +1,10 @@
+package com.google.android.gms.appindexing;
+
+import com.google.android.gms.appindexing.AppIndexApi;
+import com.google.android.gms.common.api.Api;
+import com.google.android.gms.common.api.Api.ApiOptions.NoOptions;
+
+public final class AppIndex {
+ public static final AppIndexApi AppIndexApi;
+ public static final Api<NoOptions> APP_INDEX_API;
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndexApi.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndexApi.java.txt
new file mode 100644
index 0000000..1451fef
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/AppIndexApi.java.txt
@@ -0,0 +1,27 @@
+package com.google.android.gms.appindexing;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.View;
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.Status;
+import java.util.List;
+
+public class AppIndexApi {
+ PendingResult<Status> view(GoogleApiClient var1, Activity var2, Intent var3, String var4, Uri var5, List<AppIndexApi.AppIndexingLink> var6) {}
+
+ PendingResult<Status> viewEnd(GoogleApiClient var1, Activity var2, Intent var3) {}
+
+ PendingResult<Status> view(GoogleApiClient var1, Activity var2, Uri var3, String var4, Uri var5, List<AppIndexApi.AppIndexingLink> var6) {}
+
+ PendingResult<Status> viewEnd(GoogleApiClient var1, Activity var2, Uri var3) {}
+
+ PendingResult<Status> start(GoogleApiClient var1, Action var2) {}
+
+ PendingResult<Status> end(GoogleApiClient var1, Action var2) {}
+
+ public static final class AppIndexingLink {}
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/GoogleApiClient.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/GoogleApiClient.java.txt
new file mode 100644
index 0000000..966b2ff
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/appindexing/GoogleApiClient.java.txt
@@ -0,0 +1,15 @@
+package com.google.android.gms.common.api;
+
+import com.google.android.gms.common.api.Api;
+import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions;
+
+public abstract class GoogleApiClient {
+ public abstract void connect() {}
+ public abstract void disconnect() {}
+
+ public static final class Builder {
+ public Builder(Context c);
+ public GoogleApiClient.Builder addApi(Api<? extends NotRequiredOptions> api);
+ public GoogleApiClient build();
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt
new file mode 100644
index 0000000..c2016aa
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestEndMatch.java.txt
@@ -0,0 +1,35 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ mClient.connect();
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.end(mClient, action);
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt
new file mode 100644
index 0000000..ce030cb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestGoogleApiClientAddApi.java.txt
@@ -0,0 +1,40 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ mClient.connect();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.start(mClient, action);
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.end(mClient, action);
+ mClient.disconnect();
+ }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt
new file mode 100644
index 0000000..1b154bb
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestNoStartEnd.java.txt
@@ -0,0 +1,34 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ mClient.connect();
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ mClient.disconnect();
+ }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestOk.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestOk.java.txt
new file mode 100644
index 0000000..8c06924
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestOk.java.txt
@@ -0,0 +1,40 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ mClient.connect();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.start(mClient, action);
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.end(mClient, action);
+ mClient.disconnect();
+ }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt
new file mode 100644
index 0000000..c7d5133
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestStartMatch.java.txt
@@ -0,0 +1,36 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.start(mClient, action);
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ mClient.disconnect();
+ }
+}
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt
new file mode 100644
index 0000000..9fd7994
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewEndMatch.java.txt
@@ -0,0 +1,31 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ AppIndex.AppIndexApi.viewEnd(mClient, this, APP_URI);
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt
new file mode 100644
index 0000000..020a9fa
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestViewMatch.java.txt
@@ -0,0 +1,33 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ final String title = "App Indexing API Title";
+ AppIndex.AppIndexApi.view(mClient, this, APP_URI, title, WEB_URL, null);
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt
new file mode 100644
index 0000000..924c99a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/com/example/helloworld/AppIndexingApiTestWrongOrder.java.txt
@@ -0,0 +1,40 @@
+package com.example.helloworld;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.android.gms.appindexing.Action;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+public class AppIndexingApiTest extends Activity {
+ static final Uri APP_URI = Uri.parse("android-app://com.example.helloworld/http/example.com/gizmos");
+ static final Uri WEB_URL = Uri.parse("http://example.com/gizmos");
+ private GoogleApiClient mClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ }
+
+ @Override
+ public void onStart(){
+ super.onStart();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.end(mClient, action);
+ mClient.disconnect();
+ }
+
+ @Override
+ public void onStop(){
+ super.onStop();
+ mClient.connect();
+ final String title = "App Indexing API Title";
+ Action action = Action.newAction(Action.TYPE_VIEW, title, WEB_URL, APP_URI);
+ AppIndex.AppIndexApi.start(mClient, action);
+ }
+}
+
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/my/pkg/Test.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/my/pkg/Test.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/my/pkg/Test.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/my/pkg/Test.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/ColorAsDrawable.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/ColorAsDrawable.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/ColorAsDrawable.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/ColorAsDrawable.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg1/Class1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg1/Class1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg1/Class1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg1/Class1.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg2/Class2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg2/Class2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg2/Class2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/pkg2/Class2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionBarTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt
new file mode 100644
index 0000000..90717cc
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest1_ignore.java.txt
@@ -0,0 +1,10 @@
+package test.pkg;
+
+import android.view.MenuItem;
+
+public class ActionTest1 {
+ @android.annotation.SuppressLint("AlwaysShowAction")
+ public void foo() {
+ System.out.println(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ActionTest2.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt
new file mode 100644
index 0000000..523d93d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/AlarmTest.java.txt
@@ -0,0 +1,22 @@
+package test.pkg;
+
+import android.app.AlarmManager;
+
+public class AlarmTest {
+ public void test(AlarmManager alarmManager) {
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, 60000, null); // OK
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 6000, 70000, null); // OK
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 50, 10, null); // ERROR
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, // ERROR
+ OtherClass.MY_INTERVAL, null); // ERROR
+
+ // Check value flow analysis
+ int interval = 10;
+ long interval2 = 2 * interval;
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, interval2, null); // ERROR
+ }
+
+ private static class OtherClass {
+ public static final long MY_INTERVAL = 1000L;
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
new file mode 100644
index 0000000..6b76230
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Assert.java.txt
@@ -0,0 +1,27 @@
+package test.pkg;
+
+import android.annotation.SuppressLint;
+
+public class Assert {
+ public Assert(int param, Object param2, Object param3) {
+ assert false; // ERROR
+ assert param > 5 : "My description"; // ERROR
+ assert param2 == param3; // ERROR
+ assert param2 != null && param3 == param2; // ERROR
+ assert true; // OK
+ assert param2 == null; // OK
+ assert param2 != null && param3 == null; // OK
+ assert param2 == null && param3 != null; // OK
+ assert param2 != null && param3 != null; // OK
+ assert null != param2; // OK
+ assert param2 != null; // OK
+ assert param2 != null : "My description"; // OK
+ assert checkSuppressed(5) != null; // OK
+ }
+
+ @SuppressLint("Assert")
+ public Object checkSuppressed(int param) {
+ assert param > 5 : "My description";
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/BadImport.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/BadImport.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/BadImport.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/BadImport.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CallSuperTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CallSuperTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CallSuperTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CallSuperTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckPermissions.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckPermissions.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckPermissions.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckPermissions.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckResult.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckResult.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckResult.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckResult.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAES.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAES.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAES.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAES.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESCBC.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESCBC.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESCBC.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESCBC.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESECB.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESECB.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESECB.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceAESECB.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceDES.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceDES.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceDES.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceDES.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CipherGetInstanceTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ColorUsage.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ColorUsage.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ColorUsage.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ColorUsage.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ContentProviderClientTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ContentProviderClientTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ContentProviderClientTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ContentProviderClientTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CursorTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CursorTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CursorTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CursorTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomView1.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomView1.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomView1.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomView1.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomViewTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomViewTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomViewTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CustomViewTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
new file mode 100644
index 0000000..15a06c0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
@@ -0,0 +1,46 @@
+package test.pkg;
+
+import android.view.View;
+
+public class DetachedFromWindow {
+ private static class Test1 extends ViewWithDefaultConstructor {
+ protected void onDetachedFromWindow() {
+ // Error
+ }
+ }
+
+ private static class Test2 extends ViewWithDefaultConstructor {
+ protected void onDetachedFromWindow(int foo) {
+ // OK: not overriding the right method
+ }
+ }
+
+ private static class Test3 extends ViewWithDefaultConstructor {
+ protected void onDetachedFromWindow() {
+ // OK: Calling super
+ super.onDetachedFromWindow();
+ }
+ }
+
+ private static class Test4 extends ViewWithDefaultConstructor {
+ protected void onDetachedFromWindow() {
+ // Error: missing detach call
+ int x = 1;
+ x++;
+ System.out.println(x);
+ }
+ }
+
+ private static class Test5 extends Object {
+ protected void onDetachedFromWindow() {
+ // OK - not in a view
+ // Regression test for http://b.android.com/73571
+ }
+ }
+
+ public class ViewWithDefaultConstructor extends View {
+ public ViewWithDefaultConstructor() {
+ super(null);
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Foo.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Foo.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Foo.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Foo.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseAndTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseAndTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseAndTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseAndTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseOrTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseOrTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseOrTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseOrTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseXorTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseXorTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseXorTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesBitwiseXorTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesLocalVariableTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesLocalVariableTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesLocalVariableTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesLocalVariableTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNoFlagTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNoFlagTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNoFlagTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNoFlagTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNotPackageManagerTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNotPackageManagerTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNotPackageManagerTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesNotPackageManagerTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesSingleFlagTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesSingleFlagTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesSingleFlagTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesSingleFlagTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesStaticFieldTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesStaticFieldTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesStaticFieldTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/GetSignaturesStaticFieldTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Hidden.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Hidden.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Hidden.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Hidden.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ImportFrameActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ImportFrameActivity.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ImportFrameActivity.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ImportFrameActivity.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/InflaterTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/IntDefTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/IntDefTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/IntDefTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/IntDefTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.class.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.class.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.class.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.class.data
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Java7API.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest_ignored.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest_ignored.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest_ignored.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LayoutInflationTest_ignored.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Log.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Log.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Log.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Log.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LogTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LogTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LogTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LogTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt
new file mode 100644
index 0000000..4d93d7c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/LongSparseArray.java.txt
@@ -0,0 +1,12 @@
+package test.pkg;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.content.Context;
+
+public class LongSparseArray {
+ public void test() { // but only minSdkVersion >= 17
+ Map<Long, String> myStringMap = new HashMap<Long, String>();
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/MyTracksProvider.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/MyTracksProvider.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/MyTracksProvider.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/MyTracksProvider.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NonInternationalizedSmsDetectorTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NonInternationalizedSmsDetectorTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NonInternationalizedSmsDetectorTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NonInternationalizedSmsDetectorTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/NotificationTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/OverrideConcreteTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/OverrideConcreteTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/OverrideConcreteTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/OverrideConcreteTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PasteError.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PasteError.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PasteError.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PasteError.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclass.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclass.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclass.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclass.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt
new file mode 100644
index 0000000..f4aa929
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/PreferenceActivitySubclassOverridesIsValidFragment.java.txt
@@ -0,0 +1,10 @@
+package test.pkg;
+
+import android.preference.PreferenceActivity;
+
+public class PreferenceActivitySubclass extends PreferenceActivity {
+
+ protected boolean isValidFragment(String fragmentName) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteDatabase.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteDatabase.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteDatabase.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteDatabase.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt
new file mode 100644
index 0000000..8ac42a5
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SQLiteTest.java.txt
@@ -0,0 +1,41 @@
+package test.pkg;
+
+import android.database.sqlite.SQLiteDatabase;
+
+ at SuppressWarnings({"unused", "SpellCheckingInspection"})
+public class SQLiteTest {
+ public interface Tables {
+ interface AppKeys {
+ String NAME = "appkeys";
+
+ interface Columns {
+ String _ID = "_id";
+ String PKG_NAME = "packageName";
+ String PKG_SIG = "signatureDigest";
+ }
+
+ String SCHEMA =
+ Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Columns.PKG_NAME + " STRING NOT NULL," +
+ Columns.PKG_SIG + " STRING NOT NULL";
+ }
+ }
+
+ public void test(SQLiteDatabase db, String name) {
+ db.execSQL("CREATE TABLE " + name + "(" + Tables.AppKeys.SCHEMA + ");"); // ERROR
+
+ }
+
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(TracksColumns.CREATE_TABLE); // ERROR
+ }
+
+ private void doCreate(SQLiteDatabase db) {
+ // Not yet handled; we need to flow string concatenation across procedure calls
+ createTable(db, Tables.AppKeys.NAME, Tables.AppKeys.SCHEMA); // ERROR
+ }
+
+ private void createTable(SQLiteDatabase db, String tableName, String schema) {
+ db.execSQL("CREATE TABLE " + tableName + "(" + schema + ");");
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SdCardTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SdCardTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SdCardTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SdCardTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt
new file mode 100644
index 0000000..da79803
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SetJavaScriptEnabled.java.txt
@@ -0,0 +1,37 @@
+package com.company.something;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+public class SetJavaScriptEnabled extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ WebView webView = (WebView)findViewById(R.id.webView);
+ webView.getSettings().setJavaScriptEnabled(true); // bad
+ webView.getSettings().setJavaScriptEnabled(false); // good
+ webView.loadUrl("file:///android_asset/www/index.html");
+ }
+
+ // Test Suppress
+ // Constructor: See issue 35588
+ @android.annotation.SuppressLint("SetJavaScriptEnabled")
+ public void HelloWebApp() {
+ WebView webView = (WebView)findViewById(R.id.webView);
+ webView.getSettings().setJavaScriptEnabled(true); // bad
+ webView.getSettings().setJavaScriptEnabled(false); // good
+ webView.loadUrl("file:///android_asset/www/index.html");
+ }
+
+ public static final class R {
+ public static final class layout {
+ public static final int main = 0x7f0a0000;
+ }
+ public static final class id {
+ public static final int webView = 0x7f0a0001;
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest2.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest2.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest2.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest3.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest3.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest3.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest4.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest4.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest4.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest5.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest5.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest5.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest6.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest6.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest6.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest6.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest7.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest7.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest7.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest7.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest8.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest8.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest8.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest8.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
new file mode 100644
index 0000000..fa907b8
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SparseLongArray.java.txt
@@ -0,0 +1,12 @@
+package test.pkg;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.content.Context;
+
+public class SparseLongArray {
+ public void test() { // but only minSdkVersion >= 18
+ Map<Integer, Long> myStringMap = new HashMap<Integer, Long>();
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat10.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat10.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat10.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat10.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat11.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat11.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat11.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat11.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt
new file mode 100644
index 0000000..e959d5c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat2.java.txt
@@ -0,0 +1,20 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.content.Context;
+
+public class StringFormat2 extends Activity {
+ public static final String buildUserAgent(Context context) {
+ StringBuilder arg = new StringBuilder();
+ // Snip
+ final String base = context.getResources().getText(R.string.web_user_agent).toString();
+ String ua = String.format(base, arg);
+ return ua;
+ }
+
+ public static final class R {
+ public static final class string {
+ public static final int web_user_agent = 0x7f0a000e;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt
new file mode 100644
index 0000000..ae747e4
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat3.java.txt
@@ -0,0 +1,34 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.content.Context;
+
+import java.util.Locale;
+
+public class StringFormat3 extends Activity {
+ public final void test(Context context) {
+ Article article = new Article();
+ String s1 = String.format(Locale.FRANCE,
+ context.getString(R.string.gridview_views_count), article.playsCount);
+ String s2 = String.format(Locale.FRANCE,
+ context.getString(R.string.gridview_views_count), 5);
+ String s3 = String.format(Locale.FRANCE,
+ context.getString(R.string.gridview_views_count), "wrong");
+ String s4 = String.format(context.getString(R.string.gridview_views_count), "wrong");
+ String s5 = String.format(context.getString(R.string.gridview_views_count), 5); // OK
+ String s6 = String.format(Locale.getDefault(),
+ context.getString(R.string.gridview_views_count), 5);
+ String s7 = String.format((Locale) null,
+ context.getString(R.string.gridview_views_count), "string");
+ }
+
+ private static class Article {
+ String playsCount;
+ }
+
+ private static class R {
+ private static class string {
+ public static final int gridview_views_count = 1;
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt
new file mode 100644
index 0000000..2c55a52
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat4.java.txt
@@ -0,0 +1,26 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.content.Context;
+
+public class StringFormat4 extends Activity {
+ public final void test(Context context) {
+ // data_source takes 0 formatting arguments
+ // error_and_source takes two formatting arguments
+ // preferences_about_app_title takes two formatting arguments
+ getString(R.string.error_and_source, getString(R.string.data_source)); // ERROR
+ getString(R.string.error_and_source, getString(R.string.data_source), 5); // OK
+ getString(R.string.error_and_source, "data source"); // ERROR
+ getString(R.string.error_and_source, "data source", 5); // OK
+ String.format(getString(R.string.preferences_about_app_title), getString(R.string.app_name), ""); // OK
+ }
+
+ private static class R {
+ private static class string {
+ public static final int error_and_source = 1;
+ public static final int data_source = 2;
+ public static final int preferences_about_app_title = 3;
+ public static final int app_name = 4;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt
new file mode 100644
index 0000000..aa19258
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat5.java.txt
@@ -0,0 +1,25 @@
+package test.pkg;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+
+public class StringFormat5 extends Activity {
+ public final void test(Context context) {
+ Resources resources = getResources();
+ String string = resources.getString(R.string.VibrationLevelIs, resources.getString(PolarPoint.textResourceForIPS()));
+ System.out.println(string);
+ }
+
+ private static class PolarPoint {
+ public static int textResourceForIPS() {
+ return R.string.app_name;
+ }
+ }
+
+ private static class R {
+ private static class string {
+ public static final int VibrationLevelIs = 1;
+ public static final int app_name = 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
new file mode 100644
index 0000000..782aa9b
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat8.java.txt
@@ -0,0 +1,23 @@
+package test.pkg;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+
+public class StringFormat8 extends Activity {
+ public final void test(Context context, float amount, Resources resource) {
+ Resources resources = getResources();
+ String amount1 = resources.getString(R.string.amount_string, amount);
+ String amount2 = getResources().getString(R.string.amount_string, amount);
+ String amount3 = String.format(getResources().getString(R.string.amount_string), amount);
+ String amount4 = String.format(getResources().getString(R.string.amount_string)); // ERROR
+ String amount5 = getResources().getString(R.string.amount_string, amount, amount); // ERROR
+ String misc = String.format(resource.getString(R.string.percent_newline));
+ }
+
+ private static class R {
+ private static class string {
+ public static final int amount_string = 1;
+ public static final int percent_newline = 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
new file mode 100644
index 0000000..c9e5e2f
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormat9.java.txt
@@ -0,0 +1,15 @@
+package test.pkg;
+
+import android.content.res.Resources;
+
+public class StringFormat9 {
+ public String format(Resources resources, int percentUsed) {
+ return resources.getString(R.string.toast_percent_copy_quota_used, percentUsed);
+ }
+
+ private static class R {
+ private static class string {
+ public static final int toast_percent_copy_quota_used = 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt
new file mode 100644
index 0000000..1f387f2
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity.java.txt
@@ -0,0 +1,46 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class StringFormatActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String target = "World";
+ String hello = getResources().getString(R.string.hello);
+ String output1 = String.format(hello, target);
+ String hello2 = getResources().getString(R.string.hello2);
+ String output2 = String.format(hello2, target, "How are you");
+ setContentView(R.layout.main);
+ String score = getResources().getString(R.string.score);
+ int points = 50;
+ boolean won = true;
+ String output3 = String.format(score, points);
+ String output4 = String.format(score, true); // wrong
+ String output = String.format(score, won); // wrong
+ String output5 = String.format(score, 75);
+ String.format(getResources().getString(R.string.hello2), target, "How are you");
+ //getResources().getString(R.string.hello, target, "How are you");
+ getResources().getString(R.string.hello2, target, "How are you");
+ }
+
+ // Test constructor handling (issue 35588)
+ public StringFormatActivity() {
+ String target = "World";
+ String hello = getResources().getString(R.string.hello);
+ String output1 = String.format(hello, target);
+ }
+
+ private static class R {
+ private static class string {
+ public static final int hello = 1;
+ public static final int hello2 = 2;
+ public static final int score = 3;
+ }
+ private static class layout {
+ public static final int main = 4;
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt
new file mode 100644
index 0000000..7c1300a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity2.java.txt
@@ -0,0 +1,32 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class StringFormatActivity2 extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String target = "World";
+ getResources().getString(R.string.formattest1, "hello");
+ getResources().getString(R.string.formattest2, "hello");
+ getResources().getString(R.string.formattest3, 42);
+ getResources().getString(R.string.formattest4, 42);
+ getResources().getString(R.string.formattest5, "hello");
+ getResources().getString(R.string.formattest6, "hello");
+ getResources().getString(R.string.formattest7, "hello");
+ }
+
+ private static class R {
+ private static class string {
+ public static final int formattest1 = 1;
+ public static final int formattest2 = 2;
+ public static final int formattest3 = 3;
+ public static final int formattest4 = 4;
+ public static final int formattest5 = 5;
+ public static final int formattest6 = 6;
+ public static final int formattest7 = 7;
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt
new file mode 100644
index 0000000..9ad2bed
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity3.java.txt
@@ -0,0 +1,22 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class StringFormatActivity3 extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getResources().getString(R.string.format_90);
+ getResources().getString(R.string.format_80);
+ String.format(getResources().getString(R.string.format_90));
+ String.format(getResources().getString(R.string.format_80));
+ }
+
+ public static final class R {
+ public static final class string {
+ public static final int format_90 = 0x7f0a000e;
+ public static final int format_80 = 0x7f0a000f;
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt
new file mode 100644
index 0000000..eeb366a
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/StringFormatActivity_ignore.java.txt
@@ -0,0 +1,27 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.annotation.SuppressLint;
+
+public class StringFormatActivity extends Activity {
+ /** Called when the activity is first created. */
+ @SuppressLint("all")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String target = "World";
+ String hello = getResources().getString(R.string.hello);
+ String output1 = String.format(hello, target);
+ String hello2 = getResources().getString(R.string.hello2);
+ String output2 = String.format(hello2, target, "How are you");
+ setContentView(R.layout.main);
+ String score = getResources().getString(R.string.score);
+ int points = 50;
+ boolean won = true;
+ String output3 = String.format(score, points);
+ String output4 = String.format(score, true); // wrong
+ String output4 = String.format(score, won); // wrong
+ String output5 = String.format(score, 75);
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
new file mode 100644
index 0000000..16a8d6c
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SuppressTest5.java.txt
@@ -0,0 +1,63 @@
+package test.pkg;
+
+import android.annotation.SuppressLint;
+
+ at SuppressWarnings("unused")
+public class SuppressTest5 {
+ private String suppressVariable() {
+ @SuppressLint("SdCardPath")
+ String string = "/sdcard/mypath1";
+ return string;
+ }
+
+ @SuppressLint("SdCardPath")
+ private String suppressMethod() {
+ String string = "/sdcard/mypath2";
+ return string;
+ }
+
+ @SuppressLint("SdCardPath")
+ private static class SuppressClass {
+ private String suppressMethod() {
+ String string = "/sdcard/mypath3";
+ return string;
+ }
+ }
+
+ private String suppressAll() {
+ @SuppressLint("all")
+ String string = "/sdcard/mypath4";
+ return string;
+ }
+
+ private String suppressCombination() {
+ @SuppressLint({"foo1", "foo2", "SdCardPath"})
+ String string = "/sdcard/mypath5";
+
+ // This is NOT annotated and *should* generate
+ // a warning (here to make sure we don't just
+ // suppress everything when we see an annotation
+ String notAnnotated = "/sdcard/mypath";
+
+ return string;
+ }
+
+ private String suppressWarnings() {
+ @SuppressWarnings("all")
+ String string = "/sdcard/mypath6";
+
+ @SuppressWarnings("SdCardPath")
+ String string2 = "/sdcard/mypath7";
+
+ @SuppressWarnings("AndroidLintSdCardPath")
+ String string3 = "/sdcard/mypath9";
+
+ //noinspection AndroidLintSdCardPath
+ String string4 = "/sdcard/mypath9";
+
+ return string;
+ }
+
+ @SuppressLint("SdCardPath")
+ private String supressField = "/sdcard/mypath8";
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SurfaceTextureTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SurfaceTextureTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SurfaceTextureTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SurfaceTextureTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt
new file mode 100644
index 0000000..a484cb3
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/SystemServiceTest.java.txt
@@ -0,0 +1,31 @@
+package test.pkg;
+import android.content.ClipboardManager;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.service.wallpaper.WallpaperService;
+
+public class SystemServiceTest extends Activity {
+
+ public void test1() {
+ DisplayManager displayServiceOk = (DisplayManager) getSystemService(DISPLAY_SERVICE);
+ DisplayManager displayServiceWrong = (DisplayManager) getSystemService(
+ DEVICE_POLICY_SERVICE);
+ WallpaperService wallPaperOk = (WallpaperService) getSystemService(WALLPAPER_SERVICE);
+ WallpaperManager wallPaperWrong = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
+ }
+
+ public void test2(Context context) {
+ DisplayManager displayServiceOk = (DisplayManager) context
+ .getSystemService(DISPLAY_SERVICE);
+ DisplayManager displayServiceWrong = (DisplayManager) context
+ .getSystemService(DEVICE_POLICY_SERVICE);
+ }
+
+ public void clipboard(Context context) {
+ ClipboardManager clipboard = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
+ android.content.ClipboardManager clipboard1 = (android.content.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
+ android.text.ClipboardManager clipboard2 = (android.text.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt
new file mode 100644
index 0000000..847eb87
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ToastTest.java.txt
@@ -0,0 +1,57 @@
+package foo.bar;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.Toast;
+
+public abstract class ToastTest extends Context {
+ private Toast createToast(Context context) {
+ // Don't warn here
+ return Toast.makeText(context, "foo", Toast.LENGTH_LONG);
+ }
+
+ private void showToast(Context context) {
+ // Don't warn here
+ Toast toast = Toast.makeText(context, "foo", Toast.LENGTH_LONG);
+ System.out.println("Other intermediate code here");
+ int temp = 5 + 2;
+ toast.show();
+ }
+
+ private void showToast2(Context context) {
+ // Don't warn here
+ int duration = Toast.LENGTH_LONG;
+ Toast.makeText(context, "foo", Toast.LENGTH_LONG).show();
+ Toast.makeText(context, R.string.app_name, duration).show();
+ }
+
+ private void broken(Context context) {
+ // Errors
+ Toast.makeText(context, "foo", Toast.LENGTH_LONG);
+ Toast toast = Toast.makeText(context, R.string.app_name, 5000);
+ toast.getDuration();
+ }
+
+ // Constructor test
+ public ToastTest(Context context) {
+ Toast.makeText(context, "foo", Toast.LENGTH_LONG);
+ }
+
+ @android.annotation.SuppressLint("ShowToast")
+ private void checkSuppress1(Context context) {
+ Toast toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG);
+ }
+
+ private void checkSuppress2(Context context) {
+ @android.annotation.SuppressLint("ShowToast")
+ Toast toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG);
+ }
+
+ public static final class R {
+ public static final class string {
+ public static final int app_name = 0x7f0a0000;
+ }
+ }
+}
+
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TransactionTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TransactionTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TransactionTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TransactionTest.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryCatchHang.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryCatchHang.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryCatchHang.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryCatchHang.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryWithResources.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryWithResources.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryWithResources.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/TryWithResources.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReference.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReference.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReference.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReference.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReferenceDynamic.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReferenceDynamic.java.txt
new file mode 100644
index 0000000..46c2bc0
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/UnusedReferenceDynamic.java.txt
@@ -0,0 +1,14 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.design.widget.Snackbar;
+
+public class UnusedReferenceDynamic extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(test.pkg.R.layout.main);
+ Snackbar.make(view, R.string.xyz, Snackbar.LENGTH_LONG);
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Utf8BomTest.java.data b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Utf8BomTest.java.data
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Utf8BomTest.java.data
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/Utf8BomTest.java.data
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
new file mode 100644
index 0000000..329ba3f
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/ViewHolderTest.java.txt
@@ -0,0 +1,168 @@
+package test.pkg;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+ at SuppressWarnings({"ConstantConditions", "UnusedDeclaration"})
+public abstract class ViewHolderTest extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ public static class Adapter1 extends ViewHolderTest {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return null;
+ }
+ }
+
+ public static class Adapter2 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Should use View Holder pattern here
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter3 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Already using View Holder pattern
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ }
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter4 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Already using View Holder pattern
+ //noinspection StatementWithEmptyBody
+ if (convertView != null) {
+ } else {
+ convertView = mInflater.inflate(R.layout.your_layout, null);
+ }
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter5 extends ViewHolderTest {
+ LayoutInflater mInflater;
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Already using View Holder pattern
+ convertView = convertView == null ? mInflater.inflate(R.layout.your_layout, null) : convertView;
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText("Position " + position);
+
+ return convertView;
+ }
+ }
+
+ public static class Adapter6 extends ViewHolderTest {
+ private Context mContext;
+ private LayoutInflater mLayoutInflator;
+ private ArrayList<Double> mLapTimes;
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (mLayoutInflator == null)
+ mLayoutInflator = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ View v = convertView;
+ if (v == null) v = mLayoutInflator.inflate(R.layout.your_layout, null);
+
+ LinearLayout listItemHolder = (LinearLayout) v.findViewById(R.id.laptimes_list_item_holder);
+ listItemHolder.removeAllViews();
+
+ for (int i = 0; i < mLapTimes.size(); i++) {
+ View lapItemView = mLayoutInflator.inflate(R.layout.laptime_item, null);
+ if (i == 0) {
+ TextView t = (TextView) lapItemView.findViewById(R.id.laptime_text);
+ //t.setText(TimeUtils.createStyledSpannableString(mContext, mLapTimes.get(i), true));
+ }
+
+ TextView t2 = (TextView) lapItemView.findViewById(R.id.laptime_text2);
+ if (i < mLapTimes.size() - 1 && mLapTimes.size() > 1) {
+ double laptime = mLapTimes.get(i) - mLapTimes.get(i + 1);
+ if (laptime < 0) laptime = mLapTimes.get(i);
+ //t2.setText(TimeUtils.createStyledSpannableString(mContext, laptime, true));
+ } else {
+ //t2.setText(TimeUtils.createStyledSpannableString(mContext, mLapTimes.get(i), true));
+ }
+
+ listItemHolder.addView(lapItemView);
+
+ }
+ return v;
+ }
+ }
+
+ public static class Adapter7 extends ViewHolderTest {
+ LayoutInflater inflater;
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ View rootView = convertView;
+ final int itemViewType = getItemViewType(position);
+ switch (itemViewType) {
+ case 0:
+ if (rootView != null)
+ return rootView;
+ rootView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
+ break;
+ }
+ return rootView;
+ }
+ }
+
+ public static final class R {
+ public static final class layout {
+ public static final int your_layout = 0x7f0a0000;
+ public static final int laptime_item = 0x7f0a0001;
+ }
+ public static final class id {
+ public static final int text = 0x7f0a0000;
+ public static final int laptimes_list_item_holder = 0x7f0a0001;
+ public static final int laptime_text = 0x7f0a0002;
+ public static final int laptime_text2 = 0x7f0a0003;
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WatchFaceTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WatchFaceTest.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WatchFaceTest.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WatchFaceTest.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt
new file mode 100644
index 0000000..5d0ba07
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WorldWriteableFile.java.txt
@@ -0,0 +1,58 @@
+package test.pkg;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.FileNotFoundException;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.app.Activity;
+import android.os.Bundle;
+
+public class WorldWriteableFile {
+ File mFile;
+ Context mContext;
+
+ public void foo() {
+ OutputStream out = null;
+ SharedPreferences prefs = null;
+ File dir = null;
+
+ boolean success = false;
+ try {
+ out = openFileOutput(mFile.getName()); // ok
+ out = openFileOutput(mFile.getName(), MODE_PRIVATE); // ok
+ out = openFileOutput(mFile.getName(), MODE_WORLD_WRITEABLE);
+ out = openFileOutput(mFile.getName(), MODE_WORLD_READABLE);
+
+ prefs = getSharedPreferences(mContext, 0); // ok
+ prefs = getSharedPreferences(mContext, MODE_PRIVATE); // ok
+ prefs = getSharedPreferences(mContext, MODE_WORLD_WRITEABLE);
+ prefs = getSharedPreferences(mContext, MODE_WORLD_READABLE);
+
+ dir = getDir(mFile.getName(), MODE_PRIVATE); // ok
+ dir = getDir(mFile.getName(), MODE_WORLD_WRITEABLE);
+ dir = getDir(mFile.getName(), MODE_WORLD_READABLE);
+
+ mFile.setReadable(true, true); // ok
+ mFile.setReadable(false, true); // ok
+ mFile.setReadable(false, false); // ok
+ mFile.setReadable(true, false);
+ mFile.setReadable(true); // ok
+ mFile.setReadable(false); // ok
+
+ mFile.setWritable(true, true); // ok
+ mFile.setWritable(false, true); // ok
+ mFile.setWritable(false, false); // ok
+ mFile.setWritable(true, false);
+ mFile.setWritable(true); // ok
+ mFile.setWritable(false); // ok
+
+ // Flickr.get().downloadPhoto(params[0], Flickr.PhotoSize.LARGE,
+ // out);
+ success = true;
+ } catch (FileNotFoundException e) {
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
new file mode 100644
index 0000000..5ad48c7
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongAnnotation.java.txt
@@ -0,0 +1,36 @@
+package test.pkg;
+
+import android.annotation.SuppressLint;
+import android.view.View;
+
+public class WrongAnnotation {
+
+ @SuppressLint("NewApi") // Valid: class-file check on method
+ public static void foobar(View view, @SuppressLint("NewApi") int foo) { // Invalid: class-file check
+ @SuppressLint("NewApi") // Invalid
+ boolean a;
+ @SuppressLint({"SdCardPath", "NewApi"}) // Invalid: class-file based check on local variable
+ boolean b;
+ @android.annotation.SuppressLint({"SdCardPath", "NewApi"}) // Invalid (FQN)
+ boolean c;
+ @SuppressLint("SdCardPath") // Valid: AST-based check
+ boolean d;
+ }
+
+ @SuppressLint("NewApi")
+ private int field1;
+
+ @SuppressLint("NewApi")
+ private int field2 = 5;
+
+ static {
+ // Local variable outside method: invalid
+ @SuppressLint("NewApi")
+ int localvar = 5;
+ }
+
+ private static void test() {
+ @SuppressLint("NewApi") // Invalid
+ int a = View.MEASURED_STATE_MASK;
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt
new file mode 100644
index 0000000..6d01767
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity.java.txt
@@ -0,0 +1,26 @@
+package test.pkg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.*;
+
+public class WrongCastActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.casts);
+ Button button = (Button) findViewById(R.id.button);
+ ToggleButton toggleButton = (ToggleButton) findViewById(R.id.button);
+ TextView textView = (TextView) findViewById(R.id.edittext);
+ }
+
+ public static final class R {
+ public static final class layout {
+ public static final int casts = 0x7f0a0002;
+ }
+ public static final class id {
+ public static final int button = 0x7f0a0000;
+ public static final int edittext = 0x7f0a0001;
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt
new file mode 100644
index 0000000..14313b6
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity2.java.txt
@@ -0,0 +1,22 @@
+package test.pkg;
+
+import android.app.*;
+import android.view.*;
+import android.widget.*;
+
+public class WrongCastActivity2 extends Activity {
+ private TextView additionalButton;
+
+ private void configureAdditionalButton(View bodyView) {
+ this.additionalButton = (TextView) bodyView
+ .findViewById(R.id.additional);
+ Object x = (AdapterView<?>) bodyView.findViewById(R.id.reminder_lead);
+ }
+
+ public static final class R {
+ public static final class id {
+ public static final int additional = 0x7f0a0000;
+ public static final int reminder_lead = 0x7f0a0001;
+ }
+ }
+}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt
new file mode 100644
index 0000000..7e01c35
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongCastActivity3.java.txt
@@ -0,0 +1,17 @@
+package test.pkg;
+
+import android.app.*;
+import android.view.*;
+import android.widget.*;
+
+public class WrongCastActivity3 extends Activity {
+ private void test() {
+ final Checkable check = (Checkable) findViewById(R.id.additional);
+ }
+
+ public static final class R {
+ public static final class id {
+ public static final int additional = 0x7f0a0000;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongColor.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongColor.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongColor.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/WrongColor.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/CanvasWatchFaceService.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/CanvasWatchFaceService.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/CanvasWatchFaceService.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/CanvasWatchFaceService.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/WatchFaceService.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/WatchFaceService.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/WatchFaceService.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/stubs/WatchFaceService.java.txt
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls.xml
new file mode 100644
index 0000000..9b81403
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <service
+ android:name=".InsecureTLSIntentService" >
+ </service>
+ </application>
+
+</manifest>
+
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls1.xml
new file mode 100644
index 0000000..75c8159
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/tls1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <service
+ android:name=".ExampleTLSIntentService" >
+ </service>
+ </application>
+
+</manifest>
+
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_manifest.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_manifest.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_manifest.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_manifest.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_not_found.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_not_found.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_not_found.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_not_found.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_feature2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_library2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_permission2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/typo_uses_sdk2.xml
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unsafereceiver1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unsafereceiver1.xml
new file mode 100644
index 0000000..d29bd23
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unsafereceiver1.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.pkg"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <receiver
+ android:label="@string/app_name"
+ android:name=".TestReceiver" >
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
+
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unusedR.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unusedR.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unusedR.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/unusedR.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/Foo.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/Foo.java.txt
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/Foo.java.txt
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/Foo.java.txt
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ids.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ids.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ids.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ids.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ignorelayout1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ignorelayout1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ignorelayout1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/ignorelayout1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1_ignore.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1_ignore.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1_ignore.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout1_ignore.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout2.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout2.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout2.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout2.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout3.xml b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout3.xml
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout3.xml
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/wrongid/layout3.xml
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CompositeIssueRegistryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CompositeIssueRegistryTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CompositeIssueRegistryTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CompositeIssueRegistryTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CustomRuleTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CustomRuleTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CustomRuleTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/CustomRuleTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultConfigurationTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultSdkInfoTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultSdkInfoTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultSdkInfoTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/DefaultSdkInfoTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
new file mode 100644
index 0000000..1e82f07
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.List;
+
+ at SuppressWarnings("SpellCheckingInspection")
+public class JarFileIssueRegistryTest extends AbstractCheckTest {
+ public void testError() {
+ try {
+ JarFileIssueRegistry.get(new TestLintClient(), new File("bogus"));
+ fail("Expected exception for bogus path");
+ } catch (Throwable t) {
+ // pass
+ }
+ }
+
+ public void testCached() throws Exception {
+ File targetDir = Files.createTempDir();
+ File file1 = getTestfile(targetDir, "rules/appcompat.jar.data");
+ File file2 = getTestfile(targetDir, "apicheck/unsupported.jar.data");
+ assertTrue(file1.getPath(), file1.exists());
+ final StringWriter mLoggedWarnings = new StringWriter();
+ TestLintClient client = new TestLintClient() {
+ @Override
+ public void log(@NonNull Severity severity, @Nullable Throwable exception,
+ @Nullable String format, @Nullable Object... args) {
+ if (format != null) {
+ mLoggedWarnings.append(String.format(format, args));
+ }
+ }
+
+ };
+ IssueRegistry registry1 = JarFileIssueRegistry.get(client, file1);
+ IssueRegistry registry2 = JarFileIssueRegistry.get(client, new File(file1.getPath()));
+ assertSame(registry1, registry2);
+ IssueRegistry registry3 = JarFileIssueRegistry.get(client, file2);
+ assertNotSame(registry1, registry3);
+
+ assertEquals(1, registry1.getIssues().size());
+ assertEquals("AppCompatMethod", registry1.getIssues().get(0).getId());
+
+ // Access detector state. On Java 7/8 this will access the detector class after
+ // the jar loader has been closed; this tests that we still have valid classes.
+ Detector detector =
+ registry1.getIssues().get(0).getImplementation().getDetectorClass().newInstance();
+ detector.getApplicableAsmNodeTypes();
+ assertSame(Speed.NORMAL, detector.getSpeed());
+ List<String> applicableCallNames = detector.getApplicableCallNames();
+ assertNotNull(applicableCallNames);
+ assertTrue(applicableCallNames.contains("getActionBar"));
+
+ assertEquals(
+ "Custom lint rule jar " + file2.getPath() + " does not contain a valid "
+ + "registry manifest key (Lint-Registry).\n"
+ + "Either the custom jar is invalid, or it uses an outdated API not "
+ + "supported this lint client", mLoggedWarnings.toString());
+ }
+
+ @Override
+ protected Detector getDetector() {
+ fail("Not used in this test");
+ return null;
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintClientTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintClientTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintClientTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintClientTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/LintDriverTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/ProjectTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/ProjectTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/ProjectTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/client/api/ProjectTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/CategoryTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/CategoryTest.java
new file mode 100644
index 0000000..435a346
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/CategoryTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+
+public class CategoryTest extends TestCase {
+ public void testCompare() throws Exception {
+ List<Category> categories = Lists.newArrayList();
+ for (Field field : Category.class.getDeclaredFields()) {
+ if (field.getType() == Category.class &&
+ (field.getModifiers() & Modifier.STATIC) != 0) {
+ field.setAccessible(true);
+ Object o = field.get(null);
+ if (o instanceof Category) {
+ categories.add((Category) o);
+ }
+ }
+ }
+
+ Collections.sort(categories);
+
+ assertEquals(""
+ + "Lint\n"
+ + "Correctness\n"
+ + "Correctness:Messages\n"
+ + "Security\n"
+ + "Performance\n"
+ + "Usability:Typography\n"
+ + "Usability:Icons\n"
+ + "Usability\n"
+ + "Accessibility\n"
+ + "Internationalization\n"
+ + "Internationalization:Bidirectional Text",
+ Joiner.on("\n").join(categories));
+ }
+
+ public void testGetName() {
+ assertEquals("Messages", Category.MESSAGES.getName());
+ }
+
+ public void testGetFullName() {
+ assertEquals("Correctness:Messages", Category.MESSAGES.getFullName());
+ }
+
+ public void testEquals() {
+ assertEquals(Category.MESSAGES, Category.MESSAGES);
+ assertEquals(Category.create("Correctness", 100), Category.create("Correctness", 100));
+ assertFalse(Category.MESSAGES.equals(Category.CORRECTNESS));
+ assertFalse(Category.create("Correctness", 100).equals(Category.create("Correct", 100)));
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ClassContextTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ClassContextTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ClassContextTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ClassContextTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java
new file mode 100644
index 0000000..4ef36f8
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ConstantEvaluatorTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import junit.framework.TestCase;
+
+import org.intellij.lang.annotations.Language;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicReference;
+
+import lombok.ast.CompilationUnit;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.VariableDefinitionEntry;
+
+ at SuppressWarnings("ClassNameDiffersFromFileName")
+public class ConstantEvaluatorTest extends TestCase {
+ private static void check(Object expected, @Language("JAVA") String source,
+ final String targetVariable) {
+ JavaContext context = LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
+ assertNotNull(context);
+ CompilationUnit unit = (CompilationUnit) context.getCompilationUnit();
+ assertNotNull(unit);
+
+ // Find the expression
+ final AtomicReference<Expression> reference = new AtomicReference<Expression>();
+ unit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ if (node.astName().astValue().equals(targetVariable)) {
+ reference.set(node.astInitializer());
+ }
+ return super.visitVariableDefinitionEntry(node);
+ }
+ });
+ Expression expression = reference.get();
+ Object actual = ConstantEvaluator.evaluate(context, expression);
+ if (expected == null) {
+ assertNull(actual);
+ } else {
+ assertNotNull("Couldn't compute value for " + source + ", expected " + expected,
+ actual);
+ assertEquals(expected.getClass(), actual.getClass());
+ assertEquals(expected.toString(), actual.toString());
+ }
+ assertEquals(expected, actual);
+ if (expected instanceof String) {
+ assertEquals(expected, ConstantEvaluator.evaluateString(context, expression,
+ false));
+ }
+ }
+
+ private static void checkStatements(Object expected, String statementsSource,
+ final String targetVariable) {
+ @Language("JAVA")
+ String source = ""
+ + "package test.pkg;\n"
+ + "public class Test {\n"
+ + " public void test() {\n"
+ + " " + statementsSource + "\n"
+ + " }\n"
+ + " public static final int MY_INT_FIELD = 5;\n"
+ + " public static final boolean MY_BOOLEAN_FIELD = true;\n"
+ + " public static final String MY_STRING_FIELD = \"test\";\n"
+ + "}\n";
+
+ check(expected, source, targetVariable);
+ }
+
+ private static void checkExpression(Object expected, String expressionSource) {
+ @Language("JAVA")
+ String source = ""
+ + "package test.pkg;\n"
+ + "public class Test {\n"
+ + " public void test() {\n"
+ + " Object expression = " + expressionSource + ";\n"
+ + " }\n"
+ + " public static final int MY_INT_FIELD = 5;\n"
+ + " public static final boolean MY_BOOLEAN_FIELD = true;\n"
+ + " public static final String MY_STRING_FIELD = \"test\";\n"
+ + "}\n";
+
+ check(expected, source, "expression");
+ }
+
+ public void testStrings() throws Exception {
+ checkExpression(null, "null");
+ checkExpression("hello", "\"hello\"");
+ checkExpression("abcd", "\"ab\" + \"cd\"");
+ }
+
+ public void testBooleans() throws Exception {
+ checkExpression(true, "true");
+ checkExpression(false, "false");
+ checkExpression(false, "false && true");
+ checkExpression(true, "false || true");
+ checkExpression(true, "!false");
+ }
+
+ public void testChars() throws Exception {
+ checkExpression('a', "'a'");
+ checkExpression('\007', "'\007'");
+ }
+
+ public void testCasts() throws Exception {
+ checkExpression(1, "(int)1");
+ checkExpression(1L, "(long)1");
+ checkExpression(1, "(int)1.1f");
+ checkExpression((short)65537, "(short)65537");
+ checkExpression((byte)1023, "(byte)1023");
+ checkExpression(1.5, "(double)1.5f");
+ checkExpression(-5.0, "(double)-5");
+ }
+
+ public void testArithmetic() throws Exception {
+ checkExpression(1, "1");
+ checkExpression(1L, "1L");
+ checkExpression(4, "1 + 3");
+ checkExpression(-2, "1 - 3");
+ checkExpression(10, "2 * 5");
+ checkExpression(2, "10 / 5");
+ checkExpression(1, "11 % 5");
+ checkExpression(8, "1 << 3");
+ checkExpression(16, "32 >> 1");
+ checkExpression(16, "32 >>> 1");
+ checkExpression(5, "5 | 1");
+ checkExpression(1, "5 & 1");
+ checkExpression(~5, "~5");
+ checkExpression(~(long)5, "~(long)5");
+ checkExpression(~(short)5, "~(short)5");
+ checkExpression(~(byte)5, "~(byte)5");
+ checkExpression(-(long)5, "-(long)5");
+ checkExpression(-(short)5, "-(short)5");
+ checkExpression(-(byte)5, "-(byte)5");
+ checkExpression(-(double)5, "-(double)5");
+ checkExpression(-(float)5, "-(float)5");
+ checkExpression(-2, "1 + -3");
+
+ checkExpression(false, "11 == 5");
+ checkExpression(true, "11 == 11");
+ checkExpression(true, "11 != 5");
+ checkExpression(false, "11 != 11");
+ checkExpression(true, "11 > 5");
+ checkExpression(false, "5 > 11");
+ checkExpression(false, "11 < 5");
+ checkExpression(true, "5 < 11");
+ checkExpression(true, "11 >= 5");
+ checkExpression(false, "5 >= 11");
+ checkExpression(false, "11 <= 5");
+ checkExpression(true, "5 <= 11");
+
+ checkExpression(3.5f, "1.0f + 2.5f");
+ }
+
+ public void testFieldReferences() throws Exception {
+ checkExpression(5, "MY_INT_FIELD");
+ checkExpression("test", "MY_STRING_FIELD");
+ checkExpression("prefix-test-postfix", "\"prefix-\" + MY_STRING_FIELD + \"-postfix\"");
+ checkExpression(-4, "3 - (MY_INT_FIELD + 2)");
+ }
+
+ public void testStatements() throws Exception {
+ checkStatements(9, ""
+ + "int x = +5;\n"
+ + "int y = x;\n"
+ + "int w;\n"
+ + "w = -1;\n"
+ + "int z = x + 5 + w;\n",
+ "z");
+ checkStatements("hello world", ""
+ + "String initial = \"hello\";\n"
+ + "String other;\n"
+ + "other = \" world\";\n"
+ + "String finalString = initial + other;\n",
+ "finalString");
+ }
+
+ public void testConditionals() throws Exception {
+ checkStatements(-5, ""
+ + "boolean condition = false;\n"
+ + "condition = !condition;\n"
+ + "int z = condition ? -5 : 4;\n",
+ "z");
+ checkStatements(-4, ""
+ + "boolean condition = true && false;\n"
+ + "int z = condition ? 5 : -4;\n",
+ "z");
+ }
+}
\ No newline at end of file
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ImplementationTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ImplementationTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ImplementationTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ImplementationTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/IssueTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/IssueTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/IssueTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/IssueTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
new file mode 100644
index 0000000..405d2a7
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LintUtilsTest.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.tools.lint.detector.api.LintUtils.computeResourceName;
+import static com.android.tools.lint.detector.api.LintUtils.convertVersion;
+import static com.android.tools.lint.detector.api.LintUtils.findSubstring;
+import static com.android.tools.lint.detector.api.LintUtils.getFormattedParameters;
+import static com.android.tools.lint.detector.api.LintUtils.getLocaleAndRegion;
+import static com.android.tools.lint.detector.api.LintUtils.isImported;
+import static com.android.tools.lint.detector.api.LintUtils.splitPath;
+import static com.android.utils.SdkUtils.escapePropertyValue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ApiVersion;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.tools.lint.LintCliClient;
+import com.android.tools.lint.checks.BuiltinIssueRegistry;
+import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.LintDriver;
+import com.google.common.collect.Iterables;
+
+import junit.framework.TestCase;
+
+import org.intellij.lang.annotations.Language;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+
+import lombok.ast.Node;
+
+ at SuppressWarnings("javadoc")
+public class LintUtilsTest extends TestCase {
+ public void testPrintList() throws Exception {
+ assertEquals("foo, bar, baz",
+ LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 3));
+ assertEquals("foo, bar, baz",
+ LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 5));
+
+ assertEquals("foo, bar, baz... (3 more)",
+ LintUtils.formatList(
+ Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 3));
+ assertEquals("foo... (5 more)",
+ LintUtils.formatList(
+ Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 1));
+ assertEquals("foo, bar, baz",
+ LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 0));
+ }
+
+ public void testEndsWith() throws Exception {
+ assertTrue(LintUtils.endsWith("Foo", ""));
+ assertTrue(LintUtils.endsWith("Foo", "o"));
+ assertTrue(LintUtils.endsWith("Foo", "oo"));
+ assertTrue(LintUtils.endsWith("Foo", "Foo"));
+ assertTrue(LintUtils.endsWith("Foo", "FOO"));
+ assertTrue(LintUtils.endsWith("Foo", "fOO"));
+
+ assertFalse(LintUtils.endsWith("Foo", "f"));
+ }
+
+ public void testStartsWith() throws Exception {
+ assertTrue(LintUtils.startsWith("FooBar", "Bar", 3));
+ assertTrue(LintUtils.startsWith("FooBar", "BAR", 3));
+ assertTrue(LintUtils.startsWith("FooBar", "Foo", 0));
+ assertFalse(LintUtils.startsWith("FooBar", "Foo", 2));
+ }
+
+ public void testIsXmlFile() throws Exception {
+ assertTrue(LintUtils.isXmlFile(new File("foo.xml")));
+ assertTrue(LintUtils.isXmlFile(new File("foo.Xml")));
+ assertTrue(LintUtils.isXmlFile(new File("foo.XML")));
+
+ assertFalse(LintUtils.isXmlFile(new File("foo.png")));
+ assertFalse(LintUtils.isXmlFile(new File("xml")));
+ assertFalse(LintUtils.isXmlFile(new File("xml.png")));
+ }
+
+ public void testGetBasename() throws Exception {
+ assertEquals("foo", LintUtils.getBaseName("foo.png"));
+ assertEquals("foo", LintUtils.getBaseName("foo.9.png"));
+ assertEquals(".foo", LintUtils.getBaseName(".foo"));
+ }
+
+ public void testEditDistance() {
+ assertEquals(0, LintUtils.editDistance("kitten", "kitten"));
+
+ // editing kitten to sitting has edit distance 3:
+ // replace k with s
+ // replace e with i
+ // append g
+ assertEquals(3, LintUtils.editDistance("kitten", "sitting"));
+
+ assertEquals(3, LintUtils.editDistance("saturday", "sunday"));
+ assertEquals(1, LintUtils.editDistance("button", "bitton"));
+ assertEquals(6, LintUtils.editDistance("radiobutton", "bitton"));
+ }
+
+ public void testSplitPath() throws Exception {
+ assertTrue(Arrays.equals(new String[] { "/foo", "/bar", "/baz" },
+ Iterables.toArray(splitPath("/foo:/bar:/baz"), String.class)));
+
+ assertTrue(Arrays.equals(new String[] { "/foo", "/bar" },
+ Iterables.toArray(splitPath("/foo;/bar"), String.class)));
+
+ assertTrue(Arrays.equals(new String[] { "/foo", "/bar:baz" },
+ Iterables.toArray(splitPath("/foo;/bar:baz"), String.class)));
+
+ assertTrue(Arrays.equals(new String[] { "\\foo\\bar", "\\bar\\foo" },
+ Iterables.toArray(splitPath("\\foo\\bar;\\bar\\foo"), String.class)));
+
+ assertTrue(Arrays.equals(new String[] { "${sdk.dir}\\foo\\bar", "\\bar\\foo" },
+ Iterables.toArray(splitPath("${sdk.dir}\\foo\\bar;\\bar\\foo"),
+ String.class)));
+
+ assertTrue(Arrays.equals(new String[] { "${sdk.dir}/foo/bar", "/bar/foo" },
+ Iterables.toArray(splitPath("${sdk.dir}/foo/bar:/bar/foo"),
+ String.class)));
+
+ assertTrue(Arrays.equals(new String[] { "C:\\foo", "/bar" },
+ Iterables.toArray(splitPath("C:\\foo:/bar"), String.class)));
+ }
+
+ public void testCommonParen1() {
+ assertEquals(new File("/a"), (LintUtils.getCommonParent(
+ new File("/a/b/c/d/e"), new File("/a/c"))));
+ assertEquals(new File("/a"), (LintUtils.getCommonParent(
+ new File("/a/c"), new File("/a/b/c/d/e"))));
+
+ assertEquals(new File("/"), LintUtils.getCommonParent(
+ new File("/foo/bar"), new File("/bar/baz")));
+ assertEquals(new File("/"), LintUtils.getCommonParent(
+ new File("/foo/bar"), new File("/")));
+ assertNull(LintUtils.getCommonParent(
+ new File("C:\\Program Files"), new File("F:\\")));
+ assertNull(LintUtils.getCommonParent(
+ new File("C:/Program Files"), new File("F:/")));
+
+ assertEquals(new File("/foo/bar/baz"), LintUtils.getCommonParent(
+ new File("/foo/bar/baz"), new File("/foo/bar/baz")));
+ assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
+ new File("/foo/bar/baz"), new File("/foo/bar")));
+ assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
+ new File("/foo/bar/baz"), new File("/foo/bar/foo")));
+ assertEquals(new File("/foo"), LintUtils.getCommonParent(
+ new File("/foo/bar"), new File("/foo/baz")));
+ assertEquals(new File("/foo"), LintUtils.getCommonParent(
+ new File("/foo/bar"), new File("/foo/baz")));
+ assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
+ new File("/foo/bar"), new File("/foo/bar/baz")));
+ }
+
+ public void testCommonParent2() {
+ assertEquals(new File("/"), LintUtils.getCommonParent(
+ Arrays.asList(new File("/foo/bar"), new File("/bar/baz"))));
+ assertEquals(new File("/"), LintUtils.getCommonParent(
+ Arrays.asList(new File("/foo/bar"), new File("/"))));
+ assertNull(LintUtils.getCommonParent(
+ Arrays.asList(new File("C:\\Program Files"), new File("F:\\"))));
+ assertNull(LintUtils.getCommonParent(
+ Arrays.asList(new File("C:/Program Files"), new File("F:/"))));
+
+ assertEquals(new File("/foo"), LintUtils.getCommonParent(
+ Arrays.asList(new File("/foo/bar"), new File("/foo/baz"))));
+ assertEquals(new File("/foo"), LintUtils.getCommonParent(
+ Arrays.asList(new File("/foo/bar"), new File("/foo/baz"),
+ new File("/foo/baz/f"))));
+ assertEquals(new File("/foo/bar"), LintUtils.getCommonParent(
+ Arrays.asList(new File("/foo/bar"), new File("/foo/bar/baz"),
+ new File("/foo/bar/foo2/foo3"))));
+ }
+
+ public void testStripIdPrefix() throws Exception {
+ assertEquals("foo", LintUtils.stripIdPrefix("@+id/foo"));
+ assertEquals("foo", LintUtils.stripIdPrefix("@id/foo"));
+ assertEquals("foo", LintUtils.stripIdPrefix("foo"));
+ }
+
+ public void testIdReferencesMatch() throws Exception {
+ assertTrue(LintUtils.idReferencesMatch("@+id/foo", "@+id/foo"));
+ assertTrue(LintUtils.idReferencesMatch("@id/foo", "@id/foo"));
+ assertTrue(LintUtils.idReferencesMatch("@id/foo", "@+id/foo"));
+ assertTrue(LintUtils.idReferencesMatch("@+id/foo", "@id/foo"));
+
+ assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/bar"));
+ assertFalse(LintUtils.idReferencesMatch("@id/foo", "@+id/bar"));
+ assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@id/bar"));
+ assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/bar"));
+
+ assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@+id/foo1"));
+ assertFalse(LintUtils.idReferencesMatch("@id/foo", "@id/foo1"));
+ assertFalse(LintUtils.idReferencesMatch("@id/foo", "@+id/foo1"));
+ assertFalse(LintUtils.idReferencesMatch("@+id/foo", "@id/foo1"));
+
+ assertFalse(LintUtils.idReferencesMatch("@+id/foo1", "@+id/foo"));
+ assertFalse(LintUtils.idReferencesMatch("@id/foo1", "@id/foo"));
+ assertFalse(LintUtils.idReferencesMatch("@id/foo1", "@+id/foo"));
+ assertFalse(LintUtils.idReferencesMatch("@+id/foo1", "@id/foo"));
+ }
+
+ private static void checkEncoding(String encoding, boolean writeBom, String lineEnding)
+ throws Exception {
+ @SuppressWarnings("StringBufferReplaceableByString")
+ StringBuilder sb = new StringBuilder();
+
+ // Norwegian extra vowel characters such as "latin small letter a with ring above"
+ String value = "\u00e6\u00d8\u00e5";
+ String expected = "First line." + lineEnding + "Second line." + lineEnding
+ + "Third line." + lineEnding + value + lineEnding;
+ sb.append(expected);
+ File file = File.createTempFile("getEncodingTest" + encoding + writeBom, ".txt");
+ file.deleteOnExit();
+ BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
+ OutputStreamWriter writer = new OutputStreamWriter(stream, encoding);
+
+ if (writeBom) {
+ String normalized = encoding.toLowerCase(Locale.US).replace("-", "_");
+ if (normalized.equals("utf_8")) {
+ stream.write(0xef);
+ stream.write(0xbb);
+ stream.write(0xbf);
+ } else if (normalized.equals("utf_16")) {
+ stream.write(0xfe);
+ stream.write(0xff);
+ } else if (normalized.equals("utf_16le")) {
+ stream.write(0xff);
+ stream.write(0xfe);
+ } else if (normalized.equals("utf_32")) {
+ stream.write(0x0);
+ stream.write(0x0);
+ stream.write(0xfe);
+ stream.write(0xff);
+ } else if (normalized.equals("utf_32le")) {
+ stream.write(0xff);
+ stream.write(0xfe);
+ stream.write(0x0);
+ stream.write(0x0);
+ } else {
+ fail("Can't write BOM for encoding " + encoding);
+ }
+ }
+ writer.write(sb.toString());
+ writer.close();
+
+ String s = LintUtils.getEncodedString(new LintCliClient(), file);
+ assertEquals(expected, s);
+ }
+
+ public void testGetEncodedString() throws Exception {
+ checkEncoding("utf-8", false /*bom*/, "\n");
+ checkEncoding("UTF-8", false /*bom*/, "\n");
+ checkEncoding("UTF_16", false /*bom*/, "\n");
+ checkEncoding("UTF-16", false /*bom*/, "\n");
+ checkEncoding("UTF_16LE", false /*bom*/, "\n");
+
+ // Try BOM's
+ checkEncoding("utf-8", true /*bom*/, "\n");
+ checkEncoding("UTF-8", true /*bom*/, "\n");
+ checkEncoding("UTF_16", true /*bom*/, "\n");
+ checkEncoding("UTF-16", true /*bom*/, "\n");
+ checkEncoding("UTF_16LE", true /*bom*/, "\n");
+ checkEncoding("UTF_32", true /*bom*/, "\n");
+ checkEncoding("UTF_32LE", true /*bom*/, "\n");
+
+ // Make sure this works for \r and \r\n as well
+ checkEncoding("UTF-16", false /*bom*/, "\r");
+ checkEncoding("UTF_16LE", false /*bom*/, "\r");
+ checkEncoding("UTF-16", false /*bom*/, "\r\n");
+ checkEncoding("UTF_16LE", false /*bom*/, "\r\n");
+ checkEncoding("UTF-16", true /*bom*/, "\r");
+ checkEncoding("UTF_16LE", true /*bom*/, "\r");
+ checkEncoding("UTF_32", true /*bom*/, "\r");
+ checkEncoding("UTF_32LE", true /*bom*/, "\r");
+ checkEncoding("UTF-16", true /*bom*/, "\r\n");
+ checkEncoding("UTF_16LE", true /*bom*/, "\r\n");
+ checkEncoding("UTF_32", true /*bom*/, "\r\n");
+ checkEncoding("UTF_32LE", true /*bom*/, "\r\n");
+ }
+
+ public void testGetLocaleAndRegion() throws Exception {
+ assertNull(getLocaleAndRegion(""));
+ assertNull(getLocaleAndRegion("values"));
+ assertNull(getLocaleAndRegion("values-xlarge-port"));
+ assertEquals("en", getLocaleAndRegion("values-en"));
+ assertEquals("pt-rPT", getLocaleAndRegion("values-pt-rPT-nokeys"));
+ assertEquals("b+pt+PT", getLocaleAndRegion("values-b+pt+PT-nokeys"));
+ assertEquals("zh-rCN", getLocaleAndRegion("values-zh-rCN-keyshidden"));
+ assertEquals("ms", getLocaleAndRegion("values-ms-keyshidden"));
+ }
+
+ public void testIsImported() throws Exception {
+ assertFalse(isImported(getCompilationUnit(
+ "package foo.bar;\n" +
+ "class Foo {\n" +
+ "}\n"),
+ "android.app.Activity"));
+
+ assertTrue(isImported(getCompilationUnit(
+ "package foo.bar;\n" +
+ "import foo.bar.*;\n" +
+ "import android.app.Activity;\n" +
+ "import foo.bar.Baz;\n" +
+ "class Foo {\n" +
+ "}\n"),
+ "android.app.Activity"));
+
+ assertTrue(isImported(getCompilationUnit(
+ "package foo.bar;\n" +
+ "import android.app.Activity;\n" +
+ "class Foo {\n" +
+ "}\n"),
+ "android.app.Activity"));
+
+ assertTrue(isImported(getCompilationUnit(
+ "package foo.bar;\n" +
+ "import android.app.*;\n" +
+ "class Foo {\n" +
+ "}\n"),
+ "android.app.Activity"));
+
+ assertFalse(isImported(getCompilationUnit(
+ "package foo.bar;\n" +
+ "import android.app.*;\n" +
+ "import foo.bar.Activity;\n" +
+ "class Foo {\n" +
+ "}\n"),
+ "android.app.Activity"));
+ }
+
+ public void testComputeResourceName() {
+ assertEquals("", computeResourceName("", ""));
+ assertEquals("foo", computeResourceName("", "foo"));
+ assertEquals("foo", computeResourceName("foo", ""));
+ assertEquals("prefix_name", computeResourceName("prefix_", "name"));
+ assertEquals("prefixName", computeResourceName("prefix", "name"));
+ }
+
+ public static Node getCompilationUnit(@Language("JAVA") String javaSource) {
+ return getCompilationUnit(javaSource, new File("test"));
+ }
+
+
+ public static Node getCompilationUnit(@Language("JAVA") String javaSource, File relativePath) {
+ JavaContext context = parse(javaSource, relativePath);
+ return context.getCompilationUnit();
+ }
+
+ public static JavaContext parse(@Language("JAVA") final String javaSource,
+ final File relativePath) {
+ File dir = new File("projectDir");
+ final File fullPath = new File(dir, relativePath.getPath());
+ LintCliClient client = new LintCliClient() {
+ @NonNull
+ @Override
+ public String readFile(@NonNull File file) {
+ if (file.getPath().equals(fullPath.getPath())) {
+ return javaSource;
+ }
+ return super.readFile(file);
+ }
+
+ @Nullable
+ @Override
+ public IAndroidTarget getCompileTarget(@NonNull Project project) {
+ IAndroidTarget[] targets = getTargets();
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform()) {
+ return target;
+ }
+ }
+
+ return super.getCompileTarget(project);
+ }
+ };
+ Project project = client.getProject(dir, dir);
+
+ LintDriver driver = new LintDriver(new BuiltinIssueRegistry(),
+ new LintCliClient());
+ driver.setScope(Scope.JAVA_FILE_SCOPE);
+ TestContext context = new TestContext(driver, client, project, javaSource, fullPath);
+ JavaParser parser = context.getParser();
+ parser.prepareJavaParse(Collections.<JavaContext>singletonList(context));
+ Node compilationUnit = parser.parseJava(context);
+ assertNotNull(javaSource, compilationUnit);
+ context.setCompilationUnit(compilationUnit);
+ return context;
+ }
+
+ public void testConvertVersion() {
+ assertEquals(new AndroidVersion(5, null), convertVersion(new DefaultApiVersion(5, null),
+ null));
+ assertEquals(new AndroidVersion(19, null), convertVersion(new DefaultApiVersion(19, null),
+ null));
+ //noinspection SpellCheckingInspection
+ assertEquals(new AndroidVersion(18, "KITKAT"), // a preview platform API level is not final
+ convertVersion(new DefaultApiVersion(0, "KITKAT"),
+ null));
+ }
+
+ public void testIsModelOlderThan() throws Exception {
+ AndroidProject project = mock(AndroidProject.class);
+ when(project.getModelVersion()).thenReturn("0.10.4");
+
+ assertTrue(LintUtils.isModelOlderThan(project, 0, 10, 5));
+ assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 0));
+ assertTrue(LintUtils.isModelOlderThan(project, 0, 11, 4));
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
+
+ project = mock(AndroidProject.class);
+ when(project.getModelVersion()).thenReturn("0.11.0");
+
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 10, 4));
+
+ project = mock(AndroidProject.class);
+ when(project.getModelVersion()).thenReturn("0.11.5");
+
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
+
+ project = mock(AndroidProject.class);
+ when(project.getModelVersion()).thenReturn("1.0.0");
+
+ assertTrue(LintUtils.isModelOlderThan(project, 1, 0, 1));
+ assertFalse(LintUtils.isModelOlderThan(project, 1, 0, 0));
+ assertFalse(LintUtils.isModelOlderThan(project, 0, 11, 0));
+ }
+
+ private static final class DefaultApiVersion implements ApiVersion {
+ private final int mApiLevel;
+ private final String mCodename;
+
+ public DefaultApiVersion(int apiLevel, @Nullable String codename) {
+ mApiLevel = apiLevel;
+ mCodename = codename;
+ }
+
+ @Override
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getCodename() {
+ return mCodename;
+ }
+
+ @NonNull
+ @Override
+ public String getApiString() {
+ fail("Not needed in this test");
+ return "<invalid>";
+ }
+ }
+
+ public void testFindSubstring() {
+ assertEquals("foo", findSubstring("foo", null, null));
+ assertEquals("foo", findSubstring("foo ", null, " "));
+ assertEquals("foo", findSubstring(" foo", " ", null));
+ assertEquals("foo", findSubstring("[foo]", "[", "]"));
+ }
+
+ public void testGetFormattedParameters() {
+ assertEquals(Arrays.asList("foo","bar"),
+ getFormattedParameters("Prefix %1$s Divider %2$s Suffix",
+ "Prefix foo Divider bar Suffix"));
+ }
+
+ public void testEscapePropertyValue() throws Exception {
+ assertEquals("foo", escapePropertyValue("foo"));
+ assertEquals("\\ foo ", escapePropertyValue(" foo "));
+ assertEquals("c\\:/foo/bar", escapePropertyValue("c:/foo/bar"));
+ assertEquals("\\!\\#\\:\\\\a\\\\b\\\\c", escapePropertyValue("!#:\\a\\b\\c"));
+ assertEquals(
+ "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo\\#foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
+ escapePropertyValue("foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo#foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo"));
+ }
+
+ private static class TestContext extends JavaContext {
+ private final String mJavaSource;
+ public TestContext(LintDriver driver, LintCliClient client, Project project,
+ String javaSource, File file) {
+ //noinspection ConstantConditions
+ super(driver, project,
+ null, file, client.getJavaParser(project));
+
+ mJavaSource = javaSource;
+ }
+
+ @Override
+ @Nullable
+ public String getContents() {
+ return mJavaSource;
+ }
+ }
+}
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LocationTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LocationTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LocationTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/LocationTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ScopeTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ScopeTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ScopeTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/ScopeTest.java
diff --git a/base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/SeverityTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/SeverityTest.java
similarity index 100%
rename from base/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/SeverityTest.java
rename to lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/SeverityTest.java
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java
new file mode 100644
index 0000000..577e05d
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TextFormatTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import static com.android.SdkConstants.AUTO_URI;
+import static com.android.tools.lint.detector.api.TextFormat.HTML;
+import static com.android.tools.lint.detector.api.TextFormat.HTML_WITH_UNICODE;
+import static com.android.tools.lint.detector.api.TextFormat.RAW;
+import static com.android.tools.lint.detector.api.TextFormat.TEXT;
+
+import junit.framework.TestCase;
+
+public class TextFormatTest extends TestCase {
+ private static String convertMarkup(String raw, TextFormat to) {
+ return RAW.convertTo(raw, to);
+ }
+
+ public void testConvertMarkup() throws Exception {
+ assertEquals("", convertMarkup("", HTML));
+
+ // Normal escapes
+ assertEquals("foo bar", convertMarkup("foo bar", HTML));
+ assertEquals("foo<br/>\nbar", convertMarkup("foo\nbar", HTML));
+ assertEquals("foo<br/>\nbar", convertMarkup("foo\nbar", HTML));
+ assertEquals("<&>'\"", convertMarkup("<&>'\"", HTML));
+ assertEquals("<&>'\"", convertMarkup("<&>'\"", HTML_WITH_UNICODE));
+ assertEquals("a\u1234\u2345bc", HTML.convertTo("aሴ⍅bc", HTML_WITH_UNICODE));
+
+ // Unicode
+ assertEquals("abcሴ⍅def<",
+ convertMarkup("abc\u1234\u2345def<", HTML));
+ assertEquals("abc\u1234\u2345def<",
+ convertMarkup("abc\u1234\u2345def<", HTML_WITH_UNICODE));
+
+ // HTML Formatting
+ assertEquals("<code>@TargetApi(11)</code>, ", convertMarkup("`@TargetApi(11)`, ",
+ HTML));
+ assertEquals("with <code>getArguments()</code>.",
+ convertMarkup("with `getArguments()`.", HTML));
+ assertEquals("(<code>dip</code>)", convertMarkup("(`dip`)", HTML));
+ assertEquals(" <code>0dp</code> ", convertMarkup(" `0dp` ", HTML));
+ assertEquals(
+ "resources under <code>$ANDROID_SK/platforms/android-$VERSION/data/res/.</code>",
+ convertMarkup(
+ "resources under `$ANDROID_SK/platforms/android-$VERSION/data/res/.`",
+ HTML));
+ assertEquals("wrong format. Instead of <code>-keepclasseswithmembernames</code> use ",
+ convertMarkup("wrong format. Instead of `-keepclasseswithmembernames` use ",
+ HTML));
+ assertEquals("<code>exported=false</code>)", convertMarkup("`exported=false`)",
+ HTML));
+ assertEquals("by setting <code>inputType=\"text\"</code>.",
+ convertMarkup("by setting `inputType=\"text\"`.", HTML));
+ assertEquals("* <code>View(Context context)</code><br/>\n",
+ convertMarkup("* `View(Context context)`\n", HTML));
+ assertEquals("The <code>@+id/</code> syntax", convertMarkup("The `@+id/` syntax",
+ HTML));
+ assertEquals("", convertMarkup("", HTML));
+ assertEquals("", convertMarkup("", HTML));
+ assertEquals("This is <b>bold</b>", convertMarkup("This is *bold*", HTML));
+ assertEquals("Visit <a href=\"http://google.com\">http://google.com</a>.",
+ convertMarkup("Visit http://google.com.", HTML));
+ assertEquals("This is <code>monospace</code>!", convertMarkup("This is `monospace`!",
+ HTML));
+ assertEquals(
+ "See <a href=\"http://developer.android.com/reference/android/view/" +
+ "WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON\">http://developer." +
+ "android.com/reference/android/view/WindowManager.LayoutParams.html#" +
+ "FLAG_KEEP_SCREEN_ON</a>.",
+ convertMarkup(
+ "See http://developer.android.com/reference/android/view/WindowManager.Layout" +
+ "Params.html#FLAG_KEEP_SCREEN_ON.", HTML));
+
+ // Text formatting
+ assertEquals("@TargetApi(11), ", convertMarkup("`@TargetApi(11)`, ", TEXT));
+ assertEquals("with getArguments().", convertMarkup("with `getArguments()`.", TEXT));
+ assertEquals("bold", convertMarkup("*bold*", TEXT));
+ assertEquals("Visit http://google.com.", convertMarkup("Visit http://google.com.",
+ TEXT));
+
+ // Corners (match at the beginning and end)
+ assertEquals("<b>bold</b>", convertMarkup("*bold*", HTML));
+ assertEquals("<code>monospace</code>!", convertMarkup("`monospace`!", HTML));
+
+ // Not formatting
+ assertEquals("a*b", convertMarkup("a*b", HTML));
+ assertEquals("a* b*", convertMarkup("a* b*", HTML));
+ assertEquals("*a *b", convertMarkup("*a *b", HTML));
+ assertEquals("Prefix is http:// ", convertMarkup("Prefix is http:// ", HTML));
+ assertEquals("", convertMarkup("", HTML));
+ assertEquals("", convertMarkup("", HTML));
+ assertEquals("", convertMarkup("", HTML));
+ assertEquals("", convertMarkup("", HTML));
+ assertEquals("This is * not * bold", convertMarkup("This is * not * bold", HTML));
+ assertEquals("* List item 1<br/>\n* List Item 2",
+ convertMarkup("* List item 1\n* List Item 2", HTML));
+ assertEquals("myhttp://foo.bar", convertMarkup("myhttp://foo.bar", HTML));
+ }
+
+ public void testConvertMarkup2() throws Exception {
+ // http at the end:
+ // Explanation from ManifestDetector#TARGET_NEWER
+ String explanation =
+ "When your application runs on a version of Android that is more recent than your " +
+ "targetSdkVersion specifies that it has been tested with, various compatibility " +
+ "modes kick in. This ensures that your application continues to work, but it may " +
+ "look out of place. For example, if the targetSdkVersion is less than 14, your " +
+ "app may get an option button in the UI.\n" +
+ "\n" +
+ "To fix this issue, set the targetSdkVersion to the highest available value. Then " +
+ "test your app to make sure everything works correctly. You may want to consult " +
+ "the compatibility notes to see what changes apply to each version you are adding " +
+ "support for: " +
+ "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html";
+
+ assertEquals(
+ "When your application runs on a version of Android that is more recent than your " +
+ "targetSdkVersion specifies that it has been tested with, various compatibility " +
+ "modes kick in. This ensures that your application continues to work, but it may " +
+ "look out of place. For example, if the targetSdkVersion is less than 14, your " +
+ "app may get an option button in the UI.<br/>\n" +
+ "<br/>\n" +
+ "To fix this issue, set the targetSdkVersion to the highest available value. Then " +
+ "test your app to make sure everything works correctly. You may want to consult " +
+ "the compatibility notes to see what changes apply to each version you are adding " +
+ "support for: " +
+ "<a href=\"http://developer.android.com/reference/android/os/Build.VERSION_CODES." +
+ "html\">http://developer.android.com/reference/android/os/Build.VERSION_CODES.html" +
+ "</a>",
+ convertMarkup(explanation, HTML));
+ }
+
+ public void testConvertMarkup3() throws Exception {
+ // embedded http markup test
+ // Explanation from NamespaceDetector#CUSTOMVIEW
+ String explanation =
+ "When using a custom view with custom attributes in a library project, the layout " +
+ "must use the special namespace " + AUTO_URI + " instead of a URI which includes " +
+ "the library project's own package. This will be used to automatically adjust the " +
+ "namespace of the attributes when the library resources are merged into the " +
+ "application project.";
+ assertEquals(
+ "When using a custom view with custom attributes in a library project, the layout " +
+ "must use the special namespace " +
+ "<a href=\"http://schemas.android.com/apk/res-auto\">" +
+ "http://schemas.android.com/apk/res-auto</a> " +
+ "instead of a URI which includes the library project's own package. " +
+ "This will be used to automatically adjust the namespace of the attributes when " +
+ "the library resources are merged into the application project.",
+ convertMarkup(explanation, HTML));
+ }
+
+ public void testConvertMarkup4() throws Exception {
+ // monospace test
+ String explanation =
+ "The manifest should contain a `<uses-sdk>` element which defines the " +
+ "minimum minimum API Level required for the application to run, " +
+ "as well as the target version (the highest API level you have tested " +
+ "the version for.)";
+
+ assertEquals(
+ "The manifest should contain a <code><uses-sdk></code> element which defines the " +
+ "minimum minimum API Level required for the application to run, " +
+ "as well as the target version (the highest API level you have tested " +
+ "the version for.)",
+ convertMarkup(explanation, HTML));
+ }
+
+ public void testConvertMarkup5() throws Exception {
+ // monospace and bold test
+ // From ManifestDetector#MULTIPLE_USES_SDK
+ String explanation =
+ "The `<uses-sdk>` element should appear just once; the tools will *not* merge the " +
+ "contents of all the elements so if you split up the attributes across multiple " +
+ "elements, only one of them will take effect. To fix this, just merge all the " +
+ "attributes from the various elements into a single <uses-sdk> element.";
+
+ assertEquals(
+ "The <code><uses-sdk></code> element should appear just once; the tools " +
+ "will <b>not</b> merge the " +
+ "contents of all the elements so if you split up the attributes across multiple " +
+ "elements, only one of them will take effect. To fix this, just merge all the " +
+ "attributes from the various elements into a single <uses-sdk> element.",
+ convertMarkup(explanation, HTML));
+ }
+
+ public void testConvertMarkup6() throws Exception {
+ // Embedded code next to attributes
+ // From AlwaysShowActionDetector#ISSUE
+ String explanation =
+ "Using `showAsAction=\"always\"` in menu XML, or `MenuItem.SHOW_AS_ACTION_ALWAYS` in "+
+ "Java code is usually a deviation from the user interface style guide." +
+ "Use `ifRoom` or the corresponding `MenuItem.SHOW_AS_ACTION_IF_ROOM` instead.\n" +
+ "\n" +
+ "If `always` is used sparingly there are usually no problems and behavior is " +
+ "roughly equivalent to `ifRoom` but with preference over other `ifRoom` " +
+ "items. Using it more than twice in the same menu is a bad idea.\n" +
+ "\n" +
+ "This check looks for menu XML files that contain more than two `always` " +
+ "actions, or some `always` actions and no `ifRoom` actions. In Java code, " +
+ "it looks for projects that contain references to `MenuItem.SHOW_AS_ACTION_ALWAYS` " +
+ "and no references to `MenuItem.SHOW_AS_ACTION_IF_ROOM`.";
+
+ assertEquals(
+ "Using <code>showAsAction=\"always\"</code> in menu XML, or " +
+ "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> in Java code is usually a deviation " +
+ "from the user interface style guide.Use <code>ifRoom</code> or the " +
+ "corresponding <code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code> instead.<br/>\n" +
+ "<br/>\n" +
+ "If <code>always</code> is used sparingly there are usually no problems and " +
+ "behavior is roughly equivalent to <code>ifRoom</code> but with preference over " +
+ "other <code>ifRoom</code> items. Using it more than twice in the same menu " +
+ "is a bad idea.<br/>\n" +
+ "<br/>\n" +
+ "This check looks for menu XML files that contain more than two <code>always</code> " +
+ "actions, or some <code>always</code> actions and no <code>ifRoom</code> actions. " +
+ "In Java code, it looks for projects that contain references to " +
+ "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> and no references to " +
+ "<code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code>.",
+ convertMarkup(explanation, HTML));
+ }
+
+ public void testConvertSelf() throws Exception {
+ // No changes
+ assertEquals("`foo`<b>", RAW.convertTo("`foo`<b>", RAW));
+ assertEquals("`foo`<b>", TEXT.convertTo("`foo`<b>", TEXT));
+ assertEquals("`foo`<b>", HTML.convertTo("`foo`<b>", HTML));
+ }
+
+ public void testConvertFromHtml() throws Exception {
+ assertEquals(""
+ + "Line 1\n"
+ + "Line 2 <div>\n",
+ HTML.convertTo("<html>Line 1<br>Line 2\n<!-- comment --><div></html>",
+ TEXT));
+ }
+
+ public void testConvertFromHtml2() throws Exception {
+ assertEquals(""
+ + "Using showAsAction=\"always\" in menu XML, or\n"
+ + "MenuItem.SHOW_AS_ACTION_ALWAYS in Java code is usually a\n"
+ + "deviation from the user interface style guide.Use ifRoom or\n"
+ + "the corresponding MenuItem.SHOW_AS_ACTION_IF_ROOM instead.\n"
+ + "If always is used sparingly there are usually no problems\n"
+ + "and behavior is roughly equivalent to ifRoom but with\n"
+ + "preference over other ifRoom items. Using it more than twice\n"
+ + "in the same menu is a bad idea. This check looks for menu\n"
+ + "XML files that contain more than two always actions, or some\n"
+ + "always actions and no ifRoom actions. In Java code, it looks\n"
+ + "for projects that contain references to\n"
+ + "MenuItem.SHOW_AS_ACTION_ALWAYS and no references to\n"
+ + "MenuItem.SHOW_AS_ACTION_IF_ROOM.\n",
+ HTML.convertTo(
+ "Using <code>showAsAction=\"always\"</code> in menu XML, or " +
+ "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> in Java code is usually a deviation " +
+ "from the user interface style guide.Use <code>ifRoom</code> or the " +
+ "corresponding <code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code> instead.<br/>\n" +
+ "<br/>\n" +
+ "If <code>always</code> is used sparingly there are usually no problems and " +
+ "behavior is roughly equivalent to <code>ifRoom</code> but with preference over " +
+ "other <code>ifRoom</code> items. Using it more than twice in the same menu " +
+ "is a bad idea.<br/>\n" +
+ "<br/>\n" +
+ "This check looks for menu XML files that contain more than two <code>always</code> " +
+ "actions, or some <code>always</code> actions and no <code>ifRoom</code> actions. " +
+ "In Java code, it looks for projects that contain references to " +
+ "<code>MenuItem.SHOW_AS_ACTION_ALWAYS</code> and no references to " +
+ "<code>MenuItem.SHOW_AS_ACTION_IF_ROOM</code>.",
+ TEXT));
+ }
+
+ public void testNbsp() throws Exception {
+ assertEquals(" text", RAW.convertTo("\u00a0\u00A0text", HTML));
+ }
+
+ public void test181820() throws Exception {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=181820
+ // Make sure we handle formatting characters at the end
+ assertEquals("foo bar *", convertMarkup("foo bar *", HTML));
+ }
+}
\ No newline at end of file
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TypeEvaluatorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TypeEvaluatorTest.java
new file mode 100644
index 0000000..0bdcb88
--- /dev/null
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/detector/api/TypeEvaluatorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.detector.api;
+
+import com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
+
+import junit.framework.TestCase;
+
+import org.intellij.lang.annotations.Language;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicReference;
+
+import lombok.ast.CompilationUnit;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.VariableDefinitionEntry;
+
+ at SuppressWarnings("ClassNameDiffersFromFileName")
+public class TypeEvaluatorTest extends TestCase {
+ private static void check(Object expected, @Language("JAVA") String source,
+ final String targetVariable) {
+ JavaContext context = LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
+ assertNotNull(context);
+ CompilationUnit unit = (CompilationUnit) context.getCompilationUnit();
+ assertNotNull(unit);
+
+ // Find the expression
+ final AtomicReference<Expression> reference = new AtomicReference<Expression>();
+ unit.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ if (node.astName().astValue().equals(targetVariable)) {
+ reference.set(node.astInitializer());
+ }
+ return super.visitVariableDefinitionEntry(node);
+ }
+ });
+ Expression expression = reference.get();
+ TypeDescriptor actual = TypeEvaluator.evaluate(context, expression);
+ if (expected == null) {
+ assertNull(actual);
+ } else {
+ assertNotNull("Couldn't compute type for " + source + ", expected " + expected,
+ actual);
+ String expectedString = expected.toString();
+ if (expectedString.startsWith("class ")) {
+ expectedString = expectedString.substring("class ".length());
+ }
+ assertEquals(expectedString, actual.getName());
+ }
+ }
+
+ private static void checkStatements(Object expected, String statementsSource,
+ final String targetVariable) {
+ @Language("JAVA")
+ String source = ""
+ + "package test.pkg;\n"
+ + "public class Test {\n"
+ + " public void test() {\n"
+ + " " + statementsSource + "\n"
+ + " }\n"
+ + " public static final int MY_INT_FIELD = 5;\n"
+ + " public static final boolean MY_BOOLEAN_FIELD = true;\n"
+ + " public static final String MY_STRING_FIELD = \"test\";\n"
+ + " public static final String MY_OBJECT_FIELD = \"test\";\n"
+ + "}\n";
+
+ check(expected, source, targetVariable);
+ }
+
+ private static void checkExpression(Object expected, String expressionSource) {
+ @Language("JAVA")
+ String source = ""
+ + "package test.pkg;\n"
+ + "public class Test {\n"
+ + " public void test() {\n"
+ + " Object expression = " + expressionSource + ";\n"
+ + " }\n"
+ + " public static final int MY_INT_FIELD = 5;\n"
+ + " public static final boolean MY_BOOLEAN_FIELD = true;\n"
+ + " public static final String MY_STRING_FIELD = \"test\";\n"
+ + " public static final String MY_OBJECT_FIELD = \"test\";\n"
+ + "}\n";
+
+ check(expected, source, "expression");
+ }
+
+ public void testNull() throws Exception {
+ checkExpression(null, "null");
+ }
+
+ public void testStrings() throws Exception {
+ checkExpression(String.class, "\"hello\"");
+ checkExpression(String.class, "\"ab\" + \"cd\"");
+ }
+
+ public void testBooleans() throws Exception {
+ checkExpression(Boolean.TYPE, "true");
+ checkExpression(Boolean.TYPE, "false");
+ checkExpression(Boolean.TYPE, "false && true");
+ checkExpression(Boolean.TYPE, "false || true");
+ checkExpression(Boolean.TYPE, "!false");
+ }
+
+ public void testCasts() throws Exception {
+ checkExpression(Integer.TYPE, "(int)1");
+ checkExpression(Long.TYPE, "(long)1");
+ checkExpression(Integer.TYPE, "(int)1.1f");
+ checkExpression(Short.TYPE, "(short)65537");
+ checkExpression(Byte.TYPE, "(byte)1023");
+ checkExpression(Double.TYPE, "(double)1.5f");
+ checkExpression(Double.TYPE, "(double)-5");
+ }
+
+ public void testArithmetic() throws Exception {
+ checkExpression(Integer.TYPE, "1");
+ checkExpression(Long.TYPE, "1L");
+ checkExpression(Integer.TYPE, "1 + 3");
+ checkExpression(Integer.TYPE, "1 - 3");
+ checkExpression(Integer.TYPE, "2 * 5");
+ checkExpression(Integer.TYPE, "10 / 5");
+ checkExpression(Integer.TYPE, "11 % 5");
+ checkExpression(Integer.TYPE, "1 << 3");
+ checkExpression(Integer.TYPE, "32 >> 1");
+ checkExpression(Integer.TYPE, "32 >>> 1");
+ checkExpression(Integer.TYPE, "5 | 1");
+ checkExpression(Integer.TYPE, "5 & 1");
+ checkExpression(Integer.TYPE, "~5");
+ checkExpression(Long.TYPE, "~(long)5");
+ checkExpression(Short.TYPE, "~(short)5");
+ checkExpression(Byte.TYPE, "~(byte)5");
+ checkExpression(Long.TYPE, "-(long)5");
+ checkExpression(Short.TYPE, "-(short)5");
+ checkExpression(Byte.TYPE, "-(byte)5");
+ checkExpression(Double.TYPE, "-(double)5");
+ checkExpression(Float.TYPE, "-(float)5");
+ checkExpression(Integer.TYPE, "1 + -3");
+
+ checkExpression(Boolean.TYPE, "11 == 5");
+ checkExpression(Boolean.TYPE, "11 == 11");
+ checkExpression(Boolean.TYPE, "11 != 5");
+ checkExpression(Boolean.TYPE, "11 != 11");
+ checkExpression(Boolean.TYPE, "11 > 5");
+ checkExpression(Boolean.TYPE, "5 > 11");
+ checkExpression(Boolean.TYPE, "11 < 5");
+ checkExpression(Boolean.TYPE, "5 < 11");
+ checkExpression(Boolean.TYPE, "11 >= 5");
+ checkExpression(Boolean.TYPE, "5 >= 11");
+ checkExpression(Boolean.TYPE, "11 <= 5");
+ checkExpression(Boolean.TYPE, "5 <= 11");
+
+ checkExpression(Float.TYPE, "1.0f + 2.5f");
+ }
+
+ public void testFieldReferences() throws Exception {
+ checkExpression(Integer.TYPE, "MY_INT_FIELD");
+ checkExpression(String.class, "MY_STRING_FIELD");
+ checkExpression(String.class, "\"prefix-\" + MY_STRING_FIELD + \"-postfix\"");
+ checkExpression(Integer.TYPE, "3 - (MY_INT_FIELD + 2)");
+ }
+
+ public void testStatements() throws Exception {
+ checkStatements(Integer.TYPE, ""
+ + "int x = +5;\n"
+ + "int y = x;\n"
+ + "int w;\n"
+ + "w = -1;\n"
+ + "int z = x + 5 + w;\n",
+ "z");
+ checkStatements(String.class, ""
+ + "String initial = \"hello\";\n"
+ + "String other;\n"
+ + "other = \" world\";\n"
+ + "String finalString = initial + other;\n",
+ "finalString");
+ }
+
+ public void testConditionals() throws Exception {
+ checkStatements(Integer.TYPE, ""
+ + "boolean condition = false;\n"
+ + "condition = !condition;\n"
+ + "int z = condition ? -5 : 4;\n",
+ "z");
+ checkStatements(Integer.TYPE, ""
+ + "boolean condition = true && false;\n"
+ + "int z = condition ? 5 : -4;\n",
+ "z");
+ }
+
+ public void testConstructorInvocation() throws Exception {
+ checkStatements(String.class, ""
+ + "Object o = new String(\"test\");\n"
+ + "Object bar = o;\n",
+ "bar");
+ }
+
+ public void testFieldInitializerType() throws Exception {
+ checkStatements(String.class, ""
+ + "Object o = MY_OBJECT_FIELD;\n"
+ + "Object bar = o;\n",
+ "bar");
+ }
+}
\ No newline at end of file
diff --git a/base/misc/annotations/src/android/annotation/SuppressLint.java b/misc/annotations/src/android/annotation/SuppressLint.java
similarity index 100%
rename from base/misc/annotations/src/android/annotation/SuppressLint.java
rename to misc/annotations/src/android/annotation/SuppressLint.java
diff --git a/base/misc/annotations/src/android/annotation/TargetApi.java b/misc/annotations/src/android/annotation/TargetApi.java
similarity index 100%
rename from base/misc/annotations/src/android/annotation/TargetApi.java
rename to misc/annotations/src/android/annotation/TargetApi.java
diff --git a/base/misc/api-generator/.gitignore b/misc/api-generator/.gitignore
similarity index 100%
rename from base/misc/api-generator/.gitignore
rename to misc/api-generator/.gitignore
diff --git a/misc/api-generator/build.gradle b/misc/api-generator/build.gradle
new file mode 100755
index 0000000..2973019
--- /dev/null
+++ b/misc/api-generator/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'application'
+apply plugin: 'java'
+
+mainClassName = "com.android.apigenerator.Main"
+applicationDefaultJvmArgs = ["-ea", "-Xms1048m", "-Xmx2048m"]
+
+sourceCompatibility = 1.6
+dependencies {
+ compile project(':base:common')
+ compile 'net.sf.kxml:kxml2:2.3.0'
+ compile 'org.ow2.asm:asm:4.0'
+ compile 'org.ow2.asm:asm-tree:4.0'
+}
+
+defaultTasks 'installApp'
diff --git a/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java b/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java
new file mode 100644
index 0000000..58e37ff
--- /dev/null
+++ b/misc/api-generator/src/main/java/com/android/apigenerator/AndroidJarReader.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apigenerator;
+
+import com.android.SdkConstants;
+import com.android.utils.Pair;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}.
+ *
+ */
+public class AndroidJarReader {
+ private final int mMinApi;
+ private final int mCurrentApi;
+ private final File mCurrentJar;
+ private final List<String> mPatterns;
+
+ public AndroidJarReader(List<String> patterns, int minApi, File currentJar, int currentApi) {
+ mPatterns = patterns;
+ mMinApi = minApi;
+ mCurrentJar = currentJar;
+ mCurrentApi = currentApi;
+ }
+
+ public Map<String, ApiClass> getClasses() {
+ HashMap<String, ApiClass> map = new HashMap<String, ApiClass>();
+
+ // Get all the android.jar. They are in platforms-#
+ int apiLevel = mMinApi - 1;
+ while (true) {
+ apiLevel++;
+ try {
+ File jar = null;
+ if (apiLevel == mCurrentApi) {
+ jar = mCurrentJar;
+ }
+ if (jar == null) {
+ jar = getAndroidJarFile(apiLevel);
+ }
+ if (jar == null || !jar.isFile()) {
+ System.out.println("Last API level found: " + (apiLevel-1));
+ break;
+ }
+ System.out.println("Found API " + apiLevel + " at " + jar.getPath());
+
+ FileInputStream fis = new FileInputStream(jar);
+ ZipInputStream zis = new ZipInputStream(fis);
+ ZipEntry entry = zis.getNextEntry();
+ while (entry != null) {
+ String name = entry.getName();
+
+ if (name.endsWith(SdkConstants.DOT_CLASS)) {
+ byte[] bytes = ByteStreams.toByteArray(zis);
+ if (bytes == null) {
+ System.err.println("Warning: Couldn't read " + name);
+ entry = zis.getNextEntry();
+ continue;
+ }
+
+ ClassReader reader = new ClassReader(bytes);
+ ClassNode classNode = new ClassNode();
+ reader.accept(classNode, 0 /*flags*/);
+
+ ApiClass theClass = addClass(map, classNode.name, apiLevel,
+ (classNode.access & Opcodes.ACC_DEPRECATED) != 0);
+
+ // super class
+ if (classNode.superName != null) {
+ theClass.addSuperClass(classNode.superName, apiLevel);
+ }
+
+ // interfaces
+ for (Object interfaceName : classNode.interfaces) {
+ theClass.addInterface((String) interfaceName, apiLevel);
+ }
+
+ // fields
+ for (Object field : classNode.fields) {
+ FieldNode fieldNode = (FieldNode) field;
+ if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) {
+ continue;
+ }
+ if (!fieldNode.name.startsWith("this$") &&
+ !fieldNode.name.equals("$VALUES")) {
+ boolean deprecated = (fieldNode.access & Opcodes.ACC_DEPRECATED) != 0;
+ theClass.addField(fieldNode.name, apiLevel, deprecated);
+ }
+ }
+
+ // methods
+ for (Object method : classNode.methods) {
+ MethodNode methodNode = (MethodNode) method;
+ if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) {
+ continue;
+ }
+ if (!methodNode.name.equals("<clinit>")) {
+ boolean deprecated = (methodNode.access & Opcodes.ACC_DEPRECATED) != 0;
+ theClass.addMethod(methodNode.name + methodNode.desc, apiLevel, deprecated);
+ }
+ }
+ }
+ entry = zis.getNextEntry();
+ }
+
+ Closeables.close(fis, true);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ postProcessClasses(map);
+
+ return map;
+ }
+
+ private File getAndroidJarFile(int apiLevel) {
+ for (String pattern : mPatterns) {
+ File f = new File(pattern.replace("%", Integer.toString(apiLevel)));
+ if (f.isFile()) {
+ return f;
+ }
+ }
+ return null;
+ }
+
+ private void postProcessClasses(Map<String, ApiClass> classes) {
+ for (ApiClass theClass : classes.values()) {
+ Map<String, Integer> methods = theClass.getMethods();
+ Map<String, Integer> fixedMethods = new HashMap<String, Integer>();
+
+ List<Pair<String, Integer>> superClasses = theClass.getSuperClasses();
+ List<Pair<String, Integer>> interfaces = theClass.getInterfaces();
+
+ methodLoop: for (Entry<String, Integer> method : methods.entrySet()) {
+ String methodName = method.getKey();
+ int apiLevel = method.getValue();
+
+ if (!methodName.startsWith("<init>(")) {
+
+ for (Pair<String, Integer> parent : superClasses) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass parentClass = classes.get(parent.getFirst());
+ assert parentClass != null;
+ if (parentClass != null &&
+ checkClassContains(theClass.getName(),
+ methodName, apiLevel,
+ classes, parentClass)) {
+ continue methodLoop;
+ }
+ }
+ }
+
+ for (Pair<String, Integer> parent : interfaces) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass parentClass = classes.get(parent.getFirst());
+ assert parentClass != null;
+ if (parentClass != null &&
+ checkClassContains(theClass.getName(),
+ methodName, apiLevel,
+ classes, parentClass)) {
+ continue methodLoop;
+ }
+ }
+ }
+ }
+
+ // if we reach here. the method isn't an override
+ fixedMethods.put(methodName, method.getValue());
+ }
+
+ theClass.replaceMethods(fixedMethods);
+ }
+ }
+
+ private boolean checkClassContains(String className, String methodName, int apiLevel,
+ Map<String, ApiClass> classMap, ApiClass parentClass) {
+
+ Integer parentMethodApiLevel = parentClass.getMethods().get(methodName);
+ if (parentMethodApiLevel != null && parentMethodApiLevel <= apiLevel) {
+ // the parent class has the method and it was introduced in the parent at the
+ // same api level as the method, or before.
+ return true;
+ }
+
+ // check on this class parents.
+ List<Pair<String, Integer>> superClasses = parentClass.getSuperClasses();
+ List<Pair<String, Integer>> interfaces = parentClass.getInterfaces();
+
+ for (Pair<String, Integer> parent : superClasses) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass superParentClass = classMap.get(parent.getFirst());
+ assert superParentClass != null;
+ if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
+ classMap, superParentClass)) {
+ return true;
+ }
+ }
+ }
+
+ for (Pair<String, Integer> parent : interfaces) {
+ // only check the parent if it was a parent class at the introduction
+ // of the method.
+ if (parent.getSecond() <= apiLevel) {
+ ApiClass superParentClass = classMap.get(parent.getFirst());
+ assert superParentClass != null;
+ if (superParentClass != null && checkClassContains(className, methodName, apiLevel,
+ classMap, superParentClass)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private ApiClass addClass(HashMap<String, ApiClass> classes, String name, int apiLevel, boolean deprecated) {
+ ApiClass theClass = classes.get(name);
+ if (theClass == null) {
+ theClass = new ApiClass(name, apiLevel);
+ classes.put(name, theClass);
+ }
+
+ if (deprecated && apiLevel < theClass.deprecatedIn) {
+ theClass.deprecatedIn = apiLevel;
+ }
+
+ return theClass;
+ }
+}
diff --git a/base/misc/api-generator/src/main/java/com/android/apigenerator/ApiClass.java b/misc/api-generator/src/main/java/com/android/apigenerator/ApiClass.java
similarity index 100%
rename from base/misc/api-generator/src/main/java/com/android/apigenerator/ApiClass.java
rename to misc/api-generator/src/main/java/com/android/apigenerator/ApiClass.java
diff --git a/misc/api-generator/src/main/java/com/android/apigenerator/Main.java b/misc/api-generator/src/main/java/com/android/apigenerator/Main.java
new file mode 100644
index 0000000..203d065
--- /dev/null
+++ b/misc/api-generator/src/main/java/com/android/apigenerator/Main.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apigenerator;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Main class for command line command to convert the existing API XML/TXT files into diff-based
+ * simple text files.
+ */
+public class Main {
+ public static void main(String[] args) {
+
+ boolean error = false;
+ int minApi = 1;
+ int currentApi = -1;
+ String currentCodenName = null;
+ File currentJar = null;
+ List<String> patterns = new ArrayList<String>();
+ String outPath = null;
+
+ for (int i = 0; i < args.length && !error; i++) {
+ String arg = args[i];
+
+ if (arg.equals("--pattern")) {
+ i++;
+ if (i < args.length) {
+ patterns.add(args[i]);
+ } else {
+ System.err.println("Missing argument after " + arg);
+ error = true;
+ }
+ } else if (arg.equals("--current-version")) {
+ i++;
+ if (i < args.length) {
+ currentApi = Integer.parseInt(args[i]);
+ if (currentApi <= 22) {
+ System.err.println("Suspicious currentApi=" + currentApi + ", expected at least 23");
+ error = true;
+ }
+ } else {
+ System.err.println("Missing number >= 1 after " + arg);
+ error = true;
+ }
+ } else if (arg.equals("--current-codename")) {
+ i++;
+ if (i < args.length) {
+ currentCodenName = args[i];
+ } else {
+ System.err.println("Missing codename after " + arg);
+ error = true;
+ }
+ } else if (arg.equals("--current-jar")) {
+ i++;
+ if (i < args.length) {
+ if (currentJar != null) {
+ System.err.println("--current-jar should only be specified once");
+ error = true;
+ }
+ String path = args[i];
+ currentJar = new File(path);
+ } else {
+ System.err.println("Missing argument after " + arg);
+ error = true;
+ }
+ } else if (arg.equals("--min-api")) {
+ i++;
+ if (i < args.length) {
+ minApi = Integer.parseInt(args[i]);
+ } else {
+ System.err.println("Missing number >= 1 after " + arg);
+ error = true;
+ }
+ } else if (arg.length() >= 2 && arg.substring(0, 2).equals("--")) {
+ System.err.println("Unknown argument: " + arg);
+ error = true;
+
+ } else if (outPath == null) {
+ outPath = arg;
+
+ } else if (new File(arg).isDirectory()) {
+ String pattern = arg;
+ if (!pattern.endsWith(File.separator)) {
+ pattern += File.separator;
+ }
+ pattern += "platforms" + File.separator + "android-%" + File.separator + "android.jar";
+ patterns.add(pattern);
+
+ } else {
+ System.err.println("Unknown argument: " + arg);
+ error = true;
+ }
+ }
+
+ if (!error && outPath == null) {
+ System.err.println("Missing out file path");
+ error = true;
+ }
+
+ if (!error && patterns.isEmpty()) {
+ System.err.println("Missing SdkFolder or --pattern.");
+ error = true;
+ }
+
+ if (currentJar != null && currentApi == -1 || currentJar == null && currentApi != -1) {
+ System.err.println("You must specify both --current-jar and --current-version (or neither one)");
+ error = true;
+ }
+
+ // The SDK version number
+ if (currentCodenName != null && !"REL".equals(currentCodenName)) {
+ currentApi++;
+ }
+
+ if (error) {
+ printUsage();
+ System.exit(1);
+ }
+
+ AndroidJarReader reader = new AndroidJarReader(patterns, minApi, currentJar, currentApi);
+ Map<String, ApiClass> classes = reader.getClasses();
+ if (!createApiFile(new File(outPath), classes)) {
+ System.exit(1);
+ }
+ }
+
+ private static void printUsage() {
+ System.err.println("\nGenerates a single API file from the content of an SDK.");
+ System.err.println("Usage:");
+ System.err.println("\tApiCheck [--min-api=1] OutFile [SdkFolder | --pattern sdk/%/android.jar]+");
+ System.err.println("Options:");
+ System.err.println("--min-api <int> : The first API level to consider (>=1).");
+ System.err.println("--pattern <pattern>: Path pattern to find per-API android.jar files, where\n" +
+ " '%' is replaced by the API level.");
+ System.err.println("--current-jar <path>: Path pattern to find the current android.jar");
+ System.err.println("--current-version <int>: The API level for the current API");
+ System.err.println("--current-codename <name>: REL, if a release, or codename for previews");
+ System.err.println("SdkFolder: if given, this adds the pattern\n" +
+ " '$SdkFolder/platforms/android-%/android.jar'");
+ System.err.println("If multiple --pattern are specified, they are tried in the order given.\n");
+ }
+
+ /**
+ * Creates the simplified diff-based API level.
+ * @param outFile the output file
+ * @param classes the classes to write
+ */
+ private static boolean createApiFile(File outFile, Map<String, ApiClass> classes) {
+
+ PrintStream ps = null;
+ try {
+ File parentFile = outFile.getParentFile();
+ if (!parentFile.exists()) {
+ boolean ok = parentFile.mkdirs();
+ if (!ok) {
+ System.err.println("Could not create directory " + parentFile);
+ return false;
+ }
+ }
+ ps = new PrintStream(outFile, "UTF-8");
+ ps.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+ ps.println("<api version=\"2\">");
+ TreeMap<String, ApiClass> map = new TreeMap<String, ApiClass>(classes);
+ for (ApiClass theClass : map.values()) {
+ (theClass).print(ps);
+ }
+ ps.println("</api>");
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ if (ps != null) {
+ ps.close();
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/base/misc/attribute_stats/.classpath b/misc/attribute_stats/.classpath
similarity index 100%
rename from base/misc/attribute_stats/.classpath
rename to misc/attribute_stats/.classpath
diff --git a/base/misc/attribute_stats/.gitignore b/misc/attribute_stats/.gitignore
similarity index 100%
rename from base/misc/attribute_stats/.gitignore
rename to misc/attribute_stats/.gitignore
diff --git a/base/misc/attribute_stats/.project b/misc/attribute_stats/.project
similarity index 100%
rename from base/misc/attribute_stats/.project
rename to misc/attribute_stats/.project
diff --git a/base/lint/libs/lint-checks/.settings/org.eclipse.jdt.core.prefs b/misc/attribute_stats/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/lint/libs/lint-checks/.settings/org.eclipse.jdt.core.prefs
rename to misc/attribute_stats/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/misc/attribute_stats/README.txt b/misc/attribute_stats/README.txt
similarity index 100%
rename from base/misc/attribute_stats/README.txt
rename to misc/attribute_stats/README.txt
diff --git a/base/misc/attribute_stats/src/Analyzer.java b/misc/attribute_stats/src/Analyzer.java
similarity index 100%
rename from base/misc/attribute_stats/src/Analyzer.java
rename to misc/attribute_stats/src/Analyzer.java
diff --git a/base/misc/screenshot2/.classpath b/misc/screenshot2/.classpath
similarity index 100%
rename from base/misc/screenshot2/.classpath
rename to misc/screenshot2/.classpath
diff --git a/base/misc/screenshot2/.gitignore b/misc/screenshot2/.gitignore
similarity index 100%
rename from base/misc/screenshot2/.gitignore
rename to misc/screenshot2/.gitignore
diff --git a/base/misc/screenshot2/.project b/misc/screenshot2/.project
similarity index 100%
rename from base/misc/screenshot2/.project
rename to misc/screenshot2/.project
diff --git a/base/misc/attribute_stats/.settings/org.eclipse.jdt.core.prefs b/misc/screenshot2/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/misc/attribute_stats/.settings/org.eclipse.jdt.core.prefs
rename to misc/screenshot2/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/legacy/archquery/NOTICE b/misc/screenshot2/NOTICE
similarity index 100%
rename from base/legacy/archquery/NOTICE
rename to misc/screenshot2/NOTICE
diff --git a/base/misc/screenshot2/build.gradle b/misc/screenshot2/build.gradle
similarity index 100%
rename from base/misc/screenshot2/build.gradle
rename to misc/screenshot2/build.gradle
diff --git a/base/misc/screenshot2/etc/screenshot2 b/misc/screenshot2/etc/screenshot2
similarity index 100%
rename from base/misc/screenshot2/etc/screenshot2
rename to misc/screenshot2/etc/screenshot2
diff --git a/base/misc/screenshot2/src/main/java/com/android/screenshot/Screenshot.java b/misc/screenshot2/src/main/java/com/android/screenshot/Screenshot.java
similarity index 100%
rename from base/misc/screenshot2/src/main/java/com/android/screenshot/Screenshot.java
rename to misc/screenshot2/src/main/java/com/android/screenshot/Screenshot.java
diff --git a/base/ninepatch/.classpath b/ninepatch/.classpath
similarity index 100%
rename from base/ninepatch/.classpath
rename to ninepatch/.classpath
diff --git a/base/ninepatch/.gitignore b/ninepatch/.gitignore
similarity index 100%
rename from base/ninepatch/.gitignore
rename to ninepatch/.gitignore
diff --git a/base/ninepatch/.project b/ninepatch/.project
similarity index 100%
rename from base/ninepatch/.project
rename to ninepatch/.project
diff --git a/base/misc/screenshot2/NOTICE b/ninepatch/NOTICE
similarity index 100%
rename from base/misc/screenshot2/NOTICE
rename to ninepatch/NOTICE
diff --git a/base/ninepatch/build.gradle b/ninepatch/build.gradle
similarity index 100%
rename from base/ninepatch/build.gradle
rename to ninepatch/build.gradle
diff --git a/base/ninepatch/ninepatch.iml b/ninepatch/ninepatch.iml
similarity index 100%
rename from base/ninepatch/ninepatch.iml
rename to ninepatch/ninepatch.iml
diff --git a/base/ninepatch/src/main/java/com/android/ninepatch/GraphicsUtilities.java b/ninepatch/src/main/java/com/android/ninepatch/GraphicsUtilities.java
similarity index 100%
rename from base/ninepatch/src/main/java/com/android/ninepatch/GraphicsUtilities.java
rename to ninepatch/src/main/java/com/android/ninepatch/GraphicsUtilities.java
diff --git a/base/ninepatch/src/main/java/com/android/ninepatch/NinePatch.java b/ninepatch/src/main/java/com/android/ninepatch/NinePatch.java
similarity index 100%
rename from base/ninepatch/src/main/java/com/android/ninepatch/NinePatch.java
rename to ninepatch/src/main/java/com/android/ninepatch/NinePatch.java
diff --git a/base/ninepatch/src/main/java/com/android/ninepatch/NinePatchChunk.java b/ninepatch/src/main/java/com/android/ninepatch/NinePatchChunk.java
similarity index 100%
rename from base/ninepatch/src/main/java/com/android/ninepatch/NinePatchChunk.java
rename to ninepatch/src/main/java/com/android/ninepatch/NinePatchChunk.java
diff --git a/base/ninepatch/src/test/.classpath b/ninepatch/src/test/.classpath
similarity index 100%
rename from base/ninepatch/src/test/.classpath
rename to ninepatch/src/test/.classpath
diff --git a/base/ninepatch/src/test/.gitignore b/ninepatch/src/test/.gitignore
similarity index 100%
rename from base/ninepatch/src/test/.gitignore
rename to ninepatch/src/test/.gitignore
diff --git a/base/ninepatch/src/test/.project b/ninepatch/src/test/.project
similarity index 100%
rename from base/ninepatch/src/test/.project
rename to ninepatch/src/test/.project
diff --git a/base/ninepatch/src/test/java/com/android/ninepatch/NinePatchTest.java b/ninepatch/src/test/java/com/android/ninepatch/NinePatchTest.java
similarity index 100%
rename from base/ninepatch/src/test/java/com/android/ninepatch/NinePatchTest.java
rename to ninepatch/src/test/java/com/android/ninepatch/NinePatchTest.java
diff --git a/base/ninepatch/src/test/resources/com/android/ninepatch/button.9.png b/ninepatch/src/test/resources/com/android/ninepatch/button.9.png
similarity index 100%
rename from base/ninepatch/src/test/resources/com/android/ninepatch/button.9.png
rename to ninepatch/src/test/resources/com/android/ninepatch/button.9.png
diff --git a/base/perflib/build.gradle b/perflib/build.gradle
similarity index 100%
rename from base/perflib/build.gradle
rename to perflib/build.gradle
diff --git a/base/perflib/perflib.iml b/perflib/perflib.iml
similarity index 100%
rename from base/perflib/perflib.iml
rename to perflib/perflib.iml
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalysisReport.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalysisReport.java
new file mode 100644
index 0000000..b112589
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalysisReport.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class AnalysisReport {
+
+ @NonNull
+ private Set<Listener> mListeners = new HashSet<Listener>();
+
+ @NonNull
+ private List<AnalysisResultEntry> mAnalysisResults = new ArrayList<AnalysisResultEntry>();
+
+ // volatile so other threads can see the updated value, but this is not intrinsically
+ // thread-safe. This is mainly useful for the UI to know that the analysis is complete and
+ // reflect this fact in the UI.
+ private volatile boolean mCompleted = false;
+
+ // volatile for similar reasons as mCompleted.
+ private volatile boolean mCancelled = false;
+
+ /**
+ * Add the {@code entry} to the report. Since this is effectively a "reduce" call, the listener
+ * will want to get called on the same thread as {@link #addResultListener(Listener)} and {@link
+ * #setCompleted()}.
+ */
+ public void addAnalysisResultEntries(@NonNull List<AnalysisResultEntry> entries) {
+ mAnalysisResults.addAll(entries);
+ for (Listener listener : mListeners) {
+ listener.onResultsAdded(entries);
+ }
+ }
+
+ /**
+ * Notifies all listeners that this analysis report has been completed. Mainly used for UI
+ * feedback.
+ */
+ public void setCompleted() {
+ if (mCompleted || mCancelled) {
+ return;
+ }
+
+ mCompleted = true;
+ for (Listener listener : mListeners) {
+ listener.onAnalysisComplete();
+ }
+ }
+
+ public void setCancelled() {
+ if (mCompleted || mCancelled) {
+ return;
+ }
+
+ mCancelled = true;
+ for (Listener listener : mListeners) {
+ listener.onAnalysisCancelled();
+ }
+ }
+
+ /**
+ * Adds all {@code listeners} to the set of listeners listening for results or report
+ * completion. The caller will need to add them before analysis starts.
+ */
+ public void addResultListeners(@NonNull Set<Listener> listeners) {
+ mListeners.addAll(listeners);
+ }
+
+ public void removeResultListener(@NonNull Set<Listener> listener) {
+ mListeners.removeAll(listener);
+ }
+
+ public interface Listener {
+
+ void onResultsAdded(@NonNull List<AnalysisResultEntry> entries);
+
+ void onAnalysisComplete();
+
+ void onAnalysisCancelled();
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalysisResultEntry.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalysisResultEntry.java
new file mode 100644
index 0000000..429c3a5
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalysisResultEntry.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+
+public interface AnalysisResultEntry<T> {
+
+ @NonNull
+ String getWarningMessage();
+
+ @NonNull
+ String getCategory();
+
+ @NonNull
+ Offender<T> getOffender();
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/Analyzer.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/Analyzer.java
new file mode 100644
index 0000000..5c512ca
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/Analyzer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+
+public abstract class Analyzer {
+
+ public abstract boolean accept(@NonNull CaptureGroup captureGroup);
+
+ @NonNull
+ public abstract AnalysisReport analyze(@NonNull CaptureGroup captureGroup,
+ @NonNull Set<AnalysisReport.Listener> listeners,
+ @NonNull Set<? extends AnalyzerTask> tasks,
+ @NonNull Executor taskCompleteExecutor,
+ @NonNull ExecutorService taskExecutor);
+
+ public abstract void cancel();
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalyzerTask.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalyzerTask.java
new file mode 100644
index 0000000..74e1fb8
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/AnalyzerTask.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+
+public interface AnalyzerTask {
+
+ @NonNull
+ String getTaskName();
+
+ @NonNull
+ String getTaskDescription();
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/Capture.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/Capture.java
new file mode 100644
index 0000000..351c3f8
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/Capture.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+public abstract class Capture {
+
+ @Nullable
+ public abstract <T> T getRepresentation(Class<T> asClass);
+
+ @NonNull
+ public abstract String getTypeName();
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/CaptureAnalyzer.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/CaptureAnalyzer.java
new file mode 100644
index 0000000..1f7dd27
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/CaptureAnalyzer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+
+public abstract class CaptureAnalyzer {
+
+ public abstract boolean accept(@NonNull CaptureGroup captureGroup);
+
+ @NonNull
+ public abstract AnalysisReport analyze(@NonNull CaptureGroup captureGroup);
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/CaptureGroup.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/CaptureGroup.java
new file mode 100644
index 0000000..2338363
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/CaptureGroup.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CaptureGroup {
+
+ private List<Capture> mCaptures = new ArrayList<Capture>();
+
+ public List<Capture> getCaptures() {
+ return mCaptures;
+ }
+
+ public void addCapture(@NonNull Capture capture) {
+ mCaptures.add(capture);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/analyzer/Offender.java b/perflib/src/main/java/com/android/tools/perflib/analyzer/Offender.java
new file mode 100644
index 0000000..f1c5b17
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/analyzer/Offender.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.analyzer;
+
+import com.android.annotations.NonNull;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Offender<T> {
+
+ @NonNull
+ protected List<T> mOffenders;
+
+ @NonNull
+ protected String mOffendingDescription;
+
+ public Offender(@NonNull String offendingDescription, @NonNull List<T> offendingInstances) {
+ mOffendingDescription = offendingDescription;
+ mOffenders = offendingInstances;
+ }
+
+ @NonNull
+ public List<T> getOffenders() {
+ return Collections.unmodifiableList(mOffenders);
+ }
+
+ @NonNull
+ public String getOffendingDescription() {
+ return mOffendingDescription;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/captures/DataBuffer.java b/perflib/src/main/java/com/android/tools/perflib/captures/DataBuffer.java
new file mode 100644
index 0000000..df51cdd
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/captures/DataBuffer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.captures;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.analyzer.Capture;
+
+import java.nio.ByteOrder;
+
+public interface DataBuffer {
+ ByteOrder HPROF_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+ void dispose();
+
+ void append(@NonNull byte[] data);
+
+ void read(@NonNull byte[] out);
+
+ void readSubSequence(byte[] b, int sourceStart, int sourceEnd);
+
+ byte readByte();
+
+ char readChar();
+
+ short readShort();
+
+ int readInt();
+
+ long readLong();
+
+ float readFloat();
+
+ double readDouble();
+
+ void setPosition(long position);
+
+ long position();
+
+ boolean hasRemaining();
+
+ long remaining();
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/captures/MemoryMappedFileBuffer.java b/perflib/src/main/java/com/android/tools/perflib/captures/MemoryMappedFileBuffer.java
new file mode 100644
index 0000000..d0ebf41
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/captures/MemoryMappedFileBuffer.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.captures;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.perflib.captures.DataBuffer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import sun.nio.ch.DirectBuffer;
+
+public class MemoryMappedFileBuffer implements DataBuffer {
+
+ // Default chunk size is 1 << 30, or 1,073,741,824 bytes.
+ private static final int DEFAULT_SIZE = 1 << 30;
+
+ // Eliminate wrapped, multi-byte reads across chunks in most cases.
+ private static final int DEFAULT_PADDING = 1024;
+
+ private final int mBufferSize;
+
+ private final int mPadding;
+
+ @NonNull
+ private final ByteBuffer[] mByteBuffers;
+
+ private final long mLength;
+
+ private long mCurrentPosition;
+
+ @VisibleForTesting
+ public MemoryMappedFileBuffer(@NonNull File f, int bufferSize,
+ int padding) throws IOException {
+ mBufferSize = bufferSize;
+ mPadding = padding;
+ mLength = f.length();
+ int shards = (int) (mLength / mBufferSize) + 1;
+ mByteBuffers = new ByteBuffer[shards];
+
+ FileInputStream inputStream = new FileInputStream(f);
+ try {
+ long offset = 0;
+ for (int i = 0; i < shards; i++) {
+ long size = Math.min(mLength - offset, mBufferSize + mPadding);
+ mByteBuffers[i] = inputStream.getChannel()
+ .map(FileChannel.MapMode.READ_ONLY, offset, size);
+ mByteBuffers[i].order(HPROF_BYTE_ORDER);
+ offset += mBufferSize;
+ }
+ mCurrentPosition = 0;
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ /**
+ * Creates a buffer by memory-mapping file {@param f}.
+ *
+ * It may be a good idea to dispose() the buffer if no longer needed. A garbage collection isn't
+ * guaranteed to free up the resources, and in a long-running 32-bit JVM there's the risk of
+ * exhausting the address space this way. On Windows, mmap locks the file, preventing it from
+ * being deleted. See {@link http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4715154}.
+ */
+ public MemoryMappedFileBuffer(@NonNull File f) throws IOException {
+ this(f, DEFAULT_SIZE, DEFAULT_PADDING);
+ }
+
+ /**
+ * Attempts to unmap the buffer. It is the caller's responsibility to ensure there are no other
+ * accesses to this buffer, otherwise this can result in a crash and kill the JVM.
+ */
+ @Override
+ public void dispose() {
+ try {
+ for (int i = 0; i < mByteBuffers.length; i++) {
+ ((DirectBuffer) mByteBuffers[i]).cleaner().clean();
+ }
+ } catch (Exception ex) {
+ // ignore, this is a best effort attempt.
+ }
+ }
+
+ @Override
+ public byte readByte() {
+ byte result = mByteBuffers[getIndex()].get(getOffset());
+ mCurrentPosition++;
+ return result;
+ }
+
+ @Override
+ public void append(@NonNull byte[] data) {
+ // Do nothing since this is not a streaming data buffer.
+ }
+
+ @Override
+ public void read(@NonNull byte[] b) {
+ int index = getIndex();
+ mByteBuffers[index].position(getOffset());
+ if (b.length <= mByteBuffers[index].remaining()) {
+ mByteBuffers[index].get(b, 0, b.length);
+ } else {
+ // Wrapped read
+ int split = mBufferSize - mByteBuffers[index].position();
+ mByteBuffers[index].get(b, 0, split);
+ mByteBuffers[index + 1].position(0);
+ mByteBuffers[index + 1].get(b, split, b.length - split);
+ }
+ mCurrentPosition += b.length;
+ }
+
+ @Override
+ public void readSubSequence(@NonNull byte[] b, int sourceStart, int length) {
+ assert length < mLength;
+
+ mCurrentPosition += sourceStart;
+
+ int index = getIndex();
+ mByteBuffers[index].position(getOffset());
+ if (b.length <= mByteBuffers[index].remaining()) {
+ mByteBuffers[index].get(b, 0, b.length);
+ } else {
+ int split = mBufferSize - mByteBuffers[index].position();
+ mByteBuffers[index].get(b, 0, split);
+
+ int start = split;
+ int remainingMaxLength = Math.min(length - start, b.length - start);
+ int remainingShardCount = (remainingMaxLength + mBufferSize - 1) / mBufferSize;
+ for (int i = 0; i < remainingShardCount; ++i) {
+ int maxToRead = Math.min(remainingMaxLength, mBufferSize);
+ mByteBuffers[index + 1 + i].position(0);
+ mByteBuffers[index + 1 + i].get(b, start, maxToRead);
+ start += maxToRead;
+ remainingMaxLength -= maxToRead;
+ }
+ }
+
+ mCurrentPosition += Math.min(b.length, length);
+ }
+
+ @Override
+ public char readChar() {
+ char result = mByteBuffers[getIndex()].getChar(getOffset());
+ mCurrentPosition += 2;
+ return result;
+ }
+
+ @Override
+ public short readShort() {
+ short result = mByteBuffers[getIndex()].getShort(getOffset());
+ mCurrentPosition += 2;
+ return result;
+ }
+
+ @Override
+ public int readInt() {
+ int result = mByteBuffers[getIndex()].getInt(getOffset());
+ mCurrentPosition += 4;
+ return result;
+ }
+
+ @Override
+ public long readLong() {
+ long result = mByteBuffers[getIndex()].getLong(getOffset());
+ mCurrentPosition += 8;
+ return result;
+ }
+
+ @Override
+ public float readFloat() {
+ float result = mByteBuffers[getIndex()].getFloat(getOffset());
+ mCurrentPosition += 4;
+ return result;
+ }
+
+ @Override
+ public double readDouble() {
+ double result = mByteBuffers[getIndex()].getDouble(getOffset());
+ mCurrentPosition += 8;
+ return result;
+ }
+
+ @Override
+ public void setPosition(long position) {
+ mCurrentPosition = position;
+ }
+
+ @Override
+ public long position() {
+ return mCurrentPosition;
+ }
+
+ @Override
+ public boolean hasRemaining() {
+ return mCurrentPosition < mLength;
+ }
+
+ @Override
+ public long remaining() {
+ return mLength - mCurrentPosition;
+ }
+
+ private int getIndex() {
+ return (int) (mCurrentPosition / mBufferSize);
+ }
+
+ private int getOffset() {
+ return (int) (mCurrentPosition % mBufferSize);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/ArrayInstance.java b/perflib/src/main/java/com/android/tools/perflib/heap/ArrayInstance.java
new file mode 100644
index 0000000..e940152
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/ArrayInstance.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.captures.DataBuffer;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+
+public class ArrayInstance extends Instance {
+
+ private final Type mType;
+
+ private final int mLength;
+
+ private final long mValuesOffset;
+
+ public ArrayInstance(long id, @NonNull StackTrace stack, @NonNull Type type, int length,
+ long valuesOffset) {
+ super(id, stack);
+ mType = type;
+ mLength = length;
+ mValuesOffset = valuesOffset;
+ }
+
+ @NonNull
+ public Object[] getValues() {
+ Object[] values = new Object[mLength];
+
+ getBuffer().setPosition(mValuesOffset);
+ for (int i = 0; i < mLength; i++) {
+ values[i] = readValue(mType);
+ }
+ return values;
+ }
+
+ @NonNull
+ public byte[] asRawByteArray(int start, int elementCount) {
+ getBuffer().setPosition(mValuesOffset);
+ assert mType != Type.OBJECT;
+ assert start + elementCount <= mLength;
+ byte[] bytes = new byte[elementCount * mType.getSize()];
+ getBuffer().readSubSequence(bytes, start * mType.getSize(), elementCount * mType.getSize());
+ return bytes;
+ }
+
+ @NonNull
+ public char[] asCharArray(int offset, int length) {
+ assert mType == Type.CHAR;
+ // TODO: Make this copy less by supporting offset in asRawByteArray.
+ CharBuffer charBuffer = ByteBuffer.wrap(asRawByteArray(offset, length)).order(
+ DataBuffer.HPROF_BYTE_ORDER).asCharBuffer();
+ char[] result = new char[length];
+ charBuffer.get(result);
+ return result;
+ }
+
+ @Override
+ public final int getSize() {
+ // TODO: Take the rest of the fields into account: length, type, etc (~16 bytes).
+ return mLength * mHeap.mSnapshot.getTypeSize(mType);
+ }
+
+ @Override
+ public final void resolveReferences() {
+ if (mType == Type.OBJECT) {
+ for (Object value : getValues()) {
+ if (value instanceof Instance) {
+ ((Instance)value).addReverseReference(null, this);
+ mHardForwardReferences.add((Instance)value);
+ }
+ }
+ }
+ }
+
+ @Override
+ public final void accept(@NonNull Visitor visitor) {
+ visitor.visitArrayInstance(this);
+ for (Instance instance : mHardForwardReferences) {
+ visitor.visitLater(this, instance);
+ }
+ }
+
+ @Override
+ public ClassObj getClassObj() {
+ if (mType == Type.OBJECT) {
+ return super.getClassObj();
+ } else {
+ // Primitive arrays don't set their classId, we need to do the lookup manually.
+ return mHeap.mSnapshot.findClass(Type.getClassNameOfPrimitiveArray(mType));
+ }
+ }
+
+ /**
+ * Returns the number of elements in the array.
+ */
+ public int getLength() {
+ return mLength;
+ }
+
+ public Type getArrayType() {
+ return mType;
+ }
+
+ public final String toString() {
+ String className = getClassObj().getClassName();
+ if (className.endsWith("[]")) {
+ className = className.substring(0, className.length() - 2);
+ }
+ return String.format("%s[%d]@%d (0x%x)", className, mLength, getUniqueId(), getUniqueId());
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/ClassInstance.java b/perflib/src/main/java/com/android/tools/perflib/heap/ClassInstance.java
new file mode 100644
index 0000000..e5903bd
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/ClassInstance.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ClassInstance extends Instance {
+
+ private final long mValuesOffset;
+
+ public ClassInstance(long id, @NonNull StackTrace stack, long valuesOffset) {
+ super(id, stack);
+ mValuesOffset = valuesOffset;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ List<FieldValue> getFields(String name) {
+ ArrayList<FieldValue> result = new ArrayList<FieldValue>();
+ for (FieldValue value : getValues()) {
+ if (value.getField().getName().equals(name)) {
+ result.add(value);
+ }
+ }
+ return result;
+ }
+
+ @NonNull
+ public List<FieldValue> getValues() {
+ ArrayList<FieldValue> result = new ArrayList<FieldValue>();
+
+ ClassObj clazz = getClassObj();
+ getBuffer().setPosition(mValuesOffset);
+ while (clazz != null) {
+ for (Field field : clazz.getFields()) {
+ result.add(new FieldValue(field, readValue(field.getType())));
+ }
+ clazz = clazz.getSuperClassObj();
+ }
+ return result;
+ }
+
+ @Override
+ public final void resolveReferences() {
+ for (FieldValue fieldValue : getValues()) {
+ if (fieldValue.getValue() instanceof Instance) {
+ Instance referencedInstance = (Instance)fieldValue.getValue();
+ referencedInstance.addReverseReference(fieldValue.getField(), this);
+ if (getIsSoftReference() && fieldValue.getField().getName().equals("referent")) {
+ mSoftForwardReference = referencedInstance;
+ } else {
+ mHardForwardReferences.add(referencedInstance);
+ }
+ }
+ }
+ mHardForwardReferences.trimToSize(); // Don't wait until the compactMemory stage to trim.
+ }
+
+ @Override
+ public final void accept(@NonNull Visitor visitor) {
+ visitor.visitClassInstance(this);
+ for (Instance instance : mHardForwardReferences) {
+ visitor.visitLater(this, instance);
+ }
+ }
+
+ @Override
+ public boolean getIsSoftReference() {
+ return getClassObj().getIsSoftReference();
+ }
+
+ public final String toString() {
+ return String
+ .format("%s@%d (0x%x)", getClassObj().getClassName(), getUniqueId(), getUniqueId());
+ }
+
+ public boolean isStringInstance() {
+ return getClassObj() != null && "java.lang.String".equals(getClassObj().getClassName());
+ }
+
+ @Nullable
+ public final char[] getStringChars() {
+ return getStringChars(Integer.MAX_VALUE);
+ }
+
+ @Nullable
+ public final char[] getStringChars(int maxDecodeStringLength) {
+ int count = -1;
+ int offset = 0;
+ ArrayInstance charBufferArray = null;
+ for (ClassInstance.FieldValue entry : getValues()) {
+ if (charBufferArray == null && "value".equals(entry.getField().getName())) {
+ if (entry.getValue() instanceof ArrayInstance
+ && ((ArrayInstance) entry.getValue()).getArrayType() == Type.CHAR) {
+ charBufferArray = (ArrayInstance) entry.getValue();
+ }
+ } else if ("count".equals(entry.getField().getName())) {
+ if (entry.getValue() instanceof Integer) {
+ count = (Integer) entry.getValue();
+ }
+ } else if ("offset".equals(entry.getField().getName())) {
+ if (entry.getValue() instanceof Integer) {
+ offset = (Integer) entry.getValue();
+ }
+ }
+ }
+
+ return charBufferArray == null ? null : charBufferArray
+ .asCharArray(offset >= 0 ? offset : 0,
+ Math.max(Math.min(count, maxDecodeStringLength), 0));
+ }
+
+ public static class FieldValue {
+
+ private Field mField;
+
+ private Object mValue;
+
+ public FieldValue(@NonNull Field field, @Nullable Object value) {
+ this.mField = field;
+ this.mValue = value;
+ }
+
+ public Field getField() {
+ return mField;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/ClassObj.java b/perflib/src/main/java/com/android/tools/perflib/heap/ClassObj.java
new file mode 100644
index 0000000..0846364
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/ClassObj.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import gnu.trove.TIntObjectHashMap;
+
+import java.util.*;
+
+public class ClassObj extends Instance implements Comparable<ClassObj> {
+ public static class HeapData {
+ public int mShallowSize = 0;
+
+ public List<Instance> mInstances = new ArrayList<Instance>();
+ }
+
+ @NonNull
+ final String mClassName;
+
+ private final long mStaticFieldsOffset;
+
+ long mSuperClassId;
+
+ long mClassLoaderId;
+
+ Field[] mFields;
+
+ Field[] mStaticFields;
+
+ private int mInstanceSize;
+
+ private boolean mIsSoftReference = false;
+
+ @NonNull
+ TIntObjectHashMap<HeapData> mHeapData = new TIntObjectHashMap<HeapData>();
+
+ @NonNull
+ Set<ClassObj> mSubclasses = new HashSet<ClassObj>();
+
+ public ClassObj(long id, @NonNull StackTrace stack, @NonNull String className,
+ long staticFieldsOffset) {
+ super(id, stack);
+ mClassName = className;
+ mStaticFieldsOffset = staticFieldsOffset;
+ }
+
+ public final void addSubclass(ClassObj subclass) {
+ mSubclasses.add(subclass);
+ }
+
+ @NonNull
+ public final Set<ClassObj> getSubclasses() {
+ return mSubclasses;
+ }
+
+ public final void dumpSubclasses() {
+ for (ClassObj subclass : mSubclasses) {
+ System.out.println(" " + subclass.mClassName);
+ }
+ }
+
+ @NonNull
+ public final String toString() {
+ return mClassName.replace('/', '.');
+ }
+
+ public final void addInstance(int heapId, @NonNull Instance instance) {
+ if (instance instanceof ClassInstance) {
+ instance.setSize(mInstanceSize);
+ }
+
+ HeapData heapData = mHeapData.get(heapId);
+ if (heapData == null) {
+ heapData = new HeapData();
+ mHeapData.put(heapId, heapData);
+ }
+ heapData.mInstances.add(instance);
+ heapData.mShallowSize += instance.getSize();
+ }
+
+ public final void setSuperClassId(long superClass) {
+ mSuperClassId = superClass;
+ }
+
+ public final void setClassLoaderId(long classLoader) {
+ mClassLoaderId = classLoader;
+ }
+
+ public int getAllFieldsCount() {
+ int result = 0;
+ ClassObj clazz = this;
+ while (clazz != null) {
+ result += clazz.getFields().length;
+ clazz = clazz.getSuperClassObj();
+ }
+ return result;
+ }
+
+ public Field[] getFields() {
+ return mFields;
+ }
+
+ public void setFields(@NonNull Field[] fields) {
+ mFields = fields;
+ }
+
+ public void setStaticFields(@NonNull Field[] staticFields) {
+ mStaticFields = staticFields;
+ }
+
+ public void setInstanceSize(int size) {
+ mInstanceSize = size;
+ }
+
+ public int getInstanceSize() {
+ return mInstanceSize;
+ }
+
+ public int getShallowSize(int heapId) {
+ HeapData heapData = mHeapData.get(heapId);
+ return heapData == null ? 0 : mHeapData.get(heapId).mShallowSize;
+ }
+
+ public void setIsSoftReference() {
+ mIsSoftReference = true;
+ }
+
+ @Override
+ public boolean getIsSoftReference() {
+ return mIsSoftReference;
+ }
+
+ @NonNull
+ public Map<Field, Object> getStaticFieldValues() {
+ Map<Field, Object> result = new HashMap<Field, Object>();
+ getBuffer().setPosition(mStaticFieldsOffset);
+
+ int numEntries = readUnsignedShort();
+ for (int i = 0; i < numEntries; i++) {
+ Field f = mStaticFields[i];
+
+ readId();
+ readUnsignedByte();
+
+ Object value = readValue(f.getType());
+ result.put(f, value);
+ }
+ return result;
+ }
+
+ public final void dump() {
+ System.out.println("+---------- ClassObj dump for: " + mClassName);
+
+ System.out.println("+----- Static fields");
+ Map<Field, Object> staticFields = getStaticFieldValues();
+ for (Field field : staticFields.keySet()) {
+ System.out.println(field.getName() + ": " + field.getType() + " = "
+ + staticFields.get(field));
+ }
+
+ System.out.println("+----- Instance fields");
+ for (Field field : mFields) {
+ System.out.println(field.getName() + ": " + field.getType());
+ }
+ if (getSuperClassObj() != null) {
+ getSuperClassObj().dump();
+ }
+ }
+
+ @NonNull
+ public final String getClassName() {
+ return mClassName;
+ }
+
+ @Override
+ public final void resolveReferences() {
+ for (Map.Entry<Field, Object> entry : getStaticFieldValues().entrySet()) {
+ Object value = entry.getValue();
+ if (value instanceof Instance) {
+ ((Instance)value).addReverseReference(entry.getKey(), this);
+ mHardForwardReferences.add((Instance)value);
+ }
+ }
+ }
+
+ @Override
+ public final void accept(@NonNull Visitor visitor) {
+ visitor.visitClassObj(this);
+ for (Instance instance : mHardForwardReferences) {
+ visitor.visitLater(this, instance);
+ }
+ }
+
+ @Override
+ public final int compareTo(@NonNull ClassObj o) {
+ if (getId() == o.getId()) {
+ return 0;
+ }
+
+ int nameCompareResult = mClassName.compareTo(o.mClassName);
+ if (nameCompareResult != 0) {
+ return nameCompareResult;
+ } else {
+ return getId() - o.getId() > 0 ? 1 : -1;
+ }
+ }
+
+ public final boolean equals(Object o) {
+ if (!(o instanceof ClassObj)) {
+ return false;
+ }
+
+ return 0 == compareTo((ClassObj) o);
+ }
+
+ @Override
+ public int hashCode() {
+ return mClassName.hashCode();
+ }
+
+ @VisibleForTesting
+ Object getStaticField(Type type, String name) {
+ return getStaticFieldValues().get(new Field(type, name));
+ }
+
+ public ClassObj getSuperClassObj() {
+ return mHeap.mSnapshot.findClass(mSuperClassId);
+ }
+
+ @Nullable
+ public Instance getClassLoader() {
+ return mHeap.mSnapshot.findInstance(mClassLoaderId);
+ }
+
+ public List<Instance> getInstancesList() {
+ int count = getInstanceCount();
+ ArrayList<Instance> resultList = new ArrayList<Instance>(count);
+ for (int heapId : mHeapData.keys()) {
+ resultList.addAll(getHeapInstances(heapId));
+ }
+ return resultList;
+ }
+
+ @NonNull
+ public List<Instance> getHeapInstances(int heapId) {
+ HeapData result = mHeapData.get(heapId);
+ return result == null ? new ArrayList<Instance>(0) : result.mInstances;
+ }
+
+ public int getHeapInstancesCount(int heapId) {
+ HeapData result = mHeapData.get(heapId);
+ return result == null ? 0 : result.mInstances.size();
+ }
+
+ public int getInstanceCount() {
+ int count = 0;
+ for (Object heapStat : mHeapData.getValues()) {
+ count += ((HeapData)heapStat).mInstances.size();
+ }
+ return count;
+ }
+
+ public int getShallowSize() {
+ int size = 0;
+ for (Object heapStat : mHeapData.getValues()) {
+ size += ((HeapData)heapStat).mShallowSize;
+ }
+ return size;
+ }
+
+ @NonNull
+ public static String getReferenceClassName() {
+ return "java.lang.ref.Reference";
+ }
+
+ @NonNull
+ public List<ClassObj> getDescendantClasses() {
+ List<ClassObj> descendants = new ArrayList<ClassObj>();
+
+ Stack<ClassObj> searchStack = new Stack<ClassObj>();
+ searchStack.push(this);
+
+ while (!searchStack.isEmpty()) {
+ ClassObj classObj = searchStack.pop();
+ descendants.add(classObj);
+ for (ClassObj subClass : classObj.getSubclasses()) {
+ searchStack.push(subClass);
+ }
+ }
+
+ return descendants;
+ }
+}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Field.java b/perflib/src/main/java/com/android/tools/perflib/heap/Field.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/heap/Field.java
rename to perflib/src/main/java/com/android/tools/perflib/heap/Field.java
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/Heap.java b/perflib/src/main/java/com/android/tools/perflib/heap/Heap.java
new file mode 100644
index 0000000..e7fc782
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/Heap.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import com.google.common.collect.*;
+import gnu.trove.TIntObjectHashMap;
+import gnu.trove.TLongObjectHashMap;
+import gnu.trove.TObjectProcedure;
+
+public class Heap {
+
+ private final int mId;
+
+ @NonNull
+ private final String mName;
+
+ // Root objects such as interned strings, jni locals, etc
+ @NonNull
+ ArrayList<RootObj> mRoots = new ArrayList<RootObj>();
+
+ // List of threads
+ @NonNull
+ TIntObjectHashMap<ThreadObj> mThreads = new TIntObjectHashMap<ThreadObj>();
+
+ // Class definitions
+ @NonNull
+ TLongObjectHashMap<ClassObj> mClassesById = new TLongObjectHashMap<ClassObj>();
+
+ @NonNull Multimap<String, ClassObj> mClassesByName = ArrayListMultimap.create();
+
+ // List of instances of above class definitions
+ private final TLongObjectHashMap<Instance> mInstances = new TLongObjectHashMap<Instance>();
+
+ // The snapshot that this heap is part of
+ Snapshot mSnapshot;
+
+ public Heap(int id, @NonNull String name) {
+ mId = id;
+ mName = name;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ public final void addRoot(@NonNull RootObj root) {
+ root.mIndex = mRoots.size();
+ mRoots.add(root);
+ }
+
+ public final void addThread(ThreadObj thread, int serialNumber) {
+ mThreads.put(serialNumber, thread);
+ }
+
+ public final ThreadObj getThread(int serialNumber) {
+ return mThreads.get(serialNumber);
+ }
+
+ public final void addInstance(long id, Instance instance) {
+ mInstances.put(id, instance);
+ }
+
+ public final Instance getInstance(long id) {
+ return mInstances.get(id);
+ }
+
+ public final void addClass(long id, @NonNull ClassObj theClass) {
+ mClassesById.put(id, theClass);
+ mClassesByName.put(theClass.mClassName, theClass);
+ }
+
+ public final ClassObj getClass(long id) {
+ return mClassesById.get(id);
+ }
+
+ public final ClassObj getClass(String name) {
+ Collection<ClassObj> classes = mClassesByName.get(name);
+ if (classes.size() == 1) {
+ return classes.iterator().next();
+ }
+ return null;
+ }
+
+ public final Collection<ClassObj> getClasses(String name) {
+ return mClassesByName.get(name);
+ }
+
+ public final void dumpInstanceCounts() {
+ for (Object value : mClassesById.getValues()) {
+ ClassObj theClass = (ClassObj) value;
+ int count = theClass.getInstanceCount();
+
+ if (count > 0) {
+ System.out.println(theClass + ": " + count);
+ }
+ }
+ }
+
+ public final void dumpSubclasses() {
+ for (Object value : mClassesById.getValues()) {
+ ClassObj theClass = (ClassObj) value;
+ int count = theClass.mSubclasses.size();
+
+ if (count > 0) {
+ System.out.println(theClass);
+ theClass.dumpSubclasses();
+ }
+ }
+ }
+
+ public final void dumpSizes() {
+ for (Object value : mClassesById.getValues()) {
+ ClassObj theClass = (ClassObj) value;
+
+ int size = 0;
+
+ for (Instance instance : theClass.getHeapInstances(getId())) {
+ size += instance.getCompositeSize();
+ }
+
+ if (size > 0) {
+ System.out.println(theClass + ": base " + theClass.getSize()
+ + ", composite " + size);
+ }
+ }
+ }
+
+ @NonNull
+ public Collection<ClassObj> getClasses() {
+ return mClassesByName.values();
+ }
+
+ public void forEachInstance(@NonNull TObjectProcedure<Instance> procedure) {
+ mInstances.forEachValue(procedure);
+ }
+
+ public int getInstancesCount() {
+ return mInstances.size();
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/HprofParser.java b/perflib/src/main/java/com/android/tools/perflib/heap/HprofParser.java
new file mode 100644
index 0000000..78f7bcb
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/HprofParser.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.captures.DataBuffer;
+import com.google.common.primitives.UnsignedBytes;
+import com.google.common.primitives.UnsignedInts;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+import gnu.trove.TLongObjectHashMap;
+
+class HprofParser {
+
+ private static final int STRING_IN_UTF8 = 0x01;
+
+ private static final int LOAD_CLASS = 0x02;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int UNLOAD_CLASS = 0x03;
+
+ private static final int STACK_FRAME = 0x04;
+
+ private static final int STACK_TRACE = 0x05;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int ALLOC_SITES = 0x06;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int HEAP_SUMMARY = 0x07;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int START_THREAD = 0x0a;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int END_THREAD = 0x0b;
+
+ private static final int HEAP_DUMP = 0x0c;
+
+ private static final int HEAP_DUMP_SEGMENT = 0x1c;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int HEAP_DUMP_END = 0x2c;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int CPU_SAMPLES = 0x0d;
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final int CONTROL_SETTINGS = 0x0e;
+
+ private static final int ROOT_UNKNOWN = 0xff;
+
+ private static final int ROOT_JNI_GLOBAL = 0x01;
+
+ private static final int ROOT_JNI_LOCAL = 0x02;
+
+ private static final int ROOT_JAVA_FRAME = 0x03;
+
+ private static final int ROOT_NATIVE_STACK = 0x04;
+
+ private static final int ROOT_STICKY_CLASS = 0x05;
+
+ private static final int ROOT_THREAD_BLOCK = 0x06;
+
+ private static final int ROOT_MONITOR_USED = 0x07;
+
+ private static final int ROOT_THREAD_OBJECT = 0x08;
+
+ private static final int ROOT_CLASS_DUMP = 0x20;
+
+ private static final int ROOT_INSTANCE_DUMP = 0x21;
+
+ private static final int ROOT_OBJECT_ARRAY_DUMP = 0x22;
+
+ private static final int ROOT_PRIMITIVE_ARRAY_DUMP = 0x23;
+
+ /**
+ * Android format addition
+ *
+ * Specifies information about which heap certain objects came from. When a sub-tag of this type
+ * appears in a HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT record, entries that follow it will
+ * be associated with the specified heap. The HEAP_DUMP_INFO data is reset at the end of the
+ * HEAP_DUMP[_SEGMENT]. Multiple HEAP_DUMP_INFO entries may appear in a single
+ * HEAP_DUMP[_SEGMENT].
+ *
+ * Format: u1: Tag value (0xFE) u4: heap ID ID: heap name string ID
+ */
+ private static final int ROOT_HEAP_DUMP_INFO = 0xfe;
+
+ private static final int ROOT_INTERNED_STRING = 0x89;
+
+ private static final int ROOT_FINALIZING = 0x8a;
+
+ private static final int ROOT_DEBUGGER = 0x8b;
+
+ private static final int ROOT_REFERENCE_CLEANUP = 0x8c;
+
+ private static final int ROOT_VM_INTERNAL = 0x8d;
+
+ private static final int ROOT_JNI_MONITOR = 0x8e;
+
+ private static final int ROOT_UNREACHABLE = 0x90;
+
+ private static final int ROOT_PRIMITIVE_ARRAY_NODATA = 0xc3;
+
+ @NonNull
+ private final DataBuffer mInput;
+
+ int mIdSize;
+
+ Snapshot mSnapshot;
+
+ /*
+ * These are only needed while parsing so are not kept as part of the
+ * heap data.
+ */
+ @NonNull
+ TLongObjectHashMap<String> mStrings = new TLongObjectHashMap<String>();
+
+ @NonNull
+ TLongObjectHashMap<String> mClassNames = new TLongObjectHashMap<String>();
+
+ static void parseBuffer(@NonNull Snapshot snapshot, @NonNull DataBuffer buffer) {
+ new HprofParser(snapshot, buffer).parse();
+ }
+
+ private HprofParser(@NonNull Snapshot snapshot, @NonNull DataBuffer buffer) {
+ mInput = buffer;
+ mSnapshot = snapshot;
+ }
+
+ private void parse() {
+ try {
+ try {
+ readNullTerminatedString(); // Version, ignored for now.
+
+ mIdSize = mInput.readInt();
+ mSnapshot.setIdSize(mIdSize);
+
+ mInput.readLong(); // Timestamp, ignored for now.
+
+ while (mInput.hasRemaining()) {
+ int tag = readUnsignedByte();
+ mInput.readInt(); // Ignored: timestamp
+ long length = readUnsignedInt();
+
+ switch (tag) {
+ case STRING_IN_UTF8:
+ // String length is limited by Int.MAX_VALUE anyway.
+ loadString((int) length - mIdSize);
+ break;
+
+ case LOAD_CLASS:
+ loadClass();
+ break;
+
+ case STACK_FRAME:
+ loadStackFrame();
+ break;
+
+ case STACK_TRACE:
+ loadStackTrace();
+ break;
+
+ case HEAP_DUMP:
+ loadHeapDump(length);
+ mSnapshot.setToDefaultHeap();
+ break;
+
+ case HEAP_DUMP_SEGMENT:
+ loadHeapDump(length);
+ mSnapshot.setToDefaultHeap();
+ break;
+
+ default:
+ skipFully(length);
+ }
+
+ }
+ } catch (EOFException eof) {
+ // this is fine
+ }
+ mSnapshot.resolveClasses();
+ mSnapshot.identifySoftReferences();
+ // TODO: enable this after the dominators computation is also optimized.
+ // mSnapshot.computeRetainedSizes();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ mClassNames.clear();
+ mStrings.clear();
+ }
+
+ @NonNull
+ private String readNullTerminatedString() throws IOException {
+ StringBuilder s = new StringBuilder();
+ for (byte c = mInput.readByte(); c != 0; c = mInput.readByte()) {
+ s.append((char) c);
+ }
+ return s.toString();
+ }
+
+ private long readId() throws IOException {
+ // As long as we don't interpret IDs, reading signed values here is fine.
+ switch (mIdSize) {
+ case 1:
+ return mInput.readByte();
+ case 2:
+ return mInput.readShort();
+ case 4:
+ return mInput.readInt();
+ case 8:
+ return mInput.readLong();
+ }
+
+ throw new IllegalArgumentException("ID Length must be 1, 2, 4, or 8");
+ }
+
+ @NonNull
+ private String readUTF8(int length) throws IOException {
+ byte[] b = new byte[length];
+
+ mInput.read(b);
+
+ return new String(b, "utf-8");
+ }
+
+ private int readUnsignedByte() throws IOException {
+ return UnsignedBytes.toInt(mInput.readByte());
+ }
+
+ private int readUnsignedShort() throws IOException {
+ return mInput.readShort() & 0xffff;
+ }
+
+ private long readUnsignedInt() throws IOException {
+ return UnsignedInts.toLong(mInput.readInt());
+ }
+
+ private void loadString(int length) throws IOException {
+ long id = readId();
+ String string = readUTF8(length);
+
+ mStrings.put(id, string);
+ }
+
+ private void loadClass() throws IOException {
+ mInput.readInt(); // Ignored: Class serial number.
+ long id = readId();
+ mInput.readInt(); // Ignored: Stack trace serial number.
+ String name = mStrings.get(readId());
+
+ mClassNames.put(id, name);
+ }
+
+ private void loadStackFrame() throws IOException {
+ long id = readId();
+ String methodName = mStrings.get(readId());
+ String methodSignature = mStrings.get(readId());
+ String sourceFile = mStrings.get(readId());
+ int serial = mInput.readInt();
+ int lineNumber = mInput.readInt();
+
+ StackFrame frame = new StackFrame(id, methodName, methodSignature,
+ sourceFile, serial, lineNumber);
+
+ mSnapshot.addStackFrame(frame);
+ }
+
+ private void loadStackTrace() throws IOException {
+ int serialNumber = mInput.readInt();
+ int threadSerialNumber = mInput.readInt();
+ final int numFrames = mInput.readInt();
+ StackFrame[] frames = new StackFrame[numFrames];
+
+ for (int i = 0; i < numFrames; i++) {
+ frames[i] = mSnapshot.getStackFrame(readId());
+ }
+
+ StackTrace trace = new StackTrace(serialNumber, threadSerialNumber, frames);
+
+ mSnapshot.addStackTrace(trace);
+ }
+
+ private void loadHeapDump(long length) throws IOException {
+ while (length > 0) {
+ int tag = readUnsignedByte();
+ length--;
+
+ switch (tag) {
+ case ROOT_UNKNOWN:
+ length -= loadBasicObj(RootType.UNKNOWN);
+ break;
+
+ case ROOT_JNI_GLOBAL:
+ length -= loadBasicObj(RootType.NATIVE_STATIC);
+ readId(); // ignored
+ length -= mIdSize;
+ break;
+
+ case ROOT_JNI_LOCAL:
+ length -= loadJniLocal();
+ break;
+
+ case ROOT_JAVA_FRAME:
+ length -= loadJavaFrame();
+ break;
+
+ case ROOT_NATIVE_STACK:
+ length -= loadNativeStack();
+ break;
+
+ case ROOT_STICKY_CLASS:
+ length -= loadBasicObj(RootType.SYSTEM_CLASS);
+ break;
+
+ case ROOT_THREAD_BLOCK:
+ length -= loadThreadBlock();
+ break;
+
+ case ROOT_MONITOR_USED:
+ length -= loadBasicObj(RootType.BUSY_MONITOR);
+ break;
+
+ case ROOT_THREAD_OBJECT:
+ length -= loadThreadObject();
+ break;
+
+ case ROOT_CLASS_DUMP:
+ length -= loadClassDump();
+ break;
+
+ case ROOT_INSTANCE_DUMP:
+ length -= loadInstanceDump();
+ break;
+
+ case ROOT_OBJECT_ARRAY_DUMP:
+ length -= loadObjectArrayDump();
+ break;
+
+ case ROOT_PRIMITIVE_ARRAY_DUMP:
+ length -= loadPrimitiveArrayDump();
+ break;
+
+ case ROOT_PRIMITIVE_ARRAY_NODATA:
+ System.err.println("+--- PRIMITIVE ARRAY NODATA DUMP");
+ length -= loadPrimitiveArrayDump();
+
+ throw new IllegalArgumentException(
+ "Don't know how to load a nodata array");
+
+ case ROOT_HEAP_DUMP_INFO:
+ int heapId = mInput.readInt();
+ long heapNameId = readId();
+ String heapName = mStrings.get(heapNameId);
+
+ mSnapshot.setHeapTo(heapId, heapName);
+ length -= 4 + mIdSize;
+ break;
+
+ case ROOT_INTERNED_STRING:
+ length -= loadBasicObj(RootType.INTERNED_STRING);
+ break;
+
+ case ROOT_FINALIZING:
+ length -= loadBasicObj(RootType.FINALIZING);
+ break;
+
+ case ROOT_DEBUGGER:
+ length -= loadBasicObj(RootType.DEBUGGER);
+ break;
+
+ case ROOT_REFERENCE_CLEANUP:
+ length -= loadBasicObj(RootType.REFERENCE_CLEANUP);
+ break;
+
+ case ROOT_VM_INTERNAL:
+ length -= loadBasicObj(RootType.VM_INTERNAL);
+ break;
+
+ case ROOT_JNI_MONITOR:
+ length -= loadJniMonitor();
+ break;
+
+ case ROOT_UNREACHABLE:
+ length -= loadBasicObj(RootType.UNREACHABLE);
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ "loadHeapDump loop with unknown tag " + tag
+ + " with " + mInput.remaining()
+ + " bytes possibly remaining");
+ }
+ }
+ }
+
+ private int loadJniLocal() throws IOException {
+ long id = readId();
+ int threadSerialNumber = mInput.readInt();
+ int stackFrameNumber = mInput.readInt();
+ ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
+ StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber);
+ RootObj root = new RootObj(RootType.NATIVE_LOCAL, id, threadSerialNumber, trace);
+
+ mSnapshot.addRoot(root);
+
+ return mIdSize + 4 + 4;
+ }
+
+ private int loadJavaFrame() throws IOException {
+ long id = readId();
+ int threadSerialNumber = mInput.readInt();
+ int stackFrameNumber = mInput.readInt();
+ ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
+ StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber);
+ RootObj root = new RootObj(RootType.JAVA_LOCAL, id, threadSerialNumber, trace);
+
+ mSnapshot.addRoot(root);
+
+ return mIdSize + 4 + 4;
+ }
+
+ private int loadNativeStack() throws IOException {
+ long id = readId();
+ int threadSerialNumber = mInput.readInt();
+ ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
+ StackTrace trace = mSnapshot.getStackTrace(thread.mStackTrace);
+ RootObj root = new RootObj(RootType.NATIVE_STACK, id, threadSerialNumber, trace);
+
+ mSnapshot.addRoot(root);
+
+ return mIdSize + 4;
+ }
+
+ private int loadBasicObj(RootType type) throws IOException {
+ long id = readId();
+ RootObj root = new RootObj(type, id);
+
+ mSnapshot.addRoot(root);
+
+ return mIdSize;
+ }
+
+ private int loadThreadBlock() throws IOException {
+ long id = readId();
+ int threadSerialNumber = mInput.readInt();
+ ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
+ StackTrace stack = mSnapshot.getStackTrace(thread.mStackTrace);
+ RootObj root = new RootObj(RootType.THREAD_BLOCK, id, threadSerialNumber, stack);
+
+ mSnapshot.addRoot(root);
+
+ return mIdSize + 4;
+ }
+
+ private int loadThreadObject() throws IOException {
+ long id = readId();
+ int threadSerialNumber = mInput.readInt();
+ int stackSerialNumber = mInput.readInt();
+ ThreadObj thread = new ThreadObj(id, stackSerialNumber);
+
+ mSnapshot.addThread(thread, threadSerialNumber);
+
+ return mIdSize + 4 + 4;
+ }
+
+ private int loadClassDump() throws IOException {
+ final long id = readId();
+ int stackSerialNumber = mInput.readInt();
+ StackTrace stack = mSnapshot.getStackTrace(stackSerialNumber);
+ final long superClassId = readId();
+ final long classLoaderId = readId();
+ readId(); // Ignored: Signeres ID.
+ readId(); // Ignored: Protection domain ID.
+ readId(); // RESERVED.
+ readId(); // RESERVED.
+ int instanceSize = mInput.readInt();
+
+ int bytesRead = (7 * mIdSize) + 4 + 4;
+
+ // Skip over the constant pool
+ int numEntries = readUnsignedShort();
+ bytesRead += 2;
+
+ for (int i = 0; i < numEntries; i++) {
+ readUnsignedShort();
+ bytesRead += 2 + skipValue();
+ }
+
+ final ClassObj theClass = new ClassObj(id, stack, mClassNames.get(id), mInput.position());
+ theClass.setSuperClassId(superClassId);
+ theClass.setClassLoaderId(classLoaderId);
+
+ // Skip over static fields
+ numEntries = readUnsignedShort();
+ bytesRead += 2;
+
+ Field[] staticFields = new Field[numEntries];
+
+ for (int i = 0; i < numEntries; i++) {
+ String name = mStrings.get(readId());
+ Type type = Type.getType(mInput.readByte());
+
+ staticFields[i] = new Field(type, name);
+ skipFully(mSnapshot.getTypeSize(type));
+
+ bytesRead += mIdSize + 1 + mSnapshot.getTypeSize(type);
+ }
+
+ theClass.setStaticFields(staticFields);
+
+ // Instance fields
+ numEntries = readUnsignedShort();
+ bytesRead += 2;
+
+ Field[] fields = new Field[numEntries];
+
+ for (int i = 0; i < numEntries; i++) {
+ String name = mStrings.get(readId());
+ Type type = Type.getType(readUnsignedByte());
+
+ fields[i] = new Field(type, name);
+
+ bytesRead += mIdSize + 1;
+ }
+
+ theClass.setFields(fields);
+ theClass.setInstanceSize(instanceSize);
+
+ mSnapshot.addClass(id, theClass);
+
+ return bytesRead;
+ }
+
+ private int loadInstanceDump() throws IOException {
+ long id = readId();
+ int stackId = mInput.readInt();
+ StackTrace stack = mSnapshot.getStackTrace(stackId);
+ long classId = readId();
+ int remaining = mInput.readInt();
+
+ long position = mInput.position();
+ ClassInstance instance = new ClassInstance(id, stack, position);
+ instance.setClassId(classId);
+ mSnapshot.addInstance(id, instance);
+
+ skipFully(remaining);
+ return mIdSize + 4 + mIdSize + 4 + remaining;
+ }
+
+ private int loadObjectArrayDump() throws IOException {
+ final long id = readId();
+ int stackId = mInput.readInt();
+ StackTrace stack = mSnapshot.getStackTrace(stackId);
+ int numElements = mInput.readInt();
+ long classId = readId();
+ ArrayInstance array =
+ new ArrayInstance(id, stack, Type.OBJECT, numElements, mInput.position());
+ array.setClassId(classId);
+ mSnapshot.addInstance(id, array);
+
+ int remaining = numElements * mIdSize;
+ skipFully(remaining);
+ return mIdSize + 4 + 4 + mIdSize + remaining;
+ }
+
+ private int loadPrimitiveArrayDump() throws IOException {
+ long id = readId();
+ int stackId = mInput.readInt();
+ StackTrace stack = mSnapshot.getStackTrace(stackId);
+ int numElements = mInput.readInt();
+ Type type = Type.getType(readUnsignedByte());
+ int size = mSnapshot.getTypeSize(type);
+ ArrayInstance array = new ArrayInstance(id, stack, type, numElements, mInput.position());
+ mSnapshot.addInstance(id, array);
+
+ int remaining = numElements * size;
+ skipFully(remaining);
+ return mIdSize + 4 + 4 + 1 + remaining;
+ }
+
+ private int loadJniMonitor() throws IOException {
+ long id = readId();
+ int threadSerialNumber = mInput.readInt();
+ int stackDepth = mInput.readInt();
+ ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
+ StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackDepth);
+ RootObj root = new RootObj(RootType.NATIVE_MONITOR, id, threadSerialNumber, trace);
+
+ mSnapshot.addRoot(root);
+
+ return mIdSize + 4 + 4;
+ }
+
+ private int skipValue() throws IOException {
+ Type type = Type.getType(readUnsignedByte());
+ int size = mSnapshot.getTypeSize(type);
+
+ skipFully(size);
+
+ return size + 1;
+ }
+
+ private void skipFully(long numBytes) throws IOException {
+ mInput.setPosition(mInput.position() + numBytes);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/Instance.java b/perflib/src/main/java/com/android/tools/perflib/heap/Instance.java
new file mode 100644
index 0000000..8b6b481
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/Instance.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.perflib.captures.DataBuffer;
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.UnsignedBytes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class Instance {
+ protected final long mId;
+
+ // The stack in which this object was allocated
+ @NonNull
+ protected final StackTrace mStack;
+
+ // Id of the ClassObj of which this object is an instance
+ long mClassId;
+
+ // The heap in which this object was allocated (app, zygote, etc)
+ Heap mHeap;
+
+ // The size of this object
+ int mSize;
+
+ // Another identifier for this Instance, that we computed during the analysis phase.
+ int mTopologicalOrder;
+
+ int mDistanceToGcRoot = Integer.MAX_VALUE;
+
+ Instance mNextInstanceToGcRoot = null;
+
+ // The immediate dominator of this instance, or null if not reachable from any GC roots.
+ @Nullable
+ private Instance mImmediateDominator;
+
+ // The retained size of this object, indexed by heap (default, image, app, zygote).
+ // Intuitively, this represents the amount of memory that could be reclaimed in each heap if
+ // the instance were removed.
+ // To save space, we only keep a primitive array here following the order in mSnapshot.mHeaps.
+ private long[] mRetainedSizes;
+
+ protected final ArrayList<Instance> mHardForwardReferences = new ArrayList<Instance>();
+
+ protected Instance mSoftForwardReference = null;
+
+ // List of all objects that hold a live reference to this object
+ protected final ArrayList<Instance> mHardReverseReferences = new ArrayList<Instance>();
+
+ // List of all objects that hold a soft/weak/phantom reference to this object.
+ // Don't create an actual list until we need to.
+ protected ArrayList<Instance> mSoftReverseReferences = null;
+
+ Instance(long id, @NonNull StackTrace stackTrace) {
+ mId = id;
+ mStack = stackTrace;
+ }
+
+ /**
+ * Resolves all forward/reverse + hard/soft references for this instance.
+ */
+ public abstract void resolveReferences();
+
+ public abstract void accept(Visitor visitor);
+
+ /**
+ * Trims the variable size data to their minimal size to reduce memory usage.
+ */
+ public void compactMemory() {
+ // mHardForwardReferences trimmed in resolveReferences();
+ mHardReverseReferences.trimToSize();
+ if (mSoftReverseReferences != null) {
+ mSoftReverseReferences.trimToSize();
+ }
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public long getUniqueId() {
+ return getId() & mHeap.mSnapshot.getIdSizeMask();
+ }
+
+ public void setClassId(long classId) {
+ mClassId = classId;
+ }
+
+ public ClassObj getClassObj() {
+ return mHeap.mSnapshot.findClass(mClassId);
+ }
+
+ public final int getCompositeSize() {
+ CompositeSizeVisitor visitor = new CompositeSizeVisitor();
+ visitor.doVisit(ImmutableList.of(this));
+ return visitor.getCompositeSize();
+ }
+
+ // Returns the instrinsic size of a given object
+ public int getSize() {
+ return mSize;
+ }
+
+ public void setSize(int size) {
+ mSize = size;
+ }
+
+ public void setHeap(Heap heap) {
+ mHeap = heap;
+ }
+
+ public Heap getHeap() {
+ return mHeap;
+ }
+
+ public int getTopologicalOrder() {
+ return mTopologicalOrder;
+ }
+
+ public void setTopologicalOrder(int topologicalOrder) {
+ mTopologicalOrder = topologicalOrder;
+ }
+
+ @Nullable
+ public Instance getImmediateDominator() {
+ return mImmediateDominator;
+ }
+
+ public void setImmediateDominator(@NonNull Instance dominator) {
+ mImmediateDominator = dominator;
+ }
+
+ public int getDistanceToGcRoot() {
+ return mDistanceToGcRoot;
+ }
+
+ public Instance getNextInstanceToGcRoot() {
+ return mNextInstanceToGcRoot;
+ }
+
+ public void setDistanceToGcRoot(int newDistance) {
+ assert (newDistance < mDistanceToGcRoot);
+ mDistanceToGcRoot = newDistance;
+ }
+
+ public void setNextInstanceToGcRoot(Instance instance) {
+ mNextInstanceToGcRoot = instance;
+ }
+
+ /**
+ * Determines if this instance is reachable via hard references from any GC root.
+ * The results are only valid after ShortestDistanceVisitor has been run.
+ */
+ public boolean isReachable() {
+ return mDistanceToGcRoot != Integer.MAX_VALUE;
+ }
+
+ public void resetRetainedSize() {
+ List<Heap> allHeaps = mHeap.mSnapshot.mHeaps;
+ if (mRetainedSizes == null) {
+ mRetainedSizes = new long[allHeaps.size()];
+ } else {
+ Arrays.fill(mRetainedSizes, 0);
+ }
+ mRetainedSizes[allHeaps.indexOf(mHeap)] = getSize();
+ }
+
+ public void addRetainedSize(int heapIndex, long size) {
+ mRetainedSizes[heapIndex] += size;
+ }
+
+ public long getRetainedSize(int heapIndex) {
+ return mRetainedSizes[heapIndex];
+ }
+
+ public long getTotalRetainedSize() {
+ if (mRetainedSizes == null) {
+ return 0;
+ }
+
+ long totalSize = 0;
+ for (long mRetainedSize : mRetainedSizes) {
+ totalSize += mRetainedSize;
+ }
+ return totalSize;
+ }
+
+ /**
+ * Add to the list of objects that references this Instance.
+ *
+ * @param field the named variable in #reference pointing to this instance. If the name of
+ * the field is "referent", and #reference is a soft reference type, then
+ * reference is counted as a soft reference instead of the usual hard
+ * reference.
+ * @param reference another instance that references this instance
+ */
+ public void addReverseReference(@Nullable Field field, @NonNull Instance reference) {
+ if (reference.getIsSoftReference() && field != null && field.getName().equals("referent")) {
+ if (mSoftReverseReferences == null) {
+ mSoftReverseReferences = new ArrayList<Instance>();
+ }
+ mSoftReverseReferences.add(reference);
+ } else {
+ mHardReverseReferences.add(reference);
+ }
+ }
+
+ @NonNull
+ public ArrayList<Instance> getHardForwardReferences() {
+ return mHardForwardReferences;
+ }
+
+ @NonNull
+ public Instance getSoftForwardReference() {
+ return mSoftForwardReference;
+ }
+
+ @NonNull
+ public ArrayList<Instance> getHardReverseReferences() {
+ return mHardReverseReferences;
+ }
+
+ @Nullable
+ public ArrayList<Instance> getSoftReverseReferences() {
+ return mSoftReverseReferences;
+ }
+
+ /**
+ * Removes all duplicate references AND references to itself.
+ */
+ public void dedupeReferences() {
+ Set<Instance> dedupeSet = new HashSet<Instance>(mHardReverseReferences.size());
+ dedupeSet.addAll(mHardReverseReferences);
+ dedupeSet.remove(this);
+ mHardReverseReferences.clear();
+ mHardReverseReferences.addAll(dedupeSet);
+ mHardReverseReferences.trimToSize();
+
+ if (getSoftReverseReferences() != null) {
+ dedupeSet.clear();
+ dedupeSet.addAll(getSoftReverseReferences());
+ mSoftReverseReferences.clear();
+ mSoftReverseReferences.addAll(dedupeSet);
+ mSoftReverseReferences.trimToSize();
+ }
+ }
+
+ /**
+ * There is an underlying assumption that a class that is a soft reference will only have one
+ * referent.
+ *
+ * @return true if the instance is a soft reference type, or false otherwise
+ */
+ public boolean getIsSoftReference() {
+ return false;
+ }
+
+ @Nullable
+ protected Object readValue(@NonNull Type type) {
+ switch (type) {
+ case OBJECT:
+ long id = readId();
+ return mHeap.mSnapshot.findInstance(id);
+ case BOOLEAN:
+ return getBuffer().readByte() != 0;
+ case CHAR:
+ return getBuffer().readChar();
+ case FLOAT:
+ return getBuffer().readFloat();
+ case DOUBLE:
+ return getBuffer().readDouble();
+ case BYTE:
+ return getBuffer().readByte();
+ case SHORT:
+ return getBuffer().readShort();
+ case INT:
+ return getBuffer().readInt();
+ case LONG:
+ return getBuffer().readLong();
+ }
+ return null;
+ }
+
+ protected long readId() {
+ // As long as we don't interpret IDs, reading signed values here is fine.
+ switch (mHeap.mSnapshot.getTypeSize(Type.OBJECT)) {
+ case 1:
+ return getBuffer().readByte();
+ case 2:
+ return getBuffer().readShort();
+ case 4:
+ return getBuffer().readInt();
+ case 8:
+ return getBuffer().readLong();
+ }
+ return 0;
+ }
+
+ protected int readUnsignedByte() {
+ return UnsignedBytes.toInt(getBuffer().readByte());
+ }
+
+ protected int readUnsignedShort() {
+ return getBuffer().readShort() & 0xffff;
+ }
+
+ protected DataBuffer getBuffer() {
+ return mHeap.mSnapshot.getBuffer();
+ }
+
+
+ public static class CompositeSizeVisitor extends NonRecursiveVisitor {
+ int mSize = 0;
+
+ @Override
+ protected void defaultAction(Instance node) {
+ mSize += node.getSize();
+ }
+
+ public int getCompositeSize() {
+ return mSize;
+ }
+ }
+
+ public StackTrace getStack() {
+ return mStack;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/Main.java b/perflib/src/main/java/com/android/tools/perflib/heap/Main.java
new file mode 100644
index 0000000..1913e53
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/Main.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.tools.perflib.captures.DataBuffer;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+
+public class Main {
+
+ public static void main(String argv[]) {
+ try {
+ long start = System.nanoTime();
+ DataBuffer buffer = new MemoryMappedFileBuffer(new File(argv[0]));
+ Snapshot snapshot = Snapshot.createSnapshot(buffer);
+
+ testClassesQuery(snapshot);
+ testAllClassesQuery(snapshot);
+ testFindInstancesOf(snapshot);
+ testFindAllInstancesOf(snapshot);
+
+ System.out.println("Memory stats: free=" + Runtime.getRuntime().freeMemory()
+ + " / total=" + Runtime.getRuntime().totalMemory());
+ System.out.println("Time: " + (System.nanoTime() - start) / 1000000 + "ms");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void testClassesQuery(Snapshot snapshot) {
+ String[] x = new String[]{
+ "char[",
+ "javax.",
+ "org.xml.sax"
+ };
+
+ Map<String, Set<ClassObj>> someClasses = Queries.classes(snapshot, x);
+
+ for (String thePackage : someClasses.keySet()) {
+ System.out.println("------------------- " + thePackage);
+
+ Set<ClassObj> classes = someClasses.get(thePackage);
+
+ for (ClassObj theClass : classes) {
+ System.out.println(" " + theClass.mClassName);
+ }
+ }
+ }
+
+ private static void testAllClassesQuery(Snapshot snapshot) {
+ Map<String, Set<ClassObj>> allClasses = Queries.allClasses(snapshot);
+
+ for (String thePackage : allClasses.keySet()) {
+ System.out.println("------------------- " + thePackage);
+
+ Set<ClassObj> classes = allClasses.get(thePackage);
+
+ for (ClassObj theClass : classes) {
+ System.out.println(" " + theClass.mClassName);
+ }
+ }
+ }
+
+ private static void testFindInstancesOf(Snapshot snapshot) {
+ Instance[] instances = Queries.instancesOf(snapshot, "java.lang.String");
+
+ System.out.println("There are " + instances.length + " Strings.");
+ }
+
+ private static void testFindAllInstancesOf(Snapshot snapshot) {
+ Instance[] instances = Queries.allInstancesOf(snapshot,
+ "android.graphics.drawable.Drawable");
+
+ System.out.println("There are " + instances.length
+ + " instances of Drawables and its subclasses.");
+ }
+}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/NonRecursiveVisitor.java b/perflib/src/main/java/com/android/tools/perflib/heap/NonRecursiveVisitor.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/heap/NonRecursiveVisitor.java
rename to perflib/src/main/java/com/android/tools/perflib/heap/NonRecursiveVisitor.java
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/Queries.java b/perflib/src/main/java/com/android/tools/perflib/heap/Queries.java
new file mode 100644
index 0000000..b0c86e6
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/Queries.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import gnu.trove.TObjectProcedure;
+
+import java.util.*;
+
+public class Queries {
+ /*
+ * NOTES: Here's a list of the queries that can be done in hat and
+ * how you'd perform a similar query here in hit:
+ *
+ * hat hit
+ * ------------------------------------------------------------------------
+ * allClasses classes
+ * allClassesWithPlatform allClasses
+ * class findClass
+ * instances instancesOf
+ * allInstances allInstancesOf
+ * object findObject
+ * showRoots getRoots
+ * newInstances newInstances
+ *
+ * reachableFrom make a call to findObject to get the target
+ * parent object, this will give you an Instance.
+ * Then call visit(Set, Filter) on that to have
+ * it build the set of objects in its subgraph.
+ *
+ * rootsTo make a call to findObject on the leaf node
+ * in question, this will give you an Instance.
+ * Instances have an ArrayList of all of the
+ * parent objects that refer to it. You can
+ * follow those parent links until you hit an
+ * object whose parent is null or a ThreadObj.
+ * You've not successfully traced the paths to
+ * the roots.
+ */
+
+ private static final String DEFAULT_PACKAGE = "<default>";
+
+ /*
+ * Produce a collection of all classes, broken down by package.
+ * The keys of the resultant map iterate in sorted package order.
+ * The values of the map are the classes defined in each package.
+ */
+ @NonNull
+ public static Map<String, Set<ClassObj>> allClasses(@NonNull Snapshot snapshot) {
+ return classes(snapshot, null);
+ }
+
+ @NonNull
+ public static Map<String, Set<ClassObj>> classes(@NonNull Snapshot snapshot,
+ @Nullable String[] excludedPrefixes) {
+ TreeMap<String, Set<ClassObj>> result =
+ new TreeMap<String, Set<ClassObj>>();
+
+ Set<ClassObj> classes = new TreeSet<ClassObj>();
+
+ // Build a set of all classes across all heaps
+ for (Heap heap : snapshot.mHeaps) {
+ classes.addAll(heap.getClasses());
+ }
+
+ // Filter it if needed
+ if (excludedPrefixes != null) {
+ final int N = excludedPrefixes.length;
+ Iterator<ClassObj> iter = classes.iterator();
+
+ while (iter.hasNext()) {
+ ClassObj theClass = iter.next();
+ String classPath = theClass.toString();
+
+ for (int i = 0; i < N; i++) {
+ if (classPath.startsWith(excludedPrefixes[i])) {
+ iter.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ // Now that we have a final list of classes, group them by package
+ for (ClassObj theClass : classes) {
+ String packageName = DEFAULT_PACKAGE;
+ int lastDot = theClass.mClassName.lastIndexOf('.');
+
+ if (lastDot != -1) {
+ packageName = theClass.mClassName.substring(0, lastDot);
+ }
+
+ Set<ClassObj> classSet = result.get(packageName);
+
+ if (classSet == null) {
+ classSet = new TreeSet<ClassObj>();
+ result.put(packageName, classSet);
+ }
+
+ classSet.add(theClass);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a collection of classes common to both snapshots.
+ *
+ * <p>The query returns instances from the first snapshot. Note: two classes having the same
+ * fully-qualified name are considered equal, even if they differ in static fields, superclass,
+ * classloader, etc.
+ */
+ @NonNull
+ public static Collection<ClassObj> commonClasses(@NonNull Snapshot first,
+ @NonNull Snapshot second) {
+ Collection<ClassObj> classes = new ArrayList<ClassObj>();
+ for (Heap heap : first.getHeaps()) {
+ for (ClassObj clazz : heap.getClasses()) {
+ if (second.findClass(clazz.getClassName()) != null) {
+ classes.add(clazz);
+ }
+ }
+ }
+ return classes;
+ }
+
+ /*
+ * It's sorta sad that this is a pass-through call, but it seems like
+ * having all of the hat-like query methods in one place is a good thing
+ * even if there is duplication of effort.
+ */
+ public static ClassObj findClass(@NonNull Snapshot snapshot, String name) {
+ return snapshot.findClass(name);
+ }
+
+ /*
+ * Return an array of instances of the given class. This does not include
+ * instances of subclasses.
+ */
+ @NonNull
+ public static Instance[] instancesOf(@NonNull Snapshot snapshot, String baseClassName) {
+ ClassObj theClass = snapshot.findClass(baseClassName);
+
+ if (theClass == null) {
+ throw new IllegalArgumentException("Class not found: " + baseClassName);
+ }
+
+ List<Instance> instances = theClass.getInstancesList();
+ return instances.toArray(new Instance[instances.size()]);
+ }
+
+ /*
+ * Return an array of instances of the given class. This includes
+ * instances of subclasses.
+ */
+ @NonNull
+ public static Instance[] allInstancesOf(@NonNull Snapshot snapshot, String baseClassName) {
+ ClassObj theClass = snapshot.findClass(baseClassName);
+
+ if (theClass == null) {
+ throw new IllegalArgumentException("Class not found: " + baseClassName);
+ }
+
+ ArrayList<ClassObj> classList = new ArrayList<ClassObj>();
+
+ classList.add(theClass);
+ classList.addAll(traverseSubclasses(theClass));
+
+ ArrayList<Instance> instanceList = new ArrayList<Instance>();
+
+ for (ClassObj someClass : classList) {
+ instanceList.addAll(someClass.getInstancesList());
+ }
+
+ Instance[] result = new Instance[instanceList.size()];
+
+ instanceList.toArray(result);
+
+ return result;
+ }
+
+ @NonNull
+ private static ArrayList<ClassObj> traverseSubclasses(@NonNull ClassObj base) {
+ ArrayList<ClassObj> result = new ArrayList<ClassObj>();
+
+ for (ClassObj subclass : base.mSubclasses) {
+ result.add(subclass);
+ result.addAll(traverseSubclasses(subclass));
+ }
+
+ return result;
+ }
+
+ /*
+ * Find a reference to an object based on its id. The id should be
+ * in hexadecimal.
+ */
+ public static Instance findObject(@NonNull Snapshot snapshot, String id) {
+ long id2 = Long.parseLong(id, 16);
+
+ return snapshot.findInstance(id2);
+ }
+
+ @NonNull
+ public static Collection<RootObj> getRoots(@NonNull Snapshot snapshot) {
+ HashSet<RootObj> result = new HashSet<RootObj>();
+
+ for (Heap heap : snapshot.mHeaps) {
+ result.addAll(heap.mRoots);
+ }
+
+ return result;
+ }
+
+ @NonNull
+ public static final Instance[] newInstances(@NonNull Snapshot older, @NonNull Snapshot newer) {
+ final ArrayList<Instance> resultList = new ArrayList<Instance>();
+
+ for (Heap newHeap : newer.mHeaps) {
+ final Heap oldHeap = older.getHeap(newHeap.getName());
+
+ if (oldHeap == null) {
+ continue;
+ }
+
+ newHeap.forEachInstance(new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance instance) {
+ Instance oldInstance = oldHeap.getInstance(instance.mId);
+
+ /*
+ * If this instance wasn't in the old heap, or was there,
+ * but that ID was for an obj of a different type, then we have
+ * a newly allocated object and we should report it in the
+ * results.
+ */
+ if (oldInstance == null || (instance.getClassObj() != oldInstance.getClassObj())) {
+ resultList.add(instance);
+ }
+ return true;
+ }
+ });
+ }
+
+ Instance[] resultArray = new Instance[resultList.size()];
+
+ return resultList.toArray(resultArray);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/RootObj.java b/perflib/src/main/java/com/android/tools/perflib/heap/RootObj.java
new file mode 100644
index 0000000..b8ff9fd
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/RootObj.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+public class RootObj extends Instance {
+ public static final String UNDEFINED_CLASS_NAME = "no class defined!!";
+
+ RootType mType = RootType.UNKNOWN;
+
+ int mIndex;
+
+ int mThread;
+
+ public RootObj(RootType type) {
+ this(type, 0, 0, null);
+ }
+
+ public RootObj(RootType type, long id) {
+ this(type, id, 0, null);
+ }
+
+ public RootObj(RootType type, long id, int thread, StackTrace stack) {
+ super(id, stack);
+ mType = type;
+ mThread = thread;
+ }
+
+ public final String getClassName(@NonNull Snapshot snapshot) {
+ ClassObj theClass;
+
+ if (mType == RootType.SYSTEM_CLASS) {
+ theClass = snapshot.findClass(mId);
+ } else {
+ theClass = snapshot.findInstance(mId).getClassObj();
+ }
+
+ if (theClass == null) {
+ return UNDEFINED_CLASS_NAME;
+ }
+
+ return theClass.mClassName;
+ }
+
+ @Override
+ public void resolveReferences() {
+ // Do nothing.
+ }
+
+ @Override
+ public final void accept(Visitor visitor) {
+ visitor.visitRootObj(this);
+ Instance instance = getReferredInstance();
+ if (instance != null) {
+ visitor.visitLater(null, instance);
+ }
+ }
+
+ public final String toString() {
+ return String.format("%s at 0x%08x", mType.getName(), mId);
+ }
+
+ @Nullable
+ public Instance getReferredInstance() {
+ if (mType == RootType.SYSTEM_CLASS) {
+ return mHeap.mSnapshot.findClass(mId);
+ } else {
+ return mHeap.mSnapshot.findInstance(mId);
+ }
+ }
+
+ public RootType getRootType() {
+ return mType;
+ }
+}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/RootType.java b/perflib/src/main/java/com/android/tools/perflib/heap/RootType.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/heap/RootType.java
rename to perflib/src/main/java/com/android/tools/perflib/heap/RootType.java
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/Snapshot.java b/perflib/src/main/java/com/android/tools/perflib/heap/Snapshot.java
new file mode 100644
index 0000000..45b3ea3
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/Snapshot.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.perflib.analyzer.Capture;
+import com.android.tools.perflib.captures.DataBuffer;
+import com.android.tools.perflib.heap.analysis.ComputationProgress;
+import com.android.tools.perflib.heap.analysis.DominatorsBase;
+import com.android.tools.perflib.heap.analysis.LinkEvalDominators;
+import com.android.tools.perflib.heap.analysis.ShortestDistanceVisitor;
+import com.android.tools.perflib.heap.analysis.TopologicalSort;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import gnu.trove.THashSet;
+import gnu.trove.TIntObjectHashMap;
+import gnu.trove.TLongObjectHashMap;
+import gnu.trove.TObjectProcedure;
+
+/*
+ * A snapshot of all of the heaps, and related meta-data, for the runtime at a given instant.
+ *
+ * There are three possible heaps: default, app and zygote. GC roots are always reported in the
+ * default heap, and they are simply references to objects living in the zygote or the app heap.
+ * During parsing of the HPROF file HEAP_DUMP_INFO chunks change which heap is being referenced.
+ */
+public class Snapshot extends Capture {
+ public enum DominatorComputationStage {
+ INITIALIZING(new ComputationProgress("Preparing for dominator calculation...", 0), 0.1, 0.0),
+ RESOLVING_REFERENCES(new ComputationProgress("Resolving references...", 0), 0.1, 0.2),
+ COMPUTING_SHORTEST_DISTANCE(new ComputationProgress("Computing depth to nodes...", 0), 0.3,
+ 0.03),
+ COMPUTING_TOPOLOGICAL_SORT(new ComputationProgress("Performing topological sorting...", 0),
+ 0.33, 0.30),
+ COMPUTING_DOMINATORS(new ComputationProgress("Calculating dominators...", 0), 0.63, 0.35),
+ COMPUTING_RETAINED_SIZES(new ComputationProgress("Calculating retained sizes...", 0), 0.98,
+ 0.02);
+
+ private final ComputationProgress mInitialProgress;
+
+ private final double mOffset;
+
+ private final double mScale;
+
+ DominatorComputationStage(@NonNull ComputationProgress initialProgress, double offset,
+ double scale) {
+ mInitialProgress = initialProgress;
+ mOffset = offset;
+ mScale = scale;
+ }
+
+ public ComputationProgress getInitialProgress() {
+ return mInitialProgress;
+ }
+
+ public static double toAbsoluteProgressPercentage(@NonNull DominatorComputationStage baseStage,
+ @NonNull ComputationProgress computationProgress) {
+ return computationProgress.getProgress() * baseStage.mScale + baseStage.mOffset;
+ }
+ }
+
+ public static final String TYPE_NAME = "hprof";
+
+ private static final String JAVA_LANG_CLASS = "java.lang.Class";
+
+ // Special root object used in dominator computation for objects reachable via multiple roots.
+ public static final Instance SENTINEL_ROOT = new RootObj(RootType.UNKNOWN);
+
+ private static final int DEFAULT_HEAP_ID = 0;
+
+ @NonNull
+ private final DataBuffer mBuffer;
+
+ @NonNull
+ ArrayList<Heap> mHeaps = new ArrayList<Heap>();
+
+ @NonNull
+ Heap mCurrentHeap;
+
+ // List stack traces, which are lists of stack frames
+ @NonNull
+ TIntObjectHashMap<StackTrace> mTraces = new TIntObjectHashMap<StackTrace>();
+
+ // List of individual stack frames
+ @NonNull
+ TLongObjectHashMap<StackFrame> mFrames = new TLongObjectHashMap<StackFrame>();
+
+ private List<Instance> mTopSort;
+
+ private DominatorsBase mDominators;
+
+ private volatile DominatorComputationStage mDominatorComputationStage
+ = DominatorComputationStage.INITIALIZING;
+
+ // The set of all classes that are (sub)class(es) of java.lang.ref.Reference.
+ private THashSet<ClassObj> mReferenceClasses = new THashSet<ClassObj>();
+
+ private int[] mTypeSizes;
+
+ private long mIdSizeMask = 0x00000000ffffffffL;
+
+ @NonNull
+ public static Snapshot createSnapshot(@NonNull DataBuffer buffer) {
+ try {
+ Snapshot snapshot = new Snapshot(buffer);
+ HprofParser.parseBuffer(snapshot, buffer);
+ return snapshot;
+ } catch (RuntimeException e) {
+ buffer.dispose();
+ throw e;
+ }
+ }
+
+ @VisibleForTesting
+ public Snapshot(@NonNull DataBuffer buffer) {
+ mBuffer = buffer;
+ setToDefaultHeap();
+ }
+
+ public void dispose() {
+ mBuffer.dispose();
+ }
+
+ @NonNull
+ DataBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ @NonNull
+ public Heap setToDefaultHeap() {
+ return setHeapTo(DEFAULT_HEAP_ID, "default");
+ }
+
+ @NonNull
+ public Heap setHeapTo(int id, @NonNull String name) {
+ Heap heap = getHeap(id);
+
+ if (heap == null) {
+ heap = new Heap(id, name);
+ heap.mSnapshot = this;
+ mHeaps.add(heap);
+ }
+
+ mCurrentHeap = heap;
+
+ return mCurrentHeap;
+ }
+
+ public int getHeapIndex(@NonNull Heap heap) {
+ return mHeaps.indexOf(heap);
+ }
+
+ @Nullable
+ public Heap getHeap(int id) {
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < mHeaps.size(); i++) {
+ if (mHeaps.get(i).getId() == id) {
+ return mHeaps.get(i);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public Heap getHeap(@NonNull String name) {
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < mHeaps.size(); i++) {
+ if (name.equals(mHeaps.get(i).getName())) {
+ return mHeaps.get(i);
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ public Collection<Heap> getHeaps() {
+ return mHeaps;
+ }
+
+ @NonNull
+ public Collection<RootObj> getGCRoots() {
+ // Roots are always in the default heap.
+ return mHeaps.get(DEFAULT_HEAP_ID).mRoots;
+ }
+
+ public final void addStackFrame(@NonNull StackFrame theFrame) {
+ mFrames.put(theFrame.mId, theFrame);
+ }
+
+ public final StackFrame getStackFrame(long id) {
+ return mFrames.get(id);
+ }
+
+ public final void addStackTrace(@NonNull StackTrace theTrace) {
+ mTraces.put(theTrace.mSerialNumber, theTrace);
+ }
+
+ public final StackTrace getStackTrace(int traceSerialNumber) {
+ return mTraces.get(traceSerialNumber);
+ }
+
+ public final StackTrace getStackTraceAtDepth(int traceSerialNumber, int depth) {
+ StackTrace trace = mTraces.get(traceSerialNumber);
+
+ if (trace != null) {
+ trace = trace.fromDepth(depth);
+ }
+
+ return trace;
+ }
+
+ public final void addRoot(@NonNull RootObj root) {
+ mCurrentHeap.addRoot(root);
+ root.setHeap(mCurrentHeap);
+ }
+
+ public final void addThread(ThreadObj thread, int serialNumber) {
+ mCurrentHeap.addThread(thread, serialNumber);
+ }
+
+ public final ThreadObj getThread(int serialNumber) {
+ return mCurrentHeap.getThread(serialNumber);
+ }
+
+ public final void setIdSize(int size) {
+ int maxId = -1;
+ for (int i = 0; i < Type.values().length; ++i) {
+ maxId = Math.max(Type.values()[i].getTypeId(), maxId);
+ }
+ assert (maxId > 0) && (maxId <= Type.LONG
+ .getTypeId()); // Update this if hprof format ever changes its supported types.
+ mTypeSizes = new int[maxId + 1];
+ Arrays.fill(mTypeSizes, -1);
+
+ for (int i = 0; i < Type.values().length; ++i) {
+ mTypeSizes[Type.values()[i].getTypeId()] = Type.values()[i].getSize();
+ }
+ mTypeSizes[Type.OBJECT.getTypeId()] = size;
+ mIdSizeMask = 0xffffffffffffffffL >>> ((8 - size) * 8);
+ }
+
+ public final int getTypeSize(Type type) {
+ return mTypeSizes[type.getTypeId()];
+ }
+
+ public final long getIdSizeMask() {
+ return mIdSizeMask;
+ }
+
+ public final void addInstance(long id, @NonNull Instance instance) {
+ mCurrentHeap.addInstance(id, instance);
+ instance.setHeap(mCurrentHeap);
+ }
+
+ public final void addClass(long id, @NonNull ClassObj theClass) {
+ mCurrentHeap.addClass(id, theClass);
+ theClass.setHeap(mCurrentHeap);
+ }
+
+ @Nullable
+ public final Instance findInstance(long id) {
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < mHeaps.size(); i++) {
+ Instance instance = mHeaps.get(i).getInstance(id);
+
+ if (instance != null) {
+ return instance;
+ }
+ }
+
+ // Couldn't find an instance of a class, look for a class object
+ return findClass(id);
+ }
+
+ @Nullable
+ public final ClassObj findClass(long id) {
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < mHeaps.size(); i++) {
+ ClassObj theClass = mHeaps.get(i).getClass(id);
+
+ if (theClass != null) {
+ return theClass;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the first ClassObj with a class name that matches <code>name</code>.
+ *
+ * @param name of the class to find
+ * @return the found <code>ClassObj</code>, or null if not found
+ */
+ @Nullable
+ public final ClassObj findClass(String name) {
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < mHeaps.size(); i++) {
+ ClassObj theClass = mHeaps.get(i).getClass(name);
+
+ if (theClass != null) {
+ return theClass;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds all <code>ClassObj</code>s with class name that match the given <code>name</code>.
+ *
+ * @param name of the class to find
+ * @return a collection of the found <code>ClassObj</code>s, or empty collection if not found
+ */
+ @NonNull
+ public final Collection<ClassObj> findClasses(String name) {
+ ArrayList<ClassObj> classObjs = new ArrayList<ClassObj>();
+
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < mHeaps.size(); i++) {
+ classObjs.addAll(mHeaps.get(i).getClasses(name));
+ }
+
+ return classObjs;
+ }
+
+ public void resolveClasses() {
+ ClassObj clazz = findClass(JAVA_LANG_CLASS);
+ int javaLangClassSize = clazz != null ? clazz.getInstanceSize() : 0;
+
+ for (Heap heap : mHeaps) {
+ for (ClassObj classObj : heap.getClasses()) {
+ ClassObj superClass = classObj.getSuperClassObj();
+ if (superClass != null) {
+ superClass.addSubclass(classObj);
+ }
+ // We under-approximate the size of the class by including the size of Class.class
+ // and the size of static fields, and omitting padding, vtable and imtable sizes.
+ int classSize = javaLangClassSize;
+
+ for (Field f : classObj.mStaticFields) {
+ classSize += getTypeSize(f.getType());
+ }
+ classObj.setSize(classSize);
+ }
+
+ final int heapId = heap.getId();
+ heap.forEachInstance(new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance instance) {
+ ClassObj classObj = instance.getClassObj();
+ if (classObj != null) {
+ classObj.addInstance(heapId, instance);
+ }
+ return true;
+ }
+ });
+ }
+ }
+
+ public void identifySoftReferences() {
+ List<ClassObj> referenceDescendants = findAllDescendantClasses(
+ ClassObj.getReferenceClassName());
+ for (ClassObj classObj : referenceDescendants) {
+ classObj.setIsSoftReference();
+ mReferenceClasses.add(classObj);
+ }
+ }
+
+ public void resolveReferences() {
+ for (Heap heap : getHeaps()) {
+ for (ClassObj clazz : heap.getClasses()) {
+ clazz.resolveReferences();
+ }
+ heap.forEachInstance(new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance instance) {
+ instance.resolveReferences();
+ return true;
+ }
+ });
+ }
+ }
+
+ public void compactMemory() {
+ for (Heap heap : getHeaps()) {
+ heap.forEachInstance(new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance instance) {
+ instance.compactMemory();
+ return true;
+ }
+ });
+ }
+ }
+
+ @NonNull
+ public List<ClassObj> findAllDescendantClasses(@NonNull String className) {
+ Collection<ClassObj> ancestorClasses = findClasses(className);
+ List<ClassObj> descendants = new ArrayList<ClassObj>();
+ for (ClassObj ancestor : ancestorClasses) {
+ descendants.addAll(ancestor.getDescendantClasses());
+ }
+ return descendants;
+ }
+
+ public void computeDominators() {
+ prepareDominatorComputation();
+ doComputeDominators(new LinkEvalDominators(this));
+ }
+
+ @VisibleForTesting
+ public void prepareDominatorComputation() {
+ if (mDominators != null) {
+ return;
+ }
+
+ mDominatorComputationStage = DominatorComputationStage.RESOLVING_REFERENCES;
+ resolveReferences();
+ compactMemory();
+
+ mDominatorComputationStage = DominatorComputationStage.COMPUTING_SHORTEST_DISTANCE;
+ ShortestDistanceVisitor shortestDistanceVisitor = new ShortestDistanceVisitor();
+ shortestDistanceVisitor.doVisit(getGCRoots());
+
+ mDominatorComputationStage = DominatorComputationStage.COMPUTING_TOPOLOGICAL_SORT;
+ mTopSort = TopologicalSort.compute(getGCRoots());
+ for (Instance instance : mTopSort) {
+ instance.dedupeReferences();
+ }
+ }
+
+ @VisibleForTesting
+ public void doComputeDominators(@NonNull DominatorsBase computable) {
+ if (mDominators != null) {
+ return;
+ }
+
+ mDominators = computable;
+ mDominatorComputationStage = DominatorComputationStage.COMPUTING_DOMINATORS;
+ mDominators.computeDominators();
+
+ mDominatorComputationStage = DominatorComputationStage.COMPUTING_RETAINED_SIZES;
+ mDominators.computeRetainedSizes();
+ }
+
+ @NonNull
+ public ComputationProgress getComputationProgress() {
+ if (mDominatorComputationStage == DominatorComputationStage.COMPUTING_DOMINATORS) {
+ return mDominators.getComputationProgress();
+ } else {
+ return mDominatorComputationStage.getInitialProgress();
+ }
+ }
+
+ public DominatorComputationStage getDominatorComputationStage() {
+ return mDominatorComputationStage;
+ }
+
+ @NonNull
+ public List<Instance> getReachableInstances() {
+ List<Instance> result = new ArrayList<Instance>(mTopSort.size());
+ for (Instance node : mTopSort) {
+ if (node.getImmediateDominator() != null) {
+ result.add(node);
+ }
+ }
+ return result;
+ }
+
+ public List<Instance> getTopologicalOrdering() {
+ return mTopSort;
+ }
+
+ public final void dumpInstanceCounts() {
+ for (Heap heap : mHeaps) {
+ System.out.println("+------------------ instance counts for heap: " + heap.getName());
+ heap.dumpInstanceCounts();
+ }
+ }
+
+ public final void dumpSizes() {
+ for (Heap heap : mHeaps) {
+ System.out.println("+------------------ sizes for heap: " + heap.getName());
+ heap.dumpSizes();
+ }
+ }
+
+ public final void dumpSubclasses() {
+ for (Heap heap : mHeaps) {
+ System.out.println("+------------------ subclasses for heap: " + heap.getName());
+ heap.dumpSubclasses();
+ }
+ }
+
+ @Nullable
+ @Override
+ public <T> T getRepresentation(Class<T> asClass) {
+ if (asClass.isAssignableFrom(getClass())) {
+ return asClass.cast(this);
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public String getTypeName() {
+ return TYPE_NAME;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/StackFrame.java b/perflib/src/main/java/com/android/tools/perflib/heap/StackFrame.java
new file mode 100644
index 0000000..47fc38c
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/StackFrame.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+
+public class StackFrame {
+
+ public static final int NO_LINE_NUMBER = 0;
+
+ public static final int UNKNOWN_LOCATION = -1;
+
+ public static final int COMPILED_METHOD = -2;
+
+ public static final int NATIVE_METHOD = -3;
+
+ long mId;
+
+ String mMethodName;
+
+ String mSignature;
+
+ String mFilename;
+
+ int mSerialNumber;
+
+ int mLineNumber;
+
+ public StackFrame(long id, String method, String sig, String file,
+ int serial, int line) {
+ mId = id;
+ mMethodName = method;
+ mSignature = sig;
+ mFilename = file;
+ mSerialNumber = serial;
+ mLineNumber = line;
+ }
+
+ @NonNull
+ private String lineNumberString() {
+ switch (mLineNumber) {
+ case NO_LINE_NUMBER:
+ return "No line number";
+ case UNKNOWN_LOCATION:
+ return "Unknown line number";
+ case COMPILED_METHOD:
+ return "Compiled method";
+ case NATIVE_METHOD:
+ return "Native method";
+
+ default:
+ return String.valueOf(mLineNumber);
+ }
+ }
+
+ public String getMethodName() {
+ return mMethodName;
+ }
+
+ public String getSignature() {
+ return mSignature;
+ }
+
+ public String getFilename() {
+ return mFilename;
+ }
+
+ public int getLineNumber() {
+ return mLineNumber;
+ }
+
+ @NonNull
+ public final String toString() {
+ return mMethodName
+ + mSignature.replace('/', '.')
+ + " - "
+ + mFilename + ":"
+ + lineNumberString();
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/StackTrace.java b/perflib/src/main/java/com/android/tools/perflib/heap/StackTrace.java
new file mode 100644
index 0000000..d5ebb34
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/StackTrace.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+public class StackTrace {
+
+ int mSerialNumber;
+
+ int mThreadSerialNumber;
+
+ StackFrame[] mFrames;
+
+ /*
+ * For subsets of the stack frame we'll reference the parent list of frames
+ * but keep track of its offset into the parent's list of stack frame ids.
+ * This alleviates the need to constantly be duplicating subsections of the
+ * list of stack frame ids.
+ */
+ @Nullable
+ StackTrace mParent = null;
+
+ int mOffset = 0;
+
+ private StackTrace() {
+
+ }
+
+ public StackTrace(int serial, int thread, StackFrame[] frames) {
+ mSerialNumber = serial;
+ mThreadSerialNumber = thread;
+ mFrames = frames;
+ }
+
+ @NonNull
+ public final StackTrace fromDepth(int startingDepth) {
+ StackTrace result = new StackTrace();
+
+ if (mParent != null) {
+ result.mParent = mParent;
+ } else {
+ result.mParent = this;
+ }
+
+ result.mOffset = startingDepth + mOffset;
+
+ return result;
+ }
+
+ public final void dump() {
+ final int N = mFrames.length;
+
+ for (int i = 0; i < N; i++) {
+ System.out.println(mFrames[i].toString());
+ }
+ }
+
+ public StackFrame[] getFrames() {
+ return mFrames;
+ }
+
+ public int getSerialNumber() {
+ return mSerialNumber;
+ }
+}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/ThreadObj.java b/perflib/src/main/java/com/android/tools/perflib/heap/ThreadObj.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/heap/ThreadObj.java
rename to perflib/src/main/java/com/android/tools/perflib/heap/ThreadObj.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Type.java b/perflib/src/main/java/com/android/tools/perflib/heap/Type.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/heap/Type.java
rename to perflib/src/main/java/com/android/tools/perflib/heap/Type.java
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/Value.java b/perflib/src/main/java/com/android/tools/perflib/heap/Value.java
new file mode 100644
index 0000000..ec9fce3
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/Value.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+public class Value {
+
+ private Object mValue;
+
+ /**
+ * The instance this value belongs to.
+ */
+ private final Instance instance;
+
+ public Value(Instance instance) {
+ this.instance = instance;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setValue(Object value) {
+ mValue = value;
+
+ if (value instanceof Instance) {
+ ((Instance) value).addReverseReference(null, instance);
+ }
+ }
+}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/heap/Visitor.java b/perflib/src/main/java/com/android/tools/perflib/heap/Visitor.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/heap/Visitor.java
rename to perflib/src/main/java/com/android/tools/perflib/heap/Visitor.java
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ComputationProgress.java b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ComputationProgress.java
new file mode 100644
index 0000000..e272fa8
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ComputationProgress.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.annotations.NonNull;
+
+public class ComputationProgress {
+ @NonNull
+ private String mMessage;
+
+ // Progress is a number in [0,1], where 0 represents just started, and 1 is done.
+ private double mProgress;
+
+ public ComputationProgress(@NonNull String message, double progress) {
+ mMessage = message;
+ mProgress = progress;
+ }
+
+ @NonNull
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public void setMessage(@NonNull String message) {
+ mMessage = message;
+ }
+
+ public double getProgress() {
+ return mProgress;
+ }
+
+ public void setProgress(double progress) {
+ mProgress = progress;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/analysis/DominatorsBase.java b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/DominatorsBase.java
new file mode 100644
index 0000000..5ff0c84
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/DominatorsBase.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Snapshot;
+import com.google.common.collect.Iterables;
+import gnu.trove.TObjectProcedure;
+
+import java.util.List;
+
+public abstract class DominatorsBase {
+ @NonNull
+ protected volatile ComputationProgress mCurrentProgress = new ComputationProgress(
+ "Starting dominator computation", 0.0);
+
+ protected Snapshot mSnapshot;
+
+ @NonNull
+ protected List<Instance> mTopSort;
+
+ protected DominatorsBase(@NonNull Snapshot snapshot) {
+ mSnapshot = snapshot;
+ assert mSnapshot.getTopologicalOrdering() != null;
+ mTopSort = mSnapshot.getTopologicalOrdering();
+
+ // Initialize retained sizes for all classes and objects, including unreachable ones.
+ for (Heap heap : mSnapshot.getHeaps()) {
+ for (Instance instance : heap.getClasses()) {
+ instance.resetRetainedSize();
+ }
+ heap.forEachInstance(new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance instance) {
+ instance.resetRetainedSize();
+ return true;
+ }
+ });
+ }
+ }
+
+ public void dispose() {
+ mSnapshot = null;
+ }
+
+ @NonNull
+ public abstract ComputationProgress getComputationProgress();
+
+ /**
+ * Kicks off the computation of dominators.
+ */
+ public abstract void computeDominators();
+
+ /**
+ * Computes retained sizes of instances. Only call this AFTER dominator computation.
+ */
+ public void computeRetainedSizes() {
+ // We only update the retained sizes of objects in the dominator tree (i.e. reachable).
+ for (Instance node : mSnapshot.getReachableInstances()) {
+ int heapIndex = mSnapshot.getHeapIndex(node.getHeap());
+ // Add the size of the current node to the retained size of every dominator up to the
+ // root, in the same heap.
+ for (Instance dom = node.getImmediateDominator(); dom != Snapshot.SENTINEL_ROOT;
+ dom = dom.getImmediateDominator()) {
+ assert dom != null;
+ dom.addRetainedSize(heapIndex, node.getSize());
+ }
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/analysis/LinkEvalDominators.java b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/LinkEvalDominators.java
new file mode 100644
index 0000000..f11ff4f
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/LinkEvalDominators.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import com.android.tools.perflib.heap.Snapshot;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.util.*;
+
+import gnu.trove.TIntStack;
+import gnu.trove.TObjectProcedure;
+
+/**
+ * Computes dominators based on the union-find data structure with path compression and linking by
+ * size. Using description found in: http://adambuchsbaum.com/papers/dom-toplas.pdf which is based
+ * on a copy of the paper available at:
+ * http://www.cc.gatech.edu/~harrold/6340/cs6340_fall2009/Readings/lengauer91jul.pdf
+ */
+public final class LinkEvalDominators extends DominatorsBase {
+ @NonNull
+ private ArrayList<LinkEvalNode> mNodes;
+
+ @NonNull
+ private LinkEval mLinkEval;
+
+ private volatile int mSemiDominatorProgress = 0;
+
+ private volatile int mDominatorProgress = 0;
+
+ public LinkEvalDominators(@NonNull Snapshot snapshot) {
+ super(snapshot);
+
+ final Map<Instance, LinkEvalNode> instanceNodeMap = new HashMap<Instance, LinkEvalNode>();
+ TObjectProcedure<Instance> mapProcedure = new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance instance) {
+ LinkEvalNode node = new LinkEvalNode(instance);
+ instanceNodeMap.put(instance, node);
+ return true;
+ }
+ };
+ for (Heap heap : mSnapshot.getHeaps()) {
+ for (Instance instance : heap.getClasses()) {
+ mapProcedure.execute(instance);
+ }
+ heap.forEachInstance(mapProcedure);
+ }
+
+ for (LinkEvalNode node : instanceNodeMap.values()) {
+ node.finalize(instanceNodeMap);
+ }
+
+ Collection<RootObj> roots = snapshot.getGCRoots();
+ Set<Instance> filteredRootInstances = new HashSet<Instance>(roots.size());
+ for (RootObj root : roots) {
+ Instance referredInstance = root.getReferredInstance();
+ if (referredInstance != null) {
+ filteredRootInstances.add(referredInstance);
+ }
+ }
+ Instance[] gcRootInstances = filteredRootInstances
+ .toArray(new Instance[filteredRootInstances.size()]);
+
+ // Manually construct the sentinel root.
+ LinkEvalNode[] rootNodes = new LinkEvalNode[gcRootInstances.length];
+ for (int i = 0; i < rootNodes.length; ++i) {
+ rootNodes[i] = instanceNodeMap.get(gcRootInstances[i]);
+ }
+ LinkEvalNode sentinelRootNode = new SentinelNode(Snapshot.SENTINEL_ROOT, instanceNodeMap,
+ rootNodes);
+ instanceNodeMap.put(Snapshot.SENTINEL_ROOT, sentinelRootNode);
+ for (LinkEvalNode rootNode : rootNodes) {
+ rootNode.setParent(sentinelRootNode);
+ // Stuff the sentinel root into the back references of root object Nodes.
+ LinkEvalNode[] backReferences = rootNode.getBackReferences();
+ LinkEvalNode[] augmentedBackReferences = new LinkEvalNode[backReferences.length + 1];
+ System.arraycopy(backReferences, 0, augmentedBackReferences, 1, backReferences.length);
+ augmentedBackReferences[0] = sentinelRootNode;
+ rootNode.setBackReferences(augmentedBackReferences);
+ }
+
+ mNodes = new ArrayList<LinkEvalNode>();
+ depthFirstSearch(sentinelRootNode);
+ mNodes.trimToSize();
+
+ mLinkEval = new LinkEval();
+ }
+
+ @NonNull
+ @Override
+ public ComputationProgress getComputationProgress() {
+ String progressMessage;
+ double progress;
+ if (mSemiDominatorProgress < mNodes.size()) {
+ progressMessage = String
+ .format("Calculating semi-dominators %d/%d", mSemiDominatorProgress,
+ mNodes.size());
+ progress = 0.5 * (double) mSemiDominatorProgress / (double) mNodes.size();
+ } else {
+ progressMessage = String
+ .format("Calculating immediate dominators %d/%d", mDominatorProgress,
+ mNodes.size());
+ progress = 0.5 + 0.5 * (double) mDominatorProgress / (double) mNodes.size();
+ }
+ mCurrentProgress.setMessage(progressMessage);
+ mCurrentProgress.setProgress(progress);
+ return mCurrentProgress;
+ }
+
+ @Override
+ public void computeDominators() {
+ for (int i = mNodes.size() - 1; i > 0; --i, mSemiDominatorProgress = mNodes.size() - i) {
+ LinkEvalNode currentNode = mNodes.get(i);
+
+ // Step 2 of paper.
+ for (LinkEvalNode predecessor : currentNode.getBackReferences()) {
+ LinkEvalNode u = mLinkEval.eval(predecessor);
+ if (u.getSemiDominator().getTopologicalOrder() < currentNode.getSemiDominator()
+ .getTopologicalOrder()) {
+ currentNode.setSemiDominator(u.getSemiDominator());
+ }
+ }
+
+ currentNode.getSemiDominator().getDominates().add(currentNode);
+ LinkEvalNode parent = currentNode.getParent();
+ assert parent != null;
+ LinkEval.link(parent, currentNode);
+
+ // Step 3 of paper.
+ // Manual recursion-to-loop conversion of the following code:
+ //
+ //for (Iterator<TarjanNode> it = parent.getDominates().iterator(); it.hasNext();) {
+ // TarjanNode dominatedNode = it.next();
+ // it.remove();
+ // TarjanNode u = LinkEval.eval(dominatedNode);
+ // dominatedNode.setImmediateDominator(
+ // u.getSemiDominator().getTopologicalOrder() <
+ // dominatedNode.getSemiDominator().getTopologicalOrder() ? u : parent);
+ //}
+
+ for (LinkEvalNode node : parent.getDominates()) {
+ LinkEvalNode u = mLinkEval.eval(node);
+ node.setImmediateDominator(
+ u.getSemiDominator().getTopologicalOrder() < node.getSemiDominator()
+ .getTopologicalOrder() ? u : parent);
+ }
+ parent.getDominates().clear(); // Bulk remove (slightly different from paper).
+ parent.getDominates().trimToSize();
+ }
+
+ // Step 4 of paper.
+ for (int i = 1; i < mNodes.size(); ++i) {
+ LinkEvalNode currentNode = mNodes.get(i);
+ if (currentNode.getImmediateDominator() != currentNode.getSemiDominator()) {
+ LinkEvalNode dominator = currentNode.getImmediateDominator();
+ assert dominator != null;
+ assert dominator.getImmediateDominator() != null;
+ currentNode.setImmediateDominator(dominator.getImmediateDominator());
+ }
+ mDominatorProgress = i;
+ }
+ }
+
+ /**
+ * Depth-first search in loop form, since the recursive version blows the stack.
+ */
+ private void depthFirstSearch(@NonNull LinkEvalNode root) {
+ // Manual recursion-to-loop conversion of the following code:
+ //
+ // private int depthFirstSearch(int topologicalOrder, @NonNull LinkEvalNode currentNode) {
+ // currentNode.setTopologicalOrder(topologicalOrder);
+ // ++topologicalOrder;
+ // mNodes.add(currentNode);
+ // currentNode.setSemiDominator(currentNode);
+ // for (LinkEvalNode forwardReference : currentNode.getForwardReferences()) {
+ // if (forwardReference.getSemiDominator() == null) {
+ // forwardReference.setParent(currentNode);
+ // topologicalOrder = depthFirstSearch(topologicalOrder, forwardReference);
+ // }
+ // }
+ // return topologicalOrder;
+ //}
+
+ Stack<LinkEvalNode> nodeStack = new Stack<LinkEvalNode>();
+ TIntStack childOffsetStack = new TIntStack();
+ int topologicalOrder = 0;
+
+ LinkEvalNode currentNode;
+ int currentChildOffset;
+
+ nodeStack.push(root);
+ childOffsetStack.push(0);
+
+ while (!nodeStack.empty()) {
+ currentNode = nodeStack.pop();
+ currentChildOffset = childOffsetStack.pop();
+
+ if (currentNode.getSemiDominator() == null) {
+ currentNode.setTopologicalOrder(topologicalOrder++);
+ currentNode.setSemiDominator(currentNode);
+ mNodes.add(currentNode);
+ }
+
+ LinkEvalNode[] forwardReferences = currentNode.getForwardReferences();
+ while (currentChildOffset < forwardReferences.length) {
+ LinkEvalNode successor = forwardReferences[currentChildOffset];
+ if (successor.getSemiDominator() == null) {
+ successor.setParent(currentNode);
+ nodeStack.push(currentNode);
+ childOffsetStack.push(currentChildOffset + 1);
+ nodeStack.push(successor);
+ childOffsetStack.push(0);
+ break;
+ }
+ ++currentChildOffset;
+ }
+ }
+ }
+
+ protected static class LinkEval {
+ @NonNull
+ private List<LinkEvalNode> mCompressArray = new ArrayList<LinkEvalNode>();
+
+ public static void link(@NonNull LinkEvalNode ancestor, @NonNull LinkEvalNode child) {
+ child.setAncestor(ancestor);
+ }
+
+ //private static void compress(@NonNull LinkEvalNode node) {
+ // assert node.getAncestor() != null;
+ // if (node.getAncestor().getAncestor() != null) {
+ // compressBackup(node.getAncestor());
+ // if (node.getAncestor().getLabel().getSemiDominator().getTopologicalOrder() <
+ // node.getLabel().getSemiDominator().getTopologicalOrder()) {
+ // node.setLabel(node.getAncestor().getLabel());
+ // }
+ // node.setAncestor(node.getAncestor().getAncestor());
+ // }
+ //}
+
+ private void compress(@NonNull LinkEvalNode node) {
+ assert mCompressArray.isEmpty();
+ assert node.getAncestor() != null;
+ while (node.getAncestor().getAncestor() != null) {
+ mCompressArray.add(node);
+ node = node.getAncestor();
+ assert node.getAncestor() != null;
+ }
+
+ for (LinkEvalNode toCompress : Lists.reverse(mCompressArray)) {
+ LinkEvalNode ancestor = toCompress.getAncestor();
+ assert ancestor != null;
+ if (ancestor.getLabel().getSemiDominator().getTopologicalOrder() <
+ toCompress.getLabel().getSemiDominator().getTopologicalOrder()) {
+ toCompress.setLabel(ancestor.getLabel());
+ }
+ toCompress.setAncestor(ancestor.getAncestor());
+ }
+ mCompressArray.clear();
+ }
+
+ public LinkEvalNode eval(@NonNull LinkEvalNode node) {
+ if (node.getAncestor() == null) {
+ return node;
+ } else {
+ compress(node);
+ return node.getLabel();
+ }
+ }
+ }
+
+ protected static class LinkEvalNode {
+ // A map from Node to Instances to map data within Instances to Nodes.
+ protected Map<Instance, LinkEvalNode> mInstanceLookup;
+
+ @NonNull
+ protected Instance mInstance;
+
+ @NonNull
+ protected LinkEvalNode[] mForwardReferences;
+
+ @NonNull
+ protected LinkEvalNode[] mBackReferences;
+
+ // The semi-dominator of this node.
+ protected LinkEvalNode mSemiDominator;
+
+ // The parent node in the DFS spanning tree.
+ @Nullable
+ protected LinkEvalNode mParent;
+
+ @Nullable
+ protected LinkEvalNode mAncestor;
+
+ @NonNull
+ protected LinkEvalNode mLabel;
+
+ // The nodes for which this node is the semi-dominator of.
+ protected ArrayList<LinkEvalNode> mSemisDominated;
+
+ public LinkEvalNode(@NonNull Instance instance) {
+ mInstance = instance;
+ mInstance.setTopologicalOrder(0);
+ mSemiDominator = null;
+ mParent = null;
+ mAncestor = null;
+ mLabel = this;
+ mSemisDominated = new ArrayList<LinkEvalNode>(1);
+ }
+
+ public final Instance getInstance() {
+ return mInstance;
+ }
+
+ public LinkEvalNode[] getForwardReferences() {
+ return mForwardReferences;
+ }
+
+ public LinkEvalNode[] getBackReferences() {
+ return mBackReferences;
+ }
+
+ public void setBackReferences(@NonNull LinkEvalNode[] backReferences) {
+ mBackReferences = backReferences;
+ }
+
+ public void finalize(@NonNull Map<Instance, LinkEvalNode> instanceLookup) {
+ mInstanceLookup = instanceLookup;
+ mForwardReferences = new LinkEvalNode[mInstance.getHardForwardReferences().size()];
+
+ int i = 0;
+ for (Instance instance : mInstance.getHardForwardReferences()) {
+ mForwardReferences[i++] = instanceLookup.get(instance);
+ }
+
+ // Filter reverse reference list for unreachable nodes.
+ List<LinkEvalNode> backReferenceInstances = new ArrayList<LinkEvalNode>(
+ mInstance.getHardReverseReferences().size());
+ for (Instance instance : mInstance.getHardReverseReferences()) {
+ if (instance.isReachable()) {
+ backReferenceInstances.add(instanceLookup.get(instance));
+ }
+ }
+ mBackReferences = backReferenceInstances
+ .toArray(new LinkEvalNode[backReferenceInstances.size()]);
+ }
+
+ public final void setImmediateDominator(@NonNull LinkEvalNode node) {
+ mInstance.setImmediateDominator(node.getInstance());
+ }
+
+ @Nullable
+ public final LinkEvalNode getImmediateDominator() {
+ return mInstanceLookup.get(mInstance.getImmediateDominator());
+ }
+
+ public final int getTopologicalOrder() {
+ return mInstance.getTopologicalOrder();
+ }
+
+ public void setSemiDominator(@NonNull LinkEvalNode node) {
+ mSemiDominator = node;
+ }
+
+ public LinkEvalNode getSemiDominator() {
+ return mSemiDominator;
+ }
+
+ @Nullable
+ public LinkEvalNode getParent() {
+ return mParent;
+ }
+
+ public void setParent(@NonNull LinkEvalNode parent) {
+ mParent = parent;
+ }
+
+ @Nullable
+ public LinkEvalNode getAncestor() {
+ return mAncestor;
+ }
+
+ public void setAncestor(@NonNull LinkEvalNode ancestor) {
+ mAncestor = ancestor;
+ }
+
+ @NonNull
+ public LinkEvalNode getLabel() {
+ return mLabel;
+ }
+
+ public void setLabel(@NonNull LinkEvalNode node) {
+ mLabel = node;
+ }
+
+ public void setTopologicalOrder(int order) {
+ mInstance.setTopologicalOrder(order);
+ mSemiDominator = this;
+ }
+
+ public ArrayList<LinkEvalNode> getDominates() {
+ return mSemisDominated;
+ }
+ }
+
+ protected static class SentinelNode extends LinkEvalNode {
+
+ public SentinelNode(@NonNull Instance instance,
+ @NonNull Map<Instance, LinkEvalNode> instanceLookup,
+ @NonNull LinkEvalNode[] roots) {
+ super(instance);
+ mInstanceLookup = instanceLookup;
+ mForwardReferences = roots;
+ mBackReferences = new LinkEvalNode[0];
+ }
+
+ @Override
+ public final void finalize(@NonNull Map<Instance, LinkEvalNode> instanceLookup) {
+ throw new RuntimeException("This method should not be called.");
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ShortestDistanceVisitor.java b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ShortestDistanceVisitor.java
new file mode 100644
index 0000000..cc304ca
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/ShortestDistanceVisitor.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.NonRecursiveVisitor;
+
+import java.util.Comparator;
+import java.util.PriorityQueue;
+
+public class ShortestDistanceVisitor extends NonRecursiveVisitor {
+ private PriorityQueue<Instance> mPriorityQueue = new PriorityQueue<Instance>(1024,
+ new Comparator<Instance>() {
+ @Override
+ public int compare(Instance o1, Instance o2) {
+ return o1.getDistanceToGcRoot() - o2.getDistanceToGcRoot();
+ }
+ });
+
+ private Instance mPreviousInstance = null;
+
+ private int mVisitDistance = 0;
+
+ @Override
+ public void visitLater(Instance parent, @NonNull Instance child) {
+ if (mVisitDistance < child.getDistanceToGcRoot() &&
+ (parent == null ||
+ child.getSoftReverseReferences() == null ||
+ !child.getSoftReverseReferences().contains(parent) ||
+ child.getIsSoftReference())) {
+ child.setDistanceToGcRoot(mVisitDistance);
+ child.setNextInstanceToGcRoot(mPreviousInstance);
+ mPriorityQueue.add(child);
+ }
+ }
+
+ @Override
+ public void doVisit(Iterable<? extends Instance> startNodes) {
+ // root nodes are instances that share the same id as the node they point to.
+ // This means that we cannot mark them as visited here or they would be marking
+ // the actual root instance
+ // TODO RootObj should not be Instance objects
+ for (Instance node : startNodes) {
+ node.accept(this);
+ }
+
+ while (!mPriorityQueue.isEmpty()) {
+ Instance node = mPriorityQueue.poll();
+ mVisitDistance = node.getDistanceToGcRoot() + 1;
+ mPreviousInstance = node;
+ node.accept(this);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/analysis/TopologicalSort.java b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/TopologicalSort.java
new file mode 100644
index 0000000..9fa8f49
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/analysis/TopologicalSort.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.NonRecursiveVisitor;
+import com.android.tools.perflib.heap.RootObj;
+import com.android.tools.perflib.heap.Snapshot;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import gnu.trove.TLongHashSet;
+
+public class TopologicalSort {
+ @NonNull
+ public static List<Instance> compute(@NonNull Iterable<RootObj> roots) {
+ TopologicalSortVisitor visitor = new TopologicalSortVisitor();
+ visitor.doVisit(roots);
+ List<Instance> instances = visitor.getPreorderedInstances();
+
+ // We add the special sentinel node as the single root of the object graph, to ensure the
+ // dominator algorithm terminates when having to choose between two GC roots.
+ Snapshot.SENTINEL_ROOT.setTopologicalOrder(0);
+
+ // Set localIDs in the range 1..keys.size(). This simplifies the algorithm & data structures
+ // for dominator computation.
+ int currentIndex = 0;
+ for (Instance node : instances) {
+ node.setTopologicalOrder(++currentIndex);
+ }
+
+ return instances;
+ }
+
+ /**
+ * Topological sort visitor computing a post-order traversal of the graph.
+ *
+ * We use the classic iterative three-color marking algorithm in order to correctly compute the
+ * finishing time for each node. Nodes in decreasing order of their finishing time satisfy the
+ * topological order property, i.e. any node appears before its successors.
+ */
+ private static class TopologicalSortVisitor extends NonRecursiveVisitor {
+
+ // Marks nodes that have been fully visited and popped off the stack.
+ private final TLongHashSet mVisited = new TLongHashSet();
+
+ private final List<Instance> mPostorder = Lists.newArrayList();
+
+ @Override
+ public void visitLater(Instance parent, @NonNull Instance child) {
+ if (!mSeen.contains(child.getId())) {
+ mStack.push(child);
+ }
+ }
+
+ @Override
+ public void doVisit(Iterable<? extends Instance> startNodes) {
+ // root nodes are instances that share the same id as the node they point to.
+ // This means that we cannot mark them as visited here or they would be marking
+ // the actual root instance
+ // TODO RootObj should not be Instance objects
+
+ for (Instance node : startNodes) {
+ node.accept(this);
+ }
+ while (!mStack.isEmpty()) {
+ Instance node = mStack.peek();
+ if (mSeen.add(node.getId())) {
+ node.accept(this);
+ } else {
+ mStack.pop();
+ if (mVisited.add(node.getId())) {
+ mPostorder.add(node);
+ }
+ }
+ }
+ }
+
+ List<Instance> getPreorderedInstances() {
+ return Lists.reverse(mPostorder);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/Hprof.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/Hprof.java
new file mode 100644
index 0000000..88d814a
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/Hprof.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import com.google.common.base.Charsets;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A structure representing heap dump data in the hprof format.
+ */
+public class Hprof {
+ public final String format;
+ public final int idSize;
+ public final Date date;
+ public final List<HprofRecord> records;
+
+ /**
+ * Construct an HprofBuilder with the given format, identifier size, date
+ * date, and records
+ * @param format - "JAVA PROFILE 1.0.3", for example.
+ * @param idSize - The size of identifiers in bytes.
+ * @param date - The date of the hprof dump.
+ * @param records - The list of hprof records.
+ */
+ public Hprof(String format, int idSize, Date date, List<HprofRecord> records) {
+ this.format = format;
+ this.idSize = idSize;
+ this.date = date;
+ this.records = records;
+ }
+
+ /**
+ * Write the hprof data to the given output stream in binary format.
+ * @param filename - The file to write to.
+ */
+ public void write(OutputStream os) throws IOException {
+ HprofOutputStream hprof = new HprofOutputStream(idSize, os);
+ hprof.write(format.getBytes(Charsets.US_ASCII));
+ hprof.write(0);
+ hprof.writeU4(idSize);
+
+ long time = date.getTime();
+ hprof.writeU4((int)(time >> 32));
+ hprof.writeU4((int)(time >> 0));
+
+ for (HprofRecord record : records) {
+ record.write(hprof);
+ }
+
+ hprof.flush();
+ hprof.close();
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofAllocSite.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofAllocSite.java
new file mode 100644
index 0000000..6dfb159
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofAllocSite.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofAllocSite {
+ public static final int LENGTH = 1 + 6*4;
+ public final byte arrayIndicator; // u1
+ public final int classSerialNumber; // u4
+ public final int stackTraceSerialNumber; // u4
+ public final int numberOfLiveBytes; // u4
+ public final int numberOfLiveInstances; // u4
+ public final int numberOfBytesAllocated; // u4
+ public final int numberOfInstancesAllocated; // u4
+
+ public HprofAllocSite(byte arrayIndicator, int classSerialNumber,
+ int stackTraceSerialNumber, int numberOfLiveBytes,
+ int numberOfLiveInstances, int numberOfBytesAllocated,
+ int numberOfInstancesAllocated) {
+ this.arrayIndicator = arrayIndicator;
+ this.classSerialNumber = classSerialNumber;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.numberOfLiveBytes = numberOfLiveBytes;
+ this.numberOfLiveInstances = numberOfLiveInstances;
+ this.numberOfBytesAllocated = numberOfBytesAllocated;
+ this.numberOfInstancesAllocated = numberOfInstancesAllocated;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(arrayIndicator);
+ hprof.writeU4(classSerialNumber);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeU4(numberOfLiveBytes);
+ hprof.writeU4(numberOfLiveInstances);
+ hprof.writeU4(numberOfBytesAllocated);
+ hprof.writeU4(numberOfInstancesAllocated);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofAllocSites.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofAllocSites.java
new file mode 100644
index 0000000..7691af4
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofAllocSites.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofAllocSites implements HprofRecord {
+ public static final byte TAG = 0x06;
+
+ // Bit mask flags:
+ public static final short INCREMENTAL_VS_COMPLETE = 0x1;
+ public static final short SORTED_BY_ALLOCATION_VS_LINE = 0x2;
+ public static final short FORCE_GC = 0x4;
+
+ public final int time;
+ public final short bitMaskFlags; // u2
+ public final int cutoffRatio; // u4
+ public final int totalLiveBytes; // u4
+ public final int totalLiveInstances; // u4
+ public final long totalBytesAllocated; // u8
+ public final long totalInstancesAllocated; // u8
+ public final HprofAllocSite[] sites; // u4 (length) + [AllocSite]*
+
+ public HprofAllocSites(int time, short bitMaskFlags, int cutoffRatio,
+ int totalLiveBytes, int totalLiveInstances, long totalBytesAllocated,
+ long totalInstancesAllocated, HprofAllocSite[] sites) {
+ this.time = time;
+ this.bitMaskFlags = bitMaskFlags;
+ this.cutoffRatio = cutoffRatio;
+ this.totalLiveBytes = totalLiveBytes;
+ this.totalLiveInstances = totalLiveInstances;
+ this.totalBytesAllocated = totalBytesAllocated;
+ this.totalInstancesAllocated = totalInstancesAllocated;
+ this.sites = sites;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeRecordHeader(TAG, time,
+ 2 + 4 + 4 + 4 + 8 + 8 + 4 + sites.length*HprofAllocSite.LENGTH);
+ hprof.writeU2(bitMaskFlags);
+ hprof.writeU4(cutoffRatio);
+ hprof.writeU4(totalLiveBytes);
+ hprof.writeU4(totalLiveInstances);
+ hprof.writeU8(totalBytesAllocated);
+ hprof.writeU8(totalInstancesAllocated);
+ hprof.writeU4(sites.length);
+ for (HprofAllocSite site : sites) {
+ site.write(hprof);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofClassDump.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofClassDump.java
new file mode 100644
index 0000000..c994407
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofClassDump.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofClassDump implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x20;
+
+ public final long classObjectId; // Id
+ public final int stackTraceSerialNumber; // u4
+ public final long superClassObjectId; // Id
+ public final long classLoaderObjectId; // Id
+ public final long signersObjectId; // Id
+ public final long protectionDomainObjectId; // Id
+ public final long reserved1; // Id
+ public final long reserved2; // Id
+ public final int instanceSize; // u4
+ public final HprofConstant[] constantPool; // u2 + [ConstantPool]*
+ public final HprofStaticField[] staticFields; // u2 + [StaticField]*
+ public final HprofInstanceField[] instanceFields; // u2 + [InstanceField]*
+
+ public HprofClassDump(long classObjectId, int stackTraceSerialNumber,
+ long superClassObjectId, long classLoaderObjectId,
+ long signersObjectId, long protectionDomainObjectId,
+ long reserved1, long reserved2, int instanceSize,
+ HprofConstant[] constantPool, HprofStaticField[] staticFields,
+ HprofInstanceField[] instanceFields) {
+ this.classObjectId = classObjectId;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.superClassObjectId = superClassObjectId;
+ this.classLoaderObjectId = classLoaderObjectId;
+ this.signersObjectId = signersObjectId;
+ this.protectionDomainObjectId = protectionDomainObjectId;
+ this.reserved1 = reserved1;
+ this.reserved2 = reserved2;
+ this.instanceSize = instanceSize;
+ this.constantPool = constantPool;
+ this.staticFields = staticFields;
+ this.instanceFields = instanceFields;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(classObjectId);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeId(superClassObjectId);
+ hprof.writeId(classLoaderObjectId);
+ hprof.writeId(signersObjectId);
+ hprof.writeId(protectionDomainObjectId);
+ hprof.writeId(reserved1);
+ hprof.writeId(reserved2);
+ hprof.writeU4(instanceSize);
+ hprof.writeU2((short)constantPool.length);
+ for (HprofConstant constant : constantPool) {
+ constant.write(hprof);
+ }
+ hprof.writeU2((short)staticFields.length);
+ for (HprofStaticField field : staticFields) {
+ field.write(hprof);
+ }
+ hprof.writeU2((short)instanceFields.length);
+ for (HprofInstanceField field : instanceFields) {
+ field.write(hprof);
+ }
+ }
+
+ public int getLength(int idSize) {
+ int length = 1 + 7*idSize + 2 * 4 + 3*2;
+ for (HprofConstant constant : constantPool) {
+ length += constant.getLength(idSize);
+ }
+ for (HprofStaticField field : staticFields) {
+ length += field.getLength(idSize);
+ }
+ for (HprofInstanceField field : instanceFields) {
+ length += field.getLength(idSize);
+ }
+ return length;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofConstant.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofConstant.java
new file mode 100644
index 0000000..b6410bf
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofConstant.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofConstant {
+
+ public final short constantPoolIndex; // u2
+ public final byte typeOfEntry; // u1
+ public final long value; // size depends on typeOfEntry.
+
+ public HprofConstant(short constantPoolIndex, byte typeOfEntry, long value) {
+ this.constantPoolIndex = constantPoolIndex;
+ this.typeOfEntry = typeOfEntry;
+ this.value = value;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU2(constantPoolIndex);
+ hprof.writeU1(typeOfEntry);
+ hprof.writeValue(typeOfEntry, value);
+ }
+
+ public int getLength(int idSize) {
+ return 2 + 1 + HprofType.sizeOf(typeOfEntry, idSize);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofControlSettings.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofControlSettings.java
new file mode 100644
index 0000000..cfab694
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofControlSettings.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofControlSettings implements HprofRecord {
+ public static final byte TAG = 0x0E;
+
+ // Bit mask flags:
+ public static final int ALLOC_TRACES_ON = 0x1;
+ public static final int CPU_SAMPLING_ON = 0x2;
+
+ public final int time;
+ public final int bitMaskFlags; // u4
+ public final short stackTraceDepth; // u2
+
+ public HprofControlSettings(int time, int bitMaskFlags, short stackTraceDepth) {
+ this.time = time;
+ this.bitMaskFlags = bitMaskFlags;
+ this.stackTraceDepth = stackTraceDepth;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeRecordHeader(TAG, time, 4 + 2);
+ hprof.writeU4(bitMaskFlags);
+ hprof.writeU2(stackTraceDepth);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofCpuSample.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofCpuSample.java
new file mode 100644
index 0000000..1a4d94d
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofCpuSample.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofCpuSample {
+ public static final int LENGTH = 8;
+ public final int numberOfSamples; // u4
+ public final int stackTraceSerialNumber; // u4
+
+ public HprofCpuSample(int numberOfSamples, int stackTraceSerialNumber) {
+ this.numberOfSamples = numberOfSamples;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU4(numberOfSamples);
+ hprof.writeU4(stackTraceSerialNumber);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofCpuSamples.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofCpuSamples.java
new file mode 100644
index 0000000..97e9d68
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofCpuSamples.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofCpuSamples implements HprofRecord {
+ public static final byte TAG = 0x0D;
+
+ public final int time;
+ public final int totalNumberOfSamples; // u4
+ public final HprofCpuSample[] samples; // u4 (length) + [CpuSample]*
+
+ public HprofCpuSamples(int time, int totalNumberOfSamples,
+ HprofCpuSample[] samples) {
+ this.time = time;
+ this.totalNumberOfSamples = totalNumberOfSamples;
+ this.samples = samples;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeRecordHeader(TAG, time, 4 + 4 + samples.length*HprofCpuSample.LENGTH);
+ hprof.writeU4(totalNumberOfSamples);
+ hprof.writeU4(samples.length);
+ for (HprofCpuSample sample : samples) {
+ sample.write(hprof);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofDumpRecord.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofDumpRecord.java
new file mode 100644
index 0000000..1387d2d
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofDumpRecord.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public interface HprofDumpRecord {
+ /**
+ * Write the heap dump record to the given hprof output stream.
+ */
+ public void write(HprofOutputStream hprof) throws IOException;
+
+ /**
+ * Get the length of the heap dump record, given the size of an id.
+ */
+ public int getLength(int idSize);
+}
+
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofEndThread.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofEndThread.java
new file mode 100644
index 0000000..f6964a2
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofEndThread.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofEndThread implements HprofRecord {
+ public static final byte TAG = 0x0B;
+
+ public final int time;
+ public final int threadSerialNumber; // u4
+
+ public HprofEndThread(int time, int threadSerialNumber) {
+ this.time = time;
+ this.threadSerialNumber = threadSerialNumber;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeRecordHeader(TAG, time, 4);
+ hprof.writeU4(threadSerialNumber);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDump.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDump.java
new file mode 100644
index 0000000..08c1a71
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDump.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofHeapDump implements HprofRecord {
+ public static final byte TAG = 0x0C;
+
+ public final int time;
+ public final HprofDumpRecord[] records;
+
+ public HprofHeapDump(int time, HprofDumpRecord[] records) {
+ this.time = time;
+ this.records = records;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ int idSize = hprof.getIdSize();
+ int len = 0;
+ for (HprofDumpRecord record : records) {
+ len += record.getLength(idSize);
+ }
+ hprof.writeRecordHeader(TAG, time, len);
+
+ for (HprofDumpRecord record : records) {
+ record.write(hprof);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpEnd.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpEnd.java
new file mode 100644
index 0000000..f1d8bdd
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpEnd.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofHeapDumpEnd implements HprofRecord {
+ public static final byte TAG = 0x2C;
+
+ public final int time;
+
+ public HprofHeapDumpEnd(int time) {
+ this.time = time;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeRecordHeader(TAG, time, 0);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpInfo.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpInfo.java
new file mode 100644
index 0000000..ef3ea34
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpInfo.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+/**
+ * This record is specific to Android.
+ */
+public class HprofHeapDumpInfo implements HprofDumpRecord {
+ public static final byte SUBTAG = (byte)0xfe;
+
+ // Heap types:
+ public static final int HEAP_DEFAULT = 0;
+ public static final int HEAP_ZYGOTE = 0x5A; // 'Z'
+ public static final int HEAP_APP = 0x41; // 'A'
+ public static final int HEAP_IMAGE = 0x49; // 'I'
+
+ public final int heapType; // u4
+ public final long heapNameStringId; // Id
+
+ public HprofHeapDumpInfo(int heapType, long heapNameStringId) {
+ this.heapType = heapType;
+ this.heapNameStringId = heapNameStringId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeU4(heapType);
+ hprof.writeId(heapNameStringId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + 4 + idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpSegment.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpSegment.java
new file mode 100644
index 0000000..afac455
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapDumpSegment.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofHeapDumpSegment implements HprofRecord {
+ public static final byte TAG = 0x1C;
+
+ public final int time;
+ public final HprofDumpRecord[] records;
+
+ public HprofHeapDumpSegment(int time, HprofDumpRecord[] records) {
+ this.time = time;
+ this.records = records;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ int idSize = hprof.getIdSize();
+ int len = 0;
+ for (HprofDumpRecord record : records) {
+ len += record.getLength(idSize);
+ }
+ hprof.writeRecordHeader(TAG, time, len);
+
+ for (HprofDumpRecord record : records) {
+ record.write(hprof);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapSummary.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapSummary.java
new file mode 100644
index 0000000..ae6a0a6
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofHeapSummary.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofHeapSummary implements HprofRecord {
+ public static final byte TAG = 0x07;
+
+ public final int time;
+ public final int totalLiveBytes; // u4
+ public final int totalLiveInstances; // u4
+ public final long totalBytesAllocated; // u8
+ public final long totalInstancesAllocated; // u8
+
+ public HprofHeapSummary(int time, int totalLiveBytes,
+ int totalLiveInstances, long totalBytesAllocated,
+ long totalInstancesAllocated) {
+ this.time = time;
+ this.totalLiveBytes = totalLiveBytes;
+ this.totalLiveInstances = totalLiveInstances;
+ this.totalBytesAllocated = totalBytesAllocated;
+ this.totalInstancesAllocated = totalInstancesAllocated;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeRecordHeader(TAG, time, 4 + 4 + 8 + 8);
+ hprof.writeU4(totalLiveBytes);
+ hprof.writeU4(totalLiveInstances);
+ hprof.writeU8(totalBytesAllocated);
+ hprof.writeU8(totalInstancesAllocated);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofInstanceDump.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofInstanceDump.java
new file mode 100644
index 0000000..81596bd
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofInstanceDump.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofInstanceDump implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x21;
+
+ public final long objectId; // Id
+ public final int stackTraceSerialNumber; // u4
+ public final long classObjectId; // Id
+ public final byte[] values; // u4 (size) + [value]* (packed)
+
+ public HprofInstanceDump(long objectId, int stackTraceSerialNumber,
+ long classObjectId, byte[] values) {
+ this.objectId = objectId;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.classObjectId = classObjectId;
+ this.values = values;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeId(classObjectId);
+ hprof.writeU4(values.length);
+ hprof.write(values);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4 + idSize + 4 + values.length;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofInstanceField.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofInstanceField.java
new file mode 100644
index 0000000..f7d8d46
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofInstanceField.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofInstanceField {
+
+ public final long fieldNameStringId; // Id
+ public final byte typeOfField; // u1
+
+ public HprofInstanceField(long fieldNameStringId, byte typeOfField) {
+ this.fieldNameStringId = fieldNameStringId;
+ this.typeOfField = typeOfField;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeId(fieldNameStringId);
+ hprof.writeU1(typeOfField);
+ }
+
+ public int getLength(int idSize) {
+ return idSize + 1;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofLoadClass.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofLoadClass.java
new file mode 100644
index 0000000..4097a3c
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofLoadClass.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofLoadClass implements HprofRecord {
+ public static final byte TAG = 0x02;
+ public final int time;
+ public final int classSerialNumber; // u4
+ public final long classObjectId; // ID
+ public final int stackTraceSerialNumber; // u4
+ public final long classNameStringId; // ID
+
+ public HprofLoadClass(int time, int classSerialNumber, long classObjectId,
+ int stackTraceSerialNumber, long classNameStringId) {
+ this.time = time;
+ this.classSerialNumber = classSerialNumber;
+ this.classObjectId = classObjectId;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.classNameStringId = classNameStringId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ int id = hprof.getIdSize();
+ hprof.writeRecordHeader(TAG, time, 4 + id + 4 + id);
+ hprof.writeU4(classSerialNumber);
+ hprof.writeId(classObjectId);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeId(classNameStringId);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofObjectArrayDump.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofObjectArrayDump.java
new file mode 100644
index 0000000..2d59f23
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofObjectArrayDump.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofObjectArrayDump implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x22;
+
+ public final long arrayObjectId; // Id
+ public final int stackTraceSerialNumber; // u4
+ public final long arrayClassObjectId; // Id
+ public final long[] elements; // u4 (size) + [ID]*
+
+ public HprofObjectArrayDump(long arrayObjectId, int stackTraceSerialNumber,
+ long arrayClassObjectId, long[] elements) {
+ this.arrayObjectId = arrayObjectId;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.arrayClassObjectId = arrayClassObjectId;
+ this.elements = elements;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(arrayObjectId);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeU4(elements.length);
+ hprof.writeId(arrayClassObjectId);
+ for (long element : elements) {
+ hprof.writeId(element);
+ }
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4 + idSize + 4 + elements.length * idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofOutputStream.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofOutputStream.java
new file mode 100644
index 0000000..18673b8
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofOutputStream.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.IllegalStateException;
+
+public class HprofOutputStream extends OutputStream {
+ private int mIdSize;
+ private OutputStream mOutputStream;
+
+ public HprofOutputStream(int idSize, OutputStream os) {
+ mIdSize = idSize;
+ if (idSize != 1 && idSize != 2 && idSize != 4 && idSize != 8) {
+ throw new IllegalArgumentException("Unsupproted id size: " + idSize);
+ }
+ mOutputStream = os;
+ }
+
+ public void close() throws IOException {
+ mOutputStream.close();
+ }
+
+ public void flush() throws IOException {
+ mOutputStream.flush();
+ }
+
+ public void write(byte[] b) throws IOException {
+ mOutputStream.write(b);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ mOutputStream.write(b, off, len);
+ }
+
+ public void write(int b) throws IOException {
+ mOutputStream.write(b);
+ }
+
+ /**
+ * Write a u1 to the output stream.
+ */
+ public void writeU1(byte data) throws IOException {
+ mOutputStream.write(data);
+ }
+
+ /**
+ * Write a u2 to the output stream.
+ */
+ public void writeU2(short data) throws IOException {
+ writeU1((byte)(data >> 8));
+ writeU1((byte)(data >> 0));
+ }
+
+ /**
+ * Write a u4 to the output stream.
+ */
+ public void writeU4(int data) throws IOException {
+ writeU1((byte)(data >> 24));
+ writeU1((byte)(data >> 16));
+ writeU1((byte)(data >> 8));
+ writeU1((byte)(data >> 0));
+ }
+
+ /**
+ * Write a u8 to the output stream.
+ */
+ public void writeU8(long data) throws IOException {
+ writeU1((byte)(data >> 56));
+ writeU1((byte)(data >> 48));
+ writeU1((byte)(data >> 40));
+ writeU1((byte)(data >> 32));
+ writeU1((byte)(data >> 24));
+ writeU1((byte)(data >> 16));
+ writeU1((byte)(data >> 8));
+ writeU1((byte)(data >> 0));
+ }
+
+ /**
+ * Write an ID to the output stream.
+ */
+ public void writeId(long data) throws IOException {
+ writeSized(mIdSize, data);
+ }
+
+ /**
+ * Write a value with given type to the output stream.
+ */
+ public void writeValue(byte type, long data) throws IOException {
+ writeSized(HprofType.sizeOf(type, mIdSize), data);
+ }
+
+
+ /**
+ * Write a record header to the output.
+ * @param tag - The record tag.
+ * @param time - T number of microseconds since the hprof time stamp.
+ * @param length - the number of bytes of content of the record (not
+ * including the bytes for the header).
+ */
+ public void writeRecordHeader(byte tag, int time, int length) throws IOException {
+ writeU1(tag);
+ writeU4(time);
+ writeU4(length);
+ }
+
+ public int getIdSize() {
+ return mIdSize;
+ }
+
+ /**
+ * Write data of the given size in bytes to the output stream.
+ * The size must be 1, 2, 4, or 8.
+ */
+ private void writeSized(int size, long data) throws IOException {
+ switch (size) {
+ case 1: writeU1((byte)data); break;
+ case 2: writeU2((short)data); break;
+ case 4: writeU4((int)data); break;
+ case 8: writeU8(data); break;
+ default: throw new IllegalStateException("Unexpected size: " + size);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofPrimitiveArrayDump.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofPrimitiveArrayDump.java
new file mode 100644
index 0000000..5169634
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofPrimitiveArrayDump.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofPrimitiveArrayDump implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x23;
+
+ public final long arrayObjectId; // Id
+ public final int stackTraceSerialNumber; // u4
+ public final byte elementType; // u1
+ public final long[] elements; // u4 (size) + [element]* (not packed)
+
+ public HprofPrimitiveArrayDump(long arrayObjectId,
+ int stackTraceSerialNumber, byte elementType, long[] elements) {
+ this.arrayObjectId = arrayObjectId;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.elementType = elementType;
+ this.elements = elements;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(arrayObjectId);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeU4(elements.length);
+ hprof.writeU1(elementType);
+ for (long element : elements) {
+ hprof.writeValue(elementType, element);
+ }
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4 + 4 + 1 + elements.length * HprofType.sizeOf(elementType, idSize);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRecord.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRecord.java
new file mode 100644
index 0000000..8333ab2
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRecord.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public interface HprofRecord {
+ /**
+ * Write the record to the given hprof output stream.
+ */
+ public void write(HprofOutputStream hprof) throws IOException;
+}
+
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootDebugger.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootDebugger.java
new file mode 100644
index 0000000..89de45e
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootDebugger.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+/**
+ * This record is specific to Android.
+ */
+public class HprofRootDebugger implements HprofDumpRecord {
+ public static final byte SUBTAG = (byte)0x8b;
+
+ public final long objectId; // Id
+
+ public HprofRootDebugger(long objectId) {
+ this.objectId = objectId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootInternedString.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootInternedString.java
new file mode 100644
index 0000000..2444b45
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootInternedString.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+/**
+ * This record is specific to Android.
+ */
+public class HprofRootInternedString implements HprofDumpRecord {
+ public static final byte SUBTAG = (byte)0x89;
+
+ public final long objectId; // Id
+
+ public HprofRootInternedString(long objectId) {
+ this.objectId = objectId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJavaFrame.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJavaFrame.java
new file mode 100644
index 0000000..fef1480
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJavaFrame.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootJavaFrame implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x03;
+
+ public final long objectId; // Id
+ public final int threadSerialNumber; // u4
+ public final int frameNumberInStackTrace; // u4
+
+ public HprofRootJavaFrame(long objectId, int threadSerialNumber,
+ int frameNumberInStackTrace) {
+ this.objectId = objectId;
+ this.threadSerialNumber = threadSerialNumber;
+ this.frameNumberInStackTrace = frameNumberInStackTrace;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeU4(threadSerialNumber);
+ hprof.writeU4(frameNumberInStackTrace);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4 + 4;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniGlobal.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniGlobal.java
new file mode 100644
index 0000000..cf34426
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniGlobal.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootJniGlobal implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x01;
+
+ public final long objectId; // Id
+ public final long jniGlobalRefId; // Id
+
+ public HprofRootJniGlobal(long objectId, long jniGlobalRefId) {
+ this.objectId = objectId;
+ this.jniGlobalRefId = jniGlobalRefId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeId(jniGlobalRefId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + 2*idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniLocal.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniLocal.java
new file mode 100644
index 0000000..92e2f66
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniLocal.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootJniLocal implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x02;
+
+ public final long objectId; // Id
+ public final int threadSerialNumber; // u4
+ public final int frameNumberInStackTrace; // u4
+
+ public HprofRootJniLocal(long objectId, int threadSerialNumber,
+ int frameNumberInStackTrace) {
+ this.objectId = objectId;
+ this.threadSerialNumber = threadSerialNumber;
+ this.frameNumberInStackTrace = frameNumberInStackTrace;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeU4(threadSerialNumber);
+ hprof.writeU4(frameNumberInStackTrace);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4 + 4;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniMonitor.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniMonitor.java
new file mode 100644
index 0000000..05eee64
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootJniMonitor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+/**
+ * This record is specific to Android.
+ */
+public class HprofRootJniMonitor implements HprofDumpRecord {
+ public static final byte SUBTAG = (byte)0x8e;
+
+ public final long objectId; // Id
+ public final int threadSerialNumber; // u4
+ public final int frameNumberInStackTrace; // u4
+
+ public HprofRootJniMonitor(long objectId, int threadSerialNumber,
+ int frameNumberInStackTrace) {
+ this.objectId = objectId;
+ this.threadSerialNumber = threadSerialNumber;
+ this.frameNumberInStackTrace = frameNumberInStackTrace;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeU4(threadSerialNumber);
+ hprof.writeU4(frameNumberInStackTrace);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4 + 4;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootMonitorUsed.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootMonitorUsed.java
new file mode 100644
index 0000000..8b2de33
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootMonitorUsed.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootMonitorUsed implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x07;
+
+ public final long objectId; // Id
+
+ public HprofRootMonitorUsed(long objectId) {
+ this.objectId = objectId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootNativeStack.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootNativeStack.java
new file mode 100644
index 0000000..0c1345e
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootNativeStack.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootNativeStack implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x04;
+
+ public final long objectId; // Id
+ public final int threadSerialNumber; // u4
+
+ public HprofRootNativeStack(long objectId, int threadSerialNumber) {
+ this.objectId = objectId;
+ this.threadSerialNumber = threadSerialNumber;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeU4(threadSerialNumber);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootStickyClass.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootStickyClass.java
new file mode 100644
index 0000000..2e3668a
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootStickyClass.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootStickyClass implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x05;
+
+ public final long objectId; // Id
+
+ public HprofRootStickyClass(long objectId) {
+ this.objectId = objectId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootThreadBlock.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootThreadBlock.java
new file mode 100644
index 0000000..66ad642
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootThreadBlock.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootThreadBlock implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x06;
+
+ public final long objectId; // Id
+ public final int threadSerialNumber; // u4
+
+ public HprofRootThreadBlock(long objectId, int threadSerialNumber) {
+ this.objectId = objectId;
+ this.threadSerialNumber = threadSerialNumber;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeU4(threadSerialNumber);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootThreadObject.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootThreadObject.java
new file mode 100644
index 0000000..5557e6a
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootThreadObject.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootThreadObject implements HprofDumpRecord {
+ public static final byte SUBTAG = 0x08;
+
+ public final long objectId; // Id
+ public final int threadSerialNumber; // u4
+ public final int stackTraceSerialNumber; // u4
+
+ public HprofRootThreadObject(long objectId, int threadSerialNumber,
+ int stackTraceSerialNumber) {
+ this.objectId = objectId;
+ this.threadSerialNumber = threadSerialNumber;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ hprof.writeU4(threadSerialNumber);
+ hprof.writeU4(stackTraceSerialNumber);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize + 4 + 4;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootUnknown.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootUnknown.java
new file mode 100644
index 0000000..0a75804
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootUnknown.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofRootUnknown implements HprofDumpRecord {
+ public static final byte SUBTAG = (byte)0xFF;
+
+ public final long objectId; // Id
+
+ public HprofRootUnknown(long objectId) {
+ this.objectId = objectId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootVmInternal.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootVmInternal.java
new file mode 100644
index 0000000..d567f1b
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofRootVmInternal.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+/**
+ * This record is specific to Android.
+ */
+public class HprofRootVmInternal implements HprofDumpRecord {
+ public static final byte SUBTAG = (byte)0x8d;
+
+ public final long objectId; // Id
+
+ public HprofRootVmInternal(long objectId) {
+ this.objectId = objectId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeU1(SUBTAG);
+ hprof.writeId(objectId);
+ }
+
+ public int getLength(int idSize) {
+ return 1 + idSize;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStackFrame.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStackFrame.java
new file mode 100644
index 0000000..2e3e2c6
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStackFrame.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofStackFrame implements HprofRecord {
+ public static final byte TAG = 0x04;
+
+ // The following special values can be used for lineNumber.
+ public static final int NO_LINE_INFO = 0;
+ public static final int UNKNOWN_LOCATION = -1;
+ public static final int COMPILED_METHOD = -2;
+ public static final int NATIVE_METHOD = -3;
+
+ public final int time;
+ public final long stackFrameId; // ID
+ public final long methodNameStringId; // ID
+ public final long methodSignatureStringId; // ID
+ public final long sourceFileNameStringId; // ID
+ public final int classSerialNumber; // u4
+ public final int lineNumber; // u4
+
+ public HprofStackFrame(int time, long stackFrameId, long methodNameStringId,
+ long methodSignatureStringId, long sourceFileNameStringId,
+ int classSerialNumber, int lineNumber) {
+ this.time = time;
+ this.stackFrameId = stackFrameId;
+ this.methodNameStringId = methodNameStringId;
+ this.methodSignatureStringId = methodSignatureStringId;
+ this.sourceFileNameStringId = sourceFileNameStringId;
+ this.classSerialNumber = classSerialNumber;
+ this.lineNumber = lineNumber;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ int id = hprof.getIdSize();
+ int u4 = 4;
+ hprof.writeRecordHeader(TAG, time, id + id + id + id + u4 + u4);
+ hprof.writeId(stackFrameId);
+ hprof.writeId(methodNameStringId);
+ hprof.writeId(methodSignatureStringId);
+ hprof.writeId(sourceFileNameStringId);
+ hprof.writeU4(classSerialNumber);
+ hprof.writeU4(lineNumber);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStackTrace.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStackTrace.java
new file mode 100644
index 0000000..edeea3c
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStackTrace.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofStackTrace implements HprofRecord {
+ public static final byte TAG = 0x05;
+
+ public final int time;
+ public final int stackTraceSerialNumber; // u4
+ public final int threadSerialNumber; // u4
+ public final long[] stackFrameIds; // u4 (length) + [ID]*
+
+ public HprofStackTrace(int time, int stackTraceSerialNumber,
+ int threadSerialNumber, long[] stackFrameIds) {
+ this.time = time;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.threadSerialNumber = threadSerialNumber;
+ this.stackFrameIds = stackFrameIds;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ int id = hprof.getIdSize();
+ int u4 = 4;
+ hprof.writeRecordHeader(TAG, time, u4 + u4 + u4 + stackFrameIds.length*id);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeU4(threadSerialNumber);
+ hprof.writeU4(stackFrameIds.length);
+ for (long frameId : stackFrameIds) {
+ hprof.writeId(frameId);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStartThread.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStartThread.java
new file mode 100644
index 0000000..facee36
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStartThread.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofStartThread implements HprofRecord {
+ public static final byte TAG = 0x0A;
+
+ public final int time;
+ public final int threadSerialNumber; // u4
+ public final long threadObjectId; // ID
+ public final int stackTraceSerialNumber; // u4
+ public final long threadNameStringId; // ID
+ public final long threadGroupNameId; // ID
+ public final long threadParentGroupNameId; // ID
+
+ public HprofStartThread(int time, int threadSerialNumber,
+ long threadObjectId, int stackTraceSerialNumber, long threadNameStringId,
+ long threadGroupNameId, long threadParentGroupNameId) {
+ this.time = time;
+ this.threadSerialNumber = threadSerialNumber;
+ this.threadObjectId = threadObjectId;
+ this.stackTraceSerialNumber = stackTraceSerialNumber;
+ this.threadNameStringId = threadNameStringId;
+ this.threadGroupNameId = threadGroupNameId;
+ this.threadParentGroupNameId = threadParentGroupNameId;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ int id = hprof.getIdSize();
+ hprof.writeRecordHeader(TAG, time, 4 + id + 4 + id + id + id);
+ hprof.writeU4(threadSerialNumber);
+ hprof.writeId(threadObjectId);
+ hprof.writeU4(stackTraceSerialNumber);
+ hprof.writeId(threadNameStringId);
+ hprof.writeId(threadGroupNameId);
+ hprof.writeId(threadParentGroupNameId);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStaticField.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStaticField.java
new file mode 100644
index 0000000..55a3241
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStaticField.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofStaticField {
+
+ public final long staticFieldNameStringId; // Id
+ public final byte typeOfField; // u1
+ public final long value; // size depends on typeOfField.
+
+ public HprofStaticField(long staticFieldNameStringId, byte typeOfField,
+ long value) {
+ this.staticFieldNameStringId = staticFieldNameStringId;
+ this.typeOfField = typeOfField;
+ this.value = value;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeId(staticFieldNameStringId);
+ hprof.writeU1(typeOfField);
+ hprof.writeValue(typeOfField, value);
+ }
+
+ public int getLength(int idSize) {
+ return idSize + 1 + HprofType.sizeOf(typeOfField, idSize);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofString.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofString.java
new file mode 100644
index 0000000..27e68e7
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofString.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import com.google.common.base.Charsets;
+import java.io.IOException;
+
+public class HprofString implements HprofRecord {
+ public static final byte TAG = 0x01;
+ public final int time;
+ public final long id; // ID
+ public final String string; // [u1]*
+
+ public HprofString(int time, long id, String string) {
+ this.time = time;
+ this.id = id;
+ this.string = string;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ byte[] bytes = string.getBytes(Charsets.UTF_8);
+ hprof.writeRecordHeader(TAG, time, hprof.getIdSize() + bytes.length);
+ hprof.writeId(id);
+ hprof.write(bytes);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStringBuilder.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStringBuilder.java
new file mode 100644
index 0000000..a97b50d
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofStringBuilder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HprofStringBuilder {
+ private int mTime;
+ private Map<String, Integer> mStrings;
+ private List<HprofString> mStringRecords;
+
+ public HprofStringBuilder(int time) {
+ mTime = time;
+ mStrings = new HashMap<String, Integer>();
+ mStringRecords = new ArrayList<HprofString>();
+ }
+
+ /**
+ * Return an ID to use for the given string.
+ */
+ public int get(String string) {
+ Integer id = mStrings.get(string);
+ if (id == null) {
+ id = mStrings.size()+1;
+ mStrings.put(string, id);
+ mStringRecords.add(new HprofString(mTime, id, string));
+ }
+ return id;
+ }
+
+ /**
+ * Return the list of string definitions needed for all the strings used.
+ */
+ public List<HprofString> getStringRecords() {
+ return mStringRecords;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofType.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofType.java
new file mode 100644
index 0000000..97d8a2f
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofType.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+public class HprofType {
+ public static final byte TYPE_OBJECT = 2;
+ public static final byte TYPE_BOOLEAN = 4;
+ public static final byte TYPE_CHAR = 5;
+ public static final byte TYPE_FLOAT = 6;
+ public static final byte TYPE_DOUBLE = 7;
+ public static final byte TYPE_BYTE = 8;
+ public static final byte TYPE_SHORT = 9;
+ public static final byte TYPE_INT = 10;
+ public static final byte TYPE_LONG = 11;
+
+ public static int sizeOf(byte type, int idSize) {
+ switch (type) {
+ case TYPE_OBJECT: return idSize;
+ case TYPE_BOOLEAN: return 1;
+ case TYPE_CHAR: return 2;
+ case TYPE_FLOAT: return 4;
+ case TYPE_DOUBLE: return 8;
+ case TYPE_BYTE: return 1;
+ case TYPE_SHORT: return 2;
+ case TYPE_INT: return 4;
+ case TYPE_LONG: return 8;
+ default: throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofUnloadClass.java b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofUnloadClass.java
new file mode 100644
index 0000000..ac38e2f
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/hprof/HprofUnloadClass.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.hprof;
+
+import java.io.IOException;
+
+public class HprofUnloadClass implements HprofRecord {
+ public static final byte TAG = 0x03;
+ public final int time;
+ public final int classSerialNumber; // u4
+
+ public HprofUnloadClass(int time, int classSerialNumber) {
+ this.time = time;
+ this.classSerialNumber = classSerialNumber;
+ }
+
+ public void write(HprofOutputStream hprof) throws IOException {
+ hprof.writeRecordHeader(TAG, time, 4);
+ hprof.writeU4(classSerialNumber);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/io/InMemoryBuffer.java b/perflib/src/main/java/com/android/tools/perflib/heap/io/InMemoryBuffer.java
new file mode 100644
index 0000000..0b2b332
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/io/InMemoryBuffer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.io;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.captures.DataBuffer;
+
+import java.nio.ByteBuffer;
+
+public class InMemoryBuffer implements DataBuffer {
+
+ private final ByteBuffer mBuffer;
+
+ public InMemoryBuffer(int capacity) {
+ mBuffer = ByteBuffer.allocateDirect(capacity);
+ }
+
+ /**
+ * Create an in memory buffer from a raw array of bytes.
+ */
+ public InMemoryBuffer(byte[] data) {
+ mBuffer = ByteBuffer.wrap(data);
+ }
+
+ @Override
+ public void dispose() {}
+
+ public ByteBuffer getDirectBuffer() {
+ return mBuffer;
+ }
+
+ @Override
+ public byte readByte() {
+ return mBuffer.get();
+ }
+
+ @Override
+ public void append(@NonNull byte[] data) {
+ // Do nothing, since this is not a streaming data buffer.
+ }
+
+ @Override
+ public void read(@NonNull byte[] b) {
+ mBuffer.get(b);
+ }
+
+ @Override
+ public void readSubSequence(byte[] b, int sourceStart, int sourceEnd) {
+ ((ByteBuffer)mBuffer.slice().position(sourceStart)).get(b);
+ }
+
+ @Override
+ public char readChar() {
+ return mBuffer.getChar();
+ }
+
+ @Override
+ public short readShort() {
+ return mBuffer.getShort();
+ }
+
+ @Override
+ public int readInt() {
+ return mBuffer.getInt();
+ }
+
+ @Override
+ public long readLong() {
+ return mBuffer.getLong();
+ }
+
+ @Override
+ public float readFloat() {
+ return mBuffer.getFloat();
+ }
+
+ @Override
+ public double readDouble() {
+ return mBuffer.getDouble();
+ }
+
+ @Override
+ public void setPosition(long position) {
+ mBuffer.position((int) position);
+ }
+
+ @Override
+ public long position() {
+ return mBuffer.position();
+ }
+
+ @Override
+ public boolean hasRemaining() {
+ return mBuffer.hasRemaining();
+ }
+
+ @Override
+ public long remaining() {
+ return mBuffer.remaining();
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/DuplicatedStringsAnalyzerTask.java b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/DuplicatedStringsAnalyzerTask.java
new file mode 100644
index 0000000..71220b1
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/DuplicatedStringsAnalyzerTask.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.memoryanalyzer;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.analyzer.AnalysisResultEntry;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Snapshot;
+import com.google.common.collect.HashMultimap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class DuplicatedStringsAnalyzerTask extends MemoryAnalyzerTask {
+
+ @Override
+ List<AnalysisResultEntry> analyze(@NonNull Configuration configuration,
+ @NonNull Snapshot snapshot) {
+ List<AnalysisResultEntry> results = new ArrayList<AnalysisResultEntry>();
+
+ HashMultimap<String, ClassInstance> stringIndex = HashMultimap.create();
+ ClassObj stringClass = snapshot.findClass("java.lang.String");
+ if (stringClass == null) {
+ return Collections.emptyList();
+ }
+
+ for (Heap heap : configuration.mHeaps) {
+ List<Instance> instances = stringClass.getHeapInstances(heap.getId());
+
+ for (Instance instance : instances) {
+ assert instance instanceof ClassInstance;
+ ClassInstance stringInstance = (ClassInstance) instance;
+ if (stringInstance.getDistanceToGcRoot() != Integer.MAX_VALUE) {
+ char[] characters = stringInstance.getStringChars();
+ if (characters != null) {
+ String string = new String(characters);
+ stringIndex.put(string, stringInstance);
+ }
+ }
+ }
+ }
+
+ for (String key : stringIndex.keySet()) {
+ Set<ClassInstance> classInstanceSet = stringIndex.get(key);
+ if (classInstanceSet.size() > 1) {
+ results.add(
+ new DuplicatedStringsEntry(key, new ArrayList<Instance>(classInstanceSet)));
+ }
+ }
+
+ return results;
+ }
+
+ @NonNull
+ @Override
+ public String getTaskName() {
+ return "Find Duplicate Strings";
+ }
+
+ @NonNull
+ @Override
+ public String getTaskDescription() {
+ return "Detects duplicate strings in the application.";
+ }
+
+ public static class DuplicatedStringsEntry extends MemoryAnalysisResultEntry {
+
+ private DuplicatedStringsEntry(@NonNull String offendingString,
+ @NonNull List<Instance> duplicates) {
+ super(offendingString, duplicates);
+ }
+
+ @NonNull
+ @Override
+ public String getWarningMessage() {
+ return String.format("%d instances: \"%s\"", mOffender.getOffenders().size(),
+ mOffender.getOffendingDescription());
+ }
+
+ @NonNull
+ @Override
+ public String getCategory() {
+ return "Duplicated Strings";
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/HprofBitmapProvider.java b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/HprofBitmapProvider.java
new file mode 100644
index 0000000..44d44aa
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/HprofBitmapProvider.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.memoryanalyzer;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.BitmapDecoder;
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.Field;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Type;
+
+import java.awt.Dimension;
+
+public class HprofBitmapProvider implements BitmapDecoder.BitmapDataProvider {
+
+ private ArrayInstance mBuffer = null;
+
+ private boolean mMutable = false;
+
+ private int mWidth = -1;
+
+ private int mHeight = -1;
+
+ public HprofBitmapProvider(@NonNull Instance instance) {
+ ClassInstance resolvedInstance = getBitmapClassInstance(instance);
+ if (resolvedInstance == null) {
+ throw new RuntimeException("Can not resolve Bitmap instance");
+ }
+
+ Integer width = null;
+ Integer height = null;
+ Boolean mutable = null;
+
+ for (ClassInstance.FieldValue field : resolvedInstance.getValues()) {
+ Object bitmapValue = field.getValue();
+ String bitmapFieldName = field.getField().getName();
+ if ("mBuffer".equals(bitmapFieldName) && (bitmapValue instanceof ArrayInstance)) {
+ ArrayInstance arrayInstance = (ArrayInstance) bitmapValue;
+ if (arrayInstance.getArrayType() == Type.BYTE) {
+ mBuffer = arrayInstance;
+ }
+ } else if ("mIsMutable".equals(bitmapFieldName) && (bitmapValue instanceof Boolean)) {
+ mutable = (Boolean) bitmapValue;
+ } else if ("mWidth".equals(bitmapFieldName) && (bitmapValue instanceof Integer)) {
+ width = (Integer) bitmapValue;
+ } else if ("mHeight".equals(bitmapFieldName) && (bitmapValue instanceof Integer)) {
+ height = (Integer) bitmapValue;
+ }
+ }
+
+ if (mBuffer == null || mBuffer.getArrayType() != Type.BYTE || mutable == null
+ || width == null || height == null) {
+ throw new RuntimeException("Unable to resolve bitmap instance member variables");
+ }
+
+ mMutable = mutable;
+ mWidth = width;
+ mHeight = height;
+ }
+
+ public static boolean canGetBitmapFromInstance(@NonNull Instance value) {
+ if (!(value instanceof ClassInstance)) {
+ return false;
+ }
+
+ String className = value.getClassObj().getClassName();
+ return BitmapDecoder.BITMAP_FQCN.equals(className) || BitmapDecoder.BITMAP_DRAWABLE_FQCN
+ .equals(className);
+ }
+
+ @Nullable
+ @Override
+ public String getBitmapConfigName() throws Exception {
+ int area = mWidth * mHeight;
+ int pixelSize = mBuffer.getLength() / area;
+
+ if ((!mMutable && ((mBuffer.getLength() % area) != 0)) ||
+ (mMutable && area > mBuffer.getLength())) {
+ return null;
+ }
+
+ switch (pixelSize) {
+ case 4:
+ return "\"ARGB_8888\"";
+ case 2:
+ return "\"RGB_565\"";
+ default:
+ return "\"ALPHA_8\"";
+ }
+ }
+
+ @Nullable
+ @Override
+ public Dimension getDimension() throws Exception {
+ return mWidth < 0 || mHeight < 0 ? null : new Dimension(mWidth, mHeight);
+ }
+
+ @Override
+ public boolean downsizeBitmap(@NonNull Dimension newSize) throws Exception {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public byte[] getPixelBytes(@NonNull Dimension size) throws Exception {
+ return mBuffer.asRawByteArray(0, mBuffer.getLength());
+ }
+
+ @Nullable
+ private static ClassInstance getBitmapClassInstance(@NonNull Instance instance) {
+ if (!(instance instanceof ClassInstance)) {
+ return null;
+ }
+
+ ClassInstance selectedObject = (ClassInstance) instance;
+ String className = instance.getClassObj().getClassName();
+ if (BitmapDecoder.BITMAP_FQCN.equals(className)) {
+ return selectedObject;
+ } else if (BitmapDecoder.BITMAP_DRAWABLE_FQCN.equals(className)) {
+ return getBitmapFromDrawable(selectedObject);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static ClassInstance getBitmapFromDrawable(@NonNull ClassInstance instance) {
+ ClassInstance bitmapState = getBitmapStateFromBitmapDrawable(instance);
+ if (bitmapState == null) {
+ return null;
+ }
+
+ for (ClassInstance.FieldValue fieldValue : bitmapState.getValues()) {
+ Field field = fieldValue.getField();
+ Object value = fieldValue.getValue();
+ if ("mBitmap".equals(field.getName()) && (value instanceof ClassInstance)) {
+ ClassInstance result = (ClassInstance) value;
+ String className = result.getClassObj().getClassName();
+ if (BitmapDecoder.BITMAP_FQCN.equals(className)) {
+ return (ClassInstance) value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static ClassInstance getBitmapStateFromBitmapDrawable(
+ @NonNull ClassInstance bitmapDrawable) {
+ for (ClassInstance.FieldValue field : bitmapDrawable.getValues()) {
+ String fieldName = field.getField().getName();
+ Object fieldValue = field.getValue();
+ if ("mBitmapState".equals(fieldName) && (fieldValue instanceof ClassInstance)) {
+ ClassInstance result = (ClassInstance) fieldValue;
+ String className = result.getClassObj().getClassName();
+ if ((BitmapDecoder.BITMAP_DRAWABLE_FQCN + "$BitmapState").equals(className)) {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/LeakedActivityAnalyzerTask.java b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/LeakedActivityAnalyzerTask.java
new file mode 100644
index 0000000..d6d70c5
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/LeakedActivityAnalyzerTask.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.memoryanalyzer;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.analyzer.AnalysisResultEntry;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Snapshot;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * When activities are destroyed, there should be no external references to them. Since activities
+ * are normally GC root objects, this implies no reference should be pointing to these activities if
+ * they are to be GC'ed. This analyzer detects activities that have been destroyed but still have
+ * some non-system reference(s) pointing to them, indicating a memory leak.
+ */
+public class LeakedActivityAnalyzerTask extends MemoryAnalyzerTask {
+
+ @Override
+ List<AnalysisResultEntry> analyze(@NonNull Configuration configuration,
+ @NonNull Snapshot snapshot) {
+ List<Instance> leakingInstances = new ArrayList<Instance>();
+
+ List<ClassObj> activityClasses = snapshot.findAllDescendantClasses("android.app.Activity");
+ for (ClassObj activityClass : activityClasses) {
+ List<Instance> instances = new ArrayList<Instance>();
+ for (Heap heap : configuration.mHeaps) {
+ instances.addAll(activityClass.getHeapInstances(heap.getId()));
+ }
+
+ for (Instance instance : instances) {
+ Instance immediateDominator = instance.getImmediateDominator();
+ if (!(instance instanceof ClassInstance) || immediateDominator == null) {
+ continue;
+ }
+
+ for (ClassInstance.FieldValue value : ((ClassInstance) instance).getValues()) {
+ if ("mFinished".equals(value.getField().getName()) || "mDestroyed"
+ .equals(value.getField().getName())) {
+ if (instance.getDistanceToGcRoot() != Integer.MAX_VALUE && value
+ .getValue() instanceof Boolean &&
+ (Boolean) value.getValue()) {
+ leakingInstances.add(instance);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ List<AnalysisResultEntry> results = new ArrayList<AnalysisResultEntry>(
+ leakingInstances.size());
+ for (Instance instance : leakingInstances) {
+ results.add(new LeakedActivityEntry(instance.getClassObj().getClassName(),
+ instance));
+ }
+ return results;
+ }
+
+ @NonNull
+ @Override
+ public String getTaskName() {
+ return "Detect Leaked Activities";
+ }
+
+ @NonNull
+ @Override
+ public String getTaskDescription() {
+ return "Detects leaked activities in Android applications.";
+ }
+
+ public static class LeakedActivityEntry extends MemoryAnalysisResultEntry {
+
+ private LeakedActivityEntry(@NonNull String offenseDescription,
+ @NonNull Instance offendingInstance) {
+ super(offenseDescription, Collections.singletonList(offendingInstance));
+ }
+
+ @NonNull
+ @Override
+ public String getWarningMessage() {
+ return mOffender.getOffendingDescription();
+ }
+
+ @NonNull
+ @Override
+ public String getCategory() {
+ return "Leaked Activities";
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalysisResultEntry.java b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalysisResultEntry.java
new file mode 100644
index 0000000..65fbeb8
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalysisResultEntry.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.memoryanalyzer;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.analyzer.AnalysisResultEntry;
+import com.android.tools.perflib.analyzer.Offender;
+import com.android.tools.perflib.heap.Instance;
+
+import java.util.List;
+
+public abstract class MemoryAnalysisResultEntry implements AnalysisResultEntry<Instance> {
+
+ @NonNull
+ protected Offender<Instance> mOffender;
+
+ protected MemoryAnalysisResultEntry(@NonNull String offenseDescription,
+ @NonNull List<Instance> offendingInstance) {
+ mOffender = new Offender<Instance>(offenseDescription, offendingInstance);
+ }
+
+ @NonNull
+ @Override
+ public Offender<Instance> getOffender() {
+ return mOffender;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalyzer.java b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalyzer.java
new file mode 100644
index 0000000..4001d4c
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalyzer.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.memoryanalyzer;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.perflib.analyzer.AnalysisReport;
+import com.android.tools.perflib.analyzer.AnalysisResultEntry;
+import com.android.tools.perflib.analyzer.Analyzer;
+import com.android.tools.perflib.analyzer.AnalyzerTask;
+import com.android.tools.perflib.analyzer.Capture;
+import com.android.tools.perflib.analyzer.CaptureGroup;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Snapshot;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableFutureTask;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+
+public class MemoryAnalyzer extends Analyzer {
+
+ private Set<MemoryAnalyzerTask> mTasks = new HashSet<MemoryAnalyzerTask>();
+
+ private AnalysisReport mOutstandingReport;
+
+ private ListenableFuture<List<List<AnalysisResultEntry>>> mRunningAnalyzers;
+
+ private volatile boolean mCancelAnalysis = false;
+
+ private boolean mAnalysisComplete = false;
+
+ private static boolean accept(@NonNull Capture capture) {
+ return Snapshot.TYPE_NAME.equals(capture.getTypeName());
+ }
+
+ @Override
+ public boolean accept(@NonNull CaptureGroup captureGroup) {
+ for (Capture capture : captureGroup.getCaptures()) {
+ if (accept(capture)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Analyze the given {@code captureGroup}. It is highly recommended to call this method on the
+ * same thread as that of the {@code synchronizingExecutor} to avoid race conditions.
+ *
+ * @param captureGroup captures to analyze
+ * @param synchronizingExecutor executor to synchronize the results aggregation
+ * @param taskExecutor executor service to run the analyzer tasks on
+ * @return an AnalysisReport in which the caller can listen to
+ */
+ @NonNull
+ @Override
+ public AnalysisReport analyze(@NonNull CaptureGroup captureGroup,
+ @NonNull Set<AnalysisReport.Listener> listeners,
+ @NonNull Set<? extends AnalyzerTask> tasks,
+ @NonNull final Executor synchronizingExecutor,
+ @NonNull ExecutorService taskExecutor) {
+ // TODO move this to Analyzer once Configuration is implemented
+ if (mOutstandingReport != null) {
+ return mOutstandingReport;
+ }
+
+ for (AnalyzerTask task : tasks) {
+ if (task instanceof MemoryAnalyzerTask) {
+ mTasks.add((MemoryAnalyzerTask) task);
+ }
+ }
+
+ mOutstandingReport = new AnalysisReport();
+ mOutstandingReport.addResultListeners(listeners);
+
+ List<ListenableFutureTask<List<AnalysisResultEntry>>> futuresList
+ = new ArrayList<ListenableFutureTask<List<AnalysisResultEntry>>>();
+
+ for (final Capture capture : captureGroup.getCaptures()) {
+ if (accept(capture)) {
+ final Snapshot snapshot = capture.getRepresentation(Snapshot.class);
+ if (snapshot == null) {
+ continue;
+ }
+
+ List<Heap> heapsToUse = new ArrayList<Heap>(snapshot.getHeaps().size());
+ for (Heap heap : snapshot.getHeaps()) {
+ if ("app".equals(heap.getName())) {
+ heapsToUse.add(heap);
+ break;
+ }
+ }
+ final MemoryAnalyzerTask.Configuration configuration
+ = new MemoryAnalyzerTask.Configuration(heapsToUse);
+
+ for (final MemoryAnalyzerTask task : mTasks) {
+ final ListenableFutureTask<List<AnalysisResultEntry>> futureTask =
+ ListenableFutureTask.create(new Callable<List<AnalysisResultEntry>>() {
+ @Override
+ public List<AnalysisResultEntry> call() throws Exception {
+ if (mCancelAnalysis) {
+ return null;
+ }
+
+ return task.analyze(configuration, snapshot);
+ }
+ });
+ Futures.addCallback(futureTask,
+ new FutureCallback<List<AnalysisResultEntry>>() {
+ @Override
+ public void onSuccess(List<AnalysisResultEntry> result) {
+ if (mCancelAnalysis) {
+ return;
+ }
+
+ mOutstandingReport.addAnalysisResultEntries(result);
+ }
+
+ @Override
+ public void onFailure(@Nullable Throwable t) {
+
+ }
+ }, synchronizingExecutor);
+ taskExecutor.submit(futureTask);
+ futuresList.add(futureTask);
+ }
+ }
+ }
+
+ mRunningAnalyzers = Futures.allAsList(futuresList);
+ Futures.addCallback(mRunningAnalyzers,
+ new FutureCallback<List<List<AnalysisResultEntry>>>() {
+ @Override
+ public void onSuccess(@Nullable List<List<AnalysisResultEntry>> result) {
+ mAnalysisComplete = true;
+ mOutstandingReport.setCompleted();
+ }
+
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ mAnalysisComplete = true;
+ mOutstandingReport.setCancelled();
+ }
+ }, synchronizingExecutor);
+ return mOutstandingReport;
+ }
+
+ @Override
+ public void cancel() {
+ if (mOutstandingReport == null || mAnalysisComplete) {
+ return;
+ }
+
+ mCancelAnalysis = true;
+ mRunningAnalyzers.cancel(true);
+ mOutstandingReport.setCancelled();
+ }
+
+ public boolean isRunning() {
+ return !mRunningAnalyzers.isDone();
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalyzerTask.java b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalyzerTask.java
new file mode 100644
index 0000000..2a98437
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/heap/memoryanalyzer/MemoryAnalyzerTask.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.memoryanalyzer;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.analyzer.AnalysisResultEntry;
+import com.android.tools.perflib.analyzer.AnalyzerTask;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Snapshot;
+
+import java.util.Collection;
+import java.util.List;
+
+public abstract class MemoryAnalyzerTask implements AnalyzerTask {
+
+ public static class Configuration {
+
+ public Collection<Heap> mHeaps;
+
+ public Configuration(@NonNull Collection<Heap> heaps) {
+ mHeaps = heaps;
+ }
+ }
+
+ abstract List<AnalysisResultEntry> analyze(@NonNull Configuration configuration,
+ @NonNull Snapshot snapshot);
+}
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/ClockType.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/ClockType.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/ClockType.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/ClockType.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodProfileData.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodProfileData.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodProfileData.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodProfileData.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/SearchResult.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/SearchResult.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/SearchResult.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/SearchResult.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/ThreadInfo.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/ThreadInfo.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/ThreadInfo.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/ThreadInfo.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/TimeSelector.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/TimeSelector.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/TimeSelector.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/TimeSelector.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/TraceAction.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/TraceAction.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/TraceAction.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/TraceAction.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/CallHierarchyRenderer.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/CallHierarchyRenderer.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/CallHierarchyRenderer.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/CallHierarchyRenderer.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/RenderContext.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/RenderContext.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/RenderContext.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/RenderContext.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeScaleRenderer.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeScaleRenderer.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeScaleRenderer.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeScaleRenderer.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeUtils.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeUtils.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeUtils.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeUtils.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TraceViewCanvas.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TraceViewCanvas.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TraceViewCanvas.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TraceViewCanvas.java
diff --git a/base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractor.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractor.java
similarity index 100%
rename from base/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractor.java
rename to perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractor.java
diff --git a/base/perflib/src/test/TraceViewTestApp/.gitignore b/perflib/src/test/TraceViewTestApp/.gitignore
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/.gitignore
rename to perflib/src/test/TraceViewTestApp/.gitignore
diff --git a/base/perflib/src/test/TraceViewTestApp/build.gradle b/perflib/src/test/TraceViewTestApp/build.gradle
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/build.gradle
rename to perflib/src/test/TraceViewTestApp/build.gradle
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/AndroidManifest.xml b/perflib/src/test/TraceViewTestApp/src/main/AndroidManifest.xml
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/AndroidManifest.xml
rename to perflib/src/test/TraceViewTestApp/src/main/AndroidManifest.xml
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Basic.java b/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Basic.java
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Basic.java
rename to perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Basic.java
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Exceptions.java b/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Exceptions.java
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Exceptions.java
rename to perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Exceptions.java
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MainActivity.java b/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MainActivity.java
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MainActivity.java
rename to perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MainActivity.java
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MisMatched.java b/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MisMatched.java
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MisMatched.java
rename to perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/MisMatched.java
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Tests.java b/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Tests.java
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Tests.java
rename to perflib/src/test/TraceViewTestApp/src/main/java/com/test/android/traceview/Tests.java
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/res/drawable/ic_launcher.png b/perflib/src/test/TraceViewTestApp/src/main/res/drawable/ic_launcher.png
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/res/drawable/ic_launcher.png
rename to perflib/src/test/TraceViewTestApp/src/main/res/drawable/ic_launcher.png
diff --git a/base/perflib/src/test/TraceViewTestApp/src/main/res/layout/activity_main.xml b/perflib/src/test/TraceViewTestApp/src/main/res/layout/activity_main.xml
similarity index 100%
rename from base/perflib/src/test/TraceViewTestApp/src/main/res/layout/activity_main.xml
rename to perflib/src/test/TraceViewTestApp/src/main/res/layout/activity_main.xml
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/ArrayInstanceTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/ArrayInstanceTest.java
new file mode 100644
index 0000000..250abe6
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/ArrayInstanceTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.tools.perflib.heap.hprof.*;
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class ArrayInstanceTest extends TestCase {
+
+ public void testCharArrayInstance() throws IOException {
+ // Set up a heap dump with an array of characters:
+ // {'a', 'b', 'c', 'd'}.
+ HprofStringBuilder strings = new HprofStringBuilder(0);
+ List<HprofRecord> records = new ArrayList<HprofRecord>();
+ List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+
+ long chars[] = new long[]{'a', 'b', 'c', 'd'};
+ dump.add(new HprofPrimitiveArrayDump(0xA, 0, HprofType.TYPE_CHAR, chars));
+
+ records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+
+ // TODO: When perflib can handle the case where strings are referred to
+ // before they are defined, just add the string records to the records
+ // list.
+ List<HprofRecord> actualRecords = new ArrayList<HprofRecord>();
+ actualRecords.addAll(strings.getStringRecords());
+ actualRecords.addAll(records);
+
+ Snapshot snapshot = null;
+ Hprof hprof = new Hprof("JAVA PROFILE 1.0.3", 2, new Date(), actualRecords);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ hprof.write(os);
+ InMemoryBuffer buffer = new InMemoryBuffer(os.toByteArray());
+ snapshot = Snapshot.createSnapshot(buffer);
+
+ ArrayInstance a = (ArrayInstance)snapshot.findInstance(0xA);
+ assertEquals(4, a.getLength());
+ assertEquals(Type.CHAR, a.getArrayType());
+
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'd'}, a.asCharArray(0, 4));
+ assertArrayEquals(new char[]{'b', 'c'}, a.asCharArray(1, 2));
+ assertArrayEquals(new char[]{}, a.asCharArray(1, 0));
+ }
+
+ private static void assertArrayEquals(char[] a, char[] b) {
+ assertEquals(a.length, b.length);
+ for (int i = 0; i < a.length; i++) {
+ assertEquals(a[i], b[i]);
+ }
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/ClassObjTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/ClassObjTest.java
new file mode 100644
index 0000000..abd5260
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/ClassObjTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap;
+
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class ClassObjTest extends TestCase {
+
+ Snapshot mSnapshot;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ File file = new File(getClass().getResource("/dialer.android-hprof").getFile());
+ mSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mSnapshot.dispose();
+ mSnapshot = null;
+ }
+
+ public void testGetAllFieldsCount() {
+ ClassObj application = mSnapshot.findClass("android.app.Application");
+ assertNotNull(application);
+ assertEquals(5, application.getAllFieldsCount());
+
+ assertNull(application.getClassObj());
+
+ ClassObj dialer = mSnapshot.findClass("com.android.dialer.DialerApplication");
+ assertNotNull(dialer);
+ assertEquals(5, dialer.getAllFieldsCount());
+ }
+
+ public void testComparison() {
+ ClassObj a = new ClassObj(1, null, "TestClassA", 0);
+ ClassObj b = new ClassObj(1, null, "TestClassB", 0);
+ ClassObj c = new ClassObj(2, null, "TestClassC", 0);
+ ClassObj aAlt = new ClassObj(3, null, "TestClassA", 0);
+
+ assertEquals(0, a.compareTo(a));
+ assertEquals(0, a.compareTo(b)); // This is a weird test case, since IDs are supposed to be unique.
+ assertTrue(c.compareTo(a) > 0);
+ assertTrue(aAlt.compareTo(a) > 0);
+ }
+
+ public void testSubClassNameClash() {
+ ClassObj superClass = new ClassObj(1, null, "TestClassA", 0);
+ ClassObj subClass1 = new ClassObj(2, null, "SubClass", 0);
+ ClassObj subClass2 = new ClassObj(3, null, "SubClass", 0);
+ superClass.addSubclass(subClass1);
+ superClass.addSubclass(subClass2);
+
+ assertEquals(2, superClass.getSubclasses().size());
+ assertTrue(superClass.getSubclasses().contains(subClass1));
+ assertTrue(superClass.getSubclasses().contains(subClass2));
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/HprofParserTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/HprofParserTest.java
new file mode 100644
index 0000000..dfdbeba
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/HprofParserTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+public class HprofParserTest extends TestCase {
+
+ Snapshot mSnapshot;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ File file = new File(getClass().getResource("/dialer.android-hprof").getFile());
+ mSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file));
+ mSnapshot.resolveReferences();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mSnapshot.dispose();
+ mSnapshot = null;
+ }
+
+ public void testHierarchy() {
+ ClassObj application = mSnapshot.findClass("android.app.Application");
+ assertNotNull(application);
+
+ ClassObj contextWrapper = application.getSuperClassObj();
+ assertNotNull(contextWrapper);
+ assertEquals("android.content.ContextWrapper", contextWrapper.getClassName());
+ contextWrapper.getSubclasses().contains(application);
+
+ ClassObj context = contextWrapper.getSuperClassObj();
+ assertNotNull(context);
+ assertEquals("android.content.Context", context.getClassName());
+ context.getSubclasses().contains(contextWrapper);
+
+ ClassObj object = context.getSuperClassObj();
+ assertNotNull(object);
+ assertEquals("java.lang.Object", object.getClassName());
+ object.getSubclasses().contains(context);
+
+ ClassObj none = object.getSuperClassObj();
+ assertNull(none);
+ }
+
+ public void testClassLoaders() {
+ ClassObj application = mSnapshot.findClass("android.app.Application");
+ assertNull(application.getClassLoader());
+
+ ClassObj dialer = mSnapshot.findClass("com.android.dialer.DialerApplication");
+ Instance classLoader = dialer.getClassLoader();
+ assertNotNull(classLoader);
+ assertEquals("dalvik.system.PathClassLoader", classLoader.getClassObj().getClassName());
+ }
+
+ public void testPrimitiveArrays() {
+ ClassObj byteArray = mSnapshot.findClass("byte[]");
+ assertEquals(1406, byteArray.getInstancesList().size());
+ assertEquals(0, byteArray.getInstanceSize());
+ assertEquals(681489, byteArray.getShallowSize());
+
+ ArrayInstance byteArrayInstance = (ArrayInstance) mSnapshot.findInstance(0xB0D60401);
+ assertEquals(byteArray, byteArrayInstance.getClassObj());
+ assertEquals(43224, byteArrayInstance.getSize());
+ assertEquals(43224, byteArrayInstance.getCompositeSize());
+
+ ClassObj intArrayArray = mSnapshot.findClass("int[][]");
+ assertEquals(37, intArrayArray.getInstancesList().size());
+
+ ArrayInstance intArrayInstance = (ArrayInstance) mSnapshot.findInstance(0xB0F69F58);
+ assertEquals(intArrayArray, intArrayInstance.getClassObj());
+ assertEquals(40, intArrayInstance.getSize());
+ assertEquals(52, intArrayInstance.getCompositeSize());
+
+ ClassObj stringArray = mSnapshot.findClass("java.lang.String[]");
+ assertEquals(1396, stringArray.getInstancesList().size());
+ }
+
+ /**
+ * Tests the creation of an Enum class which covers static values, fields of type references,
+ * strings and primitive values.
+ */
+ public void testObjectConstruction() {
+ ClassObj clazz = mSnapshot.findClass("java.lang.Thread$State");
+ assertNotNull(clazz);
+
+ Object object = clazz.getStaticField(Type.OBJECT, "$VALUES");
+ assertTrue(object instanceof ArrayInstance);
+ ArrayInstance array = (ArrayInstance) object;
+ Object[] values = array.getValues();
+ assertEquals(6, values.length);
+
+ Collection<Instance> instances = clazz.getInstancesList();
+ for (Object value : values) {
+ assertTrue(value instanceof Instance);
+ assertTrue(instances.contains(value));
+ }
+
+ Object enumValue = clazz.getStaticField(Type.OBJECT, "NEW");
+ assertTrue(enumValue instanceof ClassInstance);
+ ClassInstance instance = (ClassInstance) enumValue;
+ assertSame(clazz, instance.getClassObj());
+
+ List<ClassInstance.FieldValue> fields = instance.getFields("name");
+ assertEquals(1, fields.size());
+ assertEquals(Type.OBJECT, fields.get(0).getField().getType());
+ Object name = fields.get(0).getValue();
+
+ assertTrue(name instanceof ClassInstance);
+ ClassInstance string = (ClassInstance) name;
+ assertEquals("java.lang.String", string.getClassObj().getClassName());
+ fields = string.getFields("value");
+ assertEquals(1, fields.size());
+ assertEquals(Type.OBJECT, fields.get(0).getField().getType());
+ Object value = fields.get(0).getValue();
+ assertTrue(value instanceof ArrayInstance);
+ Object[] data = ((ArrayInstance) value).getValues();
+ assertEquals(3, data.length);
+ assertEquals('N', data[0]);
+ assertEquals('E', data[1]);
+ assertEquals('W', data[2]);
+
+ fields = instance.getFields("ordinal");
+ assertEquals(1, fields.size());
+ assertEquals(Type.INT, fields.get(0).getField().getType());
+ assertEquals(0, fields.get(0).getValue());
+ }
+
+ /**
+ * Tests getValues to make sure it's not adding duplicate entries to the back references.
+ */
+ public void testDuplicateEntries() {
+ mSnapshot = new SnapshotBuilder(2).addReferences(1, 2).addRoot(1).build();
+ mSnapshot.computeDominators();
+
+ assertEquals(2, mSnapshot.getReachableInstances().size());
+ ClassInstance parent = (ClassInstance)mSnapshot.findInstance(1);
+ List<ClassInstance.FieldValue> firstGet = parent.getValues();
+ List<ClassInstance.FieldValue> secondGet = parent.getValues();
+ assertEquals(1, firstGet.size());
+ assertEquals(firstGet.size(), secondGet.size());
+ Instance child = mSnapshot.findInstance(2);
+ assertEquals(1, child.getHardReverseReferences().size());
+ }
+
+ public void testResolveReferences() {
+ mSnapshot = new SnapshotBuilder(1).addRoot(1).build();
+ ClassObj subSoftReferenceClass = new ClassObj(98, null, "SubSoftReference", 0);
+ subSoftReferenceClass.setSuperClassId(SnapshotBuilder.SOFT_REFERENCE_ID);
+ ClassObj subSubSoftReferenceClass = new ClassObj(97, null, "SubSubSoftReference", 0);
+ subSubSoftReferenceClass.setSuperClassId(98);
+
+ mSnapshot.findClass(SnapshotBuilder.SOFT_REFERENCE_ID).addSubclass(subSoftReferenceClass);
+ subSoftReferenceClass.addSubclass(subSubSoftReferenceClass);
+
+ mSnapshot.addClass(98, subSoftReferenceClass);
+ mSnapshot.addClass(97, subSubSoftReferenceClass);
+
+ mSnapshot.identifySoftReferences();
+
+ assertTrue(subSoftReferenceClass.getIsSoftReference());
+ assertTrue(subSubSoftReferenceClass.getIsSoftReference());
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/QueriesTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/QueriesTest.java
new file mode 100644
index 0000000..9345e30
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/QueriesTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Collection;
+
+public class QueriesTest extends TestCase {
+
+ public void testCommonClassesQuery() throws Exception {
+ File basic = new File(getClass().getResource("/basic.android-hprof").getFile());
+ Snapshot basicSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(basic));
+
+ File dialer = new File(getClass().getResource("/dialer.android-hprof").getFile());
+ Snapshot dialerSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(dialer));
+
+ Collection<ClassObj> classes = Queries.commonClasses(basicSnapshot, dialerSnapshot);
+ assertEquals(3521, classes.size());
+
+ ClassObj clazz1 = basicSnapshot.findClass("android.app.Application");
+ assertNotNull(dialerSnapshot.findClass(clazz1.getClassName()));
+ assertTrue(classes.contains(clazz1));
+
+ // Application class in basicTest.
+ ClassObj clazz2 = basicSnapshot.findClass("com.android.tests.basic.Main");
+ assertNull(dialerSnapshot.findClass(clazz2.getClassName()));
+ assertFalse(classes.contains(clazz2));
+
+ // Application class in Dialer.
+ ClassObj clazz3 = dialerSnapshot.findClass("com.android.dialer.DialerApplication");
+ assertNull(basicSnapshot.findClass(clazz3.getClassName()));
+ assertFalse(classes.contains(clazz2));
+
+ basicSnapshot.dispose();
+ dialerSnapshot.dispose();
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/SnapshotBuilder.java b/perflib/src/test/java/com/android/tools/perflib/heap/SnapshotBuilder.java
new file mode 100644
index 0000000..02e2827
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/SnapshotBuilder.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+import com.android.tools.perflib.heap.hprof.*;
+
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Utility for creating Snapshot objects to be used in tests.
+ *
+ * As the main concern here is graph connectivity, we only initialize the app heap, creating
+ * ClassInstance objects with id in [1..numNodes], each instance pointing to a unique ClassObj.
+ * The class ids range in [101..100+numNodes] and their size is set to match the id of their object
+ * instance. The default heap holds the roots.
+ */
+public class SnapshotBuilder {
+ public static final int SOFT_REFERENCE_ID = 99;
+
+ public static final int SOFT_AND_HARD_REFERENCE_ID = 98;
+
+ private int mNextAvailableSoftReferenceNodeId;
+
+ private int mNextAvailableSoftAndHardReferenceNodeId;
+
+ private int mNumNodes;
+ private int mNumSoftNodes;
+ private int mNumSoftAndHardNodes;
+ private int mMaxTotalNodes;
+
+ // Map from node id to the list of nodes it references.
+ private List<Integer>[] mReferences;
+
+ private List<Integer> mRoots;
+
+ public SnapshotBuilder(int numNodes) {
+ this(numNodes, 0, 0);
+ }
+
+ public SnapshotBuilder(int numNodes, int numSoftNodes, int numSoftAndHardNodes) {
+ mNextAvailableSoftReferenceNodeId = numNodes + 1;
+ mNextAvailableSoftAndHardReferenceNodeId = numNodes + numSoftNodes + 1;
+ mNumNodes = numNodes;
+ mNumSoftNodes = numSoftNodes;
+ mNumSoftAndHardNodes = numSoftAndHardNodes;
+ mMaxTotalNodes = numNodes + numSoftNodes + numSoftAndHardNodes;
+ mReferences = (List<Integer>[])new List[mMaxTotalNodes+1];
+ for (int i = 0; i < mReferences.length; i++) {
+ mReferences[i] = new ArrayList<Integer>();
+ }
+ mRoots = new ArrayList<Integer>();
+ }
+
+ public SnapshotBuilder addReferences(int nodeFrom, int... nodesTo) {
+ assertEquals(mReferences[nodeFrom].size(), 0);
+ for (int i = 0; i < nodesTo.length; i++) {
+ mReferences[nodeFrom].add(nodesTo[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Inserts a soft reference instance between <code>nodeFrom</code> to <code>nodeTo</code>.
+ *
+ * @param nodeFrom the parent node
+ * @param nodeTo the child node
+ * @return this
+ */
+ public SnapshotBuilder insertSoftReference(int nodeFrom, int nodeToSoftReference) {
+ int softReferenceId = mNextAvailableSoftReferenceNodeId++;
+ assert softReferenceId <= mNumNodes + mNumSoftNodes;
+ mReferences[nodeFrom].add(softReferenceId);
+ mReferences[softReferenceId].add(nodeToSoftReference);
+ return this;
+ }
+
+ public SnapshotBuilder insertSoftAndHardReference(int nodeFrom,
+ int nodeToSoftReference, int nodeToHardReference) {
+ int softReferenceId = mNextAvailableSoftAndHardReferenceNodeId++;
+ assert softReferenceId <= mMaxTotalNodes;
+ mReferences[nodeFrom].add(softReferenceId);
+ mReferences[softReferenceId].add(nodeToSoftReference);
+ mReferences[softReferenceId].add(nodeToHardReference);
+ return this;
+ }
+
+ public SnapshotBuilder addRoot(int node) {
+ mRoots.add(node);
+ return this;
+ }
+
+ public Snapshot build() {
+ HprofStringBuilder strings = new HprofStringBuilder(0);
+ List<HprofRecord> records = new ArrayList<HprofRecord>();
+ List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+ byte objType = HprofType.TYPE_OBJECT;
+
+ // Roots go in the default heap. Add those first.
+ for (Integer id : mRoots) {
+ dump.add(new HprofRootUnknown(id));
+ }
+
+ // Everything else goes in "testHeap" with id 13.
+ dump.add(new HprofHeapDumpInfo(13, strings.get("testHeap")));
+
+ // The SoftReference class
+ records.add(new HprofLoadClass( 0, 0, SOFT_REFERENCE_ID, 0,
+ strings.get("java.lang.ref.Reference")));
+ dump.add(new HprofClassDump(SOFT_REFERENCE_ID, 0, 0, 0, 0, 0, 0, 0, 0,
+ new HprofConstant[0], new HprofStaticField[0],
+ new HprofInstanceField[]{
+ new HprofInstanceField(strings.get("referent"), objType)}));
+
+ // The SoftAndHardReference class
+ records.add(new HprofLoadClass(0, 1, SOFT_AND_HARD_REFERENCE_ID, 0,
+ strings.get("SoftAndHardReference")));
+ dump.add(new HprofClassDump(SOFT_AND_HARD_REFERENCE_ID, 0, 0, 0, 0, 0, 0, 0, 0,
+ new HprofConstant[0], new HprofStaticField[0],
+ new HprofInstanceField[]{
+ new HprofInstanceField(strings.get("referent"), objType),
+ new HprofInstanceField(strings.get("hardReference"), objType)}));
+
+ // Regular nodes and their classes
+ for (int i = 1; i <= mNumNodes; i++) {
+ HprofInstanceField[] fields = new HprofInstanceField[mReferences[i].size()];
+ ByteArrayDataOutput values = ByteStreams.newDataOutput();
+ for (int j = 0; j < fields.length; j++) {
+ fields[j] = new HprofInstanceField(strings.get("field" + j), objType);
+ values.writeShort(mReferences[i].get(j));
+ }
+
+ // Use same name classes on different loaders to extend test coverage
+ records.add(new HprofLoadClass(0, 0, 100+i, 0, strings.get("Class" + (i/2))));
+ dump.add(new HprofClassDump(100+i, 0, 0, i%2, 0, 0, 0, 0, i,
+ new HprofConstant[0], new HprofStaticField[0], fields));
+
+ dump.add(new HprofInstanceDump(i, 0, 100+i, values.toByteArray()));
+ }
+
+ // Soft reference nodes.
+ for (int i = mNumNodes + 1; i <= mNumNodes + mNumSoftNodes; ++i) {
+ assertEquals(1, mReferences[i].size());
+ ByteArrayDataOutput values = ByteStreams.newDataOutput();
+ values.writeShort(mReferences[i].get(0));
+ dump.add(new HprofInstanceDump(i, 0, SOFT_REFERENCE_ID, values.toByteArray()));
+ }
+
+ // Soft and hard reference nodes.
+ for (int i = mNumNodes + mNumSoftNodes + 1; i <= mMaxTotalNodes; ++i) {
+ assertEquals(2, mReferences[i].size());
+ ByteArrayDataOutput values = ByteStreams.newDataOutput();
+ values.writeShort(mReferences[i].get(0));
+ values.writeShort(mReferences[i].get(1));
+ dump.add(new HprofInstanceDump(i, 0, SOFT_AND_HARD_REFERENCE_ID, values.toByteArray()));
+ }
+
+ records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+
+ // TODO: Should perflib handle the case where strings are referred to
+ // before they are defined?
+ List<HprofRecord> actualRecords = new ArrayList<HprofRecord>();
+ actualRecords.addAll(strings.getStringRecords());
+ actualRecords.addAll(records);
+
+ Snapshot snapshot = null;
+ try {
+ Hprof hprof = new Hprof("JAVA PROFILE 1.0.3", 2, new Date(), actualRecords);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ hprof.write(os);
+ InMemoryBuffer buffer = new InMemoryBuffer(os.toByteArray());
+ snapshot = Snapshot.createSnapshot(buffer);
+ } catch (IOException e) {
+ fail("IOException when writing to byte output stream: " + e);
+ }
+
+ // TODO: Should the parser be setting isSoftReference, not the builder?
+ for (Heap heap : snapshot.getHeaps()) {
+ ClassObj softClass = heap.getClass(SOFT_REFERENCE_ID);
+ if (softClass != null) {
+ softClass.setIsSoftReference();
+ }
+
+ ClassObj softAndHardClass = heap.getClass(SOFT_AND_HARD_REFERENCE_ID);
+ if (softAndHardClass != null) {
+ softAndHardClass.setIsSoftReference();
+ }
+ }
+ return snapshot;
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/StacksTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/StacksTest.java
new file mode 100644
index 0000000..348de81
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/StacksTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap;
+
+import com.android.tools.perflib.heap.hprof.*;
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class StacksTest extends TestCase {
+
+ public void testStackTraces() throws IOException {
+ // Set up a heap dump with 3 stack frames, 2 stack traces, one class
+ // with two instances, where each instance is allocated from a different
+ // stack. Then test we can access the stack traces and frames for the
+ // instances.
+ HprofStringBuilder strings = new HprofStringBuilder(0);
+ List<HprofRecord> records = new ArrayList<HprofRecord>();
+ List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+
+ final int fooClassSerialNumber = 1;
+ records.add(new HprofLoadClass(0, 0, fooClassSerialNumber, 0,
+ strings.get("Foo")));
+ dump.add(new HprofClassDump(fooClassSerialNumber, 0, 0, 0, 0, 0, 0, 0, 0,
+ new HprofConstant[0], new HprofStaticField[0],
+ new HprofInstanceField[0]));
+
+ records.add(new HprofStackFrame(0, 1, strings.get("method1"),
+ strings.get("method1(int)"), strings.get("Foo.java"),
+ fooClassSerialNumber, 13));
+ records.add(new HprofStackFrame(0, 2, strings.get("method2"),
+ strings.get("method2(double)"), strings.get("Foo.java"),
+ fooClassSerialNumber, 23));
+ records.add(new HprofStackFrame(0, 3, strings.get("method3"),
+ strings.get("method3(long)"), strings.get("Foo.java"),
+ fooClassSerialNumber, 33));
+ records.add(new HprofStackTrace(0, 0x51, 1, new long[]{1, 3}));
+ records.add(new HprofStackTrace(0, 0x52, 1, new long[]{2}));
+
+ dump.add(new HprofHeapDumpInfo(0xA, strings.get("heapA")));
+ dump.add(new HprofInstanceDump(0xA1, 0x51, fooClassSerialNumber, new byte[0]));
+
+ dump.add(new HprofHeapDumpInfo(0xB, strings.get("heapB")));
+ dump.add(new HprofInstanceDump(0xB2, 0x52, fooClassSerialNumber, new byte[0]));
+ records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+
+ // TODO: When perflib can handle the case where strings are referred to
+ // before they are defined, just add the string records to the records
+ // list.
+ List<HprofRecord> actualRecords = new ArrayList<HprofRecord>();
+ actualRecords.addAll(strings.getStringRecords());
+ actualRecords.addAll(records);
+
+ Snapshot snapshot = null;
+ Hprof hprof = new Hprof("JAVA PROFILE 1.0.3", 2, new Date(), actualRecords);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ hprof.write(os);
+ InMemoryBuffer buffer = new InMemoryBuffer(os.toByteArray());
+ snapshot = Snapshot.createSnapshot(buffer);
+
+ Instance a1 = snapshot.findInstance(0xA1);
+ StackTrace a1stack = a1.getStack();
+ assertEquals(0x51 , a1stack.getSerialNumber());
+ StackFrame[] a1frames = a1stack.getFrames();
+ assertEquals(2, a1frames.length);
+ assertEquals("method1", a1frames[0].getMethodName());
+ assertEquals("method1(int)", a1frames[0].getSignature());
+ assertEquals("Foo.java", a1frames[0].getFilename());
+ assertEquals(13, a1frames[0].getLineNumber());
+ assertEquals("method3", a1frames[1].getMethodName());
+ assertEquals("method3(long)", a1frames[1].getSignature());
+ assertEquals("Foo.java", a1frames[1].getFilename());
+ assertEquals(33, a1frames[1].getLineNumber());
+
+ Instance b2 = snapshot.findInstance(0xB2);
+ StackTrace b2stack = b2.getStack();
+ assertEquals(0x52 , b2stack.getSerialNumber());
+ StackFrame[] b2frames = b2stack.getFrames();
+ assertEquals(1, b2frames.length);
+ assertEquals("method2", b2frames[0].getMethodName());
+ assertEquals("method2(double)", b2frames[0].getSignature());
+ assertEquals("Foo.java", b2frames[0].getFilename());
+ assertEquals(23, b2frames[0].getLineNumber());
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/analysis/ConvergingDominators.java b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/ConvergingDominators.java
new file mode 100644
index 0000000..1610b8a
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/ConvergingDominators.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import com.android.tools.perflib.heap.Snapshot;
+
+/**
+ * Initial implementation of dominator computation. <p/> Node <i>d</i> is said to dominate node
+ * <i>n</i> if every path from any of the roots to node <i>n</i> must go through <i>d</i>. The
+ * <b>immediate</b> dominator of a node <i>n</i> is the dominator <i>d</i> that is closest to
+ * <i>n</i>. The immediate dominance relation yields a tree called <b>dominator tree</b>, with the
+ * important property that the subtree of a node corresponds to the retained object graph of that
+ * particular node, i.e. the amount of memory that could be freed if the node were garbage
+ * collected. <p/> The full algorithm is described in {@see http://www.cs.rice.edu/~keith/EMBED/dom.pdf}.
+ * It's a simple iterative algorithm with worst-case complexity of O(N^2).
+ */
+public class ConvergingDominators extends DominatorsBase {
+ private volatile int mStartingNodesCount = 0;
+
+ private volatile int mRetiredNodes = 0;
+
+ public ConvergingDominators(@NonNull Snapshot snapshot) {
+ super(snapshot);
+ mRetiredNodes = 0;
+ mStartingNodesCount = mTopSort.size();
+
+ // Only instances reachable from the GC roots will participate in dominator computation.
+ // We will omit from the analysis any other nodes which could be considered roots, i.e. with
+ // no incoming references, if they are not GC roots.
+ for (RootObj root : snapshot.getGCRoots()) {
+ Instance ref = root.getReferredInstance();
+ if (ref != null) {
+ ref.setImmediateDominator(Snapshot.SENTINEL_ROOT);
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public ComputationProgress getComputationProgress() {
+ return new ComputationProgress(
+ String.format("Calculating dominators %d/%d", mRetiredNodes, mStartingNodesCount),
+ (double) mRetiredNodes / (double) mStartingNodesCount);
+ }
+
+ @Override
+ public void computeDominators() {
+ // We need to iterate on the dominator computation because the graph may contain cycles.
+ boolean changed = true;
+ while (changed) {
+ changed = false;
+
+ for (Instance node : mTopSort) {
+ // Root nodes and nodes immediately dominated by the SENTINEL_ROOT are skipped.
+ if (node.getImmediateDominator() != Snapshot.SENTINEL_ROOT) {
+ Instance dominator = null;
+
+ for (int j = 0; j < node.getHardReverseReferences().size(); j++) {
+ Instance predecessor = node.getHardReverseReferences().get(j);
+ if (predecessor.getImmediateDominator() == null) {
+ // If we don't have a dominator/approximation for predecessor, skip it
+ continue;
+ }
+ if (dominator == null) {
+ dominator = predecessor;
+ }
+ else {
+ Instance fingerA = dominator;
+ Instance fingerB = predecessor;
+ while (fingerA != fingerB) {
+ if (fingerA.getTopologicalOrder() < fingerB.getTopologicalOrder()) {
+ assert fingerB.getTopologicalOrder() >=
+ fingerB.getImmediateDominator().getTopologicalOrder();
+ fingerB = fingerB.getImmediateDominator();
+ }
+ else {
+ assert fingerA.getTopologicalOrder() >=
+ fingerA.getImmediateDominator().getTopologicalOrder();
+ fingerA = fingerA.getImmediateDominator();
+ }
+ }
+ dominator = fingerA;
+ }
+ }
+
+ if (node.getImmediateDominator() != dominator) {
+ node.setImmediateDominator(dominator);
+ changed = true;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorOptimizationTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorOptimizationTest.java
new file mode 100644
index 0000000..442d81b
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorOptimizationTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Snapshot;
+import gnu.trove.TObjectProcedure;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class DominatorOptimizationTest extends TestCase {
+ public void testCorrectness() throws IOException {
+ File file = new File(getClass().getResource("/native_allocation.android-hprof").getFile());
+
+ Snapshot groundTruthSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file));
+ groundTruthSnapshot.prepareDominatorComputation();
+ groundTruthSnapshot.doComputeDominators(new ConvergingDominators(groundTruthSnapshot));
+
+ Snapshot optimizedSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file));
+ optimizedSnapshot.prepareDominatorComputation();
+ optimizedSnapshot.doComputeDominators(new LinkEvalDominators(optimizedSnapshot));
+
+ for (Heap groundTruthHeap : groundTruthSnapshot.getHeaps()) {
+ int heapId = groundTruthHeap.getId();
+ final Heap optimizedHeap = optimizedSnapshot.getHeap(heapId);
+ assertNotNull(optimizedHeap);
+
+ groundTruthHeap.forEachInstance(new TObjectProcedure<Instance>() {
+ @Override
+ public boolean execute(Instance groundTruthInstance) {
+ Instance optimizedInstance = optimizedHeap.getInstance(groundTruthInstance.getId());
+ assertNotNull(optimizedInstance);
+ assertEquals(groundTruthInstance.isReachable(), optimizedInstance.isReachable());
+ if (groundTruthInstance.isReachable()) {
+ assertNotNull(groundTruthInstance.getImmediateDominator());
+ assertNotNull(optimizedInstance.getImmediateDominator());
+ assertTrue(groundTruthInstance.getTopologicalOrder() >
+ groundTruthInstance.getImmediateDominator().getTopologicalOrder());
+ assertEquals(groundTruthInstance.getImmediateDominator().getId(),
+ optimizedInstance.getImmediateDominator().getId());
+ assertEquals(groundTruthInstance.getTotalRetainedSize(),
+ optimizedInstance.getTotalRetainedSize());
+ } else {
+ assertNull(groundTruthInstance.getImmediateDominator());
+ assertEquals(groundTruthInstance.getImmediateDominator(),
+ optimizedInstance.getImmediateDominator());
+ assertEquals(groundTruthInstance.getSize(),
+ groundTruthInstance.getTotalRetainedSize());
+ assertEquals(groundTruthInstance.getSize(),
+ optimizedInstance.getTotalRetainedSize());
+ }
+ return true;
+ }
+ });
+ }
+
+ groundTruthSnapshot.dispose();
+ optimizedSnapshot.dispose();
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorsTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorsTest.java
new file mode 100644
index 0000000..5f790cc
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/DominatorsTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.tools.perflib.heap.*;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DominatorsTest extends TestCase {
+
+ private Snapshot mSnapshot;
+
+ public void testSimpleGraph() {
+ mSnapshot = new SnapshotBuilder(6)
+ .addReferences(1, 2, 3)
+ .addReferences(2, 4, 6)
+ .addReferences(3, 4, 5)
+ .addReferences(4, 6)
+ .addRoot(1)
+ .build();
+
+ mSnapshot.computeDominators();
+
+ assertEquals(6, mSnapshot.getReachableInstances().size());
+ assertDominates(1, 2);
+ assertDominates(1, 3);
+ assertDominates(1, 4);
+ assertDominates(1, 6);
+ assertDominates(3, 5);
+
+ assertParentPathToGc(2, 1);
+ assertParentPathToGc(3, 1);
+ assertParentPathToGc(4, 2, 3);
+ assertParentPathToGc(5, 3);
+ assertParentPathToGc(6, 2);
+ }
+
+ public void testCyclicGraph() {
+ mSnapshot = new SnapshotBuilder(4)
+ .addReferences(1, 2, 3, 4)
+ .addReferences(2, 3)
+ .addReferences(3, 4)
+ .addReferences(4, 2)
+ .addRoot(1)
+ .build();
+
+ mSnapshot.computeDominators();
+
+ assertEquals(4, mSnapshot.getReachableInstances().size());
+ assertDominates(1, 2);
+ assertDominates(1, 3);
+ assertDominates(1, 4);
+
+ assertParentPathToGc(2, 1);
+ assertParentPathToGc(3, 1);
+ assertParentPathToGc(4, 1);
+ }
+
+ public void testMultipleRoots() {
+ mSnapshot = new SnapshotBuilder(6)
+ .addReferences(1, 3)
+ .addReferences(2, 4)
+ .addReferences(3, 5)
+ .addReferences(4, 5)
+ .addReferences(5, 6)
+ .addRoot(1)
+ .addRoot(2)
+ .build();
+
+ mSnapshot.computeDominators();
+
+ assertEquals(6, mSnapshot.getReachableInstances().size());
+ assertDominates(1, 3);
+ assertDominates(2, 4);
+ // Node 5 is reachable via both roots, neither of which can be the sole dominator.
+ assertEquals(mSnapshot.SENTINEL_ROOT, mSnapshot.findInstance(5).getImmediateDominator());
+ assertDominates(5, 6);
+
+ assertParentPathToGc(3, 1);
+ assertParentPathToGc(4, 2);
+ assertParentPathToGc(5, 3, 4);
+ assertParentPathToGc(6, 5);
+ }
+
+ public void testDoublyLinkedList() {
+ // Node 1 points to a doubly-linked list 2-3-4-5-6-7-8-9.
+ mSnapshot = new SnapshotBuilder(9)
+ .addReferences(1, 2)
+ .addReferences(2, 3, 9)
+ .addReferences(3, 2, 4)
+ .addReferences(4, 3, 5)
+ .addReferences(5, 4, 6)
+ .addReferences(6, 5, 7)
+ .addReferences(7, 6, 8)
+ .addReferences(8, 7, 9)
+ .addReferences(9, 2, 8)
+ .addRoot(1)
+ .build();
+
+ mSnapshot.computeDominators();
+
+ assertEquals(45, mSnapshot.findInstance(1).getRetainedSize(1));
+ assertEquals(44, mSnapshot.findInstance(2).getRetainedSize(1));
+ for (int i = 3; i <= 9; i++) {
+ assertEquals(i, mSnapshot.findInstance(i).getRetainedSize(1));
+ }
+
+ assertParentPathToGc(2, 1);
+ assertParentPathToGc(3, 2);
+ assertParentPathToGc(9, 2);
+ assertParentPathToGc(4, 3);
+ assertParentPathToGc(8, 9);
+ assertParentPathToGc(5, 4);
+ assertParentPathToGc(7, 8);
+ assertParentPathToGc(6, 5, 7);
+ }
+
+ public void testSameClassDifferentLoader() {
+ mSnapshot = new SnapshotBuilder(4)
+ .addReferences(1, 3, 2)
+ .addReferences(3, 2)
+ .addRoot(1)
+ .build();
+
+ assertNotNull(mSnapshot.getHeap(13).getClass(102));
+ assertNotNull(mSnapshot.getHeap(13).getClass(103));
+
+ mSnapshot.computeDominators();
+
+ assertEquals(0, mSnapshot.getHeap(13).getClass(102).getRetainedSize(1));
+ assertEquals(0, mSnapshot.getHeap(13).getClass(103).getRetainedSize(1));
+ }
+
+ public void testTopSort() {
+ mSnapshot = new SnapshotBuilder(4)
+ .addReferences(1, 3, 2)
+ .addReferences(3, 2)
+ .addRoot(1)
+ .build();
+
+ mSnapshot.computeDominators();
+
+ assertEquals(6, mSnapshot.findInstance(1).getRetainedSize(1));
+ assertEquals(2, mSnapshot.findInstance(2).getRetainedSize(1));
+ assertEquals(3, mSnapshot.findInstance(3).getRetainedSize(1));
+ }
+
+ public void testMultiplePaths() {
+ mSnapshot = new SnapshotBuilder(8)
+ .addReferences(1, 7, 8)
+ .addReferences(7, 2, 3)
+ .addReferences(8, 2)
+ .addReferences(2, 4)
+ .addReferences(3, 5)
+ .addReferences(5, 4)
+ .addReferences(4, 6)
+ .addRoot(1)
+ .build();
+
+ mSnapshot.computeDominators();
+
+ assertEquals(mSnapshot.findInstance(1), mSnapshot.findInstance(4).getImmediateDominator());
+ assertEquals(mSnapshot.findInstance(4), mSnapshot.findInstance(6).getImmediateDominator());
+ assertEquals(36, mSnapshot.findInstance(1).getRetainedSize(1));
+ assertEquals(2, mSnapshot.findInstance(2).getRetainedSize(1));
+ assertEquals(8, mSnapshot.findInstance(3).getRetainedSize(1));
+ }
+
+ public void testReachableInstances() {
+ mSnapshot = new SnapshotBuilder(11, 2, 1)
+ .addReferences(1, 2, 3)
+ .insertSoftReference(1, 11)
+ .addReferences(2, 4)
+ .addReferences(3, 5, 6)
+ .insertSoftReference(4, 9)
+ .addReferences(5, 7)
+ .addReferences(6, 7)
+ .addReferences(7, 8, 10)
+ .insertSoftAndHardReference(8, 10, 9)
+ .addRoot(1)
+ .build();
+
+ mSnapshot.computeDominators();
+ for (Heap heap : mSnapshot.getHeaps()) {
+ ClassObj softClass = heap.getClass(SnapshotBuilder.SOFT_REFERENCE_ID);
+ if (softClass != null) {
+ assertTrue(softClass.getIsSoftReference());
+ }
+
+ ClassObj softAndHardClass = heap.getClass(SnapshotBuilder.SOFT_AND_HARD_REFERENCE_ID);
+ if (softAndHardClass != null) {
+ assertTrue(softAndHardClass.getIsSoftReference());
+ }
+ }
+
+ Instance instance9 = mSnapshot.findInstance(9);
+ assertNotNull(instance9);
+ assertNotNull(instance9.getSoftReverseReferences());
+ assertEquals(1, instance9.getHardReverseReferences().size());
+ assertEquals(1, instance9.getSoftReverseReferences().size());
+ assertEquals(6, instance9.getDistanceToGcRoot());
+
+ Instance instance10 = mSnapshot.findInstance(10);
+ assertNotNull(instance10);
+ assertNotNull(instance10.getSoftReverseReferences());
+ assertEquals(1, instance10.getHardReverseReferences().size());
+ assertEquals(1, instance10.getSoftReverseReferences().size());
+ assertEquals(4, instance10.getDistanceToGcRoot());
+
+ Instance instance11 = mSnapshot.findInstance(11);
+ assertNotNull(instance11);
+ assertNotNull(instance11.getSoftReverseReferences());
+ assertEquals(0, instance11.getHardReverseReferences().size());
+ assertEquals(1, instance11.getSoftReverseReferences().size());
+ assertEquals(Integer.MAX_VALUE, instance11.getDistanceToGcRoot());
+
+ assertEquals(13, mSnapshot.getReachableInstances().size());
+ }
+
+ public void testSampleHprof() throws Exception {
+ File file = new File(ClassLoader.getSystemResource("dialer.android-hprof").getFile());
+ mSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file));
+ mSnapshot.computeDominators();
+
+ Set<Instance> topologicalSet = new HashSet<Instance>(mSnapshot.getTopologicalOrdering());
+ assertEquals(topologicalSet.size(), mSnapshot.getTopologicalOrdering().size());
+
+ long totalInstanceCount = 0;
+ for (Heap heap : mSnapshot.getHeaps()) {
+ totalInstanceCount += heap.getInstancesCount();
+ totalInstanceCount += heap.getClasses().size();
+ }
+ assertEquals(43687, totalInstanceCount);
+
+ assertEquals(42839, mSnapshot.getReachableInstances().size());
+
+ // An object reachable via two GC roots, a JNI global and a Thread.
+ Instance instance = mSnapshot.findInstance(0xB0EDFFA0);
+ assertEquals(Snapshot.SENTINEL_ROOT, instance.getImmediateDominator());
+
+ int appIndex = mSnapshot.getHeapIndex(mSnapshot.getHeap("app"));
+ int zygoteIndex = mSnapshot.getHeapIndex(mSnapshot.getHeap("zygote"));
+
+ // The largest object in our sample hprof belongs to the zygote
+ ClassObj htmlParser = mSnapshot.findClass("android.text.Html$HtmlParser");
+ assertEquals(116492, htmlParser.getRetainedSize(zygoteIndex));
+ assertEquals(0, htmlParser.getRetainedSize(appIndex));
+
+ // One of the bigger objects in the app heap
+ ClassObj activityThread = mSnapshot.findClass("android.app.ActivityThread");
+ assertEquals(853, activityThread.getRetainedSize(zygoteIndex));
+ assertEquals(576, activityThread.getRetainedSize(appIndex));
+
+ mSnapshot.dispose();
+ mSnapshot = null;
+ }
+
+ /**
+ * Asserts that nodeA dominates nodeB in mHeap.
+ */
+ private void assertDominates(int nodeA, int nodeB) {
+ assertEquals(mSnapshot.findInstance(nodeA),
+ mSnapshot.findInstance(nodeB).getImmediateDominator());
+ }
+
+ /**
+ * Asserts that one of the parents is the direct parent to node in the shortest path to the GC root.
+ */
+ private void assertParentPathToGc(int node, int... parents) {
+ for (int parent : parents) {
+ Instance parentInstance = mSnapshot.findInstance(node).getNextInstanceToGcRoot();
+ if (parentInstance != null && parentInstance.getId() == parent) {
+ return;
+ }
+ }
+ fail();
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/analysis/VisitorsTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/VisitorsTest.java
new file mode 100644
index 0000000..a19ea12
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/analysis/VisitorsTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.analysis;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.heap.*;
+import com.android.tools.perflib.captures.DataBuffer;
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+import com.google.common.collect.Maps;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * There are two testing scenarios we want to cover here: basic connectivity between different
+ * Instance types, and the visitor's ability to deal with cycles, diamonds, etc. in the graph. For
+ * the latter we create heaps that are only concerned with connectivity between nodes.
+ */
+public class VisitorsTest extends TestCase {
+
+ private final ClassObj mDummyClass = new ClassObj(42, null, "dummy", 0);
+
+ private Snapshot mSnapshot;
+
+ @Override
+ public void setUp() throws Exception {
+ mSnapshot = new Snapshot(new InMemoryBuffer(10));
+ mSnapshot.setHeapTo(13, "testHeap");
+ mDummyClass.setFields(new Field[0]);
+ mSnapshot.addClass(42, mDummyClass);
+ }
+
+ public void testSimpleStaticFieldsGraph() {
+ mSnapshot.setIdSize(4);
+ final ClassInstance object1 = new ClassInstance(1, null, 0);
+ object1.setClassId(42);
+ object1.setSize(20);
+ mSnapshot.addInstance(1, object1);
+
+ final ClassInstance object2 = new ClassInstance(2, null, 0);
+ object2.setClassId(42);
+ object2.setSize(20);
+ mSnapshot.addInstance(2, object2);
+
+ ClassObj clazz = new ClassObj(13, null, "FooBar", 0) {
+ @NonNull
+ @Override
+ public Map<Field, Object> getStaticFieldValues() {
+ Map<Field, Object> result = Maps.newHashMap();
+ result.put(new Field(Type.OBJECT, "foo"), object1);
+ result.put(new Field(Type.OBJECT, "bar"), object2);
+ return result;
+ }
+ };
+ clazz.setSize(10);
+ mSnapshot.addClass(13, clazz);
+
+ mSnapshot.setToDefaultHeap();
+ RootObj root = new RootObj(RootType.SYSTEM_CLASS, 13);
+ mSnapshot.addRoot(root);
+ mSnapshot.resolveReferences();
+
+ // Size of root is 2 x sizeof(mDummyClass) + sizeof(clazz)
+ assertEquals(50, root.getCompositeSize());
+ }
+
+ public void testSimpleArray() {
+ mSnapshot.setIdSize(4);
+ final ClassInstance object = new ClassInstance(1, null, 0);
+ object.setClassId(42);
+ object.setSize(20);
+ mSnapshot.addInstance(1, object);
+
+ ArrayInstance array = new ArrayInstance(2, null, Type.OBJECT, 3, 0) {
+ @NonNull
+ @Override
+ public Object[] getValues() {
+ return new Object[] {object, object, object};
+ }
+ };
+ mSnapshot.addInstance(2, array);
+
+ mSnapshot.setToDefaultHeap();
+ RootObj root = new RootObj(RootType.JAVA_LOCAL, 2);
+ mSnapshot.addRoot(root);
+ mSnapshot.resolveReferences();
+
+ // Size of root is sizeof(object) + 3 x sizeof(pointer to object)
+ assertEquals(32, root.getCompositeSize());
+ }
+
+ public void testBasicDiamond() {
+ Snapshot snapshot = new SnapshotBuilder(4)
+ .addReferences(1, 2, 3)
+ .addReferences(2, 4)
+ .addReferences(3, 4)
+ .addRoot(1)
+ .build();
+ snapshot.resolveReferences();
+
+ assertEquals(10, snapshot.findInstance(1).getCompositeSize());
+ assertEquals(6, snapshot.findInstance(2).getCompositeSize());
+ assertEquals(7, snapshot.findInstance(3).getCompositeSize());
+ assertEquals(4, snapshot.findInstance(4).getCompositeSize());
+ }
+
+ public void testBasicCycle() {
+ Snapshot snapshot = new SnapshotBuilder(3)
+ .addReferences(1, 2)
+ .addReferences(2, 3)
+ .addReferences(3, 1)
+ .addRoot(1)
+ .build();
+ snapshot.resolveReferences();
+
+ // The composite size is a sum over all nodes participating in the cycle.
+ assertEquals(6, snapshot.findInstance(1).getCompositeSize());
+ assertEquals(6, snapshot.findInstance(2).getCompositeSize());
+ assertEquals(6, snapshot.findInstance(3).getCompositeSize());
+ }
+
+ public void testTopSortSimpleGraph() {
+ Snapshot snapshot = new SnapshotBuilder(6)
+ .addReferences(1, 2, 3)
+ .addReferences(2, 4, 6)
+ .addReferences(3, 4, 5)
+ .addReferences(4, 6)
+ .addRoot(1)
+ .build();
+ snapshot.resolveReferences();
+
+ List<Instance> topSort = TopologicalSort.compute(snapshot.getGCRoots());
+ assertEquals(6, topSort.size());
+ // Make sure finishing times are computed correctly. A visitor simply collecting nodes as
+ // they are expanded will not yield the correct order. The correct invariant for a DAG is:
+ // for each directed edge (u,v), topsort(u) < topsort(v).
+ assertTrue(snapshot.findInstance(1).getTopologicalOrder() <
+ snapshot.findInstance(2).getTopologicalOrder());
+ assertTrue(snapshot.findInstance(1).getTopologicalOrder() <
+ snapshot.findInstance(3).getTopologicalOrder());
+ assertTrue(snapshot.findInstance(2).getTopologicalOrder() <
+ snapshot.findInstance(4).getTopologicalOrder());
+ assertTrue(snapshot.findInstance(2).getTopologicalOrder() <
+ snapshot.findInstance(6).getTopologicalOrder());
+ assertTrue(snapshot.findInstance(3).getTopologicalOrder() <
+ snapshot.findInstance(4).getTopologicalOrder());
+ assertTrue(snapshot.findInstance(3).getTopologicalOrder() <
+ snapshot.findInstance(5).getTopologicalOrder());
+ assertTrue(snapshot.findInstance(4).getTopologicalOrder() <
+ snapshot.findInstance(6).getTopologicalOrder());
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/io/MemoryMappedFileBufferTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/io/MemoryMappedFileBufferTest.java
new file mode 100644
index 0000000..d71466a
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/io/MemoryMappedFileBufferTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.heap.io;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.heap.Snapshot;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+
+import junit.framework.TestCase;
+import sun.misc.IOUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+public class MemoryMappedFileBufferTest extends TestCase {
+
+ File file = new File(getClass().getResource("/dialer.android-hprof").getFile());
+
+ public void testSimpleMapping() throws Exception {
+ Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file));
+ assertSnapshotCorrect(snapshot);
+ snapshot.dispose();
+ }
+
+ public void testMultiMapping() throws Exception {
+ // Split the file into chunks of 4096 bytes each, leave 128 bytes for padding.
+ MemoryMappedFileBuffer shardedBuffer = new MemoryMappedFileBuffer(file, 4096, 128);
+ Snapshot snapshot = Snapshot.createSnapshot(shardedBuffer);
+ assertSnapshotCorrect(snapshot);
+ snapshot.dispose();
+ }
+
+ public void testMultiMappingWrappedRead() throws Exception {
+ // Leave just 8 bytes for padding to force wrapped reads.
+ MemoryMappedFileBuffer shardedBuffer = new MemoryMappedFileBuffer(file, 9973, 8);
+ Snapshot snapshot = Snapshot.createSnapshot(shardedBuffer);
+ assertSnapshotCorrect(snapshot);
+ snapshot.dispose();
+ }
+
+ public void testMemoryMappingRemoval() throws Exception {
+ File tmpFile = File.createTempFile("test_vm", ".tmp");
+ System.err.println("vm temp file: " + tmpFile.getAbsolutePath());
+ System.err.println("jvm " + System.getProperty("sun.arch.data.model"));
+
+ long n = 500000000L;
+ RandomAccessFile raf = new RandomAccessFile(tmpFile, "rw");
+ try {
+ raf.setLength(n);
+ raf.write(1);
+ raf.seek(n - 1);
+ raf.write(2);
+ }
+ finally {
+ raf.close();
+ }
+
+ MemoryMappedFileBuffer buffer = new MemoryMappedFileBuffer(tmpFile);
+ assertEquals(1, buffer.readByte());
+ buffer.setPosition(n - 1);
+ assertEquals(2, buffer.readByte());
+
+ // On Windows, tmpFile can't be deleted without unmapping it first.
+ buffer.dispose();
+ tmpFile.delete();
+
+ File g = new File(tmpFile.getCanonicalPath());
+ assertFalse(g.exists());
+ }
+
+ public void testSubsequenceReads() throws Exception {
+ byte[] fileContents = null;
+ FileInputStream fileInputStream = new FileInputStream(file);
+ try {
+ fileContents = IOUtils.readFully(fileInputStream, -1, true);
+ }
+ finally {
+ fileInputStream.close();
+ }
+
+ MemoryMappedFileBuffer mappedBuffer = new MemoryMappedFileBuffer(file, 8259, 8);
+
+ byte[] buffer = new byte[8190];
+ mappedBuffer.readSubSequence(buffer, 0, 8190);
+ assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 0, 8190)));
+ assertEquals(mappedBuffer.position(), 8190);
+
+ buffer = new byte[8190];
+ mappedBuffer.setPosition(0);
+ mappedBuffer.readSubSequence(buffer, 2000, 8190);
+ assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 2000, 2000 + 8190)));
+ assertEquals(mappedBuffer.position(), 2000 + 8190);
+
+ buffer = new byte[100000];
+ mappedBuffer.setPosition(0);
+ mappedBuffer.readSubSequence(buffer, 19242, 100000);
+ assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 19242, 19242 + 100000)));
+ assertEquals(mappedBuffer.position(), 19242 + 100000);
+
+ buffer = new byte[8259];
+ mappedBuffer.setPosition(0);
+ mappedBuffer.readSubSequence(buffer, 0, 8259);
+ assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 0, 8259)));
+ assertEquals(mappedBuffer.position(), 8259);
+
+ buffer = new byte[8259];
+ mappedBuffer.setPosition(0);
+ mappedBuffer.readSubSequence(buffer, 8259, 8259);
+ assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 8259, 8259 + 8259)));
+ assertEquals(mappedBuffer.position(), 8259 + 8259);
+
+ mappedBuffer.readSubSequence(buffer, 8259, 8259);
+ assertTrue(Arrays.equals(buffer, Arrays.copyOfRange(fileContents, 8259 * 3, 8259 * 4)));
+ assertEquals(mappedBuffer.position(), 8259 * 4);
+ }
+
+ private static void assertSnapshotCorrect(@NonNull Snapshot snapshot) {
+ assertEquals(11182, snapshot.getGCRoots().size());
+ assertEquals(38, snapshot.getHeap(65).getClasses().size());
+ assertEquals(1406, snapshot.getHeap(65).getInstancesCount());
+ assertEquals(3533, snapshot.getHeap(90).getClasses().size());
+ assertEquals(38710, snapshot.getHeap(90).getInstancesCount());
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/heap/memoryanalyzer/HprofBitmapProviderTest.java b/perflib/src/test/java/com/android/tools/perflib/heap/memoryanalyzer/HprofBitmapProviderTest.java
new file mode 100644
index 0000000..b2c4e17
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/heap/memoryanalyzer/HprofBitmapProviderTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.perflib.heap.memoryanalyzer;
+
+import com.android.ddmlib.BitmapDecoder;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.Snapshot;
+
+import junit.framework.TestCase;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+public class HprofBitmapProviderTest extends TestCase {
+ public static final int ARGB_565_INSTANCE = 0x12cff7c0;
+
+ public static final int ARGB_8888_MUTABLE = 0x12cff780;
+
+ public static final int ARGB_8888_INSTANCE = 0x12cff740;
+
+ public static final int BITMAP_DRAWABLE_INSTANCE = 0x12cb0c40;
+
+ public static final int ACTIVITY_INSTANCE = 0x12c722a0;
+
+ private static Snapshot mSnapshot;
+
+ private static Heap mAppHeap;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ File testHprofFile = new File(
+ getClass().getResource("/bitmap_test.android-hprof").getFile());
+ assert testHprofFile.exists();
+ mSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(testHprofFile));
+ mAppHeap = mSnapshot.getHeap("app");
+ assert mAppHeap != null;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mSnapshot.dispose();
+ mSnapshot = null;
+ mAppHeap = null;
+ }
+
+ public void testGetBitmapFromBitmapBitmap() {
+ Instance bitmapDrawable = mAppHeap.getInstance(ARGB_565_INSTANCE);
+ boolean isBitmap = HprofBitmapProvider.canGetBitmapFromInstance(bitmapDrawable);
+ assert isBitmap;
+ }
+
+ public void testGetBitmapFromBitmapDrawable() {
+ Instance bitmapDrawable = mAppHeap.getInstance(BITMAP_DRAWABLE_INSTANCE);
+ boolean isBitmap = HprofBitmapProvider.canGetBitmapFromInstance(bitmapDrawable);
+ assert isBitmap;
+ }
+
+ public void testFailGetBitmapFromWrongObject() {
+ Instance bitmapDrawable = mAppHeap.getInstance(ACTIVITY_INSTANCE);
+ boolean isBitmap = HprofBitmapProvider.canGetBitmapFromInstance(bitmapDrawable);
+ assert !isBitmap;
+ }
+
+ public void testDecodeARGB888() throws Exception {
+ Instance argb888Instance = mAppHeap.getInstance(ARGB_8888_INSTANCE);
+ BufferedImage bitmap = BitmapDecoder.getBitmap(new HprofBitmapProvider(argb888Instance));
+ assert bitmap != null;
+ }
+
+ public void testDecodeMutableBitmapWithBigBuffer() throws Exception {
+ // 360x360 mutable bitmap with buffer length 640000
+ Instance argb888MutableBitmap = mAppHeap.getInstance(ARGB_8888_MUTABLE);
+ BufferedImage bitmap = BitmapDecoder
+ .getBitmap(new HprofBitmapProvider(argb888MutableBitmap));
+ assert bitmap != null;
+ }
+
+ public void testFailDecodeWrongInstance() throws Exception {
+ Instance argb565Bitmap = mAppHeap.getInstance(ACTIVITY_INSTANCE);
+ try {
+ BitmapDecoder.getBitmap(new HprofBitmapProvider(argb565Bitmap));
+ } catch (RuntimeException e) {
+ return;
+ }
+ fail("BitmapDecoder should've thrown an error when given the wrong instance.");
+ }
+}
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java
similarity index 100%
rename from base/perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java
rename to perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java
similarity index 100%
rename from base/perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java
rename to perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/TraceView.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/TraceView.java
similarity index 100%
rename from base/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/TraceView.java
rename to perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/TraceView.java
diff --git a/base/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractorTest.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractorTest.java
similarity index 100%
rename from base/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractorTest.java
rename to perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractorTest.java
diff --git a/base/perflib/src/test/resources/.gitignore b/perflib/src/test/resources/.gitignore
similarity index 100%
rename from base/perflib/src/test/resources/.gitignore
rename to perflib/src/test/resources/.gitignore
diff --git a/base/perflib/src/test/resources/README.txt b/perflib/src/test/resources/README.txt
similarity index 100%
rename from base/perflib/src/test/resources/README.txt
rename to perflib/src/test/resources/README.txt
diff --git a/base/perflib/src/test/resources/basic-api10.trace b/perflib/src/test/resources/basic-api10.trace
similarity index 100%
rename from base/perflib/src/test/resources/basic-api10.trace
rename to perflib/src/test/resources/basic-api10.trace
diff --git a/base/perflib/src/test/resources/basic.android-hprof b/perflib/src/test/resources/basic.android-hprof
similarity index 100%
rename from base/perflib/src/test/resources/basic.android-hprof
rename to perflib/src/test/resources/basic.android-hprof
diff --git a/base/perflib/src/test/resources/basic.trace b/perflib/src/test/resources/basic.trace
similarity index 100%
rename from base/perflib/src/test/resources/basic.trace
rename to perflib/src/test/resources/basic.trace
diff --git a/perflib/src/test/resources/bitmap_test.android-hprof b/perflib/src/test/resources/bitmap_test.android-hprof
new file mode 100644
index 0000000..7850b82
Binary files /dev/null and b/perflib/src/test/resources/bitmap_test.android-hprof differ
diff --git a/base/perflib/src/test/resources/dialer.android-hprof b/perflib/src/test/resources/dialer.android-hprof
similarity index 100%
rename from base/perflib/src/test/resources/dialer.android-hprof
rename to perflib/src/test/resources/dialer.android-hprof
diff --git a/base/perflib/src/test/resources/exception.trace b/perflib/src/test/resources/exception.trace
similarity index 100%
rename from base/perflib/src/test/resources/exception.trace
rename to perflib/src/test/resources/exception.trace
diff --git a/base/perflib/src/test/resources/header.trace b/perflib/src/test/resources/header.trace
similarity index 100%
rename from base/perflib/src/test/resources/header.trace
rename to perflib/src/test/resources/header.trace
diff --git a/base/perflib/src/test/resources/mismatched.trace b/perflib/src/test/resources/mismatched.trace
similarity index 100%
rename from base/perflib/src/test/resources/mismatched.trace
rename to perflib/src/test/resources/mismatched.trace
diff --git a/perflib/src/test/resources/native_allocation.android-hprof b/perflib/src/test/resources/native_allocation.android-hprof
new file mode 100644
index 0000000..77da467
Binary files /dev/null and b/perflib/src/test/resources/native_allocation.android-hprof differ
diff --git a/base/rule-api/NOTICE b/repository/NOTICE
similarity index 100%
rename from base/rule-api/NOTICE
rename to repository/NOTICE
diff --git a/repository/build.gradle b/repository/build.gradle
new file mode 100644
index 0000000..ded5481
--- /dev/null
+++ b/repository/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'java'
+apply plugin: 'jacoco'
+apply plugin: 'sdk-java-lib'
+
+group = 'com.android.tools'
+archivesBaseName = 'repository'
+version = rootProject.ext.baseVersion
+
+dependencies {
+ compile project(':base:common')
+ compile 'org.apache.commons:commons-compress:1.8.1'
+
+ testCompile 'junit:junit:4.12'
+}
+
+test {
+ testLogging {
+ showStandardStreams = true
+ showStackTraces = true
+ exceptionFormat = "full"
+ }
+}
+
+sourceSets {
+ main.resources.srcDir 'src/main/java'
+ test.resources.srcDir 'src/test/java'
+}
+
+project.ext.pomName = 'Android Tools repository'
+project.ext.pomDesc = 'A library for downloading and managing package repositories'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
+
diff --git a/repository/repository.iml b/repository/repository.iml
new file mode 100644
index 0000000..a838ac9
--- /dev/null
+++ b/repository/repository.iml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="android-annotations" />
+ <orderEntry type="library" name="guava-tools" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
+ <orderEntry type="library" name="Ant" level="project" />
+ <orderEntry type="library" name="commons-compress" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/Revision.java b/repository/src/main/java/com/android/repository/Revision.java
new file mode 100644
index 0000000..f8b191d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/Revision.java
@@ -0,0 +1,455 @@
+/*
+ * 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.repository;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link Revision} which distinguishes between x and x.0, x.0.0, x.y.0, etc; it basically
+ * keeps track of the precision of the revision string.
+ * <p>
+ * This is vital when referencing Gradle artifact numbers,
+ * since versions x.y.0 and version x.y are not the same.
+ */
+public class Revision implements Comparable<Revision> {
+ public static final int MISSING_MAJOR_REV = 0;
+ public static final int IMPLICIT_MINOR_REV = 0;
+ public static final int IMPLICIT_MICRO_REV = 0;
+ public static final int NOT_A_PREVIEW = 0;
+
+ public static final Revision NOT_SPECIFIED = new Revision(MISSING_MAJOR_REV);
+
+ public enum Precision {
+
+ /** Only major revision specified: 1 term */
+ MAJOR(1),
+
+ /** Only major and minor revisions specified: 2 terms (x.y) */
+ MINOR(2),
+
+ /** Major, minor and micro revisions specified: 3 terms (x.y.z) */
+ MICRO(3),
+
+ /** Major, minor, micro and preview revisions specified: 4 terms (x.y.z-rcN) */
+ PREVIEW(4);
+
+ private final int mTermCount;
+ Precision(int termCount) {
+ mTermCount = termCount;
+ }
+
+ int getTermCount() {
+ return mTermCount;
+ }
+ }
+ private static final Pattern FULL_REVISION_PATTERN =
+ // 1=major 2=minor 3=micro 4=separator 5=previewType 6=preview
+ Pattern.compile("\\s*([0-9]+)(?:\\.([0-9]+)(?:\\.([0-9]+))?)?([\\s-]*)?(?:(rc|alpha|beta)([0-9]+))?\\s*");
+
+ protected static final String DEFAULT_SEPARATOR = " ";
+
+ private final int mMajor;
+ private final int mMinor;
+ private final int mMicro;
+ private final int mPreview;
+ private final Precision mPrecision;
+ private final String mPreviewSeparator;
+
+ /**
+ * Parses a string of format "major.minor.micro rcPreview" and returns a new {@link Revision}
+ * for it.
+ *
+ * All the fields except major are optional. <p/>
+ *
+ * @param revisionString A non-null revisionString to parse.
+ * @param minimumPrecision Create a {@code Revision} with at least the given precision,
+ * regardless of how precise the {@code revisionString} is.
+ * @return A new non-null {@link Revision}.
+ * @throws NumberFormatException if the parsing failed.
+ */
+ @NonNull
+ public static Revision parseRevision(@NonNull String revisionString,
+ @NonNull Precision minimumPrecision)
+ throws NumberFormatException {
+ Throwable cause = null;
+ try {
+ Matcher m = FULL_REVISION_PATTERN.matcher(revisionString);
+ if (m.matches()) {
+ int major = Integer.parseInt(m.group(1));
+
+ int minor = IMPLICIT_MINOR_REV;
+ int micro = IMPLICIT_MICRO_REV;
+ int preview = NOT_A_PREVIEW;
+ Precision precision = Precision.MAJOR;
+ String previewSeparator = " ";
+
+ String s = m.group(2);
+ if (s != null) {
+ minor = Integer.parseInt(s);
+ precision = Precision.MINOR;
+ }
+
+ s = m.group(3);
+ if (s != null) {
+ micro = Integer.parseInt(s);
+ precision = Precision.MICRO;
+ }
+
+ s = m.group(6);
+ if (s != null) {
+ preview = Integer.parseInt(s);
+ previewSeparator = m.group(4);
+ precision = Precision.PREVIEW;
+ }
+
+ if (minimumPrecision.compareTo(precision) >= 0) {
+ precision = minimumPrecision;
+ }
+
+ return new Revision(major, minor, micro, preview, precision,
+ previewSeparator);
+ }
+ } catch (Throwable t) {
+ cause = t;
+ }
+
+ NumberFormatException n = new NumberFormatException(
+ "Invalid revision: " + revisionString);
+ if (cause != null) {
+ n.initCause(cause);
+ }
+ throw n;
+ }
+
+ /**
+ * Parses a string of format "major.minor.micro rcPreview" and returns a new {@code Revision}
+ * for it.
+ *
+ * All the fields except major are optional. <p/>
+ *
+ * @param revisionString A non-null revisionString to parse.
+ * @return A new non-null {@link Revision}, with precision depending on the precision of {@code
+ * revisionString}.
+ * @throws NumberFormatException if the parsing failed.
+ */
+ @NonNull
+ public static Revision parseRevision(@NonNull String revisionString)
+ throws NumberFormatException {
+ return parseRevision(revisionString, Precision.MAJOR);
+ }
+
+ /**
+ * Creates a new {@code Revision} with the specified major revision and no other revision
+ * components.
+ */
+ public Revision(int major) {
+ this(major, IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV, NOT_A_PREVIEW, Precision.MAJOR,
+ DEFAULT_SEPARATOR);
+ }
+
+ /**
+ * Creates a new {@code Revision} with the specified major and minor revision components and no
+ * others.
+ */
+ public Revision(int major, int minor) {
+ this(major, minor, IMPLICIT_MICRO_REV, NOT_A_PREVIEW, Precision.MINOR, DEFAULT_SEPARATOR);
+ }
+
+ /**
+ * Creates a copy of the specified {@code Revision}.
+ */
+ public Revision(@NonNull Revision revision) {
+ this(revision.getMajor(), revision.getMinor(), revision.getMicro(), revision.getPreview(),
+ revision.mPrecision, revision.getSeparator());
+ }
+
+ /**
+ * Creates a new {@code Revision} with the specified major, minor, and micro revision components
+ * and no preview component.
+ */
+ public Revision(int major, int minor, int micro) {
+ this(major, minor, micro, NOT_A_PREVIEW, Precision.MICRO, DEFAULT_SEPARATOR);
+ }
+
+ /**
+ * Creates a new {@code Revision} with the specified components.
+ */
+ public Revision(int major, int minor, int micro, int preview) {
+ this(major, minor, micro, preview, Precision.PREVIEW, DEFAULT_SEPARATOR);
+ }
+
+ Revision(int major, int minor, int micro, int preview, @NonNull Precision precision,
+ @NonNull String separator) {
+ mMajor = major;
+ mMinor = minor;
+ mMicro = micro;
+ mPreview = preview;
+ mPreviewSeparator = separator;
+ mPrecision = precision;
+ }
+
+ /**
+ * Creates a new {@code Revision} with the specified components. The precision will be exactly
+ * sufficient to include all non-null components.
+ */
+ public Revision(int major, @Nullable Integer minor, @Nullable Integer micro,
+ @Nullable Integer preview) {
+ this(major, minor == null ? IMPLICIT_MINOR_REV : minor,
+ micro == null ? IMPLICIT_MICRO_REV : micro,
+ preview == null ? NOT_A_PREVIEW : preview,
+ preview != null ? Precision.PREVIEW : micro != null ? Precision.MICRO
+ : minor != null ? Precision.MINOR : Precision.MAJOR, DEFAULT_SEPARATOR);
+ }
+
+ /**
+ * Returns the version in a fixed format major.minor.micro with an optional "rc preview#". For
+ * example it would return "18.0.0", "18.1.0" or "18.1.2 rc5".
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getMajor());
+
+ if (mPrecision.compareTo(Precision.MINOR) >= 0) {
+ sb.append('.').append(getMinor());
+ if (mPrecision.compareTo(Precision.MICRO) >= 0) {
+ sb.append('.').append(getMicro());
+ if (mPrecision.compareTo(Precision.PREVIEW) >= 0 && isPreview()) {
+ sb.append(getSeparator()).append("rc").append(getPreview());
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns an {@code int[]} containing the Major, Minor, and Micro (and optionally Preview)
+ * components (if specified) of this revision
+ *
+ * @param includePreview If false, the preview component of this revision will be ignored.
+ * @return An array exactly long enough to include the components specified in this revision.
+ * For example, if only Major and Minor revisions are specified the array will be of length 2.
+ * If a preview component is specified and {@code includePreview} is true, the result will
+ * always be of length 4.
+ */
+ @NonNull
+ public int[] toIntArray(boolean includePreview) {
+ int[] result;
+ if (mPrecision.compareTo(Precision.PREVIEW) >= 0) {
+ if (includePreview) {
+ result = new int[mPrecision.getTermCount()];
+ result[3] = getPreview();
+ } else {
+ result = new int[mPrecision.getTermCount() - 1];
+ }
+ } else {
+ result = new int[mPrecision.getTermCount()];
+ }
+ result[0] = getMajor();
+ if (mPrecision.compareTo(Precision.MINOR) >= 0) {
+ result[1] = getMinor();
+ if (mPrecision.compareTo(Precision.MICRO) >= 0) {
+ result[2] = getMicro();
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns {@code true} if this revision is equal, <b>including in precision</b> to {@code rhs}.
+ * That is, {@code (new Revision(20)).equals(new Revision(20, 0, 0)} will return {@code false}.
+ */
+ @Override
+ public boolean equals(@Nullable Object rhs) {
+ if (this == rhs) {
+ return true;
+ }
+ if (rhs == null) {
+ return false;
+ }
+ if (!(rhs instanceof Revision)) {
+ return false;
+ }
+ Revision other = (Revision) rhs;
+ if (mMajor != other.mMajor) {
+ return false;
+ }
+ if (mMinor != other.mMinor) {
+ return false;
+ }
+ if (mMicro != other.mMicro) {
+ return false;
+ }
+ if (mPreview != other.mPreview) {
+ return false;
+ }
+ return mPrecision == other.mPrecision;
+ }
+
+ public int getMajor() {
+ return mMajor;
+ }
+
+ public int getMinor() {
+ return mMinor;
+ }
+
+ public int getMicro() {
+ return mMicro;
+ }
+
+ @NonNull
+ protected String getSeparator() {
+ return mPreviewSeparator;
+ }
+
+ public boolean isPreview() {
+ return mPreview > NOT_A_PREVIEW;
+ }
+
+ public int getPreview() {
+ return mPreview;
+ }
+
+ /**
+ * Returns the version in a dynamic format "major.minor.micro rc#". This is similar to {@link
+ * #toString()} except it omits minor, micro or preview versions when they are zero. For example
+ * it would return "18 rc1" instead of "18.0.0 rc1", or "18.1 rc2" instead of "18.1.0 rc2".
+ */
+ @NonNull
+ public String toShortString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mMajor);
+ if (mMinor > 0 || mMicro > 0) {
+ sb.append('.').append(mMinor);
+ }
+ if (mMicro > 0) {
+ sb.append('.').append(mMicro);
+ }
+ if (mPreview != NOT_A_PREVIEW) {
+ sb.append(mPreviewSeparator).append("rc").append(mPreview);
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mMajor;
+ result = prime * result + mMinor;
+ result = prime * result + mMicro;
+ result = prime * result + mPreview;
+ result = prime * result + mPrecision.getTermCount();
+ return result;
+ }
+
+ /**
+ * Trivial comparison of a version, e.g 17.1.2 < 18.0.0.
+ *
+ * Note that preview/release candidate are released before their final version, so "18.0.0 rc1"
+ * comes below "18.0.0". The best way to think of it as if the lack of preview number was
+ * "+inf": "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0"
+ * and more than "18.1.2.4"
+ *
+ * @param rhs The right-hand side {@link Revision} to compare with.
+ * @return <0 if lhs < rhs; 0 if lhs==rhs; >0 if lhs > rhs.
+ */
+ @Override
+ public int compareTo(@NonNull Revision rhs) {
+ return compareTo(rhs, PreviewComparison.COMPARE_NUMBER);
+ }
+
+ /**
+ * Trivial comparison of a version, e.g 17.1.2 < 18.0.0.
+ *
+ * Note that preview/release candidate are released before their final version, so "18.0.0 rc1"
+ * comes below "18.0.0". The best way to think of it as if the lack of preview number was
+ * "+inf": "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0"
+ * and more than "18.1.2.4"
+ *
+ * @param rhs The right-hand side {@link Revision} to compare with.
+ * @param comparePreview How to compare the preview value.
+ * @return <0 if lhs < rhs; 0 if lhs==rhs; >0 if lhs > rhs.
+ */
+ public int compareTo(@NonNull Revision rhs, @NonNull PreviewComparison comparePreview) {
+ int delta = mMajor - rhs.mMajor;
+ if (delta != 0) {
+ return delta;
+ }
+
+ delta = mMinor - rhs.mMinor;
+ if (delta != 0) {
+ return delta;
+ }
+
+ delta = mMicro - rhs.mMicro;
+ if (delta != 0) {
+ return delta;
+ }
+
+ int p1, p2;
+ switch (comparePreview) {
+ case IGNORE:
+ // Nothing to compare.
+ break;
+
+ case COMPARE_NUMBER:
+ p1 = mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : mPreview;
+ p2 = rhs.mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : rhs.mPreview;
+ delta = p1 - p2;
+ break;
+
+ case COMPARE_TYPE:
+ p1 = mPreview == NOT_A_PREVIEW ? 1 : 0;
+ p2 = rhs.mPreview == NOT_A_PREVIEW ? 1 : 0;
+ delta = p1 - p2;
+ break;
+ }
+ return delta;
+ }
+
+ /**
+ * Indicates how to compare the preview field in {@link Revision#compareTo(Revision,
+ * PreviewComparison)}
+ */
+ public enum PreviewComparison {
+ /**
+ * Both revisions must have exactly the same preview number.
+ */
+ COMPARE_NUMBER,
+ /**
+ * Both revisions must have the same preview type (both must be previews or both must not be
+ * previews, but the actual number is irrelevant.) This is the most typical choice used to
+ * find updates of the same type.
+ */
+ COMPARE_TYPE,
+ /**
+ * The preview field is ignored and not used in the comparison.
+ */
+ IGNORE
+ }
+
+
+}
diff --git a/repository/src/main/java/com/android/repository/api/Channel.java b/repository/src/main/java/com/android/repository/api/Channel.java
new file mode 100644
index 0000000..cb0ca72
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/Channel.java
@@ -0,0 +1,93 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.impl.meta.CommonFactory;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * An update channel, e.g. Stable or Beta. Channels are ordered from more to less stable.
+ */
+ at XmlTransient
+public abstract class Channel implements Comparable<Channel> {
+
+ /**
+ * The default channel is the most stable.
+ */
+ public static final Channel DEFAULT = create(0);
+
+ /**
+ * Create a new {@code Channel} with the specified ID.
+ *
+ * @param id The id of the channel. If this channel will ever be marshalled the
+ * value must be between 0 and 9 or the xml will fail validation.
+ */
+ @NonNull
+ public static Channel create(int id) {
+ return ((CommonFactory) RepoManager.getCommonModule()
+ .createLatestFactory()).createChannelType(id);
+ }
+
+ /**
+ * Gets the (possibly null) display name for this channel.
+ */
+ @Nullable
+ protected abstract String getValue();
+
+ /**
+ * Sets the displayName for this channel.
+ */
+ public abstract void setValue(@Nullable String displayName);
+
+ /**
+ * Gets the display name for this channel. If none is specified, the id (in the format
+ * {@code channel-N}) is returned.
+ */
+ @NonNull
+ public String getDisplayName() {
+ return getValue() == null ? getId() : getValue();
+ }
+
+ /**
+ * Gets the string ID for this channel, in the format {@code channel-N}.
+ */
+ @NonNull
+ public abstract String getId();
+
+ /**
+ * Sets the string ID for this channel. Must be in the form "channel-N".
+ */
+ public abstract void setId(@NonNull String id);
+
+ @Override
+ public int compareTo(Channel o) {
+ return getId().compareTo(o.getId());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Channel && getId().equals(((Channel) obj).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/ConsoleProgressIndicator.java b/repository/src/main/java/com/android/repository/api/ConsoleProgressIndicator.java
new file mode 100644
index 0000000..7711318
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/ConsoleProgressIndicator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * A simple {@link ProgressIndicator} that prints log messages to {@code stdout} and {@code stderr}.
+ */
+public class ConsoleProgressIndicator extends ProgressIndicatorAdapter {
+
+ @Override
+ public void logWarning(@NonNull String s, @Nullable Throwable e) {
+ System.err.println("Warning: " + s);
+ if (e != null) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void logError(@NonNull String s, @Nullable Throwable e) {
+ System.err.println("Error: " + s);
+ if (e != null) {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void logInfo(@NonNull String s) {
+ System.out.println("Info: " + s);
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/ConstantSourceProvider.java b/repository/src/main/java/com/android/repository/api/ConstantSourceProvider.java
new file mode 100644
index 0000000..996894a
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/ConstantSourceProvider.java
@@ -0,0 +1,68 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A {@link RepositorySourceProvider} that provides a single source allowing a single set of schema
+ * modules.
+ */
+public class ConstantSourceProvider implements RepositorySourceProvider {
+
+ private final RepositorySource mSource;
+
+ public ConstantSourceProvider(@NonNull String url, @NonNull String uiName,
+ @NonNull Collection<SchemaModule> permittedSchemaModules) {
+ // TODO: persist enabled state (this isn't done currently either). Probably it will be
+ // in the file that stores locally-added sites.
+ mSource = new SimpleRepositorySource(url, uiName, true, permittedSchemaModules, this);
+ }
+
+ @Override
+ @NonNull
+ public List<RepositorySource> getSources(@Nullable Downloader downloader,
+ @Nullable SettingsController settings,
+ @Nullable ProgressIndicator indicator, boolean forceRefresh) {
+ return ImmutableList.of(mSource);
+ }
+
+ @Override
+ public boolean addSource(@NonNull RepositorySource source) {
+ return false;
+ }
+
+ @Override
+ public boolean isModifiable() {
+ return false;
+ }
+
+ @Override
+ public void save(@NonNull ProgressIndicator progress) {
+ // nothing since it's not modifiable
+ }
+
+ @Override
+ public boolean removeSource(@NonNull RepositorySource source) {
+ return false;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/Dependency.java b/repository/src/main/java/com/android/repository/api/Dependency.java
new file mode 100644
index 0000000..8b2593d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/Dependency.java
@@ -0,0 +1,64 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.RevisionType;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * A dependency of one package on another. Concrete instances should be created by
+ * {@link CommonFactory}.
+ */
+ at XmlTransient
+public abstract class Dependency {
+
+ /**
+ * @return The minimum revision of the other package required.
+ */
+ @XmlTransient
+ @Nullable
+ public RevisionType getMinRevision() {
+ // Stub.
+ return null;
+ }
+
+ /**
+ * @param revision The minimum revision of the other package required.
+ */
+ public void setMinRevision(@Nullable RevisionType revision) {
+ // Stub
+ }
+
+ /**
+ * @return The path uniquely identifying the other package.
+ */
+ @XmlTransient
+ @NonNull
+ public abstract String getPath();
+
+ /**
+ * @param id The path uniquely identifying the other package.
+ */
+ public void setPath(@NonNull String id) {
+ // Stub
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/api/Downloader.java b/repository/src/main/java/com/android/repository/api/Downloader.java
new file mode 100644
index 0000000..61abb44
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/Downloader.java
@@ -0,0 +1,57 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Implementations provide a general mechanism for downloading files.
+ */
+public interface Downloader {
+
+ /**
+ * Gets a stream associated with the content at the given URL. This could be achieved by
+ * downloading the file completely, streaming it directly, or some combination.
+ *
+ * @param url The URL to fetch.
+ * @param settings Settings (e.g. proxy configuration) for the connection.
+ * @param indicator Facility for showing download progress and logging.
+ * @return An InputStream corresponding to the specified content, or {@code null} if the
+ * download is cancelled.
+ */
+ @Nullable
+ InputStream downloadAndStream(@NonNull URL url, @Nullable SettingsController settings,
+ @NonNull ProgressIndicator indicator) throws IOException;
+
+ /**
+ * Downloads the content at the given URL to a temporary file and returns a handle to that file.
+ *
+ * @param url The URL to fetch.
+ * @param settings Settings (e.g. proxy configuration) for the connection.
+ * @param indicator Facility for showing download progress and logging.
+ * @return The temporary file, or {@code null} if the download is cancelled.
+ */
+ @Nullable
+ File downloadFully(@NonNull URL url, @Nullable SettingsController settings,
+ @NonNull ProgressIndicator indicator) throws IOException;
+}
diff --git a/repository/src/main/java/com/android/repository/api/FallbackLocalRepoLoader.java b/repository/src/main/java/com/android/repository/api/FallbackLocalRepoLoader.java
new file mode 100644
index 0000000..e4192f0
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/FallbackLocalRepoLoader.java
@@ -0,0 +1,47 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.impl.manager.LocalRepoLoader;
+
+import java.io.File;
+
+/**
+ * An implementation of a local repository parser to use to try to identify a package if the normal
+ * mechanism doesn't. If one is provided to RepoManager,
+ * {@link #parseLegacyLocalPackage(File, ProgressIndicator)} will be run on every repository
+ * directory that doesn't contain a package recognized by {@link LocalRepoLoader}
+ * (or a child of such a directory). {@link LocalRepoLoader} will then use the {@link LocalPackage}
+ * generated by this to write out a package.xml in the normal format.
+ */
+public interface FallbackLocalRepoLoader {
+
+ /**
+ * Try to find a package at the given location. If found, return a {@link LocalPackage} with the
+ * package's information. Otherwise return {@code null}.
+ */
+ @Nullable
+ LocalPackage parseLegacyLocalPackage(@NonNull File f, @NonNull ProgressIndicator progress);
+
+ /**
+ * Refreshes the loader's internal state if necessary. This should probably be done (by the repo
+ * manager) whenever a new local repo load is started.
+ */
+ void refresh();
+}
diff --git a/repository/src/main/java/com/android/repository/api/FallbackRemoteRepoLoader.java b/repository/src/main/java/com/android/repository/api/FallbackRemoteRepoLoader.java
new file mode 100644
index 0000000..33cd833
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/FallbackRemoteRepoLoader.java
@@ -0,0 +1,41 @@
+/*
+ * 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.repository.api;
+
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * An implementation of a remote repo parser to use to try to parse and xml file if the normal
+ * mechanism doesn't. If one is provided to RepoManager,
+ * {@link #parseLegacyXml(RepositorySource, ProgressIndicator)} will be run on every xml file
+ * retrieved from a {@link RepositorySource} that isn't recognized by the normal mechanism.
+ */
+public interface FallbackRemoteRepoLoader {
+
+ /**
+ * Parses an xml file into {@link RemotePackage}s.
+ * @param xml The {@link RepositorySource} to read from.
+ * @return The parsed packages, null if none are found (due to an error).
+ */
+ @Nullable
+ Collection<RemotePackage> parseLegacyXml(@NonNull RepositorySource xml,
+ @NonNull ProgressIndicator progress);
+}
diff --git a/repository/src/main/java/com/android/repository/api/License.java b/repository/src/main/java/com/android/repository/api/License.java
new file mode 100644
index 0000000..27644dd
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/License.java
@@ -0,0 +1,183 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * License text, with an optional license XML reference.
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+ at XmlTransient
+public abstract class License {
+
+ /**
+ * The name of the directory used to store tokens indicating approval of licenses.
+ */
+ private static final String LICENSE_DIR = "licenses";
+
+ /**
+ * Gets the ID of this license, used to refer to it from within a {@link RepoPackage}.
+ */
+ @NonNull
+ public abstract String getId();
+
+ /**
+ * Sets the ID of this license, used to refer to it from within a {@link RepoPackage}.
+ */
+ public abstract void setId(@NonNull String id);
+
+ /**
+ * Gets the text of the license.
+ */
+ @NonNull
+ public abstract String getValue();
+
+ /**
+ * Sets the text of the license.
+ */
+ public abstract void setValue(String value);
+
+ /**
+ * Gets the type of the license. Currently only {@code "text"} is valid.
+ */
+ @Nullable
+ public String getType() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * Sets the type of the license. Currently only {@code "text"} is valid.
+ */
+ public void setType(@Nullable String type) {
+ // Stub
+ }
+
+ /**
+ * Returns the hash of the license text, used for persisting acceptance. Never null.
+ */
+ @NonNull
+ public String getLicenseHash() {
+ return Hashing.sha1().hashBytes(getValue().getBytes()).toString();
+ }
+
+ /**
+ * Returns a string representation of the license, useful for debugging.
+ * This is not designed to be shown to the user.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<License ref:")
+ .append(getId())
+ .append(", text:")
+ .append(getValue())
+ .append(">");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((getValue() == null) ? 0 : getValue().hashCode());
+ result = prime * result
+ + ((getId() == null) ? 0 : getId().hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof License)) {
+ return false;
+ }
+ License other = (License) obj;
+ return Objects.equal(getValue(), other.getValue()) &&
+ Objects.equal(getId(), other.getId());
+ }
+
+ /**
+ * Checks whether this license has previously been accepted.
+ * @param repositoryRoot The root directory of the repository
+ * @return true if this license has already been accepted
+ */
+ public boolean checkAccepted(@Nullable File repositoryRoot) {
+ if (repositoryRoot == null) {
+ return false;
+ }
+ File licenseDir = new File(repositoryRoot, LICENSE_DIR);
+ File licenseFile = new File(licenseDir, getId() == null ? getLicenseHash() : getId());
+ if (!licenseFile.exists()) {
+ return false;
+ }
+ try {
+ String hash = Files.readFirstLine(licenseFile, Charsets.UTF_8);
+ return hash.equals(getLicenseHash());
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Marks this license as accepted.
+ *
+ * @param repositoryRoot The root directory of the repository
+ * @return true if the acceptance was persisted successfully.
+ */
+ public boolean setAccepted(@Nullable File repositoryRoot) {
+ if (repositoryRoot == null) {
+ return false;
+ }
+ if (checkAccepted(repositoryRoot)) {
+ return true;
+ }
+ File licenseDir = new File(repositoryRoot, LICENSE_DIR);
+ if (licenseDir.exists() && !licenseDir.isDirectory()) {
+ return false;
+ }
+ if (!licenseDir.exists()) {
+ licenseDir.mkdir();
+ }
+ File licenseFile = new File(licenseDir, getId() == null ? getLicenseHash(): getId());
+ try {
+ Files.write(getLicenseHash(), licenseFile, Charsets.UTF_8);
+ }
+ catch (IOException e) {
+ return false;
+ }
+ return true;
+ }
+}
+
diff --git a/repository/src/main/java/com/android/repository/api/LocalPackage.java b/repository/src/main/java/com/android/repository/api/LocalPackage.java
new file mode 100644
index 0000000..59f3f4f
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/LocalPackage.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.repository.api;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * A locally-installed package. In addition to what's provided by {@link RepoPackage},
+ * {@code LocalPackage} has an actual location on disk.
+ */
+public interface LocalPackage extends RepoPackage {
+
+ /**
+ * Gets the installed location of this package.
+ */
+ @NonNull
+ File getLocation();
+
+ /**
+ * Specifies the path at which this package is installed. This should be called
+ * directly after the package is created.
+ */
+ void setInstalledPath(@NonNull File root);
+}
diff --git a/repository/src/main/java/com/android/repository/api/ProgressIndicator.java b/repository/src/main/java/com/android/repository/api/ProgressIndicator.java
new file mode 100644
index 0000000..c7711c6
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/ProgressIndicator.java
@@ -0,0 +1,101 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * A progress indicator and logger. Progress is from 0-1, or indeterminate.
+ */
+public interface ProgressIndicator {
+
+ /**
+ * Sets the main text shown in the progress indicator.
+ */
+ void setText(@Nullable String s);
+
+ /**
+ * @return True if the user has canceled this operation.
+ */
+ boolean isCanceled();
+
+ /**
+ * Try to cancel this operation.
+ */
+ void cancel();
+
+ /**
+ * Sets whether the user should be able to cancel this operation.
+ */
+ void setCancellable(boolean cancellable);
+
+ /**
+ * @return true if the user should be able to cancel this operation.
+ */
+ boolean isCancellable();
+
+ /**
+ * Sets whether this progress indicator should show indeterminate progress.
+ */
+ void setIndeterminate(boolean indeterminate);
+
+ /**
+ * @return true if this progress indicator is set to show indeterminate progress.
+ */
+ boolean isIndeterminate();
+
+ /**
+ * Sets how much progress should be shown on the progress bar, between 0 and 1.
+ */
+ void setFraction(double v);
+
+ /**
+ * @return The current amount of progress shown on the progress bar, between 0 and 1.
+ */
+ double getFraction();
+
+ /**
+ * Sets the secondary text on the progress indicator.
+ */
+ void setSecondaryText(@Nullable String s);
+
+ /**
+ * Logs a warning.
+ */
+ void logWarning(@NonNull String s);
+ /**
+ * Logs a warning, including a stacktrace.
+ */
+ void logWarning(@NonNull String s, @Nullable Throwable e);
+
+ /**
+ * Logs an error.
+ */
+ void logError(@NonNull String s);
+
+ /**
+ * Logs an error, including a stacktrace.
+ */
+ void logError(@NonNull String s, @Nullable Throwable e);
+
+ /**
+ * Logs an info message.
+ */
+ void logInfo(@NonNull String s);
+
+}
diff --git a/repository/src/main/java/com/android/repository/api/ProgressIndicatorAdapter.java b/repository/src/main/java/com/android/repository/api/ProgressIndicatorAdapter.java
new file mode 100644
index 0000000..f68a64a
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/ProgressIndicatorAdapter.java
@@ -0,0 +1,83 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * A trivial implementation of ProgressIndicator that does nothing.
+ */
+public abstract class ProgressIndicatorAdapter implements ProgressIndicator {
+
+ @Override
+ public void setText(@Nullable String s) {}
+
+ @Override
+ public boolean isCanceled() {
+ return false;
+ }
+
+ @Override
+ public void cancel() {}
+
+ @Override
+ public void setCancellable(boolean cancellable) {}
+
+ @Override
+ public boolean isCancellable() {
+ return false;
+ }
+
+ @Override
+ public void setIndeterminate(boolean indeterminate) {}
+
+ @Override
+ public boolean isIndeterminate() {
+ return false;
+ }
+
+ @Override
+ public void setFraction(double v) {}
+
+ @Override
+ public double getFraction() {
+ return 0;
+ }
+
+ @Override
+ public void setSecondaryText(@Nullable String s) {}
+
+ @Override
+ public void logWarning(@NonNull String s) {
+ logWarning(s, null);
+ }
+
+ @Override
+ public void logWarning(@NonNull String s, @Nullable Throwable e) {}
+
+ @Override
+ public void logError(@NonNull String s) {
+ logError(s, null);
+ }
+
+ @Override
+ public void logError(@NonNull String s, @Nullable Throwable e) {}
+
+ @Override
+ public void logInfo(@NonNull String s) {}
+}
diff --git a/repository/src/main/java/com/android/repository/api/ProgressRunner.java b/repository/src/main/java/com/android/repository/api/ProgressRunner.java
new file mode 100644
index 0000000..3ea6ba1
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/ProgressRunner.java
@@ -0,0 +1,48 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+
+/**
+ * An interface for facilities that can run tasks, synchronously or asynchronously, and allow them
+ * to show their progress using a {@link ProgressIndicator}.
+ */
+public interface ProgressRunner {
+
+ /**
+ * Runs a task synchronously.
+ */
+ void runAsyncWithProgress(@NonNull ProgressRunnable r);
+
+ /**
+ * Runs a task asynchronously.
+ */
+ void runSyncWithProgress(@NonNull ProgressRunnable r);
+
+ /**
+ * Runs a {@link Runnable} synchronously. Useful if e.g. it must be run on a certain thread.
+ */
+ void runSyncWithoutProgress(@NonNull Runnable r);
+
+ /**
+ * Interface for tasks that can show their progress using a {@link ProgressIndicator}.
+ */
+ interface ProgressRunnable {
+ void run(@NonNull ProgressIndicator indicator, @NonNull ProgressRunner runner);
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/RemoteListSourceProvider.java b/repository/src/main/java/com/android/repository/api/RemoteListSourceProvider.java
new file mode 100644
index 0000000..a8b95f7
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/RemoteListSourceProvider.java
@@ -0,0 +1,55 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.impl.sources.RemoteListSourceProviderImpl;
+
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * An {@link RepositorySourceProvider} that retrieves {@link RepositorySource}s from a remote
+ * location.
+ */
+public abstract class RemoteListSourceProvider implements RepositorySourceProvider {
+
+ /**
+ * Creates a new provider.
+ *
+ * @param url The URL to download from.
+ * @param sourceListModule Extension to the common source list schema, if any, used to
+ * parse the downloaded xml.
+ * @param permittedSchemaModules The {@link SchemaModule}s that are allowed to be used by the
+ * {@link RepositorySource}s created by this provider, depending
+ * on the actual type of site.
+ * @return The created provider.
+ * @throws URISyntaxException If {@code url} is invalid.
+ */
+ @NonNull
+ public static RemoteListSourceProvider create(@NonNull String url,
+ @Nullable SchemaModule sourceListModule,
+ @NonNull Map<Class<? extends RepositorySource>,
+ Collection<SchemaModule>> permittedSchemaModules)
+ throws URISyntaxException {
+ return new RemoteListSourceProviderImpl(url, sourceListModule, permittedSchemaModules);
+ }
+
+ public interface GenericSite extends RepositorySource {}
+}
diff --git a/repository/src/main/java/com/android/repository/api/RemotePackage.java b/repository/src/main/java/com/android/repository/api/RemotePackage.java
new file mode 100644
index 0000000..0344ea8
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/RemotePackage.java
@@ -0,0 +1,54 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.impl.meta.Archive;
+import com.android.repository.impl.meta.RemotePackageImpl;
+
+/**
+ * An package available for download. In addition to what's provided by {@link RepoPackage},
+ * {@code RemotePackage} has an associated {@link RepositorySource} and archives.
+ */
+public interface RemotePackage extends RepoPackage {
+
+ /**
+ * @return The {@code RepositorySource} from which we got this package.
+ */
+ @NonNull
+ RepositorySource getSource();
+
+ /**
+ * @param source The {@code RepositorySource} from which we got this package.
+ */
+ void setSource(@NonNull RepositorySource source);
+
+ /**
+ * @return The archive in this package compatible with the current hardware, OS, and JDK,
+ * or {@code null} if there is none.
+ */
+ @Nullable
+ Archive getArchive();
+
+ /**
+ * Gets the update channel (e.g. beta, stable) for this package. Channels are sorted in
+ * lexicographic order, with the first (and default) being the most stable.
+ */
+ @NonNull
+ Channel getChannel();
+}
diff --git a/repository/src/main/java/com/android/repository/api/RemoteSource.java b/repository/src/main/java/com/android/repository/api/RemoteSource.java
new file mode 100644
index 0000000..b150f5d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/RemoteSource.java
@@ -0,0 +1,127 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * A {@code RepositorySource} that was created by a {@link RemoteListSourceProvider} and superclass
+ * for xjc-generated classes used to parse the sites list xml. Generated classes provide the url and
+ * UI name; the permitted extensions must be set by the provider. If you implement a new site type,
+ * it should extend siteType in repo-sites-common-N.xsd, and thus transitively extend this class.
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+ at XmlTransient
+public abstract class RemoteSource implements RepositorySource {
+
+ private Collection<SchemaModule> mPermittedSchemaModules = null;
+
+ // TODO: refactor into RepositorySource, along with the fetching logic that sets it.
+ private String mFetchError;
+
+ @XmlTransient
+ private RepositorySourceProvider mProvider;
+
+ /**
+ * Sets the list of modules allowed to be used when parsing XML fetched from this source.
+ */
+ public void setPermittedSchemaModules(@NonNull Collection<SchemaModule> modules) {
+ mPermittedSchemaModules = modules;
+ }
+
+ /**
+ * @return The list of schema modules allowed to be used when parsing XML fetched from this
+ * source.
+ */
+ @Override
+ @NonNull
+ public Collection<SchemaModule> getPermittedModules() {
+ if (mPermittedSchemaModules == null) {
+ throw new UnsupportedOperationException(
+ "Tried to fetch permitted modules before they were initialized");
+ }
+ return mPermittedSchemaModules;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Currently not implemented.
+ */
+ @Override
+ public boolean isEnabled() {
+ // TODO (this isn't persistently implemented currently either).
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Currently not implemented.
+ */
+ @Override
+ public void setEnabled(boolean enabled) {
+ // TODO
+ }
+
+ @Override
+ @NonNull
+ public abstract String getUrl();
+
+ public void setUrl(@NonNull String url) {
+ // Stub
+ }
+
+ @Override
+ public void setFetchError(@Nullable String error) {
+ mFetchError = error;
+ }
+
+ @Override
+ @Nullable
+ public String getFetchError() {
+ return mFetchError;
+ }
+
+ @Override
+ @NonNull
+ public RepositorySourceProvider getProvider() {
+ return mProvider;
+ }
+
+ public void setProvider(@NonNull RepositorySourceProvider provider) {
+ mProvider = provider;
+ }
+
+ @Override
+ @Nullable
+ public String getDisplayName() {
+ // Stub. Implementation for compatibility with old versions.
+ return getName();
+ }
+
+ @Nullable
+ public String getName() {
+ return null;
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/api/RepoManager.java b/repository/src/main/java/com/android/repository/api/RepoManager.java
new file mode 100644
index 0000000..64afe2d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/RepoManager.java
@@ -0,0 +1,362 @@
+/*
+ * 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.repository.api;
+
+import static com.android.repository.impl.meta.TypeDetails.*;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.repository.impl.manager.RepoManagerImpl;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.ls.LSResourceResolver;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Primary interface for interacting with repository packages.
+ *
+ * To set up an {@code RepoManager}:
+ * <ul>
+ * <li>
+ * Register the {@link SchemaModule}s used to parse the package.xml files and
+ * remote repositories used by this repo using {@link
+ * #registerSchemaModule(SchemaModule)}
+ * </li>
+ * <li>
+ * Set the path where the repo is installed locally using {@link #setLocalPath(File)}.
+ * </li>
+ * <li>
+ * If your local repo might contain packages created by a previous system, set a
+ * {@link FallbackLocalRepoLoader} that can recognize and convert those packages using
+ * {@link #setFallbackLocalRepoLoader(FallbackLocalRepoLoader)}.
+ * </li>
+ * <li>
+ * Add {@link RepositorySourceProvider}s to provide URLs for remotely-available packages.
+ * </li>
+ * <li>
+ * If some sources might be in a format used by a previous system, set a {@link
+ * FallbackRemoteRepoLoader} that can read and convert them.
+ * </li>
+ * </ul>
+ * <p>
+ * To load the local and remote packages, use {@link #load(long, List, List, List, ProgressRunner,
+ * Downloader, SettingsController, boolean)}
+ * <br>
+ * TODO: it would be nice if this could be redesigned such that load didn't need to be called
+ * explicitly, or there was a better way to know if packages were or need to be loaded.
+ * <p>
+ * To use the loaded packages, get an {@link RepositoryPackages} object from {@link #getPackages()}.
+ */
+public abstract class RepoManager {
+ /**
+ * After loading the repository, this is the amount of time that must pass before we consider it
+ * to be stale and need to be reloaded.
+ */
+ public static final long DEFAULT_EXPIRATION_PERIOD_MS = TimeUnit.DAYS.toMillis(1);
+
+ /**
+ * Pattern for name of the xsd file used in {@link #sCommonModule}.
+ */
+ private static final String COMMON_XSD_PATTERN = "repo-common-%02d.xsd";
+
+ /**
+ * Pattern for fully-qualified name of the {@code ObjectFactory} used in {@link
+ * #sCommonModule}.
+ */
+ private static final String COMMON_OBJECT_FACTORY_PATTERN
+ = "com.android.repository.impl.generated.v%d.ObjectFactory";
+
+ /**
+ * Pattern for name of the xsd file used in {@link #sCommonModule}.
+ */
+ private static final String GENERIC_XSD_PATTERN = "generic-%02d.xsd";
+
+ /**
+ * Pattern for fully-qualified name of the {@code ObjectFactory} used in {@link
+ * #sCommonModule}.
+ */
+ private static final String GENERIC_OBJECT_FACTORY_PATTERN
+ = "com.android.repository.impl.generated.generic.v%d.ObjectFactory";
+
+
+ /**
+ * The base {@link SchemaModule} that is created by {@code RepoManagerImpl} itself.
+ */
+ private static SchemaModule sCommonModule;
+ static {
+ try {
+ sCommonModule = new SchemaModule(COMMON_OBJECT_FACTORY_PATTERN, COMMON_XSD_PATTERN,
+ RepoManager.class);
+ }
+ catch (Exception e) {
+ // This should never happen unless there's something wrong with the common repo schema.
+ assert false : "Failed to create SchemaModule: " + e;
+ }
+ }
+
+ /**
+ * The {@link SchemaModule} that contains an implementation of {@link GenericType}.
+ */
+ private static SchemaModule sGenericModule;
+ static {
+ try {
+ sGenericModule = new SchemaModule(GENERIC_OBJECT_FACTORY_PATTERN, GENERIC_XSD_PATTERN,
+ RepoManager.class);
+ }
+ catch (Exception e) {
+ // This should never happen unless there's something wrong with the generic repo schema.
+ assert false : "Failed to create SchemaModule: " + e;
+ }
+ }
+
+ /**
+ * @param fop The {@link FileOp} to use for local filesystem operations. Probably
+ * {@link FileOpUtils#create()} unless part of a unit test.
+ * @return A new {@code RepoManager}.
+ */
+ @NonNull
+ public static RepoManager create(@NonNull FileOp fop) {
+ return new RepoManagerImpl(fop);
+ }
+
+ /**
+ * Register an {@link SchemaModule} that can be used when parsing XML for this repo.
+ */
+ public abstract void registerSchemaModule(@NonNull SchemaModule module);
+
+ /**
+ * Gets the currently-registered {@link SchemaModule}s. This probably shouldn't be used except
+ * by code within the RepoManager or unit tests.
+ */
+ @NonNull
+ public abstract Set<SchemaModule> getSchemaModules();
+
+ /**
+ * Gets the core {@link SchemaModule} created by the RepoManager itself. Contains the base
+ * definition of repository, package, revision, etc.
+ */
+ @NonNull
+ public static SchemaModule getCommonModule() {
+ return sCommonModule;
+ }
+
+ /**
+ * Gets the {@link SchemaModule} created by the RepoManager that includes the trivial generic
+ * {@code typeDetails} type.
+ */
+ @NonNull
+ public static SchemaModule getGenericModule() {
+ return sGenericModule;
+ }
+
+ /**
+ * Sets the path to the local repository root.
+ */
+ public abstract void setLocalPath(@Nullable File path);
+
+ /**
+ * Gets the path to the local repository root. This probably shouldn't be needed except by the
+ * repository manager and unit tests.
+ */
+ @Nullable
+ public abstract File getLocalPath();
+
+ /**
+ * Sets the {@link FallbackLocalRepoLoader} to use when scanning the local repository for
+ * packages.
+ */
+ public abstract void setFallbackLocalRepoLoader(@Nullable FallbackLocalRepoLoader local);
+
+ /**
+ * Adds a {@link RepositorySourceProvider} from which to get {@link RepositorySource}s from
+ * which to download lists of available repository packages.
+ */
+ public abstract void registerSourceProvider(@NonNull RepositorySourceProvider provider);
+
+ /**
+ * Gets the currently registered {@link RepositorySourceProvider}s. Should only be needed for
+ * testing.
+ */
+ @NonNull
+ @VisibleForTesting
+ public abstract Set<RepositorySourceProvider> getSourceProviders();
+
+ /**
+ * Gets the actual {@link RepositorySource}s from the registered {@link
+ * RepositorySourceProvider}s.
+ *
+ * Probably should only be needed by a repository UI.
+ *
+ * @param downloader The {@link Downloader} to use for downloading source lists, if needed.
+ * @param settings The settings to use when downloading or reading source lists.
+ * @param progress A {@link ProgressIndicator} for source providers to use to show their
+ * progress and for logging.
+ * @param forceRefresh Individual {@link RepositorySourceProvider}s may cache their results. If
+ * {@code forceRefresh} is true, specifies that they should reload rather
+ * than returning cached results.
+ * @return The {@link RepositorySource}s obtained from the providers.
+ */
+ public abstract Set<RepositorySource> getSources(@Nullable Downloader downloader,
+ @Nullable SettingsController settings, @NonNull ProgressIndicator progress,
+ boolean forceRefresh);
+
+
+ /**
+ * Sets the {@link FallbackRemoteRepoLoader} to try when we encounter a remote xml file that the
+ * RepoManger can't read.
+ */
+ public abstract void setFallbackRemoteRepoLoader(@Nullable FallbackRemoteRepoLoader remote);
+
+ /**
+ * Load the local and remote repositories.
+ *
+ * In callbacks, be careful of invoking tasks synchronously on other threads (e.g. the swing ui
+ * thread), since they might also be used by the {@link ProgressRunner) passed in.
+ *
+ * @param cacheExpirationMs How long must have passed since the last load for us to reload.
+ * Specify {@code 0} to reload immediately.
+ * @param onLocalComplete When loading, the local repo load happens first, and should be
+ * relatively fast. When complete, the {@code onLocalComplete} {@link
+ * RepoLoadedCallback}s are run. Will be called with a {@link
+ * RepositoryPackages} that contains only the local packages.
+ * @param onSuccess Callbacks that are run when the entire load (local and remote) has
+ * completed successfully. Called with an {@link RepositoryPackages}
+ * containing both the local and remote packages.
+ * @param onError Callbacks that are run when there's an error at some point during
+ * the load.
+ * @param runner The {@link ProgressRunner} to use for any tasks started during the
+ * load, including running the callbacks.
+ * @param downloader The {@link Downloader} to use for downloading remote files,
+ * including any remote list of repo sources and the remote
+ * repositories themselves.
+ * @param settings The settings to use during the load, including for example proxy
+ * settings used when fetching remote files.
+ * @param sync If true, load synchronously. If false, load asynchronously (this
+ * method should return quickly, and the {@code onSuccess} callbacks
+ * can be used to process the completed results).
+ *
+ * TODO: throw exception if cancelled
+ *
+ * @return {@code true} if a load was performed. {@code false} if cached results were fresh
+ * enough.
+ */
+ public abstract boolean load(long cacheExpirationMs,
+ @Nullable List<RepoLoadedCallback> onLocalComplete,
+ @Nullable List<RepoLoadedCallback> onSuccess,
+ @Nullable List<Runnable> onError,
+ @NonNull ProgressRunner runner,
+ @Nullable Downloader downloader,
+ @Nullable SettingsController settings,
+ boolean sync);
+
+ /**
+ * Load the local and remote repositories synchronously.
+ *
+ * @param cacheExpirationMs How long must have passed since the last load for us to reload.
+ * Specify {@code 0} to reload immediately.
+ * @param progress The {@link ProgressIndicator} to use for showing progress and
+ * logging.
+ * @param downloader The {@link Downloader} to use for downloading remote files,
+ * including any remote list of repo sources and the remote
+ * repositories themselves.
+ * @param settings The settings to use during the load, including for example proxy
+ * settings used when fetching remote files.
+ * @return {@code true} if the load was successful (including if cached results were returned), false otherwise.
+ */
+ public final boolean loadSynchronously(long cacheExpirationMs, @NonNull final ProgressIndicator progress,
+ @Nullable Downloader downloader, @Nullable SettingsController settings) {
+ final AtomicBoolean result = new AtomicBoolean(true);
+ load(cacheExpirationMs, null, null, ImmutableList.<Runnable>of(new Runnable() {
+ @Override
+ public void run() {
+ result.set(false);
+ }
+ }), new ProgressRunner() {
+ @Override
+ public void runAsyncWithProgress(@NonNull ProgressRunnable r) {
+ r.run(progress, this);
+ }
+
+ @Override
+ public void runSyncWithProgress(@NonNull ProgressRunnable r) {
+ r.run(progress, this);
+ }
+
+ @Override
+ public void runSyncWithoutProgress(@NonNull Runnable r) {
+ r.run();
+ }
+ }, downloader, settings, true);
+
+ return result.get();
+ }
+ /**
+ * Causes cached results to be considered expired. The next time {@link #load(long, List, List,
+ * List, ProgressRunner, Downloader, SettingsController, boolean)} is called, a complete load
+ * will be done.
+ */
+ public abstract void markInvalid();
+
+ /**
+ * Gets the currently-loaded {@link RepositoryPackages}.
+ */
+ @NonNull
+ public abstract RepositoryPackages getPackages();
+
+ /**
+ * Gets an {@link LSResourceResolver} that can find the XSDs for all versions of the
+ * currently-registered {@link SchemaModule}s by namespace. Returns null if there is an error.
+ */
+ @Nullable
+ public abstract LSResourceResolver getResourceResolver(@NonNull ProgressIndicator progress);
+
+ /**
+ * Registers a listener that will be called whenever the local packages are reloaded and have
+ * changed. The {@link RepositoryPackages} instance passed to the callback will contain only
+ * the local packages.
+ */
+ public abstract void registerLocalChangeListener(@NonNull RepoLoadedCallback listener);
+
+ /**
+ * Register a listener that will be called whenever the remote packages are reloaded and have
+ * changed. The {@link RepositoryPackages} instance will contain the remote and local packages.
+ */
+ public abstract void registerRemoteChangeListener(@NonNull RepoLoadedCallback listener);
+
+ /**
+ * Callback for when repository load is completed/partially completed.
+ */
+ public interface RepoLoadedCallback {
+
+ /**
+ * @param packages The packages that have been loaded so far. When this callback is used in
+ * the {@code onLocalComplete} argument to {@link #load(long, List, List,
+ * List, ProgressRunner, Downloader, SettingsController, boolean)}
+ * {@code packages} will only include local packages.
+ */
+ void doRun(@NonNull RepositoryPackages packages);
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/RepoPackage.java b/repository/src/main/java/com/android/repository/api/RepoPackage.java
new file mode 100644
index 0000000..c35ce7b
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/RepoPackage.java
@@ -0,0 +1,85 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.TypeDetails;
+
+import java.util.Collection;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * A local or remote repository package, uniquely identified by it's {@code version} and {@code
+ * path}.
+ */
+ at XmlTransient
+public interface RepoPackage extends Comparable<RepoPackage> {
+
+ char PATH_SEPARATOR = ';';
+
+ /**
+ * Gets the {@link TypeDetails} for this package.
+ */
+ @NonNull
+ TypeDetails getTypeDetails();
+
+ /**
+ * Gets the {@link Revision} of this package.
+ */
+ @NonNull
+ Revision getVersion();
+
+ /**
+ * Gets the user-friendly name of this package.
+ */
+ @NonNull
+ String getDisplayName();
+
+ /**
+ * Gets the {@link License}, if any, associated with this package.
+ */
+ @Nullable
+ License getLicense();
+
+ /**
+ * Gets information on what versions of what packages this package depends on.
+ */
+ @NonNull
+ Collection<Dependency> getAllDependencies();
+
+ /**
+ * The install path (which also serves as unique id) for this package.
+ */
+ @NonNull
+ String getPath();
+
+ /**
+ * Whether this package is obsolete.
+ */
+ boolean obsolete();
+
+ /**
+ * Creates a {@link CommonFactory} corresponding to the {@link SchemaModule.SchemaModuleVersion}
+ * of this instance.
+ */
+ @NonNull
+ CommonFactory createFactory();
+}
diff --git a/repository/src/main/java/com/android/repository/api/Repository.java b/repository/src/main/java/com/android/repository/api/Repository.java
new file mode 100644
index 0000000..7f94812
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/Repository.java
@@ -0,0 +1,78 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.LocalPackageImpl;
+import com.android.repository.impl.meta.RepoPackageImpl;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Parsed representation of a repository xml file, including packages and licenses.
+ * Primarily stubs to be overridden by xjc-generated classes.
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+ at XmlTransient
+public abstract class Repository {
+
+ /**
+ * @return The {@link License}s included in this repository. In general licenses should be
+ * obtained from {@link RepoPackage}s, not directly from the repository (as they might not
+ * even apply to any packages.
+ */
+ @VisibleForTesting
+ @NonNull
+ public List<License> getLicense() {
+ // Stub
+ return ImmutableList.of();
+ }
+
+ /**
+ * Convenience method to add a {@link License} to this repository.
+ */
+ public void addLicense(@NonNull License l) {
+ getLicense().add(l);
+ }
+
+ @NonNull
+ public abstract CommonFactory createFactory();
+
+ @NonNull
+ public List<? extends RemotePackage> getRemotePackage() {
+ // Stub.
+ return ImmutableList.of();
+ }
+
+ public void setLocalPackage(@Nullable LocalPackageImpl p) {
+ // Stub
+ }
+
+ @Nullable
+ public LocalPackage getLocalPackage() {
+ // Stub.
+ return null;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/RepositorySource.java b/repository/src/main/java/com/android/repository/api/RepositorySource.java
new file mode 100644
index 0000000..12b20f4
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/RepositorySource.java
@@ -0,0 +1,75 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * A site from which repository XML files can be downloaded.
+ */
+public interface RepositorySource {
+
+ /**
+ * Gets the {@link SchemaModule}s that are allowed to be used to parse XML from this
+ * source.
+ */
+ @NonNull
+ Collection<SchemaModule> getPermittedModules();
+
+ /**
+ * @return true if this source is enabled.
+ */
+ boolean isEnabled();
+
+ /**
+ * @param enabled Whether this source should be enabled or disabled.
+ */
+ void setEnabled(boolean enabled);
+
+ /**
+ * @return The user-friendly name for this source.
+ */
+ @Nullable
+ String getDisplayName();
+
+ /**
+ * @return The URL from which to download.
+ */
+ String getUrl();
+
+ /**
+ * If an error was encountered loading from this source, it can be set here for display to the
+ * user.
+ */
+ void setFetchError(@Nullable String error);
+
+ /**
+ * Gets the error (if any) encountered when fetching content from this source.
+ * @return The error, or {@code null} if the load was successful.
+ */
+ @Nullable
+ String getFetchError();
+
+ /**
+ * @return The provider that created this source.
+ */
+ @NonNull
+ RepositorySourceProvider getProvider();
+}
diff --git a/repository/src/main/java/com/android/repository/api/RepositorySourceProvider.java b/repository/src/main/java/com/android/repository/api/RepositorySourceProvider.java
new file mode 100644
index 0000000..4522fcb
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/RepositorySourceProvider.java
@@ -0,0 +1,71 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * A source of {@link RepositorySource}s.
+ */
+public interface RepositorySourceProvider {
+
+ /**
+ * Gets the {@link RepositorySource}s from this provider.
+ *
+ * @param downloader The {@link Downloader}, if required by this provider.
+ * @param settings The {@link SettingsController}, if required by this provider.
+ * @param logger A {@link ProgressIndicator} to be used for showing progress and logging.
+ * @param forceRefresh If true, this provider should refresh its list of sources, rather than
+ * using a cached version.
+ */
+ @NonNull
+ List<RepositorySource> getSources(@Nullable Downloader downloader,
+ @Nullable SettingsController settings,
+ @NonNull ProgressIndicator logger, boolean forceRefresh);
+
+ /**
+ * Add a source to this provider, if this provider is editable. Changes will be reflected in
+ * {@link #getSources(Downloader, SettingsController, ProgressIndicator, boolean)}, but not
+ * persisted until {@link #save(ProgressIndicator)} is called.
+ *
+ * @param source The source to add.
+ * @return {@code true} if the source was successfully added, {@code false} otherwise.
+ */
+ boolean addSource(@NonNull RepositorySource source);
+
+ /**
+ * @return {@code true} if this provider can be edited (that is, it has a facility for saving
+ * and loading changes), {@code false} otherwise.
+ */
+ boolean isModifiable();
+
+ /**
+ * If any changes have been made, persist them.
+ */
+ void save(@NonNull ProgressIndicator progress);
+
+ /**
+ * Remove the given source from this provider, if this provider is editable.
+ * @see #addSource(RepositorySource)
+ * @param source The source to remove.
+ * @return {@code true} if the source was successfully removed, {@code false} otherwise.
+ */
+ boolean removeSource(@NonNull RepositorySource source);
+}
diff --git a/repository/src/main/java/com/android/repository/api/SchemaModule.java b/repository/src/main/java/com/android/repository/api/SchemaModule.java
new file mode 100644
index 0000000..c7b62ed
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/SchemaModule.java
@@ -0,0 +1,215 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlSchema;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Represents a versioned set of generated classes corresponding to a versioned XML schema.
+ *
+ * This can be a single stand-alone schema, or an extension to an existing schema. For example,
+ * {@code repo-common-N.xsd} defines a schema for a repository with packages, and the collection of
+ * the schemas for all N would be represented by a single {@code SchemaModule} instance.
+ * They can then be used for marshalling or unmarshalling XML documents by the repository framework.
+ */
+ at XmlTransient
+public class SchemaModule {
+
+ /**
+ * Map of XML namespaces to the SchemaModuleVersions making up this module.
+ */
+ private final Map<String, SchemaModuleVersion> mVersions = Maps.newHashMap();
+
+ /**
+ * Reference to the highest version found. Used by default when creating new objects.
+ */
+ private final SchemaModuleVersion mLatestVersion;
+
+ /**
+ * Class used with {@link Class#getResourceAsStream(String)} to look up xsd resources.
+ */
+ private final Class mResourceRoot;
+
+ /**
+ * @param ofPattern Fully-qualified class name of the JAXB {@code ObjectFactory} classes
+ * making up this module. Should have a single %d parameter, corresponding to
+ * the 1-indexed version of the schema.
+ * @param xsdPattern Filename pattern of the XSDs making up this module. Should have a single
+ * %d parameter, corresponding to the 1-indexed version of the schema.
+ * @param resourceRoot A class instance used via {@link Class#getResource(String)} to read
+ * the XSD file.
+ */
+ public SchemaModule(@NonNull String ofPattern, @NonNull String xsdPattern,
+ @NonNull Class resourceRoot) {
+ if (!ofPattern.matches(".*%[0-9.$]*d.*") || !xsdPattern.matches(".*%[0-9.$]*d.*")) {
+ assert false : "ofPattern and xsdPattern must contain a single %d parameter";
+ }
+ SchemaModuleVersion version = null;
+ for (int i = 1; ; i++) {
+ Class objectFactory;
+ try {
+ objectFactory = Class.forName(String.format(ofPattern, i));
+ } catch (ClassNotFoundException e) {
+ break;
+ }
+ String xsdLocation = String.format(xsdPattern, i);
+ version = new SchemaModuleVersion(objectFactory, xsdLocation);
+ mVersions.put(version.getNamespace(), version);
+ }
+ mLatestVersion = version;
+ assert !mVersions.isEmpty() : "No versions found";
+ mResourceRoot = resourceRoot;
+ }
+
+ /**
+ * Creates an {@code ObjectFactory} for the latest known version of this module.
+ */
+ @NonNull
+ public Object createLatestFactory() {
+ Class of = mLatestVersion.getObjectFactory();
+ try {
+ return of.newInstance();
+ } catch (IllegalAccessException e) {
+ assert false : e;
+ } catch (InstantiationException e) {
+ assert false : e;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the map of namespaces to {@link SchemaModuleVersion}s. Should only be needed by
+ * the repository framework.
+ */
+ @NonNull
+ public Map<String, SchemaModuleVersion> getNamespaceVersionMap() {
+ return mVersions;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SchemaModule)) {
+ return false;
+ }
+ return mVersions.equals(((SchemaModule)obj).getNamespaceVersionMap());
+ }
+
+ @Override
+ public int hashCode() {
+ return mVersions.hashCode();
+ }
+
+ /**
+ * Gets the namespace prefix (the namespace with the final number (if any) removed) for our
+ * latest schema version.
+ */
+ @NonNull
+ public String getNamespacePrefix() {
+ return mLatestVersion.getNamespacePrefix();
+ }
+
+ /**
+ * Gets the namespace of the our latest schema version.
+ */
+ public String getLatestNamespace() {
+ return mLatestVersion.getNamespace();
+ }
+
+ /**
+ * Represents a single version of a schema, including a single XSD and a single
+ * {@code ObjectFactory}.
+ */
+ public class SchemaModuleVersion {
+
+ private final Class mObjectFactory;
+ private final String mXsdLocation;
+ private final String mNamespace;
+
+ /**
+ * @param objectFactory The xjc-generated {@code ObjectFactory} instance for this schema
+ * version. Notably, the package containing this class must contain
+ * a {@code package-info.java} with a {@link XmlSchema} annotation
+ * giving the XML namespace of this schema.
+ * @param xsd The XSD file for this schema.
+ */
+ public SchemaModuleVersion(@NonNull Class objectFactory, @NonNull String xsdLocation) {
+ mObjectFactory = objectFactory;
+ mXsdLocation = xsdLocation;
+ String namespace = objectFactory.getPackage().getAnnotation(XmlSchema.class)
+ .namespace();
+
+ assert namespace != null : "Can't create schema module version with no namespace";
+ mNamespace = namespace;
+ }
+
+ /**
+ * Gets the {@code ObjectFactory} for this schema version.
+ */
+ @NonNull
+ public Class getObjectFactory() {
+ return mObjectFactory;
+ }
+
+ /**
+ * Gets the XSD file for this schema version.
+ */
+ @NonNull
+ public InputStream getXsd() {
+ return mResourceRoot.getResourceAsStream(mXsdLocation);
+ }
+
+ /**
+ * Gets the target namespace of this schema version.
+ */
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SchemaModuleVersion)) {
+ return false;
+ }
+ return mXsdLocation.equals(((SchemaModuleVersion)obj).mXsdLocation) &&
+ mNamespace.equals(((SchemaModuleVersion)obj).mNamespace);
+ }
+
+ @Override
+ public int hashCode() {
+ return mXsdLocation.hashCode() * 37 + mNamespace.hashCode();
+ }
+
+ /**
+ * Gets our namespace prefix (the namespace with the final number (if any) removed).
+ */
+ @NonNull
+ public String getNamespacePrefix() {
+ return mNamespace.replaceAll("/[0-9]*$", "/");
+ }
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/SettingsController.java b/repository/src/main/java/com/android/repository/api/SettingsController.java
new file mode 100644
index 0000000..214053e
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/SettingsController.java
@@ -0,0 +1,43 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.Nullable;
+
+/**
+ * Settings used by the repository framework.
+ * TODO: implement more settings.
+ */
+public interface SettingsController {
+
+ /**
+ * @return If {@code true}, all connections should be made using HTTP rather than HTTPS.
+ */
+ boolean getForceHttp();
+
+ /**
+ * @param force If {@code true}, all connections should be made using HTTP rather than HTTPS.
+ */
+ void setForceHttp(boolean force);
+
+ /**
+ * Gets the current channel. Only packages of channels at least as stable as specified will be
+ * downloaded.
+ */
+ @Nullable
+ Channel getChannel();
+}
diff --git a/repository/src/main/java/com/android/repository/api/SimpleRepositorySource.java b/repository/src/main/java/com/android/repository/api/SimpleRepositorySource.java
new file mode 100644
index 0000000..891d617
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/SimpleRepositorySource.java
@@ -0,0 +1,134 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * A simple {@link RepositorySource}.
+ */
+public class SimpleRepositorySource implements RepositorySource {
+
+ /**
+ * The URL for this source.
+ */
+ private final String mUrl;
+
+ /**
+ * The user-friendly name for this source.
+ */
+ private final String mDisplayName;
+
+ /**
+ * Whether this source is enabled.
+ * TODO: persist this value.
+ */
+ private boolean mEnabled;
+
+ /**
+ * The {@link SchemaModule}s allowed to be used when parsing the xml downloaded from
+ * this source.
+ */
+ private final Collection<SchemaModule> mAllowedModules;
+
+ /**
+ * Any error that occurred when fetching this source.
+ */
+ private String mError;
+
+ /**
+ * The {@link RepositorySourceProvider} that created this source.
+ */
+ private final RepositorySourceProvider mProvider;
+
+ /**
+ * Constructor
+ *
+ * @param url The URL this source will fetch from.
+ * @param displayName The user-friendly name for this source
+ * @param enabled Whether this source is enabled.
+ * @param allowedModules The {@link SchemaModule}s allowed to be used when parsing the xml
+ * downloaded from this source.
+ * @param provider The {@link RepositorySourceProvider} that created this source.
+ */
+ public SimpleRepositorySource(@NonNull String url,
+ @Nullable String displayName,
+ boolean enabled,
+ @NonNull Collection<SchemaModule> allowedModules,
+ @NonNull RepositorySourceProvider provider) {
+ mProvider = provider;
+ mUrl = url.trim();
+ mDisplayName = displayName;
+ mEnabled = enabled;
+ mAllowedModules = allowedModules;
+ }
+
+
+ @Override
+ @NonNull
+ public Collection<SchemaModule> getPermittedModules() {
+ return mAllowedModules;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+ @Override
+ public void setEnabled(boolean enabled) {mEnabled = enabled;}
+
+ @Override
+ @Nullable
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ @Override
+ @NonNull
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Returns a debug string representation of this object. Not for user display.
+ */
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("<RepositorySource URL='%1$s' Name='%2$s'>", mUrl, mDisplayName);
+ }
+
+ @Override
+ public void setFetchError(@Nullable String error) {
+ mError = error;
+ }
+
+ @Override
+ @Nullable
+ public String getFetchError() {
+ return mError;
+ }
+
+ @Override
+ @NonNull
+ public RepositorySourceProvider getProvider() {
+ return mProvider;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/UpdatablePackage.java b/repository/src/main/java/com/android/repository/api/UpdatablePackage.java
new file mode 100644
index 0000000..0a29501
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/UpdatablePackage.java
@@ -0,0 +1,104 @@
+/*
+ * 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.repository.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * Represents a (revisionless) package, either local, remote, or both. If both a local and remote
+ * package are specified, they should represent exactly the same package, excepting the revision.
+ * That is, the result of installing the remote package should be (a possibly updated version of)
+ * the local package.
+ */
+public class UpdatablePackage implements Comparable<UpdatablePackage> {
+
+ private LocalPackage mLocalPackage;
+
+ private RemotePackage mRemotePackage;
+
+ public UpdatablePackage(@NonNull LocalPackage localPackage) {
+ init(localPackage, null);
+ }
+
+ public UpdatablePackage(@NonNull RemotePackage remotePackage) {
+ init(null, remotePackage);
+ }
+
+ public UpdatablePackage(@NonNull LocalPackage localPackage,
+ @NonNull RemotePackage remotePackage) {
+ init(localPackage, remotePackage);
+ }
+
+ private void init(@Nullable LocalPackage localPkg, @Nullable RemotePackage remotePkg) {
+ assert localPkg != null || remotePkg != null;
+ mLocalPackage = localPkg;
+ if (remotePkg != null) {
+ setRemote(remotePkg);
+ }
+ }
+
+ /**
+ * Sets the remote package for this {@code UpdatablePackage}.
+ */
+ public void setRemote(@NonNull RemotePackage remote) {
+ mRemotePackage = remote;
+ }
+
+ @Nullable
+ public LocalPackage getLocal() {
+ return mLocalPackage;
+ }
+
+ @Nullable
+ public RemotePackage getRemote() {
+ return mRemotePackage;
+ }
+
+ public boolean hasRemote() {
+ return getRemote() != null;
+ }
+
+ public boolean hasLocal() {
+ return mLocalPackage != null;
+ }
+
+ @Override
+ public int compareTo(@NonNull UpdatablePackage o) {
+ return getRepresentative().compareTo(o.getRepresentative());
+ }
+
+ /**
+ * Gets a {@link RepoPackage} (either local or remote) corresponding to this updatable package.
+ * This will be the local package if there is one, and the remote otherwise.
+ */
+ @NonNull
+ public RepoPackage getRepresentative() {
+ if (hasLocal()) {
+ return mLocalPackage;
+ }
+ // getRemote() must be non-null if there's no local
+ //noinspection ConstantConditions
+ return getRemote();
+ }
+
+ public boolean isUpdate() {
+ RemotePackage remote = getRemote();
+ return mLocalPackage != null && remote != null
+ && mLocalPackage.getVersion().compareTo(remote.getVersion()) < 0;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/api/catalog.xml b/repository/src/main/java/com/android/repository/api/catalog.xml
new file mode 100644
index 0000000..50b4f7c
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/catalog.xml
@@ -0,0 +1,34 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ An OASIS catalog for finding the common schema components when running xjc on a schema
+ that depends on these and lives in a different directory. On the xjc command line, use
+ -catalog <relative path>/catalog.xml.
+-->
+
+<!DOCTYPE catalog
+ PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
+ "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
+<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+ prefer="public">
+ <public publicId="http://schemas.android.com/repository/android/common/01"
+ uri="repo-common-01.xsd"/>
+ <public publicId="http://schemas.android.com/repository/android/generic/01"
+ uri="generic-01.xsd"/>
+ <public publicId="http://schemas.android.com/repository/android/sites-common/1"
+ uri="repo-sites-common-1.xsd"/>
+</catalog>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/api/common.xjb b/repository/src/main/java/com/android/repository/api/common.xjb
new file mode 100644
index 0000000..1f166f6
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/common.xjb
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb">
+ <!--
+
+This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.11
+See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+Any modifications to this file will be lost upon recompilation of the source schema.
+Generated on: 2015.12.29 at 02:10:43 PM PST
+
+ -->
+ <bindings scd="x-schema::tns" xmlns:tns="http://schemas.android.com/repository/android/common/01">
+ <schemaBindings map="false">
+ <package name="com.android.repository.impl.generated.v1"/>
+ </schemaBindings>
+ <bindings scd="~tns:repositoryType">
+ <class ref="com.android.repository.impl.generated.v1.RepositoryType"/>
+ </bindings>
+ <bindings scd="~tns:remotePackage">
+ <class ref="com.android.repository.impl.generated.v1.RemotePackage"/>
+ </bindings>
+ <bindings scd="~tns:localPackage">
+ <class ref="com.android.repository.impl.generated.v1.LocalPackage"/>
+ </bindings>
+ <bindings scd="~tns:dependenciesType">
+ <class ref="com.android.repository.impl.generated.v1.DependenciesType"/>
+ </bindings>
+ <bindings scd="~tns:archivesType">
+ <class ref="com.android.repository.impl.generated.v1.ArchivesType"/>
+ </bindings>
+ <bindings scd="~tns:licenseRefType">
+ <class ref="com.android.repository.impl.generated.v1.LicenseRefType"/>
+ </bindings>
+ <bindings scd="~tns:typeDetails">
+ <class ref="com.android.repository.impl.generated.v1.TypeDetails"/>
+ </bindings>
+ <bindings scd="~tns:dependencyType">
+ <class ref="com.android.repository.impl.generated.v1.DependencyType"/>
+ </bindings>
+ <bindings scd="~tns:licenseType">
+ <class ref="com.android.repository.impl.generated.v1.LicenseType"/>
+ </bindings>
+ <bindings scd="~tns:archiveType">
+ <class ref="com.android.repository.impl.generated.v1.ArchiveType"/>
+ </bindings>
+ <bindings scd="~tns:patchesType">
+ <class ref="com.android.repository.impl.generated.v1.PatchesType"/>
+ </bindings>
+ <bindings scd="~tns:completeType">
+ <class ref="com.android.repository.impl.generated.v1.CompleteType"/>
+ </bindings>
+ <bindings scd="~tns:patchType">
+ <class ref="com.android.repository.impl.generated.v1.PatchType"/>
+ </bindings>
+ <bindings scd="~tns:channelType">
+ <class ref="com.android.repository.impl.generated.v1.ChannelType"/>
+ </bindings>
+ <bindings scd="~tns:channelRefType">
+ <class ref="com.android.repository.impl.generated.v1.ChannelRefType"/>
+ </bindings>
+ <bindings scd="~tns:revisionType">
+ <class ref="com.android.repository.impl.generated.v1.RevisionType"/>
+ </bindings>
+ </bindings>
+</bindings>
+
diff --git a/repository/src/main/java/com/android/repository/api/generic-01.xsd b/repository/src/main/java/com/android/repository/api/generic-01.xsd
new file mode 100644
index 0000000..cb8f955
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/generic-01.xsd
@@ -0,0 +1,76 @@
+<!--
+ ~ 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.
+ -->
+
+<!--
+ Extension to repo-common containing a trivial generic typeDetails implementation.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -episode repository/src/main/java/com/android/repository/api/generic.xjb \
+ -catalog repository/src/main/java/com/android/repository/api/catalog.xml \
+ -b repository/src/main/java/com/android/repository/api/common.xjb \
+ -p com.android.repository.impl.generated.generic.v1 \
+ repository/src/main/java/com/android/repository/api/generic-01.xsd \
+ -extension -Xandroid-inheritance \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -b repository/src/main/java/com/android/repository/impl/meta/generic-custom.xjb \
+ -no-header
+
+ from tools/base with jaxb-inheritance-plugin.jar, repository classes, guava, and the
+ transitive dependencies of org.glassfish.jaxb:jaxb-xjc:17.0 on the classpath.
+-->
+
+<xsd:schema
+ targetNamespace="http://schemas.android.com/repository/android/generic/01"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:common="http://schemas.android.com/repository/android/common/01"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <xsd:import namespace="http://schemas.android.com/repository/android/common/01"/>
+
+ <!-- The root element -->
+ <xsd:element name="repository" type="common:repositoryType"/>
+
+ <xsd:annotation>
+ <xsd:documentation>
+ Customization specifying the superclass of ObjectFactory.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.repository.impl.meta.GenericFactory"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+
+ <xsd:complexType name="genericDetailsType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A trivial implementation of typeDetails, for packages with no type-specific
+ information.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="common:typeDetails">
+ <xsd:all/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/api/generic.xjb b/repository/src/main/java/com/android/repository/api/generic.xjb
new file mode 100644
index 0000000..7a6810b
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/generic.xjb
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb">
+ <!--
+
+This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.11
+See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+Any modifications to this file will be lost upon recompilation of the source schema.
+Generated on: 2016.02.10 at 03:32:44 PM PST
+
+ -->
+ <bindings scd="x-schema::tns" xmlns:tns="http://schemas.android.com/repository/android/generic/01">
+ <schemaBindings map="false">
+ <package name="com.android.repository.impl.generated.generic.v1"/>
+ </schemaBindings>
+ <bindings scd="~tns:genericDetailsType">
+ <class ref="com.android.repository.impl.generated.generic.v1.GenericDetailsType"/>
+ </bindings>
+ </bindings>
+</bindings>
+
diff --git a/repository/src/main/java/com/android/repository/api/global.xjb b/repository/src/main/java/com/android/repository/api/global.xjb
new file mode 100644
index 0000000..b68dcbc
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/global.xjb
@@ -0,0 +1,23 @@
+<!--
+ ~ 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.
+ -->
+<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc">
+<globalBindings>
+ <xjc:javaType
+ name="java.lang.String"
+ xmlType="xsd:string"
+ adapter="com.android.repository.impl.meta.TrimStringAdapter"/>
+</globalBindings>
+</bindings>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/api/list-common.xjb b/repository/src/main/java/com/android/repository/api/list-common.xjb
new file mode 100644
index 0000000..97e0e33
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/list-common.xjb
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb">
+ <!--
+
+This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.11
+See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+Any modifications to this file will be lost upon recompilation of the source schema.
+Generated on: 2015.12.29 at 04:05:37 PM PST
+
+ -->
+ <bindings scd="x-schema::tns" xmlns:tns="http://schemas.android.com/repository/android/sites-common/1">
+ <schemaBindings map="false">
+ <package name="com.android.repository.impl.sources.generated.v1"/>
+ </schemaBindings>
+ <bindings scd="~tns:siteListType">
+ <class ref="com.android.repository.impl.sources.generated.v1.SiteListType"/>
+ </bindings>
+ <bindings scd="~tns:siteType">
+ <class ref="com.android.repository.impl.sources.generated.v1.SiteType"/>
+ </bindings>
+ <bindings scd="~tns:genericSiteType">
+ <class ref="com.android.repository.impl.sources.generated.v1.GenericSiteType"/>
+ </bindings>
+ </bindings>
+</bindings>
+
diff --git a/repository/src/main/java/com/android/repository/api/repo-common-01.xsd b/repository/src/main/java/com/android/repository/api/repo-common-01.xsd
new file mode 100755
index 0000000..c174ad3
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/repo-common-01.xsd
@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Schema for package repositories.
+
+ For an example XML document implmenting this schema, see
+ src/test/java/com/android/repository/impl/testData/testRepo.xml
+
+ JAXB-usable classes can be generated from this schema using the "xjc (common)" run target
+ in the tools/base IntelliJ project, or from the commandline as follows:
+ java com.sun.tools.xjc.Driver \
+ -episode repository/src/main/java/com/android/repository/api/common.xjb \
+ -p com.android.repository.impl.generated.v1 \
+ repository/src/main/java/com/android/repository/api/repo-common-01.xsd \
+ -extension -Xandroid-inheritance -d repository/src/main/java/ \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -b repository/src/main/java/com/android/repository/impl/meta/common-custom.xjb \
+ -no-header
+ from tools/base with jaxb-inheritance-plugin.jar, repository classes, guava, and the
+ transitive dependencies of org.glassfish.jaxb:jaxb-xjc:17.0 on the classpath.
+
+ Note that you cannot use the xjc executable, as it does not support 3rd-party plugins.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/repository/android/common/01"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ xmlns:repo="http://schemas.android.com/repository/android/common/01"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <!-- The root element -->
+ <xsd:element name="repository" type="repo:repositoryType"/>
+
+ <xsd:annotation>
+ <xsd:documentation>
+ Customization specifying the superclass of ObjectFactory.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.repository.impl.meta.CommonFactory"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository, consisting of a licenses and packages.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="license"
+ type="repo:licenseType"
+ minOccurs="0"
+ maxOccurs="unbounded"/>
+ <xsd:element name="channel"
+ type="repo:channelType"
+ minOccurs="0"
+ maxOccurs="unbounded"/>
+ <xsd:choice>
+ <xsd:element name="remotePackage" type="repo:remotePackage" minOccurs="0"
+ maxOccurs="unbounded"/>
+ <xsd:element name="localPackage" type="repo:localPackage" minOccurs="0"
+ maxOccurs="1"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:group name="packageFields">
+ <xsd:annotation>
+ <xsd:documentation>
+ Fields shared by local and remote packages
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <!-- Any type-specific details for this package -->
+ <xsd:element name="type-details" type="repo:typeDetails"/>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="repo:revisionType"/>
+ <!-- User-friendly name for this package -->
+ <xsd:element name="display-name" type="xsd:string"/>
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" minOccurs="0" type="repo:licenseRefType"/>
+ <!-- References to other packages that this package depends on. -->
+ <xsd:element name="dependencies" minOccurs="0" type="repo:dependenciesType"/>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:attributeGroup name="packageAttributes">
+ <!-- The path for this package, which is used as a unique identifier for it -->
+ <xsd:attribute name="path" type="repo:segmentListType" use="required"/>
+ <!-- Whether this package should be considered obsolete -->
+ <xsd:attribute name="obsolete" type="xsd:boolean"/>
+ </xsd:attributeGroup>
+
+ <xsd:complexType name="remotePackage">
+ <xsd:annotation>
+ <xsd:documentation>
+ A remote package, available for download.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:group ref="repo:packageFields"/>
+ <!-- The channel this package is in. E.g. "01-stable" -->
+ <xsd:element name="channelRef" minOccurs="0" type="repo:channelRefType"/>
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" minOccurs="1" type="repo:archivesType"/>
+ </xsd:sequence>
+ <xsd:attributeGroup ref="repo:packageAttributes"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="localPackage">
+ <xsd:annotation>
+ <xsd:documentation>
+ A locally-installed package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:group ref="repo:packageFields"/>
+ <xsd:attributeGroup ref="repo:packageAttributes"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="dependenciesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A list of dependencies.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="dependency" type="repo:dependencyType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A list of archives.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="archive" type="repo:archiveType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="licenseRefType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="typeDetails" abstract="true">
+ <xsd:annotation>
+ <xsd:documentation>
+ Type-specific details of a package. If a repository contains packages of different
+ types, with different meta-information, this should be extended to contain the
+ appropriate information.
+ This content of this element is not used by the repository framework.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:complexType>
+
+ <xsd:complexType name="dependencyType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A dependency of one package on another, including a minimum revision of the
+ depended-upon package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <xsd:element name="min-revision" type="repo:revisionType" minOccurs="0"/>
+ </xsd:all>
+ <xsd:attribute name="path" type="xsd:string" use="required"/>
+ </xsd:complexType>
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for an install path.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_.]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_\-;.]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID"/>
+ <xsd:attribute name="type" type="xsd:string" fixed="text"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="archiveType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive contains a <complete> element and zero or more
+ <patch> elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <xsd:element name="host-os" type="repo:osType" minOccurs="0"/>
+ <xsd:element name="host-bits" type="repo:bitSizeType" minOccurs="0"/>
+ <xsd:element name="jvm-bits" type="repo:bitSizeType" minOccurs="0"/>
+ <xsd:element name="min-jvm-version" type="repo:revisionType" minOccurs="0"/>
+ <xsd:element name="complete" type="repo:completeType" minOccurs="1" maxOccurs="1"/>
+ <xsd:element name="patches" minOccurs="0" type="repo:patchesType"/>
+ </xsd:all>
+ </xsd:complexType>
+
+ <xsd:complexType name="patchesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A list of patches.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="patch" type="repo:patchType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:group name="archiveFields">
+ <xsd:annotation>
+ <xsd:documentation>
+ Fields that are shared between different downloadable artifacts.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:long"/>
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum">
+ <xsd:simpleType>
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token"/>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:complexType name="completeType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A zip file containing a complete version of this package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:group ref="repo:archiveFields"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="patchType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A zip file containing a binary diff between a previous version of this package
+ (specified in <base-on>) and the current version.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="based-on" type="repo:revisionType"/>
+ <xsd:group ref="repo:archiveFields"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="channelType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A channel definition. Packages may contain a reference to a channel.
+ Channels are ordered by id, with the lowest id being most stable.
+ Only packages from channels at least as stable as that specified in the
+ user's preferences will be loaded.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:ID">
+ <xsd:pattern value="channel-[0-9]"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:simpleContent>
+
+ </xsd:complexType>
+
+ <xsd:complexType name="channelRefType">
+ <xsd:attribute name="ref" type="xsd:IDREF"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <xsd:element name="major" type="xsd:int"/>
+ <xsd:element name="minor" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="micro" type="xsd:int" minOccurs="0"/>
+ <xsd:element name="preview" type="xsd:int" minOccurs="0"/>
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="bitSizeType">
+ <xsd:annotation>
+ <xsd:documentation>A CPU bit size filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:int">
+ <xsd:pattern value="32|64"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="osType">
+ <xsd:annotation>
+ <xsd:documentation>A host OS filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="linux|macosx|windows"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/api/repo-sites-common-1.xsd b/repository/src/main/java/com/android/repository/api/repo-sites-common-1.xsd
new file mode 100644
index 0000000..e308f61
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/api/repo-sites-common-1.xsd
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Schema for a list of repository sites that can be loaded by the repository framework
+ (specifically RemoteListSourceProviderImpl).
+ This schema can be extended to provide other siteTypes, potentially associated with different
+ RepositorySchemaModules.
+
+ JAXB-usable classes can be generated from this schema using the "xjc (common source list)"
+ run target in the tools/base IntelliJ project, or from the commandline as follows:
+ java com.sun.tools.xjc.Driver \
+ -episode repository/src/main/java/com/android/repository/api/list-common.xjb \
+ -p com.android.repository.impl.sources.generated.v1 \
+ repository/src/main/java/com/android/repository/api/repo-sites-common-1.xsd \
+ -extension -Xandroid-inheritance -d repository/src/main/java/ \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -b repository/src/main/java/com/android/repository/impl/sources/repo-sites-common-custom.xjb \
+ -no-header
+
+ from tools/base with jaxb-inheritance-plugin.jar, repository classes, guava, and the
+ transitive dependencies of org.glassfish.jaxb:jaxb-xjc:17.0 on the classpath.
+
+ Note that you cannot use the xjc executable, as it does not support 3rd-party plugins.
+
+-->
+
+<xsd:schema
+ targetNamespace="http://schemas.android.com/repository/android/sites-common/1"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sl="http://schemas.android.com/repository/android/sites-common/1"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <!-- top-level element-->
+ <xsd:element name="site-list" type="sl:siteListType" />
+
+ <xsd:complexType name="siteListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A simple list of add-ons site.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="site" type="sl:siteType"/>
+ </xsd:choice>
+ </xsd:complexType>
+
+ <xsd:complexType name="siteType" abstract="true">
+ <xsd:annotation>
+ <xsd:documentation>
+ An abstract Site, containing a user-friendly name and URL.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The URL of the site. -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <!-- The user-friendly name of the site. -->
+ <xsd:element name="displayName" type="xsd:string" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <xsd:complexType name="genericSiteType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A trivial implementation of siteType.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="sl:siteType"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/repository/src/main/java/com/android/repository/impl/generated/generic/v1/GenericDetailsType.java b/repository/src/main/java/com/android/repository/impl/generated/generic/v1/GenericDetailsType.java
new file mode 100644
index 0000000..b1ac6ac
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/generic/v1/GenericDetailsType.java
@@ -0,0 +1,53 @@
+
+package com.android.repository.impl.generated.generic.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.impl.generated.v1.TypeDetails;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from generic-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A trivial implementation of typeDetails, for packages with no type-specific
+ * information.
+ *
+ *
+ * <p>Java class for genericDetailsType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="genericDetailsType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/repository/android/common/01}typeDetails">
+ * <all>
+ * </all>
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "genericDetailsType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class GenericDetailsType
+ extends TypeDetails
+ implements com.android.repository.impl.meta.TypeDetails.GenericType
+{
+
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/generic/v1/ObjectFactory.java b/repository/src/main/java/com/android/repository/impl/generated/generic/v1/ObjectFactory.java
new file mode 100644
index 0000000..29e7848
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/generic/v1/ObjectFactory.java
@@ -0,0 +1,67 @@
+
+package com.android.repository.impl.generated.generic.v1;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.api.Repository;
+import com.android.repository.impl.generated.v1.RepositoryType;
+import com.android.repository.impl.meta.GenericFactory;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from generic-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.repository.impl.generated.generic.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory
+ extends GenericFactory
+{
+
+ private final static QName _Repository_QNAME = new QName("http://schemas.android.com/repository/android/generic/01", "repository");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.repository.impl.generated.generic.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link GenericDetailsType }
+ *
+ */
+ public GenericDetailsType createGenericDetailsType() {
+ return new GenericDetailsType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link RepositoryType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/repository/android/generic/01", name = "repository")
+ public JAXBElement<RepositoryType> createRepositoryInternal(RepositoryType value) {
+ return new JAXBElement<RepositoryType>(_Repository_QNAME, RepositoryType.class, null, value);
+ }
+
+ public JAXBElement<Repository> generateRepository(Repository value) {
+ return ((JAXBElement) createRepositoryInternal(((RepositoryType) value)));
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/generic/v1/package-info.java b/repository/src/main/java/com/android/repository/impl/generated/generic/v1/package-info.java
new file mode 100644
index 0000000..1771094
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/generic/v1/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from generic-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/repository/android/generic/01")
+package com.android.repository.impl.generated.generic.v1;
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/ArchiveType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/ArchiveType.java
new file mode 100644
index 0000000..8245dc5
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/ArchiveType.java
@@ -0,0 +1,246 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.impl.meta.Archive;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A collection of files that can be downloaded for a given architecture.
+ * The <archives> node is mandatory in the repository packages and the
+ * collection must have at least one <archive> declared.
+ * Each archive contains a <complete> element and zero or more
+ * <patch> elements.
+ *
+ *
+ * <p>Java class for archiveType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="archiveType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="host-os" type="{http://schemas.android.com/repository/android/common/01}osType" minOccurs="0"/>
+ * <element name="host-bits" type="{http://schemas.android.com/repository/android/common/01}bitSizeType" minOccurs="0"/>
+ * <element name="jvm-bits" type="{http://schemas.android.com/repository/android/common/01}bitSizeType" minOccurs="0"/>
+ * <element name="min-jvm-version" type="{http://schemas.android.com/repository/android/common/01}revisionType" minOccurs="0"/>
+ * <element name="complete" type="{http://schemas.android.com/repository/android/common/01}completeType"/>
+ * <element name="patches" type="{http://schemas.android.com/repository/android/common/01}patchesType" minOccurs="0"/>
+ * </all>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "archiveType", propOrder = {
+
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class ArchiveType
+ extends Archive
+{
+
+ @XmlElement(name = "host-os")
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String hostOs;
+ @XmlElement(name = "host-bits")
+ protected Integer hostBits;
+ @XmlElement(name = "jvm-bits")
+ protected Integer jvmBits;
+ @XmlElement(name = "min-jvm-version")
+ protected com.android.repository.impl.generated.v1.RevisionType minJvmVersion;
+ @XmlElement(required = true)
+ protected com.android.repository.impl.generated.v1.CompleteType complete;
+ protected com.android.repository.impl.generated.v1.PatchesType patches;
+
+ /**
+ * Gets the value of the hostOs property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getHostOs() {
+ return hostOs;
+ }
+
+ /**
+ * Sets the value of the hostOs property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setHostOs(String value) {
+ this.hostOs = value;
+ }
+
+ /**
+ * Gets the value of the hostBits property.
+ *
+ * @return
+ * possible object is
+ * {@link Integer }
+ *
+ */
+ public Integer getHostBits() {
+ return hostBits;
+ }
+
+ /**
+ * Sets the value of the hostBits property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Integer }
+ *
+ */
+ public void setHostBits(Integer value) {
+ this.hostBits = value;
+ }
+
+ /**
+ * Gets the value of the jvmBits property.
+ *
+ * @return
+ * possible object is
+ * {@link Integer }
+ *
+ */
+ public Integer getJvmBits() {
+ return jvmBits;
+ }
+
+ /**
+ * Sets the value of the jvmBits property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Integer }
+ *
+ */
+ public void setJvmBits(Integer value) {
+ this.jvmBits = value;
+ }
+
+ /**
+ * Gets the value of the minJvmVersion property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public com.android.repository.impl.generated.v1.RevisionType getMinJvmVersion() {
+ return minJvmVersion;
+ }
+
+ /**
+ * Sets the value of the minJvmVersion property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public void setMinJvmVersionInternal(com.android.repository.impl.generated.v1.RevisionType value) {
+ this.minJvmVersion = value;
+ }
+
+ /**
+ * Gets the value of the complete property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.CompleteType }
+ *
+ */
+ public com.android.repository.impl.generated.v1.CompleteType getComplete() {
+ return complete;
+ }
+
+ /**
+ * Sets the value of the complete property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.CompleteType }
+ *
+ */
+ public void setCompleteInternal(com.android.repository.impl.generated.v1.CompleteType value) {
+ this.complete = value;
+ }
+
+ /**
+ * Gets the value of the patches property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.PatchesType }
+ *
+ */
+ public com.android.repository.impl.generated.v1.PatchesType getPatches() {
+ return patches;
+ }
+
+ /**
+ * Sets the value of the patches property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.PatchesType }
+ *
+ */
+ public void setPatchesInternal(com.android.repository.impl.generated.v1.PatchesType value) {
+ this.patches = value;
+ }
+
+ public boolean isValidHostOs(String value) {
+ return ((value == null)||(value.matches("^linux|macosx|windows$")));
+ }
+
+ public boolean isValidHostBits(String value) {
+ return ((value == null)||(value.matches("^32|64$")));
+ }
+
+ public boolean isValidJvmBits(String value) {
+ return ((value == null)||(value.matches("^32|64$")));
+ }
+
+ public void setMinJvmVersion(com.android.repository.impl.meta.RevisionType value) {
+ setMinJvmVersionInternal(((com.android.repository.impl.generated.v1.RevisionType) value));
+ }
+
+ public void setComplete(Archive.CompleteType value) {
+ setCompleteInternal(((com.android.repository.impl.generated.v1.CompleteType) value));
+ }
+
+ public void setPatches(Archive.PatchesType value) {
+ setPatchesInternal(((com.android.repository.impl.generated.v1.PatchesType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/ArchivesType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/ArchivesType.java
new file mode 100644
index 0000000..74456f6
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/ArchivesType.java
@@ -0,0 +1,92 @@
+
+package com.android.repository.impl.generated.v1;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.impl.meta.Archive;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A list of archives.
+ *
+ *
+ * <p>Java class for archivesType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="archivesType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded">
+ * <element name="archive" type="{http://schemas.android.com/repository/android/common/01}archiveType"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "archivesType", propOrder = {
+ "archive"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class ArchivesType
+ extends com.android.repository.impl.meta.RepoPackageImpl.Archives
+{
+
+ @XmlElement(required = true)
+ protected List<ArchiveType> archive;
+
+ /**
+ * Gets the value of the archive property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the archive property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getArchive().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link ArchiveType }
+ *
+ *
+ */
+ public List<ArchiveType> getArchiveInternal() {
+ if (archive == null) {
+ archive = new ArrayList<ArchiveType>();
+ }
+ return this.archive;
+ }
+
+ public List<Archive> getArchive() {
+ return ((List) getArchiveInternal());
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/ChannelRefType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/ChannelRefType.java
new file mode 100644
index 0000000..ef07c22
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/ChannelRefType.java
@@ -0,0 +1,81 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlIDREF;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.api.Channel;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * <p>Java class for channelRefType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="channelRefType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="ref" type="{http://www.w3.org/2001/XMLSchema}IDREF" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "channelRefType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class ChannelRefType
+ extends com.android.repository.impl.meta.RemotePackageImpl.ChannelRef
+{
+
+ @XmlAttribute(name = "ref")
+ @XmlIDREF
+ @XmlSchemaType(name = "IDREF")
+ protected ChannelType ref;
+
+ /**
+ * Gets the value of the ref property.
+ *
+ * @return
+ * possible object is
+ * {@link Object }
+ *
+ */
+ public ChannelType getRef() {
+ return ref;
+ }
+
+ /**
+ * Sets the value of the ref property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Object }
+ *
+ */
+ public void setRefInternal(ChannelType value) {
+ this.ref = value;
+ }
+
+ public void setRef(Channel value) {
+ setRefInternal(((ChannelType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/ChannelType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/ChannelType.java
new file mode 100644
index 0000000..85243bc
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/ChannelType.java
@@ -0,0 +1,122 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.api.Channel;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A channel definition. Packages may contain a reference to a channel.
+ * Channels are ordered by id, with the lowest id being most stable.
+ * Only packages from channels at least as stable as that specified in the
+ * user's preferences will be loaded.
+ *
+ *
+ * <p>Java class for channelType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="channelType">
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="id">
+ * <simpleType>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}ID">
+ * <pattern value="channel-[0-9]"/>
+ * </restriction>
+ * </simpleType>
+ * </attribute>
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "channelType", propOrder = {
+ "value"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class ChannelType
+ extends Channel
+{
+
+ @XmlValue
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String value;
+ @XmlAttribute(name = "id")
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlID
+ protected String id;
+
+ /**
+ * Gets the value of the value property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the value property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value of the id property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the id property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setId(String value) {
+ this.id = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/CompleteType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/CompleteType.java
new file mode 100644
index 0000000..7a23755
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/CompleteType.java
@@ -0,0 +1,134 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A zip file containing a complete version of this package.
+ *
+ *
+ * <p>Java class for completeType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="completeType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <group ref="{http://schemas.android.com/repository/android/common/01}archiveFields"/>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "completeType", propOrder = {
+ "size",
+ "checksum",
+ "url"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class CompleteType
+ extends com.android.repository.impl.meta.Archive.CompleteType
+{
+
+ protected long size;
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String checksum;
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlSchemaType(name = "token")
+ protected String url;
+
+ /**
+ * Gets the value of the size property.
+ *
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Sets the value of the size property.
+ *
+ */
+ public void setSize(long value) {
+ this.size = value;
+ }
+
+ /**
+ * Gets the value of the checksum property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getChecksum() {
+ return checksum;
+ }
+
+ /**
+ * Sets the value of the checksum property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setChecksum(String value) {
+ this.checksum = value;
+ }
+
+ /**
+ * Gets the value of the url property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Sets the value of the url property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setUrl(String value) {
+ this.url = value;
+ }
+
+ public boolean isValidChecksum(String value) {
+ return ((value != null)&&(value.matches("^([0-9a-fA-F]){40}$")));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/DependenciesType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/DependenciesType.java
new file mode 100644
index 0000000..a668e47
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/DependenciesType.java
@@ -0,0 +1,92 @@
+
+package com.android.repository.impl.generated.v1;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.api.Dependency;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A list of dependencies.
+ *
+ *
+ * <p>Java class for dependenciesType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="dependenciesType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="dependency" type="{http://schemas.android.com/repository/android/common/01}dependencyType" maxOccurs="unbounded"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "dependenciesType", propOrder = {
+ "dependency"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class DependenciesType
+ extends com.android.repository.impl.meta.RepoPackageImpl.Dependencies
+{
+
+ @XmlElement(required = true)
+ protected List<DependencyType> dependency;
+
+ /**
+ * Gets the value of the dependency property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the dependency property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getDependency().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link DependencyType }
+ *
+ *
+ */
+ public List<DependencyType> getDependencyInternal() {
+ if (dependency == null) {
+ dependency = new ArrayList<DependencyType>();
+ }
+ return this.dependency;
+ }
+
+ public List<Dependency> getDependency() {
+ return ((List) getDependencyInternal());
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/DependencyType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/DependencyType.java
new file mode 100644
index 0000000..28b3ee2
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/DependencyType.java
@@ -0,0 +1,117 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.api.Dependency;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A dependency of one package on another, including a minimum revision of the
+ * depended-upon package.
+ *
+ *
+ * <p>Java class for dependencyType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="dependencyType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="min-revision" type="{http://schemas.android.com/repository/android/common/01}revisionType" minOccurs="0"/>
+ * </all>
+ * <attribute name="path" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "dependencyType", propOrder = {
+
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class DependencyType
+ extends Dependency
+{
+
+ @XmlElement(name = "min-revision")
+ protected com.android.repository.impl.generated.v1.RevisionType minRevision;
+ @XmlAttribute(name = "path", required = true)
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String path;
+
+ /**
+ * Gets the value of the minRevision property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public com.android.repository.impl.generated.v1.RevisionType getMinRevision() {
+ return minRevision;
+ }
+
+ /**
+ * Sets the value of the minRevision property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public void setMinRevisionInternal(com.android.repository.impl.generated.v1.RevisionType value) {
+ this.minRevision = value;
+ }
+
+ /**
+ * Gets the value of the path property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Sets the value of the path property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setPath(String value) {
+ this.path = value;
+ }
+
+ public void setMinRevision(com.android.repository.impl.meta.RevisionType value) {
+ setMinRevisionInternal(((com.android.repository.impl.generated.v1.RevisionType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/LicenseRefType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/LicenseRefType.java
new file mode 100644
index 0000000..0130eed
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/LicenseRefType.java
@@ -0,0 +1,87 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlIDREF;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.api.License;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * Describes the license used by a package. The license MUST be defined
+ * using a license node and referenced using the ref attribute of the
+ * license element inside a package.
+ *
+ *
+ * <p>Java class for licenseRefType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="licenseRefType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="ref" type="{http://www.w3.org/2001/XMLSchema}IDREF" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "licenseRefType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class LicenseRefType
+ extends com.android.repository.impl.meta.RepoPackageImpl.UsesLicense
+{
+
+ @XmlAttribute(name = "ref")
+ @XmlIDREF
+ @XmlSchemaType(name = "IDREF")
+ protected LicenseType ref;
+
+ /**
+ * Gets the value of the ref property.
+ *
+ * @return
+ * possible object is
+ * {@link Object }
+ *
+ */
+ public LicenseType getRef() {
+ return ref;
+ }
+
+ /**
+ * Sets the value of the ref property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Object }
+ *
+ */
+ public void setRefInternal(LicenseType value) {
+ this.ref = value;
+ }
+
+ public void setRef(License value) {
+ setRefInternal(((LicenseType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/LicenseType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/LicenseType.java
new file mode 100644
index 0000000..4e827a0
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/LicenseType.java
@@ -0,0 +1,148 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.api.License;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A license definition. Such a license must be used later as a reference
+ * using a uses-license element in one of the package elements.
+ *
+ *
+ * <p>Java class for licenseType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="licenseType">
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ * <attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" fixed="text" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "licenseType", propOrder = {
+ "value"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class LicenseType
+ extends License
+{
+
+ @XmlValue
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String value;
+ @XmlAttribute(name = "id")
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlID
+ @XmlSchemaType(name = "ID")
+ protected String id;
+ @XmlAttribute(name = "type")
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String type;
+
+ /**
+ * Gets the value of the value property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the value property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value of the id property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the id property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setId(String value) {
+ this.id = value;
+ }
+
+ /**
+ * Gets the value of the type property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getType() {
+ if (type == null) {
+ return new TrimStringAdapter().unmarshal("text");
+ } else {
+ return type;
+ }
+ }
+
+ /**
+ * Sets the value of the type property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setType(String value) {
+ this.type = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/LocalPackage.java b/repository/src/main/java/com/android/repository/impl/generated/v1/LocalPackage.java
new file mode 100644
index 0000000..1cc4fac
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/LocalPackage.java
@@ -0,0 +1,261 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.impl.meta.LocalPackageImpl;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A locally-installed package.
+ *
+ *
+ * <p>Java class for localPackage complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="localPackage">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <group ref="{http://schemas.android.com/repository/android/common/01}packageFields"/>
+ * <attGroup ref="{http://schemas.android.com/repository/android/common/01}packageAttributes"/>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "localPackage", propOrder = {
+ "typeDetails",
+ "revision",
+ "displayName",
+ "usesLicense",
+ "dependencies"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class LocalPackage
+ extends LocalPackageImpl
+{
+
+ @XmlElement(name = "type-details", required = true)
+ protected com.android.repository.impl.generated.v1.TypeDetails typeDetails;
+ @XmlElement(required = true)
+ protected com.android.repository.impl.generated.v1.RevisionType revision;
+ @XmlElement(name = "display-name", required = true)
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String displayName;
+ @XmlElement(name = "uses-license")
+ protected LicenseRefType usesLicense;
+ protected DependenciesType dependencies;
+ @XmlAttribute(name = "path", required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ protected String path;
+ @XmlAttribute(name = "obsolete")
+ protected Boolean obsolete;
+
+ /**
+ * Gets the value of the typeDetails property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.TypeDetails }
+ *
+ */
+ public com.android.repository.impl.generated.v1.TypeDetails getTypeDetails() {
+ return typeDetails;
+ }
+
+ /**
+ * Sets the value of the typeDetails property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.TypeDetails }
+ *
+ */
+ public void setTypeDetailsInternal(com.android.repository.impl.generated.v1.TypeDetails value) {
+ this.typeDetails = value;
+ }
+
+ /**
+ * Gets the value of the revision property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public com.android.repository.impl.generated.v1.RevisionType getRevision() {
+ return revision;
+ }
+
+ /**
+ * Sets the value of the revision property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public void setRevisionInternal(com.android.repository.impl.generated.v1.RevisionType value) {
+ this.revision = value;
+ }
+
+ /**
+ * Gets the value of the displayName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * Sets the value of the displayName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setDisplayName(String value) {
+ this.displayName = value;
+ }
+
+ /**
+ * Gets the value of the usesLicense property.
+ *
+ * @return
+ * possible object is
+ * {@link LicenseRefType }
+ *
+ */
+ public LicenseRefType getUsesLicense() {
+ return usesLicense;
+ }
+
+ /**
+ * Sets the value of the usesLicense property.
+ *
+ * @param value
+ * allowed object is
+ * {@link LicenseRefType }
+ *
+ */
+ public void setUsesLicenseInternal(LicenseRefType value) {
+ this.usesLicense = value;
+ }
+
+ /**
+ * Gets the value of the dependencies property.
+ *
+ * @return
+ * possible object is
+ * {@link DependenciesType }
+ *
+ */
+ public DependenciesType getDependencies() {
+ return dependencies;
+ }
+
+ /**
+ * Sets the value of the dependencies property.
+ *
+ * @param value
+ * allowed object is
+ * {@link DependenciesType }
+ *
+ */
+ public void setDependenciesInternal(DependenciesType value) {
+ this.dependencies = value;
+ }
+
+ /**
+ * Gets the value of the path property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Sets the value of the path property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setPath(String value) {
+ this.path = value;
+ }
+
+ /**
+ * Gets the value of the obsolete property.
+ *
+ * @return
+ * possible object is
+ * {@link Boolean }
+ *
+ */
+ public Boolean isObsolete() {
+ return obsolete;
+ }
+
+ /**
+ * Sets the value of the obsolete property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Boolean }
+ *
+ */
+ public void setObsolete(Boolean value) {
+ this.obsolete = value;
+ }
+
+ public void setTypeDetails(com.android.repository.impl.meta.TypeDetails value) {
+ setTypeDetailsInternal(((com.android.repository.impl.generated.v1.TypeDetails) value));
+ }
+
+ public void setRevision(com.android.repository.impl.meta.RevisionType value) {
+ setRevisionInternal(((com.android.repository.impl.generated.v1.RevisionType) value));
+ }
+
+ public void setUsesLicense(com.android.repository.impl.meta.RepoPackageImpl.UsesLicense value) {
+ setUsesLicenseInternal(((LicenseRefType) value));
+ }
+
+ public void setDependencies(com.android.repository.impl.meta.RepoPackageImpl.Dependencies value) {
+ setDependenciesInternal(((DependenciesType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/ObjectFactory.java b/repository/src/main/java/com/android/repository/impl/generated/v1/ObjectFactory.java
new file mode 100644
index 0000000..f38d6a6
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/ObjectFactory.java
@@ -0,0 +1,178 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.api.Repository;
+import com.android.repository.impl.meta.CommonFactory;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.repository.impl.generated.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory
+ extends CommonFactory
+{
+
+ private final static QName _Repository_QNAME = new QName("http://schemas.android.com/repository/android/common/01", "repository");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.repository.impl.generated.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link RepositoryType }
+ *
+ */
+ public RepositoryType createRepositoryType() {
+ return new RepositoryType();
+ }
+
+ /**
+ * Create an instance of {@link RemotePackage }
+ *
+ */
+ public RemotePackage createRemotePackage() {
+ return new RemotePackage();
+ }
+
+ /**
+ * Create an instance of {@link LocalPackage }
+ *
+ */
+ public LocalPackage createLocalPackage() {
+ return new LocalPackage();
+ }
+
+ /**
+ * Create an instance of {@link DependenciesType }
+ *
+ */
+ public DependenciesType createDependenciesType() {
+ return new DependenciesType();
+ }
+
+ /**
+ * Create an instance of {@link ArchivesType }
+ *
+ */
+ public ArchivesType createArchivesType() {
+ return new ArchivesType();
+ }
+
+ /**
+ * Create an instance of {@link LicenseRefType }
+ *
+ */
+ public LicenseRefType createLicenseRefType() {
+ return new LicenseRefType();
+ }
+
+ /**
+ * Create an instance of {@link DependencyType }
+ *
+ */
+ public DependencyType createDependencyType() {
+ return new DependencyType();
+ }
+
+ /**
+ * Create an instance of {@link LicenseType }
+ *
+ */
+ public LicenseType createLicenseType() {
+ return new LicenseType();
+ }
+
+ /**
+ * Create an instance of {@link ArchiveType }
+ *
+ */
+ public ArchiveType createArchiveType() {
+ return new ArchiveType();
+ }
+
+ /**
+ * Create an instance of {@link PatchesType }
+ *
+ */
+ public PatchesType createPatchesType() {
+ return new PatchesType();
+ }
+
+ /**
+ * Create an instance of {@link CompleteType }
+ *
+ */
+ public CompleteType createCompleteType() {
+ return new CompleteType();
+ }
+
+ /**
+ * Create an instance of {@link PatchType }
+ *
+ */
+ public PatchType createPatchType() {
+ return new PatchType();
+ }
+
+ /**
+ * Create an instance of {@link ChannelType }
+ *
+ */
+ public ChannelType createChannelType() {
+ return new ChannelType();
+ }
+
+ /**
+ * Create an instance of {@link ChannelRefType }
+ *
+ */
+ public ChannelRefType createChannelRefType() {
+ return new ChannelRefType();
+ }
+
+ /**
+ * Create an instance of {@link RevisionType }
+ *
+ */
+ public RevisionType createRevisionType() {
+ return new RevisionType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link RepositoryType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/repository/android/common/01", name = "repository")
+ public JAXBElement<RepositoryType> createRepositoryInternal(RepositoryType value) {
+ return new JAXBElement<RepositoryType>(_Repository_QNAME, RepositoryType.class, null, value);
+ }
+
+ public JAXBElement<Repository> generateRepository(Repository value) {
+ return ((JAXBElement) createRepositoryInternal(((RepositoryType) value)));
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/PatchType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/PatchType.java
new file mode 100644
index 0000000..2d9da7a
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/PatchType.java
@@ -0,0 +1,169 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A zip file containing a binary diff between a previous version of this package
+ * (specified in <base-on>) and the current version.
+ *
+ *
+ * <p>Java class for patchType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="patchType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="based-on" type="{http://schemas.android.com/repository/android/common/01}revisionType"/>
+ * <group ref="{http://schemas.android.com/repository/android/common/01}archiveFields"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "patchType", propOrder = {
+ "basedOn",
+ "size",
+ "checksum",
+ "url"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class PatchType
+ extends com.android.repository.impl.meta.Archive.PatchType
+{
+
+ @XmlElement(name = "based-on", required = true)
+ protected com.android.repository.impl.generated.v1.RevisionType basedOn;
+ protected long size;
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String checksum;
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlSchemaType(name = "token")
+ protected String url;
+
+ /**
+ * Gets the value of the basedOn property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public com.android.repository.impl.generated.v1.RevisionType getBasedOn() {
+ return basedOn;
+ }
+
+ /**
+ * Sets the value of the basedOn property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public void setBasedOnInternal(com.android.repository.impl.generated.v1.RevisionType value) {
+ this.basedOn = value;
+ }
+
+ /**
+ * Gets the value of the size property.
+ *
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Sets the value of the size property.
+ *
+ */
+ public void setSize(long value) {
+ this.size = value;
+ }
+
+ /**
+ * Gets the value of the checksum property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getChecksum() {
+ return checksum;
+ }
+
+ /**
+ * Sets the value of the checksum property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setChecksum(String value) {
+ this.checksum = value;
+ }
+
+ /**
+ * Gets the value of the url property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Sets the value of the url property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setUrl(String value) {
+ this.url = value;
+ }
+
+ public boolean isValidChecksum(String value) {
+ return ((value != null)&&(value.matches("^([0-9a-fA-F]){40}$")));
+ }
+
+ public void setBasedOn(com.android.repository.impl.meta.RevisionType value) {
+ setBasedOnInternal(((com.android.repository.impl.generated.v1.RevisionType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/PatchesType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/PatchesType.java
new file mode 100644
index 0000000..a749460
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/PatchesType.java
@@ -0,0 +1,91 @@
+
+package com.android.repository.impl.generated.v1;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A list of patches.
+ *
+ *
+ * <p>Java class for patchesType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="patchesType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded">
+ * <element name="patch" type="{http://schemas.android.com/repository/android/common/01}patchType"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "patchesType", propOrder = {
+ "patch"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class PatchesType
+ extends com.android.repository.impl.meta.Archive.PatchesType
+{
+
+ @XmlElement(required = true)
+ protected List<com.android.repository.impl.generated.v1.PatchType> patch;
+
+ /**
+ * Gets the value of the patch property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the patch property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getPatch().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link com.android.repository.impl.generated.v1.PatchType }
+ *
+ *
+ */
+ public List<com.android.repository.impl.generated.v1.PatchType> getPatchInternal() {
+ if (patch == null) {
+ patch = new ArrayList<com.android.repository.impl.generated.v1.PatchType>();
+ }
+ return this.patch;
+ }
+
+ public List<com.android.repository.impl.meta.Archive.PatchType> getPatch() {
+ return ((List) getPatchInternal());
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/RemotePackage.java b/repository/src/main/java/com/android/repository/impl/generated/v1/RemotePackage.java
new file mode 100644
index 0000000..6c46a1d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/RemotePackage.java
@@ -0,0 +1,326 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.impl.meta.RemotePackageImpl;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A remote package, available for download.
+ *
+ *
+ * <p>Java class for remotePackage complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="remotePackage">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <group ref="{http://schemas.android.com/repository/android/common/01}packageFields"/>
+ * <element name="channelRef" type="{http://schemas.android.com/repository/android/common/01}channelRefType" minOccurs="0"/>
+ * <element name="archives" type="{http://schemas.android.com/repository/android/common/01}archivesType"/>
+ * </sequence>
+ * <attGroup ref="{http://schemas.android.com/repository/android/common/01}packageAttributes"/>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "remotePackage", propOrder = {
+ "typeDetails",
+ "revision",
+ "displayName",
+ "usesLicense",
+ "dependencies",
+ "channelRef",
+ "archives"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class RemotePackage
+ extends RemotePackageImpl
+{
+
+ @XmlElement(name = "type-details", required = true)
+ protected com.android.repository.impl.generated.v1.TypeDetails typeDetails;
+ @XmlElement(required = true)
+ protected com.android.repository.impl.generated.v1.RevisionType revision;
+ @XmlElement(name = "display-name", required = true)
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String displayName;
+ @XmlElement(name = "uses-license")
+ protected LicenseRefType usesLicense;
+ protected DependenciesType dependencies;
+ protected ChannelRefType channelRef;
+ @XmlElement(required = true)
+ protected ArchivesType archives;
+ @XmlAttribute(name = "path", required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ protected String path;
+ @XmlAttribute(name = "obsolete")
+ protected Boolean obsolete;
+
+ /**
+ * Gets the value of the typeDetails property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.TypeDetails }
+ *
+ */
+ public com.android.repository.impl.generated.v1.TypeDetails getTypeDetails() {
+ return typeDetails;
+ }
+
+ /**
+ * Sets the value of the typeDetails property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.TypeDetails }
+ *
+ */
+ public void setTypeDetailsInternal(com.android.repository.impl.generated.v1.TypeDetails value) {
+ this.typeDetails = value;
+ }
+
+ /**
+ * Gets the value of the revision property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public com.android.repository.impl.generated.v1.RevisionType getRevision() {
+ return revision;
+ }
+
+ /**
+ * Sets the value of the revision property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.repository.impl.generated.v1.RevisionType }
+ *
+ */
+ public void setRevisionInternal(com.android.repository.impl.generated.v1.RevisionType value) {
+ this.revision = value;
+ }
+
+ /**
+ * Gets the value of the displayName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * Sets the value of the displayName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setDisplayName(String value) {
+ this.displayName = value;
+ }
+
+ /**
+ * Gets the value of the usesLicense property.
+ *
+ * @return
+ * possible object is
+ * {@link LicenseRefType }
+ *
+ */
+ public LicenseRefType getUsesLicense() {
+ return usesLicense;
+ }
+
+ /**
+ * Sets the value of the usesLicense property.
+ *
+ * @param value
+ * allowed object is
+ * {@link LicenseRefType }
+ *
+ */
+ public void setUsesLicenseInternal(LicenseRefType value) {
+ this.usesLicense = value;
+ }
+
+ /**
+ * Gets the value of the dependencies property.
+ *
+ * @return
+ * possible object is
+ * {@link DependenciesType }
+ *
+ */
+ public DependenciesType getDependencies() {
+ return dependencies;
+ }
+
+ /**
+ * Sets the value of the dependencies property.
+ *
+ * @param value
+ * allowed object is
+ * {@link DependenciesType }
+ *
+ */
+ public void setDependenciesInternal(DependenciesType value) {
+ this.dependencies = value;
+ }
+
+ /**
+ * Gets the value of the channelRef property.
+ *
+ * @return
+ * possible object is
+ * {@link ChannelRefType }
+ *
+ */
+ public ChannelRefType getChannelRef() {
+ return channelRef;
+ }
+
+ /**
+ * Sets the value of the channelRef property.
+ *
+ * @param value
+ * allowed object is
+ * {@link ChannelRefType }
+ *
+ */
+ public void setChannelRefInternal(ChannelRefType value) {
+ this.channelRef = value;
+ }
+
+ /**
+ * Gets the value of the archives property.
+ *
+ * @return
+ * possible object is
+ * {@link ArchivesType }
+ *
+ */
+ public ArchivesType getArchives() {
+ return archives;
+ }
+
+ /**
+ * Sets the value of the archives property.
+ *
+ * @param value
+ * allowed object is
+ * {@link ArchivesType }
+ *
+ */
+ public void setArchivesInternal(ArchivesType value) {
+ this.archives = value;
+ }
+
+ /**
+ * Gets the value of the path property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Sets the value of the path property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setPath(String value) {
+ this.path = value;
+ }
+
+ /**
+ * Gets the value of the obsolete property.
+ *
+ * @return
+ * possible object is
+ * {@link Boolean }
+ *
+ */
+ public Boolean isObsolete() {
+ return obsolete;
+ }
+
+ /**
+ * Sets the value of the obsolete property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Boolean }
+ *
+ */
+ public void setObsolete(Boolean value) {
+ this.obsolete = value;
+ }
+
+ public void setTypeDetails(com.android.repository.impl.meta.TypeDetails value) {
+ setTypeDetailsInternal(((com.android.repository.impl.generated.v1.TypeDetails) value));
+ }
+
+ public void setRevision(com.android.repository.impl.meta.RevisionType value) {
+ setRevisionInternal(((com.android.repository.impl.generated.v1.RevisionType) value));
+ }
+
+ public void setUsesLicense(com.android.repository.impl.meta.RepoPackageImpl.UsesLicense value) {
+ setUsesLicenseInternal(((LicenseRefType) value));
+ }
+
+ public void setDependencies(com.android.repository.impl.meta.RepoPackageImpl.Dependencies value) {
+ setDependenciesInternal(((DependenciesType) value));
+ }
+
+ public void setChannelRef(RemotePackageImpl.ChannelRef value) {
+ setChannelRefInternal(((ChannelRefType) value));
+ }
+
+ public void setArchives(com.android.repository.impl.meta.RepoPackageImpl.Archives value) {
+ setArchivesInternal(((ArchivesType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/RepositoryType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/RepositoryType.java
new file mode 100644
index 0000000..6ea4535
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/RepositoryType.java
@@ -0,0 +1,199 @@
+
+package com.android.repository.impl.generated.v1;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.api.Channel;
+import com.android.repository.api.License;
+import com.android.repository.api.Repository;
+import com.android.repository.impl.meta.LocalPackageImpl;
+import com.android.repository.impl.meta.RemotePackageImpl;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * The repository, consisting of a licenses and packages.
+ *
+ *
+ * <p>Java class for repositoryType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="repositoryType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="license" type="{http://schemas.android.com/repository/android/common/01}licenseType" maxOccurs="unbounded" minOccurs="0"/>
+ * <element name="channel" type="{http://schemas.android.com/repository/android/common/01}channelType" maxOccurs="unbounded" minOccurs="0"/>
+ * <choice>
+ * <element name="remotePackage" type="{http://schemas.android.com/repository/android/common/01}remotePackage" maxOccurs="unbounded" minOccurs="0"/>
+ * <element name="localPackage" type="{http://schemas.android.com/repository/android/common/01}localPackage" minOccurs="0"/>
+ * </choice>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "repositoryType", propOrder = {
+ "license",
+ "channel",
+ "remotePackage",
+ "localPackage"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class RepositoryType
+ extends Repository
+{
+
+ protected List<LicenseType> license;
+ protected List<ChannelType> channel;
+ protected List<RemotePackage> remotePackage;
+ protected LocalPackage localPackage;
+
+ /**
+ * Gets the value of the license property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the license property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getLicense().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link LicenseType }
+ *
+ *
+ */
+ public List<LicenseType> getLicenseInternal() {
+ if (license == null) {
+ license = new ArrayList<LicenseType>();
+ }
+ return this.license;
+ }
+
+ /**
+ * Gets the value of the channel property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the channel property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getChannel().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link ChannelType }
+ *
+ *
+ */
+ public List<ChannelType> getChannelInternal() {
+ if (channel == null) {
+ channel = new ArrayList<ChannelType>();
+ }
+ return this.channel;
+ }
+
+ /**
+ * Gets the value of the remotePackage property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the remotePackage property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getRemotePackage().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link RemotePackage }
+ *
+ *
+ */
+ public List<RemotePackage> getRemotePackageInternal() {
+ if (remotePackage == null) {
+ remotePackage = new ArrayList<RemotePackage>();
+ }
+ return this.remotePackage;
+ }
+
+ /**
+ * Gets the value of the localPackage property.
+ *
+ * @return
+ * possible object is
+ * {@link LocalPackage }
+ *
+ */
+ public LocalPackage getLocalPackage() {
+ return localPackage;
+ }
+
+ /**
+ * Sets the value of the localPackage property.
+ *
+ * @param value
+ * allowed object is
+ * {@link LocalPackage }
+ *
+ */
+ public void setLocalPackageInternal(LocalPackage value) {
+ this.localPackage = value;
+ }
+
+ public List<License> getLicense() {
+ return ((List) getLicenseInternal());
+ }
+
+ public List<Channel> getChannel() {
+ return ((List) getChannelInternal());
+ }
+
+ public List<RemotePackageImpl> getRemotePackage() {
+ return ((List) getRemotePackageInternal());
+ }
+
+ public void setLocalPackage(LocalPackageImpl value) {
+ setLocalPackageInternal(((LocalPackage) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/RevisionType.java b/repository/src/main/java/com/android/repository/impl/generated/v1/RevisionType.java
new file mode 100644
index 0000000..29f4630
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/RevisionType.java
@@ -0,0 +1,149 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A full revision, with a major.minor.micro and an
+ * optional preview number. The major number is mandatory.
+ *
+ *
+ * <p>Java class for revisionType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="revisionType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="major" type="{http://www.w3.org/2001/XMLSchema}int"/>
+ * <element name="minor" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/>
+ * <element name="micro" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/>
+ * <element name="preview" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/>
+ * </all>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "revisionType", propOrder = {
+
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class RevisionType
+ extends com.android.repository.impl.meta.RevisionType
+{
+
+ protected int major;
+ protected Integer minor;
+ protected Integer micro;
+ protected Integer preview;
+
+ /**
+ * Gets the value of the major property.
+ *
+ */
+ public int getMajor() {
+ return major;
+ }
+
+ /**
+ * Sets the value of the major property.
+ *
+ */
+ public void setMajor(int value) {
+ this.major = value;
+ }
+
+ /**
+ * Gets the value of the minor property.
+ *
+ * @return
+ * possible object is
+ * {@link Integer }
+ *
+ */
+ public Integer getMinor() {
+ return minor;
+ }
+
+ /**
+ * Sets the value of the minor property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Integer }
+ *
+ */
+ public void setMinor(Integer value) {
+ this.minor = value;
+ }
+
+ /**
+ * Gets the value of the micro property.
+ *
+ * @return
+ * possible object is
+ * {@link Integer }
+ *
+ */
+ public Integer getMicro() {
+ return micro;
+ }
+
+ /**
+ * Sets the value of the micro property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Integer }
+ *
+ */
+ public void setMicro(Integer value) {
+ this.micro = value;
+ }
+
+ /**
+ * Gets the value of the preview property.
+ *
+ * @return
+ * possible object is
+ * {@link Integer }
+ *
+ */
+ public Integer getPreview() {
+ return preview;
+ }
+
+ /**
+ * Sets the value of the preview property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Integer }
+ *
+ */
+ public void setPreview(Integer value) {
+ this.preview = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/TypeDetails.java b/repository/src/main/java/com/android/repository/impl/generated/v1/TypeDetails.java
new file mode 100644
index 0000000..fb6d651
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/TypeDetails.java
@@ -0,0 +1,47 @@
+
+package com.android.repository.impl.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * Type-specific details of a package. If a repository contains packages of different
+ * types, with different meta-information, this should be extended to contain the
+ * appropriate information.
+ * This content of this element is not used by the repository framework.
+ *
+ *
+ * <p>Java class for typeDetails complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="typeDetails">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "typeDetails")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public abstract class TypeDetails
+ extends com.android.repository.impl.meta.TypeDetails
+{
+
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/generated/v1/package-info.java b/repository/src/main/java/com/android/repository/impl/generated/v1/package-info.java
new file mode 100644
index 0000000..8e0b9c3
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/generated/v1/package-info.java
@@ -0,0 +1,2 @@
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/repository/android/common/01")
+package com.android.repository.impl.generated.v1;
diff --git a/repository/src/main/java/com/android/repository/impl/installer/BasicInstaller.java b/repository/src/main/java/com/android/repository/impl/installer/BasicInstaller.java
new file mode 100644
index 0000000..7c90690
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/installer/BasicInstaller.java
@@ -0,0 +1,127 @@
+/*
+ * 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.repository.impl.installer;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.SettingsController;
+import com.android.repository.impl.meta.Archive;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.repository.util.InstallerUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * A simple {@link PackageInstaller} that just unzips the {@code complete} version of an {@link
+ * Archive} into its destination directory.
+ */
+public class BasicInstaller implements PackageInstaller {
+
+ /**
+ * Just deletes the package.
+ *
+ * @param p The {@link LocalPackage} to delete.
+ * @param progress A {@link ProgressIndicator}. Unused by this installer.
+ * @param manager A {@link RepoManager} that knows about this package.
+ * @param fop The {@link FileOp} to use. Should be {@link FileOpUtils#create()} if not in
+ * a unit test.
+ * @return {@code true} if the uninstall was successful, {@code false} otherwise.
+ */
+ @Override
+ public boolean uninstall(@NonNull LocalPackage p, @NonNull ProgressIndicator progress,
+ @NonNull RepoManager manager, @NonNull FileOp fop) {
+ String path = p.getPath();
+ path = path.replace(RepoPackage.PATH_SEPARATOR, File.separatorChar);
+ File location = new File(manager.getLocalPath(), path);
+
+ fop.deleteFileOrFolder(location);
+ manager.markInvalid();
+
+ return !fop.exists(location);
+ }
+
+ /**
+ * Installs the package by unzipping into its {@code path}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean install(@NonNull RemotePackage p, @NonNull Downloader downloader,
+ @Nullable SettingsController settings, @NonNull ProgressIndicator progress,
+ @NonNull RepoManager manager, @NonNull FileOp fop) {
+ URL url = InstallerUtil.resolveCompleteArchiveUrl(p, progress);
+ if (url == null) {
+ return false;
+ }
+ try {
+ String path = p.getPath();
+ path = path.replace(RepoPackage.PATH_SEPARATOR, File.separatorChar);
+ File dest = new File(manager.getLocalPath(), path);
+ if (!InstallerUtil.checkValidPath(dest, manager, progress)) {
+ return false;
+ }
+
+ File in = downloader.downloadFully(url, settings, progress);
+
+ File out = FileOpUtils.getNewTempDir("BasicInstaller", fop);
+ if (out == null || !fop.mkdirs(out)) {
+ throw new IOException("Failed to create temp dir");
+ }
+ fop.deleteOnExit(out);
+ progress.logInfo(String.format("Installing %1$s in %2$s", p.getDisplayName(), dest));
+ InstallerUtil.unzip(in, out, fop, p.getArchive().getComplete().getSize(), progress);
+ fop.delete(in);
+
+ // Archives must contain a single top-level directory.
+ File[] topDirContents = fop.listFiles(out);
+ File packageRoot;
+ if (topDirContents.length != 1) {
+ // TODO: we should be consistent and only support packages with a single top-level
+ // directory, but right now haxm doesn't have one. Put this check back when it's
+ // fixed.
+ // throw new IOException("Archive didn't have single top level directory");
+ packageRoot = out;
+ }
+ else {
+ packageRoot = topDirContents[0];
+ }
+
+ InstallerUtil.writePackageXml(p, packageRoot, manager, fop, progress);
+
+ // Move the final unzipped archive into place.
+ FileOpUtils.safeRecursiveOverwrite(packageRoot, dest, fop, progress);
+ manager.markInvalid();
+ return true;
+ } catch (IOException e) {
+ String message = e.getMessage();
+ progress.logWarning("An error occurred during installation" +
+ (message.isEmpty() ? "." : ": " + message + "."), e);
+ }
+
+ return false;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/installer/PackageInstaller.java b/repository/src/main/java/com/android/repository/impl/installer/PackageInstaller.java
new file mode 100644
index 0000000..4c984af
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/installer/PackageInstaller.java
@@ -0,0 +1,64 @@
+/*
+ * 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.repository.impl.installer;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.SettingsController;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+
+/**
+ * A facility for downloading and installing and uninstalling packages.
+ */
+public interface PackageInstaller {
+
+ /**
+ * Uninstall the package.
+ *
+ * @param p The {@link LocalPackage} to delete.
+ * @param progress A {@link ProgressIndicator} for showing progress and facilitating logging.
+ * @param manager A {@link RepoManager} that knows about this package.
+ * @param fop The {@link FileOp} to use. Should be {@link FileOpUtils#create()} if not in a
+ * unit test.
+ * @return {@code true} if the uninstall was successful, {@code false} otherwise.
+ */
+ boolean uninstall(@NonNull LocalPackage p, @NonNull ProgressIndicator progress,
+ @NonNull RepoManager manager, @NonNull FileOp fop);
+
+ /**
+ * Installs the package.
+ *
+ * @param p The {@link RemotePackage} to install.
+ * @param downloader The {@link Downloader} used to download the archive.
+ * @param settings The {@link SettingsController} to provide any settings needed.
+ * @param progress A {@link ProgressIndicator}, to show install progress and facilitate
+ * logging.
+ * @param manager A {@link RepoManager} that knows about this package.
+ * @param fop The {@link FileOp} to use. Should be {@link FileOpUtils#create()} if not in
+ * a unit test.
+ * @return {@code true} if the install was successful, {@code false} otherwise.
+ */
+ boolean install(@NonNull RemotePackage p, @NonNull Downloader downloader,
+ @Nullable SettingsController settings, @NonNull ProgressIndicator progress,
+ @NonNull RepoManager manager, @NonNull FileOp fop);
+}
diff --git a/repository/src/main/java/com/android/repository/impl/manager/LocalRepoLoader.java b/repository/src/main/java/com/android/repository/impl/manager/LocalRepoLoader.java
new file mode 100644
index 0000000..c40872a
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/manager/LocalRepoLoader.java
@@ -0,0 +1,259 @@
+/*
+ * 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.repository.impl.manager;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.FallbackLocalRepoLoader;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.Repository;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.LocalPackageImpl;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.xml.bind.JAXBException;
+
+/**
+ * A utility class that finds {@link LocalPackage}s under a given path based on {@code package.xml}
+ * files.
+ */
+public final class LocalRepoLoader {
+
+ /**
+ * The name of the package metadata file we can read.
+ */
+ public static final String PACKAGE_XML_FN = "package.xml";
+
+ /**
+ * The maximum depth we'll descend into the directory tree while looking for packages. TODO:
+ * adjust once the path of the current deepest package is known (e.g. maven packages).
+ */
+ private static final int MAX_SCAN_DEPTH = 10;
+
+ /**
+ * Cache of found packages. TODO: this isn't really used in the current code. Simplify by
+ * removing if wider adoption of the new APIs doesn't turn up a need for it. This applies to all
+ * the fields in this class.
+ */
+ private Map<String, LocalPackage> mPackages = null;
+
+ /**
+ * Directory under which we look for packages.
+ */
+ private final File mRoot;
+
+ private final RepoManager mRepoManager;
+
+ private final FileOp mFop;
+
+ /**
+ * If we can't find a package in a directory, we ask mFallback to find one. If it does, we write
+ * out a {@code package.xml} so we can read it next time.
+ */
+ private FallbackLocalRepoLoader mFallback;
+
+ /**
+ * Constructor. Probably should only be used within repository framework.
+ *
+ * @param root The root directory under which we'll look for packages.
+ * @param manager A RepoManager, notably containing the {@link SchemaModule}s we'll use for
+ * reading and writing {@link LocalPackage}s
+ * @param fallback The {@link FallbackLocalRepoLoader} we'll use if we can't find a package in a
+ * directory.
+ * @param fop The {@link FileOp} to use for file operations. Should be
+ * {@link FileOpUtils#create()} for normal operation.
+ */
+ public LocalRepoLoader(@NonNull File root, @NonNull RepoManager manager,
+ @Nullable FallbackLocalRepoLoader fallback, @NonNull FileOp fop) {
+ mRoot = root;
+ mRepoManager = manager;
+ mFop = fop;
+ mFallback = fallback;
+ }
+
+ /**
+ * Gets our packages, loading them if necessary.
+ *
+ * @param progress A {@link ProgressIndicator} used to show progress (unimplemented) and
+ * logging.
+ * @return A map of install path to {@link LocalPackage}, containing all the packages found in
+ * the given root.
+ */
+ @NonNull
+ public Map<String, LocalPackage> getPackages(@NonNull ProgressIndicator progress) {
+ if (mPackages == null) {
+ Map<String, LocalPackage> packages = Maps.newHashMap();
+ collectPackages(progress, packages, mRoot, 0);
+ mPackages = packages;
+ }
+ return Collections.unmodifiableMap(mPackages);
+ }
+
+ /**
+ * Collect packages under the given root into {@code collector}.
+ *
+ * @param progress {@link ProgressIndicator} for logging.
+ * @param collector The collector.
+ * @param root Directory we're looking in.
+ * @param depth The depth we've descended to so far. Once we reach {@link #MAX_SCAN_DEPTH}
+ * we'll stop recursing.
+ */
+ private void collectPackages(@NonNull ProgressIndicator progress,
+ @NonNull Map<String, LocalPackage> collector, @NonNull File root, int depth) {
+ if (depth > MAX_SCAN_DEPTH) {
+ return;
+ }
+ File packageXml = new File(root, PACKAGE_XML_FN);
+ LocalPackage p = null;
+ if (mFop.exists(packageXml)) {
+ try {
+ p = parsePackage(packageXml, progress);
+ }
+ catch (Exception e) {
+ // There was a problem parsing the package. Try the fallback loader.
+ progress.logWarning("Found corrupted package.xml at " + packageXml);
+ }
+ }
+ if (p == null && mFallback != null) {
+ p = mFallback.parseLegacyLocalPackage(root, progress);
+ if (p != null) {
+ writePackage(p, packageXml, progress);
+ }
+ else if (mFop.exists(packageXml)) {
+ File bad = new File(packageXml.getPath() + ".bad");
+ progress.logWarning(String.format(
+ "Invalid package.xml found and failed to parse using fallback. Renaming %1$s to %2$s",
+ packageXml, bad));
+ mFop.renameTo(packageXml, bad);
+ }
+ }
+ if (p != null) {
+ addPackage(p, collector, progress);
+ } else {
+ for (File f : mFop.listFiles(root)) {
+ if (mFop.isDirectory(f)) {
+ collectPackages(progress, collector, f, depth + 1);
+ }
+ }
+ }
+ }
+
+ private void addPackage(@NonNull LocalPackage p, @NonNull Map<String, LocalPackage> collector,
+ @NonNull ProgressIndicator progress) {
+ String filePath = p.getPath().replace(RepoPackage.PATH_SEPARATOR, File.separatorChar);
+ File desired = new File(mRoot, filePath);
+ File actual = p.getLocation();
+ if (!desired.equals(actual)) {
+ progress.logWarning(String.format(
+ "Observed package id '%1$s' in inconsistent location '%2$s' (Expected '%3$s')",
+ p.getPath(), actual.getPath(), desired.getPath()));
+ LocalPackage existing = collector.get(p.getPath());
+ if (existing != null) {
+ progress.logWarning(String.format(
+ "Already observed package id '%1$s' in '%2$s'. Skipping duplicate at '%3$s'",
+ p.getPath(), existing.getLocation().getPath(), actual.getPath()));
+ return;
+ }
+ }
+ collector.put(p.getPath(), p);
+ }
+
+ /**
+ * If the {@link FallbackLocalRepoLoader} finds a package, we write out a package.xml so we can
+ * load it next time without falling back.
+ *
+ * @param p The {@link LocalPackage} to write out.
+ * @param packageXml The destination to write to.
+ * @param progress {@link ProgressIndicator} for logging.
+ */
+ private void writePackage(@NonNull LocalPackage p, @NonNull File packageXml,
+ @NonNull ProgressIndicator progress) {
+ // We need a LocalPackageImpl to be able to save it.
+ LocalPackageImpl impl = LocalPackageImpl.create(p, mRepoManager);
+ OutputStream fos = null;
+ try {
+ fos = mFop.newFileOutputStream(packageXml);
+ Repository repo = impl.createFactory().createRepositoryType();
+ repo.setLocalPackage(impl);
+ repo.addLicense(impl.getLicense());
+
+ TypeDetails typeDetails = p.getTypeDetails();
+ CommonFactory factory = ((CommonFactory) RepoManager.getCommonModule()
+ .createLatestFactory());
+ SchemaModuleUtil.marshal(factory.generateRepository(repo),
+ mRepoManager.getSchemaModules(), fos,
+ mRepoManager.getResourceResolver(progress), progress);
+ } catch (FileNotFoundException e) {
+ progress.logInfo("File not found while marshalling " + packageXml
+ + ". Probably the SDK is read-only");
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+ }
+
+ /**
+ * Unmarshal a package.xml file and extract the {@link LocalPackage}.
+ */
+ @Nullable
+ private LocalPackage parsePackage(@NonNull File packageXml,
+ @NonNull ProgressIndicator progress) throws JAXBException {
+ Repository repo;
+ try {
+ progress.logInfo("Parsing " + packageXml);
+ repo = (Repository) SchemaModuleUtil.unmarshal(mFop.newFileInputStream(packageXml),
+ mRepoManager.getSchemaModules(), mRepoManager.getResourceResolver(progress),
+ false, progress);
+ } catch (FileNotFoundException e) {
+ // This shouldn't ever happen
+ progress.logError(String.format("XML file %s doesn't exist", packageXml), e);
+ return null;
+ }
+ if (repo == null) {
+ progress.logWarning(String.format("Failed to parse %s", packageXml));
+ return null;
+ } else {
+ LocalPackage p = repo.getLocalPackage();
+ if (p == null) {
+ progress.logWarning("Didn't find any local package in repository");
+ return null;
+ }
+ p.setInstalledPath(packageXml.getParentFile());
+ return p;
+ }
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/manager/RemoteRepoLoader.java b/repository/src/main/java/com/android/repository/impl/manager/RemoteRepoLoader.java
new file mode 100644
index 0000000..b929a25
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/manager/RemoteRepoLoader.java
@@ -0,0 +1,201 @@
+/*
+ * 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.repository.impl.manager;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Channel;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.FallbackRemoteRepoLoader;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.ProgressIndicatorAdapter;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.Repository;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.RepositorySourceProvider;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.api.SettingsController;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import org.w3c.dom.ls.LSResourceResolver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.JAXBException;
+
+/**
+ * Utility class that loads {@link Repository}s from {@link RepositorySource}s.
+ */
+public class RemoteRepoLoader {
+
+ /**
+ * Resource resolver to use for finding imported XSDs.
+ */
+ private final LSResourceResolver mResourceResolver;
+
+ /**
+ * {@link FallbackRemoteRepoLoader} to use if we get an XML file we can't parse.
+ */
+ private FallbackRemoteRepoLoader mFallback;
+
+ /**
+ * The {@link RepositorySourceProvider}s to load from.
+ */
+ private final Collection<RepositorySourceProvider> mSourceProviders;
+
+ /**
+ * Constructor
+ *
+ * @param sources The {@link RepositorySourceProvider}s to get the {@link
+ * RepositorySource}s to load from.
+ * @param resourceResolver The resolver to use to find imported XSDs, if necessary for the
+ * {@link SchemaModule}s used by the {@link RepositorySource}s.
+ * @param fallback The {@link FallbackRemoteRepoLoader} to use if we can't parse an XML
+ * file.
+ */
+ public RemoteRepoLoader(@NonNull Collection<RepositorySourceProvider> sources,
+ @Nullable LSResourceResolver resourceResolver,
+ @Nullable FallbackRemoteRepoLoader fallback) {
+ mResourceResolver = resourceResolver;
+ mSourceProviders = sources;
+ mFallback = fallback;
+ }
+
+ /**
+ * Actually loads {@link RemotePackage}s from the given sources.
+ *
+ * @param progress {@link ProgressIndicator} for logging and showing progress (TODO).
+ * @param downloader The {@link Downloader} to use for {@link RepositorySourceProvider}s to use
+ * if needed.
+ * @param settings The {@link SettingsController} for {@link RepositorySourceProvider}s to use
+ * if needed.
+ * @return A {@link Multimap} of install paths to {@link RemotePackage}s. Usually there should
+ * be at most two versions for a given package: a stable version and/or a preview version.
+ */
+ @NonNull
+ public Map<String, RemotePackage> fetchPackages(@NonNull ProgressIndicator progress,
+ @NonNull Downloader downloader, @Nullable SettingsController settings) {
+ Map<String, RemotePackage> result = Maps.newHashMap();
+ for (RepositorySourceProvider provider : mSourceProviders) {
+ for (RepositorySource source : provider
+ .getSources(downloader, settings, progress, false)) {
+ if (!source.isEnabled()) {
+ continue;
+ }
+ try {
+ InputStream repoStream = downloader
+ .downloadAndStream(new URL(source.getUrl()), settings, progress);
+ final List<String> errors = Lists.newArrayList();
+
+ // Don't show the errors, in case the fallback loader can read it. But keep
+ // track of them to show later in case not.
+ ProgressIndicator unmarshalProgress = new ProgressIndicatorAdapter() {
+ @Override
+ public void logWarning(@NonNull String s, Throwable e) {
+ errors.add(s);
+ if (e != null) {
+ errors.add(e.toString());
+ }
+ }
+
+ @Override
+ public void logError(@NonNull String s, Throwable e) {
+ errors.add(s);
+ if (e != null) {
+ errors.add(e.toString());
+ }
+ }
+ };
+
+ Repository repo = null;
+ try {
+ repo = (Repository) SchemaModuleUtil
+ .unmarshal(repoStream, source.getPermittedModules(),
+ mResourceResolver, true, unmarshalProgress);
+ } catch (JAXBException e) {
+ errors.add(e.toString());
+ }
+
+ Collection<? extends RemotePackage> parsedPackages = null;
+ boolean legacy = false;
+ if (repo != null) {
+ parsedPackages = repo.getRemotePackage();
+ } else if (mFallback != null) {
+ // TODO: don't require downloading again
+ parsedPackages = mFallback.parseLegacyXml(source, progress);
+ legacy = true;
+ }
+ if (parsedPackages != null && !parsedPackages.isEmpty()) {
+ for (RemotePackage pkg : parsedPackages) {
+ RemotePackage existing = result.get(pkg.getPath());
+ if (existing != null) {
+ int compare = existing.getVersion().compareTo(pkg.getVersion());
+ if (compare > 0) {
+ // If there are multiple versions of the same package available,
+ // pick the latest.
+ continue;
+ }
+ if (compare == 0 && legacy) {
+ // If legacy and non-legacy packages are available with the same
+ // version, pick the non-legacy one.
+ continue;
+ }
+ }
+ Channel settingsChannel =
+ settings == null || settings.getChannel() == null
+ ? Channel.DEFAULT : settings.getChannel();
+
+ if (pkg.getArchive() != null
+ && pkg.getChannel().compareTo(settingsChannel) <= 0) {
+ pkg.setSource(source);
+ result.put(pkg.getPath(), pkg);
+ }
+ }
+ source.setFetchError(null);
+ } else {
+ progress.logWarning("Errors during XML parse:");
+ for (String error : errors) {
+ progress.logWarning(error);
+ }
+ //noinspection VariableNotUsedInsideIf
+ if (mFallback != null) {
+ progress.logWarning(
+ "Additionally, the fallback loader failed to parse the XML.");
+ }
+ source.setFetchError(errors.isEmpty() ? "unknown error" : errors.get(0));
+ }
+ } catch (MalformedURLException e) {
+ source.setFetchError("Malformed URL");
+ progress.logWarning(e.toString());
+ } catch (IOException e) {
+ source.setFetchError(e.getMessage());
+ progress.logWarning(e.toString());
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/manager/RepoManagerImpl.java b/repository/src/main/java/com/android/repository/impl/manager/RepoManagerImpl.java
new file mode 100755
index 0000000..6e858f1
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/manager/RepoManagerImpl.java
@@ -0,0 +1,564 @@
+/*
+ * 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.repository.impl.manager;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.FallbackLocalRepoLoader;
+import com.android.repository.api.FallbackRemoteRepoLoader;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.ProgressRunner;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.RepositorySourceProvider;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.api.SettingsController;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.impl.FileOpImpl;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.w3c.dom.ls.LSResourceResolver;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+
+/**
+ * Main implementation of {@link RepoManager}. Loads local and remote {@link RepoPackage}s
+ * synchronously and asynchronously into a {@link RepositoryPackages} instance from the given local
+ * path and from the registered {@link RepositorySourceProvider}s, using the registered {@link
+ * SchemaModule}s.
+ */
+public class RepoManagerImpl extends RepoManager {
+
+ /**
+ * The registered {@link SchemaModule}s.
+ */
+ private final Set<SchemaModule> mModules = Sets.newHashSet();
+
+ /**
+ * The {@link FallbackLocalRepoLoader} to use when loading local packages.
+ */
+ @Nullable
+ private FallbackLocalRepoLoader mFallbackLocalRepoLoader;
+
+ /**
+ * The path under which to look for installed packages.
+ */
+ @Nullable
+ private File mLocalPath;
+
+ /**
+ * The {@link FallbackRemoteRepoLoader} to use if the normal {@link RemoteRepoLoader} can't
+ * understand a downloaded repository xml file.
+ */
+ @Nullable
+ private FallbackRemoteRepoLoader mFallbackRemoteRepoLoader;
+
+ /**
+ * The {@link RepositorySourceProvider}s from which to get {@link RepositorySource}s to load
+ * from.
+ */
+ private Set<RepositorySourceProvider> mSourceProviders = Sets.newHashSet();
+
+ /**
+ * The loaded packages.
+ */
+ private RepositoryPackages mPackages = new RepositoryPackages();
+
+ /**
+ * When we last loaded the remote packages.
+ */
+ private long mLastRemoteRefreshMs;
+
+ /**
+ * When we last loaded the local packages.
+ */
+ private long mLastLocalRefreshMs;
+
+ /**
+ * The task used to load packages. If non-null, a load is currently in progress.
+ */
+ private LoadTask mTask;
+
+ /**
+ * Lock used when setting {@link #mTask}.
+ */
+ private final Object mTaskLock = new Object();
+
+ /**
+ * {@link FileOp} to be used for local file operations. Should be {@link FileOpImpl} for normal
+ * operation.
+ */
+ private final FileOp mFop;
+
+ /**
+ * Listeners that will be called when the known local packages change.
+ */
+ private final List<RepoLoadedCallback> mLocalListeners = Lists.newArrayList();
+
+ /**
+ * Listeners that will be called when the known remote packages change.
+ */
+ private final List<RepoLoadedCallback> mRemoteListeners = Lists.newArrayList();
+
+ /**
+ * Create a new {@code RepoManagerImpl}. Before anything can be loaded, at least a local path
+ * and/or at least one {@link RepositorySourceProvider} must be set.
+ *
+ * @param fop {@link FileOp} to use for local file operations. Should only be null if you're
+ * never planning to load a local repo using this {@code RepoManagerImpl}.
+ */
+ public RepoManagerImpl(@Nullable FileOp fop) {
+ mFop = fop;
+ registerSchemaModule(getCommonModule());
+ registerSchemaModule(getGenericModule());
+ }
+
+ @Nullable
+ @Override
+ public File getLocalPath() {
+ return mLocalPath;
+ }
+
+ /**
+ * {@inheritDoc} This calls {@link #markInvalid()}, so a complete load will occur the next time
+ * {@link #load(long, List, List, List, ProgressRunner, Downloader, SettingsController,
+ * boolean)} is called.
+ */
+ @Override
+ public void setFallbackLocalRepoLoader(@Nullable FallbackLocalRepoLoader fallback) {
+ mFallbackLocalRepoLoader = fallback;
+ markInvalid();
+ }
+
+ /**
+ * {@inheritDoc} This calls {@link #markInvalid()}, so a complete load will occur the next time
+ * {@link #load(long, List, List, List, ProgressRunner, Downloader, SettingsController,
+ * boolean)} is called.
+ */
+ @Override
+ public void setFallbackRemoteRepoLoader(@Nullable FallbackRemoteRepoLoader remote) {
+ mFallbackRemoteRepoLoader = remote;
+ markInvalid();
+ }
+
+ /**
+ * {@inheritDoc} This calls {@link #markInvalid()}, so a complete load will occur the next time
+ * {@link #load(long, List, List, List, ProgressRunner, Downloader, SettingsController,
+ * boolean)} is called.
+ */
+ @Override
+ public void setLocalPath(@Nullable File path) {
+ mLocalPath = path;
+ markInvalid();
+ }
+
+ /**
+ * {@inheritDoc} This calls {@link #markInvalid()}, so a complete load will occur the next time
+ * {@link #load(long, List, List, List, ProgressRunner, Downloader, SettingsController,
+ * boolean)} is called.
+ */
+ @Override
+ public void registerSourceProvider(@NonNull RepositorySourceProvider provider) {
+ mSourceProviders.add(provider);
+ markInvalid();
+ }
+
+ @VisibleForTesting
+ @Override
+ @NonNull
+ public Set<RepositorySourceProvider> getSourceProviders() {
+ return mSourceProviders;
+ }
+
+ @Override
+ @NonNull
+ public Set<RepositorySource> getSources(@Nullable Downloader downloader,
+ @Nullable SettingsController settings, @NonNull ProgressIndicator progress,
+ boolean forceRefresh) {
+ Set<RepositorySource> result = Sets.newHashSet();
+ for (RepositorySourceProvider provider : mSourceProviders) {
+ result.addAll(provider.getSources(downloader, settings, progress, forceRefresh));
+ }
+ return result;
+ }
+
+ @Override
+ @NonNull
+ public Set<SchemaModule> getSchemaModules() {
+ return mModules;
+ }
+
+ /**
+ * {@inheritDoc} This calls {@link #markInvalid()}, so a complete load will occur the next time
+ * {@link #load(long, List, List, List, ProgressRunner, Downloader, SettingsController,
+ * boolean)} is called.
+ */
+ @Override
+ public void registerSchemaModule(@NonNull SchemaModule module) {
+ mModules.add(module);
+ markInvalid();
+ }
+
+ @Override
+ public void markInvalid() {
+ mLastRemoteRefreshMs = 0;
+ mLastLocalRefreshMs = 0;
+ }
+
+ @Override
+ @Nullable
+ public LSResourceResolver getResourceResolver(@NonNull ProgressIndicator progress) {
+ List<SchemaModule> allModules = ImmutableList.<SchemaModule>builder().addAll(
+ getSchemaModules()).add(
+ getCommonModule()).add(
+ getGenericModule()).build();
+ return SchemaModuleUtil.createResourceResolver(allModules, progress);
+ }
+
+ @Override
+ @NonNull
+ public RepositoryPackages getPackages() {
+ return mPackages;
+ }
+
+ // TODO: fix up invalidation. It's annoying that you have to manually reload.
+ // TODO: Maybe: when you load, instead of load as now, you get back a loader, which knows how
+ // TODO: to reload with same settings,
+ // TODO: and contains current valid or invalid packages as they are cached here.
+ @Override
+ public boolean load(long cacheExpirationMs,
+ @Nullable List<RepoLoadedCallback> onLocalComplete,
+ @Nullable List<RepoLoadedCallback> onSuccess,
+ @Nullable List<Runnable> onError,
+ @NonNull ProgressRunner runner,
+ @Nullable Downloader downloader,
+ @Nullable SettingsController settings,
+ boolean sync) {
+ if (onLocalComplete == null) {
+ onLocalComplete = ImmutableList.of();
+ }
+ if (onSuccess == null) {
+ onSuccess = ImmutableList.of();
+ }
+ if (onError == null) {
+ onError = ImmutableList.of();
+ }
+
+ // If we're not going to refresh, just run the callbacks.
+ if (checkExpiration(mLocalPath != null, downloader != null, cacheExpirationMs)) {
+ for (RepoLoadedCallback localComplete : onLocalComplete) {
+ runner.runSyncWithoutProgress(new CallbackRunnable(localComplete, mPackages));
+ }
+ for (RepoLoadedCallback success : onSuccess) {
+ runner.runSyncWithoutProgress(new CallbackRunnable(success, mPackages));
+ }
+ // false: we didn't actually reload.
+ return false;
+ }
+
+ final Semaphore completed = new Semaphore(1);
+ try {
+ completed.acquire();
+ } catch (InterruptedException e) {
+ // shouldn't happen.
+ }
+ if (sync) {
+ // If we're running synchronously, release the semaphore after run complete.
+ onSuccess = Lists.newArrayList(onSuccess);
+ onSuccess.add(new RepoLoadedCallback() {
+ @Override
+ public void doRun(@NonNull RepositoryPackages packages) {
+ completed.release();
+ }
+ });
+ onError = Lists.newArrayList(onError);
+ onError.add(new Runnable() {
+ @Override
+ public void run() {
+ completed.release();
+ }
+ });
+ }
+
+ // If we created the currently running task, we need to clean it up at the end.
+ boolean createdTask = false;
+
+ try {
+ synchronized (mTaskLock) {
+ if (mTask != null) {
+ // If there's a task running already, just add our callbacks to it.
+ mTask.addCallbacks(onLocalComplete, onSuccess, onError, runner);
+ } else {
+ // Otherwise, create a new task.
+ mTask = new LoadTask(onLocalComplete, onSuccess, onError,
+ downloader, settings);
+ createdTask = true;
+ }
+ }
+
+ if (createdTask) {
+ // If we created a task, run it.
+ if (sync) {
+ runner.runSyncWithProgress(mTask);
+ } else {
+ runner.runAsyncWithProgress(mTask);
+ }
+ } else if (sync) {
+ // Otherwise wait for the semaphore to be released by the callback if we're
+ // running synchronously.
+ try {
+ completed.acquire();
+ } catch (InterruptedException e) {
+ // shouldn't happen
+ }
+ }
+ } finally {
+ if (createdTask) {
+ // If we created a task, clean it up.
+ mTask = null;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks to see whether the local and/or remote package caches have expired and should
+ * be reloaded.
+ *
+ * @param checkLocal Whether we should check whether the local packages have expired.
+ * @param checkRemote Whether we should check whether the remote packages have expired.
+ * @param timeoutPeriod The timeout to use for the cache.
+ * @return {@code true} if {@code checkLocal} is true and the local cache was last refreshed
+ * at least {@code timeoutPeriod} ago, and/or if {@code checkRemote} is true and the remote
+ * cache was last refreshed at least {@code timeoutPeriod} ago.
+ */
+ private boolean checkExpiration(boolean checkLocal, boolean checkRemote, long timeoutPeriod) {
+ long time = System.currentTimeMillis();
+ return (!checkLocal || mLastLocalRefreshMs + timeoutPeriod > time) &&
+ (!checkRemote || mLastRemoteRefreshMs + timeoutPeriod > time);
+ }
+
+ @Override
+ public void registerLocalChangeListener(@NonNull RepoLoadedCallback listener) {
+ mLocalListeners.add(listener);
+ }
+
+ @Override
+ public void registerRemoteChangeListener(@NonNull RepoLoadedCallback listener) {
+ mRemoteListeners.add(listener);
+ }
+
+ /**
+ * A task to load the local and remote repos.
+ */
+ private class LoadTask implements ProgressRunner.ProgressRunnable {
+
+ /**
+ * If callbacks get added to an already-running task, they might have a different
+ * {@link ProgressRunner} than the one used to run the task. Here we keep the callback
+ * along with the runner so the callback can be invoked correctly.
+ */
+ private class Callback {
+ private RepoLoadedCallback mCallback;
+ private ProgressRunner mRunner;
+
+ public Callback(@NonNull RepoLoadedCallback callback, @Nullable ProgressRunner runner) {
+ mCallback = callback;
+ mRunner = runner;
+ }
+
+ public ProgressRunner getRunner(ProgressRunner defaultRunner) {
+ return mRunner == null ? defaultRunner : mRunner;
+ }
+
+ public RepoLoadedCallback getCallback() {
+ return mCallback;
+ }
+ }
+
+ private final List<Callback> mOnSuccesses = Lists.newArrayList();
+
+ private final List<Runnable> mOnErrors = Lists.newArrayList();
+
+ private final List<Callback> mOnLocalCompletes = Lists.newArrayList();
+
+ private final Downloader mDownloader;
+
+ private final SettingsController mSettings;
+
+ public LoadTask(@NonNull List<RepoLoadedCallback> onLocalComplete,
+ @NonNull List<RepoLoadedCallback> onSuccess,
+ @NonNull List<Runnable> onError,
+ @Nullable Downloader downloader,
+ @Nullable SettingsController settings) {
+ addCallbacks(onLocalComplete, onSuccess, onError, null);
+ mDownloader = downloader;
+ mSettings = settings;
+ }
+
+ /**
+ * Add callbacks to this task (if e.g. {@link #load(long, List, List, List,
+ * ProgressRunner, Downloader, SettingsController, boolean)} is called again while
+ * a task is already running.
+ */
+ public void addCallbacks(@NonNull List<RepoLoadedCallback> onLocalComplete,
+ @NonNull List<RepoLoadedCallback> onSuccess,
+ @NonNull List<Runnable> onError,
+ @Nullable ProgressRunner runner) {
+ for (RepoLoadedCallback local : onLocalComplete) {
+ mOnLocalCompletes.add(new Callback(local, runner));
+ }
+ for (RepoLoadedCallback success : onSuccess) {
+ mOnSuccesses.add(new Callback(success, runner));
+ }
+ mOnErrors.addAll(onError);
+ }
+
+ /**
+ * Do the actual load.
+ *
+ * @param indicator {@link ProgressIndicator} for logging and showing actual progress
+ * @param runner {@link ProgressRunner} for running asynchronous tasks and callbacks.
+ */
+ @Override
+ public void run(@NonNull ProgressIndicator indicator, @NonNull ProgressRunner runner) {
+ boolean success = false;
+ try {
+ if (mLocalPath != null) {
+ if (mFallbackLocalRepoLoader != null) {
+ mFallbackLocalRepoLoader.refresh();
+ }
+ LocalRepoLoader local = new LocalRepoLoader(mLocalPath, RepoManagerImpl.this,
+ mFallbackLocalRepoLoader, mFop);
+ indicator.setText("Loading local repository...");
+ Map<String, LocalPackage> newLocals = local.getPackages(indicator);
+ boolean fireListeners = !newLocals.equals(mPackages.getLocalPackages());
+ mPackages.setLocalPkgInfos(newLocals);
+ if (fireListeners) {
+ for (RepoLoadedCallback listener : mLocalListeners) {
+ listener.doRun(mPackages);
+ }
+ }
+ indicator.setFraction(0.25);
+ }
+ if (indicator.isCanceled()) {
+ return;
+ }
+ synchronized (mTaskLock) {
+ for (Callback onLocalComplete : mOnLocalCompletes) {
+ onLocalComplete.getRunner(runner).runSyncWithoutProgress(
+ new CallbackRunnable(onLocalComplete.mCallback, mPackages));
+ }
+ mOnLocalCompletes.clear();
+ }
+ indicator.setText("Fetch remote repository...");
+ indicator.setSecondaryText("");
+
+ if (!mSourceProviders.isEmpty() && mDownloader != null) {
+ RemoteRepoLoader remoteLoader = new RemoteRepoLoader(mSourceProviders,
+ getResourceResolver(indicator), mFallbackRemoteRepoLoader);
+ Map<String, RemotePackage> remotes = remoteLoader
+ .fetchPackages(indicator, mDownloader, mSettings);
+ indicator.setText("Computing updates...");
+ indicator.setFraction(0.75);
+ boolean fireListeners = !remotes.equals(mPackages.getRemotePackages());
+ mPackages.setRemotePkgInfos(remotes);
+ if (fireListeners) {
+ for (RepoLoadedCallback callback : mRemoteListeners) {
+ callback.doRun(mPackages);
+ }
+ }
+ }
+
+ if (indicator.isCanceled()) {
+ return;
+ }
+ indicator.setSecondaryText("");
+ indicator.setFraction(1.0);
+
+ if (indicator.isCanceled()) {
+ return;
+ }
+ success = true;
+ } finally {
+ if (mDownloader != null) {
+ mLastRemoteRefreshMs = System.currentTimeMillis();
+ }
+ if (mLocalPath != null) {
+ mLastLocalRefreshMs = System.currentTimeMillis();
+ }
+ synchronized (mTaskLock) {
+ // The processing of the task is now complete.
+ // To ensure that no more callbacks are added, and to allow another task to be
+ // kicked off when needed, set mTask to null.
+ mTask = null;
+ if (success) {
+ for (Callback onLocalComplete : mOnLocalCompletes) {
+ // in case some were added by another call in the interim.
+ onLocalComplete.getRunner(runner).runSyncWithoutProgress(
+ new CallbackRunnable(onLocalComplete.getCallback(), mPackages));
+ }
+ for (Callback onSuccess : mOnSuccesses) {
+ onSuccess.getRunner(runner).runSyncWithoutProgress(
+ new CallbackRunnable(onSuccess.getCallback(), mPackages));
+ }
+ } else {
+ for (final Runnable onError : mOnErrors) {
+ onError.run();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A {@link Runnable} that wraps a {@link RepoLoadedCallback} and calls it with the
+ * appropriate args.
+ */
+ private static class CallbackRunnable implements Runnable {
+
+ RepoLoadedCallback mCallback;
+
+ RepositoryPackages mPackages;
+
+ public CallbackRunnable(@NonNull RepoLoadedCallback callback,
+ @NonNull RepositoryPackages packages) {
+ mCallback = callback;
+ mPackages = packages;
+ }
+
+ @Override
+ public void run() {
+ mCallback.doRun(mPackages);
+ }
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/Archive.java b/repository/src/main/java/com/android/repository/impl/meta/Archive.java
new file mode 100644
index 0000000..7c2b66d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/Archive.java
@@ -0,0 +1,327 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.SchemaModule;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * A downloadable version of a {@link RepoPackage}, corresponding to a specific version number, and
+ * optionally host OS, host bitness, JVM version or JVM bitness. Includes a complete version of the
+ * package ({@link Archive.CompleteType}), and optionally binary patches from previous revisions to
+ * this one ({@link Archive.PatchType}).
+ *
+ * Primarily a superclass for xjc-generated JAXB-compatible classes.
+ */
+ at XmlTransient
+public abstract class Archive {
+
+ /**
+ * The detected bit size of the JVM.
+ */
+ private static int sJvmBits = 0;
+
+ /**
+ * The detected bit size of the host.
+ */
+ private static int sHostBits = 0;
+
+ /**
+ * The detected OS.
+ */
+ private static String sOs = null;
+
+ /**
+ * The detected JVM version.
+ */
+ private static Revision sJvmVersion = null;
+
+ /**
+ * @return {@code true} if this archive is compatible with the current system with respect to
+ * the specified host os, bit size, jvm version, and jvm bit size (if any).
+ */
+ public boolean isCompatible() {
+ if (getHostOs() != null) {
+ if (sOs == null) {
+ String os = System.getProperty("os.name");
+ if (os.startsWith("Mac")) {
+ os = "macosx";
+ } else if (os.startsWith("Windows")) {
+ os = "windows";
+ } else if (os.startsWith("Linux")) {
+ os = "linux";
+ }
+ sOs = os;
+ }
+ if (!getHostOs().equals(sOs)) {
+ return false;
+ }
+ }
+
+ if (getJvmBits() != null || getHostBits() != null) {
+ if (sJvmBits == 0) {
+ int jvmBits = 0;
+ String arch = System.getProperty("os.arch");
+
+ if (arch.equalsIgnoreCase("x86_64") ||
+ arch.equalsIgnoreCase("ia64") ||
+ arch.equalsIgnoreCase("amd64")) {
+ jvmBits = 64;
+ } else {
+ jvmBits = 32;
+ }
+ sJvmBits = jvmBits;
+ }
+
+ if (getJvmBits() != null && getJvmBits() != sJvmBits) {
+ return false;
+ }
+
+ if (sHostBits == 0) {
+ // TODO figure out the host bit size.
+ // When jvmBits is 64 we know it's surely 64
+ // but that's not necessarily obvious when jvmBits is 32.
+ sHostBits = sJvmBits;
+ }
+
+ if (getHostBits() != null && getHostBits() != sHostBits) {
+ return false;
+ }
+ }
+
+ if (getMinJvmVersion() != null) {
+ if (sJvmVersion == null) {
+ Revision minJvmVersion = null;
+ String javav = System.getProperty("java.version"); //$NON-NLS-1$
+ // java Version is typically in the form "1.2.3_45" and we just need to keep up to
+ // "1.2.3" since our revision numbers are in 3-parts form (1.2.3).
+ Pattern p = Pattern.compile("((\\d+)(\\.\\d+)?(\\.\\d+)?).*"); //$NON-NLS-1$
+ Matcher m = p.matcher(javav);
+ if (m.matches()) {
+ minJvmVersion = Revision.parseRevision(m.group(1));
+ }
+ sJvmVersion = minJvmVersion;
+ }
+ if (getMinJvmVersion().toRevision().compareTo(sJvmVersion) > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets the full zip of this package.
+ */
+ @NonNull
+ public abstract CompleteType getComplete();
+
+ /**
+ * Sets the full zip of this package.
+ */
+ public void setComplete(@NonNull CompleteType complete) {
+ // Stub
+ }
+
+ /**
+ * Gets the required host bit size (32 or 64), if any
+ */
+ @Nullable
+ public Integer getHostBits() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * Sets the required host bit size (32 or 64), if any.
+ */
+ public void setHostBits(@Nullable Integer bits) {
+ // Stub
+ }
+
+ /**
+ * Gets the required JVM bit size (32 or 64), if any
+ */
+ @Nullable
+ public Integer getJvmBits() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * Sets the required JVM bit size (32 or 64), if any.
+ */
+ public void setJvmBits(@Nullable Integer bits) {
+ // Stub
+ }
+
+ /**
+ * Gets the required host OS ("windows", "linux", "macosx"), if any.
+ */
+ @Nullable
+ public String getHostOs() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * Sets the required host OS ("windows", "linux", "macosx"), if any.
+ */
+ public void setHostOs(@Nullable String os) {
+ // Stub
+ }
+
+ /**
+ * Gets all the version-to-version patches for this {@code Archive}.
+ */
+ @NonNull
+ public List<PatchType> getAllPatches() {
+ return getPatches().getPatch();
+ }
+
+ /**
+ * Gets the {@link PatchesType} for this Archive. Probably only needed internally.
+ */
+ @NonNull
+ protected PatchesType getPatches() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * Sets the {@link PatchesType} for this Archive. Probably only needed internally.
+ */
+ protected void setPatches(@NonNull PatchesType patches) {
+ // Stub
+ }
+
+ /**
+ * Gets the minimum JVM version needed for this {@code Archive}, if any.
+ */
+ @Nullable
+ public RevisionType getMinJvmVersion() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * Sets the minimum JVM version needed for this {@code Archive}, if any.
+ */
+ public void setMinJvmVersion(@Nullable RevisionType revision) {
+ // Stub
+ }
+
+ /**
+ * Create a {@link CommonFactory} corresponding to this instance's {@link
+ * SchemaModule.SchemaModuleVersion}.
+ */
+ @NonNull
+ public abstract CommonFactory createFactory();
+
+ /**
+ * General parent for the actual files referenced in an archive.
+ */
+ public abstract static class ArchiveFile {
+ /**
+ * Gets the checksum for the zip.
+ */
+ @NonNull
+ public abstract String getChecksum();
+
+ /**
+ * Sets the checksum for this zip.
+ */
+ public void setChecksum(@NonNull String checksum) {
+ // Stub
+ }
+
+ /**
+ * Gets the URL to download from.
+ */
+ @NonNull
+ public abstract String getUrl();
+
+ /**
+ * Sets the URL to download from.
+ */
+ public void setUrl(@NonNull String url) {
+ // Stub
+ }
+
+ /**
+ * Gets the size of the zip.
+ */
+ public abstract long getSize();
+
+ /**
+ * Sets the size of the zip;
+ */
+ public void setSize(long size) {
+ // Stub
+ }
+ }
+
+ /**
+ * Parent for xjc-generated classes containing a complete zip of this archive.
+ */
+ @XmlTransient
+ public abstract static class CompleteType extends ArchiveFile {
+
+ }
+
+ /**
+ * A binary diff from a previous package version to this one. TODO: it's too bad that the code
+ * from CompleteType must be duplicated here. Refactor.
+ */
+ @XmlTransient
+ public abstract static class PatchType extends ArchiveFile {
+
+ /**
+ * Gets the source revision for this patch.
+ */
+ @NonNull
+ public abstract RevisionType getBasedOn();
+
+ /**
+ * Sets the source revision for this patch.
+ */
+ protected void setBasedOn(@NonNull RevisionType revision) {
+ // Stub
+ }
+ }
+
+ /**
+ * A list of {@link PatchType}s. Only used internally.
+ */
+ @XmlTransient
+ public abstract static class PatchesType {
+ @NonNull
+ public List<PatchType> getPatch() {
+ // Stub
+ return ImmutableList.of();
+ }
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/CommonFactory.java b/repository/src/main/java/com/android/repository/impl/meta/CommonFactory.java
new file mode 100644
index 0000000..128097b
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/CommonFactory.java
@@ -0,0 +1,137 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.repository.Revision;
+import com.android.repository.api.Channel;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.License;
+import com.android.repository.api.Repository;
+
+import javax.xml.bind.JAXBElement;
+
+/**
+ * Factory for creating the objects used by the repository framework.
+ * Instances of this class can be obtained from any of the objects creatable by this class,
+ * or by {@code RepoManager.getCommonModule().createLatestFactory()}.
+ *
+ * Primarily a superclass for xjc-generated {@code ObjectFactory}s. Most methods shouldn't be
+ * needed outside the repository framework.
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+public abstract class CommonFactory {
+ @NonNull
+ public abstract Repository createRepositoryType();
+
+ @NonNull
+ public abstract Archive createArchiveType();
+
+ @NonNull
+ protected abstract RepoPackageImpl.Archives createArchivesType();
+
+ @NonNull
+ public abstract LocalPackageImpl createLocalPackage();
+
+ @NonNull
+ public abstract RemotePackageImpl createRemotePackage();
+
+ @NonNull
+ protected abstract RevisionType createRevisionType();
+
+ @NonNull
+ public abstract Channel createChannelType();
+
+ @NonNull
+ public abstract JAXBElement<Repository> generateRepository(Repository repo);
+
+ /**
+ * Convenience method to create a {@link Channel} with the given numeric id.
+ */
+ @NonNull
+ public Channel createChannelType(int id) {
+ Channel res = createChannelType();
+ res.setId("channel-" + id);
+ return res;
+ }
+
+ /**
+ * Creates a {@link RevisionType} from the specified {@link Revision}.
+ */
+ @NonNull
+ public RevisionType createRevisionType(Revision revision) {
+ RevisionType rt = createRevisionType();
+ int[] components = revision.toIntArray(true);
+ rt.setMajor(components[0]);
+ if (components.length > 1) {
+ rt.setMinor(components[1]);
+ }
+ if (components.length > 2) {
+ rt.setMicro(components[2]);
+ }
+ if (components.length > 3) {
+ rt.setPreview(components[3]);
+ }
+ return rt;
+ }
+
+ @NonNull
+ public abstract RepoPackageImpl.UsesLicense createLicenseRefType();
+
+ /**
+ * Convenience method to create a license with the given id and value.
+ */
+ @NonNull
+ public License createLicenseType(String value, String id) {
+ License l = createLicenseType();
+ l.setValue(value);
+ l.setId(id);
+ l.setType("text");
+ return l;
+ }
+
+ @NonNull
+ public abstract License createLicenseType();
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ @NonNull
+ public abstract Dependency createDependencyType();
+
+ /**
+ * Creates a {@link Dependency} with the given {@code minRevision} and {@code path}.
+ */
+ @NonNull
+ public Dependency createDependencyType(@Nullable Revision minRevision, @NonNull String path) {
+ Dependency d = createDependencyType();
+ d.setMinRevision(createRevisionType(minRevision));
+ d.setPath(path);
+ return d;
+ }
+
+ @NonNull
+ public abstract RepoPackageImpl.Dependencies createDependenciesType();
+
+ @NonNull
+ public abstract Archive.CompleteType createCompleteType();
+
+ @NonNull
+ protected abstract Archive.PatchType createPatchType();
+
+ public abstract RemotePackageImpl.ChannelRef createChannelRefType();
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/GenericFactory.java b/repository/src/main/java/com/android/repository/impl/meta/GenericFactory.java
new file mode 100644
index 0000000..2262961
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/GenericFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Factory for creating types used by the generic schema.
+ */
+public abstract class GenericFactory {
+
+ @NonNull
+ public abstract TypeDetails.GenericType createGenericDetailsType();
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/LocalPackageImpl.java b/repository/src/main/java/com/android/repository/impl/meta/LocalPackageImpl.java
new file mode 100644
index 0000000..cf88ec9
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/LocalPackageImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+
+import java.io.File;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Implementation of {@link LocalPackage} that can be saved and loaded using JAXB.
+ */
+ at XmlTransient
+public abstract class LocalPackageImpl extends RepoPackageImpl implements LocalPackage {
+ @XmlTransient
+ private File mInstalledPath;
+
+ @Override
+ @NonNull
+ public File getLocation() {
+ return mInstalledPath;
+ }
+
+ @Override
+ public void setInstalledPath(@NonNull File path) {
+ mInstalledPath = path;
+ }
+
+ /**
+ * Creates a {@link LocalPackageImpl} from an arbitrary {@link RepoPackage}. Useful if you
+ * have a {@link RepoPackage} of unknown concrete type and want to marshal it using JAXB.
+ */
+ @NonNull
+ public static LocalPackageImpl create(@NonNull RepoPackage p, @NonNull RepoManager repoManager) {
+ if (p instanceof LocalPackageImpl) {
+ return (LocalPackageImpl)p;
+ }
+ CommonFactory f = (CommonFactory)repoManager.getCommonModule().createLatestFactory();
+ LocalPackageImpl result = f.createLocalPackage();
+ result.setVersion(p.getVersion());
+ result.setLicense(p.getLicense());
+ result.setPath(p.getPath());
+ for (Dependency d : p.getAllDependencies()) {
+ result.addDependency(d);
+ }
+ result.setObsolete(p.obsolete());
+ result.setTypeDetails(p.getTypeDetails());
+ result.setDisplayName(p.getDisplayName());
+ return result;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/RemotePackageImpl.java b/repository/src/main/java/com/android/repository/impl/meta/RemotePackageImpl.java
new file mode 100644
index 0000000..8edde9f
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/RemotePackageImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Channel;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.Repository;
+import com.android.repository.api.RepositorySource;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * An implementation of {@link RemotePackage} that can be created as part of a {@link Repository}
+ * by JAXB.
+ */
+ at XmlTransient
+public abstract class RemotePackageImpl extends RepoPackageImpl implements RemotePackage {
+ @XmlTransient
+ private RepositorySource mSource;
+ @Override
+ public void setSource(@NonNull RepositorySource source) {
+ mSource = source;
+ }
+
+ @Override
+ @Nullable
+ public Archive getArchive() {
+ for (Archive archive : getArchives().getArchive()) {
+ if (archive.isCompatible()) {
+ return archive;
+ }
+ }
+ return null;
+
+ }
+
+ /**
+ * Convenience method to get all the {@link Archive}s included in this package.
+ */
+ @VisibleForTesting
+ @NonNull
+ public List<Archive> getAllArchives() {
+ return getArchives().getArchive();
+ }
+
+ @NonNull
+ protected abstract Archives getArchives();
+
+ @Override
+ @XmlTransient
+ @NonNull
+ public RepositorySource getSource() {
+ assert mSource != null : "Tried to get source before it was initialized!";
+ return mSource;
+ }
+
+ protected abstract ChannelRef getChannelRef();
+
+ @NonNull
+ @Override
+ public Channel getChannel() {
+ return getChannelRef() == null ? Channel.DEFAULT : getChannelRef().getRef();
+ }
+
+ @XmlTransient
+ public abstract static class ChannelRef {
+ @NonNull
+ public abstract Channel getRef();
+
+ public abstract void setRef(@NonNull Channel channel);
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/RepoPackageImpl.java b/repository/src/main/java/com/android/repository/impl/meta/RepoPackageImpl.java
new file mode 100644
index 0000000..7ac7fef
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/RepoPackageImpl.java
@@ -0,0 +1,237 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.Channel;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.License;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoPackage;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * A local or remote {@link RepoPackage}. Primarily a superclass for xjc-generated classes.
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+ at XmlTransient
+public abstract class RepoPackageImpl implements RepoPackage {
+
+ @Override
+ @NonNull
+ @XmlTransient
+ public abstract TypeDetails getTypeDetails();
+
+ @NonNull
+ @Override
+ @XmlTransient
+ public Revision getVersion() {
+ return getRevision().toRevision();
+ }
+
+ @NonNull
+ protected abstract RevisionType getRevision();
+
+ @Override
+ @XmlTransient
+ @NonNull
+ public abstract String getDisplayName();
+
+ @Nullable
+ protected UsesLicense getUsesLicense() {
+ // Stub
+ return null;
+ }
+
+ @Override
+ @XmlTransient
+ @Nullable
+ public License getLicense() {
+ UsesLicense usesLicense = getUsesLicense();
+ if (usesLicense != null) {
+ return (License) usesLicense.getRef();
+ }
+ return null;
+ }
+
+ protected void setUsesLicense(@Nullable UsesLicense license) {
+ // Stub
+ }
+
+ /**
+ * Convenience method to add a reference to the given license to this package.
+ */
+ public void setLicense(@Nullable License l) {
+ UsesLicense ul = null;
+ if (l != null) {
+ ul = createFactory().createLicenseRefType();
+ ul.setRef(l);
+ }
+ setUsesLicense(ul);
+ }
+
+ @Nullable
+ protected Dependencies getDependencies() {
+ // Stub
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public Collection<Dependency> getAllDependencies() {
+ Dependencies dependencies = getDependencies();
+ if (dependencies == null) {
+ return ImmutableList.of();
+ }
+ return ImmutableList.copyOf(dependencies.getDependency());
+ }
+
+
+ @NonNull
+ @Override
+ @XmlTransient
+ public String getPath() {
+ // Stub
+ return "";
+ }
+
+ /**
+ * Convenience method for getting the obsolete status, defaulting {@code null} from the
+ * underlying {@link #isObsolete()} to {@code false}.
+ */
+ @Override
+ public boolean obsolete() {
+ return isObsolete() != null && isObsolete();
+ }
+
+ @Nullable
+ protected Boolean isObsolete() {
+ // Stub
+ return null;
+ }
+
+ // TODO: reevaluate if we want to include other info in comparison
+ @Override
+ public int compareTo(@NonNull RepoPackage o) {
+ int result = ComparisonChain.start()
+ .compare(getPath(), o.getPath())
+ .compare(getVersion(), o.getVersion())
+ .result();
+ if (result != 0) {
+ return result;
+ }
+ if ((this instanceof LocalPackage ^ o instanceof LocalPackage) ||
+ (this instanceof RemotePackage ^ o instanceof RemotePackage)) {
+ return getClass().getName().compareTo(o.getClass().getName());
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof RepoPackage && compareTo((RepoPackage)obj) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return getPath().hashCode() * 37 + getVersion().hashCode();
+ }
+
+ protected void setRevision(@NonNull RevisionType revision) {
+ // Stub
+ }
+
+ /**
+ * Convenience method to set this package's {@link RevisionType} based on a
+ * {@link Revision}.
+ */
+ public void setVersion(@NonNull Revision revision) {
+ setRevision(createFactory().createRevisionType(revision));
+ }
+
+ public void setDependencies(@Nullable Dependencies dependencies) {
+ // Stub
+ }
+
+ public void setTypeDetails(@Nullable TypeDetails details) {
+ // Stub
+ }
+
+ public void setDisplayName(@NonNull String name) {
+ // Stub
+ }
+
+ public void setPath(@NonNull String path) {
+ // Stub
+ }
+
+ public void setObsolete(@Nullable Boolean obsolete) {
+ // Stub
+ }
+
+ /**
+ * Convenience method to add a {@link Dependency} to this package's list of dependencies.
+ */
+ public void addDependency(@NonNull Dependency dep) {
+ Dependencies dependencies = getDependencies();
+ if (dependencies == null) {
+ dependencies = createFactory().createDependenciesType();
+ setDependencies(dependencies);
+ }
+ getDependencies().getDependency().add(dep);
+ }
+
+ /**
+ * List of {@link Archive}s.
+ */
+ public abstract static class Archives {
+ @NonNull
+ public abstract List<Archive> getArchive();
+ }
+
+ /**
+ * Reference to a {@link License}.
+ */
+ @XmlTransient
+ public abstract static class UsesLicense {
+ @NonNull
+ public abstract License getRef();
+
+ public void setRef(@NonNull License ref) {
+ // Stub
+ }
+ }
+
+ /**
+ * List of {@link Dependency}s.
+ */
+ @XmlTransient
+ public abstract static class Dependencies {
+ @NonNull
+ public abstract List<Dependency> getDependency();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/RepositoryPackages.java b/repository/src/main/java/com/android/repository/impl/meta/RepositoryPackages.java
new file mode 100755
index 0000000..25e8088
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/RepositoryPackages.java
@@ -0,0 +1,269 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.UpdatablePackage;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.collect.TreeMultimap;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+
+/**
+ * Store of currently-known local and remote packages, in convenient forms.
+ *
+ * TODO: some of the functionality of this class may no longer be needed. Reevaluate once adoption
+ * has progressed further.
+ */
+ at XmlTransient
+public final class RepositoryPackages {
+
+ /**
+ * All the packages that are locally-installed and have a remotely-available update.
+ */
+ private Set<UpdatablePackage> mUpdatedPkgs = Sets.newTreeSet();
+
+ /**
+ * All the packages that are available remotely and don't have an installed version.
+ */
+ private Set<RemotePackage> mNewPkgs = Sets.newTreeSet();
+
+ /**
+ * When this object was created.
+ */
+ private final long myTimestampMs;
+
+ /**
+ * Multimap from all prefixes of {@code path}s (the unique IDs of packages) to
+ * {@link LocalPackage}s with that path prefix. All packages that are installed or
+ * available will be included.
+ *
+ * For example, if there are packages
+ * {@code foo;bar;baz},
+ * {@code foo;bar;qux}, and
+ * {@code foo;xyzzy},
+ * this map will contain
+ * {@code foo->[Baz package, Qux package, Xyzzy package]},
+ * {@code foo;bar->[Baz package, Qux package]},
+ * {@code foo;bar;baz->[Baz package]},
+ * {@code foo;bar;qux->[Qux package]},
+ * {@code foo;xyzzy->[Xyzzy package]}
+ */
+ private Multimap<String, ? extends LocalPackage> mLocalPackagesByPrefix = TreeMultimap.create();
+
+ /**
+ * Map from {@code path} (the unique ID of a package) to {@link UpdatablePackage}, including all
+ * packages installed or available.
+ */
+ private Map<String, UpdatablePackage> mConsolidatedPkgs = Maps.newTreeMap();
+
+ /**
+ * Map from {@code path} (the unique ID of a package) to {@link LocalPackage}, including all
+ * installed packages.
+ */
+ private Map<String, ? extends LocalPackage> mLocalPackages = Maps.newHashMap();
+
+ /**
+ * Map from {@code path} (the unique ID of a package) to {@link RemotePackage}. There may be
+ * more than one version of the same {@link RemotePackage} available, for example if there is a
+ * stable and a preview version available.
+ */
+ private Map<String, RemotePackage> mRemotePackages = Maps.newTreeMap();
+
+ private final Object mLock = new Object();
+
+ public RepositoryPackages() {
+ myTimestampMs = System.currentTimeMillis();
+ }
+
+ public RepositoryPackages(@NonNull Map<String, LocalPackage> localPkgs,
+ @NonNull Map<String, RemotePackage> remotePkgs) {
+ this();
+ setLocalPkgInfos(localPkgs);
+ setRemotePkgInfos(remotePkgs);
+ }
+
+ /**
+ * Returns the timestamp (in {@link System#currentTimeMillis()} time) when this object was
+ * created.
+ */
+ public long getTimestampMs() {
+ return myTimestampMs;
+ }
+
+ /**
+ * Returns the set of packages that have local updates available.
+ *
+ * @return A non-null, possibly empty Set of update candidates.
+ */
+ @NonNull
+ public Set<UpdatablePackage> getUpdatedPkgs() {
+ Set<UpdatablePackage> result = mUpdatedPkgs;
+ if (result == null) {
+ synchronized (mLock) {
+ computeUpdates();
+ result = mUpdatedPkgs;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the set of new remote packages that are not locally present and that the user could
+ * install.
+ *
+ * @return A non-null, possibly empty Set of new install candidates.
+ */
+ @NonNull
+ public Set<RemotePackage> getNewPkgs() {
+ Set<RemotePackage> result = mNewPkgs;
+ if (result == null) {
+ synchronized (mLock) {
+ computeUpdates();
+ result = mNewPkgs;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a map of package install ids to {@link UpdatablePackage}s representing all known
+ * local and remote packages. Remote packages corresponding to local packages will be
+ * represented by a single item containing both the local and remote info. {@see
+ * IPkgDesc#getInstallId()}
+ */
+ @NonNull
+ public Map<String, UpdatablePackage> getConsolidatedPkgs() {
+ Map<String, UpdatablePackage> result = mConsolidatedPkgs;
+ if (result == null) {
+ synchronized (mLock) {
+ computeUpdates();
+ result = mConsolidatedPkgs;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a map of {@code path} (the unique ID of a package) to {@link LocalPackage}, for all
+ * packages currently installed.
+ */
+ @NonNull
+ public Map<String, ? extends LocalPackage> getLocalPackages() {
+ return mLocalPackages;
+ }
+
+ /**
+ * Returns a {@link Map} from {@code path} (the unique ID of a package) to
+ * {@link RemotePackage}.
+ */
+ @NonNull
+ public Map<String, RemotePackage> getRemotePackages() {
+ return mRemotePackages;
+ }
+
+ @NonNull
+ public Collection<? extends LocalPackage> getLocalPackagesForPrefix(
+ @Nullable String pathPrefix) {
+ return mLocalPackagesByPrefix.get(pathPrefix);
+ }
+
+ /**
+ * Sets the collection of known {@link LocalPackage}s, and recomputes the list of updates and
+ * new packages, if {@link RemotePackage}s have been set.
+ */
+ public void setLocalPkgInfos(@NonNull Map<String, ? extends LocalPackage> packages) {
+ synchronized (mLock) {
+ mLocalPackages = packages;
+ invalidate();
+ computeLocalPackagePrefixes();
+ }
+ }
+
+ /**
+ * Sets the collection of known {@link RemotePackage}s, and recomputes the list of updates and
+ * new packages, if {@link LocalPackage}s have been set.
+ */
+ public void setRemotePkgInfos(@NonNull Map<String, RemotePackage> packages) {
+ synchronized (mLock) {
+ mRemotePackages = packages;
+ invalidate();
+ }
+ }
+
+ private void invalidate() {
+ mConsolidatedPkgs = null;
+ mNewPkgs = null;
+ mUpdatedPkgs = null;
+ }
+
+ private void computeUpdates() {
+ Map<String, UpdatablePackage> newConsolidatedPkgs = Maps.newTreeMap();
+ Set<UpdatablePackage> updates = Sets.newHashSet();
+ for (String path : mLocalPackages.keySet()) {
+ LocalPackage local = mLocalPackages.get(path);
+ UpdatablePackage updatable = new UpdatablePackage(local);
+ newConsolidatedPkgs.put(path, updatable);
+ if (mRemotePackages.containsKey(path)) {
+ updatable.setRemote(mRemotePackages.get(path));
+ if (updatable.isUpdate()) {
+ updates.add(updatable);
+ }
+ }
+ }
+ Set<RemotePackage> news = Sets.newHashSet();
+ for (String path : mRemotePackages.keySet()) {
+ if (!newConsolidatedPkgs.containsKey(path)) {
+ RemotePackage remote = mRemotePackages.get(path);
+ news.add(remote);
+ UpdatablePackage updatable = new UpdatablePackage(remote);
+ newConsolidatedPkgs.put(path, updatable);
+ }
+ }
+ mNewPkgs = news;
+ mUpdatedPkgs = updates;
+ mConsolidatedPkgs = newConsolidatedPkgs;
+ }
+
+ private void computeLocalPackagePrefixes() {
+ Multimap<String, LocalPackage> res = TreeMultimap.create();
+ for (Map.Entry<String, ? extends LocalPackage> entry : mLocalPackages.entrySet()) {
+ String key = entry.getKey();
+ while(true) {
+ res.put(key, entry.getValue());
+ int endIndex = key.lastIndexOf(RepoPackage.PATH_SEPARATOR);
+ if (endIndex < 0) {
+ break;
+ }
+ key = key.substring(0, endIndex);
+ }
+ }
+ mLocalPackagesByPrefix = res;
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/RevisionType.java b/repository/src/main/java/com/android/repository/impl/meta/RevisionType.java
new file mode 100644
index 0000000..4ced016
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/RevisionType.java
@@ -0,0 +1,91 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Superclass for xjc-generated revision class. Probably shouldn't be needed outside the repository
+ * framework: normally {@link Revision} should be used.
+ */
+ at XmlTransient
+public abstract class RevisionType {
+
+ /**
+ * The major component of the revision.
+ */
+ public int getMajor() {
+ // Stub
+ return 0;
+ }
+
+ /**
+ * The minor component of the revision, or null if unspecified.
+ */
+ @Nullable
+ public Integer getMinor() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * The micro component of the revision, or null if unspecified.
+ */
+ @Nullable
+ public Integer getMicro() {
+ // Stub
+ return null;
+ }
+
+ /**
+ * The preview component of the revision, or null if unspecified.
+ * TODO: This segment might need to be more flexible.
+ */
+ @Nullable
+ public Integer getPreview() {
+ // Stub
+ return null;
+ }
+
+ public void setMajor(int major) {
+ // Stub
+ }
+
+ public void setMinor(@Nullable Integer minor) {
+ // Stub
+ }
+
+ public void setMicro(@Nullable Integer micro) {
+ // Stub
+ }
+
+ public void setPreview(@Nullable Integer preview) {
+ // Stub
+ }
+
+ /**
+ * Convenience method to convert this into a {@link Revision}.
+ */
+ @NonNull
+ public Revision toRevision() {
+ return new Revision(getMajor(), getMinor(), getMicro(), getPreview());
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/SchemaModuleUtil.java b/repository/src/main/java/com/android/repository/impl/meta/SchemaModuleUtil.java
new file mode 100644
index 0000000..248beb4
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/SchemaModuleUtil.java
@@ -0,0 +1,383 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.SchemaModule;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.w3c.dom.bootstrap.DOMImplementationRegistry;
+import org.w3c.dom.ls.DOMImplementationLS;
+import org.w3c.dom.ls.LSInput;
+import org.w3c.dom.ls.LSResourceResolver;
+import org.xml.sax.Attributes;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.ValidationEvent;
+import javax.xml.bind.ValidationEventHandler;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+/**
+ * Utilities for working with {@link SchemaModule}s, including marshalling and unmarshalling with
+ * JAXB.
+ */
+public class SchemaModuleUtil {
+
+ private static final Map<String, JAXBContext> CONTEXT_CACHE = Maps.newHashMap();
+
+ private static final Map<List<SchemaModule.SchemaModuleVersion>, Schema> SCHEMA_CACHE = Maps
+ .newHashMap();
+
+ /**
+ * Create an {@link LSResourceResolver} that will use the supplied {@link SchemaModule}s to
+ * find an XSD from its namespace. This must be used when marshalling/unmarshalling if any
+ * {@link SchemaModule}s contain XSDs which import others without specifying a complete
+ * {@code schemaLocation}.
+ */
+ @Nullable
+ public static LSResourceResolver createResourceResolver(
+ @NonNull final Collection<SchemaModule> modules, @NonNull ProgressIndicator progress) {
+
+ DOMImplementationRegistry registry;
+ try {
+ registry = DOMImplementationRegistry.newInstance();
+ } catch (Exception e) {
+ progress.logError("Error during resolver creation: ", e);
+ return null;
+ }
+ final DOMImplementationLS ls = (DOMImplementationLS)registry.getDOMImplementation("LS");
+
+ return new LSResourceResolver() {
+ @Override
+ public LSInput resolveResource(String type, String namespaceURI, String publicId,
+ String systemId, String baseURI) {
+ SchemaModule.SchemaModuleVersion version;
+ for (SchemaModule ext : modules) {
+ version = ext.getNamespaceVersionMap().get(namespaceURI);
+ if (version != null) {
+ LSInput input = ls.createLSInput();
+ input.setSystemId(version.getNamespace());
+ input.setByteStream(version.getXsd());
+ return input;
+ }
+ }
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Creates a {@link JAXBContext} from the XSDs in the given {@link SchemaModule}s.
+ */
+ @NonNull
+ private static JAXBContext getContext(@NonNull Collection<SchemaModule> possibleModules) {
+ List<String> packages = Lists.newArrayList();
+ for (SchemaModule module : possibleModules) {
+ for (SchemaModule.SchemaModuleVersion version : module
+ .getNamespaceVersionMap().values()) {
+ packages.add(version.getObjectFactory().getPackage().getName());
+ }
+ }
+ String key = Joiner.on(":").join(packages);
+ JAXBContext jc = CONTEXT_CACHE.get(key);
+ if (jc == null) {
+ try {
+ jc = JAXBContext.newInstance(key, SchemaModuleUtil.class.getClassLoader());
+ CONTEXT_CACHE.put(key, jc);
+ } catch (JAXBException e1) {
+ assert false : "Failed to create context!\n" + e1.toString();
+ }
+ }
+ return jc;
+ }
+
+ /**
+ * Creates a {@link Schema} from a collection of {@link SchemaModule}s, with a given
+ * {@link LSResourceResolver} (probably obtained from
+ * {@link #createResourceResolver(Collection, ProgressIndicator)}. Any warnings or errors are
+ * logged to the given {@link ProgressIndicator}.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static Schema getSchema(
+ final Collection<SchemaModule> possibleModules,
+ @Nullable final LSResourceResolver resourceResolver, final ProgressIndicator progress) {
+ SchemaFactory sf =
+ SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ if (resourceResolver != null) {
+ sf.setResourceResolver(resourceResolver);
+ }
+ sf.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException exception) throws SAXException {
+ progress.logWarning("Warning while creating schema:", exception);
+ }
+
+ @Override
+ public void error(SAXParseException exception) throws SAXException {
+ progress.logWarning("Error creating schema:", exception);
+
+ }
+
+ @Override
+ public void fatalError(SAXParseException exception) throws SAXException {
+ progress.logWarning("Fatal error creating schema:", exception);
+ }
+ });
+
+ List<StreamSource> sources = Lists.newArrayList();
+ List<SchemaModule.SchemaModuleVersion> key = Lists.newArrayList();
+ for (SchemaModule module : possibleModules) {
+ for (SchemaModule.SchemaModuleVersion version : module
+ .getNamespaceVersionMap()
+ .values()) {
+ key.add(version);
+ sources.add(new StreamSource(version.getXsd()));
+ }
+ }
+
+ Schema schema = SCHEMA_CACHE.get(key);
+ if (schema == null) {
+ try {
+ schema = sf.newSchema(sources.toArray(new StreamSource[sources.size()]));
+ SCHEMA_CACHE.put(key, schema);
+ }
+ catch (SAXException e) {
+ assert false : "Invalid schema found!";
+ }
+ }
+ return schema;
+ }
+
+ /**
+ * Use JAXB to create POJOs from the given XML.
+ *
+ * @param xml The XML to read. The stream will be closed after being read.
+ * @param possibleModules The {@link SchemaModule}s that are available to parse the XML.
+ * @param resourceResolver Resolver for any imported XSDs.
+ * @param progress For logging.
+ * @return The unmarshalled object.
+ * @throws JAXBException if there is an error during unmarshalling.
+ *
+ * TODO: maybe templatize and return a nicer type.
+ */
+ @Nullable
+ public static Object unmarshal(@NonNull InputStream xml,
+ @NonNull Collection<SchemaModule> possibleModules,
+ @Nullable LSResourceResolver resourceResolver, boolean strict,
+ @NonNull ProgressIndicator progress)
+ throws JAXBException {
+ Unmarshaller u = setupUnmarshaller(possibleModules, resourceResolver, strict, progress);
+ SAXSource source = setupSource(xml, possibleModules, strict, progress);
+ return ((JAXBElement) u.unmarshal(source)).getValue();
+ }
+
+ /**
+ * Creates an {@link Unmarshaller} for the given {@link SchemaModule}s.
+ *
+ * @param possibleModules The schemas we should use to unmarshal.
+ * @param resourceResolver Resolver for schema import references.
+ * @param strict Whether we should do strict validation.
+ * @param progress For logging.
+ */
+ @NonNull
+ private static Unmarshaller setupUnmarshaller(@NonNull Collection<SchemaModule> possibleModules,
+ @Nullable LSResourceResolver resourceResolver, boolean strict,
+ @NonNull ProgressIndicator progress) throws JAXBException {
+ JAXBContext context = getContext(possibleModules);
+ Schema schema = getSchema(possibleModules, resourceResolver, progress);
+ Unmarshaller u = context.createUnmarshaller();
+ u.setSchema(schema);
+ u.setEventHandler(createValidationEventHandler(progress, strict));
+ return u;
+ }
+
+ /**
+ * Creates a {@link SAXSource} for the given input.
+ *
+ * @param xml The xml input stream.
+ * @param possibleModules Possible {@link SchemaModule}s that can describe the xml
+ * @param strict Whether we should do strict validation. Specifically in this case if
+ * we should allow falling back to older schema versions if the xml uses
+ * a newer one than we have access to.
+ * @param progress For logging.
+ */
+ @NonNull
+ private static SAXSource setupSource(@NonNull InputStream xml,
+ @NonNull Collection<SchemaModule> possibleModules, boolean strict,
+ @NonNull ProgressIndicator progress) throws JAXBException {
+ SAXSource source = new SAXSource(new InputSource(xml));
+ // Create the XMLFilter
+ XMLFilter filter = new NamespaceFallbackFilter(possibleModules, strict, progress);
+
+ // Set the parent XMLReader on the XMLFilter
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setNamespaceAware(true);
+ XMLReader xr;
+ try {
+ SAXParser sp = spf.newSAXParser();
+ xr = sp.getXMLReader();
+ } catch (ParserConfigurationException e) {
+ // Shouldn't happen
+ progress.logError("Error setting up parser", e);
+ throw new JAXBException(e);
+ } catch (SAXException e) {
+ // Shouldn't happen
+ progress.logError("Error setting up parser", e);
+ throw new JAXBException(e);
+ }
+ filter.setParent(xr);
+ source.setXMLReader(filter);
+ return source;
+ }
+
+ /**
+ * Transform the given {@link JAXBElement} into xml, using JAXB and the schemas provided by the
+ * given {@link SchemaModule}s.
+ */
+ public static void marshal(@NonNull JAXBElement element,
+ @NonNull Collection<SchemaModule> possibleModules,
+ @NonNull OutputStream out, @Nullable LSResourceResolver resourceResolver,
+ @NonNull ProgressIndicator progress) {
+ JAXBContext context = getContext(possibleModules);
+ try {
+ Marshaller marshaller = context.createMarshaller();
+ marshaller.setEventHandler(createValidationEventHandler(progress, true));
+ Schema schema = getSchema(possibleModules, resourceResolver, progress);
+ marshaller.setSchema(schema);
+ marshaller.marshal(element, out);
+ out.close();
+ } catch (JAXBException e) {
+ progress.logWarning("Error during marshal", e);
+ } catch (IOException e) {
+ progress.logWarning("Error during marshal", e);
+ }
+ }
+
+ /**
+ * Creates a {@link ValidationEventHandler} that delegates logging to the given
+ * {@link ProgressIndicator}.
+ */
+ @NonNull
+ private static ValidationEventHandler createValidationEventHandler(
+ @NonNull final ProgressIndicator progress, final boolean strict) {
+ return new ValidationEventHandler() {
+ @Override
+ public boolean handleEvent(ValidationEvent event) {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ if (event.getLinkedException() != null) {
+ progress.logWarning(event.getMessage(), event.getLinkedException());
+ } else {
+ progress.logWarning(event.getMessage());
+ }
+ return !strict;
+ }
+ };
+ }
+
+ /**
+ * {@link XMLFilter} that optionally maps namespaces newer than our latest known ones to
+ * the latest one we understand.
+ * For example, if we have SchemaModuleVersions with namespaces "foo/bar/01" and "foo/bar/02"
+ * and we encounter a document with an element in namespace "foo/bar/03", this filter will
+ * transform the namespace of that element to "foo/bar/02".
+ */
+ private static class NamespaceFallbackFilter extends XMLFilterImpl {
+
+ private Map<String, SchemaModule> mPrefixMap = Maps.newHashMap();
+ private ProgressIndicator mProgress;
+ private boolean mStrict;
+ private Map<String, String> mNewToOldMap = Maps.newHashMap();
+
+ public NamespaceFallbackFilter(
+ @NonNull Collection<SchemaModule> possibleModules, boolean strict,
+ @NonNull ProgressIndicator progress) {
+ for (SchemaModule module : possibleModules) {
+ mPrefixMap.put(module.getNamespacePrefix(), module);
+ }
+ mProgress = progress;
+ mStrict = strict;
+ }
+
+ @Override
+ public void startPrefixMapping(@Nullable String prefix, @Nullable String uri)
+ throws SAXException {
+ if (uri != null) {
+ int lastSlash = uri.lastIndexOf('/') + 1;
+ if (lastSlash > 0) {
+ String namespacePrefix = uri.substring(0, lastSlash);
+ try {
+ int version = Integer.parseInt(uri.substring(lastSlash));
+ SchemaModule module = mPrefixMap.get(namespacePrefix);
+ if (module != null && module.getNamespaceVersionMap().size() < version) {
+ String oldUri = module.getLatestNamespace().intern();
+ mProgress.logWarning("Mapping new ns " + uri + " to old ns " + oldUri);
+ mNewToOldMap.put(uri, oldUri);
+ uri = oldUri;
+ }
+ } catch (NumberFormatException e) {
+ // nothing, just don't do any substitution.
+ }
+ }
+ }
+ super.startPrefixMapping(prefix, uri);
+ }
+
+ @Override
+ public void startElement(@Nullable String uri, @Nullable String localName,
+ @Nullable String qName, @Nullable Attributes atts)
+ throws SAXException {
+ AttributesImpl newAtts = new AttributesImpl(atts);
+ if (!mStrict && uri != null && mNewToOldMap.containsKey(uri)) {
+ uri = mNewToOldMap.get(uri);
+ }
+ super.startElement(uri, localName, qName, newAtts);
+ }
+
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/TrimStringAdapter.java b/repository/src/main/java/com/android/repository/impl/meta/TrimStringAdapter.java
new file mode 100644
index 0000000..340c34a
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/TrimStringAdapter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.repository.impl.meta;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+/**
+ * An {@link XmlAdapter} that removes leading and trailing whitespace, and converts internal strings
+ * of whitespace to a single space. Newlines are also removed, except when it looks like they've
+ * been added to improve readability (specifically, when there's more than one newline in a row. In
+ * other words, blank lines are preserved).
+ */
+public class TrimStringAdapter extends XmlAdapter<String, String> {
+ @Override
+ public String unmarshal(String v) {
+ if (v == null) {
+ return null;
+ }
+ return v.replaceAll("(?<=\\s)[ \t]*", "") // remove spaces and tabs preceded by space,
+ // tab, or newline.
+ .replaceAll("(?<!\n)\n(?!\n)", " ") // replace lone newlines with space
+ .replaceAll(" +", " ") // remove duplicate spaces possibly caused
+ // by previous step
+ .trim(); // remove leading or trailing spaces
+ }
+
+ @Override
+ public String marshal(String s) {
+ return s;
+ }
+}
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/impl/meta/TypeDetails.java b/repository/src/main/java/com/android/repository/impl/meta/TypeDetails.java
new file mode 100644
index 0000000..5b7c322
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/TypeDetails.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.repository.impl.meta;
+
+import com.android.repository.api.RepoPackage;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Abstract superclass for xjc-created JAXB-usable types.
+ * <p>
+ * Each {@link RepoPackage} can optionally contain an instance of a subclass of {@code TypeDetails}.
+ * Each JAXB-usable subclass should itself implement an interface that can be used to access the
+ * actual type information and methods of the concrete subclass.
+ * <p>
+ * Notably this class is used to create {@link JAXBElement}s when marshalling, since it is the one
+ * class that has access to the {@code ObjectFactory} of the relevant extension (namely, the
+ * extension in which it was defined).
+ */
+ at XmlTransient
+public abstract class TypeDetails {
+
+ @XmlTransient
+ public interface GenericType {}
+}
diff --git a/repository/src/main/java/com/android/repository/impl/meta/common-custom.xjb b/repository/src/main/java/com/android/repository/impl/meta/common-custom.xjb
new file mode 100644
index 0000000..46a38ca
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/common-custom.xjb
@@ -0,0 +1,92 @@
+<!--
+ ~ 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.
+ -->
+
+<!--
+ Customizations for repo-common. Specified here rather than inline since xjc doesn't like
+ customizations on imported schemas.
+-->
+<bindings version="2.1"
+ xmlns="http://java.sun.com/xml/ns/jaxb"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ extensionBindingPrefixes="plugin">
+ <bindings schemaLocation="../../api/repo-common-01.xsd" node="/xsd:schema">
+ <!-- unfortunately we can't use scd refs with custom plugins: JAXB-1047 -->
+ <bindings node="//xsd:complexType[@name='channelRefType']">
+ <bindings node="xsd:attribute[@name='ref']">
+ <property>
+ <baseType name="com.android.repository.impl.generated.v1.ChannelType"/>
+ </property>
+ </bindings>
+ <plugin:super name="com.android.repository.impl.meta.RemotePackageImpl$ChannelRef"/>
+
+ </bindings>
+ <bindings node="//xsd:complexType[@name='licenseRefType']">
+ <bindings node="xsd:attribute[@name='ref']">
+ <property>
+ <baseType name="com.android.repository.impl.generated.v1.LicenseType"/>
+ </property>
+ </bindings>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='channelType']">
+ <plugin:super name="com.android.repository.api.Channel"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='repositoryType']">
+ <plugin:super name="com.android.repository.api.Repository"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='localPackage']">
+ <plugin:super name="com.android.repository.impl.meta.LocalPackageImpl"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='dependenciesType']">
+ <plugin:super name="com.android.repository.impl.meta.RepoPackageImpl$Dependencies"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='remotePackage']">
+ <plugin:super name="com.android.repository.impl.meta.RemotePackageImpl"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='archivesType']">
+ <plugin:super name="com.android.repository.impl.meta.RepoPackageImpl$Archives"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='licenseRefType']">
+ <plugin:super name="com.android.repository.impl.meta.RepoPackageImpl$UsesLicense"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='typeDetails']">
+ <plugin:super name="com.android.repository.impl.meta.TypeDetails"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='dependencyType']">
+ <plugin:super name="com.android.repository.api.Dependency"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='licenseType']">
+ <plugin:super name="com.android.repository.api.License"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='archiveType']">
+ <plugin:super name="com.android.repository.impl.meta.Archive"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='patchesType']">
+ <plugin:super name="com.android.repository.impl.meta.Archive$PatchesType"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='completeType']">
+ <plugin:super name="com.android.repository.impl.meta.Archive$CompleteType"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='patchType']">
+ <plugin:super name="com.android.repository.impl.meta.Archive$PatchType"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='revisionType']">
+ <plugin:super name="com.android.repository.impl.meta.RevisionType"/>
+ </bindings>
+
+ </bindings>
+</bindings>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/impl/meta/generic-custom.xjb b/repository/src/main/java/com/android/repository/impl/meta/generic-custom.xjb
new file mode 100644
index 0000000..b69bde0
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/generic-custom.xjb
@@ -0,0 +1,32 @@
+<!--
+ ~ 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.
+ -->
+
+<!--
+ Customizations for generic.xsd. Specified here rather than inline since xjc doesn't like
+ customizations on imported schemas.
+-->
+<bindings version="2.1"
+ xmlns="http://java.sun.com/xml/ns/jaxb"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ extensionBindingPrefixes="plugin">
+ <bindings schemaLocation="../../api/generic-01.xsd" node="/xsd:schema">
+ <bindings node="//xsd:complexType[@name='genericDetailsType']">
+ <plugin:super name="com.android.repository.impl.meta.TypeDetails$GenericType"/>
+ </bindings>
+ </bindings>
+</bindings>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/impl/meta/package-info.java b/repository/src/main/java/com/android/repository/impl/meta/package-info.java
new file mode 100644
index 0000000..922aa66
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/meta/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+package com.android.repository.impl.meta;
diff --git a/repository/src/main/java/com/android/repository/impl/sources/LocalSourceProvider.java b/repository/src/main/java/com/android/repository/impl/sources/LocalSourceProvider.java
new file mode 100644
index 0000000..2e1694b
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/LocalSourceProvider.java
@@ -0,0 +1,268 @@
+/*
+ * 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.repository.impl.sources;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.RepositorySourceProvider;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.api.SettingsController;
+import com.android.repository.api.SimpleRepositorySource;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.impl.FileOpImpl;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Reads {@link RepositorySource}s saved locally. Allows sources to be saved, modified, and
+ * deleted.
+ */
+public class LocalSourceProvider implements RepositorySourceProvider {
+
+ private static final String KEY_COUNT = "count";
+
+ private static final String KEY_SRC = "src";
+
+ private static final String KEY_DISPLAY = "disp";
+
+ private static final String KEY_ENABLED = "enabled";
+
+ private final File mLocation;
+
+ private List<RepositorySource> mSources;
+
+ private static final Object LOCK = new Object();
+
+ private final Collection<SchemaModule> mAllowedModules;
+
+ private final FileOp mFop;
+
+ private RepoManager mRepoManager;
+
+ /**
+ * Create a new {@code LocalSourceProvider}.
+ *
+ * @param location The file to load from and save to.
+ * @param allowedModules The {@link SchemaModule}s that are allowed to be used by sources
+ * provided by this provider.
+ * @param fop The {@link FileOp} to use for local file operations. For normal use
+ * should probably be {@link FileOpImpl}.
+ */
+ public LocalSourceProvider(@NonNull File location,
+ @NonNull Collection<SchemaModule> allowedModules, @NonNull FileOp fop) {
+ mAllowedModules = allowedModules;
+ mLocation = location;
+ mFop = fop;
+ }
+
+ /**
+ * Sets the {@link RepoManager} that will use this provider.
+ */
+ public void setRepoManager(@NonNull RepoManager manager) {
+ mRepoManager = manager;
+ }
+
+ /**
+ * Load the source definitions (name, url, and enabled state) from {@link #mLocation}.
+ */
+ private void loadUserAddons(@NonNull ProgressIndicator progress) {
+ // mRepoManager isn't actually used in this method, but we assert here to fail fast in
+ // case someone forgets to set it. Otherwise we'll only fail if someone adds or removes
+ // a source.
+ assert mRepoManager != null;
+
+ // Copied from SdkSources.
+ // Implementation detail: synchronize on the sources list to make sure that
+ // a- the source list doesn't change while we load/save it, and most important
+ // b- to make sure it's not being saved while loaded or the reverse.
+ // In most cases we do these operation from the UI thread so it's not really
+ // that necessary. This is more a protection in case of someone calls this
+ // from a worker thread by mistake.
+ synchronized (LOCK) {
+ List<RepositorySource> result = Lists.newArrayList();
+
+ // Load new user sources from property file
+ InputStream fis = null;
+ try {
+ if (mFop.exists(mLocation)) {
+ fis = mFop.newFileInputStream(mLocation);
+
+ Properties props = new Properties();
+ props.load(fis);
+
+ int count = Integer.parseInt(props.getProperty(KEY_COUNT, "0"));
+
+ for (int i = 0; i < count; i++) {
+ String url = props.getProperty(String.format("%s%02d", KEY_SRC, i));
+ String disp = props.getProperty(String.format("%s%02d", KEY_DISPLAY, i));
+ String enabledStr = props
+ .getProperty(String.format("%s%02d", KEY_ENABLED, i));
+ boolean enabled;
+ if (enabledStr == null) {
+ // for backward compatibility
+ enabled = true;
+ } else {
+ enabled = Boolean.parseBoolean(enabledStr);
+ }
+ if (url != null) {
+ result.add(new SimpleRepositorySource(url, disp, enabled,
+ mAllowedModules, this));
+ }
+ }
+ } else {
+ progress.logWarning(
+ "File " + mLocation.getAbsolutePath() + " could not be loaded.");
+ }
+ } catch (NumberFormatException e) {
+ progress.logWarning("Failed to parse user addon file at " + mLocation, e);
+ } catch (IOException e) {
+ progress.logWarning("Failed to parse user addon file at " + mLocation, e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ // nothing
+ }
+ }
+ }
+ if (mSources == null) {
+ mSources = Lists.newArrayList(result);
+ } else {
+ mSources.clear();
+ mSources.addAll(result);
+ }
+
+ }
+ }
+
+ /**
+ * Gets the {@link RepositorySource}s from this provider.
+ *
+ * @param downloader Unused by this provider.
+ * @param settings Unused by this provider.
+ * @param logger A {@link ProgressIndicator} to be used for showing progress and logging.
+ * @param forceRefresh If true, this provider should refresh its list of sources, rather than
+ * return any cached sources.
+ */
+ @NonNull
+ @Override
+ public List<RepositorySource> getSources(@Nullable Downloader downloader,
+ @Nullable SettingsController settings, @NonNull ProgressIndicator logger,
+ boolean forceRefresh) {
+ synchronized (LOCK) {
+ if (mSources == null || forceRefresh) {
+ loadUserAddons(logger);
+ }
+ }
+ return mSources;
+ }
+
+ /**
+ * Add a source to this provider. Note that it won't be persisted until {@link
+ * #save(ProgressIndicator)} is called.
+ *
+ * @param source The source to add.
+ */
+ @Override
+ public boolean addSource(@NonNull RepositorySource source) {
+ boolean result = mSources.add(source);
+ mRepoManager.markInvalid();
+ return result;
+ }
+
+ /**
+ * Whether this source is modifiable.
+ *
+ * @return {@code true} for this class.
+ */
+ @Override
+ public boolean isModifiable() {
+ return true;
+ }
+
+ /**
+ * Saves any changes in the sources to the specified file.
+ *
+ * @param progress {@link ProgressIndicator} for logging.
+ */
+ @Override
+ public void save(@NonNull ProgressIndicator progress) {
+ synchronized (LOCK) {
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(mLocation);
+
+ Properties props = new Properties();
+
+ int count = 0;
+
+ for (RepositorySource s : mSources) {
+ props.setProperty(String.format("%s%02d", KEY_SRC, count), //$NON-NLS-1$
+ s.getUrl());
+ if (s.getDisplayName() != null) {
+ props.setProperty(String.format("%s%02d", KEY_DISPLAY, count), //$NON-NLS-1$
+ s.getDisplayName());
+ }
+ props.setProperty(String.format("%s%02d", KEY_ENABLED, count),
+ Boolean.toString(s.isEnabled()));
+ count++;
+ }
+ props.setProperty(KEY_COUNT, Integer.toString(count));
+
+ props.store(fos, "## User Sources for Android Repository"); //$NON-NLS-1$
+
+ } catch (IOException e) {
+ progress.logWarning("failed to save sites", e);
+
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ // nothing
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the specified source from this provider. Node that the remove won't be persisted until
+ * {@link #save(ProgressIndicator)} is called.
+ *
+ * @param source The source to remove.
+ * @return {@code true} if a matching source was found and actually removed.
+ */
+ @Override
+ public boolean removeSource(@NonNull RepositorySource source) {
+ boolean result = mSources.remove(source);
+ mRepoManager.markInvalid();
+ return result;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/sources/RemoteListSourceProviderImpl.java b/repository/src/main/java/com/android/repository/impl/sources/RemoteListSourceProviderImpl.java
new file mode 100644
index 0000000..5a839fd
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/RemoteListSourceProviderImpl.java
@@ -0,0 +1,269 @@
+/*
+ * 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.repository.impl.sources;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemoteListSourceProvider;
+import com.android.repository.api.RemoteSource;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.RepositorySourceProvider;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.api.SettingsController;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.w3c.dom.ls.LSResourceResolver;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * A {@link RepositorySourceProvider} that downloads a list of sources.
+ */
+public class RemoteListSourceProviderImpl extends RemoteListSourceProvider {
+
+ /**
+ * The {@link SchemaModule} specifying the core schema for the downloaded list of sources. This
+ * may be extended by the {@link SchemaModule} specified in the constructor.
+ */
+ private static SchemaModule sAddonListModule = new SchemaModule(
+ RemoteListSourceProviderImpl.class.getPackage().getName()
+ + ".generated.v%d.ObjectFactory", "repo-sites-common-%d.xsd",
+ RepoManager.class);
+
+ /**
+ * The URL to download from.
+ */
+ private final String mUrl;
+
+ /**
+ * The {@link SchemaModule}s that are allowed to be used, depending on the type of the source.
+ */
+ private final Map<Class<? extends RepositorySource>, Collection<SchemaModule>>
+ mAllowedModules;
+
+ /**
+ * An extension to the core {@link SchemaModule}.
+ */
+ private final SchemaModule mSourceListModule;
+
+ /**
+ * Cached list of source.
+ */
+ private List<RepositorySource> mSources;
+
+ /**
+ * Create a {@code RemoteListSourceProviderImpl}
+ *
+ * @param url The URL to download from
+ * @param sourceListModule Extension to the common source list schema, if any.
+ * @param permittedSchemaModules Map of concrete {@link RepositorySource} type, as defined in
+ * {@code sourceListModule}, to collection of {@link
+ * SchemaModule}s allowed to be used by that source type.
+ * @throws URISyntaxException If {@code url} can't be parsed into a URL.
+ */
+ public RemoteListSourceProviderImpl(@NonNull String url, @Nullable SchemaModule sourceListModule,
+ @NonNull Map<Class<? extends RepositorySource>,
+ Collection<SchemaModule>> permittedSchemaModules)
+ throws URISyntaxException {
+ mUrl = url;
+ mAllowedModules = permittedSchemaModules;
+ mSourceListModule = sourceListModule;
+ }
+
+ /**
+ * Gets the sources from this provider.
+ *
+ * @param downloader The {@link Downloader} to use to download the source list.
+ * Required.
+ * @param settingsController The {@link SettingsController} to use for the download settings.
+ * Required if required by the downloader.
+ * @param progress {@link ProgressIndicator} for logging.
+ * @param forceRefresh If true, this provider should refresh its list of sources, rather
+ * than return cached ones.
+ * @return The fetched {@link RepositorySource}s.
+ */
+ @NonNull
+ @Override
+ public List<RepositorySource> getSources(@Nullable Downloader downloader,
+ @Nullable SettingsController settingsController, @NonNull ProgressIndicator progress,
+ boolean forceRefresh) {
+ if (downloader == null) {
+ throw new IllegalArgumentException("downloader must not be null");
+ }
+ if (mSources != null && !forceRefresh) {
+ return mSources;
+ }
+
+ List<RepositorySource> result;
+ InputStream xml = null;
+
+ URL url = null;
+ SchemaModule sourceModule = mSourceListModule == null ? sAddonListModule
+ : mSourceListModule;
+
+ for (int version = sourceModule.getNamespaceVersionMap().size();
+ xml == null && version > 0; version--) {
+ String urlStr = String.format(mUrl, version);
+ try {
+ url = new URL(urlStr);
+ xml = downloader.downloadAndStream(url, settingsController, progress);
+ } catch (FileNotFoundException expected) {
+ // do nothing
+ } catch (UnknownHostException e) {
+ progress.logWarning("Failed to connect to host: " + urlStr);
+ } catch (MalformedURLException e) {
+ progress.logWarning("Invalid URL: " + urlStr);
+ } catch (IOException e) {
+ progress.logInfo("IOException: " + urlStr);
+ progress.logInfo(e.toString());
+ }
+ }
+
+ if (xml != null) {
+ result = parse(xml, progress, url);
+ mSources = result;
+ return mSources;
+ } else {
+ progress.logWarning("Failed to download any source lists!");
+ }
+
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ private List<RepositorySource> parse(@NonNull InputStream xml,
+ @NonNull ProgressIndicator progress, @NonNull URL url) {
+ List<SchemaModule> schemas = Lists.newArrayList(sAddonListModule);
+ if (mSourceListModule != null) {
+ schemas.add(mSourceListModule);
+ }
+ LSResourceResolver resourceResolver = SchemaModuleUtil
+ .createResourceResolver(schemas, progress);
+ SiteList sl = null;
+ try {
+ sl = (SiteList) SchemaModuleUtil
+ .unmarshal(xml, schemas, resourceResolver, true, progress);
+ } catch (JAXBException e) {
+ progress.logWarning("Failed to parse source list at " + url);
+ }
+ List<RepositorySource> result = Lists.newArrayList();
+ if (sl != null) {
+ for (RemoteSource s : sl.getSite()) {
+ for (Class<? extends RepositorySource> c : mAllowedModules.keySet()) {
+ if (c.isInstance(s)) {
+ s.setPermittedSchemaModules(mAllowedModules.get(c));
+ }
+ }
+ String urlStr = s.getUrl();
+ try {
+ URL fullUrl = new URL(url, urlStr);
+ s.setUrl(fullUrl.toExternalForm());
+ } catch (MalformedURLException e) {
+ progress.logWarning("Failed to parse URL in remote source list", e);
+ }
+ s.setProvider(this);
+ result.add(s);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Not supported by this provider.
+ *
+ * @return {@code false}.
+ */
+ @Override
+ public boolean addSource(@NonNull RepositorySource source) {
+ throw new UnsupportedOperationException("Can't add to RemoteListSourceProvider");
+ }
+
+ /**
+ * Not supported by this provider.
+ *
+ * @return {@code false}.
+ */
+ @Override
+ public boolean isModifiable() {
+ return false;
+ }
+
+ /**
+ * Not supported by this provider.
+ */
+ @Override
+ public void save(@NonNull ProgressIndicator progress) {
+ // Nothing, not modifiable.
+ }
+
+ /**
+ * Not supported by this provider.
+ *
+ * @return {@code false}.
+ */
+ @Override
+ public boolean removeSource(@NonNull RepositorySource source) {
+ throw new UnsupportedOperationException("Can't add to RemoteListSourceProvider");
+ }
+
+ /**
+ * Superclass for xjc-created JAXB-usable classes into which a site list can be unmarshalled.
+ */
+ @XmlTransient
+ public static class SiteList {
+
+ /**
+ * Gets the parsed list of {@link RemoteSource}s.
+ */
+ @NonNull
+ public List<RemoteSource> getSite() {
+ // Stub. Implementation provided to fall back to older versions.
+ //noinspection unchecked
+ return (List) getAddonSiteOrSysImgSite();
+ }
+
+ @NonNull
+ protected List<Object> getAddonSiteOrSysImgSite() {
+ // Stub. for backward compatibility only.
+ //noinspection unchecked
+ return (List) getAddonSite();
+ }
+
+ @NonNull
+ protected List<RemoteSource> getAddonSite() {
+ // Stub. for backward compatibility only
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/impl/sources/generated/v1/GenericSiteType.java b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/GenericSiteType.java
new file mode 100644
index 0000000..d66b4ab
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/GenericSiteType.java
@@ -0,0 +1,49 @@
+
+package com.android.repository.impl.sources.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-sites-common-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A trivial implementation of siteType.
+ *
+ *
+ * <p>Java class for genericSiteType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="genericSiteType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/repository/android/sites-common/1}siteType">
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "genericSiteType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class GenericSiteType
+ extends SiteType
+ implements com.android.repository.api.RemoteListSourceProvider.GenericSite
+{
+
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/sources/generated/v1/ObjectFactory.java b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/ObjectFactory.java
new file mode 100644
index 0000000..67c718b
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/ObjectFactory.java
@@ -0,0 +1,70 @@
+
+package com.android.repository.impl.sources.generated.v1;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-sites-common-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.repository.impl.sources.generated.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory {
+
+ private final static QName _SiteList_QNAME = new QName("http://schemas.android.com/repository/android/sites-common/1", "site-list");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.repository.impl.sources.generated.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link SiteListType }
+ *
+ */
+ public com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList createSiteListType() {
+ return new SiteListType();
+ }
+
+ /**
+ * Create an instance of {@link GenericSiteType }
+ *
+ */
+ public GenericSiteType createGenericSiteType() {
+ return new GenericSiteType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link SiteListType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/repository/android/sites-common/1", name = "site-list")
+ public JAXBElement<SiteListType> createSiteList(SiteListType value) {
+ return new JAXBElement<SiteListType>(_SiteList_QNAME, SiteListType.class, null, value);
+ }
+
+ public JAXBElement<com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList> generateElement(com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList value) {
+ return ((JAXBElement) createSiteList(((SiteListType) value)));
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/sources/generated/v1/SiteListType.java b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/SiteListType.java
new file mode 100644
index 0000000..5d2d180
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/SiteListType.java
@@ -0,0 +1,90 @@
+
+package com.android.repository.impl.sources.generated.v1;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.api.RemoteSource;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-sites-common-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A simple list of add-ons site.
+ *
+ *
+ * <p>Java class for siteListType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="siteListType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <choice maxOccurs="unbounded" minOccurs="0">
+ * <element name="site" type="{http://schemas.android.com/repository/android/sites-common/1}siteType"/>
+ * </choice>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "siteListType", propOrder = {
+ "site"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class SiteListType
+ extends com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList
+{
+
+ protected List<SiteType> site;
+
+ /**
+ * Gets the value of the site property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the site property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getSite().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link SiteType }
+ *
+ *
+ */
+ public List<SiteType> getSiteInternal() {
+ if (site == null) {
+ site = new ArrayList<SiteType>();
+ }
+ return this.site;
+ }
+
+ public List<RemoteSource> getSite() {
+ return ((List) getSiteInternal());
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/sources/generated/v1/SiteType.java b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/SiteType.java
new file mode 100644
index 0000000..94fd281
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/SiteType.java
@@ -0,0 +1,115 @@
+
+package com.android.repository.impl.sources.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.api.RemoteSource;
+import com.android.repository.impl.meta.TrimStringAdapter;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from repo-sites-common-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * An abstract Site, containing a user-friendly name and URL.
+ *
+ *
+ * <p>Java class for siteType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="siteType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="url" type="{http://www.w3.org/2001/XMLSchema}token"/>
+ * <element name="displayName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </all>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "siteType", propOrder = {
+
+})
+ at XmlSeeAlso({
+ GenericSiteType.class
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public abstract class SiteType
+ extends RemoteSource
+{
+
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlSchemaType(name = "token")
+ protected String url;
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(TrimStringAdapter.class)
+ protected String displayName;
+
+ /**
+ * Gets the value of the url property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Sets the value of the url property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setUrl(String value) {
+ this.url = value;
+ }
+
+ /**
+ * Gets the value of the displayName property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * Sets the value of the displayName property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setDisplayName(String value) {
+ this.displayName = value;
+ }
+
+}
diff --git a/repository/src/main/java/com/android/repository/impl/sources/generated/v1/package-info.java b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/package-info.java
new file mode 100644
index 0000000..e4d79b6
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/generated/v1/package-info.java
@@ -0,0 +1,2 @@
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/repository/android/sites-common/1")
+package com.android.repository.impl.sources.generated.v1;
diff --git a/repository/src/main/java/com/android/repository/impl/sources/repo-sites-common-custom.xjb b/repository/src/main/java/com/android/repository/impl/sources/repo-sites-common-custom.xjb
new file mode 100644
index 0000000..8d52a3d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/impl/sources/repo-sites-common-custom.xjb
@@ -0,0 +1,39 @@
+<!--
+ ~ 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.
+ -->
+
+<!--
+ Customizations for repo-sites-common. Specified here rather than inline since xjc doesn't like
+ customizations on imported schemas.
+-->
+<bindings version="2.1"
+ xmlns="http://java.sun.com/xml/ns/jaxb"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ extensionBindingPrefixes="plugin">
+ <bindings schemaLocation="../../api/repo-sites-common-1.xsd" node="/xsd:schema">
+ <!-- unfortunately we can't use scd refs with custom plugins: JAXB-1047 -->
+ <bindings node="//xsd:complexType[@name='siteListType']">
+ <plugin:super name="com.android.repository.impl.sources.RemoteListSourceProviderImpl$SiteList"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='siteType']">
+ <plugin:super name="com.android.repository.api.RemoteSource"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='genericSiteType']">
+ <plugin:super name="com.android.repository.api.RemoteListSourceProvider$GenericSite"/>
+ </bindings>
+ </bindings>
+</bindings>
\ No newline at end of file
diff --git a/repository/src/main/java/com/android/repository/io/FileOp.java b/repository/src/main/java/com/android/repository/io/FileOp.java
new file mode 100644
index 0000000..144af2d
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/io/FileOp.java
@@ -0,0 +1,212 @@
+/*
+ * 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.repository.io;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+
+/**
+ * Wraps some common {@link File} operations on files and folders.
+ * <p/>
+ * This makes it possible to override/mock/stub some file operations in unit tests.
+ */
+public interface FileOp {
+
+ File[] EMPTY_FILE_ARRAY = new File[0];
+
+ /**
+ * Helper to delete a file or a directory.
+ * For a directory, recursively deletes all of its content.
+ * Files that cannot be deleted right away are marked for deletion on exit.
+ * It's ok for the file or folder to not exist at all.
+ */
+ void deleteFileOrFolder(@NonNull File fileOrFolder);
+
+ /**
+ * Sets the executable Unix permission (+x) on a file or folder.
+ * <p/>
+ * This attempts to use File#setExecutable through reflection if
+ * it's available.
+ * If this is not available, this invokes a chmod exec instead,
+ * so there is no guarantee of it being fast.
+ * <p/>
+ * Caller must make sure to not invoke this under Windows.
+ *
+ * @param file The file to set permissions on.
+ * @throws IOException If an I/O error occurs
+ */
+ void setExecutablePermission(@NonNull File file) throws IOException;
+
+ /**
+ * Sets the file or directory as read-only.
+ *
+ * @param file The file or directory to set permissions on.
+ */
+ void setReadOnly(@NonNull File file);
+
+ /**
+ * Copies a binary file.
+ *
+ * @param source the source file to copy.
+ * @param dest the destination file to write.
+ * @throws FileNotFoundException if the source file doesn't exist.
+ * @throws IOException if there's a problem reading or writing the file.
+ */
+ void copyFile(@NonNull File source, @NonNull File dest) throws IOException;
+
+ /**
+ * Checks whether 2 binary files are the same.
+ *
+ * @param file1 the source file to copy
+ * @param file2 the destination file to write
+ * @throws FileNotFoundException if the source files don't exist.
+ * @throws IOException if there's a problem reading the files.
+ */
+ boolean isSameFile(@NonNull File file1, @NonNull File file2)
+ throws IOException;
+
+ /** Invokes {@link File#exists()} on the given {@code file}. */
+ boolean exists(@NonNull File file);
+
+ /** Invokes {@link File#isFile()} on the given {@code file}. */
+ boolean isFile(@NonNull File file);
+
+ /** Invokes {@link File#isDirectory()} on the given {@code file}. */
+ boolean isDirectory(@NonNull File file);
+
+ /** Invokes {@link File#canWrite()} on the given {@code file}. */
+ boolean canWrite(@NonNull File file);
+
+ /** Invokes {@link File#length()} on the given {@code file}. */
+ long length(@NonNull File file);
+
+ /**
+ * Invokes {@link File#delete()} on the given {@code file}.
+ * Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
+ */
+ boolean delete(@NonNull File file);
+
+ /** Invokes {@link File#mkdirs()} on the given {@code file}. */
+ boolean mkdirs(@NonNull File file);
+
+ /**
+ * Invokes {@link File#listFiles()} on the given {@code file}.
+ * Contrary to the Java API, this returns an empty array instead of null when the
+ * directory does not exist.
+ */
+ @NonNull
+ File[] listFiles(@NonNull File file);
+
+ /** Invokes {@link File#renameTo(File)} on the given files. */
+ boolean renameTo(@NonNull File oldDir, @NonNull File newDir);
+
+ /** Creates a new {@link OutputStream} for the given {@code file}. */
+ @NonNull
+ OutputStream newFileOutputStream(@NonNull File file)
+ throws FileNotFoundException;
+
+ /** Creates a new {@link InputStream} for the given {@code file}. */
+ @NonNull
+ InputStream newFileInputStream(@NonNull File file)
+ throws FileNotFoundException;
+
+ /**
+ * Load {@link Properties} from a file. Returns an empty property set on error.
+ *
+ * @param file A non-null file to load from. File may not exist.
+ * @return A new {@link Properties} with the properties loaded from the file,
+ * or an empty property set in case of error.
+ */
+ @NonNull
+ Properties loadProperties(@NonNull File file);
+
+ /**
+ * Saves (write, store) the given {@link Properties} into the given {@link File}.
+ *
+ * @param file A non-null file to write to.
+ * @param props The properties to write.
+ * @param comments A non-null description of the properly list, written in the file.
+ * @throws IOException if the write operation failed.
+ */
+ void saveProperties(
+ @NonNull File file,
+ @NonNull Properties props,
+ @NonNull String comments) throws IOException;
+
+ /**
+ * Returns the lastModified attribute of the file.
+ *
+ * @see File#lastModified()
+ * @param file The non-null file of which to retrieve the lastModified attribute.
+ * @return The last-modified attribute of the file, in milliseconds since The Epoch.
+ */
+ long lastModified(@NonNull File file);
+
+ /**
+ * Creates a new file. See {@link File#createNewFile()}.
+ */
+ boolean createNewFile(@NonNull File file) throws IOException;
+
+ /**
+ * Returns {@code true} if we're on windows, {@code false} otherwise.
+ */
+ boolean isWindows();
+
+ /**
+ * @see File#canExecute()
+ */
+ boolean canExecute(@NonNull File file);
+
+ /**
+ * If {@code in} is an in-memory file, write it out as a proper file and return it.
+ * Otherwise just return {@code in}.
+ */
+ File ensureRealFile(@NonNull File in) throws IOException;
+
+ /**
+ * @see com.google.common.io.Files#toString(File, Charset)
+ */
+ @NonNull
+ String toString(@NonNull File f, @NonNull Charset c) throws IOException;
+
+ /**
+ * @see File#list(FilenameFilter)
+ */
+ @Nullable
+ String[] list(@NonNull File folder, @Nullable FilenameFilter filenameFilter);
+
+ /**
+ * @see File#listFiles(FilenameFilter)
+ */
+ @Nullable
+ File[] listFiles(@NonNull File folder, @Nullable FilenameFilter filenameFilter);
+
+ /**
+ * @see File#deleteOnExit()
+ */
+ void deleteOnExit(File file);
+}
diff --git a/repository/src/main/java/com/android/repository/io/FileOpUtils.java b/repository/src/main/java/com/android/repository/io/FileOpUtils.java
new file mode 100644
index 0000000..cb9c654
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/io/FileOpUtils.java
@@ -0,0 +1,282 @@
+/*
+ * 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.repository.io;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.io.impl.FileOpImpl;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+/**
+ * Some convenience methods for working with {@link File}s/{@link FileOp}s.
+ */
+public final class FileOpUtils {
+
+ /**
+ * The standard way to create a {@link FileOp} that interacts with the real filesystem.
+ */
+ @NonNull
+ public static FileOp create() {
+ return new FileOpImpl();
+ }
+
+ /**
+ * Copies a file or directory tree to the given location. {@code dest} should not exist: with
+ * the file system currently looking like
+ * <pre>
+ * {@code
+ * /
+ * dir1/
+ * a.txt
+ * dir2/
+ * }
+ * </pre>
+ * Running {@code recursiveCopy(new File("/dir1"), new File("/dir2"), fOp)} will result in an
+ * exception, while {@code recursiveCopy(new File("/dir1"), new File("/dir2/foo")} will result
+ * in
+ * <pre>
+ * {@code
+ * /
+ * dir1/
+ * a.txt
+ * dir2/
+ * foo/
+ * a.txt
+ * }
+ * </pre>
+ * This is equivalent to the behavior of {@code cp -r} when the target does not exist.
+ *
+ * @param src File to copy
+ * @param dest Destination.
+ * @param fop The FileOp to use for file operations.
+ * @throws IOException If the destination already exists, or if there is a problem copying the
+ * files or creating directories.
+ */
+ public static void recursiveCopy(@NonNull File src, @NonNull File dest, @NonNull FileOp fop)
+ throws IOException {
+ if (fop.exists(dest)) {
+ throw new IOException(dest + " already exists!");
+ }
+ if (fop.isDirectory(src)) {
+ fop.mkdirs(dest);
+
+ File[] children = fop.listFiles(src);
+ for (File child : children) {
+ File newDest = new File(dest, child.getName());
+ recursiveCopy(child, newDest, fop);
+ }
+ } else if (fop.isFile(src)) {
+ fop.copyFile(src, dest);
+ if (!fop.isWindows() && fop.canExecute(src)) {
+ fop.setExecutablePermission(dest);
+ }
+ }
+ }
+
+ /**
+ * Moves a file or directory from one location to another. If the destination exists, it is
+ * moved away, and once the operation has completed successfully, deleted. If there is a problem
+ * during the copy, the original files are moved back into place.
+ *
+ * @param src File to move
+ * @param dest Destination. Follows the same rules as {@link #recursiveCopy(File, File,
+ * FileOp)}}.
+ * @param fop The FileOp to use for file operations.
+ * @param progress Currently only used for error logging.
+ * @throws IOException If some problem occurs during copies or directory creation.
+ */
+ // TODO: Seems strange to use the more-fully-featured ProgressIndicator here.
+ // Is a more general logger needed?
+ public static void safeRecursiveOverwrite(@NonNull File src, @NonNull File dest,
+ @NonNull FileOp fop, @NonNull ProgressIndicator progress) throws IOException {
+
+ if (fop.exists(dest)) {
+
+ File toDelete = getNewTempDir("FileOpUtilsToDelete", fop);
+ if (toDelete == null) {
+ // weird, try to delete in place
+ fop.deleteFileOrFolder(dest);
+ } else {
+ FileOpUtils.recursiveCopy(dest, toDelete, fop);
+ }
+ try {
+ fop.deleteFileOrFolder(dest);
+ FileOpUtils.recursiveCopy(src, dest, fop);
+ fop.deleteFileOrFolder(src);
+ } catch (IOException e) {
+ // this is bad
+ progress.logError("Old dir was moved away, but new one failed to be moved into "
+ + "place. Trying to move old one back.");
+ if (fop.exists(dest)) {
+ fop.deleteFileOrFolder(dest);
+ }
+ if (toDelete == null) {
+ // this is the worst case
+ progress.logError(
+ "Failed to move old dir back into place! Component was lost.");
+ } else {
+ FileOpUtils.recursiveCopy(toDelete, dest, fop);
+ }
+ throw new IOException("failed to move new dir into place", e);
+ }
+ if (toDelete != null) {
+ fop.deleteFileOrFolder(toDelete);
+ }
+ } else {
+ FileOpUtils.recursiveCopy(src, dest, fop);
+ fop.deleteFileOrFolder(src);
+ }
+ }
+
+ /**
+ * Creates a new subdirectory of the system temp directory. The directory will be named {@code
+ * <base> + NN}, where NN makes the directory distinct from any existing directories.
+ */
+ @Nullable
+ public static File getNewTempDir(@NonNull String base, @NonNull FileOp fileOp) {
+ File tempDir = new File(System.getProperty("java.io.tmpdir"));
+ if (!fileOp.exists(tempDir)) {
+ fileOp.mkdirs(tempDir);
+ }
+ for (int i = 1; i < 100; i++) {
+ File folder = new File(tempDir,
+ String.format("%1$s%2$02d", base, i)); //$NON-NLS-1$
+ if (!fileOp.exists(folder)) {
+ return folder;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Appends the given {@code segments} to the {@code base} file.
+ *
+ * @param base A base file, non-null.
+ * @param segments Individual folder or filename segments to append to the base file.
+ * @return A new file representing the concatenation of the base path with all the segments.
+ */
+ @NonNull
+ public static File append(@NonNull File base, @NonNull String...segments) {
+ for (String segment : segments) {
+ base = new File(base, segment);
+ }
+ return base;
+ }
+
+ /**
+ * Appends the given {@code segments} to the {@code base} file.
+ *
+ * @param base A base file path, non-empty and non-null.
+ * @param segments Individual folder or filename segments to append to the base path.
+ * @return A new file representing the concatenation of the base path with all the segments.
+ */
+ @NonNull
+ public static File append(@NonNull String base, @NonNull String...segments) {
+ return append(new File(base), segments);
+ }
+ /**
+ * Computes a relative path from "toBeRelative" relative to "baseDir".
+ *
+ * Rule:
+ * - let relative2 = makeRelative(path1, path2)
+ * - then pathJoin(path1 + relative2) == path2 after canonicalization.
+ *
+ * Principle:
+ * - let base = /c1/c2.../cN/a1/a2../aN
+ * - let toBeRelative = /c1/c2.../cN/b1/b2../bN
+ * - result is removes the common paths, goes back from aN to cN then to bN:
+ * - result = ../..../../1/b2../bN
+ *
+ * @param baseDir The base directory to be relative to.
+ * @param toBeRelative The file or directory to make relative to the base.
+ * @param fop FileOp, in this case just to determine the platform.
+ * @return A path that makes toBeRelative relative to baseDir.
+ * @throws IOException If drive letters don't match on Windows or path canonicalization fails.
+ */
+
+ @NonNull
+ public static String makeRelative(@NonNull File baseDir, @NonNull File toBeRelative, FileOp fop)
+ throws IOException {
+ return makeRelativeImpl(
+ baseDir.getCanonicalPath(),
+ toBeRelative.getCanonicalPath(),
+ fop.isWindows(),
+ File.separator);
+ }
+
+ /**
+ * Implementation detail of makeRelative to make it testable
+ * Independently of the platform.
+ */
+ @VisibleForTesting
+ @NonNull
+ static String makeRelativeImpl(@NonNull String path1,
+ @NonNull String path2,
+ boolean isWindows,
+ @NonNull String dirSeparator)
+ throws IOException {
+ if (isWindows) {
+ // Check whether both path are on the same drive letter, if any.
+ String p1 = path1;
+ String p2 = path2;
+ char drive1 = (p1.length() >= 2 && p1.charAt(1) == ':') ? p1.charAt(0) : 0;
+ char drive2 = (p2.length() >= 2 && p2.charAt(1) == ':') ? p2.charAt(0) : 0;
+ if (drive1 != drive2) {
+ // Either a mix of UNC vs drive or not the same drives.
+ throw new IOException("makeRelative: incompatible drive letters");
+ }
+ }
+
+ String[] segments1 = path1.split(Pattern.quote(dirSeparator));
+ String[] segments2 = path2.split(Pattern.quote(dirSeparator));
+
+ int len1 = segments1.length;
+ int len2 = segments2.length;
+ int len = Math.min(len1, len2);
+ int start = 0;
+ for (; start < len; start++) {
+ // On Windows should compare in case-insensitive.
+ // Mac & Linux file systems can be both type, although their default
+ // is generally to have a case-sensitive file system.
+ if (( isWindows && !segments1[start].equalsIgnoreCase(segments2[start])) ||
+ (!isWindows && !segments1[start].equals(segments2[start]))) {
+ break;
+ }
+ }
+
+ StringBuilder result = new StringBuilder();
+ for (int i = start; i < len1; i++) {
+ result.append("..").append(dirSeparator);
+ }
+ while (start < len2) {
+ result.append(segments2[start]);
+ if (++start < len2) {
+ result.append(dirSeparator);
+ }
+ }
+
+ return result.toString();
+ }
+
+ private FileOpUtils() {
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/io/impl/FileOpImpl.java b/repository/src/main/java/com/android/repository/io/impl/FileOpImpl.java
new file mode 100644
index 0000000..baa5b98
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/io/impl/FileOpImpl.java
@@ -0,0 +1,390 @@
+/*
+ * 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.repository.io.impl;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.google.common.io.Closer;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Properties;
+
+/**
+ * Wraps some common {@link File} operations on files and folders.
+ * <p/>
+ * This makes it possible to override/mock/stub some file operations in unit tests.
+ * <p/>
+ * Instances should be obtained through {@link FileOpUtils#create()}
+ */
+public class FileOpImpl implements FileOp {
+
+ /**
+ * Reflection method for File.setExecutable(boolean, boolean). Only present in Java 6.
+ */
+ private static Method sFileSetExecutable = null;
+
+ /**
+ * Parameters to call File.setExecutable through reflection.
+ */
+ private static final Object[] sFileSetExecutableParams = new Object[] {
+ Boolean.TRUE, Boolean.FALSE };
+
+ // static initialization of sFileSetExecutable.
+ static {
+ try {
+ sFileSetExecutable = File.class.getMethod("setExecutable", //$NON-NLS-1$
+ boolean.class, boolean.class);
+
+ } catch (SecurityException e) {
+ // do nothing we'll use chmod instead
+ } catch (NoSuchMethodException e) {
+ // do nothing we'll use chmod instead
+ }
+ }
+
+ @Override
+ public void deleteFileOrFolder(@NonNull File fileOrFolder) {
+ if (isDirectory(fileOrFolder)) {
+ // Must delete content recursively first
+ File[] files = fileOrFolder.listFiles();
+ if (files != null) {
+ for (File item : files) {
+ deleteFileOrFolder(item);
+ }
+ }
+ }
+
+ // Don't try to delete it if it doesn't exist.
+ if (!exists(fileOrFolder)) {
+ return;
+ }
+
+ if (isWindows()) {
+ // Trying to delete a resource on windows might fail if there's a file
+ // indexer locking the resource. Generally retrying will be enough to
+ // make it work.
+ //
+ // Try for half a second before giving up.
+
+ for (int i = 0; i < 5; i++) {
+ if (fileOrFolder.delete()) {
+ return;
+ }
+
+ try {
+ Thread.sleep(100 /*ms*/);
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+
+ fileOrFolder.deleteOnExit();
+
+ } else {
+ // On Linux or Mac, just straight deleting it should just work.
+
+ if (!fileOrFolder.delete()) {
+ fileOrFolder.deleteOnExit();
+ }
+ }
+ }
+
+ @Override
+ public void setExecutablePermission(@NonNull File file) throws IOException {
+ if (isWindows()) {
+ throw new IllegalStateException("Can't setExecutablePermission on windows!");
+ }
+ if (sFileSetExecutable != null) {
+ try {
+ sFileSetExecutable.invoke(file, sFileSetExecutableParams);
+ return;
+ } catch (IllegalArgumentException e) {
+ // we'll run chmod below
+ } catch (IllegalAccessException e) {
+ // we'll run chmod below
+ } catch (InvocationTargetException e) {
+ // we'll run chmod below
+ }
+ }
+
+ Runtime.getRuntime().exec(new String[] {
+ "chmod", "+x", file.getAbsolutePath() //$NON-NLS-1$ //$NON-NLS-2$
+ });
+ }
+
+ @Override
+ public void setReadOnly(@NonNull File file) {
+ file.setReadOnly();
+ }
+
+ @Override
+ public void copyFile(@NonNull File source, @NonNull File dest) throws IOException {
+ byte[] buffer = new byte[8192];
+
+ FileInputStream fis = null;
+ FileOutputStream fos = null;
+ try {
+ fis = new FileInputStream(source);
+ fos = new FileOutputStream(dest);
+
+ int read;
+ while ((read = fis.read(buffer)) != -1) {
+ fos.write(buffer, 0, read);
+ }
+
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isSameFile(@NonNull File file1, @NonNull File file2) throws IOException {
+
+ if (file1.length() != file2.length()) {
+ return false;
+ }
+
+ FileInputStream fis1 = null;
+ FileInputStream fis2 = null;
+
+ try {
+ fis1 = new FileInputStream(file1);
+ fis2 = new FileInputStream(file2);
+
+ byte[] buffer1 = new byte[8192];
+ byte[] buffer2 = new byte[8192];
+
+ int read1;
+ while ((read1 = fis1.read(buffer1)) != -1) {
+ int read2 = 0;
+ while (read2 < read1) {
+ int n = fis2.read(buffer2, read2, read1 - read2);
+ if (n == -1) {
+ break;
+ }
+ }
+
+ if (read2 != read1) {
+ return false;
+ }
+
+ if (!Arrays.equals(buffer1, buffer2)) {
+ return false;
+ }
+ }
+ } finally {
+ if (fis2 != null) {
+ try {
+ fis2.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ if (fis1 != null) {
+ try {
+ fis1.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean isFile(@NonNull File file) {
+ return file.isFile();
+ }
+
+ @Override
+ public boolean isDirectory(@NonNull File file) {
+ return file.isDirectory();
+ }
+
+ @Override
+ public boolean exists(@NonNull File file) {
+ return file.exists();
+ }
+
+ @Override
+ public boolean canWrite(@NonNull File file) {
+ return file.canWrite();
+ }
+
+ @Override
+ public long length(@NonNull File file) {
+ return file.length();
+ }
+
+ @Override
+ public boolean delete(@NonNull File file) {
+ return file.delete();
+ }
+
+ @Override
+ public boolean mkdirs(@NonNull File file) {
+ return file.mkdirs();
+ }
+
+ @Override
+ @NonNull
+ public File[] listFiles(@NonNull File file) {
+ File[] r = file.listFiles();
+ if (r == null) {
+ return EMPTY_FILE_ARRAY;
+ } else {
+ return r;
+ }
+ }
+
+ @Override
+ public boolean renameTo(@NonNull File oldFile, @NonNull File newFile) {
+ return oldFile.renameTo(newFile);
+ }
+
+ @Override
+ @NonNull
+ public OutputStream newFileOutputStream(@NonNull File file) throws FileNotFoundException {
+ return new FileOutputStream(file);
+ }
+
+ @Override
+ @NonNull
+ public InputStream newFileInputStream(@NonNull File file) throws FileNotFoundException {
+ return new FileInputStream(file);
+ }
+
+ @Override
+ @NonNull
+ public Properties loadProperties(@NonNull File file) {
+ Properties props = new Properties();
+ Closer closer = Closer.create();
+ try {
+ FileInputStream fis = closer.register(new FileInputStream(file));
+ props.load(fis);
+ } catch (IOException ignore) {
+ } finally {
+ try {
+ closer.close();
+ } catch (IOException e) {
+ }
+ }
+ return props;
+ }
+
+ @Override
+ public void saveProperties(
+ @NonNull File file,
+ @NonNull Properties props,
+ @NonNull String comments) throws IOException {
+ Closer closer = Closer.create();
+ try {
+ OutputStream fos = closer.register(newFileOutputStream(file));
+ props.store(fos, comments);
+ } catch (Throwable e) {
+ throw closer.rethrow(e);
+ } finally {
+ closer.close();
+ }
+ }
+
+ @Override
+ public long lastModified(@NonNull File file) {
+ return file.lastModified();
+ }
+
+ @Override
+ public boolean createNewFile(@NonNull File file) throws IOException {
+ return file.createNewFile();
+ }
+
+ @Override
+ public boolean isWindows() {
+ return System.getProperty("os.name").startsWith("Windows");
+ }
+
+ @Override
+ public boolean canExecute(@NonNull File file) {
+ return file.canExecute();
+ }
+
+ @Override
+ public File ensureRealFile(@NonNull File in) {
+ return in;
+ }
+
+ @NonNull
+ @Override
+ public String toString(@NonNull File f, @NonNull Charset c) throws IOException {
+ return Files.toString(f, c);
+ }
+
+ @Override
+ @Nullable
+ public String[] list(@NonNull File folder, @Nullable FilenameFilter filenameFilter) {
+ return folder.list(filenameFilter);
+ }
+
+ @Override
+ @Nullable
+ public File[] listFiles(@NonNull File folder, @Nullable FilenameFilter filenameFilter) {
+ return folder.listFiles(filenameFilter);
+ }
+
+ @Override
+ public void deleteOnExit(File file) {
+ file.deleteOnExit();
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof FileOpImpl;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/testframework/FakeDependency.java b/repository/src/main/java/com/android/repository/testframework/FakeDependency.java
new file mode 100644
index 0000000..376ca10
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/testframework/FakeDependency.java
@@ -0,0 +1,70 @@
+/*
+ * 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.repository.testframework;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Dependency;
+import com.android.repository.impl.meta.RevisionType;
+
+/**
+ * A {@link Dependency} for use in {@link FakePackage}.
+ */
+public class FakeDependency extends Dependency {
+
+ private final String mPath;
+
+ private final RevisionType mRevision;
+
+ public FakeDependency(String path) {
+ this(path, null, null, null);
+ }
+
+ public FakeDependency(String path, final Integer major, final Integer minor,
+ final Integer micro) {
+ mPath = path;
+ mRevision = major == null ? null : new RevisionType() {
+ @Override
+ public int getMajor() {
+ return major;
+ }
+
+ @Nullable
+ @Override
+ public Integer getMicro() {
+ return minor;
+ }
+
+ @Nullable
+ @Override
+ public Integer getMinor() {
+ return micro;
+ }
+ };
+ }
+
+ @NonNull
+ @Override
+ public String getPath() {
+ return mPath;
+ }
+
+ @Nullable
+ @Override
+ public RevisionType getMinRevision() {
+ return mRevision;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/testframework/FakeDownloader.java b/repository/src/main/java/com/android/repository/testframework/FakeDownloader.java
new file mode 100644
index 0000000..66e2691
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/testframework/FakeDownloader.java
@@ -0,0 +1,114 @@
+/*
+ * 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.repository.testframework;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.SettingsController;
+import com.android.repository.io.FileOp;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * Fake implementation of {@link Downloader} that returns some specified content for specified
+ * URLs.
+ */
+public class FakeDownloader implements Downloader {
+
+ private final MockFileOp mFileOp;
+
+ public FakeDownloader(MockFileOp fop) {
+ mFileOp = fop;
+ }
+
+ public void registerUrl(URL url, byte[] data) {
+ String filename = getFileName(url);
+ mFileOp.recordExistingFile(filename, data);
+ }
+
+ public void registerUrl(URL url, InputStream content) throws IOException {
+ byte[] data = ByteStreams.toByteArray(content);
+ String filename = getFileName(url);
+ mFileOp.recordExistingFile(filename, data);
+ }
+
+ @NonNull
+ public String getFileName(URL url) {
+ return "/tmp" + url.getFile();
+ }
+
+ @Override
+ @NonNull
+ public InputStream downloadAndStream(@NonNull URL url, @Nullable SettingsController controller,
+ @NonNull ProgressIndicator indicator) throws IOException {
+ InputStream toWrap = null;
+ try {
+ toWrap = mFileOp.newFileInputStream(new File(getFileName(url)));
+ }
+ catch (Exception e) {
+ // nothing
+ }
+ if (toWrap != null) {
+ return new ReopeningInputStream(toWrap);
+ }
+ throw new IOException("Failed to open " + url);
+ }
+
+ @Nullable
+ @Override
+ public File downloadFully(@NonNull URL url, @Nullable SettingsController settings,
+ @NonNull ProgressIndicator indicator) throws IOException {
+ return new File(getFileName(url));
+ }
+
+ /**
+ * For convenience, so we can download from the same URL more than once with this downloader,
+ * reset streams instead of closing them.
+ */
+ static class ReopeningInputStream extends InputStream {
+
+ private InputStream mWrapped;
+
+ public ReopeningInputStream(InputStream toWrap) {
+ toWrap.mark(Integer.MAX_VALUE);
+ mWrapped = toWrap;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mWrapped.read();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mWrapped.reset();
+ }
+
+ public void reallyClose() throws IOException {
+ mWrapped.close();
+ }
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/testframework/FakePackage.java b/repository/src/main/java/com/android/repository/testframework/FakePackage.java
new file mode 100644
index 0000000..d3a4941
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/testframework/FakePackage.java
@@ -0,0 +1,212 @@
+/*
+ * 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.repository.testframework;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.Channel;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.License;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.impl.meta.Archive;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.GenericFactory;
+import com.android.repository.impl.meta.TypeDetails;
+import com.google.common.base.Objects;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * A fake {@link RepoPackage} (implementing both {@link LocalPackage} and {@link RemotePackage},
+ * for use in unit tests.
+ *
+ * Currently not especially fully-featured.
+ */
+ at SuppressWarnings("ConstantConditions")
+public class FakePackage implements LocalPackage, RemotePackage {
+ private final String mPath;
+ private final Revision mVersion;
+ private final Collection<Dependency> mDependencies;
+ private TypeDetails mDetails;
+ private Channel mChannel;
+ private Archive mArchive;
+
+ public FakePackage(String path, Revision version, Collection<Dependency> dependencies) {
+ mPath = path;
+ mVersion = version;
+ mDependencies = dependencies == null ? ImmutableList.<Dependency>of() : dependencies;
+ }
+
+ @NonNull
+ @Override
+ public RepositorySource getSource() {
+ return null;
+ }
+
+ @Override
+ public void setSource(@NonNull RepositorySource source) {
+ }
+
+ @Nullable
+ @Override
+ public Archive getArchive() {
+ return mArchive;
+ }
+
+ public void setCompleteUrl(String url) {
+ mArchive = new FakeArchive(url);
+ }
+
+ public void setChannel(Channel channel) {
+ mChannel = channel;
+ }
+
+ @NonNull
+ @Override
+ public Channel getChannel() {
+ return mChannel == null ? Channel.DEFAULT : mChannel;
+ }
+
+ public void setTypeDetails(TypeDetails details) {
+ mDetails = details;
+ }
+
+ @NonNull
+ @Override
+ public TypeDetails getTypeDetails() {
+ return mDetails == null ? (TypeDetails) ((GenericFactory) RepoManager.getGenericModule()
+ .createLatestFactory()).createGenericDetailsType() : mDetails;
+ }
+
+ @NonNull
+ @Override
+ public Revision getVersion() {
+ return mVersion;
+ }
+
+ @NonNull
+ @Override
+ public String getDisplayName() {
+ return "fake package";
+ }
+
+ @Nullable
+ @Override
+ public License getLicense() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Collection<Dependency> getAllDependencies() {
+ return mDependencies;
+ }
+
+ @NonNull
+ @Override
+ public String getPath() {
+ return mPath;
+ }
+
+ @Override
+ public boolean obsolete() {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public CommonFactory createFactory() {
+ return null;
+ }
+
+ @Override
+ public int compareTo(@NonNull RepoPackage o) {
+ return ComparisonChain.start().compare(getPath(), o.getPath())
+ .compare(getVersion(), o.getVersion()).result();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof RepoPackage && ((RepoPackage) obj).getPath().equals(getPath())
+ && ((RepoPackage) obj).getVersion().equals(getVersion());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getPath(), getVersion());
+ }
+
+ @NonNull
+ @Override
+ public File getLocation() {
+ return null;
+ }
+
+ @Override
+ public void setInstalledPath(@NonNull File root) {
+ }
+
+ @Override
+ public String toString() {
+ return mPath;
+ }
+
+ private static class FakeArchive extends Archive {
+
+ private String mCompleteUrl;
+
+ public FakeArchive(String url) {
+ mCompleteUrl = url;
+ }
+
+ @NonNull
+ @Override
+ public CompleteType getComplete() {
+ return new CompleteType() {
+ @NonNull
+ @Override
+ public String getChecksum() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public String getUrl() {
+ return mCompleteUrl;
+ }
+
+ @Override
+ public long getSize() {
+ return 0;
+ }
+ };
+ }
+
+ @NonNull
+ @Override
+ public CommonFactory createFactory() {
+ return null;
+ }
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/testframework/FakeProgressIndicator.java b/repository/src/main/java/com/android/repository/testframework/FakeProgressIndicator.java
new file mode 100644
index 0000000..a1cb43a
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/testframework/FakeProgressIndicator.java
@@ -0,0 +1,155 @@
+/*
+ * 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.repository.testframework;
+
+import com.android.annotations.NonNull;
+import com.android.repository.api.ProgressIndicator;
+import com.google.common.collect.Lists;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+/**
+ * Fake {@link ProgressIndicator} that keeps track of the messages that were logged to it, and
+ * provides a convenient method for asserting that no errors or warnings occurred.
+ */
+public class FakeProgressIndicator implements ProgressIndicator {
+
+ private List<String> mInfos = Lists.newArrayList();
+
+ private List<String> mWarnings = Lists.newArrayList();
+
+ private List<String> mErrors = Lists.newArrayList();
+
+ private boolean mCancelled = false;
+
+ private boolean mCancellable = true;
+
+ @Override
+ public void setText(String s) {
+
+ }
+
+ @Override
+ public boolean isCanceled() {
+ return mCancelled;
+ }
+
+ @Override
+ public void cancel() {
+ if (mCancellable) {
+ mCancelled = true;
+ }
+ }
+
+ @Override
+ public void setCancellable(boolean cancellable) {
+ mCancellable = cancellable;
+ }
+
+ @Override
+ public boolean isCancellable() {
+ return mCancellable;
+ }
+
+ @Override
+ public void setFraction(double v) {
+
+ }
+
+ @Override
+ public double getFraction() {
+ return 0;
+ }
+
+ @Override
+ public void setSecondaryText(String s) {
+
+ }
+
+ private static String getStackTrace() {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ new Throwable().printStackTrace(pw);
+ return sw.toString();
+ }
+
+ @Override
+ public void logWarning(@NonNull String s) {
+ mWarnings.add(s);
+ mWarnings.add(getStackTrace());
+ }
+
+ @Override
+ public void logWarning(@NonNull String s, Throwable e) {
+ mWarnings.add(s + "\n" + e.toString());
+ mWarnings.add(getStackTrace());
+ }
+
+ @Override
+ public void logError(@NonNull String s) {
+ mErrors.add(s);
+ mErrors.add(getStackTrace());
+ }
+
+ @Override
+ public void logError(@NonNull String s, Throwable e) {
+ mErrors.add(s + "\n" + e.toString());
+ mErrors.add(getStackTrace());
+ }
+
+ @Override
+ public void logInfo(@NonNull String s) {
+ mInfos.add(s);
+ }
+
+ @Override
+ public boolean isIndeterminate() {
+ return false;
+ }
+
+ @Override
+ public void setIndeterminate(boolean indeterminate) {
+
+ }
+
+ public List<String> getInfos() {
+ return mInfos;
+ }
+
+ public List<String> getWarnings() {
+ return mWarnings;
+ }
+
+ public List<String> getErrors() {
+ return mErrors;
+ }
+
+ /**
+ * {@code assert} that no errors or warnings have been logged.
+ */
+ public void assertNoErrorsOrWarnings(){
+ if (!getErrors().isEmpty()) {
+ throw new Error(getErrors().toString());
+ }
+ if (!getWarnings().isEmpty()) {
+ throw new Error(getWarnings().toString());
+ }
+
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/testframework/FakeProgressRunner.java b/repository/src/main/java/com/android/repository/testframework/FakeProgressRunner.java
new file mode 100644
index 0000000..447e38e
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/testframework/FakeProgressRunner.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.repository.testframework;
+
+import com.android.annotations.NonNull;
+import com.android.repository.api.ProgressRunner;
+
+/**
+ * A basic {@link ProgressRunner} that uses a {@link FakeProgressIndicator}.
+ */
+public class FakeProgressRunner implements ProgressRunner {
+
+ FakeProgressIndicator mProgressIndicator = new FakeProgressIndicator();
+
+ @Override
+ public void runAsyncWithProgress(@NonNull final ProgressRunner.ProgressRunnable r) {
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ r.run(mProgressIndicator, FakeProgressRunner.this);
+ }
+ });
+ }
+
+ @Override
+ public void runSyncWithProgress(@NonNull ProgressRunner.ProgressRunnable r) {
+ r.run(mProgressIndicator, this);
+ }
+
+ @Override
+ public void runSyncWithoutProgress(@NonNull Runnable r) {
+ r.run();
+ }
+
+ public FakeProgressIndicator getProgressIndicator() {
+ return mProgressIndicator;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/testframework/FakeSettingsController.java b/repository/src/main/java/com/android/repository/testframework/FakeSettingsController.java
new file mode 100644
index 0000000..e667acb
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/testframework/FakeSettingsController.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.repository.testframework;
+
+import com.android.repository.api.Channel;
+import com.android.repository.api.SettingsController;
+
+/**
+ * A simple {@link SettingsController} where values can be set directly.
+ */
+public class FakeSettingsController implements SettingsController {
+
+ private boolean mForceHttp;
+ private Channel myChannel;
+
+ public FakeSettingsController(boolean forceHttp) {
+ mForceHttp = forceHttp;
+ }
+
+ @Override
+ public boolean getForceHttp() {
+ return mForceHttp;
+ }
+
+ @Override
+ public void setForceHttp(boolean force) {
+ mForceHttp = force;
+ }
+
+ public void setChannel(Channel channel) {
+ myChannel = channel;
+ }
+
+ @Override
+ public Channel getChannel() {
+ return myChannel;
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/testframework/MockFileOp.java b/repository/src/main/java/com/android/repository/testframework/MockFileOp.java
new file mode 100644
index 0000000..a82bbc4
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/testframework/MockFileOp.java
@@ -0,0 +1,733 @@
+/*
+ * 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.repository.testframework;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.repository.io.impl.FileOpImpl;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Mock version of {@link FileOpImpl} that wraps some common {@link File}
+ * operations on files and folders.
+ * <p/>
+ * This version does not perform any file operation. Instead it records a textual
+ * representation of all the file operations performed.
+ * <p/>
+ * To avoid cross-platform path issues (e.g. Windows path), the methods here should
+ * always use rooted (aka absolute) unix-looking paths, e.g. "/dir1/dir2/file3".
+ * When processing {@link File}, you can convert them using {@link #getAgnosticAbsPath(File)}.
+ */
+public class MockFileOp implements FileOp {
+
+ private final Map<String, FileInfo> mExistingFiles = Maps.newTreeMap();
+ private final Set<String> mExistingFolders = Sets.newTreeSet();
+ private final Set<String> mReadOnlyFiles = Sets.newTreeSet();
+ private final List<StringOutputStream> mOutputStreams = new ArrayList<StringOutputStream>();
+ private boolean mIsWindows;
+
+ public MockFileOp() {
+ mIsWindows = FileOpUtils.create().isWindows();
+ }
+
+ /** Resets the internal state, as if the object had been newly created. */
+ public void reset() {
+ mExistingFiles.clear();
+ mExistingFolders.clear();
+ mReadOnlyFiles.clear();
+ mOutputStreams.clear();
+ mIsWindows = FileOpUtils.create().isWindows();
+
+ }
+
+ @Override
+ public boolean isWindows() {
+ return mIsWindows;
+ }
+
+ @NonNull
+ @Override
+ public String toString(@NonNull File f, @NonNull Charset c) throws IOException {
+ FileInfo fileInfo = mExistingFiles.get(f.getAbsolutePath());
+ if (fileInfo == null || fileInfo.getContent() == null) {
+ throw new FileNotFoundException();
+ }
+ return new String(fileInfo.getContent(), c);
+ }
+
+ @Override
+ @Nullable
+ public String[] list(@NonNull File folder, @Nullable FilenameFilter filenameFilter) {
+ File contents[] = listFiles(folder);
+ String names[] = new String[contents.length];
+ for (int i = 0; i < contents.length; i++) {
+ names[i] = contents[i].getName();
+ }
+ if (filenameFilter == null) {
+ return names;
+ }
+ List<String> result = Lists.newArrayList();
+ for (String name : names) {
+ if (filenameFilter.accept(folder, name)) {
+ result.add(name);
+ }
+ }
+ return result.toArray(new String[result.size()]);
+
+ }
+
+ @Override
+ @Nullable
+ public File[] listFiles(@NonNull File folder, @Nullable FilenameFilter filenameFilter) {
+ File contents[] = listFiles(folder);
+ if (filenameFilter == null) {
+ return contents;
+ }
+ List<File> result = Lists.newArrayList();
+ for (File f : contents) {
+ if (filenameFilter.accept(folder, f.getName())) {
+ result.add(f);
+ }
+ }
+ return result.toArray(new File[result.size()]);
+
+ }
+
+ @Override
+ public void deleteOnExit(File file) {
+ // nothing
+ }
+
+ public void setIsWindows(boolean isWindows) {
+ mIsWindows = isWindows;
+ }
+
+ @NonNull
+ public String getAgnosticAbsPath(@NonNull File file) {
+ return getAgnosticAbsPath(file.getAbsolutePath());
+ }
+
+ @NonNull
+ public String getAgnosticAbsPath(@NonNull String path) {
+ if (isWindows()) {
+ // Try to convert the windows-looking path to a unix-looking one
+ path = path.replace('\\', '/');
+ path = path.replaceAll("^[A-Z]:", "");
+ }
+ return path;
+ }
+
+ /**
+ * Records a new absolute file path.
+ * Parent folders are automatically created.
+ */
+ public void recordExistingFile(@NonNull File file) {
+ recordExistingFile(getAgnosticAbsPath(file), 0, (byte[])null);
+ }
+
+ /**
+ * Records a new absolute file path.
+ * Parent folders are automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ */
+ public void recordExistingFile(@NonNull String absFilePath) {
+ recordExistingFile(absFilePath, 0, (byte[])null);
+ }
+
+ /**
+ * Records a new absolute file path & its input stream content.
+ * Parent folders are automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ * @param inputStream A non-null byte array of content to return
+ * via {@link #newFileInputStream(File)}.
+ */
+ public void recordExistingFile(@NonNull String absFilePath, @Nullable byte[] inputStream) {
+ recordExistingFile(absFilePath, 0, inputStream);
+ }
+
+ /**
+ * Records a new absolute file path & its input stream content.
+ * Parent folders are automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ * @param content A non-null UTF-8 content string to return
+ * via {@link #newFileInputStream(File)}.
+ */
+ public void recordExistingFile(@NonNull String absFilePath, @NonNull String content) {
+ recordExistingFile(absFilePath, 0, content.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Records a new absolute file path & its input stream content.
+ * Parent folders are automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ * @param inputStream A non-null byte array of content to return
+ * via {@link #newFileInputStream(File)}.
+ */
+ public void recordExistingFile(@NonNull String absFilePath,
+ long lastModified,
+ @Nullable byte[] inputStream) {
+ mExistingFiles.put(absFilePath, new FileInfo(lastModified, inputStream));
+ createParents(absFilePath);
+ }
+
+ private void createParents(String absFilePath) {
+ if (!absFilePath.endsWith("/")) {
+ absFilePath = absFilePath.substring(0, absFilePath.lastIndexOf('/'));
+ }
+ if (absFilePath.contains("/")) {
+ recordExistingFolder(absFilePath);
+ }
+ }
+
+ /**
+ * Records a new absolute file path & its input stream content.
+ * Parent folders are automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ * @param content A non-null UTF-8 content string to return
+ * via {@link #newFileInputStream(File)}.
+ */
+ public void recordExistingFile(@NonNull String absFilePath,
+ long lastModified,
+ @NonNull String content) {
+ recordExistingFile(absFilePath, lastModified, content.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Records a new absolute folder path.
+ * Parent folders are automatically created.
+ */
+ public void recordExistingFolder(File folder) {
+ recordExistingFolder(getAgnosticAbsPath(folder));
+ }
+
+ /**
+ * Records a new absolute folder path.
+ * Parent folders are automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFolderPath A unix-like folder path, e.g. "/dir/file"
+ */
+ public void recordExistingFolder(String absFolderPath) {
+ mExistingFolders.add(absFolderPath);
+ createParents(absFolderPath);
+ }
+
+ /**
+ * Returns true if a file with the given path has been recorded.
+ */
+ public boolean hasRecordedExistingFile(File file) {
+ return mExistingFiles.containsKey(getAgnosticAbsPath(file));
+ }
+
+ /**
+ * Returns true if a folder with the given path has been recorded.
+ */
+ public boolean hasRecordedExistingFolder(File folder) {
+ return mExistingFolders.contains(getAgnosticAbsPath(folder));
+ }
+
+ /**
+ * Returns the list of paths added using {@link #recordExistingFile(String)}
+ * and eventually updated by {@link #delete(File)} operations.
+ * <p/>
+ * The returned list is sorted by alphabetic absolute path string.
+ */
+ @NonNull
+ public String[] getExistingFiles() {
+ Set<String> files = mExistingFiles.keySet();
+ return files.toArray(new String[files.size()]);
+ }
+
+ /**
+ * Returns the list of folder paths added using {@link #recordExistingFolder(String)}
+ * and eventually updated {@link #delete(File)} or {@link #mkdirs(File)} operations.
+ * <p/>
+ * The returned list is sorted by alphabetic absolute path string.
+ */
+ @NonNull
+ public String[] getExistingFolders() {
+ return mExistingFolders.toArray(new String[mExistingFolders.size()]);
+ }
+
+ /**
+ * Returns the {@link StringOutputStream#toString()} as an array, in creation order.
+ * Array can be empty but not null.
+ */
+ @NonNull
+ public String[] getOutputStreams() {
+ int n = mOutputStreams.size();
+ String[] result = new String[n];
+ for (int i = 0; i < n; i++) {
+ result[i] = mOutputStreams.get(i).toString();
+ }
+ return result;
+ }
+
+ @Override
+ public void deleteFileOrFolder(@NonNull File fileOrFolder) {
+ if (isDirectory(fileOrFolder)) {
+ // Must delete content recursively first
+ for (File item : listFiles(fileOrFolder)) {
+ deleteFileOrFolder(item);
+ }
+ }
+ delete(fileOrFolder);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p/>
+ * <em>Note: this mock version does nothing.</em>
+ */
+ @Override
+ public void setExecutablePermission(@NonNull File file) throws IOException {
+ // pass
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p/>
+ * <em>Note: this mock version does nothing.</em>
+ */
+ @Override
+ public boolean canExecute(@NonNull File file) {
+ return false;
+ }
+
+ @Override
+ public File ensureRealFile(@NonNull File in) throws IOException {
+ if (!exists(in)) {
+ return in;
+ }
+ File result = File.createTempFile("MockFileOp", null);
+ result.deleteOnExit();
+ OutputStream os = new FileOutputStream(result);
+ try {
+ ByteStreams.copy(newFileInputStream(in), os);
+ }
+ finally {
+ os.close();
+ }
+ return result;
+ }
+
+ @Override
+ public void setReadOnly(@NonNull File file) {
+ mReadOnlyFiles.add(getAgnosticAbsPath(file));
+ recordExistingFile(file);
+ }
+
+ @Override
+ public void copyFile(@NonNull File source, @NonNull File dest) throws IOException {
+ InputStream in = newFileInputStream(source);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int c;
+ while ((c = in.read()) != -1) {
+ baos.write(c);
+ }
+ in.close();
+ baos.close();
+ recordExistingFile(dest.getAbsolutePath(), baos.toByteArray());
+ }
+
+ @Override
+ public boolean isSameFile(@NonNull File file1, @NonNull File file2) throws IOException {
+ String path1 = getAgnosticAbsPath(file1);
+ String path2 = getAgnosticAbsPath(file2);
+ FileInfo fi1 = mExistingFiles.get(path1);
+ FileInfo fi2 = mExistingFiles.get(path2);
+
+ if (fi1 == null) {
+ throw new FileNotFoundException("[isSameFile] Mock file not defined: " + path1);
+ }
+
+ if (fi1 == fi2) {
+ return true;
+ }
+
+ if (fi2 == null) {
+ throw new FileNotFoundException("[isSameFile] Mock file not defined: " + path2);
+ }
+
+ byte[] content1 = fi1.getContent();
+ byte[] content2 = fi2.getContent();
+
+ if (content1 == null) {
+ throw new IOException("[isSameFile] Mock file has no content: " + path1);
+ }
+ if (content2 == null) {
+ throw new IOException("[isSameFile] Mock file has no content: " + path2);
+ }
+
+ return Arrays.equals(content1, content2);
+ }
+
+ @Override
+ public boolean isFile(@NonNull File file) {
+ String path = getAgnosticAbsPath(file);
+ return mExistingFiles.containsKey(path);
+ }
+
+ @Override
+ public boolean isDirectory(@NonNull File file) {
+ String path = getAgnosticAbsPath(file);
+ if (mExistingFolders.contains(path)) {
+ return true;
+ }
+
+ // If we defined a file or folder as a child of the requested file path,
+ // then the directory exists implicitely.
+ Pattern pathRE = Pattern.compile(
+ Pattern.quote(path + (path.endsWith("/") ? "" : '/')) +
+ ".*");
+
+ for (String folder : mExistingFolders) {
+ if (pathRE.matcher(folder).matches()) {
+ return true;
+ }
+ }
+ for (String filePath : mExistingFiles.keySet()) {
+ if (pathRE.matcher(filePath).matches()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean canWrite(@NonNull File file) {
+ return !mReadOnlyFiles.contains(getAgnosticAbsPath(file));
+ }
+
+ @Override
+ public boolean exists(@NonNull File file) {
+ return isFile(file) || isDirectory(file);
+ }
+
+ @Override
+ public long length(@NonNull File file) {
+ throw new UnsupportedOperationException("MockFileUtils.length is not supported.");
+ }
+
+ @Override
+ public boolean delete(@NonNull File file) {
+ String path = getAgnosticAbsPath(file);
+
+ if (mExistingFiles.remove(path) != null) {
+ return true;
+ }
+
+ for (String folder : mExistingFolders) {
+ if (folder.startsWith(path) && !folder.equals(path)) {
+ // the File.delete operation is not recursive and would fail to remove
+ // a root dir that is not empty.
+ return false;
+ }
+ }
+
+ for (String filePath : mExistingFiles.keySet()) {
+ if (filePath.startsWith(path) && !filePath.equals(path)) {
+ // the File.delete operation is not recursive and would fail to remove
+ // a root dir that is not empty.
+ return false;
+ }
+ }
+
+ return mExistingFolders.remove(path);
+ }
+
+ @Override
+ public boolean mkdirs(@NonNull File file) {
+ //noinspection ConstantConditions
+ for (; file != null; file = file.getParentFile()) {
+ String path = getAgnosticAbsPath(file);
+ mExistingFolders.add(path);
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * TODO: this incorrectly recursively returns all files in subfolders
+ */
+ @NonNull
+ @Override
+ public File[] listFiles(@NonNull File file) {
+ TreeSet<File> files = new TreeSet<File>();
+
+ String path = getAgnosticAbsPath(file);
+ Pattern pathRE = Pattern.compile(
+ Pattern.quote(path + (path.endsWith("/") ? "" : '/')) +
+ "[^/]*");
+
+ for (String folder : mExistingFolders) {
+ if (pathRE.matcher(folder).matches()) {
+ files.add(new File(folder));
+ }
+ }
+ for (String filePath : mExistingFiles.keySet()) {
+ if (pathRE.matcher(filePath).matches()) {
+ files.add(new File(filePath));
+ }
+ }
+ return files.toArray(new File[files.size()]);
+ }
+
+ @Override
+ public boolean renameTo(@NonNull File oldFile, @NonNull File newFile) {
+ boolean renamed = false;
+
+ String oldPath = getAgnosticAbsPath(oldFile);
+ String newPath = getAgnosticAbsPath(newFile);
+ Pattern pathRE = Pattern.compile(
+ "^(" + Pattern.quote(oldPath) +
+ ")($|/.*)");
+
+ Set<String> newFolders = Sets.newTreeSet();
+ for (Iterator<String> it = mExistingFolders.iterator(); it.hasNext(); ) {
+ String folder = it.next();
+ Matcher m = pathRE.matcher(folder);
+ if (m.matches()) {
+ it.remove();
+ String newFolder = newPath + m.group(2);
+ newFolders.add(newFolder);
+ renamed = true;
+ }
+ }
+ mExistingFolders.addAll(newFolders);
+ newFolders.clear();
+
+ Map<String, FileInfo> newFiles = Maps.newTreeMap();
+ for (Iterator<Entry<String, FileInfo>> it = mExistingFiles.entrySet().iterator();
+ it.hasNext(); ) {
+ Entry<String, FileInfo> entry = it.next();
+ String filePath = entry.getKey();
+ Matcher m = pathRE.matcher(filePath);
+ if (m.matches()) {
+ it.remove();
+ String newFilePath = newPath + m.group(2);
+ newFiles.put(newFilePath, entry.getValue());
+ renamed = true;
+ }
+ }
+ mExistingFiles.putAll(newFiles);
+
+ return renamed;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p/>
+ * <em>TODO: we might want to overload this to read mock properties instead of a real file.</em>
+ */
+ @NonNull
+ @Override
+ public Properties loadProperties(@NonNull File file) {
+ Properties props = new Properties();
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ props.load(fis);
+ } catch (IOException ignore) {
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (Exception ignore) {}
+ }
+ }
+ return props;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p/>
+ * <em>Note that this uses the mock version of {@link #newFileOutputStream(File)} and thus
+ * records the write rather than actually performing it.</em>
+ */
+ @Override
+ public void saveProperties(
+ @NonNull File file,
+ @NonNull Properties props,
+ @NonNull String comments) throws IOException {
+ OutputStream fos = null;
+ try {
+ fos = newFileOutputStream(file);
+ props.store(fos, comments);
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public OutputStream newFileOutputStream(@NonNull File file) throws FileNotFoundException {
+ StringOutputStream os = new StringOutputStream(file);
+ mOutputStreams.add(os);
+ return os;
+ }
+
+ /**
+ * An {@link OutputStream} that will capture the stream as an UTF-8 string once properly closed
+ * and associate it to the given {@link File}.
+ */
+ public class StringOutputStream extends ByteArrayOutputStream {
+ private String mData;
+ private final File mFile;
+
+ public StringOutputStream(File file) {
+ mFile = file;
+ recordExistingFile(file);
+ }
+
+ public File getFile() {
+ return mFile;
+ }
+
+ /** Can be null if the stream has never been properly closed. */
+ public String getData() {
+ return mData;
+ }
+
+ /** Once the stream is properly closed, convert the byte array to an UTF-8 string */
+ @Override
+ public void close() throws IOException {
+ super.close();
+ mData = new String(toByteArray(), "UTF-8");
+ recordExistingFile(mFile.getAbsolutePath(), mData);
+ }
+
+ /** Returns a string representation suitable for unit tests validation. */
+ @Override
+ public synchronized String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('<').append(getAgnosticAbsPath(mFile)).append(": ");
+ if (mData == null) {
+ sb.append("(stream not closed properly)>");
+ } else {
+ sb.append('\'').append(mData).append("'>");
+ }
+ return sb.toString();
+ }
+ }
+
+ @NonNull
+ @Override
+ public InputStream newFileInputStream(@NonNull File file) throws FileNotFoundException {
+ FileInfo fi = mExistingFiles.get(getAgnosticAbsPath(file));
+ if (fi != null) {
+ byte[] content = fi.getContent();
+ if (content != null) {
+ return new ByteArrayInputStream(content);
+ }
+ }
+ throw new FileNotFoundException("Mock file has no content: " + getAgnosticAbsPath(file));
+ }
+
+ @Override
+ public long lastModified(@NonNull File file) {
+ FileInfo fi = mExistingFiles.get(getAgnosticAbsPath(file));
+ if (fi != null) {
+ return fi.getLastModified();
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean createNewFile(File file) throws IOException {
+ if (exists(file)) {
+ return false;
+ }
+ recordExistingFile(file);
+ return true;
+ }
+
+ // -----
+
+ private static class FileInfo {
+ private long mLastModified;
+ private byte[] mContent;
+
+ public FileInfo(long lastModified, @Nullable byte[] content) {
+ mLastModified = lastModified;
+ mContent = content;
+ }
+
+ public long getLastModified() {
+ return mLastModified;
+ }
+
+ @Nullable
+ public byte[] getContent() {
+ return mContent;
+ }
+
+ }
+}
diff --git a/repository/src/main/java/com/android/repository/util/InstallerUtil.java b/repository/src/main/java/com/android/repository/util/InstallerUtil.java
new file mode 100644
index 0000000..38197ef
--- /dev/null
+++ b/repository/src/main/java/com/android/repository/util/InstallerUtil.java
@@ -0,0 +1,365 @@
+/*
+ * 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.repository.util;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.License;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.Repository;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.UpdatablePackage;
+import com.android.repository.impl.installer.PackageInstaller;
+import com.android.repository.impl.manager.LocalRepoLoader;
+import com.android.repository.impl.meta.Archive;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.LocalPackageImpl;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.impl.meta.RevisionType;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.android.repository.io.FileOp;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import javax.xml.bind.JAXBElement;
+
+/**
+ * Utility methods for {@link PackageInstaller} implementations.
+ */
+public class InstallerUtil {
+
+ /**
+ * Unzips the given zipped input stream into the given directory.
+ *
+ * @param in The (zipped) input stream.
+ * @param out The directory into which to expand the files. Must exist.
+ * @param fop The {@link FileOp} to use for file operations.
+ * @param expectedSize Compressed size of the stream.
+ * @param progress Currently only used for logging.
+ * @throws IOException If we're unable to read or write.
+ */
+ public static void unzip(@NonNull File in, @NonNull File out, @NonNull FileOp fop,
+ long expectedSize, @NonNull ProgressIndicator progress)
+ throws IOException {
+ if (!fop.exists(out) || !fop.isDirectory(out)) {
+ throw new IllegalArgumentException("out must exist and be a directory.");
+ }
+ // ZipFile requires an actual (not mock) file, so make sure we have a real one.
+ in = fop.ensureRealFile(in);
+
+ progress.setText("Unzipping...");
+ double fraction = 0;
+ ZipFile zipFile = new ZipFile(in);
+ Enumeration entries = zipFile.getEntries();
+ while (entries.hasMoreElements()) {
+ ZipArchiveEntry entry = (ZipArchiveEntry) entries.nextElement();
+ String name = entry.getName();
+ File entryFile = new File(out, name);
+ progress.setSecondaryText(name);
+ if (entry.isDirectory()) {
+ if (!fop.exists(entryFile)) {
+ if (!fop.mkdirs(entryFile)) {
+ progress.logWarning("failed to mkdirs " + entryFile);
+ }
+ }
+ } else {
+ if (!fop.exists(entryFile)) {
+ File parent = entryFile.getParentFile();
+ if (parent != null && !fop.exists(parent)) {
+ fop.mkdirs(parent);
+ }
+ if (!fop.createNewFile(entryFile)) {
+ throw new IOException("Failed to create file " + entryFile);
+ }
+ }
+
+ int size;
+ byte[] buf = new byte[8192];
+ BufferedOutputStream bos = new BufferedOutputStream(
+ fop.newFileOutputStream(entryFile));
+ InputStream s = new BufferedInputStream(zipFile.getInputStream(entry));
+ try {
+ while ((size = s.read(buf)) > -1) {
+ bos.write(buf, 0, size);
+ fraction += ((double) entry.getCompressedSize() / expectedSize) *
+ ((double) size / entry.getSize());
+ progress.setFraction(fraction);
+ }
+ } finally {
+ bos.close();
+ s.close();
+ }
+ if (!fop.isWindows()) {
+ // get the mode and test if it contains the executable bit
+ int mode = entry.getUnixMode();
+ if ((mode & 0111) != 0) {
+ try {
+ fop.setExecutablePermission(entryFile);
+ } catch (IOException ignore) {}
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Writes out the XML for a {@link LocalPackageImpl} corresponding to the given {@link
+ * RemotePackage} to a {@code package.xml} file in {@code packageRoot}.
+ *
+ * @param p The package to convert to a local package and write out.
+ * @param packageRoot The location to write to. Must exist and be a directory.
+ * @param manager A {@link RepoManager} instance.
+ * @param fop The {@link FileOp} to use for file operations.
+ * @param progress Currently only used for logging.
+ * @throws IOException If we fail to write the output file.
+ */
+ public static void writePackageXml(@NonNull RemotePackage p, @NonNull File packageRoot,
+ @NonNull RepoManager manager, @NonNull FileOp fop, @NonNull ProgressIndicator progress)
+ throws IOException {
+ if (!fop.exists(packageRoot) || !fop.isDirectory(packageRoot)) {
+ throw new IllegalArgumentException("packageRoot must exist and be a directory.");
+ }
+ CommonFactory factory = (CommonFactory) manager.getCommonModule().createLatestFactory();
+ // Create the package.xml
+ Repository repo = factory.createRepositoryType();
+ LocalPackageImpl impl = LocalPackageImpl.create(p, manager);
+ repo.setLocalPackage(impl);
+ License l = p.getLicense();
+ if (l != null) {
+ repo.addLicense(l);
+ }
+ File packageXml = new File(packageRoot, LocalRepoLoader.PACKAGE_XML_FN);
+ OutputStream fos = fop.newFileOutputStream(packageXml);
+ JAXBElement<Repository> element = ((CommonFactory) manager.getCommonModule()
+ .createLatestFactory()).generateRepository(repo);
+ try {
+ SchemaModuleUtil
+ .marshal(element, p.getSource().getPermittedModules(), fos,
+ manager.getResourceResolver(progress), progress);
+ } finally {
+ fos.close();
+ }
+ }
+
+ /**
+ * Returns a URL corresponding to {@link Archive#getComplete()} of the given {@link
+ * RemotePackage}. If the url in the package is a relative url, resolves it by using the prefix
+ * of the url in the {@link RepositorySource} of the package.
+ *
+ * @return The resolved {@link URL}, or {@code null} if the given archive location is not
+ * parsable in its original or resolved form.
+ */
+ @Nullable
+ public static URL resolveCompleteArchiveUrl(@NonNull RemotePackage p,
+ @NonNull ProgressIndicator progress) {
+ Archive arch = p.getArchive();
+ String urlStr = arch.getComplete().getUrl();
+ URL url;
+ try {
+ url = new URL(urlStr);
+ } catch (MalformedURLException e) {
+ // If we don't have a real URL, it could be a relative URL. Pick up the URL prefix
+ // from the source.
+ try {
+ String sourceUrl = p.getSource().getUrl();
+ if (!sourceUrl.endsWith("/")) {
+ sourceUrl = sourceUrl.substring(0, sourceUrl.lastIndexOf('/') + 1);
+ }
+ urlStr = sourceUrl + urlStr;
+ url = new URL(urlStr);
+ } catch (MalformedURLException e2) {
+ progress.logWarning("Failed to parse url: " + urlStr);
+ return null;
+ }
+ }
+ return url;
+
+ }
+
+ /**
+ * Compute the complete list of packages that need to be installed to meet the dependencies of
+ * the given list (including the requested packages themselves, if they are not already installed).
+ * Returns {@code null} if we were unable to compute a complete list of dependencies due to not
+ * being able to find required packages of the specified version.
+ *
+ * Packages are returned in install order (that is, if we request A which depends on B, the
+ * result will be [B, A]). If a dependency cycle is encountered the order of the returned
+ * results at or below the cycle is undefined. For example if we have A -> [B, C], B -> D, and D
+ * -> B then the returned list will be either [B, D, C, A] or [D, B, C, A].
+ *
+ * Note that we assume installed packages already have their dependencies met.
+ */
+ @Nullable
+ public static List<RemotePackage> computeRequiredPackages(
+ @NonNull Collection<RemotePackage> requests, @NonNull RepositoryPackages packages,
+ @NonNull ProgressIndicator logger) {
+ Set<RemotePackage> requiredPackages = Sets.newHashSet();
+ Map<String, UpdatablePackage> consolidatedPackages = packages.getConsolidatedPkgs();
+
+ Set<String> seen = Sets.newHashSet();
+ Multimap<String, Dependency> allDependencies = HashMultimap.create();
+ Set<RemotePackage> roots = Sets.newHashSet();
+ Queue<RemotePackage> current = Lists.newLinkedList();
+ for (RemotePackage request : requests) {
+ UpdatablePackage updatable = consolidatedPackages.get(request.getPath());
+ if (!updatable.hasLocal() || updatable.isUpdate()) {
+ current.add(request);
+ roots.add(request);
+ requiredPackages.add(request);
+ seen.add(request.getPath());
+ }
+ }
+
+ while (!current.isEmpty()) {
+ RemotePackage currentPackage = current.remove();
+
+ Collection<Dependency> currentDependencies = currentPackage.getAllDependencies();
+ for (Dependency d : currentDependencies) {
+ String dependencyPath = d.getPath();
+ if (seen.contains(dependencyPath)) {
+ allDependencies.put(dependencyPath, d);
+ continue;
+ }
+ seen.add(dependencyPath);
+ UpdatablePackage updatableDependency = consolidatedPackages.get(dependencyPath);
+ if (updatableDependency == null) {
+ logger.logWarning(
+ String.format("Dependant package with key %s not found!",
+ dependencyPath));
+ return null;
+ }
+ LocalPackage localDependency = updatableDependency.getLocal();
+ Revision requiredMinRevision = null;
+ RevisionType r = d.getMinRevision();
+ if (r != null) {
+ requiredMinRevision = r.toRevision();
+ }
+ if ((requiredMinRevision == null && localDependency != null) ||
+ (requiredMinRevision != null && localDependency != null &&
+ requiredMinRevision.compareTo(localDependency.getVersion()) <= 0)) {
+ continue;
+ }
+ RemotePackage remoteDependency = updatableDependency.getRemote();
+ if (remoteDependency == null || (requiredMinRevision != null
+ && requiredMinRevision.compareTo(remoteDependency.getVersion()) > 0)) {
+ logger.logWarning(String
+ .format("Package \"%1$s\" with revision at least %2$s not available.",
+ updatableDependency.getRepresentative().getDisplayName(),
+ requiredMinRevision));
+ return null;
+ }
+
+ requiredPackages.add(remoteDependency);
+ allDependencies.put(dependencyPath, d);
+ current.add(remoteDependency);
+ // We had a dependency on it, so it can't be a root.
+ roots.remove(remoteDependency);
+ }
+ }
+
+ List<RemotePackage> result = Lists.newArrayList();
+
+ while (!roots.isEmpty()) {
+ RemotePackage root = roots.iterator().next();
+ roots.remove(root);
+ result.add(root);
+ for (Dependency d : root.getAllDependencies()) {
+ Collection<Dependency> nodeDeps = allDependencies.get(d.getPath());
+ if (nodeDeps.size() == 1) {
+ roots.add(consolidatedPackages.get(d.getPath()).getRemote());
+ }
+ nodeDeps.remove(d);
+ }
+ }
+
+ if (result.size() != requiredPackages.size()) {
+ logger.logInfo("Failed to sort dependencies, returning partially-sorted list.");
+ for (RemotePackage p : result) {
+ requiredPackages.remove(p);
+ }
+ result.addAll(requiredPackages);
+ }
+
+ return Lists.reverse(result);
+ }
+
+ /**
+ * Checks to see whether {@code path} is a valid install path. Specifically, checks whether
+ * there are any existing packages installed in parents or children of {@code path}.
+ * Returns {@code true} if the path is valid. Otherwise returns {@code false} and logs a
+ * warning.
+ */
+ public static boolean checkValidPath(@NonNull File path, @NonNull RepoManager manager,
+ @NonNull ProgressIndicator progress) {
+ try {
+ String check = path.getCanonicalPath() + File.separator;
+
+ for (LocalPackage p : manager.getPackages().getLocalPackages().values()) {
+ String existing = p.getLocation().getCanonicalPath() + File.separator;
+ if (!existing.equals(check)) {
+ boolean childExists = existing.startsWith(check);
+ boolean parentExists = check.startsWith(existing);
+ if (childExists || parentExists) {
+ StringBuilder message = new StringBuilder();
+ message.append("Trying to install into ")
+ .append(check)
+ .append(" but package \"")
+ .append(p.getDisplayName())
+ .append("\" already exists at ")
+ .append(existing)
+ .append(". It must be deleted or moved away before installing into a ")
+ .append(childExists ? "parent" : "child")
+ .append(" directory.");
+ progress.logWarning(message.toString());
+ return false;
+ }
+ }
+ }
+ }
+ catch (IOException e) {
+ progress.logWarning("Error while trying to check install path validity", e);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/repository/src/test/java/com/android/repository/RevisionTest.java b/repository/src/test/java/com/android/repository/RevisionTest.java
new file mode 100644
index 0000000..19f0078
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/RevisionTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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.repository;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+public class RevisionTest extends TestCase {
+
+ public final void testRevision() {
+
+ assertEquals("5", Revision.parseRevision("5").toString());
+ assertEquals("5.0", Revision.parseRevision("5.0").toString());
+ assertEquals("5.0.0", Revision.parseRevision("5.0.0").toString());
+ assertEquals("5.1.4", Revision.parseRevision("5.1.4").toString());
+ assertEquals("5.0.0", Revision.parseRevision("5", Revision.Precision.MICRO).toString());
+ assertEquals("5.1.0", Revision.parseRevision("5.1", Revision.Precision.MICRO).toString());
+
+ Revision p = new Revision(5);
+ assertEquals(5, p.getMajor());
+ assertEquals(Revision.IMPLICIT_MINOR_REV, p.getMinor());
+ assertEquals(Revision.IMPLICIT_MICRO_REV, p.getMicro());
+ assertEquals(Revision.NOT_A_PREVIEW, p.getPreview());
+ assertFalse(p.isPreview());
+ assertEquals("5", p.toShortString());
+ assertEquals(p, Revision.parseRevision("5"));
+ assertEquals("5", p.toString());
+ assertEquals(p, Revision.parseRevision("5"));
+ assertEquals("[5]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
+ assertEquals("[5]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
+
+ p = new Revision(5, 0);
+ assertEquals(5, p.getMajor());
+ assertEquals(0, p.getMinor());
+ assertEquals(Revision.IMPLICIT_MICRO_REV, p.getMicro());
+ assertEquals(Revision.NOT_A_PREVIEW, p.getPreview());
+ assertFalse(p.isPreview());
+ assertEquals("5", p.toShortString());
+ assertEquals(new Revision(5), Revision.parseRevision("5"));
+ assertEquals("5.0", p.toString());
+ assertEquals(p, Revision.parseRevision("5.0"));
+ assertEquals("[5, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
+ assertEquals("[5, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
+
+ p = new Revision(5, 0, 0);
+ assertEquals(5, p.getMajor());
+ assertEquals(0, p.getMinor());
+ assertEquals(0, p.getMicro());
+ assertEquals(Revision.NOT_A_PREVIEW, p.getPreview());
+ assertFalse(p.isPreview());
+ assertEquals("5", p.toShortString());
+ assertEquals(new Revision(5), Revision.parseRevision("5"));
+ assertEquals("5.0.0", p.toString());
+ assertEquals(p, Revision.parseRevision("5.0.0"));
+ assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
+ assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
+
+ p = new Revision(5, 0, 0, 6);
+ assertEquals(5, p.getMajor());
+ assertEquals(Revision.IMPLICIT_MINOR_REV, p.getMinor());
+ assertEquals(Revision.IMPLICIT_MICRO_REV, p.getMicro());
+ assertEquals(6, p.getPreview());
+ assertTrue(p.isPreview());
+ assertEquals("5 rc6", p.toShortString());
+ assertEquals("5.0.0 rc6", p.toString());
+ assertEquals(p, Revision.parseRevision("5.0.0 rc6"));
+ assertEquals("5.0.0-rc6", Revision.parseRevision("5.0.0-rc6").toString());
+ assertEquals("[5, 0, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
+ assertEquals("[5, 0, 0, 6]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
+
+ p = new Revision(6, 7, 0);
+ assertEquals(6, p.getMajor());
+ assertEquals(7, p.getMinor());
+ assertEquals(0, p.getMicro());
+ assertEquals(0, p.getPreview());
+ assertFalse(p.isPreview());
+ assertEquals("6.7", p.toShortString());
+ assertFalse(p.equals(Revision.parseRevision("6.7")));
+ assertEquals(new Revision(6, 7), Revision.parseRevision("6.7"));
+ assertEquals("6.7.0", p.toString());
+ assertEquals(p, Revision.parseRevision("6.7.0"));
+ assertEquals("[6, 7, 0]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
+ assertEquals("[6, 7, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
+
+ p = new Revision(10, 11, 12, Revision.NOT_A_PREVIEW);
+ assertEquals(10, p.getMajor());
+ assertEquals(11, p.getMinor());
+ assertEquals(12, p.getMicro());
+ assertEquals(0, p.getPreview());
+ assertFalse(p.isPreview());
+ assertEquals("10.11.12", p.toShortString());
+ assertEquals("10.11.12", p.toString());
+ assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
+ assertEquals("[10, 11, 12, 0]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
+
+ p = new Revision(10, 11, 12, 13);
+ assertEquals(10, p.getMajor());
+ assertEquals(11, p.getMinor());
+ assertEquals(12, p.getMicro());
+ assertEquals(13, p.getPreview());
+ assertTrue (p.isPreview());
+ assertEquals("10.11.12 rc13", p.toShortString());
+ assertEquals("10.11.12 rc13", p.toString());
+ assertEquals(p, Revision.parseRevision("10.11.12 rc13"));
+ assertEquals(p, Revision.parseRevision(" 10.11.12 rc13"));
+ assertEquals(p, Revision.parseRevision("10.11.12 rc13 "));
+ assertEquals(p, Revision.parseRevision(" 10.11.12 rc13 "));
+ assertEquals("[10, 11, 12]", Arrays.toString(p.toIntArray(false /*includePreview*/)));
+ assertEquals("[10, 11, 12, 13]", Arrays.toString(p.toIntArray(true /*includePreview*/)));
+ }
+
+ public final void testParseError() {
+ String errorMsg = null;
+ try {
+ Revision.parseRevision("not a number");
+ fail("Revision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid revision: not a number", errorMsg);
+
+ errorMsg = null;
+ try {
+ Revision.parseRevision("5 .6 .7");
+ fail("Revision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid revision: 5 .6 .7", errorMsg);
+
+ errorMsg = null;
+ try {
+ Revision.parseRevision("5.0.0 preview 1");
+ fail("Revision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid revision: 5.0.0 preview 1", errorMsg);
+
+ errorMsg = null;
+ try {
+ Revision.parseRevision(" 5.1.2 rc 42 ");
+ fail("Revision.parseRevision should thrown NumberFormatException");
+ } catch (NumberFormatException e) {
+ errorMsg = e.getMessage();
+ }
+ assertEquals("Invalid revision: 5.1.2 rc 42 ", errorMsg);
+ }
+
+ public final void testCompareTo() {
+ Revision s4 = new Revision(4);
+ Revision i4 = new Revision(4);
+ Revision g5 = new Revision(5, 1, 0, 6);
+ Revision y5 = new Revision(5);
+ Revision c5 = new Revision(5, 1, 0, 6);
+ Revision o5 = new Revision(5, 0, 0, 7);
+ Revision p5 = new Revision(5, 1, 0, 0);
+
+ assertEquals(s4, i4); // 4.0.0-0 == 4.0.0-0
+ assertEquals(g5, c5); // 5.1.0-6 == 5.1.0-6
+
+ assertFalse(y5.equals(p5)); // 5.0.0-0 != 5.1.0-0
+ assertFalse(g5.equals(p5)); // 5.1.0-6 != 5.1.0-0
+ assertTrue (s4.compareTo(i4) == 0); // 4.0.0-0 == 4.0.0-0
+ assertTrue (s4.compareTo(y5) < 0); // 4.0.0-0 < 5.0.0-0
+ assertTrue (y5.compareTo(y5) == 0); // 5.0.0-0 == 5.0.0-0
+ assertTrue (y5.compareTo(p5) < 0); // 5.0.0-0 < 5.1.0-0
+ assertTrue (o5.compareTo(y5) < 0); // 5.0.0-7 < 5.0.0-0
+ assertTrue (p5.compareTo(p5) == 0); // 5.1.0-0 == 5.1.0-0
+ assertTrue (c5.compareTo(p5) < 0); // 5.1.0-6 < 5.1.0-0
+ assertTrue (p5.compareTo(c5) > 0); // 5.1.0-0 > 5.1.0-6
+ assertTrue (p5.compareTo(o5) > 0); // 5.1.0-0 > 5.0.0-7
+ assertTrue (c5.compareTo(o5) > 0); // 5.1.0-6 > 5.0.0-7
+ assertTrue (o5.compareTo(o5) == 0); // 5.0.0-7 > 5.0.0-7
+ }
+}
\ No newline at end of file
diff --git a/repository/src/test/java/com/android/repository/impl/installer/BasicInstallerTest.java b/repository/src/test/java/com/android/repository/impl/installer/BasicInstallerTest.java
new file mode 100644
index 0000000..affe663
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/installer/BasicInstallerTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.repository.impl.installer;
+
+import com.android.repository.Revision;
+import com.android.repository.api.ConstantSourceProvider;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.impl.manager.RepoManagerImpl;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.testframework.FakeDownloader;
+import com.android.repository.testframework.FakePackage;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.FakeProgressRunner;
+import com.android.repository.testframework.FakeSettingsController;
+import com.android.repository.testframework.MockFileOp;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.net.URL;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Tests for {@link BasicInstaller}.
+ *
+ * TODO: more tests.
+ */
+public class BasicInstallerTest extends TestCase {
+
+ public void testDelete() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ // Record package.xmls for two packages.
+ fop.recordExistingFile("/repo/dummy/foo/package.xml", ByteStreams
+ .toByteArray(getClass().getResourceAsStream("../testData/testPackage.xml")));
+ fop.recordExistingFile("/repo/dummy/bar/package.xml", ByteStreams
+ .toByteArray(getClass().getResourceAsStream("../testData/testPackage2.xml")));
+
+ // Set up a RepoManager.
+ RepoManager mgr = new RepoManagerImpl(fop);
+ File root = new File("/repo");
+ mgr.setLocalPath(root);
+
+ FakeProgressRunner runner = new FakeProgressRunner();
+ // Load the local packages.
+ mgr.load(0, ImmutableList.<RepoManager.RepoLoadedCallback>of(),
+ ImmutableList.<RepoManager.RepoLoadedCallback>of(), ImmutableList.<Runnable>of(),
+ runner, new FakeDownloader(fop), new FakeSettingsController(false), true);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+ RepositoryPackages pkgs = mgr.getPackages();
+
+ // Get one of the packages to uninstall.
+ LocalPackage p = pkgs.getLocalPackages().get("dummy;foo");
+ // Uninstall it
+ new BasicInstaller().uninstall(p, new FakeProgressIndicator(), mgr, fop);
+ File[] contents = fop.listFiles(root);
+ // Verify that the deleted dir is gone.
+ assertEquals(1, contents.length);
+ contents = fop.listFiles(contents[0]);
+ assertEquals(1, contents.length);
+ assertEquals(new File("/repo/dummy/bar"), contents[0]);
+ }
+
+ // Test installing a new package
+ public void testInstallFresh() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ // We have a different package installed already.
+ fop.recordExistingFile("/repo/dummy/foo/package.xml", ByteStreams
+ .toByteArray(getClass().getResourceAsStream("../testData/testPackage.xml")));
+ RepoManager mgr = new RepoManagerImpl(fop);
+ File root = new File("/repo");
+ mgr.setLocalPath(root);
+ FakeDownloader downloader = new FakeDownloader(fop);
+ URL repoUrl = new URL("http://example.com/dummy.xml");
+
+ // The repo we're going to download
+ downloader.registerUrl(repoUrl, getClass().getResourceAsStream("../testData/testRepo.xml"));
+
+ // Create the archive and register the URL
+ URL archiveUrl = new URL("http://example.com/2/arch1");
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
+ ZipOutputStream zos = new ZipOutputStream(baos);
+ zos.putNextEntry(new ZipEntry("top-level/a"));
+ zos.write("contents1".getBytes());
+ zos.closeEntry();
+ zos.putNextEntry(new ZipEntry("top-level/dir/b"));
+ zos.write("contents2".getBytes());
+ zos.closeEntry();
+ zos.close();
+ ByteArrayInputStream is = new ByteArrayInputStream(baos.toByteArray());
+ downloader.registerUrl(archiveUrl, is);
+
+ // Register a source provider to get the repo
+ mgr.registerSourceProvider(new ConstantSourceProvider(repoUrl.toString(), "dummy",
+ ImmutableList.of(mgr.getGenericModule())));
+ FakeProgressRunner runner = new FakeProgressRunner();
+
+ // Load
+ mgr.load(RepoManager.DEFAULT_EXPIRATION_PERIOD_MS,
+ ImmutableList.<RepoManager.RepoLoadedCallback>of(),
+ ImmutableList.<RepoManager.RepoLoadedCallback>of(), ImmutableList.<Runnable>of(),
+ runner, downloader, new FakeSettingsController(false), true);
+
+ // Ensure we picked up the local package.
+ RepositoryPackages pkgs = mgr.getPackages();
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+ assertEquals(1, pkgs.getLocalPackages().size());
+ assertEquals(2, pkgs.getRemotePackages().size());
+
+ // Install one of the packages.
+ new BasicInstaller().install(pkgs.getRemotePackages().get("dummy;bar"),
+ downloader, new FakeSettingsController(false), runner.getProgressIndicator(), mgr,
+ fop);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+
+ // Reload the packages.
+ mgr.load(0, ImmutableList.<RepoManager.RepoLoadedCallback>of(),
+ ImmutableList.<RepoManager.RepoLoadedCallback>of(), ImmutableList.<Runnable>of(),
+ runner, downloader, new FakeSettingsController(false), true);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+ File[] contents = fop.listFiles(new File(root, "dummy"));
+
+ // Ensure it was installed on the filesystem
+ assertEquals(2, contents.length);
+ assertEquals(new File(root, "dummy/bar"), contents[0]);
+ assertEquals(new File(root, "dummy/foo"), contents[1]);
+
+ // Ensure it was recognized as a package.
+ Map<String, ? extends LocalPackage> locals = mgr.getPackages().getLocalPackages();
+ assertEquals(2, locals.size());
+ assertTrue(locals.containsKey("dummy;bar"));
+ LocalPackage newPkg = locals.get("dummy;bar");
+ assertEquals("Test package 2", newPkg.getDisplayName());
+ assertEquals("license text 2", newPkg.getLicense().getValue().trim());
+ assertEquals(new Revision(4, 5, 6), newPkg.getVersion());
+ }
+
+ // Test installing an upgrade to an existing package.
+ public void testInstallUpgrade() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ // Record a couple existing packages.
+ fop.recordExistingFile("/repo/dummy/foo/package.xml", ByteStreams
+ .toByteArray(getClass().getResourceAsStream("../testData/testPackage.xml")));
+ fop.recordExistingFile("/repo/dummy/bar/package.xml", ByteStreams.toByteArray(
+ getClass().getResourceAsStream("../testData/testPackage2-lowerVersion.xml")));
+ RepoManager mgr = new RepoManagerImpl(fop);
+ File root = new File("/repo");
+ mgr.setLocalPath(root);
+
+ // Create the archive and register the repo to be downloaded.
+ FakeDownloader downloader = new FakeDownloader(fop);
+ URL repoUrl = new URL("http://example.com/dummy.xml");
+ downloader.registerUrl(repoUrl, getClass().getResourceAsStream("../testData/testRepo.xml"));
+ URL archiveUrl = new URL("http://example.com/2/arch1");
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
+ ZipOutputStream zos = new ZipOutputStream(baos);
+ zos.putNextEntry(new ZipEntry("top-level/a"));
+ zos.write("contents1".getBytes());
+ zos.closeEntry();
+ zos.putNextEntry(new ZipEntry("top-level/dir/b"));
+ zos.write("contents2".getBytes());
+ zos.closeEntry();
+ zos.close();
+ ByteArrayInputStream is = new ByteArrayInputStream(baos.toByteArray());
+ downloader.registerUrl(archiveUrl, is);
+
+ // Register the source provider
+ mgr.registerSourceProvider(new ConstantSourceProvider(repoUrl.toString(), "dummy",
+ ImmutableList.of(RepoManager.getGenericModule())));
+ FakeProgressRunner runner = new FakeProgressRunner();
+
+ // Load
+ mgr.load(RepoManager.DEFAULT_EXPIRATION_PERIOD_MS,
+ ImmutableList.<RepoManager.RepoLoadedCallback>of(),
+ ImmutableList.<RepoManager.RepoLoadedCallback>of(), ImmutableList.<Runnable>of(),
+ runner, downloader, new FakeSettingsController(false), true);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+ RepositoryPackages pkgs = mgr.getPackages();
+
+ // Ensure the old local package was found with the right version
+ Map<String, ? extends LocalPackage> locals = mgr.getPackages().getLocalPackages();
+ LocalPackage oldPkg = locals.get("dummy;bar");
+ assertEquals(new Revision(3), oldPkg.getVersion());
+
+ // Ensure the new package is found with the right version
+ RemotePackage update = pkgs.getRemotePackages().get("dummy;bar");
+ assertEquals(new Revision(4, 5, 6), update.getVersion());
+
+ // Install the update
+ new BasicInstaller().install(update, downloader, new FakeSettingsController(false),
+ new FakeProgressIndicator(), mgr, fop);
+
+ // Reload the repo
+ mgr.load(0, ImmutableList.<RepoManager.RepoLoadedCallback>of(),
+ ImmutableList.<RepoManager.RepoLoadedCallback>of(), ImmutableList.<Runnable>of(),
+ runner, downloader, new FakeSettingsController(false), true);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+
+ // Ensure the files are still there
+ File[] contents = fop.listFiles(new File(root, "dummy"));
+ assertEquals(2, contents.length);
+ assertEquals(new File(root, "dummy/bar"), contents[0]);
+ assertEquals(new File(root, "dummy/foo"), contents[1]);
+
+ // Ensure the packages are still there
+ locals = mgr.getPackages().getLocalPackages();
+ assertEquals(2, locals.size());
+ assertTrue(locals.containsKey("dummy;bar"));
+ LocalPackage newPkg = locals.get("dummy;bar");
+ assertEquals("Test package 2", newPkg.getDisplayName());
+ assertEquals("license text 2", newPkg.getLicense().getValue().trim());
+
+ // Ensure the update was installed
+ assertEquals(new Revision(4, 5, 6), newPkg.getVersion());
+
+ // TODO: verify the actual contents of the update?
+ }
+
+ public void testInstallInChild() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile("/sdk/foo/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"foo\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.setLocalPath(new File("/sdk"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ mgr.loadSynchronously(0, progress, null, null);
+
+ FakePackage remote = new FakePackage("foo;bar", new Revision(1), null);
+ remote.setCompleteUrl("http://www.example.com/package.zip");
+ FakeDownloader downloader = new FakeDownloader(fop);
+
+ assertFalse(new BasicInstaller()
+ .install(remote, downloader, new FakeSettingsController(false), progress, mgr,
+ fop));
+ boolean found = false;
+ for (String warning : progress.getWarnings()) {
+ if (warning.contains("child")) {
+ found = true;
+ }
+ }
+ assertTrue(found);
+ }
+
+ public void testInstallInParent() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile("/sdk/foo/bar/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"foo;bar\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.setLocalPath(new File("/sdk"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ mgr.loadSynchronously(0, progress, null, null);
+
+ FakePackage remote = new FakePackage("foo", new Revision(1), null);
+ remote.setCompleteUrl("http://www.example.com/package.zip");
+ FakeDownloader downloader = new FakeDownloader(fop);
+
+ assertFalse(new BasicInstaller()
+ .install(remote, downloader, new FakeSettingsController(false), progress, mgr,
+ fop));
+ boolean found = false;
+ for (String warning : progress.getWarnings()) {
+ if (warning.contains("parent")) {
+ found = true;
+ }
+ }
+ assertTrue(found);
+ }
+}
diff --git a/repository/src/test/java/com/android/repository/impl/local/LocalRepoTest.java b/repository/src/test/java/com/android/repository/impl/local/LocalRepoTest.java
new file mode 100644
index 0000000..caf124c
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/local/LocalRepoTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.repository.impl.local;
+
+import com.android.repository.Revision;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.License;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.Repository;
+import com.android.repository.impl.manager.LocalRepoLoader;
+import com.android.repository.impl.manager.RepoManagerImpl;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.GenericFactory;
+import com.android.repository.impl.meta.LocalPackageImpl;
+import com.android.repository.impl.meta.RevisionType;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.google.common.collect.ImmutableSet;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * Tests for {@link LocalRepoLoader}.
+ */
+public class LocalRepoTest extends TestCase {
+
+ // Test that we can parse a basic package.
+ public void testParseGeneric() throws Exception {
+ MockFileOp mockFop = new MockFileOp();
+ mockFop.recordExistingFolder("/repo/random");
+ mockFop.recordExistingFile("/repo/random/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + "\n"
+ + " <license type=\"text\" id=\"license1\">\n"
+ + " This is the license\n"
+ + " for this platform.\n"
+ + " </license>\n"
+ + "\n"
+ + " <localPackage path=\"random\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " <uses-license ref=\"license1\"/>\n"
+ + " <dependencies>\n"
+ + " <dependency path=\"tools\">\n"
+ + " <min-revision>\n"
+ + " <major>2</major>\n"
+ + " <micro>1</micro>\n"
+ + " </min-revision>\n"
+ + " </dependency>\n"
+ + " </dependencies>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>"
+ );
+
+ RepoManager manager = RepoManager.create(mockFop);
+ LocalRepoLoader localLoader = new LocalRepoLoader(new File("/repo"), manager, null,
+ mockFop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ LocalPackage p = localLoader.getPackages(progress).get("random");
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(new Revision(3), p.getVersion());
+ assertEquals("This is the license for this platform.", p.getLicense().getValue());
+ assertTrue(p.getTypeDetails() instanceof TypeDetails.GenericType);
+ assertEquals("The first Android platform ever", p.getDisplayName());
+ // TODO: validate package in more detail
+ }
+
+ // Test writing a package out to xml
+ public void testMarshalGeneric() throws Exception {
+ RepoManager manager = new RepoManagerImpl(new MockFileOp());
+
+ CommonFactory factory = (CommonFactory)manager.getCommonModule().createLatestFactory();
+ GenericFactory genericFactory = (GenericFactory) manager.getGenericModule()
+ .createLatestFactory();
+ Repository repo = factory.createRepositoryType();
+ LocalPackageImpl p = factory.createLocalPackage();
+ License license = factory.createLicenseType("some license text", "license1");
+ p.setLicense(license);
+ p.setPath("dummy;path");
+ p.setVersion(new Revision(1, 2));
+ p.setDisplayName("package name");
+ p.setTypeDetails((TypeDetails) genericFactory.createGenericDetailsType());
+ Dependency dep = factory.createDependencyType();
+ dep.setPath("depId1");
+ RevisionType r = factory.createRevisionType(new Revision(1, 2, 3));
+ dep.setMinRevision(r);
+ p.addDependency(dep);
+ dep = factory.createDependencyType();
+ dep.setPath("depId2");
+ p.addDependency(dep);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ repo.setLocalPackage(p);
+ repo.getLicense().add(license);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ SchemaModuleUtil.marshal(
+ ((CommonFactory) RepoManager.getCommonModule().createLatestFactory())
+ .generateRepository(repo),
+ ImmutableSet.of(manager.getGenericModule()), output,
+ manager.getResourceResolver(progress), progress);
+ progress.assertNoErrorsOrWarnings();
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+
+ dbf.setNamespaceAware(true);
+ dbf.setSchema(SchemaModuleUtil.getSchema(ImmutableSet.of(manager.getGenericModule()),
+ SchemaModuleUtil.createResourceResolver(ImmutableSet.of(manager.getCommonModule()),
+ progress),
+ progress));
+ progress.assertNoErrorsOrWarnings();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+
+ db.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException exception) throws SAXException {
+ throw exception;
+ }
+
+ @Override
+ public void error(SAXParseException exception) throws SAXException {
+ throw exception;
+ }
+
+ @Override
+ public void fatalError(SAXParseException exception) throws SAXException {
+ throw exception;
+ }
+ });
+
+ // Can't just check the output against expected directly, since e.g. attribute node order
+ // can change.
+ String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns3:repository "
+ + "xmlns:ns2=\"http://schemas.android.com/repository/android/generic/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/repository/android/common/01\">"
+ + "<license type=\"text\" id=\"license1\">some license text</license>"
+ + "<localPackage path=\"dummy;path\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns2:genericDetailsType\"/>"
+ + "<revision><major>1</major><minor>2</minor></revision>"
+ + "<display-name>package name</display-name>"
+ + "<uses-license ref=\"license1\"/>"
+ + "<dependencies>"
+ + "<dependency path=\"depId1\">"
+ + "<min-revision><major>1</major><minor>2</minor><micro>3</micro></min-revision>"
+ + "</dependency>"
+ + "<dependency path=\"depId2\"/></dependencies></localPackage></ns3:repository>";
+ Document doc = db.parse(new ByteArrayInputStream(output.toByteArray()));
+ Document doc2 = db.parse(new ByteArrayInputStream(expected.getBytes()));
+ assertTrue(doc.isEqualNode(doc2));
+ }
+
+ // Test that a package in an inconsistent location gives a warning.
+ public void testWrongPath() throws Exception {
+ MockFileOp mockFop = new MockFileOp();
+ mockFop.recordExistingFile("/repo/bogus/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + "\n"
+ + " <localPackage path=\"random\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>"
+ );
+
+ RepoManager manager = RepoManager.create(mockFop);
+ LocalRepoLoader localLoader = new LocalRepoLoader(new File("/repo"), manager, null,
+ mockFop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ LocalPackage p = localLoader.getPackages(progress).get("random");
+ assertEquals(new Revision(3), p.getVersion());
+ assertTrue(!progress.getWarnings().isEmpty());
+ }
+
+ // Test that a package in an inconsistent is overridden by one in the right place
+ public void testDuplicate() throws Exception {
+ MockFileOp mockFop = new MockFileOp();
+ mockFop.recordExistingFile("/repo/bogus/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + "\n"
+ + " <localPackage path=\"random\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>1</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>"
+ );
+ mockFop.recordExistingFile("/repo/random/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + "\n"
+ + " <localPackage path=\"random\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>"
+ );
+
+ RepoManager manager = RepoManager.create(mockFop);
+ LocalRepoLoader localLoader = new LocalRepoLoader(new File("/repo"), manager, null,
+ mockFop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ LocalPackage p = localLoader.getPackages(progress).get("random");
+ assertEquals(new Revision(3), p.getVersion());
+ assertTrue(!progress.getWarnings().isEmpty());
+ }
+
+ // todo: test strictness
+}
diff --git a/repository/src/test/java/com/android/repository/impl/meta/RepositoryPackagesTest.java b/repository/src/test/java/com/android/repository/impl/meta/RepositoryPackagesTest.java
new file mode 100644
index 0000000..68e8807
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/meta/RepositoryPackagesTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.repository.impl.meta;
+
+import com.android.repository.Revision;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.UpdatablePackage;
+import com.android.repository.testframework.FakePackage;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for {@link RepositoryPackages}
+ */
+public class RepositoryPackagesTest extends TestCase {
+
+ private RepositoryPackages mPackages;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Map<String, LocalPackage> locals = Maps.newHashMap();
+ Map<String, RemotePackage> remotes = Maps.newHashMap();
+
+ // p1 has no corresponding remote
+ locals.put("p1", new FakePackage("p1", new Revision(1), null));
+
+ // p2 has an updated remote
+ locals.put("p2", new FakePackage("p2", new Revision(1), null));
+ remotes.put("p2", new FakePackage("p2", new Revision(2), null));
+
+ // p3 has a non-updated remote
+ locals.put("p3", new FakePackage("p3", new Revision(1), null));
+ remotes.put("p3", new FakePackage("p3", new Revision(1), null));
+
+ // p4 is only remote
+ remotes.put("p4", new FakePackage("p4", new Revision(1), null));
+
+ mPackages = new RepositoryPackages(locals, remotes);
+ }
+
+ public void testConsolidated() throws Exception {
+ Map<String, UpdatablePackage> consolidated = mPackages.getConsolidatedPkgs();
+ assertEquals(4, consolidated.size());
+
+ UpdatablePackage updatable = consolidated.get("p1");
+ assertFalse(updatable.isUpdate());
+ assertTrue(updatable.hasLocal());
+ assertFalse(updatable.hasRemote());
+ assertEquals(new Revision(1), updatable.getRepresentative().getVersion());
+ assertEquals("p1", updatable.getRepresentative().getPath());
+
+ updatable = consolidated.get("p2");
+ assertTrue(updatable.isUpdate());
+ assertTrue(updatable.hasLocal());
+ assertTrue(updatable.hasRemote());
+ assertEquals(new Revision(1), updatable.getLocal().getVersion());
+ assertEquals(new Revision(2), updatable.getRemote().getVersion());
+ assertEquals("p2", updatable.getRepresentative().getPath());
+
+ updatable = consolidated.get("p3");
+ assertFalse(updatable.isUpdate());
+ assertTrue(updatable.hasLocal());
+ assertTrue(updatable.hasRemote());
+ assertEquals(new Revision(1), updatable.getRepresentative().getVersion());
+ assertEquals("p3", updatable.getRepresentative().getPath());
+
+ updatable = consolidated.get("p4");
+ assertFalse(updatable.isUpdate());
+ assertFalse(updatable.hasLocal());
+ assertTrue(updatable.hasRemote());
+ assertEquals(new Revision(1), updatable.getRemote().getVersion());
+ assertEquals("p4", updatable.getRepresentative().getPath());
+ }
+
+ public void testNew() {
+ Set<RemotePackage> news = mPackages.getNewPkgs();
+
+ assertEquals(1, news.size());
+ assertEquals("p4", news.iterator().next().getPath());
+ }
+
+ public void testUpdates() {
+ Set<UpdatablePackage> updates = mPackages.getUpdatedPkgs();
+
+ assertEquals(1, updates.size());
+ assertEquals("p2", updates.iterator().next().getRepresentative().getPath());
+ }
+
+ public void testPrefixes() {
+ Map<String, LocalPackage> locals = Maps.newHashMap();
+
+ FakePackage p1 = new FakePackage("a;b;c", new Revision(1), null);
+ locals.put("a;b;c", p1);
+ FakePackage p2 = new FakePackage("a;b;d", new Revision(1), null);
+ locals.put("a;b;d", p2);
+ FakePackage p3 = new FakePackage("a;c", new Revision(1), null);
+ locals.put("a;c", p3);
+ FakePackage p4 = new FakePackage("d", new Revision(1), null);
+ locals.put("d", p4);
+
+ RepositoryPackages packages = new RepositoryPackages();
+ packages.setLocalPkgInfos(locals);
+
+ Collection<? extends LocalPackage> p = packages.getLocalPackagesForPrefix("a");
+ assertEquals(3, p.size());
+ assertTrue(p.containsAll(Sets.newHashSet(p1, p2, p3)));
+
+ p = packages.getLocalPackagesForPrefix("a;b");
+ assertEquals(2, p.size());
+ assertTrue(p.containsAll(Sets.newHashSet(p1, p2)));
+
+ p = packages.getLocalPackagesForPrefix("a;b;c");
+ assertEquals(1, p.size());
+ assertTrue(p.contains(p1));
+
+ p = packages.getLocalPackagesForPrefix("a;b;f");
+ assertEquals(0, p.size());
+ }
+
+}
diff --git a/repository/src/test/java/com/android/repository/impl/meta/TrimStringAdapterTest.java b/repository/src/test/java/com/android/repository/impl/meta/TrimStringAdapterTest.java
new file mode 100644
index 0000000..eb3ef92
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/meta/TrimStringAdapterTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.repository.impl.meta;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link TrimStringAdapter}.
+ */
+public class TrimStringAdapterTest extends TestCase {
+
+ public void testSimple() {
+ assertEquals("foo", evaluate("foo"));
+ }
+
+ public void testRepeatedSpace() {
+ assertEquals("foo bar", evaluate("foo \t \t\tbar"));
+ }
+
+ public void testLeadingAndTrailing() {
+ assertEquals("foo", evaluate(" \tfoo "));
+ }
+
+ public void testLeadingNewline() {
+ assertEquals("foo", evaluate("\n foo"));
+ }
+
+ public void testSingleNewlines() {
+ assertEquals("foo bar baz", evaluate("foo \n bar\nbaz\n"));
+ }
+
+ public void testRepeatedNewlines() {
+ assertEquals("foo\n\nbar baz", evaluate("foo\n \n bar\n baz"));
+ }
+
+ private static String evaluate(String in) {
+ return new TrimStringAdapter().unmarshal(in);
+ }
+}
diff --git a/repository/src/test/java/com/android/repository/impl/remote/RemoteRepoTest.java b/repository/src/test/java/com/android/repository/impl/remote/RemoteRepoTest.java
new file mode 100644
index 0000000..02a0e77
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/remote/RemoteRepoTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.repository.impl.remote;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.Channel;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.FallbackRemoteRepoLoader;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.RepositorySourceProvider;
+import com.android.repository.api.SettingsController;
+import com.android.repository.api.SimpleRepositorySource;
+import com.android.repository.impl.manager.RemoteRepoLoader;
+import com.android.repository.impl.meta.Archive;
+import com.android.repository.impl.meta.RemotePackageImpl;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.testframework.FakeDownloader;
+import com.android.repository.testframework.FakePackage;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.FakeSettingsController;
+import com.android.repository.testframework.MockFileOp;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import junit.framework.TestCase;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for {@link com.android.repository.impl.manager.RemoteRepoLoader}
+ */
+public class RemoteRepoTest extends TestCase {
+
+ public void testRemoteRepo() throws Exception {
+ RepositorySource source = new SimpleRepositorySource("http://www.example.com",
+ "Source UI Name", true,
+ ImmutableSet.of(RepoManager.getGenericModule()),
+ null);
+ FakeDownloader downloader = new FakeDownloader(new MockFileOp());
+ downloader.registerUrl(new URL("http://www.example.com"),
+ getClass().getResourceAsStream("../testData/testRepo.xml"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RemoteRepoLoader loader = new RemoteRepoLoader(ImmutableList.<RepositorySourceProvider>of(
+ new FakeRepositorySourceProvider(ImmutableList.of(source))), null, null);
+ Map<String, RemotePackage> pkgs = loader
+ .fetchPackages(progress, downloader, new FakeSettingsController(false));
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(2, pkgs.size());
+ RemotePackage p1 = pkgs.get("dummy;foo");
+ assertEquals(new Revision(1, 2, 3), p1.getVersion());
+ assertEquals("the license text", p1.getLicense().getValue().trim());
+ assertEquals(3, ((RemotePackageImpl) p1).getAllArchives().size());
+ assertTrue(p1.getTypeDetails() instanceof TypeDetails.GenericType);
+ Collection<Archive> archives = ((RemotePackageImpl) p1).getAllArchives();
+ assertEquals(3, archives.size());
+ Iterator<Archive> archiveIter = ((RemotePackageImpl) p1).getAllArchives().iterator();
+ Archive a1 = archiveIter.next();
+ assertEquals(1234, a1.getComplete().getSize());
+ Archive a2 = archiveIter.next();
+ Iterator<Archive.PatchType> patchIter = a2.getAllPatches().iterator();
+ Archive.PatchType patch = patchIter.next();
+ assertEquals(new Revision(1, 3, 2), patch.getBasedOn().toRevision());
+ patch = patchIter.next();
+ assertEquals(new Revision(2), patch.getBasedOn().toRevision());
+ }
+
+ public void testChannels() throws Exception {
+ RepositorySource source = new SimpleRepositorySource("http://www.example.com",
+ "Source UI Name", true,
+ ImmutableSet.of(RepoManager.getCommonModule(),
+ RepoManager.getGenericModule()),
+ null);
+ FakeDownloader downloader = new FakeDownloader(new MockFileOp());
+ downloader.registerUrl(new URL("http://www.example.com"),
+ getClass().getResourceAsStream("../testData/testRepoWithChannels.xml"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RemoteRepoLoader loader = new RemoteRepoLoader(ImmutableList.<RepositorySourceProvider>of(
+ new FakeRepositorySourceProvider(ImmutableList.of(source))), null, null);
+ FakeSettingsController settings = new FakeSettingsController(false);
+ Map<String, RemotePackage> pkgs = loader
+ .fetchPackages(progress, downloader, settings);
+ progress.assertNoErrorsOrWarnings();
+
+ assertEquals(2, pkgs.size());
+ assertEquals(new Revision(1, 2, 3), pkgs.get("dummy;foo").getVersion());
+ assertEquals(new Revision(4, 5, 6), pkgs.get("dummy;bar").getVersion());
+
+ settings.setChannel(Channel.create(1));
+ pkgs = loader.fetchPackages(progress, downloader, settings);
+ assertEquals(2, pkgs.size());
+ assertEquals(new Revision(1, 2, 4), pkgs.get("dummy;foo").getVersion());
+ assertEquals(new Revision(4, 5, 6), pkgs.get("dummy;bar").getVersion());
+
+ settings.setChannel(Channel.create(2));
+ pkgs = loader.fetchPackages(progress, downloader, settings);
+ assertEquals(2, pkgs.size());
+ assertEquals(new Revision(1, 2, 5), pkgs.get("dummy;foo").getVersion());
+ assertEquals(new Revision(4, 5, 6), pkgs.get("dummy;bar").getVersion());
+
+ settings.setChannel(Channel.create(3));
+ pkgs = loader.fetchPackages(progress, downloader, settings);
+ assertEquals(2, pkgs.size());
+ assertEquals(new Revision(1, 2, 5), pkgs.get("dummy;foo").getVersion());
+ assertEquals(new Revision(4, 5, 7), pkgs.get("dummy;bar").getVersion());
+ }
+
+ public void testFallback() throws Exception {
+ RepositorySource source = new SimpleRepositorySource("http://www.example.com",
+ "Source UI Name", true,
+ ImmutableSet.of(RepoManager.getGenericModule()),
+ null);
+ final String legacyUrl = "http://www.example.com/legacy";
+ RepositorySource legacySource = new SimpleRepositorySource(legacyUrl,
+ "Legacy UI Name", true, ImmutableSet.of(RepoManager.getGenericModule()),
+ null);
+ FakeDownloader downloader = new FakeDownloader(new MockFileOp());
+ downloader.registerUrl(new URL("http://www.example.com"),
+ getClass().getResourceAsStream("../testData/testRepo.xml"));
+ downloader.registerUrl(new URL(legacyUrl),
+ "foo".getBytes());
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RemoteRepoLoader loader = new RemoteRepoLoader(ImmutableList.<RepositorySourceProvider>of(
+ new FakeRepositorySourceProvider(ImmutableList.of(source, legacySource))), null,
+ new FallbackRemoteRepoLoader() {
+ @Nullable
+ @Override
+ public Collection<RemotePackage> parseLegacyXml(
+ @NonNull RepositorySource source,
+ @NonNull ProgressIndicator progress) {
+ assertEquals(legacyUrl, source.getUrl());
+ FakePackage legacy = new FakePackage("legacy", new Revision(1, 2, 9),
+ null);
+ legacy.setCompleteUrl("http://www.example.com/legacy.zip");
+ return ImmutableSet.<RemotePackage>of(legacy);
+ }
+ });
+ Map<String, RemotePackage> pkgs = loader
+ .fetchPackages(progress, downloader, new FakeSettingsController(false));
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(3, pkgs.size());
+ assertEquals(new Revision(1, 2, 9), pkgs.get("legacy").getVersion());
+ }
+
+ public void testNonFallbackPreferred() throws Exception {
+ RepositorySource source = new SimpleRepositorySource("http://www.example.com",
+ "Source UI Name", true,
+ ImmutableSet.of(RepoManager.getGenericModule()),
+ null);
+ final String legacyUrl = "http://www.example.com/legacy";
+ RepositorySource legacySource = new SimpleRepositorySource(legacyUrl,
+ "Legacy UI Name", true, ImmutableSet.of(RepoManager.getGenericModule()),
+ null);
+ FakeDownloader downloader = new FakeDownloader(new MockFileOp());
+ downloader.registerUrl(new URL("http://www.example.com"),
+ getClass().getResourceAsStream("../testData/testRepo.xml"));
+ downloader.registerUrl(new URL(legacyUrl),
+ "foo".getBytes());
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RemoteRepoLoader loader = new RemoteRepoLoader(ImmutableList.<RepositorySourceProvider>of(
+ new FakeRepositorySourceProvider(ImmutableList.of(source, legacySource))), null,
+ new FallbackRemoteRepoLoader() {
+ @Nullable
+ @Override
+ public Collection<RemotePackage> parseLegacyXml(
+ @NonNull RepositorySource source,
+ @NonNull ProgressIndicator progress) {
+ assertEquals(legacyUrl, source.getUrl());
+ FakePackage legacy = new FakePackage("dummy;foo", new Revision(1, 2, 3),
+ null);
+ legacy.setCompleteUrl("http://www.example.com/legacy.zip");
+ return ImmutableSet.<RemotePackage>of(legacy);
+ }
+ });
+ Map<String, RemotePackage> pkgs = loader
+ .fetchPackages(progress, downloader, new FakeSettingsController(false));
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(2, pkgs.size());
+ assertFalse(pkgs.get("dummy;foo") instanceof FakePackage);
+ }
+
+ public void testNewerFallbackPreferred() throws Exception {
+ RepositorySource source = new SimpleRepositorySource("http://www.example.com",
+ "Source UI Name", true,
+ ImmutableSet.of(RepoManager.getGenericModule()),
+ null);
+ final String legacyUrl = "http://www.example.com/legacy";
+ RepositorySource legacySource = new SimpleRepositorySource(legacyUrl,
+ "Legacy UI Name", true, ImmutableSet.of(RepoManager.getGenericModule()),
+ null);
+ FakeDownloader downloader = new FakeDownloader(new MockFileOp());
+ downloader.registerUrl(new URL("http://www.example.com"),
+ getClass().getResourceAsStream("../testData/testRepo.xml"));
+ downloader.registerUrl(new URL(legacyUrl),
+ "foo".getBytes());
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RemoteRepoLoader loader = new RemoteRepoLoader(ImmutableList.<RepositorySourceProvider>of(
+ new FakeRepositorySourceProvider(ImmutableList.of(source, legacySource))), null,
+ new FallbackRemoteRepoLoader() {
+ @Nullable
+ @Override
+ public Collection<RemotePackage> parseLegacyXml(
+ @NonNull RepositorySource source,
+ @NonNull ProgressIndicator progress) {
+ assertEquals(legacyUrl, source.getUrl());
+ FakePackage legacy = new FakePackage("dummy;foo", new Revision(1, 2, 4),
+ null);
+ legacy.setCompleteUrl("http://www.example.com/legacy.zip");
+ return ImmutableSet.<RemotePackage>of(legacy);
+ }
+ });
+ Map<String, RemotePackage> pkgs = loader
+ .fetchPackages(progress, downloader, new FakeSettingsController(false));
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(2, pkgs.size());
+ assertTrue(pkgs.get("dummy;foo") instanceof FakePackage);
+ }
+
+ private static class FakeRepositorySourceProvider implements RepositorySourceProvider {
+
+ private List<RepositorySource> mSources;
+
+ public FakeRepositorySourceProvider(List<RepositorySource> sources) {
+ mSources = sources;
+ }
+
+ @NonNull
+ @Override
+ public List<RepositorySource> getSources(Downloader downloader, SettingsController settings,
+ ProgressIndicator logger, boolean forceRefresh) {
+ return mSources;
+ }
+
+ @Override
+ public boolean addSource(@NonNull RepositorySource source) {
+ return false;
+ }
+
+ @Override
+ public boolean isModifiable() {
+ return false;
+ }
+
+ @Override
+ public void save(@NonNull ProgressIndicator progress) {
+
+ }
+
+ @Override
+ public boolean removeSource(@NonNull RepositorySource source) {
+ return false;
+ }
+ }
+}
diff --git a/repository/src/test/java/com/android/repository/impl/sources/RemoteListSourceProviderTest.java b/repository/src/test/java/com/android/repository/impl/sources/RemoteListSourceProviderTest.java
new file mode 100644
index 0000000..aeff3db
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/sources/RemoteListSourceProviderTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.repository.impl.sources;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemoteListSourceProvider;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.api.SettingsController;
+import com.android.repository.testframework.FakeDownloader;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for {@link RemoteListSourceProvider}
+ */
+public class RemoteListSourceProviderTest extends TestCase {
+
+ public void testSimple() throws Exception {
+ MockFileOp fop = new MockFileOp();
+
+ Map<Class<? extends RepositorySource>, Collection<SchemaModule>> permittedModules =
+ Maps.newHashMap();
+ permittedModules.put(RemoteListSourceProvider.GenericSite.class,
+ ImmutableList.of(RepoManager.getCommonModule()));
+ RemoteListSourceProvider provider = RemoteListSourceProvider
+ .create("http://example.com/sourceList.xml", null, permittedModules);
+ FakeDownloader downloader = new FakeDownloader(fop);
+ downloader.registerUrl(new URL("http://example.com/sourceList.xml"),
+ getClass().getResourceAsStream("../testData/testSourceList-1.xml"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ List<RepositorySource> sources = provider.getSources(downloader, null, progress, false);
+ progress.assertNoErrorsOrWarnings();
+ RepositorySource source1 = sources.get(0);
+ assertEquals("My Example Add-ons.", source1.getDisplayName());
+ assertEquals("http://www.example.com/my_addons2.xml", source1.getUrl());
+ assertEquals(ImmutableList.of(RepoManager.getCommonModule()),
+ source1.getPermittedModules());
+
+ RepositorySource source2 = sources.get(1);
+ assertEquals("ありがとうございます。", source2.getDisplayName());
+ assertEquals("http://www.example.co.jp/addons.xml", source2.getUrl());
+ assertEquals(ImmutableList.of(RepoManager.getCommonModule()),
+ source2.getPermittedModules());
+ }
+
+ public void testCache() throws Exception {
+ MockFileOp fop = new MockFileOp();
+
+ Map<Class<? extends RepositorySource>, Collection<SchemaModule>> permittedModules =
+ Maps.newHashMap();
+ permittedModules.put(RemoteListSourceProvider.GenericSite.class,
+ ImmutableList.of(RepoManager.getCommonModule()));
+ RemoteListSourceProvider provider = RemoteListSourceProvider
+ .create("http://example.com/sourceList.xml", null, permittedModules);
+ FakeDownloader downloader = new FakeDownloader(fop);
+ downloader.registerUrl(new URL("http://example.com/sourceList.xml"),
+ getClass().getResourceAsStream("../testData/testSourceList-1.xml"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ provider.getSources(downloader, null, progress, false);
+ progress.assertNoErrorsOrWarnings();
+
+ downloader = new FakeDownloader(fop) {
+ @NonNull
+ @Override
+ public InputStream downloadAndStream(@NonNull URL url,
+ @Nullable SettingsController controller,
+ @NonNull ProgressIndicator indicator) throws IOException {
+ fail("shouldn't be downloading again");
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public File downloadFully(@NonNull URL url,
+ @Nullable SettingsController controller,
+ @NonNull ProgressIndicator indicator) throws IOException {
+ fail("shouldn't be downloading again");
+ return null;
+ }
+ };
+
+ List<RepositorySource> sources = provider.getSources(downloader, null, progress, false);
+ progress.assertNoErrorsOrWarnings();
+ RepositorySource source1 = sources.get(0);
+ assertEquals("My Example Add-ons.", source1.getDisplayName());
+ assertEquals("http://www.example.com/my_addons2.xml", source1.getUrl());
+ assertEquals(ImmutableList.of(RepoManager.getCommonModule()),
+ source1.getPermittedModules());
+
+ RepositorySource source2 = sources.get(1);
+ assertEquals("ありがとうございます。", source2.getDisplayName());
+ assertEquals("http://www.example.co.jp/addons.xml", source2.getUrl());
+ assertEquals(ImmutableList.of(RepoManager.getCommonModule()),
+ source2.getPermittedModules());
+ }
+
+ public void testForceRefresh() throws Exception {
+ MockFileOp fop = new MockFileOp();
+
+ Map<Class<? extends RepositorySource>, Collection<SchemaModule>> permittedModules =
+ Maps.newHashMap();
+ permittedModules.put(RemoteListSourceProvider.GenericSite.class,
+ ImmutableList.of(RepoManager.getCommonModule()));
+ RemoteListSourceProvider provider = RemoteListSourceProvider
+ .create("http://example.com/sourceList.xml", null, permittedModules);
+ FakeDownloader downloader = new FakeDownloader(fop);
+ downloader.registerUrl(new URL("http://example.com/sourceList.xml"),
+ getClass().getResourceAsStream("../testData/testSourceList-1.xml"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ provider.getSources(downloader, null, progress, false);
+ progress.assertNoErrorsOrWarnings();
+
+ downloader.registerUrl(new URL("http://example.com/sourceList.xml"),
+ getClass().getResourceAsStream("../testData/testSourceList2-1.xml"));
+
+ List<RepositorySource> sources = provider.getSources(downloader, null, progress, true);
+ progress.assertNoErrorsOrWarnings();
+ RepositorySource source1 = sources.get(0);
+ assertEquals("A different displayname from testSourceList-1", source1.getDisplayName());
+ assertEquals("http://www.example.com/different_site.xml", source1.getUrl());
+ assertEquals(ImmutableList.of(RepoManager.getCommonModule()),
+ source1.getPermittedModules());
+
+ RepositorySource source2 = sources.get(1);
+ assertEquals("今日は土曜日です", source2.getDisplayName());
+ assertEquals("http://www.example.co.jp/different_site.xml", source2.getUrl());
+ assertEquals(ImmutableList.of(RepoManager.getCommonModule()),
+ source2.getPermittedModules());
+ }
+}
diff --git a/repository/src/test/java/com/android/repository/impl/testData/testPackage.xml b/repository/src/test/java/com/android/repository/impl/testData/testPackage.xml
new file mode 100644
index 0000000..0d1ff4b
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/testData/testPackage.xml
@@ -0,0 +1,33 @@
+<!--
+ ~ 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.
+ -->
+
+<repo:repository
+ xmlns:repo="http://schemas.android.com/repository/android/generic/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <license id="lic1" type="text">
+ the license text
+ </license>
+ <localPackage path="dummy;foo" obsolete="true">
+ <type-details xsi:type="repo:genericDetailsType"/>
+ <revision>
+ <major>1</major>
+ <minor>2</minor>
+ <micro>3</micro>
+ </revision>
+ <display-name>Test package</display-name>
+ <uses-license ref="lic1"/>
+ </localPackage>
+</repo:repository>
\ No newline at end of file
diff --git a/repository/src/test/java/com/android/repository/impl/testData/testPackage2-lowerVersion.xml b/repository/src/test/java/com/android/repository/impl/testData/testPackage2-lowerVersion.xml
new file mode 100644
index 0000000..0dc3e8d
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/testData/testPackage2-lowerVersion.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ 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.
+ -->
+
+<repo:repository
+ xmlns:repo="http://schemas.android.com/repository/android/generic/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <license id="lic2" type="text">
+ license text 2
+ </license>
+ <localPackage path="dummy;bar">
+ <type-details xsi:type="repo:genericDetailsType"/>
+ <revision>
+ <major>3</major>
+ </revision>
+ <display-name>Test package</display-name>
+ <uses-license ref="lic2"/>
+ </localPackage>
+</repo:repository>
+
diff --git a/repository/src/test/java/com/android/repository/impl/testData/testPackage2.xml b/repository/src/test/java/com/android/repository/impl/testData/testPackage2.xml
new file mode 100644
index 0000000..62b8422
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/testData/testPackage2.xml
@@ -0,0 +1,34 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<repo:repository
+ xmlns:repo="http://schemas.android.com/repository/android/generic/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <license id="lic2" type="text">
+ license text 2
+ </license>
+ <localPackage path="dummy;bar">
+ <type-details xsi:type="repo:genericDetailsType"/>
+ <revision>
+ <major>4</major>
+ <minor>5</minor>
+ <micro>6</micro>
+ </revision>
+ <display-name>Test package</display-name>
+ <uses-license ref="lic2"/>
+ </localPackage>
+</repo:repository>
+
diff --git a/repository/src/test/java/com/android/repository/impl/testData/testRepo.xml b/repository/src/test/java/com/android/repository/impl/testData/testRepo.xml
new file mode 100644
index 0000000..47394bc
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/testData/testRepo.xml
@@ -0,0 +1,101 @@
+<!--
+ ~ 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.
+ -->
+
+<repo:repository
+ xmlns:repo="http://schemas.android.com/repository/android/generic/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <license id="lic1" type="text">
+ the license text
+ </license>
+ <license id="lic2" type="text">
+ license text 2
+ </license>
+ <remotePackage path="dummy;foo">
+ <type-details xsi:type="repo:genericDetailsType"/>
+ <revision>
+ <major>1</major>
+ <minor>2</minor>
+ <micro>3</micro>
+ </revision>
+ <display-name>Test package</display-name>
+ <uses-license ref="lic1"/>
+ <archives>
+ <archive>
+ <complete>
+ <size>1234</size>
+ <checksum>4321432143214321432143214321432143214321</checksum>
+ <url>http://example.com/arch1</url>
+ </complete>
+ <host-os>linux</host-os>
+ </archive>
+ <archive>
+ <complete>
+ <size>5678</size>
+ <checksum>8765876587658765876587658765876587658765</checksum>
+ <url>http://example.com/arch2</url>
+ </complete>
+ <host-os>windows</host-os>
+ <patches>
+ <patch>
+ <based-on>
+ <major>1</major>
+ <minor>3</minor>
+ <micro>2</micro>
+ </based-on>
+ <size>987</size>
+ <checksum>234564398579acf678b32ea98b988f1ab326dc65</checksum>
+ <url>http://example.com/patch</url>
+ </patch>
+ <patch>
+ <based-on>
+ <major>2</major>
+ </based-on>
+ <size>123</size>
+ <checksum>2350927340957820498572350293847502934875</checksum>
+ <url>http://example.com/patch2</url>
+ </patch>
+ </patches>
+ </archive>
+ <archive>
+ <complete>
+ <size>9876</size>
+ <checksum>2350927340957820498572350293847502934875</checksum>
+ <url>http://example.com/arch3</url>
+ </complete>
+ </archive>
+ </archives>
+ </remotePackage>
+ <remotePackage path="dummy;bar">
+ <type-details xsi:type="repo:genericDetailsType"/>
+ <revision>
+ <major>4</major>
+ <minor>5</minor>
+ <micro>6</micro>
+ </revision>
+ <display-name>Test package 2</display-name>
+ <uses-license ref="lic2"/>
+ <archives>
+ <archive>
+ <complete>
+ <size>2345</size>
+ <checksum>abcdef1234567890abcdef1234567890abcdef12</checksum>
+ <url>http://example.com/2/arch1</url>
+ </complete>
+ </archive>
+ </archives>
+ </remotePackage>
+</repo:repository>
+
diff --git a/repository/src/test/java/com/android/repository/impl/testData/testRepoWithChannels.xml b/repository/src/test/java/com/android/repository/impl/testData/testRepoWithChannels.xml
new file mode 100644
index 0000000..86e225a
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/testData/testRepoWithChannels.xml
@@ -0,0 +1,123 @@
+<!--
+ ~ 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.
+ -->
+
+<generic:repository
+ xmlns:repo="http://schemas.android.com/repository/android/common/01"
+ xmlns:generic="http://schemas.android.com/repository/android/generic/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <channel id="channel-0">blah</channel>
+ <channel id="channel-1"/>
+ <channel id="channel-2"/>
+ <channel id="channel-3"/>
+ <remotePackage path="dummy;foo">
+ <type-details xsi:type="generic:genericDetailsType"/>
+ <revision>
+ <major>1</major>
+ <minor>2</minor>
+ <micro>3</micro>
+ </revision>
+ <display-name>Test package</display-name>
+ <channelRef ref="channel-0"/>
+ <archives>
+ <archive>
+ <complete>
+ <size>1234</size>
+ <checksum>4321432143214321432143214321432143214321</checksum>
+ <url>http://example.com/arch1</url>
+ </complete>
+ <host-os>linux</host-os>
+ </archive>
+ </archives>
+ </remotePackage>
+ <remotePackage path="dummy;foo">
+ <type-details xsi:type="generic:genericDetailsType"/>
+ <revision>
+ <major>1</major>
+ <minor>2</minor>
+ <micro>5</micro>
+ </revision>
+ <display-name>Test package</display-name>
+ <channelRef ref="channel-2"/>
+ <archives>
+ <archive>
+ <complete>
+ <size>1234</size>
+ <checksum>4321432143214321432143214321432143214321</checksum>
+ <url>http://example.com/arch1</url>
+ </complete>
+ <host-os>linux</host-os>
+ </archive>
+ </archives>
+ </remotePackage>
+ <remotePackage path="dummy;foo">
+ <type-details xsi:type="generic:genericDetailsType"/>
+ <revision>
+ <major>1</major>
+ <minor>2</minor>
+ <micro>4</micro>
+ </revision>
+ <display-name>Test package</display-name>
+ <channelRef ref="channel-1"/>
+ <archives>
+ <archive>
+ <complete>
+ <size>1234</size>
+ <checksum>4321432143214321432143214321432143214321</checksum>
+ <url>http://example.com/arch1</url>
+ </complete>
+ <host-os>linux</host-os>
+ </archive>
+ </archives>
+ </remotePackage>
+ <remotePackage path="dummy;bar">
+ <type-details xsi:type="generic:genericDetailsType"/>
+ <revision>
+ <major>4</major>
+ <minor>5</minor>
+ <micro>6</micro>
+ </revision>
+ <display-name>Test package 2</display-name>
+ <archives>
+ <archive>
+ <complete>
+ <size>2345</size>
+ <checksum>abcdef1234567890abcdef1234567890abcdef12</checksum>
+ <url>http://example.com/2/arch1</url>
+ </complete>
+ </archive>
+ </archives>
+ </remotePackage>
+ <remotePackage path="dummy;bar">
+ <type-details xsi:type="generic:genericDetailsType"/>
+ <revision>
+ <major>4</major>
+ <minor>5</minor>
+ <micro>7</micro>
+ </revision>
+ <display-name>Test package 2</display-name>
+ <channelRef ref="channel-3"/>
+ <archives>
+ <archive>
+ <complete>
+ <size>2345</size>
+ <checksum>abcdef1234567890abcdef1234567890abcdef12</checksum>
+ <url>http://example.com/2/arch1</url>
+ </complete>
+ </archive>
+ </archives>
+ </remotePackage>
+</generic:repository>
+
diff --git a/repository/src/test/java/com/android/repository/impl/testData/testSourceList-1.xml b/repository/src/test/java/com/android/repository/impl/testData/testSourceList-1.xml
new file mode 100644
index 0000000..f40b4c8
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/testData/testSourceList-1.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<repo:site-list
+ xmlns:repo="http://schemas.android.com/repository/android/sites-common/1"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <site xsi:type="repo:genericSiteType">
+ <url>http://www.example.com/my_addons2.xml</url>
+ <displayName> <!-- we'll ignore leading/trailing spacing -->
+ My Example Add-ons.
+ </displayName>
+ </site>
+
+ <site xsi:type="repo:genericSiteType">
+ <url>http://www.example.co.jp/addons.xml</url>
+ <!-- this file is UTF-8 so we support character sets -->
+ <displayName>ありがとうございます。</displayName>
+ </site>
+</repo:site-list>
diff --git a/repository/src/test/java/com/android/repository/impl/testData/testSourceList2-1.xml b/repository/src/test/java/com/android/repository/impl/testData/testSourceList2-1.xml
new file mode 100644
index 0000000..8a6ea7e
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/impl/testData/testSourceList2-1.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<repo:site-list
+ xmlns:repo="http://schemas.android.com/repository/android/sites-common/1"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <site xsi:type="repo:genericSiteType">
+ <url>http://www.example.com/different_site.xml</url>
+ <displayName>
+ A different displayname from testSourceList-1
+ </displayName>
+ </site>
+
+ <site xsi:type="repo:genericSiteType">
+ <url>http://www.example.co.jp/different_site.xml</url>
+ <displayName>今日は土曜日です</displayName>
+ </site>
+</repo:site-list>
diff --git a/repository/src/test/java/com/android/repository/io/FileOpUtilsTest.java b/repository/src/test/java/com/android/repository/io/FileOpUtilsTest.java
new file mode 100644
index 0000000..7011657
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/io/FileOpUtilsTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.repository.io;
+
+import com.android.repository.io.impl.FileOpImpl;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * Tests for FileOpUtils
+ * TODO: more tests
+ */
+public class FileOpUtilsTest extends TestCase {
+ public void testMakeRelative() throws Exception {
+ assertEquals("dir3",
+ FileOpUtils.makeRelativeImpl("/dir1/dir2",
+ "/dir1/dir2/dir3",
+ false, "/"));
+
+ assertEquals("../../../dir3",
+ FileOpUtils.makeRelativeImpl("/dir1/dir2/dir4/dir5/dir6",
+ "/dir1/dir2/dir3",
+ false, "/"));
+
+ assertEquals("dir3/dir4/dir5/dir6",
+ FileOpUtils.makeRelativeImpl("/dir1/dir2/",
+ "/dir1/dir2/dir3/dir4/dir5/dir6",
+ false, "/"));
+
+ // case-sensitive on non-Windows.
+ assertEquals("../DIR2/dir3/DIR4/dir5/DIR6",
+ FileOpUtils.makeRelativeImpl("/dir1/dir2/",
+ "/dir1/DIR2/dir3/DIR4/dir5/DIR6",
+ false, "/"));
+
+ // same path: empty result.
+ assertEquals("",
+ FileOpUtils.makeRelativeImpl("/dir1/dir2/dir3",
+ "/dir1/dir2/dir3",
+ false, "/"));
+
+ // same drive letters on Windows
+ assertEquals("..\\..\\..\\dir3",
+ FileOpUtils.makeRelativeImpl("C:\\dir1\\dir2\\dir4\\dir5\\dir6",
+ "C:\\dir1\\dir2\\dir3",
+ true, "\\"));
+
+ // not case-sensitive on Windows, results will be mixed.
+ assertEquals("dir3/DIR4/dir5/DIR6",
+ FileOpUtils.makeRelativeImpl("/DIR1/dir2/",
+ "/dir1/DIR2/dir3/DIR4/dir5/DIR6",
+ true, "/"));
+
+ // UNC path on Windows
+ assertEquals("..\\..\\..\\dir3",
+ FileOpUtils.makeRelativeImpl("\\\\myserver.domain\\dir1\\dir2\\dir4\\dir5\\dir6",
+ "\\\\myserver.domain\\dir1\\dir2\\dir3",
+ true, "\\"));
+
+ // different drive letters are not supported
+ try {
+ FileOpUtils.makeRelativeImpl("C:\\dir1\\dir2\\dir4\\dir5\\dir6",
+ "D:\\dir1\\dir2\\dir3",
+ true, "\\");
+ fail("Expected: IOException. Actual: no exception.");
+ } catch (IOException e) {
+ assertEquals("makeRelative: incompatible drive letters", e.getMessage());
+ }
+ }
+
+}
diff --git a/repository/src/test/java/com/android/repository/io/impl/MockFileOpTest.java b/repository/src/test/java/com/android/repository/io/impl/MockFileOpTest.java
new file mode 100644
index 0000000..d0387f6
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/io/impl/MockFileOpTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.repository.io.impl;
+
+import com.android.repository.testframework.MockFileOp;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit-test for the {@link MockFileOp}, which is a mock of {@link FileOpImpl} that doesn't
+ * touch the file system. Just testing the test.
+ */
+public class MockFileOpTest extends TestCase {
+
+ private MockFileOp m;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ m = new MockFileOp();
+ }
+
+ private File createFile(String...segments) {
+ File f = null;
+ for (String segment : segments) {
+ if (f == null) {
+ f = new File(segment);
+ } else {
+ f = new File(f, segment);
+ }
+ }
+
+ return f;
+ }
+
+ public void testIsFile() {
+ File f1 = createFile("/dir1", "file1");
+ assertFalse(m.isFile(f1));
+
+ m.recordExistingFile("/dir1/file1");
+ assertTrue(m.isFile(f1));
+
+ assertEquals(
+ "[/dir1/file1]",
+ Arrays.toString(m.getExistingFiles()));
+ }
+
+ public void testIsDirectory() {
+ File d4 = createFile("/dir1", "dir2", "dir3", "dir4");
+ File f7 = createFile("/dir1", "dir2", "dir6", "file7");
+ assertFalse(m.isDirectory(d4));
+
+ m.recordExistingFolder("/dir1/dir2/dir3/dir4");
+ m.recordExistingFile("/dir1/dir2/dir6/file7");
+ assertTrue(m.isDirectory(d4));
+ assertFalse(m.isDirectory(f7));
+
+ // any intermediate directory exists implicitly
+ assertTrue(m.isDirectory(createFile("/")));
+ assertTrue(m.isDirectory(createFile("/dir1")));
+ assertTrue(m.isDirectory(createFile("/dir1", "dir2")));
+ assertTrue(m.isDirectory(createFile("/dir1", "dir2", "dir3")));
+ assertTrue(m.isDirectory(createFile("/dir1", "dir2", "dir6")));
+
+ assertEquals(
+ "[/dir1, /dir1/dir2, /dir1/dir2/dir3, /dir1/dir2/dir3/dir4, /dir1/dir2/dir6]",
+ Arrays.toString(m.getExistingFolders()));
+ }
+
+ public void testDelete() {
+ m.recordExistingFolder("/dir1");
+ m.recordExistingFile("/dir1/file1");
+ m.recordExistingFile("/dir1/file2");
+
+ assertEquals(
+ "[/dir1/file1, /dir1/file2]",
+ Arrays.toString(m.getExistingFiles()));
+
+ assertTrue(m.delete(createFile("/dir1", "file1")));
+ assertFalse(m.delete(createFile("/dir1", "file3")));
+ assertFalse(m.delete(createFile("/dir2", "file2")));
+ assertEquals(
+ "[/dir1/file2]",
+ Arrays.toString(m.getExistingFiles()));
+
+ // deleting a directory with files in it fails
+ assertFalse(m.delete(createFile("/dir1")));
+ // but it works if the directory is empty
+ assertTrue(m.delete(createFile("/dir1", "file2")));
+ assertTrue(m.delete(createFile("/dir1")));
+ }
+
+ public void testListFiles() {
+ m.recordExistingFolder("/dir1");
+ m.recordExistingFile("/dir1/file1");
+ m.recordExistingFile("/dir1/file2");
+ m.recordExistingFile("/dir1/dir2/file3");
+ m.recordExistingFile("/dir4/file4");
+
+ assertEquals(
+ "[]",
+ m.getAgnosticAbsPath(Arrays.toString(m.listFiles(createFile("/not_a_dir")))));
+
+ assertEquals(
+ "[/dir1/dir2/file3]",
+ m.getAgnosticAbsPath(Arrays.toString(m.listFiles(createFile("/dir1", "dir2")))));
+
+ assertEquals(
+ "[/dir1/dir2, /dir1/file1, /dir1/file2]",
+ m.getAgnosticAbsPath(Arrays.toString(m.listFiles(createFile("/dir1")))));
+ }
+
+ public void testMkDirs() {
+ assertEquals("[]", Arrays.toString(m.getExistingFolders()));
+
+ assertTrue(m.mkdirs(createFile("/dir1")));
+ assertEquals("[/, /dir1]", Arrays.toString(m.getExistingFolders()));
+
+ m.recordExistingFolder("/dir1");
+ assertEquals("[/, /dir1]", Arrays.toString(m.getExistingFolders()));
+
+ assertTrue(m.mkdirs(createFile("/dir1/dir2/dir3")));
+ assertEquals(
+ "[/, /dir1, /dir1/dir2, /dir1/dir2/dir3]",
+ Arrays.toString(m.getExistingFolders()));
+ }
+
+ public void testRenameTo() {
+ m.recordExistingFile("/dir1/dir2/dir6/file7");
+ m.recordExistingFolder("/dir1/dir2/dir3/dir4");
+
+ assertEquals("[/dir1/dir2/dir6/file7]", Arrays.toString(m.getExistingFiles()));
+ assertEquals("[/dir1, /dir1/dir2, /dir1/dir2/dir3, /dir1/dir2/dir3/dir4, /dir1/dir2/dir6]",
+ Arrays.toString(m.getExistingFolders()));
+
+ assertTrue(m.renameTo(createFile("/dir1", "dir2"), createFile("/dir1", "newDir2")));
+ assertEquals("[/dir1/newDir2/dir6/file7]", Arrays.toString(m.getExistingFiles()));
+ assertEquals("[/dir1, /dir1/newDir2, /dir1/newDir2/dir3, /dir1/newDir2/dir3/dir4, /dir1/newDir2/dir6]",
+ Arrays.toString(m.getExistingFolders()));
+
+ assertTrue(m.renameTo(
+ createFile("/dir1", "newDir2", "dir6", "file7"),
+ createFile("/dir1", "newDir2", "dir6", "newFile7")));
+ assertTrue(m.renameTo(
+ createFile("/dir1", "newDir2", "dir3", "dir4"),
+ createFile("/dir1", "newDir2", "dir3", "newDir4")));
+ assertEquals("[/dir1/newDir2/dir6/newFile7]", Arrays.toString(m.getExistingFiles()));
+ assertEquals("[/dir1, /dir1/newDir2, /dir1/newDir2/dir3, /dir1/newDir2/dir3/newDir4, /dir1/newDir2/dir6]", Arrays.toString(m.getExistingFolders()));
+ }
+
+ public void testNewFileOutputStream() throws Exception {
+ assertEquals("[]", Arrays.toString(m.getOutputStreams()));
+
+ File f = createFile("/dir1", "dir2", "simple ascii");
+ OutputStream os = m.newFileOutputStream(f);
+ assertNotNull(os);
+ os.write("regular ascii".getBytes("UTF-8"));
+ os.close();
+
+ f = createFile("/dir1", "dir2", "utf-8 test");
+ os = m.newFileOutputStream(f);
+ assertNotNull(os);
+ os.write("nihongo in UTF-8: 日本語".getBytes("UTF-8"));
+ os.close();
+
+ f = createFile("/dir1", "dir2", "forgot to close");
+ os = m.newFileOutputStream(f);
+ assertNotNull(os);
+ os.write("wrote stuff but not closing the stream".getBytes("UTF-8"));
+
+ assertEquals(
+ "[</dir1/dir2/simple ascii: 'regular ascii'>, " +
+ "</dir1/dir2/utf-8 test: 'nihongo in UTF-8: 日本語'>, " +
+ "</dir1/dir2/forgot to close: (stream not closed properly)>]",
+ Arrays.toString(m.getOutputStreams()));
+ }
+
+ public void testReadOnlyFile() throws Exception {
+ File f1 = createFile("/root", "writable.txt");
+ File f2 = createFile("/root", "readonly.txt");
+ m.setReadOnly(f2);
+
+ assertTrue(m.canWrite(f1));
+ assertFalse(m.canWrite(f2));
+ }
+
+ public void testToString() throws Exception {
+ m.recordExistingFile("/root/blah", "foo");
+ assertEquals("foo", m.toString(new File("/root/blah"), Charsets.UTF_8));
+ try {
+ m.toString(new File("/root/bogus"), Charsets.UTF_8);
+ fail();
+ }
+ catch (Exception expected) {
+ // nothing
+ }
+ }
+
+ public void testListWithFilter() throws Exception {
+ m.recordExistingFile("/root/foo/a.txt");
+ m.recordExistingFile("/root/foo/b.csv");
+ m.recordExistingFile("/root/foo/c.txt");
+ m.recordExistingFile("/root/foo/d.txt/d.txtWasActuallyAFolder");
+ m.recordExistingFile("/root/foofoo/blah");
+ m.recordExistingFile("/root/foo/bar/baz.txt");
+ String[] result = m.list(new File("/root/foo"), new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".txt");
+ }
+ });
+ assertEquals(result.length, 3);
+ List<String> resultList = Lists.newArrayList(result);
+ assertTrue(resultList.contains("a.txt"));
+ assertTrue(resultList.contains("c.txt"));
+ assertTrue(resultList.contains("d.txt"));
+ }
+}
diff --git a/repository/src/test/java/com/android/repository/util/InstallerUtilTest.java b/repository/src/test/java/com/android/repository/util/InstallerUtilTest.java
new file mode 100644
index 0000000..ff303c0
--- /dev/null
+++ b/repository/src/test/java/com/android/repository/util/InstallerUtilTest.java
@@ -0,0 +1,483 @@
+/*
+ * 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.repository.util;
+
+import com.android.repository.Revision;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.impl.manager.RepoManagerImpl;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.testframework.FakeDependency;
+import com.android.repository.testframework.FakePackage;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for {@link InstallerUtil}.
+ */
+public class InstallerUtilTest extends TestCase {
+
+ private static final List<Dependency> NONE = ImmutableList.of();
+
+ /**
+ * Simple case: a package requires itself, even if has no dependencies set.
+ */
+ public void testNoDeps() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ assertEquals(request,
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("l1", new Revision(1), NONE))
+ .addRemote(r1)
+ .addRemote(new FakePackage("r2", new Revision(1), NONE))
+ .build(), progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * Simple chain of dependencies, r1->r2->r3. Should be returned in reverse order.
+ */
+ public void testSimple() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3")));
+ RemotePackage r3 = new FakePackage("r3", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ ImmutableList<RemotePackage> expected = ImmutableList.of(r3, r2, r1);
+ assertEquals(expected,
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("l1", new Revision(1), NONE))
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * Request r1 and r1. The latest version of r1 is installed so only r2 is returned.
+ */
+ public void testLocalInstalled() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1), NONE);
+ RemotePackage r2 = new FakePackage("r2", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1, r2);
+ ImmutableList<RemotePackage> expected = ImmutableList.of(r2);
+ assertEquals(expected,
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("r1", new Revision(1), NONE))
+ .addRemote(r1)
+ .addRemote(r2)
+ .build(), progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * Request r1 and r1. r1 is installed but there is an update for it available, and so both
+ * r1 and r2 are returned.
+ */
+ public void testLocalInstalledWithUpdate() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(2), NONE);
+ RemotePackage r2 = new FakePackage("r2", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1, r2);
+ List<RemotePackage> result = InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("r1", new Revision(1), NONE))
+ .addRemote(r1)
+ .addRemote(r2)
+ .build(), progress);
+ assertTrue(result.get(0).equals(r1) || result.get(1).equals(r1));
+ assertTrue(result.get(0).equals(r2) || result.get(1).equals(r2));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * Dependency chain r1->r2->r3, but r3 is already installed with sufficient version, and so is
+ * not returned.
+ */
+ public void testLocalSatisfies() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3", 1, 1, 1)));
+ RemotePackage r3 = new FakePackage("r3", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ ImmutableList<RemotePackage> expected = ImmutableList.of(r2, r1);
+ assertEquals(expected,
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("r3", new Revision(2), NONE))
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * Dependency chain r1->r2->r3, but r3 is already installed with no required version
+ * specified, and so is not returned.
+ */
+ public void testLocalSatisfiesNoVersion() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3")));
+ RemotePackage r3 = new FakePackage("r3", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ ImmutableList<RemotePackage> expected = ImmutableList.of(r2, r1);
+ assertEquals(expected,
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("r3", new Revision(1), NONE))
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * Dependency chain r1->r2->r3, and r3 is installed but doesn't meet the version requirement.
+ */
+ public void testUpdateNeeded() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3", 2, null, null)));
+ RemotePackage r3 = new FakePackage("r3", new Revision(2), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ ImmutableList<RemotePackage> expected = ImmutableList.of(r3, r2, r1);
+ assertEquals(expected,
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("r3", new Revision(1), NONE))
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * r1->{r2, r3}. All should be returned, with r1 last.
+ */
+ public void testMulti() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2"), new FakeDependency("r3")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1), NONE);
+ RemotePackage r3 = new FakePackage("r3", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ List<RemotePackage> result = InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress);
+ assertTrue(result.get(0).equals(r2) || result.get(1).equals(r2));
+ assertTrue(result.get(0).equals(r3) || result.get(1).equals(r3));
+ assertEquals(r1, result.get(2));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * r1->{r2, r3}->r4. All should be returned, with r4 first and r1 last.
+ */
+ public void testDag() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2"), new FakeDependency("r3")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r4")));
+ RemotePackage r3 = new FakePackage("r3", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r4")));
+ RemotePackage r4 = new FakePackage("r4", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ List<RemotePackage> result = InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .addRemote(r4)
+ .build(), progress);
+ assertEquals(r4, result.get(0));
+ assertTrue(result.get(1).equals(r2) || result.get(2).equals(r2));
+ assertTrue(result.get(1).equals(r3) || result.get(2).equals(r3));
+ assertEquals(r1, result.get(3));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * r1->r2->r3->r1. All should be returned, in undefined order.
+ */
+ public void testCycle() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3")));
+ RemotePackage r3 = new FakePackage("r3", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r1")));
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ Set<RemotePackage> expected = Sets.newHashSet(r1, r2, r3);
+ // Don't have any guarantee of order in this case.
+ assertEquals(expected,
+ Sets.newHashSet(InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress)));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * r1->r2->r3->r4->r3. All should be returned, with [r2, r1] last.
+ */
+ public void testCycle2() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2")));
+
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3")));
+ RemotePackage r3 = new FakePackage("r3", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r4")));
+ RemotePackage r4 = new FakePackage("r4", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3")));
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ List<RemotePackage> result =
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .addRemote(r4)
+ .build(), progress);
+ assertTrue(result.get(0).equals(r3) || result.get(1).equals(r3));
+ assertTrue(result.get(0).equals(r4) || result.get(1).equals(r4));
+ assertEquals(r2, result.get(2));
+ assertEquals(r1, result.get(3));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * {r1, r2}->r3. Request both r1 and r2. All should be returned, with r3 first.
+ */
+ public void testMultiRequest() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3")));
+ RemotePackage r3 = new FakePackage("r3", new Revision(1), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1, r2);
+ List<RemotePackage> result =
+ InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress);
+ assertEquals(r3, result.get(0));
+ assertTrue(result.get(1).equals(r1) || result.get(2).equals(r1));
+ assertTrue(result.get(1).equals(r2) || result.get(2).equals(r2));
+
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ /**
+ * r1->bogus. Null should be returned, and there should be an error.
+ */
+ public void testBogus() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("bogus")));
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ assertNull(InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addRemote(r1)
+ .build(), progress));
+ assertTrue(!progress.getWarnings().isEmpty());
+ }
+
+ /**
+ * r1->r2->r3. r3 is installed, but a higher version is required and not available. Null should
+ * be returned, and there should be an error.
+ */
+ public void testUpdateNotAvailable() throws Exception {
+ RemotePackage r1 = new FakePackage("r1", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r2")));
+ RemotePackage r2 = new FakePackage("r2", new Revision(1),
+ ImmutableList.<Dependency>of(new FakeDependency("r3", 4, null, null)));
+ RemotePackage r3 = new FakePackage("r3", new Revision(2), NONE);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ ImmutableList<RemotePackage> request = ImmutableList.of(r1);
+ assertNull(InstallerUtil.computeRequiredPackages(request,
+ new RepositoryPackagesBuilder()
+ .addLocal(new FakePackage("r3", new Revision(1), NONE))
+ .addRemote(r1)
+ .addRemote(r2)
+ .addRemote(r3)
+ .build(), progress));
+ assertTrue(!progress.getWarnings().isEmpty());
+ }
+
+ public void testInstallInChild() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile("/sdk/foo/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"foo\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.setLocalPath(new File("/sdk"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ mgr.loadSynchronously(0, progress, null, null);
+ assertFalse(InstallerUtil.checkValidPath(new File("/sdk/foo/bar"), mgr, progress));
+ assertFalse(progress.getWarnings().isEmpty());
+ }
+
+ public void testInstallInParent() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile("/sdk/foo/bar/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"foo;bar\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.setLocalPath(new File("/sdk"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ mgr.loadSynchronously(0, progress, null, null);
+ assertFalse(InstallerUtil.checkValidPath(new File("/sdk/foo"), mgr, progress));
+ assertFalse(progress.getWarnings().isEmpty());
+ }
+
+ public void testInstallSeparately() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile("/sdk/foo2/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"foo2\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.setLocalPath(new File("/sdk"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ mgr.loadSynchronously(0, progress, null, null);
+ assertTrue(InstallerUtil.checkValidPath(new File("/sdk/foo"), mgr, progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ public void testInstallSeparately2() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile("/sdk/foo/package.xml",
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"foo\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>The first Android platform ever</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.setLocalPath(new File("/sdk"));
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ mgr.loadSynchronously(0, progress, null, null);
+ assertTrue(InstallerUtil.checkValidPath(new File("/sdk/foo2"), mgr, progress));
+ progress.assertNoErrorsOrWarnings();
+ }
+
+ private static class RepositoryPackagesBuilder {
+ private Map<String, RemotePackage> mRemotes = Maps.newHashMap();
+ private Map<String, LocalPackage> mLocals = Maps.newHashMap();
+
+ public RepositoryPackagesBuilder addLocal(LocalPackage p) {
+ mLocals.put(p.getPath(), p);
+ return this;
+ }
+
+ public RepositoryPackagesBuilder addRemote(RemotePackage p) {
+ mRemotes.put(p.getPath(), p);
+ return this;
+ }
+
+ public RepositoryPackages build() {
+ return new RepositoryPackages(mLocals, mRemotes);
+ }
+ }
+
+
+}
diff --git a/base/rpclib/build.gradle b/rpclib/build.gradle
similarity index 100%
rename from base/rpclib/build.gradle
rename to rpclib/build.gradle
diff --git a/rpclib/rpclib.iml b/rpclib/rpclib.iml
new file mode 100644
index 0000000..6652388
--- /dev/null
+++ b/rpclib/rpclib.iml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="annotations" />
+ <orderEntry type="module" module-name="util" />
+ <orderEntry type="module" module-name="android-annotations" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="platform-api" />
+ <orderEntry type="library" name="Guava" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Bool.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Bool.java
new file mode 100644
index 0000000..469e29b
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Bool.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Bool extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private boolean mValue;
+
+ // Constructs a default-initialized {@link Bool}.
+ public Bool() {}
+
+
+ public boolean getValue() {
+ return mValue;
+ }
+
+ public Bool setValue(boolean v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "bool_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("bool", Method.Bool)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Bool(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Bool o = (Bool)obj;
+ e.bool(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Bool o = (Bool)obj;
+ o.mValue = d.bool();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/BoolSlice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/BoolSlice.java
new file mode 100644
index 0000000..3b24ef1
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/BoolSlice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class BoolSlice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private boolean[] mValue;
+
+ // Constructs a default-initialized {@link BoolSlice}.
+ public BoolSlice() {}
+
+
+ public boolean[] getValue() {
+ return mValue;
+ }
+
+ public BoolSlice setValue(boolean[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "boolSlice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("bool", Method.Bool))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new BoolSlice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ BoolSlice o = (BoolSlice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.bool(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ BoolSlice o = (BoolSlice)obj;
+ o.mValue = new boolean[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.bool();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Box.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Box.java
new file mode 100644
index 0000000..ec9caab
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Box.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.binary.BinaryObject;
+
+public abstract class Box implements BinaryObject {
+ public abstract Object unwrap();
+
+ public static Box wrap(Object value) {
+ if (value instanceof BinaryObject) {
+ return new ObjectBox().setValue((BinaryObject)value);
+ }
+ if (value instanceof Boolean) {
+ return new Bool().setValue((Boolean)value);
+ }
+ // TODO: signed/unsigned variants are indistinguishable in java
+ if (value instanceof Byte) {
+ return new Uint8().setValue((Byte)value);
+ }
+ if (value instanceof Short) {
+ return new Uint16().setValue((Short)value);
+ }
+ if (value instanceof Integer) {
+ return new Uint32().setValue((Integer)value);
+ }
+ if (value instanceof Long) {
+ return new Uint64().setValue((Long)value);
+ }
+ if (value instanceof Float) {
+ return new Float32().setValue((Float)value);
+ }
+ if (value instanceof Double) {
+ return new Float64().setValue((Double)value);
+ }
+ if (value instanceof String) {
+ return new StringBox().setValue((String)value);
+ }
+ // TODO: slice types
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Factory.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Factory.java
new file mode 100644
index 0000000..db16ad3
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Factory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+public final class Factory {
+ public static void register() {
+ //<<<Start:Java.FactoryBody:2>>>
+ BoolSlice.register();
+ Bool.register();
+ Float32Slice.register();
+ Float32.register();
+ Float64Slice.register();
+ Float64.register();
+ IdSlice.register();
+ Id.register();
+ Int16Slice.register();
+ Int16.register();
+ Int32Slice.register();
+ Int32.register();
+ Int64Slice.register();
+ Int64.register();
+ Int8Slice.register();
+ Int8.register();
+ ObjectSlice.register();
+ ObjectBox.register();
+ StringSlice.register();
+ StringBox.register();
+ Uint16Slice.register();
+ Uint16.register();
+ Uint32Slice.register();
+ Uint32.register();
+ Uint64Slice.register();
+ Uint64.register();
+ Uint8Slice.register();
+ Uint8.register();
+ //<<<End:Java.FactoryBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Float32.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Float32.java
new file mode 100644
index 0000000..447bf3a
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Float32.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Float32 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private float mValue;
+
+ // Constructs a default-initialized {@link Float32}.
+ public Float32() {}
+
+
+ public float getValue() {
+ return mValue;
+ }
+
+ public Float32 setValue(float v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "float32_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("float32", Method.Float32)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Float32(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Float32 o = (Float32)obj;
+ e.float32(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Float32 o = (Float32)obj;
+ o.mValue = d.float32();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Float32Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Float32Slice.java
new file mode 100644
index 0000000..e4349f2
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Float32Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Float32Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private float[] mValue;
+
+ // Constructs a default-initialized {@link Float32Slice}.
+ public Float32Slice() {}
+
+
+ public float[] getValue() {
+ return mValue;
+ }
+
+ public Float32Slice setValue(float[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "float32Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("float32", Method.Float32))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Float32Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Float32Slice o = (Float32Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.float32(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Float32Slice o = (Float32Slice)obj;
+ o.mValue = new float[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.float32();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Float64.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Float64.java
new file mode 100644
index 0000000..472060f
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Float64.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Float64 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private double mValue;
+
+ // Constructs a default-initialized {@link Float64}.
+ public Float64() {}
+
+
+ public double getValue() {
+ return mValue;
+ }
+
+ public Float64 setValue(double v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "float64_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("float64", Method.Float64)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Float64(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Float64 o = (Float64)obj;
+ e.float64(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Float64 o = (Float64)obj;
+ o.mValue = d.float64();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Float64Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Float64Slice.java
new file mode 100644
index 0000000..0709b86
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Float64Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Float64Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private double[] mValue;
+
+ // Constructs a default-initialized {@link Float64Slice}.
+ public Float64Slice() {}
+
+
+ public double[] getValue() {
+ return mValue;
+ }
+
+ public Float64Slice setValue(double[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "float64Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("float64", Method.Float64))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Float64Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Float64Slice o = (Float64Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.float64(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Float64Slice o = (Float64Slice)obj;
+ o.mValue = new double[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.float64();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Id.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Id.java
new file mode 100644
index 0000000..1af8aff
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Id.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryID;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Id implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private BinaryID mValue;
+
+ // Constructs a default-initialized {@link Id}.
+ public Id() {}
+
+
+ public BinaryID getValue() {
+ return mValue;
+ }
+
+ public Id setValue(BinaryID v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "id_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Array("binary.ID", new Primitive("byte", Method.Uint8), 20)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Id(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Id o = (Id)obj;
+ o.mValue.write(e);
+
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Id o = (Id)obj;
+ o.mValue = new BinaryID(d);
+
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/IdSlice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/IdSlice.java
new file mode 100644
index 0000000..08cf705
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/IdSlice.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryID;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class IdSlice implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private BinaryID[] mValue;
+
+ // Constructs a default-initialized {@link IdSlice}.
+ public IdSlice() {}
+
+
+ public BinaryID[] getValue() {
+ return mValue;
+ }
+
+ public IdSlice setValue(BinaryID[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "idSlice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Array("binary.ID", new Primitive("byte", Method.Uint8), 20))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new IdSlice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ IdSlice o = (IdSlice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ o.mValue[i].write(e);
+
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ IdSlice o = (IdSlice)obj;
+ o.mValue = new BinaryID[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = new BinaryID(d);
+
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int16.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int16.java
new file mode 100644
index 0000000..3fe65c4
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int16.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int16 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private short mValue;
+
+ // Constructs a default-initialized {@link Int16}.
+ public Int16() {}
+
+
+ public short getValue() {
+ return mValue;
+ }
+
+ public Int16 setValue(short v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int16_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("int16", Method.Int16)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int16(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int16 o = (Int16)obj;
+ e.int16(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int16 o = (Int16)obj;
+ o.mValue = d.int16();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int16Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int16Slice.java
new file mode 100644
index 0000000..b2c9707
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int16Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int16Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private short[] mValue;
+
+ // Constructs a default-initialized {@link Int16Slice}.
+ public Int16Slice() {}
+
+
+ public short[] getValue() {
+ return mValue;
+ }
+
+ public Int16Slice setValue(short[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int16Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("int16", Method.Int16))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int16Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int16Slice o = (Int16Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.int16(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int16Slice o = (Int16Slice)obj;
+ o.mValue = new short[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.int16();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int32.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int32.java
new file mode 100644
index 0000000..bdddcba
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int32.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int32 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private int mValue;
+
+ // Constructs a default-initialized {@link Int32}.
+ public Int32() {}
+
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public Int32 setValue(int v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int32_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("int32", Method.Int32)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int32(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int32 o = (Int32)obj;
+ e.int32(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int32 o = (Int32)obj;
+ o.mValue = d.int32();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int32Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int32Slice.java
new file mode 100644
index 0000000..2e9e793
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int32Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int32Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private int[] mValue;
+
+ // Constructs a default-initialized {@link Int32Slice}.
+ public Int32Slice() {}
+
+
+ public int[] getValue() {
+ return mValue;
+ }
+
+ public Int32Slice setValue(int[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int32Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("int32", Method.Int32))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int32Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int32Slice o = (Int32Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.int32(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int32Slice o = (Int32Slice)obj;
+ o.mValue = new int[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.int32();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int64.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int64.java
new file mode 100644
index 0000000..cea523b
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int64.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int64 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private long mValue;
+
+ // Constructs a default-initialized {@link Int64}.
+ public Int64() {}
+
+
+ public long getValue() {
+ return mValue;
+ }
+
+ public Int64 setValue(long v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int64_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("int64", Method.Int64)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int64(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int64 o = (Int64)obj;
+ e.int64(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int64 o = (Int64)obj;
+ o.mValue = d.int64();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int64Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int64Slice.java
new file mode 100644
index 0000000..d39e152
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int64Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int64Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private long[] mValue;
+
+ // Constructs a default-initialized {@link Int64Slice}.
+ public Int64Slice() {}
+
+
+ public long[] getValue() {
+ return mValue;
+ }
+
+ public Int64Slice setValue(long[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int64Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("int64", Method.Int64))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int64Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int64Slice o = (Int64Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.int64(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int64Slice o = (Int64Slice)obj;
+ o.mValue = new long[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.int64();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int8.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int8.java
new file mode 100644
index 0000000..b44e787
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int8.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int8 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private byte mValue;
+
+ // Constructs a default-initialized {@link Int8}.
+ public Int8() {}
+
+
+ public byte getValue() {
+ return mValue;
+ }
+
+ public Int8 setValue(byte v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int8_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("int8", Method.Int8)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int8(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int8 o = (Int8)obj;
+ e.int8(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int8 o = (Int8)obj;
+ o.mValue = d.int8();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Int8Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Int8Slice.java
new file mode 100644
index 0000000..61425f3
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Int8Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Int8Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private byte[] mValue;
+
+ // Constructs a default-initialized {@link Int8Slice}.
+ public Int8Slice() {}
+
+
+ public byte[] getValue() {
+ return mValue;
+ }
+
+ public Int8Slice setValue(byte[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "int8Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("int8", Method.Int8))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Int8Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Int8Slice o = (Int8Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.int8(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Int8Slice o = (Int8Slice)obj;
+ o.mValue = new byte[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.int8();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/ObjectBox.java b/rpclib/src/main/java/com/android/tools/rpclib/any/ObjectBox.java
new file mode 100644
index 0000000..7035774
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/ObjectBox.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class ObjectBox extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private BinaryObject mValue;
+
+ // Constructs a default-initialized {@link ObjectBox}.
+ public ObjectBox() {}
+
+
+ public BinaryObject getValue() {
+ return mValue;
+ }
+
+ public ObjectBox setValue(BinaryObject v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "object_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Interface("binary.Object")),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new ObjectBox(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ ObjectBox o = (ObjectBox)obj;
+ e.object(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ ObjectBox o = (ObjectBox)obj;
+ o.mValue = d.object();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/ObjectSlice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/ObjectSlice.java
new file mode 100644
index 0000000..a550cd9
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/ObjectSlice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class ObjectSlice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private BinaryObject[] mValue;
+
+ // Constructs a default-initialized {@link ObjectSlice}.
+ public ObjectSlice() {}
+
+
+ public BinaryObject[] getValue() {
+ return mValue;
+ }
+
+ public ObjectSlice setValue(BinaryObject[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "objectSlice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Interface("binary.Object"))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new ObjectSlice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ ObjectSlice o = (ObjectSlice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.object(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ ObjectSlice o = (ObjectSlice)obj;
+ o.mValue = new BinaryObject[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.object();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/StringBox.java b/rpclib/src/main/java/com/android/tools/rpclib/any/StringBox.java
new file mode 100644
index 0000000..a9431c4
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/StringBox.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class StringBox extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private String mValue;
+
+ // Constructs a default-initialized {@link StringBox}.
+ public StringBox() {}
+
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public StringBox setValue(String v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "string_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("string", Method.String)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new StringBox(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ StringBox o = (StringBox)obj;
+ e.string(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ StringBox o = (StringBox)obj;
+ o.mValue = d.string();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/StringSlice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/StringSlice.java
new file mode 100644
index 0000000..6b838da
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/StringSlice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class StringSlice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private String[] mValue;
+
+ // Constructs a default-initialized {@link StringSlice}.
+ public StringSlice() {}
+
+
+ public String[] getValue() {
+ return mValue;
+ }
+
+ public StringSlice setValue(String[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "stringSlice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("string", Method.String))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new StringSlice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ StringSlice o = (StringSlice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.string(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ StringSlice o = (StringSlice)obj;
+ o.mValue = new String[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.string();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint16.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint16.java
new file mode 100644
index 0000000..7386b6f
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint16.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint16 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private short mValue;
+
+ // Constructs a default-initialized {@link Uint16}.
+ public Uint16() {}
+
+
+ public short getValue() {
+ return mValue;
+ }
+
+ public Uint16 setValue(short v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint16_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("uint16", Method.Uint16)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint16(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint16 o = (Uint16)obj;
+ e.uint16(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint16 o = (Uint16)obj;
+ o.mValue = d.uint16();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint16Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint16Slice.java
new file mode 100644
index 0000000..7266a85
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint16Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint16Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private short[] mValue;
+
+ // Constructs a default-initialized {@link Uint16Slice}.
+ public Uint16Slice() {}
+
+
+ public short[] getValue() {
+ return mValue;
+ }
+
+ public Uint16Slice setValue(short[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint16Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("uint16", Method.Uint16))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint16Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint16Slice o = (Uint16Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.uint16(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint16Slice o = (Uint16Slice)obj;
+ o.mValue = new short[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.uint16();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint32.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint32.java
new file mode 100644
index 0000000..8f6382e
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint32.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint32 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private int mValue;
+
+ // Constructs a default-initialized {@link Uint32}.
+ public Uint32() {}
+
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public Uint32 setValue(int v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint32_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("uint32", Method.Uint32)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint32(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint32 o = (Uint32)obj;
+ e.uint32(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint32 o = (Uint32)obj;
+ o.mValue = d.uint32();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint32Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint32Slice.java
new file mode 100644
index 0000000..20a21eb
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint32Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint32Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private int[] mValue;
+
+ // Constructs a default-initialized {@link Uint32Slice}.
+ public Uint32Slice() {}
+
+
+ public int[] getValue() {
+ return mValue;
+ }
+
+ public Uint32Slice setValue(int[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint32Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("uint32", Method.Uint32))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint32Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint32Slice o = (Uint32Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.uint32(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint32Slice o = (Uint32Slice)obj;
+ o.mValue = new int[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.uint32();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint64.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint64.java
new file mode 100644
index 0000000..c8bc69c
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint64.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint64 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private long mValue;
+
+ // Constructs a default-initialized {@link Uint64}.
+ public Uint64() {}
+
+
+ public long getValue() {
+ return mValue;
+ }
+
+ public Uint64 setValue(long v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint64_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("uint64", Method.Uint64)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint64(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint64 o = (Uint64)obj;
+ e.uint64(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint64 o = (Uint64)obj;
+ o.mValue = d.uint64();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint64Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint64Slice.java
new file mode 100644
index 0000000..4225ca4
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint64Slice.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint64Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private long[] mValue;
+
+ // Constructs a default-initialized {@link Uint64Slice}.
+ public Uint64Slice() {}
+
+
+ public long[] getValue() {
+ return mValue;
+ }
+
+ public Uint64Slice setValue(long[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint64Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("uint64", Method.Uint64))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint64Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint64Slice o = (Uint64Slice)obj;
+ e.uint32(o.mValue.length);
+ for (int i = 0; i < o.mValue.length; i++) {
+ e.uint64(o.mValue[i]);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint64Slice o = (Uint64Slice)obj;
+ o.mValue = new long[d.uint32()];
+ for (int i = 0; i <o.mValue.length; i++) {
+ o.mValue[i] = d.uint64();
+ }
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint8.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint8.java
new file mode 100644
index 0000000..1328d18
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint8.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint8 extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private byte mValue;
+
+ // Constructs a default-initialized {@link Uint8}.
+ public Uint8() {}
+
+
+ public byte getValue() {
+ return mValue;
+ }
+
+ public Uint8 setValue(byte v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint8_", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Primitive("uint8", Method.Uint8)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint8(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint8 o = (Uint8)obj;
+ e.uint8(o.mValue);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint8 o = (Uint8)obj;
+ o.mValue = d.uint8();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/any/Uint8Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint8Slice.java
new file mode 100644
index 0000000..0c171af
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/any/Uint8Slice.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.any;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+final class Uint8Slice extends Box implements BinaryObject {
+ @Override
+ public Object unwrap() {
+ return getValue();
+ }
+
+ //<<<Start:Java.ClassBody:1>>>
+ private byte[] mValue;
+
+ // Constructs a default-initialized {@link Uint8Slice}.
+ public Uint8Slice() {}
+
+
+ public byte[] getValue() {
+ return mValue;
+ }
+
+ public Uint8Slice setValue(byte[] v) {
+ mValue = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("any", "uint8Slice", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Value", new Slice("", new Primitive("uint8", Method.Uint8))),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new Uint8Slice(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Uint8Slice o = (Uint8Slice)obj;
+ e.uint32(o.mValue.length);
+ e.write(o.mValue, o.mValue.length);
+
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Uint8Slice o = (Uint8Slice)obj;
+ o.mValue = new byte[d.uint32()];
+ d.read(o.mValue, o.mValue.length);
+
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryClass.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryClass.java
new file mode 100644
index 0000000..38f770c
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import com.android.tools.rpclib.schema.Entity;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+/**
+ *
+ */
+public interface BinaryClass {
+ @NotNull Entity entity();
+ @NotNull BinaryObject create();
+ void decode(@NotNull Decoder d, BinaryObject obj) throws IOException;
+ void encode(@NotNull Encoder e, BinaryObject obj) throws IOException;
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryID.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryID.java
new file mode 100644
index 0000000..d900941
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryID.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.xml.bind.DatatypeConverter;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * An ID is a codeable unique identifier.
+ * These are used as 20-byte-unique identifiers, often a sha checksum.
+ */
+public class BinaryID {
+ private static final int SIZE = 20;
+ public static BinaryID INVALID = new BinaryID();
+
+ @NotNull private final byte[] mValue = new byte[SIZE];
+ private final int mHashCode;
+
+ public BinaryID() {
+ mHashCode = 0;
+ }
+
+ public BinaryID(@NotNull byte[] value) {
+ assert value.length == SIZE;
+ System.arraycopy(value, 0, mValue, 0, SIZE);
+ mHashCode = ByteBuffer.wrap(mValue).getInt();
+ }
+
+ public BinaryID(@NotNull Decoder d) throws IOException {
+ d.read(mValue, SIZE);
+ mHashCode = ByteBuffer.wrap(mValue).getInt();
+ }
+
+ public void write(@NotNull Encoder e) throws IOException {
+ e.write(mValue, SIZE);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof BinaryID)) {
+ return false;
+ }
+ if (other == this) {
+ return true;
+ }
+ return Arrays.equals(mValue, ((BinaryID)other).mValue);
+ }
+
+ @Override
+ public String toString() {
+ return DatatypeConverter.printHexBinary(mValue).toLowerCase();
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ public byte[] getBytes() {
+ return mValue;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObject.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObject.java
new file mode 100644
index 0000000..b662d30
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/BinaryObject.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * An object that can be encoded and decoded using its {@link #klass}.
+ */
+public interface BinaryObject {
+ /**
+ * @return the object's type class.
+ */
+ @NotNull
+ BinaryClass klass();
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/BitField.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/BitField.java
new file mode 100644
index 0000000..3aa360b
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/BitField.java
@@ -0,0 +1,67 @@
+/*
+ * 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.tools.rpclib.binary;
+
+/**
+ * A set of constants that can be combined into a single field.
+ */
+public class BitField<T extends BitField<T>> {
+ protected int bits;
+
+ public BitField(Iterable<Bit<T>> bits) {
+ for (Bit<T> bit : bits) {
+ set(bit);
+ }
+ }
+
+ protected BitField(int value) {
+ bits = value;
+ }
+
+ public boolean isSet(Bit<T> bit) {
+ return (bits & bit.value) == bit.value;
+ }
+
+ public void set(Bit<T> bit) {
+ bits |= bit.value;
+ }
+
+ public void clear(Bit<T> bit) {
+ bits &= ~bit.value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || o.getClass() != getClass()) return false;
+ return bits == ((BitField<T>)o).bits;
+ }
+
+ @Override
+ public int hashCode() {
+ return bits;
+ }
+
+ public static class Bit<T extends BitField<T>> {
+ public final int value;
+ public final String name;
+
+ public Bit(int value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/Decoder.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/Decoder.java
new file mode 100644
index 0000000..57f5fb6
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/Decoder.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import com.android.tools.rpclib.schema.Dynamic;
+import com.android.tools.rpclib.schema.Entity;
+import gnu.trove.TIntObjectHashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * A decoder of various RPC primitive types.
+ * The encoding format is documented at the following link:
+ * https://android.googlesource.com/platform/tools/gpu/+/master/binary/doc.go
+ */
+public class Decoder {
+ @NotNull private final TIntObjectHashMap<Entity> mEntities;
+ @NotNull private final TIntObjectHashMap<BinaryObject> mObjects;
+ @NotNull private final InputStream mInputStream;
+ @NotNull private final byte[] mBuffer;
+ @NotNull private final EncodingControl mControl = new EncodingControl();
+
+ public Decoder(@NotNull InputStream in) {
+ mEntities = new TIntObjectHashMap<Entity>();
+ mObjects = new TIntObjectHashMap<BinaryObject>();
+ mInputStream = in;
+ mBuffer = new byte[9];
+ mEntities.put(0, null);
+ mObjects.put(0, null);
+ }
+
+ public void read(byte[] buf, int count) throws IOException {
+ int off = 0;
+ while (off < count) {
+ off += mInputStream.read(buf, off, count - off);
+ }
+ }
+
+ private void read(int count) throws IOException {
+ read(mBuffer, count);
+ }
+
+ public boolean bool() throws IOException {
+ read(1);
+ return mBuffer[0] != 0;
+ }
+
+ public byte int8() throws IOException {
+ read(1);
+ return mBuffer[0];
+ }
+
+ public byte uint8() throws IOException {
+ return int8();
+ }
+
+
+ private long intv() throws IOException {
+ long uv = uintv();
+ long v = uv >>> 1;
+ if ((uv & 1) != 0) {
+ v = ~v;
+ }
+ return v;
+ }
+
+ private long uintv() throws IOException {
+ read(1);
+ int count = 0;
+ while (((0x80 >> count) & mBuffer[0]) != 0) count++;
+ long v = mBuffer[0] & (0xff >> count);
+ if (count == 0) {
+ return v;
+ }
+ read(count);
+ for (int i = 0; i < count; i++) {
+ v = (v << 8) | (mBuffer[i] & 0xffL);
+ }
+ return v;
+ }
+
+ public short int16() throws IOException {
+ return (short)intv();
+ }
+
+ public short uint16() throws IOException {
+ return (short)uintv();
+ }
+
+ public int int32() throws IOException {
+ return (int)intv();
+ }
+
+ public int uint32() throws IOException {
+ return (int)uintv();
+ }
+
+ public long int64() throws IOException {
+ return intv();
+ }
+
+ public long uint64() throws IOException {
+ return uintv();
+ }
+
+ public float float32() throws IOException {
+ int bits = (int)uintv();
+ int shuffled = ((bits & 0x000000ff) << 24) |
+ ((bits & 0x0000ff00) << 8) |
+ ((bits & 0x00ff0000) >> 8) |
+ ((bits & 0xff000000) >>> 24);
+ return Float.intBitsToFloat(shuffled);
+ }
+
+ public double float64() throws IOException {
+ long bits = uintv();
+ long shuffled = ((bits & 0x00000000000000ffL) << 56) |
+ ((bits & 0x000000000000ff00L) << 40) |
+ ((bits & 0x0000000000ff0000L) << 24) |
+ ((bits & 0x00000000ff000000L) << 8) |
+ ((bits & 0x000000ff00000000L) >> 8) |
+ ((bits & 0x0000ff0000000000L) >> 24) |
+ ((bits & 0x00ff000000000000L) >> 40) |
+ ((bits & 0xff00000000000000L) >>> 56);
+ return Double.longBitsToDouble(shuffled);
+ }
+
+ public String string() throws IOException {
+ int size = uint32();
+ byte[] bytes = new byte[size];
+ for (int i = 0; i < size; i++) {
+ bytes[i] = int8();
+ }
+ try {
+ return new String(bytes, "UTF-8");
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e); // Should never happen
+ }
+ }
+
+ public String nonCompactString() throws IOException {
+ return (mControl.mode != EncodingControl.Compact) ? string() : "";
+ }
+
+ private int readSid() throws IOException {
+ int v = uint32();
+ if (v == 1) { // encoded sid 0 is a special marker
+ // read control block.
+ mControl.decode(this);
+ // read the real sid
+ v = uint32();
+ }
+ return v;
+ }
+
+ public Entity entity() throws IOException {
+ int v = readSid();
+ int sid = v >> 1;
+ if ((v & 1) != 0) {
+ Entity entity = new Entity();
+ mEntities.put(sid, entity);
+ entity.decode(this);
+ return entity;
+ }
+ if (!mEntities.containsKey(sid)) {
+ throw new RuntimeException("Unknown entity: " + sid);
+ }
+ return mEntities.get(sid);
+ }
+
+ public void value(@NotNull BinaryObject obj) throws IOException {
+ obj.klass().decode(this, obj);
+ }
+
+ @Nullable
+ public BinaryObject variant() throws IOException {
+ Entity entity = entity();
+ if (entity == null) {
+ return null;
+ }
+ BinaryClass c = Namespace.lookup(entity);
+ if (c == null) {
+ c = Dynamic.register(entity);
+ }
+ BinaryObject obj = c.create();
+ c.decode(this, obj);
+ return obj;
+ }
+
+ @Nullable
+ public BinaryObject object() throws IOException {
+ int v = readSid();
+ int sid = v >> 1;
+ if ((v & 1) != 0) {
+ BinaryObject obj = variant();
+ mObjects.put(sid, obj);
+ return obj;
+ }
+ if (!mObjects.containsKey(sid)) {
+ throw new RuntimeException("Unknown object: " + sid);
+ }
+ return mObjects.get(sid);
+ }
+
+ public InputStream stream() {
+ return mInputStream;
+ }
+
+ public int getMode() {
+ return mControl.mode;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/Encoder.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/Encoder.java
new file mode 100644
index 0000000..1b20735
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/Encoder.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import com.android.tools.rpclib.schema.Entity;
+import gnu.trove.TObjectIntHashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * An encoder of various primitive types.
+ * The encoding format is documented at the following link:
+ * https://android.googlesource.com/platform/tools/gpu/+/master/binary/doc.go
+ */
+public class Encoder {
+ @NotNull private final OutputStream mOutputStream;
+ @NotNull private final TObjectIntHashMap<Entity> mEntities;
+ @NotNull private final TObjectIntHashMap<BinaryObject> mObjects;
+ @NotNull private final byte[] mBuffer;
+ private boolean mControlNeeded = false;
+ @NotNull private final EncodingControl mControl = new EncodingControl();
+
+ public Encoder(@NotNull OutputStream out) {
+ mEntities = new TObjectIntHashMap<Entity>();
+ mObjects = new TObjectIntHashMap<BinaryObject>();
+ mOutputStream = out;
+ mBuffer = new byte[9];
+ mEntities.put(null, 0);
+ mObjects.put(null, 0);
+ }
+
+ public void write(byte[] b, int len) throws IOException {
+ mOutputStream.write(b, 0, len);
+ }
+
+ public void bool(boolean v) throws IOException {
+ mBuffer[0] = (byte)(v ? 1 : 0);
+ mOutputStream.write(mBuffer, 0, 1);
+ }
+
+ public void int8(byte v) throws IOException {
+ mBuffer[0] = v;
+ mOutputStream.write(mBuffer, 0, 1);
+ }
+
+ public void uint8(short v) throws IOException {
+ mBuffer[0] = (byte)(v & 0xff);
+ mOutputStream.write(mBuffer, 0, 1);
+ }
+
+ private void intv(long v) throws IOException {
+ long uv = v << 1;
+ if (v < 0) uv = ~uv;
+ uintv(uv);
+ }
+
+ private void uintv(long v) throws IOException {
+ long space = ~0x7fL;
+ int tag = 0;
+ for (int o = 8; true; o--) {
+ if ((v & space) == 0) {
+ mBuffer[o] = (byte)(v | tag);
+ mOutputStream.write(mBuffer, o, 9 - o);
+ return;
+ }
+ mBuffer[o] = (byte)(v&0xff);
+ v >>>= 8;
+ space >>= 1;
+ tag =(tag >> 1) | 0x80;
+ }
+ }
+
+ public void int16(short v) throws IOException {
+ intv(v);
+ }
+
+ public void uint16(int v) throws IOException {
+ uintv(v);
+ }
+
+ public void int32(int v) throws IOException {
+ intv(v);
+ }
+
+ public void uint32(long v) throws IOException {
+ uintv(v);
+ }
+
+ public void int64(long v) throws IOException {
+ intv(v);
+ }
+
+ public void uint64(long v) throws IOException {
+ uintv(v);
+ }
+
+ public void float32(float v) throws IOException {
+ int bits = Float.floatToIntBits(v);
+ int shuffled = ((bits & 0x000000ff) << 24) |
+ ((bits & 0x0000ff00) << 8) |
+ ((bits & 0x00ff0000) >> 8) |
+ ((bits & 0xff000000) >>> 24);
+ uintv(shuffled);
+ }
+
+ public void float64(double v) throws IOException {
+ long bits = Double.doubleToLongBits(v);
+ long shuffled = ((bits & 0x00000000000000ffL) << 56) |
+ ((bits & 0x000000000000ff00L) << 40) |
+ ((bits & 0x0000000000ff0000L) << 24) |
+ ((bits & 0x00000000ff000000L) << 8) |
+ ((bits & 0x000000ff00000000L) >> 8) |
+ ((bits & 0x0000ff0000000000L) >> 24) |
+ ((bits & 0x00ff000000000000L) >> 40) |
+ ((bits & 0xff00000000000000L) >>> 56);
+ uintv(shuffled);
+ }
+
+ public void string(@Nullable String v) throws IOException {
+ try {
+ if (v == null) {
+ uint32(0);
+ return;
+ }
+
+ byte[] bytes = v.getBytes("UTF-8");
+ uint32(bytes.length);
+ for (byte b : bytes) {
+ int8(b);
+ }
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e); // Should never happen
+ }
+ }
+
+ public void nonCompactString(@Nullable String v) throws IOException {
+ if (mControl.mode != EncodingControl.Compact) {
+ string(v);
+ }
+ }
+
+ public void writeSid(int sid, boolean encoded) throws IOException {
+ if (mControlNeeded) {
+ mControlNeeded = false; // set early to prevent recursive triggering
+ uint32(1); // encoded sid 0 is a special marker
+ mControl.encode(this); // and write the control block itself
+ }
+ uint32((sid << 1) | (encoded ? 1 : 0));
+ }
+
+ public void entity(Entity entity) throws IOException {
+ if (mEntities.containsKey(entity)) {
+ writeSid(mEntities.get(entity), false);
+ } else {
+ int sid = mEntities.size();
+ mEntities.put(entity, sid);
+ writeSid(sid, true);
+ entity.encode(this);
+ }
+ }
+
+ public void value(@Nullable BinaryObject obj) throws IOException {
+ obj.klass().encode(this, obj);
+ }
+
+ public void variant(@Nullable BinaryObject obj) throws IOException {
+ if (obj == null) {
+ entity(null);
+ } else {
+ BinaryClass c = obj.klass();
+ entity(c.entity());
+ c.encode(this, obj);
+ }
+ }
+
+ public void object(@Nullable BinaryObject obj) throws IOException {
+ if (mObjects.containsKey(obj)) {
+ writeSid(mObjects.get(obj), false);
+ } else {
+ int sid = mObjects.size();
+ mObjects.put(obj, sid);
+ writeSid(sid, true);
+ variant(obj);
+ }
+ }
+
+ public OutputStream stream() {
+ return mOutputStream;
+ }
+
+ public int getMode() {
+ return mControl.mode;
+ }
+
+ public int setMode(int mode) {
+ int oldMode = mControl.mode;
+ if (oldMode != mode) {
+ mControlNeeded = true;
+ mControl.mode = mode;
+ }
+ return oldMode;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/EncodingControl.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/EncodingControl.java
new file mode 100644
index 0000000..f1d5afc
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/EncodingControl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.rpclib.binary;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class EncodingControl {
+ public static final int Version = 0;
+ public static final int Compact = 0;
+ public static final int Full = 1;
+
+ public int mode;
+
+ public void encode(@NotNull Encoder e) throws IOException {
+ e.uint32(Version);
+ e.uint32(mode);
+ }
+
+ public void decode(@NotNull Decoder d) throws IOException {
+ int version = d.uint32();
+ if (version != Version) {
+ throw new IOException("(Invalid control block version");
+ }
+ mode = d.uint32();
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/binary/Namespace.java b/rpclib/src/main/java/com/android/tools/rpclib/binary/Namespace.java
new file mode 100644
index 0000000..e1fc05c
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/binary/Namespace.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import com.android.tools.rpclib.schema.Entity;
+import com.intellij.openapi.diagnostic.Logger;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ */
+public class Namespace {
+ private static Map<String, BinaryClass> registry = new ConcurrentHashMap<String, BinaryClass>(300);
+
+ @NotNull private static final Logger LOG = Logger.getInstance(Namespace.class);
+ public static void register(BinaryClass creator) {
+ registry.put(creator.entity().signature(), creator);
+ }
+
+ public static BinaryClass lookup(Entity entity) {
+ return registry.get(entity.signature());
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/futures/FutureController.java b/rpclib/src/main/java/com/android/tools/rpclib/futures/FutureController.java
new file mode 100644
index 0000000..5686466
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/futures/FutureController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.futures;
+
+import java.util.concurrent.Future;
+
+/**
+ * Interface implemented by types that modify or simply observe {@link Future}s
+ * as they are started and finished.
+ */
+public interface FutureController {
+ /**
+ * Called just after the {@link Future} is started.
+ *
+ * The function is free to cancel the {@link Future}.
+ */
+ void onStart(Future future);
+
+ /**
+ * Called just after the {@link Future} has finished.
+ *
+ * @return true if the {@link Future} was considered active by the controller.
+ */
+ boolean onStop(Future future);
+
+ /**
+ * Helper implementation of the interface that does nothing.
+ */
+ FutureController NULL_CONTROLLER = new FutureController() {
+ @Override
+ public void onStart(Future future) {}
+
+ @Override
+ public boolean onStop(Future future) { return true; }
+ };
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/futures/SingleInFlight.java b/rpclib/src/main/java/com/android/tools/rpclib/futures/SingleInFlight.java
new file mode 100644
index 0000000..d95a200
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/futures/SingleInFlight.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.futures;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An implementation of {@link FutureController} that ensures that only a single {@link Future} is
+ * in flight at any given time for a given system. If a {@link Future} is started before another
+ * has finished, then the first {@link Future} is cancelled.
+ *
+ * <p>This class can be used to automatically cancel older asynchronous tasks when newer ones are
+ * created. By using the constructor that takes a {@link Listener}, a loading progress element can
+ * be driven, even when task lifetimes overlap.
+ */
+public class SingleInFlight implements FutureController {
+ private final AtomicReference<Future> mActive = new AtomicReference<Future>();
+ private final Listener mListener;
+ private final Listener NULL_LISTENER = new Listener() {
+ @Override
+ public void onIdleToWorking() {}
+
+ @Override
+ public void onWorkingToIdle() {}
+ };
+
+ public interface Listener {
+ /**
+ * Called when transitioning from a state where no {@link Future}s are running to a
+ * single {@link Future} is running.
+ */
+ void onIdleToWorking();
+
+ /**
+ * Called when transitioning from a state where a single {@link Future} is running
+ * to no {@link Future}s are running.
+ */
+ void onWorkingToIdle();
+ }
+
+ public SingleInFlight() {
+ this.mListener = NULL_LISTENER;
+ }
+
+ public SingleInFlight(Listener listener) {
+ this.mListener = listener;
+ }
+
+ @Override
+ public void onStart(Future future) {
+ Future prev = mActive.getAndSet(future);
+ if (prev != null) {
+ prev.cancel(true);
+ } else {
+ mListener.onIdleToWorking();
+ }
+ }
+
+ @Override
+ public boolean onStop(Future future) {
+ if (mActive.compareAndSet(future, null)) {
+ mListener.onWorkingToIdle();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Channel.java b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Channel.java
new file mode 100644
index 0000000..d1b9b07
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Channel.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.multiplex;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Channel implements Closeable {
+ private final OutputStream mOutputStream;
+ private final InputStream mInputStream;
+ private final PipeInputStream mPipeInputStream;
+ private final long mId;
+ private final EventHandler mEventHandler;
+ private boolean mIsClosed;
+
+ public Channel(long id, @NotNull EventHandler events) {
+ PipeInputStream in = new PipeInputStream();
+
+ mId = id;
+ mEventHandler = events;
+ mInputStream = in;
+ mOutputStream = new Output();
+ mPipeInputStream = in;
+ }
+
+ public InputStream getInputStream() {
+ return mInputStream;
+ }
+
+ public OutputStream getOutputStream() {
+ return mOutputStream;
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ if (!mIsClosed) {
+ mIsClosed = true;
+ mEventHandler.closeChannel(mId);
+ mInputStream.close();
+ mOutputStream.close();
+ }
+ }
+
+ void receive(byte[] data) throws IOException {
+ mPipeInputStream.getSource().write(data);
+ }
+
+ synchronized void closeNoEvent() throws IOException {
+ if (!mIsClosed) {
+ mIsClosed = true;
+ mInputStream.close();
+ mOutputStream.close();
+ }
+ }
+
+ interface EventHandler {
+ void closeChannel(long id) throws IOException;
+ void writeChannel(long id, byte b[], int off, int len) throws IOException;
+ }
+
+ private class Output extends OutputStream {
+ @Override
+ public void write(int b) throws IOException, UnsupportedOperationException {
+ write(new byte[]{(byte)b}, 0, 1);
+ // We really, really should not be writing out single bytes. For now, throw an exception.
+ throw new UnsupportedOperationException("Use write(byte[], int, int) instead of writing single bytes!");
+ }
+
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ mEventHandler.writeChannel(mId, b, off, len);
+ }
+
+ @Override
+ public void close() throws IOException {
+ Channel.this.close();
+ }
+ }
+}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Message.java b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Message.java
similarity index 100%
rename from base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Message.java
rename to rpclib/src/main/java/com/android/tools/rpclib/multiplex/Message.java
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Multiplexer.java b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Multiplexer.java
new file mode 100644
index 0000000..c392d39
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Multiplexer.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.multiplex;
+
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.intellij.openapi.diagnostic.Logger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Multiplexer {
+ @NotNull private static final Logger LOG = Logger.getInstance(Multiplexer.class);
+ private final Decoder mDecoder;
+ private final Encoder mEncoder;
+ private final NewChannelListener mNewChannelListener;
+ private final Channel.EventHandler mChannelEventHandler;
+ private final Sender mSender;
+ private final AtomicLong mNextChannelId;
+ @GuardedBy("mChannelMap") private final Map<Long, Channel> mChannelMap;
+
+ public Multiplexer(@NotNull InputStream in, @NotNull OutputStream out, int mtu,
+ @NotNull ExecutorService executorService,
+ @Nullable NewChannelListener newChannelListener) {
+ mDecoder = new Decoder(in);
+ mEncoder = new Encoder(out);
+ mNewChannelListener = newChannelListener;
+ mChannelEventHandler = new ChannelEventHandler();
+ mSender = new Sender(mtu, executorService);
+ mChannelMap = new HashMap<Long, Channel>();
+ mNextChannelId = new AtomicLong(0);
+ executorService.execute(new Receiver());
+ }
+
+ public Channel openChannel() throws IOException {
+ final long id = mNextChannelId.getAndIncrement();
+ Channel channel = newChannel(id);
+ mSender.sendOpenChannel(id);
+ return channel;
+ }
+
+ private Channel newChannel(final long id) {
+ Long key = Long.valueOf(id);
+ Channel channel = new Channel(id, mChannelEventHandler);
+
+ synchronized (mChannelMap) {
+ if (mChannelMap.isEmpty()) {
+ mSender.begin(mEncoder);
+ }
+ mChannelMap.put(key, channel);
+ }
+
+ return channel;
+ }
+
+ private void deleteChannel(long id) {
+ Long key = Long.valueOf(id);
+ synchronized (mChannelMap) {
+ if (mChannelMap.containsKey(key)) {
+ // TODO: Mark channel closed.
+ mChannelMap.remove(key);
+ if (mChannelMap.isEmpty()) {
+ mSender.end();
+ }
+ }
+ else {
+ // This can happen when both ends close simultaneously.
+ LOG.info("Attempting to close unknown channel " + id);
+ }
+ }
+ }
+
+ private Channel getChannel(long id) {
+ Long key = Long.valueOf(id);
+ Channel channel;
+ synchronized (mChannelMap) {
+ channel = mChannelMap.get(key);
+ }
+ return channel;
+ }
+
+ private void closeAllChannels() {
+ synchronized (mChannelMap) {
+ for (Channel c : mChannelMap.values()) {
+ try {
+ c.close();
+ }
+ catch (IOException ignored) {}
+ }
+ mChannelMap.clear();
+ }
+ }
+
+ private class ChannelEventHandler implements Channel.EventHandler {
+ @Override
+ public void closeChannel(long id) throws IOException {
+ mSender.sendCloseChannel(id);
+ deleteChannel(id);
+ }
+
+ @Override
+ public void writeChannel(long id, byte[] b, int off, int len) throws IOException {
+ mSender.sendData(id, b, off, len);
+ }
+ }
+
+ private class Receiver extends Thread {
+ Receiver() {
+ super("rpclib.multiplex Receiver");
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ short msgType = mDecoder.uint8();
+ long id = ~(mDecoder.uint32() & 0xffffffff);
+ switch (msgType) {
+ case Message.OPEN_CHANNEL: {
+ Channel channel = newChannel(id);
+ if (mNewChannelListener != null) {
+ mNewChannelListener.onNewChannel(channel);
+ }
+ break;
+ }
+ case Message.CLOSE_CHANNEL: {
+ Channel channel = getChannel(id);
+ if (channel != null) {
+ channel.closeNoEvent();
+ deleteChannel(id);
+ }
+ break;
+ }
+ case Message.DATA: {
+ int count = mDecoder.uint32();
+ byte[] buf = new byte[count];
+ for (int offset = 0; offset < count;) {
+ offset += mDecoder.stream().read(buf, offset, count-offset);
+ }
+ Channel channel = getChannel(id);
+ if (channel != null) {
+ channel.receive(buf);
+ }
+ else {
+ // Likely this channel was closed this side, and we're receiving data
+ // that should be dropped on the floor.
+ LOG.info("Received data on unknown channel " + id);
+ }
+ break;
+ }
+ default:
+ throw new UnsupportedOperationException("Unknown msgType: " + msgType);
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ catch (UnsupportedOperationException e) {
+ LOG.error(e);
+ }
+ finally {
+ closeAllChannels();
+ }
+ }
+ }
+
+}
diff --git a/base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/NewChannelListener.java b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/NewChannelListener.java
similarity index 100%
rename from base/rpclib/src/main/java/com/android/tools/rpclib/multiplex/NewChannelListener.java
rename to rpclib/src/main/java/com/android/tools/rpclib/multiplex/NewChannelListener.java
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/multiplex/PipeInputStream.java b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/PipeInputStream.java
new file mode 100644
index 0000000..0e01f14
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/PipeInputStream.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.multiplex;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.concurrent.Semaphore;
+
+/**
+ * An object that provides an {@link java.io.InputStream} interface to read data that has been written to a
+ * {@link java.io.OutputStream}.
+ * <p/>
+ * Buffers passed to {@link java.io.OutputStream#write} on the {@link #mSource} stream are <b>not</b> internally copied,
+ * and are assumed immutable. Mutation of any buffers passed to the {@link java.io.OutputStream#write} after the method
+ * has returned will result in undefined behaviour when calling the {@link #read} methods.
+ * <p/>
+ * Note: This is similar to {@link java.io.PipedInputStream} and {@link java.io.PipedOutputStream}, except this
+ * implementation does not use an internal ring buffer, and and does not suffer from 1 second stalls (JDK-4404700).
+ */
+public class PipeInputStream extends InputStream {
+ private static final Item ITEM_CLOSE = new Item(null, 0, 0);
+ private final OutputStream mSource;
+ private final LinkedList<Item> mQueue;
+ private final Semaphore mSemaphore;
+ private final byte[] mByte;
+
+ PipeInputStream() {
+ mQueue = new LinkedList<Item>();
+ mSemaphore = new Semaphore(0);
+ mByte = new byte[1];
+ mSource = new Writer();
+ }
+
+ public OutputStream getSource() {
+ return mSource;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return (read(mByte, 0, 1) > 0) ? mByte[0] : -1;
+ }
+
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ int n = 0;
+ boolean closed = false;
+ while (!closed && len > n) {
+ try {
+ mSemaphore.acquire();
+ synchronized (mQueue) {
+ Item item = mQueue.getFirst();
+ if (item != ITEM_CLOSE) {
+ n += item.read(b, off + n, len - n);
+ if (item.remaining() == 0) {
+ mQueue.removeFirst();
+ }
+ else {
+ mSemaphore.release();
+ }
+ }
+ else {
+ closed = true;
+ }
+ }
+ }
+ catch (InterruptedException e) {
+ break;
+ }
+ }
+ if (n == 0 && closed) {
+ return -1;
+ }
+ return n;
+ }
+
+ private static class Item {
+ private final byte[] mData;
+ private final int mCount;
+ private int mOffset;
+
+ public Item(byte[] data, int offset, int count) {
+ mData = data;
+ mCount = count;
+ mOffset = offset;
+ }
+
+ public int read(byte[] out, int offset, int count) {
+ int remaining = remaining();
+ if (count > remaining) {
+ count = remaining;
+ }
+ System.arraycopy(mData, mOffset, out, offset, count);
+ mOffset += count;
+ return count;
+ }
+
+ public int remaining() {
+ return mCount - mOffset;
+ }
+ }
+
+ private class Writer extends OutputStream {
+ @Override
+ public void write(int b) throws IOException {
+ write(new byte[]{(byte)b}, 0, 1);
+ }
+
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ if (len > 0) {
+ synchronized (mQueue) {
+ mQueue.addLast(new Item(b, off, len));
+ }
+ mSemaphore.release();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (mQueue) {
+ mQueue.addLast(ITEM_CLOSE);
+ mSemaphore.release();
+ }
+ }
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Sender.java b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Sender.java
new file mode 100644
index 0000000..dbc5c6a
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/multiplex/Sender.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.multiplex;
+
+import com.android.tools.rpclib.binary.Encoder;
+import gnu.trove.TLongObjectHashMap;
+import gnu.trove.TLongObjectIterator;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+
+class Sender {
+ private static final int MAX_PENDING_SEND_COUNT = 1024;
+ private static final SendItem NOP_ITEM = new SendNop();
+ private final int mMtu;
+ @NotNull private ExecutorService mExecutorService;
+ @NotNull private final LinkedBlockingQueue<SendItem> mPendingItems;
+ private Worker mWorker;
+
+ Sender(int mtu, @NotNull ExecutorService executorService) {
+ mMtu = mtu;
+ mExecutorService = executorService;
+ mPendingItems = new LinkedBlockingQueue<SendItem>(MAX_PENDING_SEND_COUNT);
+ }
+
+ void begin(Encoder out) {
+ mWorker = new Worker(out);
+ mExecutorService.execute(mWorker);
+ }
+
+ void end() {
+ try {
+ synchronized (mWorker) {
+ mWorker.setRunning(false);
+ mPendingItems.add(NOP_ITEM); // Unblock the sender
+ while (!mWorker.isStopped()) {
+ mWorker.wait();
+ }
+ mWorker = null;
+ }
+ }
+ catch (InterruptedException e) {
+ }
+ }
+
+ void sendData(long channel, byte b[], int off, int len) throws IOException {
+ send(new SendData(channel, b, off, len));
+ }
+
+ void sendOpenChannel(long channel) throws IOException {
+ send(new OpenChannel(channel));
+ }
+
+ void sendCloseChannel(long channel) throws IOException {
+ send(new CloseChannel(channel));
+ }
+
+ private void send(SendItem item) throws IOException {
+ if (mWorker == null) {
+ throw new RuntimeException("Attempting to send item when sender is not running");
+ }
+ mPendingItems.add(item);
+ item.sync();
+ }
+
+ private static abstract class SendItem {
+ final long mChannel;
+ private boolean mDone;
+ private IOException mException;
+
+ SendItem(long channel) {
+ mChannel = channel;
+ }
+
+ /**
+ * Encodes the item to the provided {@link Encoder}, unblocking any calls to {@link #sync}.
+ *
+ * @return true if the item was fully sent, or false if there is more to send.
+ */
+ final boolean send(Encoder e) {
+ try {
+ return encode(e);
+ }
+ catch (IOException exception) {
+ synchronized (this) {
+ mException = exception;
+ }
+ return true;
+ }
+ finally {
+ synchronized (this) {
+ mDone = true;
+ notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Waits for {@link #send} to be called, re-throwing an {@link java.io.IOException} if there was an exception
+ * thrown while sending the item.
+ */
+ final void sync() throws IOException {
+ synchronized (this) {
+ while (!mDone) {
+ try {
+ this.wait();
+ }
+ catch (InterruptedException e) {
+ }
+ }
+ if (mException != null) {
+ throw mException;
+ }
+ }
+ }
+
+
+ /** @return true if the item was fully sent, or false if there is more to send. */
+ protected abstract boolean encode(Encoder e) throws IOException;
+ }
+
+ private static class OpenChannel extends SendItem {
+ OpenChannel(long channel) {
+ super(channel);
+ }
+
+ @Override
+ protected boolean encode(Encoder e) throws IOException {
+ e.uint8(Message.OPEN_CHANNEL);
+ e.uint32(mChannel);
+ return true;
+ }
+ }
+
+ private static class CloseChannel extends SendItem {
+ CloseChannel(long channel) {
+ super(channel);
+ }
+
+ @Override
+ protected boolean encode(Encoder e) throws IOException {
+ e.uint8(Message.CLOSE_CHANNEL);
+ e.uint32(mChannel);
+ return true;
+ }
+ }
+
+ /** SendNop encodes nothing, and is simply used to unblock the sender in {@link #end}. */
+ private static class SendNop extends SendItem {
+ SendNop() {
+ super(0);
+ }
+
+ @Override
+ protected boolean encode(Encoder e) {
+ return true;
+ }
+ }
+
+ private final class Worker extends Thread {
+ private final Encoder mEncoder;
+ private boolean mIsRunning;
+ private boolean mIsStopped;
+
+ Worker(Encoder encoder) {
+ super("rpclib.multiplex Sender");
+ mEncoder = encoder;
+ mIsRunning = true;
+ }
+
+ public boolean isStopped() {
+ return mIsStopped;
+ }
+
+ public void setRunning(boolean running) {
+ mIsRunning = running;
+ }
+
+ @Override
+ public void run() {
+ SendMap map = new SendMap();
+ try {
+ while (mIsRunning) {
+ SendItem item;
+ if (map.size() == 0) {
+ // If there's nothing being worked on, block until we have something.
+ item = mPendingItems.take();
+ }
+ else {
+ // If we're busy, grab more work only if there's something there.
+ item = mPendingItems.poll();
+ }
+ if (item != null) {
+ map.add(item);
+ }
+ map.flush(mEncoder);
+ }
+ // Drain map
+ while (map.size() > 0) {
+ map.flush(mEncoder);
+ }
+ // Signal that this thread is done
+ synchronized (this) {
+ mIsStopped = true;
+ notifyAll();
+ }
+ }
+ catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private class SendData extends SendItem {
+ final byte[] mData;
+ int mOffset;
+ int mLength;
+
+ SendData(long channel, byte[] data, int off, int len) {
+ super(channel);
+ mData = data.clone();
+ mOffset = off;
+ mLength = len;
+ }
+
+ @Override
+ protected boolean encode(Encoder e) throws IOException {
+ e.uint8(Message.DATA);
+ e.uint32(mChannel);
+ int c = Math.min(mLength, mMtu);
+ e.uint32(c);
+ e.stream().write(mData, mOffset, c);
+ mOffset += c;
+ mLength -= c;
+ return mLength == 0;
+ }
+ }
+
+ private class SendMap {
+ @NotNull private final TLongObjectHashMap<Queue<SendItem>> mQueues =
+ new TLongObjectHashMap<Queue<SendItem>>();
+
+ public int size() {
+ return mQueues.size();
+ }
+
+ public void add(SendItem item) {
+ long channel = item.mChannel;
+ Queue<SendItem> queue = mQueues.get(channel);
+ if (queue == null) {
+ queue = new ArrayDeque<SendItem>();
+ mQueues.put(channel, queue);
+ }
+ queue.add(item);
+ }
+
+ public void flush(Encoder e) {
+ TLongObjectIterator<Queue<SendItem>> it = mQueues.iterator();
+ for (int i = mQueues.size(); i-- > 0; ) {
+ it.advance();
+ Queue<SendItem> queue = it.value();
+ SendItem item = queue.peek();
+ if (item.send(e)) {
+ // Item has been fully encoded.
+ queue.remove();
+ if (queue.poll() == null) {
+ it.remove();
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Broadcaster.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Broadcaster.java
new file mode 100644
index 0000000..2b76342
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Broadcaster.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.rpccore;
+
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.multiplex.Channel;
+import com.android.tools.rpclib.multiplex.Multiplexer;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.ExecutorService;
+
+public class Broadcaster {
+ private final Multiplexer mMultiplexer;
+ private final int mMtu;
+ private final int mVersion;
+
+ public Broadcaster(@NotNull InputStream in, @NotNull OutputStream out, int mtu,
+ @NotNull ExecutorService executorService,
+ int version) {
+ mMultiplexer = new Multiplexer(in, out, mtu, executorService, null);
+ mMtu = mtu;
+ mVersion = version;
+ }
+
+ private void writeHeader(@NotNull Encoder encoder) throws IOException {
+ // GAPIS version 2 supports the 'rpc1' protocol.
+ // Earlier versions will not recognise the new RPC header.
+ encoder.int8((byte)'r');
+ encoder.int8((byte)'p');
+ encoder.int8((byte)'c');
+ encoder.int8((byte)(mVersion < 2 ? '0' : '1'));
+ }
+
+ public BinaryObject Send(@NotNull BinaryObject call) throws IOException, RpcException {
+ Channel channel = mMultiplexer.openChannel();
+
+ try {
+ BufferedOutputStream out = new BufferedOutputStream(channel.getOutputStream(), mMtu);
+ Encoder e = new Encoder(out);
+ Decoder d = new Decoder(channel.getInputStream());
+
+ // Write the RPC header
+ writeHeader(e);
+
+ // Write the call
+ e.object(call);
+
+ // Flush the buffer
+ out.flush();
+
+ // Wait for and read the response
+ BinaryObject res = d.object();
+
+ // Check to see if the response was an error
+ if (res instanceof RpcException) {
+ throw (RpcException)res;
+ }
+
+ return res;
+ }
+ finally {
+ // Close the channel
+ channel.close();
+ }
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrDecodingCall.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrDecodingCall.java
new file mode 100644
index 0000000..eb1eb5f
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrDecodingCall.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.rpccore;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.*;
+import com.android.tools.rpclib.schema.*;
+
+import java.io.IOException;
+
+public final class ErrDecodingCall extends RpcException implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private String mReason;
+
+ // Constructs a default-initialized {@link ErrDecodingCall}.
+ public ErrDecodingCall() {}
+
+
+ public String getReason() {
+ return mReason;
+ }
+
+ public ErrDecodingCall setReason(String v) {
+ mReason = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("rpc", "ErrDecodingCall", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Reason", new Primitive("string", Method.String)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+
+ @Override
+ public String getMessage() {
+ return "Error decoding RPC call: " + mReason;
+ }
+
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new ErrDecodingCall(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ ErrDecodingCall o = (ErrDecodingCall)obj;
+ e.string(o.mReason);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ ErrDecodingCall o = (ErrDecodingCall)obj;
+ o.mReason = d.string();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrInvalidHeader.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrInvalidHeader.java
new file mode 100644
index 0000000..ba06d1a
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrInvalidHeader.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.rpccore;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.*;
+import com.android.tools.rpclib.schema.*;
+
+import java.io.IOException;
+
+public final class ErrInvalidHeader extends RpcException implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private byte[] mHeader;
+
+ // Constructs a default-initialized {@link ErrInvalidHeader}.
+ public ErrInvalidHeader() {}
+
+
+ public byte[] getHeader() {
+ return mHeader;
+ }
+
+ public ErrInvalidHeader setHeader(byte[] v) {
+ mHeader = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("rpc", "ErrInvalidHeader", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Header", new Array("", new Primitive("byte", Method.Uint8), 4)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+
+ @Override
+ public String getMessage() {
+ return "Invalid RPC header: " + mHeader;
+ }
+
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new ErrInvalidHeader(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ ErrInvalidHeader o = (ErrInvalidHeader)obj;
+ e.write(o.mHeader, 4);
+
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ ErrInvalidHeader o = (ErrInvalidHeader)obj;
+ d.read(o.mHeader, o.mHeader.length);
+
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrPanic.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrPanic.java
new file mode 100644
index 0000000..b318e29
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrPanic.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.rpccore;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.*;
+import com.android.tools.rpclib.schema.*;
+
+import java.io.IOException;
+
+public final class ErrPanic extends RpcException implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private String mMsg;
+
+ // Constructs a default-initialized {@link ErrPanic}.
+ public ErrPanic() {}
+
+
+ public String getMsg() {
+ return mMsg;
+ }
+
+ public ErrPanic setMsg(String v) {
+ mMsg = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("rpc", "ErrPanic", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Msg", new Primitive("string", Method.String)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+
+ @Override
+ public String getMessage() {
+ return "Panic raised when calling RPC: " + mMsg;
+ }
+
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new ErrPanic(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ ErrPanic o = (ErrPanic)obj;
+ e.string(o.mMsg);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ ErrPanic o = (ErrPanic)obj;
+ o.mMsg = d.string();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrUnknownFunction.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrUnknownFunction.java
new file mode 100644
index 0000000..76a0695
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/ErrUnknownFunction.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.rpccore;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.*;
+import com.android.tools.rpclib.schema.*;
+
+import java.io.IOException;
+
+public final class ErrUnknownFunction extends RpcException implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private String mFunction;
+
+ // Constructs a default-initialized {@link ErrUnknownFunction}.
+ public ErrUnknownFunction() {}
+
+
+ public String getFunction() {
+ return mFunction;
+ }
+
+ public ErrUnknownFunction setFunction(String v) {
+ mFunction = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("rpc", "ErrUnknownFunction", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Function", new Primitive("string", Method.String)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+
+ @Override
+ public String getMessage() {
+ return "Unknown RPC function: " + mFunction;
+ }
+
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new ErrUnknownFunction(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ ErrUnknownFunction o = (ErrUnknownFunction)obj;
+ e.string(o.mFunction);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ ErrUnknownFunction o = (ErrUnknownFunction)obj;
+ o.mFunction = d.string();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Factory.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Factory.java
new file mode 100644
index 0000000..594ac8f
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Factory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.rpccore;
+
+public final class Factory {
+ public static void register() {
+ com.android.tools.rpclib.any.Factory.register();
+ com.android.tools.rpclib.schema.Factory.register();
+ //<<<Start:Java.FactoryBody:2>>>
+ ErrDecodingCall.register();
+ ErrInvalidHeader.register();
+ ErrPanic.register();
+ ErrUnknownFunction.register();
+ RpcError.register();
+ //<<<End:Java.FactoryBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Rpc.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Rpc.java
new file mode 100644
index 0000000..60c0f4b
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/Rpc.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.rpccore;
+
+import com.android.tools.rpclib.futures.FutureController;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.intellij.openapi.diagnostic.Logger;
+
+import java.util.concurrent.*;
+
+/**
+ * Holds static method helpers for getting RPC call results from {@link ListenableFuture}s
+ * created from RPC calls.
+ */
+public class Rpc {
+ public static final Executor DEFAULT_EXECUTOR = MoreExecutors.sameThreadExecutor();
+
+ /**
+ * Blocks and waits for the result of the RPC call, or throws an exception if the RPC call was not
+ * successful.
+ *
+ * <p>{@link RpcException}s packed in {@link ExecutionException}s thrown by the
+ * {@link ListenableFuture} are unpacked and rethrown so they can be explicitly
+ * handled using {@code catch} clauses by the caller.
+ *
+ * @param <V> the result value type.
+ * @return the result value.
+ * @throws RpcException if there was an error raised by the server.
+ * @throws ExecutionException if there was a non-{@link RpcException} thrown by the
+ * {@link ListenableFuture}.
+ * @throws CancellationException if the computation was cancelled.
+ */
+ public static <V> V get(final ListenableFuture<V> future, long timeout, TimeUnit unit)
+ throws RpcException, TimeoutException, ExecutionException {
+ try {
+ return Uninterruptibles.getUninterruptibly(future, timeout, unit);
+ }
+ catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (!(cause instanceof RpcException)) {
+ throw e;
+ }
+ throw (RpcException)cause;
+ }
+ }
+
+ /**
+ * Calls the provided {@link Callback#onStart} function and then {@link Callback#onFinish}
+ * with the {@link Result} once the {@link ListenableFuture} RPC call has either successfully
+ * completed or thrown an exception.
+ *
+ * <p>If {@link Callback#onFinish} does not throw an uncaught,
+ * non-{@link CancellationException} then it is logged as an error to the provided
+ * {@link Logger}.
+ *
+ * <p>Both {@link Callback#onStart} and {@link Callback#onFinish} are called using the default
+ * same thread {@link Executor}.
+ *
+ * @param future the {@link ListenableFuture} returned by the invoking the RPC call.
+ * @param log the {@link Logger} used for logging uncaught exceptions thrown from {@link Callback#onFinish}.
+ * @param callback the {@link Callback} to handle {@link Callback#onStart} and {@link Callback#onFinish} events.
+ * @param <V> the RPC result type.
+ */
+ public static <V> void listen(ListenableFuture<V> future, Logger log, Callback<V> callback) {
+ listen(future, DEFAULT_EXECUTOR, log, FutureController.NULL_CONTROLLER, callback);
+ }
+
+ /**
+ * Calls the provided {@link Callback#onStart} function and then {@link Callback#onFinish}
+ * with the {@link Result} once the {@link ListenableFuture} RPC call has either successfully
+ * completed or thrown an exception.
+ *
+ * <p>If {@link Callback#onFinish} does not throws an uncaught,
+ * non-{@link CancellationException} then it is logged as an error to the provided
+ * {@link Logger}.
+ *
+ * <p>Both {@link Callback#onStart} and {@link Callback#onFinish} are called using the provided
+ * {@link Executor}.
+ *
+ * @param future the {@link ListenableFuture} returned by the invoking the RPC call.
+ * @param executor the {@link Executor} used for invoking the {@link Callback} methods.
+ * @param log the {@link Logger} used for logging uncaught exceptions thrown from {@link Callback#onFinish}.
+ * @param callback the {@link Callback} to handle {@link Callback#onStart} and {@link Callback#onFinish} events.
+ * @param <V> the RPC result type.
+ */
+ public static <V> void listen(ListenableFuture<V> future, Executor executor, Logger log, Callback<V> callback) {
+ listen(future, executor, log, FutureController.NULL_CONTROLLER, callback);
+ }
+
+ /**
+ * Extension of {@link #listen(ListenableFuture, Logger, FutureController, Callback)}
+ * that also takes a {@link FutureController} for controlling the {@link Future}s.
+ *
+ * @param future the {@link ListenableFuture} returned by the invoking the RPC call.
+ * @param executor the {@link Executor} used for invoking the {@link Callback} methods.
+ * @param log the {@link Logger} used for logging uncaught exceptions thrown from {@link Callback#onFinish}.
+ * @param callback the {@link Callback} to handle {@link Callback#onStart} and {@link Callback#onFinish} events.
+ * @param controller the {@link FutureController} used to manage the RPC futures.
+ * @param <V> the RPC result type.
+ */
+ public static <V> void listen(ListenableFuture<V> future, Logger log, FutureController controller, Callback<V> callback) {
+ listen(future, DEFAULT_EXECUTOR, log, controller, callback);
+ }
+
+ /**
+ * Extension of {@link #listen(ListenableFuture, Executor, Logger, FutureController, Callback)}
+ * that also takes a {@link FutureController} for controlling the {@link Future}s.
+ *
+ * @param future the {@link ListenableFuture} returned by the invoking the RPC call.
+ * @param executor the {@link Executor} used for invoking the {@link Callback} methods.
+ * @param log the {@link Logger} used for logging uncaught exceptions thrown from {@link Callback#onFinish}.
+ * @param callback the {@link Callback} to handle {@link Callback#onStart} and {@link Callback#onFinish} events.
+ * @param controller the {@link FutureController} used to manage the RPC futures.
+ * @param <V> the RPC result type.
+ */
+ public static <V> void listen(final ListenableFuture<V> future,
+ final Executor executor,
+ final Logger log,
+ final FutureController controller,
+ final Callback<V> callback) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ controller.onStart(future);
+ callback.onStart();
+ }
+ });
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ if (!controller.onStop(future)) {
+ return;
+ }
+ try {
+ callback.onFinish(new Result<V>(future));
+ }
+ catch (CancellationException e) {
+ // Not an error, don't log.
+ }
+ catch (Exception e) {
+ log.error(e);
+ }
+ }
+ }, executor);
+ }
+
+ /**
+ * Callback methods for the {@link #listen} function.
+ *
+ * @param <V> the RPC result type.
+ */
+ public static abstract class Callback <V> {
+ /**
+ * Called as soon as the {@link listen} function is called.
+ * It can be used to start loading indicators.
+ */
+ public void onStart() {}
+
+ /**
+ * Called once the RPC call has a result (success or failure).
+ *
+ * <p>Call {@link Result#get()} to get the RPC result.
+ */
+ public abstract void onFinish(Result<V> result) throws RpcException, ExecutionException;
+ }
+
+ /**
+ * Result wraps the {@link ListenableFuture} passed to {@link #listen}, providing a single
+ * {@link #get} method for accessing the result of the RPC call.
+ *
+ * @param <V> the RPC result type.
+ */
+ public static class Result <V> {
+ private final ListenableFuture<V> mFuture;
+
+ private Result(ListenableFuture<V> future) {
+ mFuture = future;
+ }
+
+ /**
+ * Returns the result of the RPC call, or throws an exception if the RPC call was not
+ * successful.
+ *
+ * <p>{@link RpcException}s packed in {@link ExecutionException}s thrown by the
+ * {@link ListenableFuture} are unpacked and rethrown so they can be explicitly
+ * handled using {@code catch} clauses by the caller.
+ *
+ * @param <V> the result value type.
+ * @return the result value.
+ * @throws RpcException if there was an error raised by the server.
+ * @throws ExecutionException if there was a non-{@link RpcException} thrown by the
+ * {@link ListenableFuture}.
+ * @throws CancellationException if the computation was cancelled.
+ */
+ public V get() throws RpcException, ExecutionException {
+ try {
+ return Uninterruptibles.getUninterruptibly(mFuture);
+ }
+ catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (!(cause instanceof RpcException)) {
+ throw e;
+ }
+ throw (RpcException)cause;
+ }
+ }
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcError.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcError.java
new file mode 100644
index 0000000..1be8faf
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcError.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.rpccore;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+public final class RpcError extends RpcException implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private String mMsg;
+
+ // Constructs a default-initialized {@link RpcError}.
+ public RpcError() {}
+
+
+ public String getMsg() {
+ return mMsg;
+ }
+
+ public RpcError setMsg(String v) {
+ mMsg = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("rpc", "Error", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Msg", new Primitive("string", Method.String)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+
+ @Override
+ public String getMessage() {
+ return "RPC error: " + mMsg;
+ }
+
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new RpcError(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ RpcError o = (RpcError)obj;
+ e.string(o.mMsg);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ RpcError o = (RpcError)obj;
+ o.mMsg = d.string();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcException.java b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcException.java
new file mode 100644
index 0000000..d8410de
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/rpccore/RpcException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.rpccore;
+
+/**
+ * The exception derived by all errors sent over the wire in response to an RPC that cannot
+ * return the requested data.
+ */
+public class RpcException extends Exception {}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/AnyType.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/AnyType.java
new file mode 100644
index 0000000..19d00ff
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/AnyType.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.any.Box;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class AnyType extends Type {
+ public AnyType() {
+ }
+
+ /** @param d decoder **/
+ public AnyType(Decoder d) {
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ e.variant(Box.wrap(value));
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ Box boxed = (Box) d.variant();
+ if (boxed == null) {
+ return null;
+ }
+ return boxed.unwrap();
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.AnyTag.encode(e);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append("any");
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append('~');
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Array.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Array.java
new file mode 100644
index 0000000..c3e1fe7
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Array.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Array extends Type {
+ String mAlias;
+
+ Type mValueType;
+
+ int mSize;
+
+ public Array(String alias, Type type, int size) {
+ mAlias = alias;
+ mValueType = type;
+ mSize = size;
+ }
+
+ public Array(@NotNull Decoder d) throws IOException {
+ mSize = d.uint32();
+ mValueType = decode(d);
+ mAlias = d.nonCompactString();
+ }
+
+ public String getAlias() {
+ return mAlias;
+ }
+
+ public Type getValueType() {
+ return mValueType;
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ assert (value instanceof Object[]);
+ Object[] array = (Object[]) value;
+ for (int i = 0; i < mSize; i++) {
+ mValueType.encodeValue(e, array[i]);
+ }
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ Object[] array = new Object[mSize];
+ for (int i = 0; i < mSize; i++) {
+ array[i] = mValueType.decodeValue(d);
+ }
+ return array;
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.ArrayTag.encode(e);
+ e.uint32(mSize);
+ mValueType.encode(e);
+ e.nonCompactString(mAlias);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append("array<");
+ mValueType.name(out);
+ out.append('>');
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append('[').append(mSize).append(']');
+ mValueType.signature(out);
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Constant.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Constant.java
new file mode 100644
index 0000000..a2c1c40
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Constant.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+public final class Constant {
+ String mName;
+ Object mValue;
+
+ // Constructs a default-initialized {@link Constant}.
+ public Constant() {}
+ public String getName() {
+ return mName;
+ }
+ public Object getValue() {
+ return mValue;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/ConstantSet.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/ConstantSet.java
new file mode 100644
index 0000000..4c947b7
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/ConstantSet.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class ConstantSet {
+ private Type mType;
+ private Constant[] mEntries;
+ private HashMap<Object, List<Constant>> byValue;
+ private HashMap<String, List<Constant>> byStrValue;
+
+ private static final HashMap<Type, ConstantSet> mRegistry = new HashMap<Type, ConstantSet>();
+
+ public static void register(ConstantSet set) {
+ mRegistry.put(set.getType(), set);
+ }
+
+ public static ConstantSet lookup(Type type) {
+ return mRegistry.get(type);
+ }
+
+ // Constructs a default-initialized {@link ConstantSet}.
+ public ConstantSet(@NotNull Decoder d) throws IOException {
+ mType = Type.decode(d);
+ mEntries = new Constant[d.uint32()];
+ for (int i = 0; i < mEntries.length; i++) {
+ mEntries[i] = new Constant();
+ mEntries[i].mName = d.string();
+ mEntries[i].mValue = mType.decodeValue(d);
+ }
+ }
+
+ public Type getType() {
+ return mType;
+ }
+
+ public Constant[] getEntries() {
+ return mEntries;
+ }
+
+ private HashMap<Object, List<Constant>> byValue() {
+ if (byValue == null) {
+ // build a map from value (as Object) to List of possible constants.
+ byValue = new HashMap<Object, List<Constant>>();
+ for (Constant constant : mEntries) {
+ List<Constant> list;
+ if (!byValue.containsKey(constant.getValue())) {
+ list = new ArrayList<Constant>();
+ byValue.put(constant.getValue(), list);
+ } else {
+ list = byValue.get(constant.getValue());
+ }
+ list.add(constant);
+ }
+ }
+ return byValue;
+ }
+
+ private HashMap<String, List<Constant>> byStrValue() {
+ if (byStrValue == null) {
+ // build a map from value (as String) to List of possible constants.
+ byStrValue = new HashMap<String, List<Constant>>();
+ for (Constant constant : mEntries) {
+ List<Constant> list;
+ String strVal = constant.getValue().toString();
+ if (!byStrValue.containsKey(strVal)) {
+ list = new ArrayList<Constant>();
+ byStrValue.put(strVal, list);
+ } else {
+ list = byStrValue.get(strVal);
+ }
+ list.add(constant);
+ }
+ }
+ return byStrValue;
+ }
+
+ public List<Constant> getByValue(Object value) {
+ if (mEntries.length == 0) {
+ return null;
+ }
+ if (mEntries[0].getClass() == value.getClass()) {
+ // Prefer an exact match, when possible.
+ return byValue().get(value);
+ } else {
+ return byStrValue().get(value.toString());
+ }
+ }
+ public void encode(@NotNull Encoder e) throws IOException {
+ mType.encode(e);
+ for (Constant mEntry : mEntries) {
+ e.string(mEntry.mName);
+ mType.encodeValue(e, mEntry.mValue);
+ }
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Dynamic.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Dynamic.java
new file mode 100644
index 0000000..e240027
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Dynamic.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+
+import com.android.tools.rpclib.binary.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+public class Dynamic implements BinaryObject {
+
+ private Klass mKlass;
+
+ private Object[] mFields;
+
+ public Dynamic(Klass klass) {
+ mKlass = klass;
+ }
+
+ public Entity type() {
+ return mKlass.mType;
+ }
+
+ public static BinaryClass register(Entity type) {
+ BinaryClass klass = new Klass(type);
+ Namespace.register(klass);
+ return klass;
+ }
+
+ public int getFieldCount() {
+ return mFields.length;
+ }
+
+ public Field getFieldInfo(int index) {
+ return mKlass.mType.getFields()[index];
+ }
+
+ public Object getFieldValue(int index) {
+ return mFields[index];
+ }
+
+ @NotNull
+ @Override
+ public Klass klass() {
+ return mKlass;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof Dynamic)) {
+ return false;
+ }
+ Dynamic d = (Dynamic)obj;
+ return type().equals(d.type()) && Arrays.equals(mFields, d.mFields);
+ }
+
+ @Override
+ public int hashCode() {
+ return mKlass.hashCode() + 31 * Arrays.hashCode(mFields);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append(mKlass.entity().getName()).append('{');
+ Field[] fields = mKlass.entity().getFields();
+ for (int i = 0; i < mFields.length; i++) {
+ if (i > 0) {
+ result.append(", ");
+ }
+ result.append(fields[i].getName()).append(": ").append(mFields[i]);
+ }
+ result.append('}');
+ return result.toString();
+ }
+
+ public static class Klass implements BinaryClass {
+
+ private Entity mType;
+
+ Klass(Entity type) {
+ mType = type;
+ }
+
+ @NotNull
+ @Override
+ public Entity entity() {
+ return mType;
+ }
+
+ @Override
+ @NotNull
+ public BinaryObject create() {
+ return new Dynamic(this);
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ Dynamic o = (Dynamic) obj;
+ assert (o.mKlass == this);
+ for (int i = 0; i < mType.getFields().length; i++) {
+ Field field = mType.getFields()[i];
+ Object value = o.mFields[i];
+ field.getType().encodeValue(e, value);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Dynamic o = (Dynamic) obj;
+ o.mFields = new Object[mType.getFields().length];
+ for (int i = 0; i < mType.getFields().length; i++) {
+ Field field = mType.getFields()[i];
+ o.mFields[i] = field.getType().decodeValue(d);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof Klass)) {
+ return false;
+ }
+ return entity().equals(((Klass)obj).entity());
+ }
+
+ @Override
+ public int hashCode() {
+ return mType.hashCode();
+ }
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Entity.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Entity.java
new file mode 100644
index 0000000..f054211
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Entity.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.EncodingControl;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Entity {
+ private String mPackage;
+ private String mIdentity;
+ private String mVersion;
+ private String mDisplay;
+ private Field[] mFields;
+ private BinaryObject[] mMetadata;
+ private String mSignature;
+
+ public Entity(String pkg, String identity, String version, String display) {
+ mPackage = pkg;
+ mIdentity = identity;
+ mVersion = version;
+ mDisplay = display;
+ mFields = new Field[]{};
+ }
+
+ public Entity() {}
+
+ public void decode(@NotNull Decoder d) throws IOException {
+ mPackage = d.string();
+ mIdentity = d.string();
+ mVersion = d.string();
+ mDisplay = d.nonCompactString();
+ mFields = new Field[d.uint32()];
+ for (int i = 0; i < mFields.length; i++) {
+ mFields[i] = new Field();
+ mFields[i].mType = Type.decode(d);
+ mFields[i].mDeclared = d.nonCompactString();
+ }
+ if (d.getMode() != EncodingControl.Compact) {
+ mMetadata = new BinaryObject[d.uint32()];
+ for (int i = 0; i < mMetadata.length; i++) {
+ mMetadata[i] = d.object();
+ }
+ }
+ }
+
+ public String getPackage() {
+ return mPackage;
+ }
+
+ public String getName() {
+ return mDisplay.isEmpty() ? mIdentity : mDisplay;
+ }
+
+ public Field[] getFields() {
+ return mFields;
+ }
+ public void setFields(Field[] fields) {
+ mFields = fields;
+ }
+
+ public BinaryObject[] getMetadata() {
+ return mMetadata;
+ }
+
+ public void encode(@NotNull Encoder e) throws IOException {
+ e.string(mPackage);
+ e.string(mIdentity);
+ e.string(mVersion);
+ e.nonCompactString(mDisplay);
+ e.uint32(mFields.length);
+ for (Field field : mFields) {
+ field.mType.encode(e);
+ e.nonCompactString(field.mDeclared);
+ }
+ if (e.getMode() != EncodingControl.Compact) {
+ e.uint32(mMetadata.length);
+ for (BinaryObject meta : mMetadata) {
+ e.object(meta);
+ }
+ }
+ }
+
+ public String signature() {
+ if (mSignature == null) {
+ StringBuilder out = new StringBuilder();
+ out.append(mPackage).append('.').append(mIdentity);
+ if (mVersion.length() > 0) {
+ out.append('@').append(mVersion);
+ }
+ out.append('{');
+ for (int index = 0; index < mFields.length; ++index) {
+ if (index > 0) {
+ out.append(',');
+ }
+ mFields[index].getType().signature(out);
+ }
+ out.append('}');
+ mSignature = out.toString();
+ }
+ return mSignature;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof Entity)) {
+ return false;
+ }
+ return signature().equals(((Entity)obj).signature());
+ }
+
+ @Override
+ public int hashCode() {
+ return signature().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return signature();
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Factory.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Factory.java
new file mode 100644
index 0000000..447f533
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Factory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+public final class Factory {
+
+ public static void register() {
+ Message.register();
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Field.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Field.java
new file mode 100644
index 0000000..3d58eb0
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Field.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+public final class Field {
+
+ String mDeclared;
+
+ Type mType;
+
+ public Field(String name, Type type) {
+ mDeclared = name;
+ mType = type;
+ }
+
+ public Field() {
+ }
+
+ public String getDeclared() {
+ return mDeclared;
+ }
+
+ public Type getType() {
+ return mType;
+ }
+
+ public String getName() {
+ if (mDeclared.length() == 0) {
+ return mType.getName();
+ }
+ return mDeclared;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Field field = (Field)o;
+
+ if (!mDeclared.equals(field.mDeclared)) return false;
+ // This is the best we can do at the moment. Type.equals() is
+ // buggy because it doesn't know the package or relative types.
+ if (!mType.getClass().equals(field.mType.getClass())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mDeclared.hashCode();
+ result = 31 * result + mType.getClass().hashCode();
+ return result;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Interface.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Interface.java
new file mode 100644
index 0000000..4732ef0
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Interface.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Interface extends Type {
+ public final String name;
+
+ public Interface(String name) {
+ this.name = name;
+ }
+
+ public Interface(@NotNull Decoder d) throws IOException {
+ this.name = d.nonCompactString();
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ assert (value instanceof BinaryObject);
+ e.object((BinaryObject) value);
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ return d.object();
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.InterfaceTag.encode(e);
+ e.nonCompactString(name);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append(name);
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append('?');
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Map.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Map.java
new file mode 100644
index 0000000..dd8e39b
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Map.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+
+public final class Map extends Type {
+ String mAlias;
+
+ Type mKeyType;
+
+ Type mValueType;
+
+ public Map(String alias, Type keyType, Type valueType) {
+ mAlias = alias;
+ mKeyType = keyType;
+ mValueType = valueType;
+ }
+
+ public Map(@NotNull Decoder d) throws IOException {
+ mKeyType = decode(d);
+ mValueType = decode(d);
+ mAlias = d.nonCompactString();
+ }
+
+ public String getAlias() {
+ return mAlias;
+ }
+
+ public Type getKeyType() {
+ return mKeyType;
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ assert (value instanceof java.util.Map);
+ java.util.Map<?, ?> map = (java.util.Map<?, ?>) value;
+ e.uint32(map.size());
+ for (java.util.Map.Entry<?, ?> entry : map.entrySet()) {
+ mKeyType.encodeValue(e, entry.getKey());
+ mValueType.encodeValue(e, entry.getValue());
+ }
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ int size = d.uint32();
+ LinkedHashMap<Object, Object> map = new LinkedHashMap<Object, Object>();
+ for (int i = 0; i < size; i++) {
+ map.put(mKeyType.decodeValue(d), mValueType.decodeValue(d));
+ }
+ return map;
+ }
+
+ public Type getValueType() {
+ return mValueType;
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.MapTag.encode(e);
+ mKeyType.encode(e);
+ mValueType.encode(e);
+ e.nonCompactString(mAlias);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append("map<");
+ mKeyType.name(out);
+ out.append(',');
+ mValueType.name(out);
+ out.append('>');
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append("map[");
+ mKeyType.signature(out);
+ out.append(']');
+ mValueType.signature(out);
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Message.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Message.java
new file mode 100644
index 0000000..c5e3b68
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Message.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+/**
+ * This class is used to transmit schema data to a client.
+ * It is not a normal binary object because it requires custom encoding for the Entity objects.
+ */
+public final class Message implements BinaryObject {
+ public Entity[] entities;
+ public ConstantSet[] constants;
+
+ @Override
+ @NotNull
+ public BinaryClass klass() {
+ return Klass.INSTANCE;
+ }
+
+ static {
+ Namespace.register(Klass.INSTANCE);
+ }
+
+ public static void register() {
+ }
+
+ public enum Klass implements BinaryClass {
+ INSTANCE;
+
+ private static final Entity ENTITY = new Entity("schema","Message","","");
+
+ @Override
+ @NotNull
+ public Entity entity() {
+ return ENTITY;
+ }
+
+ @Override
+ @NotNull
+ public BinaryObject create() {
+ return new Message();
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ int oldMode = e.setMode(EncodingControl.Full);
+ try {
+ Message o = (Message)obj;
+ e.uint32(o.entities.length);
+ for (Entity entity : o.entities) {
+ e.entity(entity);
+ }
+ e.uint32(o.constants.length);
+ for (ConstantSet set : o.constants) {
+ set.encode(e);
+ }
+ } finally {
+ e.setMode(oldMode);
+ }
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ Message o = (Message) obj;
+ o.entities = new Entity[d.uint32()];
+ for (int i = 0; i < o.entities.length; i++) {
+ o.entities[i] = d.entity();
+ }
+ o.constants = new ConstantSet[d.uint32()];
+ for (int i = 0; i < o.constants.length; i++) {
+ o.constants[i] = new ConstantSet(d);
+ }
+ }
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Method.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Method.java
new file mode 100644
index 0000000..e0b5c7e
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Method.java
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.google.common.collect.ImmutableMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Method {
+ public static final Method Bool = new Method((byte)0, "Bool");
+ public static final byte BoolValue = 0;
+ public static final Method Int8 = new Method((byte)1, "Int8");
+ public static final byte Int8Value = 1;
+ public static final Method Uint8 = new Method((byte)2, "Uint8");
+ public static final byte Uint8Value = 2;
+ public static final Method Int16 = new Method((byte)3, "Int16");
+ public static final byte Int16Value = 3;
+ public static final Method Uint16 = new Method((byte)4, "Uint16");
+ public static final byte Uint16Value = 4;
+ public static final Method Int32 = new Method((byte)5, "Int32");
+ public static final byte Int32Value = 5;
+ public static final Method Uint32 = new Method((byte)6, "Uint32");
+ public static final byte Uint32Value = 6;
+ public static final Method Int64 = new Method((byte)7, "Int64");
+ public static final byte Int64Value = 7;
+ public static final Method Uint64 = new Method((byte)8, "Uint64");
+ public static final byte Uint64Value = 8;
+ public static final Method Float32 = new Method((byte)9, "Float32");
+ public static final byte Float32Value = 9;
+ public static final Method Float64 = new Method((byte)10, "Float64");
+ public static final byte Float64Value = 10;
+ public static final Method String = new Method((byte)11, "String");
+ public static final byte StringValue = 11;
+
+ private static final ImmutableMap<Byte, Method> VALUES = ImmutableMap.<Byte, Method>builder()
+ .put((byte)0, Bool)
+ .put((byte)1, Int8)
+ .put((byte)2, Uint8)
+ .put((byte)3, Int16)
+ .put((byte)4, Uint16)
+ .put((byte)5, Int32)
+ .put((byte)6, Uint32)
+ .put((byte)7, Int64)
+ .put((byte)8, Uint64)
+ .put((byte)9, Float32)
+ .put((byte)10, Float64)
+ .put((byte)11, String)
+ .build();
+
+ private final byte mValue;
+ private final String mName;
+
+ private Method(byte v, String n) {
+ mValue = v;
+ mName = n;
+ }
+
+ public byte getValue() {
+ return mValue;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void encode(@NotNull Encoder e) throws IOException {
+ e.uint8(mValue);
+ }
+
+ public static Method decode(@NotNull Decoder d) throws IOException {
+ return findOrCreate(d.uint8());
+ }
+
+ public static Method find(byte value) {
+ return VALUES.get(value);
+ }
+
+ public static Method findOrCreate(byte value) {
+ Method result = VALUES.get(value);
+ return (result == null) ? new Method(value, null) : result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof Method)) return false;
+ return mValue == ((Method)o).mValue;
+ }
+
+ @Override
+ public int hashCode() {
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return (mName == null) ? "Method(" + mValue + ")" : mName;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Pointer.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Pointer.java
new file mode 100644
index 0000000..16c4b43
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Pointer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Pointer extends Type {
+ Type mType;
+
+ public Pointer(Type type) {
+ mType = type;
+ }
+
+ public Pointer(@NotNull Decoder d) throws IOException {
+ mType = decode(d);
+ }
+
+ public Type getType() {
+ return mType;
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ assert (value instanceof BinaryObject);
+ e.object((BinaryObject) value);
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ return d.object();
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.PointerTag.encode(e);
+ mType.encode(e);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append("pointer<");
+ mType.name(out);
+ out.append('>');
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append('*');
+ mType.signature(out);
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Primitive.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Primitive.java
new file mode 100644
index 0000000..8a267d2
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Primitive.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Primitive extends Type {
+
+ String mName;
+
+ Method mMethod;
+
+ public Primitive(String name, Method method) {
+ mName = name;
+ mMethod = method;
+ }
+
+ public Primitive(@NotNull Decoder d, Method method) throws IOException {
+ mMethod = method;
+ mName = d.nonCompactString();
+ }
+
+ @Override
+ public String toString() {
+ return mName + "(" + mMethod + ")";
+ }
+
+ public Method getMethod() {
+ return mMethod;
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ switch (mMethod.getValue()) {
+ case Method.BoolValue:
+ e.bool((Boolean) value);
+ break;
+ case Method.Int8Value:
+ e.int8(((Number)value).byteValue());
+ break;
+ case Method.Uint8Value:
+ e.uint8(((Number)value).shortValue());
+ break;
+ case Method.Int16Value:
+ e.int16(((Number)value).shortValue());
+ break;
+ case Method.Uint16Value:
+ e.uint16(((Number)value).intValue());
+ break;
+ case Method.Int32Value:
+ e.int32(((Number)value).intValue());
+ break;
+ case Method.Uint32Value:
+ e.uint32(((Number)value).longValue());
+ break;
+ case Method.Int64Value:
+ e.int64(((Number)value).longValue());
+ break;
+ case Method.Uint64Value:
+ e.uint64(((Number)value).longValue());
+ break;
+ case Method.Float32Value:
+ e.float32(((Number)value).floatValue());
+ break;
+ case Method.Float64Value:
+ e.float64(((Number)value).doubleValue());
+ break;
+ case Method.StringValue:
+ e.string((value == null) ? null : value.toString());
+ break;
+ default:
+ throw new IOException("Invalid primitive method in encode");
+ }
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ switch (mMethod.getValue()) {
+ case Method.BoolValue:
+ return d.bool();
+ case Method.Int8Value:
+ return d.int8();
+ case Method.Uint8Value:
+ return d.uint8();
+ case Method.Int16Value:
+ return d.int16();
+ case Method.Uint16Value:
+ return d.uint16();
+ case Method.Int32Value:
+ return d.int32();
+ case Method.Uint32Value:
+ return d.uint32();
+ case Method.Int64Value:
+ return d.int64();
+ case Method.Uint64Value:
+ return d.uint64();
+ case Method.Float32Value:
+ return d.float32();
+ case Method.Float64Value:
+ return d.float64();
+ case Method.StringValue:
+ return d.string();
+ default:
+ throw new IOException("Invalid primitive method in decode");
+ }
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ //noinspection PointlessBitwiseExpression
+ e.uint8((short)(TypeTag.PrimitiveTagValue | ( mMethod.getValue() << 4)));
+ e.nonCompactString(mName);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append(mName);
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append(mMethod);
+ }
+
+ /**
+ * @return true if ty is a primitive of base type method.
+ */
+ public static boolean isMethod(Type ty, Method method) {
+ if (!(ty instanceof Primitive)) {
+ return false;
+ }
+ return ((Primitive)(ty)).mMethod.equals(method);
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Slice.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Slice.java
new file mode 100644
index 0000000..7f37184
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Slice.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Slice extends Type {
+ String mAlias;
+
+ Type mValueType;
+
+ public Slice(@NotNull Decoder d) throws IOException {
+ mValueType = Type.decode(d);
+ mAlias = d.nonCompactString();
+ }
+
+ public Slice(String alias, Type valueType) {
+ mAlias = alias;
+ mValueType = valueType;
+ }
+
+ public String getAlias() {
+ return mAlias;
+ }
+
+ public Type getValueType() {
+ return mValueType;
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ if (Primitive.isMethod(mValueType, Method.Uint8)) {
+ assert (value instanceof byte[]);
+ byte[] array = (byte[])value;
+ e.uint32(array.length);
+ e.write(array, array.length);
+ } else {
+ assert (value instanceof Object[]);
+ Object[] array = (Object[])value;
+ e.uint32(array.length);
+ for (Object v : array) {
+ mValueType.encodeValue(e, v);
+ }
+ }
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ if (Primitive.isMethod(mValueType, Method.Uint8)) {
+ byte[] array = new byte[d.uint32()];
+ d.read(array, array.length);
+ return array;
+ } else {
+ Object[] array = new Object[d.uint32()];
+ for (int i = 0; i < array.length; i++) {
+ array[i] = mValueType.decodeValue(d);
+ }
+ return array;
+ }
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.SliceTag.encode(e);
+ mValueType.encode(e);
+ e.nonCompactString(mAlias);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append("slice<");
+ mValueType.name(out);
+ out.append('>');
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append("[]");
+ mValueType.signature(out);
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Struct.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Struct.java
new file mode 100644
index 0000000..882d85c
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Struct.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.*;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Struct extends Type {
+ Entity mEntity;
+
+ public Struct(Entity entity) {
+ mEntity = entity;
+ }
+
+ public Struct(@NotNull Decoder d) throws IOException {
+ mEntity = d.entity();
+ d.nonCompactString();
+ }
+
+ public Entity getEntity() {
+ return mEntity;
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ assert (value instanceof BinaryObject);
+ e.value((BinaryObject) value);
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ BinaryClass klass = Namespace.lookup(mEntity);
+ if (klass == null) {
+ throw new IOException("Unknown type: " + mEntity);
+ }
+ BinaryObject obj = klass.create();
+ klass.decode(d, obj);
+ return obj;
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.StructTag.encode(e);
+ e.entity(mEntity);
+ e.nonCompactString("");
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append(mEntity.getName());
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append('$');
+ }
+
+
+ public boolean is(BinaryClass klass) {
+ return mEntity.signature().equals(klass.entity().signature());
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Type.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Type.java
new file mode 100644
index 0000000..94c65b2
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Type.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import com.google.common.collect.ImmutableMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public abstract class Type {
+ private String mName = null;
+
+ @NotNull
+ public final String getName() {
+ if (mName == null) {
+ StringBuilder out = new StringBuilder();
+ name(out);
+ mName = out.toString();
+ }
+ return mName;
+ }
+
+ public abstract void encodeValue(@NotNull Encoder e, Object value) throws IOException;
+
+ public abstract Object decodeValue(@NotNull Decoder d) throws IOException;
+
+ public abstract void encode(@NotNull Encoder e) throws IOException;
+
+ public static Type decode(@NotNull Decoder d) throws IOException {
+ byte v = d.uint8();
+ switch (v & 0xf) {
+ case TypeTag.PrimitiveTagValue:
+ return new Primitive(d, Method.find((byte)((v >> 4) & 0xf)));
+ case TypeTag.StructTagValue:
+ return new Struct(d);
+ case TypeTag.PointerTagValue:
+ return new Pointer(d);
+ case TypeTag.InterfaceTagValue:
+ return new Interface(d);
+ case TypeTag.VariantTagValue:
+ return new Variant(d);
+ case TypeTag.AnyTagValue:
+ return new AnyType(d);
+ case TypeTag.SliceTagValue:
+ return new Slice(d);
+ case TypeTag.ArrayTagValue:
+ return new Array(d);
+ case TypeTag.MapTagValue:
+ return new Map(d);
+ default:
+ throw new IOException("Decode unknown type " + v);
+ }
+ }
+
+ abstract void name(StringBuilder out);
+
+ public abstract void signature(StringBuilder out);
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Type)) return false;
+ return (getName().equals(((Type)o).getName()));
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+}
\ No newline at end of file
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/TypeTag.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/TypeTag.java
new file mode 100644
index 0000000..4d07441
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/TypeTag.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.google.common.collect.ImmutableMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class TypeTag {
+ public static final TypeTag PrimitiveTag = new TypeTag((byte)0, "PrimitiveTag");
+ public static final byte PrimitiveTagValue = 0;
+ public static final TypeTag StructTag = new TypeTag((byte)1, "StructTag");
+ public static final byte StructTagValue = 1;
+ public static final TypeTag PointerTag = new TypeTag((byte)2, "PointerTag");
+ public static final byte PointerTagValue = 2;
+ public static final TypeTag InterfaceTag = new TypeTag((byte)3, "InterfaceTag");
+ public static final byte InterfaceTagValue = 3;
+ public static final TypeTag VariantTag = new TypeTag((byte)4, "VariantTag");
+ public static final byte VariantTagValue = 4;
+ public static final TypeTag AnyTag = new TypeTag((byte)5, "AnyTag");
+ public static final byte AnyTagValue = 5;
+ public static final TypeTag SliceTag = new TypeTag((byte)6, "SliceTag");
+ public static final byte SliceTagValue = 6;
+ public static final TypeTag ArrayTag = new TypeTag((byte)7, "ArrayTag");
+ public static final byte ArrayTagValue = 7;
+ public static final TypeTag MapTag = new TypeTag((byte)8, "MapTag");
+ public static final byte MapTagValue = 8;
+
+ private static final ImmutableMap<Byte, TypeTag> VALUES = ImmutableMap.<Byte, TypeTag>builder()
+ .put((byte)0, PrimitiveTag)
+ .put((byte)1, StructTag)
+ .put((byte)2, PointerTag)
+ .put((byte)3, InterfaceTag)
+ .put((byte)4, VariantTag)
+ .put((byte)5, AnyTag)
+ .put((byte)6, SliceTag)
+ .put((byte)7, ArrayTag)
+ .put((byte)8, MapTag)
+ .build();
+
+ private final byte mValue;
+ private final String mName;
+
+ private TypeTag(byte v, String n) {
+ mValue = v;
+ mName = n;
+ }
+
+ public byte getValue() {
+ return mValue;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void encode(@NotNull Encoder e) throws IOException {
+ e.uint8(mValue);
+ }
+
+ public static TypeTag decode(@NotNull Decoder d) throws IOException {
+ return findOrCreate(d.uint8());
+ }
+
+ public static TypeTag find(byte value) {
+ return VALUES.get(value);
+ }
+
+ public static TypeTag findOrCreate(byte value) {
+ TypeTag result = VALUES.get(value);
+ return (result == null) ? new TypeTag(value, null) : result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof TypeTag)) return false;
+ return mValue == ((TypeTag)o).mValue;
+ }
+
+ @Override
+ public int hashCode() {
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return (mName == null) ? "TypeTag(" + mValue + ")" : mName;
+ }
+}
diff --git a/rpclib/src/main/java/com/android/tools/rpclib/schema/Variant.java b/rpclib/src/main/java/com/android/tools/rpclib/schema/Variant.java
new file mode 100644
index 0000000..0a08eba
--- /dev/null
+++ b/rpclib/src/main/java/com/android/tools/rpclib/schema/Variant.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.schema;
+
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public final class Variant extends Type {
+ String mName;
+
+ public Variant(String name) {
+ mName = name;
+ }
+
+ public Variant(@NotNull Decoder d) throws IOException {
+ mName = d.nonCompactString();
+ }
+
+ @Override
+ public void encodeValue(@NotNull Encoder e, Object value) throws IOException {
+ assert (value instanceof BinaryObject);
+ e.object((BinaryObject) value);
+ }
+
+ @Override
+ public Object decodeValue(@NotNull Decoder d) throws IOException {
+ return d.object();
+ }
+
+ @Override
+ public void encode(@NotNull Encoder e) throws IOException {
+ TypeTag.VariantTag.encode(e);
+ e.nonCompactString(mName);
+ }
+
+ @Override
+ void name(StringBuilder out) {
+ out.append(mName);
+ }
+
+ @Override
+ public void signature(StringBuilder out) {
+ out.append('&');
+ }
+}
diff --git a/rpclib/src/test/java/com/android/tools/rpclib/binary/DecoderTest.java b/rpclib/src/test/java/com/android/tools/rpclib/binary/DecoderTest.java
new file mode 100644
index 0000000..215d9dd
--- /dev/null
+++ b/rpclib/src/test/java/com/android/tools/rpclib/binary/DecoderTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import junit.framework.TestCase;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class DecoderTest extends TestCase {
+ public void testDecodeBool() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x01, (byte)0x00
+ });
+ final boolean[] expected = new boolean[]{true, false};
+
+ Decoder d = new Decoder(input);
+ for (boolean bool : expected) {
+ assertEquals(bool, d.bool());
+ }
+ }
+
+ public void testDecodeInt8() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff
+ });
+ final byte[] expected = new byte[]{0, 127, -128, -1};
+
+ Decoder d = new Decoder(input);
+ for (short s8 : expected) {
+ assertEquals(s8, d.int8());
+ }
+ }
+
+ public void testDecodeUint8() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff
+ });
+ final short[] expected = new short[]{0x00, 0x7f, 0x80, 0xff};
+
+ Decoder d = new Decoder(input);
+ for (short u8 : expected) {
+ assertEquals(u8, d.uint8() & 0xff);
+ }
+ }
+
+ public void testDecodeInt16() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0xff, (byte)0xfe,
+ (byte)0xc0, (byte)0xff, (byte)0xff,
+ (byte)0x01,
+ });
+ final short[] expected = new short[]{0, 32767, -32768, -1};
+
+ Decoder d = new Decoder(input);
+ for (short s16 : expected) {
+ assertEquals(s16, d.int16());
+ }
+ }
+
+ public void testDecodeUint16() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0xbe, (byte)0xef,
+ (byte)0xc0, (byte)0xc0, (byte)0xde
+ });
+ final int[] expected = new int[]{0, 0xbeef, 0xc0de};
+
+ Decoder d = new Decoder(input);
+ for (int u16 : expected) {
+ assertEquals(u16, d.uint16() & 0xffff);
+ }
+ }
+
+ public void testDecodeInt32() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
+ (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
+ (byte)0x01
+ });
+ final int[] expected = new int[]{0, 2147483647, -2147483648, -1};
+
+ Decoder d = new Decoder(input);
+ for (int s32 : expected) {
+ assertEquals(s32, d.int32());
+ }
+ }
+
+ public void testDecodeUint32() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xe1, (byte)0x23, (byte)0x45, (byte)0x67,
+ (byte)0xf0, (byte)0x10, (byte)0xab, (byte)0xcd, (byte)0xef
+ });
+ final long[] expected = new long[]{0, 0x01234567, 0x10abcdef};
+
+ Decoder d = new Decoder(input);
+ for (long u32 : expected) {
+ assertEquals(u32, d.uint32() & 0xffffffff);
+ }
+ }
+
+ public void testDecodeInt64() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
+ (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,(byte) 0xff,
+ (byte)0x01
+ });
+ final long[] expected = new long[]{0L, 9223372036854775807L, -9223372036854775808L, -1L};
+
+ Decoder d = new Decoder(input);
+ for (long s64 : expected) {
+ assertEquals(s64, d.int64());
+ }
+ }
+
+ public void testDecodeUint64() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xff, (byte)0x01, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef,
+ (byte)0xff, (byte)0xfe, (byte)0xdc, (byte)0xba, (byte)0x98, (byte)0x76, (byte)0x54, (byte)0x32, (byte)0x10
+ });
+ final long[] expected = new long[]{0L, 0x0123456789abcdefL, 0xfedcba9876543210L};
+
+ Decoder d = new Decoder(input);
+ for (long u64 : expected) {
+ assertEquals(u64, d.uint64());
+ }
+ }
+
+ public void testDecodeFloat32() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0x80, (byte)0x3f,
+ (byte)0xc0,(byte) 0x81, (byte)0x42,
+ });
+ final float[] expected = new float[]{0.F, 1.F, 64.5F};
+
+ Decoder d = new Decoder(input);
+ for (float f32 : expected) {
+ assertEquals(f32, d.float32());
+ }
+ }
+
+ public void testDecodeFloat64() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0xf0, (byte)0x3f,
+ (byte)0xe0, (byte)0x20, (byte)0x50, (byte)0x40,
+ });
+ final double[] expected = new double[]{0.D, 1.D, 64.5D};
+
+ Decoder d = new Decoder(input);
+ for (double f64 : expected) {
+ assertEquals(f64, d.float64());
+ }
+ }
+
+ public void testDecodeString() throws IOException {
+ final ByteArrayInputStream input = new ByteArrayInputStream(new byte[]{
+ 0x05, 'H', 'e', 'l', 'l', 'o',
+ 0x00, // empty string
+ 0x05, 'W', 'o', 'r', 'l', 'd',
+
+ 0x15,
+ (byte)0xe3, (byte)0x81, (byte)0x93, (byte)0xe3, (byte)0x82, (byte)0x93, (byte)0xe3,
+ (byte)0x81, (byte)0xab, (byte)0xe3, (byte)0x81, (byte)0xa1, (byte)0xe3, (byte)0x81,
+ (byte)0xaf, (byte)0xe4, (byte)0xb8, (byte)0x96, (byte)0xe7, (byte)0x95, (byte)0x8c
+ });
+ final String[] expected = new String[]{"Hello", "", "World", "こんにちは世界"};
+
+ Decoder d = new Decoder(input);
+
+ for (String str : expected) {
+ assertEquals(str, d.string());
+ }
+ }
+
+ public void testDecodeObject() throws IOException {
+ final TypeA dummyObject = new TypeA();
+ dummyObject.setData("dummy");
+
+ ByteArrayOutputStream inputBytes = new ByteArrayOutputStream();
+
+ // null BinaryObject:
+ inputBytes.write(new byte[]{(byte)0x00}); // BinaryObject.NULL_ID
+
+ // stubObject:
+ inputBytes.write(new byte[]{0x03}); // object sid + encoded
+ inputBytes.write(new byte[]{0x03}); // type sid + encoded
+ inputBytes.write(new byte[]{0x04, 't', 'e', 's', 't'}); // package
+ inputBytes.write(new byte[]{0x05, 'T', 'y', 'p', 'e', 'A'}); // identity
+ inputBytes.write(new byte[]{0x00, 0x01, (byte)0xb0}); // version, fieldcount, string
+
+ inputBytes.write(new byte[]{0x05, 'd', 'u', 'm', 'm', 'y'}); // payload
+
+ // stubObject again, only by reference this time:
+ inputBytes.write(new byte[]{0x02}); // repeated object sid
+
+ final ByteArrayInputStream input = new ByteArrayInputStream(inputBytes.toByteArray());
+ final BinaryObject[] expected = new BinaryObject[]{null, dummyObject, dummyObject};
+
+ Decoder d = new Decoder(input);
+ for (BinaryObject obj : expected) {
+ BinaryObject o = d.object();
+ if (obj == null) {
+ assertEquals(obj, o);
+ } else {
+ assertTrue(o instanceof TypeA);
+ assertEquals(((TypeA)obj).getData(), ((TypeA)o).getData());
+ }
+ }
+ }
+}
diff --git a/rpclib/src/test/java/com/android/tools/rpclib/binary/EncoderTest.java b/rpclib/src/test/java/com/android/tools/rpclib/binary/EncoderTest.java
new file mode 100644
index 0000000..8d061f5
--- /dev/null
+++ b/rpclib/src/test/java/com/android/tools/rpclib/binary/EncoderTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import junit.framework.TestCase;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class EncoderTest extends TestCase {
+ public void testEncodeBool() throws IOException {
+ final boolean[] input = new boolean[]{true, false};
+ final byte[] expected = new byte[]{(byte)0x01, (byte)0x00};
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (boolean bool : input) {
+ e.bool(bool);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeInt8() throws IOException {
+ final byte[] input = new byte[]{0, 127, -128, -1};
+ final byte[] expected = new byte[]{(byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff};
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (byte s8 : input) {
+ e.int8(s8);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeUint8() throws IOException {
+ final short[] input = new short[]{0x00, 0x7f, 0x80, 0xff};
+ final byte[] expected = new byte[]{(byte)0x00, (byte)0x7f, (byte)0x80, (byte)0xff};
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (short u8 : input) {
+ e.uint8(u8);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeInt16() throws IOException {
+ final short[] input = new short[]{0, 32767, -32768, -1};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0xff, (byte)0xfe,
+ (byte)0xc0, (byte)0xff, (byte)0xff,
+ (byte)0x01,
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (short s16 : input) {
+ e.int16(s16);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeUint16() throws IOException {
+ final int[] input = new int[]{0, 0xbeef, 0xc0de};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0xbe, (byte)0xef,
+ (byte)0xc0, (byte)0xc0, (byte)0xde
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (int u16 : input) {
+ e.uint16(u16);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeInt32() throws IOException {
+ final int[] input = new int[]{0, 2147483647, -2147483648, -1};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
+ (byte)0xf0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
+ (byte)0x01
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (int s32 : input) {
+ e.int32(s32);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeUint32() throws IOException {
+ final long[] input = new long[]{0, 0x01234567, 0x10abcdef};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xe1, (byte)0x23, (byte)0x45, (byte)0x67,
+ (byte)0xf0, (byte)0x10, (byte)0xab, (byte)0xcd, (byte)0xef
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (long u32 : input) {
+ e.uint32(u32);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeInt64() throws IOException {
+ final long[] input = new long[]{0L, 9223372036854775807L, -9223372036854775808L, -1L};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe,
+ (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,(byte) 0xff,
+ (byte)0x01
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (long s64 : input) {
+ e.int64(s64);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeUint64() throws IOException {
+ final long[] input = new long[]{0L, 0x0123456789abcdefL, 0xfedcba9876543210L};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xff, (byte)0x01, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef,
+ (byte)0xff, (byte)0xfe, (byte)0xdc, (byte)0xba, (byte)0x98, (byte)0x76, (byte)0x54, (byte)0x32, (byte)0x10
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (long u64 : input) {
+ e.uint64(u64);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeFloat32() throws IOException {
+ final float[] input = new float[]{0.F, 1.F, 64.5F};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0x80, (byte)0x3f,
+ (byte)0xc0,(byte) 0x81, (byte)0x42,
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (float f32 : input) {
+ e.float32(f32);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeFloat64() throws IOException {
+ final double[] input = new double[]{0.D, 1.D, 64.5D};
+ final byte[] expected = new byte[]{
+ (byte)0x00,
+ (byte)0xc0, (byte)0xf0, (byte)0x3f,
+ (byte)0xe0, (byte)0x20, (byte)0x50, (byte)0x40,
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (double f64 : input) {
+ e.float64(f64);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeString() throws IOException {
+ final String[] input = new String[]{null, "Hello", "", "World", "こんにちは世界"};
+ final byte[] expected = new byte[]{
+ 0x00, // null string
+ 0x05, 'H', 'e', 'l', 'l', 'o',
+ 0x00, // empty string
+ 0x05, 'W', 'o', 'r', 'l', 'd',
+
+ 0x15,
+ (byte)0xe3, (byte)0x81, (byte)0x93, (byte)0xe3, (byte)0x82, (byte)0x93, (byte)0xe3,
+ (byte)0x81, (byte)0xab, (byte)0xe3, (byte)0x81, (byte)0xa1, (byte)0xe3, (byte)0x81,
+ (byte)0xaf, (byte)0xe4, (byte)0xb8, (byte)0x96, (byte)0xe7, (byte)0x95, (byte)0x8c
+ };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (String str : input) {
+ e.string(str);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+
+ public void testEncodeObject() throws IOException {
+ final TypeA dummyObject = new TypeA();
+ dummyObject.setData("dummy");
+ final BinaryObject[] input = new BinaryObject[]{null, dummyObject, dummyObject};
+ byte[] expected = null;
+
+ ByteArrayOutputStream expectedStream = new ByteArrayOutputStream();
+
+ // null BinaryObject:
+ expectedStream.write(new byte[]{(byte)0x00}); // BinaryObject.NULL_ID
+
+ // dummyObject:
+ expectedStream.write(new byte[]{0x03}); // object sid + encoded
+ expectedStream.write(new byte[]{0x03}); // type sid + encoded
+ expectedStream.write(new byte[]{0x04, 't', 'e', 's', 't'}); // package
+ expectedStream.write(new byte[]{0x05, 'T', 'y', 'p', 'e', 'A'}); // identity
+ expectedStream.write(new byte[]{0x00, 0x01, (byte)0xb0}); // version, fieldcount, string
+
+ expectedStream.write(new byte[]{0x05, 'd', 'u', 'm', 'm', 'y'}); // payload
+
+ // dummyObject again, only by reference this time:
+ expectedStream.write(new byte[]{0x02}); // repeated object sid
+
+ expected = expectedStream.toByteArray();
+ ByteArrayOutputStream output = new ByteArrayOutputStream(expected.length);
+ Encoder e = new Encoder(output);
+
+ for (BinaryObject obj : input) {
+ e.object(obj);
+ }
+ Assert.assertArrayEquals(expected, output.toByteArray());
+ }
+}
diff --git a/rpclib/src/test/java/com/android/tools/rpclib/binary/Factory.java b/rpclib/src/test/java/com/android/tools/rpclib/binary/Factory.java
new file mode 100644
index 0000000..3925981
--- /dev/null
+++ b/rpclib/src/test/java/com/android/tools/rpclib/binary/Factory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.binary;
+
+public final class Factory {
+ public static void register() {
+ //<<<Start:Java.FactoryBody:2>>>
+ TypeA.register();
+ TypeB.register();
+ TypeC.register();
+ //<<<End:Java.FactoryBody:2>>>
+ }
+}
diff --git a/rpclib/src/test/java/com/android/tools/rpclib/binary/Simple.java b/rpclib/src/test/java/com/android/tools/rpclib/binary/Simple.java
new file mode 100644
index 0000000..493c4eb
--- /dev/null
+++ b/rpclib/src/test/java/com/android/tools/rpclib/binary/Simple.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.rpclib.binary;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public class Simple {
+ public final int value;
+
+ public Simple(int value) {
+ this.value = value;
+ }
+
+ public void encode(@NotNull Encoder e) throws IOException {
+ e.uint32(value);
+ }
+
+ public static Simple decode(@NotNull Decoder d) throws IOException {
+ return new Simple(d.uint32());
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+}
diff --git a/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeA.java b/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeA.java
new file mode 100644
index 0000000..37ee905
--- /dev/null
+++ b/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeA.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.binary;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryID;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+public final class TypeA implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private String mData;
+
+ // Constructs a default-initialized {@link TypeA}.
+ public TypeA() {}
+
+
+ public String getData() {
+ return mData;
+ }
+
+ public TypeA setData(String v) {
+ mData = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("test", "TypeA", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Data", new Primitive("string", Method.String)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new TypeA(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ TypeA o = (TypeA)obj;
+ e.string(o.mData);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ TypeA o = (TypeA)obj;
+ o.mData = d.string();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeB.java b/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeB.java
new file mode 100644
index 0000000..4686513
--- /dev/null
+++ b/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeB.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.binary;
+
+import com.android.tools.rpclib.schema.*;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryID;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+public final class TypeB implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private String mData;
+
+ // Constructs a default-initialized {@link TypeB}.
+ public TypeB() {}
+
+
+ public String getData() {
+ return mData;
+ }
+
+ public TypeB setData(String v) {
+ mData = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("test", "TypeB", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Data", new Primitive("string", Method.String)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new TypeB(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ TypeB o = (TypeB)obj;
+ e.string(o.mData);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ TypeB o = (TypeB)obj;
+ o.mData = d.string();
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeC.java b/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeC.java
new file mode 100644
index 0000000..79cc1a4
--- /dev/null
+++ b/rpclib/src/test/java/com/android/tools/rpclib/binary/TypeC.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * THIS FILE WAS GENERATED BY codergen. EDIT WITH CARE.
+ */
+package com.android.tools.rpclib.binary;
+
+import com.android.tools.rpclib.schema.Entity;
+import com.android.tools.rpclib.schema.Field;
+import com.android.tools.rpclib.schema.Method;
+import com.android.tools.rpclib.schema.Primitive;
+import org.jetbrains.annotations.NotNull;
+
+import com.android.tools.rpclib.binary.BinaryClass;
+import com.android.tools.rpclib.binary.BinaryID;
+import com.android.tools.rpclib.binary.BinaryObject;
+import com.android.tools.rpclib.binary.Decoder;
+import com.android.tools.rpclib.binary.Encoder;
+import com.android.tools.rpclib.binary.Namespace;
+
+import java.io.IOException;
+
+public final class TypeC implements BinaryObject {
+ //<<<Start:Java.ClassBody:1>>>
+ private Simple mData;
+
+ // Constructs a default-initialized {@link TypeC}.
+ public TypeC() {}
+
+
+ public Simple getData() {
+ return mData;
+ }
+
+ public TypeC setData(Simple v) {
+ mData = v;
+ return this;
+ }
+
+ @Override @NotNull
+ public BinaryClass klass() { return Klass.INSTANCE; }
+
+
+ private static final Entity ENTITY = new Entity("test", "TypeC", "", "");
+
+ static {
+ ENTITY.setFields(new Field[]{
+ new Field("Data", new Primitive("Simple", Method.Int8)),
+ });
+ Namespace.register(Klass.INSTANCE);
+ }
+ public static void register() {}
+ //<<<End:Java.ClassBody:1>>>
+ public enum Klass implements BinaryClass {
+ //<<<Start:Java.KlassBody:2>>>
+ INSTANCE;
+
+ @Override @NotNull
+ public Entity entity() { return ENTITY; }
+
+ @Override @NotNull
+ public BinaryObject create() { return new TypeC(); }
+
+ @Override
+ public void encode(@NotNull Encoder e, BinaryObject obj) throws IOException {
+ TypeC o = (TypeC)obj;
+ o.mData.encode(e);
+ }
+
+ @Override
+ public void decode(@NotNull Decoder d, BinaryObject obj) throws IOException {
+ TypeC o = (TypeC)obj;
+ o.mData = Simple.decode(d);
+ }
+ //<<<End:Java.KlassBody:2>>>
+ }
+}
diff --git a/base/rule-api/.classpath b/rule-api/.classpath
similarity index 100%
rename from base/rule-api/.classpath
rename to rule-api/.classpath
diff --git a/base/rule-api/.gitignore b/rule-api/.gitignore
similarity index 100%
rename from base/rule-api/.gitignore
rename to rule-api/.gitignore
diff --git a/base/rule-api/.project b/rule-api/.project
similarity index 100%
rename from base/rule-api/.project
rename to rule-api/.project
diff --git a/base/misc/screenshot2/.settings/org.eclipse.jdt.core.prefs b/rule-api/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/misc/screenshot2/.settings/org.eclipse.jdt.core.prefs
rename to rule-api/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/build-system/manifest-merger/NOTICE b/rule-api/NOTICE
similarity index 100%
rename from base/build-system/manifest-merger/NOTICE
rename to rule-api/NOTICE
diff --git a/base/rule-api/README.txt b/rule-api/README.txt
similarity index 100%
rename from base/rule-api/README.txt
rename to rule-api/README.txt
diff --git a/base/rule-api/build.gradle b/rule-api/build.gradle
similarity index 100%
rename from base/rule-api/build.gradle
rename to rule-api/build.gradle
diff --git a/base/rule-api/rule-api.iml b/rule-api/rule-api.iml
similarity index 100%
rename from base/rule-api/rule-api.iml
rename to rule-api/rule-api.iml
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/AbstractViewRule.java b/rule-api/src/main/java/com/android/ide/common/api/AbstractViewRule.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/AbstractViewRule.java
rename to rule-api/src/main/java/com/android/ide/common/api/AbstractViewRule.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/DrawingStyle.java b/rule-api/src/main/java/com/android/ide/common/api/DrawingStyle.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/DrawingStyle.java
rename to rule-api/src/main/java/com/android/ide/common/api/DrawingStyle.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/DropFeedback.java b/rule-api/src/main/java/com/android/ide/common/api/DropFeedback.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/DropFeedback.java
rename to rule-api/src/main/java/com/android/ide/common/api/DropFeedback.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IAttributeInfo.java b/rule-api/src/main/java/com/android/ide/common/api/IAttributeInfo.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IAttributeInfo.java
rename to rule-api/src/main/java/com/android/ide/common/api/IAttributeInfo.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IClientRulesEngine.java b/rule-api/src/main/java/com/android/ide/common/api/IClientRulesEngine.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IClientRulesEngine.java
rename to rule-api/src/main/java/com/android/ide/common/api/IClientRulesEngine.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IColor.java b/rule-api/src/main/java/com/android/ide/common/api/IColor.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IColor.java
rename to rule-api/src/main/java/com/android/ide/common/api/IColor.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IDragElement.java b/rule-api/src/main/java/com/android/ide/common/api/IDragElement.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IDragElement.java
rename to rule-api/src/main/java/com/android/ide/common/api/IDragElement.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IFeedbackPainter.java b/rule-api/src/main/java/com/android/ide/common/api/IFeedbackPainter.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IFeedbackPainter.java
rename to rule-api/src/main/java/com/android/ide/common/api/IFeedbackPainter.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IGraphics.java b/rule-api/src/main/java/com/android/ide/common/api/IGraphics.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IGraphics.java
rename to rule-api/src/main/java/com/android/ide/common/api/IGraphics.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IMenuCallback.java b/rule-api/src/main/java/com/android/ide/common/api/IMenuCallback.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IMenuCallback.java
rename to rule-api/src/main/java/com/android/ide/common/api/IMenuCallback.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/INode.java b/rule-api/src/main/java/com/android/ide/common/api/INode.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/INode.java
rename to rule-api/src/main/java/com/android/ide/common/api/INode.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/INodeHandler.java b/rule-api/src/main/java/com/android/ide/common/api/INodeHandler.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/INodeHandler.java
rename to rule-api/src/main/java/com/android/ide/common/api/INodeHandler.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IValidator.java b/rule-api/src/main/java/com/android/ide/common/api/IValidator.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IValidator.java
rename to rule-api/src/main/java/com/android/ide/common/api/IValidator.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IViewMetadata.java b/rule-api/src/main/java/com/android/ide/common/api/IViewMetadata.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IViewMetadata.java
rename to rule-api/src/main/java/com/android/ide/common/api/IViewMetadata.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/IViewRule.java b/rule-api/src/main/java/com/android/ide/common/api/IViewRule.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/IViewRule.java
rename to rule-api/src/main/java/com/android/ide/common/api/IViewRule.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/InsertType.java b/rule-api/src/main/java/com/android/ide/common/api/InsertType.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/InsertType.java
rename to rule-api/src/main/java/com/android/ide/common/api/InsertType.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/MarginType.java b/rule-api/src/main/java/com/android/ide/common/api/MarginType.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/MarginType.java
rename to rule-api/src/main/java/com/android/ide/common/api/MarginType.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/Margins.java b/rule-api/src/main/java/com/android/ide/common/api/Margins.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/Margins.java
rename to rule-api/src/main/java/com/android/ide/common/api/Margins.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/Point.java b/rule-api/src/main/java/com/android/ide/common/api/Point.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/Point.java
rename to rule-api/src/main/java/com/android/ide/common/api/Point.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/Rect.java b/rule-api/src/main/java/com/android/ide/common/api/Rect.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/Rect.java
rename to rule-api/src/main/java/com/android/ide/common/api/Rect.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/ResizePolicy.java b/rule-api/src/main/java/com/android/ide/common/api/ResizePolicy.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/ResizePolicy.java
rename to rule-api/src/main/java/com/android/ide/common/api/ResizePolicy.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/RuleAction.java b/rule-api/src/main/java/com/android/ide/common/api/RuleAction.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/RuleAction.java
rename to rule-api/src/main/java/com/android/ide/common/api/RuleAction.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/Segment.java b/rule-api/src/main/java/com/android/ide/common/api/Segment.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/Segment.java
rename to rule-api/src/main/java/com/android/ide/common/api/Segment.java
diff --git a/base/rule-api/src/main/java/com/android/ide/common/api/SegmentType.java b/rule-api/src/main/java/com/android/ide/common/api/SegmentType.java
similarity index 100%
rename from base/rule-api/src/main/java/com/android/ide/common/api/SegmentType.java
rename to rule-api/src/main/java/com/android/ide/common/api/SegmentType.java
diff --git a/base/sdk-common/.classpath b/sdk-common/.classpath
similarity index 100%
rename from base/sdk-common/.classpath
rename to sdk-common/.classpath
diff --git a/base/sdk-common/.gitignore b/sdk-common/.gitignore
similarity index 100%
rename from base/sdk-common/.gitignore
rename to sdk-common/.gitignore
diff --git a/base/sdk-common/.project b/sdk-common/.project
similarity index 100%
rename from base/sdk-common/.project
rename to sdk-common/.project
diff --git a/base/rule-api/.settings/org.eclipse.jdt.core.prefs b/sdk-common/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/rule-api/.settings/org.eclipse.jdt.core.prefs
rename to sdk-common/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/sdk-common/.settings/org.moreunit.prefs b/sdk-common/.settings/org.moreunit.prefs
similarity index 100%
rename from base/sdk-common/.settings/org.moreunit.prefs
rename to sdk-common/.settings/org.moreunit.prefs
diff --git a/base/ninepatch/NOTICE b/sdk-common/NOTICE
similarity index 100%
rename from base/ninepatch/NOTICE
rename to sdk-common/NOTICE
diff --git a/sdk-common/build.gradle b/sdk-common/build.gradle
new file mode 100644
index 0000000..8532b46
--- /dev/null
+++ b/sdk-common/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'java'
+apply plugin: 'jacoco'
+apply plugin: 'sdk-java-lib'
+
+group = 'com.android.tools'
+archivesBaseName = 'sdk-common'
+version = rootProject.ext.baseVersion
+
+dependencies {
+ compile project(':base:sdklib')
+ compile project(':base:builder-test-api')
+ compile project(':base:builder-model')
+ compile 'org.bouncycastle:bcpkix-jdk15on:1.48'
+ compile 'org.bouncycastle:bcprov-jdk15on:1.48'
+
+ testCompile 'junit:junit:4.12'
+ testCompile project(':base:sdklib').sourceSets.test.output
+ testCompile project(':base:testutils')
+ testCompile 'org.easymock:easymock:3.3'
+ testCompile 'org.mockito:mockito-all:1.9.5'
+}
+
+project.ext.pomName = 'Android Tools sdk-common library'
+project.ext.pomDesc = 'sdk-common library used by other Android tools libraries.'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
+
diff --git a/base/sdk-common/generate-locale-data/build.gradle b/sdk-common/generate-locale-data/build.gradle
similarity index 100%
rename from base/sdk-common/generate-locale-data/build.gradle
rename to sdk-common/generate-locale-data/build.gradle
diff --git a/base/sdk-common/generate-locale-data/generate-locale-data.iml b/sdk-common/generate-locale-data/generate-locale-data.iml
similarity index 100%
rename from base/sdk-common/generate-locale-data/generate-locale-data.iml
rename to sdk-common/generate-locale-data/generate-locale-data.iml
diff --git a/base/sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/.gitignore b/sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/.gitignore
similarity index 100%
rename from base/sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/.gitignore
rename to sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/.gitignore
diff --git a/base/sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/LocaleTableGenerator.java b/sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/LocaleTableGenerator.java
similarity index 100%
rename from base/sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/LocaleTableGenerator.java
rename to sdk-common/generate-locale-data/src/main/java/com/android/ide/common/generate/locale/LocaleTableGenerator.java
diff --git a/sdk-common/sdk-common-base.iml b/sdk-common/sdk-common-base.iml
new file mode 100644
index 0000000..349f949
--- /dev/null
+++ b/sdk-common/sdk-common-base.iml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="module" module-name="layoutlib-api-base" exported="" />
+ <orderEntry type="module" module-name="common" exported="" />
+ <orderEntry type="library" exported="" name="kxml2" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="testutils" scope="TEST" />
+ <orderEntry type="module" module-name="builder-model" exported="" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="module" module-name="builder-test-api" exported="" />
+ <orderEntry type="module" module-name="sdklib-base" exported="" />
+ <orderEntry type="library" scope="TEST" name="easymock-tools" level="project" />
+ <orderEntry type="library" name="bouncy-castle" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/sdk-common/sdk-common.iml b/sdk-common/sdk-common.iml
new file mode 100644
index 0000000..656c670
--- /dev/null
+++ b/sdk-common/sdk-common.iml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="module" module-name="layoutlib-api" exported="" />
+ <orderEntry type="module" module-name="common" exported="" />
+ <orderEntry type="module" module-name="sdklib" exported="" />
+ <orderEntry type="library" exported="" name="kxml2" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="testutils" scope="TEST" />
+ <orderEntry type="library" exported="" name="builder-model" level="project" />
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="library" exported="" name="gson" level="project" />
+ <orderEntry type="module" module-name="builder-test-api" />
+ <orderEntry type="library" scope="TEST" name="easymock-tools" level="project" />
+ <orderEntry type="library" name="bouncy-castle" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/MergingLog.java b/sdk-common/src/main/java/com/android/ide/common/blame/MergingLog.java
new file mode 100644
index 0000000..cdb2b37
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/MergingLog.java
@@ -0,0 +1,245 @@
+/*
+ * 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.ide.common.blame;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.FileUtils;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Stores where file and text fragments within files came from, so the original can be found
+ * later if a subsequent build step outputs an error.
+ *
+ * It is implicitly incremental, shards (which correspond to type directories for resource files,
+ * eg. layout-land) are created or loaded only when the output files that they store are changed,
+ * and only the changed files are written when {@link #write()} is called.
+ *
+ * For its use by MergeWriter, it uses ConcurrentMaps internally, so it is safe to perform any log
+ * operation from any thread.
+ */
+public class MergingLog {
+
+ /**
+ * Map from whole files to other files (e.g. for all non-value resources)
+ */
+ @NonNull
+ private final LoadingCache<String, Map<SourceFile, SourceFile>> mWholeFileMaps =
+ CacheBuilder.newBuilder().build(new CacheLoader<String, Map<SourceFile, SourceFile>>() {
+ @Override
+ public Map<SourceFile, SourceFile> load(@NonNull String shard) {
+ return MergingLogPersistUtil.loadFromSingleFile(mOutputFolder, shard);
+ }
+ });
+
+ /**
+ * Map from positions in a merged file to file positions in their source files.
+ */
+ @NonNull
+ private final LoadingCache<String, Map<SourceFile, Map<SourcePosition, SourceFilePosition>>>
+ mMergedFileMaps = CacheBuilder.newBuilder().build(
+ new CacheLoader<String, Map<SourceFile, Map<SourcePosition, SourceFilePosition>>>() {
+ @Override
+ public Map<SourceFile, Map<SourcePosition, SourceFilePosition>> load(String shard)
+ throws Exception {
+ return MergingLogPersistUtil.loadFromMultiFile(mOutputFolder, shard);
+ }
+ });
+
+ @NonNull
+ private final File mOutputFolder;
+
+
+ public MergingLog(@NonNull File outputFolder) {
+ mOutputFolder = outputFolder;
+ }
+
+ /**
+ * Store the source of a file in the merging log.
+ *
+ * @param source the original file.
+ * @param destination the destination.
+ */
+ public void logCopy(@NonNull SourceFile source, @NonNull SourceFile destination) {
+ getWholeFileMap(destination).put(destination, source);
+ }
+
+ /**
+ * Store the source of a file in the merging log.
+ *
+ * @param source the original file.
+ * @param destination the destination.
+ */
+ public void logCopy(@NonNull File source, @NonNull File destination) {
+ logCopy(new SourceFile(source), new SourceFile(destination));
+ }
+
+
+ /**
+ * Remove a merged file from the merging log.
+ */
+ public void logRemove(@NonNull SourceFile merged) {
+ getWholeFileMap(merged).remove(merged);
+ getMergedFileMap(merged).remove(merged);
+ }
+
+ /**
+ * Store the source file positions for a merged file.
+ *
+ * @param mergedFile the destination file.
+ * @param map the map from positions in the destination file to the SourceFilePosition
+ * that they came from.
+ */
+ public void logSource(
+ @NonNull SourceFile mergedFile,
+ @NonNull Map<SourcePosition, SourceFilePosition> map) {
+ getMergedFileMap(mergedFile).put(mergedFile, map);
+ }
+
+
+ @NonNull
+ private Map<SourceFile, SourceFile> getWholeFileMap(@NonNull SourceFile file) {
+ String shard = getShard(file);
+ try {
+ return mWholeFileMaps.get(shard);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ private Map<SourceFile, Map<SourcePosition, SourceFilePosition>> getMergedFileMap(
+ @NonNull SourceFile file) {
+ String shard = getShard(file);
+ try {
+ return mMergedFileMaps.get(shard);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Find the original source file corresponding to an intermediate file.
+ */
+ @NonNull
+ public SourceFile find(@NonNull SourceFile mergedFile) {
+ SourceFile sourceFile = getWholeFileMap(mergedFile).get(mergedFile);
+ return sourceFile != null ? sourceFile : mergedFile;
+ }
+
+ /**
+ * Find the original source file and position for a position in an intermediate merged file.
+ */
+ @NonNull
+ public SourceFilePosition find(@NonNull final SourceFilePosition mergedFilePosition) {
+ SourceFile mergedSourceFile = mergedFilePosition.getFile();
+ Map<SourcePosition, SourceFilePosition> positionMap =
+ getMergedFileMap(mergedSourceFile).get(mergedSourceFile);
+ if (positionMap == null) {
+ SourceFile sourceFile = find(mergedSourceFile);
+ return new SourceFilePosition(sourceFile, mergedFilePosition.getPosition());
+ }
+ final SourcePosition position = mergedFilePosition.getPosition();
+
+ // TODO: this is not very efficient, which matters if we start processing debug messages.
+ NavigableMap<SourcePosition, SourceFilePosition> sortedMap =
+ new TreeMap<SourcePosition, SourceFilePosition>(new Comparator<SourcePosition>() {
+ @Override
+ public int compare(SourcePosition position1, SourcePosition position2) {
+ return position1.compareStart(position2);
+ }
+ });
+ sortedMap.putAll(positionMap);
+
+ /*
+
+ e.g. if we have
+ <pre>
+ error1 error2
+ /--/ /--/
+ <a> <b key="c" value="d" /> </a>
+ \----------------a---------------\
+ \-----------b-----------\
+ \--\
+ c
+ </pre>
+ we want to find c for error 1 and b for error 2.
+ */
+
+ // get the element just before this one.
+ @Nullable
+ Map.Entry<SourcePosition, SourceFilePosition> candidate = sortedMap.floorEntry(position);
+
+ // Don't traverse the whole file.
+ // This is the product of the depth and breadth of nesting that can be handled.
+ int patience = 20;
+ // check if it encompasses the error position.
+ SourcePosition key;
+ while (candidate != null && position.compareEnd(key = candidate.getKey()) > 0) {
+ patience--;
+ if (patience == 0) {
+ candidate = null;
+ break;
+ }
+ candidate = sortedMap.lowerEntry(key);
+ }
+
+ if (candidate == null) {
+ // we failed to find a link, return where we were.
+ return mergedFilePosition;
+ }
+
+ return candidate.getValue();
+
+ }
+
+ @NonNull
+ private static String getShard(@NonNull SourceFile sourceFile) {
+ File file = sourceFile.getSourceFile();
+ return file != null ? file.getParentFile().getName() : "unknown";
+ }
+
+ /**
+ * Persist the current state of the merging log.
+ */
+ public void write() throws IOException {
+ FileUtils.mkdirs(mOutputFolder);
+
+ // This is intrinsically incremental, any shards that were touched were loaded, and so
+ // will be saved. Empty map will result in the deletion of the file.
+ for (Map.Entry<String, Map<SourceFile, Map<SourcePosition, SourceFilePosition>>> entry :
+ mMergedFileMaps.asMap().entrySet()) {
+ MergingLogPersistUtil
+ .saveToMultiFile(mOutputFolder, entry.getKey(), entry.getValue());
+ }
+ for (Map.Entry<String, Map<SourceFile, SourceFile>> entry :
+ mWholeFileMaps.asMap().entrySet()) {
+ MergingLogPersistUtil
+ .saveToSingleFile(mOutputFolder, entry.getKey(), entry.getValue());
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/MergingLogPersistUtil.java b/sdk-common/src/main/java/com/android/ide/common/blame/MergingLogPersistUtil.java
new file mode 100644
index 0000000..ca352df
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/MergingLogPersistUtil.java
@@ -0,0 +1,301 @@
+/*
+ * 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.ide.common.blame;
+
+import static com.android.SdkConstants.DOT_JSON;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Utility functions to save and load individual merge log files.
+ *
+ * They are sharded by the directory containing the file, giving a file layout of the form:
+ *
+ * .
+ * ├── multi
+ * │ └── values.json
+ * └── single
+ * ├── drawable.json
+ * ├── layout.json
+ * └── layout-land.json
+ *
+ * This allows incremental changes to only have to load and rewrite a small fraction of the
+ * total log data.
+ */
+public class MergingLogPersistUtil {
+
+ private static final SourceFileJsonTypeAdapter mSourceFileJsonTypeAdapter
+ = new SourceFileJsonTypeAdapter();
+
+ private static final SourcePositionJsonTypeAdapter mSourcePositionJsonTypeAdapter
+ = new SourcePositionJsonTypeAdapter();
+
+ private static final SourceFilePositionJsonSerializer mSourceFilePositionJsonTypeAdapter
+ = new SourceFilePositionJsonSerializer();
+
+ private static final String KEY_OUTPUT_FILE = "outputFile";
+ private static final String KEY_FROM = "from";
+ private static final String KEY_TO = "to";
+ private static final String KEY_MERGED = "merged";
+ private static final String KEY_SOURCE = "source";
+ private static final String KEY_MAP = "map";
+ private static final String INDENT_STRING = " ";
+
+ private static File getMultiFile(File folder, String shard) {
+ return new File (new File(folder, "multi"), shard + DOT_JSON);
+ }
+
+ private static File getSingleFile(File folder, String shard) {
+ return new File (new File(folder, "single"), shard + DOT_JSON);
+ }
+
+ /**
+ * File format example for values files: (all paths are absolute)
+ * <pre>[
+ * {
+ * "outputFile": "/path/build/intermediates/res/merged/f1/debug/values/values.xml",
+ * "map": [
+ * {
+ * "to": {
+ * "startLine": 2,
+ * "startColumn": 4,
+ * "startOffset": 55,
+ * "endColumn": 54,
+ * "endOffset": 105
+ * },
+ * "from": {
+ * "file": "/path/src/f1/res/values/strings.xml",
+ * "position": {
+ * "startLine": 2,
+ * "startColumn": 4,
+ * "startOffset": 55,
+ * "endColumn": 54,
+ * "endOffset": 105
+ * }
+ * }
+ * },
+ * ...
+ * ]
+ * },
+ * ...
+ * ]</pre>
+ *
+ * There should not be multiple object in the outer list for the same outputFile.
+ */
+ static void saveToMultiFile(
+ @NonNull File folder,
+ @NonNull String shard,
+ @NonNull Map<SourceFile, Map<SourcePosition, SourceFilePosition>> map)
+ throws IOException {
+ File file = getMultiFile(folder, shard);
+ file.getParentFile().mkdir();
+ JsonWriter out = new JsonWriter(Files.newWriter(file, Charsets.UTF_8));
+ try {
+ out.setIndent(INDENT_STRING);
+ out.beginArray();
+ for (Map.Entry<SourceFile, Map<SourcePosition, SourceFilePosition>> entry : map.entrySet()) {
+ out.beginObject().name(KEY_OUTPUT_FILE);
+ mSourceFileJsonTypeAdapter.write(out, entry.getKey());
+ out.name(KEY_MAP);
+ out.beginArray();
+ for (Map.Entry<SourcePosition, SourceFilePosition> innerEntry : entry.getValue().entrySet()) {
+ out.beginObject();
+ out.name(KEY_TO);
+ mSourcePositionJsonTypeAdapter.write(out, innerEntry.getKey());
+ out.name(KEY_FROM);
+ mSourceFilePositionJsonTypeAdapter.write(out, innerEntry.getValue());
+ out.endObject();
+ }
+ out.endArray();
+ out.endObject();
+ }
+ out.endArray();
+ } finally {
+ out.close();
+ }
+ }
+
+ @NonNull
+ static Map<SourceFile, Map<SourcePosition, SourceFilePosition>> loadFromMultiFile(
+ @NonNull File folder,
+ @NonNull String shard) {
+ Map<SourceFile, Map<SourcePosition, SourceFilePosition>> map = Maps.newConcurrentMap();
+ JsonReader reader;
+ File file = getMultiFile(folder, shard);
+ if (!file.exists()) {
+ return map;
+ }
+ try {
+ reader = new JsonReader(Files.newReader(file, Charsets.UTF_8));
+ } catch (FileNotFoundException e) {
+ // Shouldn't happen unless it disappears under us.
+ return map;
+ }
+ try {
+ reader.beginArray();
+ while (reader.peek() != JsonToken.END_ARRAY) {
+ reader.beginObject();
+ SourceFile toFile = SourceFile.UNKNOWN;
+ Map<SourcePosition, SourceFilePosition> innerMap = Maps.newLinkedHashMap();
+ while (reader.peek() != JsonToken.END_OBJECT) {
+ final String name = reader.nextName();
+ if (name.equals(KEY_OUTPUT_FILE)) {
+ toFile = mSourceFileJsonTypeAdapter.read(reader);
+ } else if (name.equals(KEY_MAP)) {
+ reader.beginArray();
+ while (reader.peek() != JsonToken.END_ARRAY) {
+ reader.beginObject();
+ SourceFilePosition from = null;
+ SourcePosition to = null;
+ while (reader.peek() != JsonToken.END_OBJECT) {
+ final String innerName = reader.nextName();
+ if (innerName.equals(KEY_FROM)) {
+ from = mSourceFilePositionJsonTypeAdapter.read(reader);
+ } else if (innerName.equals(KEY_TO)) {
+ to = mSourcePositionJsonTypeAdapter.read(reader);
+ } else {
+ throw new IOException(
+ String.format("Unexpected property: %s", innerName));
+ }
+ }
+ if (from == null || to == null) {
+ throw new IOException("Each record must contain both from and to.");
+ }
+ innerMap.put(to, from);
+ reader.endObject();
+ }
+ reader.endArray();
+ } else {
+ throw new IOException(String.format("Unexpected property: %s", name));
+ }
+ }
+ map.put(toFile, innerMap);
+ reader.endObject();
+ }
+ reader.endArray();
+ return map;
+ } catch (IOException e) {
+ // TODO: trigger a non-incremental merge if this happens.
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ reader.close();
+ } catch (Throwable e2) {
+ // well, we tried.
+ }
+ }
+ }
+
+ /**
+ * File format for single file blame.
+ * <pre>[
+ * {
+ * "merged": "/path/build/intermediates/res/merged/f1/debug/layout/main.xml",
+ * "source": "/path/src/main/res/layout/main.xml"
+ * },
+ * ...
+ * ]</pre>
+ * @param folder
+ * @param shard
+ * @param map
+ * @throws IOException
+ */
+ static void saveToSingleFile(
+ @NonNull File folder,
+ @NonNull String shard,
+ @NonNull Map<SourceFile, SourceFile> map)
+ throws IOException {
+ File file = getSingleFile(folder, shard);
+ file.getParentFile().mkdir();
+ JsonWriter out = new JsonWriter(Files.newWriter(file, Charsets.UTF_8));
+ try {
+ out.setIndent(INDENT_STRING);
+ out.beginArray();
+ for (Map.Entry<SourceFile, SourceFile> entry : map.entrySet()) {
+ out.beginObject();
+ out.name(KEY_MERGED);
+ mSourceFileJsonTypeAdapter.write(out, entry.getKey());
+ out.name(KEY_SOURCE);
+ mSourceFileJsonTypeAdapter.write(out, entry.getValue());
+ out.endObject();
+ }
+ out.endArray();
+ } finally {
+ out.close();
+ }
+ }
+
+ @NonNull
+ static Map<SourceFile, SourceFile> loadFromSingleFile(
+ @NonNull File folder,
+ @NonNull String shard) {
+ Map<SourceFile, SourceFile> fileMap = Maps.newConcurrentMap();
+ JsonReader reader;
+ File file = getSingleFile(folder, shard);
+ if (!file.exists()) {
+ return fileMap;
+ }
+ try {
+ reader = new JsonReader(Files.newReader(file, Charsets.UTF_8));
+ } catch (FileNotFoundException e) {
+ // Shouldn't happen unless it disappears under us.
+ return fileMap;
+ }
+ try {
+ reader.beginArray();
+ while (reader.peek() != JsonToken.END_ARRAY) {
+ reader.beginObject();
+ SourceFile merged = SourceFile.UNKNOWN;
+ SourceFile source = SourceFile.UNKNOWN;
+ while (reader.peek() != JsonToken.END_OBJECT) {
+ String name = reader.nextName();
+ if (name.equals(KEY_MERGED)) {
+ merged = mSourceFileJsonTypeAdapter.read(reader);
+ } else if (name.equals(KEY_SOURCE)) {
+ source = mSourceFileJsonTypeAdapter.read(reader);
+ } else {
+ throw new IOException(String.format("Unexpected property: %s", name));
+ }
+ }
+ reader.endObject();
+ fileMap.put(merged, source);
+ }
+ reader.endArray();
+ return fileMap;
+ } catch (IOException e) {
+ // TODO: trigger a non-incremental merge if this happens.
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ reader.close();
+ } catch (Throwable e) {
+ // well, we tried.
+ }
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/MergingLogRewriter.java b/sdk-common/src/main/java/com/android/ide/common/blame/MergingLogRewriter.java
new file mode 100644
index 0000000..5e4e2a9
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/MergingLogRewriter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.ide.common.blame;
+
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.util.List;
+
+/**
+ * A proxy {@link MessageReceiver} that uses a {@link MergingLog} to rewrite {@link Message}s to
+ * point to their original files.
+ */
+public class MergingLogRewriter implements MessageReceiver {
+
+ @NonNull
+ private final MessageReceiver mMessageReceiver;
+
+ @NonNull
+ private final Function<SourceFilePosition, SourceFilePosition> mGetOriginalPosition;
+
+ /**
+ * Creates a new MessageLogRewriter.
+ *
+ * @param mergingLog the MergingLog to look up file positions in.
+ * @param messageReceiver the MessageReceiver to notify with the rewritten messages.
+ */
+ public MergingLogRewriter(@NonNull final MergingLog mergingLog,
+ @NonNull MessageReceiver messageReceiver) {
+ mMessageReceiver = messageReceiver;
+ mGetOriginalPosition = new Function<SourceFilePosition, SourceFilePosition>() {
+ @Override
+ public SourceFilePosition apply(SourceFilePosition input) {
+ return mergingLog.find(input);
+ }
+ };
+ }
+
+ @Override
+ public void receiveMessage(@NonNull Message message) {
+ List<SourceFilePosition> originalPositions = message.getSourceFilePositions();
+
+ Iterable<SourceFilePosition> positions =
+ Iterables.transform(originalPositions, mGetOriginalPosition);
+
+ mMessageReceiver.receiveMessage(
+ new Message(
+ message.getKind(),
+ message.getText(),
+ message.getRawMessage(),
+ message.getToolName(),
+ ImmutableList.copyOf(positions)));
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/MessageJsonSerializer.java b/sdk-common/src/main/java/com/android/ide/common/blame/MessageJsonSerializer.java
new file mode 100644
index 0000000..768e380
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/MessageJsonSerializer.java
@@ -0,0 +1,227 @@
+/*
+ * 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.ide.common.blame;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.EnumHashBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Class to handle json serialization and deserialization of messages.
+ *
+ * Reads json objects of the form:
+ *
+ * <pre>
+ * {
+ * "kind":"ERROR",
+ * "text":"errorText",
+ * "original":"unparsed error text: Error in some intermediate file",
+ * "sources": [{
+ * "file":"/path/to/source.java",
+ * "position":{
+ * "startLine":1,
+ * "startColumn":2,
+ * "startOffset":3,
+ * "endLine":4,
+ * "endColumn":5,
+ * "endOffset":6
+ * }
+ * }]
+ * }</pre>
+ *
+ * All fields, other than text, may be omitted. They have the following defaults:
+ *
+ * <table>
+ * <tr><th>Property</th> <th>Default</th> <th>Notes</th></tr>
+ * <tr><td>kind (ERROR, WARNING,
+ * INFO, UNKNOWN)</td> <td>UNKNOWN</td> <td></td></tr>
+ * <tr><td>text</td> <td><i>Empty String</i></td> <td>Should not be omitted.</td></tr>
+ * <tr><td>file (Absolute)</td> <td>{} <i>[unknown]</i></td> <td>See {@link SourceFileJsonTypeAdapter}</td></tr>
+ * <tr><td>position</td> <td>UNKNOWN</td> <td></td></tr>
+ * <tr><td>startLine,
+ * startColumn,
+ * startOffset</td> <td>-1 <i>[unknown]</i></td> <td rowspan="2">0-based</td></tr>
+ * <tr><td>endLine,
+ * endColumn,
+ * endOffset</td> <td>startLine, startColumn,
+ * startOffset</td></tr>
+ * </table>
+ *
+ * <h4>Notes</h4>
+ * <ul>
+ * <li>Offset need not be included, if needed by the consumer of the message it can
+ * be derived from the file, line and column.</li>
+ * <li>If line is included and column is not the message will be considered to apply
+ * to the whole line.</li>
+ * <li>A message can have multiple sources.</li>
+ * </ul>
+ * It also can read legacy serialized objects of the form:
+ *
+ * <pre>{
+ * "kind":"ERROR",
+ * "text":"errorText",
+ * "sourcePath": "/path/to/source.java",
+ * "position":{
+ * "startLine":1,
+ * "startColumn":2,
+ * "startOffset":3,
+ * "endLine":4,
+ * "endColumn":5,
+ * "endOffset":6
+ * }
+ * }</pre>
+ *
+ * These serializers are implemented using the lower-level TypeAdapter gson API which gives much
+ * more control and allow changes to be made without breaking backward compatibility.
+ */
+public class MessageJsonSerializer extends TypeAdapter<Message> {
+
+ private static final String KIND = "kind";
+
+ private static final String TEXT = "text";
+
+ private static final String SOURCE_FILE_POSITIONS = "sources";
+
+ private static final String RAW_MESSAGE = "original";
+
+ private static final String TOOL_NAME = "tool";
+
+ private static final String LEGACY_SOURCE_PATH = "sourcePath";
+
+ private static final String LEGACY_POSITION = "position";
+
+ private static final BiMap<Message.Kind, String> KIND_STRING_ENUM_MAP;
+
+ static {
+ EnumHashBiMap<Message.Kind, String> map = EnumHashBiMap.create(Message.Kind.class);
+ map.put(Message.Kind.ERROR, "error");
+ map.put(Message.Kind.WARNING, "warning");
+ map.put(Message.Kind.INFO, "info");
+ map.put(Message.Kind.STATISTICS, "statistics");
+ map.put(Message.Kind.UNKNOWN, "unknown");
+ map.put(Message.Kind.SIMPLE, "simple");
+ KIND_STRING_ENUM_MAP = Maps.unmodifiableBiMap(map);
+ }
+
+ private final SourceFilePositionJsonSerializer mSourceFilePositionTypeAdapter;
+ private final SourcePositionJsonTypeAdapter mSourcePositionTypeAdapter;
+
+ public MessageJsonSerializer() {
+ mSourceFilePositionTypeAdapter = new SourceFilePositionJsonSerializer();
+ mSourcePositionTypeAdapter = mSourceFilePositionTypeAdapter.getSourcePositionTypeAdapter();
+ }
+
+ @Override
+ public void write(JsonWriter out, Message message) throws IOException {
+ out.beginObject()
+ .name(KIND).value(KIND_STRING_ENUM_MAP.get(message.getKind()))
+ .name(TEXT).value(message.getText())
+ .name(SOURCE_FILE_POSITIONS).beginArray();
+ for (SourceFilePosition position : message.getSourceFilePositions()) {
+ mSourceFilePositionTypeAdapter.write(out, position);
+ }
+ out.endArray();
+ if (!message.getRawMessage().equals(message.getText())) {
+ out.name(RAW_MESSAGE).value(message.getRawMessage());
+ }
+ if (message.getToolName().isPresent()) {
+ out.name(TOOL_NAME).value(message.getToolName().get());
+ }
+ out.endObject();
+ }
+
+ @Override
+ public Message read(JsonReader in) throws IOException {
+ in.beginObject();
+ Message.Kind kind = Message.Kind.UNKNOWN;
+ String text = "";
+ String rawMessage = null;
+ Optional<String> toolName = Optional.absent();
+ ImmutableList.Builder<SourceFilePosition> positions =
+ new ImmutableList.Builder<SourceFilePosition>();
+ SourceFile legacyFile = SourceFile.UNKNOWN;
+ SourcePosition legacyPosition = SourcePosition.UNKNOWN;
+ while (in.hasNext()) {
+ String name = in.nextName();
+ if (name.equals(KIND)) {
+ //noinspection StringToUpperCaseOrToLowerCaseWithoutLocale
+ Message.Kind theKind = KIND_STRING_ENUM_MAP.inverse()
+ .get(in.nextString().toLowerCase());
+ kind = (theKind != null) ? theKind : Message.Kind.UNKNOWN;
+ } else if (name.equals(TEXT)) {
+ text = in.nextString();
+ } else if (name.equals(RAW_MESSAGE)) {
+ rawMessage = in.nextString();
+ } else if (name.equals(TOOL_NAME)) {
+ toolName = Optional.of(in.nextString());
+ } else if (name.equals(SOURCE_FILE_POSITIONS)) {
+ switch (in.peek()) {
+ case BEGIN_ARRAY:
+ in.beginArray();
+ while(in.hasNext()) {
+ positions.add(mSourceFilePositionTypeAdapter.read(in));
+ }
+ in.endArray();
+ break;
+ case BEGIN_OBJECT:
+ positions.add(mSourceFilePositionTypeAdapter.read(in));
+ break;
+ default:
+ in.skipValue();
+ break;
+ }
+ } else if (name.equals(LEGACY_SOURCE_PATH)) {
+ legacyFile = new SourceFile(new File(in.nextString()));
+ } else if (name.equals(LEGACY_POSITION)) {
+ legacyPosition = mSourcePositionTypeAdapter.read(in);
+ } else {
+ in.skipValue();
+ }
+ }
+ in.endObject();
+
+ if (legacyFile != SourceFile.UNKNOWN || legacyPosition != SourcePosition.UNKNOWN) {
+ positions.add(new SourceFilePosition(legacyFile, legacyPosition));
+ }
+ if (rawMessage == null) {
+ rawMessage = text;
+ }
+ ImmutableList<SourceFilePosition> sourceFilePositions = positions.build();
+ if (!sourceFilePositions.isEmpty()) {
+ return new Message(kind, text, rawMessage, toolName, sourceFilePositions);
+ } else {
+ return new Message(kind, text, rawMessage, toolName, ImmutableList.of(SourceFilePosition.UNKNOWN));
+ }
+ }
+
+ public static void registerTypeAdapters(GsonBuilder builder) {
+ builder.registerTypeAdapter(SourceFile.class, new SourceFileJsonTypeAdapter());
+ builder.registerTypeAdapter(SourcePosition.class, new SourcePositionJsonTypeAdapter());
+ builder.registerTypeAdapter(SourceFilePosition.class,
+ new SourceFilePositionJsonSerializer());
+ builder.registerTypeAdapter(Message.class, new MessageJsonSerializer());
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/MessageReceiver.java b/sdk-common/src/main/java/com/android/ide/common/blame/MessageReceiver.java
new file mode 100644
index 0000000..ef25208
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/MessageReceiver.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ide.common.blame;
+
+import com.android.annotations.NonNull;
+
+/**
+ * A message receiver.
+ *
+ * {@link MessageReceiver}s receive build {@link Message}s and either
+ * <ul><li>Output them to a logging system</li>
+ * <li>Output them to a user interface</li>
+ * <li>Transform them, such as mapping from intermediate files back to source files</li></ul>
+ */
+public interface MessageReceiver {
+
+ /**
+ * Process the given message.
+ */
+ void receiveMessage(@NonNull Message message);
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/ParsingProcessOutputHandler.java b/sdk-common/src/main/java/com/android/ide/common/blame/ParsingProcessOutputHandler.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/ParsingProcessOutputHandler.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/ParsingProcessOutputHandler.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/SourceFileJsonTypeAdapter.java b/sdk-common/src/main/java/com/android/ide/common/blame/SourceFileJsonTypeAdapter.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/SourceFileJsonTypeAdapter.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/SourceFileJsonTypeAdapter.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/SourceFilePositionJsonSerializer.java b/sdk-common/src/main/java/com/android/ide/common/blame/SourceFilePositionJsonSerializer.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/SourceFilePositionJsonSerializer.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/SourceFilePositionJsonSerializer.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapter.java b/sdk-common/src/main/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapter.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapter.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapter.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/parser/DexParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/DexParser.java
new file mode 100644
index 0000000..72914f7
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/parser/DexParser.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ide.common.blame.parser;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.parser.util.OutputLineReader;
+import com.android.utils.ILogger;
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+public class DexParser implements PatternAwareOutputParser {
+
+ static final String DEX_TOOL_NAME = "Dex";
+
+ static final String DEX_LIMIT_EXCEEDED_ERROR =
+ "The number of method references in a .dex file cannot exceed 64K.\n"
+ + "Learn how to resolve this issue at "
+ + "https://developer.android.com/tools/building/multidex.html";
+
+ private static final String COULD_NOT_CONVERT_BYTECODE_TO_DEX =
+ "Error converting bytecode to dex:\nCause: %s";
+
+ @Override
+ public boolean parse(@NonNull String line, @NonNull OutputLineReader reader,
+ @NonNull List<Message> messages, @NonNull ILogger logger)
+ throws ParsingFailedException {
+ if (line.startsWith("processing ") && line.endsWith("...")) {
+ // There is one such line for every class compiled, i.e. a lot of them. Log at debug
+ // level, otherwise --info becomes unusable.
+ logger.verbose(line);
+ return true;
+ }
+
+ if (line.startsWith("writing ") && line.endsWith("size 0...")) {
+ // There is one such line for every directory in the input jars. Log at debug level.
+ logger.verbose(line);
+ return true;
+ }
+
+ if (line.startsWith("ignored resource ") && line.endsWith("/")) {
+ // There is one such line for every directory in the input jars. Log at debug level.
+ logger.verbose(line);
+ return true;
+ }
+
+ if (line.startsWith("trouble writing output: Too many method references:")) {
+ StringBuilder original1 = new StringBuilder(line).append('\n');
+ String nextLine = reader.readLine();
+ while (!Strings.isNullOrEmpty(nextLine)) {
+ original1.append(nextLine).append('\n');
+ nextLine = reader.readLine();
+ }
+ messages.add(new Message(
+ Message.Kind.ERROR,
+ DEX_LIMIT_EXCEEDED_ERROR,
+ original1.toString(),
+ Optional.of(DEX_TOOL_NAME),
+ ImmutableList.of(SourceFilePosition.UNKNOWN)));
+ return true;
+ }
+ if (!line.equals("UNEXPECTED TOP-LEVEL EXCEPTION:")) {
+ return false;
+ }
+ StringBuilder original = new StringBuilder(line).append('\n');
+ String exception = reader.readLine();
+ if (exception == null) {
+ reader.pushBack();
+ return false;
+ }
+ if (exception.startsWith(
+ "com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: ")) {
+ original.append(exception).append('\n');
+ consumeStacktrace(reader, original);
+ messages.add(new Message(
+ Message.Kind.ERROR,
+ DEX_LIMIT_EXCEEDED_ERROR,
+ original.toString(),
+ Optional.of(DEX_TOOL_NAME),
+ ImmutableList.of(SourceFilePosition.UNKNOWN)));
+ return true;
+ } else { // Other generic exception
+ original.append(exception).append('\n');
+ consumeStacktrace(reader, original);
+ messages.add(new Message(
+ Message.Kind.ERROR,
+ String.format(COULD_NOT_CONVERT_BYTECODE_TO_DEX, exception),
+ original.toString(),
+ Optional.of(DEX_TOOL_NAME),
+ ImmutableList.of(SourceFilePosition.UNKNOWN)));
+ return true;
+ }
+ }
+
+ private static void consumeStacktrace(OutputLineReader reader, StringBuilder out) {
+ String nextLine = reader.readLine();
+ while (nextLine != null &&
+ (nextLine.startsWith("\t") || nextLine.startsWith("Caused by: "))) {
+ out.append(nextLine).append('\n');
+ nextLine = reader.readLine();
+ }
+ //noinspection VariableNotUsedInsideIf
+ if (nextLine != null) {
+ reader.pushBack();
+ }
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/JsonEncodedGradleMessageParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/JsonEncodedGradleMessageParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/JsonEncodedGradleMessageParser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/JsonEncodedGradleMessageParser.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/parser/LegacyNdkOutputParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/LegacyNdkOutputParser.java
new file mode 100644
index 0000000..c972aba
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/parser/LegacyNdkOutputParser.java
@@ -0,0 +1,220 @@
+/*
+ * 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.ide.common.blame.parser;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.ide.common.blame.parser.util.OutputLineReader;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Parses output from the legacy NDK support.
+ */
+public class LegacyNdkOutputParser implements PatternAwareOutputParser {
+
+ private static final String FROM = "from";
+ private static final String UNKNOWN_MSG_PREFIX1 = "In file included " + FROM;
+ private static final String UNKNOWN_MSG_PREFIX2 = " " + FROM;
+
+ private static final char COLON = ':';
+
+ @Override
+ public boolean parse(@NonNull String line, @NonNull OutputLineReader reader,
+ @NonNull List<Message> messages, @NonNull ILogger logger)
+ throws ParsingFailedException {
+ // Parses unknown message
+ if (line.startsWith(UNKNOWN_MSG_PREFIX1) || line.startsWith(UNKNOWN_MSG_PREFIX2)) {
+ int fromIndex = line.indexOf(FROM);
+ String unknownMsgCause = line.substring(0, fromIndex).trim();
+ unknownMsgCause = "(Unknown) " + unknownMsgCause;
+ String coordinates = line.substring(fromIndex + FROM.length()).trim();
+ if (!coordinates.isEmpty()) {
+ int colonIndex1 = line.indexOf(COLON);
+ if (colonIndex1 == 1) { // drive letter (Windows)
+ coordinates = coordinates.substring(colonIndex1 + 1);
+ }
+ if (coordinates.endsWith(",") || coordinates.endsWith(":")) {
+ coordinates = coordinates.substring(0, coordinates.length() - 1);
+ }
+
+ List<String> segments = Splitter.on(COLON).splitToList(coordinates);
+ if (segments.size() == 3) {
+ String pathname = segments.get(0);
+ File file = new File(pathname);
+ int lineNumber = 0;
+ try {
+ lineNumber = Integer.parseInt(segments.get(1));
+ }
+ catch (NumberFormatException ignore) {
+ }
+ int column = 0;
+ try {
+ column = Integer.parseInt(segments.get(2));
+ }
+ catch (NumberFormatException ignore) {
+ }
+ SourceFilePosition position = new SourceFilePosition(file,
+ new SourcePosition(lineNumber - 1, column - 1, -1));
+ Message message = new Message(Message.Kind.INFO, unknownMsgCause.trim(), position);
+ if (!messages.contains(message)) {
+ // There may be a few duplicate "unknown" messages
+ addMessage(message, messages);
+ }
+ }
+ }
+ return true;
+ }
+
+ // Parses unresolved include.
+ int colonIndex1 = line.indexOf(COLON);
+ if (colonIndex1 == 1) { // drive letter (Windows)
+ colonIndex1 = line.indexOf(COLON, colonIndex1 + 1);
+ }
+ if (colonIndex1 >= 0) { // looks like found something like a file path.
+ String part1 = line.substring(0, colonIndex1).trim();
+
+ int colonIndex2 = line.indexOf(COLON, colonIndex1 + 1);
+ if (colonIndex2 >= 0) {
+ File file = new File(part1);
+ if (!file.isFile()) {
+ // the part one is not a file path.
+ return false;
+ }
+ try {
+ int lineNumber = Integer.parseInt(
+ line.substring(colonIndex1 + 1, colonIndex2).trim()); // 1-based.
+
+ int colonIndex3 = line.indexOf(COLON, colonIndex2 + 1);
+ if (colonIndex1 >= 0) {
+ int column = Integer.parseInt(
+ line.substring(colonIndex2 + 1, colonIndex3).trim());
+
+ int colonIndex4 = line.indexOf(COLON, colonIndex3 + 1);
+ if (colonIndex4 >= 0) {
+ Message.Kind kind = Message.Kind.INFO;
+
+ String severity =
+ line.substring(colonIndex3 + 1,
+ colonIndex4).toLowerCase(Locale.getDefault()).trim();
+ if (severity.endsWith("error")) {
+ kind = Message.Kind.ERROR;
+ } else if (severity.endsWith("warning")) {
+ kind = Message.Kind.WARNING;
+ }
+ String text = line.substring(colonIndex4 + 1).trim();
+ List<String> messageList = Lists.newArrayList();
+ messageList.add(text);
+ String prevLine = null;
+ do {
+ String nextLine = reader.readLine();
+ if (nextLine == null) {
+ return false;
+ }
+ if (nextLine.trim().equals("^")) {
+ String messageEnd = reader.readLine();
+
+ while (isMessageEnd(messageEnd)) {
+ messageList.add(messageEnd.trim());
+ messageEnd = reader.readLine();
+ }
+
+ if (messageEnd != null) {
+ reader.pushBack();
+ }
+ break;
+ }
+ if (prevLine != null) {
+ messageList.add(prevLine);
+ }
+ prevLine = nextLine;
+ } while (true);
+
+ if (column >= 0) {
+ messageList = convertMessages(messageList);
+ StringBuilder buf = new StringBuilder();
+ for (String m : messageList) {
+ if (buf.length() > 0) {
+ buf.append(SdkUtils.getLineSeparator());
+ }
+ buf.append(m);
+ }
+ Message msg = new Message(kind, buf.toString(),
+ new SourceFilePosition(file,
+ new SourcePosition(lineNumber - 1, column - 1, -1)));
+ if (!messages.contains(msg)) {
+ addMessage(msg, messages);
+ }
+ return true;
+ }
+ }
+
+ }
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ }
+ return false;
+ }
+
+ private static void addMessage(@NonNull Message message, @NonNull List<Message> messages) {
+ boolean duplicatesPrevious = false;
+ int messageCount = messages.size();
+ if (messageCount > 0) {
+ Message lastMessage = messages.get(messageCount - 1);
+ duplicatesPrevious = lastMessage.equals(message);
+ }
+ if (!duplicatesPrevious) {
+ messages.add(message);
+ }
+ }
+
+ private static boolean isMessageEnd(@Nullable String line) {
+ return line != null && !line.isEmpty() && Character.isWhitespace(line.charAt(0));
+ }
+
+ @NonNull
+ private static List<String> convertMessages(@NonNull List<String> messages) {
+ if (messages.size() <= 1) {
+ return messages;
+ }
+ final String line0 = messages.get(0);
+ final String line1 = messages.get(1);
+ final int colonIndex = line1.indexOf(':');
+ if (colonIndex > 0) {
+ String part1 = line1.substring(0, colonIndex).trim();
+ // jikes
+ if ("symbol".equals(part1)) {
+ String symbol = line1.substring(colonIndex + 1).trim();
+ messages.remove(1);
+ if (messages.size() >= 2) {
+ messages.remove(1);
+ }
+ messages.set(0, line0 + " " + symbol);
+ }
+ }
+ return messages;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/ParsingFailedException.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/ParsingFailedException.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/ParsingFailedException.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/ParsingFailedException.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/PatternAwareOutputParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/PatternAwareOutputParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/PatternAwareOutputParser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/PatternAwareOutputParser.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/parser/ToolOutputParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/ToolOutputParser.java
new file mode 100644
index 0000000..8e4b8a0
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/parser/ToolOutputParser.java
@@ -0,0 +1,111 @@
+/*
+ * 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.ide.common.blame.parser;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.parser.util.OutputLineReader;
+import com.android.utils.ILogger;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ToolOutputParser {
+
+ @NonNull
+ private final List<PatternAwareOutputParser> mParsers;
+
+ @NonNull
+ private final ILogger mLogger;
+
+ @NonNull
+ private final Message.Kind mUnparsedMessageKind;
+
+ public ToolOutputParser(@NonNull Iterable<PatternAwareOutputParser> parsers, @NonNull ILogger logger) {
+ this(ImmutableList.copyOf(parsers), Message.Kind.SIMPLE, logger);
+ }
+
+ public ToolOutputParser(@NonNull PatternAwareOutputParser [] parsers, @NonNull ILogger logger) {
+ this(ImmutableList.copyOf(parsers), Message.Kind.SIMPLE, logger);
+ }
+
+ public ToolOutputParser(@NonNull PatternAwareOutputParser parser, @NonNull ILogger logger) {
+ this(ImmutableList.of(parser), Message.Kind.SIMPLE, logger);
+ }
+
+ public ToolOutputParser(@NonNull PatternAwareOutputParser parser,
+ @NonNull Message.Kind unparsedMessageKind, @NonNull ILogger logger) {
+ this(ImmutableList.of(parser), unparsedMessageKind, logger);
+ }
+
+ private ToolOutputParser(@NonNull ImmutableList<PatternAwareOutputParser> parsers,
+ @NonNull Message.Kind unparsedMessageKind,
+ @NonNull ILogger logger) {
+ mParsers = parsers;
+ mUnparsedMessageKind = unparsedMessageKind;
+ mLogger = logger;
+ }
+
+ public List<Message> parseToolOutput(@NonNull String output) {
+ OutputLineReader outputReader = new OutputLineReader(output);
+
+ if (outputReader.getLineCount() == 0) {
+ return Collections.emptyList();
+ }
+
+ List<Message> messages = Lists.newArrayList();
+ String line;
+ while ((line = outputReader.readLine()) != null) {
+ if (line.trim().isEmpty()) {
+ continue;
+ }
+ boolean handled = false;
+ for (PatternAwareOutputParser parser : mParsers) {
+ try {
+ if (parser.parse(line, outputReader, messages, mLogger)) {
+ handled = true;
+ break;
+ }
+ }
+ catch (ParsingFailedException e) {
+ return Collections.emptyList();
+ }
+ }
+ if (handled) {
+ int messageCount = messages.size();
+ if (messageCount > 0) {
+ Message last = messages.get(messageCount - 1);
+ if (last.getText().contains("Build cancelled")) {
+ // Build was cancelled, just quit. Extra messages are just confusing noise.
+ break;
+ }
+ }
+ }
+ else {
+ // If none of the standard parsers recognize the input, include it as info such
+ // that users don't miss potentially vital output such as gradle plugin exceptions.
+ // If there is predictable useless input we don't want to appear here, add a custom
+ // parser to digest it.
+ messages.add(new Message(mUnparsedMessageKind, line, SourceFilePosition.UNKNOWN));
+ }
+ }
+ return messages;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AaptOutputParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AaptOutputParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AaptOutputParser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AaptOutputParser.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AbstractAaptOutputParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AbstractAaptOutputParser.java
new file mode 100644
index 0000000..d36dfd4
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/AbstractAaptOutputParser.java
@@ -0,0 +1,573 @@
+/*
+ * 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.ide.common.blame.parser.aapt;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.TAG_ITEM;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.ide.common.blame.parser.util.OutputLineReader;
+import com.android.ide.common.blame.parser.ParsingFailedException;
+import com.android.ide.common.blame.parser.PatternAwareOutputParser;
+import com.android.resources.ResourceFolderType;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+ at VisibleForTesting
+public abstract class AbstractAaptOutputParser implements PatternAwareOutputParser {
+ /**
+ * Portion of the error message which states the context in which the error occurred, such as
+ * which property was being processed and what the string value was that caused the error.
+ * <pre>
+ * error: No resource found that matches the given name (at 'text' with value '@string/foo')
+ * </pre>
+ */
+ private static final Pattern PROPERTY_NAME_AND_VALUE = Pattern
+ .compile("\\(at '(.+)' with value '(.*)'\\)");
+
+ /**
+ * Portion of error message which points to the second occurrence of a repeated resource
+ * definition. <p/> Example: error: Resource entry repeatedStyle1 already has bag item
+ * android:gravity.
+ */
+ private static final Pattern REPEATED_RESOURCE = Pattern
+ .compile("Resource entry (.+) already has bag item (.+)\\.");
+
+ /**
+ * Suffix of error message which points to the first occurrence of a repeated resource
+ * definition. Example: Originally defined here.
+ */
+ private static final String ORIGINALLY_DEFINED_HERE = "Originally defined here.";
+
+ private static final Pattern NO_RESOURCE_FOUND = Pattern
+ .compile("No resource found that matches the given name: attr '(.+)'\\.");
+
+ /**
+ * Portion of error message which points to a missing required attribute in a resource
+ * definition. <p/> Example: error: error: A 'name' attribute is required for <style>
+ */
+ private static final Pattern REQUIRED_ATTRIBUTE = Pattern
+ .compile("A '(.+)' attribute is required for <(.+)>");
+
+ // Keep in sync with SdkUtils#FILENAME_PREFIX
+ private static final String START_MARKER = "<!-- From: ";
+
+ private static final String END_MARKER = " -->";
+
+ private static final Cache<String, ReadOnlyDocument> ourDocumentsByPathCache = CacheBuilder
+ .newBuilder()
+ .weakValues().build();
+
+ private static final String AAPT_TOOL_NAME = "AAPT";
+
+ @VisibleForTesting
+ public static File ourRootDir;
+
+ @NonNull
+ private static SourcePosition findMessagePositionInFile(@NonNull File file, @NonNull String msgText, int locationLine, ILogger logger) {
+ SourcePosition exactPosition = findExactMessagePositionInFile(file, msgText, locationLine, logger);
+ if (exactPosition != null) {
+ return exactPosition;
+ } else {
+ return new SourcePosition(locationLine, -1, -1);
+ }
+ }
+
+ @Nullable
+ private static SourcePosition findExactMessagePositionInFile(@NonNull File file, @NonNull String msgText, int locationLine, @NonNull ILogger logger) {
+ Matcher matcher = PROPERTY_NAME_AND_VALUE.matcher(msgText);
+ if (matcher.find()) {
+ String name = matcher.group(1);
+ String value = matcher.group(2);
+ if (!value.isEmpty()) {
+ return findText(file, name, value, locationLine, logger);
+ }
+ SourcePosition position1 = findText(file, name, "\"\"", locationLine, logger);
+ SourcePosition position2 = findText(file, name, "''", locationLine, logger);
+ if (position1 == null) {
+ if (position2 == null) {
+ // position at the property name instead.
+ return findText(file, name, null, locationLine, logger);
+ }
+ return position2;
+ } else if (position2 == null) {
+ return position1;
+ } else if (position1.getStartOffset() < position2.getStartOffset()) {
+ return position1;
+ } else {
+ return position2;
+ }
+ }
+
+ matcher = REPEATED_RESOURCE.matcher(msgText);
+ if (matcher.find()) {
+ String property = matcher.group(2);
+ return findText(file, property, null, locationLine, logger);
+ }
+
+ matcher = NO_RESOURCE_FOUND.matcher(msgText);
+ if (matcher.find()) {
+ String property = matcher.group(1);
+ return findText(file, property, null, locationLine, logger);
+ }
+
+ matcher = REQUIRED_ATTRIBUTE.matcher(msgText);
+ if (matcher.find()) {
+ String elementName = matcher.group(2);
+ return findText(file, '<' + elementName, null, locationLine, logger);
+ }
+
+ if (msgText.endsWith(ORIGINALLY_DEFINED_HERE)) {
+ return findLineStart(file, locationLine, logger);
+ }
+ return null;
+ }
+
+ @Nullable
+ private static SourcePosition findText(@NonNull File file, @NonNull String first,
+ @Nullable String second, int locationLine, @NonNull ILogger logger) {
+ ReadOnlyDocument document = getDocument(file, logger);
+ if (document == null) {
+ return null;
+ }
+ int offset = document.lineOffset(locationLine);
+ if (offset == -1L) {
+ return null;
+ }
+ int resultOffset = document.findText(first, offset);
+ if (resultOffset == -1L) {
+ return null;
+ }
+
+ if (second != null) {
+ resultOffset = document.findText(second, resultOffset + first.length());
+ if (resultOffset == -1L) {
+ return null;
+ }
+ }
+
+ int startLineNumber = document.lineNumber(resultOffset);
+ int startLineOffset = document.lineOffset(startLineNumber);
+ int endResultOffset = resultOffset + (second != null ? second.length() : first.length());
+ int endLineNumber = document.lineNumber(endResultOffset);
+ int endLineOffset = document.lineOffset((endLineNumber));
+ return new SourcePosition(startLineNumber, resultOffset - startLineOffset, resultOffset,
+ endLineNumber, endResultOffset - endLineOffset, endResultOffset);
+ }
+
+ @Nullable
+ private static SourcePosition findLineStart(@NonNull File file, int locationLine, ILogger logger) {
+ ReadOnlyDocument document = getDocument(file, logger);
+ if (document == null) {
+ return null;
+ }
+ int lineOffset = document.lineOffset(locationLine);
+ if (lineOffset == -1L) {
+ return null;
+ }
+ int nextLineOffset = document.lineOffset(locationLine + 1);
+ if (nextLineOffset == -1) {
+ nextLineOffset = document.length();
+ }
+
+ // Ignore whitespace at the beginning of the line
+ int resultOffset = -1;
+ for (int i = lineOffset; i < nextLineOffset; i++) {
+ char c = document.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ resultOffset = i;
+ break;
+ }
+ }
+ if (resultOffset == -1L) {
+ return null;
+ }
+
+ //Ignore whitespace at the end of the line
+ int endResultOffset = resultOffset;
+ for (int i = nextLineOffset - 1; i >= resultOffset; i--) {
+ char c = document.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ endResultOffset = i;
+ break;
+ }
+ }
+ return new SourcePosition(locationLine, resultOffset - lineOffset, resultOffset,
+ locationLine, endResultOffset - lineOffset, endResultOffset);
+ }
+
+ @Nullable
+ private static ReadOnlyDocument getDocument(@NonNull File file, ILogger logger) {
+ String filePath = file.getAbsolutePath();
+ ReadOnlyDocument document = ourDocumentsByPathCache.getIfPresent(filePath);
+ if (document == null || document.isStale()) {
+ try {
+ if (!file.exists()) {
+ if (ourRootDir != null && ourRootDir.isAbsolute() && !file.isAbsolute()) {
+ file = new File(ourRootDir, file.getPath());
+ return getDocument(file, logger);
+ }
+ return null;
+ }
+ document = new ReadOnlyDocument(file);
+ ourDocumentsByPathCache.put(filePath, document);
+ } catch (IOException e) {
+ String format = "Unexpected error occurred while reading file '%s' [%s]";
+ logger.warning(format, file.getAbsolutePath(), e);
+ return null;
+ }
+ }
+ return document;
+ }
+
+ @NonNull
+ private static String urlToPath(@NonNull String url) {
+ if (url.startsWith("file:")) {
+ String prefix;
+ if (url.startsWith("file://")) {
+ prefix = "file://";
+ } else {
+ prefix = "file:";
+ }
+ return url.substring(prefix.length());
+ }
+ return url;
+ }
+
+ /**
+ * Locates a resource value definition in a given file for a given key, and returns the
+ * corresponding line number, or -1 if not found. For example, given the key
+ * "string/group2_string" it will locate an element {@code <string name="group2_string">} or
+ * {@code <item type="string" name="group2_string"}
+ */
+ public static SourcePosition findResourceLine(@NonNull File file, @NonNull String key, @NonNull ILogger logger) {
+ int slash = key.indexOf('/');
+ if (slash == -1) {
+ assert false : slash; // invalid key format
+ return SourcePosition.UNKNOWN;
+ }
+
+ final String type = key.substring(0, slash);
+ final String name = key.substring(slash + 1);
+
+ return findValueDeclaration(file, type, name, logger);
+ }
+
+ /**
+ * Locates a resource value declaration in a given file and returns the corresponding line
+ * number, or -1 if not found.
+ */
+ public static SourcePosition findValueDeclaration(@NonNull File file, @NonNull final String type,
+ @NonNull final String name, @NonNull ILogger logger) {
+ if (!file.exists()) {
+ return SourcePosition.UNKNOWN;
+ }
+
+ final ReadOnlyDocument document = getDocument(file, logger);
+ if (document == null) {
+ return SourcePosition.UNKNOWN;
+ }
+
+ // First just do something simple: scan for the string. If it only occurs once, it's easy!
+ int index = document.findText(name, 0);
+ if (index == -1) {
+ return SourcePosition.UNKNOWN;
+ }
+
+ // See if there are any more occurrences; if not, we're done
+ if (document.findText(name, index + name.length()) == -1) {
+ return document.sourcePosition(index);
+ }
+
+ // Try looking for name="$name"
+ int nameIndex = document.findText("name=\"" + name + "\"", 0);
+ if (nameIndex != -1) {
+ // TODO: Disambiguate by type, so if values.xml contains both R.string.foo and R.dimen.foo we
+ // pick the right one!
+ return document.sourcePosition(nameIndex);
+ }
+
+ SourcePosition lineNumber = findValueDeclarationViaParse(type, name, document);
+ if (!SourcePosition.UNKNOWN.equals(lineNumber)) {
+ return lineNumber;
+ }
+
+ // Just fall back to the first occurrence of the string
+ //noinspection ConstantConditions
+ assert index != -1;
+ return document.sourcePosition(index);
+ }
+
+ private static SourcePosition findValueDeclarationViaParse(final String type, final String name,
+ ReadOnlyDocument document) {
+ // Finally do a full SAX parse to identify the position
+ final int[] certain = new int[]{-1, 0}; // line,column for exact match
+ final int[] possible = new int[]{-1,
+ 0}; // line,column for possible match, not confirmed by type
+
+ final AtomicReference<Integer> line = new AtomicReference<Integer>(-1);
+ final DefaultHandler handler = new DefaultHandler() {
+ private int myDepth;
+
+ private Locator myLocator;
+
+ @Override
+ public void setDocumentLocator(final Locator locator) {
+ myLocator = locator;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+ myDepth++;
+ if (myDepth == 2) {
+ if (name.equals(attributes.getValue(ATTR_NAME))) {
+ int lineNumber = myLocator.getLineNumber() - 1;
+ int column = myLocator.getColumnNumber() - 1;
+ if (qName.equals(type) || TAG_ITEM.equals(qName) && type
+ .equals(attributes.getValue(ATTR_TYPE))) {
+ line.set(lineNumber);
+ certain[0] = lineNumber;
+ certain[1] = column;
+ } else if (line.get() < 0) {
+ // Use a negative number to indicate a match where we're not totally confident (type didn't match)
+ line.set(-lineNumber);
+ possible[0] = lineNumber;
+ possible[1] = column;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ myDepth--;
+ }
+ };
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ try {
+ // Parse the input
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(document.getContents())), handler);
+ } catch (Throwable t) {
+ // Ignore parser errors; we might have found the error position earlier than the parse error position
+ }
+
+ int endLineNumber;
+ int endColumn;
+ if (certain[0] != -1) {
+ endLineNumber = certain[0];
+ endColumn = certain[1];
+ } else {
+ endLineNumber = possible[0];
+ endColumn = possible[1];
+ }
+ if (endLineNumber != -1) {
+ // SAX' locator will point to the END of the opening declaration, meaning that if it spans multiple lines, we are pointing
+ // to the last line:
+ // <item
+ // type="dimen"
+ // name="attribute"
+ // > <--- this is where the locator points, so we need to search backwards
+ int endOffset = document.lineOffset(endLineNumber) + endColumn;
+ int offset = document.findTextBackwards(name, endOffset);
+ if (offset != -1) {
+ SourcePosition start = document.sourcePosition(offset);
+ return new SourcePosition(start.getStartLine(), start.getStartColumn(), start.getStartOffset(),
+ endLineNumber, endColumn, endOffset);
+ }
+ return new SourcePosition(endLineNumber, endColumn, endOffset);
+ }
+
+ return SourcePosition.UNKNOWN;
+ }
+
+ @Nullable
+ final Matcher getNextLineMatcher(@NonNull OutputLineReader reader, @NonNull Pattern pattern) {
+ // unless we can't, because we reached the last line
+ String line = reader.readLine();
+ if (line == null) {
+ // we expected a 2nd line, so we flag as error and we bail
+ return null;
+ }
+ Matcher m = pattern.matcher(line);
+ return m.matches() ? m : null;
+ }
+
+ @NonNull
+ Message createMessage(@NonNull Message.Kind kind,
+ @NonNull String text,
+ @Nullable String sourcePath,
+ @Nullable String lineNumberAsText,
+ @NonNull String original,
+ ILogger logger) throws ParsingFailedException {
+ File file = null;
+ if (sourcePath != null) {
+ file = new File(sourcePath);
+ if (!file.isFile()) {
+ throw new ParsingFailedException();
+ }
+ }
+
+ SourcePosition errorPosition = parseLineNumber(lineNumberAsText);
+ if (sourcePath != null) {
+ SourceFilePosition source = findSourcePosition(file, errorPosition.getStartLine(), text, logger);
+ if (source != null) {
+ file = source.getFile().getSourceFile();
+ sourcePath = file.getPath();
+ if (source.getPosition().getStartLine() != -1) {
+ errorPosition = source.getPosition();
+ }
+ }
+ }
+
+ // Attempt to determine the exact range of characters affected by this error.
+ // This will look up the actual text of the file, go to the particular error line and findText for the specific string mentioned in the
+ // error.
+ if (file != null && errorPosition.getStartLine() != -1) {
+ errorPosition = findMessagePositionInFile(file, text, errorPosition.getStartLine(), logger);
+ }
+ return new Message(kind, text, original, AAPT_TOOL_NAME,
+ new SourceFilePosition(file, errorPosition));
+ }
+
+ private SourcePosition parseLineNumber(String lineNumberAsText) throws ParsingFailedException {
+ int lineNumber = -1;
+ if (lineNumberAsText != null) {
+ try {
+ lineNumber = Integer.parseInt(lineNumberAsText);
+ } catch (NumberFormatException e) {
+ throw new ParsingFailedException();
+ }
+ }
+
+ return new SourcePosition(lineNumber - 1, -1, -1);
+ }
+
+ /**
+ *
+ * @param file
+ * @param locationLine
+ * @param message
+ * @param logger
+ * @return null if could not be found, new SourceFilePosition(new SourceFile file,
+ */
+ @Nullable
+ protected static SourceFilePosition findSourcePosition(@NonNull File file, int locationLine,
+ String message, ILogger logger) {
+ if (!file.getPath().endsWith(DOT_XML)) {
+ return null;
+ }
+
+ ReadOnlyDocument document = getDocument(file, logger);
+ if (document == null) {
+ return null;
+ }
+ // All value files get merged together into a single values file; in that case, we need to
+ // search for comment markers backwards which indicates the source file for the current file
+
+ int searchStart;
+ String fileName = file.getName();
+ boolean isManifest = fileName.equals(ANDROID_MANIFEST_XML);
+ boolean isValueFile = fileName.startsWith(ResourceFolderType.VALUES.getName());
+ if (isValueFile || isManifest) {
+ searchStart = document.lineOffset(locationLine);
+ } else {
+ searchStart = document.length();
+ }
+ if (searchStart == -1L) {
+ return null;
+ }
+
+ int start = document.findTextBackwards(START_MARKER, searchStart);
+ assert start < searchStart;
+
+ if (start == -1 && isManifest && searchStart < document.length()) {
+ // If the manifest file didn't need to merge, it will place the source reference at the end instead
+ searchStart = document.length();
+ if (searchStart != -1L) {
+ start = document.findTextBackwards(START_MARKER, searchStart);
+ assert start < searchStart;
+ }
+ }
+
+ if (start == -1) {
+ return null;
+ }
+ start += START_MARKER.length();
+ int end = document.findText(END_MARKER, start);
+ if (end == -1) {
+ return null;
+ }
+ String sourcePath = document.subsequence(start, end);
+ File sourceFile;
+ if (sourcePath.startsWith("file:")) {
+ String originalPath = sourcePath;
+ sourcePath = urlToPath(sourcePath);
+ sourceFile = new File(sourcePath);
+ if (!sourceFile.exists()) {
+ // JpsPathUtil.urlToPath just chops off the prefix; try a little harder
+ // for example to decode %2D's which are used by the MergedResourceWriter to
+ // encode --'s in the path, since those are invalid in XML comments
+ try {
+ sourceFile = SdkUtils.urlToFile(originalPath);
+ } catch (MalformedURLException e) {
+ logger.warning("Invalid file URL: " + originalPath);
+ }
+ }
+ } else {
+ sourceFile = new File(sourcePath);
+ }
+
+ if (isValueFile) {
+ // Look up the line number
+ SourcePosition position = findMessagePositionInFile(sourceFile, message,
+ 1, logger); // Search from the beginning
+ return new SourceFilePosition(new SourceFile(sourceFile), position);
+ }
+
+ return new SourceFilePosition(new SourceFile(sourceFile), SourcePosition.UNKNOWN);
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/BadXmlBlockParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/BadXmlBlockParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/BadXmlBlockParser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/BadXmlBlockParser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error1Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error1Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error1Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error1Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error2Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error2Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error2Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error2Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error3Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error3Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error3Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error3Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error4Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error4Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error4Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error4Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error5Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error5Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error5Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error5Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error6Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error6Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error6Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error6Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error7Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error7Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error7Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error7Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error8Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error8Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error8Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Error8Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/ReadOnlyDocument.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/ReadOnlyDocument.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/ReadOnlyDocument.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/ReadOnlyDocument.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingHiddenFileParser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingHiddenFileParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingHiddenFileParser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingHiddenFileParser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning1Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning1Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning1Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning1Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning2Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning2Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning2Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/SkippingWarning2Parser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Warning1Parser.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Warning1Parser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Warning1Parser.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/aapt/Warning1Parser.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/OutputLineReader.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/OutputLineReader.java
new file mode 100644
index 0000000..aa7379f
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/OutputLineReader.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.blame.parser.util;
+
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.regex.Pattern;
+
+/**
+ * Reads a compiler's output line-by-line.
+ */
+public class OutputLineReader {
+ private static final Pattern LINE_BREAK = Pattern.compile("\\r?\\n");
+
+ @NonNull private final String[] myLines;
+
+ private final int myLineCount;
+ private int myPosition;
+
+ /**
+ * Creates a new {@link OutputLineReader}.
+ *
+ * @param text the text to read.
+ */
+ public OutputLineReader(@NonNull String text) {
+ myLines = LINE_BREAK.split(text);
+ myLineCount = myLines.length;
+ }
+
+ public int getLineCount() {
+ return myLineCount;
+ }
+
+ /**
+ * Reads the next line of text, moving the line pointer to the next one.
+ *
+ * @return the contents of the next line, or {@code null} if we reached the end of the text.
+ */
+ @Nullable
+ public String readLine() {
+ if (myPosition >= 0 && myPosition < myLineCount) {
+ return myLines[myPosition++];
+ }
+ return null;
+ }
+
+ /**
+ * Reads the text of one the line at the given position, without moving the line pointer.
+ *
+ * @param lineToSkipCount the number of lines to skip from the line pointer.
+ * @return the contents of the specified line, or {@code null} if the specified position is greater than the end of the text.
+ */
+ @Nullable
+ public String peek(int lineToSkipCount) {
+ int tempPosition = lineToSkipCount + myPosition;
+ if (tempPosition >= 0 && tempPosition < myLineCount) {
+ return myLines[tempPosition];
+ }
+ return null;
+ }
+
+ public boolean hasNextLine() {
+ return myPosition < myLineCount - 1;
+ }
+
+ public void skipNextLine() {
+ myPosition++;
+ }
+
+ public void pushBack() {
+ myPosition--;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/ParserUtil.java b/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/ParserUtil.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/blame/parser/util/ParserUtil.java
rename to sdk-common/src/main/java/com/android/ide/common/blame/parser/util/ParserUtil.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/build/SplitOutputMatcher.java b/sdk-common/src/main/java/com/android/ide/common/build/SplitOutputMatcher.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/build/SplitOutputMatcher.java
rename to sdk-common/src/main/java/com/android/ide/common/build/SplitOutputMatcher.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/build/SplitSelectTool.java b/sdk-common/src/main/java/com/android/ide/common/build/SplitSelectTool.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/build/SplitSelectTool.java
rename to sdk-common/src/main/java/com/android/ide/common/build/SplitSelectTool.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/caching/CreatingCache.java b/sdk-common/src/main/java/com/android/ide/common/caching/CreatingCache.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/caching/CreatingCache.java
rename to sdk-common/src/main/java/com/android/ide/common/caching/CreatingCache.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/internal/AaptCruncher.java b/sdk-common/src/main/java/com/android/ide/common/internal/AaptCruncher.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/internal/AaptCruncher.java
rename to sdk-common/src/main/java/com/android/ide/common/internal/AaptCruncher.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java b/sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java
rename to sdk-common/src/main/java/com/android/ide/common/internal/CommandLineRunner.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/internal/ExecutorSingleton.java b/sdk-common/src/main/java/com/android/ide/common/internal/ExecutorSingleton.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/internal/ExecutorSingleton.java
rename to sdk-common/src/main/java/com/android/ide/common/internal/ExecutorSingleton.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/internal/LoggedErrorException.java b/sdk-common/src/main/java/com/android/ide/common/internal/LoggedErrorException.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/internal/LoggedErrorException.java
rename to sdk-common/src/main/java/com/android/ide/common/internal/LoggedErrorException.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/internal/PngCruncher.java b/sdk-common/src/main/java/com/android/ide/common/internal/PngCruncher.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/internal/PngCruncher.java
rename to sdk-common/src/main/java/com/android/ide/common/internal/PngCruncher.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/internal/PngException.java b/sdk-common/src/main/java/com/android/ide/common/internal/PngException.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/internal/PngException.java
rename to sdk-common/src/main/java/com/android/ide/common/internal/PngException.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java b/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java
new file mode 100644
index 0000000..0760a7d
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/internal/WaitableExecutor.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.internal;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * A utility wrapper around a {@link CompletionService} using an ThreadPoolExecutor so that it
+ * is possible to wait on all the tasks.
+ *
+ * Tasks are submitted as {@link Callable} with {@link #execute(java.util.concurrent.Callable)}.
+ *
+ * After executing all tasks, it is possible to wait on them with
+ * {@link #waitForTasksWithQuickFail(boolean)}, or {@link #waitForAllTasks()}.
+ *
+ * This class is not Thread safe!
+ *
+ * @param <T> Result type of all the tasks.
+ */
+public class WaitableExecutor<T> {
+
+ private final ExecutorService mExecutorService;
+ private final CompletionService<T> mCompletionService;
+ private final Set<Future<T>> mFutureSet = Sets.newHashSet();
+
+ /**
+ * Creates an executor that will use at most <var>nThreads</var> threads.
+ * @param nThreads the number of threads, or zero for default count (which is number of core)
+ */
+ public WaitableExecutor(int nThreads) {
+ if (nThreads < 1) {
+ nThreads = Runtime.getRuntime().availableProcessors();
+ }
+
+ mExecutorService = Executors.newFixedThreadPool(nThreads);
+ mCompletionService = new ExecutorCompletionService<T>(mExecutorService);
+ }
+
+ /**
+ * Creates an executor that will use at most 1 thread per core.
+ */
+ public WaitableExecutor() {
+ mExecutorService = null;
+ mCompletionService = new ExecutorCompletionService<T>(ExecutorSingleton.getExecutor());
+ }
+
+ /**
+ * Submits a Callable for execution.
+ *
+ * @param callable the callable to run.
+ */
+ public void execute(Callable<T> callable) {
+ mFutureSet.add(mCompletionService.submit(callable));
+ }
+
+ /**
+ * Waits for all tasks to be executed. If a tasks throws an exception, it will be thrown from
+ * this method inside a RuntimeException, preventing access to the result of the other
+ * threads.
+ *
+ * If you want to get the results of all tasks (result and/or exception), use
+ * {@link #waitForAllTasks()}
+ *
+ * @param cancelRemaining if true, and a task fails, cancel all remaining tasks.
+ *
+ * @return a list of all the return values from the tasks.
+ *
+ * @throws InterruptedException if this thread was interrupted. Not if the tasks were interrupted.
+ * @throws LoggedErrorException
+ */
+ public List<T> waitForTasksWithQuickFail(boolean cancelRemaining) throws InterruptedException,
+ LoggedErrorException {
+ List<T> results = Lists.newArrayListWithCapacity(mFutureSet.size());
+ try {
+ while (!mFutureSet.isEmpty()) {
+ Future<T> future = mCompletionService.take();
+
+ assert mFutureSet.contains(future);
+ mFutureSet.remove(future);
+
+ // Get the result from the task. If the task threw an exception,
+ // this will throw it, wrapped in an ExecutionException, caught below.
+ results.add(future.get());
+ }
+ } catch (ExecutionException e) {
+ if (cancelRemaining) {
+ cancelAllTasks();
+ }
+
+ // get the original exception and throw that one.
+ Throwable cause = e.getCause();
+ if (cause instanceof LoggedErrorException) {
+ throw (LoggedErrorException) cause;
+ } else {
+ throw new RuntimeException(cause);
+ }
+ } finally {
+ if (mExecutorService != null) {
+ mExecutorService.shutdownNow();
+ }
+ }
+
+ return results;
+ }
+
+ public static final class TaskResult<T> {
+ public T value;
+ public Throwable exception;
+
+ static <T> TaskResult<T> withValue(T value) {
+ TaskResult<T> result = new TaskResult<T>(null);
+ result.value = value;
+ return result;
+ }
+
+ TaskResult(Throwable cause) {
+ exception = cause;
+ }
+ }
+
+ /**
+ * Waits for all tasks to be executed, and returns a {@link TaskResult} for each, containing
+ * either the result or the exception thrown by the task.
+ *
+ * If a task is cancelled (and it threw InterruptedException) then the result for the task
+ * is *not* included.
+ *
+ * @return a list of all the return values from the tasks.
+ *
+ * @throws InterruptedException if this thread was interrupted. Not if the tasks were interrupted.
+ */
+ public List<TaskResult<T>> waitForAllTasks() throws InterruptedException {
+ List<TaskResult<T>> results = Lists.newArrayListWithCapacity(mFutureSet.size());
+ try {
+ while (!mFutureSet.isEmpty()) {
+ Future<T> future = mCompletionService.take();
+
+ assert mFutureSet.contains(future);
+ mFutureSet.remove(future);
+
+ // Get the result from the task.
+ try {
+ results.add(TaskResult.withValue(future.get()));
+ } catch (ExecutionException e) {
+ // the original exception thrown by the task is the cause of this one.
+ Throwable cause = e.getCause();
+
+ //noinspection StatementWithEmptyBody
+ if (cause instanceof InterruptedException) {
+ // if the task was cancelled we probably don't care about its result.
+ } else {
+ // there was an error.
+ results.add(new TaskResult<T>(cause));
+ }
+ }
+ }
+ } finally {
+ if (mExecutorService != null) {
+ mExecutorService.shutdownNow();
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * Cancel all remaining tasks.
+ */
+ public void cancelAllTasks() {
+ for (Future<T> future : mFutureSet) {
+ future.cancel(true /*mayInterruptIfRunning*/);
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java b/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java
new file mode 100644
index 0000000..633e708
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.packaging;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Utility class for packaging.
+ */
+public class PackagingUtils {
+
+ /**
+ * Checks whether a folder and its content is valid for packaging into the .apk as
+ * standard Java resource.
+ * @param folderName the name of the folder.
+ *
+ * @return true if the folder is valid for packaging.
+ */
+ public static boolean checkFolderForPackaging(@NonNull String folderName) {
+ return !folderName.equalsIgnoreCase("CVS") &&
+ !folderName.equalsIgnoreCase(".svn") &&
+ !folderName.equalsIgnoreCase("SCCS") &&
+ !folderName.startsWith("_");
+ }
+
+ /**
+ * Checks a file to make sure it should be packaged as standard resources.
+ * @param fileName the name of the file (including extension)
+ * @param allowClassFiles whether to allow java class files
+ * @return true if the file should be packaged as standard java resources.
+ */
+ public static boolean checkFileForPackaging(@NonNull String fileName, boolean allowClassFiles) {
+ String[] fileSegments = fileName.split("\\.");
+ String fileExt = "";
+ if (fileSegments.length > 1) {
+ fileExt = fileSegments[fileSegments.length-1];
+ }
+
+ return checkFileForPackaging(fileName, fileExt, allowClassFiles);
+ }
+
+ /**
+ * Checks a file to make sure it should be packaged as standard resources.
+ * @param fileName the name of the file (including extension)
+ * @return true if the file should be packaged as standard java resources.
+ */
+ public static boolean checkFileForPackaging(@NonNull String fileName) {
+ return checkFileForPackaging(fileName, false);
+ }
+
+ /**
+ * Checks a file to make sure it should be packaged as standard resources.
+ * @param fileName the name of the file (including extension)
+ * @param extension the extension of the file (excluding '.')
+ * @param allowClassFiles whether to allow java class files
+ * @return true if the file should be packaged as standard java resources.
+ */
+ public static boolean checkFileForPackaging(
+ @NonNull String fileName,
+ @NonNull String extension,
+ boolean allowClassFiles) {
+ // ignore hidden files and backup files
+ return !(fileName.charAt(0) == '.' || fileName.charAt(fileName.length() - 1) == '~') &&
+ !isOfNonResourcesExtensions(extension, allowClassFiles) &&
+ !isNotAResourceFile(fileName);
+ }
+
+ /**
+ * Checks a file to make sure it should be packaged as standard resources.
+ * @param fileName the name of the file (including extension)
+ * @param extension the extension of the file (excluding '.')
+ * @return true if the file should be packaged as standard java resources.
+ */
+ public static boolean checkFileForPackaging(
+ @NonNull String fileName,
+ @NonNull String extension) {
+ // ignore hidden files and backup files
+ return !(fileName.charAt(0) == '.' || fileName.charAt(fileName.length() - 1) == '~') &&
+ !isOfNonResourcesExtensions(extension, false) &&
+ !isNotAResourceFile(fileName);
+ }
+
+ private static boolean isOfNonResourcesExtensions(
+ @NonNull String extension,
+ boolean allowClassFiles) {
+ for (String ext : NON_RESOURCES_EXTENSIONS) {
+ if (ext.equalsIgnoreCase(extension)) {
+ return true;
+ }
+ }
+
+ return !allowClassFiles && SdkConstants.EXT_CLASS.equals(extension);
+ }
+
+ private static boolean isNotAResourceFile(@NonNull String fileName) {
+ for (String name : NON_RESOURCES_FILENAMES) {
+ if (name.equalsIgnoreCase(fileName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the list of file extensions that represents non resources files.
+ */
+ public static final ImmutableList<String> NON_RESOURCES_EXTENSIONS =
+ ImmutableList.<String>builder()
+ .add("aidl") // Aidl files
+ .add("rs") // RenderScript files
+ .add("fs") // FilterScript files
+ .add("rsh") // RenderScript header files
+ .add("d") // Dependency files
+ .add("java") // Java files
+ .add("scala") // Scala files
+ .add("scc") // VisualSourceSafe
+ .add("swp") // vi swap file
+ .build();
+
+ /**
+ * Return file names that are not resource files.
+ */
+ public static final ImmutableList<String> NON_RESOURCES_FILENAMES =
+ ImmutableList.<String>builder()
+ .add("thumbs.db") // image index file
+ .add("picasa.ini") // image index file
+ .add("about.html") // Javadoc
+ .add("package.html") // Javadoc
+ .add("overview.html") // Javadoc
+ .build();
+
+ /**
+ * Computes an "application hash", a reasonably unique identifier for an app.
+ * <p>
+ * This is currently used by Instant Run to prevent apps on a device from guessing
+ * the authentication token associated with an instant run developed app on the same
+ * device.
+ * <p>
+ * This method creates the "secret", the field in the AppInfo class which is used as a simple
+ * authentication token between the IDE and the app server.
+ * <p>
+ * This is not a cryptographically strong unique id; we could attempt to make a truly random
+ * number here, but we'd need to store that id somewhere such that subsequent builds
+ * will use the same secret, to avoid having the IDE and the app getting out of sync,
+ * and there isn't really a natural place for us to store the key to make it survive across
+ * a clean build. (One possibility is putting it into local.properties).
+ * <p>
+ * However, we have much simpler needs: we just need a key that can't be guessed from
+ * a hostile app on the developer's device, and it only has a few guesses (after providing
+ * the wrong secret to the server a few times, the server will shut down.) We can't
+ * rely on the package name along, since the port number is known, and the package name is
+ * discoverable by the hostile app (by querying the contents of /data/data/*). Therefore
+ * we also include facts that the hostile app can't know, such as as the path on the
+ * developer's machine to the app project and the name of the developer's machine, etc.
+ * The goal is for this secret to be reasonably stable (e.g. the same from build to build)
+ * yet not something an app could guess if it only has a couple of tries.
+ */
+ public static long computeApplicationHash(@NonNull File projectDir) {
+ HashFunction hashFunction = Hashing.md5();
+ Hasher hasher = hashFunction.newHasher();
+ try {
+ projectDir = projectDir.getCanonicalFile();
+ } catch (IOException ignore) {
+ // If this throws an exception the assignment won't
+ // be done and we'll stick with the regular project dir
+ }
+ String path = projectDir.getPath();
+ hasher.putBytes(path.getBytes(Charsets.UTF_8));
+ String user = System.getProperty("user.name");
+ if (user != null) {
+ hasher.putBytes(user.getBytes(Charsets.UTF_8));
+ }
+ return hasher.hash().asLong();
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/process/BaseProcessOutputHandler.java b/sdk-common/src/main/java/com/android/ide/common/process/BaseProcessOutputHandler.java
new file mode 100644
index 0000000..ee97fb1
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/process/BaseProcessOutputHandler.java
@@ -0,0 +1,78 @@
+/*
+ * 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.ide.common.process;
+
+import com.android.annotations.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+/**
+ * Partial implementation of ProcessOutputHandler that creates a ProcessOutput that caches the
+ * output in a ByteArrayOutputStream.
+ *
+ * This does not do anything with it, since it does not implement
+ * {@link ProcessOutputHandler#handleOutput(ProcessOutput)}
+ */
+public abstract class BaseProcessOutputHandler implements ProcessOutputHandler {
+
+ public BaseProcessOutputHandler() {
+ }
+
+ @NonNull
+ @Override
+ public ProcessOutput createOutput() {
+ return new BaseProcessOutput();
+ }
+
+ public static final class BaseProcessOutput implements ProcessOutput {
+ private final ByteArrayOutputStream mStandardOutput = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream mErrorOutput = new ByteArrayOutputStream();
+
+ @NonNull
+ @Override
+ public ByteArrayOutputStream getStandardOutput() {
+ return mStandardOutput;
+ }
+
+ @NonNull
+ @Override
+ public ByteArrayOutputStream getErrorOutput() {
+ return mErrorOutput;
+ }
+
+ @NonNull
+ public String getStandardOutputAsString() throws ProcessException {
+ return getString(mStandardOutput);
+ }
+
+ @NonNull
+ public String getErrorOutputAsString() throws ProcessException {
+ return getString(mErrorOutput);
+ }
+ }
+
+ private static String getString(@NonNull ByteArrayOutputStream stream) throws ProcessException {
+ try {
+ return stream.toString(Charset.defaultCharset().name());
+ } catch (UnsupportedEncodingException e) {
+ throw new ProcessException(e);
+ }
+ }
+
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/CachedProcessOutputHandler.java b/sdk-common/src/main/java/com/android/ide/common/process/CachedProcessOutputHandler.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/CachedProcessOutputHandler.java
rename to sdk-common/src/main/java/com/android/ide/common/process/CachedProcessOutputHandler.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/DefaultProcessExecutor.java b/sdk-common/src/main/java/com/android/ide/common/process/DefaultProcessExecutor.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/DefaultProcessExecutor.java
rename to sdk-common/src/main/java/com/android/ide/common/process/DefaultProcessExecutor.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/JavaProcessExecutor.java b/sdk-common/src/main/java/com/android/ide/common/process/JavaProcessExecutor.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/JavaProcessExecutor.java
rename to sdk-common/src/main/java/com/android/ide/common/process/JavaProcessExecutor.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/JavaProcessInfo.java b/sdk-common/src/main/java/com/android/ide/common/process/JavaProcessInfo.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/JavaProcessInfo.java
rename to sdk-common/src/main/java/com/android/ide/common/process/JavaProcessInfo.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/LoggedProcessOutputHandler.java b/sdk-common/src/main/java/com/android/ide/common/process/LoggedProcessOutputHandler.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/LoggedProcessOutputHandler.java
rename to sdk-common/src/main/java/com/android/ide/common/process/LoggedProcessOutputHandler.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessEnvBuilder.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessEnvBuilder.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/ProcessEnvBuilder.java
rename to sdk-common/src/main/java/com/android/ide/common/process/ProcessEnvBuilder.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessException.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessException.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/ProcessException.java
rename to sdk-common/src/main/java/com/android/ide/common/process/ProcessException.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessExecutor.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessExecutor.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/ProcessExecutor.java
rename to sdk-common/src/main/java/com/android/ide/common/process/ProcessExecutor.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessInfo.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessInfo.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/ProcessInfo.java
rename to sdk-common/src/main/java/com/android/ide/common/process/ProcessInfo.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessInfoBuilder.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessInfoBuilder.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/ProcessInfoBuilder.java
rename to sdk-common/src/main/java/com/android/ide/common/process/ProcessInfoBuilder.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessOutput.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessOutput.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/ProcessOutput.java
rename to sdk-common/src/main/java/com/android/ide/common/process/ProcessOutput.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/process/ProcessOutputHandler.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessOutputHandler.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/process/ProcessOutputHandler.java
rename to sdk-common/src/main/java/com/android/ide/common/process/ProcessOutputHandler.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/process/ProcessResult.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessResult.java
new file mode 100644
index 0000000..4630f4d
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/process/ProcessResult.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.process;
+
+import com.android.annotations.NonNull;
+
+/**
+ * The result of executing an external process.
+ */
+public interface ProcessResult {
+
+ /**
+ * Throws an exception if the process exited with a non-zero exit value.
+ * @return this
+ * @throws ProcessException if the process exited with a non-zero exit value
+ */
+ @NonNull
+ ProcessResult assertNormalExitValue() throws ProcessException;
+
+ /**
+ * Returns the exit value of the process.
+ */
+ int getExitValue();
+
+ /**
+ * Re-throws any failure executing this process.
+ * @return this
+ * @throws ProcessException the execution failure wrapped in a ProcessExecution
+ */
+ @NonNull
+ ProcessResult rethrowFailure() throws ProcessException;
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/process/ProcessResultImpl.java b/sdk-common/src/main/java/com/android/ide/common/process/ProcessResultImpl.java
new file mode 100644
index 0000000..e5df6cb
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/process/ProcessResultImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.process;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Internal implementation of ProcessResult used by DefaultProcessExecutor.
+ */
+class ProcessResultImpl implements ProcessResult {
+
+ private final String mCommand;
+ private final int mExitValue;
+ private final Exception mFailure;
+
+ ProcessResultImpl(String command, int exitValue) {
+ this(command, exitValue, null);
+ }
+
+ ProcessResultImpl(String command, Exception failure) {
+ this(command, -1, failure);
+ }
+
+ ProcessResultImpl(String command, int exitValue, Exception failure) {
+ mCommand = command;
+ mExitValue = exitValue;
+ mFailure = failure;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult assertNormalExitValue() throws ProcessException {
+ if (mExitValue != 0) {
+ throw new ProcessException(
+ String.format("Return code %d for process '%s'", mExitValue, mCommand));
+ }
+
+ return this;
+ }
+
+ @Override
+ public int getExitValue() {
+ return mExitValue;
+ }
+
+ @NonNull
+ @Override
+ public ProcessResult rethrowFailure() throws ProcessException {
+ if (mFailure != null) {
+ throw new ProcessException("", mFailure);
+ }
+
+ return this;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java b/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java
new file mode 100644
index 0000000..a33a3a4
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.rendering;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.HardwareConfig;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenRound;
+import com.android.sdklib.devices.ButtonType;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Screen;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper method to create a {@link HardwareConfig} object.
+ *
+ * The base data comes from a {@link Device} object, with additional data provided on the helper
+ * object.
+ *
+ * Since {@link HardwareConfig} is immutable, this allows creating one in several (optional)
+ * steps more easily.
+ *
+ */
+public class HardwareConfigHelper {
+
+ @NonNull
+ private final Device mDevice;
+ @NonNull
+ private ScreenOrientation mScreenOrientation = ScreenOrientation.PORTRAIT;
+
+ // optional
+ private int mMaxRenderWidth = -1;
+ private int mMaxRenderHeight = -1;
+ private int mOverrideRenderWidth = -1;
+ private int mOverrideRenderHeight = -1;
+
+ /**
+ * Creates a new helper for a given device.
+ * @param device the device to provide the base data.
+ */
+ public HardwareConfigHelper(@NonNull Device device) {
+ mDevice = device;
+ }
+
+ /**
+ * Sets the orientation of the config.
+ * @param screenOrientation the orientation.
+ * @return this (such that chains of setters can be stringed together)
+ */
+ @NonNull
+ public HardwareConfigHelper setOrientation(@NonNull ScreenOrientation screenOrientation) {
+ mScreenOrientation = screenOrientation;
+ return this;
+ }
+
+ /**
+ * Overrides the width and height to be used during rendering.
+ *
+ * A value of -1 will make the rendering use the normal width and height coming from the
+ * {@link Device} object.
+ *
+ * @param overrideRenderWidth the width in pixels of the layout to be rendered
+ * @param overrideRenderHeight the height in pixels of the layout to be rendered
+ * @return this (such that chains of setters can be stringed together)
+ */
+ @NonNull
+ public HardwareConfigHelper setOverrideRenderSize(int overrideRenderWidth,
+ int overrideRenderHeight) {
+ mOverrideRenderWidth = overrideRenderWidth;
+ mOverrideRenderHeight = overrideRenderHeight;
+ return this;
+ }
+
+ /**
+ * Sets the max width and height to be used during rendering.
+ *
+ * A value of -1 will make the rendering use the normal width and height coming from the
+ * {@link Device} object.
+ *
+ * @param maxRenderWidth the max width in pixels of the layout to be rendered
+ * @param maxRenderHeight the max height in pixels of the layout to be rendered
+ * @return this (such that chains of setters can be stringed together)
+ */
+ @NonNull
+ public HardwareConfigHelper setMaxRenderSize(int maxRenderWidth, int maxRenderHeight) {
+ mMaxRenderWidth = maxRenderWidth;
+ mMaxRenderHeight = maxRenderHeight;
+ return this;
+ }
+
+ /**
+ * Creates and returns the HardwareConfig object.
+ * @return the config
+ */
+ @SuppressWarnings("SuspiciousNameCombination") // Deliberately swapping orientations
+ @NonNull
+ public HardwareConfig getConfig() {
+ Screen screen = mDevice.getDefaultHardware().getScreen();
+
+ // compute width and height to take orientation into account.
+ int x = screen.getXDimension();
+ int y = screen.getYDimension();
+ int width, height;
+
+ if (x > y) {
+ if (mScreenOrientation == ScreenOrientation.LANDSCAPE) {
+ width = x;
+ height = y;
+ } else {
+ width = y;
+ height = x;
+ }
+ } else {
+ if (mScreenOrientation == ScreenOrientation.LANDSCAPE) {
+ width = y;
+ height = x;
+ } else {
+ width = x;
+ height = y;
+ }
+ }
+
+ if (mOverrideRenderHeight != -1) {
+ width = mOverrideRenderWidth;
+ }
+
+ if (mOverrideRenderHeight != -1) {
+ height = mOverrideRenderHeight;
+ }
+
+ if (mMaxRenderWidth != -1) {
+ width = mMaxRenderWidth;
+ }
+
+ if (mMaxRenderHeight != -1) {
+ height = mMaxRenderHeight;
+ }
+
+ return new HardwareConfig(
+ width,
+ height,
+ screen.getPixelDensity(),
+ (float) screen.getXdpi(),
+ (float) screen.getYdpi(),
+ screen.getSize(),
+ mScreenOrientation,
+ mDevice.getDefaultHardware().getScreen().getScreenRound(),
+ mDevice.getDefaultHardware().getButtonType() == ButtonType.SOFT);
+ }
+
+ // ---- Device Display Helpers ----
+
+ /** Manufacturer used by the generic devices in the device list */
+ public static final String MANUFACTURER_GENERIC = "Generic"; //$NON-NLS-1$
+ private static final String NEXUS = "Nexus"; //$NON-NLS-1$
+ private static final Pattern GENERIC_PATTERN =
+ Pattern.compile("(\\d+\\.?\\d*)\" (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
+ private static final String ID_PREFIX_WEAR = "wear_"; //$NON-NLS-1$
+ private static final String ID_PREFIX_WEAR_ROUND = "wear_round"; //$NON-NLS-1$
+ private static final String ID_PREFIX_TV = "tv_"; //$NON-NLS-1$
+
+ /**
+ * Returns a user-displayable description of the given Nexus device
+ * @param device the device to check
+ * @return the label
+ * @see #isNexus(com.android.sdklib.devices.Device)
+ */
+ @NonNull
+ public static String getNexusLabel(@NonNull Device device) {
+ String name = device.getDisplayName();
+ Screen screen = device.getDefaultHardware().getScreen();
+ float length = (float) screen.getDiagonalLength();
+ // Round dimensions to the nearest tenth
+ length = Math.round(10 * length) / 10.0f;
+ return String.format(Locale.US, "%1$s (%3$s\", %2$s)",
+ name, getResolutionString(device), Float.toString(length));
+ }
+
+ /**
+ * Returns a user-displayable description of the given generic device
+ * @param device the device to check
+ * @return the label
+ * @see #isGeneric(Device)
+ */
+ @NonNull
+ public static String getGenericLabel(@NonNull Device device) {
+ // * Use the same precision for all devices (all but one specify decimals)
+ // * Add some leading space such that the dot ends up roughly in the
+ // same space
+ // * Add in screen resolution and density
+ String name = device.getDisplayName();
+ Matcher matcher = GENERIC_PATTERN.matcher(name);
+ if (matcher.matches()) {
+ String size = matcher.group(1);
+ String n = matcher.group(2);
+ int dot = size.indexOf('.');
+ if (dot == -1) {
+ size += ".0";
+ dot = size.length() - 2;
+ }
+ for (int i = 0; i < 2 - dot; i++) {
+ size = ' ' + size;
+ }
+ name = size + "\" " + n;
+ }
+
+ return String.format(Locale.US, "%1$s (%2$s)", name,
+ getResolutionString(device));
+ }
+
+ /**
+ * Returns a user displayable screen resolution string for the given device
+ * @param device the device to look up the string for
+ * @return a user displayable string
+ */
+ @NonNull
+ public static String getResolutionString(@NonNull Device device) {
+ Screen screen = device.getDefaultHardware().getScreen();
+ return String.format(Locale.US,
+ "%1$d \u00D7 %2$d: %3$s", // U+00D7: Unicode multiplication sign
+ screen.getXDimension(),
+ screen.getYDimension(),
+ screen.getPixelDensity().getResourceValue());
+ }
+
+ /**
+ * Returns true if the given device is a generic device
+ * @param device the device to check
+ * @return true if the device is generic
+ */
+ public static boolean isGeneric(@NonNull Device device) {
+ return device.getManufacturer().equals(MANUFACTURER_GENERIC);
+ }
+
+ /**
+ * Returns true if the given device is a Nexus device
+ * @param device the device to check
+ * @return true if the device is a Nexus
+ */
+ public static boolean isNexus(@NonNull Device device) {
+ return device.getId().contains(NEXUS);
+ }
+
+ /**
+ * Whether the given device is a wear device
+ */
+ public static boolean isWear(@Nullable Device device) {
+ return device != null && device.getId().startsWith(ID_PREFIX_WEAR);
+ }
+
+ /**
+ * Whether the given device is a TV device
+ */
+ public static boolean isTv(@Nullable Device device) {
+ return device != null && device.getId().startsWith(ID_PREFIX_TV);
+ }
+
+ /**
+ * Returns the rank of the given nexus device. This can be used to order
+ * the devices chronologically.
+ *
+ * @param device the device to look up the rank for
+ * @return the rank of the device
+ */
+ public static int nexusRank(Device device) {
+ String id = device.getId();
+ if (id.equals("Nexus One")) { //$NON-NLS-1$
+ return 1;
+ }
+ if (id.equals("Nexus S")) { //$NON-NLS-1$
+ return 2;
+ }
+ if (id.equals("Galaxy Nexus")) { //$NON-NLS-1$
+ return 3;
+ }
+ if (id.equals("Nexus 7")) { //$NON-NLS-1$
+ return 4; // 2012 version
+ }
+ if (id.equals("Nexus 10")) { //$NON-NLS-1$
+ return 5;
+ }
+ if (id.equals("Nexus 4")) { //$NON-NLS-1$
+ return 6;
+ }
+ if (id.equals("Nexus 7 2013")) { //$NON-NLS-1$
+ return 7;
+ }
+ if (id.equals("Nexus 5")) { //$NON-NLS-1$
+ return 8;
+ }
+ if (id.equals("Nexus 9")) { //$NON-NLS-1$
+ return 9;
+ }
+ if (id.equals("Nexus 6")) { //$NON-NLS-1$
+ return 10;
+ }
+ if (id.equals("Nexus 6P")) { //$NON-NLS-1$
+ return 11;
+ }
+ if (id.equals("Nexus 5X")) { //$NON-NLS-1$
+ return 12;
+ }
+
+ return 100; // devices released in the future?
+ }
+
+ /**
+ * Sorts the given list of Nexus devices according to rank
+ * @param list the list to sort
+ */
+ public static void sortNexusList(@NonNull List<Device> list) {
+ Collections.sort(list, new Comparator<Device>() {
+ @Override
+ public int compare(Device device1, Device device2) {
+ // Descending order of age
+ return nexusRank(device2) - nexusRank(device1);
+ }
+ });
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java b/sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java
rename to sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/rendering/RenderParamsFlags.java b/sdk-common/src/main/java/com/android/ide/common/rendering/RenderParamsFlags.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/rendering/RenderParamsFlags.java
rename to sdk-common/src/main/java/com/android/ide/common/rendering/RenderParamsFlags.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityException.java b/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityException.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityException.java
rename to sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityException.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityManager.java b/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityManager.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityManager.java
rename to sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityManager.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/rendering/StaticRenderSession.java b/sdk-common/src/main/java/com/android/ide/common/rendering/StaticRenderSession.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/rendering/StaticRenderSession.java
rename to sdk-common/src/main/java/com/android/ide/common/rendering/StaticRenderSession.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/rendering/legacy/ILegacyPullParser.java b/sdk-common/src/main/java/com/android/ide/common/rendering/legacy/ILegacyPullParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/rendering/legacy/ILegacyPullParser.java
rename to sdk-common/src/main/java/com/android/ide/common/rendering/legacy/ILegacyPullParser.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java b/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java
new file mode 100644
index 0000000..53c3dfe
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.repository;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class represents a maven coordinate and allows for comparison at any level.
+ * <p>
+ * This class does not directly implement {@link java.lang.Comparable}; instead,
+ * you should use one of the specific {@link java.util.Comparator} constants based
+ * on what type of ordering you need.
+ */
+public class GradleCoordinate {
+ private static final String NONE = "NONE";
+
+ /**
+ * Maven coordinates take the following form: groupId:artifactId:packaging:classifier:version
+ * where
+ * groupId is dot-notated alphanumeric
+ * artifactId is the name of the project
+ * packaging is optional and is jar/war/pom/aar/etc
+ * classifier is optional and provides filtering context
+ * version uniquely identifies a version.
+ *
+ * We only care about coordinates of the following form: groupId:artifactId:revision
+ * where revision is a series of '.' separated numbers optionally terminated by a '+' character.
+ */
+
+ /**
+ * List taken from <a href="http://maven.apache.org/pom.html#Maven_Coordinates">http://maven.apache.org/pom.html#Maven_Coordinates</a>
+ */
+ public enum ArtifactType {
+ POM("pom"),
+ JAR("jar"),
+ MAVEN_PLUGIN("maven-plugin"),
+ EJB("ejb"),
+ WAR("war"),
+ EAR("ear"),
+ RAR("rar"),
+ PAR("par"),
+ AAR("aar");
+
+ private final String mId;
+
+ ArtifactType(String id) {
+ mId = id;
+ }
+
+ @Nullable
+ public static ArtifactType getArtifactType(@Nullable String name) {
+ if (name != null) {
+ for (ArtifactType type : ArtifactType.values()) {
+ if (type.mId.equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mId;
+ }
+ }
+
+ public static final String PREVIEW_ID = "rc";
+
+ /**
+ * A single component of a revision number: either a number, a string or a list of
+ * components separated by dashes.
+ */
+ public abstract static class RevisionComponent implements Comparable<RevisionComponent> {
+ public abstract int asInteger();
+ public abstract boolean isPreview();
+ }
+
+ public static class NumberComponent extends RevisionComponent {
+ private final int mNumber;
+
+ public NumberComponent(int number) {
+ mNumber = number;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(mNumber);
+ }
+
+ @Override
+ public int asInteger() {
+ return mNumber;
+ }
+
+ @Override
+ public boolean isPreview() {
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof NumberComponent && ((NumberComponent) o).mNumber == mNumber;
+ }
+
+ @Override
+ public int hashCode() {
+ return mNumber;
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ if (o instanceof NumberComponent) {
+ return mNumber - ((NumberComponent) o).mNumber;
+ }
+ if (o instanceof StringComponent) {
+ return 1;
+ }
+ if (o instanceof ListComponent) {
+ return 1; // 1.0.x > 1-1
+ }
+ return 0;
+ }
+ }
+
+ /**
+ * Like NumberComponent, but used for numeric strings that have leading zeroes which
+ * we must preserve
+ */
+ public static class PaddedNumberComponent extends NumberComponent {
+ private final String mString;
+
+ public PaddedNumberComponent(int number, String string) {
+ super(number);
+ mString = string;
+ }
+
+ @Override
+ public String toString() {
+ return mString;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof PaddedNumberComponent
+ && ((PaddedNumberComponent) o).mString.equals(mString);
+ }
+
+ @Override
+ public int hashCode() {
+ return mString.hashCode();
+ }
+ }
+
+ public static class StringComponent extends RevisionComponent {
+ private final String mString;
+
+ public StringComponent(String string) {
+ this.mString = string;
+ }
+
+ @Override
+ public String toString() {
+ return mString;
+ }
+
+ @Override
+ public int asInteger() {
+ return 0;
+ }
+
+ @Override
+ public boolean isPreview() {
+ return mString.startsWith(PREVIEW_ID)
+ || mString.startsWith("alpha")
+ || mString.startsWith("beta")
+ || mString.equals("SNAPSHOT");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof StringComponent && ((StringComponent) o).mString.equals(mString);
+ }
+
+ @Override
+ public int hashCode() {
+ return mString.hashCode();
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ if (o instanceof NumberComponent) {
+ return -1;
+ }
+ if (o instanceof StringComponent) {
+ return mString.compareTo(((StringComponent) o).mString);
+ }
+ if (o instanceof ListComponent) {
+ return -1; // 1-sp < 1-1
+ }
+ return 0;
+ }
+ }
+
+ private static class PlusComponent extends RevisionComponent {
+ @Override
+ public String toString() {
+ return "+";
+ }
+
+ @Override
+ public int asInteger() {
+ return PLUS_REV_VALUE;
+ }
+
+ @Override
+ public boolean isPreview() {
+ return false;
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ throw new UnsupportedOperationException(
+ "Please use a specific comparator that knows how to handle +");
+ }
+ }
+
+ /**
+ * A list of components separated by dashes.
+ */
+ public static class ListComponent extends RevisionComponent {
+ private final List<RevisionComponent> mItems = new ArrayList<RevisionComponent>();
+ private boolean mClosed = false;
+
+ public static ListComponent of(RevisionComponent... components) {
+ ListComponent result = new ListComponent();
+ for (RevisionComponent component : components) {
+ result.add(component);
+ }
+ return result;
+ }
+
+ public void add(RevisionComponent component) {
+ mItems.add(component);
+ }
+
+ @Override
+ public int asInteger() {
+ return 0;
+ }
+
+ @Override
+ public boolean isPreview() {
+ return !mItems.isEmpty() && mItems.get(mItems.size() - 1).isPreview();
+ }
+
+ @Override
+ public int compareTo(RevisionComponent o) {
+ if (o instanceof NumberComponent) {
+ return -1; // 1-1 < 1.0.x
+ }
+ if (o instanceof StringComponent) {
+ return 1; // 1-1 > 1-sp
+ }
+ if (o instanceof ListComponent) {
+ ListComponent rhs = (ListComponent) o;
+ for (int i = 0; i < mItems.size() && i < rhs.mItems.size(); i++) {
+ int rc = mItems.get(i).compareTo(rhs.mItems.get(i));
+ if (rc != 0) return rc;
+ }
+ return mItems.size() - rhs.mItems.size();
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof ListComponent && ((ListComponent) o).mItems.equals(mItems);
+ }
+
+ @Override
+ public int hashCode() {
+ return mItems.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return Joiner.on("-").join(mItems);
+ }
+ }
+
+ public static final PlusComponent PLUS_REV = new PlusComponent();
+ public static final int PLUS_REV_VALUE = -1;
+
+ private final String mGroupId;
+
+ private final String mArtifactId;
+
+ private final ArtifactType mArtifactType;
+
+ private final List<RevisionComponent> mRevisions = new ArrayList<RevisionComponent>(3);
+
+ private static final Pattern MAVEN_PATTERN =
+ Pattern.compile("([\\w\\d\\.-]+):([\\w\\d\\.-]+):([^:@]+)(@[\\w-]+)?");
+
+ /**
+ * Constructor
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
+ @NonNull RevisionComponent... revisions) {
+ this(groupId, artifactId, Arrays.asList(revisions), null);
+ }
+
+ /**
+ * Constructor
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
+ @NonNull int... revisions) {
+ this(groupId, artifactId, createComponents(revisions), null);
+ }
+
+ private static List<RevisionComponent> createComponents(int[] revisions) {
+ List<RevisionComponent> result = new ArrayList<RevisionComponent>(revisions.length);
+ for (int revision : revisions) {
+ if (revision == PLUS_REV_VALUE) {
+ result.add(PLUS_REV);
+ } else {
+ result.add(new NumberComponent(revision));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Constructor
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId,
+ @NonNull List<RevisionComponent> revisions, @Nullable ArtifactType type) {
+ mGroupId = groupId;
+ mArtifactId = artifactId;
+ mRevisions.addAll(revisions);
+
+ mArtifactType = type;
+ }
+
+ /**
+ * Create a GradleCoordinate from a string of the form groupId:artifactId:MajorRevision.MinorRevision.(MicroRevision|+)
+ *
+ * @param coordinateString the string to parse
+ * @return a coordinate object or null if the given string was malformed.
+ */
+ @Nullable
+ public static GradleCoordinate parseCoordinateString(@NonNull String coordinateString) {
+ if (coordinateString == null) {
+ return null;
+ }
+
+ Matcher matcher = MAVEN_PATTERN.matcher(coordinateString);
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ String groupId = matcher.group(1);
+ String artifactId = matcher.group(2);
+ String revision = matcher.group(3);
+ String typeString = matcher.group(4);
+ ArtifactType type = null;
+
+ if (typeString != null) {
+ // Strip off the '@' symbol and try to convert
+ type = ArtifactType.getArtifactType(typeString.substring(1));
+ }
+
+ List<RevisionComponent> revisions = parseRevisionNumber(revision);
+
+ return new GradleCoordinate(groupId, artifactId, revisions, type);
+ }
+
+ public static GradleCoordinate parseVersionOnly(@NonNull String revision) {
+ return new GradleCoordinate(NONE, NONE, parseRevisionNumber(revision), null);
+ }
+
+ @NonNull
+ public static List<RevisionComponent> parseRevisionNumber(@NonNull String revision) {
+ List<RevisionComponent> components = new ArrayList<RevisionComponent>();
+ StringBuilder buffer = new StringBuilder();
+ for (int i = 0; i < revision.length(); i++) {
+ char c = revision.charAt(i);
+ if (c == '.') {
+ flushBuffer(components, buffer, true);
+ } else if (c == '+') {
+ if (buffer.length() > 0) {
+ flushBuffer(components, buffer, true);
+ }
+ components.add(PLUS_REV);
+ break;
+ } else if (c == '-') {
+ flushBuffer(components, buffer, false);
+ int last = components.size() - 1;
+ if (last == -1) {
+ components.add(ListComponent.of(new NumberComponent(0)));
+ } else if (!(components.get(last) instanceof ListComponent)) {
+ components.set(last, ListComponent.of(components.get(last)));
+ }
+ } else {
+ buffer.append(c);
+ }
+ }
+ if (buffer.length() > 0 || components.isEmpty()) {
+ flushBuffer(components, buffer, true);
+ }
+ return components;
+ }
+
+ private static void flushBuffer(List<RevisionComponent> components, StringBuilder buffer,
+ boolean closeList) {
+ RevisionComponent newComponent;
+ if (buffer.length() == 0) {
+ newComponent = new NumberComponent(0);
+ } else {
+ String string = buffer.toString();
+ try {
+ int number = Integer.parseInt(string);
+ if (string.length() > 1 && string.charAt(0) == '0') {
+ newComponent = new PaddedNumberComponent(number, string);
+ } else {
+ newComponent = new NumberComponent(number);
+ }
+ } catch (NumberFormatException e) {
+ newComponent = new StringComponent(string);
+ }
+ }
+ buffer.setLength(0);
+ if (!components.isEmpty() &&
+ components.get(components.size() - 1) instanceof ListComponent) {
+ ListComponent component = (ListComponent) components.get(components.size() - 1);
+ if (!component.mClosed) {
+ component.add(newComponent);
+ if (closeList) {
+ component.mClosed = true;
+ }
+ return;
+ }
+ }
+ components.add(newComponent);
+ }
+
+ @Override
+ public String toString() {
+ String s = String.format(Locale.US, "%s:%s:%s", mGroupId, mArtifactId, getRevision());
+ if (mArtifactType != null) {
+ s += "@" + mArtifactType.toString();
+ }
+ return s;
+ }
+
+ @Nullable
+ public String getGroupId() {
+ return mGroupId;
+ }
+
+ @Nullable
+ public String getArtifactId() {
+ return mArtifactId;
+ }
+
+ @Nullable
+ public ArtifactType getArtifactType() {
+ return mArtifactType;
+ }
+
+ @Nullable
+ public String getId() {
+ if (mGroupId == null || mArtifactId == null) {
+ return null;
+ }
+
+ return String.format("%s:%s", mGroupId, mArtifactId);
+ }
+
+ @Nullable
+ public ArtifactType getType() {
+ return mArtifactType;
+ }
+
+ public boolean acceptsGreaterRevisions() {
+ return mRevisions.get(mRevisions.size() - 1) == PLUS_REV;
+ }
+
+ @NonNull
+ public String getRevision() {
+ StringBuilder revision = new StringBuilder();
+ for (RevisionComponent component : mRevisions) {
+ if (revision.length() > 0) {
+ revision.append('.');
+ }
+ revision.append(component.toString());
+ }
+
+ return revision.toString();
+ }
+
+ public boolean isPreview() {
+ return !mRevisions.isEmpty() && mRevisions.get(mRevisions.size() - 1).isPreview();
+ }
+
+ /**
+ * Returns the major version (X in X.2.3), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
+ * if it is not available
+ */
+ public int getMajorVersion() {
+ return mRevisions.isEmpty() ? Integer.MIN_VALUE : mRevisions.get(0).asInteger();
+ }
+
+ /**
+ * Returns the minor version (X in 1.X.3), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
+ * if it is not available
+ */
+ public int getMinorVersion() {
+ return mRevisions.size() < 2 ? Integer.MIN_VALUE : mRevisions.get(1).asInteger();
+ }
+
+ /**
+ * Returns the major version (X in 1.2.X), which can be {@link #PLUS_REV}, or Integer.MIN_VALUE
+ * if it is not available
+ */
+ public int getMicroVersion() {
+ return mRevisions.size() < 3 ? Integer.MIN_VALUE : mRevisions.get(2).asInteger();
+ }
+
+ /**
+ * Returns true if and only if the given coordinate refers to the same group and artifact.
+ *
+ * @param o the coordinate to compare with
+ * @return true iff the other group and artifact match the group and artifact of this
+ * coordinate.
+ */
+ public boolean isSameArtifact(@NonNull GradleCoordinate o) {
+ return o.mGroupId.equals(mGroupId) && o.mArtifactId.equals(mArtifactId);
+ }
+
+ @Override
+ public boolean equals(@NonNull Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ GradleCoordinate that = (GradleCoordinate) o;
+
+ if (!mRevisions.equals(that.mRevisions)) {
+ return false;
+ }
+ if (!mArtifactId.equals(that.mArtifactId)) {
+ return false;
+ }
+ if (!mGroupId.equals(that.mGroupId)) {
+ return false;
+ }
+ if ((mArtifactType == null) != (that.mArtifactType == null)) {
+ return false;
+ }
+ if (mArtifactType != null && !mArtifactType.equals(that.mArtifactType)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mGroupId.hashCode();
+ result = 31 * result + mArtifactId.hashCode();
+ for (RevisionComponent component : mRevisions) {
+ result = 31 * result + component.hashCode();
+ }
+ if (mArtifactType != null) {
+ result = 31 * result + mArtifactType.hashCode();
+ }
+ return result;
+ }
+
+ /**
+ * Comparator which compares Gradle versions - and treats a + version as lower
+ * than a specific number in the same place. This is typically useful when trying
+ * to for example order coordinates by "most specific".
+ */
+ public static final Comparator<GradleCoordinate> COMPARE_PLUS_LOWER =
+ new GradleCoordinateComparator(-1);
+
+ /**
+ * Comparator which compares Gradle versions - and treats a + version as higher
+ * than a specific number. This is typically useful when seeing if a dependency
+ * is met, e.g. if you require version 0.7.3, comparing it with 0.7.+ would consider
+ * 0.7.+ higher and therefore satisfying the version requirement.
+ */
+ public static final Comparator<GradleCoordinate> COMPARE_PLUS_HIGHER =
+ new GradleCoordinateComparator(1);
+
+ private static class GradleCoordinateComparator implements Comparator<GradleCoordinate> {
+ private final int mPlusResult;
+
+ private GradleCoordinateComparator(int plusResult) {
+ mPlusResult = plusResult;
+ }
+
+ @Override
+ public int compare(@NonNull GradleCoordinate a, @NonNull GradleCoordinate b) {
+ // Make sure we're comparing apples to apples. If not, compare artifactIds
+ if (!a.isSameArtifact(b)) {
+ return a.mArtifactId.compareTo(b.mArtifactId);
+ }
+
+ int sizeA = a.mRevisions.size();
+ int sizeB = b.mRevisions.size();
+ int common = Math.min(sizeA, sizeB);
+ for (int i = 0; i < common; ++i) {
+ RevisionComponent revision1 = a.mRevisions.get(i);
+ if (revision1 instanceof PlusComponent) return mPlusResult;
+ RevisionComponent revision2 = b.mRevisions.get(i);
+ if (revision2 instanceof PlusComponent) return -mPlusResult;
+ int delta = revision1.compareTo(revision2);
+ if (delta != 0) {
+ return delta;
+ }
+ }
+ if (sizeA == sizeB) {
+ return 0;
+ } else {
+ // Treat X.0 and X.0.0 as equal
+ List<RevisionComponent> revisionList;
+ int returnValueIfNonZero;
+ int from;
+ int to;
+ if (sizeA < sizeB) {
+ revisionList = b.mRevisions;
+ from = sizeA;
+ to = sizeB;
+ returnValueIfNonZero = -1;
+ } else {
+ revisionList = a.mRevisions;
+ from = sizeB;
+ to = sizeA;
+ returnValueIfNonZero = 1;
+ }
+ for (int i = from; i < to; ++i) {
+ RevisionComponent revision = revisionList.get(i);
+ if (revision instanceof NumberComponent) {
+ if (revision.asInteger() != 0) {
+ return returnValueIfNonZero;
+ }
+ } else {
+ return returnValueIfNonZero;
+ }
+ }
+ return 0;
+ }
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/repository/GradleVersion.java b/sdk-common/src/main/java/com/android/ide/common/repository/GradleVersion.java
new file mode 100644
index 0000000..3e5cde2
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/repository/GradleVersion.java
@@ -0,0 +1,410 @@
+/*
+ * 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.ide.common.repository;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Supports versions in the given formats: <ul> <li>major (e.g. 1)</li> <li>major.minor (e.g.
+ * 1.0)</li> <li>major.minor.micro (e.g. 1.1.1)</li> </ul> A version can also be a "preview" (e.g.
+ * 1-alpha1, 1.0.0-rc2) or an unreleased version (or "snapshot") (e.g. 1-SNAPSHOT,
+ * 1.0.0-alpha1-SNAPSHOT).
+ */
+public class GradleVersion implements Comparable<GradleVersion>, Serializable {
+ private static final String PLUS = "+";
+
+ private static final Pattern PREVIEW_PATTERN = Pattern.compile("([a-zA-z]+)[\\-]?([\\d]+)?");
+
+ private final String mRawValue;
+
+ @NonNull
+ private final VersionSegment mMajorSegment;
+
+ @Nullable
+ private final VersionSegment mMinorSegment;
+
+ @Nullable
+ private final VersionSegment mMicroSegment;
+
+ private final int mPreview;
+
+ @Nullable
+ private final String mPreviewType;
+
+ private final boolean mSnapshot;
+
+ @NonNull
+ private final List<VersionSegment> mAdditionalSegments;
+
+ /**
+ * Parses the given version. This method does the same as {@link #parse(String)}, but it does
+ * not throw exceptions if the given value does not conform with any of the supported version
+ * formats.
+ *
+ * @param value the version to parse.
+ * @return the created {@code Version} object, or {@code null} if the given value does not
+ * conform with any of the supported version formats.
+ */
+ @Nullable
+ public static GradleVersion tryParse(@NonNull String value) {
+ try {
+ return parse(value);
+ } catch (RuntimeException ignored) {
+ }
+ return null;
+ }
+
+ /**
+ * Parses the given version.
+ *
+ * @param value the version to parse.
+ * @return the created {@code Version} object.
+ * @throws IllegalArgumentException if the given value does not conform with any of the
+ * supported version formats.
+ */
+ @NonNull
+ public static GradleVersion parse(@NonNull String value) {
+ String version = value;
+ String qualifiers = null;
+ char dash = '-';
+ int dashIndex = value.indexOf(dash);
+ if (dashIndex != -1) {
+ if (dashIndex < value.length() - 1) {
+ qualifiers = value.substring(dashIndex + 1);
+ }
+ version = value.substring(0, dashIndex);
+ }
+
+ try {
+ List<VersionSegment> parsedVersionSegments = splitSegments(version);
+ int segmentCount = parsedVersionSegments.size();
+
+ VersionSegment majorSegment;
+ VersionSegment minorSegment = null;
+ VersionSegment microSegment = null;
+
+ List<VersionSegment> additionalSegments = Lists.newArrayList();
+ if (segmentCount > 0) {
+ majorSegment = parsedVersionSegments.get(0);
+ if (segmentCount > 1) {
+ minorSegment = parsedVersionSegments.get(1);
+ }
+ if (segmentCount >= 3) {
+ microSegment = parsedVersionSegments.get(2);
+ }
+ if (segmentCount > 3) {
+ additionalSegments.addAll(parsedVersionSegments.subList(3, segmentCount));
+ }
+
+ int preview = 0;
+ String previewType = null;
+ boolean snapshot = false;
+
+ if (qualifiers != null) {
+ String snapshotQualifier = "SNAPSHOT";
+ if (qualifiers.equalsIgnoreCase(snapshotQualifier)) {
+ snapshot = true;
+ qualifiers = null;
+ }
+ else {
+ // find and remove "SNAPSHOT" at the end of the qualifiers.
+ int lastDashIndex = qualifiers.lastIndexOf(dash);
+ if (lastDashIndex != -1) {
+ String mayBeSnapshot = qualifiers.substring(lastDashIndex + 1);
+ if (mayBeSnapshot.equalsIgnoreCase(snapshotQualifier)) {
+ snapshot = true;
+ qualifiers = qualifiers.substring(0, lastDashIndex);
+ }
+ }
+ }
+ if (!isNullOrEmpty(qualifiers)) {
+ Matcher matcher = PREVIEW_PATTERN.matcher(qualifiers);
+ if (matcher.matches()) {
+ previewType = matcher.group(1);
+ if (matcher.groupCount() == 2) {
+ preview = Integer.parseInt(matcher.group(2));
+ }
+ } else {
+ throw parsingFailure(value);
+ }
+ }
+ }
+ return new GradleVersion(value, majorSegment, minorSegment, microSegment,
+ additionalSegments, preview, previewType, snapshot);
+ }
+ } catch (NumberFormatException e) {
+ throw parsingFailure(value, e);
+ }
+ throw parsingFailure(value);
+ }
+
+ @NonNull
+ private static List<VersionSegment> splitSegments(@NonNull String version) {
+ Iterable<String> segments = Splitter.on('.').split(version);
+ List<VersionSegment> parsedSegments = Lists.newArrayListWithCapacity(3);
+
+ for (String segment : segments) {
+ parsedSegments.addAll(parseSegment(segment));
+ }
+
+ return parsedSegments;
+ }
+
+ @NonNull
+ private static List<VersionSegment> parseSegment(@NonNull String text) {
+ int length = text.length();
+ if (length > 1 && text.endsWith(PLUS)) {
+ // Segment has a number and a '+' (e.g. second segment in '2.1+')
+ List<VersionSegment> segments = Lists.newArrayListWithCapacity(2);
+
+ // We need to split '1+' into 2 segments: '1' and '+'
+ segments.add(new VersionSegment(text.substring(0, length - 1)));
+ segments.add(new VersionSegment(PLUS));
+ return segments;
+ }
+ return Collections.singletonList(new VersionSegment(text));
+ }
+
+ @NonNull
+ private static IllegalArgumentException parsingFailure(@NonNull String value) {
+ return parsingFailure(value, null);
+ }
+
+ @NonNull
+ private static IllegalArgumentException parsingFailure(@NonNull String value,
+ @Nullable Throwable cause) {
+ return new IllegalArgumentException(String.format("'%1$s' is not a valid version", value),
+ cause);
+ }
+
+ public GradleVersion(int major, int minor, int micro) {
+ this((major + "." + minor + "." + micro), new VersionSegment(major),
+ new VersionSegment(minor), new VersionSegment(micro),
+ Collections.<VersionSegment>emptyList(), 0, null, false);
+ }
+
+ private GradleVersion(@NonNull String rawValue,
+ @NonNull VersionSegment majorSegment,
+ @Nullable VersionSegment minorSegment,
+ @Nullable VersionSegment microSegment,
+ @NonNull List<VersionSegment> additionalSegments,
+ int preview,
+ @Nullable String previewType,
+ boolean snapshot) {
+ mRawValue = rawValue;
+ mMajorSegment = majorSegment;
+ mMinorSegment = minorSegment;
+ mMicroSegment = microSegment;
+ mAdditionalSegments = ImmutableList.copyOf(additionalSegments);
+ mPreview = preview;
+ mPreviewType = previewType;
+ mSnapshot = snapshot;
+ }
+
+ public int getMajor() {
+ return valueOf(mMajorSegment);
+ }
+
+ @NonNull
+ public VersionSegment getMajorSegment() {
+ return mMajorSegment;
+ }
+
+ public int getMinor() {
+ return valueOf(mMinorSegment);
+ }
+
+ @Nullable
+ public VersionSegment getMinorSegment() {
+ return mMinorSegment;
+ }
+
+ public int getMicro() {
+ return valueOf(mMicroSegment);
+ }
+
+ private static int valueOf(@Nullable VersionSegment segment) {
+ return segment != null ? segment.getValue() : 0;
+ }
+
+ @Nullable
+ public VersionSegment getMicroSegment() {
+ return mMicroSegment;
+ }
+
+ public int getPreview() {
+ return mPreview;
+ }
+
+ @Nullable
+ public String getPreviewType() {
+ return mPreviewType;
+ }
+
+ public boolean isSnapshot() {
+ return mSnapshot;
+ }
+
+ public int compareTo(@NonNull String version) {
+ return compareTo(parse(version));
+ }
+
+ @Override
+ public int compareTo(@NonNull GradleVersion version) {
+ return compareTo(version, false);
+ }
+
+ public int compareIgnoringQualifiers(@NonNull String version) {
+ return compareIgnoringQualifiers(parse(version));
+ }
+
+ public int compareIgnoringQualifiers(@NonNull GradleVersion version) {
+ return compareTo(version, true);
+ }
+
+ private int compareTo(@NonNull GradleVersion version, boolean ignoreQualifiers) {
+ int delta = getMajor() - version.getMajor();
+ if (delta != 0) {
+ return delta;
+ }
+ delta = getMinor() - version.getMinor();
+ if (delta != 0) {
+ return delta;
+ }
+ delta = getMicro() - version.getMicro();
+ if (delta != 0) {
+ return delta;
+ }
+ if (!ignoreQualifiers) {
+ if (mPreviewType == null) {
+ if (version.mPreviewType != null) {
+ return 1;
+ }
+ } else if (version.mPreviewType == null) {
+ return -1;
+ } else {
+ delta = mPreviewType.compareToIgnoreCase(version.mPreviewType);
+ }
+ if (delta != 0) {
+ return delta;
+ }
+
+ delta = mPreview - version.mPreview;
+ if (delta != 0) {
+ return delta;
+ }
+ delta = mSnapshot == version.mSnapshot ? 0 : (mSnapshot ? -1 : 1);
+ }
+ return delta;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ GradleVersion that = (GradleVersion) o;
+ return compareTo(that) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mMajorSegment, mMinorSegment, mMicroSegment, mPreview, mPreviewType,
+ mSnapshot);
+ }
+
+ @Override
+ public String toString() {
+ return mRawValue;
+ }
+
+ /**
+ * @return version segments present after the "micro" segments. For example, parsing "1.2.3.4.5"
+ * will result in "4" and "5" to be considered "additional" version segments.
+ */
+ @NonNull
+ public List<VersionSegment> getAdditionalSegments() {
+ return mAdditionalSegments;
+ }
+
+ public static class VersionSegment implements Serializable {
+ @NonNull
+ private final String mText;
+
+ private final int mValue;
+
+ VersionSegment(int value) {
+ mText = String.valueOf(value);
+ mValue = value;
+ }
+
+ VersionSegment(@NonNull String text) {
+ mText = text;
+ mValue = PLUS.equals(text) ? Integer.MAX_VALUE : Integer.parseInt(text);
+ }
+
+ @NonNull
+ public String getText() {
+ return mText;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public boolean acceptsGreaterValue() {
+ return PLUS.equals(mText);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ VersionSegment that = (VersionSegment) o;
+ return Objects.equal(mText, that.mText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mText);
+ }
+
+ @Override
+ public String toString() {
+ return mText;
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/repository/ResourceVisibilityLookup.java b/sdk-common/src/main/java/com/android/ide/common/repository/ResourceVisibilityLookup.java
new file mode 100644
index 0000000..d0719f1
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/repository/ResourceVisibilityLookup.java
@@ -0,0 +1,593 @@
+/*
+ * 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.ide.common.repository;
+
+import static com.android.SdkConstants.FN_RESOURCE_TEXT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.Variant;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.ResourceType;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class which provides information about whether Android resources for a given library are
+ * public or private.
+ */
+public abstract class ResourceVisibilityLookup {
+ /**
+ * Returns true if the given resource is private
+ *
+ * @param type the type of the resource
+ * @param name the resource field name of the resource (in other words, for
+ * style Theme:Variant.Cls the name would be Theme_Variant_Cls
+ * @return true if the given resource is private
+ */
+ public abstract boolean isPrivate(
+ @NonNull ResourceType type,
+ @NonNull String name);
+
+ /**
+ * Returns true if the given resource is private
+ *
+ * @param url the resource URL
+ * @return true if the given resource is private
+ */
+ public boolean isPrivate(@NonNull ResourceUrl url) {
+ assert !url.framework; // Framework resources are not part of the library
+ return isPrivate(url.type, url.name);
+ }
+
+ /**
+ * For a private resource, return the {@link AndroidLibrary} that the resource was defined as
+ * private in
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource
+ * @return the library which defines the resource as private
+ */
+ @Nullable
+ public abstract AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name);
+
+ /** Returns true if this repository does not declare any resources to be private */
+ public abstract boolean isEmpty();
+
+ /**
+ * Creates a {@link ResourceVisibilityLookup} for a given library.
+ * <p>
+ * NOTE: The {@link Provider} class can be used to share/cache {@link ResourceVisibilityLookup}
+ * instances, e.g. when you have library1 and library2 each referencing libraryBase, the {@link
+ * Provider} will ensure that a the libraryBase data is shared.
+ *
+ * @param library the library
+ * @return a corresponding {@link ResourceVisibilityLookup}
+ */
+ @NonNull
+ public static ResourceVisibilityLookup create(@NonNull AndroidLibrary library) {
+ return new LibraryResourceVisibility(library, new SymbolProvider());
+ }
+
+ /**
+ * Creates a {@link ResourceVisibilityLookup} for the set of libraries.
+ * <p>
+ * NOTE: The {@link Provider} class can be used to share/cache {@link ResourceVisibilityLookup}
+ * instances, e.g. when you have library1 and library2 each referencing libraryBase, the {@link
+ * Provider} will ensure that a the libraryBase data is shared.
+ *
+ * @param libraries the list of libraries
+ * @param provider an optional manager instance for caching of individual libraries, if any
+ * @return a corresponding {@link ResourceVisibilityLookup}
+ */
+ @NonNull
+ public static ResourceVisibilityLookup create(@NonNull List<AndroidLibrary> libraries,
+ @Nullable Provider provider) {
+ List<ResourceVisibilityLookup> list = Lists.newArrayListWithExpectedSize(libraries.size());
+ for (AndroidLibrary library : libraries) {
+ ResourceVisibilityLookup v = provider != null ? provider.get(library)
+ : new LibraryResourceVisibility(library, new SymbolProvider());
+ if (!v.isEmpty()) {
+ list.add(v);
+ }
+ }
+ return new MultipleLibraryResourceVisibility(list);
+ }
+
+ public static final ResourceVisibilityLookup NONE = new ResourceVisibilityLookup() {
+ @Override
+ public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
+ return null;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+ };
+
+ /**
+ * Create a key that can be used to identify a library for a specific version.
+ * We can't use {@link AndroidLibrary} directly, because (due to a lot of magic in the
+ * Gradle model) we end up with separate instances of {@link AndroidLibrary} when a single
+ * library appears more than once, such as a downstream dependency reachable from multiple
+ * upstream libraries.
+ *
+ * @param library the library to produce a map key for
+ * @return a suitable key to use with {@link Map}
+ */
+ private static String getMapKey(@NonNull AndroidLibrary library) {
+ MavenCoordinates c = library.getResolvedCoordinates();
+ if (c != null) {
+ return c.getGroupId() + ':' + c.getArtifactId() + ':' + c.getVersion();
+ } else {
+ return library.getBundle().getPath();
+ }
+ }
+
+ private static String getMapKey(@NonNull AndroidArtifact artifact) {
+ return artifact.getApplicationId();
+ }
+
+ private static String getMapKey(@NonNull Variant variant) {
+ return getMapKey(variant.getMainArtifact()) + '-' + variant.getName();
+ }
+
+ /**
+ * Given a library, return all the libraries it depends on, transitively, with each library
+ * appearing only once
+ *
+ * @param library the library to compute transitive dependencies for
+ * @return the list of libraries the given library depends on
+ */
+ private static List<AndroidLibrary> getTransitiveDependencies(
+ @NonNull AndroidLibrary library) {
+ List<AndroidLibrary> result = Lists.newArrayList();
+ for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+ addLibraries(result, new HashSet<String>(), dependency);
+ }
+
+ return result;
+ }
+
+ /** Adds unique libraries the given library depends on into the given result */
+ private static void addLibraries(@NonNull List<AndroidLibrary> result,
+ @NonNull Set<String> seen, @NonNull AndroidLibrary library) {
+ String key = getMapKey(library);
+ if (seen.contains(key)) {
+ return;
+ }
+ seen.add(key);
+ result.add(library);
+
+ for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+ addLibraries(result, seen, dependency);
+ }
+ }
+
+ /** Searches multiple libraries */
+ private static class MultipleLibraryResourceVisibility extends ResourceVisibilityLookup {
+
+ private final List<ResourceVisibilityLookup> mRepositories;
+
+ public MultipleLibraryResourceVisibility(List<ResourceVisibilityLookup> repositories) {
+ mRepositories = repositories;
+ }
+
+ // It's anticipated that these methods will be called a lot (e.g. in inner loops
+ // iterating over all resources matching code completion etc) so since we know
+ // that our list has random access, avoid creating iterators here
+ @SuppressWarnings("ForLoopReplaceableByForEach")
+ @Override
+ public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
+ for (int i = 0, n = mRepositories.size(); i < n; i++) {
+ ResourceVisibilityLookup lookup = mRepositories.get(i);
+ if (lookup.isPrivate(type, name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("ForLoopReplaceableByForEach")
+ @Override
+ public boolean isEmpty() {
+ for (int i = 0, n = mRepositories.size(); i < n; i++) {
+ if (!mRepositories.get(i).isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("ForLoopReplaceableByForEach")
+ @Nullable
+ @Override
+ public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
+ for (int i = 0, n = mRepositories.size(); i < n; i++) {
+ ResourceVisibilityLookup r = mRepositories.get(i);
+ if (r.isPrivate(type, name)) {
+ return r.getPrivateIn(type, name);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mRepositories.toString();
+ }
+ }
+
+ /**
+ * Provider which keeps a set of {@link ResourceVisibilityLookup} instances around for
+ * repeated queries, including from different libraries that may share dependencies
+ */
+ public static class Provider {
+ /**
+ * We store lookup instances for multiple separate types of keys here:
+ * {@link AndroidLibrary}, {@link AndroidArtifact}, and {@link Variant}
+ */
+ private Map<Object, ResourceVisibilityLookup> mInstances = Maps.newHashMap();
+
+ /** R.txt lookup */
+ private SymbolProvider mSymbols = new SymbolProvider();
+
+ /**
+ * Looks up a (possibly cached) {@link ResourceVisibilityLookup} for the given {@link
+ * AndroidLibrary}
+ *
+ * @param library the library
+ * @return the corresponding {@link ResourceVisibilityLookup}
+ */
+ @NonNull
+ public ResourceVisibilityLookup get(@NonNull AndroidLibrary library) {
+ String key = getMapKey(library);
+ ResourceVisibilityLookup visibility = mInstances.get(key);
+ if (visibility == null) {
+ visibility = new LibraryResourceVisibility(library, mSymbols);
+ if (visibility.isEmpty()) {
+ visibility = NONE;
+ }
+ List<? extends AndroidLibrary> dependsOn = library.getLibraryDependencies();
+ if (!dependsOn.isEmpty()) {
+ List<ResourceVisibilityLookup> list =
+ Lists.newArrayListWithExpectedSize(dependsOn.size() + 1);
+ list.add(visibility);
+ for (AndroidLibrary d : dependsOn) {
+ ResourceVisibilityLookup v = get(d);
+ if (!v.isEmpty()) {
+ list.add(v);
+ }
+ }
+ if (list.size() > 1) {
+ visibility = new MultipleLibraryResourceVisibility(list);
+ }
+ }
+ mInstances.put(key, visibility);
+ }
+ return visibility;
+ }
+
+ /**
+ * Looks up a (possibly cached) {@link ResourceVisibilityLookup} for the given {@link
+ * AndroidArtifact}
+ *
+ * @param artifact the artifact
+ * @return the corresponding {@link ResourceVisibilityLookup}
+ */
+ @NonNull
+ public ResourceVisibilityLookup get(@NonNull AndroidArtifact artifact) {
+ String key = getMapKey(artifact);
+ ResourceVisibilityLookup visibility = mInstances.get(key);
+ if (visibility == null) {
+ Collection<AndroidLibrary> dependsOn = artifact.getDependencies().getLibraries();
+ List<ResourceVisibilityLookup> list =
+ Lists.newArrayListWithExpectedSize(dependsOn.size() + 1);
+ for (AndroidLibrary d : dependsOn) {
+ ResourceVisibilityLookup v = get(d);
+ if (!v.isEmpty()) {
+ list.add(v);
+ }
+ }
+ int size = list.size();
+ visibility = size == 0 ? NONE : size == 1 ? list.get(0) : new MultipleLibraryResourceVisibility(list);
+ mInstances.put(key, visibility);
+ }
+ return visibility;
+ }
+
+ /**
+ * Returns true if the given Gradle model is compatible with public resources.
+ * (Older models than 1.3 will throw exceptions if we attempt to for example
+ * query the public resource file location.
+ *
+ * @param project the project to check
+ * @return true if the model is recent enough to support resource visibility queries
+ */
+ public static boolean isVisibilityAwareModel(@NonNull AndroidProject project) {
+ String modelVersion = project.getModelVersion();
+ // getApiVersion doesn't work prior to 1.2, and API level must be at least 3
+ return !(modelVersion.startsWith("1.0") || modelVersion.startsWith("1.1"))
+ && project.getApiVersion() >= 3;
+ }
+
+ /**
+ * Looks up a (possibly cached) {@link ResourceVisibilityLookup} for the given {@link
+ * AndroidArtifact}
+ *
+ * @param project the project
+ * @return the corresponding {@link ResourceVisibilityLookup}
+ */
+ @NonNull
+ public ResourceVisibilityLookup get(
+ @NonNull AndroidProject project,
+ @NonNull Variant variant) {
+ String key = getMapKey(variant);
+ ResourceVisibilityLookup visibility = mInstances.get(key);
+ if (visibility == null) {
+ if (isVisibilityAwareModel(project)) {
+ AndroidArtifact artifact = variant.getMainArtifact();
+ visibility = get(artifact);
+ } else {
+ visibility = NONE;
+ }
+ mInstances.put(key, visibility);
+ }
+ return visibility;
+ }
+ }
+
+ /** Visibility data for a single library */
+ private static class LibraryResourceVisibility extends ResourceVisibilityLookup {
+ private final AndroidLibrary mLibrary;
+
+ /**
+ * A map from name to resource types for all resources known to this library. This
+ * is used to make sure that when the {@link #isPrivate(ResourceType, String)} query method
+ * is called, it can tell the difference between a resource implicitly private by not being
+ * declared as public and a resource unknown to this library (e.g. defined by a different
+ * library or the user's own project resources.)
+ */
+ private final Multimap<String, ResourceType> mAll;
+
+ /**
+ * A map of explicitly exposed resources
+ */
+ private final Multimap<String, ResourceType> mPublic;
+
+ private LibraryResourceVisibility(@NonNull AndroidLibrary library,
+ @NonNull SymbolProvider symbols) {
+ mLibrary = library;
+
+ mPublic = computeVisibilityMap();
+ //noinspection VariableNotUsedInsideIf
+ if (mPublic != null) {
+ mAll = symbols.getSymbols(library);
+ } else {
+ mAll = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getMapKey(mLibrary);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mPublic == null;
+ }
+
+ @Nullable
+ @Override
+ public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
+ if (isPrivate(type, name)) {
+ return mLibrary;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a map from name to applicable resource types where the presence of the type+name
+ * combination means that the corresponding resource is explicitly public.
+ *
+ * If the result is null, there is no {@code public.txt} definition for this library, so all
+ * resources should be taken to be public.
+ *
+ * @return a map from name to resource type for public resources in this library
+ */
+ @Nullable
+ private Multimap<String, ResourceType> computeVisibilityMap() {
+ File publicResources = mLibrary.getPublicResources();
+ if (!publicResources.exists()) {
+ return null;
+ }
+
+ try {
+ List<String> lines = Files.readLines(publicResources, Charsets.UTF_8);
+ Multimap<String, ResourceType> result = ArrayListMultimap.create(lines.size(), 2);
+ for (String line : lines) {
+ // These files are written by code in MergedResourceWriter#postWriteAction
+ // Format for each line: <type><space><name>\n
+ // Therefore, we don't expect/allow variations in the format (we don't
+ // worry about extra spaces needing to be trimmed etc)
+ int index = line.indexOf(' ');
+ if (index == -1 || line.isEmpty()) {
+ continue;
+ }
+
+ String typeString = line.substring(0, index);
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type == null) {
+ // This could in theory happen if in the future a new ResourceType is
+ // introduced, and a newer version of the Gradle build system writes the
+ // name of this type into the public.txt file, and an older version of
+ // the IDE then attempts to read it. Just skip these symbols.
+ continue;
+ }
+ String name = line.substring(index + 1);
+ result.put(name, type);
+ }
+ return result;
+ } catch (IOException ignore) {
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
+ //noinspection SimplifiableIfStatement
+ if (mPublic == null) {
+ // No public definitions: Everything assumed to be public
+ return false;
+ }
+
+ //noinspection SimplifiableIfStatement
+ if (!mAll.containsEntry(name, type)) {
+ // Don't respond to resource URLs that are not part of this project
+ // since we won't have private information on them
+ return false;
+ }
+ return !mPublic.containsEntry(name, type);
+ }
+ }
+
+ /**
+ * Class which provides resource symbols (from R.txt) for a given library, while
+ * (a) caching across multiple lookups, and (b) removing symbols from upstream
+ * dependencies.
+ * <p>
+ * These are referred to as "symbols" to map the Gradle plugin terminology,
+ * e.g. "LibraryBundle#getSymbolFile", the SymbolLoader processor, etc.
+ */
+ @VisibleForTesting
+ static class SymbolProvider {
+ /** Cache from library map keys to corresponding name-to-resource type maps */
+ private Map<String, Multimap<String, ResourceType>> mCache = Maps.newHashMap();
+
+ /**
+ * Returns a map from name to resource types for all resources known to this library.
+ * Note that this will *exclude* any resources that are also declared by a dependency
+ * of this library; this means that at the end we'll hopefully only list the actual
+ * resources declared locally in this library.
+ *
+ * @return a map from name to resource type for all resources in this library
+ */
+ @VisibleForTesting
+ @NonNull
+ Multimap<String, ResourceType> getSymbols(@NonNull AndroidLibrary library) {
+ String mapKey = getMapKey(library);
+ Multimap<String, ResourceType> map = mCache.get(mapKey);
+ if (map != null) {
+ return map;
+ }
+
+ // getSymbolFile() is not defined in AndroidLibrary, only in the subclass LibraryBundle
+ File symbolFile = new File(library.getPublicResources().getParentFile(),
+ FN_RESOURCE_TEXT);
+ if (!symbolFile.exists()) {
+ Multimap<String, ResourceType> empty = ImmutableListMultimap.of();
+ mCache.put(mapKey, empty);
+ return empty;
+ }
+
+ try {
+ List<String> lines = Files.readLines(symbolFile, Charsets.UTF_8);
+ Multimap<String, ResourceType> result = ArrayListMultimap.create(lines.size(), 2);
+
+ ResourceType previousType = null;
+ String previousTypeString = "";
+ int lineIndex = 1;
+ final int count = lines.size();
+ for (; lineIndex <= count; lineIndex++) {
+ String line = lines.get(lineIndex - 1);
+
+ if (line.startsWith("int ")) { // not int[] definitions for styleables
+ // format is "int <type> <class> <name> <value>"
+ int typeStart = 4;
+ int typeEnd = line.indexOf(' ', typeStart);
+
+ // Items are sorted by type, so we can avoid looping over types in
+ // ResourceType.getEnum() for each line by sharing type in each section
+ String typeString = line.substring(typeStart, typeEnd);
+ ResourceType type;
+ if (typeString.equals(previousTypeString)) {
+ type = previousType;
+ } else {
+ type = ResourceType.getEnum(typeString);
+ previousTypeString = typeString;
+ previousType = type;
+ }
+ if (type == null) { // some newly introduced type
+ continue;
+ }
+
+ int nameStart = typeEnd + 1;
+ int nameEnd = line.indexOf(' ', nameStart);
+ String name = line.substring(nameStart, nameEnd);
+ result.put(name, type);
+ }
+ }
+
+ if (!result.isEmpty()) {
+ // Subtract out symbols from any dependencies; we don't want to double
+ // count those
+ for (AndroidLibrary dependency : getTransitiveDependencies(library)) {
+ Multimap<String, ResourceType> imported = getSymbols(dependency);
+ if (!imported.isEmpty()) {
+ for (Map.Entry<String, ResourceType> entry : imported.entries()) {
+ result.remove(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ mCache.put(mapKey, result);
+ return result;
+ } catch (IOException ignore) {
+ Multimap<String, ResourceType> empty = ImmutableListMultimap.of();
+ mCache.put(mapKey, empty);
+ return empty;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/main/java/com/android/ide/common/repository/SdkMavenRepository.java b/sdk-common/src/main/java/com/android/ide/common/repository/SdkMavenRepository.java
new file mode 100644
index 0000000..4ba2349
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/repository/SdkMavenRepository.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.repository;
+
+import static com.android.SdkConstants.FD_EXTRAS;
+import static com.android.SdkConstants.FD_M2_REPOSITORY;
+import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
+import static java.io.File.separator;
+import static java.io.File.separatorChar;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.ConsoleProgressIndicator;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RepoManager;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@linkplain com.android.ide.common.repository.SdkMavenRepository} represents a Maven
+ * repository that is shipped with the SDK and located in the {@code extras} folder of the
+ * SDK location.
+ */
+public enum SdkMavenRepository {
+ /** The Android repository; contains support lib, app compat, media router, etc */
+ ANDROID("android", "Android Support Repository"),
+
+ /** The Google repository; contains Play Services etc */
+ GOOGLE("google", "Google Support Repository");
+
+ private final String mDir;
+ @NonNull private final String myDisplayName;
+
+ SdkMavenRepository(@NonNull String dir, @NonNull String displayName) {
+ mDir = dir;
+ myDisplayName = displayName;
+ }
+
+ /**
+ * @deprecated For testability, use {@link #getRepositoryLocation(File, boolean, FileOp)}.
+ */
+ @Deprecated
+ @Nullable
+ public File getRepositoryLocation(@Nullable File sdkHome, boolean requireExists) {
+ return getRepositoryLocation(sdkHome, requireExists, FileOpUtils.create());
+ }
+
+ /**
+ * Returns the location of the repository within a given SDK home
+ * @param sdkHome the SDK home, or null
+ * @param requireExists if true, the location will only be returned if it also exists
+ * @return the location of the this repository within a given SDK
+ */
+ public File getRepositoryLocation(@Nullable File sdkHome, boolean requireExists,
+ @NonNull FileOp fileOp) {
+ if (sdkHome != null) {
+ File dir = new File(sdkHome, FD_EXTRAS + separator + mDir
+ + separator + FD_M2_REPOSITORY);
+ if (!requireExists || fileOp.isDirectory(dir)) {
+ return dir;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if the given SDK repository is installed
+ *
+ * @param sdkHome the SDK installation location
+ * @return true if the repository is installed
+ */
+ public boolean isInstalled(@Nullable File sdkHome, @NonNull FileOp fileOp) {
+ return getRepositoryLocation(sdkHome, true, fileOp) != null;
+ }
+
+ /**
+ * Returns true if the given SDK repository is installed
+ *
+ * @param sdkHandler the SDK to check
+ * @return true if the repository is installed
+ */
+ public boolean isInstalled(@Nullable AndroidSdkHandler sdkHandler) {
+ if (sdkHandler != null) {
+ ProgressIndicator progress = new ConsoleProgressIndicator();
+ RepoManager mgr = sdkHandler.getSdkManager(progress);
+ return mgr.getPackages().getLocalPackages().containsKey(getPackageId());
+ }
+
+ return false;
+ }
+
+ public String getPackageId() {
+ return String.format("extras;%s;%s", mDir, FD_M2_REPOSITORY);
+ }
+
+ /**
+ * @deprecated For testability use
+ * {@link #getHighestInstalledVersion(File, String, String, String, boolean, FileOp)}
+ */
+ @Deprecated
+ public GradleCoordinate getHighestInstalledVersion(
+ @Nullable File sdkHome,
+ @NonNull String groupId,
+ @NonNull String artifactId,
+ @Nullable String filter,
+ boolean allowPreview) {
+ return getHighestInstalledVersion(sdkHome, groupId, artifactId, filter, allowPreview,
+ FileOpUtils.create());
+ }
+
+ /**
+ * Find the best matching {@link GradleCoordinate}
+ *
+ * @param sdkHome the SDK installation
+ * @param groupId the artifact group id
+ * @param artifactId the artifact id
+ * @param filter an optional filter which the matched coordinate's version name must start
+ * with
+ * @param allowPreview whether preview versions are allowed to match
+ * @param fileOp To allow mocking of filesystem operations.
+ * @return the best (highest version) matching coordinate, or null if none were found
+ */
+ @Nullable
+ public GradleCoordinate getHighestInstalledVersion(
+ @Nullable File sdkHome,
+ @NonNull String groupId,
+ @NonNull String artifactId,
+ @Nullable String filter,
+ boolean allowPreview,
+ @NonNull FileOp fileOp) {
+ File repository = getRepositoryLocation(sdkHome, true, fileOp);
+ if (repository != null) {
+ return getHighestInstalledVersion(groupId, artifactId, repository, filter,
+ allowPreview, fileOp);
+ }
+
+ return null;
+ }
+
+ /**
+ * @deprecated For testability, use
+ * {@link #getHighestInstalledVersion(String, String, File, String, boolean, FileOp)}.
+ */
+ @Deprecated
+ @Nullable
+ public static GradleCoordinate getHighestInstalledVersion(
+ @NonNull String groupId,
+ @NonNull String artifactId,
+ @NonNull File repository,
+ @Nullable String filter,
+ boolean allowPreview) {
+ return getHighestInstalledVersion(groupId, artifactId, repository, filter, allowPreview,
+ FileOpUtils.create());
+ }
+
+ /**
+ * Find the best matching {@link GradleCoordinate}
+ *
+ * @param groupId the artifact group id
+ * @param artifactId the artifact id
+ * @param repository the path to the m2repository directory
+ * @param filter an optional filter which the matched coordinate's version name must start with
+ * @param allowPreview whether preview versions are allowed to match
+ * @return the best (highest version) matching coordinate, or null if none were found
+ */
+ @Nullable
+ public static GradleCoordinate getHighestInstalledVersion(
+ @NonNull String groupId,
+ @NonNull String artifactId,
+ @NonNull File repository,
+ @Nullable String filter,
+ boolean allowPreview,
+ @NonNull FileOp fileOp) {
+ assert FD_M2_REPOSITORY.equals(repository.getName()) : repository;
+
+ File versionDir = new File(repository,
+ groupId.replace('.', separatorChar) + separator + artifactId);
+ File[] versions = fileOp.listFiles(versionDir);
+ List<GradleCoordinate> versionCoordinates = Lists.newArrayList();
+ for (File dir : versions) {
+ if (!fileOp.isDirectory(dir)) {
+ continue;
+ }
+ if (filter != null && !dir.getName().startsWith(filter)) {
+ continue;
+ }
+ GradleCoordinate gc = GradleCoordinate.parseCoordinateString(
+ groupId + ":" + artifactId + ":" + dir.getName());
+
+ if (gc != null && (allowPreview || !gc.isPreview())) {
+ if (!allowPreview && "5.2.08".equals(gc.getRevision()) &&
+ "play-services".equals(gc.getArtifactId())) {
+ // This specific version is actually a preview version which should
+ // not be used (https://code.google.com/p/android/issues/detail?id=75292)
+ continue;
+ }
+ versionCoordinates.add(gc);
+ }
+ }
+ if (!versionCoordinates.isEmpty()) {
+ return Collections.max(versionCoordinates, COMPARE_PLUS_HIGHER);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public static SdkMavenRepository getByGroupId(@NonNull String groupId) {
+ if ("com.android.support".equals(groupId) || "com.android.support.test".equals(groupId)) {
+ return ANDROID;
+ }
+ if (groupId.startsWith("com.google.android.")) {
+ // com.google.android.gms, com.google.android.support.wearable,
+ // com.google.android.wearable, ... possibly more in the future
+ return GOOGLE;
+ }
+
+ return null;
+ }
+
+ /** The directory name of the repository inside the extras folder */
+ @NonNull
+ public String getDirName() {
+ return mDir;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java b/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java
new file mode 100644
index 0000000..36e2e1f
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.SdkConstants.ATTR_REF_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.RESOURCE_CLZ_ATTR;
+import static com.android.ide.common.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.ide.common.resources.configuration.Configurable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LocaleQualifier;
+import com.android.resources.ResourceType;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+public abstract class AbstractResourceRepository {
+
+ private final boolean mFramework;
+
+ private class RepositoryMerger implements MergeConsumer<ResourceItem> {
+
+ @Override
+ public void start(@NonNull DocumentBuilderFactory factory)
+ throws ConsumerException {
+ }
+
+ @Override
+ public void end() throws ConsumerException {
+ }
+
+ @Override
+ public void addItem(@NonNull ResourceItem item) throws ConsumerException {
+ if (item.isTouched()) {
+ AbstractResourceRepository.this.addItem(item);
+ }
+ }
+
+ @Override
+ public void removeItem(@NonNull ResourceItem removedItem, @Nullable ResourceItem replacedBy)
+ throws ConsumerException {
+ AbstractResourceRepository.this.removeItem(removedItem);
+ }
+
+ @Override
+ public boolean ignoreItemInMerge(ResourceItem item) {
+ // we never ignore any item.
+ return false;
+ }
+ }
+
+ public AbstractResourceRepository(boolean isFramework) {
+ mFramework = isFramework;
+ }
+
+ public boolean isFramework() {
+ return mFramework;
+ }
+
+ @NonNull
+ public MergeConsumer<ResourceItem> createMergeConsumer() {
+ return new RepositoryMerger();
+ }
+
+ @NonNull
+ protected abstract Map<ResourceType, ListMultimap<String, ResourceItem>> getMap();
+
+ @Nullable
+ protected abstract ListMultimap<String, ResourceItem> getMap(ResourceType type, boolean create);
+
+ @NonNull
+ protected ListMultimap<String, ResourceItem> getMap(ResourceType type) {
+ //noinspection ConstantConditions
+ return getMap(type, true); // Won't return null if create is false
+ }
+
+ @NonNull
+ public Map<ResourceType, ListMultimap<String, ResourceItem>> getItems() {
+ return getMap();
+ }
+
+ /** Lock used to protect map access */
+ protected static final Object ITEM_MAP_LOCK = new Object();
+
+ // TODO: Rename to getResourceItemList?
+ @Nullable
+ public List<ResourceItem> getResourceItem(@NonNull ResourceType resourceType,
+ @NonNull String resourceName) {
+ synchronized (ITEM_MAP_LOCK) {
+ ListMultimap<String, ResourceItem> map = getMap(resourceType, false);
+
+ if (map != null) {
+ return map.get(resourceName);
+ }
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public Collection<String> getItemsOfType(@NonNull ResourceType type) {
+ synchronized (ITEM_MAP_LOCK) {
+ Multimap<String, ResourceItem> map = getMap(type, false);
+ if (map == null) {
+ return Collections.emptyList();
+ }
+ return Collections.unmodifiableCollection(map.keySet());
+ }
+ }
+
+ /**
+ * Returns true if this resource repository contains a resource of the given
+ * name.
+ *
+ * @param url the resource URL
+ * @return true if the resource is known
+ */
+ public boolean hasResourceItem(@NonNull String url) {
+ // Handle theme references
+ if (url.startsWith(PREFIX_THEME_REF)) {
+ String remainder = url.substring(PREFIX_THEME_REF.length());
+ if (url.startsWith(ATTR_REF_PREFIX)) {
+ url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
+ return hasResourceItem(url);
+ }
+ int colon = url.indexOf(':');
+ if (colon != -1) {
+ // Convert from ?android:progressBarStyleBig to ?android:attr/progressBarStyleBig
+ if (remainder.indexOf('/', colon) == -1) {
+ remainder = remainder.substring(0, colon) + RESOURCE_CLZ_ATTR + '/'
+ + remainder.substring(colon);
+ }
+ url = PREFIX_RESOURCE_REF + remainder;
+ return hasResourceItem(url);
+ } else {
+ int slash = url.indexOf('/');
+ if (slash == -1) {
+ url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder;
+ return hasResourceItem(url);
+ }
+ }
+ }
+
+ if (!url.startsWith(PREFIX_RESOURCE_REF)) {
+ return false;
+ }
+
+ assert url.startsWith("@") || url.startsWith("?") : url;
+
+ int typeEnd = url.indexOf('/', 1);
+ if (typeEnd != -1) {
+ int nameBegin = typeEnd + 1;
+
+ // Skip @ and @+
+ int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
+
+ int colon = url.lastIndexOf(':', typeEnd);
+ if (colon != -1) {
+ typeBegin = colon + 1;
+ }
+ String typeName = url.substring(typeBegin, typeEnd);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type != null) {
+ String name = url.substring(nameBegin);
+ return hasResourceItem(type, name);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if this resource repository contains a resource of the given
+ * name.
+ *
+ * @param resourceType the type of resource to look up
+ * @param resourceName the name of the resource
+ * @return true if the resource is known
+ */
+ public boolean hasResourceItem(@NonNull ResourceType resourceType,
+ @NonNull String resourceName) {
+ synchronized (ITEM_MAP_LOCK) {
+ ListMultimap<String, ResourceItem> map = getMap(resourceType, false);
+
+ if (map != null) {
+ List<ResourceItem> itemList = map.get(resourceName);
+ return itemList != null && !itemList.isEmpty();
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the repository has resources of a given {@link ResourceType}.
+ * @param resourceType the type of resource to check.
+ * @return true if the repository contains resources of the given type, false otherwise.
+ */
+ public boolean hasResourcesOfType(@NonNull ResourceType resourceType) {
+ synchronized (ITEM_MAP_LOCK) {
+ ListMultimap<String, ResourceItem> map = getMap(resourceType, false);
+ return map != null && !map.isEmpty();
+ }
+ }
+
+ @NonNull
+ public List<ResourceType> getAvailableResourceTypes() {
+ synchronized (ITEM_MAP_LOCK) {
+ return Lists.newArrayList(getMap().keySet());
+ }
+ }
+
+ /**
+ * Returns the {@link ResourceFile} matching the given name, {@link ResourceType} and
+ * configuration.
+ * <p/>
+ * This only works with files generating one resource named after the file
+ * (for instance, layouts, bitmap based drawable, xml, anims).
+ *
+ * @param name the resource name
+ * @param type the folder type search for
+ * @param config the folder configuration to match for
+ * @return the matching file or <code>null</code> if no match was found.
+ */
+ @Nullable
+ public ResourceFile getMatchingFile(
+ @NonNull String name,
+ @NonNull ResourceType type,
+ @NonNull FolderConfiguration config) {
+ List<ResourceFile> matchingFiles = getMatchingFiles(name, type, config);
+ return matchingFiles.isEmpty() ? null : matchingFiles.get(0);
+ }
+
+ /**
+ * Returns a list of {@link ResourceFile} matching the given name, {@link ResourceType} and
+ * configuration. This ignores the qualifiers which are missing from the configuration.
+ * <p/>
+ * This only works with files generating one resource named after the file (for instance,
+ * layouts, bitmap based drawable, xml, anims).
+ *
+ * @param name the resource name
+ * @param type the folder type search for
+ * @param config the folder configuration to match for
+ *
+ * @see #getMatchingFile(String, ResourceType, FolderConfiguration)
+ */
+ @NonNull
+ public List<ResourceFile> getMatchingFiles(
+ @NonNull String name,
+ @NonNull ResourceType type,
+ @NonNull FolderConfiguration config) {
+ return getMatchingFiles(name, type, config, new HashSet<String>(), 0);
+ }
+
+ @NonNull
+ private List<ResourceFile> getMatchingFiles(
+ @NonNull String name,
+ @NonNull ResourceType type,
+ @NonNull FolderConfiguration config,
+ @NonNull Set<String> seenNames,
+ int depth) {
+ assert !seenNames.contains(name);
+ if (depth >= MAX_RESOURCE_INDIRECTION) {
+ return Collections.emptyList();
+ }
+ List<ResourceFile> output;
+ synchronized (ITEM_MAP_LOCK) {
+ ListMultimap<String, ResourceItem> typeItems = getMap(type, false);
+ if (typeItems == null) {
+ return Collections.emptyList();
+ }
+ seenNames.add(name);
+ output = new ArrayList<ResourceFile>();
+ List<ResourceItem> matchingItems = typeItems.get(name);
+ List<ResourceItem> matches = config.findMatchingConfigurables(matchingItems);
+ for (ResourceItem match : matches) {
+ // if match is an alias, check if the name is in seen names.
+ ResourceValue resourceValue = match.getResourceValue(isFramework());
+ if (resourceValue != null) {
+ String value = resourceValue.getValue();
+ if (value != null && value.startsWith(PREFIX_RESOURCE_REF)) {
+ ResourceUrl url = ResourceUrl.parse(value);
+ if (url != null && url.type == type && url.framework == isFramework()) {
+ if (!seenNames.contains(url.name)) {
+ // This resource alias needs to be resolved again.
+ output.addAll(getMatchingFiles(
+ url.name, type, config, seenNames, depth + 1));
+ }
+ continue;
+ }
+ }
+ }
+ output.add(match.getSource());
+
+ }
+ }
+
+ return output;
+ }
+
+ /**
+ * Returns the resources values matching a given {@link FolderConfiguration}.
+ *
+ * @param referenceConfig the configuration that each value must match.
+ * @return a map with guaranteed to contain an entry for each {@link ResourceType}
+ */
+ @NonNull
+ public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
+ @NonNull FolderConfiguration referenceConfig) {
+ Map<ResourceType, Map<String, ResourceValue>> map = Maps.newEnumMap(ResourceType.class);
+
+ synchronized (ITEM_MAP_LOCK) {
+ Map<ResourceType, ListMultimap<String, ResourceItem>> itemMap = getMap();
+ for (ResourceType key : ResourceType.values()) {
+ // get the local results and put them in the map
+ map.put(key, getConfiguredResources(itemMap, key, referenceConfig));
+ }
+ }
+
+ return map;
+ }
+
+ /**
+ * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
+ * <p/>The values returned are taken from the resource files best matching a given
+ * {@link FolderConfiguration}.
+ * @param type the type of the resources.
+ * @param referenceConfig the configuration to best match.
+ */
+ @NonNull
+ public Map<String, ResourceValue> getConfiguredResources(
+ @NonNull ResourceType type,
+ @NonNull FolderConfiguration referenceConfig) {
+ return getConfiguredResources(getMap(), type, referenceConfig);
+ }
+
+ @NonNull
+ public Map<String, ResourceValue> getConfiguredResources(
+ @NonNull Map<ResourceType, ListMultimap<String, ResourceItem>> itemMap,
+ @NonNull ResourceType type,
+ @NonNull FolderConfiguration referenceConfig) {
+ // get the resource item for the given type
+ ListMultimap<String, ResourceItem> items = itemMap.get(type);
+ if (items == null) {
+ return Maps.newHashMap();
+ }
+
+ Set<String> keys = items.keySet();
+
+ // create the map
+ Map<String, ResourceValue> map = Maps.newHashMapWithExpectedSize(keys.size());
+
+ for (String key : keys) {
+ List<ResourceItem> keyItems = items.get(key);
+
+ // look for the best match for the given configuration
+ // the match has to be of type ResourceFile since that's what the input list contains
+ ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
+ if (match != null) {
+ ResourceValue value = match.getResourceValue(mFramework);
+ if (value != null) {
+ map.put(match.getName(), value);
+ }
+ }
+ }
+
+ return map;
+ }
+
+ @Nullable
+ public ResourceValue getConfiguredValue(
+ @NonNull ResourceType type,
+ @NonNull String name,
+ @NonNull FolderConfiguration referenceConfig) {
+ // get the resource item for the given type
+ ListMultimap<String, ResourceItem> items = getMap(type, false);
+ if (items == null) {
+ return null;
+ }
+
+ List<ResourceItem> keyItems = items.get(name);
+ if (keyItems == null) {
+ return null;
+ }
+
+ // look for the best match for the given configuration
+ // the match has to be of type ResourceFile since that's what the input list contains
+ ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
+ return match != null ? match.getResourceValue(mFramework) : null;
+ }
+
+ private void addItem(@NonNull ResourceItem item) {
+ synchronized (ITEM_MAP_LOCK) {
+ ListMultimap<String, ResourceItem> map = getMap(item.getType());
+ if (!map.containsEntry(item.getName(), item)) {
+ map.put(item.getName(), item);
+ }
+ }
+ }
+
+ private void removeItem(@NonNull ResourceItem removedItem) {
+ synchronized (ITEM_MAP_LOCK) {
+ Multimap<String, ResourceItem> map = getMap(removedItem.getType(), false);
+ if (map != null) {
+ map.remove(removedItem.getName(), removedItem);
+ }
+ }
+ }
+
+ /**
+ * Returns the sorted list of languages used in the resources.
+ */
+ @NonNull
+ public SortedSet<String> getLanguages() {
+ SortedSet<String> set = new TreeSet<String>();
+
+ // As an optimization we could just look for values since that's typically where
+ // the languages are defined -- not on layouts, menus, etc -- especially if there
+ // are no translations for it
+ Set<String> qualifiers = Sets.newHashSet();
+
+ synchronized (ITEM_MAP_LOCK) {
+ for (ListMultimap<String, ResourceItem> map : getMap().values()) {
+ for (ResourceItem item : map.values()) {
+ qualifiers.add(item.getQualifiers());
+ }
+ }
+ }
+
+ for (String s : qualifiers) {
+ FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
+ if (configuration != null) {
+ LocaleQualifier locale = configuration.getLocaleQualifier();
+ if (locale != null) {
+ set.add(locale.getLanguage());
+ }
+ }
+ }
+
+ return set;
+ }
+
+ /**
+ * Returns the sorted list of languages used in the resources.
+ */
+ @NonNull
+ public SortedSet<LocaleQualifier> getLocales() {
+ SortedSet<LocaleQualifier> set = new TreeSet<LocaleQualifier>();
+
+ // As an optimization we could just look for values since that's typically where
+ // the languages are defined -- not on layouts, menus, etc -- especially if there
+ // are no translations for it
+ Set<String> qualifiers = Sets.newHashSet();
+
+ synchronized (ITEM_MAP_LOCK) {
+ for (ListMultimap<String, ResourceItem> map : getMap().values()) {
+ for (ResourceItem item : map.values()) {
+ qualifiers.add(item.getQualifiers());
+ }
+ }
+ }
+
+ for (String s : qualifiers) {
+ FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
+ if (configuration != null) {
+ LocaleQualifier locale = configuration.getLocaleQualifier();
+ if (locale != null) {
+ set.add(locale);
+ }
+ }
+ }
+
+ return set;
+ }
+
+ /**
+ * Returns the sorted list of regions used in the resources with the given language.
+ * @param currentLanguage the current language the region must be associated with.
+ */
+ @NonNull
+ public SortedSet<String> getRegions(@NonNull String currentLanguage) {
+ SortedSet<String> set = new TreeSet<String>();
+
+ // As an optimization we could just look for values since that's typically where
+ // the languages are defined -- not on layouts, menus, etc -- especially if there
+ // are no translations for it
+ Set<String> qualifiers = Sets.newHashSet();
+ synchronized (ITEM_MAP_LOCK) {
+ for (ListMultimap<String, ResourceItem> map : getMap().values()) {
+ for (ResourceItem item : map.values()) {
+ qualifiers.add(item.getQualifiers());
+ }
+ }
+ }
+
+ for (String s : qualifiers) {
+ FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
+ if (configuration != null) {
+ LocaleQualifier locale = configuration.getLocaleQualifier();
+ if (locale != null && locale.getRegion() != null
+ && locale.getLanguage().equals(currentLanguage)) {
+ set.add(locale.getRegion());
+ }
+ }
+ }
+
+ return set;
+ }
+
+ public void clear() {
+ getMap().clear();
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/AssetFile.java b/sdk-common/src/main/java/com/android/ide/common/res2/AssetFile.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/AssetFile.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/AssetFile.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/AssetItem.java b/sdk-common/src/main/java/com/android/ide/common/res2/AssetItem.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/AssetItem.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/AssetItem.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/AssetMerger.java b/sdk-common/src/main/java/com/android/ide/common/res2/AssetMerger.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/AssetMerger.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/AssetMerger.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceItem.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceItem.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceItem.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceType.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceType.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceType.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/DataBindingResourceType.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/DataItem.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataItem.java
new file mode 100644
index 0000000..367a558
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/DataItem.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Objects;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.File;
+
+/**
+ * A data item is the most elementary merge unit in the data merging process. Data items will
+ * generally belong to a {@link DataFile} although, temporarily during the merge process data
+ * items may not be associated to any data file. This will happen when data items are moved from
+ * one file to another.
+ *
+ * <p>Data items can represent entire files, <em>e.g.</em>, a PNG file, or they can represent
+ * individual entries in a file, <em>e.g.</em>, a string in a strings file.</p>
+ *
+ * <p>Data items have three markers that represent its "state": touched, removed and written.
+ * A touched data is a data item that needs to be examined in the merge process. A removed data
+ * item is a data item that has been removed from its file. A written data item is a data item
+ * that has been changed or added.</p>
+ *
+ * @param <F> the type of data file the item belongs to
+ */
+abstract class DataItem<F extends DataFile> {
+ /** Bit flag marking {@link #mStatus} as touched. */
+ private static final int MASK_TOUCHED = 0x01;
+
+ /** Bit flag marking {@link #mStatus} as removed. */
+ private static final int MASK_REMOVED = 0x02;
+
+ /** Bit flag marking {@link #mStatus} as written. */
+ private static final int MASK_WRITTEN = 0x10;
+
+ /** Name of the data item. */
+ @NonNull private final String mName;
+
+ /** File the data item comes from. */
+ @Nullable private F mSource;
+
+ /**
+ * The status of the Item. It's a bit mask as opposed to an enum
+ * to differentiate removed and removed+written
+ */
+ private int mStatus = 0;
+
+ /**
+ * Constructs the object with a name, type and optional value.
+ * Note that the object is not fully usable as-is. It must be added to a DataFile first.
+ * @param name the name of the item
+ */
+ DataItem(@NonNull String name) {
+ mName = name;
+ }
+
+ /**
+ * Returns the name of the item.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the DataFile the item is coming from.
+ */
+ @Nullable
+ public F getSource() {
+ return mSource;
+ }
+
+ /**
+ * Sets the DataFile. The item must not belong to a data file.
+ * @param sourceFile the data file, if null then the item is marked as being removed from the
+ * data file
+ */
+ public void setSource(@NonNull F sourceFile) {
+ mSource = sourceFile;
+ }
+
+ /**
+ * Resets the state of the item be nothing.
+ * @return this
+ */
+ DataItem<F> resetStatus() {
+ mStatus = 0;
+ return this;
+ }
+
+ /**
+ * Resets the state of the item be WRITTEN. All other states are removed.
+ * @return this
+ * @see #isWritten()
+ */
+ DataItem<F> resetStatusToWritten() {
+ mStatus = MASK_WRITTEN;
+ return this;
+ }
+
+ /**
+ * Resets the state of the item be TOUCHED. All other states are removed.
+ * @return this
+ * @see #isWritten()
+ */
+ DataItem<F> resetStatusToTouched() {
+ boolean wasNotTouched = !isTouched();
+ mStatus = MASK_TOUCHED;
+
+ if (!wasNotTouched) {
+ wasTouched();
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets the item status to contain WRITTEN. Other states are kept.
+ * @return this
+ * @see #isWritten()
+ */
+ DataItem<F> setWritten() {
+ mStatus |= MASK_WRITTEN;
+ return this;
+ }
+
+ /**
+ * Sets the item status to contain REMOVED. Other states are kept.
+ * @return this
+ * @see #isRemoved()
+ */
+ DataItem<F> setRemoved() {
+ mStatus |= MASK_REMOVED;
+ return this;
+ }
+
+ /**
+ * Sets the item status to contain TOUCHED. Other states are kept.
+ * @return this
+ * @see #isTouched()
+ */
+ DataItem<F> setTouched() {
+ if (!isTouched()) {
+ mStatus |= MASK_TOUCHED;
+ wasTouched();
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns whether the item status contains REMOVED.
+ * @return <code>true</code> if removed
+ */
+ boolean isRemoved() {
+ return (mStatus & MASK_REMOVED) != 0;
+ }
+
+ /**
+ * Returns whether the item status contains TOUCHED
+ * @return <code>true</code> if touched
+ */
+ boolean isTouched() {
+ return (mStatus & MASK_TOUCHED) != 0;
+ }
+
+ /**
+ * Returns whether the item status contains WRITTEN
+ * @return <code>true</code> if written
+ */
+ boolean isWritten() {
+ return (mStatus & MASK_WRITTEN) != 0;
+ }
+
+
+ /**
+ * Obtains the full status of the data item; should not generally be used except
+ * for debug purposes.
+ * @return the internal representation
+ */
+ protected int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Returns the key for this item. They key uniquely identifies this item.
+ */
+ public String getKey() {
+ return mName;
+ }
+
+ /**
+ * Overridden in ResourceItem, which adds the type attribute.
+ */
+ void addExtraAttributes(Document document, Node node, String namespaceUri) {
+ // nothing
+ }
+
+ /**
+ * Returns a node that describes additional properties of this {@link DataItem}.
+ * If not <code>null</code>, it will be persisted in the merger XML blob and can be used
+ * used to restore the exact state of this item. If <code>null</code> then the state of this
+ * item will not be persisted.
+ *
+ * <p>The default implementation returns <code>null</code>.</p>
+ */
+ Node getDetailsXml(Document document) {
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DataItem dataItem = (DataItem) o;
+
+ return Objects.equal(mName, dataItem.mName)
+ && Objects.equal(mSource, dataItem.mSource);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mName, mSource);
+ }
+
+ /**
+ * Hook invoked when the data item has been touched. The default implementation does nothing.
+ */
+ protected void wasTouched() {}
+
+ /**
+ * For non-values resources, this is the original source file.
+ * This method is here as {@link GeneratedResourceItem} overrides it.
+ */
+ public File getFile() {
+ return getSource().getFile();
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataMap.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataMap.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/DataMap.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/DataMap.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java
new file mode 100644
index 0000000..a88e5a0
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Merges {@link DataSet}s and writes a resulting data folder.
+ *
+ * This is able to save its post work state and reload this for incremental update.
+ */
+abstract class DataMerger<I extends DataItem<F>, F extends DataFile<I>, S extends DataSet<I,F>> implements DataMap<I> {
+
+ static final String FN_MERGER_XML = "merger.xml";
+ static final String NODE_MERGER = "merger";
+ static final String NODE_DATA_SET = "dataSet";
+
+ static final String NODE_CONFIGURATION = "configuration";
+
+ static final String ATTR_VERSION = "version";
+ static final String MERGE_BLOB_VERSION = "3";
+
+ @NonNull
+ protected final DocumentBuilderFactory mFactory;
+
+ /**
+ * All the DataSets.
+ */
+ private final List<S> mDataSets = Lists.newArrayList();
+
+ public DataMerger() {
+ mFactory = DocumentBuilderFactory.newInstance();
+ mFactory.setNamespaceAware(true);
+ mFactory.setValidating(false);
+ mFactory.setIgnoringComments(true);
+ }
+
+ protected abstract S createFromXml(Node node) throws MergingException;
+
+ protected abstract boolean requiresMerge(@NonNull String dataItemKey);
+
+ /**
+ * Merge items together, and register the merged items with the given consumer.
+ * @param dataItemKey the key for the items
+ * @param items the items, from lower priority to higher priority.
+ * @param consumer the consumer to receive the merged items.
+ * @throws MergingException
+ */
+ protected abstract void mergeItems(
+ @NonNull String dataItemKey,
+ @NonNull List<I> items,
+ @NonNull MergeConsumer<I> consumer) throws MergingException;
+
+ /**
+ * adds a new {@link DataSet} and overlays it on top of the existing DataSet.
+ *
+ * @param resourceSet the ResourceSet to add.
+ */
+ public void addDataSet(S resourceSet) {
+ // TODO figure out if we allow partial overlay through a per-resource flag.
+ mDataSets.add(resourceSet);
+ }
+
+ /**
+ * Returns the list of ResourceSet objects.
+ * @return the resource sets.
+ */
+ @NonNull
+ public List<S> getDataSets() {
+ return mDataSets;
+ }
+
+ @VisibleForTesting
+ void validateDataSets() throws DuplicateDataException {
+ for (S resourceSet : mDataSets) {
+ resourceSet.checkItems();
+ }
+ }
+
+ /**
+ * Returns the number of items.
+ * @return the number of items.
+ *
+ * @see DataMap
+ */
+ @Override
+ public int size() {
+ // put all the resource keys in a set.
+ Set<String> keys = Sets.newHashSet();
+
+ for (S resourceSet : mDataSets) {
+ ListMultimap<String, I> map = resourceSet.getDataMap();
+ keys.addAll(map.keySet());
+ }
+
+ return keys.size();
+ }
+
+ /**
+ * Returns a map of the data items.
+ * @return a map of items.
+ *
+ * @see DataMap
+ */
+ @NonNull
+ @Override
+ public ListMultimap<String, I> getDataMap() {
+ // put all the sets in a multimap. The result is that for each key,
+ // there is a sorted list of items from all the layers, including removed ones.
+ ListMultimap<String, I> fullItemMultimap = ArrayListMultimap.create();
+
+ for (S resourceSet : mDataSets) {
+ ListMultimap<String, I> map = resourceSet.getDataMap();
+ for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) {
+ fullItemMultimap.putAll(entry.getKey(), entry.getValue());
+ }
+ }
+
+ return fullItemMultimap;
+ }
+
+ /**
+ * Merges the data into a given consumer.
+ *
+ * @param consumer the consumer of the merge.
+ * @param doCleanUp clean up the state to be able to do further incremental merges. If this
+ * is a one-shot merge, this can be false to improve performance.
+ * @throws MergingException such as a DuplicateDataException or a
+ * MergeConsumer.ConsumerException if something goes wrong
+ */
+ public void mergeData(@NonNull MergeConsumer<I> consumer, boolean doCleanUp)
+ throws MergingException {
+
+ consumer.start(mFactory);
+
+ try {
+ // get all the items keys.
+ Set<String> dataItemKeys = Sets.newHashSet();
+
+ for (S dataSet : mDataSets) {
+ // quick check on duplicates in the resource set.
+ dataSet.checkItems();
+ ListMultimap<String, I> map = dataSet.getDataMap();
+ dataItemKeys.addAll(map.keySet());
+ }
+
+ // loop on all the data items.
+ for (String dataItemKey : dataItemKeys) {
+ if (requiresMerge(dataItemKey)) {
+ // get all the available items, from the lower priority, to the higher
+ // priority
+ List<I> items = Lists.newArrayListWithExpectedSize(mDataSets.size());
+ for (S dataSet : mDataSets) {
+
+ // look for the resource key in the set
+ ListMultimap<String, I> itemMap = dataSet.getDataMap();
+
+ List<I> setItems = itemMap.get(dataItemKey);
+ items.addAll(setItems);
+ }
+
+ mergeItems(dataItemKey, items, consumer);
+ continue;
+ }
+
+ // for each items, look in the data sets, starting from the end of the list.
+
+ I previouslyWritten = null;
+ I toWrite = null;
+
+ /*
+ * We are looking for what to write/delete: the last non deleted item, and the
+ * previously written one.
+ */
+
+ boolean foundIgnoredItem = false;
+
+ setLoop: for (int i = mDataSets.size() - 1 ; i >= 0 ; i--) {
+ S dataSet = mDataSets.get(i);
+
+ // look for the resource key in the set
+ ListMultimap<String, I> itemMap = dataSet.getDataMap();
+
+ List<I> items = itemMap.get(dataItemKey);
+ if (items.isEmpty()) {
+ continue;
+ }
+
+ // The list can contain at max 2 items. One touched and one deleted.
+ // More than one deleted means there was more than one which isn't possible
+ // More than one touched means there is more than one and this isn't possible.
+ for (int ii = items.size() - 1 ; ii >= 0 ; ii--) {
+ I item = items.get(ii);
+
+ if (consumer.ignoreItemInMerge(item)) {
+ foundIgnoredItem = true;
+ continue;
+ }
+
+ if (item.isWritten()) {
+ assert previouslyWritten == null;
+ previouslyWritten = item;
+ }
+
+ if (toWrite == null && !item.isRemoved()) {
+ toWrite = item;
+ }
+
+ if (toWrite != null && previouslyWritten != null) {
+ break setLoop;
+ }
+ }
+ }
+
+ // done searching, we should at least have something, unless we only
+ // found items that are not meant to be written (attr inside declare styleable)
+ assert foundIgnoredItem || previouslyWritten != null || toWrite != null;
+
+ if (toWrite != null && !filterAccept(toWrite)) {
+ toWrite = null;
+ }
+
+
+ //noinspection ConstantConditions
+ if (previouslyWritten == null && toWrite == null) {
+ continue;
+ }
+
+ // now need to handle, the type of each (single res file, multi res file), whether
+ // they are the same object or not, whether the previously written object was deleted.
+
+ if (toWrite == null) {
+ // nothing to write? delete only then.
+ assert previouslyWritten.isRemoved();
+
+ consumer.removeItem(previouslyWritten, null /*replacedBy*/);
+
+ } else if (previouslyWritten == null || previouslyWritten == toWrite) {
+ // easy one: new or updated res
+ consumer.addItem(toWrite);
+ } else {
+ // replacement of a resource by another.
+
+ // force write the new value
+ toWrite.setTouched();
+ consumer.addItem(toWrite);
+ // and remove the old one
+ consumer.removeItem(previouslyWritten, toWrite);
+ }
+ }
+ } finally {
+ consumer.end();
+ }
+
+ if (doCleanUp) {
+ // reset all states. We can't just reset the toWrite and previouslyWritten objects
+ // since overlayed items might have been touched as well.
+ // Should also clean (remove) objects that are removed.
+ postMergeCleanUp();
+ }
+ }
+
+ /**
+ * Writes a single blob file to store all that the DataMerger knows about.
+ *
+ * @param blobRootFolder the root folder where blobs are store.
+ * @param consumer the merge consumer that was used by the merge.
+ *
+ * @throws MergingException if something goes wrong
+ *
+ * @see #loadFromBlob(File, boolean)
+ */
+ public void writeBlobTo(@NonNull File blobRootFolder, @NonNull MergeConsumer<I> consumer)
+ throws MergingException {
+ // write "compact" blob
+ DocumentBuilder builder;
+
+ try {
+ builder = mFactory.newDocumentBuilder();
+ Document document = builder.newDocument();
+
+ Node rootNode = document.createElement(NODE_MERGER);
+ // add the version code.
+ NodeUtils.addAttribute(document, rootNode, null, ATTR_VERSION, MERGE_BLOB_VERSION);
+
+ document.appendChild(rootNode);
+
+ for (S dataSet : mDataSets) {
+ Node dataSetNode = document.createElement(NODE_DATA_SET);
+ rootNode.appendChild(dataSetNode);
+
+ dataSet.appendToXml(dataSetNode, document, consumer);
+ }
+
+ // write merged items
+ writeAdditionalData(document, rootNode);
+
+ String content = XmlUtils.toXml(document);
+
+ try {
+ createDir(blobRootFolder);
+ } catch (IOException ioe) {
+ throw MergingException.wrapException(ioe).withFile(blobRootFolder).build();
+ }
+ File file = new File(blobRootFolder, FN_MERGER_XML);
+ try {
+ Files.write(content, file, Charsets.UTF_8);
+ } catch (IOException ioe) {
+ throw MergingException.wrapException(ioe).withFile(file).build();
+ }
+ } catch (ParserConfigurationException e) {
+ throw MergingException.wrapException(e).build();
+ }
+ }
+
+ /**
+ * Loads the merger state from a blob file.
+ *
+ * This can be loaded into two different ways that differ only by the state on the
+ * {@link DataItem} objects.
+ *
+ * If <var>incrementalState</var> is <code>true</code> then the items that are on disk are
+ * marked as written ({@link DataItem#isWritten()} returning <code>true</code>.
+ * This is to be used by {@link MergeWriter} to update a merged res folder.
+ *
+ * If <code>false</code>, the items are marked as touched, and this can be used to feed a new
+ * {@link ResourceRepository} object.
+ *
+ * @param blobRootFolder the folder containing the blob.
+ * @param incrementalState whether to load into an incremental state or a new state.
+ * @return true if the blob was loaded.
+ * @throws MergingException if something goes wrong
+ *
+ * @see #writeBlobTo(File, MergeConsumer)
+ */
+ public boolean loadFromBlob(@NonNull File blobRootFolder, boolean incrementalState)
+ throws MergingException {
+ File file = new File(blobRootFolder, FN_MERGER_XML);
+ if (!file.isFile()) {
+ return false;
+ }
+
+ try {
+ Document document = XmlUtils.parseUtfXmlFile(file, true /*namespaceAware*/);
+
+ // get the root node
+ Node rootNode = document.getDocumentElement();
+ if (rootNode == null || !NODE_MERGER.equals(rootNode.getLocalName())) {
+ return false;
+ }
+
+ // get the version code.
+ String version = null;
+ Attr versionAttr = (Attr) rootNode.getAttributes().getNamedItem(ATTR_VERSION);
+ if (versionAttr != null) {
+ version = versionAttr.getValue();
+ }
+ if (!MERGE_BLOB_VERSION.equals(version)) {
+ return false;
+ }
+
+ NodeList nodes = rootNode.getChildNodes();
+
+ for (int i = 0, n = nodes.getLength(); i < n; i++) {
+ Node node = nodes.item(i);
+
+ if (node.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ if (NODE_DATA_SET.equals(node.getLocalName())) {
+ S dataSet = createFromXml(node);
+ if (dataSet != null) {
+ addDataSet(dataSet);
+ }
+ } else if (incrementalState
+ && getAdditionalDataTagName().equals(node.getLocalName())) {
+ loadAdditionalData(node, incrementalState);
+ }
+ }
+
+ if (incrementalState) {
+ setPostBlobLoadStateToWritten();
+ } else {
+ setPostBlobLoadStateToTouched();
+ }
+
+ return true;
+ } catch (SAXParseException e) {
+ throw MergingException.wrapException(e).withFile(file).build();
+ } catch (IOException e) {
+ throw MergingException.wrapException(e).withFile(file).build();
+ } catch (ParserConfigurationException e) {
+ throw MergingException.wrapException(e).withFile(file).build();
+ } catch (SAXException e) {
+ throw MergingException.wrapException(e).withFile(file).build();
+ }
+ }
+
+ @NonNull
+ protected String getAdditionalDataTagName() {
+ // No tag can have an empty name, so mergers that store additional data, have to provide
+ // this.
+ return "";
+ }
+
+ protected void loadAdditionalData(@NonNull Node additionalDataNode, boolean incrementalState)
+ throws MergingException {
+ // do nothing by default.
+ }
+
+ protected void writeAdditionalData(Document document, Node rootNode) throws MergingException {
+ // do nothing by default.
+ }
+
+ public void cleanBlob(@NonNull File blobRootFolder) {
+ File file = new File(blobRootFolder, FN_MERGER_XML);
+ if (file.isFile()) {
+ file.delete();
+ }
+ }
+
+ /**
+ * Sets the post blob load state to WRITTEN.
+ *
+ * After a load from the blob file, all items have their state set to nothing.
+ * If the load mode is set to incrementalState then we want the items that are in the current
+ * merge result to have their state be WRITTEN.
+ *
+ * This will allow further updates with {@link #mergeData(MergeConsumer, boolean)} to
+ * ignore the state at load time and only apply the new changes.
+ *
+ * @see #loadFromBlob(java.io.File, boolean)
+ * @see DataItem#isWritten()
+ */
+ private void setPostBlobLoadStateToWritten() {
+ ListMultimap<String, I> itemMap = ArrayListMultimap.create();
+
+ // put all the sets into list per keys. The order is important as the lower sets are
+ // overridden by the higher sets.
+ for (S dataSet : mDataSets) {
+ ListMultimap<String, I> map = dataSet.getDataMap();
+ for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) {
+ itemMap.putAll(entry.getKey(), entry.getValue());
+ }
+ }
+
+ // the items that represent the current state is the last item in the list for each key.
+ for (String key : itemMap.keySet()) {
+ List<I> itemList = itemMap.get(key);
+ itemList.get(itemList.size() - 1).resetStatusToWritten();
+ }
+ }
+
+ /**
+ * Sets the post blob load state to TOUCHED.
+ *
+ * After a load from the blob file, all items have their state set to nothing.
+ * If the load mode is not set to incrementalState then we want the items that are in the
+ * current merge result to have their state be TOUCHED.
+ *
+ * This will allow the first use of {@link #mergeData(MergeConsumer, boolean)} to add these
+ * to the consumer as if they were new items.
+ *
+ * @see #loadFromBlob(java.io.File, boolean)
+ * @see DataItem#isTouched()
+ */
+ private void setPostBlobLoadStateToTouched() {
+ ListMultimap<String, I> itemMap = ArrayListMultimap.create();
+
+ // put all the sets into list per keys. The order is important as the lower sets are
+ // overridden by the higher sets.
+ for (S dataSet : mDataSets) {
+ ListMultimap<String, I> map = dataSet.getDataMap();
+ for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) {
+ itemMap.putAll(entry.getKey(), entry.getValue());
+ }
+ }
+
+ // the items that represent the current state is the last item in the list for each key.
+ for (String key : itemMap.keySet()) {
+ List<I> itemList = itemMap.get(key);
+ itemList.get(itemList.size() - 1).resetStatusToTouched();
+ }
+ }
+
+ /**
+ * Post merge clean up.
+ *
+ * - Remove the removed items.
+ * - Clear the state of all the items (this allow newly overridden items to lose their
+ * WRITTEN state)
+ * - Set the items that are part of the new merge to be WRITTEN to allow the next merge to
+ * be incremental.
+ */
+ private void postMergeCleanUp() {
+ ListMultimap<String, I> itemMap = ArrayListMultimap.create();
+
+ // remove all removed items, and copy the rest in the full map while resetting their state.
+ for (S dataSet : mDataSets) {
+ ListMultimap<String, I> map = dataSet.getDataMap();
+
+ List<String> keys = Lists.newArrayList(map.keySet());
+ for (String key : keys) {
+ List<I> list = map.get(key);
+ for (int i = 0 ; i < list.size() ;) {
+ I item = list.get(i);
+ if (item.isRemoved()) {
+ list.remove(i);
+ } else {
+ //noinspection unchecked
+ itemMap.put(key, (I) item.resetStatus());
+ i++;
+ }
+ }
+ }
+ }
+
+ // for the last items (the one that have been written into the consumer), set their
+ // state to WRITTEN
+ for (String key : itemMap.keySet()) {
+ List<I> itemList = itemMap.get(key);
+ itemList.get(itemList.size() - 1).resetStatusToWritten();
+ }
+ }
+
+ /**
+ * Checks that a loaded merger can be updated with a given list of DataSet.
+ *
+ * For now this means the sets haven't changed.
+ *
+ * @param dataSets the resource sets.
+ * @return true if the update can be performed. false if a full merge should be done.
+ */
+ public boolean checkValidUpdate(List<S> dataSets) {
+ if (dataSets.size() != mDataSets.size()) {
+ return false;
+ }
+
+ for (int i = 0, n = dataSets.size(); i < n; i++) {
+ S localSet = mDataSets.get(i);
+ S newSet = dataSets.get(i);
+
+ List<File> localSourceFiles = localSet.getSourceFiles();
+ List<File> newSourceFiles = newSet.getSourceFiles();
+
+ // compare the config name and source files sizes.
+ if (!newSet.getConfigName().equals(localSet.getConfigName()) ||
+ localSourceFiles.size() != newSourceFiles.size()) {
+ return false;
+ }
+
+ // compare the source files. The order is not important so it should be normalized
+ // before it's compared.
+ // make copies to sort.
+ localSourceFiles = Lists.newArrayList(localSourceFiles);
+ Collections.sort(localSourceFiles);
+ newSourceFiles = Lists.newArrayList(newSourceFiles);
+ Collections.sort(newSourceFiles);
+
+ if (!localSourceFiles.equals(newSourceFiles)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Finds the {@link DataSet} that contains the given file.
+ * This methods will also performs some checks to make sure the given file is a valid file
+ * in the data set.
+ *
+ * All the information is set in a {@link FileValidity} object that is returned.
+ *
+ * {@link FileValidity} contains information about the changed file including:
+ * - is it from an known set, is it an ignored file, or is it unknown?
+ * - what data set does it belong to
+ * - what source folder in the data set does it belong to.
+ *
+ * "belong" means that the DataSet has a source file/folder that is the root folder
+ * of this file. The folder and/or file doesn't have to exist.
+ *
+ * @param file the file to check
+ *
+ * @return a new FileValidity.
+ */
+ public FileValidity<S> findDataSetContaining(@NonNull File file) {
+ return findDataSetContaining(file, null);
+ }
+
+ /**
+ * Finds the {@link DataSet} that contains the given file.
+ * This methods will also performs some checks to make sure the given file is a valid file
+ * in the data set.
+ *
+ * All the information is set in a {@link FileValidity} object that is returned. If an instance
+ * is passed, then this object is filled instead, and returned.
+ *
+ * {@link FileValidity} contains information about the changed file including:
+ * - is it from an known set, is it an ignored file, or is it unknown?
+ * - what data set does it belong to
+ * - what source folder in the data set does it belong to.
+ *
+ * "belong" means that the DataSet has a source file/folder that is the root folder
+ * of this file. The folder and/or file doesn't have to exist.
+ *
+ * @param file the file to check
+ * @param fileValidity an optional FileValidity to fill. If null a new one is returned.
+ *
+ * @return a new FileValidity or the one given as a parameter.
+ */
+ public FileValidity<S> findDataSetContaining(@NonNull File file,
+ @Nullable FileValidity<S> fileValidity) {
+ if (fileValidity == null) {
+ fileValidity = new FileValidity<S>();
+ }
+
+ if (mDataSets.isEmpty()) {
+ fileValidity.status = FileValidity.FileStatus.UNKNOWN_FILE;
+ return fileValidity;
+ }
+
+ if (DataSet.isIgnored(file)) {
+ fileValidity.status = FileValidity.FileStatus.IGNORED_FILE;
+ return fileValidity;
+ }
+
+ for (S dataSet : mDataSets) {
+ File sourceFile = dataSet.findMatchingSourceFile(file);
+
+ if (sourceFile != null) {
+ fileValidity.dataSet = dataSet;
+ fileValidity.sourceFile = sourceFile;
+ fileValidity.status = dataSet.isValidSourceFile(sourceFile, file) ?
+ FileValidity.FileStatus.VALID_FILE : FileValidity.FileStatus.IGNORED_FILE;
+ return fileValidity;
+ }
+ }
+
+ fileValidity.status = FileValidity.FileStatus.UNKNOWN_FILE;
+ return fileValidity;
+ }
+
+ protected synchronized void createDir(File folder) throws IOException {
+ if (!folder.isDirectory() && !folder.mkdirs()) {
+ throw new IOException("Failed to create directory: " + folder);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(mDataSets.toArray());
+ }
+
+ /**
+ * Method that implements data filters. A data filter will accept or reject a data item.
+ * The default implementation will accept all items but subclasses may override this.
+ * @param dataItem the data item to filter
+ * @return should this data item be accepted?
+ */
+ protected boolean filterAccept(@NonNull I dataItem) {
+ return true;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java b/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java
new file mode 100644
index 0000000..7df0bca
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * Exception when a {@link DataItem} is declared more than once in a {@link DataSet}
+ */
+public class DuplicateDataException extends MergingException {
+
+ private static final String DUPLICATE_RESOURCES = "Duplicate resources";
+
+ DuplicateDataException(Message[] messages) {
+ super(null, messages);
+ }
+
+ static <I extends DataItem> Message[] createMessages(
+ @NonNull Collection<Collection<I>> duplicateDataItemSets) {
+ List<Message> messages = Lists.newArrayListWithCapacity(duplicateDataItemSets.size());
+ for (Collection<I> duplicateItems : duplicateDataItemSets) {
+ ImmutableList.Builder<SourceFilePosition> positions = ImmutableList.builder();
+ for (I item : duplicateItems) {
+ if (!item.isRemoved()) {
+ positions.add(getPosition(item));
+ }
+ }
+ messages.add(new Message(
+ Message.Kind.ERROR,
+ DUPLICATE_RESOURCES /*text*/,
+ DUPLICATE_RESOURCES /*rawMessage*/,
+ Optional.of(RESOURCE_ASSET_MERGER_TOOL_NAME) /*toolName*/,
+ positions.build()));
+ }
+ return Iterables.toArray(messages, Message.class);
+ }
+
+ private static SourceFilePosition getPosition(DataItem item) {
+ DataFile dataFile = item.getSource();
+ if (dataFile == null) {
+ return new SourceFilePosition(new SourceFile(item.getKey()), SourcePosition.UNKNOWN);
+ }
+ File f = dataFile.getFile();
+ SourcePosition sourcePosition = SourcePosition.UNKNOWN; // TODO: find position in file.
+ return new SourceFilePosition(new SourceFile(f, item.getKey()), sourcePosition);
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/FileResourceNameValidator.java b/sdk-common/src/main/java/com/android/ide/common/res2/FileResourceNameValidator.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/FileResourceNameValidator.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/FileResourceNameValidator.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/FileStatus.java b/sdk-common/src/main/java/com/android/ide/common/res2/FileStatus.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/FileStatus.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/FileStatus.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/FileValidity.java b/sdk-common/src/main/java/com/android/ide/common/res2/FileValidity.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/FileValidity.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/FileValidity.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceItem.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceItem.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceItem.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceSet.java
new file mode 100644
index 0000000..6af888d
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/GeneratedResourceSet.java
@@ -0,0 +1,71 @@
+/*
+ * 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.ide.common.res2;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.utils.ILogger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.File;
+
+/**
+ * A {@link ResourceSet} that contains only generated files (e.g. PNGs generated from a vector
+ * drawable XML). It is always a mirror of a normal {@link ResourceSet} which delegates to this
+ * set when it encounters a file that needs to be replaced by generated files.
+ */
+public class GeneratedResourceSet extends ResourceSet {
+
+ public static final String ATTR_GENERATED = "generated";
+
+ public GeneratedResourceSet(ResourceSet originalSet) {
+ super(originalSet.getConfigName() + "$Generated", originalSet.getValidateEnabled());
+ for (File source : originalSet.getSourceFiles()) {
+ addSource(source);
+ }
+ }
+
+ public GeneratedResourceSet(String name) {
+ super(name);
+ }
+
+ @Override
+ protected DataSet<ResourceItem, ResourceFile> createSet(String name) {
+ return new GeneratedResourceSet(name);
+ }
+
+ @Override
+ void appendToXml(@NonNull Node setNode,
+ @NonNull Document document,
+ @NonNull MergeConsumer<ResourceItem> consumer) {
+ NodeUtils.addAttribute(document, setNode, null, ATTR_GENERATED, SdkConstants.VALUE_TRUE);
+ super.appendToXml(setNode, document, consumer);
+ }
+
+ @Override
+ public void loadFromFiles(ILogger logger) throws MergingException {
+ // Do nothing, the original set will hand us the generated files.
+ }
+
+ @Override
+ public File findMatchingSourceFile(File file) {
+ // Do nothing, the original set will hand us the generated files.
+ return null;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java
new file mode 100644
index 0000000..71a3bbf
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+
+import java.io.File;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * A consumer of merges. Used with {@link DataMerger#mergeData(MergeConsumer, boolean)}.
+ */
+public interface MergeConsumer<I extends DataItem> {
+
+ /**
+ * An exception thrown during resource merging by the consumer. It always contains the original
+ * exception as its cause.
+ */
+ class ConsumerException extends MergingException {
+
+ public ConsumerException(@NonNull Throwable cause) {
+ this(cause, SourceFile.UNKNOWN);
+ }
+
+ public ConsumerException(@NonNull Throwable cause, @NonNull File file) {
+ this(cause, new SourceFile(file));
+ }
+
+ private ConsumerException(@NonNull Throwable cause, @NonNull SourceFile file) {
+ super(cause, new Message(
+ Message.Kind.ERROR,
+ Objects.firstNonNull(
+ cause.getLocalizedMessage(),
+ cause.getClass().getCanonicalName()),
+ Throwables.getStackTraceAsString(cause),
+ RESOURCE_ASSET_MERGER_TOOL_NAME,
+ new SourceFilePosition(file, SourcePosition.UNKNOWN)));
+ }
+ }
+
+ /**
+ * Called before the merge starts.
+ */
+ void start(@NonNull DocumentBuilderFactory factory) throws ConsumerException;
+
+ /**
+ * Called after the merge ends.
+ */
+ void end() throws ConsumerException;
+
+ /**
+ * Adds an item. The item may already be existing. Calling {@link DataItem#isTouched()} will
+ * indicate whether the item actually changed.
+ *
+ * @param item the new item.
+ */
+ void addItem(@NonNull I item) throws ConsumerException;
+
+ /**
+ * Removes an item. Optionally pass the item that will replace this one. This methods does not
+ * do the replacement. The replaced item is just there in case the removal can be optimized when
+ * it's a replacement vs. a removal.
+ *
+ * @param removedItem the removed item.
+ * @param replacedBy the optional item that replaces the removed item.
+ */
+ void removeItem(@NonNull I removedItem, @Nullable I replacedBy) throws ConsumerException;
+
+ boolean ignoreItemInMerge(I item);
+
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/MergedAssetWriter.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergedAssetWriter.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/MergedAssetWriter.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/MergedAssetWriter.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java
new file mode 100644
index 0000000..d53aea2
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.DOT_9PNG;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.RES_QUALIFIER_SEP;
+import static com.android.SdkConstants.TAG_RESOURCES;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.MergingLog;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.ide.common.internal.PngException;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * A {@link MergeWriter} for assets, using {@link ResourceItem}.
+ */
+public class MergedResourceWriter extends MergeWriter<ResourceItem> {
+
+ @NonNull
+ private final PngCruncher mCruncher;
+
+ @NonNull
+ private final ResourcePreprocessor mPreprocessor;
+
+ /**
+ * If non-null, points to a File that we should write public.txt to
+ */
+ private final File mPublicFile;
+
+ @Nullable
+ private MergingLog mMergingLog;
+
+ private DocumentBuilderFactory mFactory;
+
+ private final boolean mCrunchPng;
+
+ private final boolean mProcess9Patch;
+
+ private final int mCruncherKey;
+
+ /**
+ * map of XML values files to write after parsing all the files. the key is the qualifier.
+ */
+ private ListMultimap<String, ResourceItem> mValuesResMap;
+
+ /**
+ * Set of qualifier that had a previously written resource now gone. This is to keep a list of
+ * values files that must be written out even with no touched or updated resources, in case one
+ * or more resources were removed.
+ */
+ private Set<String> mQualifierWithDeletedValues;
+
+ public MergedResourceWriter(@NonNull File rootFolder,
+ @NonNull PngCruncher pngRunner,
+ boolean crunchPng,
+ boolean process9Patch,
+ @Nullable File publicFile,
+ @Nullable File blameLogFolder,
+ @NonNull ResourcePreprocessor preprocessor) {
+ super(rootFolder);
+ mCruncher = pngRunner;
+ mCruncherKey = mCruncher.start();
+ mCrunchPng = crunchPng;
+ mProcess9Patch = process9Patch;
+ mPublicFile = publicFile;
+ mMergingLog = blameLogFolder != null ? new MergingLog(blameLogFolder) : null;
+ mPreprocessor = preprocessor;
+ }
+
+ @Override
+ public void start(@NonNull DocumentBuilderFactory factory) throws ConsumerException {
+ super.start(factory);
+ mValuesResMap = ArrayListMultimap.create();
+ mQualifierWithDeletedValues = Sets.newHashSet();
+ mFactory = factory;
+ }
+
+ @Override
+ public void end() throws ConsumerException {
+ // Make sure all PNGs are generated first.
+ super.end();
+ try {
+ // Wait for all PNGs to be crunched.
+ mCruncher.end(mCruncherKey);
+ } catch (InterruptedException e) {
+ throw new ConsumerException(e);
+ }
+
+ if (mMergingLog != null) {
+ try {
+ mMergingLog.write();
+ } catch (IOException e) {
+ throw new ConsumerException(e);
+ }
+ mMergingLog = null;
+ }
+
+ mValuesResMap = null;
+ mQualifierWithDeletedValues = null;
+ mFactory = null;
+ }
+
+ @Override
+ public boolean ignoreItemInMerge(ResourceItem item) {
+ return item.getIgnoredFromDiskMerge();
+ }
+
+ @Override
+ public void addItem(@NonNull final ResourceItem item) throws ConsumerException {
+ final ResourceFile.FileType type = item.getSourceType();
+
+ if (type == ResourceFile.FileType.XML_VALUES) {
+ // this is a resource for the values files
+
+ // just add the node to write to the map based on the qualifier.
+ // We'll figure out later if the files needs to be written or (not)
+ mValuesResMap.put(item.getQualifiers(), item);
+ } else {
+ checkState(item.getSource() != null);
+ // This is a single value file or a set of generated files. Only write it if the state
+ // is TOUCHED.
+ if (item.isTouched()) {
+ getExecutor().execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ File file = item.getFile();
+
+ String filename = file.getName();
+ String folderName = getFolderName(item);
+ File typeFolder = new File(getRootFolder(), folderName);
+ try {
+ createDir(typeFolder);
+ } catch (IOException ioe) {
+ throw MergingException.wrapException(ioe).withFile(typeFolder).build();
+ }
+
+ File outFile = new File(typeFolder, filename);
+
+ if (type == DataFile.FileType.GENERATED_FILES) {
+ try {
+ mPreprocessor.generateFile(file, item.getSource().getFile());
+ } catch (Exception e) {
+ throw new ConsumerException(e, item.getSource().getFile());
+ }
+ }
+
+ try {
+ if (item.getType() == ResourceType.RAW) {
+ // Don't crunch, don't insert source comments, etc - leave alone.
+ Files.copy(file, outFile);
+ } else if (filename.endsWith(DOT_PNG)) {
+ if (mCrunchPng && mProcess9Patch) {
+ mCruncher.crunchPng(mCruncherKey, file, outFile);
+ } else {
+ // we should not crunch the png files, but we should still
+ // process the nine patch.
+ if (mProcess9Patch && filename.endsWith(DOT_9PNG)) {
+ mCruncher.crunchPng(mCruncherKey, file, outFile);
+ } else {
+ Files.copy(file, outFile);
+ }
+ }
+ } else {
+ Files.copy(file, outFile);
+ }
+ if (mMergingLog != null) {
+ mMergingLog.logCopy(file, outFile);
+ }
+ } catch (PngException e) {
+ throw MergingException.wrapException(e).withFile(file).build();
+ } catch (IOException ioe) {
+ throw MergingException.wrapException(ioe).withFile(file).build();
+ }
+ return null;
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void removeItem(@NonNull ResourceItem removedItem, @Nullable ResourceItem replacedBy)
+ throws ConsumerException {
+ ResourceFile.FileType removedType = removedItem.getSourceType();
+ ResourceFile.FileType replacedType = replacedBy != null
+ ? replacedBy.getSourceType()
+ : null;
+
+ switch (removedType) {
+ case SINGLE_FILE: // Fall through.
+ case GENERATED_FILES:
+ if (replacedType == DataFile.FileType.SINGLE_FILE
+ || replacedType == DataFile.FileType.GENERATED_FILES) {
+ // Save one IO operation and don't delete a file that will be overwritten
+ // anyway.
+ break;
+ }
+ removeOutFile(removedItem);
+ break;
+ case XML_VALUES:
+ mQualifierWithDeletedValues.add(removedItem.getQualifiers());
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ protected void postWriteAction() throws ConsumerException {
+
+ // now write the values files.
+ for (String key : mValuesResMap.keySet()) {
+ // the key is the qualifier.
+
+ // check if we have to write the file due to deleted values.
+ // also remove it from that list anyway (to detect empty qualifiers later).
+ boolean mustWriteFile = mQualifierWithDeletedValues.remove(key);
+
+ // get the list of items to write
+ List<ResourceItem> items = mValuesResMap.get(key);
+
+ // now check if we really have to write it
+ if (!mustWriteFile) {
+ for (ResourceItem item : items) {
+ if (item.isTouched()) {
+ mustWriteFile = true;
+ break;
+ }
+ }
+ }
+
+ if (mustWriteFile) {
+ String folderName = key.isEmpty() ?
+ ResourceFolderType.VALUES.getName() :
+ ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key;
+
+ File valuesFolder = new File(getRootFolder(), folderName);
+ // Name of the file is the same as the folder as AAPT gets confused with name
+ // collision when not normalizing folders name.
+ File outFile = new File(valuesFolder, folderName + DOT_XML);
+ ResourceFile currentFile = null;
+ try {
+ createDir(valuesFolder);
+
+ DocumentBuilder builder = mFactory.newDocumentBuilder();
+ Document document = builder.newDocument();
+ final String publicTag = ResourceType.PUBLIC.getName();
+ List<Node> publicNodes = null;
+
+ Node rootNode = document.createElement(TAG_RESOURCES);
+ document.appendChild(rootNode);
+
+ Collections.sort(items);
+
+ for (ResourceItem item : items) {
+ Node nodeValue = item.getValue();
+ if (nodeValue != null && publicTag.equals(nodeValue.getNodeName())) {
+ if (publicNodes == null) {
+ publicNodes = Lists.newArrayList();
+ }
+ publicNodes.add(nodeValue);
+ continue;
+ }
+
+ // add a carriage return so that the nodes are not all on the same line.
+ // also add an indent of 4 spaces.
+ rootNode.appendChild(document.createTextNode("\n "));
+
+ ResourceFile source = item.getSource();
+
+ Node adoptedNode = NodeUtils.adoptNode(document, nodeValue);
+ if (source != null) {
+ XmlUtils.attachSourceFile(
+ adoptedNode, new SourceFile(source.getFile()));
+ }
+ rootNode.appendChild(adoptedNode);
+ }
+
+ // finish with a carriage return
+ rootNode.appendChild(document.createTextNode("\n"));
+
+ currentFile = null;
+
+ final String content;
+
+ if (mMergingLog != null) {
+ Map<SourcePosition, SourceFilePosition> blame = Maps.newLinkedHashMap();
+ content = XmlUtils.toXml(document, blame);
+ mMergingLog.logSource(new SourceFile(outFile), blame);
+ } else {
+ content = XmlUtils.toXml(document);
+ }
+
+ Files.write(content, outFile, Charsets.UTF_8);
+
+ if (publicNodes != null && mPublicFile != null) {
+ // Generate public.txt:
+ int size = publicNodes.size();
+ StringBuilder sb = new StringBuilder(size * 80);
+ for (Node node : publicNodes) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ String name = element.getAttribute(ATTR_NAME);
+ String type = element.getAttribute(ATTR_TYPE);
+ if (!name.isEmpty() && !type.isEmpty()) {
+ sb.append(type).append(' ').append(name).append('\n');
+ }
+ }
+ }
+ File parentFile = mPublicFile.getParentFile();
+ if (!parentFile.exists()) {
+ boolean mkdirs = parentFile.mkdirs();
+ if (!mkdirs) {
+ throw new IOException("Could not create " + parentFile);
+ }
+ }
+ String text = sb.toString();
+ Files.write(text, mPublicFile, Charsets.UTF_8);
+ }
+ } catch (Throwable t) {
+ ConsumerException exception = new ConsumerException(t,
+ currentFile != null ? currentFile.getFile() : outFile);
+ throw exception;
+ }
+ }
+ }
+
+ // now remove empty values files.
+ for (String key : mQualifierWithDeletedValues) {
+ String folderName = key != null && !key.isEmpty() ?
+ ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key :
+ ResourceFolderType.VALUES.getName();
+
+ removeOutFile(folderName, folderName + DOT_XML);
+ }
+ }
+
+ /**
+ * Removes a file that already exists in the out res folder. This has to be a non value file.
+ *
+ * @param resourceItem the source item that created the file to remove.
+ * @return true if success.
+ */
+ private boolean removeOutFile(ResourceItem resourceItem) {
+ return removeOutFile(getFolderName(resourceItem), resourceItem.getFile().getName());
+ }
+
+ /**
+ * Removes a file from a folder based on a sub folder name and a filename
+ *
+ * @param folderName the sub folder name
+ * @param fileName the file name.
+ * @return true if success.
+ */
+ private boolean removeOutFile(String folderName, String fileName) {
+ File valuesFolder = new File(getRootFolder(), folderName);
+ File outFile = new File(valuesFolder, fileName);
+ if (mMergingLog != null) {
+ mMergingLog.logRemove(new SourceFile(outFile));
+ }
+ return outFile.delete();
+ }
+
+ private synchronized void createDir(File folder) throws IOException {
+ if (!folder.isDirectory() && !folder.mkdirs()) {
+ throw new IOException("Failed to create directory: " + folder);
+ }
+ }
+
+ /**
+ * Calculates the right folder name give a resource item.
+ *
+ * @param resourceItem the resource item to calculate the folder name from.
+ * @return a relative folder name
+ */
+ @NonNull
+ private static String getFolderName(ResourceItem resourceItem) {
+ ResourceType itemType = resourceItem.getType();
+ String folderName = itemType.getName();
+ String qualifiers = resourceItem.getQualifiers();
+ if (!qualifiers.isEmpty()) {
+ folderName = folderName + RES_QUALIFIER_SEP + qualifiers;
+ }
+ return folderName;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java
new file mode 100644
index 0000000..c805c71
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.Message.Kind;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import org.xml.sax.SAXParseException;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Exception for errors during merging.
+ */
+public class MergingException extends Exception {
+
+ public static final String MULTIPLE_ERRORS = "Multiple errors:";
+
+ public static final String RESOURCE_ASSET_MERGER_TOOL_NAME = "Resource and asset merger";
+
+ @NonNull
+ private final List<Message> mMessages;
+
+ /**
+ * For internal use. Creates a new MergingException
+ *
+ * @param cause the original exception. May be null.
+ * @param messages the messaged. Must contain at least one item.
+ */
+ protected MergingException(@Nullable Throwable cause, @NonNull Message... messages) {
+ super(messages.length == 1 ? messages[0].getText() : MULTIPLE_ERRORS, cause);
+ mMessages = ImmutableList.copyOf(messages);
+ }
+
+ public static class Builder {
+
+ @Nullable
+ private Throwable mCause = null;
+
+ @Nullable
+ private String mMessageText = null;
+
+ @Nullable
+ private String mOriginalMessageText = null;
+
+ @NonNull
+ private SourceFile mFile = SourceFile.UNKNOWN;
+
+ @NonNull
+ private SourcePosition mPosition = SourcePosition.UNKNOWN;
+
+ private Builder() {
+ }
+
+ public Builder wrapException(@NonNull Throwable cause) {
+ mCause = cause;
+ mOriginalMessageText = Throwables.getStackTraceAsString(cause);
+ return this;
+ }
+
+ public Builder withFile(@NonNull File file) {
+ mFile = new SourceFile(file);
+ return this;
+ }
+
+ public Builder withFile(@NonNull SourceFile file) {
+ mFile = file;
+ return this;
+ }
+
+ public Builder withPosition(@NonNull SourcePosition position) {
+ mPosition = position;
+ return this;
+ }
+
+ public Builder withMessage(@NonNull String messageText, Object... args) {
+ mMessageText = args.length == 0 ? messageText : String.format(messageText, args);
+ return this;
+ }
+
+ public MergingException build() {
+ if (mCause != null) {
+ if (mMessageText == null) {
+ mMessageText = Objects.firstNonNull(
+ mCause.getLocalizedMessage(), mCause.getClass().getCanonicalName());
+ }
+ if (mPosition == SourcePosition.UNKNOWN && mCause instanceof SAXParseException) {
+ SAXParseException exception = (SAXParseException) mCause;
+ int lineNumber = exception.getLineNumber();
+ if (lineNumber != -1) {
+ // Convert positions to be 0-based for SourceFilePosition.
+ mPosition = new SourcePosition(lineNumber - 1,
+ exception.getColumnNumber() - 1, -1);
+ }
+ }
+ }
+
+ if (mMessageText == null) {
+ mMessageText = "Unknown error.";
+ }
+
+ return new MergingException(
+ mCause,
+ new Message(
+ Kind.ERROR,
+ mMessageText,
+ Objects.firstNonNull(mOriginalMessageText, mMessageText),
+ RESOURCE_ASSET_MERGER_TOOL_NAME,
+ new SourceFilePosition(mFile, mPosition)));
+ }
+
+ }
+
+ public static Builder wrapException(@NonNull Throwable cause) {
+ return new Builder().wrapException(cause);
+ }
+
+ public static Builder withMessage(@NonNull String message, Object... args) {
+ return new Builder().withMessage(message, args);
+ }
+
+
+ public static void throwIfNonEmpty(Collection<Message> messages) throws MergingException {
+ if (!messages.isEmpty()) {
+ throw new MergingException(null, Iterables.toArray(messages, Message.class));
+ }
+ }
+
+ @NonNull
+ public List<Message> getMessages() {
+ return mMessages;
+ }
+
+ /**
+ * Computes the error message to display for this error
+ */
+ @NonNull
+ @Override
+ public String getMessage() {
+ List<String> messages = Lists.newArrayListWithCapacity(mMessages.size());
+ for (Message message : mMessages) {
+ StringBuilder sb = new StringBuilder();
+ List<SourceFilePosition> sourceFilePositions = message.getSourceFilePositions();
+ if (sourceFilePositions.size() > 1 || !sourceFilePositions.get(0)
+ .equals(SourceFilePosition.UNKNOWN)) {
+ sb.append(Joiner.on('\t').join(sourceFilePositions));
+ }
+
+ String text = message.getText();
+ if (sb.length() > 0) {
+ sb.append(':').append(' ');
+
+ // ALWAYS insert the string "Error:" between the path and the message.
+ // This is done to make the error messages more simple to detect
+ // (since a generic path: message pattern can match a lot of output, basically
+ // any labeled output, and we don't want to do file existence checks on any random
+ // string to the left of a colon.)
+ if (!text.startsWith("Error: ")) {
+ sb.append("Error: ");
+ }
+ } else if (!text.contains("Error: ")) {
+ sb.append("Error: ");
+ }
+
+ // If the error message already starts with the path, strip it out.
+ // This avoids redundant looking error messages you can end up with
+ // like for example for permission denied errors where the error message
+ // string itself contains the path as a prefix:
+ // /my/full/path: /my/full/path (Permission denied)
+ if (sourceFilePositions.size() == 1) {
+ File file = sourceFilePositions.get(0).getFile().getSourceFile();
+ if (file != null) {
+ String path = file.getAbsolutePath();
+ if (text.startsWith(path)) {
+ int stripStart = path.length();
+ if (text.length() > stripStart && text.charAt(stripStart) == ':') {
+ stripStart++;
+ }
+ if (text.length() > stripStart && text.charAt(stripStart) == ' ') {
+ stripStart++;
+ }
+ text = text.substring(stripStart);
+ }
+ }
+ }
+
+ sb.append(text);
+ messages.add(sb.toString());
+ }
+ return Joiner.on('\n').join(messages);
+ }
+
+ @Override
+ public String toString() {
+ return getMessage();
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/NoOpResourcePreprocessor.java b/sdk-common/src/main/java/com/android/ide/common/res2/NoOpResourcePreprocessor.java
new file mode 100644
index 0000000..fa3ee99
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/NoOpResourcePreprocessor.java
@@ -0,0 +1,42 @@
+/*
+ * 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.ide.common.res2;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * A {@link ResourcePreprocessor} used when no other preprocessor is enabled.
+ */
+public class NoOpResourcePreprocessor implements ResourcePreprocessor {
+
+ @Override
+ public boolean needsPreprocessing(File file) {
+ return false;
+ }
+
+ @Override
+ public Collection<File> getFilesToBeGenerated(File original) {
+ throw new IllegalStateException("Should not be called");
+ }
+
+ @Override
+ public void generateFile(File toBeGenerated, File original) throws IOException {
+ throw new IllegalStateException("Should not be called");
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java b/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java
new file mode 100644
index 0000000..90c0d1f
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.List;
+
+
+/**
+ * Utility class to handle Nodes.
+ *
+ * - convert Node from one XML {@link Document} to be used by another Document
+ * - compare Nodes and attributes.
+ */
+class NodeUtils {
+
+ /**
+ * Makes a new document adopt a node from a different document, and correctly reassign namespace
+ * and prefix
+ * @param document the new document
+ * @param node the node to adopt.
+ * @return the adopted node.
+ */
+ static Node adoptNode(Document document, Node node) {
+ Node newNode = document.adoptNode(node);
+
+ updateNamespace(newNode, document);
+
+ return newNode;
+ }
+
+ /**
+ * Duplicates a node then makes the new document adopt the duplicated node and correctly reassign
+ * namespace and prefix.
+ * @param document the new document
+ * @param node the node to duplicate then adopt.
+ * @return the new node
+ */
+ static Node duplicateAndAdoptNode(Document document, Node node) {
+ Node newNode = duplicateNode(document, node);
+ updateNamespace(newNode, document);
+ return newNode;
+ }
+
+ /**
+ * Duplicates a node. Does not adjust namespaces and prefixes.
+ * @param document the new document
+ * @param node the node to duplicate
+ * @return the new node
+ */
+ static Node duplicateNode(Document document, Node node) {
+ Node newNode;
+ if (node.getNamespaceURI() != null) {
+ newNode = document.createElementNS(node.getNamespaceURI(), node.getLocalName());
+ } else {
+ newNode = document.createElement(node.getNodeName());
+ }
+
+ // copy the attributes
+ NamedNodeMap attributes = node.getAttributes();
+ for (int i = 0 ; i < attributes.getLength(); i++) {
+ Attr attr = (Attr) attributes.item(i);
+
+ Attr newAttr;
+ if (attr.getNamespaceURI() != null) {
+ newAttr = document.createAttributeNS(attr.getNamespaceURI(), attr.getLocalName());
+ newNode.getAttributes().setNamedItemNS(newAttr);
+ } else {
+ newAttr = document.createAttribute(attr.getName());
+ newNode.getAttributes().setNamedItem(newAttr);
+ }
+
+ newAttr.setValue(attr.getValue());
+ }
+
+ // then duplicate the sub-nodes.
+ NodeList children = node.getChildNodes();
+ for (int i = 0 ; i < children.getLength() ; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ Node duplicatedChild = duplicateNode(document, child);
+ newNode.appendChild(duplicatedChild);
+ }
+
+ return newNode;
+ }
+
+ static void addAttribute(Document document, Node node,
+ String namespaceUri, String attrName, String attrValue) {
+ Attr attr;
+ if (namespaceUri != null) {
+ attr = document.createAttributeNS(namespaceUri, attrName);
+ } else {
+ attr = document.createAttribute(attrName);
+ }
+
+ attr.setValue(attrValue);
+
+ if (namespaceUri != null) {
+ node.getAttributes().setNamedItemNS(attr);
+ } else {
+ node.getAttributes().setNamedItem(attr);
+ }
+ }
+
+ /**
+ * Updates the namespace of a given node (and its children) to work in a given document
+ * @param node the node to update
+ * @param document the new document
+ */
+ private static void updateNamespace(Node node, Document document) {
+
+ // first process this node
+ processSingleNodeNamespace(node, document);
+
+ // then its attributes
+ NamedNodeMap attributes = node.getAttributes();
+ if (attributes != null) {
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Node attribute = attributes.item(i);
+ if (!processSingleNodeNamespace(attribute, document)) {
+ String nsUri = attribute.getNamespaceURI();
+ if (nsUri != null) {
+ attributes.removeNamedItemNS(nsUri, attribute.getLocalName());
+ } else {
+ attributes.removeNamedItem(attribute.getLocalName());
+ }
+ }
+ }
+ }
+
+ // then do it for the children nodes.
+ NodeList children = node.getChildNodes();
+ if (children != null) {
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child != null) {
+ updateNamespace(child, document);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the namespace of a given node to work with a given document.
+ *
+ * @param node the node to update
+ * @param document the new document
+ *
+ * @return false if the attribute is to be dropped
+ */
+ private static boolean processSingleNodeNamespace(Node node, Document document) {
+ if ("xmlns".equals(node.getLocalName())) {
+ return false;
+ }
+
+ String ns = node.getNamespaceURI();
+ if (ns != null) {
+ NamedNodeMap docAttributes = getDocumentNamespaceAttributes(document);
+
+ String prefix = getPrefixForNs(docAttributes, ns);
+ if (prefix == null) {
+ prefix = getUniqueNsAttribute(docAttributes);
+ Attr nsAttr = document.createAttribute(prefix);
+ nsAttr.setValue(ns);
+ docAttributes.setNamedItem(nsAttr);
+ }
+
+ // set the prefix on the node, by removing the xmlns: start
+ prefix = prefix.substring(6);
+ node.setPrefix(prefix);
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the attribute map where xmlns:prefix=uri attributes will be stored by updateNamespace.
+ */
+ @VisibleForTesting
+ @NonNull
+ static NamedNodeMap getDocumentNamespaceAttributes(Document document) {
+ NamedNodeMap attributes = document.getChildNodes().item(0).getAttributes();
+ assert attributes != null;
+ return attributes;
+ }
+
+ /**
+ * Looks for an existing prefix for a given namespace.
+ * The prefix must start with "xmlns:". The whole prefix is returned.
+ * @param attributes the attributes to look through
+ * @param namespaceURI the namespace to find.
+ * @return the found prefix or null if none is found.
+ */
+ @VisibleForTesting
+ static String getPrefixForNs(@NonNull NamedNodeMap attributes, String namespaceURI) {
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr)attributes.item(i);
+ if (namespaceURI.equals(attribute.getValue()) && attribute.getName().startsWith(SdkConstants.XMLNS_PREFIX)) {
+ return attribute.getName();
+ }
+ }
+
+ return null;
+ }
+
+ private static String getUniqueNsAttribute(@NonNull NamedNodeMap attributes) {
+ int i = 1;
+ while (true) {
+ String name = String.format("xmlns:ns%d", i++);
+ if (attributes.getNamedItem(name) == null) {
+ return name;
+ }
+ }
+ }
+
+ static boolean compareElementNode(@NonNull Node node1, @NonNull Node node2, boolean strict) {
+ if (!node1.getNodeName().equals(node2.getNodeName())) {
+ return false;
+ }
+
+ NamedNodeMap attr1 = node1.getAttributes();
+ NamedNodeMap attr2 = node2.getAttributes();
+
+ if (!compareAttributes(attr1, attr2)) {
+ return false;
+ }
+
+ if (strict) {
+ return compareChildren(node1.getChildNodes(), node2.getChildNodes());
+ }
+
+ return compareContent(node1.getChildNodes(), node2.getChildNodes());
+ }
+
+ private static boolean compareChildren(
+ @NonNull NodeList children1,
+ @NonNull NodeList children2) {
+ // because this represents a resource values, we're going to be very strict about this
+ // comparison.
+ if (children1.getLength() != children2.getLength()) {
+ return false;
+ }
+
+ for (int i = 0, n = children1.getLength(); i < n; i++) {
+ Node child1 = children1.item(i);
+ Node child2 = children2.item(i);
+
+ short nodeType = child1.getNodeType();
+ if (nodeType != child2.getNodeType()) {
+ return false;
+ }
+
+ switch (nodeType) {
+ case Node.ELEMENT_NODE:
+ if (!compareElementNode(child1, child2, true)) {
+ return false;
+ }
+ break;
+ case Node.CDATA_SECTION_NODE:
+ case Node.TEXT_NODE:
+ case Node.COMMENT_NODE:
+ if (!child1.getNodeValue().equals(child2.getNodeValue())) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean compareContent(
+ @NonNull NodeList children1,
+ @NonNull NodeList children2) {
+ // only compares the content (ie not the text node).
+
+ // accumulate both true children list.
+ List<Node> childList = getElementChildren(children1);
+ List<Node> childList2 = getElementChildren(children2);
+
+ if (childList.size() != childList2.size()) {
+ return false;
+ }
+
+ // no attempt to match nodes one to one.
+ for (Node child : childList) {
+ boolean found = false;
+ for (Node child2 : childList2) {
+ if (compareElementNode(child, child2, false)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @NonNull
+ private static List<Node> getElementChildren(@NonNull NodeList children) {
+ List<Node> results = Lists.newArrayListWithExpectedSize(children.getLength());
+
+ final int len = children.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ results.add(child);
+ }
+ }
+
+ return results;
+ }
+
+ @VisibleForTesting
+ static boolean compareAttributes(
+ @NonNull NamedNodeMap attrMap1,
+ @NonNull NamedNodeMap attrMap2) {
+ if (attrMap1.getLength() != attrMap2.getLength()) {
+ return false;
+ }
+
+ for (int i = 0, n = attrMap1.getLength(); i < n; i++) {
+ Attr attr1 = (Attr) attrMap1.item(i);
+
+ String ns1 = attr1.getNamespaceURI();
+
+ Attr attr2;
+ if (ns1 != null) {
+ attr2 = (Attr) attrMap2.getNamedItemNS(ns1, attr1.getLocalName());
+ } else {
+ attr2 = (Attr) attrMap2.getNamedItem(attr1.getName());
+ }
+
+ if (attr2 == null || !attr2.getValue().equals(attr1.getValue())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Nullable
+ static String getAttribute(@NonNull Node node, @NonNull String attrName) {
+ Attr attr = (Attr) node.getAttributes().getNamedItem(attrName);
+ if (attr != null) {
+ return attr.getValue();
+ }
+ return null;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java
new file mode 100644
index 0000000..48d865e
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceFile.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.google.common.base.Objects;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Represents a file in a resource folders.
+ *
+ * It contains a link to the {@link File}, the qualifier string (which is the name of the folder
+ * after the first '-' character), a list of {@link ResourceItem}s and a type.
+ *
+ * The type of the file is based on whether the file is located in a values folder
+ * ({@link FileType#XML_VALUES}) or in another folder ({@link FileType#SINGLE_FILE} or
+ * {@link FileType#GENERATED_FILES}).
+ */
+public class ResourceFile extends DataFile<ResourceItem> {
+
+ static final String ATTR_QUALIFIER = "qualifiers";
+
+ private String mQualifiers;
+
+ /**
+ * Creates a resource file with a single resource item.
+ *
+ * The source file is set on the item with {@link ResourceItem#setSource(DataFile)}
+ *
+ * The type of the ResourceFile will be {@link FileType#SINGLE_FILE}.
+ *
+ * @param file the File
+ * @param item the resource item
+ * @param qualifiers the qualifiers.
+ */
+ public ResourceFile(@NonNull File file, @NonNull ResourceItem item,
+ @NonNull String qualifiers) {
+ super(file, FileType.SINGLE_FILE);
+ mQualifiers = qualifiers;
+ init(item);
+ }
+
+ /**
+ * Creates a resource file with a list of resource items.
+ *
+ * The source file is set on the items with {@link ResourceItem#setSource(DataFile)}
+ *
+ * The type of the ResourceFile will be {@link FileType#XML_VALUES}.
+ *
+ * @param file the File
+ * @param items the resource items
+ * @param qualifiers the qualifiers.
+ */
+ public ResourceFile(@NonNull File file, @NonNull List<ResourceItem> items,
+ @NonNull String qualifiers) {
+ this(file, items, qualifiers, FileType.XML_VALUES);
+ }
+
+ private ResourceFile(@NonNull File file, @NonNull List<ResourceItem> items,
+ @NonNull String qualifiers, @NonNull FileType fileType) {
+ super(file, fileType);
+ mQualifiers = qualifiers;
+ init(items);
+ }
+
+ public static ResourceFile generatedFiles(
+ @NonNull File file,
+ @NonNull List<ResourceItem> items,
+ @NonNull String qualifiers) {
+ // TODO: Replace other constructors with named methods.
+ return new ResourceFile(file, items, qualifiers, FileType.GENERATED_FILES);
+ }
+
+
+ @NonNull
+ public String getQualifiers() {
+ return mQualifiers;
+ }
+
+ // Used in Studio
+ public void setQualifiers(@NonNull String qualifiers) {
+ mQualifiers = qualifiers;
+ }
+
+ @Override
+ void addExtraAttributes(Document document, Node node, String namespaceUri) {
+ NodeUtils.addAttribute(document, node, namespaceUri, ATTR_QUALIFIER,
+ getQualifiers());
+
+ if (getType() == FileType.GENERATED_FILES) {
+ NodeUtils.addAttribute(document, node, namespaceUri, SdkConstants.ATTR_PREPROCESSING, "true");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(getClass())
+ .add("mFile", mFile)
+ .add("mQualifiers", mQualifiers)
+ .toString();
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceMerger.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceMerger.java
new file mode 100644
index 0000000..ce37c3b
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceMerger.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.TAG_DECLARE_STYLEABLE;
+import static com.android.ide.common.res2.DataFile.FileType;
+import static com.android.ide.common.res2.ResourceFile.ATTR_QUALIFIER;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+import com.android.utils.Pair;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Implementation of {@link DataMerger} for {@link ResourceSet}, {@link ResourceItem}, and
+ * {@link ResourceFile}.
+ */
+public class ResourceMerger extends DataMerger<ResourceItem, ResourceFile, ResourceSet> {
+ private static final String NODE_MERGED_ITEMS = "mergedItems";
+
+ /**
+ * The value for the min SDK.
+ */
+ private int mMinSdk;
+
+ /**
+ * Cached resource keys that should not be in the final artifact. If null, then the
+ * cache will have to be recomputed. To clear the cache, use {@link #clearFilterCache()}.
+ */
+ @Nullable
+ private Set<String> mRejectCache;
+
+ /**
+ * Creates a new resource merger.
+ * @param minSdk the minimum SDK, used for filtering.
+ */
+ public ResourceMerger(int minSdk) {
+ mMinSdk = minSdk;
+ }
+
+ /**
+ * Override of the normal ResourceItem to handle merged item cases.
+ * This is mostly to deal with items that do not have a matching source file.
+ * This override the method returning the qualifier or the source type, to directly
+ * return a value instead of relying on a source file (since merged items don't have any).
+ */
+ private static class MergedResourceItem extends ResourceItem {
+
+ @NonNull
+ private final String mQualifiers;
+
+ /**
+ * Constructs the object with a name, type and optional value.
+ *
+ * Note that the object is not fully usable as-is. It must be added to a ResourceFile first.
+ *
+ * @param name the name of the resource
+ * @param type the type of the resource
+ * @param qualifiers the qualifiers of the resource
+ * @param value an optional Node that represents the resource value.
+ */
+ public MergedResourceItem(
+ @NonNull String name,
+ @NonNull ResourceType type,
+ @NonNull String qualifiers,
+ @Nullable Node value) {
+ super(name, type, value);
+ mQualifiers = qualifiers;
+ }
+
+ @NonNull
+ @Override
+ public String getQualifiers() {
+ return mQualifiers;
+ }
+
+ @Override
+ @NonNull
+ public FileType getSourceType() {
+ return FileType.XML_VALUES;
+ }
+ }
+
+ /**
+ * Map of items that are purely results of merges (ie item that made up of several
+ * original items). The first map key is the associated qualifier for the items,
+ * the second map key is the item name.
+ */
+ protected final Map<String, Map<String, ResourceItem>> mMergedItems = Maps.newHashMap();
+
+
+ /**
+ * Reads the {@link ResourceSet} from the blob XML. {@link ResourceMerger} deals with two kinds
+ * of sets - {@link GeneratedResourceSet} and "plain" {@link ResourceSet} . Instances of the
+ * former are marked with {@code generated="true"} attribute. Instances of the latter have a
+ * {@code generated-set} attribute that references the corresponding generated set by name.
+ * For any variant, the generated set has a lower priority, so it comes in the XML first. This
+ * means we will find it by name at this stage.
+ */
+ @Override
+ protected ResourceSet createFromXml(Node node) throws MergingException {
+ String generated = NodeUtils.getAttribute(node, GeneratedResourceSet.ATTR_GENERATED);
+ ResourceSet set;
+ if (SdkConstants.VALUE_TRUE.equals(generated)) {
+ set = new GeneratedResourceSet("");
+ } else {
+ set = new ResourceSet("");
+ }
+ ResourceSet newResourceSet = (ResourceSet) set.createFromXml(node);
+
+ String generatedSetName = NodeUtils.getAttribute(node, ResourceSet.ATTR_GENERATED_SET);
+ if (generatedSetName != null) {
+ for (ResourceSet resourceSet : getDataSets()) {
+ if (resourceSet.getConfigName().equals(generatedSetName)) {
+ newResourceSet.setGeneratedSet(resourceSet);
+ break;
+ }
+ }
+ }
+
+ String fromDependency = NodeUtils.getAttribute(node, ResourceSet.ATTR_FROM_DEPENDENCY);
+ if (SdkConstants.VALUE_TRUE.equals(fromDependency)) {
+ newResourceSet.setFromDependency(true);
+ }
+
+ return newResourceSet;
+ }
+
+ @Override
+ protected boolean requiresMerge(@NonNull String dataItemKey) {
+ return dataItemKey.startsWith("declare-styleable/");
+ }
+
+ @Override
+ protected void mergeItems(
+ @NonNull String dataItemKey,
+ @NonNull List<ResourceItem> items,
+ @NonNull MergeConsumer<ResourceItem> consumer) throws MergingException {
+ boolean touched = false; // touched becomes true if one is touched.
+ boolean removed = true; // removed stays true if all items are removed.
+ for (ResourceItem item : items) {
+ touched |= item.isTouched();
+ removed &= item.isRemoved();
+ }
+
+ // get the name of the item (the key is the full key not just the same).
+ ResourceItem sourceItem = items.get(0);
+ String itemName = sourceItem.getName();
+ String qualifier = sourceItem.getQualifiers();
+ // get the matching mergedItem
+ ResourceItem previouslyWrittenItem = getMergedItem(qualifier, itemName);
+
+ try {
+ if (touched || (previouslyWrittenItem == null && !removed)) {
+ DocumentBuilder builder = mFactory.newDocumentBuilder();
+ Document document = builder.newDocument();
+
+ Node declareStyleableNode = document.createElementNS(null, TAG_DECLARE_STYLEABLE);
+
+ Attr nameAttr = document.createAttribute(ATTR_NAME);
+ nameAttr.setValue(itemName);
+ declareStyleableNode.getAttributes().setNamedItem(nameAttr);
+
+ // loop through all the items and gather a unique list of nodes.
+ // because we start with the lower priority items, this means that attr with
+ // format inside declare-styleable will be processed first, and added first
+ // while the redundant attr (with no format) will be ignored.
+ Set<String> attrs = Sets.newHashSet();
+
+ for (ResourceItem item : items) {
+ if (!item.isRemoved()) {
+ Node oldDeclareStyleable = item.getValue();
+ if (oldDeclareStyleable != null) {
+ NodeList children = oldDeclareStyleable.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ Node attrNode = children.item(i);
+ if (attrNode.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ if (SdkConstants.TAG_EAT_COMMENT.equals(attrNode.getLocalName())) {
+ continue;
+ }
+
+ // get the name
+ NamedNodeMap attributes = attrNode.getAttributes();
+ nameAttr = (Attr) attributes.getNamedItemNS(null, ATTR_NAME);
+ if (nameAttr == null) {
+ continue;
+ }
+
+ String name = nameAttr.getNodeValue();
+ if (attrs.contains(name)) {
+ continue;
+ }
+
+ // duplicate the node.
+ attrs.add(name);
+ Node newAttrNode = NodeUtils.duplicateNode(document, attrNode);
+ declareStyleableNode.appendChild(newAttrNode);
+ }
+ }
+ }
+ }
+
+ // always write it for now.
+ MergedResourceItem newItem = new MergedResourceItem(
+ itemName,
+ sourceItem.getType(),
+ qualifier,
+ declareStyleableNode);
+
+ // check whether the result of the merge is new or touched compared
+ // to the previous state.
+ //noinspection ConstantConditions
+ if (previouslyWrittenItem == null ||
+ !NodeUtils.compareElementNode(newItem.getValue(), previouslyWrittenItem.getValue(), false)) {
+ newItem.setTouched();
+ }
+
+ // then always add it both to the list of merged items in the merge
+ // and to the consumer.
+ addMergedItem(qualifier, newItem);
+ consumer.addItem(newItem);
+
+ } else if (previouslyWrittenItem != null) {
+ // since we are keeping the previous merge item, no need
+ // to add it internally, just send it to the consumer.
+ if (removed) {
+ consumer.removeItem(previouslyWrittenItem, null);
+ } else {
+ // don't need to compute but we need to write the item anyway since
+ // the item might be written due to the values file requiring (re)writing due
+ // to another res change
+ consumer.addItem(previouslyWrittenItem);
+ }
+ }
+ } catch (ParserConfigurationException e) {
+ throw MergingException.wrapException(e).build();
+ }
+ }
+
+ @Nullable
+ private ResourceItem getMergedItem(@NonNull String qualifiers, @NonNull String name) {
+ Map<String, ResourceItem> map = mMergedItems.get(qualifiers);
+ if (map != null) {
+ return map.get(name);
+ }
+
+ return null;
+ }
+
+ @NonNull
+ @Override
+ protected String getAdditionalDataTagName() {
+ return NODE_MERGED_ITEMS;
+ }
+
+ @Override
+ protected void loadAdditionalData(@NonNull Node mergedItemsNode, boolean incrementalState) throws MergingException {
+ // only load the merged item in incremental state.
+ // In non incremental state, they will be recreated by the touched
+ // items anyway.
+ if (!incrementalState) {
+ return;
+ }
+
+ // loop on the qualifiers.
+ NodeList configurationList = mergedItemsNode.getChildNodes();
+
+ for (int j = 0, n2 = configurationList.getLength(); j < n2; j++) {
+ Node configuration = configurationList.item(j);
+
+ if (configuration.getNodeType() != Node.ELEMENT_NODE ||
+ !NODE_CONFIGURATION.equals(configuration.getLocalName())) {
+ continue;
+ }
+
+ // get the qualifier value.
+ Attr qualifierAttr = (Attr) configuration.getAttributes().getNamedItem(
+ ATTR_QUALIFIER);
+ if (qualifierAttr == null) {
+ continue;
+ }
+
+ String qualifier = qualifierAttr.getValue();
+
+ // get the resource items
+ NodeList itemList = configuration.getChildNodes();
+
+ for (int k = 0, n3 = itemList.getLength(); k < n3; k++) {
+ Node itemNode = itemList.item(k);
+
+ if (itemNode.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ ResourceItem item = getMergedResourceItem(itemNode, qualifier);
+ if (item != null) {
+ addMergedItem(qualifier, item);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void writeAdditionalData(Document document, Node rootNode) {
+ Node mergedItemsNode = document.createElement(getAdditionalDataTagName());
+ rootNode.appendChild(mergedItemsNode);
+
+ for (String qualifier : mMergedItems.keySet()) {
+ Map<String, ResourceItem> itemMap = mMergedItems.get(qualifier);
+
+ Node qualifierNode = document.createElement(NODE_CONFIGURATION);
+ NodeUtils.addAttribute(document, qualifierNode, null, ATTR_QUALIFIER,
+ qualifier);
+
+ mergedItemsNode.appendChild(qualifierNode);
+
+ for (ResourceItem item : itemMap.values()) {
+ Node adoptedNode = item.getDetailsXml(document);
+ if (adoptedNode != null) {
+ qualifierNode.appendChild(adoptedNode);
+ }
+ }
+ }
+ }
+
+ private void addMergedItem(@NonNull String qualifier, @NonNull ResourceItem item) {
+ Map<String, ResourceItem> map = mMergedItems.get(qualifier);
+ if (map == null) {
+ map = Maps.newHashMap();
+ mMergedItems.put(qualifier, map);
+ }
+
+ map.put(item.getName(), item);
+ }
+
+ /**
+ * Returns a new ResourceItem object for a given node.
+ * @param node the node representing the resource.
+ * @return a ResourceItem object or null.
+ */
+ static MergedResourceItem getMergedResourceItem(@NonNull Node node, @NonNull String qualifiers)
+ throws MergingException {
+ ResourceType type = ValueResourceParser2.getType(node, null);
+ String name = ValueResourceParser2.getName(node);
+
+ if (name != null && type != null) {
+ return new MergedResourceItem(name, type, qualifiers, node);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void addDataSet(ResourceSet resourceSet) {
+ super.addDataSet(resourceSet);
+ }
+
+
+ /*
+ * Overridden to clear the cache filter between runs. Building the cache is relatively cheap
+ * and we're safer not reusing it between runs of mergeData.
+ */
+ @Override
+ public void mergeData(@NonNull MergeConsumer<ResourceItem> consumer, boolean doCleanUp)
+ throws MergingException {
+ clearFilterCache();
+ super.mergeData(consumer, doCleanUp);
+ }
+
+
+ @Override
+ protected boolean filterAccept(@NonNull ResourceItem dataItem) {
+ if (mRejectCache == null) {
+ buildCache();
+ }
+
+ /*
+ * We will accept all resources except those we explicitly know we can reject.
+ */
+ boolean accepted;
+ if (mRejectCache.contains(dataItem.getKey())) {
+ accepted = false;
+ } else {
+ accepted = true;
+ }
+
+ return accepted;
+ }
+
+ /**
+ * Builds the reject filter cache.
+ */
+ private void buildCache() {
+ mRejectCache = Sets.newHashSet();
+
+ /*
+ * Temporary cache. For each resource name, maps it to the best resource (min SDK and
+ * resource item) found so far. Because we need to filter by folder configuration, we
+ * maintain the best resource per folder configuration per resource.
+ *
+ * Only resource items whose min SDK is less
+ * than or equal to minSdk will be included here as all others will be accepted.
+ */
+ Table<String, FolderConfiguration, Pair<Integer, ResourceItem>> itemCache =
+ HashBasedTable.create();
+
+ /*
+ * Keys of resources we know we will accept. Only used to speed up resources with
+ * duplicate keys.
+ */
+ Set<String> acceptCache = Sets.newHashSet();
+
+ for (ResourceSet resourceSet : getDataSets()) {
+ ListMultimap<String, ResourceItem> map = resourceSet.getDataMap();
+ for (ResourceItem resourceItem : map.values()) {
+ /*
+ * Resources in different libraries may end up with the same key.
+ */
+ String resourceKey = resourceItem.getKey();
+
+ if (acceptCache.contains(resourceKey) || mRejectCache.contains(resourceKey)) {
+ /*
+ * Second time we're seeing this resource. We already know whether to
+ * accept or reject it, so there's nothing to do.
+ */
+ continue;
+ }
+
+ if (resourceItem.getSourceType() != FileType.SINGLE_FILE) {
+ /*
+ * This is a resource that is contained in a file that has multiple items.
+ * We never filter these out.
+ */
+ acceptCache.add(resourceKey);
+ continue;
+ }
+
+ /*
+ * Compute what the resource's qualifier is. Keep the SDK version separate
+ * because we'll handle that in a special way.
+ */
+ FolderConfiguration config = resourceItem.getConfiguration();
+ FolderConfiguration qualifierWithoutSdk;
+
+ int resourceMinSdk;
+ if (config.getVersionQualifier() == null
+ || !config.getVersionQualifier().isValid()) {
+ resourceMinSdk = 0;
+ qualifierWithoutSdk = config;
+ } else {
+ resourceMinSdk = config.getVersionQualifier().getVersion();
+ qualifierWithoutSdk = FolderConfiguration.copyOf(config);
+ qualifierWithoutSdk.removeQualifier(
+ qualifierWithoutSdk.getVersionQualifier());
+ }
+
+ if (resourceMinSdk > mMinSdk) {
+ /*
+ * We only filter resources that have min SDK <= minSdk because others
+ * cannot be guaranteed that won't be needed.
+ */
+ acceptCache.add(resourceKey);
+ continue;
+ }
+
+ /*
+ * Get the cache entry for the resource. resourceCacheId will contains a string
+ * which is unique for resource type / resource name. Resources with the same
+ * type or name but different qualifiers will have the same cache ID.
+ */
+ String resourceCacheId = resourceItem.getType().getName()
+ + SdkConstants.RES_QUALIFIER_SEP + resourceItem.getName();
+ Pair<Integer, ResourceItem> selectedResource = itemCache.get(resourceCacheId,
+ qualifierWithoutSdk);
+
+ if (selectedResource == null) {
+ /*
+ * We have never found a resource for this folder configuration with an
+ * SDK lower than or equal to minSdk.
+ */
+ selectedResource = Pair.of(resourceMinSdk, resourceItem);
+ itemCache.put(resourceCacheId, qualifierWithoutSdk, selectedResource);
+ acceptCache.add(resourceKey);
+ continue;
+ }
+
+ if (selectedResource.getFirst() > resourceMinSdk) {
+ /*
+ * Cache has a better resource that is still lower than or equal to minSdk.
+ * The current resource will never be used so it will be added to the
+ * reject set.
+ */
+ mRejectCache.add(resourceKey);
+ } else {
+ /*
+ * The current resource is better than or equal to the one in the cache
+ * and is still lower than or equal to minSdk. This means we will
+ * want to use the current one instead of the one we placed in the cache.
+ */
+ String removeKey = selectedResource.getSecond().getKey();
+ acceptCache.remove(removeKey);
+ mRejectCache.add(removeKey);
+ acceptCache.add(resourceKey);
+
+ selectedResource = Pair.of(resourceMinSdk, resourceItem);
+ itemCache.put(resourceCacheId, qualifierWithoutSdk, selectedResource);
+ }
+ }
+ }
+ }
+
+ /**
+ * Clears the filter cache.
+ */
+ private void clearFilterCache() {
+ mRejectCache = null;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourcePreprocessor.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourcePreprocessor.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/ResourcePreprocessor.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/ResourcePreprocessor.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceRepository.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceRepository.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/ResourceRepository.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/ResourceRepository.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java
new file mode 100644
index 0000000..d9c8be6
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.ide.common.res2.ResourceFile.ATTR_QUALIFIER;
+import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceConstants;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.utils.ILogger;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of {@link DataSet} for {@link ResourceItem} and {@link ResourceFile}.
+ *
+ * This is able to detect duplicates from the same source folders (same resource coming from
+ * the values folder in same or different files).
+ */
+public class ResourceSet extends DataSet<ResourceItem, ResourceFile> {
+ public static final String ATTR_GENERATED_SET = "generated-set";
+ public static final String ATTR_FROM_DEPENDENCY = "from-dependency";
+
+ private ResourceSet mGeneratedSet;
+ private ResourcePreprocessor mPreprocessor;
+ private boolean mIsFromDependency;
+ private boolean mShouldParseResourceIds;
+ private boolean mDontNormalizeQualifiers;
+ private boolean mTrackSourcePositions = true;
+
+ public ResourceSet(String name) {
+ this(name, true /*validateEnabled*/);
+ }
+
+ public ResourceSet(String name, boolean validateEnabled) {
+ super(name, validateEnabled);
+ mPreprocessor = new NoOpResourcePreprocessor();
+ }
+
+ public void setGeneratedSet(ResourceSet generatedSet) {
+ mGeneratedSet = generatedSet;
+ }
+
+ public void setPreprocessor(@NonNull ResourcePreprocessor preprocessor) {
+ mPreprocessor = checkNotNull(preprocessor);
+ }
+
+ @Override
+ protected DataSet<ResourceItem, ResourceFile> createSet(String name) {
+ return new ResourceSet(name);
+ }
+
+ @Override
+ protected ResourceFile createFileAndItems(File sourceFolder, File file, ILogger logger)
+ throws MergingException {
+ // get the type.
+ FolderData folderData = getFolderData(file.getParentFile());
+
+ if (folderData == null) {
+ return null;
+ }
+
+ return createResourceFile(file, folderData, logger);
+ }
+
+ @Override
+ protected ResourceFile createFileAndItemsFromXml(@NonNull File file, @NonNull Node fileNode)
+ throws MergingException {
+ String qualifier = firstNonNull(NodeUtils.getAttribute(fileNode, ATTR_QUALIFIER), "");
+ String typeAttr = NodeUtils.getAttribute(fileNode, SdkConstants.ATTR_TYPE);
+
+ if (NodeUtils.getAttribute(fileNode, SdkConstants.ATTR_PREPROCESSING) != null) {
+ // FileType.GENERATED_FILES
+ NodeList childNodes = fileNode.getChildNodes();
+ int childCount = childNodes.getLength();
+
+ List<ResourceItem> resourceItems =
+ Lists.newArrayListWithCapacity(childCount);
+
+ for (int i = 0; i < childCount; i++) {
+ Node childNode = childNodes.item(i);
+
+ String path = NodeUtils.getAttribute(childNode, SdkConstants.ATTR_PATH);
+ if (path == null) {
+ continue;
+ }
+
+ File generatedFile = new File(path);
+ String resourceType = NodeUtils.getAttribute(childNode, SdkConstants.ATTR_TYPE);
+ if (resourceType == null) {
+ continue;
+ }
+ String qualifers = NodeUtils.getAttribute(childNode, ATTR_QUALIFIER);
+ if (qualifers == null) {
+ continue;
+ }
+
+ resourceItems.add(
+ new GeneratedResourceItem(
+ getNameForFile(generatedFile),
+ generatedFile,
+ FolderTypeRelationship
+ .getRelatedResourceTypes(
+ ResourceFolderType.getTypeByName(resourceType))
+ .get(0),
+ qualifers));
+ }
+
+ return ResourceFile.generatedFiles(file, resourceItems, qualifier);
+ }
+ else if (typeAttr == null) {
+ // FileType.XML_VALUES
+ List<ResourceItem> resourceList = Lists.newArrayList();
+
+ // loop on each node that represent a resource
+ NodeList resNodes = fileNode.getChildNodes();
+ for (int iii = 0, nnn = resNodes.getLength(); iii < nnn; iii++) {
+ Node resNode = resNodes.item(iii);
+
+ if (resNode.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ ResourceItem r = ValueResourceParser2.getResource(resNode, file);
+ if (r != null) {
+ resourceList.add(r);
+ if (r.getType() == ResourceType.DECLARE_STYLEABLE) {
+ // Need to also create ATTR items for its children
+ try {
+ ValueResourceParser2.addStyleableItems(resNode, resourceList, null, file);
+ } catch (MergingException ignored) {
+ // since we are not passing a dup map, this will never be thrown
+ assert false : file + ": " + ignored.getMessage();
+ }
+ }
+ }
+ }
+
+ return new ResourceFile(file, resourceList, qualifier);
+
+ } else {
+ // single res file
+ ResourceType type = ResourceType.getEnum(typeAttr);
+ if (type == null) {
+ return null;
+ }
+
+ String nameAttr = NodeUtils.getAttribute(fileNode, ATTR_NAME);
+ if (nameAttr == null) {
+ return null;
+ }
+
+ if (getValidateEnabled()) {
+ FileResourceNameValidator.validate(file, type);
+ }
+ ResourceItem item = new ResourceItem(nameAttr, type, null);
+ return new ResourceFile(file, item, qualifier);
+ }
+ }
+
+ @Override
+ protected void readSourceFolder(File sourceFolder, ILogger logger)
+ throws MergingException {
+ List<Message> errors = Lists.newArrayList();
+ File[] folders = sourceFolder.listFiles();
+ if (folders != null) {
+ for (File folder : folders) {
+ if (folder.isDirectory() && !isIgnored(folder)) {
+ FolderData folderData = getFolderData(folder);
+ if (folderData != null) {
+ try {
+ parseFolder(sourceFolder, folder, folderData, logger);
+ } catch (MergingException e) {
+ errors.addAll(e.getMessages());
+ }
+ }
+ }
+ }
+ }
+ MergingException.throwIfNonEmpty(errors);
+ }
+
+ @Override
+ protected boolean isValidSourceFile(@NonNull File sourceFolder, @NonNull File file) {
+ if (!super.isValidSourceFile(sourceFolder, file)) {
+ return false;
+ }
+
+ File resFolder = file.getParentFile();
+ // valid files are right under a resource folder under the source folder
+ return resFolder.getParentFile().equals(sourceFolder) &&
+ !isIgnored(resFolder) &&
+ ResourceFolderType.getFolderType(resFolder.getName()) != null;
+ }
+
+ @Override
+ protected boolean handleNewFile(File sourceFolder, File file, ILogger logger)
+ throws MergingException {
+ ResourceFile resourceFile = createFileAndItems(sourceFolder, file, logger);
+ processNewResourceFile(sourceFolder, resourceFile);
+ return true;
+ }
+
+ @Override
+ protected boolean handleRemovedFile(File removedFile) {
+ if (mGeneratedSet != null && mGeneratedSet.getDataFile(removedFile) != null) {
+ return mGeneratedSet.handleRemovedFile(removedFile);
+ } else {
+ return super.handleRemovedFile(removedFile);
+ }
+ }
+
+ @Override
+ protected boolean handleChangedFile(
+ @NonNull File sourceFolder,
+ @NonNull File changedFile,
+ @NonNull ILogger logger) throws MergingException {
+ FolderData folderData = getFolderData(changedFile.getParentFile());
+ if (folderData == null) {
+ return true;
+ }
+
+ ResourceFile resourceFile = getDataFile(changedFile);
+ if (mGeneratedSet == null) {
+ // This is a generated set.
+ doHandleChangedFile(changedFile, resourceFile);
+ return true;
+ }
+
+ ResourceFile generatedSetResourceFile = mGeneratedSet.getDataFile(changedFile);
+ boolean needsPreprocessing = needsPreprocessing(changedFile);
+
+ if (resourceFile != null && generatedSetResourceFile == null && needsPreprocessing) {
+ // It didn't use to need preprocessing, but it does now.
+ handleRemovedFile(changedFile);
+ mGeneratedSet.handleNewFile(sourceFolder, changedFile, logger);
+ } else if (resourceFile == null
+ && generatedSetResourceFile != null
+ && !needsPreprocessing) {
+ // It used to need preprocessing, but not anymore.
+ mGeneratedSet.handleRemovedFile(changedFile);
+ handleNewFile(sourceFolder, changedFile, logger);
+ } else if (resourceFile == null
+ && generatedSetResourceFile != null
+ && needsPreprocessing) {
+ // Delegate to the generated set.
+ mGeneratedSet.handleChangedFile(sourceFolder, changedFile, logger);
+ } else if (resourceFile != null
+ && !needsPreprocessing
+ && generatedSetResourceFile == null) {
+ // The "normal" case, handle it here.
+ doHandleChangedFile(changedFile, resourceFile);
+ } else {
+ // Something strange happened.
+ throw MergingException.withMessage("In DataSet '%s', no data file for changedFile. "
+ + "This is an internal error in the incremental builds code; "
+ + "to work around it, try doing a full clean build.",
+ getConfigName()).withFile(changedFile).build();
+ }
+
+ return true;
+ }
+
+ private void doHandleChangedFile(@NonNull File changedFile, ResourceFile resourceFile)
+ throws MergingException {
+ switch (resourceFile.getType()) {
+ case SINGLE_FILE:
+ // single res file
+ resourceFile.getItem().setTouched();
+ break;
+ case GENERATED_FILES:
+ handleChangedItems(resourceFile,
+ getResourceItemsForGeneratedFiles(changedFile));
+ break;
+ case XML_VALUES:
+ // multi res. Need to parse the file and compare the items one by one.
+ ValueResourceParser2 parser = new ValueResourceParser2(changedFile);
+
+ List<ResourceItem> parsedItems = parser.parseFile();
+ handleChangedItems(resourceFile, parsedItems);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private void handleChangedItems(
+ ResourceFile resourceFile,
+ List<ResourceItem> currentItems) throws MergingException {
+ Map<String, ResourceItem> oldItems = Maps.newHashMap(resourceFile.getItemMap());
+ Map<String, ResourceItem> addedItems = Maps.newHashMap();
+
+ // Set the source of newly determined items, so we can call getKey() on them.
+ for (ResourceItem currentItem : currentItems) {
+ currentItem.setSource(resourceFile);
+ }
+
+ for (ResourceItem newItem : currentItems) {
+ String newKey = newItem.getKey();
+ ResourceItem oldItem = oldItems.get(newKey);
+
+ if (oldItem == null) {
+ // this is a new item
+ newItem.setTouched();
+ addedItems.put(newKey, newItem);
+ } else {
+ // remove it from the list of oldItems (this is to detect deletion)
+ //noinspection SuspiciousMethodCalls
+ oldItems.remove(oldItem.getKey());
+
+ if (oldItem.getSource().getType() == DataFile.FileType.XML_VALUES) {
+ if (!oldItem.compareValueWith(newItem)) {
+ // if the values are different, take the values from the newItem
+ // and update the old item status.
+
+ oldItem.setValue(newItem);
+ }
+ } else {
+ oldItem.setTouched();
+ }
+ }
+ }
+
+ // at this point oldItems is left with the deleted items.
+ // just update their status to removed.
+ for (ResourceItem deletedItem : oldItems.values()) {
+ deletedItem.setRemoved();
+ }
+
+ // Now we need to add the new items to the resource file and the main map
+ for (Map.Entry<String, ResourceItem> entry : addedItems.entrySet()) {
+ // Clear the item from the old file so it can be added to the new one.
+ entry.getValue().setSource(null);
+ addItem(entry.getValue(), entry.getKey());
+ }
+
+ resourceFile.addItems(addedItems.values());
+ }
+
+ /**
+ * Reads the content of a typed resource folder (sub folder to the root of res folder), and
+ * loads the resources from it.
+ *
+ *
+ * @param sourceFolder the main res folder
+ * @param folder the folder to read.
+ * @param folderData the folder Data
+ * @param logger a logger object
+ *
+ * @throws MergingException if something goes wrong
+ */
+ private void parseFolder(File sourceFolder, File folder, FolderData folderData, ILogger logger)
+ throws MergingException {
+ File[] files = folder.listFiles();
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ if (!file.isFile() || isIgnored(file)) {
+ continue;
+ }
+
+ ResourceFile resourceFile = createResourceFile(file, folderData, logger);
+ processNewResourceFile(sourceFolder, resourceFile);
+ }
+ }
+ }
+
+ private void processNewResourceFile(File sourceFolder, ResourceFile resourceFile)
+ throws MergingException {
+ if (resourceFile != null) {
+ if (resourceFile.getType() == DataFile.FileType.GENERATED_FILES
+ && mGeneratedSet != null) {
+ mGeneratedSet.processNewDataFile(sourceFolder, resourceFile, true);
+ } else {
+ processNewDataFile(sourceFolder, resourceFile, true /*setTouched*/);
+ }
+ }
+ }
+
+ private ResourceFile createResourceFile(@NonNull File file,
+ @NonNull FolderData folderData, @NonNull ILogger logger) throws MergingException {
+ if (folderData.type != null) {
+ if (getValidateEnabled()) {
+ FileResourceNameValidator.validate(file, folderData.type);
+ }
+
+ if (needsPreprocessing(file)) {
+ return ResourceFile.generatedFiles(
+ file,
+ getResourceItemsForGeneratedFiles(file),
+ folderData.qualifiers);
+ } else {
+ return new ResourceFile(
+ file,
+ new ResourceItem(getNameForFile(file), folderData.type, null),
+ folderData.qualifiers);
+ }
+ } else {
+ try {
+ ValueResourceParser2 parser = new ValueResourceParser2(file);
+ List<ResourceItem> items = parser.parseFile();
+
+ return new ResourceFile(file, items, folderData.qualifiers);
+ } catch (MergingException e) {
+ logger.error(e, "Failed to parse %s", file.getAbsolutePath());
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Determine if the given file needs preprocessing. We don't preprocess files that come from
+ * dependencies, since they should have been preprocessed when creating the AAR.
+ */
+ private boolean needsPreprocessing(@NonNull File file) {
+ return !this.isFromDependency() && mPreprocessor.needsPreprocessing(file);
+ }
+
+ @NonNull
+ private List<ResourceItem> getResourceItemsForGeneratedFiles(
+ @NonNull File file)
+ throws MergingException {
+ List<ResourceItem> resourceItems = new ArrayList<ResourceItem>();
+
+ for (File generatedFile : mPreprocessor.getFilesToBeGenerated(file)) {
+ FolderData generatedFileFolderData =
+ getFolderData(generatedFile.getParentFile());
+
+ checkState(
+ generatedFileFolderData != null,
+ "Can't determine folder type for %s",
+ generatedFile.getPath());
+
+ resourceItems.add(
+ new GeneratedResourceItem(
+ getNameForFile(generatedFile),
+ generatedFile,
+ generatedFileFolderData.type,
+ generatedFileFolderData.qualifiers));
+ }
+ return resourceItems;
+ }
+
+ @NonNull
+ private static String getNameForFile(@NonNull File file) {
+ String name = file.getName();
+ int pos = name.indexOf('.'); // get the resource name based on the filename
+ if (pos >= 0) {
+ name = name.substring(0, pos);
+ }
+ return name;
+ }
+
+ public boolean isFromDependency() {
+ return mIsFromDependency;
+ }
+
+ public void setFromDependency(boolean fromDependency) {
+ mIsFromDependency = fromDependency;
+ }
+
+ /**
+ * temp structure containing a qualifier string and a {@link com.android.resources.ResourceType}.
+ */
+ private static class FolderData {
+ String qualifiers = "";
+ ResourceType type = null;
+ ResourceFolderType folderType = null;
+ }
+
+ /**
+ * Returns a FolderData for the given folder.
+ *
+ * @param folder the folder.
+ * @return the FolderData object, or null if we can't determine the {#link ResourceFolderType}
+ * of the folder.
+ */
+ @Nullable
+ private FolderData getFolderData(File folder) throws MergingException {
+ FolderData fd = new FolderData();
+
+ String folderName = folder.getName();
+ int pos = folderName.indexOf(ResourceConstants.RES_QUALIFIER_SEP);
+ if (pos != -1) {
+ fd.folderType = ResourceFolderType.getTypeByName(folderName.substring(0, pos));
+ if (fd.folderType == null) {
+ return null;
+ }
+
+ FolderConfiguration folderConfiguration = FolderConfiguration.getConfigForFolder(folderName);
+ if (folderConfiguration == null) {
+ throw MergingException.withMessage("Invalid resource directory name")
+ .withFile(folder).build();
+ }
+
+ // normalize it
+ folderConfiguration.normalize();
+
+ // get the qualifier portion from the folder config.
+ // the returned string starts with "-" so we remove that.
+ fd.qualifiers = folderConfiguration.getUniqueKey().substring(1);
+
+ } else {
+ fd.folderType = ResourceFolderType.getTypeByName(folderName);
+ }
+
+ if (fd.folderType != null && fd.folderType != ResourceFolderType.VALUES) {
+ fd.type = FolderTypeRelationship.getRelatedResourceTypes(fd.folderType).get(0);
+ }
+
+ return fd;
+ }
+
+ @Override
+ void appendToXml(@NonNull Node setNode, @NonNull Document document,
+ @NonNull MergeConsumer<ResourceItem> consumer) {
+ if (mGeneratedSet != null) {
+ NodeUtils.addAttribute(
+ document,
+ setNode,
+ null,
+ ATTR_GENERATED_SET,
+ mGeneratedSet.getConfigName());
+ }
+
+ if (mIsFromDependency) {
+ NodeUtils.addAttribute(
+ document,
+ setNode,
+ null,
+ ATTR_FROM_DEPENDENCY,
+ SdkConstants.VALUE_TRUE);
+ }
+
+ super.appendToXml(setNode, document, consumer);
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/SourceSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/SourceSet.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/SourceSet.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/SourceSet.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceNameValidator.java b/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceNameValidator.java
new file mode 100644
index 0000000..d5f3d9f
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceNameValidator.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceType;
+
+import java.io.File;
+
+import javax.lang.model.SourceVersion;
+
+public final class ValueResourceNameValidator {
+
+ private ValueResourceNameValidator() {
+ }
+
+ /**
+ * Validate a value resource name.
+ *
+ * @param resourceName the resource name to validate.
+ * @param resourceType the resource type.
+ * @param file the file the resource came from.
+ * @throws MergingException is the resource name is not valid.
+ */
+ public static void validate(@NonNull String resourceName, @NonNull ResourceType resourceType,
+ @Nullable File file)
+ throws MergingException {
+ String error = getErrorText(resourceName, resourceType);
+ if (error != null) {
+ // TODO find location in file.
+ MergingException.Builder exception = MergingException.withMessage(error);
+ if (file != null) {
+ exception.withFile(file);
+ }
+ throw exception.build();
+ }
+ }
+
+ /**
+ * Validate a value resource name.
+ *
+ * @param fullResourceName the resource name to validate.
+ * @param resourceType the resource type.
+ * @return null if no error, otherwise a string describing the error.
+ */
+ @Nullable
+ public static String getErrorText(@NonNull String fullResourceName,
+ @Nullable ResourceType resourceType) {
+
+ if (resourceType == ResourceType.ATTR) {
+ if (fullResourceName.startsWith("android:")) {
+ fullResourceName = fullResourceName.substring(8);
+ }
+ }
+ final String resourceName = fullResourceName.replace('.', '_');
+
+ // Resource names must be valid Java identifiers, since they will
+ // be represented as Java identifiers in the R file:
+ if (!SourceVersion.isIdentifier(resourceName)) {
+ if (resourceName.isEmpty()) {
+ return "The resource name shouldn't be empty";
+ } else if (!Character.isJavaIdentifierStart(resourceName.charAt(0))) {
+ return "The resource name must start with a letter";
+ } else {
+ for (int i = 1, n = resourceName.length(); i < n; i++) {
+ char c = resourceName.charAt(i);
+ if (!Character.isJavaIdentifierPart(c)) {
+ return String
+ .format("'%1$c' is not a valid resource name character", c);
+ }
+ }
+ }
+ }
+
+ if (SourceVersion.isKeyword(resourceName)) {
+ return String.format("%1$s is not a valid resource name (reserved Java keyword)",
+ resourceName);
+ }
+
+ // Success.
+ return null;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java b/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java
rename to sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java b/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java
new file mode 100644
index 0000000..bcae9a8
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.SdkConstants.AMP_ENTITY;
+import static com.android.SdkConstants.APOS_ENTITY;
+import static com.android.SdkConstants.GT_ENTITY;
+import static com.android.SdkConstants.LT_ENTITY;
+import static com.android.SdkConstants.QUOT_ENTITY;
+
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+
+/**
+ * Helper class to help with XML values resource file.
+ */
+public class ValueXmlHelper {
+
+ /**
+ * Replaces escapes in an XML resource string with the actual characters,
+ * performing unicode substitutions (replacing any {@code \\uNNNN} references in the
+ * given string with the corresponding unicode characters), etc.
+ *
+ * @param s the string to unescape
+ * @param escapeEntities XML entities
+ * @param trim whether surrounding space and quotes should be trimmed
+ * @return the string with the escape characters removed and expanded
+ */
+ @SuppressWarnings("UnnecessaryContinue")
+ @Nullable
+ public static String unescapeResourceString(
+ @Nullable String s,
+ boolean escapeEntities, boolean trim) {
+ if (s == null) {
+ return null;
+ }
+
+ // Trim space surrounding optional quotes
+ int i = 0;
+ int n = s.length();
+ if (trim) {
+ while (i < n) {
+ char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ i++;
+ }
+ while (n > i) {
+ char c = s.charAt(n - 1);
+ if (!Character.isWhitespace(c)) {
+ //See if this was a \, and if so, see whether it was escaped
+ if (n < s.length() && isEscaped(s, n)) {
+ n++;
+ }
+ break;
+ }
+ n--;
+ }
+ }
+
+ // Perform a single pass over the string and see if it contains
+ // (1) spaces that should be converted (e.g. repeated spaces or a newline which
+ // should be converted to a space)
+ // (2) escape characters (\ and &) which will require expansions
+ // (3) quotes that need to be removed
+ // If we find neither of these, we can simply return the string
+ boolean rewriteWhitespace = false;
+ // See if we need to fold adjacent spaces
+ boolean prevSpace = false;
+ boolean hasEscape = false;
+ boolean hasQuotes = false;
+ for (int curr = i; curr < n; curr++) {
+ char c = s.charAt(curr);
+ if (c == '\\' || c == '&') {
+ hasEscape = true;
+ }
+ if (c == '"') {
+ hasQuotes = true;
+ }
+ boolean isSpace = Character.isWhitespace(c);
+ if (c == '\n' || (isSpace && prevSpace)) {
+ // rewrite newlines as spaces
+ // fold adjacent spaces
+ rewriteWhitespace = true;
+ }
+ prevSpace = isSpace;
+ }
+
+ if (!trim) {
+ rewriteWhitespace = false;
+ hasQuotes = false;
+ }
+
+ // If no surrounding whitespace and no escape characters, no need to do any
+ // more work
+ if (!rewriteWhitespace && !hasEscape && !hasQuotes && i == 0 && n == s.length()) {
+ return s;
+ }
+
+ boolean quoted = false;
+ StringBuilder sb = new StringBuilder(n - i);
+ prevSpace = false;
+ for (; i < n; i++) {
+ char c = s.charAt(i);
+ while (c == '"' && trim) {
+ quoted = !quoted;
+ i++;
+ if (i == n) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ if (i == n) {
+ break;
+ }
+
+ if (c == '\\' && i < n - 1) {
+ prevSpace = false;
+ char next = s.charAt(i + 1);
+ // Unicode escapes
+ if (next == 'u' && i < n - 5) { // case sensitive
+ String hex = s.substring(i + 2, i + 6);
+ try {
+ int unicodeValue = Integer.parseInt(hex, 16);
+ sb.append((char) unicodeValue);
+ i += 5;
+ continue;
+ } catch (NumberFormatException e) {
+ // Invalid escape: Just proceed to literally transcribe it
+ sb.append(c);
+ }
+ } else if (next == 'n') {
+ sb.append('\n');
+ i++;
+ continue;
+ } else if (next == 't') {
+ sb.append('\t');
+ i++;
+ continue;
+ } else {
+ sb.append(next);
+ i++;
+ continue;
+ }
+ } else {
+ if (c == '&' && escapeEntities) {
+ prevSpace = false;
+ if (s.regionMatches(true, i, LT_ENTITY, 0, LT_ENTITY.length())) {
+ sb.append('<');
+ i += LT_ENTITY.length() - 1;
+ continue;
+ } else if (s.regionMatches(true, i, AMP_ENTITY, 0, AMP_ENTITY.length())) {
+ sb.append('&');
+ i += AMP_ENTITY.length() - 1;
+ continue;
+ } else if (s.regionMatches(true, i, QUOT_ENTITY, 0, QUOT_ENTITY.length())) {
+ sb.append('"');
+ i += QUOT_ENTITY.length() - 1;
+ continue;
+ } else if (s.regionMatches(true, i, APOS_ENTITY, 0, APOS_ENTITY.length())) {
+ sb.append('\'');
+ i += APOS_ENTITY.length() - 1;
+ continue;
+ } else if (s.regionMatches(true, i, GT_ENTITY, 0, GT_ENTITY.length())) {
+ sb.append('>');
+ i += GT_ENTITY.length() - 1;
+ continue;
+ } else if (i < n - 2 && s.charAt(i + 1) == '#') {
+ int end = s.indexOf(';', i + 1);
+ if (end != -1) {
+ char first = s.charAt(i + 2);
+ boolean hex = first == 'x' || first == 'X';
+ String number = s.substring(i + (hex ? 3 : 2), end);
+ try {
+ int unicodeValue = Integer.parseInt(number, hex ? 16 : 10);
+ sb.append((char) unicodeValue);
+ i = end;
+ continue;
+ } catch (NumberFormatException e) {
+ // Invalid escape: Just proceed to literally transcribe it
+ sb.append(c);
+ }
+ } else {
+ // Invalid escape: Just proceed to literally transcribe it
+ sb.append(c);
+ }
+ }
+ }
+
+ if (trim && !quoted) {
+ boolean isSpace = Character.isWhitespace(c);
+ if (isSpace) {
+ if (!prevSpace) {
+ sb.append(' '); // replace newlines etc with a plain space
+ }
+ } else {
+ sb.append(c);
+ }
+ prevSpace = isSpace;
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+ s = sb.toString();
+
+ return s;
+ }
+
+ /**
+ * Returns true if the character at the given offset in the string is escaped
+ * (the previous character is a \, and that character isn't itself an escaped \)
+ *
+ * @param s the string
+ * @param index the index of the character in the string to check
+ * @return true if the character is escaped
+ */
+ @VisibleForTesting
+ static boolean isEscaped(String s, int index) {
+ if (index == 0 || index == s.length()) {
+ return false;
+ }
+ int prevPos = index - 1;
+ char prev = s.charAt(prevPos);
+ if (prev != '\\') {
+ return false;
+ }
+ // The character *may* be escaped; not sure if the \ we ran into is
+ // an escape character, or an escaped backslash; we have to search backwards
+ // to be certain.
+ int j = prevPos - 1;
+ while (j >= 0) {
+ if (s.charAt(j) != '\\') {
+ break;
+ }
+ j--;
+ }
+ // If we passed an odd number of \'s, the space is escaped
+ return (prevPos - j) % 2 == 1;
+ }
+
+ /**
+ * Escape a string value to be placed in a string resource file such that it complies with
+ * the escaping rules described here:
+ * http://developer.android.com/guide/topics/resources/string-resource.html
+ * More examples of the escaping rules can be found here:
+ * http://androidcookbook.com/Recipe.seam?recipeId=2219&recipeFrom=ViewTOC
+ * This method assumes that the String is not escaped already.
+ *
+ * Rules:
+ * <ul>
+ * <li>Double quotes are needed if string starts or ends with at least one space.
+ * <li>{@code @, ?} at beginning of string have to be escaped with a backslash.
+ * <li>{@code ', ", \} have to be escaped with a backslash.
+ * <li>{@code <, >, &} have to be replaced by their predefined xml entity.
+ * <li>{@code \n, \t} have to be replaced by a backslash and the appropriate character.
+ * </ul>
+ * @param s the string to be escaped
+ * @return the escaped string as it would appear in the XML text in a values file
+ */
+ public static String escapeResourceString(String s) {
+ return escapeResourceString(s, true);
+ }
+
+ /**
+ * Escape a string value to be placed in a string resource file such that it complies with
+ * the escaping rules described here:
+ * http://developer.android.com/guide/topics/resources/string-resource.html
+ * More examples of the escaping rules can be found here:
+ * http://androidcookbook.com/Recipe.seam?recipeId=2219&recipeFrom=ViewTOC
+ * This method assumes that the String is not escaped already.
+ *
+ * Rules:
+ * <ul>
+ * <li>Double quotes are needed if string starts or ends with at least one space.
+ * <li>{@code @, ?} at beginning of string have to be escaped with a backslash.
+ * <li>{@code ', ", \} have to be escaped with a backslash.
+ * <li>{@code <, >, &} have to be replaced by their predefined xml entity.
+ * <li>{@code \n, \t} have to be replaced by a backslash and the appropriate character.
+ * </ul>
+ * @param s the string to be escaped
+ * @param escapeXml whether XML characters like {@code <} and {@code &} should be
+ * escaped; this should normally be true, but is optional since
+ * some callers need to pass the string over to code which already escapes
+ * XML content, and you wouldn't want the ampersand in the escaped character
+ * entity to itself be escaped.
+ * @return the escaped string as it would appear in the XML text in a values file
+ */
+ public static String escapeResourceString(String s, boolean escapeXml) {
+ int n = s.length();
+ if (n == 0) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder(s.length() * 2);
+ boolean hasSpace = s.charAt(0) == ' ' || s.charAt(n - 1) == ' ';
+
+ if (hasSpace) {
+ sb.append('"');
+ } else if (s.charAt(0) == '@' || s.charAt(0) == '?') {
+ sb.append('\\');
+ }
+
+ for (int i = 0; i < n; ++i) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '\'':
+ if (!hasSpace) {
+ sb.append('\\');
+ }
+ sb.append(c);
+ break;
+ case '"':
+ case '\\':
+ sb.append('\\');
+ sb.append(c);
+ break;
+ case '<':
+ if (escapeXml) {
+ sb.append(LT_ENTITY);
+ } else {
+ sb.append(c);
+ }
+ break;
+ case '&':
+ if (escapeXml) {
+ sb.append(AMP_ENTITY);
+ } else {
+ sb.append(c);
+ }
+ break;
+ case '\n':
+ sb.append("\\n"); //$NON-NLS-1$
+ break;
+ case '\t':
+ sb.append("\\t"); //$NON-NLS-1$
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+
+ if (hasSpace) {
+ sb.append('"');
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResourceItem.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResourceItem.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResourceItem.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java b/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java b/sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/IdGeneratingResourceFile.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java b/sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/IdResourceParser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/InlineResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/resources/InlineResourceItem.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/InlineResourceItem.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/InlineResourceItem.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/IntArrayWrapper.java b/sdk-common/src/main/java/com/android/ide/common/resources/IntArrayWrapper.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/IntArrayWrapper.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/IntArrayWrapper.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java b/sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/MultiResourceFile.java b/sdk-common/src/main/java/com/android/ide/common/resources/MultiResourceFile.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/MultiResourceFile.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/MultiResourceFile.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceDeltaKind.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceDeltaKind.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceDeltaKind.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ResourceDeltaKind.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceFile.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceFile.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceFile.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ResourceFile.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceFolder.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceFolder.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceFolder.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ResourceFolder.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java
new file mode 100644
index 0000000..4aeb001
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.resources;
+
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.ide.common.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+
+import java.util.List;
+
+/**
+ * Like {@link ResourceResolver} but for a single item, so it does not need the full resource maps
+ * to be resolved up front. Typically used for cases where we may not have fully configured
+ * resource maps and we need to look up a specific value, such as in Android Studio where
+ * a color reference is found in an XML style file, and we want to resolve it in order to
+ * display the final resolved color in the editor margin.
+ */
+public class ResourceItemResolver extends RenderResources {
+ private final FolderConfiguration mConfiguration;
+ private final LayoutLog mLogger;
+ private final ResourceProvider mResourceProvider;
+ private ResourceRepository mFrameworkResources;
+ private ResourceResolver mResolver;
+ private AbstractResourceRepository myAppResources;
+ @Nullable private List<ResourceValue> mLookupChain;
+
+ public ResourceItemResolver(
+ @NonNull FolderConfiguration configuration,
+ @NonNull ResourceProvider resourceProvider,
+ @Nullable LayoutLog logger) {
+ mConfiguration = configuration;
+ mResourceProvider = resourceProvider;
+ mLogger = logger;
+ mResolver = resourceProvider.getResolver(false);
+ }
+
+ public ResourceItemResolver(
+ @NonNull FolderConfiguration configuration,
+ @NonNull ResourceRepository frameworkResources,
+ @NonNull AbstractResourceRepository appResources,
+ @Nullable LayoutLog logger) {
+ mConfiguration = configuration;
+ mResourceProvider = null;
+ mLogger = logger;
+ mFrameworkResources = frameworkResources;
+ myAppResources = appResources;
+ }
+
+ @Override
+ @Nullable
+ public ResourceValue resolveResValue(@Nullable ResourceValue resValue) {
+ if (mResolver != null) {
+ return mResolver.resolveResValue(resValue);
+ }
+ if (mLookupChain != null) {
+ mLookupChain.add(resValue);
+ }
+ return resolveResValue(resValue, 0);
+ }
+
+ @Nullable
+ private ResourceValue resolveResValue(@Nullable ResourceValue resValue, int depth) {
+ if (resValue == null) {
+ return null;
+ }
+
+ String value = resValue.getValue();
+ if (value == null || resValue instanceof ArrayResourceValue) {
+ // If there's no value or this an array resource (eg. <string-array>), return.
+ return resValue;
+ }
+
+ // else attempt to find another ResourceValue referenced by this one.
+ ResourceValue resolvedResValue = findResValue(value, resValue.isFramework());
+
+ // if the value did not reference anything, then we simply return the input value
+ if (resolvedResValue == null) {
+ return resValue;
+ }
+
+ // detect potential loop due to mishandled namespace in attributes
+ if (resValue == resolvedResValue || depth >= MAX_RESOURCE_INDIRECTION) {
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_BROKEN,
+ String.format(
+ "Potential stack overflow trying to resolve '%s': cyclic resource definitions? Render may not be accurate.",
+ value),
+ null);
+ }
+ return resValue;
+ }
+
+ // otherwise, we attempt to resolve this new value as well
+ return resolveResValue(resolvedResValue, depth + 1);
+ }
+
+ @Override
+ @Nullable
+ public ResourceValue findResValue(@Nullable String reference, boolean inFramework) {
+ if (mResolver != null) {
+ return mResolver.findResValue(reference, inFramework);
+ }
+
+ if (reference == null) {
+ return null;
+ }
+
+ if (mLookupChain != null && !mLookupChain.isEmpty() &&
+ reference.startsWith(PREFIX_RESOURCE_REF)) {
+ ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
+ if (!reference.equals(prev.getValue())) {
+ ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),
+ prev.isFramework());
+ next.setValue(reference);
+ mLookupChain.add(next);
+ }
+ }
+
+ ResourceUrl resource = ResourceUrl.parse(reference);
+ if (resource != null && resource.hasValidName()) {
+ if (resource.theme) {
+ // Do theme lookup? We can't do that here; requires full global analysis, so just use
+ // a real resource resolver
+ ResourceResolver resolver = getFullResolver();
+ if (resolver != null) {
+ return resolver.findResValue(reference, inFramework);
+ } else {
+ return null;
+ }
+ } else if (reference.startsWith(PREFIX_RESOURCE_REF)) {
+ return findResValue(resource.type, resource.name, inFramework || resource.framework);
+ }
+ }
+
+ // Looks like the value didn't reference anything. Return null.
+ return null;
+ }
+
+ private ResourceValue findResValue(ResourceType resType, String resName, boolean framework) {
+ // map of ResourceValue for the given type
+ // if allowed, search in the project resources first.
+ if (!framework) {
+ if (myAppResources == null) {
+ assert mResourceProvider != null;
+ myAppResources = mResourceProvider.getAppResources();
+ if (myAppResources == null) {
+ return null;
+ }
+ }
+ ResourceValue item = null;
+ item = myAppResources.getConfiguredValue(resType, resName, mConfiguration);
+ if (item != null) {
+ if (mLookupChain != null) {
+ mLookupChain.add(item);
+ }
+ return item;
+ }
+ } else {
+ if (mFrameworkResources == null) {
+ assert mResourceProvider != null;
+ mFrameworkResources = mResourceProvider.getFrameworkResources();
+ if (mFrameworkResources == null) {
+ return null;
+ }
+ }
+ // now search in the framework resources.
+ if (mFrameworkResources.hasResourceItem(resType, resName)) {
+ ResourceItem item = mFrameworkResources.getResourceItem(resType, resName);
+ ResourceValue value = item.getResourceValue(resType, mConfiguration, true);
+ if (value != null && mLookupChain != null) {
+ mLookupChain.add(value);
+ }
+ return value;
+ }
+ }
+
+ // didn't find the resource anywhere.
+ if (mLogger != null) {
+ mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE,
+ "Couldn't resolve resource @" +
+ (framework ? "android:" : "") + resType + "/" + resName,
+ new ResourceValue(resType, resName, framework));
+ }
+ return null;
+ }
+
+ @Override
+ public StyleResourceValue getCurrentTheme() {
+ ResourceResolver resolver = getFullResolver();
+ if (resolver != null) {
+ return resolver.getCurrentTheme();
+ }
+
+ return null;
+ }
+
+ @Override
+ public ResourceValue resolveValue(ResourceType type, String name, String value,
+ boolean isFrameworkValue) {
+ if (value == null) {
+ return null;
+ }
+
+ // get the ResourceValue referenced by this value
+ ResourceValue resValue = findResValue(value, isFrameworkValue);
+
+ // if resValue is null, but value is not null, this means it was not a reference.
+ // we return the name/value wrapper in a ResourceValue. the isFramework flag doesn't
+ // matter.
+ if (resValue == null) {
+ return new ResourceValue(type, name, value, isFrameworkValue);
+ }
+
+ // we resolved a first reference, but we need to make sure this isn't a reference also.
+ return resolveResValue(resValue);
+ }
+
+ // For theme lookup, we need to delegate to a full resource resolver
+
+ @Override
+ public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
+ assert false; // This method shouldn't be called on this resolver
+ return super.getTheme(name, frameworkTheme);
+ }
+
+ @Override
+ public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
+ assert false; // This method shouldn't be called on this resolver
+ return super.themeIsParentOf(parentTheme, childTheme);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public ResourceValue findItemInTheme(String itemName) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInTheme(itemName) : null;
+ }
+
+ @Override
+ public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInTheme(attrName, isFrameworkAttr) : null;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInStyle(style, attrName) : null;
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String attrName,
+ boolean isFrameworkAttr) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInStyle(style, attrName, isFrameworkAttr) : null;
+ }
+
+ @Override
+ public StyleResourceValue getParent(StyleResourceValue style) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.getParent(style) : null;
+ }
+
+ private ResourceResolver getFullResolver() {
+ if (mResolver == null) {
+ if (mResourceProvider == null) {
+ return null;
+ }
+ mResolver = mResourceProvider.getResolver(true);
+ if (mResolver != null) {
+ if (mLookupChain != null) {
+ mResolver = mResolver.createRecorder(mLookupChain);
+ }
+ }
+
+ }
+ return mResolver;
+ }
+
+ /**
+ * Optional method to set a list the resolver should record all value resolutions
+ * into. Useful if you want to find out the resolution chain for a resource,
+ * e.g. {@code @color/buttonForeground => @color/foreground => @android:color/black }.
+ * <p>
+ * There is no getter. Clients setting this list should look it up themselves.
+ * Note also that if this resolver has to delegate to a full resource resolver,
+ * e.g. to follow theme attributes, those resolutions will not be recorded.
+ *
+ * @param lookupChain the list to set, or null
+ */
+ public void setLookupChainList(@Nullable List<ResourceValue> lookupChain) {
+ mLookupChain = lookupChain;
+ }
+
+ /** Returns the lookup chain being used by this resolver */
+ @Nullable
+ public List<ResourceValue> getLookupChain() {
+ return mLookupChain;
+ }
+
+ /**
+ * Returns a display string for a resource lookup
+ *
+ * @param type the resource type
+ * @param name the resource name
+ * @param isFramework whether the item is in the framework
+ * @param lookupChain the list of resolved items to display
+ * @return the display string
+ */
+ public static String getDisplayString(
+ @NonNull ResourceType type,
+ @NonNull String name,
+ boolean isFramework,
+ @NonNull List<ResourceValue> lookupChain) {
+ String url = ResourceUrl.create(type, name, isFramework, false).toString();
+ return getDisplayString(url, lookupChain);
+ }
+
+ /**
+ * Returns a display string for a resource lookup
+ * @param url the resource url, such as {@code @string/foo}
+ * @param lookupChain the list of resolved items to display
+ * @return the display string
+ */
+ @NonNull
+ public static String getDisplayString(
+ @NonNull String url,
+ @NonNull List<ResourceValue> lookupChain) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(url);
+ String prev = url;
+ for (ResourceValue element : lookupChain) {
+ if (element == null) {
+ continue;
+ }
+ String value = element.getValue();
+ if (value == null) {
+ continue;
+ }
+ String text = value;
+ if (text.equals(prev)) {
+ continue;
+ }
+
+ sb.append(" => ");
+
+ // Strip paths
+ if (!(text.startsWith(PREFIX_THEME_REF) || text.startsWith(PREFIX_RESOURCE_REF))) {
+ int end = Math.max(text.lastIndexOf('/'), text.lastIndexOf('\\'));
+ if (end != -1) {
+ text = text.substring(end + 1);
+ }
+ }
+ sb.append(text);
+
+ prev = value;
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Interface implemented by clients of the {@link ResourceItemResolver} which allows
+ * it to lazily look up the project resources, the framework resources and optionally
+ * to provide a fully configured resource resolver, if any
+ */
+ public interface ResourceProvider {
+ @Nullable ResourceResolver getResolver(boolean createIfNecessary);
+ @Nullable ResourceRepository getFrameworkResources();
+ @Nullable AbstractResourceRepository getAppResources();
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
new file mode 100644
index 0000000..8f61af7
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
@@ -0,0 +1,946 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.PREFIX_ANDROID;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.REFERENCE_STYLE;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.resources.ResourceType;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ResourceResolver extends RenderResources {
+ public static final String THEME_NAME = "Theme";
+ public static final String THEME_NAME_DOT = "Theme.";
+ public static final String XLIFF_NAMESPACE_PREFIX = "urn:oasis:names:tc:xliff:document:";
+ public static final String XLIFF_G_TAG = "g";
+ public static final String ATTR_EXAMPLE = "example";
+
+ /**
+ * Constant passed to {@link #setDeviceDefaults(String)} to indicate the DeviceDefault styles
+ * should point to the default styles
+ */
+ public static final String LEGACY_THEME = "";
+
+ public static final Pattern DEVICE_DEFAULT_PATTERN = Pattern
+ .compile("(\\p{Alpha}+)?\\.?DeviceDefault\\.?(.+)?");
+
+ /**
+ * Number of indirections we'll follow for resource resolution before assuming there
+ * is a cyclic dependency error in the input
+ */
+ public static final int MAX_RESOURCE_INDIRECTION = 50;
+
+ private final Map<ResourceType, Map<String, ResourceValue>> mProjectResources;
+ private final Map<ResourceType, Map<String, ResourceValue>> mFrameworkResources;
+ private final Map<StyleResourceValue, StyleResourceValue> mStyleInheritanceMap =
+ new HashMap<StyleResourceValue, StyleResourceValue>();
+ private StyleResourceValue mDefaultTheme;
+ // The resources should be searched in all the themes in the list in order.
+ private final List<StyleResourceValue> mThemes;
+ private FrameworkResourceIdProvider mFrameworkProvider;
+ private LayoutLog mLogger;
+ private String mThemeName;
+ private boolean mIsProjectTheme;
+ // AAPT flattens the names by converting '.', '-' and ':' to '_'. These maps undo the
+ // flattening. We prefer lazy initialization of these maps since they are not used in many
+ // applications.
+ @Nullable
+ private Map<String, String> mReverseFrameworkStyles;
+ @Nullable
+ private Map<String, String> mReverseProjectStyles;
+ /** Contains the default parent for DeviceDefault styles (e.g. for API 18, "Holo") */
+ private String mDeviceDefaultParent = null;
+
+ private ResourceResolver(
+ Map<ResourceType, Map<String, ResourceValue>> projectResources,
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
+ String themeName, boolean isProjectTheme) {
+ mProjectResources = projectResources;
+ mFrameworkResources = frameworkResources;
+ mThemeName = themeName;
+ mIsProjectTheme = isProjectTheme;
+ mThemes = new LinkedList<StyleResourceValue>();
+ }
+
+ /**
+ * Creates a new {@link ResourceResolver} object.
+ *
+ * @param projectResources the project resources.
+ * @param frameworkResources the framework resources.
+ * @param themeName the name of the current theme.
+ * @param isProjectTheme Is this a project theme?
+ * @return a new {@link ResourceResolver}
+ */
+ public static ResourceResolver create(
+ Map<ResourceType, Map<String, ResourceValue>> projectResources,
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
+ String themeName, boolean isProjectTheme) {
+
+ ResourceResolver resolver = new ResourceResolver(projectResources, frameworkResources,
+ themeName, isProjectTheme);
+ resolver.computeStyleMaps();
+
+ return resolver;
+ }
+
+ /**
+ * This will override the DeviceDefault styles so they point to the given parent styles (e.g. If
+ * "Material" is passed, Theme.DeviceDefault parent will become Theme.Material). This patches
+ * all the styles (not only themes) and takes care of the light and dark variants. If {@link
+ * #LEGACY_THEME} is passed, parents will be directed to the default themes (i.e. Theme).
+ */
+ public void setDeviceDefaults(@NonNull String deviceDefaultParent) {
+ if (deviceDefaultParent.equals(mDeviceDefaultParent)) {
+ // No need to patch again with the same parent
+ return;
+ }
+
+ mDeviceDefaultParent = deviceDefaultParent;
+ // The joiner will ignore nulls so if the caller specified an empty name, we replace it with
+ // a null so it gets ignored
+ String parentName = Strings.emptyToNull(deviceDefaultParent);
+
+ // The regexp gets the prefix and suffix if they exist (without the dots)
+ for (ResourceValue value : mFrameworkResources.get(ResourceType.STYLE).values()) {
+ Matcher matcher = DEVICE_DEFAULT_PATTERN.matcher(value.getName());
+ if (!matcher.matches()) {
+ continue;
+ }
+
+ String newParentStyle =
+ Joiner.on('.').skipNulls().join(matcher.group(1), parentName,
+ ((matcher.groupCount() > 1) ? matcher.group(2) : null));
+ patchFrameworkStyleParent(value.getName(), newParentStyle);
+ }
+ }
+
+ /**
+ * Updates the parent of a given framework style. This method is used to patch DeviceDefault
+ * styles when using a CompatibilityTarget
+ */
+ private void patchFrameworkStyleParent(String childStyleName, String parentName) {
+ Map<String, ResourceValue> map = mFrameworkResources.get(ResourceType.STYLE);
+ if (map != null) {
+ StyleResourceValue from = (StyleResourceValue)map.get(childStyleName);
+ StyleResourceValue to = (StyleResourceValue)map.get(parentName);
+
+ if (from != null && to != null) {
+ StyleResourceValue newStyle = new StyleResourceValue(ResourceType.STYLE,
+ from.getName(), parentName, from.isFramework());
+ newStyle.replaceWith(from);
+ mStyleInheritanceMap.put(newStyle, to);
+ }
+ }
+ }
+
+ // ---- Methods to help dealing with older LayoutLibs.
+
+ public String getThemeName() {
+ return mThemeName;
+ }
+
+ public boolean isProjectTheme() {
+ return mIsProjectTheme;
+ }
+
+ public Map<ResourceType, Map<String, ResourceValue>> getProjectResources() {
+ return mProjectResources;
+ }
+
+ public Map<ResourceType, Map<String, ResourceValue>> getFrameworkResources() {
+ return mFrameworkResources;
+ }
+
+ // ---- RenderResources Methods
+
+ @Override
+ public void setFrameworkResourceIdProvider(FrameworkResourceIdProvider provider) {
+ mFrameworkProvider = provider;
+ }
+
+ @Override
+ public void setLogger(LayoutLog logger) {
+ mLogger = logger;
+ }
+
+ @Override
+ public StyleResourceValue getDefaultTheme() {
+ return mDefaultTheme;
+ }
+
+ @Override
+ public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+ if (theme == null) {
+ return;
+ }
+ if (useAsPrimary) {
+ mThemes.add(0, theme);
+ } else {
+ mThemes.add(theme);
+ }
+ }
+
+ @Override
+ public void clearStyles() {
+ mThemes.clear();
+ mThemes.add(mDefaultTheme);
+ }
+
+ @Override
+ public List<StyleResourceValue> getAllThemes() {
+ return mThemes;
+ }
+
+ @Override
+ public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
+ ResourceValue theme;
+
+ if (frameworkTheme) {
+ Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(
+ ResourceType.STYLE);
+ theme = frameworkStyleMap.get(name);
+ } else {
+ Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE);
+ theme = projectStyleMap.get(name);
+ }
+
+ if (theme instanceof StyleResourceValue) {
+ return (StyleResourceValue) theme;
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
+ do {
+ childTheme = mStyleInheritanceMap.get(childTheme);
+ if (childTheme == null) {
+ return false;
+ } else if (childTheme == parentTheme) {
+ return true;
+ }
+ } while (true);
+ }
+
+ @Override
+ public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
+ return getResource(resourceType, resourceName, mFrameworkResources);
+ }
+
+ @Override
+ public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
+ return getResource(resourceType, resourceName, mProjectResources);
+ }
+
+ @SuppressWarnings("deprecation") // Required to support older layoutlib clients
+ @Override
+ @Deprecated
+ public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
+ // this method is deprecated because it doesn't know about the namespace of the
+ // attribute so we search for the project namespace first and then in the
+ // android namespace if needed.
+ ResourceValue item = findItemInStyle(style, attrName, false);
+ if (item == null) {
+ item = findItemInStyle(style, attrName, true);
+ }
+
+ return item;
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
+ boolean isFrameworkAttr) {
+ return findItemInStyle(style, itemName, isFrameworkAttr, 0);
+ }
+
+ private ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
+ boolean isFrameworkAttr, int depth) {
+ ResourceValue item = style.getItem(itemName, isFrameworkAttr);
+
+ // if we didn't find it, we look in the parent style (if applicable)
+ //noinspection VariableNotUsedInsideIf
+ if (item == null) {
+ StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
+ if (parentStyle != null) {
+ if (depth >= MAX_RESOURCE_INDIRECTION) {
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_BROKEN,
+ String.format("Cyclic style parent definitions: %1$s",
+ computeCyclicStyleChain(style)),
+ null);
+ }
+
+ return null;
+ }
+
+ return findItemInStyle(parentStyle, itemName, isFrameworkAttr, depth + 1);
+ }
+ }
+
+ return item;
+ }
+
+ private String computeCyclicStyleChain(StyleResourceValue style) {
+ StringBuilder sb = new StringBuilder(100);
+ appendStyleParents(style, new HashSet<StyleResourceValue>(), 0, sb);
+ return sb.toString();
+ }
+
+ private void appendStyleParents(StyleResourceValue style, Set<StyleResourceValue> seen,
+ int depth, StringBuilder sb) {
+ if (depth >= MAX_RESOURCE_INDIRECTION) {
+ sb.append("...");
+ return;
+ }
+
+ boolean haveSeen = seen.contains(style);
+ seen.add(style);
+
+ sb.append('"');
+ if (style.isFramework()) {
+ sb.append(PREFIX_ANDROID);
+ }
+ sb.append(style.getName());
+ sb.append('"');
+
+ if (haveSeen) {
+ return;
+ }
+
+ StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
+ if (parentStyle != null) {
+ if (style.getParentStyle() != null) {
+ sb.append(" specifies parent ");
+ } else {
+ sb.append(" implies parent ");
+ }
+
+ appendStyleParents(parentStyle, seen, depth + 1, sb);
+ }
+ }
+
+ @Override
+ public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
+ if (reference == null) {
+ return null;
+ }
+
+ ResourceUrl resource = ResourceUrl.parse(reference);
+ if (resource != null && resource.hasValidName()) {
+ if (resource.theme) {
+ // no theme? no need to go further!
+ if (mDefaultTheme == null) {
+ return null;
+ }
+
+ if (resource.type != ResourceType.ATTR) {
+ // At this time, no support for ?type/name where type is not "attr"
+ return null;
+ }
+
+ // Now look for the item in the theme, starting with the current one.
+ return findItemInTheme(resource.name, forceFrameworkOnly || resource.framework);
+ } else {
+ return findResValue(resource, forceFrameworkOnly);
+ }
+ }
+
+ // Looks like the value didn't reference anything. Return null.
+ return null;
+ }
+
+ @Override
+ public ResourceValue resolveValue(ResourceType type, String name, String value,
+ boolean isFrameworkValue) {
+ if (value == null) {
+ return null;
+ }
+
+ // get the ResourceValue referenced by this value
+ ResourceValue resValue = findResValue(value, isFrameworkValue);
+
+ // if resValue is null, but value is not null, this means it was not a reference.
+ // we return the name/value wrapper in a ResourceValue. the isFramework flag doesn't
+ // matter.
+ if (resValue == null) {
+ return new ResourceValue(type, name, value, isFrameworkValue);
+ }
+
+ // we resolved a first reference, but we need to make sure this isn't a reference also.
+ return resolveResValue(resValue);
+ }
+
+ @Override
+ public ResourceValue resolveResValue(ResourceValue resValue) {
+ return resolveResValue(resValue, 0);
+ }
+
+ private ResourceValue resolveResValue(ResourceValue resValue, int depth) {
+ if (resValue == null) {
+ return null;
+ }
+
+ String value = resValue.getValue();
+ if (value == null || resValue instanceof ArrayResourceValue) {
+ // If there's no value or this an array resource (eg. <string-array>), return.
+ return resValue;
+ }
+
+ // else attempt to find another ResourceValue referenced by this one.
+ ResourceValue resolvedResValue = findResValue(value, resValue.isFramework());
+
+ // if the value did not reference anything, then we simply return the input value
+ if (resolvedResValue == null) {
+ return resValue;
+ }
+
+ // detect potential loop due to mishandled namespace in attributes
+ if (resValue == resolvedResValue || depth >= MAX_RESOURCE_INDIRECTION) {
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_BROKEN,
+ String.format("Potential stack overflow trying to resolve '%s': cyclic resource definitions? Render may not be accurate.", value),
+ null);
+ }
+ return resValue;
+ }
+
+ // otherwise, we attempt to resolve this new value as well
+ return resolveResValue(resolvedResValue, depth + 1);
+ }
+
+ // ---- Private helper methods.
+
+ /**
+ * Searches for, and returns a {@link ResourceValue} by its parsed reference.
+ * @param resource the parsed resource
+ * @param forceFramework if <code>true</code>, the method does not search in the
+ * project resources
+ */
+ private ResourceValue findResValue(ResourceUrl resource, boolean forceFramework) {
+ // map of ResourceValue for the given type
+ Map<String, ResourceValue> typeMap;
+ ResourceType resType = resource.type;
+ String resName = resource.name;
+ boolean isFramework = forceFramework || resource.framework;
+
+ if (!isFramework) {
+ typeMap = mProjectResources.get(resType);
+ ResourceValue item = typeMap.get(resName);
+ if (item != null) {
+ return item;
+ }
+ } else {
+ typeMap = mFrameworkResources.get(resType);
+ if (typeMap != null) {
+ ResourceValue item = typeMap.get(resName);
+ if (item != null) {
+ return item;
+ }
+ }
+
+ // if it was not found and the type is an id, it is possible that the ID was
+ // generated dynamically when compiling the framework resources.
+ // Look for it in the R map.
+ if (mFrameworkProvider != null && resType == ResourceType.ID) {
+ if (mFrameworkProvider.getId(resType, resName) != null) {
+ return new ResourceValue(resType, resName, true);
+ }
+ }
+ }
+
+ // didn't find the resource anywhere.
+ if (!resource.create && mLogger != null) {
+ mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE,
+ "Couldn't resolve resource @" +
+ (isFramework ? "android:" : "") + resType + "/" + resName,
+ new ResourceValue(resType, resName, isFramework));
+ }
+ return null;
+ }
+
+ private ResourceValue getResource(ResourceType resourceType, String resourceName,
+ Map<ResourceType, Map<String, ResourceValue>> resourceRepository) {
+ Map<String, ResourceValue> typeMap = resourceRepository.get(resourceType);
+ if (typeMap != null) {
+ ResourceValue item = typeMap.get(resourceName);
+ if (item != null) {
+ item = resolveResValue(item);
+ return item;
+ }
+ }
+
+ // didn't find the resource anywhere.
+ return null;
+ }
+
+ /**
+ * Compute style information from the given list of style for the project and framework.
+ */
+ private void computeStyleMaps() {
+ Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE);
+ Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(ResourceType.STYLE);
+
+ // first, get the theme
+ ResourceValue theme = null;
+
+ // project theme names have been prepended with a *
+ if (mIsProjectTheme) {
+ if (projectStyleMap != null) {
+ theme = projectStyleMap.get(mThemeName);
+ }
+ } else {
+ if (frameworkStyleMap != null) {
+ theme = frameworkStyleMap.get(mThemeName);
+ }
+ }
+
+ if (theme instanceof StyleResourceValue) {
+ // compute the inheritance map for both the project and framework styles
+ computeStyleInheritance(projectStyleMap.values(), projectStyleMap,
+ frameworkStyleMap);
+
+ // Compute the style inheritance for the framework styles/themes.
+ // Since, for those, the style parent values do not contain 'android:'
+ // we want to force looking in the framework style only to avoid using
+ // similarly named styles from the project.
+ // To do this, we pass null in lieu of the project style map.
+ if (frameworkStyleMap != null) {
+ computeStyleInheritance(frameworkStyleMap.values(), null /*inProjectStyleMap */,
+ frameworkStyleMap);
+ }
+
+ mDefaultTheme = (StyleResourceValue) theme;
+ mThemes.clear();
+ mThemes.add(mDefaultTheme);
+ }
+ }
+
+ /**
+ * Compute the parent style for all the styles in a given list.
+ * @param styles the styles for which we compute the parent.
+ * @param inProjectStyleMap the map of project styles.
+ * @param inFrameworkStyleMap the map of framework styles.
+ */
+ private void computeStyleInheritance(Collection<ResourceValue> styles,
+ Map<String, ResourceValue> inProjectStyleMap,
+ Map<String, ResourceValue> inFrameworkStyleMap) {
+ // This method will recalculate the inheritance map so any modifications done by
+ // setDeviceDefault will be lost. Set mDeviceDefaultParent to null so when setDeviceDefault
+ // is called again, it knows that it needs to modify the inheritance map again.
+ mDeviceDefaultParent = null;
+ for (ResourceValue value : styles) {
+ if (value instanceof StyleResourceValue) {
+ StyleResourceValue style = (StyleResourceValue)value;
+
+ String parentName = getParentName(style);
+
+ if (parentName != null) {
+ StyleResourceValue parentStyle = getStyle(parentName, inProjectStyleMap,
+ inFrameworkStyleMap);
+
+ if (parentStyle != null) {
+ mStyleInheritanceMap.put(style, parentStyle);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Computes the name of the parent style, or <code>null</code> if the style is a root style.
+ */
+ @Nullable
+ public static String getParentName(StyleResourceValue style) {
+ String parentName = style.getParentStyle();
+ if (parentName != null) {
+ return parentName;
+ }
+
+ String styleName = style.getName();
+ int index = styleName.lastIndexOf('.');
+ if (index != -1) {
+ return styleName.substring(0, index);
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public StyleResourceValue getParent(@NonNull StyleResourceValue style) {
+ return mStyleInheritanceMap.get(style);
+ }
+
+ @Override
+ @Nullable
+ public StyleResourceValue getStyle(@NonNull String styleName, boolean isFramework) {
+ ResourceValue res;
+ Map<String, ResourceValue> styleMap;
+
+ // First check if we can find the style directly.
+ if (isFramework) {
+ styleMap = mFrameworkResources.get(ResourceType.STYLE);
+ } else {
+ styleMap = mProjectResources.get(ResourceType.STYLE);
+ }
+ res = getStyleFromMap(styleMap, styleName);
+ if (res != null) {
+ // If the obtained resource is not StyleResourceValue, return null.
+ return res instanceof StyleResourceValue ? (StyleResourceValue) res : null;
+ }
+
+ // We cannot find the style directly. The style name may have been flattened by AAPT for use
+ // in the R class. Try and obtain the original name.
+ String xmlStyleName = getReverseStyleMap(isFramework)
+ .get(getNormalizedStyleName(styleName));
+ if (!styleName.equals(xmlStyleName)) {
+ res = getStyleFromMap(styleMap, xmlStyleName);
+ }
+ return res instanceof StyleResourceValue ? (StyleResourceValue) res : null;
+ }
+
+ @Override
+ @Nullable
+ public String getXmlName(@NonNull ResourceType type, @NonNull String name,
+ boolean isFramework) {
+ if (type != ResourceType.STYLE) {
+ // The method is currently implemented for styles only.
+ return null;
+ }
+ Map<String, String> reverseStyles;
+ reverseStyles = getReverseStyleMap(isFramework);
+ return reverseStyles.get(name);
+ }
+
+ /**
+ * Returns the reverse style map using the appropriate resources. It also initializes the map if
+ * it hasn't been initialized yet.
+ */
+ private Map<String, String> getReverseStyleMap(boolean isFramework) {
+ if (isFramework) {
+ // The reverse style map may need to be initialized.
+ if (mReverseFrameworkStyles == null) {
+ Map<String, ResourceValue> styleMap = mFrameworkResources.get(ResourceType.STYLE);
+ mReverseFrameworkStyles = createReverseStyleMap(styleMap.keySet());
+ }
+ return mReverseFrameworkStyles;
+ } else {
+ if (mReverseProjectStyles == null) {
+ Map<String, ResourceValue> styleMap = mProjectResources.get(ResourceType.STYLE);
+ mReverseProjectStyles = createReverseStyleMap(styleMap.keySet());
+ }
+ return mReverseProjectStyles;
+ }
+ }
+
+ /**
+ * Create a map from the normalized form of the style names in {@code styles} to the original
+ * style name.
+ *
+ * @see #getNormalizedStyleName(String)
+ */
+ private static Map<String, String> createReverseStyleMap(@NonNull Set<String> styles) {
+ Map<String, String> reverseStyles = Maps.newHashMapWithExpectedSize(styles.size());
+ for (String style : styles) {
+ reverseStyles.put(getNormalizedStyleName(style), style);
+ }
+ return reverseStyles;
+ }
+
+ /**
+ * Flatten the styleName like AAPT by replacing dots, dashes and colons with underscores.
+ */
+ @NonNull
+ private static String getNormalizedStyleName(@NonNull String styleName) {
+ return styleName.replace('.', '_').replace('-', '_').replace(':', '_');
+ }
+
+ /**
+ * Search for the style in the given map and log an error if the obtained resource is not
+ * {@link StyleResourceValue}.
+ *
+ * @return The {@link ResourceValue} found in the map.
+ */
+ @Nullable
+ private ResourceValue getStyleFromMap(@NonNull Map<String, ResourceValue> styleMap,
+ @NonNull String styleName) {
+ ResourceValue res;
+ res = styleMap.get(styleName);
+ if (res != null) {
+ if (!(res instanceof StyleResourceValue) && mLogger != null) {
+ mLogger.error(null, String.format(
+ "Style %1$s is not of type STYLE (instead %2$s)",
+ styleName, res.getResourceType().toString()),
+ null);
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Searches for and returns the {@link StyleResourceValue} from a given name.
+ * <p/>The format of the name can be:
+ * <ul>
+ * <li>[android:]<name></li>
+ * <li>[android:]style/<name></li>
+ * <li>@[android:]style/<name></li>
+ * </ul>
+ * @param parentName the name of the style.
+ * @param inProjectStyleMap the project style map. Can be <code>null</code>
+ * @param inFrameworkStyleMap the framework style map.
+ * @return The matching {@link StyleResourceValue} object or <code>null</code> if not found.
+ */
+ private StyleResourceValue getStyle(String parentName,
+ Map<String, ResourceValue> inProjectStyleMap,
+ Map<String, ResourceValue> inFrameworkStyleMap) {
+ boolean frameworkOnly = false;
+
+ String name = parentName;
+
+ // remove the useless @ if it's there
+ if (name.startsWith(PREFIX_RESOURCE_REF)) {
+ name = name.substring(PREFIX_RESOURCE_REF.length());
+ }
+
+ // check for framework identifier.
+ if (name.startsWith(PREFIX_ANDROID)) {
+ frameworkOnly = true;
+ name = name.substring(PREFIX_ANDROID.length());
+ }
+
+ // at this point we could have the format <type>/<name>. we want only the name as long as
+ // the type is style.
+ if (name.startsWith(REFERENCE_STYLE)) {
+ name = name.substring(REFERENCE_STYLE.length());
+ } else if (name.indexOf('/') != -1) {
+ return null;
+ }
+
+ ResourceValue parent = null;
+
+ // if allowed, search in the project resources.
+ if (!frameworkOnly && inProjectStyleMap != null) {
+ parent = inProjectStyleMap.get(name);
+ }
+
+ // if not found, then look in the framework resources.
+ if (parent == null) {
+ if (inFrameworkStyleMap == null) {
+ return null;
+ }
+ parent = inFrameworkStyleMap.get(name);
+ }
+
+ // make sure the result is the proper class type and return it.
+ if (parent instanceof StyleResourceValue) {
+ return (StyleResourceValue)parent;
+ }
+
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format("Unable to resolve parent style name: %s", parentName),
+ null /*data*/);
+ }
+
+ return null;
+ }
+
+ /** Returns true if the given {@link ResourceValue} represents a theme */
+ public boolean isTheme(
+ @NonNull ResourceValue value,
+ @Nullable Map<ResourceValue, Boolean> cache) {
+ return isTheme(value, cache, 0);
+ }
+
+ private boolean isTheme(
+ @NonNull ResourceValue value,
+ @Nullable Map<ResourceValue, Boolean> cache,
+ int depth) {
+ if (cache != null) {
+ Boolean known = cache.get(value);
+ if (known != null) {
+ return known;
+ }
+ }
+ if (value instanceof StyleResourceValue) {
+ StyleResourceValue srv = (StyleResourceValue) value;
+ String name = srv.getName();
+ if (srv.isFramework() && (name.equals(THEME_NAME) || name.startsWith(THEME_NAME_DOT))) {
+ if (cache != null) {
+ cache.put(value, true);
+ }
+ return true;
+ }
+
+ StyleResourceValue parentStyle = mStyleInheritanceMap.get(srv);
+ if (parentStyle != null) {
+ if (depth >= MAX_RESOURCE_INDIRECTION) {
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_BROKEN,
+ String.format("Cyclic style parent definitions: %1$s",
+ computeCyclicStyleChain(srv)),
+ null);
+ }
+
+ return false;
+ }
+
+ boolean result = isTheme(parentStyle, cache, depth + 1);
+ if (cache != null) {
+ cache.put(value, result);
+ }
+ return result;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the given {@code themeStyle} extends the theme given by
+ * {@code parentStyle}
+ */
+ public boolean themeExtends(@NonNull String parentStyle, @NonNull String themeStyle) {
+ ResourceValue parentValue = findResValue(parentStyle,
+ parentStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
+ if (parentValue instanceof StyleResourceValue) {
+ ResourceValue themeValue = findResValue(themeStyle,
+ themeStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
+ if (themeValue == parentValue) {
+ return true;
+ }
+ if (themeValue instanceof StyleResourceValue) {
+ return themeIsParentOf((StyleResourceValue) parentValue,
+ (StyleResourceValue) themeValue);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a new {@link ResourceResolver} which records all resource resolution
+ * lookups into the given list. Note that it is the responsibility of the caller
+ * to clear/reset the list between subsequent lookup operations.
+ *
+ * @param lookupChain the list to write resource lookups into
+ * @return a new {@link ResourceResolver}
+ */
+ public ResourceResolver createRecorder(List<ResourceValue> lookupChain) {
+ ResourceResolver resolver = new RecordingResourceResolver(
+ lookupChain, mProjectResources, mFrameworkResources, mThemeName, mIsProjectTheme);
+ resolver.mFrameworkProvider = mFrameworkProvider;
+ resolver.mLogger = mLogger;
+ resolver.mDefaultTheme = mDefaultTheme;
+ resolver.mStyleInheritanceMap.putAll(mStyleInheritanceMap);
+ resolver.mThemes.addAll(mThemes);
+ return resolver;
+ }
+
+ private static class RecordingResourceResolver extends ResourceResolver {
+ @NonNull private List<ResourceValue> mLookupChain;
+
+ private RecordingResourceResolver(
+ @NonNull List<ResourceValue> lookupChain,
+ @NonNull Map<ResourceType, Map<String, ResourceValue>> projectResources,
+ @NonNull Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
+ @NonNull String themeName, boolean isProjectTheme) {
+ super(projectResources, frameworkResources, themeName, isProjectTheme);
+ mLookupChain = lookupChain;
+ }
+
+ @Override
+ public ResourceValue resolveResValue(ResourceValue resValue) {
+ if (resValue != null) {
+ mLookupChain.add(resValue);
+ }
+
+ return super.resolveResValue(resValue);
+ }
+
+ @Override
+ public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
+ if (!mLookupChain.isEmpty() && reference != null && reference.startsWith(PREFIX_RESOURCE_REF)) {
+ ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
+ if (!reference.equals(prev.getValue())) {
+ ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),
+ prev.isFramework());
+ next.setValue(reference);
+ mLookupChain.add(next);
+ }
+ }
+
+ ResourceValue resValue = super.findResValue(reference, forceFrameworkOnly);
+
+ if (resValue != null) {
+ mLookupChain.add(resValue);
+ }
+
+ return resValue;
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
+ boolean isFrameworkAttr) {
+ ResourceValue value = super.findItemInStyle(style, itemName, isFrameworkAttr);
+ if (value != null) {
+ mLookupChain.add(value);
+ }
+ return value;
+ }
+
+ @Override
+ public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
+ ResourceValue value = super.findItemInTheme(attrName, isFrameworkAttr);
+ if (value != null) {
+ mLookupChain.add(value);
+ }
+ return value;
+ }
+
+ @Override
+ public ResourceValue resolveValue(ResourceType type, String name, String value,
+ boolean isFrameworkValue) {
+ ResourceValue resourceValue = super.resolveValue(type, name, value, isFrameworkValue);
+ if (resourceValue != null) {
+ mLookupChain.add(resourceValue);
+ }
+ return resourceValue;
+ }
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceUrl.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceUrl.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ResourceUrl.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ResourceUrl.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ScanningContext.java b/sdk-common/src/main/java/com/android/ide/common/resources/ScanningContext.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ScanningContext.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ScanningContext.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/SingleResourceFile.java b/sdk-common/src/main/java/com/android/ide/common/resources/SingleResourceFile.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/SingleResourceFile.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/SingleResourceFile.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java b/sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ValidatingResourceParser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java b/sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/ValueResourceParser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/Configurable.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/Configurable.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/Configurable.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/Configurable.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DensityQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DensityQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DensityQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/DensityQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java
new file mode 100644
index 0000000..3389bca
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java
@@ -0,0 +1,1211 @@
+/*
+ * Copyright (C) 2007 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.ide.common.resources.configuration;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.Density;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ScreenOrientation;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+
+/**
+ * Represents the configuration for Resource Folders. All the properties have a default
+ * value which means that the property is not set.
+ */
+public final class FolderConfiguration implements Comparable<FolderConfiguration> {
+
+ @SuppressWarnings("NullableProblems") // done in static-block below
+ @NonNull
+ private static final ResourceQualifier[] DEFAULT_QUALIFIERS;
+
+ static {
+ // get the default qualifiers.
+ FolderConfiguration defaultConfig = new FolderConfiguration();
+ defaultConfig.createDefault();
+ DEFAULT_QUALIFIERS = defaultConfig.getQualifiers();
+ }
+
+ /** Splitter which can be used to split qualifiers */
+ public static final Splitter QUALIFIER_SPLITTER = Splitter.on('-');
+
+
+ private final ResourceQualifier[] mQualifiers;
+
+ private static final int INDEX_COUNTRY_CODE = 0;
+ private static final int INDEX_NETWORK_CODE = 1;
+ private static final int INDEX_LOCALE = 2;
+ private static final int INDEX_LAYOUT_DIR = 3;
+ private static final int INDEX_SMALLEST_SCREEN_WIDTH = 4;
+ private static final int INDEX_SCREEN_WIDTH = 5;
+ private static final int INDEX_SCREEN_HEIGHT = 6;
+ private static final int INDEX_SCREEN_LAYOUT_SIZE = 7;
+ private static final int INDEX_SCREEN_RATIO = 8;
+ private static final int INDEX_SCREEN_ROUND = 9;
+ private static final int INDEX_SCREEN_ORIENTATION = 10;
+ private static final int INDEX_UI_MODE = 11;
+ private static final int INDEX_NIGHT_MODE = 12;
+ private static final int INDEX_PIXEL_DENSITY = 13;
+ private static final int INDEX_TOUCH_TYPE = 14;
+ private static final int INDEX_KEYBOARD_STATE = 15;
+ private static final int INDEX_TEXT_INPUT_METHOD = 16;
+ private static final int INDEX_NAVIGATION_STATE = 17;
+ private static final int INDEX_NAVIGATION_METHOD = 18;
+ private static final int INDEX_SCREEN_DIMENSION = 19;
+ private static final int INDEX_VERSION = 20;
+ private static final int INDEX_COUNT = 21;
+
+ public FolderConfiguration() {
+ mQualifiers = new ResourceQualifier[INDEX_COUNT];
+ }
+
+ private FolderConfiguration(ResourceQualifier[] qualifiers) {
+ this();
+ System.arraycopy(qualifiers, 0, mQualifiers, 0, INDEX_COUNT);
+ }
+ /**
+ * Creates a {@link FolderConfiguration} matching the folder segments.
+ * @param folderSegments The segments of the folder name. The first segments should contain
+ * the name of the folder
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ */
+ @Nullable
+ public static FolderConfiguration getConfig(@NonNull String[] folderSegments) {
+ Iterator<String> iterator = Iterators.forArray(folderSegments);
+ if (iterator.hasNext()) {
+ // Skip the first segment: it should be just the base folder, such as "values" or
+ // "layout"
+ iterator.next();
+ }
+
+ return getConfigFromQualifiers(iterator);
+ }
+
+ /**
+ * Creates a {@link FolderConfiguration} matching the folder segments.
+ * @param folderSegments The segments of the folder name. The first segments should contain
+ * the name of the folder
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ * @see FolderConfiguration#getConfig(String[])
+ */
+ @Nullable
+ public static FolderConfiguration getConfig(@NonNull Iterable<String> folderSegments) {
+ Iterator<String> iterator = folderSegments.iterator();
+ if (iterator.hasNext()) {
+ // Skip the first segment: it should be just the base folder, such as "values" or
+ // "layout"
+ iterator.next();
+ }
+
+ return getConfigFromQualifiers(iterator);
+ }
+
+ /**
+ * Creates a {@link FolderConfiguration} matching the qualifiers.
+ *
+ * @param qualifiers the qualifiers.
+ *
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ */
+ @Nullable
+ public static FolderConfiguration getConfigFromQualifiers(
+ @NonNull Iterable<String> qualifiers) {
+ return getConfigFromQualifiers(qualifiers.iterator());
+ }
+
+ /**
+ * Creates a {@link FolderConfiguration} matching the qualifiers.
+ *
+ * @param qualifiers An iterator on the qualifiers.
+ *
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ */
+ @Nullable
+ public static FolderConfiguration getConfigFromQualifiers(
+ @NonNull Iterator<String> qualifiers) {
+ FolderConfiguration config = new FolderConfiguration();
+
+ // we are going to loop through the segments, and match them with the first
+ // available qualifier. If the segment doesn't match we try with the next qualifier.
+ // Because the order of the qualifier is fixed, we do not reset the first qualifier
+ // after each successful segment.
+ // If we run out of qualifier before processing all the segments, we fail.
+
+ int qualifierIndex = 0;
+ int qualifierCount = DEFAULT_QUALIFIERS.length;
+
+ /*
+ Process a series of qualifiers and parse them into a set of ResourceQualifiers
+ in the new folder configuration.
+
+ The basic loop is as follows:
+
+ while (qualifiers.hasNext()) {
+ String seg = qualifiers.next();
+
+ while (qualifierIndex < qualifierCount &&
+ !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
+ qualifierIndex++;
+ }
+
+ // if we reached the end of the qualifier we didn't find a matching qualifier.
+ if (qualifierIndex == qualifierCount) {
+ return null;
+ } else {
+ qualifierIndex++; // already processed this one
+ }
+ }
+
+ In other words, we process through the iterable, one segment at a time, and
+ for that segment, we iterate up through the qualifiers and ask each one if
+ they can handle it (via checkAndSet); if they can, we are done with that segment
+ *and* that qualifier, so next time through the segment loop we won't keep retrying
+ the same qualifiers. So, we are basically iterating through two lists (segments
+ and qualifiers) at the same time).
+
+ However, locales are a special exception to this: we want to combine *two* segments
+ into a single qualifier when you specify both a language and a region.
+ E.g. for "en-rUS-ldltr" we want a single LocaleQualifier holding both "en" and "rUS"
+ and then a LayoutDirectionQualifier for ldltr.
+
+ Therefore, we've unrolled the above loop: we process all identifiers up to
+ the locale qualifier index.
+
+ Then, at the locale qualifier index, IF we get a match, we don't increment
+ the qualifierIndex: instead, we fetch the next segment, and if it matches
+ as a region, we augment the locale qualifier and continue -- otherwise, we
+ bail and process the next segment as usual.
+
+ And then we finally iterate through the remaining qualifiers and segments; this
+ is basically the first loop again, iterating from the post-locale qualifier
+ up to the end.
+ */
+
+ if (!qualifiers.hasNext()) {
+ return config;
+ }
+
+ while (qualifiers.hasNext()) {
+ String seg = qualifiers.next();
+ if (seg.isEmpty()) {
+ return null; // Not a valid folder configuration
+ }
+
+ // TODO: Perform case normalization later (on a per qualifier basis)
+ seg = seg.toLowerCase(Locale.US); // no-op if string is already in lower case
+
+ while (qualifierIndex < INDEX_LOCALE &&
+ !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
+ qualifierIndex++;
+ }
+
+ // if we reached the end of the qualifier we didn't find a matching qualifier.
+ if (qualifierIndex == INDEX_LOCALE) {
+ // Ready for locale matching now; that requires some special
+ // casing described below
+
+ boolean handle = true;
+ // Don't need to lowercase; qualifier will normalize case on its own
+ if (DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
+ qualifierIndex++;
+ if (qualifiers.hasNext()) {
+ seg = qualifiers.next();
+ if (seg.isEmpty()) {
+ return null; // Not a valid folder configuration
+ }
+ // Is the next qualifier a region? If so, amend the existing
+ // LocaleQualifier
+ if (LocaleQualifier.isRegionSegment(seg)) {
+ LocaleQualifier localeQualifier = config.getLocaleQualifier();
+ assert localeQualifier != null; // because checkAndSet returned true above
+ localeQualifier.setRegionSegment(seg);
+ handle = false;
+ } else {
+ // No, not a region, so perform normal processing
+ seg = seg.toLowerCase(Locale.US); // no-op if string is already in lower case
+ }
+ } else {
+ return config;
+ }
+ }
+
+ if (handle) {
+ while (qualifierIndex < qualifierCount &&
+ !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
+ qualifierIndex++;
+ }
+
+ // if we reached the end of the qualifier we didn't find a matching qualifier.
+ if (qualifierIndex == qualifierCount) {
+ // Ready for locale matching now; that requires some special
+ // casing described below
+ return null;
+ } else {
+ qualifierIndex++; // already processed this one
+ }
+ }
+
+ break;
+ } else {
+ qualifierIndex++; // already processed this one
+ }
+ }
+
+ // Same loop as above, but we continue from after the locales
+ while (qualifiers.hasNext()) {
+ String seg = qualifiers.next();
+ if (seg.isEmpty()) {
+ return null; // Not a valid folder configuration
+ }
+
+ seg = seg.toLowerCase(Locale.US); // no-op if string is already in lower case
+
+ while (qualifierIndex < qualifierCount &&
+ !DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config)) {
+ qualifierIndex++;
+ }
+
+ // if we reached the end of the qualifier we didn't find a matching qualifier.
+ if (qualifierIndex == qualifierCount) {
+ return null;
+ } else {
+ qualifierIndex++; // already processed this one
+ }
+ }
+
+ return config;
+ }
+
+ /**
+ * Creates a {@link FolderConfiguration} matching the given folder name.
+ *
+ * @param folderName the folder name
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ */
+ @Nullable
+ public static FolderConfiguration getConfigForFolder(@NonNull String folderName) {
+ return getConfig(QUALIFIER_SPLITTER.split(folderName));
+ }
+
+ /**
+ * Creates a copy of the given {@link FolderConfiguration}, that can be modified without
+ * affecting the original.
+ */
+ @NonNull
+ public static FolderConfiguration copyOf(@NonNull FolderConfiguration original) {
+ return new FolderConfiguration(original.mQualifiers);
+ }
+
+ /**
+ * Creates a {@link FolderConfiguration} matching the given qualifier string
+ * (just the qualifiers; e.g. for a folder like "values-en-rUS" this would be "en-rUS").
+ *
+ * @param qualifierString the qualifier string
+ * @return a FolderConfiguration object, or null if the qualifier string isn't valid..
+ */
+ @Nullable
+ public static FolderConfiguration getConfigForQualifierString(@NonNull String qualifierString) {
+ if (qualifierString.isEmpty()) {
+ return new FolderConfiguration();
+ } else {
+ return getConfigFromQualifiers(QUALIFIER_SPLITTER.split(qualifierString));
+ }
+ }
+
+ /**
+ * Returns the number of {@link ResourceQualifier} that make up a Folder configuration.
+ */
+ public static int getQualifierCount() {
+ return INDEX_COUNT;
+ }
+
+ /**
+ * Sets the config from the qualifiers of a given <var>config</var>.
+ * <p/>This is equivalent to <code>set(config, false)</code>
+ * @param config the configuration to set
+ *
+ * @see #set(FolderConfiguration, boolean)
+ */
+ public void set(@Nullable FolderConfiguration config) {
+ set(config, false /*nonFakeValuesOnly*/);
+ }
+
+ /**
+ * Sets the config from the qualifiers of a given <var>config</var>.
+ * @param config the configuration to set
+ * @param nonFakeValuesOnly if set to true this ignore qualifiers for which the
+ * current value is a fake value.
+ *
+ * @see ResourceQualifier#hasFakeValue()
+ */
+ public void set(@Nullable FolderConfiguration config, boolean nonFakeValuesOnly) {
+ if (config != null) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier q = config.mQualifiers[i];
+ if (!nonFakeValuesOnly || q == null || !q.hasFakeValue()) {
+ mQualifiers[i] = q;
+ }
+ }
+ }
+ }
+
+ /**
+ * Reset the config.
+ * <p/>This makes qualifiers at all indices <code>null</code>.
+ */
+ public void reset() {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ mQualifiers[i] = null;
+ }
+ }
+
+ /**
+ * Removes the qualifiers from the receiver if they are present (and valid)
+ * in the given configuration.
+ */
+ public void substract(@NonNull FolderConfiguration config) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (config.mQualifiers[i] != null && config.mQualifiers[i].isValid()) {
+ mQualifiers[i] = null;
+ }
+ }
+ }
+
+ /**
+ * Adds the non-qualifiers from the given config.
+ * Qualifiers that are null in the given config do not change in the receiver.
+ */
+ public void add(@NonNull FolderConfiguration config) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (config.mQualifiers[i] != null) {
+ mQualifiers[i] = config.mQualifiers[i];
+ }
+ }
+ }
+
+ /**
+ * Returns the first invalid qualifier, or <code>null<code> if they are all valid (or if none
+ * exists).
+ */
+ @Nullable
+ public ResourceQualifier getInvalidQualifier() {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null && !mQualifiers[i].isValid()) {
+ return mQualifiers[i];
+ }
+ }
+
+ // all allocated qualifiers are valid, we return null.
+ return null;
+ }
+
+ /**
+ * Adds a qualifier to the {@link FolderConfiguration}
+ * @param qualifier the {@link ResourceQualifier} to add.
+ */
+ public void addQualifier(@Nullable ResourceQualifier qualifier) {
+ if (qualifier instanceof CountryCodeQualifier) {
+ mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
+
+ } else if (qualifier instanceof NetworkCodeQualifier) {
+ mQualifiers[INDEX_NETWORK_CODE] = qualifier;
+
+ } else if (qualifier instanceof LocaleQualifier) {
+ mQualifiers[INDEX_LOCALE] = qualifier;
+
+ } else if (qualifier instanceof LayoutDirectionQualifier) {
+ mQualifiers[INDEX_LAYOUT_DIR] = qualifier;
+
+ } else if (qualifier instanceof SmallestScreenWidthQualifier) {
+ mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
+
+ } else if (qualifier instanceof ScreenWidthQualifier) {
+ mQualifiers[INDEX_SCREEN_WIDTH] = qualifier;
+
+ } else if (qualifier instanceof ScreenHeightQualifier) {
+ mQualifiers[INDEX_SCREEN_HEIGHT] = qualifier;
+
+ } else if (qualifier instanceof ScreenSizeQualifier) {
+ mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = qualifier;
+
+ } else if (qualifier instanceof ScreenRatioQualifier) {
+ mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
+
+ } else if (qualifier instanceof ScreenRoundQualifier) {
+ mQualifiers[INDEX_SCREEN_ROUND] = qualifier;
+
+ } else if (qualifier instanceof ScreenOrientationQualifier) {
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
+
+ } else if (qualifier instanceof UiModeQualifier) {
+ mQualifiers[INDEX_UI_MODE] = qualifier;
+
+ } else if (qualifier instanceof NightModeQualifier) {
+ mQualifiers[INDEX_NIGHT_MODE] = qualifier;
+
+ } else if (qualifier instanceof DensityQualifier) {
+ mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
+
+ } else if (qualifier instanceof TouchScreenQualifier) {
+ mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
+
+ } else if (qualifier instanceof KeyboardStateQualifier) {
+ mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
+
+ } else if (qualifier instanceof TextInputMethodQualifier) {
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
+
+ } else if (qualifier instanceof NavigationStateQualifier) {
+ mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
+
+ } else if (qualifier instanceof NavigationMethodQualifier) {
+ mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
+
+ } else if (qualifier instanceof ScreenDimensionQualifier) {
+ mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
+
+ } else if (qualifier instanceof VersionQualifier) {
+ mQualifiers[INDEX_VERSION] = qualifier;
+
+ }
+ }
+
+ /**
+ * Removes a given qualifier from the {@link FolderConfiguration}.
+ * @param qualifier the {@link ResourceQualifier} to remove.
+ */
+ public void removeQualifier(@NonNull ResourceQualifier qualifier) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] == qualifier) {
+ mQualifiers[i] = null;
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns a qualifier by its index. The total number of qualifiers can be accessed by
+ * {@link #getQualifierCount()}.
+ * @param index the index of the qualifier to return.
+ * @return the qualifier or null if there are none at the index.
+ */
+ @Nullable
+ public ResourceQualifier getQualifier(int index) {
+ return mQualifiers[index];
+ }
+
+ public void setCountryCodeQualifier(CountryCodeQualifier qualifier) {
+ mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
+ }
+
+ @Nullable
+ public CountryCodeQualifier getCountryCodeQualifier() {
+ return (CountryCodeQualifier)mQualifiers[INDEX_COUNTRY_CODE];
+ }
+
+ public void setNetworkCodeQualifier(NetworkCodeQualifier qualifier) {
+ mQualifiers[INDEX_NETWORK_CODE] = qualifier;
+ }
+
+ @Nullable
+ public NetworkCodeQualifier getNetworkCodeQualifier() {
+ return (NetworkCodeQualifier)mQualifiers[INDEX_NETWORK_CODE];
+ }
+
+ public void setLocaleQualifier(LocaleQualifier qualifier) {
+ mQualifiers[INDEX_LOCALE] = qualifier;
+ }
+
+ @Nullable
+ public LocaleQualifier getLocaleQualifier() {
+ return (LocaleQualifier)mQualifiers[INDEX_LOCALE];
+ }
+
+ public void setLayoutDirectionQualifier(LayoutDirectionQualifier qualifier) {
+ mQualifiers[INDEX_LAYOUT_DIR] = qualifier;
+ }
+
+ @Nullable
+ public LayoutDirectionQualifier getLayoutDirectionQualifier() {
+ return (LayoutDirectionQualifier)mQualifiers[INDEX_LAYOUT_DIR];
+ }
+
+ public void setSmallestScreenWidthQualifier(SmallestScreenWidthQualifier qualifier) {
+ mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
+ }
+
+ @Nullable
+ public SmallestScreenWidthQualifier getSmallestScreenWidthQualifier() {
+ return (SmallestScreenWidthQualifier) mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH];
+ }
+
+ public void setScreenWidthQualifier(ScreenWidthQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_WIDTH] = qualifier;
+ }
+
+ @Nullable
+ public ScreenWidthQualifier getScreenWidthQualifier() {
+ return (ScreenWidthQualifier) mQualifiers[INDEX_SCREEN_WIDTH];
+ }
+
+ public void setScreenHeightQualifier(ScreenHeightQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_HEIGHT] = qualifier;
+ }
+
+ @Nullable
+ public ScreenHeightQualifier getScreenHeightQualifier() {
+ return (ScreenHeightQualifier) mQualifiers[INDEX_SCREEN_HEIGHT];
+ }
+
+ public void setScreenSizeQualifier(ScreenSizeQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = qualifier;
+ }
+
+ @Nullable
+ public ScreenSizeQualifier getScreenSizeQualifier() {
+ return (ScreenSizeQualifier)mQualifiers[INDEX_SCREEN_LAYOUT_SIZE];
+ }
+
+ public void setScreenRatioQualifier(ScreenRatioQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
+ }
+
+ @Nullable
+ public ScreenRatioQualifier getScreenRatioQualifier() {
+ return (ScreenRatioQualifier)mQualifiers[INDEX_SCREEN_RATIO];
+ }
+
+ public void setScreenRoundQualifier(ScreenRoundQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_ROUND] = qualifier;
+ }
+
+ @Nullable
+ public ScreenRoundQualifier getScreenRoundQualifier() {
+ return (ScreenRoundQualifier)mQualifiers[INDEX_SCREEN_ROUND];
+ }
+
+ public void setScreenOrientationQualifier(ScreenOrientationQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
+ }
+
+ @Nullable
+ public ScreenOrientationQualifier getScreenOrientationQualifier() {
+ return (ScreenOrientationQualifier)mQualifiers[INDEX_SCREEN_ORIENTATION];
+ }
+
+ public void setUiModeQualifier(UiModeQualifier qualifier) {
+ mQualifiers[INDEX_UI_MODE] = qualifier;
+ }
+
+ @Nullable
+ public UiModeQualifier getUiModeQualifier() {
+ return (UiModeQualifier)mQualifiers[INDEX_UI_MODE];
+ }
+
+ public void setNightModeQualifier(NightModeQualifier qualifier) {
+ mQualifiers[INDEX_NIGHT_MODE] = qualifier;
+ }
+
+ @Nullable
+ public NightModeQualifier getNightModeQualifier() {
+ return (NightModeQualifier)mQualifiers[INDEX_NIGHT_MODE];
+ }
+
+ public void setDensityQualifier(DensityQualifier qualifier) {
+ mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
+ }
+
+ @Nullable
+ public DensityQualifier getDensityQualifier() {
+ return (DensityQualifier)mQualifiers[INDEX_PIXEL_DENSITY];
+ }
+
+ public void setTouchTypeQualifier(TouchScreenQualifier qualifier) {
+ mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
+ }
+
+ @Nullable
+ public TouchScreenQualifier getTouchTypeQualifier() {
+ return (TouchScreenQualifier)mQualifiers[INDEX_TOUCH_TYPE];
+ }
+
+ public void setKeyboardStateQualifier(KeyboardStateQualifier qualifier) {
+ mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
+ }
+
+ @Nullable
+ public KeyboardStateQualifier getKeyboardStateQualifier() {
+ return (KeyboardStateQualifier)mQualifiers[INDEX_KEYBOARD_STATE];
+ }
+
+ public void setTextInputMethodQualifier(TextInputMethodQualifier qualifier) {
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
+ }
+
+ @Nullable
+ public TextInputMethodQualifier getTextInputMethodQualifier() {
+ return (TextInputMethodQualifier)mQualifiers[INDEX_TEXT_INPUT_METHOD];
+ }
+
+ public void setNavigationStateQualifier(NavigationStateQualifier qualifier) {
+ mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
+ }
+
+ @Nullable
+ public NavigationStateQualifier getNavigationStateQualifier() {
+ return (NavigationStateQualifier)mQualifiers[INDEX_NAVIGATION_STATE];
+ }
+
+ public void setNavigationMethodQualifier(NavigationMethodQualifier qualifier) {
+ mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
+ }
+
+ @Nullable
+ public NavigationMethodQualifier getNavigationMethodQualifier() {
+ return (NavigationMethodQualifier)mQualifiers[INDEX_NAVIGATION_METHOD];
+ }
+
+ public void setScreenDimensionQualifier(ScreenDimensionQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
+ }
+
+ @Nullable
+ public ScreenDimensionQualifier getScreenDimensionQualifier() {
+ return (ScreenDimensionQualifier)mQualifiers[INDEX_SCREEN_DIMENSION];
+ }
+
+ public void setVersionQualifier(VersionQualifier qualifier) {
+ mQualifiers[INDEX_VERSION] = qualifier;
+ }
+
+ @Nullable
+ public VersionQualifier getVersionQualifier() {
+ return (VersionQualifier)mQualifiers[INDEX_VERSION];
+ }
+
+ /**
+ * Normalize a folder configuration based on the API level of its qualifiers
+ */
+ public void normalize() {
+ int minSdk = 1;
+ for (ResourceQualifier qualifier : mQualifiers) {
+ if (qualifier != null) {
+ int min = qualifier.since();
+ if (min > minSdk) {
+ minSdk = min;
+ }
+ }
+ }
+
+ if (minSdk == 1) {
+ return;
+ }
+
+ if (mQualifiers[INDEX_VERSION] == null ||
+ ((VersionQualifier)mQualifiers[INDEX_VERSION]).getVersion() < minSdk) {
+ mQualifiers[INDEX_VERSION] = new VersionQualifier(minSdk);
+ }
+ }
+
+ /**
+ * Updates the {@link SmallestScreenWidthQualifier}, {@link ScreenWidthQualifier}, and
+ * {@link ScreenHeightQualifier} based on the (required) values of
+ * {@link ScreenDimensionQualifier} {@link DensityQualifier}, and
+ * {@link ScreenOrientationQualifier}.
+ *
+ * Also the density cannot be {@link Density#NODPI} as it's not valid on a device.
+ */
+ public void updateScreenWidthAndHeight() {
+
+ ResourceQualifier sizeQ = mQualifiers[INDEX_SCREEN_DIMENSION];
+ ResourceQualifier densityQ = mQualifiers[INDEX_PIXEL_DENSITY];
+ ResourceQualifier orientQ = mQualifiers[INDEX_SCREEN_ORIENTATION];
+
+ if (sizeQ != null && densityQ != null && orientQ != null) {
+ Density density = ((DensityQualifier) densityQ).getValue();
+ if (density == Density.NODPI || density == Density.ANYDPI) {
+ return;
+ }
+
+ ScreenOrientation orientation = ((ScreenOrientationQualifier) orientQ).getValue();
+
+ int size1 = ((ScreenDimensionQualifier) sizeQ).getValue1();
+ int size2 = ((ScreenDimensionQualifier) sizeQ).getValue2();
+
+ // make sure size1 is the biggest (should be the case, but make sure)
+ if (size1 < size2) {
+ int a = size1;
+ size1 = size2;
+ size2 = a;
+ }
+
+ // compute the dp. round them up since we want -w480dp to match a 480.5dp screen
+ int dp1 = (int) Math.ceil(size1 * Density.DEFAULT_DENSITY / density.getDpiValue());
+ int dp2 = (int) Math.ceil(size2 * Density.DEFAULT_DENSITY / density.getDpiValue());
+
+ setSmallestScreenWidthQualifier(new SmallestScreenWidthQualifier(dp2));
+
+ switch (orientation) {
+ case PORTRAIT:
+ setScreenWidthQualifier(new ScreenWidthQualifier(dp2));
+ setScreenHeightQualifier(new ScreenHeightQualifier(dp1));
+ break;
+ case LANDSCAPE:
+ setScreenWidthQualifier(new ScreenWidthQualifier(dp1));
+ setScreenHeightQualifier(new ScreenHeightQualifier(dp2));
+ break;
+ case SQUARE:
+ setScreenWidthQualifier(new ScreenWidthQualifier(dp2));
+ setScreenHeightQualifier(new ScreenHeightQualifier(dp2));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Returns whether an object is equals to the receiver.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof FolderConfiguration) {
+ FolderConfiguration fc = (FolderConfiguration)obj;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier qualifier = mQualifiers[i];
+ ResourceQualifier fcQualifier = fc.mQualifiers[i];
+ if (qualifier != null) {
+ if (!qualifier.equals(fcQualifier)) {
+ return false;
+ }
+ } else if (fcQualifier != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * Returns whether the Configuration has only default values.
+ */
+ public boolean isDefault() {
+ for (ResourceQualifier irq : mQualifiers) {
+ if (irq != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the name of a folder with the configuration.
+ */
+ @NonNull
+ public String getFolderName(@NonNull ResourceFolderType folder) {
+ StringBuilder result = new StringBuilder(folder.getName());
+
+ for (ResourceQualifier qualifier : mQualifiers) {
+ if (qualifier != null) {
+ String segment = qualifier.getFolderSegment();
+ if (segment != null && !segment.isEmpty()) {
+ result.append(SdkConstants.RES_QUALIFIER_SEP);
+ result.append(segment);
+ }
+ }
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Returns the folder configuration as a unique key
+ */
+ @NonNull
+ public String getUniqueKey() {
+ StringBuilder result = new StringBuilder(100);
+
+ for (ResourceQualifier qualifier : mQualifiers) {
+ if (qualifier != null) {
+ String segment = qualifier.getFolderSegment();
+ if (segment != null && !segment.isEmpty()) {
+ result.append(SdkConstants.RES_QUALIFIER_SEP);
+ result.append(segment);
+ }
+ }
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Returns {@link #toDisplayString()}.
+ */
+ @NonNull
+ @Override
+ public String toString() {
+ return toDisplayString();
+ }
+
+ /**
+ * Returns a string valid for display purpose.
+ */
+ @NonNull
+ public String toDisplayString() {
+ if (isDefault()) {
+ return "default";
+ }
+
+ StringBuilder result = null;
+ int index = 0;
+ ResourceQualifier qualifier;
+
+ while (index < INDEX_COUNT) {
+ qualifier = mQualifiers[index++];
+ if (qualifier != null) {
+ if (result == null) {
+ result = new StringBuilder();
+ } else {
+ result.append(", "); //$NON-NLS-1$
+ }
+ result.append(qualifier.getLongDisplayValue());
+
+ }
+ }
+
+ return result == null ? "" : result.toString();
+ }
+
+ /**
+ * Returns a string for display purposes which uses only the short names of the qualifiers
+ */
+ @NonNull
+ public String toShortDisplayString() {
+ if (isDefault()) {
+ return "default";
+ }
+
+ StringBuilder result = new StringBuilder(100);
+ int index = 0;
+
+ while (index < INDEX_COUNT) {
+ ResourceQualifier qualifier = mQualifiers[index++];
+ if (qualifier != null) {
+ if (result.length() > 0) {
+ result.append(',');
+ }
+ result.append(qualifier.getShortDisplayValue());
+ }
+ }
+
+ return result.toString();
+ }
+
+ @Override
+ public int compareTo(@NonNull FolderConfiguration folderConfig) {
+ // default are always at the top.
+ if (isDefault()) {
+ if (folderConfig.isDefault()) {
+ return 0;
+ }
+ return -1;
+ }
+
+ // now we compare the qualifiers
+ for (int i = 0 ; i < INDEX_COUNT; i++) {
+ ResourceQualifier qualifier1 = mQualifiers[i];
+ ResourceQualifier qualifier2 = folderConfig.mQualifiers[i];
+
+ if (qualifier1 == null) {
+ if (qualifier2 != null) {
+ return -1;
+ }
+ } else {
+ if (qualifier2 == null) {
+ return 1;
+ } else {
+ int result = qualifier1.compareTo(qualifier2);
+
+ if (result == 0) {
+ continue;
+ }
+
+ return result;
+ }
+ }
+ }
+
+ // if we arrive here, all the qualifier matches
+ return 0;
+ }
+
+ /**
+ * Returns the best matching {@link Configurable} for this configuration.
+ *
+ * @param configurables the list of {@link Configurable} to choose from.
+ *
+ * @return an item from the given list of {@link Configurable} or null.
+ *
+ * See http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
+ */
+ @Nullable
+ public <T extends Configurable> T findMatchingConfigurable(@Nullable List<T> configurables) {
+ // Because we skip qualifiers where reference configuration doesn't have a valid qualifier,
+ // we can end up with more than one match. In this case, we just take the first one.
+ List<T> matches = findMatchingConfigurables(configurables);
+ return matches.isEmpty() ? null : matches.get(0);
+ }
+
+ /**
+ * Tries to eliminate as many {@link Configurable}s as possible. It skips the
+ * {@link ResourceQualifier} if it's not valid and assumes that all resources match it.
+ *
+ * @param configurables the list of {@code Configurable} to choose from.
+ *
+ * @return a list of items from the above list. This may be empty.
+ */
+ @NonNull
+ public <T extends Configurable> List<T> findMatchingConfigurables(
+ @Nullable List<T> configurables) {
+ if (configurables == null) {
+ return Collections.emptyList();
+ }
+
+ //
+ // 1: eliminate resources that contradict the reference configuration
+ // 2: pick next qualifier type
+ // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4.
+ // 4: eliminate resources that don't use this qualifier.
+ // 5: if more than one resource left, go back to 2.
+ //
+ // The precedence of the qualifiers is more important than the number of qualifiers that
+ // exactly match the device.
+
+ // 1: eliminate resources that contradict
+ ArrayList<T> matchingConfigurables = new ArrayList<T>();
+ for (T res : configurables) {
+ final FolderConfiguration configuration = res.getConfiguration();
+ if (configuration != null && configuration.isMatchFor(this)) {
+ matchingConfigurables.add(res);
+ }
+ }
+
+ // if there is at most one match, just take it
+ if (matchingConfigurables.size() < 2) {
+ return matchingConfigurables;
+ }
+
+ // 2. Loop on the qualifiers, and eliminate matches
+ final int count = getQualifierCount();
+ for (int q = 0 ; q < count ; q++) {
+ // look to see if one configurable has this qualifier.
+ // At the same time also record the best match value for the qualifier (if applicable).
+
+ // The reference value, to find the best match.
+ // Note that this qualifier could be null. In which case any qualifier found in the
+ // possible match, will all be considered best match.
+ ResourceQualifier referenceQualifier = getQualifier(q);
+
+ // If referenceQualifier is null, we don't eliminate resources based on it.
+ if (referenceQualifier == null) {
+ continue;
+ }
+
+ boolean found = false;
+ ResourceQualifier bestMatch = null; // this is to store the best match.
+ for (Configurable configurable : matchingConfigurables) {
+ ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q);
+ if (qualifier != null) {
+ // set the flag.
+ found = true;
+
+ // Now check for a best match. If the reference qualifier is null ,
+ // any qualifier is a "best" match (we don't need to record all of them.
+ // Instead the non compatible ones are removed below)
+ if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
+ bestMatch = qualifier;
+ }
+ }
+ }
+
+ // 4. If a configurable has a qualifier at the current index, remove all the ones that
+ // do not have one, or whose qualifier value does not equal the best match found above
+ // unless there's no reference qualifier, in which case they are all considered
+ // "best" match.
+ if (found) {
+ for (int i = 0 ; i < matchingConfigurables.size(); ) {
+ Configurable configurable = matchingConfigurables.get(i);
+ FolderConfiguration configuration = configurable.getConfiguration();
+ ResourceQualifier qualifier = configuration.getQualifier(q);
+
+ if (q == INDEX_SCREEN_LAYOUT_SIZE) {
+ // For screen size, the null qualifier matches better than ScreenSize.SMALL
+ // when matching a screen size normal or bigger.
+ // Since it works differently than other qualifiers, we treat it separately
+ if (!Objects.equal(bestMatch, qualifier)) {
+ matchingConfigurables.remove(configurable);
+ }
+ else {
+ i++;
+ }
+ continue;
+ }
+
+ if (qualifier == null) {
+ // this resources has no qualifier of this type: rejected.
+ matchingConfigurables.remove(configurable);
+ } else if (bestMatch != null && !bestMatch.equals(qualifier)) {
+ // there's a reference qualifier and there is a better match for it than
+ // this resource, so we reject it.
+ matchingConfigurables.remove(configurable);
+ } else {
+ // looks like we keep this resource, move on to the next one.
+ //noinspection AssignmentToForLoopParameter
+ i++;
+ }
+ }
+
+ // at this point we may have run out of matching resources before going
+ // through all the qualifiers.
+ if (matchingConfigurables.size() < 2) {
+ break;
+ }
+ }
+ }
+
+ // We've exhausted all the qualifiers. If we still have matching ones left, return all.
+ return matchingConfigurables;
+ }
+
+ /**
+ * Returns whether the configuration is a match for the given reference config.
+ * <p/>A match means that, for each qualifier of this config
+ * <ul>
+ * <li>The reference config has no value set
+ * <li>or, the qualifier of the reference config is a match. Depending on the qualifier type
+ * this does not mean the same exact value.</li>
+ * </ul>
+ * @param referenceConfig The reference configuration to test against.
+ * @return true if the configuration matches.
+ */
+ public boolean isMatchFor(@Nullable FolderConfiguration referenceConfig) {
+ if (referenceConfig == null) {
+ return false;
+ }
+
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier testQualifier = mQualifiers[i];
+ ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
+
+ // it's only a non match if both qualifiers are non-null, and they don't match.
+ if (testQualifier != null && referenceQualifier != null &&
+ !testQualifier.isMatchFor(referenceQualifier)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the index of the first non null {@link ResourceQualifier} starting at index
+ * <var>startIndex</var>
+ * @param startIndex
+ * @return -1 if no qualifier was found.
+ */
+ public int getHighestPriorityQualifier(int startIndex) {
+ for (int i = startIndex ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create default qualifiers.
+ * <p/>This creates qualifiers with no values for all indices.
+ */
+ public void createDefault() {
+ mQualifiers[INDEX_COUNTRY_CODE] = new CountryCodeQualifier();
+ mQualifiers[INDEX_NETWORK_CODE] = new NetworkCodeQualifier();
+ mQualifiers[INDEX_LOCALE] = new LocaleQualifier();
+ mQualifiers[INDEX_LAYOUT_DIR] = new LayoutDirectionQualifier();
+ mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = new SmallestScreenWidthQualifier();
+ mQualifiers[INDEX_SCREEN_WIDTH] = new ScreenWidthQualifier();
+ mQualifiers[INDEX_SCREEN_HEIGHT] = new ScreenHeightQualifier();
+ mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = new ScreenSizeQualifier();
+ mQualifiers[INDEX_SCREEN_RATIO] = new ScreenRatioQualifier();
+ mQualifiers[INDEX_SCREEN_ROUND] = new ScreenRoundQualifier();
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = new ScreenOrientationQualifier();
+ mQualifiers[INDEX_UI_MODE] = new UiModeQualifier();
+ mQualifiers[INDEX_NIGHT_MODE] = new NightModeQualifier();
+ mQualifiers[INDEX_PIXEL_DENSITY] = new DensityQualifier();
+ mQualifiers[INDEX_TOUCH_TYPE] = new TouchScreenQualifier();
+ mQualifiers[INDEX_KEYBOARD_STATE] = new KeyboardStateQualifier();
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = new TextInputMethodQualifier();
+ mQualifiers[INDEX_NAVIGATION_STATE] = new NavigationStateQualifier();
+ mQualifiers[INDEX_NAVIGATION_METHOD] = new NavigationMethodQualifier();
+ mQualifiers[INDEX_SCREEN_DIMENSION] = new ScreenDimensionQualifier();
+ mQualifiers[INDEX_VERSION] = new VersionQualifier();
+ }
+
+ /**
+ * Returns an array of all the non null qualifiers.
+ */
+ @NonNull
+ public ResourceQualifier[] getQualifiers() {
+ int count = 0;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ count++;
+ }
+ }
+
+ ResourceQualifier[] array = new ResourceQualifier[count];
+ int index = 0;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ array[index++] = mQualifiers[i];
+ }
+ }
+
+ return array;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LayoutDirectionQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LayoutDirectionQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LayoutDirectionQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/LayoutDirectionQualifier.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LocaleQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LocaleQualifier.java
new file mode 100644
index 0000000..389f5c4
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/LocaleQualifier.java
@@ -0,0 +1,608 @@
+/*
+ * 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.ide.common.resources.configuration;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ * A locale qualifier, which can be constructed from:
+ * <ul>
+ * <li>A plain 2-letter language descriptor</li>
+ * <li>A 2-letter language descriptor followed by a -r 2 letter region qualifier</li>
+ * <li>A plain 3-letter language descriptor</li>
+ * <li>A 3-letter language descriptor followed by a -r 2 letter region qualifier</li>
+ * <li>A BCP 47 language tag. The BCP-47 tag uses + instead of - as separators,
+ * and has the prefix b+. Therefore, the BCP-47 tag "zh-Hans-CN" would be
+ * written as "b+zh+Hans+CN" instead.</li>
+ * </ul>
+ */
+public final class LocaleQualifier extends ResourceQualifier {
+ public static final String FAKE_VALUE = "__"; //$NON-NLS-1$
+ public static final String NAME = "Locale";
+ // TODO: Case insensitive check!
+ public static final String BCP_47_PREFIX = "b+"; //$NON-NLS-1$
+
+ /**
+ * Old historical value for car dock mode that conflicts with the Carib language. The latter is
+ * therefore, excluded from Android.
+ */
+ private static final String CAR_DOCK_MODE = "car";
+
+ @NonNull private String mFull;
+ @NonNull private String mLanguage;
+ @Nullable private String mRegion;
+ @Nullable private String mScript;
+
+ public LocaleQualifier() {
+ mFull = "";
+ }
+
+ public LocaleQualifier(@NonNull String language) {
+ assert language.length() == 2 || language.length() == 3;
+ mLanguage = language;
+ mFull = language;
+ }
+
+ public LocaleQualifier(@Nullable String full, @NonNull String language,
+ @Nullable String region, @Nullable String script) {
+ if (full == null) {
+ if (region != null && region.length() == 3 || script != null) {
+ StringBuilder sb = new StringBuilder(BCP_47_PREFIX);
+ sb.append(language);
+ if (region != null) {
+ sb.append('+');
+ sb.append(region);
+ }
+ if (script != null) {
+ sb.append('+');
+ sb.append(script);
+ }
+ full = sb.toString();
+ } else if (region != null) {
+ full = language + "-r" + region;
+ } else {
+ full = language;
+ }
+ }
+ mFull = full;
+ mLanguage = language;
+ mRegion = region;
+ mScript = script;
+ }
+
+ public static boolean isRegionSegment(@NonNull String segment) {
+ return (segment.startsWith("r") || segment.startsWith("R")) && segment.length() == 3
+ && Character.isLetter(segment.charAt(0)) && Character.isLetter(segment.charAt(1));
+ }
+
+ /**
+ * Checks whether a string is a valid 2 letter language code in ISO 639-1 style.
+ * @param str the string to check
+ * @return is it valid?
+ */
+ private static boolean isValidAlpha2Code(@NonNull String str) {
+ return str.length() == 2 && CharMatcher.JAVA_LETTER.matchesAllOf(str);
+ }
+
+ /**
+ * Checks whether a string is a valid 3 letter language code in ISO 639-2 / ISO 639-5 style.
+ * @param str the string to check
+ * @return is it valid?
+ */
+ private static boolean isValidAlpha3Code(@NonNull String str) {
+ return str.length() == 3 && CharMatcher.JAVA_LETTER.matchesAllOf(str);
+ }
+
+ /**
+ * Checks whether a string is a valid 3 digit in the UN M.49 style.
+ * @param str the string to check
+ * @return is it valid?
+ */
+ private static boolean isValidM49Code(@NonNull String str) {
+ return str.length() == 3 && Character.isDigit(str.charAt(0))
+ && Character.isDigit(str.charAt(1))&& Character.isDigit(str.charAt(2));
+ }
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link LocaleQualifier} object or <code>null</code>
+ */
+ @Nullable
+ public static LocaleQualifier getQualifier(@NonNull String segment) {
+ /*
+ * Compute lower case version of the segment.
+ */
+ String segmentLc = segment.toLowerCase(Locale.US);
+
+ /*
+ * Special case: "car" is a valid 3 letter language code (Carib language), but
+ * it conflicts with the (much older) UI mode constant for
+ * car dock mode, so this specific language string should not be recognized
+ * as a 3 letter language string; it should match car dock mode instead.
+ */
+ if (CAR_DOCK_MODE.equals(segment.toLowerCase(Locale.US))) {
+ return null;
+ }
+
+ /*
+ * If segment starts with "b+" then it is a BCP-47 locale.
+ */
+ if (segment.startsWith(BCP_47_PREFIX)) {
+ return parseBcp47(segment);
+ }
+
+ String[] components = segment.split("-");
+ if (components.length > 2) {
+ /*
+ * More than 2 components is not supported. If complex stuff is needed, BCP-47 should
+ * be used instead.
+ */
+ return null;
+ }
+
+ /*
+ * First component: language. Has to be lower case, either 2 character ISO 639-1
+ * or three character ISO 639-2 / ISO 639-5, e.g., en, pt or eng, por...
+ */
+ String language = components[0].toLowerCase(Locale.US);
+ if (!isValidAlpha2Code(language) && !isValidAlpha3Code(language)) {
+ return null;
+ }
+
+ /*
+ * Second component (optional): region. Can be any 2 letters from ISO 3166-1, or 3 digits
+ * from UN M.49. E.g., UK, PT, or 013, 151. The second component must be prefixed with
+ * either lowercase or uppercase "r", so the code would be en-rUK or pt-RPT.
+ */
+ String region = null;
+ if (components.length > 1) {
+ if (components[1].length() < 1
+ || Character.toLowerCase(components[1].charAt(0)) != 'r') {
+ return null;
+ }
+
+ region = components[1].substring(1);
+ if (!isValidAlpha2Code(region) && !isValidM49Code(region)) {
+ return null;
+ }
+ }
+
+ if (region == null) {
+ return new LocaleQualifier(language, language, null, null);
+ } else {
+ return new LocaleQualifier(language + "-r" + region, language, region, null);
+ }
+ }
+
+ /** Given a BCP-47 string, normalizes the case to the recommended casing */
+ @NonNull
+ public static String normalizeCase(@NonNull String segment) {
+ /* According to the BCP-47 spec:
+ o [ISO639-1] recommends that language codes be written in lowercase
+ ('mn' Mongolian).
+
+ o [ISO15924] recommends that script codes use lowercase with the
+ initial letter capitalized ('Cyrl' Cyrillic).
+
+ o [ISO3166-1] recommends that country codes be capitalized ('MN'
+ Mongolia).
+
+
+ An implementation can reproduce this format without accessing the
+ registry as follows. All subtags, including extension and private
+ use subtags, use lowercase letters with two exceptions: two-letter
+ and four-letter subtags that neither appear at the start of the tag
+ nor occur after singletons. Such two-letter subtags are all
+ uppercase (as in the tags "en-CA-x-ca" or "sgn-BE-FR") and four-
+ letter subtags are titlecase (as in the tag "az-Latn-x-latn").
+ */
+ if (isNormalizedCase(segment)) {
+ return segment;
+ }
+
+ StringBuilder sb = new StringBuilder(segment.length());
+ if (segment.startsWith(BCP_47_PREFIX)) {
+ sb.append(BCP_47_PREFIX);
+ assert segment.startsWith(BCP_47_PREFIX);
+ int segmentBegin = BCP_47_PREFIX.length();
+ int segmentLength = segment.length();
+ int start = segmentBegin;
+
+ int lastLength = -1;
+ while (start < segmentLength) {
+ if (start != segmentBegin) {
+ sb.append('+');
+ }
+ int end = segment.indexOf('+', start);
+ if (end == -1) {
+ end = segmentLength;
+ }
+ int length = end - start;
+ if ((length != 2 && length != 4) || start == segmentBegin || lastLength == 1) {
+ for (int i = start; i < end; i++) {
+ sb.append(Character.toLowerCase(segment.charAt(i)));
+ }
+ } else if (length == 2) {
+ for (int i = start; i < end; i++) {
+ sb.append(Character.toUpperCase(segment.charAt(i)));
+ }
+ } else {
+ assert length == 4 : length;
+ sb.append(Character.toUpperCase(segment.charAt(start)));
+ for (int i = start + 1; i < end; i++) {
+ sb.append(Character.toLowerCase(segment.charAt(i)));
+ }
+ }
+
+ lastLength = length;
+ start = end + 1;
+ }
+ } else if (segment.length() == 6) {
+ // Language + region: ll-rRR
+ sb.append(Character.toLowerCase(segment.charAt(0)));
+ sb.append(Character.toLowerCase(segment.charAt(1)));
+ sb.append(segment.charAt(2)); // -
+ sb.append(Character.toLowerCase(segment.charAt(3))); // r
+ sb.append(Character.toUpperCase(segment.charAt(4)));
+ sb.append(Character.toUpperCase(segment.charAt(5)));
+ } else if (segment.length() == 7) {
+ // Language + region: lll-rRR
+ sb.append(Character.toLowerCase(segment.charAt(0)));
+ sb.append(Character.toLowerCase(segment.charAt(1)));
+ sb.append(Character.toLowerCase(segment.charAt(2)));
+ sb.append(segment.charAt(3)); // -
+ sb.append(Character.toLowerCase(segment.charAt(4))); // r
+ sb.append(Character.toUpperCase(segment.charAt(5)));
+ sb.append(Character.toUpperCase(segment.charAt(6)));
+ } else {
+ sb.append(segment.toLowerCase(Locale.US));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Given a BCP-47 string, determines whether the string is already
+ * capitalized correctly (where "correct" means for readability; all strings
+ * should be compared case insensitively)
+ */
+ @VisibleForTesting
+ static boolean isNormalizedCase(@NonNull String segment) {
+ if (segment.startsWith(BCP_47_PREFIX)) {
+ assert segment.startsWith(BCP_47_PREFIX);
+ int segmentBegin = BCP_47_PREFIX.length();
+ int segmentLength = segment.length();
+ int start = segmentBegin;
+
+ int lastLength = -1;
+ while (start < segmentLength) {
+ int end = segment.indexOf('+', start);
+ if (end == -1) {
+ end = segmentLength;
+ }
+ int length = end - start;
+ if ((length != 2 && length != 4) || start == segmentBegin || lastLength == 1) {
+ if (isNotLowerCase(segment, start, end)) {
+ return false;
+ }
+ } else if (length == 2) {
+ if (isNotUpperCase(segment, start, end)) {
+ return false;
+ }
+ } else {
+ assert length == 4 : length;
+ if (isNotUpperCase(segment, start, start + 1)) {
+ return false;
+ }
+ if (isNotLowerCase(segment, start + 1, end)) {
+ return false;
+ }
+ }
+
+ lastLength = length;
+ start = end + 1;
+ }
+
+ return true;
+ } else if (segment.length() == 2) {
+ // Just a language: ll
+ return Character.isLowerCase(segment.charAt(0))
+ && Character.isLowerCase(segment.charAt(1));
+ } else if (segment.length() == 3) {
+ // Just a language: lll
+ return Character.isLowerCase(segment.charAt(0))
+ && Character.isLowerCase(segment.charAt(1))
+ && Character.isLowerCase(segment.charAt(2));
+ } else if (segment.length() == 6) {
+ // Language + region: ll-rRR
+ return Character.isLowerCase(segment.charAt(0))
+ && Character.isLowerCase(segment.charAt(1))
+ && Character.isLowerCase(segment.charAt(3))
+ && Character.isUpperCase(segment.charAt(4))
+ && Character.isUpperCase(segment.charAt(5));
+ } else if (segment.length() == 7) {
+ // Language + region: lll-rRR
+ return Character.isLowerCase(segment.charAt(0))
+ && Character.isLowerCase(segment.charAt(1))
+ && Character.isLowerCase(segment.charAt(2))
+ && Character.isLowerCase(segment.charAt(4))
+ && Character.isUpperCase(segment.charAt(5))
+ && Character.isUpperCase(segment.charAt(6));
+ }
+
+ return true;
+ }
+
+ private static boolean isNotLowerCase(@NonNull String segment, int start, int end) {
+ for (int i = start; i < end; i++) {
+ if (Character.isUpperCase(segment.charAt(i))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isNotUpperCase(@NonNull String segment, int start, int end) {
+ for (int i = start; i < end; i++) {
+ if (Character.isLowerCase(segment.charAt(i))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @NonNull
+ public String getValue() {
+ return mFull;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ // This was added in Lollipop, but you can for example write b+en+US and aapt handles it
+ // compatibly so we don't want to normalize this in normalize() to append -v21 etc
+ return 1;
+ }
+
+ @Override
+ public boolean isValid() {
+ //noinspection StringEquality
+ return mFull != FAKE_VALUE;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ //noinspection StringEquality
+ return mFull == FAKE_VALUE;
+ }
+
+ public boolean hasLanguage() {
+ return !FAKE_VALUE.equals(mLanguage);
+ }
+
+ public boolean hasRegion() {
+ return mRegion != null && !FAKE_VALUE.equals(mRegion);
+ }
+
+ @Override
+ public boolean checkAndSet(@NonNull String value, @NonNull FolderConfiguration config) {
+ LocaleQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setLocaleQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Used only when constructing the qualifier, don't use after it's been assigned to a
+ * {@link FolderConfiguration}.
+ */
+ void setRegionSegment(@NonNull String segment) {
+ assert segment.length() == 3 : segment;
+ mRegion = new String(new char[] {
+ Character.toUpperCase(segment.charAt(1)),
+ Character.toUpperCase(segment.charAt(2))
+ });
+ mFull = mLanguage + "-r" + mRegion;
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ LocaleQualifier qualifier = (LocaleQualifier) o;
+
+ if (!mFull.equals(qualifier.mFull)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mFull.hashCode();
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return mFull;
+ }
+
+ /** BCP 47 tag or "language,region", or language */
+ @Override
+ public String getShortDisplayValue() {
+ if (mFull.startsWith(BCP_47_PREFIX)) {
+ return mFull;
+ } else if (mRegion != null) {
+ return mLanguage + ',' + mRegion;
+ } else {
+ return mLanguage;
+ }
+ }
+
+ /** Tag: language, or language-region, or BCP-47 tag */
+ public String getTag() {
+ if (mFull.startsWith(BCP_47_PREFIX)) {
+ return mFull.substring(BCP_47_PREFIX.length()).replace('+','-');
+ } else if (mRegion != null) {
+ return mLanguage + '-' + mRegion;
+ } else {
+ return mLanguage;
+ }
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mFull.startsWith(BCP_47_PREFIX)) {
+ return String.format("Locale %1$s", mFull);
+ } else if (mRegion != null) {
+ return String.format("Locale %1$s_%2$s", mLanguage, mRegion);
+ } else //noinspection StringEquality
+ if (mFull != FAKE_VALUE) {
+ return String.format("Locale %1$s", mLanguage);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * Parse an Android BCP-47 string (which differs from BCP-47 in that
+ * it has the prefix "b+" and the separator character has been changed from
+ * - to +.
+ *
+ * @param qualifier the folder name to parse
+ * @return a {@linkplain LocaleQualifier} holding the language, region and script
+ * or null if not a valid Android BCP 47 tag
+ */
+ @Nullable
+ public static LocaleQualifier parseBcp47(@NonNull String qualifier) {
+ if (qualifier.startsWith(BCP_47_PREFIX)) {
+ qualifier = normalizeCase(qualifier);
+ Iterator<String> iterator = Splitter.on('+').split(qualifier).iterator();
+ // Skip b+ prefix, already checked above
+ iterator.next();
+
+ if (iterator.hasNext()) {
+ String language = iterator.next();
+ String region = null;
+ String script = null;
+ if (language.length() >= 2 && language.length() <= 3) {
+ if (iterator.hasNext()) {
+ String next = iterator.next();
+ if (next.length() == 4) {
+ // Script specified; look for next
+ script = next;
+ if (iterator.hasNext()) {
+ next = iterator.next();
+ }
+ } else if (next.length() >= 5) {
+ // Past region: specifying a variant
+ return new LocaleQualifier(qualifier, language, null, null);
+ }
+ if (next.length() >= 2 && next.length() <= 3) {
+ region = next;
+ }
+ }
+ if (script == null && (region == null || region.length() == 2)
+ && !iterator.hasNext()) {
+ // Switch from BCP 47 syntax to plain
+ qualifier = language.toLowerCase(Locale.US);
+ if (region != null) {
+ qualifier = qualifier + "-r" + region.toUpperCase(Locale.US);
+ }
+ }
+ return new LocaleQualifier(qualifier, language, region, script);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public String getLanguage() {
+ return mLanguage;
+ }
+
+ @Nullable
+ public String getRegion() {
+ return mRegion;
+ }
+
+ @Nullable
+ public String getScript() {
+ return mScript;
+ }
+
+ @NonNull
+ public String getFull() {
+ return mFull;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof LocaleQualifier) {
+ LocaleQualifier other = (LocaleQualifier)qualifier;
+ if (!mLanguage.equals(other.mLanguage)) {
+ return false;
+ }
+
+ if (mRegion != null && other.mRegion != null && !mRegion.equals(other.mRegion)) {
+ return false;
+ }
+
+ if (mScript != null && other.mScript != null && !mScript.equals(other.mScript)) {
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationStateQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationStateQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationStateQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/NavigationStateQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NightModeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NightModeQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/NightModeQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/NightModeQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ResourceQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ResourceQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ResourceQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/ResourceQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRoundQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRoundQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRoundQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenRoundQualifier.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
new file mode 100644
index 0000000..eef4a0a
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2009 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.ide.common.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.ScreenSize;
+
+/**
+ * Resource Qualifier for Screen Size. Size can be "small", "normal", "large" and "x-large"
+ */
+public class ScreenSizeQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Screen Size";
+
+ private ScreenSize mValue = null;
+
+
+ public ScreenSizeQualifier() {
+ }
+
+ public ScreenSizeQualifier(ScreenSize value) {
+ mValue = value;
+ }
+
+ public ScreenSize getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Size";
+ }
+
+ @Override
+ public int since() {
+ return 4;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ ScreenSize size = ScreenSize.getEnum(value);
+ if (size != null) {
+ ScreenSizeQualifier qualifier = new ScreenSizeQualifier(size);
+ config.setScreenSizeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ // This is a match only if the screen size is smaller than the qualifier's screen size.
+ if (qualifier instanceof ScreenSizeQualifier) {
+ int qualifierIndex = ScreenSize.getIndex(((ScreenSizeQualifier) qualifier).mValue);
+ int index = ScreenSize.getIndex(mValue);
+ if (index <= qualifierIndex) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ // Small is better at matching Small than null (i.e. no qualifier)
+ // However null is better than Small at matching any other screen size
+ return getValue() != ScreenSize.SMALL || ((ScreenSizeQualifier)reference).getValue() == ScreenSize.SMALL;
+ }
+
+ ScreenSizeQualifier compareQ = (ScreenSizeQualifier) compareTo;
+ int thisIndex = ScreenSize.getIndex(mValue);
+ int compareIndex = ScreenSize.getIndex(compareQ.mValue);
+
+ // Return true if this size is larger than reference size. Since isMatchFor() is called
+ // before, it is guaranteed that the size will not be larger than the reference.
+ return thisIndex > compareIndex;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/TouchScreenQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/TouchScreenQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/TouchScreenQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/TouchScreenQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/UiModeQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/VersionQualifier.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/VersionQualifier.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/resources/configuration/VersionQualifier.java
rename to sdk-common/src/main/java/com/android/ide/common/resources/configuration/VersionQualifier.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/sdk/LoadStatus.java b/sdk-common/src/main/java/com/android/ide/common/sdk/LoadStatus.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/sdk/LoadStatus.java
rename to sdk-common/src/main/java/com/android/ide/common/sdk/LoadStatus.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/signing/CertificateInfo.java b/sdk-common/src/main/java/com/android/ide/common/signing/CertificateInfo.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/signing/CertificateInfo.java
rename to sdk-common/src/main/java/com/android/ide/common/signing/CertificateInfo.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java b/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java
new file mode 100644
index 0000000..3e4bc2d
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/signing/KeystoreHelper.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.signing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.io.Closeables;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A Helper to create and read keystore/keys.
+ */
+public final class KeystoreHelper {
+
+ /**
+ * Certificate CN value. This is a hard-coded value for the debug key.
+ * Android Market checks against this value in order to refuse applications signed with
+ * debug keys.
+ */
+ private static final String CERTIFICATE_DESC = "CN=Android Debug,O=Android,C=US";
+
+ /**
+ * Generated certificate validity.
+ */
+ private static final int DEFAULT_VALIDITY_YEARS = 30;
+
+
+ /**
+ * Returns the location of the default debug keystore.
+ * @return The location of the default debug keystore
+ * @throws AndroidLocationException if the location cannot be computed
+ */
+ @NonNull
+ public static String defaultDebugKeystoreLocation() throws AndroidLocationException {
+ //this is guaranteed to either return a non null value (terminated with a platform
+ // specific separator), or throw.
+ String folder = AndroidLocation.getFolder();
+ return folder + "debug.keystore";
+ }
+
+ /**
+ * Creates a new debug store with the location, keyalias, and passwords specified in the
+ * config.
+ * @param storeType an optional type of keystore; if {@code null} the default
+ * @param storeFile the file where the store should be created
+ * @param storePassword a password for the key store
+ * @param keyPassword a password for the key
+ * @param keyAlias the alias under which the key is stored in the store
+ * @param logger (not used, kept for backwards compatibility)
+ * @throws KeytoolException
+ */
+ public static boolean createDebugStore(@Nullable String storeType, @NonNull File storeFile,
+ @NonNull String storePassword, @NonNull String keyPassword, @NonNull String keyAlias,
+ @NonNull ILogger logger) throws KeytoolException {
+
+ return createNewStore(storeType, storeFile, storePassword, keyPassword, keyAlias,
+ CERTIFICATE_DESC, DEFAULT_VALIDITY_YEARS);
+ }
+
+ /**
+ * Creates a new store with a self-signed certificate. The certificate will be valid starting
+ * from the current date up to the number of years provided.
+ * @param storeType an optional type of keystore; if {@code null} the default
+ * @param storeFile the file where the store should be created
+ * @param storePassword a password for the key store
+ * @param keyPassword a password for the key
+ * @param keyAlias the alias under which the key is stored in the store
+ * @param dn the distinguished name of the owner and issuer of the certificate
+ * @param validityYears number of years the certificate should be valid
+ * @throws KeytoolException failed to generate the self-signed certificate or the store
+ */
+ private static boolean createNewStore(
+ @Nullable String storeType,
+ @NonNull File storeFile,
+ @NonNull String storePassword,
+ @NonNull String keyPassword,
+ @NonNull String keyAlias,
+ @NonNull String dn,
+ int validityYears)
+ throws KeytoolException {
+ Preconditions.checkArgument(validityYears > 0, "validityYears (%s) <= 0", validityYears);
+
+ String useStoreType = storeType;
+ if (useStoreType == null) {
+ useStoreType = KeyStore.getDefaultType();
+ Verify.verifyNotNull(useStoreType);
+ }
+
+ try {
+ KeyStore ks = KeyStore.getInstance(useStoreType);
+ ks.load(null, null);
+
+ Pair<PrivateKey, X509Certificate> generated = generateKeyAndCertificate("RSA",
+ "SHA1withRSA", validityYears, dn);
+ ks.setKeyEntry(keyAlias, generated.getFirst(), keyPassword.toCharArray(),
+ new Certificate[]{generated.getSecond()});
+ FileOutputStream fos = new FileOutputStream(storeFile);
+ boolean threw = true;
+ try {
+ ks.store(fos, storePassword.toCharArray());
+ threw = false;
+ } finally {
+ Closeables.close(fos, threw);
+ }
+ } catch (KeytoolException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new KeytoolException("Failed to create keystore.", e);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the CertificateInfo for the given signing configuration.
+ * @return the certificate info if it could be loaded
+ * @throws KeytoolException If the password is wrong
+ * @throws FileNotFoundException If the store file cannot be found
+ */
+ @NonNull
+ public static CertificateInfo getCertificateInfo(@Nullable String storeType,
+ @NonNull File storeFile, @NonNull String storePassword, @NonNull String keyPassword,
+ @NonNull String keyAlias) throws KeytoolException, FileNotFoundException {
+
+ try {
+ KeyStore keyStore = KeyStore.getInstance(storeType != null ?
+ storeType : KeyStore.getDefaultType());
+
+ FileInputStream fis = new FileInputStream(storeFile);
+ keyStore.load(fis, storePassword.toCharArray());
+ fis.close();
+
+ char[] keyPasswordArray = keyPassword.toCharArray();
+ PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
+ keyAlias, new KeyStore.PasswordProtection(keyPasswordArray));
+
+ if (entry == null) {
+ throw new KeytoolException(
+ String.format(
+ "No key with alias '%1$s' found in keystore %2$s",
+ keyAlias,
+ storeFile.getAbsolutePath()));
+ }
+
+ return new CertificateInfo(
+ entry.getPrivateKey(),
+ (X509Certificate) entry.getCertificate());
+ } catch (FileNotFoundException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new KeytoolException(
+ String.format("Failed to read key %1$s from store \"%2$s\": %3$s",
+ keyAlias, storeFile, e.getMessage()),
+ e);
+ }
+ }
+
+ /**
+ * Generates a key and self-signed certificate pair.
+ * @param asymmetric the asymmetric encryption algorithm (<em>e.g.,</em> {@code RSA})
+ * @param sign the signature algorithm (<em>e.g.,</em> {@code SHA1withRSA})
+ * @param validityYears number of years the certificate should be valid, must be greater than
+ * zero
+ * @param dn the distinguished name of the issuer and owner of the certificate
+ * @return a pair with the private key and the corresponding certificate
+ * @throws KeytoolException failed to generate the pair
+ */
+ private static Pair<PrivateKey, X509Certificate> generateKeyAndCertificate(
+ @NonNull String asymmetric, @NonNull String sign, int validityYears,
+ @NonNull String dn) throws KeytoolException {
+ Preconditions.checkArgument(validityYears > 0, "validityYears <= 0");
+
+ KeyPair keyPair;
+ try {
+ keyPair = KeyPairGenerator.getInstance(asymmetric).generateKeyPair();
+ } catch (NoSuchAlgorithmException e) {
+ throw new KeytoolException("Failed to generate key and certificate pair for "
+ + "algorithm '" + asymmetric + "'.", e);
+ }
+
+ Date notBefore = new Date(System.currentTimeMillis());
+ Date notAfter = new Date(System.currentTimeMillis() + validityYears * 365L * 24 * 60 * 60
+ * 1000);
+
+ X500Name issuer = new X500Name(new X500Principal(dn).getName());
+
+ SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(
+ keyPair.getPublic().getEncoded());
+ X509v1CertificateBuilder builder = new X509v1CertificateBuilder(issuer, BigInteger.ONE,
+ notBefore, notAfter, issuer, publicKeyInfo);
+
+ ContentSigner signer;
+ try {
+ signer = new JcaContentSignerBuilder(sign).setProvider(
+ new BouncyCastleProvider()).build(keyPair.getPrivate());
+ } catch (OperatorCreationException e) {
+ throw new KeytoolException("Failed to build content signer with signature algorithm '"
+ + sign + "'.", e);
+ }
+
+ X509CertificateHolder holder = builder.build(signer);
+
+ JcaX509CertificateConverter converter = new JcaX509CertificateConverter()
+ .setProvider(new BouncyCastleProvider());
+
+ X509Certificate certificate;
+ try {
+ certificate = converter.getCertificate(holder);
+ } catch (CertificateException e) {
+ throw new KeytoolException("Failed to obtain the self-signed certificate.", e);
+ }
+
+ return Pair.of(keyPair.getPrivate(), certificate);
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/signing/KeytoolException.java b/sdk-common/src/main/java/com/android/ide/common/signing/KeytoolException.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/signing/KeytoolException.java
rename to sdk-common/src/main/java/com/android/ide/common/signing/KeytoolException.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/util/AssetUtil.java b/sdk-common/src/main/java/com/android/ide/common/util/AssetUtil.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/util/AssetUtil.java
rename to sdk-common/src/main/java/com/android/ide/common/util/AssetUtil.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/util/ReferenceHolder.java b/sdk-common/src/main/java/com/android/ide/common/util/ReferenceHolder.java
new file mode 100644
index 0000000..9907ecd
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/util/ReferenceHolder.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ide.common.util;
+
+/**
+ * A simple holder for a reference.
+ *
+ * This is mutable (unlike Optional), and more light weight than Atomic*, however this is not
+ * thread-safe at all.
+ */
+public class ReferenceHolder<T> {
+
+ private T value;
+
+ public static <T> ReferenceHolder<T> of(T value) {
+ return new ReferenceHolder<T>(value);
+ }
+
+ public static <T> ReferenceHolder<T> empty() {
+ return new ReferenceHolder<T>(null);
+ }
+
+ private ReferenceHolder(T value) {
+ this.value = value;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public T setValue(T value) {
+ this.value = value;
+ return value;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/util/UrlClassLoaderUtil.java b/sdk-common/src/main/java/com/android/ide/common/util/UrlClassLoaderUtil.java
new file mode 100644
index 0000000..996d662
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/util/UrlClassLoaderUtil.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ide.common.util;
+
+import java.io.Closeable;
+import java.net.URLClassLoader;
+
+public final class UrlClassLoaderUtil {
+ /**
+ * Calls classLoader.close() on Java 7 and above. A no-op on Java 6. Fails silently.
+ *
+ * Work around on java 7 for http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5041014 :
+ * URLClassLoader, on Windows, locks the .jar file forever.
+ */
+ public static void attemptToClose(URLClassLoader classLoader) {
+ if (classLoader instanceof Closeable) {
+ try {
+ ((Closeable) classLoader).close();
+ } catch (Throwable e) {
+ // Ignore - unable to close.
+ }
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/EllipseSolver.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/EllipseSolver.java
new file mode 100644
index 0000000..6b1ba0b
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/EllipseSolver.java
@@ -0,0 +1,287 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+class EllipseSolver {
+ private static Logger logger = Logger.getLogger(EllipseSolver.class.getSimpleName());
+
+ // Final results:
+ private float mMajorAxis = 0;
+
+ private float mMinorAxis = 0;
+
+ private float mRotationDegree = 0;
+
+ private boolean mDirectionChanged;
+
+ // Intermediate results:
+ private Point2D.Float mMajorAxisPoint;
+
+ private Point2D.Float mMiddlePoint;
+
+ private Point2D.Float mMinorAxisPoint;
+
+ private Point2D.Float mDstMajorAxisPoint;
+
+ private Point2D.Float mDstMiddlePoint;
+
+ private Point2D.Float mDstMinorAxisPoint;
+
+ /**
+ * Rotate the Point2D by radians
+ *
+ * @return the rotated Point2D
+ */
+ private Point2D.Float rotatePoint2D(Point2D.Float inPoint, float radians) {
+ Point2D.Float result = new Point2D.Float();
+ float cosine = (float) Math.cos(radians);
+ float sine = (float) Math.sin(radians);
+ result.x = inPoint.x * cosine - inPoint.y * sine;
+ result.y = inPoint.x * sine + inPoint.y * cosine;
+ return result;
+ }
+
+ /**
+ * Construct the solver with all necessary parameters, and all the output value will be
+ * ready after this constructor is called.
+ *
+ * Note that all the x y values are in absolute coordinates, such that we can apply the
+ * transform directly.
+ */
+ public EllipseSolver(AffineTransform totalTransform,
+ float currentX, float currentY, float rx, float ry, float xAxisRotation,
+ float largeArcFlag, float sweepFlag, float destX, float destY) {
+ boolean largeArc = largeArcFlag != 0;
+ boolean sweep = sweepFlag != 0;
+
+ // Compute the cx and cy first.
+ Point2D.Float originalCenter = computeOriginalCenter(currentX, currentY, rx, ry,
+ xAxisRotation, largeArc, sweep, destX, destY);
+
+ // Compute 3 points from original ellipse
+ computeControlPoints(rx, ry, xAxisRotation, originalCenter.x, originalCenter.y);
+
+ // Transform 3 points and center point into destination.
+ mDstMiddlePoint = (Point2D.Float) totalTransform.transform(mMiddlePoint, null);
+ mDstMajorAxisPoint = (Point2D.Float) totalTransform.transform(mMajorAxisPoint, null);
+ mDstMinorAxisPoint = (Point2D.Float) totalTransform.transform(mMinorAxisPoint, null);
+ Point2D dstCenter = totalTransform.transform(originalCenter, null);
+ float dstCenterX = (float) dstCenter.getX();
+ float dstCenterY = (float) dstCenter.getY();
+
+ // Compute the relative 3 points:
+ float relativeDstMiddleX = mDstMiddlePoint.x - dstCenterX;
+ float relativeDstMiddleY = mDstMiddlePoint.y - dstCenterY;
+ float relativeDstMajorAxisPointX = mDstMajorAxisPoint.x - dstCenterX;
+ float relativeDstMajorAxisPointY = mDstMajorAxisPoint.y - dstCenterY;
+ float relativeDstMinorAxisPointX = mDstMinorAxisPoint.x - dstCenterX;
+ float relativeDstMinorAxisPointY = mDstMinorAxisPoint.y - dstCenterY;
+
+ // Check if the direction has change!
+ mDirectionChanged = computeDirectionChange(mMiddlePoint, mMajorAxisPoint,
+ mMinorAxisPoint,
+ mDstMiddlePoint, mDstMajorAxisPoint, mDstMinorAxisPoint);
+
+ // From 3 dest points, recompute the a, b and theta.
+ if (computeABThetaFromControlPoints(relativeDstMiddleX, relativeDstMiddleY,
+ relativeDstMajorAxisPointX,
+ relativeDstMajorAxisPointY, relativeDstMinorAxisPointX,
+ relativeDstMinorAxisPointY)) {
+ logger.log(Level.WARNING,
+ "Early return in the ellipse transformation computation!");
+ }
+ }
+
+ /**
+ * After a random transformation, the controls points may change its direction, left handed <->
+ * right handed. In this case, we better flip the flag for the ArcTo command.
+ *
+ * Here, we use the cross product to figure out the direction of the 3 control points for the
+ * src and dst ellipse.
+ */
+ private boolean computeDirectionChange(final Point2D.Float middlePoint,
+ final Point2D.Float majorAxisPoint, final Point2D.Float minorAxisPoint,
+ final Point2D.Float dstMiddlePoint, final Point2D.Float dstMajorAxisPoint,
+ final Point2D.Float dstMinorAxisPoint) {
+ // Compute both Cross Product, then compare the sign.
+ float srcCrossProduct = getCrossProduct(middlePoint, majorAxisPoint, minorAxisPoint);
+ float dstCrossProduct = getCrossProduct(dstMiddlePoint, dstMajorAxisPoint,
+ dstMinorAxisPoint);
+
+ return srcCrossProduct * dstCrossProduct < 0 ? true : false;
+ }
+
+ private float getCrossProduct(final Point2D.Float middlePoint,
+ final Point2D.Float majorAxisPoint, final Point2D.Float minorAxisPoint) {
+ float majorMinusMiddleX = majorAxisPoint.x - middlePoint.x;
+ float majorMinusMiddleY = majorAxisPoint.y - middlePoint.y;
+
+ float minorMinusMiddleX = minorAxisPoint.x - middlePoint.x;
+ float minorMinusMiddleY = minorAxisPoint.y - middlePoint.y;
+
+ return (majorMinusMiddleX * minorMinusMiddleY) - (majorMinusMiddleY
+ * minorMinusMiddleX);
+ }
+
+ /**
+ * @return true if there is something error going on, either due to the ellipse is not setup
+ * correctly, or some computation error happens. This error is ignorable, but the
+ * output ellipse will not be correct.
+ */
+ private boolean computeABThetaFromControlPoints(float relMiddleX, float relMiddleY,
+ float relativeMajorAxisPointX, float relativeMajorAxisPointY,
+ float relativeMinorAxisPointX, float relativeMinorAxisPointY) {
+ float m11 = relMiddleX * relMiddleX;
+ float m12 = relMiddleX * relMiddleY;
+ float m13 = relMiddleY * relMiddleY;
+
+ float m21 = relativeMajorAxisPointX * relativeMajorAxisPointX;
+ float m22 = relativeMajorAxisPointX * relativeMajorAxisPointY;
+ float m23 = relativeMajorAxisPointY * relativeMajorAxisPointY;
+
+ float m31 = relativeMinorAxisPointX * relativeMinorAxisPointX;
+ float m32 = relativeMinorAxisPointX * relativeMinorAxisPointY;
+ float m33 = relativeMinorAxisPointY * relativeMinorAxisPointY;
+
+ float det = -(m13 * m22 * m31 - m12 * m23 * m31 - m13 * m21 * m32
+ + m11 * m23 * m32 + m12 * m21 * m33 - m11 * m22 * m33);
+
+ if (det == 0) {
+ return true;
+ }
+ float A = (-m13 * m22 + m12 * m23 + m13 * m32 - m23 * m32 - m12 * m33 + m22 * m33)
+ / det;
+ float B = (m13 * m21 - m11 * m23 - m13 * m31 + m23 * m31 + m11 * m33 - m21 * m33) / det;
+ float C = (m12 * m21 - m11 * m22 - m12 * m31 + m22 * m31 + m11 * m32 - m21 * m32)
+ / (-det);
+
+ // Now we know A = cos(t)^2 / a^2 + sin(t)^2 / b^2
+ // B = -2 cos(t) sin(t) (1/a^2 - 1/b^2)
+ // C = sin(t)^2 / a^2 + cos(t)^2 / b^2
+
+ // Solve it , we got
+ // 2*t = arctan ( B / (A - C);
+ if ((A - C) == 0) {
+ // We know that a == b now.
+ mMinorAxis = (float) Math.hypot(relativeMajorAxisPointX, relativeMajorAxisPointY);
+ mMajorAxis = mMinorAxis;
+ mRotationDegree = 0;
+ return false;
+ }
+ float doubleThetaInRadians = (float) Math.atan(B / (A - C));
+ float thetaInRadians = doubleThetaInRadians / 2;
+ if (Math.sin(doubleThetaInRadians) == 0) {
+ mMinorAxis = (float) Math.sqrt(1 / C);
+ mMajorAxis = (float) Math.sqrt(1 / A);
+ mRotationDegree = 0;
+ // This is a valid answer, so return false;
+ return false;
+ }
+ float bSqInv = (A + C + B / (float) Math.sin(doubleThetaInRadians)) / 2;
+ float aSqInv = A + C - bSqInv;
+
+ if (bSqInv == 0 || aSqInv == 0) {
+ return true;
+ }
+ mMinorAxis = (float) Math.sqrt(1 / bSqInv);
+ mMajorAxis = (float) Math.sqrt(1 / aSqInv);
+
+ mRotationDegree = (float) Math.toDegrees(Math.PI / 2 + thetaInRadians);
+ return false;
+ }
+
+ private void computeControlPoints(float a, float b, float rot, float cx, float cy) {
+ mMajorAxisPoint = new Point2D.Float(a, 0);
+ mMinorAxisPoint = new Point2D.Float(0, b);
+
+ mMajorAxisPoint = rotatePoint2D(mMajorAxisPoint, rot);
+ mMinorAxisPoint = rotatePoint2D(mMinorAxisPoint, rot);
+
+ mMajorAxisPoint.x += cx;
+ mMajorAxisPoint.y += cy;
+
+ mMinorAxisPoint.x += cx;
+ mMinorAxisPoint.y += cy;
+
+ float middleDegree = 45; // This can be anything b/t 0 to 90.
+ float middleRadians = (float) Math.toRadians(middleDegree);
+ float middleR = a * b / (float) Math.hypot(b * (float) Math.cos(middleRadians),
+ a * (float) Math.sin(middleRadians));
+
+ mMiddlePoint = new Point2D.Float(middleR * (float) Math.cos(middleRadians),
+ middleR * (float) Math.sin(middleRadians));
+ mMiddlePoint = rotatePoint2D(mMiddlePoint, rot);
+ mMiddlePoint.x += cx;
+ mMiddlePoint.y += cy;
+ }
+
+ private Point2D.Float computeOriginalCenter(float x1, float y1, float rx, float ry,
+ float phi, boolean largeArc, boolean sweep, float x2, float y2) {
+ Point2D.Float result = new Point2D.Float();
+ float cosPhi = (float) Math.cos(phi);
+ float sinPhi = (float) Math.sin(phi);
+ float xDelta = (x1 - x2) / 2;
+ float yDelta = (y1 - y2) / 2;
+ float tempX1 = cosPhi * xDelta + sinPhi * yDelta;
+ float tempY1 = -sinPhi * xDelta + cosPhi * yDelta;
+
+ float rxSq = rx * rx;
+ float rySq = ry * ry;
+ float tempX1Sq = tempX1 * tempX1;
+ float tempY1Sq = tempY1 * tempY1;
+
+ float tempCenterFactor = rxSq * rySq - rxSq * tempY1Sq - rySq * tempX1Sq;
+ tempCenterFactor /= rxSq * tempY1Sq + rySq * tempX1Sq;
+ tempCenterFactor = (float) Math.sqrt(tempCenterFactor);
+ if (largeArc == sweep) {
+ tempCenterFactor = -tempCenterFactor;
+ }
+ float tempCx = tempCenterFactor * rx * tempY1 / ry;
+ float tempCy = -tempCenterFactor * ry * tempX1 / rx;
+
+ float xCenter = (x1 + x2) / 2;
+ float yCenter = (y1 + y2) / 2;
+
+ float cx = cosPhi * tempCx - sinPhi * tempCy + xCenter;
+ float cy = sinPhi * tempCx + cosPhi * tempCy + yCenter;
+
+ result.x = cx;
+ result.y = cy;
+ return result;
+ }
+
+ public float getMajorAxis() {
+ return mMajorAxis;
+ }
+
+ public float getMinorAxis() {
+ return mMinorAxis;
+ }
+
+ public float getRotationDegree() {
+ return mRotationDegree;
+ }
+
+ public boolean getDirectionChanged() {
+ return mDirectionChanged;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathBuilder.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathBuilder.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathBuilder.java
rename to sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathBuilder.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java
new file mode 100644
index 0000000..c5d7859
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java
@@ -0,0 +1,212 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility functions for parsing path information.
+ * The implementation details should be the same as the PathParser in Android framework.
+ */
+public class PathParser {
+ private static class ExtractFloatResult {
+ // We need to return the position of the next separator and whether the
+ // next float starts with a '-' or a '.'.
+ int mEndPosition;
+ boolean mEndWithNegOrDot;
+ }
+
+ /**
+ * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
+ * end (exclusive). The original order of elements is preserved.
+ * If {@code end} is greater than {@code original.length}, the result is padded
+ * with the value {@code 0.0f}.
+ *
+ * @param original the original array
+ * @param start the start index, inclusive
+ * @param end the end index, exclusive
+ * @return the new array
+ * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
+ * @throws IllegalArgumentException if {@code start > end}
+ * @throws NullPointerException if {@code original == null}
+ */
+ private static float[] copyOfRange(float[] original, int start, int end) {
+ if (start > end) {
+ throw new IllegalArgumentException();
+ }
+ int originalLength = original.length;
+ if (start < 0 || start > originalLength) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ int resultLength = end - start;
+ int copyLength = Math.min(resultLength, originalLength - start);
+ float[] result = new float[resultLength];
+ System.arraycopy(original, start, result, 0, copyLength);
+ return result;
+ }
+
+ /**
+ * Calculate the position of the next comma or space or negative sign
+ * @param s the string to search
+ * @param start the position to start searching
+ * @param result the result of the extraction, including the position of the
+ * the starting position of next number, whether it is ending with a '-'.
+ */
+ private static void extract(String s, int start, ExtractFloatResult result) {
+ // Now looking for ' ', ',', '.' or '-' from the start.
+ int currentIndex = start;
+ boolean foundSeparator = false;
+ result.mEndWithNegOrDot = false;
+ boolean secondDot = false;
+ boolean isExponential = false;
+ for (; currentIndex < s.length(); currentIndex++) {
+ boolean isPrevExponential = isExponential;
+ isExponential = false;
+ char currentChar = s.charAt(currentIndex);
+ switch (currentChar) {
+ case ' ':
+ case ',':
+ foundSeparator = true;
+ break;
+ case '-':
+ // The negative sign following a 'e' or 'E' is not a separator.
+ if (currentIndex != start && !isPrevExponential) {
+ foundSeparator = true;
+ result.mEndWithNegOrDot = true;
+ }
+ break;
+ case '.':
+ if (!secondDot) {
+ secondDot = true;
+ } else {
+ // This is the second dot, and it is considered as a separator.
+ foundSeparator = true;
+ result.mEndWithNegOrDot = true;
+ }
+ break;
+ case 'e':
+ case 'E':
+ isExponential = true;
+ break;
+ }
+ if (foundSeparator) {
+ break;
+ }
+ }
+ // When there is nothing found, then we put the end position to the end
+ // of the string.
+ result.mEndPosition = currentIndex;
+ }
+
+ /**
+ * parse the floats in the string this is an optimized version of parseFloat(s.split(",|\\s"));
+ *
+ * @param s the string containing a command and list of floats
+ * @return array of floats
+ */
+ private static float[] getFloats(String s) {
+ if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
+ return new float[0];
+ }
+ try {
+ float[] results = new float[s.length()];
+ int count = 0;
+ int startPosition = 1;
+ int endPosition = 0;
+
+ ExtractFloatResult result = new ExtractFloatResult();
+ int totalLength = s.length();
+
+ // The startPosition should always be the first character of the
+ // current number, and endPosition is the character after the current
+ // number.
+ while (startPosition < totalLength) {
+ extract(s, startPosition, result);
+ endPosition = result.mEndPosition;
+
+ if (startPosition < endPosition) {
+ results[count++] = Float.parseFloat(
+ s.substring(startPosition, endPosition));
+ }
+
+ if (result.mEndWithNegOrDot) {
+ // Keep the '-' or '.' sign with next number.
+ startPosition = endPosition;
+ } else {
+ startPosition = endPosition + 1;
+ }
+ }
+ return copyOfRange(results, 0, count);
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("error in parsing \"" + s + "\"", e);
+ }
+ }
+
+ private static void addNode(List<VdPath.Node> list, char cmd, float[] val) {
+ list.add(new VdPath.Node(cmd, val));
+ }
+
+ private static int nextStart(String s, int end) {
+ char c;
+
+ while (end < s.length()) {
+ c = s.charAt(end);
+ // Note that 'e' or 'E' are not valid path commands, but could be
+ // used for floating point numbers' scientific notation.
+ // Therefore, when searching for next command, we should ignore 'e'
+ // and 'E'.
+ if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+ && c != 'e' && c != 'E') {
+ return end;
+ }
+ end++;
+ }
+ return end;
+ }
+
+ public static VdPath.Node[] parsePath(String value) {
+ int start = 0;
+ int end = 1;
+ value = value.trim();
+ List<VdPath.Node> list = new ArrayList<VdPath.Node>();
+
+ while (end < value.length()) {
+ end = nextStart(value, end);
+ String s = value.substring(start, end);
+ float[] val = getFloats(s);
+ char currentCommand = s.charAt(0);
+
+ if (start == 0) {
+ // For the starting command, special handling:
+ // add M 0 0 if there is none.
+ // This is good for transformation.
+ if (currentCommand != 'M' && currentCommand != 'm'){
+ addNode(list, 'M', new float[2]);
+ }
+ }
+ addNode(list, currentCommand, val);
+
+ start = end;
+ end++;
+ }
+ if ((end - start) == 1 && start < value.length()) {
+
+ addNode(list, value.charAt(start), new float[0]);
+ }
+ return list.toArray(new VdPath.Node[list.size()]);
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/Svg2Vector.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/Svg2Vector.java
new file mode 100644
index 0000000..f1251cd
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/Svg2Vector.java
@@ -0,0 +1,689 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.awt.geom.AffineTransform;
+import java.io.*;
+import java.util.HashSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Converts SVG to VectorDrawable's XML
+ *
+ * There are 2 major functions:
+ * 1. parse(file)
+ * This include parse the .svg file and build an internal tree. The optimize this tree.
+ *
+ * 2. writeFile()
+ * This is traversing the whole tree, and write the group / path info into the XML.
+ */
+public class Svg2Vector {
+ private static Logger logger = Logger.getLogger(Svg2Vector.class.getSimpleName());
+
+ public static final String SVG_POLYGON = "polygon";
+ public static final String SVG_POLYLINE = "polyline";
+ public static final String SVG_RECT = "rect";
+ public static final String SVG_CIRCLE = "circle";
+ public static final String SVG_LINE = "line";
+ public static final String SVG_PATH = "path";
+ public static final String SVG_ELLIPSE = "ellipse";
+ public static final String SVG_GROUP = "g";
+ public static final String SVG_TRANSFORM = "transform";
+ public static final String SVG_STYLE = "style";
+ public static final String SVG_DISPLAY = "display";
+
+ public static final String SVG_D = "d";
+ public static final String SVG_STROKE_COLOR = "stroke";
+ public static final String SVG_STROKE_OPACITY = "stroke-opacity";
+ public static final String SVG_STROKE_LINEJOINE = "stroke-linejoin";
+ public static final String SVG_STROKE_LINECAP = "stroke-linecap";
+ public static final String SVG_STROKE_WIDTH = "stroke-width";
+ public static final String SVG_FILL_COLOR = "fill";
+ public static final String SVG_FILL_OPACITY = "fill-opacity";
+ public static final String SVG_OPACITY = "opacity";
+ public static final String SVG_CLIP = "clip";
+ public static final String SVG_POINTS = "points";
+
+ public static final ImmutableMap<String, String> presentationMap =
+ ImmutableMap.<String, String>builder()
+ .put(SVG_STROKE_COLOR, "android:strokeColor")
+ .put(SVG_STROKE_OPACITY, "android:strokeAlpha")
+ .put(SVG_STROKE_LINEJOINE, "android:strokeLineJoin")
+ .put(SVG_STROKE_LINECAP, "android:strokeLineCap")
+ .put(SVG_STROKE_WIDTH, "android:strokeWidth")
+ .put(SVG_FILL_COLOR, "android:fillColor")
+ .put(SVG_FILL_OPACITY, "android:fillAlpha")
+ .put(SVG_CLIP, "android:clip")
+ .put(SVG_OPACITY, "android:fillAlpha")
+ .build();
+
+ // List all the Svg nodes that we don't support. Categorized by the types.
+ private static final HashSet<String> unsupportedSvgNodes = Sets.newHashSet(
+ // Animation elements
+ "animate", "animateColor", "animateMotion", "animateTransform", "mpath", "set",
+ // Container elements
+ "a", "defs", "glyph", "marker", "mask", "missing-glyph", "pattern", "switch", "symbol",
+ // Filter primitive elements
+ "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix",
+ "feDiffuseLighting", "feDisplacementMap", "feFlood", "feFuncA", "feFuncB", "feFuncG",
+ "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology",
+ "feOffset", "feSpecularLighting", "feTile", "feTurbulence",
+ // Font elements
+ "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri",
+ "hkern", "vkern",
+ // Gradient elements
+ "linearGradient", "radialGradient", "stop",
+ // Graphics elements
+ "ellipse", "text",
+ // Light source elements
+ "feDistantLight", "fePointLight", "feSpotLight",
+ // Structural elements
+ "defs", "symbol", "use",
+ // Text content elements
+ "altGlyph", "altGlyphDef", "altGlyphItem", "glyph", "glyphRef", "textPath", "text", "tref",
+ "tspan",
+ // Text content child elements
+ "altGlyph", "textPath", "tref", "tspan",
+ // Uncategorized elements
+ "clipPath", "color-profile", "cursor", "filter", "foreignObject", "script", "view");
+
+ @NonNull
+ private static SvgTree parse(File f) throws Exception {
+ SvgTree svgTree = new SvgTree();
+ Document doc = svgTree.parse(f);
+ NodeList nSvgNode;
+
+ // Parse svg elements
+ nSvgNode = doc.getElementsByTagName("svg");
+ if (nSvgNode.getLength() != 1) {
+ throw new IllegalStateException("Not a proper SVG file");
+ }
+ Node rootNode = nSvgNode.item(0);
+ for (int i = 0; i < nSvgNode.getLength(); i++) {
+ Node nNode = nSvgNode.item(i);
+ if (nNode.getNodeType() == Node.ELEMENT_NODE) {
+ svgTree.parseDimension(nNode);
+ }
+ }
+
+ if (svgTree.getViewBox() == null) {
+ svgTree.logErrorLine("Missing \"viewBox\" in <svg> element", rootNode, SvgTree.SvgLogLevel.ERROR);
+ return svgTree;
+ }
+
+ // TODO: Properly handle "use" tag
+
+ SvgGroupNode root = new SvgGroupNode(svgTree, rootNode, "root");
+ svgTree.setRoot(root);
+
+ // Parse all the group and path node recursively.
+ traverseSVGAndExtract(svgTree, root, rootNode);
+ svgTree.flattern();
+ svgTree.dump(root);
+
+ return svgTree;
+ }
+
+ /**
+ * Traverse the tree in pre-order.
+ */
+ private static void traverseSVGAndExtract(SvgTree svgTree, SvgGroupNode currentGroup, Node item) {
+ // Recursively traverse all the group and path nodes
+ NodeList allChildren = item.getChildNodes();
+
+ for (int i = 0; i < allChildren.getLength(); i++) {
+ Node currentNode = allChildren.item(i);
+ String nodeName = currentNode.getNodeName();
+ if (!currentNode.hasChildNodes() && !currentNode.hasAttributes()) {
+ // If there is nothing in this node, just ignore it.
+ continue;
+ }
+ if (SVG_PATH.equals(nodeName) ||
+ SVG_RECT.equals(nodeName) ||
+ SVG_CIRCLE.equals(nodeName) ||
+ SVG_ELLIPSE.equals(nodeName) ||
+ SVG_POLYGON.equals(nodeName) ||
+ SVG_POLYLINE.equals(nodeName) ||
+ SVG_LINE.equals(nodeName)) {
+ SvgLeafNode child = new SvgLeafNode(svgTree, currentNode, nodeName + i);
+
+ extractAllItemsAs(svgTree, child, currentNode);
+
+ currentGroup.addChild(child);
+ svgTree.setHasLeafNode(true);
+ } else if (SVG_GROUP.equals(nodeName)) {
+ SvgGroupNode childGroup = new SvgGroupNode(svgTree, currentNode, "child" + i);
+ currentGroup.addChild(childGroup);
+ traverseSVGAndExtract(svgTree, childGroup, currentNode);
+ } else {
+ // For other fancy tags, like <switch>, they can contain children too.
+ // Report the unsupported nodes.
+ if (unsupportedSvgNodes.contains(nodeName)) {
+ svgTree.logErrorLine("<" + nodeName + "> is not supported", currentNode,
+ SvgTree.SvgLogLevel.ERROR);
+ }
+ // This is a workaround for the cases using defs to define a full icon size clip
+ // path, which is redundant information anyway.
+ if (!"defs".equals(nodeName)) {
+ traverseSVGAndExtract(svgTree, currentGroup, currentNode);
+ }
+ }
+ }
+
+ }
+
+ // Read the content from currentItem, and fill into "child"
+ private static void extractAllItemsAs(SvgTree avg, SvgLeafNode child, Node currentItem) {
+ Node currentGroup = currentItem.getParentNode();
+
+ boolean hasNodeAttr = false;
+ String styleContent = "";
+ boolean nothingToDisplay = false;
+
+ while (currentGroup != null && currentGroup.getNodeName().equals("g")) {
+ // Parse the group's attributes.
+ logger.log(Level.FINE, "Printing current parent");
+ printlnCommon(currentGroup);
+
+ NamedNodeMap attr = currentGroup.getAttributes();
+ Node nodeAttr = attr.getNamedItem(SVG_STYLE);
+ // Search for the "display:none", if existed, then skip this item.
+ if (nodeAttr != null) {
+ styleContent += nodeAttr.getTextContent() + ";";
+ logger.log(Level.FINE, "styleContent is :" + styleContent + "at number group ");
+ if (styleContent.contains("display:none")) {
+ logger.log(Level.FINE, "Found none style, skip the whole group");
+ nothingToDisplay = true;
+ break;
+ } else {
+ hasNodeAttr = true;
+ }
+ }
+
+ Node displayAttr = attr.getNamedItem(SVG_DISPLAY);
+ if (displayAttr != null && "none".equals(displayAttr.getNodeValue())) {
+ logger.log(Level.FINE, "Found display:none style, skip the whole group");
+ nothingToDisplay = true;
+ break;
+ }
+ currentGroup = currentGroup.getParentNode();
+ }
+
+ if (nothingToDisplay) {
+ // Skip this current whole item.
+ return;
+ }
+
+ logger.log(Level.FINE, "Print current item");
+ printlnCommon(currentItem);
+
+ if (hasNodeAttr && styleContent != null) {
+ addStyleToPath(child, styleContent);
+ }
+
+ Node currentGroupNode = currentItem;
+
+ if (SVG_PATH.equals(currentGroupNode.getNodeName())) {
+ extractPathItem(avg, child, currentGroupNode);
+ }
+
+ if (SVG_RECT.equals(currentGroupNode.getNodeName())) {
+ extractRectItem(avg, child, currentGroupNode);
+ }
+
+ if (SVG_CIRCLE.equals(currentGroupNode.getNodeName())) {
+ extractCircleItem(avg, child, currentGroupNode);
+ }
+
+ if (SVG_POLYGON.equals(currentGroupNode.getNodeName()) ||
+ SVG_POLYLINE.equals(currentGroupNode.getNodeName())) {
+ extractPolyItem(avg, child, currentGroupNode);
+ }
+
+ if (SVG_LINE.equals(currentGroupNode.getNodeName())) {
+ extractLineItem(avg, child, currentGroupNode);
+ }
+
+ if (SVG_ELLIPSE.equals(currentGroupNode.getNodeName())) {
+ extractEllipseItem(avg, child, currentGroupNode);
+ }
+ }
+
+ private static void printlnCommon(Node n) {
+ logger.log(Level.FINE, " nodeName=\"" + n.getNodeName() + "\"");
+
+ String val = n.getNamespaceURI();
+ if (val != null) {
+ logger.log(Level.FINE, " uri=\"" + val + "\"");
+ }
+
+ val = n.getPrefix();
+
+ if (val != null) {
+ logger.log(Level.FINE, " pre=\"" + val + "\"");
+ }
+
+ val = n.getLocalName();
+ if (val != null) {
+ logger.log(Level.FINE, " local=\"" + val + "\"");
+ }
+
+ val = n.getNodeValue();
+ if (val != null) {
+ logger.log(Level.FINE, " nodeValue=");
+ if (val.trim().equals("")) {
+ // Whitespace
+ logger.log(Level.FINE, "[WS]");
+ } else {
+ logger.log(Level.FINE, "\"" + n.getNodeValue() + "\"");
+ }
+ }
+ }
+
+ /**
+ * Convert polygon element into a path.
+ */
+ private static void extractPolyItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
+ logger.log(Level.FINE, "Polyline or Polygon found" + currentGroupNode.getTextContent());
+ if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
+
+ NamedNodeMap a = currentGroupNode.getAttributes();
+ int len = a.getLength();
+
+ for (int itemIndex = 0; itemIndex < len; itemIndex++) {
+ Node n = a.item(itemIndex);
+ String name = n.getNodeName();
+ String value = n.getNodeValue();
+ if (name.equals(SVG_STYLE)) {
+ addStyleToPath(child, value);
+ } else if (presentationMap.containsKey(name)) {
+ child.fillPresentationAttributes(name, value);
+ } else if (name.equals(SVG_POINTS)) {
+ PathBuilder builder = new PathBuilder();
+ String[] split = value.split("[\\s,]+");
+ float baseX = Float.parseFloat(split[0]);
+ float baseY = Float.parseFloat(split[1]);
+ builder.absoluteMoveTo(baseX, baseY);
+ for (int j = 2; j < split.length; j += 2) {
+ float x = Float.parseFloat(split[j]);
+ float y = Float.parseFloat(split[j + 1]);
+ builder.relativeLineTo(x - baseX, y - baseY);
+ baseX = x;
+ baseY = y;
+ }
+ if (SVG_POLYGON.equals(currentGroupNode.getNodeName())) {
+ builder.relativeClose();
+ }
+ child.setPathData(builder.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert rectangle element into a path.
+ */
+ private static void extractRectItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
+ logger.log(Level.FINE, "Rect found" + currentGroupNode.getTextContent());
+
+ if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
+ float x = 0;
+ float y = 0;
+ float width = Float.NaN;
+ float height = Float.NaN;
+ float rx = 0;
+ float ry = 0;
+
+ NamedNodeMap a = currentGroupNode.getAttributes();
+ int len = a.getLength();
+ boolean pureTransparent = false;
+ for (int j = 0; j < len; j++) {
+ Node n = a.item(j);
+ String name = n.getNodeName();
+ String value = n.getNodeValue();
+ if (name.equals(SVG_STYLE)) {
+ addStyleToPath(child, value);
+ if (value.contains("opacity:0;")) {
+ pureTransparent = true;
+ }
+ } else if (presentationMap.containsKey(name)) {
+ child.fillPresentationAttributes(name, value);
+ } else if (name.equals("clip-path") && value.startsWith("url(#SVGID_")) {
+
+ } else if (name.equals("x")) {
+ x = Float.parseFloat(value);
+ } else if (name.equals("y")) {
+ y = Float.parseFloat(value);
+ } else if (name.equals("rx")) {
+ rx = Float.parseFloat(value);
+ } else if (name.equals("ry")) {
+ ry = Float.parseFloat(value);
+ } else if (name.equals("width")) {
+ width = Float.parseFloat(value);
+ } else if (name.equals("height")) {
+ height = Float.parseFloat(value);
+ } else if (name.equals("style")) {
+
+ }
+ }
+
+ if (!pureTransparent && avg != null && !Float.isNaN(x) && !Float.isNaN(y)
+ && !Float.isNaN(width)
+ && !Float.isNaN(height)) {
+ PathBuilder builder = new PathBuilder();
+ if (rx <= 0 && ry <= 0) {
+ // "M x, y h width v height h -width z"
+ builder.absoluteMoveTo(x, y);
+ builder.relativeHorizontalTo(width);
+ builder.relativeVerticalTo(height);
+ builder.relativeHorizontalTo(-width);
+ } else {
+ // Refer to http://www.w3.org/TR/SVG/shapes.html#RectElement
+ assert rx > 0 || ry > 0;
+ if (ry == 0) {
+ ry = rx;
+ } else if (rx == 0) {
+ rx = ry;
+ }
+ if (rx > width / 2) rx = width / 2;
+ if (ry > height / 2) ry = height / 2;
+
+ builder.absoluteMoveTo(x + rx, y);
+ builder.absoluteLineTo(x + width - rx, y);
+ builder.absoluteArcTo(rx, ry, false, false, true, x + width, y + ry);
+ builder.absoluteLineTo(x + width, y + height - ry);
+
+ builder.absoluteArcTo(rx, ry, false, false, true, x + width - rx, y + height);
+ builder.absoluteLineTo(x + rx, y + height);
+
+ builder.absoluteArcTo(rx, ry, false, false, true, x, y + height - ry);
+ builder.absoluteLineTo(x, y + ry);
+ builder.absoluteArcTo(rx, ry, false, false, true, x + rx, y);
+ }
+ builder.relativeClose();
+ child.setPathData(builder.toString());
+ }
+ }
+ }
+
+ /**
+ * Convert circle element into a path.
+ */
+ private static void extractCircleItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
+ logger.log(Level.FINE, "circle found" + currentGroupNode.getTextContent());
+
+ if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
+ float cx = 0;
+ float cy = 0;
+ float radius = 0;
+
+ NamedNodeMap a = currentGroupNode.getAttributes();
+ int len = a.getLength();
+ boolean pureTransparent = false;
+ for (int j = 0; j < len; j++) {
+ Node n = a.item(j);
+ String name = n.getNodeName();
+ String value = n.getNodeValue();
+ if (name.equals(SVG_STYLE)) {
+ addStyleToPath(child, value);
+ if (value.contains("opacity:0;")) {
+ pureTransparent = true;
+ }
+ } else if (presentationMap.containsKey(name)) {
+ child.fillPresentationAttributes(name, value);
+ } else if (name.equals("clip-path") && value.startsWith("url(#SVGID_")) {
+
+ } else if (name.equals("cx")) {
+ cx = Float.parseFloat(value);
+ } else if (name.equals("cy")) {
+ cy = Float.parseFloat(value);
+ } else if (name.equals("r")) {
+ radius = Float.parseFloat(value);
+ }
+
+ }
+
+ if (!pureTransparent && avg != null && !Float.isNaN(cx) && !Float.isNaN(cy)) {
+ // "M cx cy m -r, 0 a r,r 0 1,1 (r * 2),0 a r,r 0 1,1 -(r * 2),0"
+ PathBuilder builder = new PathBuilder();
+ builder.absoluteMoveTo(cx, cy);
+ builder.relativeMoveTo(-radius, 0);
+ builder.relativeArcTo(radius, radius, false, true, true, 2 * radius, 0);
+ builder.relativeArcTo(radius, radius, false, true, true, -2 * radius, 0);
+ child.setPathData(builder.toString());
+ }
+ }
+ }
+
+ /**
+ * Convert ellipse element into a path.
+ */
+ private static void extractEllipseItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
+ logger.log(Level.FINE, "ellipse found" + currentGroupNode.getTextContent());
+
+ if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
+ float cx = 0;
+ float cy = 0;
+ float rx = 0;
+ float ry = 0;
+
+ NamedNodeMap a = currentGroupNode.getAttributes();
+ int len = a.getLength();
+ boolean pureTransparent = false;
+ for (int j = 0; j < len; j++) {
+ Node n = a.item(j);
+ String name = n.getNodeName();
+ String value = n.getNodeValue();
+ if (name.equals(SVG_STYLE)) {
+ addStyleToPath(child, value);
+ if (value.contains("opacity:0;")) {
+ pureTransparent = true;
+ }
+ } else if (presentationMap.containsKey(name)) {
+ child.fillPresentationAttributes(name, value);
+ } else if (name.equals("clip-path") && value.startsWith("url(#SVGID_")) {
+
+ } else if (name.equals("cx")) {
+ cx = Float.parseFloat(value);
+ } else if (name.equals("cy")) {
+ cy = Float.parseFloat(value);
+ } else if (name.equals("rx")) {
+ rx = Float.parseFloat(value);
+ } else if (name.equals("ry")) {
+ ry = Float.parseFloat(value);
+ }
+
+ }
+
+ if (!pureTransparent && avg != null
+ && !Float.isNaN(cx) && !Float.isNaN(cy)
+ && rx > 0 && ry > 0) {
+ // "M cx -rx, cy a rx,ry 0 1,0 (rx * 2),0 a rx,ry 0 1,0 -(rx * 2),0"
+ PathBuilder builder = new PathBuilder();
+ builder.absoluteMoveTo(cx - rx, cy);
+ builder.relativeArcTo(rx, ry, false, true, false, 2 * rx, 0);
+ builder.relativeArcTo(rx, ry, false, true, false, -2 * rx, 0);
+ builder.relativeClose();
+ child.setPathData(builder.toString());
+ }
+ }
+ }
+
+ /**
+ * Convert line element into a path.
+ */
+ private static void extractLineItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
+ logger.log(Level.FINE, "line found" + currentGroupNode.getTextContent());
+
+ if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
+ float x1 = 0;
+ float y1 = 0;
+ float x2 = 0;
+ float y2 = 0;
+
+ NamedNodeMap a = currentGroupNode.getAttributes();
+ int len = a.getLength();
+ boolean pureTransparent = false;
+ for (int j = 0; j < len; j++) {
+ Node n = a.item(j);
+ String name = n.getNodeName();
+ String value = n.getNodeValue();
+ if (name.equals(SVG_STYLE)) {
+ addStyleToPath(child, value);
+ if (value.contains("opacity:0;")) {
+ pureTransparent = true;
+ }
+ } else if (presentationMap.containsKey(name)) {
+ child.fillPresentationAttributes(name, value);
+ } else if (name.equals("clip-path") && value.startsWith("url(#SVGID_")) {
+ // TODO: Handle clip path here.
+ } else if (name.equals("x1")) {
+ x1 = Float.parseFloat(value);
+ } else if (name.equals("y1")) {
+ y1 = Float.parseFloat(value);
+ } else if (name.equals("x2")) {
+ x2 = Float.parseFloat(value);
+ } else if (name.equals("y2")) {
+ y2 = Float.parseFloat(value);
+ }
+ }
+
+ if (!pureTransparent && avg != null && !Float.isNaN(x1) && !Float.isNaN(y1)
+ && !Float.isNaN(x2) && !Float.isNaN(y2)) {
+ // "M x1, y1 L x2, y2"
+ PathBuilder builder = new PathBuilder();
+ builder.absoluteMoveTo(x1, y1);
+ builder.absoluteLineTo(x2, y2);
+ child.setPathData(builder.toString());
+ }
+ }
+
+ }
+
+ private static void extractPathItem(SvgTree avg, SvgLeafNode child, Node currentGroupNode) {
+ logger.log(Level.FINE, "Path found " + currentGroupNode.getTextContent());
+
+ if (currentGroupNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element eElement = (Element)currentGroupNode;
+
+ NamedNodeMap a = currentGroupNode.getAttributes();
+ int len = a.getLength();
+
+ for (int j = 0; j < len; j++) {
+ Node n = a.item(j);
+ String name = n.getNodeName();
+ String value = n.getNodeValue();
+ if (name.equals(SVG_STYLE)) {
+ addStyleToPath(child, value);
+ } else if (presentationMap.containsKey(name)) {
+ child.fillPresentationAttributes(name, value);
+ } else if (name.equals(SVG_D)) {
+ String pathData = value.replaceAll("(\\d)-", "$1,-");
+ child.setPathData(pathData);
+ }
+
+ }
+ }
+ }
+
+ private static void addStyleToPath(SvgLeafNode path, String value) {
+ logger.log(Level.FINE, "Style found is " + value);
+ if (value != null) {
+ String[] parts = value.split(";");
+ for (int k = parts.length - 1; k >= 0; k--) {
+ String subStyle = parts[k];
+ String[] nameValue = subStyle.split(":");
+ if (nameValue.length == 2 && nameValue[0] != null && nameValue[1] != null) {
+ if (presentationMap.containsKey(nameValue[0])) {
+ path.fillPresentationAttributes(nameValue[0], nameValue[1]);
+ } else if (nameValue[0].equals(SVG_OPACITY)) {
+ // TODO: This is hacky, since we don't have a group level
+ // android:opacity. This only works when the path didn't overlap.
+ path.fillPresentationAttributes(SVG_FILL_OPACITY, nameValue[1]);
+ }
+ }
+ }
+ }
+ }
+
+ private static final String head = "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n";
+
+ private static String getSizeString(float w, float h, float scaleFactor) {
+ String size = " android:width=\"" + (int) (w * scaleFactor) + "dp\"\n" +
+ " android:height=\"" + (int) (h * scaleFactor) + "dp\"\n";
+ return size;
+ }
+
+ private static void writeFile(OutputStream outStream, SvgTree svgTree) throws IOException {
+
+ OutputStreamWriter fw = new OutputStreamWriter(outStream);
+ fw.write(head);
+ float viewportWidth = svgTree.getViewportWidth();
+ float viewportHeight = svgTree.getViewportHeight();
+
+ fw.write(getSizeString(svgTree.getWidth(), svgTree.getHeight(), svgTree.getScaleFactor()));
+
+ fw.write(" android:viewportWidth=\"" + viewportWidth + "\"\n");
+ fw.write(" android:viewportHeight=\"" + viewportHeight + "\">\n");
+
+ svgTree.normalize();
+ // TODO: this has to happen in the tree mode!!!
+ writeXML(svgTree, fw);
+ fw.write("</vector>\n");
+
+ fw.close();
+ }
+
+ private static void writeXML(SvgTree svgTree, OutputStreamWriter fw) throws IOException {
+ svgTree.getRoot().writeXML(fw);
+ }
+
+ /**
+ * Convert a SVG file into VectorDrawable's XML content, if no error is found.
+ *
+ * @param inputSVG the input SVG file
+ * @param outStream the converted VectorDrawable's content. This can be
+ * empty if there is any error found during parsing
+ * @return the error messages, which contain things like all the tags
+ * VectorDrawble don't support or exception message.
+ */
+ public static String parseSvgToXml(File inputSVG, OutputStream outStream) {
+ // Write all the error message during parsing into SvgTree. and return here as getErrorLog().
+ // We will also log the exceptions here.
+ String errorLog = null;
+ try {
+ SvgTree svgTree = parse(inputSVG);
+ errorLog = svgTree.getErrorLog();
+ if (svgTree.getHasLeafNode()) {
+ writeFile(outStream, svgTree);
+ }
+ } catch (Exception e) {
+ errorLog = "EXCEPTION in parsing " + inputSVG.getName() + ":\n" + e.getMessage();
+ }
+ return errorLog;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgGroupNode.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgGroupNode.java
new file mode 100644
index 0000000..2285143
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgGroupNode.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.vectordrawable;
+
+import org.w3c.dom.Node;
+
+import java.awt.geom.AffineTransform;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Represent a SVG file's group element.
+ */
+class SvgGroupNode extends SvgNode {
+ private static Logger logger = Logger.getLogger(SvgGroupNode.class.getSimpleName());
+ private static final String INDENT_LEVEL = " ";
+
+ private ArrayList<SvgNode> mChildren = new ArrayList<SvgNode>();
+
+ public SvgGroupNode(SvgTree svgTree, Node docNode, String name) {
+ super(svgTree, docNode, name);
+ }
+
+ public void addChild(SvgNode child) {
+ // Pass the presentation map down to the children, who can override the attributes.
+ mChildren.add(child);
+ // The child has its own attributes map. But the parents can still fill some attributes
+ // if they don't exists
+ child.fillEmptyAttributes(mVdAttributesMap);
+ }
+
+ @Override
+ public void dumpNode(String indent) {
+ // Print the current group.
+ logger.log(Level.FINE, indent + "current group is :" + getName());
+
+ // Then print all the children.
+ for (SvgNode node : mChildren) {
+ node.dumpNode(indent + INDENT_LEVEL);
+ }
+ }
+
+ @Override
+ public boolean isGroupNode() {
+ return true;
+ }
+
+ @Override
+ public void transformIfNeeded(AffineTransform rootTransform) {
+ for (SvgNode p : mChildren) {
+ p.transformIfNeeded(rootTransform);
+ }
+ }
+
+ @Override
+ public void flattern(AffineTransform transform) {
+ for (SvgNode n : mChildren) {
+ mStackedTransform.setTransform(transform);
+ mStackedTransform.concatenate(mLocalTransform);
+ n.flattern(mStackedTransform);
+ }
+ }
+
+ @Override
+ public void writeXML(OutputStreamWriter writer) throws IOException {
+ for (SvgNode node : mChildren) {
+ node.writeXML(writer);
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgLeafNode.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgLeafNode.java
new file mode 100644
index 0000000..0f08d2c
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgLeafNode.java
@@ -0,0 +1,211 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.annotations.Nullable;
+import com.google.common.collect.ImmutableMap;
+import org.w3c.dom.Node;
+
+import java.awt.geom.AffineTransform;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Represent a SVG file's leave element.
+ */
+class SvgLeafNode extends SvgNode {
+ private static Logger logger = Logger.getLogger(SvgLeafNode.class.getSimpleName());
+
+ private String mPathData;
+
+ public SvgLeafNode(SvgTree svgTree, Node node, String nodeName) {
+ super(svgTree, node, nodeName);
+ }
+
+ private String getAttributeValues(ImmutableMap<String, String> presentationMap) {
+ StringBuilder sb = new StringBuilder("/>\n");
+ for (String key : mVdAttributesMap.keySet()) {
+ String vectorDrawableAttr = presentationMap.get(key);
+ String svgValue = mVdAttributesMap.get(key);
+ String vdValue = svgValue.trim();
+ // There are several cases we need to convert from SVG format to
+ // VectorDrawable format. Like "none", "3px" or "rgb(255, 0, 0)"
+ if ("none".equals(vdValue)) {
+ vdValue = "#00000000";
+ } else if (vdValue.endsWith("px")){
+ vdValue = vdValue.substring(0, vdValue.length() - 2);
+ } else if (vdValue.startsWith("rgb")) {
+ vdValue = vdValue.substring(3, vdValue.length());
+ vdValue = convertRGBToHex(vdValue);
+ if (vdValue == null) {
+ getTree().logErrorLine("Unsupported Color format " + vdValue, getDocumentNode(),
+ SvgTree.SvgLogLevel.ERROR);
+ }
+ }
+ String attr = "\n " + vectorDrawableAttr + "=\"" +
+ vdValue + "\"";
+ sb.insert(0, attr);
+
+ }
+ return sb.toString();
+ }
+
+ public static int clamp(int val, int min, int max) {
+ return Math.max(min, Math.min(max, val));
+ }
+
+ /**
+ * SVG allows using rgb(int, int, int) or rgb(float%, float%, float%) to
+ * represent a color, but Android doesn't. Therefore, we need to convert
+ * them into #RRGGBB format.
+ * @param svgValue in either "(int, int, int)" or "(float%, float%, float%)"
+ * @return #RRGGBB in hex format, or null, if an error is found.
+ */
+ @Nullable
+ private String convertRGBToHex(String svgValue) {
+ // We don't support color keyword yet.
+ // http://www.w3.org/TR/SVG11/types.html#ColorKeywords
+ String result = null;
+ String functionValue = svgValue.trim();
+ functionValue = svgValue.substring(1, functionValue.length() - 1);
+ // After we cut the "(", ")", we can deal with the numbers.
+ String[] numbers = functionValue.split(",");
+ if (numbers.length != 3) {
+ return null;
+ }
+ int[] color = new int[3];
+ for (int i = 0; i < 3; i ++) {
+ String number = numbers[i];
+ number = number.trim();
+ if (number.endsWith("%")) {
+ float value = Float.parseFloat(number.substring(0, number.length() - 1));
+ color[i] = clamp((int)(value * 255.0f / 100.0f), 0, 255);
+ } else {
+ int value = Integer.parseInt(number);
+ color[i] = clamp(value, 0, 255);
+ }
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append("#");
+ for (int i = 0; i < 3; i ++) {
+ builder.append(String.format("%02X", color[i]));
+ }
+ result = builder.toString();
+ assert result.length() == 7;
+ return result;
+ }
+
+ @Override
+ public void dumpNode(String indent) {
+ logger.log(Level.FINE, indent + (mPathData != null ? mPathData : " null pathData ") +
+ (mName != null ? mName : " null name "));
+ }
+
+ public void setPathData(String pathData) {
+ mPathData = pathData;
+ }
+
+ @Override
+ public boolean isGroupNode() {
+ return false;
+ }
+
+ @Override
+ public void transformIfNeeded(AffineTransform rootTransform) {
+ if ((mPathData == null)) {
+ // Nothing to draw and transform, early return.
+ return;
+ }
+ VdPath.Node[] n = PathParser.parsePath(mPathData);
+ AffineTransform finalTransform = new AffineTransform(rootTransform);
+ finalTransform.concatenate(mStackedTransform);
+ boolean needsConvertRelativeMoveAfterClose = VdPath.Node.hasRelMoveAfterClose(n);
+ if (!finalTransform.isIdentity() || needsConvertRelativeMoveAfterClose) {
+ VdPath.Node.transform(finalTransform, n);
+ }
+ String decimalFormatString = getDecimalFormatString();
+ mPathData = VdPath.Node.NodeListToString(n, decimalFormatString);
+ }
+
+ private String getDecimalFormatString() {
+ float viewportWidth = getTree().getViewportWidth();
+ float viewportHeight = getTree().getViewportHeight();
+ float minSize = Math.min(viewportHeight, viewportWidth);
+ float exponent = Math.round(Math.log10(minSize));
+ int decimalPlace = (int) Math.floor(exponent - 4);
+ String decimalFormatString = "#";
+ if (decimalPlace < 0) {
+ // Build a string with decimal places for "#.##...", and cap on 6 digits.
+ if (decimalPlace < -6) {
+ decimalPlace = -6;
+ }
+ decimalFormatString += ".";
+ for (int i = 0 ; i < -decimalPlace; i++) {
+ decimalFormatString += "#";
+ }
+ }
+ return decimalFormatString;
+ }
+
+ @Override
+ public void flattern(AffineTransform transform) {
+ mStackedTransform.setTransform(transform);
+ mStackedTransform.concatenate(mLocalTransform);
+
+ if (mVdAttributesMap.containsKey(Svg2Vector.SVG_STROKE_WIDTH)
+ && ((mStackedTransform.getType() | AffineTransform.TYPE_MASK_SCALE) != 0) ) {
+ getTree().logErrorLine("We don't scale the stroke width!", getDocumentNode(),
+ SvgTree.SvgLogLevel.WARNING);
+ }
+ }
+
+ @Override
+ public void writeXML(OutputStreamWriter writer) throws IOException {
+ // First decide whether or not we can skip this path, since it draw nothing out.
+ String fillColor = mVdAttributesMap.get(Svg2Vector.SVG_FILL_COLOR);
+ String strokeColor = mVdAttributesMap.get(Svg2Vector.SVG_STROKE_COLOR);
+ logger.log(Level.FINE, "fill color " + fillColor);
+ boolean emptyFill = fillColor != null && ("none".equals(fillColor) || "#0000000".equals(fillColor));
+ boolean emptyStroke = strokeColor == null || "none".equals(strokeColor);
+ boolean emptyPath = mPathData == null;
+ boolean nothingToDraw = emptyPath || emptyFill && emptyStroke;
+ if (nothingToDraw) {
+ return;
+ }
+
+ // Second, write the color info handling the default values.
+ writer.write(" <path\n");
+ if (!mVdAttributesMap.containsKey(Svg2Vector.SVG_FILL_COLOR)) {
+ logger.log(Level.FINE, "ADDING FILL SVG_FILL_COLOR");
+ writer.write(" android:fillColor=\"#FF000000\"\n");
+ }
+ if (!emptyStroke && !mVdAttributesMap.containsKey(Svg2Vector.SVG_STROKE_WIDTH)) {
+ logger.log(Level.FINE, "Adding default stroke width");
+ writer.write(" android:strokeWidth=\"1\"\n");
+ }
+
+ // Last, write the path data and all associated attributes.
+ writer.write(" android:pathData=\"" + mPathData + "\"");
+ writer.write(getAttributeValues(Svg2Vector.presentationMap));
+ }
+
+ public void fillPresentationAttributes(String name, String value) {
+ fillPresentationAttributes(name, value, logger);
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgNode.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgNode.java
new file mode 100644
index 0000000..914026c
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgNode.java
@@ -0,0 +1,216 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.annotations.NonNull;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.awt.geom.AffineTransform;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Parent class for a SVG file's node, can be either group or leave element.
+ */
+abstract class SvgNode {
+ private static Logger logger = Logger.getLogger(SvgNode.class.getSimpleName());
+
+ private static final String TRANSFORM_TAG = "transform";
+
+ private static final String MATRIX_ATTRIBUTE = "matrix";
+ private static final String TRANSLATE_ATTRIBUTE = "translate";
+ private static final String ROTATE_ATTRIBUTE = "rotate";
+ private static final String SCALE_ATTRIBUTE = "scale";
+ private static final String SKEWX_ATTRIBUTE = "skewX";
+ private static final String SKEWY_ATTRIBUTE = "skewY";
+
+ protected String mName;
+ // Keep a reference to the tree in order to dump the error log.
+ private SvgTree mSvgTree;
+ // Use document node to get the line number for error reporting.
+ private Node mDocumentNode;
+
+ // Key is the attributes for vector drawable, and the value is the converted from SVG.
+ protected Map<String, String> mVdAttributesMap = new HashMap<String, String>();
+ // If mLocalTransform is identity, it is the same as not having any transformation.
+ protected AffineTransform mLocalTransform = new AffineTransform();
+
+ // During the flattern() operatation, we need to merge the transformation from top down.
+ // This is the stacked transformation. And this will be used for the path data transform().
+ protected AffineTransform mStackedTransform = new AffineTransform();
+
+ /**
+ * While parsing the translate() rotate() ..., update the <code>mLocalTransform</code>
+ */
+ public SvgNode(SvgTree svgTree, Node node, String name) {
+ mName = name;
+ mSvgTree = svgTree;
+ mDocumentNode = node;
+ // Parse and generate a presentation map.
+ NamedNodeMap a = node.getAttributes();
+ int len = a.getLength();
+
+ for (int itemIndex = 0; itemIndex < len; itemIndex++) {
+ Node n = a.item(itemIndex);
+ String nodeName = n.getNodeName();
+ String nodeValue = n.getNodeValue();
+ // TODO: Handle style here. Refer to Svg2Vector::addStyleToPath().
+ if (Svg2Vector.presentationMap.containsKey(nodeName)) {
+ fillPresentationAttributes(nodeName, nodeValue, logger);
+ }
+
+ if (TRANSFORM_TAG.equals(nodeName)) {
+ logger.log(Level.FINE, nodeName + " " + nodeValue);
+ parseLocalTransform(nodeValue);
+ }
+ }
+ }
+
+ private void parseLocalTransform(String nodeValue) {
+ // We separate the string into multiple parts and look like this:
+ // "translate" "30" "rotate" "4.5e1 5e1 50"
+ nodeValue = nodeValue.replaceAll(",", " ");
+ String[] matrices = nodeValue.split("\\(|\\)");
+ AffineTransform parsedTransform;
+ for (int i = 0; i < matrices.length -1; i += 2) {
+ parsedTransform = parseOneTransform(matrices[i].trim(), matrices[i+1].trim());
+ if (parsedTransform != null) {
+ mLocalTransform.concatenate(parsedTransform);
+ }
+ }
+ }
+
+ @NonNull
+ private AffineTransform parseOneTransform(String type, String data) {
+ float[] numbers = getNumbers(data);
+ int numLength = numbers.length;
+ AffineTransform parsedTranform = new AffineTransform();
+
+ if (MATRIX_ATTRIBUTE.equalsIgnoreCase(type)) {
+ if (numLength != 6) {
+ return null;
+ }
+ parsedTranform.setTransform(numbers[0], numbers[1], numbers[2],
+ numbers[3], numbers[4], numbers[5]);
+ } else if (TRANSLATE_ATTRIBUTE.equalsIgnoreCase(type)) {
+ if (numLength != 1 && numLength != 2) {
+ return null;
+ }
+ // Default translateY is 0
+ parsedTranform.translate(numbers[0], numLength == 2 ? numbers[1] : 0);
+ } else if (SCALE_ATTRIBUTE.equalsIgnoreCase(type)) {
+ if (numLength != 1 && numLength != 2) {
+ return null;
+ }
+ // Default scaleY == scaleX
+ parsedTranform.scale(numbers[0], numLength == 2 ? numbers[1] : numbers[0]);
+ } else if (ROTATE_ATTRIBUTE.equalsIgnoreCase(type)) {
+ if (numLength != 1 && numLength != 3) {
+ return null;
+ }
+ parsedTranform.rotate(Math.toRadians(numbers[0]),
+ numLength == 3 ? numbers[1] : 0,
+ numLength == 3 ? numbers[2] : 0);
+ } else if (SKEWX_ATTRIBUTE.equalsIgnoreCase(type)) {
+ if (numLength != 1) {
+ return null;
+ }
+ // Note that Swing is pass the shear value directly to the matrix as m01 or m10,
+ // while SVG is using tan(a) in the matrix and a is in radians.
+ parsedTranform.shear(Math.tan(Math.toRadians(numbers[0])), 0);
+ } else if (SKEWY_ATTRIBUTE.equalsIgnoreCase(type)) {
+ if (numLength != 1) {
+ return null;
+ }
+ parsedTranform.shear(0, Math.tan(Math.toRadians(numbers[0])));
+ }
+ return parsedTranform;
+ }
+
+ private float[] getNumbers(String data) {
+ String[] numbers = data.split("\\s+");
+ int len = numbers.length;
+ if (len == 0) {
+ return null;
+ }
+
+ float[] results = new float[len];
+ for (int i = 0; i < len; i ++) {
+ results[i] = Float.parseFloat(numbers[i]);
+ }
+ return results;
+ }
+
+ protected SvgTree getTree() {
+ return mSvgTree;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public Node getDocumentNode() {
+ return mDocumentNode;
+ }
+
+ /**
+ * dump the current node's debug info.
+ */
+ public abstract void dumpNode(String indent);
+
+ /**
+ * Write the Node content into the VectorDrawable's XML file.
+ */
+ public abstract void writeXML(OutputStreamWriter writer) throws IOException;
+
+ /**
+ * @return true the node is a group node.
+ */
+ public abstract boolean isGroupNode();
+
+ /**
+ * Transform the current Node with the transformation matrix.
+ */
+ public abstract void transformIfNeeded(AffineTransform finalTransform);
+
+ protected void fillPresentationAttributes(String name, String value, Logger logger) {
+ logger.log(Level.FINE, ">>>> PROP " + name + " = " + value);
+ if (value.startsWith("url(")) {
+ getTree().logErrorLine("Unsupported URL value: " + value, getDocumentNode(),
+ SvgTree.SvgLogLevel.ERROR);
+ return;
+ }
+ mVdAttributesMap.put(name, value);
+ }
+
+ public void fillEmptyAttributes(Map<String, String> parentAttributesMap) {
+ // Go through the parents' attributes, if the child misses any, then fill it.
+ for (Map.Entry<String, String> entry : parentAttributesMap.entrySet()) {
+ String key = entry.getKey();
+ if (!mVdAttributesMap.containsKey(key)) {
+ mVdAttributesMap.put(key, entry.getValue());
+ }
+ }
+ }
+
+ public abstract void flattern(AffineTransform transform);
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgTree.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgTree.java
new file mode 100644
index 0000000..0c91a8a
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/SvgTree.java
@@ -0,0 +1,214 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.utils.PositionXmlParser;
+import com.google.common.base.Strings;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.awt.geom.AffineTransform;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Represent the SVG file in an internal data structure as a tree.
+ */
+class SvgTree {
+ private static Logger logger = Logger.getLogger(SvgTree.class.getSimpleName());
+
+ public static final String SVG_WIDTH = "width";
+ public static final String SVG_HEIGHT = "height";
+ public static final String SVG_VIEW_BOX = "viewBox";
+
+ private float w = -1;
+ private float h = -1;
+ private AffineTransform mRootTransform = new AffineTransform();
+ private float[] viewBox;
+ private float mScaleFactor = 1;
+
+ private SvgGroupNode mRoot;
+ private String mFileName;
+
+ private ArrayList<String> mErrorLines = new ArrayList<String>();
+
+ private boolean mHasLeafNode = false;
+
+
+ public float getWidth() { return w; }
+ public float getHeight() { return h; }
+ public float getScaleFactor() { return mScaleFactor; }
+ public void setHasLeafNode(boolean hasLeafNode) {
+ mHasLeafNode = hasLeafNode;
+ }
+
+ public float[] getViewBox() { return viewBox; }
+
+ /**
+ * From the root, top down, pass the transformation (TODO: attributes)
+ * down the children.
+ */
+ public void flattern() {
+ mRoot.flattern(new AffineTransform());
+ }
+
+ public enum SvgLogLevel {
+ ERROR,
+ WARNING
+ }
+
+ public Document parse(File f) throws Exception {
+ mFileName = f.getName();
+ Document doc = PositionXmlParser.parse(new FileInputStream(f), false);
+ return doc;
+ }
+
+ public void normalize() {
+ // mRootTransform is always setup, now just need to apply the viewbox info into.
+ mRootTransform.preConcatenate(new AffineTransform(1, 0, 0, 1, -viewBox[0], -viewBox[1]));
+ transform(mRootTransform);
+
+ logger.log(Level.FINE, "matrix=" + mRootTransform);
+ }
+
+ private void transform(AffineTransform rootTransform) {
+ mRoot.transformIfNeeded(rootTransform);
+ }
+
+ public void dump(SvgGroupNode root) {
+ logger.log(Level.FINE, "current file is :" + mFileName);
+ root.dumpNode("");
+ }
+
+ public void setRoot(SvgGroupNode root) {
+ mRoot = root;
+ }
+
+ @Nullable
+ public SvgGroupNode getRoot() {
+ return mRoot;
+ }
+
+ public void logErrorLine(String s, Node node, SvgLogLevel level) {
+ if (!Strings.isNullOrEmpty(s)) {
+ if (node != null) {
+ SourcePosition position = getPosition(node);
+ mErrorLines.add(level.name() + "@ line " + (position.getStartLine() + 1) +
+ " " + s + "\n");
+ } else {
+ mErrorLines.add(s);
+ }
+ }
+ }
+
+ /**
+ * @return Error log. Empty string if there are no errors.
+ */
+ @NonNull
+ public String getErrorLog() {
+ StringBuilder errorBuilder = new StringBuilder();
+ if (!mErrorLines.isEmpty()) {
+ errorBuilder.append("In " + mFileName + ":\n");
+ }
+ for (String log : mErrorLines) {
+ errorBuilder.append(log);
+ }
+ return errorBuilder.toString();
+ }
+
+ /**
+ * @return true when there is at least one valid child.
+ */
+ public boolean getHasLeafNode() {
+ return mHasLeafNode;
+ }
+
+ private SourcePosition getPosition(Node node) {
+ return PositionXmlParser.getPosition(node);
+ }
+
+ public float getViewportWidth() {
+ return (viewBox == null) ? -1 : viewBox[2];
+ }
+
+ public float getViewportHeight() { return (viewBox == null) ? -1 : viewBox[3]; }
+
+ private enum SizeType {
+ PIXEL,
+ PERCENTAGE;
+ }
+
+ public void parseDimension(Node nNode) {
+ NamedNodeMap a = nNode.getAttributes();
+ int len = a.getLength();
+ SizeType widthType = SizeType.PIXEL;
+ SizeType heightType = SizeType.PIXEL;
+ for (int i = 0; i < len; i++) {
+ Node n = a.item(i);
+ String name = n.getNodeName().trim();
+ String value = n.getNodeValue().trim();
+ int subStringSize = value.length();
+ SizeType currentType = SizeType.PIXEL;
+ if (value.endsWith("px")) {
+ subStringSize = subStringSize - 2;
+ } else if (value.endsWith("%")) {
+ subStringSize = subStringSize - 1;
+ currentType = SizeType.PERCENTAGE;
+ }
+
+ if (SVG_WIDTH.equals(name)) {
+ w = Float.parseFloat(value.substring(0, subStringSize));
+ widthType = currentType;
+ } else if (SVG_HEIGHT.equals(name)) {
+ h = Float.parseFloat(value.substring(0, subStringSize));
+ heightType = currentType;
+ } else if (SVG_VIEW_BOX.equals(name)) {
+ viewBox = new float[4];
+ String[] strbox = value.split(" ");
+ for (int j = 0; j < viewBox.length; j++) {
+ viewBox[j] = Float.parseFloat(strbox[j]);
+ }
+ }
+ }
+ // If there is no viewbox, then set it up according to w, h.
+ // From now on, viewport should be read from viewBox, and size should be from w and h.
+ // w and h can be set to percentage too, in this case, set it to the viewbox size.
+ if (viewBox == null && w > 0 && h > 0) {
+ viewBox = new float[4];
+ viewBox[2] = w;
+ viewBox[3] = h;
+ } else if ((w < 0 || h < 0) && viewBox != null) {
+ w = viewBox[2];
+ h = viewBox[3];
+ }
+
+ if (widthType == SizeType.PERCENTAGE && w > 0) {
+ w = viewBox[2] * w / 100;
+ }
+ if (heightType == SizeType.PERCENTAGE && h > 0) {
+ h = viewBox[3] * h / 100;
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdElement.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdElement.java
new file mode 100644
index 0000000..ba0cc95
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdElement.java
@@ -0,0 +1,39 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import org.w3c.dom.NamedNodeMap;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+
+/**
+ * Used to represent one VectorDrawble's element, can be a group or path.
+ */
+abstract class VdElement {
+ String mName;
+
+ public String getName() {
+ return mName;
+ }
+
+ public abstract void draw(Graphics2D g, AffineTransform currentMatrix, float scaleX, float scaleY);
+
+ public abstract void parseAttributes(NamedNodeMap attributes);
+
+ public abstract boolean isGroup();
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdGroup.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdGroup.java
new file mode 100644
index 0000000..9ee369d
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdGroup.java
@@ -0,0 +1,166 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import org.w3c.dom.NamedNodeMap;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Used to represent one VectorDrawble's group element.
+ */
+class VdGroup extends VdElement{
+ private static Logger logger = Logger.getLogger(VdGroup.class.getSimpleName());
+
+ private static final String GROUP_ROTATION = "android:rotation";
+ private static final String GROUP_PIVOTX = "android:pivotX";
+ private static final String GROUP_PIVOTY = "android:pivotY";
+ private static final String GROUP_TRANSLATEX = "android:translateX";
+ private static final String GROUP_TRANSLATEY = "android:translateY";
+ private static final String GROUP_SCALEX = "android:scaleX";
+ private static final String GROUP_SCALEY = "android:scaleY";
+ private static final String GROUP_NAME = "android:name";
+
+ private float mRotate = 0;
+ private float mPivotX = 0;
+ private float mPivotY = 0;
+ private float mScaleX = 1;
+ private float mScaleY = 1;
+ private float mTranslateX = 0;
+ private float mTranslateY = 0;
+
+ // Used at draw time, basically accumulative matrix from root to current group.
+ private AffineTransform mTempStackedMatrix = new AffineTransform();
+
+ // The current group's transformation.
+ private AffineTransform mLocalMatrix = new AffineTransform();
+
+ // Children can be either a {@link VdPath} or {@link VdGroup}
+ private ArrayList<VdElement> mChildren = new ArrayList<VdElement>();
+
+ public void add(VdElement pathOrGroup) {
+ mChildren.add(pathOrGroup);
+ }
+
+ public ArrayList<VdElement> getChildren() {
+ return mChildren;
+ }
+
+ public int size() {
+ return mChildren.size();
+ }
+
+ // Src = trans * src, this is called preConcatenate() in Swing, but postConcatenate() in Android
+ private void androidPostTransform(AffineTransform src, AffineTransform trans) {
+ src.preConcatenate(trans);
+ }
+
+ private void updateLocalMatrix() {
+ // The order we apply is the same as the
+ // RenderNode.cpp::applyViewPropertyTransforms().
+ mLocalMatrix.setToIdentity();
+
+ // In Android framework, the transformation is applied in
+ // VectorDrawable.java VGroup::updateLocalMatrix()
+ AffineTransform tempTrans = new AffineTransform();
+ tempTrans.setToIdentity();
+ tempTrans.translate(-mPivotX, -mPivotY);
+ androidPostTransform(mLocalMatrix, tempTrans);
+
+ tempTrans.setToIdentity();
+ tempTrans.scale(mScaleX, mScaleY);
+ androidPostTransform(mLocalMatrix, tempTrans);
+
+ tempTrans.setToIdentity();
+ tempTrans.rotate(mRotate * 3.1415926 / 180, 0, 0);
+ androidPostTransform(mLocalMatrix, tempTrans);
+
+ tempTrans.setToIdentity();
+ tempTrans.translate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+ androidPostTransform(mLocalMatrix, tempTrans);
+ }
+
+ @Override
+ public void draw(Graphics2D g, AffineTransform currentMatrix, float scaleX, float scaleY) {
+ // SWING default is pre-concatenate
+ mTempStackedMatrix.setTransform(currentMatrix);
+ mTempStackedMatrix.concatenate(mLocalMatrix);
+
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).draw(g, mTempStackedMatrix, scaleX, scaleY);
+ }
+ }
+
+ private void setNameValue(String name, String value) {
+ if (GROUP_ROTATION.equals(name)) {
+ mRotate = Float.parseFloat(value);
+ } else if (GROUP_PIVOTX.equals(name)) {
+ mPivotX = Float.parseFloat(value);
+ } else if (GROUP_PIVOTY.equals(name)) {
+ mPivotY = Float.parseFloat(value);
+ } else if (GROUP_TRANSLATEX.equals(name)) {
+ mTranslateX = Float.parseFloat(value);
+ } else if (GROUP_TRANSLATEY.equals(name)) {
+ mTranslateY = Float.parseFloat(value);
+ } else if (GROUP_SCALEX.equals(name)) {
+ mScaleX = Float.parseFloat(value);
+ } else if (GROUP_SCALEY.equals(name)) {
+ mScaleY = Float.parseFloat(value);
+ } else if (GROUP_NAME.equals(name)) {
+ mName = value;
+ } else {
+ logger.log(Level.WARNING, ">>>>>> DID NOT UNDERSTAND ! \"" + name + "\" <<<<");
+ }
+ }
+
+ @Override
+ public void parseAttributes(NamedNodeMap attributes) {
+ int len = attributes.getLength();
+ for (int i = 0; i < len; i++) {
+ String name = attributes.item(i).getNodeName();
+ String value = attributes.item(i).getNodeValue();
+ setNameValue(name, value);
+ }
+
+ updateLocalMatrix();
+ }
+
+ @Override
+ public boolean isGroup() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder pathInfo = new StringBuilder();
+ pathInfo.append("Group:");
+ pathInfo.append(" Name: " + mName);
+ pathInfo.append(" mTranslateX: " + mTranslateX);
+ pathInfo.append(" mTranslateY:" + mTranslateY);
+ pathInfo.append(" mScaleX:" + mScaleX);
+ pathInfo.append(" mScaleY:" + mScaleY);
+ pathInfo.append(" mPivotX:" + mPivotX);
+ pathInfo.append(" mPivotY:" + mPivotY);
+ pathInfo.append(" mRotate:" + mRotate);
+
+ return pathInfo.toString();
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdIcon.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdIcon.java
new file mode 100644
index 0000000..aaaceb8
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdIcon.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.ide.common.util.AssetUtil;
+
+import javax.swing.*;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * VdIcon wrap every vector drawable from Material Library into an icon. All of them are shown in a
+ * table for developer to pick.
+ */
+public class VdIcon implements Icon, Comparable<VdIcon> {
+
+ private VdTree mVdTree;
+
+ private final String mName;
+
+ private final URL mUrl;
+
+ private boolean mDrawCheckerBoardBackground;
+
+ private Rectangle myRectangle = new Rectangle();
+
+ private static final Color CHECKER_COLOR = new Color(238, 238, 238);
+
+ public VdIcon(URL url) {
+ mVdTree = parseVdTree(url);
+ mUrl = url;
+ String fileName = url.getFile();
+ mName = fileName.substring(fileName.lastIndexOf("/") + 1);
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public URL getURL() {
+ return mUrl;
+ }
+
+ private VdTree parseVdTree(URL url) {
+ final VdParser p = new VdParser();
+ VdTree result = null;
+ try {
+ result = p.parse(url.openStream(), null);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * TODO: Merge this code back with GraphicsUtil in idea.
+ * Paints a checkered board style background. Each grid square is {@code cellSize} pixels.
+ */
+ public static void paintCheckeredBackground(Graphics g, Color backgroundColor,
+ Color checkeredColor, Shape clip, int cellSize) {
+ final Shape savedClip = g.getClip();
+ ((Graphics2D)g).clip(clip);
+
+ final Rectangle rect = clip.getBounds();
+ g.setColor(backgroundColor);
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ g.setColor(checkeredColor);
+ for (int dy = 0; dy * cellSize < rect.height; dy++) {
+ for (int dx = dy % 2; dx * cellSize < rect.width; dx += 2) {
+ g.fillRect(rect.x + dx * cellSize, rect.y + dy * cellSize, cellSize, cellSize);
+ }
+ }
+
+ g.setClip(savedClip);
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ // Draw the checker board first, even when the tree is empty.
+ myRectangle.setBounds(0, 0, c.getWidth(), c.getHeight());
+ if (mDrawCheckerBoardBackground) {
+ paintCheckeredBackground(g, Color.LIGHT_GRAY, CHECKER_COLOR, myRectangle, 8);
+ }
+
+ if (mVdTree == null) {
+ return;
+ }
+ int minSize = Math.min(c.getWidth(), c.getHeight());
+ final BufferedImage image = AssetUtil.newArgbBufferedImage(minSize, minSize);
+ mVdTree.drawIntoImage(image);
+
+ // Draw in the center of the component.
+ Rectangle rect = new Rectangle(0, 0, c.getWidth(), c.getHeight());
+ AssetUtil.drawCenterInside((Graphics2D) g, image, rect);
+ }
+
+ @Override
+ public int getIconWidth() {
+ return (int) (mVdTree != null ? mVdTree.getPortWidth() : 0);
+ }
+
+ @Override
+ public int getIconHeight() {
+ return (int) (mVdTree != null ? mVdTree.getPortHeight() : 0);
+ }
+
+ @Override
+ public int compareTo(VdIcon other) {
+ return mName.compareTo(other.mName);
+ }
+
+ public void enableCheckerBoardBackground(boolean enable) {
+ mDrawCheckerBoardBackground = enable;
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdNodeRender.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdNodeRender.java
new file mode 100644
index 0000000..4e6f1d8
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdNodeRender.java
@@ -0,0 +1,403 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import java.awt.geom.Path2D;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Given an array of VdPath.Node, generate a Path2D object.
+ * In another word, this is the engine which converts the pathData into
+ * a Path2D object, which is able to draw on Swing components.
+ * The logic and math here are the same as PathParser.java in framework.
+ */
+class VdNodeRender {
+ private static Logger logger = Logger.getLogger(VdNodeRender.class
+ .getSimpleName());
+
+ public static void creatPath(VdPath.Node[] node, Path2D path) {
+ float[] current = new float[6];
+ char lastCmd = ' ';
+ for (int i = 0; i < node.length; i++) {
+ addCommand(path, current, node[i].getType(), lastCmd,node[i].getmParams());
+ lastCmd = node[i].getType();
+ }
+ }
+
+ private static void addCommand(Path2D path, float[] current, char cmd,
+ char lastCmd, float[] val) {
+
+ int incr = 2;
+
+ float cx = current[0];
+ float cy = current[1];
+ float cpx = current[2];
+ float cpy = current[3];
+ float loopX = current[4];
+ float loopY = current[5];
+
+ switch (cmd) {
+ case 'z':
+ case 'Z':
+ path.closePath();
+ cx = loopX;
+ cy = loopY;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ incr = 2;
+ break;
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ incr = 1;
+ break;
+ case 'c':
+ case 'C':
+ incr = 6;
+ break;
+ case 's':
+ case 'S':
+ case 'q':
+ case 'Q':
+ incr = 4;
+ break;
+ case 'a':
+ case 'A':
+ incr = 7;
+ }
+
+ for (int k = 0; k < val.length; k += incr) {
+ boolean reflectCtrl = false;
+ float tempReflectedX, tempReflectedY;
+
+ switch (cmd) {
+ case 'm':
+ cx += val[k + 0];
+ cy += val[k + 1];
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.lineTo(cx, cy);
+ } else {
+ path.moveTo(cx, cy);
+ loopX = cx;
+ loopY = cy;
+ }
+ break;
+ case 'M':
+ cx = val[k + 0];
+ cy = val[k + 1];
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.lineTo(cx, cy);
+ } else {
+ path.moveTo(cx, cy);
+ loopX = cx;
+ loopY = cy;
+ }
+ break;
+ case 'l':
+ cx += val[k + 0];
+ cy += val[k + 1];
+ path.lineTo(cx, cy);
+ break;
+ case 'L':
+ cx = val[k + 0];
+ cy = val[k + 1];
+ path.lineTo(cx, cy);
+ break;
+ case 'z':
+ case 'Z':
+ path.closePath();
+ cx = loopX;
+ cy = loopY;
+ break;
+ case 'h':
+ cx += val[k + 0];
+ path.lineTo(cx, cy);
+ break;
+ case 'H':
+ path.lineTo(val[k + 0], cy);
+ cx = val[k + 0];
+ break;
+ case 'v':
+ cy += val[k + 0];
+ path.lineTo(cx, cy);
+ break;
+ case 'V':
+ path.lineTo(cx, val[k + 0]);
+ cy = val[k + 0];
+ break;
+ case 'c':
+ path.curveTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
+ cy + val[k + 3], cx + val[k + 4], cy + val[k + 5]);
+ cpx = cx + val[k + 2];
+ cpy = cy + val[k + 3];
+ cx += val[k + 4];
+ cy += val[k + 5];
+ break;
+ case 'C':
+ path.curveTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+ cx = val[k + 4];
+ cy = val[k + 5];
+ cpx = val[k + 2];
+ cpy = val[k + 3];
+ break;
+ case 's':
+ reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' || lastCmd == 'S');
+ path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
+ * cy - cpy : cy, cx + val[k + 0], cy + val[k + 1], cx
+ + val[k + 2], cy + val[k + 3]);
+
+ cpx = cx + val[k + 0];
+ cpy = cy + val[k + 1];
+ cx += val[k + 2];
+ cy += val[k + 3];
+ break;
+ case 'S':
+ reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' || lastCmd == 'S');
+ path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
+ * cy - cpy : cy, val[k + 0], val[k + 1], val[k + 2],
+ val[k + 3]);
+ cpx = (val[k + 0]);
+ cpy = (val[k + 1]);
+ cx = val[k + 2];
+ cy = val[k + 3];
+ break;
+ case 'q':
+ path.quadTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
+ cy + val[k + 3]);
+ cpx = cx + val[k + 0];
+ cpy = cy + val[k + 1];
+ // Note that we have to update cpx first, since cx will be updated here.
+ cx += val[k + 2];
+ cy += val[k + 3];
+ break;
+ case 'Q':
+ path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ cx = val[k + 2];
+ cy = val[k + 3];
+ cpx = val[k + 0];
+ cpy = val[k + 1];
+ break;
+ case 't':
+ reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' || lastCmd == 'T');
+ tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
+ tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
+ path.quadTo(tempReflectedX, tempReflectedY, cx + val[k + 0], cy + val[k + 1]);
+ cpx = tempReflectedX;
+ cpy = tempReflectedY;
+ cx += val[k + 0];
+ cy += val[k + 1];
+ break;
+ case 'T':
+ reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' || lastCmd == 'T');
+ tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
+ tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
+ path.quadTo(tempReflectedX, tempReflectedY, val[k + 0], val[k + 1]);
+ cx = val[k + 0];
+ cy = val[k + 1];
+ cpx = tempReflectedX;
+ cpy = tempReflectedY;
+ break;
+ case 'a':
+ // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+ drawArc(path, cx, cy, val[k + 5] + cx, val[k + 6] + cy,
+ val[k + 0], val[k + 1], val[k + 2], val[k + 3] != 0,
+ val[k + 4] != 0);
+ cx += val[k + 5];
+ cy += val[k + 6];
+ cpx = cx;
+ cpy = cy;
+
+ break;
+ case 'A':
+ drawArc(path, cx, cy, val[k + 5], val[k + 6], val[k + 0],
+ val[k + 1], val[k + 2], val[k + 3] != 0,
+ val[k + 4] != 0);
+ cx = val[k + 5];
+ cy = val[k + 6];
+ cpx = cx;
+ cpy = cy;
+ break;
+
+ }
+ lastCmd = cmd;
+ }
+ current[0] = cx;
+ current[1] = cy;
+ current[2] = cpx;
+ current[3] = cpy;
+ current[4] = loopX;
+ current[5] = loopY;
+
+ }
+
+ private static void drawArc(Path2D p, float x0, float y0, float x1,
+ float y1, float a, float b, float theta, boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+
+ logger.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1
+ + ") {" + a + " " + b + "}");
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = theta * Math.PI / 180.0f;
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+ logger.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p
+ + "," + y1p + ")");
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ logger.log(Level.FINE, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ logger.log(Level.FINE, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta,
+ isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+ logger.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , "
+ + (x0p - cx) + ") = " + Math.toDegrees(eta0));
+
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+ logger.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , "
+ + (x1p - cx) + ") = " + Math.toDegrees(eta1));
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+ logger.log(
+ Level.FINE,
+ "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , "
+ + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0
+ + " , " + Math.toDegrees(thetaD) + " , "
+ + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep));
+
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with the horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(Path2D p, double cx, double cy, double a,
+ double b, double e1x, double e1y, double theta, double start,
+ double sweep) {
+ // Taken from equations at:
+ // http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+
+
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2)
+ - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2)
+ + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha = Math.sin(eta2 - eta1)
+ * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+
+ p.curveTo((float) q1x, (float) q1y, (float) q2x, (float) q2y,
+ (float) e2x, (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ }
+
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdOverrideInfo.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdOverrideInfo.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdOverrideInfo.java
rename to sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdOverrideInfo.java
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdParser.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdParser.java
new file mode 100644
index 0000000..37f1918
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdParser.java
@@ -0,0 +1,64 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import com.android.utils.PositionXmlParser;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.ParserConfigurationException;
+/**
+ * Parse a VectorDrawble's XML file, and generate an internal tree representation,
+ * which can be used for drawing / previewing.
+ */
+class VdParser {
+ private static Logger logger = Logger.getLogger(VdParser.class.getSimpleName());
+
+ // Note that the incoming file is the VectorDrawable's XML file, not the SVG.
+ @Nullable
+ public VdTree parse(@NonNull InputStream is, @Nullable StringBuilder vdErrorLog) {
+ final VdTree tree = new VdTree();
+ try {
+ Document doc = PositionXmlParser.parse(is, false);
+ tree.parse(doc);
+ }
+ catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ }
+ catch (SAXException e) {
+ e.printStackTrace();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ return tree;
+ }
+
+ public VdTree parse(URL r, StringBuilder vdErrorLog) throws IOException {
+ return parse(r.openStream(), vdErrorLog);
+ }
+
+
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPath.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPath.java
new file mode 100644
index 0000000..ff449a5
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPath.java
@@ -0,0 +1,588 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.google.common.collect.ImmutableMap;
+import org.w3c.dom.NamedNodeMap;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Used to represent one VectorDrawble's path element.
+ */
+class VdPath extends VdElement{
+ private static Logger logger = Logger.getLogger(VdPath.class.getSimpleName());
+
+ private static final String PATH_ID = "android:name";
+ private static final String PATH_DESCRIPTION = "android:pathData";
+ private static final String PATH_FILL = "android:fillColor";
+ private static final String PATH_FILL_OPACTIY = "android:fillAlpha";
+ private static final String PATH_STROKE = "android:strokeColor";
+ private static final String PATH_STROKE_OPACTIY = "android:strokeAlpha";
+
+ private static final String PATH_STROKE_WIDTH = "android:strokeWidth";
+ private static final String PATH_TRIM_START = "android:trimPathStart";
+ private static final String PATH_TRIM_END = "android:trimPathEnd";
+ private static final String PATH_TRIM_OFFSET = "android:trimPathOffset";
+ private static final String PATH_STROKE_LINECAP = "android:strokeLineCap";
+ private static final String PATH_STROKE_LINEJOIN = "android:strokeLineJoin";
+ private static final String PATH_STROKE_MITERLIMIT = "android:strokeMiterLimit";
+ private static final String PATH_CLIP = "android:clipToPath";
+ private static final String LINECAP_BUTT = "butt";
+ private static final String LINECAP_ROUND = "round";
+ private static final String LINECAP_SQUARE = "square";
+ private static final String LINEJOIN_MITER = "miter";
+ private static final String LINEJOIN_ROUND = "round";
+ private static final String LINEJOIN_BEVEL = "bevel";
+
+ private Node[] mNodeList = null;
+ private int mStrokeColor = 0;
+ private int mFillColor = 0;
+ private float mStrokeWidth = 0;
+ private int mStrokeLineCap = 0;
+ private int mStrokeLineJoin = 0;
+ private float mStrokeMiterlimit = 4;
+ private float mStrokeAlpha = 1.0f;
+ private float mFillAlpha = 1.0f;
+ // TODO: support trim path.
+ private float mTrimPathStart = 0;
+ private float mTrimPathEnd = 1;
+ private float mTrimPathOffset = 0;
+
+ public void toPath(Path2D path) {
+ path.reset();
+ if (mNodeList != null) {
+ VdNodeRender.creatPath(mNodeList, path);
+ }
+ }
+
+ /**
+ * Represent one segment of the path data. Like "l 0,0 1,1"
+ */
+ public static class Node {
+ private char mType;
+ private float[] mParams;
+
+ public char getType() {
+ return mType;
+ }
+
+ public float[] getmParams() {
+ return mParams;
+ }
+
+ public Node(char type, float[] params) {
+ this.mType = type;
+ this.mParams = params;
+ }
+
+ public Node(Node n) {
+ this.mType = n.mType;
+ this.mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+ }
+
+ public static boolean hasRelMoveAfterClose(Node[] nodes) {
+ char preType = ' ';
+ for (Node n : nodes) {
+ if ((preType == 'z' || preType == 'Z') && n.mType == 'm') {
+ return true;
+ }
+ preType = n.mType;
+ }
+ return false;
+ }
+
+ public static String NodeListToString(Node[] nodes, String decimalPlaceString) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < nodes.length; i++) {
+ Node n = nodes[i];
+ stringBuilder.append(n.mType);
+ int len = n.mParams.length;
+ boolean implicitLineTo = false;
+ char lineToType = ' ';
+ if ((n.mType == 'm' || n.mType == 'M') && len > 2) {
+ implicitLineTo = true;
+ lineToType = n.mType == 'm' ? 'l' : 'L';
+ }
+ for (int j = 0; j < len; j++) {
+ if (j > 0) {
+ stringBuilder.append(((j & 1) == 1) ? "," : " ");
+ }
+ if (implicitLineTo && j == 2) {
+ stringBuilder.append(lineToType);
+ }
+ // To avoid trailing zeros like 17.0, use this trick
+ float value = n.mParams[j];
+ if (value == (long) value) {
+ stringBuilder.append(String.valueOf((long) value));
+ } else {
+ DecimalFormatSymbols fractionSeparator = new DecimalFormatSymbols();
+ fractionSeparator.setDecimalSeparator('.');
+ DecimalFormat df = new DecimalFormat(decimalPlaceString, fractionSeparator);
+ df.setRoundingMode(RoundingMode.HALF_UP);
+ stringBuilder.append(df.format(value));
+ }
+
+ }
+ }
+
+ return stringBuilder.toString();
+ }
+
+ private static final char INIT_TYPE = ' ';
+ public static void transform(AffineTransform totalTransform,
+ Node[] nodes) {
+ Point2D.Float currentPoint = new Point2D.Float();
+ Point2D.Float currentSegmentStartPoint = new Point2D.Float();
+ char previousType = INIT_TYPE;
+ for (int i = 0; i < nodes.length; i++) {
+ nodes[i].transform(totalTransform, currentPoint, currentSegmentStartPoint, previousType);
+ previousType= nodes[i].mType;
+ }
+ }
+
+ private static final ImmutableMap<Character, Integer> commandStepMap =
+ ImmutableMap.<Character, Integer>builder()
+ .put('z', 2)
+ .put('Z', 2)
+ .put('m', 2)
+ .put('M', 2)
+ .put('l', 2)
+ .put('L', 2)
+ .put('t', 2)
+ .put('T', 2)
+ .put('h', 1)
+ .put('H', 1)
+ .put('v', 1)
+ .put('V', 1)
+ .put('c', 6)
+ .put('C', 6)
+ .put('s', 4)
+ .put('S', 4)
+ .put('q', 4)
+ .put('Q', 4)
+ .put('a', 7)
+ .put('A', 7)
+ .build();
+
+ private void transform(AffineTransform totalTransform, Point2D.Float currentPoint,
+ Point2D.Float currentSegmentStartPoint, char previousType) {
+ // For Horizontal / Vertical lines, we have to convert to LineTo with 2 parameters
+ // And for arcTo, we also need to isolate the parameters for transformation.
+ // Therefore a looping will be necessary for such commands.
+ //
+ // Note that if the matrix is translation only, then we can save many computations.
+
+ int paramsLen = mParams.length;
+ float[] tempParams = new float[2 * paramsLen];
+ // These has to be pre-transformed value. In another word, the same as it is
+ // in the pathData.
+ float currentX = currentPoint.x;
+ float currentY = currentPoint.y;
+ float currentSegmentStartX = currentSegmentStartPoint.x;
+ float currentSegmentStartY = currentSegmentStartPoint.y;
+
+ int step = commandStepMap.get(mType);
+ switch (mType) {
+ case 'z':
+ case 'Z':
+ currentX = currentSegmentStartX;
+ currentY = currentSegmentStartY;
+ break;
+ case 'M':
+ case 'L':
+ case 'T':
+ case 'C':
+ case 'S':
+ case 'Q':
+ currentX = mParams[paramsLen - 2];
+ currentY = mParams[paramsLen - 1];
+ if (mType == 'M') {
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
+
+ totalTransform.transform(mParams, 0, mParams, 0, paramsLen / 2);
+ break;
+ case 'm':
+ // We also need to workaround a bug in API 21 that 'm' after 'z'
+ // is not picking up the relative value correctly.
+ if (previousType == 'z' || previousType == 'Z') {
+ mType = 'M';
+ mParams[0] += currentSegmentStartX;
+ mParams[1] += currentSegmentStartY;
+ currentSegmentStartX = mParams[0];
+ currentSegmentStartY = mParams[1];
+ for (int i = 1; i < paramsLen / step; i++) {
+ mParams[i * step + 0] += mParams[(i - 1) * step + 0];
+ mParams[i * step + 1] += mParams[(i - 1) * step + 1];
+ }
+ currentX = mParams[paramsLen - 2];
+ currentY = mParams[paramsLen - 1];
+
+ totalTransform.transform(mParams, 0, mParams, 0, paramsLen / 2);
+ } else {
+
+ // We need to handle the initial 'm' similar to 'M' for first pair.
+ // Then all the following numbers are handled as 'l'
+ int startIndex = 0;
+ if (previousType == INIT_TYPE) {
+ int paramsLenInitialM = 2;
+ currentX = mParams[paramsLenInitialM - 2];
+ currentY = mParams[paramsLenInitialM - 1];
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+
+ totalTransform.transform(mParams, 0, mParams, 0, paramsLenInitialM / 2);
+ startIndex = 1;
+ }
+ for (int i = startIndex; i < paramsLen / step; i++) {
+ int indexX = i * step + (step - 2);
+ int indexY = i * step + (step - 1);
+ currentX += mParams[indexX];
+ currentY += mParams[indexY];
+ }
+
+ if (!isTranslationOnly(totalTransform)) {
+ deltaTransform(totalTransform, mParams, 2 * startIndex,
+ paramsLen - 2 * startIndex);
+ }
+ }
+
+ break;
+ case 'l':
+ case 't':
+ case 'c':
+ case 's':
+ case 'q':
+ for (int i = 0; i < paramsLen / step; i ++) {
+ int indexX = i * step + (step - 2);
+ int indexY = i * step + (step - 1);
+ currentX += mParams[indexX];
+ currentY += mParams[indexY];
+ }
+ if (!isTranslationOnly(totalTransform)) {
+ deltaTransform(totalTransform, mParams, 0, paramsLen);
+ }
+ break;
+ case 'H':
+ mType = 'L';
+ for (int i = 0; i < paramsLen; i ++) {
+ tempParams[i * 2 + 0] = mParams[i];
+ tempParams[i * 2 + 1] = currentY;
+ currentX = mParams[i];
+ }
+ totalTransform.transform(tempParams, 0, tempParams, 0, paramsLen /*points*/);
+ mParams = tempParams;
+ break;
+ case 'V':
+ mType = 'L';
+ for (int i = 0; i < paramsLen; i ++) {
+ tempParams[i * 2 + 0] = currentX;
+ tempParams[i * 2 + 1] = mParams[i];
+ currentY = mParams[i];
+ }
+ totalTransform.transform(tempParams, 0, tempParams, 0, paramsLen /*points*/);
+ mParams = tempParams;
+ break;
+ case 'h':
+ for (int i = 0; i < paramsLen; i ++) {
+ // tempParams may not be used, but I would rather merge the code here.
+ tempParams[i * 2 + 0] = mParams[i];
+ currentX += mParams[i];
+ tempParams[i * 2 + 1] = 0;
+ }
+ if (!isTranslationOnly(totalTransform)) {
+ mType = 'l';
+ deltaTransform(totalTransform, tempParams, 0, 2 * paramsLen);
+ mParams = tempParams;
+ }
+ break;
+ case 'v':
+ for (int i = 0; i < paramsLen; i++) {
+ // tempParams may not be used, but I would rather merge the code here.
+ tempParams[i * 2 + 0] = 0;
+ tempParams[i * 2 + 1] = mParams[i];
+ currentY += mParams[i];
+ }
+
+ if (!isTranslationOnly(totalTransform)) {
+ mType = 'l';
+ deltaTransform(totalTransform, tempParams, 0, 2 * paramsLen);
+ mParams = tempParams;
+ }
+ break;
+ case 'A':
+ for (int i = 0; i < paramsLen / step; i ++) {
+ // (0:rx 1:ry 2:x-axis-rotation 3:large-arc-flag 4:sweep-flag 5:x 6:y)
+ // [0, 1, 2]
+ if (!isTranslationOnly(totalTransform)) {
+ EllipseSolver ellipseSolver = new EllipseSolver(totalTransform,
+ currentX, currentY,
+ mParams[i * step + 0], mParams[i * step + 1], mParams[i * step + 2],
+ mParams[i * step + 3], mParams[i * step + 4],
+ mParams[i * step + 5], mParams[i * step + 6]);
+ mParams[i * step + 0] = ellipseSolver.getMajorAxis();
+ mParams[i * step + 1] = ellipseSolver.getMinorAxis();
+ mParams[i * step + 2] = ellipseSolver.getRotationDegree();
+ if (ellipseSolver.getDirectionChanged()) {
+ mParams[i * step + 4] = 1 - mParams[i * step + 4];
+ }
+ } else {
+ // No need to change the value of rx , ry, rotation, and flags.
+ }
+ // [5, 6]
+ currentX = mParams[i * step + 5];
+ currentY = mParams[i * step + 6];
+
+ totalTransform.transform(mParams, i * step + 5, mParams, i * step + 5, 1 /*1 point only*/);
+ }
+ break;
+ case 'a':
+ for (int i = 0; i < paramsLen / step; i ++) {
+ float oldCurrentX = currentX;
+ float oldCurrentY = currentY;
+
+ currentX += mParams[i * step + 5];
+ currentY += mParams[i * step + 6];
+ if (!isTranslationOnly(totalTransform)) {
+ EllipseSolver ellipseSolver = new EllipseSolver(totalTransform,
+ oldCurrentX, oldCurrentY,
+ mParams[i * step + 0], mParams[i * step + 1], mParams[i * step + 2],
+ mParams[i * step + 3], mParams[i * step + 4],
+ oldCurrentX + mParams[i * step + 5],
+ oldCurrentY + mParams[i * step + 6]);
+ // (0:rx 1:ry 2:x-axis-rotation 3:large-arc-flag 4:sweep-flag 5:x 6:y)
+ // [5, 6]
+ deltaTransform(totalTransform, mParams, i * step + 5, 2);
+ // [0, 1, 2]
+ mParams[i * step + 0] = ellipseSolver.getMajorAxis();
+ mParams[i * step + 1] = ellipseSolver.getMinorAxis();
+ mParams[i * step + 2] = ellipseSolver.getRotationDegree();
+ if (ellipseSolver.getDirectionChanged()) {
+ mParams[i * step + 4] = 1 - mParams[i * step + 4];
+ }
+ }
+
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Type is not right!!!");
+ }
+ currentPoint.setLocation(currentX, currentY);
+ currentSegmentStartPoint.setLocation(currentSegmentStartX, currentSegmentStartY);
+ return;
+ }
+
+ private boolean isTranslationOnly(AffineTransform totalTransform) {
+ int type = totalTransform.getType();
+ if (type == AffineTransform.TYPE_IDENTITY || type == AffineTransform.TYPE_TRANSLATION) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Convert the <code>tempParams</code> into a double array, then apply the
+ * delta transform and convert it back to float array.
+ * @params: offset in number of floats, not points.
+ * @params: paramsLen in number of floats, not points.
+ */
+ private void deltaTransform(AffineTransform totalTransform, float[] tempParams, int offset, int paramsLen) {
+ double[] doubleArray = new double[paramsLen];
+ for (int i = 0; i < paramsLen; i++)
+ {
+ doubleArray[i] = (double) tempParams[i + offset];
+ }
+
+ totalTransform.deltaTransform(doubleArray, 0, doubleArray, 0, paramsLen / 2);
+
+ for (int i = 0; i < paramsLen; i++)
+ {
+ tempParams[i + offset] = (float) doubleArray[i];
+ }
+ }
+ }
+
+ /**
+ * @return color value in #AARRGGBB format.
+ */
+ private int calculateColor(String value) {
+ int len = value.length();
+ int ret;
+ int k = 0;
+ switch (len) {
+ case 7: // #RRGGBB
+ ret = (int) Long.parseLong(value.substring(1), 16);
+ ret |= 0xFF000000;
+ break;
+ case 9: // #AARRGGBB
+ ret = (int) Long.parseLong(value.substring(1), 16);
+ break;
+ case 4: // #RGB
+ ret = (int) Long.parseLong(value.substring(1), 16);
+
+ k |= ((ret >> 8) & 0xF) * 0x110000;
+ k |= ((ret >> 4) & 0xF) * 0x1100;
+ k |= ((ret) & 0xF) * 0x11;
+ ret = k | 0xFF000000;
+ break;
+ case 5: // #ARGB
+ ret = (int) Long.parseLong(value.substring(1), 16);
+ k |= ((ret >> 12) & 0xF) * 0x11000000;
+ k |= ((ret >> 8) & 0xF) * 0x110000;
+ k |= ((ret >> 4) & 0xF) * 0x1100;
+ k |= ((ret) & 0xF) * 0x11;
+ ret = k;
+ break;
+ default:
+ return 0xFF000000;
+ }
+ return ret;
+ }
+
+ private void setNameValue(String name, String value) {
+ if (PATH_DESCRIPTION.equals(name)) {
+ mNodeList = PathParser.parsePath(value);
+ } else if (PATH_ID.equals(name)) {
+ mName = value;
+ } else if (PATH_FILL.equals(name)) {
+ mFillColor = calculateColor(value);
+ } else if (PATH_STROKE.equals(name)) {
+ mStrokeColor = calculateColor(value);
+ } else if (PATH_FILL_OPACTIY.equals(name)) {
+ mFillAlpha = Float.parseFloat(value);
+ } else if (PATH_STROKE_OPACTIY.equals(name)) {
+ mStrokeAlpha = Float.parseFloat(value);
+ } else if (PATH_STROKE_WIDTH.equals(name)) {
+ mStrokeWidth = Float.parseFloat(value);
+ } else if (PATH_TRIM_START.equals(name)) {
+ mTrimPathStart = Float.parseFloat(value);
+ } else if (PATH_TRIM_END.equals(name)) {
+ mTrimPathEnd = Float.parseFloat(value);
+ } else if (PATH_TRIM_OFFSET.equals(name)) {
+ mTrimPathOffset = Float.parseFloat(value);
+ } else if (PATH_STROKE_LINECAP.equals(name)) {
+ if (LINECAP_BUTT.equals(value)) {
+ mStrokeLineCap = 0;
+ } else if (LINECAP_ROUND.equals(value)) {
+ mStrokeLineCap = 1;
+ } else if (LINECAP_SQUARE.equals(value)) {
+ mStrokeLineCap = 2;
+ }
+ } else if (PATH_STROKE_LINEJOIN.equals(name)) {
+ if (LINEJOIN_MITER.equals(value)) {
+ mStrokeLineJoin = 0;
+ } else if (LINEJOIN_ROUND.equals(value)) {
+ mStrokeLineJoin = 1;
+ } else if (LINEJOIN_BEVEL.equals(value)) {
+ mStrokeLineJoin = 2;
+ }
+ } else if (PATH_STROKE_MITERLIMIT.equals(name)) {
+ mStrokeMiterlimit = Float.parseFloat(value);
+ } else {
+ logger.log(Level.WARNING, ">>>>>> DID NOT UNDERSTAND ! \"" + name + "\" <<<<");
+ }
+
+ }
+
+ /**
+ * Multiply the <code>alpha</code> value into the alpha channel <code>color</code>.
+ */
+ private static int applyAlpha(int color, float alpha) {
+ int alphaBytes = (color >> 24) & 0xff;
+ color &= 0x00FFFFFF;
+ color |= ((int) (alphaBytes * alpha)) << 24;
+ return color;
+ }
+
+ /**
+ * Draw the current path
+ */
+ @Override
+ public void draw(Graphics2D g, AffineTransform currentMatrix, float scaleX, float scaleY) {
+
+ Path2D path2d = new Path2D.Double();
+ toPath(path2d);
+
+ // SWing operate the matrix is using pre-concatenate by default.
+ // Below is how this is handled in Android framework.
+ // pathMatrix.set(groupStackedMatrix);
+ // pathMatrix.postScale(scaleX, scaleY);
+ g.setTransform(new AffineTransform());
+ g.scale(scaleX, scaleY);
+ g.transform(currentMatrix);
+
+ // TODO: support clip path here.
+ if (mFillColor != 0) {
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ Color fillColor = new Color(applyAlpha(mFillColor, mFillAlpha), true);
+ g.setColor(fillColor);
+ g.fill(path2d);
+ }
+ if (mStrokeColor != 0) {
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ BasicStroke stroke = new BasicStroke(mStrokeWidth, mStrokeLineCap, mStrokeLineJoin, mStrokeMiterlimit);
+ g.setStroke(stroke);
+ Color strokeColor = new Color(applyAlpha(mStrokeColor, mStrokeAlpha), true);
+ g.setColor(strokeColor);
+ g.draw(path2d);
+ }
+ return;
+ }
+
+ @Override
+ public void parseAttributes(NamedNodeMap attributes) {
+ int len = attributes.getLength();
+ for (int i = 0; i < len; i++) {
+ String name = attributes.item(i).getNodeName();
+ String value = attributes.item(i).getNodeValue();
+ setNameValue(name, value);
+ }
+ }
+
+ @Override
+ public boolean isGroup() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder pathInfo = new StringBuilder();
+ pathInfo.append("Path:");
+ pathInfo.append(" Name: " + mName);
+ pathInfo.append(" Node: " + mNodeList.toString());
+ pathInfo.append(" mFillColor: " + Integer.toHexString(mFillColor));
+ pathInfo.append(" mFillAlpha:" + mFillAlpha);
+ pathInfo.append(" mStrokeColor:" + Integer.toHexString(mStrokeColor));
+ pathInfo.append(" mStrokeWidth:" + mStrokeWidth);
+ pathInfo.append(" mStrokeAlpha:" + mStrokeAlpha);
+
+ return pathInfo.toString();
+
+ }
+
+};
\ No newline at end of file
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPreview.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPreview.java
new file mode 100644
index 0000000..f346cd0
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdPreview.java
@@ -0,0 +1,318 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.util.AssetUtil;
+import com.google.common.base.Charsets;
+import com.sun.org.apache.xml.internal.serialize.OutputFormat;
+import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.Locale;
+
+/**
+ * Generate a Image based on the VectorDrawable's XML content.
+ *
+ * <p>This class also contains a main method, which can be used to preview a vector drawable file.
+ */
+public class VdPreview {
+
+ private static final String ANDROID_ALPHA = "android:alpha";
+ private static final String ANDROID_AUTO_MIRRORED = "android:autoMirrored";
+ private static final String ANDROID_HEIGHT = "android:height";
+ private static final String ANDROID_WIDTH = "android:width";
+ public static final int MAX_PREVIEW_IMAGE_SIZE = 4096;
+ public static final int MIN_PREVIEW_IMAGE_SIZE = 1;
+
+ /**
+ * Parse the VectorDrawable's XML file into a document object.
+ *
+ * @param xmlFileContent the content of the VectorDrawable's XML file.
+ * @param errorLog when errors were found, log them in this builder if it is not null.
+ * @return parsed document or null if errors happened.
+ */
+ @Nullable
+ public static Document parseVdStringIntoDocument(@NonNull String xmlFileContent,
+ @Nullable StringBuilder errorLog) {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db;
+ Document document;
+ try {
+ db = dbf.newDocumentBuilder();
+ document = db.parse(new InputSource(new StringReader(xmlFileContent)));
+ }
+ catch (Exception e) {
+ if (errorLog != null) {
+ errorLog.append("Exception while parsing XML file:\n").append(e.getMessage());
+ }
+ return null;
+ }
+ return document;
+ }
+
+ /**
+ * This encapsulates the information used to determine the preview image size.
+ * The reason we have different ways here is that both Studio UI and build process need
+ * to use this common code path to generate images for vectordrawable.
+ * When {@code mUseWidth} is true, use {@code mImageMaxDimension} as the maximum
+ * dimension value while keeping the aspect ratio.
+ * Otherwise, use {@code mImageScale} to scale the image based on the XML's size information.
+ */
+ public static class TargetSize {
+ private boolean mUseWidth;
+
+ private int mImageMaxDimension;
+ private float mImageScale;
+
+ private TargetSize(boolean useWidth, int imageWidth, float imageScale) {
+ mUseWidth = useWidth;
+ mImageMaxDimension = imageWidth;
+ mImageScale = imageScale;
+ }
+
+ public static TargetSize createSizeFromWidth(int imageWidth) {
+ return new TargetSize(true, imageWidth, 0.0f);
+ }
+
+ public static TargetSize createSizeFromScale(float imageScale) {
+ return new TargetSize(false, 0, imageScale);
+ }
+ }
+
+ /**
+ * Since we allow overriding the vector drawable's size, we also need to keep
+ * the original size and aspect ratio.
+ */
+ public static class SourceSize {
+ public int getHeight() {
+ return mSourceHeight;
+ }
+
+ public int getWidth() {
+ return mSourceWidth;
+ }
+
+ private int mSourceWidth;
+ private int mSourceHeight;
+ }
+
+
+ /**
+ * @return a format object for XML formatting.
+ */
+ @NonNull
+ private static OutputFormat getPrettyPrintFormat() {
+ OutputFormat format = new OutputFormat();
+ format.setLineWidth(120);
+ format.setIndenting(true);
+ format.setIndent(4);
+ format.setEncoding("UTF-8");
+ format.setOmitComments(true);
+ format.setOmitXMLDeclaration(true);
+ return format;
+ }
+
+ /**
+ * Get the vector drawable's original size.
+ */
+ public static SourceSize getVdOriginalSize(@NonNull Document document) {
+ Element root = document.getDocumentElement();
+ SourceSize srcSize = new SourceSize();
+ // Update attributes, note that attributes as width and height are required,
+ // while others are optional.
+ NamedNodeMap attr = root.getAttributes();
+ Node nodeAttr = attr.getNamedItem(ANDROID_WIDTH);
+ assert nodeAttr != null;
+ srcSize.mSourceWidth = parseDimension(0, nodeAttr, false);
+
+ nodeAttr = attr.getNamedItem(ANDROID_HEIGHT);
+ assert nodeAttr != null;
+ srcSize.mSourceHeight = parseDimension(0, nodeAttr, false);
+ return srcSize;
+ }
+
+ /**
+ * The UI can override some properties of the Vector drawable.
+ * In order to override in an uniform way, we re-parse the XML file
+ * and pick the appropriate attributes to override.
+ *
+ * @param document the parsed document of original VectorDrawable's XML file.
+ * @param info incoming override information for VectorDrawable.
+ * @param errorLog log for the parsing errors and warnings.
+ * @return the overridden XML file in one string. If exception happens
+ * or no attributes needs to be overriden, return null.
+ */
+ @Nullable
+ public static String overrideXmlContent(@NonNull Document document,
+ @NonNull VdOverrideInfo info,
+ @Nullable StringBuilder errorLog) {
+ boolean isXmlFileContentChanged = false;
+ Element root = document.getDocumentElement();
+
+ // Update attributes, note that attributes as width and height are required,
+ // while others are optional.
+ NamedNodeMap attr = root.getAttributes();
+ if (info.needsOverrideWidth()) {
+ Node nodeAttr = attr.getNamedItem(ANDROID_WIDTH);
+ int overrideValue = info.getWidth();
+ int originalValue = parseDimension(overrideValue, nodeAttr, true);
+ if (originalValue != overrideValue) {
+ isXmlFileContentChanged = true;
+ }
+ }
+ if (info.needsOverrideHeight()) {
+ Node nodeAttr = attr.getNamedItem(ANDROID_HEIGHT);
+ int overrideValue = info.getHeight();
+ int originalValue = parseDimension(overrideValue, nodeAttr, true);
+ if (originalValue != overrideValue) {
+ isXmlFileContentChanged = true;
+ }
+ }
+ if (info.needsOverrideOpacity()) {
+ Node nodeAttr = attr.getNamedItem(ANDROID_ALPHA);
+ String opacityValue = String.format((Locale) null, "%.2f", info.getOpacity() / 100.0f);
+ if (nodeAttr != null) {
+ nodeAttr.setTextContent(opacityValue);
+ }
+ else {
+ root.setAttribute(ANDROID_ALPHA, opacityValue);
+ }
+ isXmlFileContentChanged = true;
+ }
+
+ // When auto mirror is set to true, then we always need to set it.
+ // Because SVG has no such attribute at all.
+ if (info.needsOverrideAutoMirrored()) {
+ Node nodeAttr = attr.getNamedItem(ANDROID_AUTO_MIRRORED);
+ if (nodeAttr != null) {
+ nodeAttr.setTextContent("true");
+ }
+ else {
+ root.setAttribute(ANDROID_AUTO_MIRRORED, "true");
+ }
+ isXmlFileContentChanged = true;
+ }
+
+ if (isXmlFileContentChanged) {
+ // Prettify the XML string from the document.
+ StringWriter stringOut = new StringWriter();
+ XMLSerializer serial = new XMLSerializer(stringOut, getPrettyPrintFormat());
+ try {
+ serial.serialize(document);
+ }
+ catch (IOException e) {
+ if (errorLog != null) {
+ errorLog.append("Exception while parsing XML file:\n").append(e.getMessage());
+ }
+ }
+ return stringOut.toString();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Query the dimension info and override it if needed.
+ *
+ * @param overrideValue the dimension value to override with.
+ * @param nodeAttr the node who contains dimension info.
+ * @param override if true then override the dimension.
+ * @return the original dimension value.
+ */
+ private static int parseDimension(int overrideValue, Node nodeAttr, boolean override) {
+ assert nodeAttr != null;
+ String content = nodeAttr.getTextContent();
+ assert content.endsWith("dp");
+ int originalValue = Integer.parseInt(content.substring(0, content.length() - 2));
+
+ if (override) {
+ nodeAttr.setTextContent(overrideValue + "dp");
+ }
+ return originalValue;
+ }
+
+ /**
+ * This generates an image according to the VectorDrawable's content {@code xmlFileContent}.
+ * At the same time, {@vdErrorLog} captures all the errors found during parsing.
+ * The size of image is determined by the {@code size}.
+ *
+ * @param targetSize the size of result image.
+ * @param xmlFileContent VectorDrawable's XML file's content.
+ * @param vdErrorLog log for the parsing errors and warnings.
+ * @return an preview image according to the VectorDrawable's XML
+ */
+ @Nullable
+ public static BufferedImage getPreviewFromVectorXml(@NonNull TargetSize targetSize,
+ @Nullable String xmlFileContent,
+ @Nullable StringBuilder vdErrorLog) {
+ if (xmlFileContent == null || xmlFileContent.isEmpty()) {
+ return null;
+ }
+ VdParser p = new VdParser();
+ VdTree vdTree;
+
+ InputStream inputStream = new ByteArrayInputStream(
+ xmlFileContent.getBytes(Charsets.UTF_8));
+ vdTree = p.parse(inputStream, vdErrorLog);
+ if (vdTree == null) {
+ return null;
+ }
+
+ // If the forceImageSize is set (>0), then we honor that.
+ // Otherwise, we will ask the vectorDrawable for the prefer size, then apply the imageScale.
+ float vdWidth = vdTree.getBaseWidth();
+ float vdHeight = vdTree.getBaseHeight();
+ float imageWidth;
+ float imageHeight;
+ int forceImageSize = targetSize.mImageMaxDimension;
+ float imageScale = targetSize.mImageScale;
+
+ if (forceImageSize > 0) {
+ // The goal here is to generate an image within certain size, while keeping the
+ // aspect ration as much as we can.
+ // If it is scaling too much to fit in, we log an error.
+ float maxVdSize = Math.max(vdWidth, vdHeight);
+ float ratioToForceImageSize = forceImageSize / maxVdSize;
+ float scaledWidth = ratioToForceImageSize * vdWidth;
+ float scaledHeight = ratioToForceImageSize * vdHeight;
+ imageWidth = Math.max(MIN_PREVIEW_IMAGE_SIZE, Math.min(MAX_PREVIEW_IMAGE_SIZE, scaledWidth));
+ imageHeight = Math.max(MIN_PREVIEW_IMAGE_SIZE, Math.min(MAX_PREVIEW_IMAGE_SIZE, scaledHeight));
+ if (scaledWidth != imageWidth || scaledHeight != imageHeight) {
+ vdErrorLog.append("Invalid image size, can't fit in a square whose size is" + forceImageSize);
+ }
+ } else {
+ imageWidth = vdWidth * imageScale;
+ imageHeight = vdHeight * imageScale;
+ }
+
+ // Create the image according to the vectorDrawable's aspect ratio.
+
+ BufferedImage image = AssetUtil.newArgbBufferedImage((int)imageWidth, (int)imageHeight);
+ vdTree.drawIntoImage(image);
+ return image;
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdTree.java b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdTree.java
new file mode 100644
index 0000000..67ca82e
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/vectordrawable/VdTree.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.vectordrawable;
+
+import com.android.ide.common.util.AssetUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Used to represent the whole VectorDrawable XML file's tree.
+ */
+class VdTree {
+ private static Logger logger = Logger.getLogger(VdTree.class.getSimpleName());
+
+ private static final String SHAPE_VECTOR = "vector";
+ private static final String SHAPE_PATH = "path";
+ private static final String SHAPE_GROUP = "group";
+
+ private VdGroup mRootGroup = new VdGroup();
+
+ private float mBaseWidth = 1;
+ private float mBaseHeight = 1;
+ private float mPortWidth = 1;
+ private float mPortHeight = 1;
+ private float mRootAlpha = 1;
+
+ private final boolean DBG_PRINT_TREE = false;
+
+ private static final String INDENT = " ";
+
+ /*package*/ float getBaseWidth(){
+ return mBaseWidth;
+ }
+
+ /*package*/ float getBaseHeight(){
+ return mBaseHeight;
+ }
+
+ /*package*/ float getPortWidth(){
+ return mPortWidth;
+ }
+
+ /*package*/ float getPortHeight(){
+ return mPortHeight;
+ }
+
+ private void drawTree(Graphics2D g, int w, int h) {
+ float scaleX = w / mPortWidth;
+ float scaleY = h / mPortHeight;
+
+ AffineTransform rootMatrix = new AffineTransform(); // identity
+
+ mRootGroup.draw(g, rootMatrix, scaleX, scaleY);
+ }
+
+ /**
+ * Draw the VdTree into an image.
+ * If the root alpha is less than 1.0, then draw into a temporary image,
+ * then draw into the result image applying alpha blending.
+ */
+ public void drawIntoImage(BufferedImage image) {
+ Graphics2D gFinal = (Graphics2D) image.getGraphics();
+ int width = image.getWidth();
+ int height = image.getHeight();
+ gFinal.setColor(new Color(255, 255, 255, 0));
+ gFinal.fillRect(0, 0, width, height);
+
+ float rootAlpha = mRootAlpha;
+ if (rootAlpha < 1.0) {
+ BufferedImage alphaImage = AssetUtil.newArgbBufferedImage(width, height);
+ Graphics2D gTemp = (Graphics2D)alphaImage.getGraphics();
+ drawTree(gTemp, width, height);
+ gFinal.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, rootAlpha));
+ gFinal.drawImage(alphaImage, 0, 0, null);
+ gTemp.dispose();
+ } else {
+ drawTree(gFinal, width, height);
+ }
+ gFinal.dispose();
+ }
+
+ public void parse(Document doc) {
+ NodeList rootNodeList = doc.getElementsByTagName(SHAPE_VECTOR);
+ assert rootNodeList.getLength() == 1;
+ Node rootNode = rootNodeList.item(0);
+
+ parseRootNode(rootNode);
+ parseTree(rootNode, mRootGroup);
+
+ if (DBG_PRINT_TREE) {
+ debugPrintTree(0, mRootGroup);
+ }
+ }
+
+ private void parseTree(Node currentNode, VdGroup currentGroup) {
+ NodeList childrenNodes = currentNode.getChildNodes();
+ int length = childrenNodes.getLength();
+ for (int i = 0; i < length; i ++) {
+ Node child = childrenNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ if (SHAPE_GROUP.equals(child.getNodeName())) {
+ VdGroup newGroup = parseGroupAttributes(child.getAttributes());
+ currentGroup.add(newGroup);
+ parseTree(child, newGroup);
+ } else if (SHAPE_PATH.equals(child.getNodeName())) {
+ VdPath newPath = parsePathAttributes(child.getAttributes());
+ currentGroup.add(newPath);
+ }
+ }
+ }
+ }
+
+ private void debugPrintTree(int level, VdGroup mRootGroup) {
+ int len = mRootGroup.size();
+ if (len == 0) {
+ return;
+ }
+ String prefix = "";
+ for (int i = 0; i < level; i ++) {
+ prefix += INDENT;
+ }
+ ArrayList<VdElement> children = mRootGroup.getChildren();
+ for (int i = 0; i < len; i ++) {
+ VdElement child = children.get(i);
+ System.out.println(prefix + child.toString());
+ if (child.isGroup()) {
+ // TODO: print group info
+ debugPrintTree(level + 1, (VdGroup) child);
+ }
+ }
+ }
+
+ private void parseRootNode(Node rootNode) {
+ if (rootNode.hasAttributes()) {
+ parseSize(rootNode.getAttributes());
+ }
+ }
+
+ private void parseSize(NamedNodeMap attributes) {
+
+ Pattern pattern = Pattern.compile("^\\s*(\\d+(\\.\\d+)*)\\s*([a-zA-Z]+)\\s*$");
+
+ int len = attributes.getLength();
+
+ for (int i = 0; i < len; i++) {
+ String name = attributes.item(i).getNodeName();
+ String value = attributes.item(i).getNodeValue();
+ Matcher matcher = pattern.matcher(value);
+ float size = 0;
+ if (matcher.matches()) {
+ float v = Float.parseFloat(matcher.group(1));
+ size = v;
+ }
+
+ // TODO: Extract dimension units like px etc. Right now all are treated as "dp".
+ if ("android:width".equals(name)) {
+ mBaseWidth = size;
+ } else if ("android:height".equals(name)) {
+ mBaseHeight = size;
+ } else if ("android:viewportWidth".equals(name)) {
+ mPortWidth = Float.parseFloat(value);
+ } else if ("android:viewportHeight".equals(name)) {
+ mPortHeight = Float.parseFloat(value);
+ } else if ("android:alpha".equals(name)) {
+ mRootAlpha = Float.parseFloat(value);
+ }
+ }
+ }
+
+ private VdPath parsePathAttributes(NamedNodeMap attributes) {
+ VdPath vgPath = new VdPath();
+ vgPath.parseAttributes(attributes);
+ return vgPath;
+ }
+
+ private VdGroup parseGroupAttributes(NamedNodeMap attributes) {
+ VdGroup vgGroup = new VdGroup();
+ vgGroup.parseAttributes(attributes);
+ return vgGroup;
+ }
+}
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java b/sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java
rename to sdk-common/src/main/java/com/android/ide/common/xml/AndroidManifestParser.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/xml/ManifestData.java b/sdk-common/src/main/java/com/android/ide/common/xml/ManifestData.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/xml/ManifestData.java
rename to sdk-common/src/main/java/com/android/ide/common/xml/ManifestData.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java b/sdk-common/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java
rename to sdk-common/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java b/sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java
rename to sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java b/sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java
rename to sdk-common/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java
diff --git a/base/sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java b/sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java
similarity index 100%
rename from base/sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java
rename to sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java
diff --git a/base/sdk-common/src/test/.classpath b/sdk-common/src/test/.classpath
similarity index 100%
rename from base/sdk-common/src/test/.classpath
rename to sdk-common/src/test/.classpath
diff --git a/base/sdk-common/src/test/.project b/sdk-common/src/test/.project
similarity index 100%
rename from base/sdk-common/src/test/.project
rename to sdk-common/src/test/.project
diff --git a/sdk-common/src/test/java/com/android/ide/common/blame/MergingLogTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/MergingLogTest.java
new file mode 100644
index 0000000..ccdfd4c
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/blame/MergingLogTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.ide.common.blame;
+
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+public class MergingLogTest {
+
+ @Test
+ public void testMergingLog() throws IOException {
+
+ final SourceFilePosition position1 = new SourceFilePosition(
+ new SourceFile(absoluteFile("exploded/a/values/values.xml")),
+ new SourcePosition(7, 8, 20));
+
+ final SourceFilePosition position2 = new SourceFilePosition(
+ new SourceFile(absoluteFile("exploded/b/values/values.xml")),
+ new SourcePosition(2, 3, 14));
+
+ File tempDir = Files.createTempDir();
+ MergingLog mergingLog = new MergingLog(tempDir);
+
+ mergingLog.logCopy(absoluteFile("exploded/layout/a"), absoluteFile("merged/layout/a"));
+ mergingLog.logCopy(absoluteFile("exploded/layout-land/a"),
+ absoluteFile("merged/layout-land/a"));
+
+ Map<SourcePosition, SourceFilePosition> map = Maps.newLinkedHashMap();
+ map.put(new SourcePosition(1, 2, 3, 7, 1, 120), position1);
+ map.put(new SourcePosition(4, 1, 34, 6, 20, 100), position2);
+ mergingLog.logSource(new SourceFile(absoluteFile("merged/values/values.xml")), map);
+
+ Map<SourcePosition, SourceFilePosition> map2 = Maps.newLinkedHashMap();
+ map2.put(
+ new SourcePosition(3, 4, 34),
+ new SourceFilePosition(
+ new SourceFile(absoluteFile("exploded/values-de/values.xml")),
+ new SourcePosition(0, 5, 5)));
+ mergingLog.logSource(new SourceFile(absoluteFile("merged/values-de/values.xml")), map2);
+
+ // Write and then reload (won't load anything immediately).
+ mergingLog.write();
+ mergingLog = new MergingLog(tempDir);
+
+ mergingLog.logRemove(new SourceFile(absoluteFile("merged/layout/a")));
+
+ mergingLog.write();
+ mergingLog = new MergingLog(tempDir);
+
+ Assert.assertEquals("", new SourceFile(absoluteFile("exploded/layout-land/a")),
+ mergingLog.find(new SourceFile(absoluteFile("merged/layout-land/a"))));
+
+
+ /*
+ Test
+ |---search query----|
+ |---------target----------|
+ */
+ Assert.assertEquals("", position2,
+ mergingLog.find(new SourceFilePosition(
+ new SourceFile(absoluteFile("merged/values/values.xml")),
+ new SourcePosition(4, 1, 35, 4, 2, 36))));
+
+ /*
+ Test
+ |---search query----|
+ |------------target-------------|
+ |-----wrong----|
+ */
+ Assert.assertEquals("", position1,
+ mergingLog.find(new SourceFilePosition(
+ new SourceFile(absoluteFile("merged/values/values.xml")),
+ new SourcePosition(5, 20, 35, 6, 25, 105))));
+
+ // Check that an unknown file returns itself.
+ SourceFilePosition noMatch1 = new SourceFilePosition(
+ new SourceFile(absoluteFile("unknownFile")),
+ new SourcePosition(1, 2, 3));
+ Assert.assertEquals("", noMatch1, mergingLog.find(noMatch1));
+
+ // And that a position that is not mapped in the file also returns itself.
+ SourceFilePosition noMatch2 = new SourceFilePosition(
+ new SourceFile(absoluteFile("merged/values/values.xml")),
+ new SourcePosition(100, 0, 3000));
+ Assert.assertEquals("", noMatch2, mergingLog.find(noMatch2));
+
+ mergingLog.write();
+ }
+
+ private File testPath = Files.createTempDir();
+
+ private File absoluteFile(String path) {
+ return new File(testPath, path);
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/blame/MessageJsonSerializerTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/MessageJsonSerializerTest.java
new file mode 100644
index 0000000..667cb37
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/blame/MessageJsonSerializerTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.ide.common.blame;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.base.Optional;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+
+ at RunWith(Enclosed.class)
+public class MessageJsonSerializerTest {
+
+ private static GsonBuilder sGsonBuilder = new GsonBuilder();
+
+ static {
+ MessageJsonSerializer.registerTypeAdapters(sGsonBuilder);
+ }
+
+ @RunWith(Parameterized.class)
+ public static class DeserializeTest {
+
+ private static Gson sGson;
+
+ @Parameterized.Parameter(value = 0)
+ public Message message;
+
+ @Parameterized.Parameter(value = 1)
+ public String serializedMessage;
+
+ @Parameterized.Parameters(name = "fromJson(\"{1}\") should give {0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {new Message(
+ Message.Kind.ERROR,
+ "some error text",
+ new SourceFilePosition(
+ new SourceFile(new File("/path/file.java")),
+ new SourcePosition(1, 3, 5))),
+ "{"
+ + "\"kind\":\"Error\", "
+ + "\"text\":\"some error text\","
+ + "\"sources\": {"
+ + "\"file\":\"/path/file.java\","
+ + "\"position\":{"
+ + "\"startLine\":1,"
+ + "\"startColumn\":3,"
+ + "\"startOffset\":5"
+ + "}"
+ + "}}"
+ }, {
+ new Message(
+ Message.Kind.ERROR,
+ "errorText",
+ "",
+ null,
+ new SourceFilePosition(
+ new File("error/source"),
+ new SourcePosition(1,2,3,4,5,6))
+ ),
+ "{\"kind\":\"ERROR\",\"text\":\"errorText\",\"sourcePath\":\"error/source\","
+ + "\"position\":{\"startLine\":1,\"startColumn\":2,\"startOffset\":3,"
+ + "\"endLine\":4,\"endColumn\":5,\"endOffset\":6},\"original\":\"\"}\n"
+ }, {new Message(Message.Kind.SIMPLE, "something else", new SourceFilePosition(SourceFile.UNKNOWN, SourcePosition.UNKNOWN)),
+ "{\"kind\":\"SIMPLE\","
+ + "\"text\":\"something else\",\"position\":{},\"original\":\"something else\"}"
+ }, {
+ new Message(
+ Message.Kind.SIMPLE,
+ "Warning: AndroidManifest.xml already defines debuggable (in http://"
+ + "schemas.android.com/apk/res/android); using existing value "
+ + "in manifest.",
+ SourceFilePosition.UNKNOWN),
+ "{\"kind\":\"simple\",\"text\":\"Warning: AndroidManifest.xml already defines "
+ + "debuggable (in http://schemas.android.com/apk/res/android); using "
+ + "existing value in manifest.\",\"sources\":\"\"}"
+ }, {
+ new Message(
+ Message.Kind.UNKNOWN,
+ "Text.",
+ SourceFilePosition.UNKNOWN),
+ "{\"text\":\"Text.\"}",
+
+ }});
+ }
+
+ @BeforeClass
+ public static void initGson() {
+ sGson = sGsonBuilder.create();
+ }
+
+ @AfterClass
+ public static void removeGson() {
+ sGson = null;
+ }
+
+ @Test
+ public void check() {
+ assertEquals(message, sGson.fromJson(serializedMessage, Message.class));
+ }
+
+
+ }
+
+ @RunWith(Parameterized.class)
+ public static class RoundTripTest {
+
+ private static Gson sGson;
+
+ @Parameterized.Parameter
+ public Message message;
+
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{{
+ new Message(
+ Message.Kind.ERROR,
+ "some error text",
+ "original error text",
+ "tool name",
+ new SourceFilePosition(
+ new SourceFile(new File("/path/file.java")),
+ new SourcePosition(1, 3, 5)))
+ }, {
+ new Message(
+ Message.Kind.SIMPLE,
+ "something else",
+ new SourceFilePosition(SourceFile.UNKNOWN, SourcePosition.UNKNOWN))
+ }, {
+ new Message(
+ Message.Kind.SIMPLE,
+ "Warning: AndroidManifest.xml already defines debuggable (in http://"
+ + "schemas.android.com/apk/res/android); using existing value "
+ + "in manifest.",
+ SourceFilePosition.UNKNOWN)
+ }});
+ }
+
+ @BeforeClass
+ public static void initGson() {
+ sGson = sGsonBuilder.create();
+ }
+
+ @AfterClass
+ public static void removeGson() {
+ sGson = null;
+ }
+
+ @Test
+ public void check() {
+ assertEquals(message, sGson.fromJson(sGson.toJson(message), Message.class));
+ }
+
+
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/blame/ParsingProcessOutputHandlerTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/ParsingProcessOutputHandlerTest.java
new file mode 100644
index 0000000..5c83ec1
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/blame/ParsingProcessOutputHandlerTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.ide.common.blame;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.blame.parser.ParsingFailedException;
+import com.android.ide.common.blame.parser.PatternAwareOutputParser;
+import com.android.ide.common.blame.parser.ToolOutputParser;
+import com.android.ide.common.blame.parser.util.OutputLineReader;
+import com.android.ide.common.process.ProcessException;
+import com.android.ide.common.process.ProcessOutput;
+import com.android.ide.common.res2.RecordingLogger;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class ParsingProcessOutputHandlerTest {
+
+ private static ParsingProcessOutputHandler sParsingProcessOutputHandler;
+
+ private static RecordingLogger sLogger;
+
+ private static PatternAwareOutputParser sFakePatternParser;
+
+ private static MessageReceiver sMessageReceiver;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ sLogger = new RecordingLogger();
+ sFakePatternParser = new FakePatternAwareOutputParser();
+
+ sMessageReceiver = Mockito.mock(MessageReceiver.class);
+ sParsingProcessOutputHandler = new ParsingProcessOutputHandler(
+ new ToolOutputParser(sFakePatternParser, sLogger),
+ sMessageReceiver);
+ }
+
+ @Test
+ public void testRewriteMessages() throws IOException, ProcessException {
+
+ String original = "error example\ntwo line error\nnext line\nsomething else";
+
+ sParsingProcessOutputHandler.handleOutput(processOutputFromErrorString(original));
+
+ Mockito.verify(sMessageReceiver).receiveMessage(new Message(
+ Message.Kind.ERROR,
+ "errorText",
+ "original_text",
+ null,
+ new SourceFilePosition(
+ new SourceFile(FakePatternAwareOutputParser.ERROR_EXAMPLE_FILE),
+ new SourcePosition(1, 2, 3, 4, 5, 6)
+ )));
+
+ Mockito.verify(sMessageReceiver).receiveMessage(new Message(
+ Message.Kind.WARNING,
+ "two line warning",
+ new SourceFilePosition(
+ new SourceFile(FakePatternAwareOutputParser.TWO_LINE_ERROR_FILE),
+ new SourcePosition(1, 2, -1)
+ )));
+
+
+ Mockito.verify(sMessageReceiver).receiveMessage(new Message(
+ Message.Kind.SIMPLE,
+ "something else",
+ SourceFilePosition.UNKNOWN));
+ Mockito.verifyNoMoreInteractions(sMessageReceiver);
+
+ String expected = "AGPBI: {"
+ + "\"kind\":\"error\","
+ + "\"text\":\"errorText\","
+ + "\"sources\":[{"
+ + "\"file\":\"" + FakePatternAwareOutputParser.ERROR_EXAMPLE_FILE.getAbsolutePath()
+ + "\",\"position\":{\"startLine\":1,\"startColumn\":2,\"startOffset\":3,"
+ + "\"endLine\":4,\"endColumn\":5,\"endOffset\":6}}],"
+ + "\"original\":\"original_text\"}\n"
+ + "AGPBI: {\"kind\":\"warning\","
+ + "\"text\":\"two line warning\","
+ + "\"sources\":[{\"file\":\"" +
+ FakePatternAwareOutputParser.TWO_LINE_ERROR_FILE.getAbsolutePath() + "\","
+ + "\"position\":{\"startLine\":1,\"startColumn\":2}}]}\n"
+ + "AGPBI: {\"kind\":\"simple\","
+ + "\"text\":\"something else\","
+ + "\"sources\":[{}]}";
+ }
+
+ @Test
+ public void parseException() throws IOException, ProcessException {
+ String original = "two line error";
+
+ sParsingProcessOutputHandler.handleOutput(processOutputFromErrorString(original));
+
+ Mockito.verifyNoMoreInteractions(sMessageReceiver);
+ }
+
+ private static ProcessOutput processOutputFromErrorString(String original) throws IOException {
+ ProcessOutput processOutput = sParsingProcessOutputHandler.createOutput();
+ processOutput.getErrorOutput().write(original.getBytes(Charsets.UTF_8));
+ return processOutput;
+ }
+
+ private static class FakePatternAwareOutputParser implements PatternAwareOutputParser {
+ static final File ERROR_EXAMPLE_FILE = new File("error/source");
+ static final File TWO_LINE_ERROR_FILE = new File("error/source/2");
+ @Override
+ public boolean parse(@NonNull String line, @NonNull OutputLineReader reader,
+ @NonNull List<Message> messages, @NonNull ILogger logger)
+ throws ParsingFailedException {
+ if (line.equals("two line error")) {
+ String nextLine = reader.readLine();
+ if ("next line".equals(nextLine)) {
+ messages.add(new Message(
+ Message.Kind.WARNING,
+ "two line warning",
+ "two line warning",
+ null,
+ new SourceFilePosition(
+ TWO_LINE_ERROR_FILE,
+ new SourcePosition(1, 2, -1))));
+ } else {
+ throw new ParsingFailedException();
+ }
+ return true;
+ }
+ if (line.equals("error example")) {
+ messages.add(
+ new Message(
+ Message.Kind.ERROR,
+ "errorText",
+ "original_text",
+ null,
+ new SourceFilePosition(
+ ERROR_EXAMPLE_FILE,
+ new SourcePosition(1, 2, 3, 4, 5, 6))));
+ return true;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/blame/SourceFileJsonTypeAdapterTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/SourceFileJsonTypeAdapterTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/blame/SourceFileJsonTypeAdapterTest.java
rename to sdk-common/src/test/java/com/android/ide/common/blame/SourceFileJsonTypeAdapterTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/blame/SourceFilePositionJsonSerializerTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/SourceFilePositionJsonSerializerTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/blame/SourceFilePositionJsonSerializerTest.java
rename to sdk-common/src/test/java/com/android/ide/common/blame/SourceFilePositionJsonSerializerTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapterTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapterTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapterTest.java
rename to sdk-common/src/test/java/com/android/ide/common/blame/SourcePositionJsonTypeAdapterTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/blame/parser/AaptOutputParserTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/parser/AaptOutputParserTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/blame/parser/AaptOutputParserTest.java
rename to sdk-common/src/test/java/com/android/ide/common/blame/parser/AaptOutputParserTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/blame/parser/DexParserTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/parser/DexParserTest.java
new file mode 100644
index 0000000..ff5c5e0
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/blame/parser/DexParserTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.ide.common.blame.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.utils.StdLogger;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link ToolOutputParser}.
+ */
+public class DexParserTest {
+
+ private static final ToolOutputParser PARSER = new ToolOutputParser(
+ new DexParser(),
+ new StdLogger(StdLogger.Level.VERBOSE));
+
+ @Test
+ public void checkExceptionFormatParsedCorrectly() {
+ String stderr = "\n"
+ + "UNEXPECTED TOP-LEVEL EXCEPTION:\n"
+ + "com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536\n"
+ + "\tat com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:502)\n"
+ + "\tat com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:277)\n"
+ + "\tat com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:491)\n"
+ + "\tat com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:168)\n"
+ + "\tat com.android.dx.merge.DexMerger.merge(DexMerger.java:189)\n"
+ + "\tat com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:502)\n"
+ + "\tat com.android.dx.command.dexer.Main.runMonoDex(Main.java:334)\n"
+ + "\tat com.android.dx.command.dexer.Main.run(Main.java:277)\n"
+ + "\tat com.android.dx.command.dexer.Main.main(Main.java:245)\n"
+ + "\tat com.android.dx.command.Main.main(Main.java:106)\n"
+ + "Caused by: com.example.SomeOtherException: someOtherCause\n"
+ + "\tat com.example.SomeOtherException.throw(SomeOtherException.java:34)\n"
+ + "\tat com.android.dex.merge.DexMerger.merxeMethodIds(DexMerger.java:490)\n"
+ + "\t... 20 more\n\n";
+
+ Message message = Iterables.getOnlyElement(PARSER.parseToolOutput(stderr));
+
+ assertEquals(Message.Kind.ERROR, message.getKind());
+ assertEquals(DexParser.DEX_LIMIT_EXCEEDED_ERROR, message.getText());
+ assertEquals(stderr.trim(), message.getRawMessage().trim());
+ assertEquals(ImmutableList.of(SourceFilePosition.UNKNOWN),
+ message.getSourceFilePositions());
+ assertEquals(Optional.of(DexParser.DEX_TOOL_NAME), message.getToolName());
+ }
+
+ @Test
+ public void checkExplanationParsedCorrectly() {
+ String stderr = "\n"
+ + "trouble writing output: Too many method references: 130016; max is 65536.\n"
+ + "You may try using --multi-dex option.\n"
+ + "References by package:\n"
+ + " 2 android.app\n"
+ + "130002 com.example\n"
+ + " 10 com.example.helloworld\n"
+ + " 2 java.lang\n\n";
+
+ Message message = Iterables.getOnlyElement(PARSER.parseToolOutput(stderr));
+
+ assertEquals(Message.Kind.ERROR, message.getKind());
+ assertTrue(message.getText().startsWith(DexParser.DEX_LIMIT_EXCEEDED_ERROR));
+ assertEquals(stderr.trim(), message.getRawMessage().trim());
+ assertEquals(ImmutableList.of(SourceFilePosition.UNKNOWN),
+ message.getSourceFilePositions());
+ assertEquals(Optional.of(DexParser.DEX_TOOL_NAME), message.getToolName());
+ }
+
+
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/blame/parser/LegacyNdkOutputParserTest.java b/sdk-common/src/test/java/com/android/ide/common/blame/parser/LegacyNdkOutputParserTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/blame/parser/LegacyNdkOutputParserTest.java
rename to sdk-common/src/test/java/com/android/ide/common/blame/parser/LegacyNdkOutputParserTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/build/SplitOutputMatcherTest.java b/sdk-common/src/test/java/com/android/ide/common/build/SplitOutputMatcherTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/build/SplitOutputMatcherTest.java
rename to sdk-common/src/test/java/com/android/ide/common/build/SplitOutputMatcherTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/caching/CreatingCacheTest.java b/sdk-common/src/test/java/com/android/ide/common/caching/CreatingCacheTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/caching/CreatingCacheTest.java
rename to sdk-common/src/test/java/com/android/ide/common/caching/CreatingCacheTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/rendering/HardwareConfigHelperTest.java b/sdk-common/src/test/java/com/android/ide/common/rendering/HardwareConfigHelperTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/rendering/HardwareConfigHelperTest.java
rename to sdk-common/src/test/java/com/android/ide/common/rendering/HardwareConfigHelperTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/rendering/RenderSecurityManagerTest.java b/sdk-common/src/test/java/com/android/ide/common/rendering/RenderSecurityManagerTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/rendering/RenderSecurityManagerTest.java
rename to sdk-common/src/test/java/com/android/ide/common/rendering/RenderSecurityManagerTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java b/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java
new file mode 100644
index 0000000..83572f2
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.repository;
+
+import com.android.ide.common.res2.BaseTestCase;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_HIGHER;
+import static com.android.ide.common.repository.GradleCoordinate.COMPARE_PLUS_LOWER;
+
+/**
+ * Test class for {@see GradleCoordinate}
+ */
+public class GradleCoordinateTest extends BaseTestCase {
+
+ public void testParseCoordinateString() throws Exception {
+ GradleCoordinate expected = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
+ assertNotNull(actual);
+ assertEquals(expected, actual);
+ assertNull(actual.getArtifactType());
+
+ expected = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.+");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
+ assertEquals(expected, actual);
+
+ List<GradleCoordinate.RevisionComponent> revisionList =
+ Lists.<GradleCoordinate.RevisionComponent>newArrayList(GradleCoordinate.PLUS_REV);
+ expected = new GradleCoordinate("a.b.c", "package", revisionList,
+ GradleCoordinate.ArtifactType.JAR);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+ at jar");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package", revisionList,
+ GradleCoordinate.ArtifactType.AAR);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+ at AAR");
+ assertNotNull(actual);
+ assertEquals(expected, actual);
+ assertEquals(GradleCoordinate.ArtifactType.AAR, actual.getArtifactType());
+
+ expected = new GradleCoordinate("a.b.c", "package",
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.StringComponent("v2"));
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1.v2");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package",
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.NumberComponent(1)));
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1-1");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package",
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.NumberComponent(1)),
+ new GradleCoordinate.NumberComponent(17),
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.NumberComponent(0),
+ new GradleCoordinate.StringComponent("rc"),
+ new GradleCoordinate.StringComponent("SNAPSHOT")));
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:v1-1.17.0-rc-SNAPSHOT");
+ assertEquals(expected, actual);
+ assertNotNull(actual);
+ assertTrue(actual.isPreview());
+ }
+
+ public void testToString() throws Exception {
+ String expected = "a.b.c:package:5.4.2";
+ String actual = new GradleCoordinate("a.b.c", "package", 5, 4, 2).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:5.4.+";
+ actual = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE)
+ .toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:5.+";
+ actual = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE)
+ .toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:+";
+ actual = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:+ at jar";
+ List<GradleCoordinate.RevisionComponent> revisionList =
+ Lists.<GradleCoordinate.RevisionComponent>newArrayList(GradleCoordinate.PLUS_REV);
+ actual = new GradleCoordinate("a.b.c", "package", revisionList,
+ GradleCoordinate.ArtifactType.JAR).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:+ at aar";
+ actual = new GradleCoordinate("a.b.c", "package", revisionList,
+ GradleCoordinate.ArtifactType.AAR).toString();
+ assertEquals(expected, actual);
+
+ expected = "com.google.maps.android:android-maps-utils:0.3";
+ actual = GradleCoordinate.parseCoordinateString(expected).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:v1.v2";
+ actual = new GradleCoordinate("a.b.c", "package",
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.StringComponent("v2")).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:v1-1.17.0-rc-SNAPSHOT";
+ actual = new GradleCoordinate("a.b.c", "package",
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.StringComponent("v1"),
+ new GradleCoordinate.NumberComponent(1)),
+ new GradleCoordinate.NumberComponent(17),
+ GradleCoordinate.ListComponent.of(
+ new GradleCoordinate.NumberComponent(0),
+ new GradleCoordinate.StringComponent("rc"),
+ new GradleCoordinate.StringComponent("SNAPSHOT"))).toString();
+ assertEquals(expected, actual);
+ }
+
+ public void testIsSameArtifact() throws Exception {
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertTrue(a.isSameArtifact(b));
+ assertTrue(b.isSameArtifact(a));
+
+ a = new GradleCoordinate("a.b", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertFalse(a.isSameArtifact(b));
+ assertFalse(b.isSameArtifact(a));
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 5, 5);
+ assertFalse(a.isSameArtifact(b));
+ assertFalse(b.isSameArtifact(a));
+ }
+
+ public void testCompareVersions() {
+ // Requirements order
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 10);
+ b = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
+ b = new GradleCoordinate("a.b.c", "package", 6, 0, 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) == 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
+
+ assertTrue((COMPARE_PLUS_HIGHER.compare(a, b) < 0) == ("package".compareTo("feature") < 0));
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
+ assert a != null;
+ assert b != null;
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:5");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
+ assert a != null;
+ assert b != null;
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:1.any");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:1.1");
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:1-1");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:1-2");
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+ }
+
+ public void testCompareSpecificity() {
+ // Order of specificity
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 10);
+ b = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
+ b = new GradleCoordinate("a.b.c", "package", 6, 0, 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) == 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
+
+ assertTrue((COMPARE_PLUS_LOWER.compare(a, b) < 0) == ("package".compareTo("feature") < 0));
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV_VALUE);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+
+ a = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
+ b = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
+ assert a != null;
+ assert b != null;
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) > 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) < 0);
+ }
+
+ public void testGetVersions() {
+ GradleCoordinate c = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ assertEquals(5, c.getMajorVersion());
+ assertEquals(4, c.getMinorVersion());
+ assertEquals(2, c.getMicroVersion());
+ }
+
+ public void testSameSharedDifferentLengths() {
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) > 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) < 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) > 0);
+ }
+
+ public void testSameSharedDifferentLengthsWithZeros() {
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 4, 0, 0, 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(a, b) == 0);
+ assertTrue(COMPARE_PLUS_HIGHER.compare(b, a) == 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(a, b) == 0);
+ assertTrue(COMPARE_PLUS_LOWER.compare(b, a) == 0);
+ }
+
+ public void testParseVersionOnly() {
+ String revision = "15.32.64";
+ GradleCoordinate a = GradleCoordinate.parseVersionOnly(revision);
+ assertEquals(revision, a.getRevision());
+ String revisionB = "16.12.0-rc1";
+ GradleCoordinate b = GradleCoordinate.parseVersionOnly(revisionB);
+ assertEquals(revisionB, b.getRevision());
+ assertTrue(b.isPreview());
+ }
+
+ public void testIsPreview_ignoresFalsePositives() {
+ String revisionB = "16.12.0-march";
+ GradleCoordinate b = GradleCoordinate.parseVersionOnly(revisionB);
+ assertFalse(b.isPreview());
+ }
+
+ public void testLeadingZeroes() {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=74612
+ // The Gradle dependency
+ // compile 'com.google.android.gms:play-services:5.2.08'
+ // is not the same as
+ // compile 'com.google.android.gms:play-services:5.2.8'
+ // So we have to keep string representations around
+
+ GradleCoordinate v5_0_89 = GradleCoordinate.parseCoordinateString(
+ "com.google.android.gms:play-services:5.0.89");
+ assertNotNull(v5_0_89);
+ assertEquals("com.google.android.gms:play-services:5.0.89", v5_0_89.toString());
+ assertEquals("5.0.89", v5_0_89.getRevision());
+
+ GradleCoordinate v5_2_08 = GradleCoordinate.parseCoordinateString(
+ "com.google.android.gms:play-services:5.2.08");
+ assertNotNull(v5_2_08);
+ assertEquals("5.2.08", v5_2_08.getRevision());
+
+ assertEquals("com.google.android.gms:play-services:5.2.08", v5_2_08.toString());
+
+ // Same artifact: 5.2.08 == 5.2.8
+ //noinspection ConstantConditions
+ assertFalse(v5_2_08.equals(GradleCoordinate.parseCoordinateString(
+ "com.google.android.gms:play-services:5.2.8")));
+
+ assertEquals(
+ GradleCoordinate.parseCoordinateString(
+ "com.google.android.gms:play-services:5.2.08"),
+ GradleCoordinate.parseCoordinateString(
+ "com.google.android.gms:play-services:5.2.08"));
+
+ }
+
+ public void testAlphaBeta() throws Exception {
+ GradleCoordinate actual = GradleCoordinate.parseCoordinateString("com.android.support:appcompat-v7:24.0.0-alpha1");
+ assertNotNull(actual);
+ assertEquals(24, actual.getMajorVersion());
+ assertTrue(actual.isPreview());
+
+ actual = GradleCoordinate.parseCoordinateString("com.android.support:appcompat-v7:24.0.0-beta7");
+ assertNotNull(actual);
+ assertEquals(24, actual.getMajorVersion());
+ assertTrue(actual.isPreview());
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/test/java/com/android/ide/common/repository/GradleVersionTest.java b/sdk-common/src/test/java/com/android/ide/common/repository/GradleVersionTest.java
new file mode 100644
index 0000000..15fad80
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/repository/GradleVersionTest.java
@@ -0,0 +1,419 @@
+/*
+ * 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.ide.common.repository;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class GradleVersionTest {
+
+ @Test
+ public void testParseOneSegment() {
+ GradleVersion version = GradleVersion.parse("2");
+ assertEquals(2, version.getMajor());
+ assertEquals(0, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("2", version.toString());
+ }
+
+ @Test
+ public void testParseOneSegmentWithPlus() {
+ GradleVersion version = GradleVersion.parse("+");
+ assertEquals(Integer.MAX_VALUE, version.getMajor());
+ assertEquals(0, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("+", version.toString());
+ }
+
+ @Test
+ public void testParseOneSegmentWithPreview() {
+ GradleVersion version = GradleVersion.parse("2-alpha1");
+ assertEquals(2, version.getMajor());
+ assertEquals(0, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(1, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("2-alpha1", version.toString());
+ }
+
+ @Test
+ public void testParseOneSegmentWithPreviewAndSnapshot() {
+ GradleVersion version = GradleVersion.parse("2-alpha1-SNAPSHOT");
+ assertEquals(2, version.getMajor());
+ assertEquals(0, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(1, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertTrue(version.isSnapshot());
+ assertEquals("2-alpha1-SNAPSHOT", version.toString());
+ }
+
+ @Test
+ public void testParseOneSegmentWithSnapshot() {
+ GradleVersion version = GradleVersion.parse("2-SNAPSHOT");
+ assertEquals(2, version.getMajor());
+ assertEquals(0, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertTrue(version.isSnapshot());
+ assertEquals("2-SNAPSHOT", version.toString());
+ }
+
+ @Test
+ public void testParseTwoSegments() {
+ GradleVersion version = GradleVersion.parse("1.2");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.2", version.toString());
+ }
+
+ @Test
+ public void testParseTwoSegmentsWithPlus() {
+ GradleVersion version = GradleVersion.parse("1.+");
+ assertEquals(1, version.getMajor());
+ assertEquals(Integer.MAX_VALUE, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.+", version.toString());
+ }
+
+ @Test
+ public void testParseTwoSegmentsWithPreview() {
+ GradleVersion version = GradleVersion.parse("1.2-alpha3");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(3, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.2-alpha3", version.toString());
+ }
+
+ @Test
+ public void testParseTwoSegmentsWithPreviewAndSnapshot() {
+ GradleVersion version = GradleVersion.parse("1.2-alpha3-SNAPSHOT");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(3, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertTrue(version.isSnapshot());
+ assertEquals("1.2-alpha3-SNAPSHOT", version.toString());
+ }
+
+ @Test
+ public void testParseTwoSegmentsWithSnapshot() {
+ GradleVersion version = GradleVersion.parse("1.2-SNAPSHOT");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(0, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertTrue(version.isSnapshot());
+ assertEquals("1.2-SNAPSHOT", version.toString());
+ }
+
+ @Test
+ public void testParseThreeSegments() {
+ GradleVersion version = GradleVersion.parse("1.2.3");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.2.3", version.toString());
+ }
+
+ @Test
+ public void testParseThreeSegmentsWithPlus() {
+ GradleVersion version = GradleVersion.parse("1.2.+");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(Integer.MAX_VALUE, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.2.+", version.toString());
+ }
+
+ @Test
+ public void testParseThreeSegmentsWithPreview() {
+ GradleVersion version = GradleVersion.parse("1.2.3-alpha4");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(4, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.2.3-alpha4", version.toString());
+ }
+
+ @Test
+ public void testParseThreeSegmentsWithPreview2() {
+ GradleVersion version = GradleVersion.parse("1.2.3-alpha-4");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(4, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.2.3-alpha-4", version.toString());
+ }
+
+ @Test
+ public void testParseThreeSegmentsWithPreviewAndSnapshot() {
+ GradleVersion version = GradleVersion.parse("1.2.3-alpha4-SNAPSHOT");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(4, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertTrue(version.isSnapshot());
+ assertEquals("1.2.3-alpha4-SNAPSHOT", version.toString());
+ }
+
+ @Test
+ public void testParseThreeSegmentsWithPreviewAndSnapshot2() {
+ GradleVersion version = GradleVersion.parse("1.2.3-alpha-4-SNAPSHOT");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(4, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertTrue(version.isSnapshot());
+ assertEquals("1.2.3-alpha-4-SNAPSHOT", version.toString());
+ }
+
+ @Test
+ public void testParseThreeSegmentsWithSnapshot() {
+ GradleVersion version = GradleVersion.parse("1.2.3-SNAPSHOT");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertTrue(version.isSnapshot());
+ }
+
+ @Test
+ public void testParseWithMoreThanThreeSegments1() {
+ GradleVersion version = GradleVersion.parse("1.2.3.4");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+
+ List<GradleVersion.VersionSegment> additional = version.getAdditionalSegments();
+ assertEquals(1, additional.size());
+
+ GradleVersion.VersionSegment fourth = additional.get(0);
+ assertEquals(4, fourth.getValue());
+ }
+
+ @Test
+ public void testParseWithMoreThanThreeSegments2() {
+ GradleVersion version = GradleVersion.parse("1.2.3.4.5-SNAPSHOT");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertTrue(version.isSnapshot());
+
+ List<GradleVersion.VersionSegment> additional = version.getAdditionalSegments();
+ assertEquals(2, additional.size());
+
+ GradleVersion.VersionSegment fourth = additional.get(0);
+ assertEquals(4, fourth.getValue());
+
+ GradleVersion.VersionSegment fifth = additional.get(1);
+ assertEquals(5, fifth.getValue());
+ }
+
+
+ @Test
+ public void testParseWithMoreThanThreeSegments3() {
+ GradleVersion version = GradleVersion.parse("1.2.3.4.5.6-alpha9-SNAPSHOT");
+ assertEquals(1, version.getMajor());
+ assertEquals(2, version.getMinor());
+ assertEquals(3, version.getMicro());
+ assertEquals(9, version.getPreview());
+ assertEquals("alpha", version.getPreviewType());
+ assertTrue(version.isSnapshot());
+
+ List<GradleVersion.VersionSegment> additional = version.getAdditionalSegments();
+ assertEquals(3, additional.size());
+
+ GradleVersion.VersionSegment fourth = additional.get(0);
+ assertEquals(4, fourth.getValue());
+
+ GradleVersion.VersionSegment fifth = additional.get(1);
+ assertEquals(5, fifth.getValue());
+
+ GradleVersion.VersionSegment sixth = additional.get(2);
+ assertEquals(6, sixth.getValue());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidVersion1() {
+ GradleVersion.parse("a");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidVersion2() {
+ GradleVersion.parse("a.b");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidVersion3() {
+ GradleVersion.parse("a.b.c");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidVersion4() {
+ GradleVersion.parse("1.2.3-foo-bar");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidVersion5() {
+ GradleVersion.parse("1.2.3-1-2");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidVersion6() {
+ GradleVersion.parse("1.2.3-1");
+ }
+
+ @Test
+ public void testCompare() {
+ assertEquals(0, GradleVersion.parse("1.0.0").compareTo("1.0.0"));
+ assertEquals(0, GradleVersion.parse("1.0.0-alpha1").compareTo("1.0.0-alpha1"));
+ assertEquals(0, GradleVersion.parse("1.0.0-SNAPSHOT").compareTo("1.0.0-SNAPSHOT"));
+
+ assertTrue(GradleVersion.parse("1.0.1").compareTo("1.0.0") > 0);
+ assertTrue(GradleVersion.parse("+").compareTo("1.0.0") > 0);
+ assertTrue(GradleVersion.parse("1.+").compareTo("1.0.0") > 0);
+ assertTrue(GradleVersion.parse("1.0.+").compareTo("1.0.0") > 0);
+
+ assertTrue(GradleVersion.parse("1.0.1").compareTo("1.0.0") > 0);
+ assertTrue(GradleVersion.parse("1.1.0").compareTo("1.0.0") > 0);
+ assertTrue(GradleVersion.parse("1.1.1").compareTo("1.0.0") > 0);
+
+ assertTrue(GradleVersion.parse("1.0.0").compareTo("1.0.1") < 0);
+ assertTrue(GradleVersion.parse("1.0.0").compareTo("1.1.0") < 0);
+ assertTrue(GradleVersion.parse("1.0.0").compareTo("1.1.1") < 0);
+
+ assertTrue(GradleVersion.parse("1.0.0").compareTo("1.0.0-alpha1") > 0);
+ assertTrue(GradleVersion.parse("1.0.0").compareTo("1.0.0-SNAPSHOT") > 0);
+ assertTrue(GradleVersion.parse("1.0.0-alpha2").compareTo("1.0.0-alpha1") > 0);
+ assertTrue(GradleVersion.parse("1.0.0-beta1").compareTo("1.0.0-alpha2") > 0);
+ assertTrue(GradleVersion.parse("1.0.0-rc1").compareTo("1.0.0-alpha2") > 0);
+ assertTrue(GradleVersion.parse("2.0.0-alpha1").compareTo("1.0.0-alpha1") > 0);
+ }
+
+ @Test
+ public void testGetSegments() {
+ GradleVersion version = GradleVersion.parse("1.2.3-SNAPSHOT");
+ assertNotNull(version.getMinorSegment());
+ assertNotNull(version.getMicroSegment());
+ assertEquals("1", version.getMajorSegment().getText());
+ assertEquals("2", version.getMinorSegment().getText());
+ assertEquals("3", version.getMicroSegment().getText());
+ assertFalse(version.getMajorSegment().acceptsGreaterValue());
+ assertFalse(version.getMinorSegment().acceptsGreaterValue());
+ assertFalse(version.getMicroSegment().acceptsGreaterValue());
+
+ version = GradleVersion.parse("1.2.+");
+ assertNotNull(version.getMinorSegment());
+ assertNotNull(version.getMicroSegment());
+ assertEquals("1", version.getMajorSegment().getText());
+ assertEquals("2", version.getMinorSegment().getText());
+ assertEquals("+", version.getMicroSegment().getText());
+ assertFalse(version.getMajorSegment().acceptsGreaterValue());
+ assertFalse(version.getMinorSegment().acceptsGreaterValue());
+ assertTrue(version.getMicroSegment().acceptsGreaterValue());
+
+ version = GradleVersion.parse("+");
+ assertEquals("+", version.getMajorSegment().getText());
+ assertTrue(version.getMajorSegment().acceptsGreaterValue());
+ assertNull(version.getMinorSegment());
+ assertNull(version.getMicroSegment());
+ }
+
+ @Test
+ public void testConstructorWithNumbers() {
+ GradleVersion version = new GradleVersion(1, 2, 3);
+ assertNotNull(version.getMinorSegment());
+ assertNotNull(version.getMicroSegment());
+ assertEquals(1, version.getMajor());
+ assertEquals("1", version.getMajorSegment().getText());
+ assertEquals(2, version.getMinor());
+ assertEquals("2", version.getMinorSegment().getText());
+ assertEquals(3, version.getMicro());
+ assertEquals("3", version.getMicroSegment().getText());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("1.2.3", version.toString());
+ }
+
+ // See https://code.google.com/p/android/issues/detail?id=201325
+ @Test
+ public void testFixFor201325() {
+ // 2.0+ is equivalent to 2.0.+
+ GradleVersion version = GradleVersion.parse("2.0+");
+ assertNotNull(version.getMinorSegment());
+ assertNotNull(version.getMicroSegment());
+ assertEquals(2, version.getMajor());
+ assertEquals("2", version.getMajorSegment().getText());
+ assertEquals(0, version.getMinor());
+ assertEquals("0", version.getMinorSegment().getText());
+ assertTrue(version.getMicroSegment().acceptsGreaterValue());
+ assertEquals(0, version.getPreview());
+ assertNull(version.getPreviewType());
+ assertFalse(version.isSnapshot());
+ assertEquals("2.0+", version.toString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseWithPlusAtBeginningOfSegment() {
+ GradleVersion.parse("2.+1");
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/test/java/com/android/ide/common/repository/ResourceVisibilityLookupTest.java b/sdk-common/src/test/java/com/android/ide/common/repository/ResourceVisibilityLookupTest.java
new file mode 100644
index 0000000..9fa6f54
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/repository/ResourceVisibilityLookupTest.java
@@ -0,0 +1,425 @@
+/*
+ * 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.ide.common.repository;
+
+import static com.android.SdkConstants.FN_PUBLIC_TXT;
+import static com.android.SdkConstants.FN_RESOURCE_TEXT;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.MavenCoordinates;
+import com.android.builder.model.Variant;
+import com.android.ide.common.repository.ResourceVisibilityLookup.SymbolProvider;
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.resources.ResourceType;
+import com.android.testutils.TestUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Multimap;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import org.mockito.stubbing.OngoingStubbing;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class ResourceVisibilityLookupTest extends TestCase {
+ public void test() throws IOException {
+ AndroidLibrary library = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen activity_horizontal_margin 0x7f030000\n"
+ + "int dimen activity_vertical_margin 0x7f030001\n"
+ + "int id action_settings 0x7f060000\n"
+ + "int layout activity_main 0x7f020000\n"
+ + "int menu menu_main 0x7f050000\n"
+ + "int string action_settings 0x7f040000\n"
+ + "int string app_name 0x7f040001\n"
+ + "int string hello_world 0x7f040002",
+ ""
+ + ""
+ + "dimen activity_vertical\n"
+ + "id action_settings\n"
+ + "layout activity_main\n"
+ );
+
+ ResourceVisibilityLookup visibility = ResourceVisibilityLookup.create(library);
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
+ assertFalse(visibility.isPrivate(ResourceType.ID, "action_settings"));
+ assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
+ //noinspection ConstantConditions
+ assertTrue(visibility.isPrivate(ResourceUrl.parse("@dimen/activity_horizontal_margin")));
+
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical")); // public
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
+ }
+
+ public void testModelVersions() throws IOException {
+ AndroidLibrary library = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen activity_horizontal_margin 0x7f030000\n"
+ + "int dimen activity_vertical_margin 0x7f030001\n"
+ + "int id action_settings 0x7f060000\n"
+ + "int layout activity_main 0x7f020000\n"
+ + "int menu menu_main 0x7f050000\n"
+ + "int string action_settings 0x7f040000\n"
+ + "int string app_name 0x7f040001\n"
+ + "int string hello_world 0x7f040002",
+ ""
+ + ""
+ + "dimen activity_vertical\n"
+ + "id action_settings\n"
+ + "layout activity_main\n"
+ );
+
+ AndroidArtifact mockArtifact = createMockArtifact(Collections.singletonList(library));
+ Variant variant = createMockVariant(mockArtifact);
+
+ AndroidProject project;
+
+ project = createMockProject("1.0.1", 0);
+ assertTrue(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
+
+
+ project = createMockProject("1.1", 0);
+ assertTrue(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
+
+ project = createMockProject("1.2", 2);
+ assertTrue(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
+
+ project = createMockProject("1.3.0", 3);
+ assertFalse(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
+
+ project = createMockProject("2.5", 45);
+ assertFalse(new ResourceVisibilityLookup.Provider().get(project, variant).isEmpty());
+
+ ResourceVisibilityLookup visibility =new ResourceVisibilityLookup.Provider().get(project,
+ variant);
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
+ assertFalse(visibility.isPrivate(ResourceType.ID, "action_settings"));
+ }
+
+ public void testAllPrivate() throws IOException {
+ AndroidLibrary library = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen activity_horizontal_margin 0x7f030000\n"
+ + "int dimen activity_vertical_margin 0x7f030001\n"
+ + "int id action_settings 0x7f060000\n"
+ + "int layout activity_main 0x7f020000\n"
+ + "int menu menu_main 0x7f050000\n"
+ + "int string action_settings 0x7f040000\n"
+ + "int string app_name 0x7f040001\n"
+ + "int string hello_world 0x7f040002",
+ ""
+ );
+
+ ResourceVisibilityLookup visibility = ResourceVisibilityLookup.create(library);
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
+ assertTrue(visibility.isPrivate(ResourceType.ID, "action_settings"));
+ assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical_margin"));
+
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
+ }
+
+ public void testNotDeclared() throws IOException {
+ AndroidLibrary library = createMockLibrary("com.android.tools:test-library:1.0.0", "",
+ null);
+
+ ResourceVisibilityLookup visibility = ResourceVisibilityLookup.create(library);
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
+ assertFalse(visibility.isPrivate(ResourceType.ID, "action_settings"));
+ assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical"));
+
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
+ }
+
+ public void testCombined() throws IOException {
+ AndroidLibrary library1 = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen activity_horizontal_margin 0x7f030000\n"
+ + "int dimen activity_vertical_margin 0x7f030001\n"
+ + "int id action_settings 0x7f060000\n"
+ + "int layout activity_main 0x7f020000\n"
+ + "int menu menu_main 0x7f050000\n"
+ + "int string action_settings 0x7f040000\n"
+ + "int string app_name 0x7f040001\n"
+ + "int string hello_world 0x7f040002",
+ ""
+ );
+ AndroidLibrary library2 = createMockLibrary(
+ "com.android.tools:test-library2:1.0.0",
+ ""
+ + "int layout foo 0x7f030001\n"
+ + "int layout bar 0x7f060000\n",
+ ""
+ + "layout foo\n"
+ );
+
+ List<AndroidLibrary> androidLibraries = Arrays.asList(library1, library2);
+ ResourceVisibilityLookup visibility = ResourceVisibilityLookup
+ .create(androidLibraries, null);
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
+ assertTrue(visibility.isPrivate(ResourceType.ID, "action_settings"));
+ assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical_margin"));
+ assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "foo"));
+ assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "bar"));
+
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
+ }
+
+ public void testDependency() throws IOException {
+ AndroidLibrary library1 = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen activity_horizontal_margin 0x7f030000\n"
+ + "int dimen activity_vertical_margin 0x7f030001\n"
+ + "int id action_settings 0x7f060000\n"
+ + "int layout activity_main 0x7f020000\n"
+ + "int menu menu_main 0x7f050000\n"
+ + "int string action_settings 0x7f040000\n"
+ + "int string app_name 0x7f040001\n"
+ + "int string hello_world 0x7f040002",
+ ""
+ );
+ AndroidLibrary library2 = createMockLibrary(
+ "com.android.tools:test-library2:1.0.0",
+ ""
+ + "int layout foo 0x7f030001\n"
+ + "int layout bar 0x7f060000\n",
+ ""
+ + "layout foo\n",
+ Collections.singletonList(library1)
+ );
+
+ List<AndroidLibrary> androidLibraries = Arrays.asList(library1, library2);
+ ResourceVisibilityLookup visibility = ResourceVisibilityLookup
+ .create(androidLibraries, null);
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_horizontal_margin"));
+ assertTrue(visibility.isPrivate(ResourceType.ID, "action_settings"));
+ assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "activity_main"));
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "activity_vertical_margin"));
+ assertFalse(visibility.isPrivate(ResourceType.LAYOUT, "foo"));
+ assertTrue(visibility.isPrivate(ResourceType.LAYOUT, "bar"));
+
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "unknown")); // not in this library
+ }
+
+ public void testManager() throws IOException {
+ AndroidLibrary library = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen activity_horizontal_margin 0x7f030000\n"
+ + "int dimen activity_vertical_margin 0x7f030001\n"
+ + "int id action_settings 0x7f060000\n"
+ + "int layout activity_main 0x7f020000\n"
+ + "int menu menu_main 0x7f050000\n"
+ + "int string action_settings 0x7f040000\n"
+ + "int string app_name 0x7f040001\n"
+ + "int string hello_world 0x7f040002",
+ ""
+ );
+ ResourceVisibilityLookup.Provider provider = new ResourceVisibilityLookup.Provider();
+ assertSame(provider.get(library), provider.get(library));
+ assertTrue(provider.get(library).isPrivate(ResourceType.DIMEN,
+ "activity_horizontal_margin"));
+
+ AndroidArtifact artifact = createMockArtifact(Collections.singletonList(library));
+ assertSame(provider.get(artifact), provider.get(artifact));
+ assertTrue(provider.get(artifact).isPrivate(ResourceType.DIMEN,
+ "activity_horizontal_margin"));
+ }
+
+ public void testImportedResources() throws IOException {
+ // Regression test for https://code.google.com/p/android/issues/detail?id=183120 :
+ // When a library depends on another library, all the resources from the dependency
+ // are imported and exposed in R.txt from the downstream library too. When both
+ // libraries expose public resources, we have to be careful such that we don't
+ // take the presence of a resource (imported) and the absence of a public.txt declaration
+ // (for the imported symbol in the dependent library) as evidence that this is a private
+ // resource.
+ AndroidLibrary library1 = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen public_library1_resource1 0x7f030000\n"
+ + "int dimen public_library1_resource2 0x7f030001\n"
+ + "int dimen private_library1_resource 0x7f030002\n",
+ ""
+ + "dimen public_library1_resource1\n"
+ + "dimen public_library1_resource2\n"
+ );
+
+ AndroidLibrary library2 = createMockLibrary(
+ "com.android.tools:test-library2:1.0.0",
+ ""
+ + "int dimen public_library2_resource1 0x7f030000\n"
+ + "int dimen public_library2_resource2 0x7f030001\n",
+ null // nothing marked as private: everything exposed
+ );
+ AndroidLibrary library3 = createMockLibrary(
+ "com.android.tools:test-library3:1.0.0",
+ ""
+ + "int dimen public_library1_resource1 0x7f030000\n" // merged from library1
+ + "int dimen public_library1_resource2 0x7f030001\n"
+ + "int dimen private_library1_resource 0x7f030002\n"
+ + "int dimen public_library2_resource1 0x7f030003\n" // merged from library2
+ + "int dimen public_library2_resource2 0x7f030004\n"
+ + "int dimen public_library3_resource1 0x7f030005\n" // unique to library3
+ + "int dimen private_library3_resource 0x7f030006\n",
+ ""
+ + "dimen public_library2_resource1\n",
+ Arrays.asList(library1, library2)
+ );
+
+ List<AndroidLibrary> androidLibraries = Arrays.asList(library1, library2, library3);
+ ResourceVisibilityLookup visibility = ResourceVisibilityLookup.create(androidLibraries,
+ null);
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "private_library1_resource"));
+ assertTrue(visibility.isPrivate(ResourceType.DIMEN, "private_library3_resource"));
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "public_library1_resource1"));
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "public_library1_resource2"));
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "public_library2_resource1"));
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "public_library2_resource2"));
+ assertFalse(visibility.isPrivate(ResourceType.DIMEN, "public_library3_resource"));
+ }
+
+ public void testSymbolProvider() throws Exception {
+ AndroidLibrary library1 = createMockLibrary(
+ "com.android.tools:test-library:1.0.0",
+ ""
+ + "int dimen public_library1_resource1 0x7f030000\n"
+ + "int dimen public_library1_resource2 0x7f030001\n"
+ + "int dimen private_library1_resource 0x7f030002\n",
+ ""
+ + "dimen public_library1_resource1\n"
+ + "dimen public_library1_resource2\n"
+ );
+
+ AndroidLibrary library3 = createMockLibrary(
+ "com.android.tools:test-library3:1.0.0",
+ ""
+ + "int dimen public_library1_resource1 0x7f030000\n" // merged from library1
+ + "int dimen public_library1_resource2 0x7f030001\n"
+ + "int dimen private_library1_resource 0x7f030002\n"
+ + "int dimen public_library2_resource1 0x7f030003\n" // merged from library2
+ + "int dimen public_library2_resource2 0x7f030004\n"
+ + "int dimen public_library3_resource1 0x7f030005\n" // unique to library3
+ + "int dimen private_library3_resource 0x7f030006\n",
+ ""
+ + "dimen public_library2_resource1\n",
+ Collections.singletonList(library1)
+ );
+
+ SymbolProvider provider = new SymbolProvider();
+ Multimap<String, ResourceType> symbols = provider.getSymbols(library3);
+
+ // Exclude imported symbols
+ assertFalse(symbols.get("public_library1_resource1").iterator().hasNext());
+
+ // Make sure non-imported symbols are there
+ assertSame(ResourceType.DIMEN, symbols.get("public_library3_resource1").iterator().next());
+
+ // Make sure we're actually caching results
+ Multimap<String, ResourceType> symbols2 = provider.getSymbols(library3);
+ assertSame(symbols, symbols2);
+ }
+
+ public static AndroidProject createMockProject(String modelVersion, int apiVersion) {
+ AndroidProject project = createNiceMock(AndroidProject.class);
+ expect(project.getApiVersion()).andReturn(apiVersion).anyTimes();
+ expect(project.getModelVersion()).andReturn(modelVersion).anyTimes();
+ replay(project);
+
+ return project;
+ }
+
+ public static Variant createMockVariant(AndroidArtifact artifact) {
+ Variant variant = createNiceMock(Variant.class);
+ expect(variant.getMainArtifact()).andReturn(artifact).anyTimes();
+ replay(variant);
+ return variant;
+
+ }
+
+ public static AndroidArtifact createMockArtifact(List<AndroidLibrary> libraries) {
+ Dependencies dependencies = createNiceMock(Dependencies.class);
+ expect(dependencies.getLibraries()).andReturn(libraries).anyTimes();
+ replay(dependencies);
+
+ AndroidArtifact artifact = createNiceMock(AndroidArtifact.class);
+ expect(artifact.getDependencies()).andReturn(dependencies).anyTimes();
+ replay(artifact);
+
+ return artifact;
+ }
+
+ public static AndroidLibrary createMockLibrary(String name, String allResources,
+ String publicResources)
+ throws IOException {
+ return createMockLibrary(name, allResources, publicResources,
+ Collections.<AndroidLibrary>emptyList());
+ }
+
+
+ public static AndroidLibrary createMockLibrary(String name,
+ String allResources, String publicResources,
+ List<AndroidLibrary> dependencies)
+ throws IOException {
+ // Identical to PrivateResourceDetectorTest, but these are in test modules that
+ // can't access each other
+ final File tempDir = TestUtils.createTempDirDeletedOnExit();
+
+ Files.write(allResources, new File(tempDir, FN_RESOURCE_TEXT), Charsets.UTF_8);
+ File publicTxtFile = new File(tempDir, FN_PUBLIC_TXT);
+ if (publicResources != null) {
+ Files.write(publicResources, publicTxtFile, Charsets.UTF_8);
+ }
+ AndroidLibrary library = mock(AndroidLibrary.class);
+ when(library.getPublicResources()).thenReturn(publicTxtFile);
+ GradleCoordinate c = GradleCoordinate.parseCoordinateString(name);
+ assertNotNull(c);
+ MavenCoordinates coordinates = mock(MavenCoordinates.class);
+ when(coordinates.getGroupId()).thenReturn(c.getGroupId());
+ when(coordinates.getArtifactId()).thenReturn(c.getArtifactId());
+ when(coordinates.getVersion()).thenReturn(c.getRevision());
+ when(library.getResolvedCoordinates()).thenReturn(coordinates);
+ when(library.getBundle()).thenReturn(new File("intermediates" + File.separator +
+ "exploded-aar" + File.separator + name));
+
+ // Work around wildcard capture
+ //when(library.getLibraryDependencies()).thenReturn(dependencies);
+ List libraryDependencies = library.getLibraryDependencies();
+ OngoingStubbing<List> setter = when(libraryDependencies);
+ setter.thenReturn(dependencies);
+ return library;
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/test/java/com/android/ide/common/repository/SdkMavenRepositoryTest.java b/sdk-common/src/test/java/com/android/ide/common/repository/SdkMavenRepositoryTest.java
new file mode 100644
index 0000000..391f90e
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/repository/SdkMavenRepositoryTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.repository;
+
+import com.android.annotations.Nullable;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class SdkMavenRepositoryTest extends TestCase {
+
+ public static final File SDK_HOME = new File("/sdk");
+ private MockFileOp mFileOp;
+ private AndroidSdkHandler mSdkHandler;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mFileOp = new MockFileOp();
+ mSdkHandler = new AndroidSdkHandler(SDK_HOME, mFileOp);
+ }
+
+ private void registerAndroidRepo() {
+ mFileOp.recordExistingFile(
+ "/sdk/extras/android/m2repository/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<addon:sdk-addon xmlns:addon=\"http://schemas.android.com/sdk/android/repo/addon2/01\""
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" >"
+ + " <localPackage path=\"extras;android;m2repository\">"
+ + " <type-details xsi:type=\"addon:extraDetailsType\">"
+ + " <vendor>"
+ + " <id>android</id>"
+ + " <display>Android</display>"
+ + " </vendor>"
+ + " </type-details>"
+ + " <revision>"
+ + " <major>25</major>"
+ + " <minor>0</minor>"
+ + " <micro>0</micro>"
+ + " </revision>"
+ + " <display-name>Android Support Repository, rev 25</display-name>"
+ + " </localPackage>"
+ + "</addon:sdk-addon>\n");
+ }
+
+ private void registerGoogleRepo() {
+ mFileOp.recordExistingFile(
+ "/sdk/extras/google/m2repository/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<addon:sdk-addon xmlns:addon=\"http://schemas.android.com/sdk/android/repo/addon2/01\""
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" >"
+ + " <localPackage path=\"extras;google;m2repository\">"
+ + " <type-details xsi:type=\"addon:extraDetailsType\">"
+ + " <vendor>"
+ + " <id>google</id>"
+ + " <display>Google Inc.</display>"
+ + " </vendor>"
+ + " </type-details>"
+ + " <revision>"
+ + " <major>23</major>"
+ + " <minor>0</minor>"
+ + " <micro>0</micro>"
+ + " </revision>"
+ + " <display-name>Google Repository, rev 23</display-name>"
+ + " </localPackage>"
+ + "</addon:sdk-addon>\n");
+
+ }
+
+ public void testGetLocation() {
+ registerGoogleRepo();
+ registerAndroidRepo();
+ assertNull(SdkMavenRepository.ANDROID.getRepositoryLocation(null, false, mFileOp));
+
+ File android = SdkMavenRepository.ANDROID.getRepositoryLocation(SDK_HOME, true, mFileOp);
+ assertNotNull(android);
+
+ File google = SdkMavenRepository.GOOGLE.getRepositoryLocation(SDK_HOME, true, mFileOp);
+ assertNotNull(google);
+ }
+
+ public void testGetBestMatch() {
+ registerAndroidRepo();
+ mFileOp.recordExistingFolder("/sdk/extras/android/m2repository/com/android/support/support-v4/19.0.0");
+ mFileOp.recordExistingFolder("/sdk/extras/android/m2repository/com/android/support/support-v4/19.1.0");
+ mFileOp.recordExistingFolder("/sdk/extras/android/m2repository/com/android/support/support-v4/20.0.0");
+ mFileOp.recordExistingFolder("/sdk/extras/android/m2repository/com/android/support/support-v4/22.0.0-rc1");
+ assertNull(SdkMavenRepository.ANDROID.getHighestInstalledVersion(
+ null, "com.android.support", "support-v4", "19", false, mFileOp));
+
+ GradleCoordinate gc1 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
+ SDK_HOME, "com.android.support", "support-v4", "19", false, mFileOp);
+ assertEquals(GradleCoordinate.parseCoordinateString(
+ "com.android.support:support-v4:19.1.0"), gc1);
+
+ GradleCoordinate gc2 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
+ SDK_HOME, "com.android.support", "support-v4", "20", false, mFileOp);
+ assertEquals(GradleCoordinate.parseCoordinateString(
+ "com.android.support:support-v4:20.0.0"), gc2);
+
+ GradleCoordinate gc3 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
+ SDK_HOME, "com.android.support", "support-v4", "22", false, mFileOp);
+ assertNull(gc3);
+
+ GradleCoordinate gc4 = SdkMavenRepository.ANDROID.getHighestInstalledVersion(
+ SDK_HOME, "com.android.support", "support-v4", "22", true, mFileOp);
+ assertEquals(GradleCoordinate.parseCoordinateString(
+ "com.android.support:support-v4:22.0.0-rc1"), gc4);
+ }
+
+ public void testIsInstalled() {
+ assertFalse(SdkMavenRepository.ANDROID.isInstalled(null, mFileOp));
+ assertFalse(SdkMavenRepository.ANDROID.isInstalled(null));
+ assertFalse(SdkMavenRepository.ANDROID.isInstalled(mSdkHandler));
+ assertFalse(SdkMavenRepository.GOOGLE.isInstalled(mSdkHandler));
+
+ registerAndroidRepo();
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ mSdkHandler.getSdkManager(progress).loadSynchronously(0, progress, null, null);
+ assertFalse(SdkMavenRepository.GOOGLE.isInstalled(mSdkHandler));
+ assertTrue(SdkMavenRepository.ANDROID.isInstalled(mSdkHandler));
+
+ registerGoogleRepo();
+ mSdkHandler.getSdkManager(progress).loadSynchronously(0, progress, null, null);
+ assertTrue(SdkMavenRepository.GOOGLE.isInstalled(mSdkHandler));
+ }
+
+ public void testGetDirName() {
+ assertEquals("android", SdkMavenRepository.ANDROID.getDirName());
+ assertEquals("google", SdkMavenRepository.GOOGLE.getDirName());
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetByGroupId() {
+ assertSame(SdkMavenRepository.ANDROID, SdkMavenRepository.getByGroupId(
+ GradleCoordinate.parseCoordinateString(
+ "com.android.support:appcompat-v7:13.0.0").getGroupId()));
+ assertSame(SdkMavenRepository.ANDROID, SdkMavenRepository.getByGroupId(
+ GradleCoordinate.parseCoordinateString(
+ "com.android.support.test:espresso:0.2").getGroupId()));
+ assertSame(SdkMavenRepository.GOOGLE, SdkMavenRepository.getByGroupId(
+ GradleCoordinate.parseCoordinateString(
+ "com.google.android.gms:play-services:5.2.08").getGroupId()));
+ assertSame(SdkMavenRepository.GOOGLE, SdkMavenRepository.getByGroupId(
+ GradleCoordinate.parseCoordinateString(
+ "com.google.android.gms:play-services-wearable:5.0.77").getGroupId()));
+ assertNull(SdkMavenRepository.getByGroupId(GradleCoordinate.parseCoordinateString(
+ "com.google.guava:guava:11.0.2").getGroupId()));
+ }
+}
\ No newline at end of file
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java
new file mode 100644
index 0000000..7e94e33
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.SdkConstants;
+import com.android.testutils.TestUtils;
+import com.google.common.collect.ListMultimap;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class AssetMergerTest extends BaseTestCase {
+
+ private static AssetMerger sAssetMerger = null;
+
+ public void testMergeByCount() throws Exception {
+ AssetMerger merger = getAssetMerger();
+
+ assertEquals(5, merger.size());
+ }
+
+ public void testMergedAssetsByName() throws Exception {
+ AssetMerger merger = getAssetMerger();
+
+ verifyResourceExists(merger,
+ "foo/icon.png",
+ "icon2.png",
+ "main.xml",
+ "values.xml",
+ "foo/foo.dat"
+ );
+ }
+
+ public void testMergeWrite() throws Exception {
+ AssetMerger merger = getAssetMerger();
+
+ File folder = getWrittenResources();
+
+ RecordingLogger logger = new RecordingLogger();
+
+ AssetSet writtenSet = new AssetSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ checkLogger(logger);
+
+ // compare the two maps, but not using the full map as the set loaded from the output
+ // won't contains all versions of each AssetItem item.
+ compareResourceMaps(merger, writtenSet, false /*full compare*/);
+ }
+
+ public void testMergeBlob() throws Exception {
+ AssetMerger merger = getAssetMerger();
+
+ File folder = Files.createTempDir();
+ merger.writeBlobTo(folder, new MergedAssetWriter(Files.createTempDir()));
+
+ AssetMerger loadedMerger = new AssetMerger();
+ loadedMerger.loadFromBlob(folder, true /*incrementalState*/);
+
+ compareResourceMaps(merger, loadedMerger, true /*full compare*/);
+ }
+
+ /**
+ * Tests the path replacement in the merger.xml file loaded from testData/
+ * @throws Exception
+ */
+ public void testLoadingTestPathReplacement() throws Exception {
+ File root = TestUtils.getRoot("assets", "baseMerge");
+ File fakeRoot = getMergedBlobFolder(root);
+
+ AssetMerger assetMerger = new AssetMerger();
+ assetMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
+ checkSourceFolders(assetMerger);
+
+ List<AssetSet> sets = assetMerger.getDataSets();
+ for (AssetSet set : sets) {
+ List<File> sourceFiles = set.getSourceFiles();
+
+ // there should only be one
+ assertEquals(1, sourceFiles.size());
+
+ File sourceFile = sourceFiles.get(0);
+ assertTrue(String.format("File %s is located in %s", sourceFile, root),
+ sourceFile.getAbsolutePath().startsWith(root.getAbsolutePath()));
+ }
+ }
+
+ public void testUpdate() throws Exception {
+ File root = getIncMergeRoot("basicFiles");
+ File fakeRoot = getMergedBlobFolder(root);
+ AssetMerger assetMerger = new AssetMerger();
+ assetMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
+ checkSourceFolders(assetMerger);
+
+ List<AssetSet> sets = assetMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // first set is the main one, no change here
+ AssetSet mainSet = sets.get(0);
+ File mainFolder = new File(root, "main");
+
+ // touched/removed files:
+ File mainTouched = new File(mainFolder, "touched.png");
+ mainSet.updateWith(mainFolder, mainTouched, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ File mainRemoved = new File(mainFolder, "removed.png");
+ mainSet.updateWith(mainFolder, mainRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ File mainAdded = new File(mainFolder, "added.png");
+ mainSet.updateWith(mainFolder, mainAdded, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ // ----------------
+ // second set is the overlay one
+ AssetSet overlaySet = sets.get(1);
+ File overlayFolder = new File(root, "overlay");
+
+ // new/removed files:
+ File overlayAdded = new File(new File(overlayFolder, "foo"), "overlay_added.png");
+ overlaySet.updateWith(overlayFolder, overlayAdded, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ File overlayRemoved = new File(overlayFolder, "overlay_removed.png");
+ overlaySet.updateWith(overlayFolder, overlayRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ assetMerger.validateDataSets();
+
+ // check the content.
+ ListMultimap<String, AssetItem> mergedMap = assetMerger.getDataMap();
+
+ // check untouched.png file is WRITTEN
+ List<AssetItem> untouchedItem = mergedMap.get("untouched.png");
+ assertEquals(1, untouchedItem.size());
+ assertTrue(untouchedItem.get(0).isWritten());
+ assertFalse(untouchedItem.get(0).isTouched());
+ assertFalse(untouchedItem.get(0).isRemoved());
+
+ // check touched.png file is TOUCHED
+ List<AssetItem> touchedItem = mergedMap.get("touched.png");
+ assertEquals(1, touchedItem.size());
+ assertTrue(touchedItem.get(0).isWritten());
+ assertTrue(touchedItem.get(0).isTouched());
+ assertFalse(touchedItem.get(0).isRemoved());
+
+ // check removed file is REMOVED
+ List<AssetItem> removedItem = mergedMap.get("removed.png");
+ assertEquals(1, removedItem.size());
+ assertTrue(removedItem.get(0).isWritten());
+ assertTrue(removedItem.get(0).isRemoved());
+
+ // check new overlay: two objects, last one is TOUCHED
+ List<AssetItem> overlayAddedItem = mergedMap.get("foo/overlay_added.png");
+ assertEquals(2, overlayAddedItem.size());
+ AssetItem newOverlay0 = overlayAddedItem.get(0);
+ assertTrue(newOverlay0.isWritten());
+ assertFalse(newOverlay0.isTouched());
+ AssetItem newOverlay1 = overlayAddedItem.get(1);
+ assertEquals(overlayAdded, newOverlay1.getSource().getFile());
+ assertFalse(newOverlay1.isWritten());
+ assertTrue(newOverlay1.isTouched());
+
+ // check removed overlay: two objects, last one is removed
+ List<AssetItem> overlayRemovedItem = mergedMap.get("overlay_removed.png");
+ assertEquals(2, overlayRemovedItem.size());
+ AssetItem overlayRemovedItem0 = overlayRemovedItem.get(0);
+ assertFalse(overlayRemovedItem0.isWritten());
+ assertFalse(overlayRemovedItem0.isTouched());
+ AssetItem overlayRemovedItem1 = overlayRemovedItem.get(1);
+ assertEquals(overlayRemoved, overlayRemovedItem1.getSource().getFile());
+ assertTrue(overlayRemovedItem1.isWritten());
+ assertTrue(overlayRemovedItem1.isRemoved());
+
+ // write and check the result of writeResourceFolder
+ // copy the current resOut which serves as pre incremental update state.
+ File resFolder = getFolderCopy(new File(root, "assetOut"));
+
+ // write the content of the resource merger.
+ MergedAssetWriter writer = new MergedAssetWriter(resFolder);
+ assetMerger.mergeData(writer, false /*doCleanUp*/);
+
+ // Check the content by checking the colors. All files should be green
+ checkImageColor(new File(resFolder, "untouched.png"), (int) 0xFF00FF00);
+ checkImageColor(new File(resFolder, "touched.png"), (int) 0xFF00FF00);
+ checkImageColor(new File(resFolder, "added.png"), (int) 0xFF00FF00);
+ checkImageColor(new File(resFolder, "overlay_removed.png"), (int) 0xFF00FF00);
+ checkImageColor(new File(new File(resFolder, "foo"), "overlay_added.png"), (int) 0xFF00FF00);
+
+ // also check the removed file is not there.
+ assertFalse(new File(resFolder, "removed.png").isFile());
+ }
+
+ public void testCheckValidUpdate() throws Exception {
+ // first merger
+ AssetMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", ("/main/res1"), ("/main/res2") },
+ new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
+ });
+
+ // 2nd merger with different order source files in sets.
+ AssetMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", ("/main/res2"), ("/main/res1") },
+ new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
+ });
+
+ assertTrue(merger1.checkValidUpdate(merger2.getDataSets()));
+
+ // write merger1 on disk to test writing empty AssetSets.
+ File folder = Files.createTempDir();
+ merger1.writeBlobTo(folder, new MergedAssetWriter(Files.createTempDir()));
+
+ // reload it
+ AssetMerger loadedMerger = new AssetMerger();
+ loadedMerger.loadFromBlob(folder, true /*incrementalState*/);
+
+ String expected = merger1.toString();
+ String actual = loadedMerger.toString();
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ expected = expected.replace(File.separatorChar, '/').
+ replaceAll("[A-Z]:/", "/");
+ actual = actual.replace(File.separatorChar, '/').
+ replaceAll("[A-Z]:/", "/");
+ assertEquals("Actual: " + actual + "\nExpected: " + expected, expected, actual);
+ } else {
+ assertTrue("Actual: " + actual + "\nExpected: " + expected,
+ loadedMerger.checkValidUpdate(merger1.getDataSets()));
+ }
+ }
+
+
+ public void testUpdateWithRemovedOverlay() throws Exception {
+ // Test with removed overlay
+ AssetMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ AssetMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", "/main/res2", "/main/res1" },
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testUpdateWithReplacedOverlays() throws Exception {
+ // Test with different overlays
+ AssetMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ AssetMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", "/main/res2", "/main/res1" },
+ new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testUpdateWithReorderedOverlays() throws Exception {
+ // Test with different overlays
+ AssetMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
+ new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ AssetMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", "/main/res2", "/main/res1" },
+ new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
+ new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testUpdateWithRemovedSourceFile() throws Exception {
+ // Test with different source files
+ AssetMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ AssetMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1" },
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testChangedIgnoredFile() throws Exception {
+ AssetSet assetSet = AssetSetTest.getBaseAssetSet();
+
+ AssetMerger assetMerger = new AssetMerger();
+ assetMerger.addDataSet(assetSet);
+
+ File root = TestUtils.getRoot("assets", "baseSet");
+ File changedCVSFoo = new File(root, "CVS/foo.txt");
+ FileValidity<AssetSet> fileValidity = assetMerger.findDataSetContaining(changedCVSFoo);
+
+ assertEquals(FileValidity.FileStatus.IGNORED_FILE, fileValidity.status);
+ }
+
+ /**
+ * Creates a fake merge with given sets.
+ *
+ * the data is an array of sets.
+ *
+ * Each set is [ setName, folder1, folder2, ...]
+ *
+ * @param data
+ * @return
+ */
+ private static AssetMerger createMerger(String[][] data) {
+ AssetMerger merger = new AssetMerger();
+ for (String[] setData : data) {
+ AssetSet set = new AssetSet(setData[0]);
+ merger.addDataSet(set);
+ for (int i = 1, n = setData.length; i < n; i++) {
+ set.addSource(new File(setData[i]));
+ }
+ }
+
+ return merger;
+ }
+
+ private static AssetMerger getAssetMerger()
+ throws IOException, MergingException {
+ if (sAssetMerger == null) {
+ File root = TestUtils.getRoot("assets", "baseMerge");
+
+ AssetSet res = AssetSetTest.getBaseAssetSet();
+
+ RecordingLogger logger = new RecordingLogger();
+
+ AssetSet overlay = new AssetSet("overlay");
+ overlay.addSource(new File(root, "overlay"));
+ overlay.loadFromFiles(logger);
+
+ checkLogger(logger);
+
+ sAssetMerger = new AssetMerger();
+ sAssetMerger.addDataSet(res);
+ sAssetMerger.addDataSet(overlay);
+ }
+
+ return sAssetMerger;
+ }
+
+ private static File getWrittenResources() throws MergingException, IOException {
+ AssetMerger assetMerger = getAssetMerger();
+
+ File folder = Files.createTempDir();
+
+ MergedAssetWriter writer = new MergedAssetWriter(folder);
+ assetMerger.mergeData(writer, false /*doCleanUp*/);
+
+ return folder;
+ }
+
+ private File getIncMergeRoot(String name) throws IOException {
+ File root = TestUtils.getCanonicalRoot("assets", "incMergeData");
+ return new File(root, name);
+ }
+
+ private static File getFolderCopy(File folder) throws IOException {
+ File dest = Files.createTempDir();
+ copyFolder(folder, dest);
+ return dest;
+ }
+
+ private static void copyFolder(File from, File to) throws IOException {
+ if (from.isFile()) {
+ Files.copy(from, to);
+ } else if (from.isDirectory()) {
+ if (!to.exists()) {
+ to.mkdirs();
+ }
+
+ File[] children = from.listFiles();
+ if (children != null) {
+ for (File f : children) {
+ copyFolder(f, new File(to, f.getName()));
+ }
+ }
+ }
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java b/sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/BaseTestCase.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/DataSetTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/DataSetTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/DataSetTest.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/DataSetTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/FileResourceNameValidatorTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/FileResourceNameValidatorTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/FileResourceNameValidatorTest.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/FileResourceNameValidatorTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java
new file mode 100644
index 0000000..cb4a790
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.ide.common.blame.Message;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import org.junit.Test;
+import org.xml.sax.SAXParseException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+ at SuppressWarnings({"ThrowableInstanceNeverThrown", "ThrowableResultOfMethodCallIgnored"})
+public class MergingExceptionTest {
+
+ private static final File file = new File("/some/random/path");
+
+ @Test
+ public void testGetMessage() {
+ assertEquals("Error: My error message",
+ MergingException.withMessage("My error message").build().getMessage());
+ assertEquals("Error: My error message",
+ MergingException.wrapException(new Exception()).withMessage(
+ "Error: My error message").build().getMessage());
+ assertEquals("Error: My error message",
+ MergingException.withMessage("Error: My error message").build().getMessage());
+ assertEquals("/some/random/path: Error: My error message",
+ MergingException.wrapException(new Exception("My error message")).withFile(file)
+ .build().getMessage());
+ MergingException.Builder builder2 = MergingException
+ .wrapException(new Exception("My error message")).withFile(file)
+ .withPosition(new SourcePosition(49, -1, -1));
+ assertEquals("/some/random/path:50: Error: My error message",
+ builder2.build()
+ .getMessage());
+ MergingException.Builder builder1 = MergingException
+ .wrapException(new Exception("My error message")).withFile(file)
+ .withPosition(new SourcePosition(49, 3, -1));
+ assertEquals("/some/random/path:50:4: Error: My error message",
+ builder1.build()
+ .getMessage());
+ MergingException.Builder builder = MergingException
+ .wrapException(new Exception("My error message")).withFile(file)
+ .withPosition(new SourcePosition(49, 3, -1));
+ assertEquals("/some/random/path:50:4: Error: My error message",
+ builder.build()
+ .getLocalizedMessage());
+ assertEquals("/some/random/path: Error: My error message",
+ MergingException.withMessage("/some/random/path: My error message").withFile(file)
+ .build().getMessage());
+ assertEquals("/some/random/path: Error: My error message",
+ MergingException.withMessage("/some/random/path My error message").withFile(file)
+ .build().getMessage());
+
+ // end of string handling checks
+ assertEquals("/some/random/path: Error: ",
+ MergingException.withMessage("/some/random/path").withFile(file).build()
+ .getMessage());
+ assertEquals("/some/random/path: Error: ",
+ MergingException.withMessage("/some/random/path").withFile(file).build().getMessage());
+ assertEquals("/some/random/path: Error: ",
+ MergingException.withMessage("/some/random/path:").withFile(file).build().getMessage());
+ assertEquals("/some/random/path: Error: ",
+ MergingException.withMessage("/some/random/path: ").withFile(file).build().getMessage());
+ }
+
+
+ @Test
+ public void testWrapSaxParseExceptionWithLocation() {
+ SAXParseException saxParseException = new SAXParseException("message", "", "", 5, 7);
+ List<Message> messages = MergingException
+ .wrapException(saxParseException).withFile(file).build().getMessages();
+ Message message = Iterables.getOnlyElement(messages);
+ assertEquals("message.getKind()", Message.Kind.ERROR, message.getKind());
+ assertEquals("message.getText()", "message", message.getText());
+ assertTrue("message.getRawMessage()",
+ message.getRawMessage().startsWith("org.xml.sax.SAXParseException: message\n"));
+ assertEquals("message.getSourceFilePositions()",
+ new SourceFilePosition(file, new SourcePosition(4, 6, -1)),
+ Iterables.getOnlyElement(message.getSourceFilePositions()));
+ }
+
+ @Test
+ public void testWrapSaxParseExceptionWithoutLocation() {
+ SAXParseException saxParseException = new SAXParseException("message2", "", "", -1, -1);
+
+ List<Message> messages = MergingException
+ .wrapException(saxParseException).withFile(file).build().getMessages();
+ Message message = Iterables.getOnlyElement(messages);
+ assertEquals("message.getKind()", Message.Kind.ERROR, message.getKind());
+ assertEquals("message.getText()", "message2", message.getText());
+ assertTrue("message.getRawMessage()",
+ message.getRawMessage().startsWith("org.xml.sax.SAXParseException: message2\n"));
+ assertEquals("message.getSourceFilePositions()",
+ new SourceFilePosition(file, SourcePosition.UNKNOWN),
+ Iterables.getOnlyElement(message.getSourceFilePositions()));
+ }
+
+ @Test
+ public void testThrowIfNonEmpty() throws MergingException{
+ MergingException.throwIfNonEmpty(ImmutableList.<Message>of());
+ try {
+ MergingException.throwIfNonEmpty(ImmutableList.<Message>of(
+ new Message(Message.Kind.ERROR, "Message", SourceFilePosition.UNKNOWN)));
+ fail();
+ } catch (MergingException e) {
+ // ok
+ }
+ }
+
+
+ @Test
+ public void testMergingExceptionWithNullMessage() {
+ MergingException exception = MergingException.wrapException(new IOException()).build();
+ Message message = Iterables.getOnlyElement(exception.getMessages());
+ assertNotNull(message.getText());
+ assertNotNull(message.getRawMessage());
+ }
+
+ @Test
+ public void testConsumerExceptionWithNullMessage() {
+ MergingException exception = new MergeConsumer.ConsumerException(new IOException());
+ Message message = Iterables.getOnlyElement(exception.getMessages());
+ assertNotNull(message.getText());
+ assertNotNull(message.getRawMessage());
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java
new file mode 100644
index 0000000..d4fa32d
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.SdkConstants;
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+public class NodeUtilsTest extends TestCase {
+
+ public void testBasicAttributes() throws Exception {
+ Document document = createDocument();
+
+ // create two nodes
+ Node node1 = document.createElement("N1");
+ Node node2 = document.createElement("N2");
+
+ NodeUtils.addAttribute(document, node1, null, "foo", "bar");
+ NodeUtils.addAttribute(document, node2, null, "foo", "bar");
+ assertTrue(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
+
+ NodeUtils.addAttribute(document, node1, null, "foo2", "bar2");
+ assertFalse(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
+
+ NodeUtils.addAttribute(document, node2, null, "foo2", "bar");
+ assertFalse(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
+ }
+
+ public void testNamespaceAttributes() throws Exception {
+ Document document = createDocument();
+
+ // create two nodes
+ Node node1 = document.createElement("N1");
+ Node node2 = document.createElement("N2");
+
+ NodeUtils.addAttribute(document, node1, "http://some.uri/", "foo", "bar");
+ NodeUtils.addAttribute(document, node2, "http://some.uri/", "foo", "bar");
+ assertTrue(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
+
+ NodeUtils.addAttribute(document, node1, "http://some.uri/", "foo2", "bar2");
+ NodeUtils.addAttribute(document, node2, "http://some.other.uri/", "foo2", "bar2");
+ assertFalse(NodeUtils.compareAttributes(node1.getAttributes(), node2.getAttributes()));
+ }
+
+ public void testNodesWithChildrenNodes() throws Exception {
+ Document document = createDocument();
+
+ // create two nodes
+ Node node1 = document.createElement("some-node");
+ Node node2 = document.createElement("some-node");
+
+ Node child1a = document.createElement("child1");
+ Node child1b = document.createElement("child2");
+ node1.appendChild(child1a);
+ node1.appendChild(child1b);
+
+ Node child2a = document.createElement("child1");
+ Node child2b = document.createElement("child2");
+ node2.appendChild(child2a);
+ node2.appendChild(child2b);
+
+ assertTrue(NodeUtils.compareElementNode(node1, node2, true));
+ }
+
+ public void testNodesWithChildrenNodesInWrongOrder() throws Exception {
+ Document document = createDocument();
+
+ // create two nodes
+ Node node1 = document.createElement("some-node");
+ Node node2 = document.createElement("some-node");
+
+ Node child1a = document.createElement("child1");
+ Node child1b = document.createElement("child2");
+ node1.appendChild(child1a);
+ node1.appendChild(child1b);
+
+ Node child2a = document.createElement("child2");
+ Node child2b = document.createElement("child1");
+ node2.appendChild(child2a);
+ node2.appendChild(child2b);
+
+ assertFalse(NodeUtils.compareElementNode(node1, node2, true));
+ }
+
+ public void testNodesWithChildrenNodesInOtherOrder() throws Exception {
+ Document document = createDocument();
+
+ // create two nodes
+ Node node1 = document.createElement("some-node");
+ Node node2 = document.createElement("some-node");
+
+ Node child1a = document.createElement("child1");
+ Node child1b = document.createElement("child2");
+ node1.appendChild(child1a);
+ node1.appendChild(child1b);
+
+ Node child2a = document.createElement("child2");
+ Node child2b = document.createElement("child1");
+ node2.appendChild(child2a);
+ node2.appendChild(child2b);
+
+ assertTrue(NodeUtils.compareElementNode(node1, node2, false));
+ }
+
+ public void testAdoptNode() throws Exception {
+ Document document = createDocument();
+ Node rootNode = document.createElement("root");
+ document.appendChild(rootNode);
+
+ // create a single s
+ Node node = document.createElement("some-node");
+
+ // add some children
+ Node child1 = document.createElement("child1");
+ Node child2 = document.createElement("child2");
+ node.appendChild(child1).appendChild(child2);
+ Node cdata = document.createCDATASection("some <random> text");
+ child2.appendChild(cdata);
+
+ // add some attributes
+ NodeUtils.addAttribute(document, node, null, "foo", "bar");
+ NodeUtils.addAttribute(document, node, "http://some.uri", "foo2", "bar2");
+ NodeUtils.addAttribute(document, child1, "http://some.other.uri", "blah", "test");
+ NodeUtils.addAttribute(document, child2, "http://another.uri", "blah", "test");
+
+ // create the other document to receive the adopted node. It must have a root node.
+ Document document2 = createDocument();
+ rootNode = document2.createElement("root");
+ document2.appendChild(rootNode);
+
+ assertTrue(NodeUtils.compareElementNode(node, NodeUtils.adoptNode(document2, node), true));
+ }
+
+ public void testDuplicateNode() throws Exception {
+ Document document = createDocument();
+ Node rootNode = document.createElement("root");
+ document.appendChild(rootNode);
+
+ // create a single s
+ Node node = document.createElement("some-node");
+
+ // add some children
+ Node child1 = document.createElement("child1");
+ Node child2 = document.createElement("child2");
+ node.appendChild(child1).appendChild(child2);
+ Node cdata = document.createCDATASection("some <random> text");
+ child2.appendChild(cdata);
+
+ // add some attributes
+ NodeUtils.addAttribute(document, node, null, "foo", "bar");
+ NodeUtils.addAttribute(document, node, "http://some.uri", "foo2", "bar2");
+ NodeUtils.addAttribute(document, child1, "http://some.other.uri", "blah", "test");
+ NodeUtils.addAttribute(document, child2, "http://another.uri", "blah", "test");
+
+ // create the other document to receive the adopted node. It must have a root node.
+ Document document2 = createDocument();
+ rootNode = document2.createElement("root");
+ document2.appendChild(rootNode);
+
+ assertTrue(NodeUtils.compareElementNode(
+ node,
+ NodeUtils.duplicateNode(document2, node),
+ false));
+ }
+
+ public void testDuplicateAdoptNodeUpdatesNS() throws Exception {
+ Document document = createDocument();
+ Node rootNode = document.createElement("root");
+ String nsURI1 = "http://some.uri";
+ String nsURI2 = "http://some.other.uri";
+ NodeUtils.addAttribute(document, rootNode, null, "xmlns:prefix1", nsURI1);
+ NodeUtils.addAttribute(document, rootNode, null, "xmlns:prefix2", nsURI2);
+ document.appendChild(rootNode);
+
+ Node node1 = document.createElement("N1");
+ Node node2 = document.createElement("N2");
+ rootNode.appendChild(node1).appendChild(node2);
+
+ NodeUtils.addAttribute(document, node1, nsURI1, "prefix1:foo", "bar");
+ NodeUtils.addAttribute(document, node2, nsURI2, "prefix2:baz", "zap");
+
+ // create the other document to receive the adopted node. It must have a root node.
+ Document document2 = createDocument();
+ rootNode = document2.createElement("root");
+ document2.appendChild(rootNode);
+
+ Node adoptedNode = NodeUtils.duplicateAndAdoptNode(document2, node1);
+ assertTrue(NodeUtils.compareElementNode(node1, adoptedNode, true));
+
+ // The new document should have xmlns attributes binding the prefix to the
+ // appropriate ns, and the two should not be mixed up.
+ NamedNodeMap doc2NamespaceAttrs = NodeUtils.getDocumentNamespaceAttributes(document2);
+ String prefix1 = NodeUtils.getPrefixForNs(doc2NamespaceAttrs, nsURI1);
+ String prefix2 = NodeUtils.getPrefixForNs(doc2NamespaceAttrs, nsURI2);
+ assertNotNull(prefix1);
+ assertNotNull(prefix2);
+ assertTrue(prefix1.startsWith(SdkConstants.XMLNS_PREFIX));
+ assertTrue(prefix2.startsWith(SdkConstants.XMLNS_PREFIX));
+ assertNotSame(prefix1, prefix2);
+
+ // Also check that the new prefixes are assigned to the right attribute nodes.
+ int prefixLen = SdkConstants.XMLNS_PREFIX.length();
+ Node adoptedFoo = adoptedNode.getAttributes()
+ .getNamedItem(prefix1.substring(prefixLen) + ":" + "foo");
+ assertTrue(adoptedFoo.getNodeValue().equals("bar"));
+ Node adoptedBaz = adoptedNode.getFirstChild().getAttributes()
+ .getNamedItem(prefix2.substring(prefixLen) + ":" + "baz");
+ assertTrue(adoptedBaz.getNodeValue().equals("zap"));
+
+ // Check that the original nodes are not modified.
+ Node originalFoo = node1.getAttributes().getNamedItem("prefix1:foo");
+ assertTrue(originalFoo.getNodeValue().equals("bar"));
+ Node originalBaz = node2.getAttributes().getNamedItem("prefix2:baz");
+ assertTrue(originalBaz.getNodeValue().equals("zap"));
+ }
+
+ private static Document createDocument() throws ParserConfigurationException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder;
+
+ builder = factory.newDocumentBuilder();
+ return builder.newDocument();
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/PartialFileResourceNameValidatorTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/PartialFileResourceNameValidatorTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/PartialFileResourceNameValidatorTest.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/PartialFileResourceNameValidatorTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java b/sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceFileTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceFileTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/ResourceFileTest.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/ResourceFileTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java
new file mode 100644
index 0000000..115e3c2
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java
@@ -0,0 +1,1692 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.TAG_ATTR;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.blame.MergingLog;
+import com.android.ide.common.blame.SourceFile;
+import com.android.ide.common.blame.SourceFilePosition;
+import com.android.ide.common.blame.SourcePosition;
+import com.android.ide.common.internal.PngCruncher;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.testutils.TestUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+public class ResourceMergerTest extends BaseTestCase {
+
+ @Mock
+ PngCruncher mPngCruncher;
+
+ @Mock
+ ResourcePreprocessor mPreprocessor;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ public void testMergeWithNormalizationByCount() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ assertEquals(31, merger.size());
+ }
+ public void testMergedResourcesWithNormalizationByName() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ verifyResourceExists(merger,
+ "drawable/icon",
+ "drawable-ldpi-v4/icon",
+ "drawable/icon2",
+ "drawable/patch",
+ "raw/foo",
+ "layout/main",
+ "layout/layout_ref",
+ "layout/alias_replaced_by_file",
+ "layout/file_replaced_by_alias",
+ "drawable/color_drawable",
+ "drawable/drawable_ref",
+ "color/color",
+ "string/basic_string",
+ "string/xliff_string",
+ "string/xliff_with_carriage_return",
+ "string/styled_string",
+ "style/style",
+ "array/string_array",
+ "attr/dimen_attr",
+ "attr/string_attr",
+ "attr/enum_attr",
+ "attr/flag_attr",
+ "attr/blah",
+ "attr/blah2",
+ "attr/flagAttr",
+ "declare-styleable/declare_styleable",
+ "dimen/dimen",
+ "dimen-sw600dp-v13/offset",
+ "id/item_id",
+ "integer/integer"
+ );
+ }
+
+ private static String getPlatformPath(String path) {
+ return path.replace('/', File.separatorChar);
+ }
+
+ public void testReplacedLayout() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+ ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap();
+
+ List<ResourceItem> values = mergedMap.get("layout/main");
+
+ // the overlay means there's 2 versions of this resource.
+ assertEquals(2, values.size());
+ ResourceItem mainLayout = values.get(1);
+
+ ResourceFile sourceFile = mainLayout.getSource();
+ assertTrue(sourceFile.getFile().getAbsolutePath()
+ .endsWith(getPlatformPath("overlay/layout/main.xml")));
+ }
+
+ public void testReplacedAlias() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+ ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap();
+
+ List<ResourceItem> values = mergedMap.get("layout/alias_replaced_by_file");
+
+ // the overlay means there's 2 versions of this resource.
+ assertEquals(2, values.size());
+ ResourceItem layout = values.get(1);
+
+ // since it's replaced by a file, there's no node.
+ assertNull(layout.getValue());
+ }
+
+ public void testReplacedFile() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+ ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap();
+
+ List<ResourceItem> values = mergedMap.get("layout/file_replaced_by_alias");
+
+ // the overlay means there's 2 versions of this resource.
+ assertEquals(2, values.size());
+ ResourceItem layout = values.get(1);
+
+ // since it's replaced by an alias, there's a node
+ assertNotNull(layout.getValue());
+ }
+
+ public void testMergeWrite() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+ RecordingLogger logger = new RecordingLogger();
+
+ File folder = getWrittenResources();
+
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ // compare the two maps, but not using the full map as the set loaded from the output
+ // won't contains all versions of each ResourceItem item.
+ compareResourceMaps(merger, writtenSet, false /*full compare*/);
+ checkLogger(logger);
+ }
+
+ public void testXliffString() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ // check the result of the load
+ List<ResourceItem> values = merger.getDataMap().get("string/xliff_string");
+
+ assertEquals(1, values.size());
+ ResourceItem string = values.get(0);
+
+ // Even though the content is
+ // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g>
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ assertEquals("Loaded String in merger",
+ "%1$s %2$s",
+ string.getValueText());
+
+ File folder = getWrittenResources();
+
+ RecordingLogger logger = new RecordingLogger();
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ values = writtenSet.getDataMap().get("string/xliff_string");
+
+ assertEquals(1, values.size());
+ string = values.get(0);
+
+ // Even though the content is
+ // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g>
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ assertEquals("Rewritten String through merger",
+ "%1$s %2$s",
+ string.getValueText());
+ }
+
+ public void testXliffStringWithCarriageReturn() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ // check the result of the load
+ List<ResourceItem> values = merger.getDataMap().get("string/xliff_with_carriage_return");
+
+ assertEquals(1, values.size());
+ ResourceItem string = values.get(0);
+
+ // Even though the content has xliff nodes
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ String value = string.getValueText();
+ assertEquals("Loaded String in merger",
+ "This is should be followed by whitespace:\n %1$s",
+ value);
+
+ File folder = getWrittenResources();
+
+ RecordingLogger logger = new RecordingLogger();
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ values = writtenSet.getDataMap().get("string/xliff_with_carriage_return");
+
+ assertEquals(1, values.size());
+ string = values.get(0);
+
+ // Even though the content has xliff nodes
+ // The valueText is going to skip the <g> node so we skip them from the comparison.
+ // What matters here is that the whitespaces are kept.
+ String newValue = string.getValueText();
+ assertEquals("Rewritten String through merger",
+ value,
+ newValue);
+ }
+
+ public void testNotMergedAttr() throws Exception {
+ RecordingLogger logger = new RecordingLogger();
+
+ File folder = getWrittenResources();
+
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ List<ResourceItem> items = writtenSet.getDataMap().get("attr/blah");
+ assertEquals(1, items.size());
+ assertTrue(items.get(0).getIgnoredFromDiskMerge());
+
+ checkLogger(logger);
+ }
+
+ public void testMergedAttr() throws Exception {
+ RecordingLogger logger = new RecordingLogger();
+
+ File folder = getWrittenResources();
+
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ List<ResourceItem> items = writtenSet.getDataMap().get("attr/blah2");
+ assertEquals(1, items.size());
+ assertFalse(items.get(0).getIgnoredFromDiskMerge());
+
+ checkLogger(logger);
+ }
+
+ public void testNotMergedAttrFromMerge() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ File folder = Files.createTempDir();
+ merger.writeBlobTo(folder,
+ getConsumer());
+
+ ResourceMerger loadedMerger = new ResourceMerger(0);
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
+
+ // check that attr/blah is ignoredFromDiskMerge.
+ List<ResourceItem> items = loadedMerger.getDataSets().get(0).getDataMap().get("attr/blah");
+ assertEquals(1, items.size());
+ assertTrue(items.get(0).getIgnoredFromDiskMerge());
+ }
+
+ public void testWrittenDeclareStyleable() throws Exception {
+ RecordingLogger logger = new RecordingLogger();
+
+ File folder = getWrittenResources();
+
+ ResourceSet writtenSet = new ResourceSet("unused");
+ writtenSet.addSource(folder);
+ writtenSet.loadFromFiles(logger);
+
+ List<ResourceItem> items = writtenSet.getDataMap().get("declare-styleable/declare_styleable");
+ assertEquals(1, items.size());
+
+ Node styleableNode = items.get(0).getValue();
+ assertNotNull(styleableNode);
+
+ // inspect the node
+ NodeList nodes = styleableNode.getChildNodes();
+
+ boolean foundBlah = false;
+ boolean foundAndroidColorForegroundInverse = false;
+ boolean foundBlah2 = false;
+
+ for (int i = 0, n = nodes.getLength(); i < n; i++) {
+ Node node = nodes.item(i);
+
+ if (node.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ String nodeName = node.getLocalName();
+ if (ResourceType.ATTR.getName().equals(nodeName)) {
+ Attr attribute = (Attr) node.getAttributes().getNamedItemNS(null, ATTR_NAME);
+
+ if (attribute != null) {
+ String name = attribute.getValue();
+ if ("blah".equals(name)) {
+ foundBlah = true;
+ } else if ("android:colorForegroundInverse".equals(name)) {
+ foundAndroidColorForegroundInverse = true;
+ } else if ("blah2".equals(name)) {
+ foundBlah2 = true;
+ }
+ }
+
+ }
+ }
+
+ assertTrue(foundBlah);
+ assertTrue(foundAndroidColorForegroundInverse);
+ assertTrue(foundBlah2);
+
+ checkLogger(logger);
+ }
+
+ public void testMergeBlob() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ File folder = Files.createTempDir();
+ merger.writeBlobTo(folder,
+ getConsumer());
+
+ ResourceMerger loadedMerger = new ResourceMerger(0);
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
+
+ compareResourceMaps(merger, loadedMerger, true /*full compare*/);
+ }
+
+ /**
+ * Tests the path replacement in the merger.xml file loaded from testData/
+ * @throws Exception
+ */
+ public void testLoadingTestPathReplacement() throws Exception {
+ File root = TestUtils.getRoot("resources", "baseMerge");
+ File fakeRoot = getMergedBlobFolder(root);
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ for (ResourceSet set : sets) {
+ List<File> sourceFiles = set.getSourceFiles();
+
+ // there should only be one
+ assertEquals(1, sourceFiles.size());
+
+ File sourceFile = sourceFiles.get(0);
+ assertTrue(String.format("File %s is located in %s", sourceFile, root),
+ sourceFile.getAbsolutePath().startsWith(root.getAbsolutePath()));
+ }
+ }
+
+ public void testUpdateWithBasicFiles() throws Exception {
+ File root = getIncMergeRoot("basicFiles");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // first set is the main one, no change here
+ ResourceSet mainSet = sets.get(0);
+ File mainBase = new File(root, "main");
+ File mainDrawable = new File(mainBase, "drawable");
+ File mainDrawableLdpi = new File(mainBase, "drawable-ldpi");
+
+ // touched/removed files:
+ File mainDrawableTouched = new File(mainDrawable, "touched.png");
+ mainSet.updateWith(mainBase, mainDrawableTouched, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ File mainDrawableRemoved = new File(mainDrawable, "removed.png");
+ mainSet.updateWith(mainBase, mainDrawableRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ File mainDrawableLdpiRemoved = new File(mainDrawableLdpi, "removed.png");
+ mainSet.updateWith(mainBase, mainDrawableLdpiRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // ----------------
+ // second set is the overlay one
+ ResourceSet overlaySet = sets.get(1);
+ File overlayBase = new File(root, "overlay");
+ File overlayDrawable = new File(overlayBase, "drawable");
+ File overlayDrawableHdpi = new File(overlayBase, "drawable-hdpi");
+
+ // new/removed files:
+ File overlayDrawableNewOverlay = new File(overlayDrawable, "new_overlay.png");
+ overlaySet.updateWith(overlayBase, overlayDrawableNewOverlay, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ File overlayDrawableRemovedOverlay = new File(overlayDrawable, "removed_overlay.png");
+ overlaySet.updateWith(overlayBase, overlayDrawableRemovedOverlay, FileStatus.REMOVED,
+ logger);
+ checkLogger(logger);
+
+ File overlayDrawableHdpiNewAlternate = new File(overlayDrawableHdpi, "new_alternate.png");
+ overlaySet.updateWith(overlayBase, overlayDrawableHdpiNewAlternate, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the content.
+ ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
+
+ // check unchanged file is WRITTEN
+ List<ResourceItem> drawableUntouched = mergedMap.get("drawable/untouched");
+ assertEquals(1, drawableUntouched.size());
+ assertTrue(drawableUntouched.get(0).isWritten());
+ assertFalse(drawableUntouched.get(0).isTouched());
+ assertFalse(drawableUntouched.get(0).isRemoved());
+
+ // check replaced file is TOUCHED
+ List<ResourceItem> drawableTouched = mergedMap.get("drawable/touched");
+ assertEquals(1, drawableTouched.size());
+ assertTrue(drawableTouched.get(0).isWritten());
+ assertTrue(drawableTouched.get(0).isTouched());
+ assertFalse(drawableTouched.get(0).isRemoved());
+
+ // check removed file is REMOVED
+ List<ResourceItem> drawableRemoved = mergedMap.get("drawable/removed");
+ assertEquals(1, drawableRemoved.size());
+ assertTrue(drawableRemoved.get(0).isWritten());
+ assertTrue(drawableRemoved.get(0).isRemoved());
+
+ // check new overlay: two objects, last one is TOUCHED
+ List<ResourceItem> drawableNewOverlay = mergedMap.get("drawable/new_overlay");
+ assertEquals(2, drawableNewOverlay.size());
+ ResourceItem newOverlay = drawableNewOverlay.get(1);
+ assertEquals(overlayDrawableNewOverlay, newOverlay.getSource().getFile());
+ assertFalse(newOverlay.isWritten());
+ assertTrue(newOverlay.isTouched());
+
+ // check new alternate: one objects, last one is TOUCHED
+ List<ResourceItem> drawableHdpiNewAlternate = mergedMap.get("drawable-hdpi-v4/new_alternate");
+ assertEquals(1, drawableHdpiNewAlternate.size());
+ ResourceItem newAlternate = drawableHdpiNewAlternate.get(0);
+ assertEquals(overlayDrawableHdpiNewAlternate, newAlternate.getSource().getFile());
+ assertFalse(newAlternate.isWritten());
+ assertTrue(newAlternate.isTouched());
+
+ // write and check the result of writeResourceFolder
+ // copy the current resOut which serves as pre incremental update state.
+ File resFolder = getFolderCopy(new File(root, "resOut"));
+ resFolder.deleteOnExit();
+
+ File mergeLogFolder = Files.createTempDir();
+ mergeLogFolder.deleteOnExit();
+
+ // write the content of the resource merger.
+ MergedResourceWriter writer = new MergedResourceWriter(resFolder, mPngCruncher,
+ false /*crunchPng*/, false /*process9Patch*/, null /*publicFile*/, mergeLogFolder,
+ mPreprocessor);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ // Check the content.
+ checkImageColor(new File(resFolder, "drawable" + File.separator + "touched.png"),
+ (int) 0xFF00FF00);
+ checkImageColor(new File(resFolder, "drawable" + File.separator + "untouched.png"),
+ (int) 0xFF00FF00);
+ checkImageColor(new File(resFolder, "drawable" + File.separator + "new_overlay.png"),
+ (int) 0xFF00FF00);
+ checkImageColor(new File(resFolder, "drawable" + File.separator + "removed_overlay.png"),
+ (int) 0xFF00FF00);
+ checkImageColor(new File(resFolder, "drawable-hdpi-v4" + File.separator + "new_alternate.png"),
+ (int) 0xFF00FF00);
+ assertFalse(new File(resFolder, "drawable-ldpi-v4" + File.separator + "removed.png").isFile());
+
+ // Blame log sanity check
+ MergingLog mergingLog = new MergingLog(mergeLogFolder);
+
+ SourceFile original = mergingLog.find(
+ new SourceFile(new File(resFolder, "drawable" + File.separator + "touched.png")));
+ assertTrue(original.getSourceFile().getAbsolutePath().endsWith(
+ "basicFiles/main/drawable/touched.png".replace('/', File.separatorChar)));
+ }
+
+ public void testUpdateWithBasicValues() throws Exception {
+ File root = getIncMergeRoot("basicValues");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // first set is the main one, no change here
+ ResourceSet mainSet = sets.get(0);
+ File mainBase = new File(root, "main");
+ File mainValues = new File(mainBase, "values");
+ File mainValuesEn = new File(mainBase, "values-en");
+
+ // touched file:
+ File mainValuesTouched = new File(mainValues, "values.xml");
+ mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+
+ // removed files
+ File mainValuesEnRemoved = new File(mainValuesEn, "values.xml");
+ mainSet.updateWith(mainBase, mainValuesEnRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+
+ // ----------------
+ // second set is the overlay one
+ ResourceSet overlaySet = sets.get(1);
+ File overlayBase = new File(root, "overlay");
+ File overlayValues = new File(overlayBase, "values");
+ File overlayValuesFr = new File(overlayBase, "values-fr");
+
+ // new files:
+ File overlayValuesNew = new File(overlayValues, "values.xml");
+ overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ File overlayValuesFrNew = new File(overlayValuesFr, "values.xml");
+ overlaySet.updateWith(overlayBase, overlayValuesFrNew, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the content.
+ ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
+
+ // check unchanged string is WRITTEN
+ List<ResourceItem> valuesUntouched = mergedMap.get("string/untouched");
+ assertEquals(1, valuesUntouched.size());
+ assertTrue(valuesUntouched.get(0).isWritten());
+ assertFalse(valuesUntouched.get(0).isTouched());
+ assertFalse(valuesUntouched.get(0).isRemoved());
+
+ // check replaced file is TOUCHED
+ List<ResourceItem> valuesTouched = mergedMap.get("string/touched");
+ assertEquals(1, valuesTouched.size());
+ assertTrue(valuesTouched.get(0).isWritten());
+ assertTrue(valuesTouched.get(0).isTouched());
+ assertFalse(valuesTouched.get(0).isRemoved());
+
+ // check removed file is REMOVED
+ List<ResourceItem> valuesRemoved = mergedMap.get("string/removed");
+ assertEquals(1, valuesRemoved.size());
+ assertTrue(valuesRemoved.get(0).isWritten());
+ assertTrue(valuesRemoved.get(0).isRemoved());
+
+ valuesRemoved = mergedMap.get("string-en/removed");
+ assertEquals(1, valuesRemoved.size());
+ assertTrue(valuesRemoved.get(0).isWritten());
+ assertTrue(valuesRemoved.get(0).isRemoved());
+
+ // check new overlay: two objects, last one is TOUCHED
+ List<ResourceItem> valuesNewOverlay = mergedMap.get("string/new_overlay");
+ assertEquals(2, valuesNewOverlay.size());
+ ResourceItem newOverlay = valuesNewOverlay.get(1);
+ assertFalse(newOverlay.isWritten());
+ assertTrue(newOverlay.isTouched());
+
+ // check new alternate: one objects, last one is TOUCHED
+ List<ResourceItem> valuesFrNewAlternate = mergedMap.get("string-fr/new_alternate");
+ assertEquals(1, valuesFrNewAlternate.size());
+ ResourceItem newAlternate = valuesFrNewAlternate.get(0);
+ assertFalse(newAlternate.isWritten());
+ assertTrue(newAlternate.isTouched());
+
+ // write and check the result of writeResourceFolder
+ // copy the current resOut which serves as pre incremental update state.
+ File resFolder = getFolderCopy(new File(root, "resOut"));
+
+
+ File mergeLogFolder = Files.createTempDir();
+ mergeLogFolder.deleteOnExit();
+
+ // write the content of the resource merger.
+ MergedResourceWriter writer = new MergedResourceWriter(
+ resFolder,
+ mPngCruncher,
+ false /*crunchPng*/,
+ false /*process9Patch*/,
+ null /*publicFile*/,
+ mergeLogFolder,
+ mPreprocessor);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ // Check the content.
+ // values/values.xml
+ Map<String, String> map = quickStringOnlyValueFileParser(
+ new File(resFolder, "values" + File.separator + "values.xml"));
+ assertEquals("untouched", map.get("untouched"));
+ assertEquals("touched", map.get("touched"));
+ assertEquals("new_overlay", map.get("new_overlay"));
+
+ // values-fr/values-fr.xml
+ map = quickStringOnlyValueFileParser(
+ new File(resFolder, "values-fr" + File.separator + "values-fr.xml"));
+ assertEquals("new_alternate", map.get("new_alternate"));
+
+ // deleted values-en/values-en.xml
+ assertFalse(new File(resFolder, "values-en" + File.separator + "values-en.xml").isFile());
+
+ // Blame log sanity check.
+ MergingLog mergingLog = new MergingLog(mergeLogFolder);
+
+ SourceFilePosition original = mergingLog.find(new SourceFilePosition(
+ new SourceFile(new File(resFolder, "values" + File.separator + "values.xml")),
+ new SourcePosition(2,5,-1)));
+
+ assertEquals(new SourcePosition(2, 4, 55, 2, 51, 102), original.getPosition());
+ assertTrue(original.getFile().getSourceFile().getAbsolutePath().endsWith(
+ "basicValues/overlay/values/values.xml".replace('/', File.separatorChar)));
+
+ }
+
+ public void testUpdateWithBasicValues2() throws Exception {
+ File root = getIncMergeRoot("basicValues2");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // first set is the main one, no change here
+
+ // ----------------
+ // second set is the overlay one
+ ResourceSet overlaySet = sets.get(1);
+ File overlayBase = new File(root, "overlay");
+ File overlayValues = new File(overlayBase, "values");
+
+ // new files:
+ File overlayValuesNew = new File(overlayValues, "values.xml");
+ overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the content.
+ ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
+
+ // check unchanged string is WRITTEN
+ List<ResourceItem> valuesUntouched = mergedMap.get("string/untouched");
+ assertEquals(1, valuesUntouched.size());
+ assertTrue(valuesUntouched.get(0).isWritten());
+ assertFalse(valuesUntouched.get(0).isTouched());
+ assertFalse(valuesUntouched.get(0).isRemoved());
+
+ // check removed_overlay is present twice.
+ List<ResourceItem> valuesRemovedOverlay = mergedMap.get("string/removed_overlay");
+ assertEquals(2, valuesRemovedOverlay.size());
+ // first is untouched
+ assertFalse(valuesRemovedOverlay.get(0).isWritten());
+ assertFalse(valuesRemovedOverlay.get(0).isTouched());
+ assertFalse(valuesRemovedOverlay.get(0).isRemoved());
+ // other is removed
+ assertTrue(valuesRemovedOverlay.get(1).isWritten());
+ assertFalse(valuesRemovedOverlay.get(1).isTouched());
+ assertTrue(valuesRemovedOverlay.get(1).isRemoved());
+
+ // write and check the result of writeResourceFolder
+ // copy the current resOut which serves as pre incremental update state.
+ File resFolder = getFolderCopy(new File(root, "resOut"));
+
+ // write the content of the resource merger.
+ MergedResourceWriter writer = getConsumer(resFolder);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ // Check the content.
+ // values/values.xml
+ Map<String, String> map = quickStringOnlyValueFileParser(
+ new File(resFolder, "values" + File.separator + "values.xml"));
+ assertEquals("untouched", map.get("untouched"));
+ assertEquals("untouched", map.get("removed_overlay"));
+ }
+
+ public void testUpdateWithFilesVsValues() throws Exception {
+ File root = getIncMergeRoot("filesVsValues");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(1, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // Load the main set
+ ResourceSet mainSet = sets.get(0);
+ File mainBase = new File(root, "main");
+ File mainValues = new File(mainBase, ResourceFolderType.VALUES.getName());
+ File mainLayout = new File(mainBase, ResourceFolderType.LAYOUT.getName());
+
+ // touched file:
+ File mainValuesTouched = new File(mainValues, "values.xml");
+ mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // new file:
+ File mainLayoutNew = new File(mainLayout, "alias_replaced_by_file.xml");
+ mainSet.updateWith(mainBase, mainLayoutNew, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ // removed file
+ File mainLayoutRemoved = new File(mainLayout, "file_replaced_by_alias.xml");
+ mainSet.updateWith(mainBase, mainLayoutRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the content.
+ ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
+
+ // check layout/main is unchanged
+ List<ResourceItem> layoutMain = mergedMap.get("layout/main");
+ assertEquals(1, layoutMain.size());
+ assertTrue(layoutMain.get(0).isWritten());
+ assertFalse(layoutMain.get(0).isTouched());
+ assertFalse(layoutMain.get(0).isRemoved());
+
+ // check file_replaced_by_alias has 2 version, 2nd is TOUCHED, and contains a Node
+ List<ResourceItem> layoutReplacedByAlias = mergedMap.get("layout/file_replaced_by_alias");
+ assertEquals(2, layoutReplacedByAlias.size());
+ // 1st one is removed version, as it already existed in the item multimap
+ ResourceItem replacedByAlias = layoutReplacedByAlias.get(0);
+ assertTrue(replacedByAlias.isWritten());
+ assertFalse(replacedByAlias.isTouched());
+ assertTrue(replacedByAlias.isRemoved());
+ assertNull(replacedByAlias.getValue());
+ assertEquals("file_replaced_by_alias.xml", replacedByAlias.getSource().getFile().getName());
+ // 2nd version is the new one
+ replacedByAlias = layoutReplacedByAlias.get(1);
+ assertFalse(replacedByAlias.isWritten());
+ assertTrue(replacedByAlias.isTouched());
+ assertFalse(replacedByAlias.isRemoved());
+ assertNotNull(replacedByAlias.getValue());
+ assertEquals("values.xml", replacedByAlias.getSource().getFile().getName());
+
+ // check alias_replaced_by_file has 2 version, 2nd is TOUCHED, and contains a Node
+ List<ResourceItem> layoutReplacedByFile = mergedMap.get("layout/alias_replaced_by_file");
+ // 1st one is removed version, as it already existed in the item multimap
+ assertEquals(2, layoutReplacedByFile.size());
+ ResourceItem replacedByFile = layoutReplacedByFile.get(0);
+ assertTrue(replacedByFile.isWritten());
+ assertFalse(replacedByFile.isTouched());
+ assertTrue(replacedByFile.isRemoved());
+ assertNotNull(replacedByFile.getValue());
+ assertEquals("values.xml", replacedByFile.getSource().getFile().getName());
+ // 2nd version is the new one
+ replacedByFile = layoutReplacedByFile.get(1);
+ assertFalse(replacedByFile.isWritten());
+ assertTrue(replacedByFile.isTouched());
+ assertFalse(replacedByFile.isRemoved());
+ assertNull(replacedByFile.getValue());
+ assertEquals("alias_replaced_by_file.xml", replacedByFile.getSource().getFile().getName());
+
+ // write and check the result of writeResourceFolder
+ // copy the current resOut which serves as pre incremental update state.
+ File resFolder = getFolderCopy(new File(root, "resOut"));
+
+ // write the content of the resource merger.
+ MergedResourceWriter writer = getConsumer(resFolder);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ // deleted layout/file_replaced_by_alias.xml
+ assertFalse(new File(resFolder, "layout" + File.separator + "file_replaced_by_alias.xml")
+ .isFile());
+ // new file layout/alias_replaced_by_file.xml
+ assertTrue(new File(resFolder, "layout" + File.separator + "alias_replaced_by_file.xml")
+ .isFile());
+
+ // quick load of the values file
+ File valuesFile = new File(resFolder, "values" + File.separator + "values.xml");
+ assertTrue(valuesFile.isFile());
+ String content = Files.toString(valuesFile, Charsets.UTF_8);
+ assertTrue(content.contains("name=\"file_replaced_by_alias\""));
+ assertFalse(content.contains("name=\"alias_replaced_by_file\""));
+ }
+
+ public void testCheckValidUpdate() throws Exception {
+ // first merger
+ ResourceMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", ("/main/res1"), ("/main/res2") },
+ new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
+ });
+
+ // 2nd merger with different order source files in sets.
+ ResourceMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", ("/main/res2"), ("/main/res1") },
+ new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") },
+ });
+
+ assertTrue(merger1.checkValidUpdate(merger2.getDataSets()));
+
+ // write merger1 on disk to test writing empty ResourceSets.
+ File folder = Files.createTempDir();
+ merger1.writeBlobTo(folder,
+ getConsumer());
+
+ // reload it
+ ResourceMerger loadedMerger = new ResourceMerger(0);
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
+
+ String expected = merger1.toString();
+ String actual = loadedMerger.toString();
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ expected = expected.replace(File.separatorChar, '/').
+ replaceAll("[A-Z]:/", "/");
+ actual = actual.replace(File.separatorChar, '/').
+ replaceAll("[A-Z]:/", "/");
+ assertEquals("Actual: " + actual + "\nExpected: " + expected, expected, actual);
+ } else {
+ assertTrue("Actual: " + actual + "\nExpected: " + expected,
+ loadedMerger.checkValidUpdate(merger1.getDataSets()));
+ }
+ }
+
+ public void testUpdateWithRemovedOverlay() throws Exception {
+ // Test with removed overlay
+ ResourceMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ ResourceMerger merger2 = createMerger(new String[][]{
+ new String[]{"main", "/main/res2", "/main/res1"},
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testUpdateWithReplacedOverlays() throws Exception {
+ // Test with different overlays
+ ResourceMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ new String[] { "overlay", "/overlay/res1", "/overlay/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ ResourceMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", "/main/res2", "/main/res1" },
+ new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testUpdateWithReorderedOverlays() throws Exception {
+ // Test with different overlays
+ ResourceMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
+ new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ ResourceMerger merger2 = createMerger(new String[][] {
+ new String[] { "main", "/main/res2", "/main/res1" },
+ new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" },
+ new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" },
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testUpdateWithRemovedSourceFile() throws Exception {
+ // Test with different source files
+ ResourceMerger merger1 = createMerger(new String[][] {
+ new String[] { "main", "/main/res1", "/main/res2" },
+ });
+
+ // 2nd merger with different order source files in sets.
+ ResourceMerger merger2 = createMerger(new String[][]{
+ new String[]{"main", "/main/res1"},
+ });
+
+ assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
+ }
+
+ public void testChangedIgnoredFile() throws Exception {
+ ResourceSet res = ResourceSetTest.getBaseResourceSet();
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.addDataSet(res);
+
+ File root = TestUtils.getRoot("resources", "baseSet");
+ File changedCVSFoo = new File(root, "CVS/foo.txt");
+ FileValidity<ResourceSet> fileValidity = resourceMerger.findDataSetContaining(
+ changedCVSFoo);
+
+ assertEquals(FileValidity.FileStatus.IGNORED_FILE, fileValidity.status);
+ }
+
+ public void testIncDataForRemovedFile() throws Exception {
+ File root = TestUtils.getCanonicalRoot("resources", "removedFile");
+ File fakeBlobRoot = getMergedBlobFolder(root);
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(1, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // Load the main set
+ ResourceSet mainSet = sets.get(0);
+ File resBase = new File(root, "res");
+ File resDrawable = new File(resBase, ResourceFolderType.DRAWABLE.getName());
+
+ // removed file
+ File resIconRemoved = new File(resDrawable, "removed.png");
+ mainSet.updateWith(resBase, resIconRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the content.
+ ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap();
+
+ // check layout/main is unchanged
+ List<ResourceItem> removedIcon = mergedMap.get("drawable/removed");
+ assertEquals(1, removedIcon.size());
+ assertTrue(removedIcon.get(0).isRemoved());
+ assertTrue(removedIcon.get(0).isWritten());
+ assertFalse(removedIcon.get(0).isTouched());
+
+ // write and check the result of writeResourceFolder
+ // copy the current resOut which serves as pre incremental update state.
+ File outFolder = getFolderCopy(new File(root, "out"));
+
+ // write the content of the resource merger.
+ MergedResourceWriter writer = getConsumer(outFolder);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ File outDrawableFolder = new File(outFolder, ResourceFolderType.DRAWABLE.getName());
+
+ // check the files are correct
+ assertFalse(new File(outDrawableFolder, "removed.png").isFile());
+ assertTrue(new File(outDrawableFolder, "icon.png").isFile());
+
+ // now write the blob
+ File outBlobFolder = Files.createTempDir();
+ resourceMerger.writeBlobTo(outBlobFolder, writer);
+
+ // check the removed icon is not present.
+ ResourceMerger resourceMerger2 = new ResourceMerger(0);
+ assertTrue(resourceMerger2.loadFromBlob(outBlobFolder, true /*incrementalState*/));
+
+ mergedMap = resourceMerger2.getDataMap();
+ removedIcon = mergedMap.get("drawable/removed");
+ assertTrue(removedIcon.isEmpty());
+ }
+
+ public void testMergedDeclareStyleable() throws Exception {
+ File root = TestUtils.getRoot("resources", "declareStyleable");
+
+ // load both base and overlay set
+ File baseRoot = new File(root, "base");
+ ResourceSet baseSet = new ResourceSet("main");
+ baseSet.addSource(baseRoot);
+ RecordingLogger logger = new RecordingLogger();
+ baseSet.loadFromFiles(logger);
+ checkLogger(logger);
+
+ File overlayRoot = new File(root, "overlay");
+ ResourceSet overlaySet = new ResourceSet("overlay");
+ overlaySet.addSource(overlayRoot);
+ logger = new RecordingLogger();
+ overlaySet.loadFromFiles(logger);
+ checkLogger(logger);
+
+ // create a merger
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.addDataSet(baseSet);
+ resourceMerger.addDataSet(overlaySet);
+
+ // write the merge result.
+ File folder = Files.createTempDir();
+ folder.deleteOnExit();
+
+ MergedResourceWriter writer = getConsumer(folder);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ // load the result as a set.
+ ResourceSet mergedSet = new ResourceSet("merged");
+ mergedSet.addSource(folder);
+ logger = new RecordingLogger();
+ mergedSet.loadFromFiles(logger);
+ checkLogger(logger);
+
+ ListMultimap<String, ResourceItem> map = mergedSet.getDataMap();
+ assertEquals(4, map.size());
+
+ List<ResourceItem> items = map.get("declare-styleable/foo");
+ assertNotNull(items);
+ assertEquals(1, items.size());
+
+ ResourceItem item = items.get(0);
+ assertNotNull(item);
+
+ // now we need to look at the item's value (which is the XML).
+ // We're looking for 3 attributes.
+ List<String> expectedAttrs = Lists.newArrayList("bar", "bar1", "boo");
+ Node rootNode = item.getValue();
+ assertNotNull(rootNode);
+ NodeList sourceNodes = rootNode.getChildNodes();
+ for (int i = 0, n = sourceNodes.getLength(); i < n; i++) {
+ Node sourceNode = sourceNodes.item(i);
+
+ if (sourceNode.getNodeType() != Node.ELEMENT_NODE ||
+ !TAG_ATTR.equals(sourceNode.getLocalName())) {
+ continue;
+ }
+
+ Attr attr = (Attr) sourceNode.getAttributes().getNamedItem(ATTR_NAME);
+ if (attr == null) {
+ continue;
+ }
+
+ String attrName = attr.getValue();
+
+ assertTrue("Check expected " + attrName, expectedAttrs.contains(attrName));
+ expectedAttrs.remove(attrName);
+ }
+
+ assertTrue("Check emptiness of " + expectedAttrs.toString(), expectedAttrs.isEmpty());
+ }
+
+ public void testUnchangedMergedItem() throws Exception {
+ // locate the merger file that contains exactly the result of the source folders.
+ File root = TestUtils.getRoot("resources", "declareStyleable");
+ File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "unchanged_merger.xml"));
+
+ // load a resource merger based on it.
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ // create a fake consumer
+ FakeMergeConsumer consumer = new FakeMergeConsumer();
+
+ // do the merge
+ resourceMerger.mergeData(consumer, false /*doCleanUp*/);
+
+ // test result of merger.
+ assertTrue(consumer.touchedItems.isEmpty());
+ assertTrue(consumer.removedItems.isEmpty());
+ }
+
+ public void testRemovedMergedItem() throws Exception {
+ // locate the merger file that contains exactly the result of the source folders.
+ File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
+ File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "removed_merger.xml"));
+
+ // load a resource merger based on it.
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ // we know have to tell the merger that the values files have been touched
+ // to trigger the removal detection based on the original merger blob.
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // Load the main set
+ ResourceSet mainSet = sets.get(0);
+ File mainRoot = new File(root, "base");
+ File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName());
+
+ // trigger changed file event
+ File touchedValueFile = new File(mainValues, "values.xml");
+ mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // same with overlay set.
+ ResourceSet overlaySet = sets.get(1);
+ File overlayRoot = new File(root, "overlay");
+ File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName());
+
+ // trigger changed file event
+ touchedValueFile = new File(overlayValues, "values.xml");
+ overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // create a fake consumer
+ FakeMergeConsumer consumer = new FakeMergeConsumer();
+
+ // do the merge
+ resourceMerger.mergeData(consumer, false /*doCleanUp*/);
+
+ // test result of merger.
+ assertTrue(consumer.touchedItems.isEmpty());
+ assertEquals(1, consumer.removedItems.size());
+ }
+
+ public void testTouchedMergedItem() throws Exception {
+ // locate the merger file that contains exactly the result of the source folders.
+ File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
+ File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "touched_merger.xml"));
+
+ // load a resource merger based on it.
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ // we know have to tell the merger that the values files have been touched
+ // to trigger the removal detection based on the original merger blob.
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // Load the main set
+ ResourceSet mainSet = sets.get(0);
+ File mainRoot = new File(root, "base");
+ File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName());
+
+ // trigger changed file event
+ File touchedValueFile = new File(mainValues, "values.xml");
+ mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // create a fake consumer
+ FakeMergeConsumer consumer = new FakeMergeConsumer();
+
+ // do the merge
+ resourceMerger.mergeData(consumer, false /*doCleanUp*/);
+
+ // test result of merger.
+ assertEquals(1, consumer.touchedItems.size());
+ assertTrue(consumer.removedItems.isEmpty());
+ }
+
+ public void testTouchedNoDiffMergedItem() throws Exception {
+ // locate the merger file that contains exactly the result of the source folders.
+ File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
+ File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "touched_nodiff_merger.xml"));
+
+ // load a resource merger based on it.
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ // we know have to tell the merger that the values files have been touched
+ // to trigger the removal detection based on the original merger blob.
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // Load the overlay set
+ ResourceSet overlaySet = sets.get(1);
+ File overlayRoot = new File(root, "overlay");
+ File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName());
+
+ // trigger changed file event
+ File touchedValueFile = new File(overlayValues, "values.xml");
+ overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // create a fake consumer
+ FakeMergeConsumer consumer = new FakeMergeConsumer();
+
+ // do the merge
+ resourceMerger.mergeData(consumer, false /*doCleanUp*/);
+
+ // test result of merger.
+ assertTrue(consumer.touchedItems.isEmpty());
+ assertTrue(consumer.removedItems.isEmpty());
+ }
+
+ public void testRemovedOtherWithNoNoDiffTouchMergedItem() throws Exception {
+ // test that when a non-merged resources is changed/removed, the result of the merge still
+ // contain the merged items even if they were touched but had no change.
+
+ // locate the merger file that contains exactly the result of the source folders.
+ File root = TestUtils.getCanonicalRoot("resources", "declareStyleable");
+ File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "removed_other_merger.xml"));
+
+ // load a resource merger based on it.
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/));
+ checkSourceFolders(resourceMerger);
+
+ // we know have to tell the merger that the values files have been touched
+ // to trigger the removal detection based on the original merger blob.
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // Load the main set
+ ResourceSet mainSet = sets.get(0);
+ File mainRoot = new File(root, "base");
+ File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName());
+
+ // trigger changed file event
+ File touchedValueFile = new File(mainValues, "values.xml");
+ mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // same for overlay
+ ResourceSet overlaySet = sets.get(1);
+ File overlayRoot = new File(root, "overlay");
+ File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName());
+
+ // trigger changed file event
+ touchedValueFile = new File(overlayValues, "values.xml");
+ overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // create a fake consumer
+ FakeMergeConsumer consumer = new FakeMergeConsumer();
+
+ // do the merge
+ resourceMerger.mergeData(consumer, false /*doCleanUp*/);
+
+ // test result of merger.
+ // only 3 items added since attr/bar isn't added (declared inline)
+ assertEquals(3, consumer.addedItems.size());
+ // no touched items
+ assertTrue(consumer.touchedItems.isEmpty());
+ // one removed string item
+ assertEquals(1, consumer.removedItems.size());
+ }
+
+ public void testStringWhiteSpaces() throws Exception {
+ File root = TestUtils.getRoot("resources", "stringWhiteSpaces");
+
+ // load res folder
+ ResourceSet baseSet = new ResourceSet("main");
+ baseSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+ baseSet.loadFromFiles(logger);
+ checkLogger(logger);
+
+ // create a merger
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.addDataSet(baseSet);
+
+ // write the merge result.
+ File folder = Files.createTempDir();
+ folder.deleteOnExit();
+
+ MergedResourceWriter writer = getConsumer(folder);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ // load the result as a set.
+ ResourceSet mergedSet = new ResourceSet("merged");
+ mergedSet.addSource(folder);
+ logger = new RecordingLogger();
+ mergedSet.loadFromFiles(logger);
+ checkLogger(logger);
+
+ ListMultimap<String, ResourceItem> originalItems = baseSet.getDataMap();
+ ListMultimap<String, ResourceItem> mergedItems = mergedSet.getDataMap();
+
+ for (Map.Entry<String, Collection<ResourceItem>> entry : originalItems.asMap().entrySet()) {
+ Collection<ResourceItem> originalItemList = entry.getValue();
+ Collection<ResourceItem> mergedItemList = mergedItems.asMap().get(entry.getKey());
+
+ // the collection should only have a single items
+ assertEquals(1, originalItemList.size());
+ assertEquals(1, mergedItemList.size());
+
+ ResourceItem originalItem = originalItemList.iterator().next();
+ ResourceItem mergedItem = mergedItemList.iterator().next();
+
+ assertTrue(originalItem.compareValueWith(mergedItem));
+ }
+ }
+
+ /**
+ * Creates a fake merge with given sets.
+ *
+ * the data is an array of sets.
+ *
+ * Each set is [ setName, folder1, folder2, ...]
+ *
+ * @param data the data sets
+ * @return the merger
+ */
+ private static ResourceMerger createMerger(String[][] data) {
+ ResourceMerger merger = new ResourceMerger(0);
+ for (String[] setData : data) {
+ ResourceSet set = new ResourceSet(setData[0]);
+ merger.addDataSet(set);
+ for (int i = 1, n = setData.length; i < n; i++) {
+ set.addSource(new File(setData[i]));
+ }
+ }
+
+ return merger;
+ }
+
+ private static ResourceMerger getResourceMerger()
+ throws MergingException, IOException {
+ File root = TestUtils.getRoot("resources", "baseMerge");
+
+ ResourceSet res = ResourceSetTest.getBaseResourceSet();
+
+ RecordingLogger logger = new RecordingLogger();
+
+ ResourceSet overlay = new ResourceSet("overlay");
+ overlay.addSource(new File(root, "overlay"));
+ overlay.loadFromFiles(logger);
+
+ checkLogger(logger);
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.addDataSet(res);
+ resourceMerger.addDataSet(overlay);
+
+ return resourceMerger;
+ }
+
+ private File getWrittenResources() throws MergingException, IOException {
+ ResourceMerger resourceMerger = getResourceMerger();
+
+ File folder = Files.createTempDir();
+
+ MergedResourceWriter writer = getConsumer(folder);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+
+ return folder;
+ }
+
+ private static File getIncMergeRoot(String name) throws IOException {
+ File root = TestUtils.getCanonicalRoot("resources", "incMergeData");
+ return new File(root, name);
+ }
+
+ private static File getFolderCopy(File folder) throws IOException {
+ File dest = Files.createTempDir();
+ copyFolder(folder, dest);
+ return dest;
+ }
+
+ private static void copyFolder(File from, File to) throws IOException {
+ if (from.isFile()) {
+ Files.copy(from, to);
+ } else if (from.isDirectory()) {
+ if (!to.exists()) {
+ to.mkdirs();
+ }
+
+ File[] children = from.listFiles();
+ if (children != null) {
+ for (File f : children) {
+ copyFolder(f, new File(to, f.getName()));
+ }
+ }
+ }
+ }
+
+ private static Map<String, String> quickStringOnlyValueFileParser(File file)
+ throws IOException, MergingException {
+ Map<String, String> result = Maps.newHashMap();
+
+ Document document = ValueResourceParser2.parseDocument(file);
+
+ // get the root node
+ Node rootNode = document.getDocumentElement();
+ if (rootNode == null) {
+ return Collections.emptyMap();
+ }
+
+ NodeList nodes = rootNode.getChildNodes();
+
+ for (int i = 0, n = nodes.getLength(); i < n; i++) {
+ Node node = nodes.item(i);
+
+ if (node.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ if (node.getNodeName().equals(SdkConstants.TAG_EAT_COMMENT)) {
+ continue;
+ }
+
+ ResourceType type = ValueResourceParser2.getType(node, file);
+ if (type != ResourceType.STRING) {
+ throw new IllegalArgumentException("Only String resources supported.");
+ }
+ String name = ValueResourceParser2.getName(node);
+
+ String value = null;
+
+ NodeList nodeList = node.getChildNodes();
+ nodeLoop: for (int ii = 0, nn = nodes.getLength(); ii < nn; ii++) {
+ Node subNode = nodeList.item(ii);
+
+ switch (subNode.getNodeType()) {
+ case Node.COMMENT_NODE:
+ break;
+ case Node.TEXT_NODE:
+ value = subNode.getNodeValue().trim(); // TODO: remove trim.
+ break nodeLoop;
+ case Node.ELEMENT_NODE:
+ break;
+ }
+ }
+
+ result.put(name, value != null ? value : "");
+ }
+
+ return result;
+ }
+
+ public void testWritePermission() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ File folder = Files.createTempDir();
+ boolean writable = folder.setWritable(false);
+ if (!writable) {
+ // Not supported on this platform
+ return;
+ }
+ try {
+ merger.writeBlobTo(folder,
+ getConsumer());
+ } catch (MergingException e) {
+ File file = new File(folder, "merger.xml");
+ assertEquals(file.getPath() + ": Error: (Permission denied)",
+ e.getMessage());
+ return;
+ }
+ fail("Exception not thrown as expected");
+ }
+
+ public void testWriteAndReadBlob() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ File folder = Files.createTempDir();
+ merger.writeBlobTo(folder,
+ getConsumer());
+
+ // new merger to read the blob
+ ResourceMerger loadedMerger = new ResourceMerger(0);
+ assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/));
+ }
+
+ public void testInvalidFileNames() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet5");
+ ResourceSet resourceSet = new ResourceSet("brokenSet5");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+
+ try {
+ resourceSet.loadFromFiles(logger);
+ } catch (MergingException e) {
+ File file = new File(root, "layout" + File.separator + "ActivityMain.xml");
+ file = file.getAbsoluteFile();
+ assertEquals(
+ file.getPath() +
+ ": Error: 'A' is not a valid file-based resource name character: "
+ + "File-based resource names must contain only lowercase a-z, 0-9,"
+ + " or underscore",
+ e.getMessage());
+ return;
+ }
+ fail("Expected error");
+ }
+
+ public void testStricterInvalidFileNames() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSetDrawableFileName");
+ ResourceSet resourceSet = new ResourceSet("brokenSetDrawableFileName");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+
+ try {
+ resourceSet.loadFromFiles(logger);
+ } catch (MergingException e) {
+ File file = new File(root, "drawable" + File.separator + "1icon.png");
+ file = file.getAbsoluteFile();
+ assertEquals(
+ file.getPath() +
+ ": Error: The resource name must start with a letter",
+ e.getMessage());
+ return;
+ }
+ fail("Expected error");
+ }
+
+ public void testXmlParseError1() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet6");
+ try {
+ ResourceSet resourceSet = new ResourceSet("brokenSet6");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+ resourceSet.loadFromFiles(logger);
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.addDataSet(resourceSet);
+
+
+ MergedResourceWriter writer = getConsumer();
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+ } catch (MergingException e) {
+ File file = new File(root, "values" + File.separator + "dimens.xml");
+ file = file.getAbsoluteFile();
+ assertEquals(file.getPath() + ":4:6: Error: The content of elements must consist "
+ + "of well-formed character data or markup.",
+ e.getMessage());
+ return;
+ }
+ fail("Expected error");
+ }
+
+ public void testXmlParseError7() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet7");
+ try {
+ ResourceSet resourceSet = new ResourceSet("brokenSet7");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+ resourceSet.loadFromFiles(logger);
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.addDataSet(resourceSet);
+
+
+ MergedResourceWriter writer = getConsumer();
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+ } catch (MergingException e) {
+ File file = new File(root, "values" + File.separator + "dimens.xml");
+ file = file.getAbsoluteFile();
+ assertTrue(e.getMessage().startsWith(file.getPath() + ":2:17"));
+ return;
+ }
+ fail("Expected error");
+ }
+
+ public void testSdkFiltering() throws Exception {
+ ResourceSet resourceSet = new ResourceSet("filterableSet");
+ resourceSet.addSource(TestUtils.getRoot("resources", "filterableSet"));
+ resourceSet.loadFromFiles(new RecordingLogger());
+
+ ResourceMerger resourceMerger = new ResourceMerger(21);
+ resourceMerger.addDataSet(resourceSet);
+
+ MergedResourceWriter consumer = getConsumer();
+ resourceMerger.mergeData(consumer, false);
+
+ File wroteRoot = consumer.getRootFolder();
+ assertTrue(wroteRoot.isDirectory());
+ assertEquals(1, wroteRoot.listFiles().length);
+
+ File v21 = new File(wroteRoot, "raw-v21");
+ assertTrue(v21.isDirectory());
+ assertEquals(1, v21.listFiles().length);
+
+ File foo = new File(v21, "foo.txt");
+ assertTrue(foo.isFile());
+
+ String fooContents = Files.toString(foo, Charset.defaultCharset());
+ assertEquals("21st foo", fooContents);
+ }
+
+
+ // create a fake consumer
+ private static class FakeMergeConsumer implements MergeConsumer<ResourceItem> {
+ final List<ResourceItem> addedItems = Lists.newArrayList();
+ final List<ResourceItem> touchedItems = Lists.newArrayList();
+ final List<ResourceItem> removedItems = Lists.newArrayList();
+
+ @Override
+ public void start(@NonNull DocumentBuilderFactory factory)
+ throws ConsumerException {
+ // do nothing
+ }
+
+ @Override
+ public void end() throws ConsumerException {
+ // do nothing
+ }
+
+ @Override
+ public void addItem(@NonNull ResourceItem item) throws ConsumerException {
+ // the default res merge writer calls this, so we should too.
+ // this is to test that the merged item are properly created
+ @SuppressWarnings("UnusedDeclaration")
+ ResourceFile.FileType type = item.getSourceType();
+
+ if (item.isTouched()) {
+ touchedItems.add(item);
+ }
+
+ addedItems.add(item);
+ }
+
+ @Override
+ public void removeItem(@NonNull ResourceItem removedItem,
+ @Nullable ResourceItem replacedBy)
+ throws ConsumerException {
+ removedItems.add(removedItem);
+ }
+
+ @Override
+ public boolean ignoreItemInMerge(ResourceItem item) {
+ return item.getIgnoredFromDiskMerge();
+ }
+ }
+
+ @NonNull
+ private MergedResourceWriter getConsumer() {
+ return getConsumer(Files.createTempDir());
+ }
+
+ @NonNull
+ private MergedResourceWriter getConsumer(File tempDir) {
+ return new MergedResourceWriter(
+ tempDir,
+ mPngCruncher,
+ false /*crunchPng*/,
+ false /*process9Patch*/,
+ null /*publicFile*/,
+ null /*blameLogFolder*/,
+ mPreprocessor);
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java
new file mode 100644
index 0000000..83df58d
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.ide.common.rendering.api.AttrResourceValue;
+import com.android.ide.common.rendering.api.ItemResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.testutils.TestUtils;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+public class ResourceRepositoryTest extends BaseTestCase {
+
+ public void testMergeByCount() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
+
+ assertEquals(6, items.get(ResourceType.DRAWABLE).size());
+ assertEquals(1, items.get(ResourceType.RAW).size());
+ assertEquals(4, items.get(ResourceType.LAYOUT).size());
+ assertEquals(1, items.get(ResourceType.COLOR).size());
+ assertEquals(4, items.get(ResourceType.STRING).size());
+ assertEquals(1, items.get(ResourceType.STYLE).size());
+ assertEquals(1, items.get(ResourceType.ARRAY).size());
+ assertEquals(7, items.get(ResourceType.ATTR).size());
+ assertEquals(1, items.get(ResourceType.DECLARE_STYLEABLE).size());
+ assertEquals(2, items.get(ResourceType.DIMEN).size());
+ assertEquals(1, items.get(ResourceType.ID).size());
+ assertEquals(1, items.get(ResourceType.INTEGER).size());
+ }
+
+ public void testMergedResourcesByName() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ // use ? between type and qualifier because of declare-styleable
+ verifyResourceExists(repo,
+ "drawable/icon",
+ "drawable?ldpi-v4/icon",
+ "drawable/icon2",
+ "drawable/patch",
+ "drawable/color_drawable",
+ "drawable/drawable_ref",
+ "raw/foo",
+ "layout/main",
+ "layout/layout_ref",
+ "layout/alias_replaced_by_file",
+ "layout/file_replaced_by_alias",
+ "color/color",
+ "string/basic_string",
+ "string/xliff_string",
+ "string/styled_string",
+ "style/style",
+ "array/string_array",
+ "attr/dimen_attr",
+ "attr/string_attr",
+ "attr/enum_attr",
+ "attr/flag_attr",
+ "attr/blah",
+ "attr/blah2",
+ "attr/flagAttr",
+ "declare-styleable/declare_styleable",
+ "dimen/dimen",
+ "dimen?sw600dp-v13/offset",
+ "id/item_id",
+ "integer/integer"
+ );
+ }
+
+ public void testBaseStringValue() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ List<ResourceItem> itemList = repo.getResourceItem(ResourceType.STRING, "basic_string");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+
+ ResourceValue value = itemList.get(0).getResourceValue(false);
+ assertNotNull(value);
+
+ assertEquals("overlay_string", value.getValue());
+ }
+
+ public void testBaseStyledStringValue() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ List<ResourceItem> itemList = repo.getResourceItem(ResourceType.STRING, "styled_string");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+
+ ResourceValue value = itemList.get(0).getResourceValue(false);
+ assertNotNull(value);
+
+ assertEquals("Forgot your username or password?\nVisit google.com/accounts/recovery.",
+ value.getValue());
+ }
+
+ public void testBaseColorValue() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ List<ResourceItem> itemList = repo.getResourceItem(ResourceType.COLOR, "color");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+
+ ResourceValue value = itemList.get(0).getResourceValue(false);
+ assertNotNull(value);
+
+ assertEquals("#FFFFFFFF", value.getValue());
+ }
+
+ public void testBaseLayoutAliasValue() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ List<ResourceItem> itemList = repo.getResourceItem(ResourceType.LAYOUT, "layout_ref");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+
+ ResourceValue value = itemList.get(0).getResourceValue(false);
+ assertNotNull(value);
+
+ assertEquals("@layout/ref", value.getValue());
+ }
+
+ public void testBaseAttrValue() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ List<ResourceItem> itemList = repo.getResourceItem(ResourceType.ATTR, "flag_attr");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+
+ ResourceValue value = itemList.get(0).getResourceValue(false);
+ assertNotNull(value);
+ assertTrue(value instanceof AttrResourceValue);
+ AttrResourceValue attrValue = (AttrResourceValue) value;
+
+ Map<String, Integer> attrValues = attrValue.getAttributeValues();
+ assertNotNull(attrValues);
+ assertEquals(3, attrValues.size());
+
+ Integer i = attrValues.get("normal");
+ assertNotNull(i);
+ assertEquals(Integer.valueOf(0), i);
+
+ i = attrValues.get("bold");
+ assertNotNull(i);
+ assertEquals(Integer.valueOf(1), i);
+
+ i = attrValues.get("italic");
+ assertNotNull(i);
+ assertEquals(Integer.valueOf(2), i);
+ }
+
+ public void testBaseStyleValue() throws Exception {
+ ResourceRepository repo = getResourceRepository();
+
+ List<ResourceItem> itemList = repo.getResourceItem(ResourceType.STYLE, "style");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+
+ ResourceValue value = itemList.get(0).getResourceValue(false);
+ assertNotNull(value);
+ assertTrue(value instanceof StyleResourceValue);
+ StyleResourceValue styleResourceValue = (StyleResourceValue) value;
+
+ assertEquals("@android:style/Holo.Light", styleResourceValue.getParentStyle());
+
+ ItemResourceValue styleValue = styleResourceValue.getItem("singleLine", true /*framework*/);
+ assertNotNull(styleValue);
+ assertEquals("true", styleValue.getValue());
+
+ styleValue = styleResourceValue.getItem("textAppearance", true /*framework*/);
+ assertNotNull(styleValue);
+ assertEquals("@style/TextAppearance.WindowTitle", styleValue.getValue());
+
+ styleValue = styleResourceValue.getItem("shadowColor", true /*framework*/);
+ assertNotNull(styleValue);
+ assertEquals("#BB000000", styleValue.getValue());
+
+ styleValue = styleResourceValue.getItem("shadowRadius", true /*framework*/);
+ assertNotNull(styleValue);
+ assertEquals("2.75", styleValue.getValue());
+
+ styleValue = styleResourceValue.getItem("foo", false /*framework*/);
+ assertNotNull(styleValue);
+ assertEquals("foo", styleValue.getValue());
+ }
+
+ public void testUpdateWithBasicFiles() throws Exception {
+ File root = getIncMergeRoot("basicFiles");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ // write the content in a repo.
+ ResourceRepository repo = new ResourceRepository(false);
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ // checks the initial state of the repo
+ Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
+ ListMultimap<String, ResourceItem> drawables = items.get(ResourceType.DRAWABLE);
+ assertNotNull("Drawable null check", drawables);
+ assertEquals("Drawable size check", 6, drawables.size());
+ verifyResourceExists(repo,
+ "drawable/new_overlay",
+ "drawable/removed",
+ "drawable?ldpi/removed",
+ "drawable/touched",
+ "drawable/removed_overlay",
+ "drawable/untouched");
+
+ // Apply updates
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // first set is the main one, no change here
+ ResourceSet mainSet = sets.get(0);
+ File mainBase = new File(root, "main");
+ File mainDrawable = new File(mainBase, "drawable");
+ File mainDrawableLdpi = new File(mainBase, "drawable-ldpi");
+
+ // touched/removed files:
+ File mainDrawableTouched = new File(mainDrawable, "touched.png");
+ mainSet.updateWith(mainBase, mainDrawableTouched, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ File mainDrawableRemoved = new File(mainDrawable, "removed.png");
+ mainSet.updateWith(mainBase, mainDrawableRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ File mainDrawableLdpiRemoved = new File(mainDrawableLdpi, "removed.png");
+ mainSet.updateWith(mainBase, mainDrawableLdpiRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // ----------------
+ // second set is the overlay one
+ ResourceSet overlaySet = sets.get(1);
+ File overlayBase = new File(root, "overlay");
+ File overlayDrawable = new File(overlayBase, "drawable");
+ File overlayDrawableHdpi = new File(overlayBase, "drawable-hdpi");
+
+ // new/removed files:
+ File overlayDrawableNewOverlay = new File(overlayDrawable, "new_overlay.png");
+ overlaySet.updateWith(overlayBase, overlayDrawableNewOverlay, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ File overlayDrawableRemovedOverlay = new File(overlayDrawable, "removed_overlay.png");
+ overlaySet.updateWith(overlayBase, overlayDrawableRemovedOverlay, FileStatus.REMOVED,
+ logger);
+ checkLogger(logger);
+
+ File overlayDrawableHdpiNewAlternate = new File(overlayDrawableHdpi, "new_alternate.png");
+ overlaySet.updateWith(overlayBase, overlayDrawableHdpiNewAlternate, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the new content.
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ drawables = items.get(ResourceType.DRAWABLE);
+ assertNotNull("Drawable null check", drawables);
+ assertEquals("Drawable size check", 5, drawables.size());
+ verifyResourceExists(repo,
+ "drawable/new_overlay",
+ "drawable/touched",
+ "drawable/removed_overlay",
+ "drawable/untouched",
+ "drawable?hdpi-v4/new_alternate");
+ checkRemovedItems(resourceMerger);
+ }
+
+ public void testUpdateWithBasicValues() throws Exception {
+ File root = getIncMergeRoot("basicValues");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ // write the content in a repo.
+ ResourceRepository repo = new ResourceRepository(false);
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ // checks the initial state of the repo
+ Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
+ ListMultimap<String, ResourceItem> strings = items.get(ResourceType.STRING);
+ assertNotNull("String null check", strings);
+ assertEquals("String size check", 5, strings.size());
+ verifyResourceExists(repo,
+ "string/untouched",
+ "string/touched",
+ "string/removed",
+ "string?en/removed",
+ "string/new_overlay");
+
+ // apply updates
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // first set is the main one, no change here
+ ResourceSet mainSet = sets.get(0);
+ File mainBase = new File(root, "main");
+ File mainValues = new File(mainBase, "values");
+ File mainValuesEn = new File(mainBase, "values-en");
+
+ // touched file:
+ File mainValuesTouched = new File(mainValues, "values.xml");
+ mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // removed files
+ File mainValuesEnRemoved = new File(mainValuesEn, "values.xml");
+ mainSet.updateWith(mainBase, mainValuesEnRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // ----------------
+ // second set is the overlay one
+ ResourceSet overlaySet = sets.get(1);
+ File overlayBase = new File(root, "overlay");
+ File overlayValues = new File(overlayBase, "values");
+ File overlayValuesFr = new File(overlayBase, "values-fr");
+
+ // new files:
+ File overlayValuesNew = new File(overlayValues, "values.xml");
+ overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ File overlayValuesFrNew = new File(overlayValuesFr, "values.xml");
+ overlaySet.updateWith(overlayBase, overlayValuesFrNew, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the new content.
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ strings = items.get(ResourceType.STRING);
+ assertNotNull("String null check", strings);
+ assertEquals("String size check", 4, strings.size());
+ verifyResourceExists(repo,
+ "string/untouched",
+ "string/touched",
+ "string/new_overlay",
+ "string?fr/new_alternate");
+ checkRemovedItems(resourceMerger);
+ }
+
+ public void testUpdateWithBasicValues2() throws Exception {
+ File root = getIncMergeRoot("basicValues2");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(2, sets.size());
+
+ // write the content in a repo.
+ ResourceRepository repo = new ResourceRepository(false);
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ // checks the initial state of the repo
+ Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
+ ListMultimap<String, ResourceItem> strings = items.get(ResourceType.STRING);
+ assertNotNull("String null check", strings);
+ assertEquals("String size check", 2, strings.size());
+ verifyResourceExists(repo,
+ "string/untouched",
+ "string/removed_overlay");
+
+ // apply updates
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // first set is the main one, no change here
+
+ // ----------------
+ // second set is the overlay one
+ ResourceSet overlaySet = sets.get(1);
+ File overlayBase = new File(root, "overlay");
+ File overlayValues = new File(overlayBase, "values");
+
+ // new files:
+ File overlayValuesNew = new File(overlayValues, "values.xml");
+ overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the new content.
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ strings = items.get(ResourceType.STRING);
+ assertNotNull("String null check", strings);
+ assertEquals("String size check", 2, strings.size());
+ verifyResourceExists(repo,
+ "string/untouched",
+ "string/removed_overlay");
+ checkRemovedItems(resourceMerger);
+ }
+
+ public void testUpdateWithFilesVsValues() throws Exception {
+ File root = getIncMergeRoot("filesVsValues");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/);
+ checkSourceFolders(resourceMerger);
+
+ List<ResourceSet> sets = resourceMerger.getDataSets();
+ assertEquals(1, sets.size());
+
+ // write the content in a repo.
+ ResourceRepository repo = new ResourceRepository(false);
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ // checks the initial state of the repo
+ Map<ResourceType, ListMultimap<String, ResourceItem>> items = repo.getItems();
+ ListMultimap<String, ResourceItem> layouts = items.get(ResourceType.LAYOUT);
+ assertNotNull("String null check", layouts);
+ assertEquals("String size check", 3, layouts.size());
+ verifyResourceExists(repo,
+ "layout/main",
+ "layout/file_replaced_by_alias",
+ "layout/alias_replaced_by_file");
+
+ // apply updates
+ RecordingLogger logger = new RecordingLogger();
+
+ // ----------------
+ // Load the main set
+ ResourceSet mainSet = sets.get(0);
+ File mainBase = new File(root, "main");
+ File mainValues = new File(mainBase, ResourceFolderType.VALUES.getName());
+ File mainLayout = new File(mainBase, ResourceFolderType.LAYOUT.getName());
+
+ // touched file:
+ File mainValuesTouched = new File(mainValues, "values.xml");
+ mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger);
+ checkLogger(logger);
+
+ // new file:
+ File mainLayoutNew = new File(mainLayout, "alias_replaced_by_file.xml");
+ mainSet.updateWith(mainBase, mainLayoutNew, FileStatus.NEW, logger);
+ checkLogger(logger);
+
+ // removed file
+ File mainLayoutRemoved = new File(mainLayout, "file_replaced_by_alias.xml");
+ mainSet.updateWith(mainBase, mainLayoutRemoved, FileStatus.REMOVED, logger);
+ checkLogger(logger);
+
+ // validate for duplicates
+ resourceMerger.validateDataSets();
+
+ // check the new content.
+ resourceMerger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+
+ layouts = items.get(ResourceType.LAYOUT);
+ assertNotNull("String null check", layouts);
+ assertEquals("String size check", 3, layouts.size());
+ verifyResourceExists(repo,
+ "layout/main",
+ "layout/file_replaced_by_alias",
+ "layout/alias_replaced_by_file");
+ checkRemovedItems(resourceMerger);
+ }
+
+ public void testUpdateFromOldFile() throws Exception {
+ File root = getIncMergeRoot("oldMerge");
+ File fakeRoot = getMergedBlobFolder(root);
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ assertFalse(resourceMerger.loadFromBlob(fakeRoot, false /*incrementalState*/));
+ }
+
+ private static void checkRemovedItems(DataMap<? extends DataItem> dataMap) {
+ for (DataItem item : dataMap.getDataMap().values()) {
+ if (item.isRemoved()) {
+ fail("Removed item found: " + item);
+ }
+ }
+ }
+
+ /**
+ * Creates a fake merge with given sets.
+ *
+ * the data is an array of sets.
+ *
+ * Each set is [ setName, folder1, folder2, ...]
+ *
+ */
+ private static ResourceMerger createMerger(String[][] data) {
+ ResourceMerger merger = new ResourceMerger(0);
+ for (String[] setData : data) {
+ ResourceSet set = new ResourceSet(setData[0]);
+ merger.addDataSet(set);
+ for (int i = 1, n = setData.length; i < n; i++) {
+ set.addSource(new File(setData[i]));
+ }
+ }
+
+ return merger;
+ }
+
+ /**
+ * Returns a merger with the baseSet and baseMerge content.
+ */
+ private static ResourceMerger getBaseResourceMerger()
+ throws MergingException, IOException {
+ File root = TestUtils.getRoot("resources", "baseMerge");
+
+ ResourceSet res = ResourceSetTest.getBaseResourceSet();
+
+ RecordingLogger logger = new RecordingLogger();
+
+ ResourceSet overlay = new ResourceSet("overlay");
+ overlay.addSource(new File(root, "overlay"));
+ overlay.loadFromFiles(logger);
+
+ checkLogger(logger);
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+ resourceMerger.addDataSet(res);
+ resourceMerger.addDataSet(overlay);
+
+ return resourceMerger;
+ }
+
+ /**
+ * Returns a merger from incMergeData initialized from the files, not from the merger
+ * state blog.
+ */
+ private static ResourceMerger getIncResourceMerger(String rootName, String... sets)
+ throws MergingException, IOException {
+
+ File root = getIncMergeRoot(rootName);
+ RecordingLogger logger = new RecordingLogger();
+
+ ResourceMerger resourceMerger = new ResourceMerger(0);
+
+ for (String setName : sets) {
+ ResourceSet resourceSet = new ResourceSet(setName);
+ resourceSet.addSource(new File(root, setName));
+ resourceSet.loadFromFiles(logger);
+ checkLogger(logger);
+
+ resourceMerger.addDataSet(resourceSet);
+ }
+
+ return resourceMerger;
+ }
+
+ private static ResourceRepository getResourceRepository()
+ throws MergingException, IOException {
+ ResourceMerger merger = getBaseResourceMerger();
+
+ ResourceRepository repo = new ResourceRepository(false);
+
+ merger.mergeData(repo.createMergeConsumer(), true /*doCleanUp*/);
+ return repo;
+ }
+
+ private static File getIncMergeRoot(String name) throws IOException {
+ File root = TestUtils.getCanonicalRoot("resources", "incMergeData");
+ return new File(root, name);
+ }
+
+ private static void verifyResourceExists(ResourceRepository repository,
+ String... dataItemKeys) {
+ Map<ResourceType, ListMultimap<String, ResourceItem>> items = repository.getItems();
+
+ for (String resKey : dataItemKeys) {
+ String type, name, qualifier = "";
+
+ int pos = resKey.indexOf('/');
+ if (pos != -1) {
+ name = resKey.substring(pos + 1);
+ type = resKey.substring(0, pos);
+ } else {
+ throw new IllegalArgumentException("Invalid key " + resKey);
+ }
+
+ // use ? as a qualifier delimiter because of
+ // declare-styleable
+ pos = type.indexOf('?');
+ if (pos != -1) {
+ qualifier = type.substring(pos + 1);
+ type = type.substring(0, pos);
+ }
+
+ ResourceType resourceType = ResourceType.getEnum(type);
+ assertNotNull("Type check for " + resKey, resourceType);
+
+ Multimap<String, ResourceItem> map = items.get(resourceType);
+ assertNotNull("Map check for " + resKey, map);
+
+ Collection<ResourceItem> list = map.get(name);
+ int found = 0;
+ for (ResourceItem resourceItem : list) {
+ if (resourceItem.getName().equals(name)) {
+ String fileQualifier = resourceItem.getSource() != null ?
+ resourceItem.getSource().getQualifiers() : "";
+
+ if (qualifier.equals(fileQualifier)) {
+ found++;
+ }
+ }
+ }
+
+ assertEquals("Match for " + resKey, 1, found);
+ }
+ }
+
+ // This utility method has been pretty handy in tracking down resource repository bugs
+ // so keeping it for future potential use, but in unit test code so no runtime overhead
+ @SuppressWarnings({"deprecation", "ConstantConditions"})
+ public static String dumpRepository(ResourceRepository repository) {
+ Map<ResourceType, ListMultimap<String, ResourceItem>> mItems = repository.getMap();
+ Comparator<ResourceItem> comparator = new Comparator<ResourceItem>() {
+ @Override
+ public int compare(ResourceItem item1, ResourceItem item2) {
+ assert item1.getType() == item2.getType();
+ String qualifiers = item2.getSource().getQualifiers();
+ return item1.getSource().getQualifiers().compareTo(qualifiers);
+ }
+ };
+
+ StringBuilder sb = new StringBuilder(5000);
+ sb.append("Resource Map Dump For Repository ").append(repository)
+ .append("\n------------------------------------------------\n");
+ for (ResourceType type : ResourceType.values()) {
+ ListMultimap<String, ResourceItem> map = mItems.get(type);
+ if (map == null) {
+ continue;
+ }
+ sb.append(type.getName()).append(':').append('\n');
+
+ List<String> keys = new ArrayList<String>(map.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ List<ResourceItem> items = map.get(key);
+ List<ResourceItem> sorted = new ArrayList<ResourceItem>(items);
+ Collections.sort(sorted, comparator);
+ sb.append(" ").append(type.getName()).append(" ").append(key).append(": ");
+ boolean first = true;
+ for (ResourceItem item : sorted) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ String qualifiers = item.getSource().getQualifiers();
+ if (qualifiers.isEmpty()) {
+ qualifiers = "default";
+ }
+ sb.append(qualifiers);
+ }
+
+ if (!sorted.isEmpty()) {
+ ResourceItem item = sorted.get(0);
+ ResourceValue resourceValue = item.getResourceValue(repository.isFramework());
+ if (resourceValue == null) {
+ sb.append(" <no value found>");
+ } else {
+ String value = resourceValue.getValue();
+ if (value == null || value.isEmpty()) {
+ if (resourceValue instanceof StyleResourceValue) {
+ StyleResourceValue srv = (StyleResourceValue) resourceValue;
+ sb.append(" parentStyle=").append(srv.getParentStyle())
+ .append("\n");
+ for (String name : srv.getNames()) {
+ ItemResourceValue value1 = srv.getItem(name, false);
+ ItemResourceValue value2 = srv.getItem(name, true);
+ if (value1 != null) {
+ Boolean framework = false;
+ sb.append(" ");
+ sb.append(name).append(" ").append(framework).append(" ");
+ sb.append(" = ");
+ sb.append('"');
+ String strValue = value1.getValue();
+ if (strValue != null) {
+ sb.append(strValue.replace("\n", "\\n"));
+ } else {
+ sb.append("???");
+ }
+ sb.append('"');
+ }
+ if (value2 != null) {
+ Boolean framework = true;
+ sb.append(" ");
+ sb.append(name).append(" ").append(framework).append(" ");
+ sb.append(" = ");
+ sb.append('"');
+ String strValue = value2.getValue();
+ if (strValue != null) {
+ sb.append(strValue.replace("\n", "\\n"));
+ } else {
+ sb.append("???");
+ }
+ sb.append('"');
+ }
+ }
+ } else {
+ sb.append(" = \"\"");
+ }
+ } else {
+ sb.append(" = ");
+ sb.append('"');
+ sb.append(value.replace("\n", "\\n"));
+ sb.append('"');
+ }
+ }
+ }
+
+ sb.append("\n");
+ }
+
+ sb.append("\n\n");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java
new file mode 100644
index 0000000..0e54b0b
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.SdkConstants.FD_RES;
+import static com.android.SdkConstants.FD_RES_DRAWABLE;
+import static com.android.SdkConstants.FD_RES_LAYOUT;
+import static com.android.SdkConstants.FD_RES_VALUES;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.TestResourceRepository;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LocaleQualifier;
+import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+
+ at SuppressWarnings("javadoc")
+public class ResourceRepositoryTest2 extends TestCase {
+ private File mTempDir;
+ private File mRes;
+ private ResourceMerger mResourceMerger;
+ private ResourceRepository mRepository;
+ private ILogger mLogger;
+
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mTempDir = Files.createTempDir();
+ mRes = new File(mTempDir, FD_RES);
+ mRes.mkdirs();
+ File layout = new File(mRes, FD_RES_LAYOUT);
+ File layoutLand = new File(mRes, FD_RES_LAYOUT + "-land");
+ File values = new File(mRes, FD_RES_VALUES);
+ File valuesEs = new File(mRes, FD_RES_VALUES + "-es");
+ File valuesEsUs = new File(mRes, FD_RES_VALUES + "-es-rUS");
+ File valuesKok = new File(mRes, FD_RES_VALUES + "-b+kok");
+ File valuesKokIn = new File(mRes, FD_RES_VALUES + "-b+kok+IN");
+ File drawable = new File(mRes, FD_RES_DRAWABLE);
+ layout.mkdirs();
+ layoutLand.mkdirs();
+ values.mkdirs();
+ valuesEs.mkdirs();
+ valuesEsUs.mkdirs();
+ valuesKok.mkdirs();
+ valuesKokIn.mkdirs();
+ drawable.mkdirs();
+ new File(layout, "layout1.xml").createNewFile();
+ new File(layoutLand, "layout1.xml").createNewFile();
+ new File(layoutLand, "only_land.xml").createNewFile();
+ new File(layout, "layout2.xml").createNewFile();
+ new File(drawable, "graphic.9.png").createNewFile();
+ File strings = new File(values, "strings.xml");
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
+ + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
+ + " <string name=\"home_title\">Home Sample</string>\n"
+ + " <string name=\"show_all_apps\">All</string>\n"
+ + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
+ + " <string name=\"menu_search\">Search</string>\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
+ + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
+ + "</resources>\n", strings, Charsets.UTF_8);
+
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n", new File(valuesEs, "strings.xml"), Charsets.UTF_8);
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n", new File(valuesEsUs, "strings.xml"), Charsets.UTF_8);
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n", new File(valuesKok, "strings.xml"), Charsets.UTF_8);
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n", new File(valuesKokIn, "strings.xml"), Charsets.UTF_8);
+
+ if ("testGetMatchingFileAliases".equals(getName())) {
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item name=\"layout2\" type=\"layout\">@layout/indirect3</item>\n"
+ + " <item name=\"indirect3\" type=\"layout\">@layout/indirect2</item>\n"
+ + " <item name=\"indirect2\" type=\"layout\">@layout/indirect1</item>\n"
+ + " <item name=\"indirect1\" type=\"layout\">@layout/layout1</item>\n"
+ + "</resources>", new File(valuesEsUs, "refs.xml"), Charsets.UTF_8);
+ }
+
+ mResourceMerger = new ResourceMerger(0);
+ ResourceSet resourceSet = new ResourceSet("main");
+ resourceSet.addSource(mRes);
+ resourceSet.loadFromFiles(mLogger = new RecordingLogger());
+ mResourceMerger.addDataSet(resourceSet);
+
+ mRepository = new ResourceRepository(false);
+ mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ deleteFile(mTempDir);
+ }
+
+ private static void deleteFile(File dir) {
+ if (dir.isDirectory()) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ deleteFile(f);
+ }
+ }
+ } else if (dir.isFile()) {
+ assertTrue(dir.getPath(), dir.delete());
+ }
+ }
+
+ public void testBasic() throws Exception {
+ assertFalse(mRepository.hasResourceItem("@layout/layout0"));
+ assertTrue(mRepository.hasResourceItem("@layout/layout1"));
+ assertFalse(mRepository.hasResourceItem(ResourceType.LAYOUT, "layout0"));
+ assertTrue(mRepository.hasResourceItem(ResourceType.LAYOUT, "layout1"));
+ assertFalse(mRepository.hasResourceItem(ResourceType.STRING, "layout1"));
+ assertTrue(mRepository.hasResourceItem(ResourceType.STRING, "home_title"));
+ assertFalse(mRepository.hasResourceItem(ResourceType.STRING, "home_title2"));
+ assertFalse(mRepository.hasResourceItem(ResourceType.DRAWABLE, "graph"));
+ assertTrue(mRepository.hasResourceItem(ResourceType.DRAWABLE, "graphic"));
+ assertTrue(mRepository.hasResourceItem("@id/action_bar_refresh"));
+ assertTrue(mRepository.hasResourceItem("@drawable/graphic"));
+ assertTrue(mRepository.hasResourcesOfType(ResourceType.DRAWABLE));
+ assertFalse(mRepository.hasResourcesOfType(ResourceType.ANIM));
+
+ List<ResourceType> availableResourceTypes = mRepository.getAvailableResourceTypes();
+ assertEquals(5, availableResourceTypes.size()); // layout, string, drawable, id, dimen
+
+ Collection<String> allStrings = mRepository.getItemsOfType(ResourceType.STRING);
+ assertEquals(7, allStrings.size());
+
+ List<ResourceItem> itemList = mRepository.getResourceItem(ResourceType.STRING, "menu_settings");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+ for (ResourceItem item : itemList) {
+ assertEquals("menu_settings", item.getName());
+ assertEquals("@string/menu_settings", item.getXmlString(ResourceType.STRING, false));
+ }
+ //assertTrue(item.hasDefault());
+
+ itemList = mRepository.getResourceItem(ResourceType.STRING, "show_all_apps");
+ assertNotNull(itemList);
+ assertTrue(itemList.size() > 1);
+ for (ResourceItem item : itemList) {
+ assertEquals("show_all_apps", item.getName());
+ assertEquals("@string/show_all_apps", item.getXmlString(ResourceType.STRING, false));
+ }
+ //assertTrue(item.hasDefault());
+ FolderConfiguration folderConfig = new FolderConfiguration();
+ folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("en"));
+ Map<ResourceType, Map<String, ResourceValue>> configuredItems = mRepository
+ .getConfiguredResources(folderConfig);
+ ResourceValue value = configuredItems.get(ResourceType.STRING).get("show_all_apps");
+ assertNotNull(value);
+ assertEquals("All", value.getValue());
+ assertSame(ResourceType.STRING, value.getResourceType());
+
+ folderConfig = new FolderConfiguration();
+ folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es"));
+ configuredItems = mRepository.getConfiguredResources(folderConfig);
+ value = configuredItems.get(ResourceType.STRING).get("show_all_apps");
+ assertNotNull(value);
+ assertEquals("Todo", value.getValue());
+ assertSame(ResourceType.STRING, value.getResourceType());
+
+ itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "only_land");
+ assertNotNull(itemList);
+ //assertFalse(item.hasDefault());
+ assertEquals(1, itemList.size());
+ ResourceFile resourceFile = itemList.get(0).getSource();
+ assertEquals("only_land.xml", resourceFile.getFile().getName());
+ assertEquals(ScreenOrientation.LANDSCAPE.getResourceValue(), resourceFile.getQualifiers());
+
+ itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "layout1");
+ assertNotNull(itemList);
+ //assertTrue(item.hasDefault());
+ assertEquals(2, itemList.size());
+
+ SortedSet<String> languages = mRepository.getLanguages();
+ assertEquals(2, languages.size());
+ assertTrue(languages.contains("es"));
+ assertTrue(languages.contains("kok"));
+ assertEquals(Collections.singleton("US"), mRepository.getRegions("es"));
+ assertEquals(Collections.singleton("IN"), mRepository.getRegions("kok"));
+
+// List<ResourceFile> layouts = mRepository.getSourceFiles(ResourceType.LAYOUT, "layout1",
+// folderConfig);
+// assertNotNull(layouts);
+// assertEquals(1, layouts.size());
+// com.android.ide.common.resources.ResourceFile file1 = layouts.get(0);
+// assertEquals("layout1.xml", file1.getFile().getName());
+// assertSame(mRepository, file1.getRepository());
+ }
+
+ public void testGetConfiguredResources() throws Exception {
+ FolderConfiguration folderConfig = new FolderConfiguration();
+ folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es"));
+ folderConfig.setScreenOrientationQualifier(
+ new ScreenOrientationQualifier(ScreenOrientation.LANDSCAPE));
+
+ Map<ResourceType, Map<String, ResourceValue>> configuredResources =
+ mRepository.getConfiguredResources(folderConfig);
+ Map<String, ResourceValue> strings = configuredResources.get(ResourceType.STRING);
+ Map<String, ResourceValue> layouts = configuredResources.get(ResourceType.LAYOUT);
+ Map<String, ResourceValue> ids = configuredResources.get(ResourceType.ID);
+ Map<String, ResourceValue> dimens = configuredResources.get(ResourceType.DIMEN);
+ assertEquals(1, ids.size());
+ assertEquals(1, dimens.size());
+ assertEquals("dialog_min_width_major", dimens.get("dialog_min_width_major").getName());
+ assertEquals("45%", dimens.get("dialog_min_width_major").getValue());
+ assertEquals("Todo", strings.get("show_all_apps").getValue());
+ assertEquals(3, layouts.size());
+ assertNotNull(layouts.get("layout1"));
+
+ ResourceFile file = mRepository.getMatchingFile("dialog_min_width_major",
+ ResourceType.DIMEN, folderConfig);
+ assertNotNull(file);
+// file = mRepository.getMatchingFile("dialog_min_width_major", ResourceFolderType.VALUES,
+// folderConfig);
+// assertNotNull(file);
+// file = mRepository.getMatchingFile("layout1", ResourceFolderType.LAYOUT, folderConfig);
+// assertNotNull(file);
+ file = mRepository.getMatchingFile("layout1", ResourceType.LAYOUT, folderConfig);
+ assertNotNull(file);
+ }
+
+ public void testGetMatchingFileAliases() throws Exception {
+ FolderConfiguration folderConfig = new FolderConfiguration();
+ folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es"));
+ folderConfig.setScreenOrientationQualifier(
+ new ScreenOrientationQualifier(ScreenOrientation.LANDSCAPE));
+
+ Map<ResourceType, Map<String, ResourceValue>> configuredResources =
+ mRepository.getConfiguredResources(folderConfig);
+ Map<String, ResourceValue> layouts = configuredResources.get(ResourceType.LAYOUT);
+ assertEquals(6, layouts.size());
+ assertNotNull(layouts.get("layout1"));
+
+ folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es-rES"));
+ ResourceFile file = mRepository.getMatchingFile("dialog_min_width_major",
+ ResourceType.DIMEN, folderConfig);
+ assertNotNull(file);
+ file = mRepository.getMatchingFile("layout2", ResourceType.LAYOUT, folderConfig);
+ assertNotNull(file);
+ assertEquals("layout2.xml", file.getFile().getName());
+ assertEquals("", file.getQualifiers());
+
+ folderConfig.setLocaleQualifier(LocaleQualifier.getQualifier("es-rUS"));
+ file = mRepository.getMatchingFile("layout2", ResourceType.LAYOUT, folderConfig);
+ assertNotNull(file);
+ assertEquals("layout1.xml", file.getFile().getName());
+ assertEquals("land", file.getQualifiers());
+ }
+
+ public void testUpdates() throws Exception {
+ assertFalse(mRepository.hasResourcesOfType(ResourceType.ANIM));
+ assertFalse(mRepository.hasResourcesOfType(ResourceType.MENU));
+ assertFalse(mRepository.hasResourcesOfType(ResourceType.BOOL));
+
+ assertTrue(mRepository.hasResourcesOfType(ResourceType.DRAWABLE));
+ assertTrue(mRepository.hasResourceItem("@drawable/graphic"));
+
+ // Delete the drawable graphic
+ ResourceSet resourceSet = mResourceMerger.getDataSets().get(0);
+
+ File drawableFolder = new File(mRes, FD_RES_DRAWABLE);
+ File graphicFile = new File(drawableFolder, "graphic.9.png");
+
+ resourceSet.updateWith(mRes, graphicFile, FileStatus.REMOVED, mLogger);
+ mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
+
+ assertFalse(mRepository.hasResourceItem("@drawable/graphic"));
+ assertFalse(mRepository.hasResourcesOfType(ResourceType.DRAWABLE));
+
+ // Delete one of the overridden layouts
+ List<ResourceItem> itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "layout1");
+ assertNotNull(itemList);
+ assertTrue(itemList.size() > 1);
+ assertTrue(mRepository.hasResourcesOfType(ResourceType.LAYOUT));
+ assertTrue(mRepository.hasResourceItem("@layout/layout1"));
+
+ File layoutFolder = new File(mRes, FD_RES_LAYOUT + "-land");
+ File layoutFile = new File(layoutFolder, "layout1.xml");
+
+ resourceSet.updateWith(mRes, layoutFile, FileStatus.REMOVED, mLogger);
+ mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
+
+ // We still have a layout1: only default now
+ assertTrue(mRepository.hasResourceItem("@layout/layout1"));
+ itemList = mRepository.getResourceItem(ResourceType.LAYOUT, "layout1");
+ assertNotNull(itemList);
+ assertEquals(1, itemList.size());
+
+ // change strings
+ assertTrue(mRepository.hasResourceItem("@string/dummy"));
+ assertFalse(mRepository.hasResourceItem("@string/myDummy"));
+ itemList = mRepository.getResourceItem(ResourceType.STRING, "dummy");
+ assertNotNull(itemList);
+ assertNotNull(itemList.get(0));
+ ResourceFile stringResFile = itemList.get(0).getSource();
+ File stringFile = stringResFile.getFile();
+ assertTrue(stringFile.exists());
+ String strings = Files.toString(stringFile, Charsets.UTF_8);
+ assertNotNull(strings);
+ strings = strings.replace("name=\"dummy\"", "name=\"myDummy\"");
+ Files.write(strings, stringFile, Charsets.UTF_8);
+
+ resourceSet.updateWith(mRes, stringFile, FileStatus.CHANGED, mLogger);
+ mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
+
+ assertTrue(mRepository.hasResourceItem("@string/myDummy"));
+ assertFalse(mRepository.hasResourceItem("@string/dummy"));
+
+ // add files
+ assertFalse(mRepository.hasResourceItem("@layout/layout5"));
+ File layout = new File(mRes, FD_RES_LAYOUT);
+ File newFile = new File(layout, "layout5.xml");
+ boolean created = newFile.createNewFile();
+ assertTrue(created);
+ resourceSet.updateWith(mRes, newFile, FileStatus.NEW, mLogger);
+ mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
+ assertTrue(mRepository.hasResourceItem("@layout/layout5"));
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testXliff() throws Exception {
+ ResourceRepository resources = TestResourceRepository.createRes2(false, new Object[]{
+ "values/strings.xml", ""
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" >\n"
+ + " <string name=\"share_with_application\">\n"
+ + " Share your score of <xliff:g id=\"score\" example=\"1337\">%1$s</xliff:g>\n"
+ + " with <xliff:g id=\"application_name\" example=\"Bluetooth\">%2$s</xliff:g>!\n"
+ + " </string>\n"
+ + " <string name=\"callDetailsDurationFormat\"><xliff:g id=\"minutes\" example=\"42\">%s</xliff:g> mins <xliff:g id=\"seconds\" example=\"28\">%s</xliff:g> secs</string>\n"
+ + " <string name=\"description_call\">Call <xliff:g id=\"name\">%1$s</xliff:g></string>\n"
+ + " <string name=\"other\"><xliff:g id=\"number_of_sessions\">%1$s</xliff:g> sessions removed from your schedule</string>\n"
+ + " <!-- Format string used to add a suffix like \"KB\" or \"MB\" to a number\n"
+ + " to display a size in kilobytes, megabytes, or other size units.\n"
+ + " Some languages (like French) will want to add a space between\n"
+ + " the placeholders. -->\n"
+ + " <string name=\"fileSizeSuffix\"><xliff:g id=\"number\" example=\"123\">%1$s</xliff:g><xliff:g id=\"unit\" example=\"KB\">%2$s</xliff:g></string>"
+ + "</resources>\n"
+ });
+ assertFalse(resources.isFramework());
+ assertNotNull(resources);
+
+ assertNotNull(resources);
+ assertEquals("Share your score of (1337) with (Bluetooth)!",
+ resources.getResourceItem(ResourceType.STRING, "share_with_application").get(0).getResourceValue(false).getValue());
+ assertEquals("Call ${name}",
+ resources.getResourceItem(ResourceType.STRING, "description_call").get(0).getResourceValue(false).getValue());
+ assertEquals("(42) mins (28) secs",
+ resources.getResourceItem(ResourceType.STRING, "callDetailsDurationFormat").get(0).getResourceValue(false).getValue());
+ assertEquals("${number_of_sessions} sessions removed from your schedule",
+ resources.getResourceItem(ResourceType.STRING, "other").get(0).getResourceValue(false).getValue());
+ assertEquals("(123)(KB)",
+ resources.getResourceItem(ResourceType.STRING, "fileSizeSuffix").get(0).getResourceValue(false).getValue());
+
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java
new file mode 100644
index 0000000..509c68e
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static java.io.File.separator;
+
+import com.android.testutils.TestUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ResourceSetTest extends BaseTestCase {
+
+ public void testBaseResourceSetByCount() throws Exception {
+ ResourceSet resourceSet = getBaseResourceSet();
+ assertEquals(29, resourceSet.size());
+ }
+
+ public void testBaseResourceSetWithNormalizationByName() throws Exception {
+ ResourceSet resourceSet = getBaseResourceSet();
+
+ verifyResourceExists(resourceSet,
+ "drawable/icon",
+ "drawable/patch",
+ "raw/foo",
+ "layout/main",
+ "layout/layout_ref",
+ "layout/alias_replaced_by_file",
+ "layout/file_replaced_by_alias",
+ "drawable/color_drawable",
+ "drawable/drawable_ref",
+ "color/color",
+ "string/basic_string",
+ "string/xliff_string",
+ "string/styled_string",
+ "style/style",
+ "array/string_array",
+ "attr/dimen_attr",
+ "attr/string_attr",
+ "attr/enum_attr",
+ "attr/flag_attr",
+ "attr/blah",
+ "attr/blah2",
+ "attr/flagAttr",
+ "declare-styleable/declare_styleable",
+ "dimen/dimen",
+ "dimen-sw600dp-v13/offset",
+ "id/item_id",
+ "integer/integer",
+ "plurals/plurals"
+ );
+ }
+
+ public void testDupResourceSet() throws Exception {
+ File root = TestUtils.getRoot("resources", "dupSet");
+
+ ResourceSet set = new ResourceSet("main");
+ set.addSource(new File(root, "res1"));
+ set.addSource(new File(root, "res2"));
+ boolean gotException = false;
+ RecordingLogger logger = new RecordingLogger();
+ try {
+ set.loadFromFiles(logger);
+ } catch (DuplicateDataException e) {
+ gotException = true;
+
+
+
+
+ String message = e.getMessage();
+ // Clean up paths etc for unit test
+ int index = message.indexOf("dupSet");
+ assertTrue(index != -1);
+ String prefix = message.substring(0, index);
+ message = message.replace(prefix, "<PREFIX>").replace('\\','/');
+ assertEquals("<PREFIX>dupSet/res1/drawable/icon.png\t<PREFIX>dupSet/res2/drawable/icon.png: "
+ + "Error: Duplicate resources", message);
+ }
+
+ checkLogger(logger);
+ assertTrue(gotException);
+ }
+
+ public void testBrokenSet() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet");
+
+ ResourceSet set = new ResourceSet("main");
+ set.addSource(root);
+
+ boolean gotException = false;
+ RecordingLogger logger = new RecordingLogger();
+ try {
+ set.loadFromFiles(logger);
+ } catch (MergingException e) {
+ gotException = true;
+ assertEquals(new File(root, "values" + separator + "dimens.xml").getAbsolutePath() +
+ ":1:1: Error: Content is not allowed in prolog.",
+ e.getMessage());
+ }
+
+ assertTrue("ResourceSet processing should have failed, but didn't", gotException);
+ assertFalse(logger.getErrorMsgs().isEmpty());
+ }
+
+ public void testBrokenSet2() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet2");
+
+ ResourceSet set = new ResourceSet("main");
+ set.addSource(root);
+
+ boolean gotException = false;
+ RecordingLogger logger = new RecordingLogger();
+ try {
+ set.loadFromFiles(logger);
+ } catch (MergingException e) {
+ gotException = true;
+ assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
+ ": Error: Found item String/app_name more than one time",
+ e.getMessage());
+ }
+
+ assertTrue("ResourceSet processing should have failed, but didn't", gotException);
+ assertFalse(logger.getErrorMsgs().isEmpty());
+ }
+
+ public void testBrokenSet3() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet3");
+
+ ResourceSet set = new ResourceSet("main");
+ set.addSource(root);
+
+ boolean gotException = false;
+ RecordingLogger logger = new RecordingLogger();
+ try {
+ set.loadFromFiles(logger);
+ } catch (MergingException e) {
+ gotException = true;
+ assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
+ ": Error: Found item Attr/d_common_attr more than one time",
+ e.getMessage());
+ }
+
+ assertTrue("ResourceSet processing should have failed, but didn't", gotException);
+ assertFalse(logger.getErrorMsgs().isEmpty());
+ }
+
+ public void testBrokenSet4() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet4");
+
+ ResourceSet set = new ResourceSet("main");
+ set.addSource(root);
+
+ boolean gotException = false;
+ RecordingLogger logger = new RecordingLogger();
+ try {
+ set.loadFromFiles(logger);
+ } catch (MergingException e) {
+ gotException = true;
+ assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
+ ":7:6: Error: The element type \"declare-styleable\" "
+ + "must be terminated by the matching end-tag \"</declare-styleable>\".",
+ e.getMessage());
+ }
+
+ assertTrue("ResourceSet processing should have failed, but didn't", gotException);
+ assertFalse(logger.getErrorMsgs().isEmpty());
+ }
+
+ static ResourceSet getBaseResourceSet() throws MergingException, IOException {
+ File root = TestUtils.getRoot("resources", "baseSet");
+
+ ResourceSet resourceSet = new ResourceSet("main");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+ resourceSet.loadFromFiles(logger);
+
+ checkLogger(logger);
+
+ return resourceSet;
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceNameValidatorTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceNameValidatorTest.java
new file mode 100644
index 0000000..75a9a70
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceNameValidatorTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.ide.common.res2;
+
+import com.android.resources.ResourceType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+
+ at RunWith(Parameterized.class)
+public class ValueResourceNameValidatorTest {
+
+ public static File DUMMY_FILE = new File("DUMMY_FILE");
+
+ @Parameterized.Parameters(name="name=\"{0}\", resourceType={1}, file={2} gives error {3}")
+ public static Collection<Object[]> expected() {
+ return Arrays.asList(new Object[][] {
+ //{ resourceName, resourceType, sourceFile, expectedException }
+ { "foo.png", ResourceType.DRAWABLE, DUMMY_FILE, null},
+ { "foo.xml", ResourceType.DRAWABLE, DUMMY_FILE, null},
+ { "foo.9.xml", ResourceType.DRAWABLE, DUMMY_FILE, null},
+ { "foo", ResourceType.DRAWABLE, DUMMY_FILE, null},
+ { "foo.png", ResourceType.DRAWABLE, DUMMY_FILE, null},
+ { "foo.txt", ResourceType.RAW, DUMMY_FILE, null},
+ { "foo.txt", ResourceType.DRAWABLE, DUMMY_FILE, null},
+ { "foo.other.png", ResourceType.DRAWABLE, DUMMY_FILE, null},
+ { "android:q", ResourceType.STRING, DUMMY_FILE, "':' is not a valid resource name character"},
+ { "android:q", ResourceType.STRING, null, "':' is not a valid resource name character"},
+ { "android:q", ResourceType.ATTR, DUMMY_FILE, null},
+ { "foo.s_3", ResourceType.STRING, null, null},
+ { "FOO$", ResourceType.STRING, null, null},
+ { "1st", ResourceType.STRING, null, "The resource name must start with a letter"},
+ { "Foo#", ResourceType.STRING, null, "'#' is not a valid resource name character"},
+ { "void", ResourceType.STRING, null, "void is not a valid resource name (reserved Java keyword)"},
+ { "", ResourceType.STRING, null, "The resource name shouldn't be empty" }
+ });
+ }
+
+ @Parameterized.Parameter
+ public String mResourceName;
+
+ @Parameterized.Parameter(value=1)
+ public ResourceType mResourceType;
+
+ @Parameterized.Parameter(value=2)
+ public File mSourceFile;
+
+ @Parameterized.Parameter(value=3)
+ public String mExpectedErrorMessage;
+
+
+ @Test
+ public void validate() {
+ String errorMessage = null;
+ try {
+ ValueResourceNameValidator.validate(mResourceName, mResourceType, mSourceFile);
+ } catch (MergingException e) {
+ errorMessage = e.getMessage();
+ }
+ FileResourceNameValidatorTest.assertErrorMessageCorrect(
+ mExpectedErrorMessage, errorMessage, mSourceFile);
+ }
+
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java b/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java
rename to sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java
new file mode 100644
index 0000000..39aa114
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import static com.android.ide.common.res2.ValueXmlHelper.escapeResourceString;
+import static com.android.ide.common.res2.ValueXmlHelper.isEscaped;
+import static com.android.ide.common.res2.ValueXmlHelper.unescapeResourceString;
+
+import junit.framework.TestCase;
+
+public class ValueXmlHelperTest extends TestCase {
+
+ public void testEscapeStringShouldEscapeXmlSpecialCharacters() throws Exception {
+ assertEquals("<", escapeResourceString("<"));
+ assertEquals("&", escapeResourceString("&"));
+ assertEquals("<", escapeResourceString("<", true));
+ assertEquals("&", escapeResourceString("&", true));
+ assertEquals("<", escapeResourceString("<", false));
+ assertEquals("&", escapeResourceString("&", false));
+ }
+
+ public void testEscapeStringShouldEscapeQuotes() throws Exception {
+ assertEquals("\\'", escapeResourceString("'"));
+ assertEquals("\\\"", escapeResourceString("\""));
+ assertEquals("\" ' \"", escapeResourceString(" ' "));
+ }
+
+ public void testEscapeStringShouldPreserveWhitespace() throws Exception {
+ assertEquals("\"at end \"", escapeResourceString("at end "));
+ assertEquals("\" at begin\"", escapeResourceString(" at begin"));
+ }
+
+ public void testEscapeStringShouldEscapeAtSignAndQuestionMarkOnlyAtBeginning()
+ throws Exception {
+ assertEquals("\\@text", escapeResourceString("@text"));
+ assertEquals("a at text", escapeResourceString("a at text"));
+ assertEquals("\\?text", escapeResourceString("?text"));
+ assertEquals("a?text", escapeResourceString("a?text"));
+ assertEquals("\" ?text\"", escapeResourceString(" ?text"));
+ }
+
+ public void testEscapeStringShouldEscapeJavaEscapeSequences() throws Exception {
+ assertEquals("\\n", escapeResourceString("\n"));
+ assertEquals("\\t", escapeResourceString("\t"));
+ assertEquals("\\\\", escapeResourceString("\\"));
+ }
+
+ public void testTrim() throws Exception {
+ assertEquals("", unescapeResourceString("", false, true));
+ assertEquals("", unescapeResourceString(" \n ", false, true));
+ assertEquals("test", unescapeResourceString(" test ", false, true));
+ assertEquals(" test ", unescapeResourceString("\" test \"", false, true));
+ assertEquals("test", unescapeResourceString("\n\t test \t\n ", false, true));
+
+ assertEquals("test\n", unescapeResourceString(" test\\n ", false, true));
+ assertEquals(" test\n ", unescapeResourceString("\" test\\n \"", false, true));
+ assertEquals("te\\st", unescapeResourceString("\n\t te\\\\st \t\n ", false, true));
+ assertEquals("te\\st", unescapeResourceString(" te\\\\st ", false, true));
+ assertEquals("test", unescapeResourceString("\"\"\"test\"\" ", false, true));
+ assertEquals("t t", unescapeResourceString(" \"\"t t\" ", false, true));
+ assertEquals("t t", unescapeResourceString(" \"\"\"t t\" ", false, true));
+ assertEquals("\"test\"", unescapeResourceString("\"\"\\\"test\\\"\" ", false, true));
+ assertEquals("test ", unescapeResourceString("test\\ ", false, true));
+ assertEquals("\\\\\\", unescapeResourceString("\\\\\\\\\\\\ ", false, true));
+ assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\\\ ", false, true));
+ }
+
+ public void testNoTrim() throws Exception {
+ assertEquals("", unescapeResourceString("", false, false));
+ assertEquals(" \n ", unescapeResourceString(" \n ", false, false));
+ assertEquals(" test ", unescapeResourceString(" test ", false, false));
+ assertEquals("\" test \"", unescapeResourceString("\" test \"", false, false));
+ assertEquals("\n\t test \t\n ", unescapeResourceString("\n\t test \t\n ", false, false));
+
+ assertEquals(" test\n ", unescapeResourceString(" test\\n ", false, false));
+ assertEquals("\" test\n \"", unescapeResourceString("\" test\\n \"", false, false));
+ assertEquals("\n\t te\\st \t\n ", unescapeResourceString("\n\t te\\\\st \t\n ", false, false));
+ assertEquals(" te\\st ", unescapeResourceString(" te\\\\st ", false, false));
+ assertEquals("\"\"\"test\"\" ", unescapeResourceString("\"\"\"test\"\" ", false, false));
+ assertEquals("\"\"\"test\"\" ", unescapeResourceString("\"\"\\\"test\\\"\" ", false, false));
+ assertEquals("test ", unescapeResourceString("test\\ ", false, false));
+ assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\ ", false, false));
+ assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\\\ ", false, false));
+ }
+
+ public void testUnescapeStringShouldUnescapeXmlSpecialCharacters() throws Exception {
+ assertEquals("<", unescapeResourceString("<", false, true));
+ assertEquals(">", unescapeResourceString(">", false, true));
+ assertEquals("<", unescapeResourceString("<", true, true));
+ assertEquals("<", unescapeResourceString(" < ", true, true));
+ assertEquals("\"", unescapeResourceString(" " ", true, true));
+ assertEquals("'", unescapeResourceString(" ' ", true, true));
+ assertEquals(">", unescapeResourceString(" > ", true, true));
+ assertEquals("&", unescapeResourceString("&", false, true));
+ assertEquals("&", unescapeResourceString("&", true, true));
+ assertEquals("&", unescapeResourceString(" & ", true, true));
+ assertEquals("!<", unescapeResourceString("!<", true, true));
+ }
+
+ public void testUnescapeStringShouldUnescapeQuotes() throws Exception {
+ assertEquals("'", unescapeResourceString("\\'", false, true));
+ assertEquals("\"", unescapeResourceString("\\\"", false, true));
+ assertEquals(" ' ", unescapeResourceString("\" ' \"", false, true));
+ }
+
+ public void testUnescapeStringShouldPreserveWhitespace() throws Exception {
+ assertEquals("at end ", unescapeResourceString("\"at end \"", false, true));
+ assertEquals(" at begin", unescapeResourceString("\" at begin\"", false, true));
+ }
+
+ public void testUnescapeStringShouldUnescapeAtSignAndQuestionMarkOnlyAtBeginning()
+ throws Exception {
+ assertEquals("@text", unescapeResourceString("\\@text", false, true));
+ assertEquals("a at text", unescapeResourceString("a at text", false, true));
+ assertEquals("?text", unescapeResourceString("\\?text", false, true));
+ assertEquals("a?text", unescapeResourceString("a?text", false, true));
+ assertEquals(" ?text", unescapeResourceString("\" ?text\"", false, true));
+ }
+
+ public void testUnescapeStringShouldUnescapeJavaUnescapeSequences() throws Exception {
+ assertEquals("\n", unescapeResourceString("\\n", false, true));
+ assertEquals("\t", unescapeResourceString("\\t", false, true));
+ assertEquals("\\", unescapeResourceString("\\\\", false, true));
+ }
+
+ public void testIsEscaped() throws Exception {
+ assertFalse(isEscaped("", 0));
+ assertFalse(isEscaped(" ", 0));
+ assertFalse(isEscaped(" ", 1));
+ assertFalse(isEscaped("x\\y ", 0));
+ assertFalse(isEscaped("x\\y ", 1));
+ assertTrue(isEscaped("x\\y ", 2));
+ assertFalse(isEscaped("x\\y ", 3));
+ assertFalse(isEscaped("x\\\\y ", 0));
+ assertFalse(isEscaped("x\\\\y ", 1));
+ assertTrue(isEscaped("x\\\\y ", 2));
+ assertFalse(isEscaped("x\\\\y ", 3));
+ assertFalse(isEscaped("\\\\\\\\y ", 0));
+ assertTrue(isEscaped( "\\\\\\\\y ", 1));
+ assertFalse(isEscaped("\\\\\\\\y ", 2));
+ assertTrue(isEscaped( "\\\\\\\\y ", 3));
+ assertFalse(isEscaped("\\\\\\\\y ", 4));
+ }
+
+ public void testRewriteSpaces() throws Exception {
+ // Ensure that \n's in the input are rewritten as spaces, and multiple spaces
+ // collapsed into a single one
+ assertEquals("This is a test",
+ unescapeResourceString("This is\na test", true, true));
+ assertEquals("This is a test",
+ unescapeResourceString("This is\n a test\n ", true, true));
+ assertEquals("This is\na test",
+ unescapeResourceString("\"This is\na test\"", true, true));
+ assertEquals("Multiple words",
+ unescapeResourceString("Multiple words", true, true));
+ assertEquals("Multiple words",
+ unescapeResourceString("\"Multiple words\"", true, true));
+ assertEquals("This is a\n test",
+ unescapeResourceString("This is a\\n test", true, true));
+ assertEquals("This is a\n test",
+ unescapeResourceString("This is\n a\\n test", true, true));
+ }
+
+ public void testHtmlEntities() throws Exception {
+ assertEquals("Entity \u00a9 \u00a9 Copyright",
+ unescapeResourceString("Entity © © Copyright", true, true));
+ }
+
+ public void testMarkupConcatenation() throws Exception {
+ assertEquals("<b>Sign in</b> or register",
+ unescapeResourceString("\n <b>Sign in</b>\n or register\n", true, true));
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/LocaleManagerTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/LocaleManagerTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/LocaleManagerTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/LocaleManagerTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java
new file mode 100644
index 0000000..b4f900a
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.res2.ResourceRepository;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+import java.util.Map;
+
+public class ResourceItemResolverTest extends TestCase {
+ @SuppressWarnings("ConstantConditions")
+ public void test() throws Exception {
+ final TestResourceRepository frameworkResources = TestResourceRepository.create(true,
+ new Object[]{
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"ok\">Ok</string>\n"
+ + " <array name=\"my_fw_array\">\"\n"
+ + " <item> fw_value1</item>\n" // also test trimming.
+ + " <item>fw_value2\n</item>\n"
+ + " <item>fw_value3</item>\n"
+ + " </array>\n"
+ + "</resources>\n",
+
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"Theme\">\n"
+ + " <item name=\"colorForeground\">@android:color/bright_foreground_dark</item>\n"
+ + " <item name=\"colorBackground\">@android:color/background_dark</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Light\">\n"
+ + " <item name=\"colorBackground\">@android:color/background_light</item>\n"
+ + " <item name=\"colorForeground\">@color/bright_foreground_light</item>\n"
+ + " </style>\n"
+ + "</resources>\n",
+
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"background_dark\">#ff000000</color>\n"
+ + " <color name=\"background_light\">#ffffffff</color>\n"
+ + " <color name=\"bright_foreground_dark\">@android:color/background_light</color>\n"
+ + " <color name=\"bright_foreground_light\">@android:color/background_dark</color>\n"
+ + "</resources>\n",
+ });
+
+ final ResourceRepository appResources = TestResourceRepository.createRes2(false,
+ new Object[]{
+ "layout/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout/layout2.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/only_land.xml", "<!--contents doesn't matter-->",
+
+ "drawable/graphic.9.png", new byte[0],
+
+ "values/styles.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n"
+ + " <item name=\"android:textColor\">#999999</item>\n"
+ + " <item name=\"foo\">?android:colorForeground</item>\n"
+ + " </style>\n"
+ + " <style name=\"MyTheme.Dotted1\" parent=\"\">\n"
+ + " </style>"
+ + " <style name=\"MyTheme.Dotted2\">\n"
+ + " </style>"
+ + " <style name=\"RandomStyle\">\n"
+ + " </style>"
+ + " <style name=\"RandomStyle2\" parent=\"RandomStyle\">\n"
+ + " </style>"
+ + "</resources>\n",
+
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
+ + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
+ + " <string name=\"home_title\">Home Sample</string>\n"
+ + " <string name=\"show_all_apps\">All</string>\n"
+ + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
+ + " <string name=\"menu_search\">Search</string>\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
+ + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
+ + " <string name=\"xliff_string\">First: <xliff:g id=\"firstName\">%1$s</xliff:g> Last: <xliff:g id=\"lastName\">%2$s</xliff:g></string>\n"
+ + " <array name=\"my_array\">\"\n"
+ + " <item>@string/home_title</item>\n"
+ + " <item>value2\n</item>\n"
+ + " <item>value3</item>\n"
+ + " </array>\n"
+ + "</resources>\n",
+
+ "values-es/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n",
+ });
+
+ final FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertFalse(appResources.isFramework());
+ assertNotNull(config);
+
+ final LayoutLog logger = new LayoutLog() {
+ @Override
+ public void warning(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void fidelityWarning(String tag, String message, Throwable throwable,
+ Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ fail(message);
+ }
+ };
+
+ ResourceItemResolver.ResourceProvider provider = new ResourceItemResolver.ResourceProvider() {
+ private ResourceResolver mResolver;
+
+ @Nullable
+ @Override
+ public ResourceResolver getResolver(boolean createIfNecessary) {
+ if (mResolver == null && createIfNecessary) {
+ Map<ResourceType, Map<String, ResourceValue>> appResourceMap;
+ appResourceMap = appResources.getConfiguredResources(config);
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResourcesMap =
+ frameworkResources.getConfiguredResources(config);
+ assertNotNull(appResourceMap);
+ mResolver = ResourceResolver.create(appResourceMap, frameworkResourcesMap,
+ "MyTheme", true);
+ assertNotNull(mResolver);
+ mResolver.setLogger(logger);
+ }
+
+ return mResolver;
+ }
+
+ @Nullable
+ @Override
+ public com.android.ide.common.resources.ResourceRepository getFrameworkResources() {
+ return frameworkResources;
+ }
+
+ @Nullable
+ @Override
+ public ResourceRepository getAppResources() {
+ return appResources;
+ }
+ };
+
+ ResourceItemResolver resolver = new ResourceItemResolver(config, provider, logger);
+
+ // findResValue
+ assertNotNull(resolver.findResValue("@string/show_all_apps", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", true));
+ assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
+ assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
+ assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
+ false).getValue());
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ assertEquals("@android:color/background_light",
+ resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
+ assertEquals("#ffffffff",
+ resolver.findResValue("@android:color/background_light", true).getValue());
+
+ // resolveResValue
+ // android:color/bright_foreground_dark => @android:color/background_light => white
+ assertEquals("Todo", resolver.resolveResValue(
+ resolver.findResValue("@string/show_all_apps", false)).getValue());
+ assertEquals("#ffffffff", resolver.resolveResValue(
+ resolver.findResValue("@android:color/bright_foreground_dark", false)).getValue());
+
+ // Test array values.
+ ResourceValue resValue = resolver.findResValue("@array/my_array", false);
+ resValue = resolver.resolveResValue(resValue); // test http://b.android.com/187097
+ assertTrue(resValue instanceof ArrayResourceValue);
+ assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
+ assertEquals("@string/home_title", ((ArrayResourceValue) resValue).getElement(0));
+ assertEquals("value2", ((ArrayResourceValue) resValue).getElement(1));
+ assertEquals("value3", ((ArrayResourceValue) resValue).getElement(2));
+ resValue = resolver.findResValue("@android:array/my_fw_array", false);
+ assertTrue(resValue instanceof ArrayResourceValue);
+ assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
+ assertEquals("fw_value1", ((ArrayResourceValue) resValue).getElement(0));
+ assertEquals("fw_value2", ((ArrayResourceValue) resValue).getElement(1));
+ assertEquals("fw_value3", ((ArrayResourceValue) resValue).getElement(2));
+
+
+ // Now do everything over again, but this time without a resource resolver.
+ // Also set a lookup chain.
+ resolver = new ResourceItemResolver(config, frameworkResources, appResources,
+ logger);
+ List<ResourceValue> chain = Lists.newArrayList();
+ resolver.setLookupChainList(chain);
+
+ // findResValue
+ assertNotNull(resolver.findResValue("@string/show_all_apps", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", true));
+ assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
+ assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
+ assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
+ false).getValue());
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ assertEquals("@android:color/background_light",
+ resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
+ assertEquals("#ffffffff",
+ resolver.findResValue("@android:color/background_light", true).getValue());
+ assertEquals("Todo", resolver.resolveResValue(
+ resolver.findResValue("@string/show_all_apps", false)).getValue());
+
+ chain.clear();
+ ResourceValue v = resolver.findResValue("@android:color/bright_foreground_dark", false);
+ assertEquals("@android:color/bright_foreground_dark => @android:color/background_light",
+ ResourceItemResolver.getDisplayString(ResourceType.COLOR, "bright_foreground_dark",
+ true, chain));
+ assertEquals("First: ${firstName} Last: ${lastName}",
+ resolver.findResValue("@string/xliff_string", false).getValue());
+ assertEquals("First: <xliff:g id=\"firstName\">%1$s</xliff:g> Last: <xliff:g id=\"lastName\">%2$s</xliff:g>",
+ resolver.findResValue("@string/xliff_string", false).getRawXmlValue());
+
+ chain.clear();
+ assertEquals("#ffffffff", resolver.resolveResValue(v).getValue());
+ assertEquals("@android:color/bright_foreground_dark => @android:color/background_light "
+ + "=> #ffffffff",
+ ResourceItemResolver.getDisplayString("@android:color/bright_foreground_dark",
+ chain));
+
+ // Try to resolve style attributes
+ resolver = new ResourceItemResolver(config, provider, logger);
+ resolver.setLookupChainList(chain);
+ chain.clear();
+ ResourceValue target = new ResourceValue(ResourceType.STRING, "dummy", false);
+ target.setValue("?foo");
+ assertEquals("#ff000000", resolver.resolveResValue(target).getValue());
+ assertEquals("?foo => ?android:colorForeground => @color/bright_foreground_light => "
+ + "@android:color/background_dark => #ff000000",
+ ResourceItemResolver.getDisplayString("?foo", chain));
+
+ // Test array values.
+ resValue = resolver.findResValue("@array/my_array", false);
+ assertTrue(resValue instanceof ArrayResourceValue);
+ assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
+ assertEquals("@string/home_title", ((ArrayResourceValue) resValue).getElement(0));
+ assertEquals("value2", ((ArrayResourceValue) resValue).getElement(1));
+ assertEquals("value3", ((ArrayResourceValue) resValue).getElement(2));
+ resValue = resolver.findResValue("@android:array/my_fw_array", false);
+ assertTrue(resValue instanceof ArrayResourceValue);
+ assertEquals(3, ((ArrayResourceValue) resValue).getElementCount());
+ assertEquals("fw_value1", ((ArrayResourceValue) resValue).getElement(0));
+ assertEquals("fw_value2", ((ArrayResourceValue) resValue).getElement(1));
+ assertEquals("fw_value3", ((ArrayResourceValue) resValue).getElement(2));
+
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java
new file mode 100644
index 0000000..2f803c1
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java
@@ -0,0 +1,643 @@
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.Density;
+import com.android.resources.ResourceType;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ResourceResolverTest extends TestCase {
+ public void test() throws Exception {
+ TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
+ new Object[]{
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"ok\">Ok</string>\n"
+ + "</resources>\n",
+
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"Theme\">\n"
+ + " <item name=\"colorForeground\">@android:color/bright_foreground_dark</item>\n"
+ + " <item name=\"colorBackground\">@android:color/background_dark</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Light\">\n"
+ + " <item name=\"colorBackground\">@android:color/background_light</item>\n"
+ + " <item name=\"colorForeground\">@color/bright_foreground_light</item>\n"
+ + " </style>\n"
+ + "</resources>\n",
+
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"background_dark\">#ff000000</color>\n"
+ + " <color name=\"background_light\">#ffffffff</color>\n"
+ + " <color name=\"bright_foreground_dark\">@android:color/background_light</color>\n"
+ + " <color name=\"bright_foreground_light\">@android:color/background_dark</color>\n"
+ + "</resources>\n",
+
+ "values/ids.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item name=\"some_framework_id\" type=\"id\" />\n"
+ + "</resources>\n",
+ });
+
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "layout/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout/layout2.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/onlyLand.xml", "<!--contents doesn't matter-->",
+
+ "drawable/graphic.9.png", new byte[0],
+
+ "mipmap-xhdpi/ic_launcher.png", new byte[0],
+
+ "values/styles.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n"
+ + " <item name=\"android:textColor\">#999999</item>\n"
+ + " <item name=\"foo\">?android:colorForeground</item>\n"
+ + " </style>\n"
+ + " <style name=\"MyTheme.Dotted1\" parent=\"\">\n"
+ + " </style>"
+ + " <style name=\"MyTheme.Dotted2\">\n"
+ + " </style>"
+ + " <style name=\"RandomStyle\">\n"
+ + " <item name=\"android:text\">© Copyright</item>\n"
+ + " </style>"
+ + " <style name=\"RandomStyle2\" parent=\"RandomStyle\">\n"
+ + " </style>"
+ + " <style name=\"Theme.FakeTheme\" parent=\"\">\n"
+ + " </style>"
+ + " <style name=\"Theme\" parent=\"RandomStyle\">\n"
+ + " </style>"
+ + "</resources>\n",
+
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
+ + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
+ + " <string name=\"home_title\">Home Sample</string>\n"
+ + " <string name=\"show_all_apps\">All</string>\n"
+ + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
+ + " <string name=\"menu_search\">Search</string>\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
+ + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
+ + "</resources>\n",
+
+ "values-es/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n",
+
+ "values/arrays.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"first\">Item1</string>\n"
+ + " <string-array name=\"my_array\">\n"
+ + " <item>@string/first</item>\n"
+ + " <item>Item2</item>\n"
+ + " <item>Item3</item>\n"
+ + " </string-array>\n"
+ + "</resources>\n",
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources =
+ frameworkRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, frameworkResources,
+ "MyTheme", true);
+ assertNotNull(resolver);
+
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void warning(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void fidelityWarning(String tag, String message, Throwable throwable,
+ Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ fail(message);
+ }
+ };
+ resolver.setLogger(logger);
+
+ assertEquals("MyTheme", resolver.getThemeName());
+ assertTrue(resolver.isProjectTheme());
+
+ // findResValue
+ assertNotNull(resolver.findResValue("@string/show_all_apps", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", true));
+ assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
+ assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
+ assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
+ false).getValue());
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ assertEquals("@android:color/background_light",
+ resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
+ assertEquals("#ffffffff",
+ resolver.findResValue("@android:color/background_light", true).getValue());
+ assertNull(resolver.findResValue("?attr/non_existent_style", false)); // shouldn't log an error.
+ assertEquals(Density.XHIGH,
+ ((DensityBasedResourceValue) resolver.findResValue("@mipmap/ic_launcher", false))
+ .getResourceDensity()); // also ensures that returned value is instance of DensityBasedResourceValue
+
+ // getTheme
+ StyleResourceValue myTheme = resolver.getTheme("MyTheme", false);
+ assertNotNull(myTheme);
+ assertSame(resolver.findResValue("@style/MyTheme", false), myTheme);
+ assertNull(resolver.getTheme("MyTheme", true));
+ assertNull(resolver.getTheme("MyNonexistentTheme", true));
+ StyleResourceValue themeLight = resolver.getTheme("Theme.Light", true);
+ assertNotNull(themeLight);
+ StyleResourceValue theme = resolver.getTheme("Theme", true);
+ assertNotNull(theme);
+
+ // getParent
+ StyleResourceValue parent = resolver.getParent(myTheme);
+ assertNotNull(parent);
+ assertEquals("Theme.Light", parent.getName());
+
+ // themeIsParentOf
+ assertTrue(resolver.themeIsParentOf(themeLight, myTheme));
+ assertFalse(resolver.themeIsParentOf(myTheme, themeLight));
+ assertTrue(resolver.themeIsParentOf(theme, themeLight));
+ assertFalse(resolver.themeIsParentOf(themeLight, theme));
+ assertTrue(resolver.themeIsParentOf(theme, myTheme));
+ assertFalse(resolver.themeIsParentOf(myTheme, theme));
+ StyleResourceValue dotted1 = resolver.getTheme("MyTheme.Dotted1", false);
+ assertNotNull(dotted1);
+ StyleResourceValue dotted2 = resolver.getTheme("MyTheme.Dotted2", false);
+ assertNotNull(dotted2);
+ assertTrue(resolver.themeIsParentOf(myTheme, dotted2));
+ assertFalse(resolver.themeIsParentOf(myTheme, dotted1)); // because parent=""
+
+ // isTheme
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/RandomStyle", false), null));
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/RandomStyle2", false), null));
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/Theme.FakeTheme", false), null));
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/Theme", false), null));
+ // check XML escaping in value resources
+ StyleResourceValue randomStyle = (StyleResourceValue) resolver.findResValue(
+ "@style/RandomStyle", false);
+ assertEquals("\u00a9 Copyright", randomStyle.getItem("text", true).getValue());
+ assertTrue(resolver.isTheme(resolver.findResValue("@style/MyTheme.Dotted2", false), null));
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/MyTheme.Dotted1", false),
+ null));
+ assertTrue(resolver.isTheme(resolver.findResValue("@style/MyTheme", false), null));
+ assertTrue(resolver.isTheme(resolver.findResValue("@android:style/Theme.Light", false),
+ null));
+ assertTrue(resolver.isTheme(resolver.findResValue("@android:style/Theme", false), null));
+
+ // findItemInStyle
+ assertNotNull(resolver.findItemInStyle(myTheme, "colorForeground", true));
+ assertEquals("@color/bright_foreground_light",
+ resolver.findItemInStyle(myTheme, "colorForeground", true).getValue());
+ assertNotNull(resolver.findItemInStyle(dotted2, "colorForeground", true));
+ assertNull(resolver.findItemInStyle(dotted1, "colorForeground", true));
+
+ // findItemInTheme
+ assertNotNull(resolver.findItemInTheme("colorForeground", true));
+ assertEquals("@color/bright_foreground_light",
+ resolver.findItemInTheme("colorForeground", true).getValue());
+ assertEquals("@color/bright_foreground_light",
+ resolver.findResValue("?colorForeground", true).getValue());
+ ResourceValue target = new ResourceValue(ResourceType.STRING, "dummy", false);
+ target.setValue("?foo");
+ assertEquals("#ff000000", resolver.resolveResValue(target).getValue());
+
+ // getFrameworkResource
+ assertNull(resolver.getFrameworkResource(ResourceType.STRING, "show_all_apps"));
+ assertNotNull(resolver.getFrameworkResource(ResourceType.STRING, "ok"));
+ assertEquals("Ok", resolver.getFrameworkResource(ResourceType.STRING, "ok").getValue());
+
+ // getProjectResource
+ assertNull(resolver.getProjectResource(ResourceType.STRING, "ok"));
+ assertNotNull(resolver.getProjectResource(ResourceType.STRING, "show_all_apps"));
+ assertEquals("Todo", resolver.getProjectResource(ResourceType.STRING,
+ "show_all_apps").getValue());
+
+
+ // resolveResValue
+ // android:color/bright_foreground_dark => @android:color/background_light => white
+ assertEquals("Todo", resolver.resolveResValue(
+ resolver.findResValue("@string/show_all_apps", false)).getValue());
+ assertEquals("#ffffffff", resolver.resolveResValue(
+ resolver.findResValue("@android:color/bright_foreground_dark", false)).getValue());
+
+ // resolveValue
+ assertEquals("#ffffffff",
+ resolver.resolveValue(ResourceType.STRING, "bright_foreground_dark",
+ "@android:color/background_light", true).getValue());
+ assertFalse(resolver.resolveValue(null, "id", "@+id/some_framework_id", false)
+ .isFramework());
+ // error expected.
+ boolean failed = false;
+ ResourceValue val = null;
+ try {
+ val = resolver.resolveValue(ResourceType.STRING, "bright_foreground_dark",
+ "@color/background_light", false);
+ } catch (AssertionError expected) {
+ failed = true;
+ }
+ assertTrue("incorrect resource returned: " + val, failed);
+ ResourceValue array = resolver
+ .resolveResValue(resolver.getProjectResource(ResourceType.ARRAY, "my_array"));
+ assertTrue("array" + "my_array" + "resolved incorrectly as " + array.getResourceType()
+ .getName(), array instanceof ArrayResourceValue);
+
+ // themeExtends
+ assertTrue(resolver.themeExtends("@android:style/Theme", "@android:style/Theme"));
+ assertTrue(resolver.themeExtends("@android:style/Theme", "@android:style/Theme.Light"));
+ assertFalse(resolver.themeExtends("@android:style/Theme.Light", "@android:style/Theme"));
+ assertTrue(resolver.themeExtends("@style/MyTheme.Dotted2", "@style/MyTheme.Dotted2"));
+ assertTrue(resolver.themeExtends("@style/MyTheme", "@style/MyTheme.Dotted2"));
+ assertTrue(resolver.themeExtends("@android:style/Theme.Light", "@style/MyTheme.Dotted2"));
+ assertTrue(resolver.themeExtends("@android:style/Theme", "@style/MyTheme.Dotted2"));
+ assertFalse(resolver.themeExtends("@style/MyTheme.Dotted1", "@style/MyTheme.Dotted2"));
+
+ // Switch to MyTheme.Dotted1 (to make sure the parent="" inheritance works properly.)
+ // To do that we need to create a new resource resolver.
+ resolver = ResourceResolver.create(projectResources, frameworkResources,
+ "MyTheme.Dotted1", true);
+ resolver.setLogger(logger);
+ assertNotNull(resolver);
+ assertEquals("MyTheme.Dotted1", resolver.getThemeName());
+ assertTrue(resolver.isProjectTheme());
+ assertNull(resolver.findItemInTheme("colorForeground", true));
+
+ resolver = ResourceResolver.create(projectResources, frameworkResources,
+ "MyTheme.Dotted2", true);
+ resolver.setLogger(logger);
+ assertNotNull(resolver);
+ assertEquals("MyTheme.Dotted2", resolver.getThemeName());
+ assertTrue(resolver.isProjectTheme());
+ assertNotNull(resolver.findItemInTheme("colorForeground", true));
+
+ // Test recording resolver
+ List<ResourceValue> chain = Lists.newArrayList();
+ resolver = ResourceResolver.create(projectResources, frameworkResources, "MyTheme", true);
+ resolver = resolver.createRecorder(chain);
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ ResourceValue v = resolver.findResValue("@android:color/bright_foreground_dark", false);
+ chain.clear();
+ assertEquals("#ffffffff", resolver.resolveResValue(v).getValue());
+ assertEquals("@android:color/bright_foreground_dark => "
+ + "@android:color/background_light => #ffffffff",
+ ResourceItemResolver.getDisplayString("@android:color/bright_foreground_dark",
+ chain));
+
+ frameworkRepository.dispose();
+ projectRepository.dispose();
+ }
+
+ public void testMissingMessage() throws Exception {
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"loop1\">@color/loop1</color>\n"
+ + " <color name=\"loop2a\">@color/loop2b</color>\n"
+ + " <color name=\"loop2b\">@color/loop2a</color>\n"
+ + "</resources>\n",
+
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
+ "MyTheme", true);
+ final AtomicBoolean wasWarned = new AtomicBoolean(false);
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void warning(String tag, String message, Object data) {
+ if ("Couldn't resolve resource @android:string/show_all_apps".equals(message)) {
+ wasWarned.set(true);
+ } else {
+ fail(message);
+ }
+ }
+ };
+ resolver.setLogger(logger);
+ assertNull(resolver.findResValue("@string/show_all_apps", true));
+ assertTrue(wasWarned.get());
+ projectRepository.dispose();
+ }
+
+ public void testLoop() throws Exception {
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"loop1\">@color/loop1</color>\n"
+ + " <color name=\"loop2a\">@color/loop2b</color>\n"
+ + " <color name=\"loop2b\">@color/loop2a</color>\n"
+ + "</resources>\n",
+
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
+ "MyTheme", true);
+ assertNotNull(resolver);
+
+ final AtomicBoolean wasWarned = new AtomicBoolean(false);
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void error(String tag, String message, Object data) {
+ if (("Potential stack overflow trying to resolve "
+ + "'@color/loop1': cyclic resource definitions?"
+ + " Render may not be accurate.").equals(message)) {
+ wasWarned.set(true);
+ } else if (("Potential stack overflow trying to resolve "
+ + "'@color/loop2b': cyclic resource definitions? "
+ + "Render may not be accurate.").equals(message)) {
+ wasWarned.set(true);
+ } else {
+ fail(message);
+ }
+ }
+ };
+ resolver.setLogger(logger);
+
+ assertNotNull(resolver.findResValue("@color/loop1", false));
+ resolver.resolveResValue(resolver.findResValue("@color/loop1", false));
+ assertTrue(wasWarned.get());
+
+ wasWarned.set(false);
+ assertNotNull(resolver.findResValue("@color/loop2a", false));
+ resolver.resolveResValue(resolver.findResValue("@color/loop2a", false));
+ assertTrue(wasWarned.get());
+
+ projectRepository.dispose();
+ }
+
+ public void testParentCycle() throws IOException {
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "values/styles.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"ButtonStyle.Base\">\n"
+ + " <item name=\"android:textColor\">#ff0000</item>\n"
+ + " </style>\n"
+ + " <style name=\"ButtonStyle\" parent=\"ButtonStyle.Base\">\n"
+ + " <item name=\"android:layout_height\">40dp</item>\n"
+ + " </style>\n"
+ + "</resources>\n",
+
+ "layouts/layout.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\">\n"
+ + "\n"
+ + " <TextView\n"
+ + " style=\"@style/ButtonStyle\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\" />\n"
+ + "\n"
+ + "</RelativeLayout>\n",
+
+ });
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
+ "ButtonStyle", true);
+ assertNotNull(resolver);
+
+ final AtomicBoolean wasWarned = new AtomicBoolean(false);
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void error(String tag, String message, Object data) {
+ assertEquals("Cyclic style parent definitions: \"ButtonStyle\" specifies "
+ + "parent \"ButtonStyle.Base\" implies parent \"ButtonStyle\"", message);
+ assertEquals(LayoutLog.TAG_BROKEN, tag);
+ wasWarned.set(true);
+ }
+ };
+ resolver.setLogger(logger);
+
+ StyleResourceValue buttonStyle = (StyleResourceValue) resolver.findResValue(
+ "@style/ButtonStyle", false);
+ ResourceValue textColor = resolver.findItemInStyle(buttonStyle, "textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#ff0000", textColor.getValue());
+ assertFalse(wasWarned.get());
+ ResourceValue missing = resolver.findItemInStyle(buttonStyle, "missing", true);
+ assertNull(missing);
+ assertTrue(wasWarned.get());
+
+ projectRepository.dispose();
+ }
+
+ public void testSetDeviceDefaults() throws Exception {
+ TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
+ new Object[] {
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"Theme.Light\" parent=\"\">\n"
+ + " <item name=\"android:textColor\">#ff0000</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Holo.Light\" parent=\"Theme.Light\">\n"
+ + " <item name=\"android:textColor\">#00ff00</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.DeviceDefault.Light\" parent=\"Theme.Holo.Light\"/>\n"
+ + " <style name=\"Theme\" parent=\"\">\n"
+ + " <item name=\"android:textColor\">#000000</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Holo\" parent=\"Theme\">\n"
+ + " <item name=\"android:textColor\">#0000ff</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.DeviceDefault\" parent=\"Theme.Holo\"/>\n"
+ + "</resources>\n",
+
+ "values/styles.xml", ""
+ + "<resources>\n"
+ + " <style name=\"Widget.Button.Small\">\n"
+ + " <item name=\"android:textColor\">#000000</item>\n"
+ + " </style>\n"
+ + " <style name=\"Widget.Holo.Button.Small\">\n"
+ + " <item name=\"android:textColor\">#ffffff</item>\n"
+ + " </style>\n"
+ + " <style name=\"Widget.DeviceDefault.Button.Small\" parent=\"Widget.Holo.Button.Small\" />\n"
+ + " <style name=\"ButtonBar\">\n"
+ + " <item name=\"android:textColor\">#000000</item>\n"
+ + " </style>\n"
+ + " <style name=\"Holo.ButtonBar\">\n"
+ + " <item name=\"android:textColor\">#ffffff</item>\n"
+ + " </style>\n"
+ + " <style name=\"DeviceDefault.ButtonBar\" parent=\"Holo.ButtonBar\" />\n"
+ + "</resources>\n",
+ });
+
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[] {
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"AppTheme\" parent=\"android:Theme.DeviceDefault.Light\"/>\n"
+ + " <style name=\"AppTheme.Dark\" parent=\"android:Theme.DeviceDefault\"/>\n"
+ + "</resources>\n"
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources = projectRepository
+ .getConfiguredResources(config);
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources = frameworkRepository
+ .getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver lightResolver = ResourceResolver.create(projectResources,
+ frameworkResources, "AppTheme", true);
+ assertNotNull(lightResolver);
+ ResourceValue textColor = lightResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#00ff00", textColor.getValue());
+
+ lightResolver.setDeviceDefaults(ResourceResolver.LEGACY_THEME);
+ textColor = lightResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#ff0000", textColor.getValue());
+
+ ResourceResolver darkResolver = ResourceResolver.create(projectResources,
+ frameworkResources, "AppTheme.Dark", true);
+ assertNotNull(darkResolver);
+ textColor = darkResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#0000ff", textColor.getValue());
+
+ darkResolver.setDeviceDefaults(ResourceResolver.LEGACY_THEME);
+ textColor = darkResolver.findItemInTheme("textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#000000", textColor.getValue());
+
+ // Check styles are correctly patched. We could use either resolver for that
+ textColor = darkResolver
+ .findItemInStyle(lightResolver.getStyle("Widget.DeviceDefault.Button.Small", true),
+ "textColor", true);
+ assertEquals("#000000", textColor.getValue());
+ textColor = darkResolver
+ .findItemInStyle(lightResolver.getStyle("DeviceDefault.ButtonBar", true),
+ "textColor", true);
+ assertEquals("#000000", textColor.getValue());
+
+ darkResolver.setDeviceDefaults("Holo");
+ textColor = darkResolver
+ .findItemInStyle(lightResolver.getStyle("Widget.DeviceDefault.Button.Small", true),
+ "textColor", true);
+ assertEquals("#ffffff", textColor.getValue());
+ textColor = darkResolver
+ .findItemInStyle(lightResolver.getStyle("DeviceDefault.ButtonBar", true),
+ "textColor", true);
+ assertEquals("#ffffff", textColor.getValue());
+ }
+
+ public void testCycle() throws Exception {
+ TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
+ new Object[] {
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"Theme.DeviceDefault.Light\"/>\n"
+ + "</resources>\n",
+ });
+
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[] {
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"AppTheme\" parent=\"android:Theme.DeviceDefault.Light\"/>\n"
+ + " <style name=\"AppTheme.Dark\" parent=\"android:Theme.DeviceDefault\"/>\n"
+ + " <style name=\"foo\" parent=\"bar\"/>\n"
+ + " <style name=\"bar\" parent=\"foo\"/>\n"
+ + "</resources>\n"
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources = projectRepository
+ .getConfiguredResources(config);
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources = frameworkRepository
+ .getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources,
+ frameworkResources, "AppTheme", true);
+
+ final AtomicBoolean wasWarned = new AtomicBoolean(false);
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void error(String tag, String message, Object data) {
+ if ("Cyclic style parent definitions: \"foo\" specifies parent \"bar\" specifies parent \"foo\"".equals(message)) {
+ wasWarned.set(true);
+ } else {
+ fail(message);
+ }
+ }
+ };
+ resolver.setLogger(logger);
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/foo", false), null));
+ assertTrue(wasWarned.get());
+
+ projectRepository.dispose();
+
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceUrlTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceUrlTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/ResourceUrlTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/ResourceUrlTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java b/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java
new file mode 100644
index 0000000..ff3ddc6
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java
@@ -0,0 +1,151 @@
+package com.android.ide.common.resources;
+
+import static com.android.SdkConstants.FD_RES;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.res2.MergingException;
+import com.android.ide.common.res2.RecordingLogger;
+import com.android.ide.common.res2.ResourceMerger;
+import com.android.ide.common.res2.ResourceSet;
+import com.android.io.FolderWrapper;
+import com.android.io.IAbstractFolder;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+
+public class TestResourceRepository extends ResourceRepository {
+ private final File mDir;
+
+ TestResourceRepository(@NonNull IAbstractFolder resFolder, boolean isFrameworkRepository,
+ File dir) {
+ super(resFolder, isFrameworkRepository);
+ mDir = dir;
+ }
+
+ @NonNull
+ @Override
+ protected ResourceItem createResourceItem(@NonNull String name) {
+ return new TestResourceItem(name);
+ }
+
+ public File getDir() {
+ return mDir;
+ }
+
+ public void dispose() {
+ deleteFile(mDir);
+ }
+
+ private static void deleteFile(File dir) {
+ if (dir.isDirectory()) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ deleteFile(f);
+ }
+ }
+ } else if (dir.isFile()) {
+ assertTrue(dir.getPath(), dir.delete());
+ }
+ }
+
+ /**
+ * Creates a resource repository for a resource folder whose contents is identified
+ * by the pairs of relative paths and file contents
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @NonNull
+ public static TestResourceRepository create(boolean isFramework, Object[] data)
+ throws IOException {
+ File dir = Files.createTempDir();
+ File res = new File(dir, FD_RES);
+ res.mkdirs();
+
+ assertTrue("Expected even number of items (path,contents)", data.length % 2 == 0);
+ for (int i = 0; i < data.length; i += 2) {
+ Object relativePathObject = data[i];
+ assertTrue(relativePathObject instanceof String);
+ String relativePath = (String) relativePathObject;
+ relativePath = relativePath.replace('/', File.separatorChar);
+ File file = new File(res, relativePath);
+ File parent = file.getParentFile();
+ parent.mkdirs();
+
+ Object fileContents = data[i + 1];
+ if (fileContents instanceof String) {
+ String text = (String) fileContents;
+ Files.write(text, file, Charsets.UTF_8);
+ } else if (fileContents instanceof byte[]) {
+ byte[] bytes = (byte[]) fileContents;
+ Files.write(bytes, file);
+ } else {
+ fail("File contents must be Strings or byte[]'s");
+ }
+ }
+
+ IAbstractFolder resFolder = new FolderWrapper(dir, FD_RES);
+ return new TestResourceRepository(resFolder, isFramework, dir);
+ }
+
+ /**
+ * Creates a res2 resource repository for a resource folder whose contents is identified
+ * by the pairs of relative paths and file contents
+ *
+ * @see #create(boolean, Object[])
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @NonNull
+ public static com.android.ide.common.res2.ResourceRepository createRes2(
+ boolean isFramework, Object[] data)
+ throws IOException, MergingException {
+ File dir = Files.createTempDir();
+ File res = new File(dir, FD_RES);
+ res.mkdirs();
+
+ assertTrue("Expected even number of items (path,contents)", data.length % 2 == 0);
+ for (int i = 0; i < data.length; i += 2) {
+ Object relativePathObject = data[i];
+ assertTrue(relativePathObject instanceof String);
+ String relativePath = (String) relativePathObject;
+ relativePath = relativePath.replace('/', File.separatorChar);
+ File file = new File(res, relativePath);
+ File parent = file.getParentFile();
+ parent.mkdirs();
+
+ Object fileContents = data[i + 1];
+ if (fileContents instanceof String) {
+ String text = (String) fileContents;
+ Files.write(text, file, Charsets.UTF_8);
+ } else if (fileContents instanceof byte[]) {
+ byte[] bytes = (byte[]) fileContents;
+ Files.write(bytes, file);
+ } else {
+ fail("File contents must be Strings or byte[]'s");
+ }
+ }
+
+ File resFolder = new File(dir, FD_RES);
+
+ ResourceMerger merger = new ResourceMerger(0);
+ ResourceSet resourceSet = new ResourceSet("main");
+ resourceSet.addSource(resFolder);
+ resourceSet.loadFromFiles(new RecordingLogger());
+ merger.addDataSet(resourceSet);
+
+ com.android.ide.common.res2.ResourceRepository repository;
+ repository = new com.android.ide.common.res2.ResourceRepository(isFramework);
+ merger.mergeData(repository.createMergeConsumer(), true /*doCleanUp*/);
+
+ return repository;
+ }
+
+ private static class TestResourceItem extends ResourceItem {
+ TestResourceItem(String name) {
+ super(name);
+ }
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/CountryCodeQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/CountryCodeQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/CountryCodeQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/CountryCodeQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/DensityQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/DensityQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/DensityQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/DensityQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/DockModeQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/DockModeQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/DockModeQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/DockModeQualifierTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java
new file mode 100644
index 0000000..d426a72
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.ide.common.res2.ResourceFile;
+import com.android.ide.common.res2.ResourceItem;
+import com.android.resources.Density;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenRound;
+import com.android.resources.UiMode;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class FolderConfigurationTest extends TestCase {
+
+ /*
+ * Test createDefault creates all the qualifiers.
+ */
+ public void testCreateDefault() {
+ FolderConfiguration defaultConfig = new FolderConfiguration();
+ defaultConfig.createDefault();
+
+ // this is always valid and up to date.
+ final int count = FolderConfiguration.getQualifierCount();
+
+ // make sure all the qualifiers were created.
+ for (int i = 0 ; i < count ; i++) {
+ assertNotNull(defaultConfig.getQualifier(i));
+ }
+ }
+
+ public void testSimpleResMatch() {
+ runConfigMatchTest(
+ "en-rGB-port-hdpi-notouch-12key",
+ 3,
+ "",
+ "en",
+ "fr-rCA",
+ "en-port",
+ "en-notouch-12key",
+ "port-ldpi",
+ "port-notouch-12key");
+ }
+
+ public void testIsMatchFor() {
+ FolderConfiguration en = FolderConfiguration.getConfigForFolder("values-en");
+ FolderConfiguration enUs = FolderConfiguration.getConfigForFolder("values-en-rUS");
+ assertNotNull(en);
+ assertNotNull(enUs);
+ assertTrue(enUs.isMatchFor(enUs));
+ assertTrue(en.isMatchFor(en));
+ assertTrue(enUs.isMatchFor(en));
+ assertTrue(en.isMatchFor(enUs));
+ }
+
+ public void testVersionResMatch() {
+ runConfigMatchTest(
+ "en-rUS-w600dp-h1024dp-large-port-mdpi-finger-nokeys-v12",
+ 2,
+ "",
+ "large",
+ "w540dp");
+ }
+
+ public void testVersionResMatchWithBcp47() {
+ runConfigMatchTest(
+ "b+kok+Knda+419+VARIANT-w600dp",
+ 2,
+ "",
+ "large",
+ "w540dp");
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testAddQualifier() {
+ FolderConfiguration defaultConfig = new FolderConfiguration();
+ defaultConfig.createDefault();
+
+ final int count = FolderConfiguration.getQualifierCount();
+ for (int i = 0 ; i < count ; i++) {
+ FolderConfiguration empty = new FolderConfiguration();
+
+ ResourceQualifier q = defaultConfig.getQualifier(i);
+
+ empty.addQualifier(q);
+
+ // check it was added
+ assertNotNull(
+ "addQualifier failed for " + q.getClass().getName(), empty.getQualifier(i));
+ }
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetConfig1() {
+ FolderConfiguration configForFolder =
+ FolderConfiguration.getConfig(new String[] { "values", "en", "rUS" });
+ assertNotNull(configForFolder);
+ assertEquals("en", configForFolder.getLocaleQualifier().getLanguage());
+ assertEquals("US", configForFolder.getLocaleQualifier().getRegion());
+ assertNull(configForFolder.getScreenDimensionQualifier());
+ assertNull(configForFolder.getLayoutDirectionQualifier());
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testInvalidRepeats() {
+ assertNull(FolderConfiguration.getConfigForFolder("values-en-rUS-rES"));
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetConfig2() {
+ FolderConfiguration configForFolder =
+ FolderConfiguration.getConfigForFolder("values-en-rUS");
+ assertNotNull(configForFolder);
+ assertEquals("en", configForFolder.getLocaleQualifier().getLanguage());
+ assertEquals("US", configForFolder.getLocaleQualifier().getRegion());
+ assertNull(configForFolder.getScreenDimensionQualifier());
+ assertNull(configForFolder.getLayoutDirectionQualifier());
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetConfigCaseInsensitive() {
+ FolderConfiguration configForFolder =
+ FolderConfiguration.getConfigForFolder("values-EN-rus");
+ assertNotNull(configForFolder);
+ assertEquals("en", configForFolder.getLocaleQualifier().getLanguage());
+ assertEquals("US", configForFolder.getLocaleQualifier().getRegion());
+ assertNull(configForFolder.getScreenDimensionQualifier());
+ assertNull(configForFolder.getLayoutDirectionQualifier());
+ assertEquals("layout-en-rUS", configForFolder.getFolderName(ResourceFolderType.LAYOUT));
+
+ runConfigMatchTest(
+ "en-rgb-Port-HDPI-notouch-12key",
+ 3,
+ "",
+ "en",
+ "fr-rCA",
+ "en-port",
+ "en-notouch-12key",
+ "port-ldpi",
+ "port-notouch-12key");
+ }
+
+ public void testToStrings() {
+ FolderConfiguration configForFolder = FolderConfiguration.getConfigForFolder("values-en-rUS");
+ assertNotNull(configForFolder);
+ assertEquals("Locale en_US", configForFolder.toDisplayString());
+ assertEquals("en,US", configForFolder.toShortDisplayString());
+ assertEquals("layout-en-rUS", configForFolder.getFolderName(ResourceFolderType.LAYOUT));
+ assertEquals("-en-rUS", configForFolder.getUniqueKey());
+ }
+
+ public void testNormalize() {
+ // test normal qualifiers that all have the same min SDK
+ doTestNormalize(4, "large");
+ doTestNormalize(8, "notnight");
+ doTestNormalize(13, "sw42dp");
+ doTestNormalize(17, "ldrtl");
+
+ // test we take the highest qualifier
+ doTestNormalize(13, "sw42dp", "large");
+
+ // test where different values have different minSdk
+ /* Ambiguous now that aapt accepts 3 letter language codes; get clarification.
+ doTestNormalize(8, "car");
+ */
+ doTestNormalize(13, "television");
+ doTestNormalize(16, "appliance");
+
+ // test case where there's already a higher -v# qualifier
+ doTestNormalize(18, "sw42dp", "v18");
+
+ // finally test that in some cases it won't add a -v# value.
+ FolderConfiguration configForFolder = FolderConfiguration.getConfigFromQualifiers(
+ Collections.singletonList("port"));
+
+ assertNotNull(configForFolder);
+
+ configForFolder.normalize();
+ VersionQualifier versionQualifier = configForFolder.getVersionQualifier();
+ assertNull(versionQualifier);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testConfigMatch() {
+ FolderConfiguration ref = new FolderConfiguration();
+ ref.createDefault();
+ ref.addQualifier(new ScreenOrientationQualifier(ScreenOrientation.PORTRAIT));
+ List<Configurable> configurables = getConfigurable(
+ "", // No qualifier
+ "xhdpi", // A matching qualifier
+ "land", // A non matching qualifier
+ "xhdpi-v14", // Matching qualifier with ignored qualifier
+ "v14" // Ignored qualifier
+ );
+ // First check that when all qualifiers are present, we match only one resource.
+ List<Configurable> matchingConfigurables = ref.findMatchingConfigurables(configurables);
+ assertEquals(ImmutableList.of(configurables.get(3)), matchingConfigurables);
+
+ // Now remove the version qualifier and check that we "xhdpi" and "xhdpi-v14"
+ ref.setVersionQualifier(null);
+ matchingConfigurables = ref.findMatchingConfigurables(configurables);
+ assertEquals(ImmutableSet.of(configurables.get(1), configurables.get(3)),
+ ImmutableSet.copyOf(matchingConfigurables));
+
+ }
+
+ public void testIsRoundMatch() {
+ FolderConfiguration configForFolder = FolderConfiguration
+ .getConfigForFolder("values-en-round");
+ assertNotNull(configForFolder);
+ assertNotNull(configForFolder.getScreenRoundQualifier());
+ assertEquals(ScreenRound.ROUND, configForFolder.getScreenRoundQualifier().getValue());
+ runConfigMatchTest("en-rgb-Round-Port-HDPI-notouch-12key", 4,
+ "",
+ "en",
+ "fr-rCa",
+ "en-notround-hdpi",
+ "en-notouch");
+
+ runConfigMatchTest("en-rgb-Round-Port-HDPI-notouch-12key", 2,
+ "",
+ "en",
+ "en-round-hdpi",
+ "port-12key");
+ }
+
+ // --- helper methods
+
+ private static final class MockConfigurable implements Configurable {
+
+ private final FolderConfiguration mConfig;
+
+ MockConfigurable(String config) {
+ mConfig = FolderConfiguration.getConfig(getFolderSegments(config));
+ }
+
+ @Override
+ public FolderConfiguration getConfiguration() {
+ return mConfig;
+ }
+
+ @Override
+ public String toString() {
+ return mConfig.toString();
+ }
+ }
+
+ private static void runConfigMatchTest(String refConfig, int resultIndex, String... configs) {
+ FolderConfiguration reference = FolderConfiguration.getConfig(getFolderSegments(refConfig));
+ assertNotNull(reference);
+
+ List<Configurable> list = getConfigurable(configs);
+
+ Configurable match = reference.findMatchingConfigurable(list);
+ assertEquals(resultIndex, list.indexOf(match));
+ }
+
+ private static List<Configurable> getConfigurable(String... configs) {
+ ArrayList<Configurable> list = new ArrayList<Configurable>();
+
+ for (String config : configs) {
+ list.add(new MockConfigurable(config));
+ }
+
+ return list;
+ }
+
+ private static String[] getFolderSegments(String config) {
+ return (!config.isEmpty() ? "foo-" + config : "foo").split("-");
+ }
+
+ public void testSort1() {
+ List<FolderConfiguration> configs = Lists.newArrayList();
+ FolderConfiguration f1 = FolderConfiguration.getConfigForFolder("values-hdpi");
+ FolderConfiguration f2 = FolderConfiguration.getConfigForFolder("values-v11");
+ FolderConfiguration f3 = FolderConfiguration.getConfigForFolder("values-sp");
+ FolderConfiguration f4 = FolderConfiguration.getConfigForFolder("values-v4");
+ configs.add(f1);
+ configs.add(f2);
+ configs.add(f3);
+ configs.add(f4);
+ assertEquals(Arrays.asList(f1, f2, f3, f4), configs);
+ Collections.sort(configs);
+ assertEquals(Arrays.asList(f2, f4, f1, f3), configs);
+ }
+
+ public void testSort2() {
+ // Test case from
+ // http://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch
+ List<FolderConfiguration> configs = Lists.newArrayList();
+ for (String name : new String[] {
+ "drawable",
+ "drawable-en",
+ "drawable-fr-rCA",
+ "drawable-en-port",
+ "drawable-en-notouch-12key",
+ "drawable-port-ldpi",
+ "drawable-port-notouch-12key"
+ }) {
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder(name);
+ assertNotNull(name, config);
+ configs.add(config);
+ }
+ Collections.sort(configs);
+ Collections.reverse(configs);
+ //assertEquals("", configs.get(0).toDisplayString());
+
+ List<String> strings = Lists.newArrayList();
+ for (FolderConfiguration config : configs) {
+ strings.add(config.getUniqueKey());
+ }
+ assertEquals("-fr-rCA,-en-port,-en-notouch-12key,-en,-port-ldpi,-port-notouch-12key,",
+ Joiner.on(",").skipNulls().join(strings));
+
+ }
+
+ private void doTestNormalize(int expectedVersion, String... segments) {
+ FolderConfiguration configForFolder = FolderConfiguration.getConfigFromQualifiers(
+ Arrays.asList(segments));
+
+ assertNotNull(configForFolder);
+
+ configForFolder.normalize();
+ VersionQualifier versionQualifier = configForFolder.getVersionQualifier();
+ assertNotNull(versionQualifier);
+ assertEquals(expectedVersion, versionQualifier.getVersion());
+
+ }
+
+ public void testCarModeAndLanguage() {
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-car");
+ assertNotNull(config);
+ assertNull(config.getLocaleQualifier());
+ assertNotNull(config.getUiModeQualifier());
+ assertEquals(UiMode.CAR, config.getUiModeQualifier().getValue());
+
+ config = FolderConfiguration.getConfigForFolder("values-b+car");
+ assertNotNull(config);
+ assertNotNull(config.getLocaleQualifier());
+ assertNull(config.getUiModeQualifier());
+ assertEquals("car", config.getLocaleQualifier().getLanguage());
+ }
+
+ public void testIsMatchForBcp47() {
+ FolderConfiguration blankFolder = FolderConfiguration.getConfigForFolder("values");
+ FolderConfiguration enFolder = FolderConfiguration.getConfigForFolder("values-en");
+ FolderConfiguration deFolder = FolderConfiguration.getConfigForFolder("values-de");
+ FolderConfiguration deBcp47Folder = FolderConfiguration.getConfigForFolder("values-b+de");
+ assertNotNull(enFolder);
+ assertNotNull(deFolder);
+ assertNotNull(deBcp47Folder);
+ assertFalse(enFolder.isMatchFor(deFolder));
+ assertFalse(deFolder.isMatchFor(enFolder));
+ assertFalse(enFolder.isMatchFor(deBcp47Folder));
+ assertFalse(deBcp47Folder.isMatchFor(enFolder));
+
+ assertTrue(enFolder.isMatchFor(blankFolder));
+ assertTrue(deFolder.isMatchFor(blankFolder));
+ assertTrue(deBcp47Folder.isMatchFor(blankFolder));
+ }
+
+ public void testFindMatchingConfigurables() {
+ ResourceItem itemBlank = new ResourceItem("foo", ResourceType.STRING, null) {
+ @Override
+ public String toString() {
+ return "itemBlank";
+ }
+ };
+ ResourceFile sourceBlank = new ResourceFile(new File("sourceBlank"), itemBlank, "");
+ itemBlank.setSource(sourceBlank);
+ FolderConfiguration configBlank = itemBlank.getConfiguration();
+
+ ResourceItem itemEn = new ResourceItem("foo", ResourceType.STRING, null) {
+ @Override
+ public String toString() {
+ return "itemEn";
+ }
+ };
+ ResourceFile sourceEn = new ResourceFile(new File("sourceEn"), itemBlank, "en");
+ itemEn.setSource(sourceEn);
+ FolderConfiguration configEn = itemEn.getConfiguration();
+
+ ResourceItem itemBcpEn = new ResourceItem("foo", ResourceType.STRING, null) {
+ @Override
+ public String toString() {
+ return "itemBcpEn";
+ }
+ };
+ ResourceFile sourceBcpEn = new ResourceFile(new File("sourceBcpEn"), itemBlank, "b+en");
+ itemBcpEn.setSource(sourceBcpEn);
+ FolderConfiguration configBcpEn = itemBcpEn.getConfiguration();
+
+ ResourceItem itemDe = new ResourceItem("foo", ResourceType.STRING, null) {
+ @Override
+ public String toString() {
+ return "itemDe";
+ }
+ };
+
+ ResourceFile sourceDe = new ResourceFile(new File("sourceDe"), itemBlank, "de");
+ itemDe.setSource(sourceDe);
+ FolderConfiguration configDe = itemDe.getConfiguration();
+
+ // "" matches everything
+ assertEquals(Arrays.<Configurable>asList(itemBlank, itemBcpEn, itemEn, itemDe),
+ configBlank.findMatchingConfigurables(
+ Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
+
+ // "de" matches only "" and "de"
+ assertEquals(Arrays.<Configurable>asList(itemBlank, itemDe),
+ configDe.findMatchingConfigurables(
+ Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
+
+ // "en" matches "en" and "b+en"
+ assertTrue(configEn.isMatchFor(configBcpEn));
+ assertTrue(configBcpEn.isMatchFor(configEn));
+ assertEquals(Arrays.<Configurable>asList(itemBcpEn, itemEn),
+ configEn.findMatchingConfigurables(
+ Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
+
+ // "b+en" matches "en and "b+en"
+ assertEquals(Arrays.<Configurable>asList(itemBcpEn, itemEn),
+ configBcpEn.findMatchingConfigurables(
+ Arrays.asList(itemBlank, itemBcpEn, itemEn, itemDe)));
+ }
+
+ public void testFromQualifierString() throws Exception {
+ FolderConfiguration blankFolder = FolderConfiguration.getConfigForQualifierString("");
+ FolderConfiguration enFolder = FolderConfiguration.getConfigForQualifierString("en");
+ FolderConfiguration deFolder = FolderConfiguration.getConfigForQualifierString("de");
+ FolderConfiguration deBcp47Folder = FolderConfiguration.getConfigForQualifierString("b+de");
+ FolderConfiguration twoQualifiersFolder =
+ FolderConfiguration.getConfigForQualifierString("de-hdpi");
+
+ assertNotNull(enFolder);
+ assertNotNull(deFolder);
+ assertNotNull(deBcp47Folder);
+ assertFalse(enFolder.isMatchFor(deFolder));
+ assertFalse(deFolder.isMatchFor(enFolder));
+ assertFalse(enFolder.isMatchFor(deBcp47Folder));
+ assertFalse(deBcp47Folder.isMatchFor(enFolder));
+
+ assertTrue(enFolder.isMatchFor(blankFolder));
+ assertTrue(deFolder.isMatchFor(blankFolder));
+ assertTrue(deBcp47Folder.isMatchFor(blankFolder));
+
+ assertEquals("de", twoQualifiersFolder.getLocaleQualifier().getLanguage());
+ assertEquals(Density.HIGH, twoQualifiersFolder.getDensityQualifier().getValue());
+ }
+
+ public void testCopyOf() throws Exception {
+ FolderConfiguration deBcp47Folder = FolderConfiguration.getConfigForFolder("values-b+de");
+ FolderConfiguration copy = FolderConfiguration.copyOf(deBcp47Folder);
+ assertTrue(copy.isMatchFor(deBcp47Folder));
+
+ copy.setLocaleQualifier(new LocaleQualifier("en"));
+ assertEquals("en", copy.getLocaleQualifier().getLanguage());
+ assertEquals("de", deBcp47Folder.getLocaleQualifier().getLanguage());
+
+ copy.setDensityQualifier(new DensityQualifier(Density.HIGH));
+ assertEquals(Density.HIGH, copy.getDensityQualifier().getValue());
+ assertNull(deBcp47Folder.getDensityQualifier());
+
+ FolderConfiguration blankFolder = FolderConfiguration.getConfigForFolder("values");
+ copy = FolderConfiguration.copyOf(blankFolder);
+ assertTrue(copy.isMatchFor(blankFolder));
+
+ copy.setVersionQualifier(new VersionQualifier(21));
+ assertEquals(21, copy.getVersionQualifier().getVersion());
+ assertNull(blankFolder.getVersionQualifier());
+ }
+
+ public void testScreenSizeMatching() {
+ runConfigMatchTest("normal-v21", 2, "", "v21", "normal", "large");
+ runConfigMatchTest("normal-v21", 1, "", "v21", "small", "large");
+ runConfigMatchTest("normal-v21", 0, "", "v23", "small", "large");
+ runConfigMatchTest("large-v21", 3, "", "v21", "small", "large");
+ runConfigMatchTest("large-v21", 1, "", "v21", "small", "xlarge");
+ runConfigMatchTest("small-v21", 2, "", "v21", "small", "xlarge");
+ runConfigMatchTest("small-v21", 1, "", "v21", "normal", "xlarge");
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/KeyboardStateQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/KeyboardStateQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/KeyboardStateQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/KeyboardStateQualifierTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LocaleQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LocaleQualifierTest.java
new file mode 100644
index 0000000..19b1f20
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/LocaleQualifierTest.java
@@ -0,0 +1,343 @@
+/*
+ * 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.ide.common.resources.configuration;
+
+import static com.android.ide.common.resources.configuration.LocaleQualifier.FAKE_VALUE;
+import static com.android.ide.common.resources.configuration.LocaleQualifier.getQualifier;
+import static com.android.ide.common.resources.configuration.LocaleQualifier.isNormalizedCase;
+import static com.android.ide.common.resources.configuration.LocaleQualifier.normalizeCase;
+import static com.android.ide.common.resources.configuration.LocaleQualifier.parseBcp47;
+
+import junit.framework.TestCase;
+
+import java.util.Locale;
+
+public class LocaleQualifierTest extends TestCase {
+
+ private FolderConfiguration config;
+ private LocaleQualifier lq;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ config = new FolderConfiguration();
+ lq = new LocaleQualifier();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ config = null;
+ lq = null;
+ }
+
+ public void testCheckAndSet() {
+ assertEquals(true, lq.checkAndSet("b+kok", config)); //$NON-NLS-1$
+ assertTrue(config.getLocaleQualifier() != null);
+ assertEquals("kok", config.getLocaleQualifier().toString()); //$NON-NLS-1$
+ }
+
+ public void testCheckAndSetCaseInsensitive() {
+ assertEquals(true, lq.checkAndSet("b+KOK", config)); //$NON-NLS-1$
+ assertTrue(config.getLocaleQualifier() != null);
+ assertEquals("kok", config.getLocaleQualifier().toString()); //$NON-NLS-1$
+ }
+
+ public void testFailures() {
+ assertEquals(false, lq.checkAndSet("", config)); //$NON-NLS-1$
+ assertEquals(false, lq.checkAndSet("abcd", config)); //$NON-NLS-1$
+ assertEquals(false, lq.checkAndSet("en-USofA", config)); //$NON-NLS-1$
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetQualifier() {
+ assertNull(getQualifier("v4")); // version qualifier shouldn't match
+
+ assertNull(getQualifier(""));
+ assertEquals("en", getQualifier("en").getLanguage());
+ assertNull(getQualifier("en").getRegion());
+ assertNull(getQualifier("en").getScript());
+ assertEquals("en", getQualifier("EN").getLanguage());
+ assertEquals("en", getQualifier("EN").getFull());
+
+ assertEquals("en", getQualifier("en-rUS").getLanguage());
+ assertEquals("US", getQualifier("en-rUS").getRegion());
+ assertNull(getQualifier("en-rUS").getScript());
+ assertEquals("en", getQualifier("EN-RUS").getLanguage());
+ assertEquals("en", getQualifier("EN-RUS").getLanguage());
+ assertEquals("US", getQualifier("EN-RUS").getRegion());
+ assertNull(getQualifier("EN-RUS").getScript());
+ assertEquals("en-rUS", getQualifier("EN-RUS").getFull());
+
+ assertEquals("eng", getQualifier("eng").getLanguage());
+ assertNull(getQualifier("eng").getRegion());
+ assertNull(getQualifier("eng").getScript());
+ assertEquals("eng", getQualifier("ENG").getLanguage());
+ assertEquals("eng", getQualifier("ENG").getFull());
+
+ assertEquals("foo", getQualifier("foo").getLanguage());
+ assertNull(getQualifier("car")); // car mode: not recognized as language
+
+ assertEquals("eng", getQualifier("eng-rUS").getLanguage());
+ assertEquals("US", getQualifier("eng-rUS").getRegion());
+ assertNull(getQualifier("eng-rUS").getScript());
+ assertEquals("eng", getQualifier("ENG-RUS").getLanguage());
+ assertEquals("eng", getQualifier("ENG-RUS").getLanguage());
+ assertEquals("US", getQualifier("ENG-RUS").getRegion());
+ assertNull(getQualifier("ENG-RUS").getScript());
+ assertEquals("eng-rUS", getQualifier("ENG-RUS").getFull());
+ assertNull(getQualifier("eng-rUSA"));
+
+ assertNull(getQualifier("kok-rIND"));
+ assertEquals("kok", getQualifier("b+kok").getLanguage());
+ assertNull(getQualifier("b+kok").getRegion());
+ assertEquals("kok", getQualifier("b+kok+VARIANT").getLanguage());
+ assertNull(getQualifier("b+kok+VARIANT").getRegion());
+ assertEquals("kok", getQualifier("b+kok+Knda+419+VARIANT").getLanguage());
+ assertEquals("419", getQualifier("b+kok+Knda+419+VARIANT").getRegion());
+ assertEquals("Knda", getQualifier("b+kok+Knda+419+VARIANT").getScript());
+ assertEquals("kok", getQualifier("b+kok+VARIANT").getLanguage());
+ assertNull(getQualifier("b+kok+VARIANT").getRegion());
+ assertEquals("kok", getQualifier("b+kok+IN").getLanguage());
+ assertEquals("IN", getQualifier("b+kok+IN").getRegion());
+ assertEquals("kok", getQualifier("b+kok+Knda").getLanguage());
+ assertNull(getQualifier("b+kok+Knda").getRegion());
+ assertEquals("kok", getQualifier("b+kok+Knda+419").getLanguage());
+ assertEquals("419", getQualifier("b+kok+Knda+419").getRegion());
+ assertEquals("b+kok+Knda+419", getQualifier("b+KOK+knda+419").getFull());
+
+ assertEquals("es-r419", getQualifier("es-r419").getFull());
+ }
+
+ public void testSetRegion() {
+ LocaleQualifier qualifier = getQualifier("en");
+ assertNotNull(qualifier);
+ qualifier.setRegionSegment("rUS");
+ assertEquals("en", qualifier.getLanguage());
+ assertEquals("US", qualifier.getRegion());
+ assertEquals("en-rUS", qualifier.getFull());
+
+ // Case check
+ qualifier = getQualifier("EN");
+ assertNotNull(qualifier);
+ qualifier.setRegionSegment("Rus");
+ assertEquals("en", qualifier.getLanguage());
+ assertEquals("US", qualifier.getRegion());
+ assertEquals("en-rUS", qualifier.getFull());
+
+ // 3 letter language
+ qualifier = getQualifier("eng");
+ assertNotNull(qualifier);
+ qualifier.setRegionSegment("rUS");
+ assertEquals("eng", qualifier.getLanguage());
+ assertEquals("US", qualifier.getRegion());
+ assertEquals("eng-rUS", qualifier.getFull());
+ }
+
+ public void testEquals() {
+ LocaleQualifier qualifier1 = getQualifier("b+KOK+knda+419");
+ LocaleQualifier qualifier2 = getQualifier("b+kok+knda+419");
+ assertNotNull(qualifier1);
+ assertNotNull(qualifier2);
+ assertEquals(qualifier1, qualifier2);
+ assertEquals(qualifier2, qualifier1);
+
+ qualifier2 = getQualifier("b+kok+knda");
+ assertNotNull(qualifier2);
+ assertFalse(qualifier1.equals(qualifier2));
+ assertFalse(qualifier2.equals(qualifier1));
+
+ // Equivalent, with different syntax
+ qualifier1 = getQualifier("b+en+US");
+ qualifier2 = getQualifier("en-rUS");
+ assertNotNull(qualifier1);
+ assertNotNull(qualifier1);
+ assertNotNull(qualifier2);
+ assertEquals(qualifier1, qualifier2);
+ assertEquals(qualifier2, qualifier1);
+
+ qualifier1 = getQualifier("b+eng+US");
+ qualifier2 = getQualifier("eng-rUS");
+ assertNotNull(qualifier1);
+ assertNotNull(qualifier1);
+ assertNotNull(qualifier2);
+ assertEquals(qualifier1, qualifier2);
+ assertEquals(qualifier2, qualifier1);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testParseBcp47() {
+
+ assertNull(parseBcp47("kok-rIN"));
+ assertEquals("kok", parseBcp47("b+kok").getLanguage());
+ assertNull(parseBcp47("b+kok").getRegion());
+
+ assertEquals("kok", parseBcp47("b+kok+VARIANT").getLanguage());
+ assertNull(parseBcp47("b+kok+VARIANT").getRegion());
+
+ assertEquals("kok", parseBcp47("b+kok+Knda+419+VARIANT").getLanguage());
+ assertEquals("419", parseBcp47("b+kok+Knda+419+VARIANT").getRegion());
+ assertEquals("Knda", parseBcp47("b+kok+Knda+419+VARIANT").getScript());
+
+ assertEquals("kok", parseBcp47("b+kok+VARIANT").getLanguage());
+ assertNull(parseBcp47("b+kok+VARIANT").getRegion());
+
+ assertEquals("kok", parseBcp47("b+kok+IN").getLanguage());
+ assertEquals("IN", parseBcp47("b+kok+IN").getRegion());
+
+ assertEquals("kok", parseBcp47("b+kok+Knda").getLanguage());
+ assertEquals("Knda", parseBcp47("b+kok+Knda").getScript());
+ assertNull(parseBcp47("b+kok+Knda").getRegion());
+
+ assertEquals("kok", parseBcp47("b+kok+Knda+419").getLanguage());
+ assertEquals("419", parseBcp47("b+kok+Knda+419").getRegion());
+ assertEquals("Knda", parseBcp47("b+kok+Knda+419").getScript());
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetLanguageAndGetRegion() {
+ assertEquals(true, lq.checkAndSet("b+kok", config)); //$NON-NLS-1$
+ assertEquals("kok", config.getLocaleQualifier().getValue());
+ assertEquals("kok", config.getLocaleQualifier().getLanguage());
+ assertEquals("kok", config.getLocaleQualifier().getLanguage());
+ assertNull("kok", config.getLocaleQualifier().getRegion());
+
+ assertEquals(true, lq.checkAndSet("b+kok+VARIANT", config)); //$NON-NLS-1$
+ assertEquals("b+kok+variant", config.getLocaleQualifier().getValue());
+ assertEquals("kok", config.getLocaleQualifier().getLanguage());
+ assertNull("kok", config.getLocaleQualifier().getRegion());
+
+ assertEquals(true, lq.checkAndSet("b+kok+Knda+419+VARIANT", config)); //$NON-NLS-1$
+ assertEquals("b+kok+Knda+419+variant", config.getLocaleQualifier().getValue());
+ assertEquals("kok", config.getLocaleQualifier().getLanguage());
+ assertEquals("419", config.getLocaleQualifier().getRegion());
+
+ assertEquals(true, lq.checkAndSet("b+kok+IN", config)); //$NON-NLS-1$
+ assertEquals("kok-rIN", config.getLocaleQualifier().getValue());
+ assertEquals("kok", config.getLocaleQualifier().getLanguage());
+ assertEquals("IN", config.getLocaleQualifier().getRegion());
+
+ assertEquals(true, lq.checkAndSet("b+kok+Knda", config)); //$NON-NLS-1$
+ assertEquals("b+kok+Knda", config.getLocaleQualifier().getValue());
+ assertEquals("kok", config.getLocaleQualifier().getLanguage());
+ assertNull(config.getLocaleQualifier().getRegion());
+
+ assertEquals(true, lq.checkAndSet("b+kok+Knda+419", config)); //$NON-NLS-1$
+ assertEquals("b+kok+Knda+419", config.getLocaleQualifier().getValue());
+ assertEquals("kok", config.getLocaleQualifier().getLanguage());
+ assertEquals("419", config.getLocaleQualifier().getRegion());
+ }
+
+ public void testIsNormalCase() {
+ // Language
+ assertFalse(isNormalizedCase("LL"));
+ assertFalse(isNormalizedCase("Ll"));
+ assertFalse(isNormalizedCase("lL"));
+ assertFalse(isNormalizedCase("LLL"));
+ assertTrue(isNormalizedCase("ll"));
+ assertTrue(isNormalizedCase("lll"));
+
+ // Language + Region
+ assertFalse(isNormalizedCase("LL-rRR"));
+ assertFalse(isNormalizedCase("ll-rrr"));
+ assertFalse(isNormalizedCase("LL-rrr"));
+ assertFalse(isNormalizedCase("ll-RRR"));
+ assertFalse(isNormalizedCase("lL-frR"));
+ assertFalse(isNormalizedCase("Ll-fRr"));
+ assertFalse(isNormalizedCase("llL-frr"));
+ assertTrue(isNormalizedCase("ll-rRR"));
+ assertTrue(isNormalizedCase("lll-rRR"));
+
+ // BCP 47
+ assertFalse(isNormalizedCase("b+en+CA+x+ca".toLowerCase(Locale.US)));
+ assertTrue(isNormalizedCase("b+en+CA+x+ca"));
+ assertFalse(isNormalizedCase("b+sgn+BE+FR".toLowerCase(Locale.US)));
+ assertTrue(isNormalizedCase("b+sgn+BE+FR"));
+ assertFalse(isNormalizedCase("b+az+Latn+x+latn".toLowerCase(Locale.US)));
+ assertTrue(isNormalizedCase("b+az+Latn+x+latn"));
+ assertFalse(isNormalizedCase("b+MN+cYRL+mn".toLowerCase(Locale.US)));
+ assertTrue(isNormalizedCase("b+mn+Cyrl+MN"));
+ assertFalse(isNormalizedCase("b+zh+CN+a+myext+x+private".toLowerCase(Locale.US)));
+ assertTrue(isNormalizedCase("b+zh+CN+a+myext+x+private"));
+ }
+
+ public void testNormalizeCase() {
+ assertEquals("bb", normalizeCase("BB"));
+ assertEquals("ll-rRR", normalizeCase("LL-Rrr"));
+ assertEquals("lll-rRR", normalizeCase("LLL-Rrr"));
+
+ assertEquals("b+en+CA+x+ca", normalizeCase("b+en+CA+x+ca".toLowerCase(Locale.US)));
+ assertEquals("b+sgn+BE+FR", normalizeCase("b+sgn+BE+FR".toLowerCase(Locale.US)));
+ assertEquals("b+az+Latn+x+latn", normalizeCase("b+az+Latn+x+latn".toLowerCase(Locale.US)));
+ assertEquals("b+mn+Cyrl+MN", normalizeCase("b+MN+cYRL+mn".toLowerCase(Locale.US)));
+ assertEquals("b+zh+CN+a+myext+x+private", normalizeCase(
+ "b+zh+CN+a+myext+x+private".toLowerCase(Locale.US)));
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testIsMatchFor() {
+ assertTrue(getQualifier("en").isMatchFor(getQualifier("en")));
+ assertFalse(getQualifier("en").isMatchFor(getQualifier("fr")));
+ assertFalse(getQualifier("fr").isMatchFor(getQualifier("en")));
+
+ assertTrue(getQualifier("en-rUS").isMatchFor(getQualifier("en-rUS")));
+ assertFalse(getQualifier("en-rUS").isMatchFor(getQualifier("en-rGB")));
+ assertFalse(getQualifier("en-rGB").isMatchFor(getQualifier("en-rUS")));
+ assertFalse(getQualifier("fr-rGB").isMatchFor(getQualifier("en-rGB")));
+
+ assertTrue(getQualifier("en-rUS").isMatchFor(getQualifier("en-rUS")));
+ assertTrue(getQualifier("en-rUS").isMatchFor(getQualifier("en")));
+ assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("b+en+US")));
+ assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("b+en")));
+ assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("en")));
+
+ assertTrue(getQualifier("b+en+US").isMatchFor(getQualifier("b+en+US")));
+ assertTrue(getQualifier("b+en+Knda+US").isMatchFor(getQualifier("b+en+Knda+US")));
+ assertTrue(getQualifier("b+en+Knda+US").isMatchFor(getQualifier("b+en")));
+
+ // Apparently isMatchFor is a bit more general than you would think; it
+ // can't restrict as shown in these two conditions because then other
+ // configuration matching code will fail
+ //assertFalse(getQualifier("en").isMatchFor(getQualifier("en-rUS")));
+ //assertFalse(getQualifier("b+en").isMatchFor(getQualifier("b+en+Knda+US")));
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ public void testGetTag() {
+ assertEquals("en-CA", getQualifier("b+en+CA".toLowerCase(Locale.US)).getTag());
+ assertEquals("eng-CA", getQualifier("b+eng+CA".toLowerCase(Locale.US)).getTag());
+ assertEquals("en-CA-x-ca", getQualifier("b+en+CA+x+ca".toLowerCase(Locale.US)).getTag());
+ assertEquals("en", getQualifier("EN").getTag());
+ assertEquals("en-US", getQualifier("EN-rUS").getTag());
+ }
+
+ public void testHasLanguage() {
+ //noinspection ConstantConditions
+ assertTrue(LocaleQualifier.getQualifier("b+en+CA+x+ca").hasLanguage());
+ assertTrue(new LocaleQualifier("en").hasLanguage());
+ assertFalse(new LocaleQualifier(FAKE_VALUE).hasLanguage());
+ }
+
+ public void testHasRegion() {
+ //noinspection ConstantConditions
+ assertTrue(LocaleQualifier.getQualifier("b+en+CA+x+ca").hasRegion());
+ //noinspection ConstantConditions
+ assertFalse(LocaleQualifier.getQualifier("b+en").hasRegion());
+ assertFalse(new LocaleQualifier(FAKE_VALUE).hasRegion());
+ assertFalse(new LocaleQualifier("", FAKE_VALUE, FAKE_VALUE, null).hasRegion());
+ }
+}
\ No newline at end of file
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NavigationMethodQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NavigationMethodQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NavigationMethodQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/NavigationMethodQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenDimensionQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenOrientationQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenRoundQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenRoundQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenRoundQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenRoundQualifierTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java
new file mode 100644
index 0000000..f9b7275
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2010 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.ide.common.resources.configuration;
+
+import com.android.resources.ScreenSize;
+
+import junit.framework.TestCase;
+
+public class ScreenSizeQualifierTest extends TestCase {
+
+ private ScreenSizeQualifier ssq;
+ private FolderConfiguration config;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ssq = new ScreenSizeQualifier();
+ config = new FolderConfiguration();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ ssq = null;
+ config = null;
+ }
+
+ public void testSmall() {
+ assertEquals(true, ssq.checkAndSet("small", config)); //$NON-NLS-1$
+ assertTrue(config.getScreenSizeQualifier() != null);
+ assertEquals(ScreenSize.SMALL, config.getScreenSizeQualifier().getValue());
+ assertEquals("small", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
+ }
+
+ public void testNormal() {
+ assertEquals(true, ssq.checkAndSet("normal", config)); //$NON-NLS-1$
+ assertTrue(config.getScreenSizeQualifier() != null);
+ assertEquals(ScreenSize.NORMAL, config.getScreenSizeQualifier().getValue());
+ assertEquals("normal", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
+ }
+
+ public void testLarge() {
+ assertEquals(true, ssq.checkAndSet("large", config)); //$NON-NLS-1$
+ assertTrue(config.getScreenSizeQualifier() != null);
+ assertEquals(ScreenSize.LARGE, config.getScreenSizeQualifier().getValue());
+ assertEquals("large", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
+ }
+
+ public void testXLarge() {
+ assertEquals(true, ssq.checkAndSet("xlarge", config)); //$NON-NLS-1$
+ assertTrue(config.getScreenSizeQualifier() != null);
+ assertEquals(ScreenSize.XLARGE, config.getScreenSizeQualifier().getValue());
+ assertEquals("xlarge", config.getScreenSizeQualifier().toString()); //$NON-NLS-1$
+ }
+
+ public void testIsMatchFor() {
+ // create qualifiers for small, normal, large and xlarge sizes.
+ ScreenSizeQualifier smallQ = new ScreenSizeQualifier(ScreenSize.SMALL);
+ ScreenSizeQualifier normalQ = new ScreenSizeQualifier(ScreenSize.NORMAL);
+ ScreenSizeQualifier largeQ = new ScreenSizeQualifier(ScreenSize.LARGE);
+ ScreenSizeQualifier xlargeQ = new ScreenSizeQualifier(ScreenSize.XLARGE);
+
+ // test that every qualifier is a match for itself.
+ assertTrue(smallQ.isMatchFor(smallQ));
+ assertTrue(normalQ.isMatchFor(normalQ));
+ assertTrue(largeQ.isMatchFor(largeQ));
+ assertTrue(xlargeQ.isMatchFor(xlargeQ));
+
+ // test that small screen sizes match the larger ones.
+ assertTrue(smallQ.isMatchFor(smallQ));
+ assertTrue(smallQ.isMatchFor(normalQ));
+ assertTrue(smallQ.isMatchFor(largeQ));
+ assertTrue(smallQ.isMatchFor(xlargeQ));
+ assertTrue(normalQ.isMatchFor(normalQ));
+ assertTrue(normalQ.isMatchFor(largeQ));
+ assertTrue(normalQ.isMatchFor(xlargeQ));
+ assertTrue(largeQ.isMatchFor(largeQ));
+ assertTrue(largeQ.isMatchFor(xlargeQ));
+ assertTrue(xlargeQ.isMatchFor(xlargeQ));
+
+ // test that larger screen sizes don't match the smaller ones.
+ assertFalse(normalQ.isMatchFor(smallQ));
+ assertFalse(largeQ.isMatchFor(smallQ));
+ assertFalse(largeQ.isMatchFor(normalQ));
+ assertFalse(xlargeQ.isMatchFor(smallQ));
+ assertFalse(xlargeQ.isMatchFor(normalQ));
+ assertFalse(xlargeQ.isMatchFor(largeQ));
+ }
+
+ public void testIsBetterMatchThan() {
+ // create qualifiers for small, normal, large and xlarge sizes.
+ ScreenSizeQualifier smallQ = new ScreenSizeQualifier(ScreenSize.SMALL);
+ ScreenSizeQualifier normalQ = new ScreenSizeQualifier(ScreenSize.NORMAL);
+ ScreenSizeQualifier largeQ = new ScreenSizeQualifier(ScreenSize.LARGE);
+ ScreenSizeQualifier xlargeQ = new ScreenSizeQualifier(ScreenSize.XLARGE);
+
+ // test that each Q is a better match than all other valid Qs when the ref is the same Q.
+ assertTrue(normalQ.isBetterMatchThan(smallQ, normalQ));
+
+ assertTrue(largeQ.isBetterMatchThan(smallQ, largeQ));
+ assertTrue(largeQ.isBetterMatchThan(normalQ, largeQ));
+
+ assertTrue(xlargeQ.isBetterMatchThan(smallQ, xlargeQ));
+ assertTrue(xlargeQ.isBetterMatchThan(normalQ, xlargeQ));
+ assertTrue(xlargeQ.isBetterMatchThan(largeQ, xlargeQ));
+
+ // test that higher screen size if preferable if there's no exact match.
+ assertTrue(normalQ.isBetterMatchThan(smallQ, largeQ));
+ assertFalse(smallQ.isBetterMatchThan(normalQ, largeQ));
+
+ assertTrue(normalQ.isBetterMatchThan(smallQ, xlargeQ));
+ assertTrue(largeQ.isBetterMatchThan(smallQ, xlargeQ));
+ assertTrue(largeQ.isBetterMatchThan(normalQ, xlargeQ));
+
+ assertFalse(smallQ.isBetterMatchThan(normalQ, xlargeQ));
+ assertFalse(smallQ.isBetterMatchThan(largeQ, xlargeQ));
+ assertFalse(normalQ.isBetterMatchThan(largeQ, xlargeQ));
+
+ // test that null (i.e. no qualifier) is a better match than small for screens normal or above
+ assertTrue(smallQ.isBetterMatchThan(null, smallQ));
+ assertFalse(smallQ.isBetterMatchThan(null, normalQ));
+ assertFalse(smallQ.isBetterMatchThan(null, xlargeQ));
+
+ assertTrue(normalQ.isBetterMatchThan(null, normalQ));
+ assertTrue(normalQ.isBetterMatchThan(null, xlargeQ));
+ assertTrue(largeQ.isBetterMatchThan(null, xlargeQ));
+ }
+
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/TextInputMethodQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/TextInputMethodQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/TextInputMethodQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/TextInputMethodQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/TouchScreenQualifierTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/TouchScreenQualifierTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/resources/configuration/TouchScreenQualifierTest.java
rename to sdk-common/src/test/java/com/android/ide/common/resources/configuration/TouchScreenQualifierTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/signing/KeyStoreHelperTest.java b/sdk-common/src/test/java/com/android/ide/common/signing/KeyStoreHelperTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/signing/KeyStoreHelperTest.java
rename to sdk-common/src/test/java/com/android/ide/common/signing/KeyStoreHelperTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/util/GeneratorTest.java b/sdk-common/src/test/java/com/android/ide/common/util/GeneratorTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/util/GeneratorTest.java
rename to sdk-common/src/test/java/com/android/ide/common/util/GeneratorTest.java
diff --git a/sdk-common/src/test/java/com/android/ide/common/vectordrawable/VectorDrawableGeneratorTest.java b/sdk-common/src/test/java/com/android/ide/common/vectordrawable/VectorDrawableGeneratorTest.java
new file mode 100644
index 0000000..020e860
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/vectordrawable/VectorDrawableGeneratorTest.java
@@ -0,0 +1,494 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.SdkConstants;
+import com.android.ide.common.util.GeneratorTest;
+import com.android.testutils.TestUtils;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import junit.framework.TestCase;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
+
+ at SuppressWarnings("javadoc")
+public class VectorDrawableGeneratorTest extends GeneratorTest {
+ private static final String TEST_DATA_REL_PATH =
+ "tools/base/sdk-common/src/test/resources/testData/vectordrawable";
+
+ private static final int IMAGE_SIZE = 64;
+
+ @Override
+ protected String getTestDataRelPath() {
+ return TEST_DATA_REL_PATH;
+ }
+
+ private enum FileType {
+ SVG,
+ XML;
+ };
+
+ private void checkVectorConversion(String testFileName, FileType type,
+ boolean dumpXml, String expectedError) throws IOException {
+ String incomingFileName;
+ if (type == FileType.SVG) {
+ incomingFileName = testFileName + ".svg";
+ } else {
+ incomingFileName = testFileName + ".xml";
+ }
+ String imageName = testFileName + ".png";
+
+ String parentDir = "vectordrawable" + File.separator;
+ File parentDirFile = TestUtils.getRoot("vectordrawable");
+
+ File incomingFile = new File(parentDirFile, incomingFileName);
+ String xmlContent = null;
+ if (type == FileType.SVG) {
+ try {
+ OutputStream outStream = new ByteArrayOutputStream();
+ String errorLog = Svg2Vector.parseSvgToXml(incomingFile, outStream);
+ if (expectedError != null) {
+ TestCase.assertNotNull(errorLog);
+ TestCase.assertFalse(errorLog.isEmpty());
+ TestCase.assertTrue(errorLog.contains(expectedError));
+ }
+ xmlContent = outStream.toString();
+ if (xmlContent == null || xmlContent.isEmpty()) {
+ TestCase.fail("Empty Xml file.");
+ }
+ if (dumpXml) {
+ File tempXmlFile = new File(parentDirFile, imageName + ".xml");
+ PrintWriter writer = new PrintWriter(tempXmlFile);
+ writer.println(xmlContent);
+ writer.close();
+ }
+ } catch (Exception e) {
+ TestCase.fail("Failure: Exception in Svg2Vector.parseSvgToXml!" + e.getMessage());
+ }
+ } else {
+ xmlContent = Files.toString(incomingFile, Charsets.UTF_8);
+ }
+
+ final VdPreview.TargetSize imageTargetSize = VdPreview.TargetSize.createSizeFromWidth(IMAGE_SIZE);
+ StringBuilder builder = new StringBuilder();
+ BufferedImage image = VdPreview.getPreviewFromVectorXml(imageTargetSize, xmlContent,
+ builder);
+
+ String imageNameWithParent = parentDir + imageName;
+ File pngFile = new File(parentDirFile, imageName);
+ if (!pngFile.exists()) {
+ // Generate golden images here.
+ generateGoldenImage(getTargetDir(), image, imageNameWithParent, imageName);
+ } else {
+ InputStream is = new FileInputStream(pngFile);
+ BufferedImage goldenImage = ImageIO.read(is);
+ // Mostly, this threshold is for JDK versions. The golden image is generated
+ // on Linux with JDK 6, the expected delta is 0 on the same platform and JDK version.
+ float diffThreshold = 1.5f;
+ if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
+ diffThreshold = 5.0f;
+ }
+ assertImageSimilar(imageNameWithParent, goldenImage, image, diffThreshold);
+ }
+
+ }
+
+ private void checkSvgConversion(String fileName) throws IOException {
+ checkVectorConversion(fileName, FileType.SVG, false, null);
+ }
+
+ private void checkXmlConversion(String filename) throws IOException {
+ checkVectorConversion(filename, FileType.XML, false, null);
+ }
+
+ private void checkSvgConversionAndContainsError(String filename, String errorLog) throws IOException {
+ checkVectorConversion(filename, FileType.SVG, false, errorLog);
+ }
+
+ private void checkSvgConversionDebug(String fileName) throws IOException {
+ checkVectorConversion(fileName, FileType.SVG, true, null);
+ }
+
+ //////////////////////////////////////////////////////////
+ // Tests starts here:
+ public void testSvgArcto1() throws Exception {
+ checkSvgConversion("test_arcto_1");
+ }
+
+ public void testSvgControlPoints01() throws Exception {
+ checkSvgConversion("test_control_points_01");
+ }
+
+ public void testSvgControlPoints02() throws Exception {
+ checkSvgConversion("test_control_points_02");
+ }
+
+ public void testSvgControlPoints03() throws Exception {
+ checkSvgConversion("test_control_points_03");
+ }
+
+ public void testSvgContentCut() throws Exception {
+ checkSvgConversion("ic_content_cut_24px");
+ }
+
+ public void testSvgInput() throws Exception {
+ checkSvgConversion("ic_input_24px");
+ }
+
+ public void testSvgLiveHelp() throws Exception {
+ checkSvgConversion("ic_live_help_24px");
+ }
+
+ public void testSvgLocalLibrary() throws Exception {
+ checkSvgConversion("ic_local_library_24px");
+ }
+
+ public void testSvgLocalPhone() throws Exception {
+ checkSvgConversion("ic_local_phone_24px");
+ }
+
+ public void testSvgMicOff() throws Exception {
+ checkSvgConversion("ic_mic_off_24px");
+ }
+
+ public void testSvgShapes() throws Exception {
+ checkSvgConversion("ic_shapes");
+ }
+
+ public void testSvgEllipse() throws Exception {
+ checkSvgConversion("test_ellipse");
+ }
+
+ public void testSvgTempHigh() throws Exception {
+ checkSvgConversion("ic_temp_high");
+ }
+
+ public void testSvgPlusSign() throws Exception {
+ checkSvgConversion("ic_plus_sign");
+ }
+
+ public void testSvgPolylineStrokeWidth() throws Exception {
+ checkSvgConversion("ic_polyline_strokewidth");
+ }
+
+ // Preview broken on linux, fine on chrome browser
+ public void testSvgStrokeWidthTransform() throws Exception {
+ checkSvgConversionAndContainsError("ic_strokewidth_transform",
+ "We don't scale the stroke width!");
+ }
+
+ public void testSvgEmptyAttributes() throws Exception {
+ checkSvgConversion("ic_empty_attributes");
+ }
+
+ public void testSvgSimpleGroupInfo() throws Exception {
+ checkSvgConversion("ic_simple_group_info");
+ }
+
+ public void testSvgContainsError() throws Exception {
+ checkSvgConversionAndContainsError("ic_contains_ignorable_error",
+ "ERROR@ line 16 <switch> is not supported\n"
+ + "ERROR@ line 17 <foreignObject> is not supported");
+ }
+
+ public void testSvgLineToMoveTo() throws Exception {
+ checkSvgConversion("test_lineto_moveto");
+ }
+
+ public void testSvgLineToMoveTo2() throws Exception {
+ checkSvgConversion("test_lineto_moveto2");
+ }
+
+ public void testSvgLineToMoveToViewbox1() throws Exception {
+ checkSvgConversion("test_lineto_moveto_viewbox1");
+ }
+
+ public void testSvgLineToMoveToViewbox2() throws Exception {
+ checkSvgConversion("test_lineto_moveto_viewbox2");
+ }
+
+ public void testSvgLineToMoveToViewbox3() throws Exception {
+ checkSvgConversion("test_lineto_moveto_viewbox3");
+ }
+
+ // It seems like different implementations has different results on this svg.
+ public void testSvgLineToMoveToViewbox4() throws Exception {
+ checkSvgConversion("test_lineto_moveto_viewbox4");
+ }
+
+ public void testSvgLineToMoveToViewbox5() throws Exception {
+ checkSvgConversion("test_lineto_moveto_viewbox5");
+ }
+
+ public void testSvgColorFormats() throws Exception {
+ checkSvgConversion("test_color_formats");
+ }
+
+ public void testSvgTransformArcComplex1() throws Exception {
+ checkSvgConversion("test_transform_arc_complex1");
+ }
+
+ public void testSvgTransformArcComplex2() throws Exception {
+ checkSvgConversion("test_transform_arc_complex2");
+ }
+
+ public void testSvgTransformArcRotateScaleTranslate() throws Exception {
+ checkSvgConversion("test_transform_arc_rotate_scale_translate");
+ }
+
+ public void testSvgTransformArcScale() throws Exception {
+ checkSvgConversion("test_transform_arc_scale");
+ }
+
+ public void testSvgTransformArcScaleRotate() throws Exception {
+ checkSvgConversion("test_transform_arc_scale_rotate");
+ }
+
+ public void testSvgTransformArcSkewx() throws Exception {
+ checkSvgConversion("test_transform_arc_skewx");
+ }
+
+ public void testSvgTransformArcSkewy() throws Exception {
+ checkSvgConversion("test_transform_arc_skewy");
+ }
+
+ public void testSvgTransformBigArcComplex() throws Exception {
+ checkSvgConversion("test_transform_big_arc_complex");
+ }
+
+ public void testSvgTransformBigArcComplexViewbox() throws Exception {
+ checkSvgConversion("test_transform_big_arc_complex_viewbox");
+ }
+
+ public void testSvgTransformBigArcScale() throws Exception {
+ checkSvgConversion("test_transform_big_arc_translate_scale");
+ }
+
+ public void testSvgTransformCircleRotate() throws Exception {
+ checkSvgConversion("test_transform_circle_rotate");
+ }
+
+ public void testSvgTransformCircleScale() throws Exception {
+ checkSvgConversion("test_transform_circle_scale");
+ }
+
+ public void testSvgTransformRectMatrix() throws Exception {
+ checkSvgConversion("test_transform_rect_matrix");
+ }
+
+ public void testSvgTransformRoundRectMatrix() throws Exception {
+ checkSvgConversion("test_transform_round_rect_matrix");
+ }
+
+ public void testSvgTransformRectRotate() throws Exception {
+ checkSvgConversion("test_transform_rect_rotate");
+ }
+
+ public void testSvgTransformRectScale() throws Exception {
+ checkSvgConversion("test_transform_rect_scale");
+ }
+
+ public void testSvgTransformRectSkewx() throws Exception {
+ checkSvgConversion("test_transform_rect_skewx");
+ }
+
+ public void testSvgTransformRectSkewy() throws Exception {
+ checkSvgConversion("test_transform_rect_skewy");
+ }
+
+ public void testSvgTransformRectTranslate() throws Exception {
+ checkSvgConversion("test_transform_rect_translate");
+ }
+
+ public void testSvgTransformHVLoopBasic() throws Exception {
+ checkSvgConversion("test_transform_h_v_loop_basic");
+ }
+
+ public void testSvgTransformHVLoopTranslate() throws Exception {
+ checkSvgConversion("test_transform_h_v_loop_translate");
+ }
+
+ public void testSvgTransformHVLoopMatrix() throws Exception {
+ checkSvgConversion("test_transform_h_v_loop_matrix");
+ }
+
+ public void testSvgTransformHVACComplex() throws Exception {
+ checkSvgConversion("test_transform_h_v_a_c_complex");
+ }
+
+ public void testSvgTransformHVAComplex() throws Exception {
+ checkSvgConversion("test_transform_h_v_a_complex");
+ }
+
+ public void testSvgTransformHVCQ() throws Exception {
+ checkSvgConversion("test_transform_h_v_c_q");
+ }
+
+ public void testSvgTransformHVCQComplex() throws Exception {
+ checkSvgConversion("test_transform_h_v_c_q_complex");
+ }
+
+ // Preview broken on linux, fine on chrome browser
+ public void testSvgTransformHVLoopComplex() throws Exception {
+ checkSvgConversion("test_transform_h_v_loop_complex");
+ }
+
+ public void testSvgTransformHVSTComplex() throws Exception {
+ checkSvgConversion("test_transform_h_v_s_t_complex");
+ }
+
+ public void testSvgTransformHVSTComplex2() throws Exception {
+ checkSvgConversion("test_transform_h_v_s_t_complex2");
+ }
+
+ public void testSvgTransformCQNoMove() throws Exception {
+ checkSvgConversion("test_transform_c_q_no_move");
+ }
+ // Preview broken on linux, fine on chrome browser
+ public void testSvgTransformMultiple1() throws Exception {
+ checkSvgConversion("test_transform_multiple_1");
+ }
+
+ // Preview broken on linux, fine on chrome browser
+ public void testSvgTransformMultiple2() throws Exception {
+ checkSvgConversion("test_transform_multiple_2");
+ }
+
+ // Preview broken on linux, fine on chrome browser
+ public void testSvgTransformMultiple3() throws Exception {
+ checkSvgConversion("test_transform_multiple_3");
+ }
+
+ public void testSvgTransformMultiple4() throws Exception {
+ checkSvgConversion("test_transform_multiple_4");
+ }
+
+ public void testSvgTransformGroup1() throws Exception {
+ checkSvgConversion("test_transform_group_1");
+ }
+
+ public void testSvgTransformGroup2() throws Exception {
+ checkSvgConversion("test_transform_group_2");
+ }
+
+ public void testSvgTransformGroup3() throws Exception {
+ checkSvgConversion("test_transform_group_3");
+ }
+
+ public void testSvgTransformGroup4() throws Exception {
+ checkSvgConversion("test_transform_group_4");
+ }
+
+ public void testSvgTransformEllipseRotateScaleTranslate() throws Exception {
+ checkSvgConversion("test_transform_ellipse_rotate_scale_translate");
+ }
+
+ public void testSvgTransformEllipseComplex() throws Exception {
+ checkSvgConversion("test_transform_ellipse_complex");
+ }
+
+ public void testSvgMoveAfterCloseTransform() throws Exception {
+ checkSvgConversion("test_move_after_close");
+ }
+
+ public void testSvgMoveAfterClose() throws Exception {
+ checkSvgConversion("test_move_after_close_transform");
+ }
+
+ // XML files start here.
+ public void testXmlIconSizeOpacity() throws Exception {
+ checkXmlConversion("ic_size_opacity");
+ }
+
+ public void testXmlColorFormats() throws Exception {
+ checkXmlConversion("test_xml_color_formats");
+ }
+
+ public void testXmlColorAlpha() throws Exception {
+ checkXmlConversion("test_fill_stroke_alpha");
+ }
+
+ public void testXmlTransformation1() throws Exception {
+ checkXmlConversion("test_xml_transformation_1");
+ }
+
+ public void testXmlTransformation2() throws Exception {
+ checkXmlConversion("test_xml_transformation_2");
+ }
+
+ public void testXmlTransformation3() throws Exception {
+ checkXmlConversion("test_xml_transformation_3");
+ }
+
+ public void testXmlTransformation4() throws Exception {
+ checkXmlConversion("test_xml_transformation_4");
+ }
+
+ public void testXmlTransformation5() throws Exception {
+ checkXmlConversion("test_xml_transformation_5");
+ }
+
+ public void testXmlTransformation6() throws Exception {
+ checkXmlConversion("test_xml_transformation_6");
+ }
+
+ public void testXmlScaleStroke1() throws Exception {
+ checkXmlConversion("test_xml_scale_stroke_1");
+ }
+
+ public void testXmlScaleStroke2() throws Exception {
+ checkXmlConversion("test_xml_scale_stroke_2");
+ }
+
+ public void testXmlRenderOrder1() throws Exception {
+ checkXmlConversion("test_xml_render_order_1");
+ }
+
+ public void testXmlRenderOrder2() throws Exception {
+ checkXmlConversion("test_xml_render_order_2");
+ }
+
+ public void testXmlRepeatedA1() throws Exception {
+ checkXmlConversion("test_xml_repeated_a_1");
+ }
+
+ public void testXmlRepeatedA2() throws Exception {
+ checkXmlConversion("test_xml_repeated_a_2");
+ }
+
+ public void testXmlRepeatedCQ() throws Exception {
+ checkXmlConversion("test_xml_repeated_cq");
+ }
+
+ public void testXmlRepeatedST() throws Exception {
+ checkXmlConversion("test_xml_repeated_st");
+ }
+
+ public void testXmlStroke1() throws Exception {
+ checkXmlConversion("test_xml_stroke_1");
+ }
+
+ public void testXmlStroke2() throws Exception {
+ checkXmlConversion("test_xml_stroke_2");
+ }
+
+ public void testXmlStroke3() throws Exception {
+ checkXmlConversion("test_xml_stroke_3");
+ }
+}
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/xml/AndroidManifestParserTest.java b/sdk-common/src/test/java/com/android/ide/common/xml/AndroidManifestParserTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/xml/AndroidManifestParserTest.java
rename to sdk-common/src/test/java/com/android/ide/common/xml/AndroidManifestParserTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/xml/SupportsScreensTest.java b/sdk-common/src/test/java/com/android/ide/common/xml/SupportsScreensTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/xml/SupportsScreensTest.java
rename to sdk-common/src/test/java/com/android/ide/common/xml/SupportsScreensTest.java
diff --git a/base/sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java b/sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java
similarity index 100%
rename from base/sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java
rename to sdk-common/src/test/java/com/android/ide/common/xml/XmlPrettyPrinterTest.java
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseMerge/merger.xml b/sdk-common/src/test/resources/testData/assets/baseMerge/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseMerge/merger.xml
rename to sdk-common/src/test/resources/testData/assets/baseMerge/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseMerge/overlay/foo/icon.png b/sdk-common/src/test/resources/testData/assets/baseMerge/overlay/foo/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseMerge/overlay/foo/icon.png
rename to sdk-common/src/test/resources/testData/assets/baseMerge/overlay/foo/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseMerge/overlay/icon2.png b/sdk-common/src/test/resources/testData/assets/baseMerge/overlay/icon2.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseMerge/overlay/icon2.png
rename to sdk-common/src/test/resources/testData/assets/baseMerge/overlay/icon2.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseSet/CVS/foo.txt b/sdk-common/src/test/resources/testData/assets/baseSet/CVS/foo.txt
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseSet/CVS/foo.txt
rename to sdk-common/src/test/resources/testData/assets/baseSet/CVS/foo.txt
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseSet/foo/foo.dat b/sdk-common/src/test/resources/testData/assets/baseSet/foo/foo.dat
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseSet/foo/foo.dat
rename to sdk-common/src/test/resources/testData/assets/baseSet/foo/foo.dat
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseSet/foo/icon.png b/sdk-common/src/test/resources/testData/assets/baseSet/foo/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseSet/foo/icon.png
rename to sdk-common/src/test/resources/testData/assets/baseSet/foo/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseSet/main.xml b/sdk-common/src/test/resources/testData/assets/baseSet/main.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseSet/main.xml
rename to sdk-common/src/test/resources/testData/assets/baseSet/main.xml
diff --git a/base/sdk-common/src/test/resources/testData/assets/baseSet/values.xml b/sdk-common/src/test/resources/testData/assets/baseSet/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/baseSet/values.xml
rename to sdk-common/src/test/resources/testData/assets/baseSet/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable-ldpi/icon.png b/sdk-common/src/test/resources/testData/assets/dupSet/assets1/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable-ldpi/icon.png
rename to sdk-common/src/test/resources/testData/assets/dupSet/assets1/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/drawable/icon.png b/sdk-common/src/test/resources/testData/assets/dupSet/assets2/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/drawable/icon.png
rename to sdk-common/src/test/resources/testData/assets/dupSet/assets2/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/foo/overlay_added.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/foo/overlay_added.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/foo/overlay_added.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/foo/overlay_added.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/overlay_removed.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/overlay_removed.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/overlay_removed.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/overlay_removed.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/removed.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/removed.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/removed.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/removed.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/touched.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/touched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/touched.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/touched.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/untouched.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/untouched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/untouched.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/assetOut/untouched.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/added.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/added.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/added.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/added.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/foo/overlay_added.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/foo/overlay_added.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/foo/overlay_added.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/foo/overlay_added.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/overlay_removed.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/overlay_removed.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/overlay_removed.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/overlay_removed.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/touched.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/touched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/touched.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/touched.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/untouched.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/untouched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/untouched.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/main/untouched.png
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/overlay/foo/overlay_added.png b/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/overlay/foo/overlay_added.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/overlay/foo/overlay_added.png
rename to sdk-common/src/test/resources/testData/assets/incMergeData/basicFiles/overlay/foo/overlay_added.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml b/sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml
rename to sdk-common/src/test/resources/testData/resources/baseMerge/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/dupSet/res1/drawable/icon.png b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable-ldpi/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/dupSet/res1/drawable/icon.png
rename to sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable-ldpi/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable/icon2.png b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable/icon2.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable/icon2.png
rename to sdk-common/src/test/resources/testData/resources/baseMerge/overlay/drawable/icon2.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/alias_replaced_by_file.xml b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/alias_replaced_by_file.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/alias_replaced_by_file.xml
rename to sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/alias_replaced_by_file.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/main.xml b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/main.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/main.xml
rename to sdk-common/src/test/resources/testData/resources/baseMerge/overlay/layout/main.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values-sw600dp-v13/values.xml b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values-sw600dp-v13/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values-sw600dp-v13/values.xml
rename to sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values-sw600dp-v13/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values/values.xml b/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/baseMerge/overlay/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/CVS/foo.txt b/sdk-common/src/test/resources/testData/resources/baseSet/CVS/foo.txt
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/CVS/foo.txt
rename to sdk-common/src/test/resources/testData/resources/baseSet/CVS/foo.txt
diff --git a/base/sdk-common/src/test/resources/testData/resources/dupSet/res2/drawable/icon.png b/sdk-common/src/test/resources/testData/resources/baseSet/drawable/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/dupSet/res2/drawable/icon.png
rename to sdk-common/src/test/resources/testData/resources/baseSet/drawable/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/drawable/patch.9.png b/sdk-common/src/test/resources/testData/resources/baseSet/drawable/patch.9.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/drawable/patch.9.png
rename to sdk-common/src/test/resources/testData/resources/baseSet/drawable/patch.9.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/layout/file_replaced_by_alias.xml b/sdk-common/src/test/resources/testData/resources/baseSet/layout/file_replaced_by_alias.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/layout/file_replaced_by_alias.xml
rename to sdk-common/src/test/resources/testData/resources/baseSet/layout/file_replaced_by_alias.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/layout/main.xml b/sdk-common/src/test/resources/testData/resources/baseSet/layout/main.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/layout/main.xml
rename to sdk-common/src/test/resources/testData/resources/baseSet/layout/main.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/raw/foo.dat b/sdk-common/src/test/resources/testData/resources/baseSet/raw/foo.dat
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/raw/foo.dat
rename to sdk-common/src/test/resources/testData/resources/baseSet/raw/foo.dat
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/values-sw600dp/values.xml b/sdk-common/src/test/resources/testData/resources/baseSet/values-sw600dp/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/values-sw600dp/values.xml
rename to sdk-common/src/test/resources/testData/resources/baseSet/values-sw600dp/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/values/empty.xml b/sdk-common/src/test/resources/testData/resources/baseSet/values/empty.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/values/empty.xml
rename to sdk-common/src/test/resources/testData/resources/baseSet/values/empty.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml b/sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet/layout/activity_main.xml b/sdk-common/src/test/resources/testData/resources/brokenSet/layout/activity_main.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet/layout/activity_main.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet/layout/activity_main.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet/values/dimens.xml b/sdk-common/src/test/resources/testData/resources/brokenSet/values/dimens.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet/values/dimens.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet/values/dimens.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet2/values/values.xml b/sdk-common/src/test/resources/testData/resources/brokenSet2/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet2/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet2/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet3/values/values.xml b/sdk-common/src/test/resources/testData/resources/brokenSet3/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet3/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet3/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet4/values/values.xml b/sdk-common/src/test/resources/testData/resources/brokenSet4/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet4/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet4/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet5/layout/ActivityMain.xml b/sdk-common/src/test/resources/testData/resources/brokenSet5/layout/ActivityMain.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet5/layout/ActivityMain.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet5/layout/ActivityMain.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet6/values/dimens.xml b/sdk-common/src/test/resources/testData/resources/brokenSet6/values/dimens.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet6/values/dimens.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet6/values/dimens.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSet7/values/dimens.xml b/sdk-common/src/test/resources/testData/resources/brokenSet7/values/dimens.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSet7/values/dimens.xml
rename to sdk-common/src/test/resources/testData/resources/brokenSet7/values/dimens.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/brokenSetDrawableFileName/drawable/1icon.png b/sdk-common/src/test/resources/testData/resources/brokenSetDrawableFileName/drawable/1icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/brokenSetDrawableFileName/drawable/1icon.png
rename to sdk-common/src/test/resources/testData/resources/brokenSetDrawableFileName/drawable/1icon.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/base/values/values.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/base/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/base/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/base/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/missing_mergeditem_merger.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/missing_mergeditem_merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/missing_mergeditem_merger.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/missing_mergeditem_merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/overlay/values/values.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/overlay/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/overlay/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/overlay/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/removed_merger.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/removed_merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/removed_merger.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/removed_merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/removed_other_merger.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/removed_other_merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/removed_other_merger.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/removed_other_merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/touched_merger.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/touched_merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/touched_merger.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/touched_merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/touched_nodiff_merger.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/touched_nodiff_merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/touched_nodiff_merger.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/touched_nodiff_merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/declareStyleable/unchanged_merger.xml b/sdk-common/src/test/resources/testData/resources/declareStyleable/unchanged_merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/declareStyleable/unchanged_merger.xml
rename to sdk-common/src/test/resources/testData/resources/declareStyleable/unchanged_merger.xml
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/res/drawable/icon.png b/sdk-common/src/test/resources/testData/resources/dupSet/res1/drawable/icon.png
similarity index 100%
copy from base/build-system/integration-test/test-projects/aidl/src/main/res/drawable/icon.png
copy to sdk-common/src/test/resources/testData/resources/dupSet/res1/drawable/icon.png
diff --git a/base/build-system/integration-test/test-projects/aidl/src/main/res/drawable/icon.png b/sdk-common/src/test/resources/testData/resources/dupSet/res2/drawable/icon.png
similarity index 100%
rename from base/build-system/integration-test/test-projects/aidl/src/main/res/drawable/icon.png
rename to sdk-common/src/test/resources/testData/resources/dupSet/res2/drawable/icon.png
diff --git a/sdk-common/src/test/resources/testData/resources/filterableSet/raw-v16/foo.txt b/sdk-common/src/test/resources/testData/resources/filterableSet/raw-v16/foo.txt
new file mode 100644
index 0000000..90e183f
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/filterableSet/raw-v16/foo.txt
@@ -0,0 +1 @@
+16th foo
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/resources/filterableSet/raw-v21/foo.txt b/sdk-common/src/test/resources/testData/resources/filterableSet/raw-v21/foo.txt
new file mode 100644
index 0000000..24c8c35
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/filterableSet/raw-v21/foo.txt
@@ -0,0 +1 @@
+21st foo
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/resources/filterableSet/raw/foo.txt b/sdk-common/src/test/resources/testData/resources/filterableSet/raw/foo.txt
new file mode 100644
index 0000000..5d08b47
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/filterableSet/raw/foo.txt
@@ -0,0 +1 @@
+raw foo
\ No newline at end of file
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/new_overlay.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/new_overlay.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/new_overlay.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/new_overlay.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/removed_overlay.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/removed_overlay.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/removed_overlay.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/removed_overlay.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/touched.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/touched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/touched.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/touched.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/untouched.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/untouched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/untouched.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/main/drawable/untouched.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/new_overlay.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/new_overlay.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/new_overlay.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/new_overlay.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable-hdpi/new_alternate.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable-hdpi/new_alternate.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable-hdpi/new_alternate.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable-hdpi/new_alternate.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable/new_overlay.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable/new_overlay.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable/new_overlay.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/overlay/drawable/new_overlay.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable-ldpi/removed.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable-ldpi/removed.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable-ldpi/removed.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable-ldpi/removed.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/new_overlay.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/new_overlay.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/new_overlay.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/new_overlay.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/removed_overlay.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/removed_overlay.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/removed_overlay.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/removed_overlay.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/touched.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/touched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/touched.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/touched.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/untouched.png b/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/untouched.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/untouched.png
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicFiles/resOut/drawable/untouched.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/main/values/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/main/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/main/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/main/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values-fr/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values-fr/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values-fr/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values-fr/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/overlay/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values-en/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values-en/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values-en/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values-en/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues/resOut/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/main/values/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/main/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/main/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/main/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/overlay/keepfolder.txt b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/overlay/keepfolder.txt
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/overlay/keepfolder.txt
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/overlay/keepfolder.txt
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/resOut/values/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/resOut/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/resOut/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/basicValues2/resOut/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/alias_replaced_by_file.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/alias_replaced_by_file.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/alias_replaced_by_file.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/alias_replaced_by_file.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/main.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/main.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/main.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/main.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/values/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/main/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/file_replaced_by_alias.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/file_replaced_by_alias.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/file_replaced_by_alias.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/file_replaced_by_alias.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/main.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/main.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/main.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/layout/main.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/values/values.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/values/values.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/values/values.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/filesVsValues/resOut/values/values.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/incMergeData/oldMerge/merger.xml b/sdk-common/src/test/resources/testData/resources/incMergeData/oldMerge/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/incMergeData/oldMerge/merger.xml
rename to sdk-common/src/test/resources/testData/resources/incMergeData/oldMerge/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/removedFile/merger.xml b/sdk-common/src/test/resources/testData/resources/removedFile/merger.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/removedFile/merger.xml
rename to sdk-common/src/test/resources/testData/resources/removedFile/merger.xml
diff --git a/base/sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/icon.png b/sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/icon.png
rename to sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/removed.png b/sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/removed.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/removed.png
rename to sdk-common/src/test/resources/testData/resources/removedFile/out/drawable/removed.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/removedFile/res/drawable/icon.png b/sdk-common/src/test/resources/testData/resources/removedFile/res/drawable/icon.png
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/removedFile/res/drawable/icon.png
rename to sdk-common/src/test/resources/testData/resources/removedFile/res/drawable/icon.png
diff --git a/base/sdk-common/src/test/resources/testData/resources/stringWhiteSpaces/values/strings.xml b/sdk-common/src/test/resources/testData/resources/stringWhiteSpaces/values/strings.xml
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/resources/stringWhiteSpaces/values/strings.xml
rename to sdk-common/src/test/resources/testData/resources/stringWhiteSpaces/values/strings.xml
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_contains_ignorable_error.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_contains_ignorable_error.png
new file mode 100644
index 0000000..f622628
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_contains_ignorable_error.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_contains_ignorable_error.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_contains_ignorable_error.svg
new file mode 100644
index 0000000..11fe122
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/ic_contains_ignorable_error.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+]>
+<svg version="1.1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24"
+ enable-background="new 0 0 24 24" xml:space="preserve">
+<switch>
+ <foreignObject requiredExtensions="&ns_ai;" x="0" y="0" width="1" height="1">
+ <i:pgfRef xlink:href="#adobe_illustrator_pgf">
+ </i:pgfRef>
+ </foreignObject>
+ <g i:extraneous="self">
+ <g id="Layer_2">
+ </g>
+ <g id="Layer_1">
+ <path d="M10.25,1.75c-0.6-0.6-1.5-0.6-2.1,0l-6.4,6.4c-0.6,0.6-0.6,1.5,0,2.1l12,12c0.6,0.6,1.5,0.6,2.1,0l6.4-6.4
+ c0.6-0.6,0.6-1.5,0-2.1L10.25,1.75z M14.85,21.25l-12-12l6.4-6.4l12,12L14.85,21.25z"/>
+ <path d="M16.55,2.5c3.3,1.5,5.6,4.7,6,8.5h1.5c-0.6-6.2-5.7-11-12-11c-0.2,0-0.4,0-0.7,0l3.8,3.8L16.55,2.5z"/>
+ <path d="M7.55,21.5c-3.3-1.5-5.6-4.7-6-8.5h-1.4c0.5,6.2,5.6,11,11.9,11c0.2,0,0.4,0,0.7,0l-3.8-3.8L7.55,21.5z"/>
+ </g>
+ <g id="Layer_3">
+ </g>
+ </g>
+</switch>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.png
new file mode 100644
index 0000000..85ba77d
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_content_cut_24px.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_empty_attributes.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_empty_attributes.png
new file mode 100644
index 0000000..cf3c194
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_empty_attributes.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_empty_attributes.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_empty_attributes.svg
new file mode 100644
index 0000000..707a719
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/ic_empty_attributes.svg
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.3 (12081) - http://www.bohemiancoding.com/sketch -->
+ <title>ic_qset_dnd_silence</title>
+ <desc>Created with Sketch.</desc>
+ <animate></animate>
+ <animateColor></animateColor>
+ <animateMotion></animateMotion>
+ <animateTransform></animateTransform>
+ <mpath></mpath>
+ <set></set>
+ <a></a>
+ <defs></defs>
+ <glyph></glyph>
+ <marker></marker>
+ <mask></mask>
+ <missing-glyph></missing-glyph>
+ <pattern></pattern>
+ <switch></switch>
+ <symbol></symbol>
+ <feBlend></feBlend>
+ <feColorMatrix></feColorMatrix>
+ <feComponentTransfer></feComponentTransfer>
+ <feComposite></feComposite>
+ <feConvolveMatrix></feConvolveMatrix>
+ <feDiffuseLighting></feDiffuseLighting>
+ <feDisplacementMap></feDisplacementMap>
+ <feFlood></feFlood>
+ <feFuncA></feFuncA>
+ <feFuncB></feFuncB>
+ <feFuncG></feFuncG>
+ <feFuncR></feFuncR>
+ <feGaussianBlur></feGaussianBlur>
+ <feImage></feImage>
+ <feMerge></feMerge>
+ <feMergeNode></feMergeNode>
+ <feMorphology></feMorphology>
+ <feOffset></feOffset>
+ <feSpecularLighting></feSpecularLighting>
+ <feTile></feTile>
+ <feTurbulence></feTurbulence>
+ <font></font>
+ <font-face></font-face>
+ <font-face-format></font-face-format>
+ <font-face-name></font-face-name>
+ <font-face-src></font-face-src>
+ <font-face-uri></font-face-uri>
+ <hkern></hkern>
+ <vkern></vkern>
+ <linearGradient></linearGradient>
+ <radialGradient></radialGradient>
+ <stop></stop>
+ <ellipse></ellipse>
+ <text></text>
+ <use></use>
+ <feDistantLight></feDistantLight>
+ <fePointLight></fePointLight>
+ <feSpotLight></feSpotLight>
+ <defs></defs>
+ <symbol></symbol>
+ <use></use>
+ <altGlyph></altGlyph>
+ <altGlyphDef></altGlyphDef>
+ <altGlyphItem></altGlyphItem>
+ <glyph></glyph>
+ <glyphRef></glyphRef>
+ <textPath></textPath>
+ <text></text>
+ <tref></tref>
+ <tspan></tspan>
+ <altGlyph></altGlyph>
+ <textPath></textPath>
+ <tref></tref>
+ <tspan></tspan>
+ <clipPath></clipPath>
+ <color-profile></color-profile>
+ <cursor></cursor>
+ <filter></filter>
+ <foreignObject></foreignObject>
+ <script></script>
+ <view></view>
+
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="ic_qset_dnd_silence" sketch:type="MSArtboardGroup">
+ <path d="M24,12 C17.4,12 12,17.4 12,24 C12,30.6 17.4,36 24,36 C30.6,36 36,30.6 36,24 C36,17.4 30.8,12 24,12 L24,12 Z M30,26 L18,26 L18,22 L30,22 L30,26 L30,26 Z" id="Shape" fill="#FF0000" sketch:type="MSShapeGroup"></path>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.png
new file mode 100644
index 0000000..17f07f3
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_input_24px.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.png
new file mode 100644
index 0000000..5dfb8b1
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_live_help_24px.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.png
new file mode 100644
index 0000000..5b1443c
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_local_library_24px.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.png
new file mode 100644
index 0000000..f29214d
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_local_phone_24px.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.png
new file mode 100644
index 0000000..e6f0d55
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_mic_off_24px.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.png
new file mode 100644
index 0000000..1963a5e
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_plus_sign.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_polyline_strokewidth.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_polyline_strokewidth.png
new file mode 100644
index 0000000..f09eca6
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_polyline_strokewidth.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_polyline_strokewidth.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_polyline_strokewidth.svg
new file mode 100644
index 0000000..988fef8
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/ic_polyline_strokewidth.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
+<svg version="1.1" baseProfile="tiny" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="7 7 14 14"
+ xml:space="preserve">
+<polyline fill="none" stroke="#8827FF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="5"
+ points="14,10 14,14 14,14.7 16.9,16.5 "/>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.png
new file mode 100644
index 0000000..d1075c3
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.svg
similarity index 100%
copy from base/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.svg
copy to sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_simple_group_info.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_simple_group_info.png
new file mode 100644
index 0000000..cf3c194
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_simple_group_info.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_simple_group_info.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_simple_group_info.svg
new file mode 100644
index 0000000..e9a02f1
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/ic_simple_group_info.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.3 (12081) - http://www.bohemiancoding.com/sketch -->
+ <title>ic_qset_dnd_silence</title>
+ <desc>Created with Sketch.</desc>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="ic_qset_dnd_silence" sketch:type="MSArtboardGroup">
+ <path d="M0,0 L48,0 L48,48 L0,48 L0,0 L0,0 Z" id="Shape" sketch:type="MSShapeGroup"></path>
+ <path d="M24,12 C17.4,12 12,17.4 12,24 C12,30.6 17.4,36 24,36 C30.6,36 36,30.6 36,24 C36,17.4 30.8,12 24,12 L24,12 Z M30,26 L18,26 L18,22 L30,22 L30,26 L30,26 Z" id="Shape" fill="#FF0000" sketch:type="MSShapeGroup"></path>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_size_opacity.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_size_opacity.png
new file mode 100644
index 0000000..100f90d
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_size_opacity.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_size_opacity.xml b/sdk-common/src/test/resources/testData/vectordrawable/ic_size_opacity.xml
new file mode 100644
index 0000000..10779dc
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/ic_size_opacity.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector android:alpha="0.39" android:height="24dp"
+ android:viewportHeight="24.0" android:viewportWidth="24.0"
+ android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000"
+ android:pathData="M7.52,21.48C4.25,19.94 1.91,16.76 1.55,13H0.05C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32zm0.89,-6.52c-0.19,0 -0.37,-0.03 -0.52,-0.08 -0.16,-0.06 -0.29,-0.13 -0.4,-0.24 -0.11,-0.1 -0.2,-0.22 -0.26,-0.37 -0.06,-0.14 -0.09,-0.3 -0.09,-0.47h-1.3c0,0.36 0.07,0.68 0.21,0.95 0.14,0.27 0.33,0.5 0.56,0.69 0.24,0.18 0.51,0.32 0.82,0.41 0.3,0.1 0.62,0.15 0.96,0.15 0.37,0 0.72,-0.05 1.03,-0.15 0.32,-0.1 0.6,-0.25 0.83,-0.44s0.42,-0.43 0.55,-0.72c0.13,-0.2 [...]
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_strokewidth_transform.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_strokewidth_transform.png
new file mode 100644
index 0000000..add5528
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_strokewidth_transform.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_strokewidth_transform.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_strokewidth_transform.svg
new file mode 100644
index 0000000..2b46fb3
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/ic_strokewidth_transform.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
+<svg version="1.1" baseProfile="tiny" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="64px" height ="64px" viewBox="0 0 64 64"
+ xml:space="preserve">
+<polyline fill="none" stroke="#88EEFF" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="5"
+ points="14,10 14,14 14,14.7 16.9,16.5 " transform="scale(4, 2)" vector-effect="non-scaling-stroke"/>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.png b/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.png
new file mode 100644
index 0000000..3c68658
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.svg b/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/ic_temp_high.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_arcto_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_arcto_1.png
new file mode 100644
index 0000000..bfc5681
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_arcto_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_arcto_1.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_arcto_1.svg
new file mode 100644
index 0000000..6d5a648
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_arcto_1.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg width="20" height="50" viewBox="140 100 20 50" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <path d="M 154.2857 147.3622 A 50.71429 62.14286 0 0 0 141.063 105.5146"
+ style="fill: #ff55ff" />
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_color_formats.png b/sdk-common/src/test/resources/testData/vectordrawable/test_color_formats.png
new file mode 100644
index 0000000..86448c7
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_color_formats.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_color_formats.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_color_formats.svg
new file mode 100644
index 0000000..f1057c3
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_color_formats.svg
@@ -0,0 +1,12 @@
+<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+ <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+ <g>
+ <title>Layer 1</title>
+ <g id="svg_1"/>
+ <rect fill="#f00" id="svg_2" height="10" width="64" y="0" x="0"/>
+ <rect fill="#00ff00" id="svg_3" height="10" width="64" y="10" x="0"/>
+ <rect id="svg_4" height="10" width="64" y="20" x="0" fill="rgb(0, -10, 355)"/>
+ <rect id="svg_5" height="10" width="64" y="30" x="0" fill="rgb(150%, -50%, -50%)"/>
+ <rect id="svg_6" height="10" width="64" y="40" x="0" fill="rgb(-50%, 150%, -150%)"/>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.png b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.png
new file mode 100644
index 0000000..cbf4e85
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/test_control_points_01.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.png b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.png
new file mode 100644
index 0000000..56d54d8
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/test_control_points_02.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.png b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.png
new file mode 100644
index 0000000..6399628
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.png differ
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.svg
rename to sdk-common/src/test/resources/testData/vectordrawable/test_control_points_03.svg
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_ellipse.png b/sdk-common/src/test/resources/testData/vectordrawable/test_ellipse.png
new file mode 100644
index 0000000..cd389ab
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_ellipse.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_ellipse.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_ellipse.svg
new file mode 100644
index 0000000..b6d38e4
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_ellipse.svg
@@ -0,0 +1,5 @@
+<svg height="150" width="500">
+ <ellipse cx="240" cy="100" rx="220" ry="30" style="fill: #CC33cc" />
+ <ellipse cx="220" cy="70" rx="190" ry="20" style="fill: #33cccc" />
+ <ellipse cx="210" cy="45" rx="170" ry="15" style="fill: #ccCC33" />
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_fill_stroke_alpha.png b/sdk-common/src/test/resources/testData/vectordrawable/test_fill_stroke_alpha.png
new file mode 100644
index 0000000..76bd233
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_fill_stroke_alpha.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_fill_stroke_alpha.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_fill_stroke_alpha.xml
new file mode 100644
index 0000000..3f53a6c
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_fill_stroke_alpha.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector android:height="24dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillAlpha="0.8" android:fillColor="#EEFFFF00" android:pathData="M16.41,24.832l1.0939999,1.171999l6.4960003,-6.0039997l-6.4960003,-6l-1.0939999,1.1680002l3.0860004,2.8319998l-17.496,0l0,4l17.5,0z"/>
+ <path android:fillAlpha="0.3" android:fillColor="#EF0F" android:pathData="M0,0h12v12h-12z"/>
+ <path android:fillAlpha="0.3" android:fillColor="#0FF" android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/>
+ <path android:pathData="M0,2 L24,2" android:strokeAlpha="0.8" android:strokeColor="#DDFFFFFF" android:strokeWidth="4"/>
+ <path android:pathData="M0,8 L24,8" android:strokeAlpha="0.3" android:strokeColor="#FFFFFF" android:strokeWidth="4"/>
+ <path android:pathData="M0,14 L24,14" android:strokeAlpha="0.8" android:strokeColor="#DFFF" android:strokeWidth="4"/>
+</vector>
+
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto.png b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto.png
new file mode 100644
index 0000000..7724d48
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto.svg
new file mode 100644
index 0000000..b249ddf
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
+ <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+ <g>
+ <title>Layer 1</title>
+ <g id="svg_1">
+ <path d="m0,0 32,0 0,32 -32,0 0,-32z"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto2.png
new file mode 100644
index 0000000..7724d48
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto2.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto2.svg
new file mode 100644
index 0000000..a076fd5
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto2.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
+ <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+ <g>
+ <title>Layer 1</title>
+ <g id="svg_1">
+ <path d="M0,0 32,0 32,32 0,32z"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox1.png
new file mode 100644
index 0000000..a066ac9
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox1.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox1.svg
new file mode 100644
index 0000000..9cdb9e4
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox1.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"
+ x="0px" y="0px" viewBox="16 16 32 32">
+ <g>
+ <title>Layer 1</title>
+ <g id="svg_1">
+ <path d="m0,0 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CC33cc"/>
+ <path d="m32 32 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CCddcc"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox2.png
new file mode 100644
index 0000000..9dac543
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox2.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox2.svg
new file mode 100644
index 0000000..4225cef
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox2.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"
+ x="0px" y="0px" viewBox="16 16 24 36" preserveAspectRatio = "none">
+ <g>
+ <title>Layer 1</title>
+ <g id="svg_1">
+ <path d="m0,0 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CC33cc"/>
+ <path d="m32 32 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CCddcc"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox3.png b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox3.png
new file mode 100644
index 0000000..903f43a
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox3.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox3.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox3.svg
new file mode 100644
index 0000000..2dbf6e8
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox3.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"
+ x="0px" y="0px" viewBox="16 16 32 32">
+ <g transform="scale(1.25, 1.25)">
+ <title>Layer 1</title>
+ <g id="svg_1">
+ <path d="m0,0 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CC33cc"/>
+ <path d="m32 32 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CCddcc"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox4.png b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox4.png
new file mode 100644
index 0000000..e1299ca
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox4.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox4.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox4.svg
new file mode 100644
index 0000000..54c1386
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox4.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="50%" height="75%" xmlns="http://www.w3.org/2000/svg"
+ x="0px" y="0px" viewBox="16 16 32 32">
+ <g transform="scale(1.25, 1.25)">
+ <title>Layer 1</title>
+ <g id="svg_1">
+ <path d="m0,0 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CC33cc"/>
+ <path d="m32 32 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CCddcc"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox5.png b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox5.png
new file mode 100644
index 0000000..f1aa9ed
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox5.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox5.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox5.svg
new file mode 100644
index 0000000..2c9529c
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_lineto_moveto_viewbox5.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="50%" height="32px" xmlns="http://www.w3.org/2000/svg"
+ x="0px" y="0px" viewBox="16 16 32 32">
+ <g transform="scale(1.25, 1.25)">
+ <title>Layer 1</title>
+ <g id="svg_1">
+ <path d="m0,0 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CC33cc"/>
+ <path d="m32 32 32,0 0,32 -32,0 0,-32z"
+ style="fill: #CCddcc"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close.png b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close.png
new file mode 100644
index 0000000..9813ae8
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close.svg
new file mode 100644
index 0000000..a70c453
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
+ viewBox="0 0 24 24">
+ <path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" style="fill: #33ffcc"/>
+</svg>
+
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close_transform.png b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close_transform.png
new file mode 100644
index 0000000..73240aa
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close_transform.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close_transform.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close_transform.svg
new file mode 100644
index 0000000..0e72ff8
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_move_after_close_transform.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
+ viewBox="0 0 24 24">
+ <path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" style="fill: #33ffcc" transform="scale(1.5, 0.5) rotate(45, 10,10)"/>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex1.png
new file mode 100644
index 0000000..24919dd
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex1.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex1.svg
new file mode 100644
index 0000000..f752764
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex1.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g transform="skewY(20) skewX(20)">
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 1,0 40,0 20,20 0 1,0 -40,0"
+ style="fill: #3333cc" transform="scale(1.5, 0.5) rotate(45, 10,10) "/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex2.png
new file mode 100644
index 0000000..510920b
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex2.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex2.svg
new file mode 100644
index 0000000..201ffc3
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_complex2.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g transform="skewY(20) skewX(20)">
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 1,0 40,0 20,20 0 1,0 -40,0"
+ style="fill: #3333cc" transform="matrix(0.5, 0.3, 1, 0.3, 0.5, 1)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_rotate_scale_translate.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_rotate_scale_translate.png
new file mode 100644
index 0000000..0b21f8b
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_rotate_scale_translate.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_rotate_scale_translate.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_rotate_scale_translate.svg
new file mode 100644
index 0000000..8990285
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_rotate_scale_translate.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 1,0 40,0 20,20 0 1,0 -40,0"
+ style="fill: #3333cc" transform="rotate(20, 50, 50) scale(1.5, 0.5) translate(-10, 10)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale.png
new file mode 100644
index 0000000..8d985a4
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale.svg
new file mode 100644
index 0000000..6dcce47
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 1,0 40,0 20,20 0 1,0 -40,0"
+ style="fill: #3333cc" transform="scale(1.5, 0.5)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale_rotate.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale_rotate.png
new file mode 100644
index 0000000..d257fc8
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale_rotate.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale_rotate.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale_rotate.svg
new file mode 100644
index 0000000..079ca62
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_scale_rotate.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 1,0 40,0 20,20 0 1,0 -40,0"
+ style="fill: #3333cc" transform="scale(1.5, 0.5) rotate(45, 10,10) "/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewx.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewx.png
new file mode 100644
index 0000000..6b3d189
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewx.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewx.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewx.svg
new file mode 100644
index 0000000..3662b8b
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewx.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g transform="skewX(10)">
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 0,0 40,0 20,20 0 0,0 -40,0"
+ style="fill: #3333cc"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewy.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewy.png
new file mode 100644
index 0000000..dff6cde
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewy.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewy.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewy.svg
new file mode 100644
index 0000000..955c31f
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_arc_skewy.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g transform="skewY(20)">
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 0,0 40,0 20,20 0 0,0 -40,0"
+ style="fill: #3333cc"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex.png
new file mode 100644
index 0000000..59c5153
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex.svg
new file mode 100644
index 0000000..b649c7e
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g transform="skewY(20) skewX(20)">
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M0,0A20,20 0 0,1 40,0 20,20 0 0,1 0,0"
+ style="fill: #3333cc" transform="matrix(0.5, 0.3, 1, 0.3, 0.5, 1)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex_viewbox.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex_viewbox.png
new file mode 100644
index 0000000..c1bce45
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex_viewbox.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex_viewbox.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex_viewbox.svg
new file mode 100644
index 0000000..dfe3712
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_complex_viewbox.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" viewBox="20 20 50 50">
+ <g transform="skewY(20) skewX(20)">
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M0,0A20,20 0 0,1 40,0 20,20 0 0,1 0,0"
+ style="fill: #3333cc" transform="matrix(0.5, 0.3, 1, 0.3, 0.5, 1)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_translate_scale.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_translate_scale.png
new file mode 100644
index 0000000..0abcb13
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_translate_scale.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_translate_scale.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_translate_scale.svg
new file mode 100644
index 0000000..6d5e078
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_big_arc_translate_scale.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M0,0A20,20 0 0,1 40,0 20,20 0 0,1 0,0"
+ style="fill: #3333cc" transform=" translate(20, 20) scale(1.5, 0.5)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_c_q_no_move.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_c_q_no_move.png
new file mode 100644
index 0000000..f173e47
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_c_q_no_move.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_c_q_no_move.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_c_q_no_move.svg
new file mode 100644
index 0000000..4e90fdd
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_c_q_no_move.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="c 2 1 7 26 40 36 q 10 51 4 5 z "
+ style="fill: #3333cc" transform="rotate(-45, 50, 0) " />
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_rotate.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_rotate.png
new file mode 100644
index 0000000..d47c8c5
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_rotate.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_rotate.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_rotate.svg
new file mode 100644
index 0000000..b3d1903
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_rotate.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <circle cx="40" cy="40" r="25"
+ style="fill: #3333cc" transform="rotate(10 50 50)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_scale.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_scale.png
new file mode 100644
index 0000000..14734da
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_scale.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_scale.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_scale.svg
new file mode 100644
index 0000000..39f3234
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_circle_scale.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <circle cx="40" cy="40" r="25"
+ style="fill: #3333cc" transform="scale(1.5, 0.5)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_complex.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_complex.png
new file mode 100644
index 0000000..83ff9e9
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_complex.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_complex.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_complex.svg
new file mode 100644
index 0000000..c5fca17
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_complex.svg
@@ -0,0 +1,7 @@
+<svg height="150" width="500">
+ <g transform="matrix(1.5, 0.3, .1, 1.3, 0.5, -1)">
+ <ellipse cx="240" cy="100" rx="220" ry="30" style="fill: #CC33cc" />
+ <ellipse cx="220" cy="70" rx="190" ry="20" style="fill: #33cccc" />
+ <ellipse cx="210" cy="45" rx="170" ry="15" style="fill: #ccCC33" />
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_rotate_scale_translate.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_rotate_scale_translate.png
new file mode 100644
index 0000000..f911eea
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_rotate_scale_translate.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_rotate_scale_translate.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_rotate_scale_translate.svg
new file mode 100644
index 0000000..d053f99
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_ellipse_rotate_scale_translate.svg
@@ -0,0 +1,7 @@
+<svg height="150" width="500">
+ <g transform="rotate(10, 50, 50) scale(1.5, 1.5) translate(-10, -20)">
+ <ellipse cx="240" cy="100" rx="220" ry="30" style="fill: #CC33cc" />
+ <ellipse cx="220" cy="70" rx="190" ry="20" style="fill: #33cccc" />
+ <ellipse cx="210" cy="45" rx="170" ry="15" style="fill: #ccCC33" />
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_1.png
new file mode 100644
index 0000000..f09b7e3
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_1.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_1.svg
new file mode 100644
index 0000000..065f407
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_1.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g transform="translate(50, 50)">
+ <g transform="rotate(45)">
+ <g transform="translate(-50,-50)">
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_2.png
new file mode 100644
index 0000000..9766309
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_2.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_2.svg
new file mode 100644
index 0000000..34cc26d
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_2.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g transform="skewX(20) skewY(20)">
+ <g transform="translate(-10,-20)">
+ <g transform="scale(1,1)">
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_3.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_3.png
new file mode 100644
index 0000000..a4ef167
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_3.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_3.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_3.svg
new file mode 100644
index 0000000..de3cb56
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_3.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+
+ <g transform="rotate(45, 50, 50)">
+ <g transform="skewX(30)">
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="scale(-0.5, 1.5)"/>
+ </g>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_4.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_4.png
new file mode 100644
index 0000000..36cdc9b
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_4.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_4.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_4.svg
new file mode 100644
index 0000000..d6f95fa
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_group_4.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+
+ <g transform="translate(10,10)">
+ <g transform="matrix(0.5, 0.3, 1, 0.3, 0.5, 1)">
+ <g transform="rotate(-30)">
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_c_complex.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_c_complex.png
new file mode 100644
index 0000000..b20c758
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_c_complex.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_c_complex.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_c_complex.svg
new file mode 100644
index 0000000..938b761
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_c_complex.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="m 0 0 a 39.4827 38.844944 54.52335 1 1 13.549484 26.81581
+ c 56.943657 51.96854 27.938824 61.148792 24.168636 26.642727
+ h 10 s 14 49 35 54 t 11 22 v 20 10 -10 h -20, -20 0 V 40 0 20 "
+ style="fill: #3333cc" transform="rotate(45, 50, 50) scale(0.9, 0.9)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_complex.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_complex.png
new file mode 100644
index 0000000..2278ccf
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_complex.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_complex.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_complex.svg
new file mode 100644
index 0000000..9e6aa8f
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_a_complex.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20a20,20 0 1,0 40,0 20,20 0 1,0 -40,0 v 20 10 -10 h -20, -20 0 V 40 0 20 "
+ style="fill: #3333cc" transform="rotate(45, 50, 50) skewX(10)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q.png
new file mode 100644
index 0000000..7b81874
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q.svg
new file mode 100644
index 0000000..df49df3
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="m 0 0 c 2.0560722 1.4615479 7.0928993 26.005287 40.137558 36.75628 q 10.356537 51.850616 4.085087 5.653175 h 0,20,60 v 20 10 -10 h -20, -20 0 V 40 0 20 z "
+ style="fill: #3333cc" />
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q_complex.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q_complex.png
new file mode 100644
index 0000000..0b4dcd3
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q_complex.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q_complex.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q_complex.svg
new file mode 100644
index 0000000..b9ab974
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_c_q_complex.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="c 2 1 7 26 40 36 q 10 51 4 5 z v 20 10 -10 h -20, -20 0 V 40 0 20 "
+ style="fill: #3333cc" transform="rotate(45, 50, 50) scale(1.5, 0.5)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_basic.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_basic.png
new file mode 100644
index 0000000..be57a88
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_basic.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_basic.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_basic.svg
new file mode 100644
index 0000000..ebd8ae3
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_basic.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="M20,20H 0,20,60 v 20 10 -10 h -20 -20 0 V 40 0 20"
+ style="fill: #3333cc" />
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_complex.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_complex.png
new file mode 100644
index 0000000..930359f
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_complex.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_complex.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_complex.svg
new file mode 100644
index 0000000..70f0b35
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_complex.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d=" M20,20H 0,20,60 v 20 10 -10 h -20, -20 0 V 40 0 20 "
+ style="fill: #3333cc" transform="rotate(45, 50, 50) scale(1.5, 0.5)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_matrix.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_matrix.png
new file mode 100644
index 0000000..9474d29
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_matrix.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_matrix.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_matrix.svg
new file mode 100644
index 0000000..369a15e
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_matrix.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d=" M20,20H 0,20,60 v 20 10 -10 h -20 -20 0 V 40 0 20"
+ style="fill: #3333cc" transform="matrix(0.5, 0.3, 1, 0.3, 0.5, 1)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_translate.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_translate.png
new file mode 100644
index 0000000..ee7d269
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_translate.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_translate.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_translate.svg
new file mode 100644
index 0000000..7f8dd21
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_loop_translate.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d=" M20,20H 0,20,60 v 20 10 -10 h -20, -20 0 V 40 0 20 "
+ style="fill: #3333cc" transform="translate(10, 10)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex.png
new file mode 100644
index 0000000..e2d30b0
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex.svg
new file mode 100644
index 0000000..4cc3335
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="m 0 0 h 10 s 14 49 35 54 t 11 22 v 20 10 -10 h -20, -20 0 V 40 0 20 "
+ style="fill: #3333cc" transform="rotate(45, 50, 50) scale(0.9, 0.9)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex2.png
new file mode 100644
index 0000000..ea22da1
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex2.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex2.svg
new file mode 100644
index 0000000..bcea417
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_h_v_s_t_complex2.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <path d="m 0 0 T 36.918854 59.542286
+ s 14 49 35 54 t 11 22 v 20 10 -10 h -20, -20 0 V 40 0 20 H 20 "
+ style="fill: #3333cc" transform="skewX(10) skewY(10)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_1.png
new file mode 100644
index 0000000..f09b7e3
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_1.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_1.svg
new file mode 100644
index 0000000..944619a
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_1.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="translate(50, 50) rotate(45), translate(-50,-50)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_2.png
new file mode 100644
index 0000000..9766309
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_2.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_2.svg
new file mode 100644
index 0000000..16e3be5
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_2.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="skewX(20) skewY(20), translate(-10,-20)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_3.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_3.png
new file mode 100644
index 0000000..a4ef167
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_3.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_3.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_3.svg
new file mode 100644
index 0000000..a112650
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_3.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="rotate(45, 50, 50) skewX(30), scale(-0.5, 1.5)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_4.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_4.png
new file mode 100644
index 0000000..36cdc9b
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_4.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_4.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_4.svg
new file mode 100644
index 0000000..40c14d3
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_multiple_4.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="translate(10,10) matrix(0.5, 0.3, 1, 0.3, 0.5, 1) rotate(-30)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_matrix.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_matrix.png
new file mode 100644
index 0000000..905a484
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_matrix.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_matrix.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_matrix.svg
new file mode 100644
index 0000000..81acd39
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_matrix.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="matrix(0.5, 0.3, 1, 0.3, 0.5, 1)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_rotate.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_rotate.png
new file mode 100644
index 0000000..f09b7e3
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_rotate.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_rotate.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_rotate.svg
new file mode 100644
index 0000000..34c9a43
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_rotate.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="rotate(4.5e1, 50, 50)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_scale.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_scale.png
new file mode 100644
index 0000000..3ac1847
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_scale.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_scale.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_scale.svg
new file mode 100644
index 0000000..fb37cdd
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_scale.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="scale(2, 3)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewx.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewx.png
new file mode 100644
index 0000000..0726351
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewx.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewx.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewx.svg
new file mode 100644
index 0000000..a36fb35
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewx.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="skewX(20)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewy.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewy.png
new file mode 100644
index 0000000..ad6bac3
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewy.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewy.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewy.svg
new file mode 100644
index 0000000..09eae0b
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_skewy.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="skewY(20)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_translate.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_translate.png
new file mode 100644
index 0000000..cb0663f
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_translate.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_translate.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_translate.svg
new file mode 100644
index 0000000..f525980
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_rect_translate.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50"
+ style="fill: #3333cc" transform="translate(25,25)"/>
+ </g>
+ </g>
+</svg>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_round_rect_matrix.png b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_round_rect_matrix.png
new file mode 100644
index 0000000..3210dc8
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_round_rect_matrix.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_transform_round_rect_matrix.svg b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_round_rect_matrix.svg
new file mode 100644
index 0000000..8c9f185
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_transform_round_rect_matrix.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <g>
+ <rect x="20" y="20" width="50" height="50" rx="20"
+ style="fill: #cc3333"/>
+ </g>
+ <g>
+ <rect x="20" y="20" width="50" height="50" ry="35"
+ style="fill: #3333cc" transform="matrix(0.5, 0.3, 1, 0.3, 0.5, 1)"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_color_formats.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_color_formats.png
new file mode 100644
index 0000000..ac549f7
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_color_formats.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_color_formats.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_color_formats.xml
new file mode 100644
index 0000000..9a72129
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_color_formats.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector android:height="24dp" android:viewportHeight="64.0"
+ android:viewportWidth="64.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#f00" android:pathData="M0,0h64v10h-64z"/>
+ <path android:fillColor="#00ff00" android:pathData="M0,10h64v10h-64z"/>
+ <path android:fillColor="#EE0000FF" android:pathData="M0,20h64v10h-64z"/>
+ <path android:fillColor="#1FF0" android:pathData="M0,30h64v10h-64z"/>
+ <path android:fillColor="#1100FFFF" android:pathData="M0,40h64v10h-64z"/>
+</vector>
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_1.png
new file mode 100644
index 0000000..36dd01f
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_1.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_1.xml
new file mode 100644
index 0000000..bbdab45
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_1.xml
@@ -0,0 +1,89 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400" >
+
+ <group
+ android:name="FirstLevelGroup"
+ android:translateX="100.0"
+ android:translateY="0.0" >
+ <path
+ android:fillColor="#FFFF0000"
+ android:fillAlpha="0.9"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+
+ <group
+ android:name="SecondLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillColor="#FF00FF00"
+ android:fillAlpha="0.81"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+
+ <group
+ android:name="ThridLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillColor="#FF0000FF"
+ android:fillAlpha="0.729"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="ThridLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.72"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ </group>
+ <group
+ android:name="SecondLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillColor="#FF0000FF"
+ android:fillAlpha="0.72"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+
+ <group
+ android:name="ThridLevelGroup3"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.648"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="ThridLevelGroup4"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.576"
+ android:fillColor="#FF00FF00"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_2.png
new file mode 100644
index 0000000..fe2a15d
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_2.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_2.xml
new file mode 100644
index 0000000..33952ed
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_render_order_2.xml
@@ -0,0 +1,89 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400" >
+
+ <group
+ android:name="FirstLevelGroup"
+ android:translateX="100.0"
+ android:translateY="0.0" >
+ <group
+ android:name="SecondLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.81"
+ android:fillColor="#FF00FF00"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+
+ <group
+ android:name="ThridLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.729"
+ android:fillColor="#FF0000FF"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="ThridLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.648"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ </group>
+ <group
+ android:name="SecondLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.72"
+ android:fillColor="#FF0000FF"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+
+ <group
+ android:name="ThridLevelGroup3"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.648"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="ThridLevelGroup4"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fillAlpha="0.576"
+ android:fillColor="#FF00FF00"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ </group>
+
+ <path
+ android:fillAlpha="0.9"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_1.png
new file mode 100644
index 0000000..d95f56b
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_1.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_1.xml
new file mode 100644
index 0000000..e27464b
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_1.xml
@@ -0,0 +1,43 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128" >
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 45.712063 19.109837
+ H 24.509682
+ a 59.3415 26.877445 22.398209 1 1 3.3506432 1.6524277
+ a 34.922844 36.72583 13.569004 0 0 24.409462 20.931156
+ a 43.47134 32.61542 52.534607 1 0 7.187504 61.509724
+ A 30.621132 41.44202 50.885685 0 0 23.235489 26.638653
+ A 7.251148 15.767811 44.704533 1 1 19.989803 21.33052
+ A 55.645584 46.20288 19.40316 0 1 32.881298 53.410923
+ c 30.649612 4.8525085 21.96682 1.3304634 17.300182 14.747681
+ a 9.375069 44.365055 57.169727 0 0 56.01326 52.59596
+ A 50.071907 37.331825 56.301754 1 0 14.676102 62.04976
+ C 36.531925 4.6217957 47.59332 54.793385 13.562473 13.753647
+ A 2.3695297 42.578487 54.250687 0 1 33.1337 41.511288
+ a 39.4827 38.844944 54.52335 1 1 13.549484 46.81581
+ c 56.943657 51.96854 27.938824 61.148792 24.168636 46.642727
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_2.png
new file mode 100644
index 0000000..043681a
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_2.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_2.xml
new file mode 100644
index 0000000..924ba1b
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_a_2.xml
@@ -0,0 +1,45 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128" >
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 45.712063 19.109837
+ H 24.509682
+ A 37.689938 2.3916092 17.462616 1 0 24.958328 48.110596
+ q 45.248383 30.396336 5.777027 3.4086685
+ a 30.966236 62.67946 50.532032 1 0 29.213684 60.63014
+ L 56.16764 8.342098
+ Q 61.172253 1.4613304 4.4721107 38.287144
+ A 6.284897 22.991482 47.409508 1 1 44.10166 60.998764
+ t 36.36881 55.68292
+ a 51.938667 35.22107 22.272938 1 1 28.572739 60.848858
+ A 19.610851 11.569599 51.407906 1 1 56.82705 24.386292
+ T 36.918854 59.542286
+ a 33.191364 10.553429 53.047726 1 0 54.874985 7.409252
+ s 30.186714 42.154182 59.73551 35.50219
+ A 47.9379 5.776497 28.307701 1 1 3.3323975 30.113499
+ a 22.462494 28.096004 55.76455 0 0 25.58981 30.816948
+ S 43.91107 54.679676 19.540264 0.34284973
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_cq.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_cq.png
new file mode 100644
index 0000000..bf93e2e
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_cq.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_cq.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_cq.xml
new file mode 100644
index 0000000..e0848f0
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_cq.xml
@@ -0,0 +1,42 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128" >
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 30.81895 41.37989
+ v 31.00579
+ c 24.291603 52.03364 40.6086 24.840137 29.56704 6.5204926
+ 45.133224 22.913471 33.052887 21.727486 33.369 61.60278
+ 9.647232 22.098152 48.939598 47.470215 53.653687 62.32235
+ C 2.0560722 1.4615479 7.0928993 26.005287 40.137558 36.75628
+ 11.246731 32.178127 59.367462 60.34823 57.254383 37.357815
+ 47.75605 11.424667 3.3105545 51.886635 56.63027 17.12133
+ q 28.37534 32.85535 25.85654 33.57151
+ 10.356537 51.850616 54.085087 35.653175
+ 12.530029 52.87991 17.44696 11.780586
+ Q 2.585228 51.92801 60.000664 56.79912
+ 54.18275 51.500694 9.375679 23.836113
+ 60.35329 59.026245 31.058632 35.14934
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_st.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_st.png
new file mode 100644
index 0000000..bef0e7c
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_st.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_st.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_st.xml
new file mode 100644
index 0000000..b104349
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_repeated_st.xml
@@ -0,0 +1,42 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128" >
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 20.20005 8.139153
+ h 10.053165
+ s 14.2943 49.612846 35.520653 54.904068
+ 50.1405 17.044182 5.470337 40.180553
+ 3.125019 34.221123 53.212563 32.862965
+ S 35.985264 35.74349 0.15337753 59.27337
+ 2.2951508 44.56783 51.089413 29.829689
+ 8.5599785 22.649555 4.3914986 28.139206
+ t 11.932453 44.041077
+ 62.629326 7.40921
+ 23.302986 54.116184
+ T 43.560753 63.370514
+ 40.156204 17.60786
+ 40.12051 60.803394
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_1.png
new file mode 100644
index 0000000..abb722c
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_1.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_1.xml
new file mode 100644
index 0000000..530c73b
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_1.xml
@@ -0,0 +1,52 @@
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
+ </group>
+ <group
+ android:scaleX="-1"
+ android:scaleY="-1" >
+ <group
+ android:scaleX="-1"
+ android:scaleY="-1" >
+ <group
+ android:pivotX="100"
+ android:pivotY="100"
+ android:rotation="45" >
+ <path
+ android:name="twoLines"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 100, 0 l 0, 100, -100, 0 z"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_2.png
new file mode 100644
index 0000000..d3a5648
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_2.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_2.xml
new file mode 100644
index 0000000..200eb61
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_scale_stroke_2.xml
@@ -0,0 +1,48 @@
+<!--
+ 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:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
+ </group>
+ <group
+ android:scaleX="2"
+ android:scaleY="0.5" >
+ <group
+ android:pivotX="100"
+ android:pivotY="100"
+ android:rotation="45" >
+ <path
+ android:name="twoLines"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 100, 0 l 0, 100, -100, 0 z"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_1.png
new file mode 100644
index 0000000..334b2c6
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_1.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_1.xml
new file mode 100644
index 0000000..af351f1
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_1.xml
@@ -0,0 +1,46 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
+ </group>
+ <group
+ android:translateX="50"
+ android:translateY="50" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,20 l 0 80 l -30 -80"
+ android:fillColor="#FF000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeLineCap="butt"
+ android:strokeLineJoin="miter"
+ android:strokeMiterLimit="6"
+ android:strokeWidth="20" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_2.png
new file mode 100644
index 0000000..1b0b5e7
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_2.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_2.xml
new file mode 100644
index 0000000..f85d5fc
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_2.xml
@@ -0,0 +1,46 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
+ </group>
+ <group
+ android:translateX="50"
+ android:translateY="50" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,20 l 0 80 l -30 -80"
+ android:fillColor="#FF000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeMiterLimit="10"
+ android:strokeWidth="20" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_3.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_3.png
new file mode 100644
index 0000000..bd3d88c
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_3.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_3.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_3.xml
new file mode 100644
index 0000000..8f3d47e
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_stroke_3.xml
@@ -0,0 +1,46 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
+ </group>
+ <group
+ android:translateX="50"
+ android:translateY="50" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,20 l 0 80 l -30 -80"
+ android:fillColor="#FF000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeLineCap="square"
+ android:strokeLineJoin="bevel"
+ android:strokeMiterLimit="10"
+ android:strokeWidth="20" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_1.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_1.png
new file mode 100644
index 0000000..c2eeb1a
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_1.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_1.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_1.xml
new file mode 100644
index 0000000..db6ec19
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_1.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="500"
+ android:viewportWidth="800" >
+
+ <group
+ android:pivotX="90"
+ android:pivotY="100"
+ android:rotation="20">
+ <path
+ android:name="pie2"
+ android:pathData="M200,350 l 50,-25
+ a25,12 -30 0,1 100,-50 l 50,-25
+ a25,25 -30 0,1 100,-50 l 50,-25
+ a25,37 -30 0,1 100,-50 l 50,-25
+ a25,50 -30 0,1 100,-50 l 50,-25"
+ android:fillColor="#00000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_2.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_2.png
new file mode 100644
index 0000000..07ade02
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_2.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_2.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_2.xml
new file mode 100644
index 0000000..87da0bb
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_2.xml
@@ -0,0 +1,48 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ <path
+ android:name="background2"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ </group>
+ <group
+ android:pivotX="100"
+ android:pivotY="100"
+ android:rotation="90"
+ android:scaleX="0.75"
+ android:scaleY="0.5"
+ android:translateX="0.0"
+ android:translateY="100.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,10 v 90 M 10,100 h 90"
+ android:fillColor="#00000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_3.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_3.png
new file mode 100644
index 0000000..e2c96cf
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_3.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_3.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_3.xml
new file mode 100644
index 0000000..fc30af3
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_3.xml
@@ -0,0 +1,48 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200" >
+
+ <group>
+ <path
+ android:name="background1"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ <path
+ android:name="background2"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ </group>
+ <group
+ android:pivotX="0"
+ android:pivotY="0"
+ android:rotation="90"
+ android:scaleX="0.75"
+ android:scaleY="0.5"
+ android:translateX="100.0"
+ android:translateY="100.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,10 v 90 M 10,100 h 90"
+ android:fillColor="#00000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_4.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_4.png
new file mode 100644
index 0000000..0be6d6c
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_4.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_4.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_4.xml
new file mode 100644
index 0000000..5b40d0d
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_4.xml
@@ -0,0 +1,68 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400" >
+
+ <group android:name="backgroundGroup" >
+ <path
+ android:name="background1"
+ android:fillColor="#80000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#80000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FFFF0000"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0" >
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0" >
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_5.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_5.png
new file mode 100644
index 0000000..b032eae
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_5.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_5.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_5.xml
new file mode 100644
index 0000000..4a27754
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_5.xml
@@ -0,0 +1,81 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400" >
+
+ <group android:name="backgroundGroup" >
+ <path
+ android:name="background1"
+ android:fillColor="#80000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#80000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 150 M 0,0 h 150"
+ android:strokeColor="#FFFF0000"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0" >
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0" >
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines3"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+
+ <group
+ android:name="translateGroupHalf"
+ android:translateX="65.0"
+ android:translateY="80.0" >
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_6.png b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_6.png
new file mode 100644
index 0000000..5a09799
Binary files /dev/null and b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_6.png differ
diff --git a/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_6.xml b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_6.xml
new file mode 100644
index 0000000..2c174fb
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/vectordrawable/test_xml_transformation_6.xml
@@ -0,0 +1,85 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400"
+ android:alpha = "0.5" >
+
+ <group android:name="backgroundGroup">
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FFFF0000"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0" >
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="20"
+ android:strokeAlpha="0.5" />
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0">
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines3"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20"
+ android:strokeAlpha="0.25" />
+ </group>
+ </group>
+
+ <group
+ android:name="translateGroupHalf"
+ android:translateX="65.0"
+ android:translateY="80.0">
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20"
+ android:strokeAlpha="0.5" />
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/base/sdklib/.classpath b/sdklib/.classpath
similarity index 100%
rename from base/sdklib/.classpath
rename to sdklib/.classpath
diff --git a/base/sdklib/.gitignore b/sdklib/.gitignore
similarity index 100%
rename from base/sdklib/.gitignore
rename to sdklib/.gitignore
diff --git a/base/sdklib/.project b/sdklib/.project
similarity index 100%
rename from base/sdklib/.project
rename to sdklib/.project
diff --git a/base/sdklib/.settings/org.eclipse.core.resources.prefs b/sdklib/.settings/org.eclipse.core.resources.prefs
similarity index 100%
rename from base/sdklib/.settings/org.eclipse.core.resources.prefs
rename to sdklib/.settings/org.eclipse.core.resources.prefs
diff --git a/base/sdk-common/.settings/org.eclipse.jdt.core.prefs b/sdklib/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/sdk-common/.settings/org.eclipse.jdt.core.prefs
rename to sdklib/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/sdklib/.settings/org.eclipse.jdt.ui.prefs b/sdklib/.settings/org.eclipse.jdt.ui.prefs
similarity index 100%
rename from base/sdklib/.settings/org.eclipse.jdt.ui.prefs
rename to sdklib/.settings/org.eclipse.jdt.ui.prefs
diff --git a/base/build-system/builder/MODULE_LICENSE_APACHE2 b/sdklib/MODULE_LICENSE_APACHE2
similarity index 100%
rename from base/build-system/builder/MODULE_LICENSE_APACHE2
rename to sdklib/MODULE_LICENSE_APACHE2
diff --git a/base/sdk-common/NOTICE b/sdklib/NOTICE
similarity index 100%
rename from base/sdk-common/NOTICE
rename to sdklib/NOTICE
diff --git a/sdklib/build.gradle b/sdklib/build.gradle
new file mode 100644
index 0000000..296bc2a
--- /dev/null
+++ b/sdklib/build.gradle
@@ -0,0 +1,70 @@
+apply plugin: 'java'
+apply plugin: 'jacoco'
+apply plugin: 'sdk-java-lib'
+
+evaluationDependsOn(':base:dvlib')
+
+group = 'com.android.tools'
+archivesBaseName = 'sdklib'
+version = rootProject.ext.baseVersion
+
+dependencies {
+ compile project(':base:layoutlib-api')
+ compile project(':base:dvlib')
+ compile project(':base:repository')
+
+ compile 'com.google.code.gson:gson:2.2.4'
+ compile 'org.apache.commons:commons-compress:1.8.1'
+ compile 'org.apache.httpcomponents:httpclient:4.1.1'
+ compile 'org.apache.httpcomponents:httpmime:4.1'
+
+ testCompile project(':base:dvlib').sourceSets.test.output
+ testCompile 'junit:junit:4.12'
+}
+
+test {
+ testLogging {
+ showStandardStreams = true
+ showStackTraces = true
+ exceptionFormat = "full"
+ }
+}
+
+sourceSets {
+ main.resources.srcDir 'src/main/java'
+ test.resources.srcDir 'src/test/java'
+}
+
+// TODO: needed?
+task copyXsd(type: Copy) {
+ from sourceSets.main.resources.srcDirs
+ include '**/*.xsd'
+
+ into new File(rootProject.buildDir, "repository-xsd")
+ eachFile { details ->
+ details.path = details.name
+ }
+}
+
+// delete the destination folder first
+copyXsd.doFirst {
+ File destFolder = file(rootProject.buildDir + "/repository-xsd")
+ destFolder.deleteDir()
+ destFolder.mkdirs()
+}
+
+// clean up after the copy task which creates empty folders.
+copyXsd.doLast {
+ File destFolder = file(rootProject.buildDir + "/repository-xsd/com")
+ destFolder.deleteDir()
+}
+
+//packageJavaLib.dependsOn copyXsd
+
+project.ext.pomName = 'Android Tools sdklib'
+project.ext.pomDesc = 'A library to parse and download the Android SDK.'
+
+apply from: "$rootDir/buildSrc/base/publish.gradle"
+apply from: "$rootDir/buildSrc/base/bintray.gradle"
+apply from: "$rootDir/buildSrc/base/javadoc.gradle"
+
diff --git a/sdklib/sdklib-base.iml b/sdklib/sdklib-base.iml
new file mode 100644
index 0000000..7b1cdb4
--- /dev/null
+++ b/sdklib/sdklib-base.iml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="module" module-name="common" exported="" />
+ <orderEntry type="module" module-name="dvlib" exported="" />
+ <orderEntry type="module" module-name="layoutlib-api-base" exported="" />
+ <orderEntry type="library" exported="" name="http-client" level="project" />
+ <orderEntry type="library" exported="" name="commons-compress" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" exported="" name="gson" level="project" />
+ <orderEntry type="module" module-name="repository" exported="" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/sdklib/sdklib.iml b/sdklib/sdklib.iml
new file mode 100644
index 0000000..da27b8a
--- /dev/null
+++ b/sdklib/sdklib.iml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="module" module-name="common" exported="" />
+ <orderEntry type="module" module-name="dvlib" exported="" />
+ <orderEntry type="module" module-name="layoutlib-api" exported="" />
+ <orderEntry type="library" exported="" name="http-client" level="project" />
+ <orderEntry type="library" exported="" name="commons-compress" level="project" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" exported="" name="gson" level="project" />
+ <orderEntry type="module" module-name="repository" exported="" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java b/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
new file mode 100644
index 0000000..f45a8c1
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.ProgressIndicator;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.targets.AndroidTargetManager;
+import com.google.common.base.Splitter;
+
+import java.util.List;
+
+
+/**
+ * Helper methods to manipulate hash strings used by {@link IAndroidTarget#hashString()}.
+ */
+public abstract class AndroidTargetHash {
+
+ /**
+ * Prefix used to build hash strings for platform targets
+ * @see AndroidTargetManager#getTargetFromHashString(String, ProgressIndicator)
+ */
+ public static final String PLATFORM_HASH_PREFIX = "android-";
+
+ /**
+ * String to compute hash for add-on targets. <br/>
+ * Format is {@code vendor:name:apiVersion}. <br/>
+ *
+ * <em>Important<em/>: the vendor and name compontents are the display strings, not the
+ * newer id strings.
+ */
+ public static final String ADD_ON_FORMAT = "%s:%s:%s"; //$NON-NLS-1$
+
+ /**
+ * String used to get a hash to the platform target.
+ * This format is compatible with the PlatformPackage.installId().
+ */
+ static final String PLATFORM_HASH = PLATFORM_HASH_PREFIX + "%s";
+
+ /**
+ * Returns the hash string for a given platform version.
+ *
+ * @param version A non-null platform version.
+ * @return A non-null hash string uniquely representing this platform target.
+ */
+ @NonNull
+ public static String getPlatformHashString(@NonNull AndroidVersion version) {
+ return String.format(AndroidTargetHash.PLATFORM_HASH, version.getApiString());
+ }
+
+ /**
+ * Returns the {@link AndroidVersion} for the given hash string,
+ * if it represents a platform. If the hash string represents a preview platform,
+ * the returned {@link AndroidVersion} will have an unknown API level (set to 1
+ * or a known matching API level.)
+ *
+ * @param hashString the hash string (e.g. "android-19" or "android-CUPCAKE")
+ * or a pure API level for convenience (e.g. "19" instead of the proper "android-19")
+ * @return a platform, or null
+ */
+ @Nullable
+ public static AndroidVersion getPlatformVersion(@NonNull String hashString) {
+ if (hashString.startsWith(PLATFORM_HASH_PREFIX)) {
+ String suffix = hashString.substring(PLATFORM_HASH_PREFIX.length());
+ if (!suffix.isEmpty()) {
+ if (Character.isDigit(suffix.charAt(0))) {
+ try {
+ int api = Integer.parseInt(suffix);
+ return new AndroidVersion(api, null);
+ } catch (NumberFormatException ignore) {}
+ } else {
+ // Note: getApiByPreviewName returns the api level for a build code,
+ // but it doesn't know whether a build code is in preview or not.
+ // Here, we make use of the knowledge that we are actually constructing a preview version,
+ // so this is the feature level:
+ int apiFeatureLevel = SdkVersionInfo.getApiByPreviewName(suffix, true);
+ int apiLevel = apiFeatureLevel - 1;
+ if (apiLevel < 1) {
+ apiLevel = 1;
+ }
+ return new AndroidVersion(apiLevel, suffix);
+ }
+ }
+ } else if (!hashString.isEmpty() && Character.isDigit(hashString.charAt(0))) {
+ // For convenience, interpret a single integer as the proper "android-NN" form.
+ try {
+ int api = Integer.parseInt(hashString);
+ return new AndroidVersion(api, null);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public static AndroidVersion getAddOnVersion(@NonNull String hashString) {
+ List<String> parts = Splitter.on(':').splitToList(hashString);
+ if (parts.size() != 3) {
+ return null;
+ }
+
+ String apiLevelPart = parts.get(2);
+ try {
+ int apiLevel = Integer.parseInt(apiLevelPart);
+ return new AndroidVersion(apiLevel, null);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the API level from a hash string, either a platform version or add-on version.
+ *
+ * @see #getAddOnVersion(String)
+ * @see #getPlatformVersion(String)
+ */
+ @Nullable
+ public static AndroidVersion getVersionFromHash(@NonNull String hashString) {
+ if (isPlatform(hashString)) {
+ return getPlatformVersion(hashString);
+ } else {
+ return getAddOnVersion(hashString);
+ }
+ }
+
+ /**
+ * Returns the hash string for a given add-on.
+ *
+ * @param addonVendorDisplay A non-null vendor. When using an {@link IdDisplay} source,
+ * this parameter should be the {@link IdDisplay#getDisplay()}.
+ * @param addonNameDisplay A non-null add-on name. When using an {@link IdDisplay} source,
+ * this parameter should be the {@link IdDisplay#getDisplay()}.
+ * @param version A non-null platform version (the addon's base platform version)
+ * @return A non-null hash string uniquely representing this add-on target.
+ */
+ public static String getAddonHashString(
+ @NonNull String addonVendorDisplay,
+ @NonNull String addonNameDisplay,
+ @NonNull AndroidVersion version) {
+ return String.format(ADD_ON_FORMAT,
+ addonVendorDisplay,
+ addonNameDisplay,
+ version.getApiString());
+ }
+
+ /**
+ * Returns the hash string for a given target (add-on or platform.)
+ *
+ * @param target A non-null target.
+ * @return A non-null hash string uniquely representing this target.
+ */
+ public static String getTargetHashString(@NonNull IAndroidTarget target) {
+ if (target.isPlatform()) {
+ return getPlatformHashString(target.getVersion());
+ } else {
+ return getAddonHashString(
+ target.getVendor(),
+ target.getName(),
+ target.getVersion());
+ }
+ }
+
+ /**
+ * Given a hash string, indicates whether this is a platform hash string.
+ * If not, it's an addon hash string.
+ *
+ * @param hashString The hash string to test.
+ * @return True if this hash string starts by the platform prefix.
+ */
+ public static boolean isPlatform(@NonNull String hashString) {
+ return hashString.startsWith(PLATFORM_HASH_PREFIX);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/AndroidVersionHelper.java b/sdklib/src/main/java/com/android/sdklib/AndroidVersionHelper.java
new file mode 100644
index 0000000..c4b4f7b
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/AndroidVersionHelper.java
@@ -0,0 +1,82 @@
+/*
+ * 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.sdklib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.Properties;
+
+public class AndroidVersionHelper {
+
+ /**
+ * Creates an {@link AndroidVersion} from {@link Properties}, with default values if the {@link
+ * Properties} object doesn't contain the expected values. <p/>The {@link Properties} is
+ * expected to have been filled with {@link #saveProperties(AndroidVersion, Properties)}.
+ */
+ @NonNull
+ public static AndroidVersion create(@Nullable Properties properties, int defaultApiLevel,
+ @Nullable String defaultCodeName) {
+ if (properties == null) {
+ return new AndroidVersion(defaultApiLevel, defaultCodeName);
+ } else {
+ int api = Integer.parseInt(properties.getProperty(PkgProps.VERSION_API_LEVEL,
+ Integer.toString(defaultApiLevel)));
+ String codeName = properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName);
+ return new AndroidVersion(api, codeName);
+ }
+ }
+
+ /**
+ * Creates an {@link AndroidVersion} from {@link Properties}. The properties must contain
+ * android version information, or an exception will be thrown.
+ *
+ * @throws AndroidVersion.AndroidVersionException if no Android version information have been
+ * found
+ * @see #saveProperties(AndroidVersion, Properties)
+ */
+ @NonNull
+ public static AndroidVersion create(@NonNull Properties properties)
+ throws AndroidVersion.AndroidVersionException {
+ Exception error = null;
+
+ String apiLevel = properties.getProperty(PkgProps.VERSION_API_LEVEL, null/*defaultValue*/);
+ if (apiLevel != null) {
+ try {
+ int api = Integer.parseInt(apiLevel);
+ String codeName = properties.getProperty(PkgProps.VERSION_CODENAME,
+ null/*defaultValue*/);
+ return new AndroidVersion(api, codeName);
+ } catch (NumberFormatException e) {
+ error = e;
+ }
+ }
+
+ // reaching here means the Properties object did not contain the apiLevel which is required.
+ throw new AndroidVersion.AndroidVersionException(PkgProps.VERSION_API_LEVEL + " not found!",
+ error);
+ }
+
+ public static void saveProperties(@NonNull AndroidVersion version, @NonNull Properties props) {
+ props.setProperty(PkgProps.VERSION_API_LEVEL, Integer.toString(version.getApiLevel()));
+ String codeName = version.getCodename();
+ if (codeName != null) {
+ props.setProperty(PkgProps.VERSION_CODENAME, codeName);
+ }
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java b/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
new file mode 100644
index 0000000..f702ca9
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import static com.android.SdkConstants.FD_LIB;
+import static com.android.SdkConstants.FN_AAPT;
+import static com.android.SdkConstants.FN_AIDL;
+import static com.android.SdkConstants.FN_BCC_COMPAT;
+import static com.android.SdkConstants.FN_DEXDUMP;
+import static com.android.SdkConstants.FN_DX;
+import static com.android.SdkConstants.FN_DX_JAR;
+import static com.android.SdkConstants.FN_JACK;
+import static com.android.SdkConstants.FN_JILL;
+import static com.android.SdkConstants.FN_LD_ARM;
+import static com.android.SdkConstants.FN_LD_MIPS;
+import static com.android.SdkConstants.FN_LD_X86;
+import static com.android.SdkConstants.FN_RENDERSCRIPT;
+import static com.android.SdkConstants.FN_SPLIT_SELECT;
+import static com.android.SdkConstants.FN_ZIPALIGN;
+import static com.android.SdkConstants.OS_FRAMEWORK_RS;
+import static com.android.SdkConstants.OS_FRAMEWORK_RS_CLANG;
+import static com.android.sdklib.BuildToolInfo.PathId.AAPT;
+import static com.android.sdklib.BuildToolInfo.PathId.AIDL;
+import static com.android.sdklib.BuildToolInfo.PathId.ANDROID_RS;
+import static com.android.sdklib.BuildToolInfo.PathId.ANDROID_RS_CLANG;
+import static com.android.sdklib.BuildToolInfo.PathId.BCC_COMPAT;
+import static com.android.sdklib.BuildToolInfo.PathId.DEXDUMP;
+import static com.android.sdklib.BuildToolInfo.PathId.DX;
+import static com.android.sdklib.BuildToolInfo.PathId.DX_JAR;
+import static com.android.sdklib.BuildToolInfo.PathId.JACK;
+import static com.android.sdklib.BuildToolInfo.PathId.JILL;
+import static com.android.sdklib.BuildToolInfo.PathId.LD_ARM;
+import static com.android.sdklib.BuildToolInfo.PathId.LD_MIPS;
+import static com.android.sdklib.BuildToolInfo.PathId.LD_X86;
+import static com.android.sdklib.BuildToolInfo.PathId.LLVM_RS_CC;
+import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT;
+import static com.android.sdklib.BuildToolInfo.PathId.ZIP_ALIGN;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.repository.io.FileOp;
+import com.android.repository.Revision;
+import com.android.repository.io.FileOpUtils;
+import com.android.utils.ILogger;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Information on a specific build-tool folder.
+ * <p/>
+ * For unit tests, see:
+ * - sdklib/src/test/.../LocalSdkTest
+ * - sdklib/src/test/.../SdkManagerTest
+ * - sdklib/src/test/.../BuildToolInfoTest
+ */
+public class BuildToolInfo {
+
+ /** Name of the file read by {@link #getRuntimeProps()} */
+ private static final String FN_RUNTIME_PROPS = "runtime.properties";
+
+ /**
+ * Property in {@link #FN_RUNTIME_PROPS} indicating the desired runtime JVM.
+ * Type: {@link Revision#toShortString()}, e.g. "1.7.0"
+ */
+ private static final String PROP_RUNTIME_JVM = "Runtime.Jvm";
+
+ /**
+ * First version with native multi-dex support.
+ */
+ public static final int SDK_LEVEL_FOR_MULTIDEX_NATIVE_SUPPORT = 21;
+
+ public enum PathId {
+ /** OS Path to the target's version of the aapt tool. */
+ AAPT("1.0.0"),
+ /** OS Path to the target's version of the aidl tool. */
+ AIDL("1.0.0"),
+ /** OS Path to the target's version of the dx tool. */
+ DX("1.0.0"),
+ /** OS Path to the target's version of the dx.jar file. */
+ DX_JAR("1.0.0"),
+ /** OS Path to the llvm-rs-cc binary for Renderscript. */
+ LLVM_RS_CC("1.0.0"),
+ /** OS Path to the Renderscript include folder. */
+ ANDROID_RS("1.0.0"),
+ /** OS Path to the Renderscript(clang) include folder. */
+ ANDROID_RS_CLANG("1.0.0"),
+
+ DEXDUMP("1.0.0"),
+
+ // --- NEW IN 18.1.0 ---
+
+ /** OS Path to the bcc_compat tool. */
+ BCC_COMPAT("18.1.0"),
+ /** OS Path to the ARM linker. */
+ LD_ARM("18.1.0"),
+ /** OS Path to the X86 linker. */
+ LD_X86("18.1.0"),
+ /** OS Path to the MIPS linker. */
+ LD_MIPS("18.1.0"),
+
+ // --- NEW IN 19.1.0 ---
+ ZIP_ALIGN("19.1.0"),
+
+ // --- NEW IN 21.x.y ---
+ JACK("21.1.0"),
+ JILL("21.1.0"),
+
+ SPLIT_SELECT("22.0.0");
+
+ /**
+ * min revision this element was introduced.
+ * Controls {@link BuildToolInfo#isValid(ILogger)}
+ */
+ private final Revision mMinRevision;
+
+ /**
+ * Creates the enum with a min revision in which this
+ * tools appeared in the build tools.
+ *
+ * @param minRevision the min revision.
+ */
+ PathId(@NonNull String minRevision) {
+ mMinRevision = Revision.parseRevision(minRevision);
+ }
+
+ /**
+ * Returns whether the enum of present in a given rev of the build tools.
+ *
+ * @param revision the build tools revision.
+ * @return true if the tool is present.
+ */
+ boolean isPresentIn(@NonNull Revision revision) {
+ return revision.compareTo(mMinRevision) >= 0;
+ }
+ }
+
+ /** The build-tool revision. */
+ @NonNull
+ private final Revision mRevision;
+
+ /** The path to the build-tool folder specific to this revision. */
+ @NonNull
+ private final File mPath;
+
+ private final Map<PathId, String> mPaths = Maps.newEnumMap(PathId.class);
+
+ public BuildToolInfo(@NonNull Revision revision, @NonNull File path) {
+ mRevision = revision;
+ mPath = path;
+
+ add(AAPT, FN_AAPT);
+ add(AIDL, FN_AIDL);
+ add(DX, FN_DX);
+ add(DX_JAR, FD_LIB + File.separator + FN_DX_JAR);
+ add(LLVM_RS_CC, FN_RENDERSCRIPT);
+ add(ANDROID_RS, OS_FRAMEWORK_RS);
+ add(ANDROID_RS_CLANG, OS_FRAMEWORK_RS_CLANG);
+ add(DEXDUMP, FN_DEXDUMP);
+ add(BCC_COMPAT, FN_BCC_COMPAT);
+ add(LD_ARM, FN_LD_ARM);
+ add(LD_X86, FN_LD_X86);
+ add(LD_MIPS, FN_LD_MIPS);
+ add(ZIP_ALIGN, FN_ZIPALIGN);
+ add(JACK, FN_JACK);
+ add(JILL, FN_JILL);
+ add(SPLIT_SELECT, FN_SPLIT_SELECT);
+ }
+
+ public BuildToolInfo(
+ @NonNull Revision revision,
+ @NonNull File mainPath,
+ @NonNull File aapt,
+ @NonNull File aidl,
+ @NonNull File dx,
+ @NonNull File dxJar,
+ @NonNull File llmvRsCc,
+ @NonNull File androidRs,
+ @NonNull File androidRsClang,
+ @Nullable File bccCompat,
+ @Nullable File ldArm,
+ @Nullable File ldX86,
+ @Nullable File ldMips,
+ @NonNull File zipAlign) {
+ mRevision = revision;
+ mPath = mainPath;
+ add(AAPT, aapt);
+ add(AIDL, aidl);
+ add(DX, dx);
+ add(DX_JAR, dxJar);
+ add(LLVM_RS_CC, llmvRsCc);
+ add(ANDROID_RS, androidRs);
+ add(ANDROID_RS_CLANG, androidRsClang);
+ add(ZIP_ALIGN, zipAlign);
+
+ if (bccCompat != null) {
+ add(BCC_COMPAT, bccCompat);
+ } else if (BCC_COMPAT.isPresentIn(revision)) {
+ throw new IllegalArgumentException("BCC_COMPAT required in " + revision.toString());
+ }
+ if (ldArm != null) {
+ add(LD_ARM, ldArm);
+ } else if (LD_ARM.isPresentIn(revision)) {
+ throw new IllegalArgumentException("LD_ARM required in " + revision.toString());
+ }
+
+ if (ldX86 != null) {
+ add(LD_X86, ldX86);
+ } else if (LD_X86.isPresentIn(revision)) {
+ throw new IllegalArgumentException("LD_X86 required in " + revision.toString());
+ }
+
+ if (ldMips != null) {
+ add(LD_MIPS, ldMips);
+ } else if (LD_MIPS.isPresentIn(revision)) {
+ throw new IllegalArgumentException("LD_MIPS required in " + revision.toString());
+ }
+ }
+
+ private void add(PathId id, String leaf) {
+ add(id, new File(mPath, leaf));
+ }
+
+ private void add(PathId id, File path) {
+ String str = path.getAbsolutePath();
+ if (path.isDirectory() && str.charAt(str.length() - 1) != File.separatorChar) {
+ str += File.separatorChar;
+ }
+ mPaths.put(id, str);
+ }
+
+ /**
+ * Returns the revision.
+ */
+ @NonNull
+ public Revision getRevision() {
+ return mRevision;
+ }
+
+ /**
+ * Returns the build-tool revision-specific folder.
+ * <p/>
+ * For compatibility reasons, use {@link #getPath(PathId)} if you need the path to a
+ * specific tool.
+ */
+ @NonNull
+ public File getLocation() {
+ return mPath;
+ }
+
+ /**
+ * Returns the path of a build-tool component.
+ *
+ * @param pathId the id representing the path to return.
+ * @return The absolute path for that tool, with a / separator if it's a folder.
+ * Null if the path-id is unknown.
+ */
+ public String getPath(PathId pathId) {
+ assert pathId.isPresentIn(mRevision);
+
+ return mPaths.get(pathId);
+ }
+
+ /**
+ * Checks whether the build-tool is valid by verifying that the expected binaries
+ * are actually present. This checks that all known paths point to a valid file
+ * or directory.
+ *
+ * @param log An optional logger. If non-null, errors will be printed there.
+ * @return True if the build-tool folder contains all the expected tools.
+ */
+ public boolean isValid(@Nullable ILogger log) {
+ for (Map.Entry<PathId, String> entry : mPaths.entrySet()) {
+ File f = new File(entry.getValue());
+ // check if file is missing. It's only ok if the revision of the build-tools
+ // is lower than the min rev of the element.
+ if (!f.exists() && entry.getKey().isPresentIn(mRevision)) {
+ if (log != null) {
+ log.warning("Build-tool %1$s is missing %2$s at %3$s", //$NON-NLS-1$
+ mRevision.toString(),
+ entry.getKey(), f.getAbsolutePath());
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Parses the build-tools runtime.props file, if present.
+ *
+ * @return The properties from runtime.props if present, otherwise an empty properties set.
+ */
+ @NonNull
+ public Properties getRuntimeProps() {
+ FileOp fop = FileOpUtils.create();
+ return fop.loadProperties(new File(mPath, FN_RUNTIME_PROPS));
+ }
+
+ /**
+ * Checks whether this build-tools package can run on the current JVM.
+ *
+ * @return True if the build-tools package has a Runtime.Jvm property and it is lesser or
+ * equal to the current JVM version.
+ * False if the property is present and the requirement is not met.
+ * True if there's an error parsing either versions and the comparison cannot be made.
+ */
+ public boolean canRunOnJvm() {
+ Properties props = getRuntimeProps();
+ String required = props.getProperty(PROP_RUNTIME_JVM);
+ if (required == null) {
+ // No requirement ==> accepts.
+ return true;
+ }
+ try {
+ Revision requiredVersion = Revision.parseRevision(required);
+ Revision currentVersion = getCurrentJvmVersion();
+ return currentVersion.compareTo(requiredVersion) >= 0;
+
+ } catch (NumberFormatException ignore) {
+ // Either we failed to parse the property version or the running JVM version.
+ // Right now take the relaxed policy of accepting it if we can't compare.
+ return true;
+ }
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ @Nullable
+ protected Revision getCurrentJvmVersion() throws NumberFormatException {
+ String javav = System.getProperty("java.version"); //$NON-NLS-1$
+ // java Version is typically in the form "1.2.3_45" and we just need to keep up to "1.2.3"
+ // since our revision numbers are in 3-parts form (1.2.3).
+ Pattern p = Pattern.compile("((\\d+)(\\.\\d+)?(\\.\\d+)?).*"); //$NON-NLS-1$
+ Matcher m = p.matcher(javav);
+ if (m.matches()) {
+ return Revision.parseRevision(m.group(1));
+ }
+ return null;
+ }
+
+ /**
+ * Returns a debug representation suitable for unit-tests.
+ * Note that unit-tests need to clean up the paths to avoid inconsistent results.
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<BuildToolInfo rev=").append(mRevision); //$NON-NLS-1$
+ builder.append(", mPath=").append(mPath); //$NON-NLS-1$
+ builder.append(", mPaths=").append(getPathString()); //$NON-NLS-1$
+ builder.append(">"); //$NON-NLS-1$
+ return builder.toString();
+ }
+
+ private String getPathString() {
+ StringBuilder sb = new StringBuilder("{");
+
+ for (Map.Entry<PathId, String> entry : mPaths.entrySet()) {
+ if (entry.getKey().isPresentIn(mRevision)) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append(entry.getKey()).append('=').append(entry.getValue());
+ }
+ }
+
+ sb.append('}');
+
+ return sb.toString();
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/FileOpFileWrapper.java b/sdklib/src/main/java/com/android/sdklib/FileOpFileWrapper.java
new file mode 100644
index 0000000..4e38613
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/FileOpFileWrapper.java
@@ -0,0 +1,161 @@
+/*
+ * 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.sdklib;
+
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.io.IAbstractResource;
+import com.android.io.StreamException;
+import com.android.repository.io.FileOp;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * An {@link IAbstractFile} or {@link IAbstractFolder} that wraps a {@link File} and uses a
+ * {@link FileOp} for file operations, to allow mocking.
+ */
+public class FileOpFileWrapper implements IAbstractFile, IAbstractFolder {
+
+ private final FileOp mFileOp;
+ private final File mFile;
+ private final boolean mIsFolder;
+
+ public FileOpFileWrapper(File file, FileOp fop, boolean isFolder) {
+ mFile = file;
+ mFileOp = fop;
+ mIsFolder = isFolder;
+ }
+
+ @Override
+ public InputStream getContents() throws StreamException {
+ try {
+ return mFileOp.newFileInputStream(mFile);
+ } catch (FileNotFoundException e) {
+ throw new StreamException(e, this);
+ }
+ }
+
+ @Override
+ public void setContents(InputStream source) throws StreamException {
+ OutputStream fos = null;
+ try {
+ fos = mFileOp.newFileOutputStream(mFile);
+
+ byte[] buffer = new byte[1024];
+ int count = 0;
+ while ((count = source.read(buffer)) != -1) {
+ fos.write(buffer, 0, count);
+ }
+ } catch (IOException e) {
+ throw new StreamException(e, this);
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ throw new StreamException(e, this);
+ }
+ }
+ }
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws StreamException {
+ try {
+ return mFileOp.newFileOutputStream(mFile);
+ } catch (FileNotFoundException ex) {
+ throw new StreamException(ex, this);
+ }
+ }
+
+ @Override
+ public PreferredWriteMode getPreferredWriteMode() {
+ return PreferredWriteMode.OUTPUTSTREAM;
+ }
+
+ @Override
+ public long getModificationStamp() {
+ return mFile.lastModified();
+ }
+
+ @Override
+ public String getName() {
+ return mFile.getName();
+ }
+
+ @Override
+ public String getOsLocation() {
+ return mFile.getAbsolutePath();
+ }
+
+ @Override
+ public boolean exists() {
+ return mIsFolder ? mFileOp.isDirectory(mFile) : mFileOp.isFile(mFile);
+ }
+
+ @Override
+ public IAbstractFolder getParentFolder() {
+ return new FileOpFileWrapper(mFile.getParentFile(), mFileOp, true);
+ }
+
+ @Override
+ public boolean delete() {
+ return mFileOp.delete(mFile);
+ }
+
+ @Override
+ public boolean hasFile(String name) {
+ return false;
+ }
+
+ @Override
+ public IAbstractFile getFile(String name) {
+ return new FileOpFileWrapper(new File(mFile, name), mFileOp, false);
+ }
+
+ @Override
+ public IAbstractFolder getFolder(String name) {
+ return new FileOpFileWrapper(new File(mFile, name), mFileOp, true);
+ }
+
+ @Override
+ public IAbstractResource[] listMembers() {
+ File[] files = mFileOp.listFiles(mFile);
+ final int count = files.length;
+ IAbstractResource[] afiles = new IAbstractResource[count];
+
+ for (int i = 0 ; i < count ; i++) {
+ File f = files[i];
+ afiles[i] = new FileOpFileWrapper(f, mFileOp, f.isDirectory());
+ }
+
+ return afiles;
+ }
+
+ @Override
+ public String[] list(final FilenameFilter filter) {
+ return mFileOp.list(mFile, new java.io.FilenameFilter() {
+ @Override
+ public boolean accept(File file, String s) {
+ return filter.accept(new FileOpFileWrapper(file, mFileOp, true), s);
+ }
+ });
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java b/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java
new file mode 100644
index 0000000..742b90b
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/IAndroidTarget.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2008 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.sdklib;
+
+import static com.android.sdklib.repositoryv2.meta.DetailsTypes.AddonDetailsType;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.LocalPackage;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+
+
+/**
+ * A version of Android that applications can target when building.
+ */
+public interface IAndroidTarget extends Comparable<IAndroidTarget> {
+
+ /** OS Path to the "android.jar" file. */
+ int ANDROID_JAR = 1;
+ /** OS Path to the "framework.aidl" file. */
+ int ANDROID_AIDL = 2;
+ /** OS Path to the "samples" folder which contains sample projects. */
+ int SAMPLES = 4;
+ /** OS Path to the "skins" folder which contains the emulator skins. */
+ int SKINS = 5;
+ /** OS Path to the "templates" folder which contains the templates for new projects. */
+ int TEMPLATES = 6;
+ /** OS Path to the "data" folder which contains data & libraries for the SDK tools. */
+ int DATA = 7;
+ /** OS Path to the "attrs.xml" file. */
+ int ATTRIBUTES = 8;
+ /** OS Path to the "attrs_manifest.xml" file. */
+ int MANIFEST_ATTRIBUTES = 9;
+ /** OS Path to the "data/layoutlib.jar" library. */
+ int LAYOUT_LIB = 10;
+ /** OS Path to the "data/res" folder. */
+ int RESOURCES = 11;
+ /** OS Path to the "data/fonts" folder. */
+ int FONTS = 12;
+ /** OS Path to the "data/widgets.txt" file. */
+ int WIDGETS = 13;
+ /** OS Path to the "data/activity_actions.txt" file. */
+ int ACTIONS_ACTIVITY = 14;
+ /** OS Path to the "data/broadcast_actions.txt" file. */
+ int ACTIONS_BROADCAST = 15;
+ /** OS Path to the "data/service_actions.txt" file. */
+ int ACTIONS_SERVICE = 16;
+ /** OS Path to the "data/categories.txt" file. */
+ int CATEGORIES = 17;
+ /** OS Path to the "sources" folder. */
+ int SOURCES = 18;
+ /** OS Path to the target specific docs */
+ int DOCS = 19;
+ /** OS Path to the "ant" folder which contains the ant build rules (ver 2 and above) */
+ int ANT = 24;
+ /** OS Path to the "uiautomator.jar" file. */
+ int UI_AUTOMATOR_JAR = 27;
+
+
+ /** An optional library provided by an Android Target */
+ interface OptionalLibrary {
+ /** The name of the library, as used in the manifest (<uses-library>). */
+ @NonNull
+ String getName();
+
+ /**
+ * Location of the jar file. Should never be {@code null} when retrieved from a target,
+ * but may be in some cases when retrieved from an {@link AddonDetailsType}.
+ */
+ @Nullable
+ File getJar();
+ /** Description of the library. */
+ @NonNull
+ String getDescription();
+ /** Whether the library requires a manifest entry */
+ boolean isManifestEntryRequired();
+
+ /**
+ * Path to the library jar file relative to the {@code libs} directory in the package.
+ * Can be {@code null} when retrieved from a {@link LocalPackage} that was installed from
+ * a legacy source.
+ */
+ @Nullable
+ String getLocalJarPath();
+ }
+
+ /**
+ * Returns the target location.
+ */
+ String getLocation();
+
+ /**
+ * Returns the name of the vendor of the target.
+ */
+ String getVendor();
+
+ /**
+ * Returns the name of the target.
+ */
+ String getName();
+
+ /**
+ * Returns the full name of the target, possibly including vendor name.
+ */
+ String getFullName();
+
+ /**
+ * Returns the name to be displayed when representing all the libraries this target contains.
+ */
+ String getClasspathName();
+
+ /**
+ * Returns the name to be displayed when representing all the libraries this target contains.
+ */
+ String getShortClasspathName();
+
+ /**
+ * Returns the description of the target.
+ */
+ String getDescription();
+
+ /**
+ * Returns the version of the target. This is guaranteed to be non-null.
+ */
+ @NonNull
+ AndroidVersion getVersion();
+
+ /**
+ * Returns the platform version as a readable string.
+ */
+ String getVersionName();
+
+ /** Returns the revision number for the target. */
+ int getRevision();
+
+ /**
+ * Returns true if the target is a standard Android platform.
+ */
+ boolean isPlatform();
+
+ /**
+ * Returns the parent target. This is likely to only be non <code>null</code> if
+ * {@link #isPlatform()} returns <code>false</code>
+ */
+ IAndroidTarget getParent();
+
+ /**
+ * Returns the path of a platform component.
+ * @param pathId the id representing the path to return.
+ * Any of the constants defined in the {@link IAndroidTarget} interface can be used.
+ */
+ String getPath(int pathId);
+
+ /**
+ * Returns the path of a platform component.
+ * <p/>
+ * This is like the legacy {@link #getPath(int)} method except it returns a {@link File}.
+ *
+ * @param pathId the id representing the path to return.
+ * Any of the constants defined in the {@link IAndroidTarget} interface can be used.
+ */
+ File getFile(int pathId);
+
+ /**
+ * Returns a BuildToolInfo for backward compatibility. If an older SDK is used this will return
+ * paths located in the platform-tools, otherwise it'll return paths located in the latest
+ * build-tools.
+ * @return a BuildToolInfo or null if none are available.
+ */
+ BuildToolInfo getBuildToolInfo();
+
+ /**
+ * Returns the boot classpath for this target.
+ * In most case, this is similar to calling {@link #getPath(int)} with
+ * {@link IAndroidTarget#ANDROID_JAR}.
+ *
+ * @return a non null list of the boot classpath.
+ */
+ @NonNull
+ List<String> getBootClasspath();
+
+ /**
+ * Returns a list of optional libraries for this target.
+ *
+ * These libraries are not automatically added to the classpath.
+ * Using them requires adding a <code>uses-library</code> entry in the manifest.
+ *
+ * @return a list of libraries.
+ *
+ * @see OptionalLibrary#getName()
+ */
+ @NonNull
+ List<OptionalLibrary> getOptionalLibraries();
+
+ /**
+ * Returns the additional libraries for this target.
+ *
+ * These libraries are automatically added to the classpath, but using them requires
+ * adding a <code>uses-library</code> entry in the manifest.
+ *
+ * @return a list of libraries.
+ *
+ * @see OptionalLibrary#getName()
+ */
+ @NonNull
+ List<OptionalLibrary> getAdditionalLibraries();
+
+ /**
+ * Returns whether the target is able to render layouts.
+ */
+ boolean hasRenderingLibrary();
+
+ /**
+ * Returns the available skin folders for this target.
+ * <p/>
+ * To get the skin names, use {@link File#getName()}. <br/>
+ * Skins come either from:
+ * <ul>
+ * <li>a platform ({@code sdk/platforms/N/skins/name})</li>
+ * <li>an add-on ({@code sdk/addons/name/skins/name})</li>
+ * <li>a tagged system-image ({@code sdk/system-images/platform-N/tag/abi/skins/name}.)</li>
+ * </ul>
+ * The array can be empty but not null.
+ */
+ @NonNull
+ File[] getSkins();
+
+ /**
+ * Returns the default skin folder for this target.
+ * <p/>
+ * To get the skin name, use {@link File#getName()}.
+ */
+ @Nullable
+ File getDefaultSkin();
+
+ /**
+ * Returns the list of libraries available for a given platform.
+ *
+ * @return an array of libraries provided by the platform or <code>null</code> if there is none.
+ */
+ String[] getPlatformLibraries();
+
+ /**
+ * Return the value of a given property for this target.
+ * @return the property value or <code>null</code> if it was not found.
+ */
+ String getProperty(String name);
+
+ /**
+ * Returns all the properties associated with this target. This can be null if the target has
+ * no properties.
+ */
+ Map<String, String> getProperties();
+
+ /**
+ * Returns whether the given target is compatible with the receiver.
+ * <p/>
+ * This means that a project using the receiver's target can run on the given target.
+ * <br/>
+ * Example:
+ * <pre>
+ * CupcakeTarget.canRunOn(DonutTarget) == true
+ * </pre>.
+ *
+ * @param target the IAndroidTarget to test.
+ */
+ boolean canRunOn(IAndroidTarget target);
+
+ /**
+ * Returns a string able to uniquely identify a target.
+ * Typically the target will encode information such as api level, whether it's a platform
+ * or add-on, and if it's an add-on vendor and add-on name.
+ * <p/>
+ * See {@link AndroidTargetHash} for helper methods to manipulate hash strings.
+ */
+ String hashString();
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/ISystemImage.java b/sdklib/src/main/java/com/android/sdklib/ISystemImage.java
new file mode 100644
index 0000000..edda820
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/ISystemImage.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.sdklib.devices.Abi;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+import java.io.File;
+
+
+/**
+ * Describes a system image as used by an {@link IAndroidTarget}.
+ * A system image has an installation path, a location type and an ABI type.
+ */
+public interface ISystemImage extends Comparable<ISystemImage> {
+
+ /** Indicates the type of location for the system image folder in the SDK. */
+ enum LocationType {
+ /**
+ * The system image is located in the legacy platform's {@link SdkConstants#FD_IMAGES}
+ * folder.
+ * <p/>
+ * Used by both platform and add-ons.
+ */
+ IN_LEGACY_FOLDER,
+
+ /**
+ * The system image is located in a sub-directory of the platform's
+ * {@link SdkConstants#FD_IMAGES} folder, allowing for multiple system
+ * images within the platform.
+ * <p/>
+ * Used by both platform and add-ons.
+ */
+ IN_IMAGES_SUBFOLDER,
+
+ /**
+ * The system image is located in the new SDK's {@link SdkConstants#FD_SYSTEM_IMAGES}
+ * folder. Supported as of Tools R14 and Repository XSD version 5.
+ * <p/>
+ * Used <em>only</em> by both platform up to Tools R22.6.
+ * Supported for add-ons as of Tools R22.8.
+ */
+ IN_SYSTEM_IMAGE,
+ }
+
+ /** Returns the actual location of an installed system image. */
+ @NonNull
+ File getLocation();
+
+ /** Returns the tag of the system image. */
+ @NonNull
+ IdDisplay getTag();
+
+ /** Returns the vendor for an add-on's system image, or null for a platform system-image. */
+ @Nullable
+ IdDisplay getAddonVendor();
+
+ /**
+ * Returns the ABI type.
+ * See {@link Abi} for a full list.
+ * Cannot be null nor empty.
+ */
+ @NonNull
+ String getAbiType();
+
+ /**
+ * Returns the skins embedded in the system image. <br/>
+ * Only supported by system images using {@link LocationType#IN_SYSTEM_IMAGE}. <br/>
+ * The skins listed here are merged in the {@link IAndroidTarget#getSkins()} list.
+ * @return A non-null skin list, possibly empty.
+ */
+ @NonNull
+ File[] getSkins();
+
+ /**
+ * Returns the revision of this system image.
+ */
+ @NonNull
+ Revision getRevision();
+
+ /**
+ * Returns the {@link AndroidVersion} of this system image.
+ */
+ @NonNull
+ AndroidVersion getAndroidVersion();
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/LayoutlibVersion.java b/sdklib/src/main/java/com/android/sdklib/LayoutlibVersion.java
new file mode 100644
index 0000000..256c7e2
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/LayoutlibVersion.java
@@ -0,0 +1,53 @@
+/*
+ * 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.sdklib;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Information on the layoutlib version.
+ *
+ * @deprecated Only the api level is relevant, and so should be represented by a single int.
+ */
+public class LayoutlibVersion implements Comparable<LayoutlibVersion> {
+
+ private final int mApi;
+
+ private final int mRevision;
+
+ public static final int NOT_SPECIFIED = 0;
+
+ public LayoutlibVersion(int api, int revision) {
+ mApi = api;
+ mRevision = revision;
+ }
+
+ public int getApi() {
+ return mApi;
+ }
+
+ public int getRevision() {
+ return mRevision;
+ }
+
+ @Override
+ public int compareTo(@NonNull LayoutlibVersion rhs) {
+ boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED;
+ int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0);
+ int rhsValue = (rhs.mApi << 16) + (useRev ? rhs.mRevision : 0);
+ return lhsValue - rhsValue;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/SdkVersionInfo.java b/sdklib/src/main/java/com/android/sdklib/SdkVersionInfo.java
new file mode 100644
index 0000000..f4956d8
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/SdkVersionInfo.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.sdklib;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Strings;
+
+import java.util.Locale;
+
+/** Information about available SDK Versions */
+public class SdkVersionInfo {
+ /**
+ * The highest known API level. Note that the tools may also look at the
+ * installed platforms to see if they can find more recently released
+ * platforms, e.g. when the tools have not yet been updated for a new
+ * release. This number is used as a baseline and any more recent platforms
+ * found can be used to increase the highest known number.
+ */
+ public static final int HIGHEST_KNOWN_API = 23;
+
+ /**
+ * Like {@link #HIGHEST_KNOWN_API} but does not include preview platforms
+ */
+ public static final int HIGHEST_KNOWN_STABLE_API = 23;
+
+ /**
+ * The lowest active API level in the ecosystem. This number will change over time
+ * as the distribution of older platforms decreases.
+ */
+ public static final int LOWEST_ACTIVE_API = 8;
+
+ /**
+
+ /**
+ * The lowest api level we can accept for compileSdkVersion for
+ * for a new project. Make sure design and appcompat is supported.
+ */
+ public static final int LOWEST_COMPILE_SDK_VERSION = 22;
+
+ /**
+ * Returns the Android version and code name of the given API level
+ * The highest number (inclusive) that is supported
+ * is {@link SdkVersionInfo#HIGHEST_KNOWN_API}.
+ *
+ * @param api the api level
+ * @return a suitable version display name
+ */
+ @NonNull
+ public static String getAndroidName(int api) {
+ // See http://source.android.com/source/build-numbers.html
+ String codeName = getCodeName(api);
+ String name = getVersionString(api);
+ if (name == null) {
+ return String.format("API %1$d", api);
+ } else if (codeName == null) {
+ return String.format("API %1$d: Android %2$s", api, name);
+ } else {
+ return String.format("API %1$d: Android %2$s (%3$s)", api, name, codeName);
+ }
+ }
+
+ @Nullable
+ public static String getVersionString(int api) {
+ switch (api) {
+ case 1: return "1.0";
+ case 2: return "1.1";
+ case 3: return "1.5";
+ case 4: return "1.6";
+ case 5: return "2.0";
+ case 6: return "2.0.1";
+ case 7: return "2.1";
+ case 8: return "2.2";
+ case 9: return "2.3";
+ case 10: return "2.3.3";
+ case 11: return "3.0";
+ case 12: return "3.1";
+ case 13: return "3.2";
+ case 14: return "4.0";
+ case 15: return "4.0.3";
+ case 16: return "4.1";
+ case 17: return "4.2";
+ case 18: return "4.3";
+ case 19: return "4.4";
+ case 20: return "4.4";
+ case 21: return "5.0";
+ case 22: return "5.1";
+ case 23: return "6.0";
+ // If you add more versions here, also update #getBuildCodes and
+ // #HIGHEST_KNOWN_API
+
+ default: return null;
+ }
+ }
+
+ @Nullable
+ public static String getCodeName(int api) {
+ switch (api) {
+ case 1:
+ case 2:
+ return null;
+ case 3:
+ return "Cupcake";
+ case 4:
+ return "Donut";
+ case 5:
+ case 6:
+ case 7:
+ return "Eclair";
+ case 8:
+ return "Froyo";
+ case 9:
+ case 10:
+ return "Gingerbread";
+ case 11:
+ case 12:
+ case 13:
+ return "Honeycomb";
+ case 14:
+ case 15:
+ return "IceCreamSandwich";
+ case 16:
+ case 17:
+ case 18:
+ return "Jelly Bean";
+ case 19:
+ return "KitKat";
+ case 20:
+ return "KitKat Wear";
+ case 21:
+ case 22:
+ return "Lollipop";
+ case 23:
+ return "Marshmallow";
+
+ // If you add more versions here, also update #getBuildCodes and
+ // #HIGHEST_KNOWN_API
+
+ default: return null;
+ }
+ }
+
+ /**
+ * Returns the applicable build code (for
+ * {@code android.os.Build.VERSION_CODES}) for the corresponding API level,
+ * or null if it's unknown. The highest number (inclusive) that is supported
+ * is {@link SdkVersionInfo#HIGHEST_KNOWN_API}.
+ *
+ * @param api the API level to look up a version code for
+ * @return the corresponding build code field name, or null
+ */
+ @Nullable
+ public static String getBuildCode(int api) {
+ // See http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
+ switch (api) {
+ case 1: return "BASE"; //$NON-NLS-1$
+ case 2: return "BASE_1_1"; //$NON-NLS-1$
+ case 3: return "CUPCAKE"; //$NON-NLS-1$
+ case 4: return "DONUT"; //$NON-NLS-1$
+ case 5: return "ECLAIR"; //$NON-NLS-1$
+ case 6: return "ECLAIR_0_1"; //$NON-NLS-1$
+ case 7: return "ECLAIR_MR1"; //$NON-NLS-1$
+ case 8: return "FROYO"; //$NON-NLS-1$
+ case 9: return "GINGERBREAD"; //$NON-NLS-1$
+ case 10: return "GINGERBREAD_MR1"; //$NON-NLS-1$
+ case 11: return "HONEYCOMB"; //$NON-NLS-1$
+ case 12: return "HONEYCOMB_MR1"; //$NON-NLS-1$
+ case 13: return "HONEYCOMB_MR2"; //$NON-NLS-1$
+ case 14: return "ICE_CREAM_SANDWICH"; //$NON-NLS-1$
+ case 15: return "ICE_CREAM_SANDWICH_MR1"; //$NON-NLS-1$
+ case 16: return "JELLY_BEAN"; //$NON-NLS-1$
+ case 17: return "JELLY_BEAN_MR1"; //$NON-NLS-1$
+ case 18: return "JELLY_BEAN_MR2"; //$NON-NLS-1$
+ case 19: return "KITKAT"; //$NON-NLS-1$
+ case 20: return "KITKAT_WATCH"; //$NON-NLS-1$
+ case 21: return "LOLLIPOP"; //$NON-NLS-1$
+ case 22: return "LOLLIPOP_MR1"; //$NON-NLS-1$
+ case 23: return "M"; //$NON-NLS-1$
+ // If you add more versions here, also update #getAndroidName and
+ // #HIGHEST_KNOWN_API
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the API level of the given build code (e.g. JELLY_BEAN_MR1 => 17), or -1 if not
+ * recognized
+ *
+ * @param buildCode the build code name (not case sensitive)
+ * @param recognizeUnknowns if true, treat an unrecognized code name as a newly released
+ * platform the tools are not yet aware of, and set its API level to
+ * some higher number than all the currently known API versions
+ * @return the API level, or -1 if not recognized (unless recognizeUnknowns is true, in which
+ * {@link #HIGHEST_KNOWN_API} plus one is returned
+ */
+ public static int getApiByBuildCode(@NonNull String buildCode, boolean recognizeUnknowns) {
+ for (int api = 1; api <= HIGHEST_KNOWN_API; api++) {
+ String code = getBuildCode(api);
+ if (code != null && code.equalsIgnoreCase(buildCode)) {
+ return api;
+ }
+ }
+
+ if (buildCode.equalsIgnoreCase("L")) {
+ return 21; // For now the Build class also provides this as an alias to Lollipop
+ }
+
+ return recognizeUnknowns ? HIGHEST_KNOWN_API + 1 : -1;
+ }
+
+ /**
+ * Returns the API level of the given preview code name (e.g. JellyBeanMR2 => 17), or -1 if not
+ * recognized
+ *
+ * @param previewName the preview name (not case sensitive)
+ * @param recognizeUnknowns if true, treat an unrecognized code name as a newly released
+ * platform the tools are not yet aware of, and set its API level to
+ * some higher number than all the currently known API versions
+ * @return the API level, or -1 if not recognized (unless recognizeUnknowns is true, in which
+ * {@link #HIGHEST_KNOWN_API} plus one is returned
+ */
+ public static int getApiByPreviewName(@NonNull String previewName, boolean recognizeUnknowns) {
+ // JellyBean => JELLY_BEAN
+ String codeName = camelCaseToUnderlines(previewName).toUpperCase(Locale.US);
+ return getApiByBuildCode(codeName, recognizeUnknowns);
+ }
+
+ /**
+ * Converts a CamelCase word into an underlined_word
+ *
+ * @param string the CamelCase version of the word
+ * @return the underlined version of the word
+ */
+ @NonNull
+ public static String camelCaseToUnderlines(@NonNull String string) {
+ if (string.isEmpty()) {
+ return string;
+ }
+
+ StringBuilder sb = new StringBuilder(2 * string.length());
+ int n = string.length();
+ boolean lastWasUpperCase = Character.isUpperCase(string.charAt(0));
+ for (int i = 0; i < n; i++) {
+ char c = string.charAt(i);
+ boolean isUpperCase = Character.isUpperCase(c);
+ if (isUpperCase && !lastWasUpperCase) {
+ sb.append('_');
+ }
+ lastWasUpperCase = isUpperCase;
+ c = Character.toLowerCase(c);
+ sb.append(c);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Converts an underlined_word into a CamelCase word
+ *
+ * @param string the underlined word to convert
+ * @return the CamelCase version of the word
+ */
+ @NonNull
+ public static String underlinesToCamelCase(@NonNull String string) {
+ StringBuilder sb = new StringBuilder(string.length());
+ int n = string.length();
+
+ int i = 0;
+ @SuppressWarnings("SpellCheckingInspection")
+ boolean upcaseNext = true;
+ for (; i < n; i++) {
+ char c = string.charAt(i);
+ if (c == '_') {
+ upcaseNext = true;
+ } else {
+ if (upcaseNext) {
+ c = Character.toUpperCase(c);
+ }
+ upcaseNext = false;
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the {@link AndroidVersion} for a given version string, which is typically an API
+ * level number, but can also be a codename for a <b>preview</b> platform. Note: This should
+ * <b>not</b> be used to look up version names for build codes; for that, use {@link
+ * #getApiByBuildCode(String, boolean)}. The primary difference between this method is that
+ * {@link #getApiByBuildCode(String, boolean)} will return the final API number for a platform
+ * (e.g. for "KITKAT" it will return 19) whereas this method will return the API number for the
+ * codename as a preview platform (e.g. 18).
+ *
+ * @param apiOrPreviewName the version string
+ * @param targets an optional array of installed targets, if available. If the version
+ * string corresponds to a code name, this is used to search for a
+ * corresponding API level.
+ * @return an {@link com.android.sdklib.AndroidVersion}, or null if the version could not be
+ * determined (e.g. an empty or invalid API number or an unknown code name)
+ */
+ @Nullable
+ public static AndroidVersion getVersion(
+ @Nullable String apiOrPreviewName,
+ @Nullable IAndroidTarget[] targets) {
+ if (Strings.isNullOrEmpty(apiOrPreviewName)) {
+ return null;
+ }
+
+ if (Character.isDigit(apiOrPreviewName.charAt(0))) {
+ try {
+ int api = Integer.parseInt(apiOrPreviewName);
+ if (api >= 1) {
+ return new AndroidVersion(api, null);
+ }
+ return null;
+ } catch (NumberFormatException e) {
+ // Invalid version string
+ return null;
+ }
+ }
+
+ // Codename
+ if (targets != null) {
+ for (int i = targets.length - 1; i >= 0; i--) {
+ IAndroidTarget target = targets[i];
+ if (target.isPlatform()) {
+ AndroidVersion version = target.getVersion();
+ if (version.isPreview() && apiOrPreviewName.equalsIgnoreCase(version.getCodename())) {
+ return new AndroidVersion(version.getApiLevel(), version.getCodename());
+ }
+ }
+ }
+ }
+
+ int api = getApiByPreviewName(apiOrPreviewName, false);
+ if (api != -1) {
+ return new AndroidVersion(api - 1, apiOrPreviewName);
+ }
+
+ // Must be a future SDK platform
+ return new AndroidVersion(HIGHEST_KNOWN_API, apiOrPreviewName);
+ }
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java b/sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java
rename to sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/ApkBuilderMain.java b/sdklib/src/main/java/com/android/sdklib/build/ApkBuilderMain.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/ApkBuilderMain.java
rename to sdklib/src/main/java/com/android/sdklib/build/ApkBuilderMain.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/ApkCreationException.java b/sdklib/src/main/java/com/android/sdklib/build/ApkCreationException.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/ApkCreationException.java
rename to sdklib/src/main/java/com/android/sdklib/build/ApkCreationException.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/DependencyFile.java b/sdklib/src/main/java/com/android/sdklib/build/DependencyFile.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/DependencyFile.java
rename to sdklib/src/main/java/com/android/sdklib/build/DependencyFile.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/DuplicateFileException.java b/sdklib/src/main/java/com/android/sdklib/build/DuplicateFileException.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/DuplicateFileException.java
rename to sdklib/src/main/java/com/android/sdklib/build/DuplicateFileException.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/FileGatherer.java b/sdklib/src/main/java/com/android/sdklib/build/FileGatherer.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/FileGatherer.java
rename to sdklib/src/main/java/com/android/sdklib/build/FileGatherer.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/IArchiveBuilder.java b/sdklib/src/main/java/com/android/sdklib/build/IArchiveBuilder.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/IArchiveBuilder.java
rename to sdklib/src/main/java/com/android/sdklib/build/IArchiveBuilder.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java b/sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java
rename to sdklib/src/main/java/com/android/sdklib/build/JarListSanitizer.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/ManualRenderScriptChecker.java b/sdklib/src/main/java/com/android/sdklib/build/ManualRenderScriptChecker.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/ManualRenderScriptChecker.java
rename to sdklib/src/main/java/com/android/sdklib/build/ManualRenderScriptChecker.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/RenderScriptChecker.java b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptChecker.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/RenderScriptChecker.java
rename to sdklib/src/main/java/com/android/sdklib/build/RenderScriptChecker.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
rename to sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/SealedApkException.java b/sdklib/src/main/java/com/android/sdklib/build/SealedApkException.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/SealedApkException.java
rename to sdklib/src/main/java/com/android/sdklib/build/SealedApkException.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/build/SourceSearcher.java b/sdklib/src/main/java/com/android/sdklib/build/SourceSearcher.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/build/SourceSearcher.java
rename to sdklib/src/main/java/com/android/sdklib/build/SourceSearcher.java
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Abi.java b/sdklib/src/main/java/com/android/sdklib/devices/Abi.java
new file mode 100644
index 0000000..84d9d6d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Abi.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.devices;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * ABI values that can appear in a device's xml <abi> field <em>and</em>
+ * in a system-image abi.
+ * <p/>
+ * The CPU arch and model values are used to configure an AVD using a given ABI.
+ */
+public enum Abi {
+ // // ABI string // Address size // Display // CPU arch
+ ARMEABI (SdkConstants.ABI_ARMEABI, 4, "ARM", SdkConstants.CPU_ARCH_ARM),
+ ARMEABI_V7A(SdkConstants.ABI_ARMEABI_V7A, 4, "ARM", SdkConstants.CPU_ARCH_ARM, SdkConstants.CPU_MODEL_CORTEX_A8),
+ ARM64_V8A (SdkConstants.ABI_ARM64_V8A, 8, "ARM", SdkConstants.CPU_ARCH_ARM64),
+ X86 (SdkConstants.ABI_INTEL_ATOM, 4, "Intel Atom", SdkConstants.CPU_ARCH_INTEL_ATOM),
+ X86_64 (SdkConstants.ABI_INTEL_ATOM64, 8, "Intel Atom", SdkConstants.CPU_ARCH_INTEL_ATOM64),
+ MIPS (SdkConstants.ABI_MIPS, 4, "MIPS", SdkConstants.CPU_ARCH_MIPS),
+ MIPS64 (SdkConstants.ABI_MIPS64, 8, "MIPS", SdkConstants.CPU_ARCH_MIPS64);
+
+ @NonNull private final String mAbi;
+ private final int mAddressSizeInBytes;
+ @NonNull private final String mCpuArch;
+ @Nullable private final String mCpuModel;
+ @NonNull private final String mDisplayName;
+
+ /**
+ * Define an ABI with a given ABI code name, a display name and a CPU architecture.
+ *
+ * @param abi The ABI code name, used in the system-images and device definitions.
+ * @param addrSizeInBytes The ABI address size in bytes.
+ * @param displayName The ABI "family" name. Typically used in the UI combined with the
+ * code name, for example "ARM (armeabi-v7a)".
+ * @param cpuArch The CPU architecture, used in the AVD configuration files.
+ */
+ Abi(@NonNull String abi, int addrSizeInBytes, @NonNull String displayName, @NonNull String cpuArch) {
+ this(abi, addrSizeInBytes, displayName, cpuArch, null);
+ }
+
+
+ /**
+ * Define an ABI with a given ABI code name, a display name, a CPU architecture
+ * and an optional CPU model.
+ *
+ * @param abi The ABI code name, used in the system-images and device definitions.
+ * @param addrByteSize The ABI address size in bytes.
+ * @param displayName The ABI "family" name. Typically used in the UI combined with the
+ * code name, for example "ARM (armeabi-v7a)".
+ * @param cpuArch The CPU architecture, used in the AVD configuration files.
+ * @param cpuModel An optional CPU model, used in the AVD configuration files.
+ * The current strategy is to leave this field out. The emulator, which uses the
+ * AVD configuration files, doesn't seem to use it.
+ */
+ Abi(@NonNull String abi, int addrSizeInBytes, @NonNull String displayName,
+ @NonNull String cpuArch, @Nullable String cpuModel) {
+ mAbi = abi;
+ mAddressSizeInBytes = addrSizeInBytes;
+ mDisplayName = displayName;
+ mCpuArch = cpuArch;
+ mCpuModel = cpuModel;
+ }
+
+ /**
+ * Returns the ABI definition matching the given ABI code name.
+ *
+ * @param abi The ABI code name, used in the system-images and device definitions.
+ * @return An existing {@link Abi} description or null.
+ */
+ @Nullable
+ public static Abi getEnum(@NonNull String abi) {
+ for (Abi a : values()) {
+ if (a.mAbi.equals(abi)) {
+ return a;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the ABI code name, as used in the system-images and device definitions
+ */
+ @NonNull
+ @Override
+ public String toString() {
+ return mAbi;
+ }
+
+ /**
+ * Returns the CPU architecture, as used in the AVD configuration files
+ */
+ @NonNull
+ public String getCpuArch() {
+ return mCpuArch;
+ }
+
+ /**
+ * Returns the address size in bytes
+ */
+ public int getAddressSizeInBytes() {
+ return mAddressSizeInBytes;
+ }
+
+ /**
+ * Returns the optional CPU model, used in the AVD configuration files.
+ * This is often null.
+ */
+ @Nullable
+ public String getCpuModel() {
+ return mCpuModel;
+ }
+
+ /**
+ * Return the ABI "family" name for display.
+ * Clients should typically display that combined with the code name,
+ * for example "ARM (armeabi-v7a)".
+ */
+ @NonNull
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * Returns true if we support SMP on this CPU architecture.
+ */
+ public boolean supportsMultipleCpuCores() {
+ return this == X86 || this == X86_64;
+ }
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/BluetoothProfile.java b/sdklib/src/main/java/com/android/sdklib/devices/BluetoothProfile.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/BluetoothProfile.java
rename to sdklib/src/main/java/com/android/sdklib/devices/BluetoothProfile.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/ButtonType.java b/sdklib/src/main/java/com/android/sdklib/devices/ButtonType.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/ButtonType.java
rename to sdklib/src/main/java/com/android/sdklib/devices/ButtonType.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Camera.java b/sdklib/src/main/java/com/android/sdklib/devices/Camera.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Camera.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Camera.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/CameraLocation.java b/sdklib/src/main/java/com/android/sdklib/devices/CameraLocation.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/CameraLocation.java
rename to sdklib/src/main/java/com/android/sdklib/devices/CameraLocation.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Device.java b/sdklib/src/main/java/com/android/sdklib/devices/Device.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Device.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Device.java
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java b/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
new file mode 100644
index 0000000..ee5a4cd
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
@@ -0,0 +1,718 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.devices;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.repository.io.FileOpUtils;
+import com.android.resources.Keyboard;
+import com.android.resources.KeyboardState;
+import com.android.resources.Navigation;
+import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.avd.HardwareProperties;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.repository.PkgProps;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Closeables;
+
+import org.xml.sax.SAXException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+
+/**
+ * Manager class for interacting with {@link Device}s within the SDK
+ */
+public class DeviceManager {
+
+ private static final String DEVICE_PROFILES_PROP = "DeviceProfiles";
+ private static final Pattern PATH_PROPERTY_PATTERN =
+ Pattern.compile('^' + PkgProps.EXTRA_PATH + '=' + DEVICE_PROFILES_PROP + '$');
+ private ILogger mLog;
+ private Table<String, String, Device> mVendorDevices;
+ private Table<String, String, Device> mSysImgDevices;
+ private Table<String, String, Device> mUserDevices;
+ private Table<String, String, Device> mDefaultDevices;
+ private final Object mLock = new Object();
+ private final List<DevicesChangedListener> sListeners = new ArrayList<DevicesChangedListener>();
+ private final String mOsSdkPath;
+
+ public enum DeviceFilter {
+ /** getDevices() flag to list default devices from the bundled devices.xml definitions. */
+ DEFAULT,
+ /** getDevices() flag to list user devices saved in the .android home folder. */
+ USER,
+ /** getDevices() flag to list vendor devices -- the bundled nexus.xml devices
+ * as well as all those coming from extra packages. */
+ VENDOR,
+ /** getDevices() flag to list devices from system-images/platform-N/tag/abi/devices.xml */
+ SYSTEM_IMAGES,
+ }
+
+ /** getDevices() flag to list all devices. */
+ public static final EnumSet<DeviceFilter> ALL_DEVICES = EnumSet.allOf(DeviceFilter.class);
+
+ public enum DeviceStatus {
+ /**
+ * The device exists unchanged from the given configuration
+ */
+ EXISTS,
+ /**
+ * A device exists with the given name and manufacturer, but has a different configuration
+ */
+ CHANGED,
+ /**
+ * There is no device with the given name and manufacturer
+ */
+ MISSING
+ }
+
+ /**
+ * Creates a new instance of DeviceManager.
+ *
+ * @param sdkLocation Path to the current SDK. If null or invalid, vendor and system images
+ * devices are ignored.
+ * @param log SDK logger instance. Should be non-null.
+ */
+ public static DeviceManager createInstance(@Nullable File sdkLocation, @NonNull ILogger log) {
+ // TODO consider using a cache and reusing the same instance of the device manager
+ // for the same manager/log combo.
+ return new DeviceManager(sdkLocation == null ? null : sdkLocation.getPath(), log);
+ }
+
+ /**
+ * Creates a new instance of DeviceManager.
+ *
+ * @param osSdkPath Path to the current SDK. If null or invalid, vendor devices are ignored.
+ * @param log SDK logger instance. Should be non-null.
+ */
+ private DeviceManager(@Nullable String osSdkPath, @NonNull ILogger log) {
+ mOsSdkPath = osSdkPath;
+ mLog = log;
+ }
+
+ /**
+ * Interface implemented by objects which want to know when changes occur to the {@link Device}
+ * lists.
+ */
+ public interface DevicesChangedListener {
+ /**
+ * Called after one of the {@link Device} lists has been updated.
+ */
+ void onDevicesChanged();
+ }
+
+ /**
+ * Register a listener to be notified when the device lists are modified.
+ *
+ * @param listener The listener to add. Ignored if already registered.
+ */
+ public void registerListener(@NonNull DevicesChangedListener listener) {
+ synchronized (sListeners) {
+ if (!sListeners.contains(listener)) {
+ sListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes a listener from the notification list such that it will no longer receive
+ * notifications when modifications to the {@link Device} list occur.
+ *
+ * @param listener The listener to remove.
+ */
+ public boolean unregisterListener(@NonNull DevicesChangedListener listener) {
+ synchronized (sListeners) {
+ return sListeners.remove(listener);
+ }
+ }
+
+ @NonNull
+ public DeviceStatus getDeviceStatus(@NonNull String name, @NonNull String manufacturer) {
+ Device d = getDevice(name, manufacturer);
+ if (d == null) {
+ return DeviceStatus.MISSING;
+ }
+
+ return DeviceStatus.EXISTS;
+ }
+
+ @Nullable
+ public Device getDevice(@NonNull String id, @NonNull String manufacturer) {
+ initDevicesLists();
+ Device d = mUserDevices.get(id, manufacturer);
+ if (d != null) {
+ return d;
+ }
+ d = mSysImgDevices.get(id, manufacturer);
+ if (d != null) {
+ return d;
+ }
+ d = mDefaultDevices.get(id, manufacturer);
+ if (d != null) {
+ return d;
+ }
+ d = mVendorDevices.get(id, manufacturer);
+ return d;
+ }
+
+ @Nullable
+ private Device getDeviceImpl(@NonNull Iterable<Device> devicesList,
+ @NonNull String id,
+ @NonNull String manufacturer) {
+ for (Device d : devicesList) {
+ if (d.getId().equals(id) && d.getManufacturer().equals(manufacturer)) {
+ return d;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the known {@link Device} list.
+ *
+ * @param deviceFilter One of the {@link DeviceFilter} constants.
+ * @return A copy of the list of {@link Device}s. Can be empty but not null.
+ */
+ @NonNull
+ public Collection<Device> getDevices(@NonNull DeviceFilter deviceFilter) {
+ return getDevices(EnumSet.of(deviceFilter));
+ }
+
+ /**
+ * Returns the known {@link Device} list.
+ *
+ * @param deviceFilter A combination of the {@link DeviceFilter} constants
+ * or the constant {@link DeviceManager#ALL_DEVICES}.
+ * @return A copy of the list of {@link Device}s. Can be empty but not null.
+ */
+ @NonNull
+ public Collection<Device> getDevices(@NonNull EnumSet<DeviceFilter> deviceFilter) {
+ initDevicesLists();
+ Table<String, String, Device> devices = HashBasedTable.create();
+ if (mUserDevices != null && (deviceFilter.contains(DeviceFilter.USER))) {
+ devices.putAll(mUserDevices);
+ }
+ if (mDefaultDevices != null && (deviceFilter.contains(DeviceFilter.DEFAULT))) {
+ devices.putAll(mDefaultDevices);
+ }
+ if (mVendorDevices != null && (deviceFilter.contains(DeviceFilter.VENDOR))) {
+ devices.putAll(mVendorDevices);
+ }
+ if (mSysImgDevices != null && (deviceFilter.contains(DeviceFilter.SYSTEM_IMAGES))) {
+ devices.putAll(mSysImgDevices);
+ }
+ return Collections.unmodifiableCollection(devices.values());
+ }
+
+ private void initDevicesLists() {
+ boolean changed = initDefaultDevices();
+ changed |= initVendorDevices();
+ changed |= initSysImgDevices();
+ changed |= initUserDevices();
+ if (changed) {
+ notifyListeners();
+ }
+ }
+
+ /**
+ * Initializes the {@link Device}s packaged with the SDK.
+ * @return True if the list has changed.
+ */
+ private boolean initDefaultDevices() {
+ synchronized (mLock) {
+ if (mDefaultDevices != null) {
+ return false;
+ }
+ InputStream stream = DeviceManager.class
+ .getResourceAsStream(SdkConstants.FN_DEVICES_XML);
+ try {
+ assert stream != null : SdkConstants.FN_DEVICES_XML + " not bundled in sdklib.";
+ mDefaultDevices = DeviceParser.parse(stream);
+ return true;
+ } catch (IllegalStateException e) {
+ // The device builders can throw IllegalStateExceptions if
+ // build gets called before everything is properly setup
+ mLog.error(e, null);
+ mDefaultDevices = HashBasedTable.create();
+ } catch (Exception e) {
+ mLog.error(e, "Error reading default devices");
+ mDefaultDevices = HashBasedTable.create();
+ } finally {
+ Closeables.closeQuietly(stream);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Initializes all vendor-provided {@link Device}s: the bundled nexus.xml devices
+ * as well as all those coming from extra packages.
+ * @return True if the list has changed.
+ */
+ private boolean initVendorDevices() {
+ synchronized (mLock) {
+ if (mVendorDevices != null) {
+ return false;
+ }
+
+ mVendorDevices = HashBasedTable.create();
+
+ // Load builtin devices
+ InputStream stream = DeviceManager.class.getResourceAsStream("nexus.xml");
+ try {
+ mVendorDevices.putAll(DeviceParser.parse(stream));
+ } catch (Exception e) {
+ mLog.error(e, "Could not load nexus devices");
+ } finally {
+ Closeables.closeQuietly(stream);
+ }
+
+ stream = DeviceManager.class.getResourceAsStream("wear.xml");
+ try {
+ mVendorDevices.putAll(DeviceParser.parse(stream));
+ } catch (Exception e) {
+ mLog.error(e, "Could not load wear devices");
+ } finally {
+ Closeables.closeQuietly(stream);
+ }
+
+ stream = DeviceManager.class.getResourceAsStream("tv.xml");
+ try {
+ mVendorDevices.putAll(DeviceParser.parse(stream));
+ } catch (Exception e) {
+ mLog.error(e, "Could not load tv devices");
+ } finally {
+ Closeables.closeQuietly(stream);
+ }
+
+ if (mOsSdkPath != null) {
+ // Load devices from vendor extras
+ File extrasFolder = new File(mOsSdkPath, SdkConstants.FD_EXTRAS);
+ List<File> deviceDirs = getExtraDirs(extrasFolder);
+ for (File deviceDir : deviceDirs) {
+ File deviceXml = new File(deviceDir, SdkConstants.FN_DEVICES_XML);
+ if (deviceXml.isFile()) {
+ mVendorDevices.putAll(loadDevices(deviceXml));
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Initializes all system-image provided {@link Device}s.
+ * @return True if the list has changed.
+ */
+ private boolean initSysImgDevices() {
+ synchronized (mLock) {
+ if (mSysImgDevices != null) {
+ return false;
+ }
+ mSysImgDevices = HashBasedTable.create();
+
+ if (mOsSdkPath == null) {
+ return false;
+ }
+
+ // Load devices from tagged system-images
+ // Path pattern is /sdk/system-images/<platform-N>/<tag>/<abi>/devices.xml
+
+ FileOp fop = FileOpUtils.create();
+ File sysImgFolder = new File(mOsSdkPath, SdkConstants.FD_SYSTEM_IMAGES);
+
+ for (File platformFolder : fop.listFiles(sysImgFolder)) {
+ if (!fop.isDirectory(platformFolder)) {
+ continue;
+ }
+
+ for (File tagFolder : fop.listFiles(platformFolder)) {
+ if (!fop.isDirectory(tagFolder)) {
+ continue;
+ }
+
+ for (File abiFolder : fop.listFiles(tagFolder)) {
+ if (!fop.isDirectory(abiFolder)) {
+ continue;
+ }
+
+ File deviceXml = new File(abiFolder, SdkConstants.FN_DEVICES_XML);
+ if (fop.isFile(deviceXml)) {
+ mSysImgDevices.putAll(loadDevices(deviceXml));
+ }
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Initializes all user-created {@link Device}s
+ * @return True if the list has changed.
+ */
+ private boolean initUserDevices() {
+ synchronized (mLock) {
+ if (mUserDevices != null) {
+ return false;
+ }
+ // User devices should be saved out to
+ // $HOME/.android/devices.xml
+ mUserDevices = HashBasedTable.create();
+ File userDevicesFile = null;
+ try {
+ userDevicesFile = new File(
+ AndroidLocation.getFolder(),
+ SdkConstants.FN_DEVICES_XML);
+ if (userDevicesFile.exists()) {
+ mUserDevices.putAll(DeviceParser.parse(userDevicesFile));
+ return true;
+ }
+ } catch (AndroidLocationException e) {
+ mLog.warning("Couldn't load user devices: %1$s", e.getMessage());
+ } catch (SAXException e) {
+ // Probably an old config file which we don't want to overwrite.
+ if (userDevicesFile != null) {
+ String base = userDevicesFile.getAbsoluteFile() + ".old";
+ File renamedConfig = new File(base);
+ int i = 0;
+ while (renamedConfig.exists()) {
+ renamedConfig = new File(base + '.' + (i++));
+ }
+ mLog.error(e, "Error parsing %1$s, backing up to %2$s",
+ userDevicesFile.getAbsolutePath(),
+ renamedConfig.getAbsolutePath());
+ userDevicesFile.renameTo(renamedConfig);
+ }
+ } catch (ParserConfigurationException e) {
+ mLog.error(e, "Error parsing %1$s",
+ userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
+ } catch (IOException e) {
+ mLog.error(e, "Error parsing %1$s",
+ userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
+ }
+ }
+ return false;
+ }
+
+ public void addUserDevice(@NonNull Device d) {
+ boolean changed = false;
+ synchronized (mLock) {
+ if (mUserDevices == null) {
+ initUserDevices();
+ assert mUserDevices != null;
+ }
+ if (mUserDevices != null) {
+ mUserDevices.put(d.getId(), d.getManufacturer(), d);
+ }
+ changed = true;
+ }
+ if (changed) {
+ notifyListeners();
+ }
+ }
+
+ public void removeUserDevice(@NonNull Device d) {
+ synchronized (mLock) {
+ if (mUserDevices == null) {
+ initUserDevices();
+ assert mUserDevices != null;
+ }
+ if (mUserDevices != null) {
+ if (mUserDevices.contains(d.getId(), d.getManufacturer())) {
+ mUserDevices.remove(d.getId(), d.getManufacturer());
+ notifyListeners();
+ }
+ }
+ }
+ }
+
+ public void replaceUserDevice(@NonNull Device d) {
+ synchronized (mLock) {
+ if (mUserDevices == null) {
+ initUserDevices();
+ }
+ removeUserDevice(d);
+ addUserDevice(d);
+ }
+ }
+
+ /**
+ * Saves out the user devices to {@link SdkConstants#FN_DEVICES_XML} in
+ * {@link AndroidLocation#getFolder()}.
+ */
+ public void saveUserDevices() {
+ if (mUserDevices == null) {
+ return;
+ }
+
+ File userDevicesFile = null;
+ try {
+ userDevicesFile = new File(AndroidLocation.getFolder(),
+ SdkConstants.FN_DEVICES_XML);
+ } catch (AndroidLocationException e) {
+ mLog.warning("Couldn't find user directory: %1$s", e.getMessage());
+ return;
+ }
+
+ if (mUserDevices.isEmpty()) {
+ userDevicesFile.delete();
+ return;
+ }
+
+ synchronized (mLock) {
+ if (!mUserDevices.isEmpty()) {
+ try {
+ DeviceWriter.writeToXml(new FileOutputStream(userDevicesFile), mUserDevices.values());
+ } catch (FileNotFoundException e) {
+ mLog.warning("Couldn't open file: %1$s", e.getMessage());
+ } catch (ParserConfigurationException e) {
+ mLog.warning("Error writing file: %1$s", e.getMessage());
+ } catch (TransformerFactoryConfigurationError e) {
+ mLog.warning("Error writing file: %1$s", e.getMessage());
+ } catch (TransformerException e) {
+ mLog.warning("Error writing file: %1$s", e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns hardware properties (defined in hardware.ini) as a {@link Map}.
+ *
+ * @param s The {@link State} from which to derive the hardware properties.
+ * @return A {@link Map} of hardware properties.
+ */
+ @NonNull
+ public static Map<String, String> getHardwareProperties(@NonNull State s) {
+ Hardware hw = s.getHardware();
+ Map<String, String> props = new HashMap<String, String>();
+ props.put(HardwareProperties.HW_MAINKEYS,
+ getBooleanVal(hw.getButtonType().equals(ButtonType.HARD)));
+ props.put(HardwareProperties.HW_TRACKBALL,
+ getBooleanVal(hw.getNav().equals(Navigation.TRACKBALL)));
+ props.put(HardwareProperties.HW_KEYBOARD,
+ getBooleanVal(hw.getKeyboard().equals(Keyboard.QWERTY)));
+ props.put(HardwareProperties.HW_DPAD,
+ getBooleanVal(hw.getNav().equals(Navigation.DPAD)));
+
+ Set<Sensor> sensors = hw.getSensors();
+ props.put(HardwareProperties.HW_GPS, getBooleanVal(sensors.contains(Sensor.GPS)));
+ props.put(HardwareProperties.HW_BATTERY,
+ getBooleanVal(hw.getChargeType().equals(PowerType.BATTERY)));
+ props.put(HardwareProperties.HW_ACCELEROMETER,
+ getBooleanVal(sensors.contains(Sensor.ACCELEROMETER)));
+ props.put(HardwareProperties.HW_ORIENTATION_SENSOR,
+ getBooleanVal(sensors.contains(Sensor.GYROSCOPE)));
+ props.put(HardwareProperties.HW_AUDIO_INPUT, getBooleanVal(hw.hasMic()));
+ props.put(HardwareProperties.HW_SDCARD, getBooleanVal(!hw.getRemovableStorage().isEmpty()));
+ props.put(HardwareProperties.HW_LCD_DENSITY,
+ Integer.toString(hw.getScreen().getPixelDensity().getDpiValue()));
+ props.put(HardwareProperties.HW_PROXIMITY_SENSOR,
+ getBooleanVal(sensors.contains(Sensor.PROXIMITY_SENSOR)));
+ return props;
+ }
+
+ /**
+ * Returns the hardware properties defined in
+ * {@link AvdManager#HARDWARE_INI} as a {@link Map}.
+ *
+ * This is intended to be dumped in the config.ini and already contains
+ * the device name, manufacturer and device hash.
+ *
+ * @param d The {@link Device} from which to derive the hardware properties.
+ * @return A {@link Map} of hardware properties.
+ */
+ @NonNull
+ public static Map<String, String> getHardwareProperties(@NonNull Device d) {
+ Map<String, String> props = getHardwareProperties(d.getDefaultState());
+ for (State s : d.getAllStates()) {
+ if (s.getKeyState().equals(KeyboardState.HIDDEN)) {
+ props.put("hw.keyboard.lid", getBooleanVal(true));
+ }
+ }
+
+ HashFunction md5 = Hashing.md5();
+ Hasher hasher = md5.newHasher();
+
+ ArrayList<String> keys = new ArrayList<String>(props.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ if (key != null) {
+ hasher.putString(key, Charsets.UTF_8);
+ String value = props.get(key);
+ hasher.putString(value == null ? "null" : value, Charsets.UTF_8);
+ }
+ }
+ // store the hash method for potential future compatibility
+ String hash = "MD5:" + hasher.hash().toString();
+ props.put(AvdManager.AVD_INI_DEVICE_HASH_V2, hash);
+ props.remove(AvdManager.AVD_INI_DEVICE_HASH_V1);
+
+ props.put(AvdManager.AVD_INI_DEVICE_NAME, d.getId());
+ props.put(AvdManager.AVD_INI_DEVICE_MANUFACTURER, d.getManufacturer());
+ return props;
+ }
+
+ /**
+ * Checks whether the the hardware props have changed.
+ * If the hash is the same, returns null for success.
+ * If the hash is not the same or there's not enough information to indicate it's
+ * the same (e.g. if in the future we change the digest method), simply return the
+ * new hash, indicating it would be best to update it.
+ *
+ * @param d The device.
+ * @param hashV2 The previous saved AvdManager.AVD_INI_DEVICE_HASH_V2 property.
+ * @return Null if the same, otherwise returns the new and different hash.
+ */
+ @Nullable
+ public static String hasHardwarePropHashChanged(@NonNull Device d, @NonNull String hashV2) {
+ Map<String, String> props = getHardwareProperties(d);
+ String newHash = props.get(AvdManager.AVD_INI_DEVICE_HASH_V2);
+
+ // Implementation detail: don't just return the hash and let the caller decide whether
+ // the hash is the same. That's because the hash contains the digest method so if in
+ // the future we decide to change it, we could potentially recompute the hash here
+ // using an older digest method here and still determine its validity, whereas the
+ // caller cannot determine that.
+
+ if (newHash != null && newHash.equals(hashV2)) {
+ return null;
+ }
+ return newHash;
+ }
+
+
+ /**
+ * Takes a boolean and returns the appropriate value for
+ * {@link HardwareProperties}
+ *
+ * @param bool The boolean value to turn into the appropriate
+ * {@link HardwareProperties} value.
+ * @return {@code HardwareProperties#BOOLEAN_YES} if true,
+ * {@code HardwareProperties#BOOLEAN_NO} otherwise.
+ */
+ private static String getBooleanVal(boolean bool) {
+ if (bool) {
+ return HardwareProperties.BOOLEAN_YES;
+ }
+ return HardwareProperties.BOOLEAN_NO;
+ }
+
+ @NonNull
+ private Table<String, String, Device> loadDevices(@NonNull File deviceXml) {
+ try {
+ return DeviceParser.parse(deviceXml);
+ } catch (SAXException e) {
+ mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath());
+ } catch (ParserConfigurationException e) {
+ mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath());
+ } catch (IOException e) {
+ mLog.error(e, "Error reading %1$s", deviceXml.getAbsolutePath());
+ } catch (AssertionError e) {
+ mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath());
+ } catch (IllegalStateException e) {
+ // The device builders can throw IllegalStateExceptions if
+ // build gets called before everything is properly setup
+ mLog.error(e, null);
+ }
+ return HashBasedTable.create();
+ }
+
+ private void notifyListeners() {
+ synchronized (sListeners) {
+ for (DevicesChangedListener listener : sListeners) {
+ listener.onDevicesChanged();
+ }
+ }
+ }
+
+ /* Returns all of DeviceProfiles in the extras/ folder */
+ @NonNull
+ private List<File> getExtraDirs(@NonNull File extrasFolder) {
+ List<File> extraDirs = new ArrayList<File>();
+ // All OEM provided device profiles are in
+ // $SDK/extras/$VENDOR/$ITEM/devices.xml
+ if (extrasFolder != null && extrasFolder.isDirectory()) {
+ for (File vendor : extrasFolder.listFiles()) {
+ if (vendor.isDirectory()) {
+ for (File item : vendor.listFiles()) {
+ if (item.isDirectory() && isDevicesExtra(item)) {
+ extraDirs.add(item);
+ }
+ }
+ }
+ }
+ }
+
+ return extraDirs;
+ }
+
+ /*
+ * Returns whether a specific folder for a specific vendor is a
+ * DeviceProfiles folder
+ */
+ private boolean isDevicesExtra(@NonNull File item) {
+ File properties = new File(item, SdkConstants.FN_SOURCE_PROP);
+ try {
+ BufferedReader propertiesReader = new BufferedReader(new FileReader(properties));
+ try {
+ String line;
+ while ((line = propertiesReader.readLine()) != null) {
+ Matcher m = PATH_PROPERTY_PATTERN.matcher(line);
+ if (m.matches()) {
+ return true;
+ }
+ }
+ } finally {
+ propertiesReader.close();
+ }
+ } catch (IOException ignore) {
+ }
+ return false;
+ }
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java b/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
rename to sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java b/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
rename to sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Hardware.java b/sdklib/src/main/java/com/android/sdklib/devices/Hardware.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Hardware.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Hardware.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Meta.java b/sdklib/src/main/java/com/android/sdklib/devices/Meta.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Meta.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Meta.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Multitouch.java b/sdklib/src/main/java/com/android/sdklib/devices/Multitouch.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Multitouch.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Multitouch.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Network.java b/sdklib/src/main/java/com/android/sdklib/devices/Network.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Network.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Network.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/PowerType.java b/sdklib/src/main/java/com/android/sdklib/devices/PowerType.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/PowerType.java
rename to sdklib/src/main/java/com/android/sdklib/devices/PowerType.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Screen.java b/sdklib/src/main/java/com/android/sdklib/devices/Screen.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Screen.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Screen.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/ScreenType.java b/sdklib/src/main/java/com/android/sdklib/devices/ScreenType.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/ScreenType.java
rename to sdklib/src/main/java/com/android/sdklib/devices/ScreenType.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Sensor.java b/sdklib/src/main/java/com/android/sdklib/devices/Sensor.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Sensor.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Sensor.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/Software.java b/sdklib/src/main/java/com/android/sdklib/devices/Software.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/Software.java
rename to sdklib/src/main/java/com/android/sdklib/devices/Software.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/State.java b/sdklib/src/main/java/com/android/sdklib/devices/State.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/State.java
rename to sdklib/src/main/java/com/android/sdklib/devices/State.java
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Storage.java b/sdklib/src/main/java/com/android/sdklib/devices/Storage.java
new file mode 100644
index 0000000..e7f5aa3
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Storage.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.devices;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+public class Storage {
+ private long mNoBytes;
+
+ public Storage(long amount, Unit unit) {
+ mNoBytes = amount * unit.getNumberOfBytes();
+ }
+
+ public Storage(long amount) {
+ this(amount, Unit.B);
+ }
+
+ /** Returns the amount of storage represented, in Bytes */
+ public long getSize() {
+ return getSizeAsUnit(Unit.B);
+ }
+
+ @NonNull
+ public Storage deepCopy() {
+ return new Storage(mNoBytes);
+ }
+
+ /**
+ * Return the amount of storage represented by the instance in the given unit
+ * @param unit The unit of the result.
+ * @return The size of the storage in the given unit.
+ */
+ public long getSizeAsUnit(@NonNull Unit unit) {
+ return mNoBytes / unit.getNumberOfBytes();
+ }
+
+ /**
+ * Returns the amount of storage represented by the instance in the given unit
+ * as a double to get a more precise result
+ * @param unit The unit of the result.
+ * @return The size of the storage in the given unit.
+ */
+ public double getPreciseSizeAsUnit(@NonNull Unit unit) {
+ return ((double)mNoBytes) / unit.getNumberOfBytes();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof Storage)) {
+ return false;
+ }
+
+ Storage s = (Storage) o;
+ if (s.getSize() == this.getSize()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ return 31 * result + (int) (mNoBytes^(mNoBytes>>>32));
+ }
+
+ public enum Unit{
+ B("B", "B", 1),
+ KiB("KiB", "KB", 1024),
+ MiB("MiB", "MB", 1024 * 1024),
+ GiB("GiB", "GB", 1024 * 1024 * 1024),
+ TiB("TiB", "TB", 1024L * 1024L * 1024L * 1024L);
+
+ @NonNull
+ private String mValue;
+
+ @NonNull
+ private String mDisplayValue;
+
+ /** The number of bytes needed to have one of the given unit */
+ private long mNoBytes;
+
+ Unit(@NonNull String val, @NonNull String displayVal, long noBytes) {
+ mValue = val;
+ mDisplayValue = displayVal;
+ mNoBytes = noBytes;
+ }
+
+ @Nullable
+ public static Unit getEnum(@NonNull String val) {
+ for (Unit v : values()) {
+ if (v.mValue.equals(val)) {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ public long getNumberOfBytes() {
+ return mNoBytes;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+
+ public String getDisplayValue() {
+ return mDisplayValue;
+ }
+ }
+
+ /**
+ * Finds the largest {@link Unit} which can display the storage value as a positive integer
+ * with no loss of accuracy.
+ * @return The most appropriate {@link Unit}.
+ */
+ @NonNull
+ public Unit getAppropriateUnits() {
+ Unit optimalUnit = Unit.B;
+ for (Unit unit : Unit.values()) {
+ if (mNoBytes % unit.getNumberOfBytes() == 0) {
+ optimalUnit = unit;
+ } else {
+ break;
+ }
+ }
+ return optimalUnit;
+ }
+
+ @Override
+ public String toString() {
+ Unit u = getAppropriateUnits();
+ return String.format("%d %s", getSizeAsUnit(u), u);
+ }
+
+
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/devices.xml b/sdklib/src/main/java/com/android/sdklib/devices/devices.xml
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/devices.xml
rename to sdklib/src/main/java/com/android/sdklib/devices/devices.xml
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml b/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
new file mode 100644
index 0000000..365f818
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
@@ -0,0 +1,1296 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<d:devices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:d="http://schemas.android.com/sdk/devices/2">
+
+ <d:device>
+ <d:name>Nexus One</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.7</d:diagonal-length>
+ <d:pixel-density>hdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>254</d:xdpi>
+ <d:ydpi>254</d:ydpi>
+ <d:touch>
+ <d:multitouch>basic</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>trackball</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="MiB">503</d:internal-storage>
+ <d:removable-storage unit="MiB">0</d:removable-storage>
+ <d:cpu>Qualcomm Scorpion</d:cpu>
+ <d:gpu>Qualcomm Adreno 200</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_one</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>7-10</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+ <d:device>
+ <d:name>Nexus S</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4</d:diagonal-length>
+ <d:pixel-density>hdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>235</d:xdpi>
+ <d:ydpi>235</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="KiB">351428</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="MiB">503</d:internal-storage>
+ <d:removable-storage unit="MiB">0</d:removable-storage>
+ <d:cpu>Samsung Exynos 3110</d:cpu>
+ <d:gpu>PowerVR SGX 540</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_s</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>9-16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>
+ GL_EXT_debug_marker
+ GL_OES_rgb8_rgba8
+ GL_OES_depth24
+ GL_OES_vertex_half_float
+ GL_OES_texture_float
+ GL_OES_texture_half_float
+ GL_OES_element_index_uint
+ GL_OES_mapbuffer
+ GL_OES_fragment_precision_high
+ GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_EGL_image
+ GL_OES_EGL_image_external
+ GL_OES_required_internalformat
+ GL_OES_depth_texture
+ GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil
+ GL_OES_standard_derivatives
+ GL_OES_vertex_array_object
+ GL_OES_egl_sync
+ GL_EXT_multi_draw_arrays
+ GL_EXT_texture_format_BGRA8888
+ GL_EXT_discard_framebuffer
+ GL_EXT_shader_texture_lod
+ GL_IMG_shader_binary
+ GL_IMG_texture_compression_pvrtc
+ GL_IMG_texture_npot
+ GL_IMG_texture_format_BGRA8888
+ GL_IMG_read_format
+ GL_IMG_program_binary
+ GL_IMG_multisampled_render_to_texture
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Galaxy Nexus</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.65</d:diagonal-length> <!-- In inches -->
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>720</d:x-dimension>
+ <d:y-dimension>1280</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>316</d:xdpi>
+ <d:ydpi>316</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">1</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">16</d:internal-storage>
+ <d:removable-storage unit="KiB"></d:removable-storage>
+ <d:cpu>OMAP 4460</d:cpu> <!-- cpu type (Tegra3) freeform -->
+ <d:gpu>PowerVR SGX540</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ </d:abi>
+ <!--dock (car, desk, tv, none)-->
+ <d:dock>
+ </d:dock>
+ <!-- plugged in (never, charge, always) -->
+ <d:power-type>battery</d:power-type>
+ <d:skin>galaxy_nexus</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>14-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles>
+ HSP
+ HFP
+ SPP
+ A2DP
+ AVRCP
+ OPP
+ PBAP
+ GAVDP
+ AVDTP
+ HID
+ HDP
+ PAN
+ </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <!--
+ These can be gotten via
+ javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
+ -->
+ <d:gl-extensions>
+ GL_EXT_discard_framebuffer
+ GL_EXT_multi_draw_arrays
+ GL_EXT_shader_texture_lod
+ GL_EXT_texture_format_BGRA8888
+ GL_IMG_multisampled_render_to_texture
+ GL_IMG_program_binary
+ GL_IMG_read_format
+ GL_IMG_shader_binary
+ GL_IMG_texture_compression_pvrtc
+ GL_IMG_texture_format_BGRA8888
+ GL_IMG_texture_npot
+ GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_depth_texture
+ GL_OES_depth24
+ GL_OES_EGL_image
+ GL_OES_EGL_image_external
+ GL_OES_egl_sync
+ GL_OES_element_index_uint
+ GL_OES_fragment_precision_high
+ GL_OES_get_program_binary
+ GL_OES_mapbuffer
+ GL_OES_packed_depth_stencil
+ GL_OES_required_internalformat
+ GL_OES_rgb8_rgba8
+ GL_OES_standard_derivatives
+ GL_OES_texture_float
+ GL_OES_texture_half_float
+ GL_OES_vertex_array_object
+ GL_OES_vertex_half_float
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 7 (2012)</d:name>
+ <d:id>Nexus 7</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>large</d:screen-size>
+ <d:diagonal-length>7.0</d:diagonal-length>
+ <d:pixel-density>tvdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>800</d:x-dimension>
+ <d:y-dimension>1280</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>195</d:xdpi>
+ <d:ydpi>200</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">1</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"> </d:removable-storage>
+ <d:cpu> Tegra3 </d:cpu>
+ <d:gpu> Tegra3 </d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_7</d:skin>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions> </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 4</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.7</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>768</d:x-dimension>
+ <d:y-dimension>1280</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>320</d:xdpi>
+ <d:ydpi>320</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="KiB">1953125</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="KiB">7811891</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>Qualcomm Snapdragon S4 Pro</d:cpu>
+ <d:gpu>Adreno 320</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock></d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_4</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles></d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>GL_EXT_debug_marker GL_AMD_compressed_ATC_texture
+ GL_AMD_performance_monitor GL_AMD_program_binary_Z400 GL_EXT_robustness
+ GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence
+ GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
+ GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_OES_standard_derivatives
+ GL_OES_texture_3D GL_OES_texture_float GL_OES_texture_half_float
+ GL_OES_texture_half_float_linear GL_OES_texture_npot GL_OES_vertex_half_float
+ GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object GL_QCOM_alpha_test
+ GL_QCOM_binning_control GL_QCOM_driver_control GL_QCOM_perfmon_global_mode
+ GL_QCOM_extended_get GL_QCOM_extended_get2 GL_QCOM_tiled_rendering
+ GL_QCOM_writeonly_rendering GL_EXT_sRGB
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 10</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>xlarge</d:screen-size>
+ <d:diagonal-length>10.055</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>2560</d:x-dimension>
+ <d:y-dimension>1600</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>300</d:xdpi>
+ <d:ydpi>300</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="KiB">1953125</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="KiB">15623782</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>Dual-core A15</d:cpu>
+ <d:gpu>Quad-core Mali T604</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock></d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_10</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles></d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>GL_EXT_debug_marker GL_ARM_rgba8 GL_ARM_mali_shader_binary
+ GL_OES_depth24 GL_OES_depth_texture GL_OES_depth_texture_cube_map
+ GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_EXT_read_format_bgra
+ GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_standard_derivatives GL_OES_EGL_image GL_OES_EGL_image_external
+ GL_OES_EGL_sync GL_OES_texture_npot GL_OES_vertex_half_float
+ GL_OES_required_internalformat GL_OES_vertex_array_object GL_OES_mapbuffer
+ GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg GL_EXT_texture_type_2_10_10_10_REV
+ GL_OES_fbo_render_mipmap GL_OES_element_index_uint GL_EXT_shadow_samplers
+ GL_EXT_occlusion_query_boolean GL_EXT_blend_minmax GL_EXT_discard_framebuffer
+ GL_OES_get_program_binary GL_OES_texture_3D GL_EXT_texture_storage
+ GL_EXT_multisampled_render_to_texture GL_OES_surfaceless_context
+ GL_ARM_mali_program_binary
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape" default="true">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 7</d:name>
+ <d:id>Nexus 7 2013</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>large</d:screen-size>
+ <d:diagonal-length>7.02</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1200</d:x-dimension>
+ <d:y-dimension>1920</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>323</d:xdpi>
+ <d:ydpi>323</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">2</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">32</d:internal-storage>
+ <d:removable-storage unit="MiB"> </d:removable-storage>
+ <d:cpu> Qualcomm Snapdragon S4 Pro, 1.5GHz </d:cpu>
+ <d:gpu> Adreno 320, 400MHz </d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock></d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_7_2013</d:skin>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>18</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>3.0</d:gl-version>
+ <d:gl-extensions>
+ GL_AMD_compressed_ATC_texture GL_AMD_performance_monitor
+ GL_AMD_program_binary_Z400 GL_EXT_debug_labelGL_EXT_debug_markerGL_EXT_robustness
+ GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence
+ GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
+ GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil GL_OES_depth_texture_cube_map GL_OES_rgb8_rgba8
+ GL_OES_standard_derivatives GL_OES_texture_3D GL_OES_texture_float
+ GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot
+ GL_OES_vertex_half_float GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object
+ GL_QCOM_alpha_test GL_QCOM_binning_control GL_QCOM_driver_control
+ GL_QCOM_perfmon_global_mode GL_QCOM_extended_get GL_QCOM_extended_get2
+ GL_QCOM_tiled_rendering GL_QCOM_writeonly_rendering GL_EXT_sRGB
+ GL_EXT_texture_filter_anisotropic GL_EXT_color_buffer_float
+ GL_EXT_color_buffer_half_float
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 5</d:name>
+ <d:id>Nexus 5</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.95</d:diagonal-length>
+ <d:pixel-density>xxhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1080</d:x-dimension>
+ <d:y-dimension>1920</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>445</d:xdpi>
+ <d:ydpi>445</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">2</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">16</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>Snapdragon 800 (MSM8974)</d:cpu>
+ <d:gpu>Adreno 330</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock></d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_5</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>19</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles></d:bluetooth-profiles>
+ <d:gl-version>3.0</d:gl-version>
+ <d:gl-extensions>
+ GL_AMD_compressed_ATC_texture GL_AMD_performance_monitor GL_AMD_program_binary_Z400
+ GL_EXT_debug_label GL_EXT_debug_marker GL_EXT_discard_framebuffer
+ GL_EXT_robustness GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV
+ GL_NV_fence GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
+ GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil GL_OES_depth_texture_cube_map GL_OES_rgb8_rgba8
+ GL_OES_standard_derivatives GL_OES_texture_3D GL_OES_texture_float
+ GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot
+ GL_OES_vertex_half_float GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object
+ GL_QCOM_alpha_test GL_QCOM_binning_control GL_QCOM_driver_control
+ GL_QCOM_perfmon_global_mode GL_QCOM_extended_get GL_QCOM_extended_get2
+ GL_QCOM_tiled_rendering GL_QCOM_writeonly_rendering GL_EXT_sRGB
+ GL_EXT_texture_filter_anisotropic GL_EXT_color_buffer_float
+ GL_EXT_color_buffer_half_float GL_EXT_disjoint_timer_query
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 6</d:name>
+ <d:id>Nexus 6</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>5.96</d:diagonal-length>
+ <d:pixel-density>560dpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1440</d:x-dimension>
+ <d:y-dimension>2560</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>493</d:xdpi>
+ <d:ydpi>493</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">3</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="MiB">1968</d:internal-storage>
+ <d:removable-storage unit="MiB">0</d:removable-storage>
+ <d:cpu>Quad core Krait 450 CPU 2.7GHz (Qualcomm Snapdragon 805 SOC)</d:cpu>
+ <d:gpu>Adreno 420</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_6</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>21</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>3.0</d:gl-version>
+ <d:gl-extensions>
+ GL_EXT_debug_marker GL_OES_EGL_image
+ GL_OES_EGL_image_external GL_OES_EGL_sync
+ GL_OES_vertex_half_float GL_OES_framebuffer_object
+ GL_OES_rgb8_rgba8 GL_OES_compressed_ETC1_RGB8_texture
+ GL_AMD_compressed_ATC_texture
+ GL_KHR_texture_compression_astc_ldr
+ GL_OES_texture_npot GL_EXT_texture_filter_anisotropic
+ GL_EXT_texture_format_BGRA8888 GL_OES_texture_3D
+ GL_EXT_color_buffer_float
+ GL_EXT_color_buffer_half_float GL_QCOM_alpha_test
+ GL_OES_depth24 GL_OES_packed_depth_stencil
+ GL_OES_depth_texture GL_OES_depth_texture_cube_map
+ GL_EXT_sRGB GL_OES_texture_float
+ GL_OES_texture_float_linear GL_OES_texture_half_float
+ GL_OES_texture_half_float_linear
+ GL_EXT_texture_type_2_10_10_10_REV
+ GL_EXT_texture_sRGB_decode GL_OES_element_index_uint
+ GL_EXT_copy_image GL_EXT_geometry_shader
+ GL_EXT_tessellation_shader GL_OES_texture_stencil8
+ GL_EXT_shader_io_blocks GL_OES_shader_image_atomic
+ GL_OES_sample_variables GL_EXT_texture_border_clamp
+ GL_EXT_multisampled_render_to_texture
+ GL_OES_shader_multisample_interpolation
+ GL_EXT_draw_buffers_indexed GL_EXT_gpu_shader5
+ GL_EXT_robustness GL_EXT_texture_buffer
+ GL_OES_texture_storage_multisample_2d_array
+ GL_OES_sample_shading GL_OES_get_program_binary
+ GL_EXT_debug_label GL_KHR_blend_equation_advanced
+ GL_KHR_blend_equation_advanced_coherent
+ GL_QCOM_tiled_rendering
+ GL_ANDROID_extension_pack_es31a
+ GL_EXT_primitive_bounding_box
+ GL_OES_standard_derivatives GL_OES_vertex_array_object
+ GL_KHR_debug
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 9</d:name>
+ <d:id>Nexus 9</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>xlarge</d:screen-size>
+ <d:diagonal-length>8.86</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>2048</d:x-dimension>
+ <d:y-dimension>1536</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>288.9949951171875</d:xdpi>
+ <d:ydpi>288.9949951171875</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="KiB">1879968</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="MiB">1545</d:internal-storage>
+ <d:removable-storage unit="MiB">0</d:removable-storage>
+ <d:cpu>64-bit NVIDIA Tegra K1 Dual Denver @ 2.3GHz</d:cpu>
+ <d:gpu>192-core Kepler GPU</d:gpu>
+ <d:abi>
+ arm64-v8a
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_9</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>21</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>3.1</d:gl-version>
+ <d:gl-extensions>
+ GL_EXT_debug_marker GL_EXT_blend_minmax
+ GL_EXT_color_buffer_float
+ GL_EXT_color_buffer_half_float GL_EXT_copy_image
+ GL_EXT_debug_label GL_EXT_draw_buffers_indexed
+ GL_EXT_frag_depth GL_EXT_geometry_point_size
+ GL_EXT_geometry_shader GL_EXT_gpu_shader5
+ GL_EXT_map_buffer_range GL_EXT_occlusion_query_boolean
+ GL_EXT_primitive_bounding_box GL_EXT_robustness
+ GL_EXT_separate_shader_objects
+ GL_EXT_shader_implicit_conversions
+ GL_EXT_shader_integer_mix GL_EXT_shader_io_blocks
+ GL_EXT_shadow_samplers GL_EXT_sRGB
+ GL_EXT_sRGB_write_control
+ GL_EXT_tessellation_point_size
+ GL_EXT_tessellation_shader GL_EXT_texture_border_clamp
+ GL_EXT_texture_buffer GL_EXT_texture_compression_dxt1
+ GL_EXT_texture_compression_s3tc
+ GL_EXT_texture_cube_map_array
+ GL_EXT_texture_filter_anisotropic
+ GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg
+ GL_EXT_texture_sRGB_decode GL_EXT_texture_storage
+ GL_EXT_texture_view GL_EXT_unpack_subimage
+ GL_KHR_debug GL_KHR_texture_compression_astc_ldr
+ GL_NV_bgr GL_NV_bindless_texture
+ GL_NV_blend_equation_advanced
+ GL_NV_blend_equation_advanced_coherent
+ GL_NV_copy_buffer GL_NV_copy_image GL_NV_draw_buffers
+ GL_NV_draw_instanced GL_NV_draw_texture
+ GL_NV_EGL_stream_consumer_external
+ GL_NV_explicit_attrib_location
+ GL_NV_fbo_color_attachments GL_NV_framebuffer_blit
+ GL_NV_framebuffer_multisample
+ GL_NV_generate_mipmap_sRGB GL_NV_instanced_arrays
+ GL_NV_occlusion_query_samples
+ GL_NV_non_square_matrices GL_NV_pack_subimage
+ GL_NV_packed_float GL_NV_packed_float_linear
+ GL_NV_path_rendering GL_NV_pixel_buffer_object
+ GL_NV_read_buffer GL_NV_read_depth
+ GL_NV_read_depth_stencil GL_NV_read_stencil
+ GL_NV_secure_context GL_NV_shadow_samplers_array
+ GL_NV_shadow_samplers_cube GL_NV_sRGB_formats
+ GL_NV_texture_array GL_NV_texture_border_clamp
+ GL_NV_texture_compression_latc
+ GL_NV_texture_compression_s3tc
+ GL_NV_texture_compression_s3tc_update
+ GL_NV_timer_query GL_KHR_blend_equation_advanced
+ GL_KHR_blend_equation_advanced_coherent
+ GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth24
+ GL_OES_depth32 GL_OES_depth_texture
+ GL_OES_depth_texture_cube_map GL_OES_EGL_image
+ GL_OES_EGL_image_external GL_OES_EGL_sync
+ GL_OES_element_index_uint GL_OES_fbo_render_mipmap
+ GL_OES_get_program_binary GL_OES_mapbuffer
+ GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8
+ GL_OES_sample_shading GL_OES_sample_variables
+ GL_OES_shader_image_atomic
+ GL_OES_shader_multisample_interpolation
+ GL_OES_standard_derivatives GL_OES_surfaceless_context
+ GL_OES_texture_npot GL_OES_texture_float
+ GL_OES_texture_float_linear GL_OES_texture_half_float
+ GL_OES_texture_half_float_linear
+ GL_OES_texture_stencil8
+ GL_OES_texture_storage_multisample_2d_array
+ GL_OES_vertex_array_object GL_OES_vertex_half_float
+ GL_ANDROID_extension_pack_es31a
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait">
+ <d:description>The tablet in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape" default="true">
+ <d:description>The tablet in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 5X</d:name>
+ <d:id>Nexus 5X</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>5.20</d:diagonal-length>
+ <d:pixel-density>420dpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1080</d:x-dimension>
+ <d:y-dimension>1920</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>422.0299987792969</d:xdpi>
+ <d:ydpi>424.0690002441406</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ <!--Fingerprint-->
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">2</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">16</d:internal-storage>
+ <d:removable-storage unit="GiB">2</d:removable-storage>
+ <d:cpu>Qualcomm Snapdragon 808 processor, 1.8 GHz hexa-core 64-bit</d:cpu>
+ <d:gpu>Qualcomm Adreno 418</d:gpu>
+ <d:abi>
+ arm64-v8a
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_5x</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>23</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>3.1</d:gl-version>
+ <d:gl-extensions>
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_EGL_sync GL_OES_vertex_half_float
+ GL_OES_framebuffer_object GL_OES_rgb8_rgba8 GL_OES_compressed_ETC1_RGB8_texture
+ GL_AMD_compressed_ATC_texture GL_KHR_texture_compression_astc_ldr
+ GL_OES_texture_npot GL_EXT_texture_filter_anisotropic GL_EXT_texture_format_BGRA8888
+ GL_OES_texture_3D GL_EXT_color_buffer_float GL_EXT_color_buffer_half_float
+ GL_QCOM_alpha_test GL_OES_depth24 GL_OES_packed_depth_stencil GL_OES_depth_texture
+ GL_OES_depth_texture_cube_map GL_EXT_sRGB GL_OES_texture_float
+ GL_OES_texture_float_linear GL_OES_texture_half_float
+ GL_OES_texture_half_float_linear GL_EXT_texture_type_2_10_10_10_REV
+ GL_EXT_texture_sRGB_decode GL_OES_element_index_uint GL_EXT_copy_image
+ GL_EXT_geometry_shader GL_EXT_tessellation_shader GL_OES_texture_stencil8
+ GL_EXT_shader_io_blocks GL_OES_shader_image_atomic GL_OES_sample_variables
+ GL_EXT_texture_border_clamp GL_EXT_multisampled_render_to_texture
+ GL_OES_shader_multisample_interpolation GL_EXT_texture_cube_map_array
+ GL_EXT_draw_buffers_indexed GL_EXT_gpu_shader5 GL_EXT_robustness
+ GL_EXT_texture_buffer GL_OES_texture_storage_multisample_2d_array
+ GL_OES_sample_shading GL_OES_get_program_binary GL_EXT_debug_label
+ GL_KHR_blend_equation_advanced GL_KHR_blend_equation_advanced_coherent
+ GL_QCOM_tiled_rendering GL_ANDROID_extension_pack_es31a
+ GL_EXT_primitive_bounding_box GL_OES_standard_derivatives GL_OES_vertex_array_object
+ GL_EXT_disjoint_timer_query GL_KHR_debug GL_EXT_YUV_target GL_EXT_sRGB_write_control
+ GL_EXT_texture_norm16 GL_EXT_discard_framebuffer GL_OES_surfaceless_context
+ GL_OVR_multiview GL_OVR_multiview2 GL_EXT_texture_sRGB_R8 GL_KHR_no_error
+ GL_EXT_debug_marker OES_EGL_image_external_essl3
+ GL_OVR_multiview_multisampled_render_to_texture GL_EXT_buffer_storage
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The device in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The device in tablet view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 6P</d:name>
+ <d:id>Nexus 6P</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>5.7</d:diagonal-length>
+ <d:pixel-density>560dpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1440</d:x-dimension>
+ <d:y-dimension>2560</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>537.8820190429688</d:xdpi>
+ <d:ydpi>537.3880004882813</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ <!--Fingerprint-->
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">3</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">32</d:internal-storage>
+ <d:removable-storage unit="GiB">2</d:removable-storage>
+ <d:cpu>Qualcomm Snapdragon 810 v2.1 processor, 2.0 GHz octa-core 64-bit</d:cpu>
+ <d:gpu>Qualcomm Adreno 430</d:gpu>
+ <d:abi>
+ arm64-v8a
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ <d:skin>nexus_6p</d:skin>
+ </d:hardware>
+ <d:software>
+ <d:api-level>23</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>3.1</d:gl-version>
+ <d:gl-extensions>
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_EGL_sync GL_OES_vertex_half_float
+ GL_OES_framebuffer_object GL_OES_rgb8_rgba8 GL_OES_compressed_ETC1_RGB8_texture
+ GL_AMD_compressed_ATC_texture GL_KHR_texture_compression_astc_ldr
+ GL_OES_texture_npot GL_EXT_texture_filter_anisotropic GL_EXT_texture_format_BGRA8888
+ GL_OES_texture_3D GL_EXT_color_buffer_float GL_EXT_color_buffer_half_float
+ GL_QCOM_alpha_test GL_OES_depth24 GL_OES_packed_depth_stencil GL_OES_depth_texture
+ GL_OES_depth_texture_cube_map GL_EXT_sRGB GL_OES_texture_float
+ GL_OES_texture_float_linear GL_OES_texture_half_float
+ GL_OES_texture_half_float_linear GL_EXT_texture_type_2_10_10_10_REV
+ GL_EXT_texture_sRGB_decode GL_OES_element_index_uint GL_EXT_copy_image
+ GL_EXT_geometry_shader GL_EXT_tessellation_shader GL_OES_texture_stencil8
+ GL_EXT_shader_io_blocks GL_OES_shader_image_atomic GL_OES_sample_variables
+ GL_EXT_texture_border_clamp GL_EXT_multisampled_render_to_texture
+ GL_OES_shader_multisample_interpolation GL_EXT_texture_cube_map_array
+ GL_EXT_draw_buffers_indexed GL_EXT_gpu_shader5 GL_EXT_robustness
+ GL_EXT_texture_buffer GL_OES_texture_storage_multisample_2d_array
+ GL_OES_sample_shading GL_OES_get_program_binary GL_EXT_debug_label
+ GL_KHR_blend_equation_advanced GL_KHR_blend_equation_advanced_coherent
+ GL_QCOM_tiled_rendering GL_ANDROID_extension_pack_es31a
+ GL_EXT_primitive_bounding_box GL_OES_standard_derivatives GL_OES_vertex_array_object
+ GL_EXT_disjoint_timer_query GL_KHR_debug GL_EXT_YUV_target GL_EXT_sRGB_write_control
+ GL_EXT_texture_norm16 GL_EXT_discard_framebuffer GL_OES_surfaceless_context
+ GL_OVR_multiview GL_OVR_multiview2 GL_EXT_texture_sRGB_R8 GL_EXT_debug_marker
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The device in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The device in tablet view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+</d:devices>
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/tv.xml b/sdklib/src/main/java/com/android/sdklib/devices/tv.xml
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/tv.xml
rename to sdklib/src/main/java/com/android/sdklib/devices/tv.xml
diff --git a/base/sdklib/src/main/java/com/android/sdklib/devices/wear.xml b/sdklib/src/main/java/com/android/sdklib/devices/wear.xml
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/devices/wear.xml
rename to sdklib/src/main/java/com/android/sdklib/devices/wear.xml
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java
new file mode 100644
index 0000000..9bef44f
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdInfo.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2008 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.sdklib.internal.avd;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.devices.Abi;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.targets.SystemImage;
+import com.google.common.base.Strings;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * An immutable structure describing an Android Virtual Device.
+ */
+public final class AvdInfo implements Comparable<AvdInfo> {
+
+ /**
+ * Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid.
+ */
+ public enum AvdStatus {
+ /** No error */
+ OK,
+ /** Missing 'path' property in the ini file */
+ ERROR_PATH,
+ /** Missing config.ini file in the AVD data folder */
+ ERROR_CONFIG,
+ /** Unable to parse config.ini */
+ ERROR_PROPERTIES,
+ /** System Image folder in config.ini doesn't exist */
+ ERROR_IMAGE_DIR,
+ /** The {@link Device} this AVD is based on has changed from its original configuration*/
+ ERROR_DEVICE_CHANGED,
+ /** The {@link Device} this AVD is based on is no longer available */
+ ERROR_DEVICE_MISSING,
+ /** the {@link SystemImage} this AVD is based on is no longer available */
+ ERROR_IMAGE_MISSING
+ }
+
+ private final String mName;
+ private final File mIniFile;
+ private final String mFolderPath;
+ /** An immutable map of properties. This must not be modified. Map can be empty. Never null. */
+ private final Map<String, String> mProperties;
+ private final AvdStatus mStatus;
+ private final ISystemImage mSystemImage;
+
+
+ /**
+ * Creates a new valid AVD info. Values are immutable.
+ * <p/>
+ * Such an AVD is available and can be used.
+ * The error string is set to null.
+ *
+ * @param name The name of the AVD (for display or reference)
+ * @param iniFile The path to the config.ini file
+ * @param folderPath The path to the data directory
+ * @param systemImage The system image.
+ * @param properties The property map. If null, an empty map will be created.
+ */
+ public AvdInfo(@NonNull String name,
+ @NonNull File iniFile,
+ @NonNull String folderPath,
+ @NonNull ISystemImage systemImage,
+ @Nullable Map<String, String> properties) {
+ this(name, iniFile, folderPath,
+ systemImage, properties, AvdStatus.OK);
+ }
+
+ /**
+ * Creates a new <em>invalid</em> AVD info. Values are immutable.
+ * <p/>
+ * Such an AVD is not complete and cannot be used.
+ * The error string must be non-null.
+ *
+ * @param name The name of the AVD (for display or reference)
+ * @param iniFile The path to the config.ini file
+ * @param folderPath The path to the data directory
+ * @param systemImage The system image. Can be null if the image wasn't found.
+ * @param properties The property map. If null, an empty map will be created.
+ * @param status The {@link AvdStatus} of this AVD. Cannot be null.
+ */
+ public AvdInfo(@NonNull String name,
+ @NonNull File iniFile,
+ @NonNull String folderPath,
+ @Nullable ISystemImage systemImage,
+ @Nullable Map<String, String> properties,
+ @NonNull AvdStatus status) {
+ mName = name;
+ mIniFile = iniFile;
+ mFolderPath = folderPath;
+ mSystemImage = systemImage;
+ mProperties = properties == null ? Collections.<String, String>emptyMap()
+ : Collections.unmodifiableMap(properties);
+ mStatus = status;
+ }
+
+ /** Returns the name of the AVD. */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Returns the path of the AVD data directory. */
+ @NonNull
+ public String getDataFolderPath() {
+ return mFolderPath;
+ }
+
+ /** Returns the tag id/display of the AVD. */
+ @NonNull
+ public IdDisplay getTag() {
+ String id = getProperties().get(AvdManager.AVD_INI_TAG_ID);
+ if (id == null) {
+ return SystemImage.DEFAULT_TAG;
+ }
+ String display = getProperties().get(AvdManager.AVD_INI_TAG_DISPLAY);
+ return IdDisplay.create(id, display == null ? id : display);
+ }
+
+ /** Returns the processor type of the AVD. */
+ @NonNull
+ public String getAbiType() {
+ return getProperties().get(AvdManager.AVD_INI_ABI_TYPE);
+ }
+
+ @NonNull
+ public AndroidVersion getAndroidVersion() {
+ String apiStr = getProperties().get(AvdManager.AVD_INI_ANDROID_API);
+ String codename = getProperties().get(AvdManager.AVD_INI_ANDROID_CODENAME);
+ int api = 1;
+ if (!Strings.isNullOrEmpty(apiStr)) {
+ try {
+ api = Integer.parseInt(apiStr);
+ }
+ catch (NumberFormatException e) {
+ // continue with the default
+ }
+ }
+ return new AndroidVersion(api, codename);
+ }
+
+ @NonNull
+ public String getCpuArch() {
+ String cpuArch = mProperties.get(AvdManager.AVD_INI_CPU_ARCH);
+ if (cpuArch != null) {
+ return cpuArch;
+ }
+
+ // legacy
+ return SdkConstants.CPU_ARCH_ARM;
+ }
+
+ @NonNull
+ public String getDeviceManufacturer() {
+ String deviceManufacturer = mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
+ if (deviceManufacturer != null && !deviceManufacturer.isEmpty()) {
+ return deviceManufacturer;
+ }
+
+ return ""; // $NON-NLS-1$
+ }
+
+ @NonNull
+ public String getDeviceName() {
+ String deviceName = mProperties.get(AvdManager.AVD_INI_DEVICE_NAME);
+ if (deviceName != null && !deviceName.isEmpty()) {
+ return deviceName;
+ }
+
+ return ""; // $NON-NLS-1$
+ }
+
+ /** Convenience function to return a more user friendly name of the abi type. */
+ @NonNull
+ public static String getPrettyAbiType(@NonNull AvdInfo avdInfo) {
+ return getPrettyAbiType(avdInfo.getTag(), avdInfo.getAbiType());
+ }
+
+ /** Convenience function to return a more user friendly name of the abi type. */
+ @NonNull
+ public static String getPrettyAbiType(@NonNull ISystemImage sysImg) {
+ return getPrettyAbiType(sysImg.getTag(), sysImg.getAbiType());
+ }
+
+ /** Convenience function to return a more user friendly name of the abi type. */
+ @NonNull
+ public static String getPrettyAbiType(@NonNull IdDisplay tag, @NonNull String rawAbi) {
+ String s = ""; // $NON-NLS-1$
+
+ if (!SystemImage.DEFAULT_TAG.equals(tag)) {
+ s = tag.getDisplay() + ' ';
+ }
+
+ Abi abi = Abi.getEnum(rawAbi);
+ s += (abi == null ? rawAbi : abi.getDisplayName()) + " (" + rawAbi + ')';
+
+ return s;
+ }
+
+ /**
+ * Gets the system image for this AVD. Can be null if the system image is not found.
+ */
+ @Nullable
+ public ISystemImage getSystemImage() {
+ return mSystemImage;
+ }
+
+ /** Returns the {@link AvdStatus} of the receiver. */
+ @NonNull
+ public AvdStatus getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Helper method that returns the default AVD folder that would be used for a given
+ * AVD name <em>if and only if</em> the AVD was created with the default choice.
+ * <p/>
+ * Callers must NOT use this to "guess" the actual folder from an actual AVD since
+ * the purpose of the AVD .ini file is to be able to change this folder. Callers
+ * should however use this to create a new {@link AvdInfo} to setup its data folder
+ * to the default.
+ * <p/>
+ * The default is {@code getDefaultAvdFolder()/avdname.avd/}.
+ * <p/>
+ * For an actual existing AVD, callers must use {@link #getDataFolderPath()} instead.
+ *
+ * @param manager The AVD Manager, used to get the AVD storage path.
+ * @param avdName The name of the desired AVD.
+ * @param unique Whether to return the default or a unique variation of the default.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ @NonNull
+ public static File getDefaultAvdFolder(@NonNull AvdManager manager, @NonNull String avdName,
+ @NonNull FileOp fileOp, boolean unique)
+ throws AndroidLocationException {
+ String base = manager.getBaseAvdFolder();
+ File result = new File(base, avdName + AvdManager.AVD_FOLDER_EXTENSION);
+ if (unique) {
+ int suffix = 0;
+ while (fileOp.exists(result)) {
+ result = new File(base, String.format("%s_%d%s", avdName, (++suffix),
+ AvdManager.AVD_FOLDER_EXTENSION));
+ }
+ }
+ return result;
+ }
+
+ /** Compatibility forwarding until the usages in tools/swt are updated */
+ @Deprecated
+ @NonNull
+ public static File getDefaultAvdFolder(@NonNull AvdManager manager, @NonNull String avdName,
+ @NonNull FileOp fileOp)
+ throws AndroidLocationException {
+ return getDefaultAvdFolder(manager, avdName, fileOp, false);
+ }
+
+ /**
+ * Helper method that returns the .ini {@link File} for a given AVD name.
+ * <p/>
+ * The default is {@code getDefaultAvdFolder()/avdname.ini}.
+ *
+ * @param manager The AVD Manager, used to get the AVD storage path.
+ * @param avdName The name of the desired AVD.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ @NonNull
+ public static File getDefaultIniFile(@NonNull AvdManager manager, @NonNull String avdName)
+ throws AndroidLocationException {
+ String avdRoot = manager.getBaseAvdFolder();
+ return new File(avdRoot, avdName + AvdManager.INI_EXTENSION);
+ }
+
+ /**
+ * Returns the .ini {@link File} for this AVD.
+ */
+ @NonNull
+ public File getIniFile() {
+ return mIniFile;
+ }
+
+ /**
+ * Helper method that returns the Config {@link File} for a given AVD name.
+ */
+ @NonNull
+ public static File getConfigFile(@NonNull String path) {
+ return new File(path, AvdManager.CONFIG_INI);
+ }
+
+ /**
+ * Returns the Config {@link File} for this AVD.
+ */
+ @NonNull
+ public File getConfigFile() {
+ return getConfigFile(mFolderPath);
+ }
+
+ /**
+ * Returns an unmodifiable map of properties for the AVD.
+ * This can be empty but not null.
+ * Callers must NOT try to modify this immutable map.
+ */
+ @NonNull
+ public Map<String, String> getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Returns the error message for the AVD or <code>null</code> if {@link #getStatus()}
+ * returns {@link AvdStatus#OK}
+ */
+ @Nullable
+ public String getErrorMessage() {
+ switch (mStatus) {
+ case ERROR_PATH:
+ return String.format("Missing AVD 'path' property in %1$s", getIniFile());
+ case ERROR_CONFIG:
+ return String.format("Missing config.ini file in %1$s", mFolderPath);
+ case ERROR_PROPERTIES:
+ return String.format("Failed to parse properties from %1$s",
+ getConfigFile());
+ case ERROR_IMAGE_DIR:
+ case ERROR_IMAGE_MISSING:
+ return String.format(
+ "Missing system image for %1$s%2$s %3$s.'",
+ SystemImage.DEFAULT_TAG.equals(getTag()) ? "" : (getTag().getDisplay() + " "),
+ getAbiType(),
+ mName);
+ case ERROR_DEVICE_CHANGED:
+ return String.format("%1$s %2$s configuration has changed since AVD creation",
+ mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
+ mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
+ case ERROR_DEVICE_MISSING:
+ return String.format("%1$s %2$s no longer exists as a device",
+ mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
+ mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
+ case OK:
+ return null;
+ }
+
+ return null;
+ }
+
+ /**
+ * Compares this object with the specified object for order. Returns a
+ * negative integer, zero, or a positive integer as this object is less
+ * than, equal to, or greater than the specified object.
+ *
+ * @param o the Object to be compared.
+ * @return a negative integer, zero, or a positive integer as this object is
+ * less than, equal to, or greater than the specified object.
+ */
+ @Override
+ public int compareTo(AvdInfo o) {
+ int imageDiff = 0;
+ if (mSystemImage == null) {
+ if (o.mSystemImage == null) {
+ imageDiff = 0;
+ }
+ else {
+ imageDiff = -1;
+ }
+ }
+ else {
+ if (o.mSystemImage == null) {
+ imageDiff = 1;
+ }
+ else {
+ imageDiff = mSystemImage.compareTo(o.mSystemImage);
+ }
+ }
+
+ if (imageDiff == 0) {
+ // same image? compare on the avd name
+ return mName.compareTo(o.mName);
+ }
+
+ return imageDiff;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
new file mode 100644
index 0000000..796cccf
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
@@ -0,0 +1,1990 @@
+/*
+ * Copyright (C) 2008 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.sdklib.internal.avd;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.io.IAbstractFile;
+import com.android.io.StreamException;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.FileOpFileWrapper;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.devices.Abi;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.DeviceManager;
+import com.android.sdklib.devices.DeviceManager.DeviceStatus;
+import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.LoggerProgressIndicatorWrapper;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput.IProcessOutput;
+import com.android.utils.GrabProcessOutput.Wait;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+import com.google.common.base.Charsets;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Table;
+import com.google.common.io.Closeables;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.ref.WeakReference;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Android Virtual Device Manager to manage AVDs.
+ */
+public class AvdManager {
+
+ /**
+ * Exception thrown when something is wrong with a target path.
+ */
+ private static final class InvalidTargetPathException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ InvalidTargetPathException(String message) {
+ super(message);
+ }
+ }
+
+ private static final Pattern INI_LINE_PATTERN =
+ Pattern.compile("^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$"); //$NON-NLS-1$
+
+ public static final String AVD_FOLDER_EXTENSION = ".avd"; //$NON-NLS-1$
+
+ /** Charset encoding used by the avd.ini/config.ini. */
+ public static final String AVD_INI_ENCODING = "avd.ini.encoding"; //$NON-NLS-1$
+
+ /**
+ * The *absolute* path to the AVD folder (which contains the #CONFIG_INI file).
+ */
+ public static final String AVD_INFO_ABS_PATH = "path"; //$NON-NLS-1$
+
+ /**
+ * The path to the AVD folder (which contains the #CONFIG_INI file) relative to
+ * the {@link AndroidLocation#FOLDER_DOT_ANDROID}. This information is written
+ * in the avd ini <b>only</b> if the AVD folder is located under the .android path
+ * (that is the relative that has no backward {@code ..} references).
+ */
+ public static final String AVD_INFO_REL_PATH = "path.rel"; //$NON-NLS-1$
+
+ /**
+ * The {@link IAndroidTarget#hashString()} of the AVD.
+ */
+ public static final String AVD_INFO_TARGET = "target"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the tag id of the specific avd
+ */
+ public static final String AVD_INI_TAG_ID = "tag.id"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the tag display of the specific avd
+ */
+ public static final String AVD_INI_TAG_DISPLAY = "tag.display"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the abi type of the specific avd
+ */
+ public static final String AVD_INI_ABI_TYPE = "abi.type"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the CPU architecture of the specific avd
+ */
+ public static final String AVD_INI_CPU_ARCH = "hw.cpu.arch"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the CPU architecture of the specific avd
+ */
+ public static final String AVD_INI_CPU_MODEL = "hw.cpu.model"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the number of processors to emulate when SMP is supported.
+ */
+ public static final String AVD_INI_CPU_CORES = "hw.cpu.ncore"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the manufacturer of the device this avd was based on.
+ */
+ public static final String AVD_INI_DEVICE_MANUFACTURER = "hw.device.manufacturer"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the name of the device this avd was based on.
+ */
+ public static final String AVD_INI_DEVICE_NAME = "hw.device.name"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the SDK-relative path of the skin folder, if any,
+ * or a 320x480 like constant for a numeric skin size.
+ *
+ * @see #NUMERIC_SKIN_SIZE
+ */
+ public static final String AVD_INI_SKIN_PATH = "skin.path"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the SDK-relative path of the skin folder to be selected if
+ * skins for this device become enabled.
+ */
+ public static final String AVD_INI_BACKUP_SKIN_PATH = "skin.path.backup"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing an UI name for the skin.
+ * This config key is ignored by the emulator. It is only used by the SDK manager or
+ * tools to give a friendlier name to the skin.
+ * If missing, use the {@link #AVD_INI_SKIN_PATH} key instead.
+ */
+ public static final String AVD_INI_SKIN_NAME = "skin.name"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing whether a dynamic skin should be displayed.
+ */
+ public static final String AVD_INI_SKIN_DYNAMIC = "skin.dynamic"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the path to the sdcard file.
+ * If missing, the default name "sdcard.img" will be used for the sdcard, if there's such
+ * a file.
+ *
+ * @see #SDCARD_IMG
+ */
+ public static final String AVD_INI_SDCARD_PATH = "sdcard.path"; //$NON-NLS-1$
+ /**
+ * AVD/config.ini key name representing the size of the SD card.
+ * This property is for UI purposes only. It is not used by the emulator.
+ *
+ * @see #SDCARD_SIZE_PATTERN
+ * @see #parseSdcardSize(String, String[])
+ */
+ public static final String AVD_INI_SDCARD_SIZE = "sdcard.size"; //$NON-NLS-1$
+ /**
+ * AVD/config.ini key name representing the first path where the emulator looks
+ * for system images. Typically this is the path to the add-on system image or
+ * the path to the platform system image if there's no add-on.
+ * <p/>
+ * The emulator looks at {@link #AVD_INI_IMAGES_1} before {@link #AVD_INI_IMAGES_2}.
+ */
+ public static final String AVD_INI_IMAGES_1 = "image.sysdir.1"; //$NON-NLS-1$
+ /**
+ * AVD/config.ini key name representing the second path where the emulator looks
+ * for system images. Typically this is the path to the platform system image.
+ *
+ * @see #AVD_INI_IMAGES_1
+ */
+ public static final String AVD_INI_IMAGES_2 = "image.sysdir.2"; //$NON-NLS-1$
+ /**
+ * AVD/config.ini key name representing the presence of the snapshots file.
+ * This property is for UI purposes only. It is not used by the emulator.
+ *
+ * @see #SNAPSHOTS_IMG
+ */
+ public static final String AVD_INI_SNAPSHOT_PRESENT = "snapshot.present"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing whether hardware OpenGLES emulation is enabled
+ */
+ public static final String AVD_INI_GPU_EMULATION = "hw.gpu.enabled"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing which software OpenGLES should be used
+ */
+ public static final String AVD_INI_GPU_MODE = "hw.gpu.mode";
+
+ /**
+ * AVD/config.ini key name representing how to emulate the front facing camera
+ */
+ public static final String AVD_INI_CAMERA_FRONT = "hw.camera.front"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing how to emulate the rear facing camera
+ */
+ public static final String AVD_INI_CAMERA_BACK = "hw.camera.back"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the amount of RAM the emulated device should have
+ */
+ public static final String AVD_INI_RAM_SIZE = "hw.ramSize";
+
+ /**
+ * AVD/config.ini key name representing the amount of memory available to applications by default
+ */
+ public static final String AVD_INI_VM_HEAP_SIZE = "vm.heapSize";
+
+ /**
+ * AVD/config.ini key name representing the size of the data partition
+ */
+ public static final String AVD_INI_DATA_PARTITION_SIZE = "disk.dataPartition.size";
+
+ /**
+ * AVD/config.ini key name representing the hash of the device this AVD is based on. <br/>
+ * This old hash is deprecated and shouldn't be used anymore.
+ * It represents the Device.hashCode() and is not stable accross implementations.
+ * @see #AVD_INI_DEVICE_HASH_V2
+ */
+ public static final String AVD_INI_DEVICE_HASH_V1 = "hw.device.hash";
+
+ /**
+ * AVD/config.ini key name representing the hash of the device hardware properties
+ * actually present in the config.ini. This replaces {@link #AVD_INI_DEVICE_HASH_V1}.
+ * <p/>
+ * To find this hash, use
+ * {@code DeviceManager.getHardwareProperties(device).get(AVD_INI_DEVICE_HASH_V2)}.
+ */
+ public static final String AVD_INI_DEVICE_HASH_V2 = "hw.device.hash2";
+
+ /**
+ * The API level of this AVD. Derived from the target hash.
+ */
+ public static final String AVD_INI_ANDROID_API = "image.androidVersion.api";
+
+ /**
+ * The API codename of this AVD. Derived from the target hash.
+ */
+ public static final String AVD_INI_ANDROID_CODENAME = "image.androidVersion.codename";
+
+ /**
+ * Pattern to match pixel-sized skin "names", e.g. "320x480".
+ */
+ public static final Pattern NUMERIC_SKIN_SIZE = Pattern.compile("([0-9]{2,})x([0-9]{2,})"); //$NON-NLS-1$
+
+ private static final String USERDATA_IMG = "userdata.img"; //$NON-NLS-1$
+ private static final String BOOT_PROP = "boot.prop"; //$NON-NLS-1$
+ static final String CONFIG_INI = "config.ini"; //$NON-NLS-1$
+ private static final String SDCARD_IMG = "sdcard.img"; //$NON-NLS-1$
+ private static final String SNAPSHOTS_IMG = "snapshots.img"; //$NON-NLS-1$
+
+ static final String INI_EXTENSION = ".ini"; //$NON-NLS-1$
+ private static final Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + //$NON-NLS-1$
+ INI_EXTENSION + "$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern IMAGE_NAME_PATTERN = Pattern.compile("(.+)\\.img$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Pattern for matching SD Card sizes, e.g. "4K" or "16M".
+ * Callers should use {@link #parseSdcardSize(String, String[])} instead of using this directly.
+ */
+ private static final Pattern SDCARD_SIZE_PATTERN = Pattern.compile("(\\d+)([KMG])"); //$NON-NLS-1$
+
+ /**
+ * Minimal size of an SDCard image file in bytes. Currently 9 MiB.
+ */
+
+ public static final long SDCARD_MIN_BYTE_SIZE = 9<<20;
+ /**
+ * Maximal size of an SDCard image file in bytes. Currently 1023 GiB.
+ */
+ public static final long SDCARD_MAX_BYTE_SIZE = 1023L<<30;
+
+ /** The sdcard string represents a valid number but the size is outside of the allowed range. */
+ public static final int SDCARD_SIZE_NOT_IN_RANGE = 0;
+ /** The sdcard string looks like a size number+suffix but the number failed to decode. */
+ public static final int SDCARD_SIZE_INVALID = -1;
+ /** The sdcard string doesn't look like a size, it might be a path instead. */
+ public static final int SDCARD_NOT_SIZE_PATTERN = -2;
+
+ /** Regex used to validate characters that compose an AVD name. */
+ public static final Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+"); //$NON-NLS-1$
+
+ /** List of valid characters for an AVD name. Used for display purposes. */
+ public static final String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -"; //$NON-NLS-1$
+
+ public static final String HARDWARE_INI = "hardware.ini"; //$NON-NLS-1$
+
+ /**
+ * Status returned by {@link AvdManager#isAvdNameConflicting(String)}.
+ */
+ public enum AvdConflict {
+ /** There is no known conflict for the given AVD name. */
+ NO_CONFLICT,
+ /** The AVD name conflicts with an existing valid AVD. */
+ CONFLICT_EXISTING_AVD,
+ /** The AVD name conflicts with an existing invalid AVD. */
+ CONFLICT_INVALID_AVD,
+ /**
+ * The AVD name does not conflict with any known AVD however there are
+ * files or directory that would cause a conflict if this were to be created.
+ */
+ CONFLICT_EXISTING_PATH,
+ }
+
+ // A map where the keys are the locations of the SDK and the values are the corresponding
+ // AvdManagers. This prevents us from creating multiple AvdManagers for the same SDK and having
+ // them get out of sync.
+ private static final Table<String, FileOp, WeakReference<AvdManager>> mManagers =
+ HashBasedTable.create();
+
+ private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
+ private AvdInfo[] mValidAvdList;
+ private AvdInfo[] mBrokenAvdList;
+ private final AndroidSdkHandler mSdkHandler;
+ private final Map<ILogger, DeviceManager> mDeviceManagers =
+ new HashMap<ILogger, DeviceManager>();
+ private final FileOp mFop;
+
+ protected AvdManager(@NonNull AndroidSdkHandler sdkHandler, @NonNull ILogger log,
+ @NonNull FileOp fop)
+ throws AndroidLocationException {
+ mSdkHandler = sdkHandler;
+ mFop = fop;
+ buildAvdList(mAllAvdList, log);
+ }
+
+ /**
+ * Returns an AVD Manager for a given SDK represented by {@code sdkHandler}.
+ * One AVD Manager instance is created by SDK location and then cached and reused.
+ *
+ * @param sdkHandler The SDK handler.
+ * @param log The log object to receive the log of the initial loading of the AVDs.
+ * This log object is not kept by this instance of AvdManager and each
+ * method takes its own logger. The rationale is that the AvdManager
+ * might be called from a variety of context, each with different
+ * logging needs. Cannot be null.
+ * @return The AVD Manager instance.
+ * @throws AndroidLocationException if {@code sdkHandler} does not have a local path set.
+ */
+ @Nullable
+ public static AvdManager getInstance(@NonNull AndroidSdkHandler sdkHandler,
+ @NonNull ILogger log)
+ throws AndroidLocationException {
+ return getInstance(sdkHandler, log, FileOpUtils.create());
+ }
+
+ @Nullable
+ public static AvdManager getInstance(@NonNull AndroidSdkHandler sdkHandler,
+ @NonNull ILogger log, @NonNull FileOp fop)
+ throws AndroidLocationException {
+ if (sdkHandler.getLocation() == null) {
+ throw new AndroidLocationException("Local SDK path not set!");
+ }
+ synchronized(mManagers) {
+ AvdManager manager;
+ WeakReference<AvdManager> ref = mManagers
+ .get(sdkHandler.getLocation().getPath(), fop);
+ if (ref != null && (manager = ref.get()) != null) {
+ return manager;
+ }
+ try {
+ manager = new AvdManager(sdkHandler, log, fop);
+ }
+ catch (AndroidLocationException e){
+ throw e;
+ }
+ catch (Exception e) {
+ log.error(e, "Exception during AvdManager initialization!");
+ return null;
+ }
+ mManagers.put(sdkHandler.getLocation().getPath(), fop,
+ new WeakReference<AvdManager>(manager));
+ return manager;
+ }
+ }
+
+ /**
+ * Returns the base folder where AVDs are created.
+ *
+ * @throws AndroidLocationException
+ */
+ @NonNull
+ public String getBaseAvdFolder() throws AndroidLocationException {
+ assert AndroidLocation.getFolder().endsWith(File.separator);
+ return AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+ }
+
+ /**
+ * Parse the sdcard string to decode the size.
+ * Returns:
+ * <ul>
+ * <li> The size in bytes > 0 if the sdcard string is a valid size in the allowed range.
+ * <li> {@link #SDCARD_SIZE_NOT_IN_RANGE} (0)
+ * if the sdcard string is a valid size NOT in the allowed range.
+ * <li> {@link #SDCARD_SIZE_INVALID} (-1)
+ * if the sdcard string is number that fails to parse correctly.
+ * <li> {@link #SDCARD_NOT_SIZE_PATTERN} (-2)
+ * if the sdcard string is not a number, in which case it's probably a file path.
+ * </ul>
+ *
+ * @param sdcard The sdcard string, which can be a file path, a size string or something else.
+ * @param parsedStrings If non-null, an array of 2 strings. The first string will be
+ * filled with the parsed numeric size and the second one will be filled with the
+ * parsed suffix. This is filled even if the returned size is deemed out of range or
+ * failed to parse. The values are null if the sdcard is not a size pattern.
+ * @return A size in byte if > 0, or {@link #SDCARD_SIZE_NOT_IN_RANGE},
+ * {@link #SDCARD_SIZE_INVALID} or {@link #SDCARD_NOT_SIZE_PATTERN} as error codes.
+ */
+ public static long parseSdcardSize(@NonNull String sdcard, @Nullable String[] parsedStrings) {
+
+ if (parsedStrings != null) {
+ assert parsedStrings.length == 2;
+ parsedStrings[0] = null;
+ parsedStrings[1] = null;
+ }
+
+ Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard);
+ if (m.matches()) {
+ if (parsedStrings != null) {
+ assert parsedStrings.length == 2;
+ parsedStrings[0] = m.group(1);
+ parsedStrings[1] = m.group(2);
+ }
+
+ // get the sdcard values for checks
+ try {
+ long sdcardSize = Long.parseLong(m.group(1));
+
+ String sdcardSizeModifier = m.group(2);
+ if ("K".equals(sdcardSizeModifier)) { //$NON-NLS-1$
+ sdcardSize <<= 10;
+ } else if ("M".equals(sdcardSizeModifier)) { //$NON-NLS-1$
+ sdcardSize <<= 20;
+ } else if ("G".equals(sdcardSizeModifier)) { //$NON-NLS-1$
+ sdcardSize <<= 30;
+ }
+
+ if (sdcardSize < SDCARD_MIN_BYTE_SIZE ||
+ sdcardSize > SDCARD_MAX_BYTE_SIZE) {
+ return SDCARD_SIZE_NOT_IN_RANGE;
+ }
+
+ return sdcardSize;
+ } catch (NumberFormatException e) {
+ // This could happen if the number is too large to fit in a long.
+ return SDCARD_SIZE_INVALID;
+ }
+ }
+
+ return SDCARD_NOT_SIZE_PATTERN;
+ }
+
+ /**
+ * Returns all the existing AVDs.
+ * @return a newly allocated array containing all the AVDs.
+ */
+ @NonNull
+ public AvdInfo[] getAllAvds() {
+ synchronized (mAllAvdList) {
+ return mAllAvdList.toArray(new AvdInfo[mAllAvdList.size()]);
+ }
+ }
+
+ /**
+ * Returns all the valid AVDs.
+ * @return a newly allocated array containing all valid the AVDs.
+ */
+ @NonNull
+ public AvdInfo[] getValidAvds() {
+ synchronized (mAllAvdList) {
+ if (mValidAvdList == null) {
+ ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
+ for (AvdInfo avd : mAllAvdList) {
+ if (avd.getStatus() == AvdStatus.OK) {
+ list.add(avd);
+ }
+ }
+
+ mValidAvdList = list.toArray(new AvdInfo[list.size()]);
+ }
+ return mValidAvdList;
+ }
+ }
+
+ /**
+ * Returns all the broken AVDs.
+ * @return a newly allocated array containing all the broken AVDs.
+ */
+ @NonNull
+ public AvdInfo[] getBrokenAvds() {
+ synchronized (mAllAvdList) {
+ if (mBrokenAvdList == null) {
+ ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
+ for (AvdInfo avd : mAllAvdList) {
+ if (avd.getStatus() != AvdStatus.OK) {
+ list.add(avd);
+ }
+ }
+ mBrokenAvdList = list.toArray(new AvdInfo[list.size()]);
+ }
+ return mBrokenAvdList;
+ }
+ }
+
+ /**
+ * Returns the {@link AvdInfo} matching the given <var>name</var>.
+ * <p/>
+ * The search is case-insensitive.
+ *
+ * @param name the name of the AVD to return
+ * @param validAvdOnly if <code>true</code>, only look through the list of valid AVDs.
+ * @return the matching AvdInfo or <code>null</code> if none were found.
+ */
+ @Nullable
+ public AvdInfo getAvd(@Nullable String name, boolean validAvdOnly) {
+
+ boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS;
+
+ if (validAvdOnly) {
+ for (AvdInfo info : getValidAvds()) {
+ String name2 = info.getName();
+ if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
+ return info;
+ }
+ }
+ } else {
+ synchronized (mAllAvdList) {
+ for (AvdInfo info : mAllAvdList) {
+ String name2 = info.getName();
+ if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
+ return info;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether an emulator is currently running the AVD.
+ */
+ public boolean isAvdRunning(@NonNull AvdInfo info, @NonNull ILogger logger) {
+ String pid;
+ try {
+ pid = getAvdPid(info);
+ }
+ catch (IOException e) {
+ logger.error(e, "IOException while getting PID");
+ // To be safe return true
+ return true;
+ }
+ if (pid != null) {
+ String command;
+ if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ command = "cmd /c \"tasklist /FI \"PID eq " + pid + "\" | findstr " + pid
+ + "\"";
+ } else {
+ command = "kill -0 " + pid;
+ }
+ try {
+ Process p = Runtime.getRuntime().exec(command);
+ // If the process ends with non-0 it means the process doesn't exist
+ return p.waitFor() == 0;
+ } catch (IOException e) {
+ logger.warning("Got IOException while checking running processes:\n%s",
+ Arrays.toString(e.getStackTrace()));
+ // To be safe return true
+ return true;
+ } catch (InterruptedException e) {
+ logger.warning("Got InterruptedException while checking running processes:\n%s",
+ Arrays.toString(e.getStackTrace()));
+ // To be safe return true
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void stopAvd(@NonNull AvdInfo info) {
+ try {
+ String pid = getAvdPid(info);
+ if (pid != null) {
+ String command;
+ if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ command = "cmd /c \"taskkill /PID " + pid + "\"";
+ } else {
+ command = "kill " + pid;
+ }
+ try {
+ Process p = Runtime.getRuntime().exec(command);
+ // If the process ends with non-0 it means the process doesn't exist
+ p.waitFor();
+ } catch (IOException e) {
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ catch (IOException e) {
+ }
+ }
+
+ private String getAvdPid(@NonNull AvdInfo info) throws IOException {
+ // this is a file on Unix, and a directory on Windows.
+ File f = new File(info.getDataFolderPath(), "userdata-qemu.img.lock"); //$NON-NLS-1$
+ if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ f = new File(f, "pid");
+ }
+ if (mFop.exists(f)) {
+ return mFop.toString(f, Charsets.UTF_8).trim();
+ }
+ return null;
+ }
+
+
+
+ /**
+ * Returns whether this AVD name would generate a conflict.
+ *
+ * @param name the name of the AVD to return
+ * @return A pair of {@link AvdConflict} and the path or AVD name that conflicts.
+ */
+ @NonNull
+ public Pair<AvdConflict, String> isAvdNameConflicting(@Nullable String name) {
+
+ boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS;
+
+ // Check whether we have a conflict with an existing or invalid AVD
+ // known to the manager.
+ synchronized (mAllAvdList) {
+ for (AvdInfo info : mAllAvdList) {
+ String name2 = info.getName();
+ if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
+ if (info.getStatus() == AvdStatus.OK) {
+ return Pair.of(AvdConflict.CONFLICT_EXISTING_AVD, name2);
+ } else {
+ return Pair.of(AvdConflict.CONFLICT_INVALID_AVD, name2);
+ }
+ }
+ }
+ }
+
+ // No conflict with known AVDs.
+ // Are some existing files/folders in the way of creating this AVD?
+
+ try {
+ File file = AvdInfo.getDefaultIniFile(this, name);
+ if (mFop.exists(file)) {
+ return Pair.of(AvdConflict.CONFLICT_EXISTING_PATH, file.getPath());
+ }
+
+ file = AvdInfo.getDefaultAvdFolder(this, name, mFop, false);
+ if (mFop.exists(file)) {
+ return Pair.of(AvdConflict.CONFLICT_EXISTING_PATH, file.getPath());
+ }
+
+ } catch (AndroidLocationException e) {
+ // ignore
+ }
+
+
+ return Pair.of(AvdConflict.NO_CONFLICT, null);
+ }
+
+ /**
+ * Reloads the AVD list.
+ * @param log the log object to receive action logs. Cannot be null.
+ * @throws AndroidLocationException if there was an error finding the location of the
+ * AVD folder.
+ */
+ public void reloadAvds(@NonNull ILogger log) throws AndroidLocationException {
+ // build the list in a temp list first, in case the method throws an exception.
+ // It's better than deleting the whole list before reading the new one.
+ ArrayList<AvdInfo> allList = new ArrayList<AvdInfo>();
+ buildAvdList(allList, log);
+
+ synchronized (mAllAvdList) {
+ mAllAvdList.clear();
+ mAllAvdList.addAll(allList);
+ mValidAvdList = mBrokenAvdList = null;
+ }
+ }
+
+ /**
+ * Reloads a single AVD but does not update the list.
+ * @param avdInfo an existing AVD
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return an updated AVD
+ * @throws AndroidLocationException if there was an error finding the location of the
+ * AVD folder.
+ */
+ public AvdInfo reloadAvd(AvdInfo avdInfo, ILogger log) throws AndroidLocationException {
+ AvdInfo newInfo = parseAvdInfo(avdInfo.getIniFile(), log);
+ synchronized (mAllAvdList) {
+ int index = mAllAvdList.indexOf(avdInfo);
+ if (index >= 0) {
+ // Update the existing list of AVDs
+ // Unless the original AVD is not found, in which case someone else may already have updated the list
+ replaceAvd(avdInfo, newInfo);
+ }
+ }
+ return newInfo;
+ }
+
+ /**
+ * Creates a new AVD. It is expected that there is no existing AVD with this name already.
+ *
+ * @param avdFolder the data folder for the AVD. It will be created as needed.
+ * Unless you want to locate it in a specific directory, the ideal default is
+ * {@code AvdManager.AvdInfo.getAvdFolder}.
+ * @param avdName the name of the AVD
+ * @param systemImage the system image of the AVD
+ * @param skinFolder the skin folder path to use, if specified. Can be null.
+ * @param skinName the name of the skin. Can be null. Must have been verified by caller.
+ * Can be a size in the form "NNNxMMM" or a directory name matching skinFolder.
+ * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
+ * an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
+ * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults.
+ * @param bootProps the optional boot properties for the AVD. Can be null.
+ * @param createSnapshot If true copy a blank snapshot image into the AVD.
+ * @param removePrevious If true remove any previous files.
+ * @param editExisting If true, edit an existing AVD, changing only the minimum required.
+ * This won't remove files unless required or unless {@code removePrevious} is set.
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return The new {@link AvdInfo} in case of success (which has just been added to the
+ * internal list) or null in case of failure.
+ */
+ @Nullable
+ public AvdInfo createAvd(
+ @NonNull File avdFolder,
+ @NonNull String avdName,
+ @NonNull ISystemImage systemImage,
+ @Nullable File skinFolder,
+ @Nullable String skinName,
+ @Nullable String sdcard,
+ @Nullable Map<String,String> hardwareConfig,
+ @Nullable Map<String,String> bootProps,
+ boolean createSnapshot,
+ boolean removePrevious,
+ boolean editExisting,
+ @NonNull ILogger log) {
+ if (log == null) {
+ throw new IllegalArgumentException("log cannot be null");
+ }
+
+ File iniFile = null;
+ boolean needCleanup = false;
+ try {
+ if (mFop.exists(avdFolder)) {
+ if (removePrevious) {
+ // AVD already exists and removePrevious is set, try to remove the
+ // directory's content first (but not the directory itself).
+ try {
+ deleteContentOf(avdFolder);
+ } catch (SecurityException e) {
+ log.error(e, "Failed to delete %1$s", avdFolder.getAbsolutePath());
+ }
+ } else if (!editExisting) {
+ // AVD shouldn't already exist if removePrevious is false and
+ // we're not editing an existing AVD.
+ log.error(null,
+ "Folder %1$s is in the way. Use --force if you want to overwrite.",
+ avdFolder.getAbsolutePath());
+ return null;
+ }
+ } else {
+ // create the AVD folder.
+ mFop.mkdirs(avdFolder);
+ // We're not editing an existing AVD.
+ editExisting = false;
+ }
+
+ // actually write the ini file
+ iniFile = createAvdIniFile(avdName, avdFolder, removePrevious,
+ systemImage.getAndroidVersion());
+
+ // writes the userdata.img in it.
+ File imageFolder = systemImage.getLocation();
+ File userdataSrc = new File(imageFolder, USERDATA_IMG);
+
+ String abiType = systemImage.getAbiType();
+
+ if (!mFop.exists(userdataSrc)) {
+ log.error(null,
+ "Unable to find a '%1$s' file for ABI %2$s to copy into the AVD folder.",
+ USERDATA_IMG,
+ abiType);
+ needCleanup = true;
+ return null;
+ }
+
+ File userdataDest = new File(avdFolder, USERDATA_IMG);
+
+ copyImageFile(userdataSrc, userdataDest);
+
+ if (!mFop.exists(userdataDest)) {
+ log.error(null, "Unable to create '%1$s' file in the AVD folder.",
+ userdataDest);
+ needCleanup = true;
+ return null;
+ }
+
+ // Config file.
+ HashMap<String, String> values = new HashMap<String, String>();
+
+ if (!setImagePathProperties(systemImage, values, log)) {
+ log.error(null, "Failed to set image path properties in the AVD folder.");
+ needCleanup = true;
+ return null;
+ }
+
+ // Create the snapshot file
+ if (createSnapshot) {
+ File snapshotDest = new File(avdFolder, SNAPSHOTS_IMG);
+ if (mFop.isFile(snapshotDest) && editExisting) {
+ log.info("Snapshot image already present, was not changed.\n");
+
+ } else {
+ File toolsLib = new File(mSdkHandler.getLocation(),
+ SdkConstants.OS_SDK_TOOLS_LIB_EMULATOR_FOLDER);
+ File snapshotBlank = new File(toolsLib, SNAPSHOTS_IMG);
+ if (!mFop.exists(snapshotBlank)) {
+ log.error(null,
+ "Unable to find a '%2$s%1$s' file to copy into the AVD folder.",
+ SNAPSHOTS_IMG, toolsLib);
+ needCleanup = true;
+ return null;
+ }
+
+ copyImageFile(snapshotBlank, snapshotDest);
+ }
+ values.put(AVD_INI_SNAPSHOT_PRESENT, "true");
+ }
+
+ // Now the tag & abi type
+ IdDisplay tag = systemImage.getTag();
+ values.put(AVD_INI_TAG_ID, tag.getId());
+ values.put(AVD_INI_TAG_DISPLAY, tag.getDisplay());
+ values.put(AVD_INI_ABI_TYPE, abiType);
+
+ // and the cpu arch.
+ Abi abi = Abi.getEnum(abiType);
+ if (abi != null) {
+ values.put(AVD_INI_CPU_ARCH, abi.getCpuArch());
+
+ String model = abi.getCpuModel();
+ if (model != null) {
+ values.put(AVD_INI_CPU_MODEL, model);
+ }
+ } else {
+ log.error(null,
+ "ABI %1$s is not supported by this version of the SDK Tools", abiType);
+ needCleanup = true;
+ return null;
+ }
+
+ // Now the skin.
+ String skinPath = null;
+
+ if (skinFolder == null && skinName != null &&
+ NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
+ // Numeric skin size. Set both skinPath and skinName to the same size.
+ skinPath = skinName;
+
+ } else if (skinFolder != null && skinName == null) {
+ // Skin folder is specified, but not skin name. Adjust it.
+ skinName = skinFolder.getName();
+
+ }
+
+ if (skinFolder != null) {
+ // skin does not exist!
+ if (!mFop.exists(skinFolder)) {
+ log.error(null, "Skin '%1$s' does not exist.", skinName);
+ return null;
+ }
+
+ // if skinFolder is in the sdk, use the relative path
+ if (skinFolder.getPath().startsWith(mSdkHandler.getLocation().getPath())) {
+ try {
+ skinPath = FileOpUtils.makeRelative(mSdkHandler.getLocation(), skinFolder,
+ mFop);
+ } catch (IOException e) {
+ // In case it fails, just use the absolute path
+ skinPath = skinFolder.getAbsolutePath();
+ }
+ } else {
+ // Skin isn't in the sdk. Just use the absolute path.
+ skinPath = skinFolder.getAbsolutePath();
+ }
+ }
+
+ // Set skin.name for display purposes in the AVD manager and
+ // set skin.path for use by the emulator.
+ if (skinName != null) {
+ values.put(AVD_INI_SKIN_NAME, skinName);
+ }
+ if (skinPath != null) {
+ values.put(AVD_INI_SKIN_PATH, skinPath);
+ }
+
+ if (sdcard != null && !sdcard.isEmpty()) {
+ // Sdcard is possibly a size. In that case we create a file called 'sdcard.img'
+ // in the AVD folder, and do not put any value in config.ini.
+
+ long sdcardSize = parseSdcardSize(sdcard, null);
+
+ if (sdcardSize == SDCARD_SIZE_NOT_IN_RANGE) {
+ log.error(null, "SD Card size must be in the range 9 MiB..1023 GiB.");
+ needCleanup = true;
+ return null;
+
+ } else if (sdcardSize == SDCARD_SIZE_INVALID) {
+ log.error(null, "Unable to parse SD Card size");
+ needCleanup = true;
+ return null;
+
+ } else if (sdcardSize == SDCARD_NOT_SIZE_PATTERN) {
+ File sdcardFile = new File(sdcard);
+ if (mFop.isFile(sdcardFile)) {
+ // sdcard value is an external sdcard, so we put its path into the config.ini
+ values.put(AVD_INI_SDCARD_PATH, sdcard);
+ } else {
+ log.error(null, "'%1$s' is not recognized as a valid sdcard value.\n"
+ + "Value should be:\n" + "1. path to an sdcard.\n"
+ + "2. size of the sdcard to create: <size>[K|M]", sdcard);
+ needCleanup = true;
+ return null;
+ }
+ } else {
+ // create the sdcard.
+ File sdcardFile = new File(avdFolder, SDCARD_IMG);
+
+ boolean runMkSdcard = true;
+ if (mFop.exists(sdcardFile)) {
+ if (sdcardFile.length() == sdcardSize && editExisting) {
+ // There's already an sdcard file with the right size and we're
+ // not overriding it... so don't remove it.
+ runMkSdcard = false;
+ log.info("SD Card already present with same size, was not changed.\n");
+ }
+ }
+ if (mFop instanceof MockFileOp) {
+ // We don't have a real filesystem, so we won't be able to run the tool. Skip.
+ runMkSdcard = false;
+ }
+
+ if (runMkSdcard) {
+ String path = sdcardFile.getAbsolutePath();
+
+ // execute mksdcard with the proper parameters.
+ File toolsFolder = new File(mSdkHandler.getLocation(),
+ SdkConstants.FD_TOOLS);
+ File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
+
+ if (!mFop.isFile(mkSdCard)) {
+ log.error(null, "'%1$s' is missing from the SDK tools folder.",
+ mkSdCard.getName());
+ needCleanup = true;
+ return null;
+ }
+
+ if (!createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log)) {
+ log.error(null, "Failed to create sdcard in the AVD folder.");
+ needCleanup = true;
+ return null; // mksdcard output has already been displayed, no need to
+ // output anything else.
+ }
+ }
+
+ // add a property containing the size of the sdcard for display purpose
+ // only when the dev does 'android list avd'
+ values.put(AVD_INI_SDCARD_SIZE, sdcard);
+ }
+ }
+
+ // add the hardware config to the config file.
+ // priority order is:
+ // - values provided by the user
+ // - values provided by the skin
+ // - values provided by the sys img
+ // In order to follow this priority, we'll add the lowest priority values first and then
+ // override by higher priority values.
+ // In the case of a platform with override values from the user, the skin value might
+ // already be there, but it's ok.
+
+ HashMap<String, String> finalHardwareValues = new HashMap<String, String>();
+
+ FileOpFileWrapper sysImgHardwareFile =
+ new FileOpFileWrapper(new File(systemImage.getLocation(), AvdManager.HARDWARE_INI),
+ mFop, false);
+ if (sysImgHardwareFile.exists()) {
+ Map<String, String> imageHardwardConfig = ProjectProperties.parsePropertyFile(
+ sysImgHardwareFile, log);
+
+ if (imageHardwardConfig != null) {
+ finalHardwareValues.putAll(imageHardwardConfig);
+ values.putAll(imageHardwardConfig);
+ }
+ }
+
+ // get the hardware properties for this skin
+ if (skinFolder != null) {
+ FileOpFileWrapper skinHardwareFile = new FileOpFileWrapper(
+ new File(skinFolder, AvdManager.HARDWARE_INI), mFop, false);
+ if (skinHardwareFile.exists()) {
+ Map<String, String> skinHardwareConfig =
+ ProjectProperties.parsePropertyFile(skinHardwareFile, log);
+
+ if (skinHardwareConfig != null) {
+ finalHardwareValues.putAll(skinHardwareConfig);
+ values.putAll(skinHardwareConfig);
+ }
+ }
+ }
+
+ // finally put the hardware provided by the user.
+ if (hardwareConfig != null) {
+ finalHardwareValues.putAll(hardwareConfig);
+ values.putAll(hardwareConfig);
+ }
+
+ File configIniFile = new File(avdFolder, CONFIG_INI);
+ writeIniFile(configIniFile, values, true);
+
+ if (bootProps != null && !bootProps.isEmpty()) {
+ File bootPropsFile = new File(avdFolder, BOOT_PROP);
+ writeIniFile(bootPropsFile, bootProps, false);
+ }
+
+ // create the AvdInfo object, and add it to the list
+ AvdInfo newAvdInfo = new AvdInfo(
+ avdName,
+ iniFile,
+ avdFolder.getAbsolutePath(),
+ systemImage,
+ values);
+
+ AvdInfo oldAvdInfo = getAvd(avdName, false /*validAvdOnly*/);
+
+ synchronized (mAllAvdList) {
+ if (oldAvdInfo != null && (removePrevious || editExisting)) {
+ mAllAvdList.remove(oldAvdInfo);
+ }
+ mAllAvdList.add(newAvdInfo);
+ mValidAvdList = mBrokenAvdList = null;
+ }
+
+ if ((removePrevious || editExisting) &&
+ newAvdInfo != null &&
+ oldAvdInfo != null &&
+ !oldAvdInfo.getDataFolderPath().equals(newAvdInfo.getDataFolderPath())) {
+ log.warning("Removing previous AVD directory at %s",
+ oldAvdInfo.getDataFolderPath());
+ // Remove the old data directory
+ File dir = new File(oldAvdInfo.getDataFolderPath());
+ try {
+ deleteContentOf(dir);
+ mFop.delete(dir);
+ } catch (SecurityException e) {
+ log.error(e, "Failed to delete %1$s", dir.getAbsolutePath());
+ }
+ }
+
+ return newAvdInfo;
+ } catch (AndroidLocationException e) {
+ log.error(e, null);
+ } catch (IOException e) {
+ log.error(e, null);
+ } catch (SecurityException e) {
+ log.error(e, null);
+ } finally {
+ if (needCleanup) {
+ if (iniFile != null && mFop.exists(iniFile)) {
+ mFop.delete(iniFile);
+ }
+
+ try {
+ deleteContentOf(avdFolder);
+ mFop.delete(avdFolder);
+ } catch (SecurityException e) {
+ log.error(e, "Failed to delete %1$s", avdFolder.getAbsolutePath());
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Copy the nominated file to the given destination.
+ *
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ private void copyImageFile(@NonNull File source, @NonNull File destination)
+ throws FileNotFoundException, IOException {
+
+ InputStream fis = mFop.newFileInputStream(source);
+ OutputStream fos = mFop.newFileOutputStream(destination);
+
+ byte[] buffer = new byte[4096];
+ int count;
+ while ((count = fis.read(buffer)) != -1) {
+ fos.write(buffer, 0, count);
+ }
+
+ fos.close();
+ fis.close();
+ }
+
+ /**
+ * Returns the path to the target images folder as a relative path to the SDK, if the folder
+ * is not empty. If the image folder is empty or does not exist, <code>null</code> is returned.
+ * @throws InvalidTargetPathException if the target image folder is not in the current SDK.
+ */
+ private String getImageRelativePath(@NonNull ISystemImage systemImage)
+ throws InvalidTargetPathException {
+
+ File folder = systemImage.getLocation();
+ String imageFullPath = folder.getAbsolutePath();
+
+ // make this path relative to the SDK location
+ String sdkLocation = mSdkHandler.getLocation().getPath();
+ if (!imageFullPath.startsWith(sdkLocation)) {
+ // this really really should not happen.
+ assert false;
+ throw new InvalidTargetPathException("Target location is not inside the SDK.");
+ }
+
+ if (mFop.isDirectory(folder)) {
+ String[] list = mFop.list(folder, new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return IMAGE_NAME_PATTERN.matcher(name).matches();
+ }
+ });
+
+ if (list.length > 0) {
+ // Remove the SDK root path, e.g. /sdk/dir1/dir2 => /dir1/dir2
+ imageFullPath = imageFullPath.substring(sdkLocation.length());
+ // The path is relative, so it must not start with a file separator
+ if (imageFullPath.charAt(0) == File.separatorChar) {
+ imageFullPath = imageFullPath.substring(1);
+ }
+ // For compatibility with previous versions, we denote folders
+ // by ending the path with file separator
+ if (!imageFullPath.endsWith(File.separator)) {
+ imageFullPath += File.separator;
+ }
+
+ return imageFullPath;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates the ini file for an AVD.
+ *
+ * @param name of the AVD.
+ * @param avdFolder path for the data folder of the AVD.
+ * @param removePrevious True if an existing ini file should be removed.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ * @throws IOException if {@link File#getAbsolutePath()} fails.
+ */
+ private File createAvdIniFile(@NonNull String name,
+ @NonNull File avdFolder,
+ boolean removePrevious,
+ @NonNull AndroidVersion version)
+ throws AndroidLocationException, IOException {
+ File iniFile = AvdInfo.getDefaultIniFile(this, name);
+
+ if (removePrevious) {
+ if (mFop.isFile(iniFile)) {
+ mFop.delete(iniFile);
+ } else if (mFop.isDirectory(iniFile)) {
+ deleteContentOf(iniFile);
+ mFop.delete(iniFile);
+ }
+ }
+
+ String absPath = avdFolder.getAbsolutePath();
+ String relPath = null;
+ String androidPath = AndroidLocation.getFolder();
+ if (absPath.startsWith(androidPath)) {
+ // Compute the AVD path relative to the android path.
+ assert androidPath.endsWith(File.separator);
+ relPath = absPath.substring(androidPath.length());
+ }
+
+ HashMap<String, String> values = new HashMap<String, String>();
+ if (relPath != null) {
+ values.put(AVD_INFO_REL_PATH, relPath);
+ }
+ values.put(AVD_INFO_ABS_PATH, absPath);
+ values.put(AVD_INFO_TARGET, AndroidTargetHash.getPlatformHashString(version));
+ writeIniFile(iniFile, values, true);
+
+ return iniFile;
+ }
+
+ /**
+ * Creates the ini file for an AVD.
+ *
+ * @param info of the AVD.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ * @throws IOException if {@link File#getAbsolutePath()} fails.
+ */
+ private File createAvdIniFile(@NonNull AvdInfo info)
+ throws AndroidLocationException, IOException {
+ return createAvdIniFile(info.getName(),
+ new File(info.getDataFolderPath()),
+ false, info.getAndroidVersion());
+ }
+
+ /**
+ * Actually deletes the files of an existing AVD.
+ * <p/>
+ * This also remove it from the manager's list, The caller does not need to
+ * call {@link #removeAvd(AvdInfo)} afterwards.
+ * <p/>
+ * This method is designed to somehow work with an unavailable AVD, that is an AVD that
+ * could not be loaded due to some error. That means this method still tries to remove
+ * the AVD ini file or its folder if it can be found. An error will be output if any of
+ * these operations fail.
+ *
+ * @param avdInfo the information on the AVD to delete
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return True if the AVD was deleted with no error.
+ */
+ public boolean deleteAvd(@NonNull AvdInfo avdInfo, @NonNull ILogger log) {
+ try {
+ boolean error = false;
+
+ File f = avdInfo.getIniFile();
+ if (f != null && mFop.exists(f)) {
+ log.info("Deleting file %1$s\n", f.getCanonicalPath());
+ if (!mFop.delete(f)) {
+ log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
+ error = true;
+ }
+ }
+
+ String path = avdInfo.getDataFolderPath();
+ if (path != null) {
+ f = new File(path);
+ if (mFop.exists(f)) {
+ log.info("Deleting folder %1$s\n", f.getCanonicalPath());
+ if (deleteContentOf(f) == false || mFop.delete(f) == false) {
+ log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
+ error = true;
+ }
+ }
+ }
+
+ removeAvd(avdInfo);
+
+ if (error) {
+ log.info("\nAVD '%1$s' deleted with errors. See errors above.\n",
+ avdInfo.getName());
+ } else {
+ log.info("\nAVD '%1$s' deleted.\n", avdInfo.getName());
+ return true;
+ }
+
+ } catch (IOException e) {
+ log.error(e, null);
+ } catch (SecurityException e) {
+ log.error(e, null);
+ }
+ return false;
+ }
+
+ /**
+ * Moves and/or rename an existing AVD and its files.
+ * This also change it in the manager's list.
+ * <p/>
+ * The caller should make sure the name or path given are valid, do not exist and are
+ * actually different than current values.
+ *
+ * @param avdInfo the information on the AVD to move.
+ * @param newName the new name of the AVD if non null.
+ * @param paramFolderPath the new data folder if non null.
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return True if the move succeeded or there was nothing to do.
+ * If false, this method will have had already output error in the log.
+ */
+ public boolean moveAvd(@NonNull AvdInfo avdInfo,
+ @Nullable String newName,
+ @Nullable String paramFolderPath,
+ @NonNull ILogger log) {
+
+ try {
+ if (paramFolderPath != null) {
+ File f = new File(avdInfo.getDataFolderPath());
+ log.warning("Moving '%1$s' to '%2$s'.",
+ avdInfo.getDataFolderPath(),
+ paramFolderPath);
+ if (!mFop.renameTo(f, new File(paramFolderPath))) {
+ log.error(null, "Failed to move '%1$s' to '%2$s'.",
+ avdInfo.getDataFolderPath(), paramFolderPath);
+ return false;
+ }
+
+ // update AVD info
+ AvdInfo info = new AvdInfo(
+ avdInfo.getName(),
+ avdInfo.getIniFile(),
+ paramFolderPath,
+ avdInfo.getSystemImage(),
+ avdInfo.getProperties());
+ replaceAvd(avdInfo, info);
+
+ // update the ini file
+ createAvdIniFile(info);
+ }
+
+ if (newName != null) {
+ File oldIniFile = avdInfo.getIniFile();
+ File newIniFile = AvdInfo.getDefaultIniFile(this, newName);
+
+ log.warning("Moving '%1$s' to '%2$s'.", oldIniFile.getPath(), newIniFile.getPath());
+ if (!mFop.renameTo(oldIniFile, newIniFile)) {
+ log.error(null, "Failed to move '%1$s' to '%2$s'.",
+ oldIniFile.getPath(), newIniFile.getPath());
+ return false;
+ }
+
+ // update AVD info
+ AvdInfo info = new AvdInfo(
+ newName,
+ avdInfo.getIniFile(),
+ avdInfo.getDataFolderPath(),
+ avdInfo.getSystemImage(),
+ avdInfo.getProperties());
+ replaceAvd(avdInfo, info);
+ }
+
+ log.info("AVD '%1$s' moved.\n", avdInfo.getName());
+
+ } catch (AndroidLocationException e) {
+ log.error(e, null);
+ } catch (IOException e) {
+ log.error(e, null);
+ }
+
+ // nothing to do or succeeded
+ return true;
+ }
+
+ /**
+ * Helper method to recursively delete a folder's content (but not the folder itself).
+ *
+ * @throws SecurityException like {@link File#delete()} does if file/folder is not writable.
+ */
+ private boolean deleteContentOf(File folder) throws SecurityException {
+ File[] files = mFop.listFiles(folder);
+ if (files != null) {
+ for (File f : files) {
+ if (mFop.isDirectory(f)) {
+ if (deleteContentOf(f) == false) {
+ return false;
+ }
+ }
+ if (mFop.delete(f) == false) {
+ return false;
+ }
+
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a list of files that are potential AVD ini files.
+ * <p/>
+ * This lists the $HOME/.android/avd/<name>.ini files.
+ * Such files are properties file than then indicate where the AVD folder is located.
+ * <p/>
+ * Note: the method is to be considered private. It is made protected so that
+ * unit tests can easily override the AVD root.
+ *
+ * @return A new {@link File} array or null. The array might be empty.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ private File[] buildAvdFilesList() throws AndroidLocationException {
+ File folder = new File(getBaseAvdFolder());
+
+ // ensure folder validity.
+ if (mFop.isFile(folder)) {
+ throw new AndroidLocationException(
+ String.format("%1$s is not a valid folder.", folder.getAbsolutePath()));
+ } else if (mFop.exists(folder) == false) {
+ // folder is not there, we create it and return
+ mFop.mkdirs(folder);
+ return null;
+ }
+
+ File[] avds = mFop.listFiles(folder, new FilenameFilter() {
+ @Override
+ public boolean accept(File parent, String name) {
+ if (INI_NAME_PATTERN.matcher(name).matches()) {
+ // check it's a file and not a folder
+ return mFop.isFile(new File(parent, name));
+ }
+
+ return false;
+ }
+ });
+
+ return avds;
+ }
+
+ /**
+ * Computes the internal list of available AVDs
+ * @param allList the list to contain all the AVDs
+ * @param log the log object to receive action logs. Cannot be null.
+ *
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ private void buildAvdList(ArrayList<AvdInfo> allList, ILogger log)
+ throws AndroidLocationException {
+ File[] avds = buildAvdFilesList();
+ if (avds != null) {
+ for (File avd : avds) {
+ AvdInfo info = parseAvdInfo(avd, log);
+ if (info != null && !allList.contains(info)) {
+ allList.add(info);
+ }
+ }
+ }
+ }
+
+ private DeviceManager getDeviceManager(ILogger logger) {
+ DeviceManager manager = mDeviceManagers.get(logger);
+ if (manager == null) {
+ manager = DeviceManager.createInstance(mSdkHandler.getLocation(), logger);
+ manager.registerListener(new DeviceManager.DevicesChangedListener() {
+ @Override
+ public void onDevicesChanged() {
+ mDeviceManagers.clear();
+ }
+ });
+ mDeviceManagers.put(logger, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Parses an AVD .ini file to create an {@link AvdInfo}.
+ *
+ * @param iniPath The path to the AVD .ini file
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is
+ * valid or not.
+ */
+ private AvdInfo parseAvdInfo(File iniPath, ILogger log) {
+ Map<String, String> map = parseIniFile(
+ new FileOpFileWrapper(iniPath, mFop, false),
+ log);
+
+ String avdPath = map.get(AVD_INFO_ABS_PATH);
+
+ if (avdPath == null || !(mFop.isDirectory(new File(avdPath)))) {
+ // Try to fallback on the relative path, if present.
+ String relPath = map.get(AVD_INFO_REL_PATH);
+ if (relPath != null) {
+ try {
+ String androidPath = AndroidLocation.getFolder();
+ File f = new File(androidPath, relPath);
+ if (mFop.isDirectory(f)) {
+ avdPath = f.getAbsolutePath();
+ }
+ } catch (AndroidLocationException ignore) {}
+ }
+ }
+
+ FileOpFileWrapper configIniFile = null;
+ Map<String, String> properties = null;
+ LoggerProgressIndicatorWrapper progress = new LoggerProgressIndicatorWrapper(log);
+
+ // load the AVD properties.
+ if (avdPath != null) {
+ configIniFile = new FileOpFileWrapper(new File(avdPath, CONFIG_INI), mFop, false);
+ }
+
+ if (configIniFile != null) {
+ if (!configIniFile.exists()) {
+ log.warning("Missing file '%1$s'.", configIniFile.getOsLocation());
+ } else {
+ properties = parseIniFile(configIniFile, log);
+ }
+ }
+
+ // get name
+ String name = iniPath.getName();
+ Matcher matcher = INI_NAME_PATTERN.matcher(iniPath.getName());
+ if (matcher.matches()) {
+ name = matcher.group(1);
+ }
+
+ // check the image.sysdir are valid
+ boolean validImageSysdir = true;
+ String imageSysDir = null;
+ ISystemImage sysImage = null;
+ if (properties != null) {
+ imageSysDir = properties.get(AVD_INI_IMAGES_1);
+ if (imageSysDir != null) {
+ sysImage = mSdkHandler.getSystemImageManager(progress)
+ .getImageAt(new File(mSdkHandler.getLocation(), imageSysDir));
+ }
+ }
+
+
+ // Get the device status if this AVD is associated with a device
+ DeviceStatus deviceStatus = null;
+ boolean updateHashV2 = false;
+ if (properties != null) {
+ String deviceName = properties.get(AVD_INI_DEVICE_NAME);
+ String deviceMfctr = properties.get(AVD_INI_DEVICE_MANUFACTURER);
+
+ Device d;
+
+ if (deviceName != null && deviceMfctr != null) {
+ DeviceManager devMan = getDeviceManager(log);
+ d = devMan.getDevice(deviceName, deviceMfctr);
+ deviceStatus = d == null ? DeviceStatus.MISSING : DeviceStatus.EXISTS;
+
+ if (d != null) {
+ updateHashV2 = true;
+ String hashV2 = properties.get(AVD_INI_DEVICE_HASH_V2);
+ if (hashV2 != null) {
+ String newHashV2 = DeviceManager.hasHardwarePropHashChanged(d, hashV2);
+ if (newHashV2 == null) {
+ updateHashV2 = false;
+ } else {
+ properties.put(AVD_INI_DEVICE_HASH_V2, newHashV2);
+ }
+ }
+
+ String hashV1 = properties.get(AVD_INI_DEVICE_HASH_V1);
+ if (hashV1 != null) {
+ // will recompute a hash v2 and save it below
+ properties.remove(AVD_INI_DEVICE_HASH_V1);
+ }
+ }
+ }
+ }
+
+
+ // TODO: What about missing sdcard, skins, etc?
+
+ AvdStatus status;
+
+ if (avdPath == null) {
+ status = AvdStatus.ERROR_PATH;
+ } else if (configIniFile == null) {
+ status = AvdStatus.ERROR_CONFIG;
+ } else if (properties == null || imageSysDir == null) {
+ status = AvdStatus.ERROR_PROPERTIES;
+ } else if (!validImageSysdir) {
+ status = AvdStatus.ERROR_IMAGE_DIR;
+ } else if (deviceStatus == DeviceStatus.CHANGED) {
+ status = AvdStatus.ERROR_DEVICE_CHANGED;
+ } else if (deviceStatus == DeviceStatus.MISSING) {
+ status = AvdStatus.ERROR_DEVICE_MISSING;
+ } else if (sysImage == null) {
+ status = AvdStatus.ERROR_IMAGE_MISSING;
+ } else {
+ status = AvdStatus.OK;
+ }
+
+ if (properties == null) {
+ properties = Maps.newHashMap();
+ }
+
+ if (!properties.containsKey(AVD_INI_ANDROID_API) &&
+ !properties.containsKey(AVD_INI_ANDROID_CODENAME)) {
+ String targetHash = map.get(AVD_INFO_TARGET);
+ AndroidVersion version = AndroidTargetHash.getVersionFromHash(targetHash);
+ if (version != null) {
+ properties.put(AVD_INI_ANDROID_API, Integer.toString(version.getApiLevel()));
+ if (version.getCodename() != null) {
+ properties.put(AVD_INI_ANDROID_CODENAME, version.getCodename());
+ }
+ }
+ }
+
+ AvdInfo info = new AvdInfo(
+ name,
+ iniPath,
+ avdPath,
+ sysImage,
+ properties,
+ status);
+
+ if (updateHashV2) {
+ try {
+ return updateDeviceChanged(info, log);
+ } catch (IOException ignore) {}
+ }
+
+ return info;
+ }
+
+ /**
+ * Writes a .ini file from a set of properties, using UTF-8 encoding.
+ * The keys are sorted.
+ * The file should be read back later by {@link #parseIniFile(IAbstractFile, ILogger)}.
+ *
+ * @param iniFile The file to generate.
+ * @param values The properties to place in the ini file.
+ * @param addEncoding When true, add a property {@link #AVD_INI_ENCODING} indicating the
+ * encoding used to write the file.
+ * @throws IOException if {@link FileWriter} fails to open, write or close the file.
+ */
+ private void writeIniFile(File iniFile, Map<String, String> values, boolean addEncoding)
+ throws IOException {
+
+ Charset charset = Charsets.UTF_8;
+ OutputStreamWriter writer = new OutputStreamWriter(mFop.newFileOutputStream(iniFile),
+ charset);
+ try {
+ if (addEncoding) {
+ // Write down the charset used in case we want to use it later.
+ writer.write(String.format("%1$s=%2$s\n", AVD_INI_ENCODING, charset.name()));
+ }
+
+ ArrayList<String> keys = new ArrayList<String>(values.keySet());
+ // Do not save these values (always recompute)
+ keys.remove(AVD_INI_ANDROID_API);
+ keys.remove(AVD_INI_ANDROID_CODENAME);
+ Collections.sort(keys);
+
+ for (String key : keys) {
+ String value = values.get(key);
+ if (value != null) {
+ writer.write(String.format("%1$s=%2$s\n", key, value));
+ }
+ }
+ } finally {
+ writer.close();
+ }
+ }
+
+ /**
+ * Parses a property file and returns a map of the content.
+ * <p/>
+ * If the file is not present, null is returned with no error messages sent to the log.
+ * <p/>
+ * Charset encoding will be either the system's default or the one specified by the
+ * {@link #AVD_INI_ENCODING} key if present.
+ *
+ * @param propFile the property file to parse
+ * @param log the ILogger object receiving warning/error from the parsing.
+ * @return the map of (key,value) pairs, or null if the parsing failed.
+ */
+ public static Map<String, String> parseIniFile(
+ @NonNull IAbstractFile propFile,
+ @Nullable ILogger log) {
+ return parseIniFileImpl(propFile, log, null);
+ }
+
+ /**
+ * Implementation helper for the {@link #parseIniFile(IAbstractFile, ILogger)} method.
+ * Don't call this one directly.
+ *
+ * @param propFile the property file to parse
+ * @param log the ILogger object receiving warning/error from the parsing.
+ * @param charset When a specific charset is specified, this will be used as-is.
+ * When null, the default charset will first be used and if the key
+ * {@link #AVD_INI_ENCODING} is found the parsing will restart using that specific
+ * charset.
+ * @return the map of (key,value) pairs, or null if the parsing failed.
+ */
+ private static Map<String, String> parseIniFileImpl(
+ @NonNull IAbstractFile propFile,
+ @Nullable ILogger log,
+ @Nullable Charset charset) {
+
+ BufferedReader reader = null;
+ try {
+ boolean canChangeCharset = false;
+ if (charset == null) {
+ canChangeCharset = true;
+ charset = Charsets.ISO_8859_1;
+ }
+ reader = new BufferedReader(new InputStreamReader(propFile.getContents(), charset));
+
+ String line = null;
+ Map<String, String> map = new HashMap<String, String>();
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (!line.isEmpty() && line.charAt(0) != '#') {
+
+ Matcher m = INI_LINE_PATTERN.matcher(line);
+ if (m.matches()) {
+ // Note: we do NOT escape values.
+ String key = m.group(1);
+ String value = m.group(2);
+
+ // If we find the charset encoding and it's not the same one and
+ // it's a valid one, re-read the file using that charset.
+ if (canChangeCharset &&
+ AVD_INI_ENCODING.equals(key) &&
+ !charset.name().equals(value) &&
+ Charset.isSupported(value)) {
+ charset = Charset.forName(value);
+ return parseIniFileImpl(propFile, log, charset);
+ }
+
+ map.put(key, value);
+ } else {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
+ propFile.getOsLocation(),
+ line);
+ }
+ return null;
+ }
+ }
+ }
+
+ return map;
+ } catch (FileNotFoundException e) {
+ // this should not happen since we usually test the file existence before
+ // calling the method.
+ // Return null below.
+ } catch (IOException e) {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.",
+ propFile.getOsLocation(),
+ e.getMessage());
+ }
+ } catch (StreamException e) {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.",
+ propFile.getOsLocation(),
+ e.getMessage());
+ }
+ } finally {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen.
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Invokes the tool to create a new SD card image file.
+ *
+ * @param toolLocation The path to the mksdcard tool.
+ * @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}.
+ * @param location The path of the new sdcard image file to generate.
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return True if the sdcard could be created.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected boolean createSdCard(String toolLocation, String size, String location, ILogger log) {
+ try {
+ String[] command = new String[3];
+ command[0] = toolLocation;
+ command[1] = size;
+ command[2] = location;
+ Process process = Runtime.getRuntime().exec(command);
+
+ final ArrayList<String> errorOutput = new ArrayList<String>();
+ final ArrayList<String> stdOutput = new ArrayList<String>();
+
+ int status = GrabProcessOutput.grabProcessOutput(
+ process,
+ Wait.WAIT_FOR_READERS,
+ new IProcessOutput() {
+ @Override
+ public void out(@Nullable String line) {
+ if (line != null) {
+ stdOutput.add(line);
+ }
+ }
+
+ @Override
+ public void err(@Nullable String line) {
+ if (line != null) {
+ errorOutput.add(line);
+ }
+ }
+ });
+
+ if (status == 0) {
+ return true;
+ } else {
+ for (String error : errorOutput) {
+ log.error(null, error);
+ }
+ }
+
+ } catch (InterruptedException e) {
+ // pass, print error below
+ } catch (IOException e) {
+ // pass, print error below
+ }
+
+ log.error(null, "Failed to create the SD card.");
+ return false;
+ }
+
+ /**
+ * Removes an {@link AvdInfo} from the internal list.
+ *
+ * @param avdInfo The {@link AvdInfo} to remove.
+ * @return true if this {@link AvdInfo} was present and has been removed.
+ */
+ public boolean removeAvd(AvdInfo avdInfo) {
+ synchronized (mAllAvdList) {
+ if (mAllAvdList.remove(avdInfo)) {
+ mValidAvdList = mBrokenAvdList = null;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public AvdInfo updateAvd(AvdInfo avd,
+ Map<String, String> newProperties,
+ AvdStatus status,
+ ILogger log) throws IOException {
+ // now write the config file
+ File configIniFile = new File(avd.getDataFolderPath(), CONFIG_INI);
+ writeIniFile(configIniFile, newProperties, true);
+
+ // finally create a new AvdInfo for this unbroken avd and add it to the list.
+ // instead of creating the AvdInfo object directly we reparse it, to detect other possible
+ // errors
+ // FIXME: We may want to create this AvdInfo by reparsing the AVD instead. This could detect other errors.
+ AvdInfo newAvd = new AvdInfo(
+ avd.getName(),
+ avd.getIniFile(),
+ avd.getDataFolderPath(),
+ avd.getSystemImage(),
+ newProperties);
+
+ replaceAvd(avd, newAvd);
+
+ return newAvd;
+ }
+
+ /**
+ * Updates the device-specific part of an AVD ini.
+ * @param avd the AVD to update.
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return The new AVD on success.
+ * @throws IOException
+ */
+ public AvdInfo updateDeviceChanged(AvdInfo avd, ILogger log) throws IOException {
+
+ // Overwrite the properties derived from the device and nothing else
+ Map<String, String> properties = new HashMap<String, String>(avd.getProperties());
+
+ DeviceManager devMan = getDeviceManager(log);
+ Collection<Device> devices = devMan.getDevices(DeviceManager.ALL_DEVICES);
+ String name = properties.get(AvdManager.AVD_INI_DEVICE_NAME);
+ String manufacturer = properties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
+
+ if (properties != null && devices != null && name != null && manufacturer != null) {
+ for (Device d : devices) {
+ if (d.getId().equals(name) && d.getManufacturer().equals(manufacturer)) {
+ properties.putAll(DeviceManager.getHardwareProperties(d));
+ try {
+ return updateAvd(avd, properties, AvdStatus.OK, log);
+ } catch (IOException e) {
+ log.error(e, null);
+ }
+ }
+ }
+ } else {
+ log.error(null, "Base device information incomplete or missing.");
+ }
+ return null;
+ }
+
+ /**
+ * Sets the paths to the system images in a properties map.
+ *
+ * @param image the system image for this avd.
+ * @param properties the properties in which to set the paths.
+ * @param log the log object to receive action logs. Cannot be null.
+ * @return true if success, false if some path are missing.
+ */
+ private boolean setImagePathProperties(ISystemImage image,
+ Map<String, String> properties,
+ ILogger log) {
+ properties.remove(AVD_INI_IMAGES_1);
+ properties.remove(AVD_INI_IMAGES_2);
+
+ try {
+ String property = AVD_INI_IMAGES_1;
+
+ // First the image folders of the target itself
+ String imagePath = getImageRelativePath(image);
+ if (imagePath != null) {
+ properties.put(property, imagePath);
+ return true;
+ }
+
+ return false;
+ } catch (InvalidTargetPathException e) {
+ log.error(e, e.getMessage());
+ }
+
+ return false;
+ }
+
+ /**
+ * Replaces an old {@link AvdInfo} with a new one in the lists storing them.
+ * @param oldAvd the {@link AvdInfo} to remove.
+ * @param newAvd the {@link AvdInfo} to add.
+ */
+ private void replaceAvd(AvdInfo oldAvd, AvdInfo newAvd) {
+ synchronized (mAllAvdList) {
+ mAllAvdList.remove(oldAvd);
+ mAllAvdList.add(newAvd);
+ mValidAvdList = mBrokenAvdList = null;
+ }
+ }
+
+ /**
+ * @deprecated This should only be used by swt code, and should be removed once swt is removed.
+ */
+ @Deprecated
+ public AndroidSdkHandler getSdkHandler() {
+ return mSdkHandler;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/avd/GpuMode.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/GpuMode.java
new file mode 100644
index 0000000..b251fc0
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/avd/GpuMode.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.sdklib.internal.avd;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * Describes the GPU mode settings supported by the emulator.
+ */
+public enum GpuMode {
+ AUTO("auto"),
+ HOST("host"),
+ MESA("mesa"),
+ SWIFT("guest"),
+ OFF("off");
+
+ private String mySetting;
+
+ GpuMode(@NonNull String setting) {
+ mySetting = setting;
+ }
+
+ @Override
+ public String toString() {
+ switch (this) {
+ case AUTO:
+ return "Auto";
+ case HOST:
+ return "Hardware - GLES 2.0";
+ case MESA:
+ case SWIFT:
+ return "Software - GLES 2.0";
+ case OFF:
+ default:
+ return "Software - GLES 1.1";
+ }
+ }
+
+ public static GpuMode fromGpuSetting(@Nullable String setting) {
+ for (GpuMode mode : values()) {
+ if (mode.mySetting.equals(setting)) {
+ return mode;
+ }
+ }
+ return OFF;
+ }
+
+ public String getGpuSetting() {
+ return mySetting;
+ }
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java
rename to sdklib/src/main/java/com/android/sdklib/internal/avd/HardwareProperties.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfig.template b/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfig.template
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfig.template
rename to sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfig.template
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java b/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java
rename to sdklib/src/main/java/com/android/sdklib/internal/build/BuildConfigGenerator.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/build/DebugKeyProvider.java b/sdklib/src/main/java/com/android/sdklib/internal/build/DebugKeyProvider.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/build/DebugKeyProvider.java
rename to sdklib/src/main/java/com/android/sdklib/internal/build/DebugKeyProvider.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java b/sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java
rename to sdklib/src/main/java/com/android/sdklib/internal/build/KeystoreHelper.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java b/sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java
rename to sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolLoader.java b/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolLoader.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolLoader.java
rename to sdklib/src/main/java/com/android/sdklib/internal/build/SymbolLoader.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java b/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java
rename to sdklib/src/main/java/com/android/sdklib/internal/build/SymbolWriter.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/project/IPropertySource.java b/sdklib/src/main/java/com/android/sdklib/internal/project/IPropertySource.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/project/IPropertySource.java
rename to sdklib/src/main/java/com/android/sdklib/internal/project/IPropertySource.java
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java
new file mode 100644
index 0000000..7e5d454
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2008 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.sdklib.internal.project;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.io.FolderWrapper;
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.io.StreamException;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.google.common.io.Closeables;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class representing project properties for both ADT and Ant-based build.
+ * <p/>The class is associated to a {@link PropertyType} that indicate which of the project
+ * property file is represented.
+ * <p/>To load an existing file, use {@link #load(IAbstractFolder, PropertyType)}.
+ * <p/>The class is meant to be always in sync (or at least not newer) than the file it represents.
+ * Once created, it can only be updated through {@link #reload()}
+ *
+ * <p/>The make modification or make new file, use a {@link ProjectPropertiesWorkingCopy} instance,
+ * either through {@link #create(IAbstractFolder, PropertyType)} or through
+ * {@link #makeWorkingCopy()}.
+ *
+ */
+public class ProjectProperties implements IPropertySource {
+ protected static final Pattern PATTERN_PROP = Pattern.compile(
+ "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
+
+ /** The property name for the project target */
+ public static final String PROPERTY_TARGET = "target";
+ /** The property name for the renderscript build target */
+ public static final String PROPERTY_RS_TARGET = "renderscript.target";
+ /** The property name for the renderscript support mode */
+ public static final String PROPERTY_RS_SUPPORT = "renderscript.support.mode";
+ /** The version of the build tools to use to compile */
+ public static final String PROPERTY_BUILD_TOOLS = "sdk.buildtools";
+
+ public static final String PROPERTY_LIBRARY = "android.library";
+ public static final String PROPERTY_LIB_REF = "android.library.reference.";
+ private static final String PROPERTY_LIB_REF_REGEX = "android.library.reference.\\d+";
+
+ public static final String PROPERTY_PROGUARD_CONFIG = "proguard.config";
+ public static final String PROPERTY_RULES_PATH = "layoutrules.jars";
+
+ public static final String PROPERTY_SDK = "sdk.dir";
+ public static final String PROPERTY_NDK = "ndk.dir";
+ // LEGACY - Kept so that we can actually remove it from local.properties.
+ private static final String PROPERTY_SDK_LEGACY = "sdk-location";
+
+ public static final String PROPERTY_SPLIT_BY_DENSITY = "split.density";
+ public static final String PROPERTY_SPLIT_BY_ABI = "split.abi";
+ public static final String PROPERTY_SPLIT_BY_LOCALE = "split.locale";
+
+ public static final String PROPERTY_TESTED_PROJECT = "tested.project.dir";
+
+ public static final String PROPERTY_BUILD_SOURCE_DIR = "source.dir";
+ public static final String PROPERTY_BUILD_OUT_DIR = "out.dir";
+
+ public static final String PROPERTY_PACKAGE = "package";
+ public static final String PROPERTY_VERSIONCODE = "versionCode";
+ public static final String PROPERTY_PROJECTS = "projects";
+ public static final String PROPERTY_KEY_STORE = "key.store";
+ public static final String PROPERTY_KEY_ALIAS = "key.alias";
+
+ public enum PropertyType {
+ ANT(SdkConstants.FN_ANT_PROPERTIES, BUILD_HEADER, new String[] {
+ PROPERTY_BUILD_SOURCE_DIR, PROPERTY_BUILD_OUT_DIR
+ }, null),
+ PROJECT(SdkConstants.FN_PROJECT_PROPERTIES, DEFAULT_HEADER, new String[] {
+ PROPERTY_TARGET, PROPERTY_LIBRARY, PROPERTY_LIB_REF_REGEX,
+ PROPERTY_KEY_STORE, PROPERTY_KEY_ALIAS, PROPERTY_PROGUARD_CONFIG,
+ PROPERTY_RULES_PATH
+ }, null),
+ LOCAL(SdkConstants.FN_LOCAL_PROPERTIES, LOCAL_HEADER, new String[] {
+ PROPERTY_SDK
+ },
+ new String[] { PROPERTY_SDK_LEGACY }),
+ @Deprecated
+ LEGACY_DEFAULT("default.properties", null, null, null),
+ @Deprecated
+ LEGACY_BUILD("build.properties", null, null, null);
+
+
+ private final String mFilename;
+ private final String mHeader;
+ private final Set<String> mKnownProps;
+ private final Set<String> mRemovedProps;
+
+ /**
+ * Returns the PropertyTypes ordered the same way Ant order them.
+ */
+ public static PropertyType[] getOrderedTypes() {
+ return new PropertyType[] {
+ PropertyType.LOCAL, PropertyType.ANT, PropertyType.PROJECT
+ };
+ }
+
+ PropertyType(String filename, String header, String[] validProps, String[] removedProps) {
+ mFilename = filename;
+ mHeader = header;
+ HashSet<String> s = new HashSet<String>();
+ if (validProps != null) {
+ s.addAll(Arrays.asList(validProps));
+ }
+ mKnownProps = Collections.unmodifiableSet(s);
+
+ s = new HashSet<String>();
+ if (removedProps != null) {
+ s.addAll(Arrays.asList(removedProps));
+ }
+ mRemovedProps = Collections.unmodifiableSet(s);
+
+ }
+
+ public String getFilename() {
+ return mFilename;
+ }
+
+ public String getHeader() {
+ return mHeader;
+ }
+
+ /**
+ * Returns whether a given property is known for the property type.
+ */
+ public boolean isKnownProperty(String name) {
+ for (String propRegex : mKnownProps) {
+ if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether a given property should be removed for the property type.
+ */
+ public boolean isRemovedProperty(String name) {
+ for (String propRegex : mRemovedProps) {
+ if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ private static final String LOCAL_HEADER =
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
+ "# This file is automatically generated by Android Tools.\n" +
+ "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
+ "#\n" +
+ "# This file must *NOT* be checked into Version Control Systems,\n" +
+ "# as it contains information specific to your local configuration.\n" +
+ "\n";
+
+ private static final String DEFAULT_HEADER =
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
+ "# This file is automatically generated by Android Tools.\n" +
+ "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
+ "#\n" +
+ "# This file must be checked in Version Control Systems.\n" +
+ "#\n" +
+ "# To customize properties used by the Ant build system edit\n" +
+ "# \"ant.properties\", and override values to adapt the script to your\n" +
+ "# project structure.\n" +
+ "#\n" +
+ "# To enable ProGuard to shrink and obfuscate your code, uncomment this "
+ + "(available properties: sdk.dir, user.home):\n" +
+ // Note: always use / separators in the properties paths. Both Ant and
+ // our ExportHelper will convert them properly according to the platform.
+ "#" + PROPERTY_PROGUARD_CONFIG + "=${" + PROPERTY_SDK +"}/"
+ + SdkConstants.FD_TOOLS + '/' + SdkConstants.FD_PROGUARD + '/'
+ + SdkConstants.FN_ANDROID_PROGUARD_FILE + ':'
+ + SdkConstants.FN_PROJECT_PROGUARD_FILE +'\n' +
+ "\n";
+
+ private static final String BUILD_HEADER =
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
+ "# This file is used to override default values used by the Ant build system.\n" +
+ "#\n" +
+ "# This file must be checked into Version Control Systems, as it is\n" +
+ "# integral to the build system of your project.\n" +
+ "\n" +
+ "# This file is only used by the Ant script.\n" +
+ "\n" +
+ "# You can use this to override default values such as\n" +
+ "# 'source.dir' for the location of your java source folder and\n" +
+ "# 'out.dir' for the location of your output folder.\n" +
+ "\n" +
+ "# You can also use it define how the release builds are signed by declaring\n" +
+ "# the following properties:\n" +
+ "# 'key.store' for the location of your keystore and\n" +
+ "# 'key.alias' for the name of the key to use.\n" +
+ "# The password will be asked during the build when you use the 'release' target.\n" +
+ "\n";
+
+ protected final IAbstractFolder mProjectFolder;
+ protected final Map<String, String> mProperties;
+ protected final PropertyType mType;
+
+ /**
+ * Loads a project properties file and return a {@link ProjectProperties} object
+ * containing the properties.
+ *
+ * @param projectFolderOsPath the project folder.
+ * @param type One the possible {@link PropertyType}s.
+ */
+ public static ProjectProperties load(String projectFolderOsPath, PropertyType type) {
+ IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath);
+ return load(wrapper, type);
+ }
+
+ /**
+ * Loads a project properties file and return a {@link ProjectProperties} object
+ * containing the properties.
+ *
+ * @param projectFolder the project folder.
+ * @param type One the possible {@link PropertyType}s.
+ */
+ public static ProjectProperties load(IAbstractFolder projectFolder, PropertyType type) {
+ if (projectFolder.exists()) {
+ IAbstractFile propFile = projectFolder.getFile(type.mFilename);
+ if (propFile.exists()) {
+ Map<String, String> map = parsePropertyFile(propFile, null /* log */);
+ if (map != null) {
+ return new ProjectProperties(projectFolder, map, type);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Deletes a project properties file.
+ *
+ * @param projectFolder the project folder.
+ * @param type One the possible {@link PropertyType}s.
+ * @return true if success.
+ */
+ public static boolean delete(IAbstractFolder projectFolder, PropertyType type) {
+ if (projectFolder.exists()) {
+ IAbstractFile propFile = projectFolder.getFile(type.mFilename);
+ if (propFile.exists()) {
+ return propFile.delete();
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Deletes a project properties file.
+ *
+ * @param projectFolderOsPath the project folder.
+ * @param type One the possible {@link PropertyType}s.
+ * @return true if success.
+ */
+ public static boolean delete(String projectFolderOsPath, PropertyType type) {
+ IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath);
+ return delete(wrapper, type);
+ }
+
+
+ /**
+ * Creates a new project properties object, with no properties.
+ * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
+ * @param projectFolderOsPath the project folder.
+ * @param type the type of property file to create
+ *
+ * @see #createEmpty(String, PropertyType)
+ */
+ public static ProjectPropertiesWorkingCopy create(@NonNull String projectFolderOsPath,
+ @NonNull PropertyType type) {
+ // create and return a ProjectProperties with an empty map.
+ IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
+ return create(folder, type);
+ }
+
+ /**
+ * Creates a new project properties object, with no properties.
+ * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
+ * @param projectFolder the project folder.
+ * @param type the type of property file to create
+ *
+ * @see #createEmpty(IAbstractFolder, PropertyType)
+ */
+ public static ProjectPropertiesWorkingCopy create(@NonNull IAbstractFolder projectFolder,
+ @NonNull PropertyType type) {
+ // create and return a ProjectProperties with an empty map.
+ return new ProjectPropertiesWorkingCopy(projectFolder, new HashMap<String, String>(), type);
+ }
+
+ /**
+ * Creates a new project properties object, with no properties.
+ * <p/>Nothing can be added to it, unless a {@link ProjectPropertiesWorkingCopy} is created
+ * first with {@link #makeWorkingCopy()}.
+ * @param projectFolderOsPath the project folder.
+ * @param type the type of property file to create
+ *
+ * @see #create(String, PropertyType)
+ */
+ public static ProjectProperties createEmpty(@NonNull String projectFolderOsPath,
+ @NonNull PropertyType type) {
+ // create and return a ProjectProperties with an empty map.
+ IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
+ return createEmpty(folder, type);
+ }
+
+ /**
+ * Creates a new project properties object, with no properties.
+ * <p/>Nothing can be added to it, unless a {@link ProjectPropertiesWorkingCopy} is created
+ * first with {@link #makeWorkingCopy()}.
+ * @param projectFolder the project folder.
+ * @param type the type of property file to create
+ *
+ * @see #create(IAbstractFolder, PropertyType)
+ */
+ public static ProjectProperties createEmpty(@NonNull IAbstractFolder projectFolder,
+ @NonNull PropertyType type) {
+ // create and return a ProjectProperties with an empty map.
+ return new ProjectProperties(projectFolder, new HashMap<String, String>(), type);
+ }
+
+ /**
+ * Returns the location of this property file.
+ */
+ public IAbstractFile getFile() {
+ return mProjectFolder.getFile(mType.mFilename);
+ }
+
+ /**
+ * Creates and returns a copy of the current properties as a
+ * {@link ProjectPropertiesWorkingCopy} that can be modified and saved.
+ * @return a new instance of {@link ProjectPropertiesWorkingCopy}
+ */
+ public ProjectPropertiesWorkingCopy makeWorkingCopy() {
+ return makeWorkingCopy(mType);
+ }
+
+ /**
+ * Creates and returns a copy of the current properties as a
+ * {@link ProjectPropertiesWorkingCopy} that can be modified and saved. This also allows
+ * converting to a new type, by specifying a different {@link PropertyType}.
+ *
+ * @param type the {@link PropertyType} of the prop file to save.
+ *
+ * @return a new instance of {@link ProjectPropertiesWorkingCopy}
+ */
+ public ProjectPropertiesWorkingCopy makeWorkingCopy(PropertyType type) {
+ // copy the current properties in a new map
+ Map<String, String> propList = new HashMap<String, String>(mProperties);
+
+ return new ProjectPropertiesWorkingCopy(mProjectFolder, propList, type);
+ }
+
+ /**
+ * Returns the type of the property file.
+ *
+ * @see PropertyType
+ */
+ public PropertyType getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the value of a property.
+ * @param name the name of the property.
+ * @return the property value or null if the property is not set.
+ */
+ @Override
+ public synchronized String getProperty(String name) {
+ return mProperties.get(name);
+ }
+
+ /**
+ * Returns a set of the property keys. Unlike {@link Map#keySet()} this is not a view of the
+ * map keys. Modifying the returned {@link Set} will not impact the underlying {@link Map}.
+ */
+ public synchronized Set<String> keySet() {
+ return new HashSet<String>(mProperties.keySet());
+ }
+
+ /**
+ * Reloads the properties from the underlying file.
+ */
+ public synchronized void reload() {
+ if (mProjectFolder.exists()) {
+ IAbstractFile propFile = mProjectFolder.getFile(mType.mFilename);
+ if (propFile.exists()) {
+ Map<String, String> map = parsePropertyFile(propFile, null /* log */);
+ if (map != null) {
+ mProperties.clear();
+ mProperties.putAll(map);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses a property file (using UTF-8 encoding) and returns a map of the content.
+ * <p/>
+ * If the file is not present, null is returned with no error messages sent to the log.
+ * <p/>
+ * IMPORTANT: This method is now unfortunately used in multiple places to parse random
+ * property files. This is NOT a safe practice since there is no corresponding method
+ * to write property files unless you use {@link ProjectPropertiesWorkingCopy#save()}.
+ * Code that writes INI or properties without at least using
+ * {@link SdkUtils#escapePropertyValue(String)} (String)} will
+ * certainly not load back correct data. <br/>
+ * Unless there's a strong legacy need to support existing files, new callers should
+ * probably just use Java's {@link Properties} which has well defined semantics.
+ * It's also a mistake to write/read property files using this code and expect it to
+ * work with Java's {@link Properties} or external tools (e.g. ant) since there can be
+ * differences in escaping and in character encoding.
+ *
+ * @param propFile the property file to parse
+ * @param log the ILogger object receiving warning/error from the parsing.
+ * @return the map of (key,value) pairs, or null if the parsing failed.
+ */
+ public static Map<String, String> parsePropertyFile(
+ @NonNull IAbstractFile propFile,
+ @Nullable ILogger log) {
+ InputStream is = null;
+ try {
+ is = propFile.getContents();
+ return parsePropertyStream(is,
+ propFile.getOsLocation(),
+ log);
+ } catch (StreamException e) {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.",
+ propFile.getOsLocation(),
+ e.getMessage());
+ }
+ } finally {
+ try {
+ Closeables.close(is, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+
+
+ return null;
+ }
+
+ /**
+ * Parses a property file (using UTF-8 encoding) and returns a map of the content.
+ * <p/>
+ * Always closes the given input stream on exit.
+ * <p/>
+ * IMPORTANT: This method is now unfortunately used in multiple places to parse random
+ * property files. This is NOT a safe practice since there is no corresponding method
+ * to write property files unless you use {@link ProjectPropertiesWorkingCopy#save()}.
+ * Code that writes INI or properties without at least using
+ * {@link SdkUtils#escapePropertyValue(String)} (String)} will
+ * certainly not load back correct data. <br/>
+ * Unless there's a strong legacy need to support existing files, new callers should
+ * probably just use Java's {@link Properties} which has well defined semantics.
+ * It's also a mistake to write/read property files using this code and expect it to
+ * work with Java's {@link Properties} or external tools (e.g. ant) since there can be
+ * differences in escaping and in character encoding.
+ *
+ * @param propStream the input stream of the property file to parse.
+ * @param propPath the file path, for display purposed in case of error.
+ * @param log the ILogger object receiving warning/error from the parsing.
+ * @return the map of (key,value) pairs, or null if the parsing failed.
+ */
+ public static Map<String, String> parsePropertyStream(
+ @NonNull InputStream propStream,
+ @NonNull String propPath,
+ @Nullable ILogger log) {
+ BufferedReader reader = null;
+ try {
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ reader = new BufferedReader(
+ new InputStreamReader(propStream, SdkConstants.INI_CHARSET));
+
+ String line = null;
+ Map<String, String> map = new HashMap<String, String>();
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (!line.isEmpty() && line.charAt(0) != '#') {
+
+ Matcher m = PATTERN_PROP.matcher(line);
+ if (m.matches()) {
+ map.put(m.group(1), unescape(m.group(2)));
+ } else {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
+ propPath,
+ line);
+ }
+ return null;
+ }
+ }
+ }
+
+ return map;
+ } catch (FileNotFoundException e) {
+ // this should not happen since we usually test the file existence before
+ // calling the method.
+ // Return null below.
+ } catch (IOException e) {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.",
+ propPath,
+ e.getMessage());
+ }
+ } finally {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(propStream, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Private constructor.
+ * <p/>
+ * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
+ * to instantiate.
+ */
+ protected ProjectProperties(
+ @NonNull IAbstractFolder projectFolder,
+ @NonNull Map<String, String> map,
+ @NonNull PropertyType type) {
+ mProjectFolder = projectFolder;
+ mProperties = map;
+ mType = type;
+ }
+
+ private static String unescape(String value) {
+ return value.replaceAll("\\\\\\\\", "\\\\").replace("\\:", ":");
+ }
+
+ @Override
+ public void debugPrint() {
+ System.out.println("DEBUG PROJECTPROPERTIES: " + mProjectFolder);
+ System.out.println("type: " + mType);
+ for (Entry<String, String> entry : mProperties.entrySet()) {
+ System.out.println(entry.getKey() + " -> " + entry.getValue());
+ }
+ System.out.println("<<< DEBUG PROJECTPROPERTIES");
+
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
new file mode 100644
index 0000000..5d7afb1
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2010 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.sdklib.internal.project;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.io.StreamException;
+import com.android.utils.SdkUtils;
+import com.google.common.io.Closeables;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Matcher;
+
+/**
+ * A modifiable and savable copy of a {@link ProjectProperties}.
+ * <p/>This copy gives access to modification method such as {@link #setProperty(String, String)}
+ * and {@link #removeProperty(String)}.
+ *
+ * To get access to an instance, use {@link ProjectProperties#makeWorkingCopy()} or
+ * {@link ProjectProperties#create(IAbstractFolder, PropertyType)}.
+ */
+public class ProjectPropertiesWorkingCopy extends ProjectProperties {
+
+ private static final Map<String, String> COMMENT_MAP = new HashMap<String, String>();
+ static {
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
+ COMMENT_MAP.put(PROPERTY_TARGET,
+ "# Project target.\n");
+ COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY,
+ "# Indicates whether an apk should be generated for each density.\n");
+ COMMENT_MAP.put(PROPERTY_SDK,
+ "# location of the SDK. This is only used by Ant\n" +
+ "# For customization when using a Version Control System, please read the\n" +
+ "# header note.\n");
+ COMMENT_MAP.put(PROPERTY_PACKAGE,
+ "# Package of the application being exported\n");
+ COMMENT_MAP.put(PROPERTY_VERSIONCODE,
+ "# Major version code\n");
+ COMMENT_MAP.put(PROPERTY_PROJECTS,
+ "# List of the Android projects being used for the export.\n" +
+ "# The list is made of paths that are relative to this project,\n" +
+ "# using forward-slash (/) as separator, and are separated by colons (:).\n");
+ }
+
+
+ /**
+ * Sets a new properties. If a property with the same name already exists, it is replaced.
+ * @param name the name of the property.
+ * @param value the value of the property.
+ */
+ public synchronized void setProperty(String name, String value) {
+ mProperties.put(name, value);
+ }
+
+ /**
+ * Removes a property and returns its previous value (or null if the property did not exist).
+ * @param name the name of the property to remove.
+ */
+ public synchronized String removeProperty(String name) {
+ return mProperties.remove(name);
+ }
+
+ /**
+ * Merges all properties from the given file into the current properties.
+ * <p/>
+ * This emulates the Ant behavior: existing properties are <em>not</em> overridden.
+ * Only new undefined properties become defined.
+ * <p/>
+ * Typical usage:
+ * <ul>
+ * <li>Create a ProjectProperties with {@code PropertyType#ANT}
+ * <li>Merge in values using {@code PropertyType#PROJECT}
+ * <li>The result is that this contains all the properties from default plus those
+ * overridden by the build.properties file.
+ * </ul>
+ *
+ * @param type One the possible {@link ProjectProperties.PropertyType}s.
+ * @return this object, for chaining.
+ */
+ public synchronized ProjectPropertiesWorkingCopy merge(PropertyType type) {
+ if (mProjectFolder.exists() && mType != type) {
+ IAbstractFile propFile = mProjectFolder.getFile(type.getFilename());
+ if (propFile.exists()) {
+ Map<String, String> map = parsePropertyFile(propFile, null /* log */);
+ if (map != null) {
+ for (Entry<String, String> entry : map.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (!mProperties.containsKey(key) && value != null) {
+ mProperties.put(key, value);
+ }
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+
+ /**
+ * Saves the property file, using UTF-8 encoding.
+ * @throws IOException
+ * @throws StreamException
+ */
+ public synchronized void save() throws IOException, StreamException {
+ IAbstractFile toSave = mProjectFolder.getFile(mType.getFilename());
+
+ // write the whole file in a byte array before dumping it in the file. This
+ // This is so that if the file already existing
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ OutputStreamWriter writer = new OutputStreamWriter(baos, SdkConstants.INI_CHARSET);
+
+ if (toSave.exists()) {
+ InputStream contentStream = toSave.getContents();
+ InputStreamReader isr = null;
+ BufferedReader reader = null;
+
+ try {
+ contentStream = toSave.getContents();
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ isr = new InputStreamReader(contentStream, SdkConstants.INI_CHARSET);
+ //noinspection IOResourceOpenedButNotSafelyClosed
+ reader = new BufferedReader(isr);
+
+ // since we're reading the existing file and replacing values with new ones, or skipping
+ // removed values, we need to record what properties have been visited, so that
+ // we can figure later what new properties need to be added at the end of the file.
+ Set<String> visitedProps = new HashSet<String>();
+
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ // check if this is a line containing a property.
+ if (!line.isEmpty() && line.charAt(0) != '#') {
+
+ Matcher m = PATTERN_PROP.matcher(line);
+ if (m.matches()) {
+ String key = m.group(1);
+ String value = m.group(2);
+
+ // record the prop
+ visitedProps.add(key);
+
+ // check if this property must be removed.
+ if (mType.isRemovedProperty(key)) {
+ value = null;
+ } else if (mProperties.containsKey(key)) { // if the property still exists.
+ // put the new value.
+ value = mProperties.get(key);
+ } else {
+ // property doesn't exist. Check if it's a known property.
+ // if it's a known one, we'll remove it, otherwise, leave it untouched.
+ if (mType.isKnownProperty(key)) {
+ value = null;
+ }
+ }
+
+ // if the value is still valid, write it down.
+ if (value != null) {
+ writeValue(writer, key, value, false /*addComment*/);
+ }
+ } else {
+ // the line was wrong, let's just ignore it so that it's removed from the
+ // file.
+ }
+ } else {
+ // non-property line: just write the line in the output as-is.
+ writer.append(line).append('\n');
+ }
+ }
+
+ // now add the new properties.
+ for (Entry<String, String> entry : mProperties.entrySet()) {
+ if (!visitedProps.contains(entry.getKey())) {
+ String value = entry.getValue();
+ if (value != null) {
+ writeValue(writer, entry.getKey(), value, true /*addComment*/);
+ }
+ }
+ }
+ } finally {
+ try {
+ Closeables.close(reader, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(isr, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ try {
+ Closeables.close(contentStream, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+ }
+
+ } else {
+ // new file, just write it all
+
+ // write the header (can be null, for example for PropertyType.LEGACY_BUILD)
+ if (mType.getHeader() != null) {
+ writer.write(mType.getHeader());
+ }
+
+ // write the properties.
+ for (Entry<String, String> entry : mProperties.entrySet()) {
+ String value = entry.getValue();
+ if (value != null) {
+ writeValue(writer, entry.getKey(), value, true /*addComment*/);
+ }
+ }
+ }
+
+ writer.flush();
+
+ // now put the content in the file.
+ OutputStream filestream = toSave.getOutputStream();
+ filestream.write(baos.toByteArray());
+ filestream.flush();
+ filestream.close();
+ }
+
+ private void writeValue(OutputStreamWriter writer, String key, String value,
+ boolean addComment) throws IOException {
+ if (addComment) {
+ String comment = COMMENT_MAP.get(key);
+ if (comment != null) {
+ writer.write(comment);
+ }
+ }
+
+ writer.write(String.format("%s=%s\n", key, SdkUtils.escapePropertyValue(value)));
+ }
+
+ /**
+ * Private constructor.
+ * <p/>
+ * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
+ * to instantiate.
+ */
+ ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map,
+ PropertyType type) {
+ super(projectFolder, map, type);
+ }
+
+ @NonNull
+ public ProjectProperties makeReadOnlyCopy() {
+ // copy the current properties in a new map
+ Map<String, String> propList = new HashMap<String, String>(mProperties);
+
+ return new ProjectProperties(mProjectFolder, propList, mType);
+ }
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/CanceledByUserException.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/CanceledByUserException.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/repository/CanceledByUserException.java
rename to sdklib/src/main/java/com/android/sdklib/internal/repository/CanceledByUserException.java
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java
new file mode 100644
index 0000000..2259e94
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/DownloadCache.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.utils.Pair;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.message.BasicHeader;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * A simple cache for the XML resources handled by the SDK Manager.
+ * <p/>
+ * Callers should use {@link #openDirectUrl} to download "large files"
+ * that should not be cached (like actual installation packages which are several MBs big)
+ * and call {@link #openCachedUrl(String, ITaskMonitor)} to download small XML files.
+ * <p/>
+ * The cache can work in 3 different strategies (direct is a pass-through, fresh-cache is the
+ * default and tries to update resources if they are older than 10 minutes by respecting
+ * either ETag or Last-Modified, and finally server-cache is a strategy to always serve
+ * cached entries if present.)
+ *
+ * @deprecated
+ * com.android.sdklib.internal.repository has moved into Studio as
+ * com.android.tools.idea.sdk.remote.internal.
+ */
+ at Deprecated
+public class DownloadCache {
+
+ /*
+ * HTTP/1.1 references:
+ * - Possible headers:
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ * - Rules about conditional requests:
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
+ * - Error codes:
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
+ */
+
+ private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null; //$NON-NLS-1$
+
+ /** Key for the Status-Code in the info properties. */
+ private static final String KEY_STATUS_CODE = "Status-Code"; //$NON-NLS-1$
+ /** Key for the URL in the info properties. */
+ private static final String KEY_URL = "URL"; //$NON-NLS-1$
+
+ /** Prefix of binary files stored in the {@link SdkConstants#FD_CACHE} directory. */
+ private static final String BIN_FILE_PREFIX = "sdkbin"; //$NON-NLS-1$
+ /** Prefix of meta info files stored in the {@link SdkConstants#FD_CACHE} directory. */
+ private static final String INFO_FILE_PREFIX = "sdkinf"; //$NON-NLS-1$
+ /* Revision suffixed to the prefix. */
+ private static final String REV_FILE_PREFIX = "-1_"; //$NON-NLS-1$
+
+ /**
+ * Minimum time before we consider a cached entry is potentially stale.
+ * Expressed in milliseconds.
+ * <p/>
+ * When using the {@link Strategy#FRESH_CACHE}, the cache will not try to refresh
+ * a cached file if it's has been saved more recently than this time.
+ * When using the direct mode or the serve mode, the cache either doesn't serve
+ * cached files or always serves caches files so this expiration delay is not used.
+ * <p/>
+ * Default is 10 minutes.
+ * <p/>
+ * TODO: change for a dynamic preference later.
+ */
+ private static final long MIN_TIME_EXPIRED_MS = 10*60*1000;
+ /**
+ * Maximum time before we consider a cache entry to be stale.
+ * Expressed in milliseconds.
+ * <p/>
+ * When using the {@link Strategy#FRESH_CACHE}, entries that have no ETag
+ * or Last-Modified will be refreshed if their file timestamp is older than
+ * this value.
+ * <p/>
+ * Default is 4 hours.
+ * <p/>
+ * TODO: change for a dynamic preference later.
+ */
+ private static final long MAX_TIME_EXPIRED_MS = 4*60*60*1000;
+
+ /**
+ * The maximum file size we'll cache for "small" files.
+ * 640KB is more than enough and is already a stretch since these are read in memory.
+ * (The actual typical size of the files handled here is in the 4-64KB range.)
+ */
+ private static final int MAX_SMALL_FILE_SIZE = 640 * 1024;
+
+ /**
+ * HTTP Headers that are saved in an info file.
+ * For HTTP/1.1 header names, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ */
+ private static final String[] INFO_HTTP_HEADERS = {
+ HttpHeaders.LAST_MODIFIED,
+ HttpHeaders.ETAG,
+ HttpHeaders.CONTENT_LENGTH,
+ HttpHeaders.DATE
+ };
+
+ private final FileOp mFileOp;
+ private final File mCacheRoot;
+ private final Strategy mStrategy;
+
+ public enum Strategy {
+ /**
+ * Exclusively serves data from the cache. If files are available in the
+ * cache, serve them as is (without trying to refresh them). If files are
+ * not available, they are <em>not</em> fetched at all.
+ */
+ ONLY_CACHE,
+ /**
+ * If the files are available in the cache, serve them as-is, otherwise
+ * download them and return the cached version. No expiration or refresh
+ * is attempted if a file is in the cache.
+ */
+ SERVE_CACHE,
+ /**
+ * If the files are available in the cache, check if there's an update
+ * (either using an e-tag check or comparing to the default time expiration).
+ * If files have expired or are not in the cache then download them and return
+ * the cached version.
+ */
+ FRESH_CACHE,
+ /**
+ * Disables caching. URLs are always downloaded and returned directly.
+ * Downloaded streams aren't cached locally.
+ */
+ DIRECT
+ }
+
+ /** Creates a default instance of the URL cache */
+ public DownloadCache(@NonNull Strategy strategy) {
+ this(FileOpUtils.create(), strategy);
+ }
+
+ /** Creates a default instance of the URL cache */
+ public DownloadCache(@NonNull FileOp fileOp, @NonNull Strategy strategy) {
+ mFileOp = fileOp;
+ mCacheRoot = initCacheRoot();
+
+ // If this is defined in the environment, never use the cache. Useful for testing.
+ if (System.getenv("SDKMAN_DISABLE_CACHE") != null) { //$NON-NLS-1$
+ strategy = Strategy.DIRECT;
+ }
+
+ mStrategy = mCacheRoot == null ? Strategy.DIRECT : strategy;
+ }
+
+ @NonNull
+ public Strategy getStrategy() {
+ return mStrategy;
+ }
+
+ @Nullable
+ public File getCacheRoot() {
+ return mCacheRoot;
+ }
+
+ /**
+ * Computes the size of the cached files.
+ *
+ * @return The sum of the byte size of the cached files.
+ */
+ public long getCurrentSize() {
+ long size = 0;
+
+ if (mCacheRoot != null) {
+ File[] files = mFileOp.listFiles(mCacheRoot);
+ for (File f : files) {
+ if (mFileOp.isFile(f)) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) || name.startsWith(INFO_FILE_PREFIX)) {
+ size += f.length();
+ }
+ }
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ * Removes all cached files from the cache directory.
+ */
+ public void clearCache() {
+ if (mCacheRoot != null) {
+ File[] files = mFileOp.listFiles(mCacheRoot);
+ for (File f : files) {
+ if (mFileOp.isFile(f)) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) || name.startsWith(INFO_FILE_PREFIX)) {
+ mFileOp.delete(f);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes all obsolete cached files from the cache directory
+ * that do not match the latest revision.
+ */
+ public void clearOldCache() {
+ String prefix1 = BIN_FILE_PREFIX + REV_FILE_PREFIX;
+ String prefix2 = INFO_FILE_PREFIX + REV_FILE_PREFIX;
+ if (mCacheRoot != null) {
+ File[] files = mFileOp.listFiles(mCacheRoot);
+ for (File f : files) {
+ if (mFileOp.isFile(f)) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) ||
+ name.startsWith(INFO_FILE_PREFIX)) {
+ if (!name.startsWith(prefix1) && !name.startsWith(prefix2)) {
+ mFileOp.delete(f);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the directory to be used as a cache.
+ * Creates it if necessary.
+ * Makes it possible to disable or override the cache location in unit tests.
+ *
+ * @return An existing directory to use as a cache root dir,
+ * or null in case of error in which case the cache will be disabled.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ @Nullable
+ protected File initCacheRoot() {
+ try {
+ File root = new File(AndroidLocation.getFolder());
+ root = new File(root, SdkConstants.FD_CACHE);
+ if (!mFileOp.exists(root)) {
+ mFileOp.mkdirs(root);
+ }
+ return root;
+ } catch (AndroidLocationException e) {
+ // No root? Disable the cache.
+ return null;
+ }
+ }
+
+ /**
+ * Calls {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
+ * to actually perform a download.
+ * <p/>
+ * Isolated so that it can be overridden by unit tests.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ @NonNull
+ protected Pair<InputStream, HttpResponse> openUrl(
+ @NonNull String url,
+ boolean needsMarkResetSupport,
+ @NonNull ITaskMonitor monitor,
+ @Nullable Header[] headers) throws IOException, CanceledByUserException {
+ return UrlOpener.openUrl(url, needsMarkResetSupport, monitor, headers);
+ }
+
+
+ /**
+ * Does a direct download of the given URL using {@link UrlOpener}.
+ * This does not check the download cache and does not attempt to cache the file.
+ * Instead the HttpClient library returns a progressive download stream.
+ * <p/>
+ * For details on realm authentication and user/password handling,
+ * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
+ * documentation.
+ * <p/>
+ * The resulting input stream may not support mark/reset.
+ *
+ * @param urlString the URL string to be opened.
+ * @param headers An optional set of headers to pass when requesting the resource. Can be null.
+ * @param monitor {@link ITaskMonitor} which is related to this URL
+ * fetching.
+ * @return Returns a pair with a {@link InputStream} and an {@link HttpResponse}.
+ * The pair is never null.
+ * The input stream can be null in case of error, although in general the
+ * method will probably throw an exception instead.
+ * The caller should look at the response code's status and only accept the
+ * input stream if it's the desired code (e.g. 200 or 206).
+ * @throws IOException Exception thrown when there are problems retrieving
+ * the URL or its content.
+ * @throws CanceledByUserException Exception thrown if the user cancels the
+ * authentication dialog.
+ */
+ @NonNull
+ public Pair<InputStream, HttpResponse> openDirectUrl(
+ @NonNull String urlString,
+ @Nullable Header[] headers,
+ @NonNull ITaskMonitor monitor)
+ throws IOException, CanceledByUserException {
+ if (DEBUG) {
+ System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
+ }
+ return openUrl(
+ urlString,
+ false /*needsMarkResetSupport*/,
+ monitor,
+ headers);
+ }
+
+ /**
+ * This is a simplified convenience method that calls
+ * {@link #openDirectUrl(String, Header[], ITaskMonitor)}
+ * without passing any specific HTTP headers and returns the resulting input stream
+ * and the HTTP status code.
+ * See the original method's description for details on its behavior.
+ * <p/>
+ * {@link #openDirectUrl(String, Header[], ITaskMonitor)} can accept customized
+ * HTTP headers to send with the requests and also returns the full HTTP
+ * response -- status line with code and protocol and all headers.
+ * <p/>
+ * The resulting input stream may not support mark/reset.
+ *
+ * @param urlString the URL string to be opened.
+ * @param monitor {@link ITaskMonitor} which is related to this URL
+ * fetching.
+ * @return Returns a pair with a {@link InputStream} and an HTTP status code.
+ * The pair is never null.
+ * The input stream can be null in case of error, although in general the
+ * method will probably throw an exception instead.
+ * The caller should look at the response code's status and only accept the
+ * input stream if it's the desired code (e.g. 200 or 206).
+ * @throws IOException Exception thrown when there are problems retrieving
+ * the URL or its content.
+ * @throws CanceledByUserException Exception thrown if the user cancels the
+ * authentication dialog.
+ * @see #openDirectUrl(String, Header[], ITaskMonitor)
+ */
+ @NonNull
+ public Pair<InputStream, Integer> openDirectUrl(
+ @NonNull String urlString,
+ @NonNull ITaskMonitor monitor)
+ throws IOException, CanceledByUserException {
+ if (DEBUG) {
+ System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
+ }
+ Pair<InputStream, HttpResponse> result = openUrl(
+ urlString,
+ false /*needsMarkResetSupport*/,
+ monitor,
+ null /*headers*/);
+ return Pair.of(result.getFirst(), result.getSecond().getStatusLine().getStatusCode());
+ }
+
+ /**
+ * Downloads a small file, typically XML manifests.
+ * The current {@link Strategy} governs whether the file is served as-is
+ * from the cache, potentially updated first or directly downloaded.
+ * <p/>
+ * For large downloads (e.g. installable archives) please do not invoke the
+ * cache and instead use the {@link #openDirectUrl} method.
+ * <p/>
+ * For details on realm authentication and user/password handling,
+ * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
+ * documentation.
+ *
+ * @param urlString the URL string to be opened.
+ * @param monitor {@link ITaskMonitor} which is related to this URL
+ * fetching.
+ * @return Returns an {@link InputStream} holding the URL content.
+ * Returns null if there's no content (e.g. resource not found.)
+ * Returns null if the document is not cached and strategy is {@link Strategy#ONLY_CACHE}.
+ * @throws IOException Exception thrown when there are problems retrieving
+ * the URL or its content.
+ * @throws CanceledByUserException Exception thrown if the user cancels the
+ * authentication dialog.
+ */
+ @NonNull
+ public InputStream openCachedUrl(@NonNull String urlString, @NonNull ITaskMonitor monitor)
+ throws IOException, CanceledByUserException {
+ // Don't cache in direct mode.
+ if (mStrategy == Strategy.DIRECT) {
+ Pair<InputStream, HttpResponse> result = openUrl(
+ urlString,
+ true /*needsMarkResetSupport*/,
+ monitor,
+ null /*headers*/);
+ return result.getFirst();
+ }
+
+ File cached = new File(mCacheRoot, getCacheFilename(urlString));
+ File info = new File(mCacheRoot, getInfoFilename(cached.getName()));
+
+ boolean useCached = mFileOp.exists(cached);
+
+ if (useCached && mStrategy == Strategy.FRESH_CACHE) {
+ // Check whether the file should be served from the cache or
+ // refreshed first.
+
+ long cacheModifiedMs = mFileOp.lastModified(cached); /* last mod time in epoch/millis */
+ boolean checkCache = true;
+
+ Properties props = readInfo(info);
+ if (props == null) {
+ // No properties, no chocolate for you.
+ useCached = false;
+ } else {
+ long minExpiration = System.currentTimeMillis() - MIN_TIME_EXPIRED_MS;
+ checkCache = cacheModifiedMs < minExpiration;
+
+ if (!checkCache && DEBUG) {
+ System.out.println(String.format(
+ "%s : Too fresh [%,d ms], not checking yet.", //$NON-NLS-1$
+ urlString, cacheModifiedMs - minExpiration));
+ }
+ }
+
+ if (useCached && checkCache) {
+ assert props != null;
+
+ // Right now we only support 200 codes and will requery all 404s.
+ String code = props.getProperty(KEY_STATUS_CODE, ""); //$NON-NLS-1$
+ useCached = Integer.toString(HttpStatus.SC_OK).equals(code);
+
+ if (!useCached && DEBUG) {
+ System.out.println(String.format(
+ "%s : cache disabled by code %s", //$NON-NLS-1$
+ urlString, code));
+ }
+
+ if (useCached) {
+ // Do we have a valid Content-Length? If so, it should match the file size.
+ try {
+ long length = Long.parseLong(props.getProperty(HttpHeaders.CONTENT_LENGTH,
+ "-1")); //$NON-NLS-1$
+ if (length >= 0) {
+ useCached = length == mFileOp.length(cached);
+
+ if (!useCached && DEBUG) {
+ System.out.println(String.format(
+ "%s : cache disabled by length mismatch %d, expected %d", //$NON-NLS-1$
+ urlString, length, cached.length()));
+ }
+ }
+ } catch (NumberFormatException ignore) {}
+ }
+
+ if (useCached) {
+ // Do we have an ETag and/or a Last-Modified?
+ String etag = props.getProperty(HttpHeaders.ETAG);
+ String lastMod = props.getProperty(HttpHeaders.LAST_MODIFIED);
+
+ if (etag != null || lastMod != null) {
+ // Details on how to use them is defined at
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
+ // Bottom line:
+ // - if there's an ETag, it should be used first with an
+ // If-None-Match header. That's a strong comparison for HTTP/1.1 servers.
+ // - otherwise use a Last-Modified if an If-Modified-Since header exists.
+ // In this case, we place both and the rules indicates a spec-abiding
+ // server should strongly match ETag and weakly the Modified-Since.
+
+ // TODO there are some servers out there which report ETag/Last-Mod
+ // yet don't honor them when presented with a precondition. In this
+ // case we should identify it in the reply and invalidate ETag support
+ // for these servers and instead fallback on the pure-timeout case below.
+
+ AtomicInteger statusCode = new AtomicInteger(0);
+ InputStream is = null;
+ List<Header> headers = new ArrayList<Header>(2);
+
+ if (etag != null) {
+ headers.add(new BasicHeader(HttpHeaders.IF_NONE_MATCH, etag));
+ }
+
+ if (lastMod != null) {
+ headers.add(new BasicHeader(HttpHeaders.IF_MODIFIED_SINCE, lastMod));
+ }
+
+ if (!headers.isEmpty()) {
+ is = downloadAndCache(urlString, monitor, cached, info,
+ headers.toArray(new Header[headers.size()]),
+ statusCode);
+ }
+
+ if (is != null && statusCode.get() == HttpStatus.SC_OK) {
+ // The resource was modified, the server said there was something
+ // new, which has been cached. We can return that to the caller.
+ return is;
+ }
+
+ // If we get here, we should have is == null and code
+ // could be:
+ // - 304 for not-modified -- same resource, still available, in
+ // which case we'll use the cached one.
+ // - 404 -- resource doesn't exist anymore in which case there's
+ // no point in retrying.
+ // - For any other code, just retry a download.
+
+ if (is != null) {
+ try {
+ is.close();
+ } catch (Exception ignore) {}
+ is = null;
+ }
+
+ if (statusCode.get() == HttpStatus.SC_NOT_MODIFIED) {
+ // Cached file was not modified.
+ // Change its timestamp for the next MIN_TIME_EXPIRED_MS check.
+ cached.setLastModified(System.currentTimeMillis());
+
+ // At this point useCached==true so we'll return
+ // the cached file below.
+ } else {
+ // URL fetch returned something other than 200 or 304.
+ // For 404, we're done, no need to check the server again.
+ // For all other codes, we'll retry a download below.
+ useCached = false;
+ if (statusCode.get() == HttpStatus.SC_NOT_FOUND) {
+ return null;
+ }
+ }
+ } else {
+ // If we don't have an Etag nor Last-Modified, let's use a
+ // basic file timestamp and compare to a 1 hour threshold.
+
+ long maxExpiration = System.currentTimeMillis() - MAX_TIME_EXPIRED_MS;
+ useCached = cacheModifiedMs >= maxExpiration;
+
+ if (!useCached && DEBUG) {
+ System.out.println(String.format(
+ "[%1$s] cache disabled by timestamp %2$tD %2$tT < %3$tD %3$tT", //$NON-NLS-1$
+ urlString, cacheModifiedMs, maxExpiration));
+ }
+ }
+ }
+ }
+ }
+
+ if (useCached) {
+ // The caller needs an InputStream that supports the reset() operation.
+ // The default FileInputStream does not, so load the file into a byte
+ // array and return that.
+ try {
+ InputStream is = readCachedFile(cached);
+ if (is != null) {
+ if (DEBUG) {
+ System.out.println(String.format("%s : Use cached file", urlString)); //$NON-NLS-1$
+ }
+
+ return is;
+ }
+ } catch (IOException ignore) {}
+ }
+
+ if (!useCached && mStrategy == Strategy.ONLY_CACHE) {
+ // We don't have a document to serve from the cache.
+ if (DEBUG) {
+ System.out.println(String.format("%s : file not in cache", urlString)); //$NON-NLS-1$
+ }
+ return null;
+ }
+
+ // If we're not using the cache, try to remove the cache and download again.
+ try {
+ mFileOp.delete(cached);
+ mFileOp.delete(info);
+ } catch (SecurityException ignore) {}
+
+ return downloadAndCache(urlString, monitor, cached, info,
+ null /*headers*/, null /*statusCode*/);
+ }
+
+
+
+ // --------------
+
+ @Nullable
+ private InputStream readCachedFile(@NonNull File cached) throws IOException {
+ InputStream is = null;
+
+ int inc = 65536;
+ int curr = 0;
+ long len = cached.length();
+ assert len < Integer.MAX_VALUE;
+ if (len >= MAX_SMALL_FILE_SIZE) {
+ // This is supposed to cache small files, not 2+ GB files.
+ return null;
+ }
+ byte[] result = new byte[(int) (len > 0 ? len : inc)];
+
+ try {
+ is = mFileOp.newFileInputStream(cached);
+
+ int n;
+ while ((n = is.read(result, curr, result.length - curr)) != -1) {
+ curr += n;
+ if (curr == result.length) {
+ byte[] temp = new byte[curr + inc];
+ System.arraycopy(result, 0, temp, 0, curr);
+ result = temp;
+ }
+ }
+
+ return new ByteArrayInputStream(result, 0, curr);
+
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+ }
+
+ /**
+ * Download, cache and return as an in-memory byte stream.
+ * The download is only done if the server returns 200/OK.
+ * On success, store an info file next to the download with
+ * a few headers.
+ * <p/>
+ * This method deletes the cached file and the info file ONLY if it
+ * attempted a download and it failed to complete. It doesn't erase
+ * anything if there's no download because the server returned a 404
+ * or 304 or similar.
+ *
+ * @return An in-memory byte buffer input stream for the downloaded
+ * and locally cached file, or null if nothing was downloaded
+ * (including if it was a 304 Not-Modified status code.)
+ */
+ @Nullable
+ private InputStream downloadAndCache(
+ @NonNull String urlString,
+ @NonNull ITaskMonitor monitor,
+ @NonNull File cached,
+ @NonNull File info,
+ @Nullable Header[] headers,
+ @Nullable AtomicInteger outStatusCode)
+ throws FileNotFoundException, IOException, CanceledByUserException {
+ InputStream is = null;
+ OutputStream os = null;
+
+ int inc = 65536;
+ int curr = 0;
+ byte[] result = new byte[inc];
+
+ try {
+ Pair<InputStream, HttpResponse> r =
+ openUrl(urlString, true /*needsMarkResetSupport*/, monitor, headers);
+
+ is = r.getFirst();
+ HttpResponse response = r.getSecond();
+
+ if (DEBUG) {
+ System.out.println(String.format("%s : fetch: %s => %s", //$NON-NLS-1$
+ urlString,
+ headers == null ? "" : Arrays.toString(headers), //$NON-NLS-1$
+ response.getStatusLine()));
+ }
+
+ int code = response.getStatusLine().getStatusCode();
+
+ if (outStatusCode != null) {
+ outStatusCode.set(code);
+ }
+
+ if (code != HttpStatus.SC_OK) {
+ // Only a 200 response code makes sense here.
+ // Even the other 20x codes should not apply, e.g. no content or partial
+ // content are not statuses we want to handle and should never happen.
+ // (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1 for list)
+ return null;
+ }
+
+ os = mFileOp.newFileOutputStream(cached);
+
+ int n;
+ while ((n = is.read(result, curr, result.length - curr)) != -1) {
+ if (os != null && n > 0) {
+ os.write(result, curr, n);
+ }
+
+ curr += n;
+
+ if (os != null && curr > MAX_SMALL_FILE_SIZE) {
+ // If the file size exceeds our "small file size" threshold,
+ // stop caching. We don't want to fill the disk.
+ try {
+ os.close();
+ } catch (IOException ignore) {}
+ try {
+ cached.delete();
+ info.delete();
+ } catch (SecurityException ignore) {}
+ os = null;
+ }
+ if (curr == result.length) {
+ byte[] temp = new byte[curr + inc];
+ System.arraycopy(result, 0, temp, 0, curr);
+ result = temp;
+ }
+ }
+
+ // Close the output stream, signaling it was stored properly.
+ if (os != null) {
+ try {
+ os.close();
+ os = null;
+
+ saveInfo(urlString, response, info);
+ } catch (IOException ignore) {}
+ }
+
+ return new ByteArrayInputStream(result, 0, curr);
+
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException ignore) {}
+ // If we get here with the output stream not null, it means there
+ // was an issue and we don't want to keep that file. We'll try to
+ // delete it.
+ try {
+ mFileOp.delete(cached);
+ mFileOp.delete(info);
+ } catch (SecurityException ignore) {}
+ }
+ }
+ }
+
+ /**
+ * Saves part of the HTTP Response to the info file.
+ */
+ private void saveInfo(
+ @NonNull String urlString,
+ @NonNull HttpResponse response,
+ @NonNull File info) throws IOException {
+ Properties props = new Properties();
+
+ // we don't need the status code & URL right now.
+ // Save it in case we want to have it later (e.g. to differentiate 200 and 404.)
+ props.setProperty(KEY_URL, urlString);
+ props.setProperty(KEY_STATUS_CODE,
+ Integer.toString(response.getStatusLine().getStatusCode()));
+
+ for (String name : INFO_HTTP_HEADERS) {
+ Header h = response.getFirstHeader(name);
+ if (h != null) {
+ props.setProperty(name, h.getValue());
+ }
+ }
+
+ mFileOp.saveProperties(info, props, "## Meta data for SDK Manager cache. Do not modify."); //$NON-NLS-1$
+ }
+
+ /**
+ * Reads the info properties file.
+ * @return The properties found or null if there's no file or it can't be read.
+ */
+ @Nullable
+ private Properties readInfo(@NonNull File info) {
+ if (mFileOp.exists(info)) {
+ return mFileOp.loadProperties(info);
+ }
+ return null;
+ }
+
+ /**
+ * Computes the cache filename for the given URL.
+ * The filename uses the {@link #BIN_FILE_PREFIX}, the full URL string's hashcode and
+ * a sanitized portion of the URL filename. The returned filename is never
+ * more than 64 characters to ensure maximum file system compatibility.
+ *
+ * @param urlString The download URL.
+ * @return A leaf filename for the cached download file.
+ */
+ @NonNull
+ private String getCacheFilename(@NonNull String urlString) {
+
+ int code = 0;
+ for (int i = 0, j = urlString.length(); i < j; i++) {
+ code = code * 31 + urlString.charAt(i);
+ }
+ String hash = String.format("%08x", code);
+
+ String leaf = urlString.toLowerCase(Locale.US);
+ if (leaf.length() >= 2) {
+ int index = urlString.lastIndexOf('/', leaf.length() - 2);
+ leaf = urlString.substring(index + 1);
+ }
+
+ leaf = leaf.replaceAll("[^a-z0-9_-]+", "_");
+ leaf = leaf.replaceAll("__+", "_");
+
+ leaf = hash + '-' + leaf;
+ String prefix = BIN_FILE_PREFIX + REV_FILE_PREFIX;
+ int n = 64 - prefix.length();
+ if (leaf.length() > n) {
+ leaf = leaf.substring(0, n);
+ }
+
+ return prefix + leaf;
+ }
+
+ @NonNull
+ private String getInfoFilename(@NonNull String cacheFilename) {
+ return cacheFilename.replaceFirst(BIN_FILE_PREFIX, INFO_FILE_PREFIX);
+ }
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITaskMonitor.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/ITaskMonitor.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/repository/ITaskMonitor.java
rename to sdklib/src/main/java/com/android/sdklib/internal/repository/ITaskMonitor.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/UrlOpener.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/UrlOpener.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/repository/UrlOpener.java
rename to sdklib/src/main/java/com/android/sdklib/internal/repository/UrlOpener.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/internal/repository/UserCredentials.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/UserCredentials.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/internal/repository/UserCredentials.java
rename to sdklib/src/main/java/com/android/sdklib/internal/repository/UserCredentials.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/AddonManifestIniProps.java b/sdklib/src/main/java/com/android/sdklib/repository/AddonManifestIniProps.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/AddonManifestIniProps.java
rename to sdklib/src/main/java/com/android/sdklib/repository/AddonManifestIniProps.java
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java b/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java
new file mode 100644
index 0000000..e4a13da
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/PkgProps.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.sdklib.repository;
+
+
+
+/**
+ * Public constants used by the repository when saving {@code source.properties}
+ * files in local packages.
+ * <p/>
+ * These constants are public and part of the SDK Manager public API.
+ * Once published we can't change them arbitrarily since various parts
+ * of our build process depend on them.
+ */
+public class PkgProps {
+
+ // Base Package
+ public static final String PKG_REVISION = "Pkg.Revision"; //$NON-NLS-1$
+ public static final String PKG_LICENSE = "Pkg.License"; //$NON-NLS-1$
+ public static final String PKG_LICENSE_REF = "Pkg.LicenseRef"; //$NON-NLS-1$
+ public static final String PKG_DESC = "Pkg.Desc"; //$NON-NLS-1$
+ public static final String PKG_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$
+ public static final String PKG_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$
+ public static final String PKG_OBSOLETE = "Pkg.Obsolete"; //$NON-NLS-1$
+ public static final String PKG_LIST_DISPLAY = "Pkg.ListDisplay"; //$NON-NLS-1$
+
+ // AndroidVersion
+
+ public static final String VERSION_API_LEVEL = "AndroidVersion.ApiLevel";//$NON-NLS-1$
+ /** Code name of the platform if the platform is not final */
+ public static final String VERSION_CODENAME = "AndroidVersion.CodeName";//$NON-NLS-1$
+
+
+ // AddonPackage
+
+ public static final String ADDON_NAME = "Addon.Name"; //$NON-NLS-1$
+ public static final String ADDON_NAME_ID = "Addon.NameId"; //$NON-NLS-1$
+ public static final String ADDON_NAME_DISPLAY = "Addon.NameDisplay"; //$NON-NLS-1$
+
+ public static final String ADDON_VENDOR = "Addon.Vendor"; //$NON-NLS-1$
+ public static final String ADDON_VENDOR_ID = "Addon.VendorId"; //$NON-NLS-1$
+ public static final String ADDON_VENDOR_DISPLAY = "Addon.VendorDisplay"; //$NON-NLS-1$
+
+ // DocPackage
+
+ // ExtraPackage
+
+ public static final String EXTRA_PATH = "Extra.Path"; //$NON-NLS-1$
+ public static final String EXTRA_OLD_PATHS = "Extra.OldPaths"; //$NON-NLS-1$
+ public static final String EXTRA_MIN_API_LEVEL = "Extra.MinApiLevel"; //$NON-NLS-1$
+ public static final String EXTRA_PROJECT_FILES = "Extra.ProjectFiles"; //$NON-NLS-1$
+ public static final String EXTRA_VENDOR_ID = "Extra.VendorId"; //$NON-NLS-1$
+ public static final String EXTRA_VENDOR_DISPLAY = "Extra.VendorDisplay"; //$NON-NLS-1$
+ public static final String EXTRA_NAME_DISPLAY = "Extra.NameDisplay"; //$NON-NLS-1$
+
+ // ILayoutlibVersion
+
+ public static final String LAYOUTLIB_API = "Layoutlib.Api"; //$NON-NLS-1$
+ public static final String LAYOUTLIB_REV = "Layoutlib.Revision"; //$NON-NLS-1$
+
+ // MinToolsPackage
+
+ public static final String MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
+
+ // PlatformPackage
+
+ public static final String PLATFORM_VERSION = "Platform.Version"; //$NON-NLS-1$
+
+ // ToolPackage
+
+ public static final String MIN_PLATFORM_TOOLS_REV = "Platform.MinPlatformToolsRev";//$NON-NLS-1$
+
+
+ // SamplePackage
+
+ public static final String SAMPLE_MIN_API_LEVEL = "Sample.MinApiLevel"; //$NON-NLS-1$
+
+ // SystemImagePackage
+
+ public static final String SYS_IMG_ABI = "SystemImage.Abi"; //$NON-NLS-1$
+ public static final String SYS_IMG_TAG_ID = "SystemImage.TagId"; //$NON-NLS-1$
+ public static final String SYS_IMG_TAG_DISPLAY = "SystemImage.TagDisplay"; //$NON-NLS-1$
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/README.txt b/sdklib/src/main/java/com/android/sdklib/repository/README.txt
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/README.txt
rename to sdklib/src/main/java/com/android/sdklib/repository/README.txt
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/RepoXsdUtil.java b/sdklib/src/main/java/com/android/sdklib/repository/RepoXsdUtil.java
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/RepoXsdUtil.java
rename to sdklib/src/main/java/com/android/sdklib/repository/RepoXsdUtil.java
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java
new file mode 100644
index 0000000..2c30e5e
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDesc.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.descriptors;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.sdklib.AndroidVersion;
+import com.android.repository.api.License;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+import java.io.File;
+
+/**
+ * {@link IPkgDesc} keeps information on individual SDK packages
+ * (both local or remote packages definitions.)
+ * <br/>
+ * Packages have different attributes depending on their type.
+ * <p/>
+ * To create a new {@link IPkgDesc}, use one of the package-specific constructors
+ * provided by {@code PkgDesc.Builder.newXxx()}.
+ * <p/>
+ * To query packages capabilities, rely on {@link #getType()} and the {@code IPkgDesc.hasXxx()}
+ * methods provided by {@link IPkgDesc}.
+ */
+public interface IPkgDesc extends Comparable<IPkgDesc> {
+
+ /**
+ * Returns the type of the package.
+ * @return Returns one of the {@link PkgType} constants.
+ */
+ @NonNull
+ PkgType getType();
+
+ /**
+ * Returns the list-display meta data of this package.
+ * @return The list-display data, if available, or null.
+ */
+ @Nullable
+ String getListDisplay();
+
+ @Nullable
+ IdDisplay getName();
+
+ @Nullable
+ String getDescriptionShort();
+
+ @Nullable
+ String getDescriptionUrl();
+
+ @Nullable
+ License getLicense();
+
+ boolean isObsolete();
+
+ /**
+ * Returns the package's {@link Revision}.
+ */
+ @NonNull
+ Revision getRevision();
+
+ /**
+ * Returns the package's {@link AndroidVersion} or null.
+ * @return A non-null value if {@link #hasAndroidVersion()} is true; otherwise a null value.
+ */
+ @Nullable
+ AndroidVersion getAndroidVersion();
+
+ /**
+ * Returns the package's path string or null.
+ * <p/>
+ * For {@link PkgType#PKG_SYS_IMAGE}, the path is the system-image ABI. <br/>
+ * For {@link PkgType#PKG_PLATFORM}, the path is the platform hash string. <br/>
+ * For {@link PkgType#PKG_ADDON}, the path is the platform hash string. <br/>
+ * For {@link PkgType#PKG_EXTRA}, the path is the extra-path string. <br/>
+ *
+ * @return A non-null value if {@link #hasPath()} is true; otherwise a null value.
+ */
+ @Nullable
+ String getPath();
+
+ /**
+ * Returns the package's tag id-display tuple or null.
+ *
+ * @return A non-null tag if {@link #hasTag()} is true; otherwise a null value.
+ */
+ @Nullable
+ IdDisplay getTag();
+
+ /**
+ * Returns the package's vendor-id string or null.
+ * @return A non-null value if {@link #hasVendor()} is true; otherwise a null value.
+ */
+ @Nullable
+ IdDisplay getVendor();
+
+ /**
+ * Returns the package's {@code min-tools-rev} or null.
+ * @return A non-null value if {@link #hasMinToolsRev()} is true; otherwise a null value.
+ */
+ @Nullable
+ Revision getMinToolsRev();
+
+ /**
+ * Returns the package's {@code min-platform-tools-rev} or null.
+ * @return A non-null value if {@link #hasMinPlatformToolsRev()} is true; otherwise null.
+ */
+ @Nullable
+ Revision getMinPlatformToolsRev();
+
+ /**
+ * Indicates whether <em>this</em> package descriptor is an update for the given
+ * existing descriptor. Preview versions are never considered updates for non-
+ * previews, and vice versa.
+ *
+ * @param existingDesc A non-null existing descriptor.
+ * @return True if this package is an update for the given one.
+ */
+ boolean isUpdateFor(@NonNull IPkgDesc existingDesc);
+
+ /**
+ * Indicates whether <em>this</em> package descriptor is an update for the given
+ * existing descriptor, using the given comparison method.
+ *
+ * @param existingDesc A non-null existing descriptor.
+ * @param previewComparison The {@link Revision.PreviewComparison} method to use
+ * when comparing the packages.
+ * @return True if this package is an update for the given one.
+ */
+ boolean isUpdateFor(@NonNull IPkgDesc existingDesc,
+ @NonNull Revision.PreviewComparison previewComparison);
+
+ /**
+ * Returns a stable string id that can be used to reference this package, including
+ * a suffix indicating that this package is a preview if it is.
+ */
+ @NonNull
+ String getInstallId();
+
+ /**
+ * Returns a stable string id that can be used to reference this package, which
+ * excludes the preview suffix.
+ */
+ String getBaseInstallId();
+
+ /**
+ * Returns the canonical location where such a package would be installed.
+ * @param sdkLocation The root of the SDK.
+ * @return the canonical location where such a package would be installed.
+ */
+ @NonNull
+ File getCanonicalInstallFolder(@NonNull File sdkLocation);
+
+ /**
+ * @return True if the revision of this package is a preview.
+ */
+ boolean isPreview();
+
+ String getListDescription();
+
+ boolean hasVendor();
+
+ boolean hasAndroidVersion();
+
+ boolean hasPath();
+
+ boolean hasTag();
+
+ boolean hasMinToolsRev();
+
+ boolean hasMinPlatformToolsRev();
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java
new file mode 100644
index 0000000..c9acb31
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/IPkgDescExtra.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.descriptors;
+
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+/**
+ * {@link IPkgDescExtra} keeps information on individual extra SDK packages
+ * (both local or remote packages definitions.) The base {@link IPkgDesc} tries
+ * to present a unified interface to package attributes and this interface
+ * adds methods specific to extras.
+ * <p/>
+ * To create a new {@link IPkgDescExtra},
+ * use {@link PkgDesc.Builder#newExtra(IdDisplay, String, String, String[], Revision)} )}.
+ * <p/>
+ * The extra's revision is a {@link Revision}; the attribute is however
+ * accessed via {@link IPkgDesc#getRevision()} instead of introducing a new
+ * custom method.
+ * <p/>
+ * To query generic packages capabilities, rely on {@link #getType()} and the
+ * {@code IPkgDesc.hasXxx()} methods provided by {@link IPkgDesc}.
+ */
+public interface IPkgDescExtra extends IPkgDesc {
+ /**
+ * Returns an optional list of older paths for this extra package.
+ * @return A non-null, possibly empty, for old paths previously used for the same extra.
+ */
+ @NonNull String[] getOldPaths();
+
+ /**
+ * Returns the display name of the Extra.
+ * @return A non-null name for the Extra, used for display purposes.
+ */
+ @NonNull String getNameDisplay();
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java
new file mode 100644
index 0000000..4438933
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDesc.java
@@ -0,0 +1,1168 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.descriptors;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.repository.Revision;
+import com.android.repository.Revision.PreviewComparison;
+import com.android.repository.api.License;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.targets.SystemImage;
+
+import java.io.File;
+import java.util.Locale;
+
+/**
+ * {@link PkgDesc} keeps information on individual SDK packages
+ * (both local or remote packages definitions.)
+ * <br/>
+ * Packages have different attributes depending on their type.
+ * <p/>
+ * To create a new {@link PkgDesc}, use one of the package-specific constructors
+ * provided here.
+ * <p/>
+ * To query packages capabilities, rely on {@link #getType()} and the {@code PkgDesc.hasXxx()}
+ * methods provided in the base {@link PkgDesc}.
+ */
+public class PkgDesc implements IPkgDesc {
+ public static final String PREVIEW_SUFFIX = "-preview";
+ private final PkgType mType;
+ private final Revision mRevision;
+ private final AndroidVersion mAndroidVersion;
+ private final String mPath;
+ private final IdDisplay mTag;
+ private final IdDisplay mVendor;
+ private final Revision mMinToolsRev;
+ private final Revision mMinPlatformToolsRev;
+ private final IIsUpdateFor mCustomIsUpdateFor;
+ private final IGetPath mCustomPath;
+
+ private final License mLicense;
+ private final String mListDisplay;
+ private final String mDescriptionShort;
+ private final String mDescriptionUrl;
+ private final boolean mIsObsolete;
+ private final IdDisplay mName;
+
+ protected PkgDesc(@NonNull PkgType type,
+ @Nullable License license,
+ @Nullable String listDisplay,
+ @Nullable String descriptionShort,
+ @Nullable String descriptionUrl,
+ boolean isObsolete,
+ @Nullable Revision revision,
+ @Nullable AndroidVersion androidVersion,
+ @Nullable String path,
+ @Nullable IdDisplay tag,
+ @Nullable IdDisplay vendor,
+ @Nullable Revision minToolsRev,
+ @Nullable Revision minPlatformToolsRev,
+ @Nullable IIsUpdateFor customIsUpdateFor,
+ @Nullable IGetPath customPath,
+ @Nullable IdDisplay name) {
+ mType = type;
+ mIsObsolete = isObsolete;
+ mLicense = license;
+ mListDisplay = listDisplay;
+ mDescriptionShort = descriptionShort;
+ mDescriptionUrl = descriptionUrl;
+ mRevision = revision;
+ mAndroidVersion = androidVersion;
+ mPath = path;
+ mTag = tag;
+ mVendor = vendor;
+ mMinToolsRev = minToolsRev;
+ mMinPlatformToolsRev = minPlatformToolsRev;
+ mCustomIsUpdateFor = customIsUpdateFor;
+ mCustomPath = customPath;
+ mName = name;
+ }
+
+ @NonNull
+ @Override
+ public PkgType getType() {
+ return mType;
+ }
+
+ @Override
+ @Nullable
+ public String getListDisplay() {
+ return mListDisplay;
+ }
+
+ @Override
+ @Nullable
+ public IdDisplay getName() {
+ return mName;
+ }
+
+ @Override
+ @Nullable
+ public String getDescriptionShort() {
+ return mDescriptionShort;
+ }
+
+ @Override
+ @Nullable
+ public String getDescriptionUrl() {
+ return mDescriptionUrl;
+ }
+
+ @Override
+ @Nullable
+ public License getLicense() {
+ return mLicense;
+ }
+
+ @Override
+ public boolean isObsolete() {
+ return mIsObsolete;
+ }
+
+ @Override
+ public final boolean hasAndroidVersion() {
+ return getType().hasAndroidVersion();
+ }
+
+ @Override
+ public final boolean hasPath() {
+ return getType().hasPath() && getPath() != null;
+ }
+
+ @Override
+ public final boolean hasTag() {
+ return getType().hasTag();
+ }
+
+ @Override
+ public boolean hasVendor() {
+ return getType().hasVendor();
+ }
+
+ public boolean hasName() {
+ return getType().hasName();
+ }
+
+ @Override
+ public final boolean hasMinToolsRev() {
+ return getType().hasMinToolsRev();
+ }
+
+ @Override
+ public final boolean hasMinPlatformToolsRev() {
+ return getType().hasMinPlatformToolsRev();
+ }
+
+ @NonNull
+ @Override
+ public final Revision getRevision() {
+ return mRevision;
+ }
+
+ @Nullable
+ @Override
+ public AndroidVersion getAndroidVersion() {
+ return mAndroidVersion;
+ }
+
+ @Override
+ public boolean isPreview() {
+ return getRevision().isPreview();
+ }
+
+ @Nullable
+ @Override
+ public String getPath() {
+ if (mCustomPath != null) {
+ return mCustomPath.getPath(this);
+ } else {
+ return mPath;
+ }
+ }
+
+ @Nullable
+ @Override
+ public IdDisplay getTag() {
+ return mTag;
+ }
+
+ @Nullable
+ @Override
+ public IdDisplay getVendor() {
+ return mVendor;
+ }
+
+ @Nullable
+ @Override
+ public Revision getMinToolsRev() {
+ return mMinToolsRev;
+ }
+
+ @Nullable
+ @Override
+ public Revision getMinPlatformToolsRev() {
+ return mMinPlatformToolsRev;
+ }
+
+ @Override
+ public String getInstallId() {
+ String id = getBaseInstallId();
+ if (getRevision().isPreview()) {
+ return id + PREVIEW_SUFFIX;
+ }
+ return id;
+ }
+
+ @Override
+ public String getBaseInstallId() {
+ StringBuilder sb = new StringBuilder();
+
+ /* iid patterns:
+ tools, platform-tools => FOLDER
+ build-tools => FOLDER-REV
+ doc, sample, source => ENUM-API
+ extra => ENUM-VENDOR.id-PATH
+ platform => android-API
+ add-on => addon-NAME.id-VENDOR.id-API
+ platform sys-img => sys-img-ABI-TAG|android-API
+ add-on sys-img => sys-img-ABI-addon-NAME.id-VENDOR.id-API
+ */
+
+ switch (mType) {
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ sb.append(mType.getFolderName());
+ break;
+
+ case PKG_LLDB:
+ Revision rev = getRevision();
+ sb.append(mType.getFolderName()).append('-');
+ sb.append(rev.getMajor());
+ sb.append('.');
+ sb.append(rev.getMinor());
+ break;
+ case PKG_BUILD_TOOLS:
+ sb.append(mType.getFolderName()).append('-');
+ // Add version number without the preview revision number. This is to make preview
+ // packages be updatable to the next revision.
+ int[] version = getRevision().toIntArray(false);
+ for (int i = 0; i < version.length; i++) {
+ sb.append(version[i]);
+ if (i != version.length - 1) {
+ sb.append('.');
+ }
+ }
+ break;
+
+ case PKG_DOC:
+ sb.append("doc");
+ break;
+
+ case PKG_SAMPLE:
+ case PKG_SOURCE:
+ sb.append(mType.toString().toLowerCase(Locale.US).replace("pkg_", ""));
+ sb.append('-').append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_EXTRA:
+ sb.append("extra-")
+ .append(getVendor().getId())
+ .append('-')
+ .append(getPath());
+ break;
+
+ case PKG_PLATFORM:
+ sb.append(AndroidTargetHash.PLATFORM_HASH_PREFIX)
+ .append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_ADDON:
+ sb.append("addon-")
+ .append(getName().getId())
+ .append('-')
+ .append(getVendor().getId())
+ .append('-')
+ .append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_SYS_IMAGE:
+ sb.append("sys-img-")
+ .append(getPath()) // path==ABI for sys-img
+ .append('-')
+ .append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
+ .append('-')
+ .append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_ADDON_SYS_IMAGE:
+ sb.append("sys-img-")
+ .append(getPath()) // path==ABI for sys-img
+ .append("-addon-")
+ .append(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
+ .append('-')
+ .append(getVendor().getId())
+ .append('-')
+ .append(getAndroidVersion().getApiString());
+ break;
+
+ case PKG_NDK:
+ sb.append("ndk");
+ break;
+
+ default:
+ throw new IllegalArgumentException("IID not defined for type " + mType.toString());
+ }
+
+ return sanitize(sb.toString());
+ }
+
+ @Override
+ public File getCanonicalInstallFolder(@NonNull File sdkLocation) {
+ File f = FileOpUtils.append(sdkLocation, mType.getFolderName());
+
+ /* folder patterns:
+ tools, platform-tools, doc => FOLDER
+ build-tools, add-on => FOLDER/IID
+ platform, sample, source => FOLDER/android-API
+ platform sys-img => FOLDER/android-API/TAG/ABI
+ add-on sys-img => FOLDER/addon-NAME.id-VENDOR.id-API/ABI
+ extra => FOLDER/VENDOR.id/PATH
+ */
+
+ switch (mType) {
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ case PKG_DOC:
+ // no-op, top-folder is all what is needed here
+ break;
+
+ case PKG_BUILD_TOOLS:
+ case PKG_ADDON:
+ f = FileOpUtils.append(f, getInstallId());
+ break;
+
+ case PKG_PLATFORM:
+ case PKG_SAMPLE:
+ case PKG_SOURCE:
+ f = FileOpUtils.append(f, AndroidTargetHash.PLATFORM_HASH_PREFIX + sanitize(
+ getAndroidVersion().getApiString()));
+ break;
+
+ case PKG_SYS_IMAGE:
+ f = FileOpUtils.append(f,
+ AndroidTargetHash.PLATFORM_HASH_PREFIX + sanitize(
+ getAndroidVersion().getApiString()),
+ sanitize(SystemImage.DEFAULT_TAG.equals(getTag()) ? "android"
+ : getTag().getId()),
+ sanitize(getPath())); // path==abi
+ break;
+
+ case PKG_ADDON_SYS_IMAGE:
+ String name = "addon-"
+ + (SystemImage.DEFAULT_TAG.equals(getTag()) ? "android" : getTag().getId())
+ + '-'
+ + getVendor().getId()
+ + '-'
+ + getAndroidVersion().getApiString();
+ f = FileOpUtils.append(f,
+ sanitize(name),
+ sanitize(getPath())); // path==abi
+ break;
+
+ case PKG_EXTRA:
+ f = FileOpUtils.append(f,
+ sanitize(getVendor().getId()),
+ sanitize(getPath()));
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ "CanonicalFolder not defined for type " + mType.toString());
+ }
+
+ return f;
+ }
+
+ //---- Updating ----
+
+ /**
+ * Computes the most general case of {@link #isUpdateFor(IPkgDesc)}.
+ * Individual package types use this and complement with their own specific cases
+ * as needed.
+ *
+ * @param existingDesc A non-null package descriptor to compare with.
+ * @return True if this package descriptor would generally update the given one.
+ */
+ @Override
+ public boolean isUpdateFor(@NonNull IPkgDesc existingDesc) {
+ return isUpdateFor(existingDesc, PreviewComparison.COMPARE_NUMBER);
+ }
+
+ @Override
+ public boolean isUpdateFor(@NonNull IPkgDesc existingDesc,
+ @NonNull PreviewComparison previewComparison) {
+ if (mCustomIsUpdateFor != null) {
+ return mCustomIsUpdateFor.isUpdateFor(this, existingDesc);
+ } else {
+ return isGenericUpdateFor(existingDesc, previewComparison);
+ }
+ }
+
+ /**
+ * Computes the most general case of {@link #isUpdateFor(IPkgDesc)}.
+ * Individual package types use this and complement with their own specific cases
+ * as needed.
+ *
+ * @param existingDesc A non-null package descriptor to compare with.
+ * @param previewComparison The type of preview comparison to do.
+ * @return True if this package descriptor would generally update the given one.
+ */
+ private boolean isGenericUpdateFor(@NonNull IPkgDesc existingDesc,
+ PreviewComparison previewComparison) {
+
+ if (existingDesc == null || !getType().equals(existingDesc.getType())) {
+ return false;
+ }
+ if (!getType().equals(PkgType.PKG_EXTRA) &&
+ !existingDesc.getBaseInstallId().equals(getBaseInstallId())) {
+ return false;
+ }
+ // Packages that have an Android version can generally only be updated
+ // for the same Android version (otherwise it's a new artifact.)
+ if (hasAndroidVersion() && !getAndroidVersion().equals(existingDesc.getAndroidVersion())) {
+ return false;
+ }
+
+ // Packages that have a vendor id need the same vendor id on both sides
+ if (hasVendor() && !getVendor().equals(existingDesc.getVendor())) {
+ return false;
+ }
+
+ // Packages that have a tag id need the same tag id on both sides
+ if (hasTag() && !getTag().getId().equals(existingDesc.getTag().getId())) {
+ return false;
+ }
+
+ // Packages that have a path can generally only be updated if both use the same path
+ if (hasPath()) {
+ if (this instanceof IPkgDescExtra) {
+ // Extra package handle paths differently, they need to use the old_path
+ // to allow upgrade compatibility.
+ if (!PkgDescExtra.compatibleVendorAndPath((IPkgDescExtra) this,
+ (IPkgDescExtra) existingDesc)) {
+ return false;
+ }
+ } else if (!getPath().equals(existingDesc.getPath())) {
+ return false;
+ }
+ }
+
+ // Packages that have a full revision are generally updates if it increases
+ // but keeps the same kind of preview (e.g. previews are only updates by previews.)
+ if (previewComparison == PreviewComparison.IGNORE
+ || existingDesc.isPreview() == isPreview()) {
+ // If both packages match in their preview type (both previews or both not previews)
+ // then is the RC/preview number an update?
+ return getRevision().compareTo(existingDesc.getRevision(),
+ PreviewComparison.COMPARE_NUMBER) > 0;
+ }
+
+ return false;
+ }
+
+
+ //---- Ordering ----
+
+ /**
+ * Compares this descriptor to another one.
+ * All fields must match for equality.
+ * <p/>
+ * This is must not be used an indication that a package is a suitable update for another one.
+ * The comparison order is however suitable for sorting packages for display purposes.
+ */
+ @Override
+ public int compareTo(@NonNull IPkgDesc o) {
+ int t1 = getType().getIntValue();
+ int t2 = o.getType().getIntValue();
+ if (t1 != t2) {
+ return t1 - t2;
+ }
+
+ if (hasAndroidVersion() && o.hasAndroidVersion()) {
+ t1 = getAndroidVersion().compareTo(o.getAndroidVersion());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasVendor() && o.hasVendor()) {
+ t1 = getVendor().compareTo(o.getVendor());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasTag() && o.hasTag()) {
+ t1 = getTag().compareTo(o.getTag());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasPath() && o.hasPath()) {
+ t1 = getPath().compareTo(o.getPath());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ t1 = getRevision().compareTo(o.getRevision());
+ if (t1 != 0) {
+ return t1;
+ }
+
+ if (hasMinToolsRev() && o.hasMinToolsRev()) {
+ t1 = getMinToolsRev().compareTo(o.getMinToolsRev());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasMinPlatformToolsRev() && o.hasMinPlatformToolsRev()) {
+ t1 = getMinPlatformToolsRev().compareTo(o.getMinPlatformToolsRev());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ return 0;
+ }
+
+ // --- display description ----
+
+ @NonNull
+ @Override
+ public String getListDescription() {
+ if (mListDisplay != null && !mListDisplay.isEmpty()) {
+ return mListDisplay;
+ }
+
+ return patternReplaceImpl(getType().getListDisplayPattern());
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected String patternReplaceImpl(String result) {
+ // Flags for list description pattern string, used in PkgType:
+ // $MAJ $FULL $API $PATH $TAG $VEND $NAME (for extras)
+
+ result = result
+ .replace("$MAJ", getRevision().toShortString());
+ result = result
+ .replace("$FULL", getRevision().toShortString());
+ result = result
+ .replace("$API", hasAndroidVersion() ? getAndroidVersion().getApiString() : "");
+ result = result.replace("$PATH", hasPath() ? getPath() : "");
+ result = result.replace("$TAG", hasTag() && !getTag().equals(
+ SystemImage.DEFAULT_TAG) ?
+ getTag().getDisplay() : "");
+ result = result.replace("$VEND", hasVendor() ? getVendor().getDisplay() : "");
+ String name = "";
+ if (hasName()) {
+ name = getName().getDisplay();
+ if (name == null) {
+ name = "";
+ }
+ }
+ result = result.replace("$NAME", name);
+
+ // Evaluate replacements.
+ // {|choice1|choice2|...|choiceN|} gets replaced by the first non-empty choice.
+ for (int start = result.indexOf("{|");
+ start >= 0;
+ start = result.indexOf("{|")) {
+ int end = result.indexOf('}', start);
+ int last = start + 1;
+ for (int pipe = result.indexOf('|', last+1);
+ pipe > start;
+ last = pipe, pipe = result.indexOf('|', last+1)) {
+ if (pipe - last > 1) {
+ result = result.substring(0, start) +
+ result.substring(last+1, pipe) +
+ result.substring(end+1);
+ break;
+ }
+ }
+ }
+
+ // Evaluate conditions.
+ // {?value>1:text to use} -- uses the text if value is greater than 1.
+ // Simplification: this is our only test right now so hard-code it instead of
+ // using a generic expression evaluation.
+ for (int start = result.indexOf("{?");
+ start >= 0;
+ start = result.indexOf("{?")) {
+ int end = result.indexOf('}', start);
+ int op = result.indexOf(">1:");
+ if (op > start) {
+ String value = "";
+ try {
+ Revision i = Revision.parseRevision(result.substring(start+2, op));
+ if (i.compareTo(new Revision(1)) > 0) {
+ value = result.substring(op+3, end);
+ }
+ } catch (NumberFormatException e) {
+ value = "ERROR " + e.getMessage() + " in " + result.substring(start, end+1);
+ }
+ result = result.substring(0, start) +
+ value +
+ result.substring(end+1);
+ }
+ }
+
+
+ return result;
+ }
+
+ /** String representation for debugging purposes. */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<PkgDesc"); //NON-NLS-1$
+
+ builder.append(" Type="); //NON-NLS-1$
+ builder.append(getType().toString()
+ .toLowerCase(Locale.US)
+ .replace("pkg_", "")); //NON-NLS-1$ //NON-NLS-2$
+
+ if (hasAndroidVersion()) {
+ builder.append(" Android=").append(getAndroidVersion()); //NON-NLS-1$
+ }
+
+ if (hasVendor()) {
+ builder.append(" Vendor=").append(getVendor().toString()); //NON-NLS-1$
+ }
+
+ if (hasTag()) {
+ builder.append(" Tag=").append(getTag()); //NON-NLS-1$
+ }
+
+ if (hasPath()) {
+ builder.append(" Path=").append(getPath()); //NON-NLS-1$
+ }
+
+ builder.append(" Rev=").append(getRevision()); //NON-NLS-1$
+
+ if (hasMinToolsRev()) {
+ builder.append(" MinToolsRev=").append(getMinToolsRev()); //NON-NLS-1$
+ }
+
+ if (hasMinPlatformToolsRev()) {
+ builder.append(" MinPlatToolsRev=").append(getMinPlatformToolsRev()); //NON-NLS-1$
+ }
+
+ if (mListDisplay != null) {
+ builder.append(" ListDisp=").append(mListDisplay); //NON-NLS-1$
+ }
+
+ if (mDescriptionShort != null) {
+ builder.append(" DescShort=").append(mDescriptionShort); //NON-NLS-1$
+ }
+
+ if (mDescriptionUrl != null) {
+ builder.append(" DescUrl=").append(mDescriptionUrl); //NON-NLS-1$
+ }
+
+ if (mLicense != null) {
+ builder.append(" License['").append(mLicense.getId()) //NON-NLS-1$
+ .append("]=") //NON-NLS-1$
+ .append(mLicense.getValue().length()).append(" chars"); //NON-NLS-1$
+ }
+
+ if (isObsolete()) {
+ builder.append(" Obsolete=yes"); //NON-NLS-1$
+ }
+
+ builder.append('>');
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (hasAndroidVersion() ? getAndroidVersion().hashCode() : 0);
+ result = prime * result + (hasVendor() ? getVendor() .hashCode() : 0);
+ result = prime * result + (hasTag() ? getTag() .hashCode() : 0);
+ result = prime * result + (hasPath() ? getPath() .hashCode() : 0);
+ result = prime * result + (getRevision() .hashCode());
+ result = prime * result + (hasMinToolsRev() ? getMinToolsRev() .hashCode() : 0);
+ result = prime * result + (hasMinPlatformToolsRev() ?
+ getMinPlatformToolsRev().hashCode() : 0);
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IPkgDesc)) return false;
+ IPkgDesc rhs = (IPkgDesc) obj;
+
+ if (hasAndroidVersion() != rhs.hasAndroidVersion()) {
+ return false;
+ }
+ if (hasAndroidVersion() && !getAndroidVersion().equals(rhs.getAndroidVersion())) {
+ return false;
+ }
+
+ if (hasTag() != rhs.hasTag()) {
+ return false;
+ }
+ if (hasTag() && !getTag().equals(rhs.getTag())) {
+ return false;
+ }
+
+ if (hasPath() != rhs.hasPath()) {
+ return false;
+ }
+ if (hasPath() && !getPath().equals(rhs.getPath())) {
+ return false;
+ }
+
+ if (!getRevision().equals(rhs.getRevision())) {
+ return false;
+ }
+
+ if (hasMinToolsRev() != rhs.hasMinToolsRev()) {
+ return false;
+ }
+ if (hasMinToolsRev() && !getMinToolsRev().equals(rhs.getMinToolsRev())) {
+ return false;
+ }
+
+ if (hasMinPlatformToolsRev() != rhs.hasMinPlatformToolsRev()) {
+ return false;
+ }
+ if (hasMinPlatformToolsRev() &&
+ !getMinPlatformToolsRev().equals(rhs.getMinPlatformToolsRev())) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ // ---- Constructors -----
+
+ public interface IIsUpdateFor {
+ boolean isUpdateFor(@NonNull PkgDesc thisPkgDesc, @NonNull IPkgDesc existingDesc);
+ }
+
+ public interface IGetPath {
+ String getPath(@NonNull PkgDesc thisPkgDesc);
+ }
+
+ public static class Builder {
+ private final PkgType mType;
+ private Revision mRevision;
+ private AndroidVersion mAndroidVersion;
+ private String mPath;
+ private IdDisplay mTag;
+ private IdDisplay mVendor;
+ private Revision mMinToolsRev;
+ private Revision mMinPlatformToolsRev;
+ private IIsUpdateFor mCustomIsUpdateFor;
+ private IGetPath mCustomPath;
+ private String[] mOldPaths;
+ private String mNameDisplay;
+ private IdDisplay mNameIdDisplay;
+
+ private License mLicense;
+ private String mListDisplay;
+ private String mDescriptionShort;
+ private String mDescriptionUrl;
+ private boolean mIsObsolete;
+
+
+ private Builder(PkgType type) {
+ mType = type;
+ }
+
+ /**
+ * Creates a new tool package descriptor.
+ *
+ * @param revision The revision of the tool package.
+ * @param minPlatformToolsRev The {@code min-platform-tools-rev}.
+ * Use {@link Revision#NOT_SPECIFIED} to indicate there is no requirement.
+ * @return A {@link PkgDesc} describing this tool package.
+ */
+ @NonNull
+ public static Builder newTool(@NonNull Revision revision,
+ @NonNull Revision minPlatformToolsRev) {
+ Builder p = new Builder(PkgType.PKG_TOOLS);
+ p.mRevision = revision;
+ p.mMinPlatformToolsRev = minPlatformToolsRev;
+ return p;
+ }
+
+ /**
+ * Creates a new platform-tool package descriptor.
+ *
+ * @param revision The revision of the platform-tool package.
+ * @return A {@link PkgDesc} describing this platform-tool package.
+ */
+ @NonNull
+ public static Builder newPlatformTool(@NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_PLATFORM_TOOLS);
+ p.mRevision = revision;
+ return p;
+ }
+
+ /**
+ * Creates a new build-tool package descriptor.
+ *
+ * @param revision The revision of the build-tool package.
+ * @return A {@link PkgDesc} describing this build-tool package.
+ */
+ @NonNull
+ public static Builder newBuildTool(@NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_BUILD_TOOLS);
+ p.mRevision = revision;
+ p.mCustomIsUpdateFor = new IIsUpdateFor() {
+ @Override
+ public boolean isUpdateFor(PkgDesc thisPkgDesc, IPkgDesc existingDesc) {
+ // Generic test checks that the preview type is the same (both previews or not).
+ // Build tool is different in that the full revision must be an exact match
+ // and not an increase.
+ return thisPkgDesc
+ .isGenericUpdateFor(existingDesc, PreviewComparison.COMPARE_NUMBER) &&
+ thisPkgDesc.getRevision().compareTo(
+ existingDesc.getRevision(),
+ PreviewComparison.COMPARE_TYPE) == 0;
+ }
+ };
+ return p;
+ }
+
+ /**
+ * Creates a new doc package descriptor.
+ *
+ * @param revision The revision of the doc package.
+ * @return A {@link PkgDesc} describing this doc package.
+ */
+ @NonNull
+ public static Builder newDoc(@NonNull AndroidVersion version,
+ @NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_DOC);
+ p.mAndroidVersion = version;
+ p.mRevision = revision;
+ p.mCustomIsUpdateFor = new IIsUpdateFor() {
+ @Override
+ public boolean isUpdateFor(PkgDesc thisPkgDesc, IPkgDesc existingDesc) {
+ if (existingDesc == null ||
+ !thisPkgDesc.getType().equals(existingDesc.getType())) {
+ return false;
+ }
+
+ // This package is unique in the SDK. It's an update if the API is newer
+ // or the revision is newer for the same API.
+ int diff = thisPkgDesc.getAndroidVersion().compareTo(
+ existingDesc.getAndroidVersion());
+ return diff > 0 ||
+ (diff == 0 && thisPkgDesc.getRevision().compareTo(
+ existingDesc.getRevision()) > 0);
+ }
+ };
+ return p;
+ }
+
+ /**
+ * Creates a new extra package descriptor.
+ *
+ * @param vendor The vendor id string of the extra package.
+ * @param path The path id string of the extra package.
+ * @param displayName The display name. If missing, caller should build one using the path.
+ * @param oldPaths An optional list of older paths for this extra package.
+ * @param revision The revision of the extra package.
+ * @return A {@link PkgDesc} describing this extra package.
+ */
+ @NonNull
+ public static Builder newExtra(@NonNull IdDisplay vendor,
+ @NonNull String path,
+ @Nullable String displayName,
+ @Nullable String[] oldPaths,
+ @NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_EXTRA);
+ p.mRevision = revision;
+ p.mVendor = vendor;
+ p.mPath = path;
+ p.mNameDisplay = displayName;
+ p.mOldPaths = oldPaths;
+ return p;
+ }
+
+ /**
+ * Creates a new platform package descriptor.
+ *
+ * @param version The android version of the platform package.
+ * @param revision The revision of the extra package.
+ * @param minToolsRev An optional {@code min-tools-rev}.
+ * Use {@link Revision#NOT_SPECIFIED} to indicate
+ * there is no requirement.
+ * @return A {@link PkgDesc} describing this platform package.
+ */
+ @NonNull
+ public static Builder newPlatform(@NonNull AndroidVersion version,
+ @NonNull Revision revision,
+ @NonNull Revision minToolsRev) {
+ Builder p = new Builder(PkgType.PKG_PLATFORM);
+ p.mAndroidVersion = version;
+ p.mRevision = revision;
+ p.mMinToolsRev = minToolsRev;
+ p.mCustomPath = new IGetPath() {
+ @Override
+ public String getPath(PkgDesc thisPkgDesc) {
+ /** The "path" of a Platform is its Target Hash. */
+ return AndroidTargetHash.getPlatformHashString(thisPkgDesc.getAndroidVersion());
+ }
+ };
+ return p;
+ }
+
+ /**
+ * Create a new add-on package descriptor.
+ * <p/>
+ * The vendor id and the name id provided are used to compute the add-on's
+ * target hash.
+ *
+ * @param version The android version of the add-on package.
+ * @param revision The revision of the add-on package.
+ * @param addonVendor The vendor id/display of the add-on package.
+ * @param addonName The name id/display of the add-on package.
+ * @return A {@link PkgDesc} describing this add-on package.
+ */
+ @NonNull
+ public static Builder newAddon(@NonNull AndroidVersion version,
+ @NonNull Revision revision,
+ @NonNull IdDisplay addonVendor,
+ @NonNull IdDisplay addonName) {
+ Builder p = new Builder(PkgType.PKG_ADDON);
+ p.mAndroidVersion = version;
+ p.mRevision = revision;
+ p.mVendor = addonVendor;
+ p.mNameIdDisplay = addonName;
+ p.mPath = AndroidTargetHash.getAddonHashString(addonVendor.getDisplay(),
+ addonName.getDisplay(),
+ version);
+ return p;
+ }
+
+ /**
+ * Create a new platform system-image package descriptor.
+ * <p/>
+ * For system-images, {@link PkgDesc#getPath()} returns the ABI.
+ *
+ * @param version The android version of the system-image package.
+ * @param tag The tag of the system-image package.
+ * @param abi The ABI of the system-image package.
+ * @param revision The revision of the system-image package.
+ * @return A {@link PkgDesc} describing this system-image package.
+ */
+ @NonNull
+ public static Builder newSysImg(@NonNull AndroidVersion version,
+ @NonNull IdDisplay tag,
+ @NonNull String abi,
+ @NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_SYS_IMAGE);
+ p.mAndroidVersion = version;
+ p.mRevision = revision;
+ p.mTag = tag;
+ p.mPath = abi;
+ p.mVendor = null;
+ return p;
+ }
+
+ /**
+ * Create a new add-on system-image package descriptor.
+ * <p/>
+ * For system-images, {@link PkgDesc#getPath()} returns the ABI.
+ *
+ * @param version The android version of the system-image package.
+ * @param addonVendor The vendor id/display of an associated add-on.
+ * @param addonName The tag of the system-image package is the add-on name.
+ * @param abi The ABI of the system-image package.
+ * @param revision The revision of the system-image package.
+ * @return A {@link PkgDesc} describing this system-image package.
+ */
+ @NonNull
+ public static Builder newAddonSysImg(@NonNull AndroidVersion version,
+ @NonNull IdDisplay addonVendor,
+ @NonNull IdDisplay addonName,
+ @NonNull String abi,
+ @NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_ADDON_SYS_IMAGE);
+ p.mAndroidVersion = version;
+ p.mRevision = revision;
+ p.mTag = addonName;
+ p.mPath = abi;
+ p.mVendor = addonVendor;
+ return p;
+ }
+
+ /**
+ * Create a new source package descriptor.
+ *
+ * @param version The android version of the source package.
+ * @param revision The revision of the source package.
+ * @return A {@link PkgDesc} describing this source package.
+ */
+ @NonNull
+ public static Builder newSource(@NonNull AndroidVersion version,
+ @NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_SOURCE);
+ p.mAndroidVersion = version;
+ p.mRevision = revision;
+ return p;
+ }
+
+ /**
+ * Create a new sample package descriptor.
+ *
+ * @param version The android version of the sample package.
+ * @param revision The revision of the sample package.
+ * @param minToolsRev An optional {@code min-tools-rev}.
+ * Use {@link Revision#NOT_SPECIFIED} to indicate
+ * there is no requirement.
+ * @return A {@link PkgDesc} describing this sample package.
+ */
+ @NonNull
+ public static Builder newSample(@NonNull AndroidVersion version,
+ @NonNull Revision revision,
+ @NonNull Revision minToolsRev) {
+ Builder p = new Builder(PkgType.PKG_SAMPLE);
+ p.mAndroidVersion = version;
+ p.mRevision = revision;
+ p.mMinToolsRev = minToolsRev;
+ return p;
+ }
+
+ /**
+ * Creates a new NDK package descriptor.
+ *
+ * @param revision The revision of the NDK package.
+ * @return A {@link PkgDesc} describing this NDK package.
+ */
+ @NonNull
+ public static Builder newNdk(@NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_NDK);
+ p.mRevision = revision;
+ return p;
+ }
+
+ /**
+ * Creates a new LLDB package descriptor.
+ *
+ * @param revision The revision of the LLDB package.
+ * @return A {@link PkgDesc} describing this LLDB package.
+ */
+ @NonNull
+ public static Builder newLLDB(@NonNull Revision revision) {
+ Builder p = new Builder(PkgType.PKG_LLDB);
+ p.mRevision = revision;
+ return p;
+ }
+
+ public Builder setLicense(@Nullable License license) {
+ mLicense = license;
+ return this;
+ }
+
+ public Builder setListDisplay(@Nullable String text) {
+ mListDisplay = text;
+ return this;
+ }
+
+ public Builder setDescriptionShort(@Nullable String text) {
+ mDescriptionShort = text;
+ return this;
+ }
+
+ public Builder setDescriptionUrl(@Nullable String text) {
+ mDescriptionUrl = text;
+ return this;
+ }
+
+ public Builder setIsObsolete(boolean isObsolete) {
+ mIsObsolete = isObsolete;
+ return this;
+ }
+
+ public IPkgDesc create() {
+
+ if (mType == PkgType.PKG_EXTRA) {
+ return new PkgDescExtra(
+ mType,
+ mLicense,
+ mListDisplay,
+ mDescriptionShort,
+ mDescriptionUrl,
+ mIsObsolete,
+ mRevision,
+ mAndroidVersion,
+ mPath,
+ mTag,
+ mVendor,
+ mMinToolsRev,
+ mMinPlatformToolsRev,
+ mNameDisplay,
+ mOldPaths);
+ }
+
+ return new PkgDesc(
+ mType,
+ mLicense,
+ mListDisplay,
+ mDescriptionShort,
+ mDescriptionUrl,
+ mIsObsolete,
+ mRevision,
+ mAndroidVersion,
+ mPath,
+ mTag,
+ mVendor,
+ mMinToolsRev,
+ mMinPlatformToolsRev,
+ mCustomIsUpdateFor,
+ mCustomPath,
+ mNameIdDisplay);
+ }
+ }
+
+ // ---- Helpers -----
+
+ @NonNull
+ private static String sanitize(@NonNull String str) {
+ str = str.toLowerCase(Locale.US).replaceAll("[^a-z0-9_.-]+", "_").replaceAll("_+", "_");
+ return str;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java
new file mode 100644
index 0000000..6c7e2ca
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgDescExtra.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.descriptors;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.License;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+/**
+ * Implementation detail of {@link IPkgDescExtra} for extra packages.
+ */
+public final class PkgDescExtra extends PkgDesc implements IPkgDescExtra {
+
+ private final String[] mOldPaths;
+
+ PkgDescExtra(@NonNull PkgType type,
+ @Nullable License license,
+ @Nullable String listDisplay,
+ @Nullable String descriptionShort,
+ @Nullable String descriptionUrl,
+ boolean isObsolete,
+ @Nullable Revision revision,
+ @Nullable AndroidVersion androidVersion,
+ @Nullable String path,
+ @Nullable IdDisplay tag,
+ @Nullable IdDisplay vendor,
+ @Nullable Revision minToolsRev,
+ @Nullable Revision minPlatformToolsRev,
+ @Nullable String nameDisplay,
+ @Nullable final String[] oldPaths) {
+ super(type,
+ license,
+ listDisplay,
+ descriptionShort,
+ descriptionUrl,
+ isObsolete,
+ revision,
+ androidVersion,
+ path,
+ tag,
+ vendor,
+ minToolsRev,
+ minPlatformToolsRev,
+ null, //customIsUpdateFor
+ null,
+ IdDisplay.create(nameDisplay == null ? "" : nameDisplay,
+ nameDisplay == null ? "" : nameDisplay));
+ mOldPaths = oldPaths != null ? oldPaths : new String[0];
+ }
+
+ @NonNull
+ @Override
+ public String[] getOldPaths() {
+ return mOldPaths;
+ }
+
+ @NonNull
+ @Override
+ public String getNameDisplay() {
+ return getName() == null || getName().getDisplay() == null ? String
+ .format("Unknown (%s)", getInstallId()) : getName().getDisplay();
+ }
+
+ // ---- Helpers ----
+
+ /**
+ * Helper method that converts the old_paths property string into the
+ * an old paths array.
+ *
+ * @param oldPathsProperty A possibly-null old_path property string.
+ * @return A list of old paths split by their separator. Can be empty but not null.
+ */
+ @NonNull
+ public static String[] convertOldPaths(@Nullable String oldPathsProperty) {
+ if (oldPathsProperty == null || oldPathsProperty.isEmpty()) {
+ return new String[0];
+ }
+ return oldPathsProperty.split(";"); //$NON-NLS-1$
+ }
+
+ /**
+ * Helper to computhe whether the extra path of both {@link IPkgDescExtra}s
+ * are compatible with each other, which means they are either equal or are
+ * matched between existing path and the potential old paths list.
+ * <p/>
+ * This also covers backward compatibility -- in earlier schemas the vendor id was
+ * merged into the path string when reloading installed extras.
+ *
+ * @param lhs A non-null {@link IPkgDescExtra}.
+ * @param rhs Another non-null {@link IPkgDescExtra}.
+ * @return true if the paths are compatible.
+ */
+ public static boolean compatibleVendorAndPath(
+ @NonNull IPkgDescExtra lhs,
+ @NonNull IPkgDescExtra rhs) {
+ String[] epOldPaths = rhs.getOldPaths();
+ int lenEpOldPaths = epOldPaths.length;
+ for (int indexEp = -1; indexEp < lenEpOldPaths; indexEp++) {
+ if (sameVendorAndPath(
+ lhs.getVendor().getId(), lhs.getPath(),
+ rhs.getVendor().getId(), indexEp < 0 ? rhs.getPath() : epOldPaths[indexEp])) {
+ return true;
+ }
+ }
+
+ String[] thisOldPaths = lhs.getOldPaths();
+ int lenThisOldPaths = thisOldPaths.length;
+ for (int indexThis = -1; indexThis < lenThisOldPaths; indexThis++) {
+ if (sameVendorAndPath(
+ lhs.getVendor().getId(), indexThis < 0 ? lhs.getPath() : thisOldPaths[indexThis],
+ rhs.getVendor().getId(), rhs.getPath())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean sameVendorAndPath(
+ @Nullable String thisVendor, @Nullable String thisPath,
+ @Nullable String otherVendor, @Nullable String otherPath) {
+ // To be backward compatible, we need to support the old vendor-path form
+ // in either the current or the remote package.
+ //
+ // The vendor test below needs to account for an old installed package
+ // (e.g. with an install path of vendor-name) that has then been updated
+ // in-place and thus when reloaded contains the vendor name in both the
+ // path and the vendor attributes.
+ if (otherPath != null && thisPath != null && thisVendor != null) {
+ if (otherPath.equals(thisVendor + '-' + thisPath) &&
+ (otherVendor == null ||
+ otherVendor.isEmpty() ||
+ otherVendor.equals(thisVendor))) {
+ return true;
+ }
+ }
+ if (thisPath != null && otherPath != null && otherVendor != null) {
+ if (thisPath.equals(otherVendor + '-' + otherPath) &&
+ (thisVendor == null ||
+ thisVendor.isEmpty() ||
+ thisVendor.equals(otherVendor))) {
+ return true;
+ }
+ }
+
+
+ if (thisPath != null && thisPath.equals(otherPath)) {
+ if ((thisVendor == null && otherVendor == null) ||
+ (thisVendor != null && thisVendor.equals(otherVendor))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java
new file mode 100644
index 0000000..1e85e65
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/descriptors/PkgType.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.descriptors;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.sdklib.AndroidVersion;
+
+import java.util.EnumSet;
+
+/**
+ * Package types handled by the legacy SDK.
+ * <p/>
+ * Integer bit values indicate the natural ordering of the packages.
+ */
+public enum PkgType {
+
+ // Boolean attributes below, in that order:
+ // api, path, tag, vend, min-t-r, min-pt-r, name
+ //
+ // Corresponding flags for list description pattern string:
+ // $MAJ $FULL $API $PATH $TAG $VEND $NAME (for extras & add-ons)
+ //
+ //
+
+ /** Filter the SDK/tools folder.
+ * Has {@link Revision}. */
+ PKG_TOOLS(0x0001, SdkConstants.FD_TOOLS,
+ "Android SDK Tools $FULL",
+ false, false, false, false, false, true, false),
+
+ /** Filter the SDK/platform-tools folder.
+ * Has {@link Revision}. */
+ PKG_PLATFORM_TOOLS(0x0002, SdkConstants.FD_PLATFORM_TOOLS,
+ "Android SDK Platform-Tools $FULL",
+ false, false, false, false, false, false, false),
+
+ /** Filter the SDK/build-tools folder.
+ * Has {@link Revision}. */
+ PKG_BUILD_TOOLS(0x0004, SdkConstants.FD_BUILD_TOOLS,
+ "Android SDK Build-Tools $FULL",
+ false, false, false, false, false, false, false),
+
+ /** Filter the SDK/docs folder.
+ * Has {@link Revision}. */
+ PKG_DOC(0x0010, SdkConstants.FD_DOCS,
+ "Documentation for Android SDK",
+ true, false, false, false, false, false, false),
+
+ /** Filter the SDK/platforms.
+ * Has {@link AndroidVersion}. Has {@link Revision}.
+ * Path returns the platform's target hash. */
+ PKG_PLATFORM(0x0100, SdkConstants.FD_PLATFORMS,
+ "Android SDK Platform $API{?$MAJ>1:, rev $MAJ}",
+ true, true, false, false, true, false, false),
+
+ /** Filter the SDK/system-images/android.
+ * Has {@link AndroidVersion}. Has {@link Revision}. Has tag.
+ * Path returns the system image ABI. */
+ PKG_SYS_IMAGE(0x0200, SdkConstants.FD_SYSTEM_IMAGES,
+ "$PATH System Image, Android $API{?$MAJ>1:, rev $MAJ}",
+ true, true, true, false, false, false, false),
+
+ /** Filter the SDK/addons.
+ * Has {@link AndroidVersion}. Has {@link Revision}.
+ * Path returns the add-on's target hash. */
+ PKG_ADDON(0x0400, SdkConstants.FD_ADDONS,
+ "{|$NAME|$VEND $PATH|}, Android $API{?$MAJ>1:, rev $MAJ}",
+ true, true, false, true, false, false, true),
+
+ /** Filter the SDK/system-images/addons.
+ * Has {@link AndroidVersion}. Has {@link Revision}. Has tag.
+ * Path returns the system image ABI. */
+ PKG_ADDON_SYS_IMAGE(0x0800, SdkConstants.FD_SYSTEM_IMAGES,
+ "{|$NAME|$VEND $PATH|} System Image, Android $API{?$MAJ>1:, rev $MAJ}",
+ true, true, true, true, false, false, false),
+
+ /** Filter the SDK/samples folder.
+ * Note: this will not detect samples located in the SDK/extras packages.
+ * Has {@link AndroidVersion}. Has {@link Revision}. */
+ PKG_SAMPLE(0x1000, SdkConstants.FD_SAMPLES,
+ "Samples for Android $API{?$MAJ>1:, rev $MAJ}",
+ true, false, false, false, true, false, false),
+
+ /** Filter the SDK/sources folder.
+ * Has {@link AndroidVersion}. Has {@link Revision}. */
+ PKG_SOURCE(0x2000, SdkConstants.FD_ANDROID_SOURCES,
+ "Sources for Android $API{?$MAJ>1:, rev $MAJ}",
+ true, false, false, false, false, false, false),
+
+ /** Filter the SDK/extras folder.
+ * Has {@code Path}. Has {@link Revision}.
+ * Path returns the combined vendor id + extra path.
+ * Cast the descriptor to {@link IPkgDescExtra} to get extra's specific attributes. */
+ PKG_EXTRA(0x4000, SdkConstants.FD_EXTRAS,
+ "{|$NAME|$VEND $PATH|}{?$FULL>1:, rev $FULL}",
+ false, true, false, true, false, false, true),
+
+ /** The SDK/ndk folder. */
+ PKG_NDK(0x8000, SdkConstants.FD_NDK, "",
+ false, true, false, false, false, false, false),
+
+ /** The SDK/lldb folder. */
+ PKG_LLDB(0xA000, SdkConstants.FD_LLDB, "",
+ false, true, false, false, false, false, false);
+
+ /** A collection of all the known PkgTypes. */
+ public static final EnumSet<PkgType> PKG_ALL = EnumSet.allOf(PkgType.class);
+
+ /** Integer value matching all available pkg types, for the old LocalSdkParer. */
+ public static final int PKG_ALL_INT = 0xFFFF;
+
+ private int mIntValue;
+ private String mFolderName;
+
+ private final boolean mHasAndroidVersion;
+ private final boolean mHasPath;
+ private final boolean mHasTag;
+ private final boolean mHasVendor;
+ private final boolean mHasMinToolsRev;
+ private final boolean mHasMinPlatformToolsRev;
+ private final String mListDisplayPattern;
+ private final boolean mHasName;
+
+ PkgType(int intValue,
+ @NonNull String folderName,
+ @NonNull String listDisplayPattern,
+ boolean hasAndroidVersion,
+ boolean hasPath,
+ boolean hasTag,
+ boolean hasVendor,
+ boolean hasMinToolsRev,
+ boolean hasMinPlatformToolsRev,
+ boolean hasName) {
+ mIntValue = intValue;
+ mFolderName = folderName;
+ mListDisplayPattern = listDisplayPattern;
+ mHasAndroidVersion = hasAndroidVersion;
+ mHasPath = hasPath;
+ mHasTag = hasTag;
+ mHasVendor = hasVendor;
+ mHasMinToolsRev = hasMinToolsRev;
+ mHasMinPlatformToolsRev = hasMinPlatformToolsRev;
+ mHasName = hasName;
+ }
+
+ /** Returns the integer value matching the type, compatible with the old LocalSdkParer. */
+ public int getIntValue() {
+ return mIntValue;
+ }
+
+ /** Returns the name of SDK top-folder where this type of package is stored. */
+ @NonNull
+ public String getFolderName() {
+ return mFolderName;
+ }
+
+ public boolean hasAndroidVersion() {
+ return mHasAndroidVersion;
+ }
+
+ public boolean hasPath() {
+ return mHasPath;
+ }
+
+ public boolean hasTag() {
+ return mHasTag;
+ }
+
+ public boolean hasVendor() {
+ return mHasVendor;
+ }
+
+ public boolean hasName() {
+ return mHasName;
+ }
+
+ public boolean hasMinToolsRev() {
+ return mHasMinToolsRev;
+ }
+
+ public boolean hasMinPlatformToolsRev() {
+ return mHasMinPlatformToolsRev;
+ }
+
+ /*
+ * Returns a pattern string used by {@link PkgDesc#getListDescription()} to
+ * compute a default list-display representation string for this package.
+ */
+ public String getListDisplayPattern() {
+ return mListDisplayPattern;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java
new file mode 100644
index 0000000..cd15783
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonPkgInfo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.Properties;
+
+ at SuppressWarnings("MethodMayBeStatic")
+public class LocalAddonPkgInfo extends LocalPlatformPkgInfo {
+
+ @NonNull
+ private final IPkgDesc mAddonDesc;
+
+ public LocalAddonPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull Revision revision,
+ @NonNull IdDisplay vendor,
+ @NonNull IdDisplay name) {
+ super(localSdk, localDir, sourceProps, version, revision, Revision.NOT_SPECIFIED);
+ mAddonDesc = PkgDesc.Builder.newAddon(version, revision, vendor, name).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mAddonDesc;
+ }
+
+ /**
+ * The "path" of an add-on is its Target Hash.
+ */
+ @Override
+ @NonNull
+ public String getTargetHash() {
+ return getDesc().getPath();
+ }
+
+ //-----
+
+ /**
+ * Computes a sanitized name-id based on an addon name-display. This is used to provide
+ * compatibility with older add-ons that lacks the new fields.
+ *
+ * @param displayName A name-display field or a old-style name field.
+ * @return A non-null sanitized name-id that fits in the {@code [a-zA-Z0-9_-]+} pattern.
+ */
+ public static String sanitizeDisplayToNameId(@NonNull String displayName) {
+ String name = displayName.toLowerCase(Locale.US);
+ name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // Trim leading and trailing underscores
+ if (name.length() > 1) {
+ name = name.replaceAll("^_+", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (name.length() > 1) {
+ name = name.replaceAll("_+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return name;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java
new file mode 100644
index 0000000..4be3344
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalAddonSysImgPkgInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.targets.SystemImage;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local add-on system-image package, for a given addon's {@link AndroidVersion} and given ABI.
+ * The system-image tag is the add-on name.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version & ABI.
+ */
+public class LocalAddonSysImgPkgInfo extends LocalPkgInfo {
+
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalAddonSysImgPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @Nullable IdDisplay addonVendor,
+ @Nullable IdDisplay addonName,
+ @NonNull String abi,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ String id = sourceProps.getProperty(PkgProps.SYS_IMG_TAG_ID);
+ IdDisplay tag;
+ if (id == null) {
+ tag = SystemImage.DEFAULT_TAG;
+ }
+ else {
+ String display = sourceProps.getProperty(PkgProps.SYS_IMG_TAG_DISPLAY);
+ tag = IdDisplay.create(id, display == null ? id : display);
+ }
+ String listDisplay = sourceProps.getProperty(PkgProps.PKG_LIST_DISPLAY);
+ if (listDisplay == null) {
+ listDisplay = "";
+ }
+ mDesc = PkgDesc.Builder.newAddonSysImg(version, addonVendor, addonName, abi, revision)
+ .setDescriptionShort(LocalSysImgPkgInfo
+ .createShortDescription(listDisplay, abi,
+ addonVendor,
+ tag, version, revision,
+ sourceProps.containsKey(PkgProps.PKG_OBSOLETE)))
+ .setListDisplay(LocalSysImgPkgInfo
+ .createListDescription(listDisplay, tag,
+ LocalSysImgPkgInfo.getAbiDisplayNameInternal(abi),
+ sourceProps.containsKey(PkgProps.PKG_OBSOLETE))).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java
new file mode 100644
index 0000000..fef7266
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalBuildToolPkgInfo.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalBuildToolPkgInfo extends LocalPkgInfo {
+
+
+ @Nullable
+ private final BuildToolInfo mBuildToolInfo;
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalBuildToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull Revision revision,
+ @Nullable BuildToolInfo btInfo) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newBuildTool(revision).create();
+ mBuildToolInfo = btInfo;
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @Nullable
+ public BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java
new file mode 100644
index 0000000..f6e1302
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDirInfo.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.repository.io.FileOp;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.zip.Adler32;
+
+/**
+ * Keeps information on a visited directory to quickly determine if it
+ * has changed later. A directory has changed if its timestamp has been
+ * modified, or if an underlying source.properties file has changed in
+ * timestamp or checksum.
+ * <p/>
+ * Note that depending on the filesystem & OS, the content of the files in
+ * a directory can change without the directory's last-modified property
+ * changing. To have a consistent behavior between OSes, we compute a quick
+ * checksum using all the files & directories modified timestamps.
+ * The content of files is not included though, except for the checksum on
+ * the source.property file since this one is the most important for the SDK.
+ * <p/>
+ * The {@link #hashCode()} and {@link #equals(Object)} methods directly
+ * defer to the underlying File object. This allows the DirInfo to be placed
+ * into a map and still call {@link Map#containsKey(Object)} with a File
+ * object to check whether there's a corresponding DirInfo in the map.
+ */
+class LocalDirInfo {
+ @NonNull
+ private final FileOp mFileOp;
+ @NonNull
+ private final File mDir;
+ private final long mDirModifiedTS;
+ private final long mDirChecksum;
+ private final long mPropsModifiedTS;
+ private final long mPropsChecksum;
+
+ /**
+ * Creates a new immutable {@link LocalDirInfo}.
+ *
+ * @param fileOp The {@link FileOp} to use for all file-based interactions.
+ * @param dir The platform/addon directory of the target. It should be a directory.
+ */
+ public LocalDirInfo(@NonNull FileOp fileOp, @NonNull File dir) {
+ mFileOp = fileOp;
+ mDir = dir;
+ mDirModifiedTS = mFileOp.lastModified(dir);
+
+ // Capture some info about the source.properties file if it exists.
+ // We use propsModifiedTS == 0 to mean there is no props file.
+ long propsChecksum = 0;
+ long propsModifiedTS = 0;
+ File props = new File(dir, SdkConstants.FN_SOURCE_PROP);
+ if (mFileOp.isFile(props)) {
+ propsModifiedTS = mFileOp.lastModified(props);
+ propsChecksum = getFileChecksum(props);
+ }
+ mPropsModifiedTS = propsModifiedTS;
+ mPropsChecksum = propsChecksum;
+ mDirChecksum = getDirChecksum(mDir);
+ }
+
+ /**
+ * Checks whether the directory/source.properties attributes have changed.
+ *
+ * @return True if the directory modified timestamp or
+ * its source.property files have changed.
+ */
+ public boolean hasChanged() {
+ // Does platform directory still exist?
+ if (!mFileOp.isDirectory(mDir)) {
+ return true;
+ }
+ // Has platform directory modified-timestamp changed?
+ if (mDirModifiedTS != mFileOp.lastModified(mDir)) {
+ return true;
+ }
+
+ File props = new File(mDir, SdkConstants.FN_SOURCE_PROP);
+
+ // The directory did not have a props file if target was null or
+ // if mPropsModifiedTS is 0.
+ boolean hadProps = mPropsModifiedTS != 0;
+
+ // Was there a props file and it vanished, or there wasn't and there's one now?
+ if (hadProps != mFileOp.isFile(props)) {
+ return true;
+ }
+
+ if (hadProps) {
+ // Has source.props file modified-timestamp changed?
+ if (mPropsModifiedTS != mFileOp.lastModified(props)) {
+ return true;
+ }
+ // Had the content of source.props changed?
+ if (mPropsChecksum != getFileChecksum(props)) {
+ return true;
+ }
+ }
+
+ // Has the deep directory checksum changed?
+ if (mDirChecksum != getDirChecksum(mDir)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes an adler32 checksum (source.props are small files, so this
+ * should be OK with an acceptable collision rate.)
+ */
+ private long getFileChecksum(@NonNull File file) {
+ InputStream fis = null;
+ try {
+ fis = mFileOp.newFileInputStream(file);
+ Adler32 a = new Adler32();
+ byte[] buf = new byte[1024];
+ int n;
+ while ((n = fis.read(buf)) > 0) {
+ a.update(buf, 0, n);
+ }
+ return a.getValue();
+ } catch (Exception ignore) {
+ } finally {
+ try {
+ if (fis != null) {
+ fis.close();
+ }
+ } catch(Exception ignore) {}
+ }
+ return 0;
+ }
+
+ /**
+ * Computes a checksum using the last-modified attributes of all
+ * the files and <em>first-level</em>directories in this root directory.
+ * <p/>
+ * Heuristic: the SDK Manager updates package by replacing whole directories
+ * so we don't need to do a recursive deep-first checksum of all files. Only
+ * the top-level of the package directory should be sufficient to detect
+ * SDK updates.
+ */
+ private long getDirChecksum(@NonNull File dir) {
+ long checksum = mFileOp.lastModified(dir);
+
+ // Get the file & directory list sorted by case-insensitive name
+ // to make the checksum more consistent.
+ File[] files = mFileOp.listFiles(dir);
+ Arrays.sort(files, new Comparator<File>() {
+ @Override
+ public int compare(File o1, File o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ });
+ for (File file : files) {
+ checksum = 31 * checksum | mFileOp.lastModified(file);
+ }
+ return checksum;
+ }
+
+ /** Returns a visual representation of this object for debugging. */
+ @Override
+ public String toString() {
+ String s = String.format("<DirInfo %1$s TS=%2$d", mDir, mDirModifiedTS); //$NON-NLS-1$
+ if (mPropsModifiedTS != 0) {
+ s += String.format(" | Props TS=%1$d, Chksum=%2$s", //$NON-NLS-1$
+ mPropsModifiedTS, mPropsChecksum);
+ }
+ return s + ">"; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the hashCode of the underlying File object.
+ * <p/>
+ * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
+ * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
+ * return the properties of the underlying File object.
+ *
+ * @see File#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return mDir.hashCode();
+ }
+
+ /**
+ * Checks equality of the underlying File object.
+ * <p/>
+ * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
+ * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
+ * return the properties of the underlying File object.
+ *
+ * @see File#equals(Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof File) {
+ return mDir.equals(obj);
+ } else if (obj instanceof LocalDirInfo) {
+ return mDir.equals(((LocalDirInfo) obj).mDir);
+ } else if (obj instanceof MapComparator) {
+ return mDir.equals(((MapComparator) obj).mDir);
+ }
+ return false;
+ }
+
+ /**
+ * Helper for Map.contains() to make sure we're comparing the inner directory File
+ * object and not the outer wrapper itself.
+ */
+ public static class MapComparator {
+ private final File mDir;
+
+ public MapComparator(File dir) {
+ mDir = dir;
+ }
+
+ /**
+ * Returns the hashCode of the underlying File object.
+ * <p/>
+ * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
+ * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
+ * return the properties of the underlying File object.
+ *
+ * @see File#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return mDir.hashCode();
+ }
+
+ /**
+ * Checks equality of the underlying File object.
+ * <p/>
+ * When a {@link LocalDirInfo} is placed in a map, what matters is to use the underlying
+ * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
+ * return the properties of the underlying File object.
+ *
+ * @see File#equals(Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof File) {
+ return mDir.equals(obj);
+ } else if (obj instanceof LocalDirInfo) {
+ return mDir.equals(((LocalDirInfo) obj).mDir);
+ } else if (obj instanceof MapComparator) {
+ return mDir.equals(((MapComparator) obj).mDir);
+ }
+ return false;
+ }
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java
new file mode 100644
index 0000000..d650cd1
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalDocPkgInfo.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalDocPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalDocPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newDoc(version, revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.java
new file mode 100644
index 0000000..31ce4fe
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalExtraPkgInfo.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.IPkgDescExtra;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalExtraPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final IPkgDescExtra mDesc;
+
+ public LocalExtraPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull IdDisplay vendor,
+ @NonNull String path,
+ @Nullable String displayName,
+ @NonNull String[] oldPaths,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = (IPkgDescExtra) PkgDesc.Builder.newExtra(
+ vendor,
+ path,
+ displayName,
+ oldPaths,
+ revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ @NonNull
+ public String[] getOldPaths() {
+ return mDesc.getOldPaths();
+ }
+
+ // --- helpers ---
+
+ /**
+ * Used to produce a suitable name-display based on the extra's path
+ * and vendor display string in addon-3 schemas.
+ *
+ * @param vendor The vendor id of the extra.
+ * @param extraPath The non-null path of the extra.
+ * @return A non-null display name based on the extra's path id.
+ */
+ public static String getPrettyName(@Nullable IdDisplay vendor, @NonNull String extraPath) {
+ String name = extraPath;
+
+ // In the past, we used to save the extras in a folder vendor-path,
+ // and that "vendor" would end up in the path when we reload the extra from
+ // disk. Detect this and compensate.
+ String disp = vendor == null ? null : vendor.getDisplay();
+ if (disp != null && !disp.isEmpty()) {
+ if (name.startsWith(disp + "-")) { //$NON-NLS-1$
+ name = name.substring(disp.length() + 1);
+ }
+ }
+
+ // Uniformize all spaces in the name
+ if (name != null) {
+ name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (name == null || name.isEmpty()) {
+ name = "Unknown Extra";
+ }
+
+ if (disp != null && !disp.isEmpty()) {
+ name = disp + " " + name; //$NON-NLS-1$
+ name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // Look at all lower case characters in range [1..n-1] and replace them by an upper
+ // case if they are preceded by a space. Also upper cases the first character of the
+ // string.
+ boolean changed = false;
+ char[] chars = name.toCharArray();
+ for (int n = chars.length - 1, i = 0; i < n; i++) {
+ if (Character.isLowerCase(chars[i]) && (i == 0 || chars[i - 1] == ' ')) {
+ chars[i] = Character.toUpperCase(chars[i]);
+ changed = true;
+ }
+ }
+ if (changed) {
+ name = new String(chars);
+ }
+
+ // Special case: reformat a few typical acronyms.
+ name = name.replaceAll(" Usb ", " USB "); //$NON-NLS-1$
+ name = name.replaceAll(" Api ", " API "); //$NON-NLS-1$
+
+ return name;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalLLDBPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalLLDBPkgInfo.java
new file mode 100644
index 0000000..f011171
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalLLDBPkgInfo.java
@@ -0,0 +1,59 @@
+/*
+ * 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.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.repository.Revision;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local package representing the Android LLDB.
+ *
+ * @deprecated in favor of new sdk system
+ */
+ at Deprecated
+public class LocalLLDBPkgInfo extends LocalPkgInfo {
+ /**
+ * The LLDB SDK package revision's major and minor numbers are pinned in Android Studio.
+ *
+ * @deprecated in favor of LLDBSdkPackageInstaller#PINNED_REVISION.
+ */
+
+ @Deprecated
+ public static final Revision PINNED_REVISION = new Revision(2, 0);
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ protected LocalLLDBPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newLLDB(revision).setDescriptionShort("LLDB").setListDisplay("LLDB").create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalNdkPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalNdkPkgInfo.java
new file mode 100644
index 0000000..913eb29
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalNdkPkgInfo.java
@@ -0,0 +1,49 @@
+/*
+ * 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.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local package representing the Android NDK
+ */
+public class LocalNdkPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ protected LocalNdkPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newNdk(revision).setDescriptionShort("Android NDK")
+ .setListDisplay("Android NDK").create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java
new file mode 100644
index 0000000..f83323e
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPkgInfo.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Information about a locally installed package.
+ * <p/>
+ * Local package information is retrieved via the {@link LocalSdk} object.
+ * Clients should not need to create instances of {@link LocalPkgInfo} directly.
+ * Instead please use the {@link LocalSdk} methods to parse and retrieve packages.
+ * <p/>
+ */
+public abstract class LocalPkgInfo
+ implements Comparable<LocalPkgInfo> {
+
+ private final LocalSdk mLocalSdk;
+ private final File mLocalDir;
+ private final Properties mSourceProperties;
+
+ private String mLoadError;
+
+ protected LocalPkgInfo(@NonNull LocalSdk localSdk, @NonNull File localDir, @NonNull Properties sourceProps) {
+ mLocalSdk = localSdk;
+ mLocalDir = localDir;
+ mSourceProperties = sourceProps;
+ }
+
+ //---- Attributes ----
+
+ @NonNull
+ public LocalSdk getLocalSdk() {
+ return mLocalSdk;
+ }
+
+ @NonNull
+ public File getLocalDir() {
+ return mLocalDir;
+ }
+
+ @NonNull
+ public Properties getSourceProperties() {
+ return mSourceProperties;
+ }
+
+ @Nullable
+ public String getLoadError() {
+ return mLoadError;
+ }
+
+ // ----
+
+ /**
+ * Returns the {@link IPkgDesc} describing this package.
+ */
+ @NonNull
+ public abstract IPkgDesc getDesc();
+
+
+ //---- Ordering ----
+
+ /**
+ * Comparison is solely done based on the {@link IPkgDesc}.
+ * <p/>
+ * Other local attributes (local directory, source properties)
+ * are <em>not used</em> in the comparison. Consequently {@link #compareTo(LocalPkgInfo)}
+ * does not match {@link #equals(Object)} and the {@link #hashCode()} properties.
+ */
+ @Override
+ public int compareTo(@NonNull LocalPkgInfo o) {
+ return getDesc().compareTo(o.getDesc());
+ }
+
+ /**
+ * String representation for debugging purposes.
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append('<').append(this.getClass().getSimpleName()).append(' ');
+ builder.append(getDesc().toString());
+ builder.append('>');
+ return builder.toString();
+ }
+
+ /**
+ * Computes a hash code specific to this instance based on the underlying
+ * {@link IPkgDesc} but also specific local properties such a local directory,
+ * and actual source properties.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((getDesc() == null) ? 0 : getDesc().hashCode());
+ result = prime * result + ((mLocalDir == null) ? 0 : mLocalDir.hashCode());
+ result = prime * result + ((mSourceProperties == null) ? 0 : mSourceProperties.hashCode());
+ return result;
+ }
+
+ /**
+ * Computes object equality to this instance based on the underlying
+ * {@link IPkgDesc} but also specific local properties such a local directory,
+ * update available and actual source properties. This is different from
+ * the behavior of {@link #compareTo(LocalPkgInfo)} which only uses the
+ * {@link IPkgDesc} for ordering.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof LocalPkgInfo)) {
+ return false;
+ }
+ LocalPkgInfo other = (LocalPkgInfo)obj;
+
+ if (!getDesc().equals(other.getDesc())) {
+ return false;
+ }
+ if (mLocalDir == null) {
+ if (other.mLocalDir != null) {
+ return false;
+ }
+ }
+ else if (!mLocalDir.equals(other.mLocalDir)) {
+ return false;
+ }
+ if (mSourceProperties == null) {
+ if (other.mSourceProperties != null) {
+ return false;
+ }
+ }
+ else if (!mSourceProperties.equals(other.mSourceProperties)) {
+ return false;
+ }
+ return true;
+ }
+
+
+ //---- Package Management ----
+
+ void appendLoadError(@NonNull String format, Object... params) {
+ String loadError = String.format(format, params);
+ if (mLoadError == null) {
+ mLoadError = loadError;
+ }
+ else {
+ mLoadError = mLoadError + '\n' + loadError;
+ }
+ }
+
+ @NonNull
+ public String getListDescription() {
+ return getDesc().getListDescription();
+ }
+
+ /**
+ * Deletes the files in the SDK corresponding to this package.
+ */
+ public void delete() {
+ FileOpUtils.create().deleteFileOrFolder(getLocalDir());
+ }
+
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java
new file mode 100644
index 0000000..8de6748
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformPkgInfo.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+ at SuppressWarnings("ConstantConditions")
+public class LocalPlatformPkgInfo extends LocalPkgInfo {
+
+ public static final String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$
+
+ public static final String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$
+
+ public static final String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+
+ private static final int LAYOUTLIB_VERSION_NOT_SPECIFIED = 0;
+ private Map<String, String> myPlatformProp;
+
+ public LocalPlatformPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull Revision revision,
+ @NonNull Revision minToolsRev) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newPlatform(version, revision, minToolsRev).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ /**
+ * The "path" of a Platform is its Target Hash.
+ */
+ @NonNull
+ public String getTargetHash() {
+ return getDesc().getPath();
+ }
+
+ //-----
+
+ public int getLayoutlibApi() {
+ Map<String, String> platformProp = getPlatformProps();
+ int layoutlibApi = 1;
+
+ if (platformProp == null) {
+ return layoutlibApi;
+ }
+ try {
+ String propApi = platformProp.get(PkgProps.LAYOUTLIB_API);
+ if (propApi == null) {
+ // In old packages it was sometimes specified differently
+ propApi = platformProp.get("sdk." + PkgProps.LAYOUTLIB_API.toLowerCase());
+ }
+ String propRev = platformProp.get(PkgProps.LAYOUTLIB_REV);
+ if (propRev == null) {
+ // In old packages it was sometimes specified differently
+ propRev = platformProp.get("sdk." + PkgProps.LAYOUTLIB_REV.toLowerCase());
+ }
+ int llApi = propApi == null ? LAYOUTLIB_VERSION_NOT_SPECIFIED :
+ Integer.parseInt(propApi);
+ int llRev = propRev == null ? LAYOUTLIB_VERSION_NOT_SPECIFIED :
+ Integer.parseInt(propRev);
+ if (llApi > LAYOUTLIB_VERSION_NOT_SPECIFIED ||
+ llRev >= LAYOUTLIB_VERSION_NOT_SPECIFIED) {
+ layoutlibApi = llApi;
+ }
+ } catch (NumberFormatException e) {
+ // do nothing, we'll ignore the layoutlib version if it's invalid
+ }
+ return layoutlibApi;
+ }
+
+ private Map<String, String> getPlatformProps() {
+ if (myPlatformProp == null) {
+ LocalSdk sdk = getLocalSdk();
+ FileOp fileOp = sdk.getFileOp();
+ File platformFolder = getLocalDir();
+ File buildProp = new File(platformFolder, SdkConstants.FN_BUILD_PROP);
+ File sourcePropFile = new File(platformFolder, SdkConstants.FN_SOURCE_PROP);
+
+ if (!fileOp.isFile(buildProp) || !fileOp.isFile(sourcePropFile)) {
+ appendLoadError("Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
+ platformFolder.getName(), SdkConstants.FN_BUILD_PROP);
+ return null;
+ }
+
+ Map<String, String> result = new HashMap<String, String>();
+
+ // add all the property files
+ Map<String, String> map = null;
+
+ try {
+ map = ProjectProperties.parsePropertyStream(fileOp.newFileInputStream(buildProp), buildProp.getPath(), null /*log*/);
+ if (map != null) {
+ result.putAll(map);
+ }
+ }
+ catch (FileNotFoundException ignore) {
+ }
+
+ try {
+ map =
+ ProjectProperties.parsePropertyStream(fileOp.newFileInputStream(sourcePropFile), sourcePropFile.getPath(), null /*log*/);
+ if (map != null) {
+ result.putAll(map);
+ }
+ }
+ catch (FileNotFoundException ignore) {
+ }
+
+ File sdkPropFile = new File(platformFolder, SdkConstants.FN_SDK_PROP);
+ if (fileOp.isFile(sdkPropFile)) { // obsolete platforms don't have this.
+ try {
+ map = ProjectProperties.parsePropertyStream(fileOp.newFileInputStream(sdkPropFile), sdkPropFile.getPath(), null /*log*/);
+ if (map != null) {
+ result.putAll(map);
+ }
+ }
+ catch (FileNotFoundException ignore) {
+ }
+ }
+ myPlatformProp = result;
+ }
+ return myPlatformProp;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.java
new file mode 100644
index 0000000..db124af
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalPlatformToolPkgInfo.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalPlatformToolPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalPlatformToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newPlatformTool(revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java
new file mode 100644
index 0000000..e80142c
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSamplePkgInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local sample package, for a given platform's {@link AndroidVersion}.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version.
+ */
+public class LocalSamplePkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalSamplePkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull Revision revision,
+ @NonNull Revision minToolsRev) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newSample(version, revision, minToolsRev).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java
new file mode 100644
index 0000000..dc1304b
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSdk.java
@@ -0,0 +1,1186 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.annotations.concurrency.GuardedBy;
+import com.android.repository.Revision;
+import com.android.repository.api.RepoManager;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.AndroidVersionHelper;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDescExtra;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.TreeMultimap;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * This class keeps information on the current locally installed SDK.
+ * It tries to lazily load information as much as possible.
+ * <p/>
+ * Packages are accessed by their type and a main query attribute, depending on the
+ * package type. There are different versions of {@link #getPkgInfo} which depend on the
+ * query attribute.
+ *
+ * <table border='1' cellpadding='3'>
+ * <tr>
+ * <th>Type</th>
+ * <th>Query parameter</th>
+ * <th>Getter</th>
+ * </tr>
+ *
+ * <tr>
+ * <td>Tools</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_TOOLS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Platform-Tools</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_PLATFORM_TOOLS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Docs</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_DOCS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Build-Tools</td>
+ * <td>{@link Revision}</td>
+ * <td>{@code getLatestBuildTool()} => {@link BuildToolInfo}, <br/>
+ * or {@code getBuildTool(Revision)} => {@link BuildToolInfo}, <br/>
+ * or {@code getPkgInfo(PkgType.PKG_BUILD_TOOLS, Revision)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_BUILD_TOOLS)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Extras</td>
+ * <td>String vendor/path</td>
+ * <td>{@code getExtra(String)} => {@link LocalExtraPkgInfo}, <br/>
+ * or {@code getPkgInfo(PkgType.PKG_EXTRAS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_EXTRAS)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Sources</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_SOURCES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_SOURCES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Samples</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_SAMPLES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_SAMPLES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Platforms</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_PLATFORMS, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgInfo(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_PLATFORMS)} => {@link LocalPkgInfo}[], <br/>
+ * or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Add-ons</td>
+ * <td>{@link AndroidVersion} x String vendor/path</td>
+ * <td>{@code getPkgInfo(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PkgType.PKG_ADDONS)} => {@link LocalPkgInfo}[], <br/>
+ * or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>System images</td>
+ * <td>{@link AndroidVersion} x {@link String} ABI</td>
+ * <td>{@code getPkgsInfos(PkgType.PKG_SYS_IMAGES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * </table>
+ *
+ * Apps/libraries that use it are encouraged to keep an existing instance around
+ * (using a singleton or similar mechanism).
+ * <p/>
+ * Threading: All accessor methods are synchronized on the same internal lock so
+ * it's safe to call them from any thread, even concurrently. <br/>
+ * A method like {@code getPkgsInfos} returns a copy of its data array, which objects are
+ * not altered after creation, so its value is not influenced by the internal state after
+ * it returns.
+ * <p/>
+ *
+ * Implementation Background:
+ * <ul>
+ * <li> The sdk manager has a set of "Package" classes that cover both local
+ * and remote SDK operations.
+ * <li> Goal was to split it in 2 cleanly separated parts: {@link LocalSdk} parses sdk on disk,
+ * and a separate class wraps the downloaded manifest (this is now handled within Studio only)
+ * <li> The local SDK should be a singleton accessible somewhere, so there will be one in ADT
+ * (via the Sdk instance), one in Studio, and one in the command line tool. <br/>
+ * Right now there's a bit of mess with some classes creating a temp LocalSdkParser,
+ * some others using an SdkManager instance, and that needs to be sorted out.
+ * <li> As a transition, the SdkManager instance wraps a LocalSdk and uses this. Eventually the
+ * SdkManager.java class will go away (its name is totally misleading, for starters.)
+ * <li> The current LocalSdkParser stays as-is for compatibility purposes and the goal is also
+ * to totally remove it when the SdkManager class goes away.
+ * </ul>
+ * @version 2 of the {@code SdkManager} class, essentially.
+ * @deprecated in favor of {@link AndroidSdkHandler}/{@link RepoManager}.
+ */
+ at Deprecated
+public class LocalSdk {
+
+ /** Location of the SDK. Maybe null. Can be changed. */
+ private File mSdkRoot;
+ /** File operation object. (Used for overriding in mock testing.) */
+ private final FileOp mFileOp;
+ /** List of package information loaded so far. Lazily populated. */
+ @GuardedBy(value="mLocalPackages")
+ private final Multimap<PkgType, LocalPkgInfo> mLocalPackages = TreeMultimap.create();
+ /** Directories already parsed into {@link #mLocalPackages}. */
+ @GuardedBy(value="mLocalPackages")
+ private final Multimap<PkgType, LocalDirInfo> mVisitedDirs = HashMultimap.create();
+ /** A legacy build-tool for older platform-tools < 17. */
+ private BuildToolInfo mLegacyBuildTools;
+
+ /**
+ * Creates an initial LocalSdk instance with an unknown location.
+ */
+ public LocalSdk() {
+ mFileOp = FileOpUtils.create();
+ }
+
+ /**
+ * Creates an initial LocalSdk instance for a known SDK location.
+ *
+ * @param sdkRoot The location of the SDK root folder.
+ */
+ public LocalSdk(@NonNull File sdkRoot) {
+ this();
+ setLocation(sdkRoot);
+ }
+
+ /**
+ * Creates an initial LocalSdk instance with an unknown location.
+ * This is designed for unit tests to override the {@link FileOp} being used.
+ *
+ * @param fileOp The alternate {@link FileOp} to use for all file-based interactions.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ public LocalSdk(@NonNull FileOp fileOp) {
+ mFileOp = fileOp;
+ }
+
+ /*
+ * Returns the current FileOp being used.
+ */
+ @NonNull
+ public FileOp getFileOp() {
+ return mFileOp;
+ }
+
+ /**
+ * Sets or changes the SDK root location. This also clears any cached information.
+ *
+ * @param sdkRoot The location of the SDK root folder.
+ */
+ public void setLocation(@NonNull File sdkRoot) {
+ assert sdkRoot != null;
+ mSdkRoot = sdkRoot;
+ clearLocalPkg(PkgType.PKG_ALL);
+ }
+
+ /**
+ * Location of the SDK. Maybe null. Can be changed.
+ *
+ * @return The location of the SDK. Null if not initialized yet.
+ */
+ @Nullable
+ public File getLocation() {
+ return mSdkRoot;
+ }
+
+ /**
+ * Location of the SDK. Maybe null. Can be changed.
+ * The getLocation() API replaces this function.
+ * @return The location of the SDK. Null if not initialized yet.
+ */
+ @Deprecated
+ @Nullable
+ public String getPath() {
+ return mSdkRoot != null ? mSdkRoot.getPath() : null;
+ }
+
+ /**
+ * Clear the tracked visited folders & the cached {@link LocalPkgInfo} for the
+ * given filter types.
+ *
+ * @param filters A set of PkgType constants or {@link PkgType#PKG_ALL} to clear everything.
+ */
+ public void clearLocalPkg(@NonNull EnumSet<PkgType> filters) {
+ mLegacyBuildTools = null;
+
+ synchronized (mLocalPackages) {
+ for (PkgType filter : filters) {
+ mVisitedDirs.removeAll(filter);
+ mLocalPackages.removeAll(filter);
+ }
+ }
+ }
+
+ //--------- Generic querying ---------
+
+
+ /**
+ * Retrieves information on a package identified by an {@link IPkgDesc}.
+ *
+ * @param descriptor {@link IPkgDesc} describing a package.
+ * @return The first package found with the same descriptor or null.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull IPkgDesc descriptor) {
+
+ for (LocalPkgInfo pkg : getPkgsInfos(EnumSet.of(descriptor.getType()))) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.equals(descriptor)) {
+ return pkg;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by an {@link AndroidVersion}.
+ *
+ * Note: don't use this for {@link PkgType#PKG_SYS_IMAGE} since there can be more than
+ * one ABI and this method only returns a single package per filter type.
+ *
+ * @param filter {@link PkgType#PKG_PLATFORM}, {@link PkgType#PKG_SAMPLE}
+ * or {@link PkgType#PKG_SOURCE}.
+ * @param version The {@link AndroidVersion} specific for this package type.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull AndroidVersion version) {
+ assert filter == PkgType.PKG_PLATFORM ||
+ filter == PkgType.PKG_SAMPLE ||
+ filter == PkgType.PKG_SOURCE;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasAndroidVersion() && d.getAndroidVersion().equals(version)) {
+ return pkg;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by its {@link Revision}.
+ * <p/>
+ * Note that {@link PkgType#PKG_TOOLS} and {@link PkgType#PKG_PLATFORM_TOOLS}
+ * are unique in a local SDK so you'll want to use {@link #getPkgInfo(PkgType)}
+ * to retrieve them instead.
+ *
+ * @param filter {@link PkgType#PKG_BUILD_TOOLS}.
+ * @param revision The {@link Revision} uniquely identifying this package.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull Revision revision) {
+
+ assert filter == PkgType.PKG_BUILD_TOOLS;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.getRevision().equals(revision)) {
+ return pkg;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by its {@link String} path.
+ * <p/>
+ * For add-ons and platforms, the path is the target hash string
+ * (see {@link AndroidTargetHash} for helpers methods to generate this string.)
+ *
+ * @param filter {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM}.
+ * @param path The vendor/path uniquely identifying this package.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull String path) {
+
+ assert filter == PkgType.PKG_ADDON ||
+ filter == PkgType.PKG_PLATFORM;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasPath() && path.equals(d.getPath())) {
+ return pkg;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by both vendor and path strings.
+ * <p/>
+ * For add-ons the path is target hash string
+ * (see {@link AndroidTargetHash} for helpers methods to generate this string.)
+ *
+ * @param filter {@link PkgType#PKG_EXTRA}, {@link PkgType#PKG_ADDON}.
+ * @param vendor The vendor id of the extra package.
+ * @param path The path uniquely identifying this package for its vendor.
+ * @return An existing package information or null if not found.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter,
+ @NonNull String vendor,
+ @NonNull String path) {
+
+ assert filter == PkgType.PKG_EXTRA ||
+ filter == PkgType.PKG_ADDON;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ IPkgDesc d = pkg.getDesc();
+ if (d.hasVendor() && vendor.equals(d.getVendor().getId())) {
+ if (d.hasPath() && path.equals(d.getPath())) {
+ return pkg;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on an extra package identified by its {@link String} vendor/path.
+ *
+ * @param vendor The vendor id of the extra package.
+ * @param path The path uniquely identifying this package for its vendor.
+ * @return An existing extra package information or null if not found.
+ */
+ @Nullable
+ public LocalExtraPkgInfo getExtra(@NonNull String vendor, @NonNull String path) {
+ return (LocalExtraPkgInfo) getPkgInfo(PkgType.PKG_EXTRA, vendor, path);
+ }
+
+ /**
+ * For unique local packages.
+ * Returns the cached LocalPkgInfo for the requested type.
+ * Loads it from disk if not cached.
+ *
+ * @param filter {@link PkgType#PKG_TOOLS} or {@link PkgType#PKG_PLATFORM_TOOLS}
+ * or {@link PkgType#PKG_DOC}.
+ * @return null if the package is not installed.
+ */
+ @Nullable
+ public LocalPkgInfo getPkgInfo(@NonNull PkgType filter) {
+ if (filter != PkgType.PKG_TOOLS &&
+ filter != PkgType.PKG_PLATFORM_TOOLS &&
+ filter != PkgType.PKG_DOC &&
+ filter != PkgType.PKG_NDK &&
+ filter != PkgType.PKG_LLDB) {
+ assert false;
+ return null;
+ }
+
+ LocalPkgInfo info = null;
+ synchronized (mLocalPackages) {
+ Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
+ assert existing.size() <= 1;
+ if (!existing.isEmpty()) {
+ return existing.iterator().next();
+ }
+
+ File uniqueDir = new File(mSdkRoot, filter.getFolderName());
+
+ if (!mVisitedDirs.containsEntry(filter, new LocalDirInfo.MapComparator(uniqueDir))) {
+ switch(filter) {
+ case PKG_TOOLS:
+ info = scanTools(uniqueDir);
+ break;
+ case PKG_PLATFORM_TOOLS:
+ info = scanPlatformTools(uniqueDir);
+ break;
+ case PKG_DOC:
+ info = scanDoc(uniqueDir);
+ break;
+ case PKG_NDK:
+ info = scanNdk(uniqueDir);
+ break;
+ case PKG_LLDB:
+ info = scanLLDB(uniqueDir);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Whether we have found a valid pkg or not, this directory has been visited.
+ mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, uniqueDir));
+
+ if (info != null) {
+ mLocalPackages.put(filter, info);
+ }
+ }
+
+ return info;
+ }
+
+ /**
+ * Retrieve all the info about the requested package type.
+ * This is used for the package types that have one or more instances, each with different
+ * versions.
+ * The resulting array is sorted according to the PkgInfo's sort order.
+ * <p/>
+ * Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS} and
+ * {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is
+ * more efficient to use {@link #getPkgInfo(PkgType)} to query them.
+ *
+ * @param filter One of {@link PkgType} constants.
+ * @return A list (possibly empty) of matching installed packages. Never returns null.
+ */
+ @NonNull
+ public LocalPkgInfo[] getPkgsInfos(@NonNull PkgType filter) {
+ return getPkgsInfos(EnumSet.of(filter));
+ }
+
+ /**
+ * Retrieve all the info about the requested package types.
+ * This is used for the package types that have one or more instances, each with different
+ * versions.
+ * The resulting array is sorted according to the PkgInfo's sort order.
+ * <p/>
+ * To force the LocalSdk parser to load <b>everything</b>, simply call this method
+ * with the {@link PkgType#PKG_ALL} argument to load all the known package types.
+ * <p/>
+ * Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS} and
+ * {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is
+ * more efficient to use {@link #getPkgInfo(PkgType)} to query them.
+ *
+ * @param filters One or more of {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM},
+ * {@link PkgType#PKG_BUILD_TOOLS}, {@link PkgType#PKG_EXTRA},
+ * {@link PkgType#PKG_SOURCE}, {@link PkgType#PKG_SYS_IMAGE}
+ * @return A list (possibly empty) of matching installed packages. Never returns null.
+ */
+ @NonNull
+ public LocalPkgInfo[] getPkgsInfos(@NonNull EnumSet<PkgType> filters) {
+ List<LocalPkgInfo> list = Lists.newArrayList();
+
+ for (PkgType filter : filters) {
+ if (filter == PkgType.PKG_TOOLS ||
+ filter == PkgType.PKG_PLATFORM_TOOLS ||
+ filter == PkgType.PKG_DOC ||
+ filter == PkgType.PKG_NDK ||
+ filter == PkgType.PKG_LLDB) {
+ LocalPkgInfo info = getPkgInfo(filter);
+ if (info != null) {
+ list.add(info);
+ }
+ } else {
+ synchronized (mLocalPackages) {
+ Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
+ assert existing != null; // Multimap returns an empty set if not found
+
+ if (!existing.isEmpty()) {
+ list.addAll(existing);
+ continue;
+ }
+
+ File subDir = new File(mSdkRoot, filter.getFolderName());
+
+ if (!mVisitedDirs.containsEntry(filter, new LocalDirInfo.MapComparator(subDir))) {
+ switch(filter) {
+ case PKG_BUILD_TOOLS:
+ scanBuildTools(subDir, existing);
+ break;
+
+ case PKG_PLATFORM:
+ scanPlatforms(subDir, existing);
+ break;
+
+ case PKG_SYS_IMAGE:
+ scanSysImages(subDir, existing, false);
+ break;
+
+ case PKG_ADDON_SYS_IMAGE:
+ scanSysImages(subDir, existing, true);
+ break;
+
+ case PKG_ADDON:
+ scanAddons(subDir, existing);
+ break;
+
+ case PKG_SAMPLE:
+ scanSamples(subDir, existing);
+ break;
+
+ case PKG_SOURCE:
+ scanSources(subDir, existing);
+ break;
+
+ case PKG_EXTRA:
+ scanExtras(subDir, existing);
+ break;
+
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ case PKG_DOC:
+ case PKG_NDK:
+ case PKG_LLDB:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported pkg type " + filter.toString());
+ }
+ mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, subDir));
+ list.addAll(existing);
+ }
+ }
+ }
+ }
+
+ Collections.sort(list);
+ return list.toArray(new LocalPkgInfo[list.size()]);
+ }
+
+ //---------- Package-specific querying --------
+
+ /**
+ * Returns the {@link BuildToolInfo} for the given revision.
+ *
+ * @param revision The requested revision.
+ * @return A {@link BuildToolInfo}. Can be null if {@code revision} is null or is
+ * not part of the known set returned by {@code getPkgsInfos(PkgType.PKG_BUILD_TOOLS)}.
+ */
+ @Nullable
+ public BuildToolInfo getBuildTool(@Nullable Revision revision) {
+ LocalPkgInfo pkg = getPkgInfo(PkgType.PKG_BUILD_TOOLS, revision);
+ if (pkg instanceof LocalBuildToolPkgInfo) {
+ return ((LocalBuildToolPkgInfo) pkg).getBuildToolInfo();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the highest build-tool revision known, or null if there are are no build-tools.
+ * <p/>
+ * If no specific build-tool package is installed but the platform-tools is lower than 17,
+ * then this creates and returns a "legacy" built-tool package using platform-tools.
+ * (We only split build-tools out of platform-tools starting with revision 17,
+ * before they were both the same thing.)
+ *
+ * @return The highest build-tool revision known, or null.
+ */
+ @Nullable
+ public BuildToolInfo getLatestBuildTool() {
+ if (mLegacyBuildTools != null) {
+ return mLegacyBuildTools;
+ }
+
+ LocalPkgInfo[] pkgs = getPkgsInfos(PkgType.PKG_BUILD_TOOLS);
+
+ if (pkgs.length == 0) {
+ LocalPkgInfo ptPkg = getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
+ if (ptPkg instanceof LocalPlatformToolPkgInfo &&
+ ptPkg.getDesc().getRevision().compareTo(new Revision(17)) < 0) {
+ // older SDK, create a compatible build-tools
+ mLegacyBuildTools = createLegacyBuildTools((LocalPlatformToolPkgInfo) ptPkg);
+ return mLegacyBuildTools;
+ }
+ return null;
+ }
+
+ assert pkgs.length > 0;
+
+ // Note: the pkgs come from a TreeMultimap so they should already be sorted.
+ // Just in case, sort them again.
+ Arrays.sort(pkgs);
+
+ // LocalBuildToolPkgInfo's comparator sorts on its Revision so we just
+ // need to take the latest element.
+
+ LocalBuildToolPkgInfo preview = null;
+ for (int i = pkgs.length - 1; i >= 0; i--) {
+ LocalPkgInfo pkg = pkgs[i];
+ // Don't want to include preview build tools
+ if (pkg instanceof LocalBuildToolPkgInfo) {
+ if (!pkg.getDesc().isPreview()) {
+ return ((LocalBuildToolPkgInfo)pkg).getBuildToolInfo();
+ }
+ else if (preview == null) {
+ preview = (LocalBuildToolPkgInfo)pkg;
+ }
+ }
+ }
+
+ // fall back to preview if we didn't find a non-preview
+ return preview != null ? preview.getBuildToolInfo() : null;
+ }
+
+ @NonNull
+ private BuildToolInfo createLegacyBuildTools(@NonNull LocalPlatformToolPkgInfo ptInfo) {
+ File platformTools = new File(getLocation(), SdkConstants.FD_PLATFORM_TOOLS);
+ File platformToolsLib = ptInfo.getLocalDir();
+ File platformToolsRs = new File(platformTools, SdkConstants.FN_FRAMEWORK_RENDERSCRIPT);
+
+ return new BuildToolInfo(
+ ptInfo.getDesc().getRevision(),
+ platformTools,
+ new File(platformTools, SdkConstants.FN_AAPT),
+ new File(platformTools, SdkConstants.FN_AIDL),
+ new File(platformTools, SdkConstants.FN_DX),
+ new File(platformToolsLib, SdkConstants.FN_DX_JAR),
+ new File(platformTools, SdkConstants.FN_RENDERSCRIPT),
+ new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE),
+ new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE_CLANG),
+ null,
+ null,
+ null,
+ null,
+ new File(platformTools, SdkConstants.FN_ZIPALIGN));
+ }
+
+ // -------------
+
+ /**
+ * Try to find a tools package at the given location.
+ * Returns null if not found.
+ */
+ private LocalToolPkgInfo scanTools(File toolFolder) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ return null;
+ }
+ rev = fullySpecifyRevision(rev);
+
+
+ Revision minPlatToolsRev =
+ PackageParserUtils.getRevisionProperty(props, PkgProps.MIN_PLATFORM_TOOLS_REV);
+ if (minPlatToolsRev == null) {
+ minPlatToolsRev = Revision.NOT_SPECIFIED;
+ }
+
+ LocalToolPkgInfo info = new LocalToolPkgInfo(this, toolFolder, props, rev, minPlatToolsRev);
+
+ // We're not going to check that all tools are present. At the very least
+ // we should expect to find android and an emulator adapted to the current OS.
+ boolean hasEmulator = false;
+ boolean hasAndroid = false;
+ String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
+ String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
+ File[] files = mFileOp.listFiles(toolFolder);
+ for (File file : files) {
+ String name = file.getName();
+ if (SdkConstants.FN_EMULATOR.equals(name)) {
+ hasEmulator = true;
+ }
+ if (android1.equals(name) || (android2 != null && android2.equals(name))) {
+ hasAndroid = true;
+ }
+ }
+ if (!hasAndroid) {
+ info.appendLoadError("Missing %1$s", SdkConstants.androidCmdName());
+ }
+ if (!hasEmulator) {
+ info.appendLoadError("Missing %1$s", SdkConstants.FN_EMULATOR);
+ }
+
+ return info;
+ }
+
+ /**
+ * Creates a new revision with at least three components (major, minor, micro)
+ * based on the given revision. This is important since in the past we would
+ * sometimes write out revisions with only one component when internally we
+ * required that they have more, and would convert using the specific Revision
+ * subclass.
+ * @param rev
+ * @return
+ */
+ private static Revision fullySpecifyRevision(Revision rev) {
+ // Since we used to require a complete revision
+ if (!rev.isPreview()) {
+ rev = new Revision(rev.getMajor(), rev.getMinor(), rev.getMicro());
+ }
+ return rev;
+ }
+
+ /**
+ * Try to find a platform-tools package at the given location.
+ * Returns null if not found.
+ */
+ private LocalPlatformToolPkgInfo scanPlatformTools(File ptFolder) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(ptFolder, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ return null;
+ }
+ // Since we used to require a complete revision
+ rev = fullySpecifyRevision(rev);
+
+ LocalPlatformToolPkgInfo info = new LocalPlatformToolPkgInfo(this, ptFolder, props, rev);
+ return info;
+ }
+
+ /**
+ * Try to find a docs package at the given location.
+ * Returns null if not found.
+ */
+ private LocalDocPkgInfo scanDoc(File docFolder) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ return null;
+ }
+
+ try {
+ AndroidVersion vers = AndroidVersionHelper.create(props);
+ LocalDocPkgInfo info = new LocalDocPkgInfo(this, docFolder, props, vers, rev);
+
+ // To start with, a doc folder should have an "index.html" to be acceptable.
+ // We don't actually check the content of the file.
+ if (!mFileOp.isFile(new File(docFolder, "index.html"))) {
+ info.appendLoadError("Missing index.html");
+ }
+ return info;
+
+ } catch (AndroidVersionException e) {
+ return null; // skip invalid or missing android version.
+ }
+ }
+
+ /**
+ * Try to find an NDK package at the given location.
+ * Returns null if not found.
+ */
+ @Nullable
+ private LocalNdkPkgInfo scanNdk(@NonNull File ndkFolder) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(ndkFolder, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ return null;
+ }
+
+ return new LocalNdkPkgInfo(this, ndkFolder, props, rev);
+ }
+
+ /**
+ * Try to find an LLDB package at the given location.
+ * Returns null if not found.
+ */
+ @Nullable
+ private LocalLLDBPkgInfo scanLLDB(@NonNull File lldbFolder) {
+ File pinnedLLDBFolder = new File(lldbFolder, LocalLLDBPkgInfo.PINNED_REVISION.toString());
+ // Can we find some properties?
+ Properties props = parseProperties(new File(pinnedLLDBFolder, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ return null;
+ }
+
+ return new LocalLLDBPkgInfo(this, pinnedLLDBFolder, props, rev);
+ }
+
+ /**
+ * Helper used by scanXyz methods below to check whether a directory should be visited.
+ * It can be skipped if it's not a directory or if it's already marked as visited in
+ * mVisitedDirs for the given package type -- in which case the directory is added to
+ * the visited map.
+ *
+ * @param pkgType The package type being scanned.
+ * @param directory The file or directory to check.
+ * @return False if directory can/should be skipped.
+ * True if directory should be visited, in which case it's registered in mVisitedDirs.
+ */
+ private boolean shouldVisitDir(@NonNull PkgType pkgType, @NonNull File directory) {
+ if (!mFileOp.isDirectory(directory)) {
+ return false;
+ }
+ synchronized (mLocalPackages) {
+ if (mVisitedDirs.containsEntry(pkgType, new LocalDirInfo.MapComparator(directory))) {
+ return false;
+ }
+ mVisitedDirs.put(pkgType, new LocalDirInfo(mFileOp, directory));
+ }
+ return true;
+ }
+
+ private void scanBuildTools(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ // The build-tool root folder contains a list of per-revision folders.
+ for (File buildToolDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(PkgType.PKG_BUILD_TOOLS, buildToolDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(buildToolDir, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ // Since we used to require a complete revision
+ rev = fullySpecifyRevision(rev);
+
+ BuildToolInfo btInfo = new BuildToolInfo(rev, buildToolDir);
+ LocalBuildToolPkgInfo pkgInfo =
+ new LocalBuildToolPkgInfo(this, buildToolDir, props, rev, btInfo);
+ outCollection.add(pkgInfo);
+ }
+ }
+
+ private void scanPlatforms(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(PkgType.PKG_PLATFORM, platformDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ Revision minToolsRev =
+ PackageParserUtils.getRevisionProperty(props, PkgProps.MIN_TOOLS_REV);
+ if (minToolsRev == null) {
+ minToolsRev = Revision.NOT_SPECIFIED;
+ }
+
+ try {
+ AndroidVersion vers = AndroidVersionHelper.create(props);
+
+ LocalPlatformPkgInfo pkgInfo =
+ new LocalPlatformPkgInfo(this, platformDir, props, vers, rev, minToolsRev);
+ outCollection.add(pkgInfo);
+
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanAddons(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File addonDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(PkgType.PKG_ADDON, addonDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(addonDir, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+ // Since we used to require a complete revision
+ rev = fullySpecifyRevision(rev);
+
+ try {
+ AndroidVersion vers = AndroidVersionHelper.create(props);
+
+ // Starting with addon-4.xsd, we have vendor-id and name-id available
+ // in the add-on source properties so we'll use that directly.
+
+ String nameId = props.getProperty(PkgProps.ADDON_NAME_ID);
+ String nameDisp = props.getProperty(PkgProps.ADDON_NAME_DISPLAY);
+ String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID);
+ String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY);
+
+ if (nameId == null) {
+ // Support earlier add-ons that only had a name display attribute
+ nameDisp = props.getProperty(PkgProps.ADDON_NAME, "Unknown");
+ nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(nameDisp);
+ }
+
+ if (nameId != null && nameDisp == null) {
+ nameDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
+ }
+
+ if (vendorId != null && vendorDisp == null) {
+ vendorDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
+ }
+
+ if (vendorId == null) {
+ // Support earlier add-ons that only had a vendor display attribute
+ vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR, "Unknown");
+ vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(vendorDisp);
+ }
+
+ LocalAddonPkgInfo pkgInfo = new LocalAddonPkgInfo(
+ this, addonDir, props, vers, rev,
+ IdDisplay.create(vendorId, vendorDisp),
+ IdDisplay.create(nameId, nameDisp));
+ outCollection.add(pkgInfo);
+
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanSysImages(
+ File collectionDir,
+ Collection<LocalPkgInfo> outCollection,
+ boolean scanAddons) {
+ List<File> propFiles = Lists.newArrayList();
+ PkgType type = scanAddons ? PkgType.PKG_ADDON_SYS_IMAGE : PkgType.PKG_SYS_IMAGE;
+
+ // Create a list of folders that contains a source.properties file matching these patterns:
+ // sys-img/target/tag/abi
+ // sys-img/target/abis
+ // sys-img/add-on-target/abi
+ // sys-img/target/add-on/abi
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(type, platformDir)) {
+ continue;
+ }
+
+ for (File dir1 : mFileOp.listFiles(platformDir)) {
+ // dir1 might be either a tag or an abi folder.
+ if (!shouldVisitDir(type, dir1)) {
+ continue;
+ }
+
+ File prop1 = new File(dir1, SdkConstants.FN_SOURCE_PROP);
+ if (mFileOp.isFile(prop1)) {
+ // dir1 was a legacy abi folder.
+ if (!propFiles.contains(prop1)) {
+ propFiles.add(prop1);
+ }
+ } else {
+ File[] dir1Files = mFileOp.listFiles(dir1);
+ for (File dir2 : dir1Files) {
+ // dir2 should be an abi folder in a tag folder.
+ if (!shouldVisitDir(type, dir2)) {
+ continue;
+ }
+
+ File prop2 = new File(dir2, SdkConstants.FN_SOURCE_PROP);
+ if (mFileOp.isFile(prop2)) {
+ if (!propFiles.contains(prop2)) {
+ propFiles.add(prop2);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (File propFile : propFiles) {
+ Properties props = parseProperties(propFile);
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = AndroidVersionHelper.create(props);
+
+ IdDisplay tag = LocalSysImgPkgInfo.extractTagFromProps(props);
+ String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID, null);
+ File abiDir = propFile.getParentFile();
+
+ if (vendorId == null && !scanAddons) {
+ LocalSysImgPkgInfo pkgInfo =
+ new LocalSysImgPkgInfo(this, abiDir, props, vers, tag, abiDir.getName(), rev);
+ outCollection.add(pkgInfo);
+
+ } else if (vendorId != null && scanAddons) {
+ String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
+ IdDisplay vendor = IdDisplay.create(vendorId, vendorDisp);
+
+ LocalAddonSysImgPkgInfo pkgInfo =
+ new LocalAddonSysImgPkgInfo(
+ this, abiDir, props, vers, vendor, tag, abiDir.getName(), rev);
+ outCollection.add(pkgInfo);
+ }
+
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanSamples(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(PkgType.PKG_SAMPLE, platformDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ Revision minToolsRev =
+ PackageParserUtils.getRevisionProperty(props, PkgProps.MIN_TOOLS_REV);
+ if (minToolsRev == null) {
+ minToolsRev = Revision.NOT_SPECIFIED;
+ }
+
+ try {
+ AndroidVersion vers = AndroidVersionHelper.create(props);
+
+ LocalSamplePkgInfo pkgInfo =
+ new LocalSamplePkgInfo(this, platformDir, props, vers, rev, minToolsRev);
+ outCollection.add(pkgInfo);
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanSources(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ // The build-tool root folder contains a list of per-revision folders.
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(PkgType.PKG_SOURCE, platformDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ Revision rev = PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = AndroidVersionHelper.create(props);
+
+ LocalSourcePkgInfo pkgInfo =
+ new LocalSourcePkgInfo(this, platformDir, props, vers, rev);
+ outCollection.add(pkgInfo);
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanExtras(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File vendorDir : mFileOp.listFiles(collectionDir)) {
+ if (!shouldVisitDir(PkgType.PKG_EXTRA, vendorDir)) {
+ continue;
+ }
+
+ for (File extraDir : mFileOp.listFiles(vendorDir)) {
+ if (!shouldVisitDir(PkgType.PKG_EXTRA, extraDir)) {
+ continue;
+ }
+
+ Properties props = parseProperties(new File(extraDir, SdkConstants.FN_SOURCE_PROP));
+ Revision rev =
+ PackageParserUtils.getRevisionProperty(props, PkgProps.PKG_REVISION);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+ // Since we used to require a three-part revision
+ rev = fullySpecifyRevision(rev);
+
+ String oldPaths =
+ PackageParserUtils.getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);
+
+ String vendorId = vendorDir.getName();
+ String vendorDisp = props.getProperty(PkgProps.EXTRA_VENDOR_DISPLAY);
+ if (vendorDisp == null || vendorDisp.isEmpty()) {
+ vendorDisp = vendorId;
+ }
+
+ String displayName = props.getProperty(PkgProps.EXTRA_NAME_DISPLAY, null);
+
+ LocalExtraPkgInfo pkgInfo = new LocalExtraPkgInfo(
+ this,
+ extraDir,
+ props,
+ IdDisplay.create(vendorId, vendorDisp),
+ extraDir.getName(),
+ displayName,
+ PkgDescExtra.convertOldPaths(oldPaths),
+ rev);
+ outCollection.add(pkgInfo);
+ }
+ }
+ }
+
+ /**
+ * Parses the given file as properties file if it exists.
+ * Returns null if the file does not exist, cannot be parsed or has no properties.
+ */
+ private Properties parseProperties(File propsFile) {
+ InputStream fis = null;
+ try {
+ if (mFileOp.exists(propsFile)) {
+ fis = mFileOp.newFileInputStream(propsFile);
+
+ Properties props = new Properties();
+ props.load(fis);
+
+ // To be valid, there must be at least one property in it.
+ if (!props.isEmpty()) {
+ return props;
+ }
+ }
+ } catch (IOException e) {
+ // Ignore
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {}
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java
new file mode 100644
index 0000000..ba76620
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSourcePkgInfo.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local source package, for a given platform's {@link AndroidVersion}.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version.
+ */
+public class LocalSourcePkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalSourcePkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newSource(version, revision).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java
new file mode 100644
index 0000000..a6b6d91
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalSysImgPkgInfo.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.targets.SystemImage;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.Properties;
+
+/**
+ * Local system-image package, for a given platform's {@link AndroidVersion}
+ * and given ABI.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version & ABI.
+ */
+public class LocalSysImgPkgInfo extends LocalPkgInfo {
+
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalSysImgPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @Nullable IdDisplay tag,
+ @NonNull String abi,
+ @NonNull Revision revision) {
+ super(localSdk, localDir, sourceProps);
+ String listDisplay = sourceProps.getProperty(PkgProps.PKG_LIST_DISPLAY);
+ if (listDisplay == null) {
+ listDisplay = "";
+ }
+ mDesc = PkgDesc.Builder.newSysImg(version, tag, abi, revision)
+ .setDescriptionShort(
+ createShortDescription(listDisplay,
+ abi, null, tag, version,
+ revision, sourceProps.containsKey(PkgProps.PKG_OBSOLETE)))
+ .setListDisplay(
+ createListDescription(listDisplay,
+ tag, getAbiDisplayNameInternal(abi),
+ sourceProps.containsKey(PkgProps.PKG_OBSOLETE)))
+ .create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+
+ /**
+ * Extracts the tag id & display from the properties.
+ * If missing, uses the "default" tag id.
+ */
+ @NonNull
+ public static IdDisplay extractTagFromProps(Properties props) {
+ if (props != null) {
+ String tagId = props.getProperty(PkgProps.SYS_IMG_TAG_ID, SystemImage.DEFAULT_TAG.getId());
+ String tagDisp = props.getProperty(PkgProps.SYS_IMG_TAG_DISPLAY, ""); //$NON-NLS-1$
+ if (tagDisp == null || tagDisp.isEmpty()) {
+ tagDisp = tagIdToDisplay(tagId);
+ }
+ assert tagId != null;
+ assert tagDisp != null;
+ return IdDisplay.create(tagId, tagDisp);
+ }
+ return SystemImage.DEFAULT_TAG;
+ }
+
+ /**
+ * Computes a display-friendly tag string based on the tag id.
+ * This is typically used when there's no tag-display attribute.
+ *
+ * @param tagId A non-null tag id to sanitize for display.
+ * @return The tag id with all non-alphanum symbols replaced by spaces and trimmed.
+ */
+ @NonNull
+ public static String tagIdToDisplay(@NonNull String tagId) {
+ String name;
+ name = tagId.replaceAll("[^A-Za-z0-9]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.trim();
+
+ if (!name.isEmpty()) {
+ char c = name.charAt(0);
+ if (!Character.isUpperCase(c)) {
+ StringBuilder sb = new StringBuilder(name);
+ sb.replace(0, 1, String.valueOf(c).toUpperCase(Locale.US));
+ name = sb.toString();
+ }
+ }
+ return name;
+ }
+
+ public static String createListDescription(String listDisplay, IdDisplay tag, String abiDisplayName, boolean obsolete) {
+ if (!listDisplay.isEmpty()) {
+ return String.format("%1$s%2$s", listDisplay, obsolete ? " (Obsolete)" : "");
+ }
+
+ boolean isDefaultTag = SystemImage.DEFAULT_TAG.equals(tag);
+ return String.format("%1$s%2$s System Image%3$s", isDefaultTag ? "" : (tag.getDisplay() + " "), abiDisplayName,
+ obsolete ? " (Obsolete)" : "");
+ }
+
+ public static String createShortDescription(String listDisplay,
+ String abi,
+ IdDisplay vendor,
+ IdDisplay tag,
+ AndroidVersion version,
+ Revision revision,
+ boolean obsolete) {
+ if (!listDisplay.isEmpty()) {
+ return String.format("%1$s, %2$s API %3$s, revision %4$s%5$s", listDisplay, vendor == null ? "Android" : vendor.getDisplay(),
+ version.getApiString(), revision.toShortString(), obsolete ? " (Obsolete)" : "");
+ }
+
+ boolean isDefaultTag = SystemImage.DEFAULT_TAG.equals(tag);
+ return String.format("%1$s%2$s System Image, %3$s API %4$s, revision %5$s%6$s", isDefaultTag ? "" : (tag.getDisplay() + " "),
+ getAbiDisplayNameInternal(abi), vendor == null ? "Android" : vendor.getDisplay(), version.getApiString(),
+ revision.toShortString(), obsolete ? " (Obsolete)" : "");
+ }
+
+ public static String getAbiDisplayNameInternal(String abi) {
+ return abi.replace("armeabi", "ARM EABI") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("arm64", "ARM 64") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("x86", "Intel x86 Atom") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("x86_64", "Intel x86_64 Atom") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("mips", "MIPS") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java
new file mode 100644
index 0000000..b5bb7c0
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/LocalToolPkgInfo.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.annotations.NonNull;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalToolPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final IPkgDesc mDesc;
+
+ public LocalToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull Revision revision,
+ @NonNull Revision minPlatformToolsRev) {
+ super(localSdk, localDir, sourceProps);
+ mDesc = PkgDesc.Builder.newTool(revision, minPlatformToolsRev).create();
+ }
+
+ @NonNull
+ @Override
+ public IPkgDesc getDesc() {
+ return mDesc;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/local/PackageParserUtils.java b/sdklib/src/main/java/com/android/sdklib/repository/local/PackageParserUtils.java
new file mode 100644
index 0000000..4a51ae0
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/local/PackageParserUtils.java
@@ -0,0 +1,117 @@
+/*
+ * 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.sdklib.repository.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.repository.PkgProps;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Misc utilities to help extracting elements and attributes out of a repository XML document.
+ */
+public class PackageParserUtils {
+
+ /**
+ * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a revision
+ * (major.minor.micro.preview).
+ *
+ * @param props The properties to parse.
+ * @param propKey The name of the property. Must not be null.
+ * @return A {@link Revision} or null if there is no such property or it couldn't be parsed.
+ */
+ @Nullable
+ public static Revision getRevisionProperty(
+ @Nullable Properties props,
+ @NonNull String propKey) {
+ String revStr = getProperty(props, propKey, null);
+
+ Revision rev = null;
+ if (revStr != null) {
+ try {
+ rev = Revision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ return rev;
+ }
+
+ /**
+ * Utility method that returns a property from a {@link Properties} object. Returns the default
+ * value if props is null or if the property is not defined.
+ *
+ * @param props The {@link Properties} to search into. If null, the default value is
+ * returned.
+ * @param propKey The name of the property. Must not be null.
+ * @param defaultValue The default value to return if {@code props} is null or if the key is not
+ * found. Can be null.
+ * @return The string value of the given key in the properties, or null if the key isn't found
+ * or if {@code props} is null.
+ */
+ @Nullable
+ public static String getProperty(
+ @Nullable Properties props,
+ @NonNull String propKey,
+ @Nullable String defaultValue) {
+ if (props == null) {
+ return defaultValue;
+ }
+ return props.getProperty(propKey, defaultValue);
+ }
+
+
+ /**
+ * Parses the skin folder and builds the skin list.
+ *
+ * @param skinRootFolder The path to the skin root folder.
+ */
+ @NonNull
+ public static List<File> parseSkinFolder(@NonNull File skinRootFolder, @NonNull FileOp fileOp) {
+ if (fileOp.isDirectory(skinRootFolder)) {
+ ArrayList<File> skinList = new ArrayList<File>();
+
+ File[] files = fileOp.listFiles(skinRootFolder);
+
+ for (File skinFolder : files) {
+ if (fileOp.isDirectory(skinFolder)) {
+ // check for layout file
+ File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
+
+ if (fileOp.isFile(layout)) {
+ // for now we don't parse the content of the layout and
+ // simply add the directory to the list.
+ skinList.add(skinFolder);
+ }
+ }
+ }
+
+ Collections.sort(skinList);
+ return skinList;
+ }
+
+ return Collections.emptyList();
+ }
+
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-01.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-01.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-01.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-01.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-02.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-02.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-02.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-02.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-03.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-03.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-03.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-03.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-04.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-04.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-04.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-04.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-05.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-05.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-05.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-05.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-06.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-06.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-06.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-06.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-07.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-07.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-07.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-07.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-1.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-1.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-1.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-1.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-2.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-2.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-2.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-addons-list-2.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-01.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-01.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-01.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-01.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-02.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-02.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-02.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-02.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-03.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-03.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-03.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-03.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-04.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-04.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-04.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-04.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-05.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-05.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-05.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-05.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-06.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-06.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-06.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-06.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-07.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-07.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-07.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-07.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-08.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-08.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-08.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-08.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-09.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-09.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-09.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-09.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-11.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-11.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-11.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-11.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-12.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-12.xsd
new file mode 100644
index 0000000..7c9d25a
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-12.xsd
@@ -0,0 +1,709 @@
+<?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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repository/12"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/12"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The repository contains a collection of downloadable items known as
+ "packages". Each package has a type and various attributes and contains
+ a list of file "archives" that can be downloaded for specific OSes.
+
+ An Android SDK repository is a web site that contains a "repository.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r3 and r4.
+
+ - v2 is used by the SDK Updater in Tools r5:
+ - It introduces a new <sample> repository type. Previously samples
+ were included in the <platform> packages. Instead this package is used
+ and and the samples are installed in $SDK/samples.
+ - All repository types have a new <obsolete> node. It works as a marker
+ to indicate the package is obsolete and should not be selected by default.
+ The UI also hides these out by default.
+
+ - v3 is used by the SDK Updater in Tools r8:
+ - It introduces a new <platform-tool> repository type. Previously platform-specific
+ tools were included in the <platform> packages. Instead this package is used
+ and platform-specific tools are installed in $SDK/platform-tools
+ - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
+ requires that at least some minimal version of <platform-tool> be installed.
+ - It removes the <addon> repository type, which is now in its own XML Schema.
+
+ - v4 is used by the SDK Updater in Tools r12:
+ - <extra> element now has a <project-files> element that contains 1 or
+ or more <path>, each indicating the relative path of a file that this package
+ can contribute to installed projects.
+ - <platform> element now has a mandatory <layoutlib> that indicates the API
+ and revision of that layout library for this particular platform.
+
+ - v5 is used by the SDK Manager in Tools r14:
+ - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+ should be detected and migrated to the new <path> for that package.
+ - <platform> has a new optional <abi-included> that describes the ABI of the
+ system image included in the platform, if any.
+ - New <system-image> package type, to store system images outside of <platform>s.
+ - New <source> package type.
+
+ - v6 is used by the SDK Manager in Tools r18:
+ - <extra> packages are removed. They are served only by the addon XML.
+ - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
+ and <sample> get a new optional field <beta-rc> which can be used to indicate
+ the package is a Beta Release Candidate and not a final release.
+
+ - v7 is used by the SDK Manager in Tools r20:
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - The <beta-rc> element is no longer supported, it is replaced by
+ <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
+ - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
+
+ - v8 is used by the SDK Manager in Tools r22.0:
+ - It introduces the new <build-tool> repository type, which contains build-specific
+ tools that were before placed in either <tool> or <platform-tool> (e.g. ant,
+ aapt, aidl, etc.)
+ - <tool> has a new "min-build-tool" attribute.
+
+ - v9 is used by the SDK Manager in Tools r22.6:
+ - It introduces a sub-tag for the <system-image> repository type.
+
+ - v10 is used by the SDK Manager in Tools r22.6.4:
+ - It introduces a <list-display> string for all package types.
+ - It removes the <system-image> type (system images are handled by sdk-sys-img-3.xsd)
+ - it changes <archive os=... arch=...> to sub-elements: <host-os>, <host-bits>,
+ <jvm-bits> and <min-jvm-version>.
+
+ -v11 added Android NDK component
+
+ -v12 added Android LLDB component
+ -->
+
+ <xsd:element name="sdk-repository" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable packages.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="platform" type="sdk:platformType" />
+ <xsd:element name="source" type="sdk:sourceType" />
+ <xsd:element name="tool" type="sdk:toolType" />
+ <xsd:element name="platform-tool" type="sdk:platformToolType" />
+ <xsd:element name="build-tool" type="sdk:buildToolType" />
+ <xsd:element name="doc" type="sdk:docType" />
+ <xsd:element name="sample" type="sdk:sampleType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ <xsd:element name="ndk" type="sdk:ndkType" />
+ <xsd:element name="lldb" type="sdk:lldbType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <!-- The definition of an NDK package. -->
+
+ <xsd:complexType name="ndkType">
+ <xsd:annotation>
+ <xsd:documentation>An NDK package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of an LLDB package. -->
+
+ <xsd:complexType name="lldbType">
+ <xsd:annotation>
+ <xsd:documentation>An LLDB package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK platform package. -->
+
+ <xsd:complexType name="platformType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android platform version. It is string such as "1.0". -->
+ <xsd:element name="version" type="xsd:normalizedString" />
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- Information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+ <!-- The ABI of the system image *included* in this platform, if any.
+ When the field is present, it means the platform already embeds one
+ system image. A platform can also have any number of external
+ <system-image> associated with it. -->
+ <xsd:element name="included-abi" type="sdk:abiType" minOccurs="0" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a layout library used by a platform. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The layoutlib API level, an int > 0,
+ incremented with each new incompatible lib. -->
+ <xsd:element name="api" type="xsd:positiveInteger" />
+ <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+ Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+ <xsd:element name="revision" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a source package. -->
+
+ <xsd:complexType name="sourceType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Sources for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this source belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK tool package. -->
+
+ <xsd:complexType name="toolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- The minimal revision of platform-tools required by this package.
+ Mandatory. Must be a revision element. -->
+ <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform-tool package. -->
+
+ <xsd:complexType name="platformToolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK build-tool package. -->
+
+ <xsd:complexType name="buildToolType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK build-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK doc package. -->
+
+ <xsd:complexType name="docType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK doc package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK sample package. -->
+
+ <xsd:complexType name="sampleType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK sample package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The optional display list item. When missing, it is auto-computed. -->
+ <xsd:element name="list-display" type="xsd:normalizedString" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a path segment used by the extra element. -->
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for the install path of an extra element.
+ It must be a single-segment path.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <xsd:element name="host-os" type="sdk:osType" minOccurs="0" />
+ <xsd:element name="host-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="jvm-bits" type="sdk:bitSizeType" minOccurs="0" />
+ <xsd:element name="min-jvm-version" type="sdk:jvmVersionType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro and an optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The preview/release candidate revision, an int > 0,
+ incremented each time a new preview is generated.
+ Not present for final releases. -->
+ <xsd:element name="preview" type="xsd:positiveInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of archive filters -->
+
+ <xsd:simpleType name="bitSizeType">
+ <xsd:annotation>
+ <xsd:documentation>A CPU bit size filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="32" />
+ <xsd:enumeration value="64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="osType">
+ <xsd:annotation>
+ <xsd:documentation>A host OS filter.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="jvmVersionType">
+ <xsd:annotation>
+ <xsd:documentation>A JVM version number, e.g. "1" or "1.6" or "1.14.15".</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([1-9](\.[1-9]{1,2}){0,2})"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-stats-1.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-stats-1.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-stats-1.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-stats-1.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-01.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-01.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-01.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-01.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-02.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-02.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-02.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-02.xsd
diff --git a/base/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-03.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-03.xsd
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-03.xsd
rename to sdklib/src/main/java/com/android/sdklib/repository/sdk-sys-img-03.xsd
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/AndroidSdkHandler.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/AndroidSdkHandler.java
new file mode 100644
index 0000000..4787f5f
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/AndroidSdkHandler.java
@@ -0,0 +1,598 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation;
+import com.android.repository.Revision;
+import com.android.repository.api.ConstantSourceProvider;
+import com.android.repository.api.FallbackRemoteRepoLoader;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemoteListSourceProvider;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.Repository;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.RepositorySourceProvider;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.impl.installer.BasicInstaller;
+import com.android.repository.impl.installer.PackageInstaller;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.impl.sources.LocalSourceProvider;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.android.sdklib.repositoryv2.meta.SysImgFactory;
+import com.android.sdklib.repositoryv2.sources.RemoteSiteType;
+import com.android.sdklib.repositoryv2.targets.AndroidTargetManager;
+import com.android.sdklib.repositoryv2.targets.SystemImage;
+import com.android.sdklib.repositoryv2.targets.SystemImageManager;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Map;
+
+
+/**
+ * Android SDK interface to {@link RepoManager}. Ensures that the proper android sdk-specific
+ * schemas and source providers are registered, and provides android sdk-specific package logic
+ * (pending as adoption continues).
+ */
+public final class AndroidSdkHandler {
+ /**
+ * Schema module containing the package type information to be used in addon repos.
+ */
+ private static final SchemaModule ADDON_MODULE = new SchemaModule(
+ "com.android.sdklib.repositoryv2.generated.addon.v%d.ObjectFactory",
+ "sdk-addon-%02d.xsd", AndroidSdkHandler.class);
+
+ /**
+ * Schema module containing the package type information to be used in the primary repo.
+ */
+ private static final SchemaModule REPOSITORY_MODULE = new SchemaModule(
+ "com.android.sdklib.repositoryv2.generated.repository.v%d.ObjectFactory",
+ "sdk-repository-%02d.xsd", AndroidSdkHandler.class);
+
+ /**
+ * Schema module containing the package type information to be used in system image repos.
+ */
+ private static final SchemaModule SYS_IMG_MODULE = new SchemaModule(
+ "com.android.sdklib.repositoryv2.generated.sysimg.v%d.ObjectFactory",
+ "sdk-sys-img-%02d.xsd", AndroidSdkHandler.class);
+
+ /**
+ * Common schema module used by the other sdk-specific modules.
+ */
+ private static final SchemaModule COMMON_MODULE = new SchemaModule(
+ "com.android.sdklib.repositoryv2.generated.common.v%d.ObjectFactory",
+ "sdk-common-%02d.xsd", AndroidSdkHandler.class);
+
+ /**
+ * The URL of the official Google sdk-repository site. The URL ends with a /, allowing easy
+ * concatenation.
+ */
+ public static final String URL_GOOGLE_SDK_SITE = "https://dl.google.com/android/repository/";
+
+ /**
+ * The name of the environment variable used to override the url of the primary repository, for
+ * testing.
+ */
+ public static final String SDK_TEST_BASE_URL_ENV_VAR = "SDK_TEST_BASE_URL";
+
+ /**
+ * The latest version of legacy remote packages we should expect to receive from a server. If
+ * you think you need to change this value you should create a new-style package instead.
+ */
+ public static final int LATEST_LEGACY_VERSION = 12;
+
+ /**
+ * The name of the file containing user-specified remote repositories.
+ */
+ @VisibleForTesting
+ static final String LOCAL_ADDONS_FILENAME = "repositories.cfg";
+
+ /**
+ * Pattern for the name of a (remote) file containing a list of urls to check for repositories.
+ *
+ * @see RemoteListSourceProvider
+ */
+ private static final String DEFAULT_SITE_LIST_FILENAME_PATTERN = "addons_list-%d.xml";
+
+ /**
+ * Lock for synchronizing changes to the our {@link RepoManager}.
+ */
+ private static final Object MANAGER_LOCK = new Object();
+
+ /**
+ * Pattern for the URL pointing to the legacy repository xml (as defined by e.g.
+ * sdk-repository-12.xsd).
+ */
+ private static final String LEGACY_REPO_URL_PATTERN = "%srepository-%d.xml";
+
+ /**
+ * Pattern for the URL pointing to the repository xml (as defined by sdk-repository-01.xsd in
+ * repositoryv2).
+ */
+ private static final String REPO_URL_PATTERN = "%srepository2-%d.xml";
+
+ /**
+ * The {@link RepoManager} initialized with our {@link SchemaModule}s, {@link
+ * RepositorySource}s, and local SDK path.
+ */
+ private RepoManager mRepoManager;
+
+ /**
+ * Finds all {@link SystemImage}s in packages known to {@link #mRepoManager};
+ */
+ private SystemImageManager mSystemImageManager;
+
+ /**
+ * Creates {@link IAndroidTarget}s based on the platforms and addons known to
+ * {@link #mRepoManager}.
+ */
+ private AndroidTargetManager mAndroidTargetManager;
+
+ /**
+ * Reference to our latest build tool package.
+ */
+ private BuildToolInfo mLatestBuildTool = null;
+
+ /**
+ * {@link FileOp} to use for local file operations. For normal operation should be
+ * {@link FileOpUtils#create()}.
+ */
+ private final FileOp mFop;
+
+ /**
+ * Singleton instance of this class.
+ */
+ private static Map<File, AndroidSdkHandler> sInstances = Maps.newConcurrentMap();
+
+ /**
+ * Location of the local SDK.
+ */
+ private final File mLocation;
+
+ /**
+ * Provider for user-specified {@link RepositorySource}s.
+ */
+ private LocalSourceProvider mUserSourceProvider;
+
+ /**
+ * Loader capable of loading old-style repository xml files, with namespace like
+ * http://schemas.android.com/sdk/android/repository/NN or similar.
+ *
+ * Static, shared among {@link AndroidSdkHandler} instances.
+ */
+ private static FallbackRemoteRepoLoader sRemoteFallback;
+
+ /**
+ * Lazily-initialized class containing our static repository configuration, shared between
+ * AndroidSdkHandler instances.
+ */
+ private static RepoConfig sRepoConfig;
+
+ /**
+ * Get a {@code AndroidSdkHandler} instance.
+ *
+ * @param localPath The path to the local SDK. If {@code null} this handler will only be used
+ * for remote operations.
+ */
+ @NonNull
+ public static AndroidSdkHandler getInstance(@Nullable File localPath) {
+ File key = localPath == null ? new File("") : localPath;
+ AndroidSdkHandler instance = sInstances.get(key);
+ if (instance == null) {
+ instance = new AndroidSdkHandler(localPath, FileOpUtils.create());
+ sInstances.put(key, instance);
+ }
+ return instance;
+ }
+
+ /**
+ * Don't use this, use {@link #getInstance(File)}, unless you're in a unit test and need to
+ * specify a custom {@link FileOp}.
+ */
+ @VisibleForTesting
+ public AndroidSdkHandler(@Nullable File localPath, @NonNull FileOp fop) {
+ mFop = fop;
+ mLocation = localPath;
+ }
+
+ /**
+ * Fetches {@link RepoManager} set up to interact with android SDK repositories. It should not
+ * cached by callers of this method, since any changes to the fundamental properties of the
+ * manager (fallback loaders, local path) will cause a new instance to be created.
+ */
+ @NonNull
+ public RepoManager getSdkManager(@NonNull ProgressIndicator progress) {
+ RepoManager result = mRepoManager;
+ synchronized (MANAGER_LOCK) {
+ if (result == null) {
+ mSystemImageManager = null;
+ mAndroidTargetManager = null;
+ mLatestBuildTool = null;
+
+ result = getRepoConfig(progress)
+ .createRepoManager(progress, mLocation, sRemoteFallback,
+ getUserSourceProvider(progress), mFop);
+ // Invalidate system images, targets, the latest build tool, and the legacy local
+ // package manager when local packages change
+ result.registerLocalChangeListener(new RepoManager.RepoLoadedCallback() {
+ @Override
+ public void doRun(@NonNull RepositoryPackages packages) {
+ mSystemImageManager = null;
+ mAndroidTargetManager = null;
+ mLatestBuildTool = null;
+ }
+ });
+ mRepoManager = result;
+ }
+ }
+ return mRepoManager;
+ }
+
+ /**
+ * Gets (and creates if necessary) a {@link SystemImageManager} based on our local sdk packages.
+ */
+ @NonNull
+ public SystemImageManager getSystemImageManager(@NonNull ProgressIndicator progress) {
+ if (mSystemImageManager == null) {
+ getSdkManager(progress);
+ mSystemImageManager = new SystemImageManager(mRepoManager,
+ (SysImgFactory) getSysImgModule().createLatestFactory(), mFop);
+ }
+ return mSystemImageManager;
+ }
+
+ /**
+ * Gets (and creates if necessary) an {@link AndroidTargetManager} based on our local sdk
+ * packages.
+ */
+ @NonNull
+ public AndroidTargetManager getAndroidTargetManager(@NonNull ProgressIndicator progress) {
+ if (mAndroidTargetManager == null) {
+ getSdkManager(progress);
+ mAndroidTargetManager = new AndroidTargetManager(this, mFop);
+ }
+ return mAndroidTargetManager;
+ }
+
+ /**
+ * Gets the path of the local SDK, if set.
+ */
+ @Nullable
+ public File getLocation() {
+ return mLocation;
+ }
+
+ /**
+ * Convenience to get a package from the local repo.
+ */
+ @Nullable
+ public LocalPackage getLocalPackage(@NonNull String path, @NonNull ProgressIndicator progress) {
+ return getSdkManager(progress).getPackages().getLocalPackages().get(path);
+ }
+
+ /**
+ * Sets the {@link FallbackRemoteRepoLoader} to be used to parse any old-style remote
+ * repositories we might receive.<p> Invalidates the repo manager; it will be recreated when
+ * next retrieved.
+ */
+ public static void setRemoteFallback(@Nullable FallbackRemoteRepoLoader fallbackSdk) {
+ synchronized (MANAGER_LOCK) {
+ sRemoteFallback = fallbackSdk;
+ invalidateAll();
+ }
+ }
+
+ /**
+ * Resets the {@link RepoManager}s of all cached {@link AndroidSdkHandler}s.
+ */
+ private static void invalidateAll() {
+ for (AndroidSdkHandler handler : sInstances.values()) {
+ handler.mRepoManager = null;
+ }
+ }
+
+ /**
+ * @return The {@link SchemaModule} containing the common sdk-specific metadata. See
+ * sdk-common-XX.xsd.
+ */
+ @NonNull
+ public static SchemaModule getCommonModule() {
+ return COMMON_MODULE;
+ }
+
+ /**
+ * @return The {@link SchemaModule} containing the metadata for addon-type {@link Repository}s.
+ * See sdk-addon-XX.xsd.
+ */
+ @NonNull
+ public static SchemaModule getAddonModule() {
+ return ADDON_MODULE;
+ }
+
+ /**
+ * @return The {@link SchemaModule} containing the metadata for the primary android SDK {@link
+ * Repository} (containin platforms etc.). See sdk-repository-XX.xsd.
+ */
+ @NonNull
+ public static SchemaModule getRepositoryModule() {
+ return REPOSITORY_MODULE;
+ }
+
+ /**
+ * @return The {@link SchemaModule} containing the metadata for system image-type {@link
+ * Repository}s. See sdk-sys-img-XX.xsd.
+ */
+ @NonNull
+ public static SchemaModule getSysImgModule() {
+ return SYS_IMG_MODULE;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ RemoteListSourceProvider getRemoteListSourceProvider(@NonNull ProgressIndicator progress) {
+ return getRepoConfig(progress).getRemoteListSourceProvider();
+ }
+
+ /**
+ * Gets the customizable {@link RepositorySourceProvider}. Can be null if there's a problem
+ * with the user's environment.
+ */
+ @Nullable
+ public LocalSourceProvider getUserSourceProvider(@NonNull ProgressIndicator progress) {
+ if (mUserSourceProvider == null) {
+ mUserSourceProvider = RepoConfig.createUserSourceProvider(progress, mFop);
+ synchronized (MANAGER_LOCK) {
+ if (mRepoManager != null) {
+ // If the repo already exists cause it to be reloaded, so the userSourceProvider
+ // can be added to the config.
+ mRepoManager = null;
+ getSdkManager(progress);
+ }
+ }
+ }
+ return mUserSourceProvider;
+ }
+
+ @NonNull
+ private RepoConfig getRepoConfig(@NonNull ProgressIndicator progress) {
+ if (sRepoConfig == null) {
+ sRepoConfig = new RepoConfig(progress);
+ }
+ return sRepoConfig;
+ }
+
+ /**
+ * Class containing the repository configuration we can (lazily) create statically, as well
+ * as a method to create a new {@link RepoManager} based on that configuration.
+ *
+ * Instances of this class may be shared between {@link AndroidSdkHandler} instances.
+ */
+ private static class RepoConfig {
+
+ /**
+ * Provider for a list of {@link RepositorySource}s fetched from the google.
+ */
+ private RemoteListSourceProvider mAddonsListSourceProvider;
+
+ /**
+ * Provider for the main new-style {@link RepositorySource}
+ */
+ private ConstantSourceProvider mRepositorySourceProvider;
+
+ /**
+ * Provider for the main legacy {@link RepositorySource}
+ */
+ private ConstantSourceProvider mLegacyRepositorySourceProvider;
+
+ /**
+ * Sets up our {@link SchemaModule}s and {@link RepositorySourceProvider}s if they haven't
+ * been yet.
+ *
+ * @param progress Used for error logging.
+ */
+ public RepoConfig(@NonNull ProgressIndicator progress) {
+ // Schema module for the list of update sites we download
+ SchemaModule addonListModule = new SchemaModule(
+ "com.android.sdklib.repositoryv2.sources.generated.v%d.ObjectFactory",
+ "sdk-sites-list-%d.xsd", RemoteSiteType.class);
+
+ try {
+ // Specify what modules are allowed to be used by what sites.
+ Map<Class<? extends RepositorySource>, Collection<SchemaModule>> siteTypes
+ = ImmutableMap.<Class<? extends RepositorySource>, Collection<SchemaModule>>builder()
+ .put(RemoteSiteType.AddonSiteType.class, ImmutableSet.of(ADDON_MODULE))
+ .put(RemoteSiteType.SysImgSiteType.class,
+ ImmutableSet.of(SYS_IMG_MODULE)).build();
+ mAddonsListSourceProvider = RemoteListSourceProvider
+ .create(getAddonListUrl(progress), addonListModule, siteTypes);
+ } catch (URISyntaxException e) {
+ progress.logError("Failed to set up addons source provider", e);
+ }
+
+ String url = String.format(REPO_URL_PATTERN, getBaseUrl(progress),
+ REPOSITORY_MODULE.getNamespaceVersionMap().size());
+ mRepositorySourceProvider = new ConstantSourceProvider(url, "Android Repository",
+ ImmutableSet.of(REPOSITORY_MODULE, RepoManager.getGenericModule()));
+
+ url = String.format(LEGACY_REPO_URL_PATTERN, getBaseUrl(progress), LATEST_LEGACY_VERSION);
+ mLegacyRepositorySourceProvider = new ConstantSourceProvider(url,
+ "Legacy Android Repository",
+ ImmutableSet.of(REPOSITORY_MODULE, RepoManager.getGenericModule()));
+ }
+
+ /**
+ * Creates a customizable {@link RepositorySourceProvider}. Can be null if there's a problem
+ * with the user's environment.
+ */
+ @Nullable
+ public static LocalSourceProvider createUserSourceProvider(
+ @NonNull ProgressIndicator progress,
+ @NonNull FileOp fileOp) {
+ try {
+ return new LocalSourceProvider(
+ new File(AndroidLocation.getFolder(), LOCAL_ADDONS_FILENAME),
+ ImmutableList.of(SYS_IMG_MODULE, ADDON_MODULE), fileOp);
+ } catch (AndroidLocation.AndroidLocationException e) {
+ progress.logWarning("Couldn't find android folder", e);
+ }
+ return null;
+ }
+
+ @NonNull
+ private static String getAddonListUrl(@NonNull ProgressIndicator progress) {
+ return getBaseUrl(progress) + DEFAULT_SITE_LIST_FILENAME_PATTERN;
+ }
+
+ /**
+ * Gets the default url (without the actual filename or specific final part of the path
+ * (e.g. sys-img). This will be either the value of {@link #SDK_TEST_BASE_URL_ENV_VAR} or
+ * {@link #URL_GOOGLE_SDK_SITE}
+ */
+ @NonNull
+ private static String getBaseUrl(@NonNull ProgressIndicator progress) {
+ String baseUrl = System.getenv(SDK_TEST_BASE_URL_ENV_VAR);
+ if (baseUrl != null) {
+ if (!baseUrl.isEmpty() && baseUrl.endsWith("/")) {
+ return baseUrl;
+ } else {
+ progress.logWarning("Ignoring invalid SDK_TEST_BASE_URL: " + baseUrl);
+ }
+ }
+ return URL_GOOGLE_SDK_SITE;
+ }
+
+ @NonNull
+ public RemoteListSourceProvider getRemoteListSourceProvider() {
+ return mAddonsListSourceProvider;
+ }
+
+ @NonNull
+ public RepoManager createRepoManager(@NonNull ProgressIndicator progress,
+ @Nullable File localLocation,
+ @Nullable FallbackRemoteRepoLoader remoteFallbackLoader,
+ @Nullable LocalSourceProvider userProvider, @NonNull FileOp fop) {
+ RepoManager result = RepoManager.create(fop);
+
+ // Create the schema modules etc. if they haven't been already.
+ result.registerSchemaModule(ADDON_MODULE);
+ result.registerSchemaModule(REPOSITORY_MODULE);
+ result.registerSchemaModule(SYS_IMG_MODULE);
+ result.registerSchemaModule(COMMON_MODULE);
+
+ result.registerSourceProvider(mRepositorySourceProvider);
+ result.registerSourceProvider(mLegacyRepositorySourceProvider);
+ result.registerSourceProvider(mAddonsListSourceProvider);
+ if (userProvider != null) {
+ result.registerSourceProvider(userProvider);
+ // The customizable source provider needs a handle on the repo manager, so it can
+ // mark the cached packages invalid if the sources change.
+ userProvider.setRepoManager(result);
+ }
+
+
+ result.setLocalPath(localLocation);
+
+ if (localLocation != null) {
+ // If we have a local sdk path set, set up the old-style loader so we can parse
+ // any legacy packages.
+ result.setFallbackLocalRepoLoader(
+ new LegacyLocalRepoLoader(localLocation, fop, result));
+
+ // If a location is set we'll always want at least the local packages loaded, so
+ // load them now.
+ result.loadSynchronously(0, progress, null, null);
+ }
+
+ result.setFallbackRemoteRepoLoader(remoteFallbackLoader);
+ return result;
+ }
+ }
+
+ /**
+ * Finds the best {@link PackageInstaller} for the given {@link RepoPackage}.
+ */
+ @NonNull
+ public static PackageInstaller findBestInstaller(@NonNull RepoPackage p) {
+ if (p.getTypeDetails() instanceof DetailsTypes.MavenType) {
+ return new MavenInstaller();
+ }
+ return new BasicInstaller();
+ }
+
+ /**
+ * Gets a {@link BuildToolInfo} corresponding to the newest installed build tool
+ * {@link RepoPackage}, or {@code null} if none are installed.
+ */
+ @Nullable
+ public BuildToolInfo getLatestBuildTool(@NonNull ProgressIndicator progress) {
+ if (mLatestBuildTool == null) {
+ RepoManager manager = getSdkManager(progress);
+ BuildToolInfo info = null;
+ for (LocalPackage p : manager.getPackages()
+ .getLocalPackagesForPrefix(SdkConstants.FD_BUILD_TOOLS)) {
+ if (info == null || info.getRevision().compareTo(p.getVersion()) < 0) {
+ info = new BuildToolInfo(p.getVersion(), p.getLocation());
+ }
+ }
+ mLatestBuildTool = info;
+ }
+ return mLatestBuildTool;
+ }
+
+ /**
+ * Creates a the {@link BuildToolInfo} for the specificed build tools revision, if available.
+ *
+ * @param revision The build tools revision requested
+ * @param progress {@link ProgressIndicator} for logging.
+ * @return The {@link BuildToolInfo} corresponding to the specified build tools package, or
+ * {@code} null if that revision is not installed.
+ */
+ @Nullable
+ public BuildToolInfo getBuildToolInfo(@NonNull Revision revision,
+ @NonNull ProgressIndicator progress) {
+ RepositoryPackages packages = getSdkManager(progress).getPackages();
+ LocalPackage p = packages.getLocalPackages()
+ .get(DetailsTypes.getBuildToolsPath(revision));
+ if (p == null) {
+ return null;
+ }
+ return new BuildToolInfo(p.getVersion(), p.getLocation());
+ }
+
+ /**
+ * Gets our {@link FileOp}. Useful so both the sdk handler and file op don't both have to be
+ * injected everywhere.
+ */
+ @NonNull
+ public FileOp getFileOp() {
+ return mFop;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/IdDisplay.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/IdDisplay.java
new file mode 100644
index 0000000..9f9593e
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/IdDisplay.java
@@ -0,0 +1,116 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.repositoryv2.meta.SdkCommonFactory;
+
+import java.util.Locale;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * A string with both user-friendly and easily-parsed versions.
+ * Contains stubs to be overridden by xjc-generated classes.
+ */
+ at SuppressWarnings("NullableProblems")
+ at XmlTransient
+public abstract class IdDisplay implements Comparable<IdDisplay> {
+
+ public static IdDisplay create(@NonNull String id, @NonNull String display) {
+ SdkCommonFactory factory = (SdkCommonFactory) AndroidSdkHandler.getCommonModule()
+ .createLatestFactory();
+ IdDisplay result = factory.createIdDisplayType();
+ result.setId(id);
+ result.setDisplay(display);
+ return result;
+ }
+
+ /**
+ * Sets the machine-friendly version of the string.
+ */
+ public abstract void setId(@NonNull String id);
+
+ /**
+ * Sets the user-friendly version of the string.
+ */
+ public abstract void setDisplay(@NonNull String display);
+
+ /**
+ * Gets the machine-friendly version of the string.
+ */
+ @NonNull
+ public abstract String getId();
+
+ /**
+ * Gets the user-friendly version of the string.
+ */
+ @NonNull
+ public abstract String getDisplay();
+
+ @Override
+ public int compareTo(IdDisplay o) {
+ return getId().compareTo(o.getId());
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof IdDisplay)) {
+ return false;
+ }
+ return compareTo((IdDisplay)o) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+ /**
+ * Returns a string representation for *debug* purposes only, not for UI display.
+ */
+ @Override
+ public String toString() {
+ return String.format("%1$s [%2$s]", getId(), getDisplay());
+ }
+
+ /**
+ * Computes a display-friendly tag string based on the id.
+ * This is typically used when there's no display attribute.
+ *
+ * @param id A non-null id to sanitize for display.
+ * @return The id with all non-alphanum symbols replaced by spaces and trimmed.
+ */
+ @NonNull
+ public static String idToDisplay(@NonNull String id) {
+ String name;
+ name = id.replaceAll("[^A-Za-z0-9]+", " ");
+ name = name.replaceAll(" +", " ");
+ name = name.trim();
+
+ if (!name.isEmpty()) {
+ char c = name.charAt(0);
+ if (!Character.isUpperCase(c)) {
+ StringBuilder sb = new StringBuilder(name);
+ sb.replace(0, 1, String.valueOf(c).toUpperCase(Locale.US));
+ name = sb.toString();
+ }
+ }
+ return name;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyDownloader.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyDownloader.java
new file mode 100644
index 0000000..3ecfc44
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyDownloader.java
@@ -0,0 +1,86 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.SettingsController;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.internal.repository.CanceledByUserException;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.utils.Pair;
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+
+/**
+ * A {@link Downloader} implementation that uses the old {@link DownloadCache}.
+ *
+ * TODO: Implement a new, fully-featured downloader.
+ */
+public class LegacyDownloader implements Downloader {
+
+ private DownloadCache mDownloadCache;
+
+ private FileOp mFileOp;
+
+ public LegacyDownloader(@NonNull FileOp fop) {
+ mDownloadCache = new DownloadCache(fop, DownloadCache.Strategy.FRESH_CACHE);
+ mFileOp = fop;
+ }
+
+ @Override
+ @Nullable
+ public InputStream downloadAndStream(@NonNull URL url, @Nullable SettingsController controller,
+ @NonNull ProgressIndicator indicator) throws IOException {
+ try {
+ return mDownloadCache.openCachedUrl(url.toString(), new LegacyTaskMonitor(indicator));
+ } catch (CanceledByUserException e) {
+ indicator.logInfo("The download was cancelled.");
+ }
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public File downloadFully(@NonNull URL url, @Nullable SettingsController settings,
+ @NonNull ProgressIndicator indicator)
+ throws IOException {
+ File result = File
+ .createTempFile("LegacyDownloader", Long.toString(System.currentTimeMillis()));
+ OutputStream out = mFileOp.newFileOutputStream(result);
+ try {
+ Pair<InputStream, Integer> downloadedResult = mDownloadCache
+ .openDirectUrl(url.toString(), new LegacyTaskMonitor(indicator));
+ if (downloadedResult.getSecond() == 200) {
+ ByteStreams.copy(downloadedResult.getFirst(), out);
+ out.close();
+ return result;
+ }
+ } catch (CanceledByUserException e) {
+ indicator.logInfo("The download was cancelled.");
+ }
+ return null;
+ }
+
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyLocalRepoLoader.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyLocalRepoLoader.java
new file mode 100644
index 0000000..7079aae
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyLocalRepoLoader.java
@@ -0,0 +1,253 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.Dependency;
+import com.android.repository.api.FallbackLocalRepoLoader;
+import com.android.repository.api.License;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.impl.meta.CommonFactory;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.local.LocalAddonPkgInfo;
+import com.android.sdklib.repository.local.LocalPkgInfo;
+import com.android.sdklib.repository.local.LocalPlatformPkgInfo;
+import com.android.sdklib.repository.local.LocalSdk;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * A {@link FallbackLocalRepoLoader} that uses a {@link LocalSdk} to parse {@link LocalPkgInfo} and
+ * convert them into {@link LocalPackage}s.
+ */
+public class LegacyLocalRepoLoader implements FallbackLocalRepoLoader {
+
+ /**
+ * The old {@link LocalSdk} we'll use to parse the old packages.
+ */
+ private final LocalSdk mLocalSdk;
+
+ /**
+ * The {@link RepoManager} we're parsing for.
+ */
+ private final RepoManager mManager;
+
+ /**
+ * Cache of packages found by {@link #mLocalSdk}.
+ */
+ private Map<File, LocalPkgInfo> mPkgs = null;
+
+ private final FileOp mFop;
+
+ /**
+ * Create a new LegacyLocalRepoLoader, based on {@link LocalSdk}.
+ *
+ * @param root The root directory of the SDK.
+ * @param fop {@link FileOp} to use. For normal operation should be {@link
+ * FileOpUtils#create()}.
+ * @param manager The {@link RepoManager} we're parsing for.
+ */
+ public LegacyLocalRepoLoader(@NonNull File root, @NonNull FileOp fop,
+ @NonNull RepoManager manager) {
+ mLocalSdk = new LocalSdk(fop);
+ mLocalSdk.setLocation(root);
+ mFop = fop;
+ mManager = manager;
+ }
+
+ /**
+ * Tries to parse a package rooted in the specified directory.
+ * @return A {@link LocalPackage} if one was found, otherwise null.
+ */
+ @Override
+ @Nullable
+ public LocalPackage parseLegacyLocalPackage(@NonNull File dir,
+ @NonNull ProgressIndicator progress) {
+ if (!mFop.exists(new File(dir, SdkConstants.FN_SOURCE_PROP))) {
+ return null;
+ }
+ progress.logInfo(String.format("Parsing legacy package: %s", dir));
+ LocalPkgInfo info;
+ if (mPkgs == null) {
+ Map<File, LocalPkgInfo> result = Maps.newHashMap();
+ mLocalSdk.clearLocalPkg(PkgType.PKG_ALL);
+ for (LocalPkgInfo local : mLocalSdk.getPkgsInfos(PkgType.PKG_ALL)) {
+ result.put(local.getLocalDir(), local);
+ }
+ mPkgs = result;
+ }
+
+ info = mPkgs.get(dir);
+ if (info == null) {
+ // If we had a source.properties in a nonstandard place, don't include it.
+ return null;
+ }
+
+ return new LegacyLocalPackage(info, progress);
+ }
+
+ @Override
+ public void refresh() {
+ mPkgs = null;
+ }
+
+ /**
+ * {@link LocalPackage} wrapper around a {@link LocalPkgInfo}.
+ */
+ class LegacyLocalPackage implements LocalPackage {
+
+ private final ProgressIndicator mProgress;
+
+ private final LocalPkgInfo mWrapped;
+
+ LegacyLocalPackage(@NonNull LocalPkgInfo wrapped, @NonNull ProgressIndicator progress) {
+ mWrapped = wrapped;
+ mProgress = progress;
+ }
+
+ @Override
+ @NonNull
+ public TypeDetails getTypeDetails() {
+ int layoutVersion = 0;
+ if (mWrapped instanceof LocalPlatformPkgInfo) {
+ layoutVersion = ((LocalPlatformPkgInfo) mWrapped).getLayoutlibApi();
+ }
+ List<IAndroidTarget.OptionalLibrary> addonLibraries = Lists.newArrayList();
+ if (mWrapped instanceof LocalAddonPkgInfo) {
+ addonLibraries = LegacyRepoUtils
+ .parseLegacyAdditionalLibraries(mWrapped.getLocalDir(), mProgress, mFop);
+ }
+ return LegacyRepoUtils
+ .createTypeDetails(mWrapped.getDesc(), layoutVersion, addonLibraries, getLocation(),
+ mProgress, mFop);
+ }
+
+ @NonNull
+ @Override
+ public Revision getVersion() {
+ return mWrapped.getDesc().getRevision();
+ }
+
+ @Override
+ @NonNull
+ public String getDisplayName() {
+ return LegacyRepoUtils.getDisplayName(mWrapped.getDesc());
+ }
+
+ @Override
+ @Nullable
+ public License getLicense() {
+ License res = mWrapped.getDesc().getLicense();
+ CommonFactory factory = (CommonFactory) mManager.getCommonModule()
+ .createLatestFactory();
+ if (res == null) {
+ res = factory.createLicenseType();
+ res.setValue(mWrapped.getSourceProperties().getProperty(PkgProps.PKG_LICENSE));
+ res.setId(String.format("license-%X", mWrapped.getSourceProperties().hashCode()));
+ res.setType("text");
+ }
+ return res;
+ }
+
+ @Override
+ @NonNull
+ public Collection<Dependency> getAllDependencies() {
+ List<Dependency> result = Lists.newArrayList();
+ Revision rev = mWrapped.getDesc().getMinPlatformToolsRev();
+ CommonFactory factory = (CommonFactory) mManager.getCommonModule()
+ .createLatestFactory();
+ if (rev != null) {
+ result.add(factory.createDependencyType(rev, SdkConstants.FD_PLATFORM_TOOLS));
+ }
+ rev = mWrapped.getDesc().getMinToolsRev();
+ if (rev != null) {
+ result.add(factory.createDependencyType(rev, SdkConstants.FD_TOOLS));
+ }
+ return result;
+ }
+
+ @Override
+ @NonNull
+ public String getPath() {
+ String relativePath = null;
+ try {
+ relativePath = FileOpUtils.makeRelative(mWrapped.getLocalSdk().getLocation(),
+ mWrapped.getLocalDir(), mFop);
+ } catch (IOException e) {
+ // nothing, we'll just not have a default path.
+ }
+ return LegacyRepoUtils.getLegacyPath(mWrapped.getDesc(), relativePath);
+ }
+
+ @Override
+ public boolean obsolete() {
+ return mWrapped.getDesc().isObsolete() ||
+ mWrapped.getDesc().getType() == PkgType.PKG_SAMPLE;
+ }
+
+ @Override
+ @NonNull
+ public CommonFactory createFactory() {
+ return (CommonFactory) mManager.getCommonModule().createLatestFactory();
+ }
+
+ @Override
+ public int compareTo(@NonNull RepoPackage o) {
+ int result = ComparisonChain.start()
+ .compare(getPath(), o.getPath())
+ .compare(getVersion(), o.getVersion())
+ .result();
+ if (result != 0) {
+ return result;
+ }
+ if (!(o instanceof LocalPackage)) {
+ return getClass().getName().compareTo(o.getClass().getName());
+ }
+ return 0;
+ }
+
+ @Override
+ @NonNull
+ public File getLocation() {
+ return mWrapped.getLocalDir();
+ }
+
+ @Override
+ public void setInstalledPath(File root) {
+ // Ignore, we already know our whole path.
+ }
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyRepoUtils.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyRepoUtils.java
new file mode 100644
index 0000000..09d98b4
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyRepoUtils.java
@@ -0,0 +1,354 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.FallbackLocalRepoLoader;
+import com.android.repository.api.FallbackRemoteRepoLoader;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.impl.meta.GenericFactory;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget.OptionalLibrary;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.repository.AddonManifestIniProps;
+import com.android.sdklib.repository.descriptors.IPkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repositoryv2.meta.AddonFactory;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.android.sdklib.repositoryv2.meta.Library;
+import com.android.sdklib.repositoryv2.meta.RepoFactory;
+import com.android.sdklib.repositoryv2.meta.SdkCommonFactory;
+import com.android.sdklib.repositoryv2.meta.SysImgFactory;
+import com.android.sdklib.repositoryv2.targets.SystemImage;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities used by the {@link FallbackLocalRepoLoader} and {@link FallbackRemoteRepoLoader}s to
+ * convert {@link IPkgDesc}s into forms useful to {@link RepoManager}.
+ */
+public class LegacyRepoUtils {
+
+ private static final Pattern PATTERN_LIB_DATA = Pattern.compile(
+ "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+
+ /**
+ * Convert a {@link IPkgDesc} and other old-style information into a {@link TypeDetails}.
+ */
+ @NonNull
+ public static TypeDetails createTypeDetails(@NonNull IPkgDesc desc,
+ int layoutLibVersion, @NonNull Collection<OptionalLibrary> addonLibraries,
+ @Nullable File packageDir, @NonNull ProgressIndicator progress, @NonNull FileOp fop) {
+
+ SdkCommonFactory sdkFactory = (SdkCommonFactory) AndroidSdkHandler
+ .getCommonModule().createLatestFactory();
+ SchemaModule repoExt = AndroidSdkHandler.getRepositoryModule();
+ SchemaModule addonExt = AndroidSdkHandler.getAddonModule();
+ SchemaModule sysImgExt = AndroidSdkHandler.getSysImgModule();
+ RepoFactory repoFactory = (RepoFactory) repoExt.createLatestFactory();
+ AddonFactory addonFactory = (AddonFactory) addonExt.createLatestFactory();
+ SysImgFactory sysImgFactory = (SysImgFactory) sysImgExt.createLatestFactory();
+ GenericFactory genericFactory = (GenericFactory) RepoManager.getGenericModule()
+ .createLatestFactory();
+
+ AndroidVersion androidVersion = desc.getAndroidVersion();
+
+ if (desc.getType() == PkgType.PKG_PLATFORM) {
+ DetailsTypes.PlatformDetailsType details = repoFactory.createPlatformDetailsType();
+
+ assert androidVersion != null;
+ details.setApiLevel(androidVersion.getApiLevel());
+ details.setCodename(androidVersion.getCodename());
+ DetailsTypes.PlatformDetailsType.LayoutlibType layoutLib = repoFactory
+ .createLayoutlibType();
+ layoutLib.setApi(layoutLibVersion);
+ details.setLayoutlib(layoutLib);
+ return (TypeDetails) details;
+ } else if (desc.getType() == PkgType.PKG_SYS_IMAGE ||
+ desc.getType() == PkgType.PKG_ADDON_SYS_IMAGE) {
+ DetailsTypes.SysImgDetailsType details = sysImgFactory.createSysImgDetailsType();
+ //noinspection ConstantConditions
+ details.setAbi(desc.getPath());
+ assert androidVersion != null;
+ details.setApiLevel(androidVersion.getApiLevel());
+ details.setCodename(androidVersion.getCodename());
+ IdDisplay tagIdDisplay = desc.getTag();
+ if (tagIdDisplay != null) {
+ IdDisplay tag = sdkFactory.createIdDisplayType();
+ tag.setId(tagIdDisplay.getId());
+ tag.setDisplay(tagIdDisplay.getDisplay());
+ details.setTag(tag);
+ } else {
+ details.setTag(SystemImage.DEFAULT_TAG);
+ }
+ IdDisplay vendorIdDisplay = desc.getVendor();
+ if (vendorIdDisplay != null) {
+ IdDisplay vendor = sdkFactory.createIdDisplayType();
+ vendor.setId(vendorIdDisplay.getId());
+ vendor.setDisplay(vendorIdDisplay.getDisplay());
+ details.setVendor(vendor);
+ }
+ return (TypeDetails) details;
+ } else if (desc.getType() == PkgType.PKG_ADDON) {
+ DetailsTypes.AddonDetailsType details = addonFactory.createAddonDetailsType();
+ IdDisplay vendorIdDisplay = desc.getVendor();
+ if (vendorIdDisplay != null) {
+ IdDisplay vendor = sdkFactory.createIdDisplayType();
+ vendor.setId(vendorIdDisplay.getId());
+ vendor.setDisplay(vendorIdDisplay.getDisplay());
+ details.setVendor(vendor);
+ }
+ IdDisplay nameIdDisplay = desc.getName();
+ if (nameIdDisplay != null) {
+ IdDisplay tag = sdkFactory.createIdDisplayType();
+ tag.setId(nameIdDisplay.getId());
+ tag.setDisplay(nameIdDisplay.getDisplay());
+ details.setTag(tag);
+ }
+ assert androidVersion != null;
+ details.setApiLevel(androidVersion.getApiLevel());
+ details.setCodename(androidVersion.getCodename());
+ if (!addonLibraries.isEmpty()) {
+ DetailsTypes.AddonDetailsType.Libraries librariesType = addonFactory.createLibrariesType();
+ List<Library> libraries = librariesType.getLibrary();
+ for (OptionalLibrary addonLib : addonLibraries) {
+ Library lib = sdkFactory.createLibraryType();
+ lib.setDescription(addonLib.getDescription());
+ lib.setName(addonLib.getName());
+ String jarPath = addonLib.getJar().getPath();
+ if (packageDir != null) {
+ lib.setPackagePath(packageDir);
+ try {
+ jarPath = FileOpUtils
+ .makeRelative(new File(packageDir, SdkConstants.FD_ADDON_LIBS),
+ new File(jarPath), fop);
+ } catch (IOException e) {
+ progress.logWarning("Error finding library", e);
+ }
+ }
+ if (!jarPath.isEmpty()) {
+ lib.setLocalJarPath(jarPath);
+ }
+ libraries.add(lib);
+ }
+ details.setLibraries(librariesType);
+ }
+ return (TypeDetails) details;
+ } else if (desc.getType() == PkgType.PKG_SOURCE) {
+ DetailsTypes.SourceDetailsType details = repoFactory.createSourceDetailsType();
+ assert androidVersion != null;
+ details.setApiLevel(androidVersion.getApiLevel());
+ details.setCodename(androidVersion.getCodename());
+ return (TypeDetails) details;
+ } else if (desc.getType() == PkgType.PKG_EXTRA) {
+ DetailsTypes.ExtraDetailsType details = addonFactory.createExtraDetailsType();
+ IdDisplay vendorIdDisplay = desc.getVendor();
+ if (vendorIdDisplay != null) {
+ IdDisplay vendor = sdkFactory.createIdDisplayType();
+ vendor.setId(vendorIdDisplay.getId());
+ vendor.setDisplay(vendorIdDisplay.getDisplay());
+ details.setVendor(vendor);
+ }
+ return (TypeDetails) details;
+ } else {
+ return (TypeDetails) genericFactory.createGenericDetailsType();
+ }
+ }
+
+ /**
+ * Gets the {@link RepoPackage#getDisplayName()} return value from an {@link IPkgDesc}.
+ */
+ public static String getDisplayName(IPkgDesc legacy) {
+ // The legacy code inconsistently adds "Obsolete" to display names, and we want
+ // to handle it separately in the new code. Remove it if it's there.
+ return getDisplayNameInternal(legacy).replace(" (Obsolete)", "");
+ }
+
+ private static String getDisplayNameInternal(IPkgDesc legacy) {
+ String result = legacy.getListDescription();
+ if (result != null) {
+ return result;
+ }
+ if (legacy.getType() == PkgType.PKG_PLATFORM) {
+ AndroidVersion androidVersion = legacy.getAndroidVersion();
+ assert androidVersion != null;
+ return SdkVersionInfo.getAndroidName(androidVersion.getFeatureLevel());
+ }
+ result = legacy.getListDescription();
+ if (!result.isEmpty()) {
+ return result;
+ }
+ result = legacy.getName() != null ? legacy.getName().getDisplay() : "";
+ if (!result.isEmpty()) {
+ return result;
+ }
+ return legacy.getInstallId();
+ }
+
+ public static List<OptionalLibrary> parseLegacyAdditionalLibraries(
+ @NonNull File packageLocation, @NonNull ProgressIndicator progress,
+ @NonNull FileOp fop) {
+ List<OptionalLibrary> result = Lists.newArrayList();
+ File addOnManifest = new File(packageLocation, SdkConstants.FN_MANIFEST_INI);
+
+ if (!fop.isFile(addOnManifest)) {
+ return result;
+ }
+ Map<String, String> propertyMap;
+ try {
+ propertyMap = ProjectProperties.parsePropertyStream(
+ fop.newFileInputStream(addOnManifest),
+ addOnManifest.getPath(),
+ null);
+
+ } catch (FileNotFoundException e) {
+ progress.logWarning("Failed to find " + addOnManifest, e);
+ return result;
+ }
+ if (propertyMap == null) {
+ return result;
+ }
+ // get the optional libraries
+ String librariesValue = propertyMap.get(AddonManifestIniProps.ADDON_LIBRARIES);
+
+ SdkCommonFactory sdkFactory = (SdkCommonFactory) AndroidSdkHandler.getCommonModule()
+ .createLatestFactory();
+
+ Map<String, String[]> libMap = Maps.newHashMap();
+ if (librariesValue != null) {
+ librariesValue = librariesValue.trim();
+ if (!librariesValue.isEmpty()) {
+ // split in the string into the libraries name
+ String[] libraryNames = librariesValue.split(";"); //$NON-NLS-1$
+ if (libraryNames.length > 0) {
+ for (String libName : libraryNames) {
+ libName = libName.trim();
+
+ // get the library data from the properties
+ String libData = propertyMap.get(libName);
+
+ if (libData != null) {
+ // split the jar file from the description
+ Matcher m = PATTERN_LIB_DATA.matcher(libData);
+ if (m.matches()) {
+ libMap.put(libName, new String[]{
+ m.group(1), m.group(2)});
+ } else {
+ progress.logWarning(String.format(
+ "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
+ libName, libData));
+ }
+ } else {
+ progress.logWarning(
+ String.format("Ignoring library '%1$s', missing property value",
+ libName));
+ }
+ }
+ }
+ }
+ for (Map.Entry<String, String[]> entry : libMap.entrySet()) {
+ String jarFile = entry.getValue()[0];
+ String desc = entry.getValue()[1];
+ Library lib = sdkFactory.createLibraryType();
+ lib.setName(entry.getKey());
+ lib.setPackagePath(packageLocation);
+ lib.setDescription(desc);
+ lib.setLocalJarPath(jarFile);
+ lib.setManifestEntryRequired(true);
+ result.add(lib);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the {@code path} (see {@link RepoPackage#getPath()}) for a legacy package.
+ * @param desc The {@link IPkgDesc} of the legacy package.
+ * @param relativeInstallPath The path of the package relative to the sdk root. Used to generate
+ * the path if normal methods fail.
+ * @return The path.
+ */
+ public static String getLegacyPath(@NonNull IPkgDesc desc,
+ @Nullable String relativeInstallPath) {
+ switch (desc.getType()) {
+ case PKG_TOOLS:
+ return SdkConstants.FD_TOOLS;
+ case PKG_PLATFORM_TOOLS:
+ return SdkConstants.FD_PLATFORM_TOOLS;
+ case PKG_BUILD_TOOLS:
+ return DetailsTypes.getBuildToolsPath(desc.getRevision());
+ case PKG_DOC:
+ return SdkConstants.FD_DOCS;
+ case PKG_PLATFORM:
+ return DetailsTypes.getPlatformPath(desc.getAndroidVersion());
+ case PKG_ADDON:
+ return DetailsTypes.getAddonPath(desc.getVendor(),
+ desc.getAndroidVersion(), desc.getName());
+ case PKG_SYS_IMAGE:
+ case PKG_ADDON_SYS_IMAGE:
+ return DetailsTypes.getSysImgPath(
+ desc.getVendor(),
+ desc.getAndroidVersion(), desc.getTag(),
+ desc.getPath());
+ case PKG_SOURCE:
+ return DetailsTypes.getSourcesPath(desc.getAndroidVersion());
+ case PKG_EXTRA:
+ String path = SdkConstants.FD_EXTRAS;
+
+ String vendor = desc.getVendor().getId();
+ if (vendor != null && vendor.length() > 0) {
+ path += RepoPackage.PATH_SEPARATOR + vendor;
+ }
+
+ String name = desc.getPath();
+ if (name != null && name.length() > 0) {
+ path += RepoPackage.PATH_SEPARATOR + name;
+ }
+
+ return path;
+ case PKG_NDK:
+ return SdkConstants.FD_NDK;
+ case PKG_LLDB:
+ return DetailsTypes.getLldbPath(desc.getRevision());
+ default:
+ // This shouldn't happen, but has been observed.
+ if (relativeInstallPath != null) {
+ return relativeInstallPath
+ .replace(File.separatorChar, RepoPackage.PATH_SEPARATOR);
+ }
+ return "unknown";
+ }
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyTaskMonitor.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyTaskMonitor.java
new file mode 100644
index 0000000..eea1547
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LegacyTaskMonitor.java
@@ -0,0 +1,121 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.UserCredentials;
+import com.android.repository.api.ProgressIndicator;
+
+/**
+ * Implementation of {@link ITaskMonitor} that wraps a {@link ProgressIndicator}, for interaction
+ * with the legacy SDK framework.
+ */
+public class LegacyTaskMonitor implements ITaskMonitor {
+
+ private final ProgressIndicator mWrapped;
+
+ private int mProgressMax;
+
+ public LegacyTaskMonitor(ProgressIndicator toWrap) {
+ mWrapped = toWrap;
+ mProgressMax = 100;
+ }
+
+ @Override
+ public void setDescription(String format, Object... args) {
+ mWrapped.setText(String.format(format, args));
+ }
+
+ @Override
+ public void log(String format, Object... args) {
+ mWrapped.logInfo(String.format(format, args));
+ }
+
+ @Override
+ public void logError(String format, Object... args) {
+ mWrapped.logError(String.format(format, args));
+ }
+
+ @Override
+ public void logVerbose(String format, Object... args) {
+ // TODO
+ }
+
+ @Override
+ public void setProgressMax(int max) {
+ mProgressMax = max;
+ }
+
+ @Override
+ public int getProgressMax() {
+ return mProgressMax;
+ }
+
+ @Override
+ public void incProgress(int delta) {
+ mWrapped.setFraction(mWrapped.getFraction() + (double) delta / (double) mProgressMax);
+ }
+
+ @Override
+ public int getProgress() {
+ return (int)(mWrapped.getFraction() * mProgressMax);
+ }
+
+ @Override
+ public boolean isCancelRequested() {
+ return mWrapped.isCanceled();
+ }
+
+ @Override
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ // TODO: implement if necessary
+ return null;
+ }
+
+ @Override
+ public boolean displayPrompt(String title, String message) {
+ // TODO: implement if necessary
+ return false;
+ }
+
+ @Override
+ public UserCredentials displayLoginCredentialsPrompt(String title, String message) {
+ // TODO: implement if necessary
+ return null;
+ }
+
+ @Override
+ public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) {
+ mWrapped.logError(String.format(msgFormat, args), t);
+ }
+
+ @Override
+ public void warning(@NonNull String msgFormat, Object... args) {
+ mWrapped.logWarning(String.format(msgFormat, args));
+ }
+
+ @Override
+ public void info(@NonNull String msgFormat, Object... args) {
+ mWrapped.logInfo(String.format(msgFormat, args));
+ }
+
+ @Override
+ public void verbose(@NonNull String msgFormat, Object... args) {
+ // TODO
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/LoggerProgressIndicatorWrapper.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LoggerProgressIndicatorWrapper.java
new file mode 100644
index 0000000..e589cbd
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/LoggerProgressIndicatorWrapper.java
@@ -0,0 +1,73 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.ProgressIndicatorAdapter;
+import com.android.utils.ILogger;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * A {@link ProgressIndicator} that wraps a {@link ILogger}.
+ */
+public class LoggerProgressIndicatorWrapper extends ProgressIndicatorAdapter {
+
+ private final ILogger mWrapped;
+
+ public LoggerProgressIndicatorWrapper(@NonNull ILogger toWrap) {
+ mWrapped = toWrap;
+ }
+
+ @Override
+ public void logWarning(@NonNull String s) {
+ mWrapped.warning(s);
+ }
+
+ @Override
+ public void logWarning(@NonNull String s, @Nullable Throwable e) {
+ if (e == null) {
+ logWarning(s);
+ return;
+ }
+
+ mWrapped.warning("%1$s:\n%2$s", s, throwableToString(e));
+ }
+
+ @Override
+ public void logError(@NonNull String s) {
+ logError(s, null);
+ }
+
+ @Override
+ public void logError(@NonNull String s, @Nullable Throwable e) {
+ mWrapped.error(e, s);
+ }
+
+ @Override
+ public void logInfo(@NonNull String s) {
+ mWrapped.info(s);
+ }
+
+ private static String throwableToString(@NonNull Throwable e) {
+ StringWriter writer = new StringWriter();
+ e.printStackTrace(new PrintWriter(writer));
+ return writer.toString();
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/MavenInstaller.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/MavenInstaller.java
new file mode 100644
index 0000000..7c9f7be
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/MavenInstaller.java
@@ -0,0 +1,404 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.Downloader;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.api.SettingsController;
+import com.android.repository.impl.installer.PackageInstaller;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.repository.util.InstallerUtil;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+import javax.xml.bind.DatatypeConverter;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.namespace.QName;
+
+/**
+ * A {@link PackageInstaller} that knows how to install Maven packages.
+ */
+public class MavenInstaller implements PackageInstaller {
+
+ public static final String MAVEN_DIR_NAME = "m2repository";
+
+ public static final String MAVEN_METADATA_FILE_NAME = "maven-metadata.xml";
+
+ @Override
+ public boolean uninstall(@NonNull LocalPackage p, @NonNull ProgressIndicator progress,
+ @NonNull RepoManager manager, @NonNull FileOp fop) {
+ fop.deleteFileOrFolder(p.getLocation());
+
+ if (fop.exists(p.getLocation())) {
+ progress.logWarning("Failed to uninstall " + p);
+ return false;
+ }
+ manager.markInvalid();
+ return removeVersion(p, fop, progress);
+ }
+
+ /**
+ * Remove the specified package from the corresponding metadata file.
+ */
+ private static boolean removeVersion(@NonNull LocalPackage p, @NonNull FileOp fop,
+ @NonNull ProgressIndicator progress) {
+ PackageInfo info = parsePackageInfo(p.getLocation(), p);
+ MavenMetadata metadata = parseMetadata(
+ new File(p.getLocation().getParent(), MAVEN_METADATA_FILE_NAME), info, progress,
+ fop);
+ if (metadata == null) {
+ return false;
+ }
+ metadata.versioning.versions.version.remove(info.version);
+ return writeMetadata(new File(p.getLocation().getParentFile(), MAVEN_METADATA_FILE_NAME),
+ progress, fop, metadata);
+ }
+
+ @Override
+ public boolean install(@NonNull RemotePackage p, @NonNull Downloader downloader,
+ @Nullable SettingsController settings, @NonNull ProgressIndicator progress,
+ @NonNull RepoManager manager, @NonNull FileOp fop) {
+ if (!p.getPath().startsWith(MAVEN_DIR_NAME)) {
+ progress.logError("Maven package paths must start with " + MAVEN_DIR_NAME);
+ return false;
+ }
+ URL url = InstallerUtil.resolveCompleteArchiveUrl(p, progress);
+ if (url == null) {
+ return false;
+ }
+ try {
+ String path = p.getPath();
+ path = path.replace(RepoPackage.PATH_SEPARATOR, File.separatorChar);
+ File dest = new File(manager.getLocalPath(), path);
+ if (!InstallerUtil.checkValidPath(dest, manager, progress)) {
+ return false;
+ }
+
+ File in = downloader.downloadFully(url, settings, progress);
+
+ File out = FileOpUtils.getNewTempDir("BasicInstaller", fop);
+ if (out == null || !fop.mkdirs(out)) {
+ throw new IOException("Failed to create temp dir");
+ }
+ fop.deleteOnExit(out);
+ InstallerUtil.unzip(in, out, fop, p.getArchive().getComplete().getSize(), progress);
+ fop.delete(in);
+
+ // Archives must contain a single top-level directory.
+ File[] topDirContents = fop.listFiles(out);
+ if (topDirContents.length != 1) {
+ throw new IOException("Archive didn't have single top level directory");
+ }
+ File packageRoot = topDirContents[0];
+
+ InstallerUtil.writePackageXml(p, packageRoot, manager, fop, progress);
+
+ // Move the final unzipped archive into place.
+ FileOpUtils.safeRecursiveOverwrite(packageRoot, dest, fop, progress);
+
+ PackageInfo info = parsePackageInfo(dest, p);
+ addVersion(new File(dest.getParentFile(), MAVEN_METADATA_FILE_NAME), info, progress,
+ fop);
+ manager.markInvalid();
+ return true;
+ } catch (IOException e) {
+ progress.logWarning("An error occurred during installation.", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the version, artifact Id, and group Id from the given path and package.
+ */
+ private static PackageInfo parsePackageInfo(@NonNull File path, @NonNull RepoPackage p) {
+ PackageInfo result = new PackageInfo();
+ result.version = path.getName();
+ result.artifactId = path.getParentFile().getName();
+ result.groupId = p.getPath().substring(p.getPath().indexOf(RepoPackage.PATH_SEPARATOR) + 1,
+ p.getPath().lastIndexOf(result.artifactId) - 1)
+ .replace(RepoPackage.PATH_SEPARATOR, '.');
+ return result;
+ }
+
+ /**
+ * Adds the specified packageInfo to the corresponding maven metadata file.
+ */
+ private static boolean addVersion(@NonNull File metadataFile, @NonNull PackageInfo info,
+ @NonNull ProgressIndicator progress, FileOp fop) {
+ MavenMetadata metadata = parseMetadata(metadataFile, info, progress, fop);
+
+ if (metadata == null) {
+ return false;
+ }
+
+ metadata.versioning.versions.version.add(info.version);
+ return writeMetadata(metadataFile, progress, fop, metadata);
+ }
+
+ /**
+ * Writes out a {@link MavenMetadata} to the specified location.
+ */
+ private static boolean writeMetadata(@NonNull File file, @NonNull ProgressIndicator progress,
+ @NonNull FileOp fop, @NonNull MavenMetadata metadata) {
+ Revision max = null;
+ for (String s : metadata.versioning.versions.version) {
+ Revision rev = Revision.parseRevision(s);
+ if (max == null || (!rev.isPreview() && rev.compareTo(max) > 0)) {
+ max = rev;
+ }
+ }
+ if (max != null) {
+ metadata.versioning.release = max.toString();
+ }
+ metadata.versioning.lastUpdated = System.currentTimeMillis();
+ Marshaller marshaller;
+ try {
+ JAXBContext context;
+ try {
+ context = JAXBContext.newInstance(MavenMetadata.class);
+ } catch (JAXBException e) {
+ // Shouldn't happen
+ progress.logError("Failed to create JAXBContext", e);
+ return false;
+ }
+ marshaller = context.createMarshaller();
+ } catch (JAXBException e) {
+ // Shouldn't happen
+ progress.logError("Failed to create Marshaller", e);
+ return false;
+ }
+ ByteArrayOutputStream metadataOutBytes = new ByteArrayOutputStream();
+ try {
+ marshaller.marshal(
+ new JAXBElement<MavenMetadata>(new QName("metadata"), MavenMetadata.class,
+ metadata), metadataOutBytes);
+ } catch (JAXBException e) {
+ progress.logWarning("Failed to write maven metadata: ", e);
+ return false;
+ }
+ OutputStream metadataOutFile = null;
+ try {
+ metadataOutFile = fop.newFileOutputStream(file);
+ metadataOutFile.write(metadataOutBytes.toByteArray());
+ } catch (FileNotFoundException e) {
+ progress.logWarning("Failed to write metadata file.", e);
+ return false;
+ } catch (IOException e) {
+ progress.logWarning("Failed to write metadata file.", e);
+ return false;
+ } finally {
+ if (metadataOutFile != null) {
+ try {
+ metadataOutFile.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ if (!writeHashFile(file, "MD5", progress, metadataOutBytes, fop)) {
+ return false;
+ }
+ if (!writeHashFile(file, "SHA1", progress, metadataOutBytes, fop)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Parses a {@link MavenMetadata} from the specified file, and verifies that it contains the
+ * specified artifact, or creates a new one if none exists.
+ */
+ private static MavenMetadata parseMetadata(@NonNull File file, @NonNull PackageInfo info,
+ @NonNull ProgressIndicator progress, @NonNull FileOp fop) {
+ MavenMetadata metadata;
+ if (fop.exists(file)) {
+ metadata = unmarshalMetadata(file, progress, fop);
+ if (metadata == null) {
+ return null;
+ }
+ if (!metadata.artifactId.equals(info.artifactId)) {
+ progress.logWarning(
+ "New artifact id '" + info.artifactId + "' doesn't match existing '"
+ + metadata.artifactId);
+ return null;
+ }
+ if (!metadata.groupId.equals(info.groupId)) {
+ progress.logWarning("New group id '" + info.groupId + "' doesn't match existing '"
+ + metadata.groupId);
+ return null;
+ }
+ } else {
+ metadata = new MavenMetadata();
+ metadata.artifactId = info.artifactId;
+ metadata.groupId = info.groupId;
+ metadata.versioning = new MavenMetadata.Versioning();
+ metadata.versioning.versions = new MavenMetadata.Versioning.Versions();
+ metadata.versioning.versions.version = Lists.newArrayList();
+ }
+ return metadata;
+ }
+
+ /**
+ * Attempts to read a {@link MavenMetadata} from the specified file.
+ */
+ @VisibleForTesting
+ static MavenMetadata unmarshalMetadata(@NonNull File metadataFile,
+ @NonNull ProgressIndicator progress, @NonNull FileOp fop) {
+ JAXBContext context;
+ try {
+ context = JAXBContext.newInstance(MavenMetadata.class);
+ } catch (JAXBException e) {
+ // Shouldn't happen
+ progress.logError("Failed to create JAXBContext", e);
+ return null;
+ }
+ Unmarshaller unmarshaller;
+ MavenMetadata result;
+ try {
+ unmarshaller = context.createUnmarshaller();
+ } catch (JAXBException e) {
+ // Shouldn't happen
+ progress.logError("Failed to create unmarshaller", e);
+ return null;
+ }
+ InputStream metadataInputStream;
+ try {
+ metadataInputStream = fop.newFileInputStream(metadataFile);
+ } catch (FileNotFoundException e) {
+ progress.logWarning("Couldn't open metadata file", e);
+ return null;
+ }
+ try {
+ result = (MavenMetadata) unmarshaller.unmarshal(metadataInputStream);
+ } catch (JAXBException e) {
+ progress.logWarning("Couldn't parse maven metadata file: " + metadataFile);
+ return null;
+ }
+ return result;
+ }
+
+ /**
+ * Writes a file containing a hash of the given file using the specified algorithm.
+ */
+ private static boolean writeHashFile(@NonNull File file, @NonNull String algorithm,
+ @NonNull ProgressIndicator progress, @NonNull ByteArrayOutputStream metadataOutBytes,
+ @NonNull FileOp fop) {
+ File md5File = new File(file.getParent(),
+ MAVEN_METADATA_FILE_NAME + "." + algorithm.toLowerCase());
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ // Shouldn't happen
+ progress.logError(algorithm + " algorithm not found", e);
+ return false;
+ }
+ OutputStream md5OutFile;
+ try {
+ md5OutFile = fop.newFileOutputStream(md5File);
+ } catch (FileNotFoundException e) {
+ progress.logWarning("Failed to open " + algorithm + " file");
+ return false;
+ }
+ try {
+ md5OutFile.write(
+ DatatypeConverter.printHexBinary(digest.digest(metadataOutBytes.toByteArray()))
+ .getBytes());
+ } catch (IOException e) {
+ progress.logWarning("Failed to write " + algorithm + " file");
+ return false;
+ } finally {
+ try {
+ md5OutFile.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ return true;
+ }
+
+ /**
+ * jaxb-usable class for marshalling/unmarshalling maven metadata files.
+ */
+ @VisibleForTesting
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlRootElement(name = "metadata")
+ static class MavenMetadata {
+
+ protected String groupId;
+
+ protected String artifactId;
+
+ protected MavenMetadata.Versioning versioning;
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Versioning {
+
+ protected String release;
+
+ protected MavenMetadata.Versioning.Versions versions;
+
+ protected long lastUpdated;
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Versions {
+
+ protected List<String> version;
+ }
+ }
+ }
+
+ /**
+ * Simple container for maven artifact version info.
+ */
+ private static class PackageInfo {
+
+ public String artifactId;
+
+ public String groupId;
+
+ public String version;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/AddonDetailsType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/AddonDetailsType.java
new file mode 100644
index 0000000..6702893
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/AddonDetailsType.java
@@ -0,0 +1,179 @@
+
+package com.android.sdklib.repositoryv2.generated.addon.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.generated.common.v1.ApiDetailsType;
+import com.android.sdklib.repositoryv2.generated.common.v1.IdDisplayType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-addon-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * type-details subclass containing api level and vendor information.
+ *
+ *
+ * <p>Java class for addonDetailsType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="addonDetailsType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/sdk/android/repo/common/01}apiDetailsType">
+ * <sequence>
+ * <element name="vendor" type="{http://schemas.android.com/sdk/android/repo/common/01}idDisplayType"/>
+ * <element name="tag" type="{http://schemas.android.com/sdk/android/repo/common/01}idDisplayType"/>
+ * <element name="default-skin" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ * <element name="libraries" type="{http://schemas.android.com/sdk/android/repo/addon2/01}librariesType" minOccurs="0"/>
+ * </sequence>
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "addonDetailsType", propOrder = {
+ "vendor",
+ "tag",
+ "defaultSkin",
+ "libraries"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class AddonDetailsType
+ extends ApiDetailsType
+ implements com.android.sdklib.repositoryv2.meta.DetailsTypes.AddonDetailsType
+{
+
+ @XmlElement(required = true)
+ protected IdDisplayType vendor;
+ @XmlElement(required = true)
+ protected IdDisplayType tag;
+ @XmlElement(name = "default-skin")
+ protected String defaultSkin;
+ protected LibrariesType libraries;
+
+ /**
+ * Gets the value of the vendor property.
+ *
+ * @return
+ * possible object is
+ * {@link IdDisplayType }
+ *
+ */
+ public IdDisplayType getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Sets the value of the vendor property.
+ *
+ * @param value
+ * allowed object is
+ * {@link IdDisplayType }
+ *
+ */
+ public void setVendorInternal(IdDisplayType value) {
+ this.vendor = value;
+ }
+
+ /**
+ * Gets the value of the tag property.
+ *
+ * @return
+ * possible object is
+ * {@link IdDisplayType }
+ *
+ */
+ public IdDisplayType getTag() {
+ return tag;
+ }
+
+ /**
+ * Sets the value of the tag property.
+ *
+ * @param value
+ * allowed object is
+ * {@link IdDisplayType }
+ *
+ */
+ public void setTagInternal(IdDisplayType value) {
+ this.tag = value;
+ }
+
+ /**
+ * Gets the value of the defaultSkin property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getDefaultSkin() {
+ return defaultSkin;
+ }
+
+ /**
+ * Sets the value of the defaultSkin property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setDefaultSkin(String value) {
+ this.defaultSkin = value;
+ }
+
+ /**
+ * Gets the value of the libraries property.
+ *
+ * @return
+ * possible object is
+ * {@link LibrariesType }
+ *
+ */
+ public LibrariesType getLibraries() {
+ return libraries;
+ }
+
+ /**
+ * Sets the value of the libraries property.
+ *
+ * @param value
+ * allowed object is
+ * {@link LibrariesType }
+ *
+ */
+ public void setLibrariesInternal(LibrariesType value) {
+ this.libraries = value;
+ }
+
+ public void setVendor(IdDisplay value) {
+ setVendorInternal(((IdDisplayType) value));
+ }
+
+ public void setTag(IdDisplay value) {
+ setTagInternal(((IdDisplayType) value));
+ }
+
+ public void setLibraries(com.android.sdklib.repositoryv2.meta.DetailsTypes.AddonDetailsType.Libraries value) {
+ setLibrariesInternal(((LibrariesType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/ExtraDetailsType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/ExtraDetailsType.java
new file mode 100644
index 0000000..86e44c9
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/ExtraDetailsType.java
@@ -0,0 +1,88 @@
+
+package com.android.sdklib.repositoryv2.generated.addon.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.impl.generated.v1.TypeDetails;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.generated.common.v1.IdDisplayType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-addon-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * type-details subclass containing vendor information.
+ *
+ *
+ * <p>Java class for extraDetailsType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="extraDetailsType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/repository/android/common/01}typeDetails">
+ * <all>
+ * <element name="vendor" type="{http://schemas.android.com/sdk/android/repo/common/01}idDisplayType"/>
+ * </all>
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "extraDetailsType", propOrder = {
+ "vendor"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class ExtraDetailsType
+ extends TypeDetails
+ implements com.android.sdklib.repositoryv2.meta.DetailsTypes.ExtraDetailsType
+{
+
+ @XmlElement(required = true)
+ protected IdDisplayType vendor;
+
+ /**
+ * Gets the value of the vendor property.
+ *
+ * @return
+ * possible object is
+ * {@link IdDisplayType }
+ *
+ */
+ public IdDisplayType getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Sets the value of the vendor property.
+ *
+ * @param value
+ * allowed object is
+ * {@link IdDisplayType }
+ *
+ */
+ public void setVendorInternal(IdDisplayType value) {
+ this.vendor = value;
+ }
+
+ public void setVendor(IdDisplay value) {
+ setVendorInternal(((IdDisplayType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/LibrariesType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/LibrariesType.java
new file mode 100644
index 0000000..c643dea
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/LibrariesType.java
@@ -0,0 +1,93 @@
+
+package com.android.sdklib.repositoryv2.generated.addon.v1;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.sdklib.repositoryv2.generated.common.v1.LibraryType;
+import com.android.sdklib.repositoryv2.meta.Library;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-addon-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * libraries provided by this addon
+ *
+ *
+ * <p>Java class for librariesType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="librariesType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="library" type="{http://schemas.android.com/sdk/android/repo/common/01}libraryType" maxOccurs="unbounded"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "librariesType", propOrder = {
+ "library"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class LibrariesType
+ extends com.android.sdklib.repositoryv2.meta.DetailsTypes.AddonDetailsType.Libraries
+{
+
+ @XmlElement(required = true)
+ protected List<LibraryType> library;
+
+ /**
+ * Gets the value of the library property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the library property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getLibrary().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link LibraryType }
+ *
+ *
+ */
+ public List<LibraryType> getLibraryInternal() {
+ if (library == null) {
+ library = new ArrayList<LibraryType>();
+ }
+ return this.library;
+ }
+
+ public List<Library> getLibrary() {
+ return ((List) getLibraryInternal());
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/MavenType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/MavenType.java
new file mode 100644
index 0000000..851f2df
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/MavenType.java
@@ -0,0 +1,84 @@
+
+package com.android.sdklib.repositoryv2.generated.addon.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.impl.generated.v1.TypeDetails;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.generated.common.v1.IdDisplayType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-addon-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * <p>Java class for mavenType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="mavenType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/repository/android/common/01}typeDetails">
+ * <all>
+ * <element name="vendor" type="{http://schemas.android.com/sdk/android/repo/common/01}idDisplayType"/>
+ * </all>
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "mavenType", propOrder = {
+ "vendor"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class MavenType
+ extends TypeDetails
+ implements com.android.sdklib.repositoryv2.meta.DetailsTypes.MavenType
+{
+
+ @XmlElement(required = true)
+ protected IdDisplayType vendor;
+
+ /**
+ * Gets the value of the vendor property.
+ *
+ * @return
+ * possible object is
+ * {@link IdDisplayType }
+ *
+ */
+ public IdDisplayType getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Sets the value of the vendor property.
+ *
+ * @param value
+ * allowed object is
+ * {@link IdDisplayType }
+ *
+ */
+ public void setVendorInternal(IdDisplayType value) {
+ this.vendor = value;
+ }
+
+ public void setVendor(IdDisplay value) {
+ setVendorInternal(((IdDisplayType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/ObjectFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/ObjectFactory.java
new file mode 100644
index 0000000..fdbc2bd
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/ObjectFactory.java
@@ -0,0 +1,91 @@
+
+package com.android.sdklib.repositoryv2.generated.addon.v1;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.api.Repository;
+import com.android.repository.impl.generated.v1.RepositoryType;
+import com.android.sdklib.repositoryv2.meta.AddonFactory;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-addon-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.sdklib.repositoryv2.generated.addon.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory
+ extends AddonFactory
+{
+
+ private final static QName _SdkAddon_QNAME = new QName("http://schemas.android.com/sdk/android/repo/addon2/01", "sdk-addon");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.sdklib.repositoryv2.generated.addon.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link AddonDetailsType }
+ *
+ */
+ public AddonDetailsType createAddonDetailsType() {
+ return new AddonDetailsType();
+ }
+
+ /**
+ * Create an instance of {@link LibrariesType }
+ *
+ */
+ public LibrariesType createLibrariesType() {
+ return new LibrariesType();
+ }
+
+ /**
+ * Create an instance of {@link ExtraDetailsType }
+ *
+ */
+ public ExtraDetailsType createExtraDetailsType() {
+ return new ExtraDetailsType();
+ }
+
+ /**
+ * Create an instance of {@link MavenType }
+ *
+ */
+ public MavenType createMavenType() {
+ return new MavenType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link RepositoryType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/sdk/android/repo/addon2/01", name = "sdk-addon")
+ public JAXBElement<RepositoryType> createSdkAddonInternal(RepositoryType value) {
+ return new JAXBElement<RepositoryType>(_SdkAddon_QNAME, RepositoryType.class, null, value);
+ }
+
+ public JAXBElement<Repository> generateSdkAddon(Repository value) {
+ return ((JAXBElement) createSdkAddonInternal(((RepositoryType) value)));
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/package-info.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/package-info.java
new file mode 100644
index 0000000..5cc2070
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/addon/v1/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-addon-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/sdk/android/repo/addon2/01")
+package com.android.sdklib.repositoryv2.generated.addon.v1;
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/ApiDetailsType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/ApiDetailsType.java
new file mode 100644
index 0000000..996eaea
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/ApiDetailsType.java
@@ -0,0 +1,98 @@
+
+package com.android.sdklib.repositoryv2.generated.common.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.impl.generated.v1.TypeDetails;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * Abstract subclass of type-details adding elements to specify the android version
+ * a package corresponds to.
+ *
+ *
+ * <p>Java class for apiDetailsType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="apiDetailsType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/repository/android/common/01}typeDetails">
+ * <sequence>
+ * <element name="api-level" type="{http://www.w3.org/2001/XMLSchema}int"/>
+ * <element name="codename" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ * </sequence>
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "apiDetailsType", propOrder = {
+ "apiLevel",
+ "codename"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public abstract class ApiDetailsType
+ extends TypeDetails
+ implements com.android.sdklib.repositoryv2.meta.DetailsTypes.ApiDetailsType
+{
+
+ @XmlElement(name = "api-level")
+ protected int apiLevel;
+ protected String codename;
+
+ /**
+ * Gets the value of the apiLevel property.
+ *
+ */
+ public int getApiLevel() {
+ return apiLevel;
+ }
+
+ /**
+ * Sets the value of the apiLevel property.
+ *
+ */
+ public void setApiLevel(int value) {
+ this.apiLevel = value;
+ }
+
+ /**
+ * Gets the value of the codename property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getCodename() {
+ return codename;
+ }
+
+ /**
+ * Sets the value of the codename property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setCodename(String value) {
+ this.codename = value;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/IdDisplayType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/IdDisplayType.java
new file mode 100644
index 0000000..bbb1e52
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/IdDisplayType.java
@@ -0,0 +1,117 @@
+
+package com.android.sdklib.repositoryv2.generated.common.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A string with both user-friendly and easily-parsed versions.
+ *
+ *
+ * <p>Java class for idDisplayType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="idDisplayType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="id" type="{http://schemas.android.com/sdk/android/repo/common/01}idType"/>
+ * <element name="display" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </all>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "idDisplayType", propOrder = {
+
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class IdDisplayType
+ extends IdDisplay
+{
+
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlSchemaType(name = "token")
+ protected String id;
+ @XmlElement(required = true)
+ protected String display;
+
+ /**
+ * Gets the value of the id property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the id property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setId(String value) {
+ this.id = value;
+ }
+
+ /**
+ * Gets the value of the display property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getDisplay() {
+ return display;
+ }
+
+ /**
+ * Sets the value of the display property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setDisplay(String value) {
+ this.display = value;
+ }
+
+ public boolean isValidId(String value) {
+ return ((value != null)&&(value.matches("^[a-zA-Z0-9_-]+$")));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/LibraryType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/LibraryType.java
new file mode 100644
index 0000000..52019c9
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/LibraryType.java
@@ -0,0 +1,167 @@
+
+package com.android.sdklib.repositoryv2.generated.common.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.sdklib.repositoryv2.meta.Library;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * a library provided by this addon
+ *
+ *
+ * <p>Java class for libraryType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="libraryType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="description" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </sequence>
+ * <attribute name="localJarPath" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="manifestEntryRequired" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "libraryType", propOrder = {
+ "description"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class LibraryType
+ extends Library
+{
+
+ @XmlElement(required = true)
+ protected String description;
+ @XmlAttribute(name = "localJarPath")
+ protected String localJarPath;
+ @XmlAttribute(name = "name", required = true)
+ protected String name;
+ @XmlAttribute(name = "manifestEntryRequired")
+ protected Boolean manifestEntryRequired;
+
+ /**
+ * Gets the value of the description property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the value of the description property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setDescription(String value) {
+ this.description = value;
+ }
+
+ /**
+ * Gets the value of the localJarPath property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getLocalJarPath() {
+ return localJarPath;
+ }
+
+ /**
+ * Sets the value of the localJarPath property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setLocalJarPath(String value) {
+ this.localJarPath = value;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ /**
+ * Gets the value of the manifestEntryRequired property.
+ *
+ * @return
+ * possible object is
+ * {@link Boolean }
+ *
+ */
+ public boolean isManifestEntryRequired() {
+ if (manifestEntryRequired == null) {
+ return true;
+ } else {
+ return manifestEntryRequired;
+ }
+ }
+
+ /**
+ * Sets the value of the manifestEntryRequired property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Boolean }
+ *
+ */
+ public void setManifestEntryRequired(Boolean value) {
+ this.manifestEntryRequired = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/ObjectFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/ObjectFactory.java
new file mode 100644
index 0000000..298507f
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/ObjectFactory.java
@@ -0,0 +1,56 @@
+
+package com.android.sdklib.repositoryv2.generated.common.v1;
+
+import javax.xml.bind.annotation.XmlRegistry;
+import com.android.sdklib.repositoryv2.meta.SdkCommonFactory;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.sdklib.repositoryv2.generated.common.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory
+ extends SdkCommonFactory
+{
+
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.sdklib.repositoryv2.generated.common.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link IdDisplayType }
+ *
+ */
+ public IdDisplayType createIdDisplayType() {
+ return new IdDisplayType();
+ }
+
+ /**
+ * Create an instance of {@link LibraryType }
+ *
+ */
+ public LibraryType createLibraryType() {
+ return new LibraryType();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/package-info.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/package-info.java
new file mode 100644
index 0000000..ea807d6
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/common/v1/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-common-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/sdk/android/repo/common/01")
+package com.android.sdklib.repositoryv2.generated.common.v1;
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/LayoutlibType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/LayoutlibType.java
new file mode 100644
index 0000000..6af6872
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/LayoutlibType.java
@@ -0,0 +1,68 @@
+
+package com.android.sdklib.repositoryv2.generated.repository.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-repository-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * The API level used by the LayoutLib included in a platform to communicate with the IDE.
+ *
+ *
+ * <p>Java class for layoutlibType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="layoutlibType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="api" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "layoutlibType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class LayoutlibType
+ extends com.android.sdklib.repositoryv2.meta.DetailsTypes.PlatformDetailsType.LayoutlibType
+{
+
+ @XmlAttribute(name = "api", required = true)
+ protected int api;
+
+ /**
+ * Gets the value of the api property.
+ *
+ */
+ public int getApi() {
+ return api;
+ }
+
+ /**
+ * Sets the value of the api property.
+ *
+ */
+ public void setApi(int value) {
+ this.api = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/ObjectFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/ObjectFactory.java
new file mode 100644
index 0000000..b3f1d3a
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/ObjectFactory.java
@@ -0,0 +1,83 @@
+
+package com.android.sdklib.repositoryv2.generated.repository.v1;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.api.Repository;
+import com.android.repository.impl.generated.v1.RepositoryType;
+import com.android.sdklib.repositoryv2.meta.RepoFactory;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-repository-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.sdklib.repositoryv2.generated.repository.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory
+ extends RepoFactory
+{
+
+ private final static QName _SdkRepository_QNAME = new QName("http://schemas.android.com/sdk/android/repo/repository2/01", "sdk-repository");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.sdklib.repositoryv2.generated.repository.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link PlatformDetailsType }
+ *
+ */
+ public PlatformDetailsType createPlatformDetailsType() {
+ return new PlatformDetailsType();
+ }
+
+ /**
+ * Create an instance of {@link LayoutlibType }
+ *
+ */
+ public LayoutlibType createLayoutlibType() {
+ return new LayoutlibType();
+ }
+
+ /**
+ * Create an instance of {@link SourceDetailsType }
+ *
+ */
+ public SourceDetailsType createSourceDetailsType() {
+ return new SourceDetailsType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link RepositoryType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/sdk/android/repo/repository2/01", name = "sdk-repository")
+ public JAXBElement<RepositoryType> generateSdkRepositoryInternal(RepositoryType value) {
+ return new JAXBElement<RepositoryType>(_SdkRepository_QNAME, RepositoryType.class, null, value);
+ }
+
+ public JAXBElement<Repository> generateSdkRepository(Repository value) {
+ return ((JAXBElement) generateSdkRepositoryInternal(((RepositoryType) value)));
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/PlatformDetailsType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/PlatformDetailsType.java
new file mode 100644
index 0000000..2941088
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/PlatformDetailsType.java
@@ -0,0 +1,87 @@
+
+package com.android.sdklib.repositoryv2.generated.repository.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.sdklib.repositoryv2.generated.common.v1.ApiDetailsType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-repository-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * type-details subclass for platform components, including information on the
+ * layoutlib provided.
+ *
+ *
+ * <p>Java class for platformDetailsType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="platformDetailsType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/sdk/android/repo/common/01}apiDetailsType">
+ * <sequence>
+ * <element name="layoutlib" type="{http://schemas.android.com/sdk/android/repo/repository2/01}layoutlibType"/>
+ * </sequence>
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "platformDetailsType", propOrder = {
+ "layoutlib"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class PlatformDetailsType
+ extends ApiDetailsType
+ implements com.android.sdklib.repositoryv2.meta.DetailsTypes.PlatformDetailsType
+{
+
+ @XmlElement(required = true)
+ protected com.android.sdklib.repositoryv2.generated.repository.v1.LayoutlibType layoutlib;
+
+ /**
+ * Gets the value of the layoutlib property.
+ *
+ * @return
+ * possible object is
+ * {@link com.android.sdklib.repositoryv2.generated.repository.v1.LayoutlibType }
+ *
+ */
+ public com.android.sdklib.repositoryv2.generated.repository.v1.LayoutlibType getLayoutlib() {
+ return layoutlib;
+ }
+
+ /**
+ * Sets the value of the layoutlib property.
+ *
+ * @param value
+ * allowed object is
+ * {@link com.android.sdklib.repositoryv2.generated.repository.v1.LayoutlibType }
+ *
+ */
+ public void setLayoutlibInternal(com.android.sdklib.repositoryv2.generated.repository.v1.LayoutlibType value) {
+ this.layoutlib = value;
+ }
+
+ public void setLayoutlib(com.android.sdklib.repositoryv2.meta.DetailsTypes.PlatformDetailsType.LayoutlibType value) {
+ setLayoutlibInternal(((com.android.sdklib.repositoryv2.generated.repository.v1.LayoutlibType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/SourceDetailsType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/SourceDetailsType.java
new file mode 100644
index 0000000..e317251
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/SourceDetailsType.java
@@ -0,0 +1,50 @@
+
+package com.android.sdklib.repositoryv2.generated.repository.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.sdklib.repositoryv2.generated.common.v1.ApiDetailsType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-repository-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * trivial type-details subclass for source components.
+ *
+ *
+ * <p>Java class for sourceDetailsType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="sourceDetailsType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/sdk/android/repo/common/01}apiDetailsType">
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "sourceDetailsType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class SourceDetailsType
+ extends ApiDetailsType
+ implements com.android.sdklib.repositoryv2.meta.DetailsTypes.SourceDetailsType
+{
+
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/package-info.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/package-info.java
new file mode 100644
index 0000000..b347919
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/repository/v1/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-repository-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/sdk/android/repo/repository2/01")
+package com.android.sdklib.repositoryv2.generated.repository.v1;
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/ObjectFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/ObjectFactory.java
new file mode 100644
index 0000000..2fbaa66
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/ObjectFactory.java
@@ -0,0 +1,67 @@
+
+package com.android.sdklib.repositoryv2.generated.sysimg.v1;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.api.Repository;
+import com.android.repository.impl.generated.v1.RepositoryType;
+import com.android.sdklib.repositoryv2.meta.SysImgFactory;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sys-img-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.sdklib.repositoryv2.generated.sysimg.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory
+ extends SysImgFactory
+{
+
+ private final static QName _SdkSysImg_QNAME = new QName("http://schemas.android.com/sdk/android/repo/sys-img2/01", "sdk-sys-img");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.sdklib.repositoryv2.generated.sysimg.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link SysImgDetailsType }
+ *
+ */
+ public SysImgDetailsType createSysImgDetailsType() {
+ return new SysImgDetailsType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link RepositoryType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/sdk/android/repo/sys-img2/01", name = "sdk-sys-img")
+ public JAXBElement<RepositoryType> createSdkSysImgInternal(RepositoryType value) {
+ return new JAXBElement<RepositoryType>(_SdkSysImg_QNAME, RepositoryType.class, null, value);
+ }
+
+ public JAXBElement<Repository> generateSdkSysImg(Repository value) {
+ return ((JAXBElement) createSdkSysImgInternal(((RepositoryType) value)));
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/SysImgDetailsType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/SysImgDetailsType.java
new file mode 100644
index 0000000..40eacc2
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/SysImgDetailsType.java
@@ -0,0 +1,154 @@
+
+package com.android.sdklib.repositoryv2.generated.sysimg.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.generated.common.v1.ApiDetailsType;
+import com.android.sdklib.repositoryv2.generated.common.v1.IdDisplayType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sys-img-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * type-details subclass including system image-specific information:
+ * - tag, specifying the device type (tablet, tv, wear, etc.)
+ * - vendor, the vendor for this system image (android, google, etc.)
+ * - abi, the architecture for this image (x86, armeabi-v7a, etc.)
+ *
+ *
+ * <p>Java class for sysImgDetailsType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="sysImgDetailsType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/sdk/android/repo/common/01}apiDetailsType">
+ * <sequence>
+ * <element name="tag" type="{http://schemas.android.com/sdk/android/repo/common/01}idDisplayType"/>
+ * <element name="vendor" type="{http://schemas.android.com/sdk/android/repo/common/01}idDisplayType" minOccurs="0"/>
+ * <element name="abi" type="{http://schemas.android.com/sdk/android/repo/sys-img2/01}abiType"/>
+ * </sequence>
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "sysImgDetailsType", propOrder = {
+ "tag",
+ "vendor",
+ "abi"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class SysImgDetailsType
+ extends ApiDetailsType
+ implements com.android.sdklib.repositoryv2.meta.DetailsTypes.SysImgDetailsType
+{
+
+ @XmlElement(required = true)
+ protected IdDisplayType tag;
+ protected IdDisplayType vendor;
+ @XmlElement(required = true)
+ protected String abi;
+
+ /**
+ * Gets the value of the tag property.
+ *
+ * @return
+ * possible object is
+ * {@link IdDisplayType }
+ *
+ */
+ public IdDisplayType getTag() {
+ return tag;
+ }
+
+ /**
+ * Sets the value of the tag property.
+ *
+ * @param value
+ * allowed object is
+ * {@link IdDisplayType }
+ *
+ */
+ public void setTagInternal(IdDisplayType value) {
+ this.tag = value;
+ }
+
+ /**
+ * Gets the value of the vendor property.
+ *
+ * @return
+ * possible object is
+ * {@link IdDisplayType }
+ *
+ */
+ public IdDisplayType getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Sets the value of the vendor property.
+ *
+ * @param value
+ * allowed object is
+ * {@link IdDisplayType }
+ *
+ */
+ public void setVendorInternal(IdDisplayType value) {
+ this.vendor = value;
+ }
+
+ /**
+ * Gets the value of the abi property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getAbi() {
+ return abi;
+ }
+
+ /**
+ * Sets the value of the abi property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setAbi(String value) {
+ this.abi = value;
+ }
+
+ public boolean isValidAbi(String value) {
+ return ((value != null)&&(value.matches("^armeabi|armeabi-v7a|arm64-v8a|x86|x86_64|mips|mips64$")));
+ }
+
+ public void setTag(IdDisplay value) {
+ setTagInternal(((IdDisplayType) value));
+ }
+
+ public void setVendor(IdDisplay value) {
+ setVendorInternal(((IdDisplayType) value));
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/package-info.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/package-info.java
new file mode 100644
index 0000000..4acd22b
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/generated/sysimg/v1/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sys-img-01.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/sdk/android/repo/sys-img2/01")
+package com.android.sdklib.repositoryv2.generated.sysimg.v1;
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/AddonFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/AddonFactory.java
new file mode 100644
index 0000000..289b9ba
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/AddonFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.sdklib.repositoryv2.meta;
+
+import com.android.annotations.NonNull;
+import com.android.repository.impl.meta.TypeDetails;
+
+/**
+ * Parent class for {@code ObjectFactories} created by xjc from sdk-addon-XX.xsd, for
+ * creating addon-schema-specific {@link TypeDetails}.
+ */
+public abstract class AddonFactory {
+ @NonNull
+ public abstract DetailsTypes.ExtraDetailsType createExtraDetailsType();
+
+ @NonNull
+ public abstract DetailsTypes.AddonDetailsType createAddonDetailsType();
+
+ @NonNull
+ public abstract DetailsTypes.AddonDetailsType.Libraries createLibrariesType();
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/DetailsTypes.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/DetailsTypes.java
new file mode 100644
index 0000000..8feb097
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/DetailsTypes.java
@@ -0,0 +1,319 @@
+/*
+ * 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.sdklib.repositoryv2.meta;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.google.common.primitives.Ints;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Container for the subclasses of {@link TypeDetails} used by the android SDK.
+ * Concrete classes are generated by xjc.
+ */
+public final class DetailsTypes {
+
+ private DetailsTypes() {
+ }
+
+ /**
+ * Common methods shared by all android version-specific details types.
+ */
+ public interface ApiDetailsType {
+
+ /**
+ * Sets the api level this package corresponds to.
+ */
+ void setApiLevel(int apiLevel);
+
+ /**
+ * Gets the api level of this package.
+ */
+ int getApiLevel();
+
+ /**
+ * If this is a preview release the api is identified by a codename in addition to the api
+ * level. In this case {@code codename} should be non-null.
+ */
+ void setCodename(@Nullable String codename);
+
+ /**
+ * Gets the codename of this release. Should be {@code null} for regular releases, and non-
+ * null for preview releases.
+ */
+ String getCodename();
+ }
+
+ /**
+ * Trivial details type for source packages.
+ */
+ @XmlTransient
+ public interface SourceDetailsType extends ApiDetailsType {
+
+ }
+
+ /**
+ * Details type for platform packages. Contains info on the layout lib version provided.
+ */
+ @XmlTransient
+ public interface PlatformDetailsType extends ApiDetailsType {
+
+ void setLayoutlib(@NonNull LayoutlibType layoutLib);
+
+ @NonNull
+ LayoutlibType getLayoutlib();
+
+ /**
+ * Parent class for xjc-generated classes containing info on the layout lib version.
+ */
+ @XmlTransient
+ abstract class LayoutlibType {
+
+ /**
+ * Sets the layout lib api level.
+ */
+ public abstract void setApi(int api);
+
+ /**
+ * Gets the layout lib api level.
+ */
+ public abstract int getApi();
+ }
+ }
+
+ /**
+ * Details type for extra packages. Includes a {@link IdDisplay} for the vendor.
+ */
+ @XmlTransient
+ public interface ExtraDetailsType {
+
+ /**
+ * Sets the vendor for this package.
+ */
+ void setVendor(@NonNull IdDisplay vendor);
+
+ /**
+ * Gets the vendor for this package.
+ */
+ @NonNull
+ IdDisplay getVendor();
+ }
+
+ /**
+ * Details type for addon packages. Includes a {@link IdDisplay} for the vendor.
+ */
+ @XmlTransient
+ public interface AddonDetailsType extends ApiDetailsType {
+
+ void setVendor(@NonNull IdDisplay vendor);
+
+ @NonNull
+ IdDisplay getVendor();
+
+ /**
+ * Gets the {@link IAndroidTarget.OptionalLibrary}s provided by this package.
+ */
+ @Nullable
+ Libraries getLibraries();
+
+ /**
+ * Gets the {@link IAndroidTarget.OptionalLibrary}s provided by this package.
+ */
+ void setLibraries(@Nullable Libraries libraries);
+
+ /**
+ * Sets the tag for this package. Used to match addon packages with corresponding system
+ * images.
+ */
+ void setTag(@NonNull IdDisplay tag);
+
+ /**
+ * Gets the tag for this package. Used to match addon packages with corresponding system
+ * images.
+ */
+ @NonNull
+ IdDisplay getTag();
+
+ /**
+ * Gets the default skin included in this package.
+ */
+ @Nullable
+ String getDefaultSkin();
+
+ /**
+ * List of all {@link Library}s included in this package.
+ */
+ abstract class Libraries {
+
+ @NonNull
+ public abstract List<Library> getLibrary();
+ }
+
+ }
+
+ /**
+ * Details type for system images packages. Includes information on the abi (architecture), tag
+ * (device type), and vendor.
+ */
+ @XmlTransient
+ public interface SysImgDetailsType extends ApiDetailsType {
+
+ /**
+ * Sets the abi type (x86, armeabi-v7a, etc.) for this package.
+ */
+ void setAbi(@NonNull String abi);
+
+ /**
+ * Gets the abi type (x86, armeabi-v7a, etc.) for this package.
+ */
+ @NonNull
+ String getAbi();
+
+ /**
+ * Checks whether {@code value} is a valid abi type.
+ */
+ boolean isValidAbi(@Nullable String value);
+
+ /**
+ * Sets the tag for this package. Used to match addon packages with corresponding system
+ * images.
+ */
+ void setTag(@NonNull IdDisplay tag);
+
+ /**
+ * Sets the tag for this package. Used to match addon packages with corresponding system
+ * images.
+ */
+ @NonNull
+ IdDisplay getTag();
+
+ /**
+ * Sets the vendor of this package.
+ */
+ void setVendor(@Nullable IdDisplay vendor);
+
+ /**
+ * Gets the vendor of this package.
+ */
+ @Nullable
+ IdDisplay getVendor();
+ }
+
+ /**
+ * Details type for packages that will be installed as maven artifacts in our local maven
+ * repository.
+ */
+ @XmlTransient
+ public interface MavenType {
+
+ }
+
+ /**
+ * Convenience method to create an {@link AndroidVersion} with the information from the given
+ * {@link ApiDetailsType}.
+ *
+ * TODO: move this into ApiDetailsType when we support java 8
+ */
+ @NonNull
+ public static AndroidVersion getAndroidVersion(@NonNull ApiDetailsType details) {
+ return new AndroidVersion(details.getApiLevel(), details.getCodename());
+ }
+
+ /**
+ * Gets the path/unique id for the platform of the given {@link AndroidVersion}.
+ *
+ * TODO: move this into PlatformDetailsType when we support java 8
+ */
+ public static String getPlatformPath(AndroidVersion version) {
+ return SdkConstants.FD_PLATFORMS + RepoPackage.PATH_SEPARATOR + "android-" +
+ version.getApiString();
+ }
+
+ /**
+ * Gets the path/unique id for the sources of the given {@link AndroidVersion}.
+ *
+ * TODO: move this into PlatformDetailsType when we support java 8
+ */
+ public static String getSourcesPath(AndroidVersion version) {
+ return SdkConstants.FD_PKG_SOURCES + RepoPackage.PATH_SEPARATOR + "android-" +
+ version.getApiString();
+ }
+
+ /**
+ * Gets the path/unique id for the LLDB of the given {@link Revision}.
+ */
+ public static String getLldbPath(Revision revision) {
+ return SdkConstants.FD_LLDB + RepoPackage.PATH_SEPARATOR + revision.getMajor() + "."
+ + revision.getMinor();
+ }
+
+ /**
+ * Gets the default path/unique id for the given addon
+ *
+ * TODO: move this into AddonDetailsType when we support java 8
+ */
+ public static String getAddonPath(IdDisplay vendor, AndroidVersion version, IdDisplay name) {
+ return new StringBuilder().append(SdkConstants.FD_ADDONS)
+ .append(RepoPackage.PATH_SEPARATOR)
+ .append("addon-")
+ .append(name.getId())
+ .append("-")
+ .append(vendor.getId())
+ .append("-")
+ .append(version.getApiString())
+ .toString();
+ }
+
+ /**
+ * Gets the default path/unique id for the given system image
+ *
+ * TODO: move this into SysImgDetailsType when we support java 8
+ */
+ public static String getSysImgPath(IdDisplay vendor, AndroidVersion version, IdDisplay name,
+ String abi) {
+ return new StringBuilder()
+ .append(SdkConstants.FD_SYSTEM_IMAGES)
+ .append(RepoPackage.PATH_SEPARATOR)
+ .append("android-")
+ .append(version.getApiString())
+ .append(RepoPackage.PATH_SEPARATOR)
+ .append(name.getId())
+ .append(RepoPackage.PATH_SEPARATOR)
+ .append(abi)
+ .toString();
+ }
+
+ /**
+ * Gets the default path/unique id for the given build tools
+ */
+ public static String getBuildToolsPath(Revision revision) {
+ String revisionStr = Ints.join(".", revision.toIntArray(false)) +
+ (revision.isPreview() ? "-preview" : "");
+ return SdkConstants.FD_BUILD_TOOLS + RepoPackage.PATH_SEPARATOR + revisionStr;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/Library.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/Library.java
new file mode 100644
index 0000000..ff5514b
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/Library.java
@@ -0,0 +1,110 @@
+/*
+ * 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.sdklib.repositoryv2.meta;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.IAndroidTarget;
+import com.google.common.base.Objects;
+
+import java.io.File;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Information about a {@link IAndroidTarget.OptionalLibrary} provided by a package.
+ */
+ at XmlTransient
+public abstract class Library implements IAndroidTarget.OptionalLibrary {
+
+ /**
+ * Reference to the path of the containing package.
+ */
+ @XmlTransient
+ private File mPackagePath;
+
+ /**
+ * Sets the path of the containing package. Must be called before calling {@link #getJar()}.
+ */
+ public void setPackagePath(@NonNull File packagePath) {
+ mPackagePath = packagePath;
+ }
+
+ /**
+ * Absolute path to the library jar file. Will be {@code null} when a legacy remote package
+ * is installed.
+ */
+ @Override
+ @Nullable
+ public File getJar() {
+ assert mPackagePath != null;
+ String localPath = getLocalJarPath();
+ if (localPath == null) {
+ return null;
+ }
+ localPath = localPath.replace('/', File.separatorChar);
+ return new File(mPackagePath, SdkConstants.OS_ADDON_LIBS_FOLDER + localPath);
+ }
+
+ /**
+ * The name of the library.
+ */
+ @Override
+ @NonNull
+ public abstract String getName();
+
+ /**
+ * User-friendly description of the library.
+ */
+ @Override
+ @NonNull
+ public abstract String getDescription();
+
+ /**
+ * Whether a manifest entry is required for this library.
+ */
+ @Override
+ public abstract boolean isManifestEntryRequired();
+
+ public abstract void setLocalJarPath(String path);
+
+ public abstract void setDescription(String description);
+
+ public abstract void setName(String name);
+
+ public boolean equals(Object o) {
+ if (!(o instanceof IAndroidTarget.OptionalLibrary)) {
+ return false;
+ }
+ IAndroidTarget.OptionalLibrary lib = (IAndroidTarget.OptionalLibrary)o;
+ return Objects.equal(lib.getLocalJarPath(), getLocalJarPath()) && lib.getName()
+ .equals(getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getLocalJarPath(), getName());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("OptionalLibrary[name=\"%1$s\" description=\"%2$s\" jar=\"%3$s\"]",
+ getName(), getDescription(), getLocalJarPath());
+ }
+
+ public abstract void setManifestEntryRequired(Boolean b);
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/RepoFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/RepoFactory.java
new file mode 100644
index 0000000..a40145d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/RepoFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repositoryv2.meta;
+
+import com.android.annotations.NonNull;
+import com.android.repository.impl.meta.TypeDetails;
+
+/**
+ * Parent class for {@code ObjectFactories} created by xjc from sdk-repository-XX.xsd, for
+ * creating repository-schema-specific {@link TypeDetails} and associated classes.
+ */
+public abstract class RepoFactory {
+
+ /**
+ * Create an instance of {@link DetailsTypes.SourceDetailsType }
+ */
+ @NonNull
+ public abstract DetailsTypes.SourceDetailsType createSourceDetailsType();
+
+ /**
+ * Create an instance of {@link DetailsTypes.PlatformDetailsType }
+ */
+ @NonNull
+ public abstract DetailsTypes.PlatformDetailsType createPlatformDetailsType();
+
+ /**
+ * Create an instance of {@link DetailsTypes.PlatformDetailsType.LayoutlibType }
+ */
+ @NonNull
+ public abstract DetailsTypes.PlatformDetailsType.LayoutlibType createLayoutlibType();
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/SdkCommonFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/SdkCommonFactory.java
new file mode 100644
index 0000000..fdacf9a
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/SdkCommonFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repositoryv2.meta;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+import java.io.File;
+
+/**
+ * Parent class for {@code ObjectFactories} created by xjc from sdk-common-XX.xsd, for
+ * creating sdk-specific types shared by multiple concrete schemas.
+ */
+ at SuppressWarnings("MethodMayBeStatic")
+public abstract class SdkCommonFactory {
+
+ /**
+ * Create a new {@link IdDisplay}.
+ */
+ @NonNull
+ public abstract IdDisplay createIdDisplayType();
+
+ /**
+ * Create a new {@link Library};
+ */
+ @NonNull
+ public abstract Library createLibraryType();
+
+ /**
+ * Convenience to create and initialize a {@link Library}.
+ */
+ public Library createLibraryType(
+ @NonNull String libraryName,
+ @NonNull String jarPath,
+ @NonNull String description,
+ @NonNull File packagePath,
+ boolean requireManifestEntry) {
+ Library result = createLibraryType();
+ result.setName(libraryName);
+ result.setLocalJarPath(jarPath);
+ result.setDescription(description);
+ result.setManifestEntryRequired(requireManifestEntry);
+ result.setPackagePath(packagePath);
+ return result;
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/SysImgFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/SysImgFactory.java
new file mode 100644
index 0000000..4593c00
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/meta/SysImgFactory.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repositoryv2.meta;
+
+import com.android.annotations.NonNull;
+import com.android.repository.impl.meta.TypeDetails;
+
+/**
+ * Parent class for {@code ObjectFactories} created by xjc from sdk-sys-img-XX.xsd, for
+ * creating system image-schema-specific {@link TypeDetails}.
+ */
+public abstract class SysImgFactory {
+
+ @NonNull
+ public abstract DetailsTypes.SysImgDetailsType createSysImgDetailsType();
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-addon-01.xsd b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-addon-01.xsd
new file mode 100755
index 0000000..87f849d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-addon-01.xsd
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Extension to repo-common for addon repositories, containing the sdk-addon root element and
+ the addon and extra typedetails subclasses.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -catalog <tools/base path>/repository/src/main/java/com/android/repository/api/catalog.xml \
+ -b <tools/base path>/repository/src/main/java/com/android/repository/api/common.xjb \
+ -b com/android/sdklib/repositoryv2/sdk-common.xjb \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -p com.android.sdklib.repositoryv2.generated.addon.v1 \
+ com/android/sdklib/repositoryv2/sdk-addon-01.xsd \
+ -extension -Xandroid-inheritance -no-header
+
+ with tools.jar, jaxb-inheritance-plugin.jar, and the repository classes on the classpath.
+ Note that you can not use the xjc executable, as it does not support 3rd-party plugins.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repo/addon2/01"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repo/addon2/01"
+ xmlns:common="http://schemas.android.com/repository/android/common/01"
+ xmlns:sdk-common="http://schemas.android.com/sdk/android/repo/common/01"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <xsd:import namespace="http://schemas.android.com/repository/android/common/01"/>
+ <xsd:import namespace="http://schemas.android.com/sdk/android/repo/common/01" schemaLocation="sdk-common-01.xsd"/>
+
+ <!-- The root element for addon repos -->
+ <xsd:element name="sdk-addon" type="common:repositoryType"/>
+
+ <xsd:annotation>
+ <xsd:documentation>
+ Customization specifying the superclass of ObjectFactory.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.AddonFactory"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+
+ <xsd:complexType name="addonDetailsType">
+ <xsd:annotation>
+ <xsd:documentation>
+ type-details subclass containing api level and vendor information.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$AddonDetailsType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="sdk-common:apiDetailsType">
+ <xsd:sequence>
+ <xsd:element name="vendor" type="sdk-common:idDisplayType" minOccurs="1" maxOccurs="1"/>
+ <xsd:element name="tag" type="sdk-common:idDisplayType" minOccurs="1" maxOccurs="1"/>
+ <xsd:element name="default-skin" type="xsd:string" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="libraries" type="sdk:librariesType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="librariesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ libraries provided by this addon
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$AddonDetailsType$Libraries"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="library" type="sdk-common:libraryType" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="extraDetailsType">
+ <xsd:annotation>
+ <xsd:documentation>
+ type-details subclass containing vendor information.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$ExtraDetailsType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="common:typeDetails">
+ <xsd:all>
+ <xsd:element name="vendor" type="sdk-common:idDisplayType"/>
+ </xsd:all>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="mavenType">
+ <xsd:annotation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$MavenType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="common:typeDetails">
+ <xsd:all>
+ <xsd:element name="vendor" type="sdk-common:idDisplayType"/>
+ </xsd:all>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common-01.xsd b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common-01.xsd
new file mode 100644
index 0000000..7dbae15
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common-01.xsd
@@ -0,0 +1,123 @@
+<!--
+ ~ 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.
+ -->
+
+<!--
+ Extension to repo-common containing common information shared by the android sdk-specific
+ schemas.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -catalog <tools/base path>/repository/src/main/java/com/android/repository/api/catalog.xml \
+ -b <tools/base path>/repository/src/main/java/com/android/repository/api/common.xjb \
+ -episode com/android/sdklib/repositoryv2/sdk-common.xjb
+ -p com.android.sdklib.repositoryv2.generated.common.v1 \
+ com/android/sdklib/repositoryv2/sdk-common-01.xsd \
+ -extension -Xandroid-inheritance \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -b sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common-custom.xjb \
+ -no-header
+
+ with tools.jar, jaxb-inheritance-plugin.jar, and the repository classes on the classpath.
+ Note that you can not use the xjc executable, as it does not support 3rd-party plugins.
+-->
+
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repo/common/01"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repo/common/01"
+ xmlns:common="http://schemas.android.com/repository/android/common/01"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <xsd:import namespace="http://schemas.android.com/repository/android/common/01"/>
+
+ <xsd:annotation>
+ <xsd:documentation>
+ Customization specifying the superclass of ObjectFactory.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.SdkCommonFactory"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+
+ <xsd:complexType name="apiDetailsType" abstract="true">
+ <xsd:annotation>
+ <xsd:documentation>
+ Abstract subclass of type-details adding elements to specify the android version
+ a package corresponds to.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="common:typeDetails">
+ <xsd:sequence>
+ <!-- api revision number for released android versions -->
+ <xsd:element name="api-level" type="xsd:int"/>
+ <!-- codename for pre-release android versions -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Simple type enforcing restrictions on machine-readable strings.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="idDisplayType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A string with both user-friendly and easily-parsed versions.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <xsd:element name="id" type="sdk:idType"/>
+ <xsd:element name="display" type="xsd:string"/>
+ </xsd:all>
+ </xsd:complexType>
+
+ <xsd:complexType name="libraryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ a library provided by this addon
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="description" type="xsd:string" minOccurs="1"/>
+ </xsd:sequence>
+ <!-- Path to the library jar file relative to the libs directory in the package.-->
+ <!-- Note that this should be required, but since it's absent in the legacy XML we don't
+ have a way to populate it when a legacy xml is installed. -->
+ <xsd:attribute name="localJarPath" type="xsd:string"/>
+ <!-- Name of this library -->
+ <xsd:attribute name="name" use="required" type="xsd:string"/>
+ <!-- Whether a manifest file entry is required for this library -->
+ <xsd:attribute name="manifestEntryRequired" use="optional" default="true"
+ type="xsd:boolean"/>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common-custom.xjb b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common-custom.xjb
new file mode 100644
index 0000000..7beb7c6
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common-custom.xjb
@@ -0,0 +1,34 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<bindings version="2.1"
+ xmlns="http://java.sun.com/xml/ns/jaxb"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ extensionBindingPrefixes="plugin">
+ <bindings schemaLocation="sdk-common-01.xsd" node="/xsd:schema">
+ <bindings node="//xsd:complexType[@name='apiDetailsType']">
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$ApiDetailsType"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='idDisplayType']">
+ <plugin:super name="com.android.sdklib.repositoryv2.IdDisplay"/>
+ </bindings>
+ <bindings node="//xsd:complexType[@name='libraryType']">
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.Library"/>
+ </bindings>
+ </bindings>
+</bindings>
\ No newline at end of file
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common.xjb b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common.xjb
new file mode 100644
index 0000000..5ba6f7d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-common.xjb
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb">
+ <!--
+
+This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.11
+See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+Any modifications to this file will be lost upon recompilation of the source schema.
+Generated on: 2015.12.29 at 03:58:42 PM PST
+
+ -->
+ <bindings scd="x-schema::tns" xmlns:tns="http://schemas.android.com/sdk/android/repo/common/01">
+ <schemaBindings map="false">
+ <package name="com.android.sdklib.repositoryv2.generated.common.v1"/>
+ </schemaBindings>
+ <bindings scd="~tns:apiDetailsType">
+ <class ref="com.android.sdklib.repositoryv2.generated.common.v1.ApiDetailsType"/>
+ </bindings>
+ <bindings scd="~tns:idDisplayType">
+ <class ref="com.android.sdklib.repositoryv2.generated.common.v1.IdDisplayType"/>
+ </bindings>
+ <bindings scd="~tns:libraryType">
+ <class ref="com.android.sdklib.repositoryv2.generated.common.v1.LibraryType"/>
+ </bindings>
+ </bindings>
+</bindings>
+
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-repository-01.xsd b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-repository-01.xsd
new file mode 100755
index 0000000..4dc2ae6
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-repository-01.xsd
@@ -0,0 +1,113 @@
+<?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.
+-->
+
+<!--
+ Extension to repo-common for the primary repository, containing the repository root element and
+ the typedetails subclasses for platforms and sources, and associated classes.
+ Note that repo XMLs can also contain generic type-details, and so that schema should be included
+ as well when unmarshalling core repository XMLs.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -catalog <tools/base path>/repository/src/main/java/com/android/repository/api/catalog.xml \
+ -b <tools/base path>/repository/src/main/java/com/android/repository/api/common.xjb \
+ -b com/android/sdklib/repositoryv2/sdk-common.xjb \
+ -b <tools/base path>/repository/src/main/java/com/android/repository/api/generic.xjb \
+ -p com.android.sdklib.repositoryv2.generated.repository.v1 \
+ com/android/sdklib/repositoryv2/sdk-repository-01.xsd \
+ -extension -Xandroid-inheritance \
+ -b repository/src/main/java/com/android/repository/api/global.xjb -no-header
+
+ with tools.jar, jaxb-inheritance-plugin.jar, and the repository classes on the classpath.
+ Note that you can not use the xjc executable, as it does not support 3rd-party plugins.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repo/repository2/01"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repo/repository2/01"
+ xmlns:common="http://schemas.android.com/repository/android/common/01"
+ xmlns:sdk-common="http://schemas.android.com/sdk/android/repo/common/01"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <xsd:import namespace="http://schemas.android.com/repository/android/common/01"/>
+ <xsd:import namespace="http://schemas.android.com/sdk/android/repo/common/01" schemaLocation="sdk-common-01.xsd"/>
+
+ <!-- the root element for primary repositories -->
+ <xsd:element name="sdk-repository" type="common:repositoryType"/>
+
+ <xsd:annotation>
+ <xsd:documentation>
+ Customization specifying the superclass of ObjectFactory.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.RepoFactory"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+
+ <xsd:complexType name="platformDetailsType">
+ <xsd:annotation>
+ <xsd:documentation>
+ type-details subclass for platform components, including information on the
+ layoutlib provided.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$PlatformDetailsType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="sdk-common:apiDetailsType">
+ <xsd:sequence>
+ <!-- The layoutlib version provided by this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="layoutlibType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The API level used by the LayoutLib included in a platform to communicate with the IDE.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$PlatformDetailsType$LayoutlibType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:attribute name="api" type="xsd:int" use="required"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="sourceDetailsType">
+ <xsd:annotation>
+ <xsd:documentation>
+ trivial type-details subclass for source components.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$SourceDetailsType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="sdk-common:apiDetailsType"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-sys-img-01.xsd b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-sys-img-01.xsd
new file mode 100755
index 0000000..bb1e56d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sdk-sys-img-01.xsd
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Extension to repo-common for system image repositories, containing the sdk-sys-img root element
+ and the sys-img typedetails subclass.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -catalog <tools/base path>/repository/src/main/java/com/android/repository/api/catalog.xml \
+ -b <tools/base path>/repository/src/main/java/com/android/repository/api/common.xjb \
+ -b com/android/sdklib/repositoryv2/sdk-common.xjb \
+ -p com.android.sdklib.repositoryv2.generated.sysimg.v1 \
+ com/android/sdklib/repositoryv2/sdk-sys-img-01.xsd \
+ -extension -Xandroid-inheritance \
+ -b repository/src/main/java/com/android/repository/api/global.xjb -no-header
+
+ with tools.jar, jaxb-inheritance-plugin.jar, and the repository classes on the classpath.
+ Note that you can not use the xjc executable, as it does not support 3rd-party plugins.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repo/sys-img2/01"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repo/sys-img2/01"
+ xmlns:common="http://schemas.android.com/repository/android/common/01"
+ xmlns:sdk-common="http://schemas.android.com/sdk/android/repo/common/01"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ version="1"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ jaxb:version="2.0">
+
+ <xsd:import namespace="http://schemas.android.com/repository/android/common/01"/>
+ <xsd:import namespace="http://schemas.android.com/sdk/android/repo/common/01" schemaLocation="sdk-common-01.xsd"/>
+
+ <!-- the root element for system image repositories -->
+ <xsd:element name="sdk-sys-img" type="common:repositoryType"/>
+
+ <xsd:annotation>
+ <xsd:documentation>
+ Customization specifying the superclass of ObjectFactory.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.SysImgFactory"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+
+ <xsd:complexType name="sysImgDetailsType">
+ <xsd:annotation>
+ <xsd:documentation>
+ type-details subclass including system image-specific information:
+ - tag, specifying the device type (tablet, tv, wear, etc.)
+ - vendor, the vendor for this system image (android, google, etc.)
+ - abi, the architecture for this image (x86, armeabi-v7a, etc.)
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.meta.DetailsTypes$SysImgDetailsType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="sdk-common:apiDetailsType">
+ <xsd:sequence>
+ <xsd:element name="tag" type="sdk-common:idDisplayType"/>
+ <xsd:element name="vendor" type="sdk-common:idDisplayType" minOccurs="0"/>
+ <xsd:element name="abi" type="sdk:abiType"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The ABI of a platform's system image.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="armeabi|armeabi-v7a|arm64-v8a|x86|x86_64|mips|mips64"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/RemoteSiteType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/RemoteSiteType.java
new file mode 100644
index 0000000..f2f3ffa
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/RemoteSiteType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repositoryv2.sources;
+
+import com.android.repository.api.RepositorySource;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Container for marker interfaces for the different {@link RepositorySource} types that can be
+ * included in the site list retrieved by {@link AndroidSdkHandler#mAddonsListSourceProvider}.
+ */
+public final class RemoteSiteType {
+
+ private RemoteSiteType() {}
+
+ @XmlTransient public interface AddonSiteType extends RepositorySource {}
+ @XmlTransient public interface SysImgSiteType extends RepositorySource {}
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/AddonSiteType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/AddonSiteType.java
new file mode 100644
index 0000000..12dec33
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/AddonSiteType.java
@@ -0,0 +1,112 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v1;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.api.RemoteSource;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * An SDK add-on site.
+ *
+ * <p>Java class for addonSiteType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="addonSiteType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="url" type="{http://www.w3.org/2001/XMLSchema}token"/>
+ * <element name="name" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </all>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "addonSiteType", propOrder = {
+
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class AddonSiteType
+ extends RemoteSource
+ implements com.android.sdklib.repositoryv2.sources.RemoteSiteType.AddonSiteType
+{
+
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlSchemaType(name = "token")
+ protected String url;
+ @XmlElement(required = true)
+ protected String name;
+
+ /**
+ * Gets the value of the url property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Sets the value of the url property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setUrl(String value) {
+ this.url = value;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/AddonsListType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/AddonsListType.java
new file mode 100644
index 0000000..4f95d33
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/AddonsListType.java
@@ -0,0 +1,92 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v1;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.api.RemoteSource;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A simple list of add-ons sites.
+ *
+ *
+ * <p>Java class for addonsListType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="addonsListType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <choice maxOccurs="unbounded" minOccurs="0">
+ * <element name="addon-site" type="{http://schemas.android.com/sdk/android/addons-list/1}addonSiteType"/>
+ * </choice>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "addonsListType", propOrder = {
+ "addonSite"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class AddonsListType
+ extends com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList
+{
+
+ @XmlElement(name = "addon-site")
+ protected List<AddonSiteType> addonSite;
+
+ /**
+ * Gets the value of the addonSite property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the addonSite property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getAddonSite().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link AddonSiteType }
+ *
+ *
+ */
+ public List<AddonSiteType> getAddonSiteInternal() {
+ if (addonSite == null) {
+ addonSite = new ArrayList<AddonSiteType>();
+ }
+ return this.addonSite;
+ }
+
+ public List<RemoteSource> getAddonSite() {
+ return ((List) getAddonSiteInternal());
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/ObjectFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/ObjectFactory.java
new file mode 100644
index 0000000..217c780
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/ObjectFactory.java
@@ -0,0 +1,71 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v1;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.api.RemoteSource;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.sdklib.repositoryv2.sources.generated.v1 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory {
+
+ private final static QName _SdkAddonsList_QNAME = new QName("http://schemas.android.com/sdk/android/addons-list/1", "sdk-addons-list");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.sdklib.repositoryv2.sources.generated.v1
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link AddonsListType }
+ *
+ */
+ public com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList createAddonsListType() {
+ return new AddonsListType();
+ }
+
+ /**
+ * Create an instance of {@link AddonSiteType }
+ *
+ */
+ public RemoteSource createAddonSiteType() {
+ return new AddonSiteType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link AddonsListType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/sdk/android/addons-list/1", name = "sdk-addons-list")
+ public JAXBElement<AddonsListType> createSdkAddonsList(AddonsListType value) {
+ return new JAXBElement<AddonsListType>(_SdkAddonsList_QNAME, AddonsListType.class, null, value);
+ }
+
+ public JAXBElement<com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList> generateElement(com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList value) {
+ return ((JAXBElement) createSdkAddonsList(((AddonsListType) value)));
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/package-info.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/package-info.java
new file mode 100644
index 0000000..2f23a87
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v1/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-1.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/sdk/android/addons-list/1", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
+package com.android.sdklib.repositoryv2.sources.generated.v1;
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/AddonSiteType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/AddonSiteType.java
new file mode 100644
index 0000000..cefeb27
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/AddonSiteType.java
@@ -0,0 +1,112 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v2;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.api.RemoteSource;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-2.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * An SDK add-on site.
+ *
+ * <p>Java class for addonSiteType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="addonSiteType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="url" type="{http://www.w3.org/2001/XMLSchema}token"/>
+ * <element name="name" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </all>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "addonSiteType", propOrder = {
+
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class AddonSiteType
+ extends RemoteSource
+ implements com.android.sdklib.repositoryv2.sources.RemoteSiteType.AddonSiteType
+{
+
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlSchemaType(name = "token")
+ protected String url;
+ @XmlElement(required = true)
+ protected String name;
+
+ /**
+ * Gets the value of the url property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Sets the value of the url property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setUrl(String value) {
+ this.url = value;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/AddonsListType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/AddonsListType.java
new file mode 100644
index 0000000..51bcede
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/AddonsListType.java
@@ -0,0 +1,93 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v2;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-2.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * A simple list of add-on and system image sites.
+ *
+ *
+ * <p>Java class for addonsListType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="addonsListType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <choice maxOccurs="unbounded" minOccurs="0">
+ * <element name="addon-site" type="{http://schemas.android.com/sdk/android/addons-list/2}addonSiteType"/>
+ * <element name="sys-img-site" type="{http://schemas.android.com/sdk/android/addons-list/2}sysImgSiteType"/>
+ * </choice>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "addonsListType", propOrder = {
+ "addonSiteOrSysImgSite"
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class AddonsListType
+ extends com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList
+{
+
+ @XmlElements({
+ @XmlElement(name = "addon-site", type = AddonSiteType.class),
+ @XmlElement(name = "sys-img-site", type = SysImgSiteType.class)
+ })
+ protected List<Object> addonSiteOrSysImgSite;
+
+ /**
+ * Gets the value of the addonSiteOrSysImgSite property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the addonSiteOrSysImgSite property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getAddonSiteOrSysImgSite().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link AddonSiteType }
+ * {@link SysImgSiteType }
+ *
+ *
+ */
+ public List<Object> getAddonSiteOrSysImgSite() {
+ if (addonSiteOrSysImgSite == null) {
+ addonSiteOrSysImgSite = new ArrayList<Object>();
+ }
+ return this.addonSiteOrSysImgSite;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/ObjectFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/ObjectFactory.java
new file mode 100644
index 0000000..090f12c
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/ObjectFactory.java
@@ -0,0 +1,79 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v2;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.api.RemoteSource;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-2.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.sdklib.repositoryv2.sources.generated.v2 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory {
+
+ private final static QName _SdkAddonsList_QNAME = new QName("http://schemas.android.com/sdk/android/addons-list/2", "sdk-addons-list");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.sdklib.repositoryv2.sources.generated.v2
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link AddonsListType }
+ *
+ */
+ public com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList createAddonsListType() {
+ return new AddonsListType();
+ }
+
+ /**
+ * Create an instance of {@link AddonSiteType }
+ *
+ */
+ public RemoteSource createAddonSiteType() {
+ return new AddonSiteType();
+ }
+
+ /**
+ * Create an instance of {@link SysImgSiteType }
+ *
+ */
+ public RemoteSource createSysImgSiteType() {
+ return new SysImgSiteType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link AddonsListType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/sdk/android/addons-list/2", name = "sdk-addons-list")
+ public JAXBElement<AddonsListType> createSdkAddonsList(AddonsListType value) {
+ return new JAXBElement<AddonsListType>(_SdkAddonsList_QNAME, AddonsListType.class, null, value);
+ }
+
+ public JAXBElement<com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList> generateElement(com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList value) {
+ return ((JAXBElement) createSdkAddonsList(((AddonsListType) value)));
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/SysImgSiteType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/SysImgSiteType.java
new file mode 100644
index 0000000..714a2bc
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/SysImgSiteType.java
@@ -0,0 +1,112 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v2;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import com.android.repository.api.RemoteSource;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-2.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * An SDK sys-img site.
+ *
+ * <p>Java class for sysImgSiteType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="sysImgSiteType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <all>
+ * <element name="url" type="{http://www.w3.org/2001/XMLSchema}token"/>
+ * <element name="name" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </all>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "sysImgSiteType", propOrder = {
+
+})
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class SysImgSiteType
+ extends RemoteSource
+ implements com.android.sdklib.repositoryv2.sources.RemoteSiteType.SysImgSiteType
+{
+
+ @XmlElement(required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlSchemaType(name = "token")
+ protected String url;
+ @XmlElement(required = true)
+ protected String name;
+
+ /**
+ * Gets the value of the url property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Sets the value of the url property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setUrl(String value) {
+ this.url = value;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/package-info.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/package-info.java
new file mode 100644
index 0000000..403e163
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v2/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-2.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/sdk/android/addons-list/2", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
+package com.android.sdklib.repositoryv2.sources.generated.v2;
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/AddonSiteType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/AddonSiteType.java
new file mode 100644
index 0000000..a3f6991
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/AddonSiteType.java
@@ -0,0 +1,50 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v3;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.impl.sources.generated.v1.SiteType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-3.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * Trivial siteType extension specifying that this is a addon site
+ *
+ *
+ * <p>Java class for addonSiteType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="addonSiteType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/repository/android/sites-common/1}siteType">
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "addonSiteType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class AddonSiteType
+ extends SiteType
+ implements com.android.sdklib.repositoryv2.sources.RemoteSiteType.AddonSiteType
+{
+
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/ObjectFactory.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/ObjectFactory.java
new file mode 100644
index 0000000..20546c3
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/ObjectFactory.java
@@ -0,0 +1,71 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v3;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+import com.android.repository.impl.sources.generated.v1.SiteListType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-3.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.android.sdklib.repositoryv2.sources.generated.v3 package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+ at XmlRegistry
+ at SuppressWarnings("override")
+public class ObjectFactory {
+
+ private final static QName _SdkAddonsList_QNAME = new QName("http://schemas.android.com/sdk/android/addons-list/3", "sdk-addons-list");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.android.sdklib.repositoryv2.sources.generated.v3
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link AddonSiteType }
+ *
+ */
+ public AddonSiteType createAddonSiteType() {
+ return new AddonSiteType();
+ }
+
+ /**
+ * Create an instance of {@link SysImgSiteType }
+ *
+ */
+ public SysImgSiteType createSysImgSiteType() {
+ return new SysImgSiteType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link SiteListType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://schemas.android.com/sdk/android/addons-list/3", name = "sdk-addons-list")
+ public JAXBElement<SiteListType> createSdkAddonsList(SiteListType value) {
+ return new JAXBElement<SiteListType>(_SdkAddonsList_QNAME, SiteListType.class, null, value);
+ }
+
+ public JAXBElement<com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList> generateElement(com.android.repository.impl.sources.RemoteListSourceProviderImpl.SiteList value) {
+ return ((JAXBElement) createSdkAddonsList(((SiteListType) value)));
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/SysImgSiteType.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/SysImgSiteType.java
new file mode 100644
index 0000000..9b9eded
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/SysImgSiteType.java
@@ -0,0 +1,50 @@
+
+package com.android.sdklib.repositoryv2.sources.generated.v3;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+import com.android.repository.impl.sources.generated.v1.SiteType;
+
+
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-3.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ *
+ * Trivial siteType extension specifying that this is a system image site
+ *
+ *
+ * <p>Java class for sysImgSiteType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="sysImgSiteType">
+ * <complexContent>
+ * <extension base="{http://schemas.android.com/repository/android/sites-common/1}siteType">
+ * </extension>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+ at XmlAccessorType(XmlAccessType.FIELD)
+ at XmlType(name = "sysImgSiteType")
+ at SuppressWarnings({
+ "override",
+ "unchecked"
+})
+public class SysImgSiteType
+ extends SiteType
+ implements com.android.sdklib.repositoryv2.sources.RemoteSiteType.SysImgSiteType
+{
+
+
+ public ObjectFactory createFactory() {
+ return new ObjectFactory();
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/package-info.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/package-info.java
new file mode 100644
index 0000000..47b4860
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/generated/v3/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * DO NOT EDIT
+ * This file was generated by xjc from sdk-sites-list-3.xsd. Any changes will be lost upon recompilation of the schema.
+ * See the schema file for instructions on running xjc.
+ *
+ */
+ at javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.android.com/sdk/android/addons-list/3")
+package com.android.sdklib.repositoryv2.sources.generated.v3;
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-1.xsd b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-1.xsd
new file mode 100644
index 0000000..d6a2aeb
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-1.xsd
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Schema for the list of sites loaded by RemoteListSourceProvider, version 1. Includes only
+ addon sites. For backward compatibility.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -p com.android.sdklib.repositoryv2.sources.generated.v1 \
+ com/android/sdklib/repositoryv2/sources/sdk-sites-list-1.xsd \
+ -extension -Xandroid-inheritance \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -no-header
+
+ with tools.jar, and jaxb-inheritance-plugin.jar on the classpath.
+ Note that you can not use the xjc executable, as it does not support 3rd-party plugins.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/addons-list/1"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sa1="http://schemas.android.com/sdk/android/addons-list/1"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <!-- root element for v1 -->
+ <xsd:element name="sdk-addons-list" type="sa1:addonsListType" />
+
+ <xsd:complexType name="addonsListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A simple list of add-ons sites.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.repository.impl.sources.RemoteListSourceProviderImpl$SiteList"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="addon-site" type="sa1:addonSiteType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <xsd:complexType name="addonSiteType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK add-on site.</xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.sources.RemoteSiteType$AddonSiteType"/>
+ <plugin:super name="com.android.repository.api.RemoteSource"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The URL of the site.
+
+ This can be either the exact URL of the an XML resource conforming
+ to the latest sdk-addon-N.xsd schema, or it can be the URL of a
+ 'directory', in which case the manager will look for a resource
+ named 'addon.xml' at this location.
+
+ Examples:
+ http://www.example.com/android/my_addons.xml
+ or
+ http://www.example.com/android/
+
+ In the second example, the manager will actually look for:
+ http://www.example.com/android/addon.xml
+ -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <!-- The UI-visible name of the add-on. -->
+ <xsd:element name="name" type="xsd:string" />
+ </xsd:all>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-2.xsd b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-2.xsd
new file mode 100644
index 0000000..82069c9
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-2.xsd
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Schema for the list of sites loaded by RemoteListSourceProvider, version 2. Includes addon
+ and system image sites. For backward compatibility.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -p com.android.sdklib.repositoryv2.sources.generated.v2 \
+ com/android/sdklib/repositoryv2/sources/sdk-sites-list-2.xsd \
+ -extension -Xandroid-inheritance \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -no-header
+
+ with tools.jar, and jaxb-inheritance-plugin.jar on the classpath.
+ Note that you can not use the xjc executable, as it does not support 3rd-party plugins.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/addons-list/2"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sa1="http://schemas.android.com/sdk/android/addons-list/2"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <!-- root element for v2 -->
+ <xsd:element name="sdk-addons-list" type="sa1:addonsListType" />
+
+ <xsd:complexType name="addonsListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A simple list of add-on and system image sites.
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.repository.impl.sources.RemoteListSourceProviderImpl$SiteList"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="addon-site" type="sa1:addonSiteType" />
+ <xsd:element name="sys-img-site" type="sa1:sysImgSiteType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <xsd:complexType name="addonSiteType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK add-on site.</xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.sources.RemoteSiteType$AddonSiteType"/>
+ <plugin:super name="com.android.repository.api.RemoteSource"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The URL of the site.
+
+ This can be either the exact URL of the an XML resource conforming
+ to the latest sdk-addon-N.xsd schema, or it can be the URL of a
+ 'directory', in which case the manager will look for a resource
+ named 'addon.xml' at this location.
+
+ Examples:
+ http://www.example.com/android/my_addons.xml
+ or
+ http://www.example.com/android/
+
+ In the second example, the manager will actually look for:
+ http://www.example.com/android/addon.xml
+ -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <!-- The UI-visible name of the add-on site. -->
+ <xsd:element name="name" type="xsd:string" />
+
+ </xsd:all>
+ </xsd:complexType>
+
+ <xsd:complexType name="sysImgSiteType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK sys-img site.</xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.sources.RemoteSiteType$SysImgSiteType"/>
+ <plugin:super name="com.android.repository.api.RemoteSource"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The URL of the site.
+
+ This can be either the exact URL of the an XML resource conforming
+ to the latest sdk-sys-img-N.xsd schema, or it can be the URL of a
+ 'directory', in which case the manager will look for a resource
+ named 'sysimg.xml' at this location.
+
+ Examples:
+ http://www.example.com/android/my_sys_img.xml
+ or
+ http://www.example.com/android/
+
+ In the second example, the manager will actually look for:
+ http://www.example.com/android/sysimg.xml
+ -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <!-- The UI-visible name of the sys-img site. -->
+ <xsd:element name="name" type="xsd:string" />
+
+ </xsd:all>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-3.xsd b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-3.xsd
new file mode 100644
index 0000000..002c8f4
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/sources/sdk-sites-list-3.xsd
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Schema for the list of sites loaded by RemoteListSourceProvider.
+ This is the first "proper" concrete version of the sites list, being an extension to the
+ base list schema provided by repository.
+
+ JAXB-usable classes can be generated from this schema as follows:
+ java com.sun.tools.internal.xjc.Driver \
+ -catalog <tools/base path>/repository/src/main/java/com/android/repository/api/catalog.xml \
+ -p com.android.sdklib.repositoryv2.sources.generated.v3 \
+ com/android/sdklib/repositoryv2/sources/sdk-sites-list-3.xsd \
+ -extension -Xandroid-inheritance \
+ -b repository/src/main/java/com/android/repository/api/list-common.xjb \
+ -b repository/src/main/java/com/android/repository/api/global.xjb \
+ -no-header
+
+ with tools.jar and jaxb-inheritance-plugin.jar on the classpath.
+ Note that you cannot use the xjc executable, as it does not support 3rd-party plugins.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/addons-list/3"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sl="http://schemas.android.com/sdk/android/addons-list/3"
+ xmlns:common="http://schemas.android.com/repository/android/sites-common/1"
+ elementFormDefault="unqualified"
+ attributeFormDefault="unqualified"
+ xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+ xmlns:plugin="http://schemas.android.com/android/jaxb/plugin/1"
+ jaxb:extensionBindingPrefixes="plugin"
+ version="1"
+ jaxb:version="2.0">
+
+ <xsd:import namespace="http://schemas.android.com/repository/android/sites-common/1"/>
+
+ <xsd:element name="sdk-addons-list" type="common:siteListType" />
+
+ <xsd:complexType name="addonSiteType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Trivial siteType extension specifying that this is a addon site
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.sources.RemoteSiteType$AddonSiteType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="common:siteType"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:complexType name="sysImgSiteType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Trivial siteType extension specifying that this is a system image site
+ </xsd:documentation>
+ <xsd:appinfo>
+ <plugin:super name="com.android.sdklib.repositoryv2.sources.RemoteSiteType$SysImgSiteType"/>
+ </xsd:appinfo>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="common:siteType"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/AddonTarget.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/AddonTarget.java
new file mode 100644
index 0000000..3e59458
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/AddonTarget.java
@@ -0,0 +1,344 @@
+/*
+ * 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.sdklib.repositoryv2.targets;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.repository.local.PackageParserUtils;
+import com.android.sdklib.repositoryv2.LegacyRepoUtils;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.android.sdklib.repositoryv2.meta.Library;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents an add-on target in the SDK. An add-on extends a standard {@link PlatformTarget}.
+ */
+public class AddonTarget implements IAndroidTarget {
+
+ /**
+ * The {@link LocalPackage} from which this target was created.
+ */
+ private LocalPackage mPackage;
+
+ /**
+ * The {@link TypeDetails} of {@link #mPackage}.
+ */
+ private DetailsTypes.AddonDetailsType mDetails;
+
+ /**
+ * The target on which this addon is based.
+ */
+ private IAndroidTarget mBasePlatform;
+
+ /**
+ * All skins included in this target, including those in this addon, the base package, and
+ * associated system images.
+ */
+ private File[] mSkins;
+
+ /**
+ * The default skin for this package, as (optionally) specified in the package xml.
+ */
+ private File mDefaultSkin;
+
+ private List<OptionalLibrary> mAdditionalLibraries;
+
+ /**
+ * Construct a new {@link AddonTarget}.
+ * @param p The {@link LocalPackage} containing this target.
+ * @param baseTarget The {@link IAndroidTarget} on which this addon is based.
+ * @param sysImgMgr A {@link SystemImageManager}, used to find {@link ISystemImage}s associated
+ * associated with this target.
+ * @param progress
+ * @param fop {@link FileOp} to use for file operations. For normal use should be {@link
+ * FileOpUtils#create()}.
+ */
+ public AddonTarget(@NonNull LocalPackage p, @NonNull IAndroidTarget baseTarget,
+ @NonNull SystemImageManager sysImgMgr, @NonNull ProgressIndicator progress,
+ @NonNull FileOp fop) {
+ mPackage = p;
+ mBasePlatform = baseTarget;
+ TypeDetails details = p.getTypeDetails();
+ assert details instanceof DetailsTypes.AddonDetailsType;
+ mDetails = (DetailsTypes.AddonDetailsType) details;
+
+ // Gather skins for this target. We'll only keep a single skin with each name.
+ Map<String, File> skins = Maps.newHashMap();
+ // Collect skins from the base target. This have precedence over system image skins with the
+ // same name.
+ for (File skin : baseTarget.getSkins()) {
+ skins.put(skin.getName(), skin);
+ }
+ // Finally collect skins from this package itself, which have highest priority.
+ for (File skin : PackageParserUtils
+ .parseSkinFolder(new File(p.getLocation(), SdkConstants.FD_SKINS), fop)) {
+ skins.put(skin.getName(), skin);
+ }
+ mSkins = skins.values().toArray(new File[skins.size()]);
+
+ String defaultSkinName = mDetails.getDefaultSkin();
+ if (defaultSkinName != null) {
+ mDefaultSkin = new File(getPath(SKINS), defaultSkinName);
+ } else {
+ // No default skin name specified, use the first one from the addon
+ // or the default from the platform.
+ if (getSkins().length == 1) {
+ mDefaultSkin = getSkins()[0];
+ } else {
+ mDefaultSkin = mBasePlatform.getDefaultSkin();
+ }
+ }
+
+ mAdditionalLibraries = parseAdditionalLibraries(p, progress, fop);
+ }
+
+ @NonNull
+ private static List<OptionalLibrary> parseAdditionalLibraries(@NonNull LocalPackage p,
+ @NonNull ProgressIndicator progress, @NonNull FileOp fop) {
+ DetailsTypes.AddonDetailsType.Libraries libraries = ((DetailsTypes.AddonDetailsType) p
+ .getTypeDetails()).getLibraries();
+ List<OptionalLibrary> result = Lists.newArrayList();
+ if (libraries != null) {
+ for (Library library : libraries.getLibrary()) {
+ if (library.getLocalJarPath() == null) {
+ // We must be looking at a legacy package. Abort and use the libraries derived
+ // in the old way.
+ return LegacyRepoUtils
+ .parseLegacyAdditionalLibraries(p.getLocation(), progress, fop);
+ }
+ library.setPackagePath(p.getLocation());
+ result.add(library);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public String getLocation() {
+ return mPackage.getLocation().getPath() + File.separator;
+ }
+
+ @Override
+ public String getVendor() {
+ return mDetails.getVendor().getDisplay();
+ }
+
+ @Override
+ public String getName() {
+ return mDetails.getTag().getDisplay();
+ }
+
+ @Override
+ public String getFullName() {
+ return mPackage.getDisplayName();
+ }
+
+ @Override
+ public String getClasspathName() {
+ return String.format("%1$s [%2$s]", getName(), mBasePlatform.getClasspathName());
+ }
+
+ @Override
+ public String getShortClasspathName() {
+ return String.format("%1$s [%2$s]", getName(), mBasePlatform.getVersionName());
+ }
+
+ @Override
+ public String getDescription() {
+ return mPackage.getDisplayName();
+ }
+
+ @NonNull
+ @Override
+ public AndroidVersion getVersion() {
+ return DetailsTypes.getAndroidVersion(mDetails);
+ }
+
+ @Override
+ public String getVersionName() {
+ return mBasePlatform.getVersionName();
+ }
+
+ @Override
+ public int getRevision() {
+ return mPackage.getVersion().getMajor();
+ }
+
+ @Override
+ public boolean isPlatform() {
+ return false;
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return mBasePlatform;
+ }
+
+ @Override
+ public String getPath(int pathId) {
+ String installPath = mPackage.getLocation().getPath();
+ switch (pathId) {
+ case SKINS:
+ return installPath + File.separator + SdkConstants.OS_SKINS_FOLDER;
+ case DOCS:
+ return installPath + File.separator + SdkConstants.FD_DOCS + File.separator
+ + SdkConstants.FD_DOCS_REFERENCE;
+
+ default:
+ return mBasePlatform.getPath(pathId);
+ }
+ }
+
+ @Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ return mBasePlatform.getBuildToolInfo();
+ }
+
+ @NonNull
+ @Override
+ public List<String> getBootClasspath() {
+ return mBasePlatform.getBootClasspath();
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getOptionalLibraries() {
+ return mBasePlatform.getOptionalLibraries();
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getAdditionalLibraries() {
+ return mAdditionalLibraries;
+ }
+
+ @Override
+ public boolean hasRenderingLibrary() {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public File[] getSkins() {
+ return mSkins;
+ }
+
+ @Nullable
+ @Override
+ public File getDefaultSkin() {
+ return mDefaultSkin;
+ }
+
+ @Override
+ public String[] getPlatformLibraries() {
+ return mBasePlatform.getPlatformLibraries();
+ }
+
+ @Override
+ public String getProperty(String name) {
+ return mBasePlatform.getProperty(name);
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return mBasePlatform.getProperties();
+ }
+
+ @Override
+ public boolean canRunOn(IAndroidTarget target) {
+ // basic test
+ if (target == this) {
+ return true;
+ }
+
+ // The receiver is an add-on. There are 2 big use cases: The add-on has libraries
+ // or the add-on doesn't (in which case we consider it a platform).
+ if (getAdditionalLibraries().isEmpty()) {
+ return mBasePlatform.canRunOn(target);
+ } else {
+ // the only targets that can run the receiver are the same add-on in the same or later
+ // versions.
+ // first check: vendor/name
+ if (!getVendor().equals(target.getVendor()) || !getName().equals(target.getName())) {
+ return false;
+ }
+
+ // now check the version. At this point since we checked the add-on part,
+ // we can revert to the basic check on version/codename which are done by the
+ // base platform already.
+ return mBasePlatform.canRunOn(target);
+ }
+ }
+
+ @Override
+ public String hashString() {
+ return String.format(AndroidTargetHash.ADD_ON_FORMAT, getVendor(), getName(),
+ mBasePlatform.getVersion().getApiString());
+ }
+
+ @Override
+ public int compareTo(@NonNull IAndroidTarget target) {
+ // quick check.
+ if (this == target) {
+ return 0;
+ }
+
+ int versionDiff = getVersion().compareTo(target.getVersion());
+
+ // only if the versions are the same do we care about platform/add-ons.
+ if (versionDiff == 0) {
+ // platforms go before add-ons.
+ if (target.isPlatform()) {
+ return +1;
+ } else {
+ AddonTarget targetAddOn = (AddonTarget) target;
+
+ // both are add-ons of the same version. Compare per vendor then by name
+ int vendorDiff = getVendor().compareTo(targetAddOn.getVendor());
+ if (vendorDiff == 0) {
+ return getName().compareTo(targetAddOn.getName());
+ } else {
+ return vendorDiff;
+ }
+ }
+
+ }
+
+ return versionDiff;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/AndroidTargetManager.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/AndroidTargetManager.java
new file mode 100644
index 0000000..5aef357
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/AndroidTargetManager.java
@@ -0,0 +1,160 @@
+/*
+ * 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.sdklib.repositoryv2.targets;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.api.RepoManager;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Maps;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * Finds and allows access to all {@link IAndroidTarget}s in a given SDK.
+ */
+public class AndroidTargetManager {
+
+ /**
+ * Cache of the {@link IAndroidTarget}s we created from platform and addon packages.
+ */
+ private Map<LocalPackage, IAndroidTarget> mTargets;
+
+ private final FileOp mFop;
+
+ private final AndroidSdkHandler mSdkHandler;
+
+ /**
+ * Map of package paths to errors encountered while loading creating the target.
+ */
+ private Map<String, String> mLoadErrors;
+
+ private Comparator<LocalPackage> TARGET_COMPARATOR;
+
+ /**
+ * Create a manager using the new {@link AndroidSdkHandler}/{@link RepoManager} mechanism for
+ * finding packages.
+ */
+ public AndroidTargetManager(@NonNull AndroidSdkHandler handler, @NonNull FileOp fop) {
+ mSdkHandler = handler;
+ mFop = fop;
+ }
+
+ /**
+ * Returns the targets (platforms & addons) that are available in the SDK, sorted in
+ * ascending order by API level.
+ */
+ @NonNull
+ public Collection<IAndroidTarget> getTargets(@NonNull ProgressIndicator progress) {
+ return getTargetMap(progress).values();
+ }
+
+ @NonNull
+ private Map<LocalPackage, IAndroidTarget> getTargetMap(@NonNull ProgressIndicator progress) {
+ if (mTargets == null) {
+ Map<String, String> newErrors = Maps.newHashMap();
+ TARGET_COMPARATOR = new Comparator<LocalPackage>() {
+ @Override
+ public int compare(LocalPackage o1, LocalPackage o2) {
+ DetailsTypes.ApiDetailsType details1 = (DetailsTypes.ApiDetailsType) o1
+ .getTypeDetails();
+ DetailsTypes.ApiDetailsType details2 = (DetailsTypes.ApiDetailsType) o2
+ .getTypeDetails();
+ AndroidVersion version1 = DetailsTypes.getAndroidVersion(details1);
+ AndroidVersion version2 = DetailsTypes.getAndroidVersion(details2);
+ return ComparisonChain.start()
+ .compare(version1, version2)
+ .compare(o1.getPath(), o2.getPath())
+ .compare(details1.getClass().getName(), details2.getClass().getName())
+ .result();
+ }
+ };
+ Map<LocalPackage, IAndroidTarget> result = Maps.newTreeMap(TARGET_COMPARATOR);
+ RepoManager manager = mSdkHandler.getSdkManager(progress);
+ Map<AndroidVersion, PlatformTarget> platformTargets = Maps.newHashMap();
+ for (LocalPackage p : manager.getPackages().getLocalPackages().values()) {
+ TypeDetails details = p.getTypeDetails();
+ if (details instanceof DetailsTypes.PlatformDetailsType) {
+ try {
+ PlatformTarget target = new PlatformTarget(p, mSdkHandler, mFop, progress);
+ result.put(p, target);
+ platformTargets.put(target.getVersion(), target);
+ } catch (IllegalArgumentException e) {
+ newErrors.put(p.getPath(), e.getMessage());
+ }
+ }
+ }
+ for (LocalPackage p : manager.getPackages().getLocalPackages().values()) {
+ TypeDetails details = p.getTypeDetails();
+ if (details instanceof DetailsTypes.AddonDetailsType) {
+ AndroidVersion addonVersion = DetailsTypes
+ .getAndroidVersion((DetailsTypes.AddonDetailsType) details);
+ PlatformTarget baseTarget = platformTargets.get(addonVersion);
+ if (baseTarget != null) {
+ result.put(p, new AddonTarget(p, baseTarget,
+ mSdkHandler.getSystemImageManager(progress), progress, mFop));
+ }
+ }
+ }
+ mTargets = result;
+ mLoadErrors = newErrors;
+ }
+ return mTargets;
+ }
+
+ /**
+ * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
+ *
+ * @param hash the {@link IAndroidTarget} hash string.
+ * @return The matching {@link IAndroidTarget} or null.
+ */
+ @Nullable
+ public IAndroidTarget getTargetFromHashString(@Nullable String hash,
+ @NonNull ProgressIndicator progress) {
+ if (hash != null) {
+ for (IAndroidTarget target : getTargets(progress)) {
+ if (target != null && hash.equals(AndroidTargetHash.getTargetHashString(target))) {
+ return target;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the error, if any, encountered when error creating a target for a package.
+ */
+ @Nullable
+ public String getErrorForPackage(@NonNull String path) {
+ return mLoadErrors.get(path);
+ }
+
+ @Nullable
+ public IAndroidTarget getTargetFromPackage(@NonNull LocalPackage p, @NonNull ProgressIndicator progress) {
+ return getTargetMap(progress).get(p);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/OptionalLibraryImpl.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/OptionalLibraryImpl.java
new file mode 100644
index 0000000..9b7ea8d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/OptionalLibraryImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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.sdklib.repositoryv2.targets;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.meta.Library;
+import com.google.common.base.Objects;
+
+import java.io.File;
+
+/**
+ * Internal implementation of OptionalLibrary
+ * @deprecated in favor of {@link Library}
+ */
+ at Deprecated
+public class OptionalLibraryImpl implements IAndroidTarget.OptionalLibrary {
+
+ @NonNull
+ private final String mLibraryName;
+ @NonNull
+ private final File mJarFile;
+ @NonNull
+ private final String mDescription;
+ private final boolean mRequireManifestEntry;
+
+ public OptionalLibraryImpl(
+ @NonNull String libraryName,
+ @NonNull File jarFile,
+ @NonNull String description,
+ boolean requireManifestEntry) {
+ mLibraryName = libraryName;
+ mJarFile = jarFile;
+ mDescription = description;
+ mRequireManifestEntry = requireManifestEntry;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mLibraryName;
+ }
+
+ @Override
+ @NonNull
+ public File getJar() {
+ return mJarFile;
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public boolean isManifestEntryRequired() {
+ return mRequireManifestEntry;
+ }
+
+ @Nullable
+ @Override
+ public String getLocalJarPath() {
+ return getJar().getName();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof IAndroidTarget.OptionalLibrary)) {
+ return false;
+ }
+ IAndroidTarget.OptionalLibrary lib = (IAndroidTarget.OptionalLibrary)o;
+ return Objects.equal(lib.getLocalJarPath(), getLocalJarPath()) && lib.getName()
+ .equals(getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getLocalJarPath(), getName());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("OptionalLibrary[name=\"%1$s\" description=\"%2$s\" jar=\"%3$s\"]",
+ getName(), getDescription(), getLocalJarPath());
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/PlatformTarget.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/PlatformTarget.java
new file mode 100644
index 0000000..959bccd
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/PlatformTarget.java
@@ -0,0 +1,434 @@
+/*
+ * 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.sdklib.repositoryv2.targets;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.repository.local.PackageParserUtils;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a platform target in the SDK.
+ */
+public class PlatformTarget implements IAndroidTarget {
+
+ /**
+ * Default vendor for platform targets
+ */
+ public static final String PLATFORM_VENDOR = "Android Open Source Project";
+
+ /**
+ * "Android NN" is the default name for platform targets.
+ */
+ private static final String PLATFORM_NAME = "Android %s";
+
+ /**
+ * "Android NN (Preview)" is the default name for preview platform targets.
+ */
+ private static final String PLATFORM_NAME_PREVIEW = "Android %s (Preview)";
+
+ /**
+ * The {@link LocalPackage} from which this target was created.
+ */
+ private LocalPackage mPackage;
+
+ /**
+ * The {@link TypeDetails} of {@link #mPackage}.
+ */
+ private DetailsTypes.PlatformDetailsType mDetails;
+
+ /**
+ * Additional {@link IAndroidTarget.OptionalLibrary}s provided by this target.
+ */
+ private List<OptionalLibrary> mOptionalLibraries = ImmutableList.of();
+
+ /**
+ * The emulator skins for this target, including those included in the package as well as those
+ * from associated system images.
+ */
+ private Set<File> mSkins;
+
+ /**
+ * Parsed version of the {@code build.prop} file in {@link #mPackage}.
+ */
+ private Map<String, String> mBuildProps;
+
+ /**
+ * Reference to the latest {@link BuildToolInfo}.
+ */
+ private BuildToolInfo mBuildToolInfo;
+
+ /**
+ * Construct a new {@code PlatformTarget} based on the given package.
+ */
+ public PlatformTarget(@NonNull LocalPackage p, @NonNull AndroidSdkHandler sdkHandler,
+ @NonNull FileOp fop, @NonNull ProgressIndicator progress) {
+ mPackage = p;
+ TypeDetails details = p.getTypeDetails();
+ assert details instanceof DetailsTypes.PlatformDetailsType;
+ mDetails = (DetailsTypes.PlatformDetailsType) details;
+
+ File optionalDir = new File(p.getLocation(), "optional");
+ if (optionalDir.isDirectory()) {
+ File optionalJson = new File(optionalDir, "optional.json");
+ if (optionalJson.isFile()) {
+ mOptionalLibraries = getLibsFromJson(optionalJson);
+ }
+ }
+
+ File buildProp = new File(getLocation(), SdkConstants.FN_BUILD_PROP);
+
+ if (!fop.isFile(buildProp)) {
+ String message = "Build properties not found for package " + p.getDisplayName();
+ progress.logWarning(message);
+ throw new IllegalArgumentException(message);
+ }
+
+ try {
+ mBuildProps = ProjectProperties.parsePropertyStream(fop.newFileInputStream(buildProp),
+ buildProp.getPath(), null);
+ } catch (FileNotFoundException ignore) {
+ }
+ if (mBuildProps == null) {
+ mBuildProps = Maps.newHashMap();
+ }
+ mBuildToolInfo = sdkHandler.getLatestBuildTool(progress);
+
+ mSkins = Sets
+ .newTreeSet(PackageParserUtils.parseSkinFolder(getFile(IAndroidTarget.SKINS), fop));
+ }
+
+ /**
+ * Simple struct used by {@link Gson} when parsing the library file.
+ */
+ public static class Library {
+
+ String name;
+
+ String jar;
+
+ boolean manifest;
+ }
+
+ /**
+ * Parses {@link IAndroidTarget.OptionalLibrary}s from the given json file.
+ */
+ @VisibleForTesting
+ @NonNull
+ static List<OptionalLibrary> getLibsFromJson(@NonNull File jsonFile) {
+
+ Gson gson = new Gson();
+
+ try {
+ Type collectionType = new TypeToken<Collection<Library>>() {
+ }.getType();
+ Collection<Library> libs = gson
+ .fromJson(Files.newReader(jsonFile, Charsets.UTF_8), collectionType);
+
+ // convert into the right format.
+ List<OptionalLibrary> optionalLibraries = Lists.newArrayListWithCapacity(libs.size());
+
+ File rootFolder = jsonFile.getParentFile();
+ for (Library lib : libs) {
+ optionalLibraries.add(new OptionalLibraryImpl(
+ lib.name,
+ new File(rootFolder, lib.jar),
+ lib.name,
+ lib.manifest));
+ }
+
+ return optionalLibraries;
+ } catch (FileNotFoundException e) {
+ // shouldn't happen since we've checked the file is here, but can happen in
+ // some cases (too many files open).
+ return Collections.emptyList();
+ }
+ }
+
+
+ @Override
+ public String getLocation() {
+ return mPackage.getLocation().getPath() + File.separator;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For platform, this is always {@link #PLATFORM_VENDOR}
+ */
+ @Override
+ public String getVendor() {
+ return PLATFORM_VENDOR;
+ }
+
+ @Override
+ public String getName() {
+ AndroidVersion version = getVersion();
+ if (version.isPreview()) {
+ return String.format(PLATFORM_NAME_PREVIEW, version);
+ } else {
+ return String.format(PLATFORM_NAME, version);
+ }
+ }
+
+ @Override
+ public String getFullName() {
+ return getName();
+ }
+
+ @Override
+ public String getDescription() {
+ // Unused outside swt
+ return getName();
+ }
+
+ @NonNull
+ @Override
+ public AndroidVersion getVersion() {
+ return new AndroidVersion(mDetails.getApiLevel(), mDetails.getCodename());
+ }
+
+ @Override
+ public String getVersionName() {
+ return SdkVersionInfo.getVersionString(mDetails.getApiLevel());
+ }
+
+ @Override
+ public int getRevision() {
+ return mPackage.getVersion().getMajor();
+ }
+
+ @Override
+ public boolean isPlatform() {
+ return true;
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public String getPath(int pathId) {
+ switch (pathId) {
+ case ANDROID_JAR:
+ return getLocation() + SdkConstants.FN_FRAMEWORK_LIBRARY;
+ case UI_AUTOMATOR_JAR:
+ return getLocation() + SdkConstants.FN_UI_AUTOMATOR_LIBRARY;
+ case SOURCES:
+ return getLocation() + SdkConstants.FD_ANDROID_SOURCES;
+ case ANDROID_AIDL:
+ return getLocation() + SdkConstants.FN_FRAMEWORK_AIDL;
+ case SAMPLES:
+ return getLocation() + SdkConstants.OS_PLATFORM_SAMPLES_FOLDER;
+ case SKINS:
+ return getLocation() + SdkConstants.OS_SKINS_FOLDER;
+ case TEMPLATES:
+ return getLocation() + SdkConstants.OS_PLATFORM_TEMPLATES_FOLDER;
+ case DATA:
+ return getLocation() + SdkConstants.OS_PLATFORM_DATA_FOLDER;
+ case ATTRIBUTES:
+ return getLocation() + SdkConstants.OS_PLATFORM_ATTRS_XML;
+ case MANIFEST_ATTRIBUTES:
+ return getLocation() + SdkConstants.OS_PLATFORM_ATTRS_MANIFEST_XML;
+ case RESOURCES:
+ return getLocation() + SdkConstants.OS_PLATFORM_RESOURCES_FOLDER;
+ case FONTS:
+ return getLocation() + SdkConstants.OS_PLATFORM_FONTS_FOLDER;
+ case LAYOUT_LIB:
+ return getLocation() + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_LAYOUTLIB_JAR;
+ case WIDGETS:
+ return getLocation() + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_WIDGETS;
+ case ACTIONS_ACTIVITY:
+ return getLocation() + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_ACTIONS_ACTIVITY;
+ case ACTIONS_BROADCAST:
+ return getLocation() + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_ACTIONS_BROADCAST;
+ case ACTIONS_SERVICE:
+ return getLocation() + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_ACTIONS_SERVICE;
+ case CATEGORIES:
+ return getLocation() + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_CATEGORIES;
+ case ANT:
+ return getLocation() + SdkConstants.OS_PLATFORM_ANT_FOLDER;
+ default:
+ return getLocation();
+ }
+ }
+
+ @NonNull
+ @Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Nullable
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getBootClasspath() {
+ return ImmutableList.of(getPath(IAndroidTarget.ANDROID_JAR));
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getOptionalLibraries() {
+ return mOptionalLibraries;
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getAdditionalLibraries() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public boolean hasRenderingLibrary() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public File[] getSkins() {
+ return mSkins.toArray(new File[mSkins.size()]);
+ }
+
+ public int getLayoutlibApi() {
+ return mDetails.getLayoutlib().getApi();
+ }
+
+
+ @Nullable
+ @Override
+ public File getDefaultSkin() {
+ // TODO: validate choice to ignore property in sdk.properties
+
+ // only one skin? easy.
+ if (mSkins.size() == 1) {
+ return mSkins.iterator().next();
+ }
+ String skinName;
+ // otherwise try to find a good default.
+ if (getVersion().getApiLevel() >= 11 && getVersion().getApiLevel() <= 13) {
+ skinName = "WXGA";
+ } else if (getVersion().getApiLevel() >= 4) {
+ // at this time, this is the default skin for all older platforms that had 2+ skins.
+ skinName = "WVGA800";
+ } else {
+ skinName = "HVGA"; // this is for 1.5 and earlier.
+ }
+
+ return new File(getFile(IAndroidTarget.SKINS), skinName);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For platforms this is always {@link SdkConstants#ANDROID_TEST_RUNNER_LIB}.
+ */
+ @NonNull
+ @Override
+ public String[] getPlatformLibraries() {
+ return new String[]{SdkConstants.ANDROID_TEST_RUNNER_LIB};
+ }
+
+ @Nullable
+ @Override
+ public String getProperty(@NonNull String name) {
+ return mBuildProps.get(name);
+ }
+
+ @Nullable
+ @Override
+ public Map<String, String> getProperties() {
+ return mBuildProps;
+ }
+
+ @NonNull
+ @Override
+ public String getShortClasspathName() {
+ return getName();
+ }
+
+ @NonNull
+ @Override
+ public String getClasspathName() {
+ return getName();
+ }
+
+ @Override
+ public boolean canRunOn(@NonNull IAndroidTarget target) {
+ if (getVersion().isPreview()) {
+ return target.getVersion().equals(getVersion());
+ }
+ return target.getVersion().getApiLevel() > getVersion().getApiLevel();
+ }
+
+ @NonNull
+ @Override
+ public String hashString() {
+ return AndroidTargetHash.getPlatformHashString(getVersion());
+ }
+
+ @Override
+ public int compareTo(@NonNull IAndroidTarget o) {
+ int res = getVersion().compareTo(o.getVersion());
+ if (res != 0) {
+ return res;
+ }
+ return o.isPlatform() ? 0 : -1;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/SystemImage.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/SystemImage.java
new file mode 100644
index 0000000..18c297c
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/SystemImage.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.sdklib.repositoryv2.targets;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.Revision;
+import com.android.repository.api.RepoPackage;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.google.common.base.Objects;
+
+import java.io.File;
+
+/**
+ * {@link ISystemImage} based on a {@link RepoPackage} (either system image, platform, or addon).
+ */
+public class SystemImage implements ISystemImage {
+ /**
+ * Tag to apply to system images if none other is specified.
+ */
+ public static final IdDisplay DEFAULT_TAG = IdDisplay.create("default", "Default");
+
+ /**
+ * Directory containing the system image.
+ */
+ private final File mLocation;
+
+ /**
+ * Tag of the system image. Used for matching addons and system images, and for filtering.
+ */
+ private final IdDisplay mTag;
+
+ /**
+ * Vendor of the system image.
+ */
+ private final IdDisplay mVendor;
+
+
+ /**
+ * Abi (x86, armeabi-v7a, etc) of the system image.
+ */
+ private final String mAbi;
+
+ /**
+ * Skins contained in this system image, or in the platform/addon it's based on.
+ */
+ private final File[] mSkins;
+
+ /**
+ * Android API level of this system image.
+ */
+ private final AndroidVersion mAndroidVersion;
+
+ /**
+ * {@link RepoPackage} that contains this system image.
+ */
+ private final RepoPackage mPackage;
+
+ public SystemImage(@NonNull File location, @Nullable IdDisplay tag, @Nullable IdDisplay vendor,
+ @NonNull String abi, @NonNull File[] skins,
+ @NonNull RepoPackage pkg) {
+ mLocation = location;
+ mTag = tag;
+ mVendor = vendor;
+ mAbi = abi;
+ mSkins = skins;
+ mPackage = pkg;
+ TypeDetails details = pkg.getTypeDetails();
+ assert details instanceof DetailsTypes.ApiDetailsType;
+ mAndroidVersion = DetailsTypes.getAndroidVersion((DetailsTypes.ApiDetailsType) details);
+ }
+
+ @NonNull
+ @Override
+ public File getLocation() {
+ return mLocation;
+ }
+
+ @NonNull
+ @Override
+ public IdDisplay getTag() {
+ return mTag;
+ }
+
+ @Nullable
+ @Override
+ public IdDisplay getAddonVendor() {
+ return mVendor;
+ }
+
+ @NonNull
+ @Override
+ public String getAbiType() {
+ return mAbi;
+ }
+
+ @NonNull
+ @Override
+ public File[] getSkins() {
+ return mSkins;
+ }
+
+ @Override
+ @NonNull
+ public AndroidVersion getAndroidVersion() {
+ return mAndroidVersion;
+ }
+
+ @NonNull
+ public RepoPackage getPackage() {
+ return mPackage;
+ }
+
+ @Override
+ public int compareTo(ISystemImage o) {
+ int res = getTag().compareTo(o.getTag());
+ if (res != 0) {
+ return res;
+ }
+ res = getAbiType().compareTo(o.getAbiType());
+ if (res != 0) {
+ return res;
+ }
+ if (getAddonVendor() == null ^ o.getAddonVendor() != null) {
+ return getAddonVendor() == null ? -1 : 1;
+ }
+ if (getAddonVendor() != null && o.getAddonVendor() != null) {
+ res = getAddonVendor().compareTo(o.getAddonVendor());
+ if (res != 0) {
+ return res;
+ }
+ }
+ res = getLocation().compareTo(o.getLocation());
+ if (res != 0) {
+ return res;
+ }
+ File[] skins = getSkins();
+ File[] otherSkins = o.getSkins();
+ for (int i = 0; i < skins.length && i < otherSkins.length; i++) {
+ res = skins[i].compareTo(otherSkins[i]);
+ if (res != 0) {
+ return res;
+ }
+ }
+ return skins.length - otherSkins.length;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof SystemImage)) {
+ return false;
+ }
+ return compareTo((SystemImage) o) == 0;
+ }
+
+ public int hashCode() {
+ int hashCode = Objects.hashCode(getTag(), getAbiType(), getAddonVendor(), getLocation());
+ for (File f : getSkins()) {
+ hashCode *= 37;
+ hashCode += f.hashCode();
+ }
+ return hashCode;
+ }
+
+ @NonNull
+ @Override
+ public Revision getRevision() {
+ return mPackage.getVersion();
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/SystemImageManager.java b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/SystemImageManager.java
new file mode 100644
index 0000000..3393c4f
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repositoryv2/targets/SystemImageManager.java
@@ -0,0 +1,235 @@
+package com.android.sdklib.repositoryv2.targets;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.repository.local.PackageParserUtils;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.android.sdklib.repositoryv2.meta.SysImgFactory;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Table;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@code SystemImageManager} finds {@link SystemImage}s in the sdk, using a {@link RepoManager}
+ */
+public class SystemImageManager {
+
+ private final FileOp mFop;
+
+ private final RepoManager mRepoManager;
+
+ /**
+ * Used to validate ABI types.
+ */
+ private final DetailsTypes.SysImgDetailsType mValidator;
+
+ private static final String SYS_IMG_NAME = "system.img";
+
+ /**
+ * How far down the directory hierarchy we'll search for system images (starting from a
+ * package root).
+ */
+ private static final int MAX_DEPTH = 4;
+
+ /**
+ * Map of packages to the images they contain
+ */
+ private Multimap<LocalPackage, SystemImage> mPackageToImage;
+
+ /**
+ * Map of directories containing {@code system.img} files to {@link SystemImage}s.
+ */
+ private Map<File, SystemImage> mPathToImage;
+
+ /**
+ * Map of tag, version, and vendor to set of system image, for convenient lookup.
+ */
+ private Table<IdDisplay, AndroidVersion, Multimap<IdDisplay, SystemImage>> mValuesToImage;
+
+ /**
+ * Create a new {@link SystemImageManager} using the given {@link RepoManager}.<br/> {@code
+ * factory} is used to enable validation.
+ */
+ public SystemImageManager(@NonNull RepoManager mgr, @NonNull SysImgFactory factory,
+ @NonNull FileOp fop) {
+ mFop = fop;
+ mRepoManager = mgr;
+ mValidator = factory.createSysImgDetailsType();
+ }
+
+ /**
+ * Gets all the {@link SystemImage}s.
+ */
+ @NonNull
+ public Collection<SystemImage> getImages() {
+ if (mPackageToImage == null) {
+ init();
+ }
+ return mPackageToImage.values();
+ }
+
+ /**
+ * Gets a map from all our {@link SystemImage}s to their containing {@link LocalPackage}s.
+ */
+ public Multimap<LocalPackage, SystemImage> getImageMap() {
+ if (mPackageToImage == null) {
+ init();
+ }
+ return mPackageToImage;
+ }
+
+ /**
+ * Lookup all the {@link SystemImage} with the given property values.
+ */
+ @NonNull
+ public Collection<SystemImage> lookup(@NonNull IdDisplay tag, @NonNull AndroidVersion version,
+ @Nullable IdDisplay vendor) {
+ if (mValuesToImage == null) {
+ init();
+ }
+ Multimap<IdDisplay, SystemImage> m = mValuesToImage.get(tag, version);
+ return m == null ? ImmutableList.<SystemImage>of() : m.get(vendor);
+ }
+
+ private void init() {
+ Multimap<LocalPackage, SystemImage> images = buildImageMap();
+ Table<IdDisplay, AndroidVersion, Multimap<IdDisplay, SystemImage>> valuesToImage =
+ HashBasedTable.create();
+ Map<File, SystemImage> pathToImages = Maps.newHashMap();
+ for (SystemImage img : images.values()) {
+ IdDisplay vendor = img.getAddonVendor();
+ IdDisplay tag = img.getTag();
+ AndroidVersion version = img.getAndroidVersion();
+ Multimap<IdDisplay, SystemImage> vendorImageMap = valuesToImage.get(tag, version);
+ if (vendorImageMap == null) {
+ vendorImageMap = HashMultimap.create();
+ valuesToImage.put(tag, version, vendorImageMap);
+ }
+ vendorImageMap.put(vendor, img);
+ pathToImages.put(img.getLocation(), img);
+ }
+ mValuesToImage = valuesToImage;
+ mPackageToImage = images;
+ mPathToImage = pathToImages;
+ }
+
+ @NonNull
+ private Multimap<LocalPackage, SystemImage> buildImageMap() {
+ Multimap<LocalPackage, SystemImage> result = HashMultimap.create();
+ Map<AndroidVersion, File> platformSkins = Maps.newHashMap();
+ Collection<? extends LocalPackage> packages =
+ mRepoManager.getPackages().getLocalPackages().values();
+ for (LocalPackage p : packages) {
+ if (p.getTypeDetails() instanceof DetailsTypes.PlatformDetailsType) {
+ File skinDir = new File(p.getLocation(), SdkConstants.FD_SKINS);
+ if (mFop.exists(skinDir)) {
+ platformSkins.put(DetailsTypes.getAndroidVersion(
+ (DetailsTypes.PlatformDetailsType) p.getTypeDetails()), skinDir);
+ }
+ }
+ }
+ for (LocalPackage p : packages) {
+ TypeDetails typeDetails = p.getTypeDetails();
+ if (typeDetails instanceof DetailsTypes.SysImgDetailsType ||
+ typeDetails instanceof DetailsTypes.PlatformDetailsType ||
+ typeDetails instanceof DetailsTypes.AddonDetailsType) {
+ collectImages(p.getLocation(), p, 0, platformSkins, result);
+ }
+ }
+ return result;
+ }
+
+ private void collectImages(File dir, LocalPackage p, int depth,
+ Map<AndroidVersion, File> platformSkins,
+ Multimap<LocalPackage, SystemImage> collector) {
+ for (File f : mFop.listFiles(dir)) {
+ // Instead of just f.getName().equals, we first check f.getPath().endsWith,
+ // because getPath() is a simpler getter whereas getName() computes a new
+ // string on each call
+ if (f.getPath().endsWith(SYS_IMG_NAME) && f.getName().equals(SYS_IMG_NAME)) {
+ collector.put(p, createSysImg(p, dir, platformSkins));
+ }
+ if (depth < MAX_DEPTH && mFop.isDirectory(f)) {
+ String name = f.getName();
+ if (name.equals(SdkConstants.FD_DATA) ||
+ name.equals(SdkConstants.FD_SAMPLES) ||
+ name.equals(SdkConstants.FD_SKINS)) {
+ // Not containers for system images, but have a lot of files
+ continue;
+ }
+ collectImages(f, p, depth + 1, platformSkins, collector);
+ }
+ }
+ }
+
+ private SystemImage createSysImg(LocalPackage p, File dir,
+ Map<AndroidVersion, File> platformSkins) {
+ String containingDir = dir.getName();
+ String abi;
+ TypeDetails details = p.getTypeDetails();
+ AndroidVersion version = null;
+ if (details instanceof DetailsTypes.ApiDetailsType) {
+ version = DetailsTypes.getAndroidVersion((DetailsTypes.ApiDetailsType) details);
+ }
+ if (details instanceof DetailsTypes.SysImgDetailsType) {
+ abi = ((DetailsTypes.SysImgDetailsType) details).getAbi();
+ } else if (mValidator.isValidAbi(containingDir)) {
+ abi = containingDir;
+ } else {
+ abi = SdkConstants.ABI_ARMEABI;
+ }
+
+ IdDisplay tag;
+ IdDisplay vendor = null;
+ if (details instanceof DetailsTypes.AddonDetailsType) {
+ vendor = ((DetailsTypes.AddonDetailsType) details).getVendor();
+ } else if (details instanceof DetailsTypes.SysImgDetailsType) {
+ vendor = ((DetailsTypes.SysImgDetailsType) details).getVendor();
+ }
+
+ if (details instanceof DetailsTypes.SysImgDetailsType) {
+ tag = ((DetailsTypes.SysImgDetailsType) details).getTag();
+ } else if (details instanceof DetailsTypes.AddonDetailsType) {
+ tag = ((DetailsTypes.AddonDetailsType) details).getTag();
+ } else {
+ tag = SystemImage.DEFAULT_TAG;
+ }
+
+ File skinDir = new File(dir, SdkConstants.FD_SKINS);
+ if (!mFop.exists(skinDir) && version != null) {
+ skinDir = platformSkins.get(version);
+ }
+ File[] skins;
+ if (skinDir != null) {
+ List<File> skinList = PackageParserUtils.parseSkinFolder(skinDir, mFop);
+ skins = skinList.toArray(new File[skinList.size()]);
+ } else {
+ skins = new File[0];
+ }
+ return new SystemImage(dir, tag, vendor, abi, skins, p);
+ }
+
+ @Nullable
+ public ISystemImage getImageAt(@NonNull File imageDir) {
+ if (mPathToImage == null) {
+ init();
+ }
+ return mPathToImage.get(imageDir);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java b/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java
new file mode 100644
index 0000000..06818b3
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/util/CommandLineParser.java
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 2008 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.sdklib.util;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.ILogger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+/**
+ * Parses the command-line and stores flags needed or requested.
+ * <p/>
+ * This is a base class. To be useful you want to:
+ * <ul>
+ * <li>override it.
+ * <li>pass an action array to the constructor.
+ * <li>define flags for your actions.
+ * </ul>
+ * <p/>
+ * To use, call {@link #parseArgs(String[])} and then
+ * call {@link #getValue(String, String, String)}.
+ */
+public class CommandLineParser {
+
+ /*
+ * Steps needed to add a new action:
+ * - Each action is defined as a "verb object" followed by parameters.
+ * - Either reuse a VERB_ constant or define a new one.
+ * - Either reuse an OBJECT_ constant or define a new one.
+ * - Add a new entry to mAction with a one-line help summary.
+ * - In the constructor, add a define() call for each parameter (either mandatory
+ * or optional) for the given action.
+ */
+
+ /** Internal verb name for internally hidden flags. */
+ public static final String GLOBAL_FLAG_VERB = "@@internal@@"; //$NON-NLS-1$
+
+ /** String to use when the verb doesn't need any object. */
+ public static final String NO_VERB_OBJECT = ""; //$NON-NLS-1$
+
+ /** The global help flag. */
+ public static final String KEY_HELP = "help";
+ /** The global verbose flag. */
+ public static final String KEY_VERBOSE = "verbose";
+ /** The global silent flag. */
+ public static final String KEY_SILENT = "silent";
+
+ /** Verb requested by the user. Null if none specified, which will be an error. */
+ private String mVerbRequested;
+ /** Direct object requested by the user. Can be null. */
+ private String mDirectObjectRequested;
+
+ /**
+ * Action definitions.
+ * <p/>
+ * This list serves two purposes: first it is used to know which verb/object
+ * actions are acceptable on the command-line; second it provides a summary
+ * for each action that is printed in the help.
+ * <p/>
+ * Each entry is a string array with:
+ * <ul>
+ * <li> the verb.
+ * <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object).
+ * <li> a description.
+ * <li> an alternate form for the object (e.g. plural).
+ * </ul>
+ */
+ private final String[][] mActions;
+
+ private static final int ACTION_VERB_INDEX = 0;
+ private static final int ACTION_OBJECT_INDEX = 1;
+ private static final int ACTION_DESC_INDEX = 2;
+ private static final int ACTION_ALT_OBJECT_INDEX = 3;
+
+ /**
+ * The map of all defined arguments.
+ * <p/>
+ * The key is a string "verb/directObject/longName".
+ */
+ private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
+ /** Logger */
+ private final ILogger mLog;
+
+ /**
+ * Constructs a new command-line processor.
+ *
+ * @param logger An SDK logger object. Must not be null.
+ * @param actions The list of actions recognized on the command-line.
+ * See the javadoc of {@link #mActions} for more details.
+ *
+ * @see #mActions
+ */
+ public CommandLineParser(ILogger logger, String[][] actions) {
+ mLog = logger;
+ mActions = actions;
+
+ /*
+ * usage should fit in 80 columns, including the space to print the options:
+ * " -v --verbose 7890123456789012345678901234567890123456789012345678901234567890"
+ */
+
+ define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
+ "Verbose mode, shows errors, warnings and all messages.",
+ false);
+ define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
+ "Silent mode, shows errors only.",
+ false);
+ define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
+ "Help on a specific command.",
+ false);
+ }
+
+ /**
+ * Indicates if this command-line can work when no verb is specified.
+ * The default is false, which generates an error when no verb/object is specified.
+ * Derived implementations can set this to true if they can deal with a lack
+ * of verb/action.
+ */
+ public boolean acceptLackOfVerb() {
+ return false;
+ }
+
+
+ //------------------
+ // Helpers to get flags values
+
+ /** Helper that returns true if --verbose was requested. */
+ public boolean isVerbose() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
+ }
+
+ /** Helper that returns true if --silent was requested. */
+ public boolean isSilent() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
+ }
+
+ /** Helper that returns true if --help was requested. */
+ public boolean isHelpRequested() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
+ }
+
+ /** Returns the verb name from the command-line. Can be null. */
+ public String getVerb() {
+ return mVerbRequested;
+ }
+
+ /** Returns the direct object name from the command-line. Can be null. */
+ public String getDirectObject() {
+ return mDirectObjectRequested;
+ }
+
+ //------------------
+
+ /**
+ * Raw access to parsed parameter values.
+ * <p/>
+ * The default is to scan all parameters. Parameters that have been explicitly set on the
+ * command line are returned first. Otherwise one with a non-null value is returned.
+ * <p/>
+ * Both a verb and a direct object filter can be specified. When they are non-null they limit
+ * the scope of the search.
+ * <p/>
+ * If nothing has been found, return the last default value seen matching the filter.
+ *
+ * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
+ * verbs that match the direct object condition will be examined and the first
+ * value set will be used.
+ * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
+ * all possible direct objects that match the verb condition will be examined and
+ * the first value set will be used.
+ * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
+ * @return The current value object stored in the parameter, which depends on the argument mode.
+ */
+ public Object getValue(String verb, String directObject, String longFlagName) {
+
+ if (verb != null && directObject != null) {
+ String key = verb + '/' + directObject + '/' + longFlagName;
+ Arg arg = mArguments.get(key);
+ return arg.getCurrentValue();
+ }
+
+ Object lastDefault = null;
+ for (Arg arg : mArguments.values()) {
+ if (arg.getLongArg().equals(longFlagName)) {
+ if (verb == null || arg.getVerb().equals(verb)) {
+ if (directObject == null || arg.getDirectObject().equals(directObject)) {
+ if (arg.isInCommandLine()) {
+ return arg.getCurrentValue();
+ }
+ if (arg.getCurrentValue() != null) {
+ lastDefault = arg.getCurrentValue();
+ }
+ }
+ }
+ }
+ }
+
+ return lastDefault;
+ }
+
+ /**
+ * Internal setter for raw parameter value.
+ * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
+ * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
+ * @param longFlagName The long flag name for the given action.
+ * @param value The new current value object stored in the parameter, which depends on the
+ * argument mode.
+ */
+ protected void setValue(String verb, String directObject, String longFlagName, Object value) {
+ String key = verb + '/' + directObject + '/' + longFlagName;
+ Arg arg = mArguments.get(key);
+ arg.setCurrentValue(value);
+ }
+
+ /**
+ * Parses the command-line arguments.
+ * <p/>
+ * This method will exit and not return if a parsing error arise.
+ *
+ * @param args The arguments typically received by a main method.
+ */
+ public void parseArgs(String[] args) {
+ String errorMsg = null;
+ String verb = null;
+ String directObject = null;
+
+ try {
+ int n = args.length;
+ for (int i = 0; i < n; i++) {
+ Arg arg = null;
+ String a = args[i];
+ if (a.startsWith("--")) { //$NON-NLS-1$
+ arg = findLongArg(verb, directObject, a.substring(2));
+ } else if (a.startsWith("-")) { //$NON-NLS-1$
+ arg = findShortArg(verb, directObject, a.substring(1));
+ }
+
+ // No matching argument name found
+ if (arg == null) {
+ // Does it looks like a dashed parameter?
+ if (a.startsWith("-")) { //$NON-NLS-1$
+ if (verb == null || directObject == null) {
+ // It looks like a dashed parameter and we don't have a a verb/object
+ // set yet, the parameter was just given too early.
+
+ errorMsg = String.format(
+ "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
+ a);
+ return;
+ } else {
+ // It looks like a dashed parameter but it is unknown by this
+ // verb-object combination
+
+ errorMsg = String.format(
+ "Flag '%1$s' is not valid for '%2$s %3$s'.",
+ a, verb, directObject);
+ return;
+ }
+ }
+
+ if (verb == null) {
+ // Fill verb first. Find it.
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
+ verb = a;
+ break;
+ }
+ }
+
+ // Error if it was not a valid verb
+ if (verb == null) {
+ errorMsg = String.format(
+ "Expected verb after global parameters but found '%1$s' instead.",
+ a);
+ return;
+ }
+
+ } else if (directObject == null) {
+ // Then fill the direct object. Find it.
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
+ if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
+ directObject = a;
+ break;
+ } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
+ actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
+ // if the alternate form exist and is used, we internally
+ // only memorize the default direct object form.
+ directObject = actionDesc[ACTION_OBJECT_INDEX];
+ break;
+ }
+ }
+ }
+
+ // Error if it was not a valid object for that verb
+ if (directObject == null) {
+ errorMsg = String.format(
+ "Expected verb after global parameters but found '%1$s' instead.",
+ a);
+ return;
+
+ }
+ } else {
+ // The argument is not a dashed parameter and we already
+ // have a verb/object. Must be some extra unknown argument.
+ errorMsg = String.format(
+ "Argument '%1$s' is not recognized.",
+ a);
+ }
+ } else if (arg != null) {
+ // This argument was present on the command line
+ arg.setInCommandLine(true);
+
+ // Process keyword
+ Object error = null;
+ if (arg.getMode().needsExtra()) {
+ if (i+1 >= n) {
+ errorMsg = String.format("Missing argument for flag %1$s.", a);
+ return;
+ }
+
+ while (i+1 < n) {
+ String b = args[i+1];
+
+ if (arg.getMode() != Mode.STRING_ARRAY) {
+ // We never accept something that looks like a valid argument
+ // unless we see -- first
+ Arg dummyArg = null;
+ if (b.startsWith("--")) { //$NON-NLS-1$
+ dummyArg = findLongArg(verb, directObject, b.substring(2));
+ } else if (b.startsWith("-")) { //$NON-NLS-1$
+ dummyArg = findShortArg(verb, directObject, b.substring(1));
+ }
+ if (dummyArg != null) {
+ errorMsg = String.format(
+ "Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.",
+ a, b);
+ return;
+ }
+ }
+
+ error = arg.getMode().process(arg, b);
+ if (error == Accept.CONTINUE) {
+ i++;
+ } else if (error == Accept.ACCEPT_AND_STOP) {
+ i++;
+ break;
+ } else if (error == Accept.REJECT_AND_STOP) {
+ break;
+ } else if (error instanceof String) {
+ // We stop because of an error
+ break;
+ }
+ }
+ } else {
+ error = arg.getMode().process(arg, null);
+
+ if (isHelpRequested()) {
+ // The --help flag was requested. We'll continue the usual processing
+ // so that we can find the optional verb/object words. Those will be
+ // used to print specific help.
+ // Setting a non-null error message triggers printing the help, however
+ // there is no specific error to print.
+ errorMsg = ""; //$NON-NLS-1$
+ }
+ }
+
+ if (error instanceof String) {
+ errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
+ return;
+ }
+ }
+ }
+
+ if (errorMsg == null) {
+ if (verb == null && !acceptLackOfVerb()) {
+ errorMsg = "Missing verb name.";
+ } else if (verb != null) {
+ if (directObject == null) {
+ // Make sure this verb has an optional direct object
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
+ actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
+ directObject = NO_VERB_OBJECT;
+ break;
+ }
+ }
+
+ if (directObject == null) {
+ errorMsg = String.format("Missing object name for verb '%1$s'.", verb);
+ return;
+ }
+ }
+
+ // Validate that all mandatory arguments are non-null for this action
+ String missing = null;
+ boolean plural = false;
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) &&
+ arg.getDirectObject().equals(directObject)) {
+ if (arg.isMandatory() && arg.getCurrentValue() == null) {
+ if (missing == null) {
+ missing = "--" + arg.getLongArg(); //$NON-NLS-1$
+ } else {
+ missing += ", --" + arg.getLongArg(); //$NON-NLS-1$
+ plural = true;
+ }
+ }
+ }
+ }
+
+ if (missing != null) {
+ errorMsg = String.format(
+ "The %1$s %2$s must be defined for action '%3$s %4$s'",
+ plural ? "parameters" : "parameter",
+ missing,
+ verb,
+ directObject);
+ }
+
+ mVerbRequested = verb;
+ mDirectObjectRequested = directObject;
+ }
+ }
+ } finally {
+ if (errorMsg != null) {
+ printHelpAndExitForAction(verb, directObject, errorMsg);
+ }
+ }
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a long flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findLongArg(String verb, String directObject, String longName) {
+ if (verb == null) {
+ verb = GLOBAL_FLAG_VERB;
+ }
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+ String key = verb + '/' + directObject + '/' + longName; //$NON-NLS-1$
+ return mArguments.get(key);
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a short flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findShortArg(String verb, String directObject, String shortName) {
+ if (verb == null) {
+ verb = GLOBAL_FLAG_VERB;
+ }
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
+ if (shortName.equals(arg.getShortArg())) {
+ return arg;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExit(String errorFormat, Object... args) {
+ printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param verb If null, displays help for all verbs. If not null, display help only
+ * for that specific verb. In all cases also displays general usage and action list.
+ * @param directObject If null, displays help for all verb objects.
+ * If not null, displays help only for that specific action
+ * In all cases also display general usage and action list.
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExitForAction(String verb, String directObject,
+ String errorFormat, Object... args) {
+ if (errorFormat != null && !errorFormat.isEmpty()) {
+ stderr(errorFormat, args);
+ }
+
+ /*
+ * usage should fit in 80 columns
+ * 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+ */
+ stdout("");
+ stdout("Usage:\n" +
+ " android [global options] %s [action options]\n" +
+ "\n" +
+ "Global options:",
+ verb == null ? (acceptLackOfVerb() ? "[action]" : "action") :
+ verb + (directObject == null ? "" : " " + directObject)); //$NON-NLS-1$
+ listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
+
+ if (verb == null || directObject == null) {
+ stdout("");
+ stdout("Valid actions are composed of a verb and an optional direct object:");
+ for (String[] action : mActions) {
+ if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
+ stdout("- %1$6s %2$-13s: %3$s",
+ action[ACTION_VERB_INDEX],
+ action[ACTION_OBJECT_INDEX],
+ action[ACTION_DESC_INDEX]);
+ }
+ }
+ }
+ if (verb == null && acceptLackOfVerb() && getDefaultVerb() != null) {
+ stdout("");
+ stdout("If a verb is not specified, the default is '%s'", getDefaultVerb());
+ }
+
+ // Only print details if a verb/object is requested
+ if (verb != null) {
+ for (String[] action : mActions) {
+ if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
+ if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
+ stdout("");
+ stdout("Action \"%1$s %2$s\":",
+ action[ACTION_VERB_INDEX],
+ action[ACTION_OBJECT_INDEX]);
+ stdout(" %1$s", action[ACTION_DESC_INDEX]);
+ stdout("Options:");
+ listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
+ }
+ }
+ }
+ }
+
+ exit();
+ }
+
+ /**
+ @return The default verb name to show in the help message. Should only return non-null if
+ {@link #acceptLackOfVerb} is true.
+ */
+ @Nullable
+ protected String getDefaultVerb() {
+ return null;
+ }
+
+ /**
+ * Internal helper to print all the option flags for a given action name.
+ */
+ protected void listOptions(String verb, String directObject) {
+ int numOptions = 0;
+ int longArgLen = 8;
+
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
+ int n = arg.getLongArg().length();
+ if (n > longArgLen) {
+ longArgLen = n;
+ }
+ }
+ }
+
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
+
+ String value = ""; //$NON-NLS-1$
+ String required = ""; //$NON-NLS-1$
+ if (arg.isMandatory()) {
+ required = " [required]";
+
+ } else {
+ if (arg.getDefaultValue() instanceof String[]) {
+ for (String v : (String[]) arg.getDefaultValue()) {
+ if (!value.isEmpty()) {
+ value += ", ";
+ }
+ value += v;
+ }
+ } else if (arg.getDefaultValue() != null) {
+ Object v = arg.getDefaultValue();
+ if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) {
+ value = v.toString();
+ }
+ }
+ if (!value.isEmpty()) {
+ value = " [Default: " + value + "]";
+ }
+ }
+
+ // Java doesn't support * for printf variable width, so we'll insert the long arg
+ // width "manually" in the printf format string.
+ String longArgWidth = Integer.toString(longArgLen + 2);
+
+ // Print a line in the form " -1_letter_arg --long_arg description"
+ // where either the 1-letter arg or the long arg are optional.
+ String output = String.format(
+ " %1$-2s %2$-" + longArgWidth + "s: %3$s%4$s%5$s", //$NON-NLS-1$ //$NON-NLS-2$
+ !arg.getShortArg().isEmpty() ?
+ "-" + arg.getShortArg() : //$NON-NLS-1$
+ "", //$NON-NLS-1$
+ !arg.getLongArg().isEmpty() ?
+ "--" + arg.getLongArg() : //$NON-NLS-1$
+ "", //$NON-NLS-1$
+ arg.getDescription(),
+ value,
+ required);
+ stdout(output);
+ numOptions++;
+ }
+ }
+
+ if (numOptions == 0) {
+ stdout(" No options");
+ }
+ }
+
+ //----
+
+ protected enum Accept {
+ CONTINUE,
+ ACCEPT_AND_STOP,
+ REJECT_AND_STOP,
+ }
+
+ /**
+ * The mode of an argument specifies the type of variable it represents,
+ * whether an extra parameter is required after the flag and how to parse it.
+ */
+ protected enum Mode {
+ /** Argument value is a Boolean. Default value is a Boolean. */
+ BOOLEAN {
+ @Override
+ public boolean needsExtra() {
+ return false;
+ }
+ @Override
+ public Object process(Arg arg, String extra) {
+ // Toggle the current value
+ arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
+ return Accept.ACCEPT_AND_STOP;
+ }
+ },
+
+ /** Argument value is an Integer. Default value is an Integer. */
+ INTEGER {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public Object process(Arg arg, String extra) {
+ try {
+ arg.setCurrentValue(Integer.parseInt(extra));
+ return null;
+ } catch (NumberFormatException e) {
+ return String.format("Failed to parse '%1$s' as an integer: %2$s", extra,
+ e.getMessage());
+ }
+ }
+ },
+
+ /** Argument value is a String. Default value is a String[]. */
+ ENUM {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public Object process(Arg arg, String extra) {
+ StringBuilder desc = new StringBuilder();
+ String[] values = (String[]) arg.getDefaultValue();
+ for (String value : values) {
+ if (value.equals(extra)) {
+ arg.setCurrentValue(extra);
+ return Accept.ACCEPT_AND_STOP;
+ }
+
+ if (desc.length() != 0) {
+ desc.append(", ");
+ }
+ desc.append(value);
+ }
+
+ return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
+ }
+ },
+
+ /** Argument value is a String. Default value is a null. */
+ STRING {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public Object process(Arg arg, String extra) {
+ arg.setCurrentValue(extra);
+ return Accept.ACCEPT_AND_STOP;
+ }
+ },
+
+ /** Argument value is a {@link List}<String>. Default value is an empty list. */
+ STRING_ARRAY {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public Object process(Arg arg, String extra) {
+ // For simplification, a string array doesn't accept something that
+ // starts with a dash unless a pure -- was seen before.
+ if (extra != null) {
+ Object v = arg.getCurrentValue();
+ if (v == null) {
+ ArrayList<String> a = new ArrayList<String>();
+ arg.setCurrentValue(a);
+ v = a;
+ }
+ if (v instanceof List<?>) {
+ @SuppressWarnings("unchecked") List<String> a = (List<String>) v;
+
+ if (extra.equals("--") ||
+ !extra.startsWith("-") ||
+ (extra.startsWith("-") && a.contains("--"))) {
+ a.add(extra);
+ return Accept.CONTINUE;
+ } else if (a.isEmpty()) {
+ return "No values provided";
+ }
+ }
+ }
+ return Accept.REJECT_AND_STOP;
+ }
+ };
+
+ /**
+ * Returns true if this mode requires an extra parameter.
+ */
+ public abstract boolean needsExtra();
+
+ /**
+ * Processes the flag for this argument.
+ *
+ * @param arg The argument being processed.
+ * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
+ * @return {@link Accept#CONTINUE} if this argument can use multiple values and
+ * wishes to receive more.
+ * Or {@link Accept#ACCEPT_AND_STOP} if this was the last value accepted by the argument.
+ * Or {@link Accept#REJECT_AND_STOP} if this was value was reject and the argument
+ * stops accepting new values with no error.
+ * Or a string in case of error.
+ * Never returns null.
+ */
+ public abstract Object process(Arg arg, String extra);
+ }
+
+ /**
+ * An argument accepted by the command-line, also called "a flag".
+ * Arguments must have a short version (one letter), a long version name and a description.
+ * They can have a default value, or it can be null.
+ * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String
+ * or a String array (in which case the first item is the current by default.)
+ */
+ protected static class Arg {
+ /** Verb for that argument. Never null. */
+ private final String mVerb;
+ /** Direct Object for that argument. Never null, but can be empty string. */
+ private final String mDirectObject;
+ /** The 1-letter short name of the argument, e.g. -v. */
+ private final String mShortName;
+ /** The long name of the argument, e.g. --verbose. */
+ private final String mLongName;
+ /** A description. Never null. */
+ private final String mDescription;
+ /** A default value. Can be null. */
+ private final Object mDefaultValue;
+ /** The argument mode (type + process method). Never null. */
+ private final Mode mMode;
+ /** True if this argument is mandatory for this verb/directobject. */
+ private final boolean mMandatory;
+ /** Current value. Initially set to the default value. */
+ private Object mCurrentValue;
+ /** True if the argument has been used on the command line. */
+ private boolean mInCommandLine;
+
+ /**
+ * Creates a new argument flag description.
+ *
+ * @param mode The {@link Mode} for the argument.
+ * @param mandatory True if this argument is mandatory for this action.
+ * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}.
+ * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}.
+ * @param shortName The one-letter short argument name. Can be empty but not null.
+ * @param longName The long argument name. Can be empty but not null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected
+ * {@link Mode}. Can be null.
+ */
+ public Arg(Mode mode,
+ boolean mandatory,
+ @NonNull String verb,
+ @NonNull String directObject,
+ @NonNull String shortName,
+ @NonNull String longName,
+ @NonNull String description,
+ @Nullable Object defaultValue) {
+ mMode = mode;
+ mMandatory = mandatory;
+ mVerb = verb;
+ mDirectObject = directObject;
+ mShortName = shortName;
+ mLongName = longName;
+ mDescription = description;
+ mDefaultValue = defaultValue;
+ mInCommandLine = false;
+ if (defaultValue instanceof String[]) {
+ mCurrentValue = ((String[])defaultValue)[0];
+ } else {
+ mCurrentValue = mDefaultValue;
+ }
+ }
+
+ /** Return true if this argument is mandatory for this verb/directobject. */
+ public boolean isMandatory() {
+ return mMandatory;
+ }
+
+ /** Returns the 1-letter short name of the argument, e.g. -v. */
+ public String getShortArg() {
+ return mShortName;
+ }
+
+ /** Returns the long name of the argument, e.g. --verbose. */
+ public String getLongArg() {
+ return mLongName;
+ }
+
+ /** Returns the description. Never null. */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /** Returns the verb for that argument. Never null. */
+ public String getVerb() {
+ return mVerb;
+ }
+
+ /** Returns the direct Object for that argument. Never null, but can be empty string. */
+ public String getDirectObject() {
+ return mDirectObject;
+ }
+
+ /** Returns the default value. Can be null. */
+ public Object getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /** Returns the current value. Initially set to the default value. Can be null. */
+ public Object getCurrentValue() {
+ return mCurrentValue;
+ }
+
+ /** Sets the current value. Can be null. */
+ public void setCurrentValue(Object currentValue) {
+ mCurrentValue = currentValue;
+ }
+
+ /** Returns the argument mode (type + process method). Never null. */
+ public Mode getMode() {
+ return mMode;
+ }
+
+ /** Returns true if the argument has been used on the command line. */
+ public boolean isInCommandLine() {
+ return mInCommandLine;
+ }
+
+ /** Sets if the argument has been used on the command line. */
+ public void setInCommandLine(boolean inCommandLine) {
+ mInCommandLine = inCommandLine;
+ }
+ }
+
+ /**
+ * Internal helper to define a new argument for a give action.
+ *
+ * @param mode The {@link Mode} for the argument.
+ * @param mandatory The argument is required (never if {@link Mode#BOOLEAN})
+ * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}.
+ * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}.
+ * @param shortName The one-letter short argument name. Can be empty but not null.
+ * @param longName The long argument name. Can be empty but not null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected
+ * {@link Mode}.
+ */
+ protected void define(Mode mode,
+ boolean mandatory,
+ @NonNull String verb,
+ @NonNull String directObject,
+ @NonNull String shortName,
+ @NonNull String longName,
+ @NonNull String description,
+ @Nullable Object defaultValue) {
+ assert verb != null;
+ assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory
+
+ // We should always have at least a short or long name, ideally both but never none.
+ assert shortName != null;
+ assert longName != null;
+ assert !shortName.isEmpty() || !longName.isEmpty();
+
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+
+ String key = verb + '/' + directObject + '/' + longName;
+ mArguments.put(key, new Arg(mode, mandatory,
+ verb, directObject, shortName, longName, description, defaultValue));
+ }
+
+ /**
+ * Exits in case of error.
+ * This is protected so that it can be overridden in unit tests.
+ */
+ protected void exit() {
+ System.exit(1);
+ }
+
+ /**
+ * Prints a line to stdout.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stdout(String format, Object...args) {
+ String output = String.format(format, args);
+ output = LineUtil.reflowLine(output);
+ mLog.info("%s\n", output); //$NON-NLS-1$
+ }
+
+ /**
+ * Prints a line to stderr.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stderr(String format, Object...args) {
+ mLog.error(null, format, args);
+ }
+
+ /**
+ * Returns the logger object.
+ * @return the logger object.
+ */
+ protected ILogger getLog() {
+ return mLog;
+ }
+}
diff --git a/base/sdklib/src/main/java/com/android/sdklib/util/FormatUtils.java b/sdklib/src/main/java/com/android/sdklib/util/FormatUtils.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/util/FormatUtils.java
rename to sdklib/src/main/java/com/android/sdklib/util/FormatUtils.java
diff --git a/base/sdklib/src/main/java/com/android/sdklib/util/LineUtil.java b/sdklib/src/main/java/com/android/sdklib/util/LineUtil.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/main/java/com/android/sdklib/util/LineUtil.java
rename to sdklib/src/main/java/com/android/sdklib/util/LineUtil.java
diff --git a/base/sdklib/src/test/java/com/android/sdklib/AndroidLocationTestCase.java b/sdklib/src/test/java/com/android/sdklib/AndroidLocationTestCase.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/AndroidLocationTestCase.java
rename to sdklib/src/test/java/com/android/sdklib/AndroidLocationTestCase.java
diff --git a/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java b/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
new file mode 100644
index 0000000..458c0ca
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import com.android.sdklib.internal.androidTarget.MockAddonTarget;
+import com.android.sdklib.internal.androidTarget.MockPlatformTarget;
+
+import junit.framework.TestCase;
+
+public class AndroidTargetHashTest extends TestCase {
+
+ public final void testGetPlatformHashString() {
+ assertEquals("android-10",
+ AndroidTargetHash.getPlatformHashString(new AndroidVersion(10, null)));
+
+ assertEquals("android-CODE_NAME",
+ AndroidTargetHash.getPlatformHashString(new AndroidVersion(10, "CODE_NAME")));
+ }
+
+ public final void testGetAddonHashString() {
+ assertEquals("The Vendor Inc.:My Addon:10",
+ AndroidTargetHash.getAddonHashString(
+ "The Vendor Inc.",
+ "My Addon",
+ new AndroidVersion(10, null)));
+ }
+
+ public final void testGetTargetHashString() {
+ MockPlatformTarget t = new MockPlatformTarget(10, 1);
+ assertEquals("android-10", AndroidTargetHash.getTargetHashString(t));
+ MockAddonTarget a = new MockAddonTarget("My Addon", t, 2);
+ assertEquals("vendor 10:My Addon:10", AndroidTargetHash.getTargetHashString(a));
+ }
+
+ public void testGetPlatformVersion() {
+ assertNull(AndroidTargetHash.getPlatformVersion("blah-5"));
+ assertNull(AndroidTargetHash.getPlatformVersion("5-blah"));
+ assertNull(AndroidTargetHash.getPlatformVersion("android-"));
+
+ AndroidVersion version = AndroidTargetHash.getPlatformVersion("android-5");
+ assertNotNull(version);
+ assertEquals(5, version.getApiLevel());
+ assertNull(version.getCodename());
+
+ version = AndroidTargetHash.getPlatformVersion("5");
+ assertNotNull(version);
+ assertEquals(5, version.getApiLevel());
+ assertEquals(5, version.getFeatureLevel());
+ assertNull(version.getCodename());
+
+ version = AndroidTargetHash.getPlatformVersion("android-CUPCAKE");
+ assertNotNull(version);
+ assertEquals(2, version.getApiLevel());
+ assertEquals(3, version.getFeatureLevel());
+ assertEquals("CUPCAKE", version.getCodename());
+
+ version = AndroidTargetHash.getPlatformVersion("android-KITKAT");
+ assertNotNull(version);
+ assertEquals(18, version.getApiLevel());
+ assertEquals(19, version.getFeatureLevel());
+ assertEquals("KITKAT", version.getCodename());
+
+ version = AndroidTargetHash.getPlatformVersion("android-N");
+ assertNotNull(version);
+ assertEquals(23, version.getApiLevel());
+ assertEquals(24, version.getFeatureLevel());
+ assertEquals("N", version.getCodename());
+
+ version = AndroidTargetHash.getPlatformVersion("android-UNKNOWN");
+ assertNotNull(version);
+ assertEquals(SdkVersionInfo.HIGHEST_KNOWN_API, version.getApiLevel());
+ assertEquals(SdkVersionInfo.HIGHEST_KNOWN_API + 1, version.getFeatureLevel());
+ assertEquals("UNKNOWN", version.getCodename());
+ }
+}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java b/sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java
rename to sdklib/src/test/java/com/android/sdklib/AndroidVersionTest.java
diff --git a/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java b/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java
new file mode 100644
index 0000000..1188e37
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/BuildToolInfoTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import com.android.repository.Revision;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class BuildToolInfoTest extends TestCase {
+
+ public void testGetCurrentJvmVersion() {
+ MockFileOp fop = new MockFileOp();
+ recordBuildTool23(fop);
+ AndroidSdkHandler sdkHandler = new AndroidSdkHandler(new File("/sdk"), fop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ BuildToolInfo bt = sdkHandler.getBuildToolInfo(new Revision(23, 0, 2), progress);
+ progress.assertNoErrorsOrWarnings();
+ assertNotNull(bt);
+
+ // Check the actual JVM running this test.
+ Revision curr = bt.getCurrentJvmVersion();
+ // We can reasonably expect this to at least run with JVM 1.5 or more
+ assertTrue(curr.compareTo(new Revision(1, 5, 0)) > 0);
+ // and we can reasonably expect to not be running with JVM 42.0.0
+ assertTrue(curr.compareTo(new Revision(42, 0, 0)) < 0);
+ }
+
+ private static void recordBuildTool23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/build-tools/23.0.2/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns2:sdk-repository "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/generic/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-19E6313A\" type=\"text\">License text\n"
+ + "</license><localPackage path=\"build-tools;23.0.2\" obsolete=\"false\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns4:genericDetailsType\"/>"
+ + "<revision><major>23</major><minor>0</minor><micro>2</micro></revision>"
+ + "<display-name>Android SDK Build-Tools 23.0.2</display-name>"
+ + "<uses-license ref=\"license-19E6313A\"/></localPackage>"
+ + "</ns2:sdk-repository>\n");
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java b/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
new file mode 100644
index 0000000..e683d6a
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.repository.Revision;
+import com.android.repository.io.FileOpUtils;
+import com.android.repository.testframework.MockFileOp;
+import com.android.resources.Density;
+import com.android.resources.Keyboard;
+import com.android.resources.KeyboardState;
+import com.android.resources.Navigation;
+import com.android.resources.NavigationState;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenRatio;
+import com.android.resources.ScreenSize;
+import com.android.resources.TouchScreen;
+import com.android.sdklib.ISystemImage.LocationType;
+import com.android.sdklib.devices.ButtonType;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Device.Builder;
+import com.android.sdklib.devices.DeviceWriter;
+import com.android.sdklib.devices.Hardware;
+import com.android.sdklib.devices.Multitouch;
+import com.android.sdklib.devices.PowerType;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.ScreenType;
+import com.android.sdklib.devices.Software;
+import com.android.sdklib.devices.State;
+import com.android.sdklib.devices.Storage;
+import com.android.sdklib.devices.Storage.Unit;
+import com.android.sdklib.mock.MockLog;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.local.LocalPlatformPkgInfo;
+import com.android.sdklib.repository.local.LocalSysImgPkgInfo;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base Test case that allocates a temporary SDK, a temporary AVD base folder with an SdkManager and
+ * an AvdManager that points to them. <p/> Also overrides the {@link AndroidLocation} to point to
+ * temp one.
+ */
+public abstract class SdkManagerTestCase extends AndroidLocationTestCase {
+
+ protected static final String TARGET_DIR_NAME_0 = "v0_0";
+
+ private File mFakeSdk;
+
+ private MockLog mLog;
+
+ private AndroidSdkHandler mSdkHandler;
+
+ private int mRepoXsdLevel;
+
+ /**
+ * Returns the {@link MockLog} for this test case.
+ */
+ public MockLog getLog() {
+ return mLog;
+ }
+
+ public AndroidSdkHandler getSdkHandler() {
+ return mSdkHandler;
+ }
+
+ /**
+ * Sets up a {@link MockLog}, a fake SDK in a temporary directory and an AVD Manager pointing to
+ * an initially-empty AVD directory.
+ */
+ public void setUp(int repoXsdLevel) throws Exception {
+ super.setUp();
+ mRepoXsdLevel = repoXsdLevel;
+ mLog = new MockLog();
+ makeFakeSdk();
+ createSdkAvdManagers();
+ }
+
+ /**
+ * Recreate the SDK and AVD Managers from scratch even if they already existed. Useful for tests
+ * that want to reset their state without recreating the android-home or the fake SDK. The SDK
+ * will be reparsed.
+ */
+ protected void createSdkAvdManagers() throws AndroidLocationException {
+ mSdkHandler = new AndroidSdkHandler(mFakeSdk, new MockFileOp());
+ }
+
+ /**
+ * Sets up a {@link MockLog}, a fake SDK in a temporary directory and an AVD Manager pointing to
+ * an initially-empty AVD directory.
+ */
+ @Override
+ public void setUp() throws Exception {
+ setUp(AndroidSdkHandler.LATEST_LEGACY_VERSION);
+ }
+
+ /**
+ * Removes the temporary SDK and AVD directories.
+ */
+ @Override
+ public void tearDown() throws Exception {
+ tearDownSdk();
+ super.tearDown();
+ }
+
+ /**
+ * Build enough of a skeleton SDK to make the tests pass. <p/> Ideally this wouldn't touch the
+ * file system but the current structure of the SdkManager and AvdManager makes this
+ * impossible.
+ */
+ private void makeFakeSdk() throws IOException {
+ // First we create a temp file to "reserve" the temp directory name we want to use.
+ mFakeSdk = File.createTempFile(
+ "sdk_" + this.getClass().getSimpleName() + '_' + this.getName(), null);
+ // Then erase the file and make the directory
+ mFakeSdk.delete();
+ mFakeSdk.mkdirs();
+
+ File addonsDir = new File(mFakeSdk, SdkConstants.FD_ADDONS);
+ addonsDir.mkdir();
+
+ File toolsDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_FOLDER);
+ toolsDir.mkdir();
+ createSourceProps(toolsDir, PkgProps.PKG_REVISION, "1.0.1");
+ new File(toolsDir, SdkConstants.androidCmdName()).createNewFile();
+ new File(toolsDir, SdkConstants.FN_EMULATOR).createNewFile();
+ new File(toolsDir, SdkConstants.mkSdCardCmdName()).createNewFile();
+
+ makePlatformTools(new File(mFakeSdk, SdkConstants.FD_PLATFORM_TOOLS));
+
+ if (mRepoXsdLevel >= 8) {
+ makeBuildTools(mFakeSdk);
+ }
+
+ File toolsLibEmuDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator");
+ toolsLibEmuDir.mkdirs();
+ new File(toolsLibEmuDir, "snapshots.img").createNewFile();
+ File platformsDir = new File(mFakeSdk, SdkConstants.FD_PLATFORMS);
+
+ // Creating a fake target here on down
+ File targetDir = makeFakeTargetInternal(platformsDir);
+ makeFakeLegacySysImg(targetDir, SdkConstants.ABI_ARMEABI);
+
+ makeFakeSkin(targetDir, "HVGA");
+ makeFakeSourceInternal(mFakeSdk);
+ }
+
+ private void tearDownSdk() {
+ deleteDir(mFakeSdk);
+ }
+
+ /**
+ * Creates the system image folder and places a fake userdata.img in it.
+ *
+ * @param systemImage A system image with a valid location.
+ */
+ protected void makeSystemImageFolder(ISystemImage systemImage, String deviceId)
+ throws Exception {
+ File sysImgDir = systemImage.getLocation();
+ String vendor = systemImage.getAddonVendor() == null ? null
+ : systemImage.getAddonVendor().getId();
+ // Path should like SDK/system-images/platform-N/tag/abi/userdata.img+source.properties
+ makeFakeSysImgInternal(
+ sysImgDir,
+ systemImage.getTag().getId(),
+ systemImage.getAbiType(),
+ deviceId,
+ vendor);
+ }
+
+ /**
+ * Creates the system image folder and places a fake userdata.img in it. This must be called
+ * after {@link #setUp()} so that it can use the temp fake SDK folder, and consequently you do
+ * not need to specify the SDK root.
+ *
+ * @param targetDir The targetDir segment of the sys-image folder. Use {@link
+ * #TARGET_DIR_NAME_0} to match the default single platform.
+ * @param tagId An optional tag id. Use null for legacy no-tag system images.
+ * @param abiType The abi for the system image.
+ * @return The directory of the system-image/tag/abi created.
+ * @throws IOException if the file fails to be created.
+ */
+ @NonNull
+ protected File makeSystemImageFolder(
+ @NonNull String targetDir,
+ @Nullable String tagId,
+ @NonNull String abiType) throws Exception {
+ File sysImgDir = new File(mFakeSdk, SdkConstants.FD_SYSTEM_IMAGES);
+ sysImgDir = new File(sysImgDir, targetDir);
+ if (tagId != null) {
+ sysImgDir = new File(sysImgDir, tagId);
+ }
+ sysImgDir = new File(sysImgDir, abiType);
+
+ makeFakeSysImgInternal(sysImgDir, tagId, abiType, null, null);
+ return sysImgDir;
+ }
+
+ //----
+
+ private void createTextFile(File dir, String filepath, String... lines) throws IOException {
+ File file = new File(dir, filepath);
+
+ File parent = file.getParentFile();
+ if (!parent.isDirectory()) {
+ parent.mkdirs();
+ }
+
+ if (!file.isFile()) {
+ assertTrue(file.createNewFile());
+ }
+ if (lines != null && lines.length > 0) {
+ FileWriter out = new FileWriter(file);
+ for (String line : lines) {
+ out.write(line);
+ }
+ out.close();
+ }
+ }
+
+ /**
+ * Utility used by {@link #makeFakeSdk()} to create a fake target with API 0, rev 0.
+ */
+ private File makeFakeTargetInternal(File platformsDir) throws IOException {
+ File targetDir = new File(platformsDir, TARGET_DIR_NAME_0);
+ targetDir.mkdirs();
+ new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile();
+ new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile();
+
+ createSourceProps(targetDir,
+ PkgProps.PKG_REVISION, "1",
+ PkgProps.PLATFORM_VERSION, "0.0",
+ PkgProps.VERSION_API_LEVEL, "0",
+ PkgProps.LAYOUTLIB_API, "5",
+ PkgProps.LAYOUTLIB_REV, "2");
+
+ createFileProps(SdkConstants.FN_BUILD_PROP, targetDir,
+ LocalPlatformPkgInfo.PROP_VERSION_RELEASE, "0.0",
+ LocalPlatformPkgInfo.PROP_VERSION_SDK, "0",
+ LocalPlatformPkgInfo.PROP_VERSION_CODENAME, "REL");
+
+ return targetDir;
+ }
+
+ /**
+ * Utility to create a fake *legacy* sys image in a platform folder. Legacy system images follow
+ * that path pattern: $SDK/platforms/platform-N/images/userdata.img
+ *
+ * They have no source.properties file in that directory.
+ */
+ private void makeFakeLegacySysImg(
+ @NonNull File platformDir,
+ @NonNull String abiType) throws IOException {
+ File imagesDir = new File(platformDir, "images");
+ imagesDir.mkdirs();
+ new File(imagesDir, "userdata.img").createNewFile();
+ }
+
+ /**
+ * Utility to create a fake sys image in the system-images folder.
+ *
+ * "modern" (as in "not legacy") system-images follow that path pattern:
+ * $SDK/system-images/platform-N/abi/source.properties $SDK/system-images/platform-N/abi/userdata.img
+ * or $SDK/system-images/platform-N/tag/abi/source.properties $SDK/system-images/platform-N/tag/abi/userdata.img
+ *
+ * The tag id is optional and was only introduced in API 20 / Tools 22.6. The platform-N and the
+ * tag folder names are irrelevant as the info from source.properties matters most.
+ */
+ private void makeFakeSysImgInternal(
+ @NonNull File sysImgDir,
+ @Nullable String tagId,
+ @NonNull String abiType,
+ @Nullable String deviceId,
+ @Nullable String deviceMfg) throws Exception {
+ sysImgDir.mkdirs();
+ new File(sysImgDir, "userdata.img").createNewFile();
+
+ if (tagId == null) {
+ createSourceProps(sysImgDir,
+ PkgProps.PKG_REVISION, "0",
+ PkgProps.VERSION_API_LEVEL, "0",
+ PkgProps.SYS_IMG_ABI, abiType);
+ } else {
+ String tagDisplay = LocalSysImgPkgInfo.tagIdToDisplay(tagId);
+ createSourceProps(sysImgDir,
+ PkgProps.PKG_REVISION, "0",
+ PkgProps.VERSION_API_LEVEL, "0",
+ PkgProps.SYS_IMG_TAG_ID, tagId,
+ PkgProps.SYS_IMG_TAG_DISPLAY, tagDisplay,
+ PkgProps.SYS_IMG_ABI, abiType,
+ PkgProps.PKG_LIST_DISPLAY,
+ "Sys-Img v0 for (" + tagDisplay + ", " + abiType + ")");
+
+ // create a devices.xml file
+ List<Device> devices = new ArrayList<Device>();
+ Builder b = new Device.Builder();
+ b.setName("Mock " + tagDisplay + " Device Name");
+ b.setId(deviceId == null ? "MockDevice-" + tagId : deviceId);
+ b.setManufacturer(deviceMfg == null ? "Mock " + tagDisplay + " OEM" : deviceMfg);
+
+ Software sw = new Software();
+ sw.setGlVersion("4.2");
+ sw.setLiveWallpaperSupport(false);
+ sw.setMaxSdkLevel(42);
+ sw.setMinSdkLevel(1);
+ sw.setStatusBar(true);
+
+ Screen sc = new Screen();
+ sc.setDiagonalLength(7);
+ sc.setMechanism(TouchScreen.FINGER);
+ sc.setMultitouch(Multitouch.JAZZ_HANDS);
+ sc.setPixelDensity(Density.HIGH);
+ sc.setRatio(ScreenRatio.NOTLONG);
+ sc.setScreenType(ScreenType.CAPACITIVE);
+ sc.setSize(ScreenSize.LARGE);
+ sc.setXDimension(5);
+ sc.setXdpi(100);
+ sc.setYDimension(4);
+ sc.setYdpi(100);
+
+ Hardware hw = new Hardware();
+ hw.setButtonType(ButtonType.SOFT);
+ hw.setChargeType(PowerType.BATTERY);
+ hw.setCpu(abiType);
+ hw.setGpu("pixelpushing");
+ hw.setHasMic(true);
+ hw.setKeyboard(Keyboard.QWERTY);
+ hw.setNav(Navigation.NONAV);
+ hw.setRam(new Storage(512, Unit.MiB));
+ hw.setScreen(sc);
+
+ State st = new State();
+ st.setName("portrait");
+ st.setDescription("Portrait");
+ st.setDefaultState(true);
+ st.setOrientation(ScreenOrientation.PORTRAIT);
+ st.setKeyState(KeyboardState.SOFT);
+ st.setNavState(NavigationState.HIDDEN);
+ st.setHardware(hw);
+
+ b.addSoftware(sw);
+ b.addState(st);
+
+ devices.add(b.build());
+
+ File f = new File(sysImgDir, "devices.xml");
+ FileOutputStream fos = new FileOutputStream(f);
+ DeviceWriter.writeToXml(fos, devices);
+ fos.close();
+ }
+ }
+
+ /**
+ * Utility to make a fake skin for the given target
+ */
+ protected void makeFakeSkin(File targetDir, String skinName) throws IOException {
+ File skinFolder = FileOpUtils.append(targetDir, "skins", skinName);
+ skinFolder.mkdirs();
+
+ // To be detected properly, the skin folder should have a "layout" file.
+ // Its content is however not parsed.
+ FileWriter out = new FileWriter(new File(skinFolder, "layout"));
+ out.write("parts {\n}\n");
+ out.close();
+ }
+
+ /**
+ * Utility to create a fake source with a few files in the given sdk folder.
+ */
+ private void makeFakeSourceInternal(File sdkDir) throws IOException {
+ File sourcesDir = FileOpUtils.append(sdkDir, SdkConstants.FD_PKG_SOURCES, "android-0");
+ sourcesDir.mkdirs();
+
+ createSourceProps(sourcesDir, PkgProps.VERSION_API_LEVEL, "0");
+
+ File dir1 = FileOpUtils.append(sourcesDir, "src", "com", "android");
+ dir1.mkdirs();
+ FileOpUtils.append(dir1, "File1.java").createNewFile();
+ FileOpUtils.append(dir1, "File2.java").createNewFile();
+
+ FileOpUtils.append(sourcesDir, "res", "values").mkdirs();
+ FileOpUtils.append(sourcesDir, "res", "values", "styles.xml").createNewFile();
+ }
+
+ private void makePlatformTools(File platformToolsDir) throws IOException {
+ platformToolsDir.mkdir();
+ createSourceProps(platformToolsDir, PkgProps.PKG_REVISION, "17.1.2");
+
+ // platform-tools revision >= 17 requires only an adb file to be valid.
+ new File(platformToolsDir, SdkConstants.FN_ADB).createNewFile();
+ }
+
+ private void makeBuildTools(File sdkDir) throws IOException {
+ for (String revision : new String[]{"3.0.0", "3.0.1", "18.3.4 rc5"}) {
+ createFakeBuildTools(sdkDir, "ANY", revision);
+ }
+ }
+
+ /**
+ * Adds a new fake build tools to the SDK In the given SDK/build-tools folder.
+ *
+ * @param sdkDir The SDK top folder. Must already exist.
+ * @param os The OS. One of HostOs#toString() or "ANY".
+ * @param revisionStr The "x.y.z rc r" revisionStr number from {@link Revision#toShortString()}.
+ */
+ protected void createFakeBuildTools(File sdkDir, String os, String revisionStr)
+ throws IOException {
+ File buildToolsTopDir = new File(sdkDir, SdkConstants.FD_BUILD_TOOLS);
+ buildToolsTopDir.mkdir();
+ File buildToolsDir = new File(buildToolsTopDir, revisionStr);
+ createSourceProps(buildToolsDir,
+ PkgProps.PKG_REVISION, revisionStr,
+ "Archive.Os", os);
+
+ Revision revision = Revision.parseRevision(revisionStr);
+
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.AAPT, SdkConstants.FN_AAPT);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.AIDL, SdkConstants.FN_AIDL);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.DX, SdkConstants.FN_DX);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.DX_JAR, SdkConstants.FD_LIB + File.separator +
+ SdkConstants.FN_DX_JAR);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.LLVM_RS_CC, SdkConstants.FN_RENDERSCRIPT);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.ANDROID_RS, SdkConstants.OS_FRAMEWORK_RS + File.separator +
+ "placeholder.txt");
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.ANDROID_RS_CLANG,
+ SdkConstants.OS_FRAMEWORK_RS_CLANG + File.separator +
+ "placeholder.txt");
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.BCC_COMPAT, SdkConstants.FN_BCC_COMPAT);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.LD_ARM, SdkConstants.FN_LD_ARM);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.LD_MIPS, SdkConstants.FN_LD_MIPS);
+ createFakeBuildToolsFile(
+ buildToolsDir, revision,
+ BuildToolInfo.PathId.LD_X86, SdkConstants.FN_LD_X86);
+ }
+
+ private void createFakeBuildToolsFile(@NonNull File dir,
+ @NonNull Revision buildToolsRevision,
+ @NonNull BuildToolInfo.PathId pathId,
+ @NonNull String filepath)
+ throws IOException {
+
+ if (pathId.isPresentIn(buildToolsRevision)) {
+ createTextFile(dir, filepath);
+ }
+ }
+
+
+ protected void createSourceProps(File parentDir, String... paramValuePairs) throws IOException {
+ createFileProps(SdkConstants.FN_SOURCE_PROP, parentDir, paramValuePairs);
+ }
+
+ protected void createFileProps(String fileName, File parentDir, String... paramValuePairs)
+ throws IOException {
+ File sourceProp = new File(parentDir, fileName);
+ parentDir = sourceProp.getParentFile();
+ if (!parentDir.isDirectory()) {
+ assertTrue(parentDir.mkdirs());
+ }
+ if (!sourceProp.isFile()) {
+ assertTrue(sourceProp.createNewFile());
+ }
+ FileWriter out = new FileWriter(sourceProp);
+ int n = paramValuePairs.length;
+ assertTrue("paramValuePairs must have an even length, format [param=value]+", n % 2 == 0);
+ for (int i = 0; i < n; i += 2) {
+ out.write(paramValuePairs[i] + '=' + paramValuePairs[i + 1] + '\n');
+ }
+ out.close();
+
+ }
+
+
+ /**
+ * Recursive delete directory. Mostly for fake SDKs.
+ *
+ * @param root directory to delete
+ */
+ protected void deleteDir(File root) {
+ if (root.exists()) {
+ for (File file : root.listFiles()) {
+ if (file.isDirectory()) {
+ deleteDir(file);
+ } else {
+ file.delete();
+ }
+ }
+ root.delete();
+ }
+ }
+
+}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/SdkVersionInfoTest.java b/sdklib/src/test/java/com/android/sdklib/SdkVersionInfoTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/SdkVersionInfoTest.java
rename to sdklib/src/test/java/com/android/sdklib/SdkVersionInfoTest.java
diff --git a/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java b/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java
new file mode 100644
index 0000000..5cdd91e
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/devices/DeviceManagerTest.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.devices;
+
+import com.android.repository.Revision;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.testframework.FakePackage;
+import com.android.resources.Keyboard;
+import com.android.resources.Navigation;
+import com.android.sdklib.SdkManagerTestCase;
+import com.android.sdklib.devices.Device.Builder;
+import com.android.sdklib.devices.DeviceManager.DeviceFilter;
+import com.android.sdklib.devices.DeviceManager.DeviceStatus;
+import com.android.sdklib.mock.MockLog;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.IdDisplay;
+import com.android.sdklib.repositoryv2.meta.AddonFactory;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.android.sdklib.repositoryv2.targets.SystemImage;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+public class DeviceManagerTest extends SdkManagerTestCase {
+
+ private DeviceManager dm;
+
+ private MockLog log;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ dm = createDeviceManager();
+ }
+
+ private DeviceManager createDeviceManager() {
+ log = super.getLog();
+ File sdkLocation = getSdkHandler().getLocation();
+ return DeviceManager.createInstance(sdkLocation, log);
+ }
+
+ /**
+ * Returns a list of just the devices' display names, for unit test comparisons.
+ */
+ private static String listDisplayName(Device device) {
+ if (device == null) {
+ return null;
+ }
+ return device.getDisplayName();
+ }
+
+ /**
+ * Returns a list of just the devices' display names, for unit test comparisons.
+ */
+ private static List<String> listDisplayNames(Collection<Device> devices) {
+ if (devices == null) {
+ return null;
+ }
+ List<String> names = new ArrayList<String>();
+ for (Device d : devices) {
+ names.add(listDisplayName(d));
+ }
+ Collections.sort(names);
+ return names;
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public final void testGetDevices_Default() {
+ // no user devices defined in the test's custom .android home folder
+ assertEquals("[]", dm.getDevices(DeviceFilter.USER).toString());
+ assertEquals("", log.toString());
+
+ // no system-images devices defined in the SDK by default
+ assertEquals("[]", dm.getDevices(DeviceFilter.SYSTEM_IMAGES).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from devices.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+ assertEquals(
+ "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, " +
+ "3.2\" HVGA slider (ADP1), 3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, " +
+ "3.7\" FWVGA slider, 3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), " +
+ "4.65\" 720p (Galaxy Nexus), 4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, " +
+ "7\" WSVGA (Tablet)]",
+ listDisplayNames(dm.getDevices(DeviceFilter.DEFAULT)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals("2.7\" QVGA",
+ listDisplayName(dm.getDevice("2.7in QVGA", "Generic")));
+
+ // this list comes from the nexus.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
+ assertEquals("[Android TV (1080p), Android TV (720p), Android Wear Round, " +
+ "Android Wear Round Chin, Android Wear Square, " +
+ "Galaxy Nexus, Nexus 10, Nexus 4, Nexus 5, Nexus 5X, Nexus 6, Nexus 6P, Nexus 7, " +
+ "Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
+ listDisplayNames(dm.getDevices(DeviceFilter.VENDOR)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals("Nexus One",
+ listDisplayName(dm.getDevice("Nexus One", "Google")));
+
+ assertEquals(
+ "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
+ "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
+ "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
+ "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet), Android TV (1080p), "
+ +
+ "Android TV (720p), Android Wear Round, Android Wear Round Chin, " +
+ "Android Wear Square, Galaxy Nexus, Nexus 10, Nexus 4, Nexus 5, " +
+ "Nexus 5X, Nexus 6, Nexus 6P, Nexus 7, Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
+ listDisplayNames(dm.getDevices(DeviceManager.ALL_DEVICES)).toString());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDevice() {
+ // get a definition from the bundled devices.xml file
+ Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
+ assertEquals("7\" WSVGA (Tablet)", d1.getDisplayName());
+ assertEquals("", log.toString());
+
+ // get a definition from the bundled nexus.xml file
+ Device d2 = dm.getDevice("Nexus One", "Google");
+ assertEquals("Nexus One", d2.getDisplayName());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDevices_UserDevice() {
+
+ Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
+
+ Builder b = new Device.Builder(d1);
+ b.setId("MyCustomTablet");
+ b.setName("My Custom Tablet");
+ b.setManufacturer("OEM");
+
+ Device d2 = b.build();
+
+ dm.addUserDevice(d2);
+ dm.saveUserDevices();
+
+ assertEquals("My Custom Tablet", dm.getDevice("MyCustomTablet", "OEM").getDisplayName());
+ assertEquals("", log.toString());
+
+ // create a new device manager, forcing it reload all files
+ dm = null;
+ DeviceManager dm2 = createDeviceManager();
+
+ assertEquals("My Custom Tablet", dm2.getDevice("MyCustomTablet", "OEM").getDisplayName());
+ assertEquals("", log.toString());
+
+ // 1 user device defined in the test's custom .android home folder
+ assertEquals("[My Custom Tablet]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.USER)).toString());
+ assertEquals("", log.toString());
+
+ // no system-images devices defined in the SDK by default
+ assertEquals("[]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.SYSTEM_IMAGES)).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from devices.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+ assertEquals(
+ "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
+ "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
+ "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
+ "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet)]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.DEFAULT)).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from the nexus.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
+ assertEquals("[Android TV (1080p), Android TV (720p), Android Wear Round, " +
+ "Android Wear Round Chin, Android Wear Square, Galaxy Nexus, " +
+ "Nexus 10, Nexus 4, Nexus 5, Nexus 5X, Nexus 6, Nexus 6P, Nexus 7, Nexus 7 (2012), " +
+ "Nexus 9, Nexus One, Nexus S]",
+ listDisplayNames(dm2.getDevices(DeviceFilter.VENDOR)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals(
+ "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
+ "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
+ "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
+ "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet), Android TV (1080p), "
+ +
+ "Android TV (720p), Android Wear Round, Android Wear Round Chin, " +
+ "Android Wear Square, Galaxy Nexus, My Custom Tablet, Nexus 10, " +
+ "Nexus 4, Nexus 5, Nexus 5X, Nexus 6, Nexus 6P, Nexus 7, Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
+ listDisplayNames(dm2.getDevices(DeviceManager.ALL_DEVICES)).toString());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDevices_SysImgDevice() throws Exception {
+ // this adds a devices.xml with one device
+ makeSystemImageFolder(TARGET_DIR_NAME_0, "tag-1", "x86");
+
+ // no user devices defined in the test's custom .android home folder
+ assertEquals("[]", listDisplayNames(dm.getDevices(DeviceFilter.USER)).toString());
+ assertEquals("", log.toString());
+
+ // find the system-images specific device added by makeSystemImageFolder above
+ // using both the getDevices() API and the device-specific getDevice() API.
+ assertEquals("[Mock Tag 1 Device Name]",
+ listDisplayNames(dm.getDevices(DeviceFilter.SYSTEM_IMAGES)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals("Mock Tag 1 Device Name",
+ listDisplayName(dm.getDevice("MockDevice-tag-1", "Mock Tag 1 OEM")));
+
+ // this list comes from devices.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+ assertEquals(
+ "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
+ "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
+ "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
+ "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet)]",
+ listDisplayNames(dm.getDevices(DeviceFilter.DEFAULT)).toString());
+ assertEquals("", log.toString());
+
+ // this list comes from the nexus.xml bundled in the JAR
+ // cf /sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
+ assertEquals(
+ "[Android TV (1080p), Android TV (720p), Android Wear Round, Android Wear Round Chin, "
+ +
+ "Android Wear Square, Galaxy Nexus, Nexus 10, Nexus 4, Nexus 5, Nexus 5X, Nexus 6, Nexus 6P, Nexus 7, "
+ +
+ "Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
+ listDisplayNames(dm.getDevices(DeviceFilter.VENDOR)).toString());
+ assertEquals("", log.toString());
+
+ assertEquals(
+ "[10.1\" WXGA (Tablet), 2.7\" QVGA, 2.7\" QVGA slider, 3.2\" HVGA slider (ADP1), " +
+ "3.2\" QVGA (ADP2), 3.3\" WQVGA, 3.4\" WQVGA, 3.7\" FWVGA slider, " +
+ "3.7\" WVGA (Nexus One), 4\" WVGA (Nexus S), 4.65\" 720p (Galaxy Nexus), " +
+ "4.7\" WXGA, 5.1\" WVGA, 5.4\" FWVGA, 7\" WSVGA (Tablet), Android TV (1080p), "
+ +
+ "Android TV (720p), Android Wear Round, Android Wear Round Chin, " +
+ "Android Wear Square, Galaxy Nexus, Mock Tag 1 Device Name, Nexus 10, Nexus 4, "
+ +
+ "Nexus 5, Nexus 5X, Nexus 6, Nexus 6P, Nexus 7, Nexus 7 (2012), Nexus 9, Nexus One, Nexus S]",
+ listDisplayNames(dm.getDevices(DeviceManager.ALL_DEVICES)).toString());
+ assertEquals("", log.toString());
+ }
+
+ public final void testGetDeviceStatus() {
+ // get a definition from the bundled devices.xml file
+ assertEquals(DeviceStatus.EXISTS,
+ dm.getDeviceStatus("7in WSVGA (Tablet)", "Generic"));
+
+ // get a definition from the bundled oem file
+ assertEquals(DeviceStatus.EXISTS,
+ dm.getDeviceStatus("Nexus One", "Google"));
+
+ // try a device that does not exist
+ assertEquals(DeviceStatus.MISSING,
+ dm.getDeviceStatus("My Device", "Custom OEM"));
+ }
+
+ public final void testHasHardwarePropHashChanged_Generic() {
+ final Device d1 = dm.getDevice("7in WSVGA (Tablet)", "Generic");
+
+ assertEquals("MD5:750a657019b49e621c42ce9a20c2cc30",
+ DeviceManager.hasHardwarePropHashChanged(
+ d1,
+ "invalid"));
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(d1, "MD5:750a657019b49e621c42ce9a20c2cc30"));
+
+ // change the device hardware props, this should change the hash
+ d1.getDefaultHardware().setNav(Navigation.TRACKBALL);
+
+ assertEquals("MD5:9c4dd5018987da51f7166f139f4361a2",
+ DeviceManager.hasHardwarePropHashChanged(d1, "MD5:750a657019b49e621c42ce9a20c2cc30"));
+
+ // change the property back, should revert its hash to the previous one
+ d1.getDefaultHardware().setNav(Navigation.NONAV);
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(d1, "MD5:750a657019b49e621c42ce9a20c2cc30"));
+ }
+
+ public final void testHasHardwarePropHashChanged_Oem() {
+ final Device d2 = dm.getDevice("Nexus One", "Google");
+
+ assertEquals("MD5:36362a51e6c830c2ab515a312c9ecbff",
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "invalid"));
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "MD5:36362a51e6c830c2ab515a312c9ecbff"));
+
+ // change the device hardware props, this should change the hash
+ d2.getDefaultHardware().setKeyboard(Keyboard.QWERTY);
+
+ assertEquals("MD5:f8f4b390755f2f58dfeb7d3020cd87db",
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "MD5:36362a51e6c830c2ab515a312c9ecbff"));
+
+ // change the property back, should revert its hash to the previous one
+ d2.getDefaultHardware().setKeyboard(Keyboard.NOKEY);
+
+ assertEquals(null,
+ DeviceManager.hasHardwarePropHashChanged(
+ d2,
+ "MD5:36362a51e6c830c2ab515a312c9ecbff"));
+ }
+
+ public final void testDeviceOverrides() throws Exception {
+ try {
+ File location = getSdkHandler().getLocation();
+ FakePackage p = new FakePackage("dummy", new Revision(1), null);
+ SchemaModule module = AndroidSdkHandler.getAddonModule();
+ DetailsTypes.AddonDetailsType details = ((AddonFactory) module.createLatestFactory())
+ .createAddonDetailsType();
+ details.setApiLevel(22);
+ details.setVendor(SystemImage.DEFAULT_TAG);
+ p.setTypeDetails((TypeDetails)details);
+ SystemImage imageWithDevice = new SystemImage(
+ new File(location, "system-images/android-22/android-wear/x86"),
+ IdDisplay.create("android-wear", "android-wear"),
+ IdDisplay.create("Google", "Google1"),
+ "x86", new File[]{}, p);
+ DeviceManager manager = DeviceManager.createInstance(location, log);
+ int count = manager.getDevices(EnumSet.allOf(DeviceFilter.class)).size();
+ Device d = manager.getDevice("wear_round", "Google");
+ assertEquals(d.getDisplayName(), "Android Wear Round");
+
+ makeSystemImageFolder(imageWithDevice, "wear_round");
+ manager = DeviceManager.createInstance(location, log);
+
+ d = manager.getDevice("wear_round", "Google");
+ assertEquals(d.getDisplayName(), "Mock Android wear Device Name");
+
+ Device d1 = dm.getDevice("wear_round", "Google");
+
+ Builder b = new Device.Builder(d1);
+ b.setName("Custom");
+
+ Device d2 = b.build();
+
+ manager.addUserDevice(d2);
+ manager.saveUserDevices();
+
+ d = manager.getDevice("wear_round", "Google");
+ assertEquals(d.getDisplayName(), "Custom");
+ assertEquals(count, manager.getDevices(EnumSet.allOf(DeviceFilter.class)).size());
+ }
+ finally {
+ super.setUp();
+ }
+ }
+}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java b/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
rename to sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
diff --git a/base/sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java b/sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java
rename to sdklib/src/test/java/com/android/sdklib/devices/DeviceWriterTest.java
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockAddonTarget.java b/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockAddonTarget.java
new file mode 100644
index 0000000..338f7a6
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockAddonTarget.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.androidTarget;
+
+import com.android.annotations.NonNull;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A mock AddonTarget.
+ * This reimplements the minimum needed from the interface for our limited testing needs.
+ */
+public class MockAddonTarget implements IAndroidTarget {
+
+ private final IAndroidTarget mParentTarget;
+ private final int mRevision;
+ private final String mName;
+ private ISystemImage[] mSystemImages;
+ private ImmutableList<OptionalLibrary> mOptionalLibraries = ImmutableList.of();
+
+ public MockAddonTarget(String name, IAndroidTarget parentTarget, int revision) {
+ mName = name;
+ mParentTarget = parentTarget;
+ mRevision = revision;
+ }
+
+ @Override
+ public String getClasspathName() {
+ return getName();
+ }
+
+ @Override
+ public String getShortClasspathName() {
+ return getName();
+ }
+
+ @Override
+ public File getDefaultSkin() {
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return getName();
+ }
+
+ @Override
+ public String getFullName() {
+ return getName();
+ }
+
+ @Override
+ public String getLocation() {
+ return "/sdk/add-ons/addon-" + mName;
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getAdditionalLibraries() {
+ return mOptionalLibraries;
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getOptionalLibraries() {
+ return ImmutableList.of();
+ }
+
+ public void setOptionalLibraries(ImmutableList<OptionalLibrary> libraries) {
+ mOptionalLibraries = libraries;
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return mParentTarget;
+ }
+
+ @Override
+ public String getPath(int pathId) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ return null;
+ }
+
+ @Override @NonNull
+ public List<String> getBootClasspath() {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public String[] getPlatformLibraries() {
+ return null;
+ }
+
+ @Override
+ public String getProperty(String name) {
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return null;
+ }
+
+ @Override
+ public int getRevision() {
+ return mRevision;
+ }
+
+ @NonNull
+ @Override
+ public File[] getSkins() {
+ return FileOp.EMPTY_FILE_ARRAY;
+ }
+
+ @NonNull
+ @Override
+ public AndroidVersion getVersion() {
+ return mParentTarget.getVersion();
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public String getVendor() {
+ return mParentTarget.getVendor();
+ }
+
+ @Override
+ public String getVersionName() {
+ return String.format("mock-addon-%1$d", getVersion().getApiLevel());
+ }
+
+ @Override
+ public String hashString() {
+ return getVersionName();
+ }
+
+ /** Returns false for an addon. */
+ @Override
+ public boolean isPlatform() {
+ return false;
+ }
+
+ @Override
+ public boolean canRunOn(IAndroidTarget target) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public int compareTo(IAndroidTarget o) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public boolean hasRenderingLibrary() {
+ return false;
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockPlatformTarget.java b/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockPlatformTarget.java
new file mode 100644
index 0000000..39121ce
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/androidTarget/MockPlatformTarget.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2009 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.sdklib.internal.androidTarget;
+
+import com.android.annotations.NonNull;
+import com.android.repository.io.FileOp;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A mock PlatformTarget.
+ * This reimplements the minimum needed from the interface for our limited testing needs.
+ */
+public class MockPlatformTarget implements IAndroidTarget {
+
+ private final int mApiLevel;
+ private final int mRevision;
+ private ISystemImage[] mSystemImages;
+
+ public MockPlatformTarget(int apiLevel, int revision) {
+ mApiLevel = apiLevel;
+ mRevision = revision;
+ }
+
+ @Override
+ public String getClasspathName() {
+ return getName();
+ }
+
+ @Override
+ public String getShortClasspathName() {
+ return getName();
+ }
+
+ @Override
+ public File getDefaultSkin() {
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return getName();
+ }
+
+ @Override
+ public String getFullName() {
+ return getName();
+ }
+
+ @Override
+ public String getLocation() {
+ return "/sdk/platforms/android-" + getVersion().getApiString();
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getOptionalLibraries() {
+ return ImmutableList.of();
+ }
+
+ @NonNull
+ @Override
+ public List<OptionalLibrary> getAdditionalLibraries() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return null;
+ }
+
+ @Override
+ public String getPath(int pathId) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public File getFile(int pathId) {
+ return new File(getPath(pathId));
+ }
+
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ return null;
+ }
+
+ @Override @NonNull
+ public List<String> getBootClasspath() {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public String[] getPlatformLibraries() {
+ return null;
+ }
+
+ @Override
+ public String getProperty(String name) {
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return null;
+ }
+
+ @Override
+ public int getRevision() {
+ return mRevision;
+ }
+
+ @NonNull
+ @Override
+ public File[] getSkins() {
+ return FileOp.EMPTY_FILE_ARRAY;
+ }
+
+ /**
+ * Returns a vendor that depends on the parent *platform* API.
+ * This works well in Unit Tests where we'll typically have different
+ * platforms as unique identifiers.
+ */
+ @Override
+ public String getVendor() {
+ return "vendor " + Integer.toString(mApiLevel);
+ }
+
+ /**
+ * Create a synthetic name using the target API level.
+ */
+ @Override
+ public String getName() {
+ return "platform r" + Integer.toString(mApiLevel);
+ }
+
+ @NonNull
+ @Override
+ public AndroidVersion getVersion() {
+ return new AndroidVersion(mApiLevel, null /*codename*/);
+ }
+
+ @Override
+ public String getVersionName() {
+ return String.format("android-%1$d", mApiLevel);
+ }
+
+ @Override
+ public String hashString() {
+ return getVersionName();
+ }
+
+ /** Returns true for a platform. */
+ @Override
+ public boolean isPlatform() {
+ return true;
+ }
+
+ @Override
+ public boolean canRunOn(IAndroidTarget target) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public int compareTo(IAndroidTarget o) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ @Override
+ public boolean hasRenderingLibrary() {
+ return false;
+ }
+}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/build/DebugKeyProviderTest.java b/sdklib/src/test/java/com/android/sdklib/internal/build/DebugKeyProviderTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/internal/build/DebugKeyProviderTest.java
rename to sdklib/src/test/java/com/android/sdklib/internal/build/DebugKeyProviderTest.java
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/build/SymbolLoaderTest.java b/sdklib/src/test/java/com/android/sdklib/internal/build/SymbolLoaderTest.java
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/internal/build/SymbolLoaderTest.java
rename to sdklib/src/test/java/com/android/sdklib/internal/build/SymbolLoaderTest.java
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/build/SymbolWriterTest.java b/sdklib/src/test/java/com/android/sdklib/internal/build/SymbolWriterTest.java
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/internal/build/SymbolWriterTest.java
rename to sdklib/src/test/java/com/android/sdklib/internal/build/SymbolWriterTest.java
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java
new file mode 100644
index 0000000..f0e25ef
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/DownloadCacheTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.AndroidLocationTestCase;
+import com.android.sdklib.internal.repository.DownloadCache.Strategy;
+import com.android.utils.Pair;
+import com.google.common.base.Charsets;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.message.BasicStatusLine;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DownloadCacheTest extends AndroidLocationTestCase {
+
+ private MockFileOp mFileOp;
+ private MockMonitor mMonitor;
+
+ /**
+ * A private version of DownloadCache that never calls {@link UrlOpener}.
+ */
+ private static class NoDownloadCache extends DownloadCache {
+
+ private final Map<String, Pair<InputStream, HttpResponse>> mReplies =
+ new HashMap<String, Pair<InputStream,HttpResponse>>();
+
+ public NoDownloadCache(@NonNull Strategy strategy) {
+ super(strategy);
+ }
+
+ public NoDownloadCache(@NonNull FileOp fileOp, @NonNull Strategy strategy) {
+ super(fileOp, strategy);
+ }
+
+ @Override
+ protected Pair<InputStream, HttpResponse> openUrl(
+ @NonNull String url,
+ boolean needsMarkResetSupport,
+ @NonNull ITaskMonitor monitor,
+ @Nullable Header[] headers) throws IOException, CanceledByUserException {
+
+ Pair<InputStream, HttpResponse> reply = mReplies.get(url);
+ if (reply != null) {
+ return reply;
+ }
+
+ // http-client's behavior is to return a FNF instead of 404.
+ throw new FileNotFoundException(url);
+ }
+
+ public void registerResponse(@NonNull String url, int httpCode, @Nullable String content) {
+ InputStream is = null;
+ if (content != null) {
+ is = new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
+ }
+
+ ProtocolVersion p = new ProtocolVersion("HTTP", 1, 1);
+ StatusLine statusLine = new BasicStatusLine(p, httpCode, "Code " + httpCode);
+ HttpResponse httpResponse = new BasicHttpResponse(statusLine);
+ Pair<InputStream, HttpResponse> reply = Pair.of(is, httpResponse);
+
+ mReplies.put(url, reply);
+ }
+
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mFileOp = new MockFileOp();
+ mMonitor = new MockMonitor();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testMissingResource() throws Exception {
+ // Downloads must fail when using the only-cache strategy and there's nothing in the cache.
+ // In that case, it returns null to indicate the resource is simply not found.
+ // Since the mock implementation always returns a 404 and no stream, there is no
+ // difference between the various cache strategies.
+
+ mFileOp.reset();
+ NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
+ InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is1);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first
+ mFileOp.reset();
+ NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
+
+ try {
+ d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ fail("Expected: NoDownloadCache.openCachedUrl should have thrown a FileNotFoundException");
+ } catch (FileNotFoundException e) {
+ assertEquals("http://www.example.com/download1.xml", e.getMessage());
+ }
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ // Try again but this time we'll define a 404 reply to test the rest of the code path.
+ mFileOp.reset();
+ d2.registerResponse("http://www.example.com/download1.xml", 404, null);
+ InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is2);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ mFileOp.reset();
+ NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
+ d3.registerResponse("http://www.example.com/download1.xml", 404, null);
+ InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is3);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ mFileOp.reset();
+ NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d4.registerResponse("http://www.example.com/download1.xml", 404, null);
+ InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is4);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+ }
+
+ public void testExistingResource() throws Exception {
+ // The resource exists but only-cache doesn't hit the network so it will
+ // fail when the resource is not cached.
+ mFileOp.reset();
+ NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
+ d1.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNull(is1);
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first
+ mFileOp.reset();
+ NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
+ d2.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNotNull(is2);
+ assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals("[]", Arrays.toString(mFileOp.getOutputStreams()));
+
+ mFileOp.reset();
+ NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
+ d3.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNotNull(is3);
+ assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals(
+ "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>, " +
+ "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
+ "#<creation timestamp>\n" +
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n" +
+ "'>]",
+ sanitize(d3, Arrays.toString(mFileOp.getOutputStreams())));
+
+ mFileOp.reset();
+ NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d4.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah");
+ InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ assertNotNull(is4);
+ assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertEquals(
+ "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>, " +
+ "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
+ "#<creation timestamp>\n" +
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n" +
+ "'>]",
+ sanitize(d4, Arrays.toString(mFileOp.getOutputStreams())));
+ }
+
+ public void testCachedResource() throws Exception {
+ mFileOp.reset();
+ NoDownloadCache d1 = new NoDownloadCache(mFileOp, Strategy.ONLY_CACHE);
+ d1.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(
+ FileOpUtils.append(d1.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d1.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Only-cache strategy returns the value from the cache, not the actual resource.
+ assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is1, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot()));
+ // The cache hasn't been modified, only read
+ assertEquals("[]", sanitize(d1, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // Direct ignores the cache.
+ mFileOp.reset();
+ NoDownloadCache d2 = new NoDownloadCache(mFileOp, Strategy.DIRECT);
+ d2.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d2.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d2.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Direct strategy ignores the cache.
+ assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d2.getCacheRoot()));
+ // Direct strategy doesn't update the cache.
+ assertEquals("[]", sanitize(d2, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // Serve-cache reads from the cache if available, ignoring its freshness (here the timestamp
+ // is way older than the 10-minute freshness encoded in the DownloadCache.)
+ mFileOp.reset();
+ NoDownloadCache d3 = new NoDownloadCache(mFileOp, Strategy.SERVE_CACHE);
+ d3.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d3.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d3.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // We get content from the cache.
+ assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d3.getCacheRoot()));
+ // Cache isn't updated since nothing fresh was read.
+ assertEquals("[]", sanitize(d3, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // fresh-cache reads the cache, finds it stale (here the timestamp
+ // is way older than the 10-minute freshness encoded in the DownloadCache)
+ // and will fetch the new resource instead and update the cache.
+ mFileOp.reset();
+ NoDownloadCache d4 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d4.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d4.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ 123456L,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d4.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ 123456L,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Cache is discarded, actual resource is returned.
+ assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d4.getCacheRoot()));
+ // Cache isn updated since something fresh was read.
+ assertEquals(
+ "[<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'This is the new content'>, " +
+ "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" +
+ "#<creation timestamp>\n" +
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n" +
+ "'>]",
+ sanitize(d4, Arrays.toString(mFileOp.getOutputStreams())));
+
+ // fresh-cache reads the cache, finds it still valid stale (less than 10-minute old),
+ // and uses the cached resource.
+ mFileOp.reset();
+ NoDownloadCache d5 = new NoDownloadCache(mFileOp, Strategy.FRESH_CACHE);
+ d5.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d5.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")),
+ System.currentTimeMillis() - 1000,
+ "This is the cached content");
+ mFileOp.recordExistingFile(
+ mFileOp.getAgnosticAbsPath(FileOpUtils.append(d5.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")),
+ System.currentTimeMillis() - 1000,
+ "URL=http\\://www.example.com/download1.xml\n" +
+ "Status-Code=200\n");
+ InputStream is5 = d5.openCachedUrl("http://www.example.com/download1.xml", mMonitor);
+ // Cache is used.
+ assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is5, Charsets.UTF_8)).readLine());
+ assertEquals("", mMonitor.getAllCapturedLogs());
+ assertTrue(mFileOp.hasRecordedExistingFolder(d5.getCacheRoot()));
+ // Cache isn't updated since nothing fresh was read.
+ assertEquals("[]", sanitize(d5, Arrays.toString(mFileOp.getOutputStreams())));
+ }
+
+ // --------
+
+ @Nullable
+ private String sanitize(@NonNull DownloadCache dc, @Nullable String msg) {
+ if (msg != null) {
+ msg = msg.replace("\r\n", "\n");
+
+ String absRoot = mFileOp.getAgnosticAbsPath(dc.getCacheRoot());
+ msg = msg.replace(absRoot, "$CACHE");
+
+ // Cached files also contain a creation timestamp which we need to find and remove.
+ msg = msg.replaceAll("\n#[A-Z][A-Za-z0-9: ]+20[0-9]{2}\n", "\n#<creation timestamp>\n");
+ }
+ return msg;
+ }
+
+}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java
rename to sdklib/src/test/java/com/android/sdklib/internal/repository/MockMonitor.java
diff --git a/base/sdklib/src/test/java/com/android/sdklib/mock/MockLog.java b/sdklib/src/test/java/com/android/sdklib/mock/MockLog.java
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/mock/MockLog.java
rename to sdklib/src/test/java/com/android/sdklib/mock/MockLog.java
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java
new file mode 100644
index 0000000..5a3421a
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgDescTest.java
@@ -0,0 +1,882 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.descriptors;
+
+import com.android.repository.Revision;
+import com.android.repository.api.ProgressIndicator;
+import com.android.repository.io.FileOpUtils;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repositoryv2.IdDisplay;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Arrays;
+
+public class PkgDescTest extends TestCase {
+
+ public final File mRoot = new File("/sdk");
+
+ public final void testPkgDescTool_NotPreview() {
+ IPkgDesc p = PkgDesc.Builder.newTool(
+ new Revision(1, 2, 3),
+ new Revision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_TOOLS, p.getType());
+
+ assertEquals(new Revision(1, 2, 3), p.getRevision());
+ assertFalse (p.getRevision().isPreview());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertTrue (p.hasMinPlatformToolsRev());
+ assertEquals(new Revision(5, 6, 7, 8), p.getMinPlatformToolsRev());
+
+ assertEquals("tools", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=tools Rev=1.2.3 MinPlatToolsRev=5.6.7 rc8>", p.toString());
+ assertEquals("Android SDK Tools 1.2.3", p.getListDescription());
+ }
+
+ public final void testPkgDescTool_Preview() {
+ IPkgDesc p = PkgDesc.Builder.newTool(
+ new Revision(1, 2, 3, 4),
+ new Revision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_TOOLS, p.getType());
+
+ assertEquals(new Revision(1, 2, 3, 4), p.getRevision());
+ assertTrue (p.getRevision().isPreview());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertTrue (p.hasMinPlatformToolsRev());
+ assertEquals(new Revision(5, 6, 7, 8), p.getMinPlatformToolsRev());
+
+ assertEquals("tools-preview", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=tools Rev=1.2.3 rc4 MinPlatToolsRev=5.6.7 rc8>", p.toString());
+ assertEquals("Android SDK Tools 1.2.3 rc4", p.getListDescription());
+ }
+
+ public final void testPkgDescTool_Update() {
+ final Revision min5670 = new Revision(5, 6, 7, 0);
+ final IPkgDesc f123 =
+ PkgDesc.Builder.newTool(new Revision(1, 2, 3, 0), min5670).create();
+ final IPkgDesc f123b =
+ PkgDesc.Builder.newTool(new Revision(1, 2, 3, 0), min5670).create();
+
+ // can't update itself
+ assertFalse(f123 .isUpdateFor(f123b));
+ assertFalse(f123b.isUpdateFor(f123));
+ assertTrue (f123 .compareTo(f123b) == 0);
+ assertTrue (f123b.compareTo(f123 ) == 0);
+
+ // min-platform-tools-rev isn't used for updates checks
+ final Revision min5680 = new Revision(5, 6, 8, 0);
+ final IPkgDesc f123c =
+ PkgDesc.Builder.newTool(new Revision(1, 2, 3, 0), min5680).create();
+ assertFalse(f123c.isUpdateFor(f123));
+ // but it's used for comparisons
+ assertTrue (f123c.compareTo(f123) > 0);
+
+ // full revision is used for updated checks
+ final IPkgDesc f124 =
+ PkgDesc.Builder.newTool(new Revision(1, 2, 4, 0), min5670).create();
+ assertTrue (f124.isUpdateFor(f123));
+ assertFalse(f123.isUpdateFor(f124));
+ assertTrue (f124.compareTo(f123) > 0);
+
+ final IPkgDesc f122 =
+ PkgDesc.Builder.newTool(new Revision(1, 2, 2, 0), min5670).create();
+ assertTrue (f123.isUpdateFor(f122));
+ assertFalse(f122.isUpdateFor(f123));
+ assertTrue (f122.compareTo(f123) < 0);
+
+ // previews are not updated by final packages
+ final Revision min5671 = new Revision(5, 6, 7, 1);
+ final IPkgDesc p1231 =
+ PkgDesc.Builder.newTool(new Revision(1, 2, 3, 1), min5671).create();
+ assertFalse(p1231.isUpdateFor(f122));
+ assertFalse(f122 .isUpdateFor(p1231));
+ assertFalse(p1231.isUpdateFor(f122, Revision.PreviewComparison.COMPARE_NUMBER));
+ assertFalse(p1231.isUpdateFor(f122, Revision.PreviewComparison.COMPARE_TYPE));
+ // ...unless we ignore them explicitly
+ assertTrue(p1231.isUpdateFor(f122, Revision.PreviewComparison.IGNORE));
+
+ // but previews are used for comparisons
+ assertTrue (p1231.compareTo(f122 ) > 0);
+ assertTrue (f123 .compareTo(p1231) > 0);
+
+ final IPkgDesc p1232 =
+ PkgDesc.Builder.newTool(new Revision(1, 2, 3, 2), min5671).create();
+ assertTrue (p1232.isUpdateFor(p1231));
+ assertFalse(p1231.isUpdateFor(p1232));
+ assertTrue (p1232.compareTo(p1231) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescPlatformTool_NotPreview() {
+ IPkgDesc p = PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 3)).create();
+
+ assertEquals(PkgType.PKG_PLATFORM_TOOLS, p.getType());
+
+ assertEquals(new Revision(1, 2, 3), p.getRevision());
+ assertFalse (p.getRevision().isPreview());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("platform-tools", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "platform-tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=platform_tools Rev=1.2.3>", p.toString());
+ assertEquals("Android SDK Platform-Tools 1.2.3", p.getListDescription());
+ }
+
+ public final void testPkgDescPlatformTool_Preview() {
+ IPkgDesc p = PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 3, 4)).create();
+
+ assertEquals(PkgType.PKG_PLATFORM_TOOLS, p.getType());
+
+ assertEquals(new Revision(1, 2, 3, 4), p.getRevision());
+ assertTrue (p.getRevision().isPreview());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("platform-tools-preview", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "platform-tools"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=platform_tools Rev=1.2.3 rc4>", p.toString());
+ assertEquals("Android SDK Platform-Tools 1.2.3 rc4", p.getListDescription());
+ }
+
+ public final void testPkgDescPlatformTool_Update() {
+ final IPkgDesc f123 =
+ PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 3, 0)).create();
+ final IPkgDesc f123b =
+ PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 3, 0)).create();
+
+ // can't update itself
+ assertFalse(f123 .isUpdateFor(f123b));
+ assertFalse(f123b.isUpdateFor(f123));
+ assertTrue (f123 .compareTo(f123b) == 0);
+ assertTrue (f123b.compareTo(f123 ) == 0);
+
+ // full revision is used for updated checks
+ final IPkgDesc f124 =
+ PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 4, 0)).create();
+ assertTrue (f124.isUpdateFor(f123));
+ assertFalse(f123.isUpdateFor(f124));
+ assertTrue (f124.compareTo(f123) > 0);
+
+ final IPkgDesc f122 =
+ PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 2, 0)).create();
+ assertTrue (f123.isUpdateFor(f122));
+ assertFalse(f122.isUpdateFor(f123));
+ assertTrue (f122.compareTo(f123) < 0);
+
+ // previews are not updated by final packages
+ final IPkgDesc p1231 =
+ PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 3, 1)).create();
+ assertFalse(p1231.isUpdateFor(f122));
+ assertFalse(f122 .isUpdateFor(p1231));
+ // but previews are used for comparisons
+ assertTrue (p1231.compareTo(f122 ) > 0);
+ assertTrue (f123 .compareTo(p1231) > 0);
+
+ final IPkgDesc p1232 =
+ PkgDesc.Builder.newPlatformTool(new Revision(1, 2, 3, 2)).create();
+ assertTrue (p1232.isUpdateFor(p1231));
+ assertFalse(p1231.isUpdateFor(p1232));
+ assertTrue (p1232.compareTo(p1231) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescDoc() throws Exception {
+ IPkgDesc p =
+ PkgDesc.Builder.newDoc(new AndroidVersion("19"), new Revision(1)).create();
+
+ assertEquals(PkgType.PKG_DOC, p.getType());
+
+ assertEquals(new Revision(1), p.getRevision());
+
+ assertTrue(p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull(p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull(p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull(p.getMinPlatformToolsRev());
+
+ assertEquals("doc", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "docs"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=doc Android=API 19 Rev=1>", p.toString());
+ assertEquals("Documentation for Android SDK", p.getListDescription());
+ }
+
+ public final void testPkgDescDoc_Update() throws Exception {
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final Revision rev1 = new Revision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newDoc(api19, rev1).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newDoc(api19, rev1).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ final IPkgDesc p19_2 = PkgDesc.Builder.newDoc(api19, new Revision(2)).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ final IPkgDesc p18_1 = PkgDesc.Builder.newDoc(new AndroidVersion("18"), rev1).create();
+ assertTrue (p19_1.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_1));
+ assertTrue (p19_1.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescBuildTool_NotPreview() {
+ IPkgDesc p = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 3)).create();
+
+ assertEquals(PkgType.PKG_BUILD_TOOLS, p.getType());
+
+ assertEquals(new Revision(1, 2, 3), p.getRevision());
+ assertFalse (p.getRevision().isPreview());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("build-tools-1.2.3", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "build-tools", "build-tools-1.2.3"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=build_tools Rev=1.2.3>", p.toString());
+ assertEquals("Android SDK Build-Tools 1.2.3", p.getListDescription());
+ }
+
+ public final void testPkgDescBuildTool_Preview() {
+ IPkgDesc p = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 3, 4)).create();
+
+ assertEquals(PkgType.PKG_BUILD_TOOLS, p.getType());
+
+ assertEquals(new Revision(1, 2, 3, 4), p.getRevision());
+ assertTrue (p.getRevision().isPreview());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("build-tools-1.2.3-preview", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "build-tools", "build-tools-1.2.3-preview"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=build_tools Rev=1.2.3 rc4>", p.toString());
+ assertEquals("Android SDK Build-Tools 1.2.3 rc4", p.getListDescription());
+ }
+
+ public final void testPkgDescBuildTool_Update() {
+ final IPkgDesc f123 = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 3, 0)).create();
+ final IPkgDesc f123b = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 3, 0)).create();
+
+ // can't update itself
+ assertFalse(f123 .isUpdateFor(f123b));
+ assertFalse(f123b.isUpdateFor(f123));
+ assertTrue (f123 .compareTo(f123b) == 0);
+ assertTrue (f123b.compareTo(f123 ) == 0);
+
+ // build-tools is different as full revisions are installed side by side
+ // so they don't update each other (except for the preview bit, see below.)
+ final IPkgDesc f124 = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 4, 0)).create();
+ assertFalse(f124.isUpdateFor(f123));
+ assertFalse(f123.isUpdateFor(f124));
+ // comparison is still done on the full revision.
+ assertTrue (f124.compareTo(f123) > 0);
+
+ final IPkgDesc f122 = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 2, 0)).create();
+ assertFalse(f123.isUpdateFor(f122));
+ assertFalse(f122.isUpdateFor(f123));
+ assertTrue (f122.compareTo(f123) < 0);
+
+ // previews are not updated by final packages
+ final IPkgDesc p1231 = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 3, 1)).create();
+ assertFalse(p1231.isUpdateFor(f122));
+ assertFalse(f122 .isUpdateFor(p1231));
+ // but previews are used for comparisons
+ assertTrue (p1231.compareTo(f122 ) > 0);
+ assertTrue (f123 .compareTo(p1231) > 0);
+
+ // previews do update other packages that have the same major.minor.micro.
+ final IPkgDesc p1232 = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 3, 2)).create()
+ ;
+ assertTrue (p1232.isUpdateFor(p1231));
+ assertFalse(p1231.isUpdateFor(p1232));
+ assertTrue (p1232.compareTo(p1231) > 0);
+
+ final IPkgDesc p1222 = PkgDesc.Builder.newBuildTool(new Revision(1, 2, 2, 2)).create();
+ assertFalse(p1232.isUpdateFor(p1222));
+ }
+
+ //----
+
+ public final void testPkgDescExtra() {
+ IdDisplay vendor = IdDisplay.create("vendor", "The Vendor");
+ IPkgDesc p = PkgDesc.Builder
+ .newExtra(vendor,
+ "extra_path",
+ "My Extra",
+ new String[] { "old_path1", "old_path2" },
+ new Revision(1, 2, 3))
+ .create();
+
+ assertEquals(PkgType.PKG_EXTRA, p.getType());
+
+ assertEquals(new Revision(1, 2, 3), p.getRevision());
+
+ assertFalse(p.hasAndroidVersion());
+ assertNull (p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("extra_path", p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("extra-vendor-extra_path", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "extras", "vendor", "extra_path"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=extra Vendor=vendor [The Vendor] Path=extra_path Rev=1.2.3>", p.toString());
+ assertEquals("My Extra, rev 1.2.3", p.getListDescription());
+
+ IPkgDescExtra e = (IPkgDescExtra) p;
+ assertEquals("vendor [The Vendor]", e.getVendor().toString());
+ assertEquals("extra_path", e.getPath());
+ assertEquals("[old_path1, old_path2]", Arrays.toString(e.getOldPaths()));
+ }
+
+ public final void testPkgDescExtra_Update() {
+ IdDisplay vendor = IdDisplay.create("vendor", "The Vendor");
+ final Revision rev123 = new Revision(1, 2, 3);
+ final IPkgDesc p123 = PkgDesc.Builder
+ .newExtra(vendor, "extra_path", "My Extra", new String[0], rev123)
+ .create();
+ final IPkgDesc p123b = PkgDesc.Builder
+ .newExtra(vendor, "extra_path", "My Extra", new String[0], rev123)
+ .create();
+
+ // can't update itself
+ assertFalse(p123 .isUpdateFor(p123b));
+ assertFalse(p123b.isUpdateFor(p123));
+ assertTrue (p123 .compareTo(p123b) == 0);
+ assertTrue (p123b.compareTo(p123 ) == 0);
+
+ // updates a lesser revision of the same vendor/path
+ final Revision rev124 = new Revision(1, 2, 4);
+ final IPkgDesc p124 = PkgDesc.Builder
+ .newExtra(vendor, "extra_path", "My Extra", new String[0], rev124)
+ .create();
+ assertTrue (p124.isUpdateFor(p123));
+ assertTrue (p124.compareTo(p123) > 0);
+
+ // does not update a different vendor
+ IdDisplay vendor2 = IdDisplay.create("different-vendor", "Not the same Vendor");
+ final IPkgDesc a124 = PkgDesc.Builder
+ .newExtra(vendor2, "extra_path", "My Extra", new String[0], rev124)
+ .create();
+ assertFalse(a124.isUpdateFor(p123));
+ assertTrue (a124.compareTo(p123) < 0);
+
+ // does not update a different extra path
+ final IPkgDesc n124 = PkgDesc.Builder
+ .newExtra(vendor, "no_va", "Oye Como Va", new String[0], rev124)
+ .create();
+ assertFalse(n124.isUpdateFor(p123));
+ assertTrue (n124.compareTo(p123) > 0);
+ // unless the old_paths mechanism is used to provide a way to update the path
+ final IPkgDesc o124 = PkgDesc.Builder
+ .newExtra(vendor, "no_va", "Oye Como Va", new String[] { "extra_path" }, rev124)
+ .create();
+ assertTrue (o124.isUpdateFor(p123));
+ assertTrue (o124.compareTo(p123) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescSource() throws Exception {
+ IPkgDesc p =
+ PkgDesc.Builder.newSource(new AndroidVersion("19"), new Revision(1)).create();
+
+ assertEquals(PkgType.PKG_SOURCE, p.getType());
+
+ assertEquals(new Revision(1), p.getRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("source-19", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "sources", "android-19"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals("<PkgDesc Type=source Android=API 19 Rev=1>", p.toString());
+ assertEquals("Sources for Android 19", p.getListDescription());
+ }
+
+ public final void testPkgDescSource_Update() throws Exception {
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final Revision rev1 = new Revision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newSource(api19, rev1).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newSource(api19, rev1).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 = PkgDesc.Builder.newSource(api19, new Revision(2)).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 = PkgDesc.Builder.newSource(new AndroidVersion("18"), rev1).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescSample() throws Exception {
+ IPkgDesc p = PkgDesc.Builder.newSample(new AndroidVersion("19"),
+ new Revision(1),
+ new Revision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_SAMPLE, p.getType());
+
+ assertEquals(new Revision(1), p.getRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertFalse(p.hasPath());
+ assertNull (p.getPath());
+
+ assertTrue (p.hasMinToolsRev());
+ assertEquals(new Revision(5, 6, 7, 8), p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("sample-19", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "samples", "android-19"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=sample Android=API 19 Rev=1 MinToolsRev=5.6.7 rc8>",
+ p.toString());
+ assertEquals("Samples for Android 19", p.getListDescription());
+ }
+
+ public final void testPkgDescSample_Update() throws Exception {
+ final Revision min5670 = new Revision(5, 6, 7, 0);
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final Revision rev1 = new Revision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newSample(api19, rev1, min5670).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newSample(api19, rev1, min5670).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // min-tools-rev isn't used for updates checks
+ final Revision min5680 = new Revision(5, 6, 8, 0);
+ final IPkgDesc p19_1c = PkgDesc.Builder.newSample(api19, rev1, min5680).create();
+ assertFalse(p19_1c.isUpdateFor(p19_1));
+ // but it's used for comparisons
+ assertTrue (p19_1c.compareTo(p19_1) > 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 =
+ PkgDesc.Builder.newSample(api19, new Revision(2), min5670).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 =
+ PkgDesc.Builder.newSample(new AndroidVersion("18"), rev1, min5670).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescPlatform() throws Exception {
+ IPkgDesc p = PkgDesc.Builder.newPlatform(new AndroidVersion("19"),
+ new Revision(1),
+ new Revision(5, 6, 7, 8)).create();
+
+ assertEquals(PkgType.PKG_PLATFORM, p.getType());
+
+ assertEquals(new Revision(1), p.getRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("android-19", p.getPath());
+
+ assertTrue (p.hasMinToolsRev());
+ assertEquals(new Revision(5, 6, 7, 8), p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("android-19", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "platforms", "android-19"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=platform Android=API 19 Path=android-19 Rev=1 MinToolsRev=5.6.7 rc8>",
+ p.toString());
+ assertEquals("Android SDK Platform 19", p.getListDescription());
+ }
+
+ public final void testPkgDescPlatform_Update() throws Exception {
+ final Revision min5670 = new Revision(5, 6, 7, 0);
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final Revision rev1 = new Revision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newPlatform(api19, rev1, min5670).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newPlatform(api19, rev1, min5670).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // min-tools-rev isn't used for updates checks
+ final Revision min5680 = new Revision(5, 6, 8, 0);
+ final IPkgDesc p19_1c = PkgDesc.Builder.newPlatform(api19, rev1, min5680).create();
+ assertFalse(p19_1c.isUpdateFor(p19_1));
+ // but it's used for comparisons
+ assertTrue (p19_1c.compareTo(p19_1) > 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 =
+ PkgDesc.Builder.newPlatform(api19, new Revision(2), min5670).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 =
+ PkgDesc.Builder.newPlatform(new AndroidVersion("18"), rev1, min5670).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+ }
+
+ //----
+
+ public final void testPkgDescAddon() throws Exception {
+ IdDisplay vendor = IdDisplay.create("vendor", "The Vendor");
+ IdDisplay name = IdDisplay.create("addon_name", "The Add-on");
+ IPkgDesc p1 = PkgDesc.Builder
+ .newAddon(new AndroidVersion("19"), new Revision(1), vendor, name)
+ .create();
+
+ assertEquals(PkgType.PKG_ADDON, p1.getType());
+
+ assertEquals(new Revision(1), p1.getRevision());
+
+ assertTrue (p1.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p1.getAndroidVersion());
+
+ assertTrue (p1.hasPath());
+ assertEquals("The Vendor:The Add-on:19", p1.getPath());
+
+ assertFalse(p1.hasMinToolsRev());
+ assertNull (p1.getMinToolsRev());
+
+ assertFalse(p1.hasMinPlatformToolsRev());
+ assertNull (p1.getMinPlatformToolsRev());
+
+ assertTrue(p1.hasVendor());
+ assertEquals(IdDisplay.create("vendor", "only the id is compared with"),
+ p1.getVendor());
+
+ assertEquals(IdDisplay.create("addon_name", "ignored"), p1.getName());
+
+ assertEquals("addon-addon_name-vendor-19", p1.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "add-ons", "addon-addon_name-vendor-19"),
+ p1.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=addon Android=API 19 Vendor=vendor [The Vendor] Path=The Vendor:The Add-on:19 Rev=1>",
+ p1.toString());
+ assertEquals("The Add-on, Android 19", p1.getListDescription());
+ }
+
+ public final void testPkgDescAddon_Update() throws Exception {
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final Revision rev1 = new Revision(1);
+ IdDisplay vendor = IdDisplay.create("vendor", "The Vendor");
+ IdDisplay name = IdDisplay.create("addon_name", "The Add-on");
+ final IPkgDesc p19_1 = PkgDesc.Builder.newAddon(api19, rev1, vendor, name)
+ .create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newAddon(api19, rev1, vendor, name)
+ .create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // updates a lesser revision of the same API
+ final Revision rev2 = new Revision(2);
+ final IPkgDesc p19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor, name)
+ .create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final AndroidVersion api18 = new AndroidVersion("18");
+ final IPkgDesc p18_1 = PkgDesc.Builder.newAddon(api18, rev2, vendor, name)
+ .create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+
+ // does not update a different vendor
+ IdDisplay vendor2 = IdDisplay.create("another_vendor", "Another Vendor");
+ final IPkgDesc a19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor2, name)
+ .create();
+ assertFalse(a19_2.isUpdateFor(p19_1));
+ assertTrue (a19_2.compareTo(p19_1) < 0);
+
+ // does not update a different add-on name
+ IdDisplay name2 = IdDisplay.create("another_name", "Another Add-on");
+ final IPkgDesc n19_2 = PkgDesc.Builder.newAddon(api19, rev2, vendor, name2)
+ .create();
+ assertFalse(n19_2.isUpdateFor(p19_1));
+ assertTrue (n19_2.compareTo(p19_1) < 0);
+ }
+
+ //----
+
+ public final void testPkgDescSysImg_Platform() throws Exception {
+ IdDisplay tag = IdDisplay.create("tag", "My Tag");
+ IPkgDesc p = PkgDesc.Builder.newSysImg(
+ new AndroidVersion("19"),
+ tag,
+ "eabi",
+ new Revision(1)).create();
+
+ assertEquals(PkgType.PKG_SYS_IMAGE, p.getType());
+
+ assertEquals(new Revision(1), p.getRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("eabi", p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertEquals("sys-img-eabi-tag-19", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "system-images", "android-19", "tag", "eabi"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=sys_image Android=API 19 Tag=tag [My Tag] Path=eabi Rev=1>",
+ p.toString());
+ assertEquals("eabi System Image, Android 19", p.getListDescription());
+ }
+
+ public final void testPkgDescSysImg_Platform_Update() throws Exception {
+ IdDisplay tag1 = IdDisplay.create("tag1", "My Tag 1");
+ final AndroidVersion api19 = new AndroidVersion("19");
+ final Revision rev1 = new Revision(1);
+ final IPkgDesc p19_1 = PkgDesc.Builder.newSysImg(api19, tag1, "eabi", rev1).create();
+ final IPkgDesc p19_1b = PkgDesc.Builder.newSysImg(api19, tag1, "eabi", rev1).create();
+
+ // can't update itself
+ assertFalse(p19_1 .isUpdateFor(p19_1b));
+ assertFalse(p19_1b.isUpdateFor(p19_1));
+ assertTrue (p19_1 .compareTo(p19_1b) == 0);
+ assertTrue (p19_1b.compareTo(p19_1 ) == 0);
+
+ // updates a lesser revision of the same API
+ final IPkgDesc p19_2 =
+ PkgDesc.Builder.newSysImg(api19, tag1, "eabi", new Revision(2)).create();
+ assertTrue (p19_2.isUpdateFor(p19_1));
+ assertTrue (p19_2.compareTo(p19_1) > 0);
+
+ // does not update a different API
+ final IPkgDesc p18_1 =
+ PkgDesc.Builder.newSysImg(new AndroidVersion("18"), tag1, "eabi", rev1).create();
+ assertFalse(p19_2.isUpdateFor(p18_1));
+ assertFalse(p18_1.isUpdateFor(p19_2));
+ assertTrue (p19_2.compareTo(p18_1) > 0);
+
+ // does not update a different ABI
+ final IPkgDesc p19_2c =
+ PkgDesc.Builder.newSysImg(api19, tag1, "ppc", new Revision(2)).create();
+ assertFalse(p19_2c.isUpdateFor(p19_1));
+ assertTrue (p19_2c.compareTo(p19_1) > 0);
+
+ // does not update a different tag
+ IdDisplay tag2 = IdDisplay.create("tag2", "My Tag 2");
+ final IPkgDesc p19_t2 =
+ PkgDesc.Builder.newSysImg(api19, tag2, "eabi", new Revision(2)).create();
+ assertFalse(p19_t2.isUpdateFor(p19_1));
+ assertTrue (p19_t2.compareTo(p19_1) > 0);
+ }
+
+ public final void testPkgDescSysImg_Addon() throws Exception {
+ IdDisplay vendor = IdDisplay.create("vendor", "The Vendor");
+ IdDisplay name = IdDisplay.create("addon_name", "The Add-on");
+ IPkgDesc p = PkgDesc.Builder.newAddonSysImg(
+ new AndroidVersion("19"),
+ vendor,
+ name,
+ "eabi",
+ new Revision(1)).create();
+
+ assertEquals(PkgType.PKG_ADDON_SYS_IMAGE, p.getType());
+
+ assertEquals(new Revision(1), p.getRevision());
+
+ assertTrue (p.hasAndroidVersion());
+ assertEquals(new AndroidVersion("19"), p.getAndroidVersion());
+
+ assertTrue (p.hasPath());
+ assertEquals("eabi", p.getPath());
+
+ assertFalse(p.hasMinToolsRev());
+ assertNull (p.getMinToolsRev());
+
+ assertFalse(p.hasMinPlatformToolsRev());
+ assertNull (p.getMinPlatformToolsRev());
+
+ assertTrue(p.hasVendor());
+ assertEquals(IdDisplay.create("vendor", "only the id is compared with"), p.getVendor());
+
+ assertTrue(p.hasTag());
+ assertEquals(IdDisplay.create("addon_name", "ignored"), p.getTag());
+
+ assertEquals("sys-img-eabi-addon-addon_name-vendor-19", p.getInstallId());
+ assertEquals(FileOpUtils.append(mRoot, "system-images", "addon-addon_name-vendor-19", "eabi"),
+ p.getCanonicalInstallFolder(mRoot));
+
+ assertEquals(
+ "<PkgDesc Type=addon_sys_image Android=API 19 Vendor=vendor [The Vendor] Tag=addon_name [The Add-on] Path=eabi Rev=1>",
+ p.toString());
+ assertEquals("The Vendor eabi System Image, Android 19", p.getListDescription());
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java
new file mode 100644
index 0000000..8000928
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/descriptors/PkgTypeTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.descriptors;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PkgTypeTest extends TestCase {
+
+ public final void testPkgTypeTool() {
+ PkgType p = PkgType.PKG_TOOLS;
+ assertFalse(p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertTrue (p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypePlatformTool() {
+ PkgType p = PkgType.PKG_PLATFORM_TOOLS;
+ assertFalse(p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeDoc() {
+ PkgType p = PkgType.PKG_DOC;
+ assertTrue (p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeBuildTool() {
+ PkgType p = PkgType.PKG_BUILD_TOOLS;
+
+ assertFalse(p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeExtra() {
+ PkgType p = PkgType.PKG_EXTRA;
+
+ assertFalse(p.hasAndroidVersion());
+ assertTrue (p.hasPath());
+ assertFalse(p.hasTag());
+ assertTrue (p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeSource() throws Exception {
+ PkgType p = PkgType.PKG_SOURCE;
+
+ assertTrue (p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeSample() throws Exception {
+ PkgType p = PkgType.PKG_SAMPLE;
+
+ assertTrue (p.hasAndroidVersion());
+ assertFalse(p.hasPath());
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertTrue (p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypePlatform() throws Exception {
+ PkgType p = PkgType.PKG_PLATFORM;
+
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // platform path is its hash string
+ assertFalse(p.hasTag());
+ assertFalse(p.hasVendor());
+ assertTrue (p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeAddon() throws Exception {
+ PkgType p = PkgType.PKG_ADDON;
+
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // add-on path is its hash string
+ assertFalse(p.hasTag());
+ assertTrue (p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeSysImg() throws Exception {
+ PkgType p = PkgType.PKG_SYS_IMAGE;
+
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // sys-img path is its ABI string
+ assertTrue (p.hasTag());
+ assertFalse(p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgTypeAddonSysImg() throws Exception {
+ PkgType p = PkgType.PKG_ADDON_SYS_IMAGE;
+
+ assertTrue (p.hasAndroidVersion());
+ assertTrue (p.hasPath()); // sys-img path is its ABI string
+ assertTrue (p.hasTag());
+ assertTrue (p.hasVendor());
+ assertFalse(p.hasMinToolsRev());
+ assertFalse(p.hasMinPlatformToolsRev());
+ }
+
+ public final void testPkgType_UniqueIntValues() {
+ // Check all types have a unique int value
+ Map<Integer, PkgType> ints = new HashMap<Integer, PkgType>();
+ for (PkgType type : PkgType.values()) {
+ Integer i = type.getIntValue();
+ if (ints.containsKey(i)) {
+ fail(String.format("Int value 0x%04x defined by both PkgType.%s and PkgType.%s",
+ i, type, ints.get(i)));
+ }
+ ints.put(i, type);
+ }
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java
new file mode 100644
index 0000000..fe06169
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalDirInfoTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.repository.io.FileOp;
+import com.android.repository.io.FileOpUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+import junit.framework.TestCase;
+
+public class LocalDirInfoTest extends TestCase {
+
+ private final FileOp mFOp = FileOpUtils.create();
+ private File mTempDir;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTempDir = File.createTempFile("test", "dir");
+ assertTrue(mTempDir.delete());
+ assertTrue(mTempDir.mkdirs());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mFOp.deleteFileOrFolder(mTempDir);
+ mTempDir = null;
+ }
+
+ // Test: start with empty directory, removing the dir marks it as changed.
+ public final void testHasChanged_Empty() {
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing the dir marks it as changed
+ mFOp.deleteFileOrFolder(mTempDir);
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: start with empty directory, adding a file marks it as changed.
+ public final void testHasChanged_AddFile() throws Exception {
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Adding a file inside the dir marks it as changed
+ createFileContent(mTempDir, "some_file.txt", "whatever content");
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: removing any file marks it as changed.
+ public final void testHasChanged_RemoveFile() throws Exception {
+ File f = createFileContent(mTempDir, "some_file.txt", "whatever content");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing a file inside the dir marks it as changed
+ mFOp.deleteFileOrFolder(f);
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: start with empty directory, adding a directory marks it as changed.
+ public final void testHasChanged_AddDir() throws Exception {
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Adding a file inside the dir marks it as changed
+ File dir = new File(mTempDir, "some_dir");
+ assertTrue(dir.mkdirs());
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: removing any directory marks it as changed.
+ public final void testHasChanged_RemoveDir() throws Exception {
+ File dir = new File(mTempDir, "some_dir");
+ assertTrue(dir.mkdirs());
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing a dir marks it as changed
+ mFOp.deleteFileOrFolder(dir);
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: directory that contains a source.properties, change source.properties's content
+ public final void testHasChanged_SourceProps_Changed() throws Exception {
+ createFileContent(mTempDir, "source.properties", "key=value");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Change source.properties's content
+ createFileContent(mTempDir, "source.properties", "other_key=other_value");
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: directory that contains a source.properties, change source.properties's timestamp
+ public final void testHasChanged_SourceProps_Timestamp() throws Exception {
+ createFileContent(mTempDir, "source.properties", "key=value");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Recreate source.properties with the same content, this changes its timestamp.
+ // Note: the last-modified resolution on Linux is 1 second on ext3/ext4 file systems
+ // so we need at least 1 second in between both edits other they will have the same
+ // last-modified value.
+ Thread.sleep(1100);
+ createFileContent(mTempDir, "source.properties", "key=value");
+ assertTrue(di.hasChanged());
+ }
+
+ // Test: directory that contains a source.properties, change delete source.properties
+ public final void testHasChanged_SourceProps_Deleted() throws Exception {
+ File sp = createFileContent(mTempDir, "source.properties", "key=value");
+
+ LocalDirInfo di = new LocalDirInfo(mFOp, mTempDir);
+ assertFalse(di.hasChanged());
+
+ // Removing the source.properties marks it as changed
+ mFOp.deleteFileOrFolder(sp);
+ assertTrue(di.hasChanged());
+ }
+
+ //---- Helpers
+
+ // Creates a new file with the specified name, in the specified
+ // parent directory with the given UTF-8 content.
+ private static File createFileContent(File parentDir,
+ String fileName,
+ String fileContent) throws IOException {
+ File f = new File(parentDir, fileName);
+ OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(f),
+ Charset.forName("UTF-8"));
+ try {
+ fw.write(fileContent);
+ } finally {
+ fw.close();
+ }
+ return f;
+ }
+
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java
new file mode 100644
index 0000000..1990db1
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repository/local/LocalSdkTest.java
@@ -0,0 +1,1128 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository.local;
+
+import com.android.SdkConstants;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.BuildToolInfo.PathId;
+import com.android.sdklib.IAndroidTarget;
+import com.android.repository.testframework.MockFileOp;
+import com.android.repository.Revision;
+import com.android.sdklib.repository.descriptors.PkgType;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.regex.Pattern;
+
+ at SuppressWarnings("MethodMayBeStatic")
+public class LocalSdkTest extends TestCase {
+
+ private MockFileOp mFOp;
+ private LocalSdk mLS;
+
+ @Override
+ protected void setUp() {
+ mFOp = new MockFileOp();
+ mLS = new LocalSdk(mFOp);
+ mLS.setLocation(new File("/sdk"));
+ }
+
+ public final void testLocalSdkTest_allPkgTypes() {
+ // Make sure getPkgInfo() can handle all defined package types.
+ for(PkgType type : PkgType.values()) {
+ mLS.getPkgsInfos(EnumSet.of(type));
+ }
+
+ // And do the same thing differently, using PKG_ALL
+ assertNotNull(mLS.getPkgsInfos(PkgType.PKG_ALL));
+ }
+
+ public final void testLocalSdkTest_getLocation() {
+ MockFileOp fop = new MockFileOp();
+ LocalSdk ls = new LocalSdk(fop);
+ assertNull(ls.getLocation());
+ ls.setLocation(new File("/sdk"));
+ assertEquals(new File("/sdk"), ls.getLocation());
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Tools() {
+ // check empty
+ assertNull(mLS.getPkgInfo(PkgType.PKG_TOOLS));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/tools");
+ mFOp.recordExistingFile("/sdk/tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=22.3.4\n" +
+ "Platform.MinPlatformToolsRev=18.0.0\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.androidCmdName(), "placeholder");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.FN_EMULATOR, "placeholder");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_TOOLS);
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalToolPkgInfo);
+ assertEquals(new File("/sdk/tools"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new Revision(22, 3, 4), pi.getDesc().getRevision());
+ assertEquals(
+ "<LocalToolPkgInfo <PkgDesc Type=tools Rev=22.3.4 MinPlatToolsRev=18.0.0>>",
+ pi.toString());
+ assertEquals("Android SDK Tools 22.3.4", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_PlatformTools() {
+ // check empty
+ assertNull(mLS.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/platform-tools");
+ mFOp.recordExistingFile("/sdk/platform-tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=18.19.20\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalPlatformToolPkgInfo);
+ assertEquals(new File("/sdk/platform-tools"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new Revision(18, 19, 20), pi.getDesc().getRevision());
+ assertEquals("<LocalPlatformToolPkgInfo <PkgDesc Type=platform_tools Rev=18.19.20>>", pi.toString());
+ assertEquals("Android SDK Platform-Tools 18.19.20", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Docs() {
+ // check empty
+ assertNull(mLS.getPkgInfo(PkgType.PKG_DOC));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/docs");
+ mFOp.recordExistingFile("/sdk/docs/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/docs/index.html", "placeholder");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_DOC);
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalDocPkgInfo);
+ assertEquals(new File("/sdk/docs"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new Revision(2), pi.getDesc().getRevision());
+ assertEquals("<LocalDocPkgInfo <PkgDesc Type=doc Android=API 18 Rev=2>>", pi.toString());
+ assertEquals("Documentation for Android SDK", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_BuildTools() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_BUILD_TOOLS)));
+
+ // We haven't defined any mock build-tools so the API will return
+ // a legacy build-tools based on top of platform tools if there's one with
+ // a revision < 17.
+ mFOp.recordExistingFolder("/sdk/platform-tools");
+ mFOp.recordExistingFile("/sdk/platform-tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=16\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+
+ // -- get latest build tool in legacy/compatibility mode
+
+ BuildToolInfo bt = mLS.getLatestBuildTool();
+ assertNotNull(bt);
+ assertEquals(new Revision(16, 0, 0), bt.getRevision());
+ assertEquals(new File("/sdk/platform-tools"), bt.getLocation());
+ assertEquals("/sdk/platform-tools/" + SdkConstants.FN_AAPT,
+ mFOp.getAgnosticAbsPath(bt.getPath(PathId.AAPT)));
+
+ // clearing local packages also clears the legacy build-tools
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+
+ // setup fake files
+ mFOp.recordExistingFolder("/sdk/build-tools");
+ mFOp.recordExistingFolder("/sdk/build-tools/17");
+ mFOp.recordExistingFolder("/sdk/build-tools/18.1.2");
+ mFOp.recordExistingFolder("/sdk/build-tools/12.2.3");
+ mFOp.recordExistingFolder("/sdk/build-tools/19.0.0-preview");
+ mFOp.recordExistingFolder("/sdk/build-tools/19.0.0-preview2");
+ mFOp.recordExistingFile("/sdk/build-tools/17/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=17.0.0\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/build-tools/18.1.2/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=18.1.2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/build-tools/12.2.3/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=12.2.3\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/build-tools/19.0.0-preview/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=19.0.0 rc1\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/build-tools/19.0.0-preview2/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=19.0.0 rc2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+
+ // -- get latest build tool 18.1.2
+
+ BuildToolInfo bt18a = mLS.getLatestBuildTool();
+ assertNotNull(bt18a);
+ assertEquals(new Revision(18, 1, 2), bt18a.getRevision());
+ assertEquals(new File("/sdk/build-tools/18.1.2"), bt18a.getLocation());
+ assertEquals("/sdk/build-tools/18.1.2/" + SdkConstants.FN_AAPT,
+ mFOp.getAgnosticAbsPath(bt18a.getPath(PathId.AAPT)));
+
+ // -- get specific build tools by version
+
+ BuildToolInfo bt18b = mLS.getBuildTool(new Revision(18, 1, 2));
+ assertSame(bt18a, bt18b);
+
+ BuildToolInfo bt17 = mLS.getBuildTool(new Revision(17, 0, 0));
+ assertNotNull(bt17);
+ assertEquals(new Revision(17, 0, 0), bt17.getRevision());
+ assertEquals(new File("/sdk/build-tools/17"), bt17.getLocation());
+ assertEquals("/sdk/build-tools/17/" + SdkConstants.FN_AAPT,
+ mFOp.getAgnosticAbsPath(bt17.getPath(PathId.AAPT)));
+
+ assertNull(mLS.getBuildTool(new Revision(0)));
+ assertNull(mLS.getBuildTool(new Revision(16, 17, 18)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_BUILD_TOOLS, new Revision(18, 1, 2));
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalBuildToolPkgInfo);
+ assertSame(bt18a, ((LocalBuildToolPkgInfo)pi).getBuildToolInfo());
+ assertEquals(new File("/sdk/build-tools/18.1.2"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new Revision(18, 1, 2), pi.getDesc().getRevision());
+ assertEquals("Android SDK Build-Tools 18.1.2", pi.getListDescription());
+
+ // -- get all build-tools and iterate, sorted by revision.
+
+ assertEquals("[<LocalBuildToolPkgInfo <PkgDesc Type=build_tools Rev=12.2.3>>, " +
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools Rev=17.0.0>>, " +
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools Rev=18.1.2>>, " +
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools Rev=19.0.0 rc1>>, " +
+ "<LocalBuildToolPkgInfo <PkgDesc Type=build_tools Rev=19.0.0 rc2>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_BUILD_TOOLS)));
+
+ mFOp.deleteFileOrFolder(new File("/sdk/build-tools/17"));
+ mFOp.deleteFileOrFolder(new File("/sdk/build-tools/18.1.2"));
+ mFOp.deleteFileOrFolder(new File("/sdk/build-tools/12.2.3"));
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ BuildToolInfo previewInfo = mLS.getLatestBuildTool();
+ assertEquals(new Revision(19, 0, 0, 2), previewInfo.getRevision());
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Extra() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_EXTRA)));
+ assertNull(mLS.getPkgInfo(PkgType.PKG_EXTRA, "vendor1", "path1"));
+ assertNull(mLS.getExtra("vendor1", "path1"));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/extras");
+ mFOp.recordExistingFolder("/sdk/extras/vendor1");
+ mFOp.recordExistingFolder("/sdk/extras/vendor1/path1");
+ mFOp.recordExistingFolder("/sdk/extras/vendor1/path2");
+ mFOp.recordExistingFolder("/sdk/extras/vendor2");
+ mFOp.recordExistingFolder("/sdk/extras/vendor2/path1");
+ mFOp.recordExistingFolder("/sdk/extras/vendor2/path2");
+ mFOp.recordExistingFolder("/sdk/extras/vendor3");
+ mFOp.recordExistingFolder("/sdk/extras/vendor3/path3");
+ mFOp.recordExistingFile("/sdk/extras/vendor1/path1/source.properties",
+ "Extra.NameDisplay=Android Support Library\n" +
+ "Extra.VendorDisplay=First Vendor\n" +
+ "Extra.VendorId=vendor1\n" +
+ "Extra.Path=path1\n" +
+ "Extra.OldPaths=compatibility\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=11.0.0\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/extras/vendor1/path2/source.properties",
+ "Extra.NameDisplay=Some Extra\n" +
+ "Extra.VendorDisplay=First Vendor\n" +
+ "Extra.VendorId=vendor1\n" +
+ "Extra.Path=path2\n" +
+ "Archive.Os=ANY\n" +
+ "Pkg.Revision=21.0.0\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/extras/vendor2/path1/source.properties",
+ "Extra.NameDisplay=Another Extra\n" +
+ "Extra.VendorDisplay=Another Vendor\n" +
+ "Extra.VendorId=vendor2\n" +
+ "Extra.Path=path1\n" +
+ "Extra.OldPaths=compatibility\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=21.0.0\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_EXTRA, "vendor1", "path1");
+ assertNotNull(pi1);
+ assertTrue(pi1 instanceof LocalExtraPkgInfo);
+ assertEquals(
+ "vendor1 [First Vendor]",
+ pi1.getDesc().getVendor().toString());
+ assertEquals(
+ "path1",
+ ((LocalExtraPkgInfo)pi1).getDesc().getPath());
+ assertEquals(new File("/sdk/extras/vendor1/path1"), pi1.getLocalDir());
+ assertSame(mLS, pi1.getLocalSdk());
+ assertEquals(null, pi1.getLoadError());
+ assertEquals(new Revision(11, 0, 0), pi1.getDesc().getRevision());
+ assertEquals("Android Support Library, rev 11", pi1.getListDescription());
+ assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
+
+ LocalExtraPkgInfo pi2 = mLS.getExtra("vendor1", "path1");
+ assertSame(pi1, pi2);
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals("[<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor1 [First Vendor] Path=path1 Rev=11.0.0>>, " +
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor1 [First Vendor] Path=path2 Rev=21.0.0>>, " +
+ "<LocalExtraPkgInfo <PkgDesc Type=extra Vendor=vendor2 [Another Vendor] Path=path1 Rev=21.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_EXTRA)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Sources() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SOURCE)));
+ assertNull(mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(18, null)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/sources");
+ mFOp.recordExistingFolder("/sdk/sources/android-CUPCAKE");
+ mFOp.recordExistingFolder("/sdk/sources/android-18");
+ mFOp.recordExistingFolder("/sdk/sources/android-42");
+ mFOp.recordExistingFile("/sdk/sources/android-CUPCAKE/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=3\n" +
+ "AndroidVersion.CodeName=CUPCAKE\n" +
+ "Pkg.Revision=1\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/sources/android-18/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/sources/android-42/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.Revision=3\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi18 = mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(18, null));
+ assertNotNull(pi18);
+ assertTrue(pi18 instanceof LocalSourcePkgInfo);
+ assertSame(mLS, pi18.getLocalSdk());
+ assertEquals(null, pi18.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi18.getDesc().getAndroidVersion());
+ assertEquals(new Revision(2), pi18.getDesc().getRevision());
+ assertEquals("Sources for Android 18, rev 2", pi18.getListDescription());
+
+ LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_SOURCE, new AndroidVersion(3, "CUPCAKE"));
+ assertNotNull(pi1);
+ assertEquals(new AndroidVersion(3, "CUPCAKE"), pi1.getDesc().getAndroidVersion());
+ assertEquals(new Revision(1), pi1.getDesc().getRevision());
+ assertEquals("Sources for Android CUPCAKE", pi1.getListDescription());
+ assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals("[<LocalSourcePkgInfo <PkgDesc Type=source Android=API 3, CUPCAKE preview Rev=1>>, " +
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 Rev=2>>, " +
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 42 Rev=3>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SOURCE)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Samples() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SAMPLE)));
+ assertNull(mLS.getPkgInfo(PkgType.PKG_SAMPLE, new AndroidVersion(18, null)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/samples");
+ mFOp.recordExistingFolder("/sdk/samples/android-18");
+ mFOp.recordExistingFolder("/sdk/samples/android-42");
+ mFOp.recordExistingFile("/sdk/samples/android-18/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/samples/android-42/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.Revision=3\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi18 = mLS.getPkgInfo(PkgType.PKG_SAMPLE, new AndroidVersion(18, null));
+ assertNotNull(pi18);
+ assertTrue(pi18 instanceof LocalSamplePkgInfo);
+ assertSame(mLS, pi18.getLocalSdk());
+ assertEquals(null, pi18.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi18.getDesc().getAndroidVersion());
+ assertEquals(new Revision(2), pi18.getDesc().getRevision());
+ assertEquals("Samples for Android 18, rev 2", pi18.getListDescription());
+ assertSame(pi18, mLS.getPkgInfo(pi18.getDesc()));
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals(
+ "[<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 18 Rev=2 MinToolsRev=0>>, " +
+ "<LocalSamplePkgInfo <PkgDesc Type=sample Android=API 42 Rev=3 MinToolsRev=0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SAMPLE)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_SysImages() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/system-images");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/armeabi-v7a");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/armeabi");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/mips");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/somedir/armeabi-v7a");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-1/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins/skinA");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/tag-2/mips/skins/skinB");
+ // without tags
+ mFOp.recordExistingFile("/sdk/system-images/android-18/armeabi-v7a/source.properties",
+ "Pkg.Revision=1\n" +
+ "SystemImage.Abi=armeabi-v7a\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/x86/source.properties",
+ "Pkg.Revision=2\n" +
+ "SystemImage.Abi=x86\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/x86/source.properties",
+ "Pkg.Revision=3\n" +
+ "SystemImage.Abi=x86\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/mips/source.properties",
+ "Pkg.Revision=4\n" +
+ "SystemImage.Abi=mips\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+
+ /*
+ It seems like LocalSdk used to detect and not include non-most-recent versions of packages
+ with multiple versions installed, and so the below was not included in the final result.
+ However, LocalSdk no longer behaves that way.
+ mFOp.recordExistingFile("/sdk/system-images/android-42/armeabi-v7a/source.properties",
+ "Pkg.Revision=5\n" +
+ "SystemImage.Abi=armeabi-v7a\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ */
+ // with tags
+ mFOp.recordExistingFile("/sdk/system-images/android-42/somedir/armeabi-v7a/source.properties",
+ "Pkg.Revision=6\n" +
+ // "SystemImage.TagId=default\n" + // Prop TagId is used instead of the "somedir" name
+ "SystemImage.Abi=armeabi-v7a\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/tag-1/x86/source.properties",
+ "Pkg.Revision=7\n" +
+ "SystemImage.TagId=tag-1\n" +
+ "SystemImage.TagDisplay=My Tag 1\n" +
+ "SystemImage.Abi=x86\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/tag-2/mips/source.properties",
+ "Pkg.Revision=8\n" +
+ "SystemImage.TagId=tag-2\n" +
+ "SystemImage.TagDisplay=My Tag 2\n" +
+ "SystemImage.Abi=mips\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/tag-2/mips/skins/skinA/layout",
+ "part {\n" +
+ "}\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/tag-2/mips/skins/skinB/layout",
+ "part {\n" +
+ "}\n");
+
+ assertEquals(
+ "[<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 "
+ + "Tag=default [Default] Path=armeabi-v7a Rev=1" +
+ " ListDisp=ARM EABI v7a System Image "
+ + "DescShort=ARM EABI v7a System Image, Android API 18, revision 1>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 "
+ + "Tag=default [Default] Path=x86 Rev=2" +
+ " ListDisp=Intel x86 Atom System Image "
+ + "DescShort=Intel x86 Atom System Image, Android API 18, revision 2>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 "
+ + "Tag=default [Default] Path=armeabi-v7a Rev=6" +
+ " ListDisp=ARM EABI v7a System Image "
+ + "DescShort=ARM EABI v7a System Image, Android API 42, revision 6>>, " +
+ // Tag=default Path=armeabi-v7a Rev=5 is
+ // overridden by the Rev=6 above
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 "
+ + "Tag=default [Default] Path=mips Rev=4" +
+ " ListDisp=MIPS System Image "
+ + "DescShort=MIPS System Image, Android API 42, revision 4>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 "
+ + "Tag=default [Default] Path=x86 Rev=3" +
+ " ListDisp=Intel x86 Atom System Image "
+ + "DescShort=Intel x86 Atom System Image, Android API 42, revision 3>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 "
+ + "Tag=tag-1 [My Tag 1] Path=x86 Rev=7" +
+ " ListDisp=My Tag 1 Intel x86 Atom System Image "
+ + "DescShort=My Tag 1 Intel x86 Atom System Image, Android API 42, "
+ + "revision 7>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 42 "
+ + "Tag=tag-2 [My Tag 2] Path=mips Rev=8" +
+ " ListDisp=My Tag 2 MIPS System Image "
+ + "DescShort=My Tag 2 MIPS System Image, Android API 42, revision 8>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
+
+ LocalPkgInfo pi = mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)[0];
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalSysImgPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new Revision(1), pi.getDesc().getRevision());
+ assertEquals("armeabi-v7a", pi.getDesc().getPath());
+ assertEquals("ARM EABI v7a System Image", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_PLATFORM)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 "
+ + "Path=android-18 Rev=1 MinToolsRev=21.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_PLATFORM)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalPlatformPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
+ assertEquals(new Revision(1), pi.getDesc().getRevision());
+ assertEquals("Android SDK Platform 18", pi.getListDescription());
+
+ LocalPkgInfo pi2 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, "android-18");
+ assertSame(pi, pi2);
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms_SysImages_Skins() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_SYS_IMAGE)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+
+ mFOp.recordExistingFolder("/sdk/system-images");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-1/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins/skinA");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/tag-2/mips/skins/skinB");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/tag-1/x86/source.properties",
+ "Pkg.Revision=7\n" +
+ "SystemImage.TagId=tag-1\n" +
+ "SystemImage.TagDisplay=My Tag 1\n" +
+ "SystemImage.Abi=x86\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/tag-2/mips/source.properties",
+ "Pkg.Revision=8\n" +
+ "SystemImage.TagId=tag-2\n" +
+ "SystemImage.TagDisplay=My Tag 2\n" +
+ "SystemImage.Abi=mips\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/tag-2/mips/skins/skinA/layout",
+ "part {\n" +
+ "}\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/tag-2/mips/skins/skinB/layout",
+ "part {\n" +
+ "}\n");
+
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 "
+ + "Rev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 "
+ + "Tag=tag-1 [My Tag 1] Path=x86 Rev=7"
+ + " ListDisp=My Tag 1 Intel x86 Atom System Image"
+ + " DescShort=My Tag 1 Intel x86 Atom System Image, Android API 18, "
+ + "revision 7>>, "
+ + "<LocalSysImgPkgInfo <PkgDesc Type=sys_image Android=API 18 "
+ + "Tag=tag-2 [My Tag 2] Path=mips Rev=8"
+ + " ListDisp=My Tag 2 MIPS System Image"
+ + " DescShort=My Tag 2 MIPS System Image, Android API 18, revision 8>>]",
+ Arrays.toString(
+ mLS.getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SYS_IMAGE))));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalPlatformPkgInfo);
+
+ assertEquals("Android SDK Platform 18", pi.getListDescription());
+ }
+
+ private String sanitizePath(String path) {
+ // On Windows the "/sdk" paths get transformed into an absolute "C:\\sdk"
+ // so we sanitize them back to "/sdk". On Linux/Mac, this is mostly a no-op.
+ String sdk = mLS.getLocation().getAbsolutePath();
+ path = path.replaceAll(Pattern.quote(sdk), "/sdk");
+ path = path.replace(File.separatorChar, '/');
+ return path;
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms_Sources() {
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 Rev=1 MinToolsRev=21.0.0>>]",
+ Arrays.toString(
+ mLS.getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SOURCE))));
+
+ // By default, IAndroidTarget returns the legacy path to a platform source,
+ // whether that directory exist or not.
+ LocalPkgInfo pi1 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ assertEquals("Android SDK Platform 18", pi1.getListDescription());
+ assertSame(pi1, mLS.getPkgInfo(pi1.getDesc()));
+
+ // However if a separate sources package folder is installed, it is returned instead.
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/sources");
+ mFOp.recordExistingFolder("/sdk/sources/android-18");
+ mFOp.recordExistingFile("/sdk/sources/android-18/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi2 = mLS.getPkgInfo(PkgType.PKG_PLATFORM, new AndroidVersion(18, null));
+ assertEquals("[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 Rev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalSourcePkgInfo <PkgDesc Type=source Android=API 18 Rev=2>>]",
+ Arrays.toString(mLS.getPkgsInfos(
+ EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_SOURCE))));
+ assertEquals("Android SDK Platform 18", pi2.getListDescription());
+ assertSame(pi2, mLS.getPkgInfo(pi2.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Addon_NoSysImg() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+ mFOp.recordExistingFolder("/sdk/add-ons");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/source.properties",
+ "Pkg.Revision=2\n" +
+ "Addon.VendorId=vendor\n" +
+ "Addon.VendorDisplay=Some Vendor\n" +
+ "Addon.NameId=name\n" +
+ "Addon.NameDisplay=Some Name\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
+ "revision=2\n" +
+ "name=Some Name\n" +
+ "name-id=name\n" +
+ "vendor=Some Vendor\n" +
+ "vendor-id=vendor\n" +
+ "api=18\n" +
+ "libraries=com.foo.lib1;com.blah.lib2\n" +
+ "com.foo.lib1=foo.jar;API for Foo\n" +
+ "com.blah.lib2=blah.jar;API for Blah\n");
+
+ assertEquals(
+ "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 Rev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalAddonPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
+ assertEquals(new Revision(2, 0, 0), pi.getDesc().getRevision());
+ assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
+ assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Addon_SysImgInLegacyFolder() {
+ // "Legacy sys-img" means there's only one sys-img of armeabi type directly
+ // in the folder addons/addon-name/images. This case is only supported for
+ // backward compatibility and we default to it when there's an images/ folder
+ // in the addon and that folder doesn't contain per-ABI subfolders and instead
+ // contains at least one .img file.
+
+
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+ mFOp.recordExistingFolder("/sdk/add-ons");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_one");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_two");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/source.properties",
+ "Pkg.Revision=2\n" +
+ "Addon.VendorId=vendor\n" +
+ "Addon.VendorDisplay=Some Vendor\n" +
+ "Addon.NameId=name\n" +
+ "Addon.NameDisplay=Some Name\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
+ "revision=2\n" +
+ "name=Some Name\n" +
+ "name-id=name\n" +
+ "vendor=Some Vendor\n" +
+ "vendor-id=vendor\n" +
+ "api=18\n" +
+ "libraries=com.foo.lib1;com.blah.lib2\n" +
+ "com.foo.lib1=foo.jar;API for Foo\n" +
+ "com.blah.lib2=blah.jar;API for Blah\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/images/system.img",
+ "placeholder\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_one/layout",
+ "parts {\n" +
+ "}\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_two/layout",
+ "parts {\n" +
+ "}\n");
+
+ assertEquals(
+ "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 Rev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalAddonPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
+ assertEquals(new Revision(2, 0, 0), pi.getDesc().getRevision());
+ assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
+ assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Addon_SysImgInSubfolder() {
+ // "sys-img in subfolder" means there is an addons/addon-name/images/ folder
+ // which in turns contains any number of folders named after the system-image ABI.
+
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+ mFOp.recordExistingFolder("/sdk/add-ons");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images/armeabi-v7a");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/images/x86");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_one");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2/skins/skin_two");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/source.properties",
+ "Pkg.Revision=2\n" +
+ "Addon.VendorId=vendor\n" +
+ "Addon.VendorDisplay=Some Vendor\n" +
+ "Addon.NameId=name\n" +
+ "Addon.NameDisplay=Some Name\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
+ "revision=2\n" +
+ "name=Some Name\n" +
+ "name-id=name\n" +
+ "vendor=Some Vendor\n" +
+ "vendor-id=vendor\n" +
+ "api=18\n" +
+ "libraries=com.foo.lib1;com.blah.lib2\n" +
+ "com.foo.lib1=foo.jar;API for Foo\n" +
+ "com.blah.lib2=blah.jar;API for Blah\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/images/armeabi-v7a/build.prop",
+ "ro.build.id=a18\n" +
+ "ro.build.display.id=addon_armeabi-v7a-18\n" +
+ "ro.build.version.sdk=18\n" +
+ "ro.build.version.codename=REL\n" +
+ "ro.product.brand=generic_armeabi-v7a\n" +
+ "ro.product.name=google_sdk_armeabi-v7a\n" +
+ "ro.product.device=generic_armeabi-v7a\n" +
+ "ro.product.board=\n" +
+ "ro.product.cpu.abi=armeabi-v7a\n" +
+ "ro.product.manufacturer=unknown\n" +
+ "ro.product.locale.language=en\n" +
+ "ro.product.locale.region=US\n" +
+ "ro.build.product=generic_armeabi-v7a\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/images/x86/build.prop",
+ "ro.build.id=a18\n" +
+ "ro.build.display.id=addon_x86-18\n" +
+ "ro.build.version.sdk=18\n" +
+ "ro.build.version.codename=REL\n" +
+ "ro.product.brand=generic_x86\n" +
+ "ro.product.name=google_sdk_x86\n" +
+ "ro.product.device=generic_x86\n" +
+ "ro.product.board=\n" +
+ "ro.product.cpu.abi=x86\n" +
+ "ro.product.manufacturer=unknown\n" +
+ "ro.product.locale.language=en\n" +
+ "ro.product.locale.region=US\n" +
+ "ro.build.product=generic_x86\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_one/layout",
+ "parts {\n" +
+ "}\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/skins/skin_two/layout",
+ "parts {\n" +
+ "}\n");
+
+ assertEquals(
+ "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 Rev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalAddonPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
+ assertEquals(new Revision(2, 0, 0), pi.getDesc().getRevision());
+ assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
+ assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Addon_SysImgFolder() {
+ // sys-img stored separately in the SDK/system-images/addon-id-name/abi/ folder.
+
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+
+ // setup fake files
+ mLS.clearLocalPkg(PkgType.PKG_ALL);
+ recordPlatform18(mFOp);
+ mFOp.recordExistingFolder("/sdk/add-ons");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
+ mFOp.recordExistingFolder("/sdk/system-images");
+ mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2");
+ mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/armeabi-v7a");
+ mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/skins");
+ mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/skins/skin_one");
+ mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/x86/skins");
+ mFOp.recordExistingFolder("/sdk/system-images/addon-vendor_name-2/x86/skins/skin_two");
+ mFOp.recordExistingFile ("/sdk/add-ons/addon-vendor_name-2/source.properties",
+ "Pkg.Revision=2\n" +
+ "Addon.VendorId=vendor\n" +
+ "Addon.VendorDisplay=Some Vendor\n" +
+ "Addon.NameId=name\n" +
+ "Addon.NameDisplay=Some Name\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
+ "revision=2\n" +
+ "name=Some Name\n" +
+ "name-id=name\n" +
+ "vendor=Some Vendor\n" +
+ "vendor-id=vendor\n" +
+ "api=18\n" +
+ "libraries=com.foo.lib1;com.blah.lib2\n" +
+ "com.foo.lib1=foo.jar;API for Foo\n" +
+ "com.blah.lib2=blah.jar;API for Blah\n");
+ mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/source.properties",
+ "Pkg.Revision=1\n" +
+ "Addon.VendorId=vendor\n" +
+ "Addon.VendorDisplay=Some Vendor\n" +
+ "SystemImage.TagId=name\n" +
+ "SystemImage.TagDisplay=Some Name\n" +
+ "SystemImage.Abi=armeabi-v7a\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/build.prop",
+ "ro.build.id=a18\n" +
+ "ro.build.display.id=addon_armeabi-v7a-18\n" +
+ "ro.build.version.sdk=18\n" +
+ "ro.build.version.codename=REL\n" +
+ "ro.product.brand=generic_armeabi-v7a\n" +
+ "ro.product.name=google_sdk_armeabi-v7a\n" +
+ "ro.product.device=generic_armeabi-v7a\n" +
+ "ro.product.board=\n" +
+ "ro.product.cpu.abi=armeabi-v7a\n" +
+ "ro.product.manufacturer=unknown\n" +
+ "ro.product.locale.language=en\n" +
+ "ro.product.locale.region=US\n" +
+ "ro.build.product=generic_armeabi-v7a\n");
+ mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/x86/source.properties",
+ "Pkg.Revision=1\n" +
+ "Addon.VendorId=vendor\n" +
+ "Addon.VendorDisplay=Some Vendor\n" +
+ "SystemImage.TagId=name\n" +
+ "SystemImage.TagDisplay=Some Name\n" +
+ "SystemImage.Abi=x86\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/x86/build.prop",
+ "ro.build.id=a18\n" +
+ "ro.build.display.id=addon_x86-18\n" +
+ "ro.build.version.sdk=18\n" +
+ "ro.build.version.codename=REL\n" +
+ "ro.product.brand=generic_x86\n" +
+ "ro.product.name=google_sdk_x86\n" +
+ "ro.product.device=generic_x86\n" +
+ "ro.product.board=\n" +
+ "ro.product.cpu.abi=x86\n" +
+ "ro.product.manufacturer=unknown\n" +
+ "ro.product.locale.language=en\n" +
+ "ro.product.locale.region=US\n" +
+ "ro.build.product=generic_x86\n");
+ mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/armeabi-v7a/skins/skin_one/layout",
+ "parts {\n" +
+ "}\n");
+ mFOp.recordExistingFile("/sdk/system-images/addon-vendor_name-2/x86/skins/skin_two/layout",
+ "parts {\n" +
+ "}\n");
+
+ assertEquals(
+ "[<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 "
+ + "Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=armeabi-v7a "
+ + "Rev=1 ListDisp=Some Name ARM EABI v7a System Image "
+ + "DescShort=Some Name ARM EABI v7a System Image, Some Vendor API 18, "
+ + "revision 1>>, " +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 "
+ + "Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=x86 Rev=1"
+ + " ListDisp=Some Name Intel x86 Atom System Image DescShort=Some Name "
+ + "Intel x86 Atom System Image, Some Vendor API 18, revision 1>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON_SYS_IMAGE)));
+ assertEquals(
+ "[<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ADDON)));
+ assertEquals(
+ "[<LocalPlatformPkgInfo <PkgDesc Type=platform Android=API 18 Path=android-18 "
+ + "Rev=1 MinToolsRev=21.0.0>>, " +
+ "<LocalAddonPkgInfo <PkgDesc Type=addon Android=API 18 "
+ + "Vendor=vendor [Some Vendor] Path=Some Vendor:Some Name:18 Rev=2.0.0"
+ + ">>, " +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 "
+ + "Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=armeabi-v7a "
+ + "Rev=1 ListDisp=Some Name ARM EABI v7a System Image "
+ + "DescShort=Some Name ARM EABI v7a System Image, Some Vendor API 18, "
+ + "revision 1>>, " +
+ "<LocalAddonSysImgPkgInfo <PkgDesc Type=addon_sys_image Android=API 18 "
+ + "Vendor=vendor [Some Vendor] Tag=name [Some Name] Path=x86 Rev=1"
+ + " ListDisp=Some Name Intel x86 Atom System Image "
+ + "DescShort=Some Name Intel x86 Atom System Image, Some Vendor API 18, "
+ + "revision 1>>]",
+ Arrays.toString(mLS.getPkgsInfos(PkgType.PKG_ALL)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(PkgType.PKG_ADDON, "Some Vendor:Some Name:18");
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalAddonPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getDesc().getAndroidVersion());
+ assertEquals(new Revision(2, 0, 0), pi.getDesc().getRevision());
+ assertEquals("Some Vendor:Some Name:18", pi.getDesc().getPath());
+ assertEquals("Some Name, Android 18, rev 2", pi.getListDescription());
+ assertSame(pi, mLS.getPkgInfo(pi.getDesc()));
+ }
+
+ //-----
+
+ private void recordPlatform18(MockFileOp fop) {
+ fop.recordExistingFolder("/sdk/platforms");
+ fop.recordExistingFolder("/sdk/platforms/android-18");
+ fop.recordExistingFile ("/sdk/platforms/android-18/android.jar");
+ fop.recordExistingFile ("/sdk/platforms/android-18/framework.aidl");
+ fop.recordExistingFile ("/sdk/platforms/android-18/source.properties",
+ "Pkg.Revision=1\n" +
+ "Platform.Version=4.3\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Layoutlib.Api=10\n" +
+ "Layoutlib.Revision=1\n" +
+ "Platform.MinToolsRev=21.0.0\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ fop.recordExistingFile("/sdk/platforms/android-18/sdk.properties",
+ "sdk.ant.templates.revision=1\n" +
+ "sdk.skin.default=WVGA800\n");
+ fop.recordExistingFile("/sdk/platforms/android-18/build.prop",
+ "ro.build.id=JB_MR2\n" +
+ "ro.build.display.id=sdk-eng 4.3 JB_MR2 819563 test-keys\n" +
+ "ro.build.version.incremental=819563\n" +
+ "ro.build.version.sdk=18\n" +
+ "ro.build.version.codename=REL\n" +
+ "ro.build.version.release=4.3\n" +
+ "ro.build.date=Tue Sep 10 18:43:31 UTC 2013\n" +
+ "ro.build.date.utc=1378838611\n" +
+ "ro.build.type=eng\n" +
+ "ro.build.tags=test-keys\n" +
+ "ro.product.model=sdk\n" +
+ "ro.product.name=sdk\n" +
+ "ro.product.board=\n" +
+ "ro.product.cpu.abi=armeabi-v7a\n" +
+ "ro.product.cpu.abi2=armeabi\n" +
+ "ro.product.locale.language=en\n" +
+ "ro.product.locale.region=US\n" +
+ "ro.wifi.channels=\n" +
+ "ro.board.platform=\n" +
+ "# ro.build.product is obsolete; use ro.product.device\n" +
+ "# Do not try to parse ro.build.description or .fingerprint\n" +
+ "ro.build.description=sdk-eng 4.3 JB_MR2 819563 test-keys\n" +
+ "ro.build.fingerprint=generic/sdk/generic:4.3/JB_MR2/819563:eng/test-keys\n" +
+ "ro.build.characteristics=default\n" +
+ "rild.libpath=/system/lib/libreference-ril.so\n" +
+ "rild.libargs=-d /dev/ttyS0\n" +
+ "ro.config.notification_sound=OnTheHunt.ogg\n" +
+ "ro.config.alarm_alert=Alarm_Classic.ogg\n" +
+ "ro.kernel.android.checkjni=1\n" +
+ "xmpp.auto-presence=true\n" +
+ "ro.config.nocheckin=yes\n" +
+ "net.bt.name=Android\n" +
+ "dalvik.vm.stack-trace-file=/data/anr/traces.txt\n" +
+ "ro.build.user=generic\n" +
+ "ro.build.host=generic\n" +
+ "ro.product.brand=generic\n" +
+ "ro.product.manufacturer=generic\n" +
+ "ro.product.device=generic\n" +
+ "ro.build.product=generic\n");
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/AddonListSourceProviderTest.java b/sdklib/src/test/java/com/android/sdklib/repositoryv2/AddonListSourceProviderTest.java
new file mode 100644
index 0000000..dfaa868
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/AddonListSourceProviderTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.prefs.AndroidLocation;
+import com.android.repository.testframework.FakeDownloader;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.api.RepositorySource;
+import com.android.repository.api.RepositorySourceProvider;
+import com.android.repository.testframework.MockFileOp;
+import com.android.utils.FileUtils;
+import com.google.common.collect.ImmutableSet;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+/**
+ * Tests for {@link RepositorySourceProvider}s.
+ */
+public class AddonListSourceProviderTest extends TestCase {
+
+ public void testRemoteSource() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ FakeDownloader downloader = new FakeDownloader(fop);
+ AndroidSdkHandler handler = new AndroidSdkHandler(null, fop);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RepositorySourceProvider provider = handler.getRemoteListSourceProvider(progress);
+
+ downloader
+ .registerUrl(new URL("https://dl.google.com/android/repository/addons_list-1.xml"),
+ getClass().getResourceAsStream("testdata/addons_list_sample_1.xml"));
+ List<RepositorySource> sources = provider.getSources(downloader, null, progress, false);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(4, sources.size());
+ assertEquals("ありがとうございます。", sources.get(1).getDisplayName());
+ assertEquals(ImmutableSet.of(AndroidSdkHandler.getAddonModule()),
+ sources.get(1).getPermittedModules());
+ downloader
+ .registerUrl(new URL("https://dl.google.com/android/repository/addons_list-2.xml"),
+ getClass().getResourceAsStream("testdata/addons_list_sample_2.xml"));
+
+ sources = provider.getSources(downloader, null, progress, true);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(6, sources.size());
+ assertEquals("ありがとうございます。", sources.get(1).getDisplayName());
+ assertEquals(ImmutableSet.of(AndroidSdkHandler.getAddonModule()),
+ sources.get(1).getPermittedModules());
+ assertEquals(ImmutableSet.of(AndroidSdkHandler.getSysImgModule()),
+ sources.get(3).getPermittedModules());
+ // TODO more tests
+
+ downloader
+ .registerUrl(new URL("https://dl.google.com/android/repository/addons_list-3.xml"),
+ getClass().getResourceAsStream("testdata/addons_list_sample_3.xml"));
+
+ sources = provider.getSources(downloader, null, progress, true);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(6, sources.size());
+ assertEquals(ImmutableSet.of(AndroidSdkHandler.getAddonModule()),
+ sources.get(1).getPermittedModules());
+ assertEquals(ImmutableSet.of(AndroidSdkHandler.getSysImgModule()),
+ sources.get(3).getPermittedModules());
+ assertEquals("http://www.example.com/my_addons2.xml", sources.get(0).getUrl());
+ }
+
+ public void testLocalSource() throws Exception {
+ AndroidLocation.resetFolder();
+ MockFileOp fop = new MockFileOp();
+ File testFile = new File(getClass().getResource("testdata/repositories.xml").toURI());
+ fop.recordExistingFile(
+ new File(AndroidLocation.getFolder(), AndroidSdkHandler.LOCAL_ADDONS_FILENAME)
+ .getAbsolutePath(),
+ FileUtils.loadFileWithUnixLineSeparators(testFile));
+ AndroidSdkHandler handler = new AndroidSdkHandler(null, fop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ handler.getSdkManager(progress);
+ RepositorySourceProvider provider = handler.getUserSourceProvider(progress);
+ List<RepositorySource> result = provider
+ .getSources(null, null, progress, false);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(3, result.size());
+ RepositorySource s0 = result.get(0);
+ assertEquals("samsung", s0.getDisplayName());
+ assertEquals("http://developer.samsung.com/sdk-manager/repository/Samsung-SDK.xml",
+ s0.getUrl());
+ RepositorySource s2 = result.get(2);
+ assertEquals("amazon", s2.getDisplayName());
+ assertEquals("https://s3.amazonaws.com/android-sdk-manager/redist/addon.xml", s2.getUrl());
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/LegacyLocalRepoTest.java b/sdklib/src/test/java/com/android/sdklib/repositoryv2/LegacyLocalRepoTest.java
new file mode 100644
index 0000000..13deda1
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/LegacyLocalRepoTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.SdkConstants;
+import com.android.repository.Revision;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.Repository;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.impl.manager.LocalRepoLoader;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.android.repository.impl.meta.TypeDetails;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.android.sdklib.repositoryv2.meta.SdkCommonFactory;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests parsing and rewriting legacy local packages.
+ */
+public class LegacyLocalRepoTest extends TestCase {
+
+ public void testParseLegacy() throws URISyntaxException, FileNotFoundException {
+ MockFileOp mockFop = new MockFileOp();
+ mockFop.recordExistingFolder("/sdk/tools");
+ mockFop.recordExistingFile("/sdk/tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=22.3.4\n" +
+ "Platform.MinPlatformToolsRev=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+ mockFop.recordExistingFile("/sdk/tools/" + SdkConstants.androidCmdName(), "placeholder");
+ mockFop.recordExistingFile("/sdk/tools/" + SdkConstants.FN_EMULATOR, "placeholder");
+
+ File root = new File("/sdk");
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RepoManager mgr = AndroidSdkHandler.getInstance(root).getSdkManager(progress);
+ progress.assertNoErrorsOrWarnings();
+
+ LocalRepoLoader sdk = new LocalRepoLoader(root, mgr,
+ new LegacyLocalRepoLoader(root, mockFop, mgr), mockFop);
+ Map<String, LocalPackage> packages = sdk.getPackages(progress);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(1, packages.size());
+ LocalPackage local = packages.get("tools");
+ assertTrue(local.getPath().startsWith(SdkConstants.FD_TOOLS));
+ assertEquals("Terms and Conditions", local.getLicense().getValue());
+ assertEquals(new Revision(22, 3, 4), local.getVersion());
+ }
+
+ public void testRewriteLegacyTools() throws Exception {
+ MockFileOp mockFop = new MockFileOp();
+ mockFop.recordExistingFolder("/sdk/tools");
+ mockFop.recordExistingFile("/sdk/tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=22.3\n" +
+ "Platform.MinPlatformToolsRev=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://example.com/repository-8.xml");
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ File root = new File("/sdk");
+ RepoManager mgr = new AndroidSdkHandler(root, mockFop).getSdkManager(progress);
+
+ progress.assertNoErrorsOrWarnings();
+
+ Collection<SchemaModule> extensions = ImmutableList
+ .of(RepoManager.getCommonModule(), RepoManager.getGenericModule());
+
+ // Now read the new package
+ Repository repo = (Repository) SchemaModuleUtil.unmarshal(
+ mockFop.newFileInputStream(new File("/sdk/tools/package.xml")),
+ extensions, mgr.getResourceResolver(progress), true, progress);
+ progress.assertNoErrorsOrWarnings();
+ LocalPackage local = repo.getLocalPackage();
+ local.setInstalledPath(mgr.getLocalPath());
+ assertTrue(local.getPath().startsWith(SdkConstants.FD_TOOLS));
+ assertEquals("Terms and Conditions", local.getLicense().getValue());
+ int[] revision = local.getVersion().toIntArray(false);
+ assertEquals(3, revision.length);
+ assertEquals(22, revision[0]);
+ assertEquals(3, revision[1]);
+ assertEquals(0, revision[2]);
+ }
+
+ public void testRewriteLegacyAddon() throws Exception {
+ MockFileOp mockFop = new MockFileOp();
+ recordLegacyGoogleApis23(mockFop);
+
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ File root = new File("/sdk");
+ AndroidSdkHandler sdkHandler = new AndroidSdkHandler(root, mockFop);
+ SdkCommonFactory factory = (SdkCommonFactory) AndroidSdkHandler.getCommonModule()
+ .createLatestFactory();
+ RepoManager mgr = sdkHandler.getSdkManager(progress);
+
+ progress.assertNoErrorsOrWarnings();
+
+ Collection<SchemaModule> extensions = ImmutableList
+ .of(RepoManager.getCommonModule(), AndroidSdkHandler.getAddonModule());
+
+ // Now read the new package
+ Repository repo = (Repository) SchemaModuleUtil.unmarshal(
+ mockFop.newFileInputStream(
+ new File("/sdk/add-ons/addon-google_apis-google-23/package.xml")),
+ extensions, mgr.getResourceResolver(progress), true, progress);
+ progress.assertNoErrorsOrWarnings();
+ LocalPackage local = repo.getLocalPackage();
+ local.setInstalledPath(mgr.getLocalPath());
+ assertTrue(local.getPath().startsWith(SdkConstants.FD_ADDONS));
+ assertEquals(new Revision(1, 0, 0), local.getVersion());
+ TypeDetails typeDetails = local.getTypeDetails();
+ assertTrue(typeDetails instanceof DetailsTypes.AddonDetailsType);
+ DetailsTypes.AddonDetailsType details = (DetailsTypes.AddonDetailsType) typeDetails;
+ Set<IAndroidTarget.OptionalLibrary> desired
+ = Sets.<IAndroidTarget.OptionalLibrary>newHashSet(
+ factory.createLibraryType("com.google.android.maps",
+ "maps.jar",
+ "API for Google Maps",
+ new File("/sdk/add-ons/addon-google_apis-google-23/"),
+ false),
+ factory.createLibraryType("com.android.future.usb.accessory",
+ "usb.jar",
+ "API for USB Accessories",
+ new File("/sdk/add-ons/addon-google_apis-google-23/"),
+ false),
+ factory.createLibraryType("com.google.android.media.effects",
+ "effects.jar",
+ "Collection of video effects",
+ new File("/sdk/add-ons/addon-google_apis-google-23/"),
+ false));
+
+ Set<IAndroidTarget.OptionalLibrary> libraries
+ = Sets.<IAndroidTarget.OptionalLibrary>newHashSet(
+ details.getLibraries().getLibrary());
+ assertEquals(desired, libraries);
+
+ }
+
+ private static void recordLegacyGoogleApis23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/source.properties",
+ "Addon.NameDisplay=Google APIs\n"
+ + "Addon.NameId=google_apis\n"
+ + "Addon.VendorDisplay=Google Inc.\n"
+ + "Addon.VendorId=google\n"
+ + "AndroidVersion.ApiLevel=23\n"
+ + "Pkg.Desc=Android + Google APIs\n"
+ + "Pkg.Revision=1\n"
+ + "Pkg.SourceUrl=https\\://dl.google.com/android/repository/addon.xml\n");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/manifest.ini",
+ "name=Google APIs\n"
+ + "name-id=google_apis\n"
+ + "vendor=Google Inc.\n"
+ + "vendor-id=google\n"
+ + "description=Android + Google APIs\n"
+ + "\n"
+ + "# version of the Android platform on which this add-on is built.\n"
+ + "api=23\n"
+ + "\n"
+ + "# revision of the add-on\n"
+ + "revision=1\n"
+ + "\n"
+ + "# list of libraries, separated by a semi-colon.\n"
+ + "libraries=com.google.android.maps;com.android.future.usb.accessory;com.google.android.media.effects\n"
+ + "\n"
+ + "# details for each library\n"
+ + "com.google.android.maps=maps.jar;API for Google Maps\n"
+ + "com.android.future.usb.accessory=usb.jar;API for USB Accessories\n"
+ + "com.google.android.media.effects=effects.jar;Collection of video effects\n"
+ + "\n"
+ + "SystemImage.GpuSupport=true\n");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/libs/effects.jar");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/libs/maps.jar");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/libs/usb.jar");
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/MavenInstallerTest.java b/sdklib/src/test/java/com/android/sdklib/repositoryv2/MavenInstallerTest.java
new file mode 100644
index 0000000..fb67481
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/MavenInstallerTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.repository.Revision;
+import com.android.repository.api.ConstantSourceProvider;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.RepoManager.RepoLoadedCallback;
+import com.android.repository.impl.manager.RepoManagerImpl;
+import com.android.repository.impl.meta.RepositoryPackages;
+import com.android.repository.testframework.FakeDownloader;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.FakeProgressRunner;
+import com.android.repository.testframework.FakeSettingsController;
+import com.android.repository.testframework.MockFileOp;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.net.URL;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Tests for {@link MavenInstaller}
+ */
+public class MavenInstallerTest extends TestCase {
+
+ public void testInstallFirst() throws Exception {
+ File root = new File("/repo");
+ MockFileOp fop = new MockFileOp();
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.registerSchemaModule(AndroidSdkHandler.getCommonModule());
+ mgr.registerSchemaModule(AndroidSdkHandler.getAddonModule());
+ mgr.setLocalPath(root);
+ FakeDownloader downloader = new FakeDownloader(fop);
+ URL repoUrl = new URL("http://example.com/dummy.xml");
+
+ // The repo we're going to download
+ downloader.registerUrl(repoUrl,
+ getClass().getResourceAsStream("testdata/remote_maven_repo.xml"));
+
+ // Create the archive and register the URL
+ URL archiveUrl = new URL("http://example.com/2/arch1");
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
+ ZipOutputStream zos = new ZipOutputStream(baos);
+ zos.putNextEntry(new ZipEntry("top-level/a"));
+ zos.write("contents1".getBytes());
+ zos.closeEntry();
+ zos.close();
+ ByteArrayInputStream is = new ByteArrayInputStream(baos.toByteArray());
+ downloader.registerUrl(archiveUrl, is);
+
+ // Register a source provider to get the repo
+ mgr.registerSourceProvider(new ConstantSourceProvider(repoUrl.toString(), "dummy",
+ ImmutableList.of(AndroidSdkHandler.getAddonModule())));
+ FakeProgressRunner runner = new FakeProgressRunner();
+
+ // Load
+ mgr.load(RepoManager.DEFAULT_EXPIRATION_PERIOD_MS, ImmutableList.<RepoLoadedCallback>of(),
+ ImmutableList.<RepoLoadedCallback>of(), ImmutableList.<Runnable>of(), runner,
+ downloader, new FakeSettingsController(false), true);
+
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+
+ RepositoryPackages pkgs = mgr.getPackages();
+
+ // Install
+ new MavenInstaller().install(
+ pkgs.getRemotePackages().get("m2repository;com;android;group1;artifact1;1.2.3"),
+ downloader, new FakeSettingsController(false), runner.getProgressIndicator(), mgr,
+ fop);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+
+ File artifactRoot = new File(root, "m2repository/com/android/group1/artifact1");
+ File mavenMetadata = new File(artifactRoot, "maven-metadata.xml");
+ MavenInstaller.MavenMetadata metadata = MavenInstaller
+ .unmarshalMetadata(mavenMetadata, runner.getProgressIndicator(), fop);
+
+ assertEquals("artifact1", metadata.artifactId);
+ assertEquals("com.android.group1", metadata.groupId);
+ assertEquals("1.2.3", metadata.versioning.release);
+ assertEquals(ImmutableList.of("1.2.3"), metadata.versioning.versions.version);
+
+ File[] contents = fop
+ .listFiles(new File(root, "m2repository/com/android/group1/artifact1/1.2.3"));
+
+ // Ensure it was installed on the filesystem
+ assertEquals(2, contents.length);
+ assertEquals(new File(root, "m2repository/com/android/group1/artifact1/1.2.3/a"),
+ contents[0]);
+ assertEquals(new File(root, "m2repository/com/android/group1/artifact1/1.2.3/package.xml"),
+ contents[1]);
+
+ // Reload
+ mgr.load(0, ImmutableList.<RepoLoadedCallback>of(), ImmutableList.<RepoLoadedCallback>of(),
+ ImmutableList.<Runnable>of(), runner, downloader, new FakeSettingsController(false),
+ true);
+
+ // Ensure it was recognized as a package.
+ Map<String, ? extends LocalPackage> locals = mgr.getPackages().getLocalPackages();
+ assertEquals(1, locals.size());
+ assertTrue(locals.containsKey("m2repository;com;android;group1;artifact1;1.2.3"));
+ LocalPackage newPkg = locals.get("m2repository;com;android;group1;artifact1;1.2.3");
+ assertEquals("maven package", newPkg.getDisplayName());
+ assertEquals(new Revision(3), newPkg.getVersion());
+
+ }
+
+
+ public void testInstallAdditional() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile("/repo/m2repository/com/android/group1/artifact1/maven-metadata.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<metadata>\n"
+ + " <groupId>com.android.group1</groupId>\n"
+ + " <artifactId>artifact1</artifactId>\n"
+ + " <release>1.0.0</release>\n"
+ + " <versioning>\n"
+ + " <versions>\n"
+ + " <version>1.0.0</version>\n"
+ + " </versions>\n"
+ + " <lastUpdated>20151006162600</lastUpdated>\n"
+ + " </versioning>\n"
+ + "</metadata>\n");
+ File root = new File("/repo");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.registerSchemaModule(AndroidSdkHandler.getCommonModule());
+ mgr.registerSchemaModule(AndroidSdkHandler.getAddonModule());
+ mgr.setLocalPath(root);
+ FakeDownloader downloader = new FakeDownloader(fop);
+ URL repoUrl = new URL("http://example.com/dummy.xml");
+
+ // The repo we're going to download
+ downloader.registerUrl(repoUrl,
+ getClass().getResourceAsStream("testdata/remote_maven_repo.xml"));
+
+ // Create the archive and register the URL
+ URL archiveUrl = new URL("http://example.com/2/arch1");
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
+ ZipOutputStream zos = new ZipOutputStream(baos);
+ zos.putNextEntry(new ZipEntry("top-level/a"));
+ zos.write("contents1".getBytes());
+ zos.closeEntry();
+ zos.close();
+ ByteArrayInputStream is = new ByteArrayInputStream(baos.toByteArray());
+ downloader.registerUrl(archiveUrl, is);
+
+ // Register a source provider to get the repo
+ mgr.registerSourceProvider(new ConstantSourceProvider(repoUrl.toString(), "dummy",
+ ImmutableList.of(AndroidSdkHandler.getAddonModule())));
+ FakeProgressRunner runner = new FakeProgressRunner();
+
+ // Load
+ mgr.load(RepoManager.DEFAULT_EXPIRATION_PERIOD_MS,
+ ImmutableList.<RepoLoadedCallback>of(), ImmutableList.<RepoLoadedCallback>of(),
+ ImmutableList.<Runnable>of(), runner,
+ downloader, new FakeSettingsController(false), true);
+
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+
+ RepositoryPackages pkgs = mgr.getPackages();
+
+ // Install
+ new MavenInstaller().install(
+ pkgs.getRemotePackages().get("m2repository;com;android;group1;artifact1;1.2.3"),
+ downloader, new FakeSettingsController(false), runner.getProgressIndicator(), mgr,
+ fop);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+
+ File artifactRoot = new File(root, "m2repository/com/android/group1/artifact1");
+ File mavenMetadata = new File(artifactRoot, "maven-metadata.xml");
+ MavenInstaller.MavenMetadata metadata = MavenInstaller
+ .unmarshalMetadata(mavenMetadata, runner.getProgressIndicator(), fop);
+
+ assertEquals("artifact1", metadata.artifactId);
+ assertEquals("com.android.group1", metadata.groupId);
+ assertEquals("1.2.3", metadata.versioning.release);
+ assertEquals(ImmutableList.of("1.0.0", "1.2.3"), metadata.versioning.versions.version);
+
+ File[] contents = fop
+ .listFiles(new File(root, "m2repository/com/android/group1/artifact1/1.2.3"));
+
+ // Ensure it was installed on the filesystem
+ assertEquals(2, contents.length);
+ assertEquals(new File(root, "m2repository/com/android/group1/artifact1/1.2.3/a"),
+ contents[0]);
+ assertEquals(new File(root, "m2repository/com/android/group1/artifact1/1.2.3/package.xml"),
+ contents[1]);
+
+ // Reload
+ mgr.load(0, ImmutableList.<RepoLoadedCallback>of(), ImmutableList.<RepoLoadedCallback>of(),
+ ImmutableList.<Runnable>of(), runner, downloader, new FakeSettingsController(false),
+ true);
+
+ // Ensure it was recognized as a package.
+ Map<String, ? extends LocalPackage> locals = mgr.getPackages().getLocalPackages();
+ assertEquals(1, locals.size());
+ assertTrue(locals.containsKey("m2repository;com;android;group1;artifact1;1.2.3"));
+ LocalPackage newPkg = locals.get("m2repository;com;android;group1;artifact1;1.2.3");
+ assertEquals("maven package", newPkg.getDisplayName());
+ assertEquals(new Revision(3), newPkg.getVersion());
+
+ }
+
+ public void testRemove() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ fop.recordExistingFile(
+ "/repo/m2repository/com/example/groupId/artifactId/1.2.3/package.xml",
+ "<repo:sdk-addon\n"
+ + " xmlns:repo=\"http://schemas.android.com/sdk/android/repo/addon2/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + "\n"
+ + " <localPackage path=\"m2repository;com;example;groupId;artifactId;1.2.3\">\n"
+ + " <type-details xsi:type=\"repo:extraDetailsType\">\n"
+ + " <vendor>\n"
+ + " <id>cyclop</id>\n"
+ + " <display>The big bus</display>\n"
+ + " </vendor>\n"
+ + " </type-details>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>A Maven artifact</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:sdk-addon>"
+ );
+ fop.recordExistingFile(
+ "/repo/m2repository/com/example/groupId/artifactId/1.2.4/package.xml",
+ "<repo:sdk-addon\n"
+ + " xmlns:repo=\"http://schemas.android.com/sdk/android/repo/addon2/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + "\n"
+ + " <localPackage path=\"m2repository;com;example;groupId;artifactId;1.2.4\">\n"
+ + " <type-details xsi:type=\"repo:extraDetailsType\">\n"
+ + " <vendor>\n"
+ + " <id>cyclop</id>\n"
+ + " <display>The big bus</display>\n"
+ + " </vendor>\n"
+ + " </type-details>\n"
+ + " <revision>\n"
+ + " <major>3</major>\n"
+ + " </revision>\n"
+ + " <display-name>Another Maven artifact</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:sdk-addon>"
+ );
+
+ String metadataPath
+ = "/repo/m2repository/com/example/groupId/artifactId/maven-metadata.xml";
+ fop.recordExistingFile(
+ metadataPath,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<metadata>\n"
+ + " <groupId>com.example.groupId</groupId>\n"
+ + " <artifactId>artifactId</artifactId>\n"
+ + " <release>1.2.4</release>\n"
+ + " <versioning>\n"
+ + " <versions>\n"
+ + " <version>1.2.3</version>\n"
+ + " <version>1.2.4</version>\n"
+ + " </versions>\n"
+ + " <lastUpdated>20151006162600</lastUpdated>\n"
+ + " </versioning>\n"
+ + "</metadata>\n");
+
+ File root = new File("/repo");
+ RepoManager mgr = new RepoManagerImpl(fop);
+ mgr.setLocalPath(root);
+ mgr.registerSchemaModule(AndroidSdkHandler.getCommonModule());
+ mgr.registerSchemaModule(AndroidSdkHandler.getAddonModule());
+
+ FakeProgressRunner runner = new FakeProgressRunner();
+ FakeDownloader downloader = new FakeDownloader(fop);
+ // Reload
+ mgr.load(0, ImmutableList.<RepoLoadedCallback>of(), ImmutableList.<RepoLoadedCallback>of(),
+ ImmutableList.<Runnable>of(), runner, downloader, new FakeSettingsController(false),
+ true);
+ runner.getProgressIndicator().assertNoErrorsOrWarnings();
+
+ Map<String, ? extends LocalPackage> locals = mgr.getPackages().getLocalPackages();
+ assertEquals(2, locals.size());
+ assertTrue(locals.containsKey("m2repository;com;example;groupId;artifactId;1.2.4"));
+
+ MavenInstaller installer = new MavenInstaller();
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ installer.uninstall(locals.get("m2repository;com;example;groupId;artifactId;1.2.4"), progress, mgr, fop);
+ progress.assertNoErrorsOrWarnings();
+ MavenInstaller.MavenMetadata metadata = MavenInstaller
+ .unmarshalMetadata(new File(metadataPath), progress, fop);
+ progress.assertNoErrorsOrWarnings();
+ assertNotNull(metadata);
+ assertEquals(ImmutableList.of("1.2.3"), metadata.versioning.versions.version);
+ assertEquals("1.2.3", metadata.versioning.release);
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/UnmarshalTest.java b/sdklib/src/test/java/com/android/sdklib/repositoryv2/UnmarshalTest.java
new file mode 100644
index 0000000..ab863ee
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/UnmarshalTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.sdklib.repositoryv2;
+
+import com.android.repository.Revision;
+import com.android.repository.api.License;
+import com.android.repository.api.LocalPackage;
+import com.android.repository.api.RemotePackage;
+import com.android.repository.api.RepoManager;
+import com.android.repository.api.Repository;
+import com.android.repository.api.SchemaModule;
+import com.android.repository.impl.meta.Archive;
+import com.android.repository.impl.meta.RemotePackageImpl;
+import com.android.repository.impl.meta.SchemaModuleUtil;
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.repositoryv2.meta.DetailsTypes;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for unmarshalling an xml repository
+ */
+public class UnmarshalTest extends TestCase {
+
+ public void testLoadRepo() throws Exception {
+ String filename = "testdata/repository2_sample_1.xml";
+ InputStream xmlStream = getClass().getResourceAsStream(filename);
+ assertNotNull("Missing test file: " + filename, xmlStream);
+
+ AndroidSdkHandler handler = new AndroidSdkHandler(new File(filename), new MockFileOp());
+ SchemaModule repoEx = AndroidSdkHandler.getRepositoryModule();
+ SchemaModule addonEx = AndroidSdkHandler.getAddonModule();
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RepoManager mgr = handler.getSdkManager(progress);
+ Repository repo = (Repository) SchemaModuleUtil.unmarshal(xmlStream,
+ ImmutableList.of(repoEx, addonEx, RepoManager.getGenericModule()),
+ mgr.getResourceResolver(progress), true, progress);
+ progress.assertNoErrorsOrWarnings();
+ List<? extends License> licenses = repo.getLicense();
+ assertEquals(licenses.size(), 2);
+ Map<String, String> licenseMap = Maps.newHashMap();
+ for (License license : licenses) {
+ licenseMap.put(license.getId(), license.getValue());
+ }
+ assertEquals(licenseMap.get("license1").trim(),
+ "This is the license for this platform.");
+ assertEquals(licenseMap.get("license2").trim(),
+ "Licenses are only of type 'text' right now, so this is implied.");
+
+ List<? extends RemotePackage> packages = repo.getRemotePackage();
+ assertEquals(3, packages.size());
+ Map<String, RemotePackage> packageMap = Maps.newHashMap();
+ for (RemotePackage p : packages) {
+ packageMap.put(p.getPath(), p);
+ }
+
+ RemotePackage platform22 = packageMap.get("platforms;android-22");
+ assertEquals(platform22.getDisplayName(), "Lollipop MR1");
+
+ assertTrue(platform22.getTypeDetails() instanceof DetailsTypes.PlatformDetailsType);
+ DetailsTypes.PlatformDetailsType details = (DetailsTypes.PlatformDetailsType) platform22
+ .getTypeDetails();
+ assertEquals(1, details.getApiLevel());
+ assertEquals(5, details.getLayoutlib().getApi());
+
+ List<Archive> archives = ((RemotePackageImpl) platform22).getAllArchives();
+ assertEquals(2, archives.size());
+ Archive archive = archives.get(1);
+ assertEquals(64, archive.getHostBits().intValue());
+ assertEquals("windows", archive.getHostOs());
+ Archive.PatchType patch = archive.getAllPatches().get(0);
+ assertEquals(new Revision(1), patch.getBasedOn().toRevision());
+ assertEquals(4321, patch.getSize());
+ assertEquals("something", patch.getUrl());
+ Archive.CompleteType complete = archive.getComplete();
+ assertEquals(65536, complete.getSize());
+ assertEquals("1234ae37115ebf13412bbef91339ee0d9454525e", complete.getChecksum());
+
+ // TODO: add other extension types as below
+/*
+ filename = "/com/android/sdklib/testdata/addon2_sample_1.xml";
+ xmlStream = getClass().getResourceAsStream(filename);
+ repo = SchemaModule.unmarshal(xmlStream, ImmutableList.of(repoEx, addonEx));
+ assertTrue(repo.getPackage().get(0).getTypeDetails() instanceof DetailsTypes.AddonDetailsType);*/
+ }
+
+ private static final String INVALID_XML =
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/01\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"dummy;foo\" obsolete=\"true\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>1</major>\n"
+ + " <minor>2</minor>\n"
+ + " <micro>3</micro>\n"
+ + " </revision>\n"
+ + " <foo bar=\"baz\"/>"
+ + " <display-name>Test package</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>";
+
+ public void testLeniency() throws Exception {
+ AndroidSdkHandler handler = new AndroidSdkHandler(new File("/sdk"), new MockFileOp());
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RepoManager mgr = handler.getSdkManager(progress);
+ Repository repo = (Repository) SchemaModuleUtil
+ .unmarshal(new ByteArrayInputStream(INVALID_XML.getBytes()),
+ ImmutableList.of(RepoManager.getGenericModule()),
+ mgr.getResourceResolver(progress), false, progress);
+ assertFalse(progress.getWarnings().isEmpty());
+ LocalPackage local = repo.getLocalPackage();
+ assertEquals("dummy;foo", local.getPath());
+ assertEquals(new Revision(1, 2, 3), local.getVersion());
+
+ try {
+ SchemaModuleUtil.unmarshal(new ByteArrayInputStream(INVALID_XML.getBytes()),
+ ImmutableList.of(RepoManager.getGenericModule()),
+ mgr.getResourceResolver(progress), true, progress);
+ fail();
+ }
+ catch (Exception e) {
+ // expected
+ }
+ }
+
+ private static final String FUTURE_XML =
+ "<repo:repository\n"
+ + " xmlns:repo=\"http://schemas.android.com/repository/android/generic/99\"\n"
+ + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ + " <localPackage path=\"dummy;foo\" obsolete=\"true\">\n"
+ + " <type-details xsi:type=\"repo:genericDetailsType\"/>\n"
+ + " <revision>\n"
+ + " <major>1</major>\n"
+ + " <minor>2</minor>\n"
+ + " <micro>3</micro>\n"
+ + " </revision>\n"
+ + " <display-name>Test package</display-name>\n"
+ + " </localPackage>\n"
+ + "</repo:repository>";
+
+ public void testNamespaceFallback() throws Exception {
+ AndroidSdkHandler handler = new AndroidSdkHandler(new File("/sdk"), new MockFileOp());
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ RepoManager mgr = handler.getSdkManager(progress);
+ Repository repo = (Repository) SchemaModuleUtil
+ .unmarshal(new ByteArrayInputStream(FUTURE_XML.getBytes()),
+ ImmutableList
+ .of(RepoManager.getGenericModule(), RepoManager.getCommonModule()),
+ mgr.getResourceResolver(progress), false, progress);
+ assertFalse(progress.getWarnings().isEmpty());
+ LocalPackage local = repo.getLocalPackage();
+ assertEquals("dummy;foo", local.getPath());
+ assertEquals(new Revision(1, 2, 3), local.getVersion());
+
+ try {
+ SchemaModuleUtil.unmarshal(new ByteArrayInputStream(FUTURE_XML.getBytes()),
+ ImmutableList.of(RepoManager.getCommonModule(), RepoManager.getGenericModule()),
+ mgr.getResourceResolver(progress), true, progress);
+ fail();
+ }
+ catch (Exception e) {
+ // expected
+ }
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/targets/AndroidTargetManagerTest.java b/sdklib/src/test/java/com/android/sdklib/repositoryv2/targets/AndroidTargetManagerTest.java
new file mode 100644
index 0000000..771c4e5
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/targets/AndroidTargetManagerTest.java
@@ -0,0 +1,586 @@
+/*
+ * 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.sdklib.repositoryv2.targets;
+
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Tests for {@link AndroidTargetManager}.
+ */
+public class AndroidTargetManagerTest extends TestCase {
+
+ public void testNew() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ recordPlatform13(fop);
+ recordPlatform23(fop);
+ recordGoogleApisAddon23(fop);
+ recordGoogleTvAddon13(fop);
+ recordBuildTool23(fop);
+ recordSysImg13(fop);
+ recordGoogleApisSysImg23(fop);
+
+ AndroidSdkHandler handler = new AndroidSdkHandler(new File("/sdk"), fop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+
+ AndroidTargetManager mgr = handler.getAndroidTargetManager(progress);
+ Collection<IAndroidTarget> targets = mgr.getTargets(progress);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(4, targets.size());
+ Iterator<IAndroidTarget> iter = targets.iterator();
+
+ IAndroidTarget addon13 = iter.next();
+ IAndroidTarget platform13 = iter.next();
+ verifyPlatform13(platform13);
+ verifyAddon13(addon13, platform13);
+ IAndroidTarget addon23 = iter.next();
+ IAndroidTarget platform23 = iter.next();
+ verifyPlatform23(platform23);
+ verifyAddon23(addon23, platform23);
+ }
+
+ public void testLegacyAddon() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ recordPlatform23(fop);
+ recordLegacyGoogleApis23(fop);
+ recordBuildTool23(fop);
+ recordGoogleApisSysImg23(fop);
+
+ AndroidSdkHandler handler = new AndroidSdkHandler(new File("/sdk"), fop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ AndroidTargetManager mgr = handler.getAndroidTargetManager(progress);
+ Collection<IAndroidTarget> targets = mgr.getTargets(progress);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(2, targets.size());
+ Iterator<IAndroidTarget> iter = targets.iterator();
+
+ IAndroidTarget addon23 = iter.next();
+ IAndroidTarget platform23 = iter.next();
+ verifyPlatform23(platform23);
+ verifyAddon23(addon23, platform23);
+ }
+
+ public void testInstalledLegacyAddon() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ recordPlatform23(fop);
+ recordInstalledLegacyGoogleApis23(fop);
+ recordBuildTool23(fop);
+ recordGoogleApisSysImg23(fop);
+
+ AndroidSdkHandler handler = new AndroidSdkHandler(new File("/sdk"), fop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+ AndroidTargetManager mgr = handler.getAndroidTargetManager(progress);
+ Collection<IAndroidTarget> targets = mgr.getTargets(progress);
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(2, targets.size());
+ Iterator<IAndroidTarget> iter = targets.iterator();
+
+ IAndroidTarget addon23 = iter.next();
+ IAndroidTarget platform23 = iter.next();
+ verifyAddon23(addon23, platform23);
+ verifyPlatform23(platform23);
+ }
+
+ private static void verifyPlatform13(IAndroidTarget target) {
+ assertEquals(new AndroidVersion(13, null), target.getVersion());
+ assertEquals("Android Open Source Project", target.getVendor());
+ assertEquals("/sdk/platforms/android-13/", target.getLocation());
+ assertNull(target.getParent());
+
+ assertEquals(ImmutableSet.of(new File("/sdk/platforms/android-13/skins/HVGA"),
+ new File("/sdk/platforms/android-13/skins/WVGA800")),
+ ImmutableSet.copyOf(target.getSkins()));
+ assertEquals(ImmutableList.of("/sdk/platforms/android-13/android.jar"),
+ target.getBootClasspath());
+ assertEquals(new File("/sdk/build-tools/23.0.2"), target.getBuildToolInfo().getLocation());
+ assertEquals(new File("/sdk/platforms/android-13/skins/WXGA"), target.getDefaultSkin());
+ }
+
+ private static void verifyPlatform23(IAndroidTarget target) {
+ assertEquals(new AndroidVersion(23, null), target.getVersion());
+ assertEquals("Android Open Source Project", target.getVendor());
+ assertEquals("/sdk/platforms/android-23/", target.getLocation());
+ assertNull(target.getParent());
+ assertTrue(Arrays.deepEquals(new File[]{new File("/sdk/platforms/android-23/skins/HVGA"),
+ new File("/sdk/platforms/android-23/skins/WVGA800")},
+ target.getSkins()));
+ assertEquals(ImmutableList.of("/sdk/platforms/android-23/android.jar"),
+ target.getBootClasspath());
+ assertEquals(new File("/sdk/build-tools/23.0.2"), target.getBuildToolInfo().getLocation());
+ assertEquals(new File("/sdk/platforms/android-23/skins/WVGA800"), target.getDefaultSkin());
+ }
+
+ private static void verifyAddon13(IAndroidTarget target, IAndroidTarget platform13) {
+ assertEquals(new AndroidVersion(13, null), target.getVersion());
+ assertEquals("Google Inc.", target.getVendor());
+ assertEquals("/sdk/add-ons/addon-google_tv_addon-google-13/", target.getLocation());
+ assertEquals(platform13, target.getParent());
+ assertEquals(ImmutableSet.of(
+ new File("/sdk/platforms/android-13/skins/HVGA"),
+ new File("/sdk/add-ons/addon-google_tv_addon-google-13/skins/1080p"),
+ new File("/sdk/add-ons/addon-google_tv_addon-google-13/skins/720p-overscan"),
+ new File("/sdk/platforms/android-13/skins/WVGA800")),
+ ImmutableSet.copyOf(target.getSkins()));
+ assertEquals(ImmutableList.of("/sdk/platforms/android-13/android.jar"),
+ target.getBootClasspath());
+ assertEquals(new File("/sdk/build-tools/23.0.2"), target.getBuildToolInfo().getLocation());
+ assertEquals(new File("/sdk/add-ons/addon-google_tv_addon-google-13/skins/720p"),
+ target.getDefaultSkin());
+ }
+
+ private static void verifyAddon23(IAndroidTarget target, IAndroidTarget platform23) {
+ assertEquals(new AndroidVersion(23, null), target.getVersion());
+ assertEquals("Google Inc.", target.getVendor());
+ assertEquals("/sdk/add-ons/addon-google_apis-google-23/", target.getLocation());
+ assertEquals(platform23, target.getParent());
+ assertEquals(ImmutableSet.of(new File("/sdk/platforms/android-23/skins/HVGA"),
+ new File("/sdk/platforms/android-23/skins/WVGA800")),
+ ImmutableSet.copyOf(target.getSkins()));
+ assertEquals(ImmutableList.of("/sdk/platforms/android-23/android.jar"),
+ target.getBootClasspath());
+ assertEquals(new File("/sdk/build-tools/23.0.2"), target.getBuildToolInfo().getLocation());
+ assertEquals(new File("/sdk/platforms/android-23/skins/WVGA800"), target.getDefaultSkin());
+
+ Set<IAndroidTarget.OptionalLibrary> desired
+ = Sets.<IAndroidTarget.OptionalLibrary>newHashSet(
+ new OptionalLibraryImpl("com.google.android.maps",
+ new File("/sdk/add-ons/addon-google_apis-google-23/libs/maps.jar"), "",
+ false),
+ new OptionalLibraryImpl("com.android.future.usb.accessory",
+ new File("/sdk/add-ons/addon-google_apis-google-23/libs/usb.jar"), "",
+ false),
+ new OptionalLibraryImpl("com.google.android.media.effects",
+ new File("/sdk/add-ons/addon-google_apis-google-23/libs/effects.jar"), "",
+ false));
+
+ Set<IAndroidTarget.OptionalLibrary> libraries = Sets
+ .newHashSet(target.getAdditionalLibraries());
+ assertEquals(desired, libraries);
+ }
+
+ private static void recordBuildTool23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/build-tools/23.0.2/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns2:sdk-repository "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/generic/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-19E6313A\" type=\"text\">License text\n"
+ + "</license><localPackage path=\"build-tools;23.0.2\" obsolete=\"false\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns4:genericDetailsType\"/>"
+ + "<revision><major>23</major><minor>0</minor><micro>2</micro></revision>"
+ + "<display-name>Android SDK Build-Tools 23.0.2</display-name>"
+ + "<uses-license ref=\"license-19E6313A\"/></localPackage>"
+ + "</ns2:sdk-repository>\n");
+ }
+
+ private static void recordPlatform13(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/platforms/android-13/images/system.img");
+ fop.recordExistingFile("/sdk/platforms/android-13/android.jar");
+ fop.recordExistingFile("/sdk/platforms/android-13/framework.aidl");
+ fop.recordExistingFile("/sdk/platforms/android-13/skins/HVGA/layout");
+ fop.recordExistingFile("/sdk/platforms/android-13/skins/dummy.txt");
+ fop.recordExistingFile("/sdk/platforms/android-13/skins/WVGA800/layout");
+ fop.recordExistingFile("/sdk/platforms/android-13/sdk.properties",
+ "sdk.ant.templates.revision=1\n" +
+ "sdk.skin.default=WXGA\n");
+ fop.recordExistingFile("/sdk/platforms/android-13/build.prop",
+ "ro.build.id=HTJ85B\n"
+ + "ro.build.display.id=sdk-eng 3.2 HTJ85B 140714 test-keys\n"
+ + "ro.build.version.incremental=140714\n"
+ + "ro.build.version.sdk=13\n"
+ + "ro.build.version.codename=REL\n"
+ + "ro.build.version.release=3.2\n"
+ + "ro.build.date=Wed Jul 6 17:51:50 PDT 2011\n"
+ + "ro.build.date.utc=1309999910\n"
+ + "ro.build.type=eng\n"
+ + "ro.build.tags=test-keys\n"
+ + "ro.product.model=sdk\n"
+ + "ro.product.name=sdk\n"
+ + "ro.product.board=\n"
+ + "ro.product.cpu.abi=armeabi\n"
+ + "ro.product.locale.language=ldpi\n"
+ + "ro.wifi.channels=\n"
+ + "ro.board.platform=\n"
+ + "# ro.build.product is obsolete; use ro.product.device\n"
+ + "# Do not try to parse ro.build.description or .fingerprint\n"
+ + "ro.build.description=sdk-eng 3.2 HTJ85B 140714 test-keys\n"
+ + "ro.build.fingerprint=generic/sdk/generic:3.2/HTJ85B/140714:eng/test-keys\n"
+ + "ro.build.characteristics=default\n"
+ + "# end build properties\n"
+ + "#\n"
+ + "# system.prop for generic sdk \n"
+ + "#\n"
+ + "\n"
+ + "rild.libpath=/system/lib/libreference-ril.so\n"
+ + "rild.libargs=-d /dev/ttyS0\n"
+ + "\n"
+ + "#\n"
+ + "# ADDITIONAL_BUILD_PROPERTIES\n"
+ + "#\n"
+ + "ro.config.notification_sound=OnTheHunt.ogg\n"
+ + "ro.config.alarm_alert=Alarm_Classic.ogg\n"
+ + "ro.kernel.android.checkjni=1\n"
+ + "ro.setupwizard.mode=OPTIONAL\n"
+ + "xmpp.auto-presence=true\n"
+ + "ro.config.nocheckin=yes\n"
+ + "net.bt.name=Android\n"
+ + "dalvik.vm.stack-trace-file=/data/anr/traces.txt\n"
+ + "ro.build.user=generic\n"
+ + "ro.build.host=generic\n"
+ + "ro.product.brand=generic\n"
+ + "ro.product.manufacturer=generic\n"
+ + "ro.product.device=generic\n"
+ + "ro.build.product=generic\n");
+ fop.recordExistingFile("/sdk/platforms/android-13/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns2:sdk-repository "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-2A86BE32\" type=\"text\">License Text\n</license>"
+ + "<localPackage path=\"platforms;android-13\" obsolete=\"false\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns2:platformDetailsType\"><api-level>13</api-level>"
+ + "<layoutlib api=\"4\"/></type-details><revision><major>1</major>"
+ + "</revision><display-name>API 13: Android 3.2 (Honeycomb)</display-name>"
+ + "<uses-license ref=\"license-2A86BE32\"/><dependencies>"
+ + "<dependency path=\"tools\"><min-revision><major>12</major></min-revision>"
+ + "</dependency></dependencies></localPackage></ns2:sdk-repository>");
+ }
+
+ private static void recordPlatform23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/platforms/android-23/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><ns2:sdk-repository "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-9A220565\" type=\"text\">Terms and Conditions\n"
+ + "</license><localPackage path=\"platforms;android-23\" "
+ + "obsolete=\"false\"><type-details "
+ + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns2:platformDetailsType\"><api-level>23</api-level>"
+ + "<layoutlib api=\"15\"/></type-details><revision><major>1</major>"
+ + "</revision><display-name>API 23: Android 6.0 (Marshmallow)"
+ + "</display-name><uses-license ref=\"license-9A220565\"/><dependencies>"
+ + "<dependency path=\"tools\"><min-revision><major>22</major>"
+ + "</min-revision></dependency></dependencies></localPackage>"
+ + "</ns2:sdk-repository>\n");
+ fop.recordExistingFile("/sdk/platforms/android-23/android.jar");
+ fop.recordExistingFile("/sdk/platforms/android-23/framework.aidl");
+ fop.recordExistingFile("/sdk/platforms/android-23/skins/HVGA/layout");
+ fop.recordExistingFile("/sdk/platforms/android-23/skins/dummy.txt");
+ fop.recordExistingFile("/sdk/platforms/android-23/skins/WVGA800/layout");
+ fop.recordExistingFile("/sdk/platforms/android-23/build.prop",
+ "# autogenerated by buildinfo.sh\n"
+ + "ro.build.id=MRA44C\n"
+ + "ro.build.display.id=sdk_phone_armv7-eng 6.0 MRA44C 2166767 test-keys\n"
+ + "ro.build.version.incremental=2166767\n"
+ + "ro.build.version.sdk=23\n"
+ + "ro.build.version.preview_sdk=0\n"
+ + "ro.build.version.codename=REL\n"
+ + "ro.build.version.all_codenames=REL\n"
+ + "ro.build.version.release=6.0\n"
+ + "ro.build.version.security_patch=\n"
+ + "ro.build.version.base_os=\n"
+ + "ro.build.date=Thu Aug 13 23:46:41 UTC 2015\n"
+ + "ro.build.date.utc=1439509601\n"
+ + "ro.build.type=eng\n"
+ + "ro.build.tags=test-keys\n"
+ + "ro.build.flavor=sdk_phone_armv7-eng\n"
+ + "ro.product.model=sdk_phone_armv7\n"
+ + "ro.product.name=sdk_phone_armv7\n"
+ + "ro.product.board=\n"
+ + "# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,\n"
+ + "# use ro.product.cpu.abilist instead.\n"
+ + "ro.product.cpu.abi=armeabi-v7a\n"
+ + "ro.product.cpu.abi2=armeabi\n"
+ + "ro.product.cpu.abilist=armeabi-v7a,armeabi\n"
+ + "ro.product.cpu.abilist32=armeabi-v7a,armeabi\n"
+ + "ro.product.cpu.abilist64=\n"
+ + "ro.product.locale=en-US\n"
+ + "ro.wifi.channels=\n"
+ + "ro.board.platform=\n"
+ + "# ro.build.product is obsolete; use ro.product.device\n"
+ + "# Do not try to parse description, fingerprint, or thumbprint\n"
+ + "ro.build.description=sdk_phone_armv7-eng 6.0 MRA44C 2166767 test-keys\n"
+ + "ro.build.fingerprint=generic/sdk_phone_armv7/generic:6.0/MRA44C/2166767:eng/test-keys\n"
+ + "ro.build.characteristics=default\n"
+ + "# end build properties\n"
+ + "#\n"
+ + "# from build/target/board/generic/system.prop\n"
+ + "#\n"
+ + "#\n"
+ + "# system.prop for generic sdk\n"
+ + "#\n"
+ + "\n"
+ + "rild.libpath=/system/lib/libreference-ril.so\n"
+ + "rild.libargs=-d /dev/ttyS0\n"
+ + "\n"
+ + "#\n"
+ + "# ADDITIONAL_BUILD_PROPERTIES\n"
+ + "#\n"
+ + "ro.config.notification_sound=OnTheHunt.ogg\n"
+ + "ro.config.alarm_alert=Alarm_Classic.ogg\n"
+ + "persist.sys.dalvik.vm.lib.2=libart\n"
+ + "dalvik.vm.isa.arm.variant=generic\n"
+ + "dalvik.vm.isa.arm.features=default\n"
+ + "ro.kernel.android.checkjni=1\n"
+ + "dalvik.vm.lockprof.threshold=500\n"
+ + "dalvik.vm.usejit=true\n"
+ + "xmpp.auto-presence=true\n"
+ + "ro.config.nocheckin=yes\n"
+ + "net.bt.name=Android\n"
+ + "dalvik.vm.stack-trace-file=/data/anr/traces.txt\n"
+ + "ro.build.user=generic\n"
+ + "ro.build.host=generic\n"
+ + "ro.product.brand=generic\n"
+ + "ro.product.manufacturer=generic\n"
+ + "ro.product.device=generic\n"
+ + "ro.build.product=generic\n");
+ }
+
+ private static void recordGoogleApisAddon23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns5:sdk-addon "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-1E15FA4A\" type=\"text\">"
+ + " Terms and Conditions\n"
+ + "</license>"
+ + "<localPackage path=\"add-ons;addon-google_apis-google-23\" "
+ + "obsolete=\"false\">"
+ + " <type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + " xsi:type=\"ns5:addonDetailsType\">"
+ + " <api-level>23</api-level>"
+ + " <vendor><id>google</id><display>Google Inc.</display></vendor>"
+ + " <tag><id>google_apis</id><display>Google APIs</display></tag>"
+ + " <libraries>"
+ + " <library name=\"com.google.android.maps\" localJarPath=\"maps.jar\">"
+ + " <description>API for Google Maps</description>"
+ + " </library>"
+ + " <library name=\"com.android.future.usb.accessory\" localJarPath=\"usb.jar\">"
+ + " <description>API for USB Accessories</description>"
+ + " </library>"
+ + " <library name=\"com.google.android.media.effects\" localJarPath=\"effects.jar\">"
+ + " <description>Collection of video effects</description>"
+ + " </library>"
+ + " </libraries>"
+ + " </type-details>"
+ + " <revision>"
+ + " <major>1</major>"
+ + " <minor>0</minor>"
+ + " <micro>0</micro>"
+ + " </revision>"
+ + " <display-name>Google APIs, Android 23</display-name>"
+ + " <uses-license ref=\"license-1E15FA4A\"/>"
+ + "</localPackage></ns5:sdk-addon>\n");
+ }
+
+ private static void recordGoogleTvAddon13(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+ "<ns5:sdk-addon xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ +
+ "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" " +
+ "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" " +
+ "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">" +
+ "<license id=\"license-A06C75BE\" type=\"text\">Terms and Conditions\n" +
+ "</license><localPackage " +
+ "path=\"add-ons;addon-google_tv_addon-google-13\" obsolete=\"false\">" +
+ "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+ "xsi:type=\"ns5:addonDetailsType\"><api-level>13</api-level>" +
+ "<vendor><id>google</id><display>Google Inc.</display></vendor>" +
+ "<tag><id>google_tv_addon</id><display>Google TV Addon</display></tag>" +
+ "<default-skin>720p</default-skin>" +
+ "</type-details><revision><major>1</major><minor>0</minor>" +
+ "<micro>0</micro></revision>" +
+ "<display-name>Google TV Addon, Android 13</display-name>" +
+ "<uses-license ref=\"license-A06C75BE\"/></localPackage>" +
+ "</ns5:sdk-addon>\n");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/skins/1080p/layout");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/skins/dummy.txt");
+ fop.recordExistingFile(
+ "/sdk/add-ons/addon-google_tv_addon-google-13/skins/720p-overscan/layout");
+ fop.recordExistingFile(
+ "/sdk/add-ons/addon-google_tv_addon-google-13/images/x86/system.img");
+ }
+
+ private static void recordSysImg13(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/system-images/android-13/default/x86/system.img");
+ fop.recordExistingFile("/sdk/system-images/android-13/default/x86/skins/res1/layout");
+ fop.recordExistingFile("/sdk/system-images/android-13/default/x86/skins/dummy");
+ fop.recordExistingFile("/sdk/system-images/android-13/default/x86/skins/res2/layout");
+ fop.recordExistingFile("/sdk/system-images/android-13/default/x86/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns3:sdk-sys-img "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-A78C4257\" type=\"text\">Terms and Conditions\n"
+ + "</license><localPackage path=\"system-images;android-13;default;x86\" "
+ + "obsolete=\"false\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns3:sysImgDetailsType\"><api-level>13</api-level>"
+ + "<tag><id>default</id><display>Default</display></tag><abi>x86</abi>"
+ + "</type-details><revision><major>5</major></revision>"
+ + "<display-name>Intel x86 Atom System Image</display-name>"
+ + "<uses-license ref=\"license-A78C4257\"/></localPackage>"
+ + "</ns3:sdk-sys-img>\n");
+ }
+
+ private static void recordGoogleApisSysImg23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/system-images/android-23/google_apis/x86_64/system.img");
+ fop.recordExistingFile("/sdk/system-images/android-23/google_apis/x86_64/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns3:sdk-sys-img "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-9A5C00D5\" type=\"text\">Terms and Conditions\n"
+ + "</license><localPackage "
+ + "path=\"system-images;android-23;google_apis;x86_64\" "
+ + "obsolete=\"false\"><type-details "
+ + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns3:sysImgDetailsType\"><api-level>23</api-level>"
+ + "<tag><id>google_apis</id><display>Google APIs</display></tag>"
+ + "<vendor><id>google</id><display>Google Inc.</display></vendor>"
+ + "<abi>x86_64</abi></type-details><revision><major>9</major></revision>"
+ + "<display-name>Google APIs Intel x86 Atom_64 System Image</display-name>"
+ + "<uses-license ref=\"license-9A5C00D5\"/></localPackage>"
+ + "</ns3:sdk-sys-img>\n");
+ }
+
+ private static void recordLegacyGoogleApis23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/source.properties",
+ "Addon.NameDisplay=Google APIs\n"
+ + "Addon.NameId=google_apis\n"
+ + "Addon.VendorDisplay=Google Inc.\n"
+ + "Addon.VendorId=google\n"
+ + "AndroidVersion.ApiLevel=23\n"
+ + "Pkg.Desc=Android + Google APIs\n"
+ + "Pkg.Revision=1\n"
+ + "Pkg.SourceUrl=https\\://dl.google.com/android/repository/addon.xml\n");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/manifest.ini",
+ "name=Google APIs\n"
+ + "name-id=google_apis\n"
+ + "vendor=Google Inc.\n"
+ + "vendor-id=google\n"
+ + "description=Android + Google APIs\n"
+ + "\n"
+ + "# version of the Android platform on which this add-on is built.\n"
+ + "api=23\n"
+ + "\n"
+ + "# revision of the add-on\n"
+ + "revision=1\n"
+ + "\n"
+ + "# list of libraries, separated by a semi-colon.\n"
+ + "libraries=com.google.android.maps;com.android.future.usb.accessory;com.google.android.media.effects\n"
+ + "\n"
+ + "# details for each library\n"
+ + "com.google.android.maps=maps.jar;API for Google Maps\n"
+ + "com.android.future.usb.accessory=usb.jar;API for USB Accessories\n"
+ + "com.google.android.media.effects=effects.jar;Collection of video effects\n"
+ + "\n"
+ + "SystemImage.GpuSupport=true\n");
+ }
+
+ private static void recordInstalledLegacyGoogleApis23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns5:sdk-addon "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-1E15FA4A\" type=\"text\">"
+ + " Terms and Conditions\n"
+ + "</license>"
+ + "<localPackage path=\"add-ons;addon-google_apis-google-23\" "
+ + "obsolete=\"false\">"
+ + " <type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + " xsi:type=\"ns5:addonDetailsType\">"
+ + " <api-level>23</api-level>"
+ + " <vendor><id>google</id><display>Google Inc.</display></vendor>"
+ + " <tag><id>google_apis</id><display>Google APIs</display></tag>"
+ + " <libraries>"
+ + " <library name=\"com.google.android.maps\">"
+ + " <description>API for Google Maps</description>"
+ + " </library>"
+ + " <library name=\"com.android.future.usb.accessory\">"
+ + " <description>API for USB Accessories</description>"
+ + " </library>"
+ + " <library name=\"com.google.android.media.effects\">"
+ + " <description>Collection of video effects</description>"
+ + " </library>"
+ + " </libraries>"
+ + " </type-details>"
+ + " <revision>"
+ + " <major>1</major>"
+ + " <minor>0</minor>"
+ + " <micro>0</micro>"
+ + " </revision>"
+ + " <display-name>Google APIs, Android 23</display-name>"
+ + " <uses-license ref=\"license-1E15FA4A\"/>"
+ + "</localPackage></ns5:sdk-addon>\n");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-23/manifest.ini",
+ "name=Google APIs\n"
+ + "name-id=google_apis\n"
+ + "vendor=Google Inc.\n"
+ + "vendor-id=google\n"
+ + "description=Android + Google APIs\n"
+ + "\n"
+ + "# version of the Android platform on which this add-on is built.\n"
+ + "api=23\n"
+ + "\n"
+ + "# revision of the add-on\n"
+ + "revision=1\n"
+ + "\n"
+ + "# list of libraries, separated by a semi-colon.\n"
+ + "libraries=com.google.android.maps;com.android.future.usb.accessory;com.google.android.media.effects\n"
+ + "\n"
+ + "# details for each library\n"
+ + "com.google.android.maps=maps.jar;API for Google Maps\n"
+ + "com.android.future.usb.accessory=usb.jar;API for USB Accessories\n"
+ + "com.google.android.media.effects=effects.jar;Collection of video effects\n"
+ + "\n"
+ + "SystemImage.GpuSupport=true\n");
+ }
+
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/targets/SystemImageManagerTest.java b/sdklib/src/test/java/com/android/sdklib/repositoryv2/targets/SystemImageManagerTest.java
new file mode 100644
index 0000000..035e107
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/targets/SystemImageManagerTest.java
@@ -0,0 +1,254 @@
+/*
+ * 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.sdklib.repositoryv2.targets;
+
+import com.android.repository.testframework.FakeProgressIndicator;
+import com.android.repository.testframework.MockFileOp;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.repositoryv2.AndroidSdkHandler;
+import com.android.sdklib.repositoryv2.meta.SysImgFactory;
+import com.google.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Tests for {@link SystemImageManager}
+ */
+public class SystemImageManagerTest extends TestCase {
+ // TODO: break up tests into separate cases
+
+ public void testSystemImageManager() throws Exception {
+ MockFileOp fop = new MockFileOp();
+ recordPlatform13(fop);
+ recordGoogleTvAddon13(fop);
+ recordGoogleApisSysImg23(fop);
+ recordSysImg23(fop);
+ recordGoogleApis13(fop);
+
+ AndroidSdkHandler handler = new AndroidSdkHandler(new File("/sdk"), fop);
+ FakeProgressIndicator progress = new FakeProgressIndicator();
+
+ SystemImageManager mgr = new SystemImageManager(handler.getSdkManager(progress),
+ (SysImgFactory)AndroidSdkHandler.getSysImgModule().createLatestFactory(), fop);
+ Set<SystemImage> images = Sets.newTreeSet(mgr.getImages());
+ progress.assertNoErrorsOrWarnings();
+ assertEquals(5, images.size());
+ Iterator<SystemImage> resultIter = images.iterator();
+
+ ISystemImage platform13 = resultIter.next();
+ verifyPlatform13(platform13);
+ assertEquals(2, platform13.getSkins().length);
+
+ verifySysImg23(resultIter.next());
+
+ ISystemImage google13 = resultIter.next();
+ verifyGoogleAddon13(google13);
+ assertEquals(2, google13.getSkins().length);
+
+ ISystemImage google23 = resultIter.next();
+ verifyGoogleApisSysImg23(google23);
+
+ ISystemImage addon13 = resultIter.next();
+ verifyTvAddon13(addon13);
+ assertEquals("google_tv_addon", addon13.getTag().getId());
+ }
+
+ private void verifyGoogleAddon13(ISystemImage img) {
+ // Nothing, just here for consistency. Note the new implementation will pick up skins from
+ // the platform.
+ }
+
+ private static void verifyPlatform13(ISystemImage img) {
+ assertEquals("armeabi", img.getAbiType());
+ assertNull(img.getAddonVendor());
+ assertEquals(new File("/sdk/platforms/android-13/images/"), img.getLocation());
+ assertEquals("default", img.getTag().getId());
+ }
+
+ private static void verifyTvAddon13(ISystemImage img) {
+ assertEquals("x86", img.getAbiType());
+ assertEquals("google", img.getAddonVendor().getId());
+ assertEquals(new File("/sdk/add-ons/addon-google_tv_addon-google-13/images/x86/"),
+ img.getLocation());
+ }
+
+ private static void verifyGoogleApisSysImg23(ISystemImage img) {
+ assertEquals("x86_64", img.getAbiType());
+ assertEquals("google", img.getAddonVendor().getId());
+ assertEquals(new File("/sdk/system-images/android-23/google_apis/x86_64/"),
+ img.getLocation());
+ assertEquals("google_apis", img.getTag().getId());
+ }
+
+ private static void verifySysImg23(ISystemImage img) {
+ assertEquals("x86", img.getAbiType());
+ assertNull(img.getAddonVendor());
+ assertEquals(new File("/sdk/system-images/android-23/default/x86/"),
+ img.getLocation());
+ assertEquals(2, img.getSkins().length);
+ assertTrue(Arrays.equals(new File[] {new File("/sdk/system-images/android-23/default/x86/skins/res1/"),
+ new File("/sdk/system-images/android-23/default/x86/skins/res2/")}, img.getSkins()));
+ assertEquals("default", img.getTag().getId());
+ }
+
+ private static void recordPlatform13(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/platforms/android-13/images/system.img");
+
+ fop.recordExistingFile("/sdk/platforms/android-13/android.jar");
+ fop.recordExistingFile("/sdk/platforms/android-13/framework.aidl");
+ fop.recordExistingFile("/sdk/platforms/android-13/skins/HVGA/layout");
+ fop.recordExistingFile("/sdk/platforms/android-13/skins/dummy.txt");
+ fop.recordExistingFile("/sdk/platforms/android-13/skins/WVGA800/layout");
+ fop.recordExistingFile("/sdk/platforms/android-13/sdk.properties",
+ "sdk.ant.templates.revision=1\n" +
+ "sdk.skin.default=WXGA\n");
+ fop.recordExistingFile("/sdk/platforms/android-13/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns2:sdk-repository "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-2A86BE32\" type=\"text\">License Text\n</license>"
+ + "<localPackage path=\"platforms;android-13\" obsolete=\"false\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns2:platformDetailsType\"><api-level>13</api-level>"
+ + "<layoutlib api=\"4\"/></type-details><revision><major>1</major>"
+ + "</revision><display-name>API 13: Android 3.2 (Honeycomb)</display-name>"
+ + "<uses-license ref=\"license-2A86BE32\"/><dependencies>"
+ + "<dependency path=\"tools\"><min-revision><major>12</major></min-revision>"
+ + "</dependency></dependencies></localPackage></ns2:sdk-repository>");
+ }
+
+ private static void recordGoogleTvAddon13(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns5:sdk-addon xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-A06C75BE\" type=\"text\">Terms and Conditions\n"
+ + "</license><localPackage "
+ + "path=\"add-ons;addon-google_tv_addon-google-13\" obsolete=\"false\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns5:addonDetailsType\"><api-level>13</api-level>"
+ + "<vendor><id>google</id><display>Google Inc.</display></vendor>"
+ + "<tag><id>google_tv_addon</id><display>Google TV Addon</display></tag>"
+ + "<default-skin>720p</default-skin>"
+ + "</type-details><revision><major>1</major><minor>0</minor>"
+ + "<micro>0</micro></revision>"
+ + "<display-name>Google TV Addon, Android 13</display-name>"
+ + "<uses-license ref=\"license-A06C75BE\"/></localPackage>"
+ + "</ns5:sdk-addon>\n");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/skins/1080p/layout");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/skins/dummy.txt");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/skins/720p-overscan/layout");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_tv_addon-google-13/images/x86/system.img");
+ }
+
+ private static void recordSysImg23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/system-images/android-23/default/x86/system.img");
+ fop.recordExistingFile("/sdk/system-images/android-23/default/x86/skins/res1/layout");
+ fop.recordExistingFile("/sdk/system-images/android-23/default/x86/skins/dummy");
+ fop.recordExistingFile("/sdk/system-images/android-23/default/x86/skins/res2/layout");
+
+ fop.recordExistingFile("/sdk/system-images/android-23/default/x86/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns3:sdk-sys-img "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-A78C4257\" type=\"text\">Terms and Conditions\n"
+ + "</license><localPackage path=\"system-images;android-23;default;x86\" "
+ + "obsolete=\"false\">"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns3:sysImgDetailsType\"><api-level>23</api-level>"
+ + "<tag><id>default</id><display>Default</display></tag><abi>x86</abi>"
+ + "</type-details><revision><major>5</major></revision>"
+ + "<display-name>Intel x86 Atom System Image</display-name>"
+ + "<uses-license ref=\"license-A78C4257\"/></localPackage>"
+ + "</ns3:sdk-sys-img>\n");
+ }
+
+ private static void recordGoogleApisSysImg23(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/system-images/android-23/google_apis/x86_64/system.img");
+
+ fop.recordExistingFile("/sdk/system-images/android-23/google_apis/x86_64/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ + "<ns3:sdk-sys-img "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">"
+ + "<license id=\"license-9A5C00D5\" type=\"text\">Terms and Conditions\n"
+ + "</license><localPackage "
+ + "path=\"system-images;android-23;google_apis;x86_64\" "
+ + "obsolete=\"false\"><type-details "
+ + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns3:sysImgDetailsType\"><api-level>23</api-level>"
+ + "<tag><id>google_apis</id><display>Google APIs</display></tag>"
+ + "<vendor><id>google</id><display>Google Inc.</display></vendor>"
+ + "<abi>x86_64</abi></type-details><revision><major>9</major></revision>"
+ + "<display-name>Google APIs Intel x86 Atom_64 System Image</display-name>"
+ + "<uses-license ref=\"license-9A5C00D5\"/></localPackage>"
+ + "</ns3:sdk-sys-img>\n");
+ }
+
+ private static void recordGoogleApis13(MockFileOp fop) {
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-13/images/system.img");
+ fop.recordExistingFile("/sdk/add-ons/addon-google_apis-google-13/package.xml",
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
+ + "<ns5:sdk-addon "
+ + "xmlns:ns2=\"http://schemas.android.com/sdk/android/repo/repository2/01\" "
+ + "xmlns:ns3=\"http://schemas.android.com/sdk/android/repo/sys-img2/01\" "
+ + "xmlns:ns4=\"http://schemas.android.com/repository/android/common/01\" "
+ + "xmlns:ns5=\"http://schemas.android.com/sdk/android/repo/addon2/01\">\n"
+ + "<license id=\"license-DB79309F\" type=\"text\">\n"
+ + "Terms and Conditions\n"
+ + "</license>\n"
+ + "<localPackage path=\"add-ons;addon-google_apis-google-13\" "
+ + "obsolete=\"false\">\n"
+ + "<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ + "xsi:type=\"ns5:addonDetailsType\">\n"
+ + "<api-level>13</api-level>\n"
+ + "<vendor>\n"
+ + "<id>google</id>\n"
+ + "<display>Google Inc.</display>\n"
+ + "</vendor>\n"
+ + "<tag>\n"
+ + "<id>google_apis</id>\n"
+ + "<display>\n"
+ + "Google APIs</display>\n"
+ + "</tag>\n"
+ + "</type-details>\n"
+ + "<revision>\n"
+ + "<major>1</major>\n"
+ + "<minor>0</minor>\n"
+ + "<micro>0</micro>\n"
+ + "</revision>\n"
+ + "<display-name>Google APIs, Android 13</display-name>\n"
+ + "<uses-license ref=\"license-DB79309F\"/>\n"
+ + "</localPackage>\n"
+ + "</ns5:sdk-addon>\n");
+ }
+
+}
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/addons_list_sample_1.xml
old mode 100755
new mode 100644
similarity index 100%
copy from base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_1.xml
copy to sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/addons_list_sample_1.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_2.xml b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/addons_list_sample_2.xml
old mode 100755
new mode 100644
similarity index 100%
copy from base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_2.xml
copy to sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/addons_list_sample_2.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/addons_list_sample_3.xml b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/addons_list_sample_3.xml
new file mode 100644
index 0000000..3c7c3a2
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/addons_list_sample_3.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<sdk:sdk-addons-list
+ xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/3"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <site xsi:type="sdk:addonSiteType">
+ <url>http://www.example.com/my_addons2.xml</url>
+ <displayName> <!-- we'll ignore leading/trailing spacing -->
+ My Example Add-ons.
+ </displayName>
+ </site>
+
+ <site xsi:type="sdk:addonSiteType">
+ <url>http://www.example.co.jp/addons.xml</url>
+ <!-- this file is UTF-8 so we support character sets -->
+ <displayName>ありがとうございます。</displayName>
+ </site>
+
+ <site xsi:type="sdk:addonSiteType">
+ <url>http://www.example.com/</url>
+ <displayName>Example of directory URL.</displayName>
+ </site>
+
+ <site xsi:type="sdk:sysImgSiteType">
+ <url>http://www.example.com/</url>
+ <displayName>Example of sys-img URL using the default xml filename.</displayName>
+ </site>
+
+ <site xsi:type="sdk:sysImgSiteType">
+ <url>http://www.example.com/specific_file.xml</url>
+ <displayName>Example of sys-img URL using a specific xml filename.</displayName>
+ </site>
+
+ <site xsi:type="sdk:addonSiteType">
+ <url>relative/url.xml</url>
+ <displayName>Relative URL.</displayName>
+ </site>
+
+</sdk:sdk-addons-list>
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/remote_maven_repo.xml b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/remote_maven_repo.xml
new file mode 100644
index 0000000..d8e931b
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/remote_maven_repo.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<sdk:sdk-addon
+ xmlns:sdk="http://schemas.android.com/sdk/android/repo/addon2/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <remotePackage path="m2repository;com;android;group1;artifact1;1.2.3">
+ <type-details xsi:type="sdk:mavenType">
+ <vendor>
+ <id>google</id>
+ <display>Google Inc.</display>
+ </vendor>
+ </type-details>
+ <revision>
+ <major>3</major>
+ </revision>
+ <display-name>maven package</display-name>
+ <archives>
+ <archive>
+ <complete>
+ <size>65536</size>
+ <checksum>2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>http://example.com/2/arch1</url>
+ </complete>
+ </archive>
+ </archives>
+ </remotePackage>
+
+</sdk:sdk-addon>
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/repositories.xml b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/repositories.xml
new file mode 100644
index 0000000..e60746d
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/repositories.xml
@@ -0,0 +1,9 @@
+### User Sources for Android SDK Manager
+#Fri Sep 25 13:49:40 PDT 2015
+disp02=amazon
+disp01=sony
+count=3
+disp00=samsung
+src02=https\://s3.amazonaws.com/android-sdk-manager/redist/addon.xml
+src01=http\://dl.developer.sony.com/wearables/sdks/Sony_Add-on_SDK/Sony-Add-on-SDK.xml
+src00=http\://developer.samsung.com/sdk-manager/repository/Samsung-SDK.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/repository2_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/repository2_sample_1.xml
new file mode 100755
index 0000000..098ebba
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/repositoryv2/testdata/repository2_sample_1.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<sdk:sdk-repository
+ xmlns:sdk="http://schemas.android.com/sdk/android/repo/repository2/01"
+ xmlns:generic="http://schemas.android.com/repository/android/generic/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+ <license type="text" id="license1">
+ This is the license
+ for this platform.
+ </license>
+
+ <license id="license2">
+ Licenses are only of type 'text' right now, so this is implied.
+ </license>
+
+ <!-- Inner elements must be either platform, add-on, doc or tool.
+ There can be 0 or more of each, in any order. -->
+ <remotePackage path="platforms;android-22">
+ <type-details xsi:type="sdk:platformDetailsType">
+ <api-level>1</api-level>
+ <layoutlib api="5"/>
+ </type-details>
+ <revision>
+ <major>3</major>
+ </revision>
+ <display-name>Lollipop MR1</display-name>
+ <uses-license ref="license1"/>
+ <dependencies>
+ <dependency path="tools">
+ <min-revision>
+ <major>2</major>
+ <micro>1</micro>
+ </min-revision>
+ </dependency>
+ </dependencies>
+
+ <archives>
+ <archive>
+ <complete>
+ <size>65536</size>
+ <checksum>2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>http://www.example.com/files/plat1.zip</url>
+ </complete>
+ </archive>
+ <archive>
+ <host-bits>64</host-bits>
+ <host-os>windows</host-os>
+ <complete>
+ <size>65536</size>
+ <checksum>1234ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>http://www.example.com/files/plat1.zip</url>
+ </complete>
+ <patches>
+ <patch>
+ <based-on>
+ <major>1</major>
+ </based-on>
+ <size>4321</size>
+ <checksum>2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>something</url>
+ </patch>
+ </patches>
+ </archive>
+ </archives>
+ </remotePackage>
+
+
+ <remotePackage path="docs">
+ <type-details xsi:type="generic:genericDetailsType"/>
+ <revision>
+ <major>1</major>
+ </revision>
+ <display-name>Documentation</display-name>
+ <archives>
+ <archive>
+ <complete>
+ <size>65536</size>
+ <checksum>2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>http://www.example.com/docs/docs1.zip</url>
+ </complete>
+ </archive>
+ </archives>
+ </remotePackage>
+
+ <remotePackage path="sources;android-1">
+ <type-details xsi:type="sdk:sourceDetailsType">
+ <api-level>1</api-level>
+ </type-details>
+ <revision>
+ <major>1</major>
+ </revision>
+ <display-name>Sources for android-1</display-name>
+ <archives>
+ <archive>
+ <complete>
+ <size>65536</size>
+ <checksum>1234ae37115ebf13412bbef91339ee0d94541234</checksum>
+ <url>http://www.example.com/plat1/sources1.zip</url>
+ </complete>
+ </archive>
+ </archives>
+ </remotePackage>
+
+</sdk:sdk-repository>
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-activityalias.xml b/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-activityalias.xml
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-activityalias.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-activityalias.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml b/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp.xml b/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp.xml
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp2.xml b/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp2.xml
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp2.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/AndroidManifest-testapp2.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/addon2_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon2_sample_1.xml
new file mode 100755
index 0000000..008a5e2
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/addon2_sample_1.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<sdk:sdk-addon
+ xmlns:sdk="http://schemas.android.com/sdk/android/addon2/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+ <license type="text" id="license1">
+ This is the license
+ for this platform.
+ </license>
+
+ <license id="license2">
+ Licenses are only of type 'text' right now, so this is implied.
+ </license>
+
+ <!-- Inner elements must be either platform, add-on, doc or tool.
+ There can be 0 or more of each, in any order. -->
+
+ <package path="addon;uno">
+ <uiName>My First Add-on for API 5, rev 0</uiName>
+
+ <type-details xsi:type="sdk:addonDetailsType">
+ <vendor>
+ <id>John_Doe</id>
+ <display>John Doe</display>
+ </vendor>
+ <api-level>1</api-level>
+ </type-details>
+
+ <revision>
+ <major>1</major>
+ </revision>
+ <uses-license ref="license2"/>
+
+ <archives>
+ <archive>
+ <size>65536</size>
+ <checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>http://www.example.com/add-ons/first.zip</url>
+ </archive>
+ </archives>
+ <!-- The libs node is mandatory, however it can be empty.
+ TODO: libs and layoutlib?-->
+ </package>
+
+ <package path="addon;dos">
+ <uiName>My Second add-on</uiName>
+ <type-details xsi:type="sdk:addonDetailsType">
+ <vendor>
+ <id>John_Deer</id>
+ <display>John Deer</display>
+ </vendor>
+ <api-level>2</api-level>
+ </type-details>
+
+ <revision>
+ <major>42</major>
+ <minor>0</minor>
+ <micro>1</micro>
+ </revision>
+ <archives>
+ <archive>
+ <host-os>windows</host-os>
+ <host-bits>64</host-bits>
+ <size>65536</size>
+ <checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>distrib/second-42-win.zip</url>
+ </archive>
+ <archive>
+ <host-os>linux</host-os>
+ <host-bits>64</host-bits>
+ <size>65536</size>
+ <checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>distrib/second-42-linux.tar.bz2</url>
+ </archive>
+ </archives>
+ <uses-license ref="license2" />
+ <!-- No layoutlib element in this package. It's optional. -->
+ </package>
+
+ <package path="extra;one" obsolete="true">
+ <uiName>Random name, not an id!</uiName>
+ <type-details xsi:type="sdk:extraDetailsType">
+ <vendor>
+ <id>cyclop</id>
+ <display>The big bus</display>
+ </vendor>
+ </type-details>
+ <uses-license ref="license2" />
+ <revision>
+ <major>43</major>
+ <minor>42</minor>
+ <micro>41</micro>
+ </revision>
+ <archives>
+ <archive>
+ <host-os>windows</host-os>
+ <host-bits>32</host-bits>
+ <size>65536</size>
+ <checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>distrib/extraduff.zip</url>
+ </archive>
+ </archives>
+ <dependencies>
+ <dependency id="tools">
+ <min-revision>
+ <major>3</major>
+ <minor>2</minor>
+ <micro>1</micro>
+ </min-revision>
+ </dependency>
+ </dependencies>
+ </package>
+
+ <package path="extra;two">
+ <uiName>Yet another extra, by Android</uiName>
+ <type-details xsi:type="sdk:extraDetailsType">
+ <vendor>
+ <id>android_vendor</id>
+ <display>Android Vendor</display>
+ </vendor>
+ </type-details>
+
+ <uses-license ref="license2" />
+ <revision>
+ <major>2</major>
+ <micro>1</micro>
+ </revision>
+ <archives>
+ <archive>
+ <host-os>windows</host-os>
+ <host-bits>64</host-bits>
+ <jvm-bits>32</jvm-bits>
+ <min-jvm-version>1.7</min-jvm-version>
+ <size>65536</size>
+ <checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</checksum>
+ <url>distrib/extra_mega_duff.zip</url>
+ </archive>
+ </archives>
+ <dependencies>
+ <dependency id="tools">
+ <min-revision>
+ <major>3</major>
+ </min-revision>
+ </dependency>
+ <dependency id="platforms;android-42">
+ <min-revision>
+ <major>1</major>
+ </min-revision>
+ </dependency>
+ </dependencies>
+
+ </package>
+
+</sdk:sdk-addon>
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_1.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_1.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_1.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_2.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_2.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_2.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_2.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_3.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_3.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_3.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_3.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_4.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_4.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_4.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_4.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_5.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_5.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_5.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_5.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_6.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_6.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_6.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_6.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_7.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_7.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_7.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_7.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_1.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_1.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_1.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_2.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_2.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_2.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/addons_list_sample_2.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repositories.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repositories.xml
new file mode 100644
index 0000000..e60746d
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/repositories.xml
@@ -0,0 +1,9 @@
+### User Sources for Android SDK Manager
+#Fri Sep 25 13:49:40 PDT 2015
+disp02=amazon
+disp01=sony
+count=3
+disp00=samsung
+src02=https\://s3.amazonaws.com/android-sdk-manager/redist/addon.xml
+src01=http\://dl.developer.sony.com/wearables/sdks/Sony_Add-on_SDK/Sony-Add-on-SDK.xml
+src00=http\://developer.samsung.com/sdk-manager/repository/Samsung-SDK.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_01.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_01.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_01.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_01.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_02.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_02.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_02.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_02.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_03.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_03.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_03.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_03.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_04.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_04.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_04.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_04.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_05.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_05.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_05.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_05.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_06.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_06.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_06.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_06.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_07.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_07.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_07.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_07.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_08.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_08.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_08.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_08.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_09.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_09.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_09.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_09.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_10.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_10.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_10.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_10.xml
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_12-test.xml b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_12-test.xml
new file mode 100755
index 0000000..3497302
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/repository_sample_12-test.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<sdk:sdk-repository
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository2/01"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <package path="dummy;foo">
+ <type-details xsi:type="sdk:platformDetailsType">
+ <api-level>1</api-level>
+ <layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>0</sdk:revision>
+ </layoutlib>
+ </type-details>
+ </package>
+</sdk:sdk-repository>
+
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/stats_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/testdata/stats_sample_1.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/stats_sample_1.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/stats_sample_1.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_1.xml b/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_1.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_1.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_1.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_2.xml b/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_2.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_2.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_2.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_3.xml b/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_3.xml
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_3.xml
rename to sdklib/src/test/java/com/android/sdklib/testdata/sys_img_sample_3.xml
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.dtd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.dtd
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.dtd
rename to sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.dtd
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd
rename to sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/XMLSchema.xsd
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/datatypes.dtd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/datatypes.dtd
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/datatypes.dtd
rename to sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/datatypes.dtd
diff --git a/base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/xml.xsd b/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/xml.xsd
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/xml.xsd
rename to sdklib/src/test/java/com/android/sdklib/testdata/www.w3.org/2001/xml.xsd
diff --git a/base/sdklib/src/test/java/com/android/sdklib/util/BSPatchTest.java b/sdklib/src/test/java/com/android/sdklib/util/BSPatchTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/util/BSPatchTest.java
rename to sdklib/src/test/java/com/android/sdklib/util/BSPatchTest.java
diff --git a/base/sdklib/src/test/java/com/android/sdklib/util/CommandLineParserTest.java b/sdklib/src/test/java/com/android/sdklib/util/CommandLineParserTest.java
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/util/CommandLineParserTest.java
rename to sdklib/src/test/java/com/android/sdklib/util/CommandLineParserTest.java
diff --git a/base/sdklib/src/test/java/com/android/sdklib/util/LineUtilTest.java b/sdklib/src/test/java/com/android/sdklib/util/LineUtilTest.java
old mode 100755
new mode 100644
similarity index 100%
rename from base/sdklib/src/test/java/com/android/sdklib/util/LineUtilTest.java
rename to sdklib/src/test/java/com/android/sdklib/util/LineUtilTest.java
diff --git a/base/sdklib/test.gradle b/sdklib/test.gradle
similarity index 100%
rename from base/sdklib/test.gradle
rename to sdklib/test.gradle
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index eb684bb..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,133 +0,0 @@
-include ':base:annotations'
-include ':base:api-generator'
-include ':base:ant-tasks'
-include ':base:archquery'
-include ':base:asset-studio'
-include ':base:common'
-include ':base:docs'
-include ':base:ddmlib'
-include ':base:perflib'
-include ':base:chartlib'
-include ':base:draw9patch'
-include ':base:dvlib'
-include ':base:jobb'
-include ':base:layoutlib-api'
-include ':base:lint'
-include ':base:lint-api'
-include ':base:lint-checks'
-include ':base:lint-tests'
-include ':base:manifest-merger'
-include ':base:ninepatch'
-include ':base:rule-api'
-include ':base:screenshot2'
-include ':base:sdk-common'
-include ':base:sdklib'
-include ':base:sdklib-test'
-include ':base:testutils'
-include ':base:gradle-import'
-include ':base:vector-drawable-tool'
-
-include ':base:profile'
-include ':base:builder-model'
-include ':base:builder-test-api'
-include ':base:transform-api'
-include ':base:builder'
-include ':base:gradle-model'
-include ':base:gradle-core'
-include ':base:gradle'
-include ':base:gradle-experimental'
-include ':base:integration-test'
-include ':base:project-test-lib'
-include ':base:project-test'
-include ':base:google-services'
-
-include ':base:templates'
-
-include ':base:jack:jack-api'
-include ':base:jack:jill-api'
-
-project(':base:api-generator' ).projectDir = new File(rootDir, 'base/misc/api-generator')
-project(':base:ant-tasks' ).projectDir = new File(rootDir, 'base/legacy/ant-tasks')
-project(':base:archquery' ).projectDir = new File(rootDir, 'base/legacy/archquery')
-project(':base:dvlib' ).projectDir = new File(rootDir, 'base/device_validator/dvlib')
-project(':base:lint' ).projectDir = new File(rootDir, 'base/lint/cli')
-project(':base:lint-api' ).projectDir = new File(rootDir, 'base/lint/libs/lint-api')
-project(':base:lint-checks' ).projectDir = new File(rootDir, 'base/lint/libs/lint-checks')
-project(':base:lint-tests' ).projectDir = new File(rootDir, 'base/lint/libs/lint-tests')
-project(':base:screenshot2' ).projectDir = new File(rootDir, 'base/misc/screenshot2')
-project(':base:sdklib-test' ).projectDir = new File(rootDir, 'base/sdklib')
-project(':base:sdklib-test' ).buildFileName = 'test.gradle'
-
-project(':base:profile' ).projectDir = new File(rootDir, 'base/build-system/profile')
-project(':base:builder-model' ).projectDir = new File(rootDir, 'base/build-system/builder-model')
-project(':base:builder-test-api' ).projectDir = new File(rootDir, 'base/build-system/builder-test-api')
-project(':base:transform-api' ).projectDir = new File(rootDir, 'base/build-system/transform-api')
-project(':base:builder' ).projectDir = new File(rootDir, 'base/build-system/builder')
-project(':base:docs' ).projectDir = new File(rootDir, 'base/build-system/docs')
-project(':base:manifest-merger' ).projectDir = new File(rootDir, 'base/build-system/manifest-merger')
-project(':base:gradle-core' ).projectDir = new File(rootDir, 'base/build-system/gradle-core')
-project(':base:gradle' ).projectDir = new File(rootDir, 'base/build-system/gradle')
-project(':base:gradle-experimental').projectDir = new File(rootDir, 'base/build-system/gradle-experimental')
-project(':base:integration-test' ).projectDir = new File(rootDir, 'base/build-system/integration-test')
-project(':base:project-test-lib' ).projectDir = new File(rootDir, 'base/build-system/project-test-lib')
-project(':base:project-test' ).projectDir = new File(rootDir, 'base/build-system/project-test')
-project(':base:google-services' ).projectDir = new File(rootDir, 'base/build-system/google-services')
-
-include ':swt:chimpchat'
-include ':swt:ddms'
-include ':swt:ddmuilib'
-include ':swt:hierarchyviewer2'
-include ':swt:hierarchyviewer2lib'
-include ':swt:monkeyrunner'
-include ':swt:sdkmanager'
-include ':swt:sdkuilib'
-include ':swt:sdkstats'
-include ':swt:swtmenubar'
-include ':swt:traceview'
-include ':swt:uiautomatorviewer'
-
-project(':swt:ddms' ).projectDir = new File(rootDir, 'swt/ddms/app')
-project(':swt:ddmuilib' ).projectDir = new File(rootDir, 'swt/ddms/ddmuilib')
-project(':swt:hierarchyviewer2' ).projectDir = new File(rootDir, 'swt/hierarchyviewer2/app')
-project(':swt:hierarchyviewer2lib').projectDir = new File(rootDir, 'swt/hierarchyviewer2/hierarchyviewer2lib')
-project(':swt:sdkmanager' ).projectDir = new File(rootDir, 'swt/sdkmanager/app')
-project(':swt:sdkuilib' ).projectDir = new File(rootDir, 'swt/sdkmanager/sdkuilib')
-
-
-include ':sdk'
-project(':sdk').projectDir = new File(rootDir.getParentFile(), "sdk")
-
-include ':sdk:annotations'
-project(':sdk:annotations').projectDir = new File(rootDir.getParentFile(), "sdk/annotations")
-
-include ':sdk:find-java'
-project(':sdk:find-java').projectDir = new File(rootDir.getParentFile(), "sdk/find_java")
-
-include ':sdk:avdlauncher'
-project(':sdk:avdlauncher').projectDir = new File(rootDir.getParentFile(), "sdk/avdlauncher")
-
-include ':sdk:eclipse:monitor'
-project(':sdk:eclipse:monitor').projectDir = new File(rootDir.getParentFile(), "sdk/eclipse/monitor")
-
-include ':sdk:sdklauncher'
-project(':sdk:sdklauncher').projectDir = new File(rootDir.getParentFile(), "sdk/sdklauncher")
-
-include ':emulator'
-project(':emulator').projectDir = new File(rootDir.getParentFile(), "external/qemu")
-
-include ':mksdcard'
-project(':mksdcard').projectDir = new File(rootDir.getParentFile(), "sdk/emulator/mksdcard")
-
-include ':external:fat32lib'
-
-include ':external:emma'
-project(':external:emma').projectDir = new File(rootDir.getParentFile(), "external/emma")
-
-include ':dataBinding'
-project(':dataBinding').projectDir = new File(rootDir, "data-binding")
-include ':dataBinding:baseLibrary'
-project(':dataBinding:baseLibrary').projectDir = new File(rootDir, "data-binding/baseLibrary")
-include ':dataBinding:compiler'
-project(':dataBinding:compiler').projectDir = new File(rootDir, "data-binding/compiler")
-include ':dataBinding:compilerCommon'
-project(':dataBinding:compilerCommon').projectDir = new File(rootDir, "data-binding/compilerCommon")
diff --git a/base/templates/NOTICE b/templates/NOTICE
similarity index 100%
rename from base/templates/NOTICE
rename to templates/NOTICE
diff --git a/templates/activities/AlwaysOnWearActivity/globals.xml.ftl b/templates/activities/AlwaysOnWearActivity/globals.xml.ftl
new file mode 100644
index 0000000..41a4c6b
--- /dev/null
+++ b/templates/activities/AlwaysOnWearActivity/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "../common/wear_common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/AlwaysOnWearActivity/recipe.xml.ftl b/templates/activities/AlwaysOnWearActivity/recipe.xml.ftl
new file mode 100644
index 0000000..80427d0
--- /dev/null
+++ b/templates/activities/AlwaysOnWearActivity/recipe.xml.ftl
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <merge from="root/AndroidManifestPermissions.xml"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+<#if appManifestOut??>
+ <merge from="root/AndroidManifestPermissions.xml"
+ to="${escapeXmlAttribute(appManifestOut)}/AndroidManifest.xml" />
+</#if>
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+
+ <instantiate from="root/res/layout/blank_activity.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/src/app_package/BlankActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/templates/activities/AlwaysOnWearActivity/root/AndroidManifest.xml.ftl b/templates/activities/AlwaysOnWearActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..8d1acf1
--- /dev/null
+++ b/templates/activities/AlwaysOnWearActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,22 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <uses-library android:name="com.google.android.wearable" android:required="false" />
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ android:theme="@android:style/Theme.DeviceDefault.Light"
+ >
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/AlwaysOnWearActivity/root/AndroidManifestPermissions.xml b/templates/activities/AlwaysOnWearActivity/root/AndroidManifestPermissions.xml
new file mode 100644
index 0000000..9155344
--- /dev/null
+++ b/templates/activities/AlwaysOnWearActivity/root/AndroidManifestPermissions.xml
@@ -0,0 +1,5 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+</manifest>
diff --git a/base/templates/activities/AlwaysOnWearActivity/root/build.gradle.ftl b/templates/activities/AlwaysOnWearActivity/root/build.gradle.ftl
similarity index 100%
rename from base/templates/activities/AlwaysOnWearActivity/root/build.gradle.ftl
rename to templates/activities/AlwaysOnWearActivity/root/build.gradle.ftl
diff --git a/base/templates/activities/AlwaysOnWearActivity/root/res/layout/blank_activity.xml.ftl b/templates/activities/AlwaysOnWearActivity/root/res/layout/blank_activity.xml.ftl
similarity index 100%
rename from base/templates/activities/AlwaysOnWearActivity/root/res/layout/blank_activity.xml.ftl
rename to templates/activities/AlwaysOnWearActivity/root/res/layout/blank_activity.xml.ftl
diff --git a/base/templates/activities/AlwaysOnWearActivity/root/res/values/strings.xml.ftl b/templates/activities/AlwaysOnWearActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/AlwaysOnWearActivity/root/res/values/strings.xml.ftl
rename to templates/activities/AlwaysOnWearActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/activities/AlwaysOnWearActivity/root/src/app_package/BlankActivity.java.ftl b/templates/activities/AlwaysOnWearActivity/root/src/app_package/BlankActivity.java.ftl
similarity index 100%
rename from base/templates/activities/AlwaysOnWearActivity/root/src/app_package/BlankActivity.java.ftl
rename to templates/activities/AlwaysOnWearActivity/root/src/app_package/BlankActivity.java.ftl
diff --git a/templates/activities/AlwaysOnWearActivity/template.xml b/templates/activities/AlwaysOnWearActivity/template.xml
new file mode 100644
index 0000000..f3ee8e1
--- /dev/null
+++ b/templates/activities/AlwaysOnWearActivity/template.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Always On Wear Activity"
+ minApi="20"
+ minBuildApi="20"
+ description="Creates an always on activity for Android Wear">
+
+ <category value="Activity" />
+ <formfactor value="Wear" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="true"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it the default launchable activity" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_thumb.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/AlwaysOnWearActivity/template_thumb.png b/templates/activities/AlwaysOnWearActivity/template_thumb.png
new file mode 100644
index 0000000..7fec89a
Binary files /dev/null and b/templates/activities/AlwaysOnWearActivity/template_thumb.png differ
diff --git a/base/templates/activities/AndroidTVActivity/globals.xml.ftl b/templates/activities/AndroidTVActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/globals.xml.ftl
rename to templates/activities/AndroidTVActivity/globals.xml.ftl
diff --git a/templates/activities/AndroidTVActivity/recipe.xml.ftl b/templates/activities/AndroidTVActivity/recipe.xml.ftl
new file mode 100644
index 0000000..8ee0015
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/recipe.xml.ftl
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.android.support:appcompat-v7:${targetApi}.+"/>
+ <dependency mavenUrl="com.github.bumptech.glide:glide:3.4.+"/>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/colors.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
+
+ <merge from="root/res/values/themes.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/themes.xml" />
+
+ <copy from="root/res/drawable"
+ to="${escapeXmlAttribute(resOut)}/drawable" />
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ <copy from="root/res/drawable-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
+
+ <instantiate from="root/res/layout/activity_main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/res/layout/activity_details.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${detailsLayoutName}.xml" />
+
+ <instantiate from="root/res/layout/playback_controls.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/playback_controls.xml" />
+
+ <instantiate from="root/src/app_package/MainActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <instantiate from="root/src/app_package/MainFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${mainFragment}.java" />
+
+ <instantiate from="root/src/app_package/DetailsActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${detailsActivity}.java" />
+
+ <instantiate from="root/src/app_package/VideoDetailsFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${detailsFragment}.java" />
+
+ <instantiate from="root/src/app_package/Movie.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/Movie.java" />
+
+ <instantiate from="root/src/app_package/MovieList.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/MovieList.java" />
+
+ <instantiate from="root/src/app_package/CardPresenter.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/CardPresenter.java" />
+
+ <instantiate from="root/src/app_package/DetailsDescriptionPresenter.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/DetailsDescriptionPresenter.java" />
+
+ <instantiate from="root/src/app_package/PlaybackOverlayActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/PlaybackOverlayActivity.java" />
+
+ <instantiate from="root/src/app_package/PlaybackOverlayFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/PlaybackOverlayFragment.java" />
+
+ <instantiate from="root/src/app_package/Utils.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/Utils.java" />
+
+ <instantiate from="root/src/app_package/ErrorFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/ErrorFragment.java" />
+
+ <instantiate from="root/src/app_package/BrowseErrorActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/BrowseErrorActivity.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/templates/activities/AndroidTVActivity/root/AndroidManifest.xml.ftl b/templates/activities/AndroidTVActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..9862c0a
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,44 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
+ <uses-feature android:name="android.software.leanback"
+ android:required="true" />
+
+ <application>
+
+ <activity android:name="${packageName}.${activityClass}"
+ android:icon="@drawable/app_icon_your_company"
+ android:logo="@drawable/app_icon_your_company"
+ android:banner="@drawable/app_icon_your_company"
+ android:screenOrientation="landscape"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+
+ <activity android:name="${packageName}.${detailsActivity}" />
+ <activity android:name="PlaybackOverlayActivity" />
+ <activity android:name="BrowseErrorActivity" />
+
+ </application>
+
+</manifest>
diff --git a/base/templates/activities/AndroidTVActivity/root/build.gradle.ftl b/templates/activities/AndroidTVActivity/root/build.gradle.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/build.gradle.ftl
rename to templates/activities/AndroidTVActivity/root/build.gradle.ftl
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum_card.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum_card.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum_card.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/app_icon_quantum_card.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/card_background_default.9.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/card_background_default.9.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/card_background_default.9.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/card_background_default.9.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/default_background.xml b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/default_background.xml
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/default_background.xml
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/default_background.xml
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/grid_bg.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/grid_bg.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/grid_bg.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/grid_bg.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/movie.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/movie.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/movie.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/movie.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_disabled.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_disabled.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_disabled.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_disabled.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_focussed.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_focussed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_focussed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_focussed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_normal.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_normal.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_normal.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_normal.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_pressed.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_pressed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_pressed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/scrubber_pressed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/shadow7.9.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/shadow7.9.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/shadow7.9.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/shadow7.9.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/star_icon.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/star_icon.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/star_icon.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/star_icon.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_banner.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_banner.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_banner.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_banner.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_icon.png b/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_icon.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_icon.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-hdpi/videos_by_google_icon.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum.png b/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum_card.png b/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum_card.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum_card.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-mdpi/app_icon_quantum_card.png
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png b/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-mdpi/ic_launcher.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_banner.png b/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_banner.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_banner.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_banner.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_icon.png b/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_icon.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_icon.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-mdpi/videos_by_google_icon.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum_card.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum_card.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum_card.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/app_icon_quantum_card.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/default_background.xml b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/default_background.xml
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/default_background.xml
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/default_background.xml
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/grid_bg.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/grid_bg.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/grid_bg.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/grid_bg.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_launcher.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_launcher.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_normal.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_normal.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_normal.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_normal.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_focussed.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_focussed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_focussed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_focussed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_normal.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_normal.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_normal.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_normal.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_pressed.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_pressed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_pressed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_action_pressed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_focussed.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_focussed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_focussed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_focussed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_normal.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_normal.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_normal.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_normal.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_pressed.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_pressed.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_pressed.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/ic_play_playcontrol_pressed.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/movie.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/movie.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/movie.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/movie.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/star_icon.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/star_icon.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/star_icon.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/star_icon.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_banner.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_banner.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_banner.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_banner.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_icon.png b/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_icon.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_icon.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xhdpi/videos_by_google_icon.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum.png b/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum_card.png b/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum_card.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum_card.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/app_icon_quantum_card.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_banner.png b/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_banner.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_banner.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_banner.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_icon.png b/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_icon.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_icon.png
rename to templates/activities/AndroidTVActivity/root/res/drawable-xxhdpi/videos_by_google_icon.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum.png b/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum_card.png b/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum_card.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum_card.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/app_icon_quantum_card.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_your_company.png b/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_your_company.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/app_icon_your_company.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/app_icon_your_company.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/details_img.png b/templates/activities/AndroidTVActivity/root/res/drawable/details_img.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/details_img.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/details_img.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/ic_action_a.png b/templates/activities/AndroidTVActivity/root/res/drawable/ic_action_a.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/ic_action_a.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/ic_action_a.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/ic_title.png b/templates/activities/AndroidTVActivity/root/res/drawable/ic_title.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/ic_title.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/ic_title.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/movie.png b/templates/activities/AndroidTVActivity/root/res/drawable/movie.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/movie.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/movie.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/player_bg_gradient_dark.xml b/templates/activities/AndroidTVActivity/root/res/drawable/player_bg_gradient_dark.xml
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/player_bg_gradient_dark.xml
rename to templates/activities/AndroidTVActivity/root/res/drawable/player_bg_gradient_dark.xml
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/shadow7.9.png b/templates/activities/AndroidTVActivity/root/res/drawable/shadow7.9.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/shadow7.9.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/shadow7.9.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_banner.png b/templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_banner.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_banner.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_banner.png
diff --git a/base/templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_icon.png b/templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_icon.png
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_icon.png
rename to templates/activities/AndroidTVActivity/root/res/drawable/videos_by_google_icon.png
diff --git a/templates/activities/AndroidTVActivity/root/res/layout/activity_details.xml.ftl b/templates/activities/AndroidTVActivity/root/res/layout/activity_details.xml.ftl
new file mode 100644
index 0000000..b154629
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/res/layout/activity_details.xml.ftl
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/details_fragment"
+ android:name="${packageName}.${detailsFragment}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${relativePackage!''}.${detailsActivity}"
+ tools:deviceIds="tv" />
diff --git a/templates/activities/AndroidTVActivity/root/res/layout/activity_main.xml.ftl b/templates/activities/AndroidTVActivity/root/res/layout/activity_main.xml.ftl
new file mode 100644
index 0000000..1d2370f
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/res/layout/activity_main.xml.ftl
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/main_browse_fragment"
+ android:name="${packageName}.${mainFragment}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${relativePackage!''}.${activityClass}"
+ tools:deviceIds="tv"
+ tools:ignore="MergeRootFrame" />
diff --git a/templates/activities/AndroidTVActivity/root/res/layout/playback_controls.xml.ftl b/templates/activities/AndroidTVActivity/root/res/layout/playback_controls.xml.ftl
new file mode 100644
index 0000000..ebb50eb
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/res/layout/playback_controls.xml.ftl
@@ -0,0 +1,34 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <VideoView
+ android:id="@+id/videoView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+ <fragment
+ android:id="@+id/playback_controls_fragment"
+ android:name="${packageName}.PlaybackOverlayFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</FrameLayout>
diff --git a/base/templates/activities/AndroidTVActivity/root/res/menu/global.xml.ftl b/templates/activities/AndroidTVActivity/root/res/menu/global.xml.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/menu/global.xml.ftl
rename to templates/activities/AndroidTVActivity/root/res/menu/global.xml.ftl
diff --git a/base/templates/activities/AndroidTVActivity/root/res/menu/main.xml.ftl b/templates/activities/AndroidTVActivity/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/menu/main.xml.ftl
rename to templates/activities/AndroidTVActivity/root/res/menu/main.xml.ftl
diff --git a/base/templates/activities/AndroidTVActivity/root/res/values-w820dp/dimens.xml b/templates/activities/AndroidTVActivity/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/values-w820dp/dimens.xml
rename to templates/activities/AndroidTVActivity/root/res/values-w820dp/dimens.xml
diff --git a/templates/activities/AndroidTVActivity/root/res/values/colors.xml.ftl b/templates/activities/AndroidTVActivity/root/res/values/colors.xml.ftl
new file mode 100644
index 0000000..e9e5c7f
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/res/values/colors.xml.ftl
@@ -0,0 +1,18 @@
+<resources>
+ <color name="background_gradient_start">#000000</color>
+ <color name="background_gradient_end">#DDDDDD</color>
+ <color name="fastlane_background">#0096a6</color>
+ <color name="search_opaque">#ffaa3f</color>
+ <color name="selected_background">#ffaa3f</color>
+ <color name="detail_background">#0096a6</color>
+ <color name="soft_opaque">#30000000</color>
+ <color name="img_soft_opaque">#30FF0000</color>
+ <color name="img_full_opaque">#00000000</color>
+ <color name="black_opaque">#AA000000</color>
+ <color name="black">#59000000</color>
+ <color name="white">#FFFFFF</color>
+ <color name="orange_transparent">#AAFADCA7</color>
+ <color name="orange">#FADCA7</color>
+ <color name="yellow">#EEFF41</color>
+ <color name="default_background">#3d3d3d</color>
+</resources>
diff --git a/templates/activities/AndroidTVActivity/root/res/values/strings.xml.ftl b/templates/activities/AndroidTVActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..8458d38
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,44 @@
+<resources>
+ <#if isNewProject>
+ <string name="app_name">Leanback ${escapeXmlString(activityTitle)}</string>
+ <#else>
+ <string name="title_${activityToLayout(activityClass)}">Leanback ${escapeXmlString(activityTitle)}</string>
+ </#if>
+ <string name="browse_title"><![CDATA[Videos by Your Company]]></string>
+ <string name="related_movies">Related Videos</string>
+ <string name="vertical_grid_title"><![CDATA[Vertical Video Grid]]></string>
+ <string name="error">Error</string>
+ <string name="ok">OK</string>
+ <string name="pause">Pause</string>
+ <string name="play">Play</string>
+ <string name="stop">Stop</string>
+ <string name="init_text">00:00</string>
+ <string name="play_pause_description">Play Pause Button</string>
+ <string name="loading">Loading…</string>
+ <string name="no_video_found">No video was found</string>
+ <string name="about_app">About DemoCast Player</string>
+ <string name="version">Version: %1$s</string>
+ <string name="popular_header">Popular Videos</string>
+ <string name="preferences">PREFERENCES</string>
+ <string name="grid_view">Grid View</string>
+ <string name="error_fragment">Error Fragment</string>
+ <string name="personal_settings">Personal Settings</string>
+ <string name="watch_trailer_1">Watch trailer</string>
+ <string name="watch_trailer_2">FREE</string>
+ <string name="rent_1">Rent By Day</string>
+ <string name="rent_2">From $1.99</string>
+ <string name="buy_1">Buy and Own</string>
+ <string name="buy_2">AT $9.99</string>
+ <string name="movie">Movie</string>
+ <string name="should_start">shouldStart</string>
+ <string name="start_position">startPosition</string>
+ <string name="search_results">Search Results</string>
+
+ <!-- Error messages -->
+ <string name="video_error_media_load_timeout">Media loading timed out</string>
+ <string name="video_error_server_inaccessible">Media server was not reachable</string>
+ <string name="video_error_unknown_error">Failed to load video</string>
+ <string name="error_fragment_message">An error occurred</string>
+ <string name="dismiss_error">Dismiss</string>
+ <string name="oops">Oops</string>
+</resources>
diff --git a/base/templates/activities/AndroidTVActivity/root/res/values/themes.xml.ftl b/templates/activities/AndroidTVActivity/root/res/values/themes.xml.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/res/values/themes.xml.ftl
rename to templates/activities/AndroidTVActivity/root/res/values/themes.xml.ftl
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/BrowseErrorActivity.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/BrowseErrorActivity.java.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/src/app_package/BrowseErrorActivity.java.ftl
rename to templates/activities/AndroidTVActivity/root/src/app_package/BrowseErrorActivity.java.ftl
diff --git a/templates/activities/AndroidTVActivity/root/src/app_package/CardPresenter.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/CardPresenter.java.ftl
new file mode 100644
index 0000000..d96d740
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/src/app_package/CardPresenter.java.ftl
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package ${packageName};
+
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.Presenter;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.bumptech.glide.Glide;
+
+/*
+ * A CardPresenter is used to generate Views and bind Objects to them on demand.
+ * It contains an Image CardView
+ */
+public class CardPresenter extends Presenter {
+ private static final String TAG = "CardPresenter";
+
+ private static final int CARD_WIDTH = 313;
+ private static final int CARD_HEIGHT = 176;
+ private static int sSelectedBackgroundColor;
+ private static int sDefaultBackgroundColor;
+ private Drawable mDefaultCardImage;
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent) {
+ Log.d(TAG, "onCreateViewHolder");
+
+ sDefaultBackgroundColor = parent.getResources().getColor(R.color.default_background);
+ sSelectedBackgroundColor = parent.getResources().getColor(R.color.selected_background);
+ mDefaultCardImage = parent.getResources().getDrawable(R.drawable.movie);
+
+ ImageCardView cardView = new ImageCardView(parent.getContext()) {
+ @Override
+ public void setSelected(boolean selected) {
+ updateCardBackgroundColor(this, selected);
+ super.setSelected(selected);
+ }
+ };
+
+ cardView.setFocusable(true);
+ cardView.setFocusableInTouchMode(true);
+ updateCardBackgroundColor(cardView, false);
+ return new ViewHolder(cardView);
+ }
+
+ private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
+ int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
+ // Both background colors should be set because the view's background is temporarily visible
+ // during animations.
+ view.setBackgroundColor(color);
+ view.findViewById(R.id.info_field).setBackgroundColor(color);
+ }
+
+ @Override
+ public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+ Movie movie = (Movie) item;
+ ImageCardView cardView = (ImageCardView) viewHolder.view;
+
+ Log.d(TAG, "onBindViewHolder");
+ if (movie.getCardImageUrl() != null) {
+ cardView.setTitleText(movie.getTitle());
+ cardView.setContentText(movie.getStudio());
+ cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
+ Glide.with(viewHolder.view.getContext())
+ .load(movie.getCardImageUrl())
+ .centerCrop()
+ .error(mDefaultCardImage)
+ .into(cardView.getMainImageView());
+ }
+ }
+
+ @Override
+ public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+ Log.d(TAG, "onUnbindViewHolder");
+ ImageCardView cardView = (ImageCardView) viewHolder.view;
+ // Remove references to images so that the garbage collector can free up memory
+ cardView.setBadgeImage(null);
+ cardView.setMainImage(null);
+ }
+}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/DetailsActivity.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/DetailsActivity.java.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/src/app_package/DetailsActivity.java.ftl
rename to templates/activities/AndroidTVActivity/root/src/app_package/DetailsActivity.java.ftl
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/DetailsDescriptionPresenter.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/DetailsDescriptionPresenter.java.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/src/app_package/DetailsDescriptionPresenter.java.ftl
rename to templates/activities/AndroidTVActivity/root/src/app_package/DetailsDescriptionPresenter.java.ftl
diff --git a/templates/activities/AndroidTVActivity/root/src/app_package/ErrorFragment.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/ErrorFragment.java.ftl
new file mode 100644
index 0000000..66522fe
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/src/app_package/ErrorFragment.java.ftl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package ${packageName};
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+/*
+ * This class demonstrates how to extend ErrorFragment
+ */
+public class ErrorFragment extends android.support.v17.leanback.app.ErrorFragment {
+ private static final String TAG = "ErrorFragment";
+ private static final boolean TRANSLUCENT = true;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.d(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+ setTitle(getResources().getString(R.string.app_name));
+ }
+
+ void setErrorContent() {
+ setImageDrawable(getResources().getDrawable(R.drawable.lb_ic_sad_cloud));
+ setMessage(getResources().getString(R.string.error_fragment_message));
+ setDefaultBackground(TRANSLUCENT);
+
+ setButtonText(getResources().getString(R.string.dismiss_error));
+ setButtonClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ getFragmentManager().beginTransaction().remove(ErrorFragment.this).commit();
+ }
+ });
+ }
+}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/MainActivity.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/MainActivity.java.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/src/app_package/MainActivity.java.ftl
rename to templates/activities/AndroidTVActivity/root/src/app_package/MainActivity.java.ftl
diff --git a/templates/activities/AndroidTVActivity/root/src/app_package/MainFragment.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/MainFragment.java.ftl
new file mode 100644
index 0000000..621558d
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/src/app_package/MainFragment.java.ftl
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package ${packageName};
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.BrowseFragment;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
+public class ${mainFragment} extends BrowseFragment {
+ private static final String TAG = "${truncate(mainFragment,23)}";
+
+ private static final int BACKGROUND_UPDATE_DELAY = 300;
+ private static final int GRID_ITEM_WIDTH = 200;
+ private static final int GRID_ITEM_HEIGHT = 200;
+ private static final int NUM_ROWS = 6;
+ private static final int NUM_COLS = 15;
+
+ private final Handler mHandler = new Handler();
+ private ArrayObjectAdapter mRowsAdapter;
+ private Drawable mDefaultBackground;
+ private DisplayMetrics mMetrics;
+ private Timer mBackgroundTimer;
+ private URI mBackgroundURI;
+ private BackgroundManager mBackgroundManager;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onActivityCreated(savedInstanceState);
+
+ prepareBackgroundManager();
+
+ setupUIElements();
+
+ loadRows();
+
+ setupEventListeners();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (null != mBackgroundTimer) {
+ Log.d(TAG, "onDestroy: " + mBackgroundTimer.toString());
+ mBackgroundTimer.cancel();
+ }
+ }
+
+ private void loadRows() {
+ List<Movie> list = MovieList.setupMovies();
+
+ mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+ CardPresenter cardPresenter = new CardPresenter();
+
+ int i;
+ for (i = 0; i < NUM_ROWS; i++) {
+ if (i != 0) {
+ Collections.shuffle(list);
+ }
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
+ for (int j = 0; j < NUM_COLS; j++) {
+ listRowAdapter.add(list.get(j % 5));
+ }
+ <#if buildApi gte 22>
+ HeaderItem header = new HeaderItem(i, MovieList.MOVIE_CATEGORY[i]);
+ <#else>
+ HeaderItem header = new HeaderItem(i, MovieList.MOVIE_CATEGORY[i], null);
+ </#if>
+ mRowsAdapter.add(new ListRow(header, listRowAdapter));
+ }
+
+ <#if buildApi gte 22>
+ HeaderItem gridHeader = new HeaderItem(i, "PREFERENCES");
+ <#else>
+ HeaderItem gridHeader = new HeaderItem(i, "PREFERENCES", null);
+ </#if>
+
+ GridItemPresenter mGridPresenter = new GridItemPresenter();
+ ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
+ gridRowAdapter.add(getResources().getString(R.string.grid_view));
+ gridRowAdapter.add(getString(R.string.error_fragment));
+ gridRowAdapter.add(getResources().getString(R.string.personal_settings));
+ mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
+
+ setAdapter(mRowsAdapter);
+
+ }
+
+ private void prepareBackgroundManager() {
+
+ mBackgroundManager = BackgroundManager.getInstance(getActivity());
+ mBackgroundManager.attach(getActivity().getWindow());
+ mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
+ mMetrics = new DisplayMetrics();
+ getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+ }
+
+ private void setupUIElements() {
+ // setBadgeDrawable(getActivity().getResources().getDrawable(
+ // R.drawable.videos_by_google_banner));
+ setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent
+ // over title
+ setHeadersState(HEADERS_ENABLED);
+ setHeadersTransitionOnBackEnabled(true);
+
+ // set fastLane (or headers) background color
+ setBrandColor(getResources().getColor(R.color.fastlane_background));
+ // set search icon color
+ setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
+ }
+
+ private void setupEventListeners() {
+ setOnSearchClickedListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ Toast.makeText(getActivity(), "Implement your own in-app search", Toast.LENGTH_LONG)
+ .show();
+ }
+ });
+
+ setOnItemViewClickedListener(new ItemViewClickedListener());
+ setOnItemViewSelectedListener(new ItemViewSelectedListener());
+ }
+
+ private final class ItemViewClickedListener implements OnItemViewClickedListener {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+ if (item instanceof Movie) {
+ Movie movie = (Movie) item;
+ Log.d(TAG, "Item: " + item.toString());
+ Intent intent = new Intent(getActivity(), ${detailsActivity}.class);
+ intent.putExtra(${detailsActivity}.MOVIE, movie);
+
+ Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+ getActivity(),
+ ((ImageCardView) itemViewHolder.view).getMainImageView(),
+ ${detailsActivity}.SHARED_ELEMENT_NAME).toBundle();
+ getActivity().startActivity(intent, bundle);
+ } else if (item instanceof String) {
+ if (((String) item).indexOf(getString(R.string.error_fragment)) >= 0) {
+ Intent intent = new Intent(getActivity(), BrowseErrorActivity.class);
+ startActivity(intent);
+ } else {
+ Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ }
+ }
+
+ private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ if (item instanceof Movie) {
+ mBackgroundURI = ((Movie) item).getBackgroundImageURI();
+ startBackgroundTimer();
+ }
+
+ }
+ }
+
+ protected void updateBackground(String uri) {
+ int width = mMetrics.widthPixels;
+ int height = mMetrics.heightPixels;
+ Glide.with(getActivity())
+ .load(uri)
+ .centerCrop()
+ .error(mDefaultBackground)
+ .into(new SimpleTarget<GlideDrawable>(width, height) {
+ @Override
+ public void onResourceReady(GlideDrawable resource,
+ GlideAnimation<? super GlideDrawable>
+ glideAnimation) {
+ mBackgroundManager.setDrawable(resource);
+ }
+ });
+ mBackgroundTimer.cancel();
+ }
+
+ private void startBackgroundTimer() {
+ if (null != mBackgroundTimer) {
+ mBackgroundTimer.cancel();
+ }
+ mBackgroundTimer = new Timer();
+ mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
+ }
+
+ private class UpdateBackgroundTask extends TimerTask {
+
+ @Override
+ public void run() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mBackgroundURI != null) {
+ updateBackground(mBackgroundURI.toString());
+ }
+ }
+ });
+
+ }
+ }
+
+ private class GridItemPresenter extends Presenter {
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent) {
+ TextView view = new TextView(parent.getContext());
+ view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
+ view.setFocusable(true);
+ view.setFocusableInTouchMode(true);
+ view.setBackgroundColor(getResources().getColor(R.color.default_background));
+ view.setTextColor(Color.WHITE);
+ view.setGravity(Gravity.CENTER);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+ ((TextView) viewHolder.view).setText((String) item);
+ }
+
+ @Override
+ public void onUnbindViewHolder(ViewHolder viewHolder) {
+ }
+ }
+
+}
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/Movie.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/Movie.java.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/src/app_package/Movie.java.ftl
rename to templates/activities/AndroidTVActivity/root/src/app_package/Movie.java.ftl
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/MovieList.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/MovieList.java.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/src/app_package/MovieList.java.ftl
rename to templates/activities/AndroidTVActivity/root/src/app_package/MovieList.java.ftl
diff --git a/base/templates/activities/AndroidTVActivity/root/src/app_package/PicassoBackgroundManagerTarget.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/PicassoBackgroundManagerTarget.java.ftl
similarity index 100%
rename from base/templates/activities/AndroidTVActivity/root/src/app_package/PicassoBackgroundManagerTarget.java.ftl
rename to templates/activities/AndroidTVActivity/root/src/app_package/PicassoBackgroundManagerTarget.java.ftl
diff --git a/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayActivity.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayActivity.java.ftl
new file mode 100644
index 0000000..dc9220d
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayActivity.java.ftl
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package ${packageName};
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.widget.VideoView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
+/**
+ * PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment
+ */
+public class PlaybackOverlayActivity extends Activity implements
+ PlaybackOverlayFragment.OnPlayPauseClickedListener {
+ private static final String TAG = "PlaybackOverlayActivity";
+
+ private VideoView mVideoView;
+ private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE;
+ private MediaSession mSession;
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.playback_controls);
+ loadViews();
+ setupCallbacks();
+ mSession = new MediaSession (this, "LeanbackSampleApp");
+ mSession.setCallback(new MediaSessionCallback());
+ mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
+ MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+
+ mSession.setActive(true);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mVideoView.suspend();
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ PlaybackOverlayFragment playbackOverlayFragment = (PlaybackOverlayFragment) getFragmentManager().findFragmentById(R.id.playback_controls_fragment);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ playbackOverlayFragment.togglePlayback(false);
+ return true;
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ playbackOverlayFragment.togglePlayback(false);
+ return true;
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
+ playbackOverlayFragment.togglePlayback(false);
+ } else {
+ playbackOverlayFragment.togglePlayback(true);
+ }
+ return true;
+ default:
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+
+ /**
+ * Implementation of OnPlayPauseClickedListener
+ */
+ public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) {
+ mVideoView.setVideoPath(movie.getVideoUrl());
+
+ if (position == 0 || mPlaybackState == LeanbackPlaybackState.IDLE) {
+ setupCallbacks();
+ mPlaybackState = LeanbackPlaybackState.IDLE;
+ }
+
+ if (playPause && mPlaybackState != LeanbackPlaybackState.PLAYING) {
+ mPlaybackState = LeanbackPlaybackState.PLAYING;
+ if (position > 0) {
+ mVideoView.seekTo(position);
+ mVideoView.start();
+ }
+ } else {
+ mPlaybackState = LeanbackPlaybackState.PAUSED;
+ mVideoView.pause();
+ }
+ updatePlaybackState(position);
+ updateMetadata(movie);
+ }
+
+ private void updatePlaybackState(int position) {
+ PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
+ .setActions(getAvailableActions());
+ int state = PlaybackState.STATE_PLAYING;
+ if (mPlaybackState == LeanbackPlaybackState.PAUSED) {
+ state = PlaybackState.STATE_PAUSED;
+ }
+ stateBuilder.setState(state, position, 1.0f);
+ mSession.setPlaybackState(stateBuilder.build());
+ }
+
+ private long getAvailableActions() {
+ long actions = PlaybackState.ACTION_PLAY |
+ PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
+ PlaybackState.ACTION_PLAY_FROM_SEARCH;
+
+ if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
+ actions |= PlaybackState.ACTION_PAUSE;
+ }
+
+ return actions;
+ }
+
+ private void updateMetadata(final Movie movie) {
+ final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
+
+ String title = movie.getTitle().replace("_", " -");
+
+ metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
+ metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
+ movie.getDescription());
+ metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
+ movie.getCardImageUrl());
+
+ // And at minimum the title and artist for legacy support
+ metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
+ metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio());
+
+ Glide.with(this)
+ .load(Uri.parse(movie.getCardImageUrl()))
+ .asBitmap()
+ .into(new SimpleTarget<Bitmap>(500, 500) {
+ @Override
+ public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
+ metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap);
+ mSession.setMetadata(metadataBuilder.build());
+ }
+ });
+ }
+
+ private void loadViews() {
+ mVideoView = (VideoView) findViewById(R.id.videoView);
+ mVideoView.setFocusable(false);
+ mVideoView.setFocusableInTouchMode(false);
+ }
+
+ private void setupCallbacks() {
+
+ mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ String msg = "";
+ if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
+ msg = getString(R.string.video_error_media_load_timeout);
+ } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
+ msg = getString(R.string.video_error_server_inaccessible);
+ } else {
+ msg = getString(R.string.video_error_unknown_error);
+ }
+ mVideoView.stopPlayback();
+ mPlaybackState = LeanbackPlaybackState.IDLE;
+ return false;
+ }
+ });
+
+ mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
+ mVideoView.start();
+ }
+ }
+ });
+
+ mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ mPlaybackState = LeanbackPlaybackState.IDLE;
+ }
+ });
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mSession.setActive(true);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mVideoView.isPlaying()) {
+ if (!requestVisibleBehind(true)) {
+ // Try to play behind launcher, but if it fails, stop playback.
+ stopPlayback();
+ }
+ } else {
+ requestVisibleBehind(false);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mSession.release();
+ }
+
+
+ @Override
+ public void onVisibleBehindCanceled() {
+ super.onVisibleBehindCanceled();
+ }
+
+ private void stopPlayback() {
+ if (mVideoView != null) {
+ mVideoView.stopPlayback();
+ }
+ }
+
+ /*
+ * List of various states that we can be in
+ */
+ public enum LeanbackPlaybackState {
+ PLAYING, PAUSED, BUFFERING, IDLE
+ }
+
+ private class MediaSessionCallback extends MediaSession.Callback {
+ }
+}
diff --git a/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayFragment.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayFragment.java.ftl
new file mode 100644
index 0000000..18629ce
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/src/app_package/PlaybackOverlayFragment.java.ftl
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package ${packageName};
+
+<#if minApiLevel lt 23>import android.app.Activity;
+<#else>import android.content.Context;</#if>
+import android.media.MediaMetadataRetriever;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ShuffleAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.SkipNextAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.SkipPreviousAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/*
+ * Class for video playback with media control
+ */
+public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment {
+ private static final String TAG = "PlaybackControlsFragmnt";
+
+ private static final boolean SHOW_DETAIL = true;
+ private static final boolean HIDE_MORE_ACTIONS = false;
+ private static final int PRIMARY_CONTROLS = 5;
+ private static final boolean SHOW_IMAGE = PRIMARY_CONTROLS <= 5;
+ private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;
+ private static final int CARD_WIDTH = 200;
+ private static final int CARD_HEIGHT = 240;
+ private static final int DEFAULT_UPDATE_PERIOD = 1000;
+ private static final int UPDATE_PERIOD = 16;
+ private static final int SIMULATED_BUFFERED_TIME = 10000;
+
+ private ArrayObjectAdapter mRowsAdapter;
+ private ArrayObjectAdapter mPrimaryActionsAdapter;
+ private ArrayObjectAdapter mSecondaryActionsAdapter;
+ private PlayPauseAction mPlayPauseAction;
+ private RepeatAction mRepeatAction;
+ private ThumbsUpAction mThumbsUpAction;
+ private ThumbsDownAction mThumbsDownAction;
+ private ShuffleAction mShuffleAction;
+ private FastForwardAction mFastForwardAction;
+ private RewindAction mRewindAction;
+ private SkipNextAction mSkipNextAction;
+ private SkipPreviousAction mSkipPreviousAction;
+ private PlaybackControlsRow mPlaybackControlsRow;
+ private ArrayList<Movie> mItems = new ArrayList<Movie>();
+ private int mCurrentItem;
+ private Handler mHandler;
+ private Runnable mRunnable;
+ private Movie mSelectedMovie;
+
+ private OnPlayPauseClickedListener mCallback;
+
+ // Container Activity must implement this interface
+ public interface OnPlayPauseClickedListener {
+ void onFragmentPlayPause(Movie movie, int position, Boolean playPause);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mItems = new ArrayList<Movie>();
+ mSelectedMovie = (Movie) getActivity()
+ .getIntent().getSerializableExtra(${detailsActivity}.MOVIE);
+
+ List<Movie> movies = MovieList.list;
+
+ for (int j = 0; j < movies.size(); j++) {
+ mItems.add(movies.get(j));
+ if (mSelectedMovie.getTitle().contentEquals(movies.get(j).getTitle())) {
+ mCurrentItem = j;
+ }
+ }
+
+ mHandler = new Handler();
+
+ setBackgroundType(BACKGROUND_TYPE);
+ setFadingEnabled(false);
+
+ setupRows();
+
+ setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemSelected: " + item + " row " + row);
+ }
+ });
+ setOnItemViewClickedListener(new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemClicked: " + item + " row " + row);
+ }
+ });
+ }
+
+ <#if minApiLevel lt 23>@SuppressWarnings("deprecation")</#if>
+ @Override
+ public void onAttach(<#if minApiLevel lt 23>Activity<#else>Context</#if> context) {
+ super.onAttach(context);
+ if (context instanceof OnPlayPauseClickedListener) {
+ mCallback = (OnPlayPauseClickedListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement OnPlayPauseClickedListener");
+ }
+ }
+
+ private void setupRows() {
+
+ ClassPresenterSelector ps = new ClassPresenterSelector();
+
+ PlaybackControlsRowPresenter playbackControlsRowPresenter;
+ if (SHOW_DETAIL) {
+ playbackControlsRowPresenter = new PlaybackControlsRowPresenter(
+ new DescriptionPresenter());
+ } else {
+ playbackControlsRowPresenter = new PlaybackControlsRowPresenter();
+ }
+ playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
+ public void onActionClicked(Action action) {
+ if (action.getId() == mPlayPauseAction.getId()) {
+ togglePlayback(mPlayPauseAction.getIndex() == PlayPauseAction.PLAY);
+ } else if (action.getId() == mSkipNextAction.getId()) {
+ next();
+ } else if (action.getId() == mSkipPreviousAction.getId()) {
+ prev();
+ } else if (action.getId() == mFastForwardAction.getId()) {
+ Toast.makeText(getActivity(), "TODO: Fast Forward", Toast.LENGTH_SHORT).show();
+ } else if (action.getId() == mRewindAction.getId()) {
+ Toast.makeText(getActivity(), "TODO: Rewind", Toast.LENGTH_SHORT).show();
+ }
+ if (action instanceof PlaybackControlsRow.MultiAction) {
+ ((PlaybackControlsRow.MultiAction) action).nextIndex();
+ notifyChanged(action);
+ }
+ }
+ });
+ playbackControlsRowPresenter.setSecondaryActionsHidden(HIDE_MORE_ACTIONS);
+
+ ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
+ ps.addClassPresenter(ListRow.class, new ListRowPresenter());
+ mRowsAdapter = new ArrayObjectAdapter(ps);
+
+ addPlaybackControlsRow();
+ addOtherRows();
+
+ setAdapter(mRowsAdapter);
+ }
+
+ public void togglePlayback(boolean playPause) {
+ if (playPause) {
+ startProgressAutomation();
+ setFadingEnabled(true);
+ mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
+ mPlaybackControlsRow.getCurrentTime(), true);
+ mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlayPauseAction.PAUSE));
+ } else {
+ stopProgressAutomation();
+ setFadingEnabled(false);
+ mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
+ mPlaybackControlsRow.getCurrentTime(), false);
+ mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlayPauseAction.PLAY));
+ }
+ notifyChanged(mPlayPauseAction);
+ }
+
+ private int getDuration() {
+ Movie movie = mItems.get(mCurrentItem);
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ mmr.setDataSource(movie.getVideoUrl(), new HashMap<String, String>());
+ } else {
+ mmr.setDataSource(movie.getVideoUrl());
+ }
+ String time = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ long duration = Long.parseLong(time);
+ return (int) duration;
+ }
+
+ private void addPlaybackControlsRow() {
+ if (SHOW_DETAIL) {
+ mPlaybackControlsRow = new PlaybackControlsRow(mSelectedMovie);
+ } else {
+ mPlaybackControlsRow = new PlaybackControlsRow();
+ }
+ mRowsAdapter.add(mPlaybackControlsRow);
+
+ updatePlaybackRow(mCurrentItem);
+
+ ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
+ mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
+ mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
+ mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
+ mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
+
+ mPlayPauseAction = new PlayPauseAction(getActivity());
+ mRepeatAction = new RepeatAction(getActivity());
+ mThumbsUpAction = new ThumbsUpAction(getActivity());
+ mThumbsDownAction = new ThumbsDownAction(getActivity());
+ mShuffleAction = new ShuffleAction(getActivity());
+ mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getActivity());
+ mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getActivity());
+ mFastForwardAction = new PlaybackControlsRow.FastForwardAction(getActivity());
+ mRewindAction = new PlaybackControlsRow.RewindAction(getActivity());
+
+ if (PRIMARY_CONTROLS > 5) {
+ mPrimaryActionsAdapter.add(mThumbsUpAction);
+ } else {
+ mSecondaryActionsAdapter.add(mThumbsUpAction);
+ }
+ mPrimaryActionsAdapter.add(mSkipPreviousAction);
+ if (PRIMARY_CONTROLS > 3) {
+ mPrimaryActionsAdapter.add(new PlaybackControlsRow.RewindAction(getActivity()));
+ }
+ mPrimaryActionsAdapter.add(mPlayPauseAction);
+ if (PRIMARY_CONTROLS > 3) {
+ mPrimaryActionsAdapter.add(new PlaybackControlsRow.FastForwardAction(getActivity()));
+ }
+ mPrimaryActionsAdapter.add(mSkipNextAction);
+
+ mSecondaryActionsAdapter.add(mRepeatAction);
+ mSecondaryActionsAdapter.add(mShuffleAction);
+ if (PRIMARY_CONTROLS > 5) {
+ mPrimaryActionsAdapter.add(mThumbsDownAction);
+ } else {
+ mSecondaryActionsAdapter.add(mThumbsDownAction);
+ }
+ mSecondaryActionsAdapter.add(new PlaybackControlsRow.HighQualityAction(getActivity()));
+ mSecondaryActionsAdapter.add(new PlaybackControlsRow.ClosedCaptioningAction(getActivity()));
+ }
+
+ private void notifyChanged(Action action) {
+ ArrayObjectAdapter adapter = mPrimaryActionsAdapter;
+ if (adapter.indexOf(action) >= 0) {
+ adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
+ return;
+ }
+ adapter = mSecondaryActionsAdapter;
+ if (adapter.indexOf(action) >= 0) {
+ adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
+ return;
+ }
+ }
+
+ private void updatePlaybackRow(int index) {
+ if (mPlaybackControlsRow.getItem() != null) {
+ Movie item = (Movie) mPlaybackControlsRow.getItem();
+ item.setTitle(mItems.get(mCurrentItem).getTitle());
+ item.setStudio(mItems.get(mCurrentItem).getStudio());
+ }
+ if (SHOW_IMAGE) {
+ updateVideoImage(mItems.get(mCurrentItem).getCardImageURI().toString());
+ }
+ mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
+ mPlaybackControlsRow.setTotalTime(getDuration());
+ mPlaybackControlsRow.setCurrentTime(0);
+ mPlaybackControlsRow.setBufferedProgress(0);
+ }
+
+ private void addOtherRows() {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+ for (Movie movie : mItems) {
+ listRowAdapter.add(movie);
+ }
+ <#if buildApi gte 22>
+ HeaderItem header = new HeaderItem(0, getString(R.string.related_movies));
+ <#else>
+ HeaderItem header = new HeaderItem(0, getString(R.string.related_movies), null);
+ </#if>
+ mRowsAdapter.add(new ListRow(header, listRowAdapter));
+
+ }
+
+ private int getUpdatePeriod() {
+ if (getView() == null || mPlaybackControlsRow.getTotalTime() <= 0) {
+ return DEFAULT_UPDATE_PERIOD;
+ }
+ return Math.max(UPDATE_PERIOD, mPlaybackControlsRow.getTotalTime() / getView().getWidth());
+ }
+
+ private void startProgressAutomation() {
+ mRunnable = new Runnable() {
+ @Override
+ public void run() {
+ int updatePeriod = getUpdatePeriod();
+ int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod;
+ int totalTime = mPlaybackControlsRow.getTotalTime();
+ mPlaybackControlsRow.setCurrentTime(currentTime);
+ mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
+
+ if (totalTime > 0 && totalTime <= currentTime) {
+ next();
+ }
+ mHandler.postDelayed(this, updatePeriod);
+ }
+ };
+ mHandler.postDelayed(mRunnable, getUpdatePeriod());
+ }
+
+ private void next() {
+ if (++mCurrentItem >= mItems.size()) {
+ mCurrentItem = 0;
+ }
+
+ if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
+ mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
+ } else {
+ mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
+ }
+ updatePlaybackRow(mCurrentItem);
+ }
+
+ private void prev() {
+ if (--mCurrentItem < 0) {
+ mCurrentItem = mItems.size() - 1;
+ }
+ if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
+ mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
+ } else {
+ mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
+ }
+ updatePlaybackRow(mCurrentItem);
+ }
+
+ private void stopProgressAutomation() {
+ if (mHandler != null && mRunnable != null) {
+ mHandler.removeCallbacks(mRunnable);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ stopProgressAutomation();
+ super.onStop();
+ }
+
+ static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
+ @Override
+ protected void onBindDescription(ViewHolder viewHolder, Object item) {
+ viewHolder.getTitle().setText(((Movie) item).getTitle());
+ viewHolder.getSubtitle().setText(((Movie) item).getStudio());
+ }
+ }
+
+ protected void updateVideoImage(String uri) {
+ Glide.with(getActivity())
+ .load(uri)
+ .centerCrop()
+ .into(new SimpleTarget<GlideDrawable>(CARD_WIDTH, CARD_HEIGHT) {
+ @Override
+ public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
+ mPlaybackControlsRow.setImageDrawable(resource);
+ mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size());
+ }
+ });
+ }
+}
diff --git a/templates/activities/AndroidTVActivity/root/src/app_package/Utils.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/Utils.java.ftl
new file mode 100644
index 0000000..d320213
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/src/app_package/Utils.java.ftl
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package ${packageName};
+
+import android.content.Context;
+import android.graphics.Point;
+import android.view.Display;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+/**
+ * A collection of utility methods, all static.
+ */
+public class Utils {
+
+ /*
+ * Making sure public utility methods remain static
+ */
+ private Utils() {
+ }
+
+ /**
+ * Returns the screen/display size
+ */
+ public static Point getDisplaySize(Context context) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+ return size;
+ }
+
+ /**
+ * Shows a (long) toast
+ */
+ public static void showToast(Context context, String msg) {
+ Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
+ }
+
+ /**
+ * Shows a (long) toast.
+ */
+ public static void showToast(Context context, int resourceId) {
+ Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
+ }
+
+ public static int convertDpToPixel(Context ctx, int dp) {
+ float density = ctx.getResources().getDisplayMetrics().density;
+ return Math.round((float) dp * density);
+ }
+
+ /**
+ * Formats time in milliseconds to hh:mm:ss string format.
+ */
+ public static String formatMillis(int millis) {
+ String result = "";
+ int hr = millis / 3600000;
+ millis %= 3600000;
+ int min = millis / 60000;
+ millis %= 60000;
+ int sec = millis / 1000;
+ if (hr > 0) {
+ result += hr + ":";
+ }
+ if (min >= 0) {
+ if (min > 9) {
+ result += min + ":";
+ } else {
+ result += "0" + min + ":";
+ }
+ }
+ if (sec > 9) {
+ result += sec;
+ } else {
+ result += "0" + sec;
+ }
+ return result;
+ }
+}
diff --git a/templates/activities/AndroidTVActivity/root/src/app_package/VideoDetailsFragment.java.ftl b/templates/activities/AndroidTVActivity/root/src/app_package/VideoDetailsFragment.java.ftl
new file mode 100644
index 0000000..eb29951
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/root/src/app_package/VideoDetailsFragment.java.ftl
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package ${packageName};
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.DetailsFragment;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DetailsOverviewRow;
+import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
+import java.util.Collections;
+import java.util.List;
+
+/*
+ * LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback details screens.
+ * It shows a detailed view of video and its meta plus related videos.
+ */
+public class ${detailsFragment} extends DetailsFragment {
+ private static final String TAG = "${truncate(detailsFragment,23)}";
+
+ private static final int ACTION_WATCH_TRAILER = 1;
+ private static final int ACTION_RENT = 2;
+ private static final int ACTION_BUY = 3;
+
+ private static final int DETAIL_THUMB_WIDTH = 274;
+ private static final int DETAIL_THUMB_HEIGHT = 274;
+
+ private static final int NUM_COLS = 10;
+
+ private Movie mSelectedMovie;
+
+ private ArrayObjectAdapter mAdapter;
+ private ClassPresenterSelector mPresenterSelector;
+
+ private BackgroundManager mBackgroundManager;
+ private Drawable mDefaultBackground;
+ private DisplayMetrics mMetrics;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.d(TAG, "onCreate DetailsFragment");
+ super.onCreate(savedInstanceState);
+
+ prepareBackgroundManager();
+
+ mSelectedMovie = (Movie) getActivity().getIntent()
+ .getSerializableExtra(${detailsActivity}.MOVIE);
+ if (mSelectedMovie != null) {
+ setupAdapter();
+ setupDetailsOverviewRow();
+ setupDetailsOverviewRowPresenter();
+ setupMovieListRow();
+ setupMovieListRowPresenter();
+ updateBackground(mSelectedMovie.getBackgroundImageUrl());
+ setOnItemViewClickedListener(new ItemViewClickedListener());
+ } else {
+ Intent intent = new Intent(getActivity(), MainActivity.class);
+ startActivity(intent);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ private void prepareBackgroundManager() {
+ mBackgroundManager = BackgroundManager.getInstance(getActivity());
+ mBackgroundManager.attach(getActivity().getWindow());
+ mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
+ mMetrics = new DisplayMetrics();
+ getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+ }
+
+ protected void updateBackground(String uri) {
+ Glide.with(getActivity())
+ .load(uri)
+ .centerCrop()
+ .error(mDefaultBackground)
+ .into(new SimpleTarget<GlideDrawable>(mMetrics.widthPixels, mMetrics.heightPixels) {
+ @Override
+ public void onResourceReady(GlideDrawable resource,
+ GlideAnimation<? super GlideDrawable> glideAnimation) {
+ mBackgroundManager.setDrawable(resource);
+ }
+ });
+ }
+
+ private void setupAdapter() {
+ mPresenterSelector = new ClassPresenterSelector();
+ mAdapter = new ArrayObjectAdapter(mPresenterSelector);
+ setAdapter(mAdapter);
+ }
+
+ private void setupDetailsOverviewRow() {
+ Log.d(TAG, "doInBackground: " + mSelectedMovie.toString());
+ final DetailsOverviewRow row = new DetailsOverviewRow(mSelectedMovie);
+ row.setImageDrawable(getResources().getDrawable(R.drawable.default_background));
+ int width = Utils.convertDpToPixel(getActivity()
+ .getApplicationContext(), DETAIL_THUMB_WIDTH);
+ int height = Utils.convertDpToPixel(getActivity()
+ .getApplicationContext(), DETAIL_THUMB_HEIGHT);
+ Glide.with(getActivity())
+ .load(mSelectedMovie.getCardImageUrl())
+ .centerCrop()
+ .error(R.drawable.default_background)
+ .into(new SimpleTarget<GlideDrawable>(width, height) {
+ @Override
+ public void onResourceReady(GlideDrawable resource,
+ GlideAnimation<? super GlideDrawable>
+ glideAnimation) {
+ Log.d(TAG, "details overview card image url ready: " + resource);
+ row.setImageDrawable(resource);
+ mAdapter.notifyArrayItemRangeChanged(0, mAdapter.size());
+ }
+ });
+
+ row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(
+ R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));
+ row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),
+ getResources().getString(R.string.rent_2)));
+ row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),
+ getResources().getString(R.string.buy_2)));
+
+ mAdapter.add(row);
+ }
+
+ private void setupDetailsOverviewRowPresenter() {
+ // Set detail background and style.
+ DetailsOverviewRowPresenter detailsPresenter =
+ new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
+ detailsPresenter.setBackgroundColor(getResources().getColor(R.color.selected_background));
+ detailsPresenter.setStyleLarge(true);
+
+ // Hook up transition element.
+ detailsPresenter.setSharedElementEnterTransition(getActivity(),
+ ${detailsActivity}.SHARED_ELEMENT_NAME);
+
+ detailsPresenter.setOnActionClickedListener(new OnActionClickedListener() {
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == ACTION_WATCH_TRAILER) {
+ Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);
+ intent.putExtra(${detailsActivity}.MOVIE, mSelectedMovie);
+ startActivity(intent);
+ } else {
+ Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter);
+ }
+
+ private void setupMovieListRow() {
+ String subcategories[] = {getString(R.string.related_movies)};
+ List<Movie> list = MovieList.list;
+
+ Collections.shuffle(list);
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+ for (int j = 0; j < NUM_COLS; j++) {
+ listRowAdapter.add(list.get(j % 5));
+ }
+
+ <#if buildApi gte 22>
+ HeaderItem header = new HeaderItem(0, subcategories[0]);
+ <#else>
+ HeaderItem header = new HeaderItem(0, subcategories[0], null);
+ </#if>
+ mAdapter.add(new ListRow(header, listRowAdapter));
+ }
+
+ private void setupMovieListRowPresenter() {
+ mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
+ }
+
+ private final class ItemViewClickedListener implements OnItemViewClickedListener {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+ if (item instanceof Movie) {
+ Movie movie = (Movie) item;
+ Log.d(TAG, "Item: " + item.toString());
+ Intent intent = new Intent(getActivity(), ${detailsActivity}.class);
+ intent.putExtra(getResources().getString(R.string.movie), mSelectedMovie);
+ intent.putExtra(getResources().getString(R.string.should_start), true);
+ startActivity(intent);
+
+
+ Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+ getActivity(),
+ ((ImageCardView) itemViewHolder.view).getMainImageView(),
+ ${detailsActivity}.SHARED_ELEMENT_NAME).toBundle();
+ getActivity().startActivity(intent, bundle);
+ }
+ }
+ }
+}
diff --git a/templates/activities/AndroidTVActivity/template-leanback-TV.png b/templates/activities/AndroidTVActivity/template-leanback-TV.png
new file mode 100644
index 0000000..719911f
Binary files /dev/null and b/templates/activities/AndroidTVActivity/template-leanback-TV.png differ
diff --git a/templates/activities/AndroidTVActivity/template.xml b/templates/activities/AndroidTVActivity/template.xml
new file mode 100644
index 0000000..493527e
--- /dev/null
+++ b/templates/activities/AndroidTVActivity/template.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="6"
+ name="Android TV Activity"
+ minApi="7"
+ minBuildApi="19"
+ description="Creates a new Android TV activity using Leanback Support library.">
+
+ <category value="Activity" />
+ <formfactor value="TV" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Main Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="mainFragment"
+ name="Main Fragment"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MainFragment"
+ suggest="MainFragment"
+ help="The name of the main fragment." />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="Title"
+ suggest="${activityClass} Title"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="detailsActivity"
+ name="Details Activity"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="DetailsActivity"
+ suggest="DetailsActivity"
+ help="The name of the details activity." />
+
+ <parameter
+ id="detailsLayoutName"
+ name="Details Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(detailsActivity)}"
+ default="details"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="detailsFragment"
+ name="Details Fragment"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="VideoDetailsFragment"
+ suggest="VideoDetailsFragment"
+ help="The name of the details fragment." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template-leanback-TV.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/BasicActivity/globals.xml.ftl b/templates/activities/BasicActivity/globals.xml.ftl
new file mode 100644
index 0000000..0d35415
--- /dev/null
+++ b/templates/activities/BasicActivity/globals.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "../common/common_globals.xml.ftl" />
+ <global id="simpleLayoutName" value="<#if appCompatActivity>${contentLayoutName}<#else>${layoutName}</#if>" />
+ <global id="appBarLayoutName" value="${layoutName}" />
+ <global id="fragmentClass" value="${activityClass}Fragment" />
+</globals>
diff --git a/templates/activities/BasicActivity/recipe.xml.ftl b/templates/activities/BasicActivity/recipe.xml.ftl
new file mode 100644
index 0000000..863d6d0
--- /dev/null
+++ b/templates/activities/BasicActivity/recipe.xml.ftl
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<recipe>
+ <#include "../common/recipe_manifest.xml.ftl" />
+
+<#if useFragment>
+ <#include "recipe_fragment.xml.ftl" />
+<#else>
+ <#include "../common/recipe_simple.xml.ftl" />
+</#if>
+
+<#if hasAppBar>
+ <#include "../common/recipe_app_bar.xml.ftl" />
+</#if>
+
+ <instantiate from="root/src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${simpleLayoutName}.xml" />
+</recipe>
diff --git a/templates/activities/BasicActivity/recipe_fragment.xml.ftl b/templates/activities/BasicActivity/recipe_fragment.xml.ftl
new file mode 100644
index 0000000..fa36454
--- /dev/null
+++ b/templates/activities/BasicActivity/recipe_fragment.xml.ftl
@@ -0,0 +1,13 @@
+<recipe>
+ <#include "../common/recipe_simple_dimens.xml" />
+ <#include "../common/recipe_simple_menu.xml.ftl" />
+
+ <instantiate from="root/res/layout/activity_fragment_container.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${simpleLayoutName}.xml" />
+
+ <instantiate from="root/res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <instantiate from="root/src/app_package/SimpleActivityFragment.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${fragmentClass}.java" />
+</recipe>
diff --git a/templates/activities/BasicActivity/root/res/layout/activity_fragment_container.xml.ftl b/templates/activities/BasicActivity/root/res/layout/activity_fragment_container.xml.ftl
new file mode 100644
index 0000000..0545649
--- /dev/null
+++ b/templates/activities/BasicActivity/root/res/layout/activity_fragment_container.xml.ftl
@@ -0,0 +1,13 @@
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+<#if hasAppBar>
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+</#if>
+ android:id="@+id/fragment"
+ android:name="${packageName}.${fragmentClass}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+<#if hasAppBar>
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+</#if>
+ tools:layout="@layout/${fragmentLayoutName}" />
diff --git a/templates/activities/BasicActivity/root/res/layout/fragment_simple.xml.ftl b/templates/activities/BasicActivity/root/res/layout/fragment_simple.xml.ftl
new file mode 100644
index 0000000..c695cb8
--- /dev/null
+++ b/templates/activities/BasicActivity/root/res/layout/fragment_simple.xml.ftl
@@ -0,0 +1,21 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+<#if hasAppBar && appBarLayoutName??>
+ tools:showIn="@layout/${appBarLayoutName}"
+</#if>
+ tools:context="${relativePackage}.${fragmentClass}">
+
+<#if isNewProject>
+ <TextView
+ android:text="Hello World!"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</#if>
+
+</RelativeLayout>
diff --git a/templates/activities/BasicActivity/root/src/app_package/SimpleActivity.java.ftl b/templates/activities/BasicActivity/root/src/app_package/SimpleActivity.java.ftl
new file mode 100644
index 0000000..0f880f1
--- /dev/null
+++ b/templates/activities/BasicActivity/root/src/app_package/SimpleActivity.java.ftl
@@ -0,0 +1,68 @@
+package ${packageName};
+
+import android.os.Bundle;
+<#if hasAppBar>
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+<#else>
+import ${superClassFqcn};
+</#if>
+<#if isNewProject>
+import android.view.Menu;
+import android.view.MenuItem;
+</#if>
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+public class ${activityClass} extends ${superClass} {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+<#if hasAppBar>
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ });
+</#if>
+<#if parentActivityClass != "">
+ get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
+</#if>
+ }
+
+<#if isNewProject>
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+</#if>
+}
diff --git a/base/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivityFragment.ftl b/templates/activities/BasicActivity/root/src/app_package/SimpleActivityFragment.ftl
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivityFragment.ftl
rename to templates/activities/BasicActivity/root/src/app_package/SimpleActivityFragment.ftl
diff --git a/templates/activities/BasicActivity/template.xml b/templates/activities/BasicActivity/template.xml
new file mode 100644
index 0000000..c89f833
--- /dev/null
+++ b/templates/activities/BasicActivity/template.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="6"
+ name="Basic Activity"
+ minApi="7"
+ minBuildApi="14"
+ requireAppTheme="true"
+ description="Creates a new basic activity with an app bar.">
+
+ <category value="Activity"/>
+ <formfactor value="Mobile"/>
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create"/>
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ help="The name of the layout to create for the activity"/>
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title."/>
+
+ <parameter
+ id="menuName"
+ name="Menu Resource Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="menu_${classToResource(activityClass)}"
+ visibility="isNewProject!false"
+ default="menu_main"
+ help="The name of the resource file to create for the menu items"/>
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher"/>
+
+ <parameter
+ id="useFragment"
+ name="Use a Fragment"
+ type="boolean"
+ default="false"
+ help="If true, the content will be a fragment"/>
+
+ <parameter
+ id="contentLayoutName"
+ name="Content Layout Name"
+ type="string"
+ constraints="layout|unique"
+ suggest="${activityToLayout(activityClass, 'content')}"
+ default="content_main"
+ visibility="false"
+ help="The name of the App Bar layout to create for the activity"/>
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button"/>
+
+ <parameter
+ id="fragmentLayoutName"
+ name="Fragment Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass, 'fragment')}"
+ default="fragment_main"
+ visibility="false"
+ help="The name of the layout to create for the activity's content fragment"/>
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp"/>
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_basic_activity.png</thumb>
+ <!-- attributes act as selectors based on chosen parameters -->
+ <thumb useFragment="true">template_basic_activity_fragment.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl"/>
+ <execute file="recipe.xml.ftl"/>
+
+</template>
diff --git a/templates/activities/BasicActivity/template_basic_activity.png b/templates/activities/BasicActivity/template_basic_activity.png
new file mode 100644
index 0000000..0319be6
Binary files /dev/null and b/templates/activities/BasicActivity/template_basic_activity.png differ
diff --git a/templates/activities/BasicActivity/template_basic_activity_fragment.png b/templates/activities/BasicActivity/template_basic_activity_fragment.png
new file mode 100644
index 0000000..564ea5c
Binary files /dev/null and b/templates/activities/BasicActivity/template_basic_activity_fragment.png differ
diff --git a/templates/activities/BlankWearActivity/globals.xml.ftl b/templates/activities/BlankWearActivity/globals.xml.ftl
new file mode 100644
index 0000000..41a4c6b
--- /dev/null
+++ b/templates/activities/BlankWearActivity/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "../common/wear_common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/BlankWearActivity/recipe.xml.ftl b/templates/activities/BlankWearActivity/recipe.xml.ftl
new file mode 100644
index 0000000..5804f5f
--- /dev/null
+++ b/templates/activities/BlankWearActivity/recipe.xml.ftl
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/res/layout/blank_activity.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/res/layout/round.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${roundLayout}.xml" />
+ <instantiate from="root/res/layout/rect.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${rectLayout}.xml" />
+
+ <instantiate from="root/src/app_package/BlankActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/BlankWearActivity/root/AndroidManifest.xml.ftl b/templates/activities/BlankWearActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankWearActivity/root/AndroidManifest.xml.ftl
rename to templates/activities/BlankWearActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/activities/BlankWearActivity/root/res/layout/blank_activity.xml.ftl b/templates/activities/BlankWearActivity/root/res/layout/blank_activity.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankWearActivity/root/res/layout/blank_activity.xml.ftl
rename to templates/activities/BlankWearActivity/root/res/layout/blank_activity.xml.ftl
diff --git a/base/templates/activities/BlankWearActivity/root/res/layout/rect.xml.ftl b/templates/activities/BlankWearActivity/root/res/layout/rect.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankWearActivity/root/res/layout/rect.xml.ftl
rename to templates/activities/BlankWearActivity/root/res/layout/rect.xml.ftl
diff --git a/base/templates/activities/BlankWearActivity/root/res/layout/round.xml.ftl b/templates/activities/BlankWearActivity/root/res/layout/round.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankWearActivity/root/res/layout/round.xml.ftl
rename to templates/activities/BlankWearActivity/root/res/layout/round.xml.ftl
diff --git a/base/templates/activities/BlankWearActivity/root/res/values/strings.xml.ftl b/templates/activities/BlankWearActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankWearActivity/root/res/values/strings.xml.ftl
rename to templates/activities/BlankWearActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/activities/BlankWearActivity/root/src/app_package/BlankActivity.java.ftl b/templates/activities/BlankWearActivity/root/src/app_package/BlankActivity.java.ftl
similarity index 100%
rename from base/templates/activities/BlankWearActivity/root/src/app_package/BlankActivity.java.ftl
rename to templates/activities/BlankWearActivity/root/src/app_package/BlankActivity.java.ftl
diff --git a/templates/activities/BlankWearActivity/template.xml b/templates/activities/BlankWearActivity/template.xml
new file mode 100644
index 0000000..3f0436d
--- /dev/null
+++ b/templates/activities/BlankWearActivity/template.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Blank Wear Activity"
+ minApi="20"
+ minBuildApi="20"
+ description="Creates a blank activity for Android Wear">
+
+ <category value="Activity" />
+ <formfactor value="Wear" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="roundLayout"
+ name="Round Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="round_${activityToLayout(activityClass)}"
+ default="round"
+ help="The name of the round layout to create for the activity" />
+
+
+ <parameter
+ id="rectLayout"
+ name="Rectangular Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="rect_${activityToLayout(activityClass)}"
+ default="rect"
+ help="The name of the rectangular layout to create for the activity" />
+
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="true"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it the default launchable activity" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>templates-WatchViewStub-Wear.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/BlankWearActivity/templates-WatchViewStub-Wear.png b/templates/activities/BlankWearActivity/templates-WatchViewStub-Wear.png
new file mode 100644
index 0000000..615cc90
Binary files /dev/null and b/templates/activities/BlankWearActivity/templates-WatchViewStub-Wear.png differ
diff --git a/templates/activities/EmptyActivity/globals.xml.ftl b/templates/activities/EmptyActivity/globals.xml.ftl
new file mode 100644
index 0000000..e8eb585
--- /dev/null
+++ b/templates/activities/EmptyActivity/globals.xml.ftl
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="hasNoActionBar" type="boolean" value="false" />
+ <global id="parentActivityClass" value="" />
+ <global id="simpleLayoutName" value="${layoutName}" />
+ <global id="excludeMenu" type="boolean" value="true" />
+ <global id="generateActivityTitle" type="boolean" value="false" />
+ <#include "../common/common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/EmptyActivity/recipe.xml.ftl b/templates/activities/EmptyActivity/recipe.xml.ftl
new file mode 100644
index 0000000..5471afd
--- /dev/null
+++ b/templates/activities/EmptyActivity/recipe.xml.ftl
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<recipe>
+ <#include "../common/recipe_manifest.xml.ftl" />
+
+<#if generateLayout>
+ <#include "../common/recipe_simple.xml.ftl" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</#if>
+
+ <instantiate from="root/src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+</recipe>
diff --git a/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl b/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
new file mode 100644
index 0000000..2bfad00
--- /dev/null
+++ b/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
@@ -0,0 +1,15 @@
+package ${packageName};
+
+import ${superClassFqcn};
+import android.os.Bundle;
+
+public class ${activityClass} extends ${superClass} {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+<#if generateLayout>
+ setContentView(R.layout.${layoutName});
+</#if>
+ }
+}
diff --git a/templates/activities/EmptyActivity/template.xml b/templates/activities/EmptyActivity/template.xml
new file mode 100644
index 0000000..35bb76f
--- /dev/null
+++ b/templates/activities/EmptyActivity/template.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="5"
+ name="Empty Activity"
+ minApi="7"
+ minBuildApi="14"
+ description="Creates a new empty activity">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="generateLayout"
+ name="Generate Layout File"
+ type="boolean"
+ default="true"
+ help="If true, a layout file will be generated" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ visibility="generateLayout"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/EmptyActivity/template_blank_activity.png b/templates/activities/EmptyActivity/template_blank_activity.png
new file mode 100644
index 0000000..c682fcf
Binary files /dev/null and b/templates/activities/EmptyActivity/template_blank_activity.png differ
diff --git a/templates/activities/FullscreenActivity/globals.xml.ftl b/templates/activities/FullscreenActivity/globals.xml.ftl
new file mode 100644
index 0000000..3529cf8
--- /dev/null
+++ b/templates/activities/FullscreenActivity/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "../common/common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/FullscreenActivity/recipe.xml.ftl b/templates/activities/FullscreenActivity/recipe.xml.ftl
new file mode 100644
index 0000000..0c1cc55
--- /dev/null
+++ b/templates/activities/FullscreenActivity/recipe.xml.ftl
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values/attrs.xml"
+ to="${escapeXmlAttribute(resOut)}/values/attrs.xml" />
+ <merge from="root/res/values/colors.xml"
+ to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
+ <merge from="root/res/values/styles.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+ <instantiate from="root/res/layout/activity_fullscreen.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/src/app_package/FullscreenActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl b/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..8d5d2e4
--- /dev/null
+++ b/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,26 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${simpleName}"
+ </#if>
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:theme="@style/FullscreenTheme"
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/base/templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl b/templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
rename to templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
diff --git a/base/templates/activities/FullscreenActivity/root/res/values/attrs.xml b/templates/activities/FullscreenActivity/root/res/values/attrs.xml
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/res/values/attrs.xml
rename to templates/activities/FullscreenActivity/root/res/values/attrs.xml
diff --git a/base/templates/activities/FullscreenActivity/root/res/values/colors.xml b/templates/activities/FullscreenActivity/root/res/values/colors.xml
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/res/values/colors.xml
rename to templates/activities/FullscreenActivity/root/res/values/colors.xml
diff --git a/base/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl b/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
rename to templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
diff --git a/templates/activities/FullscreenActivity/root/res/values/styles.xml.ftl b/templates/activities/FullscreenActivity/root/res/values/styles.xml.ftl
new file mode 100644
index 0000000..754d09a
--- /dev/null
+++ b/templates/activities/FullscreenActivity/root/res/values/styles.xml.ftl
@@ -0,0 +1,15 @@
+<resources>
+
+ <style name="FullscreenTheme" parent="${themeName}">
+ <item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
+ <item name="android:windowActionBarOverlay">true</item>
+ <item name="android:windowBackground">@null</item>
+ <item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
+ <item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
+ </style>
+
+ <style name="FullscreenActionBarStyle" parent="<#if appCompat>Widget.AppCompat.ActionBar<#else>android:Widget.Holo.ActionBar</#if>">
+ <item name="android:background">@color/black_overlay</item>
+ </style>
+
+</resources>
diff --git a/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl b/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
new file mode 100644
index 0000000..67149c2
--- /dev/null
+++ b/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
@@ -0,0 +1,194 @@
+package ${packageName};
+
+import android.annotation.SuppressLint;
+import ${actionBarClassFqcn};
+import ${superClassFqcn};
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.View;
+<#if parentActivityClass != "">
+import android.view.MenuItem;
+import android.support.v4.app.NavUtils;
+</#if>
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+/**
+ * An example full-screen activity that shows and hides the system UI (i.e.
+ * status bar and navigation/system bar) with user interaction.
+ */
+public class ${activityClass} extends ${superClass} {
+ /**
+ * Whether or not the system UI should be auto-hidden after
+ * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
+ */
+ private static final boolean AUTO_HIDE = true;
+
+ /**
+ * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
+ * user interaction before hiding the system UI.
+ */
+ private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
+
+ /**
+ * Some older devices needs a small delay between UI widget updates
+ * and a change of the status and navigation bar.
+ */
+ private static final int UI_ANIMATION_DELAY = 300;
+
+ private View mContentView;
+ private View mControlsView;
+ private boolean mVisible;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.${layoutName});
+ <#if parentActivityClass != "">
+ ActionBar actionBar = get${Support}ActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ </#if>
+
+ mVisible = true;
+ mControlsView = findViewById(R.id.fullscreen_content_controls);
+ mContentView = findViewById(R.id.fullscreen_content);
+
+
+ // Set up the user interaction to manually show or hide the system UI.
+ mContentView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ toggle();
+ }
+ });
+
+ // Upon interacting with UI controls, delay any scheduled hide()
+ // operations to prevent the jarring behavior of controls going away
+ // while interacting with the UI.
+ findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+
+ // Trigger the initial hide() shortly after the activity has been
+ // created, to briefly hint to the user that UI controls
+ // are available.
+ delayedHide(100);
+ }
+
+ <#if parentActivityClass != "">
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button.
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ </#if>
+ /**
+ * Touch listener to use for in-layout UI controls to delay hiding the
+ * system UI. This is to prevent the jarring behavior of controls going away
+ * while interacting with activity UI.
+ */
+ private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (AUTO_HIDE) {
+ delayedHide(AUTO_HIDE_DELAY_MILLIS);
+ }
+ return false;
+ }
+ };
+
+ private void toggle() {
+ if (mVisible) {
+ hide();
+ } else {
+ show();
+ }
+ }
+
+ private void hide() {
+ // Hide UI first
+ ActionBar actionBar = get${Support}ActionBar();
+ if (actionBar != null) {
+ actionBar.hide();
+ }
+ mControlsView.setVisibility(View.GONE);
+ mVisible = false;
+
+ // Schedule a runnable to remove the status and navigation bar after a delay
+ mHideHandler.removeCallbacks(mShowPart2Runnable);
+ mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
+ }
+
+ private final Runnable mHidePart2Runnable = new Runnable() {
+ @SuppressLint("InlinedApi")
+ @Override
+ public void run() {
+ // Delayed removal of status and navigation bar
+
+ // Note that some of these constants are new as of API 16 (Jelly Bean)
+ // and API 19 (KitKat). It is safe to use them, as they are inlined
+ // at compile-time and do nothing on earlier devices.
+ mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+ }
+ };
+
+ @SuppressLint("InlinedApi")
+ private void show() {
+ // Show the system bar
+ mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ mVisible = true;
+
+ // Schedule a runnable to display UI elements after a delay
+ mHideHandler.removeCallbacks(mHidePart2Runnable);
+ mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
+ }
+
+ private final Runnable mShowPart2Runnable = new Runnable() {
+ @Override
+ public void run() {
+ // Delayed display of UI elements
+ ActionBar actionBar = get${Support}ActionBar();
+ if (actionBar != null) {
+ actionBar.show();
+ }
+ mControlsView.setVisibility(View.VISIBLE);
+ }
+ };
+
+ private final Handler mHideHandler = new Handler();
+ private final Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hide();
+ }
+ };
+
+ /**
+ * Schedules a call to hide() in [delay] milliseconds, canceling any
+ * previously scheduled calls.
+ */
+ private void delayedHide(int delayMillis) {
+ mHideHandler.removeCallbacks(mHideRunnable);
+ mHideHandler.postDelayed(mHideRunnable, delayMillis);
+ }
+}
diff --git a/templates/activities/FullscreenActivity/template.xml b/templates/activities/FullscreenActivity/template.xml
new file mode 100644
index 0000000..d55e33f
--- /dev/null
+++ b/templates/activities/FullscreenActivity/template.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="6"
+ name="Fullscreen Activity"
+ description="Creates a new activity that toggles the visibility of the system UI (status and navigation bars) and action bar upon user interaction."
+ requireAppTheme="true"
+ minApi="11"
+ minBuildApi="16">
+ <dependency name="android-support-v4" revision="8" />
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="FullscreenActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_fullscreen"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="FullscreenActivity"
+ suggest="${activityClass}"
+ help="The name of the activity." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <thumbs>
+ <thumb>template_fullscreen_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/FullscreenActivity/template_fullscreen_activity.png b/templates/activities/FullscreenActivity/template_fullscreen_activity.png
new file mode 100644
index 0000000..0c9a636
Binary files /dev/null and b/templates/activities/FullscreenActivity/template_fullscreen_activity.png differ
diff --git a/base/templates/activities/BlankActivity/globals.xml.ftl b/templates/activities/GoogleAdMobAdsActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivity/globals.xml.ftl
rename to templates/activities/GoogleAdMobAdsActivity/globals.xml.ftl
diff --git a/templates/activities/GoogleAdMobAdsActivity/recipe.xml.ftl b/templates/activities/GoogleAdMobAdsActivity/recipe.xml.ftl
new file mode 100644
index 0000000..58b85ab
--- /dev/null
+++ b/templates/activities/GoogleAdMobAdsActivity/recipe.xml.ftl
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.google.android.gms:play-services-ads:7.0.0" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="root/res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <instantiate from="root/res/layout/activity_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/templates/activities/GoogleAdMobAdsActivity/root/AndroidManifest.xml.ftl b/templates/activities/GoogleAdMobAdsActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..3c84b10
--- /dev/null
+++ b/templates/activities/GoogleAdMobAdsActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,36 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <!-- Include required permissions for Google Mobile Ads to run. -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
+ <application>
+ <!--This meta-data tag is required to use Google Play Services. -->
+ <meta-data android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ <!--Include the AdActivity configChanges and theme. -->
+ <activity android:name="com.google.android.gms.ads.AdActivity"
+ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
+ android:theme="@android:style/Theme.Translucent" />
+ </application>
+
+</manifest>
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/root/res/layout/activity_simple.xml.ftl b/templates/activities/GoogleAdMobAdsActivity/root/res/layout/activity_simple.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleAdMobAdsActivity/root/res/layout/activity_simple.xml.ftl
rename to templates/activities/GoogleAdMobAdsActivity/root/res/layout/activity_simple.xml.ftl
diff --git a/base/templates/activities/BlankActivity/root/res/menu/main.xml.ftl b/templates/activities/GoogleAdMobAdsActivity/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivity/root/res/menu/main.xml.ftl
rename to templates/activities/GoogleAdMobAdsActivity/root/res/menu/main.xml.ftl
diff --git a/base/templates/activities/BlankActivity/root/res/values-w820dp/dimens.xml b/templates/activities/GoogleAdMobAdsActivity/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/activities/BlankActivity/root/res/values-w820dp/dimens.xml
rename to templates/activities/GoogleAdMobAdsActivity/root/res/values-w820dp/dimens.xml
diff --git a/base/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl b/templates/activities/GoogleAdMobAdsActivity/root/res/values/dimens.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl
rename to templates/activities/GoogleAdMobAdsActivity/root/res/values/dimens.xml.ftl
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/root/res/values/strings.xml.ftl b/templates/activities/GoogleAdMobAdsActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleAdMobAdsActivity/root/res/values/strings.xml.ftl
rename to templates/activities/GoogleAdMobAdsActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/root/src/app_package/SimpleActivity.java.ftl b/templates/activities/GoogleAdMobAdsActivity/root/src/app_package/SimpleActivity.java.ftl
similarity index 100%
rename from base/templates/activities/GoogleAdMobAdsActivity/root/src/app_package/SimpleActivity.java.ftl
rename to templates/activities/GoogleAdMobAdsActivity/root/src/app_package/SimpleActivity.java.ftl
diff --git a/templates/activities/GoogleAdMobAdsActivity/template.xml b/templates/activities/GoogleAdMobAdsActivity/template.xml
new file mode 100644
index 0000000..b8e71e7
--- /dev/null
+++ b/templates/activities/GoogleAdMobAdsActivity/template.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="5"
+ name="Google AdMob Ads Activity"
+ minApi="7"
+ minBuildApi="14"
+ description="Creates an activity with AdMob Ad fragment.">
+
+ <category value="Google" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="menuName"
+ name="Menu Resource Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="menu_${classToResource(activityClass)}"
+ default="menu_main"
+ help="The name of the resource file to create for the menu items" />
+
+ <parameter
+ id="adFormat"
+ name="Ad Format"
+ type="enum"
+ default="interstitial"
+ help="Select Interstitial Ad or Banner Ad." >
+ <option id="interstitial">Interstitial</option>
+ <option id="banner">Banner</option>
+ </parameter>
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_admob_activity.png</thumb>
+ <!-- attributes act as selectors based on chosen parameters -->
+ <thumb adFormat="interstitial">template_admob_activity_interstitial.png</thumb>
+ <thumb adFormat="banner">template_admob_activity_banner.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/GoogleAdMobAdsActivity/template_admob_activity.png b/templates/activities/GoogleAdMobAdsActivity/template_admob_activity.png
new file mode 100644
index 0000000..04fd878
Binary files /dev/null and b/templates/activities/GoogleAdMobAdsActivity/template_admob_activity.png differ
diff --git a/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_banner.png b/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_banner.png
new file mode 100644
index 0000000..0c1a754
Binary files /dev/null and b/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_banner.png differ
diff --git a/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_interstitial.png b/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_interstitial.png
new file mode 100644
index 0000000..3c4d046
Binary files /dev/null and b/templates/activities/GoogleAdMobAdsActivity/template_admob_activity_interstitial.png differ
diff --git a/templates/activities/GoogleAdMobAdsActivity/template_blank_activity.png b/templates/activities/GoogleAdMobAdsActivity/template_blank_activity.png
new file mode 100644
index 0000000..e570721
Binary files /dev/null and b/templates/activities/GoogleAdMobAdsActivity/template_blank_activity.png differ
diff --git a/templates/activities/GoogleMapsActivity/globals.xml.ftl b/templates/activities/GoogleMapsActivity/globals.xml.ftl
new file mode 100644
index 0000000..a871ba7
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/globals.xml.ftl
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="projectOut" value="." />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="debugResOut" value="${escapeXmlAttribute(projectOut)}/src/debug/res" />
+ <global id="releaseResOut" value="${escapeXmlAttribute(projectOut)}/src/release/res" />
+ <global id="resOut" value="${resDir}" />
+ <global id="simpleName" value="${activityToLayout(activityClass)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/GoogleMapsActivity/recipe.xml.ftl b/templates/activities/GoogleMapsActivity/recipe.xml.ftl
new file mode 100644
index 0000000..83eef2a
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
+ <dependency mavenUrl="com.android.support:appcompat-v7:${targetApi}.+" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/layout/activity_map.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/src/app_package/MapActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <merge from="root/debugRes/values/google_maps_api.xml.ftl"
+ to="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
+
+ <merge from="root/releaseRes/values/google_maps_api.xml.ftl"
+ to="${escapeXmlAttribute(releaseResOut)}/values/google_maps_api.xml" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <!-- Display the API key instructions. -->
+ <open file="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
+</recipe>
diff --git a/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl b/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..8911412
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,37 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+ <!--
+ The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
+ Google Maps Android API v2, but you must specify either coarse or fine
+ location permissions for the 'MyLocation' functionality.
+ -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+ <application>
+
+ <!--
+ The API key for Google Maps-based APIs is defined as a string resource.
+ (See the file "res/values/google_maps_api.xml").
+ Note that the API key is linked to the encryption key used to sign the APK.
+ You need a different API key for each encryption key, including the release key that is used to
+ sign the APK for publishing.
+ You can define the keys for the debug and release targets in src/debug/ and src/release/.
+ -->
+ <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key"/>
+
+ <activity android:name="${relativePackage}.${activityClass}"
+ android:label="@string/title_${simpleName}">
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+
+ </activity>
+ </application>
+
+</manifest>
diff --git a/base/templates/activities/GoogleMapsActivity/root/debugRes/values/google_maps_api.xml.ftl b/templates/activities/GoogleMapsActivity/root/debugRes/values/google_maps_api.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsActivity/root/debugRes/values/google_maps_api.xml.ftl
rename to templates/activities/GoogleMapsActivity/root/debugRes/values/google_maps_api.xml.ftl
diff --git a/base/templates/activities/GoogleMapsActivity/root/releaseRes/values/google_maps_api.xml.ftl b/templates/activities/GoogleMapsActivity/root/releaseRes/values/google_maps_api.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsActivity/root/releaseRes/values/google_maps_api.xml.ftl
rename to templates/activities/GoogleMapsActivity/root/releaseRes/values/google_maps_api.xml.ftl
diff --git a/base/templates/activities/GoogleMapsActivity/root/res/layout/activity_map.xml.ftl b/templates/activities/GoogleMapsActivity/root/res/layout/activity_map.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsActivity/root/res/layout/activity_map.xml.ftl
rename to templates/activities/GoogleMapsActivity/root/res/layout/activity_map.xml.ftl
diff --git a/base/templates/activities/GoogleMapsActivity/root/res/values/strings.xml.ftl b/templates/activities/GoogleMapsActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsActivity/root/res/values/strings.xml.ftl
rename to templates/activities/GoogleMapsActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/activities/GoogleMapsActivity/root/src/app_package/MapActivity.java.ftl b/templates/activities/GoogleMapsActivity/root/src/app_package/MapActivity.java.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsActivity/root/src/app_package/MapActivity.java.ftl
rename to templates/activities/GoogleMapsActivity/root/src/app_package/MapActivity.java.ftl
diff --git a/templates/activities/GoogleMapsActivity/template.xml b/templates/activities/GoogleMapsActivity/template.xml
new file mode 100644
index 0000000..fef316c
--- /dev/null
+++ b/templates/activities/GoogleMapsActivity/template.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Google Maps Activity"
+ description="Creates a new activity with a Google Map"
+ minApi="9"
+ minBuildApi="14">
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <category value="Google" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MapsActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_map"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="Map"
+ help="The name of the activity." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <thumbs>
+ <thumb>template_map_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/GoogleMapsActivity/template_map_activity.png b/templates/activities/GoogleMapsActivity/template_map_activity.png
new file mode 100644
index 0000000..70b8405
Binary files /dev/null and b/templates/activities/GoogleMapsActivity/template_map_activity.png differ
diff --git a/templates/activities/GoogleMapsWearActivity/globals.xml.ftl b/templates/activities/GoogleMapsWearActivity/globals.xml.ftl
new file mode 100644
index 0000000..41a4c6b
--- /dev/null
+++ b/templates/activities/GoogleMapsWearActivity/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "../common/wear_common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/GoogleMapsWearActivity/recipe.xml.ftl b/templates/activities/GoogleMapsWearActivity/recipe.xml.ftl
new file mode 100644
index 0000000..b3400aa
--- /dev/null
+++ b/templates/activities/GoogleMapsWearActivity/recipe.xml.ftl
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.google.android.gms:play-services-wearable:+" />
+ <dependency mavenUrl="com.google.android.gms:play-services-maps:+" />
+ <dependency mavenUrl="com.google.android.support:wearable:+" />
+
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <merge from="root/AndroidManifestPermissions.xml"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+<#if appManifestOut??>
+ <merge from="root/AndroidManifestPermissions.xml"
+ to="${escapeXmlAttribute(appManifestOut)}/AndroidManifest.xml" />
+</#if>
+
+ <instantiate from="root/res/layout/activity_map.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/res/layout/activity_map.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/src/app_package/MapActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <merge from="root/debugRes/values/google_maps_api.xml.ftl"
+ to="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
+
+ <merge from="root/releaseRes/values/google_maps_api.xml.ftl"
+ to="${escapeXmlAttribute(releaseResOut)}/values/google_maps_api.xml" />
+
+ <!-- Display the API key instructions. -->
+ <open file="${escapeXmlAttribute(debugResOut)}/values/google_maps_api.xml" />
+</recipe>
diff --git a/templates/activities/GoogleMapsWearActivity/root/AndroidManifest.xml.ftl b/templates/activities/GoogleMapsWearActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..c2a180b
--- /dev/null
+++ b/templates/activities/GoogleMapsWearActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+
+ <!--
+ The API key for Google Maps-based APIs is defined as a string resource.
+ (See the file "res/values/google_maps_api.xml").
+ Note that the API key is linked to the encryption key used to sign the APK.
+ You need a different API key for each encryption key, including the release key that is used to
+ sign the APK for publishing.
+ You can define the keys for the debug and release targets in src/debug/ and src/release/.
+ -->
+ <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key"/>
+
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ >
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/GoogleMapsWearActivity/root/AndroidManifestPermissions.xml b/templates/activities/GoogleMapsWearActivity/root/AndroidManifestPermissions.xml
new file mode 100644
index 0000000..41dbde8
--- /dev/null
+++ b/templates/activities/GoogleMapsWearActivity/root/AndroidManifestPermissions.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <!--
+ The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
+ Google Maps Android API v2, but you must specify either coarse or fine
+ location permissions for the 'MyLocation' functionality.
+ -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+</manifest>
diff --git a/base/templates/activities/GoogleMapsWearActivity/root/debugRes/values/google_maps_api.xml.ftl b/templates/activities/GoogleMapsWearActivity/root/debugRes/values/google_maps_api.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsWearActivity/root/debugRes/values/google_maps_api.xml.ftl
rename to templates/activities/GoogleMapsWearActivity/root/debugRes/values/google_maps_api.xml.ftl
diff --git a/base/templates/activities/GoogleMapsWearActivity/root/releaseRes/values/google_maps_api.xml.ftl b/templates/activities/GoogleMapsWearActivity/root/releaseRes/values/google_maps_api.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsWearActivity/root/releaseRes/values/google_maps_api.xml.ftl
rename to templates/activities/GoogleMapsWearActivity/root/releaseRes/values/google_maps_api.xml.ftl
diff --git a/base/templates/activities/GoogleMapsWearActivity/root/res/layout/activity_map.xml.ftl b/templates/activities/GoogleMapsWearActivity/root/res/layout/activity_map.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsWearActivity/root/res/layout/activity_map.xml.ftl
rename to templates/activities/GoogleMapsWearActivity/root/res/layout/activity_map.xml.ftl
diff --git a/base/templates/activities/GoogleMapsWearActivity/root/res/values/strings.xml.ftl b/templates/activities/GoogleMapsWearActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsWearActivity/root/res/values/strings.xml.ftl
rename to templates/activities/GoogleMapsWearActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/activities/GoogleMapsWearActivity/root/src/app_package/MapActivity.java.ftl b/templates/activities/GoogleMapsWearActivity/root/src/app_package/MapActivity.java.ftl
similarity index 100%
rename from base/templates/activities/GoogleMapsWearActivity/root/src/app_package/MapActivity.java.ftl
rename to templates/activities/GoogleMapsWearActivity/root/src/app_package/MapActivity.java.ftl
diff --git a/templates/activities/GoogleMapsWearActivity/template.xml b/templates/activities/GoogleMapsWearActivity/template.xml
new file mode 100644
index 0000000..c9869b6
--- /dev/null
+++ b/templates/activities/GoogleMapsWearActivity/template.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Google Maps Wear Activity"
+ minApi="20"
+ minBuildApi="20"
+ description="Creates an new activity with a Google Map for Android Wear">
+
+ <category value="Google" />
+ <formfactor value="Wear" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MapsActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_map"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="true"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it the default launchable activity" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_thumb.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/GoogleMapsWearActivity/template_thumb.png b/templates/activities/GoogleMapsWearActivity/template_thumb.png
new file mode 100644
index 0000000..a70eb6e
Binary files /dev/null and b/templates/activities/GoogleMapsWearActivity/template_thumb.png differ
diff --git a/templates/activities/LoginActivity/globals.xml.ftl b/templates/activities/LoginActivity/globals.xml.ftl
new file mode 100644
index 0000000..a49af94
--- /dev/null
+++ b/templates/activities/LoginActivity/globals.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="hasNoActionBar" type="boolean" value="false" />
+ <global id="isLauncher" type="boolean" value="${isNewProject?string}" />
+ <global id="includePermissionCheck" type="boolean" value="${(targetApi gte 23)?string}" />
+ <global id="GenericStringArgument" type="string" value="<#if buildApi lt 19>String</#if>" />
+ <#include "../common/common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/LoginActivity/recipe.xml.ftl b/templates/activities/LoginActivity/recipe.xml.ftl
new file mode 100644
index 0000000..78b4a6e
--- /dev/null
+++ b/templates/activities/LoginActivity/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+ <#if appCompat && !(hasDependency('com.android.support:appcompat-v7'))>
+ <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+" />
+ </#if>
+
+ <#if (buildApi gte 22) && appCompat && !(hasDependency('com.android.support:design'))>
+ <dependency mavenUrl="com.android.support:design:${buildApi}.+" />
+ </#if>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/res/layout/activity_login.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/src/app_package/LoginActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+</recipe>
diff --git a/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl b/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..c400320
--- /dev/null
+++ b/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,31 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <!-- To auto-complete the email text field in the login form with the user's emails -->
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.READ_PROFILE" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application>
+ <activity android:name=".${activityClass}"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${simpleName}"
+ </#if>
+ <#if hasNoActionBar>
+ android:theme="@style/${themeNameNoActionBar}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+</manifest>
diff --git a/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl b/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
new file mode 100644
index 0000000..384d5d3
--- /dev/null
+++ b/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
@@ -0,0 +1,83 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="${relativePackage}.${activityClass}">
+
+ <!-- Login progress -->
+ <ProgressBar
+ android:id="@+id/login_progress"
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"
+ android:visibility="gone"/>
+
+ <ScrollView
+ android:id="@+id/login_form"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/email_login_form"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+<#if (buildApi gte 22) && appCompat>
+ <android.support.design.widget.TextInputLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+</#if>
+ <AutoCompleteTextView
+ android:id="@+id/email"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/prompt_email"
+ android:inputType="textEmailAddress"
+ android:maxLines="1"
+ android:singleLine="true"/>
+
+<#if (buildApi gte 22) && appCompat>
+ </android.support.design.widget.TextInputLayout>
+
+ <android.support.design.widget.TextInputLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+</#if>
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/prompt_password"
+ android:imeActionId="@+id/login"
+ android:imeActionLabel="@string/action_sign_in_short"
+ android:imeOptions="actionUnspecified"
+ android:inputType="textPassword"
+ android:maxLines="1"
+ android:singleLine="true"/>
+
+<#if (buildApi gte 22) && appCompat>
+ </android.support.design.widget.TextInputLayout>
+
+</#if>
+ <Button
+ android:id="@+id/email_sign_in_button"
+ style="?android:textAppearanceSmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/action_sign_in"
+ android:textStyle="bold"/>
+
+ </LinearLayout>
+ </ScrollView>
+</LinearLayout>
diff --git a/base/templates/activities/LoginActivity/root/res/values/dimens.xml b/templates/activities/LoginActivity/root/res/values/dimens.xml
similarity index 100%
rename from base/templates/activities/LoginActivity/root/res/values/dimens.xml
rename to templates/activities/LoginActivity/root/res/values/dimens.xml
diff --git a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..929638d
--- /dev/null
+++ b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,16 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+
+ <!-- Strings related to login -->
+ <string name="prompt_email">Email</string>
+ <string name="prompt_password">Password (optional)</string>
+ <string name="action_sign_in">Sign in or register</string>
+ <string name="action_sign_in_short">Sign in</string>
+ <string name="error_invalid_email">This email address is invalid</string>
+ <string name="error_invalid_password">This password is too short</string>
+ <string name="error_incorrect_password">This password is incorrect</string>
+ <string name="error_field_required">This field is required</string>
+ <string name="permission_rationale">"Contacts permissions are needed for providing email completions."</string>
+</resources>
diff --git a/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl b/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
new file mode 100644
index 0000000..f12a2e9
--- /dev/null
+++ b/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
@@ -0,0 +1,417 @@
+package ${packageName};
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.TargetApi;
+<#if includePermissionCheck>
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
+</#if>
+import ${superClassFqcn};
+import android.app.LoaderManager.LoaderCallbacks;
+<#if minApiLevel lt 14>import android.content.ContentResolver;</#if>
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+<#if minApiLevel lt 14>import android.os.Build.VERSION;</#if>
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.inputmethod.EditorInfo;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.List;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+<#if includePermissionCheck>
+import static android.Manifest.permission.READ_CONTACTS;
+</#if>
+
+/**
+ * A login screen that offers login via email/password.
+ */
+public class ${activityClass} extends ${superClass} implements LoaderCallbacks<Cursor> {
+
+<#if includePermissionCheck>
+ /**
+ * Id to identity READ_CONTACTS permission request.
+ */
+ private static final int REQUEST_READ_CONTACTS = 0;
+
+</#if>
+ /**
+ * A dummy authentication store containing known user names and passwords.
+ * TODO: remove after connecting to a real authentication system.
+ */
+ private static final String[] DUMMY_CREDENTIALS = new String[]{
+ "foo at example.com:hello", "bar at example.com:world"
+ };
+ /**
+ * Keep track of the login task to ensure we can cancel it if requested.
+ */
+ private UserLoginTask mAuthTask = null;
+
+ // UI references.
+ private AutoCompleteTextView mEmailView;
+ private EditText mPasswordView;
+ private View mProgressView;
+ private View mLoginFormView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+<#if parentActivityClass != "">
+ setupActionBar();
+</#if>
+ // Set up the login form.
+ mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
+ populateAutoComplete();
+
+ mPasswordView = (EditText) findViewById(R.id.password);
+ mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
+ if (id == R.id.login || id == EditorInfo.IME_NULL) {
+ attemptLogin();
+ return true;
+ }
+ return false;
+ }
+ });
+
+ Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
+ mEmailSignInButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ attemptLogin();
+ }
+ });
+
+ mLoginFormView = findViewById(R.id.login_form);
+ mProgressView = findViewById(R.id.login_progress);
+ }
+
+ private void populateAutoComplete() {
+<#if includePermissionCheck>
+ if (!mayRequestContacts()) {
+ return;
+ }
+
+</#if>
+<#if minApiLevel gte 14>
+ getLoaderManager().initLoader(0, null, this);
+<#else>
+ if (VERSION.SDK_INT >= 14) {
+ // Use ContactsContract.Profile (API 14+)
+ getLoaderManager().initLoader(0, null, this);
+ } else if (VERSION.SDK_INT >= 8) {
+ // Use AccountManager (API 8+)
+ new SetupEmailAutoCompleteTask().execute(null, null);
+ }
+</#if>
+ }
+
+<#if includePermissionCheck>
+ private boolean mayRequestContacts() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return true;
+ }
+ if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
+ Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
+ .setAction(android.R.string.ok, new View.OnClickListener() {
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onClick(View v) {
+ requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
+ }
+ });
+ } else {
+ requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
+ }
+ return false;
+ }
+
+ /**
+ * Callback received when a permissions request has been completed.
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ if (requestCode == REQUEST_READ_CONTACTS) {
+ if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ populateAutoComplete();
+ }
+ }
+ }
+
+</#if>
+ <#if parentActivityClass != "">
+ /**
+ * Set up the {@link android.app.ActionBar}, if the API is available.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void setupActionBar() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ // Show the Up button in the action bar.
+ get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ }
+ </#if>
+
+ /**
+ * Attempts to sign in or register the account specified by the login form.
+ * If there are form errors (invalid email, missing fields, etc.), the
+ * errors are presented and no actual login attempt is made.
+ */
+ private void attemptLogin() {
+ if (mAuthTask != null) {
+ return;
+ }
+
+ // Reset errors.
+ mEmailView.setError(null);
+ mPasswordView.setError(null);
+
+ // Store values at the time of the login attempt.
+ String email = mEmailView.getText().toString();
+ String password = mPasswordView.getText().toString();
+
+ boolean cancel = false;
+ View focusView = null;
+
+ // Check for a valid password, if the user entered one.
+ if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
+ mPasswordView.setError(getString(R.string.error_invalid_password));
+ focusView = mPasswordView;
+ cancel = true;
+ }
+
+ // Check for a valid email address.
+ if (TextUtils.isEmpty(email)) {
+ mEmailView.setError(getString(R.string.error_field_required));
+ focusView = mEmailView;
+ cancel = true;
+ } else if (!isEmailValid(email)) {
+ mEmailView.setError(getString(R.string.error_invalid_email));
+ focusView = mEmailView;
+ cancel = true;
+ }
+
+ if (cancel) {
+ // There was an error; don't attempt login and focus the first
+ // form field with an error.
+ focusView.requestFocus();
+ } else {
+ // Show a progress spinner, and kick off a background task to
+ // perform the user login attempt.
+ showProgress(true);
+ mAuthTask = new UserLoginTask(email, password);
+ mAuthTask.execute((Void) null);
+ }
+ }
+ private boolean isEmailValid(String email) {
+ //TODO: Replace this with your own logic
+ return email.contains("@");
+ }
+
+ private boolean isPasswordValid(String password) {
+ //TODO: Replace this with your own logic
+ return password.length() > 4;
+ }
+
+ /**
+ * Shows the progress UI and hides the login form.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
+ private void showProgress(final boolean show) {
+ // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
+ // for very easy animations. If available, use these APIs to fade-in
+ // the progress spinner.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+ int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
+
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ mLoginFormView.animate().setDuration(shortAnimTime).alpha(
+ show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ }
+ });
+
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ mProgressView.animate().setDuration(shortAnimTime).alpha(
+ show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ });
+ } else {
+ // The ViewPropertyAnimator APIs are not available, so simply show
+ // and hide the relevant UI components.
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
+ return new CursorLoader(this,
+ // Retrieve data rows for the device user's 'profile' contact.
+ Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
+ ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
+
+ // Select only email addresses.
+ ContactsContract.Contacts.Data.MIMETYPE +
+ " = ?", new String[]{ContactsContract.CommonDataKinds.Email
+ .CONTENT_ITEM_TYPE},
+
+ // Show primary email addresses first. Note that there won't be
+ // a primary email address if the user hasn't specified one.
+ ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
+ List<String> emails = new ArrayList<${GenericStringArgument}>();
+ cursor.moveToFirst();
+ while (!cursor.isAfterLast()) {
+ emails.add(cursor.getString(ProfileQuery.ADDRESS));
+ cursor.moveToNext();
+ }
+
+ addEmailsToAutoComplete(emails);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> cursorLoader) {
+
+ }
+
+ private interface ProfileQuery {
+ String[] PROJECTION = {
+ ContactsContract.CommonDataKinds.Email.ADDRESS,
+ ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
+ };
+
+ int ADDRESS = 0;
+ int IS_PRIMARY = 1;
+ }
+
+<#if minApiLevel lt 14>
+ /**
+ * Use an AsyncTask to fetch the user's email addresses on a background thread, and update
+ * the email text field with results on the main UI thread.
+ */
+ class SetupEmailAutoCompleteTask extends AsyncTask<Void, Void, List<String>> {
+
+ @Override
+ protected List<String> doInBackground(Void... voids) {
+ ArrayList<String> emailAddressCollection = new ArrayList<${GenericStringArgument}>();
+
+ // Get all emails from the user's contacts and copy them to a list.
+ ContentResolver cr = getContentResolver();
+ Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
+ null, null, null);
+ while (emailCur.moveToNext()) {
+ String email = emailCur.getString(emailCur.getColumnIndex(ContactsContract
+ .CommonDataKinds.Email.DATA));
+ emailAddressCollection.add(email);
+ }
+ emailCur.close();
+
+ return emailAddressCollection;
+ }
+
+ @Override
+ protected void onPostExecute(List<String> emailAddressCollection) {
+ addEmailsToAutoComplete(emailAddressCollection);
+ }
+ }
+</#if>
+
+ private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
+ //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
+ ArrayAdapter<String> adapter =
+ new ArrayAdapter<${GenericStringArgument}>(${activityClass}.this,
+ android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
+
+ mEmailView.setAdapter(adapter);
+ }
+
+ /**
+ * Represents an asynchronous login/registration task used to authenticate
+ * the user.
+ */
+ public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
+
+ private final String mEmail;
+ private final String mPassword;
+
+ UserLoginTask(String email, String password) {
+ mEmail = email;
+ mPassword = password;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ // TODO: attempt authentication against a network service.
+
+ try {
+ // Simulate network access.
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ return false;
+ }
+
+ for (String credential : DUMMY_CREDENTIALS) {
+ String[] pieces = credential.split(":");
+ if (pieces[0].equals(mEmail)) {
+ // Account exists, return true if the password matches.
+ return pieces[1].equals(mPassword);
+ }
+ }
+
+ // TODO: register the new account here.
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(final Boolean success) {
+ mAuthTask = null;
+ showProgress(false);
+
+ if (success) {
+ finish();
+ } else {
+ mPasswordView.setError(getString(R.string.error_incorrect_password));
+ mPasswordView.requestFocus();
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ mAuthTask = null;
+ showProgress(false);
+ }
+ }
+}
+
diff --git a/templates/activities/LoginActivity/template.xml b/templates/activities/LoginActivity/template.xml
new file mode 100644
index 0000000..3478d17
--- /dev/null
+++ b/templates/activities/LoginActivity/template.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="6"
+ name="Login Activity"
+ description="Creates a new login activity, allowing users to optionally sign in with Google+ or enter an email address and password to log in to or register with your application."
+ requireAppTheme="true"
+ minApi="8"
+ minBuildApi="14">
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="LoginActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_login"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="Sign in"
+ help="The name of the activity." />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <thumbs>
+ <thumb>template_login_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/LoginActivity/template_login_activity.png b/templates/activities/LoginActivity/template_login_activity.png
new file mode 100644
index 0000000..bb0ff1d
Binary files /dev/null and b/templates/activities/LoginActivity/template_login_activity.png differ
diff --git a/templates/activities/MasterDetailFlow/globals.xml.ftl b/templates/activities/MasterDetailFlow/globals.xml.ftl
new file mode 100644
index 0000000..5c16076
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/globals.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<globals>
+ <#assign Collection=extractLetters(objectKind)>
+ <#assign collection_name=Collection?lower_case + "_list">
+ <#include "../common/common_globals.xml.ftl" />
+ <global id="CollectionName" value="${Collection}List" />
+ <global id="collection_name" value="${collection_name}" />
+ <global id="DetailName" value="${Collection}Detail" />
+ <global id="detail_name" value="${Collection?lower_case}_detail" />
+ <global id="item_list_layout" value="<#if !appCompatActivity>activity_</#if>${collection_name}" />
+ <global id="item_list_content_layout" value="${collection_name}_content" />
+</globals>
diff --git a/templates/activities/MasterDetailFlow/recipe.xml.ftl b/templates/activities/MasterDetailFlow/recipe.xml.ftl
new file mode 100644
index 0000000..033da5c
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/recipe.xml.ftl
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+" />
+ <dependency mavenUrl="com.android.support:recyclerview-v7:${buildApi}.+" />
+
+ <#if hasAppBar>
+ <dependency mavenUrl="com.android.support:design:${buildApi}.+"/>
+ </#if>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <#if minApiLevel lt 13>
+ <merge from="root/res/values-w900dp/refs.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-w900dp/refs.xml" />
+ </#if>
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <#if hasAppBar>
+ <#include "../common/recipe_no_actionbar.xml.ftl" />
+ </#if>
+
+ <instantiate from="root/res/layout/activity_item_detail.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/activity_${detail_name}.xml" />
+ <instantiate from="root/res/layout/fragment_item_list.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${item_list_layout}.xml" />
+ <#if minApiLevel lt 13>
+ <instantiate from="root/res/layout/fragment_item_list_twopane.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${item_list_layout}_twopane.xml" />
+ <#else>
+ <instantiate from="root/res/layout/fragment_item_list_twopane.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout-w900dp/${item_list_layout}.xml" />
+ </#if>
+ <instantiate from="root/res/layout/item_list_content.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${item_list_content_layout}.xml" />
+ <instantiate from="root/res/layout/fragment_item_detail.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${detail_name}.xml" />
+ <#if hasAppBar>
+ <instantiate from="root/res/layout/activity_item_list_app_bar.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/activity_${item_list_layout}.xml" />
+ </#if>
+
+ <instantiate from="root/src/app_package/ContentDetailActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${DetailName}Activity.java" />
+ <instantiate from="root/src/app_package/ContentDetailFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
+ <instantiate from="root/src/app_package/ContentListActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${CollectionName}Activity.java" />
+ <#include "../common/recipe_dummy_content.xml.ftl" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
+</recipe>
diff --git a/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl b/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..88b80f1
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
@@ -0,0 +1,38 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application>
+ <activity
+ android:name="${relativePackage}.${CollectionName}Activity"
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${collection_name}"
+ </#if>
+ <#if hasAppBar>
+ android:theme="@style/${themeNameNoActionBar}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+
+ <activity android:name="${relativePackage}.${DetailName}Activity"
+ android:label="@string/title_${detail_name}"
+ <#if hasAppBar>
+ android:theme="@style/${themeNameNoActionBar}"
+ </#if>
+ <#if buildApi gte 16>android:parentActivityName="${relativePackage}.${CollectionName}Activity"</#if>>
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${relativePackage}.${CollectionName}Activity" />
+ </activity>
+ </application>
+
+</manifest>
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/activity_item_detail.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/activity_item_detail.xml.ftl
new file mode 100644
index 0000000..87c6c12
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/res/layout/activity_item_detail.xml.ftl
@@ -0,0 +1,63 @@
+<#if !hasAppBar>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/${detail_name}_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${relativePackage}.${DetailName}Activity"
+ tools:ignore="MergeRootFrame" />
+<#else>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context="${relativePackage}.${DetailName}Activity"
+ tools:ignore="MergeRootFrame">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/app_bar_height"
+ android:fitsSystemWindows="true"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+ <android.support.design.widget.CollapsingToolbarLayout
+ android:id="@+id/toolbar_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ app:contentScrim="?attr/colorPrimary"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed"
+ app:toolbarId="@+id/toolbar">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/detail_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:layout_collapseMode="pin"
+ app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
+
+ </android.support.design.widget.CollapsingToolbarLayout>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <android.support.v4.widget.NestedScrollView
+ android:id="@+id/${detail_name}_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_margin="@dimen/fab_margin"
+ android:src="@android:drawable/stat_notify_chat"
+ app:layout_anchor="@+id/${detail_name}_container"
+ app:layout_anchorGravity="top|end" />
+
+</android.support.design.widget.CoordinatorLayout>
+</#if>
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/activity_item_list_app_bar.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/activity_item_list_app_bar.xml.ftl
new file mode 100644
index 0000000..48c3472
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/res/layout/activity_item_list_app_bar.xml.ftl
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context=".MainActivity">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/${themeNameAppBarOverlay}">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:popupTheme="@style/${themeNamePopupOverlay}" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <FrameLayout
+ android:id="@+id/frameLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+ <include layout="@layout/${item_list_layout}" />
+ </FrameLayout>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="@dimen/fab_margin"
+ android:src="@android:drawable/ic_dialog_email" />
+
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/base/templates/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/fragment_item_detail.xml.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
rename to templates/activities/MasterDetailFlow/root/res/layout/fragment_item_detail.xml.ftl
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/fragment_item_list.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/fragment_item_list.xml.ftl
new file mode 100644
index 0000000..3a1bbbe
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/res/layout/fragment_item_list.xml.ftl
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/${collection_name}"
+ android:name="${packageName}.${CollectionName}Fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ app:layoutManager="LinearLayoutManager"
+ tools:context="${relativePackage}.${CollectionName}Activity"
+ tools:listitem="@layout/${item_list_content_layout}" />
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/fragment_item_list_twopane.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/fragment_item_list_twopane.xml.ftl
new file mode 100644
index 0000000..e3db303
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/res/layout/fragment_item_list_twopane.xml.ftl
@@ -0,0 +1,44 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ android:baselineAligned="false"
+ android:divider="?android:attr/dividerHorizontal"
+ android:orientation="horizontal"
+ android:showDividers="middle"
+ tools:context="${relativePackage}.${CollectionName}Activity">
+
+ <!--
+ This layout is a two-pane layout for the ${objectKindPlural}
+ master/detail flow.
+ <#if minApiLevel lt 13>See res/values-large/refs.xml and
+ res/values-w900dp/refs.xml for an example of layout aliases
+ that replace the single-pane version of the layout with
+ this two-pane version.
+
+ For more on layout aliases, see:
+ http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters</#if>
+ -->
+
+ <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/${collection_name}"
+ android:name="${packageName}.${CollectionName}Fragment"
+ android:layout_width="@dimen/item_width"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ app:layoutManager="LinearLayoutManager"
+ tools:context="${relativePackage}.${CollectionName}Activity"
+ tools:listitem="@layout/${item_list_content_layout}" />
+
+ <FrameLayout
+ android:id="@+id/${detail_name}_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3" />
+
+</LinearLayout>
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/item_list_content.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/item_list_content.xml.ftl
new file mode 100644
index 0000000..3a7ea28
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/res/layout/item_list_content.xml.ftl
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/text_margin"
+ android:textAppearance="?attr/textAppearanceListItem"/>
+
+ <TextView
+ android:id="@+id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/text_margin"
+ android:textAppearance="?attr/textAppearanceListItem"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/templates/activities/MasterDetailFlow/root/res/values-w900dp/refs.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values-w900dp/refs.xml.ftl
new file mode 100644
index 0000000..60d3929
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/res/values-w900dp/refs.xml.ftl
@@ -0,0 +1,10 @@
+<resources>
+ <!--
+ Layout alias to replace the single-pane version of the layout with a
+ two-pane version on screens with a width of at least 900 density-independent pixels (dips).
+
+ For more on layout aliases, see:
+ http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
+ -->
+ <item type="layout" name="${item_list_layout}">@layout/${item_list_layout}_twopane</item>
+</resources>
diff --git a/templates/activities/MasterDetailFlow/root/res/values/dimens.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values/dimens.xml.ftl
new file mode 100644
index 0000000..3dda28d
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/res/values/dimens.xml.ftl
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <#if hasAppBar>
+ <dimen name="fab_margin">16dp</dimen>
+ <dimen name="app_bar_height">200dp</dimen>
+ </#if>
+ <dimen name="item_width">200dp</dimen>
+ <dimen name="text_margin">16dp</dimen>
+</resources>
diff --git a/base/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
rename to templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
diff --git a/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
new file mode 100644
index 0000000..47945c4
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
@@ -0,0 +1,102 @@
+package ${packageName};
+
+import android.content.Intent;
+import android.os.Bundle;
+<#if hasAppBar>
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+</#if>
+import ${superClassFqcn};
+import ${actionBarClassFqcn};
+<#if minApiLevel lt 16>
+import android.support.v4.app.NavUtils;
+</#if>
+import android.view.MenuItem;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+/**
+ * An activity representing a single ${objectKind} detail screen. This
+ * activity is only used narrow width devices. On tablet-size devices,
+ * item details are presented side-by-side with a list of items
+ * in a {@link ${CollectionName}Activity}.
+ */
+public class ${DetailName}Activity extends ${superClass} {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_${detail_name});
+<#if hasAppBar>
+ Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar);
+ setSupportActionBar(toolbar);
+
+ FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ });
+</#if>
+
+ // Show the Up button in the action bar.
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ // savedInstanceState is non-null when there is fragment state
+ // saved from previous configurations of this activity
+ // (e.g. when rotating the screen from portrait to landscape).
+ // In this case, the fragment will automatically be re-added
+ // to its container so we don't need to manually add it.
+ // For more information, see the Fragments API guide at:
+ //
+ // http://developer.android.com/guide/components/fragments.html
+ //
+ if (savedInstanceState == null) {
+ // Create the detail fragment and add it to the activity
+ // using a fragment transaction.
+ Bundle arguments = new Bundle();
+ arguments.putString(${DetailName}Fragment.ARG_ITEM_ID,
+ getIntent().getStringExtra(${DetailName}Fragment.ARG_ITEM_ID));
+ ${DetailName}Fragment fragment = new ${DetailName}Fragment();
+ fragment.setArguments(arguments);
+ get${Support}FragmentManager().beginTransaction()
+ .add(R.id.${detail_name}_container, fragment)
+ .commit();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+<#if minApiLevel lt 16>
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
+<#else>
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ navigateUpTo(new Intent(this, ${CollectionName}Activity.class));
+</#if>
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
new file mode 100644
index 0000000..2556e9a
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
@@ -0,0 +1,75 @@
+package ${packageName};
+
+<#if hasAppBar>
+import android.app.Activity;
+import android.support.design.widget.CollapsingToolbarLayout;
+</#if>
+import android.os.Bundle;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+import ${packageName}.dummy.DummyContent;
+
+/**
+ * A fragment representing a single ${objectKind} detail screen.
+ * This fragment is either contained in a {@link ${CollectionName}Activity}
+ * in two-pane mode (on tablets) or a {@link ${DetailName}Activity}
+ * on handsets.
+ */
+public class ${DetailName}Fragment extends Fragment {
+ /**
+ * The fragment argument representing the item ID that this fragment
+ * represents.
+ */
+ public static final String ARG_ITEM_ID = "item_id";
+
+ /**
+ * The dummy content this fragment is presenting.
+ */
+ private DummyContent.DummyItem mItem;
+
+ /**
+ * Mandatory empty constructor for the fragment manager to instantiate the
+ * fragment (e.g. upon screen orientation changes).
+ */
+ public ${DetailName}Fragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments().containsKey(ARG_ITEM_ID)) {
+ // Load the dummy content specified by the fragment
+ // arguments. In a real-world scenario, use a Loader
+ // to load content from a content provider.
+ mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
+<#if hasAppBar>
+
+ Activity activity = this.getActivity();
+ CollapsingToolbarLayout appBarLayout = (CollapsingToolbarLayout) activity.findViewById(R.id.toolbar_layout);
+ if (appBarLayout != null) {
+ appBarLayout.setTitle(mItem.content);
+ }
+</#if>
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.${detail_name}, container, false);
+
+ // Show the dummy content as text in a TextView.
+ if (mItem != null) {
+ ((TextView) rootView.findViewById(R.id.${detail_name})).setText(mItem.details);
+ }
+
+ return rootView;
+ }
+}
diff --git a/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl b/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
new file mode 100644
index 0000000..bd54b68
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
@@ -0,0 +1,181 @@
+package ${packageName};
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import ${superClassFqcn};
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+<#if hasAppBar>
+import android.support.v7.widget.Toolbar;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+</#if>
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+<#if (parentActivityClass != "" && minApiLevel lt 16)>import android.support.v4.app.NavUtils;</#if>
+<#if parentActivityClass != "">
+import ${actionBarClassFqcn};
+import android.view.MenuItem;
+</#if>
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+import ${packageName}.dummy.DummyContent;
+
+import java.util.List;
+
+/**
+ * An activity representing a list of ${objectKindPlural}. This activity
+ * has different presentations for handset and tablet-size devices. On
+ * handsets, the activity presents a list of items, which when touched,
+ * lead to a {@link ${DetailName}Activity} representing
+ * item details. On tablets, the activity presents the list of items and
+ * item details side-by-side using two vertical panes.
+ */
+public class ${CollectionName}Activity extends ${superClass} {
+
+ /**
+ * Whether or not the activity is in two-pane mode, i.e. running on a tablet
+ * device.
+ */
+ private boolean mTwoPane;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+<#if hasAppBar>
+ setContentView(R.layout.activity_${item_list_layout});
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ toolbar.setTitle(getTitle());
+
+ FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ });
+<#else>
+ setContentView(R.layout.${item_list_layout});
+</#if>
+<#if parentActivityClass != "">
+ // Show the Up button in the action bar.
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+</#if>
+
+ View recyclerView = findViewById(R.id.${collection_name});
+ assert recyclerView != null;
+ setupRecyclerView((RecyclerView) recyclerView);
+
+ if (findViewById(R.id.${detail_name}_container) != null) {
+ // The detail container view will be present only in the
+ // large-screen layouts (res/values-w900dp).
+ // If this view is present, then the
+ // activity should be in two-pane mode.
+ mTwoPane = true;
+ }
+ }
+ <#if parentActivityClass != "">
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ ${(minApiLevel lt 16)?string('NavUtils.','')}navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ </#if>
+
+ private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
+ recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(DummyContent.ITEMS));
+ }
+
+ public class SimpleItemRecyclerViewAdapter
+ extends RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder> {
+
+ private final List<DummyContent.DummyItem> mValues;
+
+ public SimpleItemRecyclerViewAdapter(List<DummyContent.DummyItem> items) {
+ mValues = items;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.${item_list_content_layout}, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ holder.mItem = mValues.get(position);
+ holder.mIdView.setText(mValues.get(position).id);
+ holder.mContentView.setText(mValues.get(position).content);
+
+ holder.mView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mTwoPane) {
+ Bundle arguments = new Bundle();
+ arguments.putString(${DetailName}Fragment.ARG_ITEM_ID, holder.mItem.id);
+ ${DetailName}Fragment fragment = new ${DetailName}Fragment();
+ fragment.setArguments(arguments);
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.${detail_name}_container, fragment)
+ .commit();
+ } else {
+ Context context = v.getContext();
+ Intent intent = new Intent(context, ${DetailName}Activity.class);
+ intent.putExtra(${DetailName}Fragment.ARG_ITEM_ID, holder.mItem.id);
+
+ context.startActivity(intent);
+ }
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mValues.size();
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+ public final View mView;
+ public final TextView mIdView;
+ public final TextView mContentView;
+ public DummyContent.DummyItem mItem;
+
+ public ViewHolder(View view) {
+ super(view);
+ mView = view;
+ mIdView = (TextView) view.findViewById(R.id.id);
+ mContentView = (TextView) view.findViewById(R.id.content);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " '" + mContentView.getText() + "'";
+ }
+ }
+ }
+}
diff --git a/templates/activities/MasterDetailFlow/template.xml b/templates/activities/MasterDetailFlow/template.xml
new file mode 100644
index 0000000..e774732
--- /dev/null
+++ b/templates/activities/MasterDetailFlow/template.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="7"
+ name="Master/Detail Flow"
+ minApi="7"
+ requireAppTheme="true"
+ description="Creates a new master/detail flow, allowing users to view a collection of objects as well as details for each object. This flow is presented using two columns on tablet-size screens and one column on handsets and smaller screens. This template creates two activities, a master fragment, and a detail fragment."
+ category="Activity">
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <thumbs>
+ <thumb>template_master_detail.png</thumb>
+ </thumbs>
+
+ <parameter
+ id="objectKind"
+ name="Object Kind"
+ type="string"
+ constraints="nonempty"
+ default="Item"
+ help="Other examples are 'Person', 'Book', etc." />
+
+ <parameter
+ id="objectKindPlural"
+ name="Object Kind Plural"
+ type="string"
+ constraints="nonempty"
+ default="Items"
+ help="Other examples are 'People', 'Books', etc." />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ suggest="${objectKindPlural}"
+ default="Items" />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, the primary activity in the flow will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/MasterDetailFlow/template_master_detail.png b/templates/activities/MasterDetailFlow/template_master_detail.png
new file mode 100644
index 0000000..2d950f6
Binary files /dev/null and b/templates/activities/MasterDetailFlow/template_master_detail.png differ
diff --git a/templates/activities/NavigationDrawerActivity/globals.xml.ftl b/templates/activities/NavigationDrawerActivity/globals.xml.ftl
new file mode 100644
index 0000000..8c67447
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/globals.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "../common/common_globals.xml.ftl" />
+<#if appCompatActivity>
+ <global id="resIn" type="string" value="res-buildApi22" />
+<#else>
+ <global id="resIn" type="string" value="res" />
+</#if>
+ <global id="menuName" value="${classToResource(activityClass)}" />
+ <global id="simpleLayoutName" value="<#if appCompatActivity>${contentLayoutName}<#else>${layoutName}</#if>" />
+ <global id="includeImageDrawables" type="boolean" value="${(minApi?number lt 21)?string}" />
+</globals>
diff --git a/templates/activities/NavigationDrawerActivity/recipe.xml.ftl b/templates/activities/NavigationDrawerActivity/recipe.xml.ftl
new file mode 100644
index 0000000..0e97988
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/recipe.xml.ftl
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if !(hasDependency('com.android.support:support-v4'))>
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/>
+ </#if>
+
+ <#include "../common/recipe_manifest.xml.ftl" />
+
+ <merge from="root/${resIn}/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/${resIn}/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+ <instantiate from="root/${resIn}/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+<#if appCompatActivity>
+ <copy from="root/res-buildApi22/drawable"
+ to="${escapeXmlAttribute(resOut)}/drawable" />
+ <copy from="root/res-buildApi22/drawable-v21"
+ to="${escapeXmlAttribute(resOut)}/drawable<#if includeImageDrawables>-v21</#if>" />
+
+ <#if includeImageDrawables>
+ <copy from="root/res-buildApi22/values/drawables.xml"
+ to="${escapeXmlAttribute(resOut)}/values/drawables.xml" />
+ </#if>
+
+ <#if !(hasDependency('com.android.support:design'))>
+ <dependency mavenUrl="com.android.support:design:${buildApi}.+"/>
+ </#if>
+
+ <#if !(hasDependency('com.android.support:appcompat-v7'))>
+ <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+"/>
+ </#if>
+
+ <#include "../common/recipe_simple.xml.ftl" />
+
+ <#if hasAppBar>
+ <#include "../common/recipe_app_bar.xml.ftl" />
+ <#else>
+ <#include "../common/recipe_no_actionbar.xml.ftl" />
+ </#if>
+
+ <instantiate from="root/res-buildApi22/menu/drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${drawerMenu}.xml" />
+
+ <instantiate from="root/res-buildApi22/layout/navigation_view.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <instantiate from="root/res-buildApi22/layout/navigation_header.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${navHeaderLayoutName}.xml" />
+
+ <instantiate from="root/src-buildApi22/app_package/DrawerActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${contentLayoutName}.xml" />
+<#else>
+ <!-- TODO: switch on Holo Dark v. Holo Light -->
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ <copy from="root/res/drawable-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
+
+ <instantiate from="root/res/menu/global.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/global.xml" />
+
+ <merge from="root/res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <instantiate from="root/res/layout/activity_drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <instantiate from="root/res/layout/fragment_navigation_drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${navigationDrawerLayout}.xml" />
+ <instantiate from="root/res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <instantiate from="root/src/app_package/DrawerActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <instantiate from="root/src/app_package/NavigationDrawerFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/NavigationDrawerFragment.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</#if>
+
+</recipe>
diff --git a/base/templates/activities/NavigationDrawerActivity/root/build.gradle.ftl b/templates/activities/NavigationDrawerActivity/root/build.gradle.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/build.gradle.ftl
rename to templates/activities/NavigationDrawerActivity/root/build.gradle.ftl
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_camera.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_camera.xml
new file mode 100644
index 0000000..0a54e80
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_camera.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+</vector>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_gallery.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_gallery.xml
new file mode 100644
index 0000000..dc44a72
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_gallery.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z"/>
+</vector>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_manage.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_manage.xml
new file mode 100644
index 0000000..1823904
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_manage.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z"/>
+</vector>
\ No newline at end of file
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_send.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_send.xml
new file mode 100644
index 0000000..e145ca8
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_send.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
+</vector>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_share.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_share.xml
new file mode 100644
index 0000000..e3fe874
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_share.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
+</vector>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_slideshow.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_slideshow.xml
new file mode 100644
index 0000000..a306e81
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable-v21/ic_menu_slideshow.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z"/>
+</vector>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable/side_nav_bar.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable/side_nav_bar.xml
new file mode 100644
index 0000000..c145985
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/drawable/side_nav_bar.xml
@@ -0,0 +1,9 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <gradient
+ android:startColor="#81C784"
+ android:centerColor="#4CAF50"
+ android:endColor="#2E7D32"
+ android:type="linear"
+ android:angle="135"/>
+</shape>
\ No newline at end of file
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/layout/navigation_header.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/layout/navigation_header.xml.ftl
new file mode 100644
index 0000000..b00c668
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/layout/navigation_header.xml.ftl
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/nav_header_height"
+ android:background="@drawable/side_nav_bar"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark"
+ android:orientation="vertical"
+ android:gravity="bottom">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/nav_header_vertical_spacing"
+ android:src="@android:drawable/sym_def_app_icon"
+ android:id="@+id/imageView" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/nav_header_vertical_spacing"
+ android:text="Android Studio"
+ android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="android.studio at android.com"
+ android:id="@+id/textView" />
+
+</LinearLayout>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/layout/navigation_view.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/layout/navigation_view.xml.ftl
new file mode 100644
index 0000000..c395171
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/layout/navigation_view.xml.ftl
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:openDrawer="start">
+
+<#if hasAppBar>
+ <include
+ layout="@layout/${appBarLayoutName}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+<#else>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
+
+ <include
+ layout="@layout/${layoutName}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </LinearLayout>
+</#if>
+
+ <android.support.design.widget.NavigationView
+ android:id="@+id/nav_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:fitsSystemWindows="true"
+ app:headerLayout="@layout/${navHeaderLayoutName}"
+ app:menu="@menu/${drawerMenu}" />
+
+</android.support.v4.widget.DrawerLayout>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/menu/drawer.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/menu/drawer.xml.ftl
new file mode 100644
index 0000000..2a7f467
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/menu/drawer.xml.ftl
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <group android:checkableBehavior="single">
+ <item
+ android:id="@+id/nav_camera"
+ android:icon="@drawable/ic_menu_camera"
+ android:title="Import" />
+ <item
+ android:id="@+id/nav_gallery"
+ android:icon="@drawable/ic_menu_gallery"
+ android:title="Gallery" />
+ <item
+ android:id="@+id/nav_slideshow"
+ android:icon="@drawable/ic_menu_slideshow"
+ android:title="Slideshow" />
+ <item
+ android:id="@+id/nav_manage"
+ android:icon="@drawable/ic_menu_manage"
+ android:title="Tools" />
+ </group>
+
+ <item android:title="Communicate">
+ <menu>
+ <item
+ android:id="@+id/nav_share"
+ android:icon="@drawable/ic_menu_share"
+ android:title="Share" />
+ <item
+ android:id="@+id/nav_send"
+ android:icon="@drawable/ic_menu_send"
+ android:title="Send" />
+ </menu>
+ </item>
+
+</menu>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/menu/main.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/menu/main.xml.ftl
new file mode 100644
index 0000000..da4e818
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/menu/main.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ app:showAsAction="never" />
+</menu>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/dimens.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/dimens.xml.ftl
new file mode 100644
index 0000000..e8c9af3
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/dimens.xml.ftl
@@ -0,0 +1,5 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="nav_header_vertical_spacing">16dp</dimen>
+ <dimen name="nav_header_height">160dp</dimen>
+</resources>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/drawables.xml b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/drawables.xml
new file mode 100644
index 0000000..52c6a6c
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/drawables.xml
@@ -0,0 +1,8 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <item name="ic_menu_camera" type="drawable">@android:drawable/ic_menu_camera</item>
+ <item name="ic_menu_gallery" type="drawable">@android:drawable/ic_menu_gallery</item>
+ <item name="ic_menu_slideshow" type="drawable">@android:drawable/ic_menu_slideshow</item>
+ <item name="ic_menu_manage" type="drawable">@android:drawable/ic_menu_manage</item>
+ <item name="ic_menu_share" type="drawable">@android:drawable/ic_menu_share</item>
+ <item name="ic_menu_send" type="drawable">@android:drawable/ic_menu_send</item>
+</resources>
diff --git a/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/strings.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/strings.xml.ftl
new file mode 100644
index 0000000..2e0cebb
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res-buildApi22/values/strings.xml.ftl
@@ -0,0 +1,10 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+
+ <string name="navigation_drawer_open">Open navigation drawer</string>
+ <string name="navigation_drawer_close">Close navigation drawer</string>
+
+ <string name="action_settings">Settings</string>
+</resources>
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png b/templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
rename to templates/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
diff --git a/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
new file mode 100644
index 0000000..5c902cf
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
@@ -0,0 +1,26 @@
+<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
+<android.support.v4.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${relativePackage}.${activityClass}">
+
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. -->
+ <FrameLayout
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- The drawer is given a fixed width in dp and extends the full height of
+ the container. -->
+ <fragment android:id="@+id/navigation_drawer"
+ android:layout_width="@dimen/navigation_drawer_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="<#if buildApi gte 17>start<#else>left</#if>"
+ android:name="${packageName}.NavigationDrawerFragment"
+ tools:layout="@layout/${navigationDrawerLayout}" />
+
+</android.support.v4.widget.DrawerLayout>
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
rename to templates/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
similarity index 100%
copy from base/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
copy to templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
rename to templates/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
rename to templates/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
diff --git a/base/templates/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml b/templates/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml
rename to templates/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
rename to templates/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl b/templates/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
rename to templates/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
diff --git a/templates/activities/NavigationDrawerActivity/root/src-buildApi22/app_package/DrawerActivity.java.ftl b/templates/activities/NavigationDrawerActivity/root/src-buildApi22/app_package/DrawerActivity.java.ftl
new file mode 100644
index 0000000..cd1c3c2
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/src-buildApi22/app_package/DrawerActivity.java.ftl
@@ -0,0 +1,105 @@
+package ${packageName};
+
+import android.os.Bundle;
+<#if hasAppBar>
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.view.View;
+</#if>
+import android.support.design.widget.NavigationView;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+
+public class ${activityClass} extends ${superClass}
+ implements NavigationView.OnNavigationItemSelectedListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+<#if hasAppBar>
+
+ FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ });
+</#if>
+
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+ this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ drawer.setDrawerListener(toggle);
+ toggle.syncState();
+
+ NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+ navigationView.setNavigationItemSelectedListener(this);
+ }
+
+ @Override
+ public void onBackPressed() {
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ if (drawer.isDrawerOpen(GravityCompat.START)) {
+ drawer.closeDrawer(GravityCompat.START);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @SuppressWarnings("StatementWithEmptyBody")
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ // Handle navigation view item clicks here.
+ int id = item.getItemId();
+
+ if (id == R.id.nav_camera) {
+ // Handle the camera action
+ } else if (id == R.id.nav_gallery) {
+
+ } else if (id == R.id.nav_slideshow) {
+
+ } else if (id == R.id.nav_manage) {
+
+ } else if (id == R.id.nav_share) {
+
+ } else if (id == R.id.nav_send) {
+
+ }
+
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ drawer.closeDrawer(GravityCompat.START);
+ return true;
+ }
+}
diff --git a/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
new file mode 100644
index 0000000..81134eb
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
@@ -0,0 +1,87 @@
+package ${packageName};
+
+import android.app.Activity;
+<#if appCompat>import ${superClassFqcn};</#if>
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+import android.<#if appCompat>support.v4.</#if>app.FragmentManager;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.support.v4.widget.DrawerLayout;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+public class ${activityClass} extends ${superClass}
+ implements NavigationDrawerFragment.NavigationDrawerCallbacks {
+
+ /**
+ * Fragment managing the behaviors, interactions and presentation of the navigation drawer.
+ */
+ private NavigationDrawerFragment mNavigationDrawerFragment;
+
+ /**
+ * Used to store the last screen title. For use in {@link #restoreActionBar()}.
+ */
+ private CharSequence mTitle;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+
+ mNavigationDrawerFragment = (NavigationDrawerFragment)
+ get${Support}FragmentManager().findFragmentById(R.id.navigation_drawer);
+ mTitle = getTitle();
+
+ // Set up the drawer.
+ mNavigationDrawerFragment.setUp(
+ R.id.navigation_drawer,
+ (DrawerLayout) findViewById(R.id.drawer_layout));
+ }
+
+ @Override
+ public void onNavigationDrawerItemSelected(int position) {
+ // update the main content by replacing fragments
+ FragmentManager fragmentManager = get${Support}FragmentManager();
+ fragmentManager.beginTransaction()
+ .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
+ .commit();
+ }
+
+ public void onSectionAttached(int number) {
+ switch (number) {
+ case 1:
+ mTitle = getString(R.string.title_section1);
+ break;
+ case 2:
+ mTitle = getString(R.string.title_section2);
+ break;
+ case 3:
+ mTitle = getString(R.string.title_section3);
+ break;
+ }
+ }
+
+ public void restoreActionBar() {
+ ActionBar actionBar = get${Support}ActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setTitle(mTitle);
+ }
+
+<#if newProject!false>
+ <#include "include_options_menu.java.ftl">
+</#if>
+ <#include "include_fragment.java.ftl">
+
+}
diff --git a/base/templates/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
rename to templates/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
rename to templates/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl b/templates/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
rename to templates/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
diff --git a/templates/activities/NavigationDrawerActivity/template.xml b/templates/activities/NavigationDrawerActivity/template.xml
new file mode 100644
index 0000000..fb8d500
--- /dev/null
+++ b/templates/activities/NavigationDrawerActivity/template.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="6"
+ name="Navigation Drawer Activity"
+ minApi="7"
+ minBuildApi="14"
+ requireAppTheme="true"
+ description="Creates a new Activity with a Navigation Drawer.">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="navigationDrawerLayout"
+ name="Navigation Drawer Fragment Name"
+ type="string"
+ visibility="false"
+ suggest="${activityToLayout(activityClass, 'drawer')}"
+ constraints="layout|unique"
+ default="fragment_navigation_drawer"/>
+
+ <parameter
+ id="fragmentLayoutName"
+ name="Fragment Layout Name"
+ type="string"
+ visibility="false"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass, 'fragment')}"
+ default="fragment_main"
+ help="The name of the layout to create for the activity's content fragment"/>
+
+ <parameter
+ id="appBarLayoutName"
+ name="App Bar Layout Name"
+ type="string"
+ constraints="layout|unique"
+ suggest="${activityToLayout(activityClass, 'app_bar')}"
+ default="app_bar_main"
+ visibility="false"
+ help="The name of the App Bar layout to create for the activity" />
+
+ <parameter
+ id="navHeaderLayoutName"
+ name="Navigation Header Layout Name"
+ type="string"
+ constraints="layout|unique"
+ suggest="${activityToLayout(activityClass, 'nav_header')}"
+ default="nav_header_main"
+ visibility="false"
+ help="The name of the Navigation header layout to create for the activity" />
+
+ <parameter
+ id="drawerMenu"
+ name="Drawer Menu Name"
+ type="string"
+ constraints="layout|unique"
+ suggest="${layoutName}_drawer"
+ default="activity_main_drawer"
+ visibility="false"
+ help="The name of the Drawer menu to create for the activity" />
+
+ <parameter
+ id="contentLayoutName"
+ name="Content Layout Name"
+ type="string"
+ constraints="layout|unique"
+ suggest="${activityToLayout(activityClass, 'content')}"
+ default="content_main"
+ visibility="false"
+ help="The name of the content layout to create for the activity" />
+
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity_drawer.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/NavigationDrawerActivity/template_blank_activity_drawer.png b/templates/activities/NavigationDrawerActivity/template_blank_activity_drawer.png
new file mode 100644
index 0000000..e5dd37f
Binary files /dev/null and b/templates/activities/NavigationDrawerActivity/template_blank_activity_drawer.png differ
diff --git a/templates/activities/ScrollActivity/globals.xml.ftl b/templates/activities/ScrollActivity/globals.xml.ftl
new file mode 100644
index 0000000..3529cf8
--- /dev/null
+++ b/templates/activities/ScrollActivity/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "../common/common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/ScrollActivity/recipe.xml.ftl b/templates/activities/ScrollActivity/recipe.xml.ftl
new file mode 100644
index 0000000..cad52f5
--- /dev/null
+++ b/templates/activities/ScrollActivity/recipe.xml.ftl
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<recipe>
+<#if appCompat && !(hasDependency('com.android.support:appcompat-v7'))>
+ <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+"/>
+</#if>
+<#if hasAppBar && !(hasDependency('com.android.support:design'))>
+ <dependency mavenUrl="com.android.support:design:${buildApi}.+"/>
+</#if>
+<#if !appCompat && !(hasDependency('com.android.support:support-v4'))>
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+" />
+</#if>
+
+ <#include "../common/recipe_manifest.xml.ftl" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+<#if hasAppBar>
+ <#include "../common/recipe_no_actionbar.xml.ftl" />
+ <#include "../common/recipe_simple_menu.xml.ftl" />
+ <instantiate from="root/res/layout/app_bar.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <instantiate from="root/res/layout/simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${simpleLayoutName}.xml" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/${simpleLayoutName}.xml" />
+<#else>
+ <instantiate from="root/res/layout/simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</#if>
+
+ <instantiate from="root/src/app_package/ScrollActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+</recipe>
diff --git a/templates/activities/ScrollActivity/root/res/layout/app_bar.xml.ftl b/templates/activities/ScrollActivity/root/res/layout/app_bar.xml.ftl
new file mode 100644
index 0000000..f28aad2
--- /dev/null
+++ b/templates/activities/ScrollActivity/root/res/layout/app_bar.xml.ftl
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context="${relativePackage}.${activityClass}">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:fitsSystemWindows="true"
+ android:layout_height="@dimen/app_bar_height"
+ android:layout_width="match_parent"
+ android:theme="@style/${themeNameAppBarOverlay}">
+
+ <android.support.design.widget.CollapsingToolbarLayout
+ android:id="@+id/toolbar_layout"
+ android:fitsSystemWindows="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed"
+ app:contentScrim="?attr/colorPrimary">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ app:layout_collapseMode="pin"
+ app:popupTheme="@style/${themeNamePopupOverlay}" />
+
+ </android.support.design.widget.CollapsingToolbarLayout>
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/${simpleLayoutName}" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/fab_margin"
+ app:layout_anchor="@id/app_bar"
+ app:layout_anchorGravity="bottom|end"
+ android:src="@android:drawable/ic_dialog_email" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/templates/activities/ScrollActivity/root/res/layout/simple.xml.ftl b/templates/activities/ScrollActivity/root/res/layout/simple.xml.ftl
new file mode 100644
index 0000000..235db21
--- /dev/null
+++ b/templates/activities/ScrollActivity/root/res/layout/simple.xml.ftl
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.NestedScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+<#if hasAppBar && layoutName??>
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:showIn="@layout/${layoutName}"
+</#if>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${relativePackage}.${activityClass}">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/text_margin"
+ android:text="@string/large_text" />
+
+</android.support.v4.widget.NestedScrollView>
diff --git a/templates/activities/ScrollActivity/root/res/values/dimens.xml.ftl b/templates/activities/ScrollActivity/root/res/values/dimens.xml.ftl
new file mode 100644
index 0000000..a199458
--- /dev/null
+++ b/templates/activities/ScrollActivity/root/res/values/dimens.xml.ftl
@@ -0,0 +1,7 @@
+<resources>
+<#if hasAppBar>
+ <dimen name="app_bar_height">180dp</dimen>
+ <dimen name="fab_margin">16dp</dimen>
+</#if>
+ <dimen name="text_margin">16dp</dimen>
+</resources>
diff --git a/templates/activities/ScrollActivity/root/res/values/strings.xml.ftl b/templates/activities/ScrollActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..fd86fac
--- /dev/null
+++ b/templates/activities/ScrollActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,94 @@
+<resources>
+<#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+</#if>
+ <string name="large_text">
+"Material is the metaphor.\n\n"
+
+"A material metaphor is the unifying theory of a rationalized space and a system of motion."
+"The material is grounded in tactile reality, inspired by the study of paper and ink, yet "
+"technologically advanced and open to imagination and magic.\n"
+"Surfaces and edges of the material provide visual cues that are grounded in reality. The "
+"use of familiar tactile attributes helps users quickly understand affordances. Yet the "
+"flexibility of the material creates new affordances that supercede those in the physical "
+"world, without breaking the rules of physics.\n"
+"The fundamentals of light, surface, and movement are key to conveying how objects move, "
+"interact, and exist in space and in relation to each other. Realistic lighting shows "
+"seams, divides space, and indicates moving parts.\n\n"
+
+"Bold, graphic, intentional.\n\n"
+
+"The foundational elements of print based design typography, grids, space, scale, color, "
+"and use of imagery guide visual treatments. These elements do far more than please the "
+"eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge "
+"imagery, large scale typography, and intentional white space create a bold and graphic "
+"interface that immerse the user in the experience.\n"
+"An emphasis on user actions makes core functionality immediately apparent and provides "
+"waypoints for the user.\n\n"
+
+"Motion provides meaning.\n\n"
+
+"Motion respects and reinforces the user as the prime mover. Primary user actions are "
+"inflection points that initiate motion, transforming the whole design.\n"
+"All action takes place in a single environment. Objects are presented to the user without "
+"breaking the continuity of experience even as they transform and reorganize.\n"
+"Motion is meaningful and appropriate, serving to focus attention and maintain continuity. "
+"Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n"
+
+"3D world.\n\n"
+
+"The material environment is a 3D space, which means all objects have x, y, and z "
+"dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the "
+"positive z-axis extending towards the viewer. Every sheet of material occupies a single "
+"position along the z-axis and has a standard 1dp thickness.\n"
+"On the web, the z-axis is used for layering and not for perspective. The 3D world is "
+"emulated by manipulating the y-axis.\n\n"
+
+"Light and shadow.\n\n"
+
+"Within the material environment, virtual lights illuminate the scene. Key lights create "
+"directional shadows, while ambient light creates soft shadows from all angles.\n"
+"Shadows in the material environment are cast by these two light sources. In Android "
+"development, shadows occur when light sources are blocked by sheets of material at "
+"various positions along the z-axis. On the web, shadows are depicted by manipulating the "
+"y-axis only. The following example shows the card with a height of 6dp.\n\n"
+
+"Resting elevation.\n\n"
+
+"All material objects, regardless of size, have a resting elevation, or default elevation "
+"that does not change. If an object changes elevation, it should return to its resting "
+"elevation as soon as possible.\n\n"
+
+"Component elevations.\n\n"
+
+"The resting elevation for a component type is consistent across apps (e.g., FAB elevation "
+"does not vary from 6dp in one app to 16dp in another app).\n"
+"Components may have different resting elevations across platforms, depending on the depth "
+"of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n"
+
+"Responsive elevation and dynamic elevation offsets.\n\n"
+
+"Some component types have responsive elevation, meaning they change elevation in response "
+"to user input (e.g., normal, focused, and pressed) or system events. These elevation "
+"changes are consistently implemented using dynamic elevation offsets.\n"
+"Dynamic elevation offsets are the goal elevation that a component moves towards, relative "
+"to the component’s resting state. They ensure that elevation changes are consistent "
+"across actions and component types. For example, all components that lift on press have "
+"the same elevation change relative to their resting elevation.\n"
+"Once the input event is completed or cancelled, the component will return to its resting "
+"elevation.\n\n"
+
+"Avoiding elevation interference.\n\n"
+
+"Components with responsive elevations may encounter other components as they move between "
+"their resting elevations and dynamic elevation offsets. Because material cannot pass "
+"through other material, components avoid interfering with one another any number of ways, "
+"whether on a per component basis or using the entire app layout.\n"
+"On a component level, components can move or be removed before they cause interference. "
+"For example, a floating action button (FAB) can disappear or move off screen before a "
+"user picks up a card, or it can move if a snackbar appears.\n"
+"On the layout level, design your app layout to minimize opportunities for interference. "
+"For example, position the FAB to one side of stream of a cards so the FAB won’t interfere "
+"when a user tries to pick up one of cards.\n\n"
+</string>
+</resources>
diff --git a/templates/activities/ScrollActivity/root/src/app_package/ScrollActivity.java.ftl b/templates/activities/ScrollActivity/root/src/app_package/ScrollActivity.java.ftl
new file mode 100644
index 0000000..3d1db98
--- /dev/null
+++ b/templates/activities/ScrollActivity/root/src/app_package/ScrollActivity.java.ftl
@@ -0,0 +1,74 @@
+package ${packageName};
+
+import android.os.Bundle;
+<#if hasAppBar>
+<#if buildApi == 22>
+import android.support.design.widget.CollapsingToolbarLayout;
+</#if>
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+<#else>
+import ${superClassFqcn};
+</#if>
+<#if isNewProject>
+import android.view.Menu;
+import android.view.MenuItem;
+</#if>
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+public class ${activityClass} extends ${superClass} {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+<#if hasAppBar>
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ <#if buildApi == 22>
+ CollapsingToolbarLayout toolBarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);
+ toolBarLayout.setTitle(getTitle());
+ </#if>
+
+ FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ });
+</#if>
+<#if parentActivityClass != "">
+ get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
+</#if>
+ }
+<#if isNewProject>
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+</#if>
+}
diff --git a/templates/activities/ScrollActivity/template.xml b/templates/activities/ScrollActivity/template.xml
new file mode 100644
index 0000000..de19de4
--- /dev/null
+++ b/templates/activities/ScrollActivity/template.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="1"
+ name="Scrolling Activity"
+ minApi="7"
+ minBuildApi="22"
+ requireAppTheme="true"
+ description="Creates a new vertical scrolling activity.">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="ScrollingActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_scrolling"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="ScrollingActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="menuName"
+ name="Menu Resource Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="menu_${classToResource(activityClass)}"
+ visibility="isNewProject!false"
+ default="menu_scrolling"
+ help="The name of the resource file to create for the menu items" />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="simpleLayoutName"
+ name="Content Layout Name"
+ type="string"
+ constraints="layout|unique"
+ suggest="${activityToLayout(activityClass, 'content')}"
+ default="content_scrolling"
+ visibility="false"
+ help="The name of the content layout to create for the activity" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_scroll_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/ScrollActivity/template_scroll_activity.png b/templates/activities/ScrollActivity/template_scroll_activity.png
new file mode 100644
index 0000000..c419b11
Binary files /dev/null and b/templates/activities/ScrollActivity/template_scroll_activity.png differ
diff --git a/templates/activities/SettingsActivity/globals.xml.ftl b/templates/activities/SettingsActivity/globals.xml.ftl
new file mode 100644
index 0000000..5c467dc
--- /dev/null
+++ b/templates/activities/SettingsActivity/globals.xml.ftl
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="hasNoActionBar" type="boolean" value="false" />
+ <#include "../common/common_globals.xml.ftl" />
+ <global id="isLauncher" type="boolean" value="${isNewProject?string}" />
+ <global id="includeImageDrawables" type="boolean" value="${(minApi?number lt 21)?string}" />
+<#if appCompatActivity>
+ <global id="preferenceSuperClass" type="string" value="AppCompatPreferenceActivity" />
+ <global id="PreferenceSupport" type = "string" value="Support" />
+ <global id="PreferenceActionBarClassFqcn" type = "string" value="android.support.v7.app.ActionBar" />
+<#else>
+ <global id="preferenceSuperClass" type="string" value="PreferenceActivity" />
+ <global id="PreferenceSupport" type = "string" value="" />
+ <global id="PreferenceActionBarClassFqcn" type = "string" value="android.app.ActionBar" />
+</#if>
+</globals>
diff --git a/templates/activities/SettingsActivity/recipe.xml.ftl b/templates/activities/SettingsActivity/recipe.xml.ftl
new file mode 100644
index 0000000..3d8b622
--- /dev/null
+++ b/templates/activities/SettingsActivity/recipe.xml.ftl
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+" />
+
+ <#include "../common/recipe_manifest.xml.ftl" />
+
+ <copy from="root/res/xml/pref_data_sync.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_data_sync.xml" />
+ <copy from="root/res/xml/pref_general.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_general.xml" />
+ <copy from="root/res/xml/pref_notification.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_notification.xml" />
+ <instantiate from="root/res/xml/pref_headers.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_headers.xml" />
+
+ <copy from="root/res/drawable-v21"
+ to="${escapeXmlAttribute(resOut)}/drawable<#if includeImageDrawables>-v21</#if>" />
+
+ <merge from="root/res/values/pref_strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/src/app_package/SettingsActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+<#if appCompatActivity>
+ <instantiate from="root/src/app_package/AppCompatPreferenceActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/AppCompatPreferenceActivity.java" />
+</#if>
+
+<#if includeImageDrawables>
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ <copy from="root/res/drawable-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
+ <copy from="root/res/drawable-xxxhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xxxhdpi" />
+</#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+</recipe>
diff --git a/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_info_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_info_black_24dp.png
new file mode 100755
index 0000000..da56077
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_info_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_notifications_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_notifications_black_24dp.png
new file mode 100755
index 0000000..e200012
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_notifications_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_sync_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_sync_black_24dp.png
new file mode 100755
index 0000000..a5ebdbd
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-hdpi/ic_sync_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_info_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_info_black_24dp.png
new file mode 100755
index 0000000..5ef3dc0
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_info_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_notifications_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_notifications_black_24dp.png
new file mode 100755
index 0000000..b36475d
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_notifications_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_sync_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_sync_black_24dp.png
new file mode 100755
index 0000000..9685e8e
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-mdpi/ic_sync_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-v21/ic_info_black_24dp.xml b/templates/activities/SettingsActivity/root/res/drawable-v21/ic_info_black_24dp.xml
new file mode 100644
index 0000000..8024b5b
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/res/drawable-v21/ic_info_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/>
+</vector>
diff --git a/templates/activities/SettingsActivity/root/res/drawable-v21/ic_notifications_black_24dp.xml b/templates/activities/SettingsActivity/root/res/drawable-v21/ic_notifications_black_24dp.xml
new file mode 100644
index 0000000..14f20f9
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/res/drawable-v21/ic_notifications_black_24dp.xml
@@ -0,0 +1,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">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z"/>
+</vector>
diff --git a/templates/activities/SettingsActivity/root/res/drawable-v21/ic_sync_black_24dp.xml b/templates/activities/SettingsActivity/root/res/drawable-v21/ic_sync_black_24dp.xml
new file mode 100644
index 0000000..ab79101
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/res/drawable-v21/ic_sync_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z"/>
+</vector>
\ No newline at end of file
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_info_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_info_black_24dp.png
new file mode 100755
index 0000000..46ed12a
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_info_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_notifications_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_notifications_black_24dp.png
new file mode 100755
index 0000000..7de8581
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_notifications_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_sync_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_sync_black_24dp.png
new file mode 100755
index 0000000..860a5db
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xhdpi/ic_sync_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_info_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_info_black_24dp.png
new file mode 100755
index 0000000..a81eeb9
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_info_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_notifications_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_notifications_black_24dp.png
new file mode 100755
index 0000000..ab8a9c4
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_notifications_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_sync_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_sync_black_24dp.png
new file mode 100755
index 0000000..f799008
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xxhdpi/ic_sync_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_info_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_info_black_24dp.png
new file mode 100755
index 0000000..c8f86b9
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_info_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_notifications_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_notifications_black_24dp.png
new file mode 100755
index 0000000..86f89d7
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_notifications_black_24dp.png differ
diff --git a/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_sync_black_24dp.png b/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_sync_black_24dp.png
new file mode 100755
index 0000000..b9f56f3
Binary files /dev/null and b/templates/activities/SettingsActivity/root/res/drawable-xxxhdpi/ic_sync_black_24dp.png differ
diff --git a/base/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl b/templates/activities/SettingsActivity/root/res/values/pref_strings.xml.ftl
similarity index 100%
rename from base/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl
rename to templates/activities/SettingsActivity/root/res/values/pref_strings.xml.ftl
diff --git a/base/templates/activities/SettingsActivity/root/res/xml/pref_data_sync.xml b/templates/activities/SettingsActivity/root/res/xml/pref_data_sync.xml
similarity index 100%
rename from base/templates/activities/SettingsActivity/root/res/xml/pref_data_sync.xml
rename to templates/activities/SettingsActivity/root/res/xml/pref_data_sync.xml
diff --git a/templates/activities/SettingsActivity/root/res/xml/pref_general.xml b/templates/activities/SettingsActivity/root/res/xml/pref_general.xml
new file mode 100644
index 0000000..d7cfdc5
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/res/xml/pref_general.xml
@@ -0,0 +1,33 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <SwitchPreference
+ android:key="example_switch"
+ android:title="@string/pref_title_social_recommendations"
+ android:summary="@string/pref_description_social_recommendations"
+ android:defaultValue="true" />
+
+ <!-- NOTE: EditTextPreference accepts EditText attributes. -->
+ <!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
+ <EditTextPreference
+ android:key="example_text"
+ android:title="@string/pref_title_display_name"
+ android:defaultValue="@string/pref_default_display_name"
+ android:selectAllOnFocus="true"
+ android:inputType="textCapWords"
+ android:capitalize="words"
+ android:singleLine="true"
+ android:maxLines="1" />
+
+ <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
+ dismiss it. -->
+ <!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
+ <ListPreference
+ android:key="example_list"
+ android:title="@string/pref_title_add_friends_to_messages"
+ android:defaultValue="-1"
+ android:entries="@array/pref_example_list_titles"
+ android:entryValues="@array/pref_example_list_values"
+ android:negativeButtonText="@null"
+ android:positiveButtonText="@null" />
+
+</PreferenceScreen>
diff --git a/templates/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl b/templates/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl
new file mode 100644
index 0000000..00cd9b0
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl
@@ -0,0 +1,20 @@
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- These settings headers are only used on tablets. -->
+
+ <header
+ android:fragment="${packageName}.${activityClass}$GeneralPreferenceFragment"
+ android:title="@string/pref_header_general"
+ android:icon="@drawable/ic_info_black_24dp" />
+
+ <header
+ android:fragment="${packageName}.${activityClass}$NotificationPreferenceFragment"
+ android:title="@string/pref_header_notifications"
+ android:icon="@drawable/ic_notifications_black_24dp" />
+
+ <header
+ android:fragment="${packageName}.${activityClass}$DataSyncPreferenceFragment"
+ android:title="@string/pref_header_data_sync"
+ android:icon="@drawable/ic_sync_black_24dp" />
+
+</preference-headers>
diff --git a/templates/activities/SettingsActivity/root/res/xml/pref_notification.xml b/templates/activities/SettingsActivity/root/res/xml/pref_notification.xml
new file mode 100644
index 0000000..298ecfb
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/res/xml/pref_notification.xml
@@ -0,0 +1,27 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- A 'parent' preference, which enables/disables child preferences (below)
+ when checked/unchecked. -->
+ <SwitchPreference
+ android:key="notifications_new_message"
+ android:title="@string/pref_title_new_message_notifications"
+ android:defaultValue="true" />
+
+ <!-- Allows the user to choose a ringtone in the 'notification' category. -->
+ <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
+ <!-- NOTE: RingtonePreference's summary should be set to its value by the activity code. -->
+ <RingtonePreference
+ android:dependency="notifications_new_message"
+ android:key="notifications_new_message_ringtone"
+ android:title="@string/pref_title_ringtone"
+ android:ringtoneType="notification"
+ android:defaultValue="content://settings/system/notification_sound" />
+
+ <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
+ <SwitchPreference
+ android:dependency="notifications_new_message"
+ android:key="notifications_new_message_vibrate"
+ android:title="@string/pref_title_vibrate"
+ android:defaultValue="true" />
+
+</PreferenceScreen>
diff --git a/templates/activities/SettingsActivity/root/src/app_package/AppCompatPreferenceActivity.java.ftl b/templates/activities/SettingsActivity/root/src/app_package/AppCompatPreferenceActivity.java.ftl
new file mode 100644
index 0000000..09a26c4
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/src/app_package/AppCompatPreferenceActivity.java.ftl
@@ -0,0 +1,109 @@
+package ${packageName};
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+
+ private AppCompatDelegate mDelegate;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getDelegate().installViewFactory();
+ getDelegate().onCreate(savedInstanceState);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
+ }
+
+ public ActionBar getSupportActionBar() {
+ return getDelegate().getSupportActionBar();
+ }
+
+ public void setSupportActionBar(@Nullable Toolbar toolbar) {
+ getDelegate().setSupportActionBar(toolbar);
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return getDelegate().getMenuInflater();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResID) {
+ getDelegate().setContentView(layoutResID);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ getDelegate().setContentView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().setContentView(view, params);
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().addContentView(view, params);
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ getDelegate().onPostResume();
+ }
+
+ @Override
+ protected void onTitleChanged(CharSequence title, int color) {
+ super.onTitleChanged(title, color);
+ getDelegate().setTitle(title);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ getDelegate().onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ getDelegate().onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getDelegate().onDestroy();
+ }
+
+ public void invalidateOptionsMenu() {
+ getDelegate().invalidateOptionsMenu();
+ }
+
+ private AppCompatDelegate getDelegate() {
+ if (mDelegate == null) {
+ mDelegate = AppCompatDelegate.create(this, null);
+ }
+ return mDelegate;
+ }
+}
diff --git a/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl b/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
new file mode 100644
index 0000000..b605ae8
--- /dev/null
+++ b/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
@@ -0,0 +1,350 @@
+package ${packageName};
+
+<#assign includeSimple=(minApiLevel?? && minApiLevel lt 10)>
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+<#if includeSimple>
+import android.preference.PreferenceCategory;
+</#if>
+import ${PreferenceActionBarClassFqcn};
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.RingtonePreference;
+import android.text.TextUtils;
+import android.view.MenuItem;
+<#if parentActivityClass != "">
+import android.support.v4.app.NavUtils;
+</#if>
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+import java.util.List;
+
+/**
+ * A {@link PreferenceActivity} that presents a set of application settings. On
+ * handset devices, settings are presented as a single list. On tablets,
+ * settings are split by category, with category headers shown to the left of
+ * the list of settings.
+ * <p>
+ * See <a href="http://developer.android.com/design/patterns/settings.html">
+ * Android Design: Settings</a> for design guidelines and the <a
+ * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
+ * API Guide</a> for more information on developing a Settings UI.
+ */
+public class ${activityClass} extends ${preferenceSuperClass} {
+ <#if includeSimple>
+ /**
+ * Determines whether to always show the simplified settings UI, where
+ * settings are presented in a single list. When false, settings are shown
+ * as a master/detail two-pane view on tablets. When true, a single pane is
+ * shown on tablets.
+ */
+ private static final boolean ALWAYS_SIMPLE_PREFS = false;
+
+ </#if>
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setupActionBar();
+ }
+
+ /**
+ * Set up the {@link android.app.ActionBar}, if the API is available.
+ */
+ private void setupActionBar() {
+ ActionBar actionBar = get${PreferenceSupport}ActionBar();
+ if (actionBar != null) {
+ // Show the Up button in the action bar.
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ <#if parentActivityClass != "">
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ if (!super.onMenuItemSelected(featureId, item)) {
+ NavUtils.navigateUpFromSameTask(this);
+ }
+ return true;
+ }
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ </#if>
+ <#if includeSimple>
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+
+ setupSimplePreferencesScreen();
+ }
+
+ /**
+ * Shows the simplified settings UI if the device configuration if the
+ * device configuration dictates that a simplified, single-pane UI should be
+ * shown.
+ */
+ private void setupSimplePreferencesScreen() {
+ if (!isSimplePreferences(this)) {
+ return;
+ }
+
+ // In the simplified UI, fragments are not used at all and we instead
+ // use the older PreferenceActivity APIs.
+
+ // Add 'general' preferences.
+ addPreferencesFromResource(R.xml.pref_general);
+
+ // Add 'notifications' preferences, and a corresponding header.
+ PreferenceCategory fakeHeader = new PreferenceCategory(this);
+ fakeHeader.setTitle(R.string.pref_header_notifications);
+ getPreferenceScreen().addPreference(fakeHeader);
+ addPreferencesFromResource(R.xml.pref_notification);
+
+ // Add 'data and sync' preferences, and a corresponding header.
+ fakeHeader = new PreferenceCategory(this);
+ fakeHeader.setTitle(R.string.pref_header_data_sync);
+ getPreferenceScreen().addPreference(fakeHeader);
+ addPreferencesFromResource(R.xml.pref_data_sync);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences to
+ // their values. When their values change, their summaries are updated
+ // to reflect the new value, per the Android Design guidelines.
+ bindPreferenceSummaryToValue(findPreference("example_text"));
+ bindPreferenceSummaryToValue(findPreference("example_list"));
+ bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
+ bindPreferenceSummaryToValue(findPreference("sync_frequency"));
+ }
+ </#if>
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onIsMultiPane() {
+ return isXLargeTablet(this)<#if includeSimple> && !isSimplePreferences(this)</#if>;
+ }
+
+ /**
+ * Helper method to determine if the device has an extra-large screen. For
+ * example, 10" tablets are extra-large.
+ */
+ private static boolean isXLargeTablet(Context context) {
+ return (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ }
+
+ <#if includeSimple>
+ /**
+ * Determines whether the simplified settings UI should be shown. This is
+ * true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
+ * doesn't have newer APIs like {@link PreferenceFragment}, or the device
+ * doesn't have an extra-large screen. In these cases, a single-pane
+ * "simplified" settings UI should be shown.
+ */
+ private static boolean isSimplePreferences(Context context) {
+ return ALWAYS_SIMPLE_PREFS
+ || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
+ || !isXLargeTablet(context);
+ }
+
+ </#if>
+ /** {@inheritDoc} */
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void onBuildHeaders(List<Header> target) {
+ <#if includeSimple>
+ if (!isSimplePreferences(this)) {
+ loadHeadersFromResource(R.xml.pref_headers, target);
+ }
+ <#else>
+ loadHeadersFromResource(R.xml.pref_headers, target);
+ </#if>
+ }
+
+ /**
+ * A preference value change listener that updates the preference's summary
+ * to reflect its new value.
+ */
+ private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ String stringValue = value.toString();
+
+ if (preference instanceof ListPreference) {
+ // For list preferences, look up the correct display value in
+ // the preference's 'entries' list.
+ ListPreference listPreference = (ListPreference) preference;
+ int index = listPreference.findIndexOfValue(stringValue);
+
+ // Set the summary to reflect the new value.
+ preference.setSummary(
+ index >= 0
+ ? listPreference.getEntries()[index]
+ : null);
+
+ } else if (preference instanceof RingtonePreference) {
+ // For ringtone preferences, look up the correct display value
+ // using RingtoneManager.
+ if (TextUtils.isEmpty(stringValue)) {
+ // Empty values correspond to 'silent' (no ringtone).
+ preference.setSummary(R.string.pref_ringtone_silent);
+
+ } else {
+ Ringtone ringtone = RingtoneManager.getRingtone(
+ preference.getContext(), Uri.parse(stringValue));
+
+ if (ringtone == null) {
+ // Clear the summary if there was a lookup error.
+ preference.setSummary(null);
+ } else {
+ // Set the summary to reflect the new ringtone display
+ // name.
+ String name = ringtone.getTitle(preference.getContext());
+ preference.setSummary(name);
+ }
+ }
+
+ } else {
+ // For all other preferences, set the summary to the value's
+ // simple string representation.
+ preference.setSummary(stringValue);
+ }
+ return true;
+ }
+ };
+
+ /**
+ * Binds a preference's summary to its value. More specifically, when the
+ * preference's value is changed, its summary (line of text below the
+ * preference title) is updated to reflect the value. The summary is also
+ * immediately updated upon calling this method. The exact display format is
+ * dependent on the type of preference.
+ *
+ * @see #sBindPreferenceSummaryToValueListener
+ */
+ private static void bindPreferenceSummaryToValue(Preference preference) {
+ // Set the listener to watch for value changes.
+ preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
+
+ // Trigger the listener immediately with the preference's
+ // current value.
+ sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
+ PreferenceManager
+ .getDefaultSharedPreferences(preference.getContext())
+ .getString(preference.getKey(), ""));
+ }
+
+ /**
+ * This method stops fragment injection in malicious applications.
+ * Make sure to deny any unknown fragments here.
+ */
+ protected boolean isValidFragment(String fragmentName) {
+ return PreferenceFragment.class.getName().equals(fragmentName)
+ || GeneralPreferenceFragment.class.getName().equals(fragmentName)
+ || DataSyncPreferenceFragment.class.getName().equals(fragmentName)
+ || NotificationPreferenceFragment.class.getName().equals(fragmentName);
+ }
+
+ /**
+ * This fragment shows general preferences only. It is used when the
+ * activity is showing a two-pane settings UI.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class GeneralPreferenceFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_general);
+ setHasOptionsMenu(true);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+ // to their values. When their values change, their summaries are
+ // updated to reflect the new value, per the Android Design
+ // guidelines.
+ bindPreferenceSummaryToValue(findPreference("example_text"));
+ bindPreferenceSummaryToValue(findPreference("example_list"));
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ startActivity(new Intent(getActivity(), ${activityClass}.class));
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * This fragment shows notification preferences only. It is used when the
+ * activity is showing a two-pane settings UI.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class NotificationPreferenceFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_notification);
+ setHasOptionsMenu(true);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+ // to their values. When their values change, their summaries are
+ // updated to reflect the new value, per the Android Design
+ // guidelines.
+ bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ startActivity(new Intent(getActivity(), ${activityClass}.class));
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * This fragment shows data and sync preferences only. It is used when the
+ * activity is showing a two-pane settings UI.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class DataSyncPreferenceFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_data_sync);
+ setHasOptionsMenu(true);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+ // to their values. When their values change, their summaries are
+ // updated to reflect the new value, per the Android Design
+ // guidelines.
+ bindPreferenceSummaryToValue(findPreference("sync_frequency"));
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ startActivity(new Intent(getActivity(), ${activityClass}.class));
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/templates/activities/SettingsActivity/template.xml b/templates/activities/SettingsActivity/template.xml
new file mode 100644
index 0000000..d49ee94
--- /dev/null
+++ b/templates/activities/SettingsActivity/template.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="5"
+ name="Settings Activity"
+ description="Creates a new application settings activity that presents alternative layouts on handset and tablet-size screens."
+ minApi="4"
+ minBuildApi="11"
+ requireAppTheme="true"
+ category="Activity">
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="SettingsActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="Settings"
+ help="The name of the activity." />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_settings_activity.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/SettingsActivity/template_settings_activity.png b/templates/activities/SettingsActivity/template_settings_activity.png
new file mode 100644
index 0000000..b987061
Binary files /dev/null and b/templates/activities/SettingsActivity/template_settings_activity.png differ
diff --git a/templates/activities/TabbedActivity/globals.xml.ftl b/templates/activities/TabbedActivity/globals.xml.ftl
new file mode 100644
index 0000000..fb7b399
--- /dev/null
+++ b/templates/activities/TabbedActivity/globals.xml.ftl
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="hasViewPager" type="boolean" value="${(features != 'spinner')?string}" />
+ <global id="viewContainer" type="string" value="<#if features == 'spinner'>android.support.v4.widget.NestedScrollView<#else>android.support.v4.view.ViewPager</#if>" />
+ <#include "../common/common_globals.xml.ftl" />
+</globals>
diff --git a/templates/activities/TabbedActivity/recipe.xml.ftl b/templates/activities/TabbedActivity/recipe.xml.ftl
new file mode 100644
index 0000000..353421a
--- /dev/null
+++ b/templates/activities/TabbedActivity/recipe.xml.ftl
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat && !(hasDependency('com.android.support:appcompat-v7'))>
+ <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+"/>
+ </#if>
+
+ <#if !appCompat && !(hasDependency('com.android.support:support-v4'))>
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/>
+ </#if>
+
+ <#if !appCompat && !(hasDependency('com.android.support:support-v13'))>
+ <dependency mavenUrl="com.android.support:support-v13:${buildApi}.+"/>
+ </#if>
+
+ <#if hasAppBar && !(hasDependency('com.android.support:design'))>
+ <dependency mavenUrl="com.android.support:design:${buildApi}.+"/>
+ </#if>
+
+ <#include "../common/recipe_manifest.xml.ftl" />
+
+ <instantiate from="root/res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="root/res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <#if hasAppBar>
+ <#include "../common/recipe_no_actionbar.xml.ftl" />
+ </#if>
+
+ <!-- Decide what kind of layout(s) to add -->
+ <#if hasAppBar>
+ <instantiate from="root/res/layout/app_bar_activity.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <#elseif hasViewPager>
+ <instantiate from="root/res/layout/activity_pager.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <#else>
+ <instantiate from="root/res/layout/activity_fragment_container.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ </#if>
+
+ <instantiate from="root/res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <!-- Decide which activity code to add -->
+ <#if hasViewPager || hasAppBar>
+ <instantiate from="root/src/app_package/TabsAndPagerActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <#else>
+ <instantiate from="root/src/app_package/DropdownActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl b/templates/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl
rename to templates/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl
diff --git a/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl b/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
new file mode 100644
index 0000000..0db858d
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
@@ -0,0 +1,6 @@
+<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${relativePackage}.${activityClass}" />
diff --git a/templates/activities/TabbedActivity/root/res/layout/app_bar_activity.xml.ftl b/templates/activities/TabbedActivity/root/res/layout/app_bar_activity.xml.ftl
new file mode 100644
index 0000000..e9d01e4
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/layout/app_bar_activity.xml.ftl
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/main_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context="${relativePackage}.${activityClass}">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/appbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/appbar_padding_top"
+ android:theme="@style/${themeNameAppBarOverlay}">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ app:popupTheme="@style/${themeNamePopupOverlay}"
+ app:layout_scrollFlags="scroll|enterAlways">
+
+ <#if features == 'spinner'>
+ <Spinner
+ android:id="@+id/spinner"
+ app:popupTheme="@style/${themeNamePopupOverlay}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </#if>
+ </android.support.v7.widget.Toolbar>
+
+ <#if features == 'tabs'>
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </#if>
+ </android.support.design.widget.AppBarLayout>
+
+ <${viewContainer}
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|bottom"
+ android:layout_margin="@dimen/fab_margin"
+ android:src="@android:drawable/ic_dialog_email" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl b/templates/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
rename to templates/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
diff --git a/base/templates/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl b/templates/activities/TabbedActivity/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl
rename to templates/activities/TabbedActivity/root/res/menu/main.xml.ftl
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/root/res/values-w820dp/dimens.xml b/templates/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/activities/GoogleAdMobAdsActivity/root/res/values-w820dp/dimens.xml
rename to templates/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
diff --git a/templates/activities/TabbedActivity/root/res/values/dimens.xml.ftl b/templates/activities/TabbedActivity/root/res/values/dimens.xml.ftl
new file mode 100644
index 0000000..81237f1
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/values/dimens.xml.ftl
@@ -0,0 +1,9 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+<#if hasAppBar>
+ <dimen name="fab_margin">16dp</dimen>
+ <dimen name="appbar_padding_top">8dp</dimen>
+</#if>
+</resources>
diff --git a/templates/activities/TabbedActivity/root/res/values/strings.xml.ftl b/templates/activities/TabbedActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..8d44253
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,7 @@
+<resources>
+ <#if !isNewProject>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+ </#if>
+ <string name="action_settings">Settings</string>
+ <string name="section_format">Hello World from section: %1$d</string>
+</resources>
diff --git a/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
new file mode 100644
index 0000000..a6b6777
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
@@ -0,0 +1,88 @@
+package ${packageName};
+
+import ${superClassFqcn};
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+public class ${activityClass} extends ${superClass} implements ActionBar.OnNavigationListener {
+
+ /**
+ * The serialization (saved instance state) Bundle key representing the
+ * current dropdown position.
+ */
+ private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+
+ // Set up the action bar to show a dropdown list.
+ final ActionBar actionBar = get${Support}ActionBar();
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ <#if parentActivityClass != "">
+ // Show the Up button in the action bar.
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ </#if>
+
+ // Set up the dropdown list navigation in the action bar.
+ actionBar.setListNavigationCallbacks(
+ // Specify a SpinnerAdapter to populate the dropdown list.
+ new ArrayAdapter<String>(
+ actionBar.getThemedContext(),
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ new String[] {
+ "Section 1",
+ "Section 2",
+ "Section 3",
+ }),
+ this);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ // Restore the previously serialized current dropdown position.
+ if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
+ get${Support}ActionBar().setSelectedNavigationItem(
+ savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ // Serialize the current dropdown position.
+ outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
+ get${Support}ActionBar().getSelectedNavigationIndex());
+ }
+
+ <#include "include_options_menu.java.ftl">
+
+ @Override
+ public boolean onNavigationItemSelected(int position, long id) {
+ // When the given dropdown item is selected, show its contents in the
+ // container view.
+ get${Support}FragmentManager().beginTransaction()
+ .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
+ .commit();
+ return true;
+ }
+
+ <#include "include_fragment.java.ftl">
+
+}
diff --git a/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
new file mode 100644
index 0000000..4afd8d2
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
@@ -0,0 +1,276 @@
+package ${packageName};
+
+<#if hasAppBar>
+<#if features == 'tabs'>
+import android.support.design.widget.TabLayout;
+</#if>
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+<#else> <#-- hasAppBar -->
+import ${superClassFqcn};
+import android.<#if appCompat>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat>support.v4.</#if>app.FragmentTransaction;
+</#if> <#-- hasAppBar -->
+import android.<#if appCompat>support.v4.</#if>app.Fragment;
+<#if hasViewPager>
+import android.<#if appCompat>support.v4.</#if>app.FragmentManager;
+import android.support.${(appCompat)?string('v4','v13')}.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+</#if>
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+<#if features == 'spinner'>
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.content.Context;
+<#if buildApi gte 23>
+import android.support.v7.widget.ThemedSpinnerAdapter;
+import android.content.res.Resources.Theme;
+<#else>
+import android.graphics.Color;
+</#if>
+</#if> <#-- features == 'spinner' -->
+import android.widget.TextView;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+public class ${activityClass} extends ${superClass}<#if !hasAppBar && features == 'tabs'> implements ActionBar.TabListener</#if> {
+
+ <#if hasViewPager>
+ /**
+ * The {@link android.support.v4.view.PagerAdapter} that will provide
+ * fragments for each of the sections. We use a
+ * {@link FragmentPagerAdapter} derivative, which will keep every
+ * loaded fragment in memory. If this becomes too memory intensive, it
+ * may be best to switch to a
+ * {@link android.support.${(appCompat)?string('v4','v13')}.app.FragmentStatePagerAdapter}.
+ */
+ private SectionsPagerAdapter mSectionsPagerAdapter;
+
+ /**
+ * The {@link ViewPager} that will host the section contents.
+ */
+ private ViewPager mViewPager;
+
+ </#if>
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+ <#if hasAppBar>
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ </#if>
+ <#if parentActivityClass != "">
+ get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
+ </#if>
+ <#if hasViewPager>
+ // Create the adapter that will return a fragment for each of the three
+ // primary sections of the activity.
+ mSectionsPagerAdapter = new SectionsPagerAdapter(get${Support}FragmentManager());
+
+ // Set up the ViewPager with the sections adapter.
+ mViewPager = (ViewPager) findViewById(R.id.container);
+ mViewPager.setAdapter(mSectionsPagerAdapter);
+
+ </#if>
+ <#if hasAppBar>
+ <#if features == 'tabs'>
+ TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
+ tabLayout.setupWithViewPager(mViewPager);
+ <#elseif features == 'spinner'>
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+
+ // Setup spinner
+ Spinner spinner = (Spinner) findViewById(R.id.spinner);
+ spinner.setAdapter(new MyAdapter(
+ toolbar.getContext(),
+ new String[]{
+ "Section 1",
+ "Section 2",
+ "Section 3",
+ }));
+
+ spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ // When the given dropdown item is selected, show its contents in the
+ // container view.
+ get${Support}FragmentManager().beginTransaction()
+ .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
+ .commit();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+ </#if>
+
+ FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ });
+ <#else> <#-- hasAppBar -->
+ <#if features == 'tabs'>
+ // Set up the action bar.
+ final ActionBar actionBar = get${Support}ActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+ // When swiping between different sections, select the corresponding
+ // tab. We can also use ActionBar.Tab#select() to do this if we have
+ // a reference to the Tab.
+ mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ actionBar.setSelectedNavigationItem(position);
+ }
+ });
+
+ // For each of the sections in the app, add a tab to the action bar.
+ for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
+ // Create a tab with text corresponding to the page title defined by
+ // the adapter. Also specify this Activity object, which implements
+ // the TabListener interface, as the callback (listener) for when
+ // this tab is selected.
+ actionBar.addTab(
+ actionBar.newTab()
+ .setText(mSectionsPagerAdapter.getPageTitle(i))
+ .setTabListener(this));
+ }
+ </#if>
+ </#if> <#-- hasAppBar -->
+ }
+
+ <#include "include_options_menu.java.ftl">
+
+ <#if !hasAppBar && features == 'tabs'>@Override
+ public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ // When the given tab is selected, switch to the corresponding page in
+ // the ViewPager.
+ mViewPager.setCurrentItem(tab.getPosition());
+ }
+
+ @Override
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }
+
+ @Override
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }
+ </#if>
+
+ <#if features == 'spinner'>
+ <#if buildApi gte 23>
+ private static class MyAdapter extends ArrayAdapter<String> implements ThemedSpinnerAdapter {
+ private final ThemedSpinnerAdapter.Helper mDropDownHelper;
+
+ public MyAdapter(Context context, String[] objects) {
+ super(context, android.R.layout.simple_list_item_1, objects);
+ mDropDownHelper = new ThemedSpinnerAdapter.Helper(context);
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ // Inflate the drop down using the helper's LayoutInflater
+ LayoutInflater inflater = mDropDownHelper.getDropDownViewInflater();
+ view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ TextView textView = (TextView) view.findViewById(android.R.id.text1);
+ textView.setText(getItem(position));
+
+ return view;
+ }
+
+ @Override
+ public void setDropDownViewTheme(Theme theme) {
+ mDropDownHelper.setDropDownViewTheme(theme);
+ }
+
+ @Override
+ public Theme getDropDownViewTheme() {
+ return mDropDownHelper.getDropDownViewTheme();
+ }
+ }
+
+ <#else> <#-- buildApi gte 23 -->
+ private static class MyAdapter extends ArrayAdapter<String> {
+
+ public MyAdapter(Context context, String[] objects) {
+ super(context, android.R.layout.simple_list_item_1, android.R.id.text1, objects);
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ View view = super.getView(position, convertView, parent);
+ TextView text = (TextView) view.findViewById(android.R.id.text1);
+ // Hack. Use BuildVersion 23 for a better approach.
+ text.setTextColor(Color.BLACK);
+ text.setBackgroundColor(Color.WHITE);
+ return view;
+ }
+ }
+ </#if>
+ </#if> <#-- features == 'spinner' -->
+
+ <#if hasViewPager>
+ /**
+ * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
+ * one of the sections/tabs/pages.
+ */
+ public class SectionsPagerAdapter extends FragmentPagerAdapter {
+
+ public SectionsPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ // getItem is called to instantiate the fragment for the given page.
+ // Return a PlaceholderFragment (defined as a static inner class below).
+ return PlaceholderFragment.newInstance(position + 1);
+ }
+
+ @Override
+ public int getCount() {
+ // Show 3 total pages.
+ return 3;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ switch (position) {
+ case 0:
+ return "SECTION 1";
+ case 1:
+ return "SECTION 2";
+ case 2:
+ return "SECTION 3";
+ }
+ return null;
+ }
+ }
+ </#if>
+
+ <#include "include_fragment.java.ftl">
+}
diff --git a/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
new file mode 100644
index 0000000..d9e2f30
--- /dev/null
+++ b/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
@@ -0,0 +1,34 @@
+ /**
+ * A placeholder fragment containing a simple view.
+ */
+ public static class PlaceholderFragment extends Fragment {
+ /**
+ * The fragment argument representing the section number for this
+ * fragment.
+ */
+ private static final String ARG_SECTION_NUMBER = "section_number";
+
+ /**
+ * Returns a new instance of this fragment for the given section
+ * number.
+ */
+ public static PlaceholderFragment newInstance(int sectionNumber) {
+ PlaceholderFragment fragment = new PlaceholderFragment();
+ Bundle args = new Bundle();
+ args.putInt(ARG_SECTION_NUMBER, sectionNumber);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public PlaceholderFragment() {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
+ TextView textView = (TextView) rootView.findViewById(R.id.section_label);
+ textView.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
+ return rootView;
+ }
+ }
diff --git a/base/templates/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl b/templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl
rename to templates/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
diff --git a/templates/activities/TabbedActivity/template.xml b/templates/activities/TabbedActivity/template.xml
new file mode 100644
index 0000000..864abbb
--- /dev/null
+++ b/templates/activities/TabbedActivity/template.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="6"
+ name="Tabbed Activity"
+ minApi="13"
+ minBuildApi="14"
+ requireAppTheme="true"
+ description="Creates a new blank activity, with an action bar and navigational elements such as tabs or horizontal swipe.">
+
+ <category value="Activity" />
+ <formfactor value="Mobile" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="${activityToLayout(activityClass)}"
+ default="activity_main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="fragmentLayoutName"
+ name="Fragment Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="fragment_${classToResource(activityClass)}"
+ default="fragment_main"
+ help="The name of the layout to create for the activity's content fragment" />
+
+ <parameter
+ id="activityTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="MainActivity"
+ suggest="${activityClass}"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="menuName"
+ name="Menu Resource Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="menu_${classToResource(activityClass)}"
+ default="menu_main"
+ help="The name of the resource file to create for the menu items" />
+
+ <parameter
+ id="isLauncher"
+ name="Launcher Activity"
+ type="boolean"
+ default="false"
+ help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
+
+ <parameter
+ id="parentActivityClass"
+ name="Hierarchical Parent"
+ type="string"
+ constraints="activity|exists|empty"
+ default=""
+ help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
+
+ <parameter
+ id="features"
+ name="Navigation Style"
+ type="enum"
+ default="pager"
+ help="Additional features to include, such as a fragment, swipe views, or a navigation drawer" >
+ <option id="pager">Swipe Views (ViewPager)</option>
+ <option id="tabs">Action Bar Tabs (with ViewPager)</option>
+ <option id="spinner">Action Bar Spinner</option>
+ </parameter>
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity_pager.png</thumb>
+ <!-- attributes act as selectors based on chosen parameters -->
+ <thumb features="tabs">template_blank_activity_tabs.png</thumb>
+ <thumb features="pager">template_blank_activity_pager.png</thumb>
+ <thumb features="spinner">template_blank_activity_dropdown.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/activities/TabbedActivity/template_blank_activity_dropdown.png b/templates/activities/TabbedActivity/template_blank_activity_dropdown.png
new file mode 100644
index 0000000..8113580
Binary files /dev/null and b/templates/activities/TabbedActivity/template_blank_activity_dropdown.png differ
diff --git a/templates/activities/TabbedActivity/template_blank_activity_pager.png b/templates/activities/TabbedActivity/template_blank_activity_pager.png
new file mode 100644
index 0000000..1047133
Binary files /dev/null and b/templates/activities/TabbedActivity/template_blank_activity_pager.png differ
diff --git a/templates/activities/TabbedActivity/template_blank_activity_tabs.png b/templates/activities/TabbedActivity/template_blank_activity_tabs.png
new file mode 100644
index 0000000..f8147c4
Binary files /dev/null and b/templates/activities/TabbedActivity/template_blank_activity_tabs.png differ
diff --git a/templates/activities/common/common_globals.xml.ftl b/templates/activities/common/common_globals.xml.ftl
new file mode 100644
index 0000000..1074e87
--- /dev/null
+++ b/templates/activities/common/common_globals.xml.ftl
@@ -0,0 +1,46 @@
+<globals>
+ <#assign theme=getApplicationTheme()!{ "name": "AppTheme", "isAppCompat": true }>
+ <#assign themeName=theme.name!'AppTheme'>
+ <#assign themeNameNoActionBar=theme.nameNoActionBar!'AppTheme.NoActionBar'>
+ <#assign appCompat=theme.isAppCompat!false>
+ <#assign appCompatActivity=appCompat && (buildApi gte 22)>
+
+ <global id="themeName" type="string" value="${themeName}" />
+ <global id="implicitParentTheme" type="boolean" value="${(themeNameNoActionBar?starts_with(themeName+'.'))?string}" />
+ <global id="themeNameNoActionBar" type="string" value="${themeNameNoActionBar}" />
+ <global id="themeExistsNoActionBar" type="boolean" value="${(theme.existsNoActionBar!false)?string}" />
+ <global id="themeNameAppBarOverlay" type="string" value="${theme.nameAppBarOverlay!'AppTheme.AppBarOverlay'}" />
+ <global id="themeExistsAppBarOverlay" type="boolean" value="${(theme.existsAppBarOverlay!false)?string}" />
+ <global id="themeNamePopupOverlay" type="string" value="${theme.namePopupOverlay!'AppTheme.PopupOverlay'}" />
+ <global id="themeExistsPopupOverlay" type="boolean" value="${(theme.existsPopupOverlay!false)?string}" />
+
+ <global id="appCompat" type="boolean" value="${((isNewProject!false) || (theme.isAppCompat!false))?string}" />
+ <global id="appCompatActivity" type="boolean" value="${appCompatActivity?string}" />
+ <global id="hasAppBar" type="boolean" value="${appCompatActivity?string}" />
+ <global id="hasNoActionBar" type="boolean" value="${appCompatActivity?string}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="buildVersion" value="${buildApi}" />
+
+<#if !appCompat>
+ <global id="superClass" type="string" value="Activity"/>
+ <global id="superClassFqcn" type="string" value="android.app.Activity"/>
+ <global id="Support" value="" />
+ <global id="actionBarClassFqcn" type = "string" value="android.app.ActionBar" />
+<#elseif appCompatActivity>
+ <global id="superClass" type="string" value="AppCompatActivity"/>
+ <global id="superClassFqcn" type="string" value="android.support.v7.app.AppCompatActivity"/>
+ <global id="Support" value="Support" />
+ <global id="actionBarClassFqcn" type = "string" value="android.support.v7.app.ActionBar" />
+<#else>
+ <global id="superClass" type="string" value="ActionBarActivity"/>
+ <global id="superClassFqcn" type="string" value="android.support.v7.app.ActionBarActivity"/>
+ <global id="Support" value="Support" />
+ <global id="actionBarClassFqcn" type = "string" value="android.support.v7.app.ActionBar" />
+</#if>
+
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="menuName" value="${classToResource(activityClass!'')}" />
+ <global id="simpleName" value="${activityToLayout(activityClass!'')}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/activities/common/recipe_app_bar.xml.ftl b/templates/activities/common/recipe_app_bar.xml.ftl
new file mode 100644
index 0000000..7f382eb
--- /dev/null
+++ b/templates/activities/common/recipe_app_bar.xml.ftl
@@ -0,0 +1,18 @@
+<recipe folder="root://activities/common">
+
+<#if !(hasDependency('com.android.support:appcompat-v7'))>
+ <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+"/>
+</#if>
+
+<#if !(hasDependency('com.android.support:design'))>
+ <dependency mavenUrl="com.android.support:design:${buildApi}.+"/>
+</#if>
+
+ <instantiate from="root/res/layout/app_bar.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${appBarLayoutName}.xml" />
+
+ <merge from="root/res/values/app_bar_dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+ <#include "recipe_no_actionbar.xml.ftl" />
+</recipe>
diff --git a/templates/activities/common/recipe_dummy_content.xml.ftl b/templates/activities/common/recipe_dummy_content.xml.ftl
new file mode 100644
index 0000000..5f4e70c
--- /dev/null
+++ b/templates/activities/common/recipe_dummy_content.xml.ftl
@@ -0,0 +1,4 @@
+<recipe folder="root://activities/common">
+ <instantiate from="root/src/app_package/dummy/DummyContent.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
+</recipe>
diff --git a/templates/activities/common/recipe_manifest.xml.ftl b/templates/activities/common/recipe_manifest.xml.ftl
new file mode 100644
index 0000000..4299406
--- /dev/null
+++ b/templates/activities/common/recipe_manifest.xml.ftl
@@ -0,0 +1,8 @@
+<recipe folder="root://activities/common">
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <merge from="root/res/values/manifest_strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+</recipe>
\ No newline at end of file
diff --git a/templates/activities/common/recipe_no_actionbar.xml.ftl b/templates/activities/common/recipe_no_actionbar.xml.ftl
new file mode 100644
index 0000000..9659445
--- /dev/null
+++ b/templates/activities/common/recipe_no_actionbar.xml.ftl
@@ -0,0 +1,9 @@
+<recipe folder="root://activities/common">
+ <merge from="root/res/values/no_actionbar_styles.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+
+<#if buildApi gte 21>
+ <merge from="root/res/values-v21/no_actionbar_styles_v21.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-v21/styles.xml" />
+</#if>
+</recipe>
diff --git a/templates/activities/common/recipe_simple.xml.ftl b/templates/activities/common/recipe_simple.xml.ftl
new file mode 100644
index 0000000..0f9fd26
--- /dev/null
+++ b/templates/activities/common/recipe_simple.xml.ftl
@@ -0,0 +1,15 @@
+<recipe folder="root://activities/common">
+
+<#if appCompat && !(hasDependency('com.android.support:appcompat-v7'))>
+ <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+"/>
+</#if>
+
+ <instantiate from="root/res/layout/simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${simpleLayoutName}.xml" />
+
+<#if (isNewProject!false) && !(excludeMenu!false)>
+ <#include "recipe_simple_menu.xml.ftl" />
+</#if>
+
+ <#include "recipe_simple_dimens.xml" />
+</recipe>
diff --git a/templates/activities/common/recipe_simple_dimens.xml b/templates/activities/common/recipe_simple_dimens.xml
new file mode 100644
index 0000000..932c1a9
--- /dev/null
+++ b/templates/activities/common/recipe_simple_dimens.xml
@@ -0,0 +1,6 @@
+<recipe folder="root://activities/common">
+ <merge from="root/res/values/simple_dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="root/res/values-w820dp/simple_dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+</recipe>
diff --git a/templates/activities/common/recipe_simple_menu.xml.ftl b/templates/activities/common/recipe_simple_menu.xml.ftl
new file mode 100644
index 0000000..051c047
--- /dev/null
+++ b/templates/activities/common/recipe_simple_menu.xml.ftl
@@ -0,0 +1,8 @@
+<recipe folder="root://activities/common">
+ <instantiate from="root/res/menu/simple_menu.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="root/res/values/simple_menu_strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+</recipe>
+
diff --git a/templates/activities/common/root/AndroidManifest.xml.ftl b/templates/activities/common/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..6dcee5a
--- /dev/null
+++ b/templates/activities/common/root/AndroidManifest.xml.ftl
@@ -0,0 +1,30 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <activity android:name="${relativePackage}.${activityClass}"
+ <#if generateActivityTitle!true>
+ <#if isNewProject>
+ android:label="@string/app_name"
+ <#else>
+ android:label="@string/title_${activityToLayout(activityClass)}"
+ </#if>
+ </#if>
+ <#if hasNoActionBar>
+ android:theme="@style/${themeNameNoActionBar}"
+ </#if>
+ <#if buildApi gte 16 && parentActivityClass != "">
+ android:parentActivityName="${parentActivityClass}"
+ </#if>>
+ <#if parentActivityClass != "">
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value="${parentActivityClass}" />
+ </#if>
+ <#if isLauncher && !(isLibraryProject!false)>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </#if>
+ </activity>
+ </application>
+</manifest>
diff --git a/templates/activities/common/root/res/layout/app_bar.xml.ftl b/templates/activities/common/root/res/layout/app_bar.xml.ftl
new file mode 100644
index 0000000..518ed11
--- /dev/null
+++ b/templates/activities/common/root/res/layout/app_bar.xml.ftl
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context="${relativePackage}.${activityClass}">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:theme="@style/${themeNameAppBarOverlay}">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ app:popupTheme="@style/${themeNamePopupOverlay}" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/${simpleLayoutName}"/>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="@dimen/fab_margin"
+ android:src="@android:drawable/ic_dialog_email" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/templates/activities/common/root/res/layout/simple.xml.ftl b/templates/activities/common/root/res/layout/simple.xml.ftl
new file mode 100644
index 0000000..0e0081d
--- /dev/null
+++ b/templates/activities/common/root/res/layout/simple.xml.ftl
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+<#if hasAppBar && appBarLayoutName??>
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+</#if>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+<#if hasAppBar && appBarLayoutName??>
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:showIn="@layout/${appBarLayoutName}"
+</#if>
+ tools:context="${relativePackage}.${activityClass}">
+
+<#if isNewProject!false>
+ <TextView
+ android:text="Hello World!"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</#if>
+</RelativeLayout>
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/root/res/menu/main.xml.ftl b/templates/activities/common/root/res/menu/simple_menu.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleAdMobAdsActivity/root/res/menu/main.xml.ftl
rename to templates/activities/common/root/res/menu/simple_menu.xml.ftl
diff --git a/templates/activities/common/root/res/values-v21/no_actionbar_styles_v21.xml.ftl b/templates/activities/common/root/res/values-v21/no_actionbar_styles_v21.xml.ftl
new file mode 100644
index 0000000..4848607
--- /dev/null
+++ b/templates/activities/common/root/res/values-v21/no_actionbar_styles_v21.xml.ftl
@@ -0,0 +1,10 @@
+<resources>
+ <#if !themeExistsNoActionBar>
+ <style name="${themeNameNoActionBar}"<#if !implicitParentTheme> parent="${themeName}"</#if>>
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ </style>
+ </#if>
+</resources>
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml b/templates/activities/common/root/res/values-w820dp/simple_dimens.xml
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml
rename to templates/activities/common/root/res/values-w820dp/simple_dimens.xml
diff --git a/templates/activities/common/root/res/values/app_bar_dimens.xml b/templates/activities/common/root/res/values/app_bar_dimens.xml
new file mode 100644
index 0000000..59a0b0c
--- /dev/null
+++ b/templates/activities/common/root/res/values/app_bar_dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+ <dimen name="fab_margin">16dp</dimen>
+</resources>
diff --git a/templates/activities/common/root/res/values/manifest_strings.xml.ftl b/templates/activities/common/root/res/values/manifest_strings.xml.ftl
new file mode 100644
index 0000000..5799481
--- /dev/null
+++ b/templates/activities/common/root/res/values/manifest_strings.xml.ftl
@@ -0,0 +1,5 @@
+<resources>
+<#if !isNewProject && generateActivityTitle!true>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
+</#if>
+</resources>
diff --git a/templates/activities/common/root/res/values/no_actionbar_styles.xml.ftl b/templates/activities/common/root/res/values/no_actionbar_styles.xml.ftl
new file mode 100644
index 0000000..916e5cb
--- /dev/null
+++ b/templates/activities/common/root/res/values/no_actionbar_styles.xml.ftl
@@ -0,0 +1,14 @@
+<resources>
+ <#if !themeExistsNoActionBar>
+ <style name="${themeNameNoActionBar}"<#if !implicitParentTheme> parent="${themeName}"</#if>>
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+ </#if>
+ <#if !themeExistsAppBarOverlay>
+ <style name="${themeNameAppBarOverlay}" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+ </#if>
+ <#if !themeExistsPopupOverlay>
+ <style name="${themeNamePopupOverlay}" parent="ThemeOverlay.AppCompat.Light" />
+ </#if>
+</resources>
diff --git a/base/templates/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl b/templates/activities/common/root/res/values/simple_dimens.xml
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl
rename to templates/activities/common/root/res/values/simple_dimens.xml
diff --git a/templates/activities/common/root/res/values/simple_menu_strings.xml.ftl b/templates/activities/common/root/res/values/simple_menu_strings.xml.ftl
new file mode 100644
index 0000000..897685b
--- /dev/null
+++ b/templates/activities/common/root/res/values/simple_menu_strings.xml.ftl
@@ -0,0 +1,3 @@
+<resources>
+ <string name="action_settings">Settings</string>
+</resources>
diff --git a/templates/activities/common/root/src/app_package/dummy/DummyContent.java.ftl b/templates/activities/common/root/src/app_package/dummy/DummyContent.java.ftl
new file mode 100644
index 0000000..dfdde9b
--- /dev/null
+++ b/templates/activities/common/root/src/app_package/dummy/DummyContent.java.ftl
@@ -0,0 +1,72 @@
+package ${packageName}.dummy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class for providing sample content for user interfaces created by
+ * Android template wizards.
+ * <p>
+ * TODO: Replace all uses of this class before publishing your app.
+ */
+public class DummyContent {
+
+ /**
+ * An array of sample (dummy) items.
+ */
+ public static final List<DummyItem> ITEMS = new ArrayList<DummyItem>();
+
+ /**
+ * A map of sample (dummy) items, by ID.
+ */
+ public static final Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
+
+ private static final int COUNT = 25;
+
+ static {
+ // Add some sample items.
+ for (int i = 1; i <= COUNT; i++) {
+ addItem(createDummyItem(i));
+ }
+ }
+
+ private static void addItem(DummyItem item) {
+ ITEMS.add(item);
+ ITEM_MAP.put(item.id, item);
+ }
+
+ private static DummyItem createDummyItem(int position) {
+ return new DummyItem(String.valueOf(position), "Item " + position, makeDetails(position));
+ }
+
+ private static String makeDetails(int position) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Details about Item: ").append(position);
+ for (int i = 0; i < position; i++) {
+ builder.append("\nMore details information here.");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * A dummy item representing a piece of content.
+ */
+ public static class DummyItem {
+ public final String id;
+ public final String content;
+ public final String details;
+
+ public DummyItem(String id, String content, String details) {
+ this.id = id;
+ this.content = content;
+ this.details = details;
+ }
+
+ @Override
+ public String toString() {
+ return content;
+ }
+ }
+}
diff --git a/templates/activities/common/wear_common_globals.xml.ftl b/templates/activities/common/wear_common_globals.xml.ftl
new file mode 100644
index 0000000..3a2f287
--- /dev/null
+++ b/templates/activities/common/wear_common_globals.xml.ftl
@@ -0,0 +1,18 @@
+<globals>
+ <global id="projectOut" value="." />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="debugResOut" value="${escapeXmlAttribute(projectOut)}/src/debug/res" />
+ <global id="releaseResOut" value="${escapeXmlAttribute(projectOut)}/src/release/res" />
+ <global id="resOut" value="${resDir}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+
+ <#if Mobileincluded!false>
+ <global id="appManifestOut" value="${topOut}/${MobileprojectName}/${manifestDir}" />
+ <#else>
+ <#assign appManifestDir=getAppManifestDir()!"">
+ <#if appManifestDir?length gt 0>
+ <global id="appManifestOut" value="${appManifestDir}" />
+ </#if>
+ </#if>
+</globals>
diff --git a/base/templates/build.gradle b/templates/build.gradle
similarity index 100%
rename from base/templates/build.gradle
rename to templates/build.gradle
diff --git a/base/templates/eclipse/activities/BlankActivity/globals.xml.ftl b/templates/eclipse/activities/BlankActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivity/globals.xml.ftl
rename to templates/eclipse/activities/BlankActivity/globals.xml.ftl
diff --git a/templates/eclipse/activities/BlankActivity/recipe.xml.ftl b/templates/eclipse/activities/BlankActivity/recipe.xml.ftl
new file mode 100644
index 0000000..9f4f32c
--- /dev/null
+++ b/templates/eclipse/activities/BlankActivity/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="root/res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <instantiate from="root/res/layout/activity_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/BlankActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/BlankActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl b/templates/eclipse/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl
rename to templates/eclipse/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl
diff --git a/base/templates/activities/TabbedActivity/root/res/menu/main.xml.ftl b/templates/eclipse/activities/BlankActivity/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/res/menu/main.xml.ftl
rename to templates/eclipse/activities/BlankActivity/root/res/menu/main.xml.ftl
diff --git a/base/templates/activities/TabbedActivity/root/res/values-w820dp/dimens.xml b/templates/eclipse/activities/BlankActivity/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
rename to templates/eclipse/activities/BlankActivity/root/res/values-w820dp/dimens.xml
diff --git a/base/templates/activities/GoogleAdMobAdsActivity/root/res/values/dimens.xml.ftl b/templates/eclipse/activities/BlankActivity/root/res/values/dimens.xml.ftl
similarity index 100%
rename from base/templates/activities/GoogleAdMobAdsActivity/root/res/values/dimens.xml.ftl
rename to templates/eclipse/activities/BlankActivity/root/res/values/dimens.xml.ftl
diff --git a/base/templates/activities/BlankActivity/root/res/values/strings.xml.ftl b/templates/eclipse/activities/BlankActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/BlankActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl b/templates/eclipse/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
rename to templates/eclipse/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
diff --git a/base/templates/eclipse/activities/BlankActivity/template.xml b/templates/eclipse/activities/BlankActivity/template.xml
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivity/template.xml
rename to templates/eclipse/activities/BlankActivity/template.xml
diff --git a/base/templates/activities/EmptyActivity/template_blank_activity.png b/templates/eclipse/activities/BlankActivity/template_blank_activity.png
similarity index 100%
rename from base/templates/activities/EmptyActivity/template_blank_activity.png
rename to templates/eclipse/activities/BlankActivity/template_blank_activity.png
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/globals.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/globals.xml.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/globals.xml.ftl
diff --git a/templates/eclipse/activities/BlankActivityWithFragment/recipe.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/recipe.xml.ftl
new file mode 100644
index 0000000..5a4b58c
--- /dev/null
+++ b/templates/eclipse/activities/BlankActivityWithFragment/recipe.xml.ftl
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="root/res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <instantiate from="root/res/layout/activity_fragment_container.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <instantiate from="root/src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/activity_fragment_container.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/res/layout/fragment_simple.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivity/root/res/menu/main.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivity/root/res/menu/main.xml.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivity/root/res/values-w820dp/dimens.xml b/templates/eclipse/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivity/root/res/values-w820dp/dimens.xml
rename to templates/eclipse/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml
diff --git a/base/templates/activities/TabbedActivity/root/res/values/dimens.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/res/values/dimens.xml.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/res/values/dimens.xml.ftl
diff --git a/base/templates/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/SimpleActivity.java.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_fragment.java.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_fragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_fragment.java.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_fragment.java.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl b/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl
rename to templates/eclipse/activities/BlankActivityWithFragment/root/src/app_package/include_options_menu.java.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/template.xml b/templates/eclipse/activities/BlankActivityWithFragment/template.xml
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/template.xml
rename to templates/eclipse/activities/BlankActivityWithFragment/template.xml
diff --git a/base/templates/activities/BlankActivityWithFragment/template_blank_activity_fragment.png b/templates/eclipse/activities/BlankActivityWithFragment/template_blank_activity_fragment.png
similarity index 100%
rename from base/templates/activities/BlankActivityWithFragment/template_blank_activity_fragment.png
rename to templates/eclipse/activities/BlankActivityWithFragment/template_blank_activity_fragment.png
diff --git a/base/templates/activities/AlwaysOnWearActivity/globals.xml.ftl b/templates/eclipse/activities/EmptyActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/activities/AlwaysOnWearActivity/globals.xml.ftl
rename to templates/eclipse/activities/EmptyActivity/globals.xml.ftl
diff --git a/templates/eclipse/activities/EmptyActivity/recipe.xml.ftl b/templates/eclipse/activities/EmptyActivity/recipe.xml.ftl
new file mode 100644
index 0000000..9249777
--- /dev/null
+++ b/templates/eclipse/activities/EmptyActivity/recipe.xml.ftl
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <copy from="root/res/layout/activity_simple.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/src/app_package/SimpleActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/EmptyActivity/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/EmptyActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/EmptyActivity/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/EmptyActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/activities/EmptyActivity/root/res/layout/activity_simple.xml b/templates/eclipse/activities/EmptyActivity/root/res/layout/activity_simple.xml
similarity index 100%
rename from base/templates/activities/EmptyActivity/root/res/layout/activity_simple.xml
rename to templates/eclipse/activities/EmptyActivity/root/res/layout/activity_simple.xml
diff --git a/base/templates/activities/EmptyActivity/root/res/values/strings.xml.ftl b/templates/eclipse/activities/EmptyActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/EmptyActivity/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/EmptyActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl b/templates/eclipse/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
similarity index 100%
rename from base/templates/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
rename to templates/eclipse/activities/EmptyActivity/root/src/app_package/SimpleActivity.java.ftl
diff --git a/base/templates/activities/EmptyActivity/template.xml b/templates/eclipse/activities/EmptyActivity/template.xml
similarity index 100%
rename from base/templates/activities/EmptyActivity/template.xml
rename to templates/eclipse/activities/EmptyActivity/template.xml
diff --git a/base/templates/eclipse/activities/BlankActivity/template_blank_activity.png b/templates/eclipse/activities/EmptyActivity/template_blank_activity.png
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivity/template_blank_activity.png
rename to templates/eclipse/activities/EmptyActivity/template_blank_activity.png
diff --git a/base/templates/eclipse/activities/FullscreenActivity/globals.xml.ftl b/templates/eclipse/activities/FullscreenActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/FullscreenActivity/globals.xml.ftl
rename to templates/eclipse/activities/FullscreenActivity/globals.xml.ftl
diff --git a/templates/eclipse/activities/FullscreenActivity/recipe.xml.ftl b/templates/eclipse/activities/FullscreenActivity/recipe.xml.ftl
new file mode 100644
index 0000000..22ed8eb
--- /dev/null
+++ b/templates/eclipse/activities/FullscreenActivity/recipe.xml.ftl
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:19.+" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values/attrs.xml"
+ to="${escapeXmlAttribute(resOut)}/values/attrs.xml" />
+ <merge from="root/res/values/colors.xml"
+ to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
+ <merge from="root/res/values/styles.xml"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+ <merge from="root/res/values-v11/styles.xml"
+ to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
+ <instantiate from="root/res/layout/activity_fullscreen.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/src/app_package/FullscreenActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <instantiate from="root/src/app_package/util/SystemUiHider.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/util/SystemUiHider.java" />
+ <instantiate from="root/src/app_package/util/SystemUiHiderBase.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderBase.java" />
+ <instantiate from="root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderHoneycomb.java" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl b/templates/eclipse/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
rename to templates/eclipse/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
diff --git a/base/templates/activities/FullscreenActivity/root/res/values-v11/styles.xml b/templates/eclipse/activities/FullscreenActivity/root/res/values-v11/styles.xml
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/res/values-v11/styles.xml
rename to templates/eclipse/activities/FullscreenActivity/root/res/values-v11/styles.xml
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/res/values/attrs.xml b/templates/eclipse/activities/FullscreenActivity/root/res/values/attrs.xml
similarity index 100%
rename from base/templates/eclipse/activities/FullscreenActivity/root/res/values/attrs.xml
rename to templates/eclipse/activities/FullscreenActivity/root/res/values/attrs.xml
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/res/values/colors.xml b/templates/eclipse/activities/FullscreenActivity/root/res/values/colors.xml
similarity index 100%
rename from base/templates/eclipse/activities/FullscreenActivity/root/res/values/colors.xml
rename to templates/eclipse/activities/FullscreenActivity/root/res/values/colors.xml
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/res/values/strings.xml.ftl b/templates/eclipse/activities/FullscreenActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/FullscreenActivity/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/FullscreenActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/activities/FullscreenActivity/root/res/values/styles.xml b/templates/eclipse/activities/FullscreenActivity/root/res/values/styles.xml
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/res/values/styles.xml
rename to templates/eclipse/activities/FullscreenActivity/root/res/values/styles.xml
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl b/templates/eclipse/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
rename to templates/eclipse/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
diff --git a/base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHider.java.ftl b/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHider.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHider.java.ftl
rename to templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHider.java.ftl
diff --git a/base/templates/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderBase.java.ftl b/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderBase.java.ftl
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderBase.java.ftl
rename to templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderBase.java.ftl
diff --git a/base/templates/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl b/templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl
similarity index 100%
rename from base/templates/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl
rename to templates/eclipse/activities/FullscreenActivity/root/src/app_package/util/SystemUiHiderHoneycomb.java.ftl
diff --git a/base/templates/activities/FullscreenActivity/template.xml b/templates/eclipse/activities/FullscreenActivity/template.xml
similarity index 100%
rename from base/templates/activities/FullscreenActivity/template.xml
rename to templates/eclipse/activities/FullscreenActivity/template.xml
diff --git a/base/templates/activities/FullscreenActivity/template_fullscreen_activity.png b/templates/eclipse/activities/FullscreenActivity/template_fullscreen_activity.png
similarity index 100%
rename from base/templates/activities/FullscreenActivity/template_fullscreen_activity.png
rename to templates/eclipse/activities/FullscreenActivity/template_fullscreen_activity.png
diff --git a/base/templates/eclipse/activities/LoginActivity/globals.xml.ftl b/templates/eclipse/activities/LoginActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/LoginActivity/globals.xml.ftl
rename to templates/eclipse/activities/LoginActivity/globals.xml.ftl
diff --git a/templates/eclipse/activities/LoginActivity/recipe.xml.ftl b/templates/eclipse/activities/LoginActivity/recipe.xml.ftl
new file mode 100644
index 0000000..9c8badd
--- /dev/null
+++ b/templates/eclipse/activities/LoginActivity/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
+ <dependency mavenUrl="com.android.support:appcompat-v7:19.+" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+ <instantiate from="root/res/layout/activity_login.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
+
+ <instantiate from="root/src/app_package/LoginActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <#if includeGooglePlus>
+ <instantiate from="root/src/app_package/PlusBaseActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/PlusBaseActivity.java" />
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+</recipe>
diff --git a/base/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/LoginActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/LoginActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl b/templates/eclipse/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
similarity index 100%
rename from base/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
rename to templates/eclipse/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
diff --git a/base/templates/eclipse/activities/LoginActivity/root/res/values/dimens.xml b/templates/eclipse/activities/LoginActivity/root/res/values/dimens.xml
similarity index 100%
rename from base/templates/eclipse/activities/LoginActivity/root/res/values/dimens.xml
rename to templates/eclipse/activities/LoginActivity/root/res/values/dimens.xml
diff --git a/base/templates/activities/LoginActivity/root/res/values/strings.xml.ftl b/templates/eclipse/activities/LoginActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/LoginActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl b/templates/eclipse/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
rename to templates/eclipse/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
diff --git a/base/templates/eclipse/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl b/templates/eclipse/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl
rename to templates/eclipse/activities/LoginActivity/root/src/app_package/PlusBaseActivity.java.ftl
diff --git a/base/templates/activities/LoginActivity/template.xml b/templates/eclipse/activities/LoginActivity/template.xml
similarity index 100%
rename from base/templates/activities/LoginActivity/template.xml
rename to templates/eclipse/activities/LoginActivity/template.xml
diff --git a/base/templates/activities/LoginActivity/template_login_activity.png b/templates/eclipse/activities/LoginActivity/template_login_activity.png
similarity index 100%
rename from base/templates/activities/LoginActivity/template_login_activity.png
rename to templates/eclipse/activities/LoginActivity/template_login_activity.png
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/globals.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/globals.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/globals.xml.ftl
diff --git a/templates/eclipse/activities/MasterDetailFlow/recipe.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/recipe.xml.ftl
new file mode 100644
index 0000000..ecbe1b4
--- /dev/null
+++ b/templates/eclipse/activities/MasterDetailFlow/recipe.xml.ftl
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:19.+" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <merge from="root/res/values-large/refs.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
+ <merge from="root/res/values-sw600dp/refs.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/res/layout/activity_content_detail.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/activity_${detail_name}.xml" />
+ <instantiate from="root/res/layout/activity_content_list.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/activity_${collection_name}.xml" />
+ <instantiate from="root/res/layout/activity_content_twopane.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" />
+ <instantiate from="root/res/layout/fragment_content_detail.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
+
+ <instantiate from="root/src/app_package/ContentDetailActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${DetailName}Activity.java" />
+ <instantiate from="root/src/app_package/ContentDetailFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
+ <instantiate from="root/src/app_package/ContentListActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${CollectionName}Activity.java" />
+ <instantiate from="root/src/app_package/ContentListFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${CollectionName}Fragment.java" />
+ <instantiate from="root/src/app_package/dummy/DummyContent.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
+</recipe>
diff --git a/base/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
diff --git a/base/templates/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
diff --git a/base/templates/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
diff --git a/base/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
diff --git a/base/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/res/values/strings.xml.ftl b/templates/eclipse/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl b/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl b/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl b/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl b/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
diff --git a/base/templates/activities/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl b/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl
rename to templates/eclipse/activities/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl
diff --git a/base/templates/activities/MasterDetailFlow/template.xml b/templates/eclipse/activities/MasterDetailFlow/template.xml
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/template.xml
rename to templates/eclipse/activities/MasterDetailFlow/template.xml
diff --git a/base/templates/activities/MasterDetailFlow/template_master_detail.png b/templates/eclipse/activities/MasterDetailFlow/template_master_detail.png
similarity index 100%
rename from base/templates/activities/MasterDetailFlow/template_master_detail.png
rename to templates/eclipse/activities/MasterDetailFlow/template_master_detail.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/globals.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/globals.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/globals.xml.ftl
diff --git a/templates/eclipse/activities/NavigationDrawerActivity/recipe.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/recipe.xml.ftl
new file mode 100644
index 0000000..ad52d40
--- /dev/null
+++ b/templates/eclipse/activities/NavigationDrawerActivity/recipe.xml.ftl
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
+ <#if !appCompat><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="root/res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <!-- TODO: switch on Holo Dark v. Holo Light -->
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ <copy from="root/res/drawable-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
+
+ <instantiate from="root/res/menu/global.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/global.xml" />
+
+ <instantiate from="root/res/layout/activity_drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <instantiate from="root/res/layout/fragment_navigation_drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${navigationDrawerLayout}.xml" />
+
+ <instantiate from="root/res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <instantiate from="root/src/app_package/DrawerActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <instantiate from="root/src/app_package/NavigationDrawerFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/NavigationDrawerFragment.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/build.gradle.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/build.gradle.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/build.gradle.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/drawer_shadow.9.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-hdpi/ic_drawer.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/drawer_shadow.9.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-mdpi/ic_drawer.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xhdpi/ic_drawer.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png b/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/drawable-xxhdpi/ic_drawer.png
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/activity_drawer.xml.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/layout/fragment_simple.xml.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/global.xml.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/menu/main.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml b/templates/eclipse/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/root/res/values-w820dp/dimens.xml
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/values/dimens.xml.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/DrawerActivity.java.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_fragment.java.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl b/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
rename to templates/eclipse/activities/NavigationDrawerActivity/root/src/app_package/include_options_menu.java.ftl
diff --git a/base/templates/activities/NavigationDrawerActivity/template.xml b/templates/eclipse/activities/NavigationDrawerActivity/template.xml
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/template.xml
rename to templates/eclipse/activities/NavigationDrawerActivity/template.xml
diff --git a/base/templates/activities/NavigationDrawerActivity/template_blank_activity_drawer.png b/templates/eclipse/activities/NavigationDrawerActivity/template_blank_activity_drawer.png
similarity index 100%
rename from base/templates/activities/NavigationDrawerActivity/template_blank_activity_drawer.png
rename to templates/eclipse/activities/NavigationDrawerActivity/template_blank_activity_drawer.png
diff --git a/base/templates/eclipse/activities/SettingsActivity/globals.xml.ftl b/templates/eclipse/activities/SettingsActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/SettingsActivity/globals.xml.ftl
rename to templates/eclipse/activities/SettingsActivity/globals.xml.ftl
diff --git a/templates/eclipse/activities/SettingsActivity/recipe.xml.ftl b/templates/eclipse/activities/SettingsActivity/recipe.xml.ftl
new file mode 100644
index 0000000..40c3f08
--- /dev/null
+++ b/templates/eclipse/activities/SettingsActivity/recipe.xml.ftl
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:19.+" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <copy from="root/res/xml/pref_data_sync.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_data_sync.xml" />
+ <copy from="root/res/xml/pref_general.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_general.xml" />
+ <merge from="root/res/xml/pref_headers.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_headers.xml" />
+ <copy from="root/res/xml/pref_notification.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/pref_notification.xml" />
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
+
+ <instantiate from="root/src/app_package/SettingsActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+</recipe>
diff --git a/base/templates/activities/SettingsActivity/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/SettingsActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/SettingsActivity/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/SettingsActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/activities/SettingsActivity/root/res/values/strings.xml.ftl b/templates/eclipse/activities/SettingsActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/SettingsActivity/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/SettingsActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_data_sync.xml b/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_data_sync.xml
similarity index 100%
rename from base/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_data_sync.xml
rename to templates/eclipse/activities/SettingsActivity/root/res/xml/pref_data_sync.xml
diff --git a/base/templates/activities/SettingsActivity/root/res/xml/pref_general.xml b/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_general.xml
similarity index 100%
rename from base/templates/activities/SettingsActivity/root/res/xml/pref_general.xml
rename to templates/eclipse/activities/SettingsActivity/root/res/xml/pref_general.xml
diff --git a/base/templates/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl b/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl
similarity index 100%
rename from base/templates/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl
rename to templates/eclipse/activities/SettingsActivity/root/res/xml/pref_headers.xml.ftl
diff --git a/base/templates/activities/SettingsActivity/root/res/xml/pref_notification.xml b/templates/eclipse/activities/SettingsActivity/root/res/xml/pref_notification.xml
similarity index 100%
rename from base/templates/activities/SettingsActivity/root/res/xml/pref_notification.xml
rename to templates/eclipse/activities/SettingsActivity/root/res/xml/pref_notification.xml
diff --git a/base/templates/eclipse/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl b/templates/eclipse/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
rename to templates/eclipse/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
diff --git a/base/templates/activities/SettingsActivity/template.xml b/templates/eclipse/activities/SettingsActivity/template.xml
similarity index 100%
rename from base/templates/activities/SettingsActivity/template.xml
rename to templates/eclipse/activities/SettingsActivity/template.xml
diff --git a/base/templates/activities/SettingsActivity/template_settings_activity.png b/templates/eclipse/activities/SettingsActivity/template_settings_activity.png
similarity index 100%
rename from base/templates/activities/SettingsActivity/template_settings_activity.png
rename to templates/eclipse/activities/SettingsActivity/template_settings_activity.png
diff --git a/base/templates/eclipse/activities/TabbedActivity/globals.xml.ftl b/templates/eclipse/activities/TabbedActivity/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/TabbedActivity/globals.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/globals.xml.ftl
diff --git a/templates/eclipse/activities/TabbedActivity/recipe.xml.ftl b/templates/eclipse/activities/TabbedActivity/recipe.xml.ftl
new file mode 100644
index 0000000..a68e529
--- /dev/null
+++ b/templates/eclipse/activities/TabbedActivity/recipe.xml.ftl
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
+ <#if !appCompat && hasViewPager><dependency mavenUrl="com.android.support:support-v13:19.+"/></#if>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/menu/main.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+ <merge from="root/res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
+
+ <!-- Decide what kind of layout(s) to add -->
+ <#if hasViewPager>
+ <instantiate from="root/res/layout/activity_pager.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
+ <#else>
+ <instantiate from="root/res/layout/activity_fragment_container.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ </#if>
+
+ <instantiate from="root/res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
+ <!-- Decide which activity code to add -->
+ <#if features == "tabs" || features == "pager">
+ <instantiate from="root/src/app_package/TabsAndPagerActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ <#elseif features == "spinner">
+ <instantiate from="root/src/app_package/DropdownActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+</recipe>
diff --git a/base/templates/activities/TabbedActivity/root/AndroidManifest.xml.ftl b/templates/eclipse/activities/TabbedActivity/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/AndroidManifest.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl b/templates/eclipse/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/root/res/layout/activity_fragment_container.xml.ftl
diff --git a/base/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl b/templates/eclipse/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/root/res/layout/activity_pager.xml.ftl
diff --git a/base/templates/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl b/templates/eclipse/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/root/res/layout/fragment_simple.xml.ftl
diff --git a/base/templates/eclipse/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl b/templates/eclipse/activities/TabbedActivity/root/res/menu/main.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivityWithFragment/root/res/menu/main.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/root/res/menu/main.xml.ftl
diff --git a/base/templates/eclipse/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml b/templates/eclipse/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
similarity index 100%
rename from base/templates/eclipse/activities/NavigationDrawerActivity/root/res/values-w820dp/dimens.xml
rename to templates/eclipse/activities/TabbedActivity/root/res/values-w820dp/dimens.xml
diff --git a/base/templates/eclipse/activities/BlankActivity/root/res/values/dimens.xml.ftl b/templates/eclipse/activities/TabbedActivity/root/res/values/dimens.xml.ftl
similarity index 100%
rename from base/templates/eclipse/activities/BlankActivity/root/res/values/dimens.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/root/res/values/dimens.xml.ftl
diff --git a/base/templates/activities/TabbedActivity/root/res/values/strings.xml.ftl b/templates/eclipse/activities/TabbedActivity/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/res/values/strings.xml.ftl
rename to templates/eclipse/activities/TabbedActivity/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl b/templates/eclipse/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
rename to templates/eclipse/activities/TabbedActivity/root/src/app_package/DropdownActivity.java.ftl
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl b/templates/eclipse/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
rename to templates/eclipse/activities/TabbedActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
diff --git a/base/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl b/templates/eclipse/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
similarity index 100%
rename from base/templates/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
rename to templates/eclipse/activities/TabbedActivity/root/src/app_package/include_fragment.java.ftl
diff --git a/base/templates/eclipse/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl b/templates/eclipse/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
rename to templates/eclipse/activities/TabbedActivity/root/src/app_package/include_options_menu.java.ftl
diff --git a/base/templates/eclipse/activities/TabbedActivity/template.xml b/templates/eclipse/activities/TabbedActivity/template.xml
similarity index 100%
rename from base/templates/eclipse/activities/TabbedActivity/template.xml
rename to templates/eclipse/activities/TabbedActivity/template.xml
diff --git a/base/templates/activities/TabbedActivity/template_blank_activity_dropdown.png b/templates/eclipse/activities/TabbedActivity/template_blank_activity_dropdown.png
similarity index 100%
rename from base/templates/activities/TabbedActivity/template_blank_activity_dropdown.png
rename to templates/eclipse/activities/TabbedActivity/template_blank_activity_dropdown.png
diff --git a/base/templates/activities/TabbedActivity/template_blank_activity_pager.png b/templates/eclipse/activities/TabbedActivity/template_blank_activity_pager.png
similarity index 100%
rename from base/templates/activities/TabbedActivity/template_blank_activity_pager.png
rename to templates/eclipse/activities/TabbedActivity/template_blank_activity_pager.png
diff --git a/base/templates/activities/TabbedActivity/template_blank_activity_tabs.png b/templates/eclipse/activities/TabbedActivity/template_blank_activity_tabs.png
similarity index 100%
rename from base/templates/activities/TabbedActivity/template_blank_activity_tabs.png
rename to templates/eclipse/activities/TabbedActivity/template_blank_activity_tabs.png
diff --git a/base/templates/eclipse/gradle/utils/dependencies.gradle.ftl b/templates/eclipse/gradle/utils/dependencies.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/gradle/utils/dependencies.gradle.ftl
rename to templates/eclipse/gradle/utils/dependencies.gradle.ftl
diff --git a/base/templates/eclipse/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties b/templates/eclipse/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from base/templates/eclipse/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
rename to templates/eclipse/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
diff --git a/base/templates/eclipse/gradle/wrapper/gradlew b/templates/eclipse/gradle/wrapper/gradlew
similarity index 100%
rename from base/templates/eclipse/gradle/wrapper/gradlew
rename to templates/eclipse/gradle/wrapper/gradlew
diff --git a/base/templates/eclipse/gradle/wrapper/gradlew.bat b/templates/eclipse/gradle/wrapper/gradlew.bat
similarity index 100%
rename from base/templates/eclipse/gradle/wrapper/gradlew.bat
rename to templates/eclipse/gradle/wrapper/gradlew.bat
diff --git a/templates/eclipse/other/AidlFile/recipe.xml.ftl b/templates/eclipse/other/AidlFile/recipe.xml.ftl
new file mode 100644
index 0000000..783336d
--- /dev/null
+++ b/templates/eclipse/other/AidlFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="root/src/app_package/interface.aidl.ftl"
+ to="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
+ <open file="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
+</recipe>
diff --git a/base/templates/eclipse/other/AidlFile/root/src/app_package/interface.aidl.ftl b/templates/eclipse/other/AidlFile/root/src/app_package/interface.aidl.ftl
similarity index 100%
rename from base/templates/eclipse/other/AidlFile/root/src/app_package/interface.aidl.ftl
rename to templates/eclipse/other/AidlFile/root/src/app_package/interface.aidl.ftl
diff --git a/base/templates/eclipse/other/AidlFile/template.xml b/templates/eclipse/other/AidlFile/template.xml
similarity index 100%
rename from base/templates/eclipse/other/AidlFile/template.xml
rename to templates/eclipse/other/AidlFile/template.xml
diff --git a/templates/eclipse/other/AidlFolder/recipe.xml.ftl b/templates/eclipse/other/AidlFolder/recipe.xml.ftl
new file mode 100644
index 0000000..61617dd
--- /dev/null
+++ b/templates/eclipse/other/AidlFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/aidl/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/AidlFolder/root/build.gradle.ftl b/templates/eclipse/other/AidlFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/AidlFolder/root/build.gradle.ftl
rename to templates/eclipse/other/AidlFolder/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/AidlFolder/template.xml b/templates/eclipse/other/AidlFolder/template.xml
similarity index 100%
rename from base/templates/eclipse/other/AidlFolder/template.xml
rename to templates/eclipse/other/AidlFolder/template.xml
diff --git a/templates/eclipse/other/AndroidManifest/recipe.xml.ftl b/templates/eclipse/other/AndroidManifest/recipe.xml.ftl
new file mode 100644
index 0000000..f984de0
--- /dev/null
+++ b/templates/eclipse/other/AndroidManifest/recipe.xml.ftl
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFile>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/AndroidManifest/root/AndroidManifest.xml.ftl b/templates/eclipse/other/AndroidManifest/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/AndroidManifest/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/AndroidManifest/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/AndroidManifest/root/build.gradle.ftl b/templates/eclipse/other/AndroidManifest/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/AndroidManifest/root/build.gradle.ftl
rename to templates/eclipse/other/AndroidManifest/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/AndroidManifest/template.xml b/templates/eclipse/other/AndroidManifest/template.xml
similarity index 100%
rename from base/templates/eclipse/other/AndroidManifest/template.xml
rename to templates/eclipse/other/AndroidManifest/template.xml
diff --git a/base/templates/eclipse/other/AppWidget/globals.xml.ftl b/templates/eclipse/other/AppWidget/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/globals.xml.ftl
rename to templates/eclipse/other/AppWidget/globals.xml.ftl
diff --git a/templates/eclipse/other/AppWidget/recipe.xml.ftl b/templates/eclipse/other/AppWidget/recipe.xml.ftl
new file mode 100644
index 0000000..fa171f3
--- /dev/null
+++ b/templates/eclipse/other/AppWidget/recipe.xml.ftl
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <copy from="root/res/drawable-nodpi/example_appwidget_preview.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_appwidget_preview.png" />
+ <instantiate from="root/res/layout/appwidget.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${class_name}.xml" />
+
+ <#if configurable>
+ <instantiate from="root/res/layout/appwidget_configure.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${class_name}_configure.xml" />
+ </#if>
+
+ <instantiate from="root/res/xml/appwidget_info.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/xml/${class_name}_info.xml" />
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+ <merge from="root/res/values-v14/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-v14/dimens.xml" />
+ <merge from="root/res/values/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+ <instantiate from="root/src/app_package/AppWidget.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <#if configurable>
+ <instantiate from="root/src/app_package/AppWidgetConfigureActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}ConfigureActivity.java" />
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/eclipse/other/AppWidget/root/AndroidManifest.xml.ftl b/templates/eclipse/other/AppWidget/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/AppWidget/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png b/templates/eclipse/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png
rename to templates/eclipse/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png
diff --git a/base/templates/eclipse/other/AppWidget/root/res/layout/appwidget.xml b/templates/eclipse/other/AppWidget/root/res/layout/appwidget.xml
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/res/layout/appwidget.xml
rename to templates/eclipse/other/AppWidget/root/res/layout/appwidget.xml
diff --git a/base/templates/eclipse/other/AppWidget/root/res/layout/appwidget_configure.xml b/templates/eclipse/other/AppWidget/root/res/layout/appwidget_configure.xml
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/res/layout/appwidget_configure.xml
rename to templates/eclipse/other/AppWidget/root/res/layout/appwidget_configure.xml
diff --git a/base/templates/eclipse/other/AppWidget/root/res/values-v14/dimens.xml b/templates/eclipse/other/AppWidget/root/res/values-v14/dimens.xml
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/res/values-v14/dimens.xml
rename to templates/eclipse/other/AppWidget/root/res/values-v14/dimens.xml
diff --git a/base/templates/eclipse/other/AppWidget/root/res/values/dimens.xml b/templates/eclipse/other/AppWidget/root/res/values/dimens.xml
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/res/values/dimens.xml
rename to templates/eclipse/other/AppWidget/root/res/values/dimens.xml
diff --git a/base/templates/eclipse/other/AppWidget/root/res/values/strings.xml.ftl b/templates/eclipse/other/AppWidget/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/res/values/strings.xml.ftl
rename to templates/eclipse/other/AppWidget/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/other/AppWidget/root/res/xml/appwidget_info.xml.ftl b/templates/eclipse/other/AppWidget/root/res/xml/appwidget_info.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/res/xml/appwidget_info.xml.ftl
rename to templates/eclipse/other/AppWidget/root/res/xml/appwidget_info.xml.ftl
diff --git a/base/templates/eclipse/other/AppWidget/root/src/app_package/AppWidget.java.ftl b/templates/eclipse/other/AppWidget/root/src/app_package/AppWidget.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/src/app_package/AppWidget.java.ftl
rename to templates/eclipse/other/AppWidget/root/src/app_package/AppWidget.java.ftl
diff --git a/base/templates/eclipse/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl b/templates/eclipse/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
rename to templates/eclipse/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
diff --git a/base/templates/eclipse/other/AppWidget/template.xml b/templates/eclipse/other/AppWidget/template.xml
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/template.xml
rename to templates/eclipse/other/AppWidget/template.xml
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x1.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x1_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x2.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x2_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x3.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x3_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x4.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_1x4_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x1.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x1_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x2.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x2_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x3.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x3_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x4.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_2x4_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x1.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x1_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x2.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x2_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x3.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x3_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x4.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_3x4_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x1.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x1_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x2.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x2_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x3.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x3_vh.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x4.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_h.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_h.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_h.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_h.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_v.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_v.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_v.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_v.png
diff --git a/base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_vh.png b/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_vh.png
similarity index 100%
rename from base/templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_vh.png
rename to templates/eclipse/other/AppWidget/thumbs/template_widget_4x4_vh.png
diff --git a/templates/eclipse/other/AssetsFolder/recipe.xml.ftl b/templates/eclipse/other/AssetsFolder/recipe.xml.ftl
new file mode 100644
index 0000000..85e8986
--- /dev/null
+++ b/templates/eclipse/other/AssetsFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/assets/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/AssetsFolder/root/build.gradle.ftl b/templates/eclipse/other/AssetsFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/AssetsFolder/root/build.gradle.ftl
rename to templates/eclipse/other/AssetsFolder/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/AssetsFolder/template.xml b/templates/eclipse/other/AssetsFolder/template.xml
similarity index 100%
rename from base/templates/eclipse/other/AssetsFolder/template.xml
rename to templates/eclipse/other/AssetsFolder/template.xml
diff --git a/base/templates/eclipse/other/BlankFragment/globals.xml.ftl b/templates/eclipse/other/BlankFragment/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/BlankFragment/globals.xml.ftl
rename to templates/eclipse/other/BlankFragment/globals.xml.ftl
diff --git a/templates/eclipse/other/BlankFragment/recipe.xml.ftl b/templates/eclipse/other/BlankFragment/recipe.xml.ftl
new file mode 100644
index 0000000..7d837e9
--- /dev/null
+++ b/templates/eclipse/other/BlankFragment/recipe.xml.ftl
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if useSupport><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
+ <merge from="root/res/values/strings.xml" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <#if includeLayout>
+ <instantiate from="root/res/layout/fragment_blank.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <instantiate from="root/src/app_package/BlankFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/base/templates/eclipse/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl b/templates/eclipse/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
rename to templates/eclipse/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
diff --git a/base/templates/eclipse/other/BlankFragment/root/res/values/strings.xml b/templates/eclipse/other/BlankFragment/root/res/values/strings.xml
similarity index 100%
rename from base/templates/eclipse/other/BlankFragment/root/res/values/strings.xml
rename to templates/eclipse/other/BlankFragment/root/res/values/strings.xml
diff --git a/base/templates/eclipse/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl b/templates/eclipse/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
rename to templates/eclipse/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
diff --git a/base/templates/eclipse/other/BlankFragment/template.xml b/templates/eclipse/other/BlankFragment/template.xml
similarity index 100%
rename from base/templates/eclipse/other/BlankFragment/template.xml
rename to templates/eclipse/other/BlankFragment/template.xml
diff --git a/base/templates/eclipse/other/BlankFragment/template_blank_fragment.png b/templates/eclipse/other/BlankFragment/template_blank_fragment.png
similarity index 100%
rename from base/templates/eclipse/other/BlankFragment/template_blank_fragment.png
rename to templates/eclipse/other/BlankFragment/template_blank_fragment.png
diff --git a/base/templates/eclipse/other/BroadcastReceiver/globals.xml.ftl b/templates/eclipse/other/BroadcastReceiver/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/BroadcastReceiver/globals.xml.ftl
rename to templates/eclipse/other/BroadcastReceiver/globals.xml.ftl
diff --git a/templates/eclipse/other/BroadcastReceiver/recipe.xml.ftl b/templates/eclipse/other/BroadcastReceiver/recipe.xml.ftl
new file mode 100644
index 0000000..4a74840
--- /dev/null
+++ b/templates/eclipse/other/BroadcastReceiver/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/BroadcastReceiver.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/eclipse/other/BroadcastReceiver/root/AndroidManifest.xml.ftl b/templates/eclipse/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl b/templates/eclipse/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl
rename to templates/eclipse/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl
diff --git a/base/templates/eclipse/other/BroadcastReceiver/template.xml b/templates/eclipse/other/BroadcastReceiver/template.xml
similarity index 100%
rename from base/templates/eclipse/other/BroadcastReceiver/template.xml
rename to templates/eclipse/other/BroadcastReceiver/template.xml
diff --git a/base/templates/eclipse/other/ContentProvider/globals.xml.ftl b/templates/eclipse/other/ContentProvider/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ContentProvider/globals.xml.ftl
rename to templates/eclipse/other/ContentProvider/globals.xml.ftl
diff --git a/templates/eclipse/other/ContentProvider/recipe.xml.ftl b/templates/eclipse/other/ContentProvider/recipe.xml.ftl
new file mode 100644
index 0000000..8ca93eb
--- /dev/null
+++ b/templates/eclipse/other/ContentProvider/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/ContentProvider.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/eclipse/other/ContentProvider/root/AndroidManifest.xml.ftl b/templates/eclipse/other/ContentProvider/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ContentProvider/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/ContentProvider/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl b/templates/eclipse/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl
rename to templates/eclipse/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl
diff --git a/base/templates/eclipse/other/ContentProvider/template.xml b/templates/eclipse/other/ContentProvider/template.xml
similarity index 100%
rename from base/templates/eclipse/other/ContentProvider/template.xml
rename to templates/eclipse/other/ContentProvider/template.xml
diff --git a/base/templates/eclipse/other/CustomView/globals.xml.ftl b/templates/eclipse/other/CustomView/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/CustomView/globals.xml.ftl
rename to templates/eclipse/other/CustomView/globals.xml.ftl
diff --git a/templates/eclipse/other/CustomView/recipe.xml.ftl b/templates/eclipse/other/CustomView/recipe.xml.ftl
new file mode 100644
index 0000000..406cc9a
--- /dev/null
+++ b/templates/eclipse/other/CustomView/recipe.xml.ftl
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/res/values/attrs.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/attrs_${view_class}.xml" />
+ <instantiate from="root/res/layout/sample.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
+
+ <instantiate from="root/src/app_package/CustomView.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
+</recipe>
diff --git a/base/templates/eclipse/other/CustomView/root/res/layout/sample.xml.ftl b/templates/eclipse/other/CustomView/root/res/layout/sample.xml.ftl
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/other/CustomView/root/res/layout/sample.xml.ftl
rename to templates/eclipse/other/CustomView/root/res/layout/sample.xml.ftl
diff --git a/base/templates/eclipse/other/CustomView/root/res/values/attrs.xml.ftl b/templates/eclipse/other/CustomView/root/res/values/attrs.xml.ftl
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/other/CustomView/root/res/values/attrs.xml.ftl
rename to templates/eclipse/other/CustomView/root/res/values/attrs.xml.ftl
diff --git a/base/templates/eclipse/other/CustomView/root/src/app_package/CustomView.java.ftl b/templates/eclipse/other/CustomView/root/src/app_package/CustomView.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/CustomView/root/src/app_package/CustomView.java.ftl
rename to templates/eclipse/other/CustomView/root/src/app_package/CustomView.java.ftl
diff --git a/base/templates/eclipse/other/CustomView/template.xml b/templates/eclipse/other/CustomView/template.xml
similarity index 100%
rename from base/templates/eclipse/other/CustomView/template.xml
rename to templates/eclipse/other/CustomView/template.xml
diff --git a/base/templates/eclipse/other/Daydream/globals.xml.ftl b/templates/eclipse/other/Daydream/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Daydream/globals.xml.ftl
rename to templates/eclipse/other/Daydream/globals.xml.ftl
diff --git a/templates/eclipse/other/Daydream/recipe.xml.ftl b/templates/eclipse/other/Daydream/recipe.xml.ftl
new file mode 100644
index 0000000..9494460
--- /dev/null
+++ b/templates/eclipse/other/Daydream/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <copy from="root/res/layout-v17/dream.xml"
+ to="${escapeXmlAttribute(resOut)}/layout-v17/${class_name}.xml" />
+
+ <instantiate from="root/src/app_package/DreamService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+<#if configurable>
+ <copy from="root/res/xml/dream_prefs.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/${prefs_name}.xml" />
+
+ <instantiate from="root/src/app_package/SettingsActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${settingsClassName}.java" />
+
+ <instantiate from="root/res/xml/xml_dream.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/xml/${info_name}.xml" />
+</#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/base/templates/eclipse/other/Daydream/root/AndroidManifest.xml.ftl b/templates/eclipse/other/Daydream/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Daydream/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/Daydream/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/Daydream/root/res/layout-v17/dream.xml b/templates/eclipse/other/Daydream/root/res/layout-v17/dream.xml
similarity index 100%
rename from base/templates/eclipse/other/Daydream/root/res/layout-v17/dream.xml
rename to templates/eclipse/other/Daydream/root/res/layout-v17/dream.xml
diff --git a/base/templates/eclipse/other/Daydream/root/res/values/strings.xml.ftl b/templates/eclipse/other/Daydream/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Daydream/root/res/values/strings.xml.ftl
rename to templates/eclipse/other/Daydream/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/other/Daydream/root/res/xml/dream_prefs.xml b/templates/eclipse/other/Daydream/root/res/xml/dream_prefs.xml
similarity index 100%
rename from base/templates/eclipse/other/Daydream/root/res/xml/dream_prefs.xml
rename to templates/eclipse/other/Daydream/root/res/xml/dream_prefs.xml
diff --git a/base/templates/eclipse/other/Daydream/root/res/xml/xml_dream.xml.ftl b/templates/eclipse/other/Daydream/root/res/xml/xml_dream.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Daydream/root/res/xml/xml_dream.xml.ftl
rename to templates/eclipse/other/Daydream/root/res/xml/xml_dream.xml.ftl
diff --git a/base/templates/eclipse/other/Daydream/root/src/app_package/DreamService.java.ftl b/templates/eclipse/other/Daydream/root/src/app_package/DreamService.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/Daydream/root/src/app_package/DreamService.java.ftl
rename to templates/eclipse/other/Daydream/root/src/app_package/DreamService.java.ftl
diff --git a/base/templates/eclipse/other/Daydream/root/src/app_package/SettingsActivity.java.ftl b/templates/eclipse/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
rename to templates/eclipse/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
diff --git a/base/templates/eclipse/other/Daydream/template.xml b/templates/eclipse/other/Daydream/template.xml
similarity index 100%
rename from base/templates/eclipse/other/Daydream/template.xml
rename to templates/eclipse/other/Daydream/template.xml
diff --git a/base/templates/eclipse/other/IntentService/globals.xml.ftl b/templates/eclipse/other/IntentService/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/IntentService/globals.xml.ftl
rename to templates/eclipse/other/IntentService/globals.xml.ftl
diff --git a/templates/eclipse/other/IntentService/recipe.xml.ftl b/templates/eclipse/other/IntentService/recipe.xml.ftl
new file mode 100644
index 0000000..208e132
--- /dev/null
+++ b/templates/eclipse/other/IntentService/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/IntentService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/eclipse/other/IntentService/root/AndroidManifest.xml.ftl b/templates/eclipse/other/IntentService/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/IntentService/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/IntentService/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/IntentService/root/src/app_package/IntentService.java.ftl b/templates/eclipse/other/IntentService/root/src/app_package/IntentService.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/IntentService/root/src/app_package/IntentService.java.ftl
rename to templates/eclipse/other/IntentService/root/src/app_package/IntentService.java.ftl
diff --git a/base/templates/eclipse/other/IntentService/template.xml b/templates/eclipse/other/IntentService/template.xml
similarity index 100%
rename from base/templates/eclipse/other/IntentService/template.xml
rename to templates/eclipse/other/IntentService/template.xml
diff --git a/templates/eclipse/other/JavaFolder/recipe.xml.ftl b/templates/eclipse/other/JavaFolder/recipe.xml.ftl
new file mode 100644
index 0000000..d7f34ac
--- /dev/null
+++ b/templates/eclipse/other/JavaFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/java/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/JavaFolder/root/build.gradle.ftl b/templates/eclipse/other/JavaFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/JavaFolder/root/build.gradle.ftl
rename to templates/eclipse/other/JavaFolder/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/JavaFolder/template.xml b/templates/eclipse/other/JavaFolder/template.xml
similarity index 100%
rename from base/templates/eclipse/other/JavaFolder/template.xml
rename to templates/eclipse/other/JavaFolder/template.xml
diff --git a/templates/eclipse/other/JniFolder/recipe.xml.ftl b/templates/eclipse/other/JniFolder/recipe.xml.ftl
new file mode 100644
index 0000000..c01b2fb
--- /dev/null
+++ b/templates/eclipse/other/JniFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/jni/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/JniFolder/root/build.gradle.ftl b/templates/eclipse/other/JniFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/JniFolder/root/build.gradle.ftl
rename to templates/eclipse/other/JniFolder/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/JniFolder/template.xml b/templates/eclipse/other/JniFolder/template.xml
similarity index 100%
rename from base/templates/eclipse/other/JniFolder/template.xml
rename to templates/eclipse/other/JniFolder/template.xml
diff --git a/templates/eclipse/other/LayoutResourceFile/recipe.xml.ftl b/templates/eclipse/other/LayoutResourceFile/recipe.xml.ftl
new file mode 100644
index 0000000..f5fafbe
--- /dev/null
+++ b/templates/eclipse/other/LayoutResourceFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="root/res/layout.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
+</recipe>
diff --git a/base/templates/eclipse/other/LayoutResourceFile/root/res/layout.xml.ftl b/templates/eclipse/other/LayoutResourceFile/root/res/layout.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/LayoutResourceFile/root/res/layout.xml.ftl
rename to templates/eclipse/other/LayoutResourceFile/root/res/layout.xml.ftl
diff --git a/base/templates/eclipse/other/LayoutResourceFile/template.xml b/templates/eclipse/other/LayoutResourceFile/template.xml
similarity index 100%
rename from base/templates/eclipse/other/LayoutResourceFile/template.xml
rename to templates/eclipse/other/LayoutResourceFile/template.xml
diff --git a/base/templates/eclipse/other/ListFragment/globals.xml.ftl b/templates/eclipse/other/ListFragment/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/globals.xml.ftl
rename to templates/eclipse/other/ListFragment/globals.xml.ftl
diff --git a/templates/eclipse/other/ListFragment/recipe.xml.ftl b/templates/eclipse/other/ListFragment/recipe.xml.ftl
new file mode 100644
index 0000000..82ed619
--- /dev/null
+++ b/templates/eclipse/other/ListFragment/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if useSupport><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
+<#if switchGrid == true>
+ <merge from="root/res/values/refs.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/refs.xml" />
+ <merge from="root/res/values/refs_lrg.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
+ <merge from="root/res/values/refs_lrg.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
+
+ <instantiate from="root/res/layout/fragment_grid.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout}_grid.xml" />
+
+ <instantiate from="root/res/layout/fragment_list.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout}_list.xml" />
+</#if>
+
+ <instantiate from="root/src/app_package/ListFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <instantiate from="root/src/app_package/dummy/DummyContent.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/base/templates/eclipse/other/ListFragment/root/res/layout/fragment_grid.xml b/templates/eclipse/other/ListFragment/root/res/layout/fragment_grid.xml
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/root/res/layout/fragment_grid.xml
rename to templates/eclipse/other/ListFragment/root/res/layout/fragment_grid.xml
diff --git a/base/templates/eclipse/other/ListFragment/root/res/layout/fragment_list.xml b/templates/eclipse/other/ListFragment/root/res/layout/fragment_list.xml
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/root/res/layout/fragment_list.xml
rename to templates/eclipse/other/ListFragment/root/res/layout/fragment_list.xml
diff --git a/base/templates/eclipse/other/ListFragment/root/res/values-large/refs_lrg.xml.ftl b/templates/eclipse/other/ListFragment/root/res/values-large/refs_lrg.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/root/res/values-large/refs_lrg.xml.ftl
rename to templates/eclipse/other/ListFragment/root/res/values-large/refs_lrg.xml.ftl
diff --git a/base/templates/eclipse/other/ListFragment/root/res/values-sw600dp/refs_lrg.xml.ftl b/templates/eclipse/other/ListFragment/root/res/values-sw600dp/refs_lrg.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/root/res/values-sw600dp/refs_lrg.xml.ftl
rename to templates/eclipse/other/ListFragment/root/res/values-sw600dp/refs_lrg.xml.ftl
diff --git a/base/templates/eclipse/other/ListFragment/root/res/values/refs.xml.ftl b/templates/eclipse/other/ListFragment/root/res/values/refs.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/root/res/values/refs.xml.ftl
rename to templates/eclipse/other/ListFragment/root/res/values/refs.xml.ftl
diff --git a/base/templates/eclipse/other/ListFragment/root/res/values/refs_lrg.xml.ftl b/templates/eclipse/other/ListFragment/root/res/values/refs_lrg.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/root/res/values/refs_lrg.xml.ftl
rename to templates/eclipse/other/ListFragment/root/res/values/refs_lrg.xml.ftl
diff --git a/base/templates/eclipse/other/ListFragment/root/src/app_package/ListFragment.java.ftl b/templates/eclipse/other/ListFragment/root/src/app_package/ListFragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/root/src/app_package/ListFragment.java.ftl
rename to templates/eclipse/other/ListFragment/root/src/app_package/ListFragment.java.ftl
diff --git a/base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl b/templates/eclipse/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl
similarity index 100%
rename from base/templates/eclipse/activities/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl
rename to templates/eclipse/other/ListFragment/root/src/app_package/dummy/DummyContent.java.ftl
diff --git a/base/templates/eclipse/other/ListFragment/template.xml b/templates/eclipse/other/ListFragment/template.xml
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/template.xml
rename to templates/eclipse/other/ListFragment/template.xml
diff --git a/base/templates/eclipse/other/ListFragment/templates_list_fragment.png b/templates/eclipse/other/ListFragment/templates_list_fragment.png
similarity index 100%
rename from base/templates/eclipse/other/ListFragment/templates_list_fragment.png
rename to templates/eclipse/other/ListFragment/templates_list_fragment.png
diff --git a/base/templates/eclipse/other/Notification/globals.xml.ftl b/templates/eclipse/other/Notification/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Notification/globals.xml.ftl
rename to templates/eclipse/other/Notification/globals.xml.ftl
diff --git a/templates/eclipse/other/Notification/recipe.xml.ftl b/templates/eclipse/other/Notification/recipe.xml.ftl
new file mode 100644
index 0000000..b36a0fe
--- /dev/null
+++ b/templates/eclipse/other/Notification/recipe.xml.ftl
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.android.support:support-v4:19.+"/>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <#if expandedStyle == "picture">
+ <copy from="root/res/drawable-nodpi/example_picture_large.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
+ <#else>
+ <copy from="root/res/drawable-nodpi/example_picture_small.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
+ </#if>
+
+ <#if moreActions>
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ </#if>
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/src/app_package/NotificationHelper.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/eclipse/other/Notification/root/AndroidManifest.xml.ftl b/templates/eclipse/other/Notification/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/Notification/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png b/templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png
rename to templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png b/templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png
rename to templates/eclipse/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png b/templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png
rename to templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png b/templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png
rename to templates/eclipse/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_large.png b/templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_large.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_large.png
rename to templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_large.png
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_small.png b/templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_small.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_small.png
rename to templates/eclipse/other/Notification/root/res/drawable-nodpi/example_picture_small.png
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png b/templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png
rename to templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png
diff --git a/base/templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png b/templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png
rename to templates/eclipse/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png
diff --git a/base/templates/eclipse/other/Notification/root/res/values/strings.xml.ftl b/templates/eclipse/other/Notification/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/res/values/strings.xml.ftl
rename to templates/eclipse/other/Notification/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/other/Notification/root/src/app_package/NotificationHelper.java.ftl b/templates/eclipse/other/Notification/root/src/app_package/NotificationHelper.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/Notification/root/src/app_package/NotificationHelper.java.ftl
rename to templates/eclipse/other/Notification/root/src/app_package/NotificationHelper.java.ftl
diff --git a/base/templates/eclipse/other/Notification/template.xml b/templates/eclipse/other/Notification/template.xml
similarity index 100%
rename from base/templates/eclipse/other/Notification/template.xml
rename to templates/eclipse/other/Notification/template.xml
diff --git a/base/templates/eclipse/other/Notification/template_notification_list.png b/templates/eclipse/other/Notification/template_notification_list.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_list.png
rename to templates/eclipse/other/Notification/template_notification_list.png
diff --git a/base/templates/eclipse/other/Notification/template_notification_list_actions.png b/templates/eclipse/other/Notification/template_notification_list_actions.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_list_actions.png
rename to templates/eclipse/other/Notification/template_notification_list_actions.png
diff --git a/base/templates/eclipse/other/Notification/template_notification_none.png b/templates/eclipse/other/Notification/template_notification_none.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_none.png
rename to templates/eclipse/other/Notification/template_notification_none.png
diff --git a/base/templates/eclipse/other/Notification/template_notification_none_actions.png b/templates/eclipse/other/Notification/template_notification_none_actions.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_none_actions.png
rename to templates/eclipse/other/Notification/template_notification_none_actions.png
diff --git a/base/templates/eclipse/other/Notification/template_notification_picture.png b/templates/eclipse/other/Notification/template_notification_picture.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_picture.png
rename to templates/eclipse/other/Notification/template_notification_picture.png
diff --git a/base/templates/eclipse/other/Notification/template_notification_picture_actions.png b/templates/eclipse/other/Notification/template_notification_picture_actions.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_picture_actions.png
rename to templates/eclipse/other/Notification/template_notification_picture_actions.png
diff --git a/base/templates/eclipse/other/Notification/template_notification_text.png b/templates/eclipse/other/Notification/template_notification_text.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_text.png
rename to templates/eclipse/other/Notification/template_notification_text.png
diff --git a/base/templates/eclipse/other/Notification/template_notification_text_actions.png b/templates/eclipse/other/Notification/template_notification_text_actions.png
similarity index 100%
rename from base/templates/eclipse/other/Notification/template_notification_text_actions.png
rename to templates/eclipse/other/Notification/template_notification_text_actions.png
diff --git a/base/templates/eclipse/other/PlusOneFragment/globals.xml.ftl b/templates/eclipse/other/PlusOneFragment/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/PlusOneFragment/globals.xml.ftl
rename to templates/eclipse/other/PlusOneFragment/globals.xml.ftl
diff --git a/templates/eclipse/other/PlusOneFragment/recipe.xml.ftl b/templates/eclipse/other/PlusOneFragment/recipe.xml.ftl
new file mode 100644
index 0000000..a7d558a
--- /dev/null
+++ b/templates/eclipse/other/PlusOneFragment/recipe.xml.ftl
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.android.support:support-v4:19.+"/>
+ <dependency mavenUrl="com.google.android.gms:play-services:4.2.42"/>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/layout/fragment_plus_one.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <instantiate from="root/src/app_package/PlusOneFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/base/templates/eclipse/other/PlusOneFragment/root/AndroidManifest.xml.ftl b/templates/eclipse/other/PlusOneFragment/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/PlusOneFragment/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/PlusOneFragment/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl b/templates/eclipse/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
rename to templates/eclipse/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
diff --git a/base/templates/eclipse/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl b/templates/eclipse/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
rename to templates/eclipse/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
diff --git a/base/templates/eclipse/other/PlusOneFragment/template.xml b/templates/eclipse/other/PlusOneFragment/template.xml
similarity index 100%
rename from base/templates/eclipse/other/PlusOneFragment/template.xml
rename to templates/eclipse/other/PlusOneFragment/template.xml
diff --git a/base/templates/eclipse/other/PlusOneFragment/templates_plusone_fragment.png b/templates/eclipse/other/PlusOneFragment/templates_plusone_fragment.png
similarity index 100%
rename from base/templates/eclipse/other/PlusOneFragment/templates_plusone_fragment.png
rename to templates/eclipse/other/PlusOneFragment/templates_plusone_fragment.png
diff --git a/templates/eclipse/other/ResFolder/recipe.xml.ftl b/templates/eclipse/other/ResFolder/recipe.xml.ftl
new file mode 100644
index 0000000..132a260
--- /dev/null
+++ b/templates/eclipse/other/ResFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/res/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/ResFolder/root/build.gradle.ftl b/templates/eclipse/other/ResFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/ResFolder/root/build.gradle.ftl
rename to templates/eclipse/other/ResFolder/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/ResFolder/template.xml b/templates/eclipse/other/ResFolder/template.xml
similarity index 100%
rename from base/templates/eclipse/other/ResFolder/template.xml
rename to templates/eclipse/other/ResFolder/template.xml
diff --git a/templates/eclipse/other/ResourcesFolder/recipe.xml.ftl b/templates/eclipse/other/ResourcesFolder/recipe.xml.ftl
new file mode 100644
index 0000000..78702cf
--- /dev/null
+++ b/templates/eclipse/other/ResourcesFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/resources/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/ResourcesFolder/root/build.gradle.ftl b/templates/eclipse/other/ResourcesFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/ResourcesFolder/root/build.gradle.ftl
rename to templates/eclipse/other/ResourcesFolder/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/ResourcesFolder/template.xml b/templates/eclipse/other/ResourcesFolder/template.xml
similarity index 100%
rename from base/templates/eclipse/other/ResourcesFolder/template.xml
rename to templates/eclipse/other/ResourcesFolder/template.xml
diff --git a/templates/eclipse/other/RsFolder/recipe.xml.ftl b/templates/eclipse/other/RsFolder/recipe.xml.ftl
new file mode 100644
index 0000000..ecb4f51
--- /dev/null
+++ b/templates/eclipse/other/RsFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/rs/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/eclipse/other/RsFolder/root/build.gradle.ftl b/templates/eclipse/other/RsFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/other/RsFolder/root/build.gradle.ftl
rename to templates/eclipse/other/RsFolder/root/build.gradle.ftl
diff --git a/base/templates/eclipse/other/RsFolder/template.xml b/templates/eclipse/other/RsFolder/template.xml
similarity index 100%
rename from base/templates/eclipse/other/RsFolder/template.xml
rename to templates/eclipse/other/RsFolder/template.xml
diff --git a/base/templates/eclipse/other/Service/globals.xml.ftl b/templates/eclipse/other/Service/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Service/globals.xml.ftl
rename to templates/eclipse/other/Service/globals.xml.ftl
diff --git a/templates/eclipse/other/Service/recipe.xml.ftl b/templates/eclipse/other/Service/recipe.xml.ftl
new file mode 100644
index 0000000..bd6a14a
--- /dev/null
+++ b/templates/eclipse/other/Service/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/Service.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/eclipse/other/Service/root/AndroidManifest.xml.ftl b/templates/eclipse/other/Service/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/Service/root/AndroidManifest.xml.ftl
rename to templates/eclipse/other/Service/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/other/Service/root/src/app_package/Service.java.ftl b/templates/eclipse/other/Service/root/src/app_package/Service.java.ftl
similarity index 100%
rename from base/templates/eclipse/other/Service/root/src/app_package/Service.java.ftl
rename to templates/eclipse/other/Service/root/src/app_package/Service.java.ftl
diff --git a/base/templates/eclipse/other/Service/template.xml b/templates/eclipse/other/Service/template.xml
similarity index 100%
rename from base/templates/eclipse/other/Service/template.xml
rename to templates/eclipse/other/Service/template.xml
diff --git a/templates/eclipse/other/ValueResourceFile/recipe.xml.ftl b/templates/eclipse/other/ValueResourceFile/recipe.xml.ftl
new file mode 100644
index 0000000..6b47992
--- /dev/null
+++ b/templates/eclipse/other/ValueResourceFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="root/res/values.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
+</recipe>
diff --git a/base/templates/eclipse/other/ValueResourceFile/root/res/values.xml.ftl b/templates/eclipse/other/ValueResourceFile/root/res/values.xml.ftl
similarity index 100%
rename from base/templates/eclipse/other/ValueResourceFile/root/res/values.xml.ftl
rename to templates/eclipse/other/ValueResourceFile/root/res/values.xml.ftl
diff --git a/base/templates/eclipse/other/ValueResourceFile/template.xml b/templates/eclipse/other/ValueResourceFile/template.xml
similarity index 100%
rename from base/templates/eclipse/other/ValueResourceFile/template.xml
rename to templates/eclipse/other/ValueResourceFile/template.xml
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/globals.xml.ftl b/templates/eclipse/projects/NewAndroidApplication/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/globals.xml.ftl
rename to templates/eclipse/projects/NewAndroidApplication/globals.xml.ftl
diff --git a/templates/eclipse/projects/NewAndroidApplication/recipe.xml.ftl b/templates/eclipse/projects/NewAndroidApplication/recipe.xml.ftl
new file mode 100644
index 0000000..8e07390
--- /dev/null
+++ b/templates/eclipse/projects/NewAndroidApplication/recipe.xml.ftl
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<recipe>
+ <instantiate from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+<#if copyIcons>
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+</#if>
+ <instantiate from="root/res/values/styles.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+<#if buildApi gte 11 && baseTheme != "none">
+ <instantiate from="root/res/values-v11/styles_hc.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
+</#if>
+<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
+ <instantiate from="root/res/values-v14/styles_ics.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
+</#if>
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+</recipe>
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/AndroidManifest.xml.ftl b/templates/eclipse/projects/NewAndroidApplication/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/AndroidManifest.xml.ftl
rename to templates/eclipse/projects/NewAndroidApplication/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/build.gradle.ftl b/templates/eclipse/projects/NewAndroidApplication/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/build.gradle.ftl
rename to templates/eclipse/projects/NewAndroidApplication/root/build.gradle.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png b/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png
rename to templates/eclipse/projects/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
rename to templates/eclipse/projects/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png b/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png
rename to templates/eclipse/projects/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl b/templates/eclipse/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
rename to templates/eclipse/projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml.ftl b/templates/eclipse/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml.ftl
rename to templates/eclipse/projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/res/values/strings.xml.ftl b/templates/eclipse/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
rename to templates/eclipse/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/res/values/styles.xml.ftl b/templates/eclipse/projects/NewAndroidApplication/root/res/values/styles.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/res/values/styles.xml.ftl
rename to templates/eclipse/projects/NewAndroidApplication/root/res/values/styles.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/root/settings.gradle.ftl b/templates/eclipse/projects/NewAndroidApplication/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/root/settings.gradle.ftl
rename to templates/eclipse/projects/NewAndroidApplication/root/settings.gradle.ftl
diff --git a/templates/eclipse/projects/NewAndroidApplication/template.xml b/templates/eclipse/projects/NewAndroidApplication/template.xml
new file mode 100644
index 0000000..7683ae2
--- /dev/null
+++ b/templates/eclipse/projects/NewAndroidApplication/template.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<template
+ format="1"
+ revision="2"
+ name="Android Application"
+ description="Creates a new Android application.">
+ <dependency name="android-support-v4" revision="8" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <category value="Applications" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package|nonempty"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Application title"
+ type="string"
+ constraints="nonempty"
+ default="My Application" />
+
+ <parameter
+ id="baseTheme"
+ name="Base Theme"
+ type="enum"
+ default="holo_light_darkactionbar"
+ help="The base user interface theme for the application">
+ <option id="none">None</option>
+ <option id="holo_dark" minBuildApi="11">Holo Dark</option>
+ <option id="holo_light" minBuildApi="11">Holo Light</option>
+ <option id="holo_light_darkactionbar" minBuildApi="14">Holo Light with Dark Action Bar</option>
+ </parameter>
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"
+ default="16" />
+
+ <parameter
+ id="buildApi"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="16" />
+
+ <parameter
+ id="copyIcons"
+ name="Include launcher icons"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/eclipse/projects/NewAndroidApplication/template_new_project.png b/templates/eclipse/projects/NewAndroidApplication/template_new_project.png
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidApplication/template_new_project.png
rename to templates/eclipse/projects/NewAndroidApplication/template_new_project.png
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/globals.xml.ftl b/templates/eclipse/projects/NewAndroidLibrary/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/globals.xml.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/globals.xml.ftl
diff --git a/templates/eclipse/projects/NewAndroidLibrary/recipe.xml.ftl b/templates/eclipse/projects/NewAndroidLibrary/recipe.xml.ftl
new file mode 100644
index 0000000..8e07390
--- /dev/null
+++ b/templates/eclipse/projects/NewAndroidLibrary/recipe.xml.ftl
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<recipe>
+ <instantiate from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+<#if copyIcons>
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+</#if>
+ <instantiate from="root/res/values/styles.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+<#if buildApi gte 11 && baseTheme != "none">
+ <instantiate from="root/res/values-v11/styles_hc.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
+</#if>
+<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
+ <instantiate from="root/res/values-v14/styles_ics.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
+</#if>
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+</recipe>
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/AndroidManifest.xml.ftl b/templates/eclipse/projects/NewAndroidLibrary/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/AndroidManifest.xml.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/root/AndroidManifest.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/build.gradle.ftl b/templates/eclipse/projects/NewAndroidLibrary/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/build.gradle.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/root/build.gradle.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
rename to templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
diff --git a/base/apps/SdkController/res/drawable-mdpi/ic_launcher.png b/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/apps/SdkController/res/drawable-mdpi/ic_launcher.png
rename to templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
rename to templates/eclipse/projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl b/templates/eclipse/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml.ftl b/templates/eclipse/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/res/values/strings.xml.ftl b/templates/eclipse/projects/NewAndroidLibrary/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/res/values/strings.xml.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/root/res/values/strings.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl b/templates/eclipse/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/root/settings.gradle.ftl b/templates/eclipse/projects/NewAndroidLibrary/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/root/settings.gradle.ftl
rename to templates/eclipse/projects/NewAndroidLibrary/root/settings.gradle.ftl
diff --git a/templates/eclipse/projects/NewAndroidLibrary/template.xml b/templates/eclipse/projects/NewAndroidLibrary/template.xml
new file mode 100644
index 0000000..a5a274c
--- /dev/null
+++ b/templates/eclipse/projects/NewAndroidLibrary/template.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<template
+ format="1"
+ revision="2"
+ name="Android Library"
+ description="Creates a new Android library.">
+ <dependency name="android-support-v4" revision="8" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <category value="Applications" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package|nonempty"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Library title"
+ type="string"
+ constraints="nonempty"
+ default="My Library"/>
+
+ <parameter
+ id="baseTheme"
+ name="Base Theme"
+ type="enum"
+ default="holo_light_darkactionbar"
+ help="The base user interface theme for the library">
+ <option id="none">None</option>
+ <option id="holo_dark" minBuildApi="11">Holo Dark</option>
+ <option id="holo_light" minBuildApi="11">Holo Light</option>
+ <option id="holo_light_darkactionbar" minBuildApi="14">Holo Light with Dark Action Bar</option>
+ </parameter>
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"
+ default="16" />
+
+ <parameter
+ id="buildApi"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="16" />
+
+ <parameter
+ id="copyIcons"
+ name="Include launcher icons"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/eclipse/projects/NewAndroidLibrary/template_new_project.png b/templates/eclipse/projects/NewAndroidLibrary/template_new_project.png
similarity index 100%
rename from base/templates/eclipse/projects/NewAndroidLibrary/template_new_project.png
rename to templates/eclipse/projects/NewAndroidLibrary/template_new_project.png
diff --git a/base/templates/eclipse/projects/NewJavaLibrary/globals.xml.ftl b/templates/eclipse/projects/NewJavaLibrary/globals.xml.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewJavaLibrary/globals.xml.ftl
rename to templates/eclipse/projects/NewJavaLibrary/globals.xml.ftl
diff --git a/templates/eclipse/projects/NewJavaLibrary/recipe.xml.ftl b/templates/eclipse/projects/NewJavaLibrary/recipe.xml.ftl
new file mode 100644
index 0000000..29bfccd
--- /dev/null
+++ b/templates/eclipse/projects/NewJavaLibrary/recipe.xml.ftl
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<recipe>
+ <instantiate from="root/src/library_package/Placeholder.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/eclipse/projects/NewJavaLibrary/root/build.gradle.ftl b/templates/eclipse/projects/NewJavaLibrary/root/build.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewJavaLibrary/root/build.gradle.ftl
rename to templates/eclipse/projects/NewJavaLibrary/root/build.gradle.ftl
diff --git a/base/templates/eclipse/projects/NewJavaLibrary/root/settings.gradle.ftl b/templates/eclipse/projects/NewJavaLibrary/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewJavaLibrary/root/settings.gradle.ftl
rename to templates/eclipse/projects/NewJavaLibrary/root/settings.gradle.ftl
diff --git a/base/templates/eclipse/projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl b/templates/eclipse/projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl
similarity index 100%
rename from base/templates/eclipse/projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl
rename to templates/eclipse/projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl
diff --git a/base/templates/eclipse/projects/NewJavaLibrary/template.xml b/templates/eclipse/projects/NewJavaLibrary/template.xml
similarity index 100%
rename from base/templates/eclipse/projects/NewJavaLibrary/template.xml
rename to templates/eclipse/projects/NewJavaLibrary/template.xml
diff --git a/base/templates/eclipse/projects/NewJavaLibrary/template_new_project.png b/templates/eclipse/projects/NewJavaLibrary/template_new_project.png
similarity index 100%
rename from base/templates/eclipse/projects/NewJavaLibrary/template_new_project.png
rename to templates/eclipse/projects/NewJavaLibrary/template_new_project.png
diff --git a/base/templates/gradle-projects/AndroidWearModule/globals.xml.ftl b/templates/gradle-projects/AndroidWearModule/globals.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/globals.xml.ftl
rename to templates/gradle-projects/AndroidWearModule/globals.xml.ftl
diff --git a/templates/gradle-projects/AndroidWearModule/recipe.xml.ftl b/templates/gradle-projects/AndroidWearModule/recipe.xml.ftl
new file mode 100644
index 0000000..8f2ae57
--- /dev/null
+++ b/templates/gradle-projects/AndroidWearModule/recipe.xml.ftl
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<recipe>
+<#if !createActivity>
+ <mkdir at="${escapeXmlAttribute(srcOut)}" />
+</#if>
+
+ <dependency mavenUrl="com.google.android.support:wearable:+" />
+ <dependency mavenUrl="com.google.android.gms:play-services-wearable:+" />
+
+ <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
+
+ <merge from="root/settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+ <instantiate from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <instantiate from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+<#if makeIgnore>
+ <copy from="root/module_ignore"
+ to="${escapeXmlAttribute(projectOut)}/.gitignore" />
+</#if>
+<#if enableProGuard>
+ <instantiate from="root/proguard-rules.txt.ftl"
+ to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
+</#if>
+ <mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
+ <copy from="root/res/mipmap-hdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
+ <copy from="root/res/mipmap-mdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
+ <copy from="root/res/mipmap-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
+ <copy from="root/res/mipmap-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+</recipe>
diff --git a/templates/gradle-projects/AndroidWearModule/root/AndroidManifest.xml.ftl b/templates/gradle-projects/AndroidWearModule/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..bb17617
--- /dev/null
+++ b/templates/gradle-projects/AndroidWearModule/root/AndroidManifest.xml.ftl
@@ -0,0 +1,12 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="${packageName}">
+ <uses-feature android:name="android.hardware.type.watch" />
+ <application android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ <#if buildApi gte 17>android:supportsRtl="true"</#if>
+ android:theme="@android:style/Theme.DeviceDefault">
+
+ </application>
+
+</manifest>
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/build.gradle.ftl b/templates/gradle-projects/AndroidWearModule/root/build.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/build.gradle.ftl
rename to templates/gradle-projects/AndroidWearModule/root/build.gradle.ftl
diff --git a/base/templates/gradle-projects/NewGlassModule/root/module_ignore b/templates/gradle-projects/AndroidWearModule/root/module_ignore
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/module_ignore
rename to templates/gradle-projects/AndroidWearModule/root/module_ignore
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/proguard-rules.txt.ftl b/templates/gradle-projects/AndroidWearModule/root/proguard-rules.txt.ftl
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/proguard-rules.txt.ftl
rename to templates/gradle-projects/AndroidWearModule/root/proguard-rules.txt.ftl
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-hdpi/ic_launcher.png b/templates/gradle-projects/AndroidWearModule/root/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-hdpi/ic_launcher.png
rename to templates/gradle-projects/AndroidWearModule/root/res/mipmap-hdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-mdpi/ic_launcher.png b/templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-mdpi/ic_launcher.png
rename to templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xhdpi/ic_launcher.png b/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xhdpi/ic_launcher.png
rename to templates/gradle-projects/AndroidWearModule/root/res/mipmap-xhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxhdpi/ic_launcher.png b/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxhdpi/ic_launcher.png
rename to templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxxhdpi/ic_launcher.png b/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxxhdpi/ic_launcher.png
rename to templates/gradle-projects/AndroidWearModule/root/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/values/strings.xml.ftl b/templates/gradle-projects/AndroidWearModule/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/res/values/strings.xml.ftl
rename to templates/gradle-projects/AndroidWearModule/root/res/values/strings.xml.ftl
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/settings.gradle.ftl b/templates/gradle-projects/AndroidWearModule/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/settings.gradle.ftl
rename to templates/gradle-projects/AndroidWearModule/root/settings.gradle.ftl
diff --git a/templates/gradle-projects/AndroidWearModule/template.xml b/templates/gradle-projects/AndroidWearModule/template.xml
new file mode 100644
index 0000000..57741ca
--- /dev/null
+++ b/templates/gradle-projects/AndroidWearModule/template.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Android Wear Module"
+ description="Creates a new Android Wear Module."
+ minApi="20"
+ minBuildApi="20">
+
+ <category value="Application" />
+
+ <formfactor value="Wear" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="app_package|nonempty"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Module title"
+ type="string"
+ constraints="nonempty"
+ default="My Wear App" />
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="4.4W" />
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="20" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"
+ default="20" />
+
+ <!--
+ Usually the same as targetApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="targetApiString"
+ name="Target API"
+ type="string"
+ constraints="apilevel"
+ default="4.4W" />
+
+ <parameter
+ id="buildApi"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="20" />
+
+ <!--
+ Usually the same as buildApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="buildApiString"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="4.4W" />
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="enableProGuard"
+ name="Enable ProGuard"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/AndroidWearModule/template_new_project.png b/templates/gradle-projects/AndroidWearModule/template_new_project.png
new file mode 100644
index 0000000..eeeb601
Binary files /dev/null and b/templates/gradle-projects/AndroidWearModule/template_new_project.png differ
diff --git a/templates/gradle-projects/ImportExistingProject/template.xml b/templates/gradle-projects/ImportExistingProject/template.xml
new file mode 100644
index 0000000..ec535e3
--- /dev/null
+++ b/templates/gradle-projects/ImportExistingProject/template.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Import Existing Project"
+ description="Import existing Eclipse ADT or Gradle project as a module">
+
+ <category value="Application" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+</template>
diff --git a/templates/gradle-projects/ImportExistingProject/template_new_project.png b/templates/gradle-projects/ImportExistingProject/template_new_project.png
new file mode 100644
index 0000000..bfce86c
Binary files /dev/null and b/templates/gradle-projects/ImportExistingProject/template_new_project.png differ
diff --git a/templates/gradle-projects/NewAndroidAutoProject/template.xml b/templates/gradle-projects/NewAndroidAutoProject/template.xml
new file mode 100755
index 0000000..11558a0
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidAutoProject/template.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Android Auto Project"
+ minApi="21"
+ description="Creates a new Android Auto project.">
+
+ <category value="Application" />
+ <formfactor value="Car" />
+
+</template>
diff --git a/templates/gradle-projects/NewAndroidModule/globals.xml.ftl b/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
new file mode 100644
index 0000000..4c534e8
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="topOut" value="." />
+ <global id="projectOut" value="." />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="testOut" value="androidTest/${slashedPackageName(packageName)}" />
+ <global id="unitTestOut" value="${escapeXmlAttribute(projectOut)}/src/test/java/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="mavenUrl" value="mavenCentral" />
+ <global id="buildToolsVersion" value="18.0.1" />
+ <global id="gradlePluginVersion" value="0.6.+" />
+ <global id="junitVersion" value="4.12" />
+ <global id="unitTestsSupported" type="boolean" value="${(compareVersions(gradlePluginVersion, '1.1.0') >= 0)?string}" />
+</globals>
diff --git a/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl b/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
new file mode 100644
index 0000000..11447cd
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.android.support:appcompat-v7:${buildApi}.+"/>
+
+<#if !createActivity>
+ <mkdir at="${escapeXmlAttribute(srcOut)}" />
+</#if>
+
+ <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
+
+ <merge from="root/settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+ <instantiate from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <instantiate from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+<mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
+<#if copyIcons && !isLibraryProject>
+ <copy from="root/res/mipmap-hdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
+ <copy from="root/res/mipmap-mdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
+ <copy from="root/res/mipmap-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
+ <copy from="root/res/mipmap-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
+ <copy from="root/res/mipmap-xxxhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xxxhdpi" />
+</#if>
+<#if makeIgnore>
+ <copy from="root/module_ignore"
+ to="${escapeXmlAttribute(projectOut)}/.gitignore" />
+</#if>
+<#if enableProGuard>
+ <instantiate from="root/proguard-rules.txt.ftl"
+ to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
+</#if>
+<#if !(isLibraryProject??) || !isLibraryProject>
+ <instantiate from="root/res/values/styles.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+<#if buildApi gte 22>
+ <copy from="root/res/values/colors.xml"
+ to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
+</#if>
+</#if>
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/test/app_package/ApplicationTest.java.ftl"
+ to="${escapeXmlAttribute(testOut)}/ApplicationTest.java" />
+
+<#if unitTestsSupported>
+ <instantiate from="root/test/app_package/ExampleUnitTest.java.ftl"
+ to="${escapeXmlAttribute(unitTestOut)}/ExampleUnitTest.java" />
+</#if>
+
+</recipe>
diff --git a/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl b/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..077b8b3
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
@@ -0,0 +1,14 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="${packageName}">
+
+ <application <#if minApiLevel gte 4 && buildApi gte 4>android:allowBackup="true"</#if>
+ android:label="@string/app_name"<#if copyIcons && !isLibraryProject>
+ android:icon="@mipmap/ic_launcher"<#elseif assetName??>
+ android:icon="@drawable/${assetName}"</#if>
+ <#if buildApi gte 17>android:supportsRtl="true"</#if>
+ <#if baseTheme != "none" && !isLibraryProject>
+ android:theme="@style/AppTheme"</#if>>
+
+ </application>
+
+</manifest>
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
rename to templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/module_ignore b/templates/gradle-projects/NewAndroidModule/root/module_ignore
similarity index 100%
copy from base/templates/gradle-projects/AndroidWearModule/root/module_ignore
copy to templates/gradle-projects/NewAndroidModule/root/module_ignore
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl b/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
rename to templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-anydpi/test.svg b/templates/gradle-projects/NewAndroidModule/root/res/mipmap-anydpi/test.svg
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-anydpi/test.svg
rename to templates/gradle-projects/NewAndroidModule/root/res/mipmap-anydpi/test.svg
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-hdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-hdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/mipmap-hdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewGlassModule/root/res/mipmap-mdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/res/mipmap-mdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/mipmap-mdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xhdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/mipmap-xhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxhdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxxhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxxhdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/templates/gradle-projects/NewAndroidModule/root/res/values/colors.xml b/templates/gradle-projects/NewAndroidModule/root/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/res/values/strings.xml.ftl b/templates/gradle-projects/NewAndroidModule/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/res/values/strings.xml.ftl
rename to templates/gradle-projects/NewAndroidModule/root/res/values/strings.xml.ftl
diff --git a/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl b/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl
new file mode 100644
index 0000000..796b60d
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl
@@ -0,0 +1,15 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat<#if
+ baseTheme?contains("light")>.Light<#if
+ baseTheme?contains("darkactionbar")>.DarkActionBar</#if></#if>">
+ <!-- Customize your theme here. -->
+<#if buildApi gte 22>
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+</#if>
+ </style>
+
+</resources>
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/root/settings.gradle.ftl b/templates/gradle-projects/NewAndroidModule/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidAutoProject/root/settings.gradle.ftl
rename to templates/gradle-projects/NewAndroidModule/root/settings.gradle.ftl
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/test/app_package/ApplicationTest.java.ftl b/templates/gradle-projects/NewAndroidModule/root/test/app_package/ApplicationTest.java.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/test/app_package/ApplicationTest.java.ftl
rename to templates/gradle-projects/NewAndroidModule/root/test/app_package/ApplicationTest.java.ftl
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/test/app_package/ExampleUnitTest.java.ftl b/templates/gradle-projects/NewAndroidModule/root/test/app_package/ExampleUnitTest.java.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/test/app_package/ExampleUnitTest.java.ftl
rename to templates/gradle-projects/NewAndroidModule/root/test/app_package/ExampleUnitTest.java.ftl
diff --git a/templates/gradle-projects/NewAndroidModule/template.xml b/templates/gradle-projects/NewAndroidModule/template.xml
new file mode 100644
index 0000000..9180dcc
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/template.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="5"
+ name="Android Module"
+ description="Creates a new Android module.">
+
+ <category value="Application" />
+
+ <formfactor value="Mobile" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="app_package|nonempty"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Module title"
+ type="string"
+ constraints="nonempty"
+ default="My Module" />
+
+ <parameter
+ id="baseTheme"
+ name="Base Theme"
+ type="enum"
+ default="holo_light_darkactionbar"
+ help="The base user interface theme for the module">
+ <option id="none">None</option>
+ <option id="holo_dark" minBuildApi="11">Holo Dark</option>
+ <option id="holo_light" minBuildApi="11">Holo Light</option>
+ <option id="holo_light_darkactionbar" minBuildApi="14">Holo Light with Dark Action Bar</option>
+ </parameter>
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="8" />
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="8" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"
+ default="19" />
+
+ <!--
+ Usually the same as targetApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="targetApiString"
+ name="Target API"
+ type="string"
+ constraints="apilevel"
+ default="19" />
+
+ <parameter
+ id="buildApi"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="19" />
+
+ <!--
+ Usually the same as buildApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="buildApiString"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="19" />
+
+ <parameter
+ id="copyIcons"
+ name="Include launcher icons"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="enableProGuard"
+ name="Enable ProGuard"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/NewAndroidModule/template_new_project.png b/templates/gradle-projects/NewAndroidModule/template_new_project.png
new file mode 100644
index 0000000..2f329dc
Binary files /dev/null and b/templates/gradle-projects/NewAndroidModule/template_new_project.png differ
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/globals.xml.ftl b/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidAutoProject/globals.xml.ftl
rename to templates/gradle-projects/NewAndroidProject/globals.xml.ftl
diff --git a/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl b/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
new file mode 100644
index 0000000..ce4c4de
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<recipe>
+ <instantiate from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/build.gradle" />
+
+<#if makeIgnore>
+ <copy from="root/project_ignore"
+ to="${escapeXmlAttribute(topOut)}/.gitignore" />
+</#if>
+
+ <instantiate from="root/settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+
+ <instantiate from="root/gradle.properties.ftl"
+ to="${escapeXmlAttribute(topOut)}/gradle.properties" />
+
+ <copy from="../../gradle/wrapper"
+ to="${escapeXmlAttribute(topOut)}/" />
+
+<#if sdkDir??>
+ <instantiate from="root/local.properties.ftl"
+ to="${escapeXmlAttribute(topOut)}/local.properties" />
+</#if>
+</recipe>
diff --git a/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
new file mode 100644
index 0000000..e60d8c0
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
@@ -0,0 +1,33 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+<#if mavenUrl != "mavenCentral">
+ maven {
+ url '${mavenUrl}'
+ }
+</#if>
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+<#if mavenUrl != "mavenCentral">
+ maven {
+ url '${mavenUrl}'
+ }
+</#if>
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/root/gradle.properties.ftl b/templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidAutoProject/root/gradle.properties.ftl
rename to templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl
diff --git a/base/templates/gradle-projects/NewAndroidAutoProject/root/local.properties.ftl b/templates/gradle-projects/NewAndroidProject/root/local.properties.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidAutoProject/root/local.properties.ftl
rename to templates/gradle-projects/NewAndroidProject/root/local.properties.ftl
diff --git a/templates/gradle-projects/NewAndroidProject/root/project_ignore b/templates/gradle-projects/NewAndroidProject/root/project_ignore
new file mode 100644
index 0000000..c6cbe56
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/root/project_ignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/base/templates/gradle-projects/NewAndroidProject/root/settings.gradle.ftl b/templates/gradle-projects/NewAndroidProject/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidProject/root/settings.gradle.ftl
rename to templates/gradle-projects/NewAndroidProject/root/settings.gradle.ftl
diff --git a/templates/gradle-projects/NewAndroidProject/template.xml b/templates/gradle-projects/NewAndroidProject/template.xml
new file mode 100644
index 0000000..f756934
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/template.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Android Project"
+ description="Creates a new Android project.">
+
+ <category value="Application" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/NewAndroidProject/template_new_project.png b/templates/gradle-projects/NewAndroidProject/template_new_project.png
new file mode 100644
index 0000000..6f35d05
Binary files /dev/null and b/templates/gradle-projects/NewAndroidProject/template_new_project.png differ
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/globals.xml.ftl b/templates/gradle-projects/NewAndroidTVModule/globals.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/globals.xml.ftl
rename to templates/gradle-projects/NewAndroidTVModule/globals.xml.ftl
diff --git a/templates/gradle-projects/NewAndroidTVModule/recipe.xml.ftl b/templates/gradle-projects/NewAndroidTVModule/recipe.xml.ftl
new file mode 100644
index 0000000..004854a
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidTVModule/recipe.xml.ftl
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:recyclerview-v7:${targetApi}.+" />
+ <dependency mavenUrl="com.android.support:leanback-v17:${targetApi}.+" />
+ <mkdir at="${escapeXmlAttribute(srcOut)}" />
+
+ <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
+
+ <merge from="root/settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+ <instantiate from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <instantiate from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+<#if copyIcons>
+ <mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
+ <copy from="root/res/mipmap-hdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
+ <copy from="root/res/mipmap-mdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
+ <copy from="root/res/mipmap-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
+ <copy from="root/res/mipmap-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
+</#if>
+<#if makeIgnore>
+ <copy from="root/module_ignore"
+ to="${escapeXmlAttribute(projectOut)}/.gitignore" />
+</#if>
+<#if enableProGuard>
+ <instantiate from="root/proguard-rules.txt.ftl"
+ to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
+</#if>
+ <instantiate from="root/res/values/styles.xml"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/test/app_package/ApplicationTest.java.ftl"
+ to="${escapeXmlAttribute(testOut)}/ApplicationTest.java" />
+
+</recipe>
diff --git a/templates/gradle-projects/NewAndroidTVModule/root/AndroidManifest.xml.ftl b/templates/gradle-projects/NewAndroidTVModule/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..4763573
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidTVModule/root/AndroidManifest.xml.ftl
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="${packageName}">
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"<#if copyIcons>
+ android:icon="@mipmap/ic_launcher"<#else>
+ android:icon="@drawable/${assetName}"</#if>
+ <#if buildApi gte 17>android:supportsRtl="true"</#if>
+ android:theme="@style/Theme.Leanback">
+
+ </application>
+
+</manifest>
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidTVModule/root/build.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/build.gradle.ftl
rename to templates/gradle-projects/NewAndroidTVModule/root/build.gradle.ftl
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/module_ignore b/templates/gradle-projects/NewAndroidTVModule/root/module_ignore
similarity index 100%
copy from base/templates/gradle-projects/AndroidWearModule/root/module_ignore
copy to templates/gradle-projects/NewAndroidTVModule/root/module_ignore
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/proguard-rules.txt.ftl b/templates/gradle-projects/NewAndroidTVModule/root/proguard-rules.txt.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/proguard-rules.txt.ftl
rename to templates/gradle-projects/NewAndroidTVModule/root/proguard-rules.txt.ftl
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-hdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-hdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-hdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
copy from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png
copy to templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-mdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewGlassModule/root/res/mipmap-xhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/res/mipmap-xhdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxhdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxxhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxxhdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidTVModule/root/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/res/values/strings.xml.ftl b/templates/gradle-projects/NewAndroidTVModule/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/res/values/strings.xml.ftl
rename to templates/gradle-projects/NewAndroidTVModule/root/res/values/strings.xml.ftl
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/res/values/styles.xml b/templates/gradle-projects/NewAndroidTVModule/root/res/values/styles.xml
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/res/values/styles.xml
rename to templates/gradle-projects/NewAndroidTVModule/root/res/values/styles.xml
diff --git a/base/templates/gradle-projects/NewAndroidModule/root/settings.gradle.ftl b/templates/gradle-projects/NewAndroidTVModule/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidModule/root/settings.gradle.ftl
rename to templates/gradle-projects/NewAndroidTVModule/root/settings.gradle.ftl
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/test/app_package/ApplicationTest.java.ftl b/templates/gradle-projects/NewAndroidTVModule/root/test/app_package/ApplicationTest.java.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/test/app_package/ApplicationTest.java.ftl
rename to templates/gradle-projects/NewAndroidTVModule/root/test/app_package/ApplicationTest.java.ftl
diff --git a/templates/gradle-projects/NewAndroidTVModule/template.xml b/templates/gradle-projects/NewAndroidTVModule/template.xml
new file mode 100644
index 0000000..642a6c6
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidTVModule/template.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Android TV Module"
+ description="Creates a new Android TV module."
+ minApi="21"
+ minBuildApi="21">
+
+ <category value="Application" />
+
+ <formfactor value="TV" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="app_package|nonempty"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Module title"
+ type="string"
+ constraints="nonempty"
+ default="My Module" />
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"/>
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="21" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"/>
+
+ <!--
+ Usually the same as targetApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="targetApiString"
+ name="Target API"
+ type="string"
+ constraints="apilevel"/>
+
+ <parameter
+ id="buildApi"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"/>
+
+ <!--
+ Usually the same as buildApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="buildApiString"
+ name="Build API level"
+ type="string"
+ constraints="apilevel" />
+
+ <parameter
+ id="copyIcons"
+ name="Include launcher icons"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="enableProGuard"
+ name="Enable ProGuard"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/NewAndroidTVModule/template_new_project.png b/templates/gradle-projects/NewAndroidTVModule/template_new_project.png
new file mode 100644
index 0000000..654d62c
Binary files /dev/null and b/templates/gradle-projects/NewAndroidTVModule/template_new_project.png differ
diff --git a/base/templates/gradle-projects/NewGlassModule/globals.xml.ftl b/templates/gradle-projects/NewGlassModule/globals.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/globals.xml.ftl
rename to templates/gradle-projects/NewGlassModule/globals.xml.ftl
diff --git a/templates/gradle-projects/NewGlassModule/recipe.xml.ftl b/templates/gradle-projects/NewGlassModule/recipe.xml.ftl
new file mode 100644
index 0000000..2d562aa
--- /dev/null
+++ b/templates/gradle-projects/NewGlassModule/recipe.xml.ftl
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<recipe>
+ <mkdir at="${escapeXmlAttribute(srcOut)}" />
+
+ <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
+
+ <merge from="root/settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+ <instantiate from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <instantiate from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+<#if copyIcons>
+ <mkdir at="${escapeXmlAttribute(resOut)}/drawable" />
+ <copy from="root/res/mipmap-hdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-hdpi" />
+ <copy from="root/res/mipmap-mdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-mdpi" />
+ <copy from="root/res/mipmap-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xhdpi" />
+ <copy from="root/res/mipmap-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/mipmap-xxhdpi" />
+</#if>
+<#if makeIgnore>
+ <copy from="root/module_ignore"
+ to="${escapeXmlAttribute(projectOut)}/.gitignore" />
+</#if>
+<#if enableProGuard>
+ <instantiate from="root/proguard-rules.txt.ftl"
+ to="${escapeXmlAttribute(projectOut)}/proguard-rules.pro" />
+</#if>
+ <instantiate from="root/res/values/styles.xml"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+
+ <instantiate from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/test/app_package/ApplicationTest.java.ftl"
+ to="${escapeXmlAttribute(testOut)}/ApplicationTest.java" />
+
+</recipe>
diff --git a/templates/gradle-projects/NewGlassModule/root/AndroidManifest.xml.ftl b/templates/gradle-projects/NewGlassModule/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..ee30ce5
--- /dev/null
+++ b/templates/gradle-projects/NewGlassModule/root/AndroidManifest.xml.ftl
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="${packageName}">
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"<#if copyIcons>
+ android:icon="@mipmap/ic_launcher"<#else>
+ android:icon="@drawable/${assetName}"</#if>
+ <#if buildApi gte 17>android:supportsRtl="true"</#if>
+ android:theme="@style/AppTheme">
+
+ </application>
+
+</manifest>
diff --git a/base/templates/gradle-projects/NewGlassModule/root/build.gradle.ftl b/templates/gradle-projects/NewGlassModule/root/build.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/build.gradle.ftl
rename to templates/gradle-projects/NewGlassModule/root/build.gradle.ftl
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/module_ignore b/templates/gradle-projects/NewGlassModule/root/module_ignore
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/module_ignore
rename to templates/gradle-projects/NewGlassModule/root/module_ignore
diff --git a/base/templates/gradle-projects/NewGlassModule/root/proguard-rules.txt.ftl b/templates/gradle-projects/NewGlassModule/root/proguard-rules.txt.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/proguard-rules.txt.ftl
rename to templates/gradle-projects/NewGlassModule/root/proguard-rules.txt.ftl
diff --git a/base/templates/gradle-projects/NewGlassModule/root/res/mipmap-hdpi/ic_launcher.png b/templates/gradle-projects/NewGlassModule/root/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/res/mipmap-hdpi/ic_launcher.png
rename to templates/gradle-projects/NewGlassModule/root/res/mipmap-hdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png b/templates/gradle-projects/NewGlassModule/root/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-mdpi/ic_launcher.png
rename to templates/gradle-projects/NewGlassModule/root/res/mipmap-mdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xhdpi/ic_launcher.png b/templates/gradle-projects/NewGlassModule/root/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/AndroidWearModule/root/res/mipmap-xhdpi/ic_launcher.png
rename to templates/gradle-projects/NewGlassModule/root/res/mipmap-xhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewGlassModule/root/res/mipmap-xxhdpi/ic_launcher.png b/templates/gradle-projects/NewGlassModule/root/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/res/mipmap-xxhdpi/ic_launcher.png
rename to templates/gradle-projects/NewGlassModule/root/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewGlassModule/root/res/mipmap-xxxhdpi/ic_launcher.png b/templates/gradle-projects/NewGlassModule/root/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/res/mipmap-xxxhdpi/ic_launcher.png
rename to templates/gradle-projects/NewGlassModule/root/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/base/templates/gradle-projects/NewGlassModule/root/res/values/strings.xml.ftl b/templates/gradle-projects/NewGlassModule/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/res/values/strings.xml.ftl
rename to templates/gradle-projects/NewGlassModule/root/res/values/strings.xml.ftl
diff --git a/base/templates/gradle-projects/NewGlassModule/root/res/values/styles.xml b/templates/gradle-projects/NewGlassModule/root/res/values/styles.xml
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/res/values/styles.xml
rename to templates/gradle-projects/NewGlassModule/root/res/values/styles.xml
diff --git a/base/templates/gradle-projects/NewAndroidTVModule/root/settings.gradle.ftl b/templates/gradle-projects/NewGlassModule/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewAndroidTVModule/root/settings.gradle.ftl
rename to templates/gradle-projects/NewGlassModule/root/settings.gradle.ftl
diff --git a/base/templates/gradle-projects/NewGlassModule/root/test/app_package/ApplicationTest.java.ftl b/templates/gradle-projects/NewGlassModule/root/test/app_package/ApplicationTest.java.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/test/app_package/ApplicationTest.java.ftl
rename to templates/gradle-projects/NewGlassModule/root/test/app_package/ApplicationTest.java.ftl
diff --git a/templates/gradle-projects/NewGlassModule/template.xml b/templates/gradle-projects/NewGlassModule/template.xml
new file mode 100644
index 0000000..76fb5d1
--- /dev/null
+++ b/templates/gradle-projects/NewGlassModule/template.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Glass Module"
+ description="Creates a new Glass module."
+ minApi="19"
+ minBuildApi="19">
+
+ <category value="Application" />
+
+ <formfactor value="Glass" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="app_package|nonempty"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Module title"
+ type="string"
+ constraints="nonempty"
+ default="My Module" />
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"/>
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="21" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"/>
+
+ <!--
+ Usually the same as targetApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="targetApiString"
+ name="Target API"
+ type="string"
+ constraints="apilevel"/>
+
+ <parameter
+ id="buildApi"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"/>
+
+ <!--
+ Usually the same as buildApi, but when targeting a preview platform this is the code name instead
+ -->
+ <parameter
+ id="buildApiString"
+ name="Build API level"
+ type="string"
+ constraints="apilevel" />
+
+ <parameter
+ id="copyIcons"
+ name="Include launcher icons"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="enableProGuard"
+ name="Enable ProGuard"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/NewGlassModule/template_new_project.png b/templates/gradle-projects/NewGlassModule/template_new_project.png
new file mode 100644
index 0000000..1509174
Binary files /dev/null and b/templates/gradle-projects/NewGlassModule/template_new_project.png differ
diff --git a/base/templates/gradle-projects/NewJavaLibrary/globals.xml.ftl b/templates/gradle-projects/NewJavaLibrary/globals.xml.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewJavaLibrary/globals.xml.ftl
rename to templates/gradle-projects/NewJavaLibrary/globals.xml.ftl
diff --git a/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl b/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
new file mode 100644
index 0000000..6162319
--- /dev/null
+++ b/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+ <instantiate from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <instantiate from="root/src/library_package/Placeholder.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+<#if makeIgnore>
+ <copy from="root/gitignore"
+ to="${escapeXmlAttribute(projectOut)}/.gitignore" />
+</#if>
+
+ <mkdir at="${escapeXmlAttribute(projectOut)}/libs" />
+</recipe>
diff --git a/base/templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl b/templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl
rename to templates/gradle-projects/NewJavaLibrary/root/build.gradle.ftl
diff --git a/base/templates/gradle-projects/NewJavaLibrary/root/gitignore b/templates/gradle-projects/NewJavaLibrary/root/gitignore
similarity index 100%
rename from base/templates/gradle-projects/NewJavaLibrary/root/gitignore
rename to templates/gradle-projects/NewJavaLibrary/root/gitignore
diff --git a/base/templates/gradle-projects/NewGlassModule/root/settings.gradle.ftl b/templates/gradle-projects/NewJavaLibrary/root/settings.gradle.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewGlassModule/root/settings.gradle.ftl
rename to templates/gradle-projects/NewJavaLibrary/root/settings.gradle.ftl
diff --git a/base/templates/gradle-projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl b/templates/gradle-projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl
similarity index 100%
rename from base/templates/gradle-projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl
rename to templates/gradle-projects/NewJavaLibrary/root/src/library_package/Placeholder.java.ftl
diff --git a/templates/gradle-projects/NewJavaLibrary/template.xml b/templates/gradle-projects/NewJavaLibrary/template.xml
new file mode 100644
index 0000000..e00c3c1
--- /dev/null
+++ b/templates/gradle-projects/NewJavaLibrary/template.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="5"
+ name="Java Library"
+ description="Creates a new Java library.">
+
+ <category value="Application" />
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <parameter
+ id="projectName"
+ name="Library name"
+ type="string"
+ constraints="nonempty|module|unique"
+ default="lib"/>
+
+ <parameter
+ id="packageName"
+ name="Java package name"
+ type="string"
+ constraints="nonempty|package"
+ default="com.example"/>
+
+ <parameter
+ id="className"
+ name="Java class name"
+ type="string"
+ constraints="nonempty|class"
+ default="MyClass"/>
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/NewJavaLibrary/template_new_project.png b/templates/gradle-projects/NewJavaLibrary/template_new_project.png
new file mode 100644
index 0000000..2f329dc
Binary files /dev/null and b/templates/gradle-projects/NewJavaLibrary/template_new_project.png differ
diff --git a/base/templates/gradle/utils/dependencies.gradle.ftl b/templates/gradle/utils/dependencies.gradle.ftl
similarity index 100%
rename from base/templates/gradle/utils/dependencies.gradle.ftl
rename to templates/gradle/utils/dependencies.gradle.ftl
diff --git a/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties b/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..122a0dc
--- /dev/null
+++ b/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
diff --git a/templates/gradle/wrapper/gradlew b/templates/gradle/wrapper/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/templates/gradle/wrapper/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/base/templates/gradle/wrapper/gradlew.bat b/templates/gradle/wrapper/gradlew.bat
similarity index 100%
rename from base/templates/gradle/wrapper/gradlew.bat
rename to templates/gradle/wrapper/gradlew.bat
diff --git a/templates/other/AidlFile/recipe.xml.ftl b/templates/other/AidlFile/recipe.xml.ftl
new file mode 100644
index 0000000..783336d
--- /dev/null
+++ b/templates/other/AidlFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="root/src/app_package/interface.aidl.ftl"
+ to="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
+ <open file="${escapeXmlAttribute(aidlOut)}/${slashedPackageName(packageName)}/${escapeXmlAttribute(interfaceName)}.aidl" />
+</recipe>
diff --git a/base/templates/other/AidlFile/root/src/app_package/interface.aidl.ftl b/templates/other/AidlFile/root/src/app_package/interface.aidl.ftl
similarity index 100%
rename from base/templates/other/AidlFile/root/src/app_package/interface.aidl.ftl
rename to templates/other/AidlFile/root/src/app_package/interface.aidl.ftl
diff --git a/templates/other/AidlFile/template.xml b/templates/other/AidlFile/template.xml
new file mode 100644
index 0000000..6d5235d
--- /dev/null
+++ b/templates/other/AidlFile/template.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="AIDL File"
+ description="Creates a new Android Interface Description Language file."
+ >
+
+ <category value="AIDL" />
+
+ <parameter
+ id="interfaceName"
+ name="Interface Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="IMyAidlInterface"
+ help="Name of the Interface." />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/AidlFolder/recipe.xml.ftl b/templates/other/AidlFolder/recipe.xml.ftl
new file mode 100644
index 0000000..61617dd
--- /dev/null
+++ b/templates/other/AidlFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/aidl/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/AidlFolder/root/build.gradle.ftl b/templates/other/AidlFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/AidlFolder/root/build.gradle.ftl
rename to templates/other/AidlFolder/root/build.gradle.ftl
diff --git a/templates/other/AidlFolder/template.xml b/templates/other/AidlFolder/template.xml
new file mode 100644
index 0000000..e9ed50a
--- /dev/null
+++ b/templates/other/AidlFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="AIDL Folder"
+ description="Creates a source root for Android Interface Description Language files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/aidl/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/activities/BlankWearActivity/globals.xml.ftl b/templates/other/AndroidAutoMediaService/globals.xml.ftl
similarity index 100%
rename from base/templates/activities/BlankWearActivity/globals.xml.ftl
rename to templates/other/AndroidAutoMediaService/globals.xml.ftl
diff --git a/templates/other/AndroidAutoMediaService/recipe.xml.ftl b/templates/other/AndroidAutoMediaService/recipe.xml.ftl
new file mode 100644
index 0000000..636d038
--- /dev/null
+++ b/templates/other/AndroidAutoMediaService/recipe.xml.ftl
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <#if useCustomTheme>
+ <merge from="root/res/values-v21/styles.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values-v21/styles.xml" />
+ </#if>
+
+ <copy from="root/res/xml/automotive_app_desc.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/automotive_app_desc.xml" />
+
+ <instantiate from="root/src/app_package/MusicService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${mediaBrowserServiceName}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${mediaBrowserServiceName}.java" />
+
+ <#if useCustomTheme>
+ <open file="${escapeXmlAttribute(resOut)}/values-v21/styles.xml" />
+ </#if>
+</recipe>
diff --git a/base/templates/other/AndroidAutoMediaService/root/AndroidManifest.xml.ftl b/templates/other/AndroidAutoMediaService/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/AndroidAutoMediaService/root/AndroidManifest.xml.ftl
rename to templates/other/AndroidAutoMediaService/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/AndroidAutoMediaService/root/res/values-v21/styles.xml.ftl b/templates/other/AndroidAutoMediaService/root/res/values-v21/styles.xml.ftl
similarity index 100%
rename from base/templates/other/AndroidAutoMediaService/root/res/values-v21/styles.xml.ftl
rename to templates/other/AndroidAutoMediaService/root/res/values-v21/styles.xml.ftl
diff --git a/base/templates/other/AndroidAutoMediaService/root/res/xml/automotive_app_desc.xml b/templates/other/AndroidAutoMediaService/root/res/xml/automotive_app_desc.xml
similarity index 100%
rename from base/templates/other/AndroidAutoMediaService/root/res/xml/automotive_app_desc.xml
rename to templates/other/AndroidAutoMediaService/root/res/xml/automotive_app_desc.xml
diff --git a/base/templates/other/AndroidAutoMediaService/root/src/app_package/MusicService.java.ftl b/templates/other/AndroidAutoMediaService/root/src/app_package/MusicService.java.ftl
similarity index 100%
rename from base/templates/other/AndroidAutoMediaService/root/src/app_package/MusicService.java.ftl
rename to templates/other/AndroidAutoMediaService/root/src/app_package/MusicService.java.ftl
diff --git a/templates/other/AndroidAutoMediaService/template.xml b/templates/other/AndroidAutoMediaService/template.xml
new file mode 100644
index 0000000..08de571
--- /dev/null
+++ b/templates/other/AndroidAutoMediaService/template.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Media service"
+ description="Create a MediaBrowserService and adds the required metadata for Android Auto"
+ minApi="21">
+
+ <category value="Android Auto" />
+ <formfactor value="Car" />
+
+ <parameter
+ id="mediaBrowserServiceName"
+ name="Class name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyMusicService"
+ help="The name of the service that will extend MediaBrowserService and contain the logic to browse and playback media" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ help="The package where the media service will be created" />
+
+ <parameter
+ id="useCustomTheme"
+ name="Use a custom theme for Android Auto colors?"
+ type="boolean"
+ constraints="package"
+ help="Android Auto apps can define a different set of colors that will be used exclusively when running on Android Auto" />
+
+ <parameter
+ id="customThemeName"
+ name="Android Auto custom theme name"
+ type="String"
+ default="CarTheme"
+ visibility="useCustomTheme"
+ constraints="nonempty"/>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+ <thumbs>
+ <thumb>templates-mediaService-Auto.png</thumb>
+ </thumbs>
+
+</template>
diff --git a/templates/other/AndroidAutoMediaService/templates-mediaService-Auto.png b/templates/other/AndroidAutoMediaService/templates-mediaService-Auto.png
new file mode 100644
index 0000000..9259cfd
Binary files /dev/null and b/templates/other/AndroidAutoMediaService/templates-mediaService-Auto.png differ
diff --git a/base/templates/activities/EmptyActivity/globals.xml.ftl b/templates/other/AndroidAutoMessagingService/globals.xml.ftl
similarity index 100%
rename from base/templates/activities/EmptyActivity/globals.xml.ftl
rename to templates/other/AndroidAutoMessagingService/globals.xml.ftl
diff --git a/templates/other/AndroidAutoMessagingService/recipe.xml.ftl b/templates/other/AndroidAutoMessagingService/recipe.xml.ftl
new file mode 100644
index 0000000..8fdb8d4
--- /dev/null
+++ b/templates/other/AndroidAutoMessagingService/recipe.xml.ftl
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:21.0.2"/>
+ <dependency mavenUrl="com.android.support:support-v13:21.0.2"/>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <copy from="root/res/xml/automotive_app_desc.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/automotive_app_desc.xml" />
+
+ <instantiate from="root/src/app_package/MessagingService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${serviceName}.java" />
+
+ <instantiate from="root/src/app_package/MessageReadReceiver.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${readReceiverName}.java" />
+
+ <instantiate from="root/src/app_package/MessageReplyReceiver.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${replyReceiverName}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${serviceName}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${readReceiverName}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${replyReceiverName}.java" />
+</recipe>
diff --git a/base/templates/other/AndroidAutoMessagingService/root/AndroidManifest.xml.ftl b/templates/other/AndroidAutoMessagingService/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/AndroidAutoMessagingService/root/AndroidManifest.xml.ftl
rename to templates/other/AndroidAutoMessagingService/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/AndroidAutoMessagingService/root/res/xml/automotive_app_desc.xml b/templates/other/AndroidAutoMessagingService/root/res/xml/automotive_app_desc.xml
similarity index 100%
rename from base/templates/other/AndroidAutoMessagingService/root/res/xml/automotive_app_desc.xml
rename to templates/other/AndroidAutoMessagingService/root/res/xml/automotive_app_desc.xml
diff --git a/base/templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReadReceiver.java.ftl b/templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReadReceiver.java.ftl
similarity index 100%
rename from base/templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReadReceiver.java.ftl
rename to templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReadReceiver.java.ftl
diff --git a/base/templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReplyReceiver.java.ftl b/templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReplyReceiver.java.ftl
similarity index 100%
rename from base/templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReplyReceiver.java.ftl
rename to templates/other/AndroidAutoMessagingService/root/src/app_package/MessageReplyReceiver.java.ftl
diff --git a/base/templates/other/AndroidAutoMessagingService/root/src/app_package/MessagingService.java.ftl b/templates/other/AndroidAutoMessagingService/root/src/app_package/MessagingService.java.ftl
similarity index 100%
rename from base/templates/other/AndroidAutoMessagingService/root/src/app_package/MessagingService.java.ftl
rename to templates/other/AndroidAutoMessagingService/root/src/app_package/MessagingService.java.ftl
diff --git a/templates/other/AndroidAutoMessagingService/template.xml b/templates/other/AndroidAutoMessagingService/template.xml
new file mode 100644
index 0000000..adfbb5f
--- /dev/null
+++ b/templates/other/AndroidAutoMessagingService/template.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Messaging service"
+ description="Create a service that sends notifications compatible with Android Auto"
+ minApi="21">
+
+ <category value="Android Auto" />
+ <formfactor value="Car" />
+
+ <parameter
+ id="serviceName"
+ name="Service class name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyMessagingService"
+ help="The name of the service that will handle incoming messages and send corresponding notifications." />
+
+ <parameter
+ id="readReceiverName"
+ name="Read receiver class name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MessageReadReceiver"
+ help="The broadcast receiver that will handle Read notifications." />
+
+ <parameter
+ id="replyReceiverName"
+ name="Reply receiver class name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MessageReplyReceiver"
+ help="The broadcast receiver that will handle Reply notifications." />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ help="The package where the messaging service will be created." />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+ <thumbs>
+ <thumb>templates-messagingService-Auto.png</thumb>
+ </thumbs>
+
+</template>
diff --git a/templates/other/AndroidAutoMessagingService/templates-messagingService-Auto.png b/templates/other/AndroidAutoMessagingService/templates-messagingService-Auto.png
new file mode 100644
index 0000000..aa23fa1
Binary files /dev/null and b/templates/other/AndroidAutoMessagingService/templates-messagingService-Auto.png differ
diff --git a/templates/other/AndroidManifest/recipe.xml.ftl b/templates/other/AndroidManifest/recipe.xml.ftl
new file mode 100644
index 0000000..f984de0
--- /dev/null
+++ b/templates/other/AndroidManifest/recipe.xml.ftl
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFile>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/AndroidManifest/root/AndroidManifest.xml.ftl b/templates/other/AndroidManifest/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/AndroidManifest/root/AndroidManifest.xml.ftl
rename to templates/other/AndroidManifest/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/AndroidManifest/root/build.gradle.ftl b/templates/other/AndroidManifest/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/AndroidManifest/root/build.gradle.ftl
rename to templates/other/AndroidManifest/root/build.gradle.ftl
diff --git a/templates/other/AndroidManifest/template.xml b/templates/other/AndroidManifest/template.xml
new file mode 100644
index 0000000..4551d41
--- /dev/null
+++ b/templates/other/AndroidManifest/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Android Manifest File"
+ description="Creates an Android Manifest XML File."
+ >
+
+ <category value="Other" />
+
+ <parameter
+ id="remapFile"
+ name="Change File Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the file location to another destination within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New File Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/AndroidManifest.xml"
+ help="The location for the new file"
+ visibility="remapFile" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/other/AppWidget/globals.xml.ftl b/templates/other/AppWidget/globals.xml.ftl
similarity index 100%
rename from base/templates/other/AppWidget/globals.xml.ftl
rename to templates/other/AppWidget/globals.xml.ftl
diff --git a/templates/other/AppWidget/recipe.xml.ftl b/templates/other/AppWidget/recipe.xml.ftl
new file mode 100644
index 0000000..fa171f3
--- /dev/null
+++ b/templates/other/AppWidget/recipe.xml.ftl
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <copy from="root/res/drawable-nodpi/example_appwidget_preview.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_appwidget_preview.png" />
+ <instantiate from="root/res/layout/appwidget.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${class_name}.xml" />
+
+ <#if configurable>
+ <instantiate from="root/res/layout/appwidget_configure.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${class_name}_configure.xml" />
+ </#if>
+
+ <instantiate from="root/res/xml/appwidget_info.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/xml/${class_name}_info.xml" />
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+ <merge from="root/res/values-v14/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-v14/dimens.xml" />
+ <merge from="root/res/values/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+ <instantiate from="root/src/app_package/AppWidget.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <#if configurable>
+ <instantiate from="root/src/app_package/AppWidgetConfigureActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}ConfigureActivity.java" />
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/other/AppWidget/root/AndroidManifest.xml.ftl b/templates/other/AppWidget/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/AppWidget/root/AndroidManifest.xml.ftl
rename to templates/other/AppWidget/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png b/templates/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png
similarity index 100%
rename from base/templates/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png
rename to templates/other/AppWidget/root/res/drawable-nodpi/example_appwidget_preview.png
diff --git a/base/templates/other/AppWidget/root/res/layout/appwidget.xml b/templates/other/AppWidget/root/res/layout/appwidget.xml
similarity index 100%
rename from base/templates/other/AppWidget/root/res/layout/appwidget.xml
rename to templates/other/AppWidget/root/res/layout/appwidget.xml
diff --git a/base/templates/other/AppWidget/root/res/layout/appwidget_configure.xml b/templates/other/AppWidget/root/res/layout/appwidget_configure.xml
similarity index 100%
rename from base/templates/other/AppWidget/root/res/layout/appwidget_configure.xml
rename to templates/other/AppWidget/root/res/layout/appwidget_configure.xml
diff --git a/base/templates/other/AppWidget/root/res/values-v14/dimens.xml b/templates/other/AppWidget/root/res/values-v14/dimens.xml
similarity index 100%
rename from base/templates/other/AppWidget/root/res/values-v14/dimens.xml
rename to templates/other/AppWidget/root/res/values-v14/dimens.xml
diff --git a/base/templates/other/AppWidget/root/res/values/dimens.xml b/templates/other/AppWidget/root/res/values/dimens.xml
similarity index 100%
rename from base/templates/other/AppWidget/root/res/values/dimens.xml
rename to templates/other/AppWidget/root/res/values/dimens.xml
diff --git a/base/templates/other/AppWidget/root/res/values/strings.xml.ftl b/templates/other/AppWidget/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/other/AppWidget/root/res/values/strings.xml.ftl
rename to templates/other/AppWidget/root/res/values/strings.xml.ftl
diff --git a/base/templates/other/AppWidget/root/res/xml/appwidget_info.xml.ftl b/templates/other/AppWidget/root/res/xml/appwidget_info.xml.ftl
similarity index 100%
rename from base/templates/other/AppWidget/root/res/xml/appwidget_info.xml.ftl
rename to templates/other/AppWidget/root/res/xml/appwidget_info.xml.ftl
diff --git a/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl b/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl
new file mode 100644
index 0000000..93e55c8
--- /dev/null
+++ b/templates/other/AppWidget/root/src/app_package/AppWidget.java.ftl
@@ -0,0 +1,63 @@
+package ${packageName};
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.widget.RemoteViews;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+/**
+ * Implementation of App Widget functionality.
+<#if configurable>
+ * App Widget Configuration implemented in {@link ${className}ConfigureActivity ${className}ConfigureActivity}
+</#if>
+ */
+public class ${className} extends AppWidgetProvider {
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // There may be multiple widgets active, so update all of them
+ for (int appWidgetId : appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId);
+ }
+ }
+
+<#if configurable>
+ @Override
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ // When the user deletes the widget, delete the preference associated with it.
+ for (int appWidgetId : appWidgetIds) {
+ ${className}ConfigureActivity.deleteTitlePref(context, appWidgetId);
+ }
+ }
+</#if>
+
+ @Override
+ public void onEnabled(Context context) {
+ // Enter relevant functionality for when the first widget is created
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+
+ static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
+ int appWidgetId) {
+
+<#if configurable>
+ CharSequence widgetText = ${className}ConfigureActivity.loadTitlePref(context, appWidgetId);
+<#else>
+ CharSequence widgetText = context.getString(R.string.appwidget_text);
+</#if>
+ // Construct the RemoteViews object
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.${class_name});
+ views.setTextViewText(R.id.appwidget_text, widgetText);
+
+ // Instruct the widget manager to update the widget
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+}
+
diff --git a/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl b/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
new file mode 100644
index 0000000..1f2f1c1
--- /dev/null
+++ b/templates/other/AppWidget/root/src/app_package/AppWidgetConfigureActivity.java.ftl
@@ -0,0 +1,103 @@
+package ${packageName};
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.EditText;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+/**
+ * The configuration screen for the {@link ${className} ${className}} AppWidget.
+ */
+public class ${className}ConfigureActivity extends Activity {
+
+ int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+ EditText mAppWidgetText;
+ private static final String PREFS_NAME = "${packageName}.${className}";
+ private static final String PREF_PREFIX_KEY = "appwidget_";
+
+ public ${className}ConfigureActivity() {
+ super();
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Set the result to CANCELED. This will cause the widget host to cancel
+ // out of the widget placement if the user presses the back button.
+ setResult(RESULT_CANCELED);
+
+ setContentView(R.layout.${class_name}_configure);
+ mAppWidgetText = (EditText)findViewById(R.id.appwidget_text);
+ findViewById(R.id.add_button).setOnClickListener(mOnClickListener);
+
+ // Find the widget id from the intent.
+ Intent intent = getIntent();
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ mAppWidgetId = extras.getInt(
+ AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ }
+
+ // If this activity was started with an intent without an app widget ID, finish with an error.
+ if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ finish();
+ return;
+ }
+
+ mAppWidgetText.setText(loadTitlePref(${className}ConfigureActivity.this, mAppWidgetId));
+ }
+
+ View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ final Context context = ${className}ConfigureActivity.this;
+
+ // When the button is clicked, store the string locally
+ String widgetText = mAppWidgetText.getText().toString();
+ saveTitlePref(context,mAppWidgetId,widgetText);
+
+ // It is the responsibility of the configuration activity to update the app widget
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ ${className}.updateAppWidget(context, appWidgetManager, mAppWidgetId);
+
+ // Make sure we pass back the original appWidgetId
+ Intent resultValue = new Intent();
+ resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+ setResult(RESULT_OK, resultValue);
+ finish();
+ }
+ };
+
+ // Write the prefix to the SharedPreferences object for this widget
+ static void saveTitlePref(Context context, int appWidgetId, String text) {
+ SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
+ prefs.putString(PREF_PREFIX_KEY + appWidgetId, text);
+ prefs.apply();
+ }
+
+ // Read the prefix from the SharedPreferences object for this widget.
+ // If there is no preference saved, get the default from a resource
+ static String loadTitlePref(Context context, int appWidgetId) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
+ String titleValue = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null);
+ if (titleValue != null) {
+ return titleValue;
+ } else {
+ return context.getString(R.string.appwidget_text);
+ }
+ }
+
+ static void deleteTitlePref(Context context, int appWidgetId) {
+ SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
+ prefs.remove(PREF_PREFIX_KEY + appWidgetId);
+ prefs.apply();
+ }
+}
+
diff --git a/templates/other/AppWidget/template.xml b/templates/other/AppWidget/template.xml
new file mode 100644
index 0000000..c5f15aa
--- /dev/null
+++ b/templates/other/AppWidget/template.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="App Widget"
+ description="Creates a new App Widget"
+ minApi="4"
+ minBuildApi="16">
+
+ <category value="Widget" />
+
+ <parameter
+ id="className"
+ name="Class Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="NewAppWidget"
+ help="The name of the App Widget to create" />
+
+ <parameter
+ id="placement"
+ name="Placement"
+ type="enum"
+ default="homescreen"
+ help="Make the widget available on the Home-screen and/or on the Keyguard. Keyguard placement is only supported in Android 4.2 and above; this setting is ignored on earlier versions and defaults to Home-screen.">
+ <option id="both">Home-screen and Keyguard</option>
+ <option id="homescreen">Home-screen only</option>
+ <option id="keyguard" >Keyguard only (API 17+)</option>
+ </parameter>
+
+ <parameter
+ id="resizable"
+ name="Resizable (API 12+)"
+ type="enum"
+ default="both"
+ help="Allow the user to resize the widget. Feature only available on Android 3.1 and above.">
+ <option id="both">Horizontally and vertically</option>
+ <option id="horizontal">Only horizontally</option>
+ <option id="vertical" >Only vertically</option>
+ <option id="none">Not resizable</option>
+ </parameter>
+
+ <parameter
+ id="minWidth"
+ name="Minimum Width (cells)"
+ type="enum"
+ default="1">
+ <option id="1">1</option>
+ <option id="2" >2</option>
+ <option id="3" >3</option>
+ <option id="4" >4</option>
+ </parameter>
+
+ <parameter
+ id="minHeight"
+ name="Minimum Height (cells)"
+ type="enum"
+ default="1">
+ <option id="1">1</option>
+ <option id="2" >2</option>
+ <option id="3" >3</option>
+ <option id="4" >4</option>
+ </parameter>
+
+ <parameter
+ id="configurable"
+ name="Configuration Screen"
+ type="boolean"
+ default="false"
+ help="Generates a widget configuration activity" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+
+ <thumbs>
+ <thumb>thumbs/template_widget_3x3_vh.png</thumb>
+
+ <thumb minWidth="1" minHeight="1" resizable="none" >thumbs/template_widget_1x1.png</thumb>
+ <thumb minWidth="2" minHeight="1" resizable="none" >thumbs/template_widget_2x1.png</thumb>
+ <thumb minWidth="3" minHeight="1" resizable="none" >thumbs/template_widget_3x1.png</thumb>
+ <thumb minWidth="4" minHeight="1" resizable="none" >thumbs/template_widget_4x1.png</thumb>
+ <thumb minWidth="1" minHeight="2" resizable="none" >thumbs/template_widget_1x2.png</thumb>
+ <thumb minWidth="2" minHeight="2" resizable="none" >thumbs/template_widget_2x2.png</thumb>
+ <thumb minWidth="3" minHeight="2" resizable="none" >thumbs/template_widget_3x2.png</thumb>
+ <thumb minWidth="4" minHeight="2" resizable="none" >thumbs/template_widget_4x2.png</thumb>
+ <thumb minWidth="1" minHeight="3" resizable="none" >thumbs/template_widget_1x3.png</thumb>
+ <thumb minWidth="2" minHeight="3" resizable="none" >thumbs/template_widget_2x3.png</thumb>
+ <thumb minWidth="3" minHeight="3" resizable="none" >thumbs/template_widget_3x3.png</thumb>
+ <thumb minWidth="4" minHeight="3" resizable="none" >thumbs/template_widget_4x3.png</thumb>
+ <thumb minWidth="1" minHeight="4" resizable="none" >thumbs/template_widget_1x4.png</thumb>
+ <thumb minWidth="2" minHeight="4" resizable="none" >thumbs/template_widget_2x4.png</thumb>
+ <thumb minWidth="3" minHeight="4" resizable="none" >thumbs/template_widget_3x4.png</thumb>
+ <thumb minWidth="4" minHeight="4" resizable="none" >thumbs/template_widget_4x4.png</thumb>
+
+ <thumb minWidth="1" minHeight="1" resizable="horizontal">thumbs/template_widget_1x1_h.png</thumb>
+ <thumb minWidth="2" minHeight="1" resizable="horizontal">thumbs/template_widget_2x1_h.png</thumb>
+ <thumb minWidth="3" minHeight="1" resizable="horizontal">thumbs/template_widget_3x1_h.png</thumb>
+ <thumb minWidth="4" minHeight="1" resizable="horizontal">thumbs/template_widget_4x1_h.png</thumb>
+ <thumb minWidth="1" minHeight="2" resizable="horizontal">thumbs/template_widget_1x2_h.png</thumb>
+ <thumb minWidth="2" minHeight="2" resizable="horizontal">thumbs/template_widget_2x2_h.png</thumb>
+ <thumb minWidth="3" minHeight="2" resizable="horizontal">thumbs/template_widget_3x2_h.png</thumb>
+ <thumb minWidth="4" minHeight="2" resizable="horizontal">thumbs/template_widget_4x2_h.png</thumb>
+ <thumb minWidth="1" minHeight="3" resizable="horizontal">thumbs/template_widget_1x3_h.png</thumb>
+ <thumb minWidth="2" minHeight="3" resizable="horizontal">thumbs/template_widget_2x3_h.png</thumb>
+ <thumb minWidth="3" minHeight="3" resizable="horizontal">thumbs/template_widget_3x3_h.png</thumb>
+ <thumb minWidth="4" minHeight="3" resizable="horizontal">thumbs/template_widget_4x3_h.png</thumb>
+ <thumb minWidth="1" minHeight="4" resizable="horizontal">thumbs/template_widget_1x4_h.png</thumb>
+ <thumb minWidth="2" minHeight="4" resizable="horizontal">thumbs/template_widget_2x4_h.png</thumb>
+ <thumb minWidth="3" minHeight="4" resizable="horizontal">thumbs/template_widget_3x4_h.png</thumb>
+ <thumb minWidth="4" minHeight="4" resizable="horizontal">thumbs/template_widget_4x4_h.png</thumb>
+
+ <thumb minWidth="1" minHeight="1" resizable="vertical" >thumbs/template_widget_1x1_v.png</thumb>
+ <thumb minWidth="2" minHeight="1" resizable="vertical" >thumbs/template_widget_2x1_v.png</thumb>
+ <thumb minWidth="3" minHeight="1" resizable="vertical" >thumbs/template_widget_3x1_v.png</thumb>
+ <thumb minWidth="4" minHeight="1" resizable="vertical" >thumbs/template_widget_4x1_v.png</thumb>
+ <thumb minWidth="1" minHeight="2" resizable="vertical" >thumbs/template_widget_1x2_v.png</thumb>
+ <thumb minWidth="2" minHeight="2" resizable="vertical" >thumbs/template_widget_2x2_v.png</thumb>
+ <thumb minWidth="3" minHeight="2" resizable="vertical" >thumbs/template_widget_3x2_v.png</thumb>
+ <thumb minWidth="4" minHeight="2" resizable="vertical" >thumbs/template_widget_4x2_v.png</thumb>
+ <thumb minWidth="1" minHeight="3" resizable="vertical" >thumbs/template_widget_1x3_v.png</thumb>
+ <thumb minWidth="2" minHeight="3" resizable="vertical" >thumbs/template_widget_2x3_v.png</thumb>
+ <thumb minWidth="3" minHeight="3" resizable="vertical" >thumbs/template_widget_3x3_v.png</thumb>
+ <thumb minWidth="4" minHeight="3" resizable="vertical" >thumbs/template_widget_4x3_v.png</thumb>
+ <thumb minWidth="1" minHeight="4" resizable="vertical" >thumbs/template_widget_1x4_v.png</thumb>
+ <thumb minWidth="2" minHeight="4" resizable="vertical" >thumbs/template_widget_2x4_v.png</thumb>
+ <thumb minWidth="3" minHeight="4" resizable="vertical" >thumbs/template_widget_3x4_v.png</thumb>
+ <thumb minWidth="4" minHeight="4" resizable="vertical" >thumbs/template_widget_4x4_v.png</thumb>
+
+ <thumb minWidth="1" minHeight="1" resizable="both" >thumbs/template_widget_1x1_vh.png</thumb>
+ <thumb minWidth="2" minHeight="1" resizable="both" >thumbs/template_widget_2x1_vh.png</thumb>
+ <thumb minWidth="3" minHeight="1" resizable="both" >thumbs/template_widget_3x1_vh.png</thumb>
+ <thumb minWidth="4" minHeight="1" resizable="both" >thumbs/template_widget_4x1_vh.png</thumb>
+ <thumb minWidth="1" minHeight="2" resizable="both" >thumbs/template_widget_1x2_vh.png</thumb>
+ <thumb minWidth="2" minHeight="2" resizable="both" >thumbs/template_widget_2x2_vh.png</thumb>
+ <thumb minWidth="3" minHeight="2" resizable="both" >thumbs/template_widget_3x2_vh.png</thumb>
+ <thumb minWidth="4" minHeight="2" resizable="both" >thumbs/template_widget_4x2_vh.png</thumb>
+ <thumb minWidth="1" minHeight="3" resizable="both" >thumbs/template_widget_1x3_vh.png</thumb>
+ <thumb minWidth="2" minHeight="3" resizable="both" >thumbs/template_widget_2x3_vh.png</thumb>
+ <thumb minWidth="3" minHeight="3" resizable="both" >thumbs/template_widget_3x3_vh.png</thumb>
+ <thumb minWidth="4" minHeight="3" resizable="both" >thumbs/template_widget_4x3_vh.png</thumb>
+ <thumb minWidth="1" minHeight="4" resizable="both" >thumbs/template_widget_1x4_vh.png</thumb>
+ <thumb minWidth="2" minHeight="4" resizable="both" >thumbs/template_widget_2x4_vh.png</thumb>
+ <thumb minWidth="3" minHeight="4" resizable="both" >thumbs/template_widget_3x4_vh.png</thumb>
+ <thumb minWidth="4" minHeight="4" resizable="both" >thumbs/template_widget_4x4_vh.png</thumb>
+ </thumbs>
+
+</template>
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x1.png b/templates/other/AppWidget/thumbs/template_widget_1x1.png
new file mode 100644
index 0000000..c54791b
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x1.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x1_h.png b/templates/other/AppWidget/thumbs/template_widget_1x1_h.png
new file mode 100644
index 0000000..32e4f00
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x1_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x1_v.png b/templates/other/AppWidget/thumbs/template_widget_1x1_v.png
new file mode 100644
index 0000000..d1373b5
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x1_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x1_vh.png b/templates/other/AppWidget/thumbs/template_widget_1x1_vh.png
new file mode 100644
index 0000000..40e5891
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x1_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x2.png b/templates/other/AppWidget/thumbs/template_widget_1x2.png
new file mode 100644
index 0000000..7bc0e02
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x2.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x2_h.png b/templates/other/AppWidget/thumbs/template_widget_1x2_h.png
new file mode 100644
index 0000000..8194056
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x2_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x2_v.png b/templates/other/AppWidget/thumbs/template_widget_1x2_v.png
new file mode 100644
index 0000000..3aa7ec5
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x2_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x2_vh.png b/templates/other/AppWidget/thumbs/template_widget_1x2_vh.png
new file mode 100644
index 0000000..ea212cd
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x2_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x3.png b/templates/other/AppWidget/thumbs/template_widget_1x3.png
new file mode 100644
index 0000000..85b8922
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x3.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x3_h.png b/templates/other/AppWidget/thumbs/template_widget_1x3_h.png
new file mode 100644
index 0000000..d3edd34
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x3_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x3_v.png b/templates/other/AppWidget/thumbs/template_widget_1x3_v.png
new file mode 100644
index 0000000..8ffec96
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x3_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x3_vh.png b/templates/other/AppWidget/thumbs/template_widget_1x3_vh.png
new file mode 100644
index 0000000..80f3b0f
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x3_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x4.png b/templates/other/AppWidget/thumbs/template_widget_1x4.png
new file mode 100644
index 0000000..fa9e6d5
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x4.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x4_h.png b/templates/other/AppWidget/thumbs/template_widget_1x4_h.png
new file mode 100644
index 0000000..303520e
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x4_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x4_v.png b/templates/other/AppWidget/thumbs/template_widget_1x4_v.png
new file mode 100644
index 0000000..5a2a09e
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x4_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_1x4_vh.png b/templates/other/AppWidget/thumbs/template_widget_1x4_vh.png
new file mode 100644
index 0000000..2f7bf1f
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_1x4_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x1.png b/templates/other/AppWidget/thumbs/template_widget_2x1.png
new file mode 100644
index 0000000..e51dce7
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x1.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x1_h.png b/templates/other/AppWidget/thumbs/template_widget_2x1_h.png
new file mode 100644
index 0000000..17831a9
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x1_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x1_v.png b/templates/other/AppWidget/thumbs/template_widget_2x1_v.png
new file mode 100644
index 0000000..d701f96
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x1_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x1_vh.png b/templates/other/AppWidget/thumbs/template_widget_2x1_vh.png
new file mode 100644
index 0000000..8b7f3a9
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x1_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x2.png b/templates/other/AppWidget/thumbs/template_widget_2x2.png
new file mode 100644
index 0000000..98bb196
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x2.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x2_h.png b/templates/other/AppWidget/thumbs/template_widget_2x2_h.png
new file mode 100644
index 0000000..c61abf5
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x2_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x2_v.png b/templates/other/AppWidget/thumbs/template_widget_2x2_v.png
new file mode 100644
index 0000000..0d33a6b
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x2_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x2_vh.png b/templates/other/AppWidget/thumbs/template_widget_2x2_vh.png
new file mode 100644
index 0000000..dfd0522
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x2_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x3.png b/templates/other/AppWidget/thumbs/template_widget_2x3.png
new file mode 100644
index 0000000..ce8cbc8
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x3.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x3_h.png b/templates/other/AppWidget/thumbs/template_widget_2x3_h.png
new file mode 100644
index 0000000..9e5b268
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x3_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x3_v.png b/templates/other/AppWidget/thumbs/template_widget_2x3_v.png
new file mode 100644
index 0000000..49290bf
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x3_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x3_vh.png b/templates/other/AppWidget/thumbs/template_widget_2x3_vh.png
new file mode 100644
index 0000000..111e9aa
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x3_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x4.png b/templates/other/AppWidget/thumbs/template_widget_2x4.png
new file mode 100644
index 0000000..0b3bd6f
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x4.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x4_h.png b/templates/other/AppWidget/thumbs/template_widget_2x4_h.png
new file mode 100644
index 0000000..6365542
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x4_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x4_v.png b/templates/other/AppWidget/thumbs/template_widget_2x4_v.png
new file mode 100644
index 0000000..55c8a63
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x4_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_2x4_vh.png b/templates/other/AppWidget/thumbs/template_widget_2x4_vh.png
new file mode 100644
index 0000000..024602c
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_2x4_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x1.png b/templates/other/AppWidget/thumbs/template_widget_3x1.png
new file mode 100644
index 0000000..d2edf2c
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x1.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x1_h.png b/templates/other/AppWidget/thumbs/template_widget_3x1_h.png
new file mode 100644
index 0000000..894f068
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x1_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x1_v.png b/templates/other/AppWidget/thumbs/template_widget_3x1_v.png
new file mode 100644
index 0000000..1025ba4
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x1_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x1_vh.png b/templates/other/AppWidget/thumbs/template_widget_3x1_vh.png
new file mode 100644
index 0000000..a422d16
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x1_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x2.png b/templates/other/AppWidget/thumbs/template_widget_3x2.png
new file mode 100644
index 0000000..9de3191
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x2.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x2_h.png b/templates/other/AppWidget/thumbs/template_widget_3x2_h.png
new file mode 100644
index 0000000..7c1ed03
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x2_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x2_v.png b/templates/other/AppWidget/thumbs/template_widget_3x2_v.png
new file mode 100644
index 0000000..251e934
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x2_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x2_vh.png b/templates/other/AppWidget/thumbs/template_widget_3x2_vh.png
new file mode 100644
index 0000000..127b9a2
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x2_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x3.png b/templates/other/AppWidget/thumbs/template_widget_3x3.png
new file mode 100644
index 0000000..a3f9784
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x3.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x3_h.png b/templates/other/AppWidget/thumbs/template_widget_3x3_h.png
new file mode 100644
index 0000000..d4f011b
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x3_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x3_v.png b/templates/other/AppWidget/thumbs/template_widget_3x3_v.png
new file mode 100644
index 0000000..faf2705
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x3_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x3_vh.png b/templates/other/AppWidget/thumbs/template_widget_3x3_vh.png
new file mode 100644
index 0000000..eb0862f
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x3_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x4.png b/templates/other/AppWidget/thumbs/template_widget_3x4.png
new file mode 100644
index 0000000..16a23d5
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x4.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x4_h.png b/templates/other/AppWidget/thumbs/template_widget_3x4_h.png
new file mode 100644
index 0000000..c6d6477
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x4_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x4_v.png b/templates/other/AppWidget/thumbs/template_widget_3x4_v.png
new file mode 100644
index 0000000..47f2406
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x4_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_3x4_vh.png b/templates/other/AppWidget/thumbs/template_widget_3x4_vh.png
new file mode 100644
index 0000000..4952ca6
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_3x4_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x1.png b/templates/other/AppWidget/thumbs/template_widget_4x1.png
new file mode 100644
index 0000000..2110cc7
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x1.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x1_h.png b/templates/other/AppWidget/thumbs/template_widget_4x1_h.png
new file mode 100644
index 0000000..5cefda9
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x1_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x1_v.png b/templates/other/AppWidget/thumbs/template_widget_4x1_v.png
new file mode 100644
index 0000000..8ed86d8
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x1_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x1_vh.png b/templates/other/AppWidget/thumbs/template_widget_4x1_vh.png
new file mode 100644
index 0000000..b2b5340
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x1_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x2.png b/templates/other/AppWidget/thumbs/template_widget_4x2.png
new file mode 100644
index 0000000..745f5ae
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x2.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x2_h.png b/templates/other/AppWidget/thumbs/template_widget_4x2_h.png
new file mode 100644
index 0000000..1991860
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x2_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x2_v.png b/templates/other/AppWidget/thumbs/template_widget_4x2_v.png
new file mode 100644
index 0000000..eed3fff
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x2_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x2_vh.png b/templates/other/AppWidget/thumbs/template_widget_4x2_vh.png
new file mode 100644
index 0000000..1d93deb
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x2_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x3.png b/templates/other/AppWidget/thumbs/template_widget_4x3.png
new file mode 100644
index 0000000..12df3e0
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x3.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x3_h.png b/templates/other/AppWidget/thumbs/template_widget_4x3_h.png
new file mode 100644
index 0000000..a7216a4
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x3_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x3_v.png b/templates/other/AppWidget/thumbs/template_widget_4x3_v.png
new file mode 100644
index 0000000..ce7e477
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x3_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x3_vh.png b/templates/other/AppWidget/thumbs/template_widget_4x3_vh.png
new file mode 100644
index 0000000..7ceb59f
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x3_vh.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x4.png b/templates/other/AppWidget/thumbs/template_widget_4x4.png
new file mode 100644
index 0000000..85da63e
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x4.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x4_h.png b/templates/other/AppWidget/thumbs/template_widget_4x4_h.png
new file mode 100644
index 0000000..132a9df
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x4_h.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x4_v.png b/templates/other/AppWidget/thumbs/template_widget_4x4_v.png
new file mode 100644
index 0000000..70763b9
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x4_v.png differ
diff --git a/templates/other/AppWidget/thumbs/template_widget_4x4_vh.png b/templates/other/AppWidget/thumbs/template_widget_4x4_vh.png
new file mode 100644
index 0000000..cbaf483
Binary files /dev/null and b/templates/other/AppWidget/thumbs/template_widget_4x4_vh.png differ
diff --git a/templates/other/AssetsFolder/recipe.xml.ftl b/templates/other/AssetsFolder/recipe.xml.ftl
new file mode 100644
index 0000000..85e8986
--- /dev/null
+++ b/templates/other/AssetsFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/assets/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/AssetsFolder/root/build.gradle.ftl b/templates/other/AssetsFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/AssetsFolder/root/build.gradle.ftl
rename to templates/other/AssetsFolder/root/build.gradle.ftl
diff --git a/templates/other/AssetsFolder/template.xml b/templates/other/AssetsFolder/template.xml
new file mode 100644
index 0000000..0d50d50
--- /dev/null
+++ b/templates/other/AssetsFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Assets Folder"
+ description="Creates a source root for assets which will be included in the APK."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/assets/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/BlankFragment/globals.xml.ftl b/templates/other/BlankFragment/globals.xml.ftl
new file mode 100644
index 0000000..219b490
--- /dev/null
+++ b/templates/other/BlankFragment/globals.xml.ftl
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<globals>
+ <#assign useSupport=(minApiLevel lt 23)>
+ <global id="useSupport" type="boolean" value="${useSupport?string}" />
+ <global id="SupportPackage" value="${useSupport?string('.support.v4','')}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/other/BlankFragment/recipe.xml.ftl b/templates/other/BlankFragment/recipe.xml.ftl
new file mode 100644
index 0000000..77ebeba
--- /dev/null
+++ b/templates/other/BlankFragment/recipe.xml.ftl
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if useSupport><dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/></#if>
+ <merge from="root/res/values/strings.xml" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <#if includeLayout>
+ <instantiate from="root/res/layout/fragment_blank.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />
+ </#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <instantiate from="root/src/app_package/BlankFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/base/templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl b/templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
similarity index 100%
rename from base/templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
rename to templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
diff --git a/base/templates/other/BlankFragment/root/res/values/strings.xml b/templates/other/BlankFragment/root/res/values/strings.xml
similarity index 100%
rename from base/templates/other/BlankFragment/root/res/values/strings.xml
rename to templates/other/BlankFragment/root/res/values/strings.xml
diff --git a/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl b/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
new file mode 100644
index 0000000..d032552
--- /dev/null
+++ b/templates/other/BlankFragment/root/src/app_package/BlankFragment.java.ftl
@@ -0,0 +1,131 @@
+package ${packageName};
+
+<#if includeCallbacks>import android.content.Context;</#if>
+<#if includeCallbacks>import android.net.Uri;</#if>
+import android.os.Bundle;
+import android${SupportPackage}.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+<#if !includeLayout>import android.widget.TextView;</#if>
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+/**
+ * A simple {@link Fragment} subclass.
+<#if includeCallbacks>
+ * Activities that contain this fragment must implement the
+ * {@link ${className}.OnFragmentInteractionListener} interface
+ * to handle interaction events.
+</#if>
+<#if includeFactory>
+ * Use the {@link ${className}#newInstance} factory method to
+ * create an instance of this fragment.
+</#if>
+ *
+ */
+public class ${className} extends Fragment {
+<#if includeFactory>
+ // TODO: Rename parameter arguments, choose names that match
+ // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+ private static final String ARG_PARAM1 = "param1";
+ private static final String ARG_PARAM2 = "param2";
+
+ // TODO: Rename and change types of parameters
+ private String mParam1;
+ private String mParam2;
+</#if>
+
+<#if includeCallbacks>
+ private OnFragmentInteractionListener mListener;
+</#if>
+
+<#if includeFactory>
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment ${className}.
+ */
+ // TODO: Rename and change types and number of parameters
+ public static ${className} newInstance(String param1, String param2) {
+ ${className} fragment = new ${className}();
+ Bundle args = new Bundle();
+ args.putString(ARG_PARAM1, param1);
+ args.putString(ARG_PARAM2, param2);
+ fragment.setArguments(args);
+ return fragment;
+ }
+</#if>
+ public ${className}() {
+ // Required empty public constructor
+ }
+
+<#if includeFactory>
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() != null) {
+ mParam1 = getArguments().getString(ARG_PARAM1);
+ mParam2 = getArguments().getString(ARG_PARAM2);
+ }
+ }
+</#if>
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+<#if includeLayout>
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.${fragmentName}, container, false);
+<#else>
+ TextView textView = new TextView(getActivity());
+ textView.setText(R.string.hello_blank_fragment);
+ return textView;
+</#if>
+ }
+
+<#if includeCallbacks>
+ // TODO: Rename method, update argument and hook method into UI event
+ public void onButtonPressed(Uri uri) {
+ if (mListener != null) {
+ mListener.onFragmentInteraction(uri);
+ }
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof OnFragmentInteractionListener) {
+ mListener = (OnFragmentInteractionListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement OnFragmentInteractionListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ /**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ * <p>
+ * See the Android Training lesson <a href=
+ * "http://developer.android.com/training/basics/fragments/communicating.html"
+ * >Communicating with Other Fragments</a> for more information.
+ */
+ public interface OnFragmentInteractionListener {
+ // TODO: Update argument type and name
+ void onFragmentInteraction(Uri uri);
+ }
+</#if>
+}
diff --git a/templates/other/BlankFragment/template.xml b/templates/other/BlankFragment/template.xml
new file mode 100644
index 0000000..83f687c
--- /dev/null
+++ b/templates/other/BlankFragment/template.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Fragment (Blank)"
+ description="Creates a blank fragment that is compatible back to API level 4."
+ minApi="7"
+ minBuildApi="8">
+
+ <category value="Fragment" />
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <parameter
+ id="className"
+ name="Fragment Name"
+ type="string"
+ constraints="class|nonempty|unique"
+ default="BlankFragment"
+ help="The name of the fragment class to create" />
+
+ <parameter
+ id="includeLayout"
+ name="Create layout XML?"
+ type="boolean"
+ default="true"
+ help="Generate a layout XML for the fragment" />
+
+ <parameter
+ id="fragmentName"
+ name="Fragment Layout Name"
+ type="string"
+ constraints="layout|nonempty|unique"
+ default="fragment_blank"
+ visibility="includeLayout"
+ suggest="fragment_${classToResource(className)}"
+ help="The name of the layout to create" />
+
+ <parameter
+ id="includeFactory"
+ name="Include fragment factory methods?"
+ type="boolean"
+ default="true"
+ help="Generate static fragment factory methods for easy instantiation" />
+
+ <parameter
+ id="includeCallbacks"
+ name="Include interface callbacks?"
+ type="boolean"
+ default="true"
+ help="Generate event callbacks for communication with an Activity or other fragments" />
+
+ <thumbs>
+ <thumb>template_blank_fragment.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/BlankFragment/template_blank_fragment.png b/templates/other/BlankFragment/template_blank_fragment.png
new file mode 100644
index 0000000..564ea5c
Binary files /dev/null and b/templates/other/BlankFragment/template_blank_fragment.png differ
diff --git a/base/templates/other/BroadcastReceiver/globals.xml.ftl b/templates/other/BroadcastReceiver/globals.xml.ftl
similarity index 100%
rename from base/templates/other/BroadcastReceiver/globals.xml.ftl
rename to templates/other/BroadcastReceiver/globals.xml.ftl
diff --git a/templates/other/BroadcastReceiver/recipe.xml.ftl b/templates/other/BroadcastReceiver/recipe.xml.ftl
new file mode 100644
index 0000000..4a74840
--- /dev/null
+++ b/templates/other/BroadcastReceiver/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/BroadcastReceiver.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl b/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
rename to templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl b/templates/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl
similarity index 100%
rename from base/templates/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl
rename to templates/other/BroadcastReceiver/root/src/app_package/BroadcastReceiver.java.ftl
diff --git a/templates/other/BroadcastReceiver/template.xml b/templates/other/BroadcastReceiver/template.xml
new file mode 100644
index 0000000..b12039c
--- /dev/null
+++ b/templates/other/BroadcastReceiver/template.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Broadcast Receiver"
+ description="Creates a new broadcast receiver component and adds it to your Android manifest.">
+
+ <parameter
+ id="className"
+ name="Class Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyReceiver" />
+
+ <parameter
+ id="isExported"
+ name="Exported"
+ type="boolean"
+ default="true"
+ help="Whether or not the broadcast receiver can receive messages from sources outside its application" />
+
+ <parameter
+ id="isEnabled"
+ name="Enabled"
+ type="boolean"
+ default="true"
+ help="Whether or not the broadcast receiver can be instantiated by the system" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/other/ContentProvider/globals.xml.ftl b/templates/other/ContentProvider/globals.xml.ftl
similarity index 100%
rename from base/templates/other/ContentProvider/globals.xml.ftl
rename to templates/other/ContentProvider/globals.xml.ftl
diff --git a/templates/other/ContentProvider/recipe.xml.ftl b/templates/other/ContentProvider/recipe.xml.ftl
new file mode 100644
index 0000000..8ca93eb
--- /dev/null
+++ b/templates/other/ContentProvider/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/ContentProvider.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/other/ContentProvider/root/AndroidManifest.xml.ftl b/templates/other/ContentProvider/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/ContentProvider/root/AndroidManifest.xml.ftl
rename to templates/other/ContentProvider/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl b/templates/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl
similarity index 100%
rename from base/templates/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl
rename to templates/other/ContentProvider/root/src/app_package/ContentProvider.java.ftl
diff --git a/templates/other/ContentProvider/template.xml b/templates/other/ContentProvider/template.xml
new file mode 100644
index 0000000..85ed15d
--- /dev/null
+++ b/templates/other/ContentProvider/template.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Content Provider"
+ description="Creates a new content provider component and adds it to your Android manifest.">
+
+ <parameter
+ id="className"
+ name="Class Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyContentProvider" />
+
+ <parameter
+ id="authorities"
+ name="URI Authorities"
+ type="string"
+ constraints="nonempty|uri_authority"
+ default=""
+ help="A semicolon separated list of one or more URI authorities that identify data under the purview of the content provider. " />
+
+ <parameter
+ id="isExported"
+ name="Exported"
+ type="boolean"
+ default="true"
+ help="Whether or not the content provider can be used by components of other applications " />
+
+ <parameter
+ id="isEnabled"
+ name="Enabled"
+ type="boolean"
+ default="true"
+ help="Whether or not the content provider can be instantiated by the system " />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/other/CustomView/globals.xml.ftl b/templates/other/CustomView/globals.xml.ftl
similarity index 100%
rename from base/templates/other/CustomView/globals.xml.ftl
rename to templates/other/CustomView/globals.xml.ftl
diff --git a/templates/other/CustomView/recipe.xml.ftl b/templates/other/CustomView/recipe.xml.ftl
new file mode 100644
index 0000000..406cc9a
--- /dev/null
+++ b/templates/other/CustomView/recipe.xml.ftl
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/res/values/attrs.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/attrs_${view_class}.xml" />
+ <instantiate from="root/res/layout/sample.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
+
+ <instantiate from="root/src/app_package/CustomView.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
+</recipe>
diff --git a/base/templates/other/CustomView/root/res/layout/sample.xml.ftl b/templates/other/CustomView/root/res/layout/sample.xml.ftl
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/other/CustomView/root/res/layout/sample.xml.ftl
rename to templates/other/CustomView/root/res/layout/sample.xml.ftl
diff --git a/base/templates/other/CustomView/root/res/values/attrs.xml.ftl b/templates/other/CustomView/root/res/values/attrs.xml.ftl
old mode 100755
new mode 100644
similarity index 100%
rename from base/templates/other/CustomView/root/res/values/attrs.xml.ftl
rename to templates/other/CustomView/root/res/values/attrs.xml.ftl
diff --git a/base/templates/other/CustomView/root/src/app_package/CustomView.java.ftl b/templates/other/CustomView/root/src/app_package/CustomView.java.ftl
similarity index 100%
rename from base/templates/other/CustomView/root/src/app_package/CustomView.java.ftl
rename to templates/other/CustomView/root/src/app_package/CustomView.java.ftl
diff --git a/templates/other/CustomView/template.xml b/templates/other/CustomView/template.xml
new file mode 100644
index 0000000..e9f7f59
--- /dev/null
+++ b/templates/other/CustomView/template.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Custom View"
+ description="Creates a new custom view that extends android.view.View and exposes custom attributes.">
+
+ <category value="UI Component" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="viewClass"
+ name="View Class"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyView"
+ help="By convention, should end in 'View'" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/other/Daydream/globals.xml.ftl b/templates/other/Daydream/globals.xml.ftl
similarity index 100%
rename from base/templates/other/Daydream/globals.xml.ftl
rename to templates/other/Daydream/globals.xml.ftl
diff --git a/templates/other/Daydream/recipe.xml.ftl b/templates/other/Daydream/recipe.xml.ftl
new file mode 100644
index 0000000..9494460
--- /dev/null
+++ b/templates/other/Daydream/recipe.xml.ftl
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <copy from="root/res/layout-v17/dream.xml"
+ to="${escapeXmlAttribute(resOut)}/layout-v17/${class_name}.xml" />
+
+ <instantiate from="root/src/app_package/DreamService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+<#if configurable>
+ <copy from="root/res/xml/dream_prefs.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/${prefs_name}.xml" />
+
+ <instantiate from="root/src/app_package/SettingsActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${settingsClassName}.java" />
+
+ <instantiate from="root/res/xml/xml_dream.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/xml/${info_name}.xml" />
+</#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/base/templates/other/Daydream/root/AndroidManifest.xml.ftl b/templates/other/Daydream/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/Daydream/root/AndroidManifest.xml.ftl
rename to templates/other/Daydream/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/Daydream/root/res/layout-v17/dream.xml b/templates/other/Daydream/root/res/layout-v17/dream.xml
similarity index 100%
rename from base/templates/other/Daydream/root/res/layout-v17/dream.xml
rename to templates/other/Daydream/root/res/layout-v17/dream.xml
diff --git a/base/templates/other/Daydream/root/res/values/strings.xml.ftl b/templates/other/Daydream/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/other/Daydream/root/res/values/strings.xml.ftl
rename to templates/other/Daydream/root/res/values/strings.xml.ftl
diff --git a/base/templates/other/Daydream/root/res/xml/dream_prefs.xml b/templates/other/Daydream/root/res/xml/dream_prefs.xml
similarity index 100%
rename from base/templates/other/Daydream/root/res/xml/dream_prefs.xml
rename to templates/other/Daydream/root/res/xml/dream_prefs.xml
diff --git a/base/templates/other/Daydream/root/res/xml/xml_dream.xml.ftl b/templates/other/Daydream/root/res/xml/xml_dream.xml.ftl
similarity index 100%
rename from base/templates/other/Daydream/root/res/xml/xml_dream.xml.ftl
rename to templates/other/Daydream/root/res/xml/xml_dream.xml.ftl
diff --git a/templates/other/Daydream/root/src/app_package/DreamService.java.ftl b/templates/other/Daydream/root/src/app_package/DreamService.java.ftl
new file mode 100644
index 0000000..b2dd15d
--- /dev/null
+++ b/templates/other/Daydream/root/src/app_package/DreamService.java.ftl
@@ -0,0 +1,143 @@
+package ${packageName};
+
+import java.util.Random;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.annotation.TargetApi;
+import android.content.SharedPreferences;
+import android.graphics.Point;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.service.dreams.DreamService;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.LinearInterpolator;
+import android.widget.TextView;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+
+/**
+ * This class is a sample implementation of a DreamService. When activated, a
+ * TextView will repeatedly, move from the left to the right of screen, at a
+ * random y-value.
+<#if configurable>
+ * The generated {@link ${settingsClassName}} allows
+ * the user to change the text which is displayed.
+</#if>
+ * <p />
+ * Daydreams are only available on devices running API v17+.
+ */
+ at TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class ${className} extends DreamService {
+
+ private static final TimeInterpolator sInterpolator = new LinearInterpolator();
+
+ private final AnimatorListener mAnimListener = new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Start animation again
+ startTextViewScrollAnimation();
+ }
+
+ };
+
+ private final Random mRandom = new Random();
+ private final Point mPointSize = new Point();
+
+ private TextView mDreamTextView;
+ private ViewPropertyAnimator mAnimator;
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Exit dream upon user touch?
+<#if isInteractive>
+ setInteractive(true);
+<#else>
+ setInteractive(false);
+</#if>
+
+ // Hide system UI?
+<#if isFullscreen>
+ setFullscreen(true);
+<#else>
+ setFullscreen(false);
+</#if>
+
+ // Keep screen at full brightness?
+<#if isScreenBright>
+ setScreenBright(true);
+<#else>
+ setScreenBright(false);
+</#if>
+
+ // Set the content view, just like you would with an Activity.
+ setContentView(R.layout.${class_name});
+
+ mDreamTextView = (TextView) findViewById(R.id.dream_text);
+ mDreamTextView.setText(getTextFromPreferences());
+ }
+
+ @Override
+ public void onDreamingStarted() {
+ super.onDreamingStarted();
+
+ // TODO: Begin animations or other behaviors here.
+
+ startTextViewScrollAnimation();
+ }
+
+ @Override
+ public void onDreamingStopped() {
+ super.onDreamingStopped();
+
+ // TODO: Stop anything that was started in onDreamingStarted()
+
+ mAnimator.cancel();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ // TODO: Dismantle resources
+ // (for example, detach from handlers and listeners).
+ }
+
+ private String getTextFromPreferences() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ return prefs.getString(getString(R.string.pref_dream_text_key),
+ getString(R.string.pref_dream_text_default));
+ }
+
+ private void startTextViewScrollAnimation() {
+ // Refresh Size of Window
+ getWindowManager().getDefaultDisplay().getSize(mPointSize);
+
+ final int windowWidth = mPointSize.x;
+ final int windowHeight = mPointSize.y;
+
+ // Move TextView so it's moved all the way to the left
+ mDreamTextView.setTranslationX(-mDreamTextView.getWidth());
+
+ // Move TextView to random y value
+ final int yRange = windowHeight - mDreamTextView.getHeight();
+ mDreamTextView.setTranslationY(mRandom.nextInt(yRange));
+
+ // Create an Animator and keep a reference to it
+ mAnimator = mDreamTextView.animate().translationX(windowWidth)
+ .setDuration(3000)
+ .setStartDelay(500)
+ .setListener(mAnimListener)
+ .setInterpolator(sInterpolator);
+
+ // Start the animation
+ mAnimator.start();
+ }
+
+}
diff --git a/base/templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl b/templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
similarity index 100%
rename from base/templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
rename to templates/other/Daydream/root/src/app_package/SettingsActivity.java.ftl
diff --git a/templates/other/Daydream/template.xml b/templates/other/Daydream/template.xml
new file mode 100644
index 0000000..2c95f07
--- /dev/null
+++ b/templates/other/Daydream/template.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Daydream"
+ description="Creates a new Daydream service component, for use on devices running Android 4.2 and later."
+ minBuildApi="17">
+
+ <parameter
+ id="className"
+ name="Class Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyDaydreamService" />
+
+ <parameter
+ id="isInteractive"
+ name="Interactive?"
+ type="boolean"
+ default="false"
+ help="Whether or not the Daydream is interactive. Touching anywhere on a non-interactive Daydreams dismisses it." />
+
+ <parameter
+ id="isFullscreen"
+ name="Fullscreen?"
+ type="boolean"
+ default="true"
+ help="Whether or not the Daydream should be in fullscreen mode (in which case system UI will be hidden)." />
+
+ <parameter
+ id="isScreenBright"
+ name="Bright Screen?"
+ type="boolean"
+ default="true"
+ help="Whether or not the screen should be bright when this Daydream is running. The screen will be dim otherwise." />
+
+ <parameter
+ id="configurable"
+ name="Configuration Activity"
+ type="boolean"
+ default="false"
+ help="Whether or not to generate an associated settings Activity." />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/DisplayNotification/globals.xml.ftl b/templates/other/DisplayNotification/globals.xml.ftl
new file mode 100644
index 0000000..314d567
--- /dev/null
+++ b/templates/other/DisplayNotification/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "root://activities/common/wear_common_globals.xml.ftl" />
+</globals>
diff --git a/templates/other/DisplayNotification/recipe.xml.ftl b/templates/other/DisplayNotification/recipe.xml.ftl
new file mode 100644
index 0000000..ddc596e
--- /dev/null
+++ b/templates/other/DisplayNotification/recipe.xml.ftl
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/layout/activity_display.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${displayActivityLayout}.xml" />
+ <instantiate from="root/src/app_package/StubActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${stubActivityClass}.java" />
+
+ <instantiate from="root/src/app_package/DisplayActivity.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${displayActivityClass}.java" />
+
+ <instantiate from="root/src/app_package/BroadcastReceiver.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${receiverClass}.java" />
+
+ <merge from="root/res/values/strings.xml"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${displayActivityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${displayActivityLayout}.xml" />
+</recipe>
diff --git a/base/templates/other/DisplayNotification/root/AndroidManifest.xml.ftl b/templates/other/DisplayNotification/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/DisplayNotification/root/AndroidManifest.xml.ftl
rename to templates/other/DisplayNotification/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/DisplayNotification/root/res/layout/activity_display.xml.ftl b/templates/other/DisplayNotification/root/res/layout/activity_display.xml.ftl
similarity index 100%
rename from base/templates/other/DisplayNotification/root/res/layout/activity_display.xml.ftl
rename to templates/other/DisplayNotification/root/res/layout/activity_display.xml.ftl
diff --git a/base/templates/other/DisplayNotification/root/res/values/strings.xml b/templates/other/DisplayNotification/root/res/values/strings.xml
similarity index 100%
rename from base/templates/other/DisplayNotification/root/res/values/strings.xml
rename to templates/other/DisplayNotification/root/res/values/strings.xml
diff --git a/base/templates/other/DisplayNotification/root/src/app_package/BroadcastReceiver.java.ftl b/templates/other/DisplayNotification/root/src/app_package/BroadcastReceiver.java.ftl
similarity index 100%
rename from base/templates/other/DisplayNotification/root/src/app_package/BroadcastReceiver.java.ftl
rename to templates/other/DisplayNotification/root/src/app_package/BroadcastReceiver.java.ftl
diff --git a/base/templates/other/DisplayNotification/root/src/app_package/DisplayActivity.java.ftl b/templates/other/DisplayNotification/root/src/app_package/DisplayActivity.java.ftl
similarity index 100%
rename from base/templates/other/DisplayNotification/root/src/app_package/DisplayActivity.java.ftl
rename to templates/other/DisplayNotification/root/src/app_package/DisplayActivity.java.ftl
diff --git a/base/templates/other/DisplayNotification/root/src/app_package/StubActivity.java.ftl b/templates/other/DisplayNotification/root/src/app_package/StubActivity.java.ftl
similarity index 100%
rename from base/templates/other/DisplayNotification/root/src/app_package/StubActivity.java.ftl
rename to templates/other/DisplayNotification/root/src/app_package/StubActivity.java.ftl
diff --git a/templates/other/DisplayNotification/template.xml b/templates/other/DisplayNotification/template.xml
new file mode 100644
index 0000000..34a94b7
--- /dev/null
+++ b/templates/other/DisplayNotification/template.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Display Notification"
+ description="Create an Activity to display inside a custom display notification and a BroadcastReceiver to post it"
+ minApi="20">
+
+ <category value="Wear" />
+ <formfactor value="Wear" />
+
+ <parameter
+ id="displayActivityClass"
+ name="Display Activity Class"
+ type="string"
+ constraints="activity|unique|nonempty"
+ default="MyDisplayActivity"
+ help="The name of the Activity that will be displayed for the notification" />
+
+ <parameter
+ id="displayActivityLayout"
+ name="Display Activity Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ default="activity_display"
+ help="The layout name for the Activity that will be displayed for the notification" />
+
+ <parameter
+ id="receiverClass"
+ name="Receiver Class"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyPostNotificationReceiver"
+ help="The name of the BroadcastReceiver that will post the notification" />
+
+ <parameter
+ id="stubActivityClass"
+ name="Launcher Stub Class"
+ type="string"
+ constraints="activity|unique|nonempty"
+ default="MyStubBroadcastActivity"
+ help="The name of the Stub Activity that sends the broadcast for testing purposes" />
+
+ <parameter
+ id="isExported"
+ name="Exported"
+ type="boolean"
+ default="true"
+ help="Whether or not the broadcast receiver can receive messages from sources outside its application" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"/>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+ <thumbs>
+ <thumb>templates-activityView-Wear.png</thumb>
+ </thumbs>
+
+</template>
diff --git a/templates/other/DisplayNotification/templates-activityView-Wear.png b/templates/other/DisplayNotification/templates-activityView-Wear.png
new file mode 100644
index 0000000..295ff11
Binary files /dev/null and b/templates/other/DisplayNotification/templates-activityView-Wear.png differ
diff --git a/base/templates/other/IntentService/globals.xml.ftl b/templates/other/IntentService/globals.xml.ftl
similarity index 100%
rename from base/templates/other/IntentService/globals.xml.ftl
rename to templates/other/IntentService/globals.xml.ftl
diff --git a/templates/other/IntentService/recipe.xml.ftl b/templates/other/IntentService/recipe.xml.ftl
new file mode 100644
index 0000000..208e132
--- /dev/null
+++ b/templates/other/IntentService/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/IntentService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/other/IntentService/root/AndroidManifest.xml.ftl b/templates/other/IntentService/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/IntentService/root/AndroidManifest.xml.ftl
rename to templates/other/IntentService/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/IntentService/root/src/app_package/IntentService.java.ftl b/templates/other/IntentService/root/src/app_package/IntentService.java.ftl
similarity index 100%
rename from base/templates/other/IntentService/root/src/app_package/IntentService.java.ftl
rename to templates/other/IntentService/root/src/app_package/IntentService.java.ftl
diff --git a/templates/other/IntentService/template.xml b/templates/other/IntentService/template.xml
new file mode 100644
index 0000000..fe6f543
--- /dev/null
+++ b/templates/other/IntentService/template.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Service (IntentService)"
+ description="Creates a new intent service class."
+ minApi="3"
+ minBuildApi="3">
+
+ <category value="Service" />
+
+ <parameter
+ id="className"
+ name="Class Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyIntentService" />
+
+ <parameter
+ id="includeHelper"
+ name="Include helper start methods?"
+ type="boolean"
+ default="true"
+ help="Generate static helper methods to start the service e.g. MyIntentService.startAction()" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/JavaFolder/recipe.xml.ftl b/templates/other/JavaFolder/recipe.xml.ftl
new file mode 100644
index 0000000..d7f34ac
--- /dev/null
+++ b/templates/other/JavaFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/java/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/JavaFolder/root/build.gradle.ftl b/templates/other/JavaFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/JavaFolder/root/build.gradle.ftl
rename to templates/other/JavaFolder/root/build.gradle.ftl
diff --git a/templates/other/JavaFolder/template.xml b/templates/other/JavaFolder/template.xml
new file mode 100644
index 0000000..6fb008f
--- /dev/null
+++ b/templates/other/JavaFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Java Folder"
+ description="Creates a source root for Java files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/java/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/JniFolder/recipe.xml.ftl b/templates/other/JniFolder/recipe.xml.ftl
new file mode 100644
index 0000000..c01b2fb
--- /dev/null
+++ b/templates/other/JniFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/jni/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/JniFolder/root/build.gradle.ftl b/templates/other/JniFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/JniFolder/root/build.gradle.ftl
rename to templates/other/JniFolder/root/build.gradle.ftl
diff --git a/templates/other/JniFolder/template.xml b/templates/other/JniFolder/template.xml
new file mode 100644
index 0000000..b60e737
--- /dev/null
+++ b/templates/other/JniFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="JNI Folder"
+ description="Creates a source root for Java Native Interface files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/jni/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/LayoutResourceFile/recipe.xml.ftl b/templates/other/LayoutResourceFile/recipe.xml.ftl
new file mode 100644
index 0000000..f5fafbe
--- /dev/null
+++ b/templates/other/LayoutResourceFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="root/res/layout.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(layoutName)}.xml" />
+</recipe>
diff --git a/base/templates/other/LayoutResourceFile/root/res/layout.xml.ftl b/templates/other/LayoutResourceFile/root/res/layout.xml.ftl
similarity index 100%
rename from base/templates/other/LayoutResourceFile/root/res/layout.xml.ftl
rename to templates/other/LayoutResourceFile/root/res/layout.xml.ftl
diff --git a/templates/other/LayoutResourceFile/template.xml b/templates/other/LayoutResourceFile/template.xml
new file mode 100644
index 0000000..7946ea7
--- /dev/null
+++ b/templates/other/LayoutResourceFile/template.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Layout XML File"
+ description="Creates a new XML layout file."
+ >
+
+ <category value="XML" />
+
+ <parameter
+ id="layoutName"
+ name="Layout File Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ default="layout"
+ help="Name of the layout XML file." />
+
+ <parameter
+ id="rootTag"
+ name="Root Tag"
+ type="string"
+ constraints="nonempty"
+ default="LinearLayout"
+ help="The root XML tag for the new file" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/ListFragment/globals.xml.ftl b/templates/other/ListFragment/globals.xml.ftl
new file mode 100644
index 0000000..3d29b83
--- /dev/null
+++ b/templates/other/ListFragment/globals.xml.ftl
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<globals>
+ <#assign useSupport=(minApiLevel lt 23)>
+ <global id="useSupport" type="boolean" value="${useSupport?string}" />
+ <global id="SupportPackage" value="${useSupport?string('.support.v4','')}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="collection_name" value="${extractLetters(objectKind?lower_case)}" />
+ <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
+</globals>
diff --git a/templates/other/ListFragment/recipe.xml.ftl b/templates/other/ListFragment/recipe.xml.ftl
new file mode 100644
index 0000000..d41514a
--- /dev/null
+++ b/templates/other/ListFragment/recipe.xml.ftl
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if useSupport><dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/></#if>
+ <dependency mavenUrl="com.android.support:recyclerview-v7:${buildApi}.+" />
+
+ <instantiate from="root/res/layout/fragment_list.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout_list}.xml" />
+ <instantiate from="root/res/layout/item_list_content.xml"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout}.xml" />
+ <instantiate from="root/src/app_package/ListFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <instantiate from="root/src/app_package/RecyclerViewAdapter.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${adapterClassName}.java" />
+
+ <#include "../../activities/common/recipe_dummy_content.xml.ftl" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <merge from="root/res/values/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+</recipe>
diff --git a/templates/other/ListFragment/root/res/layout/fragment_list.xml b/templates/other/ListFragment/root/res/layout/fragment_list.xml
new file mode 100644
index 0000000..349efce
--- /dev/null
+++ b/templates/other/ListFragment/root/res/layout/fragment_list.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/list"
+ android:name="${relativePackage}.${className}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ app:layoutManager="LinearLayoutManager"
+ tools:context="${relativePackage}.${className}"
+ tools:listitem="@layout/${fragment_layout}"/>
diff --git a/templates/other/ListFragment/root/res/layout/item_list_content.xml b/templates/other/ListFragment/root/res/layout/item_list_content.xml
new file mode 100644
index 0000000..47702aa
--- /dev/null
+++ b/templates/other/ListFragment/root/res/layout/item_list_content.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/text_margin"
+ android:textAppearance="?attr/textAppearanceListItem" />
+
+ <TextView
+ android:id="@+id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/text_margin"
+ android:textAppearance="?attr/textAppearanceListItem" />
+</LinearLayout>
diff --git a/templates/other/ListFragment/root/res/values/dimens.xml b/templates/other/ListFragment/root/res/values/dimens.xml
new file mode 100644
index 0000000..2926000
--- /dev/null
+++ b/templates/other/ListFragment/root/res/values/dimens.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="text_margin">16dp</dimen>
+</resources>
diff --git a/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl b/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl
new file mode 100644
index 0000000..f7a3cc3
--- /dev/null
+++ b/templates/other/ListFragment/root/src/app_package/ListFragment.java.ftl
@@ -0,0 +1,119 @@
+package ${packageName};
+
+import android.content.Context;
+import android.os.Bundle;
+import android${SupportPackage}.app.Fragment;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+import ${packageName}.dummy.DummyContent;
+import ${packageName}.dummy.DummyContent.DummyItem;
+
+import java.util.List;
+
+/**
+ * A fragment representing a list of Items.
+ * <p />
+ * Activities containing this fragment MUST implement the {@link OnListFragmentInteractionListener}
+ * interface.
+ */
+public class ${className} extends Fragment {
+
+ // TODO: Customize parameters
+ private int mColumnCount = ${columnCount};
+
+<#if includeFactory>
+ // TODO: Customize parameter argument names
+ private static final String ARG_COLUMN_COUNT = "column-count";
+
+</#if>
+ private OnListFragmentInteractionListener mListener;
+
+<#if includeFactory>
+ // TODO: Customize parameter initialization
+ @SuppressWarnings("unused")
+ public static ${className} newInstance(int columnCount) {
+ ${className} fragment = new ${className}();
+ Bundle args = new Bundle();
+ args.putInt(ARG_COLUMN_COUNT, columnCount);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+</#if>
+ /**
+ * Mandatory empty constructor for the fragment manager to instantiate the
+ * fragment (e.g. upon screen orientation changes).
+ */
+ public ${className}() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+<#if includeFactory>
+ if (getArguments() != null) {
+ mColumnCount = getArguments().getInt(ARG_COLUMN_COUNT);
+ }
+</#if>
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.${fragment_layout_list}, container, false);
+
+ // Set the adapter
+ if (view instanceof RecyclerView) {
+ Context context = view.getContext();
+ RecyclerView recyclerView = (RecyclerView) view;
+ if (mColumnCount <= 1) {
+ recyclerView.setLayoutManager(new LinearLayoutManager(context));
+ } else {
+ recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount));
+ }
+ recyclerView.setAdapter(new ${adapterClassName}(DummyContent.ITEMS, mListener));
+ }
+ return view;
+ }
+
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof OnListFragmentInteractionListener) {
+ mListener = (OnListFragmentInteractionListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement OnListFragmentInteractionListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ /**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ * <p/>
+ * See the Android Training lesson <a href=
+ * "http://developer.android.com/training/basics/fragments/communicating.html"
+ * >Communicating with Other Fragments</a> for more information.
+ */
+ public interface OnListFragmentInteractionListener {
+ // TODO: Update argument type and name
+ void onListFragmentInteraction(DummyItem item);
+ }
+}
diff --git a/templates/other/ListFragment/root/src/app_package/RecyclerViewAdapter.java.ftl b/templates/other/ListFragment/root/src/app_package/RecyclerViewAdapter.java.ftl
new file mode 100644
index 0000000..0a1a0e7
--- /dev/null
+++ b/templates/other/ListFragment/root/src/app_package/RecyclerViewAdapter.java.ftl
@@ -0,0 +1,77 @@
+package ${packageName};
+
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import ${packageName}.ItemFragment.OnListFragmentInteractionListener;
+import ${packageName}.dummy.DummyContent.DummyItem;
+
+import java.util.List;
+
+/**
+ * {@link RecyclerView.Adapter} that can display a {@link DummyItem} and makes a call to the
+ * specified {@link OnListFragmentInteractionListener}.
+ * TODO: Replace the implementation with code for your data type.
+ */
+public class ${adapterClassName} extends RecyclerView.Adapter<${adapterClassName}.ViewHolder> {
+
+ private final List<DummyItem> mValues;
+ private final OnListFragmentInteractionListener mListener;
+
+ public ${adapterClassName}(List<DummyItem> items, OnListFragmentInteractionListener listener) {
+ mValues = items;
+ mListener = listener;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.fragment_item, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ holder.mItem = mValues.get(position);
+ holder.mIdView.setText(mValues.get(position).id);
+ holder.mContentView.setText(mValues.get(position).content);
+
+ holder.mView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (null != mListener) {
+ // Notify the active callbacks interface (the activity, if the
+ // fragment is attached to one) that an item has been selected.
+ mListener.onListFragmentInteraction(holder.mItem);
+ }
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mValues.size();
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+ public final View mView;
+ public final TextView mIdView;
+ public final TextView mContentView;
+ public DummyItem mItem;
+
+ public ViewHolder(View view) {
+ super(view);
+ mView = view;
+ mIdView = (TextView) view.findViewById(R.id.id);
+ mContentView = (TextView) view.findViewById(R.id.content);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " '" + mContentView.getText() + "'";
+ }
+ }
+}
diff --git a/templates/other/ListFragment/template.xml b/templates/other/ListFragment/template.xml
new file mode 100644
index 0000000..11ea763
--- /dev/null
+++ b/templates/other/ListFragment/template.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Fragment (List)"
+ description="Creates a new empty fragment containing a list that can be rendered as a grid. Compatible back to API level 4."
+ minApi="7"
+ minBuildApi="8">
+
+ <category value="Fragment" />
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="objectKind"
+ name="Object Kind"
+ type="string"
+ constraints="nonempty"
+ default="Item"
+ help="Other examples are 'Person', 'Book', etc." />
+
+ <parameter
+ id="className"
+ type="string"
+ constraints="nonempty|class|unique"
+ suggest="${extractLetters(objectKind)}Fragment"
+ name="Fragment class name" />
+
+ <parameter
+ id="includeFactory"
+ name="Include fragment factory methods?"
+ type="boolean"
+ default="true"
+ help="Generate static fragment factory methods for easy instantiation" />
+
+ <parameter
+ id="columnCount"
+ name="Column Count"
+ type="enum"
+ default="1"
+ help="The number of columns in the grid" >
+ <option id="1">1 (List)</option>
+ <option id="2">2 (Grid)</option>
+ <option id="3">3</option>
+ <option id="4">4</option>
+ </parameter>
+
+ <parameter
+ id="fragment_layout"
+ type="string"
+ constraints="layout|nonempty|unique"
+ suggest="fragment_${extractLetters(objectKind?lower_case)}"
+ name="Object content layout file name" />
+
+ <parameter
+ id="fragment_layout_list"
+ type="string"
+ constraints="layout|nonempty|unique"
+ suggest="fragment_${extractLetters(objectKind?lower_case)}_list"
+ name="List layout file name" />
+
+ <parameter
+ id="adapterClassName"
+ type="string"
+ constraints="nonempty|class|unique"
+ suggest="My${extractLetters(objectKind)}RecyclerViewAdapter"
+ name="Adapter class name"
+ visible="false"/>
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>templates_list_fragment.png</thumb>
+ </thumbs>
+
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/ListFragment/templates_list_fragment.png b/templates/other/ListFragment/templates_list_fragment.png
new file mode 100644
index 0000000..0ffdd25
Binary files /dev/null and b/templates/other/ListFragment/templates_list_fragment.png differ
diff --git a/base/templates/other/Notification/globals.xml.ftl b/templates/other/Notification/globals.xml.ftl
similarity index 100%
rename from base/templates/other/Notification/globals.xml.ftl
rename to templates/other/Notification/globals.xml.ftl
diff --git a/templates/other/Notification/recipe.xml.ftl b/templates/other/Notification/recipe.xml.ftl
new file mode 100644
index 0000000..70c33e1
--- /dev/null
+++ b/templates/other/Notification/recipe.xml.ftl
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <#if expandedStyle == "picture">
+ <copy from="root/res/drawable-nodpi/example_picture_large.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
+ <#else>
+ <copy from="root/res/drawable-nodpi/example_picture_small.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
+ </#if>
+
+ <#if moreActions>
+ <copy from="root/res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="root/res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="root/res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ </#if>
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <instantiate from="root/src/app_package/NotificationHelper.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/other/Notification/root/AndroidManifest.xml.ftl b/templates/other/Notification/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/Notification/root/AndroidManifest.xml.ftl
rename to templates/other/Notification/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png b/templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png
rename to templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_reply.png
diff --git a/base/templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png b/templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png
rename to templates/other/Notification/root/res/drawable-hdpi/ic_action_stat_share.png
diff --git a/base/templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png b/templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png
rename to templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_reply.png
diff --git a/base/templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png b/templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png
rename to templates/other/Notification/root/res/drawable-mdpi/ic_action_stat_share.png
diff --git a/base/templates/other/Notification/root/res/drawable-nodpi/example_picture_large.png b/templates/other/Notification/root/res/drawable-nodpi/example_picture_large.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-nodpi/example_picture_large.png
rename to templates/other/Notification/root/res/drawable-nodpi/example_picture_large.png
diff --git a/base/templates/other/Notification/root/res/drawable-nodpi/example_picture_small.png b/templates/other/Notification/root/res/drawable-nodpi/example_picture_small.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-nodpi/example_picture_small.png
rename to templates/other/Notification/root/res/drawable-nodpi/example_picture_small.png
diff --git a/base/templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png b/templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png
rename to templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_reply.png
diff --git a/base/templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png b/templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png
similarity index 100%
rename from base/templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png
rename to templates/other/Notification/root/res/drawable-xhdpi/ic_action_stat_share.png
diff --git a/base/templates/other/Notification/root/res/values/strings.xml.ftl b/templates/other/Notification/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/other/Notification/root/res/values/strings.xml.ftl
rename to templates/other/Notification/root/res/values/strings.xml.ftl
diff --git a/base/templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl b/templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl
similarity index 100%
rename from base/templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl
rename to templates/other/Notification/root/src/app_package/NotificationHelper.java.ftl
diff --git a/templates/other/Notification/template.xml b/templates/other/Notification/template.xml
new file mode 100644
index 0000000..c548c4f
--- /dev/null
+++ b/templates/other/Notification/template.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Notification"
+ description="Creates a new helper class that can show or cancel a status bar notification."
+ minApi="4">
+
+ <category value="UI Component" />
+
+ <dependency name="android-support-v4" revision="10" />
+
+ <parameter
+ id="className"
+ name="Class Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="NewMessageNotification"
+ help="The name of the notification helper class to create, e.g. 'NewMessageNotification'." />
+
+ <parameter
+ id="expandedStyle"
+ name="Style when Expanded"
+ type="enum"
+ default="text"
+ help="The expanded notification style to use for devices running Android 4.1 or later." >
+ <option id="none">None</option>
+ <option id="text">More text</option>
+ <option id="picture">Picture</option>
+ <option id="list">List</option>
+ </parameter>
+
+ <parameter
+ id="moreActions"
+ name="Show Additional Actions"
+ type="boolean"
+ default="true"
+ help="If true, this notification will contain additional actions when expanded on devices running Android 4.1 or later." />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+ <thumbs>
+ <thumb>template_notification_text_actions.png</thumb>
+ <thumb expandedStyle="none">template_notification_none.png</thumb>
+ <thumb expandedStyle="none" moreActions="true">template_notification_none_actions.png</thumb>
+ <thumb expandedStyle="text">template_notification_text.png</thumb>
+ <thumb expandedStyle="text" moreActions="true">template_notification_text_actions.png</thumb>
+ <thumb expandedStyle="list">template_notification_list.png</thumb>
+ <thumb expandedStyle="list" moreActions="true">template_notification_list_actions.png</thumb>
+ <thumb expandedStyle="picture">template_notification_picture.png</thumb>
+ <thumb expandedStyle="picture" moreActions="true">template_notification_picture_actions.png</thumb>
+ </thumbs>
+
+ <icons
+ type="notification"
+ name="ic_stat_${camelCaseToUnderscore(className?replace('notification','','i'))}" />
+</template>
diff --git a/templates/other/Notification/template_notification_list.png b/templates/other/Notification/template_notification_list.png
new file mode 100644
index 0000000..8383ac8
Binary files /dev/null and b/templates/other/Notification/template_notification_list.png differ
diff --git a/templates/other/Notification/template_notification_list_actions.png b/templates/other/Notification/template_notification_list_actions.png
new file mode 100644
index 0000000..942c8fb
Binary files /dev/null and b/templates/other/Notification/template_notification_list_actions.png differ
diff --git a/templates/other/Notification/template_notification_none.png b/templates/other/Notification/template_notification_none.png
new file mode 100644
index 0000000..0d416e9
Binary files /dev/null and b/templates/other/Notification/template_notification_none.png differ
diff --git a/templates/other/Notification/template_notification_none_actions.png b/templates/other/Notification/template_notification_none_actions.png
new file mode 100644
index 0000000..8a670bd
Binary files /dev/null and b/templates/other/Notification/template_notification_none_actions.png differ
diff --git a/templates/other/Notification/template_notification_picture.png b/templates/other/Notification/template_notification_picture.png
new file mode 100644
index 0000000..2216376
Binary files /dev/null and b/templates/other/Notification/template_notification_picture.png differ
diff --git a/templates/other/Notification/template_notification_picture_actions.png b/templates/other/Notification/template_notification_picture_actions.png
new file mode 100644
index 0000000..233160e
Binary files /dev/null and b/templates/other/Notification/template_notification_picture_actions.png differ
diff --git a/templates/other/Notification/template_notification_text.png b/templates/other/Notification/template_notification_text.png
new file mode 100644
index 0000000..0836650
Binary files /dev/null and b/templates/other/Notification/template_notification_text.png differ
diff --git a/templates/other/Notification/template_notification_text_actions.png b/templates/other/Notification/template_notification_text_actions.png
new file mode 100644
index 0000000..621a732
Binary files /dev/null and b/templates/other/Notification/template_notification_text_actions.png differ
diff --git a/base/templates/other/PlusOneFragment/globals.xml.ftl b/templates/other/PlusOneFragment/globals.xml.ftl
similarity index 100%
rename from base/templates/other/PlusOneFragment/globals.xml.ftl
rename to templates/other/PlusOneFragment/globals.xml.ftl
diff --git a/templates/other/PlusOneFragment/recipe.xml.ftl b/templates/other/PlusOneFragment/recipe.xml.ftl
new file mode 100644
index 0000000..94cfee5
--- /dev/null
+++ b/templates/other/PlusOneFragment/recipe.xml.ftl
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.android.support:support-v4:${buildApi}.+"/>
+ <dependency mavenUrl="com.google.android.gms:play-services:+"/>
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+ <instantiate from="root/res/layout/fragment_plus_one.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+
+ <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+ <instantiate from="root/src/app_package/PlusOneFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+
+</recipe>
diff --git a/base/templates/other/PlusOneFragment/root/AndroidManifest.xml.ftl b/templates/other/PlusOneFragment/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/PlusOneFragment/root/AndroidManifest.xml.ftl
rename to templates/other/PlusOneFragment/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl b/templates/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
similarity index 100%
rename from base/templates/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
rename to templates/other/PlusOneFragment/root/res/layout/fragment_plus_one.xml.ftl
diff --git a/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl b/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
new file mode 100644
index 0000000..0d64bba
--- /dev/null
+++ b/templates/other/PlusOneFragment/root/src/app_package/PlusOneFragment.java.ftl
@@ -0,0 +1,147 @@
+package ${packageName};
+
+<#if includeCallbacks>import android.content.Context;</#if>
+<#if includeCallbacks>import android.net.Uri;</#if>
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+<#if applicationPackage??>
+import ${applicationPackage}.R;
+</#if>
+import com.google.android.gms.plus.PlusOneButton;
+
+/**
+ * A fragment with a Google +1 button.
+<#if includeCallbacks>
+ * Activities that contain this fragment must implement the
+ * {@link ${className}.OnFragmentInteractionListener} interface
+ * to handle interaction events.
+</#if>
+<#if includeFactory>
+ * Use the {@link ${className}#newInstance} factory method to
+ * create an instance of this fragment.
+</#if>
+ *
+ */
+public class ${className} extends Fragment {
+<#if includeFactory>
+ // TODO: Rename parameter arguments, choose names that match
+ // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+ private static final String ARG_PARAM1 = "param1";
+ private static final String ARG_PARAM2 = "param2";
+
+ // TODO: Rename and change types of parameters
+ private String mParam1;
+ private String mParam2;
+</#if>
+
+ // The URL to +1. Must be a valid URL.
+ private final String PLUS_ONE_URL = "http://developer.android.com";
+
+ // The request code must be 0 or greater.
+ private static final int PLUS_ONE_REQUEST_CODE = 0;
+
+ private PlusOneButton mPlusOneButton;
+
+<#if includeCallbacks>
+ private OnFragmentInteractionListener mListener;
+</#if>
+
+<#if includeFactory>
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment ${className}.
+ */
+ // TODO: Rename and change types and number of parameters
+ public static ${className} newInstance(String param1, String param2) {
+ ${className} fragment = new ${className}();
+ Bundle args = new Bundle();
+ args.putString(ARG_PARAM1, param1);
+ args.putString(ARG_PARAM2, param2);
+ fragment.setArguments(args);
+ return fragment;
+ }
+</#if>
+ public ${className}() {
+ // Required empty public constructor
+ }
+
+<#if includeFactory>
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() != null) {
+ mParam1 = getArguments().getString(ARG_PARAM1);
+ mParam2 = getArguments().getString(ARG_PARAM2);
+ }
+ }
+</#if>
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View view = inflater.inflate(R.layout.fragment_${classToResource(className)}, container, false);
+
+ //Find the +1 button
+ mPlusOneButton = (PlusOneButton) view.findViewById(R.id.plus_one_button);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Refresh the state of the +1 button each time the activity receives focus.
+ mPlusOneButton.initialize(PLUS_ONE_URL, PLUS_ONE_REQUEST_CODE);
+ }
+
+<#if includeCallbacks>
+ // TODO: Rename method, update argument and hook method into UI event
+ public void onButtonPressed(Uri uri) {
+ if (mListener != null) {
+ mListener.onFragmentInteraction(uri);
+ }
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof OnFragmentInteractionListener) {
+ mListener = (OnFragmentInteractionListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement OnFragmentInteractionListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ /**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ * <p>
+ * See the Android Training lesson <a href=
+ * "http://developer.android.com/training/basics/fragments/communicating.html"
+ * >Communicating with Other Fragments</a> for more information.
+ */
+ public interface OnFragmentInteractionListener {
+ // TODO: Update argument type and name
+ void onFragmentInteraction(Uri uri);
+ }
+</#if>
+
+}
diff --git a/templates/other/PlusOneFragment/template.xml b/templates/other/PlusOneFragment/template.xml
new file mode 100644
index 0000000..99ab44a
--- /dev/null
+++ b/templates/other/PlusOneFragment/template.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Fragment (with a +1 button)"
+ description="Creates a fragment with a Google Plus +1 button."
+ minApi="8"
+ minBuildApi="8">
+
+ <category value="Fragment" />
+
+ <dependency name="android-support-v4" revision="8" />
+
+ <category value="Other" />
+
+ <parameter
+ id="className"
+ name="Fragment Name"
+ type="string"
+ constraints="class|nonempty|unique"
+ default="PlusOneFragment"
+ help="The name of the fragment class to create" />
+
+ <parameter
+ id="includeFactory"
+ name="Include fragment factory methods?"
+ type="boolean"
+ default="true"
+ help="Generate static fragment factory methods for easy instantiation" />
+
+ <parameter
+ id="includeCallbacks"
+ name="Include interface callbacks?"
+ type="boolean"
+ default="true"
+ help="Generate event callbacks for communication with an Activity or other fragments" />
+
+ <thumbs>
+ <thumb>templates_plusone_fragment.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/PlusOneFragment/templates_plusone_fragment.png b/templates/other/PlusOneFragment/templates_plusone_fragment.png
new file mode 100644
index 0000000..c9d6b4c
Binary files /dev/null and b/templates/other/PlusOneFragment/templates_plusone_fragment.png differ
diff --git a/templates/other/ResFolder/recipe.xml.ftl b/templates/other/ResFolder/recipe.xml.ftl
new file mode 100644
index 0000000..132a260
--- /dev/null
+++ b/templates/other/ResFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/res/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/ResFolder/root/build.gradle.ftl b/templates/other/ResFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/ResFolder/root/build.gradle.ftl
rename to templates/other/ResFolder/root/build.gradle.ftl
diff --git a/templates/other/ResFolder/template.xml b/templates/other/ResFolder/template.xml
new file mode 100644
index 0000000..8cebcbc
--- /dev/null
+++ b/templates/other/ResFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Res Folder"
+ description="Creates a source root for Android Resource files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/res/"
+ help="The location for the new folder"
+ enabled="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/ResourcesFolder/recipe.xml.ftl b/templates/other/ResourcesFolder/recipe.xml.ftl
new file mode 100644
index 0000000..78702cf
--- /dev/null
+++ b/templates/other/ResourcesFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/resources/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/ResourcesFolder/root/build.gradle.ftl b/templates/other/ResourcesFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/ResourcesFolder/root/build.gradle.ftl
rename to templates/other/ResourcesFolder/root/build.gradle.ftl
diff --git a/templates/other/ResourcesFolder/template.xml b/templates/other/ResourcesFolder/template.xml
new file mode 100644
index 0000000..33e63ff
--- /dev/null
+++ b/templates/other/ResourcesFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Java Resources Folder"
+ description="Creates a source root for Java Resource (NOT Android resource) files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/resources/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/RsFolder/recipe.xml.ftl b/templates/other/RsFolder/recipe.xml.ftl
new file mode 100644
index 0000000..ecb4f51
--- /dev/null
+++ b/templates/other/RsFolder/recipe.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <#if remapFolder>
+ <mkdir at="${escapeXmlAttribute(projectOut)}/${escapeXmlAttribute(newLocation)}" />
+ <merge from="root/build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <#else>
+ <mkdir at="${escapeXmlAttribute(manifestOut)}/rs/" />
+ </#if>
+
+</recipe>
diff --git a/base/templates/other/RsFolder/root/build.gradle.ftl b/templates/other/RsFolder/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/RsFolder/root/build.gradle.ftl
rename to templates/other/RsFolder/root/build.gradle.ftl
diff --git a/templates/other/RsFolder/template.xml b/templates/other/RsFolder/template.xml
new file mode 100644
index 0000000..5f005fe
--- /dev/null
+++ b/templates/other/RsFolder/template.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="RenderScript Folder"
+ description="Creates a source root for RenderScript files."
+ >
+
+ <category value="Folder" />
+
+ <parameter
+ id="remapFolder"
+ name="Change Folder Location"
+ type="boolean"
+ constraints=""
+ default="false"
+ help="Change the folder location to another folder within the module." />
+
+ <parameter
+ id="newLocation"
+ name="New Folder Location"
+ type="string"
+ constraints="nonempty|source_set_folder|unique"
+ suggest="src/${sourceProviderName}/rs/"
+ help="The location for the new folder"
+ visibility="remapFolder" />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/other/Service/globals.xml.ftl b/templates/other/Service/globals.xml.ftl
similarity index 100%
rename from base/templates/other/Service/globals.xml.ftl
rename to templates/other/Service/globals.xml.ftl
diff --git a/templates/other/Service/recipe.xml.ftl b/templates/other/Service/recipe.xml.ftl
new file mode 100644
index 0000000..bd6a14a
--- /dev/null
+++ b/templates/other/Service/recipe.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <instantiate from="root/src/app_package/Service.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
+</recipe>
diff --git a/base/templates/other/Service/root/AndroidManifest.xml.ftl b/templates/other/Service/root/AndroidManifest.xml.ftl
similarity index 100%
rename from base/templates/other/Service/root/AndroidManifest.xml.ftl
rename to templates/other/Service/root/AndroidManifest.xml.ftl
diff --git a/base/templates/other/Service/root/src/app_package/Service.java.ftl b/templates/other/Service/root/src/app_package/Service.java.ftl
similarity index 100%
rename from base/templates/other/Service/root/src/app_package/Service.java.ftl
rename to templates/other/Service/root/src/app_package/Service.java.ftl
diff --git a/templates/other/Service/template.xml b/templates/other/Service/template.xml
new file mode 100644
index 0000000..cf8015b
--- /dev/null
+++ b/templates/other/Service/template.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Service"
+ description="Creates a new service component and adds it to your Android manifest.">
+
+ <category value="Service" />
+
+ <parameter
+ id="className"
+ name="Class Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyService" />
+
+ <parameter
+ id="isExported"
+ name="Exported"
+ type="boolean"
+ default="true"
+ help="Whether or not components of other applications can invoke the service or interact with it" />
+
+ <parameter
+ id="isEnabled"
+ name="Enabled"
+ type="boolean"
+ default="true"
+ help="Whether or not the service can be instantiated by the system" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/ValueResourceFile/recipe.xml.ftl b/templates/other/ValueResourceFile/recipe.xml.ftl
new file mode 100644
index 0000000..6b47992
--- /dev/null
+++ b/templates/other/ValueResourceFile/recipe.xml.ftl
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <instantiate from="root/res/values.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/values/${escapeXmlAttribute(fileName)}.xml" />
+</recipe>
diff --git a/base/templates/other/ValueResourceFile/root/res/values.xml.ftl b/templates/other/ValueResourceFile/root/res/values.xml.ftl
similarity index 100%
rename from base/templates/other/ValueResourceFile/root/res/values.xml.ftl
rename to templates/other/ValueResourceFile/root/res/values.xml.ftl
diff --git a/templates/other/ValueResourceFile/template.xml b/templates/other/ValueResourceFile/template.xml
new file mode 100644
index 0000000..5cef2a2
--- /dev/null
+++ b/templates/other/ValueResourceFile/template.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="2"
+ name="Values XML File"
+ description="Creates a new XML values file."
+ >
+
+ <category value="XML" />
+
+ <parameter
+ id="fileName"
+ name="Values File Name"
+ type="string"
+ constraints="unique|nonempty|values"
+ default="values"
+ help="Name of the values XML file." />
+
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/other/WatchFaceService/analog_round.png b/templates/other/WatchFaceService/analog_round.png
new file mode 100644
index 0000000..814c2a8
Binary files /dev/null and b/templates/other/WatchFaceService/analog_round.png differ
diff --git a/templates/other/WatchFaceService/analog_square.png b/templates/other/WatchFaceService/analog_square.png
new file mode 100644
index 0000000..5cf3800
Binary files /dev/null and b/templates/other/WatchFaceService/analog_square.png differ
diff --git a/templates/other/WatchFaceService/digital_round.png b/templates/other/WatchFaceService/digital_round.png
new file mode 100644
index 0000000..ead7c2d
Binary files /dev/null and b/templates/other/WatchFaceService/digital_round.png differ
diff --git a/templates/other/WatchFaceService/digital_square.png b/templates/other/WatchFaceService/digital_square.png
new file mode 100644
index 0000000..ecb2f65
Binary files /dev/null and b/templates/other/WatchFaceService/digital_square.png differ
diff --git a/templates/other/WatchFaceService/globals.xml.ftl b/templates/other/WatchFaceService/globals.xml.ftl
new file mode 100644
index 0000000..314d567
--- /dev/null
+++ b/templates/other/WatchFaceService/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <#include "root://activities/common/wear_common_globals.xml.ftl" />
+</globals>
diff --git a/templates/other/WatchFaceService/recipe.xml.ftl b/templates/other/WatchFaceService/recipe.xml.ftl
new file mode 100644
index 0000000..d093e88
--- /dev/null
+++ b/templates/other/WatchFaceService/recipe.xml.ftl
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<recipe>
+
+ <dependency mavenUrl="com.google.android.support:wearable:1.1.+" />
+
+ <merge from="root/AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <merge from="root/AndroidManifestPermissions.xml"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+<#if appManifestOut??>
+ <merge from="root/AndroidManifestPermissions.xml"
+ to="${escapeXmlAttribute(appManifestOut)}/AndroidManifest.xml" />
+</#if>
+
+ <merge from="root/res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+
+ <merge from="root/res/values/colors.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
+
+ <merge from="root/res/values/dimens.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
+
+ <copy from="root/res/xml/watch_face.xml"
+ to="${escapeXmlAttribute(resOut)}/xml/watch_face.xml" />
+
+<#if style == "analog">
+ <copy from="root/res/drawable-nodpi/preview_analog.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/preview_analog.png" />
+<#elseif style == "digital">
+ <copy from="root/res/drawable-nodpi/preview_digital.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/preview_digital.png" />
+ <copy from="root/res/drawable-nodpi/preview_digital_circular.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/preview_digital_circular.png" />
+</#if>
+
+<#if style == "analog">
+ <instantiate from="root/src/app_package/MyAnalogWatchFaceService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${serviceClass}.java" />
+<#elseif style == "digital">
+ <instantiate from="root/src/app_package/MyDigitalWatchFaceService.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/${serviceClass}.java" />
+</#if>
+
+ <open file="${escapeXmlAttribute(srcOut)}/${serviceClass}.java" />
+</recipe>
diff --git a/templates/other/WatchFaceService/root/AndroidManifest.xml.ftl b/templates/other/WatchFaceService/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..ce75b32
--- /dev/null
+++ b/templates/other/WatchFaceService/root/AndroidManifest.xml.ftl
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <uses-feature android:name="android.hardware.type.watch" />
+
+ <application>
+ <service
+ android:name="${relativePackage}.${serviceClass}"
+<#if style == "analog">
+ android:label="@string/my_analog_name"
+<#elseif style == "digital">
+ android:label="@string/my_digital_name"
+</#if>
+ android:permission="android.permission.BIND_WALLPAPER" >
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview"
+<#if style == "analog">
+ android:resource="@drawable/preview_analog" />
+<#elseif style == "digital">
+ android:resource="@drawable/preview_digital" />
+</#if>
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+<#if style == "analog">
+ android:resource="@drawable/preview_analog" />
+<#elseif style == "digital">
+ android:resource="@drawable/preview_digital_circular" />
+</#if>
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+ <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+ </intent-filter>
+ </service>
+
+ <meta-data
+ android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+ </application>
+</manifest>
diff --git a/templates/other/WatchFaceService/root/AndroidManifestPermissions.xml b/templates/other/WatchFaceService/root/AndroidManifestPermissions.xml
new file mode 100644
index 0000000..a170fb0
--- /dev/null
+++ b/templates/other/WatchFaceService/root/AndroidManifestPermissions.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <!-- Required to act as a custom watch face. -->
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+</manifest>
diff --git a/base/templates/other/WatchFaceService/root/build.gradle.ftl b/templates/other/WatchFaceService/root/build.gradle.ftl
similarity index 100%
rename from base/templates/other/WatchFaceService/root/build.gradle.ftl
rename to templates/other/WatchFaceService/root/build.gradle.ftl
diff --git a/base/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_analog.png b/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_analog.png
similarity index 100%
rename from base/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_analog.png
rename to templates/other/WatchFaceService/root/res/drawable-nodpi/preview_analog.png
diff --git a/base/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital.png b/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital.png
similarity index 100%
rename from base/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital.png
rename to templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital.png
diff --git a/base/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital_circular.png b/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital_circular.png
similarity index 100%
rename from base/templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital_circular.png
rename to templates/other/WatchFaceService/root/res/drawable-nodpi/preview_digital_circular.png
diff --git a/templates/other/WatchFaceService/root/res/values/colors.xml.ftl b/templates/other/WatchFaceService/root/res/values/colors.xml.ftl
new file mode 100644
index 0000000..97f468e
--- /dev/null
+++ b/templates/other/WatchFaceService/root/res/values/colors.xml.ftl
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="background">#000000</color>
+<#if isInteractive>
+ <color name="background2">#000088</color>
+</#if>
+<#if style == "analog">
+ <color name="analog_hands">#cccccc</color>
+<#elseif style == "digital">
+ <color name="digital_text">#ffffff</color>
+</#if>
+</resources>
diff --git a/base/templates/other/WatchFaceService/root/res/values/dimens.xml.ftl b/templates/other/WatchFaceService/root/res/values/dimens.xml.ftl
similarity index 100%
rename from base/templates/other/WatchFaceService/root/res/values/dimens.xml.ftl
rename to templates/other/WatchFaceService/root/res/values/dimens.xml.ftl
diff --git a/base/templates/other/WatchFaceService/root/res/values/strings.xml.ftl b/templates/other/WatchFaceService/root/res/values/strings.xml.ftl
similarity index 100%
rename from base/templates/other/WatchFaceService/root/res/values/strings.xml.ftl
rename to templates/other/WatchFaceService/root/res/values/strings.xml.ftl
diff --git a/base/templates/other/WatchFaceService/root/res/xml/watch_face.xml b/templates/other/WatchFaceService/root/res/xml/watch_face.xml
similarity index 100%
rename from base/templates/other/WatchFaceService/root/res/xml/watch_face.xml
rename to templates/other/WatchFaceService/root/res/xml/watch_face.xml
diff --git a/templates/other/WatchFaceService/root/src/app_package/MyAnalogWatchFaceService.java.ftl b/templates/other/WatchFaceService/root/src/app_package/MyAnalogWatchFaceService.java.ftl
new file mode 100644
index 0000000..c775d37
--- /dev/null
+++ b/templates/other/WatchFaceService/root/src/app_package/MyAnalogWatchFaceService.java.ftl
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ${packageName};
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.view.SurfaceHolder;
+
+import java.lang.ref.WeakReference;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On
+ * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode.
+ */
+public class ${serviceClass} extends CanvasWatchFaceService {
+ /**
+ * Update rate in milliseconds for interactive mode. We update once a second to advance the
+ * second hand.
+ */
+ private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
+
+ /**
+ * Handler message id for updating the time periodically in interactive mode.
+ */
+ private static final int MSG_UPDATE_TIME = 0;
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine {
+ final Handler mUpdateTimeHandler = new EngineHandler(this);
+
+ final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+ boolean mRegisteredTimeZoneReceiver = false;
+
+ Paint mBackgroundPaint;
+ Paint mHandPaint;
+
+ boolean mAmbient;
+ Time mTime;
+<#if isInteractive>
+ int mTapCount;
+</#if>
+
+ /**
+ * Whether the display supports fewer bits for each color in ambient mode. When true, we
+ * disable anti-aliasing in ambient mode.
+ */
+ boolean mLowBitAmbient;
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ super.onCreate(holder);
+
+ setWatchFaceStyle(new WatchFaceStyle.Builder(${serviceClass}.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+<#if isInteractive>
+ .setAcceptsTapEvents(true)
+</#if>
+ .build());
+
+ Resources resources = ${serviceClass}.this.getResources();
+
+ mBackgroundPaint = new Paint();
+ mBackgroundPaint.setColor(resources.getColor(R.color.background));
+
+ mHandPaint = new Paint();
+ mHandPaint.setColor(resources.getColor(R.color.analog_hands));
+ mHandPaint.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke));
+ mHandPaint.setAntiAlias(true);
+ mHandPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mTime = new Time();
+ }
+
+ @Override
+ public void onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onPropertiesChanged(Bundle properties) {
+ super.onPropertiesChanged(properties);
+ mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ invalidate();
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ super.onAmbientModeChanged(inAmbientMode);
+ if (mAmbient != inAmbientMode) {
+ mAmbient = inAmbientMode;
+ if (mLowBitAmbient) {
+ mHandPaint.setAntiAlias(!inAmbientMode);
+ }
+ invalidate();
+ }
+
+ // Whether the timer should be running depends on whether we're visible (as well as
+ // whether we're in ambient mode), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+<#if isInteractive>
+ /**
+ * Captures tap event (and tap type) and toggles the background color if the user finishes
+ * a tap.
+ */
+ @Override
+ public void onTapCommand(int tapType, int x, int y, long eventTime) {
+ Resources resources = ${serviceClass}.this.getResources();
+ switch (tapType) {
+ case TAP_TYPE_TOUCH:
+ // The user has started touching the screen.
+ break;
+ case TAP_TYPE_TOUCH_CANCEL:
+ // The user has started a different gesture or otherwise cancelled the tap.
+ break;
+ case TAP_TYPE_TAP:
+ // The user has completed the tap gesture.
+ mTapCount++;
+ mBackgroundPaint.setColor(resources.getColor(mTapCount % 2 == 0 ?
+ R.color.background : R.color.background2));
+ break;
+ }
+ invalidate();
+ }
+
+</#if>
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ mTime.setToNow();
+
+ // Draw the background.
+ if (isInAmbientMode()) {
+ canvas.drawColor(Color.BLACK);
+ } else {
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
+ }
+
+ // Find the center. Ignore the window insets so that, on round watches with a
+ // "chin", the watch face is centered on the entire screen, not just the usable
+ // portion.
+ float centerX = bounds.width() / 2f;
+ float centerY = bounds.height() / 2f;
+
+ float secRot = mTime.second / 30f * (float) Math.PI;
+ int minutes = mTime.minute;
+ float minRot = minutes / 30f * (float) Math.PI;
+ float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+
+ float secLength = centerX - 20;
+ float minLength = centerX - 40;
+ float hrLength = centerX - 80;
+
+ if (!mAmbient) {
+ float secX = (float) Math.sin(secRot) * secLength;
+ float secY = (float) -Math.cos(secRot) * secLength;
+ canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mHandPaint);
+ }
+
+ float minX = (float) Math.sin(minRot) * minLength;
+ float minY = (float) -Math.cos(minRot) * minLength;
+ canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mHandPaint);
+
+ float hrX = (float) Math.sin(hrRot) * hrLength;
+ float hrY = (float) -Math.cos(hrRot) * hrLength;
+ canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHandPaint);
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ super.onVisibilityChanged(visible);
+
+ if (visible) {
+ registerReceiver();
+
+ // Update time zone in case it changed while we weren't visible.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+ } else {
+ unregisterReceiver();
+ }
+
+ // Whether the timer should be running depends on whether we're visible (as well as
+ // whether we're in ambient mode), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ ${serviceClass}.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ ${serviceClass}.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+
+ /**
+ * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
+ * or stops it if it shouldn't be running but currently is.
+ */
+ private void updateTimer() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ if (shouldTimerBeRunning()) {
+ mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
+ }
+ }
+
+ /**
+ * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
+ * only run when we're visible and in interactive mode.
+ */
+ private boolean shouldTimerBeRunning() {
+ return isVisible() && !isInAmbientMode();
+ }
+
+ /**
+ * Handle updating the time periodically in interactive mode.
+ */
+ private void handleUpdateTimeMessage() {
+ invalidate();
+ if (shouldTimerBeRunning()) {
+ long timeMs = System.currentTimeMillis();
+ long delayMs = INTERACTIVE_UPDATE_RATE_MS
+ - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+ }
+ }
+ }
+
+ private static class EngineHandler extends Handler {
+ private final WeakReference<${serviceClass}.Engine> mWeakReference;
+
+ public EngineHandler(${serviceClass}.Engine reference) {
+ mWeakReference = new WeakReference<>(reference);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ ${serviceClass}.Engine engine = mWeakReference.get();
+ if (engine != null) {
+ switch (msg.what) {
+ case MSG_UPDATE_TIME:
+ engine.handleUpdateTimeMessage();
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/templates/other/WatchFaceService/root/src/app_package/MyDigitalWatchFaceService.java.ftl b/templates/other/WatchFaceService/root/src/app_package/MyDigitalWatchFaceService.java.ftl
new file mode 100644
index 0000000..1f7f165
--- /dev/null
+++ b/templates/other/WatchFaceService/root/src/app_package/MyDigitalWatchFaceService.java.ftl
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ${packageName};
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.view.SurfaceHolder;
+import android.view.WindowInsets;
+
+import java.lang.ref.WeakReference;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Digital watch face with seconds. In ambient mode, the seconds aren't displayed. On devices with
+ * low-bit ambient mode, the text is drawn without anti-aliasing in ambient mode.
+ */
+public class ${serviceClass} extends CanvasWatchFaceService {
+ private static final Typeface NORMAL_TYPEFACE =
+ Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
+
+ /**
+ * Update rate in milliseconds for interactive mode. We update once a second since seconds are
+ * displayed in interactive mode.
+ */
+ private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
+
+ /**
+ * Handler message id for updating the time periodically in interactive mode.
+ */
+ private static final int MSG_UPDATE_TIME = 0;
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine {
+ final Handler mUpdateTimeHandler = new EngineHandler(this);
+
+ final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+ boolean mRegisteredTimeZoneReceiver = false;
+
+ Paint mBackgroundPaint;
+ Paint mTextPaint;
+
+ boolean mAmbient;
+ Time mTime;
+<#if isInteractive>
+ int mTapCount;
+</#if>
+
+ float mXOffset;
+ float mYOffset;
+
+ /**
+ * Whether the display supports fewer bits for each color in ambient mode. When true, we
+ * disable anti-aliasing in ambient mode.
+ */
+ boolean mLowBitAmbient;
+
+ @Override
+ public void onCreate(SurfaceHolder holder) {
+ super.onCreate(holder);
+
+ setWatchFaceStyle(new WatchFaceStyle.Builder(${serviceClass}.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+<#if isInteractive>
+ .setAcceptsTapEvents(true)
+</#if>
+ .build());
+ Resources resources = ${serviceClass}.this.getResources();
+ mYOffset = resources.getDimension(R.dimen.digital_y_offset);
+
+ mBackgroundPaint = new Paint();
+ mBackgroundPaint.setColor(resources.getColor(R.color.background));
+
+ mTextPaint = new Paint();
+ mTextPaint = createTextPaint(resources.getColor(R.color.digital_text));
+
+ mTime = new Time();
+ }
+
+ @Override
+ public void onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ super.onDestroy();
+ }
+
+ private Paint createTextPaint(int textColor) {
+ Paint paint = new Paint();
+ paint.setColor(textColor);
+ paint.setTypeface(NORMAL_TYPEFACE);
+ paint.setAntiAlias(true);
+ return paint;
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ super.onVisibilityChanged(visible);
+
+ if (visible) {
+ registerReceiver();
+
+ // Update time zone in case it changed while we weren't visible.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+ } else {
+ unregisterReceiver();
+ }
+
+ // Whether the timer should be running depends on whether we're visible (as well as
+ // whether we're in ambient mode), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ ${serviceClass}.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ ${serviceClass}.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+
+ @Override
+ public void onApplyWindowInsets(WindowInsets insets) {
+ super.onApplyWindowInsets(insets);
+
+ // Load resources that have alternate values for round watches.
+ Resources resources = ${serviceClass}.this.getResources();
+ boolean isRound = insets.isRound();
+ mXOffset = resources.getDimension(isRound
+ ? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset);
+ float textSize = resources.getDimension(isRound
+ ? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
+
+ mTextPaint.setTextSize(textSize);
+ }
+
+ @Override
+ public void onPropertiesChanged(Bundle properties) {
+ super.onPropertiesChanged(properties);
+ mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ invalidate();
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ super.onAmbientModeChanged(inAmbientMode);
+ if (mAmbient != inAmbientMode) {
+ mAmbient = inAmbientMode;
+ if (mLowBitAmbient) {
+ mTextPaint.setAntiAlias(!inAmbientMode);
+ }
+ invalidate();
+ }
+
+ // Whether the timer should be running depends on whether we're visible (as well as
+ // whether we're in ambient mode), so we may need to start or stop the timer.
+ updateTimer();
+ }
+
+<#if isInteractive>
+ /**
+ * Captures tap event (and tap type) and toggles the background color if the user finishes
+ * a tap.
+ */
+ @Override
+ public void onTapCommand(int tapType, int x, int y, long eventTime) {
+ Resources resources = ${serviceClass}.this.getResources();
+ switch (tapType) {
+ case TAP_TYPE_TOUCH:
+ // The user has started touching the screen.
+ break;
+ case TAP_TYPE_TOUCH_CANCEL:
+ // The user has started a different gesture or otherwise cancelled the tap.
+ break;
+ case TAP_TYPE_TAP:
+ // The user has completed the tap gesture.
+ mTapCount++;
+ mBackgroundPaint.setColor(resources.getColor(mTapCount % 2 == 0 ?
+ R.color.background : R.color.background2));
+ break;
+ }
+ invalidate();
+ }
+
+</#if>
+ @Override
+ public void onDraw(Canvas canvas, Rect bounds) {
+ // Draw the background.
+ if (isInAmbientMode()) {
+ canvas.drawColor(Color.BLACK);
+ } else {
+ canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
+ }
+
+ // Draw H:MM in ambient mode or H:MM:SS in interactive mode.
+ mTime.setToNow();
+ String text = mAmbient
+ ? String.format("%d:%02d", mTime.hour, mTime.minute)
+ : String.format("%d:%02d:%02d", mTime.hour, mTime.minute, mTime.second);
+ canvas.drawText(text, mXOffset, mYOffset, mTextPaint);
+ }
+
+ /**
+ * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
+ * or stops it if it shouldn't be running but currently is.
+ */
+ private void updateTimer() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+ if (shouldTimerBeRunning()) {
+ mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
+ }
+ }
+
+ /**
+ * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
+ * only run when we're visible and in interactive mode.
+ */
+ private boolean shouldTimerBeRunning() {
+ return isVisible() && !isInAmbientMode();
+ }
+
+ /**
+ * Handle updating the time periodically in interactive mode.
+ */
+ private void handleUpdateTimeMessage() {
+ invalidate();
+ if (shouldTimerBeRunning()) {
+ long timeMs = System.currentTimeMillis();
+ long delayMs = INTERACTIVE_UPDATE_RATE_MS
+ - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+ }
+ }
+ }
+
+ private static class EngineHandler extends Handler {
+ private final WeakReference<${serviceClass}.Engine> mWeakReference;
+
+ public EngineHandler(${serviceClass}.Engine reference) {
+ mWeakReference = new WeakReference<>(reference);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ ${serviceClass}.Engine engine = mWeakReference.get();
+ if (engine != null) {
+ switch (msg.what) {
+ case MSG_UPDATE_TIME:
+ engine.handleUpdateTimeMessage();
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/templates/other/WatchFaceService/template.xml b/templates/other/WatchFaceService/template.xml
new file mode 100644
index 0000000..0eb4aa0
--- /dev/null
+++ b/templates/other/WatchFaceService/template.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<template
+ format="5"
+ revision="3"
+ name="Watch Face"
+ minApi="21"
+ minBuildApi="21"
+ description="Creates a watch face for Android Wear">
+
+ <category value="Wear" />
+ <formfactor value="Wear" />
+
+ <parameter
+ id="serviceClass"
+ name="Service Name"
+ type="string"
+ constraints="class|unique|nonempty"
+ default="MyWatchFace"
+ help="The name of the service class to create" />
+
+ <parameter
+ id="style"
+ name="Style"
+ type="enum"
+ default="analog"
+ help="Watch face style">
+ <option id="analog">Analog</option>
+ <option id="digital">Digital</option>
+ </parameter>
+
+ <parameter
+ id="isInteractive"
+ name="Interactive"
+ type="boolean"
+ default="true"
+ help="Whether or not to include code for interactive touch handling" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_thumbnail.png</thumb>
+ <thumb style="analog">analog_round.png</thumb>
+ <thumb style="digital">digital_square.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/base/templates/other/WatchFaceService/template_thumbnail.png b/templates/other/WatchFaceService/template_thumbnail.png
similarity index 100%
rename from base/templates/other/WatchFaceService/template_thumbnail.png
rename to templates/other/WatchFaceService/template_thumbnail.png
diff --git a/base/testutils/.classpath b/testutils/.classpath
similarity index 100%
rename from base/testutils/.classpath
rename to testutils/.classpath
diff --git a/base/testutils/.gitignore b/testutils/.gitignore
similarity index 100%
rename from base/testutils/.gitignore
rename to testutils/.gitignore
diff --git a/base/testutils/.project b/testutils/.project
similarity index 100%
rename from base/testutils/.project
rename to testutils/.project
diff --git a/base/sdklib/.settings/org.eclipse.jdt.core.prefs b/testutils/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from base/sdklib/.settings/org.eclipse.jdt.core.prefs
rename to testutils/.settings/org.eclipse.jdt.core.prefs
diff --git a/base/testutils/NOTICE b/testutils/NOTICE
similarity index 100%
rename from base/testutils/NOTICE
rename to testutils/NOTICE
diff --git a/base/testutils/build.gradle b/testutils/build.gradle
similarity index 100%
rename from base/testutils/build.gradle
rename to testutils/build.gradle
diff --git a/testutils/src/main/java/com/android/testutils/SdkTestCase.java b/testutils/src/main/java/com/android/testutils/SdkTestCase.java
new file mode 100644
index 0000000..b6fcbf9
--- /dev/null
+++ b/testutils/src/main/java/com/android/testutils/SdkTestCase.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.testutils;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+import com.google.common.io.InputSupplier;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Common test case for SDK unit tests. Contains a number of general utility methods
+ * to help writing test cases, such as looking up a temporary directory, comparing golden
+ * files, computing string diffs, etc.
+ */
+ at SuppressWarnings("javadoc")
+public abstract class SdkTestCase extends TestCase {
+ /** Update golden files if different from the actual results */
+ private static final boolean UPDATE_DIFFERENT_FILES = false;
+ /** Create golden files if missing */
+ private static final boolean UPDATE_MISSING_FILES = true;
+ private static File sTempDir = null;
+ protected static Set<File> sCleanDirs = Sets.newHashSet();
+
+ protected String getTestDataRelPath() {
+ fail("Must be overridden");
+ return null;
+ }
+
+ public static int getCaretOffset(String fileContent, String caretLocation) {
+ assertTrue(caretLocation, caretLocation.contains("^")); //$NON-NLS-1$
+
+ int caretDelta = caretLocation.indexOf("^"); //$NON-NLS-1$
+ assertTrue(caretLocation, caretDelta != -1);
+
+ // String around caret/range without the range and caret marker characters
+ String caretContext;
+ if (caretLocation.contains("[^")) { //$NON-NLS-1$
+ caretDelta--;
+ assertTrue(caretLocation, caretLocation.startsWith("[^", caretDelta)); //$NON-NLS-1$
+ int caretRangeEnd = caretLocation.indexOf(']', caretDelta + 2);
+ assertTrue(caretLocation, caretRangeEnd != -1);
+ caretContext = caretLocation.substring(0, caretDelta)
+ + caretLocation.substring(caretDelta + 2, caretRangeEnd)
+ + caretLocation.substring(caretRangeEnd + 1);
+ } else {
+ caretContext = caretLocation.substring(0, caretDelta)
+ + caretLocation.substring(caretDelta + 1); // +1: skip "^"
+ }
+
+ int caretContextIndex = fileContent.indexOf(caretContext);
+ assertTrue("Caret content " + caretContext + " not found in file",
+ caretContextIndex != -1);
+ return caretContextIndex + caretDelta;
+ }
+
+ public static String addSelection(String newFileContents, int selectionBegin, int selectionEnd) {
+ // Insert selection markers -- [ ] for the selection range, ^ for the caret
+ String newFileWithCaret;
+ if (selectionBegin < selectionEnd) {
+ newFileWithCaret = newFileContents.substring(0, selectionBegin) + "[^"
+ + newFileContents.substring(selectionBegin, selectionEnd) + "]"
+ + newFileContents.substring(selectionEnd);
+ } else {
+ // Selected range
+ newFileWithCaret = newFileContents.substring(0, selectionBegin) + "^"
+ + newFileContents.substring(selectionBegin);
+ }
+
+ return newFileWithCaret;
+ }
+
+ public static String getCaretContext(String file, int offset) {
+ int windowSize = 20;
+ int begin = Math.max(0, offset - windowSize / 2);
+ int end = Math.min(file.length(), offset + windowSize / 2);
+
+ return "..." + file.substring(begin, offset) + "^" + file.substring(offset, end) + "...";
+ }
+
+ /** Get the location to write missing golden files to */
+ protected File getTargetDir() {
+ // Set $ADT_SDK_SOURCE_PATH to point to your git "sdk" directory; if done, then
+ // if you run a unit test which refers to a golden file which does not exist, it
+ // will be created directly into the test data directory and you can rerun the
+ // test
+ // and it should pass (after you verify that the golden file contains the correct
+ // result of course).
+ String sdk = System.getenv("ADT_SDK_SOURCE_PATH");
+ if (sdk != null) {
+ File sdkPath = new File(sdk);
+ if (sdkPath.exists()) {
+ File testData = new File(sdkPath, getTestDataRelPath().replace('/',
+ File.separatorChar));
+ if (testData.exists()) {
+ addCleanupDir(testData);
+ return testData;
+ }
+ }
+ }
+ return getTempDir();
+ }
+
+ public static File getTempDir() {
+ if (sTempDir == null) {
+ File base = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ base = new File("/tmp"); //$NON-NLS-1$
+ }
+
+ // On Windows, we don't want to pollute the temp folder (which is generally
+ // already incredibly busy). So let's create a temp folder for the results.
+
+ Calendar c = Calendar.getInstance();
+ String name = String.format("sdkTests_%1$tF_%1$tT", c).replace(':', '-'); //$NON-NLS-1$
+ File tmpDir = new File(base, name);
+ if (!tmpDir.exists() && tmpDir.mkdir()) {
+ sTempDir = tmpDir;
+ } else {
+ sTempDir = base;
+ }
+ addCleanupDir(sTempDir);
+ }
+
+ return sTempDir;
+ }
+
+ protected String removeSessionData(String data) {
+ return data;
+ }
+
+ protected InputStream getTestResource(String relativePath, boolean expectExists) {
+ String path = "testdata" + File.separator + relativePath; //$NON-NLS-1$
+ InputStream stream = SdkTestCase.class.getResourceAsStream(path);
+ if (!expectExists && stream == null) {
+ return null;
+ }
+ return stream;
+ }
+
+ @SuppressWarnings("resource")
+ protected String readTestFile(String relativePath, boolean expectExists) throws IOException {
+ InputStream stream = getTestResource(relativePath, expectExists);
+ if (expectExists) {
+ assertNotNull(relativePath + " does not exist", stream);
+ } else if (stream == null) {
+ return null;
+ }
+
+ String xml = new String(ByteStreams.toByteArray(stream), Charsets.UTF_8);
+ try {
+ Closeables.close(stream, true /* swallowIOException */);
+ } catch (IOException e) {
+ // cannot happen
+ }
+
+ assertTrue(!xml.isEmpty());
+
+ // Remove any references to the project name such that we are isolated from
+ // that in golden file.
+ // Appears in strings.xml etc.
+ xml = removeSessionData(xml);
+
+ return xml;
+ }
+
+ protected void assertEqualsGolden(String basename, String actual) throws IOException {
+ assertEqualsGolden(basename, actual, basename.substring(basename.lastIndexOf('.') + 1));
+ }
+
+ protected void assertEqualsGolden(String basename, String actual, String newExtension)
+ throws IOException {
+ String testName = getName();
+ if (testName.startsWith("test")) {
+ testName = testName.substring(4);
+ if (Character.isUpperCase(testName.charAt(0))) {
+ testName = Character.toLowerCase(testName.charAt(0)) + testName.substring(1);
+ }
+ }
+ String expectedName;
+ String extension = basename.substring(basename.lastIndexOf('.') + 1);
+ if (newExtension == null) {
+ newExtension = extension;
+ }
+ expectedName = basename.substring(0, basename.indexOf('.'))
+ + "-expected-" + testName + '.' + newExtension;
+ String expected = readTestFile(expectedName, false);
+ if (expected == null) {
+ File expectedPath = new File(
+ UPDATE_MISSING_FILES ? getTargetDir() : getTempDir(), expectedName);
+ Files.write(actual, expectedPath, Charsets.UTF_8);
+ System.out.println("Expected - written to " + expectedPath + ":\n");
+ System.out.println(actual);
+ fail("Did not find golden file (" + expectedName + "): Wrote contents as "
+ + expectedPath);
+ } else {
+ if (!expected.replaceAll("\r\n", "\n").equals(actual.replaceAll("\r\n", "\n"))) {
+ File expectedPath = new File(getTempDir(), expectedName);
+ File actualPath = new File(getTempDir(),
+ expectedName.replace("expected", "actual"));
+ Files.write(expected, expectedPath, Charsets.UTF_8);
+ Files.write(actual, actualPath, Charsets.UTF_8);
+ // Also update data dir with the current value
+ if (UPDATE_DIFFERENT_FILES) {
+ Files.write(actual, new File(getTargetDir(), expectedName), Charsets.UTF_8);
+ }
+ System.out.println("The files differ: diff " + expectedPath + " "
+ + actualPath);
+ assertEquals("The files differ - see " + expectedPath + " versus " + actualPath,
+ expected, actual);
+ }
+ }
+ }
+
+ /** Creates a diff of two strings */
+ public static String getDiff(String before, String after) {
+ return getDiff(before.split("\n"), after.split("\n"));
+ }
+
+ public static String getDiff(String[] before, String[] after) {
+ // Based on the LCS section in http://introcs.cs.princeton.edu/java/96optimization/
+ StringBuilder sb = new StringBuilder();
+
+ int n = before.length;
+ int m = after.length;
+
+ // Compute longest common subsequence of x[i..m] and y[j..n] bottom up
+ int[][] lcs = new int[n + 1][m + 1];
+ for (int i = n - 1; i >= 0; i--) {
+ for (int j = m - 1; j >= 0; j--) {
+ if (before[i].equals(after[j])) {
+ lcs[i][j] = lcs[i + 1][j + 1] + 1;
+ } else {
+ lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1]);
+ }
+ }
+ }
+
+ int i = 0;
+ int j = 0;
+ while ((i < n) && (j < m)) {
+ if (before[i].equals(after[j])) {
+ i++;
+ j++;
+ } else {
+ sb.append("@@ -");
+ sb.append(Integer.toString(i + 1));
+ sb.append(" +");
+ sb.append(Integer.toString(j + 1));
+ sb.append('\n');
+ while (i < n && j < m && !before[i].equals(after[j])) {
+ if (lcs[i + 1][j] >= lcs[i][j + 1]) {
+ sb.append('-');
+ if (!before[i].trim().isEmpty()) {
+ sb.append(' ');
+ }
+ sb.append(before[i]);
+ sb.append('\n');
+ i++;
+ } else {
+ sb.append('+');
+ if (!after[j].trim().isEmpty()) {
+ sb.append(' ');
+ }
+ sb.append(after[j]);
+ sb.append('\n');
+ j++;
+ }
+ }
+ }
+ }
+
+ if (i < n || j < m) {
+ assert i == n || j == m;
+ sb.append("@@ -");
+ sb.append(Integer.toString(i + 1));
+ sb.append(" +");
+ sb.append(Integer.toString(j + 1));
+ sb.append('\n');
+ for (; i < n; i++) {
+ sb.append('-');
+ if (!before[i].trim().isEmpty()) {
+ sb.append(' ');
+ }
+ sb.append(before[i]);
+ sb.append('\n');
+ }
+ for (; j < m; j++) {
+ sb.append('+');
+ if (!after[j].trim().isEmpty()) {
+ sb.append(' ');
+ }
+ sb.append(after[j]);
+ sb.append('\n');
+ }
+ }
+
+ return sb.toString();
+ }
+
+ protected void deleteFile(File dir) {
+ TestUtils.deleteFile(dir);
+ }
+
+ protected File makeTestFile(String name, String relative,
+ final InputStream contents) throws IOException {
+ return makeTestFile(getTargetDir(), name, relative, contents);
+ }
+
+ protected File makeTestFile(File dir, String name, String relative,
+ final InputStream contents) throws IOException {
+ if (relative != null) {
+ dir = new File(dir, relative);
+ if (!dir.exists()) {
+ boolean mkdir = dir.mkdirs();
+ assertTrue(dir.getPath(), mkdir);
+ }
+ } else if (!dir.exists()) {
+ boolean mkdir = dir.mkdirs();
+ assertTrue(dir.getPath(), mkdir);
+ }
+ File tempFile = new File(dir, name);
+ if (tempFile.exists()) {
+ tempFile.delete();
+ }
+
+ Files.copy(new InputSupplier<InputStream>() {
+ @Override
+ public InputStream getInput() throws IOException {
+ return contents;
+ }
+ }, tempFile);
+
+ return tempFile;
+ }
+
+ /**
+ * Test file description, which can copy from resource directory or from
+ * a specified hardcoded string literal, and copy into a target directory
+ */
+ public class TestFile {
+ public String sourceRelativePath;
+ public String targetRelativePath;
+ public String contents;
+
+ public TestFile() {
+ }
+
+ public TestFile withSource(@NonNull String source) {
+ contents = source;
+ return this;
+ }
+
+ public TestFile from(@NonNull String from) {
+ sourceRelativePath = from;
+ return this;
+ }
+
+ public TestFile to(@NonNull String to) {
+ targetRelativePath = to;
+ return this;
+ }
+
+ public TestFile copy(@NonNull String relativePath) {
+ // Support replacing filenames and paths with a => syntax, e.g.
+ // dir/file.txt=>dir2/dir3/file2.java
+ // will read dir/file.txt from the test data and write it into the target
+ // directory as dir2/dir3/file2.java
+ String targetPath = relativePath;
+ int replaceIndex = relativePath.indexOf("=>"); //$NON-NLS-1$
+ if (replaceIndex != -1) {
+ // foo=>bar
+ targetPath = relativePath.substring(replaceIndex + "=>".length());
+ relativePath = relativePath.substring(0, replaceIndex);
+ }
+ sourceRelativePath = relativePath;
+ targetRelativePath = targetPath;
+ return this;
+ }
+
+ @NonNull
+ public File createFile(@NonNull File targetDir) throws IOException {
+ InputStream stream;
+ if (contents != null) {
+ stream = new ByteArrayInputStream(contents.getBytes(Charsets.UTF_8));
+ } else {
+ stream = getTestResource(sourceRelativePath, true);
+ assertNotNull(sourceRelativePath + " does not exist", stream);
+ }
+ int index = targetRelativePath.lastIndexOf('/');
+ String relative = null;
+ String name = targetRelativePath;
+ if (index != -1) {
+ name = targetRelativePath.substring(index + 1);
+ relative = targetRelativePath.substring(0, index);
+ }
+
+ return makeTestFile(targetDir, name, relative, stream);
+ }
+
+ @Nullable
+ public String getContents() {
+ if (contents != null) {
+ return contents;
+ } else if (sourceRelativePath != null) {
+ InputStream stream = getTestResource(sourceRelativePath, true);
+ if (stream != null) {
+ try {
+ return new String(ByteStreams.toByteArray(stream), Charsets.UTF_8);
+ } catch (IOException ignore) {
+ return "<couldn't open test file " + sourceRelativePath + ">";
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ protected File getTestfile(File targetDir, String relativePath) throws IOException {
+ // Support replacing filenames and paths with a => syntax, e.g.
+ // dir/file.txt=>dir2/dir3/file2.java
+ // will read dir/file.txt from the test data and write it into the target
+ // directory as dir2/dir3/file2.java
+
+ String targetPath = relativePath;
+ int replaceIndex = relativePath.indexOf("=>"); //$NON-NLS-1$
+ if (replaceIndex != -1) {
+ // foo=>bar
+ targetPath = relativePath.substring(replaceIndex + "=>".length());
+ relativePath = relativePath.substring(0, replaceIndex);
+ }
+
+ InputStream stream = getTestResource(relativePath, true);
+ assertNotNull(relativePath + " does not exist", stream);
+ int index = targetPath.lastIndexOf('/');
+ String relative = null;
+ String name = targetPath;
+ if (index != -1) {
+ name = targetPath.substring(index + 1);
+ relative = targetPath.substring(0, index);
+ }
+
+ return makeTestFile(targetDir, name, relative, stream);
+ }
+
+ protected static void addCleanupDir(File dir) {
+ sCleanDirs.add(dir);
+ try {
+ sCleanDirs.add(dir.getCanonicalFile());
+ } catch (IOException e) {
+ fail(e.getLocalizedMessage());
+ }
+ sCleanDirs.add(dir.getAbsoluteFile());
+ }
+
+ protected String cleanup(String result) {
+ List<File> sorted = new ArrayList<File>(sCleanDirs);
+ // Process dirs in order such that we match longest substrings first
+ Collections.sort(sorted, new Comparator<File>() {
+ @Override
+ public int compare(File file1, File file2) {
+ String path1 = file1.getPath();
+ String path2 = file2.getPath();
+ int delta = path2.length() - path1.length();
+ if (delta != 0) {
+ return delta;
+ } else {
+ return path1.compareTo(path2);
+ }
+ }
+ });
+
+ for (File dir : sorted) {
+ if (result.contains(dir.getPath())) {
+ result = result.replace(dir.getPath(), "/TESTROOT");
+ }
+ }
+
+ // The output typically contains a few directory/filenames.
+ // On Windows we need to change the separators to the unix-style
+ // forward slash to make the test as OS-agnostic as possible.
+ if (File.separatorChar != '/') {
+ result = result.replace(File.separatorChar, '/');
+ }
+
+ return result;
+ }
+
+ /** Get the location to write missing golden files to */
+ protected File findSrcDir() {
+ // Set $ANDROID_SRC to point to your git AOSP working tree
+ String rootPath = System.getenv("ANDROID_SRC");
+ if (rootPath == null) {
+ String sdk = System.getenv("ADT_SDK_SOURCE_PATH");
+ if (sdk != null) {
+ File root = new File(sdk);
+ if (root.exists()) {
+ return root.getName().equals("sdk") ? root.getParentFile() : root;
+ }
+ }
+ } else {
+ File root = new File(rootPath);
+ if (root.exists()) {
+ return root;
+ }
+ }
+
+ return null;
+ }
+
+ protected File findSrcRelativeDir(String relative) {
+ // Set $ANDROID_SRC to point to your git AOSP working tree
+ File root = findSrcDir();
+ if (root != null) {
+ File testData = new File(root, relative.replace('/', File.separatorChar));
+ if (testData.exists()) {
+ return testData;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/base/testutils/src/main/java/com/android/testutils/TestUtils.java b/testutils/src/main/java/com/android/testutils/TestUtils.java
similarity index 100%
rename from base/testutils/src/main/java/com/android/testutils/TestUtils.java
rename to testutils/src/main/java/com/android/testutils/TestUtils.java
diff --git a/base/testutils/src/test/.classpath b/testutils/src/test/.classpath
similarity index 100%
rename from base/testutils/src/test/.classpath
rename to testutils/src/test/.classpath
diff --git a/base/testutils/src/test/.project b/testutils/src/test/.project
similarity index 100%
rename from base/testutils/src/test/.project
rename to testutils/src/test/.project
diff --git a/base/testutils/src/test/java/com/android/testutils/SdkTestCaseTest.java b/testutils/src/test/java/com/android/testutils/SdkTestCaseTest.java
similarity index 100%
rename from base/testutils/src/test/java/com/android/testutils/SdkTestCaseTest.java
rename to testutils/src/test/java/com/android/testutils/SdkTestCaseTest.java
diff --git a/base/testutils/testutils.iml b/testutils/testutils.iml
similarity index 100%
rename from base/testutils/testutils.iml
rename to testutils/testutils.iml
diff --git a/vector-drawable-tool/build.gradle b/vector-drawable-tool/build.gradle
new file mode 100644
index 0000000..cdac633
--- /dev/null
+++ b/vector-drawable-tool/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'java'
+apply plugin: 'application'
+
+mainClassName = 'com.android.ide.common.vectordrawable.VdCommandLineTool'
+
+archivesBaseName = 'vd-tool'
+project.distZip.baseName = archivesBaseName
+project.startScripts.applicationName = archivesBaseName
+
+dependencies {
+ compile project(':base:sdk-common')
+ testCompile project(':base:testutils')
+ testCompile 'junit:junit:4.12'
+}
diff --git a/vector-drawable-tool/src/main/java/com/android/ide/common/vectordrawable/VdCommandLineOptions.java b/vector-drawable-tool/src/main/java/com/android/ide/common/vectordrawable/VdCommandLineOptions.java
new file mode 100644
index 0000000..f77c348
--- /dev/null
+++ b/vector-drawable-tool/src/main/java/com/android/ide/common/vectordrawable/VdCommandLineOptions.java
@@ -0,0 +1,215 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import java.io.File;
+import java.util.Arrays;
+
+public class VdCommandLineOptions {
+ // Support a command line tool to convert or show the VectorDrawable.
+ // Below are the options or information for this tool.
+
+ private static final String OPTION_CONVERT = "-c";
+
+ private static final String OPTION_DISPLAY = "-d";
+
+ private static final String OPTION_IN = "-in";
+
+ private static final String OPTION_OUT = "-out";
+
+ private static final String OPTION_FORCE_WIDTH_DP = "-widthDp";
+
+ private static final String OPTION_FORCE_HEIGHT_DP = "-heightDp";
+
+ private static final String OPTION_ADD_HEADER = "-addHeader";
+
+ public static final String COMMAND_LINE_OPTION = "Converts SVG files to VectorDrawable XML files.\n"
+ + "Displays VectorDrawables.\n"
+ + "Usage: [-c] [-d] [-in <file or directory>] [-out <directory>] [-widthDp <size>] "
+ + "[-heightDp <size>] [-addHeader]\n"
+ + "Options:\n"
+ + " -in <file or directory>: If -c is specified, Converts the given .svg file \n"
+ + " to VectorDrawable XML, or if a directory is specified,\n"
+ + " all .svg files in the given directory. Otherwise, if -d\n"
+ + " is specified, displays the given VectorDrawable XML file\n"
+ + " or all VectorDrawables in the given directory.\n"
+ + " -out <directory> If specified, write converted files out to the given\n"
+ + " directory, which must exist. If not specified the\n"
+ + " converted files will be written to the directory\n"
+ + " containing the input files.\n"
+ + " -c If present, SVG files are converted to VectorDrawable XML\n"
+ + " and written out.\n"
+ + " -d Displays the given VectorDrawable(s), or if -c is\n"
+ + " specified the results of the conversion.\n"
+ + " -widthDp <size> Force the width to be <size> dp, <size> must be integer\n"
+ + " -heightDp <size> Force the height to be <size> dp, <size> must be integer\n"
+ + " -addHeader Add AOSP header to the top of the generated XML file\n"
+ + "Examples: \n"
+ + " 1) Convert SVG files from <directory> into XML files at the same directory"
+ + " and visualize the XML file results:\n"
+ + " vd-tool -c -d -in <directory> \n"
+ + " 2) Convert SVG file and visualize the XML file results:\n"
+ + " vd-tool -c -d -in file.svg \n"
+ + " 3) Display VectorDrawable's XML files from <directory>:\n"
+ + " vd-tool -d -in <directory> \n"
+ ;
+
+ private boolean mConvertSvg;
+
+ private File[] mInputFiles;
+
+ private File mOutputDir;
+
+ private boolean mDisplayXml;
+
+ public int getForceWidth() {
+ return mForceWidth;
+ }
+
+ public int getForceHeight() {
+ return mForceHeight;
+ }
+
+ public boolean isAddHeader() {
+ return mAddHeader;
+ }
+
+ private int mForceWidth = -1;
+
+ private int mForceHeight = -1;
+
+ private boolean mAddHeader = false;
+
+ public boolean getDisplayXml() {
+ return mDisplayXml;
+ }
+
+ public boolean getConvertSvg() {
+ return mConvertSvg;
+ }
+
+ public File[] getInputFiles() {
+ return mInputFiles;
+ }
+
+ public File getOutputDir() {
+ return mOutputDir;
+ }
+
+ /**
+ * Parse the command line options.
+ *
+ * @param args the incoming command line options
+ * @return null if no critical error happens, otherwise the error message.
+ */
+ public String parse(String[] args) {
+ File argIn = null;
+ mOutputDir = null;
+ mConvertSvg = false;
+ mDisplayXml = false;
+
+ // First parse the command line options.
+ if (args != null && args.length > 0) {
+ int index = 0;
+ while (index < args.length) {
+ String currentArg = args[index];
+ if (OPTION_CONVERT.equalsIgnoreCase(currentArg)) {
+ System.out.println(OPTION_CONVERT + " parsed, so we will convert the SVG files");
+ mConvertSvg = true;
+ } else if (OPTION_DISPLAY.equalsIgnoreCase(currentArg)) {
+ System.out.println(OPTION_DISPLAY + " parsed, so we will display the XML files");
+ mDisplayXml = true;
+ } else if (OPTION_IN.equalsIgnoreCase(currentArg)) {
+ if ((index + 1) < args.length) {
+ argIn = new File(args[index + 1]);
+ System.out.println(OPTION_IN + " parsed " + argIn.getAbsolutePath());
+ index++;
+ }
+ } else if (OPTION_OUT.equalsIgnoreCase(currentArg)) {
+ if ((index + 1) < args.length) {
+ mOutputDir = new File(args[index + 1]
+ .replaceFirst("^~", System.getProperty("user.home")));
+ System.out.println(OPTION_OUT + " parsed " + mOutputDir.getAbsolutePath());
+ index++;
+ }
+ } else if (OPTION_FORCE_WIDTH_DP.equalsIgnoreCase(currentArg)) {
+ if ((index + 1) < args.length) {
+ mForceWidth = Integer.parseInt(args[index + 1]);
+ System.out.println(OPTION_FORCE_WIDTH_DP + " parsed " + mForceWidth);
+ index++;
+ }
+ } else if (OPTION_FORCE_HEIGHT_DP.equalsIgnoreCase(currentArg)) {
+ if ((index + 1) < args.length) {
+ mForceHeight = Integer.parseInt(args[index + 1]);
+ System.out.println(OPTION_FORCE_HEIGHT_DP + " parsed " + mForceHeight);
+ index++;
+ }
+ } else if (OPTION_ADD_HEADER.equalsIgnoreCase(currentArg)) {
+ mAddHeader = true;
+ System.out.println(OPTION_ADD_HEADER + " parsed, add AOSP header to the XML file");
+ } else {
+ return "ERROR: unrecognized option " + currentArg;
+ }
+ index++;
+ }
+ } else {
+ return "ERROR: empty arguments";
+ }
+
+ if (!mConvertSvg && !mDisplayXml) {
+ return "ERROR: either " + OPTION_CONVERT + " or " + OPTION_DISPLAY + " must be specified";
+ }
+ // Then we decide the input resources.
+ mInputFiles = null;
+ if (argIn != null) {
+ if (argIn.isFile()) {
+ mInputFiles = new File[1];
+ mInputFiles[0] = argIn;
+ if (mOutputDir == null && mConvertSvg) {
+ mOutputDir = argIn.getParentFile();
+ }
+ } else if (argIn.isDirectory()) {
+ File parsedSVGDir = argIn;
+ mInputFiles = parsedSVGDir.listFiles();
+ // Sort the files by the name.
+ Arrays.sort(mInputFiles);
+ if (mOutputDir == null && mConvertSvg) {
+ mOutputDir = argIn;
+ }
+ }
+ } else {
+ return "ERROR: no input files argument";
+ }
+
+ if (mConvertSvg) {
+ if (mOutputDir != null) {
+ if (!mOutputDir.isDirectory()) {
+ return ("ERROR: Output directory " + mOutputDir.getAbsolutePath()
+ + " doesn't exist or isn't a valid directory");
+ }
+ } else {
+ return "ERROR: no output directory specified";
+ }
+ }
+
+ if (mInputFiles != null && mInputFiles.length == 0) {
+ return "ERROR: There is no file to process in " + argIn.getName();
+ }
+
+ return null;
+ }
+}
diff --git a/vector-drawable-tool/src/main/java/com/android/ide/common/vectordrawable/VdCommandLineTool.java b/vector-drawable-tool/src/main/java/com/android/ide/common/vectordrawable/VdCommandLineTool.java
new file mode 100644
index 0000000..d2b596b
--- /dev/null
+++ b/vector-drawable-tool/src/main/java/com/android/ide/common/vectordrawable/VdCommandLineTool.java
@@ -0,0 +1,263 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.SdkConstants;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+
+import java.awt.*;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableCellRenderer;
+
+/**
+ * Support a command line tool to convert SVG files to VectorDrawables and display them.
+ */
+public class VdCommandLineTool {
+
+ // Show Vector Drawables in a table, below are the parameters for the table.
+ private static final int COLUMN_NUMBER = 8;
+
+ private static final int ROW_HEIGHT = 80;
+
+ private static final Dimension MAX_WINDOW_SIZE = new Dimension(600, 600);
+
+ public static final String BROKEN_FILE_EXTENSION = ".broken";
+
+ private static final boolean DBG_COPY_BROKEN_SVG = false;
+
+ private static void exitWithErrorMessage(String message) {
+ System.err.println(message);
+ System.exit(-1);;
+ }
+
+ public static void main(String[] args) {
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String criticalError = options.parse(args);
+ if (criticalError != null) {
+ exitWithErrorMessage(criticalError + "\n\n" + VdCommandLineOptions.COMMAND_LINE_OPTION);
+ }
+
+ boolean needsConvertSvg = options.getConvertSvg();
+ boolean needsDisplayXml = options.getDisplayXml();
+
+ File[] filesToDisplay = options.getInputFiles();
+ if (needsConvertSvg) {
+ filesToDisplay = convertSVGToXml(options);
+ }
+ if (needsDisplayXml) {
+ displayXmlAsync(filesToDisplay);
+ }
+ }
+
+ private static void displayXmlAsync(final File[] displayFiles) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ displayXml(displayFiles);
+ }
+ });
+ }
+
+ private static class MyTableModel extends AbstractTableModel {
+
+ private VdIcon[] mIconList;
+
+ public MyTableModel(VdIcon[] iconList) {
+ mIconList = iconList;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return null;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ int index = rowIndex * COLUMN_NUMBER + columnIndex;
+ if (index < 0) {
+ return null;
+ }
+ return mIconList.length > index ? mIconList[index] : null;
+ }
+
+ @Override
+ public int getRowCount() {
+ return mIconList.length / COLUMN_NUMBER +
+ ((mIconList.length % COLUMN_NUMBER == 0) ? 0 : 1);
+ }
+
+ @Override
+ public int getColumnCount() {
+ return Math.min(COLUMN_NUMBER, mIconList.length);
+ }
+
+ @Override
+ public Class<?> getColumnClass(int column) {
+ return Icon.class;
+ }
+ }
+
+ private static void displayXml(File[] inputFiles) {
+ ArrayList<VdIcon> iconList = new ArrayList<VdIcon>();
+ int validXmlFileCounter = 0;
+ for (int i = 0; i < inputFiles.length; i++) {
+ File xmlFile = inputFiles[i];
+ if (!xmlFile.isFile() || xmlFile.length() == 0) {
+ continue;
+ }
+ String xmlFilename = xmlFile.getName();
+ if (xmlFilename == null || xmlFilename.isEmpty()) {
+ continue;
+ }
+ if (!xmlFilename.endsWith(SdkConstants.DOT_XML)) {
+ continue;
+ }
+
+ try {
+ VdIcon icon = new VdIcon(inputFiles[i].toURL());
+ icon.enableCheckerBoardBackground(true);
+ iconList.add(icon);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ validXmlFileCounter++;
+ }
+ System.out.println("Showing " + validXmlFileCounter + " valid icons");
+
+ MyTableModel model = new MyTableModel(iconList.toArray(new VdIcon[iconList.size()]));
+
+ JTable table = new JTable(model) {
+ @Override
+ public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
+ JComponent c = (JComponent)super.prepareRenderer(renderer, row, column);
+ VdIcon icon = (VdIcon)getValueAt(row, column);
+ c.setToolTipText(icon != null ? icon.getName() : null);
+ return c;
+ }
+ };
+ table.setOpaque(false);
+ table.setPreferredScrollableViewportSize(MAX_WINDOW_SIZE);
+ table.setRowHeight(ROW_HEIGHT);
+ table.setSelectionModel(new DefaultListSelectionModel() {
+ @Override
+ public void setSelectionInterval(int index0, int index1) {
+ // do nothing
+ }
+ });
+ JFrame frame = new JFrame();
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ if (table.getPreferredSize().getHeight() > MAX_WINDOW_SIZE.getHeight()) {
+ JScrollPane pane = new JScrollPane(table);
+ frame.getContentPane().add(pane);
+ } else {
+ frame.getContentPane().add(table);
+ }
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ private final static String AOSP_HEADER = "<!--\n" +
+ "Copyright (C) " + Calendar.getInstance().get(Calendar.YEAR) +
+ " The Android Open Source Project\n\n" +
+ " Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
+ " you may not use this file except in compliance with the License.\n" +
+ " You may obtain a copy of the License at\n\n" +
+
+ " http://www.apache.org/licenses/LICENSE-2.0\n\n" +
+
+ " Unless required by applicable law or agreed to in writing, software\n" +
+ " distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
+ " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
+ " See the License for the specific language governing permissions and\n" +
+ " limitations under the License.\n" +
+ "-->\n";
+
+
+ private static File[] convertSVGToXml(VdCommandLineOptions options) {
+ File[] inputSVGFiles = options.getInputFiles();
+ File outputDir = options.getOutputDir();
+ int totalSvgFileCounter = 0;
+ int errorSvgFileCounter = 0;
+ ArrayList<File> allOutputFiles = new ArrayList<File>();
+ for (int i = 0; i < inputSVGFiles.length; i++) {
+ String svgFilename = inputSVGFiles[i].getName();
+ if (!svgFilename.endsWith(SdkConstants.DOT_SVG)) {
+ continue;
+ }
+ String svgFilenameWithoutExtension = svgFilename.substring(0, svgFilename.lastIndexOf("."));
+
+ File outputFile = new File(outputDir, svgFilenameWithoutExtension + SdkConstants.DOT_XML);
+ allOutputFiles.add(outputFile);
+ try {
+ ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream();
+
+ String error = Svg2Vector
+ .parseSvgToXml(inputSVGFiles[i], byteArrayOutStream);
+
+ if (error != null && !error.isEmpty()) {
+ errorSvgFileCounter++;
+ System.err.println("error is " + error);
+ if (DBG_COPY_BROKEN_SVG) {
+ // Copy the broken svg file in the same directory but with a new extension.
+ String brokenFileName = svgFilename + BROKEN_FILE_EXTENSION;
+ File brokenSvgFile = new File(outputDir, brokenFileName);
+ Files.copy(inputSVGFiles[i], brokenSvgFile);
+ }
+ }
+
+ // Override the size info if needed. Negative value will be ignored.
+ String vectorXmlContent = byteArrayOutStream.toString();
+ if (options.getForceHeight() > 0 || options.getForceWidth() > 0) {
+ Document vdDocument = VdPreview.parseVdStringIntoDocument(vectorXmlContent, null);
+
+ VdOverrideInfo info = new VdOverrideInfo(options.getForceWidth(), options.getForceHeight(),
+ -1 /* opacity */, false /*auto mirrored*/);
+ vectorXmlContent = VdPreview.overrideXmlContent(vdDocument, info, null);
+ }
+ if (options.isAddHeader()) {
+ vectorXmlContent = AOSP_HEADER + vectorXmlContent;
+ }
+
+ // Write the final result into the output XML file.
+ PrintWriter writer = new PrintWriter(outputFile);
+ writer.print(vectorXmlContent);
+ writer.close();
+ } catch (Exception e) {
+ System.err.println("exception" + e.getMessage());
+ e.printStackTrace();
+ }
+ totalSvgFileCounter++;
+ }
+
+ System.out.println("Convert " + totalSvgFileCounter + " SVG files in total, errors found in "
+ + errorSvgFileCounter + " files");
+ return allOutputFiles.toArray(new File[allOutputFiles.size()]);
+ }
+}
diff --git a/vector-drawable-tool/src/test/java/com/android/ide/common/vectordrawable/VdCommandLineOptionsTest.java b/vector-drawable-tool/src/test/java/com/android/ide/common/vectordrawable/VdCommandLineOptionsTest.java
new file mode 100644
index 0000000..a1ad21f
--- /dev/null
+++ b/vector-drawable-tool/src/test/java/com/android/ide/common/vectordrawable/VdCommandLineOptionsTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.ide.common.vectordrawable;
+
+import com.android.annotations.NonNull;
+import com.android.testutils.TestUtils;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class VdCommandLineOptionsTest extends TestCase {
+ @NonNull
+ private String getTestFolderPath() {
+ File testFolder = TestUtils.getRoot("vectordrawable");
+ return testFolder.getAbsolutePath();
+ }
+
+ private String getInvalidFolderPath() {
+ File testFolder = TestUtils.getRoot("vectordrawable");
+ return testFolder.getAbsolutePath() + File.separator + "_NOT_EXIST_FOLDER";
+ }
+
+ public void testCommandlineOptionsFull() {
+ // Use a known directory for testing purpose.
+ String testFolderPath = getTestFolderPath();
+
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = { "-c", "-d", "-in", testFolderPath, "-out", testFolderPath};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error == null);
+ TestCase.assertTrue(options.getDisplayXml());
+ TestCase.assertTrue(options.getConvertSvg());
+ TestCase.assertTrue(options.getOutputDir() != null);
+ TestCase.assertTrue(options.getInputFiles() != null);
+ }
+
+ public void testCommandlineOptionsDisplayOnly() {
+ // Use a known directory for testing purpose.
+ String testFolderPath = getTestFolderPath();
+
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = {"-d", "-in", testFolderPath};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error == null);
+ TestCase.assertTrue(options.getDisplayXml());
+ TestCase.assertFalse(options.getConvertSvg());
+ TestCase.assertTrue(options.getOutputDir() == null);
+ TestCase.assertTrue(options.getInputFiles() != null);
+ }
+
+ public void testCommandlineOptionsConvertOnly() {
+ // Use a known directory for testing purpose.
+ String testFolderPath = getTestFolderPath();
+
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = {"-c", "-in", testFolderPath};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error == null);
+ TestCase.assertFalse(options.getDisplayXml());
+ TestCase.assertTrue(options.getConvertSvg());
+ TestCase.assertTrue(options.getOutputDir() != null);
+ TestCase.assertTrue(options.getInputFiles() != null);
+ }
+
+ public void testCommandlineOptionsExtras() {
+ // Use a known directory for testing purpose.
+ String testFolderPath = getTestFolderPath();
+
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = {"-c", "-in", testFolderPath, "-widthdp", "23", "-heightdp", "10", "-addHeader"};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error == null);
+ TestCase.assertFalse(options.getDisplayXml());
+ TestCase.assertTrue(options.getConvertSvg());
+ TestCase.assertTrue(options.getOutputDir() != null);
+ TestCase.assertTrue(options.getInputFiles() != null);
+ TestCase.assertTrue(options.getForceWidth() == 23);
+ TestCase.assertTrue(options.getForceHeight() == 10);
+ TestCase.assertTrue(options.isAddHeader());
+ }
+
+ // Below are the negative test cases.
+ public void testCommandlineOptionsInvalidOption() {
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = { "-a"};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error != null);
+ }
+
+ public void testCommandlineOptionsMissingAction() {
+ String testFolderPath = getTestFolderPath();
+
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = {"-in", testFolderPath, "-out", testFolderPath};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error != null);
+ }
+
+ public void testCommandlineOptionsInvalidInputPath() {
+ String invalidFolderPath = getInvalidFolderPath();
+
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = {"-c", "-in", invalidFolderPath};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error != null);
+ }
+
+ public void testCommandlineOptionsInvalidOutputPath() {
+ String invalidFolderPath = getInvalidFolderPath();
+
+ VdCommandLineOptions options = new VdCommandLineOptions();
+ String[] args = {"-c", "-out", invalidFolderPath};
+ String error = options.parse(args);
+
+ TestCase.assertTrue(error != null);
+ }
+}
diff --git a/base/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.svg b/vector-drawable-tool/src/test/resources/testData/vectordrawable/ic_shapes.svg
similarity index 100%
rename from base/sdk-common/src/test/resources/testData/vectordrawable/ic_shapes.svg
rename to vector-drawable-tool/src/test/resources/testData/vectordrawable/ic_shapes.svg
diff --git a/vector-drawable-tool/vector-drawable-tool.iml b/vector-drawable-tool/vector-drawable-tool.iml
new file mode 100644
index 0000000..9f0e3dc
--- /dev/null
+++ b/vector-drawable-tool/vector-drawable-tool.iml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="sdk-common-base" />
+ <orderEntry type="module" module-name="testutils" />
+ </component>
+</module>
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/android-platform-tools-base.git
More information about the pkg-java-commits
mailing list